mirror of
https://github.com/ARM-software/workload-automation.git
synced 2025-01-19 12:24:32 +00:00
Move processing logic in fps instrument to utility file
Processing logic for frame statistics can be moved out of fps instrument to a new utility file. This will allow result processors to use the same logic to produce frame statistics on a subsection of the data produced by the fps instrument.
This commit is contained in:
parent
fee872585f
commit
b8d7956d4c
@ -37,6 +37,7 @@ from wlauto.instrumentation import instrument_is_installed
|
|||||||
from wlauto.exceptions import (InstrumentError, WorkerThreadError, ConfigError,
|
from wlauto.exceptions import (InstrumentError, WorkerThreadError, ConfigError,
|
||||||
DeviceNotRespondingError, TimeoutError)
|
DeviceNotRespondingError, TimeoutError)
|
||||||
from wlauto.utils.types import boolean, numeric
|
from wlauto.utils.types import boolean, numeric
|
||||||
|
from wlauto.utils.fps import FpsProcessor
|
||||||
|
|
||||||
|
|
||||||
VSYNC_INTERVAL = 16666667
|
VSYNC_INTERVAL = 16666667
|
||||||
@ -154,15 +155,18 @@ class FpsInstrument(Instrument):
|
|||||||
if self.is_enabled:
|
if self.is_enabled:
|
||||||
data = pd.read_csv(self.outfile)
|
data = pd.read_csv(self.outfile)
|
||||||
if not data.empty: # pylint: disable=maybe-no-member
|
if not data.empty: # pylint: disable=maybe-no-member
|
||||||
per_frame_fps = self._update_stats(context, data)
|
fp = FpsProcessor(data)
|
||||||
|
per_frame_fps, metrics = fp.process(self.collector.refresh_period, self.drop_threshold)
|
||||||
|
fps, frame_count, janks, not_at_vsync = metrics
|
||||||
|
|
||||||
|
context.result.add_metric('FPS', fps)
|
||||||
|
context.result.add_metric('frame_count', frame_count)
|
||||||
|
context.result.add_metric('janks', janks)
|
||||||
|
context.result.add_metric('not_at_vsync', not_at_vsync)
|
||||||
|
|
||||||
if self.generate_csv:
|
if self.generate_csv:
|
||||||
per_frame_fps.to_csv(self.fps_outfile, index=False, header=True)
|
per_frame_fps.to_csv(self.fps_outfile, index=False, header=True)
|
||||||
context.add_artifact('fps', path='fps.csv', kind='data')
|
context.add_artifact('fps', path='fps.csv', kind='data')
|
||||||
else:
|
|
||||||
context.result.add_metric('FPS', float('nan'))
|
|
||||||
context.result.add_metric('frame_count', 0)
|
|
||||||
context.result.add_metric('janks', 0)
|
|
||||||
context.result.add_metric('not_at_vsync', 0)
|
|
||||||
|
|
||||||
def slow_update_result(self, context):
|
def slow_update_result(self, context):
|
||||||
result = context.result
|
result = context.result
|
||||||
@ -180,44 +184,6 @@ class FpsInstrument(Instrument):
|
|||||||
result.status = IterationResult.FAILED
|
result.status = IterationResult.FAILED
|
||||||
result.add_event('Content crash detected (actual/expected frames: {:.2}).'.format(ratio))
|
result.add_event('Content crash detected (actual/expected frames: {:.2}).'.format(ratio))
|
||||||
|
|
||||||
def _update_stats(self, context, data): # pylint: disable=too-many-locals
|
|
||||||
vsync_interval = self.collector.refresh_period
|
|
||||||
# fiter out bogus frames.
|
|
||||||
actual_present_times = data.actual_present_time[data.actual_present_time != 0x7fffffffffffffff]
|
|
||||||
actual_present_time_deltas = (actual_present_times - actual_present_times.shift()).drop(0) # pylint: disable=E1103
|
|
||||||
vsyncs_to_compose = (actual_present_time_deltas / vsync_interval).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 > self.drop_threshold
|
|
||||||
filtered_vsyncs_to_compose = vsyncs_to_compose[keep_filter]
|
|
||||||
if not filtered_vsyncs_to_compose.empty:
|
|
||||||
total_vsyncs = filtered_vsyncs_to_compose.sum()
|
|
||||||
if total_vsyncs:
|
|
||||||
frame_count = filtered_vsyncs_to_compose.size
|
|
||||||
fps = 1e9 * frame_count / (vsync_interval * total_vsyncs)
|
|
||||||
context.result.add_metric('FPS', fps)
|
|
||||||
context.result.add_metric('frame_count', frame_count)
|
|
||||||
else:
|
|
||||||
context.result.add_metric('FPS', float('nan'))
|
|
||||||
context.result.add_metric('frame_count', 0)
|
|
||||||
|
|
||||||
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()
|
|
||||||
not_at_vsync = vsyncs_to_compose.apply(lambda x: (abs(x - 1.0) > EPSYLON) and 1 or 0).sum()
|
|
||||||
context.result.add_metric('janks', janks)
|
|
||||||
context.result.add_metric('not_at_vsync', not_at_vsync)
|
|
||||||
else: # no filtered_vsyncs_to_compose
|
|
||||||
context.result.add_metric('FPS', float('nan'))
|
|
||||||
context.result.add_metric('frame_count', 0)
|
|
||||||
context.result.add_metric('janks', 0)
|
|
||||||
context.result.add_metric('not_at_vsync', 0)
|
|
||||||
per_frame_fps.name = 'fps'
|
|
||||||
return per_frame_fps
|
|
||||||
|
|
||||||
|
|
||||||
class LatencyCollector(threading.Thread):
|
class LatencyCollector(threading.Thread):
|
||||||
|
|
||||||
|
107
wlauto/utils/fps.py
Normal file
107
wlauto/utils/fps.py
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
# 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.
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
class FpsProcessor(object):
|
||||||
|
"""
|
||||||
|
Provide common object for processing surfaceFlinger output for frame
|
||||||
|
statistics.
|
||||||
|
|
||||||
|
This processor adds four metrics to the results:
|
||||||
|
|
||||||
|
:FPS: Frames Per Second. This is the frame rate of the workload.
|
||||||
|
:frames: 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):
|
||||||
|
"""
|
||||||
|
data - a pandas.DataFrame object with frame data (e.g. frames.csv)
|
||||||
|
action - output metrics names with additional action specifier
|
||||||
|
"""
|
||||||
|
self.data = data
|
||||||
|
self.action = action
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
metrics = (fps, frame_count, janks, not_at_vsync)
|
||||||
|
|
||||||
|
return per_frame_fps, metrics
|
||||||
|
|
||||||
|
@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
|
Loading…
x
Reference in New Issue
Block a user