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