mirror of
https://github.com/ARM-software/workload-automation.git
synced 2025-09-03 11:52:36 +01:00
Merge pull request #277 from jimboatarm/fps-gfxinfo
FPS. Added gfxinfo methods of obtaining fps stats.
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