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