1 /* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package dalvik.system.profiler; 18 19 import java.io.DataOutputStream; 20 import java.io.IOException; 21 import java.io.OutputStream; 22 import java.util.HashMap; 23 import java.util.Map; 24 import java.util.Set; 25 26 /** 27 * BinaryHprofWriter produces hprof compatible binary output for use 28 * with third party tools. Such files can be converted to text with 29 * with {@link HprofBinaryToAscii} or read back in with {@link BinaryHprofReader}. 30 */ 31 public final class BinaryHprofWriter { 32 33 private int nextStringId = 1; // id 0 => null 34 private int nextClassId = 1; 35 private int nextStackFrameId = 1; 36 private final Map<String, Integer> stringToId = new HashMap<String, Integer>(); 37 private final Map<String, Integer> classNameToId = new HashMap<String, Integer>(); 38 private final Map<StackTraceElement, Integer> stackFrameToId 39 = new HashMap<StackTraceElement, Integer>(); 40 41 private final HprofData data; 42 private final DataOutputStream out; 43 44 /** 45 * Writes the provided data to the specified stream. 46 */ write(HprofData data, OutputStream outputStream)47 public static void write(HprofData data, OutputStream outputStream) throws IOException { 48 new BinaryHprofWriter(data, outputStream).write(); 49 } 50 BinaryHprofWriter(HprofData data, OutputStream outputStream)51 private BinaryHprofWriter(HprofData data, OutputStream outputStream) { 52 this.data = data; 53 this.out = new DataOutputStream(outputStream); 54 } 55 write()56 private void write() throws IOException { 57 try { 58 writeHeader(data.getStartMillis()); 59 60 writeControlSettings(data.getFlags(), data.getDepth()); 61 62 for (HprofData.ThreadEvent event : data.getThreadHistory()) { 63 writeThreadEvent(event); 64 } 65 66 Set<HprofData.Sample> samples = data.getSamples(); 67 int total = 0; 68 for (HprofData.Sample sample : samples) { 69 total += sample.count; 70 writeStackTrace(sample.stackTrace); 71 } 72 writeCpuSamples(total, samples); 73 74 } finally { 75 out.flush(); 76 } 77 } 78 writeHeader(long dumpTimeInMilliseconds)79 private void writeHeader(long dumpTimeInMilliseconds) throws IOException { 80 out.writeBytes(BinaryHprof.MAGIC + "1.0.2"); 81 out.writeByte(0); // null terminated string 82 out.writeInt(BinaryHprof.ID_SIZE); 83 out.writeLong(dumpTimeInMilliseconds); 84 } 85 writeControlSettings(int flags, int depth)86 private void writeControlSettings(int flags, int depth) throws IOException { 87 if (depth > Short.MAX_VALUE) { 88 throw new IllegalArgumentException("depth too large for binary hprof: " 89 + depth + " > " + Short.MAX_VALUE); 90 } 91 writeRecordHeader(BinaryHprof.Tag.CONTROL_SETTINGS, 92 0, 93 BinaryHprof.Tag.CONTROL_SETTINGS.maximumSize); 94 out.writeInt(flags); 95 out.writeShort((short) depth); 96 } 97 writeThreadEvent(HprofData.ThreadEvent e)98 private void writeThreadEvent(HprofData.ThreadEvent e) throws IOException { 99 switch (e.type) { 100 case START: 101 writeStartThread(e); 102 return; 103 case END: 104 writeStopThread(e); 105 return; 106 } 107 throw new IllegalStateException(e.type.toString()); 108 } 109 writeStartThread(HprofData.ThreadEvent e)110 private void writeStartThread(HprofData.ThreadEvent e) throws IOException { 111 int threadNameId = writeString(e.threadName); 112 int groupNameId = writeString(e.groupName); 113 int parentGroupNameId = writeString(e.parentGroupName); 114 writeRecordHeader(BinaryHprof.Tag.START_THREAD, 115 0, 116 BinaryHprof.Tag.START_THREAD.maximumSize); 117 out.writeInt(e.threadId); 118 writeId(e.objectId); 119 out.writeInt(0); // stack trace where thread was started unavailable 120 writeId(threadNameId); 121 writeId(groupNameId); 122 writeId(parentGroupNameId); 123 } 124 writeStopThread(HprofData.ThreadEvent e)125 private void writeStopThread(HprofData.ThreadEvent e) throws IOException { 126 writeRecordHeader(BinaryHprof.Tag.END_THREAD, 127 0, 128 BinaryHprof.Tag.END_THREAD.maximumSize); 129 out.writeInt(e.threadId); 130 } 131 writeRecordHeader(BinaryHprof.Tag hprofTag, int timeDeltaInMicroseconds, int recordLength)132 private void writeRecordHeader(BinaryHprof.Tag hprofTag, 133 int timeDeltaInMicroseconds, 134 int recordLength) throws IOException { 135 String error = hprofTag.checkSize(recordLength); 136 if (error != null) { 137 throw new AssertionError(error); 138 } 139 out.writeByte(hprofTag.tag); 140 out.writeInt(timeDeltaInMicroseconds); 141 out.writeInt(recordLength); 142 } 143 writeId(int id)144 private void writeId(int id) throws IOException { 145 out.writeInt(id); 146 } 147 148 /** 149 * Ensures that a string has been writen to the out and 150 * returns its ID. The ID of a null string is zero, and 151 * doesn't actually result in any output. In a string has 152 * already been written previously, the earlier ID will be 153 * returned and no output will be written. 154 */ writeString(String string)155 private int writeString(String string) throws IOException { 156 if (string == null) { 157 return 0; 158 } 159 Integer identifier = stringToId.get(string); 160 if (identifier != null) { 161 return identifier; 162 } 163 164 int id = nextStringId++; 165 stringToId.put(string, id); 166 167 byte[] bytes = string.getBytes("UTF-8"); 168 writeRecordHeader(BinaryHprof.Tag.STRING_IN_UTF8, 169 0, 170 BinaryHprof.ID_SIZE + bytes.length); 171 out.writeInt(id); 172 out.write(bytes, 0, bytes.length); 173 174 return id; 175 } 176 writeCpuSamples(int totalSamples, Set<HprofData.Sample> samples)177 private void writeCpuSamples(int totalSamples, Set<HprofData.Sample> samples) 178 throws IOException { 179 int samplesCount = samples.size(); 180 if (samplesCount == 0) { 181 return; 182 } 183 writeRecordHeader(BinaryHprof.Tag.CPU_SAMPLES, 0, 4 + 4 + (samplesCount * (4 + 4))); 184 out.writeInt(totalSamples); 185 out.writeInt(samplesCount); 186 for (HprofData.Sample sample : samples) { 187 out.writeInt(sample.count); 188 out.writeInt(sample.stackTrace.stackTraceId); 189 } 190 } 191 writeStackTrace(HprofData.StackTrace stackTrace)192 private void writeStackTrace(HprofData.StackTrace stackTrace) throws IOException { 193 int frames = stackTrace.stackFrames.length; 194 int[] stackFrameIds = new int[frames]; 195 for (int i = 0; i < frames; i++) { 196 stackFrameIds[i] = writeStackFrame(stackTrace.stackFrames[i]); 197 } 198 writeRecordHeader(BinaryHprof.Tag.STACK_TRACE, 199 0, 200 4 + 4 + 4 + (frames * BinaryHprof.ID_SIZE)); 201 out.writeInt(stackTrace.stackTraceId); 202 out.writeInt(stackTrace.threadId); 203 out.writeInt(frames); 204 for (int stackFrameId : stackFrameIds) { 205 writeId(stackFrameId); 206 } 207 } 208 writeLoadClass(String className)209 private int writeLoadClass(String className) throws IOException { 210 Integer identifier = classNameToId.get(className); 211 if (identifier != null) { 212 return identifier; 213 } 214 int id = nextClassId++; 215 classNameToId.put(className, id); 216 217 int classNameId = writeString(className); 218 writeRecordHeader(BinaryHprof.Tag.LOAD_CLASS, 219 0, 220 BinaryHprof.Tag.LOAD_CLASS.maximumSize); 221 out.writeInt(id); 222 writeId(0); // class object ID 223 out.writeInt(0); // stack trace where class was loaded is unavailable 224 writeId(classNameId); 225 226 return id; 227 } 228 writeStackFrame(StackTraceElement stackFrame)229 private int writeStackFrame(StackTraceElement stackFrame) throws IOException { 230 Integer identifier = stackFrameToId.get(stackFrame); 231 if (identifier != null) { 232 return identifier; 233 } 234 235 int id = nextStackFrameId++; 236 stackFrameToId.put(stackFrame, id); 237 238 int classId = writeLoadClass(stackFrame.getClassName()); 239 int methodNameId = writeString(stackFrame.getMethodName()); 240 int sourceId = writeString(stackFrame.getFileName()); 241 writeRecordHeader(BinaryHprof.Tag.STACK_FRAME, 242 0, 243 BinaryHprof.Tag.STACK_FRAME.maximumSize); 244 writeId(id); 245 writeId(methodNameId); 246 writeId(0); // method signature is unavailable from StackTraceElement 247 writeId(sourceId); 248 out.writeInt(classId); 249 out.writeInt(stackFrame.getLineNumber()); 250 251 return id; 252 } 253 } 254