# Copyright 2013 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Profiler using data collected from a Monsoon power meter. http://msoon.com/LabEquipment/PowerMonitor/ Data collected is a namedtuple of (amps, volts), at 5000 samples/second. Output graph plots power in watts over time in seconds. """ import csv import multiprocessing from telemetry.core import exceptions from telemetry.internal.platform import profiler from telemetry.internal.platform.profiler import monsoon from telemetry.util import statistics def _CollectData(output_path, is_collecting): mon = monsoon.Monsoon(wait=False) # Note: Telemetry requires the device to be connected by USB, but that # puts it in charging mode. This increases the power consumption. mon.SetUsbPassthrough(1) # Nominal Li-ion voltage is 3.7V, but it puts out 4.2V at max capacity. Use # 4.0V to simulate a "~80%" charged battery. Google "li-ion voltage curve". # This is true only for a single cell. (Most smartphones, some tablets.) mon.SetVoltage(4.0) samples = [] try: mon.StartDataCollection() # Do one CollectData() to make the Monsoon set up, which takes about # 0.3 seconds, and only signal that we've started after that. mon.CollectData() is_collecting.set() while is_collecting.is_set(): samples += mon.CollectData() finally: mon.StopDataCollection() # Add x-axis labels. plot_data = [(i / 5000., sample.amps * sample.volts) for i, sample in enumerate(samples)] # Print data in csv. with open(output_path, 'w') as output_file: output_writer = csv.writer(output_file) output_writer.writerows(plot_data) output_file.flush() power_samples = [s.amps * s.volts for s in samples] print 'Monsoon profile power readings in watts:' print ' Total = %f' % statistics.TrapezoidalRule(power_samples, 1/5000.) print (' Average = %f' % statistics.ArithmeticMean(power_samples) + '+-%f' % statistics.StandardDeviation(power_samples)) print ' Peak = %f' % max(power_samples) print ' Duration = %f' % (len(power_samples) / 5000.) print 'To view the Monsoon profile, run:' print (' echo "set datafile separator \',\'; plot \'%s\' with lines" | ' 'gnuplot --persist' % output_path) class MonsoonProfiler(profiler.Profiler): def __init__(self, browser_backend, platform_backend, output_path, state): super(MonsoonProfiler, self).__init__( browser_backend, platform_backend, output_path, state) # We collect the data in a separate process, so we can continuously # read the samples from the USB port while running the test. self._is_collecting = multiprocessing.Event() self._collector = multiprocessing.Process( target=_CollectData, args=(output_path, self._is_collecting)) self._collector.start() if not self._is_collecting.wait(timeout=0.5): self._collector.terminate() raise exceptions.ProfilingException('Failed to start data collection.') @classmethod def name(cls): return 'monsoon' @classmethod def is_supported(cls, browser_type): try: monsoon.Monsoon(wait=False) except EnvironmentError: return False else: return True def CollectProfile(self): self._is_collecting.clear() self._collector.join() return [self._output_path]