Index: tools/telemetry/telemetry/core/platform/power_monitor/sysfs_power_monitor.py |
diff --git a/tools/telemetry/telemetry/core/platform/power_monitor/sysfs_power_monitor.py b/tools/telemetry/telemetry/core/platform/power_monitor/sysfs_power_monitor.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..c6138d7d017fc6c281026dbbef07f17708026b32 |
--- /dev/null |
+++ b/tools/telemetry/telemetry/core/platform/power_monitor/sysfs_power_monitor.py |
@@ -0,0 +1,170 @@ |
+# Copyright 2014 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 collections |
+import os |
+import re |
+ |
+from telemetry import decorators |
+from telemetry.core.platform import power_monitor |
+ |
+CPU_PATH = '/sys/devices/system/cpu/' |
+ |
+class SysfsPowerMonitor(power_monitor.PowerMonitor): |
+ """PowerMonitor that relies on sysfs to monitor CPU statistics on several |
+ different platforms. |
+ """ |
+ def __init__(self, platform): |
+ """Constructor. |
+ |
+ Args: |
+ platform: A SysfsPlatform object. |
+ |
+ Attributes: |
+ _browser: The browser to monitor. |
+ _cpus: A list of the CPUs on the target device. |
+ _end_time: The time the test stopped monitoring power. |
+ _final_cstate: The c-state residency times after the test. |
+ _final_freq: The CPU frequency times after the test. |
+ _initial_cstate: The c-state residency times before the test. |
+ _initial_freq: The CPU frequency times before the test. |
+ _platform: A SysfsPlatform object associated with the target platform. |
+ _start_time: The time the test started monitoring power. |
+ """ |
+ super(SysfsPowerMonitor, self).__init__() |
+ self._browser = None |
+ self._cpus = filter(lambda x: re.match(r'^cpu[0-9]+', x), |
+ platform.RunShellCommand('ls %s' % CPU_PATH).split()) |
+ self._end_time = None |
+ self._final_cstate = None |
+ self._final_freq = None |
+ self._initial_cstate = None |
+ self._initial_freq = None |
+ self._platform = platform |
+ self._start_time = None |
+ |
+ @decorators.Cache |
+ def CanMonitorPower(self): |
+ return bool(self._platform.RunShellCommand( |
+ 'if [ -e %s ]; then echo true; fi' % CPU_PATH)) |
+ |
+ def StartMonitoringPower(self, browser): |
+ assert not self._browser, 'Must call StopMonitoringPower().' |
+ self._browser = browser |
+ self._start_time = int(self._platform.RunShellCommand('date +%s')) |
+ if self.CanMonitorPower(): |
+ self._initial_freq = self.GetCpuFreq() |
+ self._initial_cstate = self.GetCpuState() |
+ |
+ def StopMonitoringPower(self): |
+ assert self._browser, 'StartMonitoringPower() not called.' |
+ try: |
+ self._end_time = int(self._platform.RunShellCommand('date +%s')) |
+ out = {} |
+ if self.CanMonitorPower(): |
+ self._final_freq = self.GetCpuFreq() |
+ self._final_cstate = self.GetCpuState() |
+ frequencies = SysfsPowerMonitor.ComputeCpuStats( |
+ SysfsPowerMonitor.ParseFreqSample(self._initial_freq), |
+ SysfsPowerMonitor.ParseFreqSample(self._final_freq)) |
+ start_cstate = self._platform.ParseStateSample( |
+ self._initial_cstate, self._start_time) |
+ end_cstate = self._platform.ParseStateSample( |
+ self._final_cstate, self._end_time) |
+ cstates = SysfsPowerMonitor.ComputeCpuStats(start_cstate, end_cstate) |
+ for cpu in frequencies: |
+ out[cpu] = {'frequency_percent': frequencies[cpu]} |
+ out[cpu]['cstate_residency_percent'] = cstates[cpu] |
+ return out |
+ finally: |
+ self._browser = None |
+ |
+ def GetCpuState(self): |
+ """Retrieve CPU c-state residency times from the device. |
+ |
+ Returns: |
+ Dictionary containing c-state residency times for each CPU. |
+ """ |
+ stats = {} |
+ for cpu in self._cpus: |
+ cpu_state_path = os.path.join(CPU_PATH, cpu, 'cpuidle/state*') |
+ stats[cpu] = self._platform.RunShellCommand( |
+ 'cat %s %s %s' % (os.path.join(cpu_state_path, 'name'), |
+ os.path.join(cpu_state_path, 'time'), |
+ os.path.join(cpu_state_path, 'latency'))) |
+ return stats |
+ |
+ def GetCpuFreq(self): |
+ """Retrieve CPU frequency times from the device. |
+ |
+ Returns: |
+ Dictionary containing frequency times for each CPU. |
+ """ |
+ stats = {} |
+ for cpu in self._cpus: |
+ cpu_freq_path = os.path.join( |
+ CPU_PATH, cpu, 'cpufreq/stats/time_in_state') |
+ stats[cpu] = self._platform.RunShellCommand('cat %s' % cpu_freq_path) |
+ return stats |
+ |
+ @staticmethod |
+ def ParseFreqSample(sample): |
+ """Parse a single frequency sample. |
+ |
+ Args: |
+ sample: The single sample of frequency data to be parsed. |
+ |
+ Returns: |
+ A dictionary associating a frequency with a time. |
+ """ |
+ sample_stats = {} |
+ for cpu in sample: |
+ frequencies = {} |
+ for line in sample[cpu].splitlines(): |
+ pair = line.split() |
+ freq = int(pair[0]) * 10 ** 3 |
+ timeunits = int(pair[1]) |
+ if freq in frequencies: |
+ frequencies[freq] += timeunits |
+ else: |
+ frequencies[freq] = timeunits |
+ sample_stats[cpu] = frequencies |
+ return sample_stats |
+ |
+ @staticmethod |
+ def ComputeCpuStats(initial, final): |
+ """Parse the CPU c-state and frequency values saved during monitoring. |
+ |
+ Args: |
+ initial: The parsed dictionary of initial statistics to be converted |
+ into percentages. |
+ final: The parsed dictionary of final statistics to be converted |
+ into percentages. |
+ |
+ Returns: |
+ Dictionary containing percentages for each CPU as well as an average |
+ across all CPUs. |
+ """ |
+ cpu_stats = {} |
+ # Each core might have different states or frequencies, so keep track of |
+ # the total time in a state or frequency and how many cores report a time. |
+ cumulative_times = collections.defaultdict(lambda: (0, 0)) |
+ for cpu in initial: |
+ current_cpu = {} |
+ total = 0 |
+ for state in initial[cpu]: |
+ current_cpu[state] = final[cpu][state] - initial[cpu][state] |
+ total += current_cpu[state] |
+ for state in current_cpu: |
+ current_cpu[state] /= (float(total) / 100.0) |
+ # Calculate the average c-state residency across all CPUs. |
+ time, count = cumulative_times[state] |
+ cumulative_times[state] = (time + current_cpu[state], count + 1) |
+ cpu_stats[cpu] = current_cpu |
+ average = {} |
+ for state in cumulative_times: |
+ time, count = cumulative_times[state] |
+ average[state] = time / float(count) |
+ cpu_stats['whole_package'] = average |
+ return cpu_stats |