Chromium Code Reviews
|
| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright (c) 2011 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 color: function() { | |
|
eroman
2011/11/16 04:03:46
nit: for consistency, I suggest calling this getCo
mmenke
2011/11/16 18:39:35
Done.
| |
| 63 return this.color_; | |
| 64 }, | |
| 65 | |
| 66 setColor: function(color) { | |
| 67 this.color_ = color; | |
| 68 }, | |
| 69 | |
| 70 dataType: function() { | |
|
eroman
2011/11/16 04:03:46
nit: for consistency I suggest calling this getDat
mmenke
2011/11/16 18:39:35
Done.
| |
| 71 return this.dataType_; | |
| 72 }, | |
| 73 | |
| 74 /** | |
| 75 * Takes in a list of 2 or more times, in increasing order and equally | |
| 76 * spaced, and returns the value of the data series at each point. | |
| 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 | |
| 91 this.cacheStartTime_ = startTime; | |
| 92 this.cacheStepSize_ = stepSize; | |
| 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 in a range, to get | |
| 103 * 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 a corresponding | |
| 139 * end event. | |
| 140 */ | |
| 141 var SourceCountDataSeries = (function() { | |
| 142 'use strict'; | |
| 143 | |
| 144 var superClass = TimelineDataSeries; | |
| 145 | |
| 146 /** | |
| 147 * @constructor | |
| 148 */ | |
| 149 function SourceCountDataSeries(sourceType, eventType) { | |
| 150 superClass.call(this, TimelineDataType.SOURCE_COUNT); | |
| 151 this.sourceType_ = sourceType; | |
| 152 this.eventType_ = eventType; | |
| 153 | |
| 154 // Map of sources which we've seen a begin event more recently than an end | |
| 155 // event. Each such source has a value of "true". All others are | |
| 156 // undefined. | |
| 157 this.activeSources_ = {}; | |
| 158 // Number of sources in the previous list. | |
| 159 this.activeCount_ = 0; | |
| 160 } | |
| 161 | |
| 162 SourceCountDataSeries.prototype = { | |
| 163 // Inherit the superclass's methods. | |
| 164 __proto__: superClass.prototype, | |
| 165 | |
| 166 onReceivedLogEntry: function(entry) { | |
| 167 if (entry.source.type != this.sourceType_ || | |
| 168 entry.type != this.eventType_) { | |
| 169 return; | |
| 170 } | |
| 171 | |
| 172 if (entry.phase == LogEventPhase.PHASE_BEGIN) { | |
| 173 this.onBeginEvent(entry.source.id, entry.time); | |
| 174 return; | |
| 175 } | |
| 176 if (entry.phase == LogEventPhase.PHASE_END) | |
| 177 this.onEndEvent(entry.source.id, entry.time); | |
| 178 }, | |
| 179 | |
| 180 /** | |
| 181 * Called when the source with the specified id begins doing whatever we | |
| 182 * care about. If it's not already an active source, we add it to the map | |
| 183 * and add a data point. | |
| 184 */ | |
| 185 onBeginEvent: function(id, time) { | |
| 186 if (this.activeSources_[id]) | |
| 187 return; | |
| 188 this.activeSources_[id] = true; | |
| 189 ++this.activeCount_; | |
| 190 this.addPoint(time, this.activeCount_); | |
| 191 }, | |
| 192 | |
| 193 /** | |
| 194 * Called when the source with the specified id stops doing whatever we | |
| 195 * care about. If it's an active source, we remove it from the map and add | |
| 196 * a data point. | |
| 197 */ | |
| 198 onEndEvent: function(id, time) { | |
| 199 if (!this.activeSources_[id]) | |
| 200 return; | |
| 201 delete this.activeSources_[id]; | |
| 202 --this.activeCount_; | |
| 203 this.addPoint(time, this.activeCount_); | |
| 204 } | |
| 205 }; | |
| 206 | |
| 207 return SourceCountDataSeries; | |
| 208 })(); | |
| 209 | |
| 210 /** | |
| 211 * Tracks the number of sockets currently in use. Needs special handling of | |
| 212 * SSL sockets, so can't just use a normal SourceCountDataSeries. | |
| 213 */ | |
| 214 var SocketsInUseDataSeries = (function() { | |
| 215 'use strict'; | |
| 216 | |
| 217 var superClass = SourceCountDataSeries; | |
| 218 | |
| 219 /** | |
| 220 * @constructor | |
| 221 */ | |
| 222 function SocketsInUseDataSeries() { | |
| 223 superClass.call(this, LogSourceType.SOCKET, LogEventType.SOCKET_IN_USE); | |
| 224 } | |
| 225 | |
| 226 SocketsInUseDataSeries.prototype = { | |
| 227 // Inherit the superclass's methods. | |
| 228 __proto__: superClass.prototype, | |
| 229 | |
| 230 onReceivedLogEntry: function(entry) { | |
| 231 // SSL sockets have two nested SOCKET_IN_USE events. This is needed to | |
| 232 // mark SSL sockets as unused after SSL negotiation. | |
| 233 if (entry.type == LogEventType.SSL_CONNECT && | |
| 234 entry.phase == LogEventPhase.PHASE_END) { | |
| 235 this.onEndEvent(entry.source.id, entry.time); | |
| 236 return; | |
| 237 } | |
| 238 superClass.prototype.onReceivedLogEntry.call(this, entry); | |
| 239 } | |
| 240 }; | |
| 241 | |
| 242 return SocketsInUseDataSeries; | |
| 243 })(); | |
| 244 | |
| 245 /** | |
| 246 * Tracks approximate data rate using individual data transfer events. | |
| 247 * Abstract class, doesn't implement onReceivedLogEntry. | |
| 248 */ | |
| 249 var TransferRateDataSeries = (function() { | |
| 250 'use strict'; | |
| 251 | |
| 252 var superClass = TimelineDataSeries; | |
| 253 | |
| 254 /** | |
| 255 * @constructor | |
| 256 */ | |
| 257 function TransferRateDataSeries() { | |
| 258 superClass.call(this, TimelineDataType.BYTES_PER_SECOND); | |
| 259 } | |
| 260 | |
| 261 TransferRateDataSeries.prototype = { | |
| 262 // Inherit the superclass's methods. | |
| 263 __proto__: superClass.prototype, | |
| 264 | |
| 265 /** | |
| 266 * Returns the average data rate over each interval, only taking into | |
| 267 * account transfers that occurred within each interval. | |
| 268 * TODO(mmenke): Do something better. | |
|
eroman
2011/11/16 04:03:46
I have been having a hard time figuring exactly ho
mmenke
2011/11/16 18:39:35
Averaging over a time range is probably the right
| |
| 269 */ | |
| 270 getValuesInternal_: function(startTime, stepSize, count) { | |
| 271 // Find the first DataPoint after |startTime| - |stepSize|. | |
| 272 var nextPoint = 0; | |
| 273 while (nextPoint < this.dataPoints_.length && | |
| 274 this.dataPoints_[nextPoint].time < startTime - stepSize) { | |
| 275 ++nextPoint; | |
| 276 } | |
| 277 | |
| 278 var values = []; | |
| 279 var time = startTime; | |
| 280 for (var i = 0; i < count; ++i) { | |
| 281 // Calculate total bytes transferred from |time| - |stepSize| | |
| 282 // to |time|. We look at the transfers before |time| to give | |
| 283 // us generally non-varying values for a given time. | |
| 284 var transferred = 0; | |
| 285 while (nextPoint < this.dataPoints_.length && | |
| 286 this.dataPoints_[nextPoint].time < time) { | |
| 287 transferred += this.dataPoints_[nextPoint].value; | |
| 288 ++nextPoint; | |
| 289 } | |
| 290 // Calculate bytes per second. | |
| 291 values[i] = 1000 * transferred / stepSize; | |
| 292 time += stepSize; | |
| 293 } | |
| 294 return values; | |
| 295 } | |
| 296 }; | |
| 297 | |
| 298 return TransferRateDataSeries; | |
| 299 })(); | |
| 300 | |
| 301 /** | |
| 302 * Tracks TCP and UDP transfer rate. | |
| 303 */ | |
| 304 var NetworkTransferRateDataSeries = (function() { | |
| 305 'use strict'; | |
| 306 | |
| 307 var superClass = TransferRateDataSeries; | |
| 308 | |
| 309 /** | |
| 310 * |tcpEvent| and |udpEvent| are the event types for data transfers using | |
| 311 * TCP and UDP, respectively. | |
| 312 * @constructor | |
| 313 */ | |
| 314 function NetworkTransferRateDataSeries(tcpEvent, udpEvent) { | |
| 315 superClass.call(this); | |
| 316 this.tcpEvent_ = tcpEvent; | |
| 317 this.udpEvent_ = udpEvent; | |
| 318 } | |
| 319 | |
| 320 NetworkTransferRateDataSeries.prototype = { | |
| 321 // Inherit the superclass's methods. | |
| 322 __proto__: superClass.prototype, | |
| 323 | |
| 324 onReceivedLogEntry: function(entry) { | |
| 325 if (entry.type != this.tcpEvent_ && entry.type != this.udpEvent_) | |
| 326 return; | |
| 327 this.addPoint(entry.time, entry.params.byte_count); | |
| 328 }, | |
| 329 }; | |
| 330 | |
| 331 return NetworkTransferRateDataSeries; | |
| 332 })(); | |
| 333 | |
| 334 /** | |
| 335 * Tracks combined disk cache read or write rate. Doesn't include clearing, | |
| 336 * opening, or dooming entries, as they don't have clear size values. | |
| 337 */ | |
| 338 var DiskCacheTransferRateDataSeries = (function() { | |
| 339 'use strict'; | |
| 340 | |
| 341 var superClass = TransferRateDataSeries; | |
| 342 | |
| 343 /** | |
| 344 * @constructor | |
| 345 */ | |
| 346 function DiskCacheTransferRateDataSeries(eventType) { | |
| 347 superClass.call(this); | |
| 348 this.eventType_ = eventType; | |
| 349 } | |
| 350 | |
| 351 DiskCacheTransferRateDataSeries.prototype = { | |
| 352 // Inherit the superclass's methods. | |
| 353 __proto__: superClass.prototype, | |
| 354 | |
| 355 onReceivedLogEntry: function(entry) { | |
| 356 if (entry.source.type != LogSourceType.DISK_CACHE_ENTRY || | |
| 357 entry.type != this.eventType_ || | |
| 358 entry.phase != LogEventPhase.PHASE_END) { | |
| 359 return; | |
| 360 } | |
| 361 // The disk cache has a lot of 0-length writes, when truncating entries. | |
| 362 // Ignore those. | |
| 363 if (entry.params.bytes_copied != 0) | |
| 364 this.addPoint(entry.time, entry.params.bytes_copied); | |
| 365 } | |
| 366 }; | |
| 367 | |
| 368 return DiskCacheTransferRateDataSeries; | |
| 369 })(); | |
| OLD | NEW |