1
0
mirror of https://github.com/ARM-software/workload-automation.git synced 2025-01-19 04:21:17 +00:00

Merge pull request #281 from jimboatarm/uxperf-newfps

uxperf result_processor: updated for changes in FPS instrument
This commit is contained in:
setrofim 2016-11-08 13:20:31 +00:00 committed by GitHub
commit fce04d2938

110
wlauto/result_processors/uxperf.py Normal file → Executable file
View File

@ -20,8 +20,10 @@ import logging
from collections import defaultdict from collections import defaultdict
from distutils.version import LooseVersion from distutils.version import LooseVersion
from wlauto import ResultProcessor, Parameter from wlauto import ResultProcessor, Parameter
from wlauto.exceptions import ResultProcessorError from wlauto.instrumentation import instrument_is_enabled
from wlauto.utils.fps import FpsProcessor from wlauto.instrumentation.fps import VSYNC_INTERVAL
from wlauto.exceptions import ResultProcessorError, ConfigError
from wlauto.utils.fps import FpsProcessor, SurfaceFlingerFrame, GfxInfoFrame
from wlauto.utils.types import numeric, boolean from wlauto.utils.types import numeric, boolean
try: try:
@ -80,6 +82,8 @@ class UxPerfResultProcessor(ResultProcessor):
'(version 0.13.1 or higher) to be installed.\n' '(version 0.13.1 or higher) to be installed.\n'
'You can install it with pip, e.g. "sudo pip install pandas"') 'You can install it with pip, e.g. "sudo pip install pandas"')
raise ResultProcessorError(message) raise ResultProcessorError(message)
if self.add_frames and not instrument_is_enabled('fps'):
raise ConfigError('fps instrument must be enabled in order to add frames.')
def export_iteration_result(self, result, context): def export_iteration_result(self, result, context):
parser = UxPerfParser(context) parser = UxPerfParser(context)
@ -121,7 +125,6 @@ class UxPerfParser(object):
self.context = context self.context = context
self.actions = defaultdict(list) self.actions = defaultdict(list)
self.logger = logging.getLogger('UxPerfParser') self.logger = logging.getLogger('UxPerfParser')
# regex for matching logcat message format: # regex for matching logcat message format:
self.regex = re.compile(r'UX_PERF.*?:\s*(?P<message>.*\d+$)') self.regex = re.compile(r'UX_PERF.*?:\s*(?P<message>.*\d+$)')
@ -133,8 +136,7 @@ class UxPerfParser(object):
actions mapped to timestamps. actions mapped to timestamps.
''' '''
loglines = self._read(log) loglines = self._read(log)
timestamps = self._gen_action_timestamps(loglines) self._gen_action_timestamps(loglines)
self._group_timestamps(timestamps)
def add_action_frames(self, frames, drop_threshold, generate_csv): # pylint: disable=too-many-locals def add_action_frames(self, frames, drop_threshold, generate_csv): # pylint: disable=too-many-locals
''' '''
@ -145,14 +147,16 @@ class UxPerfParser(object):
for action in self.actions: for action in self.actions:
# default values # default values
fps = float('nan') fps, frame_count, janks, not_at_vsync = float('nan'), 0, 0, 0
frame_count, janks, not_at_vsync = 0, 0, 0 p90, p95, p99 = [float('nan')] * 3
metrics = fps, frame_count, janks, not_at_vsync metrics = (fps, frame_count, janks, not_at_vsync)
df = self._create_data_dict(action, frames) df = self._create_sub_df(self.actions[action], frames)
fp = FpsProcessor(pd.DataFrame(df), action=action) if not df.empty: # pylint: disable=maybe-no-member
fp = FpsProcessor(df, action=action)
try: try:
per_frame_fps, metrics = fp.process(refresh_period, drop_threshold) per_frame_fps, metrics = fp.process(refresh_period, drop_threshold)
fps, frame_count, janks, not_at_vsync = metrics
if generate_csv: if generate_csv:
name = action + '_fps' name = action + '_fps'
@ -160,17 +164,19 @@ class UxPerfParser(object):
fps_outfile = os.path.join(self.context.output_directory, filename) fps_outfile = os.path.join(self.context.output_directory, filename)
per_frame_fps.to_csv(fps_outfile, index=False, header=True) per_frame_fps.to_csv(fps_outfile, index=False, header=True)
self.context.add_artifact(name, path=filename, kind='data') self.context.add_artifact(name, path=filename, kind='data')
p90, p95, p99 = fp.percentiles()
except AttributeError: except AttributeError:
self.logger.warning('Non-matched timestamps in dumpsys output: action={}' self.logger.warning('Non-matched timestamps in dumpsys output: action={}'
.format(action)) .format(action))
fps, frame_count, janks, not_at_vsync = metrics self.context.result.add_metric(action + '_FPS', fps)
result = self.context.result self.context.result.add_metric(action + '_frame_count', frame_count)
self.context.result.add_metric(action + '_janks', janks, lower_is_better=True)
result.add_metric(action + '_FPS', fps) self.context.result.add_metric(action + '_not_at_vsync', not_at_vsync, lower_is_better=True)
result.add_metric(action + '_frame_count', frame_count) self.context.result.add_metric(action + '_frame_time_90percentile', p90, 'ms', lower_is_better=True)
result.add_metric(action + '_janks', janks) self.context.result.add_metric(action + '_frame_time_95percentile', p95, 'ms', lower_is_better=True)
result.add_metric(action + '_not_at_vsync', not_at_vsync) self.context.result.add_metric(action + '_frame_time_99percentile', p99, 'ms', lower_is_better=True)
def add_action_timings(self): def add_action_timings(self):
''' '''
@ -179,18 +185,23 @@ class UxPerfParser(object):
for action, timestamps in self.actions.iteritems(): for action, timestamps in self.actions.iteritems():
# nanosecond precision, but not necessarily nanosecond resolution # nanosecond precision, but not necessarily nanosecond resolution
# truncate to guarantee millisecond precision # truncate to guarantee millisecond precision
start, finish = tuple(int(ts[:-6]) for ts in timestamps) ts_ms = tuple(int(ts[:-6]) for ts in timestamps)
if len(ts_ms) == 2:
start, finish = ts_ms
duration = finish - start duration = finish - start
result = self.context.result result = self.context.result
result.add_metric(action + "_start", start, units='ms') result.add_metric(action + "_start", start, units='ms')
result.add_metric(action + "_finish", finish, units='ms') result.add_metric(action + "_finish", finish, units='ms')
result.add_metric(action + "_duration", duration, units='ms') result.add_metric(action + "_duration", duration, units='ms', lower_is_better=True)
else:
self.logger.warning('Expected two timestamps. Received {}'.format(ts_ms))
def _gen_action_timestamps(self, lines): def _gen_action_timestamps(self, lines):
''' '''
Parses lines and matches against logcat tag. Parses lines and matches against logcat tag.
Yields tuple containing action and timestamp. Groups timestamps by action name.
Creates a dictionary of lists with actions mapped to timestamps.
''' '''
for line in lines: for line in lines:
match = self.regex.search(line) match = self.regex.search(line)
@ -199,14 +210,6 @@ class UxPerfParser(object):
message = match.group('message') message = match.group('message')
action_with_suffix, timestamp = message.rsplit(' ', 1) action_with_suffix, timestamp = message.rsplit(' ', 1)
action, _ = action_with_suffix.rsplit('_', 1) action, _ = action_with_suffix.rsplit('_', 1)
yield action, timestamp
def _group_timestamps(self, markers):
'''
Groups timestamps by action name.
Creates a dictionary of lists with actions mapped to timestamps.
'''
for action, timestamp in markers:
self.actions[action].append(timestamp) self.actions[action].append(timestamp)
def _parse_refresh_peroid(self): def _parse_refresh_peroid(self):
@ -214,29 +217,33 @@ class UxPerfParser(object):
Reads the first line of the raw dumpsys output for the refresh period. Reads the first line of the raw dumpsys output for the refresh period.
''' '''
raw_path = os.path.join(self.context.output_directory, 'surfaceflinger.raw') raw_path = os.path.join(self.context.output_directory, 'surfaceflinger.raw')
if os.path.isfile(raw_path):
raw_lines = self._read(raw_path) raw_lines = self._read(raw_path)
refresh_period = raw_lines.next() refresh_period = int(raw_lines.next())
else:
refresh_period = VSYNC_INTERVAL
return int(refresh_period) return refresh_period
def _create_data_dict(self, action, frames): def _create_sub_df(self, action, frames):
''' '''
Creates a data dict containing surface flinger metrics for a captured Creates a data frame containing fps metrics for a captured action.
action. Used to create a DataFrame for use with the pandas library.
''' '''
loglines = self._read(frames) start, end = map(int, action)
loglines.next() # skip csv header df = pd.read_csv(frames)
# SurfaceFlinger Algorithm
d = defaultdict(list) if df.columns.tolist() == list(SurfaceFlingerFrame._fields): # pylint: disable=maybe-no-member
timestamps = self.actions[action] field = 'actual_present_time'
# GfxInfo Algorithm
for row in self._matched_rows(loglines, timestamps): elif df.columns.tolist() == list(GfxInfoFrame._fields): # pylint: disable=maybe-no-member
dpt, apt, frt = tuple(map(int, row.split(','))) field = 'FrameCompleted'
d["desired_present_time"].append(dpt) else:
d["actual_present_time"].append(apt) field = ''
d["frame_ready_time"].append(frt) self.logger.error('frames.csv not in a recognised format. Cannot parse.')
if field:
return d df = df[start < df[field]]
df = df[df[field] <= end]
return df
def _read(self, log): def _read(self, log):
''' '''
@ -248,14 +255,3 @@ class UxPerfParser(object):
yield line.strip() yield line.strip()
except IOError: except IOError:
self.logger.error('Could not open {}'.format(log)) self.logger.error('Could not open {}'.format(log))
@staticmethod
def _matched_rows(rows, timestamps):
'''
Helper method for matching timestamps within rows.
'''
start, finish = tuple(timestamps)
for row in rows:
_, apt, _ = row.split(',')
if apt >= start and apt <= finish:
yield row