summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--dalvik/src/main/java/dalvik/system/SamplingProfiler.java1585
-rw-r--r--dalvik/src/test/java/dalvik/system/SamplingProfilerTest.java261
-rw-r--r--luni/src/main/java/libcore/base/Objects.java4
3 files changed, 1646 insertions, 204 deletions
diff --git a/dalvik/src/main/java/dalvik/system/SamplingProfiler.java b/dalvik/src/main/java/dalvik/system/SamplingProfiler.java
index a0c9637..b741377 100644
--- a/dalvik/src/main/java/dalvik/system/SamplingProfiler.java
+++ b/dalvik/src/main/java/dalvik/system/SamplingProfiler.java
@@ -16,11 +16,17 @@
package dalvik.system;
-import java.io.BufferedOutputStream;
+import java.io.BufferedInputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.EOFException;
import java.io.File;
-import java.io.FileOutputStream;
+import java.io.FileInputStream;
import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
import java.io.PrintStream;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -34,6 +40,8 @@ import java.util.Map;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
+import libcore.base.Objects;
+import libcore.io.IoUtils;
/**
* A sampling profiler. It currently is implemented without any
@@ -62,39 +70,1288 @@ import java.util.TimerTask;
* // another period of measurement
* profiler.stop();
* profiler.shutdown();
- * profiler.writeHprofData(System.out);
+ * HprofWriter writer = new AsciiHprofWriter(profiler.getHprofData(), System.out);
+ * writer.write();
* }</pre>
*
* @hide
*/
public final class SamplingProfiler {
- /*
- * 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.
+ /**
+ * Represents sampling profiler data. Can be converted to ASCII or
+ * binary hprof-style output using {@link AsciiHprofWriter} or
+ * {@link BinaryHprofWriter}.
+ * <p>
+ * The data includes:
+ * <ul>
+ * <li>the start time of the last sampling period
+ * <li>the history of thread start and end events
+ * <li>stack traces with frequency counts
+ * <ul>
*/
- private int nextThreadId = 200001;
- private int nextTraceId = 300001;
- private int nextObjectId = 1;
+ public static final class HprofData {
+
+ public static enum ThreadEventType { START, END };
+
+ /**
+ * 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.
+ */
+ public static final class ThreadEvent {
+
+ public final ThreadEventType type;
+ public final int objectId;
+ public final int threadId;
+ public final String threadName;
+ public final String groupName;
+ public final String parentGroupName;
+
+ public static ThreadEvent start(int objectId, int threadId, String threadName,
+ String groupName, String parentGroupName) {
+ return new ThreadEvent(ThreadEventType.START, objectId, threadId,
+ threadName, groupName, parentGroupName);
+ }
+
+ public static ThreadEvent end(int threadId) {
+ return new ThreadEvent(ThreadEventType.END, threadId);
+ }
+
+ private ThreadEvent(ThreadEventType type, int objectId, int threadId,
+ String threadName, String groupName, String parentGroupName) {
+ if (threadName == null) {
+ throw new NullPointerException("threadName == null");
+ }
+ this.type = ThreadEventType.START;
+ this.objectId = objectId;
+ this.threadId = threadId;
+ this.threadName = threadName;
+ this.groupName = groupName;
+ this.parentGroupName = parentGroupName;
+ }
+
+ private ThreadEvent(ThreadEventType type, int threadId) {
+ this.type = ThreadEventType.END;
+ this.objectId = -1;
+ this.threadId = threadId;
+ this.threadName = null;
+ this.groupName = null;
+ this.parentGroupName = null;
+ }
+
+ @Override public int hashCode() {
+ int result = 17;
+ result = 31 * result + objectId;
+ result = 31 * result + threadId;
+ result = 31 * result + Objects.hashCode(threadName);
+ result = 31 * result + Objects.hashCode(groupName);
+ result = 31 * result + Objects.hashCode(parentGroupName);
+ return result;
+ }
+
+ @Override public boolean equals(Object o) {
+ if (!(o instanceof ThreadEvent)) {
+ return false;
+ }
+ ThreadEvent event = (ThreadEvent) o;
+ return (this.type == event.type
+ && this.objectId == event.objectId
+ && this.threadId == event.threadId
+ && Objects.equal(this.threadName, event.threadName)
+ && Objects.equal(this.groupName, event.groupName)
+ && Objects.equal(this.parentGroupName, event.parentGroupName));
+ }
+
+ @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 END:
+ return String.format("THREAD END (id = %d)", threadId);
+ }
+ throw new IllegalStateException(type.toString());
+ }
+ }
+
+ /**
+ * A unique stack trace for a specific thread.
+ */
+ public static final class StackTrace {
+
+ public final int stackTraceId;
+ private int threadId;
+ private StackTraceElement[] stackFrames;
+
+ private StackTrace() {
+ this.stackTraceId = -1;
+ }
+
+ public StackTrace(int stackTraceId, int threadId, StackTraceElement[] stackFrames) {
+ if (stackFrames == null) {
+ throw new NullPointerException("stackFrames == null");
+ }
+ this.stackTraceId = stackTraceId;
+ this.threadId = threadId;
+ this.stackFrames = stackFrames;
+ }
+
+ public int getThreadId() {
+ return threadId;
+ }
+
+ public StackTraceElement[] getStackFrames() {
+ return stackFrames;
+ }
+
+ @Override public int hashCode() {
+ int result = 17;
+ result = 31 * result + threadId;
+ result = 31 * result + Arrays.hashCode(stackFrames);
+ return result;
+ }
+
+ @Override public boolean equals(Object o) {
+ if (!(o instanceof StackTrace)) {
+ return false;
+ }
+ StackTrace s = (StackTrace) o;
+ return threadId == s.threadId && Arrays.equals(stackFrames, s.stackFrames);
+ }
+
+ @Override public String toString() {
+ StringBuilder frames = new StringBuilder();
+ if (stackFrames.length > 0) {
+ frames.append('\n');
+ for (StackTraceElement stackFrame : stackFrames) {
+ frames.append("\t at ");
+ frames.append(stackFrame);
+ frames.append('\n');
+ }
+ } else {
+ frames.append("<empty>");
+ }
+ return "StackTrace[stackTraceId=" + stackTraceId
+ + ", threadId=" + threadId
+ + ", frames=" + frames + "]";
+
+ }
+ }
+
+ /**
+ * A read only container combining a stack trace with its frequency.
+ */
+ public static final class Sample {
+
+ public final StackTrace stackTrace;
+ public final int count;
+
+ private Sample(StackTrace stackTrace, int count) {
+ if (stackTrace == null) {
+ throw new NullPointerException("stackTrace == null");
+ }
+ if (count < 0) {
+ throw new IllegalArgumentException("count < 0:" + count);
+ }
+ this.stackTrace = stackTrace;
+ this.count = count;
+ }
+
+ @Override public int hashCode() {
+ int result = 17;
+ result = 31 * result + stackTrace.hashCode();
+ result = 31 * result + count;
+ return result;
+ }
+
+ @Override public boolean equals(Object o) {
+ Sample s = (Sample) o;
+ return stackTrace.equals(s.stackTrace) && count == s.count;
+ }
+
+ @Override public String toString() {
+ return "Sample[count=" + count + " " + stackTrace + "]";
+ }
+
+ }
+
+ /**
+ * Start of last sampling period.
+ */
+ private long startMillis;
+
+ /**
+ * CONTROL_SETTINGS flags
+ */
+ private int flags;
+
+ /**
+ * stack sampling depth
+ */
+ private int depth;
+
+ /**
+ * List of thread creation and death events.
+ */
+ private final List<ThreadEvent> threadHistory = new ArrayList<ThreadEvent>();
+
+ /**
+ * Map of thread id to a start ThreadEvent
+ */
+ private final Map<Integer, ThreadEvent> threadIdToThreadEvent
+ = new HashMap<Integer, ThreadEvent>();
+
+ /**
+ * Map of stack traces to a mutable sample count. The map is
+ * provided by the creator of the HprofData so only have
+ * mutable access to the int[] cells that contain the sample
+ * count. Only an unmodifiable iterator view is available to
+ * users of the HprofData.
+ */
+ private final Map<HprofData.StackTrace, int[]> stackTraces;
+
+ public HprofData(Map<StackTrace, int[]> stackTraces) {
+ if (stackTraces == null) {
+ throw new NullPointerException("stackTraces == null");
+ }
+ this.stackTraces = stackTraces;
+ }
+
+ /**
+ * The start time in milliseconds of the last profiling period.
+ */
+ public long getStartMillis() {
+ return startMillis;
+ }
+
+ /**
+ * Set the time for the start of the current sampling period.
+ */
+ public void setStartMillis(long startMillis) {
+ this.startMillis = startMillis;
+ }
+
+ /**
+ * Get the {@link ControlSettings} flags
+ */
+ public int getFlags() {
+ return flags;
+ }
+
+ /**
+ * Set the {@link ControlSettings} flags
+ */
+ public void setFlags(int flags) {
+ this.flags = flags;
+ }
+
+ /**
+ * Get the stack sampling depth
+ */
+ public int getDepth() {
+ return depth;
+ }
+
+ /**
+ * Set the stack sampling depth
+ */
+ public void setDepth(int depth) {
+ this.depth = depth;
+ }
+
+ /**
+ * Return an unmodifiable history of start and end thread events.
+ */
+ public List<ThreadEvent> getThreadHistory() {
+ return Collections.unmodifiableList(threadHistory);
+ }
+
+ /**
+ * Return a new set containing the current sample data.
+ */
+ public Set<Sample> getSamples() {
+ Set<Sample> samples = new HashSet<Sample>(stackTraces.size());
+ for (Entry<StackTrace, int[]> e : stackTraces.entrySet()) {
+ StackTrace stackTrace = e.getKey();
+ int countCell[] = e.getValue();
+ int count = countCell[0];
+ Sample sample = new Sample(stackTrace, count);
+ samples.add(sample);
+ }
+ return samples;
+ }
+
+ /**
+ * Record an event in the thread history.
+ */
+ public void addThreadEvent(ThreadEvent event) {
+ if (event == null) {
+ throw new NullPointerException("event == null");
+ }
+ ThreadEvent old = threadIdToThreadEvent.put(event.threadId, event);
+ switch (event.type) {
+ case START:
+ if (old != null) {
+ throw new IllegalArgumentException("ThreadEvent already registered for id "
+ + event.threadId);
+ }
+ break;
+ case END:
+ // Do not assert that the END_THREAD matches a
+ // START_THREAD unless in strict mode. While thhis
+ // hold true in the binary hprof BinaryHprofWriter
+ // produces, it is not true of hprof files created
+ // by the RI. However, if there is an event
+ // already registed for a thread id, it should be
+ // the matching start, not a duplicate end.
+ if (old != null && old.type == ThreadEventType.END) {
+ throw new IllegalArgumentException("Duplicate ThreadEvent.end for id "
+ + event.threadId);
+ }
+ break;
+ }
+ threadHistory.add(event);
+ }
+
+ /**
+ * Record an stack trace and an associated int[] cell of
+ * sample cound for the stack trace. The caller is allowed
+ * retain a pointer to the cell to update the count. The
+ * SamplingProfiler intentionally does not present a mutable
+ * view of the count.
+ */
+ public void addStackTrace(StackTrace stackTrace, int[] countCell) {
+ if (!threadIdToThreadEvent.containsKey(stackTrace.threadId)) {
+ throw new IllegalArgumentException("Unknown thread id " + stackTrace.threadId);
+ }
+ int[] old = stackTraces.put(stackTrace, countCell);
+ if (old != null) {
+ throw new IllegalArgumentException("StackTrace already registered for id "
+ + stackTrace.stackTraceId + ":\n" + stackTrace);
+ }
+ }
+ }
+
+ public static interface HprofWriter {
+ public void write() throws IOException;
+ }
+
+ public static final class AsciiHprofWriter implements HprofWriter {
+
+ private final HprofData data;
+ private final PrintWriter out;
+
+ public AsciiHprofWriter(HprofData data, OutputStream outputStream) {
+ this.data = data;
+ this.out = new PrintWriter(outputStream);
+ }
+
+ public void write() throws IOException {
+ for (HprofData.ThreadEvent e : data.getThreadHistory()) {
+ out.println(e);
+ }
+
+ List<HprofData.Sample> samples
+ = new ArrayList<HprofData.Sample>(data.getSamples());
+ Collections.sort(samples, SAMPLE_COMPARATOR);
+ int total = 0;
+ for (HprofData.Sample sample : samples) {
+ HprofData.StackTrace stackTrace = sample.stackTrace;
+ int count = sample.count;
+ total += count;
+ out.printf("TRACE %d: (thread=%d)\n",
+ stackTrace.stackTraceId,
+ stackTrace.threadId);
+ for (StackTraceElement e : stackTrace.stackFrames) {
+ out.printf("\t%s\n", e);
+ }
+ }
+ Date now = new Date(data.getStartMillis());
+ // "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 (HprofData.Sample sample : samples) {
+ rank++;
+ HprofData.StackTrace stackTrace = sample.stackTrace;
+ int count = sample.count;
+ 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, stackTrace.stackTraceId,
+ stackTrace.stackFrames[0].getClassName(),
+ stackTrace.stackFrames[0].getMethodName());
+ }
+ out.printf("CPU SAMPLES END\n");
+ out.flush();
+ }
+
+ private static final Comparator<HprofData.Sample> SAMPLE_COMPARATOR
+ = new Comparator<HprofData.Sample>() {
+ public int compare(HprofData.Sample s1, HprofData.Sample s2) {
+ return s2.count - s1.count;
+ }
+ };
+ }
+
+
+ /**
+ * Hprof binary format related constants shared between the
+ * BinaryHprofReader and BinaryHprofWriter.
+ */
+ public static class BinaryHprof {
+ /**
+ * Currently code only supports 4 byte id size.
+ */
+ public static final int ID_SIZE = 4;
+
+ public static enum Tag {
+
+ STRING_IN_UTF8(0x01, -ID_SIZE),
+ LOAD_CLASS(0x02, 4 + ID_SIZE + 4 + ID_SIZE),
+ UNLOAD_CLASS(0x03, 4),
+ STACK_FRAME(0x04, ID_SIZE + ID_SIZE + ID_SIZE + ID_SIZE + 4 + 4),
+ STACK_TRACE(0x05, -(4 + 4 + 4)),
+ ALLOC_SITES(0x06, -(2 + 4 + 4 + 4 + 8 + 8 + 4)),
+ HEAP_SUMMARY(0x07, 4 + 4 + 8 + 8),
+ START_THREAD(0x0a, 4 + ID_SIZE + 4 + ID_SIZE + ID_SIZE + ID_SIZE),
+ END_THREAD(0x0b, 4),
+ HEAP_DUMP(0x0c, -0),
+ HEAP_DUMP_SEGMENT(0x1c, -0),
+ HEAP_DUMP_END(0x2c, 0),
+ CPU_SAMPLES(0x0d, -(4 + 4)),
+ CONTROL_SETTINGS(0x0e, 4 + 2);
+
+ public final byte tag;
+
+ /**
+ * Minimum size in bytes.
+ */
+ public final int minimumSize;
+
+ /**
+ * Maximum size in bytes. 0 mean no specific limit.
+ */
+ public final int maximumSize;
+
+ private Tag(int tag, int size) {
+ this.tag = (byte) tag;
+ if (size > 0) {
+ // fixed size, max and min the same
+ this.minimumSize = size;
+ this.maximumSize = size;
+ } else {
+ // only minimum bound
+ this.minimumSize = -size;
+ this.maximumSize = 0;
+ }
+ }
+
+ private static final Map<Byte, Tag> BYTE_TO_TAG
+ = new HashMap<Byte, Tag>();
+
+ static {
+ for (Tag v : Tag.values()) {
+ BYTE_TO_TAG.put(v.tag, v);
+ }
+ }
+
+ public static Tag get(byte tag) {
+ return BYTE_TO_TAG.get(tag);
+ }
+
+ /**
+ * Returns null if the actual size meets expectations, or a
+ * String error message if not.
+ */
+ public String checkSize(int actual) {
+ if (actual < minimumSize) {
+ return "expected a minimial record size of " + minimumSize + " for " + this
+ + " but received " + actual;
+ }
+ if (maximumSize == 0) {
+ return null;
+ }
+ if (actual > maximumSize) {
+ return "expected a maximum record size of " + maximumSize + " for " + this
+ + " but received " + actual;
+ }
+ return null;
+ }
+ }
+
+ public static enum ControlSettings {
+ ALLOC_TRACES(0x01),
+ CPU_SAMPLING(0x02);
+
+ public final int bitmask;
+
+ private ControlSettings(int bitmask) {
+ this.bitmask = bitmask;
+ }
+ }
+
+ }
+
+ public static final class BinaryHprofWriter implements HprofWriter {
+
+ private int nextStringId = 1; // id 0 => null
+ private int nextClassId = 1;
+ private int nextStackFrameId = 1;
+ private final Map<String, Integer> stringToId = new HashMap<String, Integer>();
+ private final Map<String, Integer> classNameToId = new HashMap<String, Integer>();
+ private final Map<StackTraceElement, Integer> stackFrameToId
+ = new HashMap<StackTraceElement, Integer>();
+
+ private final HprofData data;
+ private final DataOutputStream out;
+
+ public BinaryHprofWriter(HprofData data, OutputStream outputStream) {
+ this.data = data;
+ this.out = new DataOutputStream(outputStream);
+ }
+
+ public void write() throws IOException {
+ try {
+ writeHeader(data.getStartMillis());
+
+ writeControlSettings(data.getFlags(), data.getDepth());
+
+ for (HprofData.ThreadEvent event : data.getThreadHistory()) {
+ writeThreadEvent(event);
+ }
+
+ Set<HprofData.Sample> samples = data.getSamples();
+ int total = 0;
+ for (HprofData.Sample sample : samples) {
+ total += sample.count;
+ writeStackTrace(sample.stackTrace);
+ }
+ writeCpuSamples(total, samples);
+
+ } finally {
+ out.flush();
+ }
+ }
+
+ private void writeHeader(long dumpTimeInMilliseconds) throws IOException {
+ out.writeBytes("JAVA PROFILE 1.0.2");
+ out.writeByte(0); // null terminated string
+ out.writeInt(BinaryHprof.ID_SIZE);
+ out.writeLong(dumpTimeInMilliseconds);
+ }
+
+ private void writeControlSettings(int flags, int depth) throws IOException {
+ if (depth > Short.MAX_VALUE) {
+ throw new IllegalArgumentException("depth too large for binary hprof: "
+ + depth + " > " + Short.MAX_VALUE);
+ }
+ writeRecordHeader(BinaryHprof.Tag.CONTROL_SETTINGS,
+ 0,
+ BinaryHprof.Tag.CONTROL_SETTINGS.maximumSize);
+ out.writeInt(flags);
+ out.writeShort((short) depth);
+ }
+
+ private void writeThreadEvent(HprofData.ThreadEvent e) throws IOException {
+ switch (e.type) {
+ case START:
+ writeStartThread(e);
+ return;
+ case END:
+ writeStopThread(e);
+ return;
+ }
+ throw new IllegalStateException(e.type.toString());
+ }
+
+ private void writeStartThread(HprofData.ThreadEvent e) throws IOException {
+ int threadNameId = writeString(e.threadName);
+ int groupNameId = writeString(e.groupName);
+ int parentGroupNameId = writeString(e.parentGroupName);
+ writeRecordHeader(BinaryHprof.Tag.START_THREAD,
+ 0,
+ BinaryHprof.Tag.START_THREAD.maximumSize);
+ out.writeInt(e.threadId);
+ writeId(e.objectId);
+ out.writeInt(0); // stack trace where thread was started unavailable
+ writeId(threadNameId);
+ writeId(groupNameId);
+ writeId(parentGroupNameId);
+ }
+
+ private void writeStopThread(HprofData.ThreadEvent e) throws IOException {
+ writeRecordHeader(BinaryHprof.Tag.END_THREAD,
+ 0,
+ BinaryHprof.Tag.END_THREAD.maximumSize);
+ out.writeInt(e.threadId);
+ }
+
+ private void writeRecordHeader(BinaryHprof.Tag hprofTag,
+ int timeDeltaInMicroseconds,
+ int recordLength) throws IOException {
+ String error = hprofTag.checkSize(recordLength);
+ if (error != null) {
+ throw new AssertionError(error);
+ }
+ out.writeByte(hprofTag.tag);
+ out.writeInt(timeDeltaInMicroseconds);
+ out.writeInt(recordLength);
+ }
+
+ private void writeId(int id) throws IOException {
+ out.writeInt(id);
+ }
+
+ /**
+ * Ensures that a string has been writen to the out and
+ * returns its ID. The ID of a null string is zero, and
+ * doesn't actually result in any output. In a string has
+ * already been written previously, the earlier ID will be
+ * returned and no output will be written.
+ */
+ private int writeString(String string) throws IOException {
+ if (string == null) {
+ return 0;
+ }
+ Integer identifier = stringToId.get(string);
+ if (identifier != null) {
+ return identifier;
+ }
+
+ int id = nextStringId++;
+ stringToId.put(string, id);
+
+ byte[] bytes = string.getBytes("UTF-8");
+ writeRecordHeader(BinaryHprof.Tag.STRING_IN_UTF8,
+ 0,
+ BinaryHprof.ID_SIZE + bytes.length);
+ out.writeInt(id);
+ out.write(bytes, 0, bytes.length);
+
+ return id;
+ }
+
+ private void writeCpuSamples(int totalSamples, Set<HprofData.Sample> samples)
+ throws IOException {
+ int samplesCount = samples.size();
+ if (samplesCount == 0) {
+ return;
+ }
+ writeRecordHeader(BinaryHprof.Tag.CPU_SAMPLES, 0, 4 + 4 + (samplesCount * (4 + 4)));
+ out.writeInt(totalSamples);
+ out.writeInt(samplesCount);
+ for (HprofData.Sample sample : samples) {
+ out.writeInt(sample.count);
+ out.writeInt(sample.stackTrace.stackTraceId);
+ }
+ }
+
+ private void writeStackTrace(HprofData.StackTrace stackTrace) throws IOException {
+ int frames = stackTrace.stackFrames.length;
+ int[] stackFrameIds = new int[frames];
+ for (int i = 0; i < frames; i++) {
+ stackFrameIds[i] = writeStackFrame(stackTrace.stackFrames[i]);
+ }
+ writeRecordHeader(BinaryHprof.Tag.STACK_TRACE,
+ 0,
+ 4 + 4 + 4 + (frames * BinaryHprof.ID_SIZE));
+ out.writeInt(stackTrace.stackTraceId);
+ out.writeInt(stackTrace.threadId);
+ out.writeInt(frames);
+ for (int stackFrameId : stackFrameIds) {
+ writeId(stackFrameId);
+ }
+ }
+
+ private int writeLoadClass(String className) throws IOException {
+ Integer identifier = classNameToId.get(className);
+ if (identifier != null) {
+ return identifier;
+ }
+ int id = nextClassId++;
+ classNameToId.put(className, id);
+
+ int classNameId = writeString(className);
+ writeRecordHeader(BinaryHprof.Tag.LOAD_CLASS,
+ 0,
+ BinaryHprof.Tag.LOAD_CLASS.maximumSize);
+ out.writeInt(id);
+ writeId(0); // class object ID
+ out.writeInt(0); // stack trace where class was loaded is unavailable
+ writeId(classNameId);
+
+ return id;
+ }
+
+ private int writeStackFrame(StackTraceElement stackFrame) throws IOException {
+ Integer identifier = stackFrameToId.get(stackFrame);
+ if (identifier != null) {
+ return identifier;
+ }
+
+ int id = nextStackFrameId++;
+ stackFrameToId.put(stackFrame, id);
+
+ int classId = writeLoadClass(stackFrame.getClassName());
+ int methodNameId = writeString(stackFrame.getMethodName());
+ int sourceId = writeString(stackFrame.getFileName());
+ writeRecordHeader(BinaryHprof.Tag.STACK_FRAME,
+ 0,
+ BinaryHprof.Tag.STACK_FRAME.maximumSize);
+ writeId(id);
+ writeId(methodNameId);
+ writeId(0); // method signature is unavailable from StackTraceElement
+ writeId(sourceId);
+ out.writeInt(classId);
+ out.writeInt(stackFrame.getLineNumber());
+
+ return id;
+ }
+
+ public void close() throws IOException {
+ out.close();
+ }
+ }
/**
- * 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.
+ * <pre> {@code
+ * BinaryHprofReader reader = new BinaryHprofReader(new BufferedInputStream(inputStream));
+ * reader.setStrict(false); // for RI compatability
+ * reader.read();
+ * inputStream.close();
+ * reader.getVersion();
+ * reader.getHprofData();
+ * }</pre>
*/
- private final Map<Thread, Integer> threadIds = new HashMap<Thread, Integer>();
+ public static final class BinaryHprofReader {
+
+ private static final boolean TRACE = false;
+
+ private final DataInputStream in;
+
+ /**
+ * By default we try to strictly validate rules followed by
+ * our HprofWriter. For example, every end thread is preceded
+ * by a matching start thread.
+ */
+ private boolean strict = true;
+
+ /**
+ * version string from header after read has been performed,
+ * otherwise null. nullness used to detect if callers try to
+ * access data before read is called.
+ */
+ private String version;
+
+ private final Map<HprofData.StackTrace, int[]> stackTraces
+ = new HashMap<HprofData.StackTrace, int[]>();
+
+ private final HprofData hprofData = new HprofData(stackTraces);
+
+ private final Map<Integer, String> idToString = new HashMap<Integer, String>();
+ private final Map<Integer, String> idToClassName = new HashMap<Integer, String>();
+ private final Map<Integer, StackTraceElement> idToStackFrame
+ = new HashMap<Integer, StackTraceElement>();
+ private final Map<Integer, HprofData.StackTrace> idToStackTrace
+ = new HashMap<Integer, HprofData.StackTrace>();
+
+ /**
+ * Creates a BinaryHprofReader around the specified {@code
+ * inputStream}
+ */
+ public BinaryHprofReader(InputStream inputStream) throws IOException {
+ this.in = new DataInputStream(inputStream);
+ }
+
+ public boolean getStrict () {
+ return strict;
+ }
+
+ public void setStrict (boolean strict) {
+ if (version != null) {
+ throw new IllegalStateException("cannot set strict after read()");
+ }
+ this.strict = strict;
+ }
+
+ /**
+ * throws IllegalStateException if read() has not been called.
+ */
+ private void checkRead() {
+ if (version == null) {
+ throw new IllegalStateException("data access before read()");
+ }
+ }
+
+ public String getVersion() {
+ checkRead();
+ return version;
+ }
+
+ public HprofData getHprofData() {
+ checkRead();
+ return hprofData;
+ }
+
+ /**
+ * Read the hprof header and records from the input
+ */
+ public void read() throws IOException {
+ parseHeader();
+ parseRecords();
+ }
+
+ private void parseHeader() throws IOException {
+ if (TRACE) {
+ System.out.println("hprofTag=HEADER");
+ }
+ parseVersion();
+ parseIdSize();
+ parseTime();
+ }
+
+ private void parseVersion() throws IOException {
+ byte[] bytes = new byte[512];
+ for (int i = 0; i < bytes.length; i++) {
+ byte b = in.readByte();
+ if (b == '\0') {
+ String version = new String(bytes, 0, i, "UTF-8");
+ if (TRACE) {
+ System.out.println("\tversion=" + version);
+ }
+ if (!version.startsWith("JAVA PROFILE ")) {
+ throw new MalformedHprofException("Unexpected version: " + version);
+ }
+ this.version = version;
+ return;
+ }
+ bytes[i] = b;
+ }
+ throw new MalformedHprofException("Could not find HPROF version");
+ }
+
+ private void parseIdSize() throws IOException {
+ int idSize = in.readInt();
+ if (TRACE) {
+ System.out.println("\tidSize=" + idSize);
+ }
+ if (idSize != BinaryHprof.ID_SIZE) {
+ throw new MalformedHprofException("Unsupported identifier size: " + idSize);
+ }
+ }
+
+ private void parseTime() throws IOException {
+ long time = in.readLong();
+ if (TRACE) {
+ System.out.println("\ttime=" + Long.toHexString(time) + " " + new Date(time));
+ }
+ hprofData.setStartMillis(time);
+ }
+
+ private void parseRecords() throws IOException {
+ while (parseRecord()) {
+ ;
+ }
+ }
+
+ /**
+ * Read and process the next record. Returns true if a
+ * record was handled, false on EOF.
+ */
+ private boolean parseRecord() throws IOException {
+ int tagOrEOF = in.read();
+ if (tagOrEOF == -1) {
+ return false;
+ }
+ byte tag = (byte) tagOrEOF;
+ int timeDeltaInMicroseconds = in.readInt();
+ int recordLength = in.readInt();
+ BinaryHprof.Tag hprofTag = BinaryHprof.Tag.get(tag);
+ if (TRACE) {
+ System.out.println("hprofTag=" + hprofTag);
+ }
+ if (hprofTag == null) {
+ skipRecord(hprofTag, recordLength);
+ return true;
+ }
+ String error = hprofTag.checkSize(recordLength);
+ if (error != null) {
+ throw new MalformedHprofException(error);
+ }
+ switch (hprofTag) {
+ case CONTROL_SETTINGS:
+ parseControlSettings();
+ return true;
+
+ case STRING_IN_UTF8:
+ parseStringInUtf8(recordLength);
+ return true;
+
+ case START_THREAD:
+ parseStartThread();
+ return true;
+ case END_THREAD:
+ parseEndThread();
+ return true;
+
+ case LOAD_CLASS:
+ parseLoadClass();
+ return true;
+ case STACK_FRAME:
+ parseStackFrame();
+ return true;
+ case STACK_TRACE:
+ parseStackTrace(recordLength);
+ return true;
+
+ case CPU_SAMPLES:
+ parseCpuSamples(recordLength);
+ return true;
+
+ case UNLOAD_CLASS:
+ case ALLOC_SITES:
+ case HEAP_SUMMARY:
+ case HEAP_DUMP:
+ case HEAP_DUMP_SEGMENT:
+ case HEAP_DUMP_END:
+ default:
+ skipRecord(hprofTag, recordLength);
+ return true;
+ }
+ }
+
+ private void skipRecord(BinaryHprof.Tag hprofTag, long recordLength) throws IOException {
+ if (TRACE) {
+ System.out.println("\tskipping recordLength=" + recordLength);
+ }
+ long skipped = in.skip(recordLength);
+ if (skipped != recordLength) {
+ throw new EOFException("Expected to skip " + recordLength
+ + " bytes but only skipped " + skipped + " bytes");
+ }
+ }
+
+ private void parseControlSettings() throws IOException {
+ int flags = in.readInt();
+ short depth = in.readShort();
+ if (TRACE) {
+ System.out.println("\tflags=" + Integer.toHexString(flags));
+ System.out.println("\tdepth=" + depth);
+ }
+ hprofData.setFlags(flags);
+ hprofData.setDepth(depth);
+ }
+
+ private void parseStringInUtf8(int recordLength) throws IOException {
+ int stringId = in.readInt();
+ byte[] bytes = new byte[recordLength - BinaryHprof.ID_SIZE];
+ in.read(bytes);
+ String string = new String(bytes, "UTF-8");
+ if (TRACE) {
+ System.out.println("\tstring=" + string);
+ }
+ String old = idToString.put(stringId, string);
+ if (old != null) {
+ throw new MalformedHprofException("Duplicate string id: " + stringId);
+ }
+ }
+
+ private void parseLoadClass() throws IOException {
+ int classId = in.readInt();
+ int classObjectId = readId();
+ // serial number apparently not a stack trace id. (int vs ID)
+ // we don't use this field.
+ int stackTraceSerialNumber = in.readInt();
+ String className = readString();
+ if (TRACE) {
+ System.out.println("\tclassId=" + classId);
+ System.out.println("\tclassObjectId=" + classObjectId);
+ System.out.println("\tstackTraceSerialNumber=" + stackTraceSerialNumber);
+ System.out.println("\tclassName=" + className);
+ }
+ String old = idToClassName.put(classId, className);
+ if (old != null) {
+ throw new MalformedHprofException("Duplicate class id: " + classId);
+ }
+ }
+
+ private int readId() throws IOException {
+ return in.readInt();
+ }
+
+ private String readString() throws IOException {
+ int id = readId();
+ if (id == 0) {
+ return null;
+ }
+ String string = idToString.get(id);
+ if (string == null) {
+ throw new MalformedHprofException("Unknown string id " + id);
+ }
+ return string;
+ }
+
+ private String readClass() throws IOException {
+ int id = readId();
+ String string = idToClassName.get(id);
+ if (string == null) {
+ throw new MalformedHprofException("Unknown class id " + id);
+ }
+ return string;
+ }
+
+ private void parseStartThread() throws IOException {
+ int threadId = in.readInt();
+ int objectId = readId();
+ // stack trace where thread was created.
+ // serial number apparently not a stack trace id. (int vs ID)
+ // we don't use this field.
+ int stackTraceSerialNumber = in.readInt();
+ String threadName = readString();
+ String groupName = readString();
+ String parentGroupName = readString();
+ if (TRACE) {
+ System.out.println("\tthreadId=" + threadId);
+ System.out.println("\tobjectId=" + objectId);
+ System.out.println("\tstackTraceSerialNumber=" + stackTraceSerialNumber);
+ System.out.println("\tthreadName=" + threadName);
+ System.out.println("\tgroupName=" + groupName);
+ System.out.println("\tparentGroupName=" + parentGroupName);
+ }
+ HprofData.ThreadEvent event
+ = HprofData.ThreadEvent.start(objectId, threadId,
+ threadName, groupName, parentGroupName);
+ hprofData.addThreadEvent(event);
+ }
+
+ private void parseEndThread() throws IOException {
+ int threadId = in.readInt();
+ if (TRACE) {
+ System.out.println("\tthreadId=" + threadId);
+ }
+ HprofData.ThreadEvent event = HprofData.ThreadEvent.end(threadId);
+ hprofData.addThreadEvent(event);
+ }
+
+ private void parseStackFrame() throws IOException {
+ int stackFrameId = readId();
+ String methodName = readString();
+ String methodSignature = readString();
+ String file = readString();
+ String className = readClass();
+ int line = in.readInt();
+ if (TRACE) {
+ System.out.println("\tstackFrameId=" + stackFrameId);
+ System.out.println("\tclassName=" + className);
+ System.out.println("\tmethodName=" + methodName);
+ System.out.println("\tmethodSignature=" + methodSignature);
+ System.out.println("\tfile=" + file);
+ System.out.println("\tline=" + line);
+ }
+ StackTraceElement stackFrame = new StackTraceElement(className, methodName, file, line);
+ StackTraceElement old = idToStackFrame.put(stackFrameId, stackFrame);
+ if (old != null) {
+ throw new MalformedHprofException("Duplicate stack frame id: " + stackFrameId);
+ }
+ }
+
+ private void parseStackTrace(int recordLength) throws IOException {
+ int stackTraceId = in.readInt();
+ int threadId = in.readInt();
+ int frames = in.readInt();
+ if (TRACE) {
+ System.out.println("\tstackTraceId=" + stackTraceId);
+ System.out.println("\tthreadId=" + threadId);
+ System.out.println("\tframes=" + frames);
+ }
+ int expectedLength = 4 + 4 + 4 + (frames * BinaryHprof.ID_SIZE);
+ if (recordLength != expectedLength) {
+ throw new MalformedHprofException("Expected stack trace record of size "
+ + expectedLength
+ + " based on number of frames but header "
+ + "specified a length of " + recordLength);
+ }
+ StackTraceElement[] stackFrames = new StackTraceElement[frames];
+ for (int i = 0; i < frames; i++) {
+ int stackFrameId = readId();
+ StackTraceElement stackFrame = idToStackFrame.get(stackFrameId);
+ if (TRACE) {
+ System.out.println("\tstackFrameId=" + stackFrameId);
+ System.out.println("\tstackFrame=" + stackFrame);
+ }
+ if (stackFrame == null) {
+ throw new MalformedHprofException("Unknown stack frame id " + stackFrameId);
+ }
+ stackFrames[i] = stackFrame;
+ }
+
+ HprofData.StackTrace stackTrace
+ = new HprofData.StackTrace(stackTraceId, threadId, stackFrames);
+ if (strict) {
+ hprofData.addStackTrace(stackTrace, new int[1]);
+ } else {
+ // The RI can have duplicate stacks, presumably they
+ // have a minor race if two samples with the same
+ // stack are taken around the same time. if we have a
+ // duplicate, just skip adding it to hprofData, but
+ // register it locally in idToStackFrame. if it seen
+ // in CPU_SAMPLES, we will find a StackTrace is equal
+ // to the first, so they will share a countCell.
+ int[] countCell = stackTraces.get(stackTrace);
+ if (countCell == null) {
+ hprofData.addStackTrace(stackTrace, new int[1]);
+ }
+ }
+
+ HprofData.StackTrace old = idToStackTrace.put(stackTraceId, stackTrace);
+ if (old != null) {
+ throw new MalformedHprofException("Duplicate stack trace id: " + stackTraceId);
+ }
+
+ }
+
+ private void parseCpuSamples(int recordLength) throws IOException {
+ int totalSamples = in.readInt();
+ int samplesCount = in.readInt();
+ if (TRACE) {
+ System.out.println("\ttotalSamples=" + totalSamples);
+ System.out.println("\tsamplesCount=" + samplesCount);
+ }
+ int expectedLength = 4 + 4 + (samplesCount * (4 + 4));
+ if (recordLength != expectedLength) {
+ throw new MalformedHprofException("Expected CPU samples record of size "
+ + expectedLength
+ + " based on number of samples but header "
+ + "specified a length of " + recordLength);
+ }
+ int total = 0;
+ for (int i = 0; i < samplesCount; i++) {
+ int count = in.readInt();
+ int stackTraceId = in.readInt();
+ if (TRACE) {
+ System.out.println("\tcount=" + count);
+ System.out.println("\tstackTraceId=" + stackTraceId);
+ }
+ HprofData.StackTrace stackTrace = idToStackTrace.get(stackTraceId);
+ if (stackTrace == null) {
+ throw new MalformedHprofException("Unknown stack trace id " + stackTraceId);
+ }
+ if (count == 0) {
+ throw new MalformedHprofException("Zero sample count for stack trace "
+ + stackTrace);
+ }
+ int[] countCell = stackTraces.get(stackTrace);
+ if (strict) {
+ if (countCell[0] != 0) {
+ throw new MalformedHprofException("Setting sample count of stack trace "
+ + stackTrace + " to " + count
+ + " found it was already initialized to "
+ + countCell[0]);
+ }
+ } else {
+ // Coalesce counts from duplicate stack traces.
+ // For more on this, see comments in parseStackTrace.
+ count += countCell[0];
+ }
+ countCell[0] = count;
+ total += count;
+ }
+ if (strict && totalSamples != total) {
+ throw new MalformedHprofException("Expected a total of " + totalSamples
+ + " samples but saw " + total);
+ }
+ }
+ }
+
+ public static final class MalformedHprofException extends IOException {
+ private MalformedHprofException(String message) {
+ super(message);
+ }
+ private MalformedHprofException(String message, Throwable cause) {
+ super(message, cause);
+ }
+ private MalformedHprofException(Throwable cause) {
+ super(cause);
+ }
+ }
/**
- * List of thread creation and death events.
+ * Run on device with:
+ * adb shell dalvikvm 'dalvik.system.SamplingProfiler\$HprofBinaryToAscii'
+ *
+ * Run on host with:
+ * java -classpath out/target/common/obj/JAVA_LIBRARIES/core_intermediates/classes.jar
*/
- private final List<ThreadEvent> threadHistory = new ArrayList<ThreadEvent>();
+ public static final class HprofBinaryToAscii {
+
+ public static void main(String[] args) {
+ System.exit(convert(args) ? 0 : 1);
+ }
+
+ public static boolean convert(String[] args) {
+
+ if (args.length != 1) {
+ usage("binary hprof file argument expected");
+ return false;
+ }
+ File file = new File(args[0]);
+ if (!file.exists()) {
+ usage("file " + file + " does not exist");
+ return false;
+ }
+
+ HprofData hprofData;
+ InputStream inputStream = null;
+ try {
+ inputStream = new BufferedInputStream(new FileInputStream(file));
+ BinaryHprofReader reader = new BinaryHprofReader(inputStream);
+ reader.setStrict(false);
+ reader.read();
+ hprofData = reader.getHprofData();
+ } catch (IOException e) {
+ System.out.println("Problem reading binary hprof data from "
+ + file + ": " + e.getMessage());
+ return false;
+ } finally {
+ IoUtils.closeQuietly(inputStream);
+ }
+ try {
+ HprofWriter writer = new AsciiHprofWriter(hprofData, System.out);
+ writer.write();
+ } catch (IOException e) {
+ System.out.println("Problem writing ASCII hprof data: " + e.getMessage());
+ return false;
+ }
+ return true;
+ }
+
+ private static void usage(String error) {
+ System.out.print("ERROR: ");
+ System.out.println(error);
+ System.out.println();
+ System.out.println("usage: HprofBinaryToAscii <binary-hprof-file>");
+ System.out.println();
+ System.out.println("Reads a binary hprof file and print it in ASCII format");
+ }
+ }
/**
* Map of stack traces to a mutable sample count.
*/
- private final Map<Trace, int[]> traces = new HashMap<Trace, int[]>();
+ private final Map<HprofData.StackTrace, int[]> stackTraces
+ = new HashMap<HprofData.StackTrace, int[]>();
+
+ /**
+ * Data collected by the sampling profiler
+ */
+ private final HprofData hprofData = new HprofData(stackTraces);
/**
* Timer that is used for the lifetime of the profiler
@@ -138,6 +1395,8 @@ public final class SamplingProfiler {
public SamplingProfiler(int depth, ThreadSet threadSet) {
this.depth = depth;
this.threadSet = threadSet;
+ hprofData.setFlags(BinaryHprof.ControlSettings.CPU_SAMPLING.bitmask);
+ hprofData.setDepth(depth);
}
/**
@@ -248,6 +1507,7 @@ public final class SamplingProfiler {
throw new IllegalStateException("profiling already started");
}
sampler = new Sampler();
+ hprofData.setStartMillis(System.currentTimeMillis());
timer.scheduleAtFixedRate(sampler, 0, interval);
}
@@ -275,6 +1535,31 @@ public final class SamplingProfiler {
}
/**
+ * Returns the hprof data accumulated by the profiler since it was
+ * created. The profiler needs to be stopped, but not necessarily
+ * shut down, in order to access the data. If the profiler is
+ * restarted, there is no thread safe way to access the data.
+ */
+ public HprofData getHprofData() {
+ if (sampler != null) {
+ throw new IllegalStateException("cannot access hprof data while sampling");
+ }
+ return hprofData;
+ }
+
+
+ /**
+ * Prints the profiler's collected data in ASCII hprof format to
+ * the specified {@code PrintStream}.
+ *
+ * @deprecated Use {@code AsciiHprofWriter} as shown in class documentation.
+ */
+ @Deprecated public void writeHprofData(PrintStream out) {
+ // TODO remove deprecated writeHprofData
+ new AsciiHprofWriter(getHprofData(), out);
+ }
+
+ /**
* The Sampler does the real work of the profiler.
*
* At every sample time, it asks the thread set for the set
@@ -290,7 +1575,30 @@ public final class SamplingProfiler {
private Thread timerThread;
private Thread[] currentThreads = new Thread[0];
- private final Trace mutableTrace = new Trace();
+
+ /*
+ * 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 nextStackTraceId = 300001;
+ private int nextObjectId = 1;
+
+ /**
+ * 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>();
+
+ /**
+ * Mutable StackTrace that is used for probing stackTraces Map
+ * without allocating a StackTrace. If addStackTrace needs to
+ * be thread safe, this would need to be reconsidered.
+ */
+ private final HprofData.StackTrace mutableStackTrace = new HprofData.StackTrace();
public void run() {
if (timerThread == null) {
@@ -316,25 +1624,37 @@ public final class SamplingProfiler {
// TODO replace with a VMStack.getThreadStackTrace
// variant to avoid allocating unneeded elements
- StackTraceElement[] stack = thread.getStackTrace();
- if (stack.length == 0) {
+ StackTraceElement[] stackFrames = thread.getStackTrace();
+ if (stackFrames.length == 0) {
continue;
}
- if (stack.length > depth) {
- stack = Arrays.copyOfRange(stack, 0, depth);
+ if (stackFrames.length > depth) {
+ stackFrames = Arrays.copyOfRange(stackFrames, 0, depth);
}
+ recordStackTrace(thread, stackFrames);
+ }
+ }
- mutableTrace.threadId = threadIds.get(thread);
- mutableTrace.stack = stack;
+ /**
+ * Record a new stack trace. The thread should have been
+ * previously registered with addStartThread.
+ */
+ private void recordStackTrace(Thread thread, StackTraceElement[] stackFrames) {
+ Integer threadId = threadIds.get(thread);
+ if (threadId == null) {
+ throw new IllegalArgumentException("Unknown thread " + thread);
+ }
+ mutableStackTrace.threadId = threadId;
+ mutableStackTrace.stackFrames = stackFrames;
- int[] count = traces.get(mutableTrace);
- if (count == null) {
- Trace trace = new Trace(nextTraceId++, mutableTrace);
- count = new int[1];
- traces.put(trace, count);
- }
- count[0]++;
+ int[] countCell = stackTraces.get(mutableStackTrace);
+ if (countCell == null) {
+ countCell = new int[1];
+ HprofData.StackTrace stackTrace
+ = new HprofData.StackTrace(nextStackTraceId++, threadId, stackFrames);
+ hprofData.addStackTrace(stackTrace, countCell);
}
+ countCell[0]++;
}
private void updateThreadHistory(Thread[] oldThreads, Thread[] newThreads) {
@@ -359,10 +1679,7 @@ public final class SamplingProfiler {
if (thread == timerThread) {
continue;
}
- int threadId = nextThreadId++;
- threadIds.put(thread, threadId);
- ThreadEvent event = ThreadEvent.start(nextObjectId++,threadId, thread);
- threadHistory.add(event);
+ addStartThread(thread);
}
for (Thread thread : removed) {
if (thread == null) {
@@ -371,181 +1688,49 @@ public final class SamplingProfiler {
if (thread == timerThread) {
continue;
}
- int threadId = threadIds.remove(thread);
- ThreadEvent event = ThreadEvent.stop(threadId);
- threadHistory.add(event);
+ addEndThread(thread);
}
}
- }
- private enum ThreadEventType { START, STOP };
-
- /**
- * 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 {
-
- private final ThreadEventType type;
- private final int objectId;
- private final int threadId;
- private final String threadName;
- private final String groupName;
-
- private static ThreadEvent start(int objectId, int threadId, Thread thread) {
- return new ThreadEvent(ThreadEventType.START, objectId, threadId, thread);
- }
-
- private static ThreadEvent stop(int threadId) {
- return new ThreadEvent(ThreadEventType.STOP, threadId);
- }
+ /**
+ * Record that a newly noticed thread.
+ */
+ private void addStartThread(Thread thread) {
+ if (thread == null) {
+ throw new NullPointerException("thread == null");
+ }
+ int threadId = nextThreadId++;
+ Integer old = threadIds.put(thread, threadId);
+ if (old != null) {
+ throw new IllegalArgumentException("Thread already registered as " + old);
+ }
- private ThreadEvent(ThreadEventType type, int objectId, int threadId, Thread thread) {
- this.type = ThreadEventType.START;
- this.objectId = objectId;
- this.threadId = threadId;
- this.threadName = thread.getName();
+ String threadName = thread.getName();
// group will become null when thread is terminated
ThreadGroup group = thread.getThreadGroup();
- this.groupName = group == null ? null : group.getName();
- }
+ String groupName = group == null ? null : group.getName();
+ ThreadGroup parentGroup = group == null ? null : group.getParent();
+ String parentGroupName = parentGroup == null ? null : parentGroup.getName();
- private ThreadEvent(ThreadEventType type, int threadId) {
- this.type = ThreadEventType.STOP;
- this.objectId = -1;
- this.threadId = threadId;
- this.threadName = null;
- this.groupName = null;
+ HprofData.ThreadEvent event
+ = HprofData.ThreadEvent.start(nextObjectId++, threadId,
+ threadName, groupName, parentGroupName);
+ hprofData.addThreadEvent(event);
}
- @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);
+ /**
+ * Record that a thread has disappeared.
+ */
+ private void addEndThread(Thread thread) {
+ if (thread == null) {
+ throw new NullPointerException("thread == null");
}
- throw new IllegalStateException(type.toString());
- }
- }
-
- /**
- * A Trace represents a unique stack trace for a specific thread.
- */
- private static class Trace {
-
- 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());
- }
- }
-
- /**
- * 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 void writeHprofData(File file) throws IOException {
- PrintStream out = null;
- try {
- out = new PrintStream(new BufferedOutputStream(new FileOutputStream(file)));
- writeHprofData(out);
- } finally {
- if (out != null) {
- out.close();
+ Integer threadId = threadIds.remove(thread);
+ if (threadId == null) {
+ throw new IllegalArgumentException("Unknown thread " + thread);
}
+ HprofData.ThreadEvent event = HprofData.ThreadEvent.end(threadId);
+ hprofData.addThreadEvent(event);
}
}
-
- 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];
- }
- }
-
- /**
- * 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 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
index 42057c8..c132d94 100644
--- a/dalvik/src/test/java/dalvik/system/SamplingProfilerTest.java
+++ b/dalvik/src/test/java/dalvik/system/SamplingProfilerTest.java
@@ -16,27 +16,51 @@
package dalvik.system;
+import dalvik.system.SamplingProfiler.AsciiHprofWriter;
+import dalvik.system.SamplingProfiler.BinaryHprofReader;
+import dalvik.system.SamplingProfiler.BinaryHprofWriter;
+import dalvik.system.SamplingProfiler.HprofData.Sample;
+import dalvik.system.SamplingProfiler.HprofData.StackTrace;
+import dalvik.system.SamplingProfiler.HprofData.ThreadEvent;
+import dalvik.system.SamplingProfiler.HprofData;
+import dalvik.system.SamplingProfiler.HprofWriter;
import dalvik.system.SamplingProfiler.ThreadSet;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
import java.math.BigInteger;
import java.security.KeyPairGenerator;
import java.security.SecureRandom;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
import javax.crypto.spec.DHParameterSpec;
import junit.framework.TestCase;
public class SamplingProfilerTest extends TestCase {
- public void test_SamplingProfiler_basic() throws Exception {
+ /**
+ * Run the SamplingProfiler to gather some data on an actual
+ * computation, then assert that it looks correct with test_HprofData.
+ */
+ public void test_SamplingProfiler() throws Exception {
ThreadSet threadSet = SamplingProfiler.newArrayThreadSet(Thread.currentThread());
SamplingProfiler profiler = new SamplingProfiler(12, threadSet);
- profiler.start(10);
+ profiler.start(100);
toBeMeasured();
profiler.stop();
profiler.shutdown();
- profiler.writeHprofData(System.out);
+ test_HprofData(profiler.getHprofData(), true);
}
private static final String P_STR =
- "9494fec095f3b85ee286542b3836fc81a5dd0a0349b4c239dd38744d488cf8e3"
+ "9494fec095f3b85ee286542b3836fc81a5dd0a0349b4c239dd38744d488cf8e3"
+ "1db8bcb7d33b41abb9e5a33cca9144b1cef332c94bf0573bf047a3aca98cdf3b";
private static final String G_STR =
"98ab7c5c431479d8645e33aa09758e0907c78747798d0968576f9877421a9089"
@@ -57,4 +81,233 @@ public class SamplingProfilerTest extends TestCase {
long end = System.currentTimeMillis();
}
+ public void test_HprofData_null() throws Exception {
+ try {
+ new HprofData(null);
+ fail();
+ } catch (NullPointerException expected) {
+ }
+ }
+
+ public void test_HprofData_empty() throws Exception {
+ Map<StackTrace, int[]> stackTraces = new HashMap<StackTrace, int[]>();
+ HprofData hprofData = new HprofData(stackTraces);
+ test_HprofData(hprofData, true);
+ }
+
+ public void test_HprofData_timeMillis() throws Exception {
+ Map<StackTrace, int[]> stackTraces = new HashMap<StackTrace, int[]>();
+ HprofData hprofData = new HprofData(stackTraces);
+ long now = System.currentTimeMillis();
+ hprofData.setStartMillis(now);
+ assertEquals(now, hprofData.getStartMillis());
+ test_HprofData(hprofData, true);
+ }
+
+ public void test_HprofData_addThreadEvent_null() throws Exception {
+ Map<StackTrace, int[]> stackTraces = new HashMap<StackTrace, int[]>();
+ HprofData hprofData = new HprofData(stackTraces);
+ try {
+ hprofData.addThreadEvent(null);
+ fail();
+ } catch (NullPointerException expected) {
+ }
+ test_HprofData(hprofData, true);
+ }
+
+ public void test_HprofData_addThreadEvent() throws Exception {
+ Map<StackTrace, int[]> stackTraces = new HashMap<StackTrace, int[]>();
+ HprofData hprofData = new HprofData(stackTraces);
+
+ // should have nothing in the thread history to start
+ assertEquals(0, hprofData.getThreadHistory().size());
+
+ // add thread 1
+ final int threadId = 1;
+ final int objectId = 2;
+ ThreadEvent start1 = ThreadEvent.start(objectId, threadId,
+ "thread-name", "thread-group", "parent-group");
+ hprofData.addThreadEvent(start1);
+ assertEquals(Arrays.asList(start1), hprofData.getThreadHistory());
+ test_HprofData(hprofData, true);
+
+ // remove thread 2, which should not exist (but that's okay on the RI)
+ ThreadEvent end2 = ThreadEvent.end(threadId+1);
+ hprofData.addThreadEvent(end2);
+ assertEquals(Arrays.asList(start1, end2), hprofData.getThreadHistory());
+ test_HprofData(hprofData, false); // non-strict from here down because of this RI data
+
+ // remove thread 1, which should exist
+ ThreadEvent end1 = ThreadEvent.end(threadId);
+ hprofData.addThreadEvent(end1);
+ assertEquals(Arrays.asList(start1, end2, end1), hprofData.getThreadHistory());
+ test_HprofData(hprofData, false);
+
+ // remove thread 1 again, which should not exist (its not okay to have end followed by end)
+ try {
+ hprofData.addThreadEvent(ThreadEvent.end(threadId));
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ assertEquals(Arrays.asList(start1, end2, end1), hprofData.getThreadHistory());
+ test_HprofData(hprofData, false);
+ }
+
+ public void test_HprofData_addStackTrace() throws Exception {
+ Map<StackTrace, int[]> stackTraces = new HashMap<StackTrace, int[]>();
+ HprofData hprofData = new HprofData(stackTraces);
+
+ // should have no samples to start
+ assertEquals(0, hprofData.getSamples().size());
+
+ // attempt to add a stack for a non-existent thread, should fail
+ final int stackTraceId = 1;
+ final int threadId = 2;
+ final int objectId = 3;
+ final int sampleCount = 4;
+ StackTraceElement[] stackFrames = new Throwable().getStackTrace();
+ final int[] countCell = new int[] { 4 };
+ StackTrace stackTrace = new StackTrace(stackTraceId, threadId, stackFrames);
+ try {
+ hprofData.addStackTrace(stackTrace, countCell);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+
+ // add the thread and add the event
+ ThreadEvent start = ThreadEvent.start(objectId, threadId,
+ "thread-name", "thread-group", "parent-group");
+ hprofData.addThreadEvent(start);
+ hprofData.addStackTrace(stackTrace, countCell);
+ Set<Sample> samples = hprofData.getSamples();
+ assertNotNull(samples);
+ assertNotSame(samples, hprofData.getSamples());
+ assertEquals(1, samples.size());
+ Sample sample = samples.iterator().next();
+ assertNotNull(sample);
+ assertEquals(stackTrace, sample.stackTrace);
+ assertEquals(sampleCount, sample.count);
+ test_HprofData(hprofData, true);
+
+ // confirm we can mutate the sample count, but that its not
+ // visible in the current sample, but it will be visible in a
+ // new one.
+ countCell[0] += 42;
+ assertEquals(sampleCount, sample.count);
+ Sample sample2 = hprofData.getSamples().iterator().next();
+ assertEquals(sampleCount + 42, sample2.count);
+ test_HprofData(hprofData, true);
+
+ // try to reuse the stackTraceId, should fail
+ try {
+ hprofData.addStackTrace(stackTrace, countCell);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ assertEquals(1, hprofData.getSamples().size());
+ test_HprofData(hprofData, true);
+
+ }
+
+ private void test_HprofData(HprofData hprofData, boolean strict) throws Exception {
+ assertHprofData(hprofData, strict);
+ test_HprofData_ascii(hprofData);
+ test_HprofData_binary(hprofData, strict);
+ }
+
+ /**
+ * Assert general properities of HprofData hold true.
+ */
+ private void assertHprofData(HprofData hprofData, boolean strict) throws Exception {
+ List<ThreadEvent> threadHistory = hprofData.getThreadHistory();
+ assertNotNull(threadHistory);
+ Set<Integer> threadsSeen = new HashSet();
+ Set<Integer> threadsActive = new HashSet();
+ for (ThreadEvent event : threadHistory) {
+ assertNotNull(event);
+ assertNotNull(event.type);
+ switch (event.type) {
+ case START:
+ assertNotNull(event.threadName);
+ assertTrue(threadsActive.add(event.threadId));
+ assertTrue(threadsSeen.add(event.threadId));
+ break;
+ case END:
+ assertEquals(-1, event.objectId);
+ assertNull(event.threadName);
+ assertNull(event.groupName);
+ assertNull(event.parentGroupName);
+ if (strict) {
+ assertTrue(threadsActive.remove(event.threadId));
+ }
+ break;
+ }
+ }
+
+ Set<Sample> samples = hprofData.getSamples();
+ assertNotNull(samples);
+ for (Sample sample : samples) {
+ assertNotNull(sample);
+ assertTrue(sample.count > 0);
+ assertNotNull(sample.stackTrace);
+ assertTrue(sample.stackTrace.stackTraceId != -1);
+ assertTrue(threadsSeen.contains(sample.stackTrace.getThreadId()));
+ assertNotNull(sample.stackTrace.getStackFrames());
+ }
+ }
+
+ /**
+ * Convert to HprofData to ASCII to see if it triggers any exceptions
+ */
+ private void test_HprofData_ascii(HprofData hprofData) throws Exception {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ HprofWriter writer = new AsciiHprofWriter(hprofData, out);
+ writer.write();
+ out.close();
+ assertFalse(out.toByteArray().length == 0);
+ }
+
+ /**
+ * Convert to HprofData to binary and then reparse as to
+ * HprofData. Make sure the accessible data is equivalent.
+ */
+ private void test_HprofData_binary(HprofData hprofData, boolean strict) throws Exception {
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ HprofWriter writer = new BinaryHprofWriter(hprofData, out);
+ writer.write();
+ out.close();
+
+ byte[] bytes = out.toByteArray();
+ assertFalse(bytes.length == 0);
+ if (false) {
+ File file = new File("/sdcard/java.hprof");
+ OutputStream debug = new FileOutputStream(file);
+ debug.write(bytes);
+ debug.close();
+ System.out.println("Wrote binary hprof data to " + file);
+ }
+
+ InputStream in = new ByteArrayInputStream(bytes);
+ BinaryHprofReader reader = new BinaryHprofReader(in);
+ assertTrue(reader.getStrict());
+ reader.read();
+ in.close();
+ assertEquals("JAVA PROFILE 1.0.2", reader.getVersion());
+ assertNotNull(reader.getHprofData());
+
+ HprofData parsed = reader.getHprofData();
+ assertHprofData(hprofData, strict);
+
+ assertEquals(Long.toHexString(hprofData.getStartMillis()),
+ Long.toHexString(parsed.getStartMillis()));
+ assertEquals(Long.toHexString(hprofData.getFlags()),
+ Long.toHexString(parsed.getFlags()));
+ assertEquals(Long.toHexString(hprofData.getDepth()),
+ Long.toHexString(parsed.getDepth()));
+ assertEquals(hprofData.getThreadHistory(),
+ parsed.getThreadHistory());
+ assertEquals(hprofData.getSamples(),
+ parsed.getSamples());
+ }
}
diff --git a/luni/src/main/java/libcore/base/Objects.java b/luni/src/main/java/libcore/base/Objects.java
index 4c420b8..22bad9c 100644
--- a/luni/src/main/java/libcore/base/Objects.java
+++ b/luni/src/main/java/libcore/base/Objects.java
@@ -25,4 +25,8 @@ public final class Objects {
public static boolean equal(Object a, Object b) {
return a == b || (a != null && a.equals(b));
}
+
+ public static int hashCode(Object o) {
+ return (o == null) ? 0 : o.hashCode();
+ }
}