blob: 5c29838d90deb2292eb0f5b16769886d7c71388c [file] [log] [blame]
/*
* 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.profiler;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* BinaryHprofWriter produces hprof compatible binary output for use
* with third party tools. Such files can be converted to text with
* with {@link HprofBinaryToAscii} or read back in with {@link BinaryHprofReader}.
*/
public final class BinaryHprofWriter {
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;
/**
* Writes the provided data to the specified stream.
*/
public static void write(HprofData data, OutputStream outputStream) throws IOException {
new BinaryHprofWriter(data, outputStream).write();
}
private BinaryHprofWriter(HprofData data, OutputStream outputStream) {
this.data = data;
this.out = new DataOutputStream(outputStream);
}
private 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(BinaryHprof.MAGIC + "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;
}
}