| 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..8f978ad55814cc1c51029c5543066767e0ded84c
|
| --- /dev/null
|
| +++ b/systrace/systrace/tracing_agents/monsoon_agent.py
|
| @@ -0,0 +1,185 @@
|
| +# 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
|
| +
|
| + # pylint: disable=no-self-use
|
| + def IsConnectionOwner(self):
|
| + """ Returns whether this agent is a connection owner.
|
| + """
|
| + return True
|
| +
|
| + 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
|
| +
|
| + @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.")
|
|
|