13551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)# Copyright 2013 The Chromium Authors. All rights reserved.
23551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be
33551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)# found in the LICENSE file.
43551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)
5a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)"""Profiler using data collected from a Monsoon power meter.
6a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
7a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)http://msoon.com/LabEquipment/PowerMonitor/
8a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)Data collected is a namedtuple of (amps, volts), at 5000 samples/second.
9a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)Output graph plots power in watts over time in seconds.
10a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)"""
11a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
123551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)import csv
133551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)import multiprocessing
143551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)
153551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)from telemetry.core import exceptions
163551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)from telemetry.core.platform import profiler
175d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)from telemetry.core.platform.profiler import monsoon
18a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)from telemetry.util import statistics
193551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)
203551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)
213551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)def _CollectData(output_path, is_collecting):
223551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  mon = monsoon.Monsoon(wait=False)
233551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  # Note: Telemetry requires the device to be connected by USB, but that
243551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  # puts it in charging mode. This increases the power consumption.
253551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  mon.SetUsbPassthrough(1)
263551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  # Nominal Li-ion voltage is 3.7V, but it puts out 4.2V at max capacity. Use
273551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  # 4.0V to simulate a "~80%" charged battery. Google "li-ion voltage curve".
283551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  # This is true only for a single cell. (Most smartphones, some tablets.)
293551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  mon.SetVoltage(4.0)
303551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)
313551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  samples = []
323551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  try:
333551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    mon.StartDataCollection()
343551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    # Do one CollectData() to make the Monsoon set up, which takes about
353551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    # 0.3 seconds, and only signal that we've started after that.
363551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    mon.CollectData()
373551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    is_collecting.set()
383551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    while is_collecting.is_set():
393551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      samples += mon.CollectData()
403551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  finally:
413551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    mon.StopDataCollection()
423551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)
433551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  # Add x-axis labels.
44a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  plot_data = [(i / 5000., sample.amps * sample.volts)
45a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)               for i, sample in enumerate(samples)]
463551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)
473551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  # Print data in csv.
483551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  with open(output_path, 'w') as output_file:
493551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    output_writer = csv.writer(output_file)
503551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    output_writer.writerows(plot_data)
513551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    output_file.flush()
523551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)
53a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  power_samples = [s.amps * s.volts for s in samples]
54a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
55a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  print 'Monsoon profile power readings in watts:'
56a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  print ('  Total    = %f' % statistics.TrapezoidalRule(power_samples, 1/5000.))
57a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  print ('  Average  = %f' % statistics.ArithmeticMean(power_samples) +
58a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)         '+-%f' % statistics.StandardDeviation(power_samples))
59a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  print ('  Peak     = %f' % max(power_samples))
60a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  print ('  Duration = %f' % (len(power_samples) / 5000.))
61a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
623551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  print 'To view the Monsoon profile, run:'
633551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  print ('  echo "set datafile separator \',\'; plot \'%s\' with lines" | '
643551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      'gnuplot --persist' % output_path)
653551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)
663551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)
673551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)class MonsoonProfiler(profiler.Profiler):
684e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  def __init__(self, browser_backend, platform_backend, output_path, state):
693551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    super(MonsoonProfiler, self).__init__(
704e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)        browser_backend, platform_backend, output_path, state)
713551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    # We collect the data in a separate process, so we can continuously
723551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    # read the samples from the USB port while running the test.
733551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    self._is_collecting = multiprocessing.Event()
743551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    self._collector = multiprocessing.Process(
753551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)        target=_CollectData, args=(output_path, self._is_collecting))
763551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    self._collector.start()
773551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    if not self._is_collecting.wait(timeout=0.5):
783551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      self._collector.terminate()
793551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      raise exceptions.ProfilingException('Failed to start data collection.')
803551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)
813551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  @classmethod
823551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  def name(cls):
833551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    return 'monsoon'
843551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)
853551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  @classmethod
8658537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)  def is_supported(cls, browser_type):
873551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    try:
883551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      monsoon.Monsoon(wait=False)
895d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    except EnvironmentError:
903551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      return False
913551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    else:
923551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      return True
933551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)
943551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  def CollectProfile(self):
953551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    self._is_collecting.clear()
963551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    self._collector.join()
973551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    return [self._output_path]
98