| // 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; |
| })(); |