Skip to content

Commit

Permalink
Add CPU profiling support to the AdminServlet (fixes dropwizard#927)
Browse files Browse the repository at this point in the history
  • Loading branch information
jplock committed Apr 1, 2016
1 parent 5bf3464 commit bbb52c1
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 3 deletions.
5 changes: 5 additions & 0 deletions metrics-servlets/pom.xml
Expand Up @@ -37,6 +37,11 @@
<artifactId>metrics-jvm</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.papertrail</groupId>
<artifactId>profiler</artifactId>
<version>1.0.2</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
Expand Down
Expand Up @@ -14,27 +14,31 @@ public class AdminServlet extends HttpServlet {
public static final String DEFAULT_METRICS_URI = "/metrics";
public static final String DEFAULT_PING_URI = "/ping";
public static final String DEFAULT_THREADS_URI = "/threads";
public static final String DEFAULT_CPU_PROFILE_URI = "/pprof";

public static final String METRICS_URI_PARAM_KEY = "metrics-uri";
public static final String PING_URI_PARAM_KEY = "ping-uri";
public static final String THREADS_URI_PARAM_KEY = "threads-uri";
public static final String HEALTHCHECK_URI_PARAM_KEY = "healthcheck-uri";
public static final String SERVICE_NAME_PARAM_KEY= "service-name";
public static final String CPU_PROFILE_URI_PARAM_KEY = "cpu-profile-uri";

private static final String TEMPLATE = String.format(
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"%n" +
" \"http://www.w3.org/TR/html4/loose.dtd\">%n" +
"<html>%n" +
"<head>%n" +
" <title>Metrics{8}</title>%n" +
" <title>Metrics{10}</title>%n" +
"</head>%n" +
"<body>%n" +
" <h1>Operational Menu{8}</h1>%n" +
" <h1>Operational Menu{10}</h1>%n" +
" <ul>%n" +
" <li><a href=\"{0}{1}?pretty=true\">Metrics</a></li>%n" +
" <li><a href=\"{2}{3}\">Ping</a></li>%n" +
" <li><a href=\"{4}{5}\">Threads</a></li>%n" +
" <li><a href=\"{6}{7}?pretty=true\">Healthcheck</a></li>%n" +
" <li><a href=\"{8}{9}\">CPU Profile</a></li>%n" +
" <li><a href=\"{8}{9}?state=blocked\">CPU Contention</a></li>%n" +
" </ul>%n" +
"</body>%n" +
"</html>"
Expand All @@ -46,10 +50,12 @@ public class AdminServlet extends HttpServlet {
private transient MetricsServlet metricsServlet;
private transient PingServlet pingServlet;
private transient ThreadDumpServlet threadDumpServlet;
private transient CpuProfileServlet cpuProfileServlet;
private transient String metricsUri;
private transient String pingUri;
private transient String threadsUri;
private transient String healthcheckUri;
private transient String cpuprofileUri;
private transient String serviceName;

@Override
Expand All @@ -68,10 +74,14 @@ public void init(ServletConfig config) throws ServletException {
this.threadDumpServlet = new ThreadDumpServlet();
threadDumpServlet.init(config);

this.cpuProfileServlet = new CpuProfileServlet();
cpuProfileServlet.init(config);

this.metricsUri = getParam(config.getInitParameter(METRICS_URI_PARAM_KEY), DEFAULT_METRICS_URI);
this.pingUri = getParam(config.getInitParameter(PING_URI_PARAM_KEY), DEFAULT_PING_URI);
this.threadsUri = getParam(config.getInitParameter(THREADS_URI_PARAM_KEY), DEFAULT_THREADS_URI);
this.healthcheckUri = getParam(config.getInitParameter(HEALTHCHECK_URI_PARAM_KEY), DEFAULT_HEALTHCHECK_URI);
this.cpuprofileUri = getParam(config.getInitParameter(CPU_PROFILE_URI_PARAM_KEY), DEFAULT_CPU_PROFILE_URI);
this.serviceName = getParam(config.getInitParameter(SERVICE_NAME_PARAM_KEY), null);
}

Expand All @@ -85,7 +95,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws Se
final PrintWriter writer = resp.getWriter();
try {
writer.println(MessageFormat.format(TEMPLATE, path, metricsUri, path, pingUri, path,
threadsUri, path, healthcheckUri,
threadsUri, path, healthcheckUri, path, cpuprofileUri,
serviceName == null ? "" : " (" + serviceName + ")"));
} finally {
writer.close();
Expand All @@ -105,6 +115,8 @@ protected void service(HttpServletRequest req, HttpServletResponse resp) throws
pingServlet.service(req, resp);
} else if (uri.equals(threadsUri)) {
threadDumpServlet.service(req, resp);
} else if (uri.equals(cpuprofileUri)) {
cpuProfileServlet.service(req, resp);
} else {
resp.sendError(HttpServletResponse.SC_NOT_FOUND);
}
Expand Down
@@ -0,0 +1,81 @@
package com.codahale.metrics.servlets;

import java.io.IOException;
import java.io.OutputStream;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.joda.time.Duration;
import com.papertrail.profiler.CpuProfile;

/**
* An HTTP servlets which outputs a {@link https://github.com/gperftools/gperftools} parseable response.
*/
public class CpuProfileServlet extends HttpServlet {
private static final long serialVersionUID = -668666696530287501L;
private static final String CONTENT_TYPE = "pprof/raw";
private static final String CACHE_CONTROL = "Cache-Control";
private static final String NO_CACHE = "must-revalidate,no-cache,no-store";
private final Lock lock = new ReentrantLock();

@Override
protected void doGet(HttpServletRequest req,
HttpServletResponse resp) throws ServletException, IOException {

int duration = 10;
if (req.getParameter("duration") != null) {
try {
duration = Integer.parseInt(req.getParameter("duration"));
} catch (NumberFormatException e) {
duration = 10;
}
}

int frequency = 100;
if (req.getParameter("frequency") != null) {
try {
frequency = Integer.parseInt(req.getParameter("frequency"));
} catch (NumberFormatException e) {
frequency = 100;
}
}

final Thread.State state;
if ("blocked".equalsIgnoreCase(req.getParameter("state"))) {
state = Thread.State.BLOCKED;
}
else {
state = Thread.State.RUNNABLE;
}

resp.setStatus(HttpServletResponse.SC_OK);
resp.setHeader(CACHE_CONTROL, NO_CACHE);
resp.setContentType(CONTENT_TYPE);
final OutputStream output = resp.getOutputStream();
try {
doProfile(output, duration, frequency, state);
} finally {
output.close();
}
}

protected void doProfile(OutputStream out, int duration, int frequency, Thread.State state) throws IOException {
if (lock.tryLock()) {
try {
CpuProfile profile = CpuProfile.record(Duration.standardSeconds(duration),
frequency, state);
if (profile == null) {
throw new RuntimeException("could not create CpuProfile");
}
profile.writeGoogleProfile(out);
return;
} finally {
lock.unlock();
}
}
throw new RuntimeException("Only one profile request may be active at a time");
}
}
Expand Up @@ -50,6 +50,8 @@ public void returnsA200() throws Exception {
" <li><a href=\"/context/admin/ping\">Ping</a></li>%n" +
" <li><a href=\"/context/admin/threads\">Threads</a></li>%n" +
" <li><a href=\"/context/admin/healthcheck?pretty=true\">Healthcheck</a></li>%n" +
" <li><a href=\"/context/admin/pprof\">CPU Profile</a></li>%n" +
" <li><a href=\"/context/admin/pprof?state=blocked\">CPU Contention</a></li>%n" +
" </ul>%n" +
"</body>%n" +
"</html>%n"
Expand Down
@@ -0,0 +1,43 @@
package com.codahale.metrics.servlets;

import static org.assertj.core.api.Assertions.assertThat;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.servlet.ServletTester;
import org.junit.Before;
import org.junit.Test;

public class CpuProfileServletTest extends AbstractServletTest {

@Override
protected void setUp(ServletTester tester) {
tester.addServlet(CpuProfileServlet.class, "/pprof");
}

@Before
public void setUp() throws Exception {
request.setMethod("GET");
request.setURI("/pprof?duration=1");
request.setVersion("HTTP/1.0");

processRequest();
}

@Test
public void returns200OK() throws Exception {
assertThat(response.getStatus())
.isEqualTo(200);
}

@Test
public void returnsPprofRaw() throws Exception {
assertThat(response.get(HttpHeader.CONTENT_TYPE))
.isEqualTo("pprof/raw");
}

@Test
public void returnsUncacheable() throws Exception {
assertThat(response.get(HttpHeader.CACHE_CONTROL))
.isEqualTo("must-revalidate,no-cache,no-store");

}
}

0 comments on commit bbb52c1

Please sign in to comment.