1
0
mirror of https://github.com/ARM-software/workload-automation.git synced 2025-02-21 20:38:57 +00:00

Merge pull request #176 from ep1cman/cpustates

cpustates
This commit is contained in:
setrofim 2016-06-06 17:12:12 +01:00
commit 63ff8987ea
4 changed files with 115 additions and 39 deletions

View File

@ -19,7 +19,7 @@ from collections import OrderedDict
from wlauto import ResultProcessor, Parameter from wlauto import ResultProcessor, Parameter
from wlauto.core import signal from wlauto.core import signal
from wlauto.exceptions import ConfigError from wlauto.exceptions import ConfigError, DeviceError
from wlauto.instrumentation import instrument_is_installed from wlauto.instrumentation import instrument_is_installed
from wlauto.utils.power import report_power_stats from wlauto.utils.power import report_power_stats
from wlauto.utils.misc import unique from wlauto.utils.misc import unique
@ -109,6 +109,19 @@ class CpuStatesProcessor(ResultProcessor):
The values generated are floating point numbers, normalised based on the maximum The values generated are floating point numbers, normalised based on the maximum
frequency of the cluster. frequency of the cluster.
"""), """),
Parameter('start_marker_handling', kind=str, default="try",
allowed_values=['ignore', 'try', 'error'],
description="""
The trace-cmd instrument inserts a marker into the trace to indicate the beginning
of workload execution. In some cases, this marker may be missing in the final
output (e.g. due to trace buffer overrun). This parameter specifies how a missing
start marker will be handled:
:`ignore`: The start marker will be ignored. All events in the trace will be used.
:`error`: An error will be raised if the start marker is not found in the trace.
:`try`: If the start marker is not found, all events in the trace will be used.
""")
] ]
def validate(self): def validate(self):
@ -169,7 +182,10 @@ class CpuStatesProcessor(ResultProcessor):
self.logger.debug('Nudging all cores awake...') self.logger.debug('Nudging all cores awake...')
for i in xrange(len(device.core_names)): for i in xrange(len(device.core_names)):
command = device.busybox + ' taskset 0x{:x} {}' command = device.busybox + ' taskset 0x{:x} {}'
device.execute(command.format(1 << i, 'ls')) try:
device.execute(command.format(1 << i, 'ls'))
except DeviceError:
self.logger.warning("Failed to nudge CPU %s, has it been hot plugged out?", i)
def process_iteration_result(self, result, context): def process_iteration_result(self, result, context):
trace = context.get_artifact('txttrace') trace = context.get_artifact('txttrace')
@ -198,10 +214,10 @@ class CpuStatesProcessor(ResultProcessor):
timeline_csv_file=timeline_csv_file, timeline_csv_file=timeline_csv_file,
cpu_utilisation=cpu_utilisation, cpu_utilisation=cpu_utilisation,
max_freq_list=self.max_freq_list, max_freq_list=self.max_freq_list,
start_marker_handling=self.start_marker_handling,
) )
parallel_report = reports.pop(0) parallel_report = reports.pop(0)
powerstate_report = reports.pop(0) powerstate_report = reports.pop(0)
if parallel_report is None: if parallel_report is None:
self.logger.warning('No power state reports generated; are power ' self.logger.warning('No power state reports generated; are power '
'events enabled in the trace?') 'events enabled in the trace?')

View File

@ -823,3 +823,21 @@ def sha256(path, chunk=2048):
def urljoin(*parts): def urljoin(*parts):
return '/'.join(p.rstrip('/') for p in parts) return '/'.join(p.rstrip('/') for p in parts)
__memo_cache = {}
def memoized(func):
"""A decorator for memoizing functions and methods."""
func_id = repr(func)
def memoize_wrapper(*args, **kwargs):
id_string = func_id + ','.join([str(id(a)) for a in args])
id_string += ','.join('{}={}'.format(k, v)
for k, v in kwargs.iteritems())
if id_string not in __memo_cache:
__memo_cache[id_string] = func(*args, **kwargs)
return __memo_cache[id_string]
return memoize_wrapper

