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