Android: splits cache_control and perf_control.

Second step towards deprecating "perf_tests_helper.py"
towards more meaningful modules.
Also move thermal_throttle.py into perf/ and some
other minor cleanup.

BUG=

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

git-svn-id: http://src.chromium.org/svn/trunk/src/build@223606 4ff67af0-8c30-449e-8e8b-ad334ec8d88c
This commit is contained in:
bulach@chromium.org 2013-09-17 13:52:42 +00:00
Родитель a1790df76f
Коммит 68fbba6e0a
12 изменённых файлов: 472 добавлений и 419 удалений

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

@ -937,13 +937,18 @@ class AndroidCommands(object):
i += 1
return self.GetExternalStorage() + '/' + base_name % i
def RunShellCommandWithSU(self, command, timeout_time=20, log_result=False):
return self.RunShellCommand('su -c %s' % command,
timeout_time=timeout_time,
log_result=log_result)
def CanAccessProtectedFileContents(self):
"""Returns True if Get/SetProtectedFileContents would work via "su".
Devices running user builds don't have adb root, but may provide "su" which
can be used for accessing protected files.
"""
r = self.RunShellCommand('su -c cat /dev/null')
r = self.RunShellCommandWithSU('cat /dev/null')
return r == [] or r[0].strip() == ''
def GetProtectedFileContents(self, filename, log_result=False):
@ -953,7 +958,7 @@ class AndroidCommands(object):
files and device files.
"""
# Run the script as root
return self.RunShellCommand('su -c cat "%s" 2> /dev/null' % filename)
return self.RunShellCommandWithSU('cat "%s" 2> /dev/null' % filename)
def SetProtectedFileContents(self, filename, contents):
"""Writes |contents| to the protected file specified by |filename|.
@ -970,7 +975,7 @@ class AndroidCommands(object):
# Create a script to copy the file contents to its final destination
self.SetFileContents(temp_script, 'cat %s > %s' % (temp_file, filename))
# Run the script as root
self.RunShellCommand('su -c sh %s' % temp_script)
self.RunShellCommandWithSU('sh %s' % temp_script)
# And remove the temporary files
self.RunShellCommand('rm ' + temp_file)
self.RunShellCommand('rm ' + temp_script)

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

@ -1,4 +1,4 @@
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Copyright 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.

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

@ -12,11 +12,11 @@ import time
from pylib import android_commands
from pylib import constants
from pylib import flag_changer
from pylib import json_perf_parser
from pylib import perf_tests_helper
from pylib import valgrind_tools
from pylib.base import base_test_result
from pylib.base import base_test_runner
from pylib.instrumentation import json_perf_parser
import test_result

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

@ -0,0 +1,16 @@
# Copyright 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.
class CacheControl(object):
_DROP_CACHES = '/proc/sys/vm/drop_caches'
def __init__(self, adb):
self._adb = adb
def DropRamCaches(self):
"""Drops the filesystem ram caches for performance testing."""
self._adb.RunShellCommandWithSU('sync')
self._adb.SetProtectedFileContents(CacheControl._DROP_CACHES, '3')

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

@ -0,0 +1,52 @@
# Copyright 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.
import logging
class PerfControl(object):
"""Provides methods for setting the performance mode of a device."""
_SCALING_GOVERNOR_FMT = (
'/sys/devices/system/cpu/cpu%d/cpufreq/scaling_governor')
_KERNEL_MAX = '/sys/devices/system/cpu/kernel_max'
def __init__(self, adb):
self._adb = adb
kernel_max = self._adb.GetFileContents(PerfControl._KERNEL_MAX,
log_result=False)
assert kernel_max, 'Unable to find %s' % PerfControl._KERNEL_MAX
self._kernel_max = int(kernel_max[0])
logging.info('Maximum CPU index: %d', self._kernel_max)
self._original_scaling_governor = self._adb.GetFileContents(
PerfControl._SCALING_GOVERNOR_FMT % 0,
log_result=False)[0]
def SetHighPerfMode(self):
"""Sets the highest possible performance mode for the device."""
self._SetScalingGovernorInternal('performance')
def SetDefaultPerfMode(self):
"""Sets the performance mode for the device to its default mode."""
product_model = self._adb.GetProductModel()
governor_mode = {
'GT-I9300': 'pegasusq',
'Galaxy Nexus': 'interactive',
'Nexus 4': 'ondemand',
'Nexus 7': 'interactive',
'Nexus 10': 'interactive'
}.get(product_model, 'ondemand')
self._SetScalingGovernorInternal(governor_mode)
def RestoreOriginalPerfMode(self):
"""Resets the original performance mode of the device."""
self._SetScalingGovernorInternal(self._original_scaling_governor)
def _SetScalingGovernorInternal(self, value):
for cpu in range(self._kernel_max + 1):
scaling_governor_file = PerfControl._SCALING_GOVERNOR_FMT % cpu
if self._adb.FileExistsOnDevice(scaling_governor_file):
logging.info('Writing scaling governor mode \'%s\' -> %s',
value, scaling_governor_file)
self._adb.SetProtectedFileContents(scaling_governor_file, value)

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

