1<!DOCTYPE html>
2<!--
3Copyright (c) 2015 The Chromium Authors. All rights reserved.
4Use of this source code is governed by a BSD-style license that can be
5found in the LICENSE file.
6-->
7
8<link rel="import" href="/tracing/importer/importer.html">
9<link rel="import" href="/tracing/model/model.html">
10<link rel="import" href="/tracing/model/power_series.html">
11
12<script>
13/**
14 * @fileoverview Imports text files in the BattOr format into the
15 * Model. This format is output by the battor_agent executable and library.
16 *
17 * This importer assumes the events arrive as a string. The unit tests provide
18 * examples of the trace format.
19 */
20'use strict';
21
22tr.exportTo('tr.e.importer.battor', function() {
23  /**
24   * Imports a BattOr power trace into a specified model.
25   * @constructor
26   */
27  function BattorImporter(model, events) {
28    this.importPriority = 3; // runs after the linux_perf importer
29    this.sampleRate_ = undefined;
30    this.model_ = model;
31    this.events_ = events;
32    this.explicitSyncMark_ = undefined;
33  }
34
35  var TestExports = {};
36
37  var battorDataLineRE = new RegExp(
38      '^(\\d+\\.\\d+)\\s+(\\d+\\.\\d+)\\s+(\\d+\\.\\d+)' +
39      '(?:\\s+<(\\S+)>)?$'
40  );
41  var battorHeaderLineRE = /^# BattOr/;
42  var sampleRateLineRE = /^# sample_rate (\d+) Hz/;
43
44  /**
45   * Guesses whether the provided events is a BattOr string.
46   * Looks for the magic string "# BattOr" at the start of the file,
47   *
48   * @return {boolean} True when events is a BattOr array.
49   */
50  BattorImporter.canImport = function(events) {
51    if (!(typeof(events) === 'string' || events instanceof String))
52      return false;
53
54    return battorHeaderLineRE.test(events);
55  };
56
57  BattorImporter.prototype = {
58    __proto__: tr.importer.Importer.prototype,
59
60    get importerName() {
61      return 'BattorImporter';
62    },
63
64    get model() {
65      return this.model_;
66    },
67
68    /**
69     * Imports the data in this.events_ into model_.
70     */
71    importEvents: function() {
72      // Fail if the model already has a Power counter.
73      if (this.model_.device.powerSeries) {
74        this.model_.importWarning({
75          type: 'import_error',
76          message: 'Power counter exists, can not import BattOr power trace.'
77        });
78        return;
79      }
80
81      // Create series and import power samples into it.
82      var name = 'power';
83      var series = new tr.model.PowerSeries(this.model_.device);
84      this.importPowerSamples(series);
85
86      // Find the sync markers that are identified as being for the BattOr.
87      var battorSyncMarks = this.model_.getClockSyncRecordsWithSyncId('battor');
88
89      // Try each of the clock sync techinques in order of their accuracy.
90      var shiftTs = undefined;
91      shiftTs = this.correlationClockSync(battorSyncMarks, series);
92      if (shiftTs === undefined)
93        shiftTs = this.explicitClockSync();
94
95      if (shiftTs === undefined) {
96        this.model_.importWarning({
97          type: 'clock_sync',
98          message: 'All of the BattOr power trace clock sync techinques failed.'
99        });
100        return;
101      }
102
103      series.shiftTimestampsForward(shiftTs);
104      this.model_.device.powerSeries = series;
105    },
106
107    /**
108     * Walks the events and populates a time series with power samples.
109     */
110    importPowerSamples: function(series) {
111      var lines = this.events_.split('\n');
112
113      // Update the model's bounds.
114      this.model_.updateBounds();
115      var minTs = 0;
116      if (this.model_.bounds.min !== undefined)
117        minTs = this.model_.bounds.min;
118
119      lines.forEach(function(line) {
120        line = line.trim();
121        if (line.length === 0)
122          return;
123
124        if (/^#/.test(line)) {
125          // Parse sample rate.
126          groups = sampleRateLineRE.exec(line);
127          if (!groups)
128            return;
129          this.sampleRate_ = parseInt(groups[1]);
130        } else {
131          // Parse power sample.
132          var groups = battorDataLineRE.exec(line);
133          if (!groups) {
134            this.model_.importWarning({
135              type: 'parse_error',
136              message: 'Unrecognized line: ' + line
137            });
138            return;
139          }
140
141          // Add power sample.
142          var time = parseFloat(groups[1]) + minTs;
143          var voltage_mV = parseFloat(groups[2]);
144          var current_mA = parseFloat(groups[3]);
145          series.addPowerSample(time, (voltage_mV * current_mA) / 1000);
146
147          // Found first explicit clock sync - save it.
148          if (groups[4] !== undefined &&
149            this.explicitSyncMark_ === undefined) {
150            var id = groups[4];
151            this.explicitSyncMark_ = {'id' : id, 'ts' : time};
152          }
153        }
154      }, this);
155    },
156
157    correlationClockSync: function(syncMarks, series) {
158      // Check for the two markers that surround the sync signal are present.
159      if (syncMarks.length !== 2)
160        return undefined;
161
162      // Find the regulator counter for the sync.
163      var syncCtr = this.model_.kernel.counters[
164          'null.vreg ' + syncMarks[0].args['regulator'] + ' enabled'];
165      if (syncCtr === undefined) {
166        this.model_.importWarning({
167          type: 'clock_sync',
168          message: 'Cannot correlate BattOr power trace without sync vreg.'
169        });
170        return undefined;
171      }
172
173      // Store the sync events from the regulator counter.
174      var syncEvents = [];
175      var firstSyncEventTs = undefined;
176      syncCtr.series[0].iterateAllEvents(function(event) {
177        if (event.timestamp >= syncMarks[0].start &&
178            event.timestamp <= syncMarks[1].start) {
179          if (firstSyncEventTs === undefined)
180            firstSyncEventTs = event.timestamp;
181          var newEvent = {
182              'ts': (event.timestamp - firstSyncEventTs) / 1000, // msec -> sec
183              'val': event.value};
184          syncEvents.push(newEvent);
185        }
186      });
187
188      // Generate samples from sync events to be cross-correlated with power.
189      var syncSamples = [];
190      var syncNumSamples = Math.ceil(
191          syncEvents[syncEvents.length - 1].ts * this.sampleRate_
192      );
193
194      for (var i = 1; i < syncEvents.length; i++) {
195        var sampleStartIdx = Math.ceil(
196            syncEvents[i - 1].ts * this.sampleRate_
197        );
198        var sampleEndIdx = Math.ceil(
199            syncEvents[i].ts * this.sampleRate_
200        );
201
202        for (var j = sampleStartIdx; j < sampleEndIdx; j++) {
203          syncSamples[j] = syncEvents[i - 1].val;
204        }
205      }
206
207      // TODO(aschulman) Low-pass the samples to improve the cross-correlation.
208
209      var powerSamples = series.samples;
210      // Check to make sure there are enough power samples.
211      if (powerSamples.length < syncSamples.length) {
212        this.model_.importWarning({
213          type: 'not_enough_samples',
214          message: 'Not enough power samples to correlate with sync signal.'
215        });
216        return undefined;
217      }
218
219      // Cross-correlate the ground truth with the last 5s of power samples.
220      var maxShift = powerSamples.length - syncSamples.length;
221      var minShift = 0;
222      var corrNumSamples = this.sampleRate_ * 5.0;
223      if (powerSamples.length > corrNumSamples)
224        minShift = powerSamples.length - corrNumSamples;
225
226      var corr = [];
227      for (var shift = minShift; shift <= maxShift; shift++) {
228        var corrSum = 0;
229        var powerAvg = 0;
230        for (var i = 0; i < syncSamples.length; i++) {
231          corrSum += (powerSamples[i + shift].power * syncSamples[i]);
232          powerAvg += powerSamples[i + shift].power;
233        }
234        powerAvg = powerAvg / syncSamples.length;
235        corr.push(corrSum / powerAvg);
236      }
237
238      // Find the sync start time (peak of the cross-correlation).
239      var corrPeakIdx = 0;
240      var corrPeak = 0;
241      for (var i = 0; i < powerSamples.length; i++) {
242        if (corr[i] > corrPeak) {
243          corrPeak = corr[i];
244          corrPeakIdx = i;
245        }
246      }
247
248      // Shift the time of the power samples by the recovered sync start time.
249      var corrPeakTs = ((minShift + corrPeakIdx) / this.sampleRate_);
250      corrPeakTs *= 1000; // sec -> msec
251      var syncStartTs = firstSyncEventTs - this.model_.bounds.min;
252      var shiftTs = syncStartTs - corrPeakTs;
253
254      return shiftTs;
255    },
256
257    explicitClockSync: function() {
258      // Check to see if an explicit clock sync was found in the BattOr trace.
259      if (this.explicitSyncMark_ === undefined)
260        return undefined;
261
262      // Try to get the matching clock sync record for this explicit sync.
263      var syncMarks = this.model.getClockSyncRecordsWithSyncId(
264          this.explicitSyncMark_['id']);
265      if (syncMarks.length !== 1) {
266        this.model_.importWarning({
267          type: 'missing_sync_marker',
268          message: 'No single clock sync record found for explicit clock sync.'
269        });
270        return undefined;
271      }
272
273      var clockSync = syncMarks[0];
274
275      // TODO(aschulman) Actual time of sync is assumed to be half-way between
276      // when the sync message was sent and when it was received.  Chromium's
277      // serial I/O library sends bytes within a millisecond, however it seems
278      // to take tens of milliseconds to receive bytes. Therefore, until we
279      // figure out how to receive bytes without this delay, time of sync is
280      // set to the time when the the bytes are sent.
281      var syncTs = clockSync.start;
282      var traceTs = this.explicitSyncMark_['ts'];
283
284      // Shift by the difference between the explicit sync timestamps.
285      return syncTs - traceTs;
286    },
287
288    foundExplicitSyncMark: function() {
289      return this.explicitSyncMark_ !== undefined;
290    }
291  };
292
293
294
295  tr.importer.Importer.register(BattorImporter);
296
297  return {
298    BattorImporter: BattorImporter,
299    _BattorImporterTestExports: TestExports
300  };
301});
302
303</script>
304