1*9c5db199SXin Li# Lint as: python2, python3 2*9c5db199SXin Li# Copyright (c) 2012 The Chromium OS Authors. All rights reserved. 3*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be 4*9c5db199SXin Li# found in the LICENSE file. 5*9c5db199SXin Li 6*9c5db199SXin Lifrom __future__ import absolute_import 7*9c5db199SXin Lifrom __future__ import division 8*9c5db199SXin Lifrom __future__ import print_function 9*9c5db199SXin Li 10*9c5db199SXin Liimport collections 11*9c5db199SXin Liimport contextlib 12*9c5db199SXin Liimport ctypes 13*9c5db199SXin Liimport fcntl 14*9c5db199SXin Liimport glob 15*9c5db199SXin Liimport itertools 16*9c5db199SXin Liimport json 17*9c5db199SXin Liimport logging 18*9c5db199SXin Liimport math 19*9c5db199SXin Liimport numpy 20*9c5db199SXin Liimport os 21*9c5db199SXin Liimport re 22*9c5db199SXin Liimport struct 23*9c5db199SXin Liimport threading 24*9c5db199SXin Liimport time 25*9c5db199SXin Li 26*9c5db199SXin Lifrom autotest_lib.client.bin import utils 27*9c5db199SXin Lifrom autotest_lib.client.common_lib import autotest_enum 28*9c5db199SXin Lifrom autotest_lib.client.common_lib import error 29*9c5db199SXin Lifrom autotest_lib.client.common_lib.cros import retry 30*9c5db199SXin Lifrom autotest_lib.client.common_lib.utils import poll_for_condition_ex 31*9c5db199SXin Lifrom autotest_lib.client.cros import kernel_trace 32*9c5db199SXin Lifrom autotest_lib.client.cros.power import power_utils 33*9c5db199SXin Lifrom collections import namedtuple 34*9c5db199SXin Lifrom six.moves import range 35*9c5db199SXin Lifrom six.moves import zip 36*9c5db199SXin Li 37*9c5db199SXin LiBatteryDataReportType = autotest_enum.AutotestEnum('CHARGE', 'ENERGY') 38*9c5db199SXin Li 39*9c5db199SXin Li# battery data reported at 1e6 scale 40*9c5db199SXin LiBATTERY_DATA_SCALE = 1e6 41*9c5db199SXin Li# number of times to retry reading the battery in the case of bad data 42*9c5db199SXin LiBATTERY_RETRY_COUNT = 3 43*9c5db199SXin Li# default filename when saving CheckpointLogger data to file 44*9c5db199SXin LiCHECKPOINT_LOG_DEFAULT_FNAME = 'checkpoint_log.json' 45*9c5db199SXin Li 46*9c5db199SXin Li 47*9c5db199SXin Liclass DevStat(object): 48*9c5db199SXin Li """ 49*9c5db199SXin Li Device power status. This class implements generic status initialization 50*9c5db199SXin Li and parsing routines. 51*9c5db199SXin Li """ 52*9c5db199SXin Li 53*9c5db199SXin Li def __init__(self, fields, path=None): 54*9c5db199SXin Li self.fields = fields 55*9c5db199SXin Li self.path = path 56*9c5db199SXin Li 57*9c5db199SXin Li 58*9c5db199SXin Li def reset_fields(self): 59*9c5db199SXin Li """ 60*9c5db199SXin Li Reset all class fields to None to mark their status as unknown. 61*9c5db199SXin Li """ 62*9c5db199SXin Li for field in self.fields.keys(): 63*9c5db199SXin Li setattr(self, field, None) 64*9c5db199SXin Li 65*9c5db199SXin Li 66*9c5db199SXin Li def read_val(self, file_name, field_type): 67*9c5db199SXin Li """Read a value from file. 68*9c5db199SXin Li """ 69*9c5db199SXin Li try: 70*9c5db199SXin Li path = file_name 71*9c5db199SXin Li if not file_name.startswith('/'): 72*9c5db199SXin Li path = os.path.join(self.path, file_name) 73*9c5db199SXin Li f = open(path, 'r') 74*9c5db199SXin Li out = f.readline().rstrip('\n') 75*9c5db199SXin Li val = field_type(out) 76*9c5db199SXin Li return val 77*9c5db199SXin Li 78*9c5db199SXin Li except: 79*9c5db199SXin Li return field_type(0) 80*9c5db199SXin Li 81*9c5db199SXin Li 82*9c5db199SXin Li def read_all_vals(self): 83*9c5db199SXin Li """Read all values. 84*9c5db199SXin Li """ 85*9c5db199SXin Li for field, prop in self.fields.items(): 86*9c5db199SXin Li if prop[0]: 87*9c5db199SXin Li val = self.read_val(prop[0], prop[1]) 88*9c5db199SXin Li setattr(self, field, val) 89*9c5db199SXin Li 90*9c5db199SXin Li def update(self): 91*9c5db199SXin Li """Update the DevStat. 92*9c5db199SXin Li 93*9c5db199SXin Li Need to implement in subclass. 94*9c5db199SXin Li """ 95*9c5db199SXin Li pass 96*9c5db199SXin Li 97*9c5db199SXin Liclass ThermalStatACPI(DevStat): 98*9c5db199SXin Li """ 99*9c5db199SXin Li ACPI-based thermal status. 100*9c5db199SXin Li 101*9c5db199SXin Li Fields: 102*9c5db199SXin Li (All temperatures are in millidegrees Celsius.) 103*9c5db199SXin Li 104*9c5db199SXin Li str enabled: Whether thermal zone is enabled 105*9c5db199SXin Li int temp: Current temperature 106*9c5db199SXin Li str type: Thermal zone type 107*9c5db199SXin Li int num_trip_points: Number of thermal trip points that activate 108*9c5db199SXin Li cooling devices 109*9c5db199SXin Li int num_points_tripped: Temperature is above this many trip points 110*9c5db199SXin Li str trip_point_N_type: Trip point #N's type 111*9c5db199SXin Li int trip_point_N_temp: Trip point #N's temperature value 112*9c5db199SXin Li int cdevX_trip_point: Trip point o cooling device #X (index) 113*9c5db199SXin Li """ 114*9c5db199SXin Li 115*9c5db199SXin Li MAX_TRIP_POINTS = 20 116*9c5db199SXin Li 117*9c5db199SXin Li thermal_fields = { 118*9c5db199SXin Li 'enabled': ['enabled', str], 119*9c5db199SXin Li 'temp': ['temp', int], 120*9c5db199SXin Li 'type': ['type', str], 121*9c5db199SXin Li 'num_points_tripped': ['', ''] 122*9c5db199SXin Li } 123*9c5db199SXin Li path = '/sys/class/thermal/thermal_zone*' 124*9c5db199SXin Li 125*9c5db199SXin Li def __init__(self, path=None): 126*9c5db199SXin Li # Browse the thermal folder for trip point fields. 127*9c5db199SXin Li self.num_trip_points = 0 128*9c5db199SXin Li 129*9c5db199SXin Li if path is None: 130*9c5db199SXin Li path = ThermalStatACPI.path 131*9c5db199SXin Li 132*9c5db199SXin Li self.zones = {} 133*9c5db199SXin Li thermal_zones = glob.glob(path) 134*9c5db199SXin Li for (i, zone) in enumerate(thermal_zones): 135*9c5db199SXin Li desc_path = os.path.join(zone, 'device/description') 136*9c5db199SXin Li desc = '' 137*9c5db199SXin Li if os.path.exists(desc_path): 138*9c5db199SXin Li desc = utils.read_one_line(desc_path) 139*9c5db199SXin Li 140*9c5db199SXin Li # If there's no description then use the type to create a description 141*9c5db199SXin Li if desc == '': 142*9c5db199SXin Li domain_path = os.path.join(zone, 'type') 143*9c5db199SXin Li domain = utils.read_one_line(domain_path) 144*9c5db199SXin Li desc = '%s%d' % (domain, i) 145*9c5db199SXin Li 146*9c5db199SXin Li desc = desc.replace(' ', '_') 147*9c5db199SXin Li self.zones[desc] = os.path.join(zone, 'temp') 148*9c5db199SXin Li 149*9c5db199SXin Li thermal_fields = glob.glob(path + '/*') 150*9c5db199SXin Li for file in thermal_fields: 151*9c5db199SXin Li field = file[len(path + '/'):] 152*9c5db199SXin Li if field.find('trip_point') != -1: 153*9c5db199SXin Li if field.find('temp'): 154*9c5db199SXin Li field_type = int 155*9c5db199SXin Li else: 156*9c5db199SXin Li field_type = str 157*9c5db199SXin Li self.thermal_fields[field] = [field, field_type] 158*9c5db199SXin Li 159*9c5db199SXin Li # Count the number of trip points. 160*9c5db199SXin Li if field.find('_type') != -1: 161*9c5db199SXin Li self.num_trip_points += 1 162*9c5db199SXin Li 163*9c5db199SXin Li super(ThermalStatACPI, self).__init__(self.thermal_fields, path) 164*9c5db199SXin Li self.update() 165*9c5db199SXin Li 166*9c5db199SXin Li def update(self): 167*9c5db199SXin Li if not os.path.exists(self.path): 168*9c5db199SXin Li return 169*9c5db199SXin Li 170*9c5db199SXin Li self.read_all_vals() 171*9c5db199SXin Li self.num_points_tripped = 0 172*9c5db199SXin Li 173*9c5db199SXin Li for field in self.thermal_fields: 174*9c5db199SXin Li if field.find('trip_point_') != -1 and field.find('_temp') != -1 \ 175*9c5db199SXin Li and self.temp > self.read_val(field, int): 176*9c5db199SXin Li self.num_points_tripped += 1 177*9c5db199SXin Li logging.info('Temperature trip point #%s tripped.', \ 178*9c5db199SXin Li field[len('trip_point_'):field.rfind('_temp')]) 179*9c5db199SXin Li 180*9c5db199SXin Li 181*9c5db199SXin Liclass ThermalStatHwmon(DevStat): 182*9c5db199SXin Li """ 183*9c5db199SXin Li hwmon-based thermal status. Excludes overlaps with thermal zones by default 184*9c5db199SXin Li since thermal zones generally provide a more usable description. 185*9c5db199SXin Li 186*9c5db199SXin Li Fields: 187*9c5db199SXin Li int <tname>_temp<num>_input: Current temperature in millidegrees Celsius 188*9c5db199SXin Li where: 189*9c5db199SXin Li <tname> : name of hwmon device in sysfs 190*9c5db199SXin Li <num> : number of temp as some hwmon devices have multiple 191*9c5db199SXin Li 192*9c5db199SXin Li """ 193*9c5db199SXin Li path = '/sys/class/hwmon' 194*9c5db199SXin Li 195*9c5db199SXin Li thermal_fields = {} 196*9c5db199SXin Li 197*9c5db199SXin Li def __init__(self, rootpath=None, exclude_tz=True): 198*9c5db199SXin Li excluded_domains = set() 199*9c5db199SXin Li if exclude_tz: 200*9c5db199SXin Li thermal_zones = glob.glob('/sys/class/thermal/thermal_zone*') 201*9c5db199SXin Li for zone in thermal_zones: 202*9c5db199SXin Li domain_path = os.path.join(zone, 'type') 203*9c5db199SXin Li domain = utils.read_one_line(domain_path) 204*9c5db199SXin Li 205*9c5db199SXin Li excluded_domains.add(domain) 206*9c5db199SXin Li 207*9c5db199SXin Li if not rootpath: 208*9c5db199SXin Li rootpath = self.path 209*9c5db199SXin Li for subpath1 in glob.glob('%s/hwmon*' % rootpath): 210*9c5db199SXin Li for subpath2 in ['','device/']: 211*9c5db199SXin Li gpaths = glob.glob("%s/%stemp*_input" % (subpath1, subpath2)) 212*9c5db199SXin Li for gpath in gpaths: 213*9c5db199SXin Li bname = os.path.basename(gpath) 214*9c5db199SXin Li field_path = os.path.join(subpath1, subpath2, bname) 215*9c5db199SXin Li 216*9c5db199SXin Li domain_path = os.path.join(os.path.dirname(gpath), "name") 217*9c5db199SXin Li domain = utils.read_one_line(domain_path) 218*9c5db199SXin Li 219*9c5db199SXin Li if domain in excluded_domains: 220*9c5db199SXin Li continue 221*9c5db199SXin Li 222*9c5db199SXin Li field_key = "%s_%s" % (domain, bname) 223*9c5db199SXin Li self.thermal_fields[field_key] = [field_path, int] 224*9c5db199SXin Li 225*9c5db199SXin Li super(ThermalStatHwmon, self).__init__(self.thermal_fields, rootpath) 226*9c5db199SXin Li self.update() 227*9c5db199SXin Li 228*9c5db199SXin Li def update(self): 229*9c5db199SXin Li if not os.path.exists(self.path): 230*9c5db199SXin Li return 231*9c5db199SXin Li 232*9c5db199SXin Li self.read_all_vals() 233*9c5db199SXin Li 234*9c5db199SXin Li def read_val(self, file_name, field_type): 235*9c5db199SXin Li try: 236*9c5db199SXin Li path = os.path.join(self.path, file_name) 237*9c5db199SXin Li f = open(path, 'r') 238*9c5db199SXin Li out = f.readline() 239*9c5db199SXin Li return field_type(out) 240*9c5db199SXin Li except: 241*9c5db199SXin Li return field_type(0) 242*9c5db199SXin Li 243*9c5db199SXin Li 244*9c5db199SXin Liclass ThermalStat(object): 245*9c5db199SXin Li """helper class to instantiate various thermal devices.""" 246*9c5db199SXin Li def __init__(self): 247*9c5db199SXin Li self._thermals = [] 248*9c5db199SXin Li self.min_temp = 999999999 249*9c5db199SXin Li self.max_temp = -999999999 250*9c5db199SXin Li 251*9c5db199SXin Li thermal_stat_types = [(ThermalStatHwmon.path, ThermalStatHwmon), 252*9c5db199SXin Li (ThermalStatACPI.path, ThermalStatACPI)] 253*9c5db199SXin Li for thermal_glob_path, thermal_type in thermal_stat_types: 254*9c5db199SXin Li try: 255*9c5db199SXin Li thermal_path = glob.glob(thermal_glob_path)[0] 256*9c5db199SXin Li logging.debug('Using %s for thermal info.', thermal_path) 257*9c5db199SXin Li self._thermals.append(thermal_type(thermal_path)) 258*9c5db199SXin Li except: 259*9c5db199SXin Li logging.debug('Could not find thermal path %s, skipping.', 260*9c5db199SXin Li thermal_glob_path) 261*9c5db199SXin Li 262*9c5db199SXin Li 263*9c5db199SXin Li def get_temps(self): 264*9c5db199SXin Li """Get temperature readings. 265*9c5db199SXin Li 266*9c5db199SXin Li Returns: 267*9c5db199SXin Li string of temperature readings. 268*9c5db199SXin Li """ 269*9c5db199SXin Li temp_str = '' 270*9c5db199SXin Li for thermal in self._thermals: 271*9c5db199SXin Li thermal.update() 272*9c5db199SXin Li for kname in thermal.fields: 273*9c5db199SXin Li if kname is 'temp' or kname.endswith('_input'): 274*9c5db199SXin Li val = getattr(thermal, kname) 275*9c5db199SXin Li temp_str += '%s:%d ' % (kname, val) 276*9c5db199SXin Li if val > self.max_temp: 277*9c5db199SXin Li self.max_temp = val 278*9c5db199SXin Li if val < self.min_temp: 279*9c5db199SXin Li self.min_temp = val 280*9c5db199SXin Li 281*9c5db199SXin Li 282*9c5db199SXin Li return temp_str 283*9c5db199SXin Li 284*9c5db199SXin Li 285*9c5db199SXin Liclass BatteryStat(DevStat): 286*9c5db199SXin Li """ 287*9c5db199SXin Li Battery status. 288*9c5db199SXin Li 289*9c5db199SXin Li Fields: 290*9c5db199SXin Li 291*9c5db199SXin Li float charge_full: Last full capacity reached [Ah] 292*9c5db199SXin Li float charge_full_design: Full capacity by design [Ah] 293*9c5db199SXin Li float charge_now: Remaining charge [Ah] 294*9c5db199SXin Li float current_now: Battery discharge rate [A] 295*9c5db199SXin Li int cycle_count: Battery cycle count 296*9c5db199SXin Li float energy: Current battery charge [Wh] 297*9c5db199SXin Li float energy_full: Last full capacity reached [Wh] 298*9c5db199SXin Li float energy_full_design: Full capacity by design [Wh] 299*9c5db199SXin Li float energy_rate: Battery discharge rate [W] 300*9c5db199SXin Li str manufacturer: Battery manufacturer 301*9c5db199SXin Li str model_name: Battery model name 302*9c5db199SXin Li float power_now: Battery discharge rate [W] 303*9c5db199SXin Li int present: Whether battery is present 304*9c5db199SXin Li float remaining_time: Remaining discharging time [h] 305*9c5db199SXin Li str serial_number: Battery serial number 306*9c5db199SXin Li str status: Charging status 307*9c5db199SXin Li float voltage_min_design: Minimum voltage by design [V] 308*9c5db199SXin Li float voltage_max_design: Maximum voltage by design [V] 309*9c5db199SXin Li float voltage_now: Voltage now [V] 310*9c5db199SXin Li """ 311*9c5db199SXin Li 312*9c5db199SXin Li battery_fields = { 313*9c5db199SXin Li 'status': ['status', str], 314*9c5db199SXin Li 'charge_full': ['charge_full', float], 315*9c5db199SXin Li 'charge_full_design': ['charge_full_design', float], 316*9c5db199SXin Li 'charge_now': ['charge_now', float], 317*9c5db199SXin Li 'current_now': ['current_now', float], 318*9c5db199SXin Li 'cycle_count': ['cycle_count', int], 319*9c5db199SXin Li 'voltage_min_design': ['voltage_min_design', float], 320*9c5db199SXin Li 'voltage_max_design': ['voltage_max_design', float], 321*9c5db199SXin Li 'voltage_now': ['voltage_now', float], 322*9c5db199SXin Li 'energy': ['energy_now', float], 323*9c5db199SXin Li 'energy_full': ['energy_full', float], 324*9c5db199SXin Li 'energy_full_design': ['energy_full_design', float], 325*9c5db199SXin Li 'power_now': ['power_now', float], 326*9c5db199SXin Li 'present': ['present', int], 327*9c5db199SXin Li 'manufacturer': ['manufacturer', str], 328*9c5db199SXin Li 'model_name': ['model_name', str], 329*9c5db199SXin Li 'serial_number': ['serial_number', str], 330*9c5db199SXin Li 'energy_rate': ['', ''], 331*9c5db199SXin Li 'remaining_time': ['', ''] 332*9c5db199SXin Li } 333*9c5db199SXin Li 334*9c5db199SXin Li def __init__(self, path=None): 335*9c5db199SXin Li super(BatteryStat, self).__init__(self.battery_fields, path) 336*9c5db199SXin Li self.update() 337*9c5db199SXin Li 338*9c5db199SXin Li 339*9c5db199SXin Li def update(self): 340*9c5db199SXin Li for _ in range(BATTERY_RETRY_COUNT): 341*9c5db199SXin Li try: 342*9c5db199SXin Li self._read_battery() 343*9c5db199SXin Li return 344*9c5db199SXin Li except error.TestError as e: 345*9c5db199SXin Li logging.warning(e) 346*9c5db199SXin Li for field, prop in self.battery_fields.items(): 347*9c5db199SXin Li logging.warning(field + ': ' + repr(getattr(self, field))) 348*9c5db199SXin Li continue 349*9c5db199SXin Li raise error.TestError('Failed to read battery state') 350*9c5db199SXin Li 351*9c5db199SXin Li 352*9c5db199SXin Li def _read_battery(self): 353*9c5db199SXin Li self.read_all_vals() 354*9c5db199SXin Li 355*9c5db199SXin Li if self.charge_full == 0 and self.energy_full != 0: 356*9c5db199SXin Li battery_type = BatteryDataReportType.ENERGY 357*9c5db199SXin Li else: 358*9c5db199SXin Li battery_type = BatteryDataReportType.CHARGE 359*9c5db199SXin Li 360*9c5db199SXin Li if self.voltage_min_design != 0: 361*9c5db199SXin Li voltage_nominal = self.voltage_min_design 362*9c5db199SXin Li else: 363*9c5db199SXin Li voltage_nominal = self.voltage_now 364*9c5db199SXin Li 365*9c5db199SXin Li if voltage_nominal == 0: 366*9c5db199SXin Li raise error.TestError('Failed to determine battery voltage') 367*9c5db199SXin Li 368*9c5db199SXin Li battery_design_full_scale = 1 369*9c5db199SXin Li 370*9c5db199SXin Li # Since charge data is present, calculate parameters based upon 371*9c5db199SXin Li # reported charge data. 372*9c5db199SXin Li if battery_type == BatteryDataReportType.CHARGE: 373*9c5db199SXin Li self.charge_full_design *= battery_design_full_scale 374*9c5db199SXin Li 375*9c5db199SXin Li self.charge_full = self.charge_full / BATTERY_DATA_SCALE 376*9c5db199SXin Li self.charge_full_design = self.charge_full_design / \ 377*9c5db199SXin Li BATTERY_DATA_SCALE 378*9c5db199SXin Li self.charge_now = self.charge_now / BATTERY_DATA_SCALE 379*9c5db199SXin Li 380*9c5db199SXin Li self.current_now = math.fabs(self.current_now) / \ 381*9c5db199SXin Li BATTERY_DATA_SCALE 382*9c5db199SXin Li 383*9c5db199SXin Li self.energy = voltage_nominal * \ 384*9c5db199SXin Li self.charge_now / \ 385*9c5db199SXin Li BATTERY_DATA_SCALE 386*9c5db199SXin Li self.energy_full = voltage_nominal * \ 387*9c5db199SXin Li self.charge_full / \ 388*9c5db199SXin Li BATTERY_DATA_SCALE 389*9c5db199SXin Li self.energy_full_design = voltage_nominal * \ 390*9c5db199SXin Li self.charge_full_design / \ 391*9c5db199SXin Li BATTERY_DATA_SCALE 392*9c5db199SXin Li 393*9c5db199SXin Li # Charge data not present, so calculate parameters based upon 394*9c5db199SXin Li # reported energy data. 395*9c5db199SXin Li elif battery_type == BatteryDataReportType.ENERGY: 396*9c5db199SXin Li self.energy_full_design *= battery_design_full_scale 397*9c5db199SXin Li 398*9c5db199SXin Li self.charge_full = self.energy_full / voltage_nominal 399*9c5db199SXin Li self.charge_full_design = self.energy_full_design / \ 400*9c5db199SXin Li voltage_nominal 401*9c5db199SXin Li self.charge_now = self.energy / voltage_nominal 402*9c5db199SXin Li 403*9c5db199SXin Li # TODO(shawnn): check if power_now can really be reported 404*9c5db199SXin Li # as negative, in the same way current_now can 405*9c5db199SXin Li self.current_now = math.fabs(self.power_now) / \ 406*9c5db199SXin Li voltage_nominal 407*9c5db199SXin Li 408*9c5db199SXin Li self.energy = self.energy / BATTERY_DATA_SCALE 409*9c5db199SXin Li self.energy_full = self.energy_full / BATTERY_DATA_SCALE 410*9c5db199SXin Li self.energy_full_design = self.energy_full_design / \ 411*9c5db199SXin Li BATTERY_DATA_SCALE 412*9c5db199SXin Li 413*9c5db199SXin Li self.voltage_min_design = self.voltage_min_design / \ 414*9c5db199SXin Li BATTERY_DATA_SCALE 415*9c5db199SXin Li self.voltage_max_design = self.voltage_max_design / \ 416*9c5db199SXin Li BATTERY_DATA_SCALE 417*9c5db199SXin Li self.voltage_now = self.voltage_now / \ 418*9c5db199SXin Li BATTERY_DATA_SCALE 419*9c5db199SXin Li voltage_nominal = voltage_nominal / \ 420*9c5db199SXin Li BATTERY_DATA_SCALE 421*9c5db199SXin Li 422*9c5db199SXin Li self.energy_rate = self.voltage_now * self.current_now 423*9c5db199SXin Li 424*9c5db199SXin Li self.remaining_time = 0 425*9c5db199SXin Li if self.current_now and self.energy_rate: 426*9c5db199SXin Li self.remaining_time = self.energy / self.energy_rate 427*9c5db199SXin Li 428*9c5db199SXin Li if self.charge_full > (self.charge_full_design * 1.5): 429*9c5db199SXin Li raise error.TestError('Unreasonable charge_full value') 430*9c5db199SXin Li if self.charge_now > (self.charge_full_design * 1.5): 431*9c5db199SXin Li raise error.TestError('Unreasonable charge_now value') 432*9c5db199SXin Li 433*9c5db199SXin Li 434*9c5db199SXin Liclass LineStatPlaceholder(DevStat): 435*9c5db199SXin Li """ 436*9c5db199SXin Li Placeholder line stat for devices which don't provide power_supply related 437*9c5db199SXin Li sysfs interface. 438*9c5db199SXin Li """ 439*9c5db199SXin Li def __init__(self): 440*9c5db199SXin Li self.online = True 441*9c5db199SXin Li 442*9c5db199SXin Li 443*9c5db199SXin Li def update(self): 444*9c5db199SXin Li pass 445*9c5db199SXin Li 446*9c5db199SXin Liclass LineStat(DevStat): 447*9c5db199SXin Li """ 448*9c5db199SXin Li Power line status. 449*9c5db199SXin Li 450*9c5db199SXin Li Fields: 451*9c5db199SXin Li 452*9c5db199SXin Li bool online: Line power online 453*9c5db199SXin Li """ 454*9c5db199SXin Li 455*9c5db199SXin Li linepower_fields = { 456*9c5db199SXin Li 'is_online': ['online', int], 457*9c5db199SXin Li 'status': ['status', str] 458*9c5db199SXin Li } 459*9c5db199SXin Li 460*9c5db199SXin Li 461*9c5db199SXin Li def __init__(self, path=None): 462*9c5db199SXin Li super(LineStat, self).__init__(self.linepower_fields, path) 463*9c5db199SXin Li logging.debug("line path: %s", path) 464*9c5db199SXin Li self.update() 465*9c5db199SXin Li 466*9c5db199SXin Li 467*9c5db199SXin Li def update(self): 468*9c5db199SXin Li self.read_all_vals() 469*9c5db199SXin Li self.online = self.is_online == 1 470*9c5db199SXin Li 471*9c5db199SXin Li 472*9c5db199SXin Liclass SysStat(object): 473*9c5db199SXin Li """ 474*9c5db199SXin Li System power status for a given host. 475*9c5db199SXin Li 476*9c5db199SXin Li Fields: 477*9c5db199SXin Li 478*9c5db199SXin Li battery: A BatteryStat object. 479*9c5db199SXin Li linepower: A list of LineStat objects. 480*9c5db199SXin Li """ 481*9c5db199SXin Li psu_types = ['Mains', 'USB', 'USB_ACA', 'USB_C', 'USB_CDP', 'USB_DCP', 482*9c5db199SXin Li 'USB_PD', 'USB_PD_DRP', 'Unknown'] 483*9c5db199SXin Li 484*9c5db199SXin Li def __init__(self): 485*9c5db199SXin Li power_supply_path = '/sys/class/power_supply/*' 486*9c5db199SXin Li self.battery = None 487*9c5db199SXin Li self.linepower = [] 488*9c5db199SXin Li self.thermal = None 489*9c5db199SXin Li self.battery_path = None 490*9c5db199SXin Li self.linepower_path = [] 491*9c5db199SXin Li 492*9c5db199SXin Li power_supplies = glob.glob(power_supply_path) 493*9c5db199SXin Li for path in power_supplies: 494*9c5db199SXin Li type_path = os.path.join(path,'type') 495*9c5db199SXin Li if not os.path.exists(type_path): 496*9c5db199SXin Li continue 497*9c5db199SXin Li power_type = utils.read_one_line(type_path) 498*9c5db199SXin Li if power_type == 'Battery': 499*9c5db199SXin Li scope_path = os.path.join(path,'scope') 500*9c5db199SXin Li if (os.path.exists(scope_path) and 501*9c5db199SXin Li utils.read_one_line(scope_path) == 'Device'): 502*9c5db199SXin Li continue 503*9c5db199SXin Li self.battery_path = path 504*9c5db199SXin Li elif power_type in self.psu_types: 505*9c5db199SXin Li self.linepower_path.append(path) 506*9c5db199SXin Li 507*9c5db199SXin Li if not self.battery_path or not self.linepower_path: 508*9c5db199SXin Li logging.warning("System does not provide power sysfs interface") 509*9c5db199SXin Li 510*9c5db199SXin Li self.thermal = ThermalStat() 511*9c5db199SXin Li if self.battery_path: 512*9c5db199SXin Li self.sys_low_batt_p = float(utils.system_output( 513*9c5db199SXin Li 'check_powerd_config --low_battery_shutdown_percent', 514*9c5db199SXin Li ignore_status=True) or 4.0) 515*9c5db199SXin Li 516*9c5db199SXin Li 517*9c5db199SXin Li def refresh(self): 518*9c5db199SXin Li """ 519*9c5db199SXin Li Initialize device power status objects. 520*9c5db199SXin Li """ 521*9c5db199SXin Li self.linepower = [] 522*9c5db199SXin Li 523*9c5db199SXin Li if self.battery_path: 524*9c5db199SXin Li self.battery = BatteryStat(self.battery_path) 525*9c5db199SXin Li 526*9c5db199SXin Li for path in self.linepower_path: 527*9c5db199SXin Li self.linepower.append(LineStat(path)) 528*9c5db199SXin Li if not self.linepower: 529*9c5db199SXin Li self.linepower = [ LineStatPlaceholder() ] 530*9c5db199SXin Li 531*9c5db199SXin Li temp_str = self.thermal.get_temps() 532*9c5db199SXin Li if temp_str: 533*9c5db199SXin Li logging.info('Temperature reading: %s', temp_str) 534*9c5db199SXin Li else: 535*9c5db199SXin Li logging.error('Could not read temperature, skipping.') 536*9c5db199SXin Li 537*9c5db199SXin Li 538*9c5db199SXin Li def on_ac(self): 539*9c5db199SXin Li """ 540*9c5db199SXin Li Returns true if device is currently running from AC power. 541*9c5db199SXin Li """ 542*9c5db199SXin Li on_ac = False 543*9c5db199SXin Li for linepower in self.linepower: 544*9c5db199SXin Li on_ac |= linepower.online 545*9c5db199SXin Li 546*9c5db199SXin Li # Butterfly can incorrectly report AC online for some time after 547*9c5db199SXin Li # unplug. Check battery discharge state to confirm. 548*9c5db199SXin Li if utils.get_board() == 'butterfly': 549*9c5db199SXin Li on_ac &= (not self.battery_discharging()) 550*9c5db199SXin Li return on_ac 551*9c5db199SXin Li 552*9c5db199SXin Li 553*9c5db199SXin Li def battery_charging(self): 554*9c5db199SXin Li """ 555*9c5db199SXin Li Returns true if battery is currently charging or false otherwise. 556*9c5db199SXin Li """ 557*9c5db199SXin Li for linepower in self.linepower: 558*9c5db199SXin Li if linepower.status == 'Charging': 559*9c5db199SXin Li return True 560*9c5db199SXin Li 561*9c5db199SXin Li if not self.battery_path: 562*9c5db199SXin Li logging.warning('Unable to determine battery charge status') 563*9c5db199SXin Li return False 564*9c5db199SXin Li 565*9c5db199SXin Li return self.battery.status.rstrip() == 'Charging' 566*9c5db199SXin Li 567*9c5db199SXin Li 568*9c5db199SXin Li def battery_discharging(self): 569*9c5db199SXin Li """ 570*9c5db199SXin Li Returns true if battery is currently discharging or false otherwise. 571*9c5db199SXin Li """ 572*9c5db199SXin Li if not self.battery_path: 573*9c5db199SXin Li logging.warning('Unable to determine battery discharge status') 574*9c5db199SXin Li return False 575*9c5db199SXin Li 576*9c5db199SXin Li return self.battery.status.rstrip() == 'Discharging' 577*9c5db199SXin Li 578*9c5db199SXin Li def battery_full(self): 579*9c5db199SXin Li """ 580*9c5db199SXin Li Returns true if battery is currently full or false otherwise. 581*9c5db199SXin Li """ 582*9c5db199SXin Li if not self.battery_path: 583*9c5db199SXin Li logging.warning('Unable to determine battery fullness status') 584*9c5db199SXin Li return False 585*9c5db199SXin Li 586*9c5db199SXin Li return self.battery.status.rstrip() == 'Full' 587*9c5db199SXin Li 588*9c5db199SXin Li 589*9c5db199SXin Li def battery_discharge_ok_on_ac(self): 590*9c5db199SXin Li """Returns True if battery is ok to discharge on AC presently. 591*9c5db199SXin Li 592*9c5db199SXin Li some devices cycle between charge & discharge above a certain 593*9c5db199SXin Li SoC. If AC is charging and SoC > 95% we can safely assume that. 594*9c5db199SXin Li """ 595*9c5db199SXin Li return self.battery_charging() and (self.percent_current_charge() > 95) 596*9c5db199SXin Li 597*9c5db199SXin Li 598*9c5db199SXin Li def percent_current_charge(self): 599*9c5db199SXin Li """Returns current charge compare to design capacity in percent. 600*9c5db199SXin Li """ 601*9c5db199SXin Li return self.battery.charge_now * 100 / \ 602*9c5db199SXin Li self.battery.charge_full_design 603*9c5db199SXin Li 604*9c5db199SXin Li 605*9c5db199SXin Li def percent_display_charge(self): 606*9c5db199SXin Li """Returns current display charge in percent. 607*9c5db199SXin Li """ 608*9c5db199SXin Li keyvals = parse_power_supply_info() 609*9c5db199SXin Li return float(keyvals['Battery']['display percentage']) 610*9c5db199SXin Li 611*9c5db199SXin Li 612*9c5db199SXin Li def assert_battery_state(self, percent_initial_charge_min): 613*9c5db199SXin Li """Check initial power configuration state is battery. 614*9c5db199SXin Li 615*9c5db199SXin Li Args: 616*9c5db199SXin Li percent_initial_charge_min: float between 0 -> 1.00 of 617*9c5db199SXin Li percentage of battery that must be remaining. 618*9c5db199SXin Li None|0|False means check not performed. 619*9c5db199SXin Li 620*9c5db199SXin Li Raises: 621*9c5db199SXin Li TestError: if one of battery assertions fails 622*9c5db199SXin Li """ 623*9c5db199SXin Li if self.on_ac(): 624*9c5db199SXin Li raise error.TestError( 625*9c5db199SXin Li 'Running on AC power. Please remove AC power cable.') 626*9c5db199SXin Li 627*9c5db199SXin Li percent_initial_charge = self.percent_current_charge() 628*9c5db199SXin Li 629*9c5db199SXin Li if percent_initial_charge_min and percent_initial_charge < \ 630*9c5db199SXin Li percent_initial_charge_min: 631*9c5db199SXin Li raise error.TestError('Initial charge (%f) less than min (%f)' 632*9c5db199SXin Li % (percent_initial_charge, percent_initial_charge_min)) 633*9c5db199SXin Li 634*9c5db199SXin Li def assert_battery_in_range(self, min_level, max_level): 635*9c5db199SXin Li """Raise a error.TestFail if the battery level is not in range.""" 636*9c5db199SXin Li current_percent = self.percent_display_charge() 637*9c5db199SXin Li if not (min_level <= current_percent <= max_level): 638*9c5db199SXin Li raise error.TestFail('battery must be in range [{}, {}]'.format( 639*9c5db199SXin Li min_level, max_level)) 640*9c5db199SXin Li 641*9c5db199SXin Li def is_low_battery(self, low_batt_margin_p=2.0): 642*9c5db199SXin Li """Returns True if battery current charge is low 643*9c5db199SXin Li 644*9c5db199SXin Li @param low_batt_margin_p: percentage of battery that would be added to 645*9c5db199SXin Li system low battery level. 646*9c5db199SXin Li """ 647*9c5db199SXin Li return (self.battery_discharging() and 648*9c5db199SXin Li self.percent_current_charge() < self.sys_low_batt_p + 649*9c5db199SXin Li low_batt_margin_p) 650*9c5db199SXin Li 651*9c5db199SXin Li 652*9c5db199SXin Lidef get_status(): 653*9c5db199SXin Li """ 654*9c5db199SXin Li Return a new power status object (SysStat). A new power status snapshot 655*9c5db199SXin Li for a given host can be obtained by either calling this routine again and 656*9c5db199SXin Li constructing a new SysStat object, or by using the refresh method of the 657*9c5db199SXin Li SysStat object. 658*9c5db199SXin Li """ 659*9c5db199SXin Li status = SysStat() 660*9c5db199SXin Li status.refresh() 661*9c5db199SXin Li return status 662*9c5db199SXin Li 663*9c5db199SXin Li 664*9c5db199SXin Lidef poll_for_charging_behavior(behavior, timeout): 665*9c5db199SXin Li """ 666*9c5db199SXin Li Wait up to |timeout| seconds for the charging behavior to become |behavior|. 667*9c5db199SXin Li 668*9c5db199SXin Li @param behavior: One of 'ON_AC_AND_CHARGING', 669*9c5db199SXin Li 'ON_AC_AND_NOT_CHARGING', 670*9c5db199SXin Li 'NOT_ON_AC_AND_NOT_CHARGING'. 671*9c5db199SXin Li @param timeout: in seconds. 672*9c5db199SXin Li 673*9c5db199SXin Li @raises: error.TestFail if the behavior does not match in time, or another 674*9c5db199SXin Li exception if something else fails along the way. 675*9c5db199SXin Li """ 676*9c5db199SXin Li ps = get_status() 677*9c5db199SXin Li 678*9c5db199SXin Li def _verify_on_AC_and_charging(): 679*9c5db199SXin Li ps.refresh() 680*9c5db199SXin Li if not ps.on_ac(): 681*9c5db199SXin Li raise error.TestFail('Device is not on AC, but should be') 682*9c5db199SXin Li if not ps.battery_charging(): 683*9c5db199SXin Li raise error.TestFail('Device is not charging, but should be') 684*9c5db199SXin Li return True 685*9c5db199SXin Li 686*9c5db199SXin Li def _verify_on_AC_and_not_charging(): 687*9c5db199SXin Li ps.refresh() 688*9c5db199SXin Li if not ps.on_ac(): 689*9c5db199SXin Li raise error.TestFail('Device is not on AC, but should be') 690*9c5db199SXin Li if ps.battery_charging(): 691*9c5db199SXin Li raise error.TestFail('Device is charging, but should not be') 692*9c5db199SXin Li return True 693*9c5db199SXin Li 694*9c5db199SXin Li def _verify_not_on_AC_and_not_charging(): 695*9c5db199SXin Li ps.refresh() 696*9c5db199SXin Li if ps.on_ac(): 697*9c5db199SXin Li raise error.TestFail('Device is on AC, but should not be') 698*9c5db199SXin Li return True 699*9c5db199SXin Li 700*9c5db199SXin Li poll_functions = { 701*9c5db199SXin Li 'ON_AC_AND_CHARGING' : _verify_on_AC_and_charging, 702*9c5db199SXin Li 'ON_AC_AND_NOT_CHARGING' : _verify_on_AC_and_not_charging, 703*9c5db199SXin Li 'NOT_ON_AC_AND_NOT_CHARGING': _verify_not_on_AC_and_not_charging, 704*9c5db199SXin Li } 705*9c5db199SXin Li poll_for_condition_ex(poll_functions[behavior], 706*9c5db199SXin Li timeout=timeout, 707*9c5db199SXin Li sleep_interval=1) 708*9c5db199SXin Li 709*9c5db199SXin Liclass AbstractStats(object): 710*9c5db199SXin Li """ 711*9c5db199SXin Li Common superclass for measurements of percentages per state over time. 712*9c5db199SXin Li 713*9c5db199SXin Li Public Attributes: 714*9c5db199SXin Li incremental: If False, stats returned are from a single 715*9c5db199SXin Li _read_stats. Otherwise, stats are from the difference between 716*9c5db199SXin Li the current and last refresh. 717*9c5db199SXin Li """ 718*9c5db199SXin Li 719*9c5db199SXin Li @staticmethod 720*9c5db199SXin Li def to_percent(stats): 721*9c5db199SXin Li """ 722*9c5db199SXin Li Turns a dict with absolute time values into a dict with percentages. 723*9c5db199SXin Li """ 724*9c5db199SXin Li total = sum(stats.values()) 725*9c5db199SXin Li if total == 0: 726*9c5db199SXin Li return {k: 0 for k in stats} 727*9c5db199SXin Li return dict((k, v * 100.0 / total) for (k, v) in stats.items()) 728*9c5db199SXin Li 729*9c5db199SXin Li 730*9c5db199SXin Li @staticmethod 731*9c5db199SXin Li def do_diff(new, old): 732*9c5db199SXin Li """ 733*9c5db199SXin Li Returns a dict with value deltas from two dicts with matching keys. 734*9c5db199SXin Li """ 735*9c5db199SXin Li return dict((k, new[k] - old.get(k, 0)) for k in new.keys()) 736*9c5db199SXin Li 737*9c5db199SXin Li 738*9c5db199SXin Li @staticmethod 739*9c5db199SXin Li def format_results_percent(results, name, percent_stats): 740*9c5db199SXin Li """ 741*9c5db199SXin Li Formats autotest result keys to format: 742*9c5db199SXin Li percent_<name>_<key>_time 743*9c5db199SXin Li """ 744*9c5db199SXin Li for key in percent_stats: 745*9c5db199SXin Li results['percent_%s_%s_time' % (name, key)] = percent_stats[key] 746*9c5db199SXin Li 747*9c5db199SXin Li 748*9c5db199SXin Li @staticmethod 749*9c5db199SXin Li def format_results_wavg(results, name, wavg): 750*9c5db199SXin Li """ 751*9c5db199SXin Li Add an autotest result keys to format: wavg_<name> 752*9c5db199SXin Li """ 753*9c5db199SXin Li if wavg is not None: 754*9c5db199SXin Li results['wavg_%s' % (name)] = wavg 755*9c5db199SXin Li 756*9c5db199SXin Li 757*9c5db199SXin Li def __init__(self, name, incremental=True): 758*9c5db199SXin Li self.name = name 759*9c5db199SXin Li self.incremental = incremental 760*9c5db199SXin Li self._stats = self._read_stats() 761*9c5db199SXin Li self._first_stats = self._stats.copy() 762*9c5db199SXin Li 763*9c5db199SXin Li def refresh(self): 764*9c5db199SXin Li """ 765*9c5db199SXin Li Returns dict mapping state names to percentage of time spent in them. 766*9c5db199SXin Li """ 767*9c5db199SXin Li raw_stats = result = self._read_stats() 768*9c5db199SXin Li if self.incremental: 769*9c5db199SXin Li result = self.do_diff(result, self._stats) 770*9c5db199SXin Li self._stats = raw_stats 771*9c5db199SXin Li return self.to_percent(result) 772*9c5db199SXin Li 773*9c5db199SXin Li 774*9c5db199SXin Li def _automatic_weighted_average(self): 775*9c5db199SXin Li """ 776*9c5db199SXin Li Turns a dict with absolute times (or percentages) into a weighted 777*9c5db199SXin Li average value. 778*9c5db199SXin Li """ 779*9c5db199SXin Li stats = self._stats 780*9c5db199SXin Li if self.incremental: 781*9c5db199SXin Li stats = self.do_diff(stats, self._first_stats) 782*9c5db199SXin Li 783*9c5db199SXin Li total = sum(stats.values()) 784*9c5db199SXin Li if total == 0: 785*9c5db199SXin Li return None 786*9c5db199SXin Li 787*9c5db199SXin Li return sum(float(k) * v / total for k, v in stats.items()) 788*9c5db199SXin Li 789*9c5db199SXin Li def _supports_automatic_weighted_average(self): 790*9c5db199SXin Li """ 791*9c5db199SXin Li Override! 792*9c5db199SXin Li 793*9c5db199SXin Li Returns True if stats collected can be automatically converted from 794*9c5db199SXin Li percent distribution to weighted average. False otherwise. 795*9c5db199SXin Li """ 796*9c5db199SXin Li return False 797*9c5db199SXin Li 798*9c5db199SXin Li 799*9c5db199SXin Li def weighted_average(self): 800*9c5db199SXin Li """ 801*9c5db199SXin Li Return weighted average calculated using the automated average method 802*9c5db199SXin Li (if supported) or using a custom method defined by the stat. 803*9c5db199SXin Li """ 804*9c5db199SXin Li if self._supports_automatic_weighted_average(): 805*9c5db199SXin Li return self._automatic_weighted_average() 806*9c5db199SXin Li 807*9c5db199SXin Li return self._weighted_avg_fn() 808*9c5db199SXin Li 809*9c5db199SXin Li 810*9c5db199SXin Li def _weighted_avg_fn(self): 811*9c5db199SXin Li """ 812*9c5db199SXin Li Override! Custom weighted average function. 813*9c5db199SXin Li 814*9c5db199SXin Li Returns weighted average as a single floating point value. 815*9c5db199SXin Li """ 816*9c5db199SXin Li return None 817*9c5db199SXin Li 818*9c5db199SXin Li 819*9c5db199SXin Li def _read_stats(self): 820*9c5db199SXin Li """ 821*9c5db199SXin Li Override! Reads the raw data values that shall be measured into a dict. 822*9c5db199SXin Li """ 823*9c5db199SXin Li raise NotImplementedError('Override _read_stats in the subclass!') 824*9c5db199SXin Li 825*9c5db199SXin Li 826*9c5db199SXin LiCPU_BASE_PATH = '/sys/devices/system/cpu/' 827*9c5db199SXin Li 828*9c5db199SXin Lidef count_all_cpus(): 829*9c5db199SXin Li """ 830*9c5db199SXin Li Return count of cpus on system. 831*9c5db199SXin Li """ 832*9c5db199SXin Li path = '%s/cpu[0-9]*' % CPU_BASE_PATH 833*9c5db199SXin Li return len(glob.glob(path)) 834*9c5db199SXin Li 835*9c5db199SXin Lidef get_online_cpus(): 836*9c5db199SXin Li """ 837*9c5db199SXin Li Return frozenset of integer cpu numbers that are online. 838*9c5db199SXin Li """ 839*9c5db199SXin Li return frozenset(read_cpu_set('/sys/devices/system/cpu/online')) 840*9c5db199SXin Li 841*9c5db199SXin Lidef get_cpus_filepaths_for_suffix(cpus, suffix): 842*9c5db199SXin Li """ 843*9c5db199SXin Li For each cpu in |cpus| check whether |CPU_BASE_PATH|/cpu%d/|suffix| exists. 844*9c5db199SXin Li Return tuple of two lists t: 845*9c5db199SXin Li t[0]: all cpu ids where the condition above holds 846*9c5db199SXin Li t[1]: all full paths where condition above holds. 847*9c5db199SXin Li """ 848*9c5db199SXin Li available_cpus = [] 849*9c5db199SXin Li available_paths = [] 850*9c5db199SXin Li for c in cpus: 851*9c5db199SXin Li c_file_path = os.path.join(CPU_BASE_PATH, 'cpu%d' % c, suffix) 852*9c5db199SXin Li if os.path.exists(c_file_path): 853*9c5db199SXin Li available_cpus.append(c) 854*9c5db199SXin Li available_paths.append(c_file_path) 855*9c5db199SXin Li return (available_cpus, available_paths) 856*9c5db199SXin Li 857*9c5db199SXin Li 858*9c5db199SXin Liclass CPUFreqStatsPState(AbstractStats): 859*9c5db199SXin Li """ 860*9c5db199SXin Li CPU Frequency statistics for intel_pstate 861*9c5db199SXin Li """ 862*9c5db199SXin Li MSR_PLATFORM_INFO = 0xce 863*9c5db199SXin Li MSR_IA32_MPERF = 0xe7 864*9c5db199SXin Li MSR_IA32_APERF = 0xe8 865*9c5db199SXin Li 866*9c5db199SXin Li def __init__(self, cpus=None): 867*9c5db199SXin Li name = 'cpufreq' 868*9c5db199SXin Li if not cpus: 869*9c5db199SXin Li cpus = get_online_cpus() 870*9c5db199SXin Li self._cpus = cpus 871*9c5db199SXin Li 872*9c5db199SXin Li if len(cpus) and len(cpus) < count_all_cpus(): 873*9c5db199SXin Li name = '%s_%s' % (name, '_'.join([str(c) for c in cpus])) 874*9c5db199SXin Li 875*9c5db199SXin Li self._initial_perf = None 876*9c5db199SXin Li self._current_perf = None 877*9c5db199SXin Li 878*9c5db199SXin Li # max_freq is supposed to be the same for all CPUs and remain 879*9c5db199SXin Li # constant throughout. So, we set the entry only once. 880*9c5db199SXin Li # Note that this is max non-turbo frequency, some CPU can run at 881*9c5db199SXin Li # higher turbo frequency in some condition. 882*9c5db199SXin Li platform_info = utils.rdmsr(self.MSR_PLATFORM_INFO) 883*9c5db199SXin Li mul = platform_info >> 8 & 0xff 884*9c5db199SXin Li bclk = utils.get_intel_bclk_khz() 885*9c5db199SXin Li self._max_freq = mul * bclk 886*9c5db199SXin Li 887*9c5db199SXin Li super(CPUFreqStatsPState, self).__init__(name) 888*9c5db199SXin Li 889*9c5db199SXin Li def _read_stats(self): 890*9c5db199SXin Li aperf = 0 891*9c5db199SXin Li mperf = 0 892*9c5db199SXin Li 893*9c5db199SXin Li for cpu in self._cpus: 894*9c5db199SXin Li aperf += utils.rdmsr(self.MSR_IA32_APERF, cpu) 895*9c5db199SXin Li mperf += utils.rdmsr(self.MSR_IA32_MPERF, cpu) 896*9c5db199SXin Li 897*9c5db199SXin Li if not self._initial_perf: 898*9c5db199SXin Li self._initial_perf = (aperf, mperf) 899*9c5db199SXin Li 900*9c5db199SXin Li self._current_perf = (aperf, mperf) 901*9c5db199SXin Li 902*9c5db199SXin Li return {} 903*9c5db199SXin Li 904*9c5db199SXin Li def _weighted_avg_fn(self): 905*9c5db199SXin Li if (self._current_perf 906*9c5db199SXin Li and self._current_perf[1] != self._initial_perf[1]): 907*9c5db199SXin Li # Avg freq = max_freq * aperf_delta / mperf_delta 908*9c5db199SXin Li return self._max_freq * \ 909*9c5db199SXin Li float(self._current_perf[0] - self._initial_perf[0]) / \ 910*9c5db199SXin Li (self._current_perf[1] - self._initial_perf[1]) 911*9c5db199SXin Li return 1.0 912*9c5db199SXin Li 913*9c5db199SXin Li 914*9c5db199SXin Liclass CPUFreqStats(AbstractStats): 915*9c5db199SXin Li """ 916*9c5db199SXin Li CPU Frequency statistics 917*9c5db199SXin Li """ 918*9c5db199SXin Li 919*9c5db199SXin Li def __init__(self, cpus=None): 920*9c5db199SXin Li name = 'cpufreq' 921*9c5db199SXin Li stats_suffix = 'cpufreq/stats/time_in_state' 922*9c5db199SXin Li key_suffix = 'cpufreq/scaling_available_frequencies' 923*9c5db199SXin Li if not cpus: 924*9c5db199SXin Li cpus = get_online_cpus() 925*9c5db199SXin Li _, self._file_paths = get_cpus_filepaths_for_suffix(cpus, stats_suffix) 926*9c5db199SXin Li 927*9c5db199SXin Li if len(cpus) and len(cpus) < count_all_cpus(): 928*9c5db199SXin Li name = '%s_%s' % (name, '_'.join([str(c) for c in cpus])) 929*9c5db199SXin Li self._cpus = cpus 930*9c5db199SXin Li self._available_freqs = set() 931*9c5db199SXin Li 932*9c5db199SXin Li if not self._file_paths: 933*9c5db199SXin Li logging.debug('time_in_state file not found') 934*9c5db199SXin Li 935*9c5db199SXin Li # assumes cpufreq driver for CPU0 is the same as the others. 936*9c5db199SXin Li _, cpufreq_key_paths = get_cpus_filepaths_for_suffix(cpus, key_suffix) 937*9c5db199SXin Li for path in cpufreq_key_paths: 938*9c5db199SXin Li self._available_freqs |= set( 939*9c5db199SXin Li int(x) for x in utils.read_file(path).split()) 940*9c5db199SXin Li 941*9c5db199SXin Li super(CPUFreqStats, self).__init__(name) 942*9c5db199SXin Li 943*9c5db199SXin Li def _read_stats(self): 944*9c5db199SXin Li stats = dict((k, 0) for k in self._available_freqs) 945*9c5db199SXin Li for path in self._file_paths: 946*9c5db199SXin Li if not os.path.exists(path): 947*9c5db199SXin Li logging.debug('%s is not found', path) 948*9c5db199SXin Li continue 949*9c5db199SXin Li 950*9c5db199SXin Li data = utils.read_file(path) 951*9c5db199SXin Li for line in data.splitlines(): 952*9c5db199SXin Li pair = line.split() 953*9c5db199SXin Li freq = int(pair[0]) 954*9c5db199SXin Li timeunits = int(pair[1]) 955*9c5db199SXin Li if freq in stats: 956*9c5db199SXin Li stats[freq] += timeunits 957*9c5db199SXin Li else: 958*9c5db199SXin Li stats[freq] = timeunits 959*9c5db199SXin Li return stats 960*9c5db199SXin Li 961*9c5db199SXin Li def _supports_automatic_weighted_average(self): 962*9c5db199SXin Li return True 963*9c5db199SXin Li 964*9c5db199SXin Li 965*9c5db199SXin Liclass CPUCStateStats(AbstractStats): 966*9c5db199SXin Li """ 967*9c5db199SXin Li Base class for C-state residency statistics 968*9c5db199SXin Li """ 969*9c5db199SXin Li def __init__(self, name, non_c0_stat=''): 970*9c5db199SXin Li self._non_c0_stat = non_c0_stat 971*9c5db199SXin Li super(CPUCStateStats, self).__init__(name) 972*9c5db199SXin Li 973*9c5db199SXin Li 974*9c5db199SXin Li def to_percent(self, stats): 975*9c5db199SXin Li """ 976*9c5db199SXin Li Turns a dict with absolute time values into a dict with percentages. 977*9c5db199SXin Li Ignore the |non_c0_stat_name| which is aggegate stat in the total count. 978*9c5db199SXin Li """ 979*9c5db199SXin Li total = sum(v for k, v in stats.items() if k != self._non_c0_stat) 980*9c5db199SXin Li if total == 0: 981*9c5db199SXin Li return {k: 0 for k in stats} 982*9c5db199SXin Li return {k: v * 100.0 / total for k, v in stats.items()} 983*9c5db199SXin Li 984*9c5db199SXin Li 985*9c5db199SXin Liclass CPUIdleStats(CPUCStateStats): 986*9c5db199SXin Li """ 987*9c5db199SXin Li CPU Idle statistics (refresh() will not work with incremental=False!) 988*9c5db199SXin Li """ 989*9c5db199SXin Li # TODO (snanda): Handle changes in number of c-states due to events such 990*9c5db199SXin Li # as ac <-> battery transitions. 991*9c5db199SXin Li # TODO (snanda): Handle non-S0 states. Time spent in suspend states is 992*9c5db199SXin Li # currently not factored out. 993*9c5db199SXin Li def __init__(self, cpus=None): 994*9c5db199SXin Li name = 'cpuidle' 995*9c5db199SXin Li cpuidle_suffix = 'cpuidle' 996*9c5db199SXin Li if not cpus: 997*9c5db199SXin Li cpus = get_online_cpus() 998*9c5db199SXin Li cpus, self._cpus = get_cpus_filepaths_for_suffix(cpus, cpuidle_suffix) 999*9c5db199SXin Li if len(cpus) and len(cpus) < count_all_cpus(): 1000*9c5db199SXin Li name = '%s_%s' % (name, '_'.join([str(c) for c in cpus])) 1001*9c5db199SXin Li super(CPUIdleStats, self).__init__(name=name, non_c0_stat='non-C0') 1002*9c5db199SXin Li 1003*9c5db199SXin Li 1004*9c5db199SXin Li def _read_stats(self): 1005*9c5db199SXin Li cpuidle_stats = collections.defaultdict(int) 1006*9c5db199SXin Li epoch_usecs = int(time.time() * 1000 * 1000) 1007*9c5db199SXin Li for cpu in self._cpus: 1008*9c5db199SXin Li state_path = os.path.join(cpu, 'state*') 1009*9c5db199SXin Li states = glob.glob(state_path) 1010*9c5db199SXin Li cpuidle_stats['C0'] += epoch_usecs 1011*9c5db199SXin Li 1012*9c5db199SXin Li for state in states: 1013*9c5db199SXin Li name = utils.read_one_line(os.path.join(state, 'name')) 1014*9c5db199SXin Li latency = utils.read_one_line(os.path.join(state, 'latency')) 1015*9c5db199SXin Li 1016*9c5db199SXin Li if not int(latency) and name == 'POLL': 1017*9c5db199SXin Li # C0 state. Kernel stats aren't right, so calculate by 1018*9c5db199SXin Li # subtracting all other states from total time (using epoch 1019*9c5db199SXin Li # timer since we calculate differences in the end anyway). 1020*9c5db199SXin Li # NOTE: Only x86 lists C0 under cpuidle, ARM does not. 1021*9c5db199SXin Li continue 1022*9c5db199SXin Li 1023*9c5db199SXin Li usecs = int(utils.read_one_line(os.path.join(state, 'time'))) 1024*9c5db199SXin Li cpuidle_stats['C0'] -= usecs 1025*9c5db199SXin Li 1026*9c5db199SXin Li if name == '<null>': 1027*9c5db199SXin Li # Kernel race condition that can happen while a new C-state 1028*9c5db199SXin Li # gets added (e.g. AC->battery). Don't know the 'name' of 1029*9c5db199SXin Li # the state yet, but its 'time' would be 0 anyway. 1030*9c5db199SXin Li logging.warning('Read name: <null>, time: %d from %s...' 1031*9c5db199SXin Li 'skipping.', usecs, state) 1032*9c5db199SXin Li continue 1033*9c5db199SXin Li 1034*9c5db199SXin Li cpuidle_stats[name] += usecs 1035*9c5db199SXin Li cpuidle_stats['non-C0'] += usecs 1036*9c5db199SXin Li 1037*9c5db199SXin Li return cpuidle_stats 1038*9c5db199SXin Li 1039*9c5db199SXin Li 1040*9c5db199SXin Liclass CPUPackageStats(CPUCStateStats): 1041*9c5db199SXin Li """ 1042*9c5db199SXin Li Package C-state residency statistics for modern Intel CPUs. 1043*9c5db199SXin Li """ 1044*9c5db199SXin Li 1045*9c5db199SXin Li ATOM = {'C2': 0x3F8, 'C4': 0x3F9, 'C6': 0x3FA} 1046*9c5db199SXin Li NEHALEM = {'C3': 0x3F8, 'C6': 0x3F9, 'C7': 0x3FA} 1047*9c5db199SXin Li SANDY_BRIDGE = {'C2': 0x60D, 'C3': 0x3F8, 'C6': 0x3F9, 'C7': 0x3FA} 1048*9c5db199SXin Li SILVERMONT = {'C6': 0x3FA} 1049*9c5db199SXin Li GOLDMONT = {'C2': 0x60D, 'C3': 0x3F8, 'C6': 0x3F9,'C10': 0x632} 1050*9c5db199SXin Li BROADWELL = {'C2': 0x60D, 'C3': 0x3F8, 'C6': 0x3F9, 'C7': 0x3FA, 1051*9c5db199SXin Li 'C8': 0x630, 'C9': 0x631,'C10': 0x632} 1052*9c5db199SXin Li 1053*9c5db199SXin Li def __init__(self): 1054*9c5db199SXin Li def _get_platform_states(): 1055*9c5db199SXin Li """ 1056*9c5db199SXin Li Helper to decide what set of microarchitecture-specific MSRs to use. 1057*9c5db199SXin Li 1058*9c5db199SXin Li Returns: dict that maps C-state name to MSR address, or None. 1059*9c5db199SXin Li """ 1060*9c5db199SXin Li cpu_uarch = utils.get_intel_cpu_uarch() 1061*9c5db199SXin Li 1062*9c5db199SXin Li return { 1063*9c5db199SXin Li # model groups pulled from Intel SDM, volume 4 1064*9c5db199SXin Li # Group same package cstate using the older uarch name 1065*9c5db199SXin Li # 1066*9c5db199SXin Li # TODO(harry.pan): As the keys represent microarchitecture 1067*9c5db199SXin Li # names, we could consider to rename the PC state groups 1068*9c5db199SXin Li # to avoid ambiguity. 1069*9c5db199SXin Li 'Airmont': self.SILVERMONT, 1070*9c5db199SXin Li 'Atom': self.ATOM, 1071*9c5db199SXin Li 'Broadwell': self.BROADWELL, 1072*9c5db199SXin Li 'Comet Lake': self.BROADWELL, 1073*9c5db199SXin Li 'Goldmont': self.GOLDMONT, 1074*9c5db199SXin Li 'Haswell': self.SANDY_BRIDGE, 1075*9c5db199SXin Li 'Ice Lake': self.BROADWELL, 1076*9c5db199SXin Li 'Ivy Bridge': self.SANDY_BRIDGE, 1077*9c5db199SXin Li 'Ivy Bridge-E': self.SANDY_BRIDGE, 1078*9c5db199SXin Li 'Kaby Lake': self.BROADWELL, 1079*9c5db199SXin Li 'Nehalem': self.NEHALEM, 1080*9c5db199SXin Li 'Sandy Bridge': self.SANDY_BRIDGE, 1081*9c5db199SXin Li 'Silvermont': self.SILVERMONT, 1082*9c5db199SXin Li 'Skylake': self.BROADWELL, 1083*9c5db199SXin Li 'Tiger Lake': self.BROADWELL, 1084*9c5db199SXin Li 'Alder Lake': self.BROADWELL, 1085*9c5db199SXin Li 'Tremont': self.GOLDMONT, 1086*9c5db199SXin Li 'Westmere': self.NEHALEM, 1087*9c5db199SXin Li }.get(cpu_uarch, None) 1088*9c5db199SXin Li 1089*9c5db199SXin Li self._platform_states = _get_platform_states() 1090*9c5db199SXin Li super(CPUPackageStats, self).__init__(name='cpupkg', 1091*9c5db199SXin Li non_c0_stat='non-C0_C1') 1092*9c5db199SXin Li 1093*9c5db199SXin Li 1094*9c5db199SXin Li def _read_stats(self): 1095*9c5db199SXin Li packages = set() 1096*9c5db199SXin Li template = '/sys/devices/system/cpu/cpu%s/topology/physical_package_id' 1097*9c5db199SXin Li if not self._platform_states: 1098*9c5db199SXin Li return {} 1099*9c5db199SXin Li stats = dict((state, 0) for state in self._platform_states) 1100*9c5db199SXin Li stats['C0_C1'] = 0 1101*9c5db199SXin Li stats['non-C0_C1'] = 0 1102*9c5db199SXin Li 1103*9c5db199SXin Li for cpu in os.listdir('/dev/cpu'): 1104*9c5db199SXin Li if not os.path.exists(template % cpu): 1105*9c5db199SXin Li continue 1106*9c5db199SXin Li package = utils.read_one_line(template % cpu) 1107*9c5db199SXin Li if package in packages: 1108*9c5db199SXin Li continue 1109*9c5db199SXin Li packages.add(package) 1110*9c5db199SXin Li 1111*9c5db199SXin Li stats['C0_C1'] += utils.rdmsr(0x10, cpu) # TSC 1112*9c5db199SXin Li for (state, msr) in self._platform_states.items(): 1113*9c5db199SXin Li ticks = utils.rdmsr(msr, cpu) 1114*9c5db199SXin Li stats[state] += ticks 1115*9c5db199SXin Li stats['non-C0_C1'] += ticks 1116*9c5db199SXin Li stats['C0_C1'] -= ticks 1117*9c5db199SXin Li 1118*9c5db199SXin Li return stats 1119*9c5db199SXin Li 1120*9c5db199SXin Li 1121*9c5db199SXin Liclass DevFreqStats(AbstractStats): 1122*9c5db199SXin Li """ 1123*9c5db199SXin Li Devfreq device frequency stats. 1124*9c5db199SXin Li """ 1125*9c5db199SXin Li 1126*9c5db199SXin Li _DIR = '/sys/class/devfreq' 1127*9c5db199SXin Li 1128*9c5db199SXin Li 1129*9c5db199SXin Li def __init__(self, path): 1130*9c5db199SXin Li """Constructs DevFreqStats Object that track frequency stats 1131*9c5db199SXin Li for the path of the given Devfreq device. 1132*9c5db199SXin Li 1133*9c5db199SXin Li The frequencies for devfreq devices are listed in Hz. 1134*9c5db199SXin Li 1135*9c5db199SXin Li Args: 1136*9c5db199SXin Li path: the path to the devfreq device 1137*9c5db199SXin Li 1138*9c5db199SXin Li Example: 1139*9c5db199SXin Li /sys/class/devfreq/dmc 1140*9c5db199SXin Li """ 1141*9c5db199SXin Li self._path = os.path.join(self._DIR, path) 1142*9c5db199SXin Li if not os.path.exists(self._path): 1143*9c5db199SXin Li raise error.TestError( 1144*9c5db199SXin Li 'DevFreqStats: devfreq device does not exist: %s' % 1145*9c5db199SXin Li self._path) 1146*9c5db199SXin Li 1147*9c5db199SXin Li fname = os.path.join(self._path, 'available_frequencies') 1148*9c5db199SXin Li af = utils.read_one_line(fname).strip() 1149*9c5db199SXin Li self._available_freqs = sorted(af.split(), key=int) 1150*9c5db199SXin Li 1151*9c5db199SXin Li super(DevFreqStats, self).__init__(path) 1152*9c5db199SXin Li 1153*9c5db199SXin Li def _read_stats(self): 1154*9c5db199SXin Li stats = dict((freq, 0) for freq in self._available_freqs) 1155*9c5db199SXin Li fname = os.path.join(self._path, 'trans_stat') 1156*9c5db199SXin Li 1157*9c5db199SXin Li with open(fname) as fd: 1158*9c5db199SXin Li # The lines that contain the time in each frequency start on the 3rd 1159*9c5db199SXin Li # line, so skip the first 2 lines. The last line contains the number 1160*9c5db199SXin Li # of transitions, so skip that line too. 1161*9c5db199SXin Li # The time in each frequency is at the end of the line. 1162*9c5db199SXin Li freq_pattern = re.compile(r'\d+(?=:)') 1163*9c5db199SXin Li for line in fd.readlines()[2:-1]: 1164*9c5db199SXin Li freq = freq_pattern.search(line) 1165*9c5db199SXin Li if freq and freq.group() in self._available_freqs: 1166*9c5db199SXin Li stats[freq.group()] = int(line.strip().split()[-1]) 1167*9c5db199SXin Li 1168*9c5db199SXin Li return stats 1169*9c5db199SXin Li 1170*9c5db199SXin Li 1171*9c5db199SXin Liclass GPUFreqStats(AbstractStats): 1172*9c5db199SXin Li """GPU Frequency statistics class. 1173*9c5db199SXin Li 1174*9c5db199SXin Li TODO(tbroch): add stats for other GPUs 1175*9c5db199SXin Li """ 1176*9c5db199SXin Li 1177*9c5db199SXin Li _MALI_DEV = '/sys/class/misc/mali0/device' 1178*9c5db199SXin Li _MALI_EVENTS = ['mali_dvfs:mali_dvfs_set_clock'] 1179*9c5db199SXin Li _MALI_TRACE_CLK_RE = \ 1180*9c5db199SXin Li r'kworker.* (\d+\.\d+): mali_dvfs_set_clock: frequency=(\d+)\d{6}' 1181*9c5db199SXin Li 1182*9c5db199SXin Li _I915_ROOT = '/sys/kernel/debug/dri/0' 1183*9c5db199SXin Li _I915_EVENTS = ['i915:intel_gpu_freq_change'] 1184*9c5db199SXin Li _I915_CLKS_FILES = ['i915_cur_delayinfo', 'i915_frequency_info'] 1185*9c5db199SXin Li _I915_TRACE_CLK_RE = \ 1186*9c5db199SXin Li r'kworker.* (\d+\.\d+): intel_gpu_freq_change: new_freq=(\d+)' 1187*9c5db199SXin Li _I915_CUR_FREQ_RE = r'CAGF:\s+(\d+)MHz' 1188*9c5db199SXin Li _I915_MIN_FREQ_RE = r'Lowest \(RPN\) frequency:\s+(\d+)MHz' 1189*9c5db199SXin Li _I915_MAX_FREQ_RE = r'Max non-overclocked \(RP0\) frequency:\s+(\d+)MHz' 1190*9c5db199SXin Li # There are 6 frequency steps per 100 MHz 1191*9c5db199SXin Li _I915_FREQ_STEPS = [0, 17, 33, 50, 67, 83] 1192*9c5db199SXin Li 1193*9c5db199SXin Li _gpu_type = None 1194*9c5db199SXin Li 1195*9c5db199SXin Li 1196*9c5db199SXin Li def _get_mali_freqs(self): 1197*9c5db199SXin Li """Get mali clocks based on kernel version. 1198*9c5db199SXin Li 1199*9c5db199SXin Li For 3.8-3.18: 1200*9c5db199SXin Li # cat /sys/class/misc/mali0/device/clock 1201*9c5db199SXin Li 100000000 1202*9c5db199SXin Li # cat /sys/class/misc/mali0/device/available_frequencies 1203*9c5db199SXin Li 100000000 1204*9c5db199SXin Li 160000000 1205*9c5db199SXin Li 266000000 1206*9c5db199SXin Li 350000000 1207*9c5db199SXin Li 400000000 1208*9c5db199SXin Li 450000000 1209*9c5db199SXin Li 533000000 1210*9c5db199SXin Li 533000000 1211*9c5db199SXin Li 1212*9c5db199SXin Li For 4.4+: 1213*9c5db199SXin Li Tracked in DevFreqStats 1214*9c5db199SXin Li 1215*9c5db199SXin Li Returns: 1216*9c5db199SXin Li cur_mhz: string of current GPU clock in mhz 1217*9c5db199SXin Li """ 1218*9c5db199SXin Li cur_mhz = None 1219*9c5db199SXin Li fqs = [] 1220*9c5db199SXin Li 1221*9c5db199SXin Li fname = os.path.join(self._MALI_DEV, 'clock') 1222*9c5db199SXin Li if os.path.exists(fname): 1223*9c5db199SXin Li cur_mhz = str(int(int(utils.read_one_line(fname).strip()) / 1e6)) 1224*9c5db199SXin Li fname = os.path.join(self._MALI_DEV, 'available_frequencies') 1225*9c5db199SXin Li with open(fname) as fd: 1226*9c5db199SXin Li for ln in fd.readlines(): 1227*9c5db199SXin Li freq = int(int(ln.strip()) / 1e6) 1228*9c5db199SXin Li fqs.append(str(freq)) 1229*9c5db199SXin Li fqs.sort() 1230*9c5db199SXin Li 1231*9c5db199SXin Li self._freqs = fqs 1232*9c5db199SXin Li return cur_mhz 1233*9c5db199SXin Li 1234*9c5db199SXin Li 1235*9c5db199SXin Li def __init__(self, incremental=False): 1236*9c5db199SXin Li 1237*9c5db199SXin Li 1238*9c5db199SXin Li min_mhz = None 1239*9c5db199SXin Li max_mhz = None 1240*9c5db199SXin Li cur_mhz = None 1241*9c5db199SXin Li events = None 1242*9c5db199SXin Li i915_path = None 1243*9c5db199SXin Li self._freqs = [] 1244*9c5db199SXin Li self._prev_sample = None 1245*9c5db199SXin Li self._trace = None 1246*9c5db199SXin Li 1247*9c5db199SXin Li if os.path.exists(self._MALI_DEV) and \ 1248*9c5db199SXin Li not os.path.exists(os.path.join(self._MALI_DEV, "devfreq")): 1249*9c5db199SXin Li self._set_gpu_type('mali') 1250*9c5db199SXin Li else: 1251*9c5db199SXin Li for file_name in self._I915_CLKS_FILES: 1252*9c5db199SXin Li full_path = os.path.join(self._I915_ROOT, file_name) 1253*9c5db199SXin Li if os.path.exists(full_path): 1254*9c5db199SXin Li self._set_gpu_type('i915') 1255*9c5db199SXin Li i915_path = full_path 1256*9c5db199SXin Li break 1257*9c5db199SXin Li else: 1258*9c5db199SXin Li # We either don't know how to track GPU stats (yet) or the stats 1259*9c5db199SXin Li # are tracked in DevFreqStats. 1260*9c5db199SXin Li self._set_gpu_type(None) 1261*9c5db199SXin Li 1262*9c5db199SXin Li logging.debug("gpu_type is %s", self._gpu_type) 1263*9c5db199SXin Li 1264*9c5db199SXin Li if self._gpu_type is 'mali': 1265*9c5db199SXin Li events = self._MALI_EVENTS 1266*9c5db199SXin Li cur_mhz = self._get_mali_freqs() 1267*9c5db199SXin Li if self._freqs: 1268*9c5db199SXin Li min_mhz = self._freqs[0] 1269*9c5db199SXin Li max_mhz = self._freqs[-1] 1270*9c5db199SXin Li 1271*9c5db199SXin Li elif self._gpu_type is 'i915': 1272*9c5db199SXin Li events = self._I915_EVENTS 1273*9c5db199SXin Li with open(i915_path) as fd: 1274*9c5db199SXin Li for ln in fd.readlines(): 1275*9c5db199SXin Li logging.debug("ln = %s", ln.strip()) 1276*9c5db199SXin Li result = re.findall(self._I915_CUR_FREQ_RE, ln) 1277*9c5db199SXin Li if result: 1278*9c5db199SXin Li cur_mhz = result[0] 1279*9c5db199SXin Li continue 1280*9c5db199SXin Li result = re.findall(self._I915_MIN_FREQ_RE, ln) 1281*9c5db199SXin Li if result: 1282*9c5db199SXin Li min_mhz = result[0] 1283*9c5db199SXin Li continue 1284*9c5db199SXin Li result = re.findall(self._I915_MAX_FREQ_RE, ln) 1285*9c5db199SXin Li if result: 1286*9c5db199SXin Li max_mhz = result[0] 1287*9c5db199SXin Li continue 1288*9c5db199SXin Li if min_mhz and max_mhz: 1289*9c5db199SXin Li real_min_mhz = min(int(min_mhz), int(cur_mhz)) 1290*9c5db199SXin Li for i in range(real_min_mhz, int(max_mhz) + 1): 1291*9c5db199SXin Li if i % 100 in self._I915_FREQ_STEPS: 1292*9c5db199SXin Li self._freqs.append(str(i)) 1293*9c5db199SXin Li 1294*9c5db199SXin Li logging.debug("cur_mhz = %s, min_mhz = %s, max_mhz = %s", cur_mhz, 1295*9c5db199SXin Li min_mhz, max_mhz) 1296*9c5db199SXin Li 1297*9c5db199SXin Li if cur_mhz and min_mhz and max_mhz: 1298*9c5db199SXin Li self._trace = kernel_trace.KernelTrace(events=events) 1299*9c5db199SXin Li 1300*9c5db199SXin Li # Not all platforms or kernel versions support tracing. 1301*9c5db199SXin Li if not self._trace or not self._trace.is_tracing(): 1302*9c5db199SXin Li logging.warning("GPU frequency tracing not enabled.") 1303*9c5db199SXin Li else: 1304*9c5db199SXin Li self._prev_sample = (cur_mhz, self._trace.uptime_secs()) 1305*9c5db199SXin Li logging.debug("Current GPU freq: %s", cur_mhz) 1306*9c5db199SXin Li logging.debug("All GPU freqs: %s", self._freqs) 1307*9c5db199SXin Li 1308*9c5db199SXin Li super(GPUFreqStats, self).__init__('gpufreq', incremental=incremental) 1309*9c5db199SXin Li 1310*9c5db199SXin Li 1311*9c5db199SXin Li @classmethod 1312*9c5db199SXin Li def _set_gpu_type(cls, gpu_type): 1313*9c5db199SXin Li cls._gpu_type = gpu_type 1314*9c5db199SXin Li 1315*9c5db199SXin Li 1316*9c5db199SXin Li def _read_stats(self): 1317*9c5db199SXin Li if self._gpu_type: 1318*9c5db199SXin Li return getattr(self, "_%s_read_stats" % self._gpu_type)() 1319*9c5db199SXin Li return {} 1320*9c5db199SXin Li 1321*9c5db199SXin Li 1322*9c5db199SXin Li def _trace_read_stats(self, regexp): 1323*9c5db199SXin Li """Read GPU stats from kernel trace outputs. 1324*9c5db199SXin Li 1325*9c5db199SXin Li Args: 1326*9c5db199SXin Li regexp: regular expression to match trace output for frequency 1327*9c5db199SXin Li 1328*9c5db199SXin Li Returns: 1329*9c5db199SXin Li Dict with key string in mhz and val float in seconds. 1330*9c5db199SXin Li """ 1331*9c5db199SXin Li if not self._prev_sample: 1332*9c5db199SXin Li return {} 1333*9c5db199SXin Li 1334*9c5db199SXin Li stats = dict((k, 0.0) for k in self._freqs) 1335*9c5db199SXin Li results = self._trace.read(regexp=regexp) 1336*9c5db199SXin Li for (tstamp_str, freq) in results: 1337*9c5db199SXin Li tstamp = float(tstamp_str) 1338*9c5db199SXin Li 1339*9c5db199SXin Li # do not reparse lines in trace buffer 1340*9c5db199SXin Li if tstamp <= self._prev_sample[1]: 1341*9c5db199SXin Li continue 1342*9c5db199SXin Li delta = tstamp - self._prev_sample[1] 1343*9c5db199SXin Li logging.debug("freq:%s tstamp:%f - %f delta:%f", 1344*9c5db199SXin Li self._prev_sample[0], 1345*9c5db199SXin Li tstamp, self._prev_sample[1], 1346*9c5db199SXin Li delta) 1347*9c5db199SXin Li stats[self._prev_sample[0]] += delta 1348*9c5db199SXin Li self._prev_sample = (freq, tstamp) 1349*9c5db199SXin Li 1350*9c5db199SXin Li # Do last record 1351*9c5db199SXin Li delta = self._trace.uptime_secs() - self._prev_sample[1] 1352*9c5db199SXin Li logging.debug("freq:%s tstamp:uptime - %f delta:%f", 1353*9c5db199SXin Li self._prev_sample[0], 1354*9c5db199SXin Li self._prev_sample[1], delta) 1355*9c5db199SXin Li stats[self._prev_sample[0]] += delta 1356*9c5db199SXin Li 1357*9c5db199SXin Li logging.debug("GPU freq percents:%s", stats) 1358*9c5db199SXin Li return stats 1359*9c5db199SXin Li 1360*9c5db199SXin Li 1361*9c5db199SXin Li def _mali_read_stats(self): 1362*9c5db199SXin Li """Read Mali GPU stats 1363*9c5db199SXin Li 1364*9c5db199SXin Li Frequencies are reported in Hz, so use a regex that drops the last 6 1365*9c5db199SXin Li digits. 1366*9c5db199SXin Li 1367*9c5db199SXin Li Output in trace looks like this: 1368*9c5db199SXin Li 1369*9c5db199SXin Li kworker/u:24-5220 [000] .... 81060.329232: mali_dvfs_set_clock: frequency=400 1370*9c5db199SXin Li kworker/u:24-5220 [000] .... 81061.830128: mali_dvfs_set_clock: frequency=350 1371*9c5db199SXin Li 1372*9c5db199SXin Li Returns: 1373*9c5db199SXin Li Dict with frequency in mhz as key and float in seconds for time 1374*9c5db199SXin Li spent at that frequency. 1375*9c5db199SXin Li """ 1376*9c5db199SXin Li return self._trace_read_stats(self._MALI_TRACE_CLK_RE) 1377*9c5db199SXin Li 1378*9c5db199SXin Li 1379*9c5db199SXin Li def _i915_read_stats(self): 1380*9c5db199SXin Li """Read i915 GPU stats. 1381*9c5db199SXin Li 1382*9c5db199SXin Li Output looks like this (kernel >= 3.8): 1383*9c5db199SXin Li 1384*9c5db199SXin Li kworker/u:0-28247 [000] .... 259391.579610: intel_gpu_freq_change: new_freq=400 1385*9c5db199SXin Li kworker/u:0-28247 [000] .... 259391.581797: intel_gpu_freq_change: new_freq=350 1386*9c5db199SXin Li 1387*9c5db199SXin Li Returns: 1388*9c5db199SXin Li Dict with frequency in mhz as key and float in seconds for time 1389*9c5db199SXin Li spent at that frequency. 1390*9c5db199SXin Li """ 1391*9c5db199SXin Li return self._trace_read_stats(self._I915_TRACE_CLK_RE) 1392*9c5db199SXin Li 1393*9c5db199SXin Li 1394*9c5db199SXin Li def _supports_automatic_weighted_average(self): 1395*9c5db199SXin Li return self._gpu_type is not None 1396*9c5db199SXin Li 1397*9c5db199SXin Li 1398*9c5db199SXin Liclass USBSuspendStats(AbstractStats): 1399*9c5db199SXin Li """ 1400*9c5db199SXin Li USB active/suspend statistics (over all devices) 1401*9c5db199SXin Li """ 1402*9c5db199SXin Li # TODO (snanda): handle hot (un)plugging of USB devices 1403*9c5db199SXin Li # TODO (snanda): handle duration counters wraparound 1404*9c5db199SXin Li 1405*9c5db199SXin Li def __init__(self): 1406*9c5db199SXin Li usb_stats_path = '/sys/bus/usb/devices/*/power' 1407*9c5db199SXin Li self._file_paths = glob.glob(usb_stats_path) 1408*9c5db199SXin Li if not self._file_paths: 1409*9c5db199SXin Li logging.debug('USB stats path not found') 1410*9c5db199SXin Li super(USBSuspendStats, self).__init__('usb') 1411*9c5db199SXin Li 1412*9c5db199SXin Li 1413*9c5db199SXin Li def _read_stats(self): 1414*9c5db199SXin Li usb_stats = {'active': 0, 'suspended': 0} 1415*9c5db199SXin Li 1416*9c5db199SXin Li for path in self._file_paths: 1417*9c5db199SXin Li active_duration_path = os.path.join(path, 'active_duration') 1418*9c5db199SXin Li total_duration_path = os.path.join(path, 'connected_duration') 1419*9c5db199SXin Li 1420*9c5db199SXin Li if not os.path.exists(active_duration_path) or \ 1421*9c5db199SXin Li not os.path.exists(total_duration_path): 1422*9c5db199SXin Li logging.debug('duration paths do not exist for: %s', path) 1423*9c5db199SXin Li continue 1424*9c5db199SXin Li 1425*9c5db199SXin Li active = int(utils.read_file(active_duration_path)) 1426*9c5db199SXin Li total = int(utils.read_file(total_duration_path)) 1427*9c5db199SXin Li logging.debug('device %s active for %.2f%%', 1428*9c5db199SXin Li path, active * 100.0 / total) 1429*9c5db199SXin Li 1430*9c5db199SXin Li usb_stats['active'] += active 1431*9c5db199SXin Li usb_stats['suspended'] += total - active 1432*9c5db199SXin Li 1433*9c5db199SXin Li return usb_stats 1434*9c5db199SXin Li 1435*9c5db199SXin Li 1436*9c5db199SXin Lidef read_cpu_set(filename): 1437*9c5db199SXin Li """ 1438*9c5db199SXin Li Parse data of form "0,2-4,9" 1439*9c5db199SXin Li 1440*9c5db199SXin Li Return a set of ints 1441*9c5db199SXin Li """ 1442*9c5db199SXin Li data = utils.read_file(filename) 1443*9c5db199SXin Li ret = set() 1444*9c5db199SXin Li 1445*9c5db199SXin Li for entry in data.split(','): 1446*9c5db199SXin Li entry_data = entry.split('-') 1447*9c5db199SXin Li start = end = int(entry_data[0]) 1448*9c5db199SXin Li if len(entry_data) > 1: 1449*9c5db199SXin Li end = int(entry_data[1]) 1450*9c5db199SXin Li ret |= set(range(start, end + 1)) 1451*9c5db199SXin Li return ret 1452*9c5db199SXin Li 1453*9c5db199SXin Li 1454*9c5db199SXin Lidef get_cpu_sibling_groups(): 1455*9c5db199SXin Li """ 1456*9c5db199SXin Li Get CPU core groups in HMP systems. 1457*9c5db199SXin Li 1458*9c5db199SXin Li In systems with both small core and big core, 1459*9c5db199SXin Li returns groups of small and big sibling groups. 1460*9c5db199SXin Li """ 1461*9c5db199SXin Li siblings_suffix = 'topology/core_siblings_list' 1462*9c5db199SXin Li sibling_groups = [] 1463*9c5db199SXin Li cpus_processed = set() 1464*9c5db199SXin Li cpus, sibling_file_paths = get_cpus_filepaths_for_suffix(get_online_cpus(), 1465*9c5db199SXin Li siblings_suffix) 1466*9c5db199SXin Li for c, siblings_path in zip(cpus, sibling_file_paths): 1467*9c5db199SXin Li if c in cpus_processed: 1468*9c5db199SXin Li # This cpu is already part of a sibling group. Skip. 1469*9c5db199SXin Li continue 1470*9c5db199SXin Li sibling_group = read_cpu_set(siblings_path) 1471*9c5db199SXin Li cpus_processed |= sibling_group 1472*9c5db199SXin Li sibling_groups.append(frozenset(sibling_group)) 1473*9c5db199SXin Li return tuple(sibling_groups) 1474*9c5db199SXin Li 1475*9c5db199SXin Li 1476*9c5db199SXin Lidef get_available_cpu_stats(): 1477*9c5db199SXin Li """Return CPUFreq/CPUIdleStats groups by big-small siblings groups.""" 1478*9c5db199SXin Li ret = [CPUPackageStats()] 1479*9c5db199SXin Li cpu_sibling_groups = get_cpu_sibling_groups() 1480*9c5db199SXin Li 1481*9c5db199SXin Li cpufreq_stat_class = CPUFreqStats 1482*9c5db199SXin Li # assumes cpufreq driver for CPU0 is the same as the others. 1483*9c5db199SXin Li cpufreq_driver = '/sys/devices/system/cpu/cpu0/cpufreq/scaling_driver' 1484*9c5db199SXin Li if (os.path.exists(cpufreq_driver) and 1485*9c5db199SXin Li utils.read_one_line(cpufreq_driver) == 'intel_pstate'): 1486*9c5db199SXin Li logging.debug('intel_pstate driver active') 1487*9c5db199SXin Li cpufreq_stat_class = CPUFreqStatsPState 1488*9c5db199SXin Li 1489*9c5db199SXin Li if not cpu_sibling_groups: 1490*9c5db199SXin Li ret.append(cpufreq_stat_class()) 1491*9c5db199SXin Li ret.append(CPUIdleStats()) 1492*9c5db199SXin Li for cpu_group in cpu_sibling_groups: 1493*9c5db199SXin Li ret.append(cpufreq_stat_class(cpu_group)) 1494*9c5db199SXin Li ret.append(CPUIdleStats(cpu_group)) 1495*9c5db199SXin Li if has_rc6_support(): 1496*9c5db199SXin Li ret.append(GPURC6Stats()) 1497*9c5db199SXin Li return ret 1498*9c5db199SXin Li 1499*9c5db199SXin Li 1500*9c5db199SXin Liclass StatoMatic(object): 1501*9c5db199SXin Li """Class to aggregate and monitor a bunch of power related statistics.""" 1502*9c5db199SXin Li def __init__(self): 1503*9c5db199SXin Li self._start_uptime_secs = kernel_trace.KernelTrace.uptime_secs() 1504*9c5db199SXin Li self._astats = [USBSuspendStats(), GPUFreqStats(incremental=False)] 1505*9c5db199SXin Li self._astats.extend(get_available_cpu_stats()) 1506*9c5db199SXin Li if os.path.isdir(DevFreqStats._DIR): 1507*9c5db199SXin Li self._astats.extend([DevFreqStats(f) for f in \ 1508*9c5db199SXin Li os.listdir(DevFreqStats._DIR)]) 1509*9c5db199SXin Li 1510*9c5db199SXin Li self._disk = DiskStateLogger() 1511*9c5db199SXin Li self._disk.start() 1512*9c5db199SXin Li 1513*9c5db199SXin Li 1514*9c5db199SXin Li def publish(self): 1515*9c5db199SXin Li """Publishes results of various statistics gathered. 1516*9c5db199SXin Li 1517*9c5db199SXin Li Returns: 1518*9c5db199SXin Li dict with 1519*9c5db199SXin Li key = string 'percent_<name>_<key>_time' 1520*9c5db199SXin Li value = float in percent 1521*9c5db199SXin Li """ 1522*9c5db199SXin Li results = {} 1523*9c5db199SXin Li tot_secs = kernel_trace.KernelTrace.uptime_secs() - \ 1524*9c5db199SXin Li self._start_uptime_secs 1525*9c5db199SXin Li for stat_obj in self._astats: 1526*9c5db199SXin Li percent_stats = stat_obj.refresh() 1527*9c5db199SXin Li logging.debug("pstats = %s", percent_stats) 1528*9c5db199SXin Li if stat_obj.name is 'gpu': 1529*9c5db199SXin Li # TODO(tbroch) remove this once GPU freq stats have proved 1530*9c5db199SXin Li # reliable 1531*9c5db199SXin Li stats_secs = sum(stat_obj._stats.values()) 1532*9c5db199SXin Li if stats_secs < (tot_secs * 0.9) or \ 1533*9c5db199SXin Li stats_secs > (tot_secs * 1.1): 1534*9c5db199SXin Li logging.warning('%s stats dont look right. Not publishing.', 1535*9c5db199SXin Li stat_obj.name) 1536*9c5db199SXin Li continue 1537*9c5db199SXin Li new_res = {} 1538*9c5db199SXin Li AbstractStats.format_results_percent(new_res, stat_obj.name, 1539*9c5db199SXin Li percent_stats) 1540*9c5db199SXin Li wavg = stat_obj.weighted_average() 1541*9c5db199SXin Li if wavg: 1542*9c5db199SXin Li AbstractStats.format_results_wavg(new_res, stat_obj.name, wavg) 1543*9c5db199SXin Li 1544*9c5db199SXin Li results.update(new_res) 1545*9c5db199SXin Li 1546*9c5db199SXin Li new_res = {} 1547*9c5db199SXin Li if self._disk.get_error(): 1548*9c5db199SXin Li new_res['disk_logging_error'] = str(self._disk.get_error()) 1549*9c5db199SXin Li else: 1550*9c5db199SXin Li AbstractStats.format_results_percent(new_res, 'disk', 1551*9c5db199SXin Li self._disk.result()) 1552*9c5db199SXin Li results.update(new_res) 1553*9c5db199SXin Li 1554*9c5db199SXin Li return results 1555*9c5db199SXin Li 1556*9c5db199SXin Li 1557*9c5db199SXin Liclass PowerMeasurement(object): 1558*9c5db199SXin Li """Class to measure power. 1559*9c5db199SXin Li 1560*9c5db199SXin Li Public attributes: 1561*9c5db199SXin Li domain: String name of the power domain being measured. Example is 1562*9c5db199SXin Li 'system' for total system power 1563*9c5db199SXin Li 1564*9c5db199SXin Li Public methods: 1565*9c5db199SXin Li refresh: Performs any power/energy sampling and calculation and returns 1566*9c5db199SXin Li power as float in watts. This method MUST be implemented in 1567*9c5db199SXin Li subclass. 1568*9c5db199SXin Li """ 1569*9c5db199SXin Li 1570*9c5db199SXin Li def __init__(self, domain): 1571*9c5db199SXin Li """Constructor.""" 1572*9c5db199SXin Li self.domain = domain 1573*9c5db199SXin Li 1574*9c5db199SXin Li 1575*9c5db199SXin Li def refresh(self): 1576*9c5db199SXin Li """Performs any power/energy sampling and calculation. 1577*9c5db199SXin Li 1578*9c5db199SXin Li MUST be implemented in subclass 1579*9c5db199SXin Li 1580*9c5db199SXin Li Returns: 1581*9c5db199SXin Li float, power in watts. 1582*9c5db199SXin Li """ 1583*9c5db199SXin Li raise NotImplementedError("'refresh' method should be implemented in " 1584*9c5db199SXin Li "subclass.") 1585*9c5db199SXin Li 1586*9c5db199SXin Li 1587*9c5db199SXin Lidef parse_power_supply_info(): 1588*9c5db199SXin Li """Parses power_supply_info command output. 1589*9c5db199SXin Li 1590*9c5db199SXin Li Command output from power_manager ( tools/power_supply_info.cc ) looks like 1591*9c5db199SXin Li this: 1592*9c5db199SXin Li 1593*9c5db199SXin Li Device: Line Power 1594*9c5db199SXin Li path: /sys/class/power_supply/cros_ec-charger 1595*9c5db199SXin Li ... 1596*9c5db199SXin Li Device: Battery 1597*9c5db199SXin Li path: /sys/class/power_supply/sbs-9-000b 1598*9c5db199SXin Li ... 1599*9c5db199SXin Li 1600*9c5db199SXin Li """ 1601*9c5db199SXin Li rv = collections.defaultdict(dict) 1602*9c5db199SXin Li dev = None 1603*9c5db199SXin Li for ln in utils.system_output('power_supply_info').splitlines(): 1604*9c5db199SXin Li logging.debug("psu: %s", ln) 1605*9c5db199SXin Li result = re.findall(r'^Device:\s+(.*)', ln) 1606*9c5db199SXin Li if result: 1607*9c5db199SXin Li dev = result[0] 1608*9c5db199SXin Li continue 1609*9c5db199SXin Li result = re.findall(r'\s+(.+):\s+(.+)', ln) 1610*9c5db199SXin Li if result and dev: 1611*9c5db199SXin Li kname = re.findall(r'(.*)\s+\(\w+\)', result[0][0]) 1612*9c5db199SXin Li if kname: 1613*9c5db199SXin Li rv[dev][kname[0]] = result[0][1] 1614*9c5db199SXin Li else: 1615*9c5db199SXin Li rv[dev][result[0][0]] = result[0][1] 1616*9c5db199SXin Li 1617*9c5db199SXin Li return rv 1618*9c5db199SXin Li 1619*9c5db199SXin Li 1620*9c5db199SXin Liclass SystemPower(PowerMeasurement): 1621*9c5db199SXin Li """Class to measure system power. 1622*9c5db199SXin Li 1623*9c5db199SXin Li TODO(tbroch): This class provides a subset of functionality in BatteryStat 1624*9c5db199SXin Li in hopes of minimizing power draw. Investigate whether its really 1625*9c5db199SXin Li significant and if not, deprecate. 1626*9c5db199SXin Li 1627*9c5db199SXin Li Private Attributes: 1628*9c5db199SXin Li _voltage_file: path to retrieve voltage in uvolts 1629*9c5db199SXin Li _current_file: path to retrieve current in uamps 1630*9c5db199SXin Li """ 1631*9c5db199SXin Li 1632*9c5db199SXin Li def __init__(self, battery_dir): 1633*9c5db199SXin Li """Constructor. 1634*9c5db199SXin Li 1635*9c5db199SXin Li Args: 1636*9c5db199SXin Li battery_dir: path to dir containing the files to probe and log. 1637*9c5db199SXin Li usually something like /sys/class/power_supply/BAT0/ 1638*9c5db199SXin Li """ 1639*9c5db199SXin Li super(SystemPower, self).__init__('system') 1640*9c5db199SXin Li # Files to log voltage and current from 1641*9c5db199SXin Li self._voltage_file = os.path.join(battery_dir, 'voltage_now') 1642*9c5db199SXin Li self._current_file = os.path.join(battery_dir, 'current_now') 1643*9c5db199SXin Li 1644*9c5db199SXin Li 1645*9c5db199SXin Li def refresh(self): 1646*9c5db199SXin Li """refresh method. 1647*9c5db199SXin Li 1648*9c5db199SXin Li See superclass PowerMeasurement for details. 1649*9c5db199SXin Li """ 1650*9c5db199SXin Li keyvals = parse_power_supply_info() 1651*9c5db199SXin Li return float(keyvals['Battery']['energy rate']) 1652*9c5db199SXin Li 1653*9c5db199SXin Li 1654*9c5db199SXin Liclass BatteryStateOfCharge(PowerMeasurement): 1655*9c5db199SXin Li """Class for logging battery state of charge.""" 1656*9c5db199SXin Li 1657*9c5db199SXin Li def __init__(self): 1658*9c5db199SXin Li """Constructor.""" 1659*9c5db199SXin Li super(BatteryStateOfCharge, self).__init__('battery_soc') 1660*9c5db199SXin Li 1661*9c5db199SXin Li def refresh(self): 1662*9c5db199SXin Li """refresh method. 1663*9c5db199SXin Li 1664*9c5db199SXin Li See superclass PowerMeasurement for details. 1665*9c5db199SXin Li """ 1666*9c5db199SXin Li keyvals = parse_power_supply_info() 1667*9c5db199SXin Li return float(keyvals['Battery']['percentage']) 1668*9c5db199SXin Li 1669*9c5db199SXin Li 1670*9c5db199SXin Liclass CheckpointLogger(object): 1671*9c5db199SXin Li """Class to log checkpoint data. 1672*9c5db199SXin Li 1673*9c5db199SXin Li Public attributes: 1674*9c5db199SXin Li checkpoint_data: dictionary of (tname, tlist). 1675*9c5db199SXin Li tname: String of testname associated with these time intervals 1676*9c5db199SXin Li tlist: list of tuples. Tuple contains: 1677*9c5db199SXin Li tstart: Float of time when subtest started 1678*9c5db199SXin Li tend: Float of time when subtest ended 1679*9c5db199SXin Li 1680*9c5db199SXin Li Public methods: 1681*9c5db199SXin Li start: records a start timestamp 1682*9c5db199SXin Li checkpoint 1683*9c5db199SXin Li checkblock 1684*9c5db199SXin Li save_checkpoint_data 1685*9c5db199SXin Li load_checkpoint_data 1686*9c5db199SXin Li 1687*9c5db199SXin Li Static methods: 1688*9c5db199SXin Li load_checkpoint_data_static 1689*9c5db199SXin Li 1690*9c5db199SXin Li Private attributes: 1691*9c5db199SXin Li _start_time: start timestamp for checkpoint logger 1692*9c5db199SXin Li """ 1693*9c5db199SXin Li 1694*9c5db199SXin Li def __init__(self): 1695*9c5db199SXin Li self.checkpoint_data = collections.defaultdict(list) 1696*9c5db199SXin Li self.start() 1697*9c5db199SXin Li 1698*9c5db199SXin Li # If multiple MeasurementLoggers call start() on the same CheckpointLogger, 1699*9c5db199SXin Li # the latest one will register start time. 1700*9c5db199SXin Li def start(self): 1701*9c5db199SXin Li """Set start time for CheckpointLogger.""" 1702*9c5db199SXin Li self._start_time = time.time() 1703*9c5db199SXin Li 1704*9c5db199SXin Li @contextlib.contextmanager 1705*9c5db199SXin Li def checkblock(self, tname=''): 1706*9c5db199SXin Li """Check point for the following block with test tname. 1707*9c5db199SXin Li 1708*9c5db199SXin Li Args: 1709*9c5db199SXin Li tname: String of testname associated with this time interval 1710*9c5db199SXin Li """ 1711*9c5db199SXin Li start_time = time.time() 1712*9c5db199SXin Li yield 1713*9c5db199SXin Li self.checkpoint(tname, start_time) 1714*9c5db199SXin Li 1715*9c5db199SXin Li def checkpoint(self, tname='', tstart=None, tend=None): 1716*9c5db199SXin Li """Check point the times in seconds associated with test tname. 1717*9c5db199SXin Li 1718*9c5db199SXin Li Args: 1719*9c5db199SXin Li tname: String of testname associated with this time interval 1720*9c5db199SXin Li tstart: Float in seconds of when tname test started. Should be based 1721*9c5db199SXin Li off time.time(). If None, use start timestamp for the checkpoint 1722*9c5db199SXin Li logger. 1723*9c5db199SXin Li tend: Float in seconds of when tname test ended. Should be based 1724*9c5db199SXin Li off time.time(). If None, then value computed in the method. 1725*9c5db199SXin Li """ 1726*9c5db199SXin Li if not tstart and self._start_time: 1727*9c5db199SXin Li tstart = self._start_time 1728*9c5db199SXin Li if not tend: 1729*9c5db199SXin Li tend = time.time() 1730*9c5db199SXin Li self.checkpoint_data[tname].append((tstart, tend)) 1731*9c5db199SXin Li logging.info('Finished test "%s" between timestamps [%s, %s]', 1732*9c5db199SXin Li tname, tstart, tend) 1733*9c5db199SXin Li 1734*9c5db199SXin Li def convert_relative(self, start_time=None): 1735*9c5db199SXin Li """Convert data from power_status.CheckpointLogger object to relative 1736*9c5db199SXin Li checkpoint data dictionary. Timestamps are converted to time in seconds 1737*9c5db199SXin Li since the test started. 1738*9c5db199SXin Li 1739*9c5db199SXin Li Args: 1740*9c5db199SXin Li start_time: Float in seconds of the desired start time reference. 1741*9c5db199SXin Li Should be based off time.time(). If None, use start timestamp 1742*9c5db199SXin Li for the checkpoint logger. 1743*9c5db199SXin Li """ 1744*9c5db199SXin Li if start_time is None: 1745*9c5db199SXin Li start_time = self._start_time 1746*9c5db199SXin Li 1747*9c5db199SXin Li checkpoint_dict = {} 1748*9c5db199SXin Li for tname, tlist in self.checkpoint_data.items(): 1749*9c5db199SXin Li checkpoint_dict[tname] = [(tstart - start_time, tend - start_time) 1750*9c5db199SXin Li for tstart, tend in tlist] 1751*9c5db199SXin Li 1752*9c5db199SXin Li return checkpoint_dict 1753*9c5db199SXin Li 1754*9c5db199SXin Li def save_checkpoint_data(self, resultsdir, fname=CHECKPOINT_LOG_DEFAULT_FNAME): 1755*9c5db199SXin Li """Save checkpoint data. 1756*9c5db199SXin Li 1757*9c5db199SXin Li Args: 1758*9c5db199SXin Li resultsdir: String, directory to write results to 1759*9c5db199SXin Li fname: String, name of file to write results to 1760*9c5db199SXin Li """ 1761*9c5db199SXin Li fname = os.path.join(resultsdir, fname) 1762*9c5db199SXin Li with open(fname, 'wt') as f: 1763*9c5db199SXin Li json.dump(self.checkpoint_data, f, indent=4, separators=(',', ': ')) 1764*9c5db199SXin Li 1765*9c5db199SXin Li def load_checkpoint_data(self, resultsdir, 1766*9c5db199SXin Li fname=CHECKPOINT_LOG_DEFAULT_FNAME): 1767*9c5db199SXin Li """Load checkpoint data. 1768*9c5db199SXin Li 1769*9c5db199SXin Li Args: 1770*9c5db199SXin Li resultsdir: String, directory to load results from 1771*9c5db199SXin Li fname: String, name of file to load results from 1772*9c5db199SXin Li """ 1773*9c5db199SXin Li fname = os.path.join(resultsdir, fname) 1774*9c5db199SXin Li try: 1775*9c5db199SXin Li with open(fname, 'r') as f: 1776*9c5db199SXin Li self.checkpoint_data = json.load(f, 1777*9c5db199SXin Li object_hook=to_checkpoint_data) 1778*9c5db199SXin Li # Set start time to the earliest start timestamp in file. 1779*9c5db199SXin Li self._start_time = min( 1780*9c5db199SXin Li ts_pair[0] 1781*9c5db199SXin Li for ts_pair in itertools.chain.from_iterable( 1782*9c5db199SXin Li list(self.checkpoint_data.values()))) 1783*9c5db199SXin Li except Exception as exc: 1784*9c5db199SXin Li logging.warning('Failed to load checkpoint data from json file %s, ' 1785*9c5db199SXin Li 'see exception: %s', fname, exc) 1786*9c5db199SXin Li 1787*9c5db199SXin Li @staticmethod 1788*9c5db199SXin Li def load_checkpoint_data_static(resultsdir, 1789*9c5db199SXin Li fname=CHECKPOINT_LOG_DEFAULT_FNAME): 1790*9c5db199SXin Li """Load checkpoint data. 1791*9c5db199SXin Li 1792*9c5db199SXin Li Args: 1793*9c5db199SXin Li resultsdir: String, directory to load results from 1794*9c5db199SXin Li fname: String, name of file to load results from 1795*9c5db199SXin Li """ 1796*9c5db199SXin Li fname = os.path.join(resultsdir, fname) 1797*9c5db199SXin Li with open(fname, 'r') as f: 1798*9c5db199SXin Li checkpoint_data = json.load(f) 1799*9c5db199SXin Li return checkpoint_data 1800*9c5db199SXin Li 1801*9c5db199SXin Li 1802*9c5db199SXin Lidef to_checkpoint_data(json_dict): 1803*9c5db199SXin Li """Helper method to translate json object into CheckpointLogger format. 1804*9c5db199SXin Li 1805*9c5db199SXin Li Args: 1806*9c5db199SXin Li json_dict: a json object in the format of python dict 1807*9c5db199SXin Li Returns: 1808*9c5db199SXin Li a defaultdict in CheckpointLogger data format 1809*9c5db199SXin Li """ 1810*9c5db199SXin Li checkpoint_data = collections.defaultdict(list) 1811*9c5db199SXin Li for tname, tlist in json_dict.items(): 1812*9c5db199SXin Li checkpoint_data[tname].extend([tuple(ts_pair) for ts_pair in tlist]) 1813*9c5db199SXin Li return checkpoint_data 1814*9c5db199SXin Li 1815*9c5db199SXin Li 1816*9c5db199SXin Lidef get_checkpoint_logger_from_file(resultsdir, 1817*9c5db199SXin Li fname=CHECKPOINT_LOG_DEFAULT_FNAME): 1818*9c5db199SXin Li """Create a CheckpointLogger and load checkpoint data from file. 1819*9c5db199SXin Li 1820*9c5db199SXin Li Args: 1821*9c5db199SXin Li resultsdir: String, directory to load results from 1822*9c5db199SXin Li fname: String, name of file to load results from 1823*9c5db199SXin Li Returns: 1824*9c5db199SXin Li CheckpointLogger with data from file 1825*9c5db199SXin Li """ 1826*9c5db199SXin Li checkpoint_logger = CheckpointLogger() 1827*9c5db199SXin Li checkpoint_logger.load_checkpoint_data(resultsdir, fname) 1828*9c5db199SXin Li return checkpoint_logger 1829*9c5db199SXin Li 1830*9c5db199SXin Li 1831*9c5db199SXin Liclass MeasurementLogger(threading.Thread): 1832*9c5db199SXin Li """A thread that logs measurement readings. 1833*9c5db199SXin Li 1834*9c5db199SXin Li Example code snippet: 1835*9c5db199SXin Li my_logger = MeasurementLogger([Measurement1, Measurement2]) 1836*9c5db199SXin Li my_logger.start() 1837*9c5db199SXin Li for testname in tests: 1838*9c5db199SXin Li # Option 1: use checkblock 1839*9c5db199SXin Li with my_logger.checkblock(testname): 1840*9c5db199SXin Li # run the test method for testname 1841*9c5db199SXin Li 1842*9c5db199SXin Li # Option 2: use checkpoint 1843*9c5db199SXin Li start_time = time.time() 1844*9c5db199SXin Li # run the test method for testname 1845*9c5db199SXin Li my_logger.checkpoint(testname, start_time) 1846*9c5db199SXin Li 1847*9c5db199SXin Li keyvals = my_logger.calc() 1848*9c5db199SXin Li 1849*9c5db199SXin Li or using CheckpointLogger: 1850*9c5db199SXin Li checkpoint_logger = CheckpointLogger() 1851*9c5db199SXin Li my_logger = MeasurementLogger([Measurement1, Measurement2], 1852*9c5db199SXin Li checkpoint_logger) 1853*9c5db199SXin Li my_logger.start() 1854*9c5db199SXin Li for testname in tests: 1855*9c5db199SXin Li # Option 1: use checkblock 1856*9c5db199SXin Li with checkpoint_logger.checkblock(testname): 1857*9c5db199SXin Li # run the test method for testname 1858*9c5db199SXin Li 1859*9c5db199SXin Li # Option 2: use checkpoint 1860*9c5db199SXin Li start_time = time.time() 1861*9c5db199SXin Li # run the test method for testname 1862*9c5db199SXin Li checkpoint_logger.checkpoint(testname, start_time) 1863*9c5db199SXin Li 1864*9c5db199SXin Li keyvals = my_logger.calc() 1865*9c5db199SXin Li 1866*9c5db199SXin Li Public attributes: 1867*9c5db199SXin Li seconds_period: float, probing interval in seconds. 1868*9c5db199SXin Li readings: list of lists of floats of measurements. 1869*9c5db199SXin Li times: list of floats of time (since Epoch) of when measurements 1870*9c5db199SXin Li occurred. len(time) == len(readings). 1871*9c5db199SXin Li done: flag to stop the logger. 1872*9c5db199SXin Li domains: list of domain strings being measured 1873*9c5db199SXin Li 1874*9c5db199SXin Li Public methods: 1875*9c5db199SXin Li run: launches the thread to gather measurements 1876*9c5db199SXin Li refresh: perform data samplings for every measurements 1877*9c5db199SXin Li calc: calculates 1878*9c5db199SXin Li save_results: 1879*9c5db199SXin Li 1880*9c5db199SXin Li Private attributes: 1881*9c5db199SXin Li _measurements: list of Measurement objects to be sampled. 1882*9c5db199SXin Li _checkpoint_data: dictionary of (tname, tlist). 1883*9c5db199SXin Li tname: String of testname associated with these time intervals 1884*9c5db199SXin Li tlist: list of tuples. Tuple contains: 1885*9c5db199SXin Li tstart: Float of time when subtest started 1886*9c5db199SXin Li tend: Float of time when subtest ended 1887*9c5db199SXin Li _results: list of results tuples. Tuple contains: 1888*9c5db199SXin Li prefix: String of subtest 1889*9c5db199SXin Li mean: Float of mean in watts 1890*9c5db199SXin Li std: Float of standard deviation of measurements 1891*9c5db199SXin Li tstart: Float of time when subtest started 1892*9c5db199SXin Li tend: Float of time when subtest ended 1893*9c5db199SXin Li """ 1894*9c5db199SXin Li def __init__(self, measurements, seconds_period=1.0, checkpoint_logger=None): 1895*9c5db199SXin Li """Initialize a logger. 1896*9c5db199SXin Li 1897*9c5db199SXin Li Args: 1898*9c5db199SXin Li _measurements: list of Measurement objects to be sampled. 1899*9c5db199SXin Li seconds_period: float, probing interval in seconds. Default 1.0 1900*9c5db199SXin Li """ 1901*9c5db199SXin Li threading.Thread.__init__(self) 1902*9c5db199SXin Li 1903*9c5db199SXin Li self.seconds_period = seconds_period 1904*9c5db199SXin Li 1905*9c5db199SXin Li self.readings = [] 1906*9c5db199SXin Li self.times = [] 1907*9c5db199SXin Li 1908*9c5db199SXin Li self._measurements = measurements 1909*9c5db199SXin Li self.domains = [meas.domain for meas in self._measurements] 1910*9c5db199SXin Li 1911*9c5db199SXin Li self._checkpoint_logger = \ 1912*9c5db199SXin Li checkpoint_logger if checkpoint_logger else CheckpointLogger() 1913*9c5db199SXin Li 1914*9c5db199SXin Li self.done = False 1915*9c5db199SXin Li 1916*9c5db199SXin Li def start(self): 1917*9c5db199SXin Li self._checkpoint_logger.start() 1918*9c5db199SXin Li super(MeasurementLogger, self).start() 1919*9c5db199SXin Li 1920*9c5db199SXin Li def refresh(self): 1921*9c5db199SXin Li """Perform data samplings for every measurements. 1922*9c5db199SXin Li 1923*9c5db199SXin Li Returns: 1924*9c5db199SXin Li list of sampled data for every measurements. 1925*9c5db199SXin Li """ 1926*9c5db199SXin Li return [meas.refresh() for meas in self._measurements] 1927*9c5db199SXin Li 1928*9c5db199SXin Li def run(self): 1929*9c5db199SXin Li """Threads run method.""" 1930*9c5db199SXin Li loop = 1 1931*9c5db199SXin Li start_time = time.time() 1932*9c5db199SXin Li time.sleep(self.seconds_period) 1933*9c5db199SXin Li while(not self.done): 1934*9c5db199SXin Li # TODO (dbasehore): We probably need proper locking in this file 1935*9c5db199SXin Li # since there have been race conditions with modifying and accessing 1936*9c5db199SXin Li # data. 1937*9c5db199SXin Li self.readings.append(self.refresh()) 1938*9c5db199SXin Li current_time = time.time() 1939*9c5db199SXin Li self.times.append(current_time) 1940*9c5db199SXin Li loop += 1 1941*9c5db199SXin Li next_measurement_time = start_time + loop * self.seconds_period 1942*9c5db199SXin Li time.sleep(next_measurement_time - current_time) 1943*9c5db199SXin Li 1944*9c5db199SXin Li @contextlib.contextmanager 1945*9c5db199SXin Li def checkblock(self, tname=''): 1946*9c5db199SXin Li """Check point for the following block with test tname. 1947*9c5db199SXin Li 1948*9c5db199SXin Li Args: 1949*9c5db199SXin Li tname: String of testname associated with this time interval 1950*9c5db199SXin Li """ 1951*9c5db199SXin Li start_time = time.time() 1952*9c5db199SXin Li yield 1953*9c5db199SXin Li self.checkpoint(tname, start_time) 1954*9c5db199SXin Li 1955*9c5db199SXin Li def checkpoint(self, tname='', tstart=None, tend=None): 1956*9c5db199SXin Li """Just a thin method calling the CheckpointLogger checkpoint method. 1957*9c5db199SXin Li 1958*9c5db199SXin Li Args: 1959*9c5db199SXin Li tname: String of testname associated with this time interval 1960*9c5db199SXin Li tstart: Float in seconds of when tname test started. Should be based 1961*9c5db199SXin Li off time.time() 1962*9c5db199SXin Li tend: Float in seconds of when tname test ended. Should be based 1963*9c5db199SXin Li off time.time(). If None, then value computed in the method. 1964*9c5db199SXin Li """ 1965*9c5db199SXin Li self._checkpoint_logger.checkpoint(tname, tstart, tend) 1966*9c5db199SXin Li 1967*9c5db199SXin Li # TODO(seankao): It might be useful to pull this method to CheckpointLogger, 1968*9c5db199SXin Li # to allow checkpoint usage without an explicit MeasurementLogger. 1969*9c5db199SXin Li def calc(self, mtype=None): 1970*9c5db199SXin Li """Calculate average measurement during each of the sub-tests. 1971*9c5db199SXin Li 1972*9c5db199SXin Li Method performs the following steps: 1973*9c5db199SXin Li 1. Signals the thread to stop running. 1974*9c5db199SXin Li 2. Calculates mean, max, min, count on the samples for each of the 1975*9c5db199SXin Li measurements. 1976*9c5db199SXin Li 3. Stores results to be written later. 1977*9c5db199SXin Li 4. Creates keyvals for autotest publishing. 1978*9c5db199SXin Li 1979*9c5db199SXin Li Args: 1980*9c5db199SXin Li mtype: string of measurement type. For example: 1981*9c5db199SXin Li pwr == power 1982*9c5db199SXin Li temp == temperature 1983*9c5db199SXin Li Returns: 1984*9c5db199SXin Li dict of keyvals suitable for autotest results. 1985*9c5db199SXin Li """ 1986*9c5db199SXin Li if not mtype: 1987*9c5db199SXin Li mtype = 'meas' 1988*9c5db199SXin Li 1989*9c5db199SXin Li t = numpy.array(self.times) 1990*9c5db199SXin Li keyvals = {} 1991*9c5db199SXin Li results = [('domain', 'mean', 'std', 'duration (s)', 'start ts', 1992*9c5db199SXin Li 'end ts')] 1993*9c5db199SXin Li # TODO(coconutruben): ensure that values is meaningful i.e. go through 1994*9c5db199SXin Li # the Loggers and add a unit attribute to each so that the raw 1995*9c5db199SXin Li # data is readable. 1996*9c5db199SXin Li raw_results = [('domain', 'values (%s)' % mtype)] 1997*9c5db199SXin Li 1998*9c5db199SXin Li if not self.done: 1999*9c5db199SXin Li self.done = True 2000*9c5db199SXin Li # times 2 the sleep time in order to allow for readings as well. 2001*9c5db199SXin Li self.join(timeout=self.seconds_period * 2) 2002*9c5db199SXin Li 2003*9c5db199SXin Li if not self._checkpoint_logger.checkpoint_data: 2004*9c5db199SXin Li self._checkpoint_logger.checkpoint() 2005*9c5db199SXin Li 2006*9c5db199SXin Li for i, domain_readings in enumerate(zip(*self.readings)): 2007*9c5db199SXin Li meas = numpy.array(domain_readings) 2008*9c5db199SXin Li try: 2009*9c5db199SXin Li domain = self.domains[i] 2010*9c5db199SXin Li except IndexError: 2011*9c5db199SXin Li # TODO (evanbenn) temp logging for b:162610351 2012*9c5db199SXin Li logging.debug('b:162610351 IndexError: %s, %d, %d, (%s)', 2013*9c5db199SXin Li type(self).__name__, 2014*9c5db199SXin Li len(self.readings), 2015*9c5db199SXin Li len(self.domains), 2016*9c5db199SXin Li ', '.join(str(len(r)) for r in self.readings)) 2017*9c5db199SXin Li logging.debug('b:162610351 domains: %s', 2018*9c5db199SXin Li ', '.join(self.domains)) 2019*9c5db199SXin Li raise 2020*9c5db199SXin Li 2021*9c5db199SXin Li for tname, tlist in \ 2022*9c5db199SXin Li self._checkpoint_logger.checkpoint_data.items(): 2023*9c5db199SXin Li if tname: 2024*9c5db199SXin Li prefix = '%s_%s' % (tname, domain) 2025*9c5db199SXin Li else: 2026*9c5db199SXin Li prefix = domain 2027*9c5db199SXin Li keyvals[prefix+'_duration'] = 0 2028*9c5db199SXin Li # Select all readings taken between tstart and tend 2029*9c5db199SXin Li # timestamps in tlist. 2030*9c5db199SXin Li masks = [] 2031*9c5db199SXin Li for tstart, tend in tlist: 2032*9c5db199SXin Li keyvals[prefix+'_duration'] += tend - tstart 2033*9c5db199SXin Li # Try block just in case 2034*9c5db199SXin Li # code.google.com/p/chromium/issues/detail?id=318892 2035*9c5db199SXin Li # is not fixed. 2036*9c5db199SXin Li try: 2037*9c5db199SXin Li masks.append(numpy.logical_and(tstart < t, t < tend)) 2038*9c5db199SXin Li except ValueError as e: 2039*9c5db199SXin Li logging.debug('Error logging measurements: %s', str(e)) 2040*9c5db199SXin Li logging.debug('timestamps %d %s', t.len, t) 2041*9c5db199SXin Li logging.debug('timestamp start, end %f %f', tstart, tend) 2042*9c5db199SXin Li logging.debug('measurements %d %s', meas.len, meas) 2043*9c5db199SXin Li mask = numpy.logical_or.reduce(masks) 2044*9c5db199SXin Li meas_array = meas[mask] 2045*9c5db199SXin Li 2046*9c5db199SXin Li # If sub-test terminated early, avoid calculating avg, std and 2047*9c5db199SXin Li # min 2048*9c5db199SXin Li if not meas_array.size: 2049*9c5db199SXin Li continue 2050*9c5db199SXin Li meas_mean = meas_array.mean() 2051*9c5db199SXin Li meas_std = meas_array.std() 2052*9c5db199SXin Li 2053*9c5db199SXin Li # Results list can be used for pretty printing and saving as csv 2054*9c5db199SXin Li # TODO(seankao): new results format? 2055*9c5db199SXin Li result = (prefix, meas_mean, meas_std) 2056*9c5db199SXin Li for tstart, tend in tlist: 2057*9c5db199SXin Li result = result + (tend - tstart, tstart, tend) 2058*9c5db199SXin Li results.append(result) 2059*9c5db199SXin Li raw_results.append((prefix,) + tuple(meas_array.tolist())) 2060*9c5db199SXin Li 2061*9c5db199SXin Li keyvals[prefix + '_' + mtype + '_avg'] = meas_mean 2062*9c5db199SXin Li keyvals[prefix + '_' + mtype + '_cnt'] = meas_array.size 2063*9c5db199SXin Li keyvals[prefix + '_' + mtype + '_max'] = meas_array.max() 2064*9c5db199SXin Li keyvals[prefix + '_' + mtype + '_min'] = meas_array.min() 2065*9c5db199SXin Li keyvals[prefix + '_' + mtype + '_std'] = meas_std 2066*9c5db199SXin Li self._results = results 2067*9c5db199SXin Li self._raw_results = raw_results 2068*9c5db199SXin Li return keyvals 2069*9c5db199SXin Li 2070*9c5db199SXin Li 2071*9c5db199SXin Li def save_results(self, resultsdir, fname_prefix=None): 2072*9c5db199SXin Li """Save computed results in a nice tab-separated format. 2073*9c5db199SXin Li This is useful for long manual runs. 2074*9c5db199SXin Li 2075*9c5db199SXin Li Args: 2076*9c5db199SXin Li resultsdir: String, directory to write results to 2077*9c5db199SXin Li fname_prefix: prefix to use for fname. If provided outfiles 2078*9c5db199SXin Li will be [fname]_[raw|summary].txt 2079*9c5db199SXin Li """ 2080*9c5db199SXin Li if not fname_prefix: 2081*9c5db199SXin Li fname_prefix = 'meas_results_%.0f' % time.time() 2082*9c5db199SXin Li fname = '%s_summary.txt' % fname_prefix 2083*9c5db199SXin Li raw_fname = fname.replace('summary', 'raw') 2084*9c5db199SXin Li for name, data in [(fname, self._results), 2085*9c5db199SXin Li (raw_fname, self._raw_results)]: 2086*9c5db199SXin Li with open(os.path.join(resultsdir, name), 'wt') as f: 2087*9c5db199SXin Li # First row contains the headers 2088*9c5db199SXin Li f.write('%s\n' % '\t'.join(data[0])) 2089*9c5db199SXin Li for row in data[1:]: 2090*9c5db199SXin Li # First column name, rest are numbers. See _calc_power() 2091*9c5db199SXin Li fmt_row = [row[0]] + ['%.2f' % x for x in row[1:]] 2092*9c5db199SXin Li f.write('%s\n' % '\t'.join(fmt_row)) 2093*9c5db199SXin Li 2094*9c5db199SXin Li 2095*9c5db199SXin Liclass CPUStatsLogger(MeasurementLogger): 2096*9c5db199SXin Li """Class to measure CPU Frequency and CPU Idle Stats. 2097*9c5db199SXin Li 2098*9c5db199SXin Li CPUStatsLogger derived from MeasurementLogger class but overload data 2099*9c5db199SXin Li samplings method because MeasurementLogger assumed that each sampling is 2100*9c5db199SXin Li independent to each other. However, in this case it is not. For example, 2101*9c5db199SXin Li CPU time spent in C0 state is measure by time not spent in all other states. 2102*9c5db199SXin Li 2103*9c5db199SXin Li CPUStatsLogger also collects the weight average in each time period if the 2104*9c5db199SXin Li underlying AbstractStats support weight average function. 2105*9c5db199SXin Li 2106*9c5db199SXin Li Private attributes: 2107*9c5db199SXin Li _stats: list of CPU AbstractStats objects to be sampled. 2108*9c5db199SXin Li _refresh_count: number of times refresh() has been called. 2109*9c5db199SXin Li _last_wavg: dict of wavg when refresh() was last called. 2110*9c5db199SXin Li """ 2111*9c5db199SXin Li def __init__(self, seconds_period=1.0, checkpoint_logger=None): 2112*9c5db199SXin Li """Initialize a CPUStatsLogger. 2113*9c5db199SXin Li 2114*9c5db199SXin Li Args: 2115*9c5db199SXin Li seconds_period: float, probing interval in seconds. Default 1.0 2116*9c5db199SXin Li """ 2117*9c5db199SXin Li # We don't use measurements since CPU stats can't measure separately. 2118*9c5db199SXin Li super(CPUStatsLogger, self).__init__([], seconds_period, checkpoint_logger) 2119*9c5db199SXin Li 2120*9c5db199SXin Li self._stats = get_available_cpu_stats() 2121*9c5db199SXin Li self._stats.append(GPUFreqStats()) 2122*9c5db199SXin Li self.domains = [] 2123*9c5db199SXin Li self._refresh_count = 0 2124*9c5db199SXin Li self._last_wavg = collections.defaultdict(int) 2125*9c5db199SXin Li 2126*9c5db199SXin Li def _set_domains(self): 2127*9c5db199SXin Li self.domains = [] 2128*9c5db199SXin Li for stat in self._stats: 2129*9c5db199SXin Li self.domains.extend([stat.name + '_' + str(state_name) 2130*9c5db199SXin Li for state_name in stat.refresh()]) 2131*9c5db199SXin Li if stat.weighted_average(): 2132*9c5db199SXin Li self.domains.append('wavg_' + stat.name) 2133*9c5db199SXin Li 2134*9c5db199SXin Li def refresh(self): 2135*9c5db199SXin Li self._refresh_count += 1 2136*9c5db199SXin Li count = self._refresh_count 2137*9c5db199SXin Li ret = [] 2138*9c5db199SXin Li for stat in self._stats: 2139*9c5db199SXin Li ret.extend(list(stat.refresh().values())) 2140*9c5db199SXin Li wavg = stat.weighted_average() 2141*9c5db199SXin Li if wavg: 2142*9c5db199SXin Li if stat.incremental: 2143*9c5db199SXin Li last_wavg = self._last_wavg[stat.name] 2144*9c5db199SXin Li self._last_wavg[stat.name] = wavg 2145*9c5db199SXin Li # Calculate weight average in this period using current 2146*9c5db199SXin Li # total weight average and last total weight average. 2147*9c5db199SXin Li # The result will lose some precision with higher number of 2148*9c5db199SXin Li # count but still good enough for 11 significant digits even 2149*9c5db199SXin Li # if we logged the data every 1 second for a day. 2150*9c5db199SXin Li ret.append(wavg * count - last_wavg * (count - 1)) 2151*9c5db199SXin Li else: 2152*9c5db199SXin Li ret.append(wavg) 2153*9c5db199SXin Li if not self.domains: 2154*9c5db199SXin Li self._set_domains() 2155*9c5db199SXin Li elif len(self.domains) != len(ret): 2156*9c5db199SXin Li # This would make data jumble but better than IndexError. 2157*9c5db199SXin Li # Add the log to help detecting the root cause. 2158*9c5db199SXin Li logging.warning('b:162610351 len(self.domains) != len(ret)') 2159*9c5db199SXin Li logging.warning('old_domains: (%s)', ', '.join(self.domains)) 2160*9c5db199SXin Li self._set_domains() 2161*9c5db199SXin Li logging.warning('new_domains: (%s)', ', '.join(self.domains)) 2162*9c5db199SXin Li return ret 2163*9c5db199SXin Li 2164*9c5db199SXin Li def save_results(self, resultsdir, fname_prefix=None): 2165*9c5db199SXin Li if not fname_prefix: 2166*9c5db199SXin Li fname_prefix = 'cpu_results_%.0f' % time.time() 2167*9c5db199SXin Li super(CPUStatsLogger, self).save_results(resultsdir, fname_prefix) 2168*9c5db199SXin Li 2169*9c5db199SXin Li 2170*9c5db199SXin Liclass PowerLogger(MeasurementLogger): 2171*9c5db199SXin Li """Class to measure power consumption.""" 2172*9c5db199SXin Li 2173*9c5db199SXin Li def __init__(self, measurements, seconds_period=1.0, 2174*9c5db199SXin Li checkpoint_logger=None): 2175*9c5db199SXin Li if not measurements: 2176*9c5db199SXin Li measurements = self.create_measurements() 2177*9c5db199SXin Li super(PowerLogger, self).__init__(measurements, seconds_period, 2178*9c5db199SXin Li checkpoint_logger) 2179*9c5db199SXin Li 2180*9c5db199SXin Li def create_measurements(self): 2181*9c5db199SXin Li """Create power measurements based on device config.""" 2182*9c5db199SXin Li # Import here to avoid import loop. 2183*9c5db199SXin Li from autotest_lib.client.cros.power import power_rapl 2184*9c5db199SXin Li 2185*9c5db199SXin Li measurements = [] 2186*9c5db199SXin Li status = get_status() 2187*9c5db199SXin Li if status.battery: 2188*9c5db199SXin Li measurements.append(BatteryStateOfCharge()) 2189*9c5db199SXin Li if status.battery_discharging(): 2190*9c5db199SXin Li measurements.append(SystemPower(status.battery_path)) 2191*9c5db199SXin Li if power_utils.has_powercap_support(): 2192*9c5db199SXin Li measurements += power_rapl.create_powercap() 2193*9c5db199SXin Li elif power_utils.has_rapl_support(): 2194*9c5db199SXin Li measurements += power_rapl.create_rapl() 2195*9c5db199SXin Li return measurements 2196*9c5db199SXin Li 2197*9c5db199SXin Li def save_results(self, resultsdir, fname_prefix=None): 2198*9c5db199SXin Li if not fname_prefix: 2199*9c5db199SXin Li fname_prefix = 'power_results_%.0f' % time.time() 2200*9c5db199SXin Li super(PowerLogger, self).save_results(resultsdir, fname_prefix) 2201*9c5db199SXin Li 2202*9c5db199SXin Li def calc(self, mtype='pwr'): 2203*9c5db199SXin Li return super(PowerLogger, self).calc(mtype) 2204*9c5db199SXin Li 2205*9c5db199SXin Li 2206*9c5db199SXin Liclass TempMeasurement(object): 2207*9c5db199SXin Li """Class to measure temperature. 2208*9c5db199SXin Li 2209*9c5db199SXin Li Public attributes: 2210*9c5db199SXin Li domain: String name of the temperature domain being measured. Example is 2211*9c5db199SXin Li 'cpu' for cpu temperature 2212*9c5db199SXin Li 2213*9c5db199SXin Li Private attributes: 2214*9c5db199SXin Li _path: Path to temperature file to read ( in millidegrees Celsius ) 2215*9c5db199SXin Li 2216*9c5db199SXin Li Public methods: 2217*9c5db199SXin Li refresh: Performs any temperature sampling and calculation and returns 2218*9c5db199SXin Li temperature as float in degrees Celsius. 2219*9c5db199SXin Li """ 2220*9c5db199SXin Li def __init__(self, domain, path): 2221*9c5db199SXin Li """Constructor.""" 2222*9c5db199SXin Li self.domain = domain 2223*9c5db199SXin Li self._path = path 2224*9c5db199SXin Li 2225*9c5db199SXin Li 2226*9c5db199SXin Li def refresh(self): 2227*9c5db199SXin Li """Performs temperature 2228*9c5db199SXin Li 2229*9c5db199SXin Li Returns: 2230*9c5db199SXin Li float, temperature in degrees Celsius 2231*9c5db199SXin Li """ 2232*9c5db199SXin Li return int(utils.read_one_line(self._path)) / 1000. 2233*9c5db199SXin Li 2234*9c5db199SXin Li 2235*9c5db199SXin Liclass BatteryTempMeasurement(TempMeasurement): 2236*9c5db199SXin Li """Class to measure battery temperature.""" 2237*9c5db199SXin Li def __init__(self): 2238*9c5db199SXin Li super(BatteryTempMeasurement, self).__init__('battery', 'battery_temp') 2239*9c5db199SXin Li 2240*9c5db199SXin Li 2241*9c5db199SXin Li def refresh(self): 2242*9c5db199SXin Li """Perform battery temperature reading. 2243*9c5db199SXin Li 2244*9c5db199SXin Li Returns: 2245*9c5db199SXin Li float, temperature in degrees Celsius. 2246*9c5db199SXin Li """ 2247*9c5db199SXin Li result = utils.run(self._path, timeout=5, ignore_status=True) 2248*9c5db199SXin Li 2249*9c5db199SXin Li value = float(result.stdout) 2250*9c5db199SXin Li 2251*9c5db199SXin Li # `battery_temp` return in celsius unit. 2252*9c5db199SXin Li if 0 < value < 100: 2253*9c5db199SXin Li return round(value, 1) 2254*9c5db199SXin Li 2255*9c5db199SXin Li # `battery_temp` return in kelvin unit. 2256*9c5db199SXin Li if 273 < value < 373: 2257*9c5db199SXin Li return round(value - 273.15, 1) 2258*9c5db199SXin Li 2259*9c5db199SXin Li # `battery_temp` return in millicelsius unit. 2260*9c5db199SXin Li if 1000 < value < 100000: 2261*9c5db199SXin Li return round(value / 1000., 1) 2262*9c5db199SXin Li 2263*9c5db199SXin Li # The command return value in millikelvin unit. 2264*9c5db199SXin Li if 273150 < value < 373150: 2265*9c5db199SXin Li return round(value / 1000. - 273.15, 1) 2266*9c5db199SXin Li 2267*9c5db199SXin Li raise ValueError 2268*9c5db199SXin Li 2269*9c5db199SXin Li 2270*9c5db199SXin Lidef has_battery_temp(): 2271*9c5db199SXin Li """Determine if DUT can provide battery temperature. 2272*9c5db199SXin Li 2273*9c5db199SXin Li Returns: 2274*9c5db199SXin Li Boolean, True if battery temperature available. False otherwise. 2275*9c5db199SXin Li """ 2276*9c5db199SXin Li if not power_utils.has_battery(): 2277*9c5db199SXin Li return False 2278*9c5db199SXin Li 2279*9c5db199SXin Li btemp = BatteryTempMeasurement() 2280*9c5db199SXin Li try: 2281*9c5db199SXin Li btemp.refresh() 2282*9c5db199SXin Li except ValueError: 2283*9c5db199SXin Li return False 2284*9c5db199SXin Li 2285*9c5db199SXin Li return True 2286*9c5db199SXin Li 2287*9c5db199SXin Li 2288*9c5db199SXin Liclass TempLogger(MeasurementLogger): 2289*9c5db199SXin Li """A thread that logs temperature readings in millidegrees Celsius.""" 2290*9c5db199SXin Li 2291*9c5db199SXin Li def __init__(self, measurements, seconds_period=30.0, 2292*9c5db199SXin Li checkpoint_logger=None): 2293*9c5db199SXin Li if not measurements: 2294*9c5db199SXin Li measurements = self.create_measurements() 2295*9c5db199SXin Li super(TempLogger, self).__init__(measurements, seconds_period, 2296*9c5db199SXin Li checkpoint_logger) 2297*9c5db199SXin Li 2298*9c5db199SXin Li def create_measurements(self): 2299*9c5db199SXin Li """Create measurements for TempLogger.""" 2300*9c5db199SXin Li measurements = [] 2301*9c5db199SXin Li 2302*9c5db199SXin Li zstats = ThermalStatACPI() 2303*9c5db199SXin Li for desc, fpath in zstats.zones.items(): 2304*9c5db199SXin Li new_meas = TempMeasurement(desc, fpath) 2305*9c5db199SXin Li measurements.append(new_meas) 2306*9c5db199SXin Li 2307*9c5db199SXin Li tstats = ThermalStatHwmon() 2308*9c5db199SXin Li for kname in tstats.fields: 2309*9c5db199SXin Li match = re.match(r'(\S+)_temp(\d+)_input', kname) 2310*9c5db199SXin Li if not match: 2311*9c5db199SXin Li continue 2312*9c5db199SXin Li desc = match.group(1) + '-t' + match.group(2) 2313*9c5db199SXin Li fpath = tstats.fields[kname][0] 2314*9c5db199SXin Li new_meas = TempMeasurement(desc, fpath) 2315*9c5db199SXin Li measurements.append(new_meas) 2316*9c5db199SXin Li 2317*9c5db199SXin Li if has_battery_temp(): 2318*9c5db199SXin Li measurements.append(BatteryTempMeasurement()) 2319*9c5db199SXin Li 2320*9c5db199SXin Li return measurements 2321*9c5db199SXin Li 2322*9c5db199SXin Li def save_results(self, resultsdir, fname_prefix=None): 2323*9c5db199SXin Li if not fname_prefix: 2324*9c5db199SXin Li fname_prefix = 'temp_results_%.0f' % time.time() 2325*9c5db199SXin Li super(TempLogger, self).save_results(resultsdir, fname_prefix) 2326*9c5db199SXin Li 2327*9c5db199SXin Li 2328*9c5db199SXin Li def calc(self, mtype='temp'): 2329*9c5db199SXin Li return super(TempLogger, self).calc(mtype) 2330*9c5db199SXin Li 2331*9c5db199SXin Li 2332*9c5db199SXin Liclass VideoFpsLogger(MeasurementLogger): 2333*9c5db199SXin Li """Class to measure Video FPS.""" 2334*9c5db199SXin Li 2335*9c5db199SXin Li @classmethod 2336*9c5db199SXin Li def time_until_ready(cls, tab, num_video=1, timeout=120): 2337*9c5db199SXin Li """Wait until tab is ready for VideoFpsLogger and return time used. 2338*9c5db199SXin Li 2339*9c5db199SXin Li Keep polling Chrome tab until these 2 conditions are met one by one. 2340*9c5db199SXin Li - Number of <video> elements detected is equal to |num_video|. 2341*9c5db199SXin Li - All videos are played for at least 1 ms. 2342*9c5db199SXin Li 2343*9c5db199SXin Li Args: 2344*9c5db199SXin Li tab: Chrome tab object 2345*9c5db199SXin Li num_video: number of expected <video> elements, default 1. 2346*9c5db199SXin Li timeout: timeout in seconds, default 120. 2347*9c5db199SXin Li 2348*9c5db199SXin Li Returns: 2349*9c5db199SXin Li float, number of seconds elasped until condition met. 2350*9c5db199SXin Li 2351*9c5db199SXin Li Raises: 2352*9c5db199SXin Li error.TestFail if condition are not met by timeout. 2353*9c5db199SXin Li """ 2354*9c5db199SXin Li start_time = time.time() 2355*9c5db199SXin Li 2356*9c5db199SXin Li # Number of <video> elements detected is equal to |num_video|. 2357*9c5db199SXin Li c = 'document.getElementsByTagName("video").length == %d' % num_video 2358*9c5db199SXin Li tab.WaitForJavaScriptCondition(c, timeout=timeout) 2359*9c5db199SXin Li 2360*9c5db199SXin Li # All videos are played for at least 1 ms. 2361*9c5db199SXin Li c = ('Math.min(...Array.from(document.getElementsByTagName("video"))' 2362*9c5db199SXin Li '.map(v => v.currentTime)) >= 0.001') 2363*9c5db199SXin Li timeout_left = timeout - (time.time() - start_time) 2364*9c5db199SXin Li try: 2365*9c5db199SXin Li tab.WaitForJavaScriptCondition(c, timeout=timeout_left) 2366*9c5db199SXin Li # Broad exception because py_utils.TimeoutException require libchrome 2367*9c5db199SXin Li except Exception: 2368*9c5db199SXin Li times = tab.EvaluateJavaScript( 2369*9c5db199SXin Li 'Array.from(document.getElementsByTagName("video"))' 2370*9c5db199SXin Li '.map(v => v.currentTime)') 2371*9c5db199SXin Li # Not timeout exception, re-raise original exception 2372*9c5db199SXin Li if min(times) > 0.001: 2373*9c5db199SXin Li raise 2374*9c5db199SXin Li videos = tab.EvaluateJavaScript( 2375*9c5db199SXin Li 'Array.from(document.getElementsByTagName("video"))' 2376*9c5db199SXin Li '.map(v => v.id)') 2377*9c5db199SXin Li failed_videos = [v for v, t in zip(videos, times) if t < 0.001] 2378*9c5db199SXin Li raise error.TestFail('Media playback failed: %s' % failed_videos) 2379*9c5db199SXin Li return time.time() - start_time 2380*9c5db199SXin Li 2381*9c5db199SXin Li def __init__(self, tab, seconds_period=1.0, checkpoint_logger=None): 2382*9c5db199SXin Li """Initialize a VideoFpsLogger. 2383*9c5db199SXin Li 2384*9c5db199SXin Li Args: 2385*9c5db199SXin Li tab: Chrome tab object 2386*9c5db199SXin Li """ 2387*9c5db199SXin Li super(VideoFpsLogger, self).__init__([], seconds_period, 2388*9c5db199SXin Li checkpoint_logger) 2389*9c5db199SXin Li self._tab = tab 2390*9c5db199SXin Li names = self._tab.EvaluateJavaScript( 2391*9c5db199SXin Li 'Array.from(document.getElementsByTagName("video")).map(v => v.id)') 2392*9c5db199SXin Li self.domains = [n or 'video_' + str(i) for i, n in enumerate(names)] 2393*9c5db199SXin Li self._last = [0] * len(names) 2394*9c5db199SXin Li self.refresh() 2395*9c5db199SXin Li 2396*9c5db199SXin Li def refresh(self): 2397*9c5db199SXin Li @retry.retry(Exception, timeout_min=0.5, delay_sec=0.1) 2398*9c5db199SXin Li def get_fps(): 2399*9c5db199SXin Li return self._tab.EvaluateJavaScript( 2400*9c5db199SXin Li 'Array.from(document.getElementsByTagName("video")).map(' 2401*9c5db199SXin Li 'v => v.webkitDecodedFrameCount)') 2402*9c5db199SXin Li current = get_fps() 2403*9c5db199SXin Li fps = [(b - a if b >= a else b) / self.seconds_period 2404*9c5db199SXin Li for a, b in zip(self._last , current)] 2405*9c5db199SXin Li self._last = current 2406*9c5db199SXin Li return fps 2407*9c5db199SXin Li 2408*9c5db199SXin Li def save_results(self, resultsdir, fname_prefix=None): 2409*9c5db199SXin Li if not fname_prefix: 2410*9c5db199SXin Li fname_prefix = 'video_fps_results_%.0f' % time.time() 2411*9c5db199SXin Li super(VideoFpsLogger, self).save_results(resultsdir, fname_prefix) 2412*9c5db199SXin Li 2413*9c5db199SXin Li def calc(self, mtype='fps'): 2414*9c5db199SXin Li return super(VideoFpsLogger, self).calc(mtype) 2415*9c5db199SXin Li 2416*9c5db199SXin Li 2417*9c5db199SXin Lidef get_num_fans(): 2418*9c5db199SXin Li """Count how many fan DUT has. 2419*9c5db199SXin Li 2420*9c5db199SXin Li Returns: 2421*9c5db199SXin Li Integer, number of fans that DUT has. 2422*9c5db199SXin Li """ 2423*9c5db199SXin Li res = utils.run('ectool pwmgetnumfans | grep -o [0-9]', ignore_status=True) 2424*9c5db199SXin Li if not res or res.exit_status != 0: 2425*9c5db199SXin Li return 0 2426*9c5db199SXin Li return int(res.stdout) 2427*9c5db199SXin Li 2428*9c5db199SXin Li 2429*9c5db199SXin Lidef has_fan(): 2430*9c5db199SXin Li """Determine if DUT has fan. 2431*9c5db199SXin Li 2432*9c5db199SXin Li Returns: 2433*9c5db199SXin Li Boolean, True if dut has fan. False otherwise. 2434*9c5db199SXin Li """ 2435*9c5db199SXin Li return get_num_fans() > 0 2436*9c5db199SXin Li 2437*9c5db199SXin Li 2438*9c5db199SXin Liclass FanRpmLogger(MeasurementLogger): 2439*9c5db199SXin Li """Class to measure Fan RPM.""" 2440*9c5db199SXin Li 2441*9c5db199SXin Li def __init__(self, seconds_period=1.0, checkpoint_logger=None): 2442*9c5db199SXin Li """Initialize a FanRpmLogger.""" 2443*9c5db199SXin Li super(FanRpmLogger, self).__init__([], seconds_period, 2444*9c5db199SXin Li checkpoint_logger) 2445*9c5db199SXin Li self.domains = ['fan_' + str(i) for i in range(get_num_fans())] 2446*9c5db199SXin Li self.refresh() 2447*9c5db199SXin Li 2448*9c5db199SXin Li def refresh(self): 2449*9c5db199SXin Li @retry.retry(Exception, timeout_min=0.1, delay_sec=2) 2450*9c5db199SXin Li def get_fan_rpm_all(): 2451*9c5db199SXin Li """Some example outputs from ectool 2452*9c5db199SXin Li 2453*9c5db199SXin Li * Two fan system 2454*9c5db199SXin Li localhost ~ # ectool pwmgetfanrpm all 2455*9c5db199SXin Li Fan 0 RPM: 4012 2456*9c5db199SXin Li Fan 1 RPM: 4009 2457*9c5db199SXin Li 2458*9c5db199SXin Li * One fan but its stalled 2459*9c5db199SXin Li localhost ~ # ectool pwmgetfanrpm all 2460*9c5db199SXin Li Fan 0 stalled! 2461*9c5db199SXin Li """ 2462*9c5db199SXin Li cmd = 'ectool pwmgetfanrpm all' 2463*9c5db199SXin Li res = utils.run(cmd, ignore_status=True, 2464*9c5db199SXin Li stdout_tee=utils.TEE_TO_LOGS, 2465*9c5db199SXin Li stderr_tee=utils.TEE_TO_LOGS) 2466*9c5db199SXin Li rpm_data = [] 2467*9c5db199SXin Li for i, ln in enumerate(res.stdout.splitlines()): 2468*9c5db199SXin Li if ln.find('stalled') != -1: 2469*9c5db199SXin Li rpm_data.append(0) 2470*9c5db199SXin Li else: 2471*9c5db199SXin Li rpm_data.append(int(ln.split(':')[1])) 2472*9c5db199SXin Li return rpm_data 2473*9c5db199SXin Li return get_fan_rpm_all() 2474*9c5db199SXin Li 2475*9c5db199SXin Li def save_results(self, resultsdir, fname_prefix=None): 2476*9c5db199SXin Li if not fname_prefix: 2477*9c5db199SXin Li fname_prefix = 'fan_rpm_results_%.0f' % time.time() 2478*9c5db199SXin Li super(FanRpmLogger, self).save_results(resultsdir, fname_prefix) 2479*9c5db199SXin Li 2480*9c5db199SXin Li def calc(self, mtype='rpm'): 2481*9c5db199SXin Li return super(FanRpmLogger, self).calc(mtype) 2482*9c5db199SXin Li 2483*9c5db199SXin Li 2484*9c5db199SXin Liclass FreeMemoryLogger(MeasurementLogger): 2485*9c5db199SXin Li """Class to measure free memory from /proc/meminfo in KB unit.""" 2486*9c5db199SXin Li 2487*9c5db199SXin Li def __init__(self, seconds_period=1.0, checkpoint_logger=None): 2488*9c5db199SXin Li """Initialize a FreeMemoryLogger.""" 2489*9c5db199SXin Li super(FreeMemoryLogger, self).__init__([], seconds_period, 2490*9c5db199SXin Li checkpoint_logger) 2491*9c5db199SXin Li self.domains = ['MemFree', 'MemAvailable'] 2492*9c5db199SXin Li self.refresh() 2493*9c5db199SXin Li 2494*9c5db199SXin Li def refresh(self): 2495*9c5db199SXin Li return [ 2496*9c5db199SXin Li utils.read_from_meminfo('MemFree'), 2497*9c5db199SXin Li utils.read_from_meminfo('MemAvailable') 2498*9c5db199SXin Li ] 2499*9c5db199SXin Li 2500*9c5db199SXin Li def save_results(self, resultsdir, fname_prefix=None): 2501*9c5db199SXin Li if not fname_prefix: 2502*9c5db199SXin Li fname_prefix = 'free_memory_results_%.0f' % time.time() 2503*9c5db199SXin Li super(FreeMemoryLogger, self).save_results(resultsdir, fname_prefix) 2504*9c5db199SXin Li 2505*9c5db199SXin Li def calc(self, mtype='kB'): 2506*9c5db199SXin Li return super(FreeMemoryLogger, self).calc(mtype) 2507*9c5db199SXin Li 2508*9c5db199SXin Li 2509*9c5db199SXin Lidef create_measurement_loggers(seconds_period=20.0, checkpoint_logger=None): 2510*9c5db199SXin Li """Create loggers for power test that is not test-specific. 2511*9c5db199SXin Li 2512*9c5db199SXin Li Args: 2513*9c5db199SXin Li seconds_period: float, probing interval in seconds. Default 20.0 2514*9c5db199SXin Li checkpoint_logger: CheckpointLogger class for the loggers 2515*9c5db199SXin Li 2516*9c5db199SXin Li Returns: 2517*9c5db199SXin Li list of loggers created. 2518*9c5db199SXin Li """ 2519*9c5db199SXin Li loggers = [ 2520*9c5db199SXin Li PowerLogger(None, seconds_period, checkpoint_logger), 2521*9c5db199SXin Li TempLogger(None, seconds_period, checkpoint_logger), 2522*9c5db199SXin Li CPUStatsLogger(seconds_period, checkpoint_logger), 2523*9c5db199SXin Li FreeMemoryLogger(seconds_period, checkpoint_logger), 2524*9c5db199SXin Li ] 2525*9c5db199SXin Li if has_fan(): 2526*9c5db199SXin Li loggers.append(FanRpmLogger(seconds_period, checkpoint_logger)) 2527*9c5db199SXin Li 2528*9c5db199SXin Li return loggers 2529*9c5db199SXin Li 2530*9c5db199SXin Li 2531*9c5db199SXin Liclass DiskStateLogger(threading.Thread): 2532*9c5db199SXin Li """Records the time percentages the disk stays in its different power modes. 2533*9c5db199SXin Li 2534*9c5db199SXin Li Example code snippet: 2535*9c5db199SXin Li mylogger = power_status.DiskStateLogger() 2536*9c5db199SXin Li mylogger.start() 2537*9c5db199SXin Li result = mylogger.result() 2538*9c5db199SXin Li 2539*9c5db199SXin Li Public methods: 2540*9c5db199SXin Li start: Launches the thread and starts measurements. 2541*9c5db199SXin Li result: Stops the thread if it's still running and returns measurements. 2542*9c5db199SXin Li get_error: Returns the exception in _error if it exists. 2543*9c5db199SXin Li 2544*9c5db199SXin Li Private functions: 2545*9c5db199SXin Li _get_disk_state: Returns the disk's current ATA power mode as a string. 2546*9c5db199SXin Li 2547*9c5db199SXin Li Private attributes: 2548*9c5db199SXin Li _seconds_period: Disk polling interval in seconds. 2549*9c5db199SXin Li _stats: Dict that maps disk states to seconds spent in them. 2550*9c5db199SXin Li _running: Flag that is True as long as the logger should keep running. 2551*9c5db199SXin Li _time: Timestamp of last disk state reading. 2552*9c5db199SXin Li _device_path: The file system path of the disk's device node. 2553*9c5db199SXin Li _error: Contains a TestError exception if an unexpected error occured 2554*9c5db199SXin Li """ 2555*9c5db199SXin Li def __init__(self, seconds_period = 5.0, device_path = None): 2556*9c5db199SXin Li """Initializes a logger. 2557*9c5db199SXin Li 2558*9c5db199SXin Li Args: 2559*9c5db199SXin Li seconds_period: Disk polling interval in seconds. Default 5.0 2560*9c5db199SXin Li device_path: The path of the disk's device node. Default '/dev/sda' 2561*9c5db199SXin Li """ 2562*9c5db199SXin Li threading.Thread.__init__(self) 2563*9c5db199SXin Li self._seconds_period = seconds_period 2564*9c5db199SXin Li self._device_path = device_path 2565*9c5db199SXin Li self._stats = {} 2566*9c5db199SXin Li self._running = False 2567*9c5db199SXin Li self._error = None 2568*9c5db199SXin Li 2569*9c5db199SXin Li result = utils.system_output('rootdev -s') 2570*9c5db199SXin Li # TODO(tbroch) Won't work for emmc storage and will throw this error in 2571*9c5db199SXin Li # keyvals : 'ioctl(SG_IO) error: [Errno 22] Invalid argument' 2572*9c5db199SXin Li # Lets implement something complimentary for emmc 2573*9c5db199SXin Li if not device_path: 2574*9c5db199SXin Li self._device_path = \ 2575*9c5db199SXin Li re.sub('(sd[a-z]|mmcblk[0-9]+)p?[0-9]+', '\\1', result) 2576*9c5db199SXin Li logging.debug("device_path = %s", self._device_path) 2577*9c5db199SXin Li 2578*9c5db199SXin Li 2579*9c5db199SXin Li def start(self): 2580*9c5db199SXin Li logging.debug("inside DiskStateLogger.start") 2581*9c5db199SXin Li if os.path.exists(self._device_path): 2582*9c5db199SXin Li logging.debug("DiskStateLogger started") 2583*9c5db199SXin Li super(DiskStateLogger, self).start() 2584*9c5db199SXin Li 2585*9c5db199SXin Li 2586*9c5db199SXin Li def _get_disk_state(self): 2587*9c5db199SXin Li """Checks the disk's power mode and returns it as a string. 2588*9c5db199SXin Li 2589*9c5db199SXin Li This uses the SG_IO ioctl to issue a raw SCSI command data block with 2590*9c5db199SXin Li the ATA-PASS-THROUGH command that allows SCSI-to-ATA translation (see 2591*9c5db199SXin Li T10 document 04-262r8). The ATA command issued is CHECKPOWERMODE1, 2592*9c5db199SXin Li which returns the device's current power mode. 2593*9c5db199SXin Li """ 2594*9c5db199SXin Li 2595*9c5db199SXin Li def _addressof(obj): 2596*9c5db199SXin Li """Shortcut to return the memory address of an object as integer.""" 2597*9c5db199SXin Li return ctypes.cast(obj, ctypes.c_void_p).value 2598*9c5db199SXin Li 2599*9c5db199SXin Li scsi_cdb = struct.pack("12B", # SCSI command data block (uint8[12]) 2600*9c5db199SXin Li 0xa1, # SCSI opcode: ATA-PASS-THROUGH 2601*9c5db199SXin Li 3 << 1, # protocol: Non-data 2602*9c5db199SXin Li 1 << 5, # flags: CK_COND 2603*9c5db199SXin Li 0, # features 2604*9c5db199SXin Li 0, # sector count 2605*9c5db199SXin Li 0, 0, 0, # LBA 2606*9c5db199SXin Li 1 << 6, # flags: ATA-USING-LBA 2607*9c5db199SXin Li 0xe5, # ATA opcode: CHECKPOWERMODE1 2608*9c5db199SXin Li 0, # reserved 2609*9c5db199SXin Li 0, # control (no idea what this is...) 2610*9c5db199SXin Li ) 2611*9c5db199SXin Li scsi_sense = (ctypes.c_ubyte * 32)() # SCSI sense buffer (uint8[32]) 2612*9c5db199SXin Li sgio_header = struct.pack("iiBBHIPPPIIiPBBBBHHiII", # see <scsi/sg.h> 2613*9c5db199SXin Li 83, # Interface ID magic number (int32) 2614*9c5db199SXin Li -1, # data transfer direction: none (int32) 2615*9c5db199SXin Li 12, # SCSI command data block length (uint8) 2616*9c5db199SXin Li 32, # SCSI sense data block length (uint8) 2617*9c5db199SXin Li 0, # iovec_count (not applicable?) (uint16) 2618*9c5db199SXin Li 0, # data transfer length (uint32) 2619*9c5db199SXin Li 0, # data block pointer 2620*9c5db199SXin Li _addressof(scsi_cdb), # SCSI CDB pointer 2621*9c5db199SXin Li _addressof(scsi_sense), # sense buffer pointer 2622*9c5db199SXin Li 500, # timeout in milliseconds (uint32) 2623*9c5db199SXin Li 0, # flags (uint32) 2624*9c5db199SXin Li 0, # pack ID (unused) (int32) 2625*9c5db199SXin Li 0, # user data pointer (unused) 2626*9c5db199SXin Li 0, 0, 0, 0, 0, 0, 0, 0, 0, # output params 2627*9c5db199SXin Li ) 2628*9c5db199SXin Li try: 2629*9c5db199SXin Li with open(self._device_path, 'r') as dev: 2630*9c5db199SXin Li result = fcntl.ioctl(dev, 0x2285, sgio_header) 2631*9c5db199SXin Li except IOError as e: 2632*9c5db199SXin Li raise error.TestError('ioctl(SG_IO) error: %s' % str(e)) 2633*9c5db199SXin Li _, _, _, _, status, host_status, driver_status = \ 2634*9c5db199SXin Li struct.unpack("4x4xxx2x4xPPP4x4x4xPBxxxHH4x4x4x", result) 2635*9c5db199SXin Li if status != 0x2: # status: CHECK_CONDITION 2636*9c5db199SXin Li raise error.TestError('SG_IO status: %d' % status) 2637*9c5db199SXin Li if host_status != 0: 2638*9c5db199SXin Li raise error.TestError('SG_IO host status: %d' % host_status) 2639*9c5db199SXin Li if driver_status != 0x8: # driver status: SENSE 2640*9c5db199SXin Li raise error.TestError('SG_IO driver status: %d' % driver_status) 2641*9c5db199SXin Li 2642*9c5db199SXin Li if scsi_sense[0] != 0x72: # resp. code: current error, descriptor format 2643*9c5db199SXin Li raise error.TestError('SENSE response code: %d' % scsi_sense[0]) 2644*9c5db199SXin Li if scsi_sense[1] != 0: # sense key: No Sense 2645*9c5db199SXin Li raise error.TestError('SENSE key: %d' % scsi_sense[1]) 2646*9c5db199SXin Li if scsi_sense[7] < 14: # additional length (ATA status is 14 - 1 bytes) 2647*9c5db199SXin Li raise error.TestError('ADD. SENSE too short: %d' % scsi_sense[7]) 2648*9c5db199SXin Li if scsi_sense[8] != 0x9: # additional descriptor type: ATA Return Status 2649*9c5db199SXin Li raise error.TestError('SENSE descriptor type: %d' % scsi_sense[8]) 2650*9c5db199SXin Li if scsi_sense[11] != 0: # errors: none 2651*9c5db199SXin Li raise error.TestError('ATA error code: %d' % scsi_sense[11]) 2652*9c5db199SXin Li 2653*9c5db199SXin Li if scsi_sense[13] == 0x00: 2654*9c5db199SXin Li return 'standby' 2655*9c5db199SXin Li if scsi_sense[13] == 0x80: 2656*9c5db199SXin Li return 'idle' 2657*9c5db199SXin Li if scsi_sense[13] == 0xff: 2658*9c5db199SXin Li return 'active' 2659*9c5db199SXin Li return 'unknown(%d)' % scsi_sense[13] 2660*9c5db199SXin Li 2661*9c5db199SXin Li 2662*9c5db199SXin Li def run(self): 2663*9c5db199SXin Li """The Thread's run method.""" 2664*9c5db199SXin Li try: 2665*9c5db199SXin Li self._time = time.time() 2666*9c5db199SXin Li self._running = True 2667*9c5db199SXin Li while(self._running): 2668*9c5db199SXin Li time.sleep(self._seconds_period) 2669*9c5db199SXin Li state = self._get_disk_state() 2670*9c5db199SXin Li new_time = time.time() 2671*9c5db199SXin Li if state in self._stats: 2672*9c5db199SXin Li self._stats[state] += new_time - self._time 2673*9c5db199SXin Li else: 2674*9c5db199SXin Li self._stats[state] = new_time - self._time 2675*9c5db199SXin Li self._time = new_time 2676*9c5db199SXin Li except error.TestError as e: 2677*9c5db199SXin Li self._error = e 2678*9c5db199SXin Li self._running = False 2679*9c5db199SXin Li 2680*9c5db199SXin Li 2681*9c5db199SXin Li def result(self): 2682*9c5db199SXin Li """Stop the logger and return dict with result percentages.""" 2683*9c5db199SXin Li if (self._running): 2684*9c5db199SXin Li self._running = False 2685*9c5db199SXin Li self.join(self._seconds_period * 2) 2686*9c5db199SXin Li return AbstractStats.to_percent(self._stats) 2687*9c5db199SXin Li 2688*9c5db199SXin Li 2689*9c5db199SXin Li def get_error(self): 2690*9c5db199SXin Li """Returns the _error exception... please only call after result().""" 2691*9c5db199SXin Li return self._error 2692*9c5db199SXin Li 2693*9c5db199SXin LiS0ixAmdStats = namedtuple('S0ixAmdStats',['entry','exit','residency']) 2694*9c5db199SXin Li 2695*9c5db199SXin Lidef parse_amd_pmc_s0ix_residency_info(): 2696*9c5db199SXin Li """ 2697*9c5db199SXin Li Parses S0ix residency for AMD systems 2698*9c5db199SXin Li 2699*9c5db199SXin Li @returns S0ixAmdStats 2700*9c5db199SXin Li @raises error.TestNAError if the debugfs file not found. 2701*9c5db199SXin Li """ 2702*9c5db199SXin Li s = [] 2703*9c5db199SXin Li with open('/sys/kernel/debug/amd_pmc/s0ix_stats',"r") as f: 2704*9c5db199SXin Li for line in f: 2705*9c5db199SXin Li if ':' in line: 2706*9c5db199SXin Li val = line.split(": ") 2707*9c5db199SXin Li s.append(int(val[1])) 2708*9c5db199SXin Li stat = S0ixAmdStats(entry=s[0], exit=s[1], residency=s[2]) 2709*9c5db199SXin Li return stat 2710*9c5db199SXin Li raise error.TestNAError('AMD S0ix residency not supported') 2711*9c5db199SXin Li 2712*9c5db199SXin Lidef parse_pmc_s0ix_residency_info(): 2713*9c5db199SXin Li """ 2714*9c5db199SXin Li Parses S0ix residency for PMC based Intel systems 2715*9c5db199SXin Li (skylake/kabylake/apollolake), the debugfs paths might be 2716*9c5db199SXin Li different from platform to platform, yet the format is 2717*9c5db199SXin Li unified in microseconds. 2718*9c5db199SXin Li 2719*9c5db199SXin Li @returns residency in seconds. 2720*9c5db199SXin Li @raises error.TestNAError if the debugfs file not found. 2721*9c5db199SXin Li """ 2722*9c5db199SXin Li info_path = None 2723*9c5db199SXin Li for node in ['/sys/kernel/debug/pmc_core/slp_s0_residency_usec', 2724*9c5db199SXin Li '/sys/kernel/debug/telemetry/s0ix_residency_usec']: 2725*9c5db199SXin Li if os.path.exists(node): 2726*9c5db199SXin Li info_path = node 2727*9c5db199SXin Li break 2728*9c5db199SXin Li if not info_path: 2729*9c5db199SXin Li raise error.TestNAError('S0ix residency file not found') 2730*9c5db199SXin Li return float(utils.read_one_line(info_path)) * 1e-6 2731*9c5db199SXin Li 2732*9c5db199SXin Li 2733*9c5db199SXin Liclass S0ixResidencyStats(object): 2734*9c5db199SXin Li """ 2735*9c5db199SXin Li Measures the S0ix residency of a given board over time. 2736*9c5db199SXin Li """ 2737*9c5db199SXin Li def __init__(self): 2738*9c5db199SXin Li if "amd" in utils.get_cpu_soc_family(): 2739*9c5db199SXin Li self._initial_residency = parse_amd_pmc_s0ix_residency_info() 2740*9c5db199SXin Li else: 2741*9c5db199SXin Li self._initial_residency = parse_pmc_s0ix_residency_info() 2742*9c5db199SXin Li 2743*9c5db199SXin Li def get_accumulated_residency_secs(self): 2744*9c5db199SXin Li """ 2745*9c5db199SXin Li @returns S0ix Residency since the class has been initialized. 2746*9c5db199SXin Li """ 2747*9c5db199SXin Li if "amd" in utils.get_cpu_soc_family(): 2748*9c5db199SXin Li s0ix = parse_amd_pmc_s0ix_residency_info() 2749*9c5db199SXin Li if s0ix != self._initial_residency: 2750*9c5db199SXin Li return s0ix.residency 2751*9c5db199SXin Li else: 2752*9c5db199SXin Li return 0 2753*9c5db199SXin Li else: 2754*9c5db199SXin Li return parse_pmc_s0ix_residency_info() - self._initial_residency 2755*9c5db199SXin Li 2756*9c5db199SXin Li 2757*9c5db199SXin Liclass S2IdleStateStats(object): 2758*9c5db199SXin Li """ 2759*9c5db199SXin Li Usage stats of an s2idle state. 2760*9c5db199SXin Li """ 2761*9c5db199SXin Li 2762*9c5db199SXin Li def __init__(self, usage, time): 2763*9c5db199SXin Li self.usage = usage 2764*9c5db199SXin Li self.time = time 2765*9c5db199SXin Li 2766*9c5db199SXin Li 2767*9c5db199SXin Lidef get_s2idle_path(cpu, state): 2768*9c5db199SXin Li path = os.path.join(CPU_BASE_PATH, 2769*9c5db199SXin Li "cpu{}/cpuidle/{}/s2idle".format(cpu, state)) 2770*9c5db199SXin Li if not os.path.exists(path): 2771*9c5db199SXin Li return None 2772*9c5db199SXin Li 2773*9c5db199SXin Li return path 2774*9c5db199SXin Li 2775*9c5db199SXin Li 2776*9c5db199SXin Lidef get_s2idle_stats_for_state(cpu, state): 2777*9c5db199SXin Li """ 2778*9c5db199SXin Li Returns the s2idle stats for a given idle state of a CPU. 2779*9c5db199SXin Li """ 2780*9c5db199SXin Li s2idle_path = get_s2idle_path(cpu, state) 2781*9c5db199SXin Li 2782*9c5db199SXin Li path = os.path.join(s2idle_path, 'usage') 2783*9c5db199SXin Li if not os.path.exists(path): 2784*9c5db199SXin Li raise error.TestFail("File not found: {}" % path) 2785*9c5db199SXin Li 2786*9c5db199SXin Li usage = int(utils.read_one_line(path)) 2787*9c5db199SXin Li 2788*9c5db199SXin Li path = os.path.join(s2idle_path, 'time') 2789*9c5db199SXin Li if not os.path.exists(path): 2790*9c5db199SXin Li raise error.TestFail("File not found: {}" % path) 2791*9c5db199SXin Li 2792*9c5db199SXin Li time = int(utils.read_one_line(path)) 2793*9c5db199SXin Li 2794*9c5db199SXin Li return S2IdleStateStats(usage, time) 2795*9c5db199SXin Li 2796*9c5db199SXin Li 2797*9c5db199SXin Lidef get_cpuidle_states(cpu): 2798*9c5db199SXin Li """ 2799*9c5db199SXin Li Returns the cpuidle states of a CPU. 2800*9c5db199SXin Li """ 2801*9c5db199SXin Li cpuidle_path = os.path.join(CPU_BASE_PATH, "cpu{}/cpuidle".format(cpu)) 2802*9c5db199SXin Li 2803*9c5db199SXin Li pattern = os.path.join(cpuidle_path, 'state*') 2804*9c5db199SXin Li state_paths = glob.glob(pattern) 2805*9c5db199SXin Li 2806*9c5db199SXin Li return [s.split('/')[-1] for s in state_paths] 2807*9c5db199SXin Li 2808*9c5db199SXin Li 2809*9c5db199SXin Lidef get_s2idle_stats_for_cpu(cpu): 2810*9c5db199SXin Li """ 2811*9c5db199SXin Li Returns the s2idle stats for a CPU. 2812*9c5db199SXin Li """ 2813*9c5db199SXin Li return {s: get_s2idle_stats_for_state(cpu, s) 2814*9c5db199SXin Li for s in get_cpuidle_states(cpu) 2815*9c5db199SXin Li if get_s2idle_path(cpu, s) is not None} 2816*9c5db199SXin Li 2817*9c5db199SXin Li 2818*9c5db199SXin Lidef get_s2idle_stats(): 2819*9c5db199SXin Li """ 2820*9c5db199SXin Li Returns the s2idle stats for all CPUs. 2821*9c5db199SXin Li """ 2822*9c5db199SXin Li return {cpu: get_s2idle_stats_for_cpu(cpu) for cpu in get_online_cpus()} 2823*9c5db199SXin Li 2824*9c5db199SXin Li 2825*9c5db199SXin Lidef get_s2idle_residency_total_usecs(): 2826*9c5db199SXin Li """ 2827*9c5db199SXin Li Get total s2idle residency time for all CPUs and states. 2828*9c5db199SXin Li """ 2829*9c5db199SXin Li total_usecs = 0 2830*9c5db199SXin Li 2831*9c5db199SXin Li all_stats = get_s2idle_stats() 2832*9c5db199SXin Li for stats in all_stats.values(): 2833*9c5db199SXin Li for st in stats.values(): 2834*9c5db199SXin Li total_usecs += st.time 2835*9c5db199SXin Li 2836*9c5db199SXin Li return total_usecs 2837*9c5db199SXin Li 2838*9c5db199SXin Li 2839*9c5db199SXin Liclass S2IdleResidencyStats(object): 2840*9c5db199SXin Li """ 2841*9c5db199SXin Li Measures the s2idle residency of a given board over time. 2842*9c5db199SXin Li """ 2843*9c5db199SXin Li 2844*9c5db199SXin Li def __init__(self): 2845*9c5db199SXin Li self._initial_residency = get_s2idle_residency_total_usecs() 2846*9c5db199SXin Li 2847*9c5db199SXin Li def get_accumulated_residency_usecs(self): 2848*9c5db199SXin Li """ 2849*9c5db199SXin Li @returns s2idle residency since the class has been initialized. 2850*9c5db199SXin Li """ 2851*9c5db199SXin Li return get_s2idle_residency_total_usecs() - self._initial_residency 2852*9c5db199SXin Li 2853*9c5db199SXin Li 2854*9c5db199SXin Liclass DMCFirmwareStats(object): 2855*9c5db199SXin Li """ 2856*9c5db199SXin Li Collect DMC firmware stats of Intel based system (SKL+), (APL+). 2857*9c5db199SXin Li """ 2858*9c5db199SXin Li # Intel CPUs only transition to DC6 from DC5. https://git.io/vppcG 2859*9c5db199SXin Li DC6_ENTRY_KEY = 'DC5 -> DC6 count' 2860*9c5db199SXin Li 2861*9c5db199SXin Li def __init__(self): 2862*9c5db199SXin Li self._initial_stat = DMCFirmwareStats._parse_dmc_info() 2863*9c5db199SXin Li 2864*9c5db199SXin Li def check_fw_loaded(self): 2865*9c5db199SXin Li """Check that DMC firmware is loaded 2866*9c5db199SXin Li 2867*9c5db199SXin Li @returns boolean of DMC firmware loaded. 2868*9c5db199SXin Li """ 2869*9c5db199SXin Li return self._initial_stat['fw loaded'] 2870*9c5db199SXin Li 2871*9c5db199SXin Li def is_dc6_supported(self): 2872*9c5db199SXin Li """Check that DMC support DC6 state.""" 2873*9c5db199SXin Li return self.DC6_ENTRY_KEY in self._initial_stat 2874*9c5db199SXin Li 2875*9c5db199SXin Li def get_accumulated_dc6_entry(self): 2876*9c5db199SXin Li """Check number of DC6 state entry since the class has been initialized. 2877*9c5db199SXin Li 2878*9c5db199SXin Li @returns number of DC6 state entry. 2879*9c5db199SXin Li """ 2880*9c5db199SXin Li if not self.is_dc6_supported(): 2881*9c5db199SXin Li return 0 2882*9c5db199SXin Li 2883*9c5db199SXin Li key = self.DC6_ENTRY_KEY 2884*9c5db199SXin Li current_stat = DMCFirmwareStats._parse_dmc_info() 2885*9c5db199SXin Li return current_stat[key] - self._initial_stat[key] 2886*9c5db199SXin Li 2887*9c5db199SXin Li @staticmethod 2888*9c5db199SXin Li def _parse_dmc_info(): 2889*9c5db199SXin Li """ 2890*9c5db199SXin Li Parses DMC firmware info for Intel based systems. 2891*9c5db199SXin Li 2892*9c5db199SXin Li @returns dictionary of dmc_fw info 2893*9c5db199SXin Li @raises error.TestFail if the debugfs file not found. 2894*9c5db199SXin Li """ 2895*9c5db199SXin Li path = '/sys/kernel/debug/dri/0/i915_dmc_info' 2896*9c5db199SXin Li if not os.path.exists(path): 2897*9c5db199SXin Li raise error.TestFail('DMC info file not found.') 2898*9c5db199SXin Li 2899*9c5db199SXin Li with open(path, 'r') as f: 2900*9c5db199SXin Li lines = [line.strip() for line in f.readlines()] 2901*9c5db199SXin Li 2902*9c5db199SXin Li # For pre 4.16 kernel. https://git.io/vhThb 2903*9c5db199SXin Li if lines[0] == 'not supported': 2904*9c5db199SXin Li raise error.TestFail('DMC not supported.') 2905*9c5db199SXin Li 2906*9c5db199SXin Li ret = dict() 2907*9c5db199SXin Li for line in lines: 2908*9c5db199SXin Li key, val = line.rsplit(': ', 1) 2909*9c5db199SXin Li 2910*9c5db199SXin Li if key == 'fw loaded': 2911*9c5db199SXin Li val = val == 'yes' 2912*9c5db199SXin Li elif re.match(r'DC\d -> DC\d count', key): 2913*9c5db199SXin Li val = int(val) 2914*9c5db199SXin Li ret[key] = val 2915*9c5db199SXin Li return ret 2916*9c5db199SXin Li 2917*9c5db199SXin Li 2918*9c5db199SXin Liclass RC6ResidencyStats(object): 2919*9c5db199SXin Li """ 2920*9c5db199SXin Li Collect RC6 residency stats of Intel based system. 2921*9c5db199SXin Li """ 2922*9c5db199SXin Li def __init__(self): 2923*9c5db199SXin Li self._rc6_enable_checked = False 2924*9c5db199SXin Li self._previous_stat = self._parse_rc6_residency_info() 2925*9c5db199SXin Li self._accumulated_stat = 0 2926*9c5db199SXin Li 2927*9c5db199SXin Li # Setup max RC6 residency count for modern chips. The table order 2928*9c5db199SXin Li # is in small/big-core first, follows by the uarch name. We don't 2929*9c5db199SXin Li # need to deal with the wraparound for devices with v4.17+ kernel 2930*9c5db199SXin Li # which has the commit 817cc0791823 ("drm/i915: Handle RC6 counter wrap"). 2931*9c5db199SXin Li cpu_uarch = utils.get_intel_cpu_uarch() 2932*9c5db199SXin Li self._max_counter = { 2933*9c5db199SXin Li # Small-core w/ GEN9 LP graphics 2934*9c5db199SXin Li 'Airmont': 3579125, 2935*9c5db199SXin Li 'Goldmont': 3579125, 2936*9c5db199SXin Li # Big-core 2937*9c5db199SXin Li 'Broadwell': 5497558, 2938*9c5db199SXin Li 'Haswell': 5497558, 2939*9c5db199SXin Li 'Kaby Lake': 5497558, 2940*9c5db199SXin Li 'Skylake': 5497558, 2941*9c5db199SXin Li }.get(cpu_uarch, None) 2942*9c5db199SXin Li 2943*9c5db199SXin Li def get_accumulated_residency_msecs(self): 2944*9c5db199SXin Li """Check number of RC6 state entry since the class has been initialized. 2945*9c5db199SXin Li 2946*9c5db199SXin Li @returns int of RC6 residency in milliseconds since instantiation. 2947*9c5db199SXin Li """ 2948*9c5db199SXin Li current_stat = self._parse_rc6_residency_info() 2949*9c5db199SXin Li 2950*9c5db199SXin Li # The problem here is that we cannot assume the rc6_residency_ms is 2951*9c5db199SXin Li # monotonically increasing by current kernel i915 implementation. 2952*9c5db199SXin Li # 2953*9c5db199SXin Li # Considering different hardware has different wraparound period, 2954*9c5db199SXin Li # this is a mitigation plan to deal with different wraparound period 2955*9c5db199SXin Li # on various platforms, in order to make the test platform agnostic. 2956*9c5db199SXin Li # 2957*9c5db199SXin Li # This scarifes the accuracy of RC6 residency a bit, up on the calling 2958*9c5db199SXin Li # period. 2959*9c5db199SXin Li # 2960*9c5db199SXin Li # Reference: Bug 94852 - [SKL] rc6_residency_ms unreliable 2961*9c5db199SXin Li # (https://bugs.freedesktop.org/show_bug.cgi?id=94852) 2962*9c5db199SXin Li # 2963*9c5db199SXin Li # However for modern processors with a known overflow count, apply 2964*9c5db199SXin Li # constant of RC6 max counter to improve accuracy. 2965*9c5db199SXin Li # 2966*9c5db199SXin Li # Note that the max counter is bound for sysfs overflow, while the 2967*9c5db199SXin Li # accumulated residency here is the diff against the first reading. 2968*9c5db199SXin Li if current_stat < self._previous_stat: 2969*9c5db199SXin Li if self._max_counter is None: 2970*9c5db199SXin Li logging.warning('GPU: Detect rc6_residency_ms wraparound') 2971*9c5db199SXin Li self._accumulated_stat += current_stat 2972*9c5db199SXin Li else: 2973*9c5db199SXin Li self._accumulated_stat += current_stat + (self._max_counter - 2974*9c5db199SXin Li self._previous_stat) 2975*9c5db199SXin Li else: 2976*9c5db199SXin Li self._accumulated_stat += current_stat - self._previous_stat 2977*9c5db199SXin Li 2978*9c5db199SXin Li self._previous_stat = current_stat 2979*9c5db199SXin Li return self._accumulated_stat 2980*9c5db199SXin Li 2981*9c5db199SXin Li def _is_rc6_enable(self): 2982*9c5db199SXin Li """ 2983*9c5db199SXin Li Verified that RC6 is enable. 2984*9c5db199SXin Li 2985*9c5db199SXin Li @returns Boolean of RC6 enable status. 2986*9c5db199SXin Li @raises error.TestFail if the sysfs file not found. 2987*9c5db199SXin Li """ 2988*9c5db199SXin Li path = '/sys/class/drm/card0/power/rc6_enable' 2989*9c5db199SXin Li if not os.path.exists(path): 2990*9c5db199SXin Li raise error.TestFail('RC6 enable file not found.') 2991*9c5db199SXin Li 2992*9c5db199SXin Li return (int(utils.read_one_line(path)) & 0x1) == 0x1 2993*9c5db199SXin Li 2994*9c5db199SXin Li def _parse_rc6_residency_info(self): 2995*9c5db199SXin Li """ 2996*9c5db199SXin Li Parses RC6 residency info for Intel based systems. 2997*9c5db199SXin Li 2998*9c5db199SXin Li @returns int of RC6 residency in millisec since boot. 2999*9c5db199SXin Li @raises error.TestFail if the sysfs file not found or RC6 not enabled. 3000*9c5db199SXin Li """ 3001*9c5db199SXin Li if not self._rc6_enable_checked: 3002*9c5db199SXin Li if not self._is_rc6_enable(): 3003*9c5db199SXin Li raise error.TestFail('RC6 is not enabled.') 3004*9c5db199SXin Li self._rc6_enable_checked = True 3005*9c5db199SXin Li 3006*9c5db199SXin Li path = '/sys/class/drm/card0/power/rc6_residency_ms' 3007*9c5db199SXin Li if not os.path.exists(path): 3008*9c5db199SXin Li raise error.TestFail('RC6 residency file not found.') 3009*9c5db199SXin Li 3010*9c5db199SXin Li return int(utils.read_one_line(path)) 3011*9c5db199SXin Li 3012*9c5db199SXin Li 3013*9c5db199SXin Liclass PCHPowergatingStats(object): 3014*9c5db199SXin Li """ 3015*9c5db199SXin Li Collect PCH powergating status of intel based system. 3016*9c5db199SXin Li """ 3017*9c5db199SXin Li PMC_CORE_PATH = '/sys/kernel/debug/pmc_core/pch_ip_power_gating_status' 3018*9c5db199SXin Li TELEMETRY_PATH = '/sys/kernel/debug/telemetry/soc_states' 3019*9c5db199SXin Li 3020*9c5db199SXin Li def __init__(self): 3021*9c5db199SXin Li self._stat = {} 3022*9c5db199SXin Li 3023*9c5db199SXin Li def check_s0ix_requirement(self): 3024*9c5db199SXin Li """ 3025*9c5db199SXin Li Check PCH powergating status with S0ix requirement. 3026*9c5db199SXin Li 3027*9c5db199SXin Li @returns list of PCH IP block name that need to be powergated for low 3028*9c5db199SXin Li power consumption S0ix, empty list if none. 3029*9c5db199SXin Li """ 3030*9c5db199SXin Li # PCH IP block that is on for S0ix. Ignore these IP block. 3031*9c5db199SXin Li S0IX_ALLOWLIST = set([ 3032*9c5db199SXin Li 'PMC', 'OPI-DMI', 'SPI / eSPI', 'XHCI', 'xHCI', 'FUSE', 'Fuse', 3033*9c5db199SXin Li 'PCIE0', 'NPKVRC', 'NPKVNN', 'NPK_VNN', 'PSF1', 'PSF2', 'PSF3', 3034*9c5db199SXin Li 'PSF4', 'SBR0', 'SBR1', 'SBR2', 'SBR4', 'SBR5', 'SBR6', 'SBR7']) 3035*9c5db199SXin Li 3036*9c5db199SXin Li # PCH IP block that is on/off for S0ix depend on features enabled. 3037*9c5db199SXin Li # Add log when these IPs state are on. 3038*9c5db199SXin Li S0IX_WARNLIST = set([ 3039*9c5db199SXin Li 'HDA-PGD0', 'HDA-PGD1', 'HDA-PGD2', 'HDA-PGD3', 'LPSS', 3040*9c5db199SXin Li 'AVSPGD1', 'AVSPGD4']) 3041*9c5db199SXin Li 3042*9c5db199SXin Li # CNV device has 0x31dc as devid . 3043*9c5db199SXin Li if len(utils.system_output('lspci -d :31dc')) > 0: 3044*9c5db199SXin Li S0IX_ALLOWLIST.add('CNV') 3045*9c5db199SXin Li 3046*9c5db199SXin Li # HrP2 device has 0x02f0(CML) or 0x4df0(JSL) as devid. 3047*9c5db199SXin Li if (len(utils.system_output('lspci -d :02f0')) > 0 or 3048*9c5db199SXin Li len(utils.system_output('lspci -d :4df0')) > 0): 3049*9c5db199SXin Li S0IX_ALLOWLIST.update(['CNVI', 'NPK_AON']) 3050*9c5db199SXin Li 3051*9c5db199SXin Li on_ip = set(ip['name'] for ip in self._stat if ip['state']) 3052*9c5db199SXin Li on_ip -= S0IX_ALLOWLIST 3053*9c5db199SXin Li 3054*9c5db199SXin Li if on_ip: 3055*9c5db199SXin Li on_ip_in_warn_list = on_ip & S0IX_WARNLIST 3056*9c5db199SXin Li if on_ip_in_warn_list: 3057*9c5db199SXin Li logging.warning('Found PCH IP that may be able to powergate: %s', 3058*9c5db199SXin Li ', '.join(on_ip_in_warn_list)) 3059*9c5db199SXin Li on_ip -= S0IX_WARNLIST 3060*9c5db199SXin Li 3061*9c5db199SXin Li if on_ip: 3062*9c5db199SXin Li logging.error('Found PCH IP that need to powergate: %s', 3063*9c5db199SXin Li ', '.join(on_ip)) 3064*9c5db199SXin Li return on_ip 3065*9c5db199SXin Li return [] 3066*9c5db199SXin Li 3067*9c5db199SXin Li def read_pch_powergating_info(self, sleep_seconds=1): 3068*9c5db199SXin Li """ 3069*9c5db199SXin Li Read PCH powergating status info for Intel based systems. 3070*9c5db199SXin Li 3071*9c5db199SXin Li Intel currently shows powergating status in 2 different place in debugfs 3072*9c5db199SXin Li depend on which CPU platform. 3073*9c5db199SXin Li 3074*9c5db199SXin Li @param sleep_seconds: sleep time to make DUT idle before read the data. 3075*9c5db199SXin Li 3076*9c5db199SXin Li @raises error.TestFail if the debugfs file not found or parsing error. 3077*9c5db199SXin Li """ 3078*9c5db199SXin Li if os.path.exists(self.PMC_CORE_PATH): 3079*9c5db199SXin Li logging.info('Use PCH powergating info at %s', self.PMC_CORE_PATH) 3080*9c5db199SXin Li time.sleep(sleep_seconds) 3081*9c5db199SXin Li self._read_pcm_core_powergating_info() 3082*9c5db199SXin Li return 3083*9c5db199SXin Li 3084*9c5db199SXin Li if os.path.exists(self.TELEMETRY_PATH): 3085*9c5db199SXin Li logging.info('Use PCH powergating info at %s', self.TELEMETRY_PATH) 3086*9c5db199SXin Li time.sleep(sleep_seconds) 3087*9c5db199SXin Li self._read_telemetry_powergating_info() 3088*9c5db199SXin Li return 3089*9c5db199SXin Li 3090*9c5db199SXin Li raise error.TestFail('PCH powergating info file not found.') 3091*9c5db199SXin Li 3092*9c5db199SXin Li def _read_pcm_core_powergating_info(self): 3093*9c5db199SXin Li """ 3094*9c5db199SXin Li read_pch_powergating_info() for Intel Core KBL+ 3095*9c5db199SXin Li 3096*9c5db199SXin Li @raises error.TestFail if parsing error. 3097*9c5db199SXin Li """ 3098*9c5db199SXin Li with open(self.PMC_CORE_PATH, 'r') as f: 3099*9c5db199SXin Li lines = [line.strip() for line in f.readlines()] 3100*9c5db199SXin Li 3101*9c5db199SXin Li # Example pattern to match: 3102*9c5db199SXin Li # PCH IP: 0 - PMC State: On 3103*9c5db199SXin Li # PCH IP: 1 - SATA State: Off 3104*9c5db199SXin Li pattern = r'PCH IP:\s+(?P<id>\d+)\s+' \ 3105*9c5db199SXin Li r'- (?P<name>.*\w)\s+' \ 3106*9c5db199SXin Li r'State: (?P<state>Off|On)' 3107*9c5db199SXin Li matcher = re.compile(pattern) 3108*9c5db199SXin Li ret = [] 3109*9c5db199SXin Li for i, line in enumerate(lines): 3110*9c5db199SXin Li match = matcher.match(line) 3111*9c5db199SXin Li if not match: 3112*9c5db199SXin Li raise error.TestFail('Can not parse PCH powergating info: ', 3113*9c5db199SXin Li line) 3114*9c5db199SXin Li 3115*9c5db199SXin Li index = int(match.group('id')) 3116*9c5db199SXin Li if i != index: 3117*9c5db199SXin Li raise error.TestFail('Wrong index for PCH powergating info: ', 3118*9c5db199SXin Li line) 3119*9c5db199SXin Li 3120*9c5db199SXin Li name = match.group('name') 3121*9c5db199SXin Li state = match.group('state') == 'On' 3122*9c5db199SXin Li 3123*9c5db199SXin Li ret.append({'name': name, 'state': state}) 3124*9c5db199SXin Li self._stat = ret 3125*9c5db199SXin Li 3126*9c5db199SXin Li def _read_telemetry_powergating_info(self): 3127*9c5db199SXin Li """ 3128*9c5db199SXin Li read_pch_powergating_info() for Intel Atom APL+ 3129*9c5db199SXin Li 3130*9c5db199SXin Li @raises error.TestFail if parsing error. 3131*9c5db199SXin Li """ 3132*9c5db199SXin Li with open(self.TELEMETRY_PATH, 'r') as f: 3133*9c5db199SXin Li raw_str = f.read() 3134*9c5db199SXin Li 3135*9c5db199SXin Li # Example pattern to match: 3136*9c5db199SXin Li # -------------------------------------- 3137*9c5db199SXin Li # South Complex PowerGate Status 3138*9c5db199SXin Li # -------------------------------------- 3139*9c5db199SXin Li # Device PG 3140*9c5db199SXin Li # LPSS 1 3141*9c5db199SXin Li # SPI 1 3142*9c5db199SXin Li # FUSE 0 3143*9c5db199SXin Li # 3144*9c5db199SXin Li # --------------------------------------- 3145*9c5db199SXin Li trimed_pattern = r'.*South Complex PowerGate Status\n' \ 3146*9c5db199SXin Li r'-+\n' \ 3147*9c5db199SXin Li r'Device\s+PG\n' \ 3148*9c5db199SXin Li r'(?P<trimmed_section>(\w+\s+[0|1]\n)+)' \ 3149*9c5db199SXin Li r'\n-+\n.*' 3150*9c5db199SXin Li trimed_match = re.match(trimed_pattern, raw_str, re.DOTALL) 3151*9c5db199SXin Li if not trimed_match: 3152*9c5db199SXin Li raise error.TestFail('Can not parse PCH powergating info: ', 3153*9c5db199SXin Li raw_str) 3154*9c5db199SXin Li trimmed_str = trimed_match.group('trimmed_section').strip() 3155*9c5db199SXin Li lines = [line.strip() for line in trimmed_str.split('\n')] 3156*9c5db199SXin Li 3157*9c5db199SXin Li matcher = re.compile(r'(?P<name>\w+)\s+(?P<state>[0|1])') 3158*9c5db199SXin Li ret = [] 3159*9c5db199SXin Li for line in lines: 3160*9c5db199SXin Li match = matcher.match(line) 3161*9c5db199SXin Li if not match: 3162*9c5db199SXin Li raise error.TestFail('Can not parse PCH powergating info: %s', 3163*9c5db199SXin Li line) 3164*9c5db199SXin Li 3165*9c5db199SXin Li name = match.group('name') 3166*9c5db199SXin Li state = match.group('state') == '0' # 0 means on and 1 means off 3167*9c5db199SXin Li 3168*9c5db199SXin Li ret.append({'name': name, 'state': state}) 3169*9c5db199SXin Li self._stat = ret 3170*9c5db199SXin Li 3171*9c5db199SXin Lidef has_rc6_support(): 3172*9c5db199SXin Li """ 3173*9c5db199SXin Li Helper to examine that RC6 is enabled with residency counter. 3174*9c5db199SXin Li 3175*9c5db199SXin Li @returns Boolean of RC6 support status. 3176*9c5db199SXin Li """ 3177*9c5db199SXin Li enable_path = '/sys/class/drm/card0/power/rc6_enable' 3178*9c5db199SXin Li residency_path = '/sys/class/drm/card0/power/rc6_residency_ms' 3179*9c5db199SXin Li 3180*9c5db199SXin Li has_rc6_enabled = os.path.exists(enable_path) 3181*9c5db199SXin Li has_rc6_residency = False 3182*9c5db199SXin Li rc6_enable_mask = 0 3183*9c5db199SXin Li 3184*9c5db199SXin Li if has_rc6_enabled: 3185*9c5db199SXin Li # TODO (harry.pan): Some old chip has RC6P and RC6PP 3186*9c5db199SXin Li # in the bits[1:2]; in case of that, ideally these time 3187*9c5db199SXin Li # slice will fall into RC0, fix it up if required. 3188*9c5db199SXin Li rc6_enable_mask = int(utils.read_one_line(enable_path)) 3189*9c5db199SXin Li has_rc6_enabled &= (rc6_enable_mask) & 0x1 == 0x1 3190*9c5db199SXin Li has_rc6_residency = os.path.exists(residency_path) 3191*9c5db199SXin Li 3192*9c5db199SXin Li logging.debug("GPU: RC6 residency support: %s, mask: 0x%x", 3193*9c5db199SXin Li {True: "yes", False: "no"} [has_rc6_enabled and has_rc6_residency], 3194*9c5db199SXin Li rc6_enable_mask) 3195*9c5db199SXin Li 3196*9c5db199SXin Li return (has_rc6_enabled and has_rc6_residency) 3197*9c5db199SXin Li 3198*9c5db199SXin Liclass GPURC6Stats(AbstractStats): 3199*9c5db199SXin Li """ 3200*9c5db199SXin Li GPU RC6 statistics to give ratio of RC6 and RC0 residency 3201*9c5db199SXin Li 3202*9c5db199SXin Li Protected Attributes: 3203*9c5db199SXin Li _rc6: object of RC6ResidencyStats 3204*9c5db199SXin Li """ 3205*9c5db199SXin Li def __init__(self): 3206*9c5db199SXin Li self._rc6 = RC6ResidencyStats() 3207*9c5db199SXin Li super(GPURC6Stats, self).__init__(name='gpuidle') 3208*9c5db199SXin Li 3209*9c5db199SXin Li def _read_stats(self): 3210*9c5db199SXin Li total = int(time.time() * 1000) 3211*9c5db199SXin Li msecs = self._rc6.get_accumulated_residency_msecs() 3212*9c5db199SXin Li stats = collections.defaultdict(int) 3213*9c5db199SXin Li stats['RC6'] += msecs 3214*9c5db199SXin Li stats['RC0'] += total - msecs 3215*9c5db199SXin Li logging.debug("GPU: RC6 residency: %d ms", msecs) 3216*9c5db199SXin Li return stats 3217