summaryrefslogtreecommitdiff
path: root/dalvik/src/main/java/dalvik/system/profiler/HprofData.java
blob: 7b44bb9a5b545de34b80b3c5c1bf6906909b0175 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
/*
 * 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.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map.Entry;
import java.util.Map;
import java.util.Set;

/**
 * Represents sampling profiler data. Can be converted to ASCII or
 * binary hprof-style output using {@link AsciiHprofWriter} or
 * {@link BinaryHprofWriter}.
 * <p>
 * The data includes:
 * <ul>
 * <li>the start time of the last sampling period
 * <li>the history of thread start and end events
 * <li>stack traces with frequency counts
 * <ul>
 */
public final class HprofData {

    public static enum ThreadEventType { START, END };

    /**
     * ThreadEvent represents thread creation and death events for
     * reporting. It provides a record of the thread and thread group
     * names for tying samples back to their source thread.
     */
    public static final class ThreadEvent {

        public final ThreadEventType type;
        public final int objectId;
        public final int threadId;
        public final String threadName;
        public final String groupName;
        public final String parentGroupName;

        public static ThreadEvent start(int objectId, int threadId, String threadName,
                                        String groupName, String parentGroupName) {
            return new ThreadEvent(ThreadEventType.START, objectId, threadId,
                                   threadName, groupName, parentGroupName);
        }

        public static ThreadEvent end(int threadId) {
            return new ThreadEvent(ThreadEventType.END, threadId);
        }

        private ThreadEvent(ThreadEventType type, int objectId, int threadId,
                            String threadName, String groupName, String parentGroupName) {
            if (threadName == null) {
                throw new NullPointerException("threadName == null");
            }
            this.type = ThreadEventType.START;
            this.objectId = objectId;
            this.threadId = threadId;
            this.threadName = threadName;
            this.groupName = groupName;
            this.parentGroupName = parentGroupName;
        }

        private ThreadEvent(ThreadEventType type, int threadId) {
            this.type = ThreadEventType.END;
            this.objectId = -1;
            this.threadId = threadId;
            this.threadName = null;
            this.groupName = null;
            this.parentGroupName = null;
        }

        @Override public int hashCode() {
            int result = 17;
            result = 31 * result + objectId;
            result = 31 * result + threadId;
            result = 31 * result + hashCode(threadName);
            result = 31 * result + hashCode(groupName);
            result = 31 * result + hashCode(parentGroupName);
            return result;
        }

        private static int hashCode(Object o) {
            return (o == null) ? 0 : o.hashCode();
        }

        @Override public boolean equals(Object o) {
            if (!(o instanceof ThreadEvent)) {
                return false;
            }
            ThreadEvent event = (ThreadEvent) o;
            return (this.type == event.type
                    && this.objectId == event.objectId
                    && this.threadId == event.threadId
                    && equal(this.threadName, event.threadName)
                    && equal(this.groupName, event.groupName)
                    && equal(this.parentGroupName, event.parentGroupName));
        }

        private static boolean equal(Object a, Object b) {
            return a == b || (a != null && a.equals(b));
        }

        @Override public String toString() {
            switch (type) {
                case START:
                    return String.format(
                            "THREAD START (obj=%d, id = %d, name=\"%s\", group=\"%s\")",
                            objectId, threadId, threadName, groupName);
                case END:
                    return String.format("THREAD END (id = %d)", threadId);
            }
            throw new IllegalStateException(type.toString());
        }
    }

    /**
     * A unique stack trace for a specific thread.
     */
    public static final class StackTrace {

        public final int stackTraceId;
        int threadId;
        StackTraceElement[] stackFrames;

        StackTrace() {
            this.stackTraceId = -1;
        }

        public StackTrace(int stackTraceId, int threadId, StackTraceElement[] stackFrames) {
            if (stackFrames == null) {
                throw new NullPointerException("stackFrames == null");
            }
            this.stackTraceId = stackTraceId;
            this.threadId = threadId;
            this.stackFrames = stackFrames;
        }

        public int getThreadId() {
            return threadId;
        }

        public StackTraceElement[] getStackFrames() {
            return stackFrames;
        }

        @Override public int hashCode() {
            int result = 17;
            result = 31 * result + threadId;
            result = 31 * result + Arrays.hashCode(stackFrames);
            return result;
        }

        @Override public boolean equals(Object o) {
            if (!(o instanceof StackTrace)) {
                return false;
            }
            StackTrace s = (StackTrace) o;
            return threadId == s.threadId && Arrays.equals(stackFrames, s.stackFrames);
        }

        @Override public String toString() {
            StringBuilder frames = new StringBuilder();
            if (stackFrames.length > 0) {
                frames.append('\n');
                for (StackTraceElement stackFrame : stackFrames) {
                    frames.append("\t at ");
                    frames.append(stackFrame);
                    frames.append('\n');
                }
            } else {
                frames.append("<empty>");
            }
            return "StackTrace[stackTraceId=" + stackTraceId
                    + ", threadId=" + threadId
                    + ", frames=" + frames + "]";

        }
    }

    /**
     * A read only container combining a stack trace with its frequency.
     */
    public static final class Sample {

