# Copyright 2013-2015 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 from __future__ import division import os try: import jinja2 except ImportError: jinja2 = None from wlauto import Workload, settings, Parameter from wlauto.exceptions import WorkloadError from wlauto.utils.hwmon import discover_sensors from wlauto.utils.misc import get_meansd from wlauto.utils.types import boolean, identifier, list_of_strs THIS_DIR = os.path.dirname(__file__) TEMPLATE_NAME = 'device_script.template' SCRIPT_TEMPLATE = os.path.join(THIS_DIR, TEMPLATE_NAME) APP_CONFIG = { 'browser': { 'package': 'com.android.browser', 'activity': '.BrowserActivity', 'options': '-d about:blank', }, 'calculator': { 'package': 'com.android.calculator2', 'activity': '.Calculator', 'options': '', }, 'calendar': { 'package': 'com.android.calendar', 'activity': '.LaunchActivity', 'options': '', }, } class ApplaunchWorkload(Workload): name = 'applaunch' description = """ Measures the time and energy used in launching an application. """ supported_platforms = ['android'] parameters = [ Parameter('app', default='browser', allowed_values=['calculator', 'browser', 'calendar'], description='The name of the application to measure.'), Parameter('set_launcher_affinity', kind=bool, default=True, description=('If ``True``, this will explicitly set the affinity of the launcher ' 'process to the A15 cluster.')), Parameter('times', kind=int, default=8, description='Number of app launches to do on the device.'), Parameter('measure_energy', kind=boolean, default=False, description=""" Specfies wether energy measurments should be taken during the run. .. note:: This depends on appropriate sensors to be exposed through HWMON. """), Parameter('io_stress', kind=boolean, default=False, description='Specifies whether to stress IO during App launch.'), Parameter('io_scheduler', allowed_values=['noop', 'deadline', 'row', 'cfq', 'bfq'], description='Set the IO scheduler to test on the device.'), Parameter('cleanup', kind=boolean, default=True, description='Specifies whether to clean up temporary files on the device.'), ] def __init__(self, device, **kwargs): super(ApplaunchWorkload, self).__init__(device, **kwargs) if not jinja2: raise WorkloadError('Please install jinja2 Python package: "sudo pip install jinja2"') filename = '{}-{}.sh'.format(self.name, self.app) self.host_script_file = os.path.join(settings.meta_directory, filename) self.device_script_file = os.path.join(self.device.working_directory, filename) self._launcher_pid = None self._old_launcher_affinity = None self.sensors = [] def on_run_init(self, context): # pylint: disable=W0613 if self.measure_energy: self.sensors = discover_sensors(self.device, ['energy']) for sensor in self.sensors: sensor.label = identifier(sensor.label).upper() def setup(self, context): self.logger.debug('Creating script {}'.format(self.host_script_file)) with open(self.host_script_file, 'w') as wfh: env = jinja2.Environment(loader=jinja2.FileSystemLoader(THIS_DIR)) template = env.get_template(TEMPLATE_NAME) wfh.write(template.render(device=self.device, # pylint: disable=maybe-no-member sensors=self.sensors, iterations=self.times, io_stress=self.io_stress, io_scheduler=self.io_scheduler, cleanup=self.cleanup, package=APP_CONFIG[self.app]['package'], activity=APP_CONFIG[self.app]['activity'], options=APP_CONFIG[self.app]['options'], busybox=self.device.busybox, )) self.device_script_file = self.device.install(self.host_script_file) if self.set_launcher_affinity: self._set_launcher_affinity() self.device.clear_logcat() def run(self, context): self.device.execute('sh {}'.format(self.device_script_file), timeout=300, as_root=self.io_stress) def update_result(self, context): # pylint: disable=too-many-locals result_files = ['time.result'] result_files += ['{}.result'.format(sensor.label) for sensor in self.sensors] metric_suffix = '' if self.io_stress: host_scheduler_file = os.path.join(context.output_directory, 'scheduler') device_scheduler_file = '/sys/block/mmcblk0/queue/scheduler' self.device.pull_file(device_scheduler_file, host_scheduler_file) with open(host_scheduler_file) as fh: scheduler = fh.read() scheduler_used = scheduler[scheduler.index("[") + 1:scheduler.index("]")] metric_suffix = '_' + scheduler_used for filename in result_files: self._extract_results_from_file(context, filename, metric_suffix) def teardown(self, context): if self.set_launcher_affinity: self._reset_launcher_affinity() if self.cleanup: self.device.delete_file(self.device_script_file) def _set_launcher_affinity(self): try: self._launcher_pid = self.device.get_pids_of('com.android.launcher')[0] result = self.device.execute('taskset -p {}'.format(self._launcher_pid), busybox=True, as_root=True) self._old_launcher_affinity = int(result.split(':')[1].strip(), 16) cpu_ids = [i for i, x in enumerate(self.device.core_names) if x == 'a15'] if not cpu_ids or len(cpu_ids) == len(self.device.core_names): self.logger.debug('Cannot set affinity.') return new_mask = reduce(lambda x, y: x | y, cpu_ids, 0x0) self.device.execute('taskset -p 0x{:X} {}'.format(new_mask, self._launcher_pid), busybox=True, as_root=True) except IndexError: raise WorkloadError('Could not set affinity of launcher: PID not found.') def _reset_launcher_affinity(self): command = 'taskset -p 0x{:X} {}'.format(self._old_launcher_affinity, self._launcher_pid) self.device.execute(command, busybox=True, as_root=True) def _extract_results_from_file(self, context, filename, metric_suffix): host_result_file = os.path.join(context.output_directory, filename) device_result_file = self.device.path.join(self.device.working_directory, filename) self.device.pull_file(device_result_file, host_result_file) with open(host_result_file) as fh: if filename == 'time.result': values = [v / 1000 for v in map(int, fh.read().split())] _add_metric(context, 'time' + metric_suffix, values, 'Seconds') else: metric = filename.replace('.result', '').lower() numbers = iter(map(int, fh.read().split())) deltas = [(after - before) / 1000000 for before, after in zip(numbers, numbers)] _add_metric(context, metric, deltas, 'Joules') def _add_metric(context, metric, values, units): mean, sd = get_meansd(values) context.result.add_metric(metric, mean, units) context.result.add_metric(metric + ' sd', sd, units, lower_is_better=True)