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:
Родитель
a1790df76f
Коммит
68fbba6e0a
|
@ -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)
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче