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