blob: 2820752fbea368719e4a46342712df2aab63aa38 [file] [log] [blame]
// 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;
})();