1 # SPDX-License-Identifier: Apache-2.0 2 # 3 # Copyright (C) 2015, ARM Limited and contributors. 4 # 5 # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 # not use this file except in compliance with the License. 7 # You may obtain a copy of the License at 8 # 9 # http://www.apache.org/licenses/LICENSE-2.0 10 # 11 # Unless required by applicable law or agreed to in writing, software 12 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 # See the License for the specific language governing permissions and 15 # limitations under the License. 16 # 17 18 import re 19 import os 20 import logging 21 import sqlite3 22 import pandas as pd 23 24 from subprocess import Popen, PIPE 25 from time import sleep 26 27 from android import Screen, System, Workload 28 29 # Available test workloads 30 _jankbench = { 31 'list_view' : 0, 32 'image_list_view' : 1, 33 'shadow_grid' : 2, 34 'low_hitrate_text' : 3, 35 'high_hitrate_text' : 4, 36 'edit_text' : 5, 37 'overdraw' : 6, 38 } 39 40 # Regexps for benchmark synchronization 41 JANKBENCH_BENCHMARK_START_RE = re.compile( 42 r'ActivityManager: START.*' 43 '(cmp=com.android.benchmark/.app.RunLocalBenchmarksActivity)' 44 ) 45 JANKBENCH_ITERATION_COUNT_RE = re.compile( 46 r'System.out: iteration: (?P<iteration>[0-9]+)' 47 ) 48 49 # Meaning of different jankbench metrics output in the logs: 50 # 51 # BAD FRAME STATS: 52 # mean: Mean frame duration time of all frames that are > 12ms completion time 53 # std_dev: Standard deviation of all frame times > 12ms completion time 54 # count_bad: Total number of frames 55 # 56 # JANK FRAME STATS: 57 # JankP: Percent of all total frames that missed their deadline (2*16ms for 58 # tripple buffering, 16ms for double buffering). 59 # count_jank: Total frames that missed their deadline (as described above). 60 JANKBENCH_ITERATION_METRICS_RE = re.compile( 61 r'System.out: Mean: (?P<mean>[0-9\.]+)\s+JankP: (?P<jank_p>[0-9\.]+)\s+' 62 'StdDev: (?P<std_dev>[0-9\.]+)\s+Count Bad: (?P<count_bad>[0-9]+)\s+' 63 'Count Jank: (?P<count_jank>[0-9]+)' 64 ) 65 JANKBENCH_BENCHMARK_DONE_RE = re.compile( 66 r'I BENCH\s+:\s+BenchmarkDone!' 67 ) 68 69 JANKBENCH_DB_PATH = '/data/data/com.android.benchmark/databases/' 70 JANKBENCH_DB_NAME = 'BenchmarkResults' 71 72 # The amounts of time to collect energy for each test. Used when USB communication with the device 73 # is disabled during energy collection to prevent it from interfering with energy sampling. Tests 74 # may run longer or shorter than the amount of time energy is collected. 75 JANKBENCH_ENERGY_DURATIONS = { 76 'list_view' : 30, 77 'image_list_view' : 30, 78 'shadow_grid' : 30, 79 'low_hitrate_text' : 30, 80 'high_hitrate_text' : 30, 81 'edit_text' : 9, 82 } 83 84 class Jankbench(Workload): 85 """ 86 Android Jankbench workload 87 """ 88 89 # Package required by this workload 90 package = 'com.android.benchmark' 91 92 test_list = \ 93 ['list_view', 94 'image_list_view', 95 'shadow_grid', 96 'low_hitrate_text', 97 'high_hitrate_text', 98 'edit_text', 99 'overdraw'] 100 101 def __init__(self, test_env): 102 super(Jankbench, self).__init__(test_env) 103 self._log = logging.getLogger('Jankbench') 104 self._log.debug('Workload created') 105 106 # Set of output data reported by Jankbench 107 self.db_file = None 108 109 def get_test_list(self): 110 return Jankbench.test_list 111 112 def run(self, out_dir, test_name, iterations, collect): 113 """ 114 Run Jankbench workload for a number of iterations. 115 Returns a collection of results. 116 117 :param out_dir: Path to experiment directory on the host 118 where to store results. 119 :type out_dir: str 120 121 :param test_name: Name of the test to run 122 :type test_name: str 123 124 :param iterations: Number of iterations for the named test 125 :type iterations: int 126 127 :param collect: Specifies what to collect. Possible values: 128 - 'energy' 129 - 'systrace' 130 - 'ftrace' 131 - any combination of the above as a single space-separated string. 132 :type collect: list(str) 133 """ 134 135 # Keep track of mandatory parameters 136 self.out_dir = out_dir 137 self.collect = collect 138 139 # Setup test id 140 try: 141 test_id = _jankbench[test_name] 142 except KeyError: 143 raise ValueError('Jankbench test [%s] not supported', test_name) 144 145 # Restart ADB in root mode - adb needs to run as root inorder to 146 # grab the db output file 147 self._log.info('Restarting ADB in root mode...') 148 self._target.adb_root(force=True) 149 150 # Unlock device screen (assume no password required) 151 Screen.unlock(self._target) 152 153 # Close and clear application 154 System.force_stop(self._target, self.package, clear=True) 155 156 # Set airplane mode 157 System.set_airplane_mode(self._target, on=True) 158 159 # Set min brightness 160 Screen.set_brightness(self._target, auto=False, percent=0) 161 162 # Force screen in PORTRAIT mode 163 Screen.set_orientation(self._target, portrait=True) 164 165 # Clear logcat 166 self._target.clear_logcat() 167 168 self._log.debug('Start Jank Benchmark [%d:%s]', test_id, test_name) 169 test_cmd = 'am start -n "com.android.benchmark/.app.RunLocalBenchmarksActivity" '\ 170 '--eia "com.android.benchmark.EXTRA_ENABLED_BENCHMARK_IDS" {0} '\ 171 '--ei "com.android.benchmark.EXTRA_RUN_COUNT" {1}'\ 172 .format(test_id, iterations) 173 self._log.info(test_cmd) 174 self._target.execute(test_cmd); 175 176 if 'energy' in collect: 177 # Start collecting energy readings 178 self.tracingStart() 179 self._log.debug('Benchmark started!') 180 self._log.info('Running energy meter for {} seconds' 181 .format(iterations * JANKBENCH_ENERGY_DURATIONS[test_name])) 182 183 # Sleep for the approximate amount of time the test should take to run the specified 184 # number of iterations. 185 for i in xrange(0, iterations): 186 sleep(JANKBENCH_ENERGY_DURATIONS[test_name]) 187 self._log.debug('Iteration: %2d', i + 1) 188 189 # Stop collecting energy. The test may or may not be done at this point. 190 self._log.debug('Benchmark done!') 191 self.tracingStop() 192 else: 193 # Parse logcat output lines 194 logcat_cmd = self._adb( 195 'logcat ActivityManager:* System.out:I *:S BENCH:*'\ 196 .format(self._target.adb_name)) 197 self._log.info(logcat_cmd) 198 logcat = Popen(logcat_cmd, shell=True, stdout=PIPE) 199 self._log.debug('Iterations:') 200 while True: 201 202 # read next logcat line (up to max 1024 chars) 203 message = logcat.stdout.readline(1024) 204 205 # Benchmark start trigger 206 match = JANKBENCH_BENCHMARK_START_RE.search(message) 207 if match: 208 self.tracingStart() 209 self._log.debug('Benchmark started!') 210 211 # Benchmark completed trigger 212 match = JANKBENCH_BENCHMARK_DONE_RE.search(message) 213 if match: 214 self._log.debug('Benchmark done!') 215 self.tracingStop() 216 break 217 218 # Iteration completed 219 match = JANKBENCH_ITERATION_COUNT_RE.search(message) 220 if match: 221 self._log.debug('Iteration: %2d', 222 int(match.group('iteration'))+1) 223 # Iteration metrics 224 match = JANKBENCH_ITERATION_METRICS_RE.search(message) 225 if match: 226 self._log.info(' Mean: %7.3f JankP: %7.3f StdDev: %7.3f Count Bad: %4d Count Jank: %4d', 227 float(match.group('mean')), 228 float(match.group('jank_p')), 229 float(match.group('std_dev')), 230 int(match.group('count_bad')), 231 int(match.group('count_jank'))) 232 233 # Wait until the database file is available 234 db_adb = JANKBENCH_DB_PATH + JANKBENCH_DB_NAME 235 while (db_adb not in self._target.execute('ls {}'.format(db_adb), check_exit_code=False)): 236 sleep(1) 237 238 # Get results 239 self.db_file = os.path.join(out_dir, JANKBENCH_DB_NAME) 240 self._target.pull(db_adb, self.db_file) 241 self.results = self.get_results(out_dir) 242 243 # Stop the benchmark app 244 System.force_stop(self._target, self.package, clear=True) 245 246 # Go back to home screen 247 System.home(self._target) 248 249 # Reset initial setup 250 # Set orientation back to auto 251 Screen.set_orientation(self._target, auto=True) 252 253 # Turn off airplane mode 254 System.set_airplane_mode(self._target, on=False) 255 256 # Set brightness back to auto 257 Screen.set_brightness(self._target, auto=True) 258 259 @staticmethod 260 def get_results(out_dir): 261 """ 262 Extract data from results db and return as a pandas dataframe 263 264 :param out_dir: Output directory for a run of the Jankbench workload 265 :type out_dir: str 266 """ 267 path = os.path.join(out_dir, JANKBENCH_DB_NAME) 268 columns = ['_id', 'name', 'run_id', 'iteration', 'total_duration', 'jank_frame'] 269 data = [] 270 conn = sqlite3.connect(path) 271 for row in conn.execute('SELECT {} FROM ui_results'.format(','.join(columns))): 272 data.append(row) 273 return pd.DataFrame(data, columns=columns) 274 275 # vim :set tabstop=4 shiftwidth=4 expandtab 276