• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2006 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 com.android.traceview;
18 
19 import java.io.BufferedReader;
20 import java.io.File;
21 import java.io.FileInputStream;
22 import java.io.FileNotFoundException;
23 import java.io.IOException;
24 import java.io.InputStreamReader;
25 import java.nio.BufferUnderflowException;
26 import java.nio.ByteOrder;
27 import java.nio.MappedByteBuffer;
28 import java.nio.channels.FileChannel;
29 import java.util.ArrayList;
30 import java.util.Arrays;
31 import java.util.Collection;
32 import java.util.Comparator;
33 import java.util.HashMap;
34 import java.util.regex.Matcher;
35 import java.util.regex.Pattern;
36 
37 public class DmTraceReader extends TraceReader {
38     private static final int TRACE_MAGIC = 0x574f4c53;
39 
40     private static final int METHOD_TRACE_ENTER = 0x00; // method entry
41     private static final int METHOD_TRACE_EXIT = 0x01; // method exit
42     private static final int METHOD_TRACE_UNROLL = 0x02; // method exited by exception unrolling
43 
44     // When in dual clock mode, we report that a context switch has occurred
45     // when skew between the real time and thread cpu clocks is more than this
46     // many microseconds.
47     private static final long MIN_CONTEXT_SWITCH_TIME_USEC = 100;
48 
49     private enum ClockSource {
50         THREAD_CPU, WALL, DUAL,
51     };
52 
53     private int mVersionNumber;
54     private boolean mRegression;
55     private ProfileProvider mProfileProvider;
56     private String mTraceFileName;
57     private MethodData mTopLevel;
58     private ArrayList<Call> mCallList;
59     private HashMap<String, String> mPropertiesMap;
60     private HashMap<Integer, MethodData> mMethodMap;
61     private HashMap<Integer, ThreadData> mThreadMap;
62     private ThreadData[] mSortedThreads;
63     private MethodData[] mSortedMethods;
64     private long mTotalCpuTime;
65     private long mTotalRealTime;
66     private MethodData mContextSwitch;
67     private int mRecordSize;
68     private ClockSource mClockSource;
69 
70     // A regex for matching the thread "id name" lines in the .key file
71     private static final Pattern mIdNamePattern = Pattern.compile("(\\d+)\t(.*)");  //$NON-NLS-1$
72 
DmTraceReader(String traceFileName, boolean regression)73     public DmTraceReader(String traceFileName, boolean regression) throws IOException {
74         mTraceFileName = traceFileName;
75         mRegression = regression;
76         mPropertiesMap = new HashMap<String, String>();
77         mMethodMap = new HashMap<Integer, MethodData>();
78         mThreadMap = new HashMap<Integer, ThreadData>();
79         mCallList = new ArrayList<Call>();
80 
81         // Create a single top-level MethodData object to hold the profile data
82         // for time spent in the unknown caller.
83         mTopLevel = new MethodData(0, "(toplevel)");
84         mContextSwitch = new MethodData(-1, "(context switch)");
85         mMethodMap.put(0, mTopLevel);
86         mMethodMap.put(-1, mContextSwitch);
87         generateTrees();
88     }
89 
generateTrees()90     void generateTrees() throws IOException {
91         long offset = parseKeys();
92         parseData(offset);
93         analyzeData();
94     }
95 
96     @Override
getProfileProvider()97     public ProfileProvider getProfileProvider() {
98         if (mProfileProvider == null)
99             mProfileProvider = new ProfileProvider(this);
100         return mProfileProvider;
101     }
102 
mapFile(String filename, long offset)103     private MappedByteBuffer mapFile(String filename, long offset) throws IOException {
104         MappedByteBuffer buffer = null;
105         FileInputStream dataFile = new FileInputStream(filename);
106         try {
107             File file = new File(filename);
108             FileChannel fc = dataFile.getChannel();
109             buffer = fc.map(FileChannel.MapMode.READ_ONLY, offset, file.length() - offset);
110             buffer.order(ByteOrder.LITTLE_ENDIAN);
111 
112             return buffer;
113         } finally {
114             dataFile.close(); // this *also* closes the associated channel, fc
115         }
116     }
117 
readDataFileHeader(MappedByteBuffer buffer)118     private void readDataFileHeader(MappedByteBuffer buffer) {
119         int magic = buffer.getInt();
120         if (magic != TRACE_MAGIC) {
121             System.err.printf(
122                     "Error: magic number mismatch; got 0x%x, expected 0x%x\n",
123                     magic, TRACE_MAGIC);
124             throw new RuntimeException();
125         }
126 
127         // read version
128         int version = buffer.getShort();
129         if (version != mVersionNumber) {
130             System.err.printf(
131                     "Error: version number mismatch; got %d in data header but %d in options\n",
132                     version, mVersionNumber);
133             throw new RuntimeException();
134         }
135         if (version < 1 || version > 3) {
136             System.err.printf(
137                     "Error: unsupported trace version number %d.  "
138                     + "Please use a newer version of TraceView to read this file.", version);
139             throw new RuntimeException();
140         }
141 
142         // read offset
143         int offsetToData = buffer.getShort() - 16;
144 
145         // read startWhen
146         buffer.getLong();
147 
148         // read record size
149         if (version == 1) {
150             mRecordSize = 9;
151         } else if (version == 2) {
152             mRecordSize = 10;
153         } else {
154             mRecordSize = buffer.getShort();
155             offsetToData -= 2;
156         }
157 
158         // Skip over offsetToData bytes
159         while (offsetToData-- > 0) {
160             buffer.get();
161         }
162     }
163 
parseData(long offset)164     private void parseData(long offset) throws IOException {
165         MappedByteBuffer buffer = mapFile(mTraceFileName, offset);
166         readDataFileHeader(buffer);
167 
168         ArrayList<TraceAction> trace = null;
169         if (mClockSource == ClockSource.THREAD_CPU) {
170             trace = new ArrayList<TraceAction>();
171         }
172 
173         final boolean haveThreadClock = mClockSource != ClockSource.WALL;
174         final boolean haveGlobalClock = mClockSource != ClockSource.THREAD_CPU;
175 
176         // Parse all call records to obtain elapsed time information.
177         ThreadData prevThreadData = null;
178         for (;;) {
179             int threadId;
180             int methodId;
181             long threadTime, globalTime;
182             try {
183                 int recordSize = mRecordSize;
184 
185                 if (mVersionNumber == 1) {
186                     threadId = buffer.get();
187                     recordSize -= 1;
188                 } else {
189                     threadId = buffer.getShort();
190                     recordSize -= 2;
191                 }
192 
193                 methodId = buffer.getInt();
194                 recordSize -= 4;
195 
196                 switch (mClockSource) {
197                     case WALL:
198                         threadTime = 0;
199                         globalTime = buffer.getInt();
200                         recordSize -= 4;
201                         break;
202                     case DUAL:
203                         threadTime = buffer.getInt();
204                         globalTime = buffer.getInt();
205                         recordSize -= 8;
206                         break;
207                     default:
208                     case THREAD_CPU:
209                         threadTime = buffer.getInt();
210                         globalTime = 0;
211                         recordSize -= 4;
212                         break;
213                 }
214 
215                 while (recordSize-- > 0) {
216                     buffer.get();
217                 }
218             } catch (BufferUnderflowException ex) {
219                 break;
220             }
221 
222             int methodAction = methodId & 0x03;
223             methodId = methodId & ~0x03;
224             MethodData methodData = mMethodMap.get(methodId);
225             if (methodData == null) {
226                 String name = String.format("(0x%1$x)", methodId);  //$NON-NLS-1$
227                 methodData = new MethodData(methodId, name);
228                 mMethodMap.put(methodId, methodData);
229             }
230 
231             ThreadData threadData = mThreadMap.get(threadId);
232             if (threadData == null) {
233                 String name = String.format("[%1$d]", threadId);  //$NON-NLS-1$
234                 threadData = new ThreadData(threadId, name, mTopLevel);
235                 mThreadMap.put(threadId, threadData);
236             }
237 
238             long elapsedGlobalTime = 0;
239             if (haveGlobalClock) {
240                 if (!threadData.mHaveGlobalTime) {
241                     threadData.mGlobalStartTime = globalTime;
242                     threadData.mHaveGlobalTime = true;
243                 } else {
244                     elapsedGlobalTime = globalTime - threadData.mGlobalEndTime;
245                 }
246                 threadData.mGlobalEndTime = globalTime;
247             }
248 
249             if (haveThreadClock) {
250                 long elapsedThreadTime = 0;
251                 if (!threadData.mHaveThreadTime) {
252                     threadData.mThreadStartTime = threadTime;
253                     threadData.mThreadCurrentTime = threadTime;
254                     threadData.mHaveThreadTime = true;
255                 } else {
256                     elapsedThreadTime = threadTime - threadData.mThreadEndTime;
257                 }
258                 threadData.mThreadEndTime = threadTime;
259 
260                 if (!haveGlobalClock) {
261                     // Detect context switches whenever execution appears to switch from one
262                     // thread to another.  This assumption is only valid on uniprocessor
263                     // systems (which is why we now have a dual clock mode).
264                     // We represent context switches in the trace by pushing a call record
265                     // with MethodData mContextSwitch onto the stack of the previous
266                     // thread.  We arbitrarily set the start and end time of the context
267                     // switch such that the context switch occurs in the middle of the thread
268                     // time and itself accounts for zero thread time.
269                     if (prevThreadData != null && prevThreadData != threadData) {
270                         // Begin context switch from previous thread.
271                         Call switchCall = prevThreadData.enter(mContextSwitch, trace);
272                         switchCall.mThreadStartTime = prevThreadData.mThreadEndTime;
273                         mCallList.add(switchCall);
274 
275                         // Return from context switch to current thread.
276                         Call top = threadData.top();
277                         if (top.getMethodData() == mContextSwitch) {
278                             threadData.exit(mContextSwitch, trace);
279                             long beforeSwitch = elapsedThreadTime / 2;
280                             top.mThreadStartTime += beforeSwitch;
281                             top.mThreadEndTime = top.mThreadStartTime;
282                         }
283                     }
284                     prevThreadData = threadData;
285                 } else {
286                     // If we have a global clock, then we can detect context switches (or blocking
287                     // calls or cpu suspensions or clock anomalies) by comparing global time to
288                     // thread time for successive calls that occur on the same thread.
289                     // As above, we represent the context switch using a special method call.
290                     long sleepTime = elapsedGlobalTime - elapsedThreadTime;
291                     if (sleepTime > MIN_CONTEXT_SWITCH_TIME_USEC) {
292                         Call switchCall = threadData.enter(mContextSwitch, trace);
293                         long beforeSwitch = elapsedThreadTime / 2;
294                         long afterSwitch = elapsedThreadTime - beforeSwitch;
295                         switchCall.mGlobalStartTime = globalTime - elapsedGlobalTime + beforeSwitch;
296                         switchCall.mGlobalEndTime = globalTime - afterSwitch;
297                         switchCall.mThreadStartTime = threadTime - afterSwitch;
298                         switchCall.mThreadEndTime = switchCall.mThreadStartTime;
299                         threadData.exit(mContextSwitch, trace);
300                         mCallList.add(switchCall);
301                     }
302                 }
303 
304                 // Add thread CPU time.
305                 Call top = threadData.top();
306                 top.addCpuTime(elapsedThreadTime);
307             }
308 
309             switch (methodAction) {
310                 case METHOD_TRACE_ENTER: {
311                     Call call = threadData.enter(methodData, trace);
312                     if (haveGlobalClock) {
313                         call.mGlobalStartTime = globalTime;
314                     }
315                     if (haveThreadClock) {
316                         call.mThreadStartTime = threadTime;
317                     }
318                     mCallList.add(call);
319                     break;
320                 }
321                 case METHOD_TRACE_EXIT:
322                 case METHOD_TRACE_UNROLL: {
323                     Call call = threadData.exit(methodData, trace);
324                     if (call != null) {
325                         if (haveGlobalClock) {
326                             call.mGlobalEndTime = globalTime;
327                         }
328                         if (haveThreadClock) {
329                             call.mThreadEndTime = threadTime;
330                         }
331                     }
332                     break;
333                 }
334                 default:
335                     throw new RuntimeException("Unrecognized method action: " + methodAction);
336             }
337         }
338 
339         // Exit any pending open-ended calls.
340         for (ThreadData threadData : mThreadMap.values()) {
341             threadData.endTrace(trace);
342         }
343 
344         // Recreate the global timeline from thread times, if needed.
345         if (!haveGlobalClock) {
346             long globalTime = 0;
347             prevThreadData = null;
348             for (TraceAction traceAction : trace) {
349                 Call call = traceAction.mCall;
350                 ThreadData threadData = call.getThreadData();
351 
352                 if (traceAction.mAction == TraceAction.ACTION_ENTER) {
353                     long threadTime = call.mThreadStartTime;
354                     globalTime += call.mThreadStartTime - threadData.mThreadCurrentTime;
355                     call.mGlobalStartTime = globalTime;
356                     if (!threadData.mHaveGlobalTime) {
357                         threadData.mHaveGlobalTime = true;
358                         threadData.mGlobalStartTime = globalTime;
359                     }
360                     threadData.mThreadCurrentTime = threadTime;
361                 } else if (traceAction.mAction == TraceAction.ACTION_EXIT) {
362                     long threadTime = call.mThreadEndTime;
363                     globalTime += call.mThreadEndTime - threadData.mThreadCurrentTime;
364                     call.mGlobalEndTime = globalTime;
365                     threadData.mGlobalEndTime = globalTime;
366                     threadData.mThreadCurrentTime = threadTime;
367                 } // else, ignore ACTION_INCOMPLETE calls, nothing to do
368                 prevThreadData = threadData;
369             }
370         }
371 
372         // Finish updating all calls and calculate the total time spent.
373         for (int i = mCallList.size() - 1; i >= 0; i--) {
374             Call call = mCallList.get(i);
375 
376             // Calculate exclusive real-time by subtracting inclusive real time
377             // accumulated by children from the total span.
378             long realTime = call.mGlobalEndTime - call.mGlobalStartTime;
379             call.mExclusiveRealTime = Math.max(realTime - call.mInclusiveRealTime, 0);
380             call.mInclusiveRealTime = realTime;
381 
382             call.finish();
383         }
384         mTotalCpuTime = 0;
385         mTotalRealTime = 0;
386         for (ThreadData threadData : mThreadMap.values()) {
387             Call rootCall = threadData.getRootCall();
388             threadData.updateRootCallTimeBounds();
389             rootCall.finish();
390             mTotalCpuTime += rootCall.mInclusiveCpuTime;
391             mTotalRealTime += rootCall.mInclusiveRealTime;
392         }
393 
394         if (mRegression) {
395             System.out.format("totalCpuTime %dus\n", mTotalCpuTime);
396             System.out.format("totalRealTime %dus\n", mTotalRealTime);
397 
398             dumpThreadTimes();
399             dumpCallTimes();
400         }
401     }
402 
403     static final int PARSE_VERSION = 0;
404     static final int PARSE_THREADS = 1;
405     static final int PARSE_METHODS = 2;
406     static final int PARSE_OPTIONS = 4;
407 
parseKeys()408     long parseKeys() throws IOException {
409         long offset = 0;
410         BufferedReader in = null;
411         try {
412             in = new BufferedReader(new InputStreamReader(
413                     new FileInputStream(mTraceFileName), "US-ASCII"));
414 
415             int mode = PARSE_VERSION;
416             String line = null;
417             while (true) {
418                 line = in.readLine();
419                 if (line == null) {
420                     throw new IOException("Key section does not have an *end marker");
421                 }
422 
423                 // Calculate how much we have read from the file so far.  The
424                 // extra byte is for the line ending not included by readLine().
425                 offset += line.length() + 1;
426                 if (line.startsWith("*")) {
427                     if (line.equals("*version")) {
428                         mode = PARSE_VERSION;
429                         continue;
430                     }
431                     if (line.equals("*threads")) {
432                         mode = PARSE_THREADS;
433                         continue;
434                     }
435                     if (line.equals("*methods")) {
436                         mode = PARSE_METHODS;
437                         continue;
438                     }
439                     if (line.equals("*end")) {
440                         break;
441                     }
442                 }
443                 switch (mode) {
444                 case PARSE_VERSION:
445                     mVersionNumber = Integer.decode(line);
446                     mode = PARSE_OPTIONS;
447                     break;
448                 case PARSE_THREADS:
449                     parseThread(line);
450                     break;
451                 case PARSE_METHODS:
452                     parseMethod(line);
453                     break;
454                 case PARSE_OPTIONS:
455                     parseOption(line);
456                     break;
457                 }
458             }
459         } catch (FileNotFoundException ex) {
460             System.err.println(ex.getMessage());
461         } finally {
462             if (in != null) {
463                 in.close();
464             }
465         }
466 
467         if (mClockSource == null) {
468             mClockSource = ClockSource.THREAD_CPU;
469         }
470 
471         return offset;
472     }
473 
parseOption(String line)474     void parseOption(String line) {
475         String[] tokens = line.split("=");
476         if (tokens.length == 2) {
477             String key = tokens[0];
478             String value = tokens[1];
479             mPropertiesMap.put(key, value);
480 
481             if (key.equals("clock")) {
482                 if (value.equals("thread-cpu")) {
483                     mClockSource = ClockSource.THREAD_CPU;
484                 } else if (value.equals("wall")) {
485                     mClockSource = ClockSource.WALL;
486                 } else if (value.equals("dual")) {
487                     mClockSource = ClockSource.DUAL;
488                 }
489             }
490         }
491     }
492 
parseThread(String line)493     void parseThread(String line) {
494         String idStr = null;
495         String name = null;
496         Matcher matcher = mIdNamePattern.matcher(line);
497         if (matcher.find()) {
498             idStr = matcher.group(1);
499             name = matcher.group(2);
500         }
501         if (idStr == null) return;
502         if (name == null) name = "(unknown)";
503 
504         int id = Integer.decode(idStr);
505         mThreadMap.put(id, new ThreadData(id, name, mTopLevel));
506     }
507 
parseMethod(String line)508     void parseMethod(String line) {
509         String[] tokens = line.split("\t");
510         int id = Long.decode(tokens[0]).intValue();
511         String className = tokens[1];
512         String methodName = null;
513         String signature = null;
514         String pathname = null;
515         int lineNumber = -1;
516         if (tokens.length == 6) {
517             methodName = tokens[2];
518             signature = tokens[3];
519             pathname = tokens[4];
520             lineNumber = Integer.decode(tokens[5]);
521             pathname = constructPathname(className, pathname);
522         } else if (tokens.length > 2) {
523             if (tokens[3].startsWith("(")) {
524                 methodName = tokens[2];
525                 signature = tokens[3];
526             } else {
527                 pathname = tokens[2];
528                 lineNumber = Integer.decode(tokens[3]);
529             }
530         }
531 
532         mMethodMap.put(id, new MethodData(id, className, methodName, signature,
533                 pathname, lineNumber));
534     }
535 
constructPathname(String className, String pathname)536     private String constructPathname(String className, String pathname) {
537         int index = className.lastIndexOf('/');
538         if (index > 0 && index < className.length() - 1
539                 && pathname.endsWith(".java"))
540             pathname = className.substring(0, index + 1) + pathname;
541         return pathname;
542     }
543 
analyzeData()544     private void analyzeData() {
545         final TimeBase timeBase = getPreferredTimeBase();
546 
547         // Sort the threads into decreasing cpu time
548         Collection<ThreadData> tv = mThreadMap.values();
549         mSortedThreads = tv.toArray(new ThreadData[tv.size()]);
550         Arrays.sort(mSortedThreads, new Comparator<ThreadData>() {
551             @Override
552             public int compare(ThreadData td1, ThreadData td2) {
553                 if (timeBase.getTime(td2) > timeBase.getTime(td1))
554                     return 1;
555                 if (timeBase.getTime(td2) < timeBase.getTime(td1))
556                     return -1;
557                 return td2.getName().compareTo(td1.getName());
558             }
559         });
560 
561         // Sort the methods into decreasing inclusive time
562         Collection<MethodData> mv = mMethodMap.values();
563         MethodData[] methods;
564         methods = mv.toArray(new MethodData[mv.size()]);
565         Arrays.sort(methods, new Comparator<MethodData>() {
566             @Override
567             public int compare(MethodData md1, MethodData md2) {
568                 if (timeBase.getElapsedInclusiveTime(md2) > timeBase.getElapsedInclusiveTime(md1))
569                     return 1;
570                 if (timeBase.getElapsedInclusiveTime(md2) < timeBase.getElapsedInclusiveTime(md1))
571                     return -1;
572                 return md1.getName().compareTo(md2.getName());
573             }
574         });
575 
576         // Count the number of methods with non-zero inclusive time
577         int nonZero = 0;
578         for (MethodData md : methods) {
579             if (timeBase.getElapsedInclusiveTime(md) == 0)
580                 break;
581             nonZero += 1;
582         }
583 
584         // Copy the methods with non-zero time
585         mSortedMethods = new MethodData[nonZero];
586         int ii = 0;
587         for (MethodData md : methods) {
588             if (timeBase.getElapsedInclusiveTime(md) == 0)
589                 break;
590             md.setRank(ii);
591             mSortedMethods[ii++] = md;
592         }
593 
594         // Let each method analyze its profile data
595         for (MethodData md : mSortedMethods) {
596             md.analyzeData(timeBase);
597         }
598 
599         // Update all the calls to include the method rank in
600         // their name.
601         for (Call call : mCallList) {
602             call.updateName();
603         }
604 
605         if (mRegression) {
606             dumpMethodStats();
607         }
608     }
609 
610     /*
611      * This method computes a list of records that describe the the execution
612      * timeline for each thread. Each record is a pair: (row, block) where: row:
613      * is the ThreadData object block: is the call (containing the start and end
614      * times)
615      */
616     @Override
getThreadTimeRecords()617     public ArrayList<TimeLineView.Record> getThreadTimeRecords() {
618         TimeLineView.Record record;
619         ArrayList<TimeLineView.Record> timeRecs;
620         timeRecs = new ArrayList<TimeLineView.Record>();
621 
622         // For each thread, push a "toplevel" call that encompasses the
623         // entire execution of the thread.
624         for (ThreadData threadData : mSortedThreads) {
625             if (!threadData.isEmpty() && threadData.getId() != 0) {
626                 record = new TimeLineView.Record(threadData, threadData.getRootCall());
627                 timeRecs.add(record);
628             }
629         }
630 
631         for (Call call : mCallList) {
632             record = new TimeLineView.Record(call.getThreadData(), call);
633             timeRecs.add(record);
634         }
635 
636         if (mRegression) {
637             dumpTimeRecs(timeRecs);
638             System.exit(0);
639         }
640         return timeRecs;
641     }
642 
dumpThreadTimes()643     private void dumpThreadTimes() {
644         System.out.print("\nThread Times\n");
645         System.out.print("id  t-start    t-end  g-start    g-end     name\n");
646         for (ThreadData threadData : mThreadMap.values()) {
647             System.out.format("%2d %8d %8d %8d %8d  %s\n",
648                     threadData.getId(),
649                     threadData.mThreadStartTime, threadData.mThreadEndTime,
650                     threadData.mGlobalStartTime, threadData.mGlobalEndTime,
651                     threadData.getName());
652         }
653     }
654 
dumpCallTimes()655     private void dumpCallTimes() {
656         System.out.print("\nCall Times\n");
657         System.out.print("id  t-start    t-end  g-start    g-end    excl.    incl.  method\n");
658         for (Call call : mCallList) {
659             System.out.format("%2d %8d %8d %8d %8d %8d %8d  %s\n",
660                     call.getThreadId(), call.mThreadStartTime, call.mThreadEndTime,
661                     call.mGlobalStartTime, call.mGlobalEndTime,
662                     call.mExclusiveCpuTime, call.mInclusiveCpuTime,
663                     call.getMethodData().getName());
664         }
665     }
666 
dumpMethodStats()667     private void dumpMethodStats() {
668         System.out.print("\nMethod Stats\n");
669         System.out.print("Excl Cpu  Incl Cpu  Excl Real Incl Real    Calls  Method\n");
670         for (MethodData md : mSortedMethods) {
671             System.out.format("%9d %9d %9d %9d %9s  %s\n",
672                     md.getElapsedExclusiveCpuTime(), md.getElapsedInclusiveCpuTime(),
673                     md.getElapsedExclusiveRealTime(), md.getElapsedInclusiveRealTime(),
674                     md.getCalls(), md.getProfileName());
675         }
676     }
677 
dumpTimeRecs(ArrayList<TimeLineView.Record> timeRecs)678     private void dumpTimeRecs(ArrayList<TimeLineView.Record> timeRecs) {
679         System.out.print("\nTime Records\n");
680         System.out.print("id  t-start    t-end  g-start    g-end  method\n");
681         for (TimeLineView.Record record : timeRecs) {
682             Call call = (Call) record.block;
683             System.out.format("%2d %8d %8d %8d %8d  %s\n",
684                     call.getThreadId(), call.mThreadStartTime, call.mThreadEndTime,
685                     call.mGlobalStartTime, call.mGlobalEndTime,
686                     call.getMethodData().getName());
687         }
688     }
689 
690     @Override
getThreadLabels()691     public HashMap<Integer, String> getThreadLabels() {
692         HashMap<Integer, String> labels = new HashMap<Integer, String>();
693         for (ThreadData t : mThreadMap.values()) {
694             labels.put(t.getId(), t.getName());
695         }
696         return labels;
697     }
698 
699     @Override
getMethods()700     public MethodData[] getMethods() {
701         return mSortedMethods;
702     }
703 
704     @Override
getThreads()705     public ThreadData[] getThreads() {
706         return mSortedThreads;
707     }
708 
709     @Override
getTotalCpuTime()710     public long getTotalCpuTime() {
711         return mTotalCpuTime;
712     }
713 
714     @Override
getTotalRealTime()715     public long getTotalRealTime() {
716         return mTotalRealTime;
717     }
718 
719     @Override
haveCpuTime()720     public boolean haveCpuTime() {
721         return mClockSource != ClockSource.WALL;
722     }
723 
724     @Override
haveRealTime()725     public boolean haveRealTime() {
726         return mClockSource != ClockSource.THREAD_CPU;
727     }
728 
729     @Override
getProperties()730     public HashMap<String, String> getProperties() {
731         return mPropertiesMap;
732     }
733 
734     @Override
getPreferredTimeBase()735     public TimeBase getPreferredTimeBase() {
736         if (mClockSource == ClockSource.WALL) {
737             return TimeBase.REAL_TIME;
738         }
739         return TimeBase.CPU_TIME;
740     }
741 
742     @Override
getClockSource()743     public String getClockSource() {
744         switch (mClockSource) {
745             case THREAD_CPU:
746                 return "cpu time";
747             case WALL:
748                 return "real time";
749             case DUAL:
750                 return "real time, dual clock";
751         }
752         return null;
753     }
754 }
755