mirror of
https://github.com/ARM-software/workload-automation.git
synced 2025-02-21 20:38:57 +00:00
workloads: add jankbench
Add automation for Google's jankbench workload.
This commit is contained in:
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()
|
Loading…
x
Reference in New Issue
Block a user