@ -0,0 +1,296 @@
# Copyright 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.
import Queue
import datetime
import logging
import re
import threading
# Log marker containing SurfaceTexture timestamps.
_SURFACE_TEXTURE_TIMESTAMPS_MESSAGE = 'SurfaceTexture update timestamps'
_SURFACE_TEXTURE_TIMESTAMP_RE = '\d+'
class SurfaceStatsCollector(object):
"""Collects surface stats for a SurfaceView from the output of SurfaceFlinger.
Args:
adb: the adb connection to use.
"""
class Result(object):
def __init__(self, name, value, unit):
self.name = name
self.value = value
self.unit = unit
def __init__(self, adb):
self._adb = adb
self._collector_thread = None
self._use_legacy_method = False
self._surface_before = None
self._get_data_event = None
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
if self._ClearSurfaceFlingerLatencyData():
self._get_data_event = threading.Event()
self._stop_event = threading.Event()
self._data_queue = Queue.Queue()
self._collector_thread = threading.Thread(target=self._CollectorThread)
self._collector_thread.start()
else:
self._use_legacy_method = True
self._surface_before = self._GetSurfaceStatsLegacy()
def Stop(self):
self._StorePerfResults()
if self._collector_thread:
self._stop_event.set()
self._collector_thread.join()
self._collector_thread = None
def SampleResults(self):
self._StorePerfResults()
results = self.GetResults()
self._results = []
return results
def GetResults(self):
return self._results or self._GetEmptyResults()
def _GetEmptyResults(self):
return [
SurfaceStatsCollector.Result('refresh_period', None, 'seconds'),
SurfaceStatsCollector.Result('jank_count', None, 'janks'),
SurfaceStatsCollector.Result('max_frame_delay', None, 'vsyncs'),
SurfaceStatsCollector.Result('frame_lengths', None, 'vsyncs'),
SurfaceStatsCollector.Result('avg_surface_fps', None, 'fps')
]
@staticmethod
def _GetNormalizedDeltas(data, refresh_period):
deltas = [t2 - t1 for t1, t2 in zip(data, data[1:])]
return (deltas, [delta / refresh_period for delta in deltas])
@staticmethod
def _CalculateResults(refresh_period, timestamps, result_suffix):
"""Returns a list of SurfaceStatsCollector.Result."""
frame_count = len(timestamps)
seconds = timestamps[-1] - timestamps[0]
frame_lengths, normalized_frame_lengths = \
SurfaceStatsCollector._GetNormalizedDeltas(timestamps, refresh_period)
length_changes, normalized_changes = \
SurfaceStatsCollector._GetNormalizedDeltas(
frame_lengths, refresh_period)
jankiness = [max(0, round(change)) for change in normalized_changes]
pause_threshold = 20
jank_count = sum(1 for change in jankiness
if change > 0 and change < pause_threshold)
return [
SurfaceStatsCollector.Result(
'avg_surface_fps' + result_suffix,
int(round(frame_count / seconds)), 'fps'),
SurfaceStatsCollector.Result(
'jank_count' + result_suffix, jank_count, 'janks'),
SurfaceStatsCollector.Result(
'max_frame_delay' + result_suffix,
round(max(normalized_frame_lengths)),
'vsyncs'),
SurfaceStatsCollector.Result(
'frame_lengths' + result_suffix, normalized_frame_lengths,
'vsyncs'),
]
@staticmethod
def _CalculateBuckets(refresh_period, timestamps):
results = []
for pct in [0.99, 0.5]:
sliced = timestamps[min(int(-pct * len(timestamps)), -3) : ]
results += SurfaceStatsCollector._CalculateResults(
refresh_period, sliced, '_' + str(int(pct * 100)))
return results
def _StorePerfResults(self):
if self._use_legacy_method:
surface_after = self._GetSurfaceStatsLegacy()
td = surface_after['timestamp'] - self._surface_before['timestamp']
seconds = td.seconds + td.microseconds / 1e6
frame_count = (surface_after['page_flip_count'] -
self._surface_before['page_flip_count'])
self._results.append(SurfaceStatsCollector.Result(
'avg_surface_fps', int(round(frame_count / seconds)), 'fps'))
return
# Non-legacy method.
assert self._collector_thread
(refresh_period, timestamps) = self._GetDataFromThread()
if not refresh_period or not len(timestamps) >= 3:
if self._warn_about_empty_data:
logging.warning('Surface stat data is empty')
return
self._results.append(SurfaceStatsCollector.Result(
'refresh_period', refresh_period, 'seconds'))
self._results += self._CalculateResults(refresh_period, timestamps, '')
self._results += self._CalculateBuckets(refresh_period, timestamps)
def _CollectorThread(self):
last_timestamp = 0
timestamps = []
retries = 0
while not self._stop_event.is_set():
self._get_data_event.wait(1)
try:
refresh_period, new_timestamps = self._GetSurfaceFlingerFrameData()
if refresh_period is None or timestamps is None:
retries += 1
if retries < 3:
continue
if last_timestamp:
# Some data has already been collected, but either the app
# was closed or there's no new data. Signal the main thread and
# wait.
self._data_queue.put((None, None))
self._stop_event.wait()
break
raise Exception('Unable to get surface flinger latency data')
timestamps += [timestamp for timestamp in new_timestamps
if timestamp > last_timestamp]
if len(timestamps):
last_timestamp = timestamps[-1]
if self._get_data_event.is_set():
self._get_data_event.clear()
self._data_queue.put((refresh_period, timestamps))
timestamps = []
except Exception as e:
# On any error, before aborting, put the exception into _data_queue to
# prevent the main thread from waiting at _data_queue.get() infinitely.
self._data_queue.put(e)
raise
def _GetDataFromThread(self):
self._get_data_event.set()
ret = self._data_queue.get()
if isinstance(ret, Exception):
raise ret
return ret
def _ClearSurfaceFlingerLatencyData(self):
"""Clears the SurfaceFlinger latency data.
Returns:
True if SurfaceFlinger latency is supported by the device, otherwise
False.
"""
# The command returns nothing if it is supported, otherwise returns many
# lines of result just like 'dumpsys SurfaceFlinger'.
results = self._adb.RunShellCommand(
'dumpsys SurfaceFlinger --latency-clear SurfaceView')
return not len(results)
def _GetSurfaceFlingerFrameData(self):
"""Returns collected SurfaceFlinger frame timing data.
Returns:
A tuple containing:
- The display's nominal refresh period in seconds.
- A list of timestamps signifying frame presentation times in seconds.
The return value may be (None, None) if there was no data collected (for
example, if the app was closed before the collector thread has finished).
"""
# adb shell dumpsys SurfaceFlinger --latency <window name>
# prints some information about the last 128 frames displayed in
# that window.
# The data returned looks like this:
# 16954612
# 7657467895508 7657482691352 7657493499756
# 7657484466553 7657499645964 7657511077881
# 7657500793457 7657516600576 7657527404785
# (...)
#
# The first line is the refresh period (here 16.95 ms), it is followed
# by 128 lines w/ 3 timestamps in nanosecond each:
# A) when the app started to draw
# B) the vsync immediately preceding SF submitting the frame to the h/w
# C) timestamp immediately after SF submitted that frame to the h/w
#
# The difference between the 1st and 3rd timestamp is the frame-latency.
# An interesting data is when the frame latency crosses a refresh period
# boundary, this can be calculated this way:
#
# ceil((C - A) / refresh-period)
#
# (each time the number above changes, we have a "jank").
# If this happens a lot during an animation, the animation appears
# janky, even if it runs at 60 fps in average.
#
# We use the special "SurfaceView" window name because the statistics for
# the activity's main window are not updated when the main web content is
# composited into a SurfaceView.
results = self._adb.RunShellCommand(
'dumpsys SurfaceFlinger --latency SurfaceView',
log_result=logging.getLogger().isEnabledFor(logging.DEBUG))
if not len(results):
return (None, None)
timestamps = []
nanoseconds_per_second = 1e9
refresh_period = long(results[0]) / nanoseconds_per_second
# If a fence associated with a frame is still pending when we query the
# latency data, SurfaceFlinger gives the frame a timestamp of INT64_MAX.
# Since we only care about completed frames, we will ignore any timestamps
# with this value.
pending_fence_timestamp = (1 << 63) - 1
for line in results[1:]:
fields = line.split()
if len(fields) != 3:
continue
timestamp = long(fields[1])
if timestamp == pending_fence_timestamp:
continue
timestamp /= nanoseconds_per_second
timestamps.append(timestamp)
return (refresh_period, timestamps)
def _GetSurfaceStatsLegacy(self):
"""Legacy method (before JellyBean), returns the current Surface index
and timestamp.
Calculate FPS by measuring the difference of Surface index returned by
SurfaceFlinger in a period of time.
Returns:
Dict of {page_flip_count (or 0 if there was an error), timestamp}.
"""
results = self._adb.RunShellCommand('service call SurfaceFlinger 1013')
assert len(results) == 1
match = re.search('^Result: Parcel\((\w+)', results[0])
cur_surface = 0
if match:
try:
cur_surface = int(match.group(1), 16)
except Exception:
logging.error('Failed to parse current surface from ' + match.group(1))
else:
logging.warning('Failed to call SurfaceFlinger surface ' + results[0])
return {
'page_flip_count': cur_surface,
'timestamp': datetime.datetime.now(),
}

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

