aboutsummaryrefslogtreecommitdiff
path: root/catapult/telemetry/telemetry/internal/platform/power_monitor/monsoon_power_monitor.py
diff options
context:
space:
mode:
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.py120
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}