| OLD | NEW |
| 1 /* | 1 /* |
| 2 * Copyright (C) 2013 Google Inc. All rights reserved. | 2 * Copyright (C) 2013 Google Inc. All rights reserved. |
| 3 * | 3 * |
| 4 * Redistribution and use in source and binary forms, with or without | 4 * Redistribution and use in source and binary forms, with or without |
| 5 * modification, are permitted provided that the following conditions are | 5 * modification, are permitted provided that the following conditions are |
| 6 * met: | 6 * met: |
| 7 * | 7 * |
| 8 * * Redistributions of source code must retain the above copyright | 8 * * Redistributions of source code must retain the above copyright |
| 9 * notice, this list of conditions and the following disclaimer. | 9 * notice, this list of conditions and the following disclaimer. |
| 10 * * Redistributions in binary form must reproduce the above | 10 * * Redistributions in binary form must reproduce the above |
| 11 * copyright notice, this list of conditions and the following disclaimer | 11 * copyright notice, this list of conditions and the following disclaimer |
| 12 * in the documentation and/or other materials provided with the | 12 * in the documentation and/or other materials provided with the |
| 13 * distribution. | 13 * distribution. |
| 14 * * Neither the name of Google Inc. nor the names of its | 14 * * Neither the name of Google Inc. nor the names of its |
| 15 * contributors may be used to endorse or promote products derived from | 15 * contributors may be used to endorse or promote products derived from |
| 16 * this software without specific prior written permission. | 16 * this software without specific prior written permission. |
| 17 * | 17 * |
| 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 29 */ | 29 */ |
| 30 | 30 /** |
| 31 /** | 31 * @unrestricted |
| 32 * @constructor | 32 */ |
| 33 * @extends {WebInspector.TimelineOverviewBase} | 33 WebInspector.TimelineEventOverview = class extends WebInspector.TimelineOverview
Base { |
| 34 * @param {string} id | 34 /** |
| 35 * @param {?string} title | 35 * @param {string} id |
| 36 * @param {!WebInspector.TimelineModel} model | 36 * @param {?string} title |
| 37 */ | 37 * @param {!WebInspector.TimelineModel} model |
| 38 WebInspector.TimelineEventOverview = function(id, title, model) | 38 */ |
| 39 { | 39 constructor(id, title, model) { |
| 40 WebInspector.TimelineOverviewBase.call(this); | 40 super(); |
| 41 this.element.id = "timeline-overview-" + id; | 41 this.element.id = 'timeline-overview-' + id; |
| 42 this.element.classList.add("overview-strip"); | 42 this.element.classList.add('overview-strip'); |
| 43 if (title) | 43 if (title) |
| 44 this.element.createChild("div", "timeline-overview-strip-title").textCon
tent = title; | 44 this.element.createChild('div', 'timeline-overview-strip-title').textConte
nt = title; |
| 45 this._model = model; | 45 this._model = model; |
| 46 }; | 46 } |
| 47 | 47 |
| 48 WebInspector.TimelineEventOverview.prototype = { | 48 /** |
| 49 /** | 49 * @param {number} begin |
| 50 * @param {number} begin | 50 * @param {number} end |
| 51 * @param {number} end | 51 * @param {number} position |
| 52 * @param {number} position | 52 * @param {number} height |
| 53 * @param {number} height | 53 * @param {string} color |
| 54 * @param {string} color | 54 */ |
| 55 */ | 55 _renderBar(begin, end, position, height, color) { |
| 56 _renderBar: function(begin, end, position, height, color) | 56 var x = begin; |
| 57 { | 57 var width = end - begin; |
| 58 var x = begin; | 58 this._context.fillStyle = color; |
| 59 var width = end - begin; | 59 this._context.fillRect(x, position, width, height); |
| 60 this._context.fillStyle = color; | 60 } |
| 61 this._context.fillRect(x, position, width, height); | 61 |
| 62 }, | 62 /** |
| 63 | 63 * @override |
| 64 /** | 64 * @param {number} windowLeft |
| 65 * @override | 65 * @param {number} windowRight |
| 66 * @param {number} windowLeft | 66 * @return {!{startTime: number, endTime: number}} |
| 67 * @param {number} windowRight | 67 */ |
| 68 * @return {!{startTime: number, endTime: number}} | 68 windowTimes(windowLeft, windowRight) { |
| 69 */ | 69 var absoluteMin = this._model.minimumRecordTime(); |
| 70 windowTimes: function(windowLeft, windowRight) | 70 var timeSpan = this._model.maximumRecordTime() - absoluteMin; |
| 71 { | 71 return {startTime: absoluteMin + timeSpan * windowLeft, endTime: absoluteMin
+ timeSpan * windowRight}; |
| 72 var absoluteMin = this._model.minimumRecordTime(); | 72 } |
| 73 var timeSpan = this._model.maximumRecordTime() - absoluteMin; | 73 |
| 74 return { | 74 /** |
| 75 startTime: absoluteMin + timeSpan * windowLeft, | 75 * @override |
| 76 endTime: absoluteMin + timeSpan * windowRight | 76 * @param {number} startTime |
| 77 }; | 77 * @param {number} endTime |
| 78 }, | 78 * @return {!{left: number, right: number}} |
| 79 | 79 */ |
| 80 /** | 80 windowBoundaries(startTime, endTime) { |
| 81 * @override | 81 var absoluteMin = this._model.minimumRecordTime(); |
| 82 * @param {number} startTime | 82 var timeSpan = this._model.maximumRecordTime() - absoluteMin; |
| 83 * @param {number} endTime | 83 var haveRecords = absoluteMin > 0; |
| 84 * @return {!{left: number, right: number}} | 84 return { |
| 85 */ | 85 left: haveRecords && startTime ? Math.min((startTime - absoluteMin) / time
Span, 1) : 0, |
| 86 windowBoundaries: function(startTime, endTime) | 86 right: haveRecords && endTime < Infinity ? (endTime - absoluteMin) / timeS
pan : 1 |
| 87 { | 87 }; |
| 88 var absoluteMin = this._model.minimumRecordTime(); | 88 } |
| 89 var timeSpan = this._model.maximumRecordTime() - absoluteMin; | 89 }; |
| 90 var haveRecords = absoluteMin > 0; | 90 |
| 91 return { | 91 /** |
| 92 left: haveRecords && startTime ? Math.min((startTime - absoluteMin)
/ timeSpan, 1) : 0, | 92 * @unrestricted |
| 93 right: haveRecords && endTime < Infinity ? (endTime - absoluteMin) /
timeSpan : 1 | 93 */ |
| 94 }; | 94 WebInspector.TimelineEventOverviewInput = class extends WebInspector.TimelineEve
ntOverview { |
| 95 }, | 95 /** |
| 96 | 96 * @param {!WebInspector.TimelineModel} model |
| 97 __proto__: WebInspector.TimelineOverviewBase.prototype | 97 */ |
| 98 }; | 98 constructor(model) { |
| 99 | 99 super('input', null, model); |
| 100 /** | 100 } |
| 101 * @constructor | 101 |
| 102 * @extends {WebInspector.TimelineEventOverview} | 102 /** |
| 103 * @param {!WebInspector.TimelineModel} model | 103 * @override |
| 104 */ | 104 */ |
| 105 WebInspector.TimelineEventOverview.Input = function(model) | 105 update() { |
| 106 { | 106 super.update(); |
| 107 WebInspector.TimelineEventOverview.call(this, "input", null, model); | 107 var events = this._model.mainThreadEvents(); |
| 108 }; | 108 var height = this._canvas.height; |
| 109 | 109 var descriptors = WebInspector.TimelineUIUtils.eventDispatchDesciptors(); |
| 110 WebInspector.TimelineEventOverview.Input.prototype = { | 110 /** @type {!Map.<string,!WebInspector.TimelineUIUtils.EventDispatchTypeDescr
iptor>} */ |
| 111 /** | 111 var descriptorsByType = new Map(); |
| 112 * @override | 112 var maxPriority = -1; |
| 113 */ | 113 for (var descriptor of descriptors) { |
| 114 update: function() | 114 for (var type of descriptor.eventTypes) |
| 115 { | 115 descriptorsByType.set(type, descriptor); |
| 116 WebInspector.TimelineEventOverview.prototype.update.call(this); | 116 maxPriority = Math.max(maxPriority, descriptor.priority); |
| 117 var events = this._model.mainThreadEvents(); | 117 } |
| 118 var height = this._canvas.height; | 118 |
| 119 var descriptors = WebInspector.TimelineUIUtils.eventDispatchDesciptors()
; | 119 var /** @const */ minWidth = 2 * window.devicePixelRatio; |
| 120 /** @type {!Map.<string,!WebInspector.TimelineUIUtils.EventDispatchTypeD
escriptor>} */ | 120 var timeOffset = this._model.minimumRecordTime(); |
| 121 var descriptorsByType = new Map(); | 121 var timeSpan = this._model.maximumRecordTime() - timeOffset; |
| 122 var maxPriority = -1; | 122 var canvasWidth = this._canvas.width; |
| 123 for (var descriptor of descriptors) { | 123 var scale = canvasWidth / timeSpan; |
| 124 for (var type of descriptor.eventTypes) | 124 |
| 125 descriptorsByType.set(type, descriptor); | 125 for (var priority = 0; priority <= maxPriority; ++priority) { |
| 126 maxPriority = Math.max(maxPriority, descriptor.priority); | 126 for (var i = 0; i < events.length; ++i) { |
| 127 var event = events[i]; |
| 128 if (event.name !== WebInspector.TimelineModel.RecordType.EventDispatch) |
| 129 continue; |
| 130 var descriptor = descriptorsByType.get(event.args['data']['type']); |
| 131 if (!descriptor || descriptor.priority !== priority) |
| 132 continue; |
| 133 var start = Number.constrain(Math.floor((event.startTime - timeOffset) *
scale), 0, canvasWidth); |
| 134 var end = Number.constrain(Math.ceil((event.endTime - timeOffset) * scal
e), 0, canvasWidth); |
| 135 var width = Math.max(end - start, minWidth); |
| 136 this._renderBar(start, start + width, 0, height, descriptor.color); |
| 137 } |
| 138 } |
| 139 } |
| 140 }; |
| 141 |
| 142 /** |
| 143 * @unrestricted |
| 144 */ |
| 145 WebInspector.TimelineEventOverviewNetwork = class extends WebInspector.TimelineE
ventOverview { |
| 146 /** |
| 147 * @param {!WebInspector.TimelineModel} model |
| 148 */ |
| 149 constructor(model) { |
| 150 super('network', WebInspector.UIString('NET'), model); |
| 151 } |
| 152 |
| 153 /** |
| 154 * @override |
| 155 */ |
| 156 update() { |
| 157 super.update(); |
| 158 var height = this._canvas.height; |
| 159 var numBands = categoryBand(WebInspector.TimelineUIUtils.NetworkCategory.Oth
er) + 1; |
| 160 var bandHeight = Math.floor(height / numBands); |
| 161 var devicePixelRatio = window.devicePixelRatio; |
| 162 var timeOffset = this._model.minimumRecordTime(); |
| 163 var timeSpan = this._model.maximumRecordTime() - timeOffset; |
| 164 var canvasWidth = this._canvas.width; |
| 165 var scale = canvasWidth / timeSpan; |
| 166 var ctx = this._context; |
| 167 var requests = this._model.networkRequests(); |
| 168 /** @type {!Map<string,!{waiting:!Path2D,transfer:!Path2D}>} */ |
| 169 var paths = new Map(); |
| 170 requests.forEach(drawRequest); |
| 171 for (var path of paths) { |
| 172 ctx.fillStyle = path[0]; |
| 173 ctx.globalAlpha = 0.3; |
| 174 ctx.fill(path[1]['waiting']); |
| 175 ctx.globalAlpha = 1; |
| 176 ctx.fill(path[1]['transfer']); |
| 177 } |
| 178 |
| 179 /** |
| 180 * @param {!WebInspector.TimelineUIUtils.NetworkCategory} category |
| 181 * @return {number} |
| 182 */ |
| 183 function categoryBand(category) { |
| 184 var categories = WebInspector.TimelineUIUtils.NetworkCategory; |
| 185 switch (category) { |
| 186 case categories.HTML: |
| 187 return 0; |
| 188 case categories.Script: |
| 189 return 1; |
| 190 case categories.Style: |
| 191 return 2; |
| 192 case categories.Media: |
| 193 return 3; |
| 194 default: |
| 195 return 4; |
| 196 } |
| 197 } |
| 198 |
| 199 /** |
| 200 * @param {!WebInspector.TimelineModel.NetworkRequest} request |
| 201 */ |
| 202 function drawRequest(request) { |
| 203 var tickWidth = 2 * devicePixelRatio; |
| 204 var category = WebInspector.TimelineUIUtils.networkRequestCategory(request
); |
| 205 var style = WebInspector.TimelineUIUtils.networkCategoryColor(category); |
| 206 var band = categoryBand(category); |
| 207 var y = band * bandHeight; |
| 208 var path = paths.get(style); |
| 209 if (!path) { |
| 210 path = {waiting: new Path2D(), transfer: new Path2D()}; |
| 211 paths.set(style, path); |
| 212 } |
| 213 var s = Math.max(Math.floor((request.startTime - timeOffset) * scale), 0); |
| 214 var e = Math.min(Math.ceil((request.endTime - timeOffset) * scale), canvas
Width); |
| 215 path['waiting'].rect(s, y, e - s, bandHeight - 1); |
| 216 path['transfer'].rect(e - tickWidth / 2, y, tickWidth, bandHeight - 1); |
| 217 if (!request.responseTime) |
| 218 return; |
| 219 var r = Math.ceil((request.responseTime - timeOffset) * scale); |
| 220 path['transfer'].rect(r - tickWidth / 2, y, tickWidth, bandHeight - 1); |
| 221 } |
| 222 } |
| 223 }; |
| 224 |
| 225 /** |
| 226 * @unrestricted |
| 227 */ |
| 228 WebInspector.TimelineEventOverviewCPUActivity = class extends WebInspector.Timel
ineEventOverview { |
| 229 /** |
| 230 * @param {!WebInspector.TimelineModel} model |
| 231 */ |
| 232 constructor(model) { |
| 233 super('cpu-activity', WebInspector.UIString('CPU'), model); |
| 234 this._backgroundCanvas = this.element.createChild('canvas', 'fill background
'); |
| 235 } |
| 236 |
| 237 /** |
| 238 * @override |
| 239 */ |
| 240 resetCanvas() { |
| 241 super.resetCanvas(); |
| 242 this._backgroundCanvas.width = this.element.clientWidth * window.devicePixel
Ratio; |
| 243 this._backgroundCanvas.height = this.element.clientHeight * window.devicePix
elRatio; |
| 244 } |
| 245 |
| 246 /** |
| 247 * @override |
| 248 */ |
| 249 update() { |
| 250 super.update(); |
| 251 var /** @const */ quantSizePx = 4 * window.devicePixelRatio; |
| 252 var width = this._canvas.width; |
| 253 var height = this._canvas.height; |
| 254 var baseLine = height; |
| 255 var timeOffset = this._model.minimumRecordTime(); |
| 256 var timeSpan = this._model.maximumRecordTime() - timeOffset; |
| 257 var scale = width / timeSpan; |
| 258 var quantTime = quantSizePx / scale; |
| 259 var categories = WebInspector.TimelineUIUtils.categories(); |
| 260 var categoryOrder = ['idle', 'loading', 'painting', 'rendering', 'scripting'
, 'other']; |
| 261 var otherIndex = categoryOrder.indexOf('other'); |
| 262 var idleIndex = 0; |
| 263 console.assert(idleIndex === categoryOrder.indexOf('idle')); |
| 264 for (var i = idleIndex + 1; i < categoryOrder.length; ++i) |
| 265 categories[categoryOrder[i]]._overviewIndex = i; |
| 266 |
| 267 var backgroundContext = this._backgroundCanvas.getContext('2d'); |
| 268 for (var thread of this._model.virtualThreads()) |
| 269 drawThreadEvents(backgroundContext, thread.events); |
| 270 applyPattern(backgroundContext); |
| 271 drawThreadEvents(this._context, this._model.mainThreadEvents()); |
| 272 |
| 273 /** |
| 274 * @param {!CanvasRenderingContext2D} ctx |
| 275 * @param {!Array<!WebInspector.TracingModel.Event>} events |
| 276 */ |
| 277 function drawThreadEvents(ctx, events) { |
| 278 var quantizer = new WebInspector.Quantizer(timeOffset, quantTime, drawSamp
le); |
| 279 var x = 0; |
| 280 var categoryIndexStack = []; |
| 281 var paths = []; |
| 282 var lastY = []; |
| 283 for (var i = 0; i < categoryOrder.length; ++i) { |
| 284 paths[i] = new Path2D(); |
| 285 paths[i].moveTo(0, height); |
| 286 lastY[i] = height; |
| 287 } |
| 288 |
| 289 /** |
| 290 * @param {!Array<number>} counters |
| 291 */ |
| 292 function drawSample(counters) { |
| 293 var y = baseLine; |
| 294 for (var i = idleIndex + 1; i < categoryOrder.length; ++i) { |
| 295 var h = (counters[i] || 0) / quantTime * height; |
| 296 y -= h; |
| 297 paths[i].bezierCurveTo(x, lastY[i], x, y, x + quantSizePx / 2, y); |
| 298 lastY[i] = y; |
| 127 } | 299 } |
| 128 | 300 x += quantSizePx; |
| 129 var /** @const */ minWidth = 2 * window.devicePixelRatio; | 301 } |
| 130 var timeOffset = this._model.minimumRecordTime(); | 302 |
| 131 var timeSpan = this._model.maximumRecordTime() - timeOffset; | 303 /** |
| 132 var canvasWidth = this._canvas.width; | 304 * @param {!WebInspector.TracingModel.Event} e |
| 133 var scale = canvasWidth / timeSpan; | 305 */ |
| 134 | 306 function onEventStart(e) { |
| 135 for (var priority = 0; priority <= maxPriority; ++priority) { | 307 var index = categoryIndexStack.length ? categoryIndexStack.peekLast() :
idleIndex; |
| 136 for (var i = 0; i < events.length; ++i) { | 308 quantizer.appendInterval(e.startTime, index); |
| 137 var event = events[i]; | 309 categoryIndexStack.push(WebInspector.TimelineUIUtils.eventStyle(e).categ
ory._overviewIndex || otherIndex); |
| 138 if (event.name !== WebInspector.TimelineModel.RecordType.EventDi
spatch) | 310 } |
| 139 continue; | 311 |
| 140 var descriptor = descriptorsByType.get(event.args["data"]["type"
]); | 312 /** |
| 141 if (!descriptor || descriptor.priority !== priority) | 313 * @param {!WebInspector.TracingModel.Event} e |
| 142 continue; | 314 */ |
| 143 var start = Number.constrain(Math.floor((event.startTime - timeO
ffset) * scale), 0, canvasWidth); | 315 function onEventEnd(e) { |
| 144 var end = Number.constrain(Math.ceil((event.endTime - timeOffset
) * scale), 0, canvasWidth); | 316 quantizer.appendInterval(e.endTime, categoryIndexStack.pop()); |
| 145 var width = Math.max(end - start, minWidth); | 317 } |
| 146 this._renderBar(start, start + width, 0, height, descriptor.colo
r); | 318 |
| 147 } | 319 WebInspector.TimelineModel.forEachEvent(events, onEventStart, onEventEnd); |
| 148 } | 320 quantizer.appendInterval(timeOffset + timeSpan + quantTime, idleIndex); /
/ Kick drawing the last bucket. |
| 149 }, | 321 for (var i = categoryOrder.length - 1; i > 0; --i) { |
| 150 | 322 paths[i].lineTo(width, height); |
| 151 __proto__: WebInspector.TimelineEventOverview.prototype | 323 ctx.fillStyle = categories[categoryOrder[i]].color; |
| 152 }; | 324 ctx.fill(paths[i]); |
| 153 | 325 } |
| 154 /** | 326 } |
| 155 * @constructor | 327 |
| 156 * @extends {WebInspector.TimelineEventOverview} | 328 /** |
| 157 * @param {!WebInspector.TimelineModel} model | 329 * @param {!CanvasRenderingContext2D} ctx |
| 158 */ | 330 */ |
| 159 WebInspector.TimelineEventOverview.Network = function(model) | 331 function applyPattern(ctx) { |
| 160 { | 332 var step = 4 * window.devicePixelRatio; |
| 161 WebInspector.TimelineEventOverview.call(this, "network", WebInspector.UIStri
ng("NET"), model); | 333 ctx.save(); |
| 162 }; | 334 ctx.lineWidth = step / Math.sqrt(8); |
| 163 | 335 for (var x = 0.5; x < width + height; x += step) { |
| 164 WebInspector.TimelineEventOverview.Network.prototype = { | 336 ctx.moveTo(x, 0); |
| 165 /** | 337 ctx.lineTo(x - height, height); |
| 166 * @override | 338 } |
| 167 */ | 339 ctx.globalCompositeOperation = 'destination-out'; |
| 168 update: function() | 340 ctx.stroke(); |
| 169 { | 341 ctx.restore(); |
| 170 WebInspector.TimelineEventOverview.prototype.update.call(this); | 342 } |
| 171 var height = this._canvas.height; | 343 } |
| 172 var numBands = categoryBand(WebInspector.TimelineUIUtils.NetworkCategory
.Other) + 1; | 344 }; |
| 173 var bandHeight = Math.floor(height / numBands); | 345 |
| 174 var devicePixelRatio = window.devicePixelRatio; | 346 /** |
| 175 var timeOffset = this._model.minimumRecordTime(); | 347 * @unrestricted |
| 176 var timeSpan = this._model.maximumRecordTime() - timeOffset; | 348 */ |
| 177 var canvasWidth = this._canvas.width; | 349 WebInspector.TimelineEventOverviewResponsiveness = class extends WebInspector.Ti
melineEventOverview { |
| 178 var scale = canvasWidth / timeSpan; | 350 /** |
| 179 var ctx = this._context; | 351 * @param {!WebInspector.TimelineModel} model |
| 180 var requests = this._model.networkRequests(); | 352 * @param {!WebInspector.TimelineFrameModel} frameModel |
| 181 /** @type {!Map<string,!{waiting:!Path2D,transfer:!Path2D}>} */ | 353 */ |
| 182 var paths = new Map(); | 354 constructor(model, frameModel) { |
| 183 requests.forEach(drawRequest); | 355 super('responsiveness', null, model); |
| 184 for (var path of paths) { | |
| 185 ctx.fillStyle = path[0]; | |
| 186 ctx.globalAlpha = 0.3; | |
| 187 ctx.fill(path[1]["waiting"]); | |
| 188 ctx.globalAlpha = 1; | |
| 189 ctx.fill(path[1]["transfer"]); | |
| 190 } | |
| 191 | |
| 192 /** | |
| 193 * @param {!WebInspector.TimelineUIUtils.NetworkCategory} category | |
| 194 * @return {number} | |
| 195 */ | |
| 196 function categoryBand(category) | |
| 197 { | |
| 198 var categories = WebInspector.TimelineUIUtils.NetworkCategory; | |
| 199 switch (category) { | |
| 200 case categories.HTML: return 0; | |
| 201 case categories.Script: return 1; | |
| 202 case categories.Style: return 2; | |
| 203 case categories.Media: return 3; | |
| 204 default: return 4; | |
| 205 } | |
| 206 } | |
| 207 | |
| 208 /** | |
| 209 * @param {!WebInspector.TimelineModel.NetworkRequest} request | |
| 210 */ | |
| 211 function drawRequest(request) | |
| 212 { | |
| 213 var tickWidth = 2 * devicePixelRatio; | |
| 214 var category = WebInspector.TimelineUIUtils.networkRequestCategory(r
equest); | |
| 215 var style = WebInspector.TimelineUIUtils.networkCategoryColor(catego
ry); | |
| 216 var band = categoryBand(category); | |
| 217 var y = band * bandHeight; | |
| 218 var path = paths.get(style); | |
| 219 if (!path) { | |
| 220 path = { waiting: new Path2D(), transfer: new Path2D() }; | |
| 221 paths.set(style, path); | |
| 222 } | |
| 223 var s = Math.max(Math.floor((request.startTime - timeOffset) * scale
), 0); | |
| 224 var e = Math.min(Math.ceil((request.endTime - timeOffset) * scale),
canvasWidth); | |
| 225 path["waiting"].rect(s, y, e - s, bandHeight - 1); | |
| 226 path["transfer"].rect(e - tickWidth / 2, y, tickWidth, bandHeight -
1); | |
| 227 if (!request.responseTime) | |
| 228 return; | |
| 229 var r = Math.ceil((request.responseTime - timeOffset) * scale); | |
| 230 path["transfer"].rect(r - tickWidth / 2, y, tickWidth, bandHeight -
1); | |
| 231 } | |
| 232 }, | |
| 233 | |
| 234 __proto__: WebInspector.TimelineEventOverview.prototype | |
| 235 }; | |
| 236 | |
| 237 /** | |
| 238 * @constructor | |
| 239 * @extends {WebInspector.TimelineEventOverview} | |
| 240 * @param {!WebInspector.TimelineModel} model | |
| 241 */ | |
| 242 WebInspector.TimelineEventOverview.CPUActivity = function(model) | |
| 243 { | |
| 244 WebInspector.TimelineEventOverview.call(this, "cpu-activity", WebInspector.U
IString("CPU"), model); | |
| 245 this._backgroundCanvas = this.element.createChild("canvas", "fill background
"); | |
| 246 }; | |
| 247 | |
| 248 WebInspector.TimelineEventOverview.CPUActivity.prototype = { | |
| 249 /** | |
| 250 * @override | |
| 251 */ | |
| 252 resetCanvas: function() | |
| 253 { | |
| 254 WebInspector.TimelineEventOverview.prototype.resetCanvas.call(this); | |
| 255 this._backgroundCanvas.width = this.element.clientWidth * window.deviceP
ixelRatio; | |
| 256 this._backgroundCanvas.height = this.element.clientHeight * window.devic
ePixelRatio; | |
| 257 }, | |
| 258 | |
| 259 /** | |
| 260 * @override | |
| 261 */ | |
| 262 update: function() | |
| 263 { | |
| 264 WebInspector.TimelineEventOverview.prototype.update.call(this); | |
| 265 var /** @const */ quantSizePx = 4 * window.devicePixelRatio; | |
| 266 var width = this._canvas.width; | |
| 267 var height = this._canvas.height; | |
| 268 var baseLine = height; | |
| 269 var timeOffset = this._model.minimumRecordTime(); | |
| 270 var timeSpan = this._model.maximumRecordTime() - timeOffset; | |
| 271 var scale = width / timeSpan; | |
| 272 var quantTime = quantSizePx / scale; | |
| 273 var categories = WebInspector.TimelineUIUtils.categories(); | |
| 274 var categoryOrder = ["idle", "loading", "painting", "rendering", "script
ing", "other"]; | |
| 275 var otherIndex = categoryOrder.indexOf("other"); | |
| 276 var idleIndex = 0; | |
| 277 console.assert(idleIndex === categoryOrder.indexOf("idle")); | |
| 278 for (var i = idleIndex + 1; i < categoryOrder.length; ++i) | |
| 279 categories[categoryOrder[i]]._overviewIndex = i; | |
| 280 | |
| 281 var backgroundContext = this._backgroundCanvas.getContext("2d"); | |
| 282 for (var thread of this._model.virtualThreads()) | |
| 283 drawThreadEvents(backgroundContext, thread.events); | |
| 284 applyPattern(backgroundContext); | |
| 285 drawThreadEvents(this._context, this._model.mainThreadEvents()); | |
| 286 | |
| 287 /** | |
| 288 * @param {!CanvasRenderingContext2D} ctx | |
| 289 * @param {!Array<!WebInspector.TracingModel.Event>} events | |
| 290 */ | |
| 291 function drawThreadEvents(ctx, events) | |
| 292 { | |
| 293 var quantizer = new WebInspector.Quantizer(timeOffset, quantTime, dr
awSample); | |
| 294 var x = 0; | |
| 295 var categoryIndexStack = []; | |
| 296 var paths = []; | |
| 297 var lastY = []; | |
| 298 for (var i = 0; i < categoryOrder.length; ++i) { | |
| 299 paths[i] = new Path2D(); | |
| 300 paths[i].moveTo(0, height); | |
| 301 lastY[i] = height; | |
| 302 } | |
| 303 | |
| 304 /** | |
| 305 * @param {!Array<number>} counters | |
| 306 */ | |
| 307 function drawSample(counters) | |
| 308 { | |
| 309 var y = baseLine; | |
| 310 for (var i = idleIndex + 1; i < categoryOrder.length; ++i) { | |
| 311 var h = (counters[i] || 0) / quantTime * height; | |
| 312 y -= h; | |
| 313 paths[i].bezierCurveTo(x, lastY[i], x, y, x + quantSizePx /
2, y); | |
| 314 lastY[i] = y; | |
| 315 } | |
| 316 x += quantSizePx; | |
| 317 } | |
| 318 | |
| 319 /** | |
| 320 * @param {!WebInspector.TracingModel.Event} e | |
| 321 */ | |
| 322 function onEventStart(e) | |
| 323 { | |
| 324 var index = categoryIndexStack.length ? categoryIndexStack.peekL
ast() : idleIndex; | |
| 325 quantizer.appendInterval(e.startTime, index); | |
| 326 categoryIndexStack.push(WebInspector.TimelineUIUtils.eventStyle(
e).category._overviewIndex || otherIndex); | |
| 327 } | |
| 328 | |
| 329 /** | |
| 330 * @param {!WebInspector.TracingModel.Event} e | |
| 331 */ | |
| 332 function onEventEnd(e) | |
| 333 { | |
| 334 quantizer.appendInterval(e.endTime, categoryIndexStack.pop()); | |
| 335 } | |
| 336 | |
| 337 WebInspector.TimelineModel.forEachEvent(events, onEventStart, onEven
tEnd); | |
| 338 quantizer.appendInterval(timeOffset + timeSpan + quantTime, idleInde
x); // Kick drawing the last bucket. | |
| 339 for (var i = categoryOrder.length - 1; i > 0; --i) { | |
| 340 paths[i].lineTo(width, height); | |
| 341 ctx.fillStyle = categories[categoryOrder[i]].color; | |
| 342 ctx.fill(paths[i]); | |
| 343 } | |
| 344 } | |
| 345 | |
| 346 /** | |
| 347 * @param {!CanvasRenderingContext2D} ctx | |
| 348 */ | |
| 349 function applyPattern(ctx) | |
| 350 { | |
| 351 var step = 4 * window.devicePixelRatio; | |
| 352 ctx.save(); | |
| 353 ctx.lineWidth = step / Math.sqrt(8); | |
| 354 for (var x = 0.5; x < width + height; x += step) { | |
| 355 ctx.moveTo(x, 0); | |
| 356 ctx.lineTo(x - height, height); | |
| 357 } | |
| 358 ctx.globalCompositeOperation = "destination-out"; | |
| 359 ctx.stroke(); | |
| 360 ctx.restore(); | |
| 361 } | |
| 362 }, | |
| 363 | |
| 364 __proto__: WebInspector.TimelineEventOverview.prototype | |
| 365 }; | |
| 366 | |
| 367 /** | |
| 368 * @constructor | |
| 369 * @extends {WebInspector.TimelineEventOverview} | |
| 370 * @param {!WebInspector.TimelineModel} model | |
| 371 * @param {!WebInspector.TimelineFrameModel} frameModel | |
| 372 */ | |
| 373 WebInspector.TimelineEventOverview.Responsiveness = function(model, frameModel) | |
| 374 { | |
| 375 WebInspector.TimelineEventOverview.call(this, "responsiveness", null, model)
; | |
| 376 this._frameModel = frameModel; | 356 this._frameModel = frameModel; |
| 377 }; | 357 } |
| 378 | 358 |
| 379 WebInspector.TimelineEventOverview.Responsiveness.prototype = { | 359 /** |
| 380 /** | 360 * @override |
| 381 * @override | 361 */ |
| 382 */ | 362 update() { |
| 383 update: function() | 363 super.update(); |
| 384 { | 364 var height = this._canvas.height; |
| 385 WebInspector.TimelineEventOverview.prototype.update.call(this); | 365 var timeOffset = this._model.minimumRecordTime(); |
| 386 var height = this._canvas.height; | 366 var timeSpan = this._model.maximumRecordTime() - timeOffset; |
| 387 var timeOffset = this._model.minimumRecordTime(); | 367 var scale = this._canvas.width / timeSpan; |
| 388 var timeSpan = this._model.maximumRecordTime() - timeOffset; | 368 var frames = this._frameModel.frames(); |
| 389 var scale = this._canvas.width / timeSpan; | 369 var ctx = this._context; |
| 390 var frames = this._frameModel.frames(); | 370 var fillPath = new Path2D(); |
| 391 var ctx = this._context; | 371 var markersPath = new Path2D(); |
| 392 var fillPath = new Path2D(); | 372 for (var i = 0; i < frames.length; ++i) { |
| 393 var markersPath = new Path2D(); | 373 var frame = frames[i]; |
| 394 for (var i = 0; i < frames.length; ++i) { | 374 if (!frame.hasWarnings()) |
| 395 var frame = frames[i]; | 375 continue; |
| 396 if (!frame.hasWarnings()) | 376 paintWarningDecoration(frame.startTime, frame.duration); |
| 397 continue; | 377 } |
| 398 paintWarningDecoration(frame.startTime, frame.duration); | 378 |
| 399 } | 379 var events = this._model.mainThreadEvents(); |
| 400 | 380 for (var i = 0; i < events.length; ++i) { |
| 401 var events = this._model.mainThreadEvents(); | 381 if (!events[i].warning) |
| 402 for (var i = 0; i < events.length; ++i) { | 382 continue; |
| 403 if (!events[i].warning) | 383 paintWarningDecoration(events[i].startTime, events[i].duration); |
| 404 continue; | 384 } |
| 405 paintWarningDecoration(events[i].startTime, events[i].duration); | 385 |
| 406 } | 386 ctx.fillStyle = 'hsl(0, 80%, 90%)'; |
| 407 | 387 ctx.strokeStyle = 'red'; |
| 408 ctx.fillStyle = "hsl(0, 80%, 90%)"; | 388 ctx.lineWidth = 2 * window.devicePixelRatio; |
| 409 ctx.strokeStyle = "red"; | 389 ctx.fill(fillPath); |
| 410 ctx.lineWidth = 2 * window.devicePixelRatio; | 390 ctx.stroke(markersPath); |
| 411 ctx.fill(fillPath); | 391 |
| 412 ctx.stroke(markersPath); | 392 /** |
| 413 | 393 * @param {number} time |
| 414 /** | 394 * @param {number} duration |
| 415 * @param {number} time | 395 */ |
| 416 * @param {number} duration | 396 function paintWarningDecoration(time, duration) { |
| 417 */ | 397 var x = Math.round(scale * (time - timeOffset)); |
| 418 function paintWarningDecoration(time, duration) | 398 var w = Math.round(scale * duration); |
| 419 { | 399 fillPath.rect(x, 0, w, height); |
| 420 var x = Math.round(scale * (time - timeOffset)); | 400 markersPath.moveTo(x + w, 0); |
| 421 var w = Math.round(scale * duration); | 401 markersPath.lineTo(x + w, height); |
| 422 fillPath.rect(x, 0, w, height); | 402 } |
| 423 markersPath.moveTo(x + w, 0); | 403 } |
| 424 markersPath.lineTo(x + w, height); | 404 }; |
| 425 } | 405 |
| 426 }, | 406 /** |
| 427 | 407 * @unrestricted |
| 428 __proto__: WebInspector.TimelineEventOverview.prototype | 408 */ |
| 429 }; | 409 WebInspector.TimelineFilmStripOverview = class extends WebInspector.TimelineEven
tOverview { |
| 430 | 410 /** |
| 431 /** | 411 * @param {!WebInspector.TimelineModel} model |
| 432 * @constructor | 412 * @param {!WebInspector.FilmStripModel} filmStripModel |
| 433 * @extends {WebInspector.TimelineEventOverview} | 413 */ |
| 434 * @param {!WebInspector.TimelineModel} model | 414 constructor(model, filmStripModel) { |
| 435 * @param {!WebInspector.FilmStripModel} filmStripModel | 415 super('filmstrip', null, model); |
| 436 */ | |
| 437 WebInspector.TimelineFilmStripOverview = function(model, filmStripModel) | |
| 438 { | |
| 439 WebInspector.TimelineEventOverview.call(this, "filmstrip", null, model); | |
| 440 this._filmStripModel = filmStripModel; | 416 this._filmStripModel = filmStripModel; |
| 441 this.reset(); | 417 this.reset(); |
| 418 } |
| 419 |
| 420 /** |
| 421 * @override |
| 422 */ |
| 423 update() { |
| 424 super.update(); |
| 425 var frames = this._filmStripModel.frames(); |
| 426 if (!frames.length) |
| 427 return; |
| 428 |
| 429 var drawGeneration = Symbol('drawGeneration'); |
| 430 this._drawGeneration = drawGeneration; |
| 431 this._imageByFrame(frames[0]).then(image => { |
| 432 if (this._drawGeneration !== drawGeneration) |
| 433 return; |
| 434 if (!image.naturalWidth || !image.naturalHeight) |
| 435 return; |
| 436 var imageHeight = this._canvas.height - 2 * WebInspector.TimelineFilmStrip
Overview.Padding; |
| 437 var imageWidth = Math.ceil(imageHeight * image.naturalWidth / image.natura
lHeight); |
| 438 var popoverScale = Math.min(200 / image.naturalWidth, 1); |
| 439 this._emptyImage = new Image(image.naturalWidth * popoverScale, image.natu
ralHeight * popoverScale); |
| 440 this._drawFrames(imageWidth, imageHeight); |
| 441 }); |
| 442 } |
| 443 |
| 444 /** |
| 445 * @param {!WebInspector.FilmStripModel.Frame} frame |
| 446 * @return {!Promise<!HTMLImageElement>} |
| 447 */ |
| 448 _imageByFrame(frame) { |
| 449 var imagePromise = this._frameToImagePromise.get(frame); |
| 450 if (!imagePromise) { |
| 451 imagePromise = frame.imageDataPromise().then(createImage); |
| 452 this._frameToImagePromise.set(frame, imagePromise); |
| 453 } |
| 454 return imagePromise; |
| 455 |
| 456 /** |
| 457 * @param {?string} data |
| 458 * @return {!Promise<!HTMLImageElement>} |
| 459 */ |
| 460 function createImage(data) { |
| 461 var fulfill; |
| 462 var promise = new Promise(f => fulfill = f); |
| 463 |
| 464 var image = /** @type {!HTMLImageElement} */ (createElement('img')); |
| 465 if (data) |
| 466 image.src = 'data:image/jpg;base64,' + data; |
| 467 if (image.complete) { |
| 468 fulfill(image); |
| 469 } else { |
| 470 image.addEventListener('load', () => fulfill(image)); |
| 471 image.addEventListener('error', () => fulfill(image)); |
| 472 } |
| 473 return promise; |
| 474 } |
| 475 } |
| 476 |
| 477 /** |
| 478 * @param {number} imageWidth |
| 479 * @param {number} imageHeight |
| 480 */ |
| 481 _drawFrames(imageWidth, imageHeight) { |
| 482 if (!imageWidth) |
| 483 return; |
| 484 if (!this._filmStripModel.frames().length) |
| 485 return; |
| 486 var padding = WebInspector.TimelineFilmStripOverview.Padding; |
| 487 var width = this._canvas.width; |
| 488 var zeroTime = this._filmStripModel.zeroTime(); |
| 489 var spanTime = this._filmStripModel.spanTime(); |
| 490 var scale = spanTime / width; |
| 491 var context = this._canvas.getContext('2d'); |
| 492 var drawGeneration = this._drawGeneration; |
| 493 |
| 494 context.beginPath(); |
| 495 for (var x = padding; x < width; x += imageWidth + 2 * padding) { |
| 496 var time = zeroTime + (x + imageWidth / 2) * scale; |
| 497 var frame = this._filmStripModel.frameByTimestamp(time); |
| 498 if (!frame) |
| 499 continue; |
| 500 context.rect(x - 0.5, 0.5, imageWidth + 1, imageHeight + 1); |
| 501 this._imageByFrame(frame).then(drawFrameImage.bind(this, x)); |
| 502 } |
| 503 context.strokeStyle = '#ddd'; |
| 504 context.stroke(); |
| 505 |
| 506 /** |
| 507 * @param {number} x |
| 508 * @param {!HTMLImageElement} image |
| 509 * @this {WebInspector.TimelineFilmStripOverview} |
| 510 */ |
| 511 function drawFrameImage(x, image) { |
| 512 // Ignore draws deferred from a previous update call. |
| 513 if (this._drawGeneration !== drawGeneration) |
| 514 return; |
| 515 context.drawImage(image, x, 1, imageWidth, imageHeight); |
| 516 } |
| 517 } |
| 518 |
| 519 /** |
| 520 * @override |
| 521 * @param {number} x |
| 522 * @return {!Promise<?Element>} |
| 523 */ |
| 524 popoverElementPromise(x) { |
| 525 if (!this._filmStripModel.frames().length) |
| 526 return Promise.resolve(/** @type {?Element} */ (null)); |
| 527 |
| 528 var time = this._calculator.positionToTime(x); |
| 529 var frame = this._filmStripModel.frameByTimestamp(time); |
| 530 if (frame === this._lastFrame) |
| 531 return Promise.resolve(this._lastElement); |
| 532 var imagePromise = frame ? this._imageByFrame(frame) : Promise.resolve(this.
_emptyImage); |
| 533 return imagePromise.then(createFrameElement.bind(this)); |
| 534 |
| 535 /** |
| 536 * @this {WebInspector.TimelineFilmStripOverview} |
| 537 * @param {!HTMLImageElement} image |
| 538 * @return {?Element} |
| 539 */ |
| 540 function createFrameElement(image) { |
| 541 var element = createElementWithClass('div', 'frame'); |
| 542 element.createChild('div', 'thumbnail').appendChild(image); |
| 543 WebInspector.appendStyle(element, 'timeline/timelinePanel.css'); |
| 544 this._lastFrame = frame; |
| 545 this._lastElement = element; |
| 546 return element; |
| 547 } |
| 548 } |
| 549 |
| 550 /** |
| 551 * @override |
| 552 */ |
| 553 reset() { |
| 554 this._lastFrame = undefined; |
| 555 this._lastElement = null; |
| 556 /** @type {!Map<!WebInspector.FilmStripModel.Frame,!Promise<!HTMLImageElemen
t>>} */ |
| 557 this._frameToImagePromise = new Map(); |
| 558 this._imageWidth = 0; |
| 559 } |
| 442 }; | 560 }; |
| 443 | 561 |
| 444 WebInspector.TimelineFilmStripOverview.Padding = 2; | 562 WebInspector.TimelineFilmStripOverview.Padding = 2; |
| 445 | 563 |
| 446 WebInspector.TimelineFilmStripOverview.prototype = { | 564 /** |
| 447 /** | 565 * @unrestricted |
| 448 * @override | 566 */ |
| 449 */ | 567 WebInspector.TimelineEventOverviewFrames = class extends WebInspector.TimelineEv
entOverview { |
| 450 update: function() | 568 /** |
| 451 { | 569 * @param {!WebInspector.TimelineModel} model |
| 452 WebInspector.TimelineEventOverview.prototype.update.call(this); | 570 * @param {!WebInspector.TimelineFrameModel} frameModel |
| 453 var frames = this._filmStripModel.frames(); | 571 */ |
| 454 if (!frames.length) | 572 constructor(model, frameModel) { |
| 455 return; | 573 super('framerate', WebInspector.UIString('FPS'), model); |
| 456 | |
| 457 var drawGeneration = Symbol("drawGeneration"); | |
| 458 this._drawGeneration = drawGeneration; | |
| 459 this._imageByFrame(frames[0]).then(image => { | |
| 460 if (this._drawGeneration !== drawGeneration) | |
| 461 return; | |
| 462 if (!image.naturalWidth || !image.naturalHeight) | |
| 463 return; | |
| 464 var imageHeight = this._canvas.height - 2 * WebInspector.TimelineFil
mStripOverview.Padding; | |
| 465 var imageWidth = Math.ceil(imageHeight * image.naturalWidth / image.
naturalHeight); | |
| 466 var popoverScale = Math.min(200 / image.naturalWidth, 1); | |
| 467 this._emptyImage = new Image(image.naturalWidth * popoverScale, imag
e.naturalHeight * popoverScale); | |
| 468 this._drawFrames(imageWidth, imageHeight); | |
| 469 }); | |
| 470 }, | |
| 471 | |
| 472 /** | |
| 473 * @param {!WebInspector.FilmStripModel.Frame} frame | |
| 474 * @return {!Promise<!HTMLImageElement>} | |
| 475 */ | |
| 476 _imageByFrame: function(frame) | |
| 477 { | |
| 478 var imagePromise = this._frameToImagePromise.get(frame); | |
| 479 if (!imagePromise) { | |
| 480 imagePromise = frame.imageDataPromise().then(createImage); | |
| 481 this._frameToImagePromise.set(frame, imagePromise); | |
| 482 } | |
| 483 return imagePromise; | |
| 484 | |
| 485 /** | |
| 486 * @param {?string} data | |
| 487 * @return {!Promise<!HTMLImageElement>} | |
| 488 */ | |
| 489 function createImage(data) | |
| 490 { | |
| 491 var fulfill; | |
| 492 var promise = new Promise(f => fulfill = f); | |
| 493 | |
| 494 var image = /** @type {!HTMLImageElement} */ (createElement("img")); | |
| 495 if (data) | |
| 496 image.src = "data:image/jpg;base64," + data; | |
| 497 if (image.complete) { | |
| 498 fulfill(image); | |
| 499 } else { | |
| 500 image.addEventListener("load", () => fulfill(image)); | |
| 501 image.addEventListener("error", () => fulfill(image)); | |
| 502 } | |
| 503 return promise; | |
| 504 } | |
| 505 }, | |
| 506 | |
| 507 /** | |
| 508 * @param {number} imageWidth | |
| 509 * @param {number} imageHeight | |
| 510 */ | |
| 511 _drawFrames: function(imageWidth, imageHeight) | |
| 512 { | |
| 513 if (!imageWidth) | |
| 514 return; | |
| 515 if (!this._filmStripModel.frames().length) | |
| 516 return; | |
| 517 var padding = WebInspector.TimelineFilmStripOverview.Padding; | |
| 518 var width = this._canvas.width; | |
| 519 var zeroTime = this._filmStripModel.zeroTime(); | |
| 520 var spanTime = this._filmStripModel.spanTime(); | |
| 521 var scale = spanTime / width; | |
| 522 var context = this._canvas.getContext("2d"); | |
| 523 var drawGeneration = this._drawGeneration; | |
| 524 | |
| 525 context.beginPath(); | |
| 526 for (var x = padding; x < width; x += imageWidth + 2 * padding) { | |
| 527 var time = zeroTime + (x + imageWidth / 2) * scale; | |
| 528 var frame = this._filmStripModel.frameByTimestamp(time); | |
| 529 if (!frame) | |
| 530 continue; | |
| 531 context.rect(x - 0.5, 0.5, imageWidth + 1, imageHeight + 1); | |
| 532 this._imageByFrame(frame).then(drawFrameImage.bind(this, x)); | |
| 533 } | |
| 534 context.strokeStyle = "#ddd"; | |
| 535 context.stroke(); | |
| 536 | |
| 537 /** | |
| 538 * @param {number} x | |
| 539 * @param {!HTMLImageElement} image | |
| 540 * @this {WebInspector.TimelineFilmStripOverview} | |
| 541 */ | |
| 542 function drawFrameImage(x, image) | |
| 543 { | |
| 544 // Ignore draws deferred from a previous update call. | |
| 545 if (this._drawGeneration !== drawGeneration) | |
| 546 return; | |
| 547 context.drawImage(image, x, 1, imageWidth, imageHeight); | |
| 548 } | |
| 549 }, | |
| 550 | |
| 551 /** | |
| 552 * @override | |
| 553 * @param {number} x | |
| 554 * @return {!Promise<?Element>} | |
| 555 */ | |
| 556 popoverElementPromise: function(x) | |
| 557 { | |
| 558 if (!this._filmStripModel.frames().length) | |
| 559 return Promise.resolve(/** @type {?Element} */ (null)); | |
| 560 | |
| 561 var time = this._calculator.positionToTime(x); | |
| 562 var frame = this._filmStripModel.frameByTimestamp(time); | |
| 563 if (frame === this._lastFrame) | |
| 564 return Promise.resolve(this._lastElement); | |
| 565 var imagePromise = frame ? this._imageByFrame(frame) : Promise.resolve(t
his._emptyImage); | |
| 566 return imagePromise.then(createFrameElement.bind(this)); | |
| 567 | |
| 568 /** | |
| 569 * @this {WebInspector.TimelineFilmStripOverview} | |
| 570 * @param {!HTMLImageElement} image | |
| 571 * @return {?Element} | |
| 572 */ | |
| 573 function createFrameElement(image) | |
| 574 { | |
| 575 var element = createElementWithClass("div", "frame"); | |
| 576 element.createChild("div", "thumbnail").appendChild(image); | |
| 577 WebInspector.appendStyle(element, "timeline/timelinePanel.css"); | |
| 578 this._lastFrame = frame; | |
| 579 this._lastElement = element; | |
| 580 return element; | |
| 581 } | |
| 582 }, | |
| 583 | |
| 584 /** | |
| 585 * @override | |
| 586 */ | |
| 587 reset: function() | |
| 588 { | |
| 589 this._lastFrame = undefined; | |
| 590 this._lastElement = null; | |
| 591 /** @type {!Map<!WebInspector.FilmStripModel.Frame,!Promise<!HTMLImageEl
ement>>} */ | |
| 592 this._frameToImagePromise = new Map(); | |
| 593 this._imageWidth = 0; | |
| 594 }, | |
| 595 | |
| 596 __proto__: WebInspector.TimelineEventOverview.prototype | |
| 597 }; | |
| 598 | |
| 599 /** | |
| 600 * @constructor | |
| 601 * @extends {WebInspector.TimelineEventOverview} | |
| 602 * @param {!WebInspector.TimelineModel} model | |
| 603 * @param {!WebInspector.TimelineFrameModel} frameModel | |
| 604 */ | |
| 605 WebInspector.TimelineEventOverview.Frames = function(model, frameModel) | |
| 606 { | |
| 607 WebInspector.TimelineEventOverview.call(this, "framerate", WebInspector.UISt
ring("FPS"), model); | |
| 608 this._frameModel = frameModel; | 574 this._frameModel = frameModel; |
| 609 }; | 575 } |
| 610 | 576 |
| 611 WebInspector.TimelineEventOverview.Frames.prototype = { | 577 /** |
| 612 /** | 578 * @override |
| 613 * @override | 579 */ |
| 614 */ | 580 update() { |
| 615 update: function() | 581 super.update(); |
| 616 { | 582 var height = this._canvas.height; |
| 617 WebInspector.TimelineEventOverview.prototype.update.call(this); | 583 var /** @const */ padding = 1 * window.devicePixelRatio; |
| 618 var height = this._canvas.height; | 584 var /** @const */ baseFrameDurationMs = 1e3 / 60; |
| 619 var /** @const */ padding = 1 * window.devicePixelRatio; | 585 var visualHeight = height - 2 * padding; |
| 620 var /** @const */ baseFrameDurationMs = 1e3 / 60; | 586 var timeOffset = this._model.minimumRecordTime(); |
| 621 var visualHeight = height - 2 * padding; | 587 var timeSpan = this._model.maximumRecordTime() - timeOffset; |
| 622 var timeOffset = this._model.minimumRecordTime(); | 588 var scale = this._canvas.width / timeSpan; |
| 623 var timeSpan = this._model.maximumRecordTime() - timeOffset; | 589 var frames = this._frameModel.frames(); |
| 624 var scale = this._canvas.width / timeSpan; | 590 var baseY = height - padding; |
| 625 var frames = this._frameModel.frames(); | 591 var ctx = this._context; |
| 626 var baseY = height - padding; | 592 var bottomY = baseY + 10 * window.devicePixelRatio; |
| 627 var ctx = this._context; | 593 var y = bottomY; |
| 628 var bottomY = baseY + 10 * window.devicePixelRatio; | 594 if (!frames.length) |
| 629 var y = bottomY; | 595 return; |
| 630 if (!frames.length) | 596 |
| 631 return; | 597 var lineWidth = window.devicePixelRatio; |
| 632 | 598 var offset = lineWidth & 1 ? 0.5 : 0; |
| 633 var lineWidth = window.devicePixelRatio; | 599 var tickDepth = 1.5 * window.devicePixelRatio; |
| 634 var offset = lineWidth & 1 ? 0.5 : 0; | 600 ctx.beginPath(); |
| 635 var tickDepth = 1.5 * window.devicePixelRatio; | 601 ctx.moveTo(0, y); |
| 636 ctx.beginPath(); | 602 for (var i = 0; i < frames.length; ++i) { |
| 637 ctx.moveTo(0, y); | 603 var frame = frames[i]; |
| 638 for (var i = 0; i < frames.length; ++i) { | 604 var x = Math.round((frame.startTime - timeOffset) * scale) + offset; |
| 639 var frame = frames[i]; | 605 ctx.lineTo(x, y); |
| 640 var x = Math.round((frame.startTime - timeOffset) * scale) + offset; | 606 ctx.lineTo(x, y + tickDepth); |
| 641 ctx.lineTo(x, y); | 607 y = frame.idle ? bottomY : |
| 642 ctx.lineTo(x, y + tickDepth); | 608 Math.round(baseY - visualHeight * Math.min(baseFrameDurat
ionMs / frame.duration, 1)) - offset; |
| 643 y = frame.idle ? bottomY : Math.round(baseY - visualHeight * Math.mi
n(baseFrameDurationMs / frame.duration, 1)) - offset; | 609 ctx.lineTo(x, y + tickDepth); |
| 644 ctx.lineTo(x, y + tickDepth); | 610 ctx.lineTo(x, y); |
| 645 ctx.lineTo(x, y); | 611 } |
| 646 } | 612 if (frames.length) { |
| 647 if (frames.length) { | 613 var lastFrame = frames.peekLast(); |
| 648 var lastFrame = frames.peekLast(); | 614 var x = Math.round((lastFrame.startTime + lastFrame.duration - timeOffset)
* scale) + offset; |
| 649 var x = Math.round((lastFrame.startTime + lastFrame.duration - timeO
ffset) * scale) + offset; | 615 ctx.lineTo(x, y); |
| 650 ctx.lineTo(x, y); | 616 } |
| 651 } | 617 ctx.lineTo(x, bottomY); |
| 652 ctx.lineTo(x, bottomY); | 618 ctx.fillStyle = 'hsl(110, 50%, 88%)'; |
| 653 ctx.fillStyle = "hsl(110, 50%, 88%)"; | 619 ctx.strokeStyle = 'hsl(110, 50%, 60%)'; |
| 654 ctx.strokeStyle = "hsl(110, 50%, 60%)"; | 620 ctx.lineWidth = lineWidth; |
| 655 ctx.lineWidth = lineWidth; | 621 ctx.fill(); |
| 656 ctx.fill(); | 622 ctx.stroke(); |
| 657 ctx.stroke(); | 623 } |
| 658 }, | 624 }; |
| 659 | 625 |
| 660 __proto__: WebInspector.TimelineEventOverview.prototype | 626 /** |
| 661 }; | 627 * @unrestricted |
| 662 | 628 */ |
| 663 /** | 629 WebInspector.TimelineEventOverviewMemory = class extends WebInspector.TimelineEv
entOverview { |
| 664 * @constructor | 630 /** |
| 665 * @extends {WebInspector.TimelineEventOverview} | 631 * @param {!WebInspector.TimelineModel} model |
| 666 * @param {!WebInspector.TimelineModel} model | 632 */ |
| 667 */ | 633 constructor(model) { |
| 668 WebInspector.TimelineEventOverview.Memory = function(model) | 634 super('memory', WebInspector.UIString('HEAP'), model); |
| 669 { | 635 this._heapSizeLabel = this.element.createChild('div', 'memory-graph-label'); |
| 670 WebInspector.TimelineEventOverview.call(this, "memory", WebInspector.UIStrin
g("HEAP"), model); | 636 } |
| 671 this._heapSizeLabel = this.element.createChild("div", "memory-graph-label"); | 637 |
| 672 }; | 638 resetHeapSizeLabels() { |
| 673 | 639 this._heapSizeLabel.textContent = ''; |
| 674 WebInspector.TimelineEventOverview.Memory.prototype = { | 640 } |
| 675 resetHeapSizeLabels: function() | 641 |
| 676 { | 642 /** |
| 677 this._heapSizeLabel.textContent = ""; | 643 * @override |
| 678 }, | 644 */ |
| 679 | 645 update() { |
| 680 /** | 646 super.update(); |
| 681 * @override | 647 var ratio = window.devicePixelRatio; |
| 682 */ | 648 |
| 683 update: function() | 649 var events = this._model.mainThreadEvents(); |
| 684 { | 650 if (!events.length) { |
| 685 WebInspector.TimelineEventOverview.prototype.update.call(this); | 651 this.resetHeapSizeLabels(); |
| 686 var ratio = window.devicePixelRatio; | 652 return; |
| 687 | 653 } |
| 688 var events = this._model.mainThreadEvents(); | 654 |
| 689 if (!events.length) { | 655 var lowerOffset = 3 * ratio; |
| 690 this.resetHeapSizeLabels(); | 656 var maxUsedHeapSize = 0; |
| 691 return; | 657 var minUsedHeapSize = 100000000000; |
| 692 } | 658 var minTime = this._model.minimumRecordTime(); |
| 693 | 659 var maxTime = this._model.maximumRecordTime(); |
| 694 var lowerOffset = 3 * ratio; | 660 /** |
| 695 var maxUsedHeapSize = 0; | 661 * @param {!WebInspector.TracingModel.Event} event |
| 696 var minUsedHeapSize = 100000000000; | 662 * @return {boolean} |
| 697 var minTime = this._model.minimumRecordTime(); | 663 */ |
| 698 var maxTime = this._model.maximumRecordTime(); | 664 function isUpdateCountersEvent(event) { |
| 699 /** | 665 return event.name === WebInspector.TimelineModel.RecordType.UpdateCounters
; |
| 700 * @param {!WebInspector.TracingModel.Event} event | 666 } |
| 701 * @return {boolean} | 667 events = events.filter(isUpdateCountersEvent); |
| 702 */ | 668 /** |
| 703 function isUpdateCountersEvent(event) | 669 * @param {!WebInspector.TracingModel.Event} event |
| 704 { | 670 */ |
| 705 return event.name === WebInspector.TimelineModel.RecordType.UpdateCo
unters; | 671 function calculateMinMaxSizes(event) { |
| 706 } | 672 var counters = event.args.data; |
| 707 events = events.filter(isUpdateCountersEvent); | 673 if (!counters || !counters.jsHeapSizeUsed) |
| 708 /** | 674 return; |
| 709 * @param {!WebInspector.TracingModel.Event} event | 675 maxUsedHeapSize = Math.max(maxUsedHeapSize, counters.jsHeapSizeUsed); |
| 710 */ | 676 minUsedHeapSize = Math.min(minUsedHeapSize, counters.jsHeapSizeUsed); |
| 711 function calculateMinMaxSizes(event) | 677 } |
| 712 { | 678 events.forEach(calculateMinMaxSizes); |
| 713 var counters = event.args.data; | 679 minUsedHeapSize = Math.min(minUsedHeapSize, maxUsedHeapSize); |
| 714 if (!counters || !counters.jsHeapSizeUsed) | 680 |
| 715 return; | 681 var lineWidth = 1; |
| 716 maxUsedHeapSize = Math.max(maxUsedHeapSize, counters.jsHeapSizeUsed)
; | 682 var width = this._canvas.width; |
| 717 minUsedHeapSize = Math.min(minUsedHeapSize, counters.jsHeapSizeUsed)
; | 683 var height = this._canvas.height - lowerOffset; |
| 718 } | 684 var xFactor = width / (maxTime - minTime); |
| 719 events.forEach(calculateMinMaxSizes); | 685 var yFactor = (height - lineWidth) / Math.max(maxUsedHeapSize - minUsedHeapS
ize, 1); |
| 720 minUsedHeapSize = Math.min(minUsedHeapSize, maxUsedHeapSize); | 686 |
| 721 | 687 var histogram = new Array(width); |
| 722 var lineWidth = 1; | 688 |
| 723 var width = this._canvas.width; | 689 /** |
| 724 var height = this._canvas.height - lowerOffset; | 690 * @param {!WebInspector.TracingModel.Event} event |
| 725 var xFactor = width / (maxTime - minTime); | 691 */ |
| 726 var yFactor = (height - lineWidth) / Math.max(maxUsedHeapSize - minUsedH
eapSize, 1); | 692 function buildHistogram(event) { |
| 727 | 693 var counters = event.args.data; |
| 728 var histogram = new Array(width); | 694 if (!counters || !counters.jsHeapSizeUsed) |
| 729 | 695 return; |
| 730 /** | 696 var x = Math.round((event.startTime - minTime) * xFactor); |
| 731 * @param {!WebInspector.TracingModel.Event} event | 697 var y = Math.round((counters.jsHeapSizeUsed - minUsedHeapSize) * yFactor); |
| 732 */ | 698 histogram[x] = Math.max(histogram[x] || 0, y); |
| 733 function buildHistogram(event) | 699 } |
| 734 { | 700 events.forEach(buildHistogram); |
| 735 var counters = event.args.data; | 701 |
| 736 if (!counters || !counters.jsHeapSizeUsed) | 702 var ctx = this._context; |
| 737 return; | 703 var heightBeyondView = height + lowerOffset + lineWidth; |
| 738 var x = Math.round((event.startTime - minTime) * xFactor); | 704 |
| 739 var y = Math.round((counters.jsHeapSizeUsed - minUsedHeapSize) * yFa
ctor); | 705 ctx.translate(0.5, 0.5); |
| 740 histogram[x] = Math.max(histogram[x] || 0, y); | 706 ctx.beginPath(); |
| 741 } | 707 ctx.moveTo(-lineWidth, heightBeyondView); |
| 742 events.forEach(buildHistogram); | 708 var y = 0; |
| 743 | 709 var isFirstPoint = true; |
| 744 var ctx = this._context; | 710 var lastX = 0; |
| 745 var heightBeyondView = height + lowerOffset + lineWidth; | 711 for (var x = 0; x < histogram.length; x++) { |
| 746 | 712 if (typeof histogram[x] === 'undefined') |
| 747 ctx.translate(0.5, 0.5); | 713 continue; |
| 748 ctx.beginPath(); | 714 if (isFirstPoint) { |
| 749 ctx.moveTo(-lineWidth, heightBeyondView); | 715 isFirstPoint = false; |
| 750 var y = 0; | 716 y = histogram[x]; |
| 751 var isFirstPoint = true; | 717 ctx.lineTo(-lineWidth, height - y); |
| 752 var lastX = 0; | 718 } |
| 753 for (var x = 0; x < histogram.length; x++) { | 719 var nextY = histogram[x]; |
| 754 if (typeof histogram[x] === "undefined") | 720 if (Math.abs(nextY - y) > 2 && Math.abs(x - lastX) > 1) |
| 755 continue; | 721 ctx.lineTo(x, height - y); |
| 756 if (isFirstPoint) { | 722 y = nextY; |
| 757 isFirstPoint = false; | 723 ctx.lineTo(x, height - y); |
| 758 y = histogram[x]; | 724 lastX = x; |
| 759 ctx.lineTo(-lineWidth, height - y); | 725 } |
| 760 } | 726 ctx.lineTo(width + lineWidth, height - y); |
| 761 var nextY = histogram[x]; | 727 ctx.lineTo(width + lineWidth, heightBeyondView); |
| 762 if (Math.abs(nextY - y) > 2 && Math.abs(x - lastX) > 1) | 728 ctx.closePath(); |
| 763 ctx.lineTo(x, height - y); | 729 |
| 764 y = nextY; | 730 ctx.fillStyle = 'hsla(220, 90%, 70%, 0.2)'; |
| 765 ctx.lineTo(x, height - y); | 731 ctx.fill(); |
| 766 lastX = x; | 732 ctx.lineWidth = lineWidth; |
| 767 } | 733 ctx.strokeStyle = 'hsl(220, 90%, 70%)'; |
| 768 ctx.lineTo(width + lineWidth, height - y); | 734 ctx.stroke(); |
| 769 ctx.lineTo(width + lineWidth, heightBeyondView); | 735 |
| 770 ctx.closePath(); | 736 this._heapSizeLabel.textContent = WebInspector.UIString( |
| 771 | 737 '%s \u2013 %s', Number.bytesToString(minUsedHeapSize), Number.bytesToStr
ing(maxUsedHeapSize)); |
| 772 ctx.fillStyle = "hsla(220, 90%, 70%, 0.2)"; | 738 } |
| 773 ctx.fill(); | 739 }; |
| 774 ctx.lineWidth = lineWidth; | 740 |
| 775 ctx.strokeStyle = "hsl(220, 90%, 70%)"; | 741 /** |
| 776 ctx.stroke(); | 742 * @unrestricted |
| 777 | 743 */ |
| 778 this._heapSizeLabel.textContent = WebInspector.UIString("%s \u2013 %s",
Number.bytesToString(minUsedHeapSize), Number.bytesToString(maxUsedHeapSize)); | 744 WebInspector.Quantizer = class { |
| 779 }, | 745 /** |
| 780 | 746 * @param {number} startTime |
| 781 __proto__: WebInspector.TimelineEventOverview.prototype | 747 * @param {number} quantDuration |
| 782 }; | 748 * @param {function(!Array<number>)} callback |
| 783 | 749 */ |
| 784 /** | 750 constructor(startTime, quantDuration, callback) { |
| 785 * @constructor | |
| 786 * @param {number} startTime | |
| 787 * @param {number} quantDuration | |
| 788 * @param {function(!Array<number>)} callback | |
| 789 */ | |
| 790 WebInspector.Quantizer = function(startTime, quantDuration, callback) | |
| 791 { | |
| 792 this._lastTime = startTime; | 751 this._lastTime = startTime; |
| 793 this._quantDuration = quantDuration; | 752 this._quantDuration = quantDuration; |
| 794 this._callback = callback; | 753 this._callback = callback; |
| 795 this._counters = []; | 754 this._counters = []; |
| 796 this._remainder = quantDuration; | 755 this._remainder = quantDuration; |
| 797 }; | 756 } |
| 798 | 757 |
| 799 WebInspector.Quantizer.prototype = { | 758 /** |
| 800 /** | 759 * @param {number} time |
| 801 * @param {number} time | 760 * @param {number} group |
| 802 * @param {number} group | 761 */ |
| 803 */ | 762 appendInterval(time, group) { |
| 804 appendInterval: function(time, group) | 763 var interval = time - this._lastTime; |
| 805 { | 764 if (interval <= this._remainder) { |
| 806 var interval = time - this._lastTime; | 765 this._counters[group] = (this._counters[group] || 0) + interval; |
| 807 if (interval <= this._remainder) { | 766 this._remainder -= interval; |
| 808 this._counters[group] = (this._counters[group] || 0) + interval; | 767 this._lastTime = time; |
| 809 this._remainder -= interval; | 768 return; |
| 810 this._lastTime = time; | 769 } |
| 811 return; | 770 this._counters[group] = (this._counters[group] || 0) + this._remainder; |
| 812 } | 771 this._callback(this._counters); |
| 813 this._counters[group] = (this._counters[group] || 0) + this._remainder; | 772 interval -= this._remainder; |
| 814 this._callback(this._counters); | 773 while (interval >= this._quantDuration) { |
| 815 interval -= this._remainder; | 774 var counters = []; |
| 816 while (interval >= this._quantDuration) { | 775 counters[group] = this._quantDuration; |
| 817 var counters = []; | 776 this._callback(counters); |
| 818 counters[group] = this._quantDuration; | 777 interval -= this._quantDuration; |
| 819 this._callback(counters); | 778 } |
| 820 interval -= this._quantDuration; | 779 this._counters = []; |
| 821 } | 780 this._counters[group] = interval; |
| 822 this._counters = []; | 781 this._lastTime = time; |
| 823 this._counters[group] = interval; | 782 this._remainder = this._quantDuration - interval; |
| 824 this._lastTime = time; | 783 } |
| 825 this._remainder = this._quantDuration - interval; | 784 }; |
| 826 } | |
| 827 }; | |
| OLD | NEW |