Home | History | Annotate | Download | only in tests
      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