android: Add interactive surface statistics viewer

Add a simple tool for viewing graphics surface statistics interactively.
Sample output:

avg_surface_fps (fps)   frame_lengths (vsyncs)   jank_count (janks)   max_frame_delay (vsyncs)
---------------------   ----------------------   ------------------   ------------------------
            58.00                    1.158                    1                          4
            55.00                    1.215                    2                          7
            57.00                    1.174                    2                          3
            56.00                    1.192                    2                          6
            56.00                    1.196                    3                          3
            54.00                    1.239                    1                          8

BUG=https://b/8364918


Review URL: https://chromiumcodereview.appspot.com/13046007

git-svn-id: http://src.chromium.org/svn/trunk/src/build@190930 4ff67af0-8c30-449e-8e8b-ad334ec8d88c
This commit is contained in:
skyostil@chromium.org 2013-03-27 14:05:50 +00:00
Родитель 467f42371f
Коммит 194e4ec1d9
2 изменённых файлов: 152 добавлений и 2 удалений

Просмотреть файл

@ -37,6 +37,10 @@ class SurfaceStatsCollector(object):
self._data_queue = None
self._stop_event = None
self._results = []
self._warn_about_empty_data = True
def DisableWarningAboutEmptyData(self):
self._warn_about_empty_data = False
def Start(self):
assert not self._collector_thread
@ -58,6 +62,12 @@ class SurfaceStatsCollector(object):
self._collector_thread.join()
self._collector_thread = None
def SampleResults(self):
self._StorePerfResults()
results = self._results
self._results = []
return results
def GetResults(self):
return self._results
@ -77,7 +87,8 @@ class SurfaceStatsCollector(object):
assert self._collector_thread
(refresh_period, timestamps) = self._GetDataFromThread()
if not refresh_period or not len(timestamps) >= 3:
logging.warning('Surface stat data is empty')
if self._warn_about_empty_data:
logging.warning('Surface stat data is empty')
return
frame_count = len(timestamps)
seconds = timestamps[-1] - timestamps[0]
@ -208,11 +219,19 @@ class SurfaceStatsCollector(object):
nanoseconds_per_second = 1e9
refresh_period = long(results[0]) / nanoseconds_per_second
# SurfaceFlinger sometimes gives an invalid timestamp for the very latest
# frame if it is queried while the frame is still being presented. We ignore
# these timestamps.
bad_timestamp = (1 << 63) - 1
for line in results[1:]:
fields = line.split()
if len(fields) != 3:
continue
timestamp = long(fields[1]) / nanoseconds_per_second
timestamp = long(fields[1])
if timestamp == bad_timestamp:
continue
timestamp /= nanoseconds_per_second
timestamps.append(timestamp)
return (refresh_period, timestamps)

131
android/surface_stats.py Executable file
Просмотреть файл

@ -0,0 +1,131 @@
#!/usr/bin/env python
#
# Copyright (c) 2013 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Command line tool for continuously printing Android graphics surface
statistics on the console.
"""
import collections
import optparse
import sys
import time
from pylib import android_commands, surface_stats_collector
from pylib.utils import run_tests_helper
_FIELD_FORMAT = {
'jank_count (janks)': '%d',
'max_frame_delay (vsyncs)': '%d',
'avg_surface_fps (fps)': '%.2f',
'frame_lengths (vsyncs)': '%.3f',
'refresh_period (seconds)': '%.6f',
}
def _MergeResults(results, fields):
merged_results = collections.defaultdict(list)
for result in results:
if fields != ['all'] and not result.name in fields:
continue
name = '%s (%s)' % (result.name, result.unit)
if isinstance(result.value, list):
value = result.value
else:
value = [result.value]
merged_results[name] += value
for name, values in merged_results.iteritems():
merged_results[name] = sum(values) / float(len(values))
return merged_results
def _GetTerminalHeight():
try:
import fcntl, termios, struct
except ImportError:
return 0, 0
height, _, _, _ = struct.unpack('HHHH',
fcntl.ioctl(0, termios.TIOCGWINSZ,
struct.pack('HHHH', 0, 0, 0, 0)))
return height
def _PrintColumnTitles(results):
for name in results.keys():
print '%s ' % name,
print
for name in results.keys():
print '%s ' % ('-' * len(name)),
print
def _PrintResults(results):
for name, value in results.iteritems():
value = _FIELD_FORMAT.get(name, '%s') % value
print value.rjust(len(name)) + ' ',
print
def main(argv):
parser = optparse.OptionParser(usage='Usage: %prog [options]',
description=__doc__)
parser.add_option('-v',
'--verbose',
dest='verbose_count',
default=0,
action='count',
help='Verbose level (multiple times for more)')
parser.add_option('--device',
help='Serial number of device we should use.')
parser.add_option('-f',
'--fields',
dest='fields',
default='jank_count,max_frame_delay,avg_surface_fps,'
'frame_lengths',
help='Comma separated list of fields to display or "all".')
parser.add_option('-d',
'--delay',
dest='delay',
default=1,
type='float',
help='Time in seconds to sleep between updates.')
options, args = parser.parse_args(argv)
run_tests_helper.SetLogLevel(options.verbose_count)
adb = android_commands.AndroidCommands(options.device)
collector = surface_stats_collector.SurfaceStatsCollector(adb)
collector.DisableWarningAboutEmptyData()
fields = options.fields.split(',')
row_count = None
try:
collector.Start()
while True:
time.sleep(options.delay)
results = collector.SampleResults()
results = _MergeResults(results, fields)
if not results:
continue
terminal_height = _GetTerminalHeight()
if row_count is None or (terminal_height and
row_count >= terminal_height - 3):
_PrintColumnTitles(results)
row_count = 0
_PrintResults(results)
row_count += 1
except KeyboardInterrupt:
sys.exit(0)
finally:
collector.Stop()
if __name__ == '__main__':
main(sys.argv)