Skip to content

Commit

Permalink
Merge pull request #127 from timja/JENKINS-68116-metrics-for-event-queue
Browse files Browse the repository at this point in the history
 JENKINS-68116 Export metrics for scm event queue performance
  • Loading branch information
jtnord authored Apr 14, 2022
2 parents e4a6735 + a8ae36b commit 6a81757
Show file tree
Hide file tree
Showing 2 changed files with 125 additions and 52 deletions.
43 changes: 40 additions & 3 deletions src/main/java/jenkins/scm/api/SCMEvent.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import java.util.Date;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
Expand Down Expand Up @@ -135,6 +136,7 @@ public abstract class SCMEvent<P> {
* The scheduled executor thread pool. This is initialized lazily since it may be never needed.
*/
private static ScheduledExecutorService executorService;
private static ScheduledThreadPoolExecutor threadPoolExecutor;

/**
* Constructor to use when the timestamp is available from the external SCM.
Expand Down Expand Up @@ -210,13 +212,48 @@ protected SCMEvent(SCMEvent<P> copy) {
@NonNull
protected static synchronized ScheduledExecutorService executorService() {
if (executorService == null) {
// corePoolSize is set to 10, but will only be created if needed.
// ScheduledThreadPoolExecutor "acts as a fixed-sized pool using corePoolSize threads"
executorService = new ImpersonatingScheduledExecutorService(new ScheduledThreadPoolExecutor(10, new NamingThreadFactory(new ClassLoaderSanityThreadFactory(new DaemonThreadFactory()), "SCMEvent")), ACL.SYSTEM);
threadPoolExecutor = new ScheduledThreadPoolExecutor(
10,
new NamingThreadFactory(
new ClassLoaderSanityThreadFactory(new DaemonThreadFactory()), "SCMEvent")
);
executorService = new ImpersonatingScheduledExecutorService(
threadPoolExecutor,
ACL.SYSTEM2);
}

return executorService;
}

public static EventQueueMetrics getEventProcessingMetrics() {
return new EventQueueMetrics(threadPoolExecutor);
}

public static class EventQueueMetrics {

private final ThreadPoolExecutor executor;

public EventQueueMetrics(ThreadPoolExecutor executor) {
this.executor = executor;
}

public int getPoolSize() {
return executor == null ? 0 : executor.getPoolSize();
}

public int getActiveThreads() {
return executor == null ? 0 : executor.getActiveCount();
}

public int getQueuedTasks() {
return executor == null ? 0 : executor.getQueue().size();
}

public long getCompletedTasks() {
return executor == null ? 0 : executor.getCompletedTaskCount();
}
}


/**
* Shutdown the timer and throw it away.
Expand Down
134 changes: 85 additions & 49 deletions src/test/java/jenkins/scm/api/SCMEventTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,14 @@
package jenkins.scm.api;

import edu.umd.cs.findbugs.annotations.NonNull;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import javax.servlet.http.HttpServletRequest;
import org.junit.Test;
import org.junit.experimental.theories.DataPoints;
import org.junit.experimental.theories.Theories;
import org.junit.experimental.theories.Theory;
import org.junit.runner.RunWith;
import org.mockito.Mockito;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.allOf;
Expand All @@ -44,6 +45,7 @@
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.Matchers.sameInstance;
import static org.mockito.Mockito.*;

@RunWith(Theories.class)
public class SCMEventTest {
Expand All @@ -59,6 +61,40 @@ public void executorService() throws Exception {
assertThat(SCMEvent.executorService().isShutdown(), is(false));
}

@Test
public void eventProcessingMetricsReturnsZeroWhenNoEvents() throws Exception {
SCMEvent.EventQueueMetrics eventProcessingMetrics = SCMEvent.getEventProcessingMetrics();
assertThat(eventProcessingMetrics, notNullValue());
assertThat(eventProcessingMetrics.getPoolSize(), is(0));
assertThat(eventProcessingMetrics.getActiveThreads(), is(0));
assertThat(eventProcessingMetrics.getQueuedTasks(), is(0));
assertThat(eventProcessingMetrics.getCompletedTasks(), is(0L));
}

@Test
public void eventProcessingMetrics() throws Exception {

ThreadPoolExecutor executor = mock(ThreadPoolExecutor.class);
when(executor.getPoolSize()).thenReturn(20);
when(executor.getActiveCount()).thenReturn(30);

@SuppressWarnings("unchecked")
BlockingQueue<Runnable> queue = (BlockingQueue<Runnable>) mock(BlockingQueue.class);

when(queue.size()).thenReturn(500);

when(executor.getQueue()).thenReturn(queue);
when(executor.getCompletedTaskCount()).thenReturn(1000L);


SCMEvent.EventQueueMetrics eventProcessingMetrics = new SCMEvent.EventQueueMetrics(executor);
assertThat(eventProcessingMetrics, notNullValue());
assertThat(eventProcessingMetrics.getPoolSize(), is(20));
assertThat(eventProcessingMetrics.getActiveThreads(), is(30));
assertThat(eventProcessingMetrics.getQueuedTasks(), is(500));
assertThat(eventProcessingMetrics.getCompletedTasks(), is(1000L));
}

@Theory
public void getType(SCMEvent.Type type) throws Exception {
assertThat(new MySCMEvent(type, new Object()).getType(), is(type));
Expand Down Expand Up @@ -139,79 +175,79 @@ public void originOfNull() throws Exception {

@Test
public void originOfSimpleRequest() throws Exception {
HttpServletRequest req = Mockito.mock(HttpServletRequest.class);
Mockito.when(req.getScheme()).thenReturn("http");
Mockito.when(req.getServerName()).thenReturn("jenkins.example.com");
Mockito.when(req.getRequestURI()).thenReturn("/jenkins/notify");
Mockito.when(req.getLocalPort()).thenReturn(80);
Mockito.when(req.getRemoteHost()).thenReturn("scm.example.com");
Mockito.when(req.getRemoteAddr()).thenReturn("203.0.113.1");
HttpServletRequest req = mock(HttpServletRequest.class);
when(req.getScheme()).thenReturn("http");
when(req.getServerName()).thenReturn("jenkins.example.com");
when(req.getRequestURI()).thenReturn("/jenkins/notify");
when(req.getLocalPort()).thenReturn(80);
when(req.getRemoteHost()).thenReturn("scm.example.com");
when(req.getRemoteAddr()).thenReturn("203.0.113.1");
assertThat(SCMEvent.originOf(req), is("scm.example.com/203.0.113.1 ⇒ http://jenkins.example.com/jenkins/notify"));
}

@Test
public void originOfSimpleTLSRequest() throws Exception {
HttpServletRequest req = Mockito.mock(HttpServletRequest.class);
Mockito.when(req.getScheme()).thenReturn("https");
Mockito.when(req.getServerName()).thenReturn("jenkins.example.com");
Mockito.when(req.getRequestURI()).thenReturn("/jenkins/notify");
Mockito.when(req.getLocalPort()).thenReturn(443);
Mockito.when(req.getRemoteHost()).thenReturn("scm.example.com");
Mockito.when(req.getRemoteAddr()).thenReturn("203.0.113.1");
HttpServletRequest req = mock(HttpServletRequest.class);
when(req.getScheme()).thenReturn("https");
when(req.getServerName()).thenReturn("jenkins.example.com");
when(req.getRequestURI()).thenReturn("/jenkins/notify");
when(req.getLocalPort()).thenReturn(443);
when(req.getRemoteHost()).thenReturn("scm.example.com");
when(req.getRemoteAddr()).thenReturn("203.0.113.1");
assertThat(SCMEvent.originOf(req), is("scm.example.com/203.0.113.1 ⇒ https://jenkins.example.com/jenkins/notify"));
}

@Test
public void originOfSimpleRequestNonStdPort() throws Exception {
HttpServletRequest req = Mockito.mock(HttpServletRequest.class);
Mockito.when(req.getScheme()).thenReturn("http");
Mockito.when(req.getServerName()).thenReturn("jenkins.example.com");
Mockito.when(req.getRequestURI()).thenReturn("/jenkins/notify");
Mockito.when(req.getLocalPort()).thenReturn(8080);
Mockito.when(req.getRemoteHost()).thenReturn("scm.example.com");
Mockito.when(req.getRemoteAddr()).thenReturn("203.0.113.1");
HttpServletRequest req = mock(HttpServletRequest.class);
when(req.getScheme()).thenReturn("http");
when(req.getServerName()).thenReturn("jenkins.example.com");
when(req.getRequestURI()).thenReturn("/jenkins/notify");
when(req.getLocalPort()).thenReturn(8080);
when(req.getRemoteHost()).thenReturn("scm.example.com");
when(req.getRemoteAddr()).thenReturn("203.0.113.1");
assertThat(SCMEvent.originOf(req), is("scm.example.com/203.0.113.1 ⇒ http://jenkins.example.com:8080/jenkins/notify"));
}

@Test
public void originOfSimpleTLSRequestNonStdPort() throws Exception {
HttpServletRequest req = Mockito.mock(HttpServletRequest.class);
Mockito.when(req.getScheme()).thenReturn("https");
Mockito.when(req.getServerName()).thenReturn("jenkins.example.com");
Mockito.when(req.getRequestURI()).thenReturn("/jenkins/notify");
Mockito.when(req.getLocalPort()).thenReturn(8443);
Mockito.when(req.getRemoteHost()).thenReturn("scm.example.com");
Mockito.when(req.getRemoteAddr()).thenReturn("203.0.113.1");
HttpServletRequest req = mock(HttpServletRequest.class);
when(req.getScheme()).thenReturn("https");
when(req.getServerName()).thenReturn("jenkins.example.com");
when(req.getRequestURI()).thenReturn("/jenkins/notify");
when(req.getLocalPort()).thenReturn(8443);
when(req.getRemoteHost()).thenReturn("scm.example.com");
when(req.getRemoteAddr()).thenReturn("203.0.113.1");
assertThat(SCMEvent.originOf(req), is("scm.example.com/203.0.113.1 ⇒ https://jenkins.example.com:8443/jenkins/notify"));
}

@Test
public void originOfForwardedRequestSingleHop() throws Exception {
HttpServletRequest req = Mockito.mock(HttpServletRequest.class);
Mockito.when(req.getScheme()).thenReturn("http");
Mockito.when(req.getServerName()).thenReturn("jenkins.example.com");
Mockito.when(req.getHeader("X-Forwarded-Proto")).thenReturn("https");
Mockito.when(req.getHeader("X-Forwarded-Port")).thenReturn("443");
Mockito.when(req.getHeader("X-Forwarded-For")).thenReturn("scm.example.com");
Mockito.when(req.getRequestURI()).thenReturn("/jenkins/notify");
Mockito.when(req.getLocalPort()).thenReturn(8080);
Mockito.when(req.getRemoteHost()).thenReturn("proxy.example.com");
Mockito.when(req.getRemoteAddr()).thenReturn("203.0.113.1");
HttpServletRequest req = mock(HttpServletRequest.class);
when(req.getScheme()).thenReturn("http");
when(req.getServerName()).thenReturn("jenkins.example.com");
when(req.getHeader("X-Forwarded-Proto")).thenReturn("https");
when(req.getHeader("X-Forwarded-Port")).thenReturn("443");
when(req.getHeader("X-Forwarded-For")).thenReturn("scm.example.com");
when(req.getRequestURI()).thenReturn("/jenkins/notify");
when(req.getLocalPort()).thenReturn(8080);
when(req.getRemoteHost()).thenReturn("proxy.example.com");
when(req.getRemoteAddr()).thenReturn("203.0.113.1");
assertThat(SCMEvent.originOf(req), is("scm.example.com → proxy.example.com/203.0.113.1 ⇒ https://jenkins.example.com/jenkins/notify"));
}

@Test
public void originOfForwardedRequestMultiHop() throws Exception {
HttpServletRequest req = Mockito.mock(HttpServletRequest.class);
Mockito.when(req.getScheme()).thenReturn("http");
Mockito.when(req.getServerName()).thenReturn("jenkins.example.com");
Mockito.when(req.getHeader("X-Forwarded-Proto")).thenReturn("https");
Mockito.when(req.getHeader("X-Forwarded-Port")).thenReturn("443");
Mockito.when(req.getHeader("X-Forwarded-For")).thenReturn("scm.example.com, gateway.example.com, proxy.example.com");
Mockito.when(req.getRequestURI()).thenReturn("/jenkins/notify");
Mockito.when(req.getRemotePort()).thenReturn(8080);
Mockito.when(req.getRemoteHost()).thenReturn(null);
Mockito.when(req.getRemoteAddr()).thenReturn("203.0.113.1");
HttpServletRequest req = mock(HttpServletRequest.class);
when(req.getScheme()).thenReturn("http");
when(req.getServerName()).thenReturn("jenkins.example.com");
when(req.getHeader("X-Forwarded-Proto")).thenReturn("https");
when(req.getHeader("X-Forwarded-Port")).thenReturn("443");
when(req.getHeader("X-Forwarded-For")).thenReturn("scm.example.com, gateway.example.com, proxy.example.com");
when(req.getRequestURI()).thenReturn("/jenkins/notify");
when(req.getRemotePort()).thenReturn(8080);
when(req.getRemoteHost()).thenReturn(null);
when(req.getRemoteAddr()).thenReturn("203.0.113.1");
assertThat(SCMEvent.originOf(req), is("scm.example.com → gateway.example.com → proxy.example.com → 203.0.113.1 ⇒ https://jenkins.example.com/jenkins/notify"));
}

Expand Down

0 comments on commit 6a81757

Please sign in to comment.