@ -0,0 +1,74 @@
# Copyright 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.
import logging
class ThermalThrottle(object):
"""Class to detect and track thermal throttling.
Usage:
Wait for IsThrottled() to be False before running test
After running test call HasBeenThrottled() to find out if the
test run was affected by thermal throttling.
Currently assumes an OMap device.
"""
def __init__(self, adb):
self._adb = adb
self._throttled = False
def HasBeenThrottled(self):
"""True if there has been any throttling since the last call to
HasBeenThrottled or IsThrottled.
"""
return self._ReadLog()
def IsThrottled(self):
"""True if currently throttled."""
self._ReadLog()
return self._throttled
def _ReadLog(self):
has_been_throttled = False
serial_number = self._adb.Adb().GetSerialNumber()
log = self._adb.RunShellCommand('dmesg -c')
degree_symbol = unichr(0x00B0)
for line in log:
if 'omap_thermal_throttle' in line:
if not self._throttled:
logging.warning('>>> Device %s Thermally Throttled', serial_number)
self._throttled = True
has_been_throttled = True
if 'omap_thermal_unthrottle' in line:
if self._throttled:
logging.warning('>>> Device %s Thermally Unthrottled', serial_number)
self._throttled = False
has_been_throttled = True
if 'throttle_delayed_work_fn' in line:
temp = float([s for s in line.split() if s.isdigit()][0]) / 1000.0
logging.info(u' Device %s Thermally Thottled at %3.1f%sC',
serial_number, temp, degree_symbol)
if logging.getLogger().isEnabledFor(logging.DEBUG):
# Print temperature of CPU SoC.
omap_temp_file = ('/sys/devices/platform/omap/omap_temp_sensor.0/'
'temperature')
if self._adb.FileExistsOnDevice(omap_temp_file):
tempdata = self._adb.GetFileContents(omap_temp_file)
temp = float(tempdata[0]) / 1000.0
logging.debug(u'Current OMAP Temperature of %s = %3.1f%sC',
serial_number, temp, degree_symbol)
# Print temperature of battery, to give a system temperature
dumpsys_log = self._adb.RunShellCommand('dumpsys battery')
for line in dumpsys_log:
if 'temperature' in line:
btemp = float([s for s in line.split() if s.isdigit()][0]) / 10.0
logging.debug(u'Current battery temperature of %s = %3.1f%sC',
serial_number, btemp, degree_symbol)
return has_been_throttled

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

