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