sysfs_power_monitor.py revision 6e8cce623b6e4fe0c9e4af605d675dd9d0338c38
1f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org# Copyright 2014 The Chromium Authors. All rights reserved.
2f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org# Use of this source code is governed by a BSD-style license that can be
3f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org# found in the LICENSE file.
4f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org
5f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.orgimport collections
6f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.orgimport os
7f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.orgimport re
8f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org
9f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.orgfrom telemetry import decorators
10f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.orgfrom telemetry.core.platform import power_monitor
11f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org
12f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.orgCPU_PATH = '/sys/devices/system/cpu/'
13f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org
14f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.orgclass SysfsPowerMonitor(power_monitor.PowerMonitor):
15f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org  """PowerMonitor that relies on sysfs to monitor CPU statistics on several
16f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org  different platforms.
17f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org  """
18f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org  def __init__(self, platform):
19f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org    """Constructor.
20f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org
21f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org    Args:
22f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org        platform: A SysfsPlatform object.
23f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org
24f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org    Attributes:
25f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org        _browser: The browser to monitor.
26f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org        _cpus: A list of the CPUs on the target device.
27f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org        _end_time: The time the test stopped monitoring power.
28f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org        _final_cstate: The c-state residency times after the test.
29f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org        _final_freq: The CPU frequency times after the test.
30f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org        _initial_cstate: The c-state residency times before the test.
31f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org        _initial_freq: The CPU frequency times before the test.
32f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org        _platform: A SysfsPlatform object associated with the target platform.
33f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org        _start_time: The time the test started monitoring power.
34f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org    """
35f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org    super(SysfsPowerMonitor, self).__init__()
36f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org    self._browser = None
37f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org    self._cpus = filter(lambda x: re.match(r'^cpu[0-9]+', x),
38f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org                        platform.RunShellCommand('ls %s' % CPU_PATH).split())
39f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org    self._final_cstate = None
40f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org    self._final_freq = None
41f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org    self._initial_cstate = None
42f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org    self._initial_freq = None
43f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org    self._platform = platform
44f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org
45f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org  @decorators.Cache
46f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org  def CanMonitorPower(self):
47f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org    return bool(self._platform.RunShellCommand(
48f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org        'if [ -e %s ]; then echo true; fi' % CPU_PATH))
49f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org
50f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org  def StartMonitoringPower(self, browser):
51f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org    assert not self._browser, 'Must call StopMonitoringPower().'
52f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org    self._browser = browser
53f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org    if self.CanMonitorPower():
54f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org      self._initial_freq = self.GetCpuFreq()
55f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org      self._initial_cstate = self.GetCpuState()
56f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org
57f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org  def StopMonitoringPower(self):
58f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org    assert self._browser, 'StartMonitoringPower() not called.'
59f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org    try:
60f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org      out = {}
61f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org      if self.CanMonitorPower():
62f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org        self._final_freq = self.GetCpuFreq()
63f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org        self._final_cstate = self.GetCpuState()
64f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org        frequencies = SysfsPowerMonitor.ComputeCpuStats(
65f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org            SysfsPowerMonitor.ParseFreqSample(self._initial_freq),
66f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org            SysfsPowerMonitor.ParseFreqSample(self._final_freq))
67f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org        cstates = SysfsPowerMonitor.ComputeCpuStats(
68f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org            self._platform.ParseStateSample(self._initial_cstate),
69f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org            self._platform.ParseStateSample(self._final_cstate))
70f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org        for cpu in frequencies:
71f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org          out[cpu] = {'frequency_percent': frequencies[cpu]}
72f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org          out[cpu]['cstate_residency_percent'] = cstates[cpu]
73f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org      return out
74f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org    finally:
75f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org      self._browser = None
76f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org
77f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org  def GetCpuState(self):
78f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org    """Retrieve CPU c-state residency times from the device.
79f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org
80f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org    Returns:
81f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org        Dictionary containing c-state residency times for each CPU.
82f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org    """
83f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org    stats = {}
84f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org    for cpu in self._cpus:
85f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org      cpu_state_path = os.path.join(CPU_PATH, cpu, 'cpuidle/state*')
86f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org      stats[cpu] = self._platform.RunShellCommand(
87f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org          'cat %s %s %s; date +%%s' % (os.path.join(cpu_state_path, 'name'),
88f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org          os.path.join(cpu_state_path, 'time'),
89f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org          os.path.join(cpu_state_path, 'latency')))
90f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org    return stats
91f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org
92f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org  def GetCpuFreq(self):
93f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org    """Retrieve CPU frequency times from the device.
94f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org
95f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org    Returns:
96f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org        Dictionary containing frequency times for each CPU.
97f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org    """
98f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org    stats = {}
99f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org    for cpu in self._cpus:
100f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org      cpu_freq_path = os.path.join(
101f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org          CPU_PATH, cpu, 'cpufreq/stats/time_in_state')
102f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org      stats[cpu] = self._platform.RunShellCommand('cat %s' % cpu_freq_path)
103f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org    return stats
104f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org
105f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org  @staticmethod
106f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org  def ParseFreqSample(sample):
107f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org    """Parse a single frequency sample.
108f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org
109f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org    Args:
110f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org        sample: The single sample of frequency data to be parsed.
111f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org
112f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org    Returns:
113f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org        A dictionary associating a frequency with a time.
114f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org    """
115f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org    sample_stats = {}
116f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org    for cpu in sample:
117f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org      frequencies = {}
118f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org      for line in sample[cpu].splitlines():
119f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org        pair = line.split()
120f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org        freq = int(pair[0]) * 10 ** 3
121f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org        timeunits = int(pair[1])
122f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org        if freq in frequencies:
123f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org          frequencies[freq] += timeunits
124f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org        else:
125f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org          frequencies[freq] = timeunits
126f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org      sample_stats[cpu] = frequencies
127f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org    return sample_stats
128f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org
129f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org  @staticmethod
130f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org  def ComputeCpuStats(initial, final):
131f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org    """Parse the CPU c-state and frequency values saved during monitoring.
132f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org
133f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org    Args:
134f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org        initial: The parsed dictionary of initial statistics to be converted
135f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org        into percentages.
136f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org        final: The parsed dictionary of final statistics to be converted
137f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org        into percentages.
138f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org
139f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org    Returns:
140f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org        Dictionary containing percentages for each CPU as well as an average
141f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org        across all CPUs.
142f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org    """
143f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org    cpu_stats = {}
144f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org    # Each core might have different states or frequencies, so keep track of
145f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org    # the total time in a state or frequency and how many cores report a time.
146f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org    cumulative_times = collections.defaultdict(lambda: (0, 0))
147f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org    for cpu in initial:
148f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org      current_cpu = {}
149f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org      total = 0
150f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org      for state in initial[cpu]:
151f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org        current_cpu[state] = final[cpu][state] - initial[cpu][state]
152f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org        total += current_cpu[state]
153f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org      for state in current_cpu:
154f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org        current_cpu[state] /= (float(total) / 100.0)
155f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org        # Calculate the average c-state residency across all CPUs.
156f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org        time, count = cumulative_times[state]
157f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org        cumulative_times[state] = (time + current_cpu[state], count + 1)
158f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org      cpu_stats[cpu] = current_cpu
159f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org    average = {}
160f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org    for state in cumulative_times:
161f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org      time, count = cumulative_times[state]
162f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org      average[state] = time / float(count)
163f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org    cpu_stats['whole_package'] = average
164f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org    return cpu_stats
165f2ba7591b1407a7ee9209f842c50696914dc2dedkbr@chromium.org