mirror of
				https://github.com/ARM-software/workload-automation.git
				synced 2025-11-04 00:52:08 +00:00 
			
		
		
		
	workloads: add jankbench
Add automation for Google's jankbench workload.
This commit is contained in:
		
				
					committed by
					
						
						Marc Bonnici
					
				
			
			
				
	
			
			
			
						parent
						
							e6f7d3fcde
						
					
				
				
					commit
					5ed5b6f819
				
			
							
								
								
									
										234
									
								
								wa/workloads/jankbench/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										234
									
								
								wa/workloads/jankbench/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,234 @@
 | 
			
		||||
#    Copyright 2018 ARM Limited
 | 
			
		||||
#
 | 
			
		||||
# Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
# you may not use this file except in compliance with the License.
 | 
			
		||||
# You may obtain a copy of the License at
 | 
			
		||||
#
 | 
			
		||||
#     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
#
 | 
			
		||||
# Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
# distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
# See the License for the specific language governing permissions and
 | 
			
		||||
# limitations under the License.
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
# pylint: disable=E1101,W0201,E0203
 | 
			
		||||
 | 
			
		||||
from __future__ import division
 | 
			
		||||
import os
 | 
			
		||||
import re
 | 
			
		||||
import select
 | 
			
		||||
import json
 | 
			
		||||
import threading
 | 
			
		||||
import sqlite3
 | 
			
		||||
import subprocess
 | 
			
		||||
from copy import copy
 | 
			
		||||
 | 
			
		||||
import pandas as pd
 | 
			
		||||
 | 
			
		||||
from wa import ApkWorkload, Parameter, WorkloadError, ConfigError
 | 
			
		||||
