1# Copyright (c) 2017 The Chromium OS Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5import json, numpy, os, time, urllib, urllib2 6 7from autotest_lib.client.bin import utils 8from autotest_lib.client.common_lib import lsbrelease_utils 9from autotest_lib.client.cros.power import power_status 10from autotest_lib.client.cros.power import power_utils 11 12 13class BaseDashboard(object): 14 """Base class that implements method for prepare and upload data to power 15 dashboard. 16 """ 17 18 def __init__(self, logger, testname, resultsdir=None, uploadurl=None): 19 """Create BaseDashboard objects 20 21 Args: 22 logger: object that store the log. This will get convert to 23 dictionary by self._convert() 24 testname: name of current test 25 resultsdir: directory to save the power json 26 uploadurl: url to upload power data 27 """ 28 self._logger = logger 29 self._testname = testname 30 self._resultsdir = resultsdir 31 self._uploadurl = uploadurl 32 33 34 def _create_powerlog_dict(self, raw_measurement): 35 """Create powerlog dictionary from raw measurement data 36 Data format in go/power-dashboard-data 37 38 Args: 39 raw_measurement: dictionary contains raw measurement data. 40 41 Returns: 42 A dictionary of powerlog. 43 """ 44 powerlog_dict = { 45 'format_version': 2, 46 'timestamp': time.time(), 47 'test': self._testname, 48 'dut': { 49 'board': utils.get_board(), 50 'version': { 51 'hw': utils.get_hardware_revision(), 52 'milestone': 53 lsbrelease_utils.get_chromeos_release_milestone(), 54 'os': lsbrelease_utils.get_chromeos_release_version(), 55 'channel': lsbrelease_utils.get_chromeos_channel(), 56 'firmware': utils.get_firmware_version(), 57 'ec': utils.get_ec_version(), 58 'kernel': utils.get_kernel_version(), 59 }, 60 'sku' : { 61 'cpu': utils.get_cpu_name(), 62 'memory_size': utils.get_mem_total_gb(), 63 'storage_size': 64 utils.get_disk_size_gb(utils.get_root_device()), 65 'display_resolution': utils.get_screen_resolution(), 66 }, 67 'ina': { 68 'version': 0, 69 'ina': raw_measurement['data'].keys() 70 }, 71 'note': '' 72 }, 73 'power': raw_measurement 74 } 75 76 if power_utils.has_battery(): 77 # Round the battery size to nearest tenth because it is fluctuated 78 # for platform without battery norminal voltage data. 79 powerlog_dict['dut']['sku']['battery_size'] = round( 80 power_status.get_status().battery[0].energy_full_design, 1) 81 powerlog_dict['dut']['sku']['battery_shutdown_percent'] = \ 82 power_utils.get_low_battery_shutdown_percent() 83 return powerlog_dict 84 85 86 def _save_json(self, powerlog_dict, resultsdir, filename='power_log.json'): 87 """Convert powerlog dict to human readable formatted JSON and 88 save to <resultsdir>/<filename> 89 90 Args: 91 powerlog_dict: dictionary of power data 92 resultsdir: directory to save formatted JSON object 93 filename: filename to save 94 """ 95 filename = os.path.join(resultsdir, filename) 96 with file(filename, 'w') as f: 97 json.dump(powerlog_dict, f, indent=4, separators=(',', ': ')) 98 99 100 def _upload(self, powerlog_dict, uploadurl): 101 """Convert powerlog dict to minimal size JSON and upload to dashboard. 102 103 Args: 104 powerlog_dict: dictionary of power data 105 """ 106 data_obj = {'data': json.dumps(powerlog_dict)} 107 encoded = urllib.urlencode(data_obj) 108 req = urllib2.Request(uploadurl, encoded) 109 urllib2.urlopen(req) 110 111 112 def _convert(self): 113 """Convert data from self._logger object to raw power measurement 114 dictionary. 115 116 MUST be implemented in subclass 117 118 Return: 119 raw measurement dictionary 120 """ 121 raise NotImplementedError 122 123 def upload(self): 124 """Upload powerlog to dashboard and save data to results directory. 125 """ 126 raw_measurement = self._convert() 127 powerlog_dict = self._create_powerlog_dict(raw_measurement) 128 if self._resultsdir is not None: 129 self._save_json(powerlog_dict, self._resultsdir) 130 if self._uploadurl is not None: 131 self._upload(powerlog_dict, self._uploadurl) 132 133 134class MeasurementLoggerDashboard(BaseDashboard): 135 """Dashboard class for power_status.MeasurementLogger 136 """ 137 138 def _convert(self): 139 """Convert data from power_status.MeasurementLogger object to raw 140 power measurement dictionary. 141 142 Return: 143 raw measurement dictionary 144 """ 145 power_dict = { 146 'sample_count': len(self._logger.readings), 147 'sample_duration': 0, 148 'average': dict(), 149 'data': dict() 150 } 151 if power_dict['sample_count'] > 1: 152 total_duration = self._logger.times[-1] - self._logger.times[0] 153 power_dict['sample_duration'] = \ 154 1.0 * total_duration / (power_dict['sample_count'] - 1) 155 156 for i, domain_readings in enumerate(zip(*self._logger.readings)): 157 domain = self._logger.domains[i] 158 power_dict['data'][domain] = domain_readings 159 power_dict['average'][domain] = numpy.average(domain_readings) 160 return power_dict 161 162 163class PowerLoggerDashboard(MeasurementLoggerDashboard): 164 """Dashboard class for power_status.PowerLogger 165 """ 166 167 def __init__(self, logger, testname, resultsdir=None, uploadurl=None): 168 if uploadurl is None: 169 uploadurl = 'http://chrome-power.appspot.com/rapl' 170 super(PowerLoggerDashboard, self).__init__(logger, testname, resultsdir, 171 uploadurl) 172