mirror of
https://github.com/ARM-software/workload-automation.git
synced 2025-09-02 03:12:34 +01:00
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.
This commit is contained in:
112
wlauto/utils/fps.py
Normal file → Executable file
112
wlauto/utils/fps.py
Normal file → Executable file
@@ -12,6 +12,22 @@
|
||||
# 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):
|
||||
@@ -22,7 +38,7 @@ class FpsProcessor(object):
|
||||
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
|
||||
: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
|
||||
@@ -31,13 +47,15 @@ class FpsProcessor(object):
|
||||
vsync cycle.
|
||||
"""
|
||||
|
||||
def __init__(self, data, action=None):
|
||||
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
|
||||
"""
|
||||
@@ -49,39 +67,85 @@ class FpsProcessor(object):
|
||||
fps = float('nan')
|
||||
frame_count, janks, not_at_vsync = 0, 0, 0
|
||||
vsync_interval = refresh_period
|
||||
per_frame_fps = pd.Series()
|
||||
|
||||
# fiter out bogus frames.
|
||||
bogus_frames_filter = self.data.actual_present_time != 0x7fffffffffffffff
|
||||
actual_present_times = self.data.actual_present_time[bogus_frames_filter]
|
||||
# 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)
|
||||
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)))
|
||||
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'
|
||||
# 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 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)
|
||||
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)
|
||||
|
||||
# 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):
|
||||
"""
|
||||
|
Reference in New Issue
Block a user