Upstream version 9.37.197.0
[platform/framework/web/crosswalk.git] / src / third_party / trace-viewer / trace_viewer / tracing / time_summary_side_panel.js
1 // Copyright (c) 2014 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.require('tracing.analysis.util');
8 tvcm.require('tracing.selection');
9 tvcm.require('tracing.timeline_view_side_panel');
10 tvcm.require('tvcm.iteration_helpers');
11 tvcm.require('tvcm.statistics');
12 tvcm.require('tvcm.ui.dom_helpers');
13 tvcm.require('tvcm.ui.pie_chart');
14
15 tvcm.requireTemplate('tracing.time_summary_side_panel');
16
17 tvcm.exportTo('tracing', function() {
18   var ThreadSlice = tracing.trace_model.ThreadSlice;
19
20   var OVERHEAD_TRACE_CATEGORY = 'trace_event_overhead';
21   var OVERHEAD_TRACE_NAME = 'overhead';
22
23   var tsRound = tracing.analysis.tsRound;
24
25   var RequestSelectionChangeEvent = tracing.RequestSelectionChangeEvent;
26
27   function getWallTimeOverheadForEvent(event) {
28     if (event.category == OVERHEAD_TRACE_CATEGORY &&
29         event.name == OVERHEAD_TRACE_NAME) {
30       return event.duration;
31     }
32     return 0;
33   }
34
35   function getCpuTimeOverheadForEvent(event) {
36     if (event.category == OVERHEAD_TRACE_CATEGORY &&
37         event.cpuDuration) {
38       return event.cpuDuration;
39     }
40     return 0;
41   }
42
43   function getSlicesIntersectingRange(rangeOfInterest, slices) {
44     var slicesInFilterRange = [];
45     for (var i = 0; i < slices.length; i++) {
46       var slice = slices[i];
47       if (rangeOfInterest.intersectsExplicitRange(slice.start, slice.end))
48         slicesInFilterRange.push(slice);
49     }
50     return slicesInFilterRange;
51   }
52
53   /**
54    * This function takes an array of groups and merges smaller groups into the
55    * provided 'Other' group item such that the remaining items are ready for
56    * pie-chart consumption. Otherwise, the pie chart gets overwhelmed with tons
57    * of little slices.
58    */
59   function trimPieChartData(groups, otherGroup, getValue, opt_extraValue) {
60     // Copy the array so it can be mutated.
61     groups = groups.filter(function(d) {
62       return getValue(d) != 0;
63     });
64
65     // Figure out total array range.
66     var sum = tvcm.Statistics.sum(groups, getValue);
67     if (opt_extraValue !== undefined)
68       sum += opt_extraValue;
69
70     // Sort by value.
71     function compareByValue(a, b) {
72       return getValue(a) - getValue(b);
73     }
74     groups.sort(compareByValue);
75
76     // Now start fusing elements until none are less than threshold in size.
77     var thresshold = 0.1 * sum;
78     while (groups.length > 1) {
79       var group = groups[0];
80       if (getValue(group) >= thresshold)
81         break;
82
83       var v = getValue(group);
84       if (v + getValue(otherGroup) > thresshold)
85         break;
86
87       // Remove the group from the list and add it to the 'Other' group.
88       groups.splice(0, 1);
89       otherGroup.appendGroupContents(group);
90     }
91
92     // Final return.
93     if (getValue(otherGroup) > 0)
94       groups.push(otherGroup);
95
96     groups.sort(compareByValue);
97
98     return groups;
99   }
100
101   function createPieChartFromResultGroups(
102       groups, title, getValue, opt_extraData) {
103     var chart = new tvcm.ui.PieChart();
104
105     function pushDataForGroup(data, resultsForGroup, value) {
106       data.push({
107         label: resultsForGroup.name,
108         value: value,
109         valueText: tsRound(value) + 'ms',
110         onClick: function() {
111           var event = new tracing.RequestSelectionChangeEvent();
112           event.selection = new tracing.Selection(resultsForGroup.allSlices);
113           event.selection.timeSummaryGroupName = resultsForGroup.name;
114           chart.dispatchEvent(event);
115         }
116       });
117     }
118
119     // Build chart data.
120     var data = [];
121     groups.forEach(function(resultsForGroup) {
122       var value = getValue(resultsForGroup);
123       if (value === 0)
124         return;
125       pushDataForGroup(data, resultsForGroup, value);
126     });
127     if (opt_extraData)
128       data.push.apply(data, opt_extraData);
129
130     chart.chartTitle = title;
131     chart.data = data;
132     return chart;
133   }
134
135
136   /**
137    * @constructor
138    */
139   function ResultsForGroup(model, name) {
140     this.model = model;
141     this.name = name;
142     this.topLevelSlices = [];
143     this.allSlices = [];
144   }
145
146   ResultsForGroup.prototype = {
147     get wallTime() {
148       var wallSum = tvcm.Statistics.sum(
149           this.topLevelSlices, function(x) { return x.duration; });
150       return wallSum;
151     },
152
153     get cpuTime() {
154       var cpuDuration = 0;
155       for (var i = 0; i < this.topLevelSlices.length; i++) {
156         var x = this.topLevelSlices[i];
157         // Only report thread-duration if we have it for all events.
158         //
159         // A thread_duration of 0 is valid, so this only returns 0 if it is
160         // None.
161         if (x.cpuDuration === undefined) {
162           if (x.duration === undefined)
163             continue;
164           return 0;
165         } else {
166           cpuDuration += x.cpuDuration;
167         }
168       }
169
170       return cpuDuration;
171     },
172
173     appendGroupContents: function(group) {
174       if (group.model != this.model)
175         throw new Error('Models must be the same');
176
177       group.allSlices.forEach(function(slice) {
178         this.allSlices.push(slice);
179       }, this);
180       group.topLevelSlices.forEach(function(slice) {
181         this.topLevelSlices.push(slice);
182       }, this);
183     },
184
185     appendThreadSlices: function(rangeOfInterest, thread) {
186       var tmp = getSlicesIntersectingRange(
187           rangeOfInterest, thread.sliceGroup.slices);
188       tmp.forEach(function(slice) {
189         this.allSlices.push(slice);
190       }, this);
191       tmp = getSlicesIntersectingRange(
192           rangeOfInterest, thread.sliceGroup.topLevelSlices);
193       tmp.forEach(function(slice) {
194         this.topLevelSlices.push(slice);
195       }, this);
196     }
197   };
198
199   var GROUP_BY_PROCESS_NAME = 'process';
200   var GROUP_BY_THREAD_NAME = 'thread';
201
202   var WALL_TIME_GROUPING_UNIT = 'Wall time';
203   var CPU_TIME_GROUPING_UNIT = 'CPU time';
204
205   /**
206    * @constructor
207    */
208   var TimeSummarySidePanel = tvcm.ui.define('x-time-summary-side-panel',
209                                             tracing.TimelineViewSidePanel);
210   TimeSummarySidePanel.textLabel = 'Thread Times';
211   TimeSummarySidePanel.supportsModel = function(m) {
212     return {
213       supported: true
214     };
215   };
216
217   TimeSummarySidePanel.prototype = {
218     __proto__: tracing.TimelineViewSidePanel.prototype,
219
220     decorate: function() {
221       tracing.TimelineViewSidePanel.prototype.decorate.call(this);
222       this.classList.add('x-time-summary-side-panel');
223       this.appendChild(tvcm.instantiateTemplate(
224           '#x-time-summary-side-panel-template'));
225
226       this.rangeOfInterest_ = new tvcm.Range();
227       this.selection_ = undefined;
228       this.groupBy_ = GROUP_BY_PROCESS_NAME;
229       this.groupingUnit_ = CPU_TIME_GROUPING_UNIT;
230       this.showCpuIdleTime_ = true;
231       this.chart_ = undefined;
232
233       var toolbarEl = this.querySelector('toolbar');
234       this.groupBySelector_ = tvcm.ui.createSelector(
235           this, 'groupBy',
236           'timeSummarySidePanel.groupBy', this.groupBy_,
237           [{label: 'Group by process', value: GROUP_BY_PROCESS_NAME},
238            {label: 'Group by thread', value: GROUP_BY_THREAD_NAME}
239           ]);
240       toolbarEl.appendChild(this.groupBySelector_);
241
242       this.groupingUnitSelector_ = tvcm.ui.createSelector(
243           this, 'groupingUnit',
244           'timeSummarySidePanel.groupingUnit', this.groupingUnit_,
245           [{label: 'Wall time', value: WALL_TIME_GROUPING_UNIT},
246            {label: 'CPU time', value: CPU_TIME_GROUPING_UNIT}
247           ]);
248       toolbarEl.appendChild(this.groupingUnitSelector_);
249
250       this.showCpuIdleTimeCheckbox_ = tvcm.ui.createCheckBox(
251           this, 'showCpuIdleTime',
252           'timeSummarySidePanel.showCpuIdleTime', this.showCpuIdleTime_,
253           'Show CPU idle time');
254       toolbarEl.appendChild(this.showCpuIdleTimeCheckbox_);
255       this.updateShowCpuIdleTimeCheckboxVisibility_();
256     },
257
258     get model() {
259       return this.model_;
260     },
261
262     set model(model) {
263       this.model_ = model;
264       this.updateContents_();
265     },
266
267     get groupBy() {
268       return groupBy_;
269     },
270
271     set groupBy(groupBy) {
272       this.groupBy_ = groupBy;
273       if (this.groupBySelector_)
274         this.groupBySelector_.selectedValue = groupBy;
275       this.updateContents_();
276     },
277
278     get groupingUnit() {
279       return groupingUnit_;
280     },
281
282     set groupingUnit(groupingUnit) {
283       this.groupingUnit_ = groupingUnit;
284       if (this.groupingUnitSelector_)
285         this.groupingUnitSelector_.selectedValue = groupingUnit;
286       this.updateShowCpuIdleTimeCheckboxVisibility_();
287       this.updateContents_();
288     },
289
290     get showCpuIdleTime() {
291       return this.showCpuIdleTime_;
292     },
293
294     set showCpuIdleTime(showCpuIdleTime) {
295       this.showCpuIdleTime_ = showCpuIdleTime;
296       if (this.showCpuIdleTimeCheckbox_)
297         this.showCpuIdleTimeCheckbox_.checked = showCpuIdleTime;
298       this.updateContents_();
299     },
300
301     updateShowCpuIdleTimeCheckboxVisibility_: function() {
302       if (!this.showCpuIdleTimeCheckbox_)
303         return;
304       var visible = this.groupingUnit_ == CPU_TIME_GROUPING_UNIT;
305       if (visible)
306         this.showCpuIdleTimeCheckbox_.style.display = '';
307       else
308         this.showCpuIdleTimeCheckbox_.style.display = 'none';
309     },
310
311     getGroupNameForThread_: function(thread) {
312       if (this.groupBy_ == GROUP_BY_THREAD_NAME)
313         return thread.name ? thread.name : thread.userFriendlyName;
314
315       if (this.groupBy_ == GROUP_BY_PROCESS_NAME)
316         return thread.parent.userFriendlyName;
317     },
318
319     updateContents_: function() {
320       var resultArea = this.querySelector('result-area');
321       this.chart_ = undefined;
322       resultArea.textContent = '';
323
324       if (this.model_ === undefined)
325         return;
326
327       var rangeOfInterest;
328       if (this.rangeOfInterest_.isEmpty)
329         rangeOfInterest = this.model_.bounds;
330       else
331         rangeOfInterest = this.rangeOfInterest_;
332
333       var allGroup = new ResultsForGroup(this.model_, 'all');
334       var resultsByGroupName = {};
335       this.model_.getAllThreads().forEach(function(thread) {
336         var groupName = this.getGroupNameForThread_(thread);
337         if (resultsByGroupName[groupName] === undefined) {
338           resultsByGroupName[groupName] = new ResultsForGroup(
339               this.model_, groupName);
340         }
341         resultsByGroupName[groupName].appendThreadSlices(
342             rangeOfInterest, thread);
343
344         allGroup.appendThreadSlices(rangeOfInterest, thread);
345       }, this);
346
347       // Helper function for working with the produced group.
348       var getValueFromGroup = function(group) {
349         if (this.groupingUnit_ == WALL_TIME_GROUPING_UNIT)
350           return group.wallTime;
351         return group.cpuTime;
352       }.bind(this);
353
354       // Create summary.
355       var summaryText = document.createElement('div');
356       summaryText.appendChild(tvcm.ui.createSpan({
357         textContent: 'Total ' + this.groupingUnit_ + ': ',
358         bold: true}));
359       summaryText.appendChild(tvcm.ui.createSpan({
360         textContent: tsRound(getValueFromGroup(allGroup)) + 'ms'
361       }));
362       resultArea.appendChild(summaryText);
363
364       // If needed, add in the idle time.
365       var extraValue = 0;
366       var extraData = [];
367       if (this.showCpuIdleTime_ &&
368           this.groupingUnit_ === CPU_TIME_GROUPING_UNIT &&
369           this.model.kernel.bestGuessAtCpuCount !== undefined) {
370         var maxCpuTime = rangeOfInterest.range *
371             this.model.kernel.bestGuessAtCpuCount;
372         var idleTime = Math.max(0, maxCpuTime - allGroup.cpuTime);
373         extraData.push({
374           label: 'CPU Idle',
375           value: idleTime,
376           valueText: tsRound(idleTime) + 'ms'
377         });
378         extraValue += idleTime;
379       }
380
381       // Create the actual chart.
382       var otherGroup = new ResultsForGroup(this.model_, 'Other');
383       var groups = trimPieChartData(
384           tvcm.dictionaryValues(resultsByGroupName),
385           otherGroup,
386           getValueFromGroup,
387           extraValue);
388
389       if (groups.length == 0) {
390         resultArea.appendChild(tvcm.ui.createSpan({textContent: 'No data'}));
391         return undefined;
392       }
393
394       this.chart_ = createPieChartFromResultGroups(
395           groups,
396           this.groupingUnit_ + ' breakdown by ' + this.groupBy_,
397           getValueFromGroup, extraData);
398       resultArea.appendChild(this.chart_);
399       this.chart_.addEventListener('click', function() {
400         var event = new tracing.RequestSelectionChangeEvent();
401         event.selection = new tracing.Selection([]);
402         this.dispatchEvent(event);
403       });
404       this.chart_.setSize(this.chart_.getMinSize());
405     },
406
407     get selection() {
408       return selection_;
409     },
410
411     set selection(selection) {
412       this.selection_ = selection;
413
414       if (this.chart_ === undefined)
415         return;
416
417       if (selection.timeSummaryGroupName) {
418         this.chart_.highlightedLegendKey =
419             selection.timeSummaryGroupName;
420       } else {
421         this.chart_.highlightedLegendKey = undefined;
422       }
423     },
424
425     get rangeOfInterest() {
426       return this.rangeOfInterest_;
427     },
428
429     set rangeOfInterest(rangeOfInterest) {
430       this.rangeOfInterest_ = rangeOfInterest;
431       this.updateContents_();
432     }
433   };
434
435   tracing.TimelineViewSidePanel.registerPanelSubtype(TimeSummarySidePanel);
436
437   return {
438     trimPieChartData: trimPieChartData,
439     createPieChartFromResultGroups: createPieChartFromResultGroups,
440     ResultsForGroup: ResultsForGroup,
441     TimeSummarySidePanel: TimeSummarySidePanel,
442
443     GROUP_BY_PROCESS_NAME: GROUP_BY_PROCESS_NAME,
444     GROUP_BY_THREAD_NAME: GROUP_BY_THREAD_NAME,
445     WALL_TIME_GROUPING_UNIT: WALL_TIME_GROUPING_UNIT,
446     CPU_TIME_GROUPING_UNIT: CPU_TIME_GROUPING_UNIT
447   };
448 });