blob: e65c70f9ff22a7cf0945832898a1b749b923eba0 [file] [log] [blame]
Chris Craik44c28202015-05-12 17:25:16 -07001<!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="/core/importer/importer.html">
9<link rel="import" href="/core/trace_model/trace_model.html">
10
11<script>
12/**
13 * @fileoverview Imports text files in the BattOr format into the
14 * Tracemodel. This format is output by the BattOr executable
15 *
16 * This importer assumes the events arrive as a string. The unit tests provide
17 * examples of the trace format.
18 */
19'use strict';
20
21tv.exportTo('tv.e.importer.battor', function() {
22 var Importer = tv.c.importer.Importer;
23
24 /**
25 * Imports linux perf events into a specified model.
26 * @constructor
27 */
28 function BattorImporter(model, events) {
29 this.importPriority = 3; // runs after the linux_perf importer
30 this.series_ = undefined;
31 this.sampleRate_ = undefined;
32 this.model_ = model;
33 this.events_ = events;
34 this.kernelThreadStates_ = {};
35 this.lines_ = [];
36 this.pseudoThreadCounter = 1;
37 }
38
39 var TestExports = {};
40
41 var battorLineRE = /^(\d+\.\d+)\s+(\d+\.\d+)\s+(\d+\.\d+)$/;
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 if (/^# BattOr/.test(events))
55 return true;
56
57 return false;
58 };
59
60 BattorImporter.prototype = {
61 __proto__: Importer.prototype,
62
63 get model() {
64 return this.model_;
65 },
66
67 /**
68 * Imports the data in this.events_ into model_.
69 */
70 importEvents: function(isSecondaryImport) {
71 // Create series and import power samples into it.
72 var name = 'power';
73 var series = new tv.c.trace_model.CounterSeries('value',
74 tv.b.ui.getColorIdForGeneralPurposeString(name + '.' + 'value'));
75 this.importPowerSamples(series);
76 this.series_ = series;
77
78 // Find the markers for the beginning and end of the vreg sync.
79 var syncMarks = this.model_.getClockSyncRecordsNamed('battor');
80 if (syncMarks.length < 2) {
81 this.model_.importWarning({
82 type: 'clock_sync',
83 message: 'Cannot import BattOr measurments without a sync signal.'
84 });
85 return;
86 }
87
88 // Find the regulator counter for the sync.
89 var syncCtr = this.model_.kernel.counters[
90 'null.vreg ' + syncMarks[0].args['regulator'] + ' enabled'];
91 if (syncCtr === undefined) {
92 this.model_.importWarning({
93 type: 'clock_sync',
94 message: 'Cannot import BattOr power trace without sync vreg.'
95 });
96 return;
97 }
98
99 // Store the sync events from the regulator counter.
100 var syncEvents = [];
101 var firstSyncEventTs = undefined;
102 syncCtr.series[0].iterateAllEvents(function(event) {
103 if (event.timestamp >= syncMarks[0].ts &&
104 event.timestamp <= syncMarks[1].ts) {
105 if (firstSyncEventTs === undefined)
106 firstSyncEventTs = event.timestamp;
107 var newEvent = {
108 'ts': (event.timestamp - firstSyncEventTs) / 1000, // msec -> sec
109 'val': event.value};
110 syncEvents.push(newEvent);
111 }
112 });
113
114 // Generate samples from sync events to be cross-correlated with power.
115 var syncSamples = [];
116 var syncNumSamples = Math.ceil(
117 syncEvents[syncEvents.length - 1].ts * this.sampleRate_
118 );
119
120 for (var i = 1; i < syncEvents.length; i++) {
121 var sampleStartIdx = Math.ceil(
122 syncEvents[i - 1].ts * this.sampleRate_
123 );
124 var sampleEndIdx = Math.ceil(
125 syncEvents[i].ts * this.sampleRate_
126 );
127
128 for (var j = sampleStartIdx; j < sampleEndIdx; j++) {
129 syncSamples[j] = syncEvents[i - 1].val;
130 }
131 }
132
133 // TODO(aschulman) Low-pass the samples to improve the cross-correlation.
134
135 var powerSamples = series.samples;
136 // Check to make sure there are enough power samples.
137 if (powerSamples.length < syncSamples.length) {
138 this.model_.importWarning({
139 type: 'not_enough_samples',
140 message: 'Not enough power samples to correlate with sync signal.'
141 });
142 return;
143 }
144
145 // Cross-correlate the ground truth with the last 5s of power samples.
146 var maxShift = powerSamples.length - syncSamples.length;
147 var minShift = 0;
148 var corrNumSamples = this.sampleRate_ * 5.0;
149 if (powerSamples.length > corrNumSamples)
150 minShift = powerSamples.length - corrNumSamples;
151
152 var corr = [];
153 for (var shift = minShift; shift <= maxShift; shift++) {
154 var corrSum = 0;
155 var powerAvg = 0;
156 for (var i = 0; i < syncSamples.length; i++) {
157 corrSum += (powerSamples[i + shift].value * syncSamples[i]);
158 powerAvg += powerSamples[i + shift].value;
159 }
160 powerAvg = powerAvg / syncSamples.length;
161 corr.push(corrSum / powerAvg);
162 }
163
164 // Find the sync start time (peak of the cross-correlation).
165 var corrPeakIdx = 0;
166 var corrPeak = 0;
167 for (var i = 0; i < powerSamples.length; i++) {
168 if (corr[i] > corrPeak) {
169 corrPeak = corr[i];
170 corrPeakIdx = i;
171 }
172 }
173
174 // Add a counter for power to the GUI.
175 var ctr = this.model_.kernel.getOrCreateCounter(null, 'Power');
176 if (ctr.numSeries === 0) {
177 ctr.addSeries(series);
178 }
179 this.counter_ = ctr;
180
181 // Shift the time of the power samples by the recovered sync start time.
182 var corrPeakTs = ((minShift + corrPeakIdx) / this.sampleRate_);
183 corrPeakTs *= 1000; // sec -> msec
184 var syncStartTs = firstSyncEventTs - this.model_.bounds.min;
185 var shiftTs = syncStartTs - corrPeakTs;
186 this.counter_.shiftTimestampsForward(shiftTs);
187 },
188
189 /**
190 * Walks the events and populates a time series with power samples.
191 */
192 importPowerSamples: function(series) {
193 var lines = this.events_.split('\n');
194
195 // Update the model's bounds.
196 this.model_.updateBounds();
197 var minTs = 0;
198 if (this.model_.bounds.min !== undefined)
199 minTs = this.model_.bounds.min;
200
201 lines.forEach(function(line) {
202 if (line.length == 0)
203 return;
204
205 if (/^#/.test(line)) {
206 // Parse sample rate.
207 groups = sampleRateLineRE.exec(line);
208 if (!groups)
209 return;
210 this.sampleRate_ = parseInt(groups[1]);
211 } else {
212 // Parse power sample.
213 var groups = battorLineRE.exec(line);
214 if (!groups) {
215 this.model_.importWarning({
216 type: 'parse_error',
217 message: 'Unrecognized line: ' + line
218 });
219 return;
220 }
221
222 var time = parseFloat(groups[1]) + minTs;
223 var voltage_mV = parseFloat(groups[2]);
224 var current_mA = parseFloat(groups[3]);
225 series.addCounterSample(time, (voltage_mV * current_mA) / 1000);
226 }
227 }, this);
228 }
229 };
230
231 tv.c.importer.Importer.register(BattorImporter);
232
233 return {
234 BattorImporter: BattorImporter,
235 _BattorImporterTestExports: TestExports
236 };
237});
238
239</script>