Home | History | Annotate | Download | only in model
      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