blob: 591bd7e7f0643e56ae36c2663339f8e7e5aedf59 [file] [log] [blame]
<!DOCTYPE html>
<!--
Copyright (c) 2015 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.
-->
<link rel="import" href="/tracing/importer/importer.html">
<link rel="import" href="/tracing/model/model.html">
<link rel="import" href="/tracing/model/power_series.html">
<script>
/**
* @fileoverview Imports text files in the BattOr format into the
* Model. This format is output by the battor_agent executable and library.
*
* This importer assumes the events arrive as a string. The unit tests provide
* examples of the trace format.
*/
'use strict';
tr.exportTo('tr.e.importer.battor', function() {
/**
* Imports a BattOr power trace into a specified model.
* @constructor
*/
function BattorImporter(model, events) {
this.importPriority = 3; // runs after the linux_perf importer
this.sampleRate_ = undefined;
this.model_ = model;
this.events_ = events;
this.explicitSyncMark_ = undefined;
}
var TestExports = {};
var battorDataLineRE = new RegExp(
'^(\\d+\\.\\d+)\\s+(\\d+\\.\\d+)\\s+(\\d+\\.\\d+)' +
'(?:\\s+<(\\S+)>)?$'
);
var battorHeaderLineRE = /^# BattOr/;
var sampleRateLineRE = /^# sample_rate (\d+) Hz/;
/**
* Guesses whether the provided events is a BattOr string.
* Looks for the magic string "# BattOr" at the start of the file,
*
* @return {boolean} True when events is a BattOr array.
*/
BattorImporter.canImport = function(events) {
if (!(typeof(events) === 'string' || events instanceof String))
return false;
return battorHeaderLineRE.test(events);
};
BattorImporter.prototype = {
__proto__: tr.importer.Importer.prototype,
get importerName() {
return 'BattorImporter';
},
get model() {
return this.model_;
},
/**
* Imports the data in this.events_ into model_.
*/
importEvents: function() {
// Fail if the model already has a Power counter.
if (this.model_.device.powerSeries) {
this.model_.importWarning({
type: 'import_error',
message: 'Power counter exists, can not import BattOr power trace.'
});
return;
}
// Create series and import power samples into it.
var name = 'power';
var series = new tr.model.PowerSeries(this.model_.device);
this.importPowerSamples(series);
// Find the sync markers that are identified as being for the BattOr.
var battorSyncMarks = this.model_.getClockSyncRecordsWithSyncId('battor');
// Try each of the clock sync techinques in order of their accuracy.
var shiftTs = undefined;
shiftTs = this.correlationClockSync(battorSyncMarks, series);
if (shiftTs === undefined)
shiftTs = this.explicitClockSync();
if (shiftTs === undefined) {
this.model_.importWarning({
type: 'clock_sync',
message: 'All of the BattOr power trace clock sync techinques failed.'
});
return;
}
series.shiftTimestampsForward(shiftTs);
this.model_.device.powerSeries = series;
},
/**
* Walks the events and populates a time series with power samples.
*/
importPowerSamples: function(series) {
var lines = this.events_.split('\n');
// Update the model's bounds.
this.model_.updateBounds();
var minTs = 0;
if (this.model_.bounds.min !== undefined)
minTs = this.model_.bounds.min;
lines.forEach(function(line) {
line = line.trim();
if (line.length === 0)
return;
if (/^#/.test(line)) {
// Parse sample rate.
groups = sampleRateLineRE.exec(line);
if (!groups)
return;
this.sampleRate_ = parseInt(groups[1]);
} else {
// Parse power sample.
var groups = battorDataLineRE.exec(line);
if (!groups) {
this.model_.importWarning({
type: 'parse_error',
message: 'Unrecognized line: ' + line
});
return;
}
// Add power sample.
var time = parseFloat(groups[1]) + minTs;
var voltage_mV = parseFloat(groups[2]);
var current_mA = parseFloat(groups[3]);
series.addPowerSample(time, (voltage_mV * current_mA) / 1000);
// Found first explicit clock sync - save it.
if (groups[4] !== undefined &&
this.explicitSyncMark_ === undefined) {
var id = groups[4];
this.explicitSyncMark_ = {'id' : id, 'ts' : time};
}
}
}, this);
},
correlationClockSync: function(syncMarks, series) {
// Check for the two markers that surround the sync signal are present.
if (syncMarks.length !== 2)
return undefined;
// Find the regulator counter for the sync.
var syncCtr = this.model_.kernel.counters[
'null.vreg ' + syncMarks[0].args['regulator'] + ' enabled'];
if (syncCtr === undefined) {
this.model_.importWarning({
type: 'clock_sync',
message: 'Cannot correlate BattOr power trace without sync vreg.'
});
return undefined;
}
// Store the sync events from the regulator counter.
var syncEvents = [];
var firstSyncEventTs = undefined;
syncCtr.series[0].iterateAllEvents(function(event) {
if (event.timestamp >= syncMarks[0].start &&
event.timestamp <= syncMarks[1].start) {
if (firstSyncEventTs === undefined)
firstSyncEventTs = event.timestamp;
var newEvent = {
'ts': (event.timestamp - firstSyncEventTs) / 1000, // msec -> sec
'val': event.value};
syncEvents.push(newEvent);
}
});
// Generate samples from sync events to be cross-correlated with power.
var syncSamples = [];
var syncNumSamples = Math.ceil(
syncEvents[syncEvents.length - 1].ts * this.sampleRate_
);
for (var i = 1; i < syncEvents.length; i++) {
var sampleStartIdx = Math.ceil(
syncEvents[i - 1].ts * this.sampleRate_
);
var sampleEndIdx = Math.ceil(
syncEvents[i].ts * this.sampleRate_
);
for (var j = sampleStartIdx; j < sampleEndIdx; j++) {
syncSamples[j] = syncEvents[i - 1].val;
}
}
// TODO(aschulman) Low-pass the samples to improve the cross-correlation.
var powerSamples = series.samples;
// Check to make sure there are enough power samples.
if (powerSamples.length < syncSamples.length) {
this.model_.importWarning({
type: 'not_enough_samples',
message: 'Not enough power samples to correlate with sync signal.'
});
return undefined;
}
// Cross-correlate the ground truth with the last 5s of power samples.
var maxShift = powerSamples.length - syncSamples.length;
var minShift = 0;
var corrNumSamples = this.sampleRate_ * 5.0;
if (powerSamples.length > corrNumSamples)
minShift = powerSamples.length - corrNumSamples;
var corr = [];
for (var shift = minShift; shift <= maxShift; shift++) {
var corrSum = 0;
var powerAvg = 0;
for (var i = 0; i < syncSamples.length; i++) {
corrSum += (powerSamples[i + shift].power * syncSamples[i]);
powerAvg += powerSamples[i + shift].power;
}
powerAvg = powerAvg / syncSamples.length;
corr.push(corrSum / powerAvg);
}
// Find the sync start time (peak of the cross-correlation).
var corrPeakIdx = 0;
var corrPeak = 0;
for (var i = 0; i < powerSamples.length; i++) {
if (corr[i] > corrPeak) {
corrPeak = corr[i];
corrPeakIdx = i;
}
}
// Shift the time of the power samples by the recovered sync start time.
var corrPeakTs = ((minShift + corrPeakIdx) / this.sampleRate_);
corrPeakTs *= 1000; // sec -> msec
var syncStartTs = firstSyncEventTs - this.model_.bounds.min;
var shiftTs = syncStartTs - corrPeakTs;
return shiftTs;
},
explicitClockSync: function() {
// Check to see if an explicit clock sync was found in the BattOr trace.
if (this.explicitSyncMark_ === undefined)
return undefined;
// Try to get the matching clock sync record for this explicit sync.
var syncMarks = this.model.getClockSyncRecordsWithSyncId(
this.explicitSyncMark_['id']);
if (syncMarks.length !== 1) {
this.model_.importWarning({
type: 'missing_sync_marker',
message: 'No single clock sync record found for explicit clock sync.'
});
return undefined;
}
var clockSync = syncMarks[0];
// TODO(aschulman) Actual time of sync is assumed to be half-way between
// when the sync message was sent and when it was received. Chromium's
// serial I/O library sends bytes within a millisecond, however it seems
// to take tens of milliseconds to receive bytes. Therefore, until we
// figure out how to receive bytes without this delay, time of sync is
// set to the time when the the bytes are sent.
var syncTs = clockSync.start;
var traceTs = this.explicitSyncMark_['ts'];
// Shift by the difference between the explicit sync timestamps.
return syncTs - traceTs;
},
foundExplicitSyncMark: function() {
return this.explicitSyncMark_ !== undefined;
}
};
tr.importer.Importer.register(BattorImporter);
return {
BattorImporter: BattorImporter,
_BattorImporterTestExports: TestExports
};
});
</script>