Upstream version 6.35.121.0
[platform/framework/web/crosswalk.git] / src / third_party / trace-viewer / trace_viewer / cc / picture_ops_chart_summary_view.js
1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 'use strict';
6
7 tvcm.requireStylesheet('cc.picture_ops_chart_summary_view');
8
9 tvcm.exportTo('cc', function() {
10
11   var OPS_TIMING_ITERATIONS = 3;
12   var CHART_PADDING_LEFT = 65;
13   var CHART_PADDING_RIGHT = 40;
14   var AXIS_PADDING_LEFT = 60;
15   var AXIS_PADDING_RIGHT = 35;
16   var AXIS_PADDING_TOP = 25;
17   var AXIS_PADDING_BOTTOM = 45;
18   var AXIS_LABEL_PADDING = 5;
19   var AXIS_TICK_SIZE = 10;
20   var LABEL_PADDING = 5;
21   var LABEL_INTERLEAVE_OFFSET = 15;
22   var BAR_PADDING = 5;
23   var VERTICAL_TICKS = 5;
24   var HUE_CHAR_CODE_ADJUSTMENT = 5.7;
25
26   /**
27    * Provides a chart showing the cumulative time spent in Skia operations
28    * during picture rasterization.
29    *
30    * @constructor
31    */
32   var PictureOpsChartSummaryView = tvcm.ui.define(
33       'picture-ops-chart-summary-view');
34
35   PictureOpsChartSummaryView.prototype = {
36     __proto__: HTMLUnknownElement.prototype,
37
38     decorate: function() {
39       this.picture_ = undefined;
40       this.pictureDataProcessed_ = false;
41
42       this.chartScale_ = window.devicePixelRatio;
43
44       this.chart_ = document.createElement('canvas');
45       this.chartCtx_ = this.chart_.getContext('2d');
46       this.appendChild(this.chart_);
47
48       this.opsTimingData_ = [];
49
50       this.chartWidth_ = 0;
51       this.chartHeight_ = 0;
52       this.requiresRedraw_ = true;
53
54       this.currentBarMouseOverTarget_ = null;
55
56       this.chart_.addEventListener('mousemove', this.onMouseMove_.bind(this));
57     },
58
59     get requiresRedraw() {
60       return this.requiresRedraw_;
61     },
62
63     set requiresRedraw(requiresRedraw) {
64       this.requiresRedraw_ = requiresRedraw;
65     },
66
67     get picture() {
68       return this.picture_;
69     },
70
71     set picture(picture) {
72       this.picture_ = picture;
73       this.pictureDataProcessed_ = false;
74
75       if (this.classList.contains('hidden'))
76         return;
77
78       this.processPictureData_();
79       this.requiresRedraw = true;
80       this.updateChartContents();
81     },
82
83     hide: function() {
84       this.classList.add('hidden');
85     },
86
87     show: function() {
88
89       this.classList.remove('hidden');
90
91       if (this.pictureDataProcessed_)
92         return;
93
94       this.processPictureData_();
95       this.requiresRedraw = true;
96       this.updateChartContents();
97
98     },
99
100     onMouseMove_: function(e) {
101
102       var lastBarMouseOverTarget = this.currentBarMouseOverTarget_;
103       this.currentBarMouseOverTarget_ = null;
104
105       var x = e.offsetX;
106       var y = e.offsetY;
107
108       var chartLeft = CHART_PADDING_LEFT;
109       var chartRight = this.chartWidth_ - CHART_PADDING_RIGHT;
110       var chartTop = AXIS_PADDING_TOP;
111       var chartBottom = this.chartHeight_ - AXIS_PADDING_BOTTOM;
112       var chartInnerWidth = chartRight - chartLeft;
113
114       if (x > chartLeft && x < chartRight && y > chartTop && y < chartBottom) {
115
116         this.currentBarMouseOverTarget_ = Math.floor(
117             (x - chartLeft) / chartInnerWidth * this.opsTimingData_.length);
118
119         this.currentBarMouseOverTarget_ = tvcm.clamp(
120             this.currentBarMouseOverTarget_, 0, this.opsTimingData_.length - 1);
121
122       }
123
124       if (this.currentBarMouseOverTarget_ === lastBarMouseOverTarget)
125         return;
126
127       this.drawChartContents_();
128     },
129
130     updateChartContents: function() {
131
132       if (this.requiresRedraw)
133         this.updateChartDimensions_();
134
135       this.drawChartContents_();
136     },
137
138     updateChartDimensions_: function() {
139       this.chartWidth_ = this.offsetWidth;
140       this.chartHeight_ = this.offsetHeight;
141
142       // Scale up the canvas according to the devicePixelRatio, then reduce it
143       // down again via CSS. Finally we apply a scale to the canvas so that
144       // things are drawn at the correct size.
145       this.chart_.width = this.chartWidth_ * this.chartScale_;
146       this.chart_.height = this.chartHeight_ * this.chartScale_;
147
148       this.chart_.style.width = this.chartWidth_ + 'px';
149       this.chart_.style.height = this.chartHeight_ + 'px';
150
151       this.chartCtx_.scale(this.chartScale_, this.chartScale_);
152     },
153
154     processPictureData_: function() {
155
156       this.resetOpsTimingData_();
157       this.pictureDataProcessed_ = true;
158
159       if (!this.picture_)
160         return;
161
162       var ops = this.picture_.getOps();
163       if (!ops)
164         return;
165
166       ops = this.picture_.tagOpsWithTimings(ops);
167
168       // Check that there are valid times.
169       if (ops[0].cmd_time === undefined)
170         return;
171
172       this.collapseOpsToTimingBuckets_(ops);
173     },
174
175     drawChartContents_: function() {
176
177       this.clearChartContents_();
178
179       if (this.opsTimingData_.length === 0) {
180         this.showNoTimingDataMessage_();
181         return;
182       }
183
184       this.drawChartAxes_();
185       this.drawBars_();
186       this.drawLineAtBottomOfChart_();
187
188       if (this.currentBarMouseOverTarget_ === null)
189         return;
190
191       this.drawTooltip_();
192     },
193
194     drawLineAtBottomOfChart_: function() {
195       this.chartCtx_.strokeStyle = '#AAA';
196       this.chartCtx_.moveTo(0, this.chartHeight_ - 0.5);
197       this.chartCtx_.lineTo(this.chartWidth_, this.chartHeight_ - 0.5);
198       this.chartCtx_.stroke();
199     },
200
201     drawTooltip_: function() {
202
203       var tooltipData = this.opsTimingData_[this.currentBarMouseOverTarget_];
204       var tooltipTitle = tooltipData.cmd_string;
205       var tooltipTime = tooltipData.cmd_time.toFixed(4);
206
207       var tooltipWidth = 110;
208       var tooltipHeight = 40;
209       var chartInnerWidth = this.chartWidth_ - CHART_PADDING_RIGHT -
210           CHART_PADDING_LEFT;
211       var barWidth = chartInnerWidth / this.opsTimingData_.length;
212       var tooltipOffset = Math.round((tooltipWidth - barWidth) * 0.5);
213
214       var left = CHART_PADDING_LEFT + this.currentBarMouseOverTarget_ *
215           barWidth - tooltipOffset;
216       var top = Math.round((this.chartHeight_ - tooltipHeight) * 0.5);
217
218       this.chartCtx_.save();
219
220       this.chartCtx_.shadowOffsetX = 0;
221       this.chartCtx_.shadowOffsetY = 5;
222       this.chartCtx_.shadowBlur = 4;
223       this.chartCtx_.shadowColor = 'rgba(0,0,0,0.4)';
224
225       this.chartCtx_.strokeStyle = '#888';
226       this.chartCtx_.fillStyle = '#EEE';
227       this.chartCtx_.fillRect(left, top, tooltipWidth, tooltipHeight);
228
229       this.chartCtx_.shadowColor = 'transparent';
230       this.chartCtx_.translate(0.5, 0.5);
231       this.chartCtx_.strokeRect(left, top, tooltipWidth, tooltipHeight);
232
233       this.chartCtx_.restore();
234
235       this.chartCtx_.fillStyle = '#222';
236       this.chartCtx_.textBaseline = 'top';
237       this.chartCtx_.font = '800 12px Arial';
238       this.chartCtx_.fillText(tooltipTitle, left + 8, top + 8);
239
240       this.chartCtx_.fillStyle = '#555';
241       this.chartCtx_.textBaseline = 'top';
242       this.chartCtx_.font = '400 italic 10px Arial';
243       this.chartCtx_.fillText('Total: ' + tooltipTime + 'ms',
244           left + 8, top + 22);
245     },
246
247     drawBars_: function() {
248
249       var len = this.opsTimingData_.length;
250       var max = this.opsTimingData_[0].cmd_time;
251       var min = this.opsTimingData_[len - 1].cmd_time;
252
253       var width = this.chartWidth_ - CHART_PADDING_LEFT - CHART_PADDING_RIGHT;
254       var height = this.chartHeight_ - AXIS_PADDING_TOP - AXIS_PADDING_BOTTOM;
255       var barWidth = Math.floor(width / len);
256
257       var opData;
258       var opTiming;
259       var opHeight;
260       var opLabel;
261       var barLeft;
262
263       for (var b = 0; b < len; b++) {
264
265         opData = this.opsTimingData_[b];
266         opTiming = opData.cmd_time / max;
267
268         opHeight = Math.round(Math.max(1, opTiming * height));
269         opLabel = opData.cmd_string;
270         barLeft = CHART_PADDING_LEFT + b * barWidth;
271
272         this.chartCtx_.fillStyle = this.getOpColor_(opLabel);
273
274         this.chartCtx_.fillRect(barLeft + BAR_PADDING, AXIS_PADDING_TOP +
275             height - opHeight, barWidth - 2 * BAR_PADDING, opHeight);
276       }
277
278     },
279
280     getOpColor_: function(opName) {
281
282       var characters = opName.split('');
283       var hue = characters.reduce(this.reduceNameToHue, 0) % 360;
284
285       return 'hsl(' + hue + ', 30%, 50%)';
286     },
287
288     reduceNameToHue: function(previousValue, currentValue, index, array) {
289       // Get the char code and apply a magic adjustment value so we get
290       // pretty colors from around the rainbow.
291       return Math.round(previousValue + currentValue.charCodeAt(0) *
292           HUE_CHAR_CODE_ADJUSTMENT);
293     },
294
295     drawChartAxes_: function() {
296
297       var len = this.opsTimingData_.length;
298       var max = this.opsTimingData_[0].cmd_time;
299       var min = this.opsTimingData_[len - 1].cmd_time;
300
301       var width = this.chartWidth_ - AXIS_PADDING_LEFT - AXIS_PADDING_RIGHT;
302       var height = this.chartHeight_ - AXIS_PADDING_TOP - AXIS_PADDING_BOTTOM;
303
304       var totalBarWidth = this.chartWidth_ - CHART_PADDING_LEFT -
305           CHART_PADDING_RIGHT;
306       var barWidth = Math.floor(totalBarWidth / len);
307       var tickYInterval = height / (VERTICAL_TICKS - 1);
308       var tickYPosition = 0;
309       var tickValInterval = (max - min) / (VERTICAL_TICKS - 1);
310       var tickVal = 0;
311
312       this.chartCtx_.fillStyle = '#333';
313       this.chartCtx_.strokeStyle = '#777';
314       this.chartCtx_.save();
315
316       // Translate half a pixel to avoid blurry lines.
317       this.chartCtx_.translate(0.5, 0.5);
318
319       // Sides.
320
321       this.chartCtx_.save();
322
323       this.chartCtx_.translate(AXIS_PADDING_LEFT, AXIS_PADDING_TOP);
324       this.chartCtx_.moveTo(0, 0);
325       this.chartCtx_.lineTo(0, height);
326       this.chartCtx_.lineTo(width, height);
327
328       // Y-axis ticks.
329       this.chartCtx_.font = '10px Arial';
330       this.chartCtx_.textAlign = 'right';
331       this.chartCtx_.textBaseline = 'middle';
332
333       for (var t = 0; t < VERTICAL_TICKS; t++) {
334
335         tickYPosition = Math.round(t * tickYInterval);
336         tickVal = (max - t * tickValInterval).toFixed(4);
337
338         this.chartCtx_.moveTo(0, tickYPosition);
339         this.chartCtx_.lineTo(-AXIS_TICK_SIZE, tickYPosition);
340         this.chartCtx_.fillText(tickVal,
341             -AXIS_TICK_SIZE - AXIS_LABEL_PADDING, tickYPosition);
342
343       }
344
345       this.chartCtx_.stroke();
346
347       this.chartCtx_.restore();
348
349
350       // Labels.
351
352       this.chartCtx_.save();
353
354       this.chartCtx_.translate(CHART_PADDING_LEFT + Math.round(barWidth * 0.5),
355           AXIS_PADDING_TOP + height + LABEL_PADDING);
356
357       this.chartCtx_.font = '10px Arial';
358       this.chartCtx_.textAlign = 'center';
359       this.chartCtx_.textBaseline = 'top';
360
361       var labelTickLeft;
362       var labelTickBottom;
363       for (var l = 0; l < len; l++) {
364
365         labelTickLeft = Math.round(l * barWidth);
366         labelTickBottom = l % 2 * LABEL_INTERLEAVE_OFFSET;
367
368         this.chartCtx_.save();
369         this.chartCtx_.moveTo(labelTickLeft, -LABEL_PADDING);
370         this.chartCtx_.lineTo(labelTickLeft, labelTickBottom);
371         this.chartCtx_.stroke();
372         this.chartCtx_.restore();
373
374         this.chartCtx_.fillText(this.opsTimingData_[l].cmd_string,
375             labelTickLeft, labelTickBottom);
376       }
377
378       this.chartCtx_.restore();
379
380       this.chartCtx_.restore();
381     },
382
383     clearChartContents_: function() {
384       this.chartCtx_.clearRect(0, 0, this.chartWidth_, this.chartHeight_);
385     },
386
387     showNoTimingDataMessage_: function() {
388       this.chartCtx_.font = '800 italic 14px Arial';
389       this.chartCtx_.fillStyle = '#333';
390       this.chartCtx_.textAlign = 'center';
391       this.chartCtx_.textBaseline = 'middle';
392       this.chartCtx_.fillText('No timing data available.',
393           this.chartWidth_ * 0.5, this.chartHeight_ * 0.5);
394     },
395
396     collapseOpsToTimingBuckets_: function(ops) {
397
398       var opsTimingDataIndexHash_ = {};
399       var timingData = this.opsTimingData_;
400       var op;
401       var opIndex;
402
403       for (var i = 0; i < ops.length; i++) {
404
405         op = ops[i];
406
407         if (op.cmd_time === undefined)
408           continue;
409
410         // Try to locate the entry for the current operation
411         // based on its name. If that fails, then create one for it.
412         opIndex = opsTimingDataIndexHash_[op.cmd_string] || null;
413
414         if (opIndex === null) {
415           timingData.push({
416             cmd_time: 0,
417             cmd_string: op.cmd_string
418           });
419
420           opIndex = timingData.length - 1;
421           opsTimingDataIndexHash_[op.cmd_string] = opIndex;
422         }
423
424         timingData[opIndex].cmd_time += op.cmd_time;
425
426       }
427
428       timingData.sort(this.sortTimingBucketsByOpTimeDescending_);
429
430       this.collapseTimingBucketsToOther_(4);
431     },
432
433     collapseTimingBucketsToOther_: function(count) {
434
435       var timingData = this.opsTimingData_;
436       var otherSource = timingData.splice(count, timingData.length - count);
437       var otherDestination = null;
438
439       if (!otherSource.length)
440         return;
441
442       timingData.push({
443         cmd_time: 0,
444         cmd_string: 'Other'
445       });
446
447       otherDestination = timingData[timingData.length - 1];
448       for (var i = 0; i < otherSource.length; i++) {
449         otherDestination.cmd_time += otherSource[i].cmd_time;
450       }
451     },
452
453     sortTimingBucketsByOpTimeDescending_: function(a, b) {
454       return b.cmd_time - a.cmd_time;
455     },
456
457     resetOpsTimingData_: function() {
458       this.opsTimingData_.length = 0;
459     }
460   };
461
462   return {
463     PictureOpsChartSummaryView: PictureOpsChartSummaryView
464   };
465 });