Home | History | Annotate | Download | only in net_internals
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 /**
      6  * Different data types that each require their own labelled axis.
      7  */
      8 var TimelineDataType = {
      9   SOURCE_COUNT: 0,
     10   BYTES_PER_SECOND: 1
     11 };
     12 
     13 /**
     14  * A TimelineDataSeries collects an ordered series of (time, value) pairs,
     15  * and converts them to graph points.  It also keeps track of its color and
     16  * current visibility state.  DataSeries are solely responsible for tracking
     17  * data, and do not send notifications on state changes.
     18  *
     19  * Abstract class, doesn't implement onReceivedLogEntry.
     20  */
     21 var TimelineDataSeries = (function() {
     22   'use strict';
     23 
     24   /**
     25    * @constructor
     26    */
     27   function TimelineDataSeries(dataType) {
     28     // List of DataPoints in chronological order.
     29     this.dataPoints_ = [];
     30 
     31     // Data type of the DataSeries.  This is used to scale all values with
     32     // the same units in the same way.
     33     this.dataType_ = dataType;
     34     // Default color.  Should always be overridden prior to display.
     35     this.color_ = 'red';
     36     // Whether or not the data series should be drawn.
     37     this.isVisible_ = false;
     38 
     39     this.cacheStartTime_ = null;
     40     this.cacheStepSize_ = 0;
     41     this.cacheValues_ = [];
     42   }
     43 
     44   TimelineDataSeries.prototype = {
     45     /**
     46      * Adds a DataPoint to |this| with the specified time and value.
     47      * DataPoints are assumed to be received in chronological order.
     48      */
     49     addPoint: function(timeTicks, value) {
     50       var time = timeutil.convertTimeTicksToDate(timeTicks).getTime();
     51       this.dataPoints_.push(new DataPoint(time, value));
     52     },
     53 
     54     isVisible: function() {
     55       return this.isVisible_;
     56     },
     57 
     58     show: function(isVisible) {
     59       this.isVisible_ = isVisible;
     60     },
     61 
     62     getColor: function() {
     63       return this.color_;
     64     },
     65 
     66     setColor: function(color) {
     67       this.color_ = color;
     68     },
     69 
     70     getDataType: function() {
     71       return this.dataType_;
     72     },
     73 
     74     /**
     75      * Returns a list containing the values of the data series at |count|
     76      * points, starting at |startTime|, and |stepSize| milliseconds apart.
     77      * Caches values, so showing/hiding individual data series is fast, and
     78      * derived data series can be efficiently computed, if we add any.
     79      */
     80     getValues: function(startTime, stepSize, count) {
     81       // Use cached values, if we can.
     82       if (this.cacheStartTime_ == startTime &&
     83           this.cacheStepSize_ == stepSize &&
     84           this.cacheValues_.length == count) {
     85         return this.cacheValues_;
     86       }
     87 
     88       // Do all the work.
     89       this.cacheValues_ = this.getValuesInternal_(startTime, stepSize, count);
     90       this.cacheStartTime_ = startTime;
     91       this.cacheStepSize_ = stepSize;
     92 
     93       return this.cacheValues_;
     94     },
     95 
     96     /**
     97      * Does all the work of getValues when we can't use cached data.
     98      *
     99      * The default implementation just uses the |value| of the most recently
    100      * seen DataPoint before each time, but other DataSeries may use some
    101      * form of interpolation.
    102      * TODO(mmenke):  Consider returning the maximum value over each interval
    103      *                to create graphs more stable with respect to zooming.
    104      */
    105     getValuesInternal_: function(startTime, stepSize, count) {
    106       var values = [];
    107       var nextPoint = 0;
    108       var currentValue = 0;
    109       var time = startTime;
    110       for (var i = 0; i < count; ++i) {
    111         while (nextPoint < this.dataPoints_.length &&
    112                this.dataPoints_[nextPoint].time < time) {
    113           currentValue = this.dataPoints_[nextPoint].value;
    114           ++nextPoint;
    115         }
    116         values[i] = currentValue;
    117         time += stepSize;
    118       }
    119       return values;
    120     }
    121   };
    122 
    123   /**
    124    * A single point in a data series.  Each point has a time, in the form of
    125    * milliseconds since the Unix epoch, and a numeric value.
    126    * @constructor
    127    */
    128   function DataPoint(time, value) {
    129     this.time = time;
    130     this.value = value;
    131   }
    132 
    133   return TimelineDataSeries;
    134 })();
    135 
    136 /**
    137  * Tracks how many sources of the given type have seen a begin
    138  * event of type |eventType| more recently than an end event.
    139  */
    140 var SourceCountDataSeries = (function() {
    141   'use strict';
    142 
    143   var superClass = TimelineDataSeries;
    144 
    145   /**
    146    * @constructor
    147    */
    148   function SourceCountDataSeries(sourceType, eventType) {
    149     superClass.call(this, TimelineDataType.SOURCE_COUNT);
    150     this.sourceType_ = sourceType;
    151     this.eventType_ = eventType;
    152 
    153     // Map of sources for which we've seen a begin event more recently than an
    154     // end event.  Each such source has a value of "true".  All others are
    155     // undefined.
    156     this.activeSources_ = {};
    157     // Number of entries in |activeSources_|.
    158     this.activeCount_ = 0;
    159   }
    160 
    161   SourceCountDataSeries.prototype = {
    162     // Inherit the superclass's methods.
    163     __proto__: superClass.prototype,
    164 
    165     onReceivedLogEntry: function(entry) {
    166       if (entry.source.type != this.sourceType_ ||
    167           entry.type != this.eventType_) {
    168         return;
    169       }
    170 
    171       if (entry.phase == EventPhase.PHASE_BEGIN) {
    172         this.onBeginEvent(entry.source.id, entry.time);
    173         return;
    174       }
    175       if (entry.phase == EventPhase.PHASE_END)
    176         this.onEndEvent(entry.source.id, entry.time);
    177     },
    178 
    179     /**
    180      * Called when the source with the specified id begins doing whatever we
    181      * care about.  If it's not already an active source, we add it to the map
    182      * and add a data point.
    183      */
    184     onBeginEvent: function(id, time) {
    185       if (this.activeSources_[id])
    186         return;
    187       this.activeSources_[id] = true;
    188       ++this.activeCount_;
    189       this.addPoint(time, this.activeCount_);
    190     },
    191 
    192     /**
    193      * Called when the source with the specified id stops doing whatever we
    194      * care about.  If it's an active source, we remove it from the map and add
    195      * a data point.
    196      */
    197     onEndEvent: function(id, time) {
    198       if (!this.activeSources_[id])
    199         return;
    200       delete this.activeSources_[id];
    201       --this.activeCount_;
    202       this.addPoint(time, this.activeCount_);
    203     }
    204   };
    205 
    206   return SourceCountDataSeries;
    207 })();
    208 
    209 /**
    210  * Tracks the number of sockets currently in use.  Needs special handling of
    211  * SSL sockets, so can't just use a normal SourceCountDataSeries.
    212  */
    213 var SocketsInUseDataSeries = (function() {
    214   'use strict';
    215 
    216   var superClass = SourceCountDataSeries;
    217 
    218   /**
    219    * @constructor
    220    */
    221   function SocketsInUseDataSeries() {
    222     superClass.call(this, EventSourceType.SOCKET, EventType.SOCKET_IN_USE);
    223   }
    224 
    225   SocketsInUseDataSeries.prototype = {
    226     // Inherit the superclass's methods.
    227     __proto__: superClass.prototype,
    228 
    229     onReceivedLogEntry: function(entry) {
    230       // SSL sockets have two nested SOCKET_IN_USE events.  This is needed to
    231       // mark SSL sockets as unused after SSL negotiation.
    232       if (entry.type == EventType.SSL_CONNECT &&
    233           entry.phase == EventPhase.PHASE_END) {
    234         this.onEndEvent(entry.source.id, entry.time);
    235         return;
    236       }
    237       superClass.prototype.onReceivedLogEntry.call(this, entry);
    238     }
    239   };
    240 
    241   return SocketsInUseDataSeries;
    242 })();
    243 
    244 /**
    245  * Tracks approximate data rate using individual data transfer events.
    246  * Abstract class, doesn't implement onReceivedLogEntry.
    247  */
    248 var TransferRateDataSeries = (function() {
    249   'use strict';
    250 
    251   var superClass = TimelineDataSeries;
    252 
    253   /**
    254    * @constructor
    255    */
    256   function TransferRateDataSeries() {
    257     superClass.call(this, TimelineDataType.BYTES_PER_SECOND);
    258   }
    259 
    260   TransferRateDataSeries.prototype = {
    261     // Inherit the superclass's methods.
    262     __proto__: superClass.prototype,
    263 
    264     /**
    265      * Returns the average data rate over each interval, only taking into
    266      * account transfers that occurred within each interval.
    267      * TODO(mmenke): Do something better.
    268      */
    269     getValuesInternal_: function(startTime, stepSize, count) {
    270       // Find the first DataPoint after |startTime| - |stepSize|.
    271       var nextPoint = 0;
    272       while (nextPoint < this.dataPoints_.length &&
    273              this.dataPoints_[nextPoint].time < startTime - stepSize) {
    274         ++nextPoint;
    275       }
    276 
    277       var values = [];
    278       var time = startTime;
    279       for (var i = 0; i < count; ++i) {
    280         // Calculate total bytes transferred from |time| - |stepSize|
    281         // to |time|.  We look at the transfers before |time| to give
    282         // us generally non-varying values for a given time.
    283         var transferred = 0;
    284         while (nextPoint < this.dataPoints_.length &&
    285                this.dataPoints_[nextPoint].time < time) {
    286           transferred += this.dataPoints_[nextPoint].value;
    287           ++nextPoint;
    288         }
    289         // Calculate bytes per second.
    290         values[i] = 1000 * transferred / stepSize;
    291         time += stepSize;
    292       }
    293       return values;
    294     }
    295   };
    296 
    297   return TransferRateDataSeries;
    298 })();
    299 
    300 /**
    301  * Tracks TCP and UDP transfer rate.
    302  */
    303 var NetworkTransferRateDataSeries = (function() {
    304   'use strict';
    305 
    306   var superClass = TransferRateDataSeries;
    307 
    308   /**
    309    * |tcpEvent| and |udpEvent| are the event types for data transfers using
    310    * TCP and UDP, respectively.
    311    * @constructor
    312    */
    313   function NetworkTransferRateDataSeries(tcpEvent, udpEvent) {
    314     superClass.call(this);
    315     this.tcpEvent_ = tcpEvent;
    316     this.udpEvent_ = udpEvent;
    317   }
    318 
    319   NetworkTransferRateDataSeries.prototype = {
    320     // Inherit the superclass's methods.
    321     __proto__: superClass.prototype,
    322 
    323     onReceivedLogEntry: function(entry) {
    324       if (entry.type != this.tcpEvent_ && entry.type != this.udpEvent_)
    325         return;
    326       this.addPoint(entry.time, entry.params.byte_count);
    327     },
    328   };
    329 
    330   return NetworkTransferRateDataSeries;
    331 })();
    332 
    333 /**
    334  * Tracks disk cache read or write rate.  Doesn't include clearing, opening,
    335  * or dooming entries, as they don't have clear size values.
    336  */
    337 var DiskCacheTransferRateDataSeries = (function() {
    338   'use strict';
    339 
    340   var superClass = TransferRateDataSeries;
    341 
    342   /**
    343    * @constructor
    344    */
    345   function DiskCacheTransferRateDataSeries(eventType) {
    346     superClass.call(this);
    347     this.eventType_ = eventType;
    348   }
    349 
    350   DiskCacheTransferRateDataSeries.prototype = {
    351     // Inherit the superclass's methods.
    352     __proto__: superClass.prototype,
    353 
    354     onReceivedLogEntry: function(entry) {
    355       if (entry.source.type != EventSourceType.DISK_CACHE_ENTRY ||
    356           entry.type != this.eventType_ ||
    357           entry.phase != EventPhase.PHASE_END) {
    358         return;
    359       }
    360       // The disk cache has a lot of 0-length writes, when truncating entries.
    361       // Ignore those.
    362       if (entry.params.bytes_copied != 0)
    363         this.addPoint(entry.time, entry.params.bytes_copied);
    364     }
    365   };
    366 
    367   return DiskCacheTransferRateDataSeries;
    368 })();
    369