Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(762)

Unified Diff: tools/telemetry/telemetry/core/platform/mac_platform_backend.py

Issue 164133002: [Telemetry] Refactor mac power monitoring to use the base PowerMonitor class. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Follow review Created 6 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: tools/telemetry/telemetry/core/platform/mac_platform_backend.py
diff --git a/tools/telemetry/telemetry/core/platform/mac_platform_backend.py b/tools/telemetry/telemetry/core/platform/mac_platform_backend.py
index b391e221bf68f33b9f02bbd13635da3e7915a9bc..0e5cb42e82e513af51c99570bba947657d75e8b9 100644
--- a/tools/telemetry/telemetry/core/platform/mac_platform_backend.py
+++ b/tools/telemetry/telemetry/core/platform/mac_platform_backend.py
@@ -2,25 +2,18 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-import collections
import ctypes
-import logging
import os
-import plistlib
-import shutil
-import signal
-import tempfile
import time
-import xml.parsers.expat
try:
import resource # pylint: disable=F0401
except ImportError:
resource = None # Not available on all platforms
from telemetry import decorators
-from telemetry.core import util
from telemetry.core.platform import platform_backend
from telemetry.core.platform import posix_platform_backend
+from telemetry.core.platform.power_monitor import powermetrics_power_monitor
LEOPARD = platform_backend.OSVersion('leopard', 10.5)
@@ -31,67 +24,11 @@ MAVERICKS = platform_backend.OSVersion('mavericks', 10.9)
class MacPlatformBackend(posix_platform_backend.PosixPlatformBackend):
-
- class PowerMetricsUtility(object):
- def __init__(self, backend):
- self._powermetrics_process = None
- self._backend = backend
- self._output_filename = None
- self._ouput_directory = None
-
- @property
- def binary_path(self):
- return '/usr/bin/powermetrics'
-
- def StartMonitoringPowerAsync(self):
- assert not self._powermetrics_process, (
- "Must call StopMonitoringPowerAsync().")
- SAMPLE_INTERVAL_MS = 1000 / 20 # 20 Hz, arbitrary.
- # Empirically powermetrics creates an empty output file immediately upon
- # starting. We detect file creation as a signal that measurement has
- # started. In order to avoid various race conditions in tempfile creation
- # we create a temp directory and have powermetrics create it's output
- # there rather than say, creating a tempfile, deleting it and reusing its
- # name.
- self._ouput_directory = tempfile.mkdtemp()
- self._output_filename = os.path.join(self._ouput_directory,
- 'powermetrics.output')
- args = ['-f', 'plist',
- '-i', '%d' % SAMPLE_INTERVAL_MS,
- '-u', self._output_filename]
- self._powermetrics_process = self._backend.LaunchApplication(
- self.binary_path, args, elevate_privilege=True)
-
- # Block until output file is written to ensure this function call is
- # synchronous in respect to powermetrics starting.
- def _OutputFileExists():
- return os.path.isfile(self._output_filename)
- timeout_sec = 2 * (SAMPLE_INTERVAL_MS / 1000.)
- util.WaitFor(_OutputFileExists, timeout_sec)
-
- def StopMonitoringPowerAsync(self):
- assert self._powermetrics_process, (
- "StartMonitoringPowerAsync() not called.")
- # Tell powermetrics to take an immediate sample.
- try:
- self._powermetrics_process.send_signal(signal.SIGINFO)
- self._powermetrics_process.send_signal(signal.SIGTERM)
- returncode = self._powermetrics_process.wait()
- assert returncode in [0, -15], (
- "powermetrics return code: %d" % returncode)
-
- with open(self._output_filename, 'rb') as output_file:
- return output_file.read()
- finally:
- shutil.rmtree(self._ouput_directory)
- self._ouput_directory = None
- self._output_filename = None
- self._powermetrics_process = None
-
def __init__(self):
super(MacPlatformBackend, self).__init__()
self.libproc = None
- self.powermetrics_tool_ = MacPlatformBackend.PowerMetricsUtility(self)
+ self.power_monitor_ = powermetrics_power_monitor.PowerMetricsPowerMonitor(
+ self)
def StartRawDisplayFrameRateMeasurement(self):
raise NotImplementedError()
@@ -204,186 +141,11 @@ class MacPlatformBackend(posix_platform_backend.PosixPlatformBackend):
p.wait()
assert p.returncode == 0, 'Failed to flush system cache'
- @decorators.Cache
def CanMonitorPowerAsync(self):
- mavericks_or_later = self.GetOSVersionName() >= MAVERICKS
- binary_path = self.powermetrics_tool_.binary_path
- return mavericks_or_later and self.CanLaunchApplication(binary_path)
-
- def SetPowerMetricsUtilityForTest(self, obj):
- self.powermetrics_tool_ = obj
+ return self.power_monitor_.CanMonitorPowerAsync()
def StartMonitoringPowerAsync(self):
- self.powermetrics_tool_.StartMonitoringPowerAsync()
-
- def _ParsePlistString(self, plist_string):
- """Wrapper to parse a plist from a string and catch any errors.
-
- Sometimes powermetrics will exit in the middle of writing it's output,
- empirically it seems that it always writes at least one sample in it's
- entirety so we can safely ignore any errors in it's output.
-
- Returns:
- Parser output on succesful parse, None on parse error.
- """
- try:
- return plistlib.readPlistFromString(plist_string)
- except xml.parsers.expat.ExpatError:
- return None
-
- def _ParsePowerMetricsOutput(self, powermetrics_output):
- """Parse output of powermetrics command line utility.
-
- Returns:
- Dictionary in the format returned by StopMonitoringPowerAsync().
- """
-
- # Container to collect samples for running averages.
- # out_path - list containing the key path in the output dictionary.
- # src_path - list containing the key path to get the data from in
- # powermetrics' output.
- def ConstructMetric(out_path, src_path):
- RunningAverage = collections.namedtuple('RunningAverage', [
- 'out_path', 'src_path', 'samples'])
- return RunningAverage(out_path, src_path, [])
-
- # List of RunningAverage objects specifying metrics we want to aggregate.
- metrics = [
- ConstructMetric(
- ['component_utilization', 'whole_package', 'average_frequency_mhz'],
- ['processor','freq_hz']),
- ConstructMetric(
- ['component_utilization', 'whole_package', 'idle_percent'],
- ['processor','packages', 0, 'c_state_ratio'])]
-
- def DataWithMetricKeyPath(metric, powermetrics_output):
- """Retrieve the sample from powermetrics' output for a given metric.
-
- Args:
- metric: The RunningAverage object we want to collect a new sample for.
- powermetrics_output: Dictionary containing powermetrics output.
-
- Returns:
- The sample corresponding to |metric|'s keypath."""
- # Get actual data corresponding to key path.
- out_data = powermetrics_output
- for k in metric.src_path:
- out_data = out_data[k]
-
- assert type(out_data) in [int, float], (
- "Was expecting a number: %s (%s)" % (type(out_data), out_data))
- return float(out_data)
-
- power_samples = []
- sample_durations = []
- total_energy_consumption_mwh = 0
- # powermetrics outputs multiple plists separated by null terminators.
- raw_plists = powermetrics_output.split('\0')
- raw_plists = [x for x in raw_plists if len(x) > 0]
-
- # -------- Examine contents of first plist for systems specs. --------
- plist = self._ParsePlistString(raw_plists[0])
- if not plist:
- logging.warning("powermetrics produced invalid output, output length: "
- "%d" % len(powermetrics_output))
- return {}
-
- if 'GPU' in plist:
- metrics.extend([
- ConstructMetric(
- ['component_utilization', 'gpu', 'average_frequency_mhz'],
- ['GPU', 0, 'freq_hz']),
- ConstructMetric(
- ['component_utilization', 'gpu', 'idle_percent'],
- ['GPU', 0, 'c_state_ratio'])])
-
-
- # There's no way of knowing ahead of time how many cpus and packages the
- # current system has. Iterate over cores and cpus - construct metrics for
- # each one.
- if 'processor' in plist:
- core_dict = plist['processor']['packages'][0]['cores']
- num_cores = len(core_dict)
- cpu_num = 0
- for core_idx in xrange(num_cores):
- num_cpus = len(core_dict[core_idx]['cpus'])
- base_src_path = ['processor', 'packages', 0, 'cores', core_idx]
- for cpu_idx in xrange(num_cpus):
- base_out_path = ['component_utilization', 'cpu%d' % cpu_num]
- # C State ratio is per-package, component CPUs of that package may
- # have different frequencies.
- metrics.append(ConstructMetric(
- base_out_path + ['average_frequency_mhz'],
- base_src_path + ['cpus', cpu_idx, 'freq_hz']))
- metrics.append(ConstructMetric(
- base_out_path + ['idle_percent'],
- base_src_path + ['c_state_ratio']))
- cpu_num += 1
-
- # -------- Parse Data Out of Plists --------
- for raw_plist in raw_plists:
- plist = self._ParsePlistString(raw_plist)
- if not plist:
- continue
-
- # Duration of this sample.
- sample_duration_ms = int(plist['elapsed_ns']) / 10**6
- sample_durations.append(sample_duration_ms)
-
- if 'processor' not in plist:
- continue
- processor = plist['processor']
-
- energy_consumption_mw = int(processor.get('package_watts', 0)) * 10**3
-
- total_energy_consumption_mwh += (energy_consumption_mw *
- (sample_duration_ms / 3600000.))
-
- power_samples.append(energy_consumption_mw)
-
- for m in metrics:
- m.samples.append(DataWithMetricKeyPath(m, plist))
-
- # -------- Collect and Process Data --------
- out_dict = {}
- out_dict['identifier'] = 'powermetrics'
- # Raw power usage samples.
- if power_samples:
- out_dict['power_samples_mw'] = power_samples
- out_dict['energy_consumption_mwh'] = total_energy_consumption_mwh
-
- def StoreMetricAverage(metric, sample_durations, out):
- """Calculate average value of samples in a metric and store in output
- path as specified by metric.
-
- Args:
- metric: A RunningAverage object containing samples to average.
- sample_durations: A list which parallels the samples list containing
- the time slice for each sample.
- out: The output dicat, average is stored in the location specified by
- metric.out_path.
- """
- if len(metric.samples) == 0:
- return
-
- assert len(metric.samples) == len(sample_durations)
- avg = 0
- for i in xrange(len(metric.samples)):
- avg += metric.samples[i] * sample_durations[i]
- avg /= sum(sample_durations)
-
- # Store data in output, creating empty dictionaries as we go.
- for k in metric.out_path[:-1]:
- if not out.has_key(k):
- out[k] = {}
- out = out[k]
- out[metric.out_path[-1]] = avg
-
- for m in metrics:
- StoreMetricAverage(m, sample_durations, out_dict)
- return out_dict
+ self.power_monitor_.StartMonitoringPowerAsync()
def StopMonitoringPowerAsync(self):
- powermetrics_output = self.powermetrics_tool_.StopMonitoringPowerAsync()
- assert len(powermetrics_output) > 0
- return self._ParsePowerMetricsOutput(powermetrics_output)
+ return self.power_monitor_.StopMonitoringPowerAsync()

Powered by Google App Engine
This is Rietveld 408576698