3 Copyright (c) 2014 The Chromium Authors. All rights reserved.
4 Use of this source code is governed by a BSD-style license that can be
5 found in the LICENSE file.
8 <link rel="import" href="/tracing/analysis/util.html">
9 <link rel="import" href="/tracing/selection.html">
10 <link rel="import" href="/tracing/side_panel/timeline_view.html">
11 <link rel="import" href="/tvcm/iteration_helpers.html">
12 <link rel="import" href="/tvcm/statistics.html">
13 <link rel="import" href="/tvcm/ui/dom_helpers.html">
14 <link rel="import" href="/tvcm/ui/pie_chart.html">
15 <link rel="import" href="/tvcm/ui/sortable_table.html">
16 <link rel="import" href="/tvcm/ui/sunburst_chart.html">
18 <template id="x-sample-summary-panel-template">
20 .x-sample-summary-panel {
25 .x-sample-summary-panel > x-toolbar {
27 border-bottom: 1px solid black;
30 .x-sample-summary-panel > x-left-panel {
39 .x-sample-summary-panel > x-left-panel > result-area {
48 .x-sample-summary-panel > x-left-panel > x-explanation {
56 vertical-align:middle;
61 .x-sample-summary-panel > x-right-panel {
68 .x-sample-summary-panel > x-right-panel td {
75 .x-sample-summary-panel > x-right-panel > x-sequence {
84 .x-sample-summary-panel > x-right-panel > x-sequence table {
88 .x-sample-summary-panel > x-right-panel > x-callees {
98 .x-sample-summary-panel > x-right-panel > x-callees .x-col-numeric {
102 .x-sample-summary-panel > x-right-panel > x-callees .x-td-numeric {
106 .x-sample-summary-panel > x-right-panel > x-callees table {
111 .x-sample-summary-panel > x-right-panel > x-callees {
119 <x-toolbar></x-toolbar>
121 <result-area></result-area>
122 <x-explanation></x-explanation>
125 <x-sequence></x-sequence>
126 <x-callees></x-callees>
133 tvcm.exportTo('tracing', function() {
134 var THIS_DOC = document.currentScript.ownerDocument;
136 var RequestSelectionChangeEvent = tracing.RequestSelectionChangeEvent;
137 var getColorOfKey = tvcm.ui.getColorOfKey;
142 var CallTreeNode = function(name, category) {
143 this.parent = undefined;
145 this.category = category;
149 // Defined only for leaf nodes, leaf_node_id corresponds to
150 // the id of the leaf stack frame.
151 this.leaf_node_id = undefined;
154 CallTreeNode.prototype = {
155 addChild: function(node) {
156 this.children.push(node);
164 var Thread = function(thread) {
165 this.thread = thread;
166 this.rootNode = new CallTreeNode('root', 'root');
167 this.rootCategories = {};
169 this.sfToNode[0] = self.rootNode;
173 getCallTreeNode: function(stackFrame) {
174 if (stackFrame.id in this.sfToNode)
175 return this.sfToNode[stackFrame.id];
177 // Create & save the node.
178 var newNode = new CallTreeNode(stackFrame.title, stackFrame.category);
179 this.sfToNode[stackFrame.id] = newNode;
181 // Add node to parent tree node.
182 if (stackFrame.parentFrame) {
183 var parentNode = this.getCallTreeNode(stackFrame.parentFrame);
184 parentNode.addChild(newNode);
186 // Creating a root category node for each category helps group samples
187 // that may be missing call stacks.
188 var rootCategory = this.rootCategories[stackFrame.category];
191 new CallTreeNode(stackFrame.category, stackFrame.category);
192 this.rootNode.addChild(rootCategory);
193 this.rootCategories[stackFrame.category] = rootCategory;
195 rootCategory.addChild(newNode);
200 addSample: function(sample) {
201 var leaf_node = this.getCallTreeNode(sample.leafStackFrame);
202 leaf_node.leaf_node_id = sample.leafStackFrame.id;
203 leaf_node.selfTime += sample.weight;
207 function genCallTree(node, isRoot) {
209 category: node.category,
211 leaf_node_id: node.leaf_node_id
214 if (isRoot || node.children.length > 0) {
216 for (var c = 0; c < node.children.length; c++)
217 ret.children.push(genCallTree(node.children[c], false));
218 if (node.selfTime > 0.0) {
219 // say, caller (r) calls callee (e) which calls callee2 (2)
220 // and following are the samples
224 // In this case, r has a non-zero self time (4 samples to be precise)
225 // The <self> node makes the representation resemble the following
226 // where s denotes the selftime.
231 // Among the obvious visualization benefit, this also creates the
232 // invariance that a node can not simultaneously have samples and children.
235 category: ret.category,
237 leaf_node_id: node.leaf_node_id
239 delete ret.leaf_node_id; // ret is not a leaf node anymore.
243 ret.size = node.selfTime;
250 function getSampleTypes(selection) {
252 var samples = selection.getEventsOrganizedByType().samples;
253 for (var i = 0; i < samples.length; i++) {
254 sampleDict[samples[i].title] = null;
256 return Object.keys(sampleDict);
259 // Create sunburst data from the selection.
260 function createSunburstData(selection, sampleType) {
262 function getOrCreateThread(thread) {
264 if (thread.tid in threads) {
265 ret = threads[thread.tid];
267 ret = new Thread(thread);
268 threads[thread.tid] = ret;
274 var samples = selection.getEventsOrganizedByType().samples;
275 for (var i = 0; i < samples.length; i++) {
276 var sample = samples[i];
277 if (sample.title == sampleType)
278 getOrCreateThread(sample.thread).addSample(sample);
281 // Generate sunburst data.
283 name: '<All Threads>',
287 for (var t in threads) {
288 if (!threads.hasOwnProperty(t)) continue;
289 var thread = threads[t];
291 name: 'Thread ' + thread.thread.tid + ': ' + thread.thread.name,
293 children: genCallTree(thread.rootNode, true)
295 sunburstData.children.push(threadData);
303 var SamplingSummaryPanel =
304 tvcm.ui.define('x-sample-summary-panel',
305 tracing.TimelineViewSidePanel);
306 SamplingSummaryPanel.textLabel = 'Sampling Summary';
307 SamplingSummaryPanel.supportsModel = function(m) {
308 if (m == undefined) {
311 reason: 'Unknown tracing model'
315 if (m.samples.length == 0) {
318 reason: 'No sampling data in trace'
327 // Return a dict with keys as stack-frame ids
328 // and values as selection objects which contain all the
329 // samples whose leaf stack-frame id matches the key.
330 function divideSamplesBasedOnLeafStackFrame(selection) {
331 var stackFrameIdToSamples = {};
332 for (var i = 0; i < selection.length; ++i) {
333 var sample = selection[i];
334 var id = sample.leafStackFrame.id;
335 if (!stackFrameIdToSamples[id])
336 stackFrameIdToSamples[id] = new tracing.Selection();
337 stackFrameIdToSamples[id].push(sample);
339 return stackFrameIdToSamples;
342 SamplingSummaryPanel.prototype = {
343 __proto__: tracing.TimelineViewSidePanel.prototype,
345 decorate: function() {
346 tracing.TimelineViewSidePanel.prototype.decorate.call(this);
347 this.classList.add('x-sample-summary-panel');
348 this.appendChild(tvcm.instantiateTemplate(
349 '#x-sample-summary-panel-template', THIS_DOC));
351 this.sampleType_ = undefined;
352 this.sampleTypeSelector_ = undefined;
353 this.chart_ = undefined;
354 this.selection_ = undefined;
358 return this.selection_;
361 set selection(selection) {
362 this.selection_ = selection;
363 this.stackFrameIdToSamples_ = divideSamplesBasedOnLeafStackFrame(selection);
364 this.updateContents_();
368 return this.sampleType_;
371 set sampleType(type) {
372 this.sampleType_ = type;
373 if (this.sampleTypeSelector_)
374 this.sampleTypeSelector_.selectedValue = type;
375 this.updateResultArea_();
378 updateCallees_: function(d) {
379 // Update callee table.
381 var table = document.createElement('table');
383 // Add column styles.
384 var col0 = document.createElement('col');
385 var col1 = document.createElement('col');
386 col0.className += 'x-col-numeric';
387 col1.className += 'x-col-numeric';
388 table.appendChild(col0);
389 table.appendChild(col1);
392 var thead = table.createTHead();
393 var headerRow = thead.insertRow(0);
394 headerRow.style.backgroundColor = '#888';
395 headerRow.insertCell(0).appendChild(document.createTextNode('Samples'));
396 headerRow.insertCell(1).appendChild(document.createTextNode('Percent'));
397 headerRow.insertCell(2).appendChild(document.createTextNode('Symbol'));
400 var tbody = table.createTBody();
402 for (var i = 0; i < d.children.length; i++) {
403 var c = d.children[i];
404 var row = tbody.insertRow(i);
405 var bgColor = getColorOfKey(c.category);
406 if (bgColor == undefined)
408 row.style.backgroundColor = bgColor;
409 var cell0 = row.insertCell(0);
410 var cell1 = row.insertCell(1);
411 var cell2 = row.insertCell(2);
412 cell0.className += 'x-td-numeric';
413 cell1.className += 'x-td-numeric';
414 cell0.appendChild(document.createTextNode(c.value.toString()));
415 cell1.appendChild(document.createTextNode(
416 (100 * c.value / d.value).toFixed(2) + '%'));
417 cell2.appendChild(document.createTextNode(c.name));
422 tvcm.ui.SortableTable.decorate(table);
424 var calleeArea = that.querySelector('x-callees');
425 calleeArea.textContent = '';
426 calleeArea.appendChild(table);
429 updateHighlight_: function(d) {
432 // Update explanation.
434 if (that.chart_.selectedNode != null)
435 percent = 100.0 * d.value / that.chart_.selectedNode.value;
436 that.querySelector('x-explanation').innerHTML =
437 d.value + '<br>' + percent.toFixed(2) + '%';
439 // Update call stack table.
440 var table = document.createElement('table');
441 var thead = table.createTHead();
442 var tbody = table.createTBody();
443 var headerRow = thead.insertRow(0);
444 headerRow.style.backgroundColor = '#888';
445 headerRow.insertCell(0).appendChild(
446 document.createTextNode('Call Stack'));
450 while (frame && frame.id) {
451 callStack.push(frame);
452 frame = frame.parent;
455 for (var i = 0; i < callStack.length; i++) {
456 var row = tbody.insertRow(i);
457 var bgColor = getColorOfKey(callStack[i].category);
458 if (bgColor == undefined)
460 row.style.backgroundColor = bgColor;
462 row.style.fontWeight = 'bold';
463 row.insertCell(0).appendChild(
464 document.createTextNode(callStack[i].name));
467 var sequenceArea = that.querySelector('x-sequence');
468 sequenceArea.textContent = '';
469 sequenceArea.appendChild(table);
472 getSamplesFromNode_: function(node) {
473 // A node has samples associated with it, if it's a leaf node.
474 var selection = new tracing.Selection();
475 if (node.leaf_node_id !== undefined) {
476 selection.addSelection(this.stackFrameIdToSamples_[node.leaf_node_id]);
478 else if (node.children === undefined ||
479 node.children.length === 0) {
480 throw new Error('A node should either have samples, or children');
483 for (var i = 0; i < node.children.length; ++i)
484 selection.addSelection(this.getSamplesFromNode_(node.children[i]));
489 updateResultArea_: function() {
490 if (this.selection_ === undefined)
493 var resultArea = this.querySelector('result-area');
494 this.chart_ = undefined;
495 resultArea.textContent = '';
498 createSunburstData(this.selection_, this.sampleType_);
499 this.chart_ = new tvcm.ui.SunburstChart();
500 this.chart_.width = 600;
501 this.chart_.height = 600;
502 this.chart_.chartTitle = 'Sampling Summary';
504 this.chart_.addEventListener('node-selected', (function(e) {
505 this.updateCallees_(e.node);
508 this.chart_.addEventListener('node-clicked', (function(e) {
509 var event = new RequestSelectionChangeEvent();
510 var depth = e.node.depth;
511 if (e.node.name === '<self>')
513 event.selection = this.getSamplesFromNode_(e.node);
514 event.selection.sunburst_zoom_level = depth;
515 this.dispatchEvent(event);
518 this.chart_.addEventListener('node-highlighted', (function(e) {
519 this.updateHighlight_(e.node);
526 resultArea.appendChild(this.chart_);
527 this.chart_.setSize(this.chart_.getMinSize());
529 if (this.selection_.sunburst_zoom_level !== undefined) {
530 this.chart_.zoomToDepth(this.selection_.sunburst_zoom_level);
534 updateContents_: function() {
535 if (this.selection_ === undefined || this.selection_.length == 0)
538 // Get available sample types in range.
539 var sampleTypes = getSampleTypes(this.selection_);
540 if (sampleTypes.indexOf(this.sampleType_) == -1)
541 this.sampleType_ = sampleTypes[0];
543 // Create sample type dropdown.
544 var sampleTypeOptions = [];
545 for (var i = 0; i < sampleTypes.length; i++)
546 sampleTypeOptions.push({label: sampleTypes[i], value: sampleTypes[i]});
548 var toolbarEl = this.querySelector('x-toolbar');
549 this.sampleTypeSelector_ = tvcm.ui.createSelector(
552 'samplingSummaryPanel.sampleType',
555 toolbarEl.textContent = 'Sample Type: ';
556 toolbarEl.appendChild(this.sampleTypeSelector_);
561 SamplingSummaryPanel: SamplingSummaryPanel,
562 createSunburstData: createSunburstData