summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--JavaLibrary.mk13
-rw-r--r--dalvik/src/main/java/dalvik/system/SamplingProfiler.java685
-rw-r--r--dalvik/src/test/java/dalvik/system/SamplingProfilerTest.java60
3 files changed, 514 insertions, 244 deletions
diff --git a/JavaLibrary.mk b/JavaLibrary.mk
index a731aee..4881c58 100644
--- a/JavaLibrary.mk
+++ b/JavaLibrary.mk
@@ -120,7 +120,17 @@ include $(BUILD_JAVA_LIBRARY)
# large, to the point that dx(1) can't cope (and the build is
# ridiculously slow).
#
-# TODO: DalvikRunner will make this nonsense obsolete.
+# TODO: vogar will make this nonsense obsolete.
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := $(call all-test-java-files-under,dalvik)
+LOCAL_JAVA_RESOURCE_DIRS := $(test_resource_dirs)
+LOCAL_NO_STANDARD_LIBRARIES := true
+LOCAL_JAVA_LIBRARIES := core core-junit core-junitrunner core-tests-support
+LOCAL_DX_FLAGS := --core-library
+LOCAL_MODULE_TAGS := tests
+LOCAL_MODULE := core-tests-dalvik
+include $(BUILD_JAVA_LIBRARY)
include $(CLEAR_VARS)
LOCAL_SRC_FILES := $(call all-test-java-files-under,dom)
@@ -156,6 +166,7 @@ LOCAL_JAVA_LIBRARIES := \
core-junit \
core-junitrunner \
core-tests-support \
+ core-tests-dalvik \
core-tests-dom \
core-tests-json \
core-tests-xml \
diff --git a/dalvik/src/main/java/dalvik/system/SamplingProfiler.java b/dalvik/src/main/java/dalvik/system/SamplingProfiler.java
index 65d1762..f4df73a 100644
--- a/dalvik/src/main/java/dalvik/system/SamplingProfiler.java
+++ b/dalvik/src/main/java/dalvik/system/SamplingProfiler.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009 The Android Open Source Project
+ * Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,331 +16,530 @@
package dalvik.system;
-import java.io.ByteArrayInputStream;
-import java.io.DataInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
import java.io.IOException;
-import java.util.logging.Logger;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.Map;
+import java.util.Set;
+import java.util.Timer;
+import java.util.TimerTask;
/**
- * A sampling profiler.
+ * A sampling profiler. It currently is implemented without any
+ * virtual machine support, relying solely on {@code
+ * Thread.getStackTrace} to collect samples. As such, the overhead is
+ * higher than a native approach and it does not provide insight into
+ * where time is spent within native code, but it can still provide
+ * useful insight into where a program is spending time.
+ *
+ * <h3>Usage Example</h3>
+ *
+ * The following example shows how to use the {@code
+ * SamplingProfiler}. It samples the current thread's stack to a depth
+ * of 12 stack frame elements over two different measurement periods
+ * at a rate of 10 samples a second. In then prints the results in
+ * hprof format to the standard output.
+ *
+ * <pre> {@code
+ * ThreadSet threadSet = SamplingProfiler.newArrayThreadSet(Thread.currentThread());
+ * SamplingProfiler profiler = new SamplingProfiler(12, threadSet);
+ * profiler.start(10);
+ * // period of measurement
+ * profiler.stop();
+ * // period of non-measurement
+ * profiler.start(10);
+ * // another period of measurement
+ * profiler.stop();
+ * profiler.shutdown();
+ * profiler.writeHprofData(System.out);
+ * }</pre>
*
* @hide
*/
-public class SamplingProfiler {
+public final class SamplingProfiler {
- private static final Logger logger = Logger.getLogger(
- SamplingProfiler.class.getName());
+ /*
+ * Real hprof output examples don't start the thread and trace
+ * identifiers at one but seem to start at these arbitrary
+ * constants. It certainly seems useful to have relatively unique
+ * identifers when manual searching hprof output.
+ */
+ private int nextThreadId = 200001;
+ private int nextTraceId = 300001;
+ private int nextObjectId = 1;
- static final boolean DEBUG = false;
+ /**
+ * Map of currently active threads to their identifiers. When
+ * threads disappear they are removed and only referenced by their
+ * identifiers to prevent retaining garbage threads.
+ */
+ private final Map<Thread, Integer> threadIds = new HashMap<Thread, Integer>();
- enum State {
- /** The sampling thread hasn't started or is waiting to resume. */
- PAUSED,
- /** The sampling thread is collecting samples. */
- RUNNING,
- /** The sampling thread is shutting down. */
- SHUTTING_DOWN
- }
+ /**
+ * List of thread creation and death events.
+ */
+ private final List<ThreadEvent> threadHistory = new ArrayList<ThreadEvent>();
- /** Pointer to native state. */
- int pointer = 0;
+ /**
+ * Map of stack traces to a mutable sample count.
+ */
+ private final Map<Trace, int[]> traces = new HashMap<Trace, int[]>();
- /** The thread that collects samples. */
- Thread samplingThread;
+ /**
+ * Timer that is used for the lifetime of the profiler
+ */
+ private final Timer timer = new Timer(getClass().getName(), true);
- /** Time between samples. */
- volatile int delay; // ms
+ /**
+ * A sampler is created every time profiling starts and cleared
+ * everytime profiling stops because once a {@code TimerTask} is
+ * canceled it cannot be reused.
+ */
+ private TimerTask sampler;
- /** Number of samples taken (~samples per second * number of threads). */
- int totalThreadsSampled = 0;
+ /**
+ * The maximum number of {@code StackTraceElements} to retain in
+ * each stack.
+ */
+ private final int depth;
- /** Total time spent collecting samples. */
- long totalSampleTime = 0;
+ /**
+ * The {@code ThreadSet} that identifies which threads to sample.
+ */
+ private final ThreadSet threadSet;
- /** The state of the profiler. */
- volatile State state = State.PAUSED;
+ /**
+ * Create a sampling profiler that collects stacks with the
+ * specified depth from the threads specified by the specified
+ * thread collector.
+ *
+ * @param depth The maximum stack depth to retain for each sample
+ * similar to the hprof option of the same name. Any stack deeper
+ * than this will be truncated to this depth. A good starting
+ * value is 4 although it is not uncommon to need to raise this to
+ * get enough context to understand program behavior. While
+ * programs with extensive recursion may require a high value for
+ * depth, simply passing in a value for Integer.MAX_VALUE is not
+ * advised because of the significant memory need to retain such
+ * stacks and runtime overhead to compare stacks.
+ */
+ public SamplingProfiler(int depth, ThreadSet threadSet) {
+ this.depth = depth;
+ this.threadSet = threadSet;
+ }
- private SamplingProfiler() {}
+ /**
+ * A ThreadSet specifies the set of threads to sample.
+ */
+ public static interface ThreadSet {
+ /**
+ * Returns an array containing the threads to be sampled. The
+ * array may be longer than the number of threads to be
+ * sampled, in which case the extra elements must be null.
+ */
+ public Thread[] threads();
+ }
/**
- * Returns true if the profiler is running.
+ * Returns a ThreadSet for a fixed set of threads that will not
+ * vary at runtime. This has less overhead than a dynamically
+ * calculated set, such as {@link newThreadGroupTheadSet}, which has
+ * to enumerate the threads each time profiler wants to collect
+ * samples.
*/
- public boolean isRunning() {
- return state == State.RUNNING;
+ public static ThreadSet newArrayThreadSet(Thread... threads) {
+ return new ArrayThreadSet(threads);
}
/**
- * Starts collecting samples.
- *
- * @param samplesPerSecond number of times to sample each thread per second
- * @throws IllegalStateException if the profiler is
- * {@linkplain #shutDown()} shutting down}
+ * An ArrayThreadSet samples a fixed set of threads that does not
+ * vary over the life of the profiler.
*/
- public synchronized void start(int samplesPerSecond) {
- if (samplesPerSecond < 1) {
- throw new IllegalArgumentException("samplesPerSecond < 1");
- }
- ensureNotShuttingDown();
- delay = 1000 / samplesPerSecond;
- if (!isRunning()) {
- if (DEBUG) logger.info("Starting profiler...");
- state = State.RUNNING;
- if (samplingThread == null) {
- // TODO: Priority?
- samplingThread = new Thread(new Sampler(), "SamplingProfiler");
- samplingThread.setDaemon(true);
- samplingThread.start();
- } else {
- notifyAll();
+ private static class ArrayThreadSet implements ThreadSet {
+ private final Thread[] threads;
+ public ArrayThreadSet(Thread... threads) {
+ if (threads == null) {
+ throw new NullPointerException("threads == null");
}
+ this.threads = threads;
+ }
+ public Thread[] threads() {
+ return threads;
}
}
/**
- * Pauses sample collection.
+ * Returns a ThreadSet that is dynamically computed based on the
+ * threads found in the specified ThreadGroup and that
+ * ThreadGroup's children.
*/
- public synchronized void pause() {
- if (isRunning()) {
- if (DEBUG) logger.info("Pausing profiler...");
- state = State.PAUSED;
- }
+ public static ThreadSet newThreadGroupTheadSet(ThreadGroup threadGroup) {
+ return new ThreadGroupThreadSet(threadGroup);
}
/**
- * Captures collected samples and clears the sample set. Returns null
- * if no data has been captured.
- *
- * <p>Note: The exact format is not documented because it's not set in
- * stone yet.
- *
- * @throws IllegalStateException if the profiler is
- * {@linkplain #shutDown()} shutting down}
+ * An ThreadGroupThreadSet sample the threads from the specified
+ * ThreadGroup and the ThreadGroup's children
*/
- public synchronized byte[] snapshot() {
- ensureNotShuttingDown();
- if (pointer == 0 || totalThreadsSampled == 0) {
- return null;
+ private static class ThreadGroupThreadSet implements ThreadSet {
+ private final ThreadGroup threadGroup;
+ private Thread[] threads;
+ private int lastThread;
+
+ public ThreadGroupThreadSet(ThreadGroup threadGroup) {
+ if (threadGroup == null) {
+ throw new NullPointerException("threadGroup == null");
+ }
+ this.threadGroup = threadGroup;
+ resize();
+ }
+
+ private void resize() {
+ int count = threadGroup.activeCount();
+ // we can only tell if we had enough room for all active
+ // threads if we actually are larger than the the number of
+ // active threads. making it larger also leaves us room to
+ // tolerate additional threads without resizing.
+ threads = new Thread[count*2];
+ lastThread = 0;
}
- if (DEBUG) {
- int size = size(pointer);
- int collisions = collisions(pointer);
-
- long start = System.nanoTime();
- byte[] bytes = snapshot(pointer);
- long elapsed = System.nanoTime() - start;
-
- /*
- * We shifted the times by 10 bits in the sampling thread to avoid
- * overflow. Undo the shift and then convert from ns to us.
- */
- long averageSampleTime = ((totalSampleTime / totalThreadsSampled)
- << 10) / 1000;
- logger.info("Grabbed snapshot in " + (elapsed / 1000) + "us."
- + " Samples collected: " + totalThreadsSampled
- + ", Average sample time (per thread): "
- + averageSampleTime + "us"
- + ", Set size: " + size
- + ", Collisions: " + collisions);
- totalThreadsSampled = 0;
- totalSampleTime = 0;
-
- return bytes;
- } else {
- totalThreadsSampled = 0;
- return snapshot(pointer);
+ public Thread[] threads() {
+ int threadCount;
+ while (true) {
+ threadCount = threadGroup.enumerate(threads);
+ if (threadCount == threads.length) {
+ resize();
+ } else {
+ break;
+ }
+ }
+ if (threadCount < lastThread) {
+ // avoid retaining pointers to threads that have ended
+ Arrays.fill(threads, threadCount, lastThread, null);
+ }
+ lastThread = threadCount;
+ return threads;
}
}
/**
- * Identifies the "event thread". For a user-facing application, this
- * might be the UI thread. For a background process, this might be the
- * thread that processes incoming requests.
+ * Starts profiler sampling at the specified rate.
*
- * @throws IllegalStateException if the profiler is
- * {@linkplain #shutDown()} shutting down}
+ * @param samplesPerSecond The number of samples to take a second
*/
- public synchronized void setEventThread(Thread eventThread) {
- ensureNotShuttingDown();
- if (pointer == 0) {
- pointer = allocate();
+ public void start(int samplesPerSecond) {
+ if (samplesPerSecond < 1) {
+ throw new IllegalArgumentException("samplesPerSecond < 1");
}
- setEventThread(pointer, eventThread);
+ if (sampler != null) {
+ throw new IllegalStateException("profiling already started");
+ }
+ sampler = new Sampler();
+ timer.scheduleAtFixedRate(sampler, 0, 1000/samplesPerSecond);
}
- private void ensureNotShuttingDown() {
- if (state == State.SHUTTING_DOWN) {
- throw new IllegalStateException("Profiler is shutting down.");
+ /**
+ * Stops profiler sampling. It can be restarted with {@link
+ * #start(int)} to continue sampling.
+ */
+ public void stop() {
+ if (sampler == null) {
+ return;
}
+ sampler.cancel();
+ sampler = null;
}
/**
- * Shuts down the profiler thread and frees native memory. The profiler
- * will recreate the thread the next time {@link #start(int)} is called.
+ * Shuts down profiling after which it can not be restarted. It is
+ * important to shut down profiling when done to free resources
+ * used by the profiler. Shutting down the profiler also stops the
+ * profiling if that has not already been done.
+ */
+ public void shutdown() {
+ stop();
+ timer.cancel();
+ }
+
+ /**
+ * The Sampler does the real work of the profiler.
*
- * @throws IllegalStateException if the profiler is already shutting down
- * or if it hasn't started yet
+ * At every sample time, it asks the thread set for the set
+ * of threads to sample. It maintains a history of thread creation
+ * and death events based on changes observed to the threads
+ * returned by the {@code ThreadSet}.
*
+ * For each thread to be sampled, a stack is collected and used to
+ * update the set of collected samples. Stacks are truncated to a
+ * maximum depth. There is no way to tell if a stack has been truncated.
*/
- public void shutDown() {
- Thread toStop;
- synchronized (this) {
- ensureNotShuttingDown();
-
- toStop = samplingThread;
- if (toStop == null) {
- throw new IllegalStateException(
- "The profiler was never started.");
+ private class Sampler extends TimerTask {
+
+ private Thread timerThread;
+ private Thread[] currentThreads = new Thread[0];
+ private final Trace mutableTrace = new Trace();
+
+ public void run() {
+ if (timerThread == null) {
+ timerThread = Thread.currentThread();
}
- state = State.SHUTTING_DOWN;
- samplingThread = null;
- notifyAll();
- }
+ // process thread creation and death first so that we
+ // assign thread ids to any new threads before allocating
+ // new stacks for them
+ Thread[] newThreads = threadSet.threads();
+ if (!Arrays.equals(currentThreads, newThreads)) {
+ updateThreadHistory(currentThreads, newThreads);
+ currentThreads = newThreads;
+ }
- // Release lock to 'this' so background thread can grab it and stop.
- // Interrupt the thread in case it's sleeping.
- toStop.interrupt();
- while (true) {
- try {
- toStop.join();
- break;
- } catch (InterruptedException e) { /* ignore */ }
- }
+ for (Thread thread : currentThreads) {
+ if (thread == null) {
+ break;
+ }
+ if (thread == timerThread) {
+ continue;
+ }
+
+ // TODO replace with a VMStack.getThreadStackTrace
+ // variant to avoid allocating unneeded elements
+ StackTraceElement[] stack = thread.getStackTrace();
+ if (stack.length > depth) {
+ stack = Arrays.copyOfRange(stack, 0, depth);
+ }
+
+ mutableTrace.threadId = threadIds.get(thread);
+ mutableTrace.stack = stack;
- synchronized (this) {
- if (pointer != 0) {
- free(pointer);
- pointer = 0;
+ int[] count = traces.get(mutableTrace);
+ if (count == null) {
+ Trace trace = new Trace(nextTraceId++, mutableTrace);
+ count = new int[1];
+ traces.put(trace, count);
+ }
+ count[0]++;
}
+ }
+
+ private void updateThreadHistory(Thread[] oldThreads, Thread[] newThreads) {
+ // thread start/stop shouldn't happen too often and
+ // these aren't too big, so hopefully this approach
+ // won't be too slow...
+ Set<Thread> n = new HashSet<Thread>(Arrays.asList(newThreads));
+ Set<Thread> o = new HashSet<Thread>(Arrays.asList(oldThreads));
+
+ // added = new-old
+ Set<Thread> added = new HashSet<Thread>(n);
+ added.removeAll(o);
- totalThreadsSampled = 0;
- totalSampleTime = 0;
- state = State.PAUSED;
+ // removed = old-new
+ Set<Thread> removed = new HashSet<Thread>(o);
+ removed.removeAll(n);
+
+ for (Thread thread : added) {
+ if (thread == null) {
+ continue;
+ }
+ if (thread == timerThread) {
+ continue;
+ }
+ int threadId = nextThreadId++;
+ threadIds.put(thread, threadId);
+ ThreadEvent event = ThreadEvent.start(nextObjectId++,threadId, thread);
+ threadHistory.add(event);
+ }
+ for (Thread thread : removed) {
+ if (thread == null) {
+ continue;
+ }
+ if (thread == timerThread) {
+ continue;
+ }
+ int threadId = threadIds.remove(thread);
+ ThreadEvent event = ThreadEvent.stop(threadId);
+ threadHistory.add(event);
+ }
}
}
- /** Collects some data. Returns number of threads sampled. */
- private static native int sample(int pointer);
+ private enum ThreadEventType { START, STOP };
- /** Allocates native state. */
- private static native int allocate();
+ /**
+ * ThreadEvent represents thread creation and death events for
+ * reporting. It provides a record of the thread and thread group
+ * names for tying samples back to their source thread.
+ */
+ private static class ThreadEvent {
- /** Frees native state. */
- private static native void free(int pointer);
+ private final ThreadEventType type;
+ private final int objectId;
+ private final int threadId;
+ private final String threadName;
+ private final String groupName;
- /** Gets the number of methods in the sample set. */
- private static native int size(int pointer);
+ private static ThreadEvent start(int objectId, int threadId, Thread thread) {
+ return new ThreadEvent(ThreadEventType.START, objectId, threadId, thread);
+ }
- /** Gets the number of collisions in the sample set. */
- private static native int collisions(int pointer);
+ private static ThreadEvent stop(int threadId) {
+ return new ThreadEvent(ThreadEventType.STOP, threadId);
+ }
- /** Captures data. */
- private static native byte[] snapshot(int pointer);
+ private ThreadEvent(ThreadEventType type, int objectId, int threadId, Thread thread) {
+ this.type = ThreadEventType.START;
+ this.objectId = objectId;
+ this.threadId = threadId;
+ this.threadName = thread.getName();
+ this.groupName = thread.getThreadGroup().getName();
+ }
- /** Identifies the "event thread". */
- private static native void setEventThread(int pointer, Thread thread);
+ private ThreadEvent(ThreadEventType type, int threadId) {
+ this.type = ThreadEventType.STOP;
+ this.objectId = -1;
+ this.threadId = threadId;
+ this.threadName = null;
+ this.groupName = null;
+ }
+
+ @Override
+ public String toString() {
+ switch (type) {
+ case START:
+ return String.format(
+ "THREAD START (obj=%d, id = %d, name=\"%s\", group=\"%s\")",
+ objectId, threadId, threadName, groupName);
+ case STOP:
+ return String.format("THREAD END (id = %d)", threadId);
+ }
+ throw new IllegalStateException(type.toString());
+ }
+ }
/**
- * Background thread that collects samples.
+ * A Trace represents a unique stack trace for a specific thread.
*/
- class Sampler implements Runnable {
- public void run() {
- boolean firstSample = true;
- while (true) {
- synchronized (SamplingProfiler.this) {
- if (!isRunning()) {
- if (DEBUG) logger.info("Paused profiler.");
- while (!isRunning()) {
- if (state == State.SHUTTING_DOWN) {
- // Stop thread.
- return;
- }
-
- try {
- SamplingProfiler.this.wait();
- } catch (InterruptedException e) { /* ignore */ }
- }
- firstSample = true;
- }
-
- if (pointer == 0) {
- pointer = allocate();
- }
-
- if (firstSample) {
- if (DEBUG) logger.info("Started profiler.");
- firstSample = false;
- }
-
- if (DEBUG) {
- long start = System.nanoTime();
- int threadsSampled = sample(pointer);
- long elapsed = System.nanoTime() - start;
-
- totalThreadsSampled += threadsSampled;
- totalSampleTime += elapsed >> 10; // avoids overflow.
- } else {
- totalThreadsSampled += sample(pointer);
- }
- }
+ private static class Trace {
- try {
- Thread.sleep(delay);
- } catch (InterruptedException e) { /* ignore */ }
- }
+ private final int traceId;
+ private int threadId;
+ private StackTraceElement[] stack;
+
+ private Trace() {
+ this.traceId = -1;
+ }
+
+ private Trace(int traceId, Trace mutableTrace) {
+ this.traceId = traceId;
+ this.threadId = mutableTrace.threadId;
+ this.stack = mutableTrace.stack;
+ }
+
+ protected int getThreadId() {
+ return threadId;
+ }
+
+ protected StackTraceElement[] getStack() {
+ return stack;
+ }
+
+ @Override
+ public final int hashCode() {
+ int result = 17;
+ result = 31 * result + getThreadId();
+ result = 31 * result + Arrays.hashCode(getStack());
+ return result;
+ }
+
+ @Override
+ public final boolean equals(Object o) {
+ Trace s = (Trace) o;
+ return getThreadId() == s.getThreadId() && Arrays.equals(getStack(), s.getStack());
}
}
/**
- * Dumps a snapshot to the log. Useful for debugging.
+ * Prints the profiler's collected data in hprof format to the
+ * specified {@code File}. See the {@link #writeHprofData(PrintStream)
+ * PrintStream} variant for more information.
*/
- public static void logSnapshot(byte[] snapshot) {
- DataInputStream in = new DataInputStream(
- new ByteArrayInputStream(snapshot));
+ public void writeHprofData(File file) throws IOException {
+ PrintStream out = null;
try {
- int version = in.readUnsignedShort();
- int classCount = in.readUnsignedShort();
- StringBuilder sb = new StringBuilder();
- sb.append("version=").append(version).append(' ')
- .append("classes=").append(classCount).append('\n');
- logger.info(sb.toString());
- for (int i = 0; i < classCount; i++) {
- sb = new StringBuilder();
- sb.append("class ").append(in.readUTF()).append('\n');
- int methodCount = in.readUnsignedShort();
- for (int m = 0; m < methodCount; m++) {
- sb.append(" ").append(in.readUTF()).append(":\n");
- sb.append(" event:\n");
- appendCounts(in, sb);
- sb.append(" other:\n");
- appendCounts(in, sb);
- }
- logger.info(sb.toString());
+ out = new PrintStream(new BufferedOutputStream(new FileOutputStream(file)));
+ writeHprofData(out);
+ } finally {
+ if (out != null) {
+ out.close();
}
- } catch (IOException e) {
- logger.warning(e.toString());
}
}
- private static void appendCounts(DataInputStream in, StringBuilder sb)
- throws IOException {
- sb.append(" running:\n");
- sb.append(" caller: ").append(in.readShort()).append('\n');
- sb.append(" leaf: ").append(in.readShort()).append('\n');
- sb.append(" suspended:\n");
- sb.append(" caller: ").append(in.readShort()).append('\n');
- sb.append(" leaf: ").append(in.readShort()).append('\n');
+ private static final class SampleComparator implements Comparator<Entry<Trace, int[]>> {
+ private static final SampleComparator INSTANCE = new SampleComparator();
+ public int compare(Entry<Trace, int[]> e1, Entry<Trace, int[]> e2) {
+ return e2.getValue()[0] - e1.getValue()[0];
+ }
}
- /** This will be allocated when the user calls getInstance(). */
- private static final SamplingProfiler instance = new SamplingProfiler();
-
/**
- * Gets the profiler. The profiler is not running by default. Start it
- * with {@link #start(int)}.
+ * Prints the profiler's collected data in hprof format to the
+ * specified {@code PrintStream}. The profiler needs to be
+ * stopped, but not necessarily shut down, in order to produce a
+ * report.
*/
- public static synchronized SamplingProfiler getInstance() {
- return instance;
+ public void writeHprofData(PrintStream out) {
+ if (sampler != null) {
+ throw new IllegalStateException("cannot print hprof data while sampling");
+ }
+
+ for (ThreadEvent e : threadHistory) {
+ out.println(e);
+ }
+
+ List<Entry<Trace, int[]>> samples = new ArrayList<Entry<Trace, int[]>>(traces.entrySet());
+ Collections.sort(samples, SampleComparator.INSTANCE);
+ int total = 0;
+ for (Entry<Trace, int[]> sample : samples) {
+ Trace trace = sample.getKey();
+ int count = sample.getValue()[0];
+ total += count;
+ out.printf("TRACE %d: (thread=%d)\n", trace.traceId, trace.threadId);
+ for (StackTraceElement e : trace.stack) {
+ out.printf("\t%s\n", e);
+ }
+ }
+ Date now = new Date();
+ // "CPU SAMPLES BEGIN (total = 826) Wed Jul 21 12:03:46 2010"
+ out.printf("CPU SAMPLES BEGIN (total = %d) %ta %tb %td %tT %tY\n",
+ total, now, now, now, now, now);
+ out.printf("rank self accum count trace method\n");
+ int rank = 0;
+ double accum = 0;
+ for (Entry<Trace, int[]> sample : samples) {
+ rank++;
+ Trace trace = sample.getKey();
+ int count = sample.getValue()[0];
+ double self = (double)count/(double)total;
+ accum += self;
+
+ // " 1 65.62% 65.62% 542 300302 java.lang.Long.parseLong"
+ out.printf("% 4d% 6.2f%%% 6.2f%% % 7d % 5d %s.%s\n",
+ rank, self*100, accum*100, count, trace.traceId,
+ trace.stack[0].getClassName(),
+ trace.stack[0].getMethodName());
+ }
+ out.printf("CPU SAMPLES END\n");
}
}
diff --git a/dalvik/src/test/java/dalvik/system/SamplingProfilerTest.java b/dalvik/src/test/java/dalvik/system/SamplingProfilerTest.java
new file mode 100644
index 0000000..42057c8
--- /dev/null
+++ b/dalvik/src/test/java/dalvik/system/SamplingProfilerTest.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dalvik.system;
+
+import dalvik.system.SamplingProfiler.ThreadSet;
+import java.math.BigInteger;
+import java.security.KeyPairGenerator;
+import java.security.SecureRandom;
+import javax.crypto.spec.DHParameterSpec;
+import junit.framework.TestCase;
+
+public class SamplingProfilerTest extends TestCase {
+
+ public void test_SamplingProfiler_basic() throws Exception {
+ ThreadSet threadSet = SamplingProfiler.newArrayThreadSet(Thread.currentThread());
+ SamplingProfiler profiler = new SamplingProfiler(12, threadSet);
+ profiler.start(10);
+ toBeMeasured();
+ profiler.stop();
+ profiler.shutdown();
+ profiler.writeHprofData(System.out);
+ }
+
+ private static final String P_STR =
+ "9494fec095f3b85ee286542b3836fc81a5dd0a0349b4c239dd38744d488cf8e3"
+ + "1db8bcb7d33b41abb9e5a33cca9144b1cef332c94bf0573bf047a3aca98cdf3b";
+ private static final String G_STR =
+ "98ab7c5c431479d8645e33aa09758e0907c78747798d0968576f9877421a9089"
+ + "756f7876e76590b76765645c987976d764dd4564698a87585e64554984bb4445"
+ + "76e5764786f875b4456c";
+
+ private static final byte[] P = new BigInteger(P_STR,16).toByteArray();
+ private static final byte[] G = new BigInteger(G_STR,16).toByteArray();
+
+ private static void toBeMeasured () throws Exception {
+ long start = System.currentTimeMillis();
+ for (int i = 0; i < 10000; i++) {
+ BigInteger p = new BigInteger(P);
+ BigInteger g = new BigInteger(G);
+ KeyPairGenerator gen = KeyPairGenerator.getInstance("DH");
+ gen.initialize(new DHParameterSpec(p, g), new SecureRandom());
+ }
+ long end = System.currentTimeMillis();
+ }
+
+}