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