energy.py revision 87267394f63aad602a201701399787a9c0e5d796
1# SPDX-License-Identifier: Apache-2.0 2# 3# Copyright (C) 2015, ARM Limited and contributors. 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); you may 6# not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16# 17 18import devlib 19import json 20import logging 21import time 22 23# Default energy measurements for each board 24DEFAULT_ENERGY_METER = { 25 26 # ARM TC2: by default use HWMON 27 'tc2' : { 28 'instrument' : 'hwmon', 29 'conf' : { 30 'sites' : [ 'A7 Jcore', 'A15 Jcore' ], 31 'kinds' : [ 'energy'] 32 } 33 }, 34 35 # ARM Juno: by default use HWMON 36 'juno' : { 37 'instrument' : 'hwmon', 38 'conf' : { 39 'sites' : [ 'a53', 'a57' ], 40 'kinds' : [ 'energy' ] 41 } 42 }, 43 44 # Hikey: by default use AEP 45 'hikey' : { 46 'instrument' : 'aep', 47 } 48 49} 50 51class EnergyMeter(object): 52 53 _meter = None 54 55 def __init__(self, target): 56 self._target = target 57 58 @staticmethod 59 def getInstance(target, conf, force=False): 60 61 if not force and EnergyMeter._meter: 62 return EnergyMeter._meter 63 64 # Initialize energy probe to board default 65 if 'board' in conf and \ 66 conf['board'] in DEFAULT_ENERGY_METER: 67 emeter = DEFAULT_ENERGY_METER[conf['board']] 68 logging.debug('%14s - using default energy meter for [%s]', 69 'EnergyMeter', conf['board']) 70 else: 71 return None 72 73 if emeter['instrument'] == 'hwmon': 74 EnergyMeter._meter = HWMon(target, emeter['conf']) 75 elif emeter['instrument'] == 'aep': 76 EnergyMeter._meter = Aep(target) 77 return EnergyMeter._meter 78 79 def sample(self): 80 raise NotImplementedError('Missing implementation') 81 82 def reset(self): 83 raise NotImplementedError('Missing implementation') 84 85 def report(self, out_dir): 86 raise NotImplementedError('Missing implementation') 87 88class HWMon(EnergyMeter): 89 90 def __init__(self, target, hwmon_conf=None): 91 super(HWMon, self).__init__(target) 92 93 # The HWMon energy meter 94 self._hwmon = None 95 96 # Energy readings 97 self.readings = {} 98 99 if 'hwmon' not in self._target.modules: 100 logging.info('%14s - HWMON module not enabled', 101 'EnergyMeter') 102 logging.warning('%14s - Energy sampling disabled by configuration', 103 'EnergyMeter') 104 return 105 106 # Initialize HWMON instrument 107 logging.info('%14s - Scanning for HWMON channels, may take some time...', 'EnergyMeter') 108 self._hwmon = devlib.HwmonInstrument(self._target) 109 110 # Configure channels for energy measurements 111 logging.debug('%14s - Enabling channels %s', 'EnergyMeter', hwmon_conf) 112 self._hwmon.reset(**hwmon_conf) 113 114 # Logging enabled channels 115 logging.info('%14s - Channels selected for energy sampling:\n%s', 116 'EnergyMeter', str(self._hwmon.active_channels)) 117 118 def sample(self): 119 if self._hwmon is None: 120 return 121 samples = self._hwmon.take_measurement() 122 for s in samples: 123 label = s.channel.label\ 124 .replace('_energy', '')\ 125 .replace(" ", "_") 126 value = s.value 127 128 if label not in self.readings: 129 self.readings[label] = { 130 'last' : value, 131 'delta' : 0, 132 'total' : 0 133 } 134 continue 135 136 self.readings[label]['delta'] = value - self.readings[label]['last'] 137 self.readings[label]['last'] = value 138 self.readings[label]['total'] += self.readings[label]['delta'] 139 140 logging.debug('SAMPLE: %s', self.readings) 141 return self.readings 142 143 def reset(self): 144 if self._hwmon is None: 145 return 146 self.sample() 147 for label in self.readings: 148 self.readings[label]['delta'] = 0 149 self.readings[label]['total'] = 0 150 logging.debug('RESET: %s', self.readings) 151 152 153 def report(self, out_dir, out_file='energy.json'): 154 if self._hwmon is None: 155 return 156 # Retrive energy consumption data 157 nrg = self.sample() 158 # Reformat data for output generation 159 clusters_nrg = {} 160 for ch in nrg: 161 nrg_total = nrg[ch]['total'] 162 logging.info('%14s - Energy [%16s]: %.6f', 163 'EnergyReport', ch, nrg_total) 164 if self._target.little_core.upper() in ch.upper(): 165 clusters_nrg['LITTLE'] = '{:.6f}'.format(nrg_total) 166 elif self._target.big_core.upper() in ch.upper(): 167 clusters_nrg['big'] = '{:.6f}'.format(nrg_total) 168 else: 169 logging.warning('%14s - Unable to bind hwmon channel [%s]'\ 170 ' to a big.LITTLE cluster', 171 'EnergyReport', ch) 172 clusters_nrg[ch] = '{:.6f}'.format(nrg_total) 173 if 'LITTLE' not in clusters_nrg: 174 logging.warning('%14s - No energy data for LITTLE cluster', 175 'EnergyMeter') 176 if 'big' not in clusters_nrg: 177 logging.warning('%14s - No energy data for big cluster', 178 'EnergyMeter') 179 180 # Dump data as JSON file 181 nrg_file = '{}/{}'.format(out_dir, out_file) 182 with open(nrg_file, 'w') as ofile: 183 json.dump(clusters_nrg, ofile, sort_keys=True, indent=4) 184 185 return (clusters_nrg, nrg_file) 186 187class Aep(EnergyMeter): 188 189 def __init__(self, target): 190 super(Aep, self).__init__(target) 191 192 # Energy readings 193 self.readings = {} 194 195 # Time (start and diff) for power measurment 196 self.time = {} 197 198 # Initialize instrument 199 # Only one channel (first AEP channel: pc1 ... probe channel 1) is used 200 self._aep = devlib.EnergyProbeInstrument(self._target, labels=["pc1"], resistor_values=[0.033]) 201 202 # Configure channels for energy measurements 203 logging.debug('EnergyMeter - Enabling channels') 204 self._aep.reset() 205 206 # Logging enabled channels 207 logging.info('%14s - Channels selected for energy sampling:\n%s', 208 'EnergyMeter', str(self._aep.active_channels)) 209 210 def __calc_nrg(self, samples): 211 212 power = {'sum' : 0, 'count' : 0, 'avg' : 0} 213 214 for s in samples: 215 power['sum'] += s[1].value # s[1] ... power value of channel 1 216 power['count'] += 1 217 218 power['avg'] = power['sum'] / power['count'] 219 220 nrg = power['avg'] * self.time['diff'] 221 222 logging.debug('avg power: %.6f count: %s time: %.6f nrg: %.6f', 223 power['avg'], power['count'], self.time['diff'] , nrg) 224 return nrg 225 226 def sample(self): 227 if self._aep is None: 228 return 229 230 self.time['diff'] = time.time() - self.time['start'] 231 self._aep.stop() 232 233 csv_data = self._aep.get_data("/tmp/aep.csv") 234 samples = csv_data.measurements() 235 236 value = self.__calc_nrg(samples) 237 238 self.readings['last'] = value 239 self.readings['delta'] = value 240 self.readings['total'] = value 241 242 logging.debug('SAMPLE: %s', self.readings) 243 return self.readings 244 245 def reset(self): 246 if self._aep is None: 247 return 248 249 logging.debug('RESET: %s', self.readings) 250 251 self._aep.start() 252 self.time['start'] = time.time() 253 254 def report(self, out_dir, out_file='energy.json'): 255 if self._aep is None: 256 return 257 258 # Retrieve energy consumption data 259 nrg = self.sample() 260 261 # Reformat data for output generation 262 clusters_nrg = {} 263 clusters_nrg['LITTLE'] = '{:.6f}'.format(self.readings['total']) 264 265 # Dump data as JSON file 266 nrg_file = '{}/{}'.format(out_dir, out_file) 267 with open(nrg_file, 'w') as ofile: 268 json.dump(clusters_nrg, ofile, sort_keys=True, indent=4) 269 270 return (clusters_nrg, nrg_file) 271 272# vim :set tabstop=4 shiftwidth=4 expandtab 273