from wa.utils.types import list_or_string, numeric
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
DELAY = 2
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Jankbench(ApkWorkload):
 | 
			
		||||
 | 
			
		||||
    name = 'jankbench'
 | 
			
		||||
    description = """
 | 
			
		||||
    Internal Google benchmark for evaluating jank on Android.
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    package_names = ['com.android.benchmark']
 | 
			
		||||
    activity = '.app.RunLocalBenchmarksActivity'
 | 
			
		||||
 | 
			
		||||
    results_db_file = 'BenchmarkResults'
 | 
			
		||||
 | 
			
		||||
    iteration_regex = re.compile(r'System.out: iteration: (?P<iteration>[0-9]+)')
 | 
			
		||||
    metrics_regex = re.compile(
 | 
			
		||||
        r'System.out: Mean: (?P<mean>[0-9\.]+)\s+JankP: (?P<junk_p>[0-9\.]+)\s+'
 | 
			
		||||
        r'StdDev: (?P<std_dev>[0-9\.]+)\s+Count Bad: (?P<count_bad>[0-9]+)\s+'
 | 
			
		||||
        r'Count Jank: (?P<count_junk>[0-9]+)'
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    valid_test_ids = [
 | 
			
		||||
        # Order matters -- the index of the id must match what is expected by
 | 
			
		||||
        # the App.
 | 
			
		||||
        'list_view',
 | 
			
		||||
        'image_list_view',
 | 
			
		||||
        'shadow_grid',
 | 
			
		||||
        'low_hitrate_text',
 | 
			
		||||
        'high_hitrate_text',
 | 
			
		||||
        'edit_text',
 | 
			
		||||
        'overdraw_test',
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    parameters = [
 | 
			
		||||
        Parameter('test_ids', kind=list_or_string,
 | 
			
		||||
                  allowed_values=valid_test_ids,
 | 
			
		||||
                  description='ID of the jankbench test to be run.'),
 | 
			
		||||
        Parameter('reps', kind=int, default=1, constraint=lambda x: x > 0,
 | 
			
		||||
                  description='''
 | 
			
		||||
                  Specifies the number of times the benchmark will be run in a "tight loop",
 | 
			
		||||
                  i.e. without performaing setup/teardown inbetween.
 | 
			
		||||
                  '''),
 | 
			
		||||
        Parameter('pull_results_db', kind=bool,
 | 
			
		||||
                  description='''
 | 
			
		||||
                  Secifies whether an sqlite database with detailed results should be pulled
 | 
			
		||||
                  from benchmark app's data. This requires the device to be rooted.
 | 
			
		||||
 | 
			
		||||
                  This defaults to ``True`` for rooted devices and ``False`` otherwise.
 | 
			
		||||
                  '''),
 | 
			
		||||
        Parameter('run_timeout', kind=int, default=10 * 60,
 | 
			
		||||
                  description="""
 | 
			
		||||
                  Time out for workload execution. The workload will be killed if it hasn't completed
 | 
			
		||||
                  within this period.
 | 
			
		||||
                  """),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    def setup(self, context):
 | 
			
		||||
        super(Jankbench, self).setup(context)
 | 
			
		||||
 | 
			
		||||
        if self.pull_results_db is None:
 | 
			
		||||
            self.pull_results_db = self.target.is_rooted
 | 
			
		||||
        elif self.pull_results_db and not self.target.is_rooted:
 | 
			
		||||
            raise ConfigError('pull_results_db set for an unrooted device')
 | 
			
		||||
 | 
			
		||||
        self.target.ensure_screen_is_on()
 | 
			
		||||
        self.command = self._build_command()
 | 
			
		||||
        self.monitor = JankbenchRunMonitor(self.target)
 | 
			
		||||
        self.monitor.start()
 | 
			
		||||
 | 
			
		||||
    def run(self, context):
 | 
			
		||||
        result = self.target.execute(self.command, timeout=self.run_timeout)
 | 
			
		||||
        if 'FAILURE' in result:
 | 
			
		||||
            raise WorkloadError(result)
 | 
			
		||||
        else:
 | 
			
		||||
            self.logger.debug(result)
 | 
			
		||||
        self.target.sleep(DELAY)
 | 
			
		||||
        self.monitor.wait_for_run_end(self.run_timeout)
 | 
			
		||||
 | 
			
		||||
    def extract_results(self, context):
 | 
			
		||||
        self.monitor.stop()
 | 
			
		||||
        if self.pull_results_db:
 | 
			
		||||
            target_file = self.target.path.join(self.target.package_data_directory,
 | 
			
		||||
                                                self.package, 'databases', self.results_db_file)
 | 
			
		||||
            host_file = os.path.join(context.output_directory,self.results_db_file)
 | 
			
		||||
            self.target.pull(target_file, host_file, as_root=True)
 | 
			
		||||
            context.add_artifact('jankbench-results', host_file, 'data')
 | 
			
		||||
 | 
			
		||||
    def update_output(self, context):  # NOQA
 | 
			
		||||
        super(Jankbench, self).update_output(context)
 | 
			
		||||
        if self.pull_results_db:
 | 
			
		||||
            self.extract_metrics_from_db(context)
 | 
			
		||||
        else:
 | 
			
		||||
            self.extract_metrics_from_logcat(context)
 | 
			
		||||
 | 
			
		||||
    def extract_metrics_from_db(self, context):
 | 
			
		||||
        dbfile = context.get_artifact_path('jankbench-results')
 | 
			
		||||
        with sqlite3.connect(dbfile) as conn:
 | 
			
		||||
            df = pd.read_sql('select name, iteration, total_duration, jank_frame from ui_results', conn)
 | 
			
		||||
            g = df.groupby(['name', 'iteration'])
 | 
			
		||||
            janks = g.jank_frame.sum()
 | 
			
		||||
            janks_pc = janks / g.jank_frame.count() * 100
 | 
			
		||||
            results = pd.concat([
 | 
			
		||||
                g.total_duration.mean(),
 | 
			
		||||
                g.total_duration.std(),
 | 
			
		||||
                janks,
 | 
			
		||||
                janks_pc,
 | 
			
		||||
            ], axis=1)
 | 
			
		||||
            results.columns = ['mean', 'std_dev', 'count_jank', 'jank_p']
 | 
			
		||||
 | 
			
		||||
            for test_name, rep in results.index:
 | 
			
		||||
                test_results = results.ix[test_name, rep]
 | 
			
		||||
                for metric, value in test_results.iteritems():
 | 
			
		||||
                    context.add_metric(metric, value, units=None, lower_is_better=True,
 | 
			
		||||
                                       classifiers={'test_name': test_name, 'rep': rep})
 | 
			
		||||
 | 
			
		||||
    def exract_metrics_from_logcat(self, context):
 | 
			
		||||
        metric_names = ['mean', 'junk_p', 'std_dev', 'count_bad', 'count_junk']
 | 
			
		||||
        logcat_file = context.get_artifact_path('logcat')
 | 
			
		||||
        with open(logcat_file) as fh:
 | 
			
		||||
            run_tests = copy(self.test_ids or self.valid_test_ids)
 | 
			
		||||
            current_iter = None
 | 
			
		||||
            current_test = None
 | 
			
		||||
            for line in fh:
 | 
			
		||||
 | 
			
		||||
                match = self.iteration_regex.search(line)
 | 
			
		||||
                if match:
 | 
			
		||||
                    if current_iter is not None:
 | 
			
		||||
                        msg = 'Did not see results for iteration {} of {}'
 | 
			
		||||
                        self.logger.warning(msg.format(current_iter, current_test))
 | 
			
		||||
                    current_iter = int(match.group('iteration'))
 | 
			
		||||
                    if current_iter == 0:
 | 
			
		||||
                        try:
 | 
			
		||||
                            current_test = run_tests.pop(0)
 | 
			
		||||
                        except IndexError:
 | 
			
		||||
                            self.logger.warning('Encountered an iteration for an unknown test.')
 | 
			
		||||
                            current_test = 'unknown'
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
                match = self.metrics_regex.search(line)
 | 
			
		||||
                if match:
 | 
			
		||||
                    if current_iter is None:
 | 
			
		||||
                        self.logger.warning('Encountered unexpected metrics (no iteration)')
 | 
			
		||||
                        continue
 | 
			
		||||
 | 
			
		||||
                    for name in metric_names:
 | 
			
		||||
                        value = numeric(match.group(name))
 | 
			
		||||
                        context.add_metric(name, value, units=None, lower_is_better=True,
 | 
			
		||||
                                           classifiers={'test_id': current_test, 'rep': current_iter})
 | 
			
		||||
 | 
			
		||||
                    current_iter = None
 | 
			
		||||
 | 
			
		||||
    def _build_command(self):
 | 
			
		||||
        command_params = []
 | 
			
		||||
        if self.test_ids:
 | 
			
		||||
            test_idxs = [str(self.valid_test_ids.index(i)) for i in self.test_ids]
 | 
			
		||||
            command_params.append('--eia com.android.benchmark.EXTRA_ENABLED_BENCHMARK_IDS {}'.format(','.join(test_idxs)))
 | 
			
		||||
        if self.reps:
 | 
			
		||||
            command_params.append('--ei com.android.benchmark.EXTRA_RUN_COUNT {}'.format(self.reps))
 | 
			
		||||
        return 'am start -W -S -n {}/{} {}'.format(self.package,
 | 
			
		||||
                                                   self.activity,
 | 
			
		||||
                                                   ' '.join(command_params))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class JankbenchRunMonitor(threading.Thread):
 | 
			
		||||
 | 
			
		||||
    regex = re.compile(r'I BENCH\s+:\s+BenchmarkDone!')
 | 
			
		||||
 | 
			
		||||
    def __init__(self, device):
 | 
			
		||||
        super(JankbenchRunMonitor, self).__init__()
 | 
			
		||||
        self.target = device
 | 
			
		||||
        self.daemon = True
 | 
			
		||||
        self.run_ended = threading.Event()
 | 
			
		||||
        self.stop_event = threading.Event()
 | 
			
		||||
        # Not using clear_logcat() because command collects directly, i.e. will
 | 
			
		||||
        # ignore poller.
 | 
			
		||||
        self.target.execute('logcat -c')
 | 
			
		||||
        if self.target.adb_name:
 | 
			
		||||
            self.command = ['adb', '-s', self.target.adb_name, 'logcat']
 | 
			
		||||
        else:
 | 
			
		||||
            self.command = ['adb', 'logcat']
 | 
			
		||||
 | 
			
		||||
    def run(self):
 | 
			
		||||
        proc = subprocess.Popen(self.command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
 | 
			
		||||
        while not self.stop_event.is_set():
 | 
			
		||||
            if self.run_ended.is_set():
 | 
			
		||||
                self.target.sleep(DELAY)
 | 
			
		||||
            else:
 | 
			
		||||
                ready, _, _ = select.select([proc.stdout, proc.stderr], [], [], 2)
 | 
			
		||||
                if ready:
 | 
			
		||||
                    line = ready[0].readline()
 | 
			
		||||
                    if self.regex.search(line):
 | 
			
		||||
                        self.run_ended.set()
 | 
			
		||||
 | 
			
		||||
    def stop(self):
 | 
			
		||||
        self.stop_event.set()
 | 
			
		||||
        self.join()
 | 
			
		||||
 | 
			
		||||
    def wait_for_run_end(self, timeout):
 | 
			
		||||
        self.run_ended.wait(timeout)
 | 
			
		||||
        self.run_ended.clear()
 | 
			
		||||
		Reference in New Issue
	
	Block a user