| /* |
| * 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; |
| } |
| } |