1 /* 2 * Copyright (C) 2015 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.performance.tests; 18 19 import com.android.ddmlib.IDevice; 20 import com.android.ddmlib.testrunner.IRemoteAndroidTestRunner; 21 import com.android.ddmlib.testrunner.RemoteAndroidTestRunner; 22 import com.android.ddmlib.testrunner.TestResult; 23 import com.android.tradefed.config.Option; 24 import com.android.tradefed.config.Option.Importance; 25 import com.android.tradefed.device.DeviceNotAvailableException; 26 import com.android.tradefed.device.ITestDevice; 27 import com.android.tradefed.device.LogcatReceiver; 28 import com.android.tradefed.log.LogUtil.CLog; 29 import com.android.tradefed.result.CollectingTestListener; 30 import com.android.tradefed.result.FileInputStreamSource; 31 import com.android.tradefed.result.ITestInvocationListener; 32 import com.android.tradefed.result.InputStreamSource; 33 import com.android.tradefed.result.LogDataType; 34 import com.android.tradefed.testtype.IDeviceTest; 35 import com.android.tradefed.testtype.IRemoteTest; 36 import com.android.tradefed.util.FileUtil; 37 import com.android.tradefed.util.StreamUtil; 38 39 import org.junit.Assert; 40 41 import java.io.BufferedReader; 42 import java.io.File; 43 import java.io.FileNotFoundException; 44 import java.io.FileReader; 45 import java.io.IOException; 46 import java.io.InputStreamReader; 47 import java.util.ArrayList; 48 import java.util.Collection; 49 import java.util.HashMap; 50 import java.util.HashSet; 51 import java.util.LinkedList; 52 import java.util.List; 53 import java.util.Map; 54 import java.util.Set; 55 import java.util.regex.Matcher; 56 import java.util.regex.Pattern; 57 58 /** 59 * To test the app launch performance for the list of activities present in the given target package 60 * or the custom list of activities present in the target package.Activities are launched number of 61 * times present in the launch count. Launch time is analyzed from the logcat data and more detailed 62 * timing(section names) is analyzed from the atrace files captured when launching each activity. 63 */ 64 public class HermeticLaunchTest implements IRemoteTest, IDeviceTest { 65 66 private static enum AtraceSectionOptions { 67 LAYOUT("layout"), 68 DRAW("draw"), 69 BINDAPPLICATION("bindApplication"), 70 ACTIVITYSTART("activityStart"), 71 ONCREATE("onCreate"); 72 73 private final String name; 74 75 private AtraceSectionOptions(String s) { 76 this.name = s; 77 } 78 79 @Override 80 public String toString() { 81 return name; 82 } 83 } 84 85 private static final String TOTALLAUNCHTIME = "totalLaunchTime"; 86 private static final String LOGCAT_CMD = "logcat -v threadtime ActivityManager:* *:s"; 87 private static final String LAUNCH_PREFIX="^\\d*-\\d*\\s*\\d*:\\d*:\\d*.\\d*\\s*\\d*\\s*" 88 + "\\d*\\s*I ActivityManager: Displayed\\s*"; 89 private static final String LAUNCH_SUFFIX=":\\s*\\+(?<launchtime>.[a-zA-Z\\d]*)\\s*" 90 + "(?<totallaunch>.*)\\s*$"; 91 private static final Pattern LAUNCH_ENTRY=Pattern.compile("^\\d*-\\d*\\s*\\d*:\\d*:\\d*." 92 + "\\d*\\s*\\d*\\s*\\d*\\s*I ActivityManager: Displayed\\s*(?<launchinfo>.*)\\s*$"); 93 private static final Pattern TRACE_ENTRY1 = Pattern.compile( 94 "^[^-]*-(?<tid>\\d+)\\s+\\[\\d+\\]\\s+\\S{4}\\s+" + 95 "(?<secs>\\d+)\\.(?<usecs>\\d+):\\s+(?<function>.*)\\s*$"); 96 private static final Pattern TRACE_ENTRY2 = Pattern.compile( 97 "^[^-]*-(?<tid>\\d+)\\s*\\(\\s*\\d*-*\\)\\s*\\[\\d+\\]\\s+\\S{4}\\s+" + 98 "(?<secs>\\d+)\\.(?<usecs>\\d+):\\s+(?<function>.*)\\s*$"); 99 private static final Pattern ATRACE_BEGIN = Pattern 100 .compile("tracing_mark_write: B\\|(?<pid>\\d+)\\|(?<name>.+)"); 101 private static final Pattern ATRACE_END = Pattern.compile("tracing_mark_write: E"); 102 private static final Pattern ATRACE_COUNTER = Pattern 103 .compile("tracing_mark_write: C\\|(?<pid>\\d+)\\|(?<name>[^|]+)\\|(?<value>\\d+)"); 104 private static final Pattern ATRACE_HEADER_ENTRIES = Pattern 105 .compile("# entries-in-buffer/entries-written:\\s+(?<buffered>\\d+)/" 106 + "(?<written>\\d+)\\s+#P:\\d+\\s*"); 107 private static final int LOGCAT_SIZE = 20971520; //20 mb 108 private static final long SEC_TO_MILLI = 1000; 109 private static final long MILLI_TO_MICRO = 1000; 110 111 @Option(name = "runner", description = "The instrumentation test runner class name to use.") 112 private String mRunnerName = "android.support.test.runner.AndroidJUnitRunner"; 113 114 @Option(name = "package", shortName = 'p', 115 description = "The manifest package name of the Android test application to run.", 116 importance = Importance.IF_UNSET) 117 private String mPackageName = "com.android.performanceapp.tests"; 118 119 @Option(name = "target-package", description = "package which contains all the " 120 + "activities to launch") 121 private String mtargetPackage = null; 122 123 @Option(name = "activity-names", description = "Fully qualified activity " 124 + "names separated by comma" 125 + "If not set then all the activities will be included for launching") 126 private String mactivityNames = ""; 127 128 @Option(name = "launch-count", description = "number of time to launch the each activity") 129 private int mlaunchCount = 10; 130 131 @Option(name = "save-atrace", description = "Upload the atrace file in permanent storage") 132 private boolean msaveAtrace = false; 133 134 @Option(name = "atrace-section", description = "Section to be parsed from atrace file. " 135 + "This option can be repeated") 136 private Set<AtraceSectionOptions> mSectionOptionSet = new HashSet<>(); 137 138 private ITestDevice mDevice = null; 139 private IRemoteAndroidTestRunner mRunner; 140 private LogcatReceiver mLogcat; 141 private Set<String> mSectionSet = new HashSet<>(); 142 private Map<String, String> mActivityTraceFileMap; 143 private Map<String, Map<String, String>> mActivityTimeResultMap = new HashMap<>(); 144 private Map<String,String> activityErrMsg=new HashMap<>(); 145 146 @Override 147 public void run(ITestInvocationListener listener) 148 throws DeviceNotAvailableException { 149 150 mLogcat = new LogcatReceiver(getDevice(), LOGCAT_CMD, LOGCAT_SIZE, 0); 151 mLogcat.start(); 152 try { 153 if (mSectionOptionSet.isEmpty()) { 154 // Default sections 155 mSectionOptionSet.add(AtraceSectionOptions.LAYOUT); 156 mSectionOptionSet.add(AtraceSectionOptions.DRAW); 157 mSectionOptionSet.add(AtraceSectionOptions.BINDAPPLICATION); 158 mSectionOptionSet.add(AtraceSectionOptions.ACTIVITYSTART); 159 mSectionOptionSet.add(AtraceSectionOptions.ONCREATE); 160 } else if (mSectionOptionSet.contains(AtraceSectionOptions.LAYOUT)) { 161 // If layout is added, draw should also be included 162 mSectionOptionSet.add(AtraceSectionOptions.DRAW); 163 } 164 165 for (AtraceSectionOptions sectionOption : mSectionOptionSet) { 166 mSectionSet.add(sectionOption.toString()); 167 } 168 169 //Remove if there is already existing atrace_logs folder 170 mDevice.executeShellCommand("rm -rf ${EXTERNAL_STORAGE}/atrace_logs"); 171 172 mRunner = createRemoteAndroidTestRunner(mPackageName, mRunnerName, 173 mDevice.getIDevice()); 174 CollectingTestListener collectingListener = new CollectingTestListener(); 175 mDevice.runInstrumentationTests(mRunner, collectingListener); 176 177 Collection<TestResult> testResultsCollection = collectingListener 178 .getCurrentRunResults().getTestResults().values(); 179 List<TestResult> testResults = new ArrayList<>( 180 testResultsCollection); 181 /* 182 * Expected Metrics : Map of <activity name>=<comma separated list of atrace file names in 183 * external storage of the device> 184 */ 185 mActivityTraceFileMap = testResults.get(0).getMetrics(); 186 Assert.assertTrue("Unable to get the path to the trace files stored in the device", 187 (mActivityTraceFileMap != null && !mActivityTraceFileMap.isEmpty())); 188 189 // Analyze the logcat data to get total launch time 190 analyzeLogCatData(mActivityTraceFileMap.keySet()); 191 } finally { 192 // Stop the logcat 193 mLogcat.stop(); 194 } 195 196 // Analyze the atrace data to get bindApplication,activityStart etc.. 197 analyzeAtraceData(listener); 198 199 // Report the metrics to dashboard 200 reportMetrics(listener); 201 } 202 203 /** 204 * Report run metrics by creating an empty test run to stick them in. 205 * @param listener The {@link ITestInvocationListener} of test results 206 */ 207 private void reportMetrics(ITestInvocationListener listener) { 208 for (String activityName : mActivityTimeResultMap.keySet()) { 209 // Get the activity name alone from pkgname.activityname 210 String[] activityNameSplit = activityName.split("\\."); 211 if (!activityErrMsg.containsKey(activityName)) { 212 Map<String, String> activityMetrics = mActivityTimeResultMap.get(activityName); 213 if (activityMetrics != null && !activityMetrics.isEmpty()) { 214 CLog.v("Metrics for the activity : %s", activityName); 215 for (String sectionName : activityMetrics.keySet()) { 216 CLog.v(String.format("Section name : %s - Time taken : %s", 217 sectionName, activityMetrics.get(sectionName))); 218 } 219 listener.testRunStarted( 220 activityNameSplit[activityNameSplit.length - 1].trim(), 0); 221 listener.testRunEnded(0, activityMetrics); 222 } 223 } else { 224 listener.testRunStarted( 225 activityNameSplit[activityNameSplit.length - 1].trim(), 0); 226 listener.testRunFailed(activityErrMsg.get(activityName)); 227 } 228 } 229 } 230 231 /** 232 * Method to create the runner with given list of arguments 233 * @return the {@link IRemoteAndroidTestRunner} to use. 234 * @throws DeviceNotAvailableException 235 */ 236 IRemoteAndroidTestRunner createRemoteAndroidTestRunner(String packageName, 237 String runnerName, IDevice device) 238 throws DeviceNotAvailableException { 239 RemoteAndroidTestRunner runner = new RemoteAndroidTestRunner( 240 packageName, runnerName, device); 241 runner.addInstrumentationArg("targetpackage", mtargetPackage); 242 runner.addInstrumentationArg("launchcount", mlaunchCount + ""); 243 if (mactivityNames != null && !mactivityNames.isEmpty()) { 244 runner.addInstrumentationArg("activitylist", mactivityNames); 245 } 246 return runner; 247 } 248 249 /** 250 * To analyze the log cat data to get the display time reported by activity manager during the 251 * launches 252 * activitySet is set of activityNames returned as a part of testMetrics from the device 253 */ 254 public void analyzeLogCatData(Set<String> activitySet) { 255 Map<String, List<Integer>> amLaunchTimes = new HashMap<>(); 256 257 Map<Pattern, String> activityPatternMap = new HashMap<>(); 258 Matcher match = null; 259 String line; 260 261 /* 262 * Sample line format in logcat 263 * 06-17 16:55:49.6 60 642 I ActivityManager: Displayed pkg/.activity: +Tms (total +9s9ms) 264 */ 265 for (String activityName : activitySet) { 266 int lastIndex = activityName.lastIndexOf("."); 267 /* 268 * actvitySet has set of activity names in the format packageName.activityName 269 * logcat has the format packageName/.activityName --> activityAlias 270 */ 271 String activityAlias = activityName.subSequence(0, lastIndex) 272 + "/" + activityName.subSequence(lastIndex, activityName.length()); 273 String finalPattern = LAUNCH_PREFIX + activityAlias + LAUNCH_SUFFIX; 274 activityPatternMap.put(Pattern.compile(finalPattern), 275 activityName); 276 } 277 278 try (InputStreamSource input = mLogcat.getLogcatData(); 279 BufferedReader br = 280 new BufferedReader(new InputStreamReader(input.createInputStream()))) { 281 while ((line = br.readLine()) != null) { 282 /* 283 * Launch entry needed otherwise we will end up in comparing all the lines for all 284 * the patterns 285 */ 286 if ((match = matches(LAUNCH_ENTRY, line)) != null) { 287 for (Pattern pattern : activityPatternMap.keySet()) { 288 if ((match = matches(pattern, line)) != null) { 289 CLog.v("Launch Info : %s", line); 290 int displayTimeInMs = extractLaunchTime(match.group("launchtime")); 291 String activityName = activityPatternMap.get(pattern); 292 if (amLaunchTimes.containsKey(activityName)) { 293 amLaunchTimes.get(activityName).add(displayTimeInMs); 294 } else { 295 List<Integer> launchTimes = new ArrayList<>(); 296 launchTimes.add(displayTimeInMs); 297 amLaunchTimes.put(activityName, launchTimes); 298 } 299 } 300 } 301 } 302 } 303 } catch (IOException io) { 304 CLog.e(io); 305 } 306 307 // Verify logcat data 308 for (String activityName : amLaunchTimes.keySet()) { 309 Assert.assertEquals("Data lost for launch time for the activity :" 310 + activityName, amLaunchTimes.get(activityName).size(), mlaunchCount); 311 } 312 313 /* 314 * Extract and store the average launch time data reported by activity manager for each 315 * activity 316 */ 317 for (String activityName : amLaunchTimes.keySet()) { 318 Double totalTime = 0d; 319 for (Integer launchTime : amLaunchTimes.get(activityName)) { 320 totalTime += launchTime; 321 } 322 Double averageTime = Double.valueOf(totalTime / amLaunchTimes.get(activityName).size()); 323 if (mActivityTimeResultMap.containsKey(activityName)) { 324 mActivityTimeResultMap.get(activityName).put(TOTALLAUNCHTIME, 325 String.format("%.2f", averageTime)); 326 } else { 327 Map<String, String> launchTime = new HashMap<>(); 328 launchTime.put(TOTALLAUNCHTIME, 329 String.format("%.2f", averageTime)); 330 mActivityTimeResultMap.put(activityName, launchTime); 331 } 332 } 333 } 334 335 /** 336 * To extract the launch time displayed in given line 337 * 338 * @param duration 339 * @return 340 */ 341 public int extractLaunchTime(String duration) { 342 String formattedString = duration.replace("ms", ""); 343 if (formattedString.contains("s")) { 344 String[] splitString = formattedString.split("s"); 345 int finalTimeInMs = Integer.parseInt(splitString[0]) * 1000; 346 finalTimeInMs = finalTimeInMs + Integer.parseInt(splitString[1]); 347 return finalTimeInMs; 348 } else { 349 return Integer.parseInt(formattedString); 350 } 351 } 352 353 /** 354 * To analyze the trace data collected in the device during each activity launch. 355 */ 356 public void analyzeAtraceData(ITestInvocationListener listener) throws DeviceNotAvailableException { 357 for (String activityName : mActivityTraceFileMap.keySet()) { 358 try { 359 // Get the list of associated filenames for given activity 360 String filePathAll = mActivityTraceFileMap.get(activityName); 361 Assert.assertNotNull( 362 String.format("Unable to find trace file paths for activity : %s", 363 activityName), filePathAll); 364 String[] filePaths = filePathAll.split(","); 365 Assert.assertEquals(String.format("Unable to find file path for all the launches " 366 + "for the activity :%s", activityName), filePaths.length, mlaunchCount); 367 // Pull and parse the info 368 List<Map<String, List<SectionPeriod>>> mutipleLaunchTraceInfo = 369 new LinkedList<>(); 370 for (int count = 0; count < filePaths.length; count++) { 371 File currentAtraceFile = pullAtraceInfoFile(filePaths[count]); 372 String[] splitName = filePaths[count].split("-"); 373 // Process id is appended to original file name 374 Map<String, List<SectionPeriod>> singleLaunchTraceInfo = parseAtraceInfoFile( 375 currentAtraceFile, 376 splitName[splitName.length - 1]); 377 // Upload the file if needed 378 if (msaveAtrace) { 379 try (FileInputStreamSource stream = 380 new FileInputStreamSource(currentAtraceFile)) { 381 listener.testLog(currentAtraceFile.getName(), LogDataType.TEXT, stream); 382 } 383 } 384 // Remove the atrace files 385 FileUtil.deleteFile(currentAtraceFile); 386 mutipleLaunchTraceInfo.add(singleLaunchTraceInfo); 387 } 388 389 // Verify and Average out the aTrace Info and store it in result map 390 averageAtraceData(activityName, mutipleLaunchTraceInfo); 391 } catch (FileNotFoundException foe) { 392 CLog.e(foe); 393 activityErrMsg.put(activityName, 394 "Unable to find the trace file for the activity launch :" + activityName); 395 } catch (IOException ioe) { 396 CLog.e(ioe); 397 activityErrMsg.put(activityName, 398 "Unable to read the contents of the atrace file for the activity :" 399 + activityName); 400 } 401 } 402 403 } 404 405 /** 406 * To pull the trace file from the device 407 * @param aTraceFile 408 * @return 409 * @throws DeviceNotAvailableException 410 */ 411 public File pullAtraceInfoFile(String aTraceFile) 412 throws DeviceNotAvailableException { 413 String dir = "${EXTERNAL_STORAGE}/atrace_logs"; 414 File atraceFileHandler = null; 415 atraceFileHandler = getDevice().pullFile(dir + "/" + aTraceFile); 416 Assert.assertTrue("Unable to retrieve the atrace files", atraceFileHandler != null); 417 return atraceFileHandler; 418 } 419 420 /** 421 * To parse and find the time taken for the given section names in each launch 422 * @param currentAtraceFile 423 * @param sectionSet 424 * @param processId 425 * @return 426 * @throws FileNotFoundException,IOException 427 */ 428 public Map<String, List<SectionPeriod>> parseAtraceInfoFile( 429 File currentAtraceFile, String processId) 430 throws FileNotFoundException, IOException { 431 CLog.v("Currently parsing :" + currentAtraceFile.getName()); 432 String line; 433 BufferedReader br = null; 434 br = new BufferedReader(new FileReader(currentAtraceFile)); 435 LinkedList<TraceRecord> processStack = new LinkedList<>(); 436 Map<String, List<SectionPeriod>> sectionInfo = new HashMap<>(); 437 438 while ((line = br.readLine()) != null) { 439 // Skip extra lines that aren't part of the trace 440 if (line.isEmpty() || line.startsWith("capturing trace...") 441 || line.equals("TRACE:") || line.equals("done")) { 442 continue; 443 } 444 // Header information 445 Matcher match = null; 446 // Check if any trace entries were lost 447 if ((match = matches(ATRACE_HEADER_ENTRIES, line)) != null) { 448 int buffered = Integer.parseInt(match.group("buffered")); 449 int written = Integer.parseInt(match.group("written")); 450 if (written != buffered) { 451 CLog.w(String.format("%d trace entries lost for the file %s", 452 written - buffered, currentAtraceFile.getName())); 453 } 454 } else if ((match = matches(TRACE_ENTRY1, line)) != null 455 || (match = matches(TRACE_ENTRY2, line)) != null) { 456 /* 457 * Two trace entries because trace format differs across devices <...>-tid [yyy] 458 * ...1 zzz.ttt: tracing_mark_write: B|xxxx|tag_name pkg.name ( tid) [yyy] ...1 459 * zzz.tttt: tracing_mark_write: B|xxxx|tag_name 460 */ 461 long timestamp = SEC_TO_MILLI 462 * Long.parseLong(match.group("secs")) 463 + Long.parseLong(match.group("usecs")) / MILLI_TO_MICRO; 464 // Get the function name from the trace entry 465 String taskId = match.group("tid"); 466 String function = match.group("function"); 467 // Analyze the lines that matches the processid 468 if (!taskId.equals(processId)) { 469 continue; 470 } 471 if ((match = matches(ATRACE_BEGIN, function)) != null) { 472 // Matching pattern looks like tracing_mark_write: B|xxxx|tag_name 473 String sectionName = match.group("name"); 474 // Push to the stack 475 processStack.add(new TraceRecord(sectionName, taskId, 476 timestamp)); 477 } else if ((match = matches(ATRACE_END, function)) != null) { 478 /* 479 * Matching pattern looks like tracing_mark_write: E Pop from the stack when end 480 * reaches 481 */ 482 TraceRecord matchingBegin = processStack.removeLast(); 483 if (mSectionSet.contains(matchingBegin.name)) { 484 if (sectionInfo.containsKey(matchingBegin.name)) { 485 SectionPeriod newSecPeriod = new SectionPeriod(matchingBegin.timestamp, 486 timestamp); 487 CLog.v(String.format("Section :%s took :%f msecs ", matchingBegin.name, 488 newSecPeriod.duration)); 489 sectionInfo.get(matchingBegin.name).add(newSecPeriod); 490 } else { 491 List<SectionPeriod> infoList = new LinkedList<>(); 492 SectionPeriod newSecPeriod = new SectionPeriod(matchingBegin.timestamp, 493 timestamp); 494 CLog.v(String.format("Section :%s took :%f msecs ", matchingBegin.name, 495 newSecPeriod.duration)); 496 infoList.add(newSecPeriod); 497 sectionInfo.put(matchingBegin.name, infoList); 498 } 499 } 500 } else if ((match = matches(ATRACE_COUNTER, function)) != null) { 501 // Skip this for now. May want to track these later if needed. 502 } 503 } 504 505 } 506 StreamUtil.close(br); 507 return sectionInfo; 508 } 509 510 /** 511 * To take the average of the multiple launches for each activity 512 * @param activityName 513 * @param mutipleLaunchTraceInfo 514 */ 515 public void averageAtraceData(String activityName, 516 List<Map<String, List<SectionPeriod>>> mutipleLaunchTraceInfo) { 517 String verificationResult = verifyAtraceMapInfo(mutipleLaunchTraceInfo); 518 if (verificationResult != null) { 519 CLog.w( 520 "Not all the section info captured for the activity :%s. Missing: %s. " 521 + "Please go to atrace file to look for detail.", 522 activityName, verificationResult); 523 } 524 Map<String, Double> launchSum = new HashMap<>(); 525 for (String sectionName : mSectionSet) { 526 launchSum.put(sectionName, 0d); 527 } 528 for (Map<String, List<SectionPeriod>> singleLaunchInfo : mutipleLaunchTraceInfo) { 529 for (String sectionName : singleLaunchInfo.keySet()) { 530 for (SectionPeriod secPeriod : singleLaunchInfo 531 .get(sectionName)) { 532 if (sectionName.equals(AtraceSectionOptions.DRAW.toString())) { 533 // Get the first draw time for the launch 534 Double currentSum = launchSum.get(sectionName) 535 + secPeriod.duration; 536 launchSum.put(sectionName, currentSum); 537 break; 538 } 539 //Sum the multiple layout times before the first draw in this launch 540 if (sectionName.equals(AtraceSectionOptions.LAYOUT.toString())) { 541 Double drawStartTime = singleLaunchInfo 542 .get(AtraceSectionOptions.DRAW.toString()).get(0).startTime; 543 if (drawStartTime < secPeriod.startTime) { 544 break; 545 } 546 } 547 Double currentSum = launchSum.get(sectionName) + secPeriod.duration; 548 launchSum.put(sectionName, currentSum); 549 } 550 } 551 } 552 // Update the final result map 553 for (String sectionName : mSectionSet) { 554 Double averageTime = launchSum.get(sectionName) 555 / mutipleLaunchTraceInfo.size(); 556 mActivityTimeResultMap.get(activityName).put(sectionName, 557 String.format("%.2f", averageTime)); 558 } 559 } 560 561 /** 562 * To check if all the section info caught for all the app launches 563 * 564 * @param multipleLaunchTraceInfo 565 * @return String: the missing section name, null if no section info missing. 566 */ 567 public String verifyAtraceMapInfo( 568 List<Map<String, List<SectionPeriod>>> multipleLaunchTraceInfo) { 569 for (Map<String, List<SectionPeriod>> singleLaunchInfo : multipleLaunchTraceInfo) { 570 Set<String> testSet = new HashSet<>(mSectionSet); 571 testSet.removeAll(singleLaunchInfo.keySet()); 572 if (testSet.size() != 0) { 573 return testSet.toString(); 574 } 575 } 576 return null; 577 } 578 579 580 /** 581 * Checks whether {@code line} matches the given {@link Pattern}. 582 * @return The resulting {@link Matcher} obtained by matching the {@code line} against 583 * {@code pattern}, or null if the {@code line} does not match. 584 */ 585 private static Matcher matches(Pattern pattern, String line) { 586 Matcher ret = pattern.matcher(line); 587 return ret.matches() ? ret : null; 588 } 589 590 @Override 591 public void setDevice(ITestDevice device) { 592 mDevice = device; 593 } 594 595 @Override 596 public ITestDevice getDevice() { 597 return mDevice; 598 } 599 600 /** 601 * A record to keep track of the section start time,end time and the duration in milliseconds. 602 */ 603 public static class SectionPeriod { 604 605 private double startTime; 606 private double endTime; 607 private double duration; 608 609 public SectionPeriod(double startTime, double endTime) { 610 this.startTime = startTime; 611 this.endTime = endTime; 612 this.duration = endTime - startTime; 613 } 614 615 public double getStartTime() { 616 return startTime; 617 } 618 619 public void setStartTime(long startTime) { 620 this.startTime = startTime; 621 } 622 623 public double getEndTime() { 624 return endTime; 625 } 626 627 public void setEndTime(long endTime) { 628 this.endTime = endTime; 629 } 630 631 public double getDuration() { 632 return duration; 633 } 634 635 public void setDuration(long duration) { 636 this.duration = duration; 637 } 638 } 639 640 /** 641 * A record of a trace event. Includes the name of the section, and the time that the event 642 * occurred (in milliseconds). 643 */ 644 public static class TraceRecord { 645 646 private String name; 647 private String processId; 648 private double timestamp; 649 650 /** 651 * Construct a new {@link TraceRecord} with the given {@code name} and {@code timestamp} . 652 */ 653 public TraceRecord(String name, String processId, long timestamp) { 654 this.name = name; 655 this.processId = processId; 656 this.timestamp = timestamp; 657 } 658 659 public String getName() { 660 return name; 661 } 662 663 public void setName(String name) { 664 this.name = name; 665 } 666 667 public String getProcessId() { 668 return processId; 669 } 670 671 public void setProcessId(String processId) { 672 this.processId = processId; 673 } 674 675 public double getTimestamp() { 676 return timestamp; 677 } 678 679 public void setTimestamp(long timestamp) { 680 this.timestamp = timestamp; 681 } 682 } 683 684 } 685