OLD | NEW |
| (Empty) |
1 /* | |
2 * Copyright (C) 2013 Google Inc. All rights reserved. | |
3 * | |
4 * Redistribution and use in source and binary forms, with or without | |
5 * modification, are permitted provided that the following conditions are | |
6 * met: | |
7 * | |
8 * * Redistributions of source code must retain the above copyright | |
9 * notice, this list of conditions and the following disclaimer. | |
10 * * Redistributions in binary form must reproduce the above | |
11 * copyright notice, this list of conditions and the following disclaimer | |
12 * in the documentation and/or other materials provided with the | |
13 * distribution. | |
14 * * Neither the name of Google Inc. nor the names of its | |
15 * contributors may be used to endorse or promote products derived from | |
16 * this software without specific prior written permission. | |
17 * | |
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
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. | |
29 */ | |
30 | |
31 /** | |
32 * @constructor | |
33 * @param {function(string=)} showImageCallback | |
34 * @extends {WebInspector.HBox} | |
35 */ | |
36 WebInspector.PaintProfilerView = function(showImageCallback) | |
37 { | |
38 WebInspector.HBox.call(this); | |
39 this.element.classList.add("paint-profiler-overview", "hbox"); | |
40 this._canvasContainer = this.element.createChild("div", "paint-profiler-canv
as-container"); | |
41 this._progressBanner = this.element.createChild("div", "banner hidden"); | |
42 this._progressBanner.textContent = WebInspector.UIString("Profiling\u2026"); | |
43 this._pieChart = new WebInspector.PieChart(55, this._formatPieChartTime.bind
(this), true); | |
44 this._pieChart.element.classList.add("paint-profiler-pie-chart"); | |
45 this.element.appendChild(this._pieChart.element); | |
46 | |
47 this._showImageCallback = showImageCallback; | |
48 | |
49 this._canvas = this._canvasContainer.createChild("canvas", "fill"); | |
50 this._context = this._canvas.getContext("2d"); | |
51 this._selectionWindow = new WebInspector.OverviewGrid.Window(this._canvasCon
tainer); | |
52 this._selectionWindow.addEventListener(WebInspector.OverviewGrid.Events.Wind
owChanged, this._onWindowChanged, this); | |
53 | |
54 this._innerBarWidth = 4 * window.devicePixelRatio; | |
55 this._minBarHeight = window.devicePixelRatio; | |
56 this._barPaddingWidth = 2 * window.devicePixelRatio; | |
57 this._outerBarWidth = this._innerBarWidth + this._barPaddingWidth; | |
58 | |
59 this._reset(); | |
60 } | |
61 | |
62 /** @enum {symbol} */ | |
63 WebInspector.PaintProfilerView.Events = { | |
64 WindowChanged: Symbol("WindowChanged") | |
65 }; | |
66 | |
67 WebInspector.PaintProfilerView.prototype = { | |
68 onResize: function() | |
69 { | |
70 this._update(); | |
71 }, | |
72 | |
73 /** | |
74 * @param {?WebInspector.PaintProfilerSnapshot} snapshot | |
75 * @param {!Array.<!WebInspector.PaintProfilerLogItem>} log | |
76 * @param {?DOMAgent.Rect} clipRect | |
77 */ | |
78 setSnapshotAndLog: function(snapshot, log, clipRect) | |
79 { | |
80 this._reset(); | |
81 this._snapshot = snapshot; | |
82 this._log = log; | |
83 this._logCategories = this._log.map(WebInspector.PaintProfilerView._cate
goryForLogItem); | |
84 | |
85 if (!this._snapshot) { | |
86 this._update(); | |
87 this._pieChart.setTotal(0); | |
88 this._selectionWindow.setEnabled(false); | |
89 return; | |
90 } | |
91 this._selectionWindow.setEnabled(true); | |
92 this._progressBanner.classList.remove("hidden"); | |
93 snapshot.requestImage(null, null, 1, this._showImageCallback); | |
94 snapshot.profile(clipRect, onProfileDone.bind(this)); | |
95 /** | |
96 * @param {!Array.<!LayerTreeAgent.PaintProfile>=} profiles | |
97 * @this {WebInspector.PaintProfilerView} | |
98 */ | |
99 function onProfileDone(profiles) | |
100 { | |
101 this._progressBanner.classList.add("hidden"); | |
102 this._profiles = profiles; | |
103 this._update(); | |
104 this._updatePieChart(); | |
105 } | |
106 }, | |
107 | |
108 _update: function() | |
109 { | |
110 this._canvas.width = this._canvasContainer.clientWidth * window.devicePi
xelRatio; | |
111 this._canvas.height = this._canvasContainer.clientHeight * window.device
PixelRatio; | |
112 this._samplesPerBar = 0; | |
113 if (!this._profiles || !this._profiles.length) | |
114 return; | |
115 | |
116 var maxBars = Math.floor((this._canvas.width - 2 * this._barPaddingWidth
) / this._outerBarWidth); | |
117 var sampleCount = this._log.length; | |
118 this._samplesPerBar = Math.ceil(sampleCount / maxBars); | |
119 | |
120 var maxBarTime = 0; | |
121 var barTimes = []; | |
122 var barHeightByCategory = []; | |
123 var heightByCategory = {}; | |
124 for (var i = 0, lastBarIndex = 0, lastBarTime = 0; i < sampleCount;) { | |
125 var categoryName = (this._logCategories[i] && this._logCategories[i]
.name) || "misc"; | |
126 var sampleIndex = this._log[i].commandIndex; | |
127 for (var row = 0; row < this._profiles.length; row++) { | |
128 var sample = this._profiles[row][sampleIndex]; | |
129 lastBarTime += sample; | |
130 heightByCategory[categoryName] = (heightByCategory[categoryName]
|| 0) + sample; | |
131 } | |
132 ++i; | |
133 if (i - lastBarIndex === this._samplesPerBar || i === sampleCount) { | |
134 // Normalize by total number of samples accumulated. | |
135 var factor = this._profiles.length * (i - lastBarIndex); | |
136 lastBarTime /= factor; | |
137 for (categoryName in heightByCategory) | |
138 heightByCategory[categoryName] /= factor; | |
139 | |
140 barTimes.push(lastBarTime); | |
141 barHeightByCategory.push(heightByCategory); | |
142 | |
143 if (lastBarTime > maxBarTime) | |
144 maxBarTime = lastBarTime; | |
145 lastBarTime = 0; | |
146 heightByCategory = {}; | |
147 lastBarIndex = i; | |
148 } | |
149 } | |
150 | |
151 const paddingHeight = 4 * window.devicePixelRatio; | |
152 var scale = (this._canvas.height - paddingHeight - this._minBarHeight) /
maxBarTime; | |
153 for (var i = 0; i < barTimes.length; ++i) { | |
154 for (var categoryName in barHeightByCategory[i]) | |
155 barHeightByCategory[i][categoryName] *= (barTimes[i] * scale + t
his._minBarHeight) / barTimes[i]; | |
156 this._renderBar(i, barHeightByCategory[i]); | |
157 } | |
158 }, | |
159 | |
160 /** | |
161 * @param {number} index | |
162 * @param {!Object.<string, number>} heightByCategory | |
163 */ | |
164 _renderBar: function(index, heightByCategory) | |
165 { | |
166 var categories = WebInspector.PaintProfilerView.categories(); | |
167 var currentHeight = 0; | |
168 var x = this._barPaddingWidth + index * this._outerBarWidth; | |
169 for (var categoryName in categories) { | |
170 if (!heightByCategory[categoryName]) | |
171 continue; | |
172 currentHeight += heightByCategory[categoryName]; | |
173 var y = this._canvas.height - currentHeight; | |
174 this._context.fillStyle = categories[categoryName].color; | |
175 this._context.fillRect(x, y, this._innerBarWidth, heightByCategory[c
ategoryName]); | |
176 } | |
177 }, | |
178 | |
179 _onWindowChanged: function() | |
180 { | |
181 this.dispatchEventToListeners(WebInspector.PaintProfilerView.Events.Wind
owChanged); | |
182 this._updatePieChart(); | |
183 if (this._updateImageTimer) | |
184 return; | |
185 this._updateImageTimer = setTimeout(this._updateImage.bind(this), 100); | |
186 }, | |
187 | |
188 _updatePieChart: function() | |
189 { | |
190 if (!this._profiles || !this._profiles.length) | |
191 return; | |
192 var window = this.windowBoundaries(); | |
193 var totalTime = 0; | |
194 var timeByCategory = {}; | |
195 for (var i = window.left; i < window.right; ++i) { | |
196 var logEntry = this._log[i]; | |
197 var category = WebInspector.PaintProfilerView._categoryForLogItem(lo
gEntry); | |
198 timeByCategory[category.color] = timeByCategory[category.color] || 0
; | |
199 for (var j = 0; j < this._profiles.length; ++j) { | |
200 var time = this._profiles[j][logEntry.commandIndex]; | |
201 totalTime += time; | |
202 timeByCategory[category.color] += time; | |
203 } | |
204 } | |
205 this._pieChart.setTotal(totalTime / this._profiles.length); | |
206 for (var color in timeByCategory) | |
207 this._pieChart.addSlice(timeByCategory[color] / this._profiles.lengt
h, color); | |
208 }, | |
209 | |
210 /** | |
211 * @param {number} value | |
212 * @return {string} | |
213 */ | |
214 _formatPieChartTime: function(value) | |
215 { | |
216 return Number.millisToString(value * 1000, true); | |
217 }, | |
218 | |
219 /** | |
220 * @return {{left: number, right: number}} | |
221 */ | |
222 windowBoundaries: function() | |
223 { | |
224 var screenLeft = this._selectionWindow.windowLeft * this._canvas.width; | |
225 var screenRight = this._selectionWindow.windowRight * this._canvas.width
; | |
226 var barLeft = Math.floor(screenLeft / this._outerBarWidth); | |
227 var barRight = Math.floor((screenRight + this._innerBarWidth - this._bar
PaddingWidth / 2) / this._outerBarWidth); | |
228 var stepLeft = Number.constrain(barLeft * this._samplesPerBar, 0, this._
log.length - 1); | |
229 var stepRight = Number.constrain(barRight * this._samplesPerBar, 0, this
._log.length); | |
230 | |
231 return { left: stepLeft, right: stepRight }; | |
232 }, | |
233 | |
234 _updateImage: function() | |
235 { | |
236 delete this._updateImageTimer; | |
237 if (!this._profiles || !this._profiles.length) | |
238 return; | |
239 | |
240 var window = this.windowBoundaries(); | |
241 this._snapshot.requestImage(this._log[window.left].commandIndex, this._l
og[window.right - 1].commandIndex, 1, this._showImageCallback); | |
242 }, | |
243 | |
244 _reset: function() | |
245 { | |
246 this._snapshot = null; | |
247 this._profiles = null; | |
248 this._selectionWindow.reset(); | |
249 }, | |
250 | |
251 __proto__: WebInspector.HBox.prototype | |
252 }; | |
253 | |
254 /** | |
255 * @constructor | |
256 * @extends {WebInspector.VBox} | |
257 */ | |
258 WebInspector.PaintProfilerCommandLogView = function() | |
259 { | |
260 WebInspector.VBox.call(this); | |
261 this.setMinimumSize(100, 25); | |
262 this.element.classList.add("profiler-log-view"); | |
263 | |
264 this._treeOutline = new TreeOutlineInShadow(); | |
265 this.element.appendChild(this._treeOutline.element); | |
266 | |
267 this._reset(); | |
268 } | |
269 | |
270 WebInspector.PaintProfilerCommandLogView.prototype = { | |
271 /** | |
272 * @param {?WebInspector.Target} target | |
273 * @param {!Array.<!WebInspector.PaintProfilerLogItem>} log | |
274 */ | |
275 setCommandLog: function(target, log) | |
276 { | |
277 this._target = target; | |
278 this._log = log; | |
279 this.updateWindow(); | |
280 }, | |
281 | |
282 /** | |
283 * @param {!TreeOutline} treeOutline | |
284 * @param {!WebInspector.PaintProfilerLogItem} logItem | |
285 */ | |
286 _appendLogItem: function(treeOutline, logItem) | |
287 { | |
288 var treeElement = new WebInspector.LogTreeElement(this, logItem); | |
289 treeOutline.appendChild(treeElement); | |
290 }, | |
291 | |
292 /** | |
293 * @param {number=} stepLeft | |
294 * @param {number=} stepRight | |
295 */ | |
296 updateWindow: function(stepLeft, stepRight) | |
297 { | |
298 this._treeOutline.removeChildren(); | |
299 if (!this._log.length) | |
300 return; | |
301 stepLeft = stepLeft || 0; | |
302 stepRight = stepRight || this._log.length; | |
303 for (var i = stepLeft; i < stepRight; ++i) | |
304 this._appendLogItem(this._treeOutline, this._log[i]); | |
305 }, | |
306 | |
307 _reset: function() | |
308 { | |
309 this._log = []; | |
310 }, | |
311 | |
312 __proto__: WebInspector.VBox.prototype | |
313 }; | |
314 | |
315 /** | |
316 * @constructor | |
317 * @param {!WebInspector.PaintProfilerCommandLogView} ownerView | |
318 * @param {!WebInspector.PaintProfilerLogItem} logItem | |
319 * @extends {TreeElement} | |
320 */ | |
321 WebInspector.LogTreeElement = function(ownerView, logItem) | |
322 { | |
323 TreeElement.call(this, "", !!logItem.params); | |
324 this._logItem = logItem; | |
325 this._ownerView = ownerView; | |
326 this._filled = false; | |
327 } | |
328 | |
329 WebInspector.LogTreeElement.prototype = { | |
330 onattach: function() | |
331 { | |
332 this._update(); | |
333 }, | |
334 | |
335 onpopulate: function() | |
336 { | |
337 for (var param in this._logItem.params) | |
338 WebInspector.LogPropertyTreeElement._appendLogPropertyItem(this, par
am, this._logItem.params[param]); | |
339 }, | |
340 | |
341 /** | |
342 * @param {*} param | |
343 * @param {string} name | |
344 * @return {string} | |
345 */ | |
346 _paramToString: function(param, name) | |
347 { | |
348 if (typeof param !== "object") | |
349 return typeof param === "string" && param.length > 100 ? name : JSON
.stringify(param); | |
350 var str = ""; | |
351 var keyCount = 0; | |
352 for (var key in param) { | |
353 if (++keyCount > 4 || typeof param[key] === "object" || (typeof para
m[key] === "string" && param[key].length > 100)) | |
354 return name; | |
355 if (str) | |
356 str += ", "; | |
357 str += param[key]; | |
358 } | |
359 return str; | |
360 }, | |
361 | |
362 /** | |
363 * @param {?Object<string, *>} params | |
364 * @return {string} | |
365 */ | |
366 _paramsToString: function(params) | |
367 { | |
368 var str = ""; | |
369 for (var key in params) { | |
370 if (str) | |
371 str += ", "; | |
372 str += this._paramToString(params[key], key); | |
373 } | |
374 return str; | |
375 }, | |
376 | |
377 _update: function() | |
378 { | |
379 var title = createDocumentFragment(); | |
380 title.createTextChild(this._logItem.method + "(" + this._paramsToString(
this._logItem.params) + ")"); | |
381 this.title = title; | |
382 }, | |
383 | |
384 __proto__: TreeElement.prototype | |
385 }; | |
386 | |
387 /** | |
388 * @constructor | |
389 * @param {!{name: string, value}} property | |
390 * @extends {TreeElement} | |
391 */ | |
392 WebInspector.LogPropertyTreeElement = function(property) | |
393 { | |
394 TreeElement.call(this); | |
395 this._property = property; | |
396 }; | |
397 | |
398 /** | |
399 * @param {!TreeElement} element | |
400 * @param {string} name | |
401 * @param {*} value | |
402 */ | |
403 WebInspector.LogPropertyTreeElement._appendLogPropertyItem = function(element, n
ame, value) | |
404 { | |
405 var treeElement = new WebInspector.LogPropertyTreeElement({name: name, value
: value}); | |
406 element.appendChild(treeElement); | |
407 if (value && typeof value === "object") { | |
408 for (var property in value) | |
409 WebInspector.LogPropertyTreeElement._appendLogPropertyItem(treeEleme
nt, property, value[property]); | |
410 } | |
411 }; | |
412 | |
413 WebInspector.LogPropertyTreeElement.prototype = { | |
414 onattach: function() | |
415 { | |
416 var title = createDocumentFragment(); | |
417 var nameElement = title.createChild("span", "name"); | |
418 nameElement.textContent = this._property.name; | |
419 var separatorElement = title.createChild("span", "separator"); | |
420 separatorElement.textContent = ": "; | |
421 if (this._property.value === null || typeof this._property.value !== "ob
ject") { | |
422 var valueElement = title.createChild("span", "value"); | |
423 valueElement.textContent = JSON.stringify(this._property.value); | |
424 valueElement.classList.add("cm-js-" + (this._property.value === null
? "null" : typeof this._property.value)); | |
425 } | |
426 this.title = title; | |
427 }, | |
428 | |
429 __proto__: TreeElement.prototype | |
430 } | |
431 | |
432 /** | |
433 * @return {!Object.<string, !WebInspector.PaintProfilerCategory>} | |
434 */ | |
435 WebInspector.PaintProfilerView.categories = function() | |
436 { | |
437 if (WebInspector.PaintProfilerView._categories) | |
438 return WebInspector.PaintProfilerView._categories; | |
439 WebInspector.PaintProfilerView._categories = { | |
440 shapes: new WebInspector.PaintProfilerCategory("shapes", WebInspector.UI
String("Shapes"), "rgb(255, 161, 129)"), | |
441 bitmap: new WebInspector.PaintProfilerCategory("bitmap", WebInspector.UI
String("Bitmap"), "rgb(136, 196, 255)"), | |
442 text: new WebInspector.PaintProfilerCategory("text", WebInspector.UIStri
ng("Text"), "rgb(180, 255, 137)"), | |
443 misc: new WebInspector.PaintProfilerCategory("misc", WebInspector.UIStri
ng("Misc"), "rgb(206, 160, 255)") | |
444 }; | |
445 return WebInspector.PaintProfilerView._categories; | |
446 }; | |
447 | |
448 /** | |
449 * @constructor | |
450 * @param {string} name | |
451 * @param {string} title | |
452 * @param {string} color | |
453 */ | |
454 WebInspector.PaintProfilerCategory = function(name, title, color) | |
455 { | |
456 this.name = name; | |
457 this.title = title; | |
458 this.color = color; | |
459 } | |
460 | |
461 /** | |
462 * @return {!Object.<string, !WebInspector.PaintProfilerCategory>} | |
463 */ | |
464 WebInspector.PaintProfilerView._initLogItemCategories = function() | |
465 { | |
466 if (WebInspector.PaintProfilerView._logItemCategoriesMap) | |
467 return WebInspector.PaintProfilerView._logItemCategoriesMap; | |
468 | |
469 var categories = WebInspector.PaintProfilerView.categories(); | |
470 | |
471 var logItemCategories = {}; | |
472 logItemCategories["Clear"] = categories["misc"]; | |
473 logItemCategories["DrawPaint"] = categories["misc"]; | |
474 logItemCategories["DrawData"] = categories["misc"]; | |
475 logItemCategories["SetMatrix"] = categories["misc"]; | |
476 logItemCategories["PushCull"] = categories["misc"]; | |
477 logItemCategories["PopCull"] = categories["misc"]; | |
478 logItemCategories["Translate"] = categories["misc"]; | |
479 logItemCategories["Scale"] = categories["misc"]; | |
480 logItemCategories["Concat"] = categories["misc"]; | |
481 logItemCategories["Restore"] = categories["misc"]; | |
482 logItemCategories["SaveLayer"] = categories["misc"]; | |
483 logItemCategories["Save"] = categories["misc"]; | |
484 logItemCategories["BeginCommentGroup"] = categories["misc"]; | |
485 logItemCategories["AddComment"] = categories["misc"]; | |
486 logItemCategories["EndCommentGroup"] = categories["misc"]; | |
487 logItemCategories["ClipRect"] = categories["misc"]; | |
488 logItemCategories["ClipRRect"] = categories["misc"]; | |
489 logItemCategories["ClipPath"] = categories["misc"]; | |
490 logItemCategories["ClipRegion"] = categories["misc"]; | |
491 logItemCategories["DrawPoints"] = categories["shapes"]; | |
492 logItemCategories["DrawRect"] = categories["shapes"]; | |
493 logItemCategories["DrawOval"] = categories["shapes"]; | |
494 logItemCategories["DrawRRect"] = categories["shapes"]; | |
495 logItemCategories["DrawPath"] = categories["shapes"]; | |
496 logItemCategories["DrawVertices"] = categories["shapes"]; | |
497 logItemCategories["DrawDRRect"] = categories["shapes"]; | |
498 logItemCategories["DrawBitmap"] = categories["bitmap"]; | |
499 logItemCategories["DrawBitmapRectToRect"] = categories["bitmap"]; | |
500 logItemCategories["DrawBitmapMatrix"] = categories["bitmap"]; | |
501 logItemCategories["DrawBitmapNine"] = categories["bitmap"]; | |
502 logItemCategories["DrawSprite"] = categories["bitmap"]; | |
503 logItemCategories["DrawPicture"] = categories["bitmap"]; | |
504 logItemCategories["DrawText"] = categories["text"]; | |
505 logItemCategories["DrawPosText"] = categories["text"]; | |
506 logItemCategories["DrawPosTextH"] = categories["text"]; | |
507 logItemCategories["DrawTextOnPath"] = categories["text"]; | |
508 | |
509 WebInspector.PaintProfilerView._logItemCategoriesMap = logItemCategories; | |
510 return logItemCategories; | |
511 } | |
512 | |
513 /** | |
514 * @param {!Object} logItem | |
515 * @return {!WebInspector.PaintProfilerCategory} | |
516 */ | |
517 WebInspector.PaintProfilerView._categoryForLogItem = function(logItem) | |
518 { | |
519 var method = logItem.method.toTitleCase(); | |
520 | |
521 var logItemCategories = WebInspector.PaintProfilerView._initLogItemCategorie
s(); | |
522 var result = logItemCategories[method]; | |
523 if (!result) { | |
524 result = WebInspector.PaintProfilerView.categories()["misc"]; | |
525 logItemCategories[method] = result; | |
526 } | |
527 return result; | |
528 } | |
OLD | NEW |