mirror of
https://github.com/ARM-software/devlib.git
synced 2025-01-31 02:00:45 +00:00
derived: add DerivedGfxInfoStats
Add DerivedGfxInfoStats that parse output from GfxInfoFramesInstrument to produce FPS data and rendering statistics.
This commit is contained in:
parent
e4fda7898d
commit
f692315d9c
@ -21,6 +21,7 @@ from devlib.instrument.gem5power import Gem5PowerInstrument
|
|||||||
|
|
||||||
from devlib.derived import DerivedMeasurements, DerivedMetric
|
from devlib.derived import DerivedMeasurements, DerivedMetric
|
||||||
from devlib.derived.energy import DerivedEnergyMeasurements
|
from devlib.derived.energy import DerivedEnergyMeasurements
|
||||||
|
from devlib.derived.fps import DerivedGfxInfoStats
|
||||||
|
|
||||||
from devlib.trace.ftrace import FtraceCollector
|
from devlib.trace.ftrace import FtraceCollector
|
||||||
|
|
||||||
|
137
devlib/derived/fps.py
Normal file
137
devlib/derived/fps.py
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
from __future__ import division
|
||||||
|
import csv
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
|
try:
|
||||||
|
import pandas as pd
|
||||||
|
except ImportError:
|
||||||
|
pd = None
|
||||||
|
|
||||||
|
from devlib import DerivedMeasurements, DerivedMetric, MeasurementsCsv, InstrumentChannel
|
||||||
|
from devlib.utils.rendering import gfxinfo_get_last_dump, VSYNC_INTERVAL
|
||||||
|
from devlib.utils.types import numeric
|
||||||
|
|
||||||
|
|
||||||
|
class DerivedFpsStats(DerivedMeasurements):
|
||||||
|
|
||||||
|
def __init__(self, drop_threshold=5, suffix=None, filename=None, outdir=None):
|
||||||
|
self.drop_threshold = drop_threshold
|
||||||
|
self.suffix = suffix
|
||||||
|
self.filename = filename
|
||||||
|
self.outdir = outdir
|
||||||
|
if (filename is None) and (suffix is None):
|
||||||
|
self.suffix = '-fps'
|
||||||
|
elif (filename is not None) and (suffix is not None):
|
||||||
|
raise ValueError('suffix and filename cannot be specified at the same time.')
|
||||||
|
if filename is not None and os.sep in filename:
|
||||||
|
raise ValueError('filename cannot be a path (cannot countain "{}"'.format(os.sep))
|
||||||
|
|
||||||
|
def process(self, measurements_csv):
|
||||||
|
if isinstance(measurements_csv, basestring):
|
||||||
|
measurements_csv = MeasurementsCsv(measurements_csv)
|
||||||
|
if pd is not None:
|
||||||
|
return self._process_with_pandas(measurements_csv)
|
||||||
|
return self._process_without_pandas(measurements_csv)
|
||||||
|
|
||||||
|
def _get_csv_file_name(self, frames_file):
|
||||||
|
outdir = self.outdir or os.path.dirname(frames_file)
|
||||||
|
if self.filename:
|
||||||
|
return os.path.join(outdir, self.filename)
|
||||||
|
|
||||||
|
frames_basename = os.path.basename(frames_file)
|
||||||
|
rest, ext = os.path.splitext(frames_basename)
|
||||||
|
csv_basename = rest + self.suffix + ext
|
||||||
|
return os.path.join(outdir, csv_basename)
|
||||||
|
|
||||||
|
|
||||||
|
class DerivedGfxInfoStats(DerivedFpsStats):
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def process_raw(filepath, *args):
|
||||||
|
metrics = []
|
||||||
|
dump = gfxinfo_get_last_dump(filepath)
|
||||||
|
seen_stats = False
|
||||||
|
for line in dump.split('\n'):
|
||||||
|
if seen_stats and not line.strip():
|
||||||
|
break
|
||||||
|
elif line.startswith('Janky frames:'):
|
||||||
|
text = line.split(': ')[-1]
|
||||||
|
val_text, pc_text = text.split('(')
|
||||||
|
metrics.append(DerivedMetric('janks', numeric(val_text.strip()), 'count'))
|
||||||
|
metrics.append(DerivedMetric('janks_pc', numeric(pc_text[:-3]), 'percent'))
|
||||||
|
elif ' percentile: ' in line:
|
||||||
|
ptile, val_text = line.split(' percentile: ')
|
||||||
|
name = 'render_time_{}_ptile'.format(ptile)
|
||||||
|
value = numeric(val_text.strip()[:-2])
|
||||||
|
metrics.append(DerivedMetric(name, value, 'time_ms'))
|
||||||
|
elif line.startswith('Number '):
|
||||||
|
name_text, val_text = line.strip().split(': ')
|
||||||
|
name = name_text[7:].lower().replace(' ', '_')
|
||||||
|
value = numeric(val_text)
|
||||||
|
metrics.append(DerivedMetric(name, value, 'count'))
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
seen_stats = True
|
||||||
|
return metrics
|
||||||
|
|
||||||
|
def _process_without_pandas(self, measurements_csv):
|
||||||
|
per_frame_fps = []
|
||||||
|
start_vsync, end_vsync = None, None
|
||||||
|
frame_count = 0
|
||||||
|
|
||||||
|
for frame_data in measurements_csv.iter_values():
|
||||||
|
if frame_data.Flags_flags != 0:
|
||||||
|
continue
|
||||||
|
frame_count += 1
|
||||||
|
|
||||||
|
if start_vsync is None:
|
||||||
|
start_vsync = frame_data.Vsync_time_us
|
||||||
|
end_vsync = frame_data.Vsync_time_us
|
||||||
|
|
||||||
|
frame_time = frame_data.FrameCompleted_time_us - frame_data.IntendedVsync_time_us
|
||||||
|
pff = 1e9 / frame_time
|
||||||
|
if pff > self.drop_threshold:
|
||||||
|
per_frame_fps.append([pff])
|
||||||
|
|
||||||
|
if frame_count:
|
||||||
|
duration = end_vsync - start_vsync
|
||||||
|
fps = (1e9 * frame_count) / float(duration)
|
||||||
|
else:
|
||||||
|
duration = 0
|
||||||
|
fps = 0
|
||||||
|
|
||||||
|
csv_file = self._get_csv_file_name(measurements_csv.path)
|
||||||
|
with open(csv_file, 'wb') as wfh:
|
||||||
|
writer = csv.writer(wfh)
|
||||||
|
writer.writerow(['fps'])
|
||||||
|
writer.writerows(per_frame_fps)
|
||||||
|
|
||||||
|
return [DerivedMetric('fps', fps, 'fps'),
|
||||||
|
DerivedMetric('total_frames', frame_count, 'frames'),
|
||||||
|
MeasurementsCsv(csv_file)]
|
||||||
|
|
||||||
|
def _process_with_pandas(self, measurements_csv):
|
||||||
|
data = pd.read_csv(measurements_csv.path)
|
||||||
|
data = data[data.Flags_flags == 0]
|
||||||
|
frame_time = data.FrameCompleted_time_us - data.IntendedVsync_time_us
|
||||||
|
per_frame_fps = (1e9 / frame_time)
|
||||||
|
keep_filter = per_frame_fps > self.drop_threshold
|
||||||
|
per_frame_fps = per_frame_fps[keep_filter]
|
||||||
|
per_frame_fps.name = 'fps'
|
||||||
|
|
||||||
|
frame_count = data.index.size
|
||||||
|
if frame_count > 1:
|
||||||
|
duration = data.Vsync_time_us.iloc[-1] - data.Vsync_time_us.iloc[0]
|
||||||
|
fps = (1e9 * frame_count) / float(duration)
|
||||||
|
else:
|
||||||
|
duration = 0
|
||||||
|
fps = 0
|
||||||
|
|
||||||
|
csv_file = self._get_csv_file_name(measurements_csv.path)
|
||||||
|
per_frame_fps.to_csv(csv_file, index=False, header=True)
|
||||||
|
|
||||||
|
return [DerivedMetric('fps', fps, 'fps'),
|
||||||
|
DerivedMetric('total_frames', frame_count, 'frames'),
|
||||||
|
MeasurementsCsv(csv_file)]
|
||||||
|
|
@ -18,6 +18,8 @@ logger = logging.getLogger('rendering')
|
|||||||
SurfaceFlingerFrame = namedtuple('SurfaceFlingerFrame',
|
SurfaceFlingerFrame = namedtuple('SurfaceFlingerFrame',
|
||||||
'desired_present_time actual_present_time frame_ready_time')
|
'desired_present_time actual_present_time frame_ready_time')
|
||||||
|
|
||||||
|
VSYNC_INTERVAL = 16666667
|
||||||
|
|
||||||
|
|
||||||
class FrameCollector(threading.Thread):
|
class FrameCollector(threading.Thread):
|
||||||
|
|
||||||
|
@ -124,3 +124,58 @@ Energy
|
|||||||
sites of the coresponding channels according to the following patters:
|
sites of the coresponding channels according to the following patters:
|
||||||
``"<site>_total_energy"`` and ``"<site>_average_power"``.
|
``"<site>_total_energy"`` and ``"<site>_average_power"``.
|
||||||
|
|
||||||
|
|
||||||
|
FPS / Rendering
|
||||||
|
~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. class:: DerivedGfxInfoStats(drop_threshold=5, suffix='-fps', filename=None, outdir=None)
|
||||||
|
|
||||||
|
Produces FPS (frames-per-second) and other dervied statistics from
|
||||||
|
:class:`GfxInfoFramesInstrument` output. This takes several optional
|
||||||
|
parameters in creation:
|
||||||
|
|
||||||
|
:param drop_threshold: FPS in an application, such as a game, which this
|
||||||
|
processor is primarily targeted at, cannot reasonably
|
||||||
|
drop to a very low value. This is specified to this
|
||||||
|
threhold. If an FPS for a frame is computed to be
|
||||||
|
lower than this treshold, it will be dropped on the
|
||||||
|
assumption that frame rednering was suspended by the
|
||||||
|
system (e.g. when idling), or there was some sort of
|
||||||
|
error, and therefore this should be used in
|
||||||
|
performance calculations. defaults to ``5``.
|
||||||
|
:param suffix: The name of the gerated per-frame FPS csv file will be
|
||||||
|
derived from the input frames csv file by appending this
|
||||||
|
suffix. This cannot be specified at the same time as
|
||||||
|
a ``filename``.
|
||||||
|
:param filename: As an alternative to the suffix, a complete file name for
|
||||||
|
FPS csv can be specified. This cannot be used at the same
|
||||||
|
time as the ``suffix``.
|
||||||
|
:param outdir: By default, the FPS csv file will be placed in the same
|
||||||
|
directory as the input frames csv file. This can be changed
|
||||||
|
by specifying an alternate directory here
|
||||||
|
|
||||||
|
.. warning:: Specifying both ``filename`` and ``oudir`` will mean that exactly
|
||||||
|
the same file will be used for FPS output on each invocation of
|
||||||
|
``process()`` (even for different inputs) resulting in previous
|
||||||
|
results being overwritten.
|
||||||
|
|
||||||
|
.. method:: DerivedGfxInfoStats.process(measurement_csv)
|
||||||
|
|
||||||
|
Process the fames csv generated by :class:`GfxInfoFramesInstrument` and
|
||||||
|
returns a list containing exactly three entries: :class:`DerivedMetric`\ s
|
||||||
|
``fps`` and ``total_frames``, followed by a :class:`MeasurentCsv` containing
|
||||||
|
per-frame FPSs values.
|
||||||
|
|
||||||
|
.. method:: DerivedGfxInfoStats.process_raw(gfxinfo_frame_raw_file)
|
||||||
|
|
||||||
|
As input, this takes a single argument, which should be the path to the raw
|
||||||
|
output file of :class:`GfxInfoFramesInstrument`. The returns stats
|
||||||
|
accumulated by gfxinfo. At the time of wrinting, the stats (in order) are:
|
||||||
|
``janks``, ``janks_pc`` (percentage of all frames),
|
||||||
|
``render_time_50th_ptile`` (50th percentile, or median, for time to render a
|
||||||
|
frame), ``render_time_90th_ptile``, ``render_time_95th_ptile``,
|
||||||
|
``render_time_99th_ptile``, ``missed_vsync``, ``hight_input_latency``,
|
||||||
|
``slow_ui_thread``, ``slow_bitmap_uploads``, ``slow_issue_draw_commands``.
|
||||||
|
Please see the `gfxinfo documentation`_ for details.
|
||||||
|
|
||||||
|
.. _gfxinfo documentation: https://developer.android.com/training/testing/performance.html
|
||||||
|
Loading…
x
Reference in New Issue
Block a user