Index: systrace/systrace/tracing_agents/monsoon_agent.py |
diff --git a/systrace/systrace/tracing_agents/monsoon_agent.py b/systrace/systrace/tracing_agents/monsoon_agent.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..ef5291796bd0e8374db3e6dd78092579b893d86f |
--- /dev/null |
+++ b/systrace/systrace/tracing_agents/monsoon_agent.py |
@@ -0,0 +1,179 @@ |
+# Copyright 2017 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 optparse |
+import py_utils |
+import StringIO |
+import tempfile |
+import time |
+ |
+from devil.android.device_utils import DeviceUtils |
+from py_trace_event import trace_time |
+from systrace import tracing_agents |
+from systrace import trace_result |
+from monsoon.monsoon_wrapper import MonsoonWrapper # pylint: disable=import-error |
+from monsoon.monsoon_utils import ReadSamplesFromFile # pylint: disable=import-error |
+from monsoon.monsoon_utils import TransformSamplesWithFrequency # pylint: disable=import-error |
+ |
+ |
+class MonsoonConfig(tracing_agents.TracingConfig): |
+ def __init__(self, monsoon, frequency, latency_ms, device_serial_number): |
+ tracing_agents.TracingConfig.__init__(self) |
+ self.monsoon = monsoon |
+ self.frequency = frequency |
+ self.latency_ms = latency_ms |
+ self.device_serial_number = device_serial_number |
+ |
+def add_options(parser): |
+ options = optparse.OptionGroup(parser, 'Monsoon trace options') |
+ options.add_option('--monsoon', dest='monsoon', default=None, |
+ action='store', help='Path to monsoon controller script.') |
+ options.add_option('--monsoon_freq', dest='freq', default=10, |
+ action='store', help='Frequency of power monitoring in '+ |
+ 'hertz. Default is 10.') |
+ # The default latency value here has been arrived at by empirical measurement. |
+ # There is latency, it just may vary from machine to machine, depend on USB |
+ # hubs between device and host, USB bus contention, etc. If higher accuracy |
+ # is required it should be up to the user to determine their Monsoon power |
+ # latency for their local setup and use this parameter to override the |
+ # default. |
+ options.add_option('--monsoon_latency', dest='latency_ms', default=3.5, |
+ action='store', help='Latency of monsoon power measurement'+ |
+ ' in milliseconds. Value varies depending upon host ' + |
+ 'configuration and Monsoon device. Default is 3.5ms.') |
+ return options |
+ |
+def get_config(options): |
+ return MonsoonConfig( |
+ options.monsoon, int(options.freq), options.latency_ms, |
+ options.device_serial_number) |
+ |
+def try_create_agent(config): |
+ """Create a Monsoon power monitor agent. |
+ Retrieves voltage from the Monsoon device with USB disabled. BattOr logs |
+ include voltage which changes constantly. In comparison, Monsoon is a constant |
+ voltage source whose voltage only needs to be queried once. |
+ |
+ Args: |
+ config: Command line config. |
+ """ |
+ if type(config.monsoon) is not str: |
+ return None |
+ |
+ device = DeviceUtils(config.device_serial_number) |
+ monsoon_wrapper = MonsoonWrapper(config.monsoon) |
+ |
+ # Read the status values while USB is disconnected, reconnecting afterwards |
+ # for other agents to initialize. |
+ monsoon_wrapper.DisableUSB() |
+ status = monsoon_wrapper.ReadStatusProperties() |
+ monsoon_wrapper.EnableUSB() |
+ device.adb.WaitForDevice() |
+ |
+ if "voltage1" not in status: |
+ raise Exception("Could not read voltage") |
+ |
+ voltage = float(status["voltage1"]) |
+ |
+ return MonsoonTracingAgent(monsoon_wrapper, device, voltage, |
+ config.latency_ms, config.frequency) |
+ |
+class MonsoonTracingAgent(tracing_agents.TracingAgent): |
+ def __init__(self, monsoon_wrapper, device, voltage, latency_ms, frequency): |
+ super(MonsoonTracingAgent, self).__init__() |
+ self._monsoon_wrapper = monsoon_wrapper |
+ self._device = device |
+ self._voltage = voltage |
+ self._latency_seconds = latency_ms / 1e3 |
+ self._frequency = frequency |
+ self._sync_id = None |
+ self._sync_timestamp = None |
+ self._result_file = None |
+ self._trace_start = None |
+ |
+ def SupportsExplicitClockSync(self): |
+ """Returns whether this supports explicit clock sync. |
+ |
+ Although Monsoon agent implement the clock sync marker, this is performed |
+ explicitly from the trace controller as the Monsoon agent has to immediately |
+ reconnect USB afterwards for the other agents to issue clock sync markers |
+ and to retrieve data. |
+ """ |
+ return False |
+ |
+ def RecordClockSyncMarker(self, sync_id, did_record_sync_marker_callback): |
+ # pylint: disable=unused-argument |
+ assert self.SupportsExplicitClockSync(), ('Clock sync marker cannot be ' |
+ 'recorded since explicit clock sync is not supported.') |
+ |
+ @py_utils.Timeout(tracing_agents.START_STOP_TIMEOUT) |
+ def StopCollectionWithClockSync(self, sync_id, |
+ did_record_sync_marker_callback): |
+ """ Stops the collection of monsoon power data and re-enables USB. |
+ Records the time at which collection was stopped so the record at this time |
+ can be annotated in the resultant log. |
+ """ |
+ self._sync_id = sync_id |
+ sync_trace = trace_time.Now() |
+ self._sync_timestamp = time.time() |
+ did_record_sync_marker_callback(sync_trace, sync_id) |
+ |
+ # Wait for at least two synchronization periods to allow Monsoon to gather |
+ # and flush data. Min wait time is half a second. |
+ flush_period = max(self._latency_seconds * 2.0, 0.5) |
+ time.sleep(flush_period) |
+ |
+ self._monsoon_wrapper.EndExecution() |
+ self._monsoon_wrapper.EnableUSB() |
+ self._device.adb.WaitForDevice() |
+ return True |
+ |
+ @py_utils.Timeout(tracing_agents.START_STOP_TIMEOUT) |
+ def StopAgentTracing(self, timeout=None): |
+ return True |
Chris Craik
2017/09/19 18:47:02
can you do the actual stop here? we'd like to avoi
Zhen Wang
2017/09/19 23:53:50
Seem that the actual stopping is done in StopColle
ddeptford
2017/09/20 23:45:58
Unfortunately this is a limitation of the monsoon
|
+ |
+ @py_utils.Timeout(tracing_agents.START_STOP_TIMEOUT) |
+ def StartAgentTracing(self, config, timeout=None): |
+ # Disable USB pass-through while collecting power samples to prevent adding |
+ # USB power to sample data. |
+ self._monsoon_wrapper.DisableUSB() |
+ |
+ # Begin a power reading operation. |
+ self._result_file = tempfile.TemporaryFile() |
+ self._monsoon_wrapper.BeginReadPower(self._frequency, out=self._result_file) |
+ self._trace_start = time.time() |
+ return True |
+ |
+ @py_utils.Timeout(tracing_agents.GET_RESULTS_TIMEOUT) |
+ def GetResults(self, timeout=None): |
+ self._result_file.flush() |
+ self._result_file.seek(0) |
+ |
+ millivolts = self._voltage * 1000 |
+ |
+ # Retrieve the samples that have second-only precision. |
+ coarse_samples = ReadSamplesFromFile( |
+ self._result_file) |
+ |
+ # Use fixed period to transform samples with whole second precision to |
+ # fractional precision. |
+ fine_samples = TransformSamplesWithFrequency( |
+ coarse_samples, self._frequency) |
+ |
+ # Build a string result for BattOr log (whose format we use). |
+ result_builder = StringIO.StringIO() |
+ result_builder.write("# BattOr") |
+ |
+ for sample in fine_samples: |
+ # For timestamp comparisons, adjust using the monsoon latency. |
+ adj_timestamp = sample.Timestamp - self._latency_seconds |
+ if adj_timestamp >= self._trace_start: |
+ result_builder.write("\n{0:f} {1:f} {2:f}".format(sample.Timestamp * |
+ 1000.0, sample.Amps * 1000.0, millivolts)) |
+ if self._sync_timestamp < adj_timestamp: |
+ result_builder.write(" <{}>".format(self._sync_id)) |
+ return trace_result.TraceResult('powerTraceAsString', |
+ result_builder.getvalue()) |
+ |
+ raise Exception("Failed to find end timestamp in monsoon log.") |