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