package com.twitter.common.net.http.handlers.pprof; import java.io.IOException; import java.io.OutputStream; import java.lang.reflect.InvocationTargetException; import java.util.logging.Level; import java.util.logging.Logger; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.google.common.io.Closeables; import com.twitter.common.net.http.handlers.HttpServletRequestParams; import com.twitter.common.quantity.Amount; import com.twitter.common.quantity.Data; /** * A handler that collects heap allocation profile for the running application * using the Heapster agent lib, and replies in a format recognizable by * gperftools: http://code.google.com/p/gperftools */ public class HeapProfileHandler extends HttpServlet { private static final Logger LOG = Logger.getLogger(HeapProfileHandler.class.getName()); private final HeapsterHelper heapster; public HeapProfileHandler() { super(); HeapsterHelper helper = null; try { helper = new HeapsterHelper(ClassLoader.getSystemClassLoader().loadClass("Heapster")); } catch (ClassNotFoundException e) { // If your runtime environment cannot load the class, the profiler will return an // error message each time it is used. LOG.warning("Continuing without heapster profiling, could not load Heapster class: " + e); } this.heapster = helper; } /** * HeapsterHelper uses reflection to provide more readable access to the Heapster class, * which we dynamically class load. Reflection is necessary as the Heapster class will * likely not be on the classpath. */ private class HeapsterHelper { final Class klass; private HeapsterHelper(Class klass) { this.klass = klass; } private void start() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException { klass.getDeclaredMethod("start").invoke(null); } private void stop() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException { klass.getDeclaredMethod("stop").invoke(null); } private byte[] dumpProfile(Boolean forceGC) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException { return (byte[]) klass.getDeclaredMethod("dumpProfile", Boolean.class).invoke(null, forceGC); } private void clearProfile() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException { klass.getDeclaredMethod("clearProfile").invoke(null); } private void setSamplingPeriod(Integer period) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException { klass.getDeclaredMethod("setSamplingPeriod", Integer.class).invoke(null, period); } private byte[] profile(int durationSeconds, int samplingPeriodBytes, boolean forceGC) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException, InterruptedException { clearProfile(); setSamplingPeriod(samplingPeriodBytes); start(); Thread.sleep(durationSeconds * 1000L); stop(); return dumpProfile(forceGC); } } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // This will happen if the class loader failed to load Heapster at construction time. if (heapster == null) { resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Heapster not loaded!"); return; } // Default duration is 10 seconds, default period is 5MB. final int profileDurationSecs = HttpServletRequestParams.getInt(req, "seconds", 10); final int profileSamplingPeriodBytes = HttpServletRequestParams.getInt(req, "sample_period", Amount.of(5, Data.MB).as(Data.BYTES)); final boolean forceGC = HttpServletRequestParams.getBool(req, "force_gc", true); LOG.info("Collecting heap allocation profile for " + profileDurationSecs + " seconds every " + profileSamplingPeriodBytes + " bytes, with forceGC? " + forceGC); byte[] profileBytes; try { profileBytes = heapster.profile(profileDurationSecs, profileSamplingPeriodBytes, forceGC); LOG.info("Profile contains " + profileBytes.length + " bytes"); } catch (Exception e) { LOG.log(Level.WARNING, "Exception while profiling", e); resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage()); return; } resp.setHeader("Content-Type", "pprof/raw"); resp.setStatus(HttpServletResponse.SC_OK); OutputStream responseBody = resp.getOutputStream(); try { responseBody.write(profileBytes); } finally { Closeables.close(responseBody, /* swallowIOException */ true); } } }