| <!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="/core/importer/importer.html"> |
| <link rel="import" href="/core/trace_model/trace_model.html"> |
| |
| <script> |
| /** |
| * @fileoverview Imports text files in the BattOr format into the |
| * Tracemodel. This format is output by the BattOr executable |
| * |
| * This importer assumes the events arrive as a string. The unit tests provide |
| * examples of the trace format. |
| */ |
| 'use strict'; |
| |
| tv.exportTo('tv.e.importer.battor', function() { |
| var Importer = tv.c.importer.Importer; |
| |
| /** |
| * Imports linux perf events into a specified model. |
| * @constructor |
| */ |
| function BattorImporter(model, events) { |
| this.importPriority = 3; // runs after the linux_perf importer |
| this.series_ = undefined; |
| this.sampleRate_ = undefined; |
| this.model_ = model; |
| this.events_ = events; |
| this.kernelThreadStates_ = {}; |
| this.lines_ = []; |
| this.pseudoThreadCounter = 1; |
| } |
| |
| var TestExports = {}; |
| |
| var battorLineRE = /^(\d+\.\d+)\s+(\d+\.\d+)\s+(\d+\.\d+)$/; |
| 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; |
| |
| if (/^# BattOr/.test(events)) |
| return true; |
| |
| return false; |
| }; |
| |
| BattorImporter.prototype = { |
| __proto__: Importer.prototype, |
| |
| get model() { |
| return this.model_; |
| }, |
| |
| /** |
| * Imports the data in this.events_ into model_. |
| */ |
| importEvents: function(isSecondaryImport) { |
| // Create series and import power samples into it. |
| var name = 'power'; |
| var series = new tv.c.trace_model.CounterSeries('value', |
| tv.b.ui.getColorIdForGeneralPurposeString(name + '.' + 'value')); |
| this.importPowerSamples(series); |
| this.series_ = series; |
| |
| // Find the markers for the beginning and end of the vreg sync. |
| var syncMarks = this.model_.getClockSyncRecordsNamed('battor'); |
| if (syncMarks.length < 2) { |
| this.model_.importWarning({ |
| type: 'clock_sync', |
| message: 'Cannot import BattOr measurments without a sync signal.' |
| }); |
| return; |
| } |
| |
| // 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 import BattOr power trace without sync vreg.' |
| }); |
| return; |
| } |
| |
| // Store the sync events from the regulator counter. |
| var syncEvents = []; |
| var firstSyncEventTs = undefined; |
| syncCtr.series[0].iterateAllEvents(function(event) { |
| if (event.timestamp >= syncMarks[0].ts && |
| event.timestamp <= syncMarks[1].ts) { |
| 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; |
| } |
| |
| // 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].value * syncSamples[i]); |
| powerAvg += powerSamples[i + shift].value; |
| } |
| 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; |
| } |
| } |
| |
| // Add a counter for power to the GUI. |
| var ctr = this.model_.kernel.getOrCreateCounter(null, 'Power'); |
| if (ctr.numSeries === 0) { |
| ctr.addSeries(series); |
| } |
| this.counter_ = ctr; |
| |
| // 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; |
| this.counter_.shiftTimestampsForward(shiftTs); |
| }, |
| |
| /** |
| * 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) { |
| 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 = battorLineRE.exec(line); |
| if (!groups) { |
| this.model_.importWarning({ |
| type: 'parse_error', |
| message: 'Unrecognized line: ' + line |
| }); |
| return; |
| } |
| |
| var time = parseFloat(groups[1]) + minTs; |
| var voltage_mV = parseFloat(groups[2]); |
| var current_mA = parseFloat(groups[3]); |
| series.addCounterSample(time, (voltage_mV * current_mA) / 1000); |
| } |
| }, this); |
| } |
| }; |
| |
| tv.c.importer.Importer.register(BattorImporter); |
| |
| return { |
| BattorImporter: BattorImporter, |
| _BattorImporterTestExports: TestExports |
| }; |
| }); |
| |
| </script> |