3 import java.util.logging.Logger;
4 import java.io.DataInputStream;
5 import java.io.ByteArrayInputStream;
6 import java.io.IOException;
13 public class SamplingProfiler {
15 private static final Logger logger = Logger.getLogger(
16 SamplingProfiler.class.getName());
18 /** Pointer to native state. */
21 /** The thread that collects samples. */
22 Thread samplingThread;
24 /** Whether or not the profiler is running. */
25 boolean running = false;
26 int delayPerThread; // ms
28 /** Number of samples taken. */
31 /** Total time spent collecting samples. */
32 long totalSampleTime = 0;
34 private SamplingProfiler() {}
37 * Returns true if the profiler is running.
39 public boolean isRunning() {
44 * Starts collecting samples.
46 * @param threadsPerSecond number of threads to sample per second
48 public synchronized void start(int threadsPerSecond) {
49 if (threadsPerSecond < 1) {
50 throw new IllegalArgumentException("threadsPerSecond < 1");
53 logger.info("Starting profiler.");
55 if (samplingThread == null) {
57 samplingThread = new Thread(new Sampler());
58 samplingThread.setDaemon(true);
59 samplingThread.start();
64 delayPerThread = 1000 / threadsPerSecond;
68 * Stops sample collection.
70 public synchronized void stop() {
72 logger.info("Stopping profiler.");
78 * Captures collected samples and clears the sample set. Returns null
79 * if no data has been captured.
81 * <p>Note: The exact format is not documented because it's not set in
84 public synchronized byte[] snapshot() {
85 if (pointer == 0 || sampleCount == 0) {
89 int size = size(pointer);
90 int collisions = collisions(pointer);
92 long start = System.nanoTime();
93 byte[] bytes = snapshot(pointer);
94 long elapsed = System.nanoTime() - start;
96 logger.info("Grabbed snapshot in " + (elapsed / 1000) + "us."
97 + " Samples collected: " + sampleCount
98 + ", Average sample time (per thread): "
99 + (((totalSampleTime / sampleCount) << 10) / 1000) + "us"
100 + ", Set size: " + size
101 + ", Collisions: " + collisions);
109 * Identifies the "event thread". For a user-facing application, this
110 * might be the UI thread. For a background process, this might be the
111 * thread that processes incoming requests.
113 public synchronized void setEventThread(Thread eventThread) {
115 pointer = allocate();
117 setEventThread(pointer, eventThread);
120 /** Collects some data. Returns number of threads sampled. */
121 private static native int sample(int pointer);
123 /** Allocates native state. */
124 private static native int allocate();
126 /** Gets the number of methods in the sample set. */
127 private static native int size(int pointer);
129 /** Gets the number of collisions in the sample set. */
130 private static native int collisions(int pointer);
132 /** Captures data. */
133 private static native byte[] snapshot(int pointer);
135 /** Identifies the "event thread". */
136 private static native void setEventThread(int pointer, Thread thread);
139 * Background thread that collects samples.
141 class Sampler implements Runnable {
143 boolean firstSample = true;
146 synchronized (SamplingProfiler.this) {
148 logger.info("Stopped profiler.");
151 SamplingProfiler.this.wait();
152 } catch (InterruptedException e) { /* ignore */ }
158 pointer = allocate();
162 logger.info("Started profiler.");
166 long start = System.nanoTime();
167 threadsSampled = sample(pointer);
168 long elapsed = System.nanoTime() - start;
170 sampleCount += threadsSampled;
171 totalSampleTime += elapsed >> 10; // shift avoids overflow.
175 Thread.sleep(delayPerThread * threadsSampled);
176 } catch (InterruptedException e) { /* ignore */ }
182 * Dumps a snapshot to the log. Useful for debugging.
184 public static void logSnapshot(byte[] snapshot) {
185 DataInputStream in = new DataInputStream(
186 new ByteArrayInputStream(snapshot));
188 int version = in.readUnsignedShort();
189 int classCount = in.readUnsignedShort();
190 StringBuilder sb = new StringBuilder();
191 sb.append("version=").append(version).append(' ')
192 .append("classes=").append(classCount).append('\n');
193 logger.info(sb.toString());
194 for (int i = 0; i < classCount; i++) {
195 sb = new StringBuilder();
196 sb.append("class ").append(in.readUTF()).append('\n');
197 int methodCount = in.readUnsignedShort();
198 for (int m = 0; m < methodCount; m++) {
199 sb.append(" ").append(in.readUTF()).append(":\n");
200 sb.append(" event:\n");
201 appendCounts(in, sb);
202 sb.append(" other:\n");
203 appendCounts(in, sb);
205 logger.info(sb.toString());
207 } catch (IOException e) {
208 logger.warning(e.toString());
212 private static void appendCounts(DataInputStream in, StringBuilder sb)
214 sb.append(" running: ").append(in.readShort()).append('\n');
215 sb.append(" native: ").append(in.readShort()).append('\n');
216 sb.append(" suspended: ").append(in.readShort()).append('\n');
219 /** This will be allocated when the user calls getInstance(). */
220 private static final SamplingProfiler instance = new SamplingProfiler();
223 * Gets the profiler. The profiler is not running by default. Start it
224 * with {@link #start(int)}.
226 public static synchronized SamplingProfiler getInstance() {