@ -2,13 +2,17 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import json
import logging
import math
import re
import sys
import android_commands
import json
import logging
import math
from perf import cache_control
from perf import perf_control
# Valid values of result type.
RESULT_TYPES = {'unimportant': 'RESULT ',
@ -146,58 +150,11 @@ def PrintPerfResult(measurement, trace, values, units, result_type='default',
return output
class CacheControl(object):
_DROP_CACHES = '/proc/sys/vm/drop_caches'
# TODO(bulach): remove once all references to PerfControl are fixed.
class CacheControl(cache_control.CacheControl):
def __init__(self, adb):
self._adb = adb
def DropRamCaches(self):
"""Drops the filesystem ram caches for performance testing."""
self._adb.RunShellCommand('su -c sync')
self._adb.SetProtectedFileContents(CacheControl._DROP_CACHES, '3')
class PerfControl(object):
"""Provides methods for setting the performance mode of a device."""
_SCALING_GOVERNOR_FMT = (
'/sys/devices/system/cpu/cpu%d/cpufreq/scaling_governor')
super(CacheControl, self).__init__(adb)
class PerfControl(perf_control.PerfControl):
def __init__(self, adb):
self._adb = adb
kernel_max = self._adb.GetFileContents('/sys/devices/system/cpu/kernel_max',
log_result=False)
assert kernel_max, 'Unable to find /sys/devices/system/cpu/kernel_max'
self._kernel_max = int(kernel_max[0])
logging.info('Maximum CPU index: %d' % self._kernel_max)
self._original_scaling_governor = self._adb.GetFileContents(
PerfControl._SCALING_GOVERNOR_FMT % 0,
log_result=False)[0]
def SetHighPerfMode(self):
"""Sets the highest possible performance mode for the device."""
self._SetScalingGovernorInternal('performance')
def SetDefaultPerfMode(self):
"""Sets the performance mode for the device to its default mode."""
product_model = self._adb.GetProductModel()
governor_mode = {
"GT-I9300" : 'pegasusq',
"Galaxy Nexus" : 'interactive',
"Nexus 4" : 'ondemand',
"Nexus 7" : 'interactive',
"Nexus 10": 'interactive'
}.get(product_model, 'ondemand')
self._SetScalingGovernorInternal(governor_mode)
def RestoreOriginalPerfMode(self):
"""Resets the original performance mode of the device."""
self._SetScalingGovernorInternal(self._original_scaling_governor)
def _SetScalingGovernorInternal(self, value):
for cpu in range(self._kernel_max + 1):
scaling_governor_file = PerfControl._SCALING_GOVERNOR_FMT % cpu
if self._adb.FileExistsOnDevice(scaling_governor_file):
logging.info('Writing scaling governor mode \'%s\' -> %s' %
(value, scaling_governor_file))
self._adb.SetProtectedFileContents(scaling_governor_file, value)
super(PerfControl, self).__init__(adb)

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

@ -2,297 +2,9 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import Queue
import datetime
import logging
import re
import threading
from pylib import perf_tests_helper
# Log marker containing SurfaceTexture timestamps.
_SURFACE_TEXTURE_TIMESTAMPS_MESSAGE = 'SurfaceTexture update timestamps'
_SURFACE_TEXTURE_TIMESTAMP_RE = '\d+'
class SurfaceStatsCollector(object):
"""Collects surface stats for a SurfaceView from the output of SurfaceFlinger.
Args:
adb: the adb connection to use.
"""
class Result(object):
def __init__(self, name, value, unit):
self.name = name
self.value = value
self.unit = unit
from pylib.perf import surface_stats_collector
# TODO(bulach): remove once all references to SurfaceStatsCollector are fixed.
class SurfaceStatsCollector(surface_stats_collector.SurfaceStatsCollector):
def __init__(self, adb):
self._adb = adb
self._collector_thread = None
self._use_legacy_method = False
self._surface_before = None
self._get_data_event = None
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
if self._ClearSurfaceFlingerLatencyData():
self._get_data_event = threading.Event()
self._stop_event = threading.Event()
self._data_queue = Queue.Queue()
self._collector_thread = threading.Thread(target=self._CollectorThread)
self._collector_thread.start()
else:
self._use_legacy_method = True
self._surface_before = self._GetSurfaceStatsLegacy()
def Stop(self):
self._StorePerfResults()
if self._collector_thread:
self._stop_event.set()
self._collector_thread.join()
self._collector_thread = None
def SampleResults(self):
self._StorePerfResults()
results = self.GetResults()
self._results = []
return results
def GetResults(self):
return self._results or self._GetEmptyResults()
def _GetEmptyResults(self):
return [
SurfaceStatsCollector.Result('refresh_period', None, 'seconds'),
SurfaceStatsCollector.Result('jank_count', None, 'janks'),
SurfaceStatsCollector.Result('max_frame_delay', None, 'vsyncs'),
SurfaceStatsCollector.Result('frame_lengths', None, 'vsyncs'),
SurfaceStatsCollector.Result('avg_surface_fps', None, 'fps')
]
@staticmethod
def _GetNormalizedDeltas(data, refresh_period):
deltas = [t2 - t1 for t1, t2 in zip(data, data[1:])]
return (deltas, [delta / refresh_period for delta in deltas])
@staticmethod
def _CalculateResults(refresh_period, timestamps, result_suffix):
"""Returns a list of SurfaceStatsCollector.Result."""
frame_count = len(timestamps)
seconds = timestamps[-1] - timestamps[0]
frame_lengths, normalized_frame_lengths = \
SurfaceStatsCollector._GetNormalizedDeltas(timestamps, refresh_period)
length_changes, normalized_changes = \
SurfaceStatsCollector._GetNormalizedDeltas(
frame_lengths, refresh_period)
jankiness = [max(0, round(change)) for change in normalized_changes]
pause_threshold = 20
jank_count = sum(1 for change in jankiness
if change > 0 and change < pause_threshold)
return [
SurfaceStatsCollector.Result(
'avg_surface_fps' + result_suffix,
int(round(frame_count / seconds)), 'fps'),
SurfaceStatsCollector.Result(
'jank_count' + result_suffix, jank_count, 'janks'),
SurfaceStatsCollector.Result(
'max_frame_delay' + result_suffix,
round(max(normalized_frame_lengths)),
'vsyncs'),
SurfaceStatsCollector.Result(
'frame_lengths' + result_suffix, normalized_frame_lengths,
'vsyncs'),
]
@staticmethod
def _CalculateBuckets(refresh_period, timestamps):
results = []
for pct in [0.99, 0.5]:
sliced = timestamps[min(int(-pct * len(timestamps)), -3) : ]
results += SurfaceStatsCollector._CalculateResults(
refresh_period, sliced, '_' + str(int(pct * 100)))
return results
def _StorePerfResults(self):
if self._use_legacy_method:
surface_after = self._GetSurfaceStatsLegacy()
td = surface_after['timestamp'] - self._surface_before['timestamp']
seconds = td.seconds + td.microseconds / 1e6
frame_count = (surface_after['page_flip_count'] -
self._surface_before['page_flip_count'])
self._results.append(SurfaceStatsCollector.Result(
'avg_surface_fps', int(round(frame_count / seconds)), 'fps'))
return
# Non-legacy method.
assert self._collector_thread
(refresh_period, timestamps) = self._GetDataFromThread()
if not refresh_period or not len(timestamps) >= 3:
if self._warn_about_empty_data:
logging.warning('Surface stat data is empty')
return
self._results.append(SurfaceStatsCollector.Result(
'refresh_period', refresh_period, 'seconds'))
self._results += self._CalculateResults(refresh_period, timestamps, '')
self._results += self._CalculateBuckets(refresh_period, timestamps)
def _CollectorThread(self):
last_timestamp = 0
timestamps = []
retries = 0
while not self._stop_event.is_set():
self._get_data_event.wait(1)
try:
refresh_period, new_timestamps = self._GetSurfaceFlingerFrameData()
if refresh_period is None or timestamps is None:
retries += 1
if retries < 3:
continue
if last_timestamp:
# Some data has already been collected, but either the app
# was closed or there's no new data. Signal the main thread and
# wait.
self._data_queue.put((None, None))
self._stop_event.wait()
break
raise Exception('Unable to get surface flinger latency data')
timestamps += [timestamp for timestamp in new_timestamps
if timestamp > last_timestamp]
if len(timestamps):
last_timestamp = timestamps[-1]
if self._get_data_event.is_set():
self._get_data_event.clear()
self._data_queue.put((refresh_period, timestamps))
timestamps = []
except Exception as e:
# On any error, before aborting, put the exception into _data_queue to
# prevent the main thread from waiting at _data_queue.get() infinitely.
self._data_queue.put(e)
raise
def _GetDataFromThread(self):
self._get_data_event.set()
ret = self._data_queue.get()
if isinstance(ret, Exception):
raise ret
return ret
def _ClearSurfaceFlingerLatencyData(self):
"""Clears the SurfaceFlinger latency data.
Returns:
True if SurfaceFlinger latency is supported by the device, otherwise
False.
"""
# The command returns nothing if it is supported, otherwise returns many
# lines of result just like 'dumpsys SurfaceFlinger'.
results = self._adb.RunShellCommand(
'dumpsys SurfaceFlinger --latency-clear SurfaceView')
return not len(results)
def _GetSurfaceFlingerFrameData(self):
"""Returns collected SurfaceFlinger frame timing data.
Returns:
A tuple containing:
- The display's nominal refresh period in seconds.
- A list of timestamps signifying frame presentation times in seconds.
The return value may be (None, None) if there was no data collected (for
example, if the app was closed before the collector thread has finished).
"""
# adb shell dumpsys SurfaceFlinger --latency <window name>
# prints some information about the last 128 frames displayed in
# that window.
# The data returned looks like this:
# 16954612
# 7657467895508 7657482691352 7657493499756
# 7657484466553 7657499645964 7657511077881
# 7657500793457 7657516600576 7657527404785
# (...)
#
# The first line is the refresh period (here 16.95 ms), it is followed
# by 128 lines w/ 3 timestamps in nanosecond each:
# A) when the app started to draw
# B) the vsync immediately preceding SF submitting the frame to the h/w
# C) timestamp immediately after SF submitted that frame to the h/w
#
# The difference between the 1st and 3rd timestamp is the frame-latency.
# An interesting data is when the frame latency crosses a refresh period
# boundary, this can be calculated this way:
#
# ceil((C - A) / refresh-period)
#
# (each time the number above changes, we have a "jank").
# If this happens a lot during an animation, the animation appears
# janky, even if it runs at 60 fps in average.
#
# We use the special "SurfaceView" window name because the statistics for
# the activity's main window are not updated when the main web content is
# composited into a SurfaceView.
results = self._adb.RunShellCommand(
'dumpsys SurfaceFlinger --latency SurfaceView',
log_result=logging.getLogger().isEnabledFor(logging.DEBUG))
if not len(results):
return (None, None)
timestamps = []
nanoseconds_per_second = 1e9
refresh_period = long(results[0]) / nanoseconds_per_second
# If a fence associated with a frame is still pending when we query the
# latency data, SurfaceFlinger gives the frame a timestamp of INT64_MAX.
# Since we only care about completed frames, we will ignore any timestamps
# with this value.
pending_fence_timestamp = (1 << 63) - 1
for line in results[1:]:
fields = line.split()
if len(fields) != 3:
continue
timestamp = long(fields[1])
if timestamp == pending_fence_timestamp:
continue
timestamp /= nanoseconds_per_second
timestamps.append(timestamp)
return (refresh_period, timestamps)
def _GetSurfaceStatsLegacy(self):
"""Legacy method (before JellyBean), returns the current Surface index
and timestamp.
Calculate FPS by measuring the difference of Surface index returned by
SurfaceFlinger in a period of time.
Returns:
Dict of {page_flip_count (or 0 if there was an error), timestamp}.
"""
results = self._adb.RunShellCommand('service call SurfaceFlinger 1013')
assert len(results) == 1
match = re.search('^Result: Parcel\((\w+)', results[0])
cur_surface = 0
if match:
try:
cur_surface = int(match.group(1), 16)
except Exception:
logging.error('Failed to parse current surface from ' + match.group(1))
else:
logging.warning('Failed to call SurfaceFlinger surface ' + results[0])
return {
'page_flip_count': cur_surface,
'timestamp': datetime.datetime.now(),
}
super(SurfaceStatsCollector, self).__init__(adb)

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

@ -4,69 +4,9 @@
import logging
class ThermalThrottle(object):
"""Class to detect and track thermal throttling
from perf import thermal_throttle
Usage:
Wait for IsThrottled() to be False before running test
After running test call HasBeenThrottled() to find out if the
test run was affected by thermal throttling.
Currently assumes an OMap device.
"""
# TODO(bulach): remove once all references to ThermalThrottle are fixed.
class ThermalThrottle(thermal_throttle.ThermalThrottle):
def __init__(self, adb):
self._adb = adb
self._throttled = False
def HasBeenThrottled(self):
""" True if there has been any throttling since the last call to
HasBeenThrottled or IsThrottled
"""
return self._ReadLog()
def IsThrottled(self):
"""True if currently throttled"""
self._ReadLog()
return self._throttled
def _ReadLog(self):
has_been_throttled = False
serial_number = self._adb.Adb().GetSerialNumber()
log = self._adb.RunShellCommand('dmesg -c')
degree_symbol = unichr(0x00B0)
for line in log:
if 'omap_thermal_throttle' in line:
if not self._throttled:
logging.warning('>>> Device %s Thermally Throttled', serial_number)
self._throttled = True
has_been_throttled = True
if 'omap_thermal_unthrottle' in line:
if self._throttled:
logging.warning('>>> Device %s Thermally Unthrottled', serial_number)
self._throttled = False
has_been_throttled = True
if 'throttle_delayed_work_fn' in line:
temp = float([s for s in line.split() if s.isdigit()][0]) / 1000.0
logging.info(u' Device %s Thermally Thottled at %3.1f%sC',
serial_number, temp, degree_symbol)
if logging.getLogger().isEnabledFor(logging.DEBUG):
# Print temperature of CPU SoC.
omap_temp_file = ('/sys/devices/platform/omap/omap_temp_sensor.0/'
'temperature')
if self._adb.FileExistsOnDevice(omap_temp_file):
tempdata = self._adb.GetFileContents(omap_temp_file)
temp = float(tempdata[0]) / 1000.0
logging.debug(u'Current OMAP Temperature of %s = %3.1f%sC',
serial_number, temp, degree_symbol)
# Print temperature of battery, to give a system temperature
dumpsys_log = self._adb.RunShellCommand('dumpsys battery')
for line in dumpsys_log:
if 'temperature' in line:
btemp = float([s for s in line.split() if s.isdigit()][0]) / 10.0
logging.debug(u'Current battery temperature of %s = %3.1f%sC',
serial_number, btemp, degree_symbol)
return has_been_throttled
super(ThermalThrottle, self).__init__(adb)

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

@ -13,7 +13,8 @@ import optparse
import sys
import time
from pylib import android_commands, surface_stats_collector
from pylib import android_commands
from pylib.perf import surface_stats_collector
from pylib.utils import run_tests_helper

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

@ -106,8 +106,8 @@ def PrintPerfResult(measurement, trace, values, units,
Returns:
String of the formated perf result.
"""
assert (perf_result_data_type.IsValidType(result_type),
'result type: %s is invalid' % result_type)
assert perf_result_data_type.IsValidType(result_type), \
'result type: %s is invalid' % result_type
trace_name = _EscapePerfResult(trace)