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