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

Merge pull request #212 from jimboatarm/fps_util_uxperf

Additional changes to FpsProcessor, fps instrument and uxperf result processor
This commit is contained in:
Sebastian Goscik 2016-07-27 14:56:14 +01:00 committed by GitHub
commit 59874b862d
3 changed files with 59 additions and 27 deletions

View File

@ -111,6 +111,18 @@ class FpsInstrument(Instrument):
a content crash. E.g. a value of ``0.75`` means the number of actual frames counted is a
quarter lower than expected, it will treated as a content crash.
"""),
Parameter('dumpsys_period', kind=float, default=2, constraint=lambda x: x > 0,
description="""
Specifies the time period between calls to ``dumpsys SurfaceFlinger --latency`` in
seconds when collecting frame data. Using a lower value improves the granularity
of timings when recording actions that take a short time to complete. Note, this
will produce duplicate frame data in the raw dumpsys output, however, this is
filtered out in frames.csv. It may also affect the overall load on the system.
The default value of 2 seconds corresponds with the NUM_FRAME_RECORDS in
android/services/surfaceflinger/FrameTracker.h (as of the time of writing
currently 128) and a frame rate of 60 fps that is applicable to most devices.
"""),
]
clear_command = 'dumpsys SurfaceFlinger --latency-clear '
@ -135,7 +147,8 @@ class FpsInstrument(Instrument):
if hasattr(workload, 'view'):
self.fps_outfile = os.path.join(context.output_directory, 'fps.csv')
self.outfile = os.path.join(context.output_directory, 'frames.csv')
self.collector = LatencyCollector(self.outfile, self.device, workload.view or '', self.keep_raw, self.logger)
self.collector = LatencyCollector(self.outfile, self.device, workload.view or '',
self.keep_raw, self.logger, self.dumpsys_period)
self.device.execute(self.clear_command)
else:
self.logger.debug('Workload does not contain a view; disabling...')
@ -153,21 +166,22 @@ class FpsInstrument(Instrument):
def update_result(self, context):
if self.is_enabled:
fps, frame_count, janks, not_at_vsync = float('nan'), 0, 0, 0
data = pd.read_csv(self.outfile)
if not data.empty: # pylint: disable=maybe-no-member
fp = FpsProcessor(data)
per_frame_fps, metrics = fp.process(self.collector.refresh_period, self.drop_threshold)
fps, frame_count, janks, not_at_vsync = metrics
context.result.add_metric('FPS', fps)
context.result.add_metric('frame_count', frame_count)
context.result.add_metric('janks', janks)
context.result.add_metric('not_at_vsync', not_at_vsync)
if self.generate_csv:
per_frame_fps.to_csv(self.fps_outfile, index=False, header=True)
context.add_artifact('fps', path='fps.csv', kind='data')
context.result.add_metric('FPS', fps)
context.result.add_metric('frame_count', frame_count)
context.result.add_metric('janks', janks)
context.result.add_metric('not_at_vsync', not_at_vsync)
def slow_update_result(self, context):
result = context.result
if self.crash_check and result.has_metric('execution_time'):
@ -192,16 +206,17 @@ class LatencyCollector(threading.Thread):
# At the time of writing, this was hard-coded to 128. So at 60 fps
# (and there is no reason to go above that, as it matches vsync rate
# on pretty much all phones), there is just over 2 seconds' worth of
# frames in there. Hence the sleep time of 2 seconds between dumps.
# frames in there. Hence the default sleep time of 2 seconds between dumps.
#command_template = 'while (true); do dumpsys SurfaceFlinger --latency {}; sleep 2; done'
command_template = 'dumpsys SurfaceFlinger --latency {}'
def __init__(self, outfile, device, activities, keep_raw, logger):
def __init__(self, outfile, device, activities, keep_raw, logger, dumpsys_period):
super(LatencyCollector, self).__init__()
self.outfile = outfile
self.device = device
self.keep_raw = keep_raw
self.logger = logger
self.dumpsys_period = dumpsys_period
self.stop_signal = threading.Event()
self.frames = []
self.last_ready_time = 0
@ -226,7 +241,7 @@ class LatencyCollector(threading.Thread):
for activity in self.activities:
if activity in view_list:
wfh.write(self.device.execute(self.command_template.format(activity)))
time.sleep(2)
time.sleep(self.dumpsys_period)
finally:
wfh.close()
# TODO: this can happen after the run during results processing

View File

@ -15,6 +15,7 @@
import os
import re
import logging
from collections import defaultdict
from distutils.version import LooseVersion
@ -86,12 +87,15 @@ class UxPerfResultProcessor(ResultProcessor):
logfile = os.path.join(context.output_directory, 'logcat.log')
framelog = os.path.join(context.output_directory, 'frames.csv')
self.logger.debug('Parsing logcat.log for UX_PERF markers')
parser.parse(logfile)
if self.add_timings:
self.logger.debug('Adding per-action timings')
parser.add_action_timings()
if self.add_frames:
self.logger.debug('Adding per-action frame metrics')
parser.add_action_frames(framelog, self.drop_threshold, self.generate_csv)
@ -116,6 +120,7 @@ class UxPerfParser(object):
def __init__(self, context):
self.context = context
self.actions = defaultdict(list)
self.logger = logging.getLogger('UxPerfParser')
# regex for matching logcat message format:
# date time PID-TID/package priority/tag: message
@ -146,16 +151,25 @@ class UxPerfParser(object):
refresh_period = self._parse_refresh_peroid()
for action in self.actions:
# default values
fps = float('nan')
frame_count, janks, not_at_vsync = 0, 0, 0
metrics = fps, frame_count, janks, not_at_vsync
df = self._create_data_dict(action, frames)
fp = FpsProcessor(pd.DataFrame(df), action=action)
per_frame_fps, metrics = fp.process(refresh_period, drop_threshold)
try:
per_frame_fps, metrics = fp.process(refresh_period, drop_threshold)
if generate_csv:
name = action + '_fps'
filename = name + '.csv'
fps_outfile = os.path.join(self.context.output_directory, filename)
per_frame_fps.to_csv(fps_outfile, index=False, header=True)
self.context.add_artifact(name, path=filename, kind='data')
if generate_csv:
name = action + '_fps'
filename = name + '.csv'
fps_outfile = os.path.join(self.context.output_directory, filename)
per_frame_fps.to_csv(fps_outfile, index=False, header=True)
self.context.add_artifact(name, path=filename, kind='data')
except AttributeError:
self.logger.warning('Non-matched timestamps in dumpsys output: action={}'
.format(action))
fps, frame_count, janks, not_at_vsync = metrics
result = self.context.result
@ -232,14 +246,16 @@ class UxPerfParser(object):
return d
@staticmethod
def _read(log):
def _read(self, log):
'''
Opens a file a yields the lines with whitespace stripped.
'''
with open(log, 'r') as rfh:
for line in rfh:
yield line.strip()
try:
with open(log, 'r') as rfh:
for line in rfh:
yield line.strip()
except IOError:
self.logger.error('Could not open {}'.format(log))
@staticmethod
def _matched_rows(rows, timestamps):

View File

@ -16,10 +16,10 @@
class FpsProcessor(object):
"""
Provide common object for processing surfaceFlinger output for frame
Provides common object for processing surfaceFlinger output for frame
statistics.
This processor adds four metrics to the results:
This processor returns the four frame statistics below:
:FPS: Frames Per Second. This is the frame rate of the workload.
:frames: The total number of frames rendered during the execution of
@ -72,11 +72,12 @@ class FpsProcessor(object):
total_vsyncs = filtered_vsyncs_to_compose.sum()
frame_count = filtered_vsyncs_to_compose.size
if total_vsyncs:
fps = 1e9 * frame_count / (vsync_interval * total_vsyncs)
if total_vsyncs:
fps = 1e9 * frame_count / (vsync_interval * total_vsyncs)
janks = self._calc_janks(filtered_vsyncs_to_compose)
not_at_vsync = self._calc_not_at_vsync(vsyncs_to_compose)
janks = self._calc_janks(filtered_vsyncs_to_compose)
not_at_vsync = self._calc_not_at_vsync(vsyncs_to_compose)
metrics = (fps, frame_count, janks, not_at_vsync)
return per_frame_fps, metrics