1# Copyright 2019, The Android Open Source Project 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15"""Class to collector perfetto trace.""" 16import datetime 17import os 18import re 19import sys 20import time 21from datetime import timedelta 22from typing import Optional, List, Tuple 23 24# global variables 25DIR = os.path.abspath(os.path.dirname(__file__)) 26 27sys.path.append(os.path.dirname(os.path.dirname(DIR))) 28 29import app_startup.lib.adb_utils as adb_utils 30from app_startup.lib.app_runner import AppRunner, AppRunnerListener 31import lib.print_utils as print_utils 32import lib.logcat_utils as logcat_utils 33import iorap.lib.iorapd_utils as iorapd_utils 34 35class PerfettoTraceCollector(AppRunnerListener): 36 """ Class to collect perfetto trace. 37 38 To set trace duration of perfetto, change the 'trace_duration_ms'. 39 To pull the generated perfetto trace on device, set the 'output'. 40 """ 41 TRACE_FILE_SUFFIX = 'perfetto_trace.pb' 42 TRACE_DURATION_PROP = 'iorapd.perfetto.trace_duration_ms' 43 MS_PER_SEC = 1000 44 DEFAULT_TRACE_DURATION = timedelta(milliseconds=5000) # 5 seconds 45 _COLLECTOR_TIMEOUT_MULTIPLIER = 10 # take the regular timeout and multiply 46 47 def __init__(self, 48 package: str, 49 activity: Optional[str], 50 compiler_filter: Optional[str], 51 timeout: Optional[int], 52 simulate: bool, 53 trace_duration: timedelta = DEFAULT_TRACE_DURATION, 54 save_destination_file_path: Optional[str] = None): 55 """ Initialize the perfetto trace collector. """ 56 self.app_runner = AppRunner(package, 57 activity, 58 compiler_filter, 59 timeout, 60 simulate) 61 self.app_runner.add_callbacks(self) 62 63 self.trace_duration = trace_duration 64 self.save_destination_file_path = save_destination_file_path 65 66 def purge_file(self, suffix: str) -> None: 67 print_utils.debug_print('iorapd-perfetto: purge file in ' + 68 self._get_remote_path()) 69 adb_utils.delete_file_on_device(self._get_remote_path()) 70 71 def run(self) -> Optional[List[Tuple[str]]]: 72 """Runs an app. 73 74 Returns: 75 A list of (metric, value) tuples. 76 """ 77 return self.app_runner.run() 78 79 def preprocess(self): 80 # Sets up adb environment. 81 adb_utils.root() 82 adb_utils.disable_selinux() 83 time.sleep(1) 84 85 # Kill any existing process of this app 86 adb_utils.pkill(self.app_runner.package) 87 88 # Remove existing trace and compiler files 89 self.purge_file(PerfettoTraceCollector.TRACE_FILE_SUFFIX) 90 91 # Set perfetto trace duration prop to milliseconds. 92 adb_utils.set_prop(PerfettoTraceCollector.TRACE_DURATION_PROP, 93 int(self.trace_duration.total_seconds()* 94 PerfettoTraceCollector.MS_PER_SEC)) 95 96 if not iorapd_utils.stop_iorapd(): 97 raise RuntimeError('Cannot stop iorapd!') 98 99 if not iorapd_utils.enable_iorapd_perfetto(): 100 raise RuntimeError('Cannot enable perfetto!') 101 102 if not iorapd_utils.disable_iorapd_readahead(): 103 raise RuntimeError('Cannot disable readahead!') 104 105 if not iorapd_utils.start_iorapd(): 106 raise RuntimeError('Cannot start iorapd!') 107 108 # Drop all caches to get cold starts. 109 adb_utils.vm_drop_cache() 110 111 def postprocess(self, pre_launch_timestamp: str): 112 # Kill any existing process of this app 113 adb_utils.pkill(self.app_runner.package) 114 115 iorapd_utils.disable_iorapd_perfetto() 116 117 if self.save_destination_file_path is not None: 118 adb_utils.pull_file(self._get_remote_path(), 119 self.save_destination_file_path) 120 121 def metrics_selector(self, am_start_output: str, 122 pre_launch_timestamp: str) -> str: 123 """Parses the metric after app startup by reading from logcat in a blocking 124 manner until all metrics have been found". 125 126 Returns: 127 An empty string because the metric needs no further parsing. 128 """ 129 if not self._wait_for_perfetto_trace(pre_launch_timestamp): 130 raise RuntimeError('Could not save perfetto app trace file!') 131 132 return '' 133 134 def _wait_for_perfetto_trace(self, pre_launch_timestamp) -> Optional[str]: 135 """ Waits for the perfetto trace being saved to file. 136 137 The string is in the format of r".*Perfetto TraceBuffer saved to file: 138 <file path>.*" 139 140 Returns: 141 the string what the program waits for. If the string doesn't show up, 142 return None. 143 """ 144 pattern = re.compile(r'.*Perfetto TraceBuffer saved to file: {}.*'. 145 format(self._get_remote_path())) 146 147 # The pre_launch_timestamp is longer than what the datetime can parse. Trim 148 # last three digits to make them align. For example: 149 # 2019-07-02 23:20:06.972674825999 -> 2019-07-02 23:20:06.972674825 150 assert len(pre_launch_timestamp) == len('2019-07-02 23:20:06.972674825') 151 timestamp = datetime.datetime.strptime(pre_launch_timestamp[:-3], 152 '%Y-%m-%d %H:%M:%S.%f') 153 154 # The timeout of perfetto trace is longer than the normal app run timeout. 155 timeout_dt = self.app_runner.timeout * PerfettoTraceCollector._COLLECTOR_TIMEOUT_MULTIPLIER 156 timeout_end = timestamp + datetime.timedelta(seconds=timeout_dt) 157 158 return logcat_utils.blocking_wait_for_logcat_pattern(timestamp, 159 pattern, 160 timeout_end) 161 162 def _get_remote_path(self): 163 # For example: android.music%2Fmusic.TopLevelActivity.perfetto_trace.pb 164 return iorapd_utils._iorapd_path_to_data_file(self.app_runner.package, 165 self.app_runner.activity, 166 PerfettoTraceCollector.TRACE_FILE_SUFFIX) 167