summaryrefslogtreecommitdiffstats
path: root/chromium/third_party/catapult/tracing/tracing/ui/analysis
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/third_party/catapult/tracing/tracing/ui/analysis')
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/alert_sub_view.html181
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/alert_sub_view_test.html82
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/analysis_link.html147
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/analysis_link_test.html58
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/analysis_sub_view.html266
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/analysis_sub_view_test.html35
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/analysis_view.html207
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/analysis_view_test.html141
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/container_memory_dump_sub_view.html200
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/container_memory_dump_sub_view_test.html351
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/counter_sample_sub_view.html139
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/counter_sample_sub_view_test.html177
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/flow_classifier.html92
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/flow_classifier_test.html52
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/frame_power_usage_chart.html134
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/frame_power_usage_chart_perf_test.html44
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/frame_power_usage_chart_test.html267
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/generic_object_view.html347
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/generic_object_view_test.html222
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_allocator_details_pane.html893
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_allocator_details_pane_test.html1261
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_header_pane.html178
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_header_pane_test.html134
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_heap_details_breakdown_view.html354
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_heap_details_pane.html451
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_heap_details_pane_test.html4045
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_heap_details_path_view.html149
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_heap_details_util.html101
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_overview_pane.html774
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_overview_pane_test.html840
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_sub_view_test_utils.html593
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_sub_view_util.html915
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_sub_view_util_test.html1241
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_vm_regions_details_pane.html382
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_vm_regions_details_pane_test.html496
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_async_slice_sub_view.html79
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_async_slice_sub_view_test.html47
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_cpu_slice_sub_view.html51
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_cpu_slice_sub_view_test.html48
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_event_sub_view.html211
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_event_sub_view_test.html94
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_event_summary.html207
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_event_summary_table.html358
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_event_summary_table_test.html119
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_event_summary_test.html111
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_flow_event_sub_view.html49
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_flow_event_sub_view_test.html39
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_frame_sub_view.html58
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_instant_event_sub_view.html48
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_instant_event_sub_view_test.html43
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_object_sub_view.html112
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_object_sub_view_test.html46
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_power_sample_sub_view.html77
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_power_sample_sub_view_test.html64
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_sample_sub_view.html234
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_sample_sub_view_test.html71
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_thread_slice_sub_view.html104
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_thread_slice_sub_view_test.html87
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_thread_time_slice_sub_view.html52
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_thread_time_slice_sub_view_test.html49
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_user_expectation_sub_view.html80
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/object_instance_view.html62
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/object_snapshot_view.html63
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/power_sample_summary_table.html135
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/power_sample_summary_table_test.html137
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/rebuildable_behavior.html57
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/rebuildable_behavior_test.html67
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/related_events.html354
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/related_events_test.html221
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/selection_summary_table.html97
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/selection_summary_table_test.html75
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/single_async_slice_sub_view.html79
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/single_async_slice_sub_view_test.html41
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/single_cpu_slice_sub_view.html149
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/single_cpu_slice_sub_view_test.html80
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/single_event_sub_view.html356
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/single_event_sub_view_test.html277
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/single_flow_event_sub_view.html82
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/single_flow_event_sub_view_test.html53
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/single_frame_sub_view.html61
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/single_instant_event_sub_view.html63
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/single_instant_event_sub_view_test.html42
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/single_object_instance_sub_view.html129
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/single_object_instance_sub_view_test.html34
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/single_object_snapshot_sub_view.html142
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/single_object_snapshot_sub_view_test.html32
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/single_power_sample_sub_view.html122
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/single_power_sample_sub_view_test.html42
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/single_sample_sub_view.html110
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/single_sample_sub_view_test.html62
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/single_thread_slice_sub_view.html61
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/single_thread_slice_sub_view_test.html80
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/single_thread_time_slice_sub_view.html186
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/single_thread_time_slice_sub_view_test.html92
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/single_user_expectation_sub_view.html91
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/stack_frame.html81
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/stack_frame_test.html34
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/stacked_pane.html61
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/stacked_pane_test.html38
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/stacked_pane_view.html195
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/stacked_pane_view_test.html205
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/stub_analysis_table.html48
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/user_expectation_related_samples_table.html91
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/analysis/user_expectation_related_samples_table_test.html64
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>