View File

@ -24,6 +24,7 @@ from collections import defaultdict
import argparse import argparse
from wlauto.utils.trace_cmd import TraceCmdTrace, TRACE_MARKER_START, TRACE_MARKER_STOP from wlauto.utils.trace_cmd import TraceCmdTrace, TRACE_MARKER_START, TRACE_MARKER_STOP
from wlauto.exceptions import DeviceError
logger = logging.getLogger('power') logger = logging.getLogger('power')
@ -157,6 +158,8 @@ class PowerStateProcessor(object):
self.requested_states = defaultdict(lambda: -1) # cpu_id -> requeseted state self.requested_states = defaultdict(lambda: -1) # cpu_id -> requeseted state
self.wait_for_start_marker = wait_for_start_marker self.wait_for_start_marker = wait_for_start_marker
self._saw_start_marker = False self._saw_start_marker = False
self._saw_stop_marker = False
self.exceptions = []
idle_state_domains = build_idle_domains(core_clusters, idle_state_domains = build_idle_domains(core_clusters,
num_states=num_idle_states, num_states=num_idle_states,
@ -173,9 +176,17 @@ class PowerStateProcessor(object):
def process(self, event_stream): def process(self, event_stream):
for event in event_stream: for event in event_stream:
next_state = self.update_power_state(event) try:
if self._saw_start_marker or not self.wait_for_start_marker: next_state = self.update_power_state(event)
yield next_state if self._saw_start_marker or not self.wait_for_start_marker:
yield next_state
if self._saw_stop_marker:
break
except Exception as e: # pylint: disable=broad-except
self.exceptions.append(e)
else:
if self.wait_for_start_marker:
logger.warning("Did not see a STOP marker in the trace")
def update_power_state(self, event): def update_power_state(self, event):
""" """
@ -191,7 +202,7 @@ class PowerStateProcessor(object):
if event.name == 'START': if event.name == 'START':
self._saw_start_marker = True self._saw_start_marker = True
elif event.name == 'STOP': elif event.name == 'STOP':
self._saw_start_marker = False self._saw_stop_marker = True
else: else:
raise ValueError('Unexpected event type: {}'.format(event.kind)) raise ValueError('Unexpected event type: {}'.format(event.kind))
return self.power_state.copy() return self.power_state.copy()
@ -610,15 +621,29 @@ def build_idle_domains(core_clusters, # NOQA
def report_power_stats(trace_file, idle_state_names, core_names, core_clusters, def report_power_stats(trace_file, idle_state_names, core_names, core_clusters,
num_idle_states, first_cluster_state=sys.maxint, num_idle_states, first_cluster_state=sys.maxint,
first_system_state=sys.maxint, use_ratios=False, first_system_state=sys.maxint, use_ratios=False,
timeline_csv_file=None, filter_trace=False, timeline_csv_file=None, cpu_utilisation=None,
cpu_utilisation=None, max_freq_list=None): max_freq_list=None, start_marker_handling='error'):
# pylint: disable=too-many-locals # pylint: disable=too-many-locals,too-many-branches
trace = TraceCmdTrace(filter_markers=filter_trace) trace = TraceCmdTrace(trace_file,
filter_markers=False,
names=['cpu_idle', 'cpu_frequency', 'print'])
wait_for_start_marker = True
if start_marker_handling == "error" and not trace.has_start_marker:
raise DeviceError("Start marker was not found in the trace")
elif start_marker_handling == "try":
wait_for_start_marker = trace.has_start_marker
if not wait_for_start_marker:
logger.warning("Did not see a START marker in the trace, "
"state residency and parallelism statistics may be inaccurate.")
elif start_marker_handling == "ignore":
wait_for_start_marker = False
ps_processor = PowerStateProcessor(core_clusters, ps_processor = PowerStateProcessor(core_clusters,
num_idle_states=num_idle_states, num_idle_states=num_idle_states,
first_cluster_state=first_cluster_state, first_cluster_state=first_cluster_state,
first_system_state=first_system_state, first_system_state=first_system_state,
wait_for_start_marker=not filter_trace) wait_for_start_marker=wait_for_start_marker)
reporters = [ reporters = [
ParallelStats(core_clusters, use_ratios), ParallelStats(core_clusters, use_ratios),
PowerStateStats(core_names, idle_state_names, use_ratios) PowerStateStats(core_names, idle_state_names, use_ratios)
@ -632,7 +657,7 @@ def report_power_stats(trace_file, idle_state_names, core_names, core_clusters,
else: else:
logger.warning('Maximum frequencies not found. Cannot normalise. Skipping CPU Utilisation Timeline') logger.warning('Maximum frequencies not found. Cannot normalise. Skipping CPU Utilisation Timeline')
event_stream = trace.parse(trace_file, names=['cpu_idle', 'cpu_frequency', 'print']) event_stream = trace.parse()
transition_stream = stream_cpu_power_transitions(event_stream) transition_stream = stream_cpu_power_transitions(event_stream)
power_state_stream = ps_processor.process(transition_stream) power_state_stream = ps_processor.process(transition_stream)
core_state_stream = gather_core_states(power_state_stream) core_state_stream = gather_core_states(power_state_stream)
@ -641,6 +666,11 @@ def report_power_stats(trace_file, idle_state_names, core_names, core_clusters,
for reporter in reporters: for reporter in reporters:
reporter.update(timestamp, states) reporter.update(timestamp, states)
if ps_processor.exceptions:
logger.warning('There were errors while processing trace:')
for e in ps_processor.exceptions:
logger.warning(str(e))
reports = [] reports = []
for reporter in reporters: for reporter in reporters:
report = reporter.report() report = reporter.report()
@ -650,6 +680,7 @@ def report_power_stats(trace_file, idle_state_names, core_names, core_clusters,
def main(): def main():
# pylint: disable=unbalanced-tuple-unpacking # pylint: disable=unbalanced-tuple-unpacking
logging.basicConfig(level=logging.INFO)
args = parse_arguments() args = parse_arguments()
parallel_report, powerstate_report = report_power_stats( parallel_report, powerstate_report = report_power_stats(
trace_file=args.infile, trace_file=args.infile,
@ -661,9 +692,9 @@ def main():
first_system_state=args.first_system_state, first_system_state=args.first_system_state,
use_ratios=args.ratios, use_ratios=args.ratios,
timeline_csv_file=args.timeline_file, timeline_csv_file=args.timeline_file,
filter_trace=(not args.no_trace_filter),
cpu_utilisation=args.cpu_utilisation, cpu_utilisation=args.cpu_utilisation,
max_freq_list=args.max_freq_list, max_freq_list=args.max_freq_list,
start_marker_handling=args.start_marker_handling,
) )
parallel_report.write(os.path.join(args.output_directory, 'parallel.csv')) parallel_report.write(os.path.join(args.output_directory, 'parallel.csv'))
powerstate_report.write(os.path.join(args.output_directory, 'cpustate.csv')) powerstate_report.write(os.path.join(args.output_directory, 'cpustate.csv'))
@ -693,11 +724,6 @@ def parse_arguments(): # NOQA
help=''' help='''
Output directory where reports will be placed. Output directory where reports will be placed.
''') ''')
parser.add_argument('-F', '--no-trace-filter', action='store_true', default=False,
help='''
Normally, only the trace between begin and end marker is used. This disables
the filtering so the entire trace file is considered.
''')
parser.add_argument('-c', '--core-names', action=SplitListAction, parser.add_argument('-c', '--core-names', action=SplitListAction,
help=''' help='''
Comma-separated list of core names for the device on which the trace Comma-separated list of core names for the device on which the trace
@ -750,6 +776,18 @@ def parse_arguments(): # NOQA
Only required if --cpu-utilisation is set. Only required if --cpu-utilisation is set.
This is used to normalise the frequencies to obtain percentage utilisation. This is used to normalise the frequencies to obtain percentage utilisation.
''') ''')
parser.add_argument('-M', '--start-marker-handling', metavar='HANDLING', default="try",
choices=["error", "try", "ignore"],
help='''
The trace-cmd instrument inserts a marker into the trace to indicate the beginning
of workload execution. In some cases, this marker may be missing in the final
output (e.g. due to trace buffer overrun). This parameter specifies how a missing
start marker will be handled:
ignore: The start marker will be ignored. All events in the trace will be used.
error: An error will be raised if the start marker is not found in the trace.
try: If the start marker is not found, all events in the trace will be used.
''')
args = parser.parse_args() args = parser.parse_args()

View File

@ -17,7 +17,7 @@ import re
import logging import logging
from itertools import chain from itertools import chain
from wlauto.utils.misc import isiterable from wlauto.utils.misc import isiterable, memoized
from wlauto.utils.types import numeric from wlauto.utils.types import numeric
@ -215,37 +215,38 @@ EMPTY_CPU_REGEX = re.compile(r'CPU \d+ is empty')
class TraceCmdTrace(object): class TraceCmdTrace(object):
def __init__(self, filter_markers=True): @property
self.filter_markers = filter_markers @memoized
def has_start_marker(self):
with open(self.file_path) as fh:
for line in fh:
if TRACE_MARKER_START in line:
return True
return False
def parse(self, filepath, names=None, check_for_markers=True): # pylint: disable=too-many-branches,too-many-locals def __init__(self, file_path, names=None, filter_markers=True):
self.filter_markers = filter_markers
self.file_path = file_path
self.names = names or []
def parse(self): # pylint: disable=too-many-branches,too-many-locals
""" """
This is a generator for the trace event stream. This is a generator for the trace event stream.
""" """
inside_maked_region = False inside_marked_region = False
filters = [re.compile('^{}$'.format(n)) for n in names or []] filters = [re.compile('^{}$'.format(n)) for n in self.names or []]
if check_for_markers: with open(self.file_path) as fh:
with open(filepath) as fh:
for line in fh:
if TRACE_MARKER_START in line:
break
else:
# maker not found force filtering by marker to False
self.filter_markers = False
with open(filepath) as fh:
for line in fh: for line in fh:
# if processing trace markers, skip marker lines as well as all # if processing trace markers, skip marker lines as well as all
# lines outside marked region # lines outside marked region
if self.filter_markers: if self.filter_markers:
if not inside_maked_region: if not inside_marked_region:
if TRACE_MARKER_START in line: if TRACE_MARKER_START in line:
inside_maked_region = True inside_marked_region = True
continue continue
elif TRACE_MARKER_STOP in line: elif TRACE_MARKER_STOP in line:
inside_maked_region = False break
continue
match = DROPPED_EVENTS_REGEX.search(line) match = DROPPED_EVENTS_REGEX.search(line)
if match: if match:
@ -282,4 +283,7 @@ class TraceCmdTrace(object):
if isinstance(body_parser, basestring) or isinstance(body_parser, re._pattern_type): # pylint: disable=protected-access if isinstance(body_parser, basestring) or isinstance(body_parser, re._pattern_type): # pylint: disable=protected-access
body_parser = regex_body_parser(body_parser) body_parser = regex_body_parser(body_parser)
yield TraceCmdEvent(parser=body_parser, **match.groupdict()) yield TraceCmdEvent(parser=body_parser, **match.groupdict())
else:
if self.filter_markers and inside_marked_region:
logger.warning('Did not encounter a stop marker in trace')