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.util.ArrayList; 20 import java.util.Arrays; 21 import java.util.Collections; 22 import java.util.HashMap; 23 import java.util.HashSet; 24 import java.util.List; 25 import java.util.Map.Entry; 26 import java.util.Map; 27 import java.util.Set; 28 29 /** 30 * Represents sampling profiler data. Can be converted to ASCII or 31 * binary hprof-style output using {@link AsciiHprofWriter} or 32 * {@link BinaryHprofWriter}. 33 * <p> 34 * The data includes: 35 * <ul> 36 * <li>the start time of the last sampling period 37 * <li>the history of thread start and end events 38 * <li>stack traces with frequency counts 39 * <ul> 40 */ 41 public final class HprofData { 42 43 public static enum ThreadEventType { START, END }; 44 45 /** 46 * ThreadEvent represents thread creation and death events for 47 * reporting. It provides a record of the thread and thread group 48 * names for tying samples back to their source thread. 49 */ 50 public static final class ThreadEvent { 51 52 public final ThreadEventType type; 53 public final int objectId; 54 public final int threadId; 55 public final String threadName; 56 public final String groupName; 57 public final String parentGroupName; 58 59 public static ThreadEvent start(int objectId, int threadId, String threadName, 60 String groupName, String parentGroupName) { 61 return new ThreadEvent(ThreadEventType.START, objectId, threadId, 62 threadName, groupName, parentGroupName); 63 } 64 65 public static ThreadEvent end(int threadId) { 66 return new ThreadEvent(ThreadEventType.END, threadId); 67 } 68 69 private ThreadEvent(ThreadEventType type, int objectId, int threadId, 70 String threadName, String groupName, String parentGroupName) { 71 if (threadName == null) { 72 throw new NullPointerException("threadName == null"); 73 } 74 this.type = ThreadEventType.START; 75 this.objectId = objectId; 76 this.threadId = threadId; 77 this.threadName = threadName; 78 this.groupName = groupName; 79 this.parentGroupName = parentGroupName; 80 } 81 82 private ThreadEvent(ThreadEventType type, int threadId) { 83 this.type = ThreadEventType.END; 84 this.objectId = -1; 85 this.threadId = threadId; 86 this.threadName = null; 87 this.groupName = null; 88 this.parentGroupName = null; 89 } 90 91 @Override public int hashCode() { 92 int result = 17; 93 result = 31 * result + objectId; 94 result = 31 * result + threadId; 95 result = 31 * result + hashCode(threadName); 96 result = 31 * result + hashCode(groupName); 97 result = 31 * result + hashCode(parentGroupName); 98 return result; 99 } 100 101 private static int hashCode(Object o) { 102 return (o == null) ? 0 : o.hashCode(); 103 } 104 105 @Override public boolean equals(Object o) { 106 if (!(o instanceof ThreadEvent)) { 107 return false; 108 } 109 ThreadEvent event = (ThreadEvent) o; 110 return (this.type == event.type 111 && this.objectId == event.objectId 112 && this.threadId == event.threadId 113 && equal(this.threadName, event.threadName) 114 && equal(this.groupName, event.groupName) 115 && equal(this.parentGroupName, event.parentGroupName)); 116 } 117 118 private static boolean equal(Object a, Object b) { 119 return a == b || (a != null && a.equals(b)); 120 } 121 122 @Override public String toString() { 123 switch (type) { 124 case START: 125 return String.format( 126 "THREAD START (obj=%d, id = %d, name=\"%s\", group=\"%s\")", 127 objectId, threadId, threadName, groupName); 128 case END: 129 return String.format("THREAD END (id = %d)", threadId); 130 } 131 throw new IllegalStateException(type.toString()); 132 } 133 } 134 135 /** 136 * A unique stack trace for a specific thread. 137 */ 138 public static final class StackTrace { 139 140 public final int stackTraceId; 141 int threadId; 142 StackTraceElement[] stackFrames; 143 144 StackTrace() { 145 this.stackTraceId = -1; 146 } 147 148 public StackTrace(int stackTraceId, int threadId, StackTraceElement[] stackFrames) { 149 if (stackFrames == null) { 150 throw new NullPointerException("stackFrames == null"); 151 } 152 this.stackTraceId = stackTraceId; 153 this.threadId = threadId; 154 this.stackFrames = stackFrames; 155 } 156 157 public int getThreadId() { 158 return threadId; 159 } 160 161 public StackTraceElement[] getStackFrames() { 162 return stackFrames; 163 } 164 165 @Override public int hashCode() { 166 int result = 17; 167 result = 31 * result + threadId; 168 result = 31 * result + Arrays.hashCode(stackFrames); 169 return result; 170 } 171 172 @Override public boolean equals(Object o) { 173 if (!(o instanceof StackTrace)) { 174 return false; 175 } 176 StackTrace s = (StackTrace) o; 177 return threadId == s.threadId && Arrays.equals(stackFrames, s.stackFrames); 178 } 179 180 @Override public String toString() { 181 StringBuilder frames = new StringBuilder(); 182 if (stackFrames.length > 0) { 183 frames.append('\n'); 184 for (StackTraceElement stackFrame : stackFrames) { 185 frames.append("\t at "); 186 frames.append(stackFrame); 187 frames.append('\n'); 188 } 189 } else { 190 frames.append("<empty>"); 191 } 192 return "StackTrace[stackTraceId=" + stackTraceId 193 + ", threadId=" + threadId 194 + ", frames=" + frames + "]"; 195 196 } 197 } 198 199 /** 200 * A read only container combining a stack trace with its frequency. 201 */ 202 public static final class Sample { 203 204 public final StackTrace stackTrace; 205 public final int count; 206 207 private Sample(StackTrace stackTrace, int count) { 208 if (stackTrace == null) { 209 throw new NullPointerException("stackTrace == null"); 210 } 211 if (count < 0) { 212 throw new IllegalArgumentException("count < 0:" + count); 213 } 214 this.stackTrace = stackTrace; 215 this.count = count; 216 } 217 218 @Override public int hashCode() { 219 int result = 17; 220 result = 31 * result + stackTrace.hashCode(); 221 result = 31 * result + count; 222 return result; 223 } 224 225 @Override public boolean equals(Object o) { 226 if (!(o instanceof Sample)) { 227 return false; 228 } 229 Sample s = (Sample) o; 230 return count == s.count && stackTrace.equals(s.stackTrace); 231 } 232 233 @Override public String toString() { 234 return "Sample[count=" + count + " " + stackTrace + "]"; 235 } 236 237 } 238 239 /** 240 * Start of last sampling period. 241 */ 242 private long startMillis; 243 244 /** 245 * CONTROL_SETTINGS flags 246 */ 247 private int flags; 248 249 /** 250 * stack sampling depth 251 */ 252 private int depth; 253 254 /** 255 * List of thread creation and death events. 256 */ 257 private final List<ThreadEvent> threadHistory = new ArrayList<ThreadEvent>(); 258 259 /** 260 * Map of thread id to a start ThreadEvent 261 */ 262 private final Map<Integer, ThreadEvent> threadIdToThreadEvent 263 = new HashMap<Integer, ThreadEvent>(); 264 265 /** 266 * Map of stack traces to a mutable sample count. The map is 267 * provided by the creator of the HprofData so only have 268 * mutable access to the int[] cells that contain the sample 269 * count. Only an unmodifiable iterator view is available to 270 * users of the HprofData. 271 */ 272 private final Map<HprofData.StackTrace, int[]> stackTraces; 273 274 public HprofData(Map<StackTrace, int[]> stackTraces) { 275 if (stackTraces == null) { 276 throw new NullPointerException("stackTraces == null"); 277 } 278 this.stackTraces = stackTraces; 279 } 280 281 /** 282 * The start time in milliseconds of the last profiling period. 283 */ 284 public long getStartMillis() { 285 return startMillis; 286 } 287 288 /** 289 * Set the time for the start of the current sampling period. 290 */ 291 public void setStartMillis(long startMillis) { 292 this.startMillis = startMillis; 293 } 294 295 /** 296 * Get the {@link BinaryHprof.ControlSettings} flags 297 */ 298 public int getFlags() { 299 return flags; 300 } 301 302 /** 303 * Set the {@link BinaryHprof.ControlSettings} flags 304 */ 305 public void setFlags(int flags) { 306 this.flags = flags; 307 } 308 309 /** 310 * Get the stack sampling depth 311 */ 312 public int getDepth() { 313 return depth; 314 } 315 316 /** 317 * Set the stack sampling depth 318 */ 319 public void setDepth(int depth) { 320 this.depth = depth; 321 } 322 323 /** 324 * Return an unmodifiable history of start and end thread events. 325 */ 326 public List<ThreadEvent> getThreadHistory() { 327 return Collections.unmodifiableList(threadHistory); 328 } 329 330 /** 331 * Return a new set containing the current sample data. 332 */ 333 public Set<Sample> getSamples() { 334 Set<Sample> samples = new HashSet<Sample>(stackTraces.size()); 335 for (Entry<StackTrace, int[]> e : stackTraces.entrySet()) { 336 StackTrace stackTrace = e.getKey(); 337 int countCell[] = e.getValue(); 338 int count = countCell[0]; 339 Sample sample = new Sample(stackTrace, count); 340 samples.add(sample); 341 } 342 return samples; 343 } 344 345 /** 346 * Record an event in the thread history. 347 */ 348 public void addThreadEvent(ThreadEvent event) { 349 if (event == null) { 350 throw new NullPointerException("event == null"); 351 } 352 ThreadEvent old = threadIdToThreadEvent.put(event.threadId, event); 353 switch (event.type) { 354 case START: 355 if (old != null) { 356 throw new IllegalArgumentException("ThreadEvent already registered for id " 357 + event.threadId); 358 } 359 break; 360 case END: 361 // Do not assert that the END_THREAD matches a 362 // START_THREAD unless in strict mode. While thhis 363 // hold true in the binary hprof BinaryHprofWriter 364 // produces, it is not true of hprof files created 365 // by the RI. However, if there is an event 366 // already registed for a thread id, it should be 367 // the matching start, not a duplicate end. 368 if (old != null && old.type == ThreadEventType.END) { 369 throw new IllegalArgumentException("Duplicate ThreadEvent.end for id " 370 + event.threadId); 371 } 372 break; 373 } 374 threadHistory.add(event); 375 } 376 377 /** 378 * Record an stack trace and an associated int[] cell of 379 * sample cound for the stack trace. The caller is allowed 380 * retain a pointer to the cell to update the count. The 381 * SamplingProfiler intentionally does not present a mutable 382 * view of the count. 383 */ 384 public void addStackTrace(StackTrace stackTrace, int[] countCell) { 385 if (!threadIdToThreadEvent.containsKey(stackTrace.threadId)) { 386 throw new IllegalArgumentException("Unknown thread id " + stackTrace.threadId); 387 } 388 int[] old = stackTraces.put(stackTrace, countCell); 389 if (old != null) { 390 throw new IllegalArgumentException("StackTrace already registered for id " 391 + stackTrace.stackTraceId + ":\n" + stackTrace); 392 } 393 } 394 } 395