2018-07-04 15:39:44 +01:00
|
|
|
# Copyright 2013-2018 ARM Limited
|
2017-03-09 14:44:26 +00:00
|
|
|
#
|
|
|
|
# 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=W0613,no-member,attribute-defined-outside-init
|
|
|
|
"""
|
|
|
|
|
|
|
|
Some "standard" instruments to collect additional info about workload execution.
|
|
|
|
|
|
|
|
.. note:: The run() method of a Workload may perform some "boilerplate" as well as
|
|
|
|
the actual execution of the workload (e.g. it may contain UI automation
|
|
|
|
needed to start the workload). This "boilerplate" execution will also
|
|
|
|
be measured by these instruments. As such, they are not suitable for collected
|
|
|
|
precise data about specific operations.
|
|
|
|
"""
|
|
|
|
import os
|
|
|
|
import logging
|
|
|
|
import time
|
|
|
|
import tarfile
|
|
|
|
from subprocess import CalledProcessError
|
|
|
|
|
2018-05-30 13:58:49 +01:00
|
|
|
from devlib.exception import TargetError
|
2017-03-09 14:44:26 +00:00
|
|
|
from devlib.utils.android import ApkInfo
|
|
|
|
|
2017-03-17 15:57:05 +00:00
|
|
|
from wa import Instrument, Parameter, very_fast
|
2017-03-09 14:44:26 +00:00
|
|
|
from wa.framework.exception import ConfigError
|
2018-02-16 14:09:02 +00:00
|
|
|
from wa.framework.instrument import slow
|
2018-05-30 13:58:49 +01:00
|
|
|
from wa.utils.diff import diff_sysfs_dirs, diff_interrupt_files
|
2018-07-03 14:25:33 +01:00
|
|
|
from wa.utils.misc import as_relative
|
2017-03-09 14:44:26 +00:00
|
|
|
from wa.utils.misc import ensure_file_directory_exists as _f
|
|
|
|
from wa.utils.misc import ensure_directory_exists as _d
|
|
|
|
from wa.utils.types import list_of_strings
|
|
|
|
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
class SysfsExtractor(Instrument):
|
|
|
|
|
|
|
|
name = 'sysfs_extractor'
|
|
|
|
description = """
|
|
|
|
Collects the contest of a set of directories, before and after workload execution
|
|
|
|
and diffs the result.
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
mount_command = 'mount -t tmpfs -o size={} tmpfs {}'
|
|
|
|
extract_timeout = 30
|
|
|
|
tarname = 'sysfs.tar.gz'
|
|
|
|
DEVICE_PATH = 0
|
|
|
|
BEFORE_PATH = 1
|
|
|
|
AFTER_PATH = 2
|
|
|
|
DIFF_PATH = 3
|
|
|
|
|
|
|
|
parameters = [
|
|
|
|
Parameter('paths', kind=list_of_strings, mandatory=True,
|
|
|
|
description="""A list of paths to be pulled from the device. These could be directories
|
|
|
|
as well as files.""",
|
|
|
|
global_alias='sysfs_extract_dirs'),
|
|
|
|
Parameter('use_tmpfs', kind=bool, default=None,
|
|
|
|
description="""
|
|
|
|
Specifies whether tmpfs should be used to cache sysfile trees and then pull them down
|
|
|
|
as a tarball. This is significantly faster then just copying the directory trees from
|
|
|
|
the device directly, bur requres root and may not work on all devices. Defaults to
|
|
|
|
``True`` if the device is rooted and ``False`` if it is not.
|
|
|
|
"""),
|
|
|
|
Parameter('tmpfs_mount_point', default=None,
|
|
|
|
description="""Mount point for tmpfs partition used to store snapshots of paths."""),
|
|
|
|
Parameter('tmpfs_size', default='32m',
|
|
|
|
description="""Size of the tempfs partition."""),
|
|
|
|
]
|
|
|
|
|
|
|
|
def initialize(self, context):
|
2017-07-25 15:22:38 +01:00
|
|
|
if not self.target.is_rooted and self.use_tmpfs: # pylint: disable=access-member-before-definition
|
2017-03-09 14:44:26 +00:00
|
|
|
raise ConfigError('use_tempfs must be False for an unrooted device.')
|
|
|
|
elif self.use_tmpfs is None: # pylint: disable=access-member-before-definition
|
2017-07-25 15:22:38 +01:00
|
|
|
self.use_tmpfs = self.target.is_rooted
|
2017-03-09 14:44:26 +00:00
|
|
|
|
|
|
|
if self.use_tmpfs:
|
2017-07-25 15:22:38 +01:00
|
|
|
self.on_device_before = self.target.path.join(self.tmpfs_mount_point, 'before')
|
|
|
|
self.on_device_after = self.target.path.join(self.tmpfs_mount_point, 'after')
|
2017-03-09 14:44:26 +00:00
|
|
|
|
2017-07-25 15:22:38 +01:00
|
|
|
if not self.target.file_exists(self.tmpfs_mount_point):
|
|
|
|
self.target.execute('mkdir -p {}'.format(self.tmpfs_mount_point), as_root=True)
|
|
|
|
self.target.execute(self.mount_command.format(self.tmpfs_size, self.tmpfs_mount_point),
|
2017-03-09 14:44:26 +00:00
|
|
|
as_root=True)
|
|
|
|
|
|
|
|
def setup(self, context):
|
|
|
|
before_dirs = [
|
|
|
|
_d(os.path.join(context.output_directory, 'before', self._local_dir(d)))
|
|
|
|
for d in self.paths
|
|
|
|
]
|
|
|
|
after_dirs = [
|
|
|
|
_d(os.path.join(context.output_directory, 'after', self._local_dir(d)))
|
|
|
|
for d in self.paths
|
|
|
|
]
|
|
|
|
diff_dirs = [
|
|
|
|
_d(os.path.join(context.output_directory, 'diff', self._local_dir(d)))
|
|
|
|
for d in self.paths
|
|
|
|
]
|
2018-05-30 13:58:49 +01:00
|
|
|
self.device_and_host_paths = list(zip(self.paths, before_dirs, after_dirs, diff_dirs))
|
2017-03-09 14:44:26 +00:00
|
|
|
|
|
|
|
if self.use_tmpfs:
|
|
|
|
for d in self.paths:
|
2017-07-25 15:22:38 +01:00
|
|
|
before_dir = self.target.path.join(self.on_device_before,
|
|
|
|
self.target.path.dirname(as_relative(d)))
|
|
|
|
after_dir = self.target.path.join(self.on_device_after,
|
|
|
|
self.target.path.dirname(as_relative(d)))
|
|
|
|
if self.target.file_exists(before_dir):
|
|
|
|
self.target.execute('rm -rf {}'.format(before_dir), as_root=True)
|
|
|
|
self.target.execute('mkdir -p {}'.format(before_dir), as_root=True)
|
|
|
|
if self.target.file_exists(after_dir):
|
|
|
|
self.target.execute('rm -rf {}'.format(after_dir), as_root=True)
|
|
|
|
self.target.execute('mkdir -p {}'.format(after_dir), as_root=True)
|
2017-03-09 14:44:26 +00:00
|
|
|
|
2017-10-06 10:03:09 +01:00
|
|
|
@slow
|
|
|
|
def start(self, context):
|
2017-03-09 14:44:26 +00:00
|
|
|
if self.use_tmpfs:
|
|
|
|
for d in self.paths:
|
2017-07-25 15:22:38 +01:00
|
|
|
dest_dir = self.target.path.join(self.on_device_before, as_relative(d))
|
2017-03-09 14:44:26 +00:00
|
|
|
if '*' in dest_dir:
|
2017-07-25 15:22:38 +01:00
|
|
|
dest_dir = self.target.path.dirname(dest_dir)
|
|
|
|
self.target.execute('{} cp -Hr {} {}'.format(self.target.busybox, d, dest_dir),
|
2017-03-09 14:44:26 +00:00
|
|
|
as_root=True, check_exit_code=False)
|
|
|
|
else: # not rooted
|
|
|
|
for dev_dir, before_dir, _, _ in self.device_and_host_paths:
|
2017-07-25 15:22:38 +01:00
|
|
|
self.target.pull(dev_dir, before_dir)
|
2017-03-09 14:44:26 +00:00
|
|
|
|
2017-10-06 10:03:09 +01:00
|
|
|
@slow
|
|
|
|
def stop(self, context):
|
2017-03-09 14:44:26 +00:00
|
|
|
if self.use_tmpfs:
|
|
|
|
for d in self.paths:
|
2017-07-25 15:22:38 +01:00
|
|
|
dest_dir = self.target.path.join(self.on_device_after, as_relative(d))
|
2017-03-09 14:44:26 +00:00
|
|
|
if '*' in dest_dir:
|
2017-07-25 15:22:38 +01:00
|
|
|
dest_dir = self.target.path.dirname(dest_dir)
|
|
|
|
self.target.execute('{} cp -Hr {} {}'.format(self.target.busybox, d, dest_dir),
|
2017-03-09 14:44:26 +00:00
|
|
|
as_root=True, check_exit_code=False)
|
|
|
|
else: # not using tmpfs
|
|
|
|
for dev_dir, _, after_dir, _ in self.device_and_host_paths:
|
2017-07-25 15:22:38 +01:00
|
|
|
self.target.pull(dev_dir, after_dir)
|
2017-03-09 14:44:26 +00:00
|
|
|
|
2018-01-10 14:37:13 +00:00
|
|
|
def update_output(self, context):
|
2017-03-09 14:44:26 +00:00
|
|
|
if self.use_tmpfs:
|
2017-07-25 15:22:38 +01:00
|
|
|
on_device_tarball = self.target.path.join(self.target.working_directory, self.tarname)
|
|
|
|
on_host_tarball = self.target.path.join(context.output_directory, self.tarname)
|
|
|
|
self.target.execute('{} tar czf {} -C {} .'.format(self.target.busybox,
|
2017-03-09 14:44:26 +00:00
|
|
|
on_device_tarball,
|
|
|
|
self.tmpfs_mount_point),
|
|
|
|
as_root=True)
|
2017-07-25 15:22:38 +01:00
|
|
|
self.target.execute('chmod 0777 {}'.format(on_device_tarball), as_root=True)
|
|
|
|
self.target.pull(on_device_tarball, on_host_tarball)
|
2017-03-09 14:44:26 +00:00
|
|
|
with tarfile.open(on_host_tarball, 'r:gz') as tf:
|
|
|
|
tf.extractall(context.output_directory)
|
2017-07-25 15:22:38 +01:00
|
|
|
self.target.remove(on_device_tarball)
|
2017-03-09 14:44:26 +00:00
|
|
|
os.remove(on_host_tarball)
|
|
|
|
|
|
|
|
for paths in self.device_and_host_paths:
|
|
|
|
after_dir = paths[self.AFTER_PATH]
|
|
|
|
dev_dir = paths[self.DEVICE_PATH].strip('*') # remove potential trailing '*'
|
|
|
|
if (not os.listdir(after_dir) and
|
2017-07-25 15:22:38 +01:00
|
|
|
self.target.file_exists(dev_dir) and
|
|
|
|
self.target.list_directory(dev_dir)):
|
2017-03-09 14:44:26 +00:00
|
|
|
self.logger.error('sysfs files were not pulled from the device.')
|
|
|
|
self.device_and_host_paths.remove(paths) # Path is removed to skip diffing it
|
|
|
|
for _, before_dir, after_dir, diff_dir in self.device_and_host_paths:
|
2018-05-30 13:58:49 +01:00
|
|
|
diff_sysfs_dirs(before_dir, after_dir, diff_dir)
|
2017-03-09 14:44:26 +00:00
|
|
|
|
|
|
|
def teardown(self, context):
|
|
|
|
self._one_time_setup_done = []
|
|
|
|
|
|
|
|
def finalize(self, context):
|
|
|
|
if self.use_tmpfs:
|
|
|
|
try:
|
2017-07-25 15:22:38 +01:00
|
|
|
self.target.execute('umount {}'.format(self.tmpfs_mount_point), as_root=True)
|
2017-03-09 14:44:26 +00:00
|
|
|
except (TargetError, CalledProcessError):
|
|
|
|
# assume a directory but not mount point
|
|
|
|
pass
|
2017-07-25 15:22:38 +01:00
|
|
|
self.target.execute('rm -rf {}'.format(self.tmpfs_mount_point),
|
2017-03-09 14:44:26 +00:00
|
|
|
as_root=True, check_exit_code=False)
|
|
|
|
|
|
|
|
def validate(self):
|
|
|
|
if not self.tmpfs_mount_point: # pylint: disable=access-member-before-definition
|
2017-10-06 10:03:09 +01:00
|
|
|
self.tmpfs_mount_point = self.target.get_workpath('temp-fs')
|
2017-03-09 14:44:26 +00:00
|
|
|
|
|
|
|
def _local_dir(self, directory):
|
2017-07-25 15:22:38 +01:00
|
|
|
return os.path.dirname(as_relative(directory).replace(self.target.path.sep, os.sep))
|
2017-03-09 14:44:26 +00:00
|
|
|
|
|
|
|
|
|
|
|
class ExecutionTimeInstrument(Instrument):
|
|
|
|
|
|
|
|
name = 'execution_time'
|
|
|
|
description = """
|
|
|
|
Measure how long it took to execute the run() methods of a Workload.
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(self, target, **kwargs):
|
|
|
|
super(ExecutionTimeInstrument, self).__init__(target, **kwargs)
|
|
|
|
self.start_time = None
|
|
|
|
self.end_time = None
|
|
|
|
|
2017-03-17 15:57:05 +00:00
|
|
|
@very_fast
|
|
|
|
def start(self, context):
|
2017-03-09 14:44:26 +00:00
|
|
|
self.start_time = time.time()
|
|
|
|
|
2017-03-17 15:57:05 +00:00
|
|
|
@very_fast
|
|
|
|
def stop(self, context):
|
2017-03-09 14:44:26 +00:00
|
|
|
self.end_time = time.time()
|
|
|
|
|
2018-01-10 14:37:13 +00:00
|
|
|
def update_output(self, context):
|
2017-03-09 14:44:26 +00:00
|
|
|
execution_time = self.end_time - self.start_time
|
2017-03-17 15:57:05 +00:00
|
|
|
context.add_metric('execution_time', execution_time, 'seconds')
|
2017-03-09 14:44:26 +00:00
|
|
|
|
|
|
|
|
|
|
|
class ApkVersion(Instrument):
|
|
|
|
|
|
|
|
name = 'apk_version'
|
|
|
|
description = """
|
|
|
|
Extracts APK versions for workloads that have them.
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(self, device, **kwargs):
|
|
|
|
super(ApkVersion, self).__init__(device, **kwargs)
|
|
|
|
self.apk_info = None
|
|
|
|
|
|
|
|
def setup(self, context):
|
|
|
|
if hasattr(context.workload, 'apk_file'):
|
|
|
|
self.apk_info = ApkInfo(context.workload.apk_file)
|
|
|
|
else:
|
|
|
|
self.apk_info = None
|
|
|
|
|
2018-01-10 14:37:13 +00:00
|
|
|
def update_output(self, context):
|
2017-03-09 14:44:26 +00:00
|
|
|
if self.apk_info:
|
|
|
|
context.result.add_metric(self.name, self.apk_info.version_name)
|
|
|
|
|
|
|
|
|
|
|
|
class InterruptStatsInstrument(Instrument):
|
|
|
|
|
|
|
|
name = 'interrupts'
|
|
|
|
description = """
|
|
|
|
Pulls the ``/proc/interrupts`` file before and after workload execution and diffs them
|
|
|
|
to show what interrupts occurred during that time.
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
2017-10-05 17:52:36 +01:00
|
|
|
def __init__(self, target, **kwargs):
|
|
|
|
super(InterruptStatsInstrument, self).__init__(target, **kwargs)
|
2017-03-09 14:44:26 +00:00
|
|
|
self.before_file = None
|
|
|
|
self.after_file = None
|
|
|
|
self.diff_file = None
|
|
|
|
|
|
|
|
def setup(self, context):
|
|
|
|
self.before_file = os.path.join(context.output_directory, 'before', 'proc', 'interrupts')
|
|
|
|
self.after_file = os.path.join(context.output_directory, 'after', 'proc', 'interrupts')
|
|
|
|
self.diff_file = os.path.join(context.output_directory, 'diff', 'proc', 'interrupts')
|
|
|
|
|
|
|
|
def start(self, context):
|
|
|
|
with open(_f(self.before_file), 'w') as wfh:
|
2017-10-05 17:52:36 +01:00
|
|
|
wfh.write(self.target.execute('cat /proc/interrupts'))
|
2017-03-09 14:44:26 +00:00
|
|
|
|
|
|
|
def stop(self, context):
|
|
|
|
with open(_f(self.after_file), 'w') as wfh:
|
2017-10-05 17:52:36 +01:00
|
|
|
wfh.write(self.target.execute('cat /proc/interrupts'))
|
2017-03-09 14:44:26 +00:00
|
|
|
|
2018-01-10 14:37:13 +00:00
|
|
|
def update_output(self, context):
|
2017-03-09 14:44:26 +00:00
|
|
|
# If workload execution failed, the after_file may not have been created.
|
|
|
|
if os.path.isfile(self.after_file):
|
2018-05-30 13:58:49 +01:00
|
|
|
diff_interrupt_files(self.before_file, self.after_file, _f(self.diff_file))
|
2017-03-09 14:44:26 +00:00
|
|
|
|
|
|
|
|
|
|
|
class DynamicFrequencyInstrument(SysfsExtractor):
|
|
|
|
|
|
|
|
name = 'cpufreq'
|
|
|
|
description = """
|
|
|
|
Collects dynamic frequency (DVFS) settings before and after workload execution.
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
tarname = 'cpufreq.tar.gz'
|
|
|
|
|
|
|
|
parameters = [
|
|
|
|
Parameter('paths', mandatory=False, override=True),
|
|
|
|
]
|
|
|
|
|
|
|
|
def setup(self, context):
|
|
|
|
self.paths = ['/sys/devices/system/cpu']
|
|
|
|
if self.use_tmpfs:
|
|
|
|
self.paths.append('/sys/class/devfreq/*') # the '*' would cause problems for adb pull.
|
|
|
|
super(DynamicFrequencyInstrument, self).setup(context)
|
|
|
|
|
|
|
|
def validate(self):
|
2017-10-06 10:03:09 +01:00
|
|
|
super(DynamicFrequencyInstrument, self).validate()
|
2017-03-09 14:44:26 +00:00
|
|
|
if not self.tmpfs_mount_point.endswith('-cpufreq'): # pylint: disable=access-member-before-definition
|
|
|
|
self.tmpfs_mount_point += '-cpufreq'
|