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 |