Home | History | Annotate | Download | only in profiler
      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 
      7 http://msoon.com/LabEquipment/PowerMonitor/
      8 Data collected is a namedtuple of (amps, volts), at 5000 samples/second.
      9 Output graph plots power in watts over time in seconds.
     10 """
     11 
     12 import csv
     13 import multiprocessing
     14 
     15 from telemetry.core import exceptions
     16 from telemetry.internal.platform import profiler
     17 from telemetry.internal.platform.profiler import monsoon
     18 from telemetry.util import statistics
     19 
     20 
     21 def _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 
     67 class 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