        public final StackTrace stackTrace;
        public final int count;

        private Sample(StackTrace stackTrace, int count) {
            if (stackTrace == null) {
                throw new NullPointerException("stackTrace == null");
            }
            if (count < 0) {
                throw new IllegalArgumentException("count < 0:" + count);
            }
            this.stackTrace = stackTrace;
            this.count = count;
        }

        @Override public int hashCode() {
            int result = 17;
            result = 31 * result + stackTrace.hashCode();
            result = 31 * result + count;
            return result;
        }

        @Override public boolean equals(Object o) {
            if (!(o instanceof Sample)) {
                return false;
            }
            Sample s = (Sample) o;
            return count == s.count && stackTrace.equals(s.stackTrace);
        }

        @Override public String toString() {
            return "Sample[count=" + count + " " + stackTrace + "]";
        }

    }

    /**
     * Start of last sampling period.
     */
    private long startMillis;

    /**
     * CONTROL_SETTINGS flags
     */
    private int flags;

    /**
     * stack sampling depth
     */
    private int depth;

    /**
     * List of thread creation and death events.
     */
    private final List<ThreadEvent> threadHistory = new ArrayList<ThreadEvent>();

    /**
     * Map of thread id to a start ThreadEvent
     */
    private final Map<Integer, ThreadEvent> threadIdToThreadEvent
            = new HashMap<Integer, ThreadEvent>();

    /**
     * Map of stack traces to a mutable sample count. The map is
     * provided by the creator of the HprofData so only have
     * mutable access to the int[] cells that contain the sample
     * count. Only an unmodifiable iterator view is available to
     * users of the HprofData.
     */
    private final Map<HprofData.StackTrace, int[]> stackTraces;

    public HprofData(Map<StackTrace, int[]> stackTraces) {
        if (stackTraces == null) {
            throw new NullPointerException("stackTraces == null");
        }
        this.stackTraces = stackTraces;
    }

    /**
     * The start time in milliseconds of the last profiling period.
     */
    public long getStartMillis() {
        return startMillis;
    }

    /**
     * Set the time for the start of the current sampling period.
     */
    public void setStartMillis(long startMillis) {
        this.startMillis = startMillis;
    }

    /**
     * Get the {@link BinaryHprof.ControlSettings} flags
     */
    public int getFlags() {
        return flags;
    }

    /**
     * Set the {@link BinaryHprof.ControlSettings} flags
     */
    public void setFlags(int flags) {
        this.flags = flags;
    }

    /**
     * Get the stack sampling depth
     */
    public int getDepth() {
        return depth;
    }

    /**
     * Set the stack sampling depth
     */
    public void setDepth(int depth) {
        this.depth = depth;
    }

    /**
     * Return an unmodifiable history of start and end thread events.
     */
    public List<ThreadEvent> getThreadHistory() {
        return Collections.unmodifiableList(threadHistory);
    }

    /**
     * Return a new set containing the current sample data.
     */
    public Set<Sample> getSamples() {
        Set<Sample> samples = new HashSet<Sample>(stackTraces.size());
        for (Entry<StackTrace, int[]> e : stackTraces.entrySet()) {
            StackTrace stackTrace = e.getKey();
            int countCell[] = e.getValue();
            int count = countCell[0];
            Sample sample = new Sample(stackTrace, count);
            samples.add(sample);
        }
        return samples;
    }

    /**
     * Record an event in the thread history.
     */
    public void addThreadEvent(ThreadEvent event) {
        if (event == null) {
            throw new NullPointerException("event == null");
        }
        ThreadEvent old = threadIdToThreadEvent.put(event.threadId, event);
        switch (event.type) {
            case START:
                if (old != null) {
                    throw new IllegalArgumentException("ThreadEvent already registered for id "
                                                       + event.threadId);
                }
                break;
            case END:
                // Do not assert that the END_THREAD matches a
                // START_THREAD unless in strict mode. While thhis
                // hold true in the binary hprof BinaryHprofWriter
                // produces, it is not true of hprof files created
                // by the RI. However, if there is an event
                // already registed for a thread id, it should be
                // the matching start, not a duplicate end.
                if (old != null && old.type == ThreadEventType.END) {
                    throw new IllegalArgumentException("Duplicate ThreadEvent.end for id "
                                                       + event.threadId);
                }
                break;
        }
        threadHistory.add(event);
    }

    /**
     * Record an stack trace and an associated int[] cell of
     * sample cound for the stack trace. The caller is allowed
     * retain a pointer to the cell to update the count. The
     * SamplingProfiler intentionally does not present a mutable
     * view of the count.
     */
    public void addStackTrace(StackTrace stackTrace, int[] countCell) {
        if (!threadIdToThreadEvent.containsKey(stackTrace.threadId)) {
            throw new IllegalArgumentException("Unknown thread id " + stackTrace.threadId);
        }
        int[] old = stackTraces.put(stackTrace, countCell);
        if (old != null) {
            throw new IllegalArgumentException("StackTrace already registered for id "
                                               + stackTrace.stackTraceId + ":\n" + stackTrace);
        }
    }
}