| <!DOCTYPE html> |
| <!-- |
| Copyright (c) 2015 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/ui/base/dom_helpers.html"> |
| <link rel="import" href="/tracing/ui/base/info_bar_group.html"> |
| <link rel="import" href="/tracing/ui/base/overlay.html"> |
| <link rel="import" href="/tracing/ui/base/table.html"> |
| <link rel="import" href="/tracing/ui/units/time_duration_span.html"> |
| <link rel="import" href="/tracing/ui/units/histogram_span.html"> |
| <link rel="import" href="/perf_insights/results/results.html"> |
| <link rel="import" href="/perf_insights/ui/caching_column.html"> |
| <link rel="import" href="/perf_insights/ui/reports/pi_report.html"> |
| <link rel="import" href="/perf_insights/ui/grouping_table.html"> |
| <link rel="import" href="/perf_insights/ui/generic_results_view.html"> |
| <link rel="import" href="/perf_insights/ui/trace_link_list.html"> |
| <link rel="import" href="/perf_insights/mappers/slice_cost.html"> |
| |
| <polymer-element name="pi-ui-r-slice-cost-report" |
| extends="pi-ui-r-pi-report" |
| map-function-href="/perf_insights/mappers/weather_report_map_function.html" |
| map-function-name="weatherReportMapFunction"> |
| <template> |
| <style> |
| :host { |
| display: flex; |
| flex-direction: column; |
| } |
| |
| top-controls { |
| display: flex; |
| flex: 0 0 auto; |
| flex-flow: wrap; |
| background-color: rgb(236, 236, 236); |
| border-bottom: 1px solid #8e8e8e; |
| padding: 4px; |
| } |
| |
| content-pane { |
| min-height: 0; |
| display: flex; |
| flex-direction: row; |
| } |
| |
| table-container { |
| flex: 1 1 60%; |
| display: flex; |
| overflow: auto; |
| } |
| #table { |
| flex: 1 1 60%; |
| } |
| |
| right-pane { |
| border-left: 1px solid black; |
| display: flex; |
| flex-direction: column; |
| flex: 1 1 40%; |
| } |
| right-pane > * { |
| margin-bottom: 20px; |
| } |
| |
| #links { |
| min-height: 0; |
| overflow: auto; |
| } |
| </style> |
| <top-controls></top-controls> |
| <content-pane> |
| <table-container> |
| <pi-ui-grouping-table id="table"></pi-ui-grouping-table> |
| </table-container> |
| <right-pane> |
| <div id="script-costs"> |
| Script costs |
| <tr-ui-b-table id="script-costs-table"></tr-ui-b-table> |
| </div> |
| |
| <div id="costs-histogram-container"> |
| Histogram of |
| <select id="costs-histogram-cost-type"></select> values: |
| <tr-ui-u-histogram-span id="costs-histogram"></tr-ui-u-histogram-span> |
| |
| Links |
| <pi-ui-trace-link-list id="links"></pi-ui-trace-link-list> |
| </div> |
| </div> |
| </content-pane> |
| </template> |
| <script> |
| 'use strict'; |
| |
| Polymer({ |
| created: function() { |
| this.mapResults_ = undefined; |
| }, |
| |
| ready: function() { |
| this.$.table.addEventListener( |
| 'selection-changed', this.onSelectionChanged_.bind(this)); |
| |
| var topControls = this.shadowRoot.querySelector('top-controls'); |
| |
| this.groupByThreadName_ = tr.ui.b.createCheckBox( |
| undefined, undefined, |
| 'pi.ui.wr.weather_report.groupByThreadName', true, |
| 'Group by thread name', |
| this.updateContents_.bind(this)); |
| topControls.appendChild(this.groupByThreadName_); |
| |
| this.groupByRAILTypeName_ = tr.ui.b.createCheckBox( |
| undefined, undefined, |
| 'pi.ui.wr.weather_report.groupByRAILTypeName', true, |
| 'Group by RAIL Stage', |
| this.updateContents_.bind(this)); |
| topControls.appendChild(this.groupByRAILTypeName_); |
| |
| this.groupByUserFriendlyCategory_ = tr.ui.b.createCheckBox( |
| undefined, undefined, |
| 'pi.ui.wr.weather_report.groupByUserFriendlyCategory', true, |
| 'Group by Event Category', |
| this.updateContents_.bind(this)); |
| topControls.appendChild(this.groupByUserFriendlyCategory_); |
| |
| this.groupByTitle_ = tr.ui.b.createCheckBox( |
| undefined, undefined, |
| 'pi.ui.wr.weather_report.groupByTitle', false, |
| 'Group by Event Title', |
| this.updateContents_.bind(this)); |
| topControls.appendChild(this.groupByTitle_); |
| |
| this.groupByDomainCategory_ = tr.ui.b.createCheckBox( |
| undefined, undefined, |
| 'pi.ui.wr.weather_report.groupByDomainCategory', true, |
| 'Group by Domain Category', |
| this.updateContents_.bind(this)); |
| topControls.appendChild(this.groupByDomainCategory_); |
| |
| this.groupByDomain_ = tr.ui.b.createCheckBox( |
| undefined, undefined, |
| 'pi.ui.wr.weather_report.groupByDomain', true, |
| 'Group by Domain', |
| this.updateContents_.bind(this)); |
| topControls.appendChild(this.groupByDomain_); |
| |
| this.updateRightPane_(); |
| this.initCostsHistogram_(); |
| }, |
| |
| get mapResults() { |
| return this.mapResults_; |
| }, |
| |
| set mapResults(mapResults) { |
| this.mapResults_ = mapResults; |
| this.updateContents_(); |
| }, |
| |
| onSelectionChanged_: function(event) { |
| this.updateRightPane_(); |
| }, |
| |
| updateRightPane_: function() { |
| this.updateScriptCosts_(); |
| this.updateCostsHistogram_(); |
| var panes = this.shadowRoot.querySelector('right-pane').children; |
| var isFirst = true; |
| for (var i = 0; i < panes.length; i++) { |
| if (getComputedStyle(panes[i]).display === 'none') |
| continue; |
| if (isFirst) { |
| panes[i].style.borderTop = ''; |
| isFirst = false; |
| continue; |
| } |
| panes[i].style.borderTop = '1px solid black'; |
| } |
| }, |
| |
| updateScriptCosts_: function() { |
| var rows = []; |
| var footerRows = []; |
| |
| // Aggregate values. |
| var aggregated = new pi.m.SliceCostInfo(); |
| if (this.$.table.selectedTableRow) { |
| this.$.table.selectedTableRow.data.forEach(function(datum) { |
| aggregated.push(undefined, datum.sliceCostInfo); |
| }); |
| } |
| if (aggregated.jsTime === 0) { |
| this.shadowRoot.querySelector('#script-costs').style.display = 'none'; |
| return; |
| } |
| this.shadowRoot.querySelector('#script-costs').style.display = ''; |
| |
| // Display aggregated data. |
| for (var state in tr.model.source_info.JSSourceState) { |
| var stateName = tr.model.source_info.JSSourceState[state]; |
| rows.push({ |
| label: stateName, |
| value: tr.ui.units.createTimeDurationSpan( |
| aggregated.jsTimeByState[stateName]) |
| }); |
| } |
| footerRows.push({ |
| label: 'JS Time', |
| value: tr.ui.units.createTimeDurationSpan(aggregated.jsTime) |
| }); |
| |
| // Push to table. |
| var scriptCostsTable = this.shadowRoot.querySelector( |
| '#script-costs-table'); |
| scriptCostsTable.tableColumns = [ |
| { |
| title: 'Label', |
| value: function(row) { return row.label; }, |
| width: '150px' |
| }, |
| { |
| title: 'Value', |
| value: function(row) { return row.value; }, |
| width: '100%' |
| } |
| ]; |
| scriptCostsTable.showHeader = false; |
| scriptCostsTable.tableRows = rows; |
| scriptCostsTable.footerRows = footerRows; |
| |
| scriptCostsTable.rebuild(); |
| }, |
| |
| updateContents_: function() { |
| var table = this.$.table; |
| |
| var results = this.mapResults_; |
| if (!results) |
| results = new tr.r.Results(); |
| |
| var columns = this.createColumns_(); |
| table.tableColumns = columns; |
| table.sortColumnIndex = 2; |
| table.sortDescending = true; |
| |
| var allSliceCosts = []; |
| results.allValuesFromFailureFreeRuns.forEach(function(result) { |
| if (result.name != 'wr') |
| return; |
| |
| result.value.sliceCosts.forEach(function(item) { |
| var sliceCostInfo = pi.m.SliceCostInfo.fromDict(item); |
| allSliceCosts.push({ |
| runInfo: result.runInfo, |
| sliceCostInfo: sliceCostInfo |
| }); |
| }); |
| }); |
| |
| var groupBy = []; |
| if (this.groupByThreadName_.checked) { |
| groupBy.push(function(datum) { |
| return datum.sliceCostInfo.threadGroup; |
| }); |
| } |
| if (this.groupByRAILTypeName_.checked) { |
| groupBy.push(function(datum) { |
| return datum.sliceCostInfo.railTypeName; |
| }); |
| } |
| |
| if (this.groupByUserFriendlyCategory_.checked) { |
| groupBy.push(function(datum) { |
| return datum.sliceCostInfo.userFriendlyCategory || 'other'; |
| }); |
| } |
| |
| var didGroupByDomainCategoryOrDomain; |
| if (groupBy.length === 0) { |
| if (this.groupByDomainCategory_.checked) { |
| groupBy.push(function(datum) { |
| return datum.sliceCostInfo.domainCategory; |
| }); |
| didGroupByDomainCategoryOrDomain = true; |
| } |
| if (this.groupByDomain_.checked) { |
| groupBy.push(function(datum) { |
| return datum.sliceCostInfo.domain; |
| }); |
| didGroupByDomainCategoryOrDomain = true; |
| } |
| } |
| |
| if (this.groupByTitle_.checked) { |
| groupBy.push(function(datum) { |
| return datum.sliceCostInfo.title; |
| }); |
| } |
| |
| if (this.groupByDomainCategory_.checked && |
| !didGroupByDomainCategoryOrDomain) { |
| groupBy.push(function(datum) { |
| return datum.sliceCostInfo.domainCategory; |
| }); |
| } |
| |
| if (this.groupByDomain_.checked && !didGroupByDomainCategoryOrDomain) { |
| groupBy.push(function(datum) { |
| return datum.sliceCostInfo.domain; |
| }); |
| } |
| |
| table.selectionMode = tr.ui.b.TableFormat.SelectionMode.ROW; |
| table.groupBy = groupBy; |
| table.dataToGroup = allSliceCosts; |
| table.rebuild(); |
| this.updateRightPane_(); |
| }, |
| |
| createColumns_: function() { |
| var columns = [ |
| { |
| title: 'Title', |
| value: function(row) { |
| return row.title; |
| }, |
| cmp: function(a, b) { |
| return a.title.localeCompare(b.title); |
| }, |
| width: '500px' |
| }, |
| this.createCachingColumn_('Self time (total)', function(datum) { |
| return datum.sliceCostInfo.selfTime; |
| }), |
| this.createCachingColumn_('CPU Self time (total)', function(datum) { |
| return datum.sliceCostInfo.cpuSelfTime; |
| }) |
| ]; |
| return columns; |
| }, |
| |
| createCachingColumn_(title, getDataFunction) { |
| function computeStats(sliceCostInfo) { |
| var sum = tr.b.Statistics.sum(sliceCostInfo, getDataFunction); |
| return sum === undefined ? undefined : |
| tr.ui.units.createTimeDurationSpan(sum); |
| } |
| |
| var column = new pi.ui.CachingColumn(title, computeStats); |
| column.textAlign = 'right'; |
| column.cmp = function(row0, row1) { |
| var value0 = column.value(row0); |
| var value1 = column.value(row1); |
| return tr.b.comparePossiblyUndefinedValues(value0, value1, |
| function(v0, v1) { |
| return v0.duration - v1.duration; |
| }); |
| }; |
| return column; |
| }, |
| |
| initCostsHistogram_: function() { |
| var histogram = this.shadowRoot.querySelector('#costs-histogram'); |
| histogram.addEventListener('brushed-bins-changed', |
| this.onBrushedBinsChanged_.bind(this)); |
| |
| var options = [ |
| { |
| label: 'Self time', |
| value: 'selfTime', |
| func: function(datum) { return datum.sliceCostInfo.selfTime; } |
| }, |
| { |
| label: 'CPU self time', |
| value: 'cpuSelfTime', |
| func: function(datum) { return datum.sliceCostInfo.cpuSelfTime; } |
| }, |
| { |
| label: 'JS time', |
| value: 'jsTime', |
| func: function(datum) { return datum.sliceCostInfo.jsTime; } |
| } |
| ]; |
| for (var state in tr.model.source_info.JSSourceState) { |
| options.push({ |
| label: 'JS time: ' + state, |
| value: 'jsTime.' + state, |
| func: function(datum) { |
| return datum.sliceCostInfo.jsTimeByState[state]; |
| } |
| }); |
| } |
| var oldSelector = this.shadowRoot.querySelector( |
| '#costs-histogram-cost-type'); |
| var newSelector = tr.ui.b.createSelector( |
| this, 'currentSliceReportCostType', |
| 'pi.app_main.currentSliceReportCostType', |
| options[0].value, |
| options); |
| newSelector.id = 'costs-histogram-cost-type'; |
| oldSelector.parentElement.replaceChild(newSelector, oldSelector); |
| }, |
| |
| set currentSliceReportCostType(currentSliceReportCostType) { |
| this.updateCostsHistogram_(); |
| }, |
| |
| updateCostsHistogram_: function() { |
| var container = this.shadowRoot.querySelector( |
| '#costs-histogram-container'); |
| |
| if (this.$.table.selectedTableRow === undefined) { |
| container.style.display = 'none'; |
| return; |
| } |
| |
| container.style.display = ''; |
| |
| var selector = this.shadowRoot.querySelector( |
| '#costs-histogram-cost-type'); |
| var func = selector.selectedItem.func; |
| |
| var histogram = tr.b.u.Histogram.createLinear( |
| tr.b.u.Units.timeDurationInMs, |
| tr.b.Range.fromExplicitRange(0, 100), |
| 100); |
| this.$.table.selectedTableRow.data.forEach(function(datum) { |
| var value = func(datum); |
| histogram.add(value, datum.runInfo); |
| }); |
| |
| var histogramSpan = this.shadowRoot.querySelector('#costs-histogram'); |
| histogramSpan.histogram = histogram; |
| this.onBrushedBinsChanged_(); |
| }, |
| |
| onBrushedBinsChanged_: function() { |
| var histogramSpan = this.shadowRoot.querySelector('#costs-histogram'); |
| var brushedBins = histogramSpan.brushedBins; |
| var urlSet = {}; |
| brushedBins.forEach(function(bin) { |
| bin.sourceInfos.forEach(function(sourceInfo) { |
| urlSet[sourceInfo.url] = 1; |
| }); |
| }); |
| urlSet = Object.keys(urlSet); |
| urlSet.sort(); |
| this.$.links.setTraceUrls(urlSet); |
| } |
| }); |
| </script> |
| </polymer-element> |