diff options
Diffstat (limited to 'catapult/telemetry/telemetry/internal/platform/power_monitor/monsoon_power_monitor.py')
-rw-r--r-- | catapult/telemetry/telemetry/internal/platform/power_monitor/monsoon_power_monitor.py | 120 |
1 files changed, 120 insertions, 0 deletions
diff --git a/catapult/telemetry/telemetry/internal/platform/power_monitor/monsoon_power_monitor.py b/catapult/telemetry/telemetry/internal/platform/power_monitor/monsoon_power_monitor.py new file mode 100644 index 00000000..abbd9f4d --- /dev/null +++ b/catapult/telemetry/telemetry/internal/platform/power_monitor/monsoon_power_monitor.py @@ -0,0 +1,120 @@ +# Copyright 2014 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. + +import json +import logging +import multiprocessing +import tempfile +import time + +from telemetry.core import exceptions +from telemetry.internal.platform.power_monitor import android_power_monitor_base +from telemetry.internal.platform.profiler import monsoon + + +def _MonitorPower(device, is_collecting, output): + """Monitoring process + Args: + device: A profiler.monsoon object to collect samples from. + is_collecting: the event to synchronize on. + output: opened file to write the samples. + """ + with output: + samples = [] + start_time = None + end_time = None + try: + device.StartDataCollection() + is_collecting.set() + # First sample also calibrate the computation. + device.CollectData() + start_time = time.time() + while is_collecting.is_set(): + new_data = device.CollectData() + assert new_data, 'Unable to collect data from device' + samples += new_data + end_time = time.time() + finally: + device.StopDataCollection() + result = { + 'duration_s': end_time - start_time, + 'samples': samples + } + json.dump(result, output) + + +class MonsoonPowerMonitor(android_power_monitor_base.AndroidPowerMonitorBase): + def __init__(self, _, platform_backend): + super(MonsoonPowerMonitor, self).__init__() + self._powermonitor_process = None + self._powermonitor_output_file = None + self._is_collecting = None + self._monsoon = None + self._platform = platform_backend + try: + self._monsoon = monsoon.Monsoon(wait=False) + # 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.) + self._monsoon.SetVoltage(4.0) + except EnvironmentError: + self._monsoon = None + + def CanMonitorPower(self): + return self._monsoon is not None + + def StartMonitoringPower(self, browser): + self._CheckStart() + self._powermonitor_output_file = tempfile.TemporaryFile() + self._is_collecting = multiprocessing.Event() + self._powermonitor_process = multiprocessing.Process( + target=_MonitorPower, + args=(self._monsoon, + self._is_collecting, + self._powermonitor_output_file)) + # Ensure child is not left behind: parent kills daemonic children on exit. + self._powermonitor_process.daemon = True + self._powermonitor_process.start() + if not self._is_collecting.wait(timeout=0.5): + self._powermonitor_process.terminate() + raise exceptions.ProfilingException('Failed to start data collection.') + + def StopMonitoringPower(self): + self._CheckStop() + try: + # Tell powermonitor to take an immediate sample and join. + self._is_collecting.clear() + self._powermonitor_process.join() + with self._powermonitor_output_file: + self._powermonitor_output_file.seek(0) + powermonitor_output = self._powermonitor_output_file.read() + assert powermonitor_output, 'PowerMonitor produced no output' + return MonsoonPowerMonitor.ParseSamplingOutput(powermonitor_output) + finally: + self._powermonitor_output_file = None + self._powermonitor_process = None + self._is_collecting = None + + @staticmethod + def ParseSamplingOutput(powermonitor_output): + """Parse the output of of the samples collector process. + + Returns: + Dictionary in the format returned by StopMonitoringPower(). + """ + result = json.loads(powermonitor_output) + if result['samples']: + timedelta_h = (result['duration_s'] / len(result['samples'])) / 3600.0 + power_samples = [current_a * voltage_v * 10**3 + for (current_a, voltage_v) in result['samples']] + total_energy_consumption_mwh = sum(power_samples) * timedelta_h + else: + logging.warning('Sample information not available.') + power_samples = [] + total_energy_consumption_mwh = 0 + + return {'identifier':'monsoon', + 'power_samples_mw':power_samples, + 'monsoon_energy_consumption_mwh':total_energy_consumption_mwh} |