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
17package dalvik.system.profiler;
18
19import java.util.ArrayList;
20import java.util.Arrays;
21import java.util.Collections;
22import java.util.HashMap;
23import java.util.HashSet;
24import java.util.List;
25import java.util.Map.Entry;
26import java.util.Map;
27import 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 */
41public 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