1 <!DOCTYPE html> 2 <!-- 3 Copyright 2016 The Chromium Authors. All rights reserved. 4 Use of this source code is governed by a BSD-style license that can be 5 found in the LICENSE file. 6 --> 7 8 <link rel="import" href="/tracing/base/iteration_helpers.html"> 9 10 <script> 11 'use strict'; 12 13 tr.exportTo('tr.model', function() { 14 var ClockDomainId = { 15 BATTOR: 'battor', 16 CHROME: 'chrome' 17 }; 18 19 /** 20 * A ClockSyncManager holds clock sync markers and uses them to shift 21 * timestamps from agents' clock domains onto the model's clock domain. 22 * 23 * In this context, a "clock domain" is a single perspective on the passage 24 * of time. A single computer can have multiple clock domains because it 25 * can have multiple methods of retrieving a timestamp (e.g. 26 * clock_gettime(CLOCK_MONOTONIC) and clock_gettime(CLOCK_REALTIME) on Linux). 27 * Another common reason for multiple clock domains within a single trace 28 * is that traces can span devices (e.g. a laptop collecting a Chrome trace 29 * can have its power consumption recorded by a second device and the two 30 * traces can be viewed alongside each other). 31 * 32 * For more information on how to synchronize multiple time domains using this 33 * method, see: http://bit.ly/1OVkqju. 34 * 35 * @constructor 36 */ 37 function ClockSyncManager() { 38 this.connectorBySyncId_ = {}; 39 this.modelDomainId_ = undefined; 40 this.modelTimeTransformerByDomainId_ = undefined; 41 } 42 43 ClockSyncManager.prototype = { 44 /** 45 * Adds a clock sync marker to the list of known markers. 46 * 47 * @param {string} domainId The clock domain that the marker is in. 48 * @param {string} syncId The identifier shared by both sides of the clock 49 * sync marker. 50 * @param {number} startTs The time (in ms) at which the sync started. 51 * @param {number=} opt_endTs The time (in ms) at which the sync ended. If 52 * unspecified, it's assumed to be the same as the start, 53 * indicating an instantaneous sync. 54 */ 55 addClockSyncMarker: function(domainId, syncId, startTs, opt_endTs) { 56 if (tr.b.dictionaryValues(ClockDomainId).indexOf(domainId) < 0) { 57 throw new Error('"' + domainId + '" is not in the list of known ' + 58 'clock domain IDs.'); 59 } 60 61 if (this.modelDomainId_ !== undefined) { 62 throw new Error('Cannot add new clock sync markers after getting ' + 63 'a model time transformer.'); 64 } 65 66 var marker = new ClockSyncMarker(domainId, startTs, opt_endTs); 67 68 var connector = this.connectorBySyncId_[syncId]; 69 if (connector === undefined) { 70 this.connectorBySyncId_[syncId] = new ClockSyncConnector(marker); 71 return; 72 } 73 74 if (connector.marker2 !== undefined) { 75 throw new Error('Clock sync with ID "' + syncId + '" is already ' + 76 'complete - cannot add a third clock sync marker to it.'); 77 } 78 79 if (connector.marker1.domainId === domainId) 80 throw new Error('A clock domain cannot sync with itself.'); 81 82 if (this.getConnectorBetween_(connector.marker1.domainId, domainId) !== 83 undefined) { 84 throw new Error('Cannot add multiple connectors between the same ' + 85 'clock domains.'); 86 } 87 88 connector.marker2 = marker; 89 }, 90 91 /** 92 * Returns a function that, given a timestamp in the specified clock domain, 93 * returns a timestamp in the model's clock domain. 94 * 95 * NOTE: All clock sync markers should be added before calling this function 96 * for the first time. This is because the first time that this function is 97 * called, a model clock domain is selected. This clock domain must have 98 * syncs connecting it with all other clock domains. If multiple clock 99 * domains are viable candidates, the one with the clock domain ID that is 100 * the first alphabetically is selected. 101 */ 102 getModelTimeTransformer: function(domainId) { 103 if (this.modelTimeTransformerByDomainId_ === undefined) 104 this.buildModelTimeTransformerMap_(); 105 106 var transformer = this.modelTimeTransformerByDomainId_[domainId]; 107 if (transformer === undefined) { 108 throw new Error('No clock sync markers exist pairing clock domain "' + 109 domainId + '" ' + 'with model clock domain "' + 110 this.modelDomainId_ + '".'); 111 } 112 113 return transformer; 114 }, 115 116 /** 117 * Selects a model clock domain and builds the map of transformers to that 118 * domain. If no clock domains are viable candidates, an error is thrown. 119 */ 120 buildModelTimeTransformerMap_() { 121 var completeConnectorsByDomainId = 122 this.getCompleteConnectorsByDomainId_(); 123 var uniqueClockDomainIds = 124 tr.b.dictionaryKeys(completeConnectorsByDomainId); 125 126 // If there are |n| unique clock domains, then the model clock domain 127 // is the first one alphabetically that's connected to the |n-1| other 128 // clock domains. 129 uniqueClockDomainIds.sort(); 130 var isFullyConnected = function(domainId) { 131 return completeConnectorsByDomainId[domainId].length === 132 uniqueClockDomainIds.length - 1; 133 }; 134 this.modelDomainId_ = 135 tr.b.findFirstInArray(uniqueClockDomainIds, isFullyConnected); 136 137 if (this.modelDomainId_ === undefined) { 138 throw new Error('Unable to select a master clock domain because no ' + 139 'clock domain is directly connected to all others.'); 140 } 141 142 this.modelTimeTransformerByDomainId_ = {}; 143 this.modelTimeTransformerByDomainId_[this.modelDomainId_] = tr.b.identity; 144 145 var modelConnectors = completeConnectorsByDomainId[this.modelDomainId_]; 146 for (var i = 0; i < modelConnectors.length; i++) { 147 var conn = modelConnectors[i]; 148 if (conn.marker1.domainId === this.modelDomainId_) { 149 this.modelTimeTransformerByDomainId_[conn.marker2.domainId] = 150 conn.getTransformer(conn.marker2.domainId, conn.marker1.domainId); 151 } else { 152 this.modelTimeTransformerByDomainId_[conn.marker1.domainId] = 153 conn.getTransformer(conn.marker1.domainId, conn.marker2.domainId); 154 } 155 } 156 }, 157 158 /** 159 * Returns a map from clock domain ID to the complete connectors linked 160 * to that clock domain. 161 */ 162 getCompleteConnectorsByDomainId_: function() { 163 var completeConnectorsByDomainId = {}; 164 for (var syncId in this.connectorBySyncId_) { 165 var conn = this.connectorBySyncId_[syncId]; 166 167 var domain1 = conn.marker1.domainId; 168 if (completeConnectorsByDomainId[domain1] === undefined) 169 completeConnectorsByDomainId[domain1] = []; 170 171 if (conn.marker2 === undefined) 172 continue; 173 174 var domain2 = conn.marker2.domainId; 175 if (completeConnectorsByDomainId[domain2] === undefined) 176 completeConnectorsByDomainId[domain2] = []; 177 178 completeConnectorsByDomainId[domain1].push(conn); 179 completeConnectorsByDomainId[domain2].push(conn); 180 } 181 182 return completeConnectorsByDomainId; 183 }, 184 185 /** 186 * Returns the connector between the specified domains (or undefined if no 187 * such connector exists). 188 */ 189 getConnectorBetween_(domain1Id, domain2Id) { 190 for (var syncId in this.connectorBySyncId_) { 191 var connector = this.connectorBySyncId_[syncId]; 192 if (connector.isBetween(domain1Id, domain2Id)) 193 return connector; 194 } 195 196 return undefined; 197 } 198 }; 199 200 /** 201 * A ClockSyncMarker is an internal entity that represents a marker in a 202 * trace log indicating that a clock sync happened at a specified time. 203 * 204 * If no end timestamp argument is specified in the constructor, it's assumed 205 * that the end timestamp is the same as the start (i.e. the clock sync 206 * was instantaneous). 207 */ 208 function ClockSyncMarker(domainId, startTs, opt_endTs) { 209 this.domainId = domainId; 210 this.startTs = startTs; 211 this.endTs = opt_endTs === undefined ? startTs : opt_endTs; 212 } 213 214 ClockSyncMarker.prototype = { 215 get ts() { return (this.startTs + this.endTs) / 2; } 216 }; 217 218 /** 219 * A ClockSyncConnector is an internal entity that gives us the ability to 220 * compare timestamps taken in two distinct clock domains. It's formed from 221 * two clock sync markers issued at (approximately) the same time in 222 * two separate trace logs. 223 * 224 * @constructor 225 */ 226 function ClockSyncConnector(opt_marker1, opt_marker2) { 227 this.marker1 = opt_marker1; 228 this.marker2 = opt_marker2; 229 } 230 231 ClockSyncConnector.prototype = { 232 /** 233 * Returns a function that transforms timestamps from one clock domain to 234 * another. If this connector isn't able to do this, an error is thrown. 235 */ 236 getTransformer: function(fromDomainId, toDomainId) { 237 if (!this.isBetween(fromDomainId, toDomainId)) 238 throw new Error('This connector cannot perform this transformation.'); 239 240 var fromMarker, toMarker; 241 if (this.marker1.domainId === fromDomainId) { 242 fromMarker = this.marker1; 243 toMarker = this.marker2; 244 } else { 245 fromMarker = this.marker2; 246 toMarker = this.marker1; 247 } 248 249 var fromTs = fromMarker.ts, toTs = toMarker.ts; 250 251 // TODO(charliea): Usually, we estimate that the clock sync marker is 252 // issued by the agent exactly in the middle of the controller's start and 253 // end timestamps. However, there's currently a bug in the Chrome serial 254 // code that's making the clock sync ack for BattOr take much longer to 255 // read than it should (by about 8ms). This is causing the above estimate 256 // of the controller's sync timestamp to be off by a substantial enough 257 // amount that it makes traces hard to read. For now, make an exception 258 // for BattOr and just use the controller's start timestamp as the sync 259 // time. In the medium term, we should fix the Chrome serial code in order 260 // to remove this special logic and get an even more accurate estimate. 261 if (fromDomainId == ClockDomainId.BATTOR && 262 toDomainId == ClockDomainId.CHROME) { 263 toTs = toMarker.startTs; 264 } else if (fromDomainId == ClockDomainId.CHROME && 265 toDomainId == ClockDomainId.BATTOR) { 266 fromTs = fromMarker.startTs; 267 } 268 269 var tsShift = toTs - fromTs; 270 return function(ts) { return ts + tsShift; }; 271 }, 272 273 /** 274 * Returns true if this connector is between the specified clock domains. 275 */ 276 isBetween: function(domain1Id, domain2Id) { 277 if (this.marker1 === undefined || this.marker2 === undefined) 278 return false; 279 280 if (this.marker1.domainId === domain1Id && 281 this.marker2.domainId === domain2Id) { 282 return true; 283 } 284 285 if (this.marker1.domainId === domain2Id && 286 this.marker2.domainId === domain1Id) { 287 return true; 288 } 289 290 return false; 291 } 292 }; 293 294 return { 295 ClockDomainId: ClockDomainId, 296 ClockSyncManager: ClockSyncManager 297 }; 298 }); 299 </script> 300