diff options
Diffstat (limited to 'chromium/third_party/catapult/tracing/tracing/ui/analysis')
104 files changed, 23038 insertions, 0 deletions
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/alert_sub_view.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/alert_sub_view.html new file mode 100644 index 00000000000..b44741aace1 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/alert_sub_view.html @@ -0,0 +1,181 @@ +<!DOCTYPE html> +<!-- +Copyright 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/base/base.html"> +<link rel="import" href="/tracing/base/utils.html"> +<link rel="import" href="/tracing/model/event_set.html"> +<link rel="import" href="/tracing/ui/analysis/analysis_link.html"> +<link rel="import" href="/tracing/ui/analysis/analysis_sub_view.html"> +<link rel="import" href="/tracing/ui/base/dom_helpers.html"> +<link rel="import" href="/tracing/ui/base/table.html"> +<link rel="import" href="/tracing/ui/base/ui.html"> + +<dom-module id='tr-ui-a-alert-sub-view'> + <template> + <style> + :host { + display: flex; + flex-direction: column; + } + #table { + flex: 1 1 auto; + align-self: stretch; + font-size: 12px; + } + </style> + <tr-ui-b-table id="table"> + </tr-ui-b-table> + </template> +</dom-module> +<script> +'use strict'; + +Polymer({ + is: 'tr-ui-a-alert-sub-view', + behaviors: [tr.ui.analysis.AnalysisSubView], + + ready() { + this.currentSelection_ = undefined; + this.$.table.tableColumns = [ + { + title: 'Label', + value(row) { return row.name; }, + width: '150px' + }, + { + title: 'Value', + width: '100%', + value(row) { return row.value; } + } + ]; + this.$.table.showHeader = false; + }, + + get selection() { + return this.currentSelection_; + }, + + set selection(selection) { + this.currentSelection_ = selection; + this.updateContents_(); + }, + + getRowsForSingleAlert_(alert) { + const rows = []; + + // Arguments + for (const argName in alert.args) { + const argView = + document.createElement('tr-ui-a-generic-object-view'); + argView.object = alert.args[argName]; + rows.push({ name: argName, value: argView }); + } + + // Associated events + if (alert.associatedEvents.length) { + alert.associatedEvents.forEach(function(event, i) { + const linkEl = document.createElement('tr-ui-a-analysis-link'); + linkEl.setSelectionAndContent( + new tr.model.EventSet(event), event.title); + + let valueString = ''; + if (event instanceof tr.model.TimedEvent) { + valueString = 'took ' + event.duration.toFixed(2) + 'ms'; + } + + rows.push({ + name: linkEl, + value: valueString + }); + }); + } + + // Description + const descriptionEl = tr.ui.b.createDiv({ + textContent: alert.info.description, + maxWidth: '300px' + }); + rows.push({ + name: 'Description', + value: descriptionEl + }); + + // Additional Reading Links + if (alert.info.docLinks) { + alert.info.docLinks.forEach(function(linkObject) { + const linkEl = document.createElement('a'); + linkEl.target = '_blank'; + linkEl.href = linkObject.href; + Polymer.dom(linkEl).textContent = Polymer.dom(linkObject).textContent; + rows.push({ + name: linkObject.label, + value: linkEl + }); + }); + } + return rows; + }, + + getRowsForAlerts_(alerts) { + if (alerts.length === 1) { + const rows = [{ + name: 'Alert', + value: tr.b.getOnlyElement(alerts).title + }]; + const detailRows = this.getRowsForSingleAlert_(tr.b.getOnlyElement( + alerts)); + rows.push.apply(rows, detailRows); + return rows; + } + return alerts.map(function(alert) { + return { + name: 'Alert', + value: alert.title, + isExpanded: alerts.size < 10, // This is somewhat arbitrary for now. + subRows: this.getRowsForSingleAlert_(alert) + }; + }, this); + }, + + updateContents_() { + if (this.currentSelection_ === undefined) { + this.$.table.rows = []; + this.$.table.rebuild(); + return; + } + + const alerts = this.currentSelection_; + this.$.table.tableRows = this.getRowsForAlerts_(alerts); + this.$.table.rebuild(); + }, + + get relatedEventsToHighlight() { + if (!this.currentSelection_) return undefined; + const result = new tr.model.EventSet(); + for (const event of this.currentSelection_) { + result.addEventSet(event.associatedEvents); + } + return result; + } +}); + +tr.ui.analysis.AnalysisSubView.register( + 'tr-ui-a-alert-sub-view', + tr.model.Alert, + { + multi: false, + title: 'Alert', + }); + +tr.ui.analysis.AnalysisSubView.register( + 'tr-ui-a-alert-sub-view', + tr.model.Alert, + { + multi: true, + title: 'Alerts', + }); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/alert_sub_view_test.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/alert_sub_view_test.html new file mode 100644 index 00000000000..574cf5f0b86 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/alert_sub_view_test.html @@ -0,0 +1,82 @@ +<!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/base/utils.html"> +<link rel="import" href="/tracing/core/test_utils.html"> +<link rel="import" href="/tracing/model/event_set.html"> +<link rel="import" href="/tracing/model/model.html"> +<link rel="import" href="/tracing/ui/analysis/analysis_view.html"> +<link rel="import" href="/tracing/ui/base/deep_utils.html"> + +<script> +'use strict'; + +tr.b.unittest.testSuite(function() { + const newSliceEx = tr.c.TestUtils.newSliceEx; + + test('instantiate', function() { + const slice = newSliceEx({title: 'b', start: 0, duration: 0.002}); + + const alertInfo = new tr.model.EventInfo( + 'alertInfo', 'Critical alert', + [{ + label: 'Project Page', + textContent: 'Trace-Viewer Github Project', + href: 'https://github.com/google/trace-viewer/' + }]); + + const alert = new tr.model.Alert(alertInfo, 5, [slice]); + assert.strictEqual(1, alert.associatedEvents.length); + + const subView = document.createElement('tr-ui-a-alert-sub-view'); + subView.selection = new tr.model.EventSet(alert); + assert.isTrue( + subView.relatedEventsToHighlight.equals(alert.associatedEvents)); + this.addHTMLOutput(subView); + + const table = tr.ui.b.findDeepElementMatching( + subView, 'tr-ui-b-table'); + + const rows = table.tableRows; + const columns = table.tableColumns; + assert.lengthOf(rows, 4); + assert.lengthOf(columns, 2); + }); + + test('instantiate_twoAlertsWithRelatedEvents', function() { + const slice1 = newSliceEx({title: 'b', start: 0, duration: 0.002}); + const slice2 = newSliceEx({title: 'b', start: 1, duration: 0.002}); + + const alertInfo1 = new tr.model.EventInfo( + 'alertInfo1', 'Critical alert', + [{ + label: 'Project Page', + textContent: 'Trace-Viewer Github Project', + href: 'https://github.com/google/trace-viewer/' + }]); + + const alertInfo2 = new tr.model.EventInfo( + 'alertInfo2', 'Critical alert', + [{ + label: 'Google Homepage', + textContent: 'Google Search Page', + href: 'http://www.google.com' + }]); + + const alert1 = new tr.model.Alert(alertInfo1, 5, [slice1]); + const alert2 = new tr.model.Alert(alertInfo2, 5, [slice2]); + + const subView = document.createElement('tr-ui-a-alert-sub-view'); + subView.selection = new tr.model.EventSet([alert1, alert2]); + assert.isTrue(subView.relatedEventsToHighlight.equals( + new tr.model.EventSet([ + tr.b.getOnlyElement(alert1.associatedEvents), + tr.b.getOnlyElement(alert2.associatedEvents) + ]))); + }); +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/analysis_link.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/analysis_link.html new file mode 100644 index 00000000000..8d996afeeb9 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/analysis_link.html @@ -0,0 +1,147 @@ +<!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/model/event_set.html"> +<link rel="import" href="/tracing/ui/base/ui.html"> +<link rel="import" href="/tracing/ui/brushing_state_controller.html"> + +<dom-module id='tr-ui-a-analysis-link'> + <template> + <style> + :host { + display: inline; + cursor: pointer; + cursor: pointer; + white-space: nowrap; + } + a { + text-decoration: underline; + } + </style> + <a href="{{href}}" on-click="onClicked_" on-mouseenter="onMouseEnter_" on-mouseleave="onMouseLeave_"><slot></slot></a> + + </template> +</dom-module> +<script> +'use strict'; + +Polymer({ + is: 'tr-ui-a-analysis-link', + + properties: { + href: { + type: String + } + }, + + listeners: { + 'click': 'onClicked_', + 'mouseenter': 'onMouseEnter_', + 'mouseleave': 'onMouseLeave_' + }, + + ready() { + this.selection_ = undefined; + }, + + attached() { + // Save an instance of the controller since it's going to be used in + // detached() where it can no longer be obtained. + this.controller_ = + tr.c.BrushingStateController.getControllerForElement(this); + }, + + detached() { + // Reset highlights. + this.clearHighlight_(); + this.controller_ = undefined; + }, + + set color(c) { + this.style.color = c; + }, + + /** + * @return {*|function():*} + */ + get selection() { + return this.selection_; + }, + + /** + * |selection| can be anything except a function, or else a function that + * can return anything. + * + * In the context of trace_viewer, |selection| is typically an EventSet, + * whose events will be highlighted by trace_viewer when this link is + * clicked or mouse-entered. + * + * If |selection| is not a function, then it will be dispatched to this + * link's embedder via a RequestSelectionChangeEvent when this link is + * clicked or mouse-entered. + * + * If |selection| is a function, then it will be called when this link is + * clicked or mouse-entered, and its result will be dispatched to this + * link's embedder via a RequestSelectionChangeEvent. + * + * @param {*|function():*} selection + */ + set selection(selection) { + this.selection_ = selection; + Polymer.dom(this).textContent = selection.userFriendlyName; + }, + + setSelectionAndContent(selection, opt_textContent) { + this.selection_ = selection; + if (opt_textContent) { + Polymer.dom(this).textContent = opt_textContent; + } + }, + + /** + * If |selection| is a function, call it and return the result. + * Otherwise return |selection| directly. + * + * @return {*} + */ + getCurrentSelection_() { + // Gets the current selection, invoking the selection function if needed. + if (typeof this.selection_ === 'function') { + return this.selection_(); + } + return this.selection_; + }, + + setHighlight_(opt_eventSet) { + if (this.controller_) { + this.controller_.changeAnalysisLinkHoveredEvents(opt_eventSet); + } + }, + + clearHighlight_(opt_eventSet) { + this.setHighlight_(); + }, + + onClicked_(clickEvent) { + if (!this.selection_) return; + + clickEvent.stopPropagation(); + + const event = new tr.model.RequestSelectionChangeEvent(); + event.selection = this.getCurrentSelection_(); + this.dispatchEvent(event); + }, + + onMouseEnter_() { + this.setHighlight_(this.getCurrentSelection_()); + }, + + onMouseLeave_() { + this.clearHighlight_(); + } +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/analysis_link_test.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/analysis_link_test.html new file mode 100644 index 00000000000..e8caed8f601 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/analysis_link_test.html @@ -0,0 +1,58 @@ +<!DOCTYPE html> +<!-- +Copyright (c) 2014 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/model/event_set.html"> +<link rel="import" href="/tracing/model/model.html"> +<link rel="import" href="/tracing/ui/analysis/analysis_link.html"> + +<script> +'use strict'; + +tr.b.unittest.testSuite(function() { + test('testBasic', function() { + const link = document.createElement('tr-ui-a-analysis-link'); + + const i10 = new tr.model.ObjectInstance( + {}, '0x1000', 'cat', 'name', 10); + const s10 = i10.addSnapshot(10, {foo: 1}); + + link.selection = new tr.model.EventSet(s10); + this.addHTMLOutput(link); + + let didRSC = false; + link.addEventListener('requestSelectionChange', function(e) { + didRSC = true; + assert.isTrue(e.selection.equals(new tr.model.EventSet(s10))); + }); + link.click(); + assert.isTrue(didRSC); + }); + + test('testGeneratorVersion', function() { + const link = document.createElement('tr-ui-a-analysis-link'); + + const i10 = new tr.model.ObjectInstance( + {}, '0x1000', 'cat', 'name', 10); + const s10 = i10.addSnapshot(10, {foo: 1}); + + function selectionGenerator() { + return new tr.model.EventSet(s10); + } + selectionGenerator.userFriendlyName = 'hello world'; + link.selection = selectionGenerator; + this.addHTMLOutput(link); + + let didRSC = false; + link.addEventListener('requestSelectionChange', function(e) { + assert.isTrue(e.selection.equals(new tr.model.EventSet(s10))); + didRSC = true; + }); + link.click(); + assert.isTrue(didRSC); + }); +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/analysis_sub_view.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/analysis_sub_view.html new file mode 100644 index 00000000000..8bd967c8c75 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/analysis_sub_view.html @@ -0,0 +1,266 @@ +<!DOCTYPE html> +<!-- +Copyright 2014 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/base.html"> +<link rel="import" href="/tracing/base/utils.html"> +<link rel="import" href="/tracing/model/event_set.html"> + +<!-- +@fileoverview Polymer element for various analysis sub-views. +--> +<script> +'use strict'; + +tr.exportTo('tr.ui.analysis', function() { + const AnalysisSubView = { + set tabLabel(label) { + Polymer.dom(this).setAttribute('tab-label', label); + }, + + get tabLabel() { + return this.getAttribute('tab-label'); + }, + + get requiresTallView() { + return false; + }, + + get relatedEventsToHighlight() { + return undefined; + }, + + /** + * Each element extending this one must implement + * a 'selection' property. + */ + set selection(selection) { + throw new Error('Not implemented!'); + }, + + get selection() { + throw new Error('Not implemented!'); + } + }; + + // Basic registry. + const allTypeInfosByEventProto = new Map(); + let onlyRootTypeInfosByEventProto = undefined; + let eventProtoToRootTypeInfoMap = undefined; + + function AnalysisSubViewTypeInfo(eventConstructor, options) { + if (options.multi === undefined) { + throw new Error('missing field: multi'); + } + if (options.title === undefined) { + throw new Error('missing field: title'); + } + this.eventConstructor = eventConstructor; + + this.singleTagName = undefined; + this.singleTitle = undefined; + + this.multiTagName = undefined; + this.multiTitle = undefined; + + // This is computed by rebuildRootSubViewTypeInfos, so don't muck with it! + this.childrenTypeInfos_ = undefined; + } + + AnalysisSubViewTypeInfo.prototype = { + get childrenTypeInfos() { + return this.childrenTypeInfos_; + }, + + resetchildrenTypeInfos() { + this.childrenTypeInfos_ = []; + } + }; + + AnalysisSubView.register = function(tagName, eventConstructor, options) { + let typeInfo = allTypeInfosByEventProto.get(eventConstructor.prototype); + if (typeInfo === undefined) { + typeInfo = new AnalysisSubViewTypeInfo(eventConstructor, options); + allTypeInfosByEventProto.set(typeInfo.eventConstructor.prototype, + typeInfo); + + onlyRootTypeInfosByEventProto = undefined; + } + + if (!options.multi) { + if (typeInfo.singleTagName !== undefined) { + throw new Error('SingleTagName already set'); + } + typeInfo.singleTagName = tagName; + typeInfo.singleTitle = options.title; + } else { + if (typeInfo.multiTagName !== undefined) { + throw new Error('MultiTagName already set'); + } + typeInfo.multiTagName = tagName; + typeInfo.multiTitle = options.title; + } + return typeInfo; + }; + + function rebuildRootSubViewTypeInfos() { + onlyRootTypeInfosByEventProto = new Map(); + allTypeInfosByEventProto.forEach(function(typeInfo) { + typeInfo.resetchildrenTypeInfos(); + }); + + // Find all root typeInfos. + allTypeInfosByEventProto.forEach(function(typeInfo, eventProto) { + const eventPrototype = typeInfo.eventConstructor.prototype; + + let lastEventProto = eventPrototype; + let curEventProto = eventPrototype.__proto__; + while (true) { + if (!allTypeInfosByEventProto.has(curEventProto)) { + const rootTypeInfo = allTypeInfosByEventProto.get(lastEventProto); + const rootEventProto = lastEventProto; + + const isNew = onlyRootTypeInfosByEventProto.has(rootEventProto); + onlyRootTypeInfosByEventProto.set(rootEventProto, + rootTypeInfo); + break; + } + + lastEventProto = curEventProto; + curEventProto = curEventProto.__proto__; + } + }); + + // Build the childrenTypeInfos array. + allTypeInfosByEventProto.forEach(function(typeInfo, eventProto) { + const eventPrototype = typeInfo.eventConstructor.prototype; + const parentEventProto = eventPrototype.__proto__; + const parentTypeInfo = allTypeInfosByEventProto.get(parentEventProto); + if (!parentTypeInfo) return; + parentTypeInfo.childrenTypeInfos.push(typeInfo); + }); + + // Build the eventProto to rootTypeInfo map. + eventProtoToRootTypeInfoMap = new Map(); + allTypeInfosByEventProto.forEach(function(typeInfo, eventProto) { + const eventPrototype = typeInfo.eventConstructor.prototype; + + let curEventProto = eventPrototype; + while (true) { + if (onlyRootTypeInfosByEventProto.has(curEventProto)) { + const rootTypeInfo = onlyRootTypeInfosByEventProto.get( + curEventProto); + eventProtoToRootTypeInfoMap.set(eventPrototype, + rootTypeInfo); + break; + } + curEventProto = curEventProto.__proto__; + } + }); + } + + function findLowestTypeInfoForEvents(thisTypeInfo, events) { + if (events.length === 0) return thisTypeInfo; + const event0 = tr.b.getFirstElement(events); + + let candidateSubTypeInfo; + for (let i = 0; i < thisTypeInfo.childrenTypeInfos.length; i++) { + const childTypeInfo = thisTypeInfo.childrenTypeInfos[i]; + if (event0 instanceof childTypeInfo.eventConstructor) { + candidateSubTypeInfo = childTypeInfo; + break; + } + } + if (!candidateSubTypeInfo) return thisTypeInfo; + + // Validate that all the other events are instances of the candidate type. + let allMatch = true; + for (const event of events) { + if (event instanceof candidateSubTypeInfo.eventConstructor) continue; + allMatch = false; + break; + } + + if (!allMatch) { + return thisTypeInfo; + } + + return findLowestTypeInfoForEvents(candidateSubTypeInfo, events); + } + + const primaryEventProtoToTypeInfoMap = new Map(); + function getRootTypeInfoForEvent(event) { + const curProto = event.__proto__; + const typeInfo = primaryEventProtoToTypeInfoMap.get(curProto); + if (typeInfo) return typeInfo; + return getRootTypeInfoForEventSlow(event); + } + + function getRootTypeInfoForEventSlow(event) { + let typeInfo; + let curProto = event.__proto__; + while (true) { + if (curProto === Object.prototype) { + throw new Error('No view registered for ' + event.toString()); + } + typeInfo = onlyRootTypeInfosByEventProto.get(curProto); + if (typeInfo) { + primaryEventProtoToTypeInfoMap.set(event.__proto__, typeInfo); + return typeInfo; + } + curProto = curProto.__proto__; + } + } + + AnalysisSubView.getEventsOrganizedByTypeInfo = function(selection) { + if (onlyRootTypeInfosByEventProto === undefined) { + rebuildRootSubViewTypeInfos(); + } + + // Base grouping. + const eventsByRootTypeInfo = tr.b.groupIntoMap( + selection, + function(event) { + return getRootTypeInfoForEvent(event); + }, + this, tr.model.EventSet); + + // Now, try to lower the typeinfo to the most specific type that still + // encompasses the event group. + // + // For instance, if we have 3 ThreadSlices, and all three are V8 slices, + // then we can convert this to use the V8Slices's typeinfos. But, if one + // of those slices was not a V8Slice, then we must still use + // ThreadSlice. + // + // The reason for this is for the confusion that might arise from the + // alternative. Suppose you click on a set of mixed slices, we want to show + // you the most correct information, and let you navigate to . If we instead + // showed you a V8 slices tab, and a Slices tab, we present the user with an + // ambiguity: is the V8 slice also in the Slices tab? Or is it not? Better, + // we think, to just only ever show an event in one place at a time, and + // avoid the possible confusion. + const eventsByLowestTypeInfo = new Map(); + eventsByRootTypeInfo.forEach(function(events, typeInfo) { + const lowestTypeInfo = findLowestTypeInfoForEvents(typeInfo, events); + eventsByLowestTypeInfo.set(lowestTypeInfo, events); + }); + + return eventsByLowestTypeInfo; + }; + + return { + AnalysisSubView, + AnalysisSubViewTypeInfo, + }; +}); + +// Dummy element for testing +Polymer({ + is: 'tr-ui-a-sub-view', + behaviors: [tr.ui.analysis.AnalysisSubView] +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/analysis_sub_view_test.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/analysis_sub_view_test.html new file mode 100644 index 00000000000..0f3e85ea4ec --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/analysis_sub_view_test.html @@ -0,0 +1,35 @@ +<!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/model/event_set.html"> +<link rel="import" href="/tracing/ui/analysis/analysis_sub_view.html"> + +<script> +'use strict'; + +tr.b.unittest.testSuite(function() { + test('subViewThrowsNotImplementedErrors', function() { + const subView = document.createElement('tr-ui-a-sub-view'); + + assert.throw(function() { + subView.selection = new tr.model.EventSet(); + }, 'Not implemented!'); + + assert.throw(function() { + const viewSelection = subView.selection; + }, 'Not implemented!'); + + subView.tabLabel = 'Tab Label'; + assert.strictEqual(subView.getAttribute('tab-label'), 'Tab Label'); + assert.strictEqual(subView.tabLabel, 'Tab Label'); + + subView.tabLabel = 'New Label'; + assert.strictEqual(subView.getAttribute('tab-label'), 'New Label'); + assert.strictEqual(subView.tabLabel, 'New Label'); + }); +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/analysis_view.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/analysis_view.html new file mode 100644 index 00000000000..edc14edca11 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/analysis_view.html @@ -0,0 +1,207 @@ +<!DOCTYPE html> +<!-- +Copyright (c) 2014 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/utils.html"> +<link rel="import" href="/tracing/model/event_set.html"> +<link rel="import" href="/tracing/ui/analysis/alert_sub_view.html"> +<link rel="import" href="/tracing/ui/analysis/analysis_sub_view.html"> +<link rel="import" + href="/tracing/ui/analysis/container_memory_dump_sub_view.html"> +<link rel="import" href="/tracing/ui/analysis/counter_sample_sub_view.html"> +<link rel="import" href="/tracing/ui/analysis/multi_async_slice_sub_view.html"> +<link rel="import" href="/tracing/ui/analysis/multi_cpu_slice_sub_view.html"> +<link rel="import" href="/tracing/ui/analysis/multi_flow_event_sub_view.html"> +<link rel="import" href="/tracing/ui/analysis/multi_frame_sub_view.html"> +<link rel="import" + href="/tracing/ui/analysis/multi_instant_event_sub_view.html"> +<link rel="import" href="/tracing/ui/analysis/multi_object_sub_view.html"> +<link rel="import" href="/tracing/ui/analysis/multi_power_sample_sub_view.html"> +<link rel="import" href="/tracing/ui/analysis/multi_sample_sub_view.html"> +<link rel="import" href="/tracing/ui/analysis/multi_thread_slice_sub_view.html"> +<link rel="import" + href="/tracing/ui/analysis/multi_thread_time_slice_sub_view.html"> +<link rel="import" + href="/tracing/ui/analysis/multi_user_expectation_sub_view.html"> +<link rel="import" href="/tracing/ui/analysis/single_async_slice_sub_view.html"> +<link rel="import" href="/tracing/ui/analysis/single_cpu_slice_sub_view.html"> +<link rel="import" href="/tracing/ui/analysis/single_flow_event_sub_view.html"> +<link rel="import" href="/tracing/ui/analysis/single_frame_sub_view.html"> +<link rel="import" + href="/tracing/ui/analysis/single_instant_event_sub_view.html"> +<link rel="import" + href="/tracing/ui/analysis/single_object_instance_sub_view.html"> +<link rel="import" + href="/tracing/ui/analysis/single_object_snapshot_sub_view.html"> +<link rel="import" + href="/tracing/ui/analysis/single_power_sample_sub_view.html"> +<link rel="import" href="/tracing/ui/analysis/single_sample_sub_view.html"> +<link rel="import" + href="/tracing/ui/analysis/single_thread_slice_sub_view.html"> +<link rel="import" + href="/tracing/ui/analysis/single_thread_time_slice_sub_view.html"> +<link rel="import" + href="/tracing/ui/analysis/single_user_expectation_sub_view.html"> +<link rel="import" href="/tracing/ui/base/tab_view.html"> + +<!-- +@fileoverview A component used to display an analysis of a selection, +using custom elements specialized for different event types. +--> +<dom-module id='tr-ui-a-analysis-view'> + <template> + <style> + :host { + background-color: white; + display: flex; + flex-direction: column; + height: 275px; + overflow: auto; + } + + :host(.tall-mode) { + height: 525px; + } + </style> + <slot></slot> + </template> +</dom-module> +<script> +'use strict'; +(function() { + const EventRegistry = tr.model.EventRegistry; + + /** Returns the label that goes next to the list of tabs. */ + function getTabStripLabel(numEvents) { + if (numEvents === 0) { + return 'Nothing selected. Tap stuff.'; + } else if (numEvents === 1) { + return '1 item selected.'; + } + return numEvents + ' items selected.'; + } + + function createSubView(subViewTypeInfo, selection) { + let tagName; + if (selection.length === 1) { + tagName = subViewTypeInfo.singleTagName; + } else { + tagName = subViewTypeInfo.multiTagName; + } + + if (tagName === undefined) { + throw new Error('No view registered for ' + + subViewTypeInfo.eventConstructor.name); + } + const subView = document.createElement(tagName); + + let title; + if (selection.length === 1) { + title = subViewTypeInfo.singleTitle; + } else { + title = subViewTypeInfo.multiTitle; + } + title += ' (' + selection.length + ')'; + subView.tabLabel = title; + + subView.selection = selection; + return subView; + } + + Polymer({ + is: 'tr-ui-a-analysis-view', + + ready() { + this.brushingStateController_ = undefined; + this.lastSelection_ = undefined; + this.tabView_ = document.createElement('tr-ui-b-tab-view'); + this.tabView_.addEventListener( + 'selected-tab-change', this.onSelectedSubViewChanged_.bind(this)); + + Polymer.dom(this).appendChild(this.tabView_); + }, + + set tallMode(value) { + Polymer.dom(this).classList.toggle('tall-mode', value); + }, + + get tallMode() { + return Polymer.dom(this).classList.contains('tall-mode'); + }, + + get tabView() { + return this.tabView_; + }, + + get brushingStateController() { + return this.brushingStateController_; + }, + + set brushingStateController(brushingStateController) { + if (this.brushingStateController_) { + this.brushingStateController_.removeEventListener( + 'change', this.onSelectionChanged_.bind(this)); + } + + this.brushingStateController_ = brushingStateController; + if (this.brushingStateController) { + this.brushingStateController_.addEventListener( + 'change', this.onSelectionChanged_.bind(this)); + } + + // The new brushing controller may have a different selection than the + // last one, so we have to refresh the subview. + this.onSelectionChanged_(); + }, + + get selection() { + return this.brushingStateController_.selection; + }, + + onSelectionChanged_(e) { + if (this.lastSelection_ && this.selection.equals(this.lastSelection_)) { + return; + } + this.lastSelection_ = this.selection; + + this.tallMode = false; + + this.tabView_.label = getTabStripLabel(this.selection.length); + const eventsByBaseTypeName = + this.selection.getEventsOrganizedByBaseType(true); + + const ASV = tr.ui.analysis.AnalysisSubView; + const eventsByTagName = ASV.getEventsOrganizedByTypeInfo(this.selection); + const newSubViews = []; + eventsByTagName.forEach(function(events, typeInfo) { + newSubViews.push(createSubView(typeInfo, events)); + }); + + this.tabView_.resetSubViews(newSubViews); + }, + + onSelectedSubViewChanged_() { + const selectedSubView = this.tabView_.selectedSubView; + + if (!selectedSubView) { + this.tallMode = false; + this.maybeChangeRelatedEvents_(undefined); + return; + } + + this.tallMode = selectedSubView.requiresTallView; + this.maybeChangeRelatedEvents_(selectedSubView.relatedEventsToHighlight); + }, + + /** Changes the highlighted related events if possible. */ + maybeChangeRelatedEvents_(events) { + if (this.brushingStateController) { + this.brushingStateController.changeAnalysisViewRelatedEvents(events); + } + } + }); +})(); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/analysis_view_test.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/analysis_view_test.html new file mode 100644 index 00000000000..fa7b51256a6 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/analysis_view_test.html @@ -0,0 +1,141 @@ +<!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/core/test_utils.html"> +<link rel="import" href="/tracing/model/counter.html"> +<link rel="import" href="/tracing/model/counter_sample.html"> +<link rel="import" href="/tracing/model/counter_series.html"> +<link rel="import" href="/tracing/model/event_set.html"> +<link rel="import" href="/tracing/model/user_model/stub_expectation.html"> +<link rel="import" href="/tracing/ui/analysis/analysis_view.html"> +<link rel="import" href="/tracing/ui/brushing_state_controller.html"> +<link rel="import" href="/tracing/ui/extras/full_config.html"> + +<script> +'use strict'; + +tr.b.unittest.testSuite(function() { + const EventSet = tr.model.EventSet; + const BrushingStateController = tr.c.BrushingStateController; + const Model = tr.Model; + const Counter = tr.model.Counter; + const CounterSeries = tr.model.CounterSeries; + const CounterSample = tr.model.CounterSample; + const newThreadSlice = tr.c.TestUtils.newThreadSlice; + const SCHEDULING_STATE = tr.model.SCHEDULING_STATE; + const StubExpectation = tr.model.um.StubExpectation; + + function assertEventSet(actualEventSet, expectedEvents) { + const expectedEventSet = new EventSet(expectedEvents); + assert.isTrue(actualEventSet.equals(expectedEventSet), + 'EventSet objects are not equal'); + } + + function checkTab(tab, expectedTagName, expectedSelectionEvents) { + assert.strictEqual(tab.tagName, expectedTagName.toUpperCase()); + assertEventSet(tab.selection, expectedSelectionEvents); + } + + test('selectedTabChange', function() { + // Set up the model. + const model = new Model(); + const process = model.getOrCreateProcess(1); + + const counter = process.getOrCreateCounter('universe', 'planets'); + const series = counter.addSeries(new CounterSeries('x', 0)); + const sample1 = series.addCounterSample(0, 100); + const sample2 = series.addCounterSample(1, 90); + const sample3 = series.addCounterSample(2, 80); + + const thread = process.getOrCreateThread(2); + const slice1 = newThreadSlice(thread, SCHEDULING_STATE.RUNNING, 0, 1); + const slice2 = newThreadSlice(thread, SCHEDULING_STATE.SLEEPING, 1, 2.718); + thread.timeSlices = [slice1, slice2]; + + const record1 = new StubExpectation( + {parentModel: model, initiatorTitle: 'r1', start: 200, duration: 300}); + record1.associatedEvents.push(sample1); + record1.associatedEvents.push(slice1); + const record2 = new StubExpectation( + {parentModel: model, initiatorTitle: 'r2', start: 600, duration: 100}); + record2.associatedEvents.push(sample2); + record2.associatedEvents.push(sample3); + record2.associatedEvents.push(slice1); + + // Set up the analysis views and brushing state controller. + const analysisView = document.createElement('tr-ui-a-analysis-view'); + this.addHTMLOutput(analysisView); + const tabView = analysisView.tabView; + const controller = new BrushingStateController(undefined); + analysisView.brushingStateController = controller; + + function checkSelectedTab(expectedSelectedTab, expectedRelatedEvents) { + assert.strictEqual(tabView.selectedSubView, expectedSelectedTab); + assertEventSet(controller.currentBrushingState.analysisViewRelatedEvents, + expectedRelatedEvents); + } + + // 1. Empty selection (implicit). + assert.lengthOf(tabView.tabs, 0); + checkSelectedTab(undefined, []); + + // 2. Event selection: two samples and one thread slice. + controller.changeSelectionFromRequestSelectionChangeEvent( + new EventSet([sample1, slice1, sample2])); + assert.lengthOf(tabView.tabs, 2); + const sampleTab2 = tabView.tabs[0]; + checkTab(sampleTab2, + 'tr-ui-a-counter-sample-sub-view', + [sample1, sample2]); + const singleThreadSliceTab2 = tabView.tabs[1]; + checkTab(singleThreadSliceTab2, + 'tr-ui-a-single-thread-time-slice-sub-view', + [slice1]); + // First tab should be selected. + checkSelectedTab(sampleTab2, []); + + // 3. Tab selection: single thread slice tab. + tabView.selectedSubView = singleThreadSliceTab2; + checkSelectedTab(singleThreadSliceTab2, []); + + // 4. Event selection: one sample, two thread slices, and one + // user expectation. + controller.changeSelectionFromRequestSelectionChangeEvent( + new EventSet([slice1, slice2, sample3, record1])); + assert.lengthOf(tabView.tabs, 3); + const sampleTab4 = tabView.tabs[1]; + checkTab(sampleTab4, + 'tr-ui-a-counter-sample-sub-view', + [sample3]); + const singleRecordTab4 = tabView.tabs[2]; + checkTab(singleRecordTab4, + 'tr-ui-a-single-user-expectation-sub-view', + [record1]); + const multiThreadSliceTab4 = tabView.tabs[0]; + checkTab(multiThreadSliceTab4, + 'tr-ui-a-multi-thread-time-slice-sub-view', + [slice1, slice2]); + // Remember selected tab (even though the tab was destroyed). + checkSelectedTab(multiThreadSliceTab4, []); + + // 5. Tab selection: single user expectation tab. + tabView.selectedSubView = singleRecordTab4; + checkSelectedTab(singleRecordTab4, [sample1, slice1]); + + // 6. Event selection: one user expectation. + controller.changeSelectionFromRequestSelectionChangeEvent( + new EventSet([record2])); + assert.lengthOf(tabView.tabs, 1); + const singleRecordTab6 = tabView.tabs[0]; + checkTab(singleRecordTab6, + 'tr-ui-a-single-user-expectation-sub-view', + [record2]); + // Remember selected tab. + checkSelectedTab(singleRecordTab6, [sample2, sample3, slice1]); + }); +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/container_memory_dump_sub_view.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/container_memory_dump_sub_view.html new file mode 100644 index 00000000000..cc0e9155358 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/container_memory_dump_sub_view.html @@ -0,0 +1,200 @@ +<!DOCTYPE html> +<!-- +Copyright 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/base/unit.html"> +<link rel="import" href="/tracing/base/utils.html"> +<link rel="import" href="/tracing/model/model.html"> +<link rel="import" href="/tracing/ui/analysis/analysis_link.html"> +<link rel="import" href="/tracing/ui/analysis/analysis_sub_view.html"> +<link rel="import" href="/tracing/ui/analysis/memory_dump_header_pane.html"> +<link rel="import" href="/tracing/ui/analysis/stacked_pane_view.html"> +<link rel="import" href="/tracing/ui/base/dom_helpers.html"> +<link rel="import" href="/tracing/value/ui/scalar_span.html"> + +<dom-module id='tr-ui-a-container-memory-dump-sub-view'> + <template> + <style> + tr-ui-b-table { + font-size: 12px; + } + </style> + <div id="content"></div> + </template> +</dom-module> +<script> +'use strict'; + +tr.exportTo('tr.ui.analysis', function() { + Polymer({ + is: 'tr-ui-a-container-memory-dump-sub-view', + behaviors: [tr.ui.analysis.AnalysisSubView], + + set selection(selection) { + if (selection === undefined) { + this.currentSelection_ = undefined; + this.dumpsByContainerName_ = undefined; + this.updateContents_(); + return; + } + + // Check that the selection contains only container memory dumps. + selection.forEach(function(event) { + if (!(event instanceof tr.model.ContainerMemoryDump)) { + throw new Error( + 'Memory dump sub-view only supports container memory dumps'); + } + }); + this.currentSelection_ = selection; + + // Group the selected memory dumps by container name and sort them + // chronologically. + this.dumpsByContainerName_ = tr.b.groupIntoMap( + this.currentSelection_.toArray(), dump => dump.containerName); + for (const dumps of this.dumpsByContainerName_.values()) { + dumps.sort((a, b) => a.start - b.start); + } + + this.updateContents_(); + }, + + get selection() { + return this.currentSelection_; + }, + + get requiresTallView() { + return true; + }, + + updateContents_() { + Polymer.dom(this.$.content).textContent = ''; + + if (this.dumpsByContainerName_ === undefined) return; + + const containerNames = Array.from(this.dumpsByContainerName_.keys()); + if (containerNames.length === 0) return; + + if (containerNames.length > 1) { + this.buildViewForMultipleContainerNames_(); + } else { + this.buildViewForSingleContainerName_(); + } + }, + + buildViewForSingleContainerName_() { + const containerMemoryDumps = tr.b.getFirstElement( + this.dumpsByContainerName_.values()); + const dumpView = this.ownerDocument.createElement( + 'tr-ui-a-stacked-pane-view'); + Polymer.dom(this.$.content).appendChild(dumpView); + dumpView.setPaneBuilder(function() { + const headerPane = document.createElement( + 'tr-ui-a-memory-dump-header-pane'); + headerPane.containerMemoryDumps = containerMemoryDumps; + return headerPane; + }); + }, + + buildViewForMultipleContainerNames_() { + // TODO(petrcermak): Provide a more sophisticated view for this case. + const ownerDocument = this.ownerDocument; + + const rows = []; + for (const [containerName, dumps] of this.dumpsByContainerName_) { + rows.push({ + containerName, + subRows: dumps, + isExpanded: true, + }); + } + rows.sort(function(a, b) { + return a.containerName.localeCompare(b.containerName); + }); + + const columns = [ + { + title: 'Dump', + + value(row) { + if (row.subRows === undefined) { + return this.singleDumpValue_(row); + } + return this.groupedDumpValue_(row); + }, + + singleDumpValue_(row) { + const linkEl = ownerDocument.createElement('tr-ui-a-analysis-link'); + linkEl.setSelectionAndContent(new tr.model.EventSet([row])); + Polymer.dom(linkEl).appendChild(tr.v.ui.createScalarSpan( + row.start, { + unit: tr.b.Unit.byName.timeStampInMs, + ownerDocument + })); + return linkEl; + }, + + groupedDumpValue_(row) { + const linkEl = ownerDocument.createElement('tr-ui-a-analysis-link'); + linkEl.setSelectionAndContent(new tr.model.EventSet(row.subRows)); + Polymer.dom(linkEl).appendChild(tr.ui.b.createSpan({ + ownerDocument, + textContent: row.subRows.length + ' memory dump' + + (row.subRows.length === 1 ? '' : 's') + ' in ' + })); + Polymer.dom(linkEl).appendChild(tr.ui.b.createSpan({ + ownerDocument, + textContent: row.containerName, + bold: true + })); + return linkEl; + } + } + ]; + + const table = this.ownerDocument.createElement('tr-ui-b-table'); + table.tableColumns = columns; + table.tableRows = rows; + table.showHeader = false; + table.rebuild(); + Polymer.dom(this.$.content).appendChild(table); + } + }); + + tr.ui.analysis.AnalysisSubView.register( + 'tr-ui-a-container-memory-dump-sub-view', + tr.model.GlobalMemoryDump, + { + multi: false, + title: 'Global Memory Dump', + }); + + tr.ui.analysis.AnalysisSubView.register( + 'tr-ui-a-container-memory-dump-sub-view', + tr.model.GlobalMemoryDump, + { + multi: true, + title: 'Global Memory Dumps', + }); + + tr.ui.analysis.AnalysisSubView.register( + 'tr-ui-a-container-memory-dump-sub-view', + tr.model.ProcessMemoryDump, + { + multi: false, + title: 'Process Memory Dump', + }); + + tr.ui.analysis.AnalysisSubView.register( + 'tr-ui-a-container-memory-dump-sub-view', + tr.model.ProcessMemoryDump, + { + multi: true, + title: 'Process Memory Dumps', + }); + + return {}; +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/container_memory_dump_sub_view_test.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/container_memory_dump_sub_view_test.html new file mode 100644 index 00000000000..974837f545e --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/container_memory_dump_sub_view_test.html @@ -0,0 +1,351 @@ +<!DOCTYPE html> +<!-- +Copyright 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/base/utils.html"> +<link rel="import" href="/tracing/model/event_set.html"> +<link rel="import" + href="/tracing/ui/analysis/container_memory_dump_sub_view.html"> +<link rel="import" + href="/tracing/ui/analysis/memory_dump_sub_view_test_utils.html"> +<link rel="import" href="/tracing/ui/base/deep_utils.html"> +<link rel="import" href="/tracing/ui/brushing_state_controller.html"> + +<script> +'use strict'; + +tr.b.unittest.testSuite(function() { + const EventSet = tr.model.EventSet; + const extractVmRegions = tr.ui.analysis.extractVmRegions; + const extractMemoryAllocatorDumps = + tr.ui.analysis.extractMemoryAllocatorDumps; + const extractHeapDumps = tr.ui.analysis.extractHeapDumps; + + function createViewWithSelection(selection, opt_parentElement) { + const viewEl = document.createElement( + 'tr-ui-a-container-memory-dump-sub-view'); + if (opt_parentElement) { + Polymer.dom(opt_parentElement).appendChild(viewEl); + } + if (selection === undefined) { + viewEl.selection = undefined; + } else { + // Rotate the list of selected dumps to check that the sub-view sorts + // them properly. + const length = selection.length; + viewEl.selection = new tr.model.EventSet( + selection.slice(length / 2, length).concat( + selection.slice(0, length / 2))); + } + return viewEl; + } + + function createAndCheckContainerMemoryDumpView( + test, containerMemoryDumps, detailsCheckCallback, opt_parentElement) { + const viewEl = + createViewWithSelection(containerMemoryDumps, opt_parentElement); + if (!opt_parentElement) { + test.addHTMLOutput(viewEl); + } + + // The view should contain a stacked pane view with memory dump header and + // overview panes. + const stackedPaneViewEl = tr.ui.b.findDeepElementMatching( + viewEl, 'tr-ui-a-stacked-pane-view'); + const headerPaneEl = tr.ui.b.findDeepElementMatching( + stackedPaneViewEl, 'tr-ui-a-memory-dump-header-pane'); + const overviewPaneEl = tr.ui.b.findDeepElementMatching( + stackedPaneViewEl, 'tr-ui-a-memory-dump-overview-pane'); + + // Check that the header pane and overview pane are correctly set up. + const processMemoryDumps = containerMemoryDumps.map( + containerDump => containerDump.processMemoryDumps); + assert.deepEqual( + Array.from(headerPaneEl.containerMemoryDumps), containerMemoryDumps); + assert.deepEqual(overviewPaneEl.processMemoryDumps, processMemoryDumps); + assert.strictEqual( + overviewPaneEl.aggregationMode, headerPaneEl.aggregationMode); + + // Get the overview pane table to drive the details pane checks. + const overviewTableEl = tr.ui.b.findDeepElementMatching( + overviewPaneEl, 'tr-ui-b-table'); + + function checkVmRegionsPane(pid) { + const detailsPaneEl = tr.ui.b.findDeepElementMatching( + stackedPaneViewEl, 'tr-ui-a-memory-dump-vm-regions-details-pane'); + if (pid === undefined) { + assert.isUndefined(detailsPaneEl); + } else { + assert.deepEqual(Array.from(detailsPaneEl.vmRegions), + extractVmRegions(processMemoryDumps, pid)); + assert.strictEqual( + detailsPaneEl.aggregationMode, headerPaneEl.aggregationMode); + } + } + + function checkAllocatorPane(pid, allocatorName, withHeapDetailsPane) { + const allocatorDetailsPaneEl = tr.ui.b.findDeepElementMatching( + stackedPaneViewEl, 'tr-ui-a-memory-dump-allocator-details-pane'); + if (pid === undefined) { + assert.isUndefined(allocatorDetailsPaneEl); + assert.isUndefined(allocatorName); // Test sanity check. + assert.isUndefined(withHeapDetailsPane); // Test sanity check. + return; + } + + assert.deepEqual( + Array.from(allocatorDetailsPaneEl.memoryAllocatorDumps), + extractMemoryAllocatorDumps(processMemoryDumps, pid, allocatorName)); + assert.strictEqual( + allocatorDetailsPaneEl.aggregationMode, headerPaneEl.aggregationMode); + + const heapDetailsPaneEl = tr.ui.b.findDeepElementMatching( + stackedPaneViewEl, 'tr-ui-a-memory-dump-heap-details-pane'); + if (!withHeapDetailsPane) { + assert.isUndefined(heapDetailsPaneEl); + return; + } + + assert.deepEqual(Array.from(heapDetailsPaneEl.heapDumps), + extractHeapDumps(processMemoryDumps, pid, allocatorName)); + assert.strictEqual( + heapDetailsPaneEl.aggregationMode, headerPaneEl.aggregationMode); + } + + detailsCheckCallback( + overviewTableEl, checkVmRegionsPane, checkAllocatorPane); + } + + test('instantiate_empty', function() { + // All these views should be completely empty. + const unsetViewEl = document.createElement( + 'tr-ui-a-container-memory-dump-sub-view'); + this.addHTMLOutput(unsetViewEl); + assert.strictEqual(unsetViewEl.getBoundingClientRect().width, 0); + assert.strictEqual(unsetViewEl.getBoundingClientRect().height, 0); + + const undefinedViewEl = createViewWithSelection(undefined); + this.addHTMLOutput(undefinedViewEl); + assert.strictEqual(undefinedViewEl.getBoundingClientRect().width, 0); + assert.strictEqual(undefinedViewEl.getBoundingClientRect().height, 0); + + const emptyViewEl = createViewWithSelection([]); + this.addHTMLOutput(emptyViewEl); + assert.strictEqual(emptyViewEl.getBoundingClientRect().width, 0); + assert.strictEqual(emptyViewEl.getBoundingClientRect().height, 0); + }); + + test('instantiate_singleGlobalMemoryDump', function() { + createAndCheckContainerMemoryDumpView(this, + [tr.ui.analysis.createSingleTestGlobalMemoryDump()], + function(overviewTableEl, checkVmRegionsPane, checkAllocatorPane) { + // Nothing should be selected initially. + assert.isUndefined(overviewTableEl.selectedTableRow); + assert.isUndefined(overviewTableEl.selectedColumnIndex); + checkVmRegionsPane(undefined); + checkAllocatorPane(undefined); + + // Total resident of Process 1. + overviewTableEl.selectedTableRow = overviewTableEl.tableRows[0]; + overviewTableEl.selectedColumnIndex = 1; + checkVmRegionsPane(1 /* PID */); + checkAllocatorPane(undefined); + + // PSS of process 4. + overviewTableEl.selectedColumnIndex = 3; + overviewTableEl.selectedTableRow = overviewTableEl.tableRows[2]; + checkVmRegionsPane(undefined); + checkAllocatorPane(undefined); + + // Malloc of process 2. + overviewTableEl.selectedTableRow = overviewTableEl.tableRows[1]; + overviewTableEl.selectedColumnIndex = 10; + checkVmRegionsPane(undefined); + checkAllocatorPane(2 /* PID */, 'malloc', + false /* no heap details pane */); + }); + }); + + test('instantiate_multipleGlobalMemoryDumps', function() { + createAndCheckContainerMemoryDumpView(this, + tr.ui.analysis.createMultipleTestGlobalMemoryDumps(), + function(overviewTableEl, checkVmRegionsPane, checkAllocatorPane) { + // Nothing should be selected initially. + assert.isUndefined(overviewTableEl.selectedTableRow); + assert.isUndefined(overviewTableEl.selectedColumnIndex); + checkVmRegionsPane(undefined); + checkAllocatorPane(undefined); + + // Blink of Process 1. + overviewTableEl.selectedTableRow = overviewTableEl.tableRows[0]; + overviewTableEl.selectedColumnIndex = 8; + checkVmRegionsPane(undefined); + checkAllocatorPane(undefined); + + // Peak total resident of Process 4. + overviewTableEl.selectedTableRow = overviewTableEl.tableRows[3]; + overviewTableEl.selectedColumnIndex = 2; + checkVmRegionsPane(undefined); + checkAllocatorPane(undefined); + + // V8 of Process 3. + overviewTableEl.selectedTableRow = overviewTableEl.tableRows[2]; + overviewTableEl.selectedColumnIndex = 12; + checkVmRegionsPane(undefined); + checkAllocatorPane(3 /* PID */, 'v8', true /* heap details pane */); + }); + }); + + test('instantiate_singleProcessMemoryDump', function() { + createAndCheckContainerMemoryDumpView(this, + [tr.ui.analysis.createSingleTestProcessMemoryDump()], + function(overviewTableEl, checkVmRegionsPane, checkAllocatorPane) { + // Nothing should be selected initially. + assert.isUndefined(overviewTableEl.selectedTableRow); + assert.isUndefined(overviewTableEl.selectedColumnIndex); + checkVmRegionsPane(undefined); + checkAllocatorPane(undefined); + + // Tracing of Process 2. + overviewTableEl.selectedTableRow = overviewTableEl.tableRows[0]; + overviewTableEl.selectedColumnIndex = 13; + checkVmRegionsPane(undefined); + checkAllocatorPane(2 /* PID */, 'tracing', + false /* no heap details pane */); + + // Blink of Process 2. + overviewTableEl.selectedColumnIndex = 8; + checkVmRegionsPane(undefined); + checkAllocatorPane(2 /* PID */, 'blink', + false /* no heap details pane */); + + // Total resident of Process 2. + overviewTableEl.selectedColumnIndex = 1; + checkVmRegionsPane(2 /* PID */); + checkAllocatorPane(undefined); + }); + }); + + test('instantiate_multipleProcessMemoryDumps', function() { + createAndCheckContainerMemoryDumpView(this, + tr.ui.analysis.createMultipleTestProcessMemoryDumps(), + function(overviewTableEl, checkVmRegionsPane, checkAllocatorPane) { + // Nothing should be selected initially. + assert.isUndefined(overviewTableEl.selectedTableRow); + assert.isUndefined(overviewTableEl.selectedColumnIndex); + checkVmRegionsPane(undefined); + checkAllocatorPane(undefined); + + // Tracing of Process 2. + overviewTableEl.selectedTableRow = overviewTableEl.tableRows[0]; + overviewTableEl.selectedColumnIndex = 13; + checkVmRegionsPane(undefined); + checkAllocatorPane(2 /* PID */, 'tracing', + false /* no heap details pane */); + + // V8 of Process 2. + overviewTableEl.selectedColumnIndex = 12; + checkVmRegionsPane(undefined); + checkAllocatorPane(2 /* PID */, 'v8', + false /* no heap details pane */); + + // PSS of Process 2. + overviewTableEl.selectedColumnIndex = 3; + checkVmRegionsPane(2 /* PID */); + checkAllocatorPane(undefined); + }); + }); + + test('memory', function() { + const containerEl = document.createElement('div'); + containerEl.brushingStateController = + new tr.c.BrushingStateController(undefined); + + // Create the first container memory view. + createAndCheckContainerMemoryDumpView(this, + [tr.ui.analysis.createSingleTestProcessMemoryDump()], + function(overviewTableEl, checkVmRegionsPane, checkAllocatorPane) { + // Nothing should be selected initially. + assert.isUndefined(overviewTableEl.selectedTableRow); + assert.isUndefined(overviewTableEl.selectedColumnIndex); + checkVmRegionsPane(undefined); + checkAllocatorPane(undefined); + + // Select V8 of Process 2. + overviewTableEl.selectedTableRow = overviewTableEl.tableRows[0]; + overviewTableEl.selectedColumnIndex = 12; + checkVmRegionsPane(undefined); + checkAllocatorPane(2 /* PID */, 'v8', + false /* no heap details pane */); + }, containerEl); + + // Destroy the first container memory view. + Polymer.dom(containerEl).textContent = ''; + + // Create the second container memory view. + createAndCheckContainerMemoryDumpView(this, + tr.ui.analysis.createMultipleTestGlobalMemoryDumps(), + function(overviewTableEl, checkVmRegionsPane, checkAllocatorPane) { + // V8 of Process 2 should still be selected (even though the selection + // changed). + assert.strictEqual( + overviewTableEl.selectedTableRow, overviewTableEl.tableRows[1]); + assert.strictEqual(overviewTableEl.selectedColumnIndex, 12); + checkVmRegionsPane(undefined); + checkAllocatorPane(2 /* PID */, 'v8', + false /* no heap details pane */); + }, containerEl); + }); + + test('instantiate_differentProcessMemoryDumps', function() { + const globalMemoryDumps = + tr.ui.analysis.createMultipleTestGlobalMemoryDumps(); + // 2 dumps in Process 1, 3 dumps in Process 2, and 1 dump in Process 4 + // (intentionally shuffled to check sorting). + const differentProcessDumps = [ + globalMemoryDumps[1].processMemoryDumps[2], + globalMemoryDumps[0].processMemoryDumps[1], + globalMemoryDumps[0].processMemoryDumps[2], + globalMemoryDumps[1].processMemoryDumps[4], + globalMemoryDumps[1].processMemoryDumps[1], + globalMemoryDumps[2].processMemoryDumps[2] + ]; + + const viewEl = createViewWithSelection(differentProcessDumps); + this.addHTMLOutput(viewEl); + + const tableEl = tr.ui.b.findDeepElementMatching(viewEl, 'tr-ui-b-table'); + assert.lengthOf(tableEl.tableRows, 3); + assert.lengthOf(tableEl.tableColumns, 1); + const rows = tableEl.tableRows; + const col = tableEl.tableColumns[0]; + + assert.strictEqual(Polymer.dom(col.value(rows[0])).textContent, + '2 memory dumps in Process 1'); + assert.strictEqual(Polymer.dom(col.value(rows[1])).textContent, + '3 memory dumps in Process 2'); + assert.strictEqual(Polymer.dom(col.value(rows[2])).textContent, + '1 memory dump in Process 4'); + + // Check that the analysis link is associated with the right dumps. + assert.isTrue(col.value(rows[1]).selection.equals(new tr.model.EventSet([ + globalMemoryDumps[0].processMemoryDumps[2], + globalMemoryDumps[1].processMemoryDumps[2], + globalMemoryDumps[2].processMemoryDumps[2] + ]))); + + assert.lengthOf(rows[1].subRows, 3); + const subRow = rows[1].subRows[0]; + + // Check the timestamp. + assert.strictEqual(col.value(subRow).children[0].value, 42); + + // Check that the analysis link is associated with the right dump. + assert.isTrue(col.value(subRow).selection.equals( + new tr.model.EventSet(globalMemoryDumps[0].processMemoryDumps[2]))); + }); +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/counter_sample_sub_view.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/counter_sample_sub_view.html new file mode 100644 index 00000000000..a9275b19d0c --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/counter_sample_sub_view.html @@ -0,0 +1,139 @@ +<!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/utils.html"> +<link rel="import" href="/tracing/ui/analysis/analysis_sub_view.html"> +<link rel="import" href="/tracing/ui/base/table.html"> + +<dom-module id='tr-ui-a-counter-sample-sub-view'> + <template> + <style> + :host { + display: flex; + flex-direction: column; + } + tr-ui-b-table { + font-size: 12px; + } + </style> + <tr-ui-b-table id='table'></tr-ui-b-table> + </template> +</dom-module> + +<script> +'use strict'; +(function() { + const COUNTER_SAMPLE_TABLE_COLUMNS = [ + { + title: 'Counter', + width: '150px', + value(row) { return row.counter; } + }, + { + title: 'Series', + width: '150px', + value(row) { return row.series; } + }, + { + title: 'Time', + width: '150px', + value(row) { return row.start; } + }, + { + title: 'Value', + width: '100%', + value(row) { return row.value; } + } + ]; + + Polymer({ + is: 'tr-ui-a-counter-sample-sub-view', + behaviors: [tr.ui.analysis.AnalysisSubView], + + ready() { + this.currentSelection_ = undefined; + this.$.table.tableColumns = COUNTER_SAMPLE_TABLE_COLUMNS; + }, + + get selection() { + return this.currentSelection_; + }, + + set selection(selection) { + this.currentSelection_ = selection; + this.updateContents_(); + }, + + updateContents_() { + this.$.table.tableRows = + this.selection ? this.getRows_(this.selection.toArray()) : []; + this.$.table.rebuild(); + }, + + /** + * Returns the table rows for the specified samples. + * + * We print each counter/series combination the first time that it + * appears. For subsequent samples in each series, we omit the counter + * and series name. This makes it easy to scan to find the next series. + * + * Each series can be collapsed. In the expanded state, all samples + * are shown. In the collapsed state, only the first sample is displayed. + */ + getRows_(samples) { + const samplesByCounter = tr.b.groupIntoMap( + samples, sample => sample.series.counter.guid); + + const rows = []; + for (const counterSamples of samplesByCounter.values()) { + const samplesBySeries = tr.b.groupIntoMap( + counterSamples, sample => sample.series.guid); + + for (const seriesSamples of samplesBySeries.values()) { + const seriesRows = this.getRowsForSamples_(seriesSamples); + seriesRows[0].counter = seriesSamples[0].series.counter.name; + seriesRows[0].series = seriesSamples[0].series.name; + + if (seriesRows.length > 1) { + seriesRows[0].subRows = seriesRows.slice(1); + seriesRows[0].isExpanded = true; + } + + rows.push(seriesRows[0]); + } + } + + return rows; + }, + + getRowsForSamples_(samples) { + return samples.map(function(sample) { + return { + start: sample.timestamp, + value: sample.value + }; + }); + } + }); + + tr.ui.analysis.AnalysisSubView.register( + 'tr-ui-a-counter-sample-sub-view', + tr.model.CounterSample, + { + multi: false, + title: 'Counter Sample', + }); + + tr.ui.analysis.AnalysisSubView.register( + 'tr-ui-a-counter-sample-sub-view', + tr.model.CounterSample, + { + multi: true, + title: 'Counter Samples', + }); +})(); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/counter_sample_sub_view_test.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/counter_sample_sub_view_test.html new file mode 100644 index 00000000000..9d7fa370313 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/counter_sample_sub_view_test.html @@ -0,0 +1,177 @@ +<!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/model/counter.html"> +<link rel="import" href="/tracing/model/counter_series.html"> +<link rel="import" href="/tracing/model/event_set.html"> +<link rel="import" href="/tracing/ui/analysis/counter_sample_sub_view.html"> + +<script> +'use strict'; + +tr.b.unittest.testSuite(function() { + const Counter = tr.model.Counter; + const CounterSeries = tr.model.CounterSeries; + const EventSet = tr.model.EventSet; + + test('instantiate_undefinedSelection', function() { + const analysisEl = document.createElement( + 'tr-ui-a-counter-sample-sub-view'); + analysisEl.selection = new EventSet(undefined); + + assert.lengthOf(analysisEl.$.table.tableRows, 0); + }); + + test('instantiate_oneCounterOneSeries', function() { + const series = new CounterSeries('series1', 0); + series.addCounterSample(0, 0); + series.addCounterSample(1, 10); + + const counter = new Counter(null, 0, 'cat', 'ctr1'); + counter.addSeries(series); + + const analysisEl = document.createElement( + 'tr-ui-a-counter-sample-sub-view'); + analysisEl.selection = new EventSet(series.samples); + this.addHTMLOutput(analysisEl); + + // The first sample should be listed as a collapsible header row for the + // series. + const rows = analysisEl.$.table.tableRows; + assert.lengthOf(rows, 1); + assert.isTrue(rows[0].isExpanded); + assert.strictEqual(rows[0].counter, 'ctr1'); + assert.strictEqual(rows[0].series, 'series1'); + assert.strictEqual(rows[0].start, 0); + assert.strictEqual(rows[0].value, 0); + + // The second sample should be listed as a subrow of the first. + const subRows = rows[0].subRows; + assert.lengthOf(subRows, 1); + assert.isUndefined(subRows[0].counter); + assert.isUndefined(subRows[0].series); + assert.strictEqual(subRows[0].start, 1); + assert.strictEqual(subRows[0].value, 10); + }); + + test('instantiate_singleSampleDoesntHaveSubrows', function() { + const series = new CounterSeries('series1', 0); + series.addCounterSample(0, 0); + + const counter = new Counter(null, 0, 'cat', 'ctr1'); + counter.addSeries(series); + + const analysisEl = document.createElement( + 'tr-ui-a-counter-sample-sub-view'); + analysisEl.selection = new EventSet(series.samples); + this.addHTMLOutput(analysisEl); + + // The first sample should be listed as a collapsible header row for the + // series. + const rows = analysisEl.$.table.tableRows; + assert.lengthOf(rows, 1); + assert.strictEqual(rows[0].counter, 'ctr1'); + assert.strictEqual(rows[0].series, 'series1'); + assert.strictEqual(rows[0].start, 0); + assert.strictEqual(rows[0].value, 0); + assert.isUndefined(rows[0].subRows); + }); + + test('instantiate_oneCounterTwoSeries', function() { + const series1 = new CounterSeries('series1', 0); + series1.addCounterSample(1, 10); + series1.addCounterSample(2, 20); + + const series2 = new CounterSeries('series2', 0); + series2.addCounterSample(3, 30); + + const counter = new Counter(null, 0, 'cat', 'ctr1'); + counter.addSeries(series1); + counter.addSeries(series2); + + const analysisEl = document.createElement( + 'tr-ui-a-counter-sample-sub-view'); + analysisEl.selection = + new EventSet(series1.samples.concat(series2.samples)); + this.addHTMLOutput(analysisEl); + + // The first samples should be listed as collapsible header rows for the + // series. + const rows = analysisEl.$.table.tableRows; + assert.lengthOf(rows, 2); + assert.strictEqual(rows[0].counter, 'ctr1'); + assert.strictEqual(rows[0].series, 'series1'); + assert.strictEqual(rows[0].start, 1); + assert.strictEqual(rows[0].value, 10); + + assert.strictEqual(rows[1].counter, 'ctr1'); + assert.strictEqual(rows[1].series, 'series2'); + assert.strictEqual(rows[1].start, 3); + assert.strictEqual(rows[1].value, 30); + + // The subsequent samples should be listed as subrows of the first. + const subRows1 = rows[0].subRows; + assert.lengthOf(subRows1, 1); + assert.isUndefined(subRows1[0].counter); + assert.isUndefined(subRows1[0].series); + assert.strictEqual(subRows1[0].start, 2); + assert.strictEqual(subRows1[0].value, 20); + + assert.isUndefined(rows[1].subRows); + }); + + test('instantiate_twoCountersTwoSeries', function() { + const series1 = new CounterSeries('series1', 0); + series1.addCounterSample(1, 10); + + const series2 = new CounterSeries('series2', 0); + series2.addCounterSample(2, 20); + + const counter1 = new Counter(null, 0, 'cat', 'ctr1'); + const counter2 = new Counter(null, 0, 'cat', 'ctr2'); + counter1.addSeries(series1); + counter2.addSeries(series2); + + const analysisEl = document.createElement( + 'tr-ui-a-counter-sample-sub-view'); + analysisEl.selection = + new EventSet(series1.samples.concat(series2.samples)); + this.addHTMLOutput(analysisEl); + + // Each sample should be a header row with no subrows. + const rows = analysisEl.$.table.tableRows; + assert.lengthOf(rows, 2); + assert.strictEqual(rows[0].counter, 'ctr1'); + assert.strictEqual(rows[0].series, 'series1'); + assert.strictEqual(rows[0].start, 1); + assert.strictEqual(rows[0].value, 10); + assert.isUndefined(rows[0].subRows); + + assert.strictEqual(rows[1].counter, 'ctr2'); + assert.strictEqual(rows[1].series, 'series2'); + assert.strictEqual(rows[1].start, 2); + assert.strictEqual(rows[1].value, 20); + assert.isUndefined(rows[1].subRows); + }); + + test('instantiate_contentsClearedEachSelection', function() { + const series = new CounterSeries('series1', 0); + series.addCounterSample(0, 0); + + const counter = new Counter(null, 0, 'cat', 'ctr1'); + counter.addSeries(series); + + const analysisEl = document.createElement( + 'tr-ui-a-counter-sample-sub-view'); + analysisEl.selection = new EventSet(series.samples); + analysisEl.selection = new EventSet(series.samples); + this.addHTMLOutput(analysisEl); + + assert.lengthOf(analysisEl.$.table.tableRows, 1); + }); +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/flow_classifier.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/flow_classifier.html new file mode 100644 index 00000000000..1773a09f32f --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/flow_classifier.html @@ -0,0 +1,92 @@ +<!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/model/event_set.html"> + +<script> +'use strict'; + +tr.exportTo('tr.ui.analysis', function() { + const FLOW_IN = 0x1; + const FLOW_OUT = 0x2; + const FLOW_IN_OUT = FLOW_IN | FLOW_OUT; + + function FlowClassifier() { + this.numEvents_ = 0; + this.eventsByGUID_ = {}; + } + + FlowClassifier.prototype = { + getFS_(event) { + let fs = this.eventsByGUID_[event.guid]; + if (fs === undefined) { + this.numEvents_++; + fs = { + state: 0, + event + }; + this.eventsByGUID_[event.guid] = fs; + } + return fs; + }, + + addInFlow(event) { + const fs = this.getFS_(event); + fs.state |= FLOW_IN; + return event; + }, + + addOutFlow(event) { + const fs = this.getFS_(event); + fs.state |= FLOW_OUT; + return event; + }, + + hasEvents() { + return this.numEvents_ > 0; + }, + + get inFlowEvents() { + const selection = new tr.model.EventSet(); + for (const guid in this.eventsByGUID_) { + const fs = this.eventsByGUID_[guid]; + if (fs.state === FLOW_IN) { + selection.push(fs.event); + } + } + return selection; + }, + + get outFlowEvents() { + const selection = new tr.model.EventSet(); + for (const guid in this.eventsByGUID_) { + const fs = this.eventsByGUID_[guid]; + if (fs.state === FLOW_OUT) { + selection.push(fs.event); + } + } + return selection; + }, + + get internalFlowEvents() { + const selection = new tr.model.EventSet(); + for (const guid in this.eventsByGUID_) { + const fs = this.eventsByGUID_[guid]; + if (fs.state === FLOW_IN_OUT) { + selection.push(fs.event); + } + } + return selection; + } + }; + + return { + FlowClassifier, + }; +}); +</script> + diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/flow_classifier_test.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/flow_classifier_test.html new file mode 100644 index 00000000000..ba68f671b57 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/flow_classifier_test.html @@ -0,0 +1,52 @@ +<!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/base/utils.html"> +<link rel="import" href="/tracing/core/test_utils.html"> +<link rel="import" href="/tracing/ui/analysis/flow_classifier.html"> + +<script> +'use strict'; + +tr.b.unittest.testSuite(function() { + const newFlowEventEx = tr.c.TestUtils.newFlowEventEx; + + test('basic', function() { + const a = newFlowEventEx({ + title: 'a', start: 0, end: 10 }); + const b = newFlowEventEx({ + title: 'b', start: 10, end: 20 }); + const c = newFlowEventEx({ + title: 'c', start: 20, end: 25 }); + const d = newFlowEventEx({ + title: 'd', start: 30, end: 35 }); + + const fc = new tr.ui.analysis.FlowClassifier(); + fc.addInFlow(a); + + fc.addInFlow(b); + fc.addOutFlow(b); + + fc.addInFlow(c); + fc.addOutFlow(c); + + fc.addOutFlow(d); + + function asSortedArray(selection) { + const events = Array.from(selection); + events.sort(function(a, b) { + return a.guid - b.guid; + }); + return events; + } + + assert.deepEqual(Array.from(fc.inFlowEvents), [a]); + assert.deepEqual(Array.from(fc.outFlowEvents), [d]); + assert.deepEqual(Array.from(fc.internalFlowEvents), [b, c]); + }); +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/frame_power_usage_chart.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/frame_power_usage_chart.html new file mode 100644 index 00000000000..bc3f4fcead8 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/frame_power_usage_chart.html @@ -0,0 +1,134 @@ +<!DOCTYPE html> +<!-- +Copyright 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/model/event_set.html"> +<link rel="import" href="/tracing/ui/base/line_chart.html"> + +<!-- +@fileoverview A line chart showing milliseconds since the start of the frame on +the x-axis and power consumption on the y-axis. Each frame is shown as a +separate line in the chart. Vertical sync events are used as the start of each +frame. + +This chart aims to help users understand the shape of the power consumption +curve over the course of a frame or set of frames. +--> +<dom-module id='tr-ui-a-frame-power-usage-chart'> + <template> + <div id="content"></div> + </template> +</dom-module> + +<script> +'use strict'; + +const EventSet = tr.model.EventSet; + +const CHART_TITLE = 'Power (W) by ms since vertical sync'; + +Polymer({ + is: 'tr-ui-a-frame-power-usage-chart', + + ready() { + this.chart_ = undefined; + this.samples_ = new EventSet(); + this.vSyncTimestamps_ = []; + }, + + attached() { + if (this.samples_) this.updateContents_(); + }, + + get chart() { + return this.chart_; + }, + + get samples() { + return this.samples_; + }, + + get vSyncTimestamps() { + return this.vSyncTimestamps_; + }, + + /** + * Sets the data that powers the chart. Vsync timestamps must be in + * chronological order. + */ + setData(samples, vSyncTimestamps) { + this.samples_ = (samples === undefined) ? new EventSet() : samples; + this.vSyncTimestamps_ = + (vSyncTimestamps === undefined) ? [] : vSyncTimestamps; + if (this.isAttached) this.updateContents_(); + }, + + updateContents_() { + this.clearChart_(); + + const data = this.getDataForLineChart_(); + + if (data.length === 0) return; + + this.chart_ = new tr.ui.b.LineChart(); + Polymer.dom(this.$.content).appendChild(this.chart_); + this.chart_.chartTitle = CHART_TITLE; + this.chart_.data = data; + }, + + clearChart_() { + const content = this.$.content; + while (Polymer.dom(content).firstChild) { + Polymer.dom(content).removeChild(Polymer.dom(content).firstChild); + } + + this.chart_ = undefined; + }, + + // TODO(charliea): Limit the ms since vsync to the median frame length. The + // vertical syncs are not 100% regular and highlighting any sample that's + // in one of these 'vertical sync lulls' makes the x-axis have a much larger + // scale than it should, effectively squishing the other samples into the + // left side of the chart. + /** + * Returns an array of data points for the chart. Each element in the array + * is of the form { x: <ms since vsync>, f<frame#>: <power in mW> }. + */ + getDataForLineChart_() { + const sortedSamples = this.sortSamplesByTimestampAscending_(this.samples); + const vSyncTimestamps = this.vSyncTimestamps.slice(); + + let lastVSyncTimestamp = undefined; + const points = []; + + // For each power sample, find and record the frame number that it belongs + // to as well as the amount of time elapsed since that frame began. + let frameNumber = 0; + sortedSamples.forEach(function(sample) { + while (vSyncTimestamps.length > 0 && vSyncTimestamps[0] <= sample.start) { + lastVSyncTimestamp = vSyncTimestamps.shift(); + frameNumber++; + } + + // If no vertical sync occurred before the power sample, don't use the + // power sample. + if (lastVSyncTimestamp === undefined) return; + + const point = { x: sample.start - lastVSyncTimestamp }; + point['f' + frameNumber] = sample.powerInW; + points.push(point); + }); + + return points; + }, + + sortSamplesByTimestampAscending_(samples) { + return samples.toArray().sort(function(smpl1, smpl2) { + return smpl1.start - smpl2.start; + }); + } +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/frame_power_usage_chart_perf_test.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/frame_power_usage_chart_perf_test.html new file mode 100644 index 00000000000..caf4601f33c --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/frame_power_usage_chart_perf_test.html @@ -0,0 +1,44 @@ +<!DOCTYPE html> +<!-- +Copyright 2016 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/model/event_set.html"> +<link rel="import" href="/tracing/model/model.html"> +<link rel="import" href="/tracing/model/power_sample.html"> +<link rel="import" href="/tracing/model/power_series.html"> +<link rel="import" href="/tracing/ui/analysis/frame_power_usage_chart.html"> + +<script> +'use strict'; + +tr.b.unittest.testSuite(function() { + function instantiateManyFrames() { + const model = new tr.Model(); + const numFrames = 200; + const samplesPerFrame = 200; + + // Set up the test data. + const series = new tr.model.PowerSeries(model.device); + const vsyncTimestamps = []; + for (let i = 0; i < numFrames; i++) { + vsyncTimestamps.push(i * samplesPerFrame); + for (let j = 0; j < samplesPerFrame; j++) { + series.addPowerSample(vsyncTimestamps[i] + j, j); + } + } + const samples = series.samples; + + // Display the chart. + const chart = document.createElement('tr-ui-a-frame-power-usage-chart'); + chart.setData(new tr.model.EventSet(samples), vsyncTimestamps); + this.addHTMLOutput(chart); + } + + timedPerfTest('frame_power_usage_chart', instantiateManyFrames, { + iterations: 1 + }); +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/frame_power_usage_chart_test.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/frame_power_usage_chart_test.html new file mode 100644 index 00000000000..04ba9388852 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/frame_power_usage_chart_test.html @@ -0,0 +1,267 @@ +<!DOCTYPE html> +<!-- +Copyright 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/model/event_set.html"> +<link rel="import" href="/tracing/model/model.html"> +<link rel="import" href="/tracing/model/power_sample.html"> +<link rel="import" href="/tracing/model/power_series.html"> +<link rel="import" href="/tracing/ui/analysis/frame_power_usage_chart.html"> + +<script> +'use strict'; + +tr.b.unittest.testSuite(function() { + test('instantiate_noSamples', function() { + const series = new tr.model.PowerSeries(new tr.Model().device); + + const chart = document.createElement('tr-ui-a-frame-power-usage-chart'); + chart.setData(undefined, [0]); + + assert.isUndefined(chart.chart); + }); + + test('instantiate_noVSyncs', function() { + const series = new tr.model.PowerSeries(new tr.Model().device); + + series.addPowerSample(0, 1); + series.addPowerSample(1, 2); + series.addPowerSample(2, 3); + series.addPowerSample(3, 2); + + const chart = document.createElement('tr-ui-a-frame-power-usage-chart'); + chart.setData(new tr.model.EventSet(series.samples), []); + + assert.isUndefined(chart.chart); + }); + + test('instantiate_noSamplesOrVSyncs', function() { + const series = new tr.model.PowerSeries(new tr.Model().device); + + const chart = document.createElement('tr-ui-a-frame-power-usage-chart'); + chart.setData(undefined, []); + + assert.isUndefined(chart.chart); + }); + + test('instantiate_oneFrame', function() { + const series = new tr.model.PowerSeries(new tr.Model().device); + + const vSyncTimestamps = [0]; + series.addPowerSample(0, 1); + series.addPowerSample(1, 2); + series.addPowerSample(2, 3); + series.addPowerSample(3, 2); + + const chart = document.createElement('tr-ui-a-frame-power-usage-chart'); + chart.setData(new tr.model.EventSet(series.samples), vSyncTimestamps); + + this.addHTMLOutput(chart); + + const expectedChartData = [ + { x: 0, f1: 1 }, + { x: 1, f1: 2 }, + { x: 2, f1: 3 }, + { x: 3, f1: 2 } + ]; + assert.sameDeepMembers(chart.chart.data, expectedChartData); + }); + + test('instantiate_twoFrames', function() { + const series = new tr.model.PowerSeries(new tr.Model().device); + + const vSyncTimestamps = [0, 4]; + series.addPowerSample(0, 1); + series.addPowerSample(1, 2); + series.addPowerSample(2, 3); + series.addPowerSample(3, 2); + series.addPowerSample(4, 2); + series.addPowerSample(5, 3); + series.addPowerSample(6, 4); + series.addPowerSample(7, 3); + + const chart = document.createElement('tr-ui-a-frame-power-usage-chart'); + chart.setData(new tr.model.EventSet(series.samples), vSyncTimestamps); + + this.addHTMLOutput(chart); + + const expectedChartData = [ + { x: 0, f1: 1 }, + { x: 1, f1: 2 }, + { x: 2, f1: 3 }, + { x: 3, f1: 2 }, + { x: 0, f2: 2 }, + { x: 1, f2: 3 }, + { x: 2, f2: 4 }, + { x: 3, f2: 3 } + ]; + assert.sameDeepMembers(chart.chart.data, expectedChartData); + }); + + test('instantiate_twoFramesDifferentXValues', function() { + const series = new tr.model.PowerSeries(new tr.Model().device); + + // Power samples taken at 0, 1, 2, and 3s after frame start. + const vSyncTimestamps = [0, 4]; + series.addPowerSample(0, 1); + series.addPowerSample(1, 2); + series.addPowerSample(2, 3); + series.addPowerSample(3, 2); + // Power samples taken at 0.5, 1.5, 2.5, and 3.5s after frame start. + series.addPowerSample(4.5, 2); + series.addPowerSample(5.5, 3); + series.addPowerSample(6.5, 4); + series.addPowerSample(7.5, 3); + + const chart = document.createElement('tr-ui-a-frame-power-usage-chart'); + chart.setData(new tr.model.EventSet(series.samples), vSyncTimestamps); + + this.addHTMLOutput(chart); + + const expectedChartData = [ + { x: 0, f1: 1 }, + { x: 1, f1: 2 }, + { x: 2, f1: 3 }, + { x: 3, f1: 2 }, + { x: 0.5, f2: 2 }, + { x: 1.5, f2: 3 }, + { x: 2.5, f2: 4 }, + { x: 3.5, f2: 3 } + ]; + assert.sameDeepMembers(chart.chart.data, expectedChartData); + }); + + test('instantiate_samplesBeforeFirstVSync', function() { + const series = new tr.model.PowerSeries(new tr.Model().device); + + const vSyncTimestamps = [4]; + series.addPowerSample(0, 1); + series.addPowerSample(1, 2); + series.addPowerSample(2, 3); + series.addPowerSample(3, 2); + series.addPowerSample(4, 2); + series.addPowerSample(5, 3); + series.addPowerSample(6, 4); + series.addPowerSample(7, 3); + + const chart = document.createElement('tr-ui-a-frame-power-usage-chart'); + chart.setData(new tr.model.EventSet(series.samples), vSyncTimestamps); + + this.addHTMLOutput(chart); + + const expectedChartData = [ + { x: 0, f1: 2 }, + { x: 1, f1: 3 }, + { x: 2, f1: 4 }, + { x: 3, f1: 3 } + ]; + assert.sameDeepMembers(chart.chart.data, expectedChartData); + }); + + test('instantiate_allSamplesBeforeFirstVSync', function() { + const series = new tr.model.PowerSeries(new tr.Model().device); + + const vSyncTimestamps = [4]; + series.addPowerSample(0, 1); + series.addPowerSample(1, 2); + series.addPowerSample(2, 3); + series.addPowerSample(3, 2); + + const chart = document.createElement('tr-ui-a-frame-power-usage-chart'); + chart.setData(new tr.model.EventSet(series.samples), vSyncTimestamps); + + const expectedChartData = [ + { x: 0, f1: 2 }, + { x: 1, f1: 3 }, + { x: 2, f1: 4 }, + { x: 3, f1: 3 } + ]; + assert.isUndefined(chart.chart); + }); + + test('instantiate_vSyncsAfterLastPowerSample', function() { + const series = new tr.model.PowerSeries(new tr.Model().device); + + const vSyncTimestamps = [0, 4, 8, 12]; + series.addPowerSample(0, 1); + series.addPowerSample(1, 2); + series.addPowerSample(2, 3); + series.addPowerSample(3, 2); + series.addPowerSample(4, 2); + series.addPowerSample(5, 3); + series.addPowerSample(6, 4); + series.addPowerSample(7, 3); + + const chart = document.createElement('tr-ui-a-frame-power-usage-chart'); + chart.setData(new tr.model.EventSet(series.samples), vSyncTimestamps); + + this.addHTMLOutput(chart); + + const expectedChartData = [ + { x: 0, f1: 1 }, + { x: 1, f1: 2 }, + { x: 2, f1: 3 }, + { x: 3, f1: 2 }, + { x: 0, f2: 2 }, + { x: 1, f2: 3 }, + { x: 2, f2: 4 }, + { x: 3, f2: 3 } + ]; + assert.sameDeepMembers(chart.chart.data, expectedChartData); + }); + + test('instantiate_onlyVSyncAfterLastPowerSample', function() { + const series = new tr.model.PowerSeries(new tr.Model().device); + + const vSyncTimestamps = [8]; + series.addPowerSample(0, 1); + series.addPowerSample(1, 2); + series.addPowerSample(2, 3); + series.addPowerSample(3, 2); + series.addPowerSample(4, 2); + series.addPowerSample(5, 3); + series.addPowerSample(6, 4); + series.addPowerSample(7, 3); + + const chart = document.createElement('tr-ui-a-frame-power-usage-chart'); + chart.setData(new tr.model.EventSet(series.samples), vSyncTimestamps); + + assert.isUndefined(chart.chart); + }); + + + test('instantiate_samplesNotInChronologicalOrder', function() { + const series = new tr.model.PowerSeries(new tr.Model().device); + + const vSyncTimestamps = [0, 4]; + series.addPowerSample(4, 2); + series.addPowerSample(5, 3); + series.addPowerSample(6, 4); + series.addPowerSample(7, 3); + series.addPowerSample(0, 1); + series.addPowerSample(1, 2); + series.addPowerSample(2, 3); + series.addPowerSample(3, 2); + + const chart = document.createElement('tr-ui-a-frame-power-usage-chart'); + chart.setData(new tr.model.EventSet(series.samples), vSyncTimestamps); + + this.addHTMLOutput(chart); + + const expectedChartData = [ + { x: 0, f1: 1 }, + { x: 1, f1: 2 }, + { x: 2, f1: 3 }, + { x: 3, f1: 2 }, + { x: 0, f2: 2 }, + { x: 1, f2: 3 }, + { x: 2, f2: 4 }, + { x: 3, f2: 3 } + ]; + assert.sameDeepMembers(chart.chart.data, expectedChartData); + }); +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/generic_object_view.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/generic_object_view.html new file mode 100644 index 00000000000..e0c33df1231 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/generic_object_view.html @@ -0,0 +1,347 @@ +<!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/math/rect.html"> +<link rel="import" href="/tracing/base/scalar.html"> +<link rel="import" href="/tracing/base/utils.html"> +<link rel="import" href="/tracing/model/event_set.html"> +<link rel="import" href="/tracing/model/object_instance.html"> +<link rel="import" href="/tracing/model/object_snapshot.html"> +<link rel="import" href="/tracing/ui/analysis/analysis_link.html"> +<link rel="import" href="/tracing/ui/base/table.html"> +<link rel="import" href="/tracing/ui/base/ui.html"> +<link rel="import" href="/tracing/value/ui/scalar_span.html"> + +<dom-module id='tr-ui-a-generic-object-view'> + <template> + <style> + :host { + display: block; + font-family: monospace; + } + </style> + <div id="content"> + </div> + </template> +</dom-module> +<script> +'use strict'; + +function isTable(object) { + if (!(object instanceof Array) || + (object.length < 2)) return false; + for (const colName in object[0]) { + if (typeof colName !== 'string') return false; + } + for (let i = 0; i < object.length; ++i) { + if (!(object[i] instanceof Object)) return false; + for (const colName in object[i]) { + if (i && (object[0][colName] === undefined)) return false; + const cellType = typeof object[i][colName]; + if (cellType !== 'string' && cellType !== 'number') return false; + } + if (i) { + for (const colName in object[0]) { + if (object[i][colName] === undefined) return false; + } + } + } + return true; +} + +Polymer({ + is: 'tr-ui-a-generic-object-view', + + ready() { + this.object_ = undefined; + }, + + get object() { + return this.object_; + }, + + set object(object) { + this.object_ = object; + this.updateContents_(); + }, + + updateContents_() { + Polymer.dom(this.$.content).textContent = ''; + this.appendElementsForType_('', this.object_, 0, 0, 5, ''); + }, + + appendElementsForType_( + label, object, indent, depth, maxDepth, suffix) { + if (depth > maxDepth) { + this.appendSimpleText_( + label, indent, '<recursion limit reached>', suffix); + return; + } + + if (object === undefined) { + this.appendSimpleText_(label, indent, 'undefined', suffix); + return; + } + + if (object === null) { + this.appendSimpleText_(label, indent, 'null', suffix); + return; + } + + if (!(object instanceof Object)) { + const type = typeof object; + if (type !== 'string') { + return this.appendSimpleText_(label, indent, object, suffix); + } + let objectReplaced = false; + if ((object[0] === '{' && object[object.length - 1] === '}') || + (object[0] === '[' && object[object.length - 1] === ']')) { + try { + object = JSON.parse(object); + objectReplaced = true; + } catch (e) { + } + } + if (!objectReplaced) { + if (object.includes('\n')) { + const lines = object.split('\n'); + lines.forEach(function(line, i) { + let text; + let ioff; + let ll; + let ss; + if (i === 0) { + text = '"' + line; + ioff = 0; + ll = label; + ss = ''; + } else if (i < lines.length - 1) { + text = line; + ioff = 1; + ll = ''; + ss = ''; + } else { + text = line + '"'; + ioff = 1; + ll = ''; + ss = suffix; + } + + const el = this.appendSimpleText_( + ll, indent + ioff * label.length + ioff, text, ss); + el.style.whiteSpace = 'pre'; + return el; + }, this); + return; + } + if (tr.b.isUrl(object)) { + const link = document.createElement('a'); + link.href = object; + link.textContent = object; + this.appendElementWithLabel_(label, indent, link, suffix); + return; + } + this.appendSimpleText_( + label, indent, '"' + object + '"', suffix); + return; + } + } + + if (object instanceof tr.model.ObjectSnapshot) { + const link = document.createElement('tr-ui-a-analysis-link'); + link.selection = new tr.model.EventSet(object); + this.appendElementWithLabel_(label, indent, link, suffix); + return; + } + + if (object instanceof tr.model.ObjectInstance) { + const link = document.createElement('tr-ui-a-analysis-link'); + link.selection = new tr.model.EventSet(object); + this.appendElementWithLabel_(label, indent, link, suffix); + return; + } + + if (object instanceof tr.b.math.Rect) { + this.appendSimpleText_(label, indent, object.toString(), suffix); + return; + } + + if (object instanceof tr.b.Scalar) { + const el = this.ownerDocument.createElement('tr-v-ui-scalar-span'); + el.value = object; + el.inline = true; + this.appendElementWithLabel_(label, indent, el, suffix); + return; + } + + if (object instanceof Array) { + this.appendElementsForArray_( + label, object, indent, depth, maxDepth, suffix); + return; + } + + this.appendElementsForObject_( + label, object, indent, depth, maxDepth, suffix); + }, + + appendElementsForArray_( + label, object, indent, depth, maxDepth, suffix) { + if (object.length === 0) { + this.appendSimpleText_(label, indent, '[]', suffix); + return; + } + + if (isTable(object)) { + const table = document.createElement('tr-ui-b-table'); + const columns = []; + for (const colName of Object.keys(object[0])) { + let allStrings = true; + let allNumbers = true; + for (let i = 0; i < object.length; ++i) { + if (typeof(object[i][colName]) !== 'string') { + allStrings = false; + } + + if (typeof(object[i][colName]) !== 'number') { + allNumbers = false; + } + + if (!allStrings && !allNumbers) break; + } + + const column = {title: colName}; + column.value = function(row) { + return row[colName]; + }; + + if (allStrings) { + column.cmp = function(x, y) { + return x[colName].localeCompare(y[colName]); + }; + } else if (allNumbers) { + column.cmp = function(x, y) { + return x[colName] - y[colName]; + }; + } + columns.push(column); + } + table.tableColumns = columns; + table.tableRows = object; + this.appendElementWithLabel_(label, indent, table, suffix); + table.rebuild(); + return; + } + + this.appendElementsForType_( + label + '[', + object[0], + indent, depth + 1, maxDepth, + object.length > 1 ? ',' : ']' + suffix); + for (let i = 1; i < object.length; i++) { + this.appendElementsForType_( + '', + object[i], + indent + label.length + 1, depth + 1, maxDepth, + i < object.length - 1 ? ',' : ']' + suffix); + } + return; + }, + + appendElementsForObject_( + label, object, indent, depth, maxDepth, suffix) { + const keys = Object.keys(object); + if (keys.length === 0) { + this.appendSimpleText_(label, indent, '{}', suffix); + return; + } + + this.appendElementsForType_( + label + '{' + keys[0] + ': ', + object[keys[0]], + indent, depth, maxDepth, + keys.length > 1 ? ',' : '}' + suffix); + for (let i = 1; i < keys.length; i++) { + this.appendElementsForType_( + keys[i] + ': ', + object[keys[i]], + indent + label.length + 1, depth + 1, maxDepth, + i < keys.length - 1 ? ',' : '}' + suffix); + } + }, + + appendElementWithLabel_(label, indent, dataElement, suffix) { + const row = document.createElement('div'); + + const indentSpan = document.createElement('span'); + indentSpan.style.whiteSpace = 'pre'; + for (let i = 0; i < indent; i++) { + Polymer.dom(indentSpan).textContent += ' '; + } + Polymer.dom(row).appendChild(indentSpan); + + const labelSpan = document.createElement('span'); + Polymer.dom(labelSpan).textContent = label; + Polymer.dom(row).appendChild(labelSpan); + + Polymer.dom(row).appendChild(dataElement); + const suffixSpan = document.createElement('span'); + Polymer.dom(suffixSpan).textContent = suffix; + Polymer.dom(row).appendChild(suffixSpan); + + row.dataElement = dataElement; + Polymer.dom(this.$.content).appendChild(row); + }, + + appendSimpleText_(label, indent, text, suffix) { + const el = this.ownerDocument.createElement('span'); + Polymer.dom(el).textContent = text; + this.appendElementWithLabel_(label, indent, el, suffix); + return el; + } +}); +</script> + +<dom-module id='tr-ui-a-generic-object-view-with-label'> + <template> + <style> + :host { + display: block; + } + </style> + </template> +</dom-module> +<script> +'use strict'; + +Polymer({ + is: 'tr-ui-a-generic-object-view-with-label', + + ready() { + this.labelEl_ = document.createElement('div'); + this.genericObjectView_ = + document.createElement('tr-ui-a-generic-object-view'); + Polymer.dom(this.root).appendChild(this.labelEl_); + Polymer.dom(this.root).appendChild(this.genericObjectView_); + }, + + get label() { + return Polymer.dom(this.labelEl_).textContent; + }, + + set label(label) { + Polymer.dom(this.labelEl_).textContent = label; + }, + + get object() { + return this.genericObjectView_.object; + }, + + set object(object) { + this.genericObjectView_.object = object; + } +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/generic_object_view_test.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/generic_object_view_test.html new file mode 100644 index 00000000000..2e7812e2730 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/generic_object_view_test.html @@ -0,0 +1,222 @@ +<!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/scalar.html"> +<link rel="import" href="/tracing/base/unit.html"> +<link rel="import" href="/tracing/model/object_instance.html"> +<link rel="import" href="/tracing/ui/analysis/generic_object_view.html"> +<link rel="import" href="/tracing/ui/base/deep_utils.html"> + +<script> +'use strict'; + +tr.b.unittest.testSuite(function() { + test('undefinedValue', function() { + const view = document.createElement('tr-ui-a-generic-object-view'); + view.object = undefined; + assert.strictEqual(Polymer.dom(view.$.content).textContent, 'undefined'); + }); + + test('nullValue', function() { + const view = document.createElement('tr-ui-a-generic-object-view'); + view.object = null; + assert.strictEqual(Polymer.dom(view.$.content).textContent, 'null'); + }); + + test('stringValue', function() { + const view = document.createElement('tr-ui-a-generic-object-view'); + view.object = 'string value'; + assert.strictEqual( + Polymer.dom(view.$.content).textContent, '"string value"'); + }); + + test('multiLineStringValue', function() { + const view = document.createElement('tr-ui-a-generic-object-view'); + view.object = 'i am a\n string value\ni have\n various indents'; + this.addHTMLOutput(view); + const c = view.$.content; + }); + + test('multiLineStringValueInsideObject', function() { + const view = document.createElement('tr-ui-a-generic-object-view'); + view.object = {key: 'i am a\n string value\ni have\n various indents', + value: 'simple'}; + this.addHTMLOutput(view); + const c = view.$.content; + }); + + test('jsonObjectStringValue', function() { + const view = document.createElement('tr-ui-a-generic-object-view'); + view.object = '{"x": 1}'; + assert.strictEqual(view.$.content.children.length, 1); + assert.strictEqual(view.$.content.children[0].children.length, 4); + }); + + test('jsonArrayStringValue', function() { + const view = document.createElement('tr-ui-a-generic-object-view'); + view.object = '[1,2,3]'; + assert.strictEqual(view.$.content.children.length, 3); + }); + + test('booleanValue', function() { + const view = document.createElement('tr-ui-a-generic-object-view'); + view.object = false; + assert.strictEqual(Polymer.dom(view.$.content).textContent, 'false'); + }); + + test('numberValue', function() { + const view = document.createElement('tr-ui-a-generic-object-view'); + view.object = 3.14159; + assert.strictEqual(Polymer.dom(view.$.content).textContent, '3.14159'); + }); + + test('objectSnapshotValue', function() { + const view = document.createElement('tr-ui-a-generic-object-view'); + + const i10 = new tr.model.ObjectInstance( + {}, '0x1000', 'cat', 'name', 10); + const s10 = i10.addSnapshot(10, {foo: 1}); + + view.object = s10; + this.addHTMLOutput(view); + assert.strictEqual(view.$.content.children[0].dataElement.tagName, + 'TR-UI-A-ANALYSIS-LINK'); + }); + + test('objectInstanceValue', function() { + const view = document.createElement('tr-ui-a-generic-object-view'); + + const i10 = new tr.model.ObjectInstance( + {}, '0x1000', 'cat', 'name', 10); + const s10 = i10.addSnapshot(10, {foo: 1}); + + view.object = i10; + assert.strictEqual(view.$.content.children[0].dataElement.tagName, + 'TR-UI-A-ANALYSIS-LINK'); + }); + + test('instantiate_emptyArrayValue', function() { + const view = document.createElement('tr-ui-a-generic-object-view'); + view.object = []; + this.addHTMLOutput(view); + }); + + test('instantiate_twoValueArrayValue', function() { + const view = document.createElement('tr-ui-a-generic-object-view'); + view.object = [1, 2]; + this.addHTMLOutput(view); + }); + + test('instantiate_twoValueBArrayValue', function() { + const view = document.createElement('tr-ui-a-generic-object-view'); + view.object = [1, {x: 1}]; + this.addHTMLOutput(view); + }); + + test('instantiate_arrayValue', function() { + const view = document.createElement('tr-ui-a-generic-object-view'); + view.object = [1, 2, 'three']; + this.addHTMLOutput(view); + }); + + test('instantiate_arrayWithSimpleObjectValue', function() { + const view = document.createElement('tr-ui-a-generic-object-view'); + view.object = [{simple: 'object'}]; + this.addHTMLOutput(view); + }); + + test('instantiate_arrayWithComplexObjectValue', function() { + const view = document.createElement('tr-ui-a-generic-object-view'); + view.object = [{col0: 'object', col1: 0}, + {col2: 'Object', col3: 1}]; + this.addHTMLOutput(view); + assert.strictEqual(undefined, tr.ui.b.findDeepElementMatching( + view.$.content, 'table')); + }); + + test('instantiate_arrayWithDeepObjectValue', function() { + const view = document.createElement('tr-ui-a-generic-object-view'); + view.object = [{key: {deep: 'object values make isTable() return false'}}]; + this.addHTMLOutput(view); + assert.strictEqual(undefined, tr.ui.b.findDeepElementMatching( + view.$.content, 'table')); + }); + + test('jsonTableValue', function() { + const view = document.createElement('tr-ui-a-generic-object-view'); + view.object = [ + {col0: 'object', col1: 0, col2: 'foo'}, + {col0: 'Object', col1: 1, col2: 42} + ]; + this.addHTMLOutput(view); + + const table = tr.ui.b.findDeepElementMatching( + view.$.content, 'tr-ui-b-table'); + assert.strictEqual('col0', table.tableColumns[0].title); + assert.strictEqual('col1', table.tableColumns[1].title); + assert.strictEqual( + 'object', table.tableColumns[0].value(table.tableRows[0])); + assert.strictEqual( + 'Object', table.tableColumns[0].value(table.tableRows[1])); + assert.strictEqual(0, table.tableColumns[1].value(table.tableRows[0])); + assert.strictEqual(1, table.tableColumns[1].value(table.tableRows[1])); + assert.isDefined(table.tableColumns[0].cmp); + assert.isDefined(table.tableColumns[1].cmp); + assert.isUndefined(table.tableColumns[2].cmp); + }); + + test('instantiate_objectValue', function() { + const view = document.createElement('tr-ui-a-generic-object-view'); + view.object = { + 'entry_one': 'entry_one_value', + 'entry_two': 2, + 'entry_three': [3, 4, 5] + }; + this.addHTMLOutput(view); + }); + + test('timeDurationValue', function() { + const view = document.createElement('tr-ui-a-generic-object-view'); + view.object = + new tr.b.Scalar(tr.b.Unit.byName.timeDurationInMs, 3); + this.addHTMLOutput(view); + assert.isDefined(tr.ui.b.findDeepElementMatching( + view.$.content, 'tr-v-ui-scalar-span')); + }); + + test('timeStampValue', function() { + const view = document.createElement('tr-ui-a-generic-object-view'); + view.object = new tr.b.Scalar(tr.b.Unit.byName.timeStampInMs, 3); + this.addHTMLOutput(view); + assert.isDefined(tr.ui.b.findDeepElementMatching( + view.$.content, 'tr-v-ui-scalar-span')); + }); + + test('scalarValue', function() { + const view = document.createElement('tr-ui-a-generic-object-view'); + view.object = + new tr.b.Scalar(tr.b.Unit.byName.normalizedPercentage, .3); + this.addHTMLOutput(view); + const m = tr.ui.b.findDeepElementMatching( + view.$.content, 'tr-v-ui-scalar-span'); + assert.isDefined(m); + assert.strictEqual(m.value, .3); + assert.strictEqual(m.unit, tr.b.Unit.byName.normalizedPercentage); + }); + + test('httpLink', function() { + const view = document.createElement('tr-ui-a-generic-object-view'); + const url = 'https://google.com/chrome'; + view.object = {a: url}; + this.addHTMLOutput(view); + const a = tr.ui.b.findDeepElementMatching(view.$.content, 'a'); + assert.isDefined(a); + assert.strictEqual(url, a.href); + assert.strictEqual(url, a.textContent); + }); +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_allocator_details_pane.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_allocator_details_pane.html new file mode 100644 index 00000000000..88b3c3ccc7c --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_allocator_details_pane.html @@ -0,0 +1,893 @@ +<!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/base/math/range.html"> +<link rel="import" href="/tracing/base/unit.html"> +<link rel="import" href="/tracing/base/utils.html"> +<link rel="import" href="/tracing/model/memory_allocator_dump.html"> +<link rel="import" href="/tracing/ui/analysis/memory_dump_heap_details_pane.html"> +<link rel="import" href="/tracing/ui/analysis/memory_dump_sub_view_util.html"> +<link rel="import" href="/tracing/ui/analysis/stacked_pane.html"> +<link rel="import" href="/tracing/ui/base/dom_helpers.html"> +<link rel="import" href="/tracing/ui/base/table.html"> + + +<dom-module id='tr-ui-a-memory-dump-allocator-details-pane'> + <template> + <style> + :host { + display: flex; + flex-direction: column; + } + + #label { + flex: 0 0 auto; + padding: 8px; + + background-color: #eee; + border-bottom: 1px solid #8e8e8e; + border-top: 1px solid white; + + font-size: 15px; + font-weight: bold; + } + + #contents { + flex: 1 0 auto; + align-self: stretch; + font-size: 12px; + } + + #info_text { + padding: 8px; + color: #666; + font-style: italic; + text-align: center; + } + + #table { + display: none; /* Hide until memory allocator dumps are set. */ + flex: 1 0 auto; + align-self: stretch; + font-size: 12px; + } + </style> + <div id="label">Component details</div> + <div id="contents"> + <div id="info_text">No memory allocator dump selected</div> + <tr-ui-b-table id="table"></tr-ui-b-table> + </div> + </template> +</dom-module> +<script> +'use strict'; + +tr.exportTo('tr.ui.analysis', function() { + // Link to docs. + const URL_TO_SIZE_VS_EFFECTIVE_SIZE = 'https://chromium.googlesource.com/chromium/src/+/master/docs/memory-infra/README.md#effective_size-vs_size'; + + // Constant representing the context in suballocation rows. + const SUBALLOCATION_CONTEXT = true; + + // Size numeric info types. + const MemoryAllocatorDumpInfoType = tr.model.MemoryAllocatorDumpInfoType; + const PROVIDED_SIZE_LESS_THAN_AGGREGATED_CHILDREN = + MemoryAllocatorDumpInfoType.PROVIDED_SIZE_LESS_THAN_AGGREGATED_CHILDREN; + const PROVIDED_SIZE_LESS_THAN_LARGEST_OWNER = + MemoryAllocatorDumpInfoType.PROVIDED_SIZE_LESS_THAN_LARGEST_OWNER; + + // Unicode symbols used for memory cell info icons and messages. + const LEFTWARDS_OPEN_HEADED_ARROW = String.fromCharCode(0x21FD); + const RIGHTWARDS_OPEN_HEADED_ARROW = String.fromCharCode(0x21FE); + const EN_DASH = String.fromCharCode(0x2013); + const CIRCLED_LATIN_SMALL_LETTER_I = String.fromCharCode(0x24D8); + + /** @constructor */ + function AllocatorDumpNameColumn() { + tr.ui.analysis.TitleColumn.call(this, 'Component'); + } + + AllocatorDumpNameColumn.prototype = { + __proto__: tr.ui.analysis.TitleColumn.prototype, + + formatTitle(row) { + if (!row.suballocation) { + return row.title; + } + return tr.ui.b.createSpan({ + textContent: row.title, + italic: true, + tooltip: row.fullNames === undefined ? + undefined : row.fullNames.join(', ') + }); + } + }; + + /** + * Retrieve the entry associated with a given name from a map and increment + * its count. + * + * If there is no entry associated with the name, a new entry is created, the + * creation callback is called, the entry's count is incremented (from 0 to + * 1) and the newly created entry is returned. + */ + function getAndUpdateEntry(map, name, createdCallback) { + let entry = map.get(name); + if (entry === undefined) { + entry = {count: 0}; + createdCallback(entry); + map.set(name, entry); + } + entry.count++; + return entry; + } + + /** + * Helper class for building size and effective size column info messages. + * + * @constructor + */ + function SizeInfoMessageBuilder() { + this.parts_ = []; + this.indent_ = 0; + } + + SizeInfoMessageBuilder.prototype = { + append(/* arguments */) { + this.parts_.push.apply( + this.parts_, Array.prototype.slice.apply(arguments)); + }, + + /** + * Append the entries of a map to the message according to the following + * rules: + * + * 1. If the map is empty, append emptyText to the message (if provided). + * Examples: + * + * emptyText=undefined + * Hello, World! ====================> Hello, World! + * + * emptyText='empty' + * The bottle is ====================> The bottle is empty + * + * 2. If the map contains a single entry, append a space and call + * itemCallback on the entry (which is in turn expected to append a + * message for the entry). Example: + * + * Please do not ====================> Please do not [item-message] + * + * 3. If the map contains multiple entries, append them as a list + * with itemCallback called on each entry. If hasPluralSuffix is true, + * 's' will be appended to the message before the list. Examples: + * + * hasPluralSuffix=false + * I need to buy ====================> I need to buy: + * - [item1-message] + * - [item2-message] + * [...] + * - [itemN-message] + * + * hasPluralSuffix=true + * Suspected CL ====================> Suspected CLs: + * - [item1-message] + * - [item2-message] + * [...] + * - [itemN-message] + */ + appendMap( + map, hasPluralSuffix, emptyText, itemCallback, opt_this) { + opt_this = opt_this || this; + if (map.size === 0) { + if (emptyText) { + this.append(emptyText); + } + } else if (map.size === 1) { + this.parts_.push(' '); + const key = map.keys().next().value; + itemCallback.call(opt_this, key, map.get(key)); + } else { + if (hasPluralSuffix) { + this.parts_.push('s'); + } + this.parts_.push(':'); + this.indent_++; + for (const key of map.keys()) { + this.parts_.push('\n', ' '.repeat(3 * (this.indent_ - 1)), ' - '); + itemCallback.call(opt_this, key, map.get(key)); + } + this.indent_--; + } + }, + + appendImportanceRange(range) { + this.append(' (importance: '); + if (range.min === range.max) { + this.append(range.min); + } else { + this.append(range.min, EN_DASH, range.max); + } + this.append(')'); + }, + + appendSizeIfDefined(size) { + if (size !== undefined) { + this.append(' (', tr.b.Unit.byName.sizeInBytes.format(size), ')'); + } + }, + + appendSomeTimestampsQuantifier() { + this.append( + ' ', tr.ui.analysis.MemoryColumn.SOME_TIMESTAMPS_INFO_QUANTIFIER); + }, + + build() { + return this.parts_.join(''); + } + }; + + /** @constructor */ + function EffectiveSizeColumn(name, cellPath, aggregationMode) { + tr.ui.analysis.DetailsNumericMemoryColumn.call( + this, name, cellPath, aggregationMode); + } + + EffectiveSizeColumn.prototype = { + __proto__: tr.ui.analysis.DetailsNumericMemoryColumn.prototype, + + get title() { + return tr.ui.b.createLink({ + textContent: this.name, + tooltip: 'Memory used by this component', + href: URL_TO_SIZE_VS_EFFECTIVE_SIZE + }); + }, + + addInfos(numerics, memoryAllocatorDumps, infos) { + if (memoryAllocatorDumps === undefined) return; + + // Quantified name of an owner dump (of the given dump) -> {count, + // importanceRange}. + const ownerNameToEntry = new Map(); + + // Quantified name of an owned dump (by the given dump) -> {count, + // importanceRange, sharerNameToEntry}, where sharerNameToEntry is a map + // from quantified names of other owners of the owned dump to {count, + // importanceRange}. + const ownedNameToEntry = new Map(); + + for (let i = 0; i < numerics.length; i++) { + if (numerics[i] === undefined) continue; + + const dump = memoryAllocatorDumps[i]; + if (dump === SUBALLOCATION_CONTEXT) { + return; // No ownership of suballocation internal rows. + } + + // Gather owners of this dump. + dump.ownedBy.forEach(function(ownerLink) { + const ownerDump = ownerLink.source; + this.getAndUpdateOwnershipEntry_( + ownerNameToEntry, ownerDump, ownerLink); + }, this); + + // Gather dumps owned by this dump and other owner dumps sharing them + // (with this dump). + const ownedLink = dump.owns; + if (ownedLink !== undefined) { + const ownedDump = ownedLink.target; + const ownedEntry = this.getAndUpdateOwnershipEntry_(ownedNameToEntry, + ownedDump, ownedLink, true /* opt_withSharerNameToEntry */); + const sharerNameToEntry = ownedEntry.sharerNameToEntry; + ownedDump.ownedBy.forEach(function(sharerLink) { + const sharerDump = sharerLink.source; + if (sharerDump === dump) return; + this.getAndUpdateOwnershipEntry_( + sharerNameToEntry, sharerDump, sharerLink); + }, this); + } + } + + // Emit a single info listing all owners of this dump. + if (ownerNameToEntry.size > 0) { + const messageBuilder = new SizeInfoMessageBuilder(); + messageBuilder.append('shared by'); + messageBuilder.appendMap( + ownerNameToEntry, + false /* hasPluralSuffix */, + undefined /* emptyText */, + function(ownerName, ownerEntry) { + messageBuilder.append(ownerName); + if (ownerEntry.count < numerics.length) { + messageBuilder.appendSomeTimestampsQuantifier(); + } + messageBuilder.appendImportanceRange(ownerEntry.importanceRange); + }, this); + infos.push({ + message: messageBuilder.build(), + icon: LEFTWARDS_OPEN_HEADED_ARROW, + color: 'green' + }); + } + + // Emit a single info listing all dumps owned by this dump together + // with list(s) of other owner dumps sharing them with this dump. + if (ownedNameToEntry.size > 0) { + const messageBuilder = new SizeInfoMessageBuilder(); + messageBuilder.append('shares'); + messageBuilder.appendMap( + ownedNameToEntry, + false /* hasPluralSuffix */, + undefined /* emptyText */, + function(ownedName, ownedEntry) { + messageBuilder.append(ownedName); + const ownedCount = ownedEntry.count; + if (ownedCount < numerics.length) { + messageBuilder.appendSomeTimestampsQuantifier(); + } + messageBuilder.appendImportanceRange(ownedEntry.importanceRange); + messageBuilder.append(' with'); + messageBuilder.appendMap( + ownedEntry.sharerNameToEntry, + false /* hasPluralSuffix */, + ' no other dumps', + function(sharerName, sharerEntry) { + messageBuilder.append(sharerName); + if (sharerEntry.count < ownedCount) { + messageBuilder.appendSomeTimestampsQuantifier(); + } + messageBuilder.appendImportanceRange( + sharerEntry.importanceRange); + }, this); + }, this); + infos.push({ + message: messageBuilder.build(), + icon: RIGHTWARDS_OPEN_HEADED_ARROW, + color: 'green' + }); + } + }, + + getAndUpdateOwnershipEntry_( + map, dump, link, opt_withSharerNameToEntry) { + const entry = getAndUpdateEntry(map, dump.quantifiedName, + function(newEntry) { + newEntry.importanceRange = new tr.b.math.Range(); + if (opt_withSharerNameToEntry) { + newEntry.sharerNameToEntry = new Map(); + } + }); + entry.importanceRange.addValue(link.importance || 0); + return entry; + } + }; + + /** @constructor */ + function SizeColumn(name, cellPath, aggregationMode) { + tr.ui.analysis.DetailsNumericMemoryColumn.call( + this, name, cellPath, aggregationMode); + } + + SizeColumn.prototype = { + __proto__: tr.ui.analysis.DetailsNumericMemoryColumn.prototype, + + get title() { + return tr.ui.b.createLink({ + textContent: this.name, + tooltip: 'Memory requested by this component', + href: URL_TO_SIZE_VS_EFFECTIVE_SIZE + }); + }, + + addInfos(numerics, memoryAllocatorDumps, infos) { + if (memoryAllocatorDumps === undefined) return; + this.addOverlapInfo_(numerics, memoryAllocatorDumps, infos); + this.addProvidedSizeWarningInfos_(numerics, memoryAllocatorDumps, infos); + }, + + addOverlapInfo_(numerics, memoryAllocatorDumps, infos) { + // Sibling allocator dump name -> {count, size}. The latter field (size) + // is omitted in multi-selection mode. + const siblingNameToEntry = new Map(); + for (let i = 0; i < numerics.length; i++) { + if (numerics[i] === undefined) continue; + const dump = memoryAllocatorDumps[i]; + if (dump === SUBALLOCATION_CONTEXT) { + return; // No ownership of suballocation internal rows. + } + const ownedBySiblingSizes = dump.ownedBySiblingSizes; + for (const siblingDump of ownedBySiblingSizes.keys()) { + const siblingName = siblingDump.name; + getAndUpdateEntry(siblingNameToEntry, siblingName, + function(newEntry) { + if (numerics.length === 1 /* single-selection mode */) { + newEntry.size = ownedBySiblingSizes.get(siblingDump); + } + }); + } + } + + // Emit a single info describing all overlaps with siblings (if + // applicable). + if (siblingNameToEntry.size > 0) { + const messageBuilder = new SizeInfoMessageBuilder(); + messageBuilder.append('overlaps with its sibling'); + messageBuilder.appendMap( + siblingNameToEntry, + true /* hasPluralSuffix */, + undefined /* emptyText */, + function(siblingName, siblingEntry) { + messageBuilder.append('\'', siblingName, '\''); + messageBuilder.appendSizeIfDefined(siblingEntry.size); + if (siblingEntry.count < numerics.length) { + messageBuilder.appendSomeTimestampsQuantifier(); + } + }, this); + infos.push({ + message: messageBuilder.build(), + icon: CIRCLED_LATIN_SMALL_LETTER_I, + color: 'blue' + }); + } + }, + + addProvidedSizeWarningInfos_(numerics, memoryAllocatorDumps, + infos) { + // Info type (see MemoryAllocatorDumpInfoType) -> {count, providedSize, + // dependencySize}. The latter two fields (providedSize and + // dependencySize) are omitted in multi-selection mode. + const infoTypeToEntry = new Map(); + for (let i = 0; i < numerics.length; i++) { + if (numerics[i] === undefined) continue; + const dump = memoryAllocatorDumps[i]; + if (dump === SUBALLOCATION_CONTEXT) { + return; // Suballocation internal rows have no provided size. + } + dump.infos.forEach(function(dumpInfo) { + getAndUpdateEntry(infoTypeToEntry, dumpInfo.type, function(newEntry) { + if (numerics.length === 1 /* single-selection mode */) { + newEntry.providedSize = dumpInfo.providedSize; + newEntry.dependencySize = dumpInfo.dependencySize; + } + }); + }); + } + + // Emit a warning info for every info type. + for (const infoType of infoTypeToEntry.keys()) { + const entry = infoTypeToEntry.get(infoType); + const messageBuilder = new SizeInfoMessageBuilder(); + messageBuilder.append('provided size'); + messageBuilder.appendSizeIfDefined(entry.providedSize); + let dependencyName; + switch (infoType) { + case PROVIDED_SIZE_LESS_THAN_AGGREGATED_CHILDREN: + dependencyName = 'the aggregated size of the children'; + break; + case PROVIDED_SIZE_LESS_THAN_LARGEST_OWNER: + dependencyName = 'the size of the largest owner'; + break; + default: + dependencyName = 'an unknown dependency'; + break; + } + messageBuilder.append(' was less than ', dependencyName); + messageBuilder.appendSizeIfDefined(entry.dependencySize); + if (entry.count < numerics.length) { + messageBuilder.appendSomeTimestampsQuantifier(); + } + infos.push(tr.ui.analysis.createWarningInfo(messageBuilder.build())); + } + } + }; + + const NUMERIC_COLUMN_RULES = [ + { + condition: tr.model.MemoryAllocatorDump.EFFECTIVE_SIZE_NUMERIC_NAME, + importance: 10, + columnConstructor: EffectiveSizeColumn + }, + { + condition: tr.model.MemoryAllocatorDump.SIZE_NUMERIC_NAME, + importance: 9, + columnConstructor: SizeColumn + }, + { + condition: 'page_size', + importance: 0, + columnConstructor: tr.ui.analysis.DetailsNumericMemoryColumn + }, + { + condition: /size/, + importance: 5, + columnConstructor: tr.ui.analysis.DetailsNumericMemoryColumn + }, + { + // All other columns. + importance: 0, + columnConstructor: tr.ui.analysis.DetailsNumericMemoryColumn + } + ]; + + const DIAGNOSTIC_COLUMN_RULES = [ + { + importance: 0, + columnConstructor: tr.ui.analysis.StringMemoryColumn + } + ]; + + Polymer({ + is: 'tr-ui-a-memory-dump-allocator-details-pane', + behaviors: [tr.ui.analysis.StackedPane], + + created() { + this.memoryAllocatorDumps_ = undefined; + this.heapDumps_ = undefined; + this.aggregationMode_ = undefined; + }, + + ready() { + this.$.table.selectionMode = tr.ui.b.TableFormat.SelectionMode.ROW; + }, + + /** + * Sets the memory allocator dumps and schedules rebuilding the pane. + * + * The provided value should be a chronological list of memory allocator + * dumps. All dumps are assumed to belong to the same process and have + * the same full name. Example: + * + * [ + * tr.model.MemoryAllocatorDump {}, // MAD at timestamp 1. + * undefined, // MAD not provided at timestamp 2. + * tr.model.MemoryAllocatorDump {}, // MAD at timestamp 3. + * ] + */ + set memoryAllocatorDumps(memoryAllocatorDumps) { + this.memoryAllocatorDumps_ = memoryAllocatorDumps; + this.scheduleRebuild_(); + }, + + get memoryAllocatorDumps() { + return this.memoryAllocatorDumps_; + }, + + // TODO(petrcermak): Don't plumb the heap dumps through the allocator + // details pane. Maybe add support for multiple child panes to stacked pane + // (view) instead. + set heapDumps(heapDumps) { + this.heapDumps_ = heapDumps; + this.scheduleRebuild_(); + }, + + set aggregationMode(aggregationMode) { + this.aggregationMode_ = aggregationMode; + this.scheduleRebuild_(); + }, + + get aggregationMode() { + return this.aggregationMode_; + }, + + onRebuild_() { + if (this.memoryAllocatorDumps_ === undefined || + this.memoryAllocatorDumps_.length === 0) { + // Show the info text (hide the table). + this.$.info_text.style.display = 'block'; + this.$.table.style.display = 'none'; + + this.$.table.clear(); + this.$.table.rebuild(); + + // Hide the heap details pane (if applicable). + this.childPaneBuilder = undefined; + return; + } + + // Show the table (hide the info text). + this.$.info_text.style.display = 'none'; + this.$.table.style.display = 'block'; + + const rows = this.createRows_(); + const columns = this.createColumns_(rows); + rows.forEach(function(rootRow) { + tr.ui.analysis.aggregateTableRowCellsRecursively(rootRow, columns, + function(contexts) { + // Only aggregate suballocation rows (numerics of regular rows + // corresponding to MADs have already been aggregated by the + // model in MemoryAllocatorDump.aggregateNumericsRecursively). + return contexts !== undefined && contexts.some(function(context) { + return context === SUBALLOCATION_CONTEXT; + }); + }); + }); + + this.$.table.tableRows = rows; + this.$.table.tableColumns = columns; + this.$.table.rebuild(); + tr.ui.analysis.expandTableRowsRecursively(this.$.table); + + // Show/hide the heap details pane. + if (this.heapDumps_ === undefined) { + this.childPaneBuilder = undefined; + } else { + this.childPaneBuilder = function() { + const pane = + document.createElement('tr-ui-a-memory-dump-heap-details-pane'); + pane.heapDumps = this.heapDumps_; + pane.aggregationMode = this.aggregationMode_; + return pane; + }.bind(this); + } + }, + + createRows_() { + return [ + this.createAllocatorRowRecursively_(this.memoryAllocatorDumps_) + ]; + }, + + createAllocatorRowRecursively_(dumps) { + // Get the name of the memory allocator dumps. We can use any defined + // dump in dumps since they all have the same name. + const definedDump = dumps.find(x => x); + const title = definedDump.name; + const fullName = definedDump.fullName; + + // Transform a chronological list of memory allocator dumps into two + // dictionaries of cells (where each cell contains a chronological list + // of the values of one of its numerics or diagnostics). + const numericCells = tr.ui.analysis.createCells(dumps, function(dump) { + return dump.numerics; + }); + const diagnosticCells = tr.ui.analysis.createCells(dumps, function(dump) { + return dump.diagnostics; + }); + + // Determine whether the memory allocator dump is a suballocation. A + // dump is assumed to be a suballocation if (1) its name starts with + // two underscores, (2) it has an owner from within the same process at + // some timestamp, and (3) it is undefined, has no owners, or has the + // same owner (and no other owners) at all other timestamps. + let suballocatedBy = undefined; + if (title.startsWith('__')) { + for (let i = 0; i < dumps.length; i++) { + const dump = dumps[i]; + if (dump === undefined || dump.ownedBy.length === 0) { + // Ignore timestamps where the dump is undefined or doesn't + // have any owner. + continue; + } + const ownerDump = dump.ownedBy[0].source; + if (dump.ownedBy.length > 1 || + dump.children.length > 0 || + ownerDump.containerMemoryDump !== dump.containerMemoryDump) { + // If the dump has (1) any children, (2) multiple owners, or + // (3) its owner is in a different process (otherwise, the + // modified title would be ambiguous), then it's not considered + // to be a suballocation. + suballocatedBy = undefined; + break; + } + if (suballocatedBy === undefined) { + suballocatedBy = ownerDump.fullName; + } else if (suballocatedBy !== ownerDump.fullName) { + // The full name of the owner dump changed over time, so this + // dump is not a suballocation. + suballocatedBy = undefined; + break; + } + } + } + + const row = { + title, + fullNames: [fullName], + contexts: dumps, + numericCells, + diagnosticCells, + suballocatedBy + }; + + // Child memory dump name (dict key) -> Timestamp (list index) -> + // Child dump. + const childDumpNameToDumps = tr.b.invertArrayOfDicts(dumps, + function(dump) { + const results = {}; + for (const child of dump.children) { + results[child.name] = child; + } + return results; + }); + + // Recursively create sub-rows for children (if applicable). + const subRows = []; + let suballocationClassificationRootNode = undefined; + for (const childDumps of Object.values(childDumpNameToDumps)) { + const childRow = this.createAllocatorRowRecursively_(childDumps); + if (childRow.suballocatedBy === undefined) { + // Not a suballocation row: just append it. + subRows.push(childRow); + } else { + // Suballocation row: classify it in a tree of suballocations. + suballocationClassificationRootNode = + this.classifySuballocationRow_( + childRow, suballocationClassificationRootNode); + } + } + + // Build the tree of suballocations (if applicable). + if (suballocationClassificationRootNode !== undefined) { + const suballocationRow = this.createSuballocationRowRecursively_( + 'suballocations', suballocationClassificationRootNode); + subRows.push(suballocationRow); + } + + if (subRows.length > 0) { + row.subRows = subRows; + } + + return row; + }, + + classifySuballocationRow_(suballocationRow, rootNode) { + if (rootNode === undefined) { + rootNode = { + children: {}, + row: undefined + }; + } + + const suballocationLevels = suballocationRow.suballocatedBy.split('/'); + let currentNode = rootNode; + for (let i = 0; i < suballocationLevels.length; i++) { + const suballocationLevel = suballocationLevels[i]; + let nextNode = currentNode.children[suballocationLevel]; + if (nextNode === undefined) { + currentNode.children[suballocationLevel] = nextNode = { + children: {}, + row: undefined + }; + } + currentNode = nextNode; + } + + const existingRow = currentNode.row; + if (existingRow !== undefined) { + // On rare occasions it can happen that one dump (e.g. sqlite) owns + // different suballocations at different timestamps (e.g. + // malloc/allocated_objects/_7d35 and malloc/allocated_objects/_511e). + // When this happens, we merge the two suballocations into a single row + // (malloc/allocated_objects/suballocations/sqlite). + for (let i = 0; i < suballocationRow.contexts.length; i++) { + const newContext = suballocationRow.contexts[i]; + if (newContext === undefined) continue; + + if (existingRow.contexts[i] !== undefined) { + throw new Error('Multiple suballocations with the same owner name'); + } + + existingRow.contexts[i] = newContext; + ['numericCells', 'diagnosticCells'].forEach(function(cellKey) { + const suballocationCells = suballocationRow[cellKey]; + if (suballocationCells === undefined) return; + for (const [cellName, cell] of Object.entries(suballocationCells)) { + if (cell === undefined) continue; + const fields = cell.fields; + if (fields === undefined) continue; + const field = fields[i]; + if (field === undefined) continue; + let existingCells = existingRow[cellKey]; + if (existingCells === undefined) { + existingCells = {}; + existingRow[cellKey] = existingCells; + } + let existingCell = existingCells[cellName]; + if (existingCell === undefined) { + existingCell = new tr.ui.analysis.MemoryCell( + new Array(fields.length)); + existingCells[cellName] = existingCell; + } + existingCell.fields[i] = field; + } + }); + } + existingRow.fullNames.push.apply( + existingRow.fullNames, suballocationRow.fullNames); + } else { + currentNode.row = suballocationRow; + } + + return rootNode; + }, + + createSuballocationRowRecursively_(name, node) { + const childCount = Object.keys(node.children).length; + if (childCount === 0) { + if (node.row === undefined) { + throw new Error('Suballocation node must have a row or children'); + } + // Leaf row of the suballocation tree: Change the row's title from + // '__MEANINGLESSHASH' to the name of the suballocation owner. + const row = node.row; + row.title = name; + row.suballocation = true; + return row; + } + + // Internal row of the suballocation tree: Recursively create its + // sub-rows. + const subRows = []; + for (const [subName, subNode] of Object.entries(node.children)) { + subRows.push(this.createSuballocationRowRecursively_(subName, subNode)); + } + + if (node.row !== undefined) { + // Very unlikely case: Both an ancestor (e.g. 'skia') and one of its + // descendants (e.g. 'skia/sk_glyph_cache') both suballocate from the + // same MemoryAllocatorDump (e.g. 'malloc/allocated_objects'). In + // this case, the suballocation from the ancestor must be mapped to + // 'malloc/allocated_objects/suballocations/skia/<unspecified>' so + // that 'malloc/allocated_objects/suballocations/skia' could + // aggregate the numerics of the two suballocations properly. + const row = node.row; + row.title = '<unspecified>'; + row.suballocation = true; + subRows.unshift(row); + } + + // An internal row of the suballocation tree is assumed to be defined + // at a given timestamp if at least one of its sub-rows is defined at + // the timestamp. + const contexts = new Array(subRows[0].contexts.length); + for (let i = 0; i < subRows.length; i++) { + subRows[i].contexts.forEach(function(subContext, index) { + if (subContext !== undefined) { + contexts[index] = SUBALLOCATION_CONTEXT; + } + }); + } + + return { + title: name, + suballocation: true, + contexts, + subRows + }; + }, + + createColumns_(rows) { + const titleColumn = new AllocatorDumpNameColumn(); + titleColumn.width = '200px'; + + const numericColumns = tr.ui.analysis.MemoryColumn.fromRows(rows, { + cellKey: 'numericCells', + aggregationMode: this.aggregationMode_, + rules: NUMERIC_COLUMN_RULES + }); + const diagnosticColumns = tr.ui.analysis.MemoryColumn.fromRows(rows, { + cellKey: 'diagnosticCells', + aggregationMode: this.aggregationMode_, + rules: DIAGNOSTIC_COLUMN_RULES + }); + const fieldColumns = numericColumns.concat(diagnosticColumns); + tr.ui.analysis.MemoryColumn.spaceEqually(fieldColumns); + + const columns = [titleColumn].concat(fieldColumns); + return columns; + } + }); + + return { + // All exports are for testing only. + SUBALLOCATION_CONTEXT, + AllocatorDumpNameColumn, + EffectiveSizeColumn, + SizeColumn, + }; +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_allocator_details_pane_test.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_allocator_details_pane_test.html new file mode 100644 index 00000000000..6fda765b34b --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_allocator_details_pane_test.html @@ -0,0 +1,1261 @@ +<!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/base/scalar.html"> +<link rel="import" href="/tracing/base/unit.html"> +<link rel="import" href="/tracing/base/utils.html"> +<link rel="import" href="/tracing/core/test_utils.html"> +<link rel="import" href="/tracing/model/heap_dump.html"> +<link rel="import" href="/tracing/model/memory_allocator_dump.html"> +<link rel="import" href="/tracing/model/memory_dump_test_utils.html"> +<link rel="import" href="/tracing/ui/analysis/memory_dump_allocator_details_pane.html"> +<link rel="import" href="/tracing/ui/analysis/memory_dump_sub_view_test_utils.html"> +<link rel="import" href="/tracing/ui/analysis/memory_dump_sub_view_util.html"> + +<script> +'use strict'; + +tr.b.unittest.testSuite(function() { + const MemoryAllocatorDump = tr.model.MemoryAllocatorDump; + const Scalar = tr.b.Scalar; + const unitlessNumber_smallerIsBetter = + tr.b.Unit.byName.unitlessNumber_smallerIsBetter; + const sizeInBytes_smallerIsBetter = + tr.b.Unit.byName.sizeInBytes_smallerIsBetter; + const HeapDump = tr.model.HeapDump; + const AggregationMode = tr.ui.analysis.MemoryColumn.AggregationMode; + const checkNumericFields = tr.ui.analysis.checkNumericFields; + const checkSizeNumericFields = tr.ui.analysis.checkSizeNumericFields; + const checkStringFields = tr.ui.analysis.checkStringFields; + const checkColumnInfosAndColor = tr.ui.analysis.checkColumnInfosAndColor; + const checkColumns = tr.ui.analysis.checkColumns; + const isElementDisplayed = tr.ui.analysis.isElementDisplayed; + const AllocatorDumpNameColumn = tr.ui.analysis.AllocatorDumpNameColumn; + const EffectiveSizeColumn = tr.ui.analysis.EffectiveSizeColumn; + const SizeColumn = tr.ui.analysis.SizeColumn; + const StringMemoryColumn = tr.ui.analysis.StringMemoryColumn; + const NumericMemoryColumn = tr.ui.analysis.NumericMemoryColumn; + const addGlobalMemoryDump = tr.model.MemoryDumpTestUtils.addGlobalMemoryDump; + const addProcessMemoryDump = + tr.model.MemoryDumpTestUtils.addProcessMemoryDump; + const newAllocatorDump = tr.model.MemoryDumpTestUtils.newAllocatorDump; + const addChildDump = tr.model.MemoryDumpTestUtils.addChildDump; + const addOwnershipLink = tr.model.MemoryDumpTestUtils.addOwnershipLink; + + const SUBALLOCATION_CONTEXT = tr.ui.analysis.SUBALLOCATION_CONTEXT; + const MemoryAllocatorDumpInfoType = tr.model.MemoryAllocatorDumpInfoType; + const PROVIDED_SIZE_LESS_THAN_AGGREGATED_CHILDREN = + MemoryAllocatorDumpInfoType.PROVIDED_SIZE_LESS_THAN_AGGREGATED_CHILDREN; + const PROVIDED_SIZE_LESS_THAN_LARGEST_OWNER = + MemoryAllocatorDumpInfoType.PROVIDED_SIZE_LESS_THAN_LARGEST_OWNER; + + function addRootDumps(containerMemoryDump, rootNames, addedCallback) { + // Test sanity check. + assert.isUndefined(containerMemoryDump.memoryAllocatorDumps); + + const rootDumps = rootNames.map(function(rootName) { + return new MemoryAllocatorDump(containerMemoryDump, rootName); + }); + addedCallback.apply(null, rootDumps); + containerMemoryDump.memoryAllocatorDumps = rootDumps; + } + + function newSuballocationDump(ownerDump, parentDump, name, size) { + const suballocationDump = addChildDump(parentDump, name, + {numerics: {size}}); + if (ownerDump !== undefined) { + addOwnershipLink(ownerDump, suballocationDump); + } + return suballocationDump; + } + + function createProcessMemoryDumps() { + const model = tr.c.TestUtils.newModel(function(model) { + const process = model.getOrCreateProcess(1); + + // First timestamp. + const gmd1 = addGlobalMemoryDump(model, {ts: -10}); + const pmd1 = addProcessMemoryDump(gmd1, process, {ts: -11}); + pmd1.memoryAllocatorDumps = (function() { + const v8Dump = newAllocatorDump(pmd1, 'v8', {numerics: { + size: 1073741824 /* 1 GiB */, + inner_size: 2097152 /* 2 MiB */, + objects_count: new Scalar(unitlessNumber_smallerIsBetter, 204) + }}); + + const v8HeapsDump = addChildDump(v8Dump, 'heaps', + {numerics: {size: 805306368 /* 768 MiB */}}); + addChildDump(v8HeapsDump, 'heap42', + {numerics: {size: 804782080 /* 767.5 MiB */}}); + + const v8ObjectsDump = addChildDump(v8Dump, 'objects'); + v8ObjectsDump.addDiagnostic('url', 'http://example.com'); + addChildDump(v8ObjectsDump, 'foo', + {numerics: {size: 1022976 /* 999 KiB */}}); + addChildDump(v8ObjectsDump, 'bar', + {numerics: {size: 1024000 /* 1000 KiB */}}); + + const oilpanDump = newAllocatorDump(pmd1, 'oilpan', + {numerics: {size: 125829120 /* 120 MiB */}}); + newSuballocationDump( + oilpanDump, v8Dump, '__99BEAD', 150994944 /* 144 MiB */); + + const oilpanSubDump = addChildDump(oilpanDump, 'animals'); + + const oilpanSubDump1 = addChildDump(oilpanSubDump, 'cow', + {numerics: {size: 33554432 /* 32 MiB */}}); + newSuballocationDump( + oilpanSubDump1, v8Dump, '__42BEEF', 67108864 /* 64 MiB */); + + const oilpanSubDump2 = addChildDump(oilpanSubDump, 'chicken', + {numerics: {size: 16777216 /* 16 MiB */}}); + newSuballocationDump( + oilpanSubDump2, v8Dump, '__68DEAD', 33554432 /* 32 MiB */); + + const skiaDump = newAllocatorDump(pmd1, 'skia', + {numerics: {size: 8388608 /* 8 MiB */}}); + const suballocationDump = newSuballocationDump( + skiaDump, v8Dump, '__15FADE', 16777216 /* 16 MiB */); + + const ccDump = newAllocatorDump(pmd1, 'cc', + {numerics: {size: 4194304 /* 4 MiB */}}); + newSuballocationDump( + ccDump, v8Dump, '__12FEED', 5242880 /* 5 MiB */).addDiagnostic( + 'url', 'localhost:1234'); + + return [v8Dump, oilpanDump, skiaDump, ccDump]; + })(); + + // Second timestamp. + const gmd2 = addGlobalMemoryDump(model, {ts: 10}); + const pmd2 = addProcessMemoryDump(gmd2, process, {ts: 11}); + pmd2.memoryAllocatorDumps = (function() { + const v8Dump = newAllocatorDump(pmd2, 'v8', {numerics: { + size: 1073741824 /* 1 GiB */, + inner_size: 2097152 /* 2 MiB */, + objects_count: new Scalar(unitlessNumber_smallerIsBetter, 204) + }}); + + const v8ObjectsDump = addChildDump(v8Dump, 'objects'); + v8ObjectsDump.addDiagnostic('url', 'http://sample.net'); + addChildDump(v8ObjectsDump, 'foo', + {numerics: {size: 1020928 /* 997 KiB */}}); + addChildDump(v8ObjectsDump, 'bar', + {numerics: {size: 1026048 /* 1002 KiB */}}); + + newSuballocationDump( + undefined, v8Dump, '__99BEAD', 268435456 /* 256 MiB */); + + const ccDump = newAllocatorDump(pmd2, 'cc', + {numerics: {size: 7340032 /* 7 MiB */}}); + newSuballocationDump( + ccDump, v8Dump, '__13DEED', 11534336 /* 11 MiB */).addDiagnostic( + 'url', 'localhost:5678'); + + return [v8Dump, ccDump]; + })(); + }); + + return model.processes[1].memoryDumps; + } + + function createSizeFields(values) { + return values.map(function(value) { + if (value === undefined) return undefined; + return new Scalar(sizeInBytes_smallerIsBetter, value); + }); + } + + const EXPECTED_COLUMNS = [ + { title: 'Component', type: AllocatorDumpNameColumn, noAggregation: true }, + { title: 'effective_size', type: EffectiveSizeColumn }, + { title: 'size', type: SizeColumn }, + { title: 'inner_size', type: NumericMemoryColumn }, + { title: 'objects_count', type: NumericMemoryColumn }, + { title: 'url', type: StringMemoryColumn } + ]; + + function checkRow(columns, row, expectations) { + const formattedTitle = columns[0].formatTitle(row); + const expectedTitle = expectations.title; + if (typeof expectedTitle === 'function') { + expectedTitle(formattedTitle); + } else { + assert.strictEqual(formattedTitle, expectedTitle); + } + + checkSizeNumericFields(row, columns[1], expectations.size); + checkSizeNumericFields(row, columns[2], expectations.effective_size); + checkSizeNumericFields(row, columns[3], expectations.inner_size); + checkNumericFields(row, columns[4], expectations.objects_count, + unitlessNumber_smallerIsBetter); + checkStringFields(row, columns[5], expectations.url); + + const expectedSubRowCount = expectations.sub_row_count; + if (expectedSubRowCount === undefined) { + assert.isUndefined(row.subRows); + } else { + assert.lengthOf(row.subRows, expectedSubRowCount); + } + + const expectedContexts = expectations.contexts; + if (expectedContexts === undefined) { + assert.isUndefined(row.contexts); + } else { + assert.deepEqual(Array.from(row.contexts), expectedContexts); + } + } + + function buildProcessMemoryDumps(count, preFinalizeDumpsCallback) { + const pmds = new Array(count); + tr.c.TestUtils.newModel(function(model) { + const process = model.getOrCreateProcess(1); + for (let i = 0; i < count; i++) { + const timestamp = 10 + i; + const gmd = addGlobalMemoryDump(model, {ts: timestamp}); + pmds[i] = addProcessMemoryDump(gmd, process, {ts: timestamp}); + } + preFinalizeDumpsCallback(pmds); + }); + return pmds; + } + + function getAllocatorDumps(pmds, fullName) { + return pmds.map(function(pmd) { + if (pmd === undefined) return undefined; + return pmd.getMemoryAllocatorDumpByFullName(fullName); + }); + } + + function checkAllocatorPaneColumnInfosAndColor( + column, dumps, numericName, expectedInfos) { + const numerics = dumps.map(function(dump) { + if (dump === undefined) return undefined; + return dump.numerics[numericName]; + }); + checkColumnInfosAndColor( + column, numerics, dumps, expectedInfos, undefined /* no color */); + } + + test('instantiate_empty', function() { + tr.ui.analysis.createAndCheckEmptyPanes(this, + 'tr-ui-a-memory-dump-allocator-details-pane', 'memoryAllocatorDumps', + function(viewEl) { + // Check that the info text is shown. + assert.isTrue(isElementDisplayed(viewEl.$.info_text)); + assert.isFalse(isElementDisplayed(viewEl.$.table)); + }); + }); + + test('instantiate_single', function() { + const processMemoryDumps = createProcessMemoryDumps().slice(0, 1); + + const viewEl = tr.ui.analysis.createTestPane( + 'tr-ui-a-memory-dump-allocator-details-pane'); + viewEl.memoryAllocatorDumps = getAllocatorDumps(processMemoryDumps, 'v8'); + viewEl.rebuild(); + assert.deepEqual(viewEl.requestedChildPanes, [undefined]); + this.addHTMLOutput(viewEl); + + // Check that the table is shown. + assert.isTrue(isElementDisplayed(viewEl.$.table)); + assert.isFalse(isElementDisplayed(viewEl.$.info_text)); + + const table = viewEl.$.table; + const columns = table.tableColumns; + checkColumns(columns, EXPECTED_COLUMNS, undefined /* no aggregation */); + const rows = table.tableRows; + assert.lengthOf(rows, 1); + + // Check the rows of the table. + const rootRow = rows[0]; + checkRow(columns, rootRow, { + title: 'v8', + size: [942619648], + effective_size: [1081031680], + inner_size: [2097152], + objects_count: [204], + sub_row_count: 3, + contexts: getAllocatorDumps(processMemoryDumps, 'v8'), + }); + + const heapsSubRow = rootRow.subRows[0]; + checkRow(columns, heapsSubRow, { + title: 'heaps', + size: [805306368], + effective_size: [805306368], + sub_row_count: 2, + contexts: getAllocatorDumps(processMemoryDumps, 'v8/heaps'), + }); + + const heapsUnspecifiedSubRow = heapsSubRow.subRows[0]; + checkRow(columns, heapsUnspecifiedSubRow, { + title: '<unspecified>', + size: [524288], + effective_size: [524288], + contexts: getAllocatorDumps(processMemoryDumps, 'v8/heaps/<unspecified>'), + }); + + const suballocationsSubRow = rootRow.subRows[2]; + checkRow(columns, suballocationsSubRow, { + title(formattedTitle) { + assert.strictEqual( + Polymer.dom(formattedTitle).textContent, 'suballocations'); + assert.strictEqual(formattedTitle.title, ''); + }, + size: [135266304], + effective_size: [273678336], + sub_row_count: 3, + contexts: [SUBALLOCATION_CONTEXT], + }); + + const oilpanSuballocationSubRow = suballocationsSubRow.subRows[0]; + checkRow(columns, oilpanSuballocationSubRow, { + title(formattedTitle) { + assert.strictEqual(Polymer.dom(formattedTitle).textContent, 'oilpan'); + assert.strictEqual(formattedTitle.title, ''); + }, + size: [125829120], + effective_size: [251658240], + sub_row_count: 2, + contexts: [SUBALLOCATION_CONTEXT], + }); + + const oilpanUnspecifiedSuballocationSubRow = + oilpanSuballocationSubRow.subRows[0]; + checkRow(columns, oilpanUnspecifiedSuballocationSubRow, { + title(formattedTitle) { + assert.strictEqual( + Polymer.dom(formattedTitle).textContent, '<unspecified>'); + assert.strictEqual(formattedTitle.title, 'v8/__99BEAD'); + }, + size: [75497472], + effective_size: [150994944], + contexts: getAllocatorDumps(processMemoryDumps, 'v8/__99BEAD'), + }); + + const oilpanAnimalsSuballocationSubRow = + oilpanSuballocationSubRow.subRows[1]; + checkRow(columns, oilpanAnimalsSuballocationSubRow, { + title(formattedTitle) { + assert.strictEqual(Polymer.dom(formattedTitle).textContent, 'animals'); + assert.strictEqual(formattedTitle.title, ''); + }, + size: [50331648], + effective_size: [100663296], + sub_row_count: 2, + contexts: [SUBALLOCATION_CONTEXT], + }); + + const oilpanCowSuballocationSubRow = + oilpanAnimalsSuballocationSubRow.subRows[0]; + checkRow(columns, oilpanCowSuballocationSubRow, { + title(formattedTitle) { + assert.strictEqual(Polymer.dom(formattedTitle).textContent, 'cow'); + assert.strictEqual(formattedTitle.title, 'v8/__42BEEF'); + }, + size: [33554432], + effective_size: [67108864], + contexts: getAllocatorDumps(processMemoryDumps, 'v8/__42BEEF'), + }); + + const skiaSuballocationSubRow = suballocationsSubRow.subRows[1]; + checkRow(columns, skiaSuballocationSubRow, { + title(formattedTitle) { + assert.strictEqual(Polymer.dom(formattedTitle).textContent, 'skia'); + assert.strictEqual(formattedTitle.title, 'v8/__15FADE'); + }, + size: [8388608], + effective_size: [16777216], + contexts: getAllocatorDumps(processMemoryDumps, 'v8/__15FADE'), + }); + + const ccSuballocationSubRow = suballocationsSubRow.subRows[2]; + checkRow(columns, ccSuballocationSubRow, { + title(formattedTitle) { + assert.strictEqual(Polymer.dom(formattedTitle).textContent, 'cc'); + assert.strictEqual(formattedTitle.title, 'v8/__12FEED'); + }, + size: [1048576], + effective_size: [5242880], + url: ['localhost:1234'], + contexts: getAllocatorDumps(processMemoryDumps, 'v8/__12FEED') + }); + }); + + test('instantiate_multipleDiff', function() { + const processMemoryDumps = createProcessMemoryDumps(); + + const viewEl = tr.ui.analysis.createTestPane( + 'tr-ui-a-memory-dump-allocator-details-pane'); + viewEl.memoryAllocatorDumps = getAllocatorDumps(processMemoryDumps, 'v8'); + viewEl.aggregationMode = AggregationMode.DIFF; + viewEl.rebuild(); + assert.deepEqual(viewEl.requestedChildPanes, [undefined]); + this.addHTMLOutput(viewEl); + + // Check that the table is shown. + assert.isTrue(isElementDisplayed(viewEl.$.table)); + assert.isFalse(isElementDisplayed(viewEl.$.info_text)); + + const table = viewEl.$.table; + const columns = table.tableColumns; + checkColumns(columns, EXPECTED_COLUMNS, AggregationMode.DIFF); + const rows = table.tableRows; + assert.lengthOf(rows, 1); + + // Check the rows of the table. + const rootRow = rows[0]; + checkRow(columns, rootRow, { + title: 'v8', + size: [942619648, 1066401792], + effective_size: [1081031680, 1073741824], + inner_size: [2097152, 2097152], + objects_count: [204, 204], + sub_row_count: 4, + contexts: getAllocatorDumps(processMemoryDumps, 'v8'), + }); + + const heapsSubRow = rootRow.subRows[0]; + checkRow(columns, heapsSubRow, { + title: 'heaps', + size: [805306368, undefined], + effective_size: [805306368, undefined], + sub_row_count: 2, + contexts: getAllocatorDumps(processMemoryDumps, 'v8/heaps'), + }); + + const heapsUnspecifiedSubRow = heapsSubRow.subRows[0]; + checkRow(columns, heapsUnspecifiedSubRow, { + title: '<unspecified>', + size: [524288, undefined], + effective_size: [524288, undefined], + contexts: getAllocatorDumps(processMemoryDumps, 'v8/heaps/<unspecified>'), + }); + + const unspecifiedSubRow = rootRow.subRows[2]; + checkRow(columns, unspecifiedSubRow, { + title: '<unspecified>', + size: [undefined, 791725056], + effective_size: [undefined, 791725056], + contexts: getAllocatorDumps(processMemoryDumps, 'v8/<unspecified>'), + }); + + const suballocationsSubRow = rootRow.subRows[3]; + checkRow(columns, suballocationsSubRow, { + title(formattedTitle) { + assert.strictEqual( + Polymer.dom(formattedTitle).textContent, 'suballocations'); + assert.strictEqual(formattedTitle.title, ''); + }, + size: [135266304, 272629760], + effective_size: [273678336, 279969792], + sub_row_count: 3, + contexts: [SUBALLOCATION_CONTEXT, SUBALLOCATION_CONTEXT], + }); + + const oilpanSuballocationSubRow = suballocationsSubRow.subRows[0]; + checkRow(columns, oilpanSuballocationSubRow, { + title(formattedTitle) { + assert.strictEqual(Polymer.dom(formattedTitle).textContent, 'oilpan'); + assert.strictEqual(formattedTitle.title, ''); + }, + size: [125829120, 268435456], + effective_size: [251658240, 268435456], + sub_row_count: 2, + contexts: [SUBALLOCATION_CONTEXT, SUBALLOCATION_CONTEXT], + }); + + const oilpanUnspecifiedSuballocationSubRow = + oilpanSuballocationSubRow.subRows[0]; + checkRow(columns, oilpanUnspecifiedSuballocationSubRow, { + title(formattedTitle) { + assert.strictEqual( + Polymer.dom(formattedTitle).textContent, '<unspecified>'); + assert.strictEqual(formattedTitle.title, 'v8/__99BEAD'); + }, + size: [75497472, 268435456], + effective_size: [150994944, 268435456], + contexts: getAllocatorDumps(processMemoryDumps, 'v8/__99BEAD'), + }); + + const oilpanAnimalsSuballocationSubRow = + oilpanSuballocationSubRow.subRows[1]; + checkRow(columns, oilpanAnimalsSuballocationSubRow, { + title(formattedTitle) { + assert.strictEqual(Polymer.dom(formattedTitle).textContent, 'animals'); + assert.strictEqual(formattedTitle.title, ''); + }, + size: [50331648, undefined], + effective_size: [100663296, undefined], + sub_row_count: 2, + contexts: [SUBALLOCATION_CONTEXT, undefined], + }); + + const oilpanCowSuballocationSubRow = + oilpanAnimalsSuballocationSubRow.subRows[0]; + checkRow(columns, oilpanCowSuballocationSubRow, { + title(formattedTitle) { + assert.strictEqual(Polymer.dom(formattedTitle).textContent, 'cow'); + assert.strictEqual(formattedTitle.title, 'v8/__42BEEF'); + }, + size: [33554432, undefined], + effective_size: [67108864, undefined], + contexts: getAllocatorDumps(processMemoryDumps, 'v8/__42BEEF'), + }); + + const skiaSuballocationSubRow = suballocationsSubRow.subRows[1]; + checkRow(columns, skiaSuballocationSubRow, { + title(formattedTitle) { + assert.strictEqual(Polymer.dom(formattedTitle).textContent, 'skia'); + assert.strictEqual(formattedTitle.title, 'v8/__15FADE'); + }, + size: [8388608, undefined], + effective_size: [16777216, undefined], + contexts: getAllocatorDumps(processMemoryDumps, 'v8/__15FADE'), + }); + + const ccSuballocationSubRow = suballocationsSubRow.subRows[2]; + checkRow(columns, ccSuballocationSubRow, { + title(formattedTitle) { + assert.strictEqual(Polymer.dom(formattedTitle).textContent, 'cc'); + assert.strictEqual(formattedTitle.title, 'v8/__12FEED, v8/__13DEED'); + }, + size: [1048576, 4194304], + effective_size: [5242880, 11534336], + url: ['localhost:1234', 'localhost:5678'], + contexts: [ + processMemoryDumps[0].getMemoryAllocatorDumpByFullName('v8/__12FEED'), + processMemoryDumps[1].getMemoryAllocatorDumpByFullName('v8/__13DEED') + ] + }); + }); + + test('instantiate_multipleMax', function() { + const processMemoryDumps = createProcessMemoryDumps(); + + const viewEl = tr.ui.analysis.createTestPane( + 'tr-ui-a-memory-dump-allocator-details-pane'); + viewEl.memoryAllocatorDumps = getAllocatorDumps(processMemoryDumps, 'v8'); + viewEl.aggregationMode = AggregationMode.MAX; + viewEl.rebuild(); + assert.deepEqual(viewEl.requestedChildPanes, [undefined]); + this.addHTMLOutput(viewEl); + + // Check that the table is shown. + assert.isTrue(isElementDisplayed(viewEl.$.table)); + assert.isFalse(isElementDisplayed(viewEl.$.info_text)); + + // Just check that the aggregation mode was propagated to the columns. + const table = viewEl.$.table; + const columns = table.tableColumns; + checkColumns(columns, EXPECTED_COLUMNS, AggregationMode.MAX); + const rows = table.tableRows; + assert.lengthOf(rows, 1); + }); + + test('instantiate_multipleWithUndefined', function() { + const processMemoryDumps = createProcessMemoryDumps(); + processMemoryDumps.splice(1, 0, undefined); + + const viewEl = tr.ui.analysis.createTestPane( + 'tr-ui-a-memory-dump-allocator-details-pane'); + viewEl.memoryAllocatorDumps = getAllocatorDumps(processMemoryDumps, 'v8'); + viewEl.aggregationMode = AggregationMode.DIFF; + viewEl.rebuild(); + assert.deepEqual(viewEl.requestedChildPanes, [undefined]); + this.addHTMLOutput(viewEl); + + // Check that the table is shown. + assert.isTrue(isElementDisplayed(viewEl.$.table)); + assert.isFalse(isElementDisplayed(viewEl.$.info_text)); + + const table = viewEl.$.table; + const columns = table.tableColumns; + checkColumns(columns, EXPECTED_COLUMNS, AggregationMode.DIFF); + const rows = table.tableRows; + assert.lengthOf(rows, 1); + + // Check only a few rows of the table. + const rootRow = rows[0]; + checkRow(columns, rootRow, { + title: 'v8', + size: [942619648, undefined, 1066401792], + effective_size: [1081031680, undefined, 1073741824], + inner_size: [2097152, undefined, 2097152], + objects_count: [204, undefined, 204], + sub_row_count: 4, + contexts: getAllocatorDumps(processMemoryDumps, 'v8'), + }); + + const unspecifiedSubRow = rootRow.subRows[2]; + checkRow(columns, unspecifiedSubRow, { + title: '<unspecified>', + size: [undefined, undefined, 791725056], + effective_size: [undefined, undefined, 791725056], + contexts: getAllocatorDumps(processMemoryDumps, 'v8/<unspecified>'), + }); + + const suballocationsSubRow = rootRow.subRows[3]; + checkRow(columns, suballocationsSubRow, { + title(formattedTitle) { + assert.strictEqual( + Polymer.dom(formattedTitle).textContent, 'suballocations'); + assert.strictEqual(formattedTitle.title, ''); + }, + size: [135266304, undefined, 272629760], + effective_size: [273678336, undefined, 279969792], + sub_row_count: 3, + contexts: [SUBALLOCATION_CONTEXT, undefined, SUBALLOCATION_CONTEXT], + }); + }); + + test('heapDumpsPassThrough', function() { + const processMemoryDumps = createProcessMemoryDumps(); + const heapDumps = processMemoryDumps.map(function(dump) { + if (dump === undefined) return undefined; + return new HeapDump(dump, 'v8'); + }); + + // Start by creating a component details pane without any heap dumps. + const viewEl = tr.ui.analysis.createTestPane( + 'tr-ui-a-memory-dump-allocator-details-pane'); + viewEl.memoryAllocatorDumps = getAllocatorDumps(processMemoryDumps, 'v8'); + viewEl.aggregationMode = AggregationMode.MAX; + viewEl.rebuild(); + + assert.lengthOf(viewEl.requestedChildPanes, 1); + assert.isUndefined(viewEl.requestedChildPanes[0]); + + // Set the heap dumps. This should trigger creating a heap details pane. + viewEl.heapDumps = heapDumps; + viewEl.aggregationMode = AggregationMode.DIFF; + viewEl.rebuild(); + + assert.lengthOf(viewEl.requestedChildPanes, 2); + assert.strictEqual(viewEl.requestedChildPanes[1].tagName, + 'TR-UI-A-MEMORY-DUMP-HEAP-DETAILS-PANE'); + assert.strictEqual(viewEl.requestedChildPanes[1].heapDumps, heapDumps); + assert.strictEqual(viewEl.requestedChildPanes[1].aggregationMode, + AggregationMode.DIFF); + + // Unset the heap dumps. This should trigger removing the heap details pane. + viewEl.heapDumps = undefined; + viewEl.rebuild(); + + assert.lengthOf(viewEl.requestedChildPanes, 3); + assert.isUndefined(viewEl.requestedChildPanes[2]); + }); + + test('allocatorDumpNameColumn', function() { + const c = new AllocatorDumpNameColumn(); + + // Regular row. + assert.strictEqual(c.formatTitle({title: 'Regular row'}), 'Regular row'); + + // Sub-allocation row. + const row = c.formatTitle({ + title: 'Suballocation row', + suballocation: true, + }); + assert.strictEqual(Polymer.dom(row).textContent, 'Suballocation row'); + assert.strictEqual(row.style.fontStyle, 'italic'); + }); + + test('effectiveSizeColumn_noContext', function() { + const c = new EffectiveSizeColumn('Effective Size', 'bytes', (x => x), + AggregationMode.DIFF); + + // Single selection. + checkColumnInfosAndColor(c, + createSizeFields([128]), + undefined /* no context */, + [] /* no infos */, + undefined /* no color */); + + // Multi-selection. + checkColumnInfosAndColor(c, + createSizeFields([128, 256, undefined, 64]), + undefined /* no context */, + [] /* no infos */, + undefined /* no color */); + }); + + test('effectiveSizeColumn_suballocationContext', function() { + const c = new EffectiveSizeColumn('Effective Size', 'bytes', (x => x), + AggregationMode.MAX); + + // Single selection. + checkColumnInfosAndColor(c, + createSizeFields([128]), + [SUBALLOCATION_CONTEXT], + [] /* no infos */, + undefined /* no color */); + + // Multi-selection. + checkColumnInfosAndColor(c, + createSizeFields([undefined, 256, undefined, 64]), + [undefined, SUBALLOCATION_CONTEXT, SUBALLOCATION_CONTEXT, + SUBALLOCATION_CONTEXT], + [] /* no infos */, + undefined /* no color */); + }); + + test('effectiveSizeColumn_dumpContext_noOwnership', function() { + const c = new EffectiveSizeColumn('Effective Size', 'bytes', (x => x), + AggregationMode.DIFF); + const pmds = buildProcessMemoryDumps(4 /* count */, function(pmds) { + addRootDumps(pmds[0], ['v8'], function(v8Dump) { + addChildDump(v8Dump, 'heaps', {numerics: {size: 64}}); + }); + addRootDumps(pmds[2], ['v8'], function(v8Dump) { + addChildDump(v8Dump, 'heaps', {numerics: {size: 128}}); + }); + addRootDumps(pmds[3], ['v8'], function(v8Dump) {}); + }); + const v8HeapsDumps = getAllocatorDumps(pmds, 'v8/heaps'); + + // Single selection. + checkAllocatorPaneColumnInfosAndColor(c, + [v8HeapsDumps[0]], + 'effective_size', + [] /* no infos */); + + // Multi-selection, all dumps defined. + checkAllocatorPaneColumnInfosAndColor(c, + [v8HeapsDumps[0], v8HeapsDumps[2]], + 'effective_size', + [] /* no infos */); + + // Multi-selection, some dumps missing. + checkAllocatorPaneColumnInfosAndColor(c, + v8HeapsDumps, + 'effective_size', + [] /* no infos */); + }); + + test('effectiveSizeColumn_dumpContext_singleOwnership', function() { + const c = new EffectiveSizeColumn('Effective Size', 'bytes', (x => x), + AggregationMode.MAX); + const pmds = buildProcessMemoryDumps(5 /* count */, function(pmds) { + addRootDumps(pmds[0], ['v8', 'oilpan'], function(v8Dump, oilpanDump) { + const v8HeapsDump = addChildDump(v8Dump, 'heaps', + {numerics: {size: 32}}); + const oilpanObjectsDump = + addChildDump(oilpanDump, 'objects', {numerics: {size: 64}}); + addOwnershipLink(v8HeapsDump, oilpanObjectsDump); + }); + addRootDumps(pmds[1], ['v8'], function(v8Dump) { + addChildDump(v8Dump, 'heaps', {numerics: {size: 32}}); + // Missing oilpan/objects dump. + }); + addRootDumps(pmds[2], ['v8', 'oilpan'], function(v8Dump, oilpanDump) { + addChildDump(oilpanDump, 'objects', {numerics: {size: 64}}); + // Missing v8/heaps dump. + }); + addRootDumps(pmds[3], ['v8', 'oilpan'], function(v8Dump, oilpanDump) { + addChildDump(v8Dump, 'heaps', {numerics: {size: 32}}); + addChildDump(oilpanDump, 'objects', {numerics: {size: 64}}); + // Missing ownership link. + }); + addRootDumps(pmds[4], ['v8', 'oilpan'], function(v8Dump, oilpanDump) { + const v8HeapsDump = addChildDump(v8Dump, 'heaps', + {numerics: {size: 32}}); + const oilpanObjectsDump = + addChildDump(oilpanDump, 'objects', {numerics: {size: 64}}); + addOwnershipLink(v8HeapsDump, oilpanObjectsDump, 2); + }); + }); + const v8HeapsDump = getAllocatorDumps(pmds, 'v8/heaps'); + const oilpanObjectsDump = getAllocatorDumps(pmds, 'oilpan/objects'); + + // Single selection. + checkAllocatorPaneColumnInfosAndColor(c, + [v8HeapsDump[0]], + 'effective_size', + [ + { + icon: '\u21FE', + message: 'shares \'oilpan/objects\' in Process 1 (importance: 0) ' + + 'with no other dumps', + color: 'green' + } + ]); + checkAllocatorPaneColumnInfosAndColor(c, + [oilpanObjectsDump[4]], + 'effective_size', + [ + { + icon: '\u21FD', + message: 'shared by \'v8/heaps\' in Process 1 (importance: 2)', + color: 'green' + } + ]); + + // Multi-selection, all dumps defined. + checkAllocatorPaneColumnInfosAndColor(c, + [v8HeapsDump[0], v8HeapsDump[4]], + 'effective_size', + [ + { + icon: '\u21FE', + message: 'shares \'oilpan/objects\' in Process 1 (importance: ' + + '0\u20132) with no other dumps', + color: 'green' + } + ]); + checkAllocatorPaneColumnInfosAndColor(c, + [oilpanObjectsDump[0], oilpanObjectsDump[4]], + 'effective_size', + [ + { + icon: '\u21FD', + message: 'shared by \'v8/heaps\' in Process 1 (importance: ' + + '0\u20132)', + color: 'green' + } + ]); + + // Multi-selection, some dumps missing. + checkAllocatorPaneColumnInfosAndColor(c, + v8HeapsDump, + 'effective_size', + [ + { + icon: '\u21FE', + message: 'shares \'oilpan/objects\' in Process 1 at some ' + + 'selected timestamps (importance: 0\u20132) with no other ' + + 'dumps', + color: 'green' + } + ]); + checkAllocatorPaneColumnInfosAndColor(c, + oilpanObjectsDump, + 'effective_size', + [ + { + icon: '\u21FD', + message: 'shared by \'v8/heaps\' in Process 1 at some selected ' + + 'timestamps (importance: 0\u20132)', + color: 'green' + } + ]); + }); + + test('effectiveSizeColumn_dumpContext_multipleOwnerships', function() { + const c = new EffectiveSizeColumn('Effective Size', 'bytes', (x => x), + AggregationMode.DIFF); + const pmds = buildProcessMemoryDumps(6 /* count */, function(pmds) { + addRootDumps(pmds[0], ['v8', 'oilpan'], function(v8Dump, oilpanDump) { + const v8HeapsDump = addChildDump(v8Dump, 'heaps', + {numerics: {size: 32}}); + const v8QueuesDump = addChildDump(v8Dump, 'queues', + {numerics: {size: 8}}); + const oilpanObjectsDump = + addChildDump(oilpanDump, 'objects', {numerics: {size: 64}}); + addOwnershipLink(v8HeapsDump, oilpanObjectsDump); + addOwnershipLink(v8QueuesDump, oilpanObjectsDump, 1); + }); + addRootDumps(pmds[1], ['v8'], function(v8Dump) {}); + addRootDumps(pmds[2], ['v8', 'oilpan'], function(v8Dump, oilpanDump) { + const v8HeapsDump = addChildDump(v8Dump, 'heaps', + {numerics: {size: 32}}); + const v8QueuesDump = addChildDump(v8Dump, 'queues', + {numerics: {size: 8}}); + const v8PilesDump = addChildDump(v8Dump, 'piles', + {numerics: {size: 48}}); + const oilpanObjectsDump = + addChildDump(oilpanDump, 'objects', {numerics: {size: 64}}); + addOwnershipLink(v8HeapsDump, oilpanObjectsDump, 2); + addOwnershipLink(v8QueuesDump, oilpanObjectsDump, 1); + addOwnershipLink(v8PilesDump, oilpanObjectsDump); + }); + addRootDumps(pmds[3], ['v8', 'blink'], function(v8Dump, blinkDump) { + const blinkHandlesDump = addChildDump(blinkDump, 'handles', + {numerics: {size: 32}}); + const v8HeapsDump = addChildDump(v8Dump, 'heaps', + {numerics: {size: 64}}); + const blinkObjectsDump = addChildDump(blinkDump, 'objects', + {numerics: {size: 32}}); + addOwnershipLink(blinkHandlesDump, v8HeapsDump, -273); + addOwnershipLink(v8HeapsDump, blinkObjectsDump, 3); + }); + addRootDumps(pmds[4], ['v8', 'gpu'], function(v8Dump, gpuDump) { + const v8HeapsDump = addChildDump(v8Dump, 'heaps', + {numerics: {size: 64}}); + const gpuTile1Dump = addChildDump(gpuDump, 'tile1', + {numerics: {size: 100}}); + const gpuTile2Dump = addChildDump(gpuDump, 'tile2', + {numerics: {size: 99}}); + addOwnershipLink(v8HeapsDump, gpuTile1Dump, 3); + addOwnershipLink(gpuTile2Dump, gpuTile1Dump, -1); + }); + addRootDumps(pmds[5], ['v8', 'oilpan'], function(v8Dump, oilpanDump) { + const v8HeapsDump = addChildDump(v8Dump, 'heaps', + {numerics: {size: 32}}); + const v8QueuesDump = addChildDump(v8Dump, 'queues', + {numerics: {size: 8}}); + const v8PilesDump = addChildDump(v8Dump, 'piles', + {numerics: {size: 48}}); + const oilpanObjectsDump = + addChildDump(oilpanDump, 'objects', {numerics: {size: 64}}); + addOwnershipLink(v8HeapsDump, oilpanObjectsDump, 1); + addOwnershipLink(v8QueuesDump, oilpanObjectsDump, 1); + addOwnershipLink(v8PilesDump, oilpanObjectsDump, 7); + }); + }); + const v8HeapsDump = getAllocatorDumps(pmds, 'v8/heaps'); + const oilpanObjectsDump = getAllocatorDumps(pmds, 'oilpan/objects'); + const gpuTile1Dump = getAllocatorDumps(pmds, 'gpu/tile1'); + + // Single selection. + checkAllocatorPaneColumnInfosAndColor(c, + [v8HeapsDump[4]], + 'effective_size', + [ + { + icon: '\u21FE', + message: 'shares \'gpu/tile1\' in Process 1 (importance: 3) with ' + + '\'gpu/tile2\' in Process 1 (importance: -1)', + color: 'green' + } + ]); + checkAllocatorPaneColumnInfosAndColor(c, + [gpuTile1Dump[4]], + 'effective_size', + [ + { + icon: '\u21FD', + message: 'shared by:\n' + + ' - \'v8/heaps\' in Process 1 (importance: 3)\n' + + ' - \'gpu/tile2\' in Process 1 (importance: -1)', + color: 'green' + } + ]); + + // Multi-selection, all dumps defined. + checkAllocatorPaneColumnInfosAndColor(c, + [v8HeapsDump[2], v8HeapsDump[5]], + 'effective_size', + [ + { + icon: '\u21FE', + message: 'shares \'oilpan/objects\' in Process 1 (importance: ' + + '1\u20132) with:\n' + + ' - \'v8/queues\' in Process 1 (importance: 1)\n' + + ' - \'v8/piles\' in Process 1 (importance: 0\u20137)', + color: 'green' + } + ]); + checkAllocatorPaneColumnInfosAndColor(c, + [oilpanObjectsDump[2], oilpanObjectsDump[5]], + 'effective_size', + [ + { + icon: '\u21FD', + message: 'shared by:\n' + + ' - \'v8/heaps\' in Process 1 (importance: 1\u20132)\n' + + ' - \'v8/queues\' in Process 1 (importance: 1)\n' + + ' - \'v8/piles\' in Process 1 (importance: 0\u20137)', + color: 'green' + } + ]); + + // Multi-selection, some dumps missing. + checkAllocatorPaneColumnInfosAndColor(c, + v8HeapsDump, + 'effective_size', + [ // v8/objects is both owned (first info) and an owner (second info). + { + icon: '\u21FD', + message: 'shared by \'blink/handles\' in Process 1 at some ' + + 'selected timestamps (importance: -273)', + color: 'green' + }, + { + icon: '\u21FE', + message: 'shares:\n' + + ' - \'oilpan/objects\' in Process 1 at some selected ' + + 'timestamps (importance: 0\u20132) with:\n' + + ' - \'v8/queues\' in Process 1 (importance: 1)\n' + + ' - \'v8/piles\' in Process 1 at some selected ' + + 'timestamps (importance: 0\u20137)\n' + + ' - \'blink/objects\' in Process 1 at some selected ' + + 'timestamps (importance: 3) with no other dumps\n' + + ' - \'gpu/tile1\' in Process 1 at some selected timestamps ' + + '(importance: 3) with \'gpu/tile2\' in Process 1 ' + + '(importance: -1)', + color: 'green' + } + ]); + checkAllocatorPaneColumnInfosAndColor(c, + oilpanObjectsDump, + 'effective_size', + [ + { + icon: '\u21FD', + message: 'shared by:\n' + + ' - \'v8/heaps\' in Process 1 at some selected timestamps ' + + '(importance: 0\u20132)\n' + + ' - \'v8/queues\' in Process 1 at some selected timestamps ' + + '(importance: 1)\n' + + ' - \'v8/piles\' in Process 1 at some selected timestamps ' + + '(importance: 0\u20137)', + color: 'green' + } + ]); + }); + + test('sizeColumn_noContext', function() { + const c = new SizeColumn('Size', 'bytes', (x => x), + AggregationMode.DIFF); + + // Single selection. + checkColumnInfosAndColor(c, + createSizeFields([128]), + undefined /* no context */, + [] /* no infos */, + undefined /* no color */); + + // Multi-selection. + checkColumnInfosAndColor(c, + createSizeFields([128, 256, undefined, 64]), + undefined /* no context */, + [] /* no infos */, + undefined /* no color */); + }); + + test('sizeColumn_suballocationContext', function() { + const c = new SizeColumn('Size', 'bytes', (x => x), + AggregationMode.MAX); + + // Single selection. + checkColumnInfosAndColor(c, + createSizeFields([128]), + [SUBALLOCATION_CONTEXT], + [] /* no infos */, + undefined /* no color */); + + // Multi-selection. + checkColumnInfosAndColor(c, + createSizeFields([undefined, 256, undefined, 64]), + [undefined, SUBALLOCATION_CONTEXT, undefined, SUBALLOCATION_CONTEXT], + [] /* no infos */, + undefined /* no color */); + }); + + test('sizeColumn_dumpContext', function() { + const c = new SizeColumn('Size', 'bytes', (x => x), AggregationMode.DIFF); + const pmds = buildProcessMemoryDumps(7 /* count */, function(pmds) { + addRootDumps(pmds[0], ['v8'], function(v8Dump) { + // Single direct overlap (v8/objects -> v8/heaps). + const v8ObjectsDump = addChildDump(v8Dump, 'objects', + {numerics: {size: 1536}}); + const v8HeapsDump = addChildDump(v8Dump, 'heaps', + {numerics: {size: 2048}}); + addOwnershipLink(v8ObjectsDump, v8HeapsDump); + }); + // pmd[1] intentionally skipped. + addRootDumps(pmds[2], ['v8'], function(v8Dump, oilpanDump) { + // Single direct overlap with inconsistent owned dump size. + const v8ObjectsDump = addChildDump(v8Dump, 'objects', + {numerics: {size: 3072}}); + const v8HeapsDump = addChildDump(v8Dump, 'heaps', + {numerics: {size: 2048}}); + addOwnershipLink(v8ObjectsDump, v8HeapsDump); + }); + addRootDumps(pmds[3], ['v8'], function(v8Dump) { + // Single indirect overlap (v8/objects/X -> v8/heaps/42). + const v8ObjectsDump = addChildDump(v8Dump, 'objects', + {numerics: {size: 1536}}); + const v8ObjectsXDump = addChildDump(v8ObjectsDump, 'X', + {numerics: {size: 512}}); + const v8HeapsDump = addChildDump(v8Dump, 'heaps', + {numerics: {size: 2048}}); + const v8Heaps42Dump = addChildDump(v8HeapsDump, '42', + {numerics: {size: 1024}}); + addOwnershipLink(v8ObjectsXDump, v8Heaps42Dump); + }); + addRootDumps(pmds[4], ['v8'], function(v8Dump) { + // Multiple overlaps. + const v8ObjectsDump = addChildDump(v8Dump, 'objects', + {numerics: {size: 1024}}); + const v8HeapsDump = addChildDump(v8Dump, 'heaps', + {numerics: {size: 2048}}); + + const v8ObjectsXDump = addChildDump(v8ObjectsDump, 'X', + {numerics: {size: 512}}); + const v8Heaps42Dump = addChildDump(v8HeapsDump, '42', + {numerics: {size: 1280}}); + addOwnershipLink(v8ObjectsXDump, v8Heaps42Dump); + + const v8ObjectsYDump = addChildDump(v8ObjectsDump, 'Y', + {numerics: {size: 128}}); + const v8Heaps90Dump = addChildDump(v8HeapsDump, '90', + {numerics: {size: 256}}); + addOwnershipLink(v8ObjectsYDump, v8Heaps90Dump); + + const v8BlocksDump = addChildDump(v8Dump, 'blocks', + {numerics: {size: 768}}); + addOwnershipLink(v8BlocksDump, v8Heaps42Dump); + }); + addRootDumps(pmds[5], ['v8'], function(v8Dump) { + // No overlaps, inconsistent parent size. + const v8HeapsDump = addChildDump(v8Dump, 'heaps', + {numerics: {size: 2048}}); + addChildDump(v8HeapsDump, '42', {numerics: {size: 1536}}); + addChildDump(v8HeapsDump, '90', {numerics: {size: 615}}); + }); + addRootDumps(pmds[6], ['v8', 'oilpan'], function(v8Dump, oilpanDump) { + // No overlaps, inconsistent parent and owned dump size. + const v8HeapsDump = addChildDump(v8Dump, 'heaps', + {numerics: {size: 2048}}); + addChildDump(v8HeapsDump, '42', {numerics: {size: 1536}}); + addChildDump(v8HeapsDump, '90', {numerics: {size: 615}}); + const oilpanObjectsDump = + addChildDump(oilpanDump, 'objects', {numerics: {size: 3072}}); + addOwnershipLink(oilpanObjectsDump, v8HeapsDump); + }); + }); + const v8HeapDumps = getAllocatorDumps(pmds, 'v8/heaps'); + + // Single selection, single overlap. + checkAllocatorPaneColumnInfosAndColor(c, + [v8HeapDumps[0]], + 'size', + [ + { + icon: '\u24D8', + message: 'overlaps with its sibling \'objects\' (1.5 KiB)', + color: 'blue' + } + ]); + + // Single selection, multiple overlaps. + checkAllocatorPaneColumnInfosAndColor(c, + [v8HeapDumps[4]], + 'size', + [ + { + icon: '\u24D8', + message: 'overlaps with its siblings:\n' + + ' - \'objects\' (640.0 B)\n' + + ' - \'blocks\' (768.0 B)', + color: 'blue' + } + ]); + + // Single selection, warnings with no overlaps. + checkAllocatorPaneColumnInfosAndColor(c, + [v8HeapDumps[6]], + 'size', + [ + { + icon: '\u26A0', + message: 'provided size (2.0 KiB) was less than the aggregated ' + + 'size of the children (2.1 KiB)', + color: 'red' + }, + { + icon: '\u26A0', + message: 'provided size (2.0 KiB) was less than the size of the ' + + 'largest owner (3.0 KiB)', + color: 'red' + } + ]); + + // Single selection, single overlap with a warning. + checkAllocatorPaneColumnInfosAndColor(c, + [v8HeapDumps[2]], + 'size', + [ + { + icon: '\u24D8', + message: 'overlaps with its sibling \'objects\' (3.0 KiB)', + color: 'blue' + }, + { + icon: '\u26A0', + message: 'provided size (2.0 KiB) was less than the size of the ' + + 'largest owner (3.0 KiB)', + color: 'red' + } + ]); + + // Multi-selection, single overlap. + checkAllocatorPaneColumnInfosAndColor(c, + [v8HeapDumps[0], v8HeapDumps[3]], + 'size', + [ + { + icon: '\u24D8', + message: 'overlaps with its sibling \'objects\'', + color: 'blue' + } + ]); + + // Multi-selection, multiple overlaps. + checkAllocatorPaneColumnInfosAndColor(c, + [v8HeapDumps[0], v8HeapDumps[4]], + 'size', + [ + { + icon: '\u24D8', + message: 'overlaps with its siblings:\n' + + ' - \'objects\'\n' + + ' - \'blocks\' at some selected timestamps', + color: 'blue' + } + ]); + + // Multi-selection, warnings with no overlaps. + checkAllocatorPaneColumnInfosAndColor(c, + [v8HeapDumps[5], v8HeapDumps[6]], + 'size', + [ + { + icon: '\u26A0', + message: 'provided size was less than the aggregated ' + + 'size of the children', + color: 'red' + }, + { + icon: '\u26A0', + message: 'provided size was less than the size of the largest ' + + 'owner at some selected timestamps', + color: 'red' + } + ]); + + // Multi-selection, multiple overlaps with warnings. + checkAllocatorPaneColumnInfosAndColor(c, + v8HeapDumps, + 'size', + [ + { + icon: '\u24D8', + message: 'overlaps with its siblings:\n' + + ' - \'objects\' at some selected timestamps\n' + + ' - \'blocks\' at some selected timestamps', + color: 'blue' + }, + { + icon: '\u26A0', + message: 'provided size was less than the size of the largest ' + + 'owner at some selected timestamps', + color: 'red' + }, + { + icon: '\u26A0', + message: 'provided size was less than the aggregated size of ' + + 'the children at some selected timestamps', + color: 'red' + } + ]); + }); +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_header_pane.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_header_pane.html new file mode 100644 index 00000000000..1141116ec86 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_header_pane.html @@ -0,0 +1,178 @@ +<!DOCTYPE html> +<!-- +Copyright 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/base/unit.html"> +<link rel="import" href="/tracing/ui/analysis/memory_dump_overview_pane.html"> +<link rel="import" href="/tracing/ui/analysis/memory_dump_sub_view_util.html"> +<link rel="import" href="/tracing/ui/analysis/stacked_pane.html"> +<link rel="import" href="/tracing/ui/base/dom_helpers.html"> + +<dom-module id='tr-ui-a-memory-dump-header-pane'> + <template> + <style> + :host { + display: flex; + flex-direction: row; + align-items: center; + + background-color: #d0d0d0; + border-bottom: 1px solid #8e8e8e; + border-top: 1px solid white; + } + + #label { + flex: 1 1 auto; + padding: 6px; + font-size: 15px; + } + + #aggregation_mode_container { + display: none; + flex: 0 0 auto; + padding: 5px; + font-size: 15px; + } + </style> + </tr-ui-b-view-specific-brushing-state> + <div id="label"></div> + <div id="aggregation_mode_container"> + <span>Metric aggregation:</span> + <!-- Aggregation mode selector (added in Polymer.ready()) --> + </div> + </template> +</dom-module> +<script> +'use strict'; + +tr.exportTo('tr.ui.analysis', function() { + Polymer({ + is: 'tr-ui-a-memory-dump-header-pane', + behaviors: [tr.ui.analysis.StackedPane], + + created() { + this.containerMemoryDumps_ = undefined; + }, + + ready() { + Polymer.dom(this.$.aggregation_mode_container).appendChild( + tr.ui.b.createSelector(this, 'aggregationMode', + 'memoryDumpHeaderPane.aggregationMode', + tr.ui.analysis.MemoryColumn.AggregationMode.DIFF, [ + { + label: 'Diff', + value: tr.ui.analysis.MemoryColumn.AggregationMode.DIFF + }, + { + label: 'Max', + value: tr.ui.analysis.MemoryColumn.AggregationMode.MAX + } + ])); + }, + + /** + * Sets the container memory dumps and schedules rebuilding the pane. + * + * The provided value should be a chronologically sorted list of + * ContainerMemoryDump objects. All of the dumps must be associated with + * the same container (i.e. containerMemoryDumps must be either a list of + * ProcessMemoryDump(s) belonging to the same process, or a list of + * GlobalMemoryDump(s)). Example: + * + * [ + * tr.model.ProcessMemoryDump {}, // PMD at timestamp 1. + * tr.model.ProcessMemoryDump {}, // PMD at timestamp 2. + * tr.model.ProcessMemoryDump {} // PMD at timestamp 3. + * ] + */ + set containerMemoryDumps(containerMemoryDumps) { + this.containerMemoryDumps_ = containerMemoryDumps; + this.scheduleRebuild_(); + }, + + get containerMemoryDumps() { + return this.containerMemoryDumps_; + }, + + set aggregationMode(aggregationMode) { + this.aggregationMode_ = aggregationMode; + this.scheduleRebuild_(); + }, + + get aggregationMode() { + return this.aggregationMode_; + }, + + onRebuild_() { + this.updateLabel_(); + this.updateAggregationModeSelector_(); + this.changeChildPane_(); + }, + + updateLabel_() { + Polymer.dom(this.$.label).textContent = ''; + + if (this.containerMemoryDumps_ === undefined || + this.containerMemoryDumps_.length <= 0) { + Polymer.dom(this.$.label).textContent = 'No memory dumps selected'; + return; + } + + const containerDumpCount = this.containerMemoryDumps_.length; + const isMultiSelection = containerDumpCount > 1; + + Polymer.dom(this.$.label).appendChild(document.createTextNode( + 'Selected ' + containerDumpCount + ' memory dump' + + (isMultiSelection ? 's' : '') + + ' in ' + this.containerMemoryDumps_[0].containerName + ' at ')); + // TODO(petrcermak): Use <tr-v-ui-scalar-span> once it can be displayed + // inline. See https://github.com/catapult-project/catapult/issues/1371. + Polymer.dom(this.$.label).appendChild(document.createTextNode( + tr.b.Unit.byName.timeStampInMs.format( + this.containerMemoryDumps_[0].start))); + if (isMultiSelection) { + const ELLIPSIS = String.fromCharCode(8230); + Polymer.dom(this.$.label).appendChild( + document.createTextNode(ELLIPSIS)); + Polymer.dom(this.$.label).appendChild(document.createTextNode( + tr.b.Unit.byName.timeStampInMs.format( + this.containerMemoryDumps_[containerDumpCount - 1].start))); + } + }, + + updateAggregationModeSelector_() { + let displayStyle; + if (this.containerMemoryDumps_ === undefined || + this.containerMemoryDumps_.length <= 1) { + displayStyle = 'none'; + } else { + displayStyle = 'initial'; + } + this.$.aggregation_mode_container.style.display = displayStyle; + }, + + changeChildPane_() { + this.childPaneBuilder = function() { + if (this.containerMemoryDumps_ === undefined || + this.containerMemoryDumps_.length <= 0) { + return undefined; + } + + const overviewPane = document.createElement( + 'tr-ui-a-memory-dump-overview-pane'); + overviewPane.processMemoryDumps = this.containerMemoryDumps_.map( + function(containerDump) { + return containerDump.processMemoryDumps; + }); + overviewPane.aggregationMode = this.aggregationMode; + return overviewPane; + }.bind(this); + } + }); + + return {}; +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_header_pane_test.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_header_pane_test.html new file mode 100644 index 00000000000..3d5d20a7c47 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_header_pane_test.html @@ -0,0 +1,134 @@ +<!DOCTYPE html> +<!-- +Copyright 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/analysis/memory_dump_header_pane.html"> +<link rel="import" + href="/tracing/ui/analysis/memory_dump_sub_view_test_utils.html"> +<link rel="import" href="/tracing/ui/analysis/memory_dump_sub_view_util.html"> +<link rel="import" href="/tracing/ui/base/deep_utils.html"> + +<script> +'use strict'; + +tr.b.unittest.testSuite(function() { + const AggregationMode = tr.ui.analysis.MemoryColumn.AggregationMode; + const isElementDisplayed = tr.ui.analysis.isElementDisplayed; + + function createAndCheckMemoryDumpHeaderPane(test, containerMemoryDumps, + expectedLabelText, expectedChildPaneRequested, expectedSelectorVisible) { + const viewEl = + tr.ui.analysis.createTestPane('tr-ui-a-memory-dump-header-pane'); + viewEl.containerMemoryDumps = containerMemoryDumps; + viewEl.rebuild(); + test.addHTMLOutput(viewEl); + checkMemoryDumpHeaderPane(viewEl, containerMemoryDumps, expectedLabelText, + expectedChildPaneRequested, expectedSelectorVisible); + } + + function checkMemoryDumpHeaderPane(viewEl, containerMemoryDumps, + expectedLabelText, expectedChildPaneRequested, expectedSelectorVisible) { + // The default aggregation mode is DIFF. + assert.strictEqual(viewEl.aggregationMode, AggregationMode.DIFF); + + // Check the text in the label. + assert.strictEqual( + Polymer.dom(viewEl.$.label).textContent, expectedLabelText); + + // Check the visibility of aggregation mode selector. + const aggregationModeContainerVisible = + isElementDisplayed(viewEl.$.aggregation_mode_container); + const childPanes = viewEl.requestedChildPanes; + + // Check the requested child panes. + if (containerMemoryDumps === undefined || + containerMemoryDumps.length === 0) { + assert.isTrue(!expectedSelectorVisible); // Test sanity check. + assert.isFalse(aggregationModeContainerVisible); + assert.lengthOf(childPanes, 1); + assert.isUndefined(childPanes[0]); + return; + } + + const expectedProcessMemoryDumps = containerMemoryDumps.map( + function(containerMemoryDump) { + return containerMemoryDump.processMemoryDumps; + }); + function checkLastChildPane(expectedChildPaneCount) { + assert.lengthOf(childPanes, expectedChildPaneCount); + const lastChildPane = childPanes[expectedChildPaneCount - 1]; + assert.strictEqual( + lastChildPane.tagName, 'TR-UI-A-MEMORY-DUMP-OVERVIEW-PANE'); + assert.deepEqual(lastChildPane.processMemoryDumps, + expectedProcessMemoryDumps); + assert.strictEqual(lastChildPane.aggregationMode, viewEl.aggregationMode); + } + + checkLastChildPane(1); + + // Check the behavior of aggregation mode selector (if visible). + if (!expectedSelectorVisible) { + assert.isFalse(aggregationModeContainerVisible); + return; + } + + assert.isTrue(aggregationModeContainerVisible); + const selector = tr.ui.b.findDeepElementMatching(viewEl, 'select'); + + selector.selectedValue = AggregationMode.MAX; + viewEl.rebuild(); + assert.strictEqual(viewEl.aggregationMode, AggregationMode.MAX); + checkLastChildPane(2); + + selector.selectedValue = AggregationMode.DIFF; + viewEl.rebuild(); + assert.strictEqual(viewEl.aggregationMode, AggregationMode.DIFF); + checkLastChildPane(3); + } + + test('instantiate_empty', function() { + tr.ui.analysis.createAndCheckEmptyPanes(this, + 'tr-ui-a-memory-dump-header-pane', 'containerMemoryDumps', + function(viewEl) { + checkMemoryDumpHeaderPane(viewEl, [], 'No memory dumps selected', + false /* no child pane requested */, + false /* aggregation mode selector hidden */); + }); + }); + + test('instantiate_singleGlobalMemoryDump', function() { + createAndCheckMemoryDumpHeaderPane(this, + [tr.ui.analysis.createSingleTestGlobalMemoryDump()], + 'Selected 1 memory dump in global space at 68.000 ms', + true /* child pane requested */, + false /* aggregation mode selector hidden */); + }); + + test('instantiate_multipleGlobalMemoryDumps', function() { + createAndCheckMemoryDumpHeaderPane(this, + tr.ui.analysis.createMultipleTestGlobalMemoryDumps(), + 'Selected 3 memory dumps in global space at 42.000 ms\u2026100.000 ms', + true /* child pane requested */, + true /* aggregation selector visible */); + }); + + test('instantiate_singleProcessMemoryDump', function() { + createAndCheckMemoryDumpHeaderPane(this, + [tr.ui.analysis.createSingleTestProcessMemoryDump()], + 'Selected 1 memory dump in Process 2 at 69.000 ms', + true /* child pane requested */, + false /* aggregation mode selector hidden */); + }); + + test('instantiate_multipleProcessMemoryDumps', function() { + createAndCheckMemoryDumpHeaderPane(this, + tr.ui.analysis.createMultipleTestProcessMemoryDumps(), + 'Selected 3 memory dumps in Process 2 at 42.000 ms\u2026102.000 ms', + true /* child pane requested */, + true /* aggregation selector visible */); + }); +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_heap_details_breakdown_view.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_heap_details_breakdown_view.html new file mode 100644 index 00000000000..9d17e39ce85 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_heap_details_breakdown_view.html @@ -0,0 +1,354 @@ +<!DOCTYPE html> +<!-- +Copyright 2016 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/color_scheme.html"> +<link rel="import" href="/tracing/base/event.html"> +<link rel="import" href="/tracing/ui/analysis/memory_dump_heap_details_util.html"> +<link rel="import" href="/tracing/ui/analysis/memory_dump_sub_view_util.html"> +<link rel="import" href="/tracing/ui/analysis/rebuildable_behavior.html"> +<link rel="import" href="/tracing/ui/base/dom_helpers.html"> +<link rel="import" href="/tracing/ui/base/tab_view.html"> +<link rel="import" href="/tracing/ui/base/table.html"> +<link rel="import" href="/tracing/value/ui/scalar_context_controller.html"> + +<dom-module id='tr-ui-a-memory-dump-heap-details-breakdown-view'> + <template> + <tr-ui-b-tab-view id="tabs"></tr-ui-b-tab-view> + </template> +</dom-module> + +<dom-module id='tr-ui-a-memory-dump-heap-details-breakdown-view-tab'> + <template> + <tr-v-ui-scalar-context-controller></tr-v-ui-scalar-context-controller> + <tr-ui-b-info-bar id="info" hidden></tr-ui-b-info-bar> + <tr-ui-b-table id="table"></tr-ui-b-table> + </template> +</dom-module> + +<script> +'use strict'; + +tr.exportTo('tr.ui.analysis', function() { + const RESONABLE_NUMBER_OF_ROWS = 200; + + const TabUiState = { + NO_LONG_TAIL: 0, + HIDING_LONG_TAIL: 1, + SHOWING_LONG_TAIL: 2, + }; + + /** @constructor */ + function EmptyFillerColumn() {} + + EmptyFillerColumn.prototype = { + title: '', + + value() { + return ''; + }, + }; + + Polymer({ + is: 'tr-ui-a-memory-dump-heap-details-breakdown-view', + behaviors: [tr.ui.analysis.RebuildableBehavior], + + created() { + this.displayedNode_ = undefined; + this.dimensionToTab_ = new Map(); + }, + + ready() { + this.scheduleRebuild_(); + this.root.addEventListener('keydown', this.onKeyDown_.bind(this), true); + }, + + get displayedNode() { + return this.displayedNode_; + }, + + set displayedNode(node) { + this.displayedNode_ = node; + this.scheduleRebuild_(); + }, + + get aggregationMode() { + return this.aggregationMode_; + }, + + set aggregationMode(aggregationMode) { + this.aggregationMode_ = aggregationMode; + for (const tab of this.$.tabs.tabs) { + tab.aggregationMode = aggregationMode; + } + }, + + onRebuild_() { + const previouslySelectedTab = this.$.tabs.selectedSubView; + let previouslySelectedTabFocused = false; + let previouslySelectedDimension = undefined; + if (previouslySelectedTab) { + previouslySelectedTabFocused = previouslySelectedTab.isFocused; + previouslySelectedDimension = previouslySelectedTab.dimension; + } + + for (const tab of this.$.tabs.tabs) { + tab.nodes = undefined; + } + this.$.tabs.clearSubViews(); + + if (this.displayedNode_ === undefined) { + this.$.tabs.label = 'No heap node provided.'; + return; + } + + for (const [dimension, children] of this.displayedNode_.childNodes) { + if (!this.dimensionToTab_.has(dimension)) { + this.dimensionToTab_.set(dimension, document.createElement( + 'tr-ui-a-memory-dump-heap-details-breakdown-view-tab')); + } + const tab = this.dimensionToTab_.get(dimension); + tab.aggregationMode = this.aggregationMode_; + tab.dimension = dimension; + tab.nodes = children; + this.$.tabs.addSubView(tab); + tab.rebuild(); + if (dimension === previouslySelectedDimension) { + this.$.tabs.selectedSubView = tab; + if (previouslySelectedTabFocused) { + tab.focus(); + } + } + } + + if (this.$.tabs.tabs.length > 0) { + this.$.tabs.label = 'Break selected node further by:'; + } else { + this.$.tabs.label = 'Selected node cannot be broken down any further.'; + } + }, + + onKeyDown_(keyEvent) { + if (!this.displayedNode_) return; + + let keyHandled = false; + switch (keyEvent.keyCode) { + case 8: { + // Backspace. + if (!this.displayedNode_.parentNode) break; + + // Enter the parent node upon pressing backspace. + const viewEvent = new tr.b.Event('enter-node'); + viewEvent.node = this.displayedNode_.parentNode; + this.dispatchEvent(viewEvent); + keyHandled = true; + break; + } + + case 37: // Left arrow. + case 39: // Right arrow. + { + const wasFocused = this.$.tabs.selectedSubView.isFocused; + keyHandled = keyEvent.keyCode === 37 ? + this.$.tabs.selectPreviousTabIfPossible() : + this.$.tabs.selectNextTabIfPossible(); + if (wasFocused && keyHandled) { + this.$.tabs.selectedSubView.focus(); // Restore focus to new tab. + } + } + } + + if (!keyHandled) return; + keyEvent.stopPropagation(); + keyEvent.preventDefault(); + } + }); + + Polymer({ + is: 'tr-ui-a-memory-dump-heap-details-breakdown-view-tab', + behaviors: [tr.ui.analysis.RebuildableBehavior], + + created() { + this.dimension_ = undefined; + this.nodes_ = undefined; + this.aggregationMode_ = undefined; + this.displayLongTail_ = false; + }, + + ready() { + this.$.table.addEventListener('step-into', function(tableEvent) { + const viewEvent = new tr.b.Event('enter-node'); + viewEvent.node = tableEvent.tableRow; + this.dispatchEvent(viewEvent); + }.bind(this)); + }, + + get displayLongTail() { + return this.displayLongTail_; + }, + + set displayLongTail(newValue) { + if (this.displayLongTail === newValue) return; + this.displayLongTail_ = newValue; + this.scheduleRebuild_(); + }, + + get dimension() { + return this.dimension_; + }, + + set dimension(dimension) { + this.dimension_ = dimension; + this.scheduleRebuild_(); + }, + + get nodes() { + return this.nodes_; + }, + + set nodes(nodes) { + this.nodes_ = nodes; + this.scheduleRebuild_(); + }, + + get nodes() { + return this.nodes_ || []; + }, + + get dimensionLabel_() { + if (this.dimension_ === undefined) return '(undefined)'; + return this.dimension_.label; + }, + + get tabLabel() { + let nodeCount = 0; + if (this.nodes_) { + nodeCount = this.nodes_.length; + } + return this.dimensionLabel_ + ' (' + nodeCount + ')'; + }, + + get tabIcon() { + if (this.dimension_ === undefined || + this.dimension_ === tr.ui.analysis.HeapDetailsRowDimension.ROOT) { + return undefined; + } + return { + text: this.dimension_.symbol, + style: 'color: ' + tr.b.ColorScheme.getColorForReservedNameAsString( + this.dimension_.color) + ';' + }; + }, + + get aggregationMode() { + return this.aggregationMode_; + }, + + set aggregationMode(aggregationMode) { + this.aggregationMode_ = aggregationMode; + this.scheduleRebuild_(); + }, + + focus() { + this.$.table.focus(); + }, + + blur() { + this.$.table.blur(); + }, + + get isFocused() { + return this.$.table.isFocused; + }, + + onRebuild_() { + this.$.table.selectionMode = tr.ui.b.TableFormat.SelectionMode.ROW; + this.$.table.emptyValue = 'Cannot break down by ' + + this.dimensionLabel_.toLowerCase() + ' any further.'; + const [state, rows] = this.getRows_(); + const total = this.nodes.length; + const displayed = rows.length; + const hidden = total - displayed; + this.updateInfoBar_(state, [total, displayed, hidden]); + this.$.table.tableRows = rows; + this.$.table.tableColumns = this.createColumns_(rows); + if (this.$.table.sortColumnIndex === undefined) { + this.$.table.sortColumnIndex = 0; + this.$.table.sortDescending = false; + } + this.$.table.rebuild(); + }, + + createColumns_(rows) { + const titleColumn = new tr.ui.analysis.HeapDetailsTitleColumn( + this.dimensionLabel_); + titleColumn.width = '400px'; + + const numericColumns = tr.ui.analysis.MemoryColumn.fromRows(rows, { + cellKey: 'cells', + aggregationMode: this.aggregationMode_, + rules: tr.ui.analysis.HEAP_DETAILS_COLUMN_RULES, + shouldSetContextGroup: true + }); + if (numericColumns.length === 0) { + numericColumns.push(new EmptyFillerColumn()); + } + tr.ui.analysis.MemoryColumn.spaceEqually(numericColumns); + + const columns = [titleColumn].concat(numericColumns); + return columns; + }, + + getRows_() { + let rows = this.nodes; + if (rows.length <= RESONABLE_NUMBER_OF_ROWS) { + return [TabUiState.NO_LONG_TAIL, rows]; + } else if (this.displayLongTail) { + return [TabUiState.SHOWING_LONG_TAIL, rows]; + } + const absSize = row => Math.max(row.cells.Size.fields[0].value); + rows.sort((a, b) => absSize(b) - absSize(a)); + rows = rows.slice(0, RESONABLE_NUMBER_OF_ROWS); + return [TabUiState.HIDING_LONG_TAIL, rows]; + }, + + updateInfoBar_(state, rowStats) { + if (state === TabUiState.SHOWING_LONG_TAIL) { + this.longTailVisibleInfoBar_(rowStats); + } else if (state === TabUiState.HIDING_LONG_TAIL) { + this.longTailHiddenInfoBar_(rowStats); + } else { + this.hideInfoBar_(); + } + }, + + longTailVisibleInfoBar_(rowStats) { + const [total, visible, hidden] = rowStats; + const couldHide = total - RESONABLE_NUMBER_OF_ROWS; + this.$.info.message = 'Showing ' + total + ' rows. This may be slow.'; + this.$.info.removeAllButtons(); + const buttonText = 'Hide ' + couldHide + ' rows.'; + this.$.info.addButton(buttonText, () => this.displayLongTail = false); + this.$.info.visible = true; + }, + + longTailHiddenInfoBar_(rowStats) { + const [total, visible, hidden] = rowStats; + this.$.info.message = 'Hiding the smallest ' + hidden + ' rows.'; + this.$.info.removeAllButtons(); + this.$.info.addButton('Show all.', () => this.displayLongTail = true); + this.$.info.visible = true; + }, + + hideInfoBar_() { + this.$.info.visible = false; + }, + + }); + + return {}; +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_heap_details_pane.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_heap_details_pane.html new file mode 100644 index 00000000000..a43fdaa8189 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_heap_details_pane.html @@ -0,0 +1,451 @@ +<!DOCTYPE html> +<!-- +Copyright 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/base/color_scheme.html"> +<link rel="import" href="/tracing/base/multi_dimensional_view.html"> +<link rel="import" href="/tracing/base/scalar.html"> +<link rel="import" href="/tracing/base/utils.html"> +<link rel="import" href="/tracing/ui/analysis/memory_dump_heap_details_breakdown_view.html"> +<link rel="import" href="/tracing/ui/analysis/memory_dump_heap_details_path_view.html"> +<link rel="import" href="/tracing/ui/analysis/memory_dump_heap_details_util.html"> +<link rel="import" href="/tracing/ui/analysis/memory_dump_sub_view_util.html"> +<link rel="import" href="/tracing/ui/analysis/stacked_pane.html"> +<link rel="import" href="/tracing/ui/base/dom_helpers.html"> +<link rel="import" href="/tracing/ui/base/drag_handle.html"> +<link rel="import" href="/tracing/ui/base/info_bar.html"> + +<dom-module id='tr-ui-a-memory-dump-heap-details-pane'> + <template> + <style> + :host { + display: flex; + flex-direction: column; + } + + #header { + flex: 0 0 auto; + display: flex; + flex-direction: row; + align-items: center; + + background-color: #eee; + border-bottom: 1px solid #8e8e8e; + border-top: 1px solid white; + } + + #label { + flex: 1 1 auto; + padding: 8px; + font-size: 15px; + font-weight: bold; + } + + #view_mode_container { + display: none; + flex: 0 0 auto; + padding: 5px; + font-size: 15px; + } + + #contents { + flex: 1 0 auto; + align-self: stretch; + font-size: 12px; + } + + #info_text { + padding: 8px; + color: #666; + font-style: italic; + text-align: center; + } + + #split_view { + display: none; /* Hide until memory allocator dumps are set. */ + flex: 1 0 auto; + align-self: stretch; + flex-direction: row; + } + + #path_view { + width: 50%; + } + + #breakdown_view { + flex: 1 1 auto; + width: 0; + } + + #path_view, #breakdown_view { + overflow-x: auto; /* Show scrollbar if necessary. */ + } + </style> + <div id="header"> + <div id="label">Heap details</div> + <div id="view_mode_container"> + <span>View mode:</span> + <!-- View mode selector (added in Polymer.ready()) --> + </div> + </div> + <div id="contents"> + <tr-ui-b-info-bar id="info_bar" hidden> + </tr-ui-b-info-bar> + + <div id="info_text">No heap dump selected</div> + + <div id="split_view"> + <tr-ui-a-memory-dump-heap-details-path-view id="path_view"> + </tr-ui-a-memory-dump-heap-details-path-view> + <tr-ui-b-drag-handle id="drag_handle"></tr-ui-b-drag-handle> + <tr-ui-a-memory-dump-heap-details-breakdown-view id="breakdown_view"> + </tr-ui-a-memory-dump-heap-details-breakdown-view> + </div> + </div> + </template> +</dom-module> +<script> +'use strict'; + +tr.exportTo('tr.ui.analysis', function() { + const Scalar = tr.b.Scalar; + const sizeInBytes_smallerIsBetter = + tr.b.Unit.byName.sizeInBytes_smallerIsBetter; + const count_smallerIsBetter = tr.b.Unit.byName.count_smallerIsBetter; + const MultiDimensionalViewBuilder = tr.b.MultiDimensionalViewBuilder; + const TotalState = tr.b.MultiDimensionalViewNode.TotalState; + + /** @{constructor} */ + function HeapDumpTreeNode( + stackFrameNodes, dimension, title, heavyView, parentNode) { + this.dimension = dimension; + this.title = title; + this.parentNode = parentNode; + + this.heavyView_ = heavyView; + this.stackFrameNodes_ = stackFrameNodes; + this.lazyCells_ = undefined; + this.lazyChildNodes_ = undefined; + } + + HeapDumpTreeNode.prototype = { + get minDisplayedTotalState_() { + if (this.heavyView_) { + // Show lower-bound and exact values in heavy views. + return TotalState.LOWER_BOUND; + } + // Show only exact values in tree view. + return TotalState.EXACT; + }, + + get childNodes() { + if (!this.lazyChildNodes_) { + this.lazyChildNodes_ = new Map(); + this.addDimensionChildNodes_( + tr.ui.analysis.HeapDetailsRowDimension.STACK_FRAME, 0); + this.addDimensionChildNodes_( + tr.ui.analysis.HeapDetailsRowDimension.OBJECT_TYPE, 1); + this.releaseStackFrameNodesIfPossible_(); + } + return this.lazyChildNodes_; + }, + + get cells() { + if (!this.lazyCells_) { + this.addCells_(); + this.releaseStackFrameNodesIfPossible_(); + } + return this.lazyCells_; + }, + + releaseStackFrameNodesIfPossible_() { + if (this.lazyCells_ && this.lazyChildNodes_) { + // Don't unnecessarily hold a reference to the stack frame nodes when + // we don't need them anymore. + this.stackFrameNodes_ = undefined; + } + }, + + addDimensionChildNodes_(dimension, dimensionIndex) { + // Child title -> Timestamp (list index) -> Child + // MultiDimensionalViewNode. + const dimensionChildTitleToStackFrameNodes = tr.b.invertArrayOfDicts( + this.stackFrameNodes_, + node => this.convertStackFrameNodeDimensionToChildDict_( + node, dimensionIndex)); + + // Child title (list index) -> Child HeapDumpTreeNode. + const dimensionChildNodes = []; + for (const [childTitle, childStackFrameNodes] of + Object.entries(dimensionChildTitleToStackFrameNodes)) { + dimensionChildNodes.push(new HeapDumpTreeNode(childStackFrameNodes, + dimension, childTitle, this.heavyView_, this)); + } + this.lazyChildNodes_.set(dimension, dimensionChildNodes); + }, + + convertStackFrameNodeDimensionToChildDict_( + stackFrameNode, dimensionIndex) { + const childDict = {}; + let displayedChildrenTotalSize = 0; + let displayedChildrenTotalCount = 0; + let hasDisplayedChildren = false; + let allDisplayedChildrenHaveDisplayedCounts = true; + for (const child of stackFrameNode.children[dimensionIndex].values()) { + if (child.values[0].totalState < this.minDisplayedTotalState_) { + continue; + } + if (child.values[1].totalState < this.minDisplayedTotalState_) { + allDisplayedChildrenHaveDisplayedCounts = false; + } + childDict[child.title[dimensionIndex]] = child; + displayedChildrenTotalSize += child.values[0].total; + displayedChildrenTotalCount += child.values[1].total; + hasDisplayedChildren = true; + } + + const nodeTotalSize = stackFrameNode.values[0].total; + const nodeTotalCount = stackFrameNode.values[1].total; + + // Add '<other>' node if necessary in tree-view. + const hasUnclassifiedSizeOrCount = + displayedChildrenTotalSize < nodeTotalSize || + displayedChildrenTotalCount < nodeTotalCount; + if (!this.heavyView_ && hasUnclassifiedSizeOrCount && + hasDisplayedChildren) { + const otherTitle = stackFrameNode.title.slice(); + otherTitle[dimensionIndex] = '<other>'; + const otherNode = new tr.b.MultiDimensionalViewNode(otherTitle, 2); + childDict[otherTitle[dimensionIndex]] = otherNode; + + // '<other>' node size. + otherNode.values[0].total = nodeTotalSize - displayedChildrenTotalSize; + otherNode.values[0].totalState = this.minDisplayedTotalState_; + + // '<other>' node allocation count. + otherNode.values[1].total = + nodeTotalCount - displayedChildrenTotalCount; + // Don't show allocation count of the '<other>' node if there is a + // displayed child node that did NOT display allocation count. + otherNode.values[1].totalState = + allDisplayedChildrenHaveDisplayedCounts ? + this.minDisplayedTotalState_ : TotalState.NOT_PROVIDED; + } + + return childDict; + }, + + addCells_() { + // Transform a chronological list of heap stack frame tree nodes into a + // dictionary of cells (where each cell contains a chronological list + // of the values of its numeric). + this.lazyCells_ = tr.ui.analysis.createCells(this.stackFrameNodes_, + function(stackFrameNode) { + const size = stackFrameNode.values[0].total; + const numerics = { + 'Size': new Scalar(sizeInBytes_smallerIsBetter, size) + }; + const countValue = stackFrameNode.values[1]; + if (countValue.totalState >= this.minDisplayedTotalState_) { + const count = countValue.total; + numerics.Count = new Scalar( + count_smallerIsBetter, count); + } + return numerics; + }, this); + } + }; + + Polymer({ + is: 'tr-ui-a-memory-dump-heap-details-pane', + behaviors: [tr.ui.analysis.StackedPane], + + created() { + this.heapDumps_ = undefined; + this.viewMode_ = undefined; + this.aggregationMode_ = undefined; + this.cachedBuilders_ = new Map(); + }, + + ready() { + this.$.info_bar.message = 'Note: Values displayed in the heavy view ' + + 'are lower bounds (except for the root).'; + + Polymer.dom(this.$.view_mode_container).appendChild( + tr.ui.b.createSelector( + this, 'viewMode', 'memoryDumpHeapDetailsPane.viewMode', + MultiDimensionalViewBuilder.ViewType.TOP_DOWN_TREE_VIEW, + [ + { + label: 'Top-down (Tree)', + value: MultiDimensionalViewBuilder.ViewType.TOP_DOWN_TREE_VIEW + }, + { + label: 'Top-down (Heavy)', + value: + MultiDimensionalViewBuilder.ViewType.TOP_DOWN_HEAVY_VIEW + }, + { + label: 'Bottom-up (Heavy)', + value: + MultiDimensionalViewBuilder.ViewType.BOTTOM_UP_HEAVY_VIEW + } + ])); + + this.$.drag_handle.target = this.$.path_view; + this.$.drag_handle.horizontal = false; + + // If the user selects a node in the path view, show its children in the + // breakdown view. + this.$.path_view.addEventListener('selected-node-changed', (function(e) { + this.$.breakdown_view.displayedNode = this.$.path_view.selectedNode; + }).bind(this)); + + // If the user double-clicks on a node in the breakdown view, select the + // node in the path view. + this.$.breakdown_view.addEventListener('enter-node', (function(e) { + this.$.path_view.selectedNode = e.node; + }).bind(this)); + }, + + /** + * Sets the heap dumps and schedules rebuilding the pane. + * + * The provided value should be a chronological list of heap dumps. All + * dumps are assumed to belong to the same process and belong to the same + * allocator. Example: + * + * [ + * tr.model.HeapDump {}, // Heap dump at timestamp 1. + * undefined, // Heap dump not provided at timestamp 2. + * tr.model.HeapDump {}, // Heap dump at timestamp 3. + * ] + */ + set heapDumps(heapDumps) { + this.heapDumps_ = heapDumps; + this.scheduleRebuild_(); + }, + + get heapDumps() { + return this.heapDumps_; + }, + + set aggregationMode(aggregationMode) { + this.aggregationMode_ = aggregationMode; + this.$.path_view.aggregationMode = aggregationMode; + this.$.breakdown_view.aggregationMode = aggregationMode; + }, + + get aggregationMode() { + return this.aggregationMode_; + }, + + set viewMode(viewMode) { + this.viewMode_ = viewMode; + this.scheduleRebuild_(); + }, + + get viewMode() { + return this.viewMode_; + }, + + get heavyView() { + switch (this.viewMode) { + case MultiDimensionalViewBuilder.ViewType.TOP_DOWN_HEAVY_VIEW: + case MultiDimensionalViewBuilder.ViewType.BOTTOM_UP_HEAVY_VIEW: + return true; + default: + return false; + } + }, + + onRebuild_() { + if (this.heapDumps_ === undefined || + this.heapDumps_.length === 0) { + // Show the info text (hide the table and the view mode selector). + this.$.info_text.style.display = 'block'; + this.$.split_view.style.display = 'none'; + this.$.view_mode_container.style.display = 'none'; + this.$.info_bar.hidden = true; + this.$.path_view.selectedNode = undefined; + return; + } + + // Show the table and the view mode selector (hide the info text). + this.$.info_text.style.display = 'none'; + this.$.split_view.style.display = 'flex'; + this.$.view_mode_container.style.display = 'block'; + + // Show the info bar if in heavy view mode. + this.$.info_bar.hidden = !this.heavyView; + + this.$.path_view.selectedNode = this.createHeapTree_(); + this.$.path_view.rebuild(); + this.$.breakdown_view.rebuild(); + }, + + createHeapTree_() { + const definedHeapDump = this.heapDumps_.find(x => x); + if (definedHeapDump === undefined) return undefined; + + // The title of the root node is the name of the allocator. + const rootRowTitle = definedHeapDump.allocatorName; + + const stackFrameTrees = this.createStackFrameTrees_(this.heapDumps_); + + return new HeapDumpTreeNode(stackFrameTrees, + tr.ui.analysis.HeapDetailsRowDimension.ROOT, rootRowTitle, + this.heavyView); + }, + + createStackFrameTrees_(heapDumps) { + const builders = heapDumps.map(heapDump => this.createBuilder_(heapDump)); + const views = builders.map(builder => { + if (builder === undefined) return undefined; + return builder.buildView(this.viewMode); + }); + return views; + }, + + createBuilder_(heapDump) { + if (heapDump === undefined) return undefined; + + if (this.cachedBuilders_.has(heapDump)) { + return this.cachedBuilders_.get(heapDump); + } + + const dimensions = 2; // stack frames, object type + const valueCount = 2; // size, count + const builder = new MultiDimensionalViewBuilder(dimensions, valueCount); + + // Build the heap tree. + for (const entry of heapDump.entries) { + const leafStackFrame = entry.leafStackFrame; + const stackTracePath = leafStackFrame === undefined ? + [] : leafStackFrame.getUserFriendlyStackTrace().reverse(); + + const objectTypeName = entry.objectTypeName; + const objectTypeNamePath = objectTypeName === undefined ? + [] : [objectTypeName]; + + const valueKind = entry.valuesAreTotals ? + MultiDimensionalViewBuilder.ValueKind.TOTAL : + MultiDimensionalViewBuilder.ValueKind.SELF; + + builder.addPath([stackTracePath, objectTypeNamePath], + [entry.size, entry.count], + valueKind); + } + + builder.complete = heapDump.isComplete; + this.cachedBuilders_.set(heapDump, builder); + return builder; + }, + }); + + return {}; +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_heap_details_pane_test.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_heap_details_pane_test.html new file mode 100644 index 00000000000..947cc532e7a --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_heap_details_pane_test.html @@ -0,0 +1,4045 @@ +<!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/base/multi_dimensional_view.html'> +<link rel='import' href='/tracing/base/unit.html'> +<link rel='import' href='/tracing/base/utils.html'> +<link rel='import' href='/tracing/core/test_utils.html'> +<link rel='import' href='/tracing/model/heap_dump.html'> +<link rel='import' href='/tracing/model/memory_dump_test_utils.html'> +<link rel='import' + href='/tracing/ui/analysis/memory_dump_heap_details_pane.html'> +<link rel='import' + href='/tracing/ui/analysis/memory_dump_sub_view_test_utils.html'> +<link rel='import' href='/tracing/ui/analysis/memory_dump_sub_view_util.html'> + +<script> +'use strict'; + +tr.b.unittest.testSuite(function() { + const ViewType = tr.b.MultiDimensionalViewBuilder.ViewType; + const TOP_DOWN_TREE_VIEW = ViewType.TOP_DOWN_TREE_VIEW; + const TOP_DOWN_HEAVY_VIEW = ViewType.TOP_DOWN_HEAVY_VIEW; + const BOTTOM_UP_HEAVY_VIEW = ViewType.BOTTOM_UP_HEAVY_VIEW; + const HeapDump = tr.model.HeapDump; + const HeapDetailsRowDimension = tr.ui.analysis.HeapDetailsRowDimension; + const ROOT = HeapDetailsRowDimension.ROOT; + const STACK_FRAME = HeapDetailsRowDimension.STACK_FRAME; + const OBJECT_TYPE = HeapDetailsRowDimension.OBJECT_TYPE; + const TitleColumn = tr.ui.analysis.TitleColumn; + const NumericMemoryColumn = tr.ui.analysis.NumericMemoryColumn; + const AggregationMode = tr.ui.analysis.MemoryColumn.AggregationMode; + const addGlobalMemoryDump = tr.model.MemoryDumpTestUtils.addGlobalMemoryDump; + const addProcessMemoryDump = + tr.model.MemoryDumpTestUtils.addProcessMemoryDump; + const checkColumns = tr.ui.analysis.checkColumns; + const checkNumericFields = tr.ui.analysis.checkNumericFields; + const checkSizeNumericFields = tr.ui.analysis.checkSizeNumericFields; + const isElementDisplayed = tr.ui.analysis.isElementDisplayed; + const count_smallerIsBetter = tr.b.Unit.byName.count_smallerIsBetter; + + function createHeapDumps(withCount) { + const model = new tr.Model(); + const process = model.getOrCreateProcess(1); + + function addHeapEntry(heapDump, stackFrames, objectTypeName, size, count) { + const leafStackFrame = stackFrames === undefined ? undefined : + tr.c.TestUtils.newStackTrace(model, stackFrames); + heapDump.addEntry(leafStackFrame, objectTypeName, size, + withCount ? count : undefined); + } + + // First timestamp. + const gmd1 = addGlobalMemoryDump(model, {ts: -10}); + const pmd1 = addProcessMemoryDump(gmd1, process, {ts: -11}); + const hd1 = new HeapDump(pmd1, 'partition_alloc'); + + addHeapEntry(hd1, undefined /* sum over all traces */, + undefined /* sum over all types */, 4194304 /* 4 MiB */, 1000); + addHeapEntry(hd1, undefined /* sum over all traces */, 'v8::Context', + 1048576 /* 1 MiB */, 200); + addHeapEntry(hd1, undefined /* sum over all traces */, 'blink::Node', + 331776 /* 324 KiB */, 10); + addHeapEntry(hd1, ['MessageLoop::RunTask'], + undefined /* sum over all types */, 4194304 /* 4 MiB */, 1000); + addHeapEntry(hd1, ['MessageLoop::RunTask'], 'v8::Context', + 1048576 /* 1 MiB */, 200); + + addHeapEntry(hd1, ['MessageLoop::RunTask', 'FunctionCall'], + undefined /* sum over all types */, 1406976 /* 1.3 MiB */, 299); + addHeapEntry(hd1, ['MessageLoop::RunTask', 'FunctionCall'], + 'blink::Node', 331776 /* 324 KiB */, 10); + addHeapEntry(hd1, ['MessageLoop::RunTask', 'FunctionCall'], 'v8::Context', + 1024000 /* 1000 KiB */, 176); + addHeapEntry(hd1, ['MessageLoop::RunTask', 'FunctionCall', '<self>'], + undefined /* sum over all types */, 102400 /* 100 KiB */, 30); + addHeapEntry(hd1, ['MessageLoop::RunTask', 'FunctionCall', 'V8.Execute'], + 'v8::Context', 716800 /* 700 KiB */, 100); + addHeapEntry(hd1, ['MessageLoop::RunTask', 'FunctionCall', 'V8.Execute'], + undefined /* sum over all types */, 1048576 /* 1 MiB */, 101); + addHeapEntry(hd1, ['MessageLoop::RunTask', 'FunctionCall', 'FunctionCall'], + undefined /* sum over all types */, + 153600 /* 150 KiB, lower than the actual sum (should be ignored) */, + 25 /* the allocation count should, however, NOT be ignored */); + addHeapEntry(hd1, ['MessageLoop::RunTask', 'FunctionCall', 'FunctionCall'], + 'v8::Context', 153600 /* 150 KiB */, 15); + + // The following entry should not appear in the tree-view because there is + // no entry for its parent stack frame. + addHeapEntry(hd1, ['MessageLoop::RunTask', 'MissingParent', 'FunctionCall'], + undefined /* sum over all types */, 10 /* 10 B */, 2); + + // The following entry should not appear in the tree-view because there is + // no sum over all types (for the given stack trace). However, it will lead + // to a visible increase of the (incorrectly provided) sum over all types + // of MessageLoop::RunTask -> FunctionCall -> FunctionCall by 50 KiB. + addHeapEntry(hd1, + ['MessageLoop::RunTask', 'FunctionCall', 'FunctionCall', + 'FunctionCall'], + 'MissingSumOverAllTypes', 51200 /* 50 KiB */, 9); + + addHeapEntry(hd1, ['MessageLoop::RunTask', 'V8.Execute'], + undefined /* sum over all types */, 2404352 /* 2.3 MiB */, 399); + addHeapEntry(hd1, ['MessageLoop::RunTask', 'V8.Execute', 'FunctionCall'], + undefined /* sum over all types */, 2404352 /* 2.3 MiB */, 399); + addHeapEntry(hd1, ['MessageLoop::RunTask', 'V8.Execute', 'FunctionCall'], + 'v8::Context', 20480 /* 20 KiB */, 6); + addHeapEntry(hd1, + ['MessageLoop::RunTask', 'V8.Execute', 'FunctionCall', '<self>'], + 'v8::Context', 15360 /* 15 KiB */, 5); + addHeapEntry(hd1, + ['MessageLoop::RunTask', 'V8.Execute', 'FunctionCall', 'V8.Execute'], + undefined /* sum over all types */, 2097152 /* 2 MiB */, 99); + addHeapEntry(hd1, + ['MessageLoop::RunTask', 'V8.Execute', 'FunctionCall', 'V8.Execute', + 'V8.Execute'], + undefined /* sum over all types */, 2097152 /* 2 MiB */, 99); + addHeapEntry(hd1, + ['MessageLoop::RunTask', 'V8.Execute', 'FunctionCall', '<self>'], + undefined /* sum over all types */, 307200 /* 300 KiB */, 300); + + // Second timestamp. + const gmd2 = addGlobalMemoryDump(model, {ts: 10}); + const pmd2 = addProcessMemoryDump(gmd2, process, {ts: 11}); + const hd2 = new HeapDump(pmd2, 'partition_alloc'); + + addHeapEntry(hd2, undefined /* sum over all traces */, + undefined /* sum over all types */, + 3145728 /* 3 MiB, lower than the actual sum (should be ignored) */, + 900 /* the allocation count should, however, NOT be ignored */); + addHeapEntry(hd2, undefined /* sum over all traces */, + 'v8::Context', 1258291 /* 1.2 MiB */, 520); + addHeapEntry(hd2, undefined /* sum over all traces */, + 'blink::Node', 1048576 /* 1 MiB */, 5); + addHeapEntry(hd2, ['<self>'], undefined /* sum over all types */, + 131072 /* 128 KiB */, 16); + addHeapEntry(hd2, ['<self>'], 'v8::Context', 131072 /* 128 KiB */, 16); + addHeapEntry(hd2, ['MessageLoop::RunTask'], + undefined /* sum over all types */, 4823449 /* 4.6 MiB */, 884); + addHeapEntry(hd2, ['MessageLoop::RunTask'], 'v8::Context', + 1127219 /* 1.1 MiB */, 317); + + addHeapEntry(hd2, ['MessageLoop::RunTask', 'FunctionCall'], + undefined /* sum over all types */, 2170880 /* 2.1 MiB */, 600); + addHeapEntry(hd2, ['MessageLoop::RunTask', 'FunctionCall'], 'v8::Context', + 1024000 /* 1000 KiB */, 500); + addHeapEntry(hd2, ['MessageLoop::RunTask', 'FunctionCall'], 'blink::Node', + 819200 /* 800 KiB */, 4); + addHeapEntry(hd2, ['MessageLoop::RunTask', 'FunctionCall', 'V8.Execute'], + undefined /* sum over all types */, 1572864 /* 1.5 MiB */, 270); + addHeapEntry(hd2, ['MessageLoop::RunTask', 'FunctionCall', 'V8.Execute'], + 'v8::Context', 614400 /* 600 KiB */, 123); + addHeapEntry(hd2, ['MessageLoop::RunTask', 'FunctionCall', 'V8.Execute'], + 'blink::Node', 819200 /* 800 KiB */, 4); + addHeapEntry(hd2, ['MessageLoop::RunTask', 'FunctionCall', 'FunctionCall'], + undefined /* sum over all types */, 204800 /* 200 KiB */, 313); + addHeapEntry(hd2, ['MessageLoop::RunTask', 'FunctionCall', 'FunctionCall'], + 'v8::Context', 122880 /* 120 KiB */, 270); + addHeapEntry(hd2, + ['MessageLoop::RunTask', 'FunctionCall', 'FunctionCall', + 'FunctionCall'], + undefined /* sum over all types */, 204800 /* 200 KiB */, 313); + addHeapEntry(hd2, ['MessageLoop::RunTask', 'FunctionCall', '<self>'], + undefined /* sum over all types */, 393216 /* 384 KiB */, 17); + + addHeapEntry(hd2, ['MessageLoop::RunTask', 'V8.Execute'], + undefined /* sum over all types */, 2621440 /* 2.5 MiB */, 199); + addHeapEntry(hd2, ['MessageLoop::RunTask', 'V8.Execute', 'FunctionCall'], + undefined /* sum over all types */, 2621440 /* 2.5 MiB */, 199); + addHeapEntry(hd2, ['MessageLoop::RunTask', 'V8.Execute', 'FunctionCall'], + 'v8::Context', 20480 /* 20 KiB */, 4); + addHeapEntry(hd2, ['MessageLoop::RunTask', 'V8.Execute', 'FunctionCall'], + 'WTF::StringImpl', 126362 /* 123.4 KiB */, 56); + addHeapEntry(hd2, + ['MessageLoop::RunTask', 'V8.Execute', 'FunctionCall', 'V8.Execute'], + undefined /* sum over all types */, 2516582 /* 2.4 MiB */, 158); + + return [hd1, hd2]; + } + + function createSelfHeapDumps(withCount) { + const model = new tr.Model(); + const process = model.getOrCreateProcess(1); + + function addHeapEntry(heapDump, stackFrames, objectTypeName, size, count) { + const leafStackFrame = stackFrames === undefined ? undefined : + tr.c.TestUtils.newStackTrace(model, stackFrames); + heapDump.addEntry(leafStackFrame, objectTypeName, size, + withCount ? count : undefined, false /* valuesAreTotals */); + } + + // First timestamp. + const gmd1 = addGlobalMemoryDump(model, {ts: -10}); + const pmd1 = addProcessMemoryDump(gmd1, process, {ts: -11}); + const hd1 = new HeapDump(pmd1, 'partition_alloc'); + hd1.isComplete = true; + + addHeapEntry(hd1, ['MessageLoop::RunTask', 'a', 'AllocSomething'], + 'v8::Context', 1024, 100); + addHeapEntry(hd1, ['MessageLoop::RunTask', 'a', 'b', 'AllocSomething'], + 'v8::Context', 1024, 100); + addHeapEntry(hd1, ['MessageLoop::RunTask', 'a', 'b', 'c', 'AllocSomething'], + 'v8::Context', 1024, 100); + + return [hd1]; + } + + + function checkDisplayedElements(viewEl, displayExpectations) { + assert.strictEqual(isElementDisplayed(viewEl.$.info_text), + displayExpectations.infoText); + assert.strictEqual(isElementDisplayed(viewEl.$.info_bar), + displayExpectations.infoBar); + assert.strictEqual(isElementDisplayed(viewEl.$.split_view), + displayExpectations.tableAndSplitView); + assert.strictEqual(isElementDisplayed(viewEl.$.view_mode_container), + displayExpectations.tableAndSplitView); + } + + const EXPECTED_COLUMNS_WITHOUT_COUNT = [ + { title: 'Current path', type: TitleColumn, noAggregation: true }, + { title: 'Size', type: NumericMemoryColumn } + ]; + + const EXPECTED_COLUMNS_WITH_COUNT = EXPECTED_COLUMNS_WITHOUT_COUNT.concat([ + { title: 'Count', type: NumericMemoryColumn }, + ]); + + const EXPECTED_CELLS = ['Size', 'Count']; + + function checkNode(node, expectedNodeStructure, expectedParentNode) { + assert.strictEqual(node.title, expectedNodeStructure.title); + assert.strictEqual(node.dimension, expectedNodeStructure.dimension); + assert.strictEqual(node.parentNode, expectedParentNode); + + // Check that there AREN'T any cells that we are NOT expecting. + const cells = node.cells; + assert.includeMembers(EXPECTED_CELLS, Object.keys(cells)); + + const sizeCell = cells.Size; + const sizeFields = sizeCell ? sizeCell.fields : undefined; + checkSizeNumericFields(sizeFields, undefined, expectedNodeStructure.size); + + const countCell = cells.Count; + const countFields = countCell ? countCell.fields : undefined; + checkNumericFields(countFields, undefined, expectedNodeStructure.count, + count_smallerIsBetter); + + assert.strictEqual(node.childNodes.size, 2); + + // If |expectedNodeStructure.children| is undefined, check that there are + // no child nodes. + if (!expectedNodeStructure.children) { + assert.lengthOf(node.childNodes.get(STACK_FRAME), 0); + assert.lengthOf(node.childNodes.get(OBJECT_TYPE), 0); + return; + } + + // If |expectedNodeStructure.children| is just a number, check total number + // of child nodes. + if (typeof expectedNodeStructure.children === 'number') { + assert.strictEqual(expectedNodeStructure.children, + node.childNodes.get(STACK_FRAME).length + + node.childNodes.get(OBJECT_TYPE).length); + return; + } + + // Check child nodes wrt both dimensions. + checkNodes(node.childNodes.get(STACK_FRAME), + expectedNodeStructure.children.filter(c => c.dimension === STACK_FRAME), + node); + checkNodes(node.childNodes.get(OBJECT_TYPE), + expectedNodeStructure.children.filter(c => c.dimension === OBJECT_TYPE), + node); + } + + function checkNodes(nodes, expectedStructure, expectedParentNode) { + assert.lengthOf(nodes, expectedStructure.length); + for (let i = 0; i < expectedStructure.length; i++) { + checkNode(nodes[i], expectedStructure[i], expectedParentNode); + } + } + + function checkSplitView(viewEl, expectedConfig, expectedStructure) { + checkDisplayedElements(viewEl, { + infoText: false, + tableAndSplitView: true, + infoBar: !!expectedConfig.expectedInfoBarDisplayed + }); + + // Both the split view and breakdown view should be displaying the same + // node. + const selectedNode = viewEl.$.path_view.selectedNode; + assert.strictEqual(viewEl.$.breakdown_view.displayedNode, selectedNode); + checkNodes([selectedNode], expectedStructure, + undefined /* expectedParentNode */); + + // TODO: Add proper tests for tr-ui-a-memory-dump-heap-details-path-view + // and tr-ui-a-memory-dump-heap-details-breakdown-view. + const expectedColumns = expectedConfig.expectedCountColumns ? + EXPECTED_COLUMNS_WITH_COUNT : EXPECTED_COLUMNS_WITHOUT_COUNT; + checkColumns(viewEl.$.path_view.$.table.tableColumns, expectedColumns, + expectedConfig.expectedAggregationMode); + } + + function changeView(viewEl, viewType) { + tr.ui.b.findDeepElementMatching(viewEl, 'select').selectedValue = viewType; + viewEl.rebuild(); + } + + test('instantiate_empty', function() { + tr.ui.analysis.createAndCheckEmptyPanes(this, + 'tr-ui-a-memory-dump-heap-details-pane', 'heapDumps', + function(viewEl) { + // Check that the info text is shown. + checkDisplayedElements(viewEl, { + infoText: true, + tableAndSplitView: false, + infoBar: false + }); + }); + }); + + test('instantiate_noEntries', function() { + const heapDumps = createHeapDumps(false).slice(0, 1); + heapDumps[0].entries = []; + + const viewEl = tr.ui.analysis.createTestPane( + 'tr-ui-a-memory-dump-heap-details-pane'); + viewEl.heapDumps = heapDumps; + viewEl.rebuild(); + this.addHTMLOutput(viewEl); + + // Top-down tree view (default). + checkSplitView(viewEl, + { /* empty expectedConfig */ }, + [ + { + dimension: ROOT, + title: 'partition_alloc', + size: [0], + defined: [true] + } + ]); + + changeView(viewEl, TOP_DOWN_HEAVY_VIEW); + checkSplitView(viewEl, + { expectedInfoBarDisplayed: true }, + [ + { + dimension: ROOT, + title: 'partition_alloc', + size: [0], + defined: [true] + } + ]); + + changeView(viewEl, BOTTOM_UP_HEAVY_VIEW); + checkSplitView(viewEl, + { expectedInfoBarDisplayed: true }, + [ + { + dimension: ROOT, + title: 'partition_alloc', + size: [0], + defined: [true] + } + ]); + + changeView(viewEl, TOP_DOWN_TREE_VIEW); + }); + + test('instantiate_single', function() { + const heapDumps = createHeapDumps(false).slice(0, 1); + + const viewEl = tr.ui.analysis.createTestPane( + 'tr-ui-a-memory-dump-heap-details-pane'); + viewEl.heapDumps = heapDumps; + viewEl.rebuild(); + this.addHTMLOutput(viewEl); + + // Top-down tree view (default). + checkSplitView(viewEl, + { /* empty expectedConfig */ }, + [ + { + dimension: ROOT, + title: 'partition_alloc', + size: [4194304], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'MessageLoop::RunTask', + size: [4194304], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [1406976], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: '<self>', + size: [102400], + defined: [true], + }, + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [1048576], + defined: [true], + children: [ + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [716800], + defined: [true], + }, + { + dimension: OBJECT_TYPE, + title: '<other>', + size: [331776], + defined: [true], + } + ] + }, + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [153600 + 51200], + defined: [true], + children: [ + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [153600], + defined: [true], + }, + { + dimension: OBJECT_TYPE, + title: '<other>', + size: [51200], + defined: [true], + } + ] + }, + { + dimension: STACK_FRAME, + title: '<other>', + size: [51200], + defined: [true], + }, + { + dimension: OBJECT_TYPE, + title: 'blink::Node', + size: [331776], + defined: [true], + }, + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [1024000], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [716800], + defined: [true], + }, + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [153600], + defined: [true], + }, + { + dimension: STACK_FRAME, + title: '<other>', + size: [153600], + defined: [true], + } + ] + }, + { + dimension: OBJECT_TYPE, + title: '<other>', + size: [51200], + defined: [true], + } + ] + }, + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [2404352], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [2404352], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: '<self>', + size: [307200], + defined: [true], + children: [ + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [15360], + defined: [true], + }, + { + dimension: OBJECT_TYPE, + title: '<other>', + size: [291840], + defined: [true], + } + ] + }, + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [2097152], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [2097152], + defined: [true], + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [20480], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: '<self>', + size: [15360], + defined: [true], + }, + { + dimension: STACK_FRAME, + title: '<other>', + size: [5120], + defined: [true], + } + ] + }, + { + dimension: OBJECT_TYPE, + title: '<other>', + size: [2383872], + defined: [true], + } + ] + } + ] + }, + { + dimension: STACK_FRAME, + title: '<other>', + size: [382976], + defined: [true], + }, + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [1048576], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [1024000], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [716800], + defined: [true], + }, + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [153600], + defined: [true], + }, + { + dimension: STACK_FRAME, + title: '<other>', + size: [153600], + defined: [true], + } + ] + }, + { + dimension: STACK_FRAME, + title: '<other>', + size: [24576], + defined: [true], + } + ] + }, + { + dimension: OBJECT_TYPE, + title: '<other>', + size: [3145728], + defined: [true], + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [1048576], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'MessageLoop::RunTask', + size: [1048576], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [1024000], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [716800], + defined: [true], + }, + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [153600], + defined: [true], + }, + { + dimension: STACK_FRAME, + title: '<other>', + size: [153600], + defined: [true], + } + ] + }, + { + dimension: STACK_FRAME, + title: '<other>', + size: [24576], + defined: [true], + } + ] + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'blink::Node', + size: [331776], + defined: [true], + }, + { + dimension: OBJECT_TYPE, + title: '<other>', + size: [2813952], + defined: [true], + } + ] + } + ]); + + changeView(viewEl, BOTTOM_UP_HEAVY_VIEW); + checkSplitView(viewEl, + { expectedInfoBarDisplayed: true }, + [ + { + dimension: ROOT, + title: 'partition_alloc', + size: [4194304], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'MessageLoop::RunTask', + size: [4194304], + defined: [true], + children: [ + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [1048576], + defined: [true] + }, + { + dimension: OBJECT_TYPE, + title: 'blink::Node', + size: [331776], + defined: [true] + }, + { + dimension: OBJECT_TYPE, + title: 'MissingSumOverAllTypes', + size: [51200], + defined: [true] + } + ] + }, + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [3811338], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'MessageLoop::RunTask', + size: [1406976], + defined: [true], + children: [ + { + dimension: OBJECT_TYPE, + title: 'blink::Node', + size: [331776], + defined: [true] + }, + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [1024000], + defined: [true] + }, + { + dimension: OBJECT_TYPE, + title: 'MissingSumOverAllTypes', + size: [51200], + defined: [true] + } + ] + }, + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [204800], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'MessageLoop::RunTask', + size: [204800], + defined: [true], + children: [ + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [153600], + defined: [true] + }, + { + dimension: OBJECT_TYPE, + title: 'MissingSumOverAllTypes', + size: [51200], + defined: [true] + } + ] + }, + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [51200], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'MessageLoop::RunTask', + size: [51200], + defined: [true], + children: [ + { + dimension: OBJECT_TYPE, + title: 'MissingSumOverAllTypes', + size: [51200], + defined: [true] + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'MissingSumOverAllTypes', + size: [51200], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'MessageLoop::RunTask', + size: [51200], + defined: [true] + } + ] + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [153600], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'MessageLoop::RunTask', + size: [153600], + defined: [true] + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'MissingSumOverAllTypes', + size: [51200], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'MessageLoop::RunTask', + size: [51200], + defined: [true] + }, + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [51200], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'MessageLoop::RunTask', + size: [51200], + defined: [true] + } + ] + } + ] + } + ] + }, + { + dimension: STACK_FRAME, + title: 'MissingParent', + size: [10], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'MessageLoop::RunTask', + size: [10], + defined: [true] + } + ] + }, + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [2404352], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'MessageLoop::RunTask', + size: [2404352], + defined: [true], + children: [ + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [20480], + defined: [true] + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [20480], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'MessageLoop::RunTask', + size: [20480], + defined: [true] + } + ] + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'blink::Node', + size: [331776], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'MessageLoop::RunTask', + size: [331776], + defined: [true] + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [1044480], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'MessageLoop::RunTask', + size: [1024000], + defined: [true] + }, + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [153600], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'MessageLoop::RunTask', + size: [153600], + defined: [true] + } + ] + }, + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [20480], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'MessageLoop::RunTask', + size: [20480], + defined: [true] + } + ] + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'MissingSumOverAllTypes', + size: [51200], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'MessageLoop::RunTask', + size: [51200], + defined: [true] + }, + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [51200], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'MessageLoop::RunTask', + size: [51200], + defined: [true] + }, + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [51200], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'MessageLoop::RunTask', + size: [51200], + defined: [true] + } + ] + } + ] + } + ] + } + ] + }, + { + dimension: STACK_FRAME, + title: '<self>', + size: [409600], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [409600], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'MessageLoop::RunTask', + size: [102400], + defined: [true] + }, + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [307200], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'MessageLoop::RunTask', + size: [307200], + defined: [true], + children: [ + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [15360], + defined: [true] + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [15360], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'MessageLoop::RunTask', + size: [15360], + defined: [true] + } + ] + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [15360], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [15360], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'MessageLoop::RunTask', + size: [15360], + defined: [true] + } + ] + } + ] + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [15360], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [15360], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [15360], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'MessageLoop::RunTask', + size: [15360], + defined: [true] + } + ] + } + ] + } + ] + } + ] + }, + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [3452928], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [3145728], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'MessageLoop::RunTask', + size: [1048576], + defined: [true], + children: [ + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [716800], + defined: [true] + } + ] + }, + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [2097152], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'MessageLoop::RunTask', + size: [2097152], + defined: [true] + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [716800], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'MessageLoop::RunTask', + size: [716800], + defined: [true] + } + ] + } + ] + }, + { + dimension: STACK_FRAME, + title: 'MessageLoop::RunTask', + size: [2404352], + defined: [true], + children: [ + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [20480], + defined: [true] + } + ] + }, + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [2097152], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [2097152], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [2097152], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'MessageLoop::RunTask', + size: [2097152], + defined: [true] + } + ] + } + ] + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [737280], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [716800], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'MessageLoop::RunTask', + size: [716800], + defined: [true] + } + ] + }, + { + dimension: STACK_FRAME, + title: 'MessageLoop::RunTask', + size: [20480], + defined: [true] + } + ] + } + ] + }, + { + dimension: STACK_FRAME, + title: 'MissingParent', + size: [10], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'MessageLoop::RunTask', + size: [10], + defined: [true] + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [1048576], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'MessageLoop::RunTask', + size: [1048576], + defined: [true] + }, + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [1044480], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'MessageLoop::RunTask', + size: [1024000], + defined: [true] + }, + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [153600], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'MessageLoop::RunTask', + size: [153600], + defined: [true] + } + ] + }, + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [20480], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'MessageLoop::RunTask', + size: [20480], + defined: [true] + } + ] + } + ] + }, + { + dimension: STACK_FRAME, + title: '<self>', + size: [15360], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [15360], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [15360], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'MessageLoop::RunTask', + size: [15360], + defined: [true] + } + ] + } + ] + } + ] + }, + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [737280], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [716800], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'MessageLoop::RunTask', + size: [716800], + defined: [true] + } + ] + }, + { + dimension: STACK_FRAME, + title: 'MessageLoop::RunTask', + size: [20480], + defined: [true] + } + ] + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'blink::Node', + size: [331776], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'MessageLoop::RunTask', + size: [331776], + defined: [true] + }, + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [331776], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'MessageLoop::RunTask', + size: [331776], + defined: [true] + } + ] + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'MissingSumOverAllTypes', + size: [51200], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'MessageLoop::RunTask', + size: [51200], + defined: [true] + }, + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [51200], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'MessageLoop::RunTask', + size: [51200], + defined: [true] + }, + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [51200], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'MessageLoop::RunTask', + size: [51200], + defined: [true] + }, + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [51200], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'MessageLoop::RunTask', + size: [51200], + defined: [true] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ]); + + changeView(viewEl, TOP_DOWN_HEAVY_VIEW); + checkSplitView(viewEl, + { expectedInfoBarDisplayed: true }, [ + { + dimension: ROOT, + title: 'partition_alloc', + size: [4194304], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'MessageLoop::RunTask', + size: [4194304], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [1406976], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: '<self>', + size: [102400], + defined: [true], + }, + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [1048576], + defined: [true], + children: [ + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [716800], + defined: [true], + } + ] + }, + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [153600 + 51200], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [51200], + defined: [true], + children: [ + { + dimension: OBJECT_TYPE, + title: 'MissingSumOverAllTypes', + size: [51200], + defined: [true], + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [153600], + defined: [true], + }, + { + dimension: OBJECT_TYPE, + title: 'MissingSumOverAllTypes', + size: [51200], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [51200], + defined: [true], + } + ] + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'blink::Node', + size: [331776], + defined: [true], + }, + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [1024000], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [716800], + defined: [true], + }, + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [153600], + defined: [true], + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'MissingSumOverAllTypes', + size: [51200], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [51200], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [51200], + defined: [true], + } + ] + } + ] + } + ] + }, + { + dimension: STACK_FRAME, + title: 'MissingParent', + size: [10], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [10], + defined: [true], + } + ] + }, + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [2404352], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [2404352], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: '<self>', + size: [307200], + defined: [true], + children: [ + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [15360], + defined: [true], + } + ] + }, + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [2097152], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [2097152], + defined: [true], + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [20480], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: '<self>', + size: [15360], + defined: [true], + } + ] + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [20480], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [20480], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: '<self>', + size: [15360], + defined: [true], + } + ] + } + ] + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [1048576], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [1024000], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [716800], + defined: [true], + }, + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [153600], + defined: [true], + } + ] + }, + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [20480], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [20480], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: '<self>', + size: [15360], + defined: [true], + } + ] + } + ] + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'blink::Node', + size: [331776], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [331776], + defined: [true], + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'MissingSumOverAllTypes', + size: [51200], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [51200], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [51200], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [51200], + defined: [true], + } + ] + } + ] + } + ] + } + ] + }, + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [1406976 + 10 + 2404352], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: '<self>', + size: [102400 + 307200], + defined: [true], + children: [ + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [15360], + defined: [true], + } + ] + }, + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [1048576 + 2097152], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [2097152], + defined: [true], + }, + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [716800], + defined: [true], + } + ] + }, + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [153600 + 51200], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [51200], + defined: [true], + children: [ + { + dimension: OBJECT_TYPE, + title: 'MissingSumOverAllTypes', + size: [51200], + defined: [true], + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [153600], + defined: [true], + }, + { + dimension: OBJECT_TYPE, + title: 'MissingSumOverAllTypes', + size: [51200], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [51200], + defined: [true], + } + ] + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'blink::Node', + size: [331776], + defined: [true], + }, + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [1024000 + 20480], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: '<self>', + size: [15360], + defined: [true], + }, + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [716800], + defined: [true], + }, + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [153600], + defined: [true], + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'MissingSumOverAllTypes', + size: [51200], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [51200], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [51200], + defined: [true], + } + ] + } + ] + } + ] + }, + { + dimension: STACK_FRAME, + title: '<self>', + size: [102400 + 307200], + defined: [true], + children: [ + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [15360], + defined: [true], + } + ] + }, + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [1048576 + 2404352], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [2404352], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: '<self>', + size: [307200], + defined: [true], + children: [ + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [15360], + defined: [true], + } + ] + }, + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [2097152], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [2097152], + defined: [true], + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [20480], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: '<self>', + size: [15360], + defined: [true], + } + ] + } + ] + }, + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [2097152], + defined: [true], + }, + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [716800 + 20480], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [20480], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: '<self>', + size: [15360], + defined: [true], + } + ] + } + ] + } + ] + }, + { + dimension: STACK_FRAME, + title: 'MissingParent', + size: [10], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [10], + defined: [true], + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [1048576], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'MessageLoop::RunTask', + size: [1048576], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [1024000], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [716800], + defined: [true], + }, + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [153600], + defined: [true], + } + ] + }, + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [20480], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [20480], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: '<self>', + size: [15360], + defined: [true], + } + ] + } + ] + } + ] + }, + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [1024000 + 20480], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: '<self>', + size: [15360], + defined: [true], + }, + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [716800], + defined: [true], + }, + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [153600], + defined: [true], + } + ] + }, + { + dimension: STACK_FRAME, + title: '<self>', + size: [15360], + defined: [true], + }, + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [716800 + 20480], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [20480], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: '<self>', + size: [15360], + defined: [true], + } + ] + } + ] + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'blink::Node', + size: [331776], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'MessageLoop::RunTask', + size: [331776], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [331776], + defined: [true], + } + ] + }, + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [331776], + defined: [true], + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'MissingSumOverAllTypes', + size: [51200], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'MessageLoop::RunTask', + size: [51200], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [51200], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [51200], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [51200], + defined: [true], + } + ] + } + ] + } + ] + }, + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [51200], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [51200], + defined: [true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [51200], + defined: [true], + } + ] + } + ] + } + ] + } + ] + } + ]); + }); + + test('instantiate_multipleDiff', function() { + const heapDumps = createHeapDumps(true /* with allocation counts */); + + const viewEl = tr.ui.analysis.createTestPane( + 'tr-ui-a-memory-dump-heap-details-pane'); + viewEl.heapDumps = heapDumps; + viewEl.aggregationMode = AggregationMode.DIFF; + viewEl.rebuild(); + this.addHTMLOutput(viewEl); + + changeView(viewEl, TOP_DOWN_HEAVY_VIEW); + checkSplitView(viewEl, + { + expectedAggregationMode: AggregationMode.DIFF, + expectedInfoBarDisplayed: true, + expectedCountColumns: true + }, + [ + { + dimension: ROOT, + title: 'partition_alloc', + size: [4194304, 4954521], + count: [1000, 900], + averageSize: [4194304 / 1000, 4954521 / 900], + defined: [true, true], + children: [ + { + dimension: STACK_FRAME, + title: 'MessageLoop::RunTask', + size: [4194304, 4823449], + count: [1000, 884], + averageSize: [4194304 / 1000, 4823449 / 884], + defined: [true, true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [1406976, 2170880], + count: [299, 600], + averageSize: [1406976 / 299, 2170880 / 600], + defined: [true, true], + children: [ + { + dimension: STACK_FRAME, + title: '<self>', + size: [102400, 393216], + count: [30, 17], + averageSize: [102400 / 30, 393216 / 17], + defined: [true, true] + }, + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [1048576, 1572864], + count: [101, 270], + averageSize: [1048576 / 101, 1572864 / 270], + defined: [true, true], + children: [ + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [716800, 614400], + count: [100, 123], + averageSize: [716800 / 100, 614400 / 123], + defined: [true, true] + }, + { + dimension: OBJECT_TYPE, + title: 'blink::Node', + size: [undefined, 819200], + count: [undefined, 4], + averageSize: [undefined, 819200 / 4], + defined: [false, true] + } + ] + }, + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [204800, 204800], + count: [25, 313], + averageSize: [204800 / 25, 204800 / 313], + defined: [true, true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [51200, 204800], + count: [9, 313], + averageSize: [51200 / 9, 204800 / 313], + defined: [true, true], + children: [ + { + dimension: OBJECT_TYPE, + title: 'MissingSumOverAllTypes', + size: [51200, undefined], + count: [9, undefined], + averageSize: [51200 / 9, undefined], + defined: [true, false] + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [153600, 122880], + count: [15, 270], + averageSize: [153600 / 15, 122880 / 270], + defined: [true, true] + }, + { + dimension: OBJECT_TYPE, + title: 'MissingSumOverAllTypes', + size: [51200, undefined], + count: [9, undefined], + averageSize: [51200 / 9, undefined], + defined: [true, false], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [51200, undefined], + count: [9, undefined], + averageSize: [51200 / 9, undefined], + defined: [true, false] + } + ] + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'blink::Node', + size: [331776, 819200], + count: [10, 4], + averageSize: [331776 / 10, 819200 / 4], + defined: [true, true], + children: [ + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [undefined, 819200], + count: [undefined, 4], + averageSize: [undefined, 819200 / 4], + defined: [false, true] + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [1024000, 1024000], + count: [176, 500], + averageSize: [1024000 / 176, 1024000 / 500], + defined: [true, true], + children: [ + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [716800, 614400], + count: [100, 123], + averageSize: [716800 / 100, 614400 / 123], + defined: [true, true] + }, + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [153600, 122880], + count: [15, 270], + averageSize: [153600 / 15, 122880 / 270], + defined: [true, true] + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'MissingSumOverAllTypes', + size: [51200, undefined], + count: [9, undefined], + averageSize: [51200 / 9, undefined], + defined: [true, false], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [51200, undefined], + count: [9, undefined], + averageSize: [51200 / 9, undefined], + defined: [true, false], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [51200, undefined], + count: [9, undefined], + averageSize: [51200 / 9, undefined], + defined: [true, false] + } + ] + } + ] + } + ] + }, + { + dimension: STACK_FRAME, + title: 'MissingParent', + size: [10, undefined], + count: [2, undefined], + averageSize: [10 / 2, undefined], + defined: [true, false], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [10, undefined], + count: [2, undefined], + averageSize: [10 / 2, undefined], + defined: [true, false] + } + ] + }, + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [2404352, 2621440], + count: [399, 199], + averageSize: [2404352 / 399, 2621440 / 199], + defined: [true, true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [2404352, 2621440], + count: [399, 199], + averageSize: [2404352 / 399, 2621440 / 199], + defined: [true, true], + children: [ + { + dimension: STACK_FRAME, + title: '<self>', + size: [307200, undefined], + count: [300, undefined], + averageSize: [307200 / 300, undefined], + defined: [true, false], + children: [ + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [15360, undefined], + count: [5, undefined], + averageSize: [15360 / 5, undefined], + defined: [true, false] + } + ] + }, + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [2097152, 2516582], + count: [99, 158], + averageSize: [2097152 / 99, 2516582 / 158], + defined: [true, true], + children: [ + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [2097152, undefined], + count: [99, undefined], + averageSize: [2097152 / 99, undefined], + defined: [true, false] + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [20480, 20480], + count: [6, 4], + averageSize: [20480 / 6, 20480 / 4], + defined: [true, true], + children: [ + { + dimension: STACK_FRAME, + title: '<self>', + size: [15360, undefined], + count: [5, undefined], + averageSize: [15360 / 5, undefined], + defined: [true, false] + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'WTF::StringImpl', + size: [undefined, 126362], + count: [undefined, 56], + averageSize: [undefined, 126362 / 56], + defined: [false, true] + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [20480, 20480], + count: [6, 4], + averageSize: [20480 / 6, 20480 / 4], + defined: [true, true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [20480, 20480], + count: [6, 4], + averageSize: [20480 / 6, 20480 / 4], + defined: [true, true], + children: [ + { + dimension: STACK_FRAME, + title: '<self>', + size: [15360, undefined], + count: [5, undefined], + averageSize: [15360 / 5, undefined], + defined: [true, false] + } + ] + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'WTF::StringImpl', + size: [undefined, 126362], + count: [undefined, 56], + averageSize: [undefined, 126362 / 56], + defined: [false, true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [undefined, 126362], + count: [undefined, 56], + averageSize: [undefined, 126362 / 56], + defined: [false, true] + } + ] + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [1048576, 1127219], + count: [200, 504], + averageSize: [1048576 / 200, 1127219 / 504], + defined: [true, true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [1024000, 1024000], + count: [176, 500], + averageSize: [1024000 / 176, 1024000 / 500], + defined: [true, true], + children: [ + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [716800, 614400], + count: [100, 123], + averageSize: [716800 / 100, 614400 / 123], + defined: [true, true] + }, + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [153600, 122880], + count: [15, 270], + averageSize: [153600 / 15, 122880 / 270], + defined: [true, true] + } + ] + }, + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [20480, 20480], + count: [6, 4], + averageSize: [20480 / 6, 20480 / 4], + defined: [true, true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [20480, 20480], + count: [6, 4], + averageSize: [20480 / 6, 20480 / 4], + defined: [true, true], + children: [ + { + dimension: STACK_FRAME, + title: '<self>', + size: [15360, undefined], + count: [5, undefined], + averageSize: [15360 / 5, undefined], + defined: [true, false] + } + ] + } + ] + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'blink::Node', + size: [331776, 819200], + count: [10, 4], + averageSize: [331776 / 10, 819200 / 4], + defined: [true, true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [331776, 819200], + count: [10, 4], + averageSize: [331776 / 10, 819200 / 4], + defined: [true, true], + children: [ + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [undefined, 819200], + count: [undefined, 4], + averageSize: [undefined, 819200 / 4], + defined: [false, true] + } + ] + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'MissingSumOverAllTypes', + size: [51200, undefined], + count: [9, undefined], + averageSize: [51200 / 9, undefined], + defined: [true, false], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [51200, undefined], + count: [9, undefined], + averageSize: [51200 / 9, undefined], + defined: [true, false], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [51200, undefined], + count: [9, undefined], + averageSize: [51200 / 9, undefined], + defined: [true, false], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [51200, undefined], + count: [9, undefined], + averageSize: [51200 / 9, undefined], + defined: [true, false] + } + ] + } + ] + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'WTF::StringImpl', + size: [undefined, 126362], + count: [undefined, 56], + averageSize: [undefined, 126362 / 56], + defined: [false, true], + children: [ + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [undefined, 126362], + count: [undefined, 56], + averageSize: [undefined, 126362 / 56], + defined: [false, true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [undefined, 126362], + count: [undefined, 56], + averageSize: [undefined, 126362 / 56], + defined: [false, true] + } + ] + } + ] + } + ] + }, + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [3811338, 4792320], + count: [700, 799], + averageSize: [3811338 / 700, 4792320 / 799], + defined: [true, true], + children: [ + { + dimension: STACK_FRAME, + title: '<self>', + size: [409600, 393216], + count: [330, 17], + averageSize: [409600 / 330, 393216 / 17], + defined: [true, true], + children: [ + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [15360, undefined], + count: [5, undefined], + averageSize: [15360 / 5, undefined], + defined: [true, false] + } + ] + }, + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [3145728, 4089446], + count: [200, 428], + averageSize: [3145728 / 200, 4089446 / 428], + defined: [true, true], + children: [ + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [2097152, undefined], + count: [99, undefined], + averageSize: [2097152 / 99, undefined], + defined: [true, false] + }, + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [716800, 614400], + count: [100, 123], + averageSize: [716800 / 100, 614400 / 123], + defined: [true, true] + }, + { + dimension: OBJECT_TYPE, + title: 'blink::Node', + size: [undefined, 819200], + count: [undefined, 4], + averageSize: [undefined, 819200 / 4], + defined: [false, true] + } + ] + }, + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [204800, 204800], + count: [25, 313], + averageSize: [204800 / 25, 204800 / 313], + defined: [true, true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [51200, 204800], + count: [9, 313], + averageSize: [51200 / 9, 204800 / 313], + defined: [true, true], + children: [ + { + dimension: OBJECT_TYPE, + title: 'MissingSumOverAllTypes', + size: [51200, undefined], + count: [9, undefined], + averageSize: [51200 / 9, undefined], + defined: [true, false] + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [153600, 122880], + count: [15, 270], + averageSize: [153600 / 15, 122880 / 270], + defined: [true, true] + }, + { + dimension: OBJECT_TYPE, + title: 'MissingSumOverAllTypes', + size: [51200, undefined], + count: [9, undefined], + averageSize: [51200 / 9, undefined], + defined: [true, false], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [51200, undefined], + count: [9, undefined], + averageSize: [51200 / 9, undefined], + defined: [true, false] + } + ] + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'blink::Node', + size: [331776, 819200], + count: [10, 4], + averageSize: [331776 / 10, 819200 / 4], + defined: [true, true], + children: [ + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [undefined, 819200], + count: [undefined, 4], + averageSize: [undefined, 819200 / 4], + defined: [false, true] + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [1044480, 1044480], + count: [182, 504], + averageSize: [1044480 / 182, 1044480 / 504], + defined: [true, true], + children: [ + { + dimension: STACK_FRAME, + title: '<self>', + size: [15360, undefined], + count: [5, undefined], + averageSize: [15360 / 5, undefined], + defined: [true, false] + }, + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [716800, 614400], + count: [100, 123], + averageSize: [716800 / 100, 614400 / 123], + defined: [true, true] + }, + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [153600, 122880], + count: [15, 270], + averageSize: [153600 / 15, 122880 / 270], + defined: [true, true] + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'MissingSumOverAllTypes', + size: [51200, undefined], + count: [9, undefined], + averageSize: [51200 / 9, undefined], + defined: [true, false], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [51200, undefined], + count: [9, undefined], + averageSize: [51200 / 9, undefined], + defined: [true, false], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [51200, undefined], + count: [9, undefined], + averageSize: [51200 / 9, undefined], + defined: [true, false] + } + ] + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'WTF::StringImpl', + size: [undefined, 126362], + count: [undefined, 56], + averageSize: [undefined, 126362 / 56], + defined: [false, true] + } + ] + }, + { + dimension: STACK_FRAME, + title: '<self>', + size: [409600, 524288], + count: [330, 33], + averageSize: [409600 / 330, 524288 / 33], + defined: [true, true], + children: [ + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [15360, 131072], + count: [5, 16], + averageSize: [15360 / 5, 131072 / 16], + defined: [true, true] + } + ] + }, + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [3452928, 4194304], + count: [500, 469], + averageSize: [3452928 / 500, 4194304 / 469], + defined: [true, true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [2404352, 2621440], + count: [399, 199], + averageSize: [2404352 / 399, 2621440 / 199], + defined: [true, true], + children: [ + { + dimension: STACK_FRAME, + title: '<self>', + size: [307200, undefined], + count: [300, undefined], + averageSize: [307200 / 300, undefined], + defined: [true, false], + children: [ + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [15360, undefined], + count: [5, undefined], + averageSize: [15360 / 5, undefined], + defined: [true, false] + } + ] + }, + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [2097152, 2516582], + count: [99, 158], + averageSize: [2097152 / 99, 2516582 / 158], + defined: [true, true], + children: [ + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [2097152, undefined], + count: [99, undefined], + averageSize: [2097152 / 99, undefined], + defined: [true, false] + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [20480, 20480], + count: [6, 4], + averageSize: [20480 / 6, 20480 / 4], + defined: [true, true], + children: [ + { + dimension: STACK_FRAME, + title: '<self>', + size: [15360, undefined], + count: [5, undefined], + averageSize: [15360 / 5, undefined], + defined: [true, false] + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'WTF::StringImpl', + size: [undefined, 126362], + count: [undefined, 56], + averageSize: [undefined, 126362 / 56], + defined: [false, true] + } + ] + }, + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [2097152, undefined], + count: [99, undefined], + averageSize: [2097152 / 99, undefined], + defined: [true, false] + }, + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [737280, 634880], + count: [106, 127], + averageSize: [737280 / 106, 634880 / 127], + defined: [true, true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [20480, 20480], + count: [6, 4], + averageSize: [20480 / 6, 20480 / 4], + defined: [true, true], + children: [ + { + dimension: STACK_FRAME, + title: '<self>', + size: [15360, undefined], + count: [5, undefined], + averageSize: [15360 / 5, undefined], + defined: [true, false] + } + ] + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'blink::Node', + size: [undefined, 819200], + count: [undefined, 4], + averageSize: [undefined, 819200 / 4], + defined: [false, true] + }, + { + dimension: OBJECT_TYPE, + title: 'WTF::StringImpl', + size: [undefined, 126362], + count: [undefined, 56], + averageSize: [undefined, 126362 / 56], + defined: [false, true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [undefined, 126362], + count: [undefined, 56], + averageSize: [undefined, 126362 / 56], + defined: [false, true] + } + ] + } + ] + }, + { + dimension: STACK_FRAME, + title: 'MissingParent', + size: [10, undefined], + count: [2, undefined], + averageSize: [10 / 2, undefined], + defined: [true, false], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [10, undefined], + count: [2, undefined], + averageSize: [10 / 2, undefined], + defined: [true, false] + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [1048576, 1258291], + count: [200, 520], + averageSize: [1048576 / 200, 1258291 / 520], + defined: [true, true], + children: [ + { + dimension: STACK_FRAME, + title: 'MessageLoop::RunTask', + size: [1048576, 1127219], + count: [200, 504], + averageSize: [1048576 / 200, 1127219 / 504], + defined: [true, true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [1024000, 1024000], + count: [176, 500], + averageSize: [1024000 / 176, 1024000 / 500], + defined: [true, true], + children: [ + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [716800, 614400], + count: [100, 123], + averageSize: [716800 / 100, 614400 / 123], + defined: [true, true] + }, + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [153600, 122880], + count: [15, 270], + averageSize: [153600 / 15, 122880 / 270], + defined: [true, true] + } + ] + }, + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [20480, 20480], + count: [6, 4], + averageSize: [20480 / 6, 20480 / 4], + defined: [true, true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [20480, 20480], + count: [6, 4], + averageSize: [20480 / 6, 20480 / 4], + defined: [true, true], + children: [ + { + dimension: STACK_FRAME, + title: '<self>', + size: [15360, undefined], + count: [5, undefined], + averageSize: [15360 / 5, undefined], + defined: [true, false] + } + ] + } + ] + } + ] + }, + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [1044480, 1044480], + count: [182, 504], + averageSize: [1044480 / 182, 1044480 / 504], + defined: [true, true], + children: [ + { + dimension: STACK_FRAME, + title: '<self>', + size: [15360, undefined], + count: [5, undefined], + averageSize: [15360 / 5, undefined], + defined: [true, false] + }, + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [716800, 614400], + count: [100, 123], + averageSize: [716800 / 100, 614400 / 123], + defined: [true, true] + }, + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [153600, 122880], + count: [15, 270], + averageSize: [153600 / 15, 122880 / 270], + defined: [true, true] + } + ] + }, + { + dimension: STACK_FRAME, + title: '<self>', + size: [15360, 131072], + count: [5, 16], + averageSize: [15360 / 5, 131072 / 16], + defined: [true, true] + }, + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [737280, 634880], + count: [106, 127], + averageSize: [737280 / 106, 634880 / 127], + defined: [true, true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [20480, 20480], + count: [6, 4], + averageSize: [20480 / 6, 20480 / 4], + defined: [true, true], + children: [ + { + dimension: STACK_FRAME, + title: '<self>', + size: [15360, undefined], + count: [5, undefined], + averageSize: [15360 / 5, undefined], + defined: [true, false] + } + ] + } + ] + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'blink::Node', + size: [331776, 1048576], + count: [10, 5], + averageSize: [331776 / 10, 1048576 / 5], + defined: [true, true], + children: [ + { + dimension: STACK_FRAME, + title: 'MessageLoop::RunTask', + size: [331776, 819200], + count: [10, 4], + averageSize: [331776 / 10, 819200 / 4], + defined: [true, true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [331776, 819200], + count: [10, 4], + averageSize: [331776 / 10, 819200 / 4], + defined: [true, true], + children: [ + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [undefined, 819200], + count: [undefined, 4], + averageSize: [undefined, 819200 / 4], + defined: [false, true] + } + ] + } + ] + }, + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [331776, 819200], + count: [10, 4], + averageSize: [331776 / 10, 819200 / 4], + defined: [true, true], + children: [ + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [undefined, 819200], + count: [undefined, 4], + averageSize: [undefined, 819200 / 4], + defined: [false, true] + } + ] + }, + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [undefined, 819200], + count: [undefined, 4], + averageSize: [undefined, 819200 / 4], + defined: [false, true] + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'MissingSumOverAllTypes', + size: [51200, undefined], + count: [9, undefined], + averageSize: [51200 / 9, undefined], + defined: [true, false], + children: [ + { + dimension: STACK_FRAME, + title: 'MessageLoop::RunTask', + size: [51200, undefined], + count: [9, undefined], + averageSize: [51200 / 9, undefined], + defined: [true, false], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [51200, undefined], + count: [9, undefined], + averageSize: [51200 / 9, undefined], + defined: [true, false], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [51200, undefined], + count: [9, undefined], + averageSize: [51200 / 9, undefined], + defined: [true, false], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [51200, undefined], + count: [9, undefined], + averageSize: [51200 / 9, undefined], + defined: [true, false] + } + ] + } + ] + } + ] + }, + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [51200, undefined], + count: [9, undefined], + averageSize: [51200 / 9, undefined], + defined: [true, false], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [51200, undefined], + count: [9, undefined], + averageSize: [51200 / 9, undefined], + defined: [true, false], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [51200, undefined], + count: [9, undefined], + averageSize: [51200 / 9, undefined], + defined: [true, false] + } + ] + } + ] + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'WTF::StringImpl', + size: [undefined, 126362], + count: [undefined, 56], + averageSize: [undefined, 126362 / 56], + defined: [false, true], + children: [ + { + dimension: STACK_FRAME, + title: 'MessageLoop::RunTask', + size: [undefined, 126362], + count: [undefined, 56], + averageSize: [undefined, 126362 / 56], + defined: [false, true], + children: [ + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [undefined, 126362], + count: [undefined, 56], + averageSize: [undefined, 126362 / 56], + defined: [false, true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [undefined, 126362], + count: [undefined, 56], + averageSize: [undefined, 126362 / 56], + defined: [false, true] + } + ] + } + ] + }, + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [undefined, 126362], + count: [undefined, 56], + averageSize: [undefined, 126362 / 56], + defined: [false, true] + }, + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [undefined, 126362], + count: [undefined, 56], + averageSize: [undefined, 126362 / 56], + defined: [false, true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [undefined, 126362], + count: [undefined, 56], + averageSize: [undefined, 126362 / 56], + defined: [false, true] + } + ] + } + ] + } + ] + } + ]); + + changeView(viewEl, TOP_DOWN_TREE_VIEW); + checkSplitView(viewEl, + { + expectedAggregationMode: AggregationMode.DIFF, + expectedCountColumns: true + }, + [ + { + dimension: ROOT, + title: 'partition_alloc', + size: [4194304, 4954521], + count: [1000, 900], + averageSize: [4194304 / 1000, 4954521 / 900], + defined: [true, true], + children: [ + { + dimension: STACK_FRAME, + title: 'MessageLoop::RunTask', + size: [4194304, 4823449], + count: [1000, 884], + averageSize: [4194304 / 1000, 4823449 / 884], + defined: [true, true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [1406976, 2170880], + count: [299, 600], + averageSize: [1406976 / 299, 2170880 / 600], + defined: [true, true], + children: [ + { + dimension: STACK_FRAME, + title: '<self>', + size: [102400, 393216], + count: [30, 17], + averageSize: [102400 / 30, 393216 / 17], + defined: [true, true] + }, + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [1048576, 1572864], + count: [101, 270], + averageSize: [1048576 / 101, 1572864 / 270], + defined: [true, true], + children: [ + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [716800, 614400], + count: [100, 123], + averageSize: [716800 / 100, 614400 / 123], + defined: [true, true] + }, + { + dimension: OBJECT_TYPE, + title: '<other>', + size: [331776, 139264], + count: [1, 143], + averageSize: [331776 / 1, 139264 / 143], + defined: [true, true] + }, + { + dimension: OBJECT_TYPE, + title: 'blink::Node', + size: [undefined, 819200], + count: [undefined, 4], + averageSize: [undefined, 819200 / 4], + defined: [false, true] + } + ] + }, + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [204800, 204800], + count: [25, 313], + averageSize: [204800 / 25, 204800 / 313], + defined: [true, true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [undefined, 204800], + count: [undefined, 313], + averageSize: [undefined, 204800 / 313], + defined: [false, true] + }, + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [153600, 122880], + count: [15, 270], + averageSize: [153600 / 15, 122880 / 270], + defined: [true, true] + }, + { + dimension: OBJECT_TYPE, + title: '<other>', + size: [51200, 81920], + count: [10, 43], + averageSize: [51200 / 10, 81920 / 43], + defined: [true, true] + } + ] + }, + { + dimension: STACK_FRAME, + title: '<other>', + size: [51200, undefined], + count: [143, undefined], + averageSize: [51200 / 143, undefined], + defined: [true, false] + }, + { + dimension: OBJECT_TYPE, + title: 'blink::Node', + size: [331776, 819200], + count: [10, 4], + averageSize: [331776 / 10, 819200 / 4], + defined: [true, true], + children: [ + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [undefined, 819200], + count: [undefined, 4], + averageSize: [undefined, 819200 / 4], + defined: [false, true] + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [1024000, 1024000], + count: [176, 500], + averageSize: [1024000 / 176, 1024000 / 500], + defined: [true, true], + children: [ + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [716800, 614400], + count: [100, 123], + averageSize: [716800 / 100, 614400 / 123], + defined: [true, true] + }, + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [153600, 122880], + count: [15, 270], + averageSize: [153600 / 15, 122880 / 270], + defined: [true, true] + }, + { + dimension: STACK_FRAME, + title: '<other>', + size: [153600, 286720], + count: [61, 107], + averageSize: [153600 / 61, 286720 / 107], + defined: [true, true] + } + ] + }, + { + dimension: OBJECT_TYPE, + title: '<other>', + size: [51200, 327680], + count: [113, 96], + averageSize: [51200 / 113, 327680 / 96], + defined: [true, true] + } + ] + }, + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [2404352, 2621440], + count: [399, 199], + averageSize: [2404352 / 399, 2621440 / 199], + defined: [true, true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [2404352, 2621440], + count: [399, 199], + averageSize: [2404352 / 399, 2621440 / 199], + defined: [true, true], + children: [ + { + dimension: STACK_FRAME, + title: '<self>', + size: [307200, undefined], + count: [300, undefined], + averageSize: [307200 / 300, undefined], + defined: [true, false], + children: [ + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [15360, undefined], + count: [5, undefined], + averageSize: [15360 / 5, undefined], + defined: [true, false] + }, + { + dimension: OBJECT_TYPE, + title: '<other>', + size: [291840, undefined], + count: [295, undefined], + averageSize: [291840 / 295, undefined], + defined: [true, false] + } + ] + }, + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [2097152, 2516582], + count: [99, 158], + averageSize: [2097152 / 99, 2516582 / 158], + defined: [true, true], + children: [ + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [2097152, undefined], + count: [99, undefined], + averageSize: [2097152 / 99, undefined], + defined: [true, false] + } + ] + }, + { + dimension: STACK_FRAME, + title: '<other>', + size: [undefined, 104858], + count: [undefined, 41], + averageSize: [undefined, 104858 / 41], + defined: [false, true] + }, + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [20480, 20480], + count: [6, 4], + averageSize: [20480 / 6, 20480 / 4], + defined: [true, true], + children: [ + { + dimension: STACK_FRAME, + title: '<self>', + size: [15360, undefined], + count: [5, undefined], + averageSize: [15360 / 5, undefined], + defined: [true, false] + }, + { + dimension: STACK_FRAME, + title: '<other>', + size: [5120, undefined], + count: [1, undefined], + averageSize: [5120 / 1, undefined], + defined: [true, false] + } + ] + }, + { + dimension: OBJECT_TYPE, + title: '<other>', + size: [2383872, 2474598], + count: [393, 139], + averageSize: [2383872 / 393, 2474598 / 139], + defined: [true, true] + }, + { + dimension: OBJECT_TYPE, + title: 'WTF::StringImpl', + size: [undefined, 126362], + count: [undefined, 56], + averageSize: [undefined, 126362 / 56], + defined: [false, true] + } + ] + } + ] + }, + { + dimension: STACK_FRAME, + title: '<other>', + size: [382976, 31129], + count: [302, 85], + averageSize: [382976 / 302, 31129 / 85], + defined: [true, true] + }, + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [1048576, 1127219], + count: [200, 504], + averageSize: [1048576 / 200, 1127219 / 504], + defined: [true, true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [1024000, 1024000], + count: [176, 500], + averageSize: [1024000 / 176, 1024000 / 500], + defined: [true, true], + children: [ + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [716800, 614400], + count: [100, 123], + averageSize: [716800 / 100, 614400 / 123], + defined: [true, true] + }, + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [153600, 122880], + count: [15, 270], + averageSize: [153600 / 15, 122880 / 270], + defined: [true, true] + }, + { + dimension: STACK_FRAME, + title: '<other>', + size: [153600, 286720], + count: [61, 107], + averageSize: [153600 / 61, 286720 / 107], + defined: [true, true] + } + ] + }, + { + dimension: STACK_FRAME, + title: '<other>', + size: [24576, 103219], + count: [24, 4], + averageSize: [24576 / 24, 103219 / 4], + defined: [true, true] + } + ] + }, + { + dimension: OBJECT_TYPE, + title: '<other>', + size: [3145728, 3696230], + count: [800, 380], + averageSize: [3145728 / 800, 3696230 / 380], + defined: [true, true] + } + ] + }, + { + dimension: STACK_FRAME, + title: '<self>', + size: [undefined, 131072], + count: [undefined, 16], + averageSize: [undefined, 131072 / 16], + defined: [false, true], + children: [ + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [undefined, 131072], + count: [undefined, 16], + averageSize: [undefined, 131072 / 16], + defined: [false, true] + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'v8::Context', + size: [1048576, 1258291], + count: [200, 520], + averageSize: [1048576 / 200, 1258291 / 520], + defined: [true, true], + children: [ + { + dimension: STACK_FRAME, + title: 'MessageLoop::RunTask', + size: [1048576, 1127219], + count: [200, 504], + averageSize: [1048576 / 200, 1127219 / 504], + defined: [true, true], + children: [ + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [1024000, 1024000], + count: [176, 500], + averageSize: [1024000 / 176, 1024000 / 500], + defined: [true, true], + children: [ + { + dimension: STACK_FRAME, + title: 'V8.Execute', + size: [716800, 614400], + count: [100, 123], + averageSize: [716800 / 100, 614400 / 123], + defined: [true, true] + }, + { + dimension: STACK_FRAME, + title: 'FunctionCall', + size: [153600, 122880], + count: [15, 270], + averageSize: [153600 / 15, 122880 / 270], + defined: [true, true] + }, + { + dimension: STACK_FRAME, + title: '<other>', + size: [153600, 286720], + count: [61, 107], + averageSize: [153600 / 61, 286720 / 107], + defined: [true, true] + } + ] + }, + { + dimension: STACK_FRAME, + title: '<other>', + size: [24576, 103219], + count: [24, 4], + averageSize: [24576 / 24, 103219 / 4], + defined: [true, true] + } + ] + }, + { + dimension: STACK_FRAME, + title: '<self>', + size: [undefined, 131072], + count: [undefined, 16], + averageSize: [undefined, 131072 / 16], + defined: [false, true] + } + ] + }, + { + dimension: OBJECT_TYPE, + title: 'blink::Node', + size: [331776, 1048576], + count: [10, 5], + averageSize: [331776 / 10, 1048576 / 5], + defined: [true, true] + }, + { + dimension: OBJECT_TYPE, + title: '<other>', + size: [2813952, 2647654], + count: [790, 375], + averageSize: [2813952 / 790, 2647654 / 375], + defined: [true, true] + } + ] + } + ]); + }); + + test('instantiate_multipleMax', function() { + const heapDumps = createHeapDumps(false); + + const viewEl = tr.ui.analysis.createTestPane( + 'tr-ui-a-memory-dump-heap-details-pane'); + viewEl.heapDumps = heapDumps; + viewEl.aggregationMode = AggregationMode.MAX; + viewEl.rebuild(); + this.addHTMLOutput(viewEl); + + changeView(viewEl, TOP_DOWN_HEAVY_VIEW); + checkSplitView(viewEl, + { + expectedAggregationMode: AggregationMode.MAX, + expectedInfoBarDisplayed: true + }, + [ + { + dimension: ROOT, + title: 'partition_alloc', + size: [4194304, 4954521], + defined: [true, true], + children: 9 // No need to check the full structure again. + } + ]); + }); + + test('instantiate_multipleWithUndefined', function() { + const heapDumps = createHeapDumps(false); + heapDumps.splice(1, 0, undefined); + + const viewEl = tr.ui.analysis.createTestPane( + 'tr-ui-a-memory-dump-heap-details-pane'); + viewEl.heapDumps = heapDumps; + viewEl.aggregationMode = AggregationMode.DIFF; + viewEl.rebuild(); + this.addHTMLOutput(viewEl); + + // Top-down tree view (default). + checkSplitView(viewEl, + { expectedAggregationMode: AggregationMode.DIFF }, + [ + { + dimension: ROOT, + title: 'partition_alloc', + size: [4194304, undefined, 4954521], + defined: [true, false, true], + children: 5 // No need to check the full structure again. + } + ]); + }); + + test('instantiate_selfHeapSingle', function() { + const heapDumps = createSelfHeapDumps(true).slice(0, 1); + + const viewEl = tr.ui.analysis.createTestPane( + 'tr-ui-a-memory-dump-heap-details-pane'); + viewEl.heapDumps = heapDumps; + viewEl.rebuild(); + this.addHTMLOutput(viewEl); + + // Top-down tree view (default). + checkSplitView(viewEl, + { expectedCountColumns: true }, + [ + { + dimension: ROOT, + title: 'partition_alloc', + size: [1024 * 3], + count: [300], + defined: [true], + children: 2 // No need to check the full structure again. + } + ]); + }); +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_heap_details_path_view.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_heap_details_path_view.html new file mode 100644 index 00000000000..1cc3c8d7e5f --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_heap_details_path_view.html @@ -0,0 +1,149 @@ +<!DOCTYPE html> +<!-- +Copyright 2016 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/color_scheme.html"> +<link rel="import" href="/tracing/base/event.html"> +<link rel="import" href="/tracing/ui/analysis/memory_dump_heap_details_util.html"> +<link rel="import" href="/tracing/ui/analysis/memory_dump_sub_view_util.html"> +<link rel="import" href="/tracing/ui/analysis/rebuildable_behavior.html"> +<link rel="import" href="/tracing/ui/base/dom_helpers.html"> +<link rel="import" href="/tracing/ui/base/table.html"> +<link rel="import" href="/tracing/value/ui/scalar_context_controller.html"> + +<dom-module id='tr-ui-a-memory-dump-heap-details-path-view'> + <template> + <style> + :host { + display: flex; + flex-direction: column; + } + </style> + <tr-v-ui-scalar-context-controller></tr-v-ui-scalar-context-controller> + <tr-ui-b-table id="table"></tr-ui-b-table> + </template> +</dom-module> +<script> +'use strict'; + +tr.exportTo('tr.ui.analysis', function() { + const DOWNWARDS_ARROW_WITH_TIP_RIGHTWARDS = String.fromCharCode(0x21B3); + + function HeapDetailsPathColumn(title) { + tr.ui.analysis.HeapDetailsTitleColumn.call(this, title); + } + + HeapDetailsPathColumn.prototype = { + __proto__: tr.ui.analysis.HeapDetailsTitleColumn.prototype, + + formatTitle(row) { + const title = tr.ui.analysis.HeapDetailsTitleColumn.prototype. + formatTitle.call(this, row); + if (row.dimension === tr.ui.analysis.HeapDetailsRowDimension.ROOT) { + return title; + } + + const arrowEl = document.createElement('span'); + Polymer.dom(arrowEl).textContent = DOWNWARDS_ARROW_WITH_TIP_RIGHTWARDS; + arrowEl.style.paddingRight = '2px'; + arrowEl.style.fontWeight = 'bold'; + arrowEl.style.color = tr.b.ColorScheme.getColorForReservedNameAsString( + 'heap_dump_child_node_arrow'); + + const rowEl = document.createElement('span'); + Polymer.dom(rowEl).appendChild(arrowEl); + Polymer.dom(rowEl).appendChild(tr.ui.b.asHTMLOrTextNode(title)); + return rowEl; + } + }; + + Polymer({ + is: 'tr-ui-a-memory-dump-heap-details-path-view', + behaviors: [tr.ui.analysis.RebuildableBehavior], + + created() { + this.selectedNode_ = undefined; + this.aggregationMode_ = undefined; + }, + + ready() { + this.$.table.addEventListener('selection-changed', function(event) { + this.selectedNode_ = this.$.table.selectedTableRow; + this.didSelectedNodeChange_(); + }.bind(this)); + }, + + didSelectedNodeChange_() { + this.dispatchEvent(new tr.b.Event('selected-node-changed')); + }, + + get selectedNode() { + return this.selectedNode_; + }, + + set selectedNode(node) { + this.selectedNode_ = node; + this.didSelectedNodeChange_(); + this.scheduleRebuild_(); + }, + + get aggregationMode() { + return this.aggregationMode_; + }, + + set aggregationMode(aggregationMode) { + this.aggregationMode_ = aggregationMode; + this.scheduleRebuild_(); + }, + + onRebuild_() { + if (this.selectedNode_ === undefined) { + this.$.table.clear(); + return; + } + + if (this.$.table.tableRows.includes(this.selectedNode_)) { + this.$.table.selectedTableRow = this.selectedNode_; + return; + } + + this.$.table.selectionMode = tr.ui.b.TableFormat.SelectionMode.ROW; + this.$.table.userCanModifySortOrder = false; + const rows = this.createRows_(this.selectedNode_); + this.$.table.tableRows = rows; + this.$.table.tableColumns = this.createColumns_(rows); + this.$.table.selectedTableRow = rows[rows.length - 1]; + }, + + createRows_(node) { + const rows = []; + while (node) { + rows.push(node); + node = node.parentNode; + } + rows.reverse(); + return rows; + }, + + createColumns_(rows) { + const titleColumn = new HeapDetailsPathColumn('Current path'); + titleColumn.width = '200px'; + + const numericColumns = tr.ui.analysis.MemoryColumn.fromRows(rows, { + cellKey: 'cells', + aggregationMode: this.aggregationMode_, + rules: tr.ui.analysis.HEAP_DETAILS_COLUMN_RULES, + shouldSetContextGroup: true + }); + tr.ui.analysis.MemoryColumn.spaceEqually(numericColumns); + + return [titleColumn].concat(numericColumns); + } + }); + + return {}; +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_heap_details_util.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_heap_details_util.html new file mode 100644 index 00000000000..2cf6c6ec8b1 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_heap_details_util.html @@ -0,0 +1,101 @@ +<!DOCTYPE html> +<!-- +Copyright 2016 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/color_scheme.html"> +<link rel="import" href="/tracing/ui/analysis/memory_dump_sub_view_util.html"> + +<script> +'use strict'; + +tr.exportTo('tr.ui.analysis', function() { + const LATIN_SMALL_LETTER_F_WITH_HOOK = String.fromCharCode(0x0192); + const CIRCLED_LATIN_CAPITAL_LETTER_T = String.fromCharCode(0x24C9); + + /** @{enum} */ + const HeapDetailsRowDimension = { + ROOT: {}, + STACK_FRAME: { + label: 'Stack frame', + symbol: LATIN_SMALL_LETTER_F_WITH_HOOK, + color: 'heap_dump_stack_frame' + }, + OBJECT_TYPE: { + label: 'Object type', + symbol: CIRCLED_LATIN_CAPITAL_LETTER_T, + color: 'heap_dump_object_type' + } + }; + + /** @{constructor} */ + function HeapDetailsTitleColumn(title) { + tr.ui.analysis.TitleColumn.call(this, title); + } + + HeapDetailsTitleColumn.prototype = { + __proto__: tr.ui.analysis.TitleColumn.prototype, + + formatTitle(row) { + if (row.dimension === HeapDetailsRowDimension.ROOT) { + return row.title; + } + + const symbolEl = document.createElement('span'); + Polymer.dom(symbolEl).textContent = row.dimension.symbol; + symbolEl.title = row.dimension.label; + symbolEl.style.color = tr.b.ColorScheme.getColorForReservedNameAsString( + row.dimension.color); + symbolEl.style.paddingRight = '4px'; + symbolEl.style.cursor = 'help'; + symbolEl.style.fontWeight = 'bold'; + + const titleEl = document.createElement('span'); + Polymer.dom(titleEl).appendChild(symbolEl); + Polymer.dom(titleEl).appendChild(document.createTextNode(row.title)); + + return titleEl; + } + }; + + /** @constructor */ + function AllocationCountColumn(name, cellPath, aggregationMode) { + tr.ui.analysis.DetailsNumericMemoryColumn.call( + this, name, cellPath, aggregationMode); + } + + AllocationCountColumn.prototype = { + __proto__: tr.ui.analysis.DetailsNumericMemoryColumn.prototype, + + getFormattingContext(unit) { + return { minimumFractionDigits: 0 }; + } + }; + + const HEAP_DETAILS_COLUMN_RULES = [ + { + condition: 'Size', + importance: 2, + columnConstructor: tr.ui.analysis.DetailsNumericMemoryColumn + }, + { + condition: 'Count', + importance: 1, + columnConstructor: AllocationCountColumn + }, + { + importance: 0, + columnConstructor: tr.ui.analysis.DetailsNumericMemoryColumn + } + ]; + + return { + HeapDetailsRowDimension, + HeapDetailsTitleColumn, + AllocationCountColumn, + HEAP_DETAILS_COLUMN_RULES, + }; +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_overview_pane.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_overview_pane.html new file mode 100644 index 00000000000..5df80bb88b4 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_overview_pane.html @@ -0,0 +1,774 @@ +<!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/base/fixed_color_scheme.html"> +<link rel="import" href="/tracing/base/scalar.html"> +<link rel="import" href="/tracing/base/unit.html"> +<link rel="import" href="/tracing/base/unit_scale.html"> +<link rel="import" href="/tracing/base/utils.html"> +<link rel="import" href="/tracing/model/memory_allocator_dump.html"> +<link rel="import" href="/tracing/ui/analysis/memory_dump_allocator_details_pane.html"> +<link rel="import" href="/tracing/ui/analysis/memory_dump_sub_view_util.html"> +<link rel="import" href="/tracing/ui/analysis/memory_dump_vm_regions_details_pane.html"> +<link rel="import" href="/tracing/ui/analysis/stacked_pane.html"> +<link rel="import" href="/tracing/ui/base/color_legend.html"> +<link rel="import" href="/tracing/ui/base/dom_helpers.html"> +<link rel="import" href="/tracing/ui/base/table.html"> +<link rel="import" href="/tracing/ui/view_specific_brushing_state.html"> + +<dom-module id='tr-ui-a-memory-dump-overview-pane'> + <template> + <style> + :host { + display: flex; + flex-direction: column; + } + + #label { + flex: 0 0 auto; + padding: 8px; + + background-color: #eee; + border-bottom: 1px solid #8e8e8e; + border-top: 1px solid white; + + font-size: 15px; + font-weight: bold; + } + + #label a { + font-weight: normal; + float: right; + } + + #contents { + flex: 1 0 auto; + align-self: stretch; + font-size: 12px; + overflow: auto; + } + + #info_text { + padding: 8px; + color: #666; + font-style: italic; + text-align: center; + } + + #table { + display: none; /* Hide until memory dumps are set. */ + flex: 1 0 auto; + align-self: stretch; + font-size: 12px; + } + </style> + <tr-ui-b-view-specific-brushing-state id="state" + view-id="analysis.memory_dump_overview_pane"> + </tr-ui-b-view-specific-brushing-state> + <div id="label">Overview <a href="https://chromium.googlesource.com/chromium/src/+/master/docs/memory-infra">Help</a></div> + <div id="contents"> + <div id="info_text">No memory memory dumps selected</div> + <tr-ui-b-table id="table"></tr-ui-b-table> + </div> + </template> +</dom-module> +<script> +'use strict'; + +tr.exportTo('tr.ui.analysis', function() { + const MemoryColumnColorScheme = tr.b.MemoryColumnColorScheme; + const Scalar = tr.b.Scalar; + const sizeInBytes_smallerIsBetter = + tr.b.Unit.byName.sizeInBytes_smallerIsBetter; + + const PLATFORM_SPECIFIC_TOTAL_NAME_SUFFIX = '_bytes'; + + const DISPLAYED_SIZE_NUMERIC_NAME = + tr.model.MemoryAllocatorDump.DISPLAYED_SIZE_NUMERIC_NAME; + const SOME_TIMESTAMPS_INFO_QUANTIFIER = + tr.ui.analysis.MemoryColumn.SOME_TIMESTAMPS_INFO_QUANTIFIER; + + // Unicode symbols used for memory cell info icons and messages. + const RIGHTWARDS_ARROW_WITH_HOOK = String.fromCharCode(0x21AA); + const RIGHTWARDS_ARROW_FROM_BAR = String.fromCharCode(0x21A6); + const GREATER_THAN_OR_EQUAL_TO = String.fromCharCode(0x2265); + const UNMARRIED_PARTNERSHIP_SYMBOL = String.fromCharCode(0x26AF); + const TRIGRAM_FOR_HEAVEN = String.fromCharCode(0x2630); + + function lazyMap(list, fn, opt_this) { + opt_this = opt_this || this; + let result = undefined; + list.forEach(function(item, index) { + const value = fn.call(opt_this, item, index); + if (value === undefined) return; + if (result === undefined) { + result = new Array(list.length); + } + result[index] = value; + }); + return result; + } + + /** @constructor */ + function ProcessNameColumn() { + tr.ui.analysis.TitleColumn.call(this, 'Process'); + } + + ProcessNameColumn.prototype = { + __proto__: tr.ui.analysis.TitleColumn.prototype, + + formatTitle(row) { + if (row.contexts === undefined) { + return row.title; // Total row. + } + const titleEl = document.createElement('tr-ui-b-color-legend'); + titleEl.label = row.title; + return titleEl; + } + }; + + /** @constructor */ + function UsedMemoryColumn(name, cellPath, aggregationMode) { + tr.ui.analysis.NumericMemoryColumn.call( + this, name, cellPath, aggregationMode); + } + + UsedMemoryColumn.COLOR = + MemoryColumnColorScheme.getColor('used_memory_column').toString(); + UsedMemoryColumn.OLDER_COLOR = + MemoryColumnColorScheme.getColor('older_used_memory_column').toString(); + + UsedMemoryColumn.prototype = { + __proto__: tr.ui.analysis.NumericMemoryColumn.prototype, + + get title() { + return tr.ui.b.createSpan({ + textContent: this.name, + color: UsedMemoryColumn.COLOR + }); + }, + + getFormattingContext(unit) { + return { unitPrefix: tr.b.UnitPrefixScale.BINARY.MEBI }; + }, + + color(numerics, processMemoryDumps) { + return UsedMemoryColumn.COLOR; + }, + + getChildPaneBuilder(processMemoryDumps) { + if (processMemoryDumps === undefined) return undefined; + + const vmRegions = lazyMap(processMemoryDumps, function(pmd) { + if (pmd === undefined) return undefined; + return pmd.mostRecentVmRegions; + }); + if (vmRegions === undefined) return undefined; + + return function() { + const pane = document.createElement( + 'tr-ui-a-memory-dump-vm-regions-details-pane'); + pane.vmRegions = vmRegions; + pane.aggregationMode = this.aggregationMode; + return pane; + }.bind(this); + } + }; + + /** @constructor */ + function PeakMemoryColumn(name, cellPath, aggregationMode) { + UsedMemoryColumn.call(this, name, cellPath, aggregationMode); + } + + PeakMemoryColumn.prototype = { + __proto__: UsedMemoryColumn.prototype, + + addInfos(numerics, processMemoryDumps, infos) { + if (processMemoryDumps === undefined) return; // Total row. + + let resettableValueCount = 0; + let nonResettableValueCount = 0; + for (let i = 0; i < numerics.length; i++) { + if (numerics[i] === undefined) continue; + if (processMemoryDumps[i].arePeakResidentBytesResettable) { + resettableValueCount++; + } else { + nonResettableValueCount++; + } + } + + if (resettableValueCount > 0 && nonResettableValueCount > 0) { + infos.push(tr.ui.analysis.createWarningInfo('Both resettable and ' + + 'non-resettable peak RSS values were provided by the process')); + } else if (resettableValueCount > 0) { + infos.push({ + icon: RIGHTWARDS_ARROW_WITH_HOOK, + message: 'Peak RSS since previous memory dump.' + }); + } else { + infos.push({ + icon: RIGHTWARDS_ARROW_FROM_BAR, + message: 'Peak RSS since process startup. Finer grained ' + + 'peaks require a Linux kernel version ' + + GREATER_THAN_OR_EQUAL_TO + ' 4.0.' + }); + } + } + }; + + /** @constructor */ + function ByteStatColumn(name, cellPath, aggregationMode) { + UsedMemoryColumn.call(this, name, cellPath, aggregationMode); + } + + ByteStatColumn.prototype = { + __proto__: UsedMemoryColumn.prototype, + + color(numerics, processMemoryDumps) { + if (processMemoryDumps === undefined) { + return UsedMemoryColumn.COLOR; // Total row. + } + + const allOlderValues = processMemoryDumps.every( + function(processMemoryDump) { + if (processMemoryDump === undefined) return true; + return !processMemoryDump.hasOwnVmRegions; + }); + + // Show the cell in lighter blue if all values were older (i.e. none of + // the defined process memory dumps had own VM regions). + if (allOlderValues) { + return UsedMemoryColumn.OLDER_COLOR; + } + return UsedMemoryColumn.COLOR; + }, + + addInfos(numerics, processMemoryDumps, infos) { + if (processMemoryDumps === undefined) return; // Total row. + + let olderValueCount = 0; + for (let i = 0; i < numerics.length; i++) { + const processMemoryDump = processMemoryDumps[i]; + if (processMemoryDump !== undefined && + !processMemoryDump.hasOwnVmRegions) { + olderValueCount++; + } + } + + if (olderValueCount === 0) { + return; // There are no older values. + } + + const infoQuantifier = olderValueCount < numerics.length ? + ' ' + SOME_TIMESTAMPS_INFO_QUANTIFIER : /* some values are older */ + ''; /* all values are older */ + + // Emit an info if there was at least one older value (i.e. at least one + // defined process memory dump did not have own VM regions). + infos.push({ + message: 'Older value' + infoQuantifier + + ' (only heavy (purple) memory dumps contain memory maps).', + icon: UNMARRIED_PARTNERSHIP_SYMBOL + }); + } + }; + + // Rules for constructing and sorting used memory columns. + UsedMemoryColumn.RULES = [ + { + condition: 'Total resident', + importance: 10, + columnConstructor: UsedMemoryColumn + }, + { + condition: 'Peak total resident', + importance: 9, + columnConstructor: PeakMemoryColumn + }, + { + condition: 'PSS', + importance: 8, + columnConstructor: ByteStatColumn + }, + { + condition: 'Private dirty', + importance: 7, + columnConstructor: ByteStatColumn + }, + { + condition: 'Swapped', + importance: 6, + columnConstructor: ByteStatColumn + }, + { + // All other columns. + importance: 0, + columnConstructor: UsedMemoryColumn + } + ]; + + // Map from ProcessMemoryDump totals fields to column names. + UsedMemoryColumn.TOTALS_MAP = { + 'residentBytes': 'Total resident', + 'peakResidentBytes': 'Peak total resident', + 'privateFootprintBytes': 'Private footprint', + }; + + // Map from ProcessMemoryDump platform-specific totals fields to column names. + UsedMemoryColumn.PLATFORM_SPECIFIC_TOTALS_MAP = { + 'vm': 'Total virtual', + 'swp': 'Swapped', + 'pc': 'Private clean', + 'pd': 'Private dirty', + 'sc': 'Shared clean', + 'sd': 'Shared dirty', + 'gpu_egl': 'GPU EGL', + 'gpu_egl_pss': 'GPU EGL PSS', + 'gpu_gl': 'GPU GL', + 'gpu_gl_pss': 'GPU GL PSS', + 'gpu_etc': 'GPU Other', + 'gpu_etc_pss': 'GPU Other PSS', + }; + + // Map from VMRegionByteStats field names to column names. + UsedMemoryColumn.BYTE_STAT_MAP = { + 'proportionalResident': 'PSS', + 'privateDirtyResident': 'Private dirty', + 'swapped': 'Swapped' + }; + + /** @constructor */ + function AllocatorColumn(name, cellPath, aggregationMode) { + tr.ui.analysis.NumericMemoryColumn.call( + this, name, cellPath, aggregationMode); + } + + AllocatorColumn.prototype = { + __proto__: tr.ui.analysis.NumericMemoryColumn.prototype, + + get title() { + const titleEl = document.createElement('tr-ui-b-color-legend'); + titleEl.label = this.name; + return titleEl; + }, + + getFormattingContext(unit) { + return { unitPrefix: tr.b.UnitPrefixScale.BINARY.MEBI }; + }, + + addInfos(numerics, processMemoryDumps, infos) { + if (processMemoryDumps === undefined) return; + + let heapDumpCount = 0; + let missingSizeCount = 0; + + for (let i = 0; i < processMemoryDumps.length; i++) { + const processMemoryDump = processMemoryDumps[i]; + if (processMemoryDump === undefined) continue; + + const heapDumps = processMemoryDump.heapDumps; + if (heapDumps !== undefined && heapDumps[this.name] !== undefined) { + heapDumpCount++; + } + const allocatorDump = + processMemoryDump.getMemoryAllocatorDumpByFullName(this.name); + + if (allocatorDump !== undefined && + allocatorDump.numerics[DISPLAYED_SIZE_NUMERIC_NAME] === undefined) { + missingSizeCount++; + } + } + + // Emit a heap dump info if at least one of the process memory dumps has + // a heap dump associated with this allocator. + if (heapDumpCount > 0) { + const infoQuantifier = heapDumpCount < numerics.length ? + ' ' + SOME_TIMESTAMPS_INFO_QUANTIFIER : ''; + infos.push({ + message: 'Heap dump provided' + infoQuantifier + '.', + icon: TRIGRAM_FOR_HEAVEN + }); + } + + // Emit a warning if this allocator did not provide size in at least one + // of the process memory dumps. + if (missingSizeCount > 0) { + const infoQuantifier = missingSizeCount < numerics.length ? + ' ' + SOME_TIMESTAMPS_INFO_QUANTIFIER : ''; + infos.push(tr.ui.analysis.createWarningInfo( + 'Size was not provided' + infoQuantifier + '.')); + } + }, + + getChildPaneBuilder(processMemoryDumps) { + if (processMemoryDumps === undefined) return undefined; + + const memoryAllocatorDumps = lazyMap(processMemoryDumps, function(pmd) { + if (pmd === undefined) return undefined; + return pmd.getMemoryAllocatorDumpByFullName(this.name); + }, this); + if (memoryAllocatorDumps === undefined) return undefined; + + const heapDumps = lazyMap(processMemoryDumps, function(pmd) { + if (pmd === undefined || pmd.heapDumps === undefined) return undefined; + return pmd.heapDumps[this.name]; + }, this); + + return function() { + const pane = document.createElement( + 'tr-ui-a-memory-dump-allocator-details-pane'); + pane.memoryAllocatorDumps = memoryAllocatorDumps; + pane.heapDumps = heapDumps; + pane.aggregationMode = this.aggregationMode; + return pane; + }.bind(this); + } + }; + + /** @constructor */ + function TracingColumn(name, cellPath, aggregationMode) { + AllocatorColumn.call(this, name, cellPath, aggregationMode); + } + + TracingColumn.COLOR = + MemoryColumnColorScheme.getColor('tracing_memory_column').toString(); + + TracingColumn.prototype = { + __proto__: AllocatorColumn.prototype, + + get title() { + return tr.ui.b.createSpan({ + textContent: this.name, + color: TracingColumn.COLOR + }); + }, + + color(numerics, processMemoryDumps) { + return TracingColumn.COLOR; + } + }; + + // Rules for constructing and sorting allocator columns. + AllocatorColumn.RULES = [ + { + condition: 'tracing', + importance: 0, + columnConstructor: TracingColumn + }, + { + // All other columns. + importance: 1, + columnConstructor: AllocatorColumn + } + ]; + + Polymer({ + is: 'tr-ui-a-memory-dump-overview-pane', + behaviors: [tr.ui.analysis.StackedPane], + + created() { + this.processMemoryDumps_ = undefined; + this.aggregationMode_ = undefined; + }, + + ready() { + this.$.table.selectionMode = tr.ui.b.TableFormat.SelectionMode.CELL; + this.$.table.addEventListener('selection-changed', + function(tableEvent) { + tableEvent.stopPropagation(); + this.changeChildPane_(); + }.bind(this)); + }, + + /** + * Sets the process memory dumps and schedules rebuilding the pane. + * + * The provided value should be a chronological list of dictionaries + * mapping process IDs to process memory dumps. Example: + * + * [ + * { + * // PMDs at timestamp 1. + * 42: tr.model.ProcessMemoryDump {} + * }, + * { + * // PMDs at timestamp 2. + * 42: tr.model.ProcessMemoryDump {}, + * 89: tr.model.ProcessMemoryDump {} + * } + * ] + */ + set processMemoryDumps(processMemoryDumps) { + this.processMemoryDumps_ = processMemoryDumps; + this.scheduleRebuild_(); + }, + + get processMemoryDumps() { + return this.processMemoryDumps_; + }, + + set aggregationMode(aggregationMode) { + this.aggregationMode_ = aggregationMode; + this.scheduleRebuild_(); + }, + + get aggregationMode() { + return this.aggregationMode_; + }, + + get selectedMemoryCell() { + if (this.processMemoryDumps_ === undefined || + this.processMemoryDumps_.length === 0) { + return undefined; + } + + const selectedTableRow = this.$.table.selectedTableRow; + if (!selectedTableRow) return undefined; + + const selectedColumnIndex = this.$.table.selectedColumnIndex; + if (selectedColumnIndex === undefined) return undefined; + + const selectedColumn = this.$.table.tableColumns[selectedColumnIndex]; + const selectedMemoryCell = selectedColumn.cell(selectedTableRow); + return selectedMemoryCell; + }, + + changeChildPane_() { + this.storeSelection_(); + this.childPaneBuilder = this.determineChildPaneBuilderFromSelection_(); + }, + + determineChildPaneBuilderFromSelection_() { + if (this.processMemoryDumps_ === undefined || + this.processMemoryDumps_.length === 0) { + return undefined; + } + + const selectedTableRow = this.$.table.selectedTableRow; + if (!selectedTableRow) return undefined; + + const selectedColumnIndex = this.$.table.selectedColumnIndex; + if (selectedColumnIndex === undefined) return undefined; + const selectedColumn = this.$.table.tableColumns[selectedColumnIndex]; + + return selectedColumn.getChildPaneBuilder(selectedTableRow.contexts); + }, + + onRebuild_() { + if (this.processMemoryDumps_ === undefined || + this.processMemoryDumps_.length === 0) { + // Show the info text (hide the table). + this.$.info_text.style.display = 'block'; + this.$.table.style.display = 'none'; + + this.$.table.clear(); + this.$.table.rebuild(); + return; + } + + // Show the table (hide the info text). + this.$.info_text.style.display = 'none'; + this.$.table.style.display = 'block'; + + const rows = this.createRows_(); + const columns = this.createColumns_(rows); + const footerRows = this.createFooterRows_(rows, columns); + + this.$.table.tableRows = rows; + this.$.table.footerRows = footerRows; + this.$.table.tableColumns = columns; + this.$.table.rebuild(); + + this.restoreSelection_(); + }, + + createRows_() { + // Timestamp (list index) -> Process ID (dict key) -> PMD. + const timeToPidToProcessMemoryDump = this.processMemoryDumps_; + + // Process ID (dict key) -> Timestamp (list index) -> PMD or undefined. + const pidToTimeToProcessMemoryDump = tr.b.invertArrayOfDicts( + timeToPidToProcessMemoryDump); + + // Process (list index) -> Component (dict key) -> Cell. + const rows = []; + for (const [pid, timeToDump] of + Object.entries(pidToTimeToProcessMemoryDump)) { + // Get the process associated with the dumps. We can use any defined + // process memory dump in timeToDump since they all have the same + // pid. + const process = timeToDump.find(x => x).process; + + // Used memory (total resident, PSS, ...). + const usedMemoryCells = tr.ui.analysis.createCells(timeToDump, + function(dump) { + const sizes = {}; + + const totals = dump.totals; + if (totals !== undefined) { + // Common totals. + for (const [totalName, cellName] of + Object.entries(UsedMemoryColumn.TOTALS_MAP)) { + const total = totals[totalName]; + if (total === undefined) continue; + sizes[cellName] = new Scalar( + sizeInBytes_smallerIsBetter, total); + } + + // Platform-specific totals (e.g. private resident on Mac). + const platformSpecific = totals.platformSpecific; + if (platformSpecific !== undefined) { + for (const [name, size] of Object.entries(platformSpecific)) { + let newName = name; + if (UsedMemoryColumn.PLATFORM_SPECIFIC_TOTALS_MAP[name] === + undefined) { + // Change raw OS-specific total name to a friendly + // column title (e.g. 'private_bytes' -> 'Private'). + if (name.endsWith(PLATFORM_SPECIFIC_TOTAL_NAME_SUFFIX)) { + newName = name.substring(0, name.length - + PLATFORM_SPECIFIC_TOTAL_NAME_SUFFIX.length); + } + newName = newName.replace('_', ' ').trim(); + newName = + newName.charAt(0).toUpperCase() + newName.slice(1); + } else { + newName = + UsedMemoryColumn.PLATFORM_SPECIFIC_TOTALS_MAP[name]; + } + sizes[newName] = new Scalar( + sizeInBytes_smallerIsBetter, size); + } + } + } + + // VM regions byte stats. + const vmRegions = dump.mostRecentVmRegions; + if (vmRegions !== undefined) { + for (const [byteStatName, cellName] of + Object.entries(UsedMemoryColumn.BYTE_STAT_MAP)) { + const byteStat = vmRegions.byteStats[byteStatName]; + if (byteStat === undefined) continue; + sizes[cellName] = new Scalar( + sizeInBytes_smallerIsBetter, byteStat); + } + } + + return sizes; + }); + + // Allocator memory (v8, oilpan, ...). + const allocatorCells = tr.ui.analysis.createCells(timeToDump, + function(dump) { + const memoryAllocatorDumps = dump.memoryAllocatorDumps; + if (memoryAllocatorDumps === undefined) return undefined; + + const sizes = {}; + memoryAllocatorDumps.forEach(function(allocatorDump) { + let rootDisplayedSizeNumeric = allocatorDump.numerics[ + DISPLAYED_SIZE_NUMERIC_NAME]; + if (rootDisplayedSizeNumeric === undefined) { + rootDisplayedSizeNumeric = + new Scalar(sizeInBytes_smallerIsBetter, 0); + } + sizes[allocatorDump.fullName] = rootDisplayedSizeNumeric; + }); + return sizes; + }); + + rows.push({ + title: process.userFriendlyName, + contexts: timeToDump, + usedMemoryCells, + allocatorCells + }); + } + return rows; + }, + + createFooterRows_(rows, columns) { + // Add a 'Total' row if there are at least two process memory dumps. + if (rows.length <= 1) return []; + + const totalRow = {title: 'Total'}; + tr.ui.analysis.aggregateTableRowCells(totalRow, rows, columns); + + return [totalRow]; + }, + + createColumns_(rows) { + const titleColumn = new ProcessNameColumn(); + titleColumn.width = '200px'; + + const usedMemorySizeColumns = tr.ui.analysis.MemoryColumn.fromRows(rows, { + cellKey: 'usedMemoryCells', + aggregationMode: this.aggregationMode_, + rules: UsedMemoryColumn.RULES + }); + + const allocatorSizeColumns = tr.ui.analysis.MemoryColumn.fromRows(rows, { + cellKey: 'allocatorCells', + aggregationMode: this.aggregationMode_, + rules: AllocatorColumn.RULES + }); + + const sizeColumns = usedMemorySizeColumns.concat(allocatorSizeColumns); + tr.ui.analysis.MemoryColumn.spaceEqually(sizeColumns); + + const columns = [titleColumn].concat(sizeColumns); + return columns; + }, + + storeSelection_() { + let selectedRowTitle; + const selectedRow = this.$.table.selectedTableRow; + if (selectedRow !== undefined) { + selectedRowTitle = selectedRow.title; + } + + let selectedColumnName; + const selectedColumnIndex = this.$.table.selectedColumnIndex; + if (selectedColumnIndex !== undefined) { + const selectedColumn = this.$.table.tableColumns[selectedColumnIndex]; + selectedColumnName = selectedColumn.name; + } + + this.$.state.set( + {rowTitle: selectedRowTitle, columnName: selectedColumnName}); + }, + + restoreSelection_() { + const settings = this.$.state.get(); + if (settings === undefined || settings.rowTitle === undefined || + settings.columnName === undefined) { + return; + } + + const selectedColumnIndex = this.$.table.tableColumns.findIndex( + col => col.name === settings.columnName); + if (selectedColumnIndex === -1) return; + + const selectedRowTitle = settings.rowTitle; + const selectedRow = this.$.table.tableRows.find( + row => row.title === selectedRowTitle); + if (selectedRow === undefined) return; + + this.$.table.selectedTableRow = selectedRow; + this.$.table.selectedColumnIndex = selectedColumnIndex; + } + }); + + return { + // All exports are for testing only. + ProcessNameColumn, + UsedMemoryColumn, + PeakMemoryColumn, + ByteStatColumn, + AllocatorColumn, + TracingColumn, + }; +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_overview_pane_test.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_overview_pane_test.html new file mode 100644 index 00000000000..80127e020a8 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_overview_pane_test.html @@ -0,0 +1,840 @@ +<!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/base/utils.html"> +<link rel="import" href="/tracing/core/test_utils.html"> +<link rel="import" href="/tracing/model/heap_dump.html"> +<link rel="import" href="/tracing/model/memory_allocator_dump.html"> +<link rel="import" href="/tracing/model/memory_dump_test_utils.html"> +<link rel="import" href="/tracing/ui/analysis/memory_dump_overview_pane.html"> +<link rel="import" + href="/tracing/ui/analysis/memory_dump_sub_view_test_utils.html"> +<link rel="import" href="/tracing/ui/analysis/memory_dump_sub_view_util.html"> +<link rel="import" href="/tracing/ui/brushing_state_controller.html"> + +<script> +'use strict'; + +tr.b.unittest.testSuite(function() { + const Scalar = tr.b.Scalar; + const sizeInBytes_smallerIsBetter = + tr.b.Unit.byName.sizeInBytes_smallerIsBetter; + const MemoryAllocatorDump = tr.model.MemoryAllocatorDump; + const HeapDump = tr.model.HeapDump; + const AggregationMode = tr.ui.analysis.MemoryColumn.AggregationMode; + const checkSizeNumericFields = tr.ui.analysis.checkSizeNumericFields; + const checkColor = tr.ui.analysis.checkColor; + const checkColumns = tr.ui.analysis.checkColumns; + const checkColumnInfosAndColor = tr.ui.analysis.checkColumnInfosAndColor; + const convertToProcessMemoryDumps = + tr.ui.analysis.convertToProcessMemoryDumps; + const extractProcessMemoryDumps = tr.ui.analysis.extractProcessMemoryDumps; + const extractVmRegions = tr.ui.analysis.extractVmRegions; + const extractMemoryAllocatorDumps = + tr.ui.analysis.extractMemoryAllocatorDumps; + const isElementDisplayed = tr.ui.analysis.isElementDisplayed; + const addProcessMemoryDump = + tr.model.MemoryDumpTestUtils.addProcessMemoryDump; + const addGlobalMemoryDump = tr.model.MemoryDumpTestUtils.addGlobalMemoryDump; + const ProcessNameColumn = tr.ui.analysis.ProcessNameColumn; + const UsedMemoryColumn = tr.ui.analysis.UsedMemoryColumn; + const PeakMemoryColumn = tr.ui.analysis.PeakMemoryColumn; + const ByteStatColumn = tr.ui.analysis.ByteStatColumn; + const AllocatorColumn = tr.ui.analysis.AllocatorColumn; + const TracingColumn = tr.ui.analysis.TracingColumn; + + function spanMatcher(expectedTitle) { + return function(actualTitle) { + assert.instanceOf(actualTitle, HTMLElement); + assert.strictEqual(actualTitle.tagName, 'SPAN'); + assert.strictEqual(Polymer.dom(actualTitle).textContent, expectedTitle); + }; + } + + function colorLegendMatcher(expectedTitle) { + return function(actualTitle) { + assert.instanceOf(actualTitle, HTMLElement); + assert.strictEqual(actualTitle.tagName, 'TR-UI-B-COLOR-LEGEND'); + assert.strictEqual(actualTitle.label, expectedTitle); + }; + } + + const EXPECTED_COLUMNS = [ + { title: 'Process', type: ProcessNameColumn, noAggregation: true }, + { title: spanMatcher('Total resident'), type: UsedMemoryColumn }, + { title: spanMatcher('Peak total resident'), type: PeakMemoryColumn }, + { title: spanMatcher('PSS'), type: ByteStatColumn }, + { title: spanMatcher('Private dirty'), type: ByteStatColumn }, + { title: spanMatcher('Swapped'), type: ByteStatColumn }, + { title: spanMatcher('Private'), type: UsedMemoryColumn }, + { title: spanMatcher('Private footprint'), type: UsedMemoryColumn }, + { title: colorLegendMatcher('blink'), type: AllocatorColumn }, + { title: colorLegendMatcher('gpu'), type: AllocatorColumn }, + { title: colorLegendMatcher('malloc'), type: AllocatorColumn }, + { title: colorLegendMatcher('oilpan'), type: AllocatorColumn }, + { title: colorLegendMatcher('v8'), type: AllocatorColumn }, + { title: spanMatcher('tracing'), type: TracingColumn } + ]; + + function checkRow(columns, row, expectedTitle, expectedSizes, + expectedContexts) { + // Check title. + const formattedTitle = columns[0].formatTitle(row); + if (typeof expectedTitle === 'function') { + expectedTitle(formattedTitle); + } else { + assert.strictEqual(formattedTitle, expectedTitle); + } + + // Check all sizes. The first assert below is a test sanity check. + assert.lengthOf(expectedSizes, columns.length - 1 /* all except title */); + for (let i = 0; i < expectedSizes.length; i++) { + checkSizeNumericFields(row, columns[i + 1], expectedSizes[i]); + } + + // There should be no row nesting on the overview pane. + assert.isUndefined(row.subRows); + + if (expectedContexts) { + assert.deepEqual(Array.from(row.contexts), expectedContexts); + } else { + assert.isUndefined(row.contexts); + } + } + + function checkRows(columns, actualRows, expectedRows) { + if (expectedRows === undefined) { + assert.isUndefined(actualRows); + return; + } + assert.lengthOf(actualRows, expectedRows.length); + for (let i = 0; i < expectedRows.length; i++) { + const actualRow = actualRows[i]; + const expectedRow = expectedRows[i]; + checkRow(columns, actualRow, expectedRow.title, expectedRow.sizes, + expectedRow.contexts); + } + } + + function checkSpanWithColor(span, expectedText, expectedColor) { + assert.strictEqual(span.tagName, 'SPAN'); + assert.strictEqual(Polymer.dom(span).textContent, expectedText); + checkColor(span.style.color, expectedColor); + } + + function checkColorLegend(legend, expectedLabel) { + assert.strictEqual(legend.tagName, 'TR-UI-B-COLOR-LEGEND'); + assert.strictEqual(legend.label, expectedLabel); + } + + function createAndCheckMemoryDumpOverviewPane( + test, processMemoryDumps, expectedRows, expectedFooterRows, + aggregationMode) { + const viewEl = + tr.ui.analysis.createTestPane('tr-ui-a-memory-dump-overview-pane'); + viewEl.processMemoryDumps = processMemoryDumps; + viewEl.aggregationMode = aggregationMode; + viewEl.rebuild(); + test.addHTMLOutput(viewEl); + + // Check that the table is shown. + assert.isTrue(isElementDisplayed(viewEl.$.table)); + assert.isFalse(isElementDisplayed(viewEl.$.info_text)); + + assert.isUndefined(viewEl.createChildPane()); + + const table = viewEl.$.table; + const columns = table.tableColumns; + checkColumns(columns, EXPECTED_COLUMNS, aggregationMode); + const rows = table.tableRows; + + checkRows(columns, table.tableRows, expectedRows); + checkRows(columns, table.footerRows, expectedFooterRows); + } + + const FIELD = 1 << 0; + const DUMP = 1 << 1; + + function checkOverviewColumnInfosAndColor(column, fieldAndDumpMask, + dumpCreatedCallback, expectedInfos, expectedColorReservedName) { + const fields = fieldAndDumpMask.map(function(mask, index) { + return mask & FIELD ? + new Scalar(sizeInBytes_smallerIsBetter, 1024 + 32 * index) : + undefined; + }); + + let contexts; + if (dumpCreatedCallback === undefined) { + contexts = undefined; + } else { + tr.c.TestUtils.newModel(function(model) { + const process = model.getOrCreateProcess(1); + fieldAndDumpMask.forEach(function(mask, i) { + const timestamp = 10 + i; + const gmd = addGlobalMemoryDump(model, {ts: timestamp}); + if (mask & DUMP) { + const pmd = addProcessMemoryDump(gmd, process, {ts: timestamp}); + dumpCreatedCallback(pmd, mask); + } + }); + contexts = model.globalMemoryDumps.map(function(gmd) { + return gmd.processMemoryDumps[1]; + }); + }); + } + + checkColumnInfosAndColor( + column, fields, contexts, expectedInfos, expectedColorReservedName); + } + + test('colorsAreDefined', function() { + // We use these constants in the code and the tests so here we guard + // against them being undefined and causing all the tests to still + // pass while the we end up with no colors. + assert.isDefined(UsedMemoryColumn.COLOR); + assert.isDefined(UsedMemoryColumn.OLDER_COLOR); + assert.isDefined(TracingColumn.COLOR); + }); + + test('instantiate_empty', function() { + tr.ui.analysis.createAndCheckEmptyPanes(this, + 'tr-ui-a-memory-dump-overview-pane', 'processMemoryDumps', + function(viewEl) { + // Check that the info text is shown. + assert.isTrue(isElementDisplayed(viewEl.$.info_text)); + assert.isFalse(isElementDisplayed(viewEl.$.table)); + }); + }); + + test('instantiate_singleGlobalMemoryDump', function() { + const processMemoryDumps = convertToProcessMemoryDumps( + [tr.ui.analysis.createSingleTestGlobalMemoryDump()]); + createAndCheckMemoryDumpOverviewPane(this, + processMemoryDumps, + [ // Table rows. + { + title: colorLegendMatcher('Process 1'), + sizes: [[29884416], undefined, [9437184], [5767168], undefined, + undefined, undefined, undefined, undefined, [7340032], undefined, + undefined, [2097152]], + contexts: extractProcessMemoryDumps(processMemoryDumps, 1) + }, + { + title: colorLegendMatcher('Process 2'), + sizes: [[17825792], [39845888], [18350080], [0], [32], [8912896], + [15728640], [7340032], [0], [1048576], [1], [5242880], + [1572864]], + contexts: extractProcessMemoryDumps(processMemoryDumps, 2) + }, + { + title: colorLegendMatcher('Process 4'), + sizes: [undefined, [17825792], undefined, undefined, undefined, + undefined, undefined, undefined, undefined, undefined, + undefined, undefined, undefined], + contexts: extractProcessMemoryDumps(processMemoryDumps, 4) + } + ], + [ // Footer rows. + { + title: 'Total', + sizes: [[47710208], [57671680], [27787264], [5767168], [32], + [8912896], [15728640], [7340032], [0], [8388608], [1], + [5242880], [3670016]], + contexts: undefined + } + ], + undefined /* no aggregation */); + }); + + test('instantiate_multipleGlobalMemoryDumps', function() { + const processMemoryDumps = convertToProcessMemoryDumps( + tr.ui.analysis.createMultipleTestGlobalMemoryDumps()); + createAndCheckMemoryDumpOverviewPane(this, + processMemoryDumps, + [ // Table rows. + { + title: colorLegendMatcher('Process 1'), + sizes: [[31457280, 29884416, undefined], undefined, + [10485760, 9437184, undefined], [8388608, 5767168, undefined], + undefined, undefined, undefined, undefined, undefined, + [undefined, 7340032, undefined], undefined, undefined, + [undefined, 2097152, undefined]], + contexts: extractProcessMemoryDumps(processMemoryDumps, 1) + }, + { + title: colorLegendMatcher('Process 2'), + sizes: [[19398656, 17825792, 15728640], + [40370176, 39845888, 40894464], [18350080, 18350080, 18350080], + [0, 0, -2621440], [32, 32, 64], [10485760, 8912896, 7340032], + [15728640, 15728640, 15728640], [undefined, 7340032, 6291456], + [undefined, 0, 1048576], [2097152, 1048576, 786432], + [undefined, 1, undefined], [5242880, 5242880, 5767168], + [1048576, 1572864, 2097152]], + contexts: extractProcessMemoryDumps(processMemoryDumps, 2) + }, + { + title: colorLegendMatcher('Process 3'), + sizes: [undefined, undefined, undefined, undefined, undefined, + undefined, undefined, undefined, undefined, undefined, + [2147483648, undefined, 1073741824], + [1073741824, undefined, 2147483648], undefined], + contexts: extractProcessMemoryDumps(processMemoryDumps, 3) + }, + { + title: colorLegendMatcher('Process 4'), + sizes: [undefined, [undefined, 17825792, 17825792], undefined, + undefined, undefined, undefined, undefined, undefined, + undefined, undefined, undefined, undefined, undefined], + contexts: extractProcessMemoryDumps(processMemoryDumps, 4) + } + ], + [ // Footer rows. + { + title: 'Total', + sizes: [[50855936, 47710208, 15728640], + [40370176, 57671680, 58720256], [28835840, 27787264, 18350080], + [8388608, 5767168, -2621440], [32, 32, 64], + [10485760, 8912896, 7340032], [15728640, 15728640, 15728640], + [undefined, 7340032, 6291456], [undefined, 0, 1048576], + [2097152, 8388608, 786432], [2147483648, 1, 1073741824], + [1078984704, 5242880, 2153250816], + [1048576, 3670016, 2097152]], + contexts: undefined + } + ], + AggregationMode.DIFF); + }); + + test('instantiate_singleProcessMemoryDump', function() { + const processMemoryDumps = convertToProcessMemoryDumps( + [tr.ui.analysis.createSingleTestProcessMemoryDump()]); + createAndCheckMemoryDumpOverviewPane(this, + processMemoryDumps, + [ // Table rows. + { + title: colorLegendMatcher('Process 2'), + sizes: [[17825792], [39845888], [18350080], [0], [32], [8912896], + [15728640], [7340032], [0], [1048576], [1], [5242880], + [1572864]], + contexts: extractProcessMemoryDumps(processMemoryDumps, 2) + } + ], + [] /* footer rows */, + undefined /* no aggregation */); + }); + + test('instantiate_multipleProcessMemoryDumps', function() { + const processMemoryDumps = convertToProcessMemoryDumps( + tr.ui.analysis.createMultipleTestProcessMemoryDumps()); + createAndCheckMemoryDumpOverviewPane(this, + processMemoryDumps, + [ // Table rows. + { + title: colorLegendMatcher('Process 2'), + sizes: [[19398656, 17825792, 15728640], + [40370176, 39845888, 40894464], [18350080, 18350080, 18350080], + [0, 0, -2621440], [32, 32, 64], [10485760, 8912896, 7340032], + [15728640, 15728640, 15728640], [undefined, 7340032, 6291456], + [undefined, 0, 1048576], [2097152, 1048576, 786432], + [undefined, 1, undefined], [5242880, 5242880, 5767168], + [1048576, 1572864, 2097152]], + contexts: extractProcessMemoryDumps(processMemoryDumps, 2) + } + ], + [] /* footer rows */, + AggregationMode.MAX); + }); + + test('selection', function() { + const processMemoryDumps = convertToProcessMemoryDumps( + tr.ui.analysis.createMultipleTestGlobalMemoryDumps()); + + const viewEl = + tr.ui.analysis.createTestPane('tr-ui-a-memory-dump-overview-pane'); + viewEl.processMemoryDumps = processMemoryDumps; + viewEl.aggregationMode = AggregationMode.DIFF; + viewEl.rebuild(); + this.addHTMLOutput(viewEl); + + const table = viewEl.$.table; + + // Simulate clicking on the 'malloc' cell of the second process. + table.selectedTableRow = table.tableRows[1]; + table.selectedColumnIndex = 10; + assert.lengthOf(viewEl.requestedChildPanes, 2); + let lastChildPane = viewEl.requestedChildPanes[1]; + assert.strictEqual( + lastChildPane.tagName, 'TR-UI-A-MEMORY-DUMP-ALLOCATOR-DETAILS-PANE'); + assert.strictEqual(lastChildPane.aggregationMode, AggregationMode.DIFF); + assert.deepEqual(lastChildPane.memoryAllocatorDumps, + extractMemoryAllocatorDumps(processMemoryDumps, 2, 'malloc')); + + // Simulate clicking on the 'Oilpan' cell of the second process. + table.selectedColumnIndex = 10; + assert.lengthOf(viewEl.requestedChildPanes, 3); + lastChildPane = viewEl.requestedChildPanes[2]; + assert.isUndefined(viewEl.lastChildPane); + }); + + test('memory', function() { + const processMemoryDumps = convertToProcessMemoryDumps( + tr.ui.analysis.createMultipleTestGlobalMemoryDumps()); + const containerEl = document.createElement('div'); + containerEl.brushingStateController = + new tr.c.BrushingStateController(undefined); + + function simulateView(pids, aggregationMode, + expectedSelectedCellFieldValues, expectedSelectedRowTitle, + expectedSelectedColumnIndex, callback) { + const viewEl = + tr.ui.analysis.createTestPane('tr-ui-a-memory-dump-overview-pane'); + const table = viewEl.$.table; + Polymer.dom(containerEl).textContent = ''; + Polymer.dom(containerEl).appendChild(viewEl); + + const displayedProcessMemoryDumps = processMemoryDumps.map( + function(memoryDumps) { + const result = {}; + for (const [pid, pmd] of Object.entries(memoryDumps)) { + if (pids.includes(pmd.process.pid)) result[pid] = pmd; + } + return result; + }); + viewEl.processMemoryDumps = displayedProcessMemoryDumps; + viewEl.aggregationMode = aggregationMode; + viewEl.rebuild(); + + if (expectedSelectedCellFieldValues === undefined) { + assert.isUndefined(viewEl.childPaneBuilder); + } else { + checkSizeNumericFields(table.selectedTableRow, + table.tableColumns[table.selectedColumnIndex], + expectedSelectedCellFieldValues); + } + + assert.strictEqual( + table.selectedColumnIndex, expectedSelectedColumnIndex); + if (expectedSelectedRowTitle === undefined) { + assert.isUndefined(table.selectedTableRow); + } else { + assert.strictEqual( + table.selectedTableRow.title, expectedSelectedRowTitle); + } + + callback(viewEl, viewEl.$.table); + } + + simulateView( + [1, 2, 3, 4], // All processes. + AggregationMode.DIFF, + undefined, undefined, undefined, // No cell should be selected. + function(view, table) { + assert.isUndefined(view.createChildPane()); + + // Select the 'PSS' column of the second process. + table.selectedTableRow = table.tableRows[1]; + table.selectedColumnIndex = 3; + }); + + simulateView( + [2, 3], + AggregationMode.MAX, + [18350080, 18350080, 18350080], 'Process 2', 3, /* PSS */ + function(view, table) { + const childPane = view.createChildPane(); + assert.strictEqual( + childPane.tagName, 'TR-UI-A-MEMORY-DUMP-VM-REGIONS-DETAILS-PANE'); + assert.deepEqual(Array.from(childPane.vmRegions), + extractVmRegions(processMemoryDumps, 2)); + assert.strictEqual(childPane.aggregationMode, AggregationMode.MAX); + }); + + simulateView( + [3], + undefined, /* No aggregation */ + undefined, undefined, undefined, // No cell selected. + function(view, table) { + assert.isUndefined(view.createChildPane()); + }); + + simulateView( + [1, 2, 3, 4], + AggregationMode.DIFF, + [18350080, 18350080, 18350080], 'Process 2', 3, /* PSS */ + function(view, table) { + const childPane = view.createChildPane(); + assert.strictEqual( + childPane.tagName, 'TR-UI-A-MEMORY-DUMP-VM-REGIONS-DETAILS-PANE'); + assert.deepEqual(Array.from(childPane.vmRegions), + extractVmRegions(processMemoryDumps, 2)); + assert.strictEqual(childPane.aggregationMode, AggregationMode.DIFF); + + // Select the 'v8' column of the first process (empty cell). + table.selectedTableRow = table.tableRows[0]; + table.selectedColumnIndex = 11; + }); + + simulateView( + [1], + undefined, /* No aggregation */ + undefined, undefined, undefined, // No cell should selected. + function(view, table) { + assert.isUndefined(view.createChildPane()); + + // Select 'Total resident' column of the first process. + table.selectedTableRow = table.tableRows[0]; + table.selectedColumnIndex = 1; + }); + + simulateView( + [1, 2, 3, 4], + AggregationMode.MAX, + [31457280, 29884416, undefined], 'Process 1', 1, /* Total resident */ + function(view, table) { + const childPane = view.createChildPane(); + assert.strictEqual( + childPane.tagName, 'TR-UI-A-MEMORY-DUMP-VM-REGIONS-DETAILS-PANE'); + assert.deepEqual(Array.from(childPane.vmRegions), + extractVmRegions(processMemoryDumps, 1)); + assert.strictEqual(childPane.aggregationMode, AggregationMode.MAX); + }); + }); + + test('processNameColumn_formatTitle', function() { + const c = new ProcessNameColumn(); + + // With context (total row). + assert.strictEqual(c.formatTitle({ + title: 'Total', + usedMemoryCells: {} + }), 'Total'); + + // Without context (process row). + const title = c.formatTitle({ + title: 'Process 1', + usedMemoryCells: {}, + contexts: [tr.ui.analysis.createSingleTestProcessMemoryDump()] + }); + checkColorLegend(title, 'Process 1'); + }); + + test('usedMemoryColumn', function() { + const c = new UsedMemoryColumn('Private', 'bytes', (x => x), + AggregationMode.DIFF); + checkSpanWithColor(c.title, 'Private', + UsedMemoryColumn.COLOR /* blue (column title) */); + checkColor(c.color(undefined /* contexts */), + UsedMemoryColumn.COLOR /* blue (column cells) */); + }); + + test('peakMemoryColumn', function() { + const c = new PeakMemoryColumn('Peak', 'bytes', (x => x), + AggregationMode.MAX); + checkSpanWithColor(c.title, 'Peak', + UsedMemoryColumn.COLOR /* blue (column title) */); + checkColor(c.color(undefined) /* contexts */, + UsedMemoryColumn.COLOR /* blue (column cells) */); + + const RESETTABLE_PEAK = 1 << 2; + const NON_RESETTABLE_PEAK = 1 << 3; + function checkPeakColumnInfosAndColor(fieldAndDumpMask, expectedInfos) { + checkOverviewColumnInfosAndColor(c, + fieldAndDumpMask, + function(pmd, mask) { + if (mask & RESETTABLE_PEAK) { + assert.strictEqual( + mask & NON_RESETTABLE_PEAK, 0); // Test sanity check. + pmd.arePeakResidentBytesResettable = true; + } else if (mask & NON_RESETTABLE_PEAK) { + pmd.arePeakResidentBytesResettable = false; + } + }, + expectedInfos, + UsedMemoryColumn.COLOR); + } + + // No context. + checkOverviewColumnInfosAndColor(c, + [FIELD], + undefined /* no context */, + [] /* no infos */, + UsedMemoryColumn.COLOR /* blue color */); + checkOverviewColumnInfosAndColor(c, + [FIELD, FIELD, 0, FIELD], + undefined /* no context */, + [] /* no infos */, + UsedMemoryColumn.COLOR /* blue color */); + + // All resettable. + const EXPECTED_RESETTABLE_INFO = { + icon: '\u21AA', + message: 'Peak RSS since previous memory dump.' + }; + checkPeakColumnInfosAndColor([ + FIELD | DUMP | RESETTABLE_PEAK + ], [EXPECTED_RESETTABLE_INFO]); + checkPeakColumnInfosAndColor([ + FIELD | DUMP | RESETTABLE_PEAK, + DUMP /* ignored because there's no field */, + 0, + FIELD | DUMP | RESETTABLE_PEAK + ], [EXPECTED_RESETTABLE_INFO]); + + // All non-resettable. + const EXPECTED_NON_RESETTABLE_INFO = { + icon: '\u21A6', + message: 'Peak RSS since process startup. Finer grained peaks require ' + + 'a Linux kernel version \u2265 4.0.' + }; + checkPeakColumnInfosAndColor([ + FIELD | DUMP | NON_RESETTABLE_PEAK + ], [EXPECTED_NON_RESETTABLE_INFO]); + checkPeakColumnInfosAndColor([ + 0, + DUMP | RESETTABLE_PEAK /* ignored because there's no field */, + FIELD | DUMP | NON_RESETTABLE_PEAK, + FIELD | DUMP | NON_RESETTABLE_PEAK + ], [EXPECTED_NON_RESETTABLE_INFO]); + + // Combination (warning). + const EXPECTED_COMBINATION_INFO = { + icon: '\u26A0', + message: 'Both resettable and non-resettable peak RSS values were ' + + 'provided by the process', + color: 'red' + }; + checkPeakColumnInfosAndColor([ + FIELD | DUMP | NON_RESETTABLE_PEAK, + 0, + FIELD | DUMP | RESETTABLE_PEAK, + 0 + ], [EXPECTED_COMBINATION_INFO]); + }); + + test('byteStatColumn', function() { + const c = new ByteStatColumn('Stat', 'bytes', (x => x), + AggregationMode.DIFF); + checkSpanWithColor(c.title, 'Stat', + UsedMemoryColumn.COLOR /* blue (column title) */); + + const HAS_OWN_VM_REGIONS = 1 << 2; + function checkByteStatColumnInfosAndColor( + fieldAndDumpMask, expectedInfos, expectedIsOlderColor) { + checkOverviewColumnInfosAndColor(c, + fieldAndDumpMask, + function(pmd, mask) { + if (mask & HAS_OWN_VM_REGIONS) { + pmd.vmRegions = []; + } + }, + expectedInfos, + expectedIsOlderColor ? + UsedMemoryColumn.OLDER_COLOR /* light blue */ : + UsedMemoryColumn.COLOR /* blue color */); + } + + const EXPECTED_ALL_OLDER_VALUES = { + icon: '\u26AF', + message: 'Older value (only heavy (purple) memory dumps contain ' + + 'memory maps).' + }; + const EXPECTED_SOME_OLDER_VALUES = { + icon: '\u26AF', + message: 'Older value at some selected timestamps (only heavy ' + + '(purple) memory dumps contain memory maps).' + }; + + // No context. + checkOverviewColumnInfosAndColor(c, + [FIELD], + undefined /* no context */, + [] /* no infos */, + UsedMemoryColumn.COLOR /* blue color */); + checkOverviewColumnInfosAndColor(c, + [FIELD, FIELD, 0, FIELD], + undefined /* no context */, + [] /* no infos */, + UsedMemoryColumn.COLOR /* blue color */); + + // All process memory dumps have own VM regions. + checkByteStatColumnInfosAndColor([ + FIELD | DUMP | HAS_OWN_VM_REGIONS + ], [] /* no infos */, false /* blue color */); + checkByteStatColumnInfosAndColor([ + FIELD | DUMP | HAS_OWN_VM_REGIONS, + FIELD | DUMP | HAS_OWN_VM_REGIONS, + 0, + FIELD | DUMP | HAS_OWN_VM_REGIONS + ], [] /* no infos */, false /* blue color */); + + // No process memory dumps have own VM regions. + checkByteStatColumnInfosAndColor([ + FIELD | DUMP + ], [EXPECTED_ALL_OLDER_VALUES], true /* light blue */); + checkByteStatColumnInfosAndColor([ + FIELD | DUMP, + FIELD | DUMP + ], [EXPECTED_ALL_OLDER_VALUES], true /* light blue */); + + // Some process memory dumps don't have own VM regions. + checkByteStatColumnInfosAndColor([ + FIELD | DUMP, + 0, + FIELD | DUMP + ], [EXPECTED_SOME_OLDER_VALUES], true /* light blue */); + checkByteStatColumnInfosAndColor([ + FIELD | DUMP | HAS_OWN_VM_REGIONS, + FIELD | DUMP, + FIELD | DUMP | HAS_OWN_VM_REGIONS + ], [EXPECTED_SOME_OLDER_VALUES], false /* blue */); + }); + + test('allocatorColumn', function() { + const c = new AllocatorColumn('Allocator', 'bytes', (x => x), + AggregationMode.MAX); + checkColorLegend(c.title, 'Allocator'); + checkColor(c.color(undefined /* contexts */), + undefined /* no color (column cells) */); + + const HAS_HEAP_DUMPS = 1 << 2; + const HAS_ALLOCATOR_HEAP_DUMP = 1 << 3; + const MISSING_SIZE = 1 << 4; + function checkAllocatorColumnInfosAndColor(fieldAndDumpMask, + expectedInfos) { + checkOverviewColumnInfosAndColor(c, + fieldAndDumpMask, + function(pmd, mask) { + if (mask & HAS_HEAP_DUMPS) { + pmd.heapDumps = {}; + } + if (mask & HAS_ALLOCATOR_HEAP_DUMP) { + pmd.heapDumps.Allocator = new HeapDump(pmd, 'Allocator'); + } + const mad = new MemoryAllocatorDump(pmd, 'Allocator'); + if (!(mask & MISSING_SIZE)) { + mad.addNumeric('size', + new Scalar(sizeInBytes_smallerIsBetter, 7)); + } + pmd.memoryAllocatorDumps = [mad]; + }, + expectedInfos, + undefined /* no color */); + } + + // No context. + checkOverviewColumnInfosAndColor(c, + [FIELD], + undefined /* no context */, + [] /* no infos */, + undefined /* no color */); + checkOverviewColumnInfosAndColor(c, + [FIELD, FIELD, 0, FIELD], + undefined /* no context */, + [] /* no infos */, + undefined /* no color */); + + // No infos. + checkAllocatorColumnInfosAndColor([ + FIELD | DUMP + ], [] /* no infos */); + checkAllocatorColumnInfosAndColor([ + FIELD | DUMP, + FIELD | DUMP | HAS_HEAP_DUMPS, + 0, + FIELD | DUMP + ], [] /* infos */); + + const EXPECTED_ALL_HAVE_ALLOCATOR_HEAP_DUMP = { + icon: '\u2630', + message: 'Heap dump provided.' + }; + const EXPECTED_SOME_HAVE_ALLOCATOR_HEAP_DUMP = { + icon: '\u2630', + message: 'Heap dump provided at some selected timestamps.' + }; + + // All process memory dumps have heap dumps. + checkAllocatorColumnInfosAndColor([ + FIELD | DUMP | HAS_HEAP_DUMPS | HAS_ALLOCATOR_HEAP_DUMP + ], [EXPECTED_ALL_HAVE_ALLOCATOR_HEAP_DUMP]); + checkAllocatorColumnInfosAndColor([ + FIELD | DUMP | HAS_HEAP_DUMPS | HAS_ALLOCATOR_HEAP_DUMP, + FIELD | DUMP | HAS_HEAP_DUMPS | HAS_ALLOCATOR_HEAP_DUMP, + FIELD | DUMP | HAS_HEAP_DUMPS | HAS_ALLOCATOR_HEAP_DUMP + ], [EXPECTED_ALL_HAVE_ALLOCATOR_HEAP_DUMP]); + + // Some process memory dumps have heap dumps. + checkAllocatorColumnInfosAndColor([ + 0, + FIELD | DUMP | HAS_HEAP_DUMPS | HAS_ALLOCATOR_HEAP_DUMP + ], [EXPECTED_SOME_HAVE_ALLOCATOR_HEAP_DUMP]); + checkAllocatorColumnInfosAndColor([ + FIELD | DUMP | HAS_HEAP_DUMPS | HAS_ALLOCATOR_HEAP_DUMP, + FIELD | DUMP | HAS_HEAP_DUMPS, + FIELD | DUMP | HAS_HEAP_DUMPS | HAS_ALLOCATOR_HEAP_DUMP + ], [EXPECTED_SOME_HAVE_ALLOCATOR_HEAP_DUMP]); + + const EXPECTED_ALL_MISSING_SIZE = { + icon: '\u26A0', + message: 'Size was not provided.', + color: 'red' + }; + const EXPECTED_SOME_MISSING_SIZE = { + icon: '\u26A0', + message: 'Size was not provided at some selected timestamps.', + color: 'red' + }; + + // All process memory dumps are missing allocator size. + checkAllocatorColumnInfosAndColor([ + FIELD | DUMP | MISSING_SIZE + ], [EXPECTED_ALL_MISSING_SIZE]); + checkAllocatorColumnInfosAndColor([ + FIELD | DUMP | MISSING_SIZE, + FIELD | DUMP | MISSING_SIZE, + FIELD | DUMP | MISSING_SIZE + ], [EXPECTED_ALL_MISSING_SIZE]); + + // Some process memory dumps use Android memtrack PSS fallback. + checkAllocatorColumnInfosAndColor([ + 0, + FIELD | DUMP | MISSING_SIZE + ], [EXPECTED_SOME_MISSING_SIZE]); + checkAllocatorColumnInfosAndColor([ + FIELD | DUMP | MISSING_SIZE, + FIELD | DUMP, + FIELD | DUMP | MISSING_SIZE + ], [EXPECTED_SOME_MISSING_SIZE]); + + // Combination of heap dump and memtrack fallback infos. + checkAllocatorColumnInfosAndColor([ + FIELD | DUMP | MISSING_SIZE | HAS_HEAP_DUMPS | + HAS_ALLOCATOR_HEAP_DUMP + ], [ + EXPECTED_ALL_HAVE_ALLOCATOR_HEAP_DUMP, + EXPECTED_ALL_MISSING_SIZE + ]); + checkAllocatorColumnInfosAndColor([ + FIELD | DUMP | HAS_HEAP_DUMPS | HAS_ALLOCATOR_HEAP_DUMP, + FIELD | DUMP, + FIELD | DUMP | MISSING_SIZE + ], [ + EXPECTED_SOME_HAVE_ALLOCATOR_HEAP_DUMP, + EXPECTED_SOME_MISSING_SIZE + ]); + }); + + test('tracingColumn', function() { + const c = new TracingColumn('Tracing', 'bytes', (x => x), + AggregationMode.DIFF); + checkSpanWithColor(c.title, 'Tracing', + TracingColumn.COLOR /* expected column title gray color */); + checkColor(c.color(undefined /* contexts */), + TracingColumn.COLOR /* expected column cells gray color */); + }); +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_sub_view_test_utils.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_sub_view_test_utils.html new file mode 100644 index 00000000000..2f702242140 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_sub_view_test_utils.html @@ -0,0 +1,593 @@ +<!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/base/color.html"> +<link rel="import" href="/tracing/base/color_scheme.html"> +<link rel="import" href="/tracing/base/scalar.html"> +<link rel="import" href="/tracing/base/unit.html"> +<link rel="import" href="/tracing/core/test_utils.html"> +<link rel="import" href="/tracing/model/global_memory_dump.html"> +<link rel="import" href="/tracing/model/heap_dump.html"> +<link rel="import" href="/tracing/model/memory_dump_test_utils.html"> +<link rel="import" href="/tracing/model/process_memory_dump.html"> +<link rel="import" href="/tracing/model/vm_region.html"> + +<script> +'use strict'; + +/** + * @fileoverview Helper functions for memory dump analysis sub-view tests. + */ +tr.exportTo('tr.ui.analysis', function() { + const Color = tr.b.Color; + const ColorScheme = tr.b.ColorScheme; + const GlobalMemoryDump = tr.model.GlobalMemoryDump; + const ProcessMemoryDump = tr.model.ProcessMemoryDump; + const VMRegion = tr.model.VMRegion; + const VMRegionClassificationNode = tr.model.VMRegionClassificationNode; + const Scalar = tr.b.Scalar; + const sizeInBytes_smallerIsBetter = + tr.b.Unit.byName.sizeInBytes_smallerIsBetter; + const unitlessNumber_smallerIsBetter = + tr.b.Unit.byName.unitlessNumber_smallerIsBetter; + const HeapDump = tr.model.HeapDump; + const addGlobalMemoryDump = tr.model.MemoryDumpTestUtils.addGlobalMemoryDump; + const addProcessMemoryDump = + tr.model.MemoryDumpTestUtils.addProcessMemoryDump; + const newAllocatorDump = tr.model.MemoryDumpTestUtils.newAllocatorDump; + const addOwnershipLink = tr.model.MemoryDumpTestUtils.addOwnershipLink; + + function createMultipleTestGlobalMemoryDumps() { + const model = tr.c.TestUtils.newModel(function(model) { + const pA = model.getOrCreateProcess(1); + const pB = model.getOrCreateProcess(2); + const pC = model.getOrCreateProcess(3); + const pD = model.getOrCreateProcess(4); + + // ====================================================================== + // First timestamp. + // ====================================================================== + const gmd1 = addGlobalMemoryDump(model, {ts: 42}); + + // Totals and VM regions. + const pmd1A = addProcessMemoryDump(gmd1, pA, {ts: 41}); + pmd1A.totals = {residentBytes: 31457280 /* 30 MiB */}; + pmd1A.vmRegions = VMRegionClassificationNode.fromRegions([ + VMRegion.fromDict({ + startAddress: 1024, + sizeInBytes: 20971520, /* 20 MiB */ + protectionFlags: VMRegion.PROTECTION_FLAG_READ, + mappedFile: '[stack]', + byteStats: { + privateDirtyResident: 8388608, /* 8 MiB */ + sharedCleanResident: 12582912, /* 12 MiB */ + proportionalResident: 10485760 /* 10 MiB */ + } + }) + ]); + + // Everything. + const pmd1B = addProcessMemoryDump(gmd1, pB, {ts: 42}); + pmd1B.totals = { + residentBytes: 20971520, /* 20 MiB */ + peakResidentBytes: 41943040, /* 40 MiB */ + arePeakResidentBytesResettable: false, + privateFootprintBytes: 15728640, /* 15 MiB */ + platformSpecific: { + private_bytes: 10485760 /* 10 MiB */ + } + }; + pmd1B.vmRegions = VMRegionClassificationNode.fromRegions([ + VMRegion.fromDict({ + startAddress: 256, + sizeInBytes: 6000, + protectionFlags: VMRegion.PROTECTION_FLAG_READ | + VMRegion.PROTECTION_FLAG_WRITE, + mappedFile: '[stack:20310]', + byteStats: { + proportionalResident: 15728640, /* 15 MiB */ + privateDirtyResident: 1572864, /* 1.5 MiB */ + swapped: 32 /* 32 B */ + } + }), + VMRegion.fromDict({ + startAddress: 100000, + sizeInBytes: 4096, + protectionFlags: VMRegion.PROTECTION_FLAG_READ, + mappedFile: '/usr/lib/libwtf.so', + byteStats: { + proportionalResident: 4194304, /* 4 MiB */ + privateDirtyResident: 0, + swapped: 0 /* 32 B */ + } + }) + ]); + pmd1B.memoryAllocatorDumps = [ + newAllocatorDump(pmd1B, 'malloc', + {numerics: {size: 3145728 /* 3 MiB */}}), + newAllocatorDump(pmd1B, 'v8', {numerics: {size: 5242880 /* 5 MiB */}}), + newAllocatorDump(pmd1B, 'tracing', {numerics: { + size: 1048576 /* 1 MiB */, + resident_size: 1572864 /* 1.5 MiB */ + }}) + ]; + + // Allocator dumps only. + const pmd1C = addProcessMemoryDump(gmd1, pC, {ts: 43}); + pmd1C.memoryAllocatorDumps = (function() { + const oilpanDump = newAllocatorDump(pmd1C, 'oilpan', {numerics: { + size: 3221225472 /* 3 GiB */, + inner_size: 5242880 /* 5 MiB */, + objects_count: new Scalar(unitlessNumber_smallerIsBetter, 2015) + }}); + const v8Dump = newAllocatorDump(pmd1C, 'v8', {numerics: { + size: 1073741824 /* 1 GiB */, + inner_size: 2097152 /* 2 MiB */, + objects_count: new Scalar(unitlessNumber_smallerIsBetter, 204) + }}); + + addOwnershipLink(v8Dump, oilpanDump); + + return [oilpanDump, v8Dump]; + })(); + pmd1C.heapDumps = { + 'v8': (function() { + const v8HeapDump = new HeapDump(pmd1C, 'v8'); + v8HeapDump.addEntry( + tr.c.TestUtils.newStackTrace(model, + ['V8.Execute', 'UpdateLayoutTree']), + undefined /* sum over all object types */, + 536870912 /* 512 MiB */); + return v8HeapDump; + })() + }; + + // ====================================================================== + // Second timestamp. + // ====================================================================== + const gmd2 = addGlobalMemoryDump(model, {ts: 68}); + + // Everything. + const pmd2A = addProcessMemoryDump(gmd2, pA, {ts: 67}); + pmd2A.totals = {residentBytes: 32505856 /* 31 MiB */}; + pmd2A.vmRegions = VMRegionClassificationNode.fromRegions([ + VMRegion.fromDict({ + startAddress: 1024, + sizeInBytes: 20971520, /* 20 MiB */ + protectionFlags: VMRegion.PROTECTION_FLAG_READ, + mappedFile: '[stack]', + byteStats: { + privateDirtyResident: 8388608, /* 8 MiB */ + sharedCleanResident: 11534336, /* 11 MiB */ + proportionalResident: 11534336 /* 11 MiB */ + } + }), + VMRegion.fromDict({ + startAddress: 104857600, + sizeInBytes: 5242880, /* 5 MiB */ + protectionFlags: VMRegion.PROTECTION_FLAG_EXECUTE, + mappedFile: '/usr/bin/google-chrome', + byteStats: { + privateDirtyResident: 0, + sharedCleanResident: 4194304, /* 4 MiB */ + proportionalResident: 524288 /* 512 KiB */ + } + }) + ]); + pmd2A.memoryAllocatorDumps = [ + newAllocatorDump(pmd2A, 'malloc', {numerics: { + size: 9437184 /* 9 MiB */ + }}), + newAllocatorDump(pmd2A, 'tracing', {numerics: { + size: 2097152 /* 2 MiB */, + resident_size: 2621440 /* 2.5 MiB */ + }}) + ]; + + // Totals and allocator dumps only. + const pmd2B = addProcessMemoryDump(gmd2, pB, {ts: 69}); + pmd2B.totals = { + residentBytes: 19922944, /* 19 MiB */ + peakResidentBytes: 41943040, /* 40 MiB */ + arePeakResidentBytesResettable: false, + privateFootprintBytes: 15728640, /* 15 MiB */ + platformSpecific: { + private_bytes: 8912896 /* 8.5 MiB */ + } + }; + pmd2B.memoryAllocatorDumps = [ + newAllocatorDump(pmd2B, 'malloc', {numerics: { + size: 2621440 /* 2.5 MiB */ + }}), + newAllocatorDump(pmd2B, 'v8', {numerics: { + size: 5242880 /* 5 MiB */ + }}), + newAllocatorDump(pmd2B, 'blink', {numerics: { + size: 7340032 /* 7 MiB */ + }}), + newAllocatorDump(pmd2B, 'oilpan', {numerics: {size: 1}}), + newAllocatorDump(pmd2B, 'tracing', {numerics: { + size: 1572864 /* 1.5 MiB */, + resident_size: 2097152 /* 2 MiB */ + }}), + newAllocatorDump(pmd2B, 'gpu', {numerics: { + memtrack_pss: 524288 /* 512 KiB */ + }}) + ]; + + // Resettable peak total size only. + const pmd2D = addProcessMemoryDump(gmd2, pD, {ts: 71}); + pmd2D.totals = { + peakResidentBytes: 17825792, /* 17 MiB */ + arePeakResidentBytesResettable: true + }; + + // ====================================================================== + // Third timestamp. + // ====================================================================== + const gmd3 = addGlobalMemoryDump(model, {ts: 100}); + + // Everything. + const pmd3B = addProcessMemoryDump(gmd3, pB, {ts: 102}); + pmd3B.totals = { + residentBytes: 18874368, /* 18 MiB */ + peakResidentBytes: 44040192, /* 42 MiB */ + privateFootprintBytes: 15728640, /* 16 MiB */ + arePeakResidentBytesResettable: false, + platformSpecific: { + private_bytes: 7340032 /* 7 MiB */ + } + }; + pmd3B.vmRegions = VMRegionClassificationNode.fromRegions([ + VMRegion.fromDict({ + startAddress: 256, + sizeInBytes: 6000, + protectionFlags: VMRegion.PROTECTION_FLAG_READ | + VMRegion.PROTECTION_FLAG_WRITE, + mappedFile: '[stack:20310]', + byteStats: { + proportionalResident: 21495808, /* 20.5 MiB */ + privateDirtyResident: 524288, /* 0.5 MiB */ + swapped: 64 /* 32 B */ + } + }) + ]); + pmd3B.memoryAllocatorDumps = [ + newAllocatorDump(pmd3B, 'malloc', {numerics: { + size: 2883584 /* 2.75 MiB */ + }}), + newAllocatorDump(pmd3B, 'v8', {numerics: { + size: 5767168 /* 5.5 MiB */ + }}), + newAllocatorDump(pmd3B, 'blink', {numerics: { + size: 6291456 /* 7 MiB */ + }}), + newAllocatorDump(pmd3B, 'tracing', {numerics: { + size: 2097152 /* 2 MiB */, + resident_size: 3145728 /* 3 MiB */ + }}), + newAllocatorDump(pmd3B, 'gpu', {numerics: { + size: 1048576 /* 1 MiB */, + memtrack_pss: 786432 /* 768 KiB */ + }}) + ]; + + // Allocator dumps only. + const pmd3C = addProcessMemoryDump(gmd3, pC, {ts: 100}); + pmd3C.memoryAllocatorDumps = (function() { + const oilpanDump = newAllocatorDump(pmd3C, 'oilpan', {numerics: { + size: 3221225472 /* 3 GiB */, + inner_size: 5242880 /* 5 MiB */, + objects_count: new Scalar(unitlessNumber_smallerIsBetter, 2015) + }}); + const v8Dump = newAllocatorDump(pmd3C, 'v8', {numerics: { + size: 2147483648 /* 2 GiB */, + inner_size: 2097152 /* 2 MiB */, + objects_count: new Scalar(unitlessNumber_smallerIsBetter, 204) + }}); + + addOwnershipLink(v8Dump, oilpanDump); + + return [oilpanDump, v8Dump]; + })(); + pmd3C.heapDumps = { + 'v8': (function() { + const v8HeapDump = new HeapDump(pmd1C, 'v8'); + v8HeapDump.addEntry( + tr.c.TestUtils.newStackTrace(model, + ['V8.Execute', 'UpdateLayoutTree']), + undefined /* sum over all object types */, + 268435456 /* 256 MiB */); + v8HeapDump.addEntry( + tr.c.TestUtils.newStackTrace(model, + ['V8.Execute', 'FrameView::layout']), + undefined /* sum over all object types */, + 134217728 /* 128 MiB */); + return v8HeapDump; + })() + }; + + // Resettable peak total size only. + const pmd3D = addProcessMemoryDump(gmd3, pD, {ts: 99}); + pmd3D.totals = { + peakResidentBytes: 17825792, /* 17 MiB */ + arePeakResidentBytesResettable: true + }; + }); + + return model.globalMemoryDumps; + } + + function createSingleTestGlobalMemoryDump() { + return createMultipleTestGlobalMemoryDumps()[1]; + } + + function createMultipleTestProcessMemoryDumps() { + return createMultipleTestGlobalMemoryDumps().map(function(gmd) { + return gmd.processMemoryDumps[2]; + }); + } + + function createSingleTestProcessMemoryDump() { + return createMultipleTestProcessMemoryDumps()[1]; + } + + function checkNumericFields(row, column, expectedValues, expectedUnit) { + let fields; + if (column === undefined) { + fields = row; + } else { + fields = column.fields(row); + } + + if (expectedValues === undefined) { + assert.isUndefined(fields); + return; + } + + assert.lengthOf(fields, expectedValues.length); + for (let i = 0; i < fields.length; i++) { + const field = fields[i]; + const expectedValue = expectedValues[i]; + if (expectedValue === undefined) { + assert.isUndefined(field); + } else { + assert.isDefined(expectedUnit); // Test sanity check. + assert.instanceOf(field, Scalar); + assert.strictEqual(field.value, expectedValue); + assert.strictEqual(field.unit, expectedUnit); + } + } + } + + function checkSizeNumericFields(row, column, expectedValues) { + checkNumericFields(row, column, expectedValues, + sizeInBytes_smallerIsBetter); + } + + function checkStringFields(row, column, expectedStrings) { + const fields = column.fields(row); + + if (expectedStrings === undefined) { + assert.isUndefined(fields); + return; + } + + assert.deepEqual(Array.from(fields), expectedStrings); + } + + /** + * Check the titles, types and aggregation modes of a list of columns. + * expectedColumns is a list of dictionaries with the following fields: + * + * - title: Either the expected title (string), or a matcher for it + * (function that accepts the actual title as its argument). + * - type: The expected class of the column. + * - noAggregation: If true, the column is expected to have no aggregation + * mode (regardless of expectedAggregationMode). + */ + function checkColumns(columns, expectedColumns, expectedAggregationMode) { + assert.lengthOf(columns, expectedColumns.length); + for (let i = 0; i < expectedColumns.length; i++) { + const actualColumn = columns[i]; + const expectedColumn = expectedColumns[i]; + const expectedTitle = expectedColumn.title; + if (typeof expectedTitle === 'function') { + expectedTitle(actualColumn.title); // Custom title matcher. + } else if (actualColumn.title.innerText) { + // HTML title. + assert.strictEqual(actualColumn.title.innerText, expectedTitle); + } else { + assert.strictEqual(actualColumn.title, expectedTitle); // String title. + } + assert.instanceOf(actualColumn, expectedColumn.type); + assert.strictEqual(actualColumn.aggregationMode, + expectedColumn.noAggregation ? undefined : expectedAggregationMode); + } + } + + function checkColumnInfosAndColor( + column, fields, contexts, expectedInfos, expectedColorReservedName) { + // Test sanity checks. + assert.isDefined(fields); + if (contexts !== undefined) { + assert.lengthOf(contexts, fields.length); + } + + // Check infos. + const infos = []; + column.addInfos(fields, contexts, infos); + assert.lengthOf(infos, expectedInfos.length); + for (let i = 0; i < expectedInfos.length; i++) { + assert.deepEqual(infos[i], expectedInfos[i]); + } + + // Check color. + const actualColor = typeof column.color === 'function' ? + column.color(fields, contexts) : + column.color; + checkColor(actualColor, expectedColorReservedName); + } + + function checkColor(actualColorString, expectedColorString) { + if (actualColorString === undefined) { + assert.isUndefined(expectedColorString); + return; + } + const actualColor = Color.fromString(actualColorString); + const expectedColor = Color.fromString(expectedColorString); + assert.deepEqual(actualColor, expectedColor); + } + + function createAndCheckEmptyPanes( + test, paneTagName, propertyName, opt_callback) { + // Unset property. + const unsetViewEl = createTestPane(paneTagName); + unsetViewEl.rebuild(); + assert.isUndefined(unsetViewEl.createChildPane()); + test.addHTMLOutput(unsetViewEl); + + // Undefined property. + const undefinedViewEl = createTestPane(paneTagName); + undefinedViewEl[propertyName] = undefined; + undefinedViewEl.rebuild(); + assert.isUndefined(undefinedViewEl.createChildPane()); + test.addHTMLOutput(undefinedViewEl); + + // Empty property. + const emptyViewEl = createTestPane(paneTagName); + emptyViewEl[propertyName] = []; + emptyViewEl.rebuild(); + assert.isUndefined(undefinedViewEl.createChildPane()); + test.addHTMLOutput(emptyViewEl); + + // Check that all the panes have the same dimensions. + const unsetBounds = unsetViewEl.getBoundingClientRect(); + const undefinedBounds = undefinedViewEl.getBoundingClientRect(); + const emptyBounds = emptyViewEl.getBoundingClientRect(); + assert.strictEqual(undefinedBounds.width, unsetBounds.width); + assert.strictEqual(emptyBounds.width, unsetBounds.width); + assert.strictEqual(undefinedBounds.height, unsetBounds.height); + assert.strictEqual(emptyBounds.height, unsetBounds.height); + + // Custom checks (if provided). + if (opt_callback) { + opt_callback(unsetViewEl); + opt_callback(undefinedViewEl); + opt_callback(emptyViewEl); + } + } + + function createTestPane(tagName) { + const paneEl = document.createElement(tagName); + + // Store a list of requested child panes (for inspection in tests). + paneEl.requestedChildPanes = []; + paneEl.addEventListener('request-child-pane-change', function() { + paneEl.requestedChildPanes.push(paneEl.createChildPane()); + }); + + paneEl.createChildPane = function() { + const childPaneBuilder = this.childPaneBuilder; + if (childPaneBuilder === undefined) return undefined; + return childPaneBuilder(); + }; + + return paneEl; + } + + // TODO(petrcermak): Consider moving this to tracing/ui/base/dom_helpers.html. + function isElementDisplayed(element) { + const style = getComputedStyle(element); + const displayed = style.display; + if (displayed === undefined) return true; + return displayed.indexOf('none') === -1; + } + + /** + * Convert a list of ContainerMemoryDump(s) to a list of dictionaries of the + * underlying ProcessMemoryDump(s). + */ + function convertToProcessMemoryDumps(containerMemoryDumps) { + return containerMemoryDumps.map(function(containerMemoryDump) { + return containerMemoryDump.processMemoryDumps; + }); + } + + /** + * Extract a chronological list of ProcessMemoryDump(s) (for a given process) + * from a chronological list of dictionaries of ProcessMemoryDump(s). + */ + function extractProcessMemoryDumps(processMemoryDumps, pid) { + return processMemoryDumps.map(function(memoryDumps) { + return memoryDumps[pid]; + }); + } + + /** + * Extract a chronological list of lists of VMRegion(s) (for a given process) + * from a chronological list of dictionaries of ProcessMemoryDump(s). + */ + function extractVmRegions(processMemoryDumps, pid) { + return processMemoryDumps.map(function(memoryDumps) { + const processMemoryDump = memoryDumps[pid]; + if (processMemoryDump === undefined) return undefined; + return processMemoryDump.mostRecentVmRegions; + }); + } + + /** + * Extract a chronological list of MemoryAllocatorDump(s) (for a given + * process and allocator name) from a chronological list of dictionaries of + * ProcessMemoryDump(s). + */ + function extractMemoryAllocatorDumps(processMemoryDumps, pid, allocatorName) { + return processMemoryDumps.map(function(memoryDumps) { + const processMemoryDump = memoryDumps[pid]; + if (processMemoryDump === undefined) return undefined; + return processMemoryDump.getMemoryAllocatorDumpByFullName(allocatorName); + }); + } + + /** + * Extract a chronological list of HeapDump(s) (for a given process and + * allocator name) from a chronological list of dictionaries of + * ProcessMemoryDump(s). + */ + function extractHeapDumps(processMemoryDumps, pid, allocatorName) { + return processMemoryDumps.map(function(memoryDumps) { + const processMemoryDump = memoryDumps[pid]; + if (processMemoryDump === undefined || + processMemoryDump.heapDumps === undefined) { + return undefined; + } + return processMemoryDump.heapDumps[allocatorName]; + }); + } + + return { + createSingleTestGlobalMemoryDump, + createMultipleTestGlobalMemoryDumps, + createSingleTestProcessMemoryDump, + createMultipleTestProcessMemoryDumps, + checkNumericFields, + checkSizeNumericFields, + checkStringFields, + checkColumns, + checkColumnInfosAndColor, + checkColor, + createAndCheckEmptyPanes, + createTestPane, + isElementDisplayed, + convertToProcessMemoryDumps, + extractProcessMemoryDumps, + extractVmRegions, + extractMemoryAllocatorDumps, + extractHeapDumps, + }; +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_sub_view_util.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_sub_view_util.html new file mode 100644 index 00000000000..654ce0ea73f --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_sub_view_util.html @@ -0,0 +1,915 @@ +<!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/base/scalar.html"> +<link rel="import" href="/tracing/base/utils.html"> +<link rel="import" href="/tracing/ui/base/dom_helpers.html"> +<link rel="import" href="/tracing/ui/base/table.html"> +<link rel="import" href="/tracing/value/ui/scalar_span.html"> + +<script> +'use strict'; + +/** + * @fileoverview Helper code for memory dump sub-views. + */ +tr.exportTo('tr.ui.analysis', function() { + const NO_BREAK_SPACE = String.fromCharCode(160); + const RIGHTWARDS_ARROW = String.fromCharCode(8594); + + const COLLATOR = new Intl.Collator(undefined, {numeric: true}); + + /** + * A table column for displaying memory dump row titles. + * + * @constructor + */ + function TitleColumn(title) { + this.title = title; + } + + TitleColumn.prototype = { + supportsCellSelection: false, + + /** + * Get the title associated with a given row. + * + * This method will decorate the title with color and '+++'/'---' prefix if + * appropriate (as determined by the optional row.contexts field). + * Examples: + * + * +----------------------+-----------------+--------+--------+ + * | Contexts provided at | Interpretation | Prefix | Color | + * +----------------------+-----------------+--------+--------+ + * | 1111111111 | always present | | | + * | 0000111111 | added | +++ | red | + * | 1111111000 | deleted | --- | green | + * | 1100111111* | flaky | | purple | + * | 0001001111 | added + flaky | +++ | purple | + * | 1111100010 | deleted + flaky | --- | purple | + * +----------------------+-----------------+--------+--------+ + * + * *) This means that, given a selection of 10 memory dumps, a particular + * row (e.g. a process) was present in the first 2 and last 6 of them + * (but not in the third and fourth dump). + * + * This method should therefore NOT be overriden by subclasses. The + * formatTitle method should be overriden instead when necessary. + */ + value(row) { + const formattedTitle = this.formatTitle(row); + + const contexts = row.contexts; + if (contexts === undefined || contexts.length === 0) { + return formattedTitle; + } + + // Determine if the row was provided in the first and last row and how + // many times it changed between being provided and not provided. + const firstContext = contexts[0]; + const lastContext = contexts[contexts.length - 1]; + let changeDefinedContextCount = 0; + for (let i = 1; i < contexts.length; i++) { + if ((contexts[i] === undefined) !== (contexts[i - 1] === undefined)) { + changeDefinedContextCount++; + } + } + + // Determine the color and prefix of the title. + let color = undefined; + let prefix = undefined; + if (!firstContext && lastContext) { + // The row was added. + color = 'red'; + prefix = '+++'; + } else if (firstContext && !lastContext) { + // The row was removed. + color = 'green'; + prefix = '---'; + } + if (changeDefinedContextCount > 1) { + // The row was flaky (added/removed more than once). + color = 'purple'; + } + + if (color === undefined && prefix === undefined) { + return formattedTitle; + } + + const titleEl = document.createElement('span'); + if (prefix !== undefined) { + const prefixEl = tr.ui.b.createSpan({textContent: prefix}); + // Enforce same width of '+++' and '---'. + prefixEl.style.fontFamily = 'monospace'; + Polymer.dom(titleEl).appendChild(prefixEl); + Polymer.dom(titleEl).appendChild( + tr.ui.b.asHTMLOrTextNode(NO_BREAK_SPACE)); + } + if (color !== undefined) { + titleEl.style.color = color; + } + Polymer.dom(titleEl).appendChild( + tr.ui.b.asHTMLOrTextNode(formattedTitle)); + return titleEl; + }, + + /** + * Format the title associated with a given row. This method is intended to + * be overriden by subclasses. + */ + formatTitle(row) { + return row.title; + }, + + cmp(rowA, rowB) { + return COLLATOR.compare(rowA.title, rowB.title); + } + }; + + /** + * Abstract table column for displaying memory dump data. + * + * @constructor + */ + function MemoryColumn(name, cellPath, aggregationMode) { + this.name = name; + this.cellPath = cellPath; + this.shouldSetContextGroup = false; + + // See MemoryColumn.AggregationMode enum in this file. + this.aggregationMode = aggregationMode; + } + + /** + * Construct columns from cells in a hierarchy of rows and a list of rules. + * + * The list of rules contains objects with three fields: + * + * condition: Optional string or regular expression matched against the + * name of a cell. If omitted, the rule will match any cell. + * importance: Mandatory number which determines the final order of the + * columns. The column with the highest importance will be first in the + * returned array. + * columnConstructor: Mandatory memory column constructor. + * + * Example: + * + * const importanceRules = [ + * { + * condition: 'page_size', + * columnConstructor: NumericMemoryColumn, + * importance: 8 + * }, + * { + * condition: /size/, + * columnConstructor: CustomNumericMemoryColumn, + * importance: 10 + * }, + * { + * // No condition: matches all columns. + * columnConstructor: NumericMemoryColumn, + * importance: 9 + * } + * ]; + * + * Given a name of a cell, the corresponding column constructor and + * importance are determined by the first rule whose condition matches the + * column's name. For example, given a cell with name 'inner_size', the + * corresponding column will be constructed using CustomNumericMemoryColumn + * and its importance (for sorting purposes) will be 10 (second rule). + * + * After columns are constructed for all cell names, they are sorted in + * descending order of importance and the resulting list is returned. In the + * example above, the constructed columns will be sorted into three groups as + * follows: + * + * [most important, left in the resulting table] + * 1. columns whose name contains 'size' excluding 'page_size' because it + * would have already matched the first rule (Note that string matches + * must be exact so a column named 'page_size2' would not match the + * first rule and would therefore belong to this group). + * 2. columns whose name does not contain 'size'. + * 3. columns whose name is 'page_size'. + * [least important, right in the resulting table] + * + * where columns will be sorted alphabetically within each group. + * + * @param {!Array.<!Object>} rows + * @param {!Object} config + * @param {string} config.cellKey + * @param {!MemoryColumn.AggregationMode=} config.aggregationMode + * @param {!Array.<!{ + * condition: (string|!RegExp)=, + * importance: number, + * columnConstructor: !function(new: MemoryColumn, ...)=, + * shouldSetContextGroup: boolean= + * }>} config.rules + */ + MemoryColumn.fromRows = function(rows, config) { + // Recursively find the names of all cells of the rows (and their sub-rows). + const cellNames = new Set(); + function gatherCellNames(rows) { + rows.forEach(function(row) { + if (row === undefined) return; + const fieldCells = row[config.cellKey]; + if (fieldCells !== undefined) { + for (const [fieldName, fieldCell] of Object.entries(fieldCells)) { + if (fieldCell === undefined || fieldCell.fields === undefined) { + continue; + } + cellNames.add(fieldName); + } + } + const subRows = row.subRows; + if (subRows !== undefined) { + gatherCellNames(subRows); + } + }); + } + gatherCellNames(rows); + + // Based on the provided list of rules, construct the columns and calculate + // their importance. + const positions = []; + cellNames.forEach(function(cellName) { + const cellPath = [config.cellKey, cellName]; + const matchingRule = MemoryColumn.findMatchingRule( + cellName, config.rules); + const constructor = matchingRule.columnConstructor; + const column = new constructor( + cellName, cellPath, config.aggregationMode); + column.shouldSetContextGroup = !!config.shouldSetContextGroup; + positions.push({ + importance: matchingRule.importance, + column + }); + }); + + positions.sort(function(a, b) { + // Sort columns with the same importance alphabetically. + if (a.importance === b.importance) { + return COLLATOR.compare(a.column.name, b.column.name); + } + + // Sort columns in descending order of importance. + return b.importance - a.importance; + }); + + return positions.map(function(position) { return position.column; }); + }; + + MemoryColumn.spaceEqually = function(columns) { + const columnWidth = (100 / columns.length).toFixed(3) + '%'; + columns.forEach(function(column) { + column.width = columnWidth; + }); + }; + + MemoryColumn.findMatchingRule = function(name, rules) { + for (let i = 0; i < rules.length; i++) { + const rule = rules[i]; + if (MemoryColumn.nameMatchesCondition(name, rule.condition)) { + return rule; + } + } + return undefined; + }; + + MemoryColumn.nameMatchesCondition = function(name, condition) { + // Rules without conditions match all columns. + if (condition === undefined) return true; + + // String conditions must match the column name exactly. + if (typeof(condition) === 'string') return name === condition; + + // If the condition is not a string, assume it is a RegExp. + return condition.test(name); + }; + + /** @enum */ + MemoryColumn.AggregationMode = { + DIFF: 0, + MAX: 1 + }; + + MemoryColumn.SOME_TIMESTAMPS_INFO_QUANTIFIER = 'at some selected timestamps'; + + MemoryColumn.prototype = { + get title() { + return this.name; + }, + + cell(row) { + let cell = row; + const cellPath = this.cellPath; + for (let i = 0; i < cellPath.length; i++) { + if (cell === undefined) return undefined; + cell = cell[cellPath[i]]; + } + return cell; + }, + + aggregateCells(row, subRows) { + // No generic aggregation. + }, + + fields(row) { + const cell = this.cell(row); + if (cell === undefined) return undefined; + return cell.fields; + }, + + /** + * Format a cell associated with this column from the given row. This + * method is not intended to be overriden. + */ + value(row) { + const fields = this.fields(row); + if (this.hasAllRelevantFieldsUndefined(fields)) return ''; + + // Determine the color and infos of the resulting element. + const contexts = row.contexts; + const color = this.color(fields, contexts); + const infos = []; + this.addInfos(fields, contexts, infos); + + // Format the actual fields. + const formattedFields = this.formatFields(fields); + + // If no color is specified and there are no infos, there is no need to + // wrap the value in a span element.# + if ((color === undefined || formattedFields === '') && + infos.length === 0) { + return formattedFields; + } + + const fieldEl = document.createElement('span'); + fieldEl.style.display = 'flex'; + fieldEl.style.alignItems = 'center'; + fieldEl.style.justifyContent = 'flex-end'; + Polymer.dom(fieldEl).appendChild( + tr.ui.b.asHTMLOrTextNode(formattedFields)); + + // Add info icons with tooltips. + infos.forEach(function(info) { + const infoEl = document.createElement('span'); + infoEl.style.paddingLeft = '4px'; + infoEl.style.cursor = 'help'; + infoEl.style.fontWeight = 'bold'; + Polymer.dom(infoEl).textContent = info.icon; + if (info.color !== undefined) { + infoEl.style.color = info.color; + } + infoEl.title = info.message; + Polymer.dom(fieldEl).appendChild(infoEl); + }, this); + + // Set the color of the element. + if (color !== undefined) { + fieldEl.style.color = color; + } + + return fieldEl; + }, + + /** + * Returns true iff all fields of a row which are relevant for the current + * aggregation mode (e.g. first and last field for diff mode) are undefined. + */ + hasAllRelevantFieldsUndefined(fields) { + if (fields === undefined) return true; + + switch (this.aggregationMode) { + case MemoryColumn.AggregationMode.DIFF: + // Only the first and last field are relevant. + return fields[0] === undefined && + fields[fields.length - 1] === undefined; + + case MemoryColumn.AggregationMode.MAX: + default: + // All fields are relevant. + return fields.every(function(field) { return field === undefined; }); + } + }, + + /** + * Get the color of the given fields formatted by this column. At least one + * field relevant for the current aggregation mode is guaranteed to be + * defined. + */ + color(fields, contexts) { + return undefined; + }, + + /** + * Format an arbitrary number of fields. At least one field relevant for + * the current aggregation mode is guaranteed to be defined. + */ + formatFields(fields) { + if (fields.length === 1) { + return this.formatSingleField(fields[0]); + } + return this.formatMultipleFields(fields); + }, + + /** + * Format a single defined field. + * + * This method is intended to be overriden by field type specific columns + * (e.g. show '1.0 KiB' instead of '1024' for Scalar(s) representing + * bytes). + */ + formatSingleField(field) { + throw new Error('Not implemented'); + }, + + /** + * Format multiple fields. At least one field relevant for the current + * aggregation mode is guaranteed to be defined. + * + * The aggregation mode specializations of this method (e.g. + * formatMultipleFieldsDiff) are intended to be overriden by field type + * specific columns. + */ + formatMultipleFields(fields) { + switch (this.aggregationMode) { + case MemoryColumn.AggregationMode.DIFF: + return this.formatMultipleFieldsDiff( + fields[0], fields[fields.length - 1]); + + case MemoryColumn.AggregationMode.MAX: + return this.formatMultipleFieldsMax(fields); + + default: + return tr.ui.b.createSpan({ + textContent: '(unsupported aggregation mode)', + italic: true + }); + } + }, + + formatMultipleFieldsDiff(firstField, lastField) { + throw new Error('Not implemented'); + }, + + formatMultipleFieldsMax(fields) { + return this.formatSingleField(this.getMaxField(fields)); + }, + + cmp(rowA, rowB) { + const fieldsA = this.fields(rowA); + const fieldsB = this.fields(rowB); + + // Sanity check. + if (fieldsA !== undefined && fieldsB !== undefined && + fieldsA.length !== fieldsB.length) { + throw new Error('Different number of fields'); + } + + // Handle empty fields. + const undefinedA = this.hasAllRelevantFieldsUndefined(fieldsA); + const undefinedB = this.hasAllRelevantFieldsUndefined(fieldsB); + if (undefinedA && undefinedB) return 0; + if (undefinedA) return -1; + if (undefinedB) return 1; + + return this.compareFields(fieldsA, fieldsB); + }, + + /** + * Compare a pair of single or multiple fields. At least one field relevant + * for the current aggregation mode is guaranteed to be defined in each of + * the two lists. + */ + compareFields(fieldsA, fieldsB) { + if (fieldsA.length === 1) { + return this.compareSingleFields(fieldsA[0], fieldsB[0]); + } + return this.compareMultipleFields(fieldsA, fieldsB); + }, + + /** + * Compare a pair of single defined fields. + * + * This method is intended to be overriden by field type specific columns. + */ + compareSingleFields(fieldA, fieldB) { + throw new Error('Not implemented'); + }, + + /** + * Compare a pair of multiple fields. At least one field relevant for the + * current aggregation mode is guaranteed to be defined in each of the two + * lists. + * + * The aggregation mode specializations of this method (e.g. + * compareMultipleFieldsDiff) are intended to be overriden by field type + * specific columns. + */ + compareMultipleFields(fieldsA, fieldsB) { + switch (this.aggregationMode) { + case MemoryColumn.AggregationMode.DIFF: + return this.compareMultipleFieldsDiff( + fieldsA[0], fieldsA[fieldsA.length - 1], + fieldsB[0], fieldsB[fieldsB.length - 1]); + + case MemoryColumn.AggregationMode.MAX: + return this.compareMultipleFieldsMax(fieldsA, fieldsB); + + default: + return 0; + } + }, + + compareMultipleFieldsDiff(firstFieldA, lastFieldA, firstFieldB, + lastFieldB) { + throw new Error('Not implemented'); + }, + + compareMultipleFieldsMax(fieldsA, fieldsB) { + return this.compareSingleFields( + this.getMaxField(fieldsA), this.getMaxField(fieldsB)); + }, + + getMaxField(fields) { + return fields.reduce(function(accumulator, field) { + if (field === undefined) { + return accumulator; + } + if (accumulator === undefined || + this.compareSingleFields(field, accumulator) > 0) { + return field; + } + return accumulator; + }.bind(this), undefined); + }, + + addInfos(fields, contexts, infos) { + // No generic infos. + }, + + getImportance(importanceRules) { + if (importanceRules.length === 0) return 0; + + // Find the first matching rule. + const matchingRule = + MemoryColumn.findMatchingRule(this.name, importanceRules); + if (matchingRule !== undefined) { + return matchingRule.importance; + } + + // No matching rule. Return lower importance than all rules. + let minImportance = importanceRules[0].importance; + for (let i = 1; i < importanceRules.length; i++) { + minImportance = Math.min(minImportance, importanceRules[i].importance); + } + return minImportance - 1; + } + }; + + /** + * @constructor + */ + function StringMemoryColumn(name, cellPath, aggregationMode) { + MemoryColumn.call(this, name, cellPath, aggregationMode); + } + + StringMemoryColumn.prototype = { + __proto__: MemoryColumn.prototype, + + formatSingleField(string) { + return string; + }, + + formatMultipleFieldsDiff(firstString, lastString) { + if (firstString === undefined) { + // String was added ("+NEW_VALUE" in red). + const spanEl = tr.ui.b.createSpan({color: 'red'}); + Polymer.dom(spanEl).appendChild(tr.ui.b.asHTMLOrTextNode('+')); + Polymer.dom(spanEl).appendChild(tr.ui.b.asHTMLOrTextNode( + this.formatSingleField(lastString))); + return spanEl; + } else if (lastString === undefined) { + // String was removed ("-OLD_VALUE" in green). + const spanEl = tr.ui.b.createSpan({color: 'green'}); + Polymer.dom(spanEl).appendChild(tr.ui.b.asHTMLOrTextNode('-')); + Polymer.dom(spanEl).appendChild(tr.ui.b.asHTMLOrTextNode( + this.formatSingleField(firstString))); + return spanEl; + } else if (firstString === lastString) { + // String didn't change ("VALUE" with unchanged color). + return this.formatSingleField(firstString); + } + // String changed ("OLD_VALUE -> NEW_VALUE" in orange). + const spanEl = tr.ui.b.createSpan({color: 'DarkOrange'}); + Polymer.dom(spanEl).appendChild(tr.ui.b.asHTMLOrTextNode( + this.formatSingleField(firstString))); + Polymer.dom(spanEl).appendChild(tr.ui.b.asHTMLOrTextNode( + ' ' + RIGHTWARDS_ARROW + ' ')); + Polymer.dom(spanEl).appendChild(tr.ui.b.asHTMLOrTextNode( + this.formatSingleField(lastString))); + return spanEl; + }, + + compareSingleFields(stringA, stringB) { + return COLLATOR.compare(stringA, stringB); + }, + + compareMultipleFieldsDiff(firstStringA, lastStringA, firstStringB, + lastStringB) { + // If one of the strings was added (and the other one wasn't), mark the + // corresponding diff as greater. + if (firstStringA === undefined && firstStringB !== undefined) { + return 1; + } + if (firstStringA !== undefined && firstStringB === undefined) { + return -1; + } + + // If both strings were added, compare the last values (greater last + // value implies greater diff). + if (firstStringA === undefined && firstStringB === undefined) { + return this.compareSingleFields(lastStringA, lastStringB); + } + + // If one of the strings was removed (and the other one wasn't), mark the + // corresponding diff as lower. + if (lastStringA === undefined && lastStringB !== undefined) { + return -1; + } + if (lastStringA !== undefined && lastStringB === undefined) { + return 1; + } + + // If both strings were removed, compare the first values (greater first + // value implies smaller (!) diff). + if (lastStringA === undefined && lastStringB === undefined) { + return this.compareSingleFields(firstStringB, firstStringA); + } + + const areStringsAEqual = firstStringA === lastStringA; + const areStringsBEqual = firstStringB === lastStringB; + + // Consider diffs of strings that did not change to be smaller than diffs + // of strings that did change. + if (areStringsAEqual && areStringsBEqual) return 0; + if (areStringsAEqual) return -1; + if (areStringsBEqual) return 1; + + // Both strings changed. We are unable to determine the ordering of the + // diffs. + return 0; + } + }; + + /** + * @constructor + */ + function NumericMemoryColumn(name, cellPath, aggregationMode) { + MemoryColumn.call(this, name, cellPath, aggregationMode); + } + + // Avoid tiny positive/negative diffs (displayed in the UI as '+0.0 B' and + // '-0.0 B') due to imprecise floating-point arithmetic by treating all diffs + // within the (-DIFF_EPSILON, DIFF_EPSILON) range as zeros. + NumericMemoryColumn.DIFF_EPSILON = 0.0001; + + NumericMemoryColumn.prototype = { + __proto__: MemoryColumn.prototype, + + align: tr.ui.b.TableFormat.ColumnAlignment.RIGHT, + + aggregateCells(row, subRows) { + const subRowCells = subRows.map(this.cell, this); + + // Determine if there is at least one defined numeric in the sub-row + // cells and the timestamp count. + let hasDefinedSubRowNumeric = false; + let timestampCount = undefined; + subRowCells.forEach(function(subRowCell) { + if (subRowCell === undefined) return; + + const subRowNumerics = subRowCell.fields; + if (subRowNumerics === undefined) return; + + if (timestampCount === undefined) { + timestampCount = subRowNumerics.length; + } else if (timestampCount !== subRowNumerics.length) { + throw new Error('Sub-rows have different numbers of timestamps'); + } + + if (hasDefinedSubRowNumeric) { + return; // Avoid unnecessary traversals of the numerics. + } + hasDefinedSubRowNumeric = subRowNumerics.some(function(numeric) { + return numeric !== undefined; + }); + }); + if (!hasDefinedSubRowNumeric) { + return; // No numeric to aggregate. + } + + // Get or create the row cell. + const cellPath = this.cellPath; + let rowCell = row; + for (let i = 0; i < cellPath.length; i++) { + const nextStepName = cellPath[i]; + let nextStep = rowCell[nextStepName]; + if (nextStep === undefined) { + if (i < cellPath.length - 1) { + nextStep = {}; + } else { + nextStep = new MemoryCell(undefined); + } + rowCell[nextStepName] = nextStep; + } + rowCell = nextStep; + } + if (rowCell.fields === undefined) { + rowCell.fields = new Array(timestampCount); + } else if (rowCell.fields.length !== timestampCount) { + throw new Error( + 'Row has a different number of timestamps than sub-rows'); + } + + for (let i = 0; i < timestampCount; i++) { + if (rowCell.fields[i] !== undefined) continue; + rowCell.fields[i] = tr.model.MemoryAllocatorDump.aggregateNumerics( + subRowCells.map(function(subRowCell) { + if (subRowCell === undefined || subRowCell.fields === undefined) { + return undefined; + } + return subRowCell.fields[i]; + })); + } + }, + + formatSingleField(numeric) { + return tr.v.ui.createScalarSpan(numeric, { + context: this.getFormattingContext(numeric.unit), + contextGroup: this.shouldSetContextGroup ? this.name : undefined, + inline: true, + }); + }, + + getFormattingContext(unit) { + return undefined; + }, + + formatMultipleFieldsDiff(firstNumeric, lastNumeric) { + return this.formatSingleField( + this.getDiffField_(firstNumeric, lastNumeric)); + }, + + compareSingleFields(numericA, numericB) { + return numericA.value - numericB.value; + }, + + compareMultipleFieldsDiff(firstNumericA, lastNumericA, + firstNumericB, lastNumericB) { + return this.getDiffFieldValue_(firstNumericA, lastNumericA) - + this.getDiffFieldValue_(firstNumericB, lastNumericB); + }, + + getDiffField_(firstNumeric, lastNumeric) { + const definedNumeric = firstNumeric || lastNumeric; + return new tr.b.Scalar(definedNumeric.unit.correspondingDeltaUnit, + this.getDiffFieldValue_(firstNumeric, lastNumeric)); + }, + + getDiffFieldValue_(firstNumeric, lastNumeric) { + const firstValue = firstNumeric === undefined ? 0 : firstNumeric.value; + const lastValue = lastNumeric === undefined ? 0 : lastNumeric.value; + const diff = lastValue - firstValue; + return Math.abs(diff) < NumericMemoryColumn.DIFF_EPSILON ? 0 : diff; + } + }; + + /** + * @constructor + */ + function MemoryCell(fields) { + this.fields = fields; + } + + MemoryCell.extractFields = function(cell) { + if (cell === undefined) return undefined; + return cell.fields; + }; + + /** Limit for the number of sub-rows for recursive table row expansion. */ + const RECURSIVE_EXPANSION_MAX_VISIBLE_ROW_COUNT = 10; + + function expandTableRowsRecursively(table) { + let currentLevelRows = table.tableRows; + let totalVisibleRowCount = currentLevelRows.length; + + while (currentLevelRows.length > 0) { + // Calculate the total number of sub-rows on the current level. + let nextLevelRowCount = 0; + currentLevelRows.forEach(function(currentLevelRow) { + const subRows = currentLevelRow.subRows; + if (subRows === undefined || subRows.length === 0) return; + nextLevelRowCount += subRows.length; + }); + + // Determine whether expanding all rows on the current level would cause + // the total number of visible rows go over the limit. + if (totalVisibleRowCount + nextLevelRowCount > + RECURSIVE_EXPANSION_MAX_VISIBLE_ROW_COUNT) { + break; + } + + // Expand all rows on the current level and gather their sub-rows. + const nextLevelRows = new Array(nextLevelRowCount); + let nextLevelRowIndex = 0; + currentLevelRows.forEach(function(currentLevelRow) { + const subRows = currentLevelRow.subRows; + if (subRows === undefined || subRows.length === 0) return; + table.setExpandedForTableRow(currentLevelRow, true); + subRows.forEach(function(subRow) { + nextLevelRows[nextLevelRowIndex++] = subRow; + }); + }); + + // Update the total number of visible rows and progress to the next level. + totalVisibleRowCount += nextLevelRowCount; + currentLevelRows = nextLevelRows; + } + } + + function aggregateTableRowCellsRecursively(row, columns, opt_predicate) { + const subRows = row.subRows; + if (subRows === undefined || subRows.length === 0) return; + + subRows.forEach(function(subRow) { + aggregateTableRowCellsRecursively(subRow, columns, opt_predicate); + }); + + if (opt_predicate === undefined || opt_predicate(row.contexts)) { + aggregateTableRowCells(row, subRows, columns); + } + } + + function aggregateTableRowCells(row, subRows, columns) { + columns.forEach(function(column) { + if (!(column instanceof MemoryColumn)) return; + column.aggregateCells(row, subRows); + }); + } + + function createCells(timeToValues, valueFieldsGetter, opt_this) { + opt_this = opt_this || this; + const fieldNameToFields = tr.b.invertArrayOfDicts( + timeToValues, valueFieldsGetter, opt_this); + const result = {}; + for (const [fieldName, fields] of Object.entries(fieldNameToFields)) { + result[fieldName] = new tr.ui.analysis.MemoryCell(fields); + } + return result; + } + + function createWarningInfo(message) { + return { + message, + icon: String.fromCharCode(9888), + color: 'red' + }; + } + + // TODO(petrcermak): Use a context manager instead + // (https://github.com/catapult-project/catapult/issues/2420). + function DetailsNumericMemoryColumn(name, cellPath, aggregationMode) { + NumericMemoryColumn.call(this, name, cellPath, aggregationMode); + } + + DetailsNumericMemoryColumn.prototype = { + __proto__: NumericMemoryColumn.prototype, + + getFormattingContext(unit) { + if (unit.baseUnit === tr.b.Unit.byName.sizeInBytes) { + return { unitPrefix: tr.b.UnitPrefixScale.BINARY.KIBI }; + } + return undefined; + } + }; + + return { + TitleColumn, + MemoryColumn, + StringMemoryColumn, + NumericMemoryColumn, + MemoryCell, + expandTableRowsRecursively, + aggregateTableRowCellsRecursively, + aggregateTableRowCells, + createCells, + createWarningInfo, + DetailsNumericMemoryColumn, + }; +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_sub_view_util_test.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_sub_view_util_test.html new file mode 100644 index 00000000000..859f78433d6 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_sub_view_util_test.html @@ -0,0 +1,1241 @@ +<!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/base/scalar.html"> +<link rel="import" href="/tracing/base/unit.html"> +<link rel="import" href="/tracing/base/utils.html"> +<link rel="import" href="/tracing/ui/analysis/memory_dump_sub_view_test_utils.html"> +<link rel="import" href="/tracing/ui/analysis/memory_dump_sub_view_util.html"> +<link rel="import" href="/tracing/ui/base/dom_helpers.html"> +<link rel="import" href="/tracing/ui/base/table.html"> + +<script> +'use strict'; + +tr.b.unittest.testSuite(function() { + const TitleColumn = tr.ui.analysis.TitleColumn; + const MemoryColumn = tr.ui.analysis.MemoryColumn; + const AggregationMode = MemoryColumn.AggregationMode; + const StringMemoryColumn = tr.ui.analysis.StringMemoryColumn; + const NumericMemoryColumn = tr.ui.analysis.NumericMemoryColumn; + const MemoryCell = tr.ui.analysis.MemoryCell; + const expandTableRowsRecursively = tr.ui.analysis.expandTableRowsRecursively; + const aggregateTableRowCells = tr.ui.analysis.aggregateTableRowCells; + const aggregateTableRowCellsRecursively = + tr.ui.analysis.aggregateTableRowCellsRecursively; + const Scalar = tr.b.Scalar; + const sizeInBytes_smallerIsBetter = + tr.b.Unit.byName.sizeInBytes_smallerIsBetter; + const checkSizeNumericFields = tr.ui.analysis.checkSizeNumericFields; + const checkNumericFields = tr.ui.analysis.checkNumericFields; + const checkStringFields = tr.ui.analysis.checkStringFields; + const createCells = tr.ui.analysis.createCells; + const createWarningInfo = tr.ui.analysis.createWarningInfo; + + function checkPercent(string, expectedPercent) { + assert.strictEqual(Number(string.slice(0, -1)), expectedPercent); + assert.strictEqual(string.slice(-1), '%'); + } + + function checkMemoryColumnFieldFormat(test, column, fields, + expectedTextContent, opt_expectedColor) { + const value = column.formatMultipleFields(fields); + if (expectedTextContent === undefined) { + assert.strictEqual(value, ''); + assert.isUndefined(opt_expectedColor); // Test sanity check. + return; + } + + const node = tr.ui.b.asHTMLOrTextNode(value); + const spanEl = document.createElement('span'); + Polymer.dom(spanEl).appendChild(node); + test.addHTMLOutput(spanEl); + + assert.strictEqual(Polymer.dom(node).textContent, expectedTextContent); + if (opt_expectedColor === undefined) { + assert.notInstanceOf(node, HTMLElement); + } else { + assert.strictEqual(node.style.color, opt_expectedColor); + } + } + + function checkCompareFieldsEqual(column, fieldValuesA, fieldValuesB) { + assert.strictEqual(column.compareFields(fieldValuesA, fieldValuesB), 0); + } + + function checkCompareFieldsLess(column, fieldValuesA, fieldValuesB) { + assert.isBelow(column.compareFields(fieldValuesA, fieldValuesB), 0); + assert.isAbove(column.compareFields(fieldValuesB, fieldValuesA), 0); + } + + function checkNumericMemoryColumnFieldFormat(test, column, fieldValues, unit, + expectedValue) { + const value = column.formatMultipleFields( + buildScalarCell(unit, fieldValues).fields); + if (expectedValue === undefined) { + assert.strictEqual(value, ''); + assert.isUndefined(expectedUnits); // Test sanity check. + return; + } + + test.addHTMLOutput(value); + assert.strictEqual(value.tagName, 'TR-V-UI-SCALAR-SPAN'); + assert.strictEqual(value.value, expectedValue); + assert.strictEqual(value.unit, unit); + } + + function buildScalarCell(unit, values) { + return new MemoryCell(values.map(function(value) { + if (value === undefined) return undefined; + return new Scalar(unit, value); + })); + } + + function buildTestRows() { + return [ + { + title: 'Row 1', + fields: { + 'cpu_temperature': new MemoryCell(['below zero', 'absolute zero']) + }, + subRows: [ + { + title: 'Row 1A', + fields: { + 'page_size': buildScalarCell(sizeInBytes_smallerIsBetter, + [1024, 1025]) + } + }, + { + title: 'Row 1B', + fields: { + 'page_size': buildScalarCell(sizeInBytes_smallerIsBetter, + [512, 513]), + 'mixed': new MemoryCell(['0.01', '0.10']), + 'mixed2': new MemoryCell([ + new Scalar(tr.b.Unit.byName.powerInWatts, 2.43e18), + new Scalar(tr.b.Unit.byName.powerInWatts, 0.5433) + ]) + } + } + ] + }, + { + title: 'Row 2', + fields: { + 'cpu_temperature': undefined, + 'mixed': buildScalarCell(tr.b.Unit.byName.timeDurationInMs, + [0.99, 0.999]) + } + } + ]; + } + + function checkCellValue( + test, value, expectedText, expectedColor, opt_expectedInfos) { + const expectedInfos = opt_expectedInfos || []; + assert.lengthOf(Polymer.dom(value).childNodes, 1 + expectedInfos.length); + assert.strictEqual(value.style.color, expectedColor); + if (typeof expectedText === 'string') { + assert.strictEqual( + Polymer.dom(Polymer.dom(value).childNodes[0]).textContent, + expectedText); + } else { + expectedText(Polymer.dom(value).childNodes[0]); + } + for (let i = 0; i < expectedInfos.length; i++) { + const expectedInfo = expectedInfos[i]; + const infoEl = Polymer.dom(value).childNodes[i + 1]; + assert.strictEqual(Polymer.dom(infoEl).textContent, expectedInfo.icon); + assert.strictEqual(infoEl.title, expectedInfo.message); + assert.strictEqual(infoEl.style.color, expectedInfo.color || ''); + } + test.addHTMLOutput(value); + } + + function sizeSpanMatcher( + expectedValue, opt_expectedIsDelta, opt_expectedContext) { + return function(element) { + assert.strictEqual(element.tagName, 'TR-V-UI-SCALAR-SPAN'); + assert.strictEqual(element.value, expectedValue); + assert.strictEqual(element.unit, opt_expectedIsDelta ? + tr.b.Unit.byName.sizeInBytesDelta_smallerIsBetter : + tr.b.Unit.byName.sizeInBytes_smallerIsBetter); + assert.deepEqual(element.context, opt_expectedContext); + }; + } + + test('checkTitleColumn_value', function() { + const column = new TitleColumn('column_title'); + assert.strictEqual(column.title, 'column_title'); + assert.isFalse(column.supportsCellSelection); + + let row = {title: 'undefined', contexts: undefined}; + assert.strictEqual(column.formatTitle(row), 'undefined'); + assert.strictEqual(column.value(row), 'undefined'); + + row = {title: 'constant', contexts: [{}, {}, {}, {}]}; + assert.strictEqual(column.formatTitle(row), 'constant'); + assert.strictEqual(column.value(row), 'constant'); + + row = {title: 'added', contexts: [undefined, undefined, undefined, {}]}; + assert.strictEqual(column.formatTitle(row), 'added'); + let value = column.value(row); + assert.strictEqual(Polymer.dom(value).textContent, '+++\u00A0added'); + assert.strictEqual(value.style.color, 'red'); + + row = {title: 'removed', contexts: [true, true, undefined, undefined]}; + assert.strictEqual(column.formatTitle(row), 'removed'); + value = column.value(row); + assert.strictEqual(Polymer.dom(value).textContent, '---\u00A0removed'); + assert.strictEqual(value.style.color, 'green'); + + row = {title: 'flaky', contexts: [true, undefined, true, true]}; + assert.strictEqual(column.formatTitle(row), 'flaky'); + value = column.value(row); + assert.strictEqual(Polymer.dom(value).textContent, 'flaky'); + assert.strictEqual(value.style.color, 'purple'); + + row = {title: 'added-flaky', contexts: [undefined, {}, undefined, true]}; + assert.strictEqual(column.formatTitle(row), 'added-flaky'); + value = column.value(row); + assert.strictEqual(Polymer.dom(value).textContent, '+++\u00A0added-flaky'); + assert.strictEqual(value.style.color, 'purple'); + + row = {title: 'removed-flaky', contexts: [true, undefined, {}, undefined]}; + assert.strictEqual(column.formatTitle(row), 'removed-flaky'); + value = column.value(row); + assert.strictEqual( + Polymer.dom(value).textContent, '---\u00A0removed-flaky'); + assert.strictEqual(value.style.color, 'purple'); + }); + + test('checkTitleColumn_cmp', function() { + const column = new TitleColumn('column_title'); + + assert.isBelow(column.cmp({title: 'a'}, {title: 'b'}), 0); + assert.strictEqual(column.cmp({title: 'cc'}, {title: 'cc'}), 0); + assert.isAbove(column.cmp({title: '10'}, {title: '2'}), 0); + }); + + test('checkMemoryColumn_fromRows', function() { + function MockColumn0() { + MemoryColumn.apply(this, arguments); + } + MockColumn0.prototype = { + __proto__: MemoryColumn.prototype, + get title() { return 'MockColumn0'; } + }; + + function MockColumn1() { + MemoryColumn.apply(this, arguments); + } + MockColumn1.prototype = { + __proto__: MemoryColumn.prototype, + get title() { return 'MockColumn1'; } + }; + + function MockColumn2() { + MemoryColumn.apply(this, arguments); + } + MockColumn2.prototype = { + __proto__: MemoryColumn.prototype, + get title() { return 'MockColumn2'; } + }; + + const rules = [ + { + condition: /size/, + importance: 10, + columnConstructor: MockColumn0 + }, + { + condition: 'cpu_temperature', + importance: 0, + columnConstructor: MockColumn1 + }, + { + condition: 'unmatched', + importance: -1, + get columnConstructor() { + throw new Error('The constructor should never be retrieved'); + } + }, + { + importance: 1, + columnConstructor: MockColumn2 + } + ]; + + const rows = buildTestRows(); + const columns = MemoryColumn.fromRows(rows, { + cellKey: 'fields', + aggregationMode: AggregationMode.MAX, + rules, + shouldSetContextGroup: true + }); + assert.lengthOf(columns, 4); + + const pageSizeColumn = columns[0]; + assert.strictEqual(pageSizeColumn.name, 'page_size'); + assert.strictEqual(pageSizeColumn.title, 'MockColumn0'); + assert.strictEqual(pageSizeColumn.aggregationMode, AggregationMode.MAX); + assert.strictEqual(pageSizeColumn.cell({fields: {page_size: 'large'}}), + 'large'); + assert.isTrue(pageSizeColumn.shouldSetContextGroup); + assert.instanceOf(pageSizeColumn, MockColumn0); + + const mixedColumn = columns[1]; + assert.strictEqual(mixedColumn.name, 'mixed'); + assert.strictEqual(mixedColumn.title, 'MockColumn2'); + assert.strictEqual(mixedColumn.aggregationMode, AggregationMode.MAX); + assert.strictEqual(mixedColumn.cell({fields: {mixed: 89}}), 89); + assert.isTrue(mixedColumn.shouldSetContextGroup); + assert.instanceOf(mixedColumn, MockColumn2); + + const mixed2Column = columns[2]; + assert.strictEqual(mixed2Column.name, 'mixed2'); + assert.strictEqual(mixed2Column.title, 'MockColumn2'); + assert.strictEqual(mixed2Column.aggregationMode, AggregationMode.MAX); + assert.strictEqual(mixed2Column.cell({fields: {mixed2: 'invalid'}}), + 'invalid'); + assert.isTrue(mixed2Column.shouldSetContextGroup); + assert.instanceOf(mixed2Column, MockColumn2); + + const cpuTemperatureColumn = columns[3]; + assert.strictEqual(cpuTemperatureColumn.name, 'cpu_temperature'); + assert.strictEqual(cpuTemperatureColumn.title, 'MockColumn1'); + assert.strictEqual(cpuTemperatureColumn.aggregationMode, + AggregationMode.MAX); + assert.strictEqual( + cpuTemperatureColumn.cell({fields: {cpu_temperature: 42}}), 42); + assert.isTrue(cpuTemperatureColumn.shouldSetContextGroup); + assert.instanceOf(cpuTemperatureColumn, MockColumn1); + }); + + test('checkMemoryColumn_spaceEqually', function() { + // Zero columns. + let columns = []; + MemoryColumn.spaceEqually(columns); + + // One column. + columns = [ + { + title: 'First Column', + value(row) { return row.firstData; } + } + ]; + MemoryColumn.spaceEqually(columns); + checkPercent(columns[0].width, 100); + + // Two columns. + columns = [ + { + title: 'First Column', + value(row) { return row.firstData; } + }, + { + title: 'Second Column', + value(row) { return row.firstData; } + } + ]; + MemoryColumn.spaceEqually(columns); + checkPercent(columns[0].width, 50); + checkPercent(columns[1].width, 50); + }); + + test('checkMemoryColumn_instantiate', function() { + const c = new MemoryColumn('test_column', ['x'], AggregationMode.MAX); + assert.strictEqual(c.name, 'test_column'); + assert.strictEqual(c.title, 'test_column'); + assert.strictEqual(c.cell({x: 95}), 95); + assert.isUndefined(c.width); + assert.isUndefined(c.color()); + }); + + test('checkMemoryColumn_cell', function() { + const c = new MemoryColumn('test_column', ['a', 'b'], AggregationMode.MAX); + const cell = new MemoryCell(undefined); + + assert.isUndefined(c.cell(undefined)); + assert.isUndefined(c.cell({b: cell})); + assert.isUndefined(c.cell({a: {c: cell}})); + assert.strictEqual(c.cell({a: {b: cell, c: 42}}), cell); + }); + + test('checkMemoryColumn_fields', function() { + const c = new MemoryColumn('test_column', ['x'], + AggregationMode.MAX); + + // Undefined cell or field inside cell. + assert.isUndefined(c.fields({})); + assert.isUndefined(c.fields({x: new MemoryCell(undefined)})); + + // Defined field(s) inside cell. + const field1 = new Scalar(tr.b.Unit.byName.powerInWatts, 1013.25); + const field2 = new Scalar(tr.b.Unit.byName.powerInWatts, 1065); + const row1 = {x: new MemoryCell([field1])}; + const row2 = {x: new MemoryCell([field1, field2])}; + assert.deepEqual(c.fields(row1), [field1]); + assert.deepEqual(c.fields(row2), [field1, field2]); + }); + + test('checkMemoryColumn_hasAllRelevantFieldsUndefined', function() { + // Single field. + const c1 = new MemoryColumn('single_column', ['x'], + undefined /* aggregation mode */); + assert.isTrue(c1.hasAllRelevantFieldsUndefined([undefined])); + assert.isFalse(c1.hasAllRelevantFieldsUndefined( + [new Scalar(sizeInBytes_smallerIsBetter, 16)])); + + // Multiple fields, diff aggregation mode. + const c2 = new MemoryColumn('diff_column', ['x'], + AggregationMode.DIFF); + assert.isTrue(c2.hasAllRelevantFieldsUndefined([undefined, undefined])); + assert.isTrue(c2.hasAllRelevantFieldsUndefined( + [undefined, undefined, undefined])); + assert.isTrue(c2.hasAllRelevantFieldsUndefined( + [undefined, new Scalar(sizeInBytes_smallerIsBetter, 16), undefined])); + assert.isFalse(c2.hasAllRelevantFieldsUndefined( + [undefined, new Scalar(sizeInBytes_smallerIsBetter, 32)])); + assert.isFalse(c2.hasAllRelevantFieldsUndefined( + [new Scalar(sizeInBytes_smallerIsBetter, 32), undefined, undefined])); + assert.isFalse(c2.hasAllRelevantFieldsUndefined([ + new Scalar(sizeInBytes_smallerIsBetter, 16), + undefined, + new Scalar(sizeInBytes_smallerIsBetter, 32) + ])); + + // Multiple fields, max aggregation mode. + const c3 = new MemoryColumn('max_column', ['x'], + AggregationMode.MAX); + assert.isTrue(c3.hasAllRelevantFieldsUndefined([undefined, undefined])); + assert.isTrue(c3.hasAllRelevantFieldsUndefined( + [undefined, undefined, undefined])); + assert.isFalse(c3.hasAllRelevantFieldsUndefined( + [undefined, new Scalar(sizeInBytes_smallerIsBetter, 16), undefined])); + assert.isFalse(c3.hasAllRelevantFieldsUndefined( + [undefined, new Scalar(sizeInBytes_smallerIsBetter, 32)])); + assert.isFalse(c3.hasAllRelevantFieldsUndefined([ + new Scalar(sizeInBytes_smallerIsBetter, 32), + undefined, + new Scalar(sizeInBytes_smallerIsBetter, 16) + ])); + }); + + test('checkMemoryColumn_value_allFieldsUndefined', function() { + const c1 = new MemoryColumn('no_color', ['x'], + AggregationMode.MAX); + const c2 = new MemoryColumn('color', ['x'], + AggregationMode.DIFF); + Object.defineProperty(c2, 'color', { + get() { + throw new Error('The color should never be retrieved'); + } + }); + + // Infos should be completely ignored. + c1.addInfos = c2.addInfos = function() { + throw new Error('This method should never be called'); + }; + + [c1, c2].forEach(function(c) { + assert.strictEqual(c.value({}), ''); + assert.strictEqual(c.value({x: new MemoryCell(undefined)}), ''); + assert.strictEqual(c.value({x: new MemoryCell([undefined])}), ''); + assert.strictEqual( + c.value({x: new MemoryCell([undefined, undefined])}), ''); + }); + + // Diff should only take into account the first and last field value. + assert.strictEqual(c2.value({ + x: new MemoryCell([ + undefined, + new Scalar(sizeInBytes_smallerIsBetter, 16), + undefined + ]) + }), ''); + }); + + test('checkMemoryColumn_getImportance', function() { + const c = new NumericMemoryColumn('test_column', ['x']); + + const rules1 = []; + assert.strictEqual(c.getImportance(rules1), 0); + + const rules2 = [ + { + condition: 'test', + importance: 4 + }, + { + condition: /test$/, + importance: 2 + } + ]; + assert.strictEqual(c.getImportance(rules2), 1); + + const rules3 = [ + { + condition: 'test_column', + importance: 10 + }, + { + importance: 5 + } + ]; + assert.strictEqual(c.getImportance(rules3), 10); + + const rules4 = [ + { + condition: 'test_column2', + importance: 8 + }, + { + condition: /column/, + importance: 12 + } + ]; + assert.strictEqual(c.getImportance(rules4), 12); + }); + + test('checkMemoryColumn_nameMatchesCondition', function() { + const c = new NumericMemoryColumn('test_column', ['x']); + + assert.isTrue(MemoryColumn.nameMatchesCondition('test_column', undefined)); + + assert.isFalse(MemoryColumn.nameMatchesCondition('test_column', 'test')); + assert.isTrue( + MemoryColumn.nameMatchesCondition('test_column', 'test_column')); + assert.isFalse( + MemoryColumn.nameMatchesCondition('test_column', 'test_column2')); + + assert.isTrue(MemoryColumn.nameMatchesCondition('test_column', /test/)); + assert.isTrue( + MemoryColumn.nameMatchesCondition('test_column', /^[^_]*_[^_]*$/)); + assert.isFalse(MemoryColumn.nameMatchesCondition('test_column', /test$/)); + }); + + test('checkStringMemoryColumn_value_singleField', function() { + const c = new StringMemoryColumn('', ['x'], AggregationMode.MAX); + c.color = function(fields, contexts) { + if (fields[0] < '0') return 'green'; + if (contexts && contexts[0] % 2 === 0) return 'red'; + return undefined; + }; + + const infos1 = [{ icon: '\u{1F648}', message: 'Some info', color: 'blue' }]; + const infos2 = [ + { icon: '\u{1F649}', message: 'Start', color: 'cyan' }, + { icon: '\u{1F64A}', message: 'Stop' } + ]; + c.addInfos = function(fields, contexts, infos) { + if (fields[0] < '0') { + infos.push.apply(infos, infos1); + } else if (contexts && contexts[0] % 2 === 0) { + infos.push.apply(infos, infos2); + } + }; + + let row = {x: new MemoryCell(['123'])}; + assert.strictEqual(c.value(row), '123'); + + row = {x: new MemoryCell(['-123']), contexts: [undefined]}; + checkCellValue(this, c.value(row), '-123', 'green', infos1); + + row = {x: new MemoryCell(['123']), contexts: [42]}; + checkCellValue(this, c.value(row), '123', 'red', infos2); + }); + + test('checkStringMemoryColumn_value_multipleFields', function() { + const c1 = new StringMemoryColumn('test_column1', ['x'], + undefined /* aggregation mode */); + const c2 = new StringMemoryColumn('test_column2', ['x'], + AggregationMode.DIFF); + c2.color = function(fields, contexts) { + return '#009999'; + }; + const c3 = new StringMemoryColumn('test_column3', ['x'], + AggregationMode.MAX); + c3.color = function(fields, contexts) { + if (fields[0] < '0') { + return 'green'; + } else if (contexts && contexts[contexts.length - 1] % 2 === 0) { + return 'red'; + } + return undefined; + }; + + const infos1 = [{ icon: '\u{1F648}', message: 'Some info', color: 'blue' }]; + const infos2 = [ + { icon: '\u{1F649}', message: 'Start', color: 'cyan' }, + { icon: '\u{1F64A}', message: 'Stop' } + ]; + c1.addInfos = c2.addInfos = c3.addInfos = + function(fields, contexts, infos) { + if (fields[0] < '0') { + infos.push.apply(infos, infos1); + } else if (contexts && contexts[contexts.length - 1] % 2 === 0) { + infos.push.apply(infos, infos2); + } + }; + + let row = {x: new MemoryCell(['123', '456'])}; + checkCellValue(this, c1.value(row), '(unsupported aggregation mode)', ''); + checkCellValue(this, c2.value(row), '123 \u2192 456', 'rgb(0, 153, 153)'); + assert.strictEqual(c3.value(row), '456'); + + row = { + x: new MemoryCell(['-123', undefined, '+123']), + contexts: [12, 14, undefined] + }; + checkCellValue(this, c1.value(row), '(unsupported aggregation mode)', '', + infos1); + checkCellValue(this, c2.value(row), '-123 \u2192 +123', 'rgb(0, 153, 153)', + infos1); + checkCellValue(this, c3.value(row), '+123', 'green', infos1); + + row = { + x: new MemoryCell(['123', undefined, '456']), + contexts: [31, 7, -2] + }; + checkCellValue(this, c1.value(row), '(unsupported aggregation mode)', '', + infos2); + checkCellValue(this, c2.value(row), '123 \u2192 456', 'rgb(0, 153, 153)', + infos2); + checkCellValue(this, c3.value(row), '456', 'red', infos2); + }); + + test('checkStringMemoryColumn_formatSingleField', function() { + const c = new StringMemoryColumn('test_column', ['x'], + undefined /* aggregation mode */); + + assert.strictEqual(c.formatSingleField('1024'), '1024'); + assert.strictEqual(c.formatSingleField('~10'), '~10'); + }); + + test('checkStringMemoryColumn_formatMultipleFields_diff', function() { + const c = new StringMemoryColumn('test_column', ['x'], + AggregationMode.DIFF); + + // Added value. + checkMemoryColumnFieldFormat(this, c, [undefined, 'few'], '+few', 'red'); + checkMemoryColumnFieldFormat(this, c, [undefined, 64, 32], '+32', 'red'); + + // Removed value. + checkMemoryColumnFieldFormat(this, c, ['00', undefined], '-00', 'green'); + checkMemoryColumnFieldFormat(this, c, [1, undefined, 2, undefined], '-1', + 'green'); + + // Identical values. + checkMemoryColumnFieldFormat(this, c, ['Unchanged', 'Unchanged'], + 'Unchanged', undefined /* unchanged color (not an HTML element) */); + checkMemoryColumnFieldFormat(this, c, [16, 32, undefined, 64, 16], '16', + undefined /* unchanged color (not an HTML element) */); + + // Different values. + checkMemoryColumnFieldFormat(this, c, ['A', 'C', undefined, 'C', 'B'], + 'A \u2192 B', 'darkorange'); + checkMemoryColumnFieldFormat(this, c, [16, undefined, 64], '16 \u2192 64', + 'darkorange'); + }); + + test('checkStringMemoryColumn_formatMultipleFields_max', function() { + const c = new StringMemoryColumn('test_column', ['x'], + AggregationMode.MAX); + + // Different values. + checkMemoryColumnFieldFormat(this, c, ['A', 'B', 'A'], 'B', + undefined /* unchanged color (not an HTML element) */); + checkMemoryColumnFieldFormat(this, c, [16, 16, undefined, 17], '17', + undefined /* unchanged color (not an HTML element) */); + + // Identical values. + checkMemoryColumnFieldFormat(this, c, ['X', 'X'], 'X', + undefined /* unchanged color (not an HTML element) */); + checkMemoryColumnFieldFormat(this, c, [7, undefined, 7, undefined, 7], '7', + undefined /* unchanged color (not an HTML element) */); + }); + + test('checkStringMemoryColumn_compareSingleFields', function() { + const c = new StringMemoryColumn('test_column', ['x'], + undefined /* aggregation mode */); + + assert.isBelow(c.compareSingleFields( + new Scalar(sizeInBytes_smallerIsBetter, 2), + new Scalar(sizeInBytes_smallerIsBetter, 10)), 0); + assert.strictEqual(c.compareSingleFields('equal', 'equal'), 0); + assert.isAbove(c.compareSingleFields('100', '99'), 0); + }); + + test('checkStringMemoryColumn_compareMultipleFields_diff', function() { + const c = new StringMemoryColumn('test_column', ['x'], + AggregationMode.DIFF); + + // One field was added. + checkCompareFieldsLess(c, [-10, 10], [undefined, 5]); + checkCompareFieldsLess(c, + [-100, undefined, undefined], [undefined, 4, 5]); + checkCompareFieldsLess(c, + [1, 2, 3, 4], [undefined, 'x', undefined, 'y']); + + // Both fields were added. + checkCompareFieldsEqual(c, + [undefined, 'C', undefined, 'A'], [undefined, 'B', 'D', 'A']); + checkCompareFieldsLess(c, [undefined, 1], [undefined, 2]); + checkCompareFieldsLess(c, [undefined, 6, 3], [undefined, 5, 4]); + + // One field was removed (neither was added). + checkCompareFieldsLess(c, ['B', undefined], ['A', 'A']); + checkCompareFieldsLess(c, + [5, undefined, undefined], [undefined, -5, -10]); + + // Both fields were removed (neither was added) + checkCompareFieldsEqual(c, ['T', 'A', undefined, undefined], + ['T', 'B', 'C', undefined]); + checkCompareFieldsLess(c, [5, undefined], [4, undefined]); + + // Neither field was added or removed. + checkCompareFieldsLess(c, ['BB', 'BB'], ['AA', 'CC']); + checkCompareFieldsEqual(c, [7, 8, 9], [6, 9, 10]); + checkCompareFieldsEqual(c, [5, undefined, 5], [4, 3, 4]); + }); + + test('checkStringMemoryColumn_compareMultipleFields_max', function() { + const c = new StringMemoryColumn('test_column', ['x'], + AggregationMode.MAX); + + // At least one field has multiple values. + checkCompareFieldsEqual(c, [0, 1, 3], [1, 3, 2]); + checkCompareFieldsLess(c, ['4', undefined, '4'], ['3', '4', '5']); + checkCompareFieldsLess(c, [3, 3, 3], [9, undefined, 10]); + + // Both fields have single values. + checkCompareFieldsEqual(c, + [undefined, 'ttt', undefined], ['ttt', 'ttt', undefined]); + checkCompareFieldsLess(c, [undefined, -1, undefined], [-2, -2, -2]); + checkCompareFieldsLess(c, ['Q', 'Q', undefined], ['X', undefined, 'X']); + }); + + test('checkStringMemoryColumn_cmp', function() { + const c = new StringMemoryColumn('test_column', ['x'], + AggregationMode.DIFF); + + // Cell (or the associated field) undefined in one or both rows. + assert.strictEqual(c.cmp({}, {y: new MemoryCell([undefined])}), 0); + assert.strictEqual(c.cmp({x: new MemoryCell(undefined)}, {}), 0); + assert.strictEqual( + c.cmp({x: new MemoryCell([undefined, undefined])}, {}), 0); + assert.isAbove(c.cmp({x: new MemoryCell(['negative'])}, {}), 0); + assert.isAbove(c.cmp({x: new MemoryCell(['negative'])}, + {x: new MemoryCell([undefined])}), 0); + assert.isBelow(c.cmp({}, {x: new MemoryCell(['positive'])}), 0); + assert.isBelow(c.cmp({x: new MemoryCell(undefined)}, + {x: new MemoryCell(['positive'])}), 0); + + // Single field. + assert.strictEqual(c.cmp({x: new MemoryCell(['equal'])}, + {x: new MemoryCell(['equal'])}), 0); + assert.isAbove(c.cmp({x: new MemoryCell(['bigger'])}, + {x: new MemoryCell(['BIG'])}), 0); + assert.isBelow(c.cmp({x: new MemoryCell(['small'])}, + {x: new MemoryCell(['smaLL'])}), 0); + + // Multiple fields. + assert.isBelow(c.cmp( + {x: new MemoryCell(['MemoryColumn', 'supports*', undefined])}, + {x: new MemoryCell(['comparing', 'multiple', 'values :-)'])}), 0); + }); + + test('checkNumericMemoryColumn_value', function() { + const c = new NumericMemoryColumn('test_column', ['x'], + AggregationMode.DIFF); + c.color = function(fields, contexts) { + return '#009999'; + }; + const infos1 = [createWarningInfo('Attention!')]; + c.addInfos = function(fields, contexts, infos) { + infos.push.apply(infos, infos1); + }; + + // Undefined field values. + let row = {x: buildScalarCell(sizeInBytes_smallerIsBetter, + [undefined, 1, undefined])}; + assert.strictEqual(c.value(row), ''); + + // Single field value. + row = {x: buildScalarCell(sizeInBytes_smallerIsBetter, + [5.4975581e13/* 50 TiB */])}; + checkCellValue(this, c.value(row), sizeSpanMatcher(5.4975581e13), + 'rgb(0, 153, 153)', infos1); + + // Multiple field values. + row = { + x: buildScalarCell(sizeInBytes_smallerIsBetter, + [5.4975581e13/* 50 TiB */, undefined, 2.1990233e13/* 20 TiB */]) + }; + checkCellValue(this, c.value(row), + sizeSpanMatcher(-3.2985348e13, true /* opt_expectedIsDelta */), + 'rgb(0, 153, 153)', infos1); + + // With custom formatting context. + c.getFormattingContext = function(unit) { + assert.strictEqual(unit, + tr.b.Unit.byName.sizeInBytesDelta_smallerIsBetter); + return { minimumFractionDigits: 2 }; + }; + checkCellValue(this, c.value(row), + sizeSpanMatcher(-3.2985348e13, true /* opt_expectedIsDelta */, + { minimumFractionDigits: 2 }), + 'rgb(0, 153, 153)', infos1); + }); + + test('checkNumericMemoryColumn_formatSingleField', function() { + let c = new NumericMemoryColumn('non_bytes_column', ['x'], + undefined /* aggregation mode */); + let value = c.formatSingleField(new Scalar( + tr.b.Unit.byName.unitlessNumber_smallerIsBetter, 123)); + assert.strictEqual(value.tagName, 'TR-V-UI-SCALAR-SPAN'); + assert.strictEqual(value.value, 123); + assert.strictEqual(value.unit, + tr.b.Unit.byName.unitlessNumber_smallerIsBetter); + assert.isUndefined(value.contextGroup); + this.addHTMLOutput(value); + + c = new NumericMemoryColumn('bytes_column', ['x'], + undefined /* aggregation mode */); + c.shouldSetContextGroup = true; + value = c.formatSingleField(new Scalar( + sizeInBytes_smallerIsBetter, 456)); + assert.strictEqual(value.tagName, 'TR-V-UI-SCALAR-SPAN'); + assert.strictEqual(value.value, 456); + assert.strictEqual(value.unit, + tr.b.Unit.byName.sizeInBytes_smallerIsBetter); + assert.strictEqual(value.contextGroup, 'bytes_column'); + this.addHTMLOutput(value); + }); + + test('checkNumericMemoryColumn_formatMultipleFields_diff', + function() { + let c = new NumericMemoryColumn( + 'non_bytes_column', ['x'], AggregationMode.DIFF); + checkNumericMemoryColumnFieldFormat(this, c, [1, 2, 3], + tr.b.Unit.byName.unitlessNumberDelta_smallerIsBetter, 2); + checkNumericMemoryColumnFieldFormat(this, c, [10, undefined], + tr.b.Unit.byName.unitlessNumberDelta_smallerIsBetter, -10); + checkNumericMemoryColumnFieldFormat(this, c, [undefined, 60, 0], + tr.b.Unit.byName.unitlessNumberDelta_smallerIsBetter, 0); + checkNumericMemoryColumnFieldFormat( + this, c, [2.71828, 2.71829] /* diff within epsilon */, + tr.b.Unit.byName.unitlessNumberDelta_smallerIsBetter, 0); + + c = new NumericMemoryColumn( + 'bytes_column', ['x'], AggregationMode.DIFF); + checkNumericMemoryColumnFieldFormat(this, c, [1, 2, 3], + tr.b.Unit.byName.sizeInBytesDelta_smallerIsBetter, 2); + checkNumericMemoryColumnFieldFormat(this, c, [10, undefined], + tr.b.Unit.byName.sizeInBytesDelta_smallerIsBetter, -10); + checkNumericMemoryColumnFieldFormat(this, c, [undefined, 60, 0], + tr.b.Unit.byName.sizeInBytesDelta_smallerIsBetter, 0); + checkNumericMemoryColumnFieldFormat( + this, c, [1.41421, 1.41422] /* diff within epsilon */, + tr.b.Unit.byName.sizeInBytesDelta_smallerIsBetter, 0); + }); + + test('checkNumericMemoryColumn_formatMultipleFields_max', + function() { + let c = new NumericMemoryColumn( + 'non_bytes_column', ['x'], AggregationMode.MAX); + checkNumericMemoryColumnFieldFormat(this, c, [1, 2, 3], + tr.b.Unit.byName.unitlessNumber_smallerIsBetter, 3); + checkNumericMemoryColumnFieldFormat(this, c, [10, undefined], + tr.b.Unit.byName.unitlessNumber_smallerIsBetter, 10); + checkNumericMemoryColumnFieldFormat(this, c, [undefined, 60, 0], + tr.b.Unit.byName.unitlessNumber_smallerIsBetter, 60); + checkNumericMemoryColumnFieldFormat( + this, c, [undefined, 10, 20, undefined], + tr.b.Unit.byName.unitlessNumber_smallerIsBetter, 20); + + c = new NumericMemoryColumn( + 'bytes_column', ['x'], AggregationMode.MAX); + checkNumericMemoryColumnFieldFormat(this, c, [1, 2, 3], + tr.b.Unit.byName.sizeInBytes_smallerIsBetter, 3); + checkNumericMemoryColumnFieldFormat(this, c, [10, undefined], + tr.b.Unit.byName.sizeInBytes_smallerIsBetter, 10); + checkNumericMemoryColumnFieldFormat(this, c, [undefined, 60, 0], + tr.b.Unit.byName.sizeInBytes_smallerIsBetter, 60); + checkNumericMemoryColumnFieldFormat( + this, c, [undefined, 10, 20, undefined], + tr.b.Unit.byName.sizeInBytes_smallerIsBetter, 20); + }); + + test('checkNumericMemoryColumn_cmp', function() { + const c = new NumericMemoryColumn( + 'test_column', ['x'], AggregationMode.DIFF); + + // Undefined field values. + assert.isAbove(c.cmp({x: buildScalarCell(sizeInBytes_smallerIsBetter, + [-9999999999])}, + {x: undefined}), 0); + assert.isBelow(c.cmp({x: new MemoryCell(undefined)}, + {x: buildScalarCell(sizeInBytes_smallerIsBetter, [748, 749])}), 0); + assert.strictEqual( + c.cmp({}, { + x: buildScalarCell( + sizeInBytes_smallerIsBetter, [undefined, undefined]) + }), 0); + + // Single field value. + assert.isBelow(c.cmp( + {x: buildScalarCell(sizeInBytes_smallerIsBetter, [16384])}, + {x: buildScalarCell(sizeInBytes_smallerIsBetter, [32768])}), 0); + + // Multiple field values. + assert.strictEqual(c.cmp( + {x: buildScalarCell( + sizeInBytes_smallerIsBetter, [999, undefined, 1001])}, + {x: buildScalarCell( + sizeInBytes_smallerIsBetter, [undefined, 5, 2])}), 0); + }); + + test('checkNumericMemoryColumn_compareSingleFields', function() { + const c = new NumericMemoryColumn('test_column', ['x'], + undefined /* aggregation mode */); + + assert.isBelow(c.compareSingleFields( + new Scalar( + tr.b.Unit.byName.timeDurationInMs_smallerIsBetter, 99), + new Scalar( + tr.b.Unit.byName.timeDurationInMs_smallerIsBetter, 100)), 0); + assert.strictEqual(c.compareSingleFields( + new Scalar(tr.b.Unit.byName.unitlessNumber, 0xEEE), + new Scalar(tr.b.Unit.byName.unitlessNumber, 0xEEE)), 0); + assert.isAbove(c.compareSingleFields( + new Scalar(sizeInBytes_smallerIsBetter, 10), + new Scalar(sizeInBytes_smallerIsBetter, 2)), 0); + }); + + test('checkNumericMemoryColumn_compareMultipleFields_diff', function() { + const c = new NumericMemoryColumn('test_column', ['x'], + AggregationMode.DIFF); + + assert.isBelow(c.compareMultipleFields( + buildScalarCell(sizeInBytes_smallerIsBetter, + [10000, 10001, 10002] /* diff +2 */).fields, + buildScalarCell(sizeInBytes_smallerIsBetter, + [5, 7, 8] /* diff +3 */).fields), 0); + assert.strictEqual(c.compareMultipleFields( + buildScalarCell(tr.b.Unit.byName.timeDurationInMs_smallerIsBetter, + [4, undefined] /* diff -4 */).fields, + buildScalarCell(tr.b.Unit.byName.timeDurationInMs_smallerIsBetter, + [999, 995] /* diff -4 */).fields), 0); + assert.isAbove(c.compareMultipleFields( + buildScalarCell(sizeInBytes_smallerIsBetter, + [10, undefined, 12] /* diff +2 */).fields, + buildScalarCell(sizeInBytes_smallerIsBetter, + [11, 50, 12] /* diff +1 */).fields), 0); + assert.strictEqual(c.compareMultipleFields( + buildScalarCell(tr.b.Unit.byName.powerInWatts_smallerIsBetter, + [17, undefined, 17] /* diff 0 */).fields, + buildScalarCell(tr.b.Unit.byName.powerInWatts_smallerIsBetter, + [undefined, 100, undefined] /* diff 0 */).fields), 0); + assert.strictEqual(c.compareMultipleFields( + buildScalarCell(sizeInBytes_smallerIsBetter, + [3.14159, undefined, 3.14160] /* diff within epsilon */).fields, + buildScalarCell(sizeInBytes_smallerIsBetter, + [100, 100, 100] /* diff 0 */).fields), 0); + }); + + test('checkNumericMemoryColumn_compareMultipleFields_max', function() { + const c = new NumericMemoryColumn('test_column', ['x'], + AggregationMode.MAX); + + assert.isBelow(c.compareMultipleFields( + buildScalarCell(sizeInBytes_smallerIsBetter, + [10, undefined, 12]).fields, + buildScalarCell(sizeInBytes_smallerIsBetter, [11, 50, 12]).fields), 0); + assert.strictEqual(c.compareMultipleFields( + buildScalarCell(tr.b.Unit.byName.timeDurationInMs_smallerIsBetter, + [999, undefined, -8888]).fields, + buildScalarCell(tr.b.Unit.byName.timeDurationInMs_smallerIsBetter, + [undefined, 999, undefined]).fields), 0); + assert.isAbove(c.compareMultipleFields( + buildScalarCell(sizeInBytes_smallerIsBetter, + [10000, 10001, 10002]).fields, + buildScalarCell(sizeInBytes_smallerIsBetter, [5, 7, 8]).fields), 0); + assert.isBelow(c.compareMultipleFields( + buildScalarCell(tr.b.Unit.byName.powerInWatts_smallerIsBetter, + [17, undefined, 17]).fields, + buildScalarCell(tr.b.Unit.byName.powerInWatts_smallerIsBetter, + [undefined, 100, undefined]).fields), 0); + }); + + test('checkNumericMemoryColumn_getDiffFieldValue', function() { + const c = new NumericMemoryColumn('test_column', ['x'], + AggregationMode.MAX); + function checkDiffValue(first, last, expectedDiffValue) { + const actualDiffValue = c.getDiffFieldValue_( + first === undefined ? undefined : + new Scalar(sizeInBytes_smallerIsBetter, first), + last === undefined ? undefined : + new Scalar(sizeInBytes_smallerIsBetter, last)); + assert.closeTo(actualDiffValue, expectedDiffValue, 1e-8); + } + + // Diff outside epsilon range. + checkDiffValue(0, 0.0002, 0.0002); + checkDiffValue(undefined, 0.0003, 0.0003); + checkDiffValue(0.3334, 0.3332, -0.0002); + checkDiffValue(0.0005, undefined, -0.0005); + + // Diff inside epsilon range. + checkDiffValue(5, 5.00009, 0); + checkDiffValue(undefined, 0.0000888, 0); + checkDiffValue(0.29999, 0.3, 0); + checkDiffValue(0.00009, undefined, 0); + checkDiffValue(0.777777, 0.777777, 0); + checkDiffValue(undefined, undefined, 0); + }); + + test('checkExpandTableRowsRecursively', function() { + const columns = [ + { + title: 'Single column', + value(row) { return row.data; }, + width: '100px' + } + ]; + + const rows = [ + { + data: 'allocated', + subRows: [ + { + data: 'v8', + subRows: [] + }, + { + data: 'oilpan', + subRows: [ + { + data: 'still_visible', + subRows: [ + { + data: 'not_visible_any_more' + } + ] + }, + { + data: 'also_visible' + } + ] + } + ] + }, + { + data: 'no_sub_rows' + }, + { + data: 'fragmentation', + subRows: [ + { + data: 'internal' + }, + { + data: 'external', + subRows: [ + { + data: 'unexpanded' + } + ] + } + ] + } + ]; + + const table = document.createElement('tr-ui-b-table'); + table.tableColumns = columns; + table.tableRows = rows; + table.rebuild(); + + expandTableRowsRecursively(table); + + function isExpanded(row) { return table.getExpandedForTableRow(row); } + + // Level 0 (3 rows) should be expanded (except for nodes which have no + // sub-rows). + assert.isTrue(isExpanded(rows[0] /* allocated */)); + assert.isFalse(isExpanded(rows[1] /* no_sub_rows */)); + assert.isTrue(isExpanded(rows[2] /* overhead */)); + + // Level 1 (4 rows) should be expanded (except for nodes which have no + // sub-rows). + assert.isFalse(isExpanded(rows[0].subRows[0] /* allocated/v8 */)); + assert.isTrue(isExpanded(rows[0].subRows[1] /* allocated/oilpan */)); + assert.isFalse(isExpanded(rows[2].subRows[0] /* fragmentation/internal */)); + assert.isTrue(isExpanded(rows[2].subRows[1] /* fragmentation/external */)); + + // Level 2 (3 rows) should not be expanded any more. + assert.isFalse(isExpanded( + rows[0].subRows[1].subRows[0] /* allocated/oilpan/still_visible */)); + assert.isFalse(isExpanded( + rows[0].subRows[1].subRows[1] /* allocated/oilpan/also_visible */)); + assert.isFalse(isExpanded( + rows[2].subRows[1].subRows[0] /* fragmentation/external/unexpanded */)); + }); + + test('checkMemoryCell_extractFields', function() { + assert.isUndefined(MemoryCell.extractFields(undefined)); + + assert.isUndefined(MemoryCell.extractFields(new MemoryCell(undefined))); + + const fields = [new Scalar(sizeInBytes_smallerIsBetter, 1024)]; + assert.strictEqual( + MemoryCell.extractFields(new MemoryCell(fields)), fields); + }); + + test('checkAggregateTableRowCellsRecursively', function() { + const row = { + testCells: { + a: buildScalarCell(sizeInBytes_smallerIsBetter, [17]) + }, + subRows: [ + { + // Intentionally no testCells. + subRows: [ + { + testCells: { + b: buildScalarCell(sizeInBytes_smallerIsBetter, [103]), + c: new MemoryCell(['should-not-propagate-upwards']), + d: buildScalarCell(sizeInBytes_smallerIsBetter, [-200]) + } + // Intentionally no subRows. + }, + { + testCells: {}, + subRows: [] + } + ], + contexts: ['skip-row-when-using-predicate'] + }, + { + testCells: { + b: buildScalarCell(sizeInBytes_smallerIsBetter, [20]), + a: buildScalarCell(sizeInBytes_smallerIsBetter, [13]), + e: buildScalarCell(sizeInBytes_smallerIsBetter, [-300]) + }, + contexts: ['don\'t-skip'] + } + ] + }; + + // Without a predicate. + const ca = new NumericMemoryColumn('column_a', ['testCells', 'a']); + const cb = new NumericMemoryColumn('column_b', ['testCells', 'b']); + const cc = new StringMemoryColumn('column_c', ['testCells', 'c']); + aggregateTableRowCellsRecursively(row, [ca, cb, cc]); + checkSizeNumericFields(row, ca, [17]); + checkSizeNumericFields(row, cb, [123]); + checkStringFields(row, cc, undefined); + + // With a predicate. + const cd = new NumericMemoryColumn('column_d', ['testCells', 'd']); + const ce = new NumericMemoryColumn('column_e', ['testCells', 'e']); + aggregateTableRowCellsRecursively(row, [cd, ce], function(contexts) { + return contexts === undefined || !contexts[0].startsWith('skip'); + }); + checkSizeNumericFields(row, cd, undefined); + checkSizeNumericFields(row, ce, [-300]); + }); + + test('checkAggregateTableRowCells', function() { + const row = { + // Intentionally no testCells. + otherCells: { + a: buildScalarCell(tr.b.Unit.byName.unitlessNumber, + [5, undefined, undefined]) + } + }; + const subRows = [ + { + testCells: { + a: buildScalarCell(sizeInBytes_smallerIsBetter, [1, 9]) + }, + subRows: [ + { + testCells: { + c: buildScalarCell(sizeInBytes_smallerIsBetter, [13]) + } + } + ] + }, + { + testCells: { + a: buildScalarCell(sizeInBytes_smallerIsBetter, [2, 17]), + b: buildScalarCell(sizeInBytes_smallerIsBetter, [5]) + }, + otherCells: { + a: buildScalarCell(tr.b.Unit.byName.unitlessNumber, + [153, undefined, 257]), + b: new MemoryCell(['field-should-not-propagate-upwards', '']) + } + } + ]; + + const cta = new NumericMemoryColumn('column_test_a', ['testCells', 'a']); + const ctb = new NumericMemoryColumn('column_test_b', ['testCells', 'b']); + const ctc = new NumericMemoryColumn('column_test_c', ['testCells', 'c']); + const coa = new NumericMemoryColumn('column_other_a', ['otherCells', 'a']); + const cob = new StringMemoryColumn('column_other_b', ['otherCells', 'b']); + + aggregateTableRowCells(row, subRows, [cta, ctb, ctc, coa, cob]); + + checkSizeNumericFields(row, cta, [3, 26]); + checkSizeNumericFields(row, ctb, [5]); + checkSizeNumericFields(row, ctc, undefined); + + checkNumericFields(row, coa, [5, undefined, 257], + tr.b.Unit.byName.unitlessNumber); + checkStringFields(row, cob, undefined); + }); + + test('checkCreateCells', function() { + const values = [ + { + a: 9, + b: 314 + }, + { + b: 159, + c: undefined + }, + undefined, + { + b: 265, + d: 0 + } + ]; + + const mockColumn = new MemoryColumn('', [], undefined); + + const cells = createCells(values, function(dict) { + const fields = {}; + for (const [key, value] of Object.entries(dict)) { + if (value === undefined) continue; + fields[key] = new Scalar(sizeInBytes_smallerIsBetter, value); + } + return fields; + }); + assert.deepEqual(Object.keys(cells), ['a', 'b', 'd']); + checkSizeNumericFields( + cells.a, mockColumn, [9, undefined, undefined, undefined]); + checkSizeNumericFields(cells.b, mockColumn, [314, 159, undefined, 265]); + checkSizeNumericFields( + cells.d, mockColumn, [undefined, undefined, undefined, 0]); + }); +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_vm_regions_details_pane.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_vm_regions_details_pane.html new file mode 100644 index 00000000000..2a20bb3c27e --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_vm_regions_details_pane.html @@ -0,0 +1,382 @@ +<!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/base/scalar.html"> +<link rel="import" href="/tracing/base/unit.html"> +<link rel="import" href="/tracing/base/utils.html"> +<link rel="import" href="/tracing/ui/analysis/memory_dump_sub_view_util.html"> +<link rel="import" href="/tracing/ui/analysis/stacked_pane.html"> +<link rel="import" href="/tracing/ui/base/table.html"> + +<dom-module id='tr-ui-a-memory-dump-vm-regions-details-pane'> + <template> + <style> + :host { + display: flex; + flex-direction: column; + } + + #label { + flex: 0 0 auto; + padding: 8px; + + background-color: #eee; + border-bottom: 1px solid #8e8e8e; + border-top: 1px solid white; + + font-size: 15px; + font-weight: bold; + } + + #contents { + flex: 1 0 auto; + align-self: stretch; + font-size: 12px; + } + + #info_text { + padding: 8px; + color: #666; + font-style: italic; + text-align: center; + } + + #table { + display: none; /* Hide until memory dumps are set. */ + flex: 1 0 auto; + align-self: stretch; + font-size: 12px; + } + </style> + <div id="label">Memory maps</div> + <div id="contents"> + <div id="info_text">No memory maps selected</div> + <tr-ui-b-table id="table"></tr-ui-b-table> + </div> + </template> +</dom-module> +<script> +'use strict'; + +tr.exportTo('tr.ui.analysis', function() { + const Scalar = tr.b.Scalar; + const sizeInBytes_smallerIsBetter = + tr.b.Unit.byName.sizeInBytes_smallerIsBetter; + + const CONSTANT_COLUMN_RULES = [ + { + condition: 'Start address', + importance: 0, + columnConstructor: tr.ui.analysis.StringMemoryColumn + } + ]; + + const VARIABLE_COLUMN_RULES = [ + { + condition: 'Virtual size', + importance: 7, + columnConstructor: tr.ui.analysis.DetailsNumericMemoryColumn + }, + { + condition: 'Protection flags', + importance: 6, + columnConstructor: tr.ui.analysis.StringMemoryColumn + }, + { + condition: 'PSS', + importance: 5, + columnConstructor: tr.ui.analysis.DetailsNumericMemoryColumn + }, + { + condition: 'Private dirty', + importance: 4, + columnConstructor: tr.ui.analysis.DetailsNumericMemoryColumn + }, + { + condition: 'Private clean', + importance: 3, + columnConstructor: tr.ui.analysis.DetailsNumericMemoryColumn + }, + { + condition: 'Shared dirty', + importance: 2, + columnConstructor: tr.ui.analysis.DetailsNumericMemoryColumn + }, + { + condition: 'Shared clean', + importance: 1, + columnConstructor: tr.ui.analysis.DetailsNumericMemoryColumn + }, + { + condition: 'Swapped', + importance: 0, + columnConstructor: tr.ui.analysis.DetailsNumericMemoryColumn + } + ]; + + const BYTE_STAT_COLUMN_MAP = { + 'proportionalResident': 'PSS', + 'privateDirtyResident': 'Private dirty', + 'privateCleanResident': 'Private clean', + 'sharedDirtyResident': 'Shared dirty', + 'sharedCleanResident': 'Shared clean', + 'swapped': 'Swapped' + }; + + function hexString(address, is64BitAddress) { + if (address === undefined) return undefined; + const hexPadding = is64BitAddress ? '0000000000000000' : '00000000'; + return (hexPadding + address.toString(16)).substr(-hexPadding.length); + } + + function pruneEmptyRuleRows(row) { + if (row.subRows === undefined || row.subRows.length === 0) return; + + // Either all sub-rows are rule rows, or all sub-rows are VM region rows. + if (row.subRows[0].rule === undefined) { + // VM region rows: Early out to avoid filtering a large array for + // performance reasons (no sub-rows would be removed, but the whole array + // would be unnecessarily copied to a new array). + return; + } + + row.subRows.forEach(pruneEmptyRuleRows); + row.subRows = row.subRows.filter(function(subRow) { + return subRow.subRows.length > 0; + }); + } + + Polymer({ + is: 'tr-ui-a-memory-dump-vm-regions-details-pane', + behaviors: [tr.ui.analysis.StackedPane], + + created() { + this.vmRegions_ = undefined; + this.aggregationMode_ = undefined; + }, + + ready() { + this.$.table.selectionMode = tr.ui.b.TableFormat.SelectionMode.ROW; + }, + + /** + * Sets the VM regions and schedules rebuilding the pane. + * + * The provided value should be a chronological list of lists of VM + * regions. All VM regions are assumed to belong to the same process. + * Example: + * + * [ + * [ + * // VM regions at timestamp 1. + * tr.model.VMRegion {}, + * tr.model.VMRegion {}, + * tr.model.VMRegion {} + * ], + * undefined, // No VM regions provided at timestamp 2. + * [ + * // VM regions at timestamp 3. + * tr.model.VMRegion {}, + * tr.model.VMRegion {} + * ] + * ] + */ + set vmRegions(vmRegions) { + this.vmRegions_ = vmRegions; + this.scheduleRebuild_(); + }, + + get vmRegions() { + return this.vmRegions_; + }, + + set aggregationMode(aggregationMode) { + this.aggregationMode_ = aggregationMode; + this.scheduleRebuild_(); + }, + + get aggregationMode() { + return this.aggregationMode_; + }, + + onRebuild_() { + if (this.vmRegions_ === undefined || this.vmRegions_.length === 0) { + // Show the info text (hide the table). + this.$.info_text.style.display = 'block'; + this.$.table.style.display = 'none'; + + this.$.table.clear(); + this.$.table.rebuild(); + return; + } + + // Show the table (hide the info text). + this.$.info_text.style.display = 'none'; + this.$.table.style.display = 'block'; + + const rows = this.createRows_(this.vmRegions_); + const columns = this.createColumns_(rows); + + // Note: There is no need to aggregate fields of the VM regions because + // the classification tree already takes care of that. + + this.$.table.tableRows = rows; + this.$.table.tableColumns = columns; + + // TODO(petrcermak): This can be quite slow. Consider doing this somehow + // asynchronously. + this.$.table.rebuild(); + + tr.ui.analysis.expandTableRowsRecursively(this.$.table); + }, + + createRows_(timeToVmRegionTree) { + // Determine if any start address is outside the 32-bit range. + const is64BitAddress = timeToVmRegionTree.some(function(vmRegionTree) { + if (vmRegionTree === undefined) return false; + return vmRegionTree.someRegion(function(region) { + if (region.startAddress === undefined) return false; + return region.startAddress >= 4294967296; /* 2^32 */ + }); + }); + + return [ + this.createClassificationNodeRow(timeToVmRegionTree, is64BitAddress) + ]; + }, + + createClassificationNodeRow(timeToNode, is64BitAddress) { + // Get any defined classification node so that we can extract the + // properties which don't change over time. + const definedNode = timeToNode.find(x => x); + + // Child node ID (list index) -> Timestamp (list index) -> + // VM region classification node. + const childNodeIdToTimeToNode = Object.values( + tr.b.invertArrayOfDicts(timeToNode, function(node) { + const children = node.children; + if (children === undefined) return undefined; + const childMap = {}; + children.forEach(function(childNode) { + if (!childNode.hasRegions) return; + childMap[childNode.title] = childNode; + }); + return childMap; + })); + const childNodeSubRows = childNodeIdToTimeToNode.map( + function(timeToChildNode) { + return this.createClassificationNodeRow( + timeToChildNode, is64BitAddress); + }, this); + + // Region ID (list index) -> Timestamp (list index) -> VM region. + const regionIdToTimeToRegion = Object.values( + tr.b.invertArrayOfDicts(timeToNode, function(node) { + const regions = node.regions; + if (regions === undefined) return undefined; + + const results = {}; + for (const region of regions) { + results[region.uniqueIdWithinProcess] = region; + } + return results; + })); + const regionSubRows = regionIdToTimeToRegion.map(function(timeToRegion) { + return this.createRegionRow_(timeToRegion, is64BitAddress); + }, this); + + const subRows = childNodeSubRows.concat(regionSubRows); + + return { + title: definedNode.title, + contexts: timeToNode, + variableCells: this.createVariableCells_(timeToNode), + subRows + }; + }, + + createRegionRow_(timeToRegion, is64BitAddress) { + // Get any defined VM region so that we can extract the properties which + // don't change over time. + const definedRegion = timeToRegion.find(x => x); + + return { + title: definedRegion.mappedFile, + contexts: timeToRegion, + constantCells: this.createConstantCells_(definedRegion, is64BitAddress), + variableCells: this.createVariableCells_(timeToRegion) + }; + }, + + /** + * Create cells for VM region properties which DON'T change over time. + * + * Note that there are currently no such properties of classification nodes. + */ + createConstantCells_(definedRegion, is64BitAddress) { + return tr.ui.analysis.createCells([definedRegion], function(region) { + const startAddress = region.startAddress; + if (startAddress === undefined) return undefined; + return { 'Start address': hexString(startAddress, is64BitAddress) }; + }); + }, + + /** + * Create cells for VM region (classification node) properties which DO + * change over time. + */ + createVariableCells_(timeToRegion) { + return tr.ui.analysis.createCells(timeToRegion, function(region) { + const fields = {}; + + const sizeInBytes = region.sizeInBytes; + if (sizeInBytes !== undefined) { + fields['Virtual size'] = new Scalar( + sizeInBytes_smallerIsBetter, sizeInBytes); + } + const protectionFlags = region.protectionFlagsToString; + if (protectionFlags !== undefined) { + fields['Protection flags'] = protectionFlags; + } + + for (const [byteStatName, columnName] of + Object.entries(BYTE_STAT_COLUMN_MAP)) { + const byteStat = region.byteStats[byteStatName]; + if (byteStat === undefined) continue; + fields[columnName] = new Scalar( + sizeInBytes_smallerIsBetter, byteStat); + } + + return fields; + }); + }, + + createColumns_(rows) { + const titleColumn = new tr.ui.analysis.TitleColumn('Mapped file'); + titleColumn.width = '200px'; + + const constantColumns = tr.ui.analysis.MemoryColumn.fromRows(rows, { + cellKey: 'constantCells', + aggregationMode: undefined, + rules: CONSTANT_COLUMN_RULES + }); + const variableColumns = tr.ui.analysis.MemoryColumn.fromRows(rows, { + cellKey: 'variableCells', + aggregationMode: this.aggregationMode_, + rules: VARIABLE_COLUMN_RULES + }); + const fieldColumns = constantColumns.concat(variableColumns); + tr.ui.analysis.MemoryColumn.spaceEqually(fieldColumns); + + const columns = [titleColumn].concat(fieldColumns); + return columns; + } + }); + + return {}; +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_vm_regions_details_pane_test.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_vm_regions_details_pane_test.html new file mode 100644 index 00000000000..7534727091b --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_vm_regions_details_pane_test.html @@ -0,0 +1,496 @@ +<!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/base/utils.html"> +<link rel="import" href="/tracing/core/test_utils.html"> +<link rel="import" href="/tracing/model/container_memory_dump.html"> +<link rel="import" href="/tracing/model/memory_dump_test_utils.html"> +<link rel="import" href="/tracing/model/vm_region.html"> +<link rel="import" + href="/tracing/ui/analysis/memory_dump_sub_view_test_utils.html"> +<link rel="import" href="/tracing/ui/analysis/memory_dump_sub_view_util.html"> +<link rel="import" + href="/tracing/ui/analysis/memory_dump_vm_regions_details_pane.html"> + +<script> +'use strict'; + +tr.b.unittest.testSuite(function() { + const newAllocatorDump = tr.model.MemoryDumpTestUtils.newAllocatorDump; + const VMRegion = tr.model.VMRegion; + const VMRegionClassificationNode = tr.model.VMRegionClassificationNode; + const TitleColumn = tr.ui.analysis.TitleColumn; + const StringMemoryColumn = tr.ui.analysis.StringMemoryColumn; + const NumericMemoryColumn = tr.ui.analysis.NumericMemoryColumn; + const AggregationMode = tr.ui.analysis.MemoryColumn.AggregationMode; + const addGlobalMemoryDump = tr.model.MemoryDumpTestUtils.addGlobalMemoryDump; + const addProcessMemoryDump = + tr.model.MemoryDumpTestUtils.addProcessMemoryDump; + const checkSizeNumericFields = tr.ui.analysis.checkSizeNumericFields; + const checkStringFields = tr.ui.analysis.checkStringFields; + const checkColumns = tr.ui.analysis.checkColumns; + const isElementDisplayed = tr.ui.analysis.isElementDisplayed; + const DETAILED = tr.model.ContainerMemoryDump.LevelOfDetail.DETAILED; + + function createVMRegions() { + const model = tr.c.TestUtils.newModel(function(model) { + const process = model.getOrCreateProcess(1); + + // First timestamp. + const gmd1 = addGlobalMemoryDump( + model, {ts: 42, levelOfDetail: DETAILED}); + const pmd1 = addProcessMemoryDump(gmd1, process, {ts: 42}); + pmd1.vmRegions = VMRegionClassificationNode.fromRegions([ + VMRegion.fromDict({ + mappedFile: '/lib/chrome.so', + startAddress: 65536, + sizeInBytes: 536870912, + protectionFlags: VMRegion.PROTECTION_FLAG_READ | + VMRegion.PROTECTION_FLAG_EXECUTE, + byteStats: { + proportionalResident: 8192 + } + }), + VMRegion.fromDict({ + mappedFile: '/usr/lib/x86_64-linux-gnu/libX11.so.6.3.0', + startAddress: 140296983150592, + sizeInBytes: 2097152, + protectionFlags: 0, + byteStats: { + proportionalResident: 0 + } + }), + VMRegion.fromDict({ + startAddress: 10995116277760, + sizeInBytes: 2147483648, + protectionFlags: VMRegion.PROTECTION_FLAG_READ | + VMRegion.PROTECTION_FLAG_WRITE, + byteStats: { + privateDirtyResident: 0, + swapped: 0 + } + }), + VMRegion.fromDict({ + startAddress: 12094627905536, + sizeInBytes: 2147483648, + protectionFlags: VMRegion.PROTECTION_FLAG_READ | + VMRegion.PROTECTION_FLAG_WRITE, + byteStats: { + privateDirtyResident: 0, + swapped: 0 + } + }), + VMRegion.fromDict({ + mappedFile: '/dev/ashmem/dalvik-zygote space', + startAddress: 13194139533312, + sizeInBytes: 100, + protectionFlags: VMRegion.PROTECTION_FLAG_READ | + VMRegion.PROTECTION_FLAG_EXECUTE, + byteStats: { + proportionalResident: 100, + privateDirtyResident: 0, + swapped: 0 + } + }), + VMRegion.fromDict({ + mappedFile: '/dev/ashmem/libc malloc', + startAddress: 14293651161088, + sizeInBytes: 200, + protectionFlags: VMRegion.PROTECTION_FLAG_READ | + VMRegion.PROTECTION_FLAG_EXECUTE, + byteStats: { + proportionalResident: 200, + privateDirtyResident: 96, + swapped: 0 + } + }) + ]); + + // This is here so that we could test that tracing is discounted from the + // 'Native heap' category. + pmd1.memoryAllocatorDumps = [ + newAllocatorDump(pmd1, 'tracing', + {numerics: {size: 500, resident_size: 32}}) + ]; + + // Second timestamp. + const gmd2 = addGlobalMemoryDump( + model, {ts: 42, levelOfDetail: DETAILED}); + const pmd2 = addProcessMemoryDump(gmd2, process, {ts: 42}); + pmd2.vmRegions = VMRegionClassificationNode.fromRegions([ + VMRegion.fromDict({ + mappedFile: '/lib/chrome.so', + startAddress: 65536, + sizeInBytes: 536870912, + protectionFlags: VMRegion.PROTECTION_FLAG_READ | + VMRegion.PROTECTION_FLAG_EXECUTE, + byteStats: { + proportionalResident: 9216 + } + }), + VMRegion.fromDict({ + mappedFile: '/lib/chrome.so', + startAddress: 140296983150592, + sizeInBytes: 536870912, + protectionFlags: VMRegion.PROTECTION_FLAG_READ | + VMRegion.PROTECTION_FLAG_EXECUTE, + byteStats: { + proportionalResident: 10240 + } + }), + VMRegion.fromDict({ + startAddress: 10995116277760, + sizeInBytes: 2147483648, + protectionFlags: VMRegion.PROTECTION_FLAG_READ | + VMRegion.PROTECTION_FLAG_WRITE, + byteStats: { + privateDirtyResident: 0, + swapped: 32 + } + }), + VMRegion.fromDict({ + startAddress: 12094627905536, + sizeInBytes: 2147483648, + protectionFlags: VMRegion.PROTECTION_FLAG_READ | + VMRegion.PROTECTION_FLAG_WRITE, + byteStats: { + privateDirtyResident: 0, + swapped: 0 + } + }), + VMRegion.fromDict({ + mappedFile: '/dev/ashmem/dalvik-zygote space', + startAddress: 13194139533312, + sizeInBytes: 100, + protectionFlags: VMRegion.PROTECTION_FLAG_READ | + VMRegion.PROTECTION_FLAG_EXECUTE, + byteStats: { + proportionalResident: 0, + privateDirtyResident: 100, + swapped: 0 + } + }), + VMRegion.fromDict({ + mappedFile: '/dev/ashmem/libc malloc', + startAddress: 14293651161088, + sizeInBytes: 200, + protectionFlags: VMRegion.PROTECTION_FLAG_READ | + VMRegion.PROTECTION_FLAG_EXECUTE, + byteStats: { + proportionalResident: 100, + privateDirtyResident: 96, + swapped: 0 + } + }), + VMRegion.fromDict({ + mappedFile: '/usr/share/fonts/DejaVuSansMono.ttf', + startAddress: 140121259503616, + sizeInBytes: 335872, + protectionFlags: VMRegion.PROTECTION_FLAG_READ, + byteStats: { + proportionalResident: 22528 + } + }), + VMRegion.fromDict({ + mappedFile: 'another-map', + startAddress: 52583094233905872, + sizeInBytes: 1, + byteStats: { + proportionalResident: 1, + privateDirtyResident: 1, + swapped: 1 + } + }) + ]); + }); + + return model.processes[1].memoryDumps.map(function(pmd) { + return pmd.mostRecentVmRegions; + }); + } + + const EXPECTED_COLUMNS = [ + { title: 'Mapped file', type: TitleColumn, noAggregation: true }, + { title: 'Start address', type: StringMemoryColumn, noAggregation: true }, + { title: 'Virtual size', type: NumericMemoryColumn }, + { title: 'Protection flags', type: StringMemoryColumn }, + { title: 'PSS', type: NumericMemoryColumn }, + { title: 'Private dirty', type: NumericMemoryColumn }, + { title: 'Swapped', type: NumericMemoryColumn } + ]; + + function checkRow(columns, row, expectedTitle, expectedStartAddress, + expectedVirtualSize, expectedProtectionFlags, + expectedProportionalResidentValues, expectedPrivateDirtyResidentValues, + expectedSwappedValues, expectedSubRowCount, expectedContexts) { + assert.strictEqual(columns[0].formatTitle(row), expectedTitle); + checkStringFields(row, columns[1], expectedStartAddress); + checkSizeNumericFields(row, columns[2], expectedVirtualSize); + checkStringFields(row, columns[3], expectedProtectionFlags); + checkSizeNumericFields(row, columns[4], expectedProportionalResidentValues); + checkSizeNumericFields(row, columns[5], expectedPrivateDirtyResidentValues); + checkSizeNumericFields(row, columns[6], expectedSwappedValues); + + if (expectedSubRowCount === undefined) { + assert.isUndefined(row.subRows); + } else { + assert.lengthOf(row.subRows, expectedSubRowCount); + } + + if (typeof expectedContexts === 'function') { + expectedContexts(row.contexts); + } else if (expectedContexts !== undefined) { + assert.deepEqual(Array.from(row.contexts), expectedContexts); + } else { + assert.isUndefined(row.contexts); + } + } + + function genericMatcher(callback, defined) { + return function(actualValues) { + assert.lengthOf(actualValues, defined.length); + for (let i = 0; i < defined.length; i++) { + const actualValue = actualValues[i]; + if (defined[i]) { + callback(actualValue); + } else { + assert.isUndefined(actualValue); + } + } + }; + } + + function vmRegionsMatcher(expectedMappedFile, expectedStartAddress, defined) { + return genericMatcher(function(actualRegion) { + assert.instanceOf(actualRegion, VMRegion); + assert.strictEqual(actualRegion.mappedFile, expectedMappedFile); + assert.strictEqual(actualRegion.startAddress, expectedStartAddress); + }, defined); + } + + function classificationNodesMatcher(expectedTitle, defined) { + return genericMatcher(function(actualNode) { + assert.instanceOf(actualNode, VMRegionClassificationNode); + assert.strictEqual(actualNode.title, expectedTitle); + }, defined); + } + + test('instantiate_empty', function() { + tr.ui.analysis.createAndCheckEmptyPanes(this, + 'tr-ui-a-memory-dump-vm-regions-details-pane', 'vmRegions', + function(viewEl) { + // Check that the info text is shown. + assert.isTrue(isElementDisplayed(viewEl.$.info_text)); + assert.isFalse(isElementDisplayed(viewEl.$.table)); + }); + }); + + test('instantiate_single', function() { + const vmRegions = createVMRegions().slice(0, 1); + + const viewEl = document.createElement( + 'tr-ui-a-memory-dump-vm-regions-details-pane'); + viewEl.vmRegions = vmRegions; + viewEl.rebuild(); + this.addHTMLOutput(viewEl); + + // Check that the table is shown. + assert.isTrue(isElementDisplayed(viewEl.$.table)); + assert.isFalse(isElementDisplayed(viewEl.$.info_text)); + + const table = viewEl.$.table; + const columns = table.tableColumns; + checkColumns(columns, EXPECTED_COLUMNS, undefined /* no aggregation */); + const rows = table.tableRows; + assert.lengthOf(rows, 1); + + // Check the rows of the table. + const totalRow = rows[0]; + checkRow(columns, totalRow, 'Total', undefined, [4833935160], undefined, + [8460], [64], [0], 3, vmRegions); + + const androidRow = totalRow.subRows[0]; + checkRow(columns, androidRow, 'Android', undefined, [100], undefined, + [100], [0], [0], 1, classificationNodesMatcher('Android', [true])); + + const javaRuntimeRow = androidRow.subRows[0]; + checkRow(columns, javaRuntimeRow, 'Java runtime', undefined, [100], + undefined, [100], [0], [0], 1, + classificationNodesMatcher('Java runtime', [true])); + + const spacesRow = javaRuntimeRow.subRows[0]; + checkRow(columns, spacesRow, 'Spaces', undefined, [100], undefined, [100], + [0], [0], 1, classificationNodesMatcher('Spaces', [true])); + + const nativeHeapRow = totalRow.subRows[1]; + checkRow(columns, nativeHeapRow, 'Native heap', undefined, [4294966996], + undefined, [168], [64], [0], 4, + classificationNodesMatcher('Native heap', [true])); + + const discountedTracingOverheadRow = nativeHeapRow.subRows[3]; + checkRow(columns, discountedTracingOverheadRow, + '[discounted tracing overhead]', undefined, [-500], undefined, [-32], + [-32], undefined, undefined, + vmRegionsMatcher('[discounted tracing overhead]', undefined, [true])); + + const filesRow = totalRow.subRows[2]; + checkRow(columns, filesRow, 'Files', undefined, [538968064], undefined, + [8192], undefined, undefined, 1, + classificationNodesMatcher('Files', [true])); + + const soRow = filesRow.subRows[0]; + checkRow(columns, soRow, 'so', undefined, [538968064], undefined, + [8192], undefined, undefined, 2, + classificationNodesMatcher('so', [true])); + + const mmapChromeRow = soRow.subRows[0]; + checkRow(columns, mmapChromeRow, '/lib/chrome.so', ['0000000000010000'], + [536870912], ['r-xp'], [8192], undefined, undefined, undefined, + vmRegionsMatcher('/lib/chrome.so', 65536, [true])); + + const mmapLibX11Row = soRow.subRows[1]; + checkRow(columns, mmapLibX11Row, + '/usr/lib/x86_64-linux-gnu/libX11.so.6.3.0', ['00007f996fd80000'], + [2097152], ['---p'], [0], undefined, undefined, undefined, + vmRegionsMatcher('/usr/lib/x86_64-linux-gnu/libX11.so.6.3.0', + 140296983150592, [true])); + }); + + test('instantiate_multipleDiff', function() { + const vmRegions = createVMRegions(); + + const viewEl = document.createElement( + 'tr-ui-a-memory-dump-vm-regions-details-pane'); + viewEl.vmRegions = vmRegions; + viewEl.aggregationMode = AggregationMode.DIFF; + viewEl.rebuild(); + this.addHTMLOutput(viewEl); + + // Check that the table is shown. + assert.isTrue(isElementDisplayed(viewEl.$.table)); + assert.isFalse(isElementDisplayed(viewEl.$.info_text)); + + const table = viewEl.$.table; + const columns = table.tableColumns; + checkColumns(columns, EXPECTED_COLUMNS, AggregationMode.DIFF); + const rows = table.tableRows; + assert.lengthOf(rows, 1); + + // Check the rows of the table. + const totalRow = rows[0]; + checkRow(columns, totalRow, 'Total', undefined, [4833935160, 5369045293], + undefined, [8460, 42085], [64, 197], [0, 33], 4, vmRegions); + + const androidRow = totalRow.subRows[0]; + checkRow(columns, androidRow, 'Android', undefined, [100, 100], undefined, + [100, 0], [0, 100], [0, 0], 1, + classificationNodesMatcher('Android', [true, true])); + + const javaRuntimeRow = androidRow.subRows[0]; + checkRow(columns, javaRuntimeRow, 'Java runtime', undefined, [100, 100], + undefined, [100, 0], [0, 100], [0, 0], 1, + classificationNodesMatcher('Java runtime', [true, true])); + + const spacesRow = javaRuntimeRow.subRows[0]; + checkRow(columns, spacesRow, 'Spaces', undefined, [100, 100], undefined, + [100, 0], [0, 100], [0, 0], 1, + classificationNodesMatcher('Spaces', [true, true])); + + const nativeHeapRow = totalRow.subRows[1]; + checkRow(columns, nativeHeapRow, 'Native heap', undefined, + [4294966996, 4294967496], undefined, [168, 100], [64, 96], [0, 32], 4, + classificationNodesMatcher('Native heap', [true, true])); + + const discountedTracingOverheadRow = nativeHeapRow.subRows[3]; + checkRow(columns, discountedTracingOverheadRow, + '[discounted tracing overhead]', undefined, [-500, undefined], + undefined, [-32, undefined], [-32, undefined], undefined, undefined, + vmRegionsMatcher('[discounted tracing overhead]', undefined, + [true, false])); + + const filesRow = totalRow.subRows[2]; + checkRow(columns, filesRow, 'Files', undefined, [538968064, 1074077696], + undefined, [8192, 41984], undefined, undefined, 2, + classificationNodesMatcher('Files', [true, true])); + + const soRow = filesRow.subRows[0]; + checkRow(columns, soRow, 'so', undefined, [538968064, 1073741824], + undefined, [8192, 19456], undefined, undefined, 3, + classificationNodesMatcher('so', [true, true])); + + const mmapChromeRow = soRow.subRows[0]; + checkRow(columns, mmapChromeRow, '/lib/chrome.so', ['0000000000010000'], + [536870912, 536870912], ['r-xp', 'r-xp'], [8192, 9216], undefined, + undefined, undefined, + vmRegionsMatcher('/lib/chrome.so', 65536, [true, true])); + + const mmapLibX11Row = soRow.subRows[1]; + checkRow(columns, mmapLibX11Row, + '/usr/lib/x86_64-linux-gnu/libX11.so.6.3.0', ['00007f996fd80000'], + [2097152, undefined], ['---p', undefined], [0, undefined], undefined, + undefined, undefined, + vmRegionsMatcher('/usr/lib/x86_64-linux-gnu/libX11.so.6.3.0', + 140296983150592, [true, false])); + + const otherRow = totalRow.subRows[3]; + checkRow(columns, otherRow, 'Other', undefined, [undefined, 1], undefined, + [undefined, 1], [undefined, 1], [undefined, 1], 1, + classificationNodesMatcher('Other', [false, true])); + + const anotherMapRow = otherRow.subRows[0]; + checkRow(columns, anotherMapRow, 'another-map', ['00bad00bad00bad0'], + [undefined, 1], undefined, [undefined, 1], [undefined, 1], + [undefined, 1], undefined, + vmRegionsMatcher('another-map', 52583094233905872, [false, true])); + }); + + test('instantiate_multipleMax', function() { + const vmRegions = createVMRegions(); + + const viewEl = document.createElement( + 'tr-ui-a-memory-dump-vm-regions-details-pane'); + viewEl.vmRegions = vmRegions; + viewEl.aggregationMode = AggregationMode.MAX; + viewEl.rebuild(); + this.addHTMLOutput(viewEl); + + // Check that the table is shown. + assert.isTrue(isElementDisplayed(viewEl.$.table)); + assert.isFalse(isElementDisplayed(viewEl.$.info_text)); + + // Just check that the aggregation mode was propagated to the columns. + const table = viewEl.$.table; + const columns = table.tableColumns; + checkColumns(columns, EXPECTED_COLUMNS, AggregationMode.MAX); + const rows = table.tableRows; + assert.lengthOf(rows, 1); + }); + + test('instantiate_multipleWithUndefined', function() { + const vmRegions = createVMRegions(); + vmRegions.splice(1, 0, undefined); + + const viewEl = document.createElement( + 'tr-ui-a-memory-dump-vm-regions-details-pane'); + viewEl.vmRegions = vmRegions; + viewEl.aggregationMode = AggregationMode.DIFF; + viewEl.rebuild(); + this.addHTMLOutput(viewEl); + + // Check that the table is shown. + assert.isTrue(isElementDisplayed(viewEl.$.table)); + assert.isFalse(isElementDisplayed(viewEl.$.info_text)); + + // Just check that the table has the right shape. + const table = viewEl.$.table; + const columns = table.tableColumns; + checkColumns(columns, EXPECTED_COLUMNS, AggregationMode.DIFF); + const rows = table.tableRows; + assert.lengthOf(rows, 1); + }); +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_async_slice_sub_view.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_async_slice_sub_view.html new file mode 100644 index 00000000000..0bb39b7e0f2 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_async_slice_sub_view.html @@ -0,0 +1,79 @@ +<!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/model/event_set.html"> +<link rel="import" href="/tracing/ui/analysis/analysis_sub_view.html"> +<link rel="import" href="/tracing/ui/analysis/multi_event_sub_view.html"> +<link rel="import" href="/tracing/ui/analysis/related_events.html"> + +<dom-module id='tr-ui-a-multi-async-slice-sub-view'> + <template> + <style> + :host { + display: flex; + } + #container { + display: flex; + flex: 1 1 auto; + } + #events { + margin-left: 8px; + flex: 0 1 200px; + } + </style> + <div id="container"> + <tr-ui-a-multi-event-sub-view id="content"></tr-ui-a-multi-event-sub-view> + <div id="events"> + <tr-ui-a-related-events id="relatedEvents"></tr-ui-a-related-events> + </div> + </div> + </template> +</dom-module> +<script> +'use strict'; + +Polymer({ + is: 'tr-ui-a-multi-async-slice-sub-view', + behaviors: [tr.ui.analysis.AnalysisSubView], + + get selection() { + return this.$.content.selection; + }, + + set selection(selection) { + this.$.content.selection = selection; + this.$.relatedEvents.setRelatedEvents(selection); + if (this.$.relatedEvents.hasRelatedEvents()) { + this.$.relatedEvents.style.display = ''; + } else { + this.$.relatedEvents.style.display = 'none'; + } + }, + + get relatedEventsToHighlight() { + if (!this.$.content.selection) return undefined; + + const selection = new tr.model.EventSet(); + this.$.content.selection.forEach(function(asyncEvent) { + if (!asyncEvent.associatedEvents) return; + + asyncEvent.associatedEvents.forEach(function(event) { + selection.push(event); + }); + }); + if (selection.length) return selection; + return undefined; + } +}); +tr.ui.analysis.AnalysisSubView.register( + 'tr-ui-a-multi-async-slice-sub-view', + tr.model.AsyncSlice, + { + multi: true, + title: 'Async Slices', + }); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_async_slice_sub_view_test.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_async_slice_sub_view_test.html new file mode 100644 index 00000000000..20fb52c058f --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_async_slice_sub_view_test.html @@ -0,0 +1,47 @@ +<!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/core/test_utils.html"> +<link rel="import" href="/tracing/model/event_set.html"> +<link rel="import" href="/tracing/model/model.html"> +<link rel="import" href="/tracing/ui/analysis/multi_async_slice_sub_view.html"> + +<script> +'use strict'; + +tr.b.unittest.testSuite(function() { + const newAsyncSliceEx = tr.c.TestUtils.newAsyncSliceEx; + + test('instantiate', function() { + const model = new tr.Model(); + const p1 = model.getOrCreateProcess(1); + const t1 = p1.getOrCreateThread(1); + t1.asyncSliceGroup.push(newAsyncSliceEx({ + title: 'a', + start: 10, + end: 20, + startThread: t1, + endThread: t1 + })); + t1.asyncSliceGroup.push(newAsyncSliceEx({ + title: 'b', + start: 25, + end: 40, + startThread: t1, + endThread: t1 + })); + + const selection = new tr.model.EventSet(); + selection.push(t1.asyncSliceGroup.slices[0]); + selection.push(t1.asyncSliceGroup.slices[1]); + + const viewEl = document.createElement('tr-ui-a-multi-async-slice-sub-view'); + viewEl.selection = selection; + this.addHTMLOutput(viewEl); + }); +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_cpu_slice_sub_view.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_cpu_slice_sub_view.html new file mode 100644 index 00000000000..4525df0e8c2 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_cpu_slice_sub_view.html @@ -0,0 +1,51 @@ +<!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/ui/analysis/analysis_sub_view.html"> +<link rel="import" href="/tracing/ui/analysis/multi_event_sub_view.html"> + +<dom-module id='tr-ui-a-multi-cpu-slice-sub-view'> + <template> + <style> + :host { + display: flex; + } + #content { + flex: 1 1 auto; + } + </style> + <tr-ui-a-multi-event-sub-view id="content"></tr-ui-a-multi-event-sub-view> + </template> +</dom-module> +<script> +'use strict'; + +Polymer({ + is: 'tr-ui-a-multi-cpu-slice-sub-view', + behaviors: [tr.ui.analysis.AnalysisSubView], + + ready() { + this.$.content.eventsHaveSubRows = false; + }, + + get selection() { + return this.$.content.selection; + }, + + set selection(selection) { + this.$.content.setSelectionWithoutErrorChecks(selection); + } +}); + +tr.ui.analysis.AnalysisSubView.register( + 'tr-ui-a-multi-cpu-slice-sub-view', + tr.model.CpuSlice, + { + multi: true, + title: 'CPU Slices', + }); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_cpu_slice_sub_view_test.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_cpu_slice_sub_view_test.html new file mode 100644 index 00000000000..36dc99bd338 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_cpu_slice_sub_view_test.html @@ -0,0 +1,48 @@ +<!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/core/test_utils.html"> +<link rel="import" href="/tracing/extras/importer/linux_perf/ftrace_importer.html"> +<link rel="import" href="/tracing/model/event_set.html"> +<link rel="import" href="/tracing/model/model.html"> +<link rel="import" href="/tracing/ui/analysis/multi_cpu_slice_sub_view.html"> + +<script> +'use strict'; + +tr.b.unittest.testSuite(function() { + function createBasicModel() { + const lines = [ + 'Android.launcher-584 [001] d..3 12622.506890: sched_switch: prev_comm=Android.launcher prev_pid=584 prev_prio=120 prev_state=R+ ==> next_comm=Binder_1 next_pid=217 next_prio=120', // @suppress longLineCheck + ' Binder_1-217 [001] d..3 12622.506918: sched_switch: prev_comm=Binder_1 prev_pid=217 prev_prio=120 prev_state=D ==> next_comm=Android.launcher next_pid=584 next_prio=120', // @suppress longLineCheck + 'Android.launcher-584 [001] d..4 12622.506936: sched_wakeup: comm=Binder_1 pid=217 prio=120 success=1 target_cpu=001', // @suppress longLineCheck + 'Android.launcher-584 [001] d..3 12622.506950: sched_switch: prev_comm=Android.launcher prev_pid=584 prev_prio=120 prev_state=R+ ==> next_comm=Binder_1 next_pid=217 next_prio=120', // @suppress longLineCheck + ' Binder_1-217 [001] ...1 12622.507057: tracing_mark_write: B|128|queueBuffer', // @suppress longLineCheck + ' Binder_1-217 [001] ...1 12622.507175: tracing_mark_write: E', + ' Binder_1-217 [001] d..3 12622.507253: sched_switch: prev_comm=Binder_1 prev_pid=217 prev_prio=120 prev_state=S ==> next_comm=Android.launcher next_pid=584 next_prio=120' // @suppress longLineCheck + ]; + + return tr.c.TestUtils.newModelWithEvents([lines.join('\n')], { + shiftWorldToZero: false + }); + } + + test('instantiate', function() { + const m = createBasicModel(); + const cpu = m.kernel.cpus[1]; + assert.isDefined(cpu); + + const selection = new tr.model.EventSet(); + selection.push(cpu.slices[0]); + selection.push(cpu.slices[1]); + + const viewEl = document.createElement('tr-ui-a-multi-cpu-slice-sub-view'); + viewEl.selection = selection; + this.addHTMLOutput(viewEl); + }); +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_event_sub_view.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_event_sub_view.html new file mode 100644 index 00000000000..52908c620bc --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_event_sub_view.html @@ -0,0 +1,211 @@ +<!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/base.html"> +<link rel="import" href="/tracing/ui/analysis/analysis_sub_view.html"> +<link rel="import" href="/tracing/ui/analysis/multi_event_summary_table.html"> +<link rel="import" href="/tracing/ui/analysis/selection_summary_table.html"> +<link rel="import" href="/tracing/ui/base/radio_picker.html"> +<link rel="import" href="/tracing/ui/base/table.html"> +<link rel="import" href="/tracing/ui/base/ui.html"> +<link rel="import" href="/tracing/value/diagnostics/scalar.html"> +<link rel="import" href="/tracing/value/histogram.html"> +<link rel="import" href="/tracing/value/ui/histogram_span.html"> + +<dom-module id='tr-ui-a-multi-event-sub-view'> + <template> + <style> + :host { + display: flex; + overflow: auto; + } + #content { + display: flex; + flex-direction: column; + flex: 0 1 auto; + align-self: stretch; + } + #content > * { + flex: 0 0 auto; + align-self: stretch; + } + #histogramContainer { + display: flex; + } + + tr-ui-a-multi-event-summary-table { + border-bottom: 1px solid #aaa; + } + + tr-ui-a-selection-summary-table { + margin-top: 1.25em; + border-top: 1px solid #aaa; + background-color: #eee; + font-weight: bold; + margin-bottom: 1.25em; + border-bottom: 1px solid #aaa; + } + </style> + <div id="content"> + <tr-ui-a-multi-event-summary-table id="eventSummaryTable"> + </tr-ui-a-multi-event-summary-table> + <tr-ui-a-selection-summary-table id="selectionSummaryTable"> + </tr-ui-a-selection-summary-table> + <tr-ui-b-radio-picker id="radioPicker"> + </tr-ui-b-radio-picker> + <div id="histogramContainer"> + <tr-v-ui-histogram-span id="histogramSpan"> + </tr-v-ui-histogram-span> + </div> + </div> + </template> +</dom-module> +<script> +'use strict'; + +tr.exportTo('tr.ui.analysis', function() { + const EVENT_FIELD = [ + {key: 'start', label: 'Start'}, + {key: 'cpuDuration', label: 'CPU Duration'}, + {key: 'duration', label: 'Duration'}, + {key: 'cpuSelfTime', label: 'CPU Self Time'}, + {key: 'selfTime', label: 'Self Time'} + ]; + + function buildDiagnostics_(slice) { + const diagnostics = {}; + for (const item of EVENT_FIELD) { + const fieldName = item.key; + if (slice[fieldName] === undefined) continue; + diagnostics[fieldName] = new tr.v.d.Scalar(new tr.b.Scalar( + tr.b.Unit.byName.timeDurationInMs, slice[fieldName])); + } + diagnostics.args = new tr.v.d.GenericSet([slice.args]); + diagnostics.event = new tr.v.d.RelatedEventSet(slice); + return diagnostics; + } + + Polymer({ + is: 'tr-ui-a-multi-event-sub-view', + behaviors: [tr.ui.analysis.AnalysisSubView], + + created() { + this.currentSelection_ = undefined; + this.eventsHaveDuration_ = true; + this.eventsHaveSubRows_ = true; + }, + + ready() { + this.$.radioPicker.style.display = 'none'; + this.$.radioPicker.items = EVENT_FIELD; + this.$.radioPicker.select('cpuSelfTime'); + this.$.radioPicker.addEventListener('change', () => { + if (this.isAttached) this.updateContents_(); + }); + + this.$.histogramSpan.graphWidth = 400; + this.$.histogramSpan.canMergeSampleDiagnostics = false; + this.$.histogramContainer.style.display = 'none'; + }, + + attached() { + if (this.currentSelection_ !== undefined) this.updateContents_(); + }, + + set selection(selection) { + if (selection.length <= 1) { + throw new Error('Only supports multiple items'); + } + this.setSelectionWithoutErrorChecks(selection); + }, + + get selection() { + return this.currentSelection_; + }, + + setSelectionWithoutErrorChecks(selection) { + this.currentSelection_ = selection; + if (this.isAttached) this.updateContents_(); + }, + + get eventsHaveDuration() { + return this.eventsHaveDuration_; + }, + + set eventsHaveDuration(eventsHaveDuration) { + this.eventsHaveDuration_ = eventsHaveDuration; + if (this.isAttached) this.updateContents_(); + }, + + get eventsHaveSubRows() { + return this.eventsHaveSubRows_; + }, + + set eventsHaveSubRows(eventsHaveSubRows) { + this.eventsHaveSubRows_ = eventsHaveSubRows; + if (this.isAttached) this.updateContents_(); + }, + + buildHistogram_(selectedKey) { + let leftBoundary = Number.MAX_VALUE; + let rightBoundary = tr.b.math.Statistics.percentile( + this.currentSelection_, 0.95, + function(value) { + leftBoundary = Math.min(leftBoundary, value[selectedKey]); + return value[selectedKey]; + }); + + if (leftBoundary === rightBoundary) rightBoundary += 1; + const histogram = new tr.v.Histogram( + '', + tr.b.Unit.byName.timeDurationInMs, + tr.v.HistogramBinBoundaries.createLinear( + leftBoundary, rightBoundary, + Math.ceil(Math.sqrt(this.currentSelection_.length)))); + histogram.customizeSummaryOptions({sum: false}); + for (const slice of this.currentSelection_) { + histogram.addSample(slice[selectedKey], + buildDiagnostics_(slice)); + } + + return histogram; + }, + + updateContents_() { + const selection = this.currentSelection_; + if (!selection) return; + + const eventsByTitle = selection.getEventsOrganizedByTitle(); + const numTitles = Object.keys(eventsByTitle).length; + + this.$.eventSummaryTable.configure({ + showTotals: numTitles > 1, + eventsByTitle, + eventsHaveDuration: this.eventsHaveDuration_, + eventsHaveSubRows: this.eventsHaveSubRows_ + }); + + this.$.selectionSummaryTable.selection = this.currentSelection_; + + if (numTitles === 1) { + this.$.radioPicker.style.display = 'block'; + this.$.histogramContainer.style.display = 'flex'; + this.$.histogramSpan.build( + this.buildHistogram_(this.$.radioPicker.selectedKey)); + if (this.$.histogramSpan.histogram.numValues === 0) { + this.$.histogramContainer.style.display = 'none'; + } + } else { + this.$.radioPicker.style.display = 'none'; + this.$.histogramContainer.style.display = 'none'; + } + } + }); + + return {}; +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_event_sub_view_test.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_event_sub_view_test.html new file mode 100644 index 00000000000..9958b7db81c --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_event_sub_view_test.html @@ -0,0 +1,94 @@ +<!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/core/test_utils.html"> +<link rel="import" href="/tracing/model/event_set.html"> +<link rel="import" href="/tracing/model/model.html"> +<link rel="import" href="/tracing/ui/analysis/multi_event_sub_view.html"> +<link rel="import" href="/tracing/ui/base/deep_utils.html"> + +<script> +'use strict'; + +tr.b.unittest.testSuite(function() { + const Model = tr.Model; + const Thread = tr.model.Thread; + const EventSet = tr.model.EventSet; + const newSliceEx = tr.c.TestUtils.newSliceEx; + + test('differentTitles', function() { + const model = new Model(); + const t53 = model.getOrCreateProcess(52).getOrCreateThread(53); + t53.sliceGroup.pushSlice(newSliceEx( + {title: 'a', start: 0.0, duration: 0.04})); + t53.sliceGroup.pushSlice(newSliceEx( + {title: 'a', start: 0.12, duration: 0.06})); + t53.sliceGroup.pushSlice(newSliceEx( + {title: 'aa', start: 0.5, duration: 0.5})); + t53.sliceGroup.createSubSlices(); + + const t53track = {}; + t53track.thread = t53; + + const selection = new EventSet(); + selection.push(t53.sliceGroup.slices[0]); + selection.push(t53.sliceGroup.slices[1]); + selection.push(t53.sliceGroup.slices[2]); + + const viewEl = document.createElement('tr-ui-a-multi-event-sub-view'); + viewEl.selection = selection; + this.addHTMLOutput(viewEl); + + const summaryTableEl = tr.ui.b.findDeepElementMatching( + viewEl, 'tr-ui-a-multi-event-summary-table'); + assert.isTrue(summaryTableEl.showTotals); + assert.lengthOf(Object.keys(summaryTableEl.eventsByTitle), 2); + + const selectionSummaryTableEl = tr.ui.b.findDeepElementMatching( + viewEl, 'tr-ui-a-selection-summary-table'); + assert.strictEqual(selectionSummaryTableEl.selection, selection); + + const radioPickerEl = + tr.ui.b.findDeepElementMatching(viewEl, 'tr-ui-b-radio-picker'); + assert.strictEqual(radioPickerEl.style.display, 'none'); + }); + + test('sameTitles', function() { + const model = new Model(); + const t53 = model.getOrCreateProcess(52).getOrCreateThread(53); + t53.sliceGroup.pushSlice(newSliceEx( + {title: 'c', start: 0.0, duration: 0.04})); + t53.sliceGroup.pushSlice(newSliceEx( + {title: 'c', start: 0.12, duration: 0.06})); + t53.sliceGroup.createSubSlices(); + + const t53track = {}; + t53track.thread = t53; + + const selection = new EventSet(); + selection.push(t53.sliceGroup.slices[0]); + selection.push(t53.sliceGroup.slices[1]); + + const viewEl = document.createElement('tr-ui-a-multi-event-sub-view'); + viewEl.selection = selection; + this.addHTMLOutput(viewEl); + + const summaryTableEl = tr.ui.b.findDeepElementMatching( + viewEl, 'tr-ui-a-multi-event-summary-table'); + assert.isFalse(summaryTableEl.showTotals); + assert.lengthOf(Object.keys(summaryTableEl.eventsByTitle), 1); + + const selectionSummaryTableEl = tr.ui.b.findDeepElementMatching( + viewEl, 'tr-ui-a-selection-summary-table'); + assert.strictEqual(selectionSummaryTableEl.selection, selection); + + const radioPickerEl = + tr.ui.b.findDeepElementMatching(viewEl, 'tr-ui-b-radio-picker'); + assert.strictEqual(radioPickerEl.style.display, 'block'); + }); +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_event_summary.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_event_summary.html new file mode 100644 index 00000000000..886e315863e --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_event_summary.html @@ -0,0 +1,207 @@ +<!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/base.html"> +<link rel="import" href="/tracing/base/math/statistics.html"> +<link rel="import" href="/tracing/base/utils.html"> + +<script> +'use strict'; +tr.exportTo('tr.ui.analysis', function() { + function MultiEventSummary(title, events) { + this.title = title; + this.duration_ = undefined; + this.selfTime_ = undefined; + this.events_ = events; + + this.cpuTimesComputed_ = false; + this.cpuSelfTime_ = undefined; + this.cpuDuration_ = undefined; + + this.maxDuration_ = undefined; + this.maxCpuDuration_ = undefined; + this.maxSelfTime_ = undefined; + this.maxCpuSelfTime_ = undefined; + + this.untotallableArgs_ = []; + this.totalledArgs_ = undefined; + } + MultiEventSummary.prototype = { + + set title(title) { + if (title === 'Totals') { + this.totalsRow = true; + } + this.title_ = title; + }, + + get title() { + return this.title_; + }, + + get duration() { + if (this.duration_ === undefined) { + this.duration_ = tr.b.math.Statistics.sum( + this.events_, function(event) { + return event.duration; + }); + } + return this.duration_; + }, + + get cpuSelfTime() { + this.computeCpuTimesIfNeeded_(); + return this.cpuSelfTime_; + }, + + get cpuDuration() { + this.computeCpuTimesIfNeeded_(); + return this.cpuDuration_; + }, + + computeCpuTimesIfNeeded_() { + if (this.cpuTimesComputed_) return; + this.cpuTimesComputed_ = true; + + let cpuSelfTime = 0; + let cpuDuration = 0; + let hasCpuData = false; + for (const event of this.events_) { + if (event.cpuDuration !== undefined) { + cpuDuration += event.cpuDuration; + hasCpuData = true; + } + + if (event.cpuSelfTime !== undefined) { + cpuSelfTime += event.cpuSelfTime; + hasCpuData = true; + } + } + if (hasCpuData) { + this.cpuDuration_ = cpuDuration; + this.cpuSelfTime_ = cpuSelfTime; + } + }, + + get selfTime() { + if (this.selfTime_ === undefined) { + this.selfTime_ = 0; + for (const event of this.events_) { + if (event.selfTime !== undefined) { + this.selfTime_ += event.selfTime; + } + } + } + return this.selfTime_; + }, + + get events() { + return this.events_; + }, + + get numEvents() { + return this.events_.length; + }, + + get numAlerts() { + if (this.numAlerts_ === undefined) { + this.numAlerts_ = tr.b.math.Statistics.sum(this.events_, event => + event.associatedAlerts.length + ); + } + return this.numAlerts_; + }, + + get untotallableArgs() { + this.updateArgsIfNeeded_(); + return this.untotallableArgs_; + }, + + get totalledArgs() { + this.updateArgsIfNeeded_(); + return this.totalledArgs_; + }, + + + get maxDuration() { + if (this.maxDuration_ === undefined) { + this.maxDuration_ = tr.b.math.Statistics.max( + this.events_, function(event) { + return event.duration; + }); + } + return this.maxDuration_; + }, + + + get maxCpuDuration() { + if (this.maxCpuDuration_ === undefined) { + this.maxCpuDuration_ = tr.b.math.Statistics.max( + this.events_, function(event) { + return event.cpuDuration; + }); + } + return this.maxCpuDuration_; + }, + + + get maxSelfTime() { + if (this.maxSelfTime_ === undefined) { + this.maxSelfTime_ = tr.b.math.Statistics.max( + this.events_, function(event) { + return event.selfTime; + }); + } + return this.maxSelfTime_; + }, + + + get maxCpuSelfTime() { + if (this.maxCpuSelfTime_ === undefined) { + this.maxCpuSelfTime_ = tr.b.math.Statistics.max( + this.events_, function(event) { + return event.cpuSelfTime; + }); + } + return this.maxCpuSelfTime_; + }, + + + updateArgsIfNeeded_() { + if (this.totalledArgs_ !== undefined) return; + + const untotallableArgs = {}; + const totalledArgs = {}; + for (const event of this.events_) { + for (const argName in event.args) { + const argVal = event.args[argName]; + const type = typeof argVal; + if (type !== 'number') { + untotallableArgs[argName] = true; + delete totalledArgs[argName]; + continue; + } + if (untotallableArgs[argName]) { + continue; + } + + if (totalledArgs[argName] === undefined) { + totalledArgs[argName] = 0; + } + totalledArgs[argName] += argVal; + } + } + this.untotallableArgs_ = Object.keys(untotallableArgs); + this.totalledArgs_ = totalledArgs; + } + }; + + return { + MultiEventSummary, + }; +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_event_summary_table.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_event_summary_table.html new file mode 100644 index 00000000000..1b32d606f61 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_event_summary_table.html @@ -0,0 +1,358 @@ +<!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/base.html"> +<link rel="import" href="/tracing/base/math/range.html"> +<link rel="import" href="/tracing/base/math/statistics.html"> +<link rel="import" href="/tracing/base/unit.html"> +<link rel="import" href="/tracing/base/utils.html"> +<link rel="import" href="/tracing/model/event_set.html"> +<link rel="import" href="/tracing/ui/analysis/analysis_link.html"> +<link rel="import" href="/tracing/ui/analysis/multi_event_summary.html"> +<link rel="import" href="/tracing/ui/base/table.html"> +<link rel="import" href="/tracing/value/ui/scalar_span.html"> + +<dom-module id='tr-ui-a-multi-event-summary-table'> + <template> + <style> + :host { + display: flex; + } + #table { + flex: 1 1 auto; + align-self: stretch; + font-size: 12px; + } + </style> + <tr-ui-b-table id="table"> + </tr-ui-b-table> + </div> + </template> +</dom-module> +<script> +'use strict'; + +Polymer({ + is: 'tr-ui-a-multi-event-summary-table', + + ready() { + this.showTotals_ = false; + this.eventsHaveDuration_ = true; + this.eventsHaveSubRows_ = true; + this.eventsByTitle_ = undefined; + }, + + updateTableColumns_(rows, maxValues) { + let hasCpuData = false; + let hasAlerts = false; + rows.forEach(function(row) { + if (row.cpuDuration !== undefined) { + hasCpuData = true; + } + if (row.cpuSelfTime !== undefined) { + hasCpuData = true; + } + if (row.numAlerts) { + hasAlerts = true; + } + }); + + const ownerDocument = this.ownerDocument; + + const columns = []; + + columns.push({ + title: 'Name', + value(row) { + if (row.title === 'Totals') return 'Totals'; + const container = document.createElement('div'); + const linkEl = document.createElement('tr-ui-a-analysis-link'); + linkEl.setSelectionAndContent(function() { + return new tr.model.EventSet(row.events); + }, row.title); + container.appendChild(linkEl); + + if (tr.isExported('tr-ui-e-chrome-codesearch')) { + const link = document.createElement('tr-ui-e-chrome-codesearch'); + link.searchPhrase = row.title; + container.appendChild(link); + } + return container; + }, + width: '350px', + cmp(rowA, rowB) { + return rowA.title.localeCompare(rowB.title); + } + }); + if (this.eventsHaveDuration_) { + columns.push({ + title: 'Wall Duration', + value(row) { + return tr.v.ui.createScalarSpan(row.duration, { + unit: tr.b.Unit.byName.timeDurationInMs, + customContextRange: row.totalsRow ? undefined : + tr.b.math.Range.fromExplicitRange(0, maxValues.duration), + ownerDocument, + }); + }, + width: '<upated further down>', + cmp(rowA, rowB) { + return rowA.duration - rowB.duration; + } + }); + } + + if (this.eventsHaveDuration_ && hasCpuData) { + columns.push({ + title: 'CPU Duration', + value(row) { + return tr.v.ui.createScalarSpan(row.cpuDuration, { + unit: tr.b.Unit.byName.timeDurationInMs, + customContextRange: row.totalsRow ? undefined : + tr.b.math.Range.fromExplicitRange(0, maxValues.cpuDuration), + ownerDocument, + }); + }, + width: '<upated further down>', + cmp(rowA, rowB) { + return rowA.cpuDuration - rowB.cpuDuration; + } + }); + } + + if (this.eventsHaveSubRows_ && this.eventsHaveDuration_) { + columns.push({ + title: 'Self time', + value(row) { + return tr.v.ui.createScalarSpan(row.selfTime, { + unit: tr.b.Unit.byName.timeDurationInMs, + customContextRange: row.totalsRow ? undefined : + tr.b.math.Range.fromExplicitRange(0, maxValues.selfTime), + ownerDocument, + }); + }, + width: '<upated further down>', + cmp(rowA, rowB) { + return rowA.selfTime - rowB.selfTime; + } + }); + } + + if (this.eventsHaveSubRows_ && this.eventsHaveDuration_ && hasCpuData) { + columns.push({ + title: 'CPU Self Time', + value(row) { + return tr.v.ui.createScalarSpan(row.cpuSelfTime, { + unit: tr.b.Unit.byName.timeDurationInMs, + customContextRange: row.totalsRow ? undefined : + tr.b.math.Range.fromExplicitRange(0, maxValues.cpuSelfTime), + ownerDocument, + }); + }, + width: '<upated further down>', + cmp(rowA, rowB) { + return rowA.cpuSelfTime - rowB.cpuSelfTime; + } + }); + } + + if (this.eventsHaveDuration_) { + columns.push({ + title: 'Average ' + (hasCpuData ? 'CPU' : 'Wall') + ' Duration', + value(row) { + const totalDuration = hasCpuData ? row.cpuDuration : row.duration; + return tr.v.ui.createScalarSpan(totalDuration / row.numEvents, { + unit: tr.b.Unit.byName.timeDurationInMs, + customContextRange: row.totalsRow ? undefined : + tr.b.math.Range.fromExplicitRange(0, maxValues.duration), + ownerDocument, + }); + }, + width: '<upated further down>', + cmp(rowA, rowB) { + if (hasCpuData) { + return rowA.cpuDuration / rowA.numEvents - + rowB.cpuDuration / rowB.numEvents; + } + return rowA.duration / rowA.numEvents - + rowB.duration / rowB.numEvents; + } + }); + } + + columns.push({ + title: 'Occurrences', + value(row) { + return row.numEvents; + }, + width: '<upated further down>', + cmp(rowA, rowB) { + return rowA.numEvents - rowB.numEvents; + } + }); + + let alertsColumnIndex; + if (hasAlerts) { + columns.push({ + title: 'Num Alerts', + value(row) { + return row.numAlerts; + }, + width: '<upated further down>', + cmp(rowA, rowB) { + return rowA.numAlerts - rowB.numAlerts; + } + }); + alertsColumnIndex = columns.length - 1; + } + let colWidthPercentage; + if (columns.length === 1) { + colWidthPercentage = '100%'; + } else { + colWidthPercentage = (100 / (columns.length - 1)).toFixed(3) + '%'; + } + + for (let i = 1; i < columns.length; i++) { + columns[i].width = colWidthPercentage; + } + + this.$.table.tableColumns = columns; + + if (hasAlerts) { + this.$.table.sortColumnIndex = alertsColumnIndex; + this.$.table.sortDescending = true; + } + }, + + configure(config) { + if (config.eventsByTitle === undefined) { + throw new Error('Required: eventsByTitle'); + } + + if (config.showTotals !== undefined) { + this.showTotals_ = config.showTotals; + } else { + this.showTotals_ = true; + } + + if (config.eventsHaveDuration !== undefined) { + this.eventsHaveDuration_ = config.eventsHaveDuration; + } else { + this.eventsHaveDuration_ = true; + } + + if (config.eventsHaveSubRows !== undefined) { + this.eventsHaveSubRows_ = config.eventsHaveSubRows; + } else { + this.eventsHaveSubRows_ = true; + } + + this.eventsByTitle_ = config.eventsByTitle; + this.updateContents_(); + }, + + get showTotals() { + return this.showTotals_; + }, + + set showTotals(showTotals) { + this.showTotals_ = showTotals; + this.updateContents_(); + }, + + get eventsHaveDuration() { + return this.eventsHaveDuration_; + }, + + set eventsHaveDuration(eventsHaveDuration) { + this.eventsHaveDuration_ = eventsHaveDuration; + this.updateContents_(); + }, + + get eventsHaveSubRows() { + return this.eventsHaveSubRows_; + }, + + set eventsHaveSubRows(eventsHaveSubRows) { + this.eventsHaveSubRows_ = eventsHaveSubRows; + this.updateContents_(); + }, + + get eventsByTitle() { + return this.eventsByTitle_; + }, + + set eventsByTitle(eventsByTitle) { + this.eventsByTitle_ = eventsByTitle; + this.updateContents_(); + }, + + get selectionBounds() { + return this.selectionBounds_; + }, + + set selectionBounds(selectionBounds) { + this.selectionBounds_ = selectionBounds; + this.updateContents_(); + }, + + updateContents_() { + let eventsByTitle; + if (this.eventsByTitle_ !== undefined) { + eventsByTitle = this.eventsByTitle_; + } else { + eventsByTitle = []; + } + + const allEvents = new tr.model.EventSet(); + const rows = []; + for (const [title, eventsOfSingleTitle] of Object.entries(eventsByTitle)) { + for (const event of eventsOfSingleTitle) allEvents.push(event); + const row = new tr.ui.analysis.MultiEventSummary( + title, eventsOfSingleTitle); + rows.push(row); + } + + this.updateTableColumns_(rows); + this.$.table.tableRows = rows; + + const maxValues = { + duration: undefined, + selfTime: undefined, + cpuSelfTime: undefined, + cpuDuration: undefined + }; + + if (this.eventsHaveDuration) { + for (const column in maxValues) { + maxValues[column] = tr.b.math.Statistics.max(rows, function(event) { + return event[column]; + }); + } + } + + const footerRows = []; + + if (this.showTotals_) { + const multiEventSummary = new tr.ui.analysis.MultiEventSummary( + 'Totals', allEvents); + footerRows.push(multiEventSummary); + } + + + this.updateTableColumns_(rows, maxValues); + this.$.table.tableRows = rows; + + // TODO(selection bounds). + + // TODO(sorting) + + this.$.table.footerRows = footerRows; + this.$.table.rebuild(); + } +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_event_summary_table_test.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_event_summary_table_test.html new file mode 100644 index 00000000000..32efc0de1ff --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_event_summary_table_test.html @@ -0,0 +1,119 @@ +<!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/core/test_utils.html"> +<link rel="import" href="/tracing/model/event_set.html"> +<link rel="import" href="/tracing/model/model.html"> +<link rel="import" href="/tracing/ui/analysis/multi_event_summary_table.html"> +<link rel="import" href="/tracing/ui/base/deep_utils.html"> + +<script> +'use strict'; + +tr.b.unittest.testSuite(function() { + const Model = tr.Model; + const EventSet = tr.model.EventSet; + const newSliceEx = tr.c.TestUtils.newSliceEx; + + test('basicNoCpu', function() { + const model = new Model(); + const thread = model.getOrCreateProcess(1).getOrCreateThread(2); + const tsg = thread.sliceGroup; + tsg.pushSlice(newSliceEx({title: 'a', start: 0, duration: 0.5})); + tsg.pushSlice(newSliceEx({title: 'b', start: 1, duration: 0.5})); + tsg.pushSlice(newSliceEx({title: 'b', start: 2, duration: 0.5})); + tsg.createSubSlices(); + + const threadTrack = {}; + threadTrack.thread = thread; + + const selection = new EventSet(tsg.slices); + + const viewEl = document.createElement('tr-ui-a-multi-event-summary-table'); + viewEl.configure({ + showTotals: true, + eventsHaveDuration: true, + eventsByTitle: selection.getEventsOrganizedByTitle() + }); + this.addHTMLOutput(viewEl); + }); + + test('basicWithCpu', function() { + const model = new Model(); + const thread = model.getOrCreateProcess(1).getOrCreateThread(2); + const tsg = thread.sliceGroup; + tsg.pushSlice(newSliceEx({title: 'a', start: 0, end: 3, + cpuStart: 0, cpuEnd: 3})); + tsg.pushSlice(newSliceEx({title: 'b', start: 1, end: 2, + cpuStart: 1, cpuEnd: 1.75})); + tsg.pushSlice(newSliceEx({title: 'b', start: 4, end: 5, + cpuStart: 3, cpuEnd: 3.75})); + tsg.createSubSlices(); + + const threadTrack = {}; + threadTrack.thread = thread; + + const selection = new EventSet(tsg.slices); + + const viewEl = document.createElement('tr-ui-a-multi-event-summary-table'); + viewEl.configure({ + showTotals: true, + eventsHaveDuration: true, + eventsByTitle: selection.getEventsOrganizedByTitle() + }); + this.addHTMLOutput(viewEl); + + const totals = tr.ui.b.findDeepElementMatchingPredicate( + viewEl, e => e.tagName === 'TFOOT'); + const scalars = tr.ui.b.findDeepElementsMatchingPredicate( + totals, e => e.tagName === 'TR-V-UI-SCALAR-SPAN'); + assert.strictEqual(scalars[0].value, 5); + assert.closeTo(scalars[1].value, 4.5, 1e-6); + assert.strictEqual(scalars[2].value, 4); + assert.closeTo(scalars[3].value, 3.75, 1e-6); + assert.closeTo(scalars[4].value, 1.5, 1e-6); + assert.strictEqual('3', totals.children[0].children[6].textContent); + }); + + test('noSelfTimeNoSubRows', function() { + const model = new Model(); + + const fe1 = new tr.model.FlowEvent('cat', 1234, 'title', 7, 10, {}); + const fe2 = new tr.model.FlowEvent('cat', 1234, 'title', 8, 20, {}); + + // Make reading some properties an explosion, as a way to ensure that they + // aren't read. Note that 'duration' is read since it is used by the + // EventSet to get the range. + const failProp = { + get() { + throw new Error('Should not be called'); + } + }; + Object.defineProperty(fe1, 'subRows', failProp); + Object.defineProperty(fe2, 'subRows', failProp); + + Object.defineProperty(fe1, 'selfTime', failProp); + Object.defineProperty(fe2, 'selfTime', failProp); + + model.flowEvents.push(fe1); + model.flowEvents.push(fe2); + + const selection = new EventSet([fe1, fe2]); + + const viewEl = document.createElement('tr-ui-a-multi-event-summary-table'); + viewEl.configure({ + showTotals: true, + eventsHaveDuration: false, + eventsHaveSubRows: false, + eventsByTitle: selection.getEventsOrganizedByTitle() + }); + this.addHTMLOutput(viewEl); + }); + + // TODO(nduca): Tooltippish stuff. +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_event_summary_test.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_event_summary_test.html new file mode 100644 index 00000000000..fcc73e1d608 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_event_summary_test.html @@ -0,0 +1,111 @@ +<!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/core/test_utils.html"> +<link rel="import" href="/tracing/model/event_set.html"> +<link rel="import" href="/tracing/model/model.html"> +<link rel="import" href="/tracing/ui/analysis/multi_event_summary.html"> + +<script> +'use strict'; + +tr.b.unittest.testSuite(function() { + const Model = tr.Model; + const newSliceEx = tr.c.TestUtils.newSliceEx; + + test('summaryRowNoCpu', function() { + const model = new Model(); + const thread = model.getOrCreateProcess(1).getOrCreateThread(2); + const tsg = thread.sliceGroup; + + tsg.pushSlice(newSliceEx({title: 'a', start: 0, end: 3})); + tsg.pushSlice(newSliceEx({title: 'bb', start: 1, end: 2})); + tsg.pushSlice(newSliceEx({title: 'bb', start: 4, end: 5})); + tsg.createSubSlices(); + + const row = new tr.ui.analysis.MultiEventSummary('x', tsg.slices.slice(0)); + assert.strictEqual(row.duration, 5); + assert.strictEqual(row.selfTime, 4); + assert.isUndefined(row.cpuDuration); + assert.isUndefined(row.cpuSelfTime); + }); + + test('summaryRowWithCpu', function() { + const model = new Model(); + const thread = model.getOrCreateProcess(1).getOrCreateThread(2); + const tsg = thread.sliceGroup; + + tsg.pushSlice(newSliceEx({title: 'a', start: 0, end: 3, + cpuStart: 0, cpuEnd: 3})); + tsg.pushSlice(newSliceEx({title: 'b', start: 1, end: 2, + cpuStart: 1, cpuEnd: 1.75})); + tsg.pushSlice(newSliceEx({title: 'b', start: 4, end: 5, + cpuStart: 3, cpuEnd: 3.75})); + tsg.createSubSlices(); + + const row = new tr.ui.analysis.MultiEventSummary('x', tsg.slices.slice(0)); + assert.strictEqual(row.duration, 5); + assert.strictEqual(row.selfTime, 4); + assert.strictEqual(row.cpuDuration, 4.5); + assert.strictEqual(row.cpuSelfTime, 3.75); + assert.strictEqual(row.maxDuration, 3); + assert.strictEqual(row.maxSelfTime, 2); + assert.strictEqual(row.maxCpuDuration, 3); + assert.strictEqual(row.maxCpuSelfTime, 2.25); + }); + + test('summaryRowNonSlice', function() { + const model = new Model(); + const thread = model.getOrCreateProcess(1).getOrCreateThread(2); + const tsg = thread.sliceGroup; + + const fe1 = new tr.model.FlowEvent('cat', 1234, 'title', 7, 10, {}); + const fe2 = new tr.model.FlowEvent('cat', 1234, 'title', 8, 20, {}); + model.flowEvents.push(fe1); + model.flowEvents.push(fe2); + + const row = new tr.ui.analysis.MultiEventSummary('a', [fe1, fe2]); + assert.strictEqual(row.duration, 0); + assert.strictEqual(row.selfTime, 0); + assert.isUndefined(row.cpuDuration); + assert.isUndefined(row.cpuSelfTime); + assert.strictEqual(row.maxDuration, 0); + }); + + test('summaryNumAlerts', function() { + const slice = newSliceEx({title: 'b', start: 0, duration: 0.002}); + + const ALERT_INFO_1 = new tr.model.EventInfo( + 'Alert 1', 'Critical alert'); + + const alert = new tr.model.Alert(ALERT_INFO_1, 5, [slice]); + + const row = new tr.ui.analysis.MultiEventSummary('a', [slice]); + assert.strictEqual(row.numAlerts, 1); + }); + + test('argSummary', function() { + const model = new Model(); + const thread = model.getOrCreateProcess(1).getOrCreateThread(2); + const tsg = thread.sliceGroup; + + tsg.pushSlice(newSliceEx({title: 'a', start: 0, end: 3, + args: {value1: 3, value2: 'x', value3: 1}})); + tsg.pushSlice(newSliceEx({title: 'b', start: 1, end: 2, + args: {value1: 3, value2: 'y', value3: 2}})); + tsg.pushSlice(newSliceEx({title: 'b', start: 4, end: 5, + args: {value1: 3, value2: 'z', value3: 'x'}})); + tsg.createSubSlices(); + + const row = new tr.ui.analysis.MultiEventSummary('x', tsg.slices.slice(0)); + assert.deepEqual(row.totalledArgs, {value1: 9}); + assert.deepEqual(row.untotallableArgs, ['value2', 'value3']); + assert.strictEqual(row.maxDuration, 3); + assert.strictEqual(row.maxSelfTime, 2); + }); +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_flow_event_sub_view.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_flow_event_sub_view.html new file mode 100644 index 00000000000..3e509508732 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_flow_event_sub_view.html @@ -0,0 +1,49 @@ +<!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/ui/analysis/analysis_sub_view.html"> +<link rel="import" href="/tracing/ui/analysis/multi_event_sub_view.html"> + +<dom-module id='tr-ui-a-multi-flow-event-sub-view'> + <template> + <style> + :host { + display: flex; + } + </style> + <tr-ui-a-multi-event-sub-view id="content"></tr-ui-a-multi-event-sub-view> + </template> +</dom-module> +<script> +'use strict'; + +Polymer({ + is: 'tr-ui-a-multi-flow-event-sub-view', + behaviors: [tr.ui.analysis.AnalysisSubView], + + ready() { + this.$.content.eventsHaveDuration = false; + this.$.content.eventsHaveSubRows = false; + }, + + set selection(selection) { + this.$.content.selection = selection; + }, + + get selection() { + return this.$.content.selection; + } +}); + +tr.ui.analysis.AnalysisSubView.register( + 'tr-ui-a-multi-flow-event-sub-view', + tr.model.FlowEvent, + { + multi: true, + title: 'Flow Events', + }); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_flow_event_sub_view_test.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_flow_event_sub_view_test.html new file mode 100644 index 00000000000..8b1d98d7bd1 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_flow_event_sub_view_test.html @@ -0,0 +1,39 @@ +<!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/core/test_utils.html"> +<link rel="import" href="/tracing/model/event_set.html"> +<link rel="import" href="/tracing/model/model.html"> +<link rel="import" href="/tracing/ui/analysis/analysis_view.html"> + +<script> +'use strict'; + +tr.b.unittest.testSuite(function() { + const Model = tr.Model; + const EventSet = tr.model.EventSet; + + test('analyzeSelectionWithSingleEvent', function() { + const model = new Model(); + + const fe1 = new tr.model.FlowEvent('cat', 1234, 'title', 7, 10, {}); + const fe2 = new tr.model.FlowEvent('cat', 1234, 'title', 8, 20, {}); + model.flowEvents.push(fe1); + model.flowEvents.push(fe2); + + const selection = new EventSet(); + selection.push(fe1); + selection.push(fe2); + assert.strictEqual(selection.length, 2); + + const subView = document.createElement('tr-ui-a-multi-flow-event-sub-view'); + subView.selection = selection; + + this.addHTMLOutput(subView); + }); +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_frame_sub_view.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_frame_sub_view.html new file mode 100644 index 00000000000..cdf71245df3 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_frame_sub_view.html @@ -0,0 +1,58 @@ +<!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/model/event_set.html"> +<link rel="import" href="/tracing/ui/analysis/analysis_sub_view.html"> +<link rel="import" href="/tracing/ui/analysis/multi_event_sub_view.html"> + +<script> +'use strict'; + +Polymer({ + is: 'tr-ui-a-multi-frame-sub-view', + behaviors: [tr.ui.analysis.AnalysisSubView], + + created() { + this.currentSelection_ = undefined; + }, + + set selection(selection) { + Polymer.dom(this).textContent = ''; + const realView = document.createElement('tr-ui-a-multi-event-sub-view'); + realView.eventsHaveDuration = false; + realView.eventsHaveSubRows = false; + + Polymer.dom(this).appendChild(realView); + realView.setSelectionWithoutErrorChecks(selection); + + this.currentSelection_ = selection; + }, + + get selection() { + return this.currentSelection_; + }, + + get relatedEventsToHighlight() { + if (!this.currentSelection_) return undefined; + + const selection = new tr.model.EventSet(); + this.currentSelection_.forEach(function(frameEvent) { + frameEvent.associatedEvents.forEach(function(event) { + selection.push(event); + }); + }); + return selection; + } +}); +tr.ui.analysis.AnalysisSubView.register( + 'tr-ui-a-multi-frame-sub-view', + tr.model.Frame, + { + multi: true, + title: 'Frames', + }); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_instant_event_sub_view.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_instant_event_sub_view.html new file mode 100644 index 00000000000..c2252f90e14 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_instant_event_sub_view.html @@ -0,0 +1,48 @@ +<!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/ui/analysis/analysis_sub_view.html"> +<link rel="import" href="/tracing/ui/analysis/multi_event_sub_view.html"> + +<dom-module id='tr-ui-a-multi-instant-event-sub-view'> + <template> + <style> + :host { + display: block; + } + </style> + <div id='content'></div> + </template> +</dom-module> +<script> +'use strict'; + +Polymer({ + is: 'tr-ui-a-multi-instant-event-sub-view', + behaviors: [tr.ui.analysis.AnalysisSubView], + + created() { + this.currentSelection_ = undefined; + }, + + set selection(selection) { + Polymer.dom(this.$.content).textContent = ''; + const realView = document.createElement('tr-ui-a-multi-event-sub-view'); + realView.eventsHaveDuration = false; + realView.eventsHaveSubRows = false; + + Polymer.dom(this.$.content).appendChild(realView); + realView.setSelectionWithoutErrorChecks(selection); + + this.currentSelection_ = selection; + }, + + get selection() { + return this.currentSelection_; + } +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_instant_event_sub_view_test.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_instant_event_sub_view_test.html new file mode 100644 index 00000000000..ca228515de5 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_instant_event_sub_view_test.html @@ -0,0 +1,43 @@ +<!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/core/test_utils.html"> +<link rel="import" href="/tracing/model/event_set.html"> +<link rel="import" href="/tracing/model/model.html"> +<link rel="import" href="/tracing/ui/analysis/analysis_view.html"> + +<script> +'use strict'; + +tr.b.unittest.testSuite(function() { + const Model = tr.Model; + const EventSet = tr.model.EventSet; + + test('analyzeSelectionWithSingleEvent', function() { + const model = new Model(); + const p52 = model.getOrCreateProcess(52); + const t53 = p52.getOrCreateThread(53); + + const ie1 = new tr.model.ProcessInstantEvent('cat', 'title', 7, 10, {}); + const ie2 = new tr.model.ProcessInstantEvent('cat', 'title', 7, 20, {}); + p52.instantEvents.push(ie1); + p52.instantEvents.push(ie2); + + + const selection = new EventSet(); + selection.push(ie1); + selection.push(ie2); + assert.strictEqual(selection.length, 2); + + const subView = + document.createElement('tr-ui-a-multi-instant-event-sub-view'); + subView.selection = selection; + + this.addHTMLOutput(subView); + }); +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_object_sub_view.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_object_sub_view.html new file mode 100644 index 00000000000..089f4b011a2 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_object_sub_view.html @@ -0,0 +1,112 @@ +<!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/unit.html"> +<link rel="import" href="/tracing/model/event_set.html"> +<link rel="import" href="/tracing/ui/analysis/analysis_link.html"> +<link rel="import" href="/tracing/ui/analysis/analysis_sub_view.html"> +<link rel="import" href="/tracing/ui/base/dom_helpers.html"> +<link rel="import" href="/tracing/ui/base/table.html"> +<link rel="import" href="/tracing/value/ui/scalar_span.html"> + +<dom-module id='tr-ui-a-multi-object-sub-view'> + <template> + <style> + :host { + display: flex; + font-size: 12px; + } + </style> + <tr-ui-b-table id="content"></tr-ui-b-table> + </template> +</dom-module> +<script> +'use strict'; + +Polymer({ + is: 'tr-ui-a-multi-object-sub-view', + behaviors: [tr.ui.analysis.AnalysisSubView], + + created() { + this.currentSelection_ = undefined; + }, + + ready() { + this.$.content.showHeader = false; + }, + + get selection() { + return this.currentSelection_; + }, + + set selection(selection) { + this.currentSelection_ = selection; + + const objectEvents = Array.from(selection).sort( + tr.b.math.Range.compareByMinTimes); + + const timeSpanConfig = { + unit: tr.b.Unit.byName.timeStampInMs, + ownerDocument: this.ownerDocument + }; + const table = this.$.content; + table.tableColumns = [ + { + title: 'First', + value(event) { + if (event instanceof tr.model.ObjectSnapshot) { + return tr.v.ui.createScalarSpan(event.ts, timeSpanConfig); + } + + const spanEl = document.createElement('span'); + Polymer.dom(spanEl).appendChild(tr.v.ui.createScalarSpan( + event.creationTs, timeSpanConfig)); + Polymer.dom(spanEl).appendChild(tr.ui.b.createSpan({ + textContent: '-', + marginLeft: '4px', + marginRight: '4px' + })); + if (event.deletionTs !== Number.MAX_VALUE) { + Polymer.dom(spanEl).appendChild(tr.v.ui.createScalarSpan( + event.deletionTs, timeSpanConfig)); + } + return spanEl; + }, + width: '200px' + }, + { + title: 'Second', + value(event) { + const linkEl = document.createElement('tr-ui-a-analysis-link'); + linkEl.setSelectionAndContent(function() { + return new tr.model.EventSet(event); + }, event.userFriendlyName); + return linkEl; + }, + width: '100%' + } + ]; + table.tableRows = objectEvents; + table.rebuild(); + } +}); + +tr.ui.analysis.AnalysisSubView.register( + 'tr-ui-a-multi-object-sub-view', + tr.model.ObjectInstance, + { + multi: true, + title: 'Object Instances', + }); +tr.ui.analysis.AnalysisSubView.register( + 'tr-ui-a-multi-object-sub-view', + tr.model.ObjectSnapshot, + { + multi: true, + title: 'Object Snapshots', + }); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_object_sub_view_test.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_object_sub_view_test.html new file mode 100644 index 00000000000..18e62170e7d --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_object_sub_view_test.html @@ -0,0 +1,46 @@ +<!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/core/test_utils.html"> +<link rel="import" href="/tracing/model/event_set.html"> +<link rel="import" href="/tracing/model/model.html"> +<link rel="import" href="/tracing/ui/analysis/analysis_view.html"> + +<script> +'use strict'; + +tr.b.unittest.testSuite(function() { + const EventSet = tr.model.EventSet; + const ObjectInstance = tr.model.ObjectInstance; + + test('instantiate_analysisWithObjects', function() { + const model = new tr.Model(); + const p1 = model.getOrCreateProcess(1); + const objects = p1.objects; + const i10 = objects.idWasCreated( + '0x1000', 'tr.e.cc', 'LayerTreeHostImpl', 10); + const s10 = objects.addSnapshot('0x1000', 'tr.e.cc', 'LayerTreeHostImpl', + 10, 'snapshot-1'); + const s25 = objects.addSnapshot('0x1000', 'tr.e.cc', 'LayerTreeHostImpl', + 25, 'snapshot-2'); + const s40 = objects.addSnapshot('0x1000', 'tr.e.cc', 'LayerTreeHostImpl', + 40, 'snapshot-3'); + objects.idWasDeleted('0x1000', 'tr.e.cc', 'LayerTreeHostImpl', 45); + + const track = {}; + const selection = new EventSet(); + selection.push(i10); + selection.push(s10); + selection.push(s25); + selection.push(s40); + + const analysisEl = document.createElement('tr-ui-a-multi-object-sub-view'); + analysisEl.selection = selection; + this.addHTMLOutput(analysisEl); + }); +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_power_sample_sub_view.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_power_sample_sub_view.html new file mode 100644 index 00000000000..32315c220a1 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_power_sample_sub_view.html @@ -0,0 +1,77 @@ +<!DOCTYPE html> +<!-- +Copyright 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/base/utils.html"> +<link rel="import" href="/tracing/ui/analysis/analysis_sub_view.html"> +<link rel="import" href="/tracing/ui/analysis/frame_power_usage_chart.html"> +<link rel="import" href="/tracing/ui/analysis/power_sample_summary_table.html"> + +<dom-module id='tr-ui-a-multi-power-sample-sub-view'> + <template> + <style> + :host { + display: flex; + flex-direction: row; + } + #tables { + display: flex; + flex-direction: column; + width: 50%; + } + #chart { + width: 50%; + } + </style> + <div id="tables"> + <tr-ui-a-power-sample-summary-table id="summaryTable"> + </tr-ui-a-power-sample-summary-table> + </div> + <tr-ui-a-frame-power-usage-chart id="chart"> + </tr-ui-a-frame-power-usage-chart> + </template> +</dom-module> + +<script> +'use strict'; + +// TODO(charliea): Add a dropdown that allows the user to select which type of +// power sample analysis view they want (e.g. table of samples, graph). +Polymer({ + is: 'tr-ui-a-multi-power-sample-sub-view', + behaviors: [tr.ui.analysis.AnalysisSubView], + + ready() { + this.currentSelection_ = undefined; + }, + + get selection() { + return this.currentSelection_; + }, + + set selection(selection) { + this.currentSelection_ = selection; + this.updateContents_(); + }, + + updateContents_() { + const samples = this.selection; + const vSyncTimestamps = (!samples ? [] : + tr.b.getFirstElement(samples).series.device.vSyncTimestamps); + + this.$.summaryTable.samples = samples; + this.$.chart.setData(this.selection, vSyncTimestamps); + } +}); + +tr.ui.analysis.AnalysisSubView.register( + 'tr-ui-a-multi-power-sample-sub-view', + tr.model.PowerSample, + { + multi: true, + title: 'Power Samples', + }); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_power_sample_sub_view_test.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_power_sample_sub_view_test.html new file mode 100644 index 00000000000..f7759572e28 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_power_sample_sub_view_test.html @@ -0,0 +1,64 @@ +<!DOCTYPE html> +<!-- +Copyright 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/model/event_set.html"> +<link rel="import" href="/tracing/model/model.html"> +<link rel="import" href="/tracing/model/power_series.html"> +<link rel="import" href="/tracing/ui/analysis/multi_power_sample_sub_view.html"> + +<script> +'use strict'; + +tr.b.unittest.testSuite(function() { + test('instantiate_noSamplesOrVSyncs', function() { + const viewEl = document.createElement( + 'tr-ui-a-multi-power-sample-sub-view'); + viewEl.selection = undefined; + this.addHTMLOutput(viewEl); + }); + + test('instantiate_noVSyncs', function() { + const model = new tr.Model(); + const series = new tr.model.PowerSeries(model.device); + + model.device.vSyncTimestamps = []; + series.addPowerSample(1, 1); + series.addPowerSample(2, 2); + series.addPowerSample(3, 3); + series.addPowerSample(4, 2); + + const view = document.createElement('tr-ui-a-multi-power-sample-sub-view'); + const eventSet = new tr.model.EventSet(series.samples); + view.selection = eventSet; + + this.addHTMLOutput(view); + + assert.deepEqual(view.$.chart.samples, eventSet); + assert.sameDeepMembers(view.$.chart.vSyncTimestamps, []); + }); + + test('instantiate', function() { + const model = new tr.Model(); + const series = new tr.model.PowerSeries(model.device); + + model.device.vSyncTimestamps = [0]; + series.addPowerSample(1, 1); + series.addPowerSample(2, 2); + series.addPowerSample(3, 3); + series.addPowerSample(4, 2); + + const view = document.createElement('tr-ui-a-multi-power-sample-sub-view'); + const eventSet = new tr.model.EventSet(series.samples); + view.selection = eventSet; + + this.addHTMLOutput(view); + + assert.deepEqual(view.$.chart.samples, eventSet); + assert.sameDeepMembers(view.$.chart.vSyncTimestamps, [0]); + }); +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_sample_sub_view.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_sample_sub_view.html new file mode 100644 index 00000000000..1737894f875 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_sample_sub_view.html @@ -0,0 +1,234 @@ +<!DOCTYPE html> +<!-- +Copyright (c) 2016 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/math/range.html"> +<link rel="import" href="/tracing/base/multi_dimensional_view.html"> +<link rel="import" href="/tracing/base/unit.html"> +<link rel="import" href="/tracing/ui/analysis/analysis_sub_view.html"> +<link rel="import" href="/tracing/ui/base/table.html"> +<link rel="import" href="/tracing/value/ui/scalar_span.html"> + +<dom-module id='tr-ui-a-multi-sample-sub-view'> + <template> + <style> + :host { display: block; } + #control { + background-color: #e6e6e6; + background-image: -webkit-gradient(linear, 0 0, 0 100%, + from(#E5E5E5), to(#D1D1D1)); + flex: 0 0 auto; + overflow-x: auto; + } + #control::-webkit-scrollbar { height: 0px; } + #control { + font-size: 12px; + display: flex; + flex-direction: row; + align-items: stretch; + margin: 1px; + margin-right: 2px; + } + tr-ui-b-table { + font-size: 12px; + } + </style> + <div id="control"> + Sample View Option + </div> + <tr-ui-b-table id="table"> + </tr-ui-b-table> + </template> +</dom-module> +<script> +'use strict'; + +(function() { + const MultiDimensionalViewBuilder = tr.b.MultiDimensionalViewBuilder; + + Polymer({ + is: 'tr-ui-a-multi-sample-sub-view', + behaviors: [tr.ui.analysis.AnalysisSubView], + + created() { + this.viewOption_ = undefined; + this.selection_ = undefined; + }, + + ready() { + const viewSelector = tr.ui.b.createSelector( + this, 'viewOption', 'tracing.ui.analysis.multi_sample_sub_view', + MultiDimensionalViewBuilder.ViewType.TOP_DOWN_TREE_VIEW, + [ + { + label: 'Top-down (Tree)', + value: MultiDimensionalViewBuilder.ViewType.TOP_DOWN_TREE_VIEW + }, + { + label: 'Top-down (Heavy)', + value: MultiDimensionalViewBuilder.ViewType.TOP_DOWN_HEAVY_VIEW + }, + { + label: 'Bottom-up (Heavy)', + value: MultiDimensionalViewBuilder.ViewType.BOTTOM_UP_HEAVY_VIEW + } + ]); + Polymer.dom(this.$.control).appendChild(viewSelector); + this.$.table.selectionMode = tr.ui.b.TableFormat.SelectionMode.ROW; + }, + + get selection() { + return this.selection_; + }, + + set selection(selection) { + this.selection_ = selection; + this.updateContents_(); + }, + + get viewOption() { + return this.viewOption_; + }, + + set viewOption(viewOption) { + this.viewOption_ = viewOption; + this.updateContents_(); + }, + + createSamplingSummary_(selection, viewOption) { + const builder = new MultiDimensionalViewBuilder( + 1 /* dimensions */, 1 /* valueCount */); + const samples = selection.filter( + event => event instanceof tr.model.Sample); + + samples.forEach(function(sample) { + builder.addPath([sample.userFriendlyStack.reverse()], + [1], MultiDimensionalViewBuilder.ValueKind.SELF); + }); + + return builder.buildView(viewOption); + }, + + processSampleRows_(rows) { + for (const row of rows) { + let title = row.title[0]; + let results = /(.*) (Deoptimized reason: .*)/.exec(title); + if (results !== null) { + row.deoptReason = results[2]; + title = results[1]; + } + results = /(.*) url: (.*)/.exec(title); + if (results !== null) { + row.functionName = results[1]; + row.url = results[2]; + if (row.functionName === '') { + row.functionName = '(anonymous function)'; + } + if (row.url === '') { + row.url = 'unknown'; + } + } else { + row.functionName = title; + row.url = 'unknown'; + } + this.processSampleRows_(row.subRows); + } + }, + + updateContents_() { + if (this.selection === undefined) { + this.$.table.tableColumns = []; + this.$.table.tableRows = []; + this.$.table.rebuild(); + return; + } + + const samplingData = this.createSamplingSummary_( + this.selection, this.viewOption); + const total = samplingData.values[0].total; + const columns = [ + this.createPercentColumn_('Total', total), + this.createSamplesColumn_('Total'), + this.createPercentColumn_('Self', total), + this.createSamplesColumn_('Self'), + { + title: 'Function Name', + value(row) { + // For function that got deoptimized, show function name + // as red italic with a tooltip + if (row.deoptReason !== undefined) { + const spanEl = tr.ui.b.createSpan({ + italic: true, + color: '#F44336', + tooltip: row.deoptReason + }); + spanEl.innerText = row.functionName; + return spanEl; + } + return row.functionName; + }, + width: '150px', + cmp: (a, b) => a.functionName.localeCompare(b.functionName), + showExpandButtons: true + }, + { + title: 'Location', + value(row) { return row.url; }, + width: '250px', + cmp: (a, b) => a.url.localeCompare(b.url), + } + ]; + + this.processSampleRows_(samplingData.subRows); + this.$.table.tableColumns = columns; + this.$.table.sortColumnIndex = 1; /* Total samples */ + this.$.table.sortDescending = true; + this.$.table.tableRows = samplingData.subRows; + this.$.table.rebuild(); + }, + + createPercentColumn_(title, samplingDataTotal) { + const field = title.toLowerCase(); + return { + title: title + ' percent', + value(row) { + return tr.v.ui.createScalarSpan( + row.values[0][field] / samplingDataTotal, { + customContextRange: tr.b.math.Range.PERCENT_RANGE, + unit: tr.b.Unit.byName.normalizedPercentage, + context: { minimumFractionDigits: 2, maximumFractionDigits: 2 }, + }); + }, + width: '60px', + cmp: (a, b) => a.values[0][field] - b.values[0][field] + }; + }, + + createSamplesColumn_(title) { + const field = title.toLowerCase(); + return { + title: title + ' samples', + value(row) { + return tr.v.ui.createScalarSpan(row.values[0][field], { + unit: tr.b.Unit.byName.unitlessNumber, + context: { maximumFractionDigits: 0 }, + }); + }, + width: '60px', + cmp: (a, b) => a.values[0][field] - b.values[0][field] + }; + } + }); + + tr.ui.analysis.AnalysisSubView.register( + 'tr-ui-a-multi-sample-sub-view', + tr.model.Sample, + { + multi: true, + title: 'Samples', + }); +})(); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_sample_sub_view_test.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_sample_sub_view_test.html new file mode 100644 index 00000000000..e148a2d7c95 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_sample_sub_view_test.html @@ -0,0 +1,71 @@ +<!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/core/test_utils.html"> +<link rel="import" href="/tracing/model/event_set.html"> +<link rel="import" href="/tracing/ui/analysis/multi_sample_sub_view.html"> + +<script> +'use strict'; + +tr.b.unittest.testSuite(function() { + const newSampleNamed = tr.c.TestUtils.newSampleNamed; + + function instantiateWithTraces(traces) { + let t53; + const m = tr.c.TestUtils.newModelWithEvents([], { + shiftWorldToZero: false, + pruneContainers: false, + customizeModelCallback(model) { + t53 = model.getOrCreateProcess(52).getOrCreateThread(53); + traces.forEach(function(trace, index) { + model.samples.push( + newSampleNamed(t53, 'X', 'cat', trace, index * 0.02)); + }); + } + }); + + const t53track = {}; + t53track.thread = t53; + + const selection = new tr.model.EventSet(); + for (let i = 0; i < t53.samples.length; i++) { + selection.push(t53.samples[i]); + } + + const view = document.createElement('tr-ui-a-multi-sample-sub-view'); + view.style.height = '500px'; + this.addHTMLOutput(view); + view.selection = selection; + return view; + } + + test('instantiate_flat', function() { + instantiateWithTraces.call(this, [ + ['BBB'], + ['AAA'], + ['AAA'], + ['Sleeping'], + ['BBB'], + ['AAA'], + ['CCC'], + ['Sleeping'] + ]); + }); + + test('instantiate_nested', function() { + instantiateWithTraces.call(this, [ + ['AAA', 'BBB'], + ['AAA', 'BBB', 'CCC'], + ['AAA', 'BBB'], + ['BBB', 'AAA', 'BBB'], + ['BBB', 'AAA', 'BBB'], + ['BBB', 'AAA', 'BBB'] + ]); + }); +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_thread_slice_sub_view.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_thread_slice_sub_view.html new file mode 100644 index 00000000000..b896a7bf699 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_thread_slice_sub_view.html @@ -0,0 +1,104 @@ +<!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/ui/analysis/analysis_sub_view.html"> +<link rel="import" href="/tracing/ui/analysis/multi_event_sub_view.html"> +<link rel="import" href="/tracing/ui/analysis/related_events.html"> + +<dom-module id='tr-ui-a-multi-thread-slice-sub-view'> + <template> + <style> + :host { + display: flex; + } + #content { + display: flex; + flex: 1 1 auto; + min-width: 0; + } + #content > tr-ui-a-related-events { + margin-left: 8px; + flex: 0 1 200px; + } + </style> + <div id="content"></div> + </template> +</dom-module> +<script> +'use strict'; + +Polymer({ + is: 'tr-ui-a-multi-thread-slice-sub-view', + behaviors: [tr.ui.analysis.AnalysisSubView], + + created() { + this.selection_ = undefined; + }, + + get selection() { + return this.selection_; + }, + + set selection(selection) { + this.selection_ = selection; + + // TODO(nduca): This is a gross hack for cc Frame Viewer, but its only + // the frame viewer that needs this feature, so ~shrug~. + // We check for its presence so that we do not have a hard dependency + // on frame viewer. + if (tr.isExported('tr.ui.e.chrome.cc.RasterTaskSelection')) { + if (tr.ui.e.chrome.cc.RasterTaskSelection.supports(selection)) { + const ltvSelection = new tr.ui.e.chrome.cc.RasterTaskSelection( + selection); + + const ltv = new tr.ui.e.chrome.cc.LayerTreeHostImplSnapshotView(); + ltv.objectSnapshot = ltvSelection.containingSnapshot; + ltv.selection = ltvSelection; + ltv.extraHighlightsByLayerId = ltvSelection.extraHighlightsByLayerId; + + Polymer.dom(this.$.content).textContent = ''; + Polymer.dom(this.$.content).appendChild(ltv); + + this.requiresTallView_ = true; + return; + } + } + + Polymer.dom(this.$.content).textContent = ''; + + const mesv = document.createElement('tr-ui-a-multi-event-sub-view'); + mesv.selection = selection; + Polymer.dom(this.$.content).appendChild(mesv); + + const relatedEvents = document.createElement('tr-ui-a-related-events'); + relatedEvents.setRelatedEvents(selection); + + if (relatedEvents.hasRelatedEvents()) { + Polymer.dom(this.$.content).appendChild(relatedEvents); + } + }, + + get requiresTallView() { + if (this.$.content.children.length === 0) return false; + const childTagName = this.$.content.children[0].tagName; + if (childTagName === 'TR-UI-A-MULTI-EVENT-SUB-VIEW') { + return false; + } + + // Using raster task view. + return true; + } +}); + +tr.ui.analysis.AnalysisSubView.register( + 'tr-ui-a-multi-thread-slice-sub-view', + tr.model.ThreadSlice, + { + multi: true, + title: 'Slices', + }); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_thread_slice_sub_view_test.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_thread_slice_sub_view_test.html new file mode 100644 index 00000000000..a44419cf536 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_thread_slice_sub_view_test.html @@ -0,0 +1,87 @@ +<!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/core/test_utils.html"> +<link rel="import" href="/tracing/model/event_set.html"> +<link rel="import" href="/tracing/model/model.html"> +<link rel="import" href="/tracing/model/thread_slice.html"> +<link rel="import" href="/tracing/ui/analysis/multi_thread_slice_sub_view.html"> + +<script> +'use strict'; + +tr.b.unittest.testSuite(function() { + const newSliceEx = tr.c.TestUtils.newSliceEx; + const newFlowEventEx = tr.c.TestUtils.newFlowEventEx; + + test('instantiate', function() { + const model = new tr.Model(); + const t53 = model.getOrCreateProcess(52).getOrCreateThread(53); + t53.sliceGroup.pushSlice( + newSliceEx({title: 'a', start: 0.0, duration: 0.5, + type: tr.model.ThreadSlice})); + t53.sliceGroup.pushSlice( + newSliceEx({title: 'b', start: 1.0, duration: 2, + type: tr.model.ThreadSlice})); + t53.sliceGroup.createSubSlices(); + + const selection = new tr.model.EventSet(); + selection.push(t53.sliceGroup.slices[0]); + selection.push(t53.sliceGroup.slices[1]); + + const viewEl = document.createElement( + 'tr-ui-a-multi-thread-slice-sub-view'); + viewEl.selection = selection; + this.addHTMLOutput(viewEl); + }); + + test('withFlows', function() { + const m = tr.c.TestUtils.newModel(function(m) { + m.p1 = m.getOrCreateProcess(1); + + m.t2 = m.p1.getOrCreateThread(2); + m.t3 = m.p1.getOrCreateThread(3); + m.t4 = m.p1.getOrCreateThread(4); + + m.sA = m.t2.sliceGroup.pushSlice( + newSliceEx({title: 'a', start: 0, end: 5, + type: tr.model.ThreadSlice})); + m.sB = m.t3.sliceGroup.pushSlice( + newSliceEx({title: 'b', start: 10, end: 15, + type: tr.model.ThreadSlice})); + m.sC = m.t4.sliceGroup.pushSlice( + newSliceEx({title: 'c', start: 20, end: 20, + type: tr.model.ThreadSlice})); + + m.t2.createSubSlices(); + m.t3.createSubSlices(); + m.t4.createSubSlices(); + + m.f1 = newFlowEventEx({ + title: 'flowish', start: 0, end: 10, + startSlice: m.sA, + endSlice: m.sB + }); + m.f2 = newFlowEventEx({ + title: 'flowish', start: 15, end: 21, + startSlice: m.sB, + endSlice: m.sC + }); + }); + + const selection = new tr.model.EventSet(); + selection.push(m.sA); + selection.push(m.sB); + selection.push(m.sC); + + const viewEl = document.createElement( + 'tr-ui-a-multi-thread-slice-sub-view'); + viewEl.selection = selection; + this.addHTMLOutput(viewEl); + }); +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_thread_time_slice_sub_view.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_thread_time_slice_sub_view.html new file mode 100644 index 00000000000..f1f0666fc43 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_thread_time_slice_sub_view.html @@ -0,0 +1,52 @@ +<!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/ui/analysis/analysis_sub_view.html"> +<link rel="import" href="/tracing/ui/analysis/multi_event_sub_view.html"> + +<dom-module id='tr-ui-a-multi-thread-time-slice-sub-view'> + <template> + <style> + :host { + display: flex; + } + #content { + flex: 1 1 auto; + min-width: 0; + } + </style> + <tr-ui-a-multi-event-sub-view id="content"></tr-ui-a-multi-event-sub-view> + </template> +</dom-module> +<script> +'use strict'; + +Polymer({ + is: 'tr-ui-a-multi-thread-time-slice-sub-view', + behaviors: [tr.ui.analysis.AnalysisSubView], + + ready() { + this.$.content.eventsHaveSubRows = false; + }, + + get selection() { + return this.$.content.selection; + }, + + set selection(selection) { + this.$.content.setSelectionWithoutErrorChecks(selection); + } +}); + +tr.ui.analysis.AnalysisSubView.register( + 'tr-ui-a-multi-thread-time-slice-sub-view', + tr.model.ThreadTimeSlice, + { + multi: true, + title: 'Thread Timeslices', + }); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_thread_time_slice_sub_view_test.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_thread_time_slice_sub_view_test.html new file mode 100644 index 00000000000..e3489fe1c9c --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_thread_time_slice_sub_view_test.html @@ -0,0 +1,49 @@ +<!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/core/test_utils.html"> +<link rel="import" href="/tracing/extras/importer/linux_perf/ftrace_importer.html"> +<link rel="import" href="/tracing/model/event_set.html"> +<link rel="import" href="/tracing/model/model.html"> +<link rel="import" href="/tracing/ui/analysis/multi_thread_time_slice_sub_view.html"> + +<script> +'use strict'; + +tr.b.unittest.testSuite(function() { + function createBasicModel() { + const lines = [ + 'Android.launcher-584 [001] d..3 12622.506890: sched_switch: prev_comm=Android.launcher prev_pid=584 prev_prio=120 prev_state=R+ ==> next_comm=Binder_1 next_pid=217 next_prio=120', // @suppress longLineCheck + ' Binder_1-217 [001] d..3 12622.506918: sched_switch: prev_comm=Binder_1 prev_pid=217 prev_prio=120 prev_state=D ==> next_comm=Android.launcher next_pid=584 next_prio=120', // @suppress longLineCheck + 'Android.launcher-584 [001] d..4 12622.506936: sched_wakeup: comm=Binder_1 pid=217 prio=120 success=1 target_cpu=001', // @suppress longLineCheck + 'Android.launcher-584 [001] d..3 12622.506950: sched_switch: prev_comm=Android.launcher prev_pid=584 prev_prio=120 prev_state=R+ ==> next_comm=Binder_1 next_pid=217 next_prio=120', // @suppress longLineCheck + ' Binder_1-217 [001] ...1 12622.507057: tracing_mark_write: B|128|queueBuffer', // @suppress longLineCheck + ' Binder_1-217 [001] ...1 12622.507175: tracing_mark_write: E', + ' Binder_1-217 [001] d..3 12622.507253: sched_switch: prev_comm=Binder_1 prev_pid=217 prev_prio=120 prev_state=S ==> next_comm=Android.launcher next_pid=584 next_prio=120' // @suppress longLineCheck + ]; + + return tr.c.TestUtils.newModelWithEvents([lines.join('\n')], { + shiftWorldToZero: false + }); + } + + test('instantiate', function() { + const m = createBasicModel(); + + const thread = m.findAllThreadsNamed('Binder_1')[0]; + + const selection = new tr.model.EventSet(); + selection.push(thread.timeSlices[0]); + selection.push(thread.timeSlices[1]); + + const viewEl = document.createElement( + 'tr-ui-a-multi-thread-time-slice-sub-view'); + viewEl.selection = selection; + this.addHTMLOutput(viewEl); + }); +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_user_expectation_sub_view.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_user_expectation_sub_view.html new file mode 100644 index 00000000000..b89e3fd0724 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_user_expectation_sub_view.html @@ -0,0 +1,80 @@ +<!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/model/event_set.html"> +<link rel="import" href="/tracing/ui/analysis/analysis_sub_view.html"> +<link rel="import" href="/tracing/ui/analysis/multi_event_sub_view.html"> +<link rel="import" + href="/tracing/ui/analysis/user_expectation_related_samples_table.html"> + +<dom-module id='tr-ui-a-multi-user-expectation-sub-view'> + <template> + <style> + :host { + display: flex; + flex: 1 1 auto; + } + #events { + margin-left: 8px; + flex: 0 1 200px; + } + </style> + <tr-ui-a-multi-event-sub-view id="realView"></tr-ui-a-multi-event-sub-view> + <div id="events"> + <tr-ui-a-user-expectation-related-samples-table id="relatedSamples"></tr-ui-a-user-expectation-related-samples-table> + </div> + </template> +</dom-module> +<script> +'use strict'; + +Polymer({ + is: 'tr-ui-a-multi-interaction-record-sub-view', + behaviors: [tr.ui.analysis.AnalysisSubView], + + created() { + this.currentSelection_ = undefined; + }, + + set selection(selection) { + this.currentSelection_ = selection; + this.$.realView.setSelectionWithoutErrorChecks(selection); + + this.currentSelection_ = selection; + + this.$.relatedSamples.selection = selection; + if (this.$.relatedSamples.hasRelatedSamples()) { + this.$.events.style.display = ''; + } else { + this.$.events.style.display = 'none'; + } + }, + + get selection() { + return this.currentSelection_; + }, + + get relatedEventsToHighlight() { + if (!this.currentSelection_) return undefined; + + const selection = new tr.model.EventSet(); + this.currentSelection_.forEach(function(ir) { + ir.associatedEvents.forEach(function(event) { + selection.push(event); + }); + }); + return selection; + } +}); +tr.ui.analysis.AnalysisSubView.register( + 'tr-ui-a-single-user-expectation-sub-view', + tr.model.um.UserExpectation, + { + multi: true, + title: 'User Expectations', + }); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/object_instance_view.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/object_instance_view.html new file mode 100644 index 00000000000..3c76dc8c9e3 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/object_instance_view.html @@ -0,0 +1,62 @@ +<!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/extension_registry.html"> +<link rel="import" href="/tracing/ui/base/ui.html"> + +<script> +'use strict'; + +tr.exportTo('tr.ui.analysis', function() { + const ObjectInstanceView = tr.ui.b.define('object-instance-view'); + + ObjectInstanceView.prototype = { + __proto__: HTMLDivElement.prototype, + + decorate() { + this.objectInstance_ = undefined; + }, + + get requiresTallView() { + return true; + }, + + set modelEvent(obj) { + this.objectInstance = obj; + }, + + get modelEvent() { + return this.objectInstance; + }, + + get objectInstance() { + return this.objectInstance_; + }, + + set objectInstance(i) { + this.objectInstance_ = i; + this.updateContents(); + }, + + updateContents() { + throw new Error('Not implemented'); + } + }; + + const options = new tr.b.ExtensionRegistryOptions( + tr.b.TYPE_BASED_REGISTRY_MODE); + options.mandatoryBaseClass = ObjectInstanceView; + options.defaultMetadata = { + showInTrackView: true + }; + tr.b.decorateExtensionRegistry(ObjectInstanceView, options); + + return { + ObjectInstanceView, + }; +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/object_snapshot_view.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/object_snapshot_view.html new file mode 100644 index 00000000000..a42ed0ec02b --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/object_snapshot_view.html @@ -0,0 +1,63 @@ +<!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/extension_registry.html"> +<link rel="import" href="/tracing/ui/base/ui.html"> + +<script> +'use strict'; + +tr.exportTo('tr.ui.analysis', function() { + const ObjectSnapshotView = tr.ui.b.define('object-snapshot-view'); + + ObjectSnapshotView.prototype = { + __proto__: HTMLDivElement.prototype, + + decorate() { + this.objectSnapshot_ = undefined; + }, + + get requiresTallView() { + return true; + }, + + set modelEvent(obj) { + this.objectSnapshot = obj; + }, + + get modelEvent() { + return this.objectSnapshot; + }, + + get objectSnapshot() { + return this.objectSnapshot_; + }, + + set objectSnapshot(i) { + this.objectSnapshot_ = i; + this.updateContents(); + }, + + updateContents() { + throw new Error('Not implemented'); + } + }; + + const options = new tr.b.ExtensionRegistryOptions( + tr.b.TYPE_BASED_REGISTRY_MODE); + options.mandatoryBaseClass = ObjectSnapshotView; + options.defaultMetadata = { + showInstances: true, + showInTrackView: true + }; + tr.b.decorateExtensionRegistry(ObjectSnapshotView, options); + + return { + ObjectSnapshotView, + }; +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/power_sample_summary_table.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/power_sample_summary_table.html new file mode 100644 index 00000000000..337e4f5ba56 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/power_sample_summary_table.html @@ -0,0 +1,135 @@ +<!DOCTYPE html> +<!-- +Copyright 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/base/unit.html"> +<link rel="import" href="/tracing/base/unit_scale.html"> +<link rel="import" href="/tracing/base/utils.html"> +<link rel="import" href="/tracing/model/event_set.html"> +<link rel="import" href="/tracing/ui/base/table.html"> + +<dom-module id='tr-ui-a-power-sample-summary-table'> + <template> + <style> + tr-ui-b-table { + font-size: 12px; + } + </style> + <tr-ui-b-table id="table"></tr-ui-b-table> + </template> +</dom-module> +<script> +'use strict'; + +Polymer({ + is: 'tr-ui-a-power-sample-summary-table', + + ready() { + this.$.table.tableColumns = [ + { + title: 'Min power', + width: '100px', + value(row) { + return tr.b.Unit.byName.powerInWatts.format(row.min); + } + }, + { + title: 'Max power', + width: '100px', + value(row) { + return tr.b.Unit.byName.powerInWatts.format(row.max); + } + }, + { + title: 'Time-weighted average', + width: '100px', + value(row) { + return tr.b.Unit.byName.powerInWatts.format( + row.timeWeightedAverageInW); + } + }, + { + title: 'Energy consumed', + width: '100px', + value(row) { + return tr.b.Unit.byName.energyInJoules.format(row.energyConsumedInJ); + } + }, + { + title: 'Sample count', + width: '100%', + value(row) { return row.sampleCount; } + } + ]; + this.samples = new tr.model.EventSet(); + }, + + get samples() { + return this.samples_; + }, + + set samples(samples) { + if (samples === this.samples) return; + + this.samples_ = + (samples === undefined) ? new tr.model.EventSet() : samples; + this.updateContents_(); + }, + + updateContents_() { + if (this.samples.length === 0) { + this.$.table.tableRows = []; + } else { + this.$.table.tableRows = [{ + min: this.getMin(), + max: this.getMax(), + timeWeightedAverageInW: this.getTimeWeightedAverageInW(), + energyConsumedInJ: this.getEnergyConsumedInJ(), + sampleCount: this.samples.length + }]; + } + + this.$.table.rebuild(); + }, + + getMin() { + return Math.min.apply(null, this.samples.map(function(sample) { + return sample.powerInW; + })); + }, + + getMax() { + return Math.max.apply(null, this.samples.map(function(sample) { + return sample.powerInW; + })); + }, + + /** + * Returns a time-weighted average of the power consumption (Watts) + * in between the first sample (inclusive) and last sample (exclusive). + */ + getTimeWeightedAverageInW() { + const energyConsumedInJ = this.getEnergyConsumedInJ(); + + if (energyConsumedInJ === 'N/A') return 'N/A'; + + const durationInS = tr.b.convertUnit(this.samples.bounds.duration, + tr.b.UnitPrefixScale.METRIC.MILLI, + tr.b.UnitPrefixScale.METRIC.NONE); + + return energyConsumedInJ / durationInS; + }, + + + getEnergyConsumedInJ() { + if (this.samples.length < 2) return 'N/A'; + + const bounds = this.samples.bounds; + const series = tr.b.getFirstElement(this.samples).series; + return series.getEnergyConsumedInJ(bounds.min, bounds.max); + } +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/power_sample_summary_table_test.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/power_sample_summary_table_test.html new file mode 100644 index 00000000000..f5bf8c7d5a0 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/power_sample_summary_table_test.html @@ -0,0 +1,137 @@ +<!DOCTYPE html> +<!-- +Copyright 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/model/event_set.html"> +<link rel="import" href="/tracing/model/model.html"> +<link rel="import" href="/tracing/model/power_series.html"> +<link rel="import" href="/tracing/ui/analysis/power_sample_summary_table.html"> + +<script> +'use strict'; + +tr.b.unittest.testSuite(function() { + const EventSet = tr.model.EventSet; + const Model = tr.Model; + const PowerSeries = tr.model.PowerSeries; + + test('instantiate', function() { + const series = new PowerSeries(new Model().device); + + series.addPowerSample(0, 1); + series.addPowerSample(1000, 2); + series.addPowerSample(2000, 3); + series.addPowerSample(3000, 4); + + const table = document.createElement('tr-ui-a-power-sample-summary-table'); + table.samples = new EventSet(series.samples); + + this.addHTMLOutput(table); + }); + + test('setSamples_undefinedPowerSamples', function() { + const table = document.createElement('tr-ui-a-power-sample-summary-table'); + table.samples = undefined; + + assert.lengthOf(table.$.table.tableRows, 0); + }); + + test('setSamples_noPowerSamples', function() { + const table = document.createElement('tr-ui-a-power-sample-summary-table'); + table.samples = new EventSet([]); + + assert.lengthOf(table.$.table.tableRows, 0); + }); + + test('setSamples_onePowerSample', function() { + const series = new PowerSeries(new Model().device); + + series.addPowerSample(0, 1); + + const table = document.createElement('tr-ui-a-power-sample-summary-table'); + table.samples = new EventSet(series.samples); + + assert.lengthOf(table.$.table.tableRows, 1); + assert.strictEqual(table.$.table.tableRows[0].min, 1); + assert.strictEqual(table.$.table.tableRows[0].max, 1); + assert.strictEqual( + table.$.table.tableRows[0].timeWeightedAverageInW, 'N/A'); + assert.strictEqual(table.$.table.tableRows[0].energyConsumedInJ, 'N/A'); + assert.strictEqual(table.$.table.tableRows[0].sampleCount, 1); + }); + + test('setSamples_twoPowerSamples', function() { + const series = new PowerSeries(new Model().device); + + series.addPowerSample(0, 1); + series.addPowerSample(1000, 2); + + const table = document.createElement('tr-ui-a-power-sample-summary-table'); + table.samples = new EventSet(series.samples); + + assert.lengthOf(table.$.table.tableRows, 1); + assert.strictEqual(table.$.table.tableRows[0].min, 1); + assert.strictEqual(table.$.table.tableRows[0].max, 2); + assert.strictEqual(table.$.table.tableRows[0].timeWeightedAverageInW, 1); + assert.strictEqual(table.$.table.tableRows[0].energyConsumedInJ, 1); + assert.strictEqual(table.$.table.tableRows[0].sampleCount, 2); + }); + + test('setSamples_threePowerSamples', function() { + const series = new PowerSeries(new Model().device); + + series.addPowerSample(0, 1); + series.addPowerSample(1000, 2); + series.addPowerSample(2000, 3); + + const table = document.createElement('tr-ui-a-power-sample-summary-table'); + table.samples = new EventSet(series.samples); + + assert.lengthOf(table.$.table.tableRows, 1); + assert.strictEqual(table.$.table.tableRows[0].min, 1); + assert.strictEqual(table.$.table.tableRows[0].max, 3); + assert.strictEqual(table.$.table.tableRows[0].timeWeightedAverageInW, 1.5); + assert.strictEqual(table.$.table.tableRows[0].energyConsumedInJ, 3); + assert.strictEqual(table.$.table.tableRows[0].sampleCount, 3); + }); + + test('setSamples_columnsInitialized', function() { + const series = new PowerSeries(new Model().device); + + series.addPowerSample(0, 1); + series.addPowerSample(1000, 2); + series.addPowerSample(2000, 3); + + const table = document.createElement('tr-ui-a-power-sample-summary-table'); + table.samples = new EventSet(series.samples); + + const row = table.$.table.tableRows[0]; + const columns = table.$.table.tableColumns; + + assert.lengthOf(columns, 5); + + assert.strictEqual(columns[0].title, 'Min power'); + assert.strictEqual(columns[0].width, '100px'); + assert.strictEqual(columns[0].value(row), '1.000 W'); + + assert.strictEqual(columns[1].title, 'Max power'); + assert.strictEqual(columns[1].width, '100px'); + assert.strictEqual(columns[1].value(row), '3.000 W'); + + assert.strictEqual(columns[2].title, 'Time-weighted average'); + assert.strictEqual(columns[2].width, '100px'); + assert.strictEqual(columns[2].value(row), '1.500 W'); + + assert.strictEqual(columns[3].title, 'Energy consumed'); + assert.strictEqual(columns[3].width, '100px'); + assert.strictEqual(columns[3].value(row), '3.000 J'); + + assert.strictEqual(columns[4].title, 'Sample count'); + assert.strictEqual(columns[4].width, '100%'); + assert.strictEqual(columns[4].value(row), 3); + }); +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/rebuildable_behavior.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/rebuildable_behavior.html new file mode 100644 index 00000000000..62abbe8076b --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/rebuildable_behavior.html @@ -0,0 +1,57 @@ +<!DOCTYPE html> +<!-- +Copyright 2016 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/raf.html"> + +<script> +'use strict'; + +tr.exportTo('tr.ui.analysis', function() { + const RebuildableBehavior = { + rebuild() { + /** + * Rebuild the pane if necessary. + * + * This method is not intended to be overriden by subclasses. Please + * override scheduleRebuild_() instead. + */ + if (!this.paneDirty_) { + // Avoid rebuilding unnecessarily as it breaks things like table + // selection. + return; + } + + this.paneDirty_ = false; + this.onRebuild_(); + }, + + /** + * Mark the UI state of the pane as dirty and schedule a rebuild. + * + * This method is intended to be called by subclasses. + */ + scheduleRebuild_() { + if (this.paneDirty_) return; + this.paneDirty_ = true; + tr.b.requestAnimationFrame(this.rebuild.bind(this)); + }, + + /** + * Called when the pane is dirty and a rebuild is triggered. + * + * This method is intended to be overriden by subclasses (instead of + * directly overriding rebuild()). + */ + onRebuild_() { + } + }; + + return { + RebuildableBehavior, + }; +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/rebuildable_behavior_test.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/rebuildable_behavior_test.html new file mode 100644 index 00000000000..2a2f083ceb3 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/rebuildable_behavior_test.html @@ -0,0 +1,67 @@ +<!DOCTYPE html> +<!-- +Copyright 2016 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/analysis/rebuildable_behavior.html"> + +<script> +'use strict'; + +tr.b.unittest.testSuite(function() { + Polymer({ + is: 'tr-ui-analysis-rebuildable-test-element', + behaviors: [tr.ui.analysis.RebuildableBehavior] + }); + + test('rebuild', function() { + const el = document.createElement( + 'tr-ui-analysis-rebuildable-test-element'); + let didFireOnRebuild; + el.onRebuild_ = function() { + assert.strictEqual(this, el); + didFireOnRebuild = true; + }; + + function checkManualRebuild(expectedDidFireOnRebuild) { + didFireOnRebuild = false; + el.rebuild(); + assert.strictEqual(didFireOnRebuild, expectedDidFireOnRebuild); + } + + function checkRAFRebuild(expectedDidFireOnRebuild) { + didFireOnRebuild = false; + tr.b.forcePendingRAFTasksToRun(); + assert.strictEqual(didFireOnRebuild, expectedDidFireOnRebuild); + } + + // No rebuilds should occur when not scheduled. + checkManualRebuild(false); + checkRAFRebuild(false); + + // Single rebuild should occur when scheduled once. + el.scheduleRebuild_(); + checkManualRebuild(true); + checkManualRebuild(false); + + el.scheduleRebuild_(); + checkRAFRebuild(true); + checkRAFRebuild(false); + + // Only a single rebuild should occur even when scheduled multiple times. + el.scheduleRebuild_(); + el.scheduleRebuild_(); + checkManualRebuild(true); + checkRAFRebuild(false); + checkManualRebuild(false); + + el.scheduleRebuild_(); + el.scheduleRebuild_(); + checkRAFRebuild(true); + checkRAFRebuild(false); + checkManualRebuild(false); + }); +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/related_events.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/related_events.html new file mode 100644 index 00000000000..b4036837bf5 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/related_events.html @@ -0,0 +1,354 @@ +<!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/base/math/range.html"> +<link rel="import" href="/tracing/base/task.html"> +<link rel="import" href="/tracing/model/event_set.html"> +<link rel="import" href="/tracing/ui/analysis/analysis_link.html"> +<link rel="import" href="/tracing/ui/analysis/flow_classifier.html"> +<link rel="import" href="/tracing/ui/base/dom_helpers.html"> +<link rel="import" href="/tracing/ui/base/table.html"> + +<dom-module id='tr-ui-a-related-events'> + <template> + <style> + :host { + display: flex; + flex-direction: column; + } + #table { + flex: 1 1 auto; + align-self: stretch; + font-size: 12px; + } + </style> + <tr-ui-b-table id="table"></tr-ui-b-table> + </template> +</dom-module> +<script> +'use strict'; + +function* getEventInFlowEvents(event) { + if (!event.inFlowEvents) return; + yield* event.inFlowEvents; +} + +function* getEventOutFlowEvents(event) { + if (!event.outFlowEvents) return; + yield* event.outFlowEvents; +} + +function* getEventAncestors(event) { + if (!event.enumerateAllAncestors) return; + yield* event.enumerateAllAncestors(); +} + +function* getEventDescendents(event) { + if (!event.enumerateAllDescendents) return; + yield* event.enumerateAllDescendents(); +} + +Polymer({ + is: 'tr-ui-a-related-events', + + ready() { + this.eventGroups_ = []; + this.cancelFunctions_ = []; + + this.$.table.tableColumns = [ + { + title: 'Event(s)', + value(row) { + const typeEl = document.createElement('span'); + typeEl.innerText = row.type; + if (row.tooltip) { + typeEl.title = row.tooltip; + } + return typeEl; + }, + width: '150px' + }, + { + title: 'Link', + width: '100%', + value(row) { + const linkEl = document.createElement('tr-ui-a-analysis-link'); + if (row.name) { + linkEl.setSelectionAndContent(row.selection, row.name); + } else { + linkEl.selection = row.selection; + } + return linkEl; + } + } + ]; + }, + + hasRelatedEvents() { + return (this.eventGroups_ && this.eventGroups_.length > 0); + }, + + setRelatedEvents(eventSet) { + this.cancelAllTasks_(); + this.eventGroups_ = []; + this.addRuntimeCallStats_(eventSet); + this.addOverlappingV8ICStats_(eventSet); + this.addV8GCObjectStats_(eventSet); + this.addV8Slices_(eventSet); + this.addConnectedFlows_(eventSet); + this.addConnectedEvents_(eventSet); + this.addOverlappingSamples_(eventSet); + this.updateContents_(); + }, + + addConnectedFlows_(eventSet) { + const classifier = new tr.ui.analysis.FlowClassifier(); + eventSet.forEach(function(slice) { + if (slice.inFlowEvents) { + slice.inFlowEvents.forEach(function(flow) { + classifier.addInFlow(flow); + }); + } + if (slice.outFlowEvents) { + slice.outFlowEvents.forEach(function(flow) { + classifier.addOutFlow(flow); + }); + } + }); + if (!classifier.hasEvents()) return; + + const addToEventGroups = function(type, flowEvent) { + this.eventGroups_.push({ + type, + selection: new tr.model.EventSet(flowEvent), + name: flowEvent.title + }); + }; + + classifier.inFlowEvents.forEach( + addToEventGroups.bind(this, 'Incoming flow')); + classifier.outFlowEvents.forEach( + addToEventGroups.bind(this, 'Outgoing flow')); + classifier.internalFlowEvents.forEach( + addToEventGroups.bind(this, 'Internal flow')); + }, + + cancelAllTasks_() { + this.cancelFunctions_.forEach(function(cancelFunction) { + cancelFunction(); + }); + this.cancelFunctions_ = []; + }, + + addConnectedEvents_(eventSet) { + this.cancelFunctions_.push(this.createEventsLinkIfNeeded_( + 'Preceding events', + 'Add all events that have led to the selected one(s), connected by ' + + 'flow arrows or by call stack.', + eventSet, + function* (event) { + yield* getEventInFlowEvents(event); + yield* getEventAncestors(event); + if (event.startSlice) { + yield event.startSlice; + } + }.bind(this))); + this.cancelFunctions_.push(this.createEventsLinkIfNeeded_( + 'Following events', + 'Add all events that have been caused by the selected one(s), ' + + 'connected by flow arrows or by call stack.', + eventSet, + function* (event) { + yield* getEventOutFlowEvents(event); + yield* getEventDescendents(event); + if (event.endSlice) { + yield event.endSlice; + } + }.bind(this))); + this.cancelFunctions_.push(this.createEventsLinkIfNeeded_( + 'All connected events', + 'Add all events connected to the selected one(s) by flow arrows or ' + + 'by call stack.', + eventSet, + function* (event) { + yield* getEventInFlowEvents(event); + yield* getEventOutFlowEvents(event); + yield* getEventAncestors(event); + yield* getEventDescendents(event); + if (event.startSlice) { + yield event.startSlice; + } + if (event.endSlice) { + yield event.endSlice; + } + }.bind(this))); + }, + + createEventsLinkIfNeeded_(title, tooltip, events, connectedFn) { + events = new tr.model.EventSet(events); + const eventsToProcess = new Set(events); + // for (let event of events) + // eventsToProcess.add(event); + let wasChanged = false; + let task; + let isCanceled = false; + function addEventsUntilTimeout() { + if (isCanceled) return; + // Let's grant ourselves a budget of 8 ms. If time runs out, then + // create another task to do the rest. + const timeout = window.performance.now() + 8; + // TODO(alexandermont): Don't check window.performance.now + // every iteration. + while (eventsToProcess.size > 0 && + window.performance.now() <= timeout) { + // Get the next event. + const nextEvent = tr.b.getFirstElement(eventsToProcess); + eventsToProcess.delete(nextEvent); + + // Add the connected events to the list. + for (const eventToAdd of connectedFn(nextEvent)) { + if (!events.contains(eventToAdd)) { + events.push(eventToAdd); + eventsToProcess.add(eventToAdd); + wasChanged = true; + } + } + } + if (eventsToProcess.size > 0) { + // There are still events to process, but we ran out of time. Post + // more work for later. + const newTask = new tr.b.Task( + addEventsUntilTimeout.bind(this), this); + task.after(newTask); + task = newTask; + return; + } + // Went through all events, add the link. + if (!wasChanged) return; + this.eventGroups_.push({ + type: title, + tooltip, + selection: events + }); + this.updateContents_(); + } + function cancelTask() { + isCanceled = true; + } + task = new tr.b.Task(addEventsUntilTimeout.bind(this), this); + tr.b.Task.RunWhenIdle(task); + return cancelTask; + }, + + addOverlappingSamples_(eventSet) { + const samples = new tr.model.EventSet(); + for (const slice of eventSet) { + if (!slice.parentContainer || !slice.parentContainer.samples) { + continue; + } + const candidates = slice.parentContainer.samples; + const range = tr.b.math.Range.fromExplicitRange( + slice.start, slice.start + slice.duration); + const filteredSamples = range.filterArray( + candidates, function(value) {return value.start;}); + for (const sample of filteredSamples) { + samples.push(sample); + } + } + if (samples.length > 0) { + this.eventGroups_.push({ + type: 'Overlapping samples', + tooltip: 'All samples overlapping the selected slice(s).', + selection: samples + }); + } + }, + + addV8Slices_(eventSet) { + const v8Slices = new tr.model.EventSet(); + for (const slice of eventSet) { + if (slice.category === 'v8') { + v8Slices.push(slice); + } + } + if (v8Slices.length > 0) { + this.eventGroups_.push({ + type: 'V8 Slices', + tooltip: 'All V8 slices in the selected slice(s).', + selection: v8Slices + }); + } + }, + + addRuntimeCallStats_(eventSet) { + const slices = eventSet.filter(function(slice) { + return (slice.category === 'v8' || + slice.category === 'disabled-by-default-v8.runtime_stats') && + slice.runtimeCallStats; + }); + if (slices.length > 0) { + this.eventGroups_.push({ + type: 'Runtime call stats table', + // eslint-disable-next-line + tooltip: 'All V8 slices containing runtime call stats table in the selected slice(s).', + selection: slices + }); + } + }, + + addV8GCObjectStats_(eventSet) { + const slices = new tr.model.EventSet(); + for (const slice of eventSet) { + if (slice.title === 'V8.GC_Objects_Stats') { + slices.push(slice); + } + } + if (slices.length > 0) { + this.eventGroups_.push({ + type: 'V8 GC stats table', + tooltip: 'All V8 GC statistics slices in the selected set.', + selection: slices + }); + } + }, + + addOverlappingV8ICStats_(eventSet) { + const slices = new tr.model.EventSet(); + for (const slice of eventSet) { + if (!slice.parentContainer || !slice.parentContainer.sliceGroup) { + continue; + } + const sliceGroup = slice.parentContainer.sliceGroup.slices; + const range = tr.b.math.Range.fromExplicitRange( + slice.start, slice.start + slice.duration); + const filteredSlices = range.filterArray( + sliceGroup, value => value.start); + const icSlices = filteredSlices.filter(x => x.title === 'V8.ICStats'); + for (const icSlice of icSlices) { + slices.push(icSlice); + } + } + if (slices.length > 0) { + this.eventGroups_.push({ + type: 'Overlapping V8 IC stats', + tooltip: 'All V8 IC statistics overlapping the selected set.', + selection: slices + }); + } + }, + + updateContents_() { + const table = this.$.table; + if (this.eventGroups_ === undefined) { + table.tableRows = []; + } else { + table.tableRows = this.eventGroups_.slice(); + } + table.rebuild(); + } +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/related_events_test.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/related_events_test.html new file mode 100644 index 00000000000..5d14d68faa3 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/related_events_test.html @@ -0,0 +1,221 @@ +<!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/base/raf.html"> +<link rel="import" href="/tracing/base/utils.html"> +<link rel="import" href="/tracing/core/test_utils.html"> +<link rel="import" href="/tracing/model/event_set.html"> +<link rel="import" href="/tracing/model/model.html"> +<link rel="import" href="/tracing/model/sample.html"> +<link rel="import" href="/tracing/model/thread_slice.html"> +<link rel="import" href="/tracing/ui/analysis/related_events.html"> + +<script> +'use strict'; + +tr.b.unittest.testSuite(function() { + const newSliceEx = tr.c.TestUtils.newSliceEx; + const newFlowEventEx = tr.c.TestUtils.newFlowEventEx; + + function createModel() { + const m = tr.c.TestUtils.newModel(function(m) { + m.p1 = m.getOrCreateProcess(1); + + m.t2 = m.p1.getOrCreateThread(2); + m.t3 = m.p1.getOrCreateThread(3); + m.t4 = m.p1.getOrCreateThread(4); + const node = tr.c.TestUtils.newProfileNodes(m, ['fake']); + + // Setup samples and slices in this way: + // 0 5 10 15 20 + // _____________________________ + // t2 * + // [ a ][ ]aa + // ----------------------------- + // t3 * * * * * + // * * + // [ b ] + // [bb] + // []bbb + // ----------------------------- + // t4 |c + // ----------------------------- + m.samples.push( + new tr.model.Sample(10, 'b10_1', node, m.t3), + new tr.model.Sample(7, 'b7', node, m.t3), + new tr.model.Sample(12, 'b12', node, m.t3), + new tr.model.Sample(20, 'b20', node, m.t3), + new tr.model.Sample(10, 'b10_2', node, m.t3), + new tr.model.Sample(15, 'b15_1', node, m.t3), + new tr.model.Sample(15, 'b15_2', node, m.t3), + new tr.model.Sample(12, 'a12', node, m.t2) + ); + + m.sA = m.t2.sliceGroup.pushSlice( + newSliceEx({title: 'a', start: 0, end: 5, + type: tr.model.ThreadSlice})); + m.sAA = m.t2.sliceGroup.pushSlice( + newSliceEx({title: 'aa', start: 6, end: 8, + type: tr.model.ThreadSlice})); + m.sB = m.t3.sliceGroup.pushSlice( + newSliceEx({title: 'b', start: 10, end: 15, + type: tr.model.ThreadSlice})); + m.sBB = m.t3.sliceGroup.pushSlice( + newSliceEx({title: 'bb', start: 11, end: 14, + type: tr.model.ThreadSlice})); + m.sBBB = m.t3.sliceGroup.pushSlice( + newSliceEx({title: 'bbb', start: 12, end: 13, + type: tr.model.ThreadSlice})); + m.sC = m.t4.sliceGroup.pushSlice( + newSliceEx({title: 'c', start: 20, end: 20, + type: tr.model.ThreadSlice})); + + m.t2.createSubSlices(); + m.t3.createSubSlices(); + m.t4.createSubSlices(); + + // Add flow events. + m.f0 = newFlowEventEx({ + title: 'a_aa', start: 5, end: 6, + startSlice: m.sA, + endSlice: m.sAA + }); + m.f1 = newFlowEventEx({ + title: 'a_b', start: 0, end: 10, + startSlice: m.sA, + endSlice: m.sB + }); + m.f2 = newFlowEventEx({ + title: 'b_bbb', start: 10, end: 12, + startSlice: m.sB, + endSlice: m.sBBB + }); + m.f3 = newFlowEventEx({ + title: 'bbb_c', start: 13, end: 20, + startSlice: m.sBBB, + endSlice: m.sC + }); + }); + return m; + } + + test('instantiate', function() { + const m = createModel(); + + const viewEl = document.createElement('tr-ui-a-related-events'); + const selection = new tr.model.EventSet( + [m.sA, m.f0, m.sAA, m.f1, m.sB, m.f2, m.sBB, m.sBBB, m.f3, m.sC]); + viewEl.setRelatedEvents(selection); + this.addHTMLOutput(viewEl); + tr.b.forceAllPendingTasksToRunForTest(); + + // Check that the element handles multiple setRelatedEvents calls correctly. + assert.lengthOf(viewEl.$.table.tableRows, 5); + viewEl.setRelatedEvents(selection); + assert.lengthOf(viewEl.$.table.tableRows, 5); + }); + + test('validateFlows', function() { + const m = createModel(); + + const viewEl = document.createElement('tr-ui-a-related-events'); + viewEl.setRelatedEvents(new tr.model.EventSet([m.sB, m.sBB, m.sBBB])); + this.addHTMLOutput(viewEl); + tr.b.forceAllPendingTasksToRunForTest(); + + let inFlows; + let outFlows; + let internalFlows; + viewEl.$.table.tableRows.forEach(function(row) { + if (row.type === 'Incoming flow') { + assert.isUndefined(inFlows); + inFlows = row.selection; + } + if (row.type === 'Outgoing flow') { + assert.isUndefined(outFlows); + outFlows = row.selection; + } + if (row.type === 'Internal flow') { + assert.isUndefined(internalFlows); + internalFlows = row.selection; + } + }); + assert.strictEqual(inFlows.length, 1); + assert.strictEqual(tr.b.getOnlyElement(inFlows).title, 'a_b'); + assert.strictEqual(outFlows.length, 1); + assert.strictEqual(tr.b.getOnlyElement(outFlows).title, 'bbb_c'); + assert.strictEqual(internalFlows.length, 1); + assert.strictEqual(tr.b.getOnlyElement(internalFlows).title, 'b_bbb'); + }); + + test('validateConnectedEvents', function() { + const m = createModel(); + + const viewEl = document.createElement('tr-ui-a-related-events'); + viewEl.setRelatedEvents(new tr.model.EventSet([m.sBB])); + this.addHTMLOutput(viewEl); + tr.b.forceAllPendingTasksToRunForTest(); + + let precedingEvents; + let followingEvents; + let allEvents; + viewEl.$.table.tableRows.forEach(function(row) { + if (row.type === 'Preceding events') { + assert.isUndefined(precedingEvents); + precedingEvents = row.selection; + } + if (row.type === 'Following events') { + assert.isUndefined(followingEvents); + followingEvents = row.selection; + } + if (row.type === 'All connected events') { + assert.isUndefined(allEvents); + allEvents = row.selection; + } + }); + + const precedingTitles = precedingEvents.map(function(e) { + return e.title; + }); + assert.sameMembers(precedingTitles, ['a', 'a_b', 'b', 'bb']); + + const followingTitles = followingEvents.map(function(e) { + return e.title; + }); + assert.sameMembers(followingTitles, ['bb', 'bbb', 'bbb_c', 'c']); + + const allTitles = allEvents.map(function(e) { + return e.title; + }); + assert.sameMembers(allTitles, + ['a', 'a_aa', 'aa', 'a_b', 'b', 'bb', 'bbb', 'b_bbb', 'bbb_c', 'c']); + }); + + test('validateOverlappingSamples', function() { + const m = createModel(); + + const viewEl = document.createElement('tr-ui-a-related-events'); + viewEl.setRelatedEvents(new tr.model.EventSet([m.sB])); + this.addHTMLOutput(viewEl); + tr.b.forceAllPendingTasksToRunForTest(); + + let overlappingSamples; + viewEl.$.table.tableRows.forEach(function(row) { + if (row.type === 'Overlapping samples') { + assert.isUndefined(overlappingSamples); + overlappingSamples = row.selection; + } + }); + + const samplesTitles = overlappingSamples.map(function(e) { + return e.title; + }); + assert.sameMembers(samplesTitles, + ['b10_1', 'b10_2', 'b12', 'b15_1', 'b15_2']); + }); +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/selection_summary_table.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/selection_summary_table.html new file mode 100644 index 00000000000..68ca4d533d4 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/selection_summary_table.html @@ -0,0 +1,97 @@ +<!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/base.html"> +<link rel="import" href="/tracing/base/unit.html"> +<link rel="import" href="/tracing/ui/base/table.html"> +<link rel="import" href="/tracing/value/ui/scalar_span.html"> + +<dom-module id='tr-ui-a-selection-summary-table'> + <template> + <style> + :host { + display: flex; + } + #table { + flex: 1 1 auto; + align-self: stretch; + font-size: 12px; + } + </style> + <tr-ui-b-table id="table"> + </tr-ui-b-table> + </div> + </template> +</dom-module> +<script> +'use strict'; + +Polymer({ + is: 'tr-ui-a-selection-summary-table', + created() { + this.selection_ = new tr.b.math.Range(); + }, + + ready() { + this.$.table.showHeader = false; + this.$.table.tableColumns = [ + { + title: 'Name', + value(row) { return row.title; }, + width: '350px' + }, + { + title: 'Value', + width: '100%', + value(row) { + return row.value; + } + } + ]; + }, + + get selection() { + return this.selection_; + }, + + set selection(selection) { + this.selection_ = selection; + this.updateContents_(); + }, + + updateContents_() { + const selection = this.selection_; + const rows = []; + let hasRange; + if (this.selection_ && (!selection.bounds.isEmpty)) { + hasRange = true; + } else { + hasRange = false; + } + + rows.push({ + title: 'Selection start', + value: hasRange ? tr.v.ui.createScalarSpan( + selection.bounds.min, { + unit: tr.b.Unit.byName.timeStampInMs, + ownerDocument: this.ownerDocument + }) : '<empty>' + }); + rows.push({ + title: 'Selection extent', + value: hasRange ? tr.v.ui.createScalarSpan( + selection.bounds.range, { + unit: tr.b.Unit.byName.timeDurationInMs, + ownerDocument: this.ownerDocument + }) : '<empty>' + }); + + this.$.table.tableRows = rows; + this.$.table.rebuild(); + } +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/selection_summary_table_test.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/selection_summary_table_test.html new file mode 100644 index 00000000000..20a8daf3b47 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/selection_summary_table_test.html @@ -0,0 +1,75 @@ +<!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/unit.html"> +<link rel="import" href="/tracing/core/test_utils.html"> +<link rel="import" href="/tracing/model/event_set.html"> +<link rel="import" href="/tracing/model/model.html"> +<link rel="import" href="/tracing/ui/analysis/selection_summary_table.html"> +<link rel="import" href="/tracing/ui/base/deep_utils.html"> + +<script> +'use strict'; + +tr.b.unittest.testSuite(function() { + const Model = tr.Model; + const EventSet = tr.model.EventSet; + const newSliceEx = tr.c.TestUtils.newSliceEx; + + test('noSelection', function() { + const summaryTable = + document.createElement('tr-ui-a-selection-summary-table'); + summaryTable.selection = undefined; + this.addHTMLOutput(summaryTable); + + const tableEl = tr.ui.b.findDeepElementMatching( + summaryTable, 'tr-ui-b-table'); + assert.strictEqual(tableEl.tableRows[0].value, '<empty>'); + assert.strictEqual(tableEl.tableRows[1].value, '<empty>'); + }); + + test('emptySelection', function() { + const summaryTable = + document.createElement('tr-ui-a-selection-summary-table'); + const selection = new EventSet(); + summaryTable.selection = selection; + this.addHTMLOutput(summaryTable); + + const tableEl = tr.ui.b.findDeepElementMatching( + summaryTable, 'tr-ui-b-table'); + assert.strictEqual(tableEl.tableRows[0].value, '<empty>'); + assert.strictEqual(tableEl.tableRows[1].value, '<empty>'); + }); + + test('selection', function() { + const model = new Model(); + const thread = model.getOrCreateProcess(1).getOrCreateThread(2); + const tsg = thread.sliceGroup; + + tsg.pushSlice(newSliceEx({title: 'a', start: 0, end: 3})); + tsg.pushSlice(newSliceEx({title: 'b', start: 1, end: 2})); + + const selection = new EventSet(); + selection.push(tsg.slices[0]); + selection.push(tsg.slices[1]); + + const summaryTable = + document.createElement('tr-ui-a-selection-summary-table'); + summaryTable.selection = selection; + this.addHTMLOutput(summaryTable); + + const tableEl = tr.ui.b.findDeepElementMatching( + summaryTable, 'tr-ui-b-table'); + assert.strictEqual(tableEl.tableRows[0].value.value, 0); + assert.strictEqual(tableEl.tableRows[0].value.unit, + tr.b.Unit.byName.timeStampInMs); + assert.strictEqual(tableEl.tableRows[1].value.value, 3); + assert.strictEqual(tableEl.tableRows[1].value.unit, + tr.b.Unit.byName.timeDurationInMs); + }); +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_async_slice_sub_view.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_async_slice_sub_view.html new file mode 100644 index 00000000000..cc7f7b840dd --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_async_slice_sub_view.html @@ -0,0 +1,79 @@ +<!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/utils.html"> +<link rel="import" href="/tracing/ui/analysis/analysis_sub_view.html"> +<link rel="import" href="/tracing/ui/analysis/related_events.html"> +<link rel="import" href="/tracing/ui/analysis/single_event_sub_view.html"> + +<dom-module id='tr-ui-a-single-async-slice-sub-view'> + <template> + <style> + :host { + display: flex; + flex-direction: row; + } + #events { + display:flex; + flex-direction: column; + } + </style> + <tr-ui-a-single-event-sub-view id="content"></tr-ui-a-single-event-sub-view> + <div id="events"> + <tr-ui-a-related-events id="relatedEvents"></tr-ui-a-related-events> + </div> + </template> +</dom-module> +<script> +'use strict'; + +Polymer({ + is: 'tr-ui-a-single-async-slice-sub-view', + behaviors: [tr.ui.analysis.AnalysisSubView], + + get selection() { + return this.$.content.selection; + }, + + set selection(selection) { + if (selection.length !== 1) { + throw new Error('Only supports single slices'); + } + this.$.content.setSelectionWithoutErrorChecks(selection); + this.$.relatedEvents.setRelatedEvents(selection); + if (this.$.relatedEvents.hasRelatedEvents()) { + this.$.relatedEvents.style.display = ''; + } else { + this.$.relatedEvents.style.display = 'none'; + } + }, + + getEventRows_(event) { + // TODO(nduca): Figure out if there is a cleaner way to do this. + const rows = this.__proto__.__proto__.getEventRows_(event); + + // Put the ID up top. + rows.splice(0, 0, { + name: 'ID', + value: event.id + }); + return rows; + }, + + get relatedEventsToHighlight() { + if (!this.currentSelection_) return undefined; + return tr.b.getOnlyElement(this.currentSelection_).associatedEvents; + } +}); +tr.ui.analysis.AnalysisSubView.register( + 'tr-ui-a-single-async-slice-sub-view', + tr.model.AsyncSlice, + { + multi: false, + title: 'Async Slice', + }); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_async_slice_sub_view_test.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_async_slice_sub_view_test.html new file mode 100644 index 00000000000..6cd341011bb --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_async_slice_sub_view_test.html @@ -0,0 +1,41 @@ +<!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/core/test_utils.html"> +<link rel="import" href="/tracing/model/event_set.html"> +<link rel="import" href="/tracing/model/model.html"> +<link rel="import" href="/tracing/ui/analysis/single_async_slice_sub_view.html"> + +<script> +'use strict'; + +tr.b.unittest.testSuite(function() { + const newAsyncSliceEx = tr.c.TestUtils.newAsyncSliceEx; + + test('instantiate', function() { + const model = new tr.Model(); + const p1 = model.getOrCreateProcess(1); + const t1 = p1.getOrCreateThread(1); + t1.asyncSliceGroup.push(newAsyncSliceEx({ + id: 31415, + title: 'a', + start: 10, + duration: 20, + startThread: t1, + endThread: t1 + })); + + const selection = new tr.model.EventSet(); + selection.push(t1.asyncSliceGroup.slices[0]); + + const viewEl = document.createElement( + 'tr-ui-a-single-async-slice-sub-view'); + viewEl.selection = selection; + this.addHTMLOutput(viewEl); + }); +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_cpu_slice_sub_view.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_cpu_slice_sub_view.html new file mode 100644 index 00000000000..12040ca9bad --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_cpu_slice_sub_view.html @@ -0,0 +1,149 @@ +<!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/unit.html"> +<link rel="import" href="/tracing/base/utils.html"> +<link rel="import" href="/tracing/model/event_set.html"> +<link rel="import" href="/tracing/ui/analysis/analysis_link.html"> +<link rel="import" href="/tracing/ui/analysis/analysis_sub_view.html"> +<link rel="import" href="/tracing/value/ui/scalar_span.html"> + +<dom-module id='tr-ui-a-single-cpu-slice-sub-view'> + <template> + <style> + table { + border-collapse: collapse; + border-width: 0; + margin-bottom: 25px; + width: 100%; + } + + table tr > td:first-child { + padding-left: 2px; + } + + table tr > td { + padding: 2px 4px 2px 4px; + vertical-align: text-top; + width: 150px; + } + + table td td { + padding: 0 0 0 0; + width: auto; + } + tr { + vertical-align: top; + } + + tr:nth-child(2n+0) { + background-color: #e2e2e2; + } + </style> + <table> + <tr> + <td>Running process:</td><td id="process-name"></td> + </tr> + <tr> + <td>Running thread:</td><td id="thread-name"></td> + </tr> + <tr> + <td>Start:</td> + <td> + <tr-v-ui-scalar-span id="start"> + </tr-v-ui-scalar-span> + </td> + </tr> + <tr> + <td>Duration:</td> + <td> + <tr-v-ui-scalar-span id="duration"> + </tr-v-ui-scalar-span> + </td> + </tr> + <tr> + <td>Active slices:</td><td id="running-thread"></td> + </tr> + <tr> + <td>Args:</td> + <td> + <tr-ui-a-generic-object-view id="args"> + </tr-ui-a-generic-object-view> + </td> + </tr> + </table> + </template> +</dom-module> +<script> +'use strict'; + +Polymer({ + is: 'tr-ui-a-single-cpu-slice-sub-view', + behaviors: [tr.ui.analysis.AnalysisSubView], + + created() { + this.currentSelection_ = undefined; + }, + + get selection() { + return this.currentSelection_; + }, + + set selection(selection) { + const cpuSlice = tr.b.getOnlyElement(selection); + if (!(cpuSlice instanceof tr.model.CpuSlice)) { + throw new Error('Only supports thread time slices'); + } + + this.currentSelection_ = selection; + + const thread = cpuSlice.threadThatWasRunning; + + const root = Polymer.dom(this.root); + if (thread) { + Polymer.dom(root.querySelector('#process-name')).textContent = + thread.parent.userFriendlyName; + Polymer.dom(root.querySelector('#thread-name')).textContent = + thread.userFriendlyName; + } else { + root.querySelector('#process-name').parentElement.style.display = + 'none'; + Polymer.dom(root.querySelector('#thread-name')).textContent = + cpuSlice.title; + } + + root.querySelector('#start').setValueAndUnit( + cpuSlice.start, tr.b.Unit.byName.timeStampInMs); + root.querySelector('#duration').setValueAndUnit( + cpuSlice.duration, tr.b.Unit.byName.timeDurationInMs); + + const runningThreadEl = root.querySelector('#running-thread'); + + const timeSlice = cpuSlice.getAssociatedTimeslice(); + if (!timeSlice) { + runningThreadEl.parentElement.style.display = 'none'; + } else { + const threadLink = document.createElement('tr-ui-a-analysis-link'); + threadLink.selection = new tr.model.EventSet(timeSlice); + Polymer.dom(threadLink).textContent = 'Click to select'; + runningThreadEl.parentElement.style.display = ''; + Polymer.dom(runningThreadEl).textContent = ''; + Polymer.dom(runningThreadEl).appendChild(threadLink); + } + + root.querySelector('#args').object = cpuSlice.args; + } +}); + +tr.ui.analysis.AnalysisSubView.register( + 'tr-ui-a-single-cpu-slice-sub-view', + tr.model.CpuSlice, + { + multi: false, + title: 'CPU Slice', + }); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_cpu_slice_sub_view_test.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_cpu_slice_sub_view_test.html new file mode 100644 index 00000000000..ee47fe5c58a --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_cpu_slice_sub_view_test.html @@ -0,0 +1,80 @@ +<!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/core/test_utils.html"> +<link rel="import" href="/tracing/extras/importer/linux_perf/ftrace_importer.html"> +<link rel="import" href="/tracing/model/event_set.html"> +<link rel="import" href="/tracing/model/model.html"> +<link rel="import" href="/tracing/ui/analysis/single_cpu_slice_sub_view.html"> + +<script> +'use strict'; + +tr.b.unittest.testSuite(function() { + function createBasicModel() { + const lines = [ + 'Android.launcher-584 [001] d..3 12622.506890: sched_switch: prev_comm=Android.launcher prev_pid=584 prev_prio=120 prev_state=R+ ==> next_comm=Binder_1 next_pid=217 next_prio=120', // @suppress longLineCheck + ' Binder_1-217 [001] d..3 12622.506918: sched_switch: prev_comm=Binder_1 prev_pid=217 prev_prio=120 prev_state=D ==> next_comm=Android.launcher next_pid=584 next_prio=120', // @suppress longLineCheck + 'Android.launcher-584 [001] d..4 12622.506936: sched_wakeup: comm=Binder_1 pid=217 prio=120 success=1 target_cpu=001', // @suppress longLineCheck + 'Android.launcher-584 [001] d..3 12622.506950: sched_switch: prev_comm=Android.launcher prev_pid=584 prev_prio=120 prev_state=R+ ==> next_comm=Binder_1 next_pid=217 next_prio=120', // @suppress longLineCheck + ' Binder_1-217 [001] ...1 12622.507057: tracing_mark_write: B|128|queueBuffer', // @suppress longLineCheck + ' Binder_1-217 [001] ...1 12622.507175: tracing_mark_write: E', + ' Binder_1-217 [001] d..3 12622.507253: sched_switch: prev_comm=Binder_1 prev_pid=217 prev_prio=120 prev_state=S ==> next_comm=Android.launcher next_pid=584 next_prio=120' // @suppress longLineCheck + ]; + + return tr.c.TestUtils.newModelWithEvents([lines.join('\n')], { + shiftWorldToZero: false + }); + } + + test('cpuSliceView_withCpuSliceOnExistingThread', function() { + const m = createBasicModel(); + + const cpu = m.kernel.cpus[1]; + assert.isDefined(cpu); + const cpuSlice = cpu.slices[0]; + assert.strictEqual('Binder_1', cpuSlice.title); + + const thread = m.findAllThreadsNamed('Binder_1')[0]; + assert.isDefined(thread); + assert.strictEqual(cpuSlice.threadThatWasRunning, thread); + + const view = document.createElement('tr-ui-a-single-cpu-slice-sub-view'); + const selection = new tr.model.EventSet(); + selection.push(cpuSlice); + view.selection = selection; + this.addHTMLOutput(view); + + // Clicking the analysis link should focus the Binder1's timeslice. + let didSelectionChangeHappen = false; + view.addEventListener('requestSelectionChange', function(e) { + assert.isTrue(e.selection.equals( + new tr.model.EventSet(thread.timeSlices[0]))); + didSelectionChangeHappen = true; + }); + Polymer.dom(view.root).querySelector('tr-ui-a-analysis-link').click(); + assert.isTrue(didSelectionChangeHappen); + }); + + test('cpuSliceViewWithCpuSliceOnMissingThread', function() { + const m = createBasicModel(); + + const cpu = m.kernel.cpus[1]; + assert.isDefined(cpu); + const cpuSlice = cpu.slices[1]; + assert.strictEqual('Android.launcher', cpuSlice.title); + assert.isUndefined(cpuSlice.thread); + + const selection = new tr.model.EventSet(); + selection.push(cpuSlice); + + const view = document.createElement('tr-ui-a-single-cpu-slice-sub-view'); + view.selection = selection; + this.addHTMLOutput(view); + }); +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_event_sub_view.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_event_sub_view.html new file mode 100644 index 00000000000..d49125af9e3 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_event_sub_view.html @@ -0,0 +1,356 @@ +<!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/base.html"> +<link rel="import" href="/tracing/base/unit.html"> +<link rel="import" href="/tracing/base/utils.html"> +<link rel="import" href="/tracing/model/event_set.html"> +<link rel="import" href="/tracing/ui/analysis/analysis_sub_view.html"> +<link rel="import" href="/tracing/ui/analysis/generic_object_view.html"> +<link rel="import" href="/tracing/ui/analysis/stack_frame.html"> +<link rel="import" href="/tracing/ui/base/table.html"> +<link rel="import" href="/tracing/ui/base/ui.html"> +<link rel="import" href="/tracing/value/ui/scalar_span.html"> + +<dom-module id='tr-ui-a-single-event-sub-view'> + <template> + <style> + :host { + display: flex; + flex: 0 1; + flex-direction: column; + } + #table { + flex: 0 1 auto; + align-self: stretch; + font-size: 12px; + } + </style> + <tr-ui-b-table id="table"> + </tr-ui-b-table> + </template> +</dom-module> +<script> +'use strict'; + +Polymer({ + is: 'tr-ui-a-single-event-sub-view', + behaviors: [tr.ui.analysis.AnalysisSubView], + + properties: { + isFlow: { + type: Boolean, + value: false + } + }, + + ready() { + this.currentSelection_ = undefined; + this.$.table.tableColumns = [ + { + title: 'Label', + value(row) { return row.name; }, + width: '150px' + }, + { + title: 'Value', + width: '100%', + value(row) { return row.value; } + } + ]; + this.$.table.showHeader = false; + }, + + get selection() { + return this.currentSelection_; + }, + + set selection(selection) { + if (selection.length !== 1) { + throw new Error('Only supports single slices'); + } + this.setSelectionWithoutErrorChecks(selection); + }, + + setSelectionWithoutErrorChecks(selection) { + this.currentSelection_ = selection; + this.updateContents_(); + }, + + getFlowEventRows_(event) { + // TODO(nduca): Figure out if there is a cleaner way to do this. + + const rows = this.getEventRowsHelper_(event); + + // Put the ID up top. + rows.splice(0, 0, { + name: 'ID', + value: event.id + }); + + function createLinkTo(slice) { + const linkEl = document.createElement('tr-ui-a-analysis-link'); + linkEl.setSelectionAndContent(function() { + return new tr.model.EventSet(slice); + }); + Polymer.dom(linkEl).textContent = slice.userFriendlyName; + return linkEl; + } + + rows.push({ + name: 'From', + value: createLinkTo(event.startSlice) + }); + rows.push({ + name: 'To', + value: createLinkTo(event.endSlice) + }); + return rows; + }, + + getEventRowsHelper_(event) { + const rows = []; + + if (event.error) { + rows.push({ name: 'Error', value: event.error }); + } + + if (event.title) { + let title = event.title; + if (tr.isExported('tr-ui-e-chrome-codesearch')) { + const container = document.createElement('div'); + container.appendChild(document.createTextNode(title)); + const link = document.createElement('tr-ui-e-chrome-codesearch'); + link.searchPhrase = title; + container.appendChild(link); + title = container; + } + rows.push({ name: 'Title', value: title }); + } + + if (event.category) { + rows.push({ name: 'Category', value: event.category }); + } + + if (event.model !== undefined) { + const ufc = event.model.getUserFriendlyCategoryFromEvent(event); + if (ufc !== undefined) { + rows.push({ name: 'User Friendly Category', value: ufc }); + } + } + + if (event.name) { + rows.push({ name: 'Name', value: event.name }); + } + + rows.push({ + name: 'Start', + value: tr.v.ui.createScalarSpan(event.start, { + unit: tr.b.Unit.byName.timeStampInMs + }) + }); + + if (event.duration) { + rows.push({ + name: 'Wall Duration', + value: tr.v.ui.createScalarSpan(event.duration, { + unit: tr.b.Unit.byName.timeDurationInMs + }) + }); + } + + if (event.cpuDuration) { + rows.push({ + name: 'CPU Duration', + value: tr.v.ui.createScalarSpan(event.cpuDuration, { + unit: tr.b.Unit.byName.timeDurationInMs + }) + }); + } + + if (event.subSlices !== undefined && event.subSlices.length !== 0) { + if (event.selfTime) { + rows.push({ + name: 'Self Time', + value: tr.v.ui.createScalarSpan(event.selfTime, { + unit: tr.b.Unit.byName.timeDurationInMs + }) + }); + } + + if (event.cpuSelfTime) { + const cpuSelfTimeEl = tr.v.ui.createScalarSpan(event.cpuSelfTime, { + unit: tr.b.Unit.byName.timeDurationInMs + }); + if (event.cpuSelfTime > event.selfTime) { + cpuSelfTimeEl.warning = + ' Note that CPU Self Time is larger than Self Time. ' + + 'This is a known limitation of this system, which occurs ' + + 'due to several subslices, rounding issues, and imprecise ' + + 'time at which we get cpu- and real-time.'; + } + rows.push({ name: 'CPU Self Time', value: cpuSelfTimeEl }); + } + } + + if (event.durationInUserTime) { + rows.push({ + name: 'Duration (U)', + value: tr.v.ui.createScalarSpan(event.durationInUserTime, { + unit: tr.b.Unit.byName.timeDurationInMs + }) + }); + } + + function createStackFrameEl(sf) { + const sfEl = document.createElement('tr-ui-a-stack-frame'); + sfEl.stackFrame = sf; + return sfEl; + } + if (event.startStackFrame && event.endStackFrame) { + if (event.startStackFrame === event.endStackFrame) { + rows.push({name: 'Start+End Stack Trace', + value: createStackFrameEl(event.startStackFrame)}); + } else { + rows.push({ name: 'Start Stack Trace', + value: createStackFrameEl(event.startStackFrame)}); + rows.push({ name: 'End Stack Trace', + value: createStackFrameEl(event.endStackFrame)}); + } + } else if (event.startStackFrame) { + rows.push({ name: 'Start Stack Trace', + value: createStackFrameEl(event.startStackFrame)}); + } else if (event.endStackFrame) { + rows.push({ name: 'End Stack Trace', + value: createStackFrameEl(event.endStackFrame)}); + } + + if (event.info) { + const descriptionEl = tr.ui.b.createDiv({ + textContent: event.info.description, + maxWidth: '300px' + }); + rows.push({ + name: 'Description', + value: descriptionEl + }); + + + if (event.info.docLinks) { + event.info.docLinks.forEach(function(linkObject) { + const linkEl = document.createElement('a'); + linkEl.target = '_blank'; + linkEl.href = linkObject.href; + Polymer.dom(linkEl).textContent = Polymer.dom(linkObject).textContent; + rows.push({ + name: linkObject.label, + value: linkEl + }); + }); + } + } + + if (event.associatedAlerts.length) { + const alertSubRows = []; + event.associatedAlerts.forEach(function(alert) { + const linkEl = document.createElement('tr-ui-a-analysis-link'); + linkEl.setSelectionAndContent(function() { + return new tr.model.EventSet(alert); + }, alert.info.description); + alertSubRows.push({ + name: alert.title, + value: linkEl + }); + }); + + rows.push({ + name: 'Alerts', value: '', + isExpanded: true, subRows: alertSubRows + }); + } + return rows; + }, + + getEventRows_(event) { + if (this.isFlow) { + return this.getFlowEventRows_(event); + } + + return this.getEventRowsHelper_(event); + }, + + addArgsToRows_(rows, args) { + let n = 0; + for (const argName in args) { + n += 1; + } + if (n > 0) { + const subRows = []; + for (const argName in args) { + n += 1; + } + if (n > 0) { + const subRows = []; + for (const argName in args) { + const argView = + document.createElement('tr-ui-a-generic-object-view'); + argView.object = args[argName]; + subRows.push({name: argName, value: argView}); + } + rows.push({ + name: 'Args', + value: '', + isExpanded: true, + subRows + }); + } + } + }, + + addContextsToRows_(rows, contexts) { + if (contexts.length) { + const subRows = contexts.map(function(context) { + const contextView = + document.createElement('tr-ui-a-generic-object-view'); + contextView.object = context; + return {name: 'Context', value: contextView}; + }); + rows.push({ + name: 'Contexts', + value: '', + isExpanded: true, + subRows + }); + } + }, + + updateContents_() { + if (this.currentSelection_ === undefined) { + this.$.table.rows = []; + this.$.table.rebuild(); + return; + } + + const event = tr.b.getOnlyElement(this.currentSelection_); + + const rows = this.getEventRows_(event); + if (event.argsStripped) { + rows.push({ name: 'Args', value: 'Stripped' }); + } else { + this.addArgsToRows_(rows, event.args); + } + this.addContextsToRows_(rows, event.contexts); + + const customizeRowsEvent = new tr.b.Event('customize-rows'); + customizeRowsEvent.rows = rows; + this.dispatchEvent(customizeRowsEvent); + + this.$.table.tableRows = rows; + this.$.table.rebuild(); + } +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_event_sub_view_test.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_event_sub_view_test.html new file mode 100644 index 00000000000..41c42308e3e --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_event_sub_view_test.html @@ -0,0 +1,277 @@ +<!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/core/test_utils.html"> +<link rel="import" href="/tracing/model/event_set.html"> +<link rel="import" href="/tracing/model/model.html"> +<link rel="import" href="/tracing/ui/analysis/single_event_sub_view.html"> +<link rel="import" href="/tracing/ui/base/deep_utils.html"> + +<script> +'use strict'; + +tr.b.unittest.testSuite(function() { + const Model = tr.Model; + const Thread = tr.model.Thread; + const EventSet = tr.model.EventSet; + const newSliceEx = tr.c.TestUtils.newSliceEx; + + function createSelection(customizeThreadCallback) { + const model = tr.c.TestUtils.newModelWithEvents([], { + customizeModelCallback(model) { + const t53 = model.getOrCreateProcess(52).getOrCreateThread(53); + customizeThreadCallback(t53, model); + } + }); + + const t53 = model.processes[52].threads[53]; + const t53track = {}; + t53track.thread = t53; + + const selection = new EventSet(); + selection.push(t53.sliceGroup.slices[0]); + assert.strictEqual(selection.length, 1); + + return selection; + } + + function createSelectionWithSingleSlice(opt_options) { + const options = opt_options || {}; + return createSelection(function(t53, model) { + let fA; + let fB; + if (options.withStartStackFrame || options.withEndStackFrame) { + fA = tr.c.TestUtils.newStackTrace(model, ['a1', 'a2']); + fB = tr.c.TestUtils.newStackTrace(model, ['b1', 'b2']); + } + + const slice = newSliceEx({title: 'b', start: 0, duration: 0.002}); + slice.category = options.withCategory ? 'foo' : ''; + + if (options.withStartStackFrame) { + slice.startStackFrame = options.withStartStackFrame === 'a' ? fA : fB; + } + + if (options.withEndStackFrame) { + slice.endStackFrame = options.withEndStackFrame === 'a' ? fA : fB; + } + + t53.sliceGroup.pushSlice(slice); + }); + } + + test('instantiate_withSingleSlice', function() { + const selection = createSelectionWithSingleSlice(); + + const analysisEl = document.createElement('tr-ui-a-single-event-sub-view'); + analysisEl.selection = selection; + this.addHTMLOutput(analysisEl); + }); + + test('alerts', function() { + const slice = newSliceEx({title: 'b', start: 0, duration: 0.002}); + + const ALERT_INFO_1 = new tr.model.EventInfo( + 'Alert 1', 'Critical alert'); + + const alert = new tr.model.Alert(ALERT_INFO_1, 5, [slice]); + + const selection = new EventSet(); + selection.push(slice); + + const analysisEl = document.createElement('tr-ui-a-single-event-sub-view'); + analysisEl.selection = selection; + this.addHTMLOutput(analysisEl); + }); + + test('instantiate_withSingleSliceWithArg', function() { + const selection = createSelection(function(t53) { + const slice = newSliceEx({title: 'my_slice', start: 0, duration: 1.0}); + slice.args = { + 'complex': { + 'b': '2 as a string', + 'c': [3, 4, 5] + } + }; + t53.sliceGroup.pushSlice(slice); + }); + + const subView = document.createElement('tr-ui-a-single-event-sub-view'); + subView.selection = selection; + this.addHTMLOutput(subView); + + const gov = tr.ui.b.findDeepElementMatching(subView, + 'tr-ui-a-generic-object-view'); + assert.isDefined(gov); + }); + + + test('instantiate_withSingleSliceCategory', function() { + const selection = createSelectionWithSingleSlice({withCategory: true}); + + const analysisEl = document.createElement('tr-ui-a-single-event-sub-view'); + analysisEl.selection = selection; + this.addHTMLOutput(analysisEl); + }); + + test('instantiate_withSingleStartStackFrame', function() { + const selection = createSelectionWithSingleSlice( + {withStartStackFrame: 'a'}); + + const analysisEl = document.createElement('tr-ui-a-single-event-sub-view'); + analysisEl.selection = selection; + this.addHTMLOutput(analysisEl); + + const e = tr.ui.b.findDeepElementWithTextContent( + analysisEl, /Start Stack Trace/); + assert.isDefined(e); + assert.isDefined(Polymer.dom(e).nextSibling.children[0].stackFrame); + }); + + test('instantiate_withSingleEndStackFrame', function() { + const selection = createSelectionWithSingleSlice( + {withEndStackFrame: 'b'}); + + const analysisEl = document.createElement('tr-ui-a-single-event-sub-view'); + analysisEl.selection = selection; + this.addHTMLOutput(analysisEl); + + const e = tr.ui.b.findDeepElementWithTextContent( + analysisEl, /End Stack Trace/); + assert.isDefined(e); + assert.isDefined(Polymer.dom(e).nextSibling.children[0].stackFrame); + assert.strictEqual( + Polymer.dom(e).nextSibling.children[0].stackFrame.title, 'b2'); + }); + + test('instantiate_withDifferentStartAndEndStackFrames', function() { + const selection = createSelectionWithSingleSlice( + {withStartStackFrame: 'a', + withEndStackFrame: 'b'}); + + const analysisEl = document.createElement('tr-ui-a-single-event-sub-view'); + analysisEl.selection = selection; + this.addHTMLOutput(analysisEl); + + const eA = tr.ui.b.findDeepElementWithTextContent( + analysisEl, /Start Stack Trace/); + assert.isDefined(eA); + assert.isDefined(Polymer.dom(eA).nextSibling.children[0].stackFrame); + assert.strictEqual( + Polymer.dom(eA).nextSibling.children[0].stackFrame.title, 'a2'); + + const eB = tr.ui.b.findDeepElementWithTextContent( + analysisEl, /End Stack Trace/); + assert.isDefined(eB); + assert.isDefined(Polymer.dom(eB).nextSibling.children[0].stackFrame); + assert.strictEqual( + Polymer.dom(eB).nextSibling.children[0].stackFrame.title, 'b2'); + }); + + test('instantiate_withSameStartAndEndStackFrames', function() { + const selection = createSelectionWithSingleSlice( + {withStartStackFrame: 'a', + withEndStackFrame: 'a'}); + + const analysisEl = document.createElement('tr-ui-a-single-event-sub-view'); + analysisEl.selection = selection; + this.addHTMLOutput(analysisEl); + + const e = tr.ui.b.findDeepElementWithTextContent( + analysisEl, /Start\+End Stack Trace/); + assert.isDefined(e); + assert.isDefined(Polymer.dom(e).nextSibling.children[0].stackFrame); + assert.strictEqual( + Polymer.dom(e).nextSibling.children[0].stackFrame.title, 'a2'); + }); + + test('analyzeSelectionWithSingleSlice', function() { + const selection = createSelectionWithSingleSlice(); + const subView = document.createElement('tr-ui-a-single-event-sub-view'); + subView.selection = selection; + this.addHTMLOutput(subView); + + const table = tr.ui.b.findDeepElementMatching( + subView, 'tr-ui-b-table'); + assert.strictEqual(table.tableRows.length, 3); + if (tr.isExported('tr-ui-e-chrome-codesearch')) { + assert.strictEqual(table.tableRows[0].value.innerText, 'b'); + } else { + assert.strictEqual(table.tableRows[0].value, 'b'); + } + assert.strictEqual(table.tableRows[1].value.value, 0); + assert.strictEqual(table.tableRows[1].value.unit, + tr.b.Unit.byName.timeStampInMs); + assert.strictEqual(table.tableRows[2].value.value, 0.002); + assert.strictEqual(table.tableRows[2].value.unit, + tr.b.Unit.byName.timeDurationInMs); + }); + + test('analyzeSelectionWithSingleSliceCategory', function() { + const selection = createSelectionWithSingleSlice({withCategory: true}); + + const subView = document.createElement('tr-ui-a-single-event-sub-view'); + subView.selection = selection; + this.addHTMLOutput(subView); + + const table = tr.ui.b.findDeepElementMatching( + subView, 'tr-ui-b-table'); + assert.strictEqual(table.tableRows.length, 4); + if (tr.isExported('tr-ui-e-chrome-codesearch')) { + assert.strictEqual(table.tableRows[0].value.innerText, 'b'); + } else { + assert.strictEqual(table.tableRows[0].value, 'b'); + } + assert.strictEqual(table.tableRows[1].value, 'foo'); + assert.strictEqual(table.tableRows[2].value.value, 0); + assert.strictEqual(table.tableRows[2].value.unit, + tr.b.Unit.byName.timeStampInMs); + assert.strictEqual(table.tableRows[3].value.value, 0.002); + assert.strictEqual(table.tableRows[3].value.unit, + tr.b.Unit.byName.timeDurationInMs); + }); + + test('instantiate_withSingleSliceContainingIDRef', function() { + const model = new Model(); + const p1 = model.getOrCreateProcess(1); + const myObjectSlice = p1.objects.addSnapshot( + '0x1000', 'cat', 'my_object', 0); + + const t1 = p1.getOrCreateThread(1); + t1.sliceGroup.pushSlice(newSliceEx({title: 'b', start: 0, duration: 2})); + t1.sliceGroup.slices[0].args.my_object = myObjectSlice; + + const t1track = {}; + t1track.thread = t1; + + const selection = new EventSet(); + selection.push(t1.sliceGroup.slices[0]); + assert.strictEqual(selection.length, 1); + + const subView = document.createElement('tr-ui-a-single-event-sub-view'); + subView.selection = selection; + this.addHTMLOutput(subView); + + const analysisLink = tr.ui.b.findDeepElementMatching(subView, + 'tr-ui-a-analysis-link'); + assert.isDefined(analysisLink); + }); + + test('instantiate_withSingleSliceContainingInfo', function() { + const slice = newSliceEx({title: 'b', start: 0, duration: 1}); + slice.info = new tr.model.EventInfo( + 'Info title', 'Description'); + + const selection = new EventSet(); + selection.push(slice); + + const analysisEl = document.createElement('tr-ui-a-single-event-sub-view'); + analysisEl.selection = selection; + this.addHTMLOutput(analysisEl); + }); +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_flow_event_sub_view.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_flow_event_sub_view.html new file mode 100644 index 00000000000..b201b161ffd --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_flow_event_sub_view.html @@ -0,0 +1,82 @@ +<!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/utils.html"> +<link rel="import" href="/tracing/model/event_set.html"> +<link rel="import" href="/tracing/ui/analysis/analysis_link.html"> +<link rel="import" href="/tracing/ui/analysis/analysis_sub_view.html"> +<link rel="import" href="/tracing/ui/analysis/single_event_sub_view.html"> + +<dom-module id="tr-ui-a-single-flow-event-sub-view"> + <template> + <style> + :host { + display: block; + } + </style> + <tr-ui-a-single-event-sub-view id="singleEventSubView"> + </tr-ui-a-single-event-sub-view> + </template> +</dom-module> +<script> +'use strict'; + +function createAnalysisLinkTo(event) { + const linkEl = document.createElement('tr-ui-a-analysis-link'); + linkEl.setSelectionAndContent( + new tr.model.EventSet(event), event.userFriendlyName); + return linkEl; +} + +Polymer({ + is: 'tr-ui-a-single-flow-event-sub-view', + behaviors: [tr.ui.analysis.AnalysisSubView], + + listeners: { + 'singleEventSubView.customize-rows': 'onCustomizeRows_' + }, + + set selection(selection) { + this.currentSelection_ = selection; + this.$.singleEventSubView.setSelectionWithoutErrorChecks(selection); + }, + + get selection() { + return this.currentSelection_; + }, + + /** + * Event handler for an event that's fired after the single event sub view has + * finished row construction. This hook gives us the opportunity to customize + * the rows present in the sub view. + */ + onCustomizeRows_(e) { + const event = tr.b.getOnlyElement(this.currentSelection_); + const rows = e.rows; + + rows.unshift({ + name: 'ID', + value: event.id + }); + rows.push({ + name: 'From', + value: createAnalysisLinkTo(event.startSlice) + }); + rows.push({ + name: 'To', + value: createAnalysisLinkTo(event.endSlice) + }); + } +}); +tr.ui.analysis.AnalysisSubView.register( + 'tr-ui-a-single-flow-event-sub-view', + tr.model.FlowEvent, + { + multi: false, + title: 'Flow Event', + }); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_flow_event_sub_view_test.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_flow_event_sub_view_test.html new file mode 100644 index 00000000000..31e3eb18f25 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_flow_event_sub_view_test.html @@ -0,0 +1,53 @@ +<!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/core/test_utils.html"> +<link rel="import" href="/tracing/model/event_set.html"> +<link rel="import" href="/tracing/model/model.html"> +<link rel="import" href="/tracing/ui/analysis/analysis_view.html"> + +<script> +'use strict'; + +tr.b.unittest.testSuite(function() { + const Model = tr.Model; + const EventSet = tr.model.EventSet; + const TestUtils = tr.c.TestUtils; + + test('analyzeSelectionWithSingleEvent', function() { + const model = TestUtils.newModel(function(model) { + model.p1 = model.getOrCreateProcess(1); + model.t2 = model.p1.getOrCreateThread(model.p1); + model.sA = model.t2.sliceGroup.pushSlice(TestUtils.newSliceEx({ + title: 'a', start: 0, end: 2 + })); + model.sB = model.t2.sliceGroup.pushSlice(TestUtils.newSliceEx({ + title: 'b', start: 9, end: 11 + })); + model.fe = TestUtils.newFlowEventEx({ + cat: 'cat', + id: 1234, + title: 'MyFlow', + start: 1, + end: 10, + startSlice: model.sA, + endSlice: model.sB + }); + model.flowEvents.push(model.fe); + }); + + const selection = new EventSet(); + selection.push(model.fe); + assert.strictEqual(selection.length, 1); + + const subView = document.createElement('tr-ui-a-single-event-sub-view'); + subView.isFlow = true; + subView.selection = selection; + this.addHTMLOutput(subView); + }); +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_frame_sub_view.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_frame_sub_view.html new file mode 100644 index 00000000000..e89fa2626ef --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_frame_sub_view.html @@ -0,0 +1,61 @@ +<!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/base/utils.html"> +<link rel="import" href="/tracing/ui/analysis/alert_sub_view.html"> +<link rel="import" href="/tracing/ui/analysis/analysis_sub_view.html"> + +<dom-module id='tr-ui-a-single-frame-sub-view'> + <template> + <style> + :host { + display: flex; + flex-direction: column; + } + #asv { + flex: 0 0 auto; + align-self: stretch; + } + </style> + <tr-ui-a-alert-sub-view id="asv"> + </tr-ui-a-alert-sub-view> + </template> +</dom-module> +<script> +'use strict'; + +Polymer({ + is: 'tr-ui-a-single-frame-sub-view', + behaviors: [tr.ui.analysis.AnalysisSubView], + + ready() { + this.currentSelection_ = undefined; + }, + + get selection() { + return this.currentSelection_; + }, + + set selection(selection) { + this.currentSelection_ = selection; + this.$.asv.selection = tr.b.getOnlyElement(selection).associatedAlerts; + }, + + get relatedEventsToHighlight() { + if (!this.currentSelection_) return undefined; + return tr.b.getOnlyElement(this.currentSelection_).associatedEvents; + } +}); + +tr.ui.analysis.AnalysisSubView.register( + 'tr-ui-a-single-frame-sub-view', + tr.model.Frame, + { + multi: false, + title: 'Frame', + }); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_instant_event_sub_view.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_instant_event_sub_view.html new file mode 100644 index 00000000000..43b0e8a80cd --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_instant_event_sub_view.html @@ -0,0 +1,63 @@ +<!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/ui/analysis/analysis_sub_view.html"> +<link rel="import" href="/tracing/ui/analysis/single_event_sub_view.html"> + +<dom-module id='tr-ui-a-single-instant-event-sub-view'> + <template> + <style> + :host { + display: block; + } + </style> + <div id='content'></div> + </template> +</dom-module> +<script> +'use strict'; + +Polymer({ + is: 'tr-ui-a-single-instant-event-sub-view', + behaviors: [tr.ui.analysis.AnalysisSubView], + + created() { + this.currentSelection_ = undefined; + }, + + set selection(selection) { + Polymer.dom(this.$.content).textContent = ''; + const realView = document.createElement('tr-ui-a-single-event-sub-view'); + realView.setSelectionWithoutErrorChecks(selection); + + Polymer.dom(this.$.content).appendChild(realView); + + this.currentSelection_ = selection; + }, + + get selection() { + return this.currentSelection_; + } +}); + +tr.ui.analysis.AnalysisSubView.register( + 'tr-ui-a-single-instant-event-sub-view', + tr.model.InstantEvent, + { + multi: false, + title: 'Instant Event', + }); + +tr.ui.analysis.AnalysisSubView.register( + 'tr-ui-a-multi-instant-event-sub-view', + tr.model.InstantEvent, + { + multi: true, + title: 'Instant Events', + }); + +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_instant_event_sub_view_test.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_instant_event_sub_view_test.html new file mode 100644 index 00000000000..4ad85d2e6db --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_instant_event_sub_view_test.html @@ -0,0 +1,42 @@ +<!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/core/test_utils.html"> +<link rel="import" href="/tracing/model/event_set.html"> +<link rel="import" href="/tracing/model/model.html"> +<link rel="import" href="/tracing/ui/analysis/analysis_view.html"> + +<script> +'use strict'; + +tr.b.unittest.testSuite(function() { + const Model = tr.Model; + const Thread = tr.model.Thread; + const EventSet = tr.model.EventSet; + + test('analyzeSelectionWithSingleEvent', function() { + const model = new Model(); + const p52 = model.getOrCreateProcess(52); + const t53 = p52.getOrCreateThread(53); + + const ie = new tr.model.ProcessInstantEvent('cat', 'title', 7, 10, {}); + ie.duration = 20; + p52.instantEvents.push(ie); + + + const selection = new EventSet(); + selection.push(ie); + assert.strictEqual(selection.length, 1); + + const subView = document.createElement( + 'tr-ui-a-single-instant-event-sub-view'); + subView.selection = selection; + + this.addHTMLOutput(subView); + }); +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_object_instance_sub_view.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_object_instance_sub_view.html new file mode 100644 index 00000000000..49810ab3fbd --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_object_instance_sub_view.html @@ -0,0 +1,129 @@ +<!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/utils.html"> +<link rel="import" href="/tracing/model/event_set.html"> +<link rel="import" href="/tracing/ui/analysis/analysis_link.html"> +<link rel="import" href="/tracing/ui/analysis/analysis_sub_view.html"> +<link rel="import" href="/tracing/ui/analysis/generic_object_view.html"> +<link rel="import" href="/tracing/ui/analysis/object_instance_view.html"> +<link rel="import" href="/tracing/ui/analysis/single_event_sub_view.html"> + +<dom-module id='tr-ui-a-single-object-instance-sub-view'> + <template> + <style> + :host { + display: block; + } + + #snapshots > * { + display: block; + } + + :host { + overflow: auto; + display: block; + } + + * { + -webkit-user-select: text; + } + + .title { + border-bottom: 1px solid rgb(128, 128, 128); + font-size: 110%; + font-weight: bold; + } + + td, th { + font-family: monospace; + vertical-align: top; + } + </style> + <div id='content'></div> + </template> +</dom-module> +<script> +'use strict'; + +Polymer({ + is: 'tr-ui-a-single-object-instance-sub-view', + behaviors: [tr.ui.analysis.AnalysisSubView], + + created() { + this.currentSelection_ = undefined; + }, + + get requiresTallView() { + if (this.$.content.children.length === 0) { + return false; + } + if (this.$.content.children[0] instanceof + tr.ui.analysis.ObjectInstanceView) { + return this.$.content.children[0].requiresTallView; + } + }, + + get selection() { + return this.currentSelection_; + }, + + set selection(selection) { + const instance = tr.b.getOnlyElement(selection); + if (!(instance instanceof tr.model.ObjectInstance)) { + throw new Error('Only supports object instances'); + } + + Polymer.dom(this.$.content).textContent = ''; + this.currentSelection_ = selection; + + const typeInfo = tr.ui.analysis.ObjectInstanceView.getTypeInfo( + instance.category, instance.typeName); + if (typeInfo) { + const customView = new typeInfo.constructor(); + Polymer.dom(this.$.content).appendChild(customView); + customView.modelEvent = instance; + } else { + this.appendGenericAnalysis_(instance); + } + }, + + appendGenericAnalysis_(instance) { + let html = ''; + html += '<div class="title">' + + instance.typeName + ' ' + + instance.id + '</div>\n'; + html += '<table>'; + html += '<tr>'; + html += '<tr><td>creationTs:</td><td>' + + instance.creationTs + '</td></tr>\n'; + if (instance.deletionTs !== Number.MAX_VALUE) { + html += '<tr><td>deletionTs:</td><td>' + + instance.deletionTs + '</td></tr>\n'; + } else { + html += '<tr><td>deletionTs:</td><td>not deleted</td></tr>\n'; + } + html += '<tr><td>snapshots:</td><td id="snapshots"></td></tr>\n'; + html += '</table>'; + Polymer.dom(this.$.content).innerHTML = html; + const snapshotsEl = Polymer.dom(this.$.content).querySelector('#snapshots'); + instance.snapshots.forEach(function(snapshot) { + const snapshotLink = document.createElement('tr-ui-a-analysis-link'); + snapshotLink.selection = new tr.model.EventSet(snapshot); + Polymer.dom(snapshotsEl).appendChild(snapshotLink); + }); + } +}); + +tr.ui.analysis.AnalysisSubView.register( + 'tr-ui-a-single-object-instance-sub-view', + tr.model.ObjectInstance, + { + multi: false, + title: 'Object Instance', + }); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_object_instance_sub_view_test.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_object_instance_sub_view_test.html new file mode 100644 index 00000000000..f5414dd957a --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_object_instance_sub_view_test.html @@ -0,0 +1,34 @@ +<!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/core/test_utils.html"> +<link rel="import" href="/tracing/model/event_set.html"> +<link rel="import" href="/tracing/model/model.html"> +<link rel="import" href="/tracing/ui/analysis/single_object_instance_sub_view.html"> + +<script> +'use strict'; + +tr.b.unittest.testSuite(function() { + const ObjectInstance = tr.model.ObjectInstance; + + test('analyzeSelectionWithObjectInstanceUnknownType', function() { + const i10 = new ObjectInstance( + {}, '0x1000', 'cat', 'someUnhandledName', 10); + const s10 = i10.addSnapshot(10, {foo: 1}); + const s20 = i10.addSnapshot(20, {foo: 2}); + + const selection = new tr.model.EventSet(); + selection.push(i10); + + const view = + document.createElement('tr-ui-a-single-object-instance-sub-view'); + view.selection = selection; + this.addHTMLOutput(view); + }); +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_object_snapshot_sub_view.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_object_snapshot_sub_view.html new file mode 100644 index 00000000000..5565db8d004 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_object_snapshot_sub_view.html @@ -0,0 +1,142 @@ +<!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/unit.html"> +<link rel="import" href="/tracing/base/utils.html"> +<link rel="import" href="/tracing/model/event_set.html"> +<link rel="import" href="/tracing/ui/analysis/analysis_link.html"> +<link rel="import" href="/tracing/ui/analysis/analysis_sub_view.html"> +<link rel="import" href="/tracing/ui/analysis/generic_object_view.html"> +<link rel="import" href="/tracing/ui/analysis/object_instance_view.html"> +<link rel="import" href="/tracing/ui/analysis/object_snapshot_view.html"> +<link rel="import" href="/tracing/ui/analysis/single_event_sub_view.html"> +<link rel="import" href="/tracing/value/ui/scalar_span.html"> + +<dom-module id='tr-ui-a-single-object-snapshot-sub-view'> + <template> + <style> + #args { + white-space: pre; + } + + :host { + overflow: auto; + display: flex; + } + + ::content * { + -webkit-user-select: text; + } + + ::content .title { + border-bottom: 1px solid rgb(128, 128, 128); + font-size: 110%; + font-weight: bold; + } + + ::content td, th { + font-family: monospace; + vertical-align: top; + } + </style> + <slot></slot> + </template> +</dom-module> +<script> +'use strict'; + +Polymer({ + is: 'tr-ui-a-single-object-snapshot-sub-view', + behaviors: [tr.ui.analysis.AnalysisSubView], + + created() { + this.currentSelection_ = undefined; + }, + + get requiresTallView() { + if (this.children.length === 0) { + return false; + } + if (this.children[0] instanceof tr.ui.analysis.ObjectSnapshotView) { + return this.children[0].requiresTallView; + } + }, + + get selection() { + return this.currentSelection_; + }, + + set selection(selection) { + const snapshot = tr.b.getOnlyElement(selection); + if (!(snapshot instanceof tr.model.ObjectSnapshot)) { + throw new Error('Only supports object instances'); + } + + Polymer.dom(this).textContent = ''; + this.currentSelection_ = selection; + + const typeInfo = tr.ui.analysis.ObjectSnapshotView.getTypeInfo( + snapshot.objectInstance.category, snapshot.objectInstance.typeName); + if (typeInfo) { + const customView = new typeInfo.constructor(); + Polymer.dom(this).appendChild(customView); + customView.modelEvent = snapshot; + } else { + this.appendGenericAnalysis_(snapshot); + } + }, + + appendGenericAnalysis_(snapshot) { + const instance = snapshot.objectInstance; + + Polymer.dom(this).textContent = ''; + + const titleEl = document.createElement('div'); + Polymer.dom(titleEl).classList.add('title'); + Polymer.dom(titleEl).appendChild(document.createTextNode('Snapshot of ')); + Polymer.dom(this).appendChild(titleEl); + + const instanceLinkEl = document.createElement('tr-ui-a-analysis-link'); + instanceLinkEl.selection = new tr.model.EventSet(instance); + Polymer.dom(titleEl).appendChild(instanceLinkEl); + + Polymer.dom(titleEl).appendChild(document.createTextNode(' @ ')); + + Polymer.dom(titleEl).appendChild(tr.v.ui.createScalarSpan(snapshot.ts, { + unit: tr.b.Unit.byName.timeStampInMs, + ownerDocument: this.ownerDocument, + inline: true, + })); + + const tableEl = document.createElement('table'); + Polymer.dom(this).appendChild(tableEl); + + const rowEl = document.createElement('tr'); + Polymer.dom(tableEl).appendChild(rowEl); + + const labelEl = document.createElement('td'); + Polymer.dom(labelEl).textContent = 'args:'; + Polymer.dom(rowEl).appendChild(labelEl); + + const argsEl = document.createElement('td'); + argsEl.id = 'args'; + Polymer.dom(rowEl).appendChild(argsEl); + + const objectViewEl = document.createElement('tr-ui-a-generic-object-view'); + objectViewEl.object = snapshot.args; + Polymer.dom(argsEl).appendChild(objectViewEl); + } +}); + +tr.ui.analysis.AnalysisSubView.register( + 'tr-ui-a-single-object-snapshot-sub-view', + tr.model.ObjectSnapshot, + { + multi: false, + title: 'Object Snapshot', + }); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_object_snapshot_sub_view_test.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_object_snapshot_sub_view_test.html new file mode 100644 index 00000000000..41fca173931 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_object_snapshot_sub_view_test.html @@ -0,0 +1,32 @@ +<!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/core/test_utils.html"> +<link rel="import" href="/tracing/model/event_set.html"> +<link rel="import" href="/tracing/model/model.html"> +<link rel="import" href="/tracing/ui/analysis/single_object_snapshot_sub_view.html"> + +<script> +'use strict'; + +tr.b.unittest.testSuite(function() { + test('instantiate_snapshotView', function() { + const i10 = new tr.model.ObjectInstance( + {}, '0x1000', 'cat', 'name', 10); + const s10 = i10.addSnapshot(10, {foo: 1}); + i10.updateBounds(); + + const selection = new tr.model.EventSet(); + selection.push(s10); + + const view = + document.createElement('tr-ui-a-single-object-snapshot-sub-view'); + view.selection = selection; + this.addHTMLOutput(view); + }); +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_power_sample_sub_view.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_power_sample_sub_view.html new file mode 100644 index 00000000000..7396cfa3eca --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_power_sample_sub_view.html @@ -0,0 +1,122 @@ +<!DOCTYPE html> +<!-- +Copyright 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/base/unit.html"> +<link rel="import" href="/tracing/base/utils.html"> +<link rel="import" href="/tracing/model/event_set.html"> +<link rel="import" href="/tracing/ui/analysis/analysis_sub_view.html"> +<link rel="import" href="/tracing/ui/base/table.html"> +<link rel="import" href="/tracing/value/ui/scalar_span.html"> + +<dom-module id='tr-ui-a-power-sample-table'> + <template> + <style> + :host { + display: flex; + font-size: 12px; + } + </style> + <tr-ui-b-table id="table"></tr-ui-b-table> + </template> +</dom-module> + +<script> +'use strict'; + +Polymer({ + is: 'tr-ui-a-power-sample-table', + + ready() { + this.$.table.tableColumns = [ + { + title: 'Time', + width: '100px', + value(row) { + return tr.v.ui.createScalarSpan(row.start, { + unit: tr.b.Unit.byName.timeStampInMs + }); + } + }, + { + title: 'Power', + width: '100%', + value(row) { + return tr.v.ui.createScalarSpan(row.powerInW, { + unit: tr.b.Unit.byName.powerInWatts + }); + } + } + ]; + this.sample = undefined; + }, + + get sample() { + return this.sample_; + }, + + set sample(sample) { + this.sample_ = sample; + this.updateContents_(); + }, + + updateContents_() { + if (this.sample === undefined) { + this.$.table.tableRows = []; + } else { + this.$.table.tableRows = [this.sample]; + } + this.$.table.rebuild(); + } +}); +</script> + +<dom-module id='tr-ui-a-single-power-sample-sub-view'> + <template> + <style> + :host { display: block; } + </style> + <tr-ui-a-power-sample-table id="samplesTable"> + </tr-ui-a-power-sample-table> + </template> +</dom-module> +<script> +'use strict'; + +Polymer({ + is: 'tr-ui-a-single-power-sample-sub-view', + behaviors: [tr.ui.analysis.AnalysisSubView], + + ready() { + this.currentSelection_ = undefined; + }, + + get selection() { + return this.currentSelection_; + }, + + set selection(selection) { + this.currentSelection_ = selection; + this.updateContents_(); + }, + + updateContents_() { + if (this.selection.length !== 1) { + throw new Error('Cannot pass multiple samples to sample table.'); + } + this.$.samplesTable.sample = tr.b.getOnlyElement(this.selection); + } +}); + +tr.ui.analysis.AnalysisSubView.register( + 'tr-ui-a-single-power-sample-sub-view', + tr.model.PowerSample, + { + multi: false, + title: 'Power Sample', + }); + +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_power_sample_sub_view_test.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_power_sample_sub_view_test.html new file mode 100644 index 00000000000..8ee1dfcf899 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_power_sample_sub_view_test.html @@ -0,0 +1,42 @@ +<!DOCTYPE html> +<!-- +Copyright 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/base/utils.html"> +<link rel="import" href="/tracing/model/event_set.html"> +<link rel="import" href="/tracing/model/model.html"> +<link rel="import" href="/tracing/model/power_series.html"> +<link rel="import" href="/tracing/ui/analysis/single_power_sample_sub_view.html"> + +<script> +'use strict'; + +tr.b.unittest.testSuite(function() { + test('instantiate', function() { + const model = new tr.Model(); + const series = new tr.model.PowerSeries(model.device); + series.addPowerSample(1, 1); + + const view = document.createElement('tr-ui-a-single-power-sample-sub-view'); + view.selection = new tr.model.EventSet(series.samples); + + this.addHTMLOutput(view); + }); + + test('setSelection', function() { + const model = new tr.Model(); + const series = new tr.model.PowerSeries(model.device); + series.addPowerSample(1, 1); + + const view = document.createElement('tr-ui-a-single-power-sample-sub-view'); + const eventSet = new tr.model.EventSet(series.samples); + view.selection = eventSet; + + assert.deepEqual(view.$.samplesTable.sample, + tr.b.getOnlyElement(series.samples)); + }); +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_sample_sub_view.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_sample_sub_view.html new file mode 100644 index 00000000000..851c60952ff --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_sample_sub_view.html @@ -0,0 +1,110 @@ +<!DOCTYPE html> +<!-- +Copyright (c) 2016 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/unit.html"> +<link rel="import" href="/tracing/ui/analysis/analysis_sub_view.html"> +<link rel="import" href="/tracing/ui/analysis/stack_frame.html"> +<link rel="import" href="/tracing/ui/base/table.html"> +<link rel="import" href="/tracing/value/ui/scalar_span.html"> + +<dom-module id='tr-ui-a-single-sample-sub-view'> + <template> + <style> + :host { + display: flex; + font-size: 12px; + } + </style> + <tr-ui-b-table id="content"></tr-ui-b-table> + </template> +</dom-module> +<script> +'use strict'; + +Polymer({ + is: 'tr-ui-a-single-sample-sub-view', + behaviors: [tr.ui.analysis.AnalysisSubView], + + created() { + this.currentSelection_ = undefined; + }, + + ready() { + this.$.content.tableColumns = [ + { + title: '', + value: row => row.title, + width: '100px' + }, + { + title: '', + value: row => row.value, + width: '100%' + } + ]; + this.$.content.showHeader = false; + }, + + get selection() { + return this.currentSelection_; + }, + + set selection(selection) { + this.currentSelection_ = selection; + + if (this.currentSelection_ === undefined) { + this.$.content.tableRows = []; + return; + } + + const sample = tr.b.getOnlyElement(this.currentSelection_); + const table = this.$.content; + const rows = []; + + rows.push({ + title: 'Title', + value: sample.title + }); + + rows.push({ + title: 'Sample time', + value: tr.v.ui.createScalarSpan(sample.start, { + unit: tr.b.Unit.byName.timeStampInMs, + ownerDocument: this.ownerDocument + }) + }); + + const callStackTableEl = document.createElement('tr-ui-b-table'); + callStackTableEl.tableRows = sample.getNodesAsArray().reverse(); + callStackTableEl.tableColumns = [ + { + title: 'function name', + value: row => row.functionName || '(anonymous function)' + }, + { + title: 'location', + value: row => row.url + } + ]; + callStackTableEl.rebuild(); + rows.push({ + title: 'Call stack', + value: callStackTableEl + }); + table.tableRows = rows; + table.rebuild(); + } +}); + +tr.ui.analysis.AnalysisSubView.register( + 'tr-ui-a-single-sample-sub-view', + tr.model.Sample, + { + multi: false, + title: 'Sample', + }); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_sample_sub_view_test.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_sample_sub_view_test.html new file mode 100644 index 00000000000..7f8c131f82c --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_sample_sub_view_test.html @@ -0,0 +1,62 @@ +<!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/unit.html"> +<link rel="import" href="/tracing/core/test_utils.html"> +<link rel="import" href="/tracing/model/event_set.html"> +<link rel="import" href="/tracing/model/model.html"> +<link rel="import" href="/tracing/ui/analysis/single_sample_sub_view.html"> +<link rel="import" href="/tracing/ui/base/deep_utils.html"> + +<script> +'use strict'; + +tr.b.unittest.testSuite(function() { + const Model = tr.Model; + const EventSet = tr.model.EventSet; + const newSampleNamed = tr.c.TestUtils.newSampleNamed; + + test('instantiate_withSingleSample', function() { + let t53; + const model = tr.c.TestUtils.newModelWithEvents([], { + shiftWorldToZero: false, + pruneContainers: false, + customizeModelCallback(model) { + t53 = model.getOrCreateProcess(52).getOrCreateThread(53); + model.samples.push(newSampleNamed(t53, 'X', 'my-category', + ['a', 'b', 'c'], 0.184)); + } + }); + + const t53track = {}; + t53track.thread = t53; + + const selection = new EventSet(); + + assert.strictEqual(selection.length, 0); + selection.push(t53.samples[0]); + assert.strictEqual(selection.length, 1); + + const view = document.createElement('tr-ui-a-single-sample-sub-view'); + view.selection = selection; + this.addHTMLOutput(view); + + const table = tr.ui.b.findDeepElementMatching( + view, 'tr-ui-b-table'); + + const rows = table.tableRows; + assert.strictEqual(rows.length, 3); + assert.strictEqual(rows[0].value, 'X'); + assert.strictEqual(rows[1].value.value, 0.184); + assert.strictEqual(rows[1].value.unit, tr.b.Unit.byName.timeStampInMs); + + const callStackRows = rows[2].value.tableRows; + assert.lengthOf(callStackRows, 3); + assert.deepEqual(callStackRows.map(x => x.title), ['a', 'b', 'c']); + }); +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_thread_slice_sub_view.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_thread_slice_sub_view.html new file mode 100644 index 00000000000..720fdfeb65a --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_thread_slice_sub_view.html @@ -0,0 +1,61 @@ +<!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/ui/analysis/analysis_sub_view.html"> +<link rel="import" href="/tracing/ui/analysis/related_events.html"> +<link rel="import" href="/tracing/ui/analysis/single_event_sub_view.html"> + +<dom-module id='tr-ui-a-single-thread-slice-sub-view'> + <template> + <style> + :host { + display: flex; + flex-direction: row; + } + #events { + display: flex; + flex-direction: column; + } + + </style> + <tr-ui-a-single-event-sub-view id="content"></tr-ui-a-single-event-sub-view> + <div id="events"> + <tr-ui-a-related-events id="relatedEvents"> + </tr-ui-a-related-events> + </div> + </template> +</dom-module> +<script> +'use strict'; + +Polymer({ + is: 'tr-ui-a-single-thread-slice-sub-view', + behaviors: [tr.ui.analysis.AnalysisSubView], + + get selection() { + return this.$.content.selection; + }, + + set selection(selection) { + this.$.content.selection = selection; + this.$.relatedEvents.setRelatedEvents(selection); + if (this.$.relatedEvents.hasRelatedEvents()) { + this.$.relatedEvents.style.display = ''; + } else { + this.$.relatedEvents.style.display = 'none'; + } + } +}); + +tr.ui.analysis.AnalysisSubView.register( + 'tr-ui-a-single-thread-slice-sub-view', + tr.model.ThreadSlice, + { + multi: false, + title: 'Slice', + }); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_thread_slice_sub_view_test.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_thread_slice_sub_view_test.html new file mode 100644 index 00000000000..84bb292384e --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_thread_slice_sub_view_test.html @@ -0,0 +1,80 @@ +<!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/core/test_utils.html"> +<link rel="import" href="/tracing/model/event_set.html"> +<link rel="import" href="/tracing/model/model.html"> +<link rel="import" href="/tracing/model/thread_slice.html"> +<link rel="import" href="/tracing/ui/analysis/single_thread_slice_sub_view.html"> + +<script> +'use strict'; + +tr.b.unittest.testSuite(function() { + const newSliceEx = tr.c.TestUtils.newSliceEx; + const newFlowEventEx = tr.c.TestUtils.newFlowEventEx; + + test('instantiate', function() { + const model = new tr.Model(); + const t53 = model.getOrCreateProcess(52).getOrCreateThread(53); + t53.sliceGroup.pushSlice( + newSliceEx({title: 'a', start: 0.0, duration: 0.5})); + t53.sliceGroup.createSubSlices(); + + const selection = new tr.model.EventSet(); + selection.push(t53.sliceGroup.slices[0]); + + const viewEl = document.createElement( + 'tr-ui-a-single-thread-slice-sub-view'); + viewEl.selection = selection; + this.addHTMLOutput(viewEl); + }); + + test('instantiateWithFlowEvent', function() { + const m = tr.c.TestUtils.newModel(function(m) { + m.p1 = m.getOrCreateProcess(1); + + m.t2 = m.p1.getOrCreateThread(2); + m.t3 = m.p1.getOrCreateThread(3); + m.t4 = m.p1.getOrCreateThread(4); + + m.sA = m.t2.sliceGroup.pushSlice( + newSliceEx({title: 'a', start: 0, end: 5, + type: tr.model.ThreadSlice})); + m.sB = m.t3.sliceGroup.pushSlice( + newSliceEx({title: 'b', start: 10, end: 15, + type: tr.model.ThreadSlice})); + m.sC = m.t4.sliceGroup.pushSlice( + newSliceEx({title: 'c', start: 20, end: 20, + type: tr.model.ThreadSlice})); + + m.t2.createSubSlices(); + m.t3.createSubSlices(); + m.t4.createSubSlices(); + + m.f1 = newFlowEventEx({ + title: 'flowish', start: 0, end: 10, + startSlice: m.sA, + endSlice: m.sB + }); + m.f2 = newFlowEventEx({ + title: 'flowish', start: 15, end: 21, + startSlice: m.sB, + endSlice: m.sC + }); + }); + + const selection = new tr.model.EventSet(); + selection.push(m.sA); + + const viewEl = document.createElement( + 'tr-ui-a-single-thread-slice-sub-view'); + viewEl.selection = selection; + this.addHTMLOutput(viewEl); + }); +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_thread_time_slice_sub_view.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_thread_time_slice_sub_view.html new file mode 100644 index 00000000000..225b2729769 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_thread_time_slice_sub_view.html @@ -0,0 +1,186 @@ +<!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/color_scheme.html"> +<link rel="import" href="/tracing/base/unit.html"> +<link rel="import" href="/tracing/base/utils.html"> +<link rel="import" href="/tracing/model/event_set.html"> +<link rel="import" href="/tracing/model/model.html"> +<link rel="import" href="/tracing/ui/analysis/analysis_link.html"> +<link rel="import" href="/tracing/ui/analysis/analysis_sub_view.html"> +<link rel="import" href="/tracing/ui/analysis/generic_object_view.html"> +<link rel="import" href="/tracing/value/ui/scalar_span.html"> + +<dom-module id='tr-ui-a-single-thread-time-slice-sub-view'> + <template> + <style> + table { + border-collapse: collapse; + border-width: 0; + margin-bottom: 25px; + width: 100%; + } + + table tr > td:first-child { + padding-left: 2px; + } + + table tr > td { + padding: 2px 4px 2px 4px; + vertical-align: text-top; + width: 150px; + } + + table td td { + padding: 0 0 0 0; + width: auto; + } + tr { + vertical-align: top; + } + + tr:nth-child(2n+0) { + background-color: #e2e2e2; + } + </style> + <table> + <tr> + <td>Running process:</td><td id="process-name"></td> + </tr> + <tr> + <td>Running thread:</td><td id="thread-name"></td> + </tr> + <tr> + <td>State:</td> + <td><b><span id="state"></span></b></td> + </tr> + <tr> + <td>Start:</td> + <td> + <tr-v-ui-scalar-span id="start"> + </tr-v-ui-scalar-span> + </td> + </tr> + <tr> + <td>Duration:</td> + <td> + <tr-v-ui-scalar-span id="duration"> + </tr-v-ui-scalar-span> + </td> + </tr> + + <tr> + <td>On CPU:</td><td id="on-cpu"></td> + </tr> + + <tr> + <td>Running instead:</td><td id="running-instead"></td> + </tr> + + <tr> + <td>Args:</td><td id="args"></td> + </tr> + </table> + </template> +</dom-module> +<script> +'use strict'; + +Polymer({ + is: 'tr-ui-a-single-thread-time-slice-sub-view', + behaviors: [tr.ui.analysis.AnalysisSubView], + + created() { + this.currentSelection_ = undefined; + }, + + get selection() { + return this.currentSelection_; + }, + + set selection(selection) { + const timeSlice = tr.b.getOnlyElement(selection); + + if (!(timeSlice instanceof tr.model.ThreadTimeSlice)) { + throw new Error('Only supports thread time slices'); + } + + this.currentSelection_ = selection; + + const thread = timeSlice.thread; + + const root = Polymer.dom(this.root); + Polymer.dom(root.querySelector('#state')).textContent = + timeSlice.title; + const stateColor = tr.b.ColorScheme.colorsAsStrings[timeSlice.colorId]; + root.querySelector('#state').style.backgroundColor = stateColor; + + Polymer.dom(root.querySelector('#process-name')).textContent = + thread.parent.userFriendlyName; + Polymer.dom(root.querySelector('#thread-name')).textContent = + thread.userFriendlyName; + + root.querySelector('#start').setValueAndUnit( + timeSlice.start, tr.b.Unit.byName.timeStampInMs); + root.querySelector('#duration').setValueAndUnit( + timeSlice.duration, tr.b.Unit.byName.timeDurationInMs); + + const onCpuEl = root.querySelector('#on-cpu'); + Polymer.dom(onCpuEl).textContent = ''; + const runningInsteadEl = root.querySelector('#running-instead'); + if (timeSlice.cpuOnWhichThreadWasRunning) { + Polymer.dom(runningInsteadEl.parentElement).removeChild(runningInsteadEl); + + const cpuLink = document.createElement('tr-ui-a-analysis-link'); + cpuLink.selection = new tr.model.EventSet( + timeSlice.getAssociatedCpuSlice()); + Polymer.dom(cpuLink).textContent = + timeSlice.cpuOnWhichThreadWasRunning.userFriendlyName; + Polymer.dom(onCpuEl).appendChild(cpuLink); + } else { + Polymer.dom(onCpuEl.parentElement).removeChild(onCpuEl); + + const cpuSliceThatTookCpu = timeSlice.getCpuSliceThatTookCpu(); + if (cpuSliceThatTookCpu) { + const cpuLink = document.createElement('tr-ui-a-analysis-link'); + cpuLink.selection = new tr.model.EventSet(cpuSliceThatTookCpu); + if (cpuSliceThatTookCpu.thread) { + Polymer.dom(cpuLink).textContent = + cpuSliceThatTookCpu.thread.userFriendlyName; + } else { + Polymer.dom(cpuLink).textContent = cpuSliceThatTookCpu.title; + } + Polymer.dom(runningInsteadEl).appendChild(cpuLink); + } else { + Polymer.dom(runningInsteadEl.parentElement).removeChild( + runningInsteadEl); + } + } + + const argsEl = root.querySelector('#args'); + if (Object.keys(timeSlice.args).length > 0) { + const argsView = + document.createElement('tr-ui-a-generic-object-view'); + argsView.object = timeSlice.args; + + argsEl.parentElement.style.display = ''; + Polymer.dom(argsEl).textContent = ''; + Polymer.dom(argsEl).appendChild(argsView); + } else { + argsEl.parentElement.style.display = 'none'; + } + } +}); + +tr.ui.analysis.AnalysisSubView.register( + 'tr-ui-a-single-thread-time-slice-sub-view', + tr.model.ThreadTimeSlice, + { + multi: false, + title: 'Thread Timeslice', + }); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_thread_time_slice_sub_view_test.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_thread_time_slice_sub_view_test.html new file mode 100644 index 00000000000..bfffd41861b --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_thread_time_slice_sub_view_test.html @@ -0,0 +1,92 @@ +<!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/core/test_utils.html"> +<link rel="import" href="/tracing/extras/importer/linux_perf/ftrace_importer.html"> +<link rel="import" href="/tracing/model/event_set.html"> +<link rel="import" href="/tracing/model/model.html"> +<link rel="import" href="/tracing/ui/analysis/single_thread_time_slice_sub_view.html"> + +<script> +'use strict'; + +tr.b.unittest.testSuite(function() { + function createBasicModel() { + const lines = [ + 'Android.launcher-584 [001] d..3 12622.506890: sched_switch: prev_comm=Android.launcher prev_pid=584 prev_prio=120 prev_state=R+ ==> next_comm=Binder_1 next_pid=217 next_prio=120', // @suppress longLineCheck + ' Binder_1-217 [001] d..3 12622.506918: sched_switch: prev_comm=Binder_1 prev_pid=217 prev_prio=120 prev_state=D ==> next_comm=Android.launcher next_pid=584 next_prio=120', // @suppress longLineCheck + 'Android.launcher-584 [001] d..4 12622.506936: sched_wakeup: comm=Binder_1 pid=217 prio=120 success=1 target_cpu=001', // @suppress longLineCheck + 'Android.launcher-584 [001] d..3 12622.506950: sched_switch: prev_comm=Android.launcher prev_pid=584 prev_prio=120 prev_state=R+ ==> next_comm=Binder_1 next_pid=217 next_prio=120', // @suppress longLineCheck + ' Binder_1-217 [001] ...1 12622.507057: tracing_mark_write: B|128|queueBuffer', // @suppress longLineCheck + ' Binder_1-217 [001] ...1 12622.507175: tracing_mark_write: E', + ' Binder_1-217 [001] d..3 12622.507253: sched_switch: prev_comm=Binder_1 prev_pid=217 prev_prio=120 prev_state=S ==> next_comm=Android.launcher next_pid=584 next_prio=120' // @suppress longLineCheck + ]; + + return tr.c.TestUtils.newModelWithEvents([lines.join('\n')], { + shiftWorldToZero: false + }); + } + + test('runningSlice', function() { + const m = createBasicModel(); + + const cpu = m.kernel.cpus[1]; + const binderSlice = cpu.slices[0]; + assert.strictEqual(binderSlice.title, 'Binder_1'); + const launcherSlice = cpu.slices[1]; + assert.strictEqual(launcherSlice.title, 'Android.launcher'); + + + const thread = m.findAllThreadsNamed('Binder_1')[0]; + + const view = document.createElement( + 'tr-ui-a-single-thread-time-slice-sub-view'); + const selection = new tr.model.EventSet(); + selection.push(thread.timeSlices[0]); + view.selection = selection; + this.addHTMLOutput(view); + + // Clicking the analysis link should focus the Binder1's timeslice. + let didSelectionChangeHappen = false; + view.addEventListener('requestSelectionChange', function(e) { + assert.isTrue(e.selection.equals(new tr.model.EventSet(binderSlice))); + didSelectionChangeHappen = true; + }); + Polymer.dom(view.root).querySelector('tr-ui-a-analysis-link').click(); + assert.isTrue(didSelectionChangeHappen); + }); + + test('sleepingSlice', function() { + const m = createBasicModel(); + + const cpu = m.kernel.cpus[1]; + const binderSlice = cpu.slices[0]; + assert.strictEqual(binderSlice.title, 'Binder_1'); + const launcherSlice = cpu.slices[1]; + assert.strictEqual(launcherSlice.title, 'Android.launcher'); + + + const thread = m.findAllThreadsNamed('Binder_1')[0]; + + const view = document.createElement( + 'tr-ui-a-single-thread-time-slice-sub-view'); + const selection = new tr.model.EventSet(); + selection.push(thread.timeSlices[1]); + view.selection = selection; + this.addHTMLOutput(view); + + // Clicking the analysis link should focus the Android.launcher slice + let didSelectionChangeHappen = false; + view.addEventListener('requestSelectionChange', function(e) { + assert.isTrue(e.selection.equals(new tr.model.EventSet(launcherSlice))); + didSelectionChangeHappen = true; + }); + Polymer.dom(view.root).querySelector('tr-ui-a-analysis-link').click(); + assert.isTrue(didSelectionChangeHappen); + }); +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_user_expectation_sub_view.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_user_expectation_sub_view.html new file mode 100644 index 00000000000..76110b4b468 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/single_user_expectation_sub_view.html @@ -0,0 +1,91 @@ +<!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/base/unit.html"> +<link rel="import" href="/tracing/base/utils.html"> +<link rel="import" href="/tracing/ui/analysis/analysis_sub_view.html"> +<link rel="import" href="/tracing/ui/analysis/single_event_sub_view.html"> +<link rel="import" + href="/tracing/ui/analysis/user_expectation_related_samples_table.html"> +<link rel="import" href="/tracing/value/histogram_set.html"> +<link rel="import" href="/tracing/value/ui/scalar_span.html"> + +<dom-module id='tr-ui-a-single-user-expectation-sub-view'> + <template> + <style> + :host { + display: flex; + flex-direction: row; + } + #events { + display: flex; + flex-direction: column; + } + </style> + <tr-ui-a-single-event-sub-view id="realView"></tr-ui-a-single-event-sub-view> + <div id="events"> + <tr-ui-a-user-expectation-related-samples-table id="relatedSamples"></tr-ui-a-user-expectation-related-samples-table> + </div> + </template> +</dom-module> +<script> +'use strict'; + +Polymer({ + is: 'tr-ui-a-single-user-expectation-sub-view', + behaviors: [tr.ui.analysis.AnalysisSubView], + + created() { + this.currentSelection_ = undefined; + }, + + get selection() { + return this.currentSelection_; + }, + + set selection(selection) { + this.$.realView.addEventListener('customize-rows', + this.onCustomizeRows_.bind(this)); + + this.currentSelection_ = selection; + this.$.realView.setSelectionWithoutErrorChecks(selection); + + this.$.relatedSamples.selection = selection; + if (this.$.relatedSamples.hasRelatedSamples()) { + this.$.events.style.display = ''; + } else { + this.$.events.style.display = 'none'; + } + }, + + get relatedEventsToHighlight() { + if (!this.currentSelection_) return undefined; + return tr.b.getOnlyElement(this.currentSelection_).associatedEvents; + }, + + onCustomizeRows_(event) { + const ue = tr.b.getOnlyElement(this.selection); + + if (ue.rawCpuMs) { + event.rows.push({ + name: 'Total CPU', + value: tr.v.ui.createScalarSpan(ue.totalCpuMs, { + unit: tr.b.Unit.byName.timeDurationInMs + }) + }); + } + } +}); + +tr.ui.analysis.AnalysisSubView.register( + 'tr-ui-a-single-user-expectation-sub-view', + tr.model.um.UserExpectation, + { + multi: false, + title: 'User Expectation', + }); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/stack_frame.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/stack_frame.html new file mode 100644 index 00000000000..92c4594af5f --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/stack_frame.html @@ -0,0 +1,81 @@ +<!DOCTYPE html> +<!-- +Copyright (c) 2014 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/table.html"> + +<dom-module id='tr-ui-a-stack-frame'> + <template> + <style> + :host { + display: flex; + flex-direction: row; + align-items: center; + font-size: 12px; + } + </style> + <tr-ui-b-table id="table"></tr-ui-b-table> + </template> +</dom-module> +<script> +'use strict'; + +Polymer({ + is: 'tr-ui-a-stack-frame', + + ready() { + this.stackFrame_ = undefined; + this.$.table.tableColumns = []; + this.$.table.showHeader = true; + }, + + get stackFrame() { + return this.stackFrame_; + }, + + set stackFrame(stackFrame) { + const table = this.$.table; + + this.stackFrame_ = stackFrame; + if (stackFrame === undefined) { + table.tableColumns = []; + table.tableRows = []; + table.rebuild(); + return; + } + + let hasName = false; + let hasTitle = false; + + table.tableRows = stackFrame.stackTrace; + table.tableRows.forEach(function(row) { + hasName |= row.name !== undefined; + hasTitle |= row.title !== undefined; + }); + + const cols = []; + if (hasName) { + cols.push({ + title: 'Name', + value(row) { return row.name; } + }); + } + + if (hasTitle) { + cols.push({ + title: 'Title', + value(row) { return row.title; } + }); + } + + table.tableColumns = cols; + table.rebuild(); + }, + + tableForTesting() { + return this.$.table; + } +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/stack_frame_test.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/stack_frame_test.html new file mode 100644 index 00000000000..4523906a321 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/stack_frame_test.html @@ -0,0 +1,34 @@ +<!DOCTYPE html> +<!-- +Copyright (c) 2014 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/core/test_utils.html"> +<link rel="import" href="/tracing/ui/analysis/stack_frame.html"> +<script> +'use strict'; + +tr.b.unittest.testSuite(function() { + test('instantiate', function() { + const model = new tr.Model(); + const fA = tr.c.TestUtils.newStackTrace(model, ['a1', 'a2', 'a3']); + + const stackFrameView = document.createElement('tr-ui-a-stack-frame'); + stackFrameView.stackFrame = fA; + this.addHTMLOutput(stackFrameView); + }); + + test('clearingStackFrame', function() { + const model = new tr.Model(); + const fA = tr.c.TestUtils.newStackTrace(model, ['a1', 'a2', 'a3']); + + const stackFrameView = document.createElement('tr-ui-a-stack-frame'); + stackFrameView.stackFrame = fA; + stackFrameView.stackFrame = undefined; + + assert.isUndefined(stackFrameView.stackFrame); + assert.lengthOf(stackFrameView.$.table.$.body.children, 0); + }); +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/stacked_pane.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/stacked_pane.html new file mode 100644 index 00000000000..0e4f633fb00 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/stacked_pane.html @@ -0,0 +1,61 @@ +<!DOCTYPE html> +<!-- +Copyright 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/analysis/rebuildable_behavior.html"> + +<!-- +@fileoverview Analysis view stacked pane. See the stacked pane view element +(tr-ui-a-stacked-pane-view) documentation for more details. +--> +<script> +'use strict'; + +tr.exportTo('tr.ui.analysis', function() { + const StackedPaneImpl = { + /** + * Request changing the child pane of this pane in the associated stacked + * pane view. If the assigned builder is undefined, request removing the + * current child pane. + * + * Note that setting this property before appended() is called will have no + * effect (as there will be no listener attached to the pane). + * + * This method is intended to be called by subclasses. + */ + set childPaneBuilder(childPaneBuilder) { + this.childPaneBuilder_ = childPaneBuilder; + this.dispatchEvent(new tr.b.Event('request-child-pane-change')); + }, + + get childPaneBuilder() { + return this.childPaneBuilder_; + }, + + /** + * Called right after the pane is appended to a pane view. + * + * This method triggers an immediate rebuild by default. Subclasses are + * free to change this behavior (e.g. if a pane has lots of data to display, + * it might decide to defer rebuilding in order not to cause jank). + */ + appended() { + this.rebuild(); + } + }; + + const StackedPane = [tr.ui.analysis.RebuildableBehavior, StackedPaneImpl]; + + return { + StackedPane, + }; +}); + +Polymer({ + is: 'tr-ui-a-stacked-pane', + behaviors: [tr.ui.analysis.StackedPane] +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/stacked_pane_test.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/stacked_pane_test.html new file mode 100644 index 00000000000..7af70fa3d7a --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/stacked_pane_test.html @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<!-- +Copyright 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/analysis/stacked_pane.html"> + +<script> +'use strict'; + +tr.b.unittest.testSuite(function() { + test('changeChildPane', function() { + const pane = document.createElement('tr-ui-a-stacked-pane'); + let didFireEvent; + pane.addEventListener('request-child-pane-change', function() { + didFireEvent = true; + }); + + didFireEvent = false; + pane.childPaneBuilder = undefined; + assert.isTrue(didFireEvent); + + didFireEvent = false; + pane.childPaneBuilder = function() { + return undefined; + }; + assert.isTrue(didFireEvent); + + didFireEvent = false; + pane.childPaneBuilder = function() { + return document.createElement('tr-ui-a-stacked-pane'); + }; + assert.isTrue(didFireEvent); + }); +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/stacked_pane_view.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/stacked_pane_view.html new file mode 100644 index 00000000000..e8bea2dd034 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/stacked_pane_view.html @@ -0,0 +1,195 @@ +<!DOCTYPE html> +<!-- +Copyright 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/base/base.html"> + +<!-- +@fileoverview Analysis view container which displays vertically stacked panes. +The panes represent a hierarchy where a child pane contains the details of the +current selection in its parent pane. The container provides simple primitives +for panes to request changing their child pane: + + +=<tr-ui-a-stacked-pane-view>=+ +=<tr-ui-a-stacked-pane-view>=+ + |+.<tr-ui-a-stacked-pane>....+| |+.<tr-ui-a-stacked-pane>....+| + |: Pane 1 +| ===========> |: Pane 1 +| + |+...........................+| Pane 1 |+...........................+| + |+.<tr-ui-a-stacked-pane>....+| requests |+.<tr-ui-a-stacked-pane>....+| + |: Pane 2 (detail of Pane 1) +| child pane |: Pane 4 (detail of Pane 1) +| + |+...........................+| change (e.g. |+...........................+| + |+.<tr-ui-a-stacked-pane>....+| selection +=============================+ + |: Pane 3 (detail of Pane 2) +| changed) + |+...........................+| + +=============================+ + +Note that the actual UI provided by tr-ui-a-stacked-pane-view and +tr-ui-a-stacked-pane is merely a wrapper container with flex box vertical +stacking. No other visual features (such as pane spacing or borders) is +provided by either element. + +The stacked pane element (tr-ui-a-stacked-pane) is defined in a separate file. + +Sample use case: + + Create an empty stacked pane view and add it to the DOM: + + const paneView = document.createElement('tr-ui-a-stacked-pane-view'); + Polymer.dom(someParentView).appendChild(paneView); + + Define one or more pane subclasses: + + TODO(polymer): Write this documentation + <polymer-element name="some-pane-1" extends="tr-ui-a-stacked-pane"> + ... + </polymer-element> + + Set the top-level pane (by providing a builder function): + + paneView.setPaneBuilder(function() { + const topPane = document.createElement('some-pane-1'); + pane.someProperty = someValue; + return topPane; + }); + + Show a child pane with details upon user interaction (these methods should be + in the definition of the pane subclass Polymer element): + + ready: function() { + this.$.table.addEventListener( + 'selection-changed', this.changeChildPane_.bind(this)); + } + + changeChildPane_: function() { + this.childPaneBuilder = function() { + const selectedRow = this.$.table.selectedTableRow; + const detailsPane = document.createElement('some-pane-2'); + detailsPane.someProperty = selectedRow; + return detailsPane; + }.bind(this); + } +--> +<dom-module id='tr-ui-a-stacked-pane-view'> + <template> + <style> + :host { + display: flex; + flex-direction: column; + } + + #pane_container > * { + flex: 0 0 auto; + } + </style> + <div id="pane_container"> + </div> + </template> +</dom-module> +<script> +'use strict'; + +Polymer({ + is: 'tr-ui-a-stacked-pane-view', + + /** + * Add a pane to the stacked pane view. This method performs two operations: + * + * 1. Remove existing descendant panes + * If the optional parent pane is provided, all its current descendant + * panes are removed. Otherwise, all panes are removed from the view. + * + * 2. Build and add new pane + * If a pane builder is provided and returns a pane, the new pane is + * appended to the view (after the provided parent, or at the top). + */ + setPaneBuilder(paneBuilder, opt_parentPane) { + const paneContainer = this.$.pane_container; + + // If the parent pane is provided, it must be an HTML element and a child + // of the pane container. + if (opt_parentPane) { + if (!(opt_parentPane instanceof HTMLElement)) { + throw new Error('Parent pane must be an HTML element'); + } + if (opt_parentPane.parentElement !== paneContainer) { + throw new Error('Parent pane must be a child of the pane container'); + } + } + + // Remove all descendants of the parent pane (or all panes if no parent + // pane was specified) in reverse order. + while (Polymer.dom(paneContainer).lastElementChild !== null && + Polymer.dom(paneContainer).lastElementChild !== opt_parentPane) { + const removedPane = Polymer.dom(this.$.pane_container).lastElementChild; + const listener = this.listeners_.get(removedPane); + if (listener === undefined) { + throw new Error('No listener associated with pane'); + } + this.listeners_.delete(removedPane); + removedPane.removeEventListener( + 'request-child-pane-change', listener); + Polymer.dom(paneContainer).removeChild(removedPane); + } + + if (opt_parentPane && opt_parentPane.parentElement !== paneContainer) { + throw new Error('Parent pane was removed from the pane container'); + } + + // This check is performed here (and not at the beginning of the method) + // because undefined pane builder means that the parent pane requested + // having no child pane (e.g. when selection is cleared). + if (!paneBuilder) return; + + const pane = paneBuilder(); + if (!pane) return; + + if (!(pane instanceof HTMLElement)) { + throw new Error('Pane must be an HTML element'); + } + + // Listen for child pane change requests from the newly added pane. + const listener = function(event) { + this.setPaneBuilder(pane.childPaneBuilder, pane); + }.bind(this); + if (!this.listeners_) { + // Instead of initializing the listeners map in a created() callback, + // we do it lazily here so that subclasses could provide their own + // created() callback (Polymer currently doesn't allow calling overriden + // superclass methods in strict mode). + this.listeners_ = new WeakMap(); + } + this.listeners_.set(pane, listener); + pane.addEventListener('request-child-pane-change', listener); + + Polymer.dom(paneContainer).appendChild(pane); + pane.appended(); + }, + + /** + * Request rebuilding all panes in the view. The panes are rebuilt from the + * top to the bottom (so that parent panes could request changing their + * child panes when they're being rebuilt and the newly constructed child + * panes would be rebuilt as well). + */ + rebuild() { + let currentPane = Polymer.dom(this.$.pane_container).firstElementChild; + while (currentPane) { + currentPane.rebuild(); + currentPane = currentPane.nextElementSibling; + } + }, + + // For testing purposes. + get panesForTesting() { + const panes = []; + let currentChild = Polymer.dom(this.$.pane_container).firstElementChild; + while (currentChild) { + panes.push(currentChild); + currentChild = currentChild.nextElementSibling; + } + return panes; + } +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/stacked_pane_view_test.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/stacked_pane_view_test.html new file mode 100644 index 00000000000..ceae19ab0db --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/stacked_pane_view_test.html @@ -0,0 +1,205 @@ +<!DOCTYPE html> +<!-- +Copyright 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/analysis/stacked_pane.html"> +<link rel="import" href="/tracing/ui/analysis/stacked_pane_view.html"> + +<script> +'use strict'; + +tr.b.unittest.testSuite(function() { + function createPaneView() { + return document.createElement('tr-ui-a-stacked-pane-view'); + } + + function createPane(paneId, opt_rebuildPaneCallback, opt_appendedCallback) { + const paneEl = document.createElement('tr-ui-a-stacked-pane'); + paneEl.paneId = paneId; + + const divEl = document.createElement('div'); + Polymer.dom(divEl).textContent = 'Pane ' + paneId; + divEl.style.width = '400px'; + divEl.style.background = '#ccc'; + divEl.style.textAlign = 'center'; + Polymer.dom(paneEl).appendChild(divEl); + + if (opt_rebuildPaneCallback) { + paneEl.onRebuild_ = opt_rebuildPaneCallback; + } + + if (opt_appendedCallback) { + paneEl.appended = opt_appendedCallback; + } + + return paneEl; + } + + function createPaneBuilder(paneId, opt_rebuildPaneCallback, + opt_appendedCallback) { + return createPane.bind( + undefined, paneId, opt_rebuildPaneCallback, opt_appendedCallback); + } + + function assertPanes(paneView, expectedPaneIds) { + const actualPaneIds = paneView.panesForTesting.map(function(pane) { + return pane.paneId; + }); + assert.deepEqual(actualPaneIds, expectedPaneIds); + } + + test('instantiate_empty', function() { + const viewEl = createPaneView(); + viewEl.rebuild(); + assertPanes(viewEl, []); + // Don't add the pane to HTML output because it has zero height. + }); + + test('instantiate_singlePane', function() { + const viewEl = createPaneView(); + + viewEl.setPaneBuilder(createPaneBuilder(1)); + viewEl.rebuild(); + + assertPanes(viewEl, [1]); + this.addHTMLOutput(viewEl); + }); + + test('instantiate_multiplePanes', function() { + const viewEl = createPaneView(); + + viewEl.setPaneBuilder(createPaneBuilder(1)); + viewEl.setPaneBuilder(createPaneBuilder(2), viewEl.panesForTesting[0]); + viewEl.setPaneBuilder(createPaneBuilder(3), viewEl.panesForTesting[1]); + + assertPanes(viewEl, [1, 2, 3]); + this.addHTMLOutput(viewEl); + }); + + test('changePanes', function() { + const viewEl = createPaneView(); + + viewEl.setPaneBuilder(createPaneBuilder(1)); + assertPanes(viewEl, [1]); + + viewEl.setPaneBuilder(null); + assertPanes(viewEl, []); + + viewEl.setPaneBuilder(createPaneBuilder(2)); + assertPanes(viewEl, [2]); + + viewEl.setPaneBuilder(createPaneBuilder(3), viewEl.panesForTesting[0]); + assertPanes(viewEl, [2, 3]); + + viewEl.setPaneBuilder(createPaneBuilder(4), viewEl.panesForTesting[0]); + assertPanes(viewEl, [2, 4]); + + viewEl.setPaneBuilder(createPaneBuilder(5), viewEl.panesForTesting[1]); + assertPanes(viewEl, [2, 4, 5]); + + viewEl.setPaneBuilder(createPaneBuilder(6), viewEl.panesForTesting[2]); + assertPanes(viewEl, [2, 4, 5, 6]); + + viewEl.setPaneBuilder(createPaneBuilder(7), viewEl.panesForTesting[1]); + assertPanes(viewEl, [2, 4, 7]); + + this.addHTMLOutput(viewEl); + }); + + test('childPanes', function() { + const viewEl = createPaneView(); + + viewEl.setPaneBuilder(createPaneBuilder(1)); + assertPanes(viewEl, [1]); + + // Pane 1 requests a child pane 2. + const pane1 = viewEl.panesForTesting[0]; + pane1.childPaneBuilder = createPaneBuilder(2); + assertPanes(viewEl, [1, 2]); + + // Pane 2 requests removing its child pane (nothing happens). + const pane2 = viewEl.panesForTesting[1]; + pane2.childPaneBuilder = undefined; + assertPanes(viewEl, [1, 2]); + + // Pane 2 requests a child pane 3. + pane2.childPaneBuilder = createPaneBuilder(3); + assertPanes(viewEl, [1, 2, 3]); + + // Pane 2 requests a child pane 4 (its previous child pane 3 is removed). + pane2.childPaneBuilder = createPaneBuilder(4); + assertPanes(viewEl, [1, 2, 4]); + + // Pane 1 requests removing its child pane (panes 2 and 4 are removed). + pane1.childPaneBuilder = undefined; + assertPanes(viewEl, [1]); + + // Check that removed panes cannot affect the pane view. + pane2.childPaneBuilder = createPaneBuilder(5); + assertPanes(viewEl, [1]); + + // Pane 1 requests a child pane 6 (check that everything still works). + pane1.childPaneBuilder = createPaneBuilder(6); + assertPanes(viewEl, [1, 6]); + + // Change the top pane to pane 7. + viewEl.setPaneBuilder(createPaneBuilder(7)); + assertPanes(viewEl, [7]); + + // Check that removed panes cannot affect the pane view. + pane1.childPaneBuilder = createPaneBuilder(5); + assertPanes(viewEl, [7]); + }); + + test('rebuild', function() { + const viewEl = createPaneView(); + + const rebuiltPaneIds = []; + const rebuildPaneCallback = function() { + rebuiltPaneIds.push(this.paneId); + }; + + viewEl.setPaneBuilder(createPaneBuilder(1, rebuildPaneCallback)); + viewEl.setPaneBuilder(createPaneBuilder(2, rebuildPaneCallback), + viewEl.panesForTesting[0]); + viewEl.setPaneBuilder(createPaneBuilder(3, rebuildPaneCallback), + viewEl.panesForTesting[1]); + + // Rebuild isn't triggered. + assert.deepEqual(rebuiltPaneIds, []); + + // Rebuild is triggered, but it isn't necessary (all panes are clean). + viewEl.rebuild(); + assert.deepEqual(rebuiltPaneIds, []); + + // All panes are now marked as dirty, but rebuild isn't triggered (it was + // only scheduled). + viewEl.panesForTesting.forEach(function(pane) { + pane.scheduleRebuild_(); + }); + assert.deepEqual(rebuiltPaneIds, []); + + // Finally, rebuild was triggered and the panes are dirty. + viewEl.rebuild(); + assert.deepEqual(rebuiltPaneIds, [1, 2, 3]); + + // Make sure that panes are clean after the previous rebuild. + viewEl.rebuild(); + assert.deepEqual(rebuiltPaneIds, [1, 2, 3]); + }); + + test('appended', function() { + const viewEl = createPaneView(); + let didFireAppended; + + didFireAppended = false; + viewEl.setPaneBuilder(createPaneBuilder(1, undefined, function() { + didFireAppended = true; + })); + assert.isTrue(didFireAppended); + }); +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/stub_analysis_table.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/stub_analysis_table.html new file mode 100644 index 00000000000..b371eac4cf9 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/stub_analysis_table.html @@ -0,0 +1,48 @@ +<!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/base.html"> +<script> +'use strict'; + +tr.exportTo('tr.ui.analysis', function() { + function StubAnalysisTable() { + this.ownerDocument_ = document; + this.nodes_ = []; + } + + StubAnalysisTable.prototype = { + __proto__: Object.protoype, + + get ownerDocument() { + return this.ownerDocument_; + }, + + appendChild(node) { + if (node.tagName === 'TFOOT' || node.tagName === 'THEAD' || + node.tagName === 'TBODY') { + node.__proto__ = StubAnalysisTable.prototype; + node.nodes_ = []; + node.ownerDocument_ = document; + } + this.nodes_.push(node); + }, + + get lastNode() { + return this.nodes_.pop(); + }, + + get nodeCount() { + return this.nodes_.length; + } + }; + + return { + StubAnalysisTable, + }; +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/user_expectation_related_samples_table.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/user_expectation_related_samples_table.html new file mode 100644 index 00000000000..3c2bfdbd9cf --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/user_expectation_related_samples_table.html @@ -0,0 +1,91 @@ +<!DOCTYPE html> +<!-- +Copyright (c) 2016 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/analysis/analysis_link.html"> +<link rel="import" href="/tracing/ui/base/table.html"> + +<dom-module id='tr-ui-a-user-expectation-related-samples-table'> + <template> + <style> + #table { + flex: 1 1 auto; + align-self: stretch; + font-size: 12px; + } + </style> + <tr-ui-b-table id="table"></tr-ui-b-table> + </template> +</dom-module> +<script> +'use strict'; + +Polymer({ + is: 'tr-ui-a-user-expectation-related-samples-table', + + ready() { + this.samples_ = []; + this.$.table.tableColumns = [ + { + title: 'Event(s)', + value(row) { + const typeEl = document.createElement('span'); + typeEl.innerText = row.type; + if (row.tooltip) { + typeEl.title = row.tooltip; + } + return typeEl; + }, + width: '150px' + }, + { + title: 'Link', + width: '100%', + value(row) { + const linkEl = document.createElement('tr-ui-a-analysis-link'); + if (row.name) { + linkEl.setSelectionAndContent(row.selection, row.name); + } else { + linkEl.selection = row.selection; + } + return linkEl; + } + } + ]; + }, + + hasRelatedSamples() { + return (this.samples_ && this.samples_.length > 0); + }, + + set selection(eventSet) { + this.samples_ = []; + const samples = new tr.model.EventSet; + eventSet.forEach(function(ue) { + samples.addEventSet(ue.associatedSamples); + }.bind(this)); + + if (samples.length > 0) { + this.samples_.push({ + type: 'Overlapping samples', + tooltip: 'All samples overlapping the selected user expectation(s).', + selection: samples + }); + } + this.updateContents_(); + }, + + updateContents_() { + const table = this.$.table; + if (this.samples_ && this.samples_.length > 0) { + table.tableRows = this.samples_.slice(); + } else { + table.tableRows = []; + } + table.rebuild(); + } +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/user_expectation_related_samples_table_test.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/user_expectation_related_samples_table_test.html new file mode 100644 index 00000000000..368f41ce6c6 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/user_expectation_related_samples_table_test.html @@ -0,0 +1,64 @@ +<!DOCTYPE html> +<!-- +Copyright (c) 2016 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/core/test_utils.html"> +<link rel="import" href="/tracing/model/event_set.html"> +<link rel="import" href="/tracing/model/model.html"> +<link rel="import" href="/tracing/model/sample.html"> +<link rel="import" href="/tracing/model/thread_slice.html"> +<link rel="import" + href="/tracing/ui/analysis/user_expectation_related_samples_table.html"> + +<script> +'use strict'; + +tr.b.unittest.testSuite(function() { + function createModel() { + const m = tr.c.TestUtils.newModel(function(m) { + m.p1 = m.getOrCreateProcess(1); + m.t2 = m.p1.getOrCreateThread(2); + const node = tr.c.TestUtils.newProfileNodes(m, ['fake']); + const s1 = new tr.model.Sample(1, 'a_1', node, m.t2); + const s2 = new tr.model.Sample(2, 'a_2', node, m.t2); + const s3 = new tr.model.Sample(3, 'a_3', node, m.t2); + const s4 = new tr.model.Sample(4, 'a_4', node, m.t2); + const s5 = new tr.model.Sample(5, 'a_5', node, m.t2); + const s6 = new tr.model.Sample(6, 'a_6', node, m.t2); + m.samples.push(s1, s2, s3, s4, s5, s6); + m.ve = new tr.c.TestUtils.newSliceEx( + {title: 'V8.Execute', start: 0, end: 4, type: tr.model.ThreadSlice}); + m.t2.sliceGroup.pushSlice(m.ve); + m.up = new tr.c.TestUtils.newInteractionRecord(m, 0, 4); + m.up.associatedEvents.push(m.ve); + m.userModel.expectations.push(m.up); + }); + return m; + } + + test('overlappingSamples', function() { + const m = createModel(); + + const viewEl = document.createElement( + 'tr-ui-a-user-expectation-related-samples-table'); + viewEl.selection = new tr.model.EventSet([m.up]); + + let overlappingSamples; + viewEl.$.table.tableRows.forEach(function(row) { + if (row.type === 'Overlapping samples') { + assert.isUndefined(overlappingSamples); + overlappingSamples = row.selection; + } + }); + + const samplesTitles = overlappingSamples.map(function(e) { + return e.title; + }); + assert.sameMembers(samplesTitles, + ['a_1', 'a_2', 'a_3', 'a_4']); + }); +}); +</script> |