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