• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2014 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import json
6import multiprocessing
7import tempfile
8import time
9
10from telemetry.core import exceptions
11from telemetry.core.platform.profiler import monsoon
12import telemetry.core.platform.power_monitor as power_monitor
13
14
15def _MonitorPower(device, is_collecting, output):
16  """Monitoring process
17     Args:
18       device: A profiler.monsoon object to collect samples from.
19       is_collecting: the event to synchronize on.
20       output: opened file to write the samples.
21  """
22  with output:
23    samples = []
24    start_time = None
25    end_time = None
26    try:
27      device.StartDataCollection()
28      is_collecting.set()
29      # First sample also calibrate the computation.
30      device.CollectData()
31      start_time = time.time()
32      while is_collecting.is_set():
33        new_data = device.CollectData()
34        assert new_data, 'Unable to collect data from device'
35        samples += new_data
36      end_time = time.time()
37    finally:
38      device.StopDataCollection()
39    result = {
40      'duration_s': end_time - start_time,
41      'samples': samples
42    }
43    json.dump(result, output)
44
45class MonsoonPowerMonitor(power_monitor.PowerMonitor):
46  def __init__(self):
47    super(MonsoonPowerMonitor, self).__init__()
48    self._powermonitor_process = None
49    self._powermonitor_output_file = None
50    self._is_collecting = None
51    self._monsoon = None
52    try:
53      self._monsoon = monsoon.Monsoon(wait=False)
54      # Nominal Li-ion voltage is 3.7V, but it puts out 4.2V at max capacity.
55      # Use 4.0V to simulate a "~80%" charged battery. Google "li-ion voltage
56      # curve". This is true only for a single cell. (Most smartphones, some
57      # tablets.)
58      self._monsoon.SetVoltage(4.0)
59    except EnvironmentError:
60      self._monsoon = None
61
62  def CanMonitorPower(self):
63    return self._monsoon is not None
64
65  def StartMonitoringPower(self, browser):
66    assert not self._powermonitor_process, (
67        'Must call StopMonitoringPower().')
68    self._powermonitor_output_file = tempfile.TemporaryFile()
69    self._is_collecting = multiprocessing.Event()
70    self._powermonitor_process = multiprocessing.Process(
71        target=_MonitorPower,
72        args=(self._monsoon,
73              self._is_collecting,
74              self._powermonitor_output_file))
75    self._powermonitor_process.start()
76    if not self._is_collecting.wait(timeout=0.5):
77      self._powermonitor_process.terminate()
78      raise exceptions.ProfilingException('Failed to start data collection.')
79
80  def StopMonitoringPower(self):
81    assert self._powermonitor_process, (
82        'StartMonitoringPower() not called.')
83    try:
84      # Tell powermonitor to take an immediate sample and join.
85      self._is_collecting.clear()
86      self._powermonitor_process.join()
87      with self._powermonitor_output_file:
88        self._powermonitor_output_file.seek(0)
89        powermonitor_output = self._powermonitor_output_file.read()
90      assert powermonitor_output, 'PowerMonitor produced no output'
91      return MonsoonPowerMonitor.ParseSamplingOutput(powermonitor_output)
92    finally:
93      self._powermonitor_output_file = None
94      self._powermonitor_process = None
95      self._is_collecting = None
96
97  @staticmethod
98  def ParseSamplingOutput(powermonitor_output):
99    """Parse the output of of the samples collector process.
100
101    Returns:
102        Dictionary in the format returned by StopMonitoringPower().
103    """
104    power_samples = []
105    total_energy_consumption_mwh = 0
106    result = json.loads(powermonitor_output)
107    timedelta_h = result['duration_s'] / len(result['samples']) / 3600
108    for (current_a, voltage_v) in result['samples']:
109      energy_consumption_mw = current_a * voltage_v * 10**3
110      total_energy_consumption_mwh += energy_consumption_mw * timedelta_h
111      power_samples.append(energy_consumption_mw)
112    # -------- Collect and Process Data -------------
113    out_dict = {}
114    # Raw power usage samples.
115    out_dict['identifier'] = 'monsoon'
116    out_dict['power_samples_mw'] = power_samples
117    out_dict['energy_consumption_mwh'] = total_energy_consumption_mwh
118
119    return out_dict
120