Index: netlog_viewer/timeline_data_series.js |
diff --git a/netlog_viewer/timeline_data_series.js b/netlog_viewer/timeline_data_series.js |
new file mode 100644 |
index 0000000000000000000000000000000000000000..ef4573fd9e8b5f259390f561e7b86721b1861b17 |
--- /dev/null |
+++ b/netlog_viewer/timeline_data_series.js |
@@ -0,0 +1,369 @@ |
+// Copyright (c) 2012 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. |
+ |
+/** |
+ * Different data types that each require their own labelled axis. |
+ */ |
+var TimelineDataType = { |
+ SOURCE_COUNT: 0, |
+ BYTES_PER_SECOND: 1 |
+}; |
+ |
+/** |
+ * A TimelineDataSeries collects an ordered series of (time, value) pairs, |
+ * and converts them to graph points. It also keeps track of its color and |
+ * current visibility state. DataSeries are solely responsible for tracking |
+ * data, and do not send notifications on state changes. |
+ * |
+ * Abstract class, doesn't implement onReceivedLogEntry. |
+ */ |
+var TimelineDataSeries = (function() { |
+ 'use strict'; |
+ |
+ /** |
+ * @constructor |
+ */ |
+ function TimelineDataSeries(dataType) { |
+ // List of DataPoints in chronological order. |
+ this.dataPoints_ = []; |
+ |
+ // Data type of the DataSeries. This is used to scale all values with |
+ // the same units in the same way. |
+ this.dataType_ = dataType; |
+ // Default color. Should always be overridden prior to display. |
+ this.color_ = 'red'; |
+ // Whether or not the data series should be drawn. |
+ this.isVisible_ = false; |
+ |
+ this.cacheStartTime_ = null; |
+ this.cacheStepSize_ = 0; |
+ this.cacheValues_ = []; |
+ } |
+ |
+ TimelineDataSeries.prototype = { |
+ /** |
+ * Adds a DataPoint to |this| with the specified time and value. |
+ * DataPoints are assumed to be received in chronological order. |
+ */ |
+ addPoint: function(timeTicks, value) { |
+ var time = timeutil.convertTimeTicksToDate(timeTicks).getTime(); |
+ this.dataPoints_.push(new DataPoint(time, value)); |
+ }, |
+ |
+ isVisible: function() { |
+ return this.isVisible_; |
+ }, |
+ |
+ show: function(isVisible) { |
+ this.isVisible_ = isVisible; |
+ }, |
+ |
+ getColor: function() { |
+ return this.color_; |
+ }, |
+ |
+ setColor: function(color) { |
+ this.color_ = color; |
+ }, |
+ |
+ getDataType: function() { |
+ return this.dataType_; |
+ }, |
+ |
+ /** |
+ * Returns a list containing the values of the data series at |count| |
+ * points, starting at |startTime|, and |stepSize| milliseconds apart. |
+ * Caches values, so showing/hiding individual data series is fast, and |
+ * derived data series can be efficiently computed, if we add any. |
+ */ |
+ getValues: function(startTime, stepSize, count) { |
+ // Use cached values, if we can. |
+ if (this.cacheStartTime_ == startTime && |
+ this.cacheStepSize_ == stepSize && |
+ this.cacheValues_.length == count) { |
+ return this.cacheValues_; |
+ } |
+ |
+ // Do all the work. |
+ this.cacheValues_ = this.getValuesInternal_(startTime, stepSize, count); |
+ this.cacheStartTime_ = startTime; |
+ this.cacheStepSize_ = stepSize; |
+ |
+ return this.cacheValues_; |
+ }, |
+ |
+ /** |
+ * Does all the work of getValues when we can't use cached data. |
+ * |
+ * The default implementation just uses the |value| of the most recently |
+ * seen DataPoint before each time, but other DataSeries may use some |
+ * form of interpolation. |
+ * TODO(mmenke): Consider returning the maximum value over each interval |
+ * to create graphs more stable with respect to zooming. |
+ */ |
+ getValuesInternal_: function(startTime, stepSize, count) { |
+ var values = []; |
+ var nextPoint = 0; |
+ var currentValue = 0; |
+ var time = startTime; |
+ for (var i = 0; i < count; ++i) { |
+ while (nextPoint < this.dataPoints_.length && |
+ this.dataPoints_[nextPoint].time < time) { |
+ currentValue = this.dataPoints_[nextPoint].value; |
+ ++nextPoint; |
+ } |
+ values[i] = currentValue; |
+ time += stepSize; |
+ } |
+ return values; |
+ } |
+ }; |
+ |
+ /** |
+ * A single point in a data series. Each point has a time, in the form of |
+ * milliseconds since the Unix epoch, and a numeric value. |
+ * @constructor |
+ */ |
+ function DataPoint(time, value) { |
+ this.time = time; |
+ this.value = value; |
+ } |
+ |
+ return TimelineDataSeries; |
+})(); |
+ |
+/** |
+ * Tracks how many sources of the given type have seen a begin |
+ * event of type |eventType| more recently than an end event. |
+ */ |
+var SourceCountDataSeries = (function() { |
+ 'use strict'; |
+ |
+ var superClass = TimelineDataSeries; |
+ |
+ /** |
+ * @constructor |
+ */ |
+ function SourceCountDataSeries(sourceType, eventType) { |
+ superClass.call(this, TimelineDataType.SOURCE_COUNT); |
+ this.sourceType_ = sourceType; |
+ this.eventType_ = eventType; |
+ |
+ // Map of sources for which we've seen a begin event more recently than an |
+ // end event. Each such source has a value of "true". All others are |
+ // undefined. |
+ this.activeSources_ = {}; |
+ // Number of entries in |activeSources_|. |
+ this.activeCount_ = 0; |
+ } |
+ |
+ SourceCountDataSeries.prototype = { |
+ // Inherit the superclass's methods. |
+ __proto__: superClass.prototype, |
+ |
+ onReceivedLogEntry: function(entry) { |
+ if (entry.source.type != this.sourceType_ || |
+ entry.type != this.eventType_) { |
+ return; |
+ } |
+ |
+ if (entry.phase == EventPhase.PHASE_BEGIN) { |
+ this.onBeginEvent(entry.source.id, entry.time); |
+ return; |
+ } |
+ if (entry.phase == EventPhase.PHASE_END) |
+ this.onEndEvent(entry.source.id, entry.time); |
+ }, |
+ |
+ /** |
+ * Called when the source with the specified id begins doing whatever we |
+ * care about. If it's not already an active source, we add it to the map |
+ * and add a data point. |
+ */ |
+ onBeginEvent: function(id, time) { |
+ if (this.activeSources_[id]) |
+ return; |
+ this.activeSources_[id] = true; |
+ ++this.activeCount_; |
+ this.addPoint(time, this.activeCount_); |
+ }, |
+ |
+ /** |
+ * Called when the source with the specified id stops doing whatever we |
+ * care about. If it's an active source, we remove it from the map and add |
+ * a data point. |
+ */ |
+ onEndEvent: function(id, time) { |
+ if (!this.activeSources_[id]) |
+ return; |
+ delete this.activeSources_[id]; |
+ --this.activeCount_; |
+ this.addPoint(time, this.activeCount_); |
+ } |
+ }; |
+ |
+ return SourceCountDataSeries; |
+})(); |
+ |
+/** |
+ * Tracks the number of sockets currently in use. Needs special handling of |
+ * SSL sockets, so can't just use a normal SourceCountDataSeries. |
+ */ |
+var SocketsInUseDataSeries = (function() { |
+ 'use strict'; |
+ |
+ var superClass = SourceCountDataSeries; |
+ |
+ /** |
+ * @constructor |
+ */ |
+ function SocketsInUseDataSeries() { |
+ superClass.call(this, EventSourceType.SOCKET, EventType.SOCKET_IN_USE); |
+ } |
+ |
+ SocketsInUseDataSeries.prototype = { |
+ // Inherit the superclass's methods. |
+ __proto__: superClass.prototype, |
+ |
+ onReceivedLogEntry: function(entry) { |
+ // SSL sockets have two nested SOCKET_IN_USE events. This is needed to |
+ // mark SSL sockets as unused after SSL negotiation. |
+ if (entry.type == EventType.SSL_CONNECT && |
+ entry.phase == EventPhase.PHASE_END) { |
+ this.onEndEvent(entry.source.id, entry.time); |
+ return; |
+ } |
+ superClass.prototype.onReceivedLogEntry.call(this, entry); |
+ } |
+ }; |
+ |
+ return SocketsInUseDataSeries; |
+})(); |
+ |
+/** |
+ * Tracks approximate data rate using individual data transfer events. |
+ * Abstract class, doesn't implement onReceivedLogEntry. |
+ */ |
+var TransferRateDataSeries = (function() { |
+ 'use strict'; |
+ |
+ var superClass = TimelineDataSeries; |
+ |
+ /** |
+ * @constructor |
+ */ |
+ function TransferRateDataSeries() { |
+ superClass.call(this, TimelineDataType.BYTES_PER_SECOND); |
+ } |
+ |
+ TransferRateDataSeries.prototype = { |
+ // Inherit the superclass's methods. |
+ __proto__: superClass.prototype, |
+ |
+ /** |
+ * Returns the average data rate over each interval, only taking into |
+ * account transfers that occurred within each interval. |
+ * TODO(mmenke): Do something better. |
+ */ |
+ getValuesInternal_: function(startTime, stepSize, count) { |
+ // Find the first DataPoint after |startTime| - |stepSize|. |
+ var nextPoint = 0; |
+ while (nextPoint < this.dataPoints_.length && |
+ this.dataPoints_[nextPoint].time < startTime - stepSize) { |
+ ++nextPoint; |
+ } |
+ |
+ var values = []; |
+ var time = startTime; |
+ for (var i = 0; i < count; ++i) { |
+ // Calculate total bytes transferred from |time| - |stepSize| |
+ // to |time|. We look at the transfers before |time| to give |
+ // us generally non-varying values for a given time. |
+ var transferred = 0; |
+ while (nextPoint < this.dataPoints_.length && |
+ this.dataPoints_[nextPoint].time < time) { |
+ transferred += this.dataPoints_[nextPoint].value; |
+ ++nextPoint; |
+ } |
+ // Calculate bytes per second. |
+ values[i] = 1000 * transferred / stepSize; |
+ time += stepSize; |
+ } |
+ return values; |
+ } |
+ }; |
+ |
+ return TransferRateDataSeries; |
+})(); |
+ |
+/** |
+ * Tracks TCP and UDP transfer rate. |
+ */ |
+var NetworkTransferRateDataSeries = (function() { |
+ 'use strict'; |
+ |
+ var superClass = TransferRateDataSeries; |
+ |
+ /** |
+ * |tcpEvent| and |udpEvent| are the event types for data transfers using |
+ * TCP and UDP, respectively. |
+ * @constructor |
+ */ |
+ function NetworkTransferRateDataSeries(tcpEvent, udpEvent) { |
+ superClass.call(this); |
+ this.tcpEvent_ = tcpEvent; |
+ this.udpEvent_ = udpEvent; |
+ } |
+ |
+ NetworkTransferRateDataSeries.prototype = { |
+ // Inherit the superclass's methods. |
+ __proto__: superClass.prototype, |
+ |
+ onReceivedLogEntry: function(entry) { |
+ if (entry.type != this.tcpEvent_ && entry.type != this.udpEvent_) |
+ return; |
+ this.addPoint(entry.time, entry.params.byte_count); |
+ }, |
+ }; |
+ |
+ return NetworkTransferRateDataSeries; |
+})(); |
+ |
+/** |
+ * Tracks disk cache read or write rate. Doesn't include clearing, opening, |
+ * or dooming entries, as they don't have clear size values. |
+ */ |
+var DiskCacheTransferRateDataSeries = (function() { |
+ 'use strict'; |
+ |
+ var superClass = TransferRateDataSeries; |
+ |
+ /** |
+ * @constructor |
+ */ |
+ function DiskCacheTransferRateDataSeries(eventType) { |
+ superClass.call(this); |
+ this.eventType_ = eventType; |
+ } |
+ |
+ DiskCacheTransferRateDataSeries.prototype = { |
+ // Inherit the superclass's methods. |
+ __proto__: superClass.prototype, |
+ |
+ onReceivedLogEntry: function(entry) { |
+ if (entry.source.type != EventSourceType.DISK_CACHE_ENTRY || |
+ entry.type != this.eventType_ || |
+ entry.phase != EventPhase.PHASE_END) { |
+ return; |
+ } |
+ // The disk cache has a lot of 0-length writes, when truncating entries. |
+ // Ignore those. |
+ if (entry.params.bytes_copied != 0) |
+ this.addPoint(entry.time, entry.params.bytes_copied); |
+ } |
+ }; |
+ |
+ return DiskCacheTransferRateDataSeries; |
+})(); |
+ |