15f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)# Copyright 2014 The Chromium Authors. All rights reserved.
25f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be
35f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)# found in the LICENSE file.
45f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
55f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)import collections
61320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucciimport logging
75f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)import os
85f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)import re
95f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
105f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)from telemetry import decorators
115f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)from telemetry.core.platform import power_monitor
125f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
145f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)CPU_PATH = '/sys/devices/system/cpu/'
155f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
175f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)class SysfsPowerMonitor(power_monitor.PowerMonitor):
185f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  """PowerMonitor that relies on sysfs to monitor CPU statistics on several
195f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  different platforms.
205f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  """
211320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  def __init__(self, linux_based_platform_backend):
225f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    """Constructor.
235f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
245f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    Args:
251320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        linux_based_platform_backend: A LinuxBasedPlatformBackend object.
265f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
275f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    Attributes:
285f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        _browser: The browser to monitor.
295f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        _cpus: A list of the CPUs on the target device.
305f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        _end_time: The time the test stopped monitoring power.
315f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        _final_cstate: The c-state residency times after the test.
325f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        _final_freq: The CPU frequency times after the test.
335f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        _initial_cstate: The c-state residency times before the test.
345f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        _initial_freq: The CPU frequency times before the test.
351320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        _platform: A LinuxBasedPlatformBackend object associated with the
361320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci            target platform.
375f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        _start_time: The time the test started monitoring power.
385f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    """
395f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    super(SysfsPowerMonitor, self).__init__()
405f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    self._browser = None
4103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    self._cpus = None
425f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    self._final_cstate = None
435f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    self._final_freq = None
445f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    self._initial_cstate = None
455f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    self._initial_freq = None
461320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    self._platform = linux_based_platform_backend
475f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
485f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  @decorators.Cache
495f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  def CanMonitorPower(self):
501320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    return bool(self._platform.RunCommand(
515f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        'if [ -e %s ]; then echo true; fi' % CPU_PATH))
525f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
535f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  def StartMonitoringPower(self, browser):
545f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    assert not self._browser, 'Must call StopMonitoringPower().'
555f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    self._browser = browser
565f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    if self.CanMonitorPower():
5703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)      self._cpus = filter(
5803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)          lambda x: re.match(r'^cpu[0-9]+', x),
591320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci          self._platform.RunCommand('ls %s' % CPU_PATH).split())
605f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      self._initial_freq = self.GetCpuFreq()
615f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      self._initial_cstate = self.GetCpuState()
625f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
635f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  def StopMonitoringPower(self):
645f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    assert self._browser, 'StartMonitoringPower() not called.'
655f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    try:
665f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      out = {}
6703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)      if SysfsPowerMonitor.CanMonitorPower(self):
685f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        self._final_freq = self.GetCpuFreq()
695f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        self._final_cstate = self.GetCpuState()
705f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        frequencies = SysfsPowerMonitor.ComputeCpuStats(
715f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            SysfsPowerMonitor.ParseFreqSample(self._initial_freq),
725f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            SysfsPowerMonitor.ParseFreqSample(self._final_freq))
736e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        cstates = SysfsPowerMonitor.ComputeCpuStats(
741320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci            self._platform.ParseCStateSample(self._initial_cstate),
751320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci            self._platform.ParseCStateSample(self._final_cstate))
765f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        for cpu in frequencies:
775f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)          out[cpu] = {'frequency_percent': frequencies[cpu]}
785f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)          out[cpu]['cstate_residency_percent'] = cstates[cpu]
795f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      return out
805f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    finally:
815f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      self._browser = None
825f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
835f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  def GetCpuState(self):
845f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    """Retrieve CPU c-state residency times from the device.
855f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
865f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    Returns:
875f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        Dictionary containing c-state residency times for each CPU.
885f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    """
895f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    stats = {}
905f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    for cpu in self._cpus:
915f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      cpu_state_path = os.path.join(CPU_PATH, cpu, 'cpuidle/state*')
921320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      stats[cpu] = self._platform.RunCommand(
936e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)          'cat %s %s %s; date +%%s' % (os.path.join(cpu_state_path, 'name'),
945f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)          os.path.join(cpu_state_path, 'time'),
955f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)          os.path.join(cpu_state_path, 'latency')))
965f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    return stats
975f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
985f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  def GetCpuFreq(self):
995f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    """Retrieve CPU frequency times from the device.
1005f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1015f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    Returns:
1025f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        Dictionary containing frequency times for each CPU.
1035f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    """
1045f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    stats = {}
1055f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    for cpu in self._cpus:
1065f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      cpu_freq_path = os.path.join(
1075f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)          CPU_PATH, cpu, 'cpufreq/stats/time_in_state')
1081320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      try:
1091320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        stats[cpu] = self._platform.GetFileContents(cpu_freq_path)
1101320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      except Exception as e:
1111320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        logging.warning(
1121320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci            'Cannot read cpu frequency times in %s due to error: %s' %
1131320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci            (cpu_freq_path, e.message))
1141320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        stats[cpu] = None
1155f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    return stats
1165f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1175f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  @staticmethod
1185f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  def ParseFreqSample(sample):
1195f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    """Parse a single frequency sample.
1205f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1215f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    Args:
1225f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        sample: The single sample of frequency data to be parsed.
1235f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1245f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    Returns:
1255f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        A dictionary associating a frequency with a time.
1265f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    """
1275f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    sample_stats = {}
1285f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    for cpu in sample:
1295f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      frequencies = {}
1301320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      if sample[cpu] is None:
1311320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        sample_stats[cpu] = None
1321320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        continue
1335f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      for line in sample[cpu].splitlines():
1345f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        pair = line.split()
1355f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        freq = int(pair[0]) * 10 ** 3
1365f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        timeunits = int(pair[1])
1375f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        if freq in frequencies:
1385f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)          frequencies[freq] += timeunits
1395f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        else:
1405f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)          frequencies[freq] = timeunits
1415f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      sample_stats[cpu] = frequencies
1425f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    return sample_stats
1435f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1445f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  @staticmethod
1455f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  def ComputeCpuStats(initial, final):
1465f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    """Parse the CPU c-state and frequency values saved during monitoring.
1475f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1485f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    Args:
1495f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        initial: The parsed dictionary of initial statistics to be converted
1505f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        into percentages.
1515f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        final: The parsed dictionary of final statistics to be converted
1525f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        into percentages.
1535f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1545f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    Returns:
1555f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        Dictionary containing percentages for each CPU as well as an average
1565f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        across all CPUs.
1575f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    """
1585f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    cpu_stats = {}
1595f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    # Each core might have different states or frequencies, so keep track of
1605f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    # the total time in a state or frequency and how many cores report a time.
1615f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    cumulative_times = collections.defaultdict(lambda: (0, 0))
1625f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    for cpu in initial:
1635f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      current_cpu = {}
1645f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      total = 0
1651320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      if not initial[cpu] or not final[cpu]:
1661320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        cpu_stats[cpu] = collections.defaultdict(int)
1671320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        continue
1685f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      for state in initial[cpu]:
1695f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        current_cpu[state] = final[cpu][state] - initial[cpu][state]
1705f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        total += current_cpu[state]
1715f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      for state in current_cpu:
1725f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        current_cpu[state] /= (float(total) / 100.0)
1735f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        # Calculate the average c-state residency across all CPUs.
1745f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        time, count = cumulative_times[state]
1755f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        cumulative_times[state] = (time + current_cpu[state], count + 1)
1765f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      cpu_stats[cpu] = current_cpu
1775f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    average = {}
1785f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    for state in cumulative_times:
1795f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      time, count = cumulative_times[state]
1805f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      average[state] = time / float(count)
1815f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    cpu_stats['whole_package'] = average
1825f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    return cpu_stats
18303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
18403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  @staticmethod
18503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  def CombineResults(cpu_stats, power_stats):
18603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    """Add frequency and c-state residency data to the power data.
18703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
18803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    Args:
18903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)        cpu_stats: Dictionary containing CPU statistics.
19003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)        power_stats: Dictionary containing power statistics.
19103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
19203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    Returns:
19303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)        Dictionary in the format returned by StopMonitoringPower.
19403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    """
19503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    if not cpu_stats:
19603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)      return power_stats
19703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    if 'component_utilization' not in power_stats:
19803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)      power_stats['component_utilization'] = {}
19903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    for cpu in cpu_stats:
20003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)      power_stats['component_utilization'][cpu] = cpu_stats[cpu]
20103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    return power_stats
202