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