blob: abbd9f4de158b0d1ce5e19b0c30fa6dd6b442f82 [file] [log] [blame]
# 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}