1
0
mirror of https://github.com/ARM-software/workload-automation.git synced 2025-03-29 05:58:55 +00:00
Michael McGeagh 311c4e419f FPS. Added gfxinfo methods of obtaining fps stats. Auto detects and uses appropriate method via android version of device
Output 90th, 95th and 99th percentile metrics. This happens for both SurfaceFlinger and Gfxinfo methods.
2016-11-04 17:05:00 +00:00

173 lines
7.5 KiB
Python
Executable File

# Copyright 2016 ARM Limited
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import collections
try:
import pandas as pd
except ImportError:
pd = None
SurfaceFlingerFrame = collections.namedtuple('SurfaceFlingerFrame', 'desired_present_time actual_present_time frame_ready_time')
GfxInfoFrame = collections.namedtuple('GfxInfoFrame', 'Flags IntendedVsync Vsync OldestInputEvent NewestInputEvent HandleInputStart AnimationStart PerformTraversalsStart DrawStart SyncQueued SyncStart IssueDrawCommandsStart SwapBuffers FrameCompleted')
# https://android.googlesource.com/platform/frameworks/base/+/marshmallow-release/libs/hwui/JankTracker.cpp
# Frames that are exempt from jank metrics.
# First-draw frames, for example, are expected to be slow,
# this is hidden from the user with window animations and other tricks
# Similarly, we don't track direct-drawing via Surface:lockHardwareCanvas() for now
# Android M: WindowLayoutChanged | SurfaceCanvas
GFXINFO_EXEMPT = 1 | 4
class FpsProcessor(object):
"""
Provides common object for processing surfaceFlinger output for frame
statistics.
This processor returns the four frame statistics below:
:FPS: Frames Per Second. This is the frame rate of the workload.
:frame_count: The total number of frames rendered during the execution of
the workload.
:janks: The number of "janks" that occurred during execution of the
workload. Janks are sudden shifts in frame rate. They result
in a "stuttery" UI. See http://jankfree.org/jank-busters-io
:not_at_vsync: The number of frames that did not render in a single
vsync cycle.
"""
def __init__(self, data, action=None, extra_data=None):
"""
data - a pandas.DataFrame object with frame data (e.g. frames.csv)
action - output metrics names with additional action specifier
extra_data - extra data given to use for calculations of metrics
"""
self.data = data
self.action = action
self.extra_data = extra_data
def process(self, refresh_period, drop_threshold): # pylint: disable=too-many-locals
"""
Generate frame per second (fps) and associated metrics for workload.
refresh_period - the vsync interval
drop_threshold - data points below this fps will be dropped
"""
fps = float('nan')
frame_count, janks, not_at_vsync = 0, 0, 0
vsync_interval = refresh_period
per_frame_fps = pd.Series()
# SurfaceFlinger Algorithm
if self.data.columns.tolist() == list(SurfaceFlingerFrame._fields):
# fiter out bogus frames.
bogus_frames_filter = self.data.actual_present_time != 0x7fffffffffffffff
actual_present_times = self.data.actual_present_time[bogus_frames_filter]
actual_present_time_deltas = actual_present_times - actual_present_times.shift()
actual_present_time_deltas = actual_present_time_deltas.drop(0)
vsyncs_to_compose = actual_present_time_deltas / 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 * (vsync_interval / 1e9)))
keep_filter = per_frame_fps > drop_threshold
filtered_vsyncs_to_compose = vsyncs_to_compose[keep_filter]
per_frame_fps.name = 'fps'
if not filtered_vsyncs_to_compose.empty:
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)
# GfxInfo Algorithm
elif self.data.columns.tolist() == list(GfxInfoFrame._fields):
frame_time = self.data.FrameCompleted - self.data.IntendedVsync
per_frame_fps = (1e9 / frame_time)
keep_filter = per_frame_fps > drop_threshold
per_frame_fps = per_frame_fps[keep_filter]
per_frame_fps.name = 'fps'
frame_count = self.data.index.size
janks = frame_time[frame_time >= vsync_interval].count()
not_at_vsync = self.data.IntendedVsync - self.data.Vsync
not_at_vsync = not_at_vsync[not_at_vsync != 0].count()
duration = self.data.Vsync.iloc[-1] - self.data.Vsync.iloc[0]
fps = (1e9 * frame_count) / float(duration)
# If gfxinfocsv is provided, get stats from that instead
if self.extra_data:
series = pd.read_csv(self.extra_data, header=None, index_col=0, squeeze=True)
if not series.empty: # pylint: disable=maybe-no-member
frame_count = series['Total frames rendered']
janks = series['Janky frames']
not_at_vsync = series['Number Missed Vsync']
metrics = (fps, frame_count, janks, not_at_vsync)
return per_frame_fps, metrics
def percentiles(self):
# SurfaceFlinger Algorithm
if self.data.columns.tolist() == list(SurfaceFlingerFrame._fields):
frame_time = self.data.frame_ready_time.diff()
# GfxInfo Algorithm
elif self.data.columns.tolist() == list(GfxInfoFrame._fields):
frame_time = self.data.FrameCompleted - self.data.IntendedVsync
data = frame_time.quantile([0.90, 0.95, 0.99])
# Convert to ms, round to nearest, cast to int
data = data.div(1e6).round().astype('int')
# If gfxinfocsv is provided, get stats from that instead
if self.extra_data:
series = pd.read_csv(self.extra_data, header=None, index_col=0, squeeze=True)
if not series.empty: # pylint: disable=maybe-no-member
data = series[series.index.str.contains('th percentile')] # pylint: disable=maybe-no-member
return list(data.get_values())
@staticmethod
def _calc_janks(filtered_vsyncs_to_compose):
"""
Internal method for calculating jank frames.
"""
pause_latency = 20
vtc_deltas = filtered_vsyncs_to_compose - filtered_vsyncs_to_compose.shift()
vtc_deltas.index = range(0, vtc_deltas.size)
vtc_deltas = vtc_deltas.drop(0).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