Home | History | Annotate | Download | only in profiler
      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