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 
start(int objectId, int threadId, String threadName, String groupName, String parentGroupName)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 
end(int threadId)65         public static ThreadEvent end(int threadId) {
66             return new ThreadEvent(ThreadEventType.END, threadId);
67         }
68 
ThreadEvent(ThreadEventType type, int objectId, int threadId, String threadName, String groupName, String parentGroupName)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 
ThreadEvent(ThreadEventType type, int threadId)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 
hashCode()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 
hashCode(Object o)101         private static int hashCode(Object o) {
102             return (o == null) ? 0 : o.hashCode();
103         }
104 
equals(Object o)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 
equal(Object a, Object b)118         private static boolean equal(Object a, Object b) {
119             return a == b || (a != null && a.equals(b));
120         }
121 
toString()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 
StackTrace()144         StackTrace() {
145             this.stackTraceId = -1;
146         }
147 
StackTrace(int stackTraceId, int threadId, StackTraceElement[] stackFrames)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 
getThreadId()157         public int getThreadId() {
158             return threadId;
159         }
160 
getStackFrames()161         public StackTraceElement[] getStackFrames() {
162             return stackFrames;
163         }
164 
hashCode()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 
equals(Object o)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 
toString()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 
Sample(StackTrace stackTrace, int count)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 
hashCode()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 
equals(Object o)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 
toString()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 
HprofData(Map<StackTrace, int[]> stackTraces)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      */
getStartMillis()284     public long getStartMillis() {
285         return startMillis;
286     }
287 
288     /**
289      * Set the time for the start of the current sampling period.
290      */
setStartMillis(long startMillis)291     public void setStartMillis(long startMillis) {
292         this.startMillis = startMillis;
293     }
294 
295     /**
296      * Get the {@link BinaryHprof.ControlSettings} flags
297      */
getFlags()298     public int getFlags() {
299         return flags;
300     }
301 
302     /**
303      * Set the {@link BinaryHprof.ControlSettings} flags
304      */
setFlags(int flags)305     public void setFlags(int flags) {
306         this.flags = flags;
307     }
308 
309     /**
310      * Get the stack sampling depth
311      */
getDepth()312     public int getDepth() {
313         return depth;
314     }
315 
316     /**
317      * Set the stack sampling depth
318      */
setDepth(int depth)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      */
getThreadHistory()326     public List<ThreadEvent> getThreadHistory() {
327         return Collections.unmodifiableList(threadHistory);
328     }
329 
330     /**
331      * Return a new set containing the current sample data.
332      */
getSamples()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      */
addThreadEvent(ThreadEvent event)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      */
addStackTrace(StackTrace stackTrace, int[] countCell)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