| <!DOCTYPE html> |
| <!-- |
| Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| Use of this source code is governed by a BSD-style license that can be |
| found in the LICENSE file. |
| --> |
| |
| <link rel="import" href="/tracing/base/iteration_helpers.html"> |
| <link rel="import" href="/tracing/base/statistics.html"> |
| <link rel="import" href="/tracing/model/event_set.html"> |
| <link rel="import" href="/tracing/ui/base/dom_helpers.html"> |
| <link rel="import" href="/tracing/ui/base/pie_chart.html"> |
| <link rel="import" href="/tracing/ui/side_panel/side_panel.html"> |
| <link rel="import" href="/tracing/value/ui/scalar_span.html"> |
| <link rel="import" href="/tracing/value/unit.html"> |
| |
| <polymer-element name="tr-ui-e-s-time-summary-side-panel" |
| extends="tr-ui-side-panel"> |
| <template> |
| <style> |
| :host { |
| flex-direction: column; |
| display: flex; |
| } |
| toolbar { |
| flex: 0 0 auto; |
| border-bottom: 1px solid black; |
| display: flex; |
| } |
| result-area { |
| flex: 1 1 auto; |
| display: block; |
| min-height: 0; |
| overflow-y: auto; |
| } |
| </style> |
| |
| <toolbar id='toolbar'></toolbar> |
| <result-area id='result_area'></result-area> |
| </template> |
| |
| <script> |
| 'use strict'; |
| (function() { |
| var GROUP_BY_PROCESS_NAME = 'process'; |
| var GROUP_BY_THREAD_NAME = 'thread'; |
| |
| var WALL_TIME_GROUPING_UNIT = 'Wall time'; |
| var CPU_TIME_GROUPING_UNIT = 'CPU time'; |
| |
| /** |
| * @constructor |
| */ |
| function ResultsForGroup(model, name) { |
| this.model = model; |
| this.name = name; |
| this.topLevelSlices = []; |
| this.allSlices = []; |
| } |
| |
| ResultsForGroup.prototype = { |
| get wallTime() { |
| var wallSum = tr.b.Statistics.sum( |
| this.topLevelSlices, function(x) { return x.duration; }); |
| return wallSum; |
| }, |
| |
| get cpuTime() { |
| var cpuDuration = 0; |
| for (var i = 0; i < this.topLevelSlices.length; i++) { |
| var x = this.topLevelSlices[i]; |
| // Only report thread-duration if we have it for all events. |
| // |
| // A thread_duration of 0 is valid, so this only returns 0 if it is |
| // None. |
| if (x.cpuDuration === undefined) { |
| if (x.duration === undefined) |
| continue; |
| return 0; |
| } |
| cpuDuration += x.cpuDuration; |
| } |
| return cpuDuration; |
| }, |
| |
| appendGroupContents: function(group) { |
| if (group.model != this.model) |
| throw new Error('Models must be the same'); |
| |
| group.allSlices.forEach(function(slice) { |
| this.allSlices.push(slice); |
| }, this); |
| group.topLevelSlices.forEach(function(slice) { |
| this.topLevelSlices.push(slice); |
| }, this); |
| }, |
| |
| appendThreadSlices: function(rangeOfInterest, thread) { |
| var tmp = this.getSlicesIntersectingRange( |
| rangeOfInterest, thread.sliceGroup.slices); |
| tmp.forEach(function(slice) { |
| this.allSlices.push(slice); |
| }, this); |
| tmp = this.getSlicesIntersectingRange( |
| rangeOfInterest, thread.sliceGroup.topLevelSlices); |
| tmp.forEach(function(slice) { |
| this.topLevelSlices.push(slice); |
| }, this); |
| }, |
| |
| getSlicesIntersectingRange: function(rangeOfInterest, slices) { |
| var slicesInFilterRange = []; |
| for (var i = 0; i < slices.length; i++) { |
| var slice = slices[i]; |
| if (rangeOfInterest.intersectsExplicitRangeInclusive( |
| slice.start, slice.end)) |
| slicesInFilterRange.push(slice); |
| } |
| return slicesInFilterRange; |
| } |
| }; |
| |
| Polymer({ |
| ready: function() { |
| this.rangeOfInterest_ = new tr.b.Range(); |
| this.selection_ = undefined; |
| this.groupBy_ = GROUP_BY_PROCESS_NAME; |
| this.groupingUnit_ = CPU_TIME_GROUPING_UNIT; |
| this.showCpuIdleTime_ = true; |
| this.chart_ = undefined; |
| |
| var toolbarEl = this.$.toolbar; |
| this.groupBySelector_ = tr.ui.b.createSelector( |
| this, 'groupBy', |
| 'timeSummarySidePanel.groupBy', this.groupBy_, |
| [{label: 'Group by process', value: GROUP_BY_PROCESS_NAME}, |
| {label: 'Group by thread', value: GROUP_BY_THREAD_NAME} |
| ]); |
| toolbarEl.appendChild(this.groupBySelector_); |
| |
| this.groupingUnitSelector_ = tr.ui.b.createSelector( |
| this, 'groupingUnit', |
| 'timeSummarySidePanel.groupingUnit', this.groupingUnit_, |
| [{label: 'Wall time', value: WALL_TIME_GROUPING_UNIT}, |
| {label: 'CPU time', value: CPU_TIME_GROUPING_UNIT} |
| ]); |
| toolbarEl.appendChild(this.groupingUnitSelector_); |
| |
| this.showCpuIdleTimeCheckbox_ = tr.ui.b.createCheckBox( |
| this, 'showCpuIdleTime', |
| 'timeSummarySidePanel.showCpuIdleTime', this.showCpuIdleTime_, |
| 'Show CPU idle time'); |
| toolbarEl.appendChild(this.showCpuIdleTimeCheckbox_); |
| this.updateShowCpuIdleTimeCheckboxVisibility_(); |
| }, |
| |
| /** |
| * This function takes an array of groups and merges smaller groups into |
| * the provided 'Other' group item such that the remaining items are ready |
| * for pie-chart consumption. Otherwise, the pie chart gets overwhelmed |
| * with tons of little slices. |
| */ |
| trimPieChartData: function(groups, otherGroup, getValue, opt_extraValue) { |
| // Copy the array so it can be mutated. |
| groups = groups.filter(function(d) { |
| return getValue(d) != 0; |
| }); |
| |
| // Figure out total array range. |
| var sum = tr.b.Statistics.sum(groups, getValue); |
| if (opt_extraValue !== undefined) |
| sum += opt_extraValue; |
| |
| // Sort by value. |
| function compareByValue(a, b) { |
| return getValue(a) - getValue(b); |
| } |
| groups.sort(compareByValue); |
| |
| // Now start fusing elements until none are less than threshold in size. |
| var thresshold = 0.1 * sum; |
| while (groups.length > 1) { |
| var group = groups[0]; |
| if (getValue(group) >= thresshold) |
| break; |
| |
| var v = getValue(group); |
| if (v + getValue(otherGroup) > thresshold) |
| break; |
| |
| // Remove the group from the list and add it to the 'Other' group. |
| groups.splice(0, 1); |
| otherGroup.appendGroupContents(group); |
| } |
| |
| // Final return. |
| if (getValue(otherGroup) > 0) |
| groups.push(otherGroup); |
| |
| groups.sort(compareByValue); |
| |
| return groups; |
| }, |
| |
| generateResultsForGroup: function(model, name) { |
| return new ResultsForGroup(model, name); |
| }, |
| |
| createPieChartFromResultGroups: function( |
| groups, title, getValue, opt_extraData) { |
| var chart = new tr.ui.b.PieChart(); |
| |
| function pushDataForGroup(data, resultsForGroup, value) { |
| data.push({ |
| label: resultsForGroup.name, |
| value: value, |
| valueText: tr.v.Unit.byName.timeDurationInMs.format(value), |
| resultsForGroup: resultsForGroup |
| }); |
| } |
| chart.addEventListener('item-click', function(clickEvent) { |
| var resultsForGroup = clickEvent.data.resultsForGroup; |
| if (resultsForGroup === undefined) |
| return; |
| |
| var event = new tr.model.RequestSelectionChangeEvent(); |
| event.selection = new tr.model.EventSet(resultsForGroup.allSlices); |
| event.selection.timeSummaryGroupName = resultsForGroup.name; |
| chart.dispatchEvent(event); |
| }); |
| |
| |
| // Build chart data. |
| var data = []; |
| groups.forEach(function(resultsForGroup) { |
| var value = getValue(resultsForGroup); |
| if (value === 0) |
| return; |
| pushDataForGroup(data, resultsForGroup, value); |
| }); |
| if (opt_extraData) |
| data.push.apply(data, opt_extraData); |
| |
| chart.chartTitle = title; |
| chart.data = data; |
| return chart; |
| }, |
| |
| get model() { |
| return this.model_; |
| }, |
| |
| set model(model) { |
| this.model_ = model; |
| this.updateContents_(); |
| }, |
| |
| get groupBy() { |
| return groupBy_; |
| }, |
| |
| set groupBy(groupBy) { |
| this.groupBy_ = groupBy; |
| if (this.groupBySelector_) |
| this.groupBySelector_.selectedValue = groupBy; |
| this.updateContents_(); |
| }, |
| |
| get groupingUnit() { |
| return groupingUnit_; |
| }, |
| |
| set groupingUnit(groupingUnit) { |
| this.groupingUnit_ = groupingUnit; |
| if (this.groupingUnitSelector_) |
| this.groupingUnitSelector_.selectedValue = groupingUnit; |
| this.updateShowCpuIdleTimeCheckboxVisibility_(); |
| this.updateContents_(); |
| }, |
| |
| get showCpuIdleTime() { |
| return this.showCpuIdleTime_; |
| }, |
| |
| set showCpuIdleTime(showCpuIdleTime) { |
| this.showCpuIdleTime_ = showCpuIdleTime; |
| if (this.showCpuIdleTimeCheckbox_) |
| this.showCpuIdleTimeCheckbox_.checked = showCpuIdleTime; |
| this.updateContents_(); |
| }, |
| |
| updateShowCpuIdleTimeCheckboxVisibility_: function() { |
| if (!this.showCpuIdleTimeCheckbox_) |
| return; |
| var visible = this.groupingUnit_ == CPU_TIME_GROUPING_UNIT; |
| if (visible) |
| this.showCpuIdleTimeCheckbox_.style.display = ''; |
| else |
| this.showCpuIdleTimeCheckbox_.style.display = 'none'; |
| }, |
| |
| getGroupNameForThread_: function(thread) { |
| if (this.groupBy_ == GROUP_BY_THREAD_NAME) |
| return thread.name ? thread.name : thread.userFriendlyName; |
| |
| if (this.groupBy_ == GROUP_BY_PROCESS_NAME) |
| return thread.parent.userFriendlyName; |
| }, |
| |
| updateContents_: function() { |
| var resultArea = this.$.result_area; |
| this.chart_ = undefined; |
| resultArea.textContent = ''; |
| |
| if (this.model_ === undefined) |
| return; |
| |
| var rangeOfInterest; |
| if (this.rangeOfInterest_.isEmpty) |
| rangeOfInterest = this.model_.bounds; |
| else |
| rangeOfInterest = this.rangeOfInterest_; |
| |
| var allGroup = this.generateResultsForGroup(this.model_, 'all'); |
| var resultsByGroupName = {}; |
| this.model_.getAllThreads().forEach(function(thread) { |
| var groupName = this.getGroupNameForThread_(thread); |
| if (resultsByGroupName[groupName] === undefined) { |
| resultsByGroupName[groupName] = this.generateResultsForGroup( |
| this.model_, groupName); |
| } |
| resultsByGroupName[groupName].appendThreadSlices( |
| rangeOfInterest, thread); |
| |
| allGroup.appendThreadSlices(rangeOfInterest, thread); |
| }, this); |
| |
| // Helper function for working with the produced group. |
| var getValueFromGroup = function(group) { |
| if (this.groupingUnit_ == WALL_TIME_GROUPING_UNIT) |
| return group.wallTime; |
| return group.cpuTime; |
| }.bind(this); |
| |
| // Create summary. |
| var summaryText = document.createElement('div'); |
| summaryText.appendChild(tr.ui.b.createSpan({ |
| textContent: 'Total ' + this.groupingUnit_ + ': ', |
| bold: true})); |
| summaryText.appendChild(tr.v.ui.createScalarSpan( |
| getValueFromGroup(allGroup), { |
| unit: tr.v.Unit.byName.timeDurationInMs, |
| ownerDocument: this.ownerDocument |
| })); |
| resultArea.appendChild(summaryText); |
| |
| // If needed, add in the idle time. |
| var extraValue = 0; |
| var extraData = []; |
| if (this.showCpuIdleTime_ && |
| this.groupingUnit_ === CPU_TIME_GROUPING_UNIT && |
| this.model.kernel.bestGuessAtCpuCount !== undefined) { |
| var maxCpuTime = rangeOfInterest.range * |
| this.model.kernel.bestGuessAtCpuCount; |
| var idleTime = Math.max(0, maxCpuTime - allGroup.cpuTime); |
| extraData.push({ |
| label: 'CPU Idle', |
| value: idleTime, |
| valueText: tr.v.Unit.byName.timeDurationInMs.format(idleTime) |
| }); |
| extraValue += idleTime; |
| } |
| |
| // Create the actual chart. |
| var otherGroup = this.generateResultsForGroup(this.model_, 'Other'); |
| var groups = this.trimPieChartData( |
| tr.b.dictionaryValues(resultsByGroupName), |
| otherGroup, |
| getValueFromGroup, |
| extraValue); |
| |
| if (groups.length == 0) { |
| resultArea.appendChild(tr.ui.b.createSpan({textContent: 'No data'})); |
| return undefined; |
| } |
| |
| this.chart_ = this.createPieChartFromResultGroups( |
| groups, |
| this.groupingUnit_ + ' breakdown by ' + this.groupBy_, |
| getValueFromGroup, extraData); |
| resultArea.appendChild(this.chart_); |
| |
| this.chart_.addEventListener('click', function() { |
| var event = new tr.model.RequestSelectionChangeEvent(); |
| event.selection = new tr.c.EventSet([]); |
| this.dispatchEvent(event); |
| }); |
| this.chart_.setSize(this.chart_.getMinSize()); |
| }, |
| |
| get selection() { |
| return selection_; |
| }, |
| |
| set selection(selection) { |
| this.selection_ = selection; |
| |
| if (this.chart_ === undefined) |
| return; |
| |
| if (selection.timeSummaryGroupName) |
| this.chart_.highlightedLegendKey = selection.timeSummaryGroupName; |
| else |
| this.chart_.highlightedLegendKey = undefined; |
| }, |
| |
| get rangeOfInterest() { |
| return this.rangeOfInterest_; |
| }, |
| |
| set rangeOfInterest(rangeOfInterest) { |
| this.rangeOfInterest_ = rangeOfInterest; |
| this.updateContents_(); |
| }, |
| |
| supportsModel: function(model) { |
| return { |
| supported: false |
| }; |
| }, |
| |
| get textLabel() { |
| return 'Time Summary'; |
| } |
| }); |
| }()); |
| </script> |
| </polymer-element> |