1# Copyright 2013 The Chromium Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5"""Profiler using data collected from a Monsoon power meter. 6 7http://msoon.com/LabEquipment/PowerMonitor/ 8Data collected is a namedtuple of (amps, volts), at 5000 samples/second. 9Output graph plots power in watts over time in seconds. 10""" 11 12import csv 13import multiprocessing 14 15from telemetry.core import exceptions 16from telemetry.internal.platform import profiler 17from telemetry.internal.platform.profiler import monsoon 18from telemetry.util import statistics 19 20 21def _CollectData(output_path, is_collecting): 22 mon = monsoon.Monsoon(wait=False) 23 # Note: Telemetry requires the device to be connected by USB, but that 24 # puts it in charging mode. This increases the power consumption. 25 mon.SetUsbPassthrough(1) 26 # Nominal Li-ion voltage is 3.7V, but it puts out 4.2V at max capacity. Use 27 # 4.0V to simulate a "~80%" charged battery. Google "li-ion voltage curve". 28 # This is true only for a single cell. (Most smartphones, some tablets.) 29 mon.SetVoltage(4.0) 30 31 samples = [] 32 try: 33 mon.StartDataCollection() 34 # Do one CollectData() to make the Monsoon set up, which takes about 35 # 0.3 seconds, and only signal that we've started after that. 36 mon.CollectData() 37 is_collecting.set() 38 while is_collecting.is_set(): 39 samples += mon.CollectData() 40 finally: 41 mon.StopDataCollection() 42 43 # Add x-axis labels. 44 plot_data = [(i / 5000., sample.amps * sample.volts) 45 for i, sample in enumerate(samples)] 46 47 # Print data in csv. 48 with open(output_path, 'w') as output_file: 49 output_writer = csv.writer(output_file) 50 output_writer.writerows(plot_data) 51 output_file.flush() 52 53 power_samples = [s.amps * s.volts for s in samples] 54 55 print 'Monsoon profile power readings in watts:' 56 print ' Total = %f' % statistics.TrapezoidalRule(power_samples, 1/5000.) 57 print (' Average = %f' % statistics.ArithmeticMean(power_samples) + 58 '+-%f' % statistics.StandardDeviation(power_samples)) 59 print ' Peak = %f' % max(power_samples) 60 print ' Duration = %f' % (len(power_samples) / 5000.) 61 62 print 'To view the Monsoon profile, run:' 63 print (' echo "set datafile separator \',\'; plot \'%s\' with lines" | ' 64 'gnuplot --persist' % output_path) 65 66 67class MonsoonProfiler(profiler.Profiler): 68 def __init__(self, browser_backend, platform_backend, output_path, state): 69 super(MonsoonProfiler, self).__init__( 70 browser_backend, platform_backend, output_path, state) 71 # We collect the data in a separate process, so we can continuously 72 # read the samples from the USB port while running the test. 73 self._is_collecting = multiprocessing.Event() 74 self._collector = multiprocessing.Process( 75 target=_CollectData, args=(output_path, self._is_collecting)) 76 self._collector.start() 77 if not self._is_collecting.wait(timeout=0.5): 78 self._collector.terminate() 79 raise exceptions.ProfilingException('Failed to start data collection.') 80 81 @classmethod 82 def name(cls): 83 return 'monsoon' 84 85 @classmethod 86 def is_supported(cls, browser_type): 87 try: 88 monsoon.Monsoon(wait=False) 89 except EnvironmentError: 90 return False 91 else: 92 return True 93 94 def CollectProfile(self): 95 self._is_collecting.clear() 96 self._collector.join() 97 return [self._output_path] 98