From 9f666320f38db60fdadb096b656076208faa915a Mon Sep 17 00:00:00 2001 From: Sergei Trofimov Date: Wed, 13 Sep 2017 11:45:55 +0100 Subject: [PATCH] derived: add DerivedSurfaceFlingerStats Add DerivedSurfaceFlingerStats that parse output from SurfaceFlingerFramesInstrument to produce FPS data and rendering statistics. --- devlib/__init__.py | 4 +- devlib/derived/fps.py | 77 ++++++++++++++++++++++++++++++++++++ doc/derived_measurements.rst | 40 +++++++++++++++++++ 3 files changed, 119 insertions(+), 2 deletions(-) diff --git a/devlib/__init__.py b/devlib/__init__.py index efcf0bc..2b3f3b6 100644 --- a/devlib/__init__.py +++ b/devlib/__init__.py @@ -13,7 +13,7 @@ from devlib.instrument import Instrument, InstrumentChannel, Measurement, Measur from devlib.instrument import MEASUREMENT_TYPES, INSTANTANEOUS, CONTINUOUS from devlib.instrument.daq import DaqInstrument from devlib.instrument.energy_probe import EnergyProbeInstrument -from devlib.instrument.frames import GfxInfoFramesInstrument +from devlib.instrument.frames import GfxInfoFramesInstrument, SurfaceFlingerFramesInstrument from devlib.instrument.hwmon import HwmonInstrument from devlib.instrument.monsoon import MonsoonInstrument from devlib.instrument.netstats import NetstatsInstrument @@ -21,7 +21,7 @@ from devlib.instrument.gem5power import Gem5PowerInstrument from devlib.derived import DerivedMeasurements, DerivedMetric from devlib.derived.energy import DerivedEnergyMeasurements -from devlib.derived.fps import DerivedGfxInfoStats +from devlib.derived.fps import DerivedGfxInfoStats, DerivedSurfaceFlingerStats from devlib.trace.ftrace import FtraceCollector diff --git a/devlib/derived/fps.py b/devlib/derived/fps.py index 31a5399..e595926 100644 --- a/devlib/derived/fps.py +++ b/devlib/derived/fps.py @@ -9,6 +9,7 @@ except ImportError: pd = None from devlib import DerivedMeasurements, DerivedMetric, MeasurementsCsv, InstrumentChannel +from devlib.exception import HostError from devlib.utils.rendering import gfxinfo_get_last_dump, VSYNC_INTERVAL from devlib.utils.types import numeric @@ -135,3 +136,79 @@ class DerivedGfxInfoStats(DerivedFpsStats): DerivedMetric('total_frames', frame_count, 'frames'), MeasurementsCsv(csv_file)] + +class DerivedSurfaceFlingerStats(DerivedFpsStats): + + def _process_with_pandas(self, measurements_csv): + data = pd.read_csv(measurements_csv.path) + + # fiter out bogus frames. + bogus_frames_filter = data.actual_present_time_us != 0x7fffffffffffffff + actual_present_times = data.actual_present_time_us[bogus_frames_filter] + actual_present_time_deltas = actual_present_times.diff().dropna() + + vsyncs_to_compose = actual_present_time_deltas.div(VSYNC_INTERVAL) + vsyncs_to_compose.apply(lambda x: int(round(x, 0))) + + # drop values lower than drop_threshold FPS as real in-game frame + # rate is unlikely to drop below that (except on loading screens + # etc, which should not be factored in frame rate calculation). + per_frame_fps = (1.0 / (vsyncs_to_compose.multiply(VSYNC_INTERVAL / 1e9))) + keep_filter = per_frame_fps > self.drop_threshold + filtered_vsyncs_to_compose = vsyncs_to_compose[keep_filter] + per_frame_fps.name = 'fps' + + csv_file = self._get_csv_file_name(measurements_csv.path) + per_frame_fps.to_csv(csv_file, index=False, header=True) + + if not filtered_vsyncs_to_compose.empty: + fps = 0 + 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) + + janks = self._calc_janks(filtered_vsyncs_to_compose) + not_at_vsync = self._calc_not_at_vsync(vsyncs_to_compose) + else: + fps = 0 + frame_count = 0 + janks = 0 + not_at_vsync = 0 + + return [DerivedMetric('fps', fps, 'fps'), + DerivedMetric('total_frames', frame_count, 'frames'), + MeasurementsCsv(csv_file), + DerivedMetric('janks', janks, 'count'), + DerivedMetric('janks_pc', janks * 100 / frame_count, 'percent'), + DerivedMetric('missed_vsync', not_at_vsync, 'count')] + + def _process_without_pandas(self, measurements_csv): + # Given that SurfaceFlinger has been deprecated in favor of GfxInfo, + # it does not seem worth it implementing this. + raise HostError('Please install "pandas" Python package to process SurfaceFlinger frames') + + @staticmethod + def _calc_janks(filtered_vsyncs_to_compose): + """ + Internal method for calculating jank frames. + """ + pause_latency = 20 + vtc_deltas = filtered_vsyncs_to_compose.diff().dropna() + vtc_deltas = vtc_deltas.abs() + janks = vtc_deltas.apply(lambda x: (pause_latency > x > 1.5) and 1 or 0).sum() + + return janks + + @staticmethod + def _calc_not_at_vsync(vsyncs_to_compose): + """ + Internal method for calculating the number of frames that did not + render in a single vsync cycle. + """ + epsilon = 0.0001 + func = lambda x: (abs(x - 1.0) > epsilon) and 1 or 0 + not_at_vsync = vsyncs_to_compose.apply(func).sum() + + return not_at_vsync diff --git a/doc/derived_measurements.rst b/doc/derived_measurements.rst index 0a678bb..d56f94b 100644 --- a/doc/derived_measurements.rst +++ b/doc/derived_measurements.rst @@ -179,3 +179,43 @@ FPS / Rendering Please see the `gfxinfo documentation`_ for details. .. _gfxinfo documentation: https://developer.android.com/training/testing/performance.html + + +.. class:: DerivedSurfaceFlingerStats(drop_threshold=5, suffix='-fps', filename=None, outdir=None) + + Produces FPS (frames-per-second) and other dervied statistics from + :class:`SurfaceFlingerFramesInstrument` 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:: DerivedSurfaceFlingerStats.process(measurement_csv) + + Process the fames csv generated by :class:`SurfaceFlingerFramesInstrument` 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, followed by ``janks`` ``janks_pc``, and + ``missed_vsync`` metrics.