summaryrefslogtreecommitdiff
path: root/chromium/third_party/catapult/tracing/tracing/model
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/third_party/catapult/tracing/tracing/model')
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/activity.html56
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/alert.html55
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/annotation.html86
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/annotation_test.html44
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/async_slice.html169
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/async_slice_group.html211
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/async_slice_group_test.html171
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/async_slice_test.html57
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/clock_sync_manager.html467
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/clock_sync_manager_test.html471
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/comment_box_annotation.html60
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/compound_event_selection_state.html39
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/constants.html29
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/container_memory_dump.html92
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/container_memory_dump_test.html181
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/counter.html190
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/counter_sample.html95
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/counter_sample_test.html46
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/counter_series.html124
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/counter_test.html124
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/cpu.html282
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/cpu_slice.html68
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/cpu_test.html206
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/device.html124
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/device_test.html71
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/event.html77
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/event_container.html167
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/event_container_test.html95
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/event_info.html39
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/event_registry.html91
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/event_set.html302
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/event_set_test.html360
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/event_test.html46
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/flow_event.html67
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/frame.html96
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/global_memory_dump.html849
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/global_memory_dump_test.html3954
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/heap_dump.html76
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/heap_dump_test.html61
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/helpers/android_app.html344
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/helpers/android_model_helper.html105
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/helpers/android_model_helper_test.html267
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/helpers/android_surface_flinger.html115
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/helpers/chrome_browser_helper.html119
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/helpers/chrome_browser_helper_test.html74
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/helpers/chrome_gpu_helper.html47
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/helpers/chrome_gpu_helper_test.html110
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/helpers/chrome_model_helper.html168
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/helpers/chrome_model_helper_test.html238
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/helpers/chrome_process_helper.html120
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/helpers/chrome_renderer_helper.html76
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/helpers/chrome_renderer_helper_test.html16
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/helpers/chrome_thread_helper.html37
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/helpers/chrome_thread_helper_test.html97
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/helpers/telemetry_helper.html128
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/helpers/telemetry_helper_test.html101
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/instant_event.html100
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/interaction_record_test.html115
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/ir_coverage.html91
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/ir_coverage_test.html90
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/kernel.html137
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/kernel_test.html73
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/location.html158
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/memory_allocator_dump.html221
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/memory_allocator_dump_test.html221
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/memory_dump_test_utils.html220
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/model.html670
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/model_indices.html56
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/model_indices_test.html46
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/model_settings.html148
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/model_settings_test.html178
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/model_stats.html97
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/model_stats_test.html61
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/model_test.html343
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/object_collection.html229
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/object_collection_test.html230
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/object_instance.html213
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/object_instance_test.html70
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/object_snapshot.html90
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/object_snapshot_test.html40
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/power_sample.html71
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/power_sample_test.html51
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/power_series.html129
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/power_series_test.html151
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/process.html175
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/process_base.html244
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/process_memory_dump.html247
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/process_memory_dump_test.html561
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/process_test.html127
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/profile_node.html90
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/profile_tree.html88
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/proxy_selectable_item.html45
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/proxy_selectable_item_test.html39
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/rect_annotation.html65
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/resource_usage_sample.html74
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/resource_usage_sample_test.html51
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/resource_usage_series.html121
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/resource_usage_series_test.html152
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/sample.html97
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/sample_test.html31
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/scoped_id.html42
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/selectable_item.html57
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/selectable_item_test.html80
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/selection_state.html72
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/slice.html302
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/slice_group.html720
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/slice_group_test.html935
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/slice_test.html239
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/source_info/js_source_info.html57
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/source_info/source_info.html60
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/source_info/source_info_test.html27
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/stack_frame.html111
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/stack_frame_test.html29
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/thread.html358
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/thread_slice.html71
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/thread_slice_test.html41
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/thread_test.html209
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/thread_time_slice.html154
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/time_to_object_instance_map.html179
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/time_to_object_instance_map_test.html164
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/timed_event.html69
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/timed_event_test.html43
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/user_model/animation_expectation.html51
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/user_model/idle_expectation.html34
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/user_model/load_expectation.html93
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/user_model/response_expectation.html35
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/user_model/segment.html49
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/user_model/startup_expectation.html33
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/user_model/stub_expectation.html76
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/user_model/user_expectation.html177
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/user_model/user_model.html95
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/user_model/user_model_test.html45
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/vm_region.html444
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/vm_region_test.html1216
-rw-r--r--chromium/third_party/catapult/tracing/tracing/model/x_marker_annotation.html50
135 files changed, 24883 insertions, 0 deletions
diff --git a/chromium/third_party/catapult/tracing/tracing/model/activity.html b/chromium/third_party/catapult/tracing/tracing/model/activity.html
new file mode 100644
index 00000000000..2db257c7e06
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/activity.html
@@ -0,0 +1,56 @@
+<!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_scheme.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Class representing a user activity that is running
+ * in the process.
+ * On the Android platform, activities are mapped to Android Activities
+ * running in the foreground of the process.
+ * On Windows/OS X this could for example represent
+ * the currently active window of the process.
+ */
+tr.exportTo('tr.model', function() {
+ const ColorScheme = tr.b.ColorScheme;
+
+ /**
+ * @constructor
+ * @param {String} name Name of the activity
+ * @param {String} category Category of the activities
+ * @param {String} range The time range where the activity was running
+ * @param {String} args Additional arguments
+ */
+ function Activity(name, category, range, args) {
+ tr.model.TimedEvent.call(this, range.min);
+ this.title = name;
+ this.category = category;
+ this.colorId = ColorScheme.getColorIdForGeneralPurposeString(name);
+ this.duration = range.duration;
+ this.args = args;
+ this.name = name;
+ }
+
+ Activity.prototype = {
+ __proto__: tr.model.TimedEvent.prototype,
+
+ shiftTimestampsForward(amount) {
+ this.start += amount;
+ },
+
+ addBoundsToRange(range) {
+ range.addValue(this.start);
+ range.addValue(this.end);
+ }
+ };
+ return {
+ Activity,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/alert.html b/chromium/third_party/catapult/tracing/tracing/model/alert.html
new file mode 100644
index 00000000000..a8955caec93
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/alert.html
@@ -0,0 +1,55 @@
+<!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/model/event_info.html">
+<link rel="import" href="/tracing/model/event_set.html">
+<link rel="import" href="/tracing/model/timed_event.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.model', function() {
+ function Alert(info, start, opt_associatedEvents, opt_args) {
+ tr.model.TimedEvent.call(this, start);
+ this.info = info;
+ this.args = opt_args || {};
+ this.associatedEvents = new tr.model.EventSet(opt_associatedEvents);
+ this.associatedEvents.forEach(function(event) {
+ event.addAssociatedAlert(this);
+ }, this);
+ }
+
+ Alert.prototype = {
+ __proto__: tr.model.TimedEvent.prototype,
+
+ get title() {
+ return this.info.title;
+ },
+
+ get colorId() {
+ return this.info.colorId;
+ },
+
+ get userFriendlyName() {
+ return 'Alert ' + this.title + ' at ' +
+ tr.b.Unit.byName.timeStampInMs.format(this.start);
+ }
+ };
+
+ tr.model.EventRegistry.register(
+ Alert,
+ {
+ name: 'alert',
+ pluralName: 'alerts'
+ });
+
+ return {
+ Alert,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/annotation.html b/chromium/third_party/catapult/tracing/tracing/model/annotation.html
new file mode 100644
index 00000000000..9a3c1a726ba
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/annotation.html
@@ -0,0 +1,86 @@
+<!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/extension_registry.html">
+<link rel="import" href="/tracing/base/guid.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.model', function() {
+ /**
+ * Annotation is a base class that represents all annotation objects that
+ * can be drawn on the timeline.
+ *
+ * @constructor
+ */
+ function Annotation() {
+ this.guid_ = tr.b.GUID.allocateSimple();
+ this.view_ = undefined;
+ }
+
+ Annotation.fromDictIfPossible = function(args) {
+ if (args.typeName === undefined) {
+ throw new Error('Missing typeName argument');
+ }
+
+ const typeInfo = Annotation.findTypeInfoMatching(function(typeInfo) {
+ return typeInfo.metadata.typeName === args.typeName;
+ });
+
+ if (typeInfo === undefined) return undefined;
+
+ return typeInfo.constructor.fromDict(args);
+ };
+
+ Annotation.fromDict = function() {
+ throw new Error('Not implemented');
+ };
+
+ Annotation.prototype = {
+ get guid() {
+ return this.guid_;
+ },
+
+ // Invoked by trace model when this annotation is removed.
+ onRemove() {
+ },
+
+ toDict() {
+ throw new Error('Not implemented');
+ },
+
+ getOrCreateView(viewport) {
+ if (!this.view_) {
+ this.view_ = this.createView_(viewport);
+ }
+ return this.view_;
+ },
+
+ createView_() {
+ throw new Error('Not implemented');
+ }
+ };
+
+ const options = new tr.b.ExtensionRegistryOptions(tr.b. BASIC_REGISTRY_MODE);
+ tr.b.decorateExtensionRegistry(Annotation, options);
+
+ Annotation.addEventListener('will-register', function(e) {
+ if (!e.typeInfo.constructor.hasOwnProperty('fromDict')) {
+ throw new Error('Must have fromDict method');
+ }
+
+ if (!e.typeInfo.metadata.typeName) {
+ throw new Error('Registered Annotations must provide typeName');
+ }
+ });
+
+ return {
+ Annotation,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/annotation_test.html b/chromium/third_party/catapult/tracing/tracing/model/annotation_test.html
new file mode 100644
index 00000000000..f0d38843a3f
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/annotation_test.html
@@ -0,0 +1,44 @@
+<!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/comment_box_annotation.html">
+<link rel="import" href="/tracing/model/location.html">
+<link rel="import" href="/tracing/model/model.html">
+<link rel="import" href="/tracing/model/rect_annotation.html">
+<link rel="import" href="/tracing/model/x_marker_annotation.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ test('rectAnnotation', function() {
+ const fakeYComponents1 = [{stableId: '1.2', yPercentOffset: 0.3}];
+ const fakeYComponents2 = [{stableId: '1.2', yPercentOffset: 0.9}];
+ const start = new tr.model.Location(50, fakeYComponents1);
+ const end = new tr.model.Location(100, fakeYComponents2);
+ const rectAnnotation = new tr.model.RectAnnotation(start, end);
+ assert.strictEqual(rectAnnotation.startLocation, start);
+ assert.strictEqual(rectAnnotation.endLocation, end);
+ });
+
+ test('xMarkerAnnotation', function() {
+ const xMarkerAnnotation = new tr.model.XMarkerAnnotation(90);
+ assert.strictEqual(xMarkerAnnotation.timestamp, 90);
+ });
+
+ test('commentBoxAnnotation', function() {
+ const fakeYComponents = [{stableId: '1.2', yPercentOffset: 0.5}];
+ const location = new tr.model.Location(120, fakeYComponents);
+ const text = 'abc';
+ const commentBoxAnnotation =
+ new tr.model.CommentBoxAnnotation(location, text);
+ assert.strictEqual(commentBoxAnnotation.location, location);
+ assert.strictEqual(commentBoxAnnotation.text, text);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/async_slice.html b/chromium/third_party/catapult/tracing/tracing/model/async_slice.html
new file mode 100644
index 00000000000..9e395ab4cb7
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/async_slice.html
@@ -0,0 +1,169 @@
+<!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/timed_event.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Provides the AsyncSlice class.
+ */
+tr.exportTo('tr.model', function() {
+ /**
+ * A AsyncSlice represents an interval of time during which an
+ * asynchronous operation is in progress. An AsyncSlice consumes no CPU time
+ * itself and so is only associated with Threads at its start and end point.
+ *
+ * @constructor
+ */
+ function AsyncSlice(category, title, colorId, start, args, duration,
+ opt_isTopLevel, opt_cpuStart, opt_cpuDuration,
+ opt_argsStripped) {
+ tr.model.TimedEvent.call(this, start);
+
+ this.category = category || '';
+ // We keep the original title from the trace file in originalTitle since
+ // some sub-classes, e.g. NetAsyncSlice, change the title field.
+ this.originalTitle = title;
+ this.title = title;
+ this.colorId = colorId;
+ this.args = args;
+ this.startStackFrame = undefined;
+ this.endStackFrame = undefined;
+ this.didNotFinish = false;
+ this.important = false;
+ this.subSlices = [];
+ this.parentContainer_ = undefined;
+
+ this.id = undefined;
+ this.startThread = undefined;
+ this.endThread = undefined;
+ this.cpuStart = undefined;
+ this.cpuDuration = undefined;
+ this.argsStripped = false;
+
+ this.startStackFrame = undefined;
+ this.endStackFrame = undefined;
+
+ this.duration = duration;
+
+ // isTopLevel is set at import because only NESTABLE_ASYNC events might not
+ // be topLevel. All legacy async events are toplevel by definition.
+ this.isTopLevel = (opt_isTopLevel === true);
+
+ if (opt_cpuStart !== undefined) {
+ this.cpuStart = opt_cpuStart;
+ }
+
+ if (opt_cpuDuration !== undefined) {
+ this.cpuDuration = opt_cpuDuration;
+ }
+
+ if (opt_argsStripped !== undefined) {
+ this.argsStripped = opt_argsStripped;
+ }
+ }
+
+ AsyncSlice.prototype = {
+ __proto__: tr.model.TimedEvent.prototype,
+
+ get analysisTypeName() {
+ return this.title;
+ },
+
+ get parentContainer() {
+ return this.parentContainer_;
+ },
+
+ set parentContainer(parentContainer) {
+ this.parentContainer_ = parentContainer;
+ for (let i = 0; i < this.subSlices.length; i++) {
+ const subSlice = this.subSlices[i];
+ if (subSlice.parentContainer === undefined) {
+ subSlice.parentContainer = parentContainer;
+ }
+ }
+ },
+
+ get viewSubGroupTitle() {
+ return this.title;
+ },
+
+ // Certain AsyncSlices can provide a grouping key to group a set of
+ // independent tracks, while grouping by |viewSubGroupTitle| puts slices
+ // into a single (maybe multi-row but single) track.
+ get viewSubGroupGroupingKey() {
+ return undefined;
+ },
+
+ get userFriendlyName() {
+ return 'Async slice ' + this.title + ' at ' +
+ tr.b.Unit.byName.timeStampInMs.format(this.start);
+ },
+
+ get stableId() {
+ const parentAsyncSliceGroup = this.parentContainer.asyncSliceGroup;
+ return parentAsyncSliceGroup.stableId + '.' +
+ parentAsyncSliceGroup.slices.indexOf(this);
+ },
+
+ * findTopmostSlicesRelativeToThisSlice(eventPredicate, opt_this) {
+ if (eventPredicate(this)) {
+ yield this;
+ return;
+ }
+ for (const s of this.subSlices) {
+ yield* s.findTopmostSlicesRelativeToThisSlice(eventPredicate);
+ }
+ },
+
+ findDescendentSlice(targetTitle) {
+ if (!this.subSlices) return undefined;
+
+ for (let i = 0; i < this.subSlices.length; i++) {
+ if (this.subSlices[i].title === targetTitle) {
+ return this.subSlices[i];
+ }
+ const slice = this.subSlices[i].findDescendentSlice(targetTitle);
+ if (slice) return slice;
+ }
+ return undefined;
+ },
+
+ * enumerateAllDescendents() {
+ for (const slice of this.subSlices) {
+ yield slice;
+ }
+ for (const slice of this.subSlices) {
+ // Slices might contain sub-events different from AsyncSlice.
+ // We don't go any deeper in that case.
+ if (slice.enumerateAllDescendents !== undefined) {
+ yield* slice.enumerateAllDescendents();
+ }
+ }
+ },
+
+ compareTo(that) {
+ return this.title.localeCompare(that.title);
+ }
+ };
+
+ tr.model.EventRegistry.register(
+ AsyncSlice,
+ {
+ name: 'asyncSlice',
+ pluralName: 'asyncSlices'
+ });
+
+
+ return {
+ AsyncSlice,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/async_slice_group.html b/chromium/third_party/catapult/tracing/tracing/model/async_slice_group.html
new file mode 100644
index 00000000000..249e6396667
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/async_slice_group.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/guid.html">
+<link rel="import" href="/tracing/base/math/range.html">
+<link rel="import" href="/tracing/model/async_slice.html">
+<link rel="import" href="/tracing/model/event_container.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Provides the AsyncSliceGroup class.
+ */
+tr.exportTo('tr.model', function() {
+ /**
+ * Group of AsyncSlices associated with a thread or an upper-level
+ * AsyncSliceGroup. Thread can have number of AsyncSliceGroups and these
+ * groups can have nested groups too. No further nested levels are allowed.
+ */
+ class AsyncSliceGroup extends tr.model.EventContainer {
+ /**
+ * @param {Thread} parentContainer Thread on which async slices start.
+ * @param {String} opt_name Optional name (no IDs please) for settings key.
+ */
+ constructor(parentContainer, opt_name) {
+ super();
+ this.parentContainer_ = parentContainer;
+ this.name_ = opt_name;
+ this.slices = [];
+ this.viewSubGroups_ = undefined;
+
+ // Default values for the root group.
+ // Nested groups will get these values reassigned.
+ this.nestedLevel_ = 0;
+ this.hasNestedSubGroups_ = true;
+ this.title_ = undefined;
+ }
+
+ get parentContainer() {
+ return this.parentContainer_;
+ }
+
+ get model() {
+ return this.parentContainer_.parent.model;
+ }
+
+ get stableId() {
+ return this.parentContainer_.stableId + '.AsyncSliceGroup';
+ }
+
+ get title() {
+ // Title isn't defined for the root group (nested level 0) because
+ // slices it contains aren't grouped on that level.
+ // All the nested groups have their title which is:
+ // - |slice.viewSubGroupGroupingKey| if defined (level 1 only), or
+ // - |slice.viewSubGroupTitle| otherwise (most cases).
+ if (this.nested_level_ === 0) {
+ return '<root>';
+ }
+ return this.title_;
+ }
+
+ getSettingsKey() {
+ if (this.name_ === undefined) {
+ return undefined;
+ }
+ const parentKey = this.parentContainer_.getSettingsKey();
+ if (parentKey === undefined) {
+ return undefined;
+ }
+ return parentKey + '.' + this.name_;
+ }
+
+ /**
+ * Helper function that pushes the provided slice onto the slices array.
+ */
+ push(slice) {
+ if (this.viewSubGroups_ !== undefined) {
+ throw new Error(
+ 'No new slices are allowed when view sub-groups already formed.');
+ }
+ slice.parentContainer = this.parentContainer;
+ this.slices.push(slice);
+ return slice;
+ }
+
+ /**
+ * @return {Number} The number of slices in this group.
+ */
+ get length() {
+ return this.slices.length;
+ }
+
+ /**
+ * Shifts all the timestamps inside this group forward by the amount
+ * specified, including all nested subSlices if there are any.
+ */
+ shiftTimestampsForward(amount) {
+ for (const slice of this.childEvents()) {
+ slice.start += amount;
+ }
+ }
+
+ /**
+ * Updates the bounds for this group based on the slices it contains.
+ */
+ updateBounds() {
+ this.bounds.reset();
+ for (let i = 0; i < this.slices.length; i++) {
+ this.bounds.addValue(this.slices[i].start);
+ this.bounds.addValue(this.slices[i].end);
+ }
+ }
+
+ /**
+ * Closes any open slices.
+ * All open slices assumed as finished at the end of model's time bounds.
+ */
+ autoCloseOpenSlices() {
+ const maxTimestamp = this.parentContainer_.parent.model.bounds.max;
+ for (const slice of this.childEvents()) {
+ if (slice.didNotFinish) {
+ slice.duration = maxTimestamp - slice.start;
+ }
+ }
+ }
+
+ /**
+ * Get AsyncSlice sub-groups arranged by title and grouping key.
+ *
+ * @return {Array} An array of AsyncSliceGroups where each group has
+ * a title and sub-set of the original slices. Returns an empty array
+ * if group can't be sub-divided.
+ */
+ get viewSubGroups() {
+ // Only 2 nested levels are allowed (see class description).
+ // Also it's always known in advance whether group will be sub-divided or
+ // not (most of them). Root group is always divisible.
+ if (!this.hasNestedSubGroups_ || this.nestedLevel_ === 2) {
+ return [];
+ }
+ if (this.viewSubGroups_ !== undefined) {
+ return this.viewSubGroups_;
+ }
+
+ const subGroupsByTitle = new Map();
+
+ for (const slice of this.slices) {
+ // Group by title by default.
+ let subGroupTitle = slice.viewSubGroupTitle;
+ let hasNestedSubGroups = false;
+
+ // Apply custom grouping rules for special slice classes.
+ if (this.nestedLevel_ === 0 &&
+ slice.viewSubGroupGroupingKey !== undefined) {
+ subGroupTitle = slice.viewSubGroupGroupingKey;
+ hasNestedSubGroups = true;
+ }
+
+ let subGroup = subGroupsByTitle.get(subGroupTitle);
+ if (subGroup === undefined) {
+ let name;
+ if (this.name_ !== undefined) {
+ name = this.name_ + '.' + subGroupTitle;
+ } else {
+ name = subGroupTitle;
+ }
+ subGroup = new AsyncSliceGroup(this.parentContainer_, name);
+ subGroup.title_ = subGroupTitle;
+ subGroup.hasNestedSubGroups_ = hasNestedSubGroups;
+ subGroup.nestedLevel_ = this.nestedLevel_ + 1;
+ subGroupsByTitle.set(subGroupTitle, subGroup);
+ }
+ subGroup.push(slice);
+ }
+
+ this.viewSubGroups_ = Array.from(subGroupsByTitle.values());
+ this.viewSubGroups_.sort((a, b) => a.title.localeCompare(b.title));
+ return this.viewSubGroups_;
+ }
+
+ * findTopmostSlicesInThisContainer(eventPredicate, opt_this) {
+ for (const slice of this.slices) {
+ if (slice.isTopLevel) {
+ yield* slice.findTopmostSlicesRelativeToThisSlice(
+ eventPredicate, opt_this);
+ }
+ }
+ }
+
+ * childEvents() {
+ for (const slice of this.slices) {
+ yield slice;
+ yield* slice.enumerateAllDescendents();
+ }
+ }
+
+ * childEventContainers() {
+ }
+ }
+
+ return {
+ AsyncSliceGroup,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/async_slice_group_test.html b/chromium/third_party/catapult/tracing/tracing/model/async_slice_group_test.html
new file mode 100644
index 00000000000..f996a63f2b9
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/async_slice_group_test.html
@@ -0,0 +1,171 @@
+<!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/chrome_config.html">
+<link rel="import" href="/tracing/model/model.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const Process = tr.model.Process;
+ const Thread = tr.model.Thread;
+ const AsyncSlice = tr.model.AsyncSlice;
+ const AsyncSliceGroup = tr.model.AsyncSliceGroup;
+ const newAsyncSlice = tr.c.TestUtils.newAsyncSlice;
+ const newAsyncSliceEx = tr.c.TestUtils.newAsyncSliceEx;
+ const newModel = tr.c.TestUtils.newModel;
+
+ test('asyncSliceGroupBounds_Empty', function() {
+ const thread = {};
+ const g = new AsyncSliceGroup(thread);
+ g.updateBounds();
+ assert.isTrue(g.bounds.isEmpty);
+ });
+
+ test('asyncSliceGroupBounds_Basic', function() {
+ const model = new tr.Model();
+ const p1 = new Process(model, 1);
+ const t1 = p1.getOrCreateThread(456);
+ const g = new AsyncSliceGroup(t1);
+ g.push(newAsyncSlice(0, 1, t1, t1));
+ g.push(newAsyncSlice(1, 1.5, t1, t1));
+ assert.strictEqual(g.length, 2);
+ g.updateBounds();
+ assert.strictEqual(g.bounds.min, 0);
+ assert.strictEqual(g.bounds.max, 2.5);
+ });
+
+ test('asyncSliceGroupChildEvents', function() {
+ const t1 = {}; // Fake thread.
+ const g = new AsyncSliceGroup(t1);
+ const sl1 = newAsyncSlice(0, 1, t1, t1);
+ const sl2 = newAsyncSlice(1, 3, t1, t1);
+ const sl2sub1 = newAsyncSlice(1, 2, t1, t1);
+ sl2.subSlices.push(sl2sub1);
+ const sl2sub1sub1 = newAsyncSlice(2, 1, t1, t1);
+ sl2sub1.subSlices.push(sl2sub1sub1);
+ const sl2sub2 = newAsyncSlice(3, 1, t1, t1);
+ sl2.subSlices.push(sl2sub2);
+ g.push(sl1);
+ g.push(sl2);
+
+ assert.sameMembers(
+ Array.from(g.childEvents()), [sl1, sl2, sl2sub1, sl2sub1sub1, sl2sub2]);
+ });
+
+ test('asyncSliceGroupShiftTimestamps', function() {
+ const t1 = {}; // Fake thread.
+ const g = new AsyncSliceGroup(t1);
+ const sl1 = newAsyncSlice(1, 2, t1, t1);
+ const sl2 = newAsyncSlice(3, 4, t1, t1);
+ const sl2sub1 = newAsyncSlice(3.5, 2, t1, t1);
+ sl2.subSlices.push(sl2sub1);
+ const sl2sub1sub1 = newAsyncSlice(4, 0.5, t1, t1);
+ sl2sub1.subSlices.push(sl2sub1sub1);
+ g.push(sl1);
+ g.push(sl2);
+
+ g.updateBounds();
+ assert.strictEqual(g.bounds.min, 1);
+ assert.strictEqual(g.bounds.max, 7);
+
+ g.shiftTimestampsForward(1.5);
+ g.updateBounds();
+ assert.strictEqual(g.bounds.min, 2.5);
+ assert.strictEqual(g.bounds.max, 8.5);
+ assert.strictEqual(sl2sub1.start, 5);
+ assert.strictEqual(sl2sub1.duration, 2);
+ assert.strictEqual(sl2sub1sub1.start, 5.5);
+ assert.strictEqual(sl2sub1sub1.duration, 0.5);
+ });
+
+ test('asyncSliceGroupViewSubGroups', function() {
+ const model = new tr.Model();
+ const p1 = new Process(model, 1);
+ p1.name = 'Renderer';
+ const t1 = p1.getOrCreateThread(321);
+ t1.name = 'MainThread';
+ const g = new AsyncSliceGroup(t1);
+ g.push(newAsyncSliceEx(
+ { title: 'VeryBusy',
+ start: 0, duration: 1 }));
+ g.push(newAsyncSliceEx(
+ { cat: 'renderer.scheduler',
+ id: ':ptr:0xdeadbeef', title: 'FrameScheduler.Foo',
+ start: 0.5, duration: 0.1 }));
+ g.push(newAsyncSliceEx(
+ { cat: 'renderer.scheduler',
+ id: ':ptr:0xdeadbeef', title: 'FrameScheduler.Bar',
+ start: 0.55, duration: 0.2 }));
+ g.push(newAsyncSliceEx(
+ { cat: 'renderer.scheduler',
+ id: ':ptr:0x1ee7beef', title: 'FrameScheduler.Baz',
+ start: 0.3, duration: 0.3 }));
+ g.push(newAsyncSliceEx(
+ { cat: 'renderer.scheduler',
+ id: ':ptr:0x1ee7beef', title: 'FrameScheduler.Baz',
+ start: 0.7, duration: 0.2 }));
+ g.push(newAsyncSliceEx(
+ { title: 'VeryBusy',
+ start: 1, duration: 1.5 }));
+ g.push(newAsyncSliceEx(
+ { title: 'Loading',
+ start: 0, duration: 5 }));
+ assert.strictEqual(g.length, 7);
+
+ const vsg = g.viewSubGroups;
+ assert.strictEqual(vsg.length, 4);
+ // Groups must be sorted by title.
+ assert.strictEqual(vsg[0].title, 'Frame:ptr:0x1ee7beef');
+ assert.strictEqual(vsg[1].title, 'Frame:ptr:0xdeadbeef');
+ assert.strictEqual(vsg[2].title, 'Loading');
+ assert.strictEqual(vsg[3].title, 'VeryBusy');
+ // Check nested view sub-groups.
+ assert.strictEqual(vsg[2].viewSubGroups.length, 0);
+ assert.strictEqual(vsg[3].viewSubGroups.length, 0);
+ const wf1vsg = vsg[0].viewSubGroups;
+ assert.strictEqual(wf1vsg.length, 1);
+ assert.strictEqual(wf1vsg[0].title, 'Baz');
+ assert.strictEqual(wf1vsg[0].getSettingsKey(),
+ 'processes.Renderer.MainThread.Frame:ptr:0x1ee7beef.Baz');
+ const wf2vsg = vsg[1].viewSubGroups;
+ assert.strictEqual(wf2vsg.length, 2);
+ assert.strictEqual(wf2vsg[0].title, 'Bar');
+ assert.strictEqual(wf2vsg[0].getSettingsKey(),
+ 'processes.Renderer.MainThread.Frame:ptr:0xdeadbeef.Bar');
+ assert.strictEqual(wf2vsg[1].title, 'Foo');
+ assert.strictEqual(wf2vsg[1].getSettingsKey(),
+ 'processes.Renderer.MainThread.Frame:ptr:0xdeadbeef.Foo');
+ });
+
+ test('asyncSliceGroupStableId', function() {
+ const model = new tr.Model();
+ const process = model.getOrCreateProcess(123);
+ const thread = process.getOrCreateThread(456);
+ const group = new AsyncSliceGroup(thread);
+
+ assert.strictEqual(process.stableId, 123);
+ assert.strictEqual(thread.stableId, '123.456');
+ assert.strictEqual(group.stableId, '123.456.AsyncSliceGroup');
+ });
+
+ test('asyncSliceParentContainerSetAtPush', function() {
+ const m = newModel(function(m) {
+ m.process = m.getOrCreateProcess(123);
+ m.thread = m.process.getOrCreateThread(456);
+ m.group = new AsyncSliceGroup(m.thread);
+
+ m.sA = m.group.push(newAsyncSliceEx(
+ { title: 'sA', start: 0.0, duration: 10.0 }));
+ });
+
+ assert.deepEqual(m.sA.parentContainer, m.thread);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/async_slice_test.html b/chromium/third_party/catapult/tracing/tracing/model/async_slice_test.html
new file mode 100644
index 00000000000..1de8493035e
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/async_slice_test.html
@@ -0,0 +1,57 @@
+<!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">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const AsyncSlice = tr.model.AsyncSlice;
+ const Process = tr.model.Process;
+ const Thread = tr.model.Thread;
+ const newAsyncSlice = tr.c.TestUtils.newAsyncSlice;
+ const newAsyncSliceEx = tr.c.TestUtils.newAsyncSliceEx;
+ const newFakeThread = tr.c.TestUtils.newFakeThread;
+
+ test('stableId', function() {
+ const thread = newFakeThread();
+ const group = thread.asyncSliceGroup;
+
+ const sA = group.push(newAsyncSliceEx(
+ { title: 'sA', start: 0.0, duration: 10.0 }));
+ const sB = group.push(newAsyncSliceEx(
+ { title: 'sB', start: 10.0, duration: 20.0 }));
+ const sC = group.push(newAsyncSliceEx(
+ { title: 'sC', start: 20.0, duration: 30.0 }));
+
+ assert.strictEqual(group.stableId + '.0', sA.stableId);
+ assert.strictEqual(group.stableId + '.1', sB.stableId);
+ assert.strictEqual(group.stableId + '.2', sC.stableId);
+ });
+
+ test('setParentContainerForSubSlices', function() {
+ const model = new tr.Model();
+ const p1 = new Process(model, 1);
+ const t1 = new Thread(p1, 1);
+ const asyncSlice = newAsyncSlice(0, 10, t1, t1);
+ const subSlice1 = newAsyncSlice(1, 5, t1, t1);
+ const subSlice2 = newAsyncSlice(6, 9, t1, t1);
+ const subSlice3 = newAsyncSlice(2, 3, t1, t1);
+ subSlice1.subSlices.push(subSlice3);
+ asyncSlice.subSlices.push(subSlice1);
+ asyncSlice.subSlices.push(subSlice2);
+ asyncSlice.parentContainer = t1;
+ assert.strictEqual(asyncSlice.subSlices.length, 2);
+ assert.strictEqual(subSlice1.subSlices.length, 1);
+ assert.deepEqual(asyncSlice.parentContainer, t1);
+ assert.deepEqual(subSlice1.parentContainer, t1);
+ assert.deepEqual(subSlice2.parentContainer, t1);
+ assert.deepEqual(subSlice3.parentContainer, t1);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/clock_sync_manager.html b/chromium/third_party/catapult/tracing/tracing/model/clock_sync_manager.html
new file mode 100644
index 00000000000..cb9a30bad79
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/clock_sync_manager.html
@@ -0,0 +1,467 @@
+<!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/utils.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.model', function() {
+ const ClockDomainId = {
+ BATTOR: 'BATTOR',
+
+ // NOTE: Exists for backwards compatibility with old Chrome traces which
+ // didn't explicitly specify the clock being used.
+ UNKNOWN_CHROME_LEGACY: 'UNKNOWN_CHROME_LEGACY',
+
+ LINUX_CLOCK_MONOTONIC: 'LINUX_CLOCK_MONOTONIC',
+ LINUX_FTRACE_GLOBAL: 'LINUX_FTRACE_GLOBAL',
+ MAC_MACH_ABSOLUTE_TIME: 'MAC_MACH_ABSOLUTE_TIME',
+ WIN_ROLLOVER_PROTECTED_TIME_GET_TIME:
+ 'WIN_ROLLOVER_PROTECTED_TIME_GET_TIME',
+ WIN_QPC: 'WIN_QPC',
+
+ // 'TELEMETRY' and 'SYSTRACE' aren't really clock domains because they
+ // actually can use one of several clock domains, just like Chrome. However,
+ // because there's a chance that they are running off of the same clock as
+ // Chrome (e.g. LINUX_CLOCK_MONOTONIC) but on a separate device (i.e. on a
+ // a host computer with Chrome running on an attached phone), there's a
+ // chance that Chrome and the controller will erroneously get put into
+ // the same clock domain. The solution for this is that clock domains should
+ // actually be some (unique_device_id, clock_id) tuple. For now, though,
+ // we'll hack around this by putting Telemetry into its own clock domain.
+ SYSTRACE: 'SYSTRACE',
+ TELEMETRY: 'TELEMETRY'
+ };
+
+ const POSSIBLE_CHROME_CLOCK_DOMAINS = new Set([
+ ClockDomainId.UNKNOWN_CHROME_LEGACY,
+ ClockDomainId.LINUX_CLOCK_MONOTONIC,
+ ClockDomainId.MAC_MACH_ABSOLUTE_TIME,
+ ClockDomainId.WIN_ROLLOVER_PROTECTED_TIME_GET_TIME,
+ ClockDomainId.WIN_QPC
+ ]);
+
+ // The number of milliseconds above which the BattOr sync is no longer
+ // considered "fast", and it's more accurate to use the sync start timestamp
+ // instead of the normal sync timestamp due to a bug in the Chrome serial code
+ // making serial reads too slow.
+ const BATTOR_FAST_SYNC_THRESHOLD_MS = 3;
+
+ /**
+ * A ClockSyncManager holds clock sync markers and uses them to shift
+ * timestamps from agents' clock domains onto the model's clock domain.
+ *
+ * In this context, a "clock domain" is a single perspective on the passage
+ * of time. A single computer can have multiple clock domains because it
+ * can have multiple methods of retrieving a timestamp (e.g.
+ * clock_gettime(CLOCK_MONOTONIC) and clock_gettime(CLOCK_REALTIME) on Linux).
+ * Another common reason for multiple clock domains within a single trace
+ * is that traces can span devices (e.g. a laptop collecting a Chrome trace
+ * can have its power consumption recorded by a second device and the two
+ * traces can be viewed alongside each other).
+ *
+ * For more information on how to synchronize multiple time domains using this
+ * method, see: http://bit.ly/1OVkqju.
+ *
+ * @constructor
+ */
+ function ClockSyncManager() {
+ // A set of all domains seen by the ClockSyncManager.
+ this.domainsSeen_ = new Set();
+ this.markersBySyncId_ = new Map();
+ // transformerMapByDomainId_[fromDomainId][toDomainId] returns the function
+ // that converts timestamps in the "from" domain to timestamps in the "to"
+ // domain.
+ this.transformerMapByDomainId_ = {};
+ }
+
+ ClockSyncManager.prototype = {
+ /**
+ * Adds a clock sync marker to the list of known markers.
+ *
+ * @param {string} domainId The clock domain that the marker is in.
+ * @param {string} syncId The identifier shared by both sides of the clock
+ * sync marker.
+ * @param {number} startTs The time (in ms) at which the sync started.
+ * @param {number=} opt_endTs The time (in ms) at which the sync ended. If
+ * unspecified, it's assumed to be the same as the start,
+ * indicating an instantaneous sync.
+ */
+ addClockSyncMarker(domainId, syncId, startTs, opt_endTs) {
+ this.onDomainSeen_(domainId);
+
+ if (Object.values(ClockDomainId).indexOf(domainId) < 0) {
+ throw new Error('"' + domainId + '" is not in the list of known ' +
+ 'clock domain IDs.');
+ }
+
+ if (this.modelDomainId_) {
+ throw new Error('Cannot add new clock sync markers after getting ' +
+ 'a model time transformer.');
+ }
+
+ const marker = new ClockSyncMarker(domainId, startTs, opt_endTs);
+
+ if (!this.markersBySyncId_.has(syncId)) {
+ this.markersBySyncId_.set(syncId, [marker]);
+ return;
+ }
+
+ const markers = this.markersBySyncId_.get(syncId);
+
+ if (markers.length === 2) {
+ throw new Error('Clock sync with ID "' + syncId + '" is already ' +
+ 'complete - cannot add a third clock sync marker to it.');
+ }
+
+ if (markers[0].domainId === domainId) {
+ throw new Error('A clock domain cannot sync with itself.');
+ }
+
+ markers.push(marker);
+ this.onSyncCompleted_(markers[0], marker);
+ },
+
+ // TODO(charliea): Remove this once the clockSyncMetric is no longer using
+ // it.
+ get markersBySyncId() {
+ return this.markersBySyncId_;
+ },
+
+ /** @return {Set<String>} The string IDs of the domains seen so far. */
+ get domainsSeen() {
+ return this.domainsSeen_;
+ },
+
+ /**
+ * Returns a function that, given a timestamp in the domain with |domainId|,
+ * returns a timestamp in the model's clock domain.
+ *
+ * NOTE: All clock sync markers should be added before calling this function
+ * for the first time. This is because the first time that this function is
+ * called, a model clock domain is selected. This clock domain must have
+ * syncs connecting it with all other clock domains. If multiple clock
+ * domains are viable candidates, the one with the clock domain ID that is
+ * the first alphabetically is selected.
+ */
+ getModelTimeTransformer(domainId) {
+ this.onDomainSeen_(domainId);
+
+ if (!this.modelDomainId_) {
+ this.selectModelDomainId_();
+ }
+
+ return this.getTimeTransformerRaw_(domainId, this.modelDomainId_).fn;
+ },
+
+ /**
+ * Returns the error associated with the transformation given by
+ * |getModelTimeTransformer(domainId)|.
+ */
+ getTimeTransformerError(fromDomainId, toDomainId) {
+ this.onDomainSeen_(fromDomainId);
+ this.onDomainSeen_(toDomainId);
+ return this.getTimeTransformerRaw_(fromDomainId, toDomainId).error;
+ },
+
+ getTimeTransformerRaw_(fromDomainId, toDomainId) {
+ const transformer =
+ this.getTransformerBetween_(fromDomainId, toDomainId);
+ if (!transformer) {
+ throw new Error('No clock sync markers exist pairing clock domain "' +
+ fromDomainId + '" ' + 'with target clock domain "' +
+ toDomainId + '".');
+ }
+
+ return transformer;
+ },
+
+ /**
+ * Returns a function that, given a timestamp in the "from" domain, returns
+ * a timestamp in the "to" domain.
+ */
+ getTransformerBetween_(fromDomainId, toDomainId) {
+ // Do a breadth-first search from the "from" domain until we reach the
+ // "to" domain.
+ const visitedDomainIds = new Set();
+ // Keep a queue of nodes to visit, starting with the "from" domain.
+ const queue = [{
+ domainId: fromDomainId,
+ transformer: Transformer.IDENTITY
+ }];
+
+ while (queue.length > 0) {
+ // NOTE: Using a priority queue here would theoretically be much more
+ // efficient, but the actual performance difference is negligible given
+ // how few clock domains we have in a trace.
+ queue.sort((domain1, domain2) =>
+ domain1.transformer.error - domain2.transformer.error);
+
+ const current = queue.shift();
+
+ if (current.domainId === toDomainId) {
+ return current.transformer;
+ }
+
+ if (visitedDomainIds.has(current.domainId)) {
+ continue;
+ }
+ visitedDomainIds.add(current.domainId);
+
+ const outgoingTransformers =
+ this.transformerMapByDomainId_[current.domainId];
+
+ if (!outgoingTransformers) continue;
+
+ // Add all nodes that are directly connected to this one to the queue.
+ for (const outgoingDomainId in outgoingTransformers) {
+ // We have two transformers: one to get us from the "from" domain to
+ // the current domain, and another to get us from the current domain
+ // to the next domain. By composing those two transformers, we can
+ // create one that gets us from the "from" domain to the next domain.
+ const toNextDomainTransformer =
+ outgoingTransformers[outgoingDomainId];
+ const toCurrentDomainTransformer = current.transformer;
+
+ queue.push({
+ domainId: outgoingDomainId,
+ transformer: Transformer.compose(
+ toNextDomainTransformer, toCurrentDomainTransformer)
+ });
+ }
+ }
+
+ return undefined;
+ },
+
+ /**
+ * Selects the domain to use as the model domain from among the domains
+ * with registered markers.
+ *
+ * This is necessary because some common domain must be chosen before all
+ * timestamps can be shifted onto the same domain.
+ *
+ * For the time being, preference is given to Chrome clock domains. If no
+ * Chrome clock domain is present, the first clock domain alphabetically
+ * is selected.
+ */
+ selectModelDomainId_() {
+ this.ensureAllDomainsAreConnected_();
+
+ // While we're migrating to the new clock sync system, we have to make
+ // sure to prefer the Chrome clock domain because legacy clock sync
+ // mechanisms assume that's the case.
+ for (const chromeDomainId of POSSIBLE_CHROME_CLOCK_DOMAINS) {
+ if (this.domainsSeen_.has(chromeDomainId)) {
+ this.modelDomainId_ = chromeDomainId;
+ return;
+ }
+ }
+
+ const domainsSeenArray = Array.from(this.domainsSeen_);
+ domainsSeenArray.sort();
+ this.modelDomainId_ = domainsSeenArray[0];
+ },
+
+ /** Throws an error if all domains are not connected. */
+ ensureAllDomainsAreConnected_() {
+ // NOTE: this is a ridiculously inefficient way to do this. Given how few
+ // clock domains we're likely to have, this shouldn't be a problem.
+ let firstDomainId = undefined;
+ for (const domainId of this.domainsSeen_) {
+ if (!firstDomainId) {
+ firstDomainId = domainId;
+ continue;
+ }
+
+ if (!this.getTransformerBetween_(firstDomainId, domainId)) {
+ throw new Error('Unable to select a master clock domain because no ' +
+ 'path can be found from "' + firstDomainId + '" to "' + domainId +
+ '".');
+ }
+ }
+
+ return true;
+ },
+
+ /** Observer called each time that a clock domain is seen. */
+ onDomainSeen_(domainId) {
+ if (domainId === ClockDomainId.UNKNOWN_CHROME_LEGACY &&
+ !this.domainsSeen_.has(ClockDomainId.UNKNOWN_CHROME_LEGACY)) {
+ // UNKNOWN_CHROME_LEGACY was just seen for the first time: collapse it
+ // and the other Chrome clock domains into one.
+ //
+ // This makes sure that we don't have two separate clock sync graphs:
+ // one attached to UNKNOWN_CHROME_LEGACY and the other attached to the
+ // real Chrome clock domain.
+ for (const chromeDomainId of POSSIBLE_CHROME_CLOCK_DOMAINS) {
+ if (chromeDomainId === ClockDomainId.UNKNOWN_CHROME_LEGACY) {
+ continue;
+ }
+
+ this.collapseDomains_(
+ ClockDomainId.UNKNOWN_CHROME_LEGACY, chromeDomainId);
+ }
+ }
+
+ this.domainsSeen_.add(domainId);
+ },
+
+ /**
+ * Observer called when a complete sync is made involving |marker1| and
+ * |marker2|.
+ */
+ onSyncCompleted_(marker1, marker2) {
+ const forwardTransformer = Transformer.fromMarkers(marker1, marker2);
+ const backwardTransformer = Transformer.fromMarkers(marker2, marker1);
+
+ const existingTransformer =
+ this.getOrCreateTransformerMap_(marker1.domainId)[marker2.domainId];
+ if (!existingTransformer ||
+ forwardTransformer.error < existingTransformer.error) {
+ this.getOrCreateTransformerMap_(marker1.domainId)[marker2.domainId] =
+ forwardTransformer;
+ this.getOrCreateTransformerMap_(marker2.domainId)[marker1.domainId] =
+ backwardTransformer;
+ }
+ },
+
+ /** Makes timestamps in the two clock domains interchangeable. */
+ collapseDomains_(domain1Id, domain2Id) {
+ this.getOrCreateTransformerMap_(domain1Id)[domain2Id] =
+ this.getOrCreateTransformerMap_(domain2Id)[domain1Id] =
+ Transformer.IDENTITY;
+ },
+
+ /**
+ * Returns (and creates if it doesn't exist) the transformer map describing
+ * how to transform timestamps between directly connected clock domains.
+ */
+ getOrCreateTransformerMap_(domainId) {
+ if (!this.transformerMapByDomainId_[domainId]) {
+ this.transformerMapByDomainId_[domainId] = {};
+ }
+
+ return this.transformerMapByDomainId_[domainId];
+ },
+
+ /**
+ * @return {string} The clock sync graph represented in the DOT language.
+ * This is useful for debugging incorrect clock sync behavior.
+ */
+ computeDotGraph() {
+ let dotString = 'graph {\n';
+
+ const domainsSeen = [...this.domainsSeen_].sort();
+ for (const domainId of domainsSeen) {
+ dotString += ` ${domainId}[shape=box]\n`;
+ }
+
+ const markersBySyncIdEntries = [...this.markersBySyncId_.entries()].sort(
+ ([syncId1, markers1], [syncId2, markers2]) =>
+ syncId1.localeCompare(syncId2));
+
+ for (const [syncId, markers] of markersBySyncIdEntries) {
+ const sortedMarkers = markers.sort(
+ (a, b) => a.domainId.localeCompare(b.domainId));
+ for (const m of markers) {
+ dotString += ` "${syncId}" -- ${m.domainId} `;
+ dotString += `[label="[${m.startTs}, ${m.endTs}]"]\n`;
+ }
+ }
+
+ dotString += '}';
+
+ return dotString;
+ }
+ };
+
+ /**
+ * A ClockSyncMarker is an internal entity that represents a marker in a
+ * trace log indicating that a clock sync happened at a specified time.
+ *
+ * If no end timestamp argument is specified in the constructor, it's assumed
+ * that the end timestamp is the same as the start (i.e. the clock sync
+ * was instantaneous).
+ */
+ function ClockSyncMarker(domainId, startTs, opt_endTs) {
+ this.domainId = domainId;
+ this.startTs = startTs;
+ this.endTs = opt_endTs === undefined ? startTs : opt_endTs;
+ }
+
+ ClockSyncMarker.prototype = {
+ get duration() { return this.endTs - this.startTs; },
+ get ts() { return this.startTs + this.duration / 2; }
+ };
+
+ /**
+ * A Transformer encapsulates information about how to turn timestamps in one
+ * clock domain into timestamps in another. It also stores additional data
+ * about the maximum error involved in doing so.
+ */
+ function Transformer(fn, error) {
+ this.fn = fn;
+ this.error = error;
+ }
+
+ Transformer.IDENTITY = new Transformer((x => x), 0);
+
+ /**
+ * Given two transformers, creates a third that's a composition of the two.
+ *
+ * @param {function(Number): Number} aToB A function capable of converting a
+ * timestamp from domain A to domain B.
+ * @param {function(Number): Number} bToC A function capable of converting a
+ * timestamp from domain B to domain C.
+ *
+ * @return {function(Number): Number} A function capable of converting a
+ * timestamp from domain A to domain C.
+ */
+ Transformer.compose = function(aToB, bToC) {
+ return new Transformer(
+ (ts) => bToC.fn(aToB.fn(ts)), aToB.error + bToC.error);
+ };
+
+ /**
+ * Returns a function that, given a timestamp in |fromMarker|'s domain,
+ * returns a timestamp in |toMarker|'s domain.
+ */
+ Transformer.fromMarkers = function(fromMarker, toMarker) {
+ let fromTs = fromMarker.ts;
+ let toTs = toMarker.ts;
+
+ // TODO(charliea): Usually, we estimate that the clock sync marker is
+ // issued by the agent exactly in the middle of the controller's start and
+ // end timestamps. However, there's currently a bug in the Chrome serial
+ // code that's making the clock sync ack for BattOr take much longer to
+ // read than it should (by about 8ms). This is causing the above estimate
+ // of the controller's sync timestamp to be off by a substantial enough
+ // amount that it makes traces hard to read. For now, make an exception
+ // for BattOr and just use the controller's start timestamp as the sync
+ // time. In the medium term, we should fix the Chrome serial code in order
+ // to remove this special logic and get an even more accurate estimate.
+ if (fromMarker.domainId === ClockDomainId.BATTOR &&
+ toMarker.duration > BATTOR_FAST_SYNC_THRESHOLD_MS) {
+ toTs = toMarker.startTs;
+ } else if (toMarker.domainId === ClockDomainId.BATTOR &&
+ fromMarker.duration > BATTOR_FAST_SYNC_THRESHOLD_MS) {
+ fromTs = fromMarker.startTs;
+ }
+
+ const tsShift = toTs - fromTs;
+ return new Transformer(
+ (ts) => ts + tsShift, fromMarker.duration + toMarker.duration);
+ };
+
+ return {
+ ClockDomainId,
+ ClockSyncManager,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/clock_sync_manager_test.html b/chromium/third_party/catapult/tracing/tracing/model/clock_sync_manager_test.html
new file mode 100644
index 00000000000..a35049396de
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/clock_sync_manager_test.html
@@ -0,0 +1,471 @@
+<!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/clock_sync_manager.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const ClockDomainId = tr.model.ClockDomainId;
+ const ClockSyncManager = tr.model.ClockSyncManager;
+
+ const testOptions = {
+ setUp() {
+ // Add a few testing clock domains to the list of permissible domains.
+ ClockDomainId.DOMAIN_1 = 'DOMAIN1';
+ ClockDomainId.DOMAIN_2 = 'DOMAIN2';
+ ClockDomainId.DOMAIN_3 = 'DOMAIN3';
+ ClockDomainId.DOMAIN_4 = 'DOMAIN4';
+ ClockDomainId.DOMAIN_5 = 'DOMAIN5';
+ },
+
+ tearDown() {
+ delete ClockDomainId.DOMAIN_1;
+ delete ClockDomainId.DOMAIN_2;
+ delete ClockDomainId.DOMAIN_3;
+ delete ClockDomainId.DOMAIN_4;
+ delete ClockDomainId.DOMAIN_5;
+ }
+ };
+
+ test('addClockSyncMarker_throwsWithUnknownDomain', function() {
+ const mgr = new ClockSyncManager();
+
+ assert.throws(function() {
+ mgr.addClockSyncMarker('unknown', 'sync1', 100, 200);
+ }, '"unknown" is not in the list of known clock domain IDs.');
+ }, testOptions);
+
+
+ test('addClockSyncMarker_throwsWhenSelfSyncing', function() {
+ const mgr = new ClockSyncManager();
+ mgr.addClockSyncMarker(ClockDomainId.DOMAIN_1, 'sync1', 100, 200);
+
+ assert.throws(function() {
+ mgr.addClockSyncMarker(ClockDomainId.DOMAIN_1, 'sync1', 200, 300);
+ }, 'A clock domain cannot sync with itself.');
+ }, testOptions);
+
+ test('addClockSyncMarker_throwsWhenAddingThirdSyncMarkerToSync', function() {
+ const mgr = new ClockSyncManager();
+ mgr.addClockSyncMarker(ClockDomainId.DOMAIN_1, 'sync1', 100);
+ mgr.addClockSyncMarker(ClockDomainId.DOMAIN_2, 'sync1', 100);
+
+ assert.throws(function() {
+ mgr.addClockSyncMarker(ClockDomainId.DOMAIN_3, 'sync1', 100);
+ }, 'Clock sync with ID "sync1" is already complete - cannot add a third ' +
+ 'clock sync marker to it.');
+ }, testOptions);
+
+ test('addClockSyncMarker_throwsAfterGetModelTimeTransformer', function() {
+ const mgr = new ClockSyncManager();
+ mgr.addClockSyncMarker(ClockDomainId.DOMAIN_1, 'sync1', 100);
+ mgr.addClockSyncMarker(ClockDomainId.DOMAIN_2, 'sync1', 100);
+
+ mgr.getModelTimeTransformer(ClockDomainId.DOMAIN_1);
+
+ assert.throws(function() {
+ mgr.addClockSyncMarker(ClockDomainId.DOMAIN_2, 'sync2', 100);
+ }, 'Cannot add new clock sync markers after getting a model time ' +
+ 'transformer.');
+ }, testOptions);
+
+ test('getModelTimeTransformer_noMarkers', function() {
+ const mgr = new ClockSyncManager();
+
+ assert.strictEqual(
+ mgr.getModelTimeTransformer(ClockDomainId.DOMAIN_1)(100), 100);
+ }, testOptions);
+
+ test('getModelTimeTransformer_noMarkersSecondDomainThrows', function() {
+ const mgr = new ClockSyncManager();
+
+ mgr.getModelTimeTransformer(ClockDomainId.DOMAIN_1);
+
+ assert.throws(function() {
+ mgr.getModelTimeTransformer(ClockDomainId.DOMAIN_2);
+ }, 'No clock sync markers exist pairing clock domain "DOMAIN2" with' +
+ ' target clock domain "DOMAIN1".');
+ }, testOptions);
+
+ test('getModelTimeTransformer_noMarkersChromeLegacyFirst', function() {
+ const mgr = new ClockSyncManager();
+
+ assert.strictEqual(
+ mgr.getModelTimeTransformer(ClockDomainId.UNKNOWN_CHROME_LEGACY)(100),
+ 100);
+ assert.strictEqual(
+ mgr.getModelTimeTransformer(ClockDomainId.LINUX_CLOCK_MONOTONIC)(100),
+ 100);
+ }, testOptions);
+
+ test('getModelTimeTransformer_noMarkersChromeLegacySecond', function() {
+ const mgr = new ClockSyncManager();
+
+ assert.strictEqual(
+ mgr.getModelTimeTransformer(ClockDomainId.LINUX_CLOCK_MONOTONIC)(100),
+ 100);
+ assert.strictEqual(
+ mgr.getModelTimeTransformer(ClockDomainId.UNKNOWN_CHROME_LEGACY)(100),
+ 100);
+ }, testOptions);
+
+ test('getModelTimeTransformer_oneMarker', function() {
+ const mgr = new ClockSyncManager();
+ mgr.addClockSyncMarker(ClockDomainId.DOMAIN_1, 'sync1', 100);
+
+ assert.strictEqual(
+ mgr.getModelTimeTransformer(ClockDomainId.DOMAIN_1)(100), 100);
+ }, testOptions);
+
+ test('getModelTimeTransformer_oneCompleteSync', function() {
+ const mgr = new ClockSyncManager();
+ mgr.addClockSyncMarker(ClockDomainId.DOMAIN_1, 'sync1', 100);
+ mgr.addClockSyncMarker(ClockDomainId.DOMAIN_2, 'sync1', 350);
+
+ assert.strictEqual(
+ mgr.getModelTimeTransformer(ClockDomainId.DOMAIN_2)(350), 100);
+ assert.strictEqual(
+ mgr.getModelTimeTransformer(ClockDomainId.DOMAIN_1)(100), 100);
+ }, testOptions);
+
+ test('getModelTimeTransformer_oneCompleteSyncWithChromeLegacyBefore',
+ function() {
+ const mgr = new ClockSyncManager();
+ mgr.addClockSyncMarker(ClockDomainId.DOMAIN_1, 'sync1', 100);
+ mgr.addClockSyncMarker(
+ ClockDomainId.UNKNOWN_CHROME_LEGACY, 'sync1', 350);
+
+ assert.strictEqual(
+ mgr.getModelTimeTransformer(
+ ClockDomainId.UNKNOWN_CHROME_LEGACY)(350),
+ 350);
+ assert.strictEqual(
+ mgr.getModelTimeTransformer(ClockDomainId.DOMAIN_1)(100), 350);
+ assert.strictEqual(
+ mgr.getModelTimeTransformer(
+ ClockDomainId.LINUX_CLOCK_MONOTONIC)(350),
+ 350);
+ }, testOptions);
+
+ test('getModelTimeTransformer_oneCompleteSyncWithChromeLegacyAfter',
+ function() {
+ const mgr = new ClockSyncManager();
+ mgr.addClockSyncMarker(ClockDomainId.DOMAIN_1, 'sync1', 100);
+ mgr.addClockSyncMarker(
+ ClockDomainId.LINUX_CLOCK_MONOTONIC, 'sync1', 350);
+
+ assert.strictEqual(
+ mgr.getModelTimeTransformer(
+ ClockDomainId.UNKNOWN_CHROME_LEGACY)(350),
+ 350);
+ assert.strictEqual(
+ mgr.getModelTimeTransformer(ClockDomainId.DOMAIN_1)(100), 350);
+ assert.strictEqual(
+ mgr.getModelTimeTransformer(
+ ClockDomainId.LINUX_CLOCK_MONOTONIC)(350),
+ 350);
+ }, testOptions);
+
+ test('getModelTimeTransformer_twoCompleteSyncs', function() {
+ const mgr = new ClockSyncManager();
+ mgr.addClockSyncMarker(ClockDomainId.DOMAIN_1, 'sync1', 100);
+ mgr.addClockSyncMarker(ClockDomainId.DOMAIN_2, 'sync1', 350);
+
+ const tx = mgr.getTransformerBetween_('DOMAIN1', 'DOMAIN3');
+ mgr.addClockSyncMarker(ClockDomainId.DOMAIN_1, 'sync2', 200);
+ mgr.addClockSyncMarker(ClockDomainId.DOMAIN_3, 'sync2', 250);
+
+ assert.strictEqual(
+ mgr.getModelTimeTransformer(ClockDomainId.DOMAIN_2)(350), 100);
+ assert.strictEqual(
+ mgr.getModelTimeTransformer(ClockDomainId.DOMAIN_1)(100), 100);
+ assert.strictEqual(
+ mgr.getModelTimeTransformer(ClockDomainId.DOMAIN_3)(250), 200);
+ }, testOptions);
+
+ test('getModelTimeTransformer_twoSyncMarkersWithEndTs', function() {
+ const mgr = new ClockSyncManager();
+ mgr.addClockSyncMarker(ClockDomainId.DOMAIN_1, 'sync1', 100, 200);
+ mgr.addClockSyncMarker(ClockDomainId.DOMAIN_2, 'sync1', 350);
+
+ assert.strictEqual(
+ mgr.getModelTimeTransformer(ClockDomainId.DOMAIN_2)(350), 150);
+ assert.strictEqual(
+ mgr.getModelTimeTransformer(ClockDomainId.DOMAIN_1)(150), 150);
+ }, testOptions);
+
+ test('getModelTimeTransformer_indirectlyConnectedGraph',
+ function() {
+ const mgr = new ClockSyncManager();
+ mgr.addClockSyncMarker(ClockDomainId.DOMAIN_1, 'sync1', 100);
+ mgr.addClockSyncMarker(ClockDomainId.DOMAIN_2, 'sync1', 200);
+
+ mgr.addClockSyncMarker(ClockDomainId.DOMAIN_2, 'sync2', 200);
+ mgr.addClockSyncMarker(ClockDomainId.DOMAIN_3, 'sync2', 300);
+
+ assert.strictEqual(
+ mgr.getModelTimeTransformer(ClockDomainId.DOMAIN_1)(100), 100);
+ assert.strictEqual(
+ mgr.getModelTimeTransformer(ClockDomainId.DOMAIN_3)(300), 100);
+ }, testOptions);
+
+ test('getModelTimeTransformer_usesPathWithLeastError', function() {
+ const mgr = new ClockSyncManager();
+ mgr.addClockSyncMarker(ClockDomainId.DOMAIN_1, 'sync2', 100, 150);
+ mgr.addClockSyncMarker(ClockDomainId.DOMAIN_2, 'sync2', 100);
+
+ mgr.addClockSyncMarker(ClockDomainId.DOMAIN_1, 'sync1', 100, 200);
+ mgr.addClockSyncMarker(ClockDomainId.DOMAIN_2, 'sync1', 100);
+
+ assert.strictEqual(
+ mgr.getModelTimeTransformer(ClockDomainId.DOMAIN_1)(125), 125);
+ assert.strictEqual(
+ mgr.getModelTimeTransformer(ClockDomainId.DOMAIN_2)(100), 125);
+ }, testOptions);
+
+ // NOTE: This is the same test as the above, but reversed to ensure that
+ // the result didn't stem from some ordering coincidence.
+ test('getModelTimeTransformer_usesPathWithLeastErrorReverse', function() {
+ const mgr = new ClockSyncManager();
+ mgr.addClockSyncMarker(ClockDomainId.DOMAIN_2, 'sync1', 100);
+ mgr.addClockSyncMarker(ClockDomainId.DOMAIN_1, 'sync1', 100, 150);
+
+ mgr.addClockSyncMarker(ClockDomainId.DOMAIN_2, 'sync2', 100);
+ mgr.addClockSyncMarker(ClockDomainId.DOMAIN_1, 'sync2', 100, 200);
+
+ assert.strictEqual(
+ mgr.getModelTimeTransformer(ClockDomainId.DOMAIN_1)(125), 125);
+ assert.strictEqual(
+ mgr.getModelTimeTransformer(ClockDomainId.DOMAIN_2)(100), 125);
+ }, testOptions);
+
+ test('getModelTimeTransformer_battorSyncUsesNormalTimestampWhenFast',
+ function() {
+ const mgr = new ClockSyncManager();
+ mgr.addClockSyncMarker(ClockDomainId.DOMAIN_1, 'sync1', 100, 102);
+ mgr.addClockSyncMarker(ClockDomainId.BATTOR, 'sync1', 350);
+
+ assert.strictEqual(
+ mgr.getModelTimeTransformer(ClockDomainId.DOMAIN_1)(101),
+ 350);
+ }, testOptions);
+
+ test('getModelTimeTransformer_battorSyncUsesChromeLegacyStartTsWhenTooSlow',
+ function() {
+ const mgr = new ClockSyncManager();
+ mgr.addClockSyncMarker(ClockDomainId.DOMAIN_1, 'sync1', 100, 200);
+ mgr.addClockSyncMarker(ClockDomainId.BATTOR, 'sync1', 350);
+
+ assert.strictEqual(
+ mgr.getModelTimeTransformer(ClockDomainId.DOMAIN_1)(100),
+ 350);
+ }, testOptions);
+
+ test('getModelTimeTransformer_prefersChromeLegacyDomain', function() {
+ const mgr = new ClockSyncManager();
+ mgr.addClockSyncMarker(ClockDomainId.DOMAIN_1, 'sync1', 100);
+ mgr.addClockSyncMarker(ClockDomainId.UNKNOWN_CHROME_LEGACY, 'sync1', 350);
+
+ assert.strictEqual(
+ mgr.getModelTimeTransformer(ClockDomainId.DOMAIN_1)(100),
+ 350);
+ assert.strictEqual(
+ mgr.getModelTimeTransformer(ClockDomainId.UNKNOWN_CHROME_LEGACY)(350),
+ 350);
+ }, testOptions);
+
+ test('getModelTimeTransformer_collapsesUnknownChromeLegacyDomainLinux',
+ function() {
+ const mgr = new ClockSyncManager();
+ mgr.addClockSyncMarker(ClockDomainId.DOMAIN_1, 'sync1', 100);
+ mgr.addClockSyncMarker(
+ ClockDomainId.UNKNOWN_CHROME_LEGACY, 'sync1', 350);
+
+ mgr.addClockSyncMarker(
+ ClockDomainId.LINUX_CLOCK_MONOTONIC, 'sync2', 350);
+ mgr.addClockSyncMarker(ClockDomainId.DOMAIN_2, 'sync2', 450);
+
+ assert.strictEqual(
+ mgr.getModelTimeTransformer(ClockDomainId.DOMAIN_1)(100), 350);
+ assert.strictEqual(
+ mgr.getModelTimeTransformer(ClockDomainId.DOMAIN_2)(450), 350);
+ assert.strictEqual(
+ mgr.getModelTimeTransformer(
+ ClockDomainId.LINUX_CLOCK_MONOTONIC)(350),
+ 350);
+ assert.strictEqual(
+ mgr.getModelTimeTransformer(
+ ClockDomainId.UNKNOWN_CHROME_LEGACY)(350),
+ 350);
+ }, testOptions);
+
+ test('getModelTimeTransformer_collapsesUnknownChromeLegacyDomainMac',
+ function() {
+ const mgr = new ClockSyncManager();
+ mgr.addClockSyncMarker(ClockDomainId.DOMAIN_1, 'sync1', 100);
+ mgr.addClockSyncMarker(
+ ClockDomainId.UNKNOWN_CHROME_LEGACY, 'sync1', 350);
+
+ mgr.addClockSyncMarker(
+ ClockDomainId.MAC_MACH_ABSOLUTE_TIME, 'sync2', 350);
+ mgr.addClockSyncMarker(ClockDomainId.DOMAIN_2, 'sync2', 450);
+
+ assert.strictEqual(
+ mgr.getModelTimeTransformer(ClockDomainId.DOMAIN_1)(100), 350);
+ assert.strictEqual(
+ mgr.getModelTimeTransformer(ClockDomainId.DOMAIN_2)(450), 350);
+ assert.strictEqual(
+ mgr.getModelTimeTransformer(
+ ClockDomainId.MAC_MACH_ABSOLUTE_TIME)(350),
+ 350);
+ assert.strictEqual(
+ mgr.getModelTimeTransformer(
+ ClockDomainId.UNKNOWN_CHROME_LEGACY)(350),
+ 350);
+ }, testOptions);
+
+ test('getModelTimeTransformer_collapsesUnknownChromeLegacyDomainWinLoRes',
+ function() {
+ const mgr = new ClockSyncManager();
+ mgr.addClockSyncMarker(ClockDomainId.DOMAIN_1, 'sync1', 100);
+ mgr.addClockSyncMarker(
+ ClockDomainId.UNKNOWN_CHROME_LEGACY, 'sync1', 350);
+
+ mgr.addClockSyncMarker(
+ ClockDomainId.WIN_ROLLOVER_PROTECTED_TIME_GET_TIME, 'sync2', 350);
+ mgr.addClockSyncMarker(ClockDomainId.DOMAIN_2, 'sync2', 450);
+
+ assert.strictEqual(
+ mgr.getModelTimeTransformer(ClockDomainId.DOMAIN_1)(100), 350);
+ assert.strictEqual(
+ mgr.getModelTimeTransformer(ClockDomainId.DOMAIN_2)(450), 350);
+ assert.strictEqual(
+ mgr.getModelTimeTransformer(
+ ClockDomainId.WIN_ROLLOVER_PROTECTED_TIME_GET_TIME)(350),
+ 350);
+ assert.strictEqual(
+ mgr.getModelTimeTransformer(
+ ClockDomainId.UNKNOWN_CHROME_LEGACY)(350),
+ 350);
+ }, testOptions);
+
+ test('getModelTimeTransformer_collapsesChromeLegacyDomainWinHiRes',
+ function() {
+ const mgr = new ClockSyncManager();
+ mgr.addClockSyncMarker(ClockDomainId.DOMAIN_1, 'sync1', 100);
+ mgr.addClockSyncMarker(
+ ClockDomainId.UNKNOWN_CHROME_LEGACY, 'sync1', 350);
+
+ mgr.addClockSyncMarker(ClockDomainId.WIN_QPC, 'sync2', 350);
+ mgr.addClockSyncMarker(ClockDomainId.DOMAIN_2, 'sync2', 450);
+
+ assert.strictEqual(
+ mgr.getModelTimeTransformer(ClockDomainId.DOMAIN_1)(100), 350);
+ assert.strictEqual(
+ mgr.getModelTimeTransformer(ClockDomainId.DOMAIN_2)(450), 350);
+ assert.strictEqual(
+ mgr.getModelTimeTransformer(ClockDomainId.WIN_QPC)(350), 350);
+ assert.strictEqual(
+ mgr.getModelTimeTransformer(
+ ClockDomainId.UNKNOWN_CHROME_LEGACY)(350),
+ 350);
+ }, testOptions);
+
+ test('getModelTimeTransformer_throwsWithTwoDistinctGraphs', function() {
+ const mgr = new ClockSyncManager();
+ mgr.addClockSyncMarker(ClockDomainId.DOMAIN_1, 'sync1', 100);
+ mgr.addClockSyncMarker(ClockDomainId.DOMAIN_2, 'sync1', 100);
+
+ mgr.addClockSyncMarker(ClockDomainId.DOMAIN_1, 'sync2', 100);
+ mgr.addClockSyncMarker(ClockDomainId.DOMAIN_3, 'sync2', 100);
+
+ mgr.addClockSyncMarker(ClockDomainId.DOMAIN_4, 'sync3', 100);
+ mgr.addClockSyncMarker(ClockDomainId.DOMAIN_5, 'sync3', 100);
+
+ assert.throws(function() {
+ mgr.getModelTimeTransformer(ClockDomainId.DOMAIN_5);
+ }, 'Unable to select a master clock domain because no path can be found ' +
+ 'from "DOMAIN1" to "DOMAIN4"');
+ }, testOptions);
+
+ test('computeDotGraph_noMarkers', function() {
+ const mgr = new ClockSyncManager();
+
+ assert.strictEqual(mgr.computeDotGraph(), [
+ 'graph {',
+ '}'
+ ].join('\n'));
+ }, testOptions);
+
+ test('computeDotGraph_oneMarker', function() {
+ const mgr = new ClockSyncManager();
+ mgr.addClockSyncMarker(ClockDomainId.DOMAIN_1, 'sync1', 100);
+
+ assert.strictEqual(mgr.computeDotGraph(), [
+ 'graph {',
+ ' DOMAIN1[shape=box]',
+ ' "sync1" -- DOMAIN1 [label="[100, 100]"]',
+ '}'
+ ].join('\n'));
+ }, testOptions);
+
+ test('computeDotGraph_oneCompleteSync', function() {
+ const mgr = new ClockSyncManager();
+ mgr.addClockSyncMarker(ClockDomainId.DOMAIN_1, 'sync1', 100);
+ mgr.addClockSyncMarker(ClockDomainId.DOMAIN_2, 'sync1', 350);
+
+ assert.strictEqual(mgr.computeDotGraph(), [
+ 'graph {',
+ ' DOMAIN1[shape=box]',
+ ' DOMAIN2[shape=box]',
+ ' "sync1" -- DOMAIN1 [label="[100, 100]"]',
+ ' "sync1" -- DOMAIN2 [label="[350, 350]"]',
+ '}'
+ ].join('\n'));
+ }, testOptions);
+
+ test('computeDotGraph_twoCompleteSyncs', function() {
+ const mgr = new ClockSyncManager();
+ mgr.addClockSyncMarker(ClockDomainId.DOMAIN_2, 'sync2', 0, 50);
+ mgr.addClockSyncMarker(ClockDomainId.DOMAIN_3, 'sync2', 400);
+ mgr.addClockSyncMarker(ClockDomainId.DOMAIN_1, 'sync1', 100);
+ mgr.addClockSyncMarker(ClockDomainId.DOMAIN_2, 'sync1', 350);
+
+ assert.strictEqual(mgr.computeDotGraph(), [
+ 'graph {',
+ ' DOMAIN1[shape=box]',
+ ' DOMAIN2[shape=box]',
+ ' DOMAIN3[shape=box]',
+ ' "sync1" -- DOMAIN1 [label="[100, 100]"]',
+ ' "sync1" -- DOMAIN2 [label="[350, 350]"]',
+ ' "sync2" -- DOMAIN2 [label="[0, 50]"]',
+ ' "sync2" -- DOMAIN3 [label="[400, 400]"]',
+ '}'
+ ].join('\n'));
+ }, testOptions);
+
+ test('computeDotGraph_oneCompleteSyncOneIncompleteSync', function() {
+ const mgr = new ClockSyncManager();
+ mgr.addClockSyncMarker(ClockDomainId.DOMAIN_1, 'sync1', 100);
+ mgr.addClockSyncMarker(ClockDomainId.DOMAIN_2, 'sync1', 350);
+ mgr.addClockSyncMarker(ClockDomainId.DOMAIN_3, 'sync2', 450);
+
+ assert.strictEqual(mgr.computeDotGraph(), [
+ 'graph {',
+ ' DOMAIN1[shape=box]',
+ ' DOMAIN2[shape=box]',
+ ' DOMAIN3[shape=box]',
+ ' "sync1" -- DOMAIN1 [label="[100, 100]"]',
+ ' "sync1" -- DOMAIN2 [label="[350, 350]"]',
+ ' "sync2" -- DOMAIN3 [label="[450, 450]"]',
+ '}'
+ ].join('\n'));
+ }, testOptions);
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/comment_box_annotation.html b/chromium/third_party/catapult/tracing/tracing/model/comment_box_annotation.html
new file mode 100644
index 00000000000..16d2cb098bc
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/comment_box_annotation.html
@@ -0,0 +1,60 @@
+<!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/annotation.html">
+<link rel="import" href="/tracing/model/location.html">
+<link rel="import" href="/tracing/model/rect_annotation.html">
+<link rel="import" href="/tracing/ui/annotations/comment_box_annotation_view.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.model', function() {
+ function CommentBoxAnnotation(location, text) {
+ tr.model.Annotation.apply(this, arguments);
+
+ this.location = location;
+ this.text = text;
+ }
+
+ CommentBoxAnnotation.fromDict = function(dict) {
+ const args = dict.args;
+ const location =
+ new tr.model.Location(args.location.xWorld, args.location.yComponents);
+ return new tr.model.CommentBoxAnnotation(location, args.text);
+ };
+
+ CommentBoxAnnotation.prototype = {
+ __proto__: tr.model.Annotation.prototype,
+
+ onRemove() {
+ this.view_.removeTextArea();
+ },
+
+ toDict() {
+ return {
+ typeName: 'comment_box',
+ args: {
+ text: this.text,
+ location: this.location.toDict()
+ }
+ };
+ },
+
+ createView_(viewport) {
+ return new tr.ui.annotations.CommentBoxAnnotationView(viewport, this);
+ }
+ };
+
+ tr.model.Annotation.register(
+ CommentBoxAnnotation, {typeName: 'comment_box'});
+
+ return {
+ CommentBoxAnnotation,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/compound_event_selection_state.html b/chromium/third_party/catapult/tracing/tracing/model/compound_event_selection_state.html
new file mode 100644
index 00000000000..cefc2247095
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/compound_event_selection_state.html
@@ -0,0 +1,39 @@
+<!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/base.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.model', function() {
+ /**
+ * Indicates how much of a compound-event is selected [if any].
+ *
+ * The CompoundEventSelectionState enum is used with events that are
+ * directly selectable, but also have associated events, too, that can be
+ * selected. In this situation, there are a variety of different
+ * selected states other than just "yes, no". This enum encodes those
+ * various possible states.
+ */
+ const CompoundEventSelectionState = {
+ // Basic bit states.
+ NOT_SELECTED: 0,
+ EVENT_SELECTED: 0x1,
+ SOME_ASSOCIATED_EVENTS_SELECTED: 0x2,
+ ALL_ASSOCIATED_EVENTS_SELECTED: 0x4,
+
+ // Common combinations.
+ EVENT_AND_SOME_ASSOCIATED_SELECTED: 0x1 | 0x2,
+ EVENT_AND_ALL_ASSOCIATED_SELECTED: 0x1 | 0x4
+ };
+
+ return {
+ CompoundEventSelectionState,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/constants.html b/chromium/third_party/catapult/tracing/tracing/model/constants.html
new file mode 100644
index 00000000000..0bf134ec7fe
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/constants.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2012 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.model', function() {
+ return {
+ // Since the PID of the browser process is not known to the child processes,
+ // we let them use "pid_ref = -1" to reference an object created in the
+ // browser process.
+ BROWSER_PROCESS_PID_REF: -1,
+
+ // The default scope of object events, when not explicitly specified.
+ OBJECT_DEFAULT_SCOPE: 'ptr',
+
+ // Event phases that have process-local IDs, unless a global ID is
+ // explicitly specified.
+ LOCAL_ID_PHASES: new Set(['N', 'D', 'O', '(', ')'])
+ };
+});
+</script>
+
diff --git a/chromium/third_party/catapult/tracing/tracing/model/container_memory_dump.html b/chromium/third_party/catapult/tracing/tracing/model/container_memory_dump.html
new file mode 100644
index 00000000000..1078b764862
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/container_memory_dump.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/timed_event.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Provides the ContainerMemoryDump class.
+ */
+tr.exportTo('tr.model', function() {
+ /**
+ * The ContainerMemoryDump represents an abstract container memory dump.
+ * @constructor
+ */
+ function ContainerMemoryDump(start) {
+ tr.model.TimedEvent.call(this, start);
+
+ this.levelOfDetail = undefined;
+
+ this.memoryAllocatorDumps_ = undefined;
+ this.memoryAllocatorDumpsByFullName_ = undefined;
+ }
+
+ /**
+ * Memory dump level of detail. See base::trace_event::MemoryDumpLevelOfDetail
+ * in the Chromium repository.
+ *
+ * @enum
+ */
+ ContainerMemoryDump.LevelOfDetail = {
+ BACKGROUND: 0,
+ LIGHT: 1,
+ DETAILED: 2
+ };
+
+ ContainerMemoryDump.prototype = {
+ __proto__: tr.model.TimedEvent.prototype,
+
+ shiftTimestampsForward(amount) {
+ this.start += amount;
+ },
+
+ get memoryAllocatorDumps() {
+ return this.memoryAllocatorDumps_;
+ },
+
+ set memoryAllocatorDumps(memoryAllocatorDumps) {
+ this.memoryAllocatorDumps_ = memoryAllocatorDumps;
+ this.forceRebuildingMemoryAllocatorDumpByFullNameIndex();
+ },
+
+ getMemoryAllocatorDumpByFullName(fullName) {
+ if (this.memoryAllocatorDumps_ === undefined) return undefined;
+
+ // Lazily generate the index if necessary.
+ if (this.memoryAllocatorDumpsByFullName_ === undefined) {
+ const index = {};
+ function addDumpsToIndex(dumps) {
+ dumps.forEach(function(dump) {
+ index[dump.fullName] = dump;
+ addDumpsToIndex(dump.children);
+ });
+ }
+ addDumpsToIndex(this.memoryAllocatorDumps_);
+ this.memoryAllocatorDumpsByFullName_ = index;
+ }
+
+ return this.memoryAllocatorDumpsByFullName_[fullName];
+ },
+
+ forceRebuildingMemoryAllocatorDumpByFullNameIndex() {
+ // Clear the index and generate it lazily.
+ this.memoryAllocatorDumpsByFullName_ = undefined;
+ },
+
+ iterateRootAllocatorDumps(fn, opt_this) {
+ if (this.memoryAllocatorDumps === undefined) return;
+ this.memoryAllocatorDumps.forEach(fn, opt_this || this);
+ }
+ };
+
+ return {
+ ContainerMemoryDump,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/container_memory_dump_test.html b/chromium/third_party/catapult/tracing/tracing/model/container_memory_dump_test.html
new file mode 100644
index 00000000000..a6b2009c96e
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/container_memory_dump_test.html
@@ -0,0 +1,181 @@
+<!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/model/container_memory_dump.html">
+<link rel="import" href="/tracing/model/memory_allocator_dump.html">
+<link rel="import" href="/tracing/model/memory_dump_test_utils.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const ContainerMemoryDump = tr.model.ContainerMemoryDump;
+ const MemoryAllocatorDump = tr.model.MemoryAllocatorDump;
+ const Scalar = tr.b.Scalar;
+ const unitlessNumber_smallerIsBetter =
+ tr.b.Unit.byName.unitlessNumber_smallerIsBetter;
+ const newAllocatorDump = tr.model.MemoryDumpTestUtils.newAllocatorDump;
+ const addChildDump = tr.model.MemoryDumpTestUtils.addChildDump;
+
+ test('memoryAllocatorDumps_undefined', function() {
+ const md = new ContainerMemoryDump(42);
+
+ assert.isUndefined(md.memoryAllocatorDumps);
+ assert.isUndefined(md.getMemoryAllocatorDumpByFullName('malloc'));
+ });
+
+ test('memoryAllocatorDumps_zero', function() {
+ const md = new ContainerMemoryDump(42);
+ md.memoryAllocatorDumps = [];
+
+ assert.lengthOf(md.memoryAllocatorDumps, 0);
+ assert.isUndefined(md.getMemoryAllocatorDumpByFullName('malloc'));
+ });
+
+ test('memoryAllocatorDumps_flat', function() {
+ const md = new ContainerMemoryDump(42);
+
+ const oilpanDump = newAllocatorDump(md, 'oilpan', {
+ size: 1024,
+ objects_count: new Scalar(unitlessNumber_smallerIsBetter, 7),
+ inner_size: 768
+ });
+ const v8Dump = newAllocatorDump(md, 'v8', {
+ size: 2048,
+ objects_count: new Scalar(unitlessNumber_smallerIsBetter, 15),
+ inner_size: 1999
+ });
+ md.memoryAllocatorDumps = [oilpanDump, v8Dump];
+
+ assert.lengthOf(md.memoryAllocatorDumps, 2);
+ assert.strictEqual(md.memoryAllocatorDumps[0], oilpanDump);
+ assert.strictEqual(md.memoryAllocatorDumps[1], v8Dump);
+
+ assert.strictEqual(
+ md.getMemoryAllocatorDumpByFullName('oilpan'), oilpanDump);
+ assert.strictEqual(md.getMemoryAllocatorDumpByFullName('v8'), v8Dump);
+ assert.isUndefined(md.getMemoryAllocatorDumpByFullName('malloc'));
+ });
+
+ test('memoryAllocatorDumps_nested', function() {
+ const md = new ContainerMemoryDump(42);
+
+ const oilpanDump = newAllocatorDump(md, 'oilpan', {
+ size: 1024,
+ objects_count: new Scalar(unitlessNumber_smallerIsBetter, 7),
+ inner_size: 768
+ });
+
+ const oilpanBucket1Dump = addChildDump(oilpanDump, 'bucket1', {
+ size: 512,
+ objects_count: new Scalar(unitlessNumber_smallerIsBetter, 3),
+ inner_size: 256
+ });
+
+ const oilpanBucket2Dump = addChildDump(oilpanDump, 'bucket2', {
+ size: 512,
+ objects_count: new Scalar(unitlessNumber_smallerIsBetter, 4),
+ inner_size: 512
+ });
+
+ const oilpanBucket2StringsDump = addChildDump(
+ oilpanBucket2Dump, 'strings', {
+ size: 512,
+ objects_count: new Scalar(unitlessNumber_smallerIsBetter, 4),
+ inner_size: 512
+ });
+
+ const v8Dump = newAllocatorDump(md, 'v8', {
+ size: 2048,
+ objects_count: new Scalar(unitlessNumber_smallerIsBetter, 15),
+ inner_size: 1999
+ });
+
+ md.memoryAllocatorDumps = [oilpanDump, v8Dump];
+
+ assert.lengthOf(md.memoryAllocatorDumps, 2);
+ assert.strictEqual(md.memoryAllocatorDumps[0], oilpanDump);
+ assert.strictEqual(md.memoryAllocatorDumps[1], v8Dump);
+
+ assert.strictEqual(
+ md.getMemoryAllocatorDumpByFullName('oilpan'), oilpanDump);
+ assert.strictEqual(md.getMemoryAllocatorDumpByFullName('oilpan/bucket1'),
+ oilpanBucket1Dump);
+ assert.strictEqual(md.getMemoryAllocatorDumpByFullName('oilpan/bucket2'),
+ oilpanBucket2Dump);
+ assert.strictEqual(
+ md.getMemoryAllocatorDumpByFullName('oilpan/bucket2/strings'),
+ oilpanBucket2StringsDump);
+ assert.strictEqual(md.getMemoryAllocatorDumpByFullName('v8'), v8Dump);
+ assert.isUndefined(md.getMemoryAllocatorDumpByFullName('malloc'));
+ });
+
+ test('iterateRootAllocatorDumps', function() {
+ const containerDump = new ContainerMemoryDump(42);
+
+ const oilpanDump = new MemoryAllocatorDump(containerDump, 'oilpan');
+ const v8Dump = new MemoryAllocatorDump(containerDump, 'v8');
+ addChildDump(v8Dump, 'heaps');
+
+ containerDump.memoryAllocatorDumps = [oilpanDump, v8Dump];
+
+ const visitedAllocatorDumps = [];
+ containerDump.iterateRootAllocatorDumps(
+ function(dump) { this.visitedAllocatorDumps.push(dump); },
+ { visitedAllocatorDumps });
+ assert.sameMembers(visitedAllocatorDumps, [oilpanDump, v8Dump]);
+ });
+
+ test('forceRebuildingMemoryAllocatorDumpByFullNameIndex', function() {
+ const containerDump = new ContainerMemoryDump(42);
+
+ const v8Dump = new MemoryAllocatorDump(containerDump, 'v8');
+ const v8HeapsDump = addChildDump(v8Dump, 'heaps');
+ const v8HeapSmallDump = addChildDump(v8HeapsDump, 'S');
+
+ // Setting the memory allocator dumps should update the index properly.
+ containerDump.memoryAllocatorDumps = [v8Dump];
+ assert.strictEqual(
+ containerDump.getMemoryAllocatorDumpByFullName('v8'), v8Dump);
+ assert.strictEqual(containerDump.getMemoryAllocatorDumpByFullName(
+ 'v8/heaps'), v8HeapsDump);
+ assert.strictEqual(containerDump.getMemoryAllocatorDumpByFullName(
+ 'v8/heaps/S'), v8HeapSmallDump);
+
+ // Add a second grandchild (v8/heaps/L).
+ const v8HeapLargeDump = addChildDump(v8HeapsDump, 'L');
+
+ // Setting the memory allocator dumps again should update the index
+ // properly again.
+ containerDump.memoryAllocatorDumps = [v8Dump];
+ assert.strictEqual(
+ containerDump.getMemoryAllocatorDumpByFullName('v8'), v8Dump);
+ assert.strictEqual(containerDump.getMemoryAllocatorDumpByFullName(
+ 'v8/heaps'), v8HeapsDump);
+ assert.strictEqual(containerDump.getMemoryAllocatorDumpByFullName(
+ 'v8/heaps/S'), v8HeapSmallDump);
+ assert.strictEqual(containerDump.getMemoryAllocatorDumpByFullName(
+ 'v8/heaps/L'), v8HeapLargeDump);
+
+ // Remove the first grandchild (v8/heaps/S).
+ v8HeapsDump.children.splice(0, 1);
+
+ // Force rebuilding the index and check that it was updated properly.
+ containerDump.forceRebuildingMemoryAllocatorDumpByFullNameIndex();
+ assert.strictEqual(
+ containerDump.getMemoryAllocatorDumpByFullName('v8'), v8Dump);
+ assert.strictEqual(containerDump.getMemoryAllocatorDumpByFullName(
+ 'v8/heaps'), v8HeapsDump);
+ assert.isUndefined(containerDump.getMemoryAllocatorDumpByFullName(
+ 'v8/heaps/S'));
+ assert.strictEqual(containerDump.getMemoryAllocatorDumpByFullName(
+ 'v8/heaps/L'), v8HeapLargeDump);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/counter.html b/chromium/third_party/catapult/tracing/tracing/model/counter.html
new file mode 100644
index 00000000000..5b276984fd2
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/counter.html
@@ -0,0 +1,190 @@
+<!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/guid.html">
+<link rel="import" href="/tracing/base/math/range.html">
+<link rel="import" href="/tracing/model/counter_series.html">
+<link rel="import" href="/tracing/model/event_container.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.model', function() {
+ /**
+ * A container holding all series of a given type of measurement.
+ *
+ * As an example, if we're measuring the throughput of data sent over several
+ * USB connections, the throughput of each cable might be added as a separate
+ * series to a single counter.
+ *
+ * @constructor
+ * @extends {EventContainer}
+ */
+ function Counter(parent, id, category, name) {
+ tr.model.EventContainer.call(this);
+
+ this.parent_ = parent;
+ this.id_ = id;
+ this.category_ = category || '';
+ this.name_ = name;
+
+ this.series_ = [];
+ this.totals = [];
+ }
+
+ Counter.prototype = {
+ __proto__: tr.model.EventContainer.prototype,
+
+ get parent() {
+ return this.parent_;
+ },
+
+ get id() {
+ return this.id_;
+ },
+
+ get category() {
+ return this.category_;
+ },
+
+ get name() {
+ return this.name_;
+ },
+
+ * childEvents() {
+ },
+
+ * childEventContainers() {
+ yield* this.series;
+ },
+
+ set timestamps(arg) {
+ throw new Error('Bad counter API. No cookie.');
+ },
+
+ set seriesNames(arg) {
+ throw new Error('Bad counter API. No cookie.');
+ },
+
+ set seriesColors(arg) {
+ throw new Error('Bad counter API. No cookie.');
+ },
+
+ set samples(arg) {
+ throw new Error('Bad counter API. No cookie.');
+ },
+
+ addSeries(series) {
+ series.counter = this;
+ series.seriesIndex = this.series_.length;
+ this.series_.push(series);
+ return series;
+ },
+
+ getSeries(idx) {
+ return this.series_[idx];
+ },
+
+ get series() {
+ return this.series_;
+ },
+
+ get numSeries() {
+ return this.series_.length;
+ },
+
+ get numSamples() {
+ if (this.series_.length === 0) return 0;
+ return this.series_[0].length;
+ },
+
+ get timestamps() {
+ if (this.series_.length === 0) return [];
+ return this.series_[0].timestamps;
+ },
+
+ /**
+ * Obtains min, max, avg, values, start, and end for different series for
+ * a given counter
+ * getSampleStatistics([0,1])
+ * The statistics objects that this returns are an array of objects, one
+ * object for each series for the counter in the form:
+ * {min: minVal, max: maxVal, avg: avgVal, start: startVal, end: endVal}
+ *
+ * @param {Array.<Number>} Indices to summarize.
+ * @return {Object} An array of statistics. Each element in the array
+ * has data for one of the series in the selected counter.
+ */
+ getSampleStatistics(sampleIndices) {
+ sampleIndices.sort();
+
+ const ret = [];
+ this.series_.forEach(function(series) {
+ ret.push(series.getStatistics(sampleIndices));
+ });
+ return ret;
+ },
+
+ /**
+ * Shifts all the timestamps inside this counter forward by the amount
+ * specified.
+ */
+ shiftTimestampsForward(amount) {
+ for (let i = 0; i < this.series_.length; ++i) {
+ this.series_[i].shiftTimestampsForward(amount);
+ }
+ },
+
+ /**
+ * Updates the bounds for this counter based on the samples it contains.
+ */
+ updateBounds() {
+ this.totals = [];
+ this.maxTotal = 0;
+ this.bounds.reset();
+
+ if (this.series_.length === 0) return;
+
+ const firstSeries = this.series_[0];
+ const lastSeries = this.series_[this.series_.length - 1];
+
+ this.bounds.addValue(firstSeries.getTimestamp(0));
+ this.bounds.addValue(lastSeries.getTimestamp(lastSeries.length - 1));
+
+ const numSeries = this.numSeries;
+ this.maxTotal = -Infinity;
+
+ // Sum the samples at each timestamp.
+ // Note, this assumes that all series have all timestamps.
+ for (let i = 0; i < firstSeries.length; ++i) {
+ let total = 0;
+ this.series_.forEach(function(series) {
+ total += series.getSample(i).value;
+ this.totals.push(total);
+ }.bind(this));
+
+ this.maxTotal = Math.max(total, this.maxTotal);
+ }
+ }
+ };
+
+ /**
+ * Comparison between counters that orders by parent.compareTo, then name.
+ */
+ Counter.compare = function(x, y) {
+ let tmp = x.parent.compareTo(y.parent);
+ if (tmp !== 0) return tmp;
+ tmp = x.name.localeCompare(y.name);
+ if (tmp === 0) return x.tid - y.tid;
+ return tmp;
+ };
+
+ return {
+ Counter,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/counter_sample.html b/chromium/third_party/catapult/tracing/tracing/model/counter_sample.html
new file mode 100644
index 00000000000..3da38c349c7
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/counter_sample.html
@@ -0,0 +1,95 @@
+<!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.html">
+<link rel="import" href="/tracing/model/event_registry.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.model', function() {
+ /**
+ * The value of a given measurement at a given time.
+ *
+ * As an example, if we're measuring the throughput of data sent over a USB
+ * connection, each counter sample might represent the instantaneous
+ * throughput of the connection at a given time.
+ *
+ * @constructor
+ * @extends {Event}
+ */
+ function CounterSample(series, timestamp, value) {
+ tr.model.Event.call(this);
+ this.series_ = series;
+ this.timestamp_ = timestamp;
+ this.value_ = value;
+ }
+
+ CounterSample.groupByTimestamp = function(samples) {
+ const samplesByTimestamp = tr.b.groupIntoMap(samples, s => s.timestamp);
+ const timestamps = Array.from(samplesByTimestamp.keys());
+ timestamps.sort();
+ const groups = [];
+ for (const ts of timestamps) {
+ const group = samplesByTimestamp.get(ts);
+ group.sort((x, y) => x.series.seriesIndex - y.series.seriesIndex);
+ groups.push(group);
+ }
+ return groups;
+ };
+
+ CounterSample.prototype = {
+ __proto__: tr.model.Event.prototype,
+
+ get series() {
+ return this.series_;
+ },
+
+ get timestamp() {
+ return this.timestamp_;
+ },
+
+ get value() {
+ return this.value_;
+ },
+
+ set timestamp(timestamp) {
+ this.timestamp_ = timestamp;
+ },
+
+ addBoundsToRange(range) {
+ range.addValue(this.timestamp);
+ },
+
+ getSampleIndex() {
+ return tr.b.findLowIndexInSortedArray(
+ this.series.timestamps,
+ function(x) { return x; },
+ this.timestamp_);
+ },
+
+ get userFriendlyName() {
+ return 'Counter sample from ' + this.series_.title + ' at ' +
+ tr.b.Unit.byName.timeStampInMs.format(this.timestamp);
+ }
+ };
+
+
+ tr.model.EventRegistry.register(
+ CounterSample,
+ {
+ name: 'counterSample',
+ pluralName: 'counterSamples'
+ });
+
+ return {
+ CounterSample,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/counter_sample_test.html b/chromium/third_party/catapult/tracing/tracing/model/counter_sample_test.html
new file mode 100644
index 00000000000..d4ad8fcb0d4
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/counter_sample_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/model/counter.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const Counter = tr.model.Counter;
+ const CounterSeries = tr.model.CounterSeries;
+ const CounterSample = tr.model.CounterSample;
+
+ test('groupByTimestamp', function() {
+ const counter = new Counter();
+ const slice0 = counter.addSeries(new CounterSeries('x', 0));
+ const slice1 = counter.addSeries(new CounterSeries('y', 1));
+
+ const slice0Sample0 = slice0.addCounterSample(0, 100);
+ const slice0Sample1 = slice1.addCounterSample(0, 200);
+ const slice1Sample0 = slice0.addCounterSample(1, 100);
+ const slice1Sample1 = slice1.addCounterSample(1, 200);
+
+ const groups = CounterSample.groupByTimestamp([slice0Sample1, slice0Sample0,
+ slice1Sample1, slice1Sample0]);
+ assert.strictEqual(groups.length, 2);
+ assert.deepEqual(groups[0], [slice0Sample0, slice0Sample1]);
+ assert.deepEqual(groups[1], [slice1Sample0, slice1Sample1]);
+ });
+
+ test('getSampleIndex', function() {
+ const ctr = new Counter(null, 0, '', 'myCounter');
+ const slice0 = new CounterSeries('a', 0);
+ ctr.addSeries(slice0);
+
+ const slice0Sample0 = slice0.addCounterSample(0, 0);
+ const slice0Sample1 = slice0.addCounterSample(1, 100);
+ assert.strictEqual(slice0Sample0.getSampleIndex(), 0);
+ assert.strictEqual(slice0Sample1.getSampleIndex(), 1);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/counter_series.html b/chromium/third_party/catapult/tracing/tracing/model/counter_series.html
new file mode 100644
index 00000000000..b2ab8f4fe58
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/counter_series.html
@@ -0,0 +1,124 @@
+<!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_sample.html">
+<link rel="import" href="/tracing/model/event_container.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.model', function() {
+ const CounterSample = tr.model.CounterSample;
+
+ /**
+ * A container holding all samples of a given measurement over time.
+ *
+ * As an example, a counter series might measure the throughput of data sent
+ * over a USB connection, with each sample representing the instantaneous
+ * throughput of the connection.
+ *
+ * @constructor
+ * @extends {EventContainer}
+ */
+ function CounterSeries(name, color) {
+ tr.model.EventContainer.call(this);
+
+ this.name_ = name;
+ this.color_ = color;
+
+ this.timestamps_ = [];
+ this.samples_ = [];
+
+ // Set by counter.addSeries
+ this.counter = undefined;
+ this.seriesIndex = undefined;
+ }
+
+ CounterSeries.prototype = {
+ __proto__: tr.model.EventContainer.prototype,
+
+ get length() {
+ return this.timestamps_.length;
+ },
+
+ get name() {
+ return this.name_;
+ },
+
+ get color() {
+ return this.color_;
+ },
+
+ get samples() {
+ return this.samples_;
+ },
+
+ get timestamps() {
+ return this.timestamps_;
+ },
+
+ getSample(idx) {
+ return this.samples_[idx];
+ },
+
+ getTimestamp(idx) {
+ return this.timestamps_[idx];
+ },
+
+ addCounterSample(ts, val) {
+ const sample = new CounterSample(this, ts, val);
+ this.addSample(sample);
+ return sample;
+ },
+
+ addSample(sample) {
+ this.timestamps_.push(sample.timestamp);
+ this.samples_.push(sample);
+ },
+
+ getStatistics(sampleIndices) {
+ let sum = 0;
+ let min = Number.MAX_VALUE;
+ let max = -Number.MAX_VALUE;
+
+ for (let i = 0; i < sampleIndices.length; ++i) {
+ const sample = this.getSample(sampleIndices[i]).value;
+
+ sum += sample;
+ min = Math.min(sample, min);
+ max = Math.max(sample, max);
+ }
+
+ return {
+ min,
+ max,
+ avg: (sum / sampleIndices.length),
+ start: this.getSample(sampleIndices[0]).value,
+ end: this.getSample(sampleIndices.length - 1).value
+ };
+ },
+
+ shiftTimestampsForward(amount) {
+ for (let i = 0; i < this.timestamps_.length; ++i) {
+ this.timestamps_[i] += amount;
+ this.samples_[i].timestamp = this.timestamps_[i];
+ }
+ },
+
+ * childEvents() {
+ yield* this.samples_;
+ },
+
+ * childEventContainers() {
+ }
+ };
+
+ return {
+ CounterSeries,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/counter_test.html b/chromium/third_party/catapult/tracing/tracing/model/counter_test.html
new file mode 100644
index 00000000000..5c64e7c273d
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/counter_test.html
@@ -0,0 +1,124 @@
+<!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_series.html">
+<link rel="import" href="/tracing/model/model.html">
+<link rel="import" href="/tracing/model/process.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const Counter = tr.model.Counter;
+ const CounterSeries = tr.model.CounterSeries;
+ const CounterSample = tr.model.CounterSample;
+
+ const createCounterWithTwoSeries = function() {
+ const ctr = new Counter(null, 0, '', 'myCounter');
+ const aSeries = new CounterSeries('a', 0);
+ const bSeries = new CounterSeries('b', 0);
+ ctr.addSeries(aSeries);
+ ctr.addSeries(bSeries);
+
+ aSeries.addCounterSample(0, 5);
+ aSeries.addCounterSample(1, 6);
+ aSeries.addCounterSample(2, 5);
+ aSeries.addCounterSample(3, 7);
+
+ bSeries.addCounterSample(0, 10);
+ bSeries.addCounterSample(1, 15);
+ bSeries.addCounterSample(2, 12);
+ bSeries.addCounterSample(3, 16);
+
+ return ctr;
+ };
+
+ test('getSampleStatisticsWithSingleSelection', function() {
+ const ctr = createCounterWithTwoSeries();
+ const ret = ctr.getSampleStatistics([0]);
+
+ assert.strictEqual(ret[0].min, 5);
+ assert.strictEqual(ret[0].max, 5);
+ assert.strictEqual(ret[0].avg, 5);
+ assert.strictEqual(ret[0].start, 5);
+ assert.strictEqual(ret[0].end, 5);
+
+ assert.strictEqual(ret[1].min, 10);
+ assert.strictEqual(ret[1].max, 10);
+ assert.strictEqual(ret[1].avg, 10);
+ assert.strictEqual(ret[1].start, 10);
+ assert.strictEqual(ret[1].end, 10);
+ });
+
+ test('getSampleStatisticsWithMultipleSelections', function() {
+ const ctr = createCounterWithTwoSeries();
+ const ret = ctr.getSampleStatistics([0, 1]);
+
+ assert.strictEqual(ret[0].min, 5);
+ assert.strictEqual(ret[0].max, 6);
+ assert.strictEqual(ret[0].avg, (5 + 6) / 2);
+ assert.strictEqual(ret[0].start, 5);
+ assert.strictEqual(ret[0].end, 6);
+
+ assert.strictEqual(ret[1].min, 10);
+ assert.strictEqual(ret[1].max, 15);
+ assert.strictEqual(ret[1].avg, (10 + 15) / 2);
+ assert.strictEqual(ret[1].start, 10);
+ assert.strictEqual(ret[1].end, 15);
+ });
+
+ test('getSampleStatisticsWithOutofOrderIndices', function() {
+ const ctr = createCounterWithTwoSeries();
+ const ret = ctr.getSampleStatistics([1, 0]);
+
+ assert.strictEqual(ret[0].min, 5);
+ assert.strictEqual(ret[0].max, 6);
+ assert.strictEqual(ret[0].avg, (5 + 6) / 2);
+ assert.strictEqual(ret[0].start, 5);
+ assert.strictEqual(ret[0].end, 6);
+
+ assert.strictEqual(ret[1].min, 10);
+ assert.strictEqual(ret[1].max, 15);
+ assert.strictEqual(ret[1].avg, (10 + 15) / 2);
+ assert.strictEqual(ret[1].start, 10);
+ assert.strictEqual(ret[1].end, 15);
+ });
+
+ test('getSampleStatisticsWithAllSelections', function() {
+ const ctr = createCounterWithTwoSeries();
+ const ret = ctr.getSampleStatistics([1, 0, 2, 3]);
+
+ assert.strictEqual(ret[0].min, 5);
+ assert.strictEqual(ret[0].max, 7);
+ assert.strictEqual(ret[0].avg, (5 + 6 + 5 + 7) / 4);
+ assert.strictEqual(ret[0].start, 5);
+ assert.strictEqual(ret[0].end, 7);
+
+ assert.strictEqual(ret[1].min, 10);
+ assert.strictEqual(ret[1].max, 16);
+ assert.strictEqual(ret[1].avg, (10 + 15 + 12 + 16) / 4);
+ assert.strictEqual(ret[1].start, 10);
+ assert.strictEqual(ret[1].end, 16);
+ });
+
+ test('testCounterSortRemainInOrder', function() {
+ const model = new tr.Model();
+ const process = new tr.model.Process(model, 4);
+ const ctr1 = new Counter(process, 0, '', 'a');
+ const ctr2 = new Counter(process, 0, '', 'b');
+
+ const array = [ctr1, ctr2];
+ array.sort(tr.model.Counter.compare);
+
+ assert.strictEqual(array[0], ctr1);
+ assert.strictEqual(array[1], ctr2);
+ });
+});
+</script>
+
diff --git a/chromium/third_party/catapult/tracing/tracing/model/cpu.html b/chromium/third_party/catapult/tracing/tracing/model/cpu.html
new file mode 100644
index 00000000000..beb2f63e7cf
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/cpu.html
@@ -0,0 +1,282 @@
+<!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/range.html">
+<link rel="import" href="/tracing/model/counter.html">
+<link rel="import" href="/tracing/model/cpu_slice.html">
+<link rel="import" href="/tracing/model/process_base.html">
+<link rel="import" href="/tracing/model/thread_time_slice.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Provides the Cpu class.
+ */
+tr.exportTo('tr.model', function() {
+ const ColorScheme = tr.b.ColorScheme;
+ const Counter = tr.model.Counter;
+ const CpuSlice = tr.model.CpuSlice;
+
+ /**
+ * The Cpu represents a Cpu from the kernel's point of view.
+ * @constructor
+ */
+ function Cpu(kernel, number) {
+ if (kernel === undefined || number === undefined) {
+ throw new Error('Missing arguments');
+ }
+ this.kernel = kernel;
+ this.cpuNumber = number;
+ this.slices = [];
+ this.counters = {};
+ this.bounds_ = new tr.b.math.Range();
+ this.samples_ = undefined; // Set during createSubSlices
+
+ // Start timestamp of the last active thread.
+ this.lastActiveTimestamp_ = undefined;
+
+ // Identifier of the last active thread. On Linux, it's a pid while on
+ // Windows it's a thread id.
+ this.lastActiveThread_ = undefined;
+
+ // Name and arguments of the last active thread.
+ this.lastActiveName_ = undefined;
+ this.lastActiveArgs_ = undefined;
+ }
+
+ Cpu.prototype = {
+ __proto__: tr.model.EventContainer.prototype,
+
+ get samples() {
+ return this.samples_;
+ },
+
+ get userFriendlyName() {
+ return 'CPU ' + this.cpuNumber;
+ },
+
+ * findTopmostSlicesInThisContainer(eventPredicate, opt_this) {
+ // All CpuSlices are toplevel since CpuSlices do not nest.
+ for (const s of this.slices) {
+ yield* s.findTopmostSlicesRelativeToThisSlice(
+ eventPredicate, opt_this);
+ }
+ },
+
+ * childEvents() {
+ yield* this.slices;
+
+ if (this.samples_) {
+ yield* this.samples_;
+ }
+ },
+
+ * childEventContainers() {
+ yield* Object.values(this.counters);
+ },
+
+ /**
+ * @return {Counter} The counter on this CPU with the given category/name
+ * combination, creating it if it doesn't exist.
+ */
+ getOrCreateCounter(cat, name) {
+ const id = cat + '.' + name;
+ if (!this.counters[id]) {
+ this.counters[id] = new Counter(this, id, cat, name);
+ }
+ return this.counters[id];
+ },
+
+ /**
+ * @return {Counter} the counter on this CPU with the given category/name
+ * combination, or undefined if it doesn't exist.
+ */
+ getCounter(cat, name) {
+ const id = cat + '.' + name;
+ if (!this.counters[id]) {
+ return undefined;
+ }
+ return this.counters[id];
+ },
+
+ /**
+ * Shifts all the timestamps inside this CPU forward by the amount
+ * specified.
+ */
+ shiftTimestampsForward(amount) {
+ for (let sI = 0; sI < this.slices.length; sI++) {
+ this.slices[sI].start = (this.slices[sI].start + amount);
+ }
+ for (const id in this.counters) {
+ this.counters[id].shiftTimestampsForward(amount);
+ }
+ },
+
+ /**
+ * Updates the range based on the current slices attached to the cpu.
+ */
+ updateBounds() {
+ this.bounds_.reset();
+ if (this.slices.length) {
+ this.bounds_.addValue(this.slices[0].start);
+ this.bounds_.addValue(this.slices[this.slices.length - 1].end);
+ }
+ for (const id in this.counters) {
+ this.counters[id].updateBounds();
+ this.bounds_.addRange(this.counters[id].bounds);
+ }
+ if (this.samples_ && this.samples_.length) {
+ this.bounds_.addValue(this.samples_[0].start);
+ this.bounds_.addValue(
+ this.samples_[this.samples_.length - 1].end);
+ }
+ },
+
+ createSubSlices() {
+ this.samples_ = this.kernel.model.samples.filter(function(sample) {
+ return sample.cpu === this;
+ }, this);
+ },
+
+ addCategoriesToDict(categoriesDict) {
+ for (let i = 0; i < this.slices.length; i++) {
+ categoriesDict[this.slices[i].category] = true;
+ }
+ for (const id in this.counters) {
+ categoriesDict[this.counters[id].category] = true;
+ }
+ for (let i = 0; i < this.samples_.length; i++) {
+ categoriesDict[this.samples_[i].category] = true;
+ }
+ },
+
+ /*
+ * Returns the index of the slice in the CPU's slices, or undefined.
+ */
+ indexOf(cpuSlice) {
+ const i = tr.b.findLowIndexInSortedArray(
+ this.slices,
+ function(slice) { return slice.start; },
+ cpuSlice.start);
+ if (this.slices[i] !== cpuSlice) return undefined;
+ return i;
+ },
+
+ /**
+ * Closes the thread running on the CPU. |endTimestamp| is the timestamp
+ * at which the thread was unscheduled. |args| is merged with the arguments
+ * specified when the thread was initially scheduled.
+ */
+ closeActiveThread(endTimestamp, args) {
+ // Don't generate a slice if the last active thread is the idle task.
+ if (this.lastActiveThread_ === undefined ||
+ this.lastActiveThread_ === 0) {
+ return;
+ }
+
+ if (endTimestamp < this.lastActiveTimestamp_) {
+ throw new Error('The end timestamp of a thread running on CPU ' +
+ this.cpuNumber + ' is before its start timestamp.');
+ }
+
+ // Merge |args| with |this.lastActiveArgs_|. If a key is in both
+ // dictionaries, the value from |args| is used.
+ for (const key in args) {
+ this.lastActiveArgs_[key] = args[key];
+ }
+
+ const duration = endTimestamp - this.lastActiveTimestamp_;
+ const slice = new tr.model.CpuSlice(
+ '', this.lastActiveName_,
+ ColorScheme.getColorIdForGeneralPurposeString(this.lastActiveName_),
+ this.lastActiveTimestamp_,
+ this.lastActiveArgs_,
+ duration);
+ slice.cpu = this;
+ this.slices.push(slice);
+
+ // Clear the last state.
+ this.lastActiveTimestamp_ = undefined;
+ this.lastActiveThread_ = undefined;
+ this.lastActiveName_ = undefined;
+ this.lastActiveArgs_ = undefined;
+ },
+
+ switchActiveThread(timestamp, oldThreadArgs, newThreadId,
+ newThreadName, newThreadArgs) {
+ // Close the previous active thread and generate a slice.
+ this.closeActiveThread(timestamp, oldThreadArgs);
+
+ // Keep track of the new thread.
+ this.lastActiveTimestamp_ = timestamp;
+ this.lastActiveThread_ = newThreadId;
+ this.lastActiveName_ = newThreadName;
+ this.lastActiveArgs_ = newThreadArgs;
+ },
+
+ /**
+ * Returns the frequency statistics for this CPU;
+ * the returned object contains the frequencies as keys,
+ * and the duration at this frequency in milliseconds as the value,
+ * for the range that was specified.
+ */
+ getFreqStatsForRange(range) {
+ const stats = {};
+
+ function addStatsForFreq(freqSample, index) {
+ // Counters don't have an explicit end or duration;
+ // calculate the end by looking at the starting point
+ // of the next value in the series, or if that doesn't
+ // exist, assume this frequency is held until the end.
+ const freqEnd = (index < freqSample.series_.length - 1) ?
+ freqSample.series_.samples_[index + 1].timestamp : range.max;
+
+ const freqRange = tr.b.math.Range.fromExplicitRange(
+ freqSample.timestamp, freqEnd);
+ const intersection = freqRange.findIntersection(range);
+ if (!(freqSample.value in stats)) {
+ stats[freqSample.value] = 0;
+ }
+ stats[freqSample.value] += intersection.duration;
+ }
+
+ const freqCounter = this.getCounter('', 'Clock Frequency');
+ if (freqCounter !== undefined) {
+ const freqSeries = freqCounter.getSeries(0);
+ if (!freqSeries) return;
+
+ tr.b.iterateOverIntersectingIntervals(freqSeries.samples_,
+ function(x) { return x.timestamp; },
+ function(x, index) {
+ if (index < freqSeries.length - 1) {
+ return freqSeries.samples_[index + 1].timestamp;
+ }
+ return range.max;
+ },
+ range.min,
+ range.max,
+ addStatsForFreq);
+ }
+
+ return stats;
+ }
+ };
+
+ /**
+ * Comparison between processes that orders by cpuNumber.
+ */
+ Cpu.compare = function(x, y) {
+ return x.cpuNumber - y.cpuNumber;
+ };
+
+
+ return {
+ Cpu,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/cpu_slice.html b/chromium/third_party/catapult/tracing/tracing/model/cpu_slice.html
new file mode 100644
index 00000000000..6faf5d143fa
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/cpu_slice.html
@@ -0,0 +1,68 @@
+<!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/range.html">
+<link rel="import" href="/tracing/model/thread_time_slice.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Provides the CpuSlice class.
+ */
+tr.exportTo('tr.model', function() {
+ const Slice = tr.model.Slice;
+
+ /**
+ * A CpuSlice represents a slice of time on a CPU.
+ *
+ * @constructor
+ */
+ function CpuSlice(cat, title, colorId, start, args, opt_duration) {
+ Slice.apply(this, arguments);
+ this.threadThatWasRunning = undefined;
+ this.cpu = undefined;
+ }
+
+ CpuSlice.prototype = {
+ __proto__: Slice.prototype,
+
+ get analysisTypeName() {
+ return 'tr.ui.analysis.CpuSlice';
+ },
+
+ getAssociatedTimeslice() {
+ if (!this.threadThatWasRunning) {
+ return undefined;
+ }
+ const timeSlices = this.threadThatWasRunning.timeSlices;
+ for (let i = 0; i < timeSlices.length; i++) {
+ const timeSlice = timeSlices[i];
+ if (timeSlice.start !== this.start) {
+ continue;
+ }
+ if (timeSlice.duration !== this.duration) {
+ continue;
+ }
+ return timeSlice;
+ }
+ return undefined;
+ }
+ };
+
+ tr.model.EventRegistry.register(
+ CpuSlice,
+ {
+ name: 'cpuSlice',
+ pluralName: 'cpuSlices'
+ });
+
+ return {
+ CpuSlice,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/cpu_test.html b/chromium/third_party/catapult/tracing/tracing/model/cpu_test.html
new file mode 100644
index 00000000000..49f01dff4f3
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/cpu_test.html
@@ -0,0 +1,206 @@
+<!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/range.html">
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/model/model.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const ColorScheme = tr.b.ColorScheme;
+ const Cpu = tr.model.Cpu;
+ const newThreadSlice = tr.c.TestUtils.newThreadSlice;
+
+ test('cpuBounds_Empty', function() {
+ const cpu = new Cpu({}, 1);
+ cpu.updateBounds();
+ assert.isUndefined(cpu.bounds.min);
+ assert.isUndefined(cpu.bounds.max);
+ });
+
+ test('cpuBounds_OneSlice', function() {
+ const cpu = new Cpu({}, 1);
+ cpu.slices.push(tr.c.TestUtils.newSliceEx({start: 1, duration: 3}));
+ cpu.updateBounds();
+ assert.strictEqual(cpu.bounds.min, 1);
+ assert.strictEqual(cpu.bounds.max, 4);
+ });
+
+ test('getOrCreateCounter', function() {
+ const cpu = new Cpu({}, 1);
+ const ctrBar = cpu.getOrCreateCounter('foo', 'bar');
+ const ctrBar2 = cpu.getOrCreateCounter('foo', 'bar');
+ assert.strictEqual(ctrBar, ctrBar2);
+ });
+
+ test('shiftTimestampsForward', function() {
+ const cpu = new Cpu({}, 1);
+ const ctr = cpu.getOrCreateCounter('foo', 'bar');
+ cpu.slices.push(tr.c.TestUtils.newSliceEx({start: 1, duration: 3}));
+ let shiftCount = 0;
+ ctr.shiftTimestampsForward = function(ts) {
+ if (ts === 0.32) {
+ shiftCount++;
+ }
+ };
+ cpu.slices.push(tr.c.TestUtils.newSliceEx({start: 1, duration: 3}));
+ cpu.shiftTimestampsForward(0.32);
+ assert.strictEqual(1, shiftCount);
+ assert.strictEqual(cpu.slices[0].start, 1.32);
+ });
+
+
+ function newCpuSliceNamed(cpu, name, start, duration, opt_thread) {
+ const s = new tr.model.CpuSlice(
+ 'cat', name, 0, start, {}, duration);
+ s.cpu = cpu;
+ if (opt_thread) {
+ s.threadThatWasRunning = opt_thread;
+ }
+ return s;
+ }
+
+ test('getTimesliceForCpuSlice', function() {
+ const m = new tr.Model();
+ const SCHEDULING_STATE = tr.model.SCHEDULING_STATE;
+ const cpu = m.kernel.getOrCreateCpu(1);
+ const t2 = m.getOrCreateProcess(1).getOrCreateThread(2);
+ t2.timeSlices = [newThreadSlice(t2, SCHEDULING_STATE.RUNNING, 0, 10, cpu),
+ newThreadSlice(t2, SCHEDULING_STATE.SLEEPING, 10, 10),
+ newThreadSlice(t2, SCHEDULING_STATE.RUNNING, 20, 10, cpu)];
+ cpu.slices = [newCpuSliceNamed(cpu, 'x', 0, 10, t2),
+ newCpuSliceNamed(cpu, 'x', 20, 10, t2)];
+ assert.strictEqual(
+ cpu.slices[0].getAssociatedTimeslice(), t2.timeSlices[0]);
+ assert.strictEqual(
+ cpu.slices[1].getAssociatedTimeslice(), t2.timeSlices[2]);
+
+ assert.strictEqual(t2.timeSlices[0].getAssociatedCpuSlice(), cpu.slices[0]);
+ assert.isUndefined(t2.timeSlices[1].getAssociatedCpuSlice());
+ assert.strictEqual(t2.timeSlices[2].getAssociatedCpuSlice(), cpu.slices[1]);
+
+ assert.strictEqual(cpu.indexOf(cpu.slices[0]), 0);
+ assert.strictEqual(cpu.indexOf(cpu.slices[1]), 1);
+
+ assert.strictEqual(t2.indexOfTimeSlice(t2.timeSlices[0]), 0);
+ assert.strictEqual(t2.indexOfTimeSlice(t2.timeSlices[1]), 1);
+ assert.strictEqual(t2.indexOfTimeSlice(t2.timeSlices[2]), 2);
+ });
+
+ test('putToSleepFor', function() {
+ const m = new tr.Model();
+ const SCHEDULING_STATE = tr.model.SCHEDULING_STATE;
+ const cpu = m.kernel.getOrCreateCpu(1);
+
+ const t2 = m.getOrCreateProcess(1).getOrCreateThread(2);
+ const t3 = m.getOrCreateProcess(1).getOrCreateThread(3);
+ t2.timeSlices = [newThreadSlice(t2, SCHEDULING_STATE.RUNNING, 0, 10, cpu),
+ newThreadSlice(t2, SCHEDULING_STATE.SLEEPING, 10, 10),
+ newThreadSlice(t2, SCHEDULING_STATE.RUNNING, 20, 10, cpu)];
+ t3.timeSlices = [newThreadSlice(t3, SCHEDULING_STATE.RUNNING, 10, 5, cpu)];
+ cpu.slices = [newCpuSliceNamed(cpu, 'x', 0, 10, t2),
+ newCpuSliceNamed(cpu, 'x', 10, 5, t3),
+ newCpuSliceNamed(cpu, 'x', 20, 10, t2)];
+
+ // At timeslice 0, the thread is running.
+ assert.isUndefined(t2.timeSlices[0].getCpuSliceThatTookCpu());
+
+ // t2 lost the cpu to t3 at t=10
+ assert.strictEqual(
+ cpu.slices[1],
+ t2.timeSlices[1].getCpuSliceThatTookCpu());
+ });
+
+ test('putToSleepForNothing', function() {
+ const m = new tr.Model();
+ const SCHEDULING_STATE = tr.model.SCHEDULING_STATE;
+ const cpu = m.kernel.getOrCreateCpu(1);
+
+ const t2 = m.getOrCreateProcess(1).getOrCreateThread(2);
+ const t3 = m.getOrCreateProcess(1).getOrCreateThread(3);
+ t2.timeSlices = [newThreadSlice(t2, SCHEDULING_STATE.RUNNING, 0, 10, cpu),
+ newThreadSlice(t2, SCHEDULING_STATE.SLEEPING, 10, 10),
+ newThreadSlice(t2, SCHEDULING_STATE.RUNNING, 20, 10, cpu)];
+ t3.timeSlices = [newThreadSlice(t3, SCHEDULING_STATE.RUNNING, 15, 5, cpu)];
+ cpu.slices = [newCpuSliceNamed(cpu, 'x', 0, 10, t2),
+ newCpuSliceNamed(cpu, 'x', 15, 5, t3),
+ newCpuSliceNamed(cpu, 'x', 20, 10, t2)];
+ assert.isUndefined(t2.timeSlices[1].getCpuSliceThatTookCpu());
+ });
+
+ test('switchActiveThread', function() {
+ const m = new tr.Model();
+ const cpu = m.kernel.getOrCreateCpu(1);
+
+ cpu.switchActiveThread(5, {}, 0, 'idle thread', {});
+ cpu.switchActiveThread(10, {}, 1, 'thread one', {a: 1});
+ cpu.switchActiveThread(15, {b: 2}, 2, 'thread two', {c: 3});
+ cpu.switchActiveThread(30, {c: 4, d: 5}, 3, 'thread three', {e: 6});
+ cpu.closeActiveThread(40, {f: 7});
+ cpu.switchActiveThread(50, {}, 4, 'thread four', {g: 8});
+ cpu.switchActiveThread(60, {}, 1, 'thread one', {});
+ cpu.closeActiveThread(70, {});
+
+ assert.strictEqual(cpu.slices.length, 5);
+
+ assert.strictEqual(cpu.slices[0].title, 'thread one');
+ assert.strictEqual(cpu.slices[0].start, 10);
+ assert.strictEqual(cpu.slices[0].duration, 5);
+ assert.strictEqual(Object.keys(cpu.slices[0].args).length, 2);
+ assert.strictEqual(cpu.slices[0].args.a, 1);
+ assert.strictEqual(cpu.slices[0].args.b, 2);
+
+ assert.strictEqual(cpu.slices[1].title, 'thread two');
+ assert.strictEqual(cpu.slices[1].start, 15);
+ assert.strictEqual(cpu.slices[1].duration, 15);
+ assert.strictEqual(Object.keys(cpu.slices[1].args).length, 2);
+ assert.strictEqual(cpu.slices[1].args.c, 4);
+ assert.strictEqual(cpu.slices[1].args.d, 5);
+
+ assert.strictEqual(cpu.slices[2].title, 'thread three');
+ assert.strictEqual(cpu.slices[2].start, 30);
+ assert.strictEqual(cpu.slices[2].duration, 10);
+ assert.strictEqual(Object.keys(cpu.slices[2].args).length, 2);
+ assert.strictEqual(cpu.slices[2].args.e, 6);
+ assert.strictEqual(cpu.slices[2].args.f, 7);
+
+ assert.strictEqual(cpu.slices[3].title, 'thread four');
+ assert.strictEqual(cpu.slices[3].start, 50);
+ assert.strictEqual(cpu.slices[3].duration, 10);
+ assert.strictEqual(Object.keys(cpu.slices[3].args).length, 1);
+ assert.strictEqual(cpu.slices[3].args.g, 8);
+
+ assert.strictEqual(cpu.slices[4].title, 'thread one');
+ assert.strictEqual(cpu.slices[4].start, 60);
+ assert.strictEqual(cpu.slices[4].duration, 10);
+ assert.strictEqual(Object.keys(cpu.slices[4].args).length, 0);
+ });
+
+ test('getFrequencyStats', function() {
+ const m = new tr.Model();
+ const cpu = m.kernel.getOrCreateCpu(1);
+ const powerCounter = cpu.getOrCreateCounter('', 'Clock Frequency');
+ const series = powerCounter.addSeries(new tr.model.CounterSeries('state',
+ ColorScheme.getColorIdForGeneralPurposeString('test')));
+
+ series.addCounterSample(0, 100000);
+ series.addCounterSample(20, 300000);
+ series.addCounterSample(30, 100000);
+ series.addCounterSample(80, 500000);
+ series.addCounterSample(100, 300000);
+
+ const range = tr.b.math.Range.fromExplicitRange(10, 90);
+ const stats = cpu.getFreqStatsForRange(range);
+ assert.strictEqual(stats[100000], 60);
+ assert.strictEqual(stats[300000], 10);
+ assert.strictEqual(stats[500000], 10);
+ });
+});
+</script>
+
diff --git a/chromium/third_party/catapult/tracing/tracing/model/device.html b/chromium/third_party/catapult/tracing/tracing/model/device.html
new file mode 100644
index 00000000000..5dca1e03151
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/device.html
@@ -0,0 +1,124 @@
+<!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/guid.html">
+<link rel="import" href="/tracing/base/math/range.html">
+<link rel="import" href="/tracing/model/cpu.html">
+<link rel="import" href="/tracing/model/event_container.html">
+<link rel="import" href="/tracing/model/power_series.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Provides the Device class.
+ */
+tr.exportTo('tr.model', function() {
+ /**
+ * Device represents the device-level objects in the model.
+ * @constructor
+ * @extends {tr.model.EventContainer}
+ */
+ function Device(model) {
+ if (!model) {
+ throw new Error('Must provide a model.');
+ }
+
+ tr.model.EventContainer.call(this);
+
+ this.powerSeries_ = undefined;
+ this.cpuUsageSeries_ = undefined;
+ this.vSyncTimestamps_ = [];
+ }
+
+ Device.compare = function(x, y) {
+ return x.guid - y.guid;
+ };
+
+ Device.prototype = {
+ __proto__: tr.model.EventContainer.prototype,
+
+ compareTo(that) {
+ return Device.compare(this, that);
+ },
+
+ get userFriendlyName() {
+ return 'Device';
+ },
+
+ get userFriendlyDetails() {
+ return 'Device';
+ },
+
+ get stableId() {
+ return 'Device';
+ },
+
+ getSettingsKey() {
+ return 'device';
+ },
+
+ get powerSeries() {
+ return this.powerSeries_;
+ },
+
+ set powerSeries(powerSeries) {
+ this.powerSeries_ = powerSeries;
+ },
+
+ get cpuUsageSeries() {
+ return this.cpuUsageSeries_;
+ },
+
+ set cpuUsageSeries(cpuUsageSeries) {
+ this.cpuUsageSeries_ = cpuUsageSeries;
+ },
+
+ get vSyncTimestamps() {
+ return this.vSyncTimestamps_;
+ },
+
+ set vSyncTimestamps(value) {
+ this.vSyncTimestamps_ = value;
+ },
+
+ updateBounds() {
+ this.bounds.reset();
+ for (const child of this.childEventContainers()) {
+ child.updateBounds();
+ this.bounds.addRange(child.bounds);
+ }
+ },
+
+ shiftTimestampsForward(amount) {
+ for (const child of this.childEventContainers()) {
+ child.shiftTimestampsForward(amount);
+ }
+
+ for (let i = 0; i < this.vSyncTimestamps_.length; i++) {
+ this.vSyncTimestamps_[i] += amount;
+ }
+ },
+
+ addCategoriesToDict(categoriesDict) {
+ },
+
+ * childEventContainers() {
+ if (this.powerSeries_) {
+ yield this.powerSeries_;
+ }
+ if (this.cpuUsageSeries_) {
+ yield this.cpuUsageSeries_;
+ }
+ }
+ };
+
+ return {
+ Device,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/device_test.html b/chromium/third_party/catapult/tracing/tracing/model/device_test.html
new file mode 100644
index 00000000000..6e46cced01b
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/device_test.html
@@ -0,0 +1,71 @@
+<!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/device.html">
+<link rel="import" href="/tracing/model/model.html">
+<link rel="import" href="/tracing/model/power_series.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const Device = tr.model.Device;
+ const Model = tr.Model;
+ const PowerSeries = tr.model.PowerSeries;
+
+ test('updateBounds', function() {
+ const device = new Device(new Model());
+ device.powerSeries = new PowerSeries(device);
+
+ // Verify that the bounds match the lowest and highest timestamps.
+ device.powerSeries.addPowerSample(100, 5);
+ device.powerSeries.addPowerSample(200, 5);
+ device.updateBounds();
+
+ assert.strictEqual(device.bounds.min, 100);
+ assert.strictEqual(device.bounds.max, 200);
+
+ // Add a new sample and verify that the bounds change.
+ device.powerSeries.addPowerSample(700, 5);
+ device.updateBounds();
+
+ assert.strictEqual(device.bounds.min, 100);
+ assert.strictEqual(device.bounds.max, 700);
+ });
+
+ test('shiftTimestampsForward', function() {
+ const device = new Device(new Model());
+ device.powerSeries = new PowerSeries(device);
+
+ device.powerSeries.addPowerSample(100, 2);
+ device.powerSeries.addPowerSample(200, 2);
+ device.shiftTimestampsForward(2);
+
+ assert.strictEqual(device.powerSeries.samples[0].start, 102);
+ assert.strictEqual(device.powerSeries.samples[1].start, 202);
+ });
+
+ test('childEventContainers_noPowerSeries', function() {
+ const device = new Device(new Model());
+ const childEventContainers = [];
+ for (const container of device.childEventContainers()) {
+ childEventContainers.push(container);
+ }
+ assert.deepEqual(childEventContainers, []);
+ });
+
+ test('childEventContainers_powerSeries', function() {
+ const device = new Device(new Model());
+ device.powerSeries = new PowerSeries(device);
+ const childEventContainers = [];
+ for (const container of device.childEventContainers()) {
+ childEventContainers.push(container);
+ }
+ assert.deepEqual(childEventContainers, [device.powerSeries]);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/event.html b/chromium/third_party/catapult/tracing/tracing/model/event.html
new file mode 100644
index 00000000000..99495fcc913
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/event.html
@@ -0,0 +1,77 @@
+<!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/guid.html">
+<link rel="import" href="/tracing/base/math/range.html">
+<link rel="import" href="/tracing/model/event_set.html">
+<link rel="import" href="/tracing/model/selectable_item.html">
+<link rel="import" href="/tracing/model/selection_state.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Provides the Event class.
+ */
+tr.exportTo('tr.model', function() {
+ const SelectableItem = tr.model.SelectableItem;
+ const SelectionState = tr.model.SelectionState;
+ const IMMUTABLE_EMPTY_SET = tr.model.EventSet.IMMUTABLE_EMPTY_SET;
+
+ /**
+ * An Event is the base type for any non-container, selectable piece
+ * of data in the trace model.
+ *
+ * @constructor
+ * @extends {SelectableItem}
+ */
+ function Event() {
+ SelectableItem.call(this, this /* modelItem */);
+ this.guid_ = tr.b.GUID.allocateSimple();
+ this.selectionState = SelectionState.NONE;
+ this.info = undefined;
+ }
+
+ Event.prototype = {
+ __proto__: SelectableItem.prototype,
+
+ get guid() {
+ return this.guid_;
+ },
+
+ get stableId() {
+ return undefined;
+ },
+
+ get range() {
+ const range = new tr.b.math.Range();
+ this.addBoundsToRange(range);
+ return range;
+ },
+
+ // Empty by default. Lazily initialized on an instance in
+ // addAssociatedAlert(). See #1930.
+ associatedAlerts: IMMUTABLE_EMPTY_SET,
+
+ addAssociatedAlert(alert) {
+ if (this.associatedAlerts === IMMUTABLE_EMPTY_SET) {
+ this.associatedAlerts = new tr.model.EventSet();
+ }
+ this.associatedAlerts.push(alert);
+ },
+
+ // Adds the range of timestamps for this event to the specified range.
+ // If this is not overridden in subclass, it means that type of event
+ // doesn't have timestamps.
+ addBoundsToRange(range) {}
+ };
+
+ return {
+ Event,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/event_container.html b/chromium/third_party/catapult/tracing/tracing/model/event_container.html
new file mode 100644
index 00000000000..8c71eb4b688
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/event_container.html
@@ -0,0 +1,167 @@
+<!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/base.html">
+<link rel="import" href="/tracing/base/guid.html">
+<link rel="import" href="/tracing/base/math/range.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.model', function() {
+ /**
+ * EventContainer is a base class for any class in the trace model that
+ * contains child events or child EventContainers.
+ *
+ * For all EventContainers, updateBounds() must be called after modifying the
+ * container's events if an up-to-date bounds is expected.
+ *
+ * @constructor
+ */
+ function EventContainer() {
+ this.guid_ = tr.b.GUID.allocateSimple();
+ this.important = true;
+ this.bounds_ = new tr.b.math.Range();
+ }
+
+ EventContainer.prototype = {
+ get guid() {
+ return this.guid_;
+ },
+
+ /**
+ * @return {String} A stable and unique identifier that describes this
+ * container's position in the event tree relative to the root. If an event
+ * container 'B' is a child to another event container 'A', then container
+ * B's stable ID would be 'A.B'.
+ */
+ get stableId() {
+ throw new Error('Not implemented');
+ },
+
+ /**
+ * Returns the bounds of the event container, which describe the range
+ * of timestamps for all ancestor events.
+ */
+ get bounds() {
+ return this.bounds_;
+ },
+
+ // TODO(charliea): A default implementation of this method could likely be
+ // provided that iterates throuch getDescendantEvents.
+ /**
+ * Updates the bounds of the event container. After updating, this.bounds
+ * will describe the range of timestamps of all ancestor events.
+ */
+ updateBounds() {
+ throw new Error('Not implemented');
+ },
+
+ // TODO(charliea): A default implementation of this method could likely be
+ // provided that iterates through getDescendantEvents.
+ /**
+ * Shifts the timestamps for ancestor events by 'amount' milliseconds.
+ */
+ shiftTimestampsForward(amount) {
+ throw new Error('Not implemented');
+ },
+
+
+ /**
+ * Returns an iterable of all child events.
+ */
+ * childEvents() {
+ },
+
+ /**
+ * Returns an iterable of all events in this and descendant
+ * event containers.
+ */
+ * getDescendantEvents() {
+ yield* this.childEvents();
+ for (const container of this.childEventContainers()) {
+ yield* container.getDescendantEvents();
+ }
+ },
+
+ /**
+ * Returns an iterable of all child event containers.
+ */
+ * childEventContainers() {
+ },
+
+ /**
+ * Returns an iterable containing this and all descendant event containers.
+ */
+ * getDescendantEventContainers() {
+ yield this;
+ for (const container of this.childEventContainers()) {
+ yield* container.getDescendantEventContainers();
+ }
+ },
+
+ /**
+ * Returns an iterable of all events in this and descendant event containers
+ * in a given set of ranges.
+ *
+ * This base class provides a default implementation with no assumptions
+ * about the order of events. A container can override this implementation
+ * with a more efficient one, for example if its events are sorted.
+ */
+ * getDescendantEventsInSortedRanges(ranges, opt_containerPredicate) {
+ if (opt_containerPredicate === undefined ||
+ opt_containerPredicate(this)) {
+ for (const event of this.childEvents()) {
+ const i = tr.b.findFirstTrueIndexInSortedArray(
+ ranges, range => event.start <= range.max);
+ if (i < ranges.length && event.end >= ranges[i].min) yield event;
+ }
+ }
+
+ for (const container of this.childEventContainers()) {
+ yield* container.getDescendantEventsInSortedRanges(
+ ranges, opt_containerPredicate);
+ }
+ },
+
+ /**
+ * Finds topmost slices in this container (see docstring for
+ * findTopmostSlices).
+ */
+ * findTopmostSlicesInThisContainer(eventPredicate, opt_this) {
+ },
+
+ /**
+ * The findTopmostSlices* series of helpers find all topmost slices
+ * satisfying the given predicates.
+ *
+ * As an example, suppose we are trying to find slices named 'C', with the
+ * following thread:
+ *
+ * -> |---C---| |-----D-----|
+ * |-C-| |---C---| <-
+ *
+ * findTopmostSlices would locate the pointed-to Cs, because the bottom C on
+ * the left is not the topmost C, and the right one is, even though it is
+ * not itself a top-level slice.
+ */
+ * findTopmostSlices(eventPredicate) {
+ for (const ec of this.getDescendantEventContainers()) {
+ yield* ec.findTopmostSlicesInThisContainer(eventPredicate);
+ }
+ },
+
+ * findTopmostSlicesNamed(name) {
+ yield* this.findTopmostSlices(e => e.title === name);
+ }
+ };
+
+ return {
+ EventContainer,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/event_container_test.html b/chromium/third_party/catapult/tracing/tracing/model/event_container_test.html
new file mode 100644
index 00000000000..06b06a391c2
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/event_container_test.html
@@ -0,0 +1,95 @@
+<!DOCTYPE html>
+<!--
+Copyright 2018 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/model/event_container.html">
+<link rel="import" href="/tracing/model/timed_event.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ test('getDescendantEventsInSortedRanges', function() {
+ class ContainerTypeX extends tr.model.EventContainer {
+ * childEvents() {
+ const event1 = new tr.model.TimedEvent(4);
+ event1.duration = 2;
+ event1.title = 'X-1';
+ yield event1;
+
+ const event2 = new tr.model.TimedEvent(1);
+ event2.duration = 2;
+ event2.title = 'X-2';
+ yield event2;
+ }
+ }
+
+ class ContainerTypeY extends tr.model.EventContainer {
+ constructor() {
+ super();
+ this.childContainer_ = new ContainerTypeX();
+ }
+
+ * childEvents() {
+ const event1 = new tr.model.TimedEvent(5);
+ event1.duration = 4;
+ event1.title = 'Y-1';
+ yield event1;
+
+ const event2 = new tr.model.TimedEvent(6);
+ event2.duration = 2;
+ event2.title = 'Y-2';
+ yield event2;
+ }
+
+ * childEventContainers() {
+ yield this.childContainer_;
+ }
+ }
+
+ // We have the following timed events:
+ // 1 2 3 4 5 6 7 8 9
+ // ContainerTypeY <----- Y-1 ----->
+ // <- Y2 ->
+ // ContainerTypeX <- X-2 -> <- X-1 ->
+ const container = new ContainerTypeY();
+
+ // [2, 5] intersect X-1, X-2, and Y-1.
+ const r1 = new tr.b.math.Range.fromExplicitRange(2, 5);
+ let slices = [...container.getDescendantEventsInSortedRanges([r1])];
+ slices = slices.map(s => s.title).sort();
+ assert.strictEqual(slices.length, 3);
+ assert.strictEqual(slices[0], 'X-1');
+ assert.strictEqual(slices[1], 'X-2');
+ assert.strictEqual(slices[2], 'Y-1');
+
+ // [2, 5], [7, 8] intersect X-1, X-2, Y-1, and Y-2.
+ const r2 = new tr.b.math.Range.fromExplicitRange(7, 8);
+ slices = [...container.getDescendantEventsInSortedRanges([r1, r2])];
+ slices = slices.map(s => s.title).sort();
+ assert.strictEqual(slices.length, 4);
+ assert.strictEqual(slices[0], 'X-1');
+ assert.strictEqual(slices[1], 'X-2');
+ assert.strictEqual(slices[2], 'Y-1');
+ assert.strictEqual(slices[3], 'Y-2');
+
+ // We should see events from ContainerTypeX only.
+ slices = [...container.getDescendantEventsInSortedRanges(
+ [r1], container => container instanceof ContainerTypeX)];
+ slices = slices.map(s => s.title).sort();
+ assert.strictEqual(slices.length, 2);
+ assert.strictEqual(slices[0], 'X-1');
+ assert.strictEqual(slices[1], 'X-2');
+
+ // We should see events from ContainerTypeY only.
+ slices = [...container.getDescendantEventsInSortedRanges(
+ [r1], container => container instanceof ContainerTypeY)];
+ slices = slices.map(s => s.title).sort();
+ assert.strictEqual(slices.length, 1);
+ assert.strictEqual(slices[0], 'Y-1');
+ });
+});
diff --git a/chromium/third_party/catapult/tracing/tracing/model/event_info.html b/chromium/third_party/catapult/tracing/tracing/model/event_info.html
new file mode 100644
index 00000000000..ecc309d471a
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/event_info.html
@@ -0,0 +1,39 @@
+<!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_scheme.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.model', function() {
+ const ColorScheme = tr.b.ColorScheme;
+
+ /**
+ * EventInfo is an annotation added to Events in order to document
+ * what they represent, and override their title/colorId values.
+ *
+ * TODO(ccraik): eventually support more complex structure/paragraphs.
+ *
+ * @param {string} title A user-visible title for the event.
+ * @param {string} description A user-visible description of the event.
+ * @param {Array} docLinks A list of Objects, each of the form
+ * {label: str, textContent: str, href: str}
+ *
+ * @constructor
+ */
+ function EventInfo(title, description, docLinks) {
+ this.title = title;
+ this.description = description;
+ this.docLinks = docLinks;
+ this.colorId = ColorScheme.getColorIdForGeneralPurposeString(title);
+ }
+
+ return {
+ EventInfo,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/event_registry.html b/chromium/third_party/catapult/tracing/tracing/model/event_registry.html
new file mode 100644
index 00000000000..7913d7ffe80
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/event_registry.html
@@ -0,0 +1,91 @@
+<!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/extension_registry.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Provides the EventRegistry class.
+ */
+tr.exportTo('tr.model', function() {
+ // Create the type registry.
+ function EventRegistry() {
+ }
+
+ const options = new tr.b.ExtensionRegistryOptions(tr.b.BASIC_REGISTRY_MODE);
+ tr.b.decorateExtensionRegistry(EventRegistry, options);
+
+ // Enforce all options objects have the right fields.
+ EventRegistry.addEventListener('will-register', function(e) {
+ const metadata = e.typeInfo.metadata;
+ if (metadata.name === undefined) {
+ throw new Error('Registered events must provide name metadata');
+ }
+ if (metadata.pluralName === undefined) {
+ throw new Error('Registered events must provide pluralName metadata');
+ }
+
+ // Add a subtype registry to every event so that all events can be
+ // extended
+ if (metadata.subTypes === undefined) {
+ metadata.subTypes = {};
+ const options = new tr.b.ExtensionRegistryOptions(
+ tr.b.TYPE_BASED_REGISTRY_MODE);
+ options.mandatoryBaseClass = e.typeInfo.constructor;
+ options.defaultConstructor = e.typeInfo.constructor;
+ tr.b.decorateExtensionRegistry(metadata.subTypes, options);
+ } else {
+ if (!metadata.subTypes.register) {
+ throw new Error('metadata.subTypes must be an extension registry.');
+ }
+ }
+
+ e.typeInfo.constructor.subTypes = metadata.subTypes;
+ });
+
+ // Helper: lookup Events indexed by type name.
+ let eventsByTypeName = undefined;
+ EventRegistry.getEventTypeInfoByTypeName = function(typeName) {
+ if (eventsByTypeName === undefined) {
+ eventsByTypeName = {};
+ EventRegistry.getAllRegisteredTypeInfos().forEach(function(typeInfo) {
+ eventsByTypeName[typeInfo.metadata.name] = typeInfo;
+ });
+ }
+ return eventsByTypeName[typeName];
+ };
+
+ // Ensure eventsByTypeName stays current.
+ EventRegistry.addEventListener('registry-changed', function() {
+ eventsByTypeName = undefined;
+ });
+
+ function convertCamelCaseToTitleCase(name) {
+ let result = name.replace(/[A-Z]/g, ' $&');
+ result = result.charAt(0).toUpperCase() + result.slice(1);
+ return result;
+ }
+
+ EventRegistry.getUserFriendlySingularName = function(typeName) {
+ const typeInfo = EventRegistry.getEventTypeInfoByTypeName(typeName);
+ const str = typeInfo.metadata.name;
+ return convertCamelCaseToTitleCase(str);
+ };
+
+ EventRegistry.getUserFriendlyPluralName = function(typeName) {
+ const typeInfo = EventRegistry.getEventTypeInfoByTypeName(typeName);
+ const str = typeInfo.metadata.pluralName;
+ return convertCamelCaseToTitleCase(str);
+ };
+
+ return {
+ EventRegistry,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/event_set.html b/chromium/third_party/catapult/tracing/tracing/model/event_set.html
new file mode 100644
index 00000000000..8805a6302ba
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/event_set.html
@@ -0,0 +1,302 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2012 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/event.html">
+<link rel="import" href="/tracing/base/guid.html">
+<link rel="import" href="/tracing/base/math/range.html">
+<link rel="import" href="/tracing/base/utils.html">
+<link rel="import" href="/tracing/model/event_registry.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.model', function() {
+ const EventRegistry = tr.model.EventRegistry;
+
+ const RequestSelectionChangeEvent = tr.b.Event.bind(
+ undefined, 'requestSelectionChange', true, false);
+
+ /**
+ * Represents a event set within a and its associated set of tracks.
+ * @constructor
+ */
+ function EventSet(opt_events) {
+ this.bounds_ = new tr.b.math.Range();
+ this.events_ = new Set();
+ this.guid_ = tr.b.GUID.allocateSimple();
+
+ if (opt_events) {
+ if (opt_events instanceof Array) {
+ for (const event of opt_events) {
+ this.push(event);
+ }
+ } else if (opt_events instanceof EventSet) {
+ this.addEventSet(opt_events);
+ } else {
+ this.push(opt_events);
+ }
+ }
+ }
+
+ EventSet.prototype = {
+ __proto__: Object.prototype,
+
+ get bounds() {
+ return this.bounds_;
+ },
+
+ get duration() {
+ if (this.bounds_.isEmpty) return 0;
+ return this.bounds_.max - this.bounds_.min;
+ },
+
+ get length() {
+ return this.events_.size;
+ },
+
+ get guid() {
+ return this.guid_;
+ },
+
+ * [Symbol.iterator]() {
+ for (const event of this.events_) {
+ yield event;
+ }
+ },
+
+ clear() {
+ this.bounds_ = new tr.b.math.Range();
+ this.events_.clear();
+ },
+
+ /**
+ * Pushes each argument onto the EventSet. Returns the number of
+ * arguments pushed.
+ */
+ push(...events) {
+ let numPushed;
+ for (const event of events) {
+ if (event.guid === undefined) {
+ throw new Error('Event must have a GUID');
+ }
+
+ if (!this.events_.has(event)) {
+ this.events_.add(event);
+ // Some uses of eventSet (e.g. in tests) have Events as objects that
+ // don't have addBoundsToRange as a function. Thus we need to handle
+ // this case.
+ if (event.addBoundsToRange) {
+ if (this.bounds_ !== undefined) {
+ event.addBoundsToRange(this.bounds_);
+ }
+ }
+ }
+ numPushed++;
+ }
+ return numPushed;
+ },
+
+ contains(event) {
+ if (this.events_.has(event)) return event;
+ return undefined;
+ },
+
+ addEventSet(eventSet) {
+ for (const event of eventSet) {
+ this.push(event);
+ }
+ },
+
+ intersectionIsEmpty(otherEventSet) {
+ return !this.some(event => otherEventSet.contains(event));
+ },
+
+ equals(that) {
+ if (this.length !== that.length) return false;
+ return this.every(event => that.contains(event));
+ },
+
+ sortEvents(compare) {
+ // Convert to array, then sort, then convert back
+ const ary = this.toArray();
+ ary.sort(compare);
+
+ this.clear();
+ for (const event of ary) {
+ this.push(event);
+ }
+ },
+
+ getEventsOrganizedByBaseType(opt_pruneEmpty) {
+ const allTypeInfos = EventRegistry.getAllRegisteredTypeInfos();
+
+ const events = this.getEventsOrganizedByCallback(function(event) {
+ let maxEventIndex = -1;
+ let maxEventTypeInfo = undefined;
+
+ allTypeInfos.forEach(function(eventTypeInfo, eventIndex) {
+ if (!(event instanceof eventTypeInfo.constructor)) return;
+
+ if (eventIndex > maxEventIndex) {
+ maxEventIndex = eventIndex;
+ maxEventTypeInfo = eventTypeInfo;
+ }
+ });
+
+ if (maxEventIndex === -1) {
+ throw new Error(`Unrecognized event type: ${event.constructor.name}`);
+ }
+
+ return maxEventTypeInfo.metadata.name;
+ });
+
+ if (!opt_pruneEmpty) {
+ allTypeInfos.forEach(function(eventTypeInfo) {
+ if (events[eventTypeInfo.metadata.name] === undefined) {
+ events[eventTypeInfo.metadata.name] = new EventSet();
+ }
+ });
+ }
+
+ return events;
+ },
+
+ getEventsOrganizedByTitle() {
+ return this.getEventsOrganizedByCallback(function(event) {
+ if (event.title === undefined) {
+ throw new Error('An event didn\'t have a title!');
+ }
+ return event.title;
+ });
+ },
+
+ /**
+ * @param {!function(!tr.model.Event):string} cb
+ * @param {*=} opt_this
+ * @return {!Object} TODO(#3432) Return Map.
+ */
+ getEventsOrganizedByCallback(cb, opt_this) {
+ const groupedEvents = tr.b.groupIntoMap(this, cb, opt_this || this);
+ const groupedEventsDict = {};
+ for (const [k, events] of groupedEvents) {
+ groupedEventsDict[k] = new EventSet(events);
+ }
+ return groupedEventsDict;
+ },
+
+ enumEventsOfType(type, func) {
+ for (const event of this) {
+ if (event instanceof type) {
+ func(event);
+ }
+ }
+ },
+
+ get userFriendlyName() {
+ if (this.length === 0) {
+ throw new Error('Empty event set');
+ }
+
+ const eventsByBaseType = this.getEventsOrganizedByBaseType(true);
+ const eventTypeName = Object.keys(eventsByBaseType)[0];
+
+ if (this.length === 1) {
+ const tmp = EventRegistry.getUserFriendlySingularName(eventTypeName);
+ return tr.b.getOnlyElement(this.events_).userFriendlyName;
+ }
+
+ const numEventTypes = Object.keys(eventsByBaseType).length;
+ if (numEventTypes !== 1) {
+ return this.length + ' events of various types';
+ }
+
+ const tmp = EventRegistry.getUserFriendlyPluralName(eventTypeName);
+ return this.length + ' ' + tmp;
+ },
+
+ filter(fn, opt_this) {
+ const res = new EventSet();
+ for (const event of this) {
+ if (fn.call(opt_this, event)) {
+ res.push(event);
+ }
+ }
+
+ return res;
+ },
+
+ toArray() {
+ const ary = [];
+ for (const event of this) {
+ ary.push(event);
+ }
+ return ary;
+ },
+
+ forEach(fn, opt_this) {
+ for (const event of this) {
+ fn.call(opt_this, event);
+ }
+ },
+
+ map(fn, opt_this) {
+ const res = [];
+ for (const event of this) {
+ res.push(fn.call(opt_this, event));
+ }
+ return res;
+ },
+
+ every(fn, opt_this) {
+ for (const event of this) {
+ if (!fn.call(opt_this, event)) {
+ return false;
+ }
+ }
+ return true;
+ },
+
+ some(fn, opt_this) {
+ for (const event of this) {
+ if (fn.call(opt_this, event)) {
+ return true;
+ }
+ }
+ return false;
+ },
+
+ asDict() {
+ const stableIds = [];
+ for (const event of this) {
+ stableIds.push(event.stableId);
+ }
+ return {'events': stableIds};
+ },
+
+ asSet() {
+ return this.events_;
+ }
+ };
+
+ EventSet.IMMUTABLE_EMPTY_SET = (function() {
+ const s = new EventSet();
+ s.push = function() {
+ throw new Error('Cannot push to an immutable event set');
+ };
+ s.addEventSet = function() {
+ throw new Error('Cannot add to an immutable event set');
+ };
+ Object.freeze(s);
+ return s;
+ })();
+
+ return {
+ EventSet,
+ RequestSelectionChangeEvent,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/event_set_test.html b/chromium/third_party/catapult/tracing/tracing/model/event_set_test.html
new file mode 100644
index 00000000000..9f597ed0984
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/event_set_test.html
@@ -0,0 +1,360 @@
+<!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/core/test_utils.html">
+<link rel="import" href="/tracing/model/event_set.html">
+<link rel="import" href="/tracing/model/model.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const newModel = tr.c.TestUtils.newModel;
+ const newSliceEx = tr.c.TestUtils.newSliceEx;
+
+ test('eventSetObject', function() {
+ const model = new tr.Model();
+ const p1 = model.getOrCreateProcess(1);
+ const t1 = p1.getOrCreateThread(1);
+ t1.sliceGroup.pushSlice(
+ new tr.model.ThreadSlice('', 'a', 0, 1, {}, 3));
+ t1.sliceGroup.pushSlice(
+ new tr.model.ThreadSlice('', 'a', 0, 5, {}, 1));
+
+ const eventSet = new tr.model.EventSet();
+ eventSet.push(t1.sliceGroup.slices[0]);
+
+ assert.strictEqual(eventSet.bounds.min, 1);
+ assert.strictEqual(eventSet.bounds.max, 4);
+ assert.deepEqual(eventSet.asSet(), new tr.model.EventSet(
+ t1.sliceGroup.slices[0]).asSet());
+
+ eventSet.push(t1.sliceGroup.slices[1]);
+ assert.strictEqual(eventSet.bounds.min, 1);
+ assert.strictEqual(eventSet.bounds.max, 6);
+ assert.deepEqual(eventSet.asSet(), new tr.model.EventSet(
+ [t1.sliceGroup.slices[0], t1.sliceGroup.slices[1]]).asSet());
+
+ eventSet.clear();
+ assert.strictEqual(eventSet.length, 0);
+ });
+
+ test('push_noEvents', function() {
+ const eventSet = new tr.model.EventSet();
+ eventSet.push();
+ assert.strictEqual(eventSet.length, 0);
+ });
+
+ test('push_oneEvent', function() {
+ const eventSet = new tr.model.EventSet();
+ eventSet.push({guid: 1});
+ assert.strictEqual(eventSet.length, 1);
+ });
+
+ test('push_multipleEvents', function() {
+ const eventSet = new tr.model.EventSet();
+ eventSet.push({guid: 1}, {guid: 2}, {guid: 3});
+ assert.strictEqual(eventSet.length, 3);
+ });
+
+ test('push_multiplePushes', function() {
+ const eventSet = new tr.model.EventSet();
+ eventSet.push({guid: 1}, {guid: 2}, {guid: 3});
+ eventSet.push({guid: 4}, {guid: 5}, {guid: 6});
+ assert.strictEqual(eventSet.length, 6);
+ });
+
+ test('push_noGuidThrows', function() {
+ const eventSet = new tr.model.EventSet();
+ assert.throws(() => eventSet.push({guid: 1}, {badItem: 2}, {guid: 3}),
+ 'Event must have a GUID');
+ });
+
+ test('iteration', function() {
+ const eventSet = new tr.model.EventSet([{guid: 1}, {guid: 2}, {guid: 3}]);
+
+ let expectedId = 1;
+ for (const event of eventSet) {
+ assert.strictEqual(event.guid, expectedId++);
+ }
+ });
+
+ test('uniqueContents', function() {
+ const sample1 = {guid: 1};
+ const sample2 = {guid: 2};
+
+ const eventSet = new tr.model.EventSet();
+
+ eventSet.push(sample1);
+ eventSet.push(sample2);
+ assert.strictEqual(eventSet.length, 2);
+
+ eventSet.push(sample1);
+ assert.strictEqual(eventSet.length, 2);
+ });
+
+ test('userFriendlyNameSingular', function() {
+ const model = new tr.Model();
+ const p1 = model.getOrCreateProcess(1);
+ const t1 = p1.getOrCreateThread(1);
+ t1.sliceGroup.pushSlice(
+ new tr.model.ThreadSlice('', 'a', 0, 1, {}, 3));
+ const selection = new tr.model.EventSet(t1.sliceGroup.slices[0]);
+ assert.isDefined(selection.userFriendlyName);
+ });
+
+ test('userFriendlyNamePlural', function() {
+ const model = new tr.Model();
+ const p1 = model.getOrCreateProcess(1);
+ const t1 = p1.getOrCreateThread(1);
+ t1.sliceGroup.pushSlice(
+ new tr.model.ThreadSlice('', 'a', 0, 1, {}, 3));
+ t1.sliceGroup.pushSlice(
+ new tr.model.ThreadSlice('', 'a', 0, 2, {}, 3));
+ const eventSet = new tr.model.EventSet([
+ t1.sliceGroup.slices[0],
+ t1.sliceGroup.slices[1]
+ ]);
+ assert.isDefined(eventSet.userFriendlyName);
+ });
+
+ test('userFriendlyNameMixedPlural', function() {
+ const model = new tr.Model();
+ const p1 = model.getOrCreateProcess(1);
+ const t1 = p1.getOrCreateThread(1);
+ t1.sliceGroup.pushSlice(
+ new tr.model.ThreadSlice('', 'a', 0, 1, {}, 3));
+ t1.sliceGroup.pushSlice(
+ new tr.model.ThreadSlice('', 'a', 0, 2, {}, 3));
+
+ const i10 = new tr.model.ObjectInstance(
+ {}, '0x1000', 'cat', 'name', 10);
+ const s10 = i10.addSnapshot(10, {foo: 1});
+
+ const eventSet = new tr.model.EventSet([
+ t1.sliceGroup.slices[0],
+ s10
+ ]);
+ assert.isDefined(eventSet.userFriendlyName);
+ });
+
+ test('groupEventsByTitle', function() {
+ const model = new tr.Model();
+ const p1 = model.getOrCreateProcess(1);
+ const t1 = p1.getOrCreateThread(1);
+ t1.sliceGroup.pushSlice(
+ new tr.model.ThreadSlice('', 'a', 0, 1, {}, 3));
+ t1.sliceGroup.pushSlice(
+ new tr.model.ThreadSlice('', 'a', 0, 2, {}, 3));
+ t1.sliceGroup.pushSlice(
+ new tr.model.ThreadSlice('', 'b', 0, 3, {}, 3));
+ const eventSet = new tr.model.EventSet([
+ t1.sliceGroup.slices[0],
+ t1.sliceGroup.slices[1],
+ t1.sliceGroup.slices[2]
+ ]);
+
+ const eventsByTitle = eventSet.getEventsOrganizedByTitle();
+ assert.strictEqual(2, Object.keys(eventsByTitle).length);
+ assert.sameMembers(eventsByTitle.a.toArray(),
+ [t1.sliceGroup.slices[0], t1.sliceGroup.slices[1]]);
+ assert.sameMembers(eventsByTitle.b.toArray(),
+ [t1.sliceGroup.slices[2]]);
+ });
+
+ test('groupEventsByCallback', function() {
+ const a1 = new tr.model.ThreadSlice('', 'a', 0, 1, {}, 3);
+ const a2 = new tr.model.ThreadSlice('', 'a', 0, 2, {}, 3);
+ const b1 = new tr.model.ThreadSlice('', 'b', 0, 1, {}, 3);
+ const eventSet = new tr.model.EventSet([a1, a2, b1]);
+ function getEventKey(event) {
+ return 's' + event.start;
+ }
+ const eventsByCallback = eventSet.getEventsOrganizedByCallback(getEventKey);
+ assert.strictEqual(2, Object.keys(eventsByCallback).length);
+ assert.sameMembers(eventsByCallback.s1.toArray(), [a1, b1]);
+ assert.sameMembers(eventsByCallback.s2.toArray(), [a2]);
+ });
+
+ test('groupEventsByBaseType', function() {
+ const a = new tr.model.ThreadSlice('', 'a', 0, 1, {}, 3);
+ const b = new tr.model.ThreadSlice('', 'b', 0, 1, {}, 3);
+ const c = new tr.model.AsyncSlice('', 'c', 0, 1, {}, 3);
+ const eventSet = new tr.model.EventSet([a, b, c]);
+ let eventsByBaseType = eventSet.getEventsOrganizedByBaseType(true);
+ assert.strictEqual(2, Object.keys(eventsByBaseType).length);
+ assert.sameMembers(eventsByBaseType.slice.toArray(), [a, b]);
+ assert.sameMembers(eventsByBaseType.asyncSlice.toArray(), [c]);
+
+ eventsByBaseType = eventSet.getEventsOrganizedByBaseType(false);
+ assert.isTrue(2 < Object.keys(eventsByBaseType).length);
+ assert.sameMembers(eventsByBaseType.slice.toArray(), [a, b]);
+ assert.sameMembers(eventsByBaseType.asyncSlice.toArray(), [c]);
+ for (const baseType in eventsByBaseType) {
+ if (baseType !== 'slice' && baseType !== 'asyncSlice') {
+ assert.strictEqual(0, eventsByBaseType[baseType].length);
+ }
+ }
+ });
+
+ test('intersectionIsEmpty1', function() {
+ const model = new tr.Model();
+ const p1 = model.getOrCreateProcess(1);
+ const t1 = p1.getOrCreateThread(1);
+ t1.sliceGroup.pushSlice(
+ new tr.model.ThreadSlice('', 'a', 0, 1, {}, 3));
+ t1.sliceGroup.pushSlice(
+ new tr.model.ThreadSlice('', 'a', 0, 2, {}, 3));
+ t1.sliceGroup.pushSlice(
+ new tr.model.ThreadSlice('', 'b', 0, 3, {}, 3));
+
+ const set1 = new tr.model.EventSet([
+ t1.sliceGroup.slices[0],
+ t1.sliceGroup.slices[1],
+ t1.sliceGroup.slices[2]
+ ]);
+ const set2 = new tr.model.EventSet([
+ t1.sliceGroup.slices[2]
+ ]);
+ assert.isFalse(set1.intersectionIsEmpty(set2));
+ });
+
+ test('intersectionIsNonEmpty2', function() {
+ const model = new tr.Model();
+ const p1 = model.getOrCreateProcess(1);
+ const t1 = p1.getOrCreateThread(1);
+ t1.sliceGroup.pushSlice(
+ new tr.model.ThreadSlice('', 'a', 0, 1, {}, 3));
+ t1.sliceGroup.pushSlice(
+ new tr.model.ThreadSlice('', 'a', 0, 2, {}, 3));
+ t1.sliceGroup.pushSlice(
+ new tr.model.ThreadSlice('', 'b', 0, 3, {}, 3));
+
+ const set1 = new tr.model.EventSet([
+ t1.sliceGroup.slices[0],
+ t1.sliceGroup.slices[1]
+ ]);
+ const set2 = new tr.model.EventSet([
+ t1.sliceGroup.slices[2]
+ ]);
+ assert.isTrue(set1.intersectionIsEmpty(set2));
+ });
+
+ test('equals', function() {
+ const model = new tr.Model();
+ const p1 = model.getOrCreateProcess(1);
+ const t1 = p1.getOrCreateThread(1);
+ t1.sliceGroup.pushSlice(
+ new tr.model.ThreadSlice('', 'a', 0, 1, {}, 3));
+ t1.sliceGroup.pushSlice(
+ new tr.model.ThreadSlice('', 'b', 0, 2, {}, 3));
+ t1.sliceGroup.pushSlice(
+ new tr.model.ThreadSlice('', 'c', 0, 3, {}, 3));
+ t1.sliceGroup.pushSlice(
+ new tr.model.ThreadSlice('', 'd', 0, 4, {}, 5));
+
+ const eventSet1 = new tr.model.EventSet([
+ t1.sliceGroup.slices[0],
+ t1.sliceGroup.slices[1],
+ t1.sliceGroup.slices[2]
+ ]);
+ const eventSet2 = new tr.model.EventSet([
+ t1.sliceGroup.slices[1],
+ t1.sliceGroup.slices[0],
+ t1.sliceGroup.slices[2]
+ ]);
+ const eventSet3 = new tr.model.EventSet([
+ t1.sliceGroup.slices[1],
+ t1.sliceGroup.slices[0],
+ t1.sliceGroup.slices[3]
+ ]);
+ const eventSet4 = new tr.model.EventSet([
+ t1.sliceGroup.slices[1],
+ t1.sliceGroup.slices[0]
+ ]);
+ assert.isTrue(eventSet1.equals(eventSet2));
+ assert.isFalse(eventSet1.equals(eventSet3));
+ assert.isFalse(eventSet1.equals(eventSet4));
+ });
+
+ test('filter', function() {
+ const m = newModel(function(m) {
+ const p = m.getOrCreateProcess(1);
+ const t = p.getOrCreateThread(1);
+
+ m.s0 = t.sliceGroup.pushSlice(newSliceEx(
+ { title: 's0', start: 0.0, duration: 1.0 }));
+ m.s1 = t.sliceGroup.pushSlice(newSliceEx(
+ { title: 's1', start: 0.0, duration: 1.0 }));
+ m.s2 = t.sliceGroup.pushSlice(newSliceEx(
+ { title: 's2', start: 0.0, duration: 1.0 }));
+
+ m.eventSet = new tr.model.EventSet([m.s0, m.s1, m.s2]);
+ });
+
+ const res = m.eventSet.filter(function(slice) {
+ return slice.title === 's0';
+ });
+
+ assert.isTrue(res.equals(new tr.model.EventSet([m.s0])));
+ });
+
+ test('toArray', function() {
+ const samples = [
+ {guid: 1},
+ {guid: 2}
+ ];
+ const eventSet = new tr.model.EventSet(samples);
+
+ assert.deepEqual(eventSet.toArray(), samples);
+ });
+
+ test('asDict', function() {
+ const model = new tr.Model();
+ const p1 = model.getOrCreateProcess(1);
+ const t1 = p1.getOrCreateThread(1);
+ t1.sliceGroup.pushSlice(
+ new tr.model.ThreadSlice('', 'a', 0, 1, {}, 3));
+ t1.sliceGroup.pushSlice(
+ new tr.model.ThreadSlice('', 'b', 0, 2, {}, 3));
+ t1.sliceGroup.pushSlice(
+ new tr.model.ThreadSlice('', 'c', 0, 3, {}, 3));
+ t1.sliceGroup.pushSlice(
+ new tr.model.ThreadSlice('', 'd', 0, 4, {}, 5));
+
+ const eventSet = new tr.model.EventSet([
+ t1.sliceGroup.slices[0],
+ t1.sliceGroup.slices[1],
+ t1.sliceGroup.slices[2]
+ ]);
+
+ assert.deepEqual(
+ {'events':
+ ['1.1.SliceGroup.0', '1.1.SliceGroup.1', '1.1.SliceGroup.2']},
+ eventSet.asDict());
+ });
+
+ test('immutableEmptySet', function() {
+ const s = tr.model.EventSet.IMMUTABLE_EMPTY_SET;
+ assert.lengthOf(s, 0);
+ assert.isTrue(s.bounds.isEmpty);
+
+ // Check that the iteration methods still work correctly.
+ function throwOnCall() {
+ throw new Error('This function should never be called!!!');
+ }
+ assert.deepEqual(s.map(throwOnCall), []);
+ s.forEach(throwOnCall);
+
+ // Check that the set is indeed immutable.
+ assert.throws(function() { s[0] = b; });
+ assert.throws(function() { s.push(b); });
+ assert.throws(function() { s.addEventSet(new tr.model.EventSet()); });
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/event_test.html b/chromium/third_party/catapult/tracing/tracing/model/event_test.html
new file mode 100644
index 00000000000..efbf26b1d13
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/event_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/model/alert.html">
+<link rel="import" href="/tracing/model/event.html">
+<link rel="import" href="/tracing/model/event_info.html">
+<link rel="import" href="/tracing/model/event_set.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const Alert = tr.model.Alert;
+ const Event = tr.model.Event;
+ const EventInfo = tr.model.EventInfo;
+ const EventSet = tr.model.EventSet;
+ const ImmutableEventSet = tr.model.ImmutableEventSet;
+
+ test('checkModelItem', function() {
+ const event = new Event;
+ assert.strictEqual(event.modelItem, event);
+ });
+
+ test('checkAssociatedAlerts', function() {
+ const event = new Event();
+ assert.strictEqual(event.associatedAlerts, EventSet.IMMUTABLE_EMPTY_SET);
+ assert.sameMembers(event.associatedAlerts.toArray(), []);
+
+ const info1 = new EventInfo('Critical', 'Critical alert!!!', []);
+ const alert1 = new Alert(info1, 7);
+ event.addAssociatedAlert(alert1);
+ assert.instanceOf(event.associatedAlerts, EventSet);
+ assert.sameMembers(event.associatedAlerts.toArray(), [alert1]);
+
+ const info2 = new EventInfo('Warning', 'Warning alert???', []);
+ const alert2 = new Alert(info2, 42);
+ event.addAssociatedAlert(alert2);
+ assert.instanceOf(event.associatedAlerts, EventSet);
+ assert.sameMembers(event.associatedAlerts.toArray(), [alert1, alert2]);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/flow_event.html b/chromium/third_party/catapult/tracing/tracing/model/flow_event.html
new file mode 100644
index 00000000000..9b85217d6f8
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/flow_event.html
@@ -0,0 +1,67 @@
+<!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/timed_event.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Provides the Flow class.
+ */
+tr.exportTo('tr.model', function() {
+ /**
+ * A Flow represents an interval of time plus parameters associated
+ * with that interval.
+ *
+ * @constructor
+ */
+ function FlowEvent(category, id, title, colorId, start, args, opt_duration) {
+ tr.model.TimedEvent.call(this, start);
+
+ this.category = category || '';
+ this.title = title;
+ this.colorId = colorId;
+ this.start = start;
+ this.args = args;
+
+ this.id = id;
+
+ this.startSlice = undefined;
+ this.endSlice = undefined;
+
+ this.startStackFrame = undefined;
+ this.endStackFrame = undefined;
+
+ if (opt_duration !== undefined) {
+ this.duration = opt_duration;
+ }
+ }
+
+ FlowEvent.prototype = {
+ __proto__: tr.model.TimedEvent.prototype,
+
+ get userFriendlyName() {
+ return 'Flow event named ' + this.title + ' at ' +
+ tr.b.Unit.byName.timeStampInMs.format(this.timestamp);
+ }
+ };
+
+ tr.model.EventRegistry.register(
+ FlowEvent,
+ {
+ name: 'flowEvent',
+ pluralName: 'flowEvents'
+ });
+
+ return {
+ FlowEvent,
+ };
+});
+</script>
+
diff --git a/chromium/third_party/catapult/tracing/tracing/model/frame.html b/chromium/third_party/catapult/tracing/tracing/model/frame.html
new file mode 100644
index 00000000000..6d1051a603d
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/frame.html
@@ -0,0 +1,96 @@
+<!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_scheme.html">
+<link rel="import" href="/tracing/base/math/statistics.html">
+<link rel="import" href="/tracing/model/event.html">
+<link rel="import" href="/tracing/model/event_set.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Class describing rendered frames.
+ *
+ * Because a frame is produced by multiple threads, it does not inherit from
+ * TimedEvent, and has no duration.
+ */
+tr.exportTo('tr.model', function() {
+ const ColorScheme = tr.b.ColorScheme;
+ const Statistics = tr.b.math.Statistics;
+
+ const FRAME_PERF_CLASS = {
+ GOOD: 'good',
+ BAD: 'bad',
+ TERRIBLE: 'terrible',
+ NEUTRAL: 'generic_work'
+ };
+
+ /**
+ * @constructor
+ * @param {Array} associatedEvents Selection of events composing the frame.
+ * @param {Array} threadTimeRanges Array of {thread, start, end}
+ * for each thread, describing the critical path of the frame.
+ */
+ function Frame(associatedEvents, threadTimeRanges, opt_args) {
+ tr.model.Event.call(this);
+
+ this.threadTimeRanges = threadTimeRanges;
+ this.associatedEvents = new tr.model.EventSet(associatedEvents);
+ this.args = opt_args || {};
+
+ this.title = 'Frame';
+ this.start = Statistics.min(
+ threadTimeRanges, function(x) { return x.start; });
+ this.end = Statistics.max(
+ threadTimeRanges, function(x) { return x.end; });
+ this.totalDuration = Statistics.sum(
+ threadTimeRanges, function(x) { return x.end - x.start; });
+
+ this.perfClass = FRAME_PERF_CLASS.NEUTRAL;
+ }
+
+ Frame.prototype = {
+ __proto__: tr.model.Event.prototype,
+
+ set perfClass(perfClass) {
+ this.colorId = ColorScheme.getColorIdForReservedName(perfClass);
+ this.perfClass_ = perfClass;
+ },
+
+ get perfClass() {
+ return this.perfClass_;
+ },
+
+ shiftTimestampsForward(amount) {
+ this.start += amount;
+ this.end += amount;
+
+ for (let i = 0; i < this.threadTimeRanges.length; i++) {
+ this.threadTimeRanges[i].start += amount;
+ this.threadTimeRanges[i].end += amount;
+ }
+ },
+
+ addBoundsToRange(range) {
+ range.addValue(this.start);
+ range.addValue(this.end);
+ }
+ };
+
+ tr.model.EventRegistry.register(
+ Frame,
+ {
+ name: 'frame',
+ pluralName: 'frames'
+ });
+
+ return {
+ Frame,
+ FRAME_PERF_CLASS,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/global_memory_dump.html b/chromium/third_party/catapult/tracing/tracing/model/global_memory_dump.html
new file mode 100644
index 00000000000..058f52f14e9
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/global_memory_dump.html
@@ -0,0 +1,849 @@
+<!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/model/container_memory_dump.html">
+<link rel="import" href="/tracing/model/event_registry.html">
+<link rel="import" href="/tracing/model/memory_allocator_dump.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Provides the GlobalMemoryDump class.
+ */
+tr.exportTo('tr.model', function() {
+ /**
+ * The GlobalMemoryDump represents a simultaneous memory dump of all
+ * processes.
+ * @constructor
+ */
+ function GlobalMemoryDump(model, start) {
+ tr.model.ContainerMemoryDump.call(this, start);
+ this.model = model;
+ this.processMemoryDumps = {};
+ }
+
+ // Size numeric names.
+ const SIZE_NUMERIC_NAME = tr.model.MemoryAllocatorDump.SIZE_NUMERIC_NAME;
+ const EFFECTIVE_SIZE_NUMERIC_NAME =
+ tr.model.MemoryAllocatorDump.EFFECTIVE_SIZE_NUMERIC_NAME;
+
+ // 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;
+
+ function getSize(dump) {
+ const numeric = dump.numerics[SIZE_NUMERIC_NAME];
+ if (numeric === undefined) return 0;
+ return numeric.value;
+ }
+
+ function hasSize(dump) {
+ return dump.numerics[SIZE_NUMERIC_NAME] !== undefined;
+ }
+
+ function optional(value, defaultValue) {
+ if (value === undefined) return defaultValue;
+ return value;
+ }
+
+ GlobalMemoryDump.prototype = {
+ __proto__: tr.model.ContainerMemoryDump.prototype,
+
+ get userFriendlyName() {
+ return 'Global memory dump at ' +
+ tr.b.Unit.byName.timeStampInMs.format(this.start);
+ },
+
+ get containerName() {
+ return 'global space';
+ },
+
+ finalizeGraph() {
+ // 1. Transitively remove weak memory allocator dumps and all their
+ // owners and descendants from the model. This must be performed before
+ // any other steps.
+ this.removeWeakDumps();
+
+ // 2. Add ownership links from tracing MADs to descendants of malloc or
+ // winheap MADs so that tracing would be automatically discounted from
+ // them later (step 3).
+ this.setUpTracingOverheadOwnership();
+
+ // 3. Aggregate all other numerics of all MADs (*excluding* sizes and
+ // effective sizes) and propagate numerics from global MADs to their
+ // owners (*including* sizes and effective sizes). This step must be
+ // carried out before the sizes of all MADs are calculated (step 3).
+ // Otherwise, the propagated sizes of all MADs would not be aggregated.
+ this.aggregateNumerics();
+
+ // 4. Calculate the sizes of all memory allocator dumps (MADs). This step
+ // requires that the memory allocator dump graph has been finalized (step
+ // 1) and numerics were propagated from global MADs (step 2). Subsequent
+ // modifications of the graph will most likely break the calculation
+ // invariants.
+ this.calculateSizes();
+
+ // 5. Calculate the effective sizes of all MADs. This step requires that
+ // the sizes of all MADs have already been calculated (step 3).
+ this.calculateEffectiveSizes();
+
+ // 6. Discount tracing from VM regions stats. This steps requires that
+ // resident sizes (step 2) and sizes (step 3) of the tracing MADs have
+ // already been calculated.
+ this.discountTracingOverheadFromVmRegions();
+
+ // 7. The above steps (especially steps 1 and 3) can create new memory
+ // allocator dumps, so we force rebuilding the memory allocator dump
+ // indices of all container memory dumps.
+ this.forceRebuildingMemoryAllocatorDumpByFullNameIndices();
+ },
+
+ removeWeakDumps() {
+ // Mark all transitive owners and children of weak memory allocator dumps
+ // as weak.
+ this.traverseAllocatorDumpsInDepthFirstPreOrder(function(dump) {
+ if (dump.weak) return;
+ if ((dump.owns !== undefined && dump.owns.target.weak) ||
+ (dump.parent !== undefined && dump.parent.weak)) {
+ dump.weak = true;
+ }
+ });
+
+ function removeWeakDumpsFromListRecursively(dumps) {
+ tr.b.inPlaceFilter(dumps, function(dump) {
+ if (dump.weak) {
+ // The dump is weak, so remove it. This will implicitly remove all
+ // its descendants, which are also weak due to the initial marking
+ // step.
+ return false;
+ }
+
+ // This dump is non-weak, so keep it. Recursively remove its weak
+ // descendants and ownership links from weak dumps instead.
+ removeWeakDumpsFromListRecursively(dump.children);
+ tr.b.inPlaceFilter(dump.ownedBy, function(ownershipLink) {
+ return !ownershipLink.source.weak;
+ });
+
+ return true;
+ });
+ }
+
+ this.iterateContainerDumps(function(containerDump) {
+ const memoryAllocatorDumps = containerDump.memoryAllocatorDumps;
+ if (memoryAllocatorDumps !== undefined) {
+ removeWeakDumpsFromListRecursively(memoryAllocatorDumps);
+ }
+ });
+ },
+
+ /**
+ * Calculate the size of all memory allocator dumps in the dump graph.
+ *
+ * The size refers to the allocated size of a (sub)component. It is a
+ * natural extension of the optional size numeric provided by
+ * MemoryAllocatorDump(s):
+ *
+ * - If a MAD provides a size numeric, then its size is assumed to be
+ * equal to it.
+ * - If a MAD does not provide a size numeric, then its size is assumed
+ * to be the maximum of (1) the size of the largest owner of the MAD
+ * and (2) the aggregated size of the MAD's children.
+ *
+ * Metric motivation: "How big is a (sub)system?"
+ *
+ * Please refer to the Memory Dump Graph Metric Calculation design document
+ * for more details (https://goo.gl/fKg0dt).
+ */
+ calculateSizes() {
+ this.traverseAllocatorDumpsInDepthFirstPostOrder(
+ this.calculateMemoryAllocatorDumpSize_.bind(this));
+ },
+
+ /**
+ * Calculate the size of the given MemoryAllocatorDump. This method assumes
+ * that the size of both the children and owners of the dump has already
+ * been calculated.
+ */
+ calculateMemoryAllocatorDumpSize_(dump) {
+ // This flag becomes true if the size numeric of the current dump should
+ // be defined, i.e. if (1) the current dump's size numeric is defined,
+ // (2) the size of at least one of its children is defined or (3) the
+ // size of at least one of its owners is defined.
+ let shouldDefineSize = false;
+
+ // This helper function returns the value of the size numeric of the
+ // given dependent memory allocator dump. If the numeric is defined, the
+ // shouldDefineSize flag above is also set to true (because condition
+ // (2) or (3) is satisfied). Otherwise, zero is returned (and the flag is
+ // left unchanged).
+ function getDependencySize(dependencyDump) {
+ const numeric = dependencyDump.numerics[SIZE_NUMERIC_NAME];
+ if (numeric === undefined) return 0;
+ shouldDefineSize = true;
+ return numeric.value;
+ }
+
+ // 1. Get the size provided by the dump. If present, define a function
+ // for checking dependent size consistency (a dump must always be bigger
+ // than all its children aggregated together and/or its largest owner).
+ const sizeNumeric = dump.numerics[SIZE_NUMERIC_NAME];
+ let size = 0;
+ let checkDependencySizeIsConsistent = function() { /* no-op */ };
+ if (sizeNumeric !== undefined) {
+ size = sizeNumeric.value;
+ shouldDefineSize = true;
+ if (sizeNumeric.unit !== tr.b.Unit.byName.sizeInBytes_smallerIsBetter) {
+ this.model.importWarning({
+ type: 'memory_dump_parse_error',
+ message: 'Invalid unit of \'size\' numeric of memory allocator ' +
+ 'dump ' + dump.quantifiedName + ': ' +
+ sizeNumeric.unit.unitName + '.'
+ });
+ }
+ checkDependencySizeIsConsistent = function(
+ dependencySize, dependencyInfoType, dependencyName) {
+ if (size >= dependencySize) return;
+ this.model.importWarning({
+ type: 'memory_dump_parse_error',
+ message: 'Size provided by memory allocator dump \'' +
+ dump.fullName + '\'' +
+ tr.b.Unit.byName.sizeInBytes.format(size) +
+ ') is less than ' + dependencyName + ' (' +
+ tr.b.Unit.byName.sizeInBytes.format(dependencySize) + ').'
+ });
+ dump.infos.push({
+ type: dependencyInfoType,
+ providedSize: size,
+ dependencySize
+ });
+ }.bind(this);
+ }
+
+ // 2. Aggregate size of children. The recursive function traverses all
+ // descendants and ensures that double-counting due to ownership within a
+ // subsystem is avoided.
+ let aggregatedChildrenSize = 0;
+ // Owned child dump name -> (Owner child dump name -> overlapping size).
+ const allOverlaps = {};
+ dump.children.forEach(function(childDump) {
+ function aggregateDescendantDump(descendantDump) {
+ // Don't count this descendant dump if it owns another descendant of
+ // the current dump (would cause double-counting).
+ const ownedDumpLink = descendantDump.owns;
+ if (ownedDumpLink !== undefined &&
+ ownedDumpLink.target.isDescendantOf(dump)) {
+ // If the target owned dump is a descendant of a *different* child
+ // of the the current dump (i.e. not childDump), then we remember
+ // the ownership so that we could explain why the size of the
+ // current dump is not equal to the sum of its children.
+ let ownedChildDump = ownedDumpLink.target;
+ while (ownedChildDump.parent !== dump) {
+ ownedChildDump = ownedChildDump.parent;
+ }
+ if (childDump !== ownedChildDump) {
+ const ownedBySiblingSize = getDependencySize(descendantDump);
+ if (ownedBySiblingSize > 0) {
+ const previousTotalOwnedBySiblingSize =
+ ownedChildDump.ownedBySiblingSizes.get(childDump) || 0;
+ const updatedTotalOwnedBySiblingSize =
+ previousTotalOwnedBySiblingSize + ownedBySiblingSize;
+ ownedChildDump.ownedBySiblingSizes.set(
+ childDump, updatedTotalOwnedBySiblingSize);
+ }
+ }
+ return;
+ }
+
+ // If this descendant dump is a leaf node, add its size to the
+ // aggregated size.
+ if (descendantDump.children.length === 0) {
+ aggregatedChildrenSize += getDependencySize(descendantDump);
+ return;
+ }
+
+ // If this descendant dump is an intermediate node, recurse down into
+ // its children. Note that the dump's size is NOT added because it is
+ // an aggregate of its children (would cause double-counting).
+ descendantDump.children.forEach(aggregateDescendantDump);
+ }
+ aggregateDescendantDump(childDump);
+ });
+ checkDependencySizeIsConsistent(
+ aggregatedChildrenSize,
+ PROVIDED_SIZE_LESS_THAN_AGGREGATED_CHILDREN,
+ 'the aggregated size of its children');
+
+ // 3. Calculate the largest owner size.
+ let largestOwnerSize = 0;
+ dump.ownedBy.forEach(function(ownershipLink) {
+ const owner = ownershipLink.source;
+ const ownerSize = getDependencySize(owner);
+ largestOwnerSize = Math.max(largestOwnerSize, ownerSize);
+ });
+ checkDependencySizeIsConsistent(
+ largestOwnerSize,
+ PROVIDED_SIZE_LESS_THAN_LARGEST_OWNER,
+ 'the size of its largest owner');
+
+ // If neither the dump nor any of its dependencies (children and owners)
+ // provide a size, do NOT add a zero size numeric.
+ if (!shouldDefineSize) {
+ // The rest of the pipeline relies on size being either a valid
+ // Scalar, or undefined.
+ delete dump.numerics[SIZE_NUMERIC_NAME];
+ return;
+ }
+
+ // A dump must always be bigger than all its children aggregated
+ // together and/or its largest owner.
+ size = Math.max(size, aggregatedChildrenSize, largestOwnerSize);
+
+ dump.numerics[SIZE_NUMERIC_NAME] = new tr.b.Scalar(
+ tr.b.Unit.byName.sizeInBytes_smallerIsBetter, size);
+
+ // Add a virtual child to make up for extra size of the dump with
+ // respect to its children (if applicable).
+ if (aggregatedChildrenSize < size &&
+ dump.children !== undefined && dump.children.length > 0) {
+ const virtualChild = new tr.model.MemoryAllocatorDump(
+ dump.containerMemoryDump, dump.fullName + '/<unspecified>');
+ virtualChild.parent = dump;
+ dump.children.unshift(virtualChild);
+ virtualChild.numerics[SIZE_NUMERIC_NAME] = new tr.b.Scalar(
+ tr.b.Unit.byName.sizeInBytes_smallerIsBetter,
+ size - aggregatedChildrenSize);
+ }
+ },
+
+ /**
+ * Calculate the effective size of all memory allocator dumps in the dump
+ * graph.
+ *
+ * The effective size refers to the amount of memory a particular component
+ * is using/consuming. In other words, every (reported) byte of used memory
+ * is uniquely attributed to exactly one component. Consequently, unlike
+ * size, effective size is cumulative, i.e. the sum of the effective sizes
+ * of (top-level) components is equal to the total amount of (reported)
+ * used memory.
+ *
+ * Metric motivation: "How much memory does a (sub)system use?" or "For how
+ * much memory should a (sub)system be 'charged'?"
+ *
+ * Please refer to the Memory Dump Graph Metric Calculation design document
+ * for more details (https://goo.gl/fKg0dt).
+ *
+ * This method assumes that the size of all contained memory allocator
+ * dumps has already been calculated [see calculateSizes()].
+ */
+ calculateEffectiveSizes() {
+ // 1. Calculate not-owned and not-owning sub-sizes of all MADs
+ // (depth-first post-order traversal).
+ this.traverseAllocatorDumpsInDepthFirstPostOrder(
+ this.calculateDumpSubSizes_.bind(this));
+
+ // 2. Calculate owned and owning coefficients of owned and owner MADs
+ // respectively (arbitrary traversal).
+ this.traverseAllocatorDumpsInDepthFirstPostOrder(
+ this.calculateDumpOwnershipCoefficient_.bind(this));
+
+ // 3. Calculate cumulative owned and owning coefficients of all MADs
+ // (depth-first pre-order traversal).
+ this.traverseAllocatorDumpsInDepthFirstPreOrder(
+ this.calculateDumpCumulativeOwnershipCoefficient_.bind(this));
+
+ // 4. Calculate the effective sizes of all MADs (depth-first post-order
+ // traversal).
+ this.traverseAllocatorDumpsInDepthFirstPostOrder(
+ this.calculateDumpEffectiveSize_.bind(this));
+ },
+
+ /**
+ * Calculate not-owned and not-owning sub-sizes of a memory allocator dump
+ * from its children's (sub-)sizes.
+ *
+ * Not-owned sub-size refers to the aggregated memory of all children which
+ * is not owned by other MADs. Conversely, not-owning sub-size is the
+ * aggregated memory of all children which do not own another MAD. The
+ * diagram below illustrates these two concepts:
+ *
+ * ROOT 1 ROOT 2
+ * size: 4 size: 5
+ * not-owned sub-size: 4 not-owned sub-size: 1 (!)
+ * not-owning sub-size: 0 (!) not-owning sub-size: 5
+ *
+ * ^ ^
+ * | |
+ *
+ * PARENT 1 ===== owns =====> PARENT 2
+ * size: 4 size: 5
+ * not-owned sub-size: 4 not-owned sub-size: 5
+ * not-owning sub-size: 4 not-owning sub-size: 5
+ *
+ * ^ ^
+ * | |
+ *
+ * CHILD 1 CHILD 2
+ * size [given]: 4 size [given]: 5
+ * not-owned sub-size: 4 not-owned sub-size: 5
+ * not-owning sub-size: 4 not-owning sub-size: 5
+ *
+ * This method assumes that (1) the size of the dump, its children, and its
+ * owners [see calculateSizes()] and (2) the not-owned and not-owning
+ * sub-sizes of both the children and owners of the dump have already been
+ * calculated [depth-first post-order traversal].
+ */
+ calculateDumpSubSizes_(dump) {
+ // Completely skip dumps with undefined size.
+ if (!hasSize(dump)) return;
+
+ // If the dump is a leaf node, then both sub-sizes are equal to the size.
+ if (dump.children === undefined || dump.children.length === 0) {
+ const size = getSize(dump);
+ dump.notOwningSubSize_ = size;
+ dump.notOwnedSubSize_ = size;
+ return;
+ }
+
+ // Calculate this dump's not-owning sub-size by summing up the not-owning
+ // sub-sizes of children MADs which do not own another MAD.
+ let notOwningSubSize = 0;
+ dump.children.forEach(function(childDump) {
+ if (childDump.owns !== undefined) return;
+ notOwningSubSize += optional(childDump.notOwningSubSize_, 0);
+ });
+ dump.notOwningSubSize_ = notOwningSubSize;
+
+ // Calculate this dump's not-owned sub-size.
+ let notOwnedSubSize = 0;
+ dump.children.forEach(function(childDump) {
+ // If the child dump is not owned, then add its not-owned sub-size.
+ if (childDump.ownedBy.length === 0) {
+ notOwnedSubSize += optional(childDump.notOwnedSubSize_, 0);
+ return;
+ }
+ // If the child dump is owned, then add the difference between its size
+ // and the largest owner.
+ let largestChildOwnerSize = 0;
+ childDump.ownedBy.forEach(function(ownershipLink) {
+ largestChildOwnerSize = Math.max(
+ largestChildOwnerSize, getSize(ownershipLink.source));
+ });
+ notOwnedSubSize += getSize(childDump) - largestChildOwnerSize;
+ });
+ dump.notOwnedSubSize_ = notOwnedSubSize;
+ },
+
+ /**
+ * Calculate owned and owning coefficients of a memory allocator dump and
+ * its owners.
+ *
+ * The owning coefficient refers to the proportion of a dump's not-owning
+ * sub-size which is attributed to the dump (only relevant to owning MADs).
+ * Conversely, the owned coefficient is the proportion of a dump's
+ * not-owned sub-size, which is attributed to it (only relevant to owned
+ * MADs).
+ *
+ * The not-owned size of the owned dump is split among its owners in the
+ * order of the ownership importance as demonstrated by the following
+ * example:
+ *
+ * memory allocator dumps
+ * OWNED OWNER1 OWNER2 OWNER3 OWNER4
+ * not-owned sub-size [given] 10 - - - -
+ * not-owning sub-size [given] - 6 7 5 8
+ * importance [given] - 2 2 1 0
+ * attributed not-owned sub-size 2 - - - -
+ * attributed not-owning sub-size - 3 4 0 1
+ * owned coefficient 2/10 - - - -
+ * owning coefficient - 3/6 4/7 0/5 1/8
+ *
+ * Explanation: Firstly, 6 bytes are split equally among OWNER1 and OWNER2
+ * (highest importance). OWNER2 owns one more byte, so its attributed
+ * not-owning sub-size is 6/2 + 1 = 4 bytes. OWNER3 is attributed no size
+ * because it is smaller than the owners with higher priority. However,
+ * OWNER4 is larger, so it's attributed the difference 8 - 7 = 1 byte.
+ * Finally, 2 bytes remain unattributed and are hence kept in the OWNED
+ * dump as attributed not-owned sub-size. The coefficients are then
+ * directly calculated as fractions of the sub-sizes and corresponding
+ * attributed sub-sizes.
+ *
+ * Note that we always assume that all ownerships of a dump overlap (e.g.
+ * OWNER3 is subsumed by both OWNER1 and OWNER2). Hence, the table could
+ * be alternatively represented as follows:
+ *
+ * owned memory range
+ * 0 1 2 3 4 5 6 7 8 9 10
+ * Priority 2 | OWNER1 + OWNER2 (split) | OWNER2 |
+ * Priority 1 | (already attributed) |
+ * Priority 0 | - - - (already attributed) - - - | OWNER4 |
+ * Remainder | - - - - - (already attributed) - - - - - - | OWNED |
+ *
+ * This method assumes that (1) the size of the dump [see calculateSizes()]
+ * and (2) the not-owned size of the dump and not-owning sub-sizes of its
+ * owners [see the first step of calculateEffectiveSizes()] have already
+ * been calculated. Note that the method doesn't make any assumptions about
+ * the order in which dumps are visited.
+ */
+ calculateDumpOwnershipCoefficient_(dump) {
+ // Completely skip dumps with undefined size.
+ if (!hasSize(dump)) return;
+
+ // We only need to consider owned dumps.
+ if (dump.ownedBy.length === 0) return;
+
+ // Sort the owners in decreasing order of ownership importance and
+ // increasing order of not-owning sub-size (in case of equal importance).
+ const owners = dump.ownedBy.map(function(ownershipLink) {
+ return {
+ dump: ownershipLink.source,
+ importance: optional(ownershipLink.importance, 0),
+ notOwningSubSize: optional(ownershipLink.source.notOwningSubSize_, 0)
+ };
+ });
+ owners.sort(function(a, b) {
+ if (a.importance === b.importance) {
+ return a.notOwningSubSize - b.notOwningSubSize;
+ }
+ return b.importance - a.importance;
+ });
+
+ // Loop over the list of owners and distribute the owned dump's not-owned
+ // sub-size among them according to their ownership importance and
+ // not-owning sub-size.
+ let currentImportanceStartPos = 0;
+ let alreadyAttributedSubSize = 0;
+ while (currentImportanceStartPos < owners.length) {
+ const currentImportance = owners[currentImportanceStartPos].importance;
+
+ // Find the position of the first owner with lower priority.
+ let nextImportanceStartPos = currentImportanceStartPos + 1;
+ while (nextImportanceStartPos < owners.length &&
+ owners[nextImportanceStartPos].importance ===
+ currentImportance) {
+ nextImportanceStartPos++;
+ }
+
+ // Visit the owners with the same importance in increasing order of
+ // not-owned sub-size, split the owned memory among them appropriately,
+ // and calculate their owning coefficients.
+ let attributedNotOwningSubSize = 0;
+ for (let pos = currentImportanceStartPos; pos < nextImportanceStartPos;
+ pos++) {
+ const owner = owners[pos];
+ const notOwningSubSize = owner.notOwningSubSize;
+ if (notOwningSubSize > alreadyAttributedSubSize) {
+ attributedNotOwningSubSize +=
+ (notOwningSubSize - alreadyAttributedSubSize) /
+ (nextImportanceStartPos - pos);
+ alreadyAttributedSubSize = notOwningSubSize;
+ }
+
+ let owningCoefficient = 0;
+ if (notOwningSubSize !== 0) {
+ owningCoefficient = attributedNotOwningSubSize / notOwningSubSize;
+ }
+ owner.dump.owningCoefficient_ = owningCoefficient;
+ }
+
+ currentImportanceStartPos = nextImportanceStartPos;
+ }
+
+ // Attribute the remainder of the owned dump's not-owned sub-size to
+ // the dump itself and calculate its owned coefficient.
+ const notOwnedSubSize = optional(dump.notOwnedSubSize_, 0);
+ const remainderSubSize = notOwnedSubSize - alreadyAttributedSubSize;
+ let ownedCoefficient = 0;
+ if (notOwnedSubSize !== 0) {
+ ownedCoefficient = remainderSubSize / notOwnedSubSize;
+ }
+ dump.ownedCoefficient_ = ownedCoefficient;
+ },
+
+ /**
+ * Calculate cumulative owned and owning coefficients of a memory allocator
+ * dump from its (non-cumulative) owned and owning coefficients and the
+ * cumulative coefficients of its parent and/or owned dump.
+ *
+ * The cumulative coefficients represent the total effect of all
+ * (non-strict) ancestor ownerships on a memory allocator dump. The
+ * cumulative owned coefficient of a MAD can be calculated simply as:
+ *
+ * cumulativeOwnedC(M) = ownedC(M) * cumulativeOwnedC(parent(M))
+ *
+ * This reflects the assumption that if a parent of a child MAD is
+ * (partially) owned, then the parent's owner also indirectly owns (a part
+ * of) the child MAD.
+ *
+ * The cumulative owning coefficient of a MAD depends on whether the MAD
+ * owns another dump:
+ *
+ * [if M doesn't own another MAD]
+ * / cumulativeOwningC(parent(M))
+ * cumulativeOwningC(M) =
+ * \ [if M owns another MAD]
+ * owningC(M) * cumulativeOwningC(owned(M))
+ *
+ * The reasoning behind the first case is similar to the one for cumulative
+ * owned coefficient above. The only difference is that we don't need to
+ * include the dump's (non-cumulative) owning coefficient because it is
+ * implicitly 1.
+ *
+ * The formula for the second case is derived as follows: Since the MAD
+ * owns another dump, its memory is not included in its parent's not-owning
+ * sub-size and hence shouldn't be affected by the parent's corresponding
+ * cumulative coefficient. Instead, the MAD indirectly owns everything
+ * owned by its owned dump (and so it should be affected by the
+ * corresponding coefficient).
+ *
+ * Note that undefined coefficients (and coefficients of non-existent
+ * dumps) are implicitly assumed to be 1.
+ *
+ * This method assumes that (1) the size of the dump [see calculateSizes()],
+ * (2) the (non-cumulative) owned and owning coefficients of the dump [see
+ * the second step of calculateEffectiveSizes()], and (3) the cumulative
+ * coefficients of the dump's parent and owned MADs (if present)
+ * [depth-first pre-order traversal] have already been calculated.
+ */
+ calculateDumpCumulativeOwnershipCoefficient_(dump) {
+ // Completely skip dumps with undefined size.
+ if (!hasSize(dump)) return;
+
+ let cumulativeOwnedCoefficient = optional(dump.ownedCoefficient_, 1);
+ const parent = dump.parent;
+ if (dump.parent !== undefined) {
+ cumulativeOwnedCoefficient *= dump.parent.cumulativeOwnedCoefficient_;
+ }
+ dump.cumulativeOwnedCoefficient_ = cumulativeOwnedCoefficient;
+
+ let cumulativeOwningCoefficient;
+ if (dump.owns !== undefined) {
+ cumulativeOwningCoefficient = dump.owningCoefficient_ *
+ dump.owns.target.cumulativeOwningCoefficient_;
+ } else if (dump.parent !== undefined) {
+ cumulativeOwningCoefficient = dump.parent.cumulativeOwningCoefficient_;
+ } else {
+ cumulativeOwningCoefficient = 1;
+ }
+ dump.cumulativeOwningCoefficient_ = cumulativeOwningCoefficient;
+ },
+
+ /**
+ * Calculate the effective size of a memory allocator dump.
+ *
+ * In order to simplify the (already complex) calculation, we use the fact
+ * that effective size is cumulative (unlike regular size), i.e. the
+ * effective size of a non-leaf node is equal to the sum of effective sizes
+ * of its children. The effective size of a leaf MAD is calculated as:
+ *
+ * effectiveSize(M) = size(M) * cumulativeOwningC(M) * cumulativeOwnedC(M)
+ *
+ * This method assumes that (1) the size of the dump and its children [see
+ * calculateSizes()] and (2) the cumulative owning and owned coefficients
+ * of the dump (if it's a leaf node) [see the third step of
+ * calculateEffectiveSizes()] or the effective sizes of its children (if
+ * it's a non-leaf node) [depth-first post-order traversal] have already
+ * been calculated.
+ */
+ calculateDumpEffectiveSize_(dump) {
+ // Completely skip dumps with undefined size. As a result, each dump will
+ // have defined effective size if and only if it has defined size.
+ if (!hasSize(dump)) {
+ // The rest of the pipeline relies on effective size being either a
+ // valid Scalar, or undefined.
+ delete dump.numerics[EFFECTIVE_SIZE_NUMERIC_NAME];
+ return;
+ }
+
+ let effectiveSize;
+ if (dump.children === undefined || dump.children.length === 0) {
+ // Leaf dump.
+ effectiveSize = getSize(dump) * dump.cumulativeOwningCoefficient_ *
+ dump.cumulativeOwnedCoefficient_;
+ } else {
+ // Non-leaf dump.
+ effectiveSize = 0;
+ dump.children.forEach(function(childDump) {
+ if (!hasSize(childDump)) return;
+ effectiveSize +=
+ childDump.numerics[EFFECTIVE_SIZE_NUMERIC_NAME].value;
+ });
+ }
+ dump.numerics[EFFECTIVE_SIZE_NUMERIC_NAME] = new tr.b.Scalar(
+ tr.b.Unit.byName.sizeInBytes_smallerIsBetter, effectiveSize);
+ },
+
+ aggregateNumerics() {
+ // 1. Aggregate numerics in this global memory dump.
+ this.iterateRootAllocatorDumps(function(dump) {
+ dump.aggregateNumericsRecursively(this.model);
+ });
+
+ // 2. Propagate numerics and diagnostics from global memory allocator
+ // dumps to their owners.
+ this.iterateRootAllocatorDumps(
+ this.propagateNumericsAndDiagnosticsRecursively);
+
+ // 3. Aggregate numerics in the associated process memory dumps.
+ for (const processMemoryDump of Object.values(this.processMemoryDumps)) {
+ processMemoryDump.iterateRootAllocatorDumps(function(dump) {
+ dump.aggregateNumericsRecursively(this.model);
+ }, this);
+ }
+ },
+
+ propagateNumericsAndDiagnosticsRecursively(globalAllocatorDump) {
+ ['numerics', 'diagnostics'].forEach(function(field) {
+ for (const [name, value] of
+ Object.entries(globalAllocatorDump[field])) {
+ globalAllocatorDump.ownedBy.forEach(function(ownershipLink) {
+ const processAllocatorDump = ownershipLink.source;
+ if (processAllocatorDump[field][name] !== undefined) {
+ // Numerics and diagnostics provided by process memory allocator
+ // dumps themselves have precedence over numerics and diagnostics
+ // propagated from global memory allocator dumps.
+ return;
+ }
+ processAllocatorDump[field][name] = value;
+ });
+ }
+ });
+
+ // Recursively propagate numerics from all child memory allocator dumps.
+ globalAllocatorDump.children.forEach(
+ this.propagateNumericsAndDiagnosticsRecursively, this);
+ },
+
+ setUpTracingOverheadOwnership() {
+ for (const dump of Object.values(this.processMemoryDumps)) {
+ dump.setUpTracingOverheadOwnership(this.model);
+ }
+ },
+
+ discountTracingOverheadFromVmRegions() {
+ // TODO(petrcermak): Consider factoring out all the finalization code and
+ // constants to a single file.
+ for (const dump of Object.values(this.processMemoryDumps)) {
+ dump.discountTracingOverheadFromVmRegions(this.model);
+ }
+ },
+
+ forceRebuildingMemoryAllocatorDumpByFullNameIndices() {
+ this.iterateContainerDumps(function(containerDump) {
+ containerDump.forceRebuildingMemoryAllocatorDumpByFullNameIndex();
+ });
+ },
+
+ iterateContainerDumps(fn) {
+ fn.call(this, this);
+ for (const processDump of Object.values(this.processMemoryDumps)) {
+ fn.call(this, processDump);
+ }
+ },
+
+ iterateAllRootAllocatorDumps(fn) {
+ this.iterateContainerDumps(function(containerDump) {
+ containerDump.iterateRootAllocatorDumps(fn, this);
+ });
+ },
+
+ /**
+ * Traverse the memory dump graph in a depth first post-order, i.e.
+ * children and owners of a memory allocator dump are visited before the
+ * dump itself. This method will throw an exception if the graph contains
+ * a cycle.
+ */
+ traverseAllocatorDumpsInDepthFirstPostOrder(fn) {
+ const visitedDumps = new WeakSet();
+ const openDumps = new WeakSet();
+
+ function visit(dump) {
+ if (visitedDumps.has(dump)) return;
+
+ if (openDumps.has(dump)) {
+ throw new Error(dump.userFriendlyName + ' contains a cycle');
+ }
+ openDumps.add(dump);
+
+ // Visit owners before the dumps they own.
+ dump.ownedBy.forEach(function(ownershipLink) {
+ visit.call(this, ownershipLink.source);
+ }, this);
+
+ // Visit children before parents.
+ dump.children.forEach(visit, this);
+
+ // Actually visit the current memory allocator dump.
+ fn.call(this, dump);
+ visitedDumps.add(dump);
+
+ openDumps.delete(dump);
+ }
+
+ this.iterateAllRootAllocatorDumps(visit);
+ },
+
+ /**
+ * Traverse the memory dump graph in a depth first pre-order, i.e.
+ * children and owners of a memory allocator dump are visited after the
+ * dump itself. This method will not visit some dumps if the graph contains
+ * a cycle.
+ */
+ traverseAllocatorDumpsInDepthFirstPreOrder(fn) {
+ const visitedDumps = new WeakSet();
+
+ function visit(dump) {
+ if (visitedDumps.has(dump)) return;
+
+ // If this dumps owns another dump which hasn't been visited yet, then
+ // wait for this dump to be visited later.
+ if (dump.owns !== undefined && !visitedDumps.has(dump.owns.target)) {
+ return;
+ }
+
+ // If this dump's parent hasn't been visited yet, then wait for this
+ // dump to be visited later.
+ if (dump.parent !== undefined && !visitedDumps.has(dump.parent)) {
+ return;
+ }
+
+ // Actually visit the current memory allocator dump.
+ fn.call(this, dump);
+ visitedDumps.add(dump);
+
+ // Visit owners after the dumps they own.
+ dump.ownedBy.forEach(function(ownershipLink) {
+ visit.call(this, ownershipLink.source);
+ }, this);
+
+ // Visit children after parents.
+ dump.children.forEach(visit, this);
+ }
+
+ this.iterateAllRootAllocatorDumps(visit);
+ }
+ };
+
+ tr.model.EventRegistry.register(
+ GlobalMemoryDump,
+ {
+ name: 'globalMemoryDump',
+ pluralName: 'globalMemoryDumps'
+ });
+
+ return {
+ GlobalMemoryDump,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/global_memory_dump_test.html b/chromium/third_party/catapult/tracing/tracing/model/global_memory_dump_test.html
new file mode 100644
index 00000000000..219ce0f28d8
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/global_memory_dump_test.html
@@ -0,0 +1,3954 @@
+<!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/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/importer/import.html">
+<link rel="import" href="/tracing/model/global_memory_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/model/model.html">
+<link rel="import" href="/tracing/model/process_memory_dump.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const Model = tr.Model;
+ const GlobalMemoryDump = tr.model.GlobalMemoryDump;
+ const ProcessMemoryDump = tr.model.ProcessMemoryDump;
+ const MemoryAllocatorDump = tr.model.MemoryAllocatorDump;
+ const MemoryAllocatorDumpLink = tr.model.MemoryAllocatorDumpLink;
+ const Scalar = tr.b.Scalar;
+ const sizeInBytes_smallerIsBetter =
+ tr.b.Unit.byName.sizeInBytes_smallerIsBetter;
+ const newAllocatorDump = tr.model.MemoryDumpTestUtils.newAllocatorDump;
+ const addChildDump = tr.model.MemoryDumpTestUtils.addChildDump;
+ const addOwnershipLink = tr.model.MemoryDumpTestUtils.addOwnershipLink;
+ const checkDumpNumericsAndDiagnostics =
+ tr.model.MemoryDumpTestUtils.checkDumpNumericsAndDiagnostics;
+ const SIZE_DELTA = tr.model.MemoryDumpTestUtils.SIZE_DELTA;
+ 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 buildArgPusher(array) {
+ return function(arg) { array.push(arg); };
+ }
+
+ function assertEqualUniqueMembers(actualArray, expectedArray) {
+ assert.lengthOf(actualArray, expectedArray.length);
+ assert.sameMembers(actualArray, expectedArray);
+ }
+
+ function assertUndefinedNumeric(dump, numericName) {
+ const numeric = dump.numerics[numericName];
+ assert.isUndefined(numeric, 'expected numeric \'' + numericName +
+ '\' of memory allocator dump \'' + dump.fullName + '\' in ' +
+ dump.containerMemoryDump.userFriendlyName + ' to be undefined');
+ }
+
+ function assertDefinedNumeric(dump, numericName, expectedUnit, expectedValue,
+ opt_delta) {
+ const numeric = dump.numerics[numericName];
+ const errorMessagePrefix = 'expected numeric \'' + numericName +
+ '\' of memory allocator dump \'' + dump.fullName + '\' in ' +
+ dump.containerMemoryDump.userFriendlyName + ' to ';
+
+ assert.instanceOf(numeric, Scalar,
+ errorMessagePrefix + 'be an instance of Scalar');
+ assert.strictEqual(numeric.unit, expectedUnit,
+ errorMessagePrefix + 'have unit \'' + expectedUnit.unitName +
+ '\' but got \'' + numeric.unit.unitName + '\' instead');
+
+ const valueErrorMessage = errorMessagePrefix + 'have value \'' +
+ expectedValue + '\' but got \'' + numeric.value + '\'';
+ if (opt_delta !== undefined) {
+ assert.closeTo(
+ numeric.value, expectedValue, opt_delta, valueErrorMessage);
+ } else {
+ assert.strictEqual(numeric.value, expectedValue, valueErrorMessage);
+ }
+ }
+
+ function assertSizeNumeric(dump, sizeName, expectedValue) {
+ if (expectedValue === undefined) {
+ assertUndefinedNumeric(dump, sizeName);
+ } else {
+ assertDefinedNumeric(dump, sizeName, sizeInBytes_smallerIsBetter,
+ expectedValue, SIZE_DELTA);
+ }
+ }
+
+ function assertDumpSizes(dump, expectedSize, expectedEffectiveSize,
+ opt_expectedInfos, opt_expectedOwnedBySiblingSizes) {
+ // Check the 'size' numeric.
+ assertSizeNumeric(dump, 'size', expectedSize);
+
+ // Check the 'effective_size' numeric.
+ assertSizeNumeric(dump, 'effective_size', expectedEffectiveSize);
+
+ // Check the 'infos' list.
+ const expectedInfos = opt_expectedInfos || [];
+ const actualInfos = dump.infos;
+ assert.lengthOf(actualInfos, expectedInfos.length,
+ 'expected memory allocator dump \'' + dump.fullName + '\' in ' +
+ dump.containerMemoryDump.userFriendlyName + ' to have ' +
+ expectedInfos.length + ' infos but got ' + actualInfos.length);
+ for (let k = 0; k < actualInfos.length; k++) {
+ assert.deepEqual(actualInfos[k], expectedInfos[k],
+ 'info ' + k + ' of memory allocator dump \'' + dump.fullName +
+ '\' in ' + dump.containerMemoryDump.userFriendlyName +
+ ' doesn\'t match the expected info');
+ }
+
+ // Checked the 'ownedBySiblingSizes' map.
+ const expectedOwnedBySiblingSizes = opt_expectedOwnedBySiblingSizes || {};
+ const actualOwnedBySiblingSizes = {};
+ for (const siblingDump of dump.ownedBySiblingSizes.keys()) {
+ assert.strictEqual(siblingDump.parent, dump.parent);
+ actualOwnedBySiblingSizes[siblingDump.name] =
+ dump.ownedBySiblingSizes.get(siblingDump);
+ }
+ assert.deepEqual(actualOwnedBySiblingSizes, expectedOwnedBySiblingSizes,
+ 'ownedBySiblingSizes of memory allocator dump \'' + dump.fullName +
+ '\' in ' + dump.containerMemoryDump.userFriendlyName +
+ ' doesn\'t contain the expected values');
+ }
+
+ function createContainerDumps(processMemoryDumpCount, opt_model) {
+ let model = opt_model;
+ if (model === undefined) {
+ model = new Model();
+ }
+
+ const gmd = new GlobalMemoryDump(model, 0);
+ model.globalMemoryDumps.push(gmd);
+
+ const pmds = [];
+ for (let i = 0; i < processMemoryDumpCount; i++) {
+ const process = model.getOrCreateProcess(i);
+ const pmd = new ProcessMemoryDump(gmd, process, 0);
+ gmd.processMemoryDumps[i] = pmd;
+ process.memoryDumps.push(pmd);
+ pmds.push(pmd);
+ }
+
+ return [gmd].concat(pmds);
+ }
+
+ /**
+ * Build container memory dumps from tree recipes. This function returns
+ * a list containing a global memory dump and zero or more process memory
+ * dumps constructed from the provided function argument as follows:
+ *
+ * allTreeRecipes (argument):
+ *
+ * [
+ * [tree recipe GA, tree recipe GB, ...],
+ * [tree recipe P1A, tree recipe P1B, ...],
+ * [tree recipe P2A, tree recipe P2B ...],
+ * ...
+ * ]
+ *
+ * return value:
+ *
+ * [
+ * GlobalMemoryDump with root MemoryAllocatorDump(s) [GA, GB, ...],
+ * ProcessMemoryDump with root MemoryAllocatorDump(s) [P1A, P1B, ...],
+ * ProcessMemoryDump with root MemoryAllocatorDump(s) [P2A, P2B, ...],
+ * ...
+ * ]
+ *
+ * where a tree recipe is an object (a recursive data structure) with the
+ * following fields:
+ *
+ * name: Name of the resulting MAD.
+ * guid: GUID of the resulting MAD (can be undefined).
+ * owns: GUID of another MAD owned by the resulting MAD (no owned MAD if
+ * undefined).
+ * importance: Importance of the above ownership (can be undefined).
+ * size: Value of the 'size' numeric of the resulting MAD (no 'size'
+ * numeric if undefined).
+ * numerics: Extra numerics of the resulting MAD (dictionary).
+ * diagnostics: Extra diagnostics of the resulting MAD (dictionary).
+ * weak: Whether the resulting MAD should be weak (undefined implies
+ * non-weak).
+ * children: List of tree recipes for child MADs (no children if undefined).
+ * skip_build: If this optional property is set to true, this function will
+ * skip the corresponding tree recipe node and will not create a MAD
+ * for it (not allowed in root recipes).
+ *
+ * Other fields (most importantly 'expected_size') of a tree recipe are
+ * ignored by this function.
+ */
+ function buildDumpTrees(allTreeRecipes, opt_model) {
+ assert.isAbove(allTreeRecipes.length, 0);
+
+ // owned GUID -> {dump: owner, importance: optional}.
+ const ownerDumps = {};
+ const ownableDumps = {}; // ownable GUID -> ownable dump.
+
+ function buildAndAddDumpTrees(containerDump, treeRecipes) {
+ if (treeRecipes === undefined) return;
+
+ function buildDumpTreeRecursively(treeRecipe, namePrefix) {
+ const skipBuild = treeRecipe.skip_build;
+ const name = treeRecipe.name;
+ const guid = treeRecipe.guid;
+ const owns = treeRecipe.owns;
+ const size = treeRecipe.size;
+ const numerics = treeRecipe.numerics;
+ const diagnostics = treeRecipe.diagnostics;
+ const importance = treeRecipe.importance;
+ const weak = treeRecipe.weak;
+
+ assert.notStrictEqual(skipBuild, true);
+ assert.isDefined(name);
+
+ const fullName = namePrefix + name;
+ const dump = new MemoryAllocatorDump(containerDump, fullName, guid);
+
+ if (size !== undefined) {
+ dump.addNumeric(
+ 'size', new Scalar(sizeInBytes_smallerIsBetter, size));
+ }
+ if (guid !== undefined) {
+ assert.notProperty(guid, ownableDumps);
+ ownableDumps[guid] = dump;
+ }
+ if (owns !== undefined) {
+ if (!(owns in ownerDumps)) {
+ ownerDumps[owns] = [];
+ }
+ ownerDumps[owns].push({dump, importance});
+ } else {
+ assert.isUndefined(importance); // Test sanity check.
+ }
+
+ if (treeRecipe.children !== undefined) {
+ treeRecipe.children.forEach(function(childTreeRecipe) {
+ // Virtual children are added during size calculation.
+ if (childTreeRecipe.skip_build === true) return;
+ const childDump =
+ buildDumpTreeRecursively(childTreeRecipe, fullName + '/');
+ childDump.parent = dump;
+ dump.children.push(childDump);
+ });
+ }
+
+ if (numerics !== undefined) {
+ for (const [name, item] of Object.entries(numerics)) {
+ dump.addNumeric(name, item);
+ }
+ }
+ if (diagnostics !== undefined) {
+ for (const [name, item] of Object.entries(diagnostics)) {
+ dump.addDiagnostic(name, item);
+ }
+ }
+
+ if (weak) dump.weak = true;
+
+ return dump;
+ }
+
+ const memoryAllocatorDumps = treeRecipes.map(function(treeRecipe) {
+ return buildDumpTreeRecursively(treeRecipe, '');
+ });
+ containerDump.memoryAllocatorDumps = memoryAllocatorDumps;
+ }
+
+ // Recursively build memory allocator dump trees for all container dumps.
+ const containerDumps = createContainerDumps(
+ allTreeRecipes.length - 1, opt_model);
+ for (let i = 0; i < allTreeRecipes.length; i++) {
+ buildAndAddDumpTrees(containerDumps[i], allTreeRecipes[i]);
+ }
+
+ // Hook up ownership links.
+ for (const [ownedGuid, ownershipInfos] of Object.entries(ownerDumps)) {
+ const ownedDump = ownableDumps[ownedGuid];
+ assert.isDefined(ownedDump, 'Tree recipes don\'t contain a memory ' +
+ 'allocator dump with guid \'' + ownedGuid + '\'');
+
+ ownershipInfos.forEach(function(ownershipInfo) {
+ addOwnershipLink(
+ ownershipInfo.dump, ownedDump, ownershipInfo.importance);
+ });
+ }
+
+ return containerDumps;
+ }
+
+ // Check that the buildDumpTrees testing helper method above builds a
+ // hierarchy of container and memory allocator dumps from tree recipes
+ // correctly.
+ test('testSanityCheck_buildDumpTrees', function() {
+ const containerDumps = buildDumpTrees([
+ [ // GMD.
+ {
+ 'name': 'globalSharedDump1',
+ 'size': 123
+ },
+ {
+ 'name': 'globalSharedDump2',
+ 'subsystem_size': 999,
+ 'owns': 7,
+ 'importance': -1
+ }
+ ],
+ undefined, // PMD1.
+ [ // PMD2.
+ {
+ 'name': 'v8',
+ 'children': [
+ {
+ 'name': 'isolate1',
+ 'guid': 7,
+ 'weak': true
+ },
+ {
+ 'name': 'isolate2',
+ 'skip_build': true
+ },
+ {
+ 'name': 'isolate3',
+ 'size': 54,
+ 'guid': 60,
+ 'children': [
+ {
+ 'name': 'obj1',
+ 'size': 89,
+ 'guid': 3
+ },
+ {
+ 'name': 'obj2',
+ 'owns': 3,
+ 'weak': true
+ },
+ {
+ 'name': 'obj3',
+ 'owns': 3,
+ 'importance': 2
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ ]);
+ assert.lengthOf(containerDumps, 3);
+ const gmd = containerDumps[0];
+ const pmd1 = containerDumps[1];
+ const pmd2 = containerDumps[2];
+
+ function checkDump(dump, expectedGuid, expectedFullName, expectedParent,
+ expectedChildrenCount, expectedSize, expectedIsOwner,
+ expectedOwnersCount, expectedContainerDump, opt_expectedWeak) {
+ assert.isDefined(dump);
+ assert.instanceOf(dump, MemoryAllocatorDump);
+ assert.strictEqual(dump.guid, expectedGuid);
+ assert.strictEqual(dump.fullName, expectedFullName);
+ assert.strictEqual(dump.parent, expectedParent);
+ assert.lengthOf(dump.children, expectedChildrenCount);
+
+ assertSizeNumeric(dump, 'size', expectedSize);
+ assertSizeNumeric(dump, 'subsystem_size', undefined);
+
+ if (expectedIsOwner) {
+ assert.isDefined(dump.owns);
+ } else {
+ assert.isUndefined(dump.owns);
+ }
+ assert.lengthOf(dump.ownedBy, expectedOwnersCount);
+
+ assert.strictEqual(dump.containerMemoryDump, expectedContainerDump);
+ assert.strictEqual(expectedContainerDump.getMemoryAllocatorDumpByFullName(
+ expectedFullName), dump);
+ assert.strictEqual(dump.weak, !!opt_expectedWeak);
+ }
+
+ function checkOwnershipLink(expectedSourceDump, expectedTargetDump,
+ expectedImportance) {
+ const link = expectedSourceDump.owns;
+ assert.isDefined(link);
+ assert.instanceOf(link, MemoryAllocatorDumpLink);
+ assert.strictEqual(link.source, expectedSourceDump);
+ assert.strictEqual(link.target, expectedTargetDump);
+ assert.strictEqual(link.importance, expectedImportance);
+ assert.include(expectedTargetDump.ownedBy, link);
+ }
+
+ // GMD memory allocator dumps.
+ assert.lengthOf(gmd.memoryAllocatorDumps, 2);
+ const globalSharedDump1 = gmd.memoryAllocatorDumps[0];
+ checkDump(globalSharedDump1, undefined, 'globalSharedDump1', undefined, 0,
+ 123, false, 0, gmd);
+ const globalSharedDump2 = gmd.memoryAllocatorDumps[1];
+ checkDump(globalSharedDump2, undefined, 'globalSharedDump2', undefined, 0,
+ undefined, true, 0, gmd);
+
+ // PMD1 memory allocator dumps.
+ assert.isUndefined(pmd1.memoryAllocatorDumps);
+
+ // PMD2 memory allocator dumps.
+ assert.lengthOf(pmd2.memoryAllocatorDumps, 1);
+ const v8Dump = pmd2.memoryAllocatorDumps[0];
+ checkDump(v8Dump, undefined, 'v8', undefined, 2, undefined, false, 0,
+ pmd2);
+ const isolate1Dump = v8Dump.children[0];
+ checkDump(isolate1Dump, 7, 'v8/isolate1', v8Dump, 0, undefined, false, 1,
+ pmd2, true /* weak dump */);
+ const isolate3Dump = v8Dump.children[1];
+ checkDump(isolate3Dump, 60, 'v8/isolate3', v8Dump, 3, 54, false, 0, pmd2);
+ const obj1Dump = isolate3Dump.children[0];
+ checkDump(obj1Dump, 3, 'v8/isolate3/obj1', isolate3Dump, 0, 89, false, 2,
+ pmd2);
+ const obj2Dump = isolate3Dump.children[1];
+ checkDump(obj2Dump, undefined, 'v8/isolate3/obj2', isolate3Dump, 0,
+ undefined, true, 0, pmd2, true /* weak dump */);
+ const obj3Dump = isolate3Dump.children[2];
+ checkDump(obj3Dump, undefined, 'v8/isolate3/obj3', isolate3Dump, 0,
+ undefined, true, 0, pmd2);
+
+ // Ownership links.
+ checkOwnershipLink(globalSharedDump2, isolate1Dump, -1);
+ checkOwnershipLink(obj2Dump, obj1Dump, undefined);
+ checkOwnershipLink(obj3Dump, obj1Dump, 2);
+ });
+
+ /**
+ * Check that container memory dumps have the expected structure with sizes
+ * as described by tree recipes. The fields of a tree recipe are used by this
+ * function to check the properties of a MemoryAllocatorDump as follows (see
+ * the buildDumpTrees documentation for more details about the structure of
+ * tree recipes):
+ *
+ * name: Expected name of the MAD.
+ * expected_removed: If provided and true, it is expected that there is no
+ * dump for the recipe.
+ * expected_size: Expected value of the 'size' numeric of the MAD (no
+ * 'size' numeric expected if undefined).
+ * expected_effective_size: Expected value of the 'effective_size'
+ * numeric of the MAD (no 'effective_size' numeric expected if
+ * undefined).
+ * expected_infos: List of expected MAD infos (zero infos expected if
+ * undefined).
+ * weak: Whether the MAD is expected to be weak (non-weak if undefined).
+ * owns: Expected GUID of the dump owned by the MAD.
+ * importance: Expected importance of the owhership from this MAD.
+ * expected_owned_by_links_count: Expected number of 'ownedBy' links of the
+ * MAD.
+ * children: List of tree recipes for child MADs (no children expected if
+ * undefined).
+ *
+ * Other fields of a tree recipe (including 'skip_build') are ignored by this
+ * function.
+ */
+ function checkDumpTrees(containerDumps, allTreeRecipes) {
+ assert.lengthOf(containerDumps, allTreeRecipes.length);
+
+ for (let i = 0; i < containerDumps.length; i++) {
+ const containerDump = containerDumps[i];
+ const treeRecipes = allTreeRecipes[i];
+
+ const memoryAllocatorDumps = containerDump.memoryAllocatorDumps;
+ if (treeRecipes === undefined) {
+ assert.isUndefined(memoryAllocatorDumps,
+ 'expected undefined memory allocator dumps in ' +
+ containerDump.userFriendlyName);
+ continue;
+ }
+
+ const expectedTreeRecipes = treeRecipes.filter(function(treeRecipe) {
+ return !treeRecipe.expected_removed;
+ });
+
+ assert.isDefined(memoryAllocatorDumps,
+ 'expected defined memory allocator dumps in ' +
+ containerDump.userFriendlyName);
+ assert.lengthOf(memoryAllocatorDumps, expectedTreeRecipes.length,
+ 'expected ' + expectedTreeRecipes.length + ' root memory allocator ' +
+ 'dumps but got ' + memoryAllocatorDumps.length + ' in ' +
+ containerDump.userFriendlyName);
+
+ function checkDumpTree(dump, treeRecipe, expectedParent, namePrefix) {
+ // Test sanity check.
+ assert.isFalse(!!treeRecipe.expected_removed);
+
+ // Check full name, parent, and container dump.
+ const expectedFullName = namePrefix + treeRecipe.name;
+ const quantifiedName = dump.quantifiedName;
+ assert.strictEqual(dump.fullName, expectedFullName,
+ quantifiedName + ' has invalid full name');
+ assert.strictEqual(dump.parent, expectedParent,
+ quantifiedName + ' has invalid parent');
+ assert.strictEqual(dump.containerMemoryDump, containerDump,
+ quantifiedName + ' has invalid container memory dump');
+ assert.strictEqual(containerDump.getMemoryAllocatorDumpByFullName(
+ expectedFullName), dump, quantifiedName +
+ 'is not indexed in its container memory dump');
+
+ // Check the guid of the dump.
+ assert.strictEqual(dump.guid, treeRecipe.guid,
+ quantifiedName + ' has invalid guid');
+
+ // Check that the 'weak' flag is correct.
+ assert.strictEqual(dump.weak, !!treeRecipe.weak,
+ quantifiedName + ' has invalid weak flag');
+
+ // Check that sizes were calculated correctly.
+ assertDumpSizes(dump,
+ treeRecipe.expected_size,
+ treeRecipe.expected_effective_size,
+ treeRecipe.expected_infos,
+ treeRecipe.expected_owned_by_sibling_sizes);
+
+ // Check that the 'owns' link is correct.
+ if (treeRecipe.owns === undefined) {
+ assert.isUndefined(dump.owns,
+ quantifiedName + ' was expected not to own another dump');
+ } else {
+ const ownershipLink = dump.owns;
+ assert.isDefined(dump.owns, quantifiedName +
+ ' was expected to have an \'owns\' link');
+ assert.strictEqual(ownershipLink.source, dump,
+ 'the \'owns\' link of ' + quantifiedName + ' has invalid source');
+ const expectedImportance = treeRecipe.importance;
+ assert.strictEqual(ownershipLink.importance, expectedImportance,
+ 'expected the importance of the \'owns\' link of ' +
+ quantifiedName + ' to be ' + expectedImportance +
+ ' but got ' + ownershipLink.importance);
+ const ownedDump = ownershipLink.target;
+ assert.strictEqual(ownedDump.guid, treeRecipe.owns,
+ 'the \'owns\' link of ' + quantifiedName +
+ ' has an invalid target');
+ assert.include(ownedDump.ownedBy, ownershipLink,
+ 'the target of the \'owns\' link of ' + quantifiedName +
+ ' doesn\'t have the link in its \'ownedBy\' list');
+ }
+
+ // Check that the number of 'ownedBy' links is correct.
+ const expectedOwnedByLinksCount =
+ treeRecipe.expected_owned_by_links_count;
+ if (expectedOwnedByLinksCount !== undefined) {
+ assert.lengthOf(dump.ownedBy, expectedOwnedByLinksCount,
+ 'expected ' + quantifiedName + ' to have ' +
+ expectedOwnedByLinksCount + ' \'ownedBy\' links but got ' +
+ dump.ownedBy.length);
+ }
+
+ // Check children recursively.
+ const actualChildren = dump.children;
+ const expectedChildren = (treeRecipe.children || []).filter(
+ function(childRecipe) {
+ return !childRecipe.expected_removed;
+ });
+ assert.lengthOf(actualChildren, expectedChildren.length,
+ 'expected ' + quantifiedName + ' to have ' +
+ expectedChildren.length + ' children but got ' +
+ actualChildren.length);
+ for (let k = 0; k < actualChildren.length; k++) {
+ checkDumpTree(actualChildren[k], expectedChildren[k], dump,
+ expectedFullName + '/');
+ }
+ }
+
+ for (let j = 0; j < memoryAllocatorDumps.length; j++) {
+ checkDumpTree(
+ memoryAllocatorDumps[j], expectedTreeRecipes[j], undefined, '');
+ }
+ }
+ }
+
+ // Check that the checkDumpTrees testing helper method above actually
+ // performs the expected checks. Since it will be used heavily throughout
+ // this file (where it is expected to pass), we only need to verify that it
+ // does indeed fail in several cases where it should.
+ test('testSanityCheck_checkDumpTrees_invalidName', function() {
+ const containerDumps = createContainerDumps(0);
+ const gmd = containerDumps[0];
+ const v8Dump = new MemoryAllocatorDump(gmd, 'v8');
+ const heapsDump =
+ new MemoryAllocatorDump(gmd, 'heaps'); // Should be 'v8/heaps'.
+ v8Dump.children.push(heapsDump);
+ heapsDump.parent = v8Dump;
+ gmd.memoryAllocatorDumps = [v8Dump];
+
+ assert.throws(function() {
+ checkDumpTrees(containerDumps, [
+ [
+ {
+ 'name': 'v8',
+ 'children': [
+ {
+ 'name': 'heaps'
+ }
+ ]
+ }
+ ]
+ ]);
+ }, /'heaps'.*invalid full name/);
+ });
+
+ test('testSanityCheck_checkDumpTrees_invalidGuid', function() {
+ const containerDumps = createContainerDumps(0);
+ const gmd = containerDumps[0];
+ gmd.memoryAllocatorDumps = [new MemoryAllocatorDump(gmd, 'v8', 42)];
+
+ assert.throws(function() {
+ checkDumpTrees(containerDumps, [
+ [
+ {
+ 'name': 'v8',
+ 'guid': 43 // This should be 42.
+ }
+ ]
+ ]);
+ }, /'v8'.*\binvalid guid\b/);
+ });
+
+ test('testSanityCheck_checkDumpTrees_invalidStructure', function() {
+ const containerDumps = createContainerDumps(0);
+ const gmd = containerDumps[0];
+ const rootDump = new MemoryAllocatorDump(gmd, 'root');
+ addChildDump(rootDump, 'child1');
+ gmd.memoryAllocatorDumps = [rootDump];
+
+ assert.throws(function() {
+ checkDumpTrees(containerDumps, [
+ [
+ {
+ 'name': 'root',
+ 'children': [
+ {
+ 'name': 'child1'
+ },
+ {
+ // This child is not present in the dump.
+ 'name': 'child2',
+ 'skip_build': true
+ }
+ ]
+ }
+ ]
+ ]);
+ }, /expected.*\b2\b.*children.*got.*\b1\b/);
+ });
+
+ test('testSanityCheck_checkDumpTrees_invalidParentLink', function() {
+ const containerDumps = createContainerDumps(0);
+ const gmd = containerDumps[0];
+ const rootDump = new MemoryAllocatorDump(gmd, 'root');
+ const parentDump = addChildDump(rootDump, 'parent');
+ const childDump = addChildDump(parentDump, 'child');
+ childDump.parent = rootDump; // This should correctly be parentDump.
+ gmd.memoryAllocatorDumps = [rootDump];
+
+ assert.throws(function() {
+ checkDumpTrees(containerDumps, [
+ [
+ {
+ 'name': 'root',
+ 'children': [
+ {
+ 'name': 'parent',
+ 'children': [
+ {
+ 'name': 'child'
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ ]);
+ }, 'invalid parent');
+ });
+
+ test('testSanityCheck_checkDumpTrees_invalidSize', function() {
+ const containerDumps = createContainerDumps(1);
+ const gmd = containerDumps[0];
+ const pmd = containerDumps[1];
+ const rootDump = newAllocatorDump(pmd, 'root', {numerics: {size: 100}});
+ addChildDump(rootDump, 'parent', {numerics: {size: 49}});
+ pmd.memoryAllocatorDumps = [rootDump];
+
+ assert.throws(function() {
+ checkDumpTrees(containerDumps, [
+ undefined,
+ [
+ {
+ 'name': 'root',
+ 'expected_size': 100,
+ 'children': [
+ {
+ 'name': 'parent',
+ 'expected_size': 50 // This should be 49.
+ }
+ ]
+ }
+ ]
+ ]);
+ }, /expected.*'size'.*value.*\b50\b.*got.*\b49\b/);
+ });
+
+ test('testSanityCheck_checkDumpTrees_invalidEffectiveSize', function() {
+ const containerDumps = createContainerDumps(1);
+ const gmd = containerDumps[0];
+ const pmd = containerDumps[1];
+ const rootDump = newAllocatorDump(pmd, 'root',
+ {numerics: {effective_size: 99}});
+ addChildDump(rootDump, 'parent', {numerics: {effective_size: 50}});
+ pmd.memoryAllocatorDumps = [rootDump];
+
+ assert.throws(function() {
+ checkDumpTrees(containerDumps, [
+ undefined,
+ [
+ {
+ 'name': 'root',
+ 'expected_effective_size': 100, // This should be 99.
+ 'children': [
+ {
+ 'name': 'parent',
+ 'expected_effective_size': 50
+ }
+ ]
+ }
+ ]
+ ]);
+ }, /expected.*'effective_size'.*value.*\b100\b.*got.*\b99\b/);
+ });
+
+ test('testSanityCheck_checkDumpTrees_invalidInfoCount', function() {
+ const containerDumps = createContainerDumps(0);
+ const gmd = containerDumps[0];
+ gmd.memoryAllocatorDumps = [
+ newAllocatorDump(gmd, 'v8', {numerics: {size: 50}})
+ ];
+
+ assert.throws(function() {
+ checkDumpTrees(containerDumps, [
+ [
+ {
+ 'name': 'v8',
+ 'expected_size': 50,
+ 'expected_infos': [
+ {
+ type: PROVIDED_SIZE_LESS_THAN_AGGREGATED_CHILDREN,
+ providedSize: 50,
+ dependencySize: 60
+ }
+ ]
+ }
+ ]
+ ]);
+ }, /expected.*'v8'.*\b1 infos\b.*\bgot\b.*\b0\b/);
+ });
+
+ test('testSanityCheck_checkDumpTrees_invalidInfo', function() {
+ const containerDumps = createContainerDumps(0);
+ const gmd = containerDumps[0];
+ const v8Dump = newAllocatorDump(gmd, 'v8', {numerics: {size: 50}});
+ v8Dump.infos.push({
+ type: PROVIDED_SIZE_LESS_THAN_AGGREGATED_CHILDREN,
+ providedSize: 40,
+ dependencySize: 50
+ });
+ gmd.memoryAllocatorDumps = [v8Dump];
+
+ assert.throws(function() {
+ checkDumpTrees(containerDumps, [
+ [
+ {
+ 'name': 'v8',
+ 'expected_size': 50,
+ 'expected_infos': [
+ {
+ // Should be PROVIDED_SIZE_LESS_THAN_AGGREGATED_CHILDREN below.
+ type: PROVIDED_SIZE_LESS_THAN_LARGEST_OWNER,
+ providedSize: 40,
+ dependencySize: 50
+ }
+ ]
+ }
+ ]
+ ]);
+ }, /\binfo 0\b.*'v8'.*\bexpected\b/);
+ });
+
+ test('testSanityCheck_checkDumpTrees_invalidOwnedBySiblingSizes', function() {
+ const containerDumps = createContainerDumps(0);
+ const gmd = containerDumps[0];
+ const v8Dump = new MemoryAllocatorDump(gmd, 'v8');
+ addChildDump(v8Dump, 'child1', {guid: 42});
+ addChildDump(v8Dump, 'child2');
+ gmd.memoryAllocatorDumps = [v8Dump];
+
+ assert.throws(function() {
+ checkDumpTrees(containerDumps, [
+ [
+ {
+ 'name': 'v8',
+ 'children': [
+ {
+ 'name': 'child1',
+ 'guid': 42
+ },
+ {
+ 'name': 'child2',
+ 'expected_owned_by_sibling_sizes': {
+ 'child1': 40 // This should be 30.
+ }
+ }
+ ]
+ }
+ ]
+ ]);
+ }, /\bownedBySiblingSizes\b.*'v8\/child2'.*\bexpected\b/);
+ });
+
+ test('testSanityCheck_checkDumpTrees_invalidWeakFlag',
+ function() {
+ const containerDumps = createContainerDumps(0);
+ const gmd = containerDumps[0];
+ const parentDump = new MemoryAllocatorDump(gmd, 'parent');
+ const childDump = addChildDump(parentDump, 'child');
+ childDump.weak = true;
+ gmd.memoryAllocatorDumps = [parentDump];
+
+ assert.throws(function() {
+ checkDumpTrees(containerDumps, [
+ [
+ {
+ 'name': 'parent',
+ 'children': [
+ {
+ 'name': 'child',
+ // Missing "'weak': true".
+ }
+ ]
+ }
+ ]
+ ]);
+ }, /'parent\/child'.*\binvalid weak flag\b/);
+
+ assert.throws(function() {
+ checkDumpTrees(containerDumps, [
+ [
+ {
+ 'name': 'parent',
+ 'weak': true, // This should be false (or not provided).
+ 'children': [
+ {
+ 'name': 'child',
+ 'weak': true
+ }
+ ]
+ }
+ ]
+ ]);
+ }, /'parent'.*\binvalid weak flag\b/);
+ });
+
+ test('testSanityCheck_checkDumpTrees_dumpNotRemoved', function() {
+ const containerDumps = createContainerDumps(0);
+ const gmd = containerDumps[0];
+ const parentDump = new MemoryAllocatorDump(gmd, 'parent');
+ for (let i = 1; i <= 3; i++) {
+ addChildDump(parentDump, 'child' + i);
+ }
+ const otherDump = new MemoryAllocatorDump(gmd, 'other');
+ gmd.memoryAllocatorDumps = [parentDump, otherDump];
+
+ // Child MAD not removed.
+ assert.throws(function() {
+ checkDumpTrees(containerDumps, [
+ [
+ {
+ 'name': 'parent',
+ 'children': [
+ {
+ 'name': 'child1',
+ },
+ {
+ 'name': 'child2',
+ 'expected_removed': true
+ },
+ {
+ 'name': 'child3',
+ }
+ ]
+ },
+ {
+ 'name': 'other'
+ }
+ ]
+ ]);
+ }, /\bexpected\b.*'parent'.*\b2 children\b.*\bgot 3\b/);
+
+ // Root MAD not removed.
+ assert.throws(function() {
+ checkDumpTrees(containerDumps, [
+ [
+ {
+ 'name': 'parent',
+ 'children': [
+ {
+ 'name': 'child1',
+ },
+ {
+ 'name': 'child2'
+ },
+ {
+ 'name': 'child3',
+ }
+ ]
+ },
+ {
+ 'name': 'other',
+ 'expected_removed': true
+ }
+ ]
+ ]);
+ }, /\bexpected\b.*\b1 root memory allocator dumps\b.*\bgot 2\b/);
+ });
+
+ test('testSanityCheck_checkDumpTrees_invalidOwnership', function() {
+ const containerDumps = createContainerDumps(1);
+ const gmd = containerDumps[0];
+ const pmd1 = containerDumps[1];
+ const ownedDump = new MemoryAllocatorDump(gmd, 'owned', 42);
+ const ownerDump1 = new MemoryAllocatorDump(pmd1, 'owner1');
+ const link1 = addOwnershipLink(ownerDump1, ownedDump);
+ const ownerDump2 = new MemoryAllocatorDump(pmd1, 'owner2');
+ const link2 = addOwnershipLink(ownerDump2, ownedDump, 3);
+ const nonOwnerDump = new MemoryAllocatorDump(pmd1, 'non-owner', 90);
+ gmd.memoryAllocatorDumps = [ownedDump];
+ pmd1.memoryAllocatorDumps = [ownerDump1, ownerDump2, nonOwnerDump];
+
+ // Missing 'owns' link.
+ assert.throws(function() {
+ checkDumpTrees(containerDumps, [
+ [
+ {
+ 'name': 'owned',
+ 'guid': 42
+ }
+ ],
+ [
+ {
+ 'name': 'owner1',
+ 'owns': 42
+ },
+ {
+ 'name': 'owner2',
+ 'importance': 3,
+ 'owns': 42
+ },
+ {
+ 'name': 'non-owner',
+ 'guid': 90,
+ 'owns': 42 // This should not be here.
+ }
+ ]
+ ]);
+ }, /'non-owner'.*\bwas expected to have\b.*'owns' link\b/);
+
+ // Extra 'owns' link.
+ assert.throws(function() {
+ checkDumpTrees(containerDumps, [
+ [
+ {
+ 'name': 'owned',
+ 'guid': 42
+ }
+ ],
+ [
+ {
+ 'name': 'owner1'
+ // Missing: "'owns': 42".
+ },
+ {
+ 'name': 'owner2',
+ 'importance': 3,
+ 'owns': 42
+ },
+ {
+ 'name': 'non-owner',
+ 'guid': 90
+ }
+ ]
+ ]);
+ }, /'owner1'.*\bwas expected not to own\b/);
+
+ // Invalid ownership importance.
+ assert.throws(function() {
+ checkDumpTrees(containerDumps, [
+ [
+ {
+ 'name': 'owned',
+ 'guid': 42
+ }
+ ],
+ [
+ {
+ 'name': 'owner1',
+ 'owns': 42
+ },
+ {
+ 'name': 'owner2',
+ 'importance': 2, // This should be 3.
+ 'owns': 42
+ },
+ {
+ 'name': 'non-owner',
+ 'guid': 90
+ }
+ ]
+ ]);
+ }, /\bexpected\b.*\bimportance\b.*'owner2'.*\b2 but got 3\b/);
+
+ // Invalid ownership target.
+ assert.throws(function() {
+ checkDumpTrees(containerDumps, [
+ [
+ {
+ 'name': 'owned',
+ 'guid': 42
+ }
+ ],
+ [
+ {
+ 'name': 'owner1',
+ 'owns': 90 // This should be 42.
+ },
+ {
+ 'name': 'owner2',
+ 'importance': 3,
+ 'owns': 42
+ },
+ {
+ 'name': 'non-owner',
+ 'guid': 90
+ }
+ ]
+ ]);
+ }, /'owner1'.*\binvalid target\b/);
+
+ // Invalid 'ownedBy' ownership links count.
+ assert.throws(function() {
+ checkDumpTrees(containerDumps, [
+ [
+ {
+ 'name': 'owned',
+ 'guid': 42,
+ 'expected_owned_by_links_count': 3 // This should be 2.
+ }
+ ],
+ [
+ {
+ 'name': 'owner1',
+ 'owns': 42
+ },
+ {
+ 'name': 'owner2',
+ 'importance': 3,
+ 'owns': 42
+ },
+ {
+ 'name': 'non-owner',
+ 'guid': 90
+ }
+ ]
+ ]);
+ }, /'owned'.*\bhave 3 'ownedBy' links\b.*\bgot 2\b/);
+
+ // Invalid ownership source.
+ link1.source = ownerDump2;
+ assert.throws(function() {
+ checkDumpTrees(containerDumps, [
+ [
+ {
+ 'name': 'owned',
+ 'guid': 42
+ }
+ ],
+ [
+ {
+ 'name': 'owner1',
+ 'owns': 42
+ },
+ {
+ 'name': 'owner2',
+ 'importance': 3,
+ 'owns': 42
+ },
+ {
+ 'name': 'non-owner',
+ 'guid': 90
+ }
+ ]
+ ]);
+ }, /'owns' link\b.*'owner1'.*\binvalid source\b/);
+ link1.source = ownerDump1;
+
+ // Ownership link not in target's 'ownedBy' list.
+ ownedDump.ownedBy.pop();
+ assert.throws(function() {
+ checkDumpTrees(containerDumps, [
+ [
+ {
+ 'name': 'owned',
+ 'guid': 42
+ }
+ ],
+ [
+ {
+ 'name': 'owner1',
+ 'owns': 42
+ },
+ {
+ 'name': 'owner2',
+ 'importance': 3,
+ 'owns': 42
+ },
+ {
+ 'name': 'non-owner',
+ 'guid': 90
+ }
+ ]
+ ]);
+ }, /\btarget of\b.*'owner2'.*'ownedBy' list\b/);
+ ownedDump.ownedBy.push(link2);
+ });
+
+ /**
+ * Build container memory dumps from tree recipes, let the resulting
+ * GlobalMemoryDump calculate sizes and effective sizes, and then check that
+ * the augmented container memory dumps have the expected structure with
+ * correct sizes and effective sizes (as described by the same tree recipes).
+ *
+ * See the documentation for buildDumpTrees and checkDumpTrees for more
+ * details about the structure of tree recipes.
+ */
+ function testSizesCalculation(allTreeRecipes) {
+ const m = new Model();
+ const io = new tr.importer.ImportOptions();
+ io.showImportWarnings = false;
+ m.importOptions = io;
+
+ const containerDumps = buildDumpTrees(allTreeRecipes, m);
+ const gmd = containerDumps[0];
+ gmd.calculateSizes();
+ gmd.calculateEffectiveSizes();
+ gmd.forceRebuildingMemoryAllocatorDumpByFullNameIndices();
+ checkDumpTrees(containerDumps, allTreeRecipes);
+ }
+
+ // Check that the testSizesCalculation testing helper method above
+ // actually performs the expected checks. Since it will be used heavily
+ // throughout this file (where it is expected to pass), we only need to
+ // verify that it does indeed fail when it should.
+ test('testSanityCheck_testSizesCalculation', function() {
+ assert.throws(function() {
+ testSizesCalculation([
+ [],
+ undefined,
+ [
+ {
+ 'name': 'winheap'
+ },
+ {
+ 'name': 'malloc',
+ 'expected_size': 100,
+ 'children': [
+ {
+ 'name': 'allocated_objects',
+ 'size': 100,
+ 'expected_size': 100
+ },
+ {
+ 'name': 'extra',
+ 'size': 20,
+ 'expected_size': 20
+ }
+ ]
+ }
+ ]
+ ]);
+ }, /expected.*'size'.*value.*\b100\b.*got.*\b120\b/);
+ });
+
+ function calculationTest(caseName, treeRecipes) {
+ test('calculateSizes_' + caseName, function() {
+ testSizesCalculation(treeRecipes);
+ });
+ }
+
+ /**
+ * Build container memory dumps from tree recipes, let the resulting
+ * GlobalMemoryDump remove weak memory dumps, and then check that the updated
+ * container memory dumps have the expected structure (as described by the
+ * same tree recipes).
+ *
+ * See the documentation for buildDumpTrees and checkDumpTrees for more
+ * details about the structure of tree recipes.
+ */
+ function testWeakDumpRemoval(allTreeRecipes) {
+ const m = new tr.Model();
+ const io = new tr.importer.ImportOptions();
+ io.showImportWarnings = false;
+ m.importOptions = io;
+
+ const containerDumps = buildDumpTrees(allTreeRecipes, m);
+ const gmd = containerDumps[0];
+ gmd.removeWeakDumps();
+ gmd.forceRebuildingMemoryAllocatorDumpByFullNameIndices();
+ checkDumpTrees(containerDumps, allTreeRecipes);
+ }
+
+ // Similarly to testSanityCheck_testSizesCalculation, check that the
+ // testWeakDumpRemoval testing helper method above actually performs the
+ // expected checks.
+ test('testSanityCheck_testWeakDumpRemoval', function() {
+ assert.throws(function() {
+ testWeakDumpRemoval([
+ [],
+ undefined,
+ [
+ {
+ 'name': 'winheap'
+ },
+ {
+ 'name': 'malloc',
+ 'children': [
+ {
+ 'name': 'allocated_objects'
+ },
+ {
+ 'name': 'directly_weak',
+ 'guid': 42,
+ 'weak': true,
+ 'expected_removed': true
+ },
+ {
+ 'name': 'indirectly_weak',
+ 'owns': 42
+ // Missing: "'expected_removed': true".
+ }
+ ]
+ }
+ ]
+ ]);
+ }, /expected.*'malloc'.*\b2 children\b.*\bgot 1\b/);
+ });
+
+ function weakDumpRemovalTest(caseName, treeRecipes) {
+ test('removeWeakDumps_' + caseName, function() {
+ testWeakDumpRemoval(treeRecipes);
+ });
+ }
+
+ /////////////////////////////////////////////////////////////////////////////
+ // Actual tests begin here.
+ /////////////////////////////////////////////////////////////////////////////
+
+ test('iterateContainerDumps_withoutProcessMemoryDumps', function() {
+ const containerDumps = createContainerDumps(0);
+ const gmd = containerDumps[0];
+
+ const visitedContainerDumps = [];
+ gmd.iterateContainerDumps(buildArgPusher(visitedContainerDumps));
+ assertEqualUniqueMembers(visitedContainerDumps, containerDumps);
+ });
+
+ test('iterateContainerDumps_withProcessMemoryDumps', function() {
+ const containerDumps = createContainerDumps(2);
+ const gmd = containerDumps[0];
+
+ const visitedContainerDumps = [];
+ gmd.iterateContainerDumps(buildArgPusher(visitedContainerDumps));
+ assertEqualUniqueMembers(visitedContainerDumps, containerDumps);
+ });
+
+ test('iterateAllRootAllocatorDumps', function() {
+ const containerDumps = buildDumpTrees([
+ [ // GMD.
+ {
+ 'name': 'globalSharedDump1'
+ },
+ {
+ 'name': 'globalSharedDump2'
+ }
+ ],
+ [ // PMD.
+ {
+ 'name': 'v8',
+ 'children': [
+ {
+ 'name': 'isolate'
+ }
+ ]
+ }
+ ]
+ ]);
+ const gmd = containerDumps[0];
+ const pmd = containerDumps[1];
+
+ const visitedAllocatorDumps = [];
+ gmd.iterateAllRootAllocatorDumps(buildArgPusher(visitedAllocatorDumps));
+ assertEqualUniqueMembers(visitedAllocatorDumps, [
+ gmd.getMemoryAllocatorDumpByFullName('globalSharedDump1'),
+ gmd.getMemoryAllocatorDumpByFullName('globalSharedDump2'),
+ pmd.getMemoryAllocatorDumpByFullName('v8')
+ ]);
+ });
+
+ test('traverseAllocatorDumpsInDepthFirstOrder_oneTreeWithoutOwners',
+ function() {
+ const containerDumps = buildDumpTrees([
+ [ // GMD.
+ {
+ 'name': 'root',
+ 'children': [
+ {
+ 'name': 'parent',
+ 'children': [
+ {
+ 'name': 'child1'
+ },
+ {
+ 'name': 'child2'
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ ]);
+ const gmd = containerDumps[0];
+
+ // Post-order.
+ let visitedAllocatorDumps = [];
+ gmd.traverseAllocatorDumpsInDepthFirstPostOrder(
+ buildArgPusher(visitedAllocatorDumps));
+ assert.deepEqual(visitedAllocatorDumps, [
+ gmd.getMemoryAllocatorDumpByFullName('root/parent/child1'),
+ gmd.getMemoryAllocatorDumpByFullName('root/parent/child2'),
+ gmd.getMemoryAllocatorDumpByFullName('root/parent'),
+ gmd.getMemoryAllocatorDumpByFullName('root')
+ ]);
+
+ // Pre-order.
+ visitedAllocatorDumps = [];
+ gmd.traverseAllocatorDumpsInDepthFirstPreOrder(
+ buildArgPusher(visitedAllocatorDumps));
+ assert.deepEqual(visitedAllocatorDumps, [
+ gmd.getMemoryAllocatorDumpByFullName('root'),
+ gmd.getMemoryAllocatorDumpByFullName('root/parent'),
+ gmd.getMemoryAllocatorDumpByFullName('root/parent/child1'),
+ gmd.getMemoryAllocatorDumpByFullName('root/parent/child2')
+ ]);
+ });
+
+ test('traverseAllocatorDumpsInDepthFirstOrder_oneTreeWithOwners',
+ function() {
+ const containerDumps = buildDumpTrees([
+ [], // GMD.
+ [ // PMD.
+ {
+ 'name': 'root',
+ 'children': [
+ {
+ 'name': 'parent',
+ 'children': [
+ {
+ 'name': 'child1',
+ 'owns': 0
+ },
+ {
+ 'name': 'child2',
+ 'guid': 0
+ },
+ {
+ 'name': 'child3',
+ 'owns': 0
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ ]);
+ const gmd = containerDumps[0];
+ const pmd = containerDumps[1];
+
+ // Post-order.
+ let visitedAllocatorDumps = [];
+ gmd.traverseAllocatorDumpsInDepthFirstPostOrder(
+ buildArgPusher(visitedAllocatorDumps));
+ assert.deepEqual(visitedAllocatorDumps, [
+ pmd.getMemoryAllocatorDumpByFullName('root/parent/child1'),
+ pmd.getMemoryAllocatorDumpByFullName('root/parent/child3'),
+ pmd.getMemoryAllocatorDumpByFullName('root/parent/child2'),
+ pmd.getMemoryAllocatorDumpByFullName('root/parent'),
+ pmd.getMemoryAllocatorDumpByFullName('root')
+ ]);
+
+ // Pre-order.
+ visitedAllocatorDumps = [];
+ gmd.traverseAllocatorDumpsInDepthFirstPreOrder(
+ buildArgPusher(visitedAllocatorDumps));
+ assert.deepEqual(visitedAllocatorDumps, [
+ pmd.getMemoryAllocatorDumpByFullName('root'),
+ pmd.getMemoryAllocatorDumpByFullName('root/parent'),
+ pmd.getMemoryAllocatorDumpByFullName('root/parent/child2'),
+ pmd.getMemoryAllocatorDumpByFullName('root/parent/child1'),
+ pmd.getMemoryAllocatorDumpByFullName('root/parent/child3')
+ ]);
+ });
+
+ test('traverseAllocatorDumpsInDepthFirstOrder_multipleTrees', function() {
+ const containerDumps = buildDumpTrees([
+ [ // GMD.
+ {
+ 'name': 'shared',
+ 'children': [
+ {
+ 'name': 'pool1',
+ 'guid': 1
+ },
+ {
+ 'name': 'pool2',
+ 'owns': 3
+ }
+ ]
+ }
+ ],
+ [ // PMD.
+ {
+ 'name': 'oilpan',
+ 'children': [
+ {
+ 'name': 'objects'
+ },
+ {
+ 'name': 'heaps',
+ 'owns': 1,
+ 'children': [
+ {
+ 'name': 'small',
+ 'guid': 2
+ },
+ {
+ 'name': 'large'
+ }
+ ]
+ }
+ ]
+ },
+ {
+ 'name': 'v8',
+ 'children': [
+ {
+ 'name': 'isolate1',
+ 'owns': 2
+ },
+ {
+ 'name': 'isolate2',
+ 'guid': 3
+ }
+ ]
+ }
+ ]
+ ]);
+ const gmd = containerDumps[0];
+ const pmd = containerDumps[1];
+
+ // Post-order.
+ let visitedAllocatorDumps = [];
+ gmd.traverseAllocatorDumpsInDepthFirstPostOrder(
+ buildArgPusher(visitedAllocatorDumps));
+ assert.deepEqual(visitedAllocatorDumps, [
+ pmd.getMemoryAllocatorDumpByFullName('v8/isolate1'),
+ pmd.getMemoryAllocatorDumpByFullName('oilpan/heaps/small'),
+ pmd.getMemoryAllocatorDumpByFullName('oilpan/heaps/large'),
+ pmd.getMemoryAllocatorDumpByFullName('oilpan/heaps'),
+ gmd.getMemoryAllocatorDumpByFullName('shared/pool1'),
+ gmd.getMemoryAllocatorDumpByFullName('shared/pool2'),
+ gmd.getMemoryAllocatorDumpByFullName('shared'),
+ pmd.getMemoryAllocatorDumpByFullName('oilpan/objects'),
+ pmd.getMemoryAllocatorDumpByFullName('oilpan'),
+ pmd.getMemoryAllocatorDumpByFullName('v8/isolate2'),
+ pmd.getMemoryAllocatorDumpByFullName('v8')
+ ]);
+
+ // Pre-order.
+ visitedAllocatorDumps = [];
+ gmd.traverseAllocatorDumpsInDepthFirstPreOrder(
+ buildArgPusher(visitedAllocatorDumps));
+ assert.deepEqual(visitedAllocatorDumps, [
+ gmd.getMemoryAllocatorDumpByFullName('shared'),
+ gmd.getMemoryAllocatorDumpByFullName('shared/pool1'),
+ pmd.getMemoryAllocatorDumpByFullName('oilpan'),
+ pmd.getMemoryAllocatorDumpByFullName('oilpan/objects'),
+ pmd.getMemoryAllocatorDumpByFullName('oilpan/heaps'),
+ pmd.getMemoryAllocatorDumpByFullName('oilpan/heaps/small'),
+ pmd.getMemoryAllocatorDumpByFullName('oilpan/heaps/large'),
+ pmd.getMemoryAllocatorDumpByFullName('v8'),
+ pmd.getMemoryAllocatorDumpByFullName('v8/isolate1'),
+ pmd.getMemoryAllocatorDumpByFullName('v8/isolate2'),
+ gmd.getMemoryAllocatorDumpByFullName('shared/pool2')
+ ]);
+ });
+
+ test('traverseAllocatorDumpsInDepthFirstPostOrder_cycle', function() {
+ const containerDumps = buildDumpTrees([
+ [ // GMD.
+ {
+ 'name': 'shared',
+ 'owns': 2,
+ 'children': [
+ {
+ 'name': 'pool',
+ 'guid': 1
+ }
+ ]
+ }
+ ],
+ [ // PMD.
+ {
+ 'name': 'oilpan',
+ 'owns': 1,
+ 'children': [
+ {
+ 'name': 'objects',
+ 'guid': 2
+ }
+ ]
+ }
+ ]
+ ]);
+ const gmd = containerDumps[0];
+
+ // Post-order.
+ assert.throws(function() {
+ gmd.traverseAllocatorDumpsInDepthFirstPostOrder(function() {});
+ }, /contains.*cycle/);
+
+ // Pre-order.
+ const visitedAllocatorDumps = [];
+ gmd.traverseAllocatorDumpsInDepthFirstPreOrder(
+ buildArgPusher(visitedAllocatorDumps));
+ assert.deepEqual(visitedAllocatorDumps, []);
+ });
+
+ // Just check that the method doesn't crash upon encountering empty and/or
+ // undefined memory allocator dumps.
+ calculationTest('noDumps', [
+ undefined, // GMD.
+ [], // PMD1.
+ undefined // PMD2.
+ ]);
+
+ calculationTest('flatDumps', [
+ [ // GMD.
+ {
+ 'name': 'shared',
+ 'size': 1024,
+ 'expected_size': 1024,
+ 'expected_effective_size': 1024
+ }
+ ],
+ [ // PMD.
+ {
+ 'name': 'gpu'
+ }
+ ]
+ ]);
+
+ calculationTest('zeroSizes', [
+ [ // GMD.
+ {
+ 'name': 'shared',
+ 'size': 0,
+ 'expected_size': 0,
+ 'expected_effective_size': 0
+ }
+ ],
+ [ // PMD1.
+ {
+ 'name': 'gpu',
+ 'expected_size': 0,
+ 'expected_effective_size': 0,
+ 'children': [
+ {
+ 'name': 'zero',
+ 'size': 0,
+ 'expected_size': 0,
+ 'expected_effective_size': 0
+ }
+ ]
+ }
+ ],
+ [ // PMD2.
+ {
+ 'name': 'gpu',
+ 'expected_size': 0,
+ 'expected_effective_size': 0,
+ 'children': [
+ {
+ 'name': 'zero',
+ 'size': 0,
+ 'expected_size': 0,
+ 'expected_effective_size': 0
+ },
+ {
+ 'name': 'undefined'
+ }
+ ]
+ }
+ ]
+ ]);
+
+ calculationTest('children_allSizesUndefined', [
+ [
+ {
+ 'name': 'parent',
+ 'children': [
+ {
+ 'name': 'child1'
+ },
+ {
+ 'name': 'child2'
+ }
+ ]
+ }
+ ]
+ ]);
+
+ calculationTest('children_parentSizeUndefined', [
+ [
+ {
+ 'name': 'parent',
+ 'expected_size': 384,
+ 'expected_effective_size': 384,
+ 'children': [
+ {
+ 'name': 'child1',
+ 'size': 128,
+ 'expected_size': 128,
+ 'expected_effective_size': 128
+ },
+ {
+ 'name': 'child2',
+ 'size': 256,
+ 'expected_size': 256,
+ 'expected_effective_size': 256
+ }
+ ]
+ }
+ ]
+ ]);
+
+ calculationTest('children_parentSizeDefined_childrenAddUp', [
+ [
+ {
+ 'name': 'parent',
+ 'size': 0,
+ 'expected_size': 0,
+ 'expected_effective_size': 0,
+ 'children': [
+ {
+ 'name': 'child1'
+ },
+ {
+ 'name': 'child2'
+ }
+ ]
+ }
+ ]
+ ]);
+
+ calculationTest('children_parentSizeDefined_childrenDontAddUp', [
+ [
+ {
+ 'name': 'parent',
+ 'size': 2048,
+ 'expected_size': 2048,
+ 'expected_effective_size': 2048,
+ 'children': [
+ {
+ 'name': '<unspecified>',
+ 'skip_build': true,
+ 'expected_size': 2048,
+ 'expected_effective_size': 2048
+ },
+ {
+ 'name': 'child1'
+ },
+ {
+ 'name': 'child2'
+ }
+ ]
+ }
+ ]
+ ]);
+
+ calculationTest('children_oneChildSizeUndefined_childrenAddUp', [
+ [
+ {
+ 'name': 'parent',
+ 'size': 4096,
+ 'expected_size': 4096,
+ 'expected_effective_size': 4096,
+ 'children': [
+ {
+ 'name': 'child1'
+ },
+ {
+ 'name': 'child2',
+ 'size': 4096,
+ 'expected_size': 4096,
+ 'expected_effective_size': 4096
+ }
+ ]
+ }
+ ]
+ ]);
+
+ calculationTest('children_oneChildSizeUndefined_childrenDontAddUp', [
+ [
+ {
+ 'name': 'parent',
+ 'size': 6144,
+ 'expected_size': 6144,
+ 'expected_effective_size': 6144,
+ 'children': [
+ {
+ 'name': '<unspecified>',
+ 'skip_build': true,
+ 'expected_size': 2048,
+ 'expected_effective_size': 2048
+ },
+ {
+ 'name': 'child1'
+ },
+ {
+ 'name': 'child2',
+ 'size': 4096,
+ 'expected_size': 4096,
+ 'expected_effective_size': 4096
+ }
+ ]
+ }
+ ]
+ ]);
+
+ calculationTest('children_allSizesDefined_childrenAddUp', [
+ [
+ {
+ 'name': 'parent',
+ 'size': 100,
+ 'expected_size': 100,
+ 'expected_effective_size': 100,
+ 'children': [
+ {
+ 'name': 'child1',
+ 'size': 70,
+ 'expected_size': 70,
+ 'expected_effective_size': 70
+ },
+ {
+ 'name': 'child2',
+ 'size': 30,
+ 'expected_size': 30,
+ 'expected_effective_size': 30
+ }
+ ]
+ }
+ ]
+ ]);
+
+ calculationTest('children_allSizesDefined_childrenDontUp', [
+ [
+ {
+ 'name': 'parent',
+ 'size': 150,
+ 'expected_size': 150,
+ 'expected_effective_size': 150,
+ 'children': [
+ {
+ 'name': '<unspecified>',
+ 'skip_build': true,
+ 'expected_size': 50,
+ 'expected_effective_size': 50
+ },
+ {
+ 'name': 'child1',
+ 'size': 70,
+ 'expected_size': 70,
+ 'expected_effective_size': 70
+ },
+ {
+ 'name': 'child2',
+ 'size': 30,
+ 'expected_size': 30,
+ 'expected_effective_size': 30
+ }
+ ]
+ }
+ ]
+ ]);
+
+ calculationTest('children_oneChildSizeDefined', [
+ [
+ {
+ 'name': 'parent',
+ 'expected_size': 49,
+ 'expected_effective_size': 49,
+ 'children': [
+ {
+ 'name': 'child1',
+ 'size': 49,
+ 'expected_size': 49,
+ 'expected_effective_size': 49
+ },
+ {
+ 'name': 'child2'
+ }
+ ]
+ }
+ ]
+ ]);
+
+ calculationTest('children_multipleLevels', [
+ [], // GMD.
+ [ // PMD.
+ {
+ 'name': 'v8',
+ 'expected_size': 36,
+ 'expected_effective_size': 36,
+ 'children': [
+ {
+ 'name': 'isolate1',
+ 'size': 10,
+ 'expected_size': 10,
+ 'expected_effective_size': 10,
+ 'children': [
+ {
+ 'skip_build': true,
+ 'name': '<unspecified>',
+ 'expected_size': 3,
+ 'expected_effective_size': 3
+ },
+ {
+ 'name': 'objects',
+ 'size': 3,
+ 'expected_size': 3,
+ 'expected_effective_size': 3
+ },
+ {
+ 'name': 'heaps',
+ 'size': 4,
+ 'expected_size': 4,
+ 'expected_effective_size': 4
+ }
+ ]
+ },
+ {
+ 'name': 'isolate2',
+ 'size': 12,
+ 'expected_size': 12,
+ 'expected_effective_size': 12,
+ 'children': [
+ {
+ 'name': 'objects',
+ 'size': 5,
+ 'expected_size': 5,
+ 'expected_effective_size': 5
+ },
+ {
+ 'name': 'heaps',
+ 'size': 7,
+ 'expected_size': 7,
+ 'expected_effective_size': 7
+ }
+ ]
+ },
+ {
+ 'name': 'isolate3',
+ 'expected_size': 14,
+ 'expected_effective_size': 14,
+ 'children': [
+ {
+ 'name': 'objects',
+ 'size': 14,
+ 'expected_size': 14,
+ 'expected_effective_size': 14
+ },
+ {
+ 'name': 'heaps'
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ ]);
+
+ calculationTest('owners_allSizesUndefined', [
+ [ // GMD.
+ {
+ 'name': 'bitmap',
+ 'guid': 7
+ }
+ ],
+ [ // PMD1.
+ {
+ 'name': 'tile',
+ 'owns': 7,
+ 'importance': 1
+ }
+ ],
+ [ // PMD2.
+ {
+ 'name': 'chunk',
+ 'owns': 7
+ }
+ ]
+ ]);
+
+ calculationTest('owners_ownedSizeDefined', [
+ [ // GMD.
+ {
+ 'name': 'bitmap',
+ 'size': 15,
+ 'expected_size': 15,
+ 'expected_effective_size': 15,
+ 'guid': 7
+ }
+ ],
+ [ // PMD1.
+ {
+ 'name': 'tile',
+ 'owns': 7
+ }
+ ],
+ [ // PMD2.
+ {
+ 'name': 'chunk',
+ 'owns': 7
+ }
+ ]
+ ]);
+
+ calculationTest('owners_ownedSizeUndefined', [
+ [ // GMD.
+ {
+ 'name': 'bitmap',
+ 'expected_size': 9,
+ 'expected_effective_size': 0,
+ 'guid': 7
+ }
+ ],
+ [ // PMD1.
+ {
+ 'name': 'tile',
+ 'size': 5,
+ 'expected_size': 5,
+ 'expected_effective_size': 2.5,
+ 'owns': 7
+ }
+ ],
+ [ // PMD2.
+ {
+ 'name': 'chunk',
+ 'size': 9,
+ 'expected_size': 9,
+ 'expected_effective_size': 6.5,
+ 'owns': 7
+ }
+ ]
+ ]);
+
+ calculationTest('owners_oneOwnerSizeDefined', [
+ [ // GMD.
+ {
+ 'name': 'bitmap',
+ 'expected_size': 16,
+ 'expected_effective_size': 0,
+ 'guid': 7
+ }
+ ],
+ [ // PMD1.
+ {
+ 'name': 'tile',
+ 'size': 16,
+ 'expected_size': 16,
+ 'expected_effective_size': 16,
+ 'owns': 7
+ }
+ ],
+ [ // PMD2.
+ {
+ 'name': 'chunk',
+ 'owns': 7
+ }
+ ]
+ ]);
+
+ calculationTest('owners_oneOwnerSizeUndefined', [
+ [ // GMD.
+ {
+ 'name': 'bitmap',
+ 'size': 20,
+ 'expected_size': 20,
+ 'expected_effective_size': 2,
+ 'guid': 7
+ }
+ ],
+ [ // PMD1.
+ {
+ 'name': 'tile',
+ 'owns': 7
+ }
+ ],
+ [ // PMD2.
+ {
+ 'name': 'chunk',
+ 'size': 18,
+ 'expected_size': 18,
+ 'expected_effective_size': 18,
+ 'owns': 7
+ }
+ ]
+ ]);
+
+ calculationTest('owners_allSizesDefined', [
+ [ // GMD.
+ {
+ 'name': 'bitmap',
+ 'size': 60,
+ 'expected_size': 60,
+ 'expected_effective_size': 31,
+ 'guid': 7
+ }
+ ],
+ [ // PMD1.
+ {
+ 'name': 'tile',
+ 'size': 29,
+ 'expected_size': 29,
+ 'expected_effective_size': 19.5,
+ 'owns': 7
+ }
+ ],
+ [ // PMD2.
+ {
+ 'name': 'chunk',
+ 'size': 19,
+ 'expected_size': 19,
+ 'expected_effective_size': 9.5,
+ 'owns': 7
+ }
+ ]
+ ]);
+
+ calculationTest('owners_hierarchy', [
+ [ // GMD.
+ {
+ 'name': 'bitmap',
+ 'expected_size': 50,
+ 'expected_effective_size': 0,
+ 'guid': 7
+ }
+ ],
+ [ // PMD1.
+ {
+ 'name': 'tile',
+ 'expected_size': 50,
+ 'expected_effective_size': 0,
+ 'owns': 7,
+ 'guid': 0
+ },
+ {
+ 'name': 'object1',
+ 'size': 30,
+ 'owns': 0,
+ 'expected_size': 30,
+ 'expected_effective_size': 9
+ },
+ {
+ 'name': 'object2',
+ 'owns': 0
+ },
+ {
+ 'name': 'object3',
+ 'size': 50,
+ 'owns': 0,
+ 'expected_size': 50,
+ 'expected_effective_size': 21
+ }
+ ],
+ [ // PMD2.
+ {
+ 'name': 'chunk',
+ 'size': 40,
+ 'expected_size': 40,
+ 'expected_effective_size': 20,
+ 'owns': 7
+ }
+ ]
+ ]);
+
+ calculationTest('owners_withChildren', [
+ [ // GMD.
+ {
+ 'name': 'bitmap',
+ 'guid': 7,
+ 'expected_size': 48,
+ 'expected_effective_size': 17,
+ 'children': [
+ {
+ 'name': 'subbitmap1',
+ 'size': 32,
+ 'expected_size': 32,
+ 'expected_effective_size': 17 * (32 / 48)
+ },
+ {
+ 'name': 'subbitmap2',
+ 'size': 16,
+ 'expected_size': 16,
+ 'expected_effective_size': 17 * (16 / 48)
+ }
+ ]
+ }
+ ],
+ [ // PMD.
+ {
+ 'name': 'tile',
+ 'expected_size': 31,
+ 'expected_effective_size': 0,
+ 'guid': 8,
+ 'owns': 7,
+ 'children': [
+ {
+ 'name': '<unspecified>',
+ 'skip_build': true,
+ 'expected_size': 7,
+ 'expected_effective_size': 0
+ },
+ {
+ 'name': 'subtile',
+ 'size': 24,
+ 'expected_size': 24,
+ 'expected_effective_size': 0
+ }
+ ]
+ },
+ {
+ 'name': 'cc',
+ 'owns': 8,
+ 'size': 31,
+ 'expected_size': 31,
+ 'expected_effective_size': 31
+ }
+ ]
+ ]);
+
+ calculationTest('owners_withParents', [
+ [ // GMD.
+ {
+ 'name': 'bitmap',
+ 'size': 96,
+ 'expected_size': 96,
+ 'expected_effective_size': 32,
+ 'children': [
+ {
+ 'name': '<unspecified>',
+ 'skip_build': true,
+ 'expected_size': 32,
+ 'expected_effective_size': 32
+ },
+ {
+ 'name': 'subbitmap',
+ 'guid': 2,
+ 'expected_size': 64,
+ 'expected_effective_size': 0
+ }
+ ]
+ }
+ ],
+ [ // PMD.
+ {
+ 'name': 'tile',
+ 'expected_size': 64,
+ 'expected_effective_size': 0,
+ 'children': [
+ {
+ 'name': 'subtile',
+ 'guid': 1,
+ 'owns': 2,
+ 'expected_size': 64,
+ 'expected_effective_size': 0
+ }
+ ]
+ },
+ {
+ 'name': 'cc',
+ 'owns': 1,
+ 'size': 64,
+ 'expected_size': 64,
+ 'expected_effective_size': 64
+ }
+ ]
+ ]);
+
+ calculationTest('owners_multipleLevels', [
+ [ // GMD.
+ {
+ 'name': 'bitmap',
+ 'size': 96,
+ 'expected_size': 96,
+ 'expected_effective_size': 32,
+ 'children': [
+ {
+ 'name': '<unspecified>',
+ 'skip_build': true,
+ 'expected_size': 32,
+ 'expected_effective_size': 32
+ },
+ {
+ 'name': 'subbitmap',
+ 'guid': 2,
+ 'expected_size': 64,
+ 'expected_effective_size': 0
+ }
+ ]
+ }
+ ],
+ [ // PMD.
+ {
+ 'name': 'tile',
+ 'expected_size': 64,
+ 'expected_effective_size': 0,
+ 'owns': 2,
+ 'children': [
+ {
+ 'name': 'subtile',
+ 'guid': 1,
+ 'expected_size': 64,
+ 'expected_effective_size': 0
+ }
+ ]
+ },
+ {
+ 'name': 'cc',
+ 'owns': 1,
+ 'size': 64,
+ 'expected_size': 64,
+ 'expected_effective_size': 64
+ }
+ ]
+ ]);
+
+ calculationTest('views_allSizesUndefined', [
+ [
+ {
+ 'name': 'v8',
+ 'children': [
+ {
+ 'name': 'v8/heaps',
+ 'guid': 1
+ },
+ {
+ 'name': 'v8/objects',
+ 'owns': 1
+ }
+ ]
+ }
+ ]
+ ]);
+
+ calculationTest('views_ownedSizeDefined', [
+ [
+ {
+ 'name': 'v8',
+ 'expected_size': 10,
+ 'expected_effective_size': 10,
+ 'children': [
+ {
+ 'name': 'heaps',
+ 'guid': 1,
+ 'size': 10,
+ 'expected_size': 10,
+ 'expected_effective_size': 10
+ },
+ {
+ 'name': 'objects',
+ 'owns': 1
+ }
+ ]
+ }
+ ]
+ ]);
+
+ calculationTest('views_ownerSizeDefined', [
+ [
+ {
+ 'name': 'v8',
+ 'expected_size': 20,
+ 'expected_effective_size': 20,
+ 'children': [
+ {
+ 'name': 'heaps',
+ 'guid': 1,
+ 'expected_size': 20,
+ 'expected_effective_size': 0,
+ 'expected_owned_by_sibling_sizes': {
+ 'objects': 20
+ }
+ },
+ {
+ 'name': 'objects',
+ 'owns': 1,
+ 'size': 20,
+ 'expected_size': 20,
+ 'expected_effective_size': 20
+ }
+ ]
+ }
+ ]
+ ]);
+
+ calculationTest('views_parentSizeUndefined', [
+ [
+ {
+ 'name': 'v8',
+ 'expected_size': 30,
+ 'expected_effective_size': 30,
+ 'children': [
+ {
+ 'name': 'heaps',
+ 'guid': 1,
+ 'size': 30,
+ 'expected_size': 30,
+ 'expected_effective_size': 10,
+ 'expected_owned_by_sibling_sizes': {
+ 'objects': 20
+ }
+ },
+ {
+ 'name': 'objects',
+ 'owns': 1,
+ 'size': 20,
+ 'expected_size': 20,
+ 'expected_effective_size': 20
+ }
+ ]
+ }
+ ]
+ ]);
+
+ calculationTest('views_ownerSizeUndefined_childrenAddUp', [
+ [
+ {
+ 'name': 'v8',
+ 'size': 30,
+ 'expected_size': 30,
+ 'expected_effective_size': 30,
+ 'children': [
+ {
+ 'name': 'heaps',
+ 'guid': 1,
+ 'size': 30,
+ 'expected_size': 30,
+ 'expected_effective_size': 30
+ },
+ {
+ 'name': 'objects',
+ 'owns': 1
+ }
+ ]
+ }
+ ]
+ ]);
+
+ calculationTest('views_ownerSizeUndefined_childrenDontAddUp', [
+ [
+ {
+ 'name': 'v8',
+ 'size': 40,
+ 'expected_size': 40,
+ 'expected_effective_size': 40,
+ 'children': [
+ {
+ 'name': '<unspecified>',
+ 'skip_build': true,
+ 'expected_size': 10,
+ 'expected_effective_size': 10
+ },
+ {
+ 'name': 'heaps',
+ 'guid': 1,
+ 'size': 30,
+ 'expected_size': 30,
+ 'expected_effective_size': 30
+ },
+ {
+ 'name': 'objects',
+ 'owns': 1
+ }
+ ]
+ }
+ ]
+ ]);
+
+ calculationTest('views_ownedSizeUndefined_childrenAddUp', [
+ [
+ {
+ 'name': 'v8',
+ 'size': 30,
+ 'expected_size': 30,
+ 'expected_effective_size': 30,
+ 'children': [
+ {
+ 'name': 'heaps',
+ 'guid': 1,
+ 'expected_size': 30,
+ 'expected_effective_size': 0,
+ 'expected_owned_by_sibling_sizes': {
+ 'objects': 30
+ }
+ },
+ {
+ 'name': 'objects',
+ 'owns': 1,
+ 'size': 30,
+ 'expected_size': 30,
+ 'expected_effective_size': 30
+ }
+ ]
+ }
+ ]
+ ]);
+
+ calculationTest('views_ownedSizeUndefined_childrenDontAddUp', [
+ [
+ {
+ 'name': 'v8',
+ 'size': 40,
+ 'expected_size': 40,
+ 'expected_effective_size': 40,
+ 'children': [
+ {
+ 'name': '<unspecified>',
+ 'skip_build': true,
+ 'expected_size': 10,
+ 'expected_effective_size': 10
+ },
+ {
+ 'name': 'heaps',
+ 'guid': 1,
+ 'expected_size': 30,
+ 'expected_effective_size': 0,
+ 'expected_owned_by_sibling_sizes': {
+ 'objects': 30
+ }
+ },
+ {
+ 'name': 'objects',
+ 'owns': 1,
+ 'size': 30,
+ 'expected_size': 30,
+ 'expected_effective_size': 30
+ }
+ ]
+ }
+ ]
+ ]);
+
+ calculationTest('views_allSizesDefined_childrenAddUp', [
+ [
+ {
+ 'name': 'v8',
+ 'size': 30,
+ 'expected_size': 30,
+ 'expected_effective_size': 30,
+ 'children': [
+ {
+ 'name': 'heaps',
+ 'guid': 1,
+ 'size': 30,
+ 'expected_size': 30,
+ 'expected_effective_size': 16,
+ 'expected_owned_by_sibling_sizes': {
+ 'objects': 14
+ }
+ },
+ {
+ 'name': 'objects',
+ 'owns': 1,
+ 'size': 14,
+ 'expected_size': 14,
+ 'expected_effective_size': 14
+ }
+ ]
+ }
+ ]
+ ]);
+
+ calculationTest('views_allSizesDefined_childrenDontAddUp', [
+ [
+ {
+ 'name': 'v8',
+ 'size': 35,
+ 'expected_size': 35,
+ 'expected_effective_size': 35,
+ 'children': [
+ {
+ 'name': '<unspecified>',
+ 'skip_build': true,
+ 'expected_size': 5,
+ 'expected_effective_size': 5
+ },
+ {
+ 'name': 'heaps',
+ 'guid': 1,
+ 'size': 30,
+ 'expected_size': 30,
+ 'expected_effective_size': 16,
+ 'expected_owned_by_sibling_sizes': {
+ 'objects': 14
+ }
+ },
+ {
+ 'name': 'objects',
+ 'owns': 1,
+ 'size': 14,
+ 'expected_size': 14,
+ 'expected_effective_size': 14
+ }
+ ]
+ }
+ ]
+ ]);
+
+ calculationTest('views_deep', [
+ [
+ {
+ 'name': 'root',
+ 'expected_size': 17,
+ 'expected_effective_size': 17,
+ 'children': [
+ {
+ 'name': 'parent1',
+ 'size': 10,
+ 'expected_size': 10,
+ 'expected_effective_size': 5,
+ 'expected_owned_by_sibling_sizes': {
+ 'parent2': 5
+ },
+ 'children': [
+ {
+ 'name': '<unspecified>',
+ 'skip_build': true,
+ 'expected_size': 2,
+ 'expected_effective_size': 2
+ },
+ {
+ 'name': 'child',
+ 'guid': 1,
+ 'size': 8,
+ 'expected_size': 8,
+ 'expected_effective_size': 3
+ }
+ ]
+ },
+ {
+ 'name': 'parent2',
+ 'size': 8,
+ 'expected_size': 8,
+ 'expected_effective_size': 5,
+ 'expected_owned_by_sibling_sizes': {
+ 'parent3': 3
+ },
+ 'children': [
+ {
+ 'name': '<unspecified>',
+ 'skip_build': true,
+ 'expected_size': 3,
+ 'expected_effective_size': 3
+ },
+ {
+ 'name': 'child',
+ 'guid': 2,
+ 'owns': 1,
+ 'size': 5,
+ 'expected_size': 5,
+ 'expected_effective_size': 2
+ }
+ ]
+ },
+ {
+ 'name': 'parent3',
+ 'size': 7,
+ 'expected_size': 7,
+ 'expected_effective_size': 7,
+ 'children': [
+ {
+ 'name': '<unspecified>',
+ 'skip_build': true,
+ 'expected_size': 4,
+ 'expected_effective_size': 4
+ },
+ {
+ 'name': 'child',
+ 'owns': 2,
+ 'size': 3,
+ 'expected_size': 3,
+ 'expected_effective_size': 3
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ ]);
+
+ calculationTest('views_nested', [
+ [
+ {
+ 'name': 'system',
+ 'expected_size': 7,
+ 'expected_effective_size': 7,
+ 'children': [
+ {
+ 'name': 'subsystem-A',
+ 'owns': 15,
+ 'expected_size': 5,
+ 'expected_effective_size': 5,
+ 'children': [
+ {
+ 'name': 'objects',
+ 'owns': 30,
+ 'size': 3,
+ 'expected_size': 3,
+ 'expected_effective_size': 3
+ },
+ {
+ 'name': 'heaps',
+ 'guid': 30,
+ 'size': 5,
+ 'expected_size': 5,
+ 'expected_effective_size': 2,
+ 'expected_owned_by_sibling_sizes': {
+ 'objects': 3
+ }
+ }
+ ]
+ },
+ {
+ 'name': 'subsystem-B',
+ 'guid': 15,
+ 'expected_size': 7,
+ 'expected_effective_size': 2,
+ 'expected_owned_by_sibling_sizes': {
+ 'subsystem-A': 5
+ },
+ 'children': [
+ {
+ 'name': 'objects',
+ 'owns': 40,
+ 'size': 7,
+ 'expected_size': 7,
+ 'expected_effective_size': 2
+ },
+ {
+ 'name': 'heaps',
+ 'guid': 40,
+ 'expected_size': 7,
+ 'expected_effective_size': 0,
+ 'expected_owned_by_sibling_sizes': {
+ 'objects': 7
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ ]);
+
+ calculationTest('importance_equal', [
+ [ // GMD (both importances undefined and equal sizes).
+ {
+ 'name': 'owned',
+ 'guid': 1,
+ 'size': 10,
+ 'expected_size': 10,
+ 'expected_effective_size': 4
+ },
+ {
+ 'name': 'owner1',
+ 'owns': 1,
+ 'size': 6,
+ 'expected_size': 6,
+ 'expected_effective_size': 3
+ },
+ {
+ 'name': 'owner2',
+ 'owns': 1,
+ 'size': 6,
+ 'expected_size': 6,
+ 'expected_effective_size': 3
+ }
+ ],
+ [ // PMD1 (only one importance defined and different sizes).
+ {
+ 'name': 'owned',
+ 'guid': 2,
+ 'size': 20,
+ 'expected_size': 20,
+ 'expected_effective_size': 5
+ },
+ {
+ 'name': 'owner1',
+ 'owns': 2,
+ 'importance': 0,
+ 'size': 15,
+ 'expected_size': 15,
+ 'expected_effective_size': 10 / 2 + 5
+ },
+ {
+ 'name': 'owner2',
+ 'owns': 2,
+ 'size': 10,
+ 'expected_size': 10,
+ 'expected_effective_size': 10 / 2
+ }
+ ],
+ [ // PMD2 (all importances defined and different sizes).
+ {
+ 'name': 'owned',
+ 'guid': 3,
+ 'size': 15,
+ 'expected_size': 15,
+ 'expected_effective_size': 5
+ },
+ {
+ 'name': 'owner1',
+ 'owns': 3,
+ 'importance': 3,
+ 'size': 8,
+ 'expected_size': 8,
+ 'expected_effective_size': 8 / 3
+ },
+ {
+ 'name': 'owner2',
+ 'owns': 3,
+ 'importance': 3,
+ 'size': 9,
+ 'expected_size': 9,
+ 'expected_effective_size': 8 / 3 + 1 / 2
+ },
+ {
+ 'name': 'owner3',
+ 'owns': 3,
+ 'importance': 3,
+ 'size': 10,
+ 'expected_size': 10,
+ 'expected_effective_size': 8 / 3 + 1 / 2 + 1
+ }
+ ]
+ ]);
+
+ calculationTest('importance_notEqual', [
+ [ // GMD (one importance undefined and equal sizes).
+ {
+ 'name': 'owned',
+ 'guid': 1,
+ 'size': 10,
+ 'expected_size': 10,
+ 'expected_effective_size': 4
+ },
+ {
+ 'name': 'owner1',
+ 'owns': 1,
+ 'size': 6,
+ 'expected_size': 6,
+ 'expected_effective_size': 0
+ },
+ {
+ 'name': 'owner2',
+ 'owns': 1,
+ 'importance': 1,
+ 'size': 6,
+ 'expected_size': 6,
+ 'expected_effective_size': 6
+ }
+ ],
+ [ // PMD1 (one importance undefined and different sizes).
+ {
+ 'name': 'owned',
+ 'guid': 2,
+ 'size': 20,
+ 'expected_size': 20,
+ 'expected_effective_size': 4
+ },
+ {
+ 'name': 'owner1',
+ 'owns': 2,
+ 'importance': -1,
+ 'size': 16,
+ 'expected_size': 16,
+ 'expected_effective_size': 6
+ },
+ {
+ 'name': 'owner2',
+ 'owns': 2,
+ 'size': 10,
+ 'expected_size': 10,
+ 'expected_effective_size': 10
+ }
+ ],
+ [ // PMD2 (all importances defined and different sizes).
+ {
+ 'name': 'owned',
+ 'guid': 3,
+ 'size': 15,
+ 'expected_size': 15,
+ 'expected_effective_size': 5
+ },
+ {
+ 'name': 'owner1',
+ 'owns': 3,
+ 'importance': 4,
+ 'size': 8,
+ 'expected_size': 8,
+ 'expected_effective_size': 8
+ },
+ {
+ 'name': 'owner2',
+ 'owns': 3,
+ 'importance': 3,
+ 'size': 6,
+ 'expected_size': 6,
+ 'expected_effective_size': 0
+ },
+ {
+ 'name': 'owner3',
+ 'owns': 3,
+ 'importance': 2,
+ 'size': 10,
+ 'expected_size': 10,
+ 'expected_effective_size': 2
+ }
+ ]
+ ]);
+
+ // Example taken from GlobalMemoryDump.calculateDumpOwnershipCoefficient_()
+ // documentation.
+ calculationTest('importance_manyOwners', [
+ [ // GMD.
+ {
+ 'name': 'owned',
+ 'guid': 4,
+ 'size': 10,
+ 'expected_size': 10,
+ 'expected_effective_size': 2
+ }
+ ],
+ [ // PMD1.
+ {
+ 'name': 'owner1',
+ 'owns': 4,
+ 'importance': 2,
+ 'size': 6,
+ 'expected_size': 6,
+ 'expected_effective_size': 6 / 2
+ }
+ ],
+ [ // PMD2.
+ {
+ 'name': 'some_parent',
+ 'expected_size': 7,
+ 'expected_effective_size': 6 / 2 + 1,
+ 'children': [
+ {
+ 'name': 'owner2',
+ 'owns': 4,
+ 'importance': 2,
+ 'size': 7,
+ 'expected_size': 7,
+ 'expected_effective_size': 6 / 2 + 1
+ }
+ ]
+ }
+ ],
+ [ // PMD3.
+ {
+ 'name': 'owner3',
+ 'owns': 4,
+ 'importance': 1,
+ 'size': 5,
+ 'expected_size': 5,
+ 'expected_effective_size': 0
+ },
+ {
+ 'name': 'owner4',
+ 'owns': 4,
+ 'importance': 0,
+ 'size': 8,
+ 'expected_size': 8,
+ 'expected_effective_size': 1
+ }
+ ]
+ ]);
+
+ calculationTest('importance_chainOwnerships', [
+ [ // GMD.
+ {
+ 'name': 'owned',
+ 'guid': 5,
+ 'size': 10,
+ 'expected_size': 10,
+ 'expected_effective_size': 2
+ }
+ ],
+ [ // PMD1.
+ {
+ 'name': 'owner1',
+ 'owns': 5,
+ 'importance': 2,
+ 'guid': 6,
+ 'size': 6,
+ 'expected_size': 6,
+ 'expected_effective_size': 2
+ },
+ {
+ 'name': 'subowner1',
+ 'owns': 6,
+ 'size': 4,
+ 'expected_size': 4,
+ 'expected_effective_size': 4
+ }
+ ],
+ [ // PMD2.
+ {
+ 'name': 'owner2',
+ 'owns': 5,
+ 'importance': 1,
+ 'guid': 8,
+ 'size': 8,
+ 'expected_size': 8,
+ 'expected_effective_size': 2 - 2 / 4
+ },
+ {
+ 'name': 'subowner2',
+ 'owns': 8,
+ 'size': 2,
+ 'expected_size': 2,
+ 'expected_effective_size': 2 / 4
+ }
+ ]
+ ]);
+
+ calculationTest('importance_nested', [
+ [
+ {
+ 'name': 'grey',
+ 'guid': 15,
+ 'size': 20,
+ 'expected_size': 20,
+ 'expected_effective_size': 6
+ },
+ {
+ 'name': 'blue',
+ 'guid': 18,
+ 'owns': 15,
+ 'importance': 1,
+ 'size': 14,
+ 'expected_size': 14,
+ 'expected_effective_size': 1
+ },
+ {
+ 'name': 'purple',
+ 'owns': 15,
+ 'importance': 2,
+ 'size': 7,
+ 'expected_size': 7,
+ 'expected_effective_size': 7
+ },
+ {
+ 'name': 'yellow',
+ 'owns': 21,
+ 'importance': 3,
+ 'size': 10,
+ 'expected_size': 10,
+ 'expected_effective_size': 3
+ },
+ {
+ 'name': 'red',
+ 'guid': 21,
+ 'owns': 18,
+ 'size': 12,
+ 'expected_size': 12,
+ 'expected_effective_size': 1
+ },
+ {
+ 'name': 'green',
+ 'owns': 21,
+ 'importance': 3,
+ 'size': 8,
+ 'expected_size': 8,
+ 'expected_effective_size': 2
+ }
+ ]
+ ]);
+
+ calculationTest('importance_singleRefinement', [
+ [
+ {
+ 'name': 'v8',
+ 'expected_size': 13,
+ 'expected_effective_size': 13,
+ 'children': [
+ {
+ 'name': 'objects',
+ 'owns': 1,
+ 'size': 11,
+ 'expected_size': 11,
+ 'expected_effective_size': 11,
+ 'children': [
+ {
+ 'name': '<unspecified>',
+ 'skip_build': true,
+ 'expected_size': 4,
+ 'expected_effective_size': 4
+ },
+ {
+ 'name': 'object1',
+ 'owns': 2,
+ 'size': 7,
+ 'expected_size': 7,
+ 'expected_effective_size': 7
+ }
+ ]
+ },
+ {
+ 'name': 'heaps',
+ 'guid': 1,
+ 'size': 13,
+ 'expected_size': 13,
+ 'expected_effective_size': 2,
+ 'expected_owned_by_sibling_sizes': {
+ 'objects': 11
+ },
+ 'children': [
+ {
+ 'name': '<unspecified>',
+ 'skip_build': true,
+ 'expected_size': 3,
+ 'expected_effective_size': 1
+ },
+ {
+ 'name': 'heap1',
+ 'guid': 2,
+ 'size': 10,
+ 'expected_size': 10,
+ 'expected_effective_size': 1,
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ ]);
+
+ calculationTest('importance_sharedRefinement', [
+ [ // GMD.
+ {
+ 'name': 'shared_bitmap',
+ 'guid': 0,
+ 'size': 23,
+ 'expected_size': 23,
+ 'expected_effective_size': 5,
+ 'children': [
+ {
+ 'name': '<unspecified>',
+ 'skip_build': true,
+ 'expected_size': 13,
+ 'expected_effective_size': 13 * 5 / (13 + 3)
+ },
+ {
+ 'name': 'bitmap0x7',
+ 'guid': 999,
+ 'size': 10,
+ 'expected_size': 10,
+ 'expected_effective_size': 3 * 5 / (13 + 3),
+ }
+ ]
+ }
+ ],
+ [ // PMD1.
+ {
+ 'name': 'tile_manager',
+ 'owns': 0,
+ 'importance': 2,
+ 'size': 12,
+ 'expected_size': 12,
+ 'expected_effective_size': 5 + 2,
+ 'children': [
+ {
+ 'name': '<unspecified>',
+ 'skip_build': true,
+ 'expected_size': 5,
+ 'expected_effective_size': 5
+ },
+ {
+ 'name': 'tile42',
+ 'owns': 999,
+ 'importance': 1,
+ 'size': 7,
+ 'expected_size': 7,
+ 'expected_effective_size': 2,
+ }
+ ]
+ }
+ ],
+ [ // PMD2.
+ {
+ 'name': 'gpu',
+ 'owns': 0,
+ 'importance': 1,
+ 'size': 16,
+ 'expected_size': 16,
+ 'expected_effective_size': 6 + 5,
+ 'children': [
+ {
+ 'name': '<unspecified>',
+ 'skip_build': true,
+ 'expected_size': 11,
+ 'expected_effective_size': 6
+ },
+ {
+ 'name': 'chunk-3.14',
+ 'owns': 999,
+ 'importance': 2,
+ 'size': 5,
+ 'expected_size': 5,
+ 'expected_effective_size': 5,
+ }
+ ]
+ }
+ ]
+ ]);
+
+ // Example taken from https://goo.gl/fKg0dt.
+ calculationTest('documentationExample', [
+ [ // GMD, Global (shared) memory.
+ {
+ 'name': 'unknown',
+ 'guid': 2,
+ 'expected_size': 16,
+ 'expected_effective_size': 0,
+ }
+ ],
+ [ // PMD1, Browser process.
+ {
+ 'name': 'sharedbitmap',
+ 'size': 17,
+ 'expected_size': 17,
+ 'expected_effective_size': 9,
+ 'children': [
+ {
+ 'name': '<unspecified>',
+ 'skip_build': true,
+ 'expected_size': 1,
+ 'expected_effective_size': 1
+ },
+ {
+ 'name': '0x7',
+ 'size': 16,
+ 'expected_size': 16,
+ 'expected_effective_size': 8,
+ 'owns': 2,
+ 'importance': 1,
+ 'children': [
+ {
+ 'name': '<unspecified>',
+ 'skip_build': true,
+ 'expected_size': 16,
+ 'expected_effective_size': 8
+ },
+ {
+ 'name': 'y'
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ [ // PMD2, Renderer process.
+ {
+ 'name': 'v8',
+ 'expected_size': 13,
+ 'expected_effective_size': 13,
+ 'children': [
+ {
+ 'name': 'heaps',
+ 'guid': 100,
+ 'expected_size': 12,
+ 'expected_effective_size': 3,
+ 'expected_owned_by_sibling_sizes': {
+ 'objects': 9
+ },
+ 'children': [
+ {
+ 'name': '1',
+ 'size': 8,
+ 'expected_size': 8,
+ 'expected_effective_size': 2,
+ 'owns': 2,
+ 'importance': 2
+ },
+ {
+ 'name': '2',
+ 'expected_size': 4,
+ 'expected_effective_size': 1,
+ 'size': 4
+ }
+ ]
+ },
+ {
+ 'name': 'objects',
+ 'size': 10,
+ 'expected_size': 10,
+ 'expected_effective_size': 10,
+ 'children': [
+ {
+ 'name': '<unspecified>',
+ 'skip_build': true,
+ 'expected_size': 1,
+ 'expected_effective_size': 1
+ },
+ {
+ 'name': 'strings',
+ 'size': 9,
+ 'expected_size': 9,
+ 'expected_effective_size': 9,
+ 'owns': 100
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ ]);
+
+ // This should never happen. Nevertheless, this test checks that we can
+ // handle invalid sizes (parent dump being smaller than its aggregated
+ // children and owned dump being smaller than its largest owner) gracefully.
+ calculationTest('invalidSizes', [
+ [
+ {
+ 'name': 'root1',
+ 'size': 24,
+ 'expected_size': 24,
+ 'expected_effective_size': 4,
+ 'children': [
+ {
+ 'name': '<unspecified>',
+ 'skip_build': true,
+ 'expected_size': 4,
+ 'expected_effective_size': 4
+ },
+ {
+ 'name': 'parent',
+ 'guid': 2,
+ 'size': 17, // Invalid: child has larger size.
+ 'expected_size': 20,
+ 'expected_infos': [
+ {
+ type: PROVIDED_SIZE_LESS_THAN_AGGREGATED_CHILDREN,
+ providedSize: 17,
+ dependencySize: 20
+ },
+ {
+ type: PROVIDED_SIZE_LESS_THAN_LARGEST_OWNER,
+ providedSize: 17,
+ dependencySize: 18
+ }
+ ],
+ 'expected_effective_size': 0,
+ 'children': [
+ {
+ 'name': 'child',
+ 'guid': 1,
+ 'size': 10, // Invalid: owner has larger size.
+ 'expected_size': 20,
+ 'expected_infos': [
+ {
+ type: PROVIDED_SIZE_LESS_THAN_LARGEST_OWNER,
+ providedSize: 10,
+ dependencySize: 20
+ }
+ ],
+ 'expected_effective_size': 0,
+ }
+ ]
+ }
+ ]
+ },
+ {
+ 'name': 'root2',
+ 'owns': 1,
+ 'size': 20,
+ 'expected_size': 20,
+ 'expected_effective_size': 20
+ },
+ {
+ 'name': 'root3',
+ 'owns': 2,
+ 'importance': -1,
+ 'size': 18,
+ 'expected_size': 18,
+ 'expected_effective_size': 18
+ }
+ ]
+ ]);
+
+ calculationTest('multipleInfos', [
+ [
+ {
+ 'name': 'root',
+ 'expected_size': 10,
+ 'expected_effective_size': 10,
+ 'children': [
+ {
+ 'name': 'parent1',
+ 'size': 5,
+ 'expected_size': 10,
+ 'expected_infos': [
+ {
+ type: PROVIDED_SIZE_LESS_THAN_AGGREGATED_CHILDREN,
+ providedSize: 5,
+ dependencySize: 10
+ }
+ ],
+ 'expected_effective_size': 1,
+ 'expected_owned_by_sibling_sizes': {
+ 'parent2': 17,
+ 'parent3': 7
+ },
+ 'children': [
+ {
+ 'name': 'child',
+ 'guid': 3,
+ 'size': 10,
+ 'expected_size': 10,
+ 'expected_effective_size': 1,
+ }
+ ]
+ },
+ {
+ 'name': 'parent2',
+ // NOTE(petrcermak): The expected size here is a little strange
+ // because the children both own the same dump (namely
+ // root/parent1/child). It would, therefore, probably make more
+ // sense for the calculated size to be 9. Since this is an unlikely
+ // case and would complicate the (already complex) size
+ // calculation, we will now keep the algorithm as is.
+ 'expected_size': 17,
+ 'expected_effective_size': 14 / 3 + 2,
+ 'children': [
+ {
+ 'name': 'child1',
+ 'owns': 3,
+ 'size': 9,
+ 'expected_size': 9,
+ 'expected_effective_size': 7 / 3 + 1 / 2 + 1,
+ },
+ {
+ 'name': 'child2',
+ 'owns': 3,
+ 'size': 8,
+ 'expected_size': 8,
+ 'expected_effective_size': 7 / 3 + 1 / 2,
+ }
+ ]
+ },
+ {
+ 'name': 'parent3',
+ 'size': 7,
+ 'expected_size': 7,
+ 'expected_effective_size': 7 / 3,
+ 'owns': 3
+ }
+ ]
+ }
+ ]
+ ]);
+
+ // Check that size calculation is NOT preceded by numeric aggregation, which
+ // would recursively sum up size numerics.
+ test('finalizeGraph_aggregation', function() {
+ const model = tr.c.TestUtils.newModel(function(model) {
+ buildDumpTrees([
+ undefined, // GMD.
+ [ // PMD.
+ {
+ 'name': 'root',
+ 'children': [
+ {
+ 'name': 'owner_child',
+ 'owns': 9,
+ 'size': 7
+ },
+ {
+ 'name': 'owned_child',
+ 'guid': 9,
+ 'size': 20
+ }
+ ]
+ }
+ ]
+ ], model);
+ });
+ const pmd = model.getProcess(0).memoryDumps[0];
+
+ const rootDump = pmd.getMemoryAllocatorDumpByFullName('root');
+ assertDumpSizes(rootDump, 20, 20);
+
+ const ownerChildDump = pmd.getMemoryAllocatorDumpByFullName(
+ 'root/owner_child');
+ assertDumpSizes(ownerChildDump, 7, 7);
+
+ const ownedChildDump = pmd.getMemoryAllocatorDumpByFullName(
+ 'root/owned_child');
+ assertDumpSizes(ownedChildDump, 20, 13, [] /* expectedInfos */,
+ {'owner_child': 7} /* expectedOwnedBySiblingSizes */);
+ });
+
+ // Check that numeric and diagnostics propagation and aggregation are
+ // performed in the correct order.
+ test('finalizeGraph_propagation', function() {
+ const model = tr.c.TestUtils.newModel(function(model) {
+ buildDumpTrees([
+ [ // GMD.
+ {
+ 'name': 'owned_root',
+ 'guid': 1,
+ 'size': 10,
+ 'diagnostics': {
+ 'url': 'https://hello.world.com:42'
+ },
+ 'children': [
+ {
+ 'name': 'owned_child1',
+ 'numerics': {
+ 'summed': new Scalar(sizeInBytes_smallerIsBetter, 12)
+ },
+ 'diagnostics': {
+ 'url2': 'http://not.aggregated.to/owned/parent/dump'
+ }
+ },
+ {
+ 'name': 'owned_child2',
+ 'numerics': {
+ 'summed': new Scalar(sizeInBytes_smallerIsBetter, 15)
+ }
+ }
+ ]
+ }
+ ],
+ [ // PMD.
+ {
+ 'name': 'direct_owner',
+ 'owns': 1,
+ 'guid': 2,
+ 'diagnostics': {
+ 'url': 'file://not_overriden.html'
+ }
+ },
+ {
+ 'name': 'parent_owner',
+ 'children': [
+ {
+ 'name': 'child_owner',
+ 'owns': 1
+ },
+ {
+ 'name': 'sibling',
+ 'size': 5,
+ 'numerics': {
+ 'summed': new Scalar(sizeInBytes_smallerIsBetter, 13)
+ }
+ }
+ ]
+ },
+ {
+ 'name': 'precedent_owner',
+ 'owns': 1,
+ 'numerics': {
+ 'summed': new Scalar(sizeInBytes_smallerIsBetter, 0)
+ }
+ },
+ {
+ 'name': 'indirect_owner',
+ 'owns': 2
+ }
+ ]
+ ], model);
+ });
+ const pmd = model.getProcess(0).memoryDumps[0];
+
+ checkDumpNumericsAndDiagnostics(
+ pmd.getMemoryAllocatorDumpByFullName('direct_owner'),
+ {
+ size: 10,
+ effective_size: 3.3333,
+ summed: 27
+ },
+ {
+ url: 'file://not_overriden.html'
+ });
+ checkDumpNumericsAndDiagnostics(
+ pmd.getMemoryAllocatorDumpByFullName('parent_owner/child_owner'),
+ {
+ size: 10,
+ effective_size: 3.3333,
+ summed: 27
+ },
+ {
+ url: 'https://hello.world.com:42'
+ });
+ checkDumpNumericsAndDiagnostics(
+ pmd.getMemoryAllocatorDumpByFullName('parent_owner'),
+ {
+ size: 15,
+ effective_size: 8.3333,
+ summed: 40
+ }, {});
+ checkDumpNumericsAndDiagnostics(
+ pmd.getMemoryAllocatorDumpByFullName('precedent_owner'),
+ {
+ size: 10,
+ effective_size: 3.3333,
+ summed: 0
+ },
+ {
+ url: 'https://hello.world.com:42'
+ });
+ checkDumpNumericsAndDiagnostics(
+ pmd.getMemoryAllocatorDumpByFullName('indirect_owner'), {}, {});
+ });
+
+ // Check that weak dumps are removed before size size calculation.
+ test('finalizeGraph_weakDumpRemoval', function() {
+ const model = tr.c.TestUtils.newModel(function(model) {
+ buildDumpTrees([
+ undefined, // GMD.
+ [ // PMD.
+ {
+ 'name': 'root',
+ 'children': [
+ {
+ 'name': 'directly_weak_child',
+ 'weak': true,
+ 'guid': 5,
+ 'owns': 10,
+ 'size': 100
+ },
+ {
+ 'name': 'strong_child',
+ 'guid': 10,
+ 'size': 120
+ },
+ {
+ 'name': 'indirectly_weak_child',
+ 'owns': 5,
+ 'size': 70
+ },
+ {
+ 'name': 'separate_weak_child',
+ 'weak': true,
+ 'size': 300
+ }
+ ]
+ }
+ ]
+ ], model);
+ });
+ const pmd = model.getProcess(0).memoryDumps[0];
+
+ const rootDump = pmd.getMemoryAllocatorDumpByFullName('root');
+ assertDumpSizes(rootDump, 120, 120);
+ assert.lengthOf(rootDump.children, 1);
+
+ const strongChildDump = pmd.getMemoryAllocatorDumpByFullName(
+ 'root/strong_child');
+ assertDumpSizes(strongChildDump, 120, 120);
+ assert.lengthOf(strongChildDump.ownedBy, 0);
+
+ assert.isUndefined(pmd.getMemoryAllocatorDumpByFullName(
+ 'root/directly_weak_child'));
+ assert.isUndefined(pmd.getMemoryAllocatorDumpByFullName(
+ 'root/indirectly_weak_child'));
+ assert.isUndefined(pmd.getMemoryAllocatorDumpByFullName(
+ 'root/separate_weak_child'));
+ });
+
+ test('indicesUpdatedCorrectly', function() {
+ let gmd;
+ let rootDump;
+ let childDump;
+ const model = tr.c.TestUtils.newModel(function(model) {
+ gmd = new GlobalMemoryDump(model, 10);
+ model.globalMemoryDumps.push(gmd);
+
+ rootDump = newAllocatorDump(gmd, 'root', {numerics: {size: 64}});
+ childDump = addChildDump(rootDump, 'child', {numerics: {size: 48}});
+
+ gmd.memoryAllocatorDumps = [rootDump];
+
+ // Before model is finalized.
+ assert.strictEqual(
+ gmd.getMemoryAllocatorDumpByFullName('root'), rootDump);
+ assert.strictEqual(
+ gmd.getMemoryAllocatorDumpByFullName('root/child'), childDump);
+ assert.isUndefined(
+ gmd.getMemoryAllocatorDumpByFullName('root/<unspecified>'));
+ });
+
+ // Test sanity check.
+ assert.isDefined(gmd);
+ assert.isDefined(rootDump);
+ assert.isDefined(childDump);
+
+ // After model is finalized.
+ assert.strictEqual(gmd.getMemoryAllocatorDumpByFullName('root'), rootDump);
+ assert.strictEqual(
+ gmd.getMemoryAllocatorDumpByFullName('root/child'), childDump);
+ const unspecifiedDump =
+ gmd.getMemoryAllocatorDumpByFullName('root/<unspecified>');
+ assert.strictEqual(unspecifiedDump.fullName, 'root/<unspecified>');
+ assert.strictEqual(unspecifiedDump.parent, rootDump);
+ assert.strictEqual(rootDump.children[0], unspecifiedDump);
+ });
+
+ weakDumpRemovalTest('allDumpsNonWeak', [
+ [ // GMD.
+ {
+ 'name': 'malloc',
+ 'children': [
+ {
+ 'name': 'allocated_objects',
+ 'children': [
+ {
+ 'name': 'obj42',
+ 'guid': 5,
+ 'expected_owned_by_links_count': 2
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ undefined, // PMD1.
+ [ // PMD2.
+ {
+ 'name': 'oilpan'
+ },
+ {
+ 'name': 'v8',
+ 'children': [
+ {
+ 'name': 'heaps',
+ 'children': [
+ {
+ 'name': 'S',
+ 'owns': 5
+ },
+ {
+ 'name': 'L',
+ 'owns': 5
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ ]);
+
+ weakDumpRemovalTest('weakRootDump', [
+ [], // GMD.
+ [ // PMD1.
+ {
+ 'name': 'strong1'
+ },
+ {
+ 'name': 'weak',
+ 'weak': true,
+ 'expected_removed': true
+ },
+ {
+ 'name': 'strong2'
+ }
+ ]
+ ]);
+
+ weakDumpRemovalTest('weakChildDump', [
+ [ // GMD.
+ {
+ 'name': 'root',
+ 'children': [
+ {
+ 'name': 'parent',
+ 'children': [
+ {
+ 'name': 'strong1'
+ },
+ {
+ 'name': 'weak',
+ 'weak': true,
+ 'expected_removed': true,
+ 'children': [
+ {
+ 'name': 'implicitly-removed'
+ }
+ ]
+ },
+ {
+ 'name': 'strong2'
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ ]);
+
+ weakDumpRemovalTest('transitiveOwnerRemoval', [
+ [ // GMD.
+ {
+ 'name': 'not-removed-strong-dump',
+ 'guid': 0,
+ 'expected_owned_by_links_count': 1
+ },
+ {
+ 'name': 'weak-owned-dump',
+ 'guid': 1,
+ 'owns': 0,
+ 'weak': true,
+ 'expected_removed': true
+ }
+ ],
+ [ // PMD1.
+ {
+ 'name': 'direct-owner-dump',
+ 'guid': 2,
+ 'owns': 1,
+ 'expected_removed': true
+ },
+ {
+ 'name': 'also-not-removed-strong-dump',
+ 'owns': 0
+ }
+ ],
+ [ // PMD2.
+ {
+ 'name': 'indirect-owner-dump',
+ 'owns': 2,
+ 'expected_removed': true
+ }
+ ]
+ ]);
+
+ weakDumpRemovalTest('transitiveDescendantRemoval', [
+ [ // GMD.
+ {
+ 'name': 'A',
+ 'owns': 10,
+ // A =owns=> B -child-of-> C -> D => E -> F -> G (weak).
+ 'expected_removed': true
+ },
+ {
+ 'name': 'D',
+ 'owns': 5,
+ 'expected_removed': true, // D =owns=> E -child-of-> F -> G (weak).
+ 'children': [
+ {
+ 'name': 'C',
+ 'children': [
+ {
+ 'name': 'B',
+ 'guid': 10
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ undefined, // PMD1.
+ [ // PMD2.
+ {
+ 'name': 'first-retained-dump',
+ 'children': [
+ {
+ 'name': 'G',
+ 'weak': true,
+ 'expected_removed': true,
+ 'children': [
+ {
+ 'name': 'F',
+ 'children': [
+ {
+ 'name': 'E',
+ 'guid': 5
+ }
+ ]
+ },
+ {
+ 'name': 'H',
+ 'children': [
+ {
+ 'name': 'I',
+ 'children': [
+ {
+ 'name': 'J',
+ 'owns': 2
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ [ // PMD3.
+ {
+ 'name': 'second-retained-dump',
+ 'guid': 2,
+ // The only owner (J) is removed because J -child-of-> I -> H ->
+ // G (weak).
+ 'expected_owned_by_links_count': 0
+ }
+ ]
+ ]);
+
+ weakDumpRemovalTest('subownerships', [
+ [ // GMD.
+ {
+ 'name': 'root1',
+ 'owns': 20,
+ 'expected_removed': true, // root1 =owns=> root2 (weak).
+ 'children': [
+ {
+ 'name': 'child1',
+ 'owns': 2
+ }
+ ]
+ },
+ {
+ 'name': 'root2',
+ 'guid': 20,
+ 'owns': 30,
+ 'weak': true,
+ 'expected_removed': true,
+ 'children': [
+ {
+ 'name': 'child2',
+ 'guid': 2,
+ 'owns': 3
+ }
+ ]
+ },
+ {
+ 'name': 'root3',
+ 'guid': 30,
+ 'owns': 40,
+ 'expected_owned_by_links_count': 0,
+ 'children': [
+ {
+ 'name': 'child3',
+ 'guid': 3,
+ 'owns': 4,
+ 'weak': true,
+ 'expected_removed': true
+ }
+ ]
+ }
+ ],
+ [ // PMD1.
+ {
+ 'name': 'root4',
+ 'guid': 40,
+ 'expected_owned_by_links_count': 1,
+ 'children': [
+ {
+ 'name': 'child4',
+ 'guid': 4,
+ 'expected_owned_by_links_count': 0
+ }
+ ]
+ }
+ ],
+ [ // PMD2.
+ {
+ 'name': 'root5',
+ 'owns': 60,
+ 'expected_removed': true, // root5 =owns=> root6 => root7 (weak).
+ 'children': [
+ {
+ 'name': 'child5',
+ 'owns': 6
+ }
+ ]
+ },
+ {
+ 'name': 'root6',
+ 'guid': 60,
+ 'owns': 70,
+ 'expected_removed': true, // root6 =owns=> root7 (weak).
+ 'children': [
+ {
+ 'name': 'child6',
+ 'guid': 6,
+ 'owns': 7
+ }
+ ]
+ },
+ {
+ 'name': 'root7',
+ 'guid': 70,
+ 'owns': 40,
+ 'weak': true,
+ 'expected_removed': true,
+ 'children': [
+ {
+ 'name': 'child7',
+ 'guid': 7,
+ 'owns': 4
+ }
+ ]
+ }
+ ]
+ ]);
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/heap_dump.html b/chromium/third_party/catapult/tracing/tracing/model/heap_dump.html
new file mode 100644
index 00000000000..ec81a382b6f
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/heap_dump.html
@@ -0,0 +1,76 @@
+<!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">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.model', function() {
+ /**
+ * HeapEntry represents a single value describing the state of the heap of an
+ * allocator in a single process.
+ *
+ * An entry specifies how much space (e.g. 19 MiB) was allocated in a
+ * particular context, which consists of a codepath (e.g. drawQuad <- draw <-
+ * MessageLoop::RunTask) and an object type (e.g. HTMLImportLoader).
+ *
+ * If |valuesAreTotals| is true the size and count of this entry are totals
+ * for this and all more specific entries, otherwise they are values just for
+ * this specific entry.
+ *
+ * @{constructor}
+ */
+ function HeapEntry(
+ heapDump, leafStackFrame, objectTypeName, size, count, valuesAreTotals) {
+ this.heapDump = heapDump;
+
+ // The leaf stack frame of the associated backtrace (e.g. drawQuad for the
+ // drawQuad <- draw <- MessageLoop::RunTask backtrace). If undefined, the
+ // backtrace is empty.
+ this.leafStackFrame = leafStackFrame;
+
+ // The name of the allocated object type (e.g. 'HTMLImportLoader'). If
+ // undefined, the entry represents the sum over all object types.
+ this.objectTypeName = objectTypeName;
+
+ this.size = size;
+ this.count = count;
+ this.valuesAreTotals = valuesAreTotals;
+ }
+
+ /**
+ * HeapDump represents a dump of the heap of an allocator in a single process
+ * at a particular timestamp.
+ *
+ * @{constructor}
+ */
+ function HeapDump(processMemoryDump, allocatorName, isComplete) {
+ this.processMemoryDump = processMemoryDump;
+ this.allocatorName = allocatorName;
+ this.isComplete = isComplete;
+ this.entries = [];
+ }
+
+ HeapDump.prototype = {
+ addEntry(
+ leafStackFrame, objectTypeName, size, count, opt_valuesAreTotals) {
+ if (opt_valuesAreTotals === undefined) opt_valuesAreTotals = true;
+ const valuesAreTotals = opt_valuesAreTotals;
+ const entry = new HeapEntry(
+ this, leafStackFrame, objectTypeName, size, count, valuesAreTotals);
+ this.entries.push(entry);
+ return entry;
+ }
+ };
+
+ return {
+ HeapEntry,
+ HeapDump,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/heap_dump_test.html b/chromium/third_party/catapult/tracing/tracing/model/heap_dump_test.html
new file mode 100644
index 00000000000..9ce01725c03
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/heap_dump_test.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/model/global_memory_dump.html">
+<link rel="import" href="/tracing/model/heap_dump.html">
+<link rel="import" href="/tracing/model/model.html">
+<link rel="import" href="/tracing/model/process_memory_dump.html">
+<link rel="import" href="/tracing/model/stack_frame.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const Model = tr.Model;
+ const GlobalMemoryDump = tr.model.GlobalMemoryDump;
+ const ProcessMemoryDump = tr.model.ProcessMemoryDump;
+ const StackFrame = tr.model.StackFrame;
+ const HeapEntry = tr.model.HeapEntry;
+ const HeapDump = tr.model.HeapDump;
+
+ test('heapDumps', function() {
+ const model = new tr.Model();
+ const process = model.getOrCreateProcess(89);
+ const gmd = new GlobalMemoryDump(model, 42);
+ model.globalMemoryDumps.push(gmd);
+ const pmd = new ProcessMemoryDump(gmd, process, 42);
+ process.memoryDumps.push(pmd);
+
+ const rootFrame = new StackFrame(
+ undefined, tr.b.GUID.allocateSimple(), undefined);
+ const childFrame = new StackFrame(
+ rootFrame, tr.b.GUID.allocateSimple(), 'draw');
+ rootFrame.addChild(childFrame);
+
+ const dump = new HeapDump(pmd);
+ assert.strictEqual(dump.processMemoryDump, pmd);
+ assert.lengthOf(dump.entries, 0);
+
+ const entry1 = dump.addEntry(
+ childFrame, 'HTMLImportLoader', 1024, undefined);
+ assert.strictEqual(entry1.heapDump, dump);
+ assert.strictEqual(entry1.leafStackFrame, childFrame);
+ assert.strictEqual(entry1.objectTypeName, 'HTMLImportLoader');
+ assert.strictEqual(entry1.size, 1024);
+ assert.isUndefined(entry1.count);
+
+ const entry2 = dump.addEntry(undefined, undefined, 1048576, 42);
+ assert.strictEqual(entry2.heapDump, dump);
+ assert.isUndefined(entry2.leafStackFrame);
+ assert.isUndefined(entry2.objectTypeName);
+ assert.strictEqual(entry2.size, 1048576);
+ assert.strictEqual(entry2.count, 42);
+
+ assert.deepEqual(dump.entries, [entry1, entry2]);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/helpers/android_app.html b/chromium/third_party/catapult/tracing/tracing/model/helpers/android_app.html
new file mode 100644
index 00000000000..9cabb504606
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/helpers/android_app.html
@@ -0,0 +1,344 @@
+<!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_utils.html">
+<link rel="import" href="/tracing/base/math/statistics.html">
+<link rel="import" href="/tracing/base/utils.html">
+<link rel="import" href="/tracing/model/frame.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Class for managing android-specific model meta data,
+ * such as rendering apps, and frames rendered.
+ */
+tr.exportTo('tr.model.helpers', function() {
+ const Frame = tr.model.Frame;
+ const Statistics = tr.b.math.Statistics;
+
+ const UI_DRAW_TYPE = {
+ NONE: 'none',
+ LEGACY: 'legacy',
+ MARSHMALLOW: 'marshmallow'
+ };
+
+ const UI_THREAD_DRAW_NAMES = {
+ 'performTraversals': UI_DRAW_TYPE.LEGACY,
+ 'Choreographer#doFrame': UI_DRAW_TYPE.MARSHMALLOW
+ };
+
+ const RENDER_THREAD_DRAW_NAME = 'DrawFrame';
+ const RENDER_THREAD_INDEP_DRAW_NAME = 'doFrame';
+ const RENDER_THREAD_QUEUE_NAME = 'queueBuffer';
+ const RENDER_THREAD_SWAP_NAME = 'eglSwapBuffers';
+ const THREAD_SYNC_NAME = 'syncFrameState';
+
+ function getSlicesForThreadTimeRanges(threadTimeRanges) {
+ const ret = [];
+ threadTimeRanges.forEach(function(threadTimeRange) {
+ const slices = [];
+
+ threadTimeRange.thread.sliceGroup.iterSlicesInTimeRange(
+ function(slice) { slices.push(slice); },
+ threadTimeRange.start, threadTimeRange.end);
+ ret.push.apply(ret, slices);
+ });
+ return ret;
+ }
+
+ function makeFrame(threadTimeRanges, surfaceFlinger) {
+ const args = {};
+ if (surfaceFlinger && surfaceFlinger.hasVsyncs) {
+ const start = Statistics.min(threadTimeRanges,
+ function(threadTimeRanges) { return threadTimeRanges.start; });
+ args.deadline = surfaceFlinger.getFrameDeadline(start);
+ args.frameKickoff = surfaceFlinger.getFrameKickoff(start);
+ }
+ const events = getSlicesForThreadTimeRanges(threadTimeRanges);
+ return new Frame(events, threadTimeRanges, args);
+ }
+
+ function findOverlappingDrawFrame(renderThread, uiDrawSlice) {
+ if (!renderThread) return undefined;
+
+ // of all top level renderthread slices, find the one that has a 'sync'
+ // within the uiDrawSlice
+ let overlappingDrawFrame;
+ const slices = tr.b.iterateOverIntersectingIntervals(
+ renderThread.sliceGroup.slices,
+ function(range) { return range.start; },
+ function(range) { return range.end; },
+ uiDrawSlice.start,
+ uiDrawSlice.end,
+ function(rtDrawSlice) {
+ if (rtDrawSlice.title === RENDER_THREAD_DRAW_NAME) {
+ const rtSyncSlice = rtDrawSlice.findDescendentSlice(
+ THREAD_SYNC_NAME);
+ if (rtSyncSlice &&
+ rtSyncSlice.start >= uiDrawSlice.start &&
+ rtSyncSlice.end <= uiDrawSlice.end) {
+ // sync observed which overlaps ui draw. This means the RT draw
+ // corresponds to the UI draw
+ overlappingDrawFrame = rtDrawSlice;
+ }
+ }
+ });
+ return overlappingDrawFrame;
+ }
+
+ /**
+ * Builds an array of {start, end} ranges grouping common work of a frame
+ * that occurs just before performTraversals().
+ *
+ * Only necessary before Choreographer#doFrame tracing existed.
+ */
+ function getPreTraversalWorkRanges(uiThread) {
+ if (!uiThread) return [];
+
+ // gather all frame work that occurs outside of performTraversals
+ const preFrameEvents = [];
+ uiThread.sliceGroup.slices.forEach(function(slice) {
+ if (slice.title === 'obtainView' ||
+ slice.title === 'setupListItem' ||
+ slice.title === 'deliverInputEvent' ||
+ slice.title === 'RV Scroll') {
+ preFrameEvents.push(slice);
+ }
+ });
+ uiThread.asyncSliceGroup.slices.forEach(function(slice) {
+ if (slice.title === 'deliverInputEvent') {
+ preFrameEvents.push(slice);
+ }
+ });
+
+ return tr.b.math.mergeRanges(
+ tr.b.math.convertEventsToRanges(preFrameEvents),
+ 3,
+ function(events) {
+ return {
+ start: events[0].min,
+ end: events[events.length - 1].max
+ };
+ });
+ }
+
+ function getFrameStartTime(traversalStart, preTraversalWorkRanges) {
+ const preTraversalWorkRange =
+ tr.b.findClosestIntervalInSortedIntervals(
+ preTraversalWorkRanges,
+ function(range) { return range.start; },
+ function(range) { return range.end; },
+ traversalStart,
+ 3);
+
+ if (preTraversalWorkRange) {
+ return preTraversalWorkRange.start;
+ }
+ return traversalStart;
+ }
+
+ function getRtFrameEndTime(rtDrawSlice) {
+ // First try and get time that frame is queued:
+ const rtQueueSlice = rtDrawSlice.findDescendentSlice(
+ RENDER_THREAD_QUEUE_NAME);
+ if (rtQueueSlice) {
+ return rtQueueSlice.end;
+ }
+ // failing that, end of swapbuffers:
+ const rtSwapSlice = rtDrawSlice.findDescendentSlice(
+ RENDER_THREAD_SWAP_NAME);
+ if (rtSwapSlice) {
+ return rtSwapSlice.end;
+ }
+ // failing that, end of renderthread frame trace
+ return rtDrawSlice.end;
+ }
+
+ function getUiThreadDrivenFrames(app) {
+ if (!app.uiThread) return [];
+
+ let preTraversalWorkRanges = [];
+ if (app.uiDrawType === UI_DRAW_TYPE.LEGACY) {
+ preTraversalWorkRanges = getPreTraversalWorkRanges(app.uiThread);
+ }
+
+ const frames = [];
+ app.uiThread.sliceGroup.slices.forEach(function(slice) {
+ if (!(slice.title in UI_THREAD_DRAW_NAMES)) {
+ return;
+ }
+
+ const threadTimeRanges = [];
+ const uiThreadTimeRange = {
+ thread: app.uiThread,
+ start: getFrameStartTime(slice.start, preTraversalWorkRanges),
+ end: slice.end
+ };
+ threadTimeRanges.push(uiThreadTimeRange);
+
+ // on SDK 21+ devices with RenderThread,
+ // account for time taken on RenderThread
+ const rtDrawSlice = findOverlappingDrawFrame(
+ app.renderThread, slice);
+ if (rtDrawSlice) {
+ const rtSyncSlice = rtDrawSlice.findDescendentSlice(THREAD_SYNC_NAME);
+ if (rtSyncSlice) {
+ // Generally, the UI thread is only on the critical path
+ // until the start of sync.
+ uiThreadTimeRange.end = Math.min(uiThreadTimeRange.end,
+ rtSyncSlice.start);
+ }
+
+ threadTimeRanges.push({
+ thread: app.renderThread,
+ start: rtDrawSlice.start,
+ end: getRtFrameEndTime(rtDrawSlice)
+ });
+ }
+ frames.push(makeFrame(threadTimeRanges, app.surfaceFlinger));
+ });
+ return frames;
+ }
+
+ function getRenderThreadDrivenFrames(app) {
+ if (!app.renderThread) return [];
+
+ const frames = [];
+ app.renderThread.sliceGroup.getSlicesOfName(RENDER_THREAD_INDEP_DRAW_NAME)
+ .forEach(function(slice) {
+ const threadTimeRanges = [{
+ thread: app.renderThread,
+ start: slice.start,
+ end: slice.end
+ }];
+ frames.push(makeFrame(threadTimeRanges, app.surfaceFlinger));
+ });
+ return frames;
+ }
+
+ function getUiDrawType(uiThread) {
+ if (!uiThread) {
+ return UI_DRAW_TYPE.NONE;
+ }
+
+ const slices = uiThread.sliceGroup.slices;
+ for (let i = 0; i < slices.length; i++) {
+ if (slices[i].title in UI_THREAD_DRAW_NAMES) {
+ return UI_THREAD_DRAW_NAMES[slices[i].title];
+ }
+ }
+ return UI_DRAW_TYPE.NONE;
+ }
+
+ function getInputSamples(process) {
+ let samples = undefined;
+ for (const counterName in process.counters) {
+ if (/^android\.aq\:pending/.test(counterName) &&
+ process.counters[counterName].numSeries === 1) {
+ samples = process.counters[counterName].series[0].samples;
+ break;
+ }
+ }
+
+ if (!samples) return [];
+
+ // output rising edges only, since those are user inputs
+ const inputSamples = [];
+ let lastValue = 0;
+ samples.forEach(function(sample) {
+ if (sample.value > lastValue) {
+ inputSamples.push(sample);
+ }
+ lastValue = sample.value;
+ });
+ return inputSamples;
+ }
+
+ function getAnimationAsyncSlices(uiThread) {
+ if (!uiThread) return [];
+
+ const slices = [];
+ for (const slice of uiThread.asyncSliceGroup.getDescendantEvents()) {
+ if (/^animator\:/.test(slice.title)) {
+ slices.push(slice);
+ }
+ }
+ return slices;
+ }
+
+ /**
+ * Model for Android App specific data.
+ * @constructor
+ */
+ function AndroidApp(process, uiThread, renderThread, surfaceFlinger,
+ uiDrawType) {
+ this.process = process;
+ this.uiThread = uiThread;
+ this.renderThread = renderThread;
+ this.surfaceFlinger = surfaceFlinger;
+ this.uiDrawType = uiDrawType;
+
+ this.frames_ = undefined;
+ this.inputs_ = undefined;
+ }
+
+ AndroidApp.createForProcessIfPossible = function(process, surfaceFlinger) {
+ let uiThread = process.getThread(process.pid);
+ const uiDrawType = getUiDrawType(uiThread);
+ if (uiDrawType === UI_DRAW_TYPE.NONE) {
+ uiThread = undefined;
+ }
+ const renderThreads = process.findAllThreadsNamed('RenderThread');
+ const renderThread = (renderThreads.length === 1 ?
+ renderThreads[0] : undefined);
+
+ if (uiThread || renderThread) {
+ return new AndroidApp(process, uiThread, renderThread, surfaceFlinger,
+ uiDrawType);
+ }
+ };
+
+ AndroidApp.prototype = {
+ /**
+ * Returns a list of all frames in the trace for the app,
+ * constructed on first query.
+ */
+ getFrames() {
+ if (!this.frames_) {
+ const uiFrames = getUiThreadDrivenFrames(this);
+ const rtFrames = getRenderThreadDrivenFrames(this);
+ this.frames_ = uiFrames.concat(rtFrames);
+
+ // merge frames by sorting by end timestamp
+ this.frames_.sort(function(a, b) { a.end - b.end; });
+ }
+ return this.frames_;
+ },
+
+ /**
+ * Returns list of CounterSamples for each input event enqueued to the app.
+ */
+ getInputSamples() {
+ if (!this.inputs_) {
+ this.inputs_ = getInputSamples(this.process);
+ }
+ return this.inputs_;
+ },
+
+ getAnimationAsyncSlices() {
+ if (!this.animations_) {
+ this.animations_ = getAnimationAsyncSlices(this.uiThread);
+ }
+ return this.animations_;
+ }
+ };
+
+ return {
+ AndroidApp,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/helpers/android_model_helper.html b/chromium/third_party/catapult/tracing/tracing/model/helpers/android_model_helper.html
new file mode 100644
index 00000000000..76239a31942
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/helpers/android_model_helper.html
@@ -0,0 +1,105 @@
+<!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/guid.html">
+<link rel="import" href="/tracing/base/math/range_utils.html">
+<link rel="import" href="/tracing/core/auditor.html">
+<link rel="import" href="/tracing/model/helpers/android_app.html">
+<link rel="import" href="/tracing/model/helpers/android_surface_flinger.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Class for managing android-specific model meta data,
+ * such as rendering apps, frames rendered, and SurfaceFlinger.
+ */
+tr.exportTo('tr.model.helpers', function() {
+ const AndroidApp = tr.model.helpers.AndroidApp;
+ const AndroidSurfaceFlinger = tr.model.helpers.AndroidSurfaceFlinger;
+
+ const IMPORTANT_SURFACE_FLINGER_SLICES = {
+ 'doComposition': true,
+ 'updateTexImage': true,
+ 'postFramebuffer': true
+ };
+ const IMPORTANT_UI_THREAD_SLICES = {
+ 'Choreographer#doFrame': true,
+ 'performTraversals': true,
+ 'deliverInputEvent': true
+ };
+ const IMPORTANT_RENDER_THREAD_SLICES = {
+ 'doFrame': true
+ };
+
+ function iterateImportantThreadSlices(thread, important, callback) {
+ if (!thread) return;
+
+ thread.sliceGroup.slices.forEach(function(slice) {
+ if (slice.title in important) {
+ callback(slice);
+ }
+ });
+ }
+
+ /**
+ * Model for Android-specific data.
+ * @constructor
+ */
+ function AndroidModelHelper(model) {
+ this.model = model;
+ this.apps = [];
+ this.surfaceFlinger = undefined;
+
+ const processes = model.getAllProcesses();
+ for (let i = 0; i < processes.length && !this.surfaceFlinger; i++) {
+ this.surfaceFlinger =
+ AndroidSurfaceFlinger.createForProcessIfPossible(processes[i]);
+ }
+
+ model.getAllProcesses().forEach(function(process) {
+ const app = AndroidApp.createForProcessIfPossible(
+ process, this.surfaceFlinger);
+ if (app) {
+ this.apps.push(app);
+ }
+ }, this);
+ }
+
+ AndroidModelHelper.guid = tr.b.GUID.allocateSimple();
+
+ AndroidModelHelper.supportsModel = function(model) {
+ return true;
+ };
+
+ AndroidModelHelper.prototype = {
+ iterateImportantSlices(callback) {
+ if (this.surfaceFlinger) {
+ iterateImportantThreadSlices(
+ this.surfaceFlinger.thread,
+ IMPORTANT_SURFACE_FLINGER_SLICES,
+ callback);
+ }
+
+ this.apps.forEach(function(app) {
+ iterateImportantThreadSlices(
+ app.uiThread,
+ IMPORTANT_UI_THREAD_SLICES,
+ callback);
+ iterateImportantThreadSlices(
+ app.renderThread,
+ IMPORTANT_RENDER_THREAD_SLICES,
+ callback);
+ });
+ }
+ };
+
+ return {
+ AndroidModelHelper,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/helpers/android_model_helper_test.html b/chromium/third_party/catapult/tracing/tracing/model/helpers/android_model_helper_test.html
new file mode 100644
index 00000000000..0add58195af
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/helpers/android_model_helper_test.html
@@ -0,0 +1,267 @@
+<!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/extras/android/android_auditor.html">
+<link rel="import" href="/tracing/extras/importer/linux_perf/ftrace_importer.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const AndroidModelHelper = tr.model.helpers.AndroidModelHelper;
+ const newAsyncSliceNamed = tr.c.TestUtils.newAsyncSliceNamed;
+ const newSliceEx = tr.c.TestUtils.newSliceEx;
+ const newCounterNamed = tr.c.TestUtils.newCounterNamed;
+ const newCounterSeries = tr.c.TestUtils.newCounterSeries;
+
+ function createSurfaceFlinger(model, vsyncCallback) {
+ if (model.getProcess(2)) {
+ throw new Error('process already exists');
+ }
+
+ const sfProcess = model.getOrCreateProcess(2);
+ const sfThread = sfProcess.getOrCreateThread(2); // main thread, tid = pid
+ sfThread.name = '/system/bin/surfaceflinger';
+
+ // ensure slicegroup has data
+ sfThread.sliceGroup.pushSlice(newSliceEx({
+ title: 'doComposition',
+ start: 8,
+ duration: 2
+ }));
+
+ vsyncCallback(sfProcess);
+ }
+
+ function createSurfaceFlingerWithVsync(model) {
+ createSurfaceFlinger(model, function(sfProcess) {
+ const counter = sfProcess.getOrCreateCounter('android', 'VSYNC');
+ const series = newCounterSeries();
+ for (let i = 0; i <= 10; i++) {
+ series.addCounterSample(i * 10, i % 2);
+ }
+ counter.addSeries(series);
+ });
+ }
+
+ /*
+ * List of customizeModelCallbacks which produce different 80ms frames,
+ * each starting at 10ms, and with a single important slice
+ */
+ const SINGLE_FRAME_CUSTOM_MODELS = [
+ function(model) {
+ // UI thread only
+ const uiThread = model.getOrCreateProcess(120).getOrCreateThread(120);
+ uiThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'performTraversals', start: 10, duration: 80}));
+
+ model.uiThread = uiThread;
+ },
+
+ function(model) {
+ // RenderThread only
+ const renderThread = model.getOrCreateProcess(120).getOrCreateThread(200);
+ renderThread.name = 'RenderThread';
+ renderThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'doFrame', start: 10, duration: 80}));
+
+ model.renderThread = renderThread;
+ },
+
+ function(model) {
+ const uiThread = model.getOrCreateProcess(120).getOrCreateThread(120);
+
+ // UI thread time - 19 (from 10 to 29, ignored after)
+ uiThread.asyncSliceGroup.push(
+ newAsyncSliceNamed('deliverInputEvent', 10, 9, uiThread, uiThread));
+ uiThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'performTraversals', start: 20, duration: 110}));
+ uiThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'draw', start: 20, duration: 108}));
+ uiThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'Record View#draw()', start: 20, duration: 8}));
+
+ // RenderThread time - 61 (from 29 to 90, ignored after)
+ const renderThread = model.getOrCreateProcess(120).getOrCreateThread(200);
+ renderThread.name = 'RenderThread';
+ renderThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'DrawFrame', start: 29, duration: 70}));
+ renderThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'syncFrameState', start: 29, duration: 1}));
+ renderThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'queueBuffer', start: 89, duration: 1}));
+
+ model.uiThread = uiThread;
+ model.renderThread = renderThread;
+ }
+ ];
+
+ test('getThreads', function() {
+ SINGLE_FRAME_CUSTOM_MODELS.forEach(function(customizeModelCallback) {
+ const model = tr.c.TestUtils.newModel(customizeModelCallback);
+ const helper = model.getOrCreateHelper(AndroidModelHelper);
+ assert.strictEqual(helper.apps[0].uiThread, model.uiThread);
+ assert.strictEqual(helper.apps[0].renderThread, model.renderThread);
+ });
+ });
+
+ test('iterateImportantSlices', function() {
+ SINGLE_FRAME_CUSTOM_MODELS.forEach(function(customizeModelCallback) {
+ const model = tr.c.TestUtils.newModel(customizeModelCallback);
+ const helper = model.getOrCreateHelper(AndroidModelHelper);
+
+ let seen = 0;
+ helper.iterateImportantSlices(function(importantSlice) {
+ assert.isTrue(importantSlice instanceof tr.model.Slice);
+ seen++;
+ });
+ assert.strictEqual(seen, 1);
+ });
+ });
+
+ test('getFrames', function() {
+ SINGLE_FRAME_CUSTOM_MODELS.forEach(function(customizeModelCallback) {
+ const model = tr.c.TestUtils.newModel(customizeModelCallback);
+ const helper = model.getOrCreateHelper(AndroidModelHelper);
+ assert.strictEqual(helper.apps.length, 1);
+
+ const frames = helper.apps[0].getFrames();
+ assert.strictEqual(frames.length, 1);
+ assert.closeTo(frames[0].totalDuration, 80, 1e-5);
+
+ assert.closeTo(frames[0].start, 10, 1e-5);
+ assert.closeTo(frames[0].end, 90, 1e-5);
+ });
+ });
+
+ test('surfaceFlingerVsyncs', function() {
+ const model = tr.c.TestUtils.newModel(createSurfaceFlingerWithVsync);
+ const helper = model.getOrCreateHelper(AndroidModelHelper);
+ assert.isTrue(helper.surfaceFlinger.hasVsyncs);
+
+ // test querying the vsyncs
+ assert.closeTo(helper.surfaceFlinger.getFrameKickoff(5), 0, 1e-5);
+ assert.closeTo(helper.surfaceFlinger.getFrameDeadline(95), 100, 1e-5);
+
+ assert.closeTo(helper.surfaceFlinger.getFrameKickoff(10), 10, 1e-5);
+ assert.closeTo(helper.surfaceFlinger.getFrameDeadline(90), 100, 1e-5);
+
+ // test undefined behavior outside of vsyncs.
+ assert.isUndefined(helper.surfaceFlinger.getFrameKickoff(-5));
+ assert.isUndefined(helper.surfaceFlinger.getFrameDeadline(105));
+ });
+
+ test('surfaceFlingerShiftedVsyncs', function() {
+ const model = tr.c.TestUtils.newModel(function(model) {
+ createSurfaceFlinger(model, function(sfProcess) {
+ const appSeries = newCounterSeries();
+ const sfSeries = newCounterSeries();
+ for (let i = 0; i <= 10; i++) {
+ // SF vsync is 4ms after app
+ appSeries.addCounterSample(i * 16, i % 2);
+ sfSeries.addCounterSample(i * 16 + 4, i % 2);
+ }
+ sfProcess.getOrCreateCounter('android', 'VSYNC-sf')
+ .addSeries(sfSeries);
+ sfProcess.getOrCreateCounter('android', 'VSYNC-app')
+ .addSeries(appSeries);
+ });
+ });
+ const helper = model.getOrCreateHelper(AndroidModelHelper);
+ assert.isTrue(helper.surfaceFlinger.hasVsyncs);
+
+ // test querying the vsyncs - Frames should have 20ms window
+ assert.closeTo(helper.surfaceFlinger.getFrameKickoff(0), 0, 1e-5);
+ assert.closeTo(helper.surfaceFlinger.getFrameDeadline(0), 20, 1e-5);
+
+ assert.closeTo(helper.surfaceFlinger.getFrameKickoff(16), 16, 1e-5);
+ assert.closeTo(helper.surfaceFlinger.getFrameDeadline(16), 36, 1e-5);
+
+ // test undefined behavior outside of vsyncs.
+ assert.isUndefined(helper.surfaceFlinger.getFrameKickoff(-5));
+ assert.isUndefined(helper.surfaceFlinger.getFrameDeadline(165));
+ });
+
+ test('frameVsyncInterop', function() {
+ const model = tr.c.TestUtils.newModel(function(model) {
+ // app - 3 good, 3 bad frames
+ const uiThread = model.getOrCreateProcess(1).getOrCreateThread(1);
+ uiThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'performTraversals', start: 1, duration: 8}));
+ uiThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'performTraversals', start: 10, duration: 8}));
+ uiThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'performTraversals', start: 20, duration: 8}));
+ uiThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'performTraversals', start: 31, duration: 11}));
+ uiThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'performTraversals', start: 45, duration: 6}));
+ uiThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'performTraversals', start: 60, duration: 20}));
+
+ // surface flinger - vsync every 10ms
+ createSurfaceFlingerWithVsync(model);
+ });
+ const helper = model.getOrCreateHelper(AndroidModelHelper);
+
+ const frames = helper.apps[0].getFrames();
+ assert.strictEqual(frames.length, 6);
+ for (let i = 0; i < 6; i++) {
+ const shouldMissDeadline = i >= 3;
+ const missedDeadline = frames[i].args.deadline < frames[i].end;
+ assert.strictEqual(shouldMissDeadline, missedDeadline);
+ }
+ });
+
+ test('appInputs', function() {
+ const model = tr.c.TestUtils.newModel(function(model) {
+ const process = model.getOrCreateProcess(120);
+ const uiThread = process.getOrCreateThread(120);
+ uiThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'performTraversals', start: 20, duration: 4}));
+ uiThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'performTraversals', start: 40, duration: 4}));
+
+ const counter = process.getOrCreateCounter('android', 'aq:pending:foo');
+ const series = newCounterSeries();
+ series.addCounterSample(10, 1);
+ series.addCounterSample(20, 0);
+ series.addCounterSample(30, 1);
+ series.addCounterSample(40, 2);
+ series.addCounterSample(50, 0);
+ counter.addSeries(series);
+ });
+ const helper = model.getOrCreateHelper(AndroidModelHelper);
+ assert.strictEqual(helper.apps.length, 1);
+
+ const inputSamples = helper.apps[0].getInputSamples();
+ assert.strictEqual(inputSamples.length, 3);
+ assert.strictEqual(inputSamples[0].timestamp, 10);
+ assert.strictEqual(inputSamples[1].timestamp, 30);
+ assert.strictEqual(inputSamples[2].timestamp, 40);
+ });
+
+ test('appAnimations', function() {
+ const model = tr.c.TestUtils.newModel(function(model) {
+ const process = model.getOrCreateProcess(120);
+ const uiThread = process.getOrCreateThread(120);
+ uiThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'performTraversals', start: 10, duration: 10}));
+ uiThread.asyncSliceGroup.push(newAsyncSliceNamed('animator:foo', 0, 10,
+ uiThread, uiThread));
+ });
+ const helper = model.getOrCreateHelper(AndroidModelHelper);
+ assert.strictEqual(helper.apps.length, 1);
+
+ const animations = helper.apps[0].getAnimationAsyncSlices();
+ assert.strictEqual(animations.length, 1);
+ assert.strictEqual(animations[0].start, 0);
+ assert.strictEqual(animations[0].end, 10);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/helpers/android_surface_flinger.html b/chromium/third_party/catapult/tracing/tracing/model/helpers/android_surface_flinger.html
new file mode 100644
index 00000000000..7595be059b3
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/helpers/android_surface_flinger.html
@@ -0,0 +1,115 @@
+<!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">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Class for representing SurfaceFlinger process and its Vsyncs.
+ */
+tr.exportTo('tr.model.helpers', function() {
+ const findLowIndexInSortedArray = tr.b.findLowIndexInSortedArray;
+
+ const VSYNC_SF_NAME = 'android.VSYNC-sf';
+ const VSYNC_APP_NAME = 'android.VSYNC-app';
+ const VSYNC_FALLBACK_NAME = 'android.VSYNC';
+
+ // when sampling vsync, push samples back by this much to ensure
+ // frame start samples *between* vsyncs
+ const TIMESTAMP_FUDGE_MS = 0.01;
+
+ function getVsyncTimestamps(process, counterName) {
+ let vsync = process.counters[counterName];
+ if (!vsync) {
+ vsync = process.counters[VSYNC_FALLBACK_NAME];
+ }
+
+ if (vsync && vsync.numSeries === 1 && vsync.numSamples > 1) {
+ return vsync.series[0].timestamps;
+ }
+ return undefined;
+ }
+
+ /**
+ * Model for SurfaceFlinger specific data.
+ * @constructor
+ */
+ function AndroidSurfaceFlinger(process, thread) {
+ this.process = process;
+ this.thread = thread;
+
+ this.appVsync_ = undefined;
+ this.sfVsync_ = undefined;
+
+ this.appVsyncTimestamps_ = getVsyncTimestamps(process, VSYNC_APP_NAME);
+ this.sfVsyncTimestamps_ = getVsyncTimestamps(process, VSYNC_SF_NAME);
+
+ // separation of vsync of app vs sf - assume app has at least window of 5ms
+ this.deadlineDelayMs_ =
+ this.appVsyncTimestamps_ !== this.sfVsyncTimestamps_ ?
+ 5 : TIMESTAMP_FUDGE_MS;
+ }
+
+ AndroidSurfaceFlinger.createForProcessIfPossible = function(process) {
+ const mainThread = process.getThread(process.pid);
+
+ // newer versions - main thread, lowercase name, preceeding forward slash
+ if (mainThread && mainThread.name &&
+ /surfaceflinger/.test(mainThread.name)) {
+ return new AndroidSurfaceFlinger(process, mainThread);
+ }
+
+ // older versions - another thread is named SurfaceFlinger
+ const primaryThreads = process.findAllThreadsNamed('SurfaceFlinger');
+ if (primaryThreads.length === 1) {
+ return new AndroidSurfaceFlinger(process, primaryThreads[0]);
+ }
+ return undefined;
+ };
+
+ AndroidSurfaceFlinger.prototype = {
+ get hasVsyncs() {
+ return !!this.appVsyncTimestamps_ && !!this.sfVsyncTimestamps_;
+ },
+
+ getFrameKickoff(timestamp) {
+ if (!this.hasVsyncs) {
+ throw new Error('cannot query vsync info without vsyncs');
+ }
+
+ const firstGreaterIndex =
+ findLowIndexInSortedArray(this.appVsyncTimestamps_,
+ function(x) { return x; },
+ timestamp + TIMESTAMP_FUDGE_MS);
+
+ if (firstGreaterIndex < 1) return undefined;
+ return this.appVsyncTimestamps_[firstGreaterIndex - 1];
+ },
+
+ getFrameDeadline(timestamp) {
+ if (!this.hasVsyncs) {
+ throw new Error('cannot query vsync info without vsyncs');
+ }
+
+ const firstGreaterIndex =
+ findLowIndexInSortedArray(this.sfVsyncTimestamps_,
+ function(x) { return x; },
+ timestamp + this.deadlineDelayMs_);
+ if (firstGreaterIndex >= this.sfVsyncTimestamps_.length) {
+ return undefined;
+ }
+ return this.sfVsyncTimestamps_[firstGreaterIndex];
+ }
+ };
+
+ return {
+ AndroidSurfaceFlinger,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/helpers/chrome_browser_helper.html b/chromium/third_party/catapult/tracing/tracing/model/helpers/chrome_browser_helper.html
new file mode 100644
index 00000000000..9c9cb6e473b
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/helpers/chrome_browser_helper.html
@@ -0,0 +1,119 @@
+<!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/helpers/chrome_process_helper.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Utilities for accessing trace data about the Chrome browser.
+ */
+tr.exportTo('tr.model.helpers', function() {
+ function ChromeBrowserHelper(modelHelper, process) {
+ tr.model.helpers.ChromeProcessHelper.call(this, modelHelper, process);
+ this.mainThread_ = process.findAtMostOneThreadNamed('CrBrowserMain');
+ if (!process.name) {
+ process.name = ChromeBrowserHelper.PROCESS_NAME;
+ }
+ }
+
+ ChromeBrowserHelper.PROCESS_NAME = 'Browser';
+
+ ChromeBrowserHelper.isBrowserProcess = function(process) {
+ return !!process.findAtMostOneThreadNamed('CrBrowserMain');
+ };
+
+ ChromeBrowserHelper.prototype = {
+ __proto__: tr.model.helpers.ChromeProcessHelper.prototype,
+
+ // TODO(petrcermak): Pass browser name in a metadata event (see
+ // crbug.com/605088).
+ get browserName() {
+ const hasInProcessRendererThread = this.process.findAllThreadsNamed(
+ 'Chrome_InProcRendererThread').length > 0;
+ return hasInProcessRendererThread ? 'webview' : 'chrome';
+ },
+
+ get mainThread() {
+ return this.mainThread_;
+ },
+
+ get rendererHelpers() {
+ return this.modelHelper.rendererHelpers;
+ },
+
+ getLoadingEventsInRange(rangeOfInterest) {
+ return this.getAllAsyncSlicesMatching(function(slice) {
+ return slice.title.indexOf('WebContentsImpl Loading') === 0 &&
+ rangeOfInterest.intersectsExplicitRangeInclusive(
+ slice.start, slice.end);
+ });
+ },
+
+ getCommitProvisionalLoadEventsInRange(rangeOfInterest) {
+ return this.getAllAsyncSlicesMatching(function(slice) {
+ return slice.title === 'RenderFrameImpl::didCommitProvisionalLoad' &&
+ rangeOfInterest.intersectsExplicitRangeInclusive(
+ slice.start, slice.end);
+ });
+ },
+
+ get hasLatencyEvents() {
+ let hasLatency = false;
+ for (const thread of this.modelHelper.model.getAllThreads()) {
+ for (const event of thread.getDescendantEvents()) {
+ if (!event.isTopLevel) continue;
+ if (!(event instanceof tr.e.cc.InputLatencyAsyncSlice)) {
+ continue;
+ }
+ hasLatency = true;
+ }
+ }
+ return hasLatency;
+ },
+
+ getLatencyEventsInRange(rangeOfInterest) {
+ return this.getAllAsyncSlicesMatching(function(slice) {
+ return (slice.title.indexOf('InputLatency') === 0) &&
+ rangeOfInterest.intersectsExplicitRangeInclusive(
+ slice.start, slice.end);
+ });
+ },
+
+ getAllAsyncSlicesMatching(pred, opt_this) {
+ const events = [];
+ this.iterAllThreads(function(thread) {
+ for (const slice of thread.getDescendantEvents()) {
+ if (pred.call(opt_this, slice)) {
+ events.push(slice);
+ }
+ }
+ });
+ return events;
+ },
+
+ iterAllThreads(func, opt_this) {
+ for (const thread of Object.values(this.process.threads)) {
+ func.call(opt_this, thread);
+ }
+
+ for (const rendererHelper of Object.values(this.rendererHelpers)) {
+ const rendererProcess = rendererHelper.process;
+ for (const thread of Object.values(rendererProcess.threads)) {
+ func.call(opt_this, thread);
+ }
+ }
+ }
+ };
+
+ return {
+ ChromeBrowserHelper,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/helpers/chrome_browser_helper_test.html b/chromium/third_party/catapult/tracing/tracing/model/helpers/chrome_browser_helper_test.html
new file mode 100644
index 00000000000..a53f31127ea
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/helpers/chrome_browser_helper_test.html
@@ -0,0 +1,74 @@
+<!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/extras/chrome/cc/input_latency_async_slice.html">
+<link rel="import" href="/tracing/extras/chrome/chrome_test_utils.html">
+<link rel="import" href="/tracing/extras/importer/trace_event_importer.html">
+<link rel="import" href="/tracing/model/helpers/chrome_browser_helper.html">
+<link rel="import" href="/tracing/model/helpers/chrome_model_helper.html">
+<link rel="import" href="/tracing/model/model.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const INPUT_TYPE = tr.e.cc.INPUT_EVENT_TYPE_NAMES;
+
+ function getRange(min, max) {
+ const range = new tr.b.math.Range();
+ range.min = min;
+ range.max = max;
+ return range;
+ }
+
+ test('LoadingEvent', function() {
+ const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function() { });
+ const modelHelper = model.getOrCreateHelper(
+ tr.model.helpers.ChromeModelHelper);
+ tr.e.chrome.ChromeTestUtils.addLoadingEvent(model, {start: 1, end: 10});
+ assert.strictEqual(1, modelHelper.browserHelper.getLoadingEventsInRange(
+ getRange(0, 100)).length);
+ });
+
+ test('ProvisionalLoadEvent', function() {
+ const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function() { });
+ const modelHelper = model.getOrCreateHelper(
+ tr.model.helpers.ChromeModelHelper);
+ tr.e.chrome.ChromeTestUtils.addCommitLoadEvent(model, {start: 1, end: 10});
+ assert.strictEqual(1,
+ modelHelper.browserHelper.getCommitProvisionalLoadEventsInRange(
+ getRange(0, 100)).length);
+ });
+
+ test('LatencyEvent', function() {
+ const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function() { });
+ const modelHelper = model.getOrCreateHelper(
+ tr.model.helpers.ChromeModelHelper);
+ tr.e.chrome.ChromeTestUtils.addInputEvent(
+ model, INPUT_TYPE.UNKNOWN, {start: 1, end: 10});
+ assert.strictEqual(1, modelHelper.browserHelper.getLatencyEventsInRange(
+ getRange(0, 100)).length);
+ });
+
+ test('browserName_chrome', function() {
+ const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function() { });
+ const modelHelper = model.getOrCreateHelper(
+ tr.model.helpers.ChromeModelHelper);
+ assert.strictEqual(modelHelper.browserHelper.browserName, 'chrome');
+ });
+
+ test('browserName_webview', function() {
+ const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function() { });
+ model.browserProcess.getOrCreateThread(42).name =
+ 'Chrome_InProcRendererThread';
+ const modelHelper = model.getOrCreateHelper(
+ tr.model.helpers.ChromeModelHelper);
+ assert.strictEqual(modelHelper.browserHelper.browserName, 'webview');
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/helpers/chrome_gpu_helper.html b/chromium/third_party/catapult/tracing/tracing/model/helpers/chrome_gpu_helper.html
new file mode 100644
index 00000000000..cf3857f1636
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/helpers/chrome_gpu_helper.html
@@ -0,0 +1,47 @@
+<!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/helpers/chrome_process_helper.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Utilities for accessing the Chrome GPU Process.
+ */
+tr.exportTo('tr.model.helpers', function() {
+ // TODO(charliea): This method should probably throw if this isn't a Chrome
+ // GPU process.
+ function ChromeGpuHelper(modelHelper, process) {
+ tr.model.helpers.ChromeProcessHelper.call(this, modelHelper, process);
+ if (!process.name) {
+ process.name = ChromeGpuHelper.PROCESS_NAME;
+ }
+ }
+
+ ChromeGpuHelper.PROCESS_NAME = 'GPU Process';
+
+ ChromeGpuHelper.isGpuProcess = function(process) {
+ // In some Android builds the GPU thread is not in a separate process.
+ if (process.findAtMostOneThreadNamed('CrBrowserMain') ||
+ process.findAtMostOneThreadNamed('CrRendererMain')) {
+ return false;
+ }
+
+ // On Android, there can sometimes be GPU processes with multiple main
+ // threads. We need to recognize those processes as GPU processes.
+ return process.findAllThreadsNamed('CrGpuMain').length > 0;
+ };
+
+ ChromeGpuHelper.prototype = {
+ __proto__: tr.model.helpers.ChromeProcessHelper.prototype
+ };
+
+ return {
+ ChromeGpuHelper,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/helpers/chrome_gpu_helper_test.html b/chromium/third_party/catapult/tracing/tracing/model/helpers/chrome_gpu_helper_test.html
new file mode 100644
index 00000000000..11f3be603b0
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/helpers/chrome_gpu_helper_test.html
@@ -0,0 +1,110 @@
+<!DOCTYPE html>
+<!--
+Copyright 2017 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/helpers/chrome_gpu_helper.html">
+<link rel="import" href="/tracing/model/helpers/chrome_model_helper.html">
+<link rel="import" href="/tracing/model/model.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const ChromeGpuHelper = tr.model.helpers.ChromeGpuHelper;
+
+ test('constructor_doesntThrowIfMultipleMainThreads', function() {
+ const model = tr.c.TestUtils.newModel(model => {
+ model.getOrCreateProcess(1).getOrCreateThread(1).name = 'CrGpuMain';
+ model.getOrCreateProcess(1).getOrCreateThread(2).name = 'CrGpuMain';
+ });
+
+ const modelHelper = model.getOrCreateHelper(
+ tr.model.helpers.ChromeModelHelper);
+
+ new ChromeGpuHelper(modelHelper, model.getOrCreateProcess(1));
+ });
+
+ test('constructor_doesntThrowIfNoMainThread', function() {
+ const model = tr.c.TestUtils.newModel(model => {
+ model.getOrCreateProcess(1);
+ });
+
+ const modelHelper = model.getOrCreateHelper(
+ tr.model.helpers.ChromeModelHelper);
+
+ new ChromeGpuHelper(modelHelper, model.getOrCreateProcess(1));
+ });
+
+ test('constructor_namesProcessIfUnnamed', function() {
+ const model = tr.c.TestUtils.newModel(model => {
+ model.getOrCreateProcess(1);
+ });
+
+ const modelHelper = model.getOrCreateHelper(
+ tr.model.helpers.ChromeModelHelper);
+
+ new ChromeGpuHelper(modelHelper, model.getOrCreateProcess(1));
+ assert.strictEqual(model.getOrCreateProcess(1).name, 'GPU Process');
+ });
+
+ test('constructor_doesntNameProcessIfNamed', function() {
+ const model = tr.c.TestUtils.newModel(model => {
+ model.getOrCreateProcess(1).name = 'Example process name';
+ });
+
+ const modelHelper = model.getOrCreateHelper(
+ tr.model.helpers.ChromeModelHelper);
+
+ new ChromeGpuHelper(modelHelper, model.getOrCreateProcess(1));
+ assert.strictEqual(
+ model.getOrCreateProcess(1).name, 'Example process name');
+ });
+
+ test('isGpuProcess_falseIfNoMainThread', function() {
+ const model = tr.c.TestUtils.newModel(model => {
+ model.getOrCreateProcess(1);
+ });
+
+ assert.isFalse(ChromeGpuHelper.isGpuProcess(model.getOrCreateProcess(1)));
+ });
+
+ test('isGpuProcess', function() {
+ const model = tr.c.TestUtils.newModel(model => {
+ model.getOrCreateProcess(1).getOrCreateThread(1).name = 'CrGpuMain';
+ });
+
+ assert.isTrue(ChromeGpuHelper.isGpuProcess(model.getOrCreateProcess(1)));
+ });
+
+ test('isGpuProcess_trueIfMultipleMainThreads', function() {
+ const model = tr.c.TestUtils.newModel(model => {
+ model.getOrCreateProcess(1).getOrCreateThread(1).name = 'CrGpuMain';
+ model.getOrCreateProcess(1).getOrCreateThread(2).name = 'CrGpuMain';
+ });
+
+ assert.isTrue(ChromeGpuHelper.isGpuProcess(model.getOrCreateProcess(1)));
+ });
+
+ test('isGpuProcess_falseIfBrowserProcess', function() {
+ const model = tr.c.TestUtils.newModel(model => {
+ model.getOrCreateProcess(1).getOrCreateThread(1).name = 'CrGpuMain';
+ model.getOrCreateProcess(1).getOrCreateThread(2).name = 'CrBrowserMain';
+ });
+
+ assert.isFalse(ChromeGpuHelper.isGpuProcess(model.getOrCreateProcess(1)));
+ });
+
+ test('isGpuProcess_falseIfRendererProcess', function() {
+ const model = tr.c.TestUtils.newModel(model => {
+ model.getOrCreateProcess(1).getOrCreateThread(1).name = 'CrGpuMain';
+ model.getOrCreateProcess(1).getOrCreateThread(2).name = 'CrRendererMain';
+ });
+
+ assert.isFalse(ChromeGpuHelper.isGpuProcess(model.getOrCreateProcess(1)));
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/helpers/chrome_model_helper.html b/chromium/third_party/catapult/tracing/tracing/model/helpers/chrome_model_helper.html
new file mode 100644
index 00000000000..156a96b1304
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/helpers/chrome_model_helper.html
@@ -0,0 +1,168 @@
+<!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/guid.html">
+<link rel="import" href="/tracing/base/utils.html">
+<link rel="import" href="/tracing/model/helpers/chrome_browser_helper.html">
+<link rel="import" href="/tracing/model/helpers/chrome_gpu_helper.html">
+<link rel="import" href="/tracing/model/helpers/chrome_renderer_helper.html">
+<link rel="import" href="/tracing/model/helpers/telemetry_helper.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Utilities for accessing trace data about the Chrome browser.
+ */
+tr.exportTo('tr.model.helpers', function() {
+ function findChromeBrowserProcesses(model) {
+ return model.getAllProcesses(
+ tr.model.helpers.ChromeBrowserHelper.isBrowserProcess);
+ }
+
+ function findChromeRenderProcesses(model) {
+ return model.getAllProcesses(
+ tr.model.helpers.ChromeRendererHelper.isRenderProcess);
+ }
+
+ function findChromeGpuProcess(model) {
+ const gpuProcesses = model.getAllProcesses(
+ tr.model.helpers.ChromeGpuHelper.isGpuProcess);
+ if (gpuProcesses.length !== 1) return undefined;
+ return gpuProcesses[0];
+ }
+
+ function findTelemetrySurfaceFlingerProcess(model) {
+ const surfaceFlingerProcesses = model.getAllProcesses(
+ process => (process.name === 'SurfaceFlinger'));
+ if (surfaceFlingerProcesses.length !== 1) return undefined;
+ return surfaceFlingerProcesses[0];
+ }
+
+ function ChromeModelHelper(model) {
+ this.model_ = model;
+
+ // Find browserHelpers.
+ const browserProcesses = findChromeBrowserProcesses(model);
+ this.browserHelpers_ = browserProcesses.map(
+ p => new tr.model.helpers.ChromeBrowserHelper(this, p));
+
+ // Find gpuHelper.
+ const gpuProcess = findChromeGpuProcess(model);
+ if (gpuProcess) {
+ this.gpuHelper_ = new tr.model.helpers.ChromeGpuHelper(
+ this, gpuProcess);
+ } else {
+ this.gpuHelper_ = undefined;
+ }
+
+ // Find rendererHelpers.
+ const rendererProcesses_ = findChromeRenderProcesses(model);
+
+ this.rendererHelpers_ = {};
+ rendererProcesses_.forEach(function(renderProcess) {
+ const rendererHelper = new tr.model.helpers.ChromeRendererHelper(
+ this, renderProcess);
+ this.rendererHelpers_[rendererHelper.pid] = rendererHelper;
+ }, this);
+
+ this.surfaceFlingerProcess_ = findTelemetrySurfaceFlingerProcess(model);
+
+ this.chromeBounds_ = undefined;
+
+ this.telemetryHelper_ = new tr.model.helpers.TelemetryHelper(this);
+ }
+
+ ChromeModelHelper.guid = tr.b.GUID.allocateSimple();
+
+ ChromeModelHelper.supportsModel = function(model) {
+ if (findChromeBrowserProcesses(model).length) return true;
+ if (findChromeRenderProcesses(model).length) return true;
+ return false;
+ };
+
+ ChromeModelHelper.prototype = {
+ get pid() {
+ throw new Error('woah');
+ },
+
+ get process() {
+ throw new Error('woah');
+ },
+
+ get model() {
+ return this.model_;
+ },
+
+ // TODO: Make all users of ChromeModelHelper support multiple browsers and
+ // remove this getter (see #2119).
+ get browserProcess() {
+ if (this.browserHelper === undefined) return undefined;
+ return this.browserHelper.process;
+ },
+
+ // TODO: Make all users of ChromeModelHelper support multiple browsers and
+ // remove this getter (see #2119).
+ get browserHelper() {
+ return this.browserHelpers_[0];
+ },
+
+ get browserHelpers() {
+ return this.browserHelpers_;
+ },
+
+ get gpuHelper() {
+ return this.gpuHelper_;
+ },
+
+ get rendererHelpers() {
+ return this.rendererHelpers_;
+ },
+
+ get surfaceFlingerProcess() {
+ return this.surfaceFlingerProcess_;
+ },
+
+ /**
+ * Returns the minimal bounds that includes all Chrome-related slices, or
+ * undefined if no such minimal bounds could be established. This relies on
+ * the assumption that all Chrome-relevant traces are bounded by the browser
+ * process.
+ */
+ get chromeBounds() {
+ if (!this.chromeBounds_) {
+ this.chromeBounds_ = new tr.b.math.Range();
+ for (const browserHelper of Object.values(this.browserHelpers)) {
+ this.chromeBounds_.addRange(browserHelper.process.bounds);
+ }
+
+ for (const rendererHelper of Object.values(this.rendererHelpers)) {
+ this.chromeBounds_.addRange(rendererHelper.process.bounds);
+ }
+
+ if (this.gpuHelper) {
+ this.chromeBounds_.addRange(this.gpuHelper.process.bounds);
+ }
+ }
+
+ if (this.chromeBounds_.isEmpty) {
+ return undefined;
+ }
+
+ return this.chromeBounds_;
+ },
+
+ get telemetryHelper() {
+ return this.telemetryHelper_;
+ }
+ };
+
+ return {
+ ChromeModelHelper,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/helpers/chrome_model_helper_test.html b/chromium/third_party/catapult/tracing/tracing/model/helpers/chrome_model_helper_test.html
new file mode 100644
index 00000000000..e347cd883ba
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/helpers/chrome_model_helper_test.html
@@ -0,0 +1,238 @@
+<!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/extras/chrome/chrome_test_utils.html">
+<link rel="import" href="/tracing/extras/importer/trace_event_importer.html">
+<link rel="import" href="/tracing/model/helpers/chrome_browser_helper.html">
+<link rel="import" href="/tracing/model/helpers/chrome_model_helper.html">
+<link rel="import" href="/tracing/model/model.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const newAsyncSliceEx = tr.c.TestUtils.newAsyncSliceEx;
+
+ test('getLatencyData', function() {
+ const m = tr.e.chrome.ChromeTestUtils.newChromeModel(function(m) {
+ m.browserMain.asyncSliceGroup.push(newAsyncSliceEx({
+ title: 'InputLatency::GestureScrollUpdate',
+ cat: 'benchmark',
+ start: 0,
+ end: 10,
+ id: '0x100',
+ isTopLevel: true,
+ args: {
+ data: {
+ INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT: {'time': 0},
+ INPUT_EVENT_LATENCY_TERMINATED_FRAME_SWAP_COMPONENT: {time: 10}
+ }
+ }
+ }));
+ });
+
+ const modelHelper = m.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);
+ const latencyEvents = modelHelper.browserHelper.getLatencyEventsInRange(
+ m.bounds);
+ assert.strictEqual(latencyEvents.length, 1);
+ });
+
+ test('getFrametime', function() {
+ let frameTs;
+ const events = [];
+ // Browser process 3507
+ events.push({'cat': '__metadata', 'pid': 3507, 'tid': 3507, 'ts': 0, 'ph': 'M', 'name': 'thread_name', 'args': {'name': 'CrBrowserMain'}}); // @suppress longLineCheck
+
+ // Renderer process 3508
+ events.push({'cat': '__metadata', 'pid': 3508, 'tid': 3508, 'ts': 0, 'ph': 'M', 'name': 'thread_name', 'args': {'name': 'CrRendererMain'}}); // @suppress longLineCheck
+ // Compositor thread 3510
+ events.push({'cat': '__metadata', 'pid': 3508, 'tid': 3510, 'ts': 0, 'ph': 'M', 'name': 'thread_name', 'args': {'name': 'Compositor'}}); // @suppress longLineCheck
+
+ // Renderer process 3509
+ events.push({'cat': '__metadata', 'pid': 3509, 'tid': 3509, 'ts': 0, 'ph': 'M', 'name': 'thread_name', 'args': {'name': 'CrRendererMain'}}); // @suppress longLineCheck
+
+ // Compositor thread 3511
+ events.push({'cat': '__metadata', 'pid': 3509, 'tid': 3511, 'ts': 0, 'ph': 'M', 'name': 'thread_name', 'args': {'name': 'Compositor'}}); // @suppress longLineCheck
+
+ frameTs = 0;
+ // Add impl rendering stats for browser process 3507
+ for (let i = 0; i < 10; i++) {
+ events.push({'cat': 'benchmark', 'pid': 3507, 'tid': 3507, 'ts': frameTs, 'ph': 'i', 'name': 'BenchmarkInstrumentation::ImplThreadRenderingStats', 's': 't'}); // @suppress longLineCheck
+ frameTs += 16000 + 1000 * (i % 2);
+ }
+
+ frameTs = 0;
+ // Add main rendering stats for renderer process 3508
+ for (let i = 0; i < 10; i++) {
+ events.push({'cat': 'benchmark', 'pid': 3508, 'tid': 3508, 'ts': frameTs, 'ph': 'i', 'name': 'BenchmarkInstrumentation::MainThreadRenderingStats', 's': 't'}); // @suppress longLineCheck
+ frameTs += 16000 + 1000 * (i % 2);
+ }
+ events.push({'cat': 'benchmark', 'pid': 3508, 'tid': 3510, 'ts': 1600, 'ph': 'i', 'name': 'KeepAlive', 's': 't'}); // @suppress longLineCheck
+
+ frameTs = 0;
+ // Add impl and main rendering stats for renderer process 3509
+ for (let i = 0; i < 10; i++) {
+ events.push({'cat': 'benchmark', 'pid': 3509, 'tid': 3511, 'ts': frameTs, 'ph': 'i', 'name': 'BenchmarkInstrumentation::ImplThreadRenderingStats', 's': 't'}); // @suppress longLineCheck
+ events.push({'cat': 'benchmark', 'pid': 3509, 'tid': 3509, 'ts': frameTs, 'ph': 'i', 'name': 'BenchmarkInstrumentation::MainThreadRenderingStats', 's': 't'}); // @suppress longLineCheck
+ frameTs += 16000 + 1000 * (i % 2);
+ }
+
+ const m = tr.c.TestUtils.newModelWithEvents([events]);
+ const modelHelper = m.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);
+
+ // Testing browser impl and main rendering stats.
+ let frameEvents = modelHelper.browserHelper.getFrameEventsInRange(
+ tr.model.helpers.IMPL_FRAMETIME_TYPE, m.bounds);
+ let frametimeData = tr.model.helpers.getFrametimeDataFromEvents(
+ frameEvents);
+ assert.strictEqual(frametimeData.length, 9);
+ for (let i = 0; i < frametimeData.length; i++) {
+ assert.strictEqual(frametimeData[i].frametime, 16 + i % 2);
+ }
+ // No main rendering stats.
+ frameEvents = modelHelper.browserHelper.getFrameEventsInRange(
+ tr.model.helpers.MAIN_FRAMETIME_TYPE, m.bounds);
+ assert.strictEqual(frameEvents.length, 0);
+
+
+ // Testing renderer 3508 impl and main rendering stats.
+ frameEvents = modelHelper.rendererHelpers[3508].getFrameEventsInRange(
+ tr.model.helpers.MAIN_FRAMETIME_TYPE, m.bounds);
+ frametimeData = tr.model.helpers.getFrametimeDataFromEvents(frameEvents);
+ assert.strictEqual(frametimeData.length, 9);
+ for (let i = 0; i < frametimeData.length; i++) {
+ assert.strictEqual(frametimeData[i].frametime, 16 + i % 2);
+ }
+
+ // No impl rendering stats.
+ frameEvents = modelHelper.rendererHelpers[3508].getFrameEventsInRange(
+ tr.model.helpers.IMPL_FRAMETIME_TYPE, m.bounds);
+ assert.strictEqual(frameEvents.length, 0);
+
+
+ // Testing renderer 3509 impl and main rendering stats.
+ frameEvents = modelHelper.rendererHelpers[3509].getFrameEventsInRange(
+ tr.model.helpers.IMPL_FRAMETIME_TYPE, m.bounds);
+ frametimeData = tr.model.helpers.getFrametimeDataFromEvents(frameEvents);
+ assert.strictEqual(frametimeData.length, 9);
+ for (let i = 0; i < frametimeData.length; i++) {
+ assert.strictEqual(frametimeData[i].frametime, 16 + i % 2);
+ }
+
+ frameEvents = modelHelper.rendererHelpers[3509].getFrameEventsInRange(
+ tr.model.helpers.MAIN_FRAMETIME_TYPE, m.bounds);
+ frametimeData = tr.model.helpers.getFrametimeDataFromEvents(frameEvents);
+ assert.strictEqual(frametimeData.length, 9);
+ for (let i = 0; i < frametimeData.length; i++) {
+ assert.strictEqual(frametimeData[i].frametime, 16 + i % 2);
+ }
+ });
+
+ test('multipleBrowsers', function() {
+ const m = tr.c.TestUtils.newModel(function(model) {
+ const browserProcess1 = model.getOrCreateProcess(1);
+ browserProcess1.getOrCreateThread(2).name = 'CrBrowserMain';
+
+ const browserProcess2 = model.getOrCreateProcess(3);
+ browserProcess2.getOrCreateThread(4);
+ browserProcess2.getOrCreateThread(5).name = 'CrBrowserMain';
+
+ const nonBrowserProcess = model.getOrCreateProcess(6);
+ nonBrowserProcess.getOrCreateThread(7);
+
+ const browserProcess3 = model.getOrCreateProcess(8);
+ browserProcess3.getOrCreateThread(9).name = 'CrBrowserMain';
+ browserProcess3.getOrCreateThread(10);
+ });
+
+ const modelHelper = m.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);
+ const browserHelpers = modelHelper.browserHelpers;
+
+ // Check that the correct processes were marked as Chrome browser processes.
+ assert.sameMembers(browserHelpers.map(h => h.process.pid), [1, 3, 8]);
+
+ // Check that the browser helpers have the correct structure.
+ browserHelpers.forEach(function(helper) {
+ assert.instanceOf(helper, tr.model.helpers.ChromeBrowserHelper);
+ assert.strictEqual(helper.modelHelper, modelHelper);
+ });
+ });
+
+ test('chromeBounds_considersAllChromeProcesses', function() {
+ const model1 = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
+ model.browserMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ type: tr.model.ThreadSlice,
+ isTopLevel: true,
+ start: 0,
+ duration: 100,
+ }));
+ model.rendererMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ type: tr.model.ThreadSlice,
+ isTopLevel: true,
+ start: 200,
+ duration: 100,
+ }));
+ });
+
+ const model2 = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
+ const gpuProcess = model.getOrCreateProcess(42);
+ const gpuMainThread = gpuProcess.getOrCreateThread(1);
+ gpuMainThread.name = 'CrGpuMain';
+ gpuMainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ type: tr.model.ThreadSlice,
+ isTopLevel: true,
+ start: 0,
+ duration: 50,
+ }));
+ model.browserMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ type: tr.model.ThreadSlice,
+ isTopLevel: true,
+ start: 100,
+ duration: 50,
+ }));
+ });
+
+ const modelHelper1 =
+ model1.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);
+ assert.strictEqual(modelHelper1.chromeBounds.min, 0);
+ assert.strictEqual(modelHelper1.chromeBounds.max, 300);
+
+ const modelHelper2 =
+ model2.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);
+ assert.strictEqual(modelHelper2.chromeBounds.min, 0);
+ assert.strictEqual(modelHelper2.chromeBounds.max, 150);
+ });
+
+ test('chromeBounds_onlyConsidersChromeProcesses', function() {
+ const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
+ model.browserMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ type: tr.model.ThreadSlice,
+ isTopLevel: true,
+ start: 0,
+ duration: 100,
+ }));
+
+ // The bounds of this process should not be included in chrome bounds.
+ const nonChromeProcess = model.getOrCreateProcess(1234);
+ nonChromeProcess.name = 'Telemetry';
+ const nonChromeThread = nonChromeProcess.getOrCreateThread(1);
+ nonChromeThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ type: tr.model.ThreadSlice,
+ isTopLevel: true,
+ start: 200,
+ duration: 100,
+ }));
+ });
+
+ const modelHelper =
+ model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);
+ assert.strictEqual(modelHelper.chromeBounds.min, 0);
+ assert.strictEqual(modelHelper.chromeBounds.max, 100);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/helpers/chrome_process_helper.html b/chromium/third_party/catapult/tracing/tracing/model/helpers/chrome_process_helper.html
new file mode 100644
index 00000000000..208a4131c34
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/helpers/chrome_process_helper.html
@@ -0,0 +1,120 @@
+<!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/base.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Utilities for accessing trace data about the Chrome browser.
+ */
+tr.exportTo('tr.model.helpers', function() {
+ const MAIN_FRAMETIME_TYPE = 'main_frametime_type';
+ const IMPL_FRAMETIME_TYPE = 'impl_frametime_type';
+
+ const MAIN_RENDERING_STATS =
+ 'BenchmarkInstrumentation::MainThreadRenderingStats';
+ const IMPL_RENDERING_STATS =
+ 'BenchmarkInstrumentation::ImplThreadRenderingStats';
+
+
+ function getSlicesIntersectingRange(rangeOfInterest, slices) {
+ const slicesInFilterRange = [];
+ for (let i = 0; i < slices.length; i++) {
+ const slice = slices[i];
+ if (rangeOfInterest.intersectsExplicitRangeInclusive(
+ slice.start, slice.end)) {
+ slicesInFilterRange.push(slice);
+ }
+ }
+ return slicesInFilterRange;
+ }
+
+
+ function ChromeProcessHelper(modelHelper, process) {
+ this.modelHelper = modelHelper;
+ this.process = process;
+ this.telemetryInternalRanges_ = undefined;
+ }
+
+ ChromeProcessHelper.prototype = {
+ get pid() {
+ return this.process.pid;
+ },
+
+ isTelemetryInternalEvent(slice) {
+ if (this.telemetryInternalRanges_ === undefined) {
+ this.findTelemetryInternalRanges_();
+ }
+ for (const range of this.telemetryInternalRanges_) {
+ if (range.containsExplicitRangeInclusive(slice.start, slice.end)) {
+ return true;
+ }
+ }
+ return false;
+ },
+
+ findTelemetryInternalRanges_() {
+ this.telemetryInternalRanges_ = [];
+ let start = 0;
+ for (const thread of Object.values(this.process.threads)) {
+ for (const slice of thread.asyncSliceGroup.getDescendantEvents()) {
+ if (/^telemetry\.internal\..*\.start$/.test(slice.title)) {
+ start = slice.start;
+ } else if (/^telemetry\.internal\..*\.end$/.test(slice.title) &&
+ start !== undefined) {
+ this.telemetryInternalRanges_.push(
+ tr.b.math.Range.fromExplicitRange(start, slice.end));
+ start = undefined;
+ }
+ }
+ }
+ },
+
+ getFrameEventsInRange(frametimeType, range) {
+ const titleToGet = (frametimeType === MAIN_FRAMETIME_TYPE ?
+ MAIN_RENDERING_STATS : IMPL_RENDERING_STATS);
+
+ const frameEvents = [];
+ for (const event of this.process.getDescendantEvents()) {
+ if (event.title === titleToGet) {
+ if (range.intersectsExplicitRangeInclusive(event.start, event.end)) {
+ frameEvents.push(event);
+ }
+ }
+ }
+
+ frameEvents.sort(function(a, b) {return a.start - b.start;});
+ return frameEvents;
+ }
+ };
+
+ function getFrametimeDataFromEvents(frameEvents) {
+ const frametimeData = [];
+ for (let i = 1; i < frameEvents.length; i++) {
+ const diff = frameEvents[i].start - frameEvents[i - 1].start;
+ frametimeData.push({
+ 'x': frameEvents[i].start,
+ 'frametime': diff
+ });
+ }
+ return frametimeData;
+ }
+
+ return {
+ ChromeProcessHelper,
+
+ MAIN_FRAMETIME_TYPE,
+ IMPL_FRAMETIME_TYPE,
+ MAIN_RENDERING_STATS,
+ IMPL_RENDERING_STATS,
+
+ getSlicesIntersectingRange,
+ getFrametimeDataFromEvents,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/helpers/chrome_renderer_helper.html b/chromium/third_party/catapult/tracing/tracing/model/helpers/chrome_renderer_helper.html
new file mode 100644
index 00000000000..a10e7eaff01
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/helpers/chrome_renderer_helper.html
@@ -0,0 +1,76 @@
+<!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/math/range.html">
+<link rel="import" href="/tracing/model/helpers/chrome_process_helper.html">
+<link rel="import" href="/tracing/model/helpers/chrome_thread_helper.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.model.helpers', function() {
+ const ChromeThreadHelper = tr.model.helpers.ChromeThreadHelper;
+
+ function ChromeRendererHelper(modelHelper, process) {
+ tr.model.helpers.ChromeProcessHelper.call(this, modelHelper, process);
+ this.mainThread_ = process.findAtMostOneThreadNamed('CrRendererMain') ||
+ process.findAtMostOneThreadNamed('Chrome_InProcRendererThread');
+ this.compositorThread_ = process.findAtMostOneThreadNamed('Compositor');
+ this.rasterWorkerThreads_ = process.findAllThreadsMatching(function(t) {
+ if (t.name === undefined) return false;
+ if (t.name.indexOf('CompositorTileWorker') === 0) return true;
+ if (t.name.indexOf('CompositorRasterWorker') === 0) return true;
+ return false;
+ });
+
+ if (!process.name) {
+ process.name = ChromeRendererHelper.PROCESS_NAME;
+ }
+ }
+
+ ChromeRendererHelper.PROCESS_NAME = 'Renderer';
+
+ // Returns true if there is either a main thread or a compositor thread.
+ ChromeRendererHelper.isRenderProcess = function(process) {
+ if (process.findAtMostOneThreadNamed('CrRendererMain')) return true;
+ if (process.findAtMostOneThreadNamed('Compositor')) return true;
+ return false;
+ };
+
+ ChromeRendererHelper.isTracingProcess = function(process) {
+ return process.labels !== undefined &&
+ process.labels.length === 1 &&
+ process.labels[0] === 'chrome://tracing';
+ };
+
+ ChromeRendererHelper.prototype = {
+ __proto__: tr.model.helpers.ChromeProcessHelper.prototype,
+
+ // May be undefined.
+ get mainThread() {
+ return this.mainThread_;
+ },
+
+ // May be undefined.
+ get compositorThread() {
+ return this.compositorThread_;
+ },
+
+ // May be empty.
+ get rasterWorkerThreads() {
+ return this.rasterWorkerThreads_;
+ },
+
+ get isChromeTracingUI() {
+ return ChromeRendererHelper.isTracingProcess(this.process);
+ },
+ };
+
+ return {
+ ChromeRendererHelper,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/helpers/chrome_renderer_helper_test.html b/chromium/third_party/catapult/tracing/tracing/model/helpers/chrome_renderer_helper_test.html
new file mode 100644
index 00000000000..8ca62f70d25
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/helpers/chrome_renderer_helper_test.html
@@ -0,0 +1,16 @@
+<!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/core/test_utils.html">
+<link rel="import" href="/tracing/model/helpers/chrome_model_helper.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/helpers/chrome_thread_helper.html b/chromium/third_party/catapult/tracing/tracing/model/helpers/chrome_thread_helper.html
new file mode 100644
index 00000000000..39d6159ec60
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/helpers/chrome_thread_helper.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<!--
+Copyright 2017 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.model.helpers', function() {
+ const NET_CATEGORIES = new Set(['net', 'netlog',
+ 'disabled-by-default-netlog', 'disabled-by-default-network']);
+
+ class ChromeThreadHelper {
+ constructor(thread) {
+ this.thread = thread;
+ }
+
+ getNetworkEvents() {
+ const networkEvents = [];
+ for (const slice of this.thread.asyncSliceGroup.slices) {
+ const categories = tr.b.getCategoryParts(slice.category);
+ const isNetEvent = category => NET_CATEGORIES.has(category);
+ if (categories.filter(isNetEvent).length === 0) continue;
+ networkEvents.push(slice);
+ }
+ return networkEvents;
+ }
+ }
+
+ return {
+ ChromeThreadHelper,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/helpers/chrome_thread_helper_test.html b/chromium/third_party/catapult/tracing/tracing/model/helpers/chrome_thread_helper_test.html
new file mode 100644
index 00000000000..49dbdd63938
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/helpers/chrome_thread_helper_test.html
@@ -0,0 +1,97 @@
+<!DOCTYPE html>
+<!--
+Copyright 2017 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/helpers/chrome_thread_helper.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const Process = tr.model.Process;
+ const Thread = tr.model.Thread;
+ const ChromeThreadHelper = tr.model.helpers.ChromeThreadHelper;
+
+ test('getNetworkEvents_empty', function() {
+ const model = new tr.Model();
+ const t = new Thread(new tr.model.Process(model, 7), 1);
+ const threadHelper = new ChromeThreadHelper(t);
+ assert.sameDeepMembers([],
+ threadHelper.getNetworkEvents());
+ });
+
+ test('getNetworkEvents_withIrrelevantEvents', function() {
+ const model = new tr.Model();
+ const t = new Thread(new Process(model, 7), 1);
+ const netEvent1 = tr.c.TestUtils.newAsyncSliceEx({
+ cat: 'netlog',
+ title: 'Generic Network event',
+ start: 100,
+ duration: 200,
+ });
+ t.asyncSliceGroup.push(netEvent1);
+ const netEvent2 = tr.c.TestUtils.newAsyncSliceEx({
+ cat: 'net',
+ title: 'Generic Network event',
+ start: 101,
+ duration: 201,
+ });
+ t.asyncSliceGroup.push(netEvent2);
+ t.asyncSliceGroup.push(tr.c.TestUtils.newAsyncSliceEx({
+ cat: 'irrelevant async event',
+ title: 'irrelevant async event',
+ start: 100,
+ duration: 200,
+ }));
+ t.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'irrelevant sync event',
+ title: 'irrelevant sync event',
+ start: 100,
+ duration: 200,
+ }));
+ const threadHelper = new ChromeThreadHelper(t);
+ assert.sameDeepMembers([netEvent1, netEvent2],
+ threadHelper.getNetworkEvents());
+ });
+
+ test('getNetworkEvents_allTypes', function() {
+ const model = new tr.Model();
+ const t = new Thread(new Process(model, 7), 1);
+ const netEvent1 = tr.c.TestUtils.newAsyncSliceEx({
+ cat: 'netlog',
+ title: 'Generic Network event',
+ start: 100,
+ duration: 200,
+ });
+ t.asyncSliceGroup.push(netEvent1);
+ const netEvent2 = tr.c.TestUtils.newAsyncSliceEx({
+ cat: 'net',
+ title: 'Generic Network event',
+ start: 101,
+ duration: 201,
+ });
+ t.asyncSliceGroup.push(netEvent2);
+ const netEvent3 = tr.c.TestUtils.newAsyncSliceEx({
+ cat: 'disabled-by-default-netlog',
+ title: 'Generic Network event',
+ start: 101,
+ duration: 201,
+ });
+ t.asyncSliceGroup.push(netEvent3);
+ const netEvent4 = tr.c.TestUtils.newAsyncSliceEx({
+ cat: 'disabled-by-default-network',
+ title: 'Generic Network event',
+ start: 101,
+ duration: 201,
+ });
+ t.asyncSliceGroup.push(netEvent4);
+ const threadHelper = new ChromeThreadHelper(t);
+ assert.sameDeepMembers([netEvent1, netEvent2, netEvent3, netEvent4],
+ threadHelper.getNetworkEvents());
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/helpers/telemetry_helper.html b/chromium/third_party/catapult/tracing/tracing/model/helpers/telemetry_helper.html
new file mode 100644
index 00000000000..0c4486f8b54
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/helpers/telemetry_helper.html
@@ -0,0 +1,128 @@
+<!DOCTYPE html>
+<!--
+Copyright 2018 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/helpers/chrome_renderer_helper.html">
+<link rel="import" href="/tracing/model/user_model/segment.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.model.helpers', function() {
+ const GESTURE_EVENT = 'SyntheticGestureController::running';
+ const IR_REG_EXP = /Interaction\.([^/]+)(\/[^/]*)?$/;
+ const ChromeRendererHelper = tr.model.helpers.ChromeRendererHelper;
+
+ class TelemetryHelper {
+ constructor(modelHelper) {
+ this.modelHelper = modelHelper;
+
+ this.renderersWithIR_ = undefined;
+ this.segments_ = undefined;
+ this.uiSegments_ = undefined;
+ }
+
+ get renderersWithIR() {
+ this.findIRs_();
+ return this.renderersWithIR_;
+ }
+
+ get segments() {
+ this.findIRs_();
+ return this.segments_;
+ }
+
+ get uiSegments() {
+ this.findIRs_();
+ return this.uiSegments_;
+ }
+
+ /**
+ * Finds interesting segments we want to compute metrics in. We use trace
+ * events produced by Telemetry. One drawback of this method is that we
+ * cannot compute the metric from Chrome traces that are not produced by
+ * Telemetry. Alternatively, we could use the user model to detect
+ * interesting segments, like animation segments in the following way:
+ *
+ * const animationSegments = model.userModel.segments.filter(
+ * segment => segment.expectations.find(
+ * ue => ue instanceof tr.model.um.AnimationExpectation));
+ *
+ * However, the user model cannot detect all types of animations, yet. For
+ * more discussion about using test generated interaction records vs the
+ * user model please refer to http://bit.ly/ir-tbmv2. TODO(chiniforooshan):
+ * Improve the user model detection of animations.
+ *
+ * Also, some of the metrics we compute here are not animations specific.
+ */
+ findIRs_() {
+ if (this.segments_ !== undefined) return;
+
+ this.renderersWithIR_ = [];
+ const gestureEvents = [];
+ const interactionRecords = [];
+ const processes = Object.values(this.modelHelper.rendererHelpers)
+ .concat(this.modelHelper.browserHelpers)
+ .map(processHelper => processHelper.process);
+ for (const process of processes) {
+ let foundIR = false;
+ for (const thread of Object.values(process.threads)) {
+ for (const slice of thread.asyncSliceGroup.slices) {
+ if (slice.title === GESTURE_EVENT) {
+ gestureEvents.push(slice);
+ } else if (IR_REG_EXP.test(slice.title)) {
+ interactionRecords.push(slice);
+ foundIR = true;
+ }
+ }
+ }
+ if (foundIR && ChromeRendererHelper.isRenderProcess(process) &&
+ !ChromeRendererHelper.isTracingProcess(process)) {
+ this.renderersWithIR_.push(
+ new ChromeRendererHelper(this.modelHelper, process));
+ }
+ }
+
+ // Convert interaction record slices to segments. If an interaction record
+ // contains a gesture whose time range overlaps with the interaction
+ // record's range, use the gesture's time range.
+ //
+ // The synthetic gesture controller inserts a trace marker to precisely
+ // demarcate when the gesture was running. We check for overlap, not
+ // inclusion, because gesture actions can start/end slightly outside the
+ // telemetry markers on Windows.
+ this.segments_ = [];
+ this.uiSegments_ = [];
+ for (const ir of interactionRecords) {
+ const parts = IR_REG_EXP.exec(ir.title);
+ let gestureEventFound = false;
+ if (parts[1].startsWith('Gesture_')) {
+ for (const gestureEvent of gestureEvents) {
+ if (ir.boundsRange.intersectsRangeInclusive(
+ gestureEvent.boundsRange)) {
+ this.segments_.push(new tr.model.um.Segment(
+ gestureEvent.start, gestureEvent.duration));
+ gestureEventFound = true;
+ break;
+ }
+ }
+ } else if (parts[1].startsWith('ui_')) {
+ this.uiSegments_.push(new tr.model.um.Segment(ir.start, ir.duration));
+ }
+ if (!gestureEventFound) {
+ this.segments_.push(new tr.model.um.Segment(ir.start, ir.duration));
+ }
+ }
+
+ this.segments_.sort((x, y) => x.start - y.start);
+ this.uiSegments_.sort((x, y) => x.start - y.start);
+ }
+ }
+
+ return {
+ TelemetryHelper,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/helpers/telemetry_helper_test.html b/chromium/third_party/catapult/tracing/tracing/model/helpers/telemetry_helper_test.html
new file mode 100644
index 00000000000..1434603886e
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/helpers/telemetry_helper_test.html
@@ -0,0 +1,101 @@
+<!DOCTYPE html>
+<!--
+Copyright 2018 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/helpers/chrome_renderer_helper.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ test('renderersWithIR', function() {
+ const m = tr.c.TestUtils.newModel((m) => {
+ m.getOrCreateProcess(0).getOrCreateThread(0).name = 'CrBrowserMain';
+
+ // There is no IR in this renderer process.
+ const r1 = m.getOrCreateProcess(1).getOrCreateThread(1);
+ r1.name = 'CrRendererMain';
+ r1.asyncSliceGroup.push(
+ tr.c.TestUtils.newAsyncSliceNamed('AnAsyncSlice', 1, 1));
+
+ // This is the renderer created by Telemetry.
+ const r2 = m.getOrCreateProcess(2).getOrCreateThread(2);
+ r2.name = 'CrRendererMain';
+ r2.asyncSliceGroup.push(
+ tr.c.TestUtils.newAsyncSliceNamed('Interaction.Action', 1, 1));
+
+ // This is the Telemetry process, not a real renderer process.
+ const r3 = m.getOrCreateProcess(3).getOrCreateThread(3);
+ r3.name = 'CrRendererMain';
+ r3.parent.labels = ['chrome://tracing'];
+ r3.asyncSliceGroup.push(
+ tr.c.TestUtils.newAsyncSliceNamed('Interaction.Action', 1, 1));
+ });
+ const modelHelper = m.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);
+ const renderers = modelHelper.telemetryHelper.renderersWithIR;
+ assert.strictEqual(1, renderers.length);
+ assert.strictEqual(2, renderers[0].process.pid);
+ });
+
+ test('segments', function() {
+ const m = tr.c.TestUtils.newModel((m) => {
+ // There is no IR in this renderer process.
+ const r = m.getOrCreateProcess(1).getOrCreateThread(1);
+ r.name = 'CrRendererMain';
+ r.asyncSliceGroup.push(tr.c.TestUtils.newAsyncSliceNamed(
+ 'SyntheticGestureController::running', 6, 2));
+ r.asyncSliceGroup.push(
+ tr.c.TestUtils.newAsyncSliceNamed('Interaction.A', 1, 1));
+ r.asyncSliceGroup.push(
+ tr.c.TestUtils.newAsyncSliceNamed('Interaction.Gesture_C', 5, 2));
+ r.asyncSliceGroup.push(
+ tr.c.TestUtils.newAsyncSliceNamed('Interaction.ui_B', 3, 1));
+ });
+
+ // Async slices are:
+ //
+ // 1 2 3 4 5 6 7 8
+ // Interactions <-- A --> <-- B --> <------ C ------>
+ // Gestures <--------------->
+ //
+ // Segments should be: [1, 2], [3, 4], and [6, 8].
+ const modelHelper = m.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);
+ const segments = modelHelper.telemetryHelper.segments;
+ assert.strictEqual(3, segments.length);
+ assert.deepEqual([1, 2], [segments[0].start, segments[0].end]);
+ assert.deepEqual([3, 4], [segments[1].start, segments[1].end]);
+ assert.deepEqual([6, 8], [segments[2].start, segments[2].end]);
+ });
+
+ test('uiSegments', function() {
+ const m = tr.c.TestUtils.newModel((m) => {
+ // There is no IR in this renderer process.
+ const r = m.getOrCreateProcess(1).getOrCreateThread(1);
+ r.name = 'CrRendererMain';
+ r.asyncSliceGroup.push(tr.c.TestUtils.newAsyncSliceNamed(
+ 'SyntheticGestureController::running', 6, 2));
+ r.asyncSliceGroup.push(
+ tr.c.TestUtils.newAsyncSliceNamed('Interaction.A', 1, 1));
+ r.asyncSliceGroup.push(
+ tr.c.TestUtils.newAsyncSliceNamed('Interaction.Gesture_C', 5, 2));
+ r.asyncSliceGroup.push(
+ tr.c.TestUtils.newAsyncSliceNamed('Interaction.ui_B', 3, 1));
+ });
+
+ // Async slices are:
+ //
+ // 1 2 3 4 5 6 7 8
+ // Interactions <-- A --> <-- B --> <------ C ------>
+ // Gestures <--------------->
+ //
+ // The only UI segment is [3, 4].
+ const modelHelper = m.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);
+ const uiSegments = modelHelper.telemetryHelper.uiSegments;
+ assert.strictEqual(1, uiSegments.length);
+ assert.deepEqual([3, 4], [uiSegments[0].start, uiSegments[0].end]);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/instant_event.html b/chromium/third_party/catapult/tracing/tracing/model/instant_event.html
new file mode 100644
index 00000000000..d2f6cfbfd0b
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/instant_event.html
@@ -0,0 +1,100 @@
+<!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/timed_event.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.model', function() {
+ const InstantEventType = {
+ GLOBAL: 1,
+ PROCESS: 2
+ };
+
+ /**
+ * An InstantEvent is a zero-duration event.
+ *
+ * @constructor
+ */
+ function InstantEvent(category, title, colorId, start, args) {
+ tr.model.TimedEvent.call(this, start);
+
+ this.category = category || '';
+ this.title = title;
+ this.colorId = colorId;
+ this.args = args;
+
+ this.type = undefined;
+ }
+
+ InstantEvent.prototype = {
+ __proto__: tr.model.TimedEvent.prototype
+ };
+
+ /**
+ * A GlobalInstantEvent is a zero-duration event that's not tied to any
+ * particular process.
+ *
+ * An example is a trace event that's issued when a new USB device is plugged
+ * into the machine.
+ *
+ * @constructor
+ */
+ function GlobalInstantEvent(category, title, colorId, start, args) {
+ InstantEvent.apply(this, arguments);
+ this.type = InstantEventType.GLOBAL;
+ }
+
+ GlobalInstantEvent.prototype = {
+ __proto__: InstantEvent.prototype,
+ get userFriendlyName() {
+ return 'Global instant event ' + this.title + ' @ ' +
+ tr.b.Unit.byName.timeStampInMs.format(start);
+ }
+ };
+
+ /**
+ * A ProcessInstantEvent is a zero-duration event that's tied to a
+ * particular process.
+ *
+ * An example is a trace event that's issued when a kill signal is received.
+ *
+ * @constructor
+ */
+ function ProcessInstantEvent(category, title, colorId, start, args) {
+ InstantEvent.apply(this, arguments);
+ this.type = InstantEventType.PROCESS;
+ }
+
+ ProcessInstantEvent.prototype = {
+ __proto__: InstantEvent.prototype,
+
+ get userFriendlyName() {
+ return 'Process-level instant event ' + this.title + ' @ ' +
+ tr.b.Unit.byName.timeStampInMs.format(start);
+ }
+ };
+
+ tr.model.EventRegistry.register(
+ InstantEvent,
+ {
+ name: 'instantEvent',
+ pluralName: 'instantEvents'
+ });
+
+ return {
+ GlobalInstantEvent,
+ ProcessInstantEvent,
+
+ InstantEventType,
+ InstantEvent,
+ };
+});
+</script>
+
diff --git a/chromium/third_party/catapult/tracing/tracing/model/interaction_record_test.html b/chromium/third_party/catapult/tracing/tracing/model/interaction_record_test.html
new file mode 100644
index 00000000000..8380bcb0849
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/interaction_record_test.html
@@ -0,0 +1,115 @@
+<!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/model.html">
+<link rel="import" href="/tracing/model/user_model/stub_expectation.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const TestUtils = tr.c.TestUtils;
+ const CompoundEventSelectionState = tr.model.CompoundEventSelectionState;
+
+ function createModel(opt_customizeModelCallback) {
+ return TestUtils.newModel(function(model) {
+ model.p1 = model.getOrCreateProcess(1);
+ model.t2 = model.p1.getOrCreateThread(2);
+
+ model.s1 = model.t2.sliceGroup.pushSlice(TestUtils.newSliceEx({
+ title: 'a', start: 10, end: 20
+ }));
+ model.s2 = model.t2.sliceGroup.pushSlice(TestUtils.newSliceEx({
+ title: 'b', start: 20, end: 30
+ }));
+
+ model.ir1 = new tr.model.um.StubExpectation({
+ parentModel: model,
+ start: 100, end: 200,
+ typeName: 'Response',
+ normalizedEfficiency: 1.,
+ normalizedUserComfort: 0.0
+ });
+ model.userModel.expectations.push(model.ir1);
+ model.ir1.associatedEvents.push(model.s1);
+ model.ir1.associatedEvents.push(model.s2);
+
+ if (opt_customizeModelCallback) {
+ opt_customizeModelCallback(model);
+ }
+ });
+ }
+ test('notSelected', function() {
+ const model = createModel();
+
+ const sel = new tr.model.EventSet();
+
+ assert.strictEqual(CompoundEventSelectionState.NOT_SELECTED,
+ model.ir1.computeCompoundEvenSelectionState(sel));
+ });
+
+ test('directSelected', function() {
+ const model = createModel();
+
+ const sel = new tr.model.EventSet();
+ sel.push(model.ir1);
+
+ assert.strictEqual(CompoundEventSelectionState.EVENT_SELECTED,
+ model.ir1.computeCompoundEvenSelectionState(sel));
+ });
+
+ test('directAndSomeAssociatedSelected', function() {
+ const model = createModel();
+
+ const sel = new tr.model.EventSet();
+ sel.push(model.ir1);
+ sel.push(model.s1);
+
+ assert.strictEqual(
+ CompoundEventSelectionState.EVENT_AND_SOME_ASSOCIATED_SELECTED,
+ model.ir1.computeCompoundEvenSelectionState(sel));
+ });
+
+ test('allAssociatedEventsSelected', function() {
+ const model = createModel();
+
+ const sel = new tr.model.EventSet();
+ sel.push(model.s1);
+ sel.push(model.s2);
+
+ assert.strictEqual(
+ CompoundEventSelectionState.ALL_ASSOCIATED_EVENTS_SELECTED,
+ model.ir1.computeCompoundEvenSelectionState(sel));
+ });
+
+ test('directAndAllAssociated', function() {
+ const model = createModel();
+
+ const sel = new tr.model.EventSet();
+ sel.push(model.ir1);
+ sel.push(model.s1);
+ sel.push(model.s2);
+
+ assert.strictEqual(
+ CompoundEventSelectionState.EVENT_AND_ALL_ASSOCIATED_SELECTED,
+ model.ir1.computeCompoundEvenSelectionState(sel));
+ });
+
+ test('stableId', function() {
+ const model = TestUtils.newModel();
+
+ const ir1 = TestUtils.newInteractionRecord(model, 0, 10);
+ const ir2 = TestUtils.newInteractionRecord(model, 10, 10);
+ const ir3 = TestUtils.newInteractionRecord(model, 20, 10);
+
+ assert.strictEqual('UserExpectation.' + ir1.guid, ir1.stableId);
+ assert.strictEqual('UserExpectation.' + ir2.guid, ir2.stableId);
+ assert.strictEqual('UserExpectation.' + ir3.guid, ir3.stableId);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/ir_coverage.html b/chromium/third_party/catapult/tracing/tracing/model/ir_coverage.html
new file mode 100644
index 00000000000..2b8264ac276
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/ir_coverage.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/utils.html">
+<link rel="import" href="/tracing/model/event_set.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.model', function() {
+ function getAssociatedEvents(irs) {
+ const allAssociatedEvents = new tr.model.EventSet();
+ irs.forEach(function(ir) {
+ ir.associatedEvents.forEach(function(event) {
+ // FlowEvents don't have parentContainers or cpuDurations, and it's
+ // annoying to highlight them.
+ if (event instanceof tr.model.FlowEvent) return;
+ allAssociatedEvents.push(event);
+ });
+ });
+ return allAssociatedEvents;
+ }
+
+ function getUnassociatedEvents(model, associatedEvents) {
+ const unassociatedEvents = new tr.model.EventSet();
+ // The set of unassociated events contains only events that are not in
+ // the set of associated events.
+ // Only add event to the set of unassociated events if it is not in
+ // the set of associated events.
+ for (const proc of model.getAllProcesses()) {
+ for (const thread of Object.values(proc.threads)) {
+ for (const event of thread.sliceGroup.getDescendantEvents()) {
+ if (!associatedEvents.contains(event)) {
+ unassociatedEvents.push(event);
+ }
+ }
+ }
+ }
+ return unassociatedEvents;
+ }
+
+ function getTotalCpuDuration(events) {
+ let cpuMs = 0;
+ events.forEach(function(event) {
+ // Add up events' cpu self time if they have any.
+ if (event.cpuSelfTime) {
+ cpuMs += event.cpuSelfTime;
+ }
+ });
+ return cpuMs;
+ }
+
+ function getIRCoverageFromModel(model) {
+ const associatedEvents = getAssociatedEvents(model.userModel.expectations);
+
+ if (!associatedEvents.length) return undefined;
+
+ const unassociatedEvents = getUnassociatedEvents(
+ model, associatedEvents);
+
+ const associatedCpuMs = getTotalCpuDuration(associatedEvents);
+ const unassociatedCpuMs = getTotalCpuDuration(unassociatedEvents);
+
+ const totalEventCount = associatedEvents.length + unassociatedEvents.length;
+ const totalCpuMs = associatedCpuMs + unassociatedCpuMs;
+ let coveredEventsCpuTimeRatio = undefined;
+ if (totalCpuMs !== 0) {
+ coveredEventsCpuTimeRatio = associatedCpuMs / totalCpuMs;
+ }
+
+ return {
+ associatedEventsCount: associatedEvents.length,
+ unassociatedEventsCount: unassociatedEvents.length,
+ associatedEventsCpuTimeMs: associatedCpuMs,
+ unassociatedEventsCpuTimeMs: unassociatedCpuMs,
+ coveredEventsCountRatio: associatedEvents.length / totalEventCount,
+ coveredEventsCpuTimeRatio
+ };
+ }
+
+ return {
+ getIRCoverageFromModel,
+ getAssociatedEvents,
+ getUnassociatedEvents,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/ir_coverage_test.html b/chromium/third_party/catapult/tracing/tracing/model/ir_coverage_test.html
new file mode 100644
index 00000000000..4a3b471ae6f
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/ir_coverage_test.html
@@ -0,0 +1,90 @@
+<!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/ir_coverage.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const newSliceEx = tr.c.TestUtils.newSliceEx;
+
+ function createModel() {
+ return tr.c.TestUtils.newModel(function(model) {
+ const process = model.getOrCreateProcess(1);
+ const thread = process.getOrCreateThread(2);
+ const s0 = thread.sliceGroup.pushSlice(newSliceEx(
+ {title: 's0', start: 0.0, duration: 1.0}));
+ s0.isTopLevel = true;
+ const unassociatedEvent = thread.sliceGroup.pushSlice(newSliceEx(
+ {title: 's1', start: 6.0, duration: 1.0}));
+ unassociatedEvent.isTopLevel = true;
+ const s2 = thread.sliceGroup.pushSlice(newSliceEx(
+ {title: 's2', start: 2.0, duration: 1.0}));
+ s2.isTopLevel = true;
+ const f0 = tr.c.TestUtils.newFlowEventEx({
+ title: 'test1',
+ start: 0,
+ end: 10,
+ startSlice: s0,
+ endSlice: s2,
+ id: '0x100'
+ });
+ model.flowEvents.push(f0);
+ const as1 = tr.c.TestUtils.newAsyncSliceEx({
+ title: 'InputLatency::GestureTap',
+ cat: 'benchmark,latencyInfo',
+ start: 2,
+ end: 10,
+ id: '0x100',
+ isTopLevel: true,
+ startThread: thread
+ });
+ thread.asyncSliceGroup.push(as1);
+ const ir = new tr.model.um.StubExpectation(
+ {parentModel: model, start: 0, duration: 7});
+ ir.associatedEvents.push(as1);
+ ir.associatedEvents.push(s0);
+ ir.associatedEvents.push(s2);
+ ir.associatedEvents.push(f0);
+ model.userModel.expectations.push(ir);
+ });
+ }
+
+ test('computeCoverage', function() {
+ const model = createModel();
+ for (const event of model.getDescendantEvents()) {
+ if (event.title === 's0' || event.title === 's2') {
+ event.cpuSelfTime = 0.4;
+ } else if (event.title === 's1') {
+ event.cpuSelfTime = 0.8;
+ }
+ }
+
+ const coverage = tr.model.getIRCoverageFromModel(model);
+ assert.strictEqual(3, coverage.associatedEventsCount);
+ assert.strictEqual(1, coverage.unassociatedEventsCount);
+ assert.closeTo(0.75, coverage.coveredEventsCountRatio, 1e-3);
+ assert.closeTo(0.8, coverage.associatedEventsCpuTimeMs, 1e-3);
+ assert.closeTo(0.8, coverage.unassociatedEventsCpuTimeMs, 1e-3);
+ assert.closeTo(0.5, coverage.coveredEventsCpuTimeRatio, 1e-3);
+ });
+
+ test('zeroCPU', function() {
+ const model = createModel();
+ const coverage = tr.model.getIRCoverageFromModel(model);
+ assert.strictEqual(3, coverage.associatedEventsCount);
+ assert.strictEqual(1, coverage.unassociatedEventsCount);
+ assert.closeTo(0.75, coverage.coveredEventsCountRatio, 1e-3);
+ assert.closeTo(0.0, coverage.associatedEventsCpuTimeMs, 1e-3);
+ assert.closeTo(0.0, coverage.unassociatedEventsCpuTimeMs, 1e-3);
+ assert.strictEqual(undefined, coverage.coveredEventsCpuTimeRatio, 1e-3);
+ });
+});
+</script>
+
diff --git a/chromium/third_party/catapult/tracing/tracing/model/kernel.html b/chromium/third_party/catapult/tracing/tracing/model/kernel.html
new file mode 100644
index 00000000000..834a91eea06
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/kernel.html
@@ -0,0 +1,137 @@
+<!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/cpu.html">
+<link rel="import" href="/tracing/model/process_base.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Provides the Process class.
+ */
+tr.exportTo('tr.model', function() {
+ const Cpu = tr.model.Cpu;
+ const ProcessBase = tr.model.ProcessBase;
+
+ /**
+ * The Kernel represents kernel-level objects in the model.
+ * @constructor
+ */
+ function Kernel(model) {
+ ProcessBase.call(this, model);
+
+ this.cpus = {};
+ this.softwareMeasuredCpuCount_ = undefined;
+ }
+
+ /**
+ * Comparison between kernels is pretty meaningless.
+ */
+ Kernel.compare = function(x, y) {
+ return 0;
+ };
+
+ Kernel.prototype = {
+ __proto__: ProcessBase.prototype,
+
+ compareTo(that) {
+ return Kernel.compare(this, that);
+ },
+
+ get userFriendlyName() {
+ return 'Kernel';
+ },
+
+ get userFriendlyDetails() {
+ return 'Kernel';
+ },
+
+ get stableId() {
+ return 'Kernel';
+ },
+
+ /**
+ * @return {Cpu} Gets a specific Cpu or creates one if
+ * it does not exist.
+ */
+ getOrCreateCpu(cpuNumber) {
+ if (!this.cpus[cpuNumber]) {
+ this.cpus[cpuNumber] = new Cpu(this, cpuNumber);
+ }
+ return this.cpus[cpuNumber];
+ },
+
+ get softwareMeasuredCpuCount() {
+ return this.softwareMeasuredCpuCount_;
+ },
+
+ set softwareMeasuredCpuCount(softwareMeasuredCpuCount) {
+ if (this.softwareMeasuredCpuCount_ !== undefined &&
+ this.softwareMeasuredCpuCount_ !== softwareMeasuredCpuCount) {
+ throw new Error(
+ 'Cannot change the softwareMeasuredCpuCount once it is set');
+ }
+
+ this.softwareMeasuredCpuCount_ = softwareMeasuredCpuCount;
+ },
+
+ /**
+ * Estimates how many cpus are in the system, for use in system load
+ * estimation.
+ *
+ * If kernel trace was provided, uses that data. Otherwise, uses the
+ * software measured cpu count.
+ */
+ get bestGuessAtCpuCount() {
+ const realCpuCount = Object.keys(this.cpus).length;
+ if (realCpuCount !== 0) {
+ return realCpuCount;
+ }
+ return this.softwareMeasuredCpuCount;
+ },
+
+ updateBounds() {
+ ProcessBase.prototype.updateBounds.call(this);
+ for (const cpuNumber in this.cpus) {
+ const cpu = this.cpus[cpuNumber];
+ cpu.updateBounds();
+ this.bounds.addRange(cpu.bounds);
+ }
+ },
+
+ createSubSlices() {
+ ProcessBase.prototype.createSubSlices.call(this);
+ for (const cpuNumber in this.cpus) {
+ const cpu = this.cpus[cpuNumber];
+ cpu.createSubSlices();
+ }
+ },
+
+ addCategoriesToDict(categoriesDict) {
+ ProcessBase.prototype.addCategoriesToDict.call(this, categoriesDict);
+ for (const cpuNumber in this.cpus) {
+ this.cpus[cpuNumber].addCategoriesToDict(categoriesDict);
+ }
+ },
+
+ getSettingsKey() {
+ return 'kernel';
+ },
+
+ * childEventContainers() {
+ yield* ProcessBase.prototype.childEventContainers.call(this);
+ yield* Object.values(this.cpus);
+ },
+ };
+
+ return {
+ Kernel,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/kernel_test.html b/chromium/third_party/catapult/tracing/tracing/model/kernel_test.html
new file mode 100644
index 00000000000..ffe145adef6
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/kernel_test.html
@@ -0,0 +1,73 @@
+<!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/kernel.html">
+<link rel="import" href="/tracing/model/model.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ function newModel(events, callback) {
+ return tr.c.TestUtils.newModelWithEvents([events], {
+ shiftWorldToZero: false,
+ pruneContainers: false,
+ customizeModelCallback: callback
+ });
+ }
+
+ test('bestGuessAtCpuCountWithNoData', function() {
+ const m = newModel([]);
+ assert.isUndefined(m.kernel.bestGuessAtCpuCount);
+ });
+
+ test('bestGuessAtCpuCountWithCpuData', function() {
+ const m = newModel([], function(m) {
+ const c1 = m.kernel.getOrCreateCpu(1);
+ const c2 = m.kernel.getOrCreateCpu(2);
+ });
+ assert.strictEqual(m.kernel.bestGuessAtCpuCount, 2);
+ });
+
+ test('bestGuessAtCpuCountWithSoftwareCpuCount', function() {
+ const m = newModel([], function(m) {
+ m.kernel.softwareMeasuredCpuCount = 2;
+ });
+ assert.strictEqual(m.kernel.bestGuessAtCpuCount, 2);
+ });
+
+ test('kernelStableId', function() {
+ const model = newModel([]);
+ assert.strictEqual(model.kernel.stableId, 'Kernel');
+ });
+
+ test('kernelTimeShifting', function() {
+ const m = newModel([]);
+ const ctr1 = m.kernel.getOrCreateCounter('cat', 'ctr1');
+ const series1 = new tr.model.CounterSeries('a', 0);
+ series1.addCounterSample(100, 5);
+ series1.addCounterSample(200, 5);
+ ctr1.addSeries(series1);
+
+ const ctr2 = m.kernel.getOrCreateCpu(1).getOrCreateCounter('cat', 'ctr2');
+ const series2 = new tr.model.CounterSeries('b', 0);
+ series2.addCounterSample(300, 5);
+ series2.addCounterSample(400, 5);
+ ctr2.addSeries(series2);
+
+ m.kernel.shiftTimestampsForward(2);
+
+ assert.strictEqual(ctr1.series[0].samples[0].timestamp, 102);
+ assert.strictEqual(ctr1.series[0].samples[1].timestamp, 202);
+
+ assert.strictEqual(ctr2.series[0].samples[0].timestamp, 302);
+ assert.strictEqual(ctr2.series[0].samples[1].timestamp, 402);
+ });
+});
+</script>
+
diff --git a/chromium/third_party/catapult/tracing/tracing/model/location.html b/chromium/third_party/catapult/tracing/tracing/model/location.html
new file mode 100644
index 00000000000..b6403ae4c3f
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/location.html
@@ -0,0 +1,158 @@
+<!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/base.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.model', function() {
+ /**
+ * YComponent is a class that handles storing the stableId and the percentage
+ * offset in the y direction of all tracks within a specific viewX and viewY
+ * coordinate.
+ * @constructor
+ */
+ function YComponent(stableId, yPercentOffset) {
+ this.stableId = stableId;
+ this.yPercentOffset = yPercentOffset;
+ }
+
+ YComponent.prototype = {
+ toDict() {
+ return {
+ stableId: this.stableId,
+ yPercentOffset: this.yPercentOffset
+ };
+ }
+ };
+
+ /**
+ * Location is a class that represents a spatial location on the timeline
+ * that is specified by percent offsets within tracks rather than specific
+ * points.
+ *
+ * @constructor
+ */
+ function Location(xWorld, yComponents) {
+ this.xWorld_ = xWorld;
+ this.yComponents_ = yComponents;
+ }
+
+ /**
+ * Returns a new Location given by x and y coordinates with respect to
+ * the timeline's drawing canvas.
+ */
+ Location.fromViewCoordinates = function(viewport, viewX, viewY) {
+ const dt = viewport.currentDisplayTransform;
+ const xWorld = dt.xViewToWorld(viewX);
+ const yComponents = [];
+
+ // Since we're given coordinates within the timeline canvas, we need to
+ // convert them to document coordinates to get the element.
+ let elem = document.elementFromPoint(
+ viewX + viewport.modelTrackContainer.canvas.offsetLeft,
+ viewY + viewport.modelTrackContainer.canvas.offsetTop);
+ // Build yComponents by calculating percentage offset with respect to
+ // each parent track.
+ while (elem instanceof tr.ui.tracks.Track) {
+ if (elem.eventContainer) {
+ const boundRect = elem.getBoundingClientRect();
+ const yPercentOffset = (viewY - boundRect.top) / boundRect.height;
+ yComponents.push(
+ new YComponent(elem.eventContainer.stableId, yPercentOffset));
+ }
+ elem = elem.parentElement;
+ }
+
+ if (yComponents.length === 0) return;
+ return new Location(xWorld, yComponents);
+ };
+
+ Location.fromStableIdAndTimestamp = function(viewport, stableId, ts) {
+ const xWorld = ts;
+ const yComponents = [];
+
+ // The y components' percentage offsets will be calculated with respect to
+ // the boundingRect's top of containing track.
+ const containerToTrack = viewport.containerToTrackMap;
+ let elem = containerToTrack.getTrackByStableId(stableId);
+ if (!elem) return;
+
+ const firstY = elem.getBoundingClientRect().top;
+ while (elem instanceof tr.ui.tracks.Track) {
+ if (elem.eventContainer) {
+ const boundRect = elem.getBoundingClientRect();
+ const yPercentOffset = (firstY - boundRect.top) / boundRect.height;
+ yComponents.push(
+ new YComponent(elem.eventContainer.stableId, yPercentOffset));
+ }
+ elem = elem.parentElement;
+ }
+
+ if (yComponents.length === 0) return;
+ return new Location(xWorld, yComponents);
+ };
+
+ Location.prototype = {
+
+ get xWorld() {
+ return this.xWorld_;
+ },
+
+ /**
+ * Returns the first valid containing track based on the
+ * internal yComponents.
+ */
+ getContainingTrack(viewport) {
+ const containerToTrack = viewport.containerToTrackMap;
+ for (const i in this.yComponents_) {
+ const yComponent = this.yComponents_[i];
+ const track = containerToTrack.getTrackByStableId(yComponent.stableId);
+ if (track !== undefined) return track;
+ }
+ },
+
+ /**
+ * Calculates and returns x and y coordinates of the current location with
+ * respect to the timeline's canvas.
+ */
+ toViewCoordinates(viewport) {
+ const dt = viewport.currentDisplayTransform;
+ const containerToTrack = viewport.containerToTrackMap;
+ const viewX = dt.xWorldToView(this.xWorld_);
+
+ let viewY = -1;
+ for (const index in this.yComponents_) {
+ const yComponent = this.yComponents_[index];
+ const track = containerToTrack.getTrackByStableId(yComponent.stableId);
+ if (track !== undefined) {
+ const boundRect = track.getBoundingClientRect();
+ viewY = yComponent.yPercentOffset * boundRect.height + boundRect.top;
+ break;
+ }
+ }
+
+ return {
+ viewX,
+ viewY
+ };
+ },
+
+ toDict() {
+ return {
+ xWorld: this.xWorld_,
+ yComponents: this.yComponents_
+ };
+ }
+ };
+
+ return {
+ Location,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/memory_allocator_dump.html b/chromium/third_party/catapult/tracing/tracing/model/memory_allocator_dump.html
new file mode 100644
index 00000000000..0a723b8a9d9
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/memory_allocator_dump.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/scalar.html">
+<link rel="import" href="/tracing/base/unit.html">
+<link rel="import" href="/tracing/base/utils.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Provides the MemoryAllocatorDump class.
+ */
+tr.exportTo('tr.model', function() {
+ /**
+ * @constructor
+ */
+ function MemoryAllocatorDump(containerMemoryDump, fullName, opt_guid) {
+ this.fullName = fullName;
+ this.parent = undefined;
+ this.children = [];
+
+ // String -> Scalar.
+ this.numerics = {};
+
+ // String -> string.
+ this.diagnostics = {};
+
+ // The associated container memory dump.
+ this.containerMemoryDump = containerMemoryDump;
+
+ // Ownership relationship between memory allocator dumps.
+ this.owns = undefined;
+ this.ownedBy = [];
+
+ // Map from sibling dumps (other children of this dump's parent) to the
+ // proportion of this dump's size which they (or their descendants) own.
+ this.ownedBySiblingSizes = new Map();
+
+ // Retention relationship between memory allocator dumps.
+ this.retains = [];
+ this.retainedBy = [];
+
+ // Weak memory allocator dumps are removed from the model after import in
+ // tr.model.GlobalMemoryDump.removeWeakDumps(). See
+ // base::trace_event::MemoryAllocatorDump::Flags::WEAK in the Chromium
+ // codebase.
+ this.weak = false;
+
+ // A list of information about the memory allocator dump (e.g. about how
+ // its fields were calculated). Each item should be an object with
+ // a mandatory 'type' property and type-specific extra arguments (see
+ // MemoryAllocatorDumpInfoType).
+ this.infos = [];
+
+ // For debugging purposes.
+ this.guid = opt_guid;
+ }
+
+ /**
+ * Size numeric names. Please refer to the Memory Dump Graph Metric
+ * Calculation design document for more details (https://goo.gl/fKg0dt).
+ */
+ MemoryAllocatorDump.SIZE_NUMERIC_NAME = 'size';
+ MemoryAllocatorDump.EFFECTIVE_SIZE_NUMERIC_NAME = 'effective_size';
+ MemoryAllocatorDump.RESIDENT_SIZE_NUMERIC_NAME = 'resident_size';
+ MemoryAllocatorDump.DISPLAYED_SIZE_NUMERIC_NAME =
+ MemoryAllocatorDump.EFFECTIVE_SIZE_NUMERIC_NAME;
+
+ MemoryAllocatorDump.prototype = {
+ get name() {
+ return this.fullName.substring(this.fullName.lastIndexOf('/') + 1);
+ },
+
+ get quantifiedName() {
+ return '\'' + this.fullName + '\' in ' +
+ this.containerMemoryDump.containerName;
+ },
+
+ getDescendantDumpByFullName(fullName) {
+ return this.containerMemoryDump.getMemoryAllocatorDumpByFullName(
+ this.fullName + '/' + fullName);
+ },
+
+ isDescendantOf(otherDump) {
+ if (this === otherDump) return true;
+ if (this.parent === undefined) return false;
+ return this.parent.isDescendantOf(otherDump);
+ },
+
+ addNumeric(name, numeric) {
+ if (!(numeric instanceof tr.b.Scalar)) {
+ throw new Error('Numeric value must be an instance of Scalar.');
+ }
+ if (name in this.numerics) {
+ throw new Error('Duplicate numeric name: ' + name + '.');
+ }
+ this.numerics[name] = numeric;
+ },
+
+ addDiagnostic(name, text) {
+ if (typeof text !== 'string') {
+ throw new Error('Diagnostic text must be a string.');
+ }
+ if (name in this.diagnostics) {
+ throw new Error('Duplicate diagnostic name: ' + name + '.');
+ }
+ this.diagnostics[name] = text;
+ },
+
+ aggregateNumericsRecursively(opt_model) {
+ const numericNames = new Set();
+
+ // Aggregate descendants's numerics recursively and gather children's
+ // numeric names.
+ this.children.forEach(function(child) {
+ child.aggregateNumericsRecursively(opt_model);
+ for (const [item, value] of Object.entries(child.numerics)) {
+ numericNames.add(item, value);
+ }
+ }, this);
+
+ // Aggregate children's numerics.
+ numericNames.forEach(function(numericName) {
+ if (numericName === MemoryAllocatorDump.SIZE_NUMERIC_NAME ||
+ numericName === MemoryAllocatorDump.EFFECTIVE_SIZE_NUMERIC_NAME ||
+ this.numerics[numericName] !== undefined) {
+ // Don't aggregate size and effective size numerics. These are
+ // calculated in GlobalMemoryDump.prototype.calculateSizes() and
+ // GlobalMemoryDump.prototype.calculateEffectiveSizes respectively.
+ // Also don't aggregate numerics that the parent already has.
+ return;
+ }
+
+ this.numerics[numericName] = MemoryAllocatorDump.aggregateNumerics(
+ this.children.map(function(child) {
+ return child.numerics[numericName];
+ }), opt_model);
+ }, this);
+ }
+ };
+
+ // TODO(petrcermak): Consider moving this to tr.v.Histogram.
+ MemoryAllocatorDump.aggregateNumerics = function(numerics, opt_model) {
+ let shouldLogWarning = !!opt_model;
+ let aggregatedUnit = undefined;
+ let aggregatedValue = 0;
+
+ // Aggregate the units and sum up the values of the numerics.
+ numerics.forEach(function(numeric) {
+ if (numeric === undefined) return;
+
+ const unit = numeric.unit;
+ if (aggregatedUnit === undefined) {
+ aggregatedUnit = unit;
+ } else if (aggregatedUnit !== unit) {
+ if (shouldLogWarning) {
+ opt_model.importWarning({
+ type: 'numeric_parse_error',
+ message: 'Multiple units provided for numeric: \'' +
+ aggregatedUnit.unitName + '\' and \'' + unit.unitName + '\'.'
+ });
+ shouldLogWarning = false; // Don't log multiple warnings.
+ }
+ // Use the most generic unit when the numerics don't agree (best
+ // effort).
+ aggregatedUnit = tr.b.Unit.byName.unitlessNumber_smallerIsBetter;
+ }
+
+ aggregatedValue += numeric.value;
+ }, this);
+
+ if (aggregatedUnit === undefined) return undefined;
+
+ return new tr.b.Scalar(aggregatedUnit, aggregatedValue);
+ };
+
+ /**
+ * @constructor
+ */
+ function MemoryAllocatorDumpLink(source, target, opt_importance) {
+ this.source = source;
+ this.target = target;
+ this.importance = opt_importance;
+ this.size = undefined;
+ }
+
+ /**
+ * Types of size numeric information.
+ *
+ * @enum
+ */
+ const MemoryAllocatorDumpInfoType = {
+ // The provided size of a MemoryAllocatorDump was less than the aggregated
+ // size of its children.
+ //
+ // Mandatory extra args:
+ // * providedSize: The inconsistent provided size.
+ // * dependencySize: The aggregated size of the children.
+ PROVIDED_SIZE_LESS_THAN_AGGREGATED_CHILDREN: 0,
+
+ // The provided size of a MemoryAllocatorDump was less than the size of its
+ // largest owner.
+ //
+ // Mandatory extra args:
+ // * providedSize: The inconsistent provided size.
+ // * dependencySize: The size of the largest owner.
+ PROVIDED_SIZE_LESS_THAN_LARGEST_OWNER: 1
+ };
+
+ return {
+ MemoryAllocatorDump,
+ MemoryAllocatorDumpLink,
+ MemoryAllocatorDumpInfoType,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/memory_allocator_dump_test.html b/chromium/third_party/catapult/tracing/tracing/model/memory_allocator_dump_test.html
new file mode 100644
index 00000000000..7cc100dce25
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/memory_allocator_dump_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/scalar.html">
+<link rel="import" href="/tracing/base/unit.html">
+<link rel="import" href="/tracing/model/container_memory_dump.html">
+<link rel="import" href="/tracing/model/memory_allocator_dump.html">
+<link rel="import" href="/tracing/model/memory_dump_test_utils.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const ContainerMemoryDump = tr.model.ContainerMemoryDump;
+ const MemoryAllocatorDump = tr.model.MemoryAllocatorDump;
+ const MemoryAllocatorDumpLink = tr.model.MemoryAllocatorDumpLink;
+ const Scalar = tr.b.Scalar;
+ const unitlessNumber_smallerIsBetter =
+ tr.b.Unit.byName.unitlessNumber_smallerIsBetter;
+ const sizeInBytes = tr.b.Unit.byName.sizeInBytes;
+ const powerInWatts = tr.b.Unit.byName.powerInWatts;
+ const newAllocatorDump = tr.model.MemoryDumpTestUtils.newAllocatorDump;
+ const addChildDump = tr.model.MemoryDumpTestUtils.addChildDump;
+ const checkDumpNumericsAndDiagnostics =
+ tr.model.MemoryDumpTestUtils.checkDumpNumericsAndDiagnostics;
+
+ test('memoryAllocatorDump_instantiate', function() {
+ const containerDump = new ContainerMemoryDump(42);
+ containerDump.containerName = 'super dump';
+ const dump = new MemoryAllocatorDump(containerDump, 'v8/objects/object7');
+
+ assert.strictEqual(dump.name, 'object7');
+ assert.strictEqual(dump.fullName, 'v8/objects/object7');
+ assert.strictEqual(dump.containerMemoryDump, containerDump);
+ assert.strictEqual(
+ dump.quantifiedName, '\'v8/objects/object7\' in super dump');
+ });
+
+ test('memoryAllocatorDumps_aggregateNumericsRecursively', function() {
+ const md = new ContainerMemoryDump(42);
+
+ const oilpanDump = newAllocatorDump(md, 'oilpan', {numerics: {
+ objects_count: new Scalar(unitlessNumber_smallerIsBetter, 7)
+ }});
+
+ addChildDump(oilpanDump, 'bucket1', {numerics: {
+ size: 512,
+ objects_count: new Scalar(unitlessNumber_smallerIsBetter, 3),
+ inner_size: 256,
+ outer_size: 1024
+ }});
+
+ const oilpanBucket2Dump = addChildDump(oilpanDump, 'bucket2');
+
+ const oilpanBucket2StringsDump = addChildDump(
+ oilpanBucket2Dump, 'strings', {numerics: {
+ size: 512,
+ objects_count: new Scalar(unitlessNumber_smallerIsBetter, 4),
+ inner_size: 512,
+ outer_size: 2048
+ }});
+
+ oilpanDump.aggregateNumericsRecursively();
+
+ // oilpan has *some* numerics aggregated.
+ checkDumpNumericsAndDiagnostics(oilpanDump, {
+ objects_count: new Scalar(unitlessNumber_smallerIsBetter, 7),
+ inner_size: 768,
+ outer_size: 3072
+ }, {});
+
+ // oilpan/bucket2 has *all* numerics aggregated (except for size).
+ checkDumpNumericsAndDiagnostics(oilpanBucket2Dump, {
+ objects_count: new Scalar(unitlessNumber_smallerIsBetter, 4),
+ inner_size: 512,
+ outer_size: 2048
+ }, {});
+
+ // oilpan/bucket2/strings has *no* numerics aggregated.
+ checkDumpNumericsAndDiagnostics(oilpanBucket2StringsDump, {
+ size: 512,
+ objects_count: new Scalar(unitlessNumber_smallerIsBetter, 4),
+ inner_size: 512,
+ outer_size: 2048
+ }, {});
+ });
+
+ test('memoryAllocatorDump_aggregateNumerics', function() {
+ function checkAggregateNumerics(numerics, expectedValue, expectedUnit,
+ opt_expectedWarningCount) {
+ function checkResult(result) {
+ if (expectedValue === undefined) {
+ assert.isUndefined(result);
+ assert.isUndefined(expectedUnit); // Test sanity check.
+ } else {
+ assert.instanceOf(result, Scalar);
+ assert.strictEqual(result.value, expectedValue);
+ assert.strictEqual(result.unit, expectedUnit);
+ }
+ }
+
+ // Without model parameter.
+ const result1 = MemoryAllocatorDump.aggregateNumerics(numerics);
+ checkResult(result1);
+
+ // With model parameter.
+ const mockModel = {
+ warnings: [],
+ importWarning(warning) {
+ this.warnings.push(warning);
+ }
+ };
+ const result2 = MemoryAllocatorDump.aggregateNumerics(
+ numerics, mockModel);
+ checkResult(result2);
+ assert.lengthOf(mockModel.warnings, opt_expectedWarningCount || 0);
+ }
+
+ // No defined numerics.
+ checkAggregateNumerics([], undefined);
+ checkAggregateNumerics([undefined], undefined);
+ checkAggregateNumerics([undefined, undefined], undefined);
+
+ // Consistent units.
+ checkAggregateNumerics(
+ [new Scalar(unitlessNumber_smallerIsBetter, 10)],
+ 10, unitlessNumber_smallerIsBetter);
+ checkAggregateNumerics(
+ [new Scalar(sizeInBytes, 10),
+ new Scalar(sizeInBytes, 20),
+ new Scalar(sizeInBytes, 40)],
+ 70, sizeInBytes);
+ checkAggregateNumerics(
+ [undefined,
+ new Scalar(sizeInBytes, 16),
+ undefined,
+ new Scalar(sizeInBytes, 32),
+ undefined],
+ 48, sizeInBytes);
+
+ // Inconsistent units.
+ checkAggregateNumerics(
+ [new Scalar(sizeInBytes, 10),
+ new Scalar(powerInWatts, 20)],
+ 30, unitlessNumber_smallerIsBetter, 1 /* opt_expectedWarningCount */);
+ checkAggregateNumerics(
+ [undefined,
+ new Scalar(powerInWatts, 16),
+ undefined,
+ new Scalar(unitlessNumber_smallerIsBetter, 32),
+ undefined,
+ new Scalar(sizeInBytes, 64),
+ undefined],
+ 112, unitlessNumber_smallerIsBetter, 1 /* opt_expectedWarningCount */);
+ });
+
+ test('memoryAllocatorDumps_isDescendantOf', function() {
+ const md = new ContainerMemoryDump(42);
+
+ const v8Dump = new MemoryAllocatorDump(md, 'v8');
+ const v8HeapsDump = addChildDump(v8Dump, 'heaps');
+ const v8ObjectsDump = addChildDump(v8Dump, 'objects');
+ const v8Object1Dump = addChildDump(v8ObjectsDump, 'obj1');
+ const v8Object2Dump = addChildDump(v8ObjectsDump, 'obj2');
+
+ const oilpanDump = new MemoryAllocatorDump(md, 'oilpan');
+
+ assert.isTrue(v8Dump.isDescendantOf(v8Dump));
+ assert.isTrue(v8HeapsDump.isDescendantOf(v8Dump));
+ assert.isTrue(v8ObjectsDump.isDescendantOf(v8Dump));
+ assert.isTrue(v8Object1Dump.isDescendantOf(v8Dump));
+ assert.isTrue(v8Object2Dump.isDescendantOf(v8Dump));
+ assert.isTrue(v8ObjectsDump.isDescendantOf(v8ObjectsDump));
+ assert.isTrue(v8Object1Dump.isDescendantOf(v8ObjectsDump));
+ assert.isTrue(v8Object2Dump.isDescendantOf(v8ObjectsDump));
+ assert.isTrue(oilpanDump.isDescendantOf(oilpanDump));
+
+ assert.isFalse(v8Dump.isDescendantOf(oilpanDump));
+ assert.isFalse(v8Dump.isDescendantOf(v8HeapsDump));
+ assert.isFalse(v8Dump.isDescendantOf(v8ObjectsDump));
+ assert.isFalse(v8Dump.isDescendantOf(v8Object1Dump));
+ assert.isFalse(v8Dump.isDescendantOf(v8Object2Dump));
+ assert.isFalse(v8Object1Dump.isDescendantOf(v8Object2Dump));
+ assert.isFalse(v8Object2Dump.isDescendantOf(v8Object1Dump));
+ });
+
+ test('memoryAllocatorDumps_getDescendantDumpByFullName', function() {
+ const containerDump = new ContainerMemoryDump(42);
+
+ const gpuDump = new MemoryAllocatorDump(containerDump, 'gpu');
+ containerDump.memoryAllocatorDumps = [gpuDump];
+
+ const memtrackDump = addChildDump(gpuDump, 'android_memtrack');
+ const glDump = addChildDump(memtrackDump, 'gl');
+ const gfxDump = addChildDump(memtrackDump, 'gfx');
+ const tileDump = addChildDump(gfxDump, 'tile');
+
+ assert.strictEqual(gpuDump.getDescendantDumpByFullName(
+ 'android_memtrack'), memtrackDump);
+ assert.strictEqual(gpuDump.getDescendantDumpByFullName(
+ 'android_memtrack/gfx/tile'), tileDump);
+ assert.strictEqual(memtrackDump.getDescendantDumpByFullName('gl'), glDump);
+ assert.strictEqual(memtrackDump.getDescendantDumpByFullName(
+ 'gfx/tile'), tileDump);
+ });
+
+ test('memoryAllocatorDumpLink_instantiate', function() {
+ const d1 = new MemoryAllocatorDump('v8/isolate1');
+ const d2 = new MemoryAllocatorDump('oilpan/document1');
+ const link = new MemoryAllocatorDumpLink(d1, d2, 3);
+
+ assert.strictEqual(link.source, d1);
+ assert.strictEqual(link.target, d2);
+ assert.strictEqual(link.importance, 3);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/memory_dump_test_utils.html b/chromium/third_party/catapult/tracing/tracing/model/memory_dump_test_utils.html
new file mode 100644
index 00000000000..de3413f7f44
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/memory_dump_test_utils.html
@@ -0,0 +1,220 @@
+<!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/scalar.html">
+<link rel="import" href="/tracing/base/unit.html">
+<link rel="import" href="/tracing/model/container_memory_dump.html">
+<link rel="import" href="/tracing/model/global_memory_dump.html">
+<link rel="import" href="/tracing/model/memory_allocator_dump.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 tests involving memory dumps.
+ */
+tr.exportTo('tr.model', function() {
+ const GlobalMemoryDump = tr.model.GlobalMemoryDump;
+ const ProcessMemoryDump = tr.model.ProcessMemoryDump;
+ const MemoryAllocatorDump = tr.model.MemoryAllocatorDump;
+ const MemoryAllocatorDumpLink = tr.model.MemoryAllocatorDumpLink;
+ 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 LIGHT = tr.model.ContainerMemoryDump.LevelOfDetail.LIGHT;
+
+ function castToScalar(value) {
+ if (typeof value === 'number') {
+ return new Scalar(sizeInBytes_smallerIsBetter, value);
+ }
+ assert.instanceOf(value, Scalar);
+ return value;
+ }
+
+ function getOption(opt_options, key, opt_defaultValue) {
+ if (opt_options && (key in opt_options)) {
+ return opt_options[key];
+ }
+ return opt_defaultValue;
+ }
+
+ function MemoryDumpTestUtils() {
+ throw new Error('Static class');
+ }
+
+ MemoryDumpTestUtils.SIZE_DELTA = 0.0001;
+
+ /**
+ * Create a new global memory dump and add it to a model.
+ *
+ * @param {!tr.Model} model The trace model to which the new global dump
+ * should be added.
+ * @param {!{
+ * ts: (number|undefined),
+ * duration: (number|undefined),
+ * levelOfDetail: (!tr.model.ContainerMemoryDump.LevelOfDetail|undefined)
+ * }=} opt_options Options for creating the new global dump.
+ * @return {!tr.model.GlobalMemoryDump} The newly created global memory dump.
+ */
+ MemoryDumpTestUtils.addGlobalMemoryDump = function(model, opt_options) {
+ const timestamp = getOption(opt_options, 'ts', 0);
+ const gmd = new GlobalMemoryDump(model, timestamp);
+ gmd.levelOfDetail = getOption(opt_options, 'levelOfDetail', LIGHT);
+ gmd.duration = getOption(opt_options, 'duration', 0);
+ model.globalMemoryDumps.push(gmd);
+ return gmd;
+ };
+
+ /**
+ * Create a new process memory dump and add it to a global memory dump.
+ *
+ * @param {!tr.model.GlobalMemoryDump} gmd The global dump to which the new
+ * process dump should be added.
+ * @param {!tr.model.Process} pmd The process associated with the process
+ * dump.
+ * @param {!{
+ * ts: (number|undefined)
+ * }=} opt_options Options for creating the new process dump.
+ * @return {!tr.model.ProcessMemoryDump} The newly created process memory
+ * dump.
+ */
+ MemoryDumpTestUtils.addProcessMemoryDump =
+ function(gmd, process, opt_options) {
+ const timestamp = getOption(opt_options, 'ts', gmd.start);
+ const pmd = new ProcessMemoryDump(gmd, process, timestamp);
+ process.memoryDumps.push(pmd);
+ if (process.pid in gmd.processMemoryDumps) {
+ // Test sanity check.
+ throw new Error('Process memory dump for process with pid=' +
+ process.pid + ' has already been provided');
+ }
+ gmd.processMemoryDumps[process.pid] = pmd;
+ return pmd;
+ };
+
+ /**
+ * Create a new memory allocator dump.
+ *
+ * @param {!tr.model.ContainerMemoryDump} containerDump The container dump
+ * associated with the new allocator dump.
+ * @param {string} fullName The full name of the new allocator dump
+ * (including ancestors).
+ * @param {!{
+ * guid: (number|undefined),
+ * numerics: (!Object<string, (number|!tr.b.Scalar)>|undefined)
+ * }=} opt_options Options for creating the new allocator dump.
+ * @return {!tr.model.MemoryAllocatorDump} The newly created memory allocator
+ * dump.
+ */
+ MemoryDumpTestUtils.newAllocatorDump = function(
+ containerDump, fullName, opt_options) {
+ const dump = new MemoryAllocatorDump(containerDump, fullName,
+ getOption(opt_options, 'guid'));
+ const numerics = getOption(opt_options, 'numerics');
+ if (numerics) {
+ for (const [numericName, value] of Object.entries(numerics)) {
+ dump.addNumeric(numericName, castToScalar(value));
+ }
+ }
+ const children = getOption(opt_options, 'children');
+ if (children) dump.children = children;
+ return dump;
+ };
+
+ /**
+ * Create a new child memory allocator dump and add it to a parent memory
+ * allocator dump.
+ *
+ * @param {!tr.model.MemoryAllocatorDump} parentDump The parent allocator
+ * dump.
+ * @param {string} name The name of the child allocator dump (excluding
+ * ancestors).
+ * @param {!{
+ * guid: (number|undefined),
+ * numerics: (!Object<string, (number|!tr.b.Scalar)>|undefined)
+ * }=} opt_options Options for creating the child allocator dump.
+ * @return {!tr.model.MemoryAllocatorDump} The newly created child memory
+ * allocator dump.
+ */
+ MemoryDumpTestUtils.addChildDump = function(parentDump, name, opt_options) {
+ const childDump = MemoryDumpTestUtils.newAllocatorDump(
+ parentDump.containerMemoryDump, parentDump.fullName + '/' + name,
+ opt_options);
+ childDump.parent = parentDump;
+ parentDump.children.push(childDump);
+ return childDump;
+ };
+
+ MemoryDumpTestUtils.addOwnershipLink = function(
+ ownerDump, ownedDump, opt_importance) {
+ assert.isUndefined(ownerDump.owns); // Sanity check.
+ const ownershipLink =
+ new MemoryAllocatorDumpLink(ownerDump, ownedDump, opt_importance);
+ ownerDump.owns = ownershipLink;
+ ownedDump.ownedBy.push(ownershipLink);
+ return ownershipLink;
+ };
+
+ MemoryDumpTestUtils.checkDumpNumericsAndDiagnostics =
+ function(dump, expectedNumerics, expectedDiagnostics) {
+ const actualNumerics = dump.numerics;
+ assert.sameMembers(
+ Object.keys(actualNumerics), Object.keys(expectedNumerics));
+ for (const numericName in actualNumerics) {
+ const actualNumeric = actualNumerics[numericName];
+ const expectedNumeric = castToScalar(expectedNumerics[numericName]);
+ assert.instanceOf(actualNumeric, tr.b.Scalar);
+ assert.strictEqual(actualNumeric.unit, expectedNumeric.unit);
+ assert.closeTo(actualNumeric.value, expectedNumeric.value,
+ MemoryDumpTestUtils.SIZE_DELTA);
+ }
+
+ assert.deepEqual(dump.diagnostics, expectedDiagnostics);
+ };
+
+ MemoryDumpTestUtils.checkVMRegions = function(vmRegions, expectedRegions) {
+ if (vmRegions instanceof VMRegionClassificationNode) {
+ vmRegions = vmRegions.allRegionsForTesting;
+ }
+
+ const expectedRegionsMap = new Map();
+ expectedRegions.forEach(function(region) {
+ if (!(region instanceof VMRegion)) {
+ region = VMRegion.fromDict(region);
+ }
+ expectedRegionsMap.set(region.uniqueIdWithinProcess, region);
+ });
+ const actualRegionsMap = new Map();
+ vmRegions.forEach(function(region) {
+ actualRegionsMap.set(region.uniqueIdWithinProcess, region);
+ });
+
+ assert.strictEqual(actualRegionsMap.size, expectedRegionsMap.size);
+ for (const id of expectedRegionsMap.keys()) {
+ const expectedRegion = expectedRegionsMap.get(id);
+ const actualRegion = actualRegionsMap.get(id);
+
+ assert.instanceOf(actualRegion, VMRegion);
+ assert.strictEqual(actualRegion.startAddress,
+ expectedRegion.startAddress);
+ assert.strictEqual(actualRegion.sizeInBytes, expectedRegion.sizeInBytes);
+ assert.strictEqual(actualRegion.protectionFlags,
+ expectedRegion.protectionFlags);
+ assert.strictEqual(actualRegion.mappedFile, expectedRegion.mappedFile);
+ assert.deepEqual(actualRegion.byteStats, expectedRegion.byteStats);
+ }
+ };
+
+ return {
+ MemoryDumpTestUtils,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/model.html b/chromium/third_party/catapult/tracing/tracing/model/model.html
new file mode 100644
index 00000000000..022b7cf7fbe
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/model.html
@@ -0,0 +1,670 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2012 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/event.html">
+<link rel="import" href="/tracing/base/interval_tree.html">
+<link rel="import" href="/tracing/base/math/quad.html">
+<link rel="import" href="/tracing/base/math/range.html">
+<link rel="import" href="/tracing/base/task.html">
+<link rel="import" href="/tracing/base/time_display_modes.html">
+<link rel="import" href="/tracing/base/unit.html">
+<link rel="import" href="/tracing/core/auditor.html">
+<link rel="import" href="/tracing/core/filter.html">
+<link rel="import" href="/tracing/model/alert.html">
+<link rel="import" href="/tracing/model/clock_sync_manager.html">
+<link rel="import" href="/tracing/model/constants.html">
+<link rel="import" href="/tracing/model/device.html">
+<link rel="import" href="/tracing/model/flow_event.html">
+<link rel="import" href="/tracing/model/frame.html">
+<link rel="import" href="/tracing/model/global_memory_dump.html">
+<link rel="import" href="/tracing/model/instant_event.html">
+<link rel="import" href="/tracing/model/kernel.html">
+<link rel="import" href="/tracing/model/model_indices.html">
+<link rel="import" href="/tracing/model/model_stats.html">
+<link rel="import" href="/tracing/model/object_snapshot.html">
+<link rel="import" href="/tracing/model/process.html">
+<link rel="import" href="/tracing/model/process_memory_dump.html">
+<link rel="import" href="/tracing/model/sample.html">
+<link rel="import" href="/tracing/model/stack_frame.html">
+<link rel="import" href="/tracing/model/user_model/user_expectation.html">
+<link rel="import" href="/tracing/model/user_model/user_model.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Model is a parsed representation of the
+ * TraceEvents obtained from base/trace_event in which the begin-end
+ * tokens are converted into a hierarchy of processes, threads,
+ * subrows, and slices.
+ *
+ * The building block of the model is a slice. A slice is roughly
+ * equivalent to function call executing on a specific thread. As a
+ * result, slices may have one or more subslices.
+ *
+ * A thread contains one or more subrows of slices. Row 0 corresponds to
+ * the "root" slices, e.g. the topmost slices. Row 1 contains slices that
+ * are nested 1 deep in the stack, and so on. We use these subrows to draw
+ * nesting tasks.
+ *
+ */
+tr.exportTo('tr', function() {
+ const Process = tr.model.Process;
+ const Device = tr.model.Device;
+ const Kernel = tr.model.Kernel;
+ const GlobalMemoryDump = tr.model.GlobalMemoryDump;
+ const GlobalInstantEvent = tr.model.GlobalInstantEvent;
+ const FlowEvent = tr.model.FlowEvent;
+ const Alert = tr.model.Alert;
+ const Sample = tr.model.Sample;
+
+ /**
+ * @constructor
+ */
+ function Model() {
+ tr.model.EventContainer.call(this);
+ tr.b.EventTarget.decorate(this);
+
+ this.timestampShiftToZeroAmount_ = 0;
+
+ this.faviconHue = 'blue'; // Should be a key from favicons.html
+
+ this.device = new Device(this);
+ this.kernel = new Kernel(this);
+ this.processes = {};
+ this.metadata = [];
+ this.categories = [];
+ this.instantEvents = [];
+ this.flowEvents = [];
+ this.clockSyncManager = new tr.model.ClockSyncManager();
+ this.intrinsicTimeUnit_ = undefined;
+
+ this.stackFrames = {};
+ this.samples = [];
+
+ this.alerts = [];
+ this.userModel = new tr.model.um.UserModel(this);
+
+ this.flowIntervalTree = new tr.b.IntervalTree((f) => f.start, (f) => f.end);
+ this.globalMemoryDumps = [];
+
+ this.userFriendlyCategoryDrivers_ = [];
+
+ this.annotationsByGuid_ = {};
+ this.modelIndices = undefined;
+
+ this.stats = new tr.model.ModelStats();
+
+ this.importWarnings_ = [];
+ this.reportedImportWarnings_ = {};
+
+ this.isTimeHighResolution_ = true;
+
+ this.patchupsToApply_ = [];
+
+ this.doesHelperGUIDSupportThisModel_ = {};
+ this.helpersByConstructorGUID_ = {};
+ this.eventsByStableId_ = undefined;
+ }
+
+ Model.prototype = {
+ __proto__: tr.model.EventContainer.prototype,
+
+ getEventByStableId(stableId) {
+ if (this.eventsByStableId_ === undefined) {
+ this.eventsByStableId_ = {};
+ for (const event of this.getDescendantEvents()) {
+ this.eventsByStableId_[event.stableId] = event;
+ }
+ }
+ return this.eventsByStableId_[stableId];
+ },
+
+ getOrCreateHelper(constructor) {
+ if (!constructor.guid) {
+ throw new Error('Helper constructors must have GUIDs');
+ }
+
+ if (this.helpersByConstructorGUID_[constructor.guid] === undefined) {
+ if (this.doesHelperGUIDSupportThisModel_[constructor.guid] ===
+ undefined) {
+ this.doesHelperGUIDSupportThisModel_[constructor.guid] =
+ constructor.supportsModel(this);
+ }
+
+ if (!this.doesHelperGUIDSupportThisModel_[constructor.guid]) {
+ return undefined;
+ }
+
+ this.helpersByConstructorGUID_[constructor.guid] = new constructor(
+ this);
+ }
+ return this.helpersByConstructorGUID_[constructor.guid];
+ },
+
+ * childEvents() {
+ yield* this.globalMemoryDumps;
+ yield* this.instantEvents;
+ yield* this.flowEvents;
+ yield* this.alerts;
+ yield* this.samples;
+ },
+
+ * childEventContainers() {
+ yield this.userModel;
+ yield this.device;
+ yield this.kernel;
+ yield* Object.values(this.processes);
+ },
+
+ /**
+ * Some objects in the model can persist their state in ModelSettings.
+ *
+ * This iterates through them.
+ */
+ iterateAllPersistableObjects(callback) {
+ this.kernel.iterateAllPersistableObjects(callback);
+ for (const pid in this.processes) {
+ this.processes[pid].iterateAllPersistableObjects(callback);
+ }
+ },
+
+ updateBounds() {
+ this.bounds.reset();
+ const bounds = this.bounds;
+ for (const ec of this.childEventContainers()) {
+ ec.updateBounds();
+ bounds.addRange(ec.bounds);
+ }
+ for (const event of this.childEvents()) {
+ event.addBoundsToRange(bounds);
+ }
+ },
+
+ shiftWorldToZero() {
+ const shiftAmount = -this.bounds.min;
+ this.timestampShiftToZeroAmount_ = shiftAmount;
+ for (const ec of this.childEventContainers()) {
+ ec.shiftTimestampsForward(shiftAmount);
+ }
+
+ for (const event of this.childEvents()) {
+ event.start += shiftAmount;
+ }
+ this.updateBounds();
+ },
+
+ convertTimestampToModelTime(sourceClockDomainName, ts) {
+ if (sourceClockDomainName !== 'traceEventClock') {
+ throw new Error('Only traceEventClock is supported.');
+ }
+ return tr.b.Unit.timestampFromUs(ts) +
+ this.timestampShiftToZeroAmount_;
+ },
+
+ get numProcesses() {
+ let n = 0;
+ for (const p in this.processes) {
+ n++;
+ }
+ return n;
+ },
+
+ /**
+ * @return {Process} Gets a TimelineProcess for a specified pid. Returns
+ * undefined if the process doesn't exist.
+ */
+ getProcess(pid) {
+ return this.processes[pid];
+ },
+
+ /**
+ * @return {Process} Gets a TimelineProcess for a specified pid or
+ * creates one if it does not exist.
+ */
+ getOrCreateProcess(pid) {
+ if (!this.processes[pid]) {
+ this.processes[pid] = new Process(this, pid);
+ }
+ return this.processes[pid];
+ },
+
+ addStackFrame(stackFrame) {
+ if (this.stackFrames[stackFrame.id]) {
+ throw new Error('Stack frame already exists');
+ }
+ this.stackFrames[stackFrame.id] = stackFrame;
+ return stackFrame;
+ },
+
+ /**
+ * Generates the set of categories from the slices and counters.
+ */
+ updateCategories_() {
+ const categoriesDict = {};
+ this.userModel.addCategoriesToDict(categoriesDict);
+ this.device.addCategoriesToDict(categoriesDict);
+ this.kernel.addCategoriesToDict(categoriesDict);
+ for (const pid in this.processes) {
+ this.processes[pid].addCategoriesToDict(categoriesDict);
+ }
+
+ this.categories = [];
+ for (const category in categoriesDict) {
+ if (category !== '') {
+ this.categories.push(category);
+ }
+ }
+ },
+
+ getAllThreads() {
+ const threads = [];
+ for (const tid in this.kernel.threads) {
+ threads.push(process.threads[tid]);
+ }
+ for (const pid in this.processes) {
+ const process = this.processes[pid];
+ for (const tid in process.threads) {
+ threads.push(process.threads[tid]);
+ }
+ }
+ return threads;
+ },
+
+ /**
+ * @param {(!function(!tr.model.Process): boolean)=} opt_predicate Optional
+ * predicate for filtering the returned processes. If undefined, all
+ * process in the model will be returned.
+ * @return {!Array<!tr.model.Process>} An array of processes in the model.
+ */
+ getAllProcesses(opt_predicate) {
+ const processes = [];
+ for (const pid in this.processes) {
+ const process = this.processes[pid];
+ if (opt_predicate === undefined || opt_predicate(process)) {
+ processes.push(process);
+ }
+ }
+ return processes;
+ },
+
+ /**
+ * @return {Array} An array of all the counters in the model.
+ */
+ getAllCounters() {
+ const counters = [];
+ counters.push.apply(
+ counters, Object.values(this.device.counters || {}));
+ counters.push.apply(
+ counters, Object.values(this.kernel.counters || {}));
+ for (const pid in this.processes) {
+ const process = this.processes[pid];
+ for (const tid in process.counters) {
+ counters.push(process.counters[tid]);
+ }
+ }
+ return counters;
+ },
+
+ getAnnotationByGUID(guid) {
+ return this.annotationsByGuid_[guid];
+ },
+
+ addAnnotation(annotation) {
+ if (!annotation.guid) {
+ throw new Error('Annotation with undefined guid given');
+ }
+
+ this.annotationsByGuid_[annotation.guid] = annotation;
+ tr.b.dispatchSimpleEvent(this, 'annotationChange');
+ },
+
+ removeAnnotation(annotation) {
+ this.annotationsByGuid_[annotation.guid].onRemove();
+ delete this.annotationsByGuid_[annotation.guid];
+ tr.b.dispatchSimpleEvent(this, 'annotationChange');
+ },
+
+ getAllAnnotations() {
+ return Object.values(this.annotationsByGuid_);
+ },
+
+ addUserFriendlyCategoryDriver(ufcd) {
+ this.userFriendlyCategoryDrivers_.push(ufcd);
+ },
+
+ /**
+ * Gets the user friendly category string from an event.
+ *
+ * Returns undefined if none is known.
+ */
+ getUserFriendlyCategoryFromEvent(event) {
+ for (let i = 0; i < this.userFriendlyCategoryDrivers_.length; i++) {
+ const ufc = this.userFriendlyCategoryDrivers_[i].fromEvent(event);
+ if (ufc !== undefined) return ufc;
+ }
+ return undefined;
+ },
+
+ /**
+ * @param {String} The name of the thread to find.
+ * @return {Array} An array of all the matched threads.
+ */
+ findAllThreadsNamed(name) {
+ const namedThreads = [];
+ namedThreads.push.apply(
+ namedThreads,
+ this.kernel.findAllThreadsNamed(name));
+ for (const pid in this.processes) {
+ namedThreads.push.apply(
+ namedThreads,
+ this.processes[pid].findAllThreadsNamed(name));
+ }
+ return namedThreads;
+ },
+
+ get importOptions() {
+ return this.importOptions_;
+ },
+
+ set importOptions(options) {
+ this.importOptions_ = options;
+ },
+
+ /**
+ * Returns a time unit that is used to format values and determines the
+ * precision of the timestamp values.
+ */
+ get intrinsicTimeUnit() {
+ if (this.intrinsicTimeUnit_ === undefined) {
+ return tr.b.TimeDisplayModes.ms;
+ }
+ return this.intrinsicTimeUnit_;
+ },
+
+ set intrinsicTimeUnit(value) {
+ if (this.intrinsicTimeUnit_ === value) return;
+ if (this.intrinsicTimeUnit_ !== undefined) {
+ throw new Error('Intrinsic time unit already set');
+ }
+ this.intrinsicTimeUnit_ = value;
+ },
+
+ get isTimeHighResolution() {
+ return this.isTimeHighResolution_;
+ },
+
+ set isTimeHighResolution(value) {
+ this.isTimeHighResolution_ = value;
+ },
+
+ /**
+ * Returns a link to a trace data file that this model was imported from.
+ * This is NOT the URL of a site being traced, but instead an indicator of
+ * where the data is stored.
+ */
+ get canonicalUrl() {
+ return this.canonicalUrl_;
+ },
+
+ set canonicalUrl(value) {
+ if (this.canonicalUrl_ === value) return;
+ if (this.canonicalUrl_ !== undefined) {
+ throw new Error('canonicalUrl already set');
+ }
+ this.canonicalUrl_ = value;
+ },
+
+ /**
+ * Saves a warning that happened during import.
+ *
+ * Warnings are typically logged to the console, and optionally, the
+ * more critical ones are shown to the user.
+ *
+ * @param {Object} data The import warning data. Data must provide two
+ * accessors: type, message. The types are used to determine if we
+ * should output the message, we'll only output one message of each type.
+ * The message is the actual warning content.
+ */
+ importWarning(data) {
+ data.showToUser = !!data.showToUser;
+
+ this.importWarnings_.push(data);
+
+ // Only log each warning type once. We may want to add some kind of
+ // flag to allow reporting all importer warnings.
+ if (this.reportedImportWarnings_[data.type] === true) return;
+
+ this.reportedImportWarnings_[data.type] = true;
+ },
+
+ get hasImportWarnings() {
+ return (this.importWarnings_.length > 0);
+ },
+
+ get importWarnings() {
+ return this.importWarnings_;
+ },
+
+ get importWarningsThatShouldBeShownToUser() {
+ return this.importWarnings_.filter(function(warning) {
+ return warning.showToUser;
+ });
+ },
+
+ autoCloseOpenSlices() {
+ // Sort the samples.
+ this.samples.sort(function(x, y) {
+ return x.start - y.start;
+ });
+
+ this.updateBounds();
+ this.kernel.autoCloseOpenSlices();
+ for (const pid in this.processes) {
+ this.processes[pid].autoCloseOpenSlices();
+ }
+ },
+
+ createSubSlices() {
+ this.kernel.createSubSlices();
+ for (const pid in this.processes) {
+ this.processes[pid].createSubSlices();
+ }
+ },
+
+ preInitializeObjects() {
+ for (const pid in this.processes) {
+ this.processes[pid].preInitializeObjects();
+ }
+ },
+
+ initializeObjects() {
+ for (const pid in this.processes) {
+ this.processes[pid].initializeObjects();
+ }
+ },
+
+ pruneEmptyContainers() {
+ this.kernel.pruneEmptyContainers();
+ for (const pid in this.processes) {
+ this.processes[pid].pruneEmptyContainers();
+ }
+ },
+
+ mergeKernelWithUserland() {
+ for (const pid in this.processes) {
+ this.processes[pid].mergeKernelWithUserland();
+ }
+ },
+
+ computeWorldBounds(shiftWorldToZero) {
+ this.updateBounds();
+ this.updateCategories_();
+
+ if (shiftWorldToZero) {
+ this.shiftWorldToZero();
+ }
+ },
+
+ buildFlowEventIntervalTree() {
+ for (let i = 0; i < this.flowEvents.length; ++i) {
+ const flowEvent = this.flowEvents[i];
+ this.flowIntervalTree.insert(flowEvent);
+ }
+ this.flowIntervalTree.updateHighValues();
+ },
+
+ cleanupUndeletedObjects() {
+ for (const pid in this.processes) {
+ this.processes[pid].autoDeleteObjects(this.bounds.max);
+ }
+ },
+
+ sortMemoryDumps() {
+ this.globalMemoryDumps.sort(function(x, y) {
+ return x.start - y.start;
+ });
+
+ for (const pid in this.processes) {
+ this.processes[pid].sortMemoryDumps();
+ }
+ },
+
+ finalizeMemoryGraphs() {
+ this.globalMemoryDumps.forEach(function(dump) {
+ dump.finalizeGraph();
+ });
+ },
+
+ buildEventIndices() {
+ this.modelIndices = new tr.model.ModelIndices(this);
+ },
+
+ sortAlerts() {
+ this.alerts.sort(function(x, y) {
+ return x.start - y.start;
+ });
+ },
+
+ applyObjectRefPatchups() {
+ // Change all the fields pointing at id_refs to their real values.
+ const unresolved = [];
+ this.patchupsToApply_.forEach(function(patchup) {
+ if (patchup.pidRef in this.processes) {
+ const snapshot = this.processes[patchup.pidRef].objects.getSnapshotAt(
+ patchup.scopedId, patchup.ts);
+ if (snapshot) {
+ patchup.object[patchup.field] = snapshot;
+ snapshot.referencedAt(patchup.item, patchup.object, patchup.field);
+ return;
+ }
+ }
+ unresolved.push(patchup);
+ }, this);
+ this.patchupsToApply_ = unresolved;
+ },
+
+ replacePIDRefsInPatchups(oldPidRef, newPidRef) {
+ this.patchupsToApply_.forEach(function(patchup) {
+ if (patchup.pidRef === oldPidRef) {
+ patchup.pidRef = newPidRef;
+ }
+ });
+ },
+
+ /**
+ * Called by the model to join references between objects, after final model
+ * bounds have been computed.
+ */
+ joinRefs() {
+ this.joinObjectRefs_();
+ this.applyObjectRefPatchups();
+ },
+
+ joinObjectRefs_() {
+ for (const [pid, process] of Object.entries(this.processes)) {
+ this.joinObjectRefsForProcess_(pid, process);
+ }
+ },
+
+ joinObjectRefsForProcess_(pid, process) {
+ // Iterate the world, looking for id_refs
+ for (const thread of Object.values(process.threads)) {
+ thread.asyncSliceGroup.slices.forEach(function(item) {
+ this.searchItemForIDRefs_(pid, 'start', item);
+ }, this);
+ thread.sliceGroup.slices.forEach(function(item) {
+ this.searchItemForIDRefs_(pid, 'start', item);
+ }, this);
+ }
+ process.objects.iterObjectInstances(function(instance) {
+ instance.snapshots.forEach(function(item) {
+ this.searchItemForIDRefs_(pid, 'ts', item);
+ }, this);
+ }, this);
+ },
+
+ searchItemForIDRefs_(pid, itemTimestampField, item) {
+ if (!item.args && !item.contexts) return;
+ const patchupsToApply = this.patchupsToApply_;
+
+ function handleField(object, fieldName, fieldValue) {
+ if (!fieldValue || (!fieldValue.id_ref && !fieldValue.idRef)) {
+ return;
+ }
+
+ const scope = fieldValue.scope || tr.model.OBJECT_DEFAULT_SCOPE;
+ const idRef = fieldValue.id_ref || fieldValue.idRef;
+ const scopedId = new tr.model.ScopedId(scope, idRef);
+ const pidRef = fieldValue.pid_ref || fieldValue.pidRef || pid;
+ const ts = item[itemTimestampField];
+ // We have to delay the actual change to the new value until after all
+ // refs have been located. Otherwise, we could end up recursing in
+ // ways we definitely didn't intend.
+ patchupsToApply.push({
+ item,
+ object,
+ field: fieldName,
+ pidRef,
+ scopedId,
+ ts});
+ }
+ function iterObjectFieldsRecursively(object) {
+ if (!(object instanceof Object)) return;
+
+ if ((object instanceof tr.model.ObjectSnapshot) ||
+ (object instanceof Float32Array) ||
+ (object instanceof tr.b.math.Quad)) {
+ return;
+ }
+
+ if (object instanceof Array) {
+ for (let i = 0; i < object.length; i++) {
+ handleField(object, i, object[i]);
+ iterObjectFieldsRecursively(object[i]);
+ }
+ return;
+ }
+
+ for (const key in object) {
+ const value = object[key];
+ handleField(object, key, value);
+ iterObjectFieldsRecursively(value);
+ }
+ }
+
+ iterObjectFieldsRecursively(item.args);
+ iterObjectFieldsRecursively(item.contexts);
+ }
+ };
+
+ return {
+ Model,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/model_indices.html b/chromium/third_party/catapult/tracing/tracing/model/model_indices.html
new file mode 100644
index 00000000000..27f89dba0b0
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/model_indices.html
@@ -0,0 +1,56 @@
+<!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';
+
+/**
+ * @fileoverview Provides the Event Index class.
+ */
+tr.exportTo('tr.model', function() {
+ /**
+ * A Event Index maps an id to all the events that have that particular id
+ *
+ * @constructor
+ */
+ function ModelIndices(model) {
+ // For now the only indices we construct are for flowEvents
+ this.flowEventsById_ = {};
+ model.flowEvents.forEach(function(fe) {
+ if (fe.id !== undefined) {
+ if (!this.flowEventsById_.hasOwnProperty(fe.id)) {
+ this.flowEventsById_[fe.id] = [];
+ }
+ this.flowEventsById_[fe.id].push(fe);
+ }
+ }, this);
+ }
+
+ ModelIndices.prototype = {
+ addEventWithId(id, event) {
+ if (!this.flowEventsById_.hasOwnProperty(id)) {
+ this.flowEventsById_[id] = [];
+ }
+ this.flowEventsById_[id].push(event);
+ },
+
+ getFlowEventsWithId(id) {
+ if (!this.flowEventsById_.hasOwnProperty(id)) {
+ return [];
+ }
+ return this.flowEventsById_[id];
+ }
+ };
+
+ return {
+ ModelIndices,
+ };
+});
+</script>
+
diff --git a/chromium/third_party/catapult/tracing/tracing/model/model_indices_test.html b/chromium/third_party/catapult/tracing/tracing/model/model_indices_test.html
new file mode 100644
index 00000000000..bc5d99a0a29
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/model_indices_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/model.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const newFlowEventEx = tr.c.TestUtils.newFlowEventEx;
+ const newModel = tr.c.TestUtils.newModel;
+
+ test('getCorrectModelIndices', function() {
+ const m = newModel(function(m) {
+ m.f1 = newFlowEventEx({
+ title: 'test1',
+ start: 0,
+ end: 10,
+ id: '0x100'
+ });
+
+ m.f2 = newFlowEventEx({
+ title: 'test2',
+ start: 0,
+ end: 10,
+ id: '0x100'
+ });
+
+ m.flowEvents.push(m.f1);
+ m.flowEvents.push(m.f2);
+ });
+
+ assert.isDefined(m.modelIndices);
+ const modelIndices = m.modelIndices;
+ assert.strictEqual(modelIndices.getFlowEventsWithId('0x100').length, 2);
+ assert.strictEqual(
+ modelIndices.getFlowEventsWithId('0x100')[0].id, '0x100');
+ assert.strictEqual(modelIndices.getFlowEventsWithId('0x101').length, 0);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/model_settings.html b/chromium/third_party/catapult/tracing/tracing/model/model_settings.html
new file mode 100644
index 00000000000..c34040183d2
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/model_settings.html
@@ -0,0 +1,148 @@
+<!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/settings.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.model', function() {
+ const Settings = tr.b.Settings;
+
+ /**
+ * A way to persist settings specific to parts of a trace model.
+ *
+ * This object should not be persisted because it builds up internal data
+ * structures that map model objects to settings keys. It should thus be
+ * created for the duration of whatever interaction(s) you're going to do with
+ * model settings, and then discarded.
+ *
+ * This system works on a notion of an object key: for an object's key, it
+ * considers all the other keys in the model. If it is unique, then the key is
+ * persisted to tr.b.Settings. However, if it is not unique, then the
+ * setting is stored on the object itself. Thus, objects with unique keys will
+ * be persisted across page reloads, whereas objects with nonunique keys will
+ * not.
+ */
+ function ModelSettings(model) {
+ this.model = model;
+ this.objectsByKey_ = [];
+ this.nonuniqueKeys_ = [];
+ this.buildObjectsByKeyMap_();
+ this.removeNonuniqueKeysFromSettings_();
+ this.ephemeralSettingsByGUID_ = {};
+ }
+
+ ModelSettings.prototype = {
+ buildObjectsByKeyMap_() {
+ const objects = [];
+ this.model.iterateAllPersistableObjects(function(o) {
+ objects.push(o);
+ });
+
+ const objectsByKey = {};
+ const NONUNIQUE_KEY = 'nonuniqueKey';
+ for (let i = 0; i < objects.length; i++) {
+ const object = objects[i];
+ const objectKey = object.getSettingsKey();
+ if (!objectKey) continue;
+ if (objectsByKey[objectKey] === undefined) {
+ objectsByKey[objectKey] = object;
+ continue;
+ }
+ objectsByKey[objectKey] = NONUNIQUE_KEY;
+ }
+
+ const nonuniqueKeys = {};
+ Object.keys(objectsByKey).forEach(function(objectKey) {
+ if (objectsByKey[objectKey] !== NONUNIQUE_KEY) {
+ return;
+ }
+ delete objectsByKey[objectKey];
+ nonuniqueKeys[objectKey] = true;
+ });
+
+ this.nonuniqueKeys = nonuniqueKeys;
+ this.objectsByKey_ = objectsByKey;
+ },
+
+ removeNonuniqueKeysFromSettings_() {
+ const settings = Settings.get('trace_model_settings', {});
+ let settingsChanged = false;
+ Object.keys(settings).forEach(function(objectKey) {
+ if (!this.nonuniqueKeys[objectKey]) {
+ return;
+ }
+ settingsChanged = true;
+ delete settings[objectKey];
+ }, this);
+ if (settingsChanged) {
+ Settings.set('trace_model_settings', settings);
+ }
+ },
+
+ hasUniqueSettingKey(object) {
+ const objectKey = object.getSettingsKey();
+ if (!objectKey) return false;
+ return this.objectsByKey_[objectKey] !== undefined;
+ },
+
+ getSettingFor(object, objectLevelKey, defaultValue) {
+ const objectKey = object.getSettingsKey();
+ if (!objectKey || !this.objectsByKey_[objectKey]) {
+ const settings = this.getEphemeralSettingsFor_(object);
+ const ephemeralValue = settings[objectLevelKey];
+ if (ephemeralValue !== undefined) {
+ return ephemeralValue;
+ }
+ return defaultValue;
+ }
+
+ const settings = Settings.get('trace_model_settings', {});
+ if (!settings[objectKey]) {
+ settings[objectKey] = {};
+ }
+ const value = settings[objectKey][objectLevelKey];
+ if (value !== undefined) {
+ return value;
+ }
+ return defaultValue;
+ },
+
+ setSettingFor(object, objectLevelKey, value) {
+ const objectKey = object.getSettingsKey();
+ if (!objectKey || !this.objectsByKey_[objectKey]) {
+ this.getEphemeralSettingsFor_(object)[objectLevelKey] = value;
+ return;
+ }
+
+ const settings = Settings.get('trace_model_settings', {});
+ if (!settings[objectKey]) {
+ settings[objectKey] = {};
+ }
+ if (settings[objectKey][objectLevelKey] === value) {
+ return;
+ }
+ settings[objectKey][objectLevelKey] = value;
+ Settings.set('trace_model_settings', settings);
+ },
+
+ getEphemeralSettingsFor_(object) {
+ if (object.guid === undefined) {
+ throw new Error('Only objects with GUIDs can be persisted');
+ }
+ if (this.ephemeralSettingsByGUID_[object.guid] === undefined) {
+ this.ephemeralSettingsByGUID_[object.guid] = {};
+ }
+ return this.ephemeralSettingsByGUID_[object.guid];
+ }
+ };
+
+ return {
+ ModelSettings,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/model_settings_test.html b/chromium/third_party/catapult/tracing/tracing/model/model_settings_test.html
new file mode 100644
index 00000000000..cec8898180c
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/model_settings_test.html
@@ -0,0 +1,178 @@
+<!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/model.html">
+<link rel="import" href="/tracing/model/model_settings.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ test('process_name_uniqueness_0', function() {
+ const model = new tr.Model();
+ const p1 = model.getOrCreateProcess(1);
+ const settings = new tr.model.ModelSettings(model);
+ assert.isFalse(settings.hasUniqueSettingKey(p1));
+ });
+
+ test('process_name_uniqueness_1', function() {
+ const model = new tr.Model();
+ const p1 = model.getOrCreateProcess(1);
+ p1.name = 'Browser';
+ const settings = new tr.model.ModelSettings(model);
+ assert.isTrue(settings.hasUniqueSettingKey(p1));
+ });
+
+ test('process_name_uniqueness_2', function() {
+ const model = new tr.Model();
+ const p1 = model.getOrCreateProcess(1);
+ const p2 = model.getOrCreateProcess(2);
+ p1.name = 'Renderer';
+ p2.name = 'Renderer';
+ const settings = new tr.model.ModelSettings(model);
+ assert.isFalse(settings.hasUniqueSettingKey(p1));
+ assert.isFalse(settings.hasUniqueSettingKey(p2));
+ });
+
+ test('process_name_uniqueness_3', function() {
+ const model = new tr.Model();
+ const p1 = model.getOrCreateProcess(1);
+ const p2 = model.getOrCreateProcess(2);
+ p1.name = 'Renderer';
+ p1.labels.push('Google Search');
+ p2.name = 'Renderer';
+ const settings = new tr.model.ModelSettings(model);
+ assert.isTrue(settings.hasUniqueSettingKey(p1));
+ assert.isTrue(settings.hasUniqueSettingKey(p2));
+ });
+
+ test('thread_name_uniqueness_0', function() {
+ const model = new tr.Model();
+ const p1 = model.getOrCreateProcess(1);
+ const p2 = model.getOrCreateProcess(2);
+ const t1 = p1.getOrCreateThread(1);
+ const t2 = p2.getOrCreateThread(2);
+ p1.name = 'Browser';
+ p2.name = 'Renderer';
+ t1.name = 'Main';
+ t2.name = 'Main';
+ const settings = new tr.model.ModelSettings(model);
+ assert.isTrue(settings.hasUniqueSettingKey(t1));
+ assert.isTrue(settings.hasUniqueSettingKey(t2));
+ });
+
+ test('thread_name_uniqueness_1', function() {
+ const model = new tr.Model();
+ const p1 = model.getOrCreateProcess(1);
+ const p2 = model.getOrCreateProcess(2);
+ const t1 = p1.getOrCreateThread(1);
+ const t2 = p2.getOrCreateThread(2);
+ p1.name = 'Renderer';
+ p2.name = 'Renderer';
+ t1.name = 'Main';
+ t2.name = 'Main';
+ const settings = new tr.model.ModelSettings(model);
+ assert.isFalse(settings.hasUniqueSettingKey(t1));
+ assert.isFalse(settings.hasUniqueSettingKey(t2));
+ });
+
+ test('process_persistence_when_not_unique', function() {
+ let model = new tr.Model();
+ let p1 = model.getOrCreateProcess(1);
+ let settings = new tr.model.ModelSettings(model);
+ assert.isTrue(settings.getSettingFor(p1, 'true_by_default', true));
+
+ settings.setSettingFor(p1, 'true_by_default', false);
+ assert.isFalse(settings.getSettingFor(p1, 'true_by_default', true));
+
+ // Now, clobber the model, and verify that it didn't persist.
+ model = new tr.Model();
+ p1 = model.getOrCreateProcess(1);
+ settings = new tr.model.ModelSettings(model);
+ assert.isTrue(settings.getSettingFor(p1, 'true_by_default', true));
+ });
+
+ test('process_persistence_when_not_unique_with_name', function() {
+ let model = new tr.Model();
+ let p1 = model.getOrCreateProcess(1);
+ p1.name = 'Browser';
+ let settings = new tr.model.ModelSettings(model);
+ assert.isTrue(settings.getSettingFor(p1, 'true_by_default', true));
+
+ settings.setSettingFor(p1, 'true_by_default', false);
+ assert.isFalse(settings.getSettingFor(p1, 'true_by_default', true));
+
+ // Now, clobber the model, and verify that it persisted.
+ model = new tr.Model();
+ p1 = model.getOrCreateProcess(1);
+ p1.name = 'Browser';
+ settings = new tr.model.ModelSettings(model);
+ assert.isFalse(settings.getSettingFor(p1, 'true_by_default', true));
+ });
+
+ test('thread_persistence_when_not_unique', function() {
+ let model = new tr.Model();
+ let p1 = model.getOrCreateProcess(1);
+ let p2 = model.getOrCreateProcess(2);
+ let t1 = p1.getOrCreateThread(1);
+ let t2 = p2.getOrCreateThread(2);
+ p1.name = 'Renderer';
+ p2.name = 'Renderer';
+ t1.name = 'Main';
+ t2.name = 'Main';
+ let settings = new tr.model.ModelSettings(model);
+ assert.isTrue(settings.getSettingFor(t1, 'true_by_default', true));
+
+ settings.setSettingFor(t1, 'true_by_default', false);
+ assert.isFalse(settings.getSettingFor(t1, 'true_by_default', true));
+
+ // Now, clobber the model, and verify that it persisted.
+ model = new tr.Model();
+ p1 = model.getOrCreateProcess(1);
+ p2 = model.getOrCreateProcess(2);
+ t1 = p1.getOrCreateThread(1);
+ t2 = p2.getOrCreateThread(2);
+ p1.name = 'Renderer';
+ p2.name = 'Renderer';
+ t1.name = 'Main';
+ t2.name = 'Main';
+ settings = new tr.model.ModelSettings(model);
+ assert.isTrue(settings.getSettingFor(t1, 'true_by_default', true));
+ });
+
+ test('thread_persistence_when_unique', function() {
+ let model = new tr.Model();
+ let p1 = model.getOrCreateProcess(1);
+ let p2 = model.getOrCreateProcess(2);
+ let t1 = p1.getOrCreateThread(1);
+ let t2 = p2.getOrCreateThread(2);
+ p1.name = 'Browser';
+ p2.name = 'Renderer';
+ t1.name = 'Main';
+ t2.name = 'Main';
+ let settings = new tr.model.ModelSettings(model);
+ assert.isTrue(settings.getSettingFor(t1, 'true_by_default', true));
+
+ settings.setSettingFor(t1, 'true_by_default', false);
+ assert.isFalse(settings.getSettingFor(t1, 'true_by_default', true));
+
+ // Now, clobber the model, and verify that it persisted.
+ model = new tr.Model();
+ p1 = model.getOrCreateProcess(1);
+ p2 = model.getOrCreateProcess(2);
+ t1 = p1.getOrCreateThread(1);
+ t2 = p2.getOrCreateThread(2);
+ p1.name = 'Browser';
+ p2.name = 'Renderer';
+ t1.name = 'Main';
+ t2.name = 'Main';
+ settings = new tr.model.ModelSettings(model);
+ assert.isFalse(settings.getSettingFor(t1, 'true_by_default', true));
+ });
+});
+</script>
+
diff --git a/chromium/third_party/catapult/tracing/tracing/model/model_stats.html b/chromium/third_party/catapult/tracing/tracing/model/model_stats.html
new file mode 100644
index 00000000000..41f7dd64602
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/model_stats.html
@@ -0,0 +1,97 @@
+<!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">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.model', function() {
+ /**
+ * @constructor
+ */
+ function ModelStats() {
+ this.traceEventCountsByKey_ = new Map();
+ this.allTraceEventStats_ = [];
+
+ this.traceEventStatsInTimeIntervals_ = new Map();
+ this.allTraceEventStatsInTimeIntervals_ = [];
+
+ this.hasEventSizesinBytes_ = false;
+
+ this.traceImportDurationMs_ = undefined;
+ }
+
+ ModelStats.prototype = {
+ TIME_INTERVAL_SIZE_IN_MS: 100,
+
+ willProcessBasicTraceEvent(phase, category, title, ts,
+ opt_eventSizeinBytes) {
+ const key = phase + '/' + category + '/' + title;
+ let eventStats = this.traceEventCountsByKey_.get(key);
+ if (eventStats === undefined) {
+ eventStats = {
+ phase,
+ category,
+ title,
+ numEvents: 0,
+ totalEventSizeinBytes: 0
+ };
+ this.traceEventCountsByKey_.set(key, eventStats);
+ this.allTraceEventStats_.push(eventStats);
+ }
+ eventStats.numEvents++;
+
+ const timeIntervalKey = Math.floor(
+ tr.b.Unit.timestampFromUs(ts) / this.TIME_INTERVAL_SIZE_IN_MS);
+ let eventStatsByTimeInverval =
+ this.traceEventStatsInTimeIntervals_.get(timeIntervalKey);
+ if (eventStatsByTimeInverval === undefined) {
+ eventStatsByTimeInverval = {
+ timeInterval: timeIntervalKey,
+ numEvents: 0,
+ totalEventSizeinBytes: 0
+ };
+ this.traceEventStatsInTimeIntervals_.set(timeIntervalKey,
+ eventStatsByTimeInverval);
+ this.allTraceEventStatsInTimeIntervals_.push(eventStatsByTimeInverval);
+ }
+ eventStatsByTimeInverval.numEvents++;
+
+ if (opt_eventSizeinBytes !== undefined) {
+ this.hasEventSizesinBytes_ = true;
+ eventStats.totalEventSizeinBytes += opt_eventSizeinBytes;
+ eventStatsByTimeInverval.totalEventSizeinBytes += opt_eventSizeinBytes;
+ }
+ },
+
+ get allTraceEventStats() {
+ return this.allTraceEventStats_;
+ },
+
+ get allTraceEventStatsInTimeIntervals() {
+ return this.allTraceEventStatsInTimeIntervals_;
+ },
+
+ get hasEventSizesinBytes() {
+ return this.hasEventSizesinBytes_;
+ },
+
+ get traceImportDurationMs() {
+ return this.traceImportDurationMs_;
+ },
+
+ set traceImportDurationMs(traceImportDurationMs) {
+ this.traceImportDurationMs_ = traceImportDurationMs;
+ }
+ };
+
+ return {
+ ModelStats,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/model_stats_test.html b/chromium/third_party/catapult/tracing/tracing/model/model_stats_test.html
new file mode 100644
index 00000000000..a1108b1b57a
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/model_stats_test.html
@@ -0,0 +1,61 @@
+<!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/model/model_stats.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const ModelStats = tr.model.ModelStats;
+
+ test('getTraceEventStatsByCategory', function() {
+ const modelStats = new ModelStats();
+ modelStats.willProcessBasicTraceEvent('X', 'cat1', 'title1');
+ modelStats.willProcessBasicTraceEvent('X', 'cat1', 'title1');
+ modelStats.willProcessBasicTraceEvent('X', 'cat2', 'title3');
+
+ assert.strictEqual(modelStats.allTraceEventStats.length, 2);
+ assert.strictEqual(
+ modelStats.traceEventCountsByKey_.get('X/cat1/title1').numEvents,
+ 2);
+ assert.strictEqual(
+ modelStats.traceEventCountsByKey_.get('X/cat2/title3').numEvents,
+ 1);
+ });
+
+ test('getTraceEventStatsInTimeIntervals', function() {
+ const modelStats = new ModelStats();
+ const timeIntervalSizeInUs = modelStats.TIME_INTERVAL_SIZE_IN_MS * 1000;
+ modelStats.willProcessBasicTraceEvent('X', 'cat1', 'title1', 1, 1);
+ modelStats.willProcessBasicTraceEvent(
+ 'X', 'cat1', 'title1', timeIntervalSizeInUs + 1, 2);
+ modelStats.willProcessBasicTraceEvent(
+ 'X', 'cat1', 'title1', 2 * timeIntervalSizeInUs + 1, 3);
+ modelStats.willProcessBasicTraceEvent(
+ 'X', 'cat2', 'title3', 2 * timeIntervalSizeInUs + 2, 4);
+
+ assert.strictEqual(modelStats.allTraceEventStatsInTimeIntervals.length, 3);
+ assert.strictEqual(
+ modelStats.traceEventStatsInTimeIntervals_.get(0).numEvents, 1);
+ assert.strictEqual(
+ modelStats.traceEventStatsInTimeIntervals_.get(1).numEvents, 1);
+ assert.strictEqual(
+ modelStats.traceEventStatsInTimeIntervals_.get(2).numEvents, 2);
+
+ assert.isTrue(modelStats.hasEventSizesinBytes);
+ assert.strictEqual(
+ modelStats.traceEventStatsInTimeIntervals_.get(0).totalEventSizeinBytes,
+ 1);
+ assert.strictEqual(
+ modelStats.traceEventStatsInTimeIntervals_.get(1).totalEventSizeinBytes,
+ 2);
+ assert.strictEqual(
+ modelStats.traceEventStatsInTimeIntervals_.get(2).totalEventSizeinBytes,
+ 7);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/model_test.html b/chromium/third_party/catapult/tracing/tracing/model/model_test.html
new file mode 100644
index 00000000000..7613b421254
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/model_test.html
@@ -0,0 +1,343 @@
+<!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/time_display_modes.html">
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/importer/trace_event_importer.html">
+<link rel="import" href="/tracing/importer/import.html">
+<link rel="import" href="/tracing/model/annotation.html">
+<link rel="import" href="/tracing/model/model.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const ThreadSlice = tr.model.ThreadSlice;
+ const TitleOrCategoryFilter = tr.c.TitleOrCategoryFilter;
+ const Frame = tr.model.Frame;
+
+ const createModelWithOneOfEverything = function() {
+ const m = new tr.Model();
+ const cpu = m.kernel.getOrCreateCpu(1);
+ cpu.slices.push(tr.c.TestUtils.newSliceEx({start: 1, duration: 3}));
+
+ const p = m.getOrCreateProcess(1);
+ const t = p.getOrCreateThread(1);
+ const slice = new ThreadSlice('', 'a', 0, 1, {}, 4);
+ t.sliceGroup.pushSlice(slice);
+ t.asyncSliceGroup.push(tr.c.TestUtils.newAsyncSlice(0, 1, t, t));
+
+ const c = p.getOrCreateCounter('', 'ProcessCounter');
+ let aSeries = new tr.model.CounterSeries('a', 0);
+ let bSeries = new tr.model.CounterSeries('b', 0);
+ c.addSeries(aSeries);
+ c.addSeries(bSeries);
+
+ aSeries.addCounterSample(0, 5);
+ aSeries.addCounterSample(1, 6);
+ aSeries.addCounterSample(2, 5);
+ aSeries.addCounterSample(3, 7);
+
+ bSeries.addCounterSample(0, 10);
+ bSeries.addCounterSample(1, 15);
+ bSeries.addCounterSample(2, 12);
+ bSeries.addCounterSample(3, 16);
+
+ const c1 = cpu.getOrCreateCounter('', 'CpuCounter');
+ aSeries = new tr.model.CounterSeries('a', 0);
+ bSeries = new tr.model.CounterSeries('b', 0);
+ c1.addSeries(aSeries);
+ c1.addSeries(bSeries);
+
+ aSeries.addCounterSample(0, 5);
+ aSeries.addCounterSample(1, 6);
+ aSeries.addCounterSample(2, 5);
+ aSeries.addCounterSample(3, 7);
+
+ bSeries.addCounterSample(0, 10);
+ bSeries.addCounterSample(1, 15);
+ bSeries.addCounterSample(2, 12);
+ bSeries.addCounterSample(3, 16);
+
+ const frame1 = new Frame([slice], [{thread: t, start: 1, end: 5}]);
+ p.frames.push.apply(p.frames, frame1);
+
+ const gd = new tr.model.GlobalMemoryDump(m, 2);
+ const pd = new tr.model.ProcessMemoryDump(gd, p, 2);
+ gd.processMemoryDumps[1] = pd;
+ m.globalMemoryDumps.push(gd);
+ p.memoryDumps.push(pd);
+
+ m.updateBounds();
+
+ return m;
+ };
+
+ test('helper', function() {
+ function Helper(model) {
+ this.model = model;
+ }
+ Helper.guid = tr.b.GUID.allocateSimple();
+ Helper.supportsModel = function(model) {
+ return true;
+ };
+
+ const m = new tr.Model();
+ const h = m.getOrCreateHelper(Helper);
+ assert.isTrue(h instanceof Helper);
+ assert.isTrue(h === m.getOrCreateHelper(Helper));
+
+ function UnsupportedHelper(model) {
+ this.model = model;
+ }
+ UnsupportedHelper.guid = tr.b.GUID.allocateSimple();
+ UnsupportedHelper.supportsModel = function(model) {
+ return false;
+ };
+
+ assert.isUndefined(m.getOrCreateHelper(UnsupportedHelper));
+ // Try again to test doesHelperGUIDSupportThisModel_ .
+ assert.isUndefined(m.getOrCreateHelper(UnsupportedHelper));
+ });
+
+ test('modelBounds_EmptyModel', function() {
+ const m = new tr.Model();
+ m.updateBounds();
+ assert.isUndefined(m.bounds.min);
+ assert.isUndefined(m.bounds.max);
+ });
+
+ test('modelBounds_OneEmptyThread', function() {
+ const m = new tr.Model();
+ const t = m.getOrCreateProcess(1).getOrCreateThread(1);
+ m.updateBounds();
+ assert.isUndefined(m.bounds.min);
+ assert.isUndefined(m.bounds.max);
+ });
+
+ test('modelBounds_OneThread', function() {
+ const m = new tr.Model();
+ const t = m.getOrCreateProcess(1).getOrCreateThread(1);
+ t.sliceGroup.pushSlice(new ThreadSlice('', 'a', 0, 1, {}, 3));
+ m.updateBounds();
+ assert.strictEqual(m.bounds.min, 1);
+ assert.strictEqual(m.bounds.max, 4);
+ });
+
+ test('modelBounds_OneThreadAndOneEmptyThread', function() {
+ const m = new tr.Model();
+ const t1 = m.getOrCreateProcess(1).getOrCreateThread(1);
+ t1.sliceGroup.pushSlice(new ThreadSlice('', 'a', 0, 1, {}, 3));
+ const t2 = m.getOrCreateProcess(1).getOrCreateThread(1);
+ m.updateBounds();
+ assert.strictEqual(m.bounds.min, 1);
+ assert.strictEqual(m.bounds.max, 4);
+ });
+
+ test('modelBounds_OneCpu', function() {
+ const m = new tr.Model();
+ const cpu = m.kernel.getOrCreateCpu(1);
+ cpu.slices.push(tr.c.TestUtils.newSliceEx({start: 1, duration: 3}));
+ m.updateBounds();
+ assert.strictEqual(m.bounds.min, 1);
+ assert.strictEqual(m.bounds.max, 4);
+ });
+
+ test('modelBounds_OneCpuOneThread', function() {
+ const m = new tr.Model();
+ const cpu = m.kernel.getOrCreateCpu(1);
+ cpu.slices.push(tr.c.TestUtils.newSliceEx({start: 1, duration: 3}));
+
+ const t = m.getOrCreateProcess(1).getOrCreateThread(1);
+ t.sliceGroup.pushSlice(new ThreadSlice('', 'a', 0, 1, {}, 4));
+
+ m.updateBounds();
+ assert.strictEqual(m.bounds.min, 1);
+ assert.strictEqual(m.bounds.max, 5);
+ });
+
+ test('modelBounds_GlobalMemoryDumps', function() {
+ const m = new tr.Model();
+ m.globalMemoryDumps.push(new tr.model.GlobalMemoryDump(m, 1));
+ m.globalMemoryDumps.push(new tr.model.GlobalMemoryDump(m, 3));
+ m.globalMemoryDumps.push(new tr.model.GlobalMemoryDump(m, 5));
+
+ m.updateBounds();
+ assert.strictEqual(m.bounds.min, 1);
+ assert.strictEqual(m.bounds.max, 5);
+ });
+
+ test('modelBounds_ProcessMemoryDumps', function() {
+ const m = new tr.Model();
+ const p = m.getOrCreateProcess(1);
+ const gd = new tr.model.GlobalMemoryDump(m, -1);
+ p.memoryDumps.push(new tr.model.ProcessMemoryDump(gd, m, 1));
+ p.memoryDumps.push(new tr.model.ProcessMemoryDump(gd, m, 3));
+ p.memoryDumps.push(new tr.model.ProcessMemoryDump(gd, m, 5));
+
+ m.updateBounds();
+ assert.strictEqual(m.bounds.min, 1);
+ assert.strictEqual(m.bounds.max, 5);
+ });
+
+
+ test('modelConvertsTimestampToModelTime', function() {
+ const m = new tr.Model();
+ const traceEvents = [
+ {ts: 1000, pid: 1, tid: 1, ph: 'B', cat: 'a', name: 'taskA', args: {}},
+ {ts: 2000, pid: 1, tid: 1, ph: 'E', cat: 'a', name: 'taskA', args: {}}
+ ];
+ const i = new tr.importer.Import(m);
+ i.importTraces([traceEvents]);
+ assert.strictEqual(
+ m.convertTimestampToModelTime('traceEventClock', 1000), 0);
+ assert.strictEqual(
+ m.convertTimestampToModelTime('traceEventClock', 2000), 1);
+ });
+
+ test('TitleOrCategoryFilter', function() {
+ const s0 = tr.c.TestUtils.newSliceEx({start: 1, duration: 3});
+ assert.isTrue(new TitleOrCategoryFilter('a').matchSlice(s0));
+ assert.isFalse(new TitleOrCategoryFilter('x').matchSlice(s0));
+
+ const s1 = tr.c.TestUtils.newSliceEx({title: 'ba', start: 1, duration: 3});
+ assert.isTrue(new TitleOrCategoryFilter('a').matchSlice(s1));
+ assert.isTrue(new TitleOrCategoryFilter('ba').matchSlice(s1));
+ assert.isFalse(new TitleOrCategoryFilter('x').matchSlice(s1));
+ });
+
+ test('model_findAllThreadsNamed', function() {
+ const m = new tr.Model();
+ const t = m.getOrCreateProcess(1).getOrCreateThread(1);
+ t.name = 'CrBrowserMain';
+
+ m.updateBounds();
+ let f = m.findAllThreadsNamed('CrBrowserMain');
+ assert.deepEqual([t], f);
+ f = m.findAllThreadsNamed('NoSuchThread');
+ assert.strictEqual(f.length, 0);
+ });
+
+ test('model_updateCategories', function() {
+ const m = new tr.Model();
+ const t = m.getOrCreateProcess(1).getOrCreateThread(1);
+ t.sliceGroup.pushSlice(new ThreadSlice('categoryA', 'a', 0, 1, {}, 3));
+ t.sliceGroup.pushSlice(new ThreadSlice('categoryA', 'a', 0, 1, {}, 3));
+ t.sliceGroup.pushSlice(new ThreadSlice('categoryB', 'a', 0, 1, {}, 3));
+ t.sliceGroup.pushSlice(new ThreadSlice('categoryA', 'a', 0, 1, {}, 3));
+ t.sliceGroup.pushSlice(new ThreadSlice('', 'a', 0, 1, {}, 3));
+ m.updateCategories_();
+ assert.deepEqual(['categoryA', 'categoryB'], m.categories);
+ });
+
+ test('getEventByStableId', function() {
+ const m = new tr.Model();
+ const p = m.getOrCreateProcess(0);
+ const t = p.getOrCreateThread(1);
+ const slice = tr.c.TestUtils.newSliceEx({start: 0, duration: 10});
+ t.sliceGroup.pushSlice(slice);
+ const ue = tr.c.TestUtils.newInteractionRecord(m, 0, 10);
+ m.userModel.expectations.push(ue);
+ const gie = tr.c.TestUtils.newInstantEvent({
+ title: 'gie',
+ start: 0,
+ colorId: 0
+ });
+ m.instantEvents.push(gie);
+
+ assert.strictEqual(slice, m.getEventByStableId(slice.stableId));
+ assert.strictEqual(ue, m.getEventByStableId(ue.stableId));
+ assert.strictEqual(gie, m.getEventByStableId(gie.stableId));
+ });
+
+ test('model_annotationAddRemove', function() {
+ const m = new tr.Model();
+ const a1 = new tr.model.Annotation();
+ const a2 = new tr.model.Annotation();
+
+ assert.strictEqual(m.getAllAnnotations().length, 0);
+ m.addAnnotation(a1);
+ assert.strictEqual(m.getAllAnnotations().length, 1);
+ m.addAnnotation(a2);
+ assert.strictEqual(m.getAllAnnotations().length, 2);
+
+ assert.strictEqual(m.getAnnotationByGUID(a1.guid), a1);
+ assert.strictEqual(m.getAnnotationByGUID(a2.guid), a2);
+
+ m.removeAnnotation(a1);
+ assert.isUndefined(m.getAnnotationByGUID(a1.guid));
+ assert.strictEqual(m.getAnnotationByGUID(a2.guid), a2);
+ assert.strictEqual(m.getAllAnnotations().length, 1);
+ });
+
+ test('model_intrinsicTimeUnit', function() {
+ const unit = tr.b.TimeDisplayModes;
+ const m = new tr.Model();
+
+ // by default it should be milliseconds
+ assert.strictEqual(m.intrinsicTimeUnit, unit.ms);
+
+ m.intrinsicTimeUnit = unit.ns;
+ assert.strictEqual(m.intrinsicTimeUnit, unit.ns);
+ // should be able to set to the same
+ m.intrinsicTimeUnit = unit.ns;
+ assert.strictEqual(m.intrinsicTimeUnit, unit.ns);
+ // should not be able to change it after fixing it
+ assert.throw(function() { m.intrinsicTimeUnit = unit.ms; });
+ assert.strictEqual(m.intrinsicTimeUnit, unit.ns);
+ });
+
+ test('model_getAllProcesses', function() {
+ const m = new tr.Model();
+ const p1 = m.getOrCreateProcess(1);
+ const p2 = m.getOrCreateProcess(2);
+ const p3 = m.getOrCreateProcess(3);
+ const p4 = m.getOrCreateProcess(4);
+ const p5 = m.getOrCreateProcess(5);
+
+ assert.sameMembers(m.getAllProcesses(), [p1, p2, p3, p4, p5]);
+ assert.sameMembers(m.getAllProcesses(p => true), [p1, p2, p3, p4, p5]);
+ assert.sameMembers(m.getAllProcesses(p => false), []);
+ assert.sameMembers(m.getAllProcesses(p => p.pid % 2 === 0), [p2, p4]);
+ });
+
+ test('model_joinRefs', function() {
+ function RefCountingSnapshot() {
+ tr.model.ObjectSnapshot.apply(this, arguments);
+ this.refCount = 0;
+ }
+
+ RefCountingSnapshot.prototype = {
+ __proto__: tr.model.ObjectSnapshot.prototype,
+
+ referencedAt() {
+ ++this.refCount;
+ }
+ };
+
+ const typeName = 'RefCountingSnapshot';
+ tr.model.ObjectSnapshot.subTypes.register(
+ RefCountingSnapshot,
+ {typeName});
+
+ const m = new tr.Model();
+ const p = m.getOrCreateProcess(1);
+ const s1 = p.objects.addSnapshot(new tr.model.ScopedId(typeName, '0x1'),
+ 'cat', typeName, 1000, {});
+ const s2 = p.objects.addSnapshot(new tr.model.ScopedId(typeName, '0x2'),
+ 'cat', typeName, 2000, {
+ myRef: {
+ scope: typeName,
+ id_ref: '0x1'
+ }
+ });
+ m.joinRefs();
+ assert.strictEqual(s1.refCount, 1);
+ assert.strictEqual(s2.refCount, 0);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/object_collection.html b/chromium/third_party/catapult/tracing/tracing/model/object_collection.html
new file mode 100644
index 00000000000..5b4ed2325dc
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/object_collection.html
@@ -0,0 +1,229 @@
+<!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/range.html">
+<link rel="import" href="/tracing/base/utils.html">
+<link rel="import" href="/tracing/model/event_container.html">
+<link rel="import" href="/tracing/model/object_instance.html">
+<link rel="import" href="/tracing/model/time_to_object_instance_map.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Provides the ObjectCollection class.
+ */
+tr.exportTo('tr.model', function() {
+ const ObjectInstance = tr.model.ObjectInstance;
+ const ObjectSnapshot = tr.model.ObjectSnapshot;
+
+ /**
+ * A collection of object instances and their snapshots, accessible by id and
+ * time, or by object name.
+ *
+ * @constructor
+ */
+ function ObjectCollection(parent) {
+ tr.model.EventContainer.call(this);
+ this.parent = parent;
+ // scope -> {id -> TimeToObjectInstanceMap}
+ this.instanceMapsByScopedId_ = {};
+ this.instancesByTypeName_ = {};
+ this.createObjectInstance_ = this.createObjectInstance_.bind(this);
+ }
+
+ ObjectCollection.prototype = {
+ __proto__: tr.model.EventContainer.prototype,
+
+ * childEvents() {
+ for (const instance of this.getAllObjectInstances()) {
+ yield instance;
+ yield* instance.snapshots;
+ }
+ },
+
+ createObjectInstance_(
+ parent, scopedId, category, name, creationTs, opt_baseTypeName) {
+ const constructor = tr.model.ObjectInstance.subTypes.getConstructor(
+ category, name);
+ const instance = new constructor(
+ parent, scopedId, category, name, creationTs, opt_baseTypeName);
+ const typeName = instance.typeName;
+ let instancesOfTypeName = this.instancesByTypeName_[typeName];
+ if (!instancesOfTypeName) {
+ instancesOfTypeName = [];
+ this.instancesByTypeName_[typeName] = instancesOfTypeName;
+ }
+ instancesOfTypeName.push(instance);
+ return instance;
+ },
+
+ getOrCreateInstanceMap_(scopedId) {
+ let dict;
+ if (scopedId.scope in this.instanceMapsByScopedId_) {
+ dict = this.instanceMapsByScopedId_[scopedId.scope];
+ } else {
+ dict = {};
+ this.instanceMapsByScopedId_[scopedId.scope] = dict;
+ }
+ let instanceMap = dict[scopedId.id];
+ if (instanceMap) return instanceMap;
+ instanceMap = new tr.model.TimeToObjectInstanceMap(
+ this.createObjectInstance_, this.parent, scopedId);
+ dict[scopedId.id] = instanceMap;
+ return instanceMap;
+ },
+
+ idWasCreated(scopedId, category, name, ts) {
+ const instanceMap = this.getOrCreateInstanceMap_(scopedId);
+ return instanceMap.idWasCreated(category, name, ts);
+ },
+
+ addSnapshot(
+ scopedId, category, name, ts, args, opt_baseTypeName) {
+ const instanceMap = this.getOrCreateInstanceMap_(scopedId);
+ const snapshot = instanceMap.addSnapshot(
+ category, name, ts, args, opt_baseTypeName);
+ if (snapshot.objectInstance.category !== category) {
+ const msg = 'Added snapshot name=' + name + ' with cat=' + category +
+ ' impossible. It instance was created/snapshotted with cat=' +
+ snapshot.objectInstance.category + ' name=' +
+ snapshot.objectInstance.name;
+ throw new Error(msg);
+ }
+ if (opt_baseTypeName &&
+ snapshot.objectInstance.baseTypeName !== opt_baseTypeName) {
+ throw new Error('Could not add snapshot with baseTypeName=' +
+ opt_baseTypeName + '. It ' +
+ 'was previously created with name=' +
+ snapshot.objectInstance.baseTypeName);
+ }
+ if (snapshot.objectInstance.name !== name) {
+ throw new Error('Could not add snapshot with name=' + name + '. It ' +
+ 'was previously created with name=' +
+ snapshot.objectInstance.name);
+ }
+ return snapshot;
+ },
+
+ idWasDeleted(scopedId, category, name, ts) {
+ const instanceMap = this.getOrCreateInstanceMap_(scopedId);
+ const deletedInstance = instanceMap.idWasDeleted(category, name, ts);
+ if (!deletedInstance) return;
+
+ if (deletedInstance.category !== category) {
+ const msg = 'Deleting object ' + deletedInstance.name +
+ ' with a different category ' +
+ 'than when it was created. It previous had cat=' +
+ deletedInstance.category + ' but the delete command ' +
+ 'had cat=' + category;
+ throw new Error(msg);
+ }
+ if (deletedInstance.baseTypeName !== name) {
+ throw new Error('Deletion requested for name=' +
+ name + ' could not proceed: ' +
+ 'An existing object with baseTypeName=' +
+ deletedInstance.baseTypeName + ' existed.');
+ }
+ },
+
+ autoDeleteObjects(maxTimestamp) {
+ for (const imapById of Object.values(this.instanceMapsByScopedId_)) {
+ for (const i2imap of Object.values(imapById)) {
+ const lastInstance = i2imap.lastInstance;
+ if (lastInstance.deletionTs !== Number.MAX_VALUE) continue;
+ i2imap.idWasDeleted(
+ lastInstance.category, lastInstance.name, maxTimestamp);
+ // idWasDeleted will cause lastInstance.deletionTsWasExplicit to be
+ // set to true. Unset it here.
+ lastInstance.deletionTsWasExplicit = false;
+ }
+ }
+ },
+
+ getObjectInstanceAt(scopedId, ts) {
+ let instanceMap;
+ if (scopedId.scope in this.instanceMapsByScopedId_) {
+ instanceMap = this.instanceMapsByScopedId_[scopedId.scope][scopedId.id];
+ }
+ if (!instanceMap) return undefined;
+ return instanceMap.getInstanceAt(ts);
+ },
+
+ getSnapshotAt(scopedId, ts) {
+ const instance = this.getObjectInstanceAt(scopedId, ts);
+ if (!instance) return undefined;
+ return instance.getSnapshotAt(ts);
+ },
+
+ iterObjectInstances(iter, opt_this) {
+ opt_this = opt_this || this;
+ for (const imapById of Object.values(this.instanceMapsByScopedId_)) {
+ for (const i2imap of Object.values(imapById)) {
+ i2imap.instances.forEach(iter, opt_this);
+ }
+ }
+ },
+
+ getAllObjectInstances() {
+ const instances = [];
+ this.iterObjectInstances(function(i) { instances.push(i); });
+ return instances;
+ },
+
+ getAllInstancesNamed(name) {
+ return this.instancesByTypeName_[name];
+ },
+
+ getAllInstancesByTypeName() {
+ return this.instancesByTypeName_;
+ },
+
+ preInitializeAllObjects() {
+ this.iterObjectInstances(function(instance) {
+ instance.preInitialize();
+ });
+ },
+
+ initializeAllObjects() {
+ this.iterObjectInstances(function(instance) {
+ instance.initialize();
+ });
+ },
+
+ initializeInstances() {
+ this.iterObjectInstances(function(instance) {
+ instance.initialize();
+ });
+ },
+
+ updateBounds() {
+ this.bounds.reset();
+ this.iterObjectInstances(function(instance) {
+ instance.updateBounds();
+ this.bounds.addRange(instance.bounds);
+ }, this);
+ },
+
+ shiftTimestampsForward(amount) {
+ this.iterObjectInstances(function(instance) {
+ instance.shiftTimestampsForward(amount);
+ });
+ },
+
+ addCategoriesToDict(categoriesDict) {
+ this.iterObjectInstances(function(instance) {
+ categoriesDict[instance.category] = true;
+ });
+ }
+ };
+
+ return {
+ ObjectCollection,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/object_collection_test.html b/chromium/third_party/catapult/tracing/tracing/model/object_collection_test.html
new file mode 100644
index 00000000000..a04c23ff656
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/object_collection_test.html
@@ -0,0 +1,230 @@
+<!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/object_collection.html">
+<link rel="import" href="/tracing/model/scoped_id.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const TestObjectInstance = function(
+ parent, scopedId, category, name, creationTs) {
+ tr.model.ObjectInstance.call(
+ this, parent, scopedId, category, name, creationTs);
+ };
+
+ TestObjectInstance.prototype = {
+ __proto__: tr.model.ObjectInstance.prototype
+ };
+
+ test('objectInstanceSubtype', function() {
+ // Register that TestObjects are bound to TestObjectInstance.
+ tr.model.ObjectInstance.subTypes.register(
+ TestObjectInstance,
+ {typeName: 'TestObject'});
+
+ try {
+ const collection = new tr.model.ObjectCollection({ });
+ const scopedId = new tr.model.ScopedId('ptr', '0x1000');
+ collection.idWasCreated(
+ scopedId, 'tr.e.cc', 'Frame', 10);
+ collection.idWasDeleted(
+ scopedId, 'tr.e.cc', 'Frame', 15);
+ collection.idWasCreated(
+ scopedId, 'skia', 'TestObject', 20);
+ collection.idWasDeleted(
+ scopedId, 'skia', 'TestObject', 25);
+
+ const testFrame = collection.getObjectInstanceAt(scopedId, 10);
+ assert.instanceOf(testFrame, tr.model.ObjectInstance);
+ assert.notInstanceOf(testFrame, TestObjectInstance);
+
+ const testObject = collection.getObjectInstanceAt(scopedId, 20);
+ assert.instanceOf(testObject, tr.model.ObjectInstance);
+ assert.instanceOf(testObject, TestObjectInstance);
+ } finally {
+ tr.model.ObjectInstance.subTypes.unregister(TestObjectInstance);
+ }
+ });
+
+ test('twoSnapshots', function() {
+ const collection = new tr.model.ObjectCollection({});
+ const scopedId = new tr.model.ScopedId('ptr', '0x1000');
+ collection.idWasCreated(
+ scopedId, 'cat', 'Frame', 10);
+ collection.addSnapshot(
+ scopedId, 'cat', 'Frame', 10, {foo: 1});
+ collection.addSnapshot(
+ scopedId, 'cat', 'Frame', 20, {foo: 2});
+
+ collection.updateBounds();
+ assert.strictEqual(collection.bounds.min, 10);
+ assert.strictEqual(collection.bounds.max, 20);
+
+ const s0 = collection.getSnapshotAt(scopedId, 1);
+ assert.isUndefined(s0);
+
+ const s1 = collection.getSnapshotAt(scopedId, 10);
+ assert.strictEqual(s1.args.foo, 1);
+
+ const s2 = collection.getSnapshotAt(scopedId, 15);
+ assert.strictEqual(s2.args.foo, 1);
+ assert.strictEqual(s1, s2);
+
+ const s3 = collection.getSnapshotAt(scopedId, 20);
+ assert.strictEqual(s3.args.foo, 2);
+ assert.strictEqual(s1.object, s3.object);
+
+ const s4 = collection.getSnapshotAt(scopedId, 25);
+ assert.strictEqual(s4, s3);
+ });
+
+ test('twoObjectsSharingOneID', function() {
+ const collection = new tr.model.ObjectCollection({});
+ const scopedId = new tr.model.ScopedId('ptr', '0x1000');
+ collection.idWasCreated(
+ scopedId, 'tr.e.cc', 'Frame', 10);
+ collection.idWasDeleted(
+ scopedId, 'tr.e.cc', 'Frame', 15);
+ collection.idWasCreated(
+ scopedId, 'skia', 'Picture', 20);
+ collection.idWasDeleted(
+ scopedId, 'skia', 'Picture', 25);
+
+ const frame = collection.getObjectInstanceAt(scopedId, 10);
+ assert.strictEqual(frame.category, 'tr.e.cc');
+ assert.strictEqual(frame.name, 'Frame');
+
+ const picture = collection.getObjectInstanceAt(scopedId, 20);
+ assert.strictEqual(picture.category, 'skia');
+ assert.strictEqual(picture.name, 'Picture');
+
+ const typeNames = Object.keys(collection.getAllInstancesByTypeName());
+ typeNames.sort();
+ assert.deepEqual(
+ ['Frame', 'Picture'],
+ typeNames);
+ assert.deepEqual(
+ [frame],
+ collection.getAllInstancesByTypeName().Frame);
+ assert.deepEqual(
+ [picture],
+ collection.getAllInstancesByTypeName().Picture);
+ });
+
+ test('createSnapDelete', function() {
+ const collection = new tr.model.ObjectCollection({});
+ const scopedId = new tr.model.ScopedId('ptr', '0x1000');
+ collection.idWasCreated(
+ scopedId, 'cat', 'Frame', 10);
+ collection.addSnapshot(
+ scopedId, 'cat', 'Frame', 10, {foo: 1});
+ collection.idWasDeleted(
+ scopedId, 'cat', 'Frame', 15);
+
+ collection.updateBounds();
+ assert.strictEqual(collection.bounds.min, 10);
+ assert.strictEqual(collection.bounds.max, 15);
+
+ const s10 = collection.getSnapshotAt(scopedId, 10);
+ const i10 = s10.objectInstance;
+ assert.strictEqual(i10.creationTs, 10);
+ assert.strictEqual(i10.deletionTs, 15);
+ });
+
+ test('boundsOnUndeletedObject', function() {
+ const collection = new tr.model.ObjectCollection({});
+ const scopedId = new tr.model.ScopedId('ptr', '0x1000');
+ collection.idWasCreated(
+ scopedId, 'cat', 'Frame', 10);
+ collection.addSnapshot(
+ scopedId, 'cat', 'Frame', 15, {foo: 1});
+
+ collection.updateBounds();
+ assert.strictEqual(10, collection.bounds.min);
+ assert.strictEqual(15, collection.bounds.max);
+ });
+
+ test('snapshotWithCustomBaseTypeThenDelete', function() {
+ const collection = new tr.model.ObjectCollection({});
+ const scopedId = new tr.model.ScopedId('ptr', '0x1000');
+ const s10 = collection.addSnapshot(
+ scopedId, 'cat', 'cc::PictureLayerImpl', 10, {}, 'cc::LayerImpl');
+ collection.idWasDeleted(
+ scopedId, 'cat', 'cc::LayerImpl', 15);
+ collection.updateBounds();
+ assert.strictEqual(10, collection.bounds.min);
+ assert.strictEqual(15, collection.bounds.max);
+ assert.strictEqual(s10.objectInstance.name, 'cc::PictureLayerImpl');
+ assert.strictEqual(s10.objectInstance.baseTypeName, 'cc::LayerImpl');
+ });
+
+ test('newWithSnapshotThatChangesBaseType', function() {
+ const collection = new tr.model.ObjectCollection({});
+ const scopedId = new tr.model.ScopedId('ptr', '0x1000');
+ const i10 = collection.idWasCreated(
+ scopedId, 'cat', 'cc::LayerImpl', 10);
+ const s15 = collection.addSnapshot(
+ scopedId, 'cat', 'cc::PictureLayerImpl', 15, {}, 'cc::LayerImpl');
+ collection.updateBounds();
+ assert.strictEqual(10, collection.bounds.min);
+ assert.strictEqual(15, collection.bounds.max);
+ assert.strictEqual(s15.objectInstance, i10);
+ assert.strictEqual(i10.name, 'cc::PictureLayerImpl');
+ assert.strictEqual(i10.baseTypeName, 'cc::LayerImpl');
+ });
+
+ test('deleteThenSnapshotWithCustomBase', function() {
+ const collection = new tr.model.ObjectCollection({});
+ const scopedId = new tr.model.ScopedId('ptr', '0x1000');
+ collection.idWasDeleted(
+ scopedId, 'cat', 'cc::LayerImpl', 10);
+ const s15 = collection.addSnapshot(
+ scopedId, 'cat', 'cc::PictureLayerImpl', 15, {}, 'cc::LayerImpl');
+ collection.updateBounds();
+ assert.strictEqual(10, collection.bounds.min);
+ assert.strictEqual(15, collection.bounds.max);
+ assert.strictEqual(s15.objectInstance.name, 'cc::PictureLayerImpl');
+ });
+
+ test('autoDelete', function() {
+ const collection = new tr.model.ObjectCollection({});
+ const scopedId = new tr.model.ScopedId('ptr', '0x1000');
+ collection.idWasCreated(
+ scopedId, 'cat', 'Frame', 10);
+ collection.addSnapshot(
+ scopedId, 'cat', 'Frame', 10, {foo: 1});
+ collection.autoDeleteObjects(15);
+
+ const s10 = collection.getSnapshotAt(scopedId, 10);
+ const i10 = s10.objectInstance;
+ assert.strictEqual(15, i10.deletionTs);
+ });
+
+ test('differentScopes', function() {
+ const collection = new tr.model.ObjectCollection({});
+ const scopedId1 = new tr.model.ScopedId('ptr', '0x1000');
+ const scopedId2 = new tr.model.ScopedId('cc', '0x1000');
+ collection.idWasCreated(
+ scopedId1, 'cat', 'ptr::object', 10);
+ collection.idWasDeleted(
+ scopedId1, 'cat', 'ptr::object', 15);
+ collection.idWasCreated(
+ scopedId2, 'cat', 'cc::object', 10);
+ collection.idWasDeleted(
+ scopedId2, 'cat', 'cc::object', 15);
+
+ let instance = collection.getObjectInstanceAt(scopedId1, 10);
+ assert.strictEqual(instance.name, 'ptr::object');
+
+ instance = collection.getObjectInstanceAt(scopedId2, 10);
+ assert.strictEqual(instance.name, 'cc::object');
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/object_instance.html b/chromium/third_party/catapult/tracing/tracing/model/object_instance.html
new file mode 100644
index 00000000000..659ed22ad11
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/object_instance.html
@@ -0,0 +1,213 @@
+<!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/range.html">
+<link rel="import" href="/tracing/base/utils.html">
+<link rel="import" href="/tracing/model/event.html">
+<link rel="import" href="/tracing/model/object_snapshot.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Provides the ObjectSnapshot and ObjectHistory classes.
+ */
+tr.exportTo('tr.model', function() {
+ const ObjectSnapshot = tr.model.ObjectSnapshot;
+
+ /**
+ * An object with a specific id, whose state has been snapshotted several
+ * times.
+ *
+ * @constructor
+ */
+ function ObjectInstance(
+ parent, scopedId, category, name, creationTs, opt_baseTypeName) {
+ tr.model.Event.call(this);
+ this.parent = parent;
+ this.scopedId = scopedId;
+ this.category = category;
+ this.baseTypeName = opt_baseTypeName ? opt_baseTypeName : name;
+ this.name = name;
+ this.creationTs = creationTs;
+ this.creationTsWasExplicit = false;
+ this.deletionTs = Number.MAX_VALUE;
+ this.deletionTsWasExplicit = false;
+ this.colorId = 0;
+ this.bounds = new tr.b.math.Range();
+ this.snapshots = [];
+ this.hasImplicitSnapshots = false;
+ }
+
+ ObjectInstance.prototype = {
+ __proto__: tr.model.Event.prototype,
+
+ get typeName() {
+ return this.name;
+ },
+
+ addBoundsToRange(range) {
+ range.addRange(this.bounds);
+ },
+
+ addSnapshot(ts, args, opt_name, opt_baseTypeName) {
+ if (ts < this.creationTs) {
+ throw new Error('Snapshots must be >= instance.creationTs');
+ }
+ if (ts >= this.deletionTs) {
+ throw new Error('Snapshots cannot be added after ' +
+ 'an objects deletion timestamp.');
+ }
+
+ let lastSnapshot;
+ if (this.snapshots.length > 0) {
+ lastSnapshot = this.snapshots[this.snapshots.length - 1];
+ if (lastSnapshot.ts === ts) {
+ throw new Error('Snapshots already exists at this time!');
+ }
+ if (ts < lastSnapshot.ts) {
+ throw new Error(
+ 'Snapshots must be added in increasing timestamp order');
+ }
+ }
+
+ // Update baseTypeName if needed.
+ if (opt_name &&
+ (this.name !== opt_name)) {
+ if (!opt_baseTypeName) {
+ throw new Error('Must provide base type name for name update');
+ }
+ if (this.baseTypeName !== opt_baseTypeName) {
+ throw new Error('Cannot update type name: base types dont match');
+ }
+ this.name = opt_name;
+ }
+
+ const snapshotConstructor =
+ tr.model.ObjectSnapshot.subTypes.getConstructor(
+ this.category, this.name);
+ const snapshot = new snapshotConstructor(this, ts, args);
+ this.snapshots.push(snapshot);
+ return snapshot;
+ },
+
+ wasDeleted(ts) {
+ let lastSnapshot;
+ if (this.snapshots.length > 0) {
+ lastSnapshot = this.snapshots[this.snapshots.length - 1];
+ if (lastSnapshot.ts > ts) {
+ throw new Error(
+ 'Instance cannot be deleted at ts=' +
+ ts + '. A snapshot exists that is older.');
+ }
+ }
+ this.deletionTs = ts;
+ this.deletionTsWasExplicit = true;
+ },
+
+ /**
+ * See ObjectSnapshot constructor notes on object initialization.
+ */
+ preInitialize() {
+ for (let i = 0; i < this.snapshots.length; i++) {
+ this.snapshots[i].preInitialize();
+ }
+ },
+
+ /**
+ * See ObjectSnapshot constructor notes on object initialization.
+ */
+ initialize() {
+ for (let i = 0; i < this.snapshots.length; i++) {
+ this.snapshots[i].initialize();
+ }
+ },
+
+ isAliveAt(ts) {
+ if (ts < this.creationTs && this.creationTsWasExplicit) {
+ return false;
+ }
+ if (ts > this.deletionTs) {
+ return false;
+ }
+
+ return true;
+ },
+
+ getSnapshotAt(ts) {
+ if (ts < this.creationTs) {
+ if (this.creationTsWasExplicit) {
+ throw new Error('ts must be within lifetime of this instance');
+ }
+ return this.snapshots[0];
+ }
+ if (ts > this.deletionTs) {
+ throw new Error('ts must be within lifetime of this instance');
+ }
+
+ const snapshots = this.snapshots;
+ const i = tr.b.findIndexInSortedIntervals(
+ snapshots,
+ function(snapshot) { return snapshot.ts; },
+ function(snapshot, i) {
+ if (i === snapshots.length - 1) {
+ return snapshots[i].objectInstance.deletionTs;
+ }
+ return snapshots[i + 1].ts - snapshots[i].ts;
+ },
+ ts);
+ if (i < 0) {
+ // Note, this is a little bit sketchy: this lets early ts point at the
+ // first snapshot, even before it is taken. We do this because raster
+ // tasks usually post before their tile snapshots are dumped. This may
+ // be a good line of code to re-visit if we start seeing strange and
+ // confusing object references showing up in the traces.
+ return this.snapshots[0];
+ }
+ if (i >= this.snapshots.length) {
+ return this.snapshots[this.snapshots.length - 1];
+ }
+ return this.snapshots[i];
+ },
+
+ updateBounds() {
+ this.bounds.reset();
+ this.bounds.addValue(this.creationTs);
+ if (this.deletionTs !== Number.MAX_VALUE) {
+ this.bounds.addValue(this.deletionTs);
+ } else if (this.snapshots.length > 0) {
+ this.bounds.addValue(this.snapshots[this.snapshots.length - 1].ts);
+ }
+ },
+
+ shiftTimestampsForward(amount) {
+ this.creationTs += amount;
+ if (this.deletionTs !== Number.MAX_VALUE) {
+ this.deletionTs += amount;
+ }
+ this.snapshots.forEach(function(snapshot) {
+ snapshot.ts += amount;
+ });
+ },
+
+ get userFriendlyName() {
+ return this.typeName + ' object ' + this.scopedId;
+ }
+ };
+
+ tr.model.EventRegistry.register(
+ ObjectInstance,
+ {
+ name: 'objectInstance',
+ pluralName: 'objectInstances'
+ });
+
+ return {
+ ObjectInstance,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/object_instance_test.html b/chromium/third_party/catapult/tracing/tracing/model/object_instance_test.html
new file mode 100644
index 00000000000..703e98d37c0
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/object_instance_test.html
@@ -0,0 +1,70 @@
+<!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/object_instance.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ test('getSnapshotAtWithImplicitCreation', function() {
+ const instance = new tr.model.ObjectInstance(
+ {}, '0x1000', 'cat', 'n', 10);
+ let s10 = instance.addSnapshot(10, 'a');
+ instance.addSnapshot(40, 'b');
+ instance.wasDeleted(60);
+
+ const s1 = instance.getSnapshotAt(1);
+ assert.strictEqual(s1, s10);
+
+ s10 = instance.getSnapshotAt(10);
+ assert.strictEqual(s10.args, 'a');
+ assert.strictEqual(instance.getSnapshotAt(15), s10);
+
+ const s40 = instance.getSnapshotAt(40);
+ assert.strictEqual(s40.args, 'b');
+ assert.strictEqual(instance.getSnapshotAt(50), s40);
+ assert.strictEqual(instance.getSnapshotAt(59.9), s40);
+ });
+
+ test('getSnapshotAtWithExplicitCreation', function() {
+ const instance = new tr.model.ObjectInstance(
+ {}, '0x1000', 'cat', 'n', 10);
+ instance.creationTsWasExplicit = true;
+ instance.addSnapshot(10, 'a');
+ instance.wasDeleted(60);
+
+ assert.throws(function() {
+ instance.getSnapshotAt(1);
+ });
+
+ const s10 = instance.getSnapshotAt(10);
+ assert.strictEqual(s10.args, 'a');
+ assert.strictEqual(instance.getSnapshotAt(15), s10);
+ });
+
+ test('getSnapshotBeforeFirstSnapshot', function() {
+ const instance = new tr.model.ObjectInstance(
+ {}, '0x1000', 'cat', 'n', 10);
+ const s15 = instance.addSnapshot(15, 'a');
+ instance.wasDeleted(40);
+
+ assert.strictEqual(instance.getSnapshotAt(10), s15);
+ });
+
+ test('getSnapshotAfterLastSnapshot', function() {
+ const instance = new tr.model.ObjectInstance(
+ {}, '0x1000', 'cat', 'n', 10);
+ const s15 = instance.addSnapshot(15, 'a');
+ instance.wasDeleted(40);
+
+ assert.strictEqual(instance.getSnapshotAt(20), s15);
+ });
+});
+</script>
+
diff --git a/chromium/third_party/catapult/tracing/tracing/model/object_snapshot.html b/chromium/third_party/catapult/tracing/tracing/model/object_snapshot.html
new file mode 100644
index 00000000000..09678f34a45
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/object_snapshot.html
@@ -0,0 +1,90 @@
+<!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.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.model', function() {
+ /**
+ * A snapshot of an object instance, at a given moment in time.
+ *
+ * Initialization of snapshots and instances is three phased:
+ *
+ * 1. Instances and snapshots are constructed. This happens during event
+ * importing. Little should be done here, because the object's data
+ * are still being used by the importer to reconstruct object references.
+ *
+ * 2. Instances and snapshtos are preinitialized. This happens after implicit
+ * objects have been found, but before any references have been found and
+ * switched to direct references. Thus, every snapshot stands on its own.
+ * This is a good time to do global field renaming and type conversion,
+ * e.g. recognizing domain-specific types and converting from C++ naming
+ * convention to JS.
+ *
+ * 3. Instances and snapshtos are initialized. At this point, {id_ref:
+ * '0x1000'} fields have been converted to snapshot references. This is a
+ * good time to generic initialization steps and argument verification.
+ *
+ * @constructor
+ */
+ function ObjectSnapshot(objectInstance, ts, args) {
+ tr.model.Event.call(this);
+ this.objectInstance = objectInstance;
+ this.ts = ts;
+ this.args = args;
+ }
+
+ ObjectSnapshot.prototype = {
+ __proto__: tr.model.Event.prototype,
+
+ /**
+ * See ObjectSnapshot constructor notes on object initialization.
+ */
+ preInitialize() {
+ },
+
+ /**
+ * See ObjectSnapshot constructor notes on object initialization.
+ */
+ initialize() {
+ },
+
+ /**
+ * Called when an object reference is resolved as this ObjectSnapshot.
+ * @param {Object} item The event (async slice, slice or object) containing
+ * the resolved reference.
+ * @param {Object} object The object directly containing the reference.
+ * @param {String} field The field name of the reference in |object|.
+ */
+ referencedAt(item, object, field) {
+ },
+
+ addBoundsToRange(range) {
+ range.addValue(this.ts);
+ },
+
+ get userFriendlyName() {
+ return 'Snapshot of ' + this.objectInstance.userFriendlyName + ' @ ' +
+ tr.b.Unit.byName.timeStampInMs.format(this.ts);
+ }
+ };
+
+ tr.model.EventRegistry.register(
+ ObjectSnapshot,
+ {
+ name: 'objectSnapshot',
+ pluralName: 'objectSnapshots'
+ });
+
+ return {
+ ObjectSnapshot,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/object_snapshot_test.html b/chromium/third_party/catapult/tracing/tracing/model/object_snapshot_test.html
new file mode 100644
index 00000000000..c026851e5aa
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/object_snapshot_test.html
@@ -0,0 +1,40 @@
+<!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/object_instance.html">
+<link rel="import" href="/tracing/model/object_snapshot.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ test('snapshotTypeRegistry', function() {
+ function MySnapshot() {
+ tr.model.ObjectSnapshot.apply(this, arguments);
+ this.myFoo = this.args.foo;
+ }
+
+ MySnapshot.prototype = {
+ __proto__: tr.model.ObjectSnapshot.prototype
+ };
+
+ const instance = new tr.model.ObjectInstance(
+ {}, '0x1000', 'cat', 'MySnapshot', 10);
+ try {
+ tr.model.ObjectSnapshot.subTypes.register(
+ MySnapshot,
+ {typeName: 'MySnapshot'});
+ const snapshot = instance.addSnapshot(15, {foo: 'bar'});
+ assert.instanceOf(snapshot, MySnapshot);
+ assert.strictEqual(snapshot.myFoo, 'bar');
+ } finally {
+ tr.model.ObjectSnapshot.subTypes.unregister(MySnapshot);
+ }
+ });
+});
+</script>
+
diff --git a/chromium/third_party/catapult/tracing/tracing/model/power_sample.html b/chromium/third_party/catapult/tracing/tracing/model/power_sample.html
new file mode 100644
index 00000000000..b9816f76159
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/power_sample.html
@@ -0,0 +1,71 @@
+<!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.html">
+<link rel="import" href="/tracing/model/event_registry.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.model', function() {
+ const Event = tr.model.Event;
+ const EventRegistry = tr.model.EventRegistry;
+
+ /**
+ * A sample that contains a power measurement (in W).
+ *
+ * @constructor
+ * @extends {Event}
+ */
+ function PowerSample(series, start, powerInW) {
+ Event.call(this);
+
+ this.series_ = series;
+ this.start_ = parseFloat(start);
+ this.powerInW_ = parseFloat(powerInW);
+ }
+
+ PowerSample.prototype = {
+ __proto__: Event.prototype,
+
+ get series() {
+ return this.series_;
+ },
+
+ get start() {
+ return this.start_;
+ },
+
+ set start(value) {
+ this.start_ = value;
+ },
+
+ get powerInW() {
+ return this.powerInW_;
+ },
+
+ set powerInW(value) {
+ this.powerInW_ = value;
+ },
+
+ addBoundsToRange(range) {
+ range.addValue(this.start);
+ }
+ };
+
+ EventRegistry.register(
+ PowerSample,
+ {
+ name: 'powerSample',
+ pluralName: 'powerSamples'
+ });
+
+ return {
+ PowerSample,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/power_sample_test.html b/chromium/third_party/catapult/tracing/tracing/model/power_sample_test.html
new file mode 100644
index 00000000000..ebed90bd2b5
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/power_sample_test.html
@@ -0,0 +1,51 @@
+<!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/model/model.html">
+<link rel="import" href="/tracing/model/power_sample.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const PowerSample = tr.model.PowerSample;
+
+ test('powerSample', function() {
+ const series = new tr.model.PowerSeries(new tr.Model().device);
+
+ const sample1 = new PowerSample(series, 0.0, 1000.0);
+ const sample2 = new PowerSample(series, 1.0, 2000.0);
+
+ assert.strictEqual(sample1.series, series);
+ assert.strictEqual(sample1.start, 0.0);
+ assert.strictEqual(sample1.powerInW, 1000.0);
+
+ assert.strictEqual(sample2.series, series);
+ assert.strictEqual(sample2.start, 1.0);
+ assert.strictEqual(sample2.powerInW, 2000.0);
+ });
+
+ test('addBoundsToRange', function() {
+ const series = new tr.model.PowerSeries(new tr.Model().device);
+
+ const sample1 = new PowerSample(series, 0.0, 1000.0);
+ const sample2 = new PowerSample(series, 1.0, 2000.0);
+
+ const range = new tr.b.math.Range();
+ sample1.addBoundsToRange(range);
+
+ assert.strictEqual(range.min, 0);
+ assert.strictEqual(range.max, 0);
+
+ sample2.addBoundsToRange(range);
+
+ assert.strictEqual(range.min, 0);
+ assert.strictEqual(range.max, 1);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/power_series.html b/chromium/third_party/catapult/tracing/tracing/model/power_series.html
new file mode 100644
index 00000000000..c496b1e4810
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/power_series.html
@@ -0,0 +1,129 @@
+<!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_scale.html">
+<link rel="import" href="/tracing/base/utils.html">
+<link rel="import" href="/tracing/model/event_container.html">
+<link rel="import" href="/tracing/model/power_sample.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.model', function() {
+ const PowerSample = tr.model.PowerSample;
+
+ /**
+ * A container holding a time series of power samples.
+ *
+ * @constructor
+ * @extends {EventContainer}
+ */
+ function PowerSeries(device) {
+ tr.model.EventContainer.call(this);
+
+ this.device_ = device;
+ this.samples_ = [];
+ }
+
+ PowerSeries.prototype = {
+ __proto__: tr.model.EventContainer.prototype,
+
+ get device() {
+ return this.device_;
+ },
+
+ get samples() {
+ return this.samples_;
+ },
+
+ get stableId() {
+ return this.device_.stableId + '.PowerSeries';
+ },
+
+ /**
+ * Adds a power sample to the series and returns it.
+ *
+ * Note: Samples must be added in chronological order.
+ */
+ addPowerSample(ts, val) {
+ const sample = new PowerSample(this, ts, val);
+ this.samples_.push(sample);
+ return sample;
+ },
+
+ /**
+ * Returns the total energy (in Joules) consumed between the specified
+ * start and end timestamps (in milliseconds).
+ */
+ getEnergyConsumedInJ(start, end) {
+ const measurementRange = tr.b.math.Range.fromExplicitRange(start, end);
+
+ let energyConsumedInJ = 0;
+ let startIndex = tr.b.findLowIndexInSortedArray(
+ this.samples, x => x.start, start) - 1;
+ const endIndex = tr.b.findLowIndexInSortedArray(
+ this.samples, x => x.start, end);
+
+ if (startIndex < 0) {
+ startIndex = 0;
+ }
+
+ for (let i = startIndex; i < endIndex; i++) {
+ const sample = this.samples[i];
+ const nextSample = this.samples[i + 1];
+
+ const sampleRange = new tr.b.math.Range();
+ sampleRange.addValue(sample.start);
+ sampleRange.addValue(nextSample ? nextSample.start : sample.start);
+
+ const intersectionRangeInMs = measurementRange.findIntersection(
+ sampleRange);
+
+ const durationInS = tr.b.convertUnit(intersectionRangeInMs.duration,
+ tr.b.UnitPrefixScale.METRIC.MILLI,
+ tr.b.UnitPrefixScale.METRIC.NONE);
+
+ energyConsumedInJ += durationInS * sample.powerInW;
+ }
+
+ return energyConsumedInJ;
+ },
+
+ getSamplesWithinRange(start, end) {
+ const startIndex = tr.b.findLowIndexInSortedArray(
+ this.samples, x => x.start, start);
+ const endIndex = tr.b.findLowIndexInSortedArray(
+ this.samples, x => x.start, end);
+ return this.samples.slice(startIndex, endIndex);
+ },
+
+ shiftTimestampsForward(amount) {
+ for (let i = 0; i < this.samples_.length; ++i) {
+ this.samples_[i].start += amount;
+ }
+ },
+
+ updateBounds() {
+ this.bounds.reset();
+
+ if (this.samples_.length === 0) return;
+
+ this.bounds.addValue(this.samples_[0].start);
+ this.bounds.addValue(this.samples_[this.samples_.length - 1].start);
+ },
+
+ * childEvents() {
+ yield* this.samples_;
+ },
+ };
+
+ return {
+ PowerSeries,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/power_series_test.html b/chromium/third_party/catapult/tracing/tracing/model/power_series_test.html
new file mode 100644
index 00000000000..aa1e1c46d54
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/power_series_test.html
@@ -0,0 +1,151 @@
+<!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/device.html">
+<link rel="import" href="/tracing/model/model.html">
+<link rel="import" href="/tracing/model/power_series.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const Model = tr.Model;
+ const PowerSeries = tr.model.PowerSeries;
+
+ test('stableId', function() {
+ const device = { stableId: 'test' };
+ const series = new PowerSeries(device);
+
+ assert.strictEqual(series.stableId, 'test.PowerSeries');
+ });
+
+ test('device', function() {
+ const device = new tr.model.Device(new Model());
+ const series = new PowerSeries(device);
+
+ assert.strictEqual(series.device, device);
+ });
+
+ test('addPowerSample', function() {
+ const series = new PowerSeries(new Model().device);
+
+ assert.strictEqual(series.samples.length, 0);
+
+ const sample1 = series.addPowerSample(0, 1);
+ const sample2 = series.addPowerSample(1, 2);
+
+ assert.deepEqual(series.samples, [sample1, sample2]);
+ });
+
+ test('getEnergyConsumed_oneInterval', function() {
+ const series = new PowerSeries(new Model().device);
+ series.addPowerSample(0, 1);
+ series.addPowerSample(1000, 2);
+
+ assert.strictEqual(series.getEnergyConsumedInJ(0, 1000), 1);
+ });
+
+ test('getEnergyConsumed_twoIntervals', function() {
+ const series = new PowerSeries(new Model().device);
+ series.addPowerSample(0, 1);
+ series.addPowerSample(1000, 2);
+ series.addPowerSample(2000, 2);
+
+ assert.strictEqual(series.getEnergyConsumedInJ(0, 2000), 3);
+ });
+
+ test('getEnergyConsumed_oneSample', function() {
+ const series = new PowerSeries(new Model().device);
+ series.addPowerSample(1000, 1);
+
+ assert.strictEqual(series.getEnergyConsumedInJ(0, 2000), 0);
+ });
+
+ test('getEnergyConsumed_samplesAfterStart', function() {
+ const series = new PowerSeries(new Model().device);
+ series.addPowerSample(1000, 1);
+ series.addPowerSample(2000, 2);
+
+ assert.strictEqual(series.getEnergyConsumedInJ(0, 2000), 1);
+ });
+
+ test('getEnergyConsumed_extraSamplesBeforeStart', function() {
+ const series = new PowerSeries(new Model().device);
+ series.addPowerSample(0, 10);
+ series.addPowerSample(1000, 1);
+ series.addPowerSample(2000, 1);
+ series.addPowerSample(3000, 1);
+
+ assert.strictEqual(series.getEnergyConsumedInJ(2000, 4000), 1);
+ });
+
+ test('getEnergyConsumed_extraSamplesAfterEnd', function() {
+ const series = new PowerSeries(new Model().device);
+ series.addPowerSample(0, 1);
+ series.addPowerSample(1000, 1);
+ series.addPowerSample(2000, 1);
+ series.addPowerSample(3000, 10);
+
+ assert.strictEqual(series.getEnergyConsumedInJ(0, 2000), 2);
+ });
+
+ test('shiftTimestampsForward', function() {
+ const series = new PowerSeries(new Model().device);
+
+ series.addPowerSample(0, 1);
+ series.addPowerSample(1, 2);
+
+ series.shiftTimestampsForward(2);
+
+ assert.strictEqual(series.samples[0].start, 2);
+ assert.strictEqual(series.samples[1].start, 3);
+
+ series.shiftTimestampsForward(-4);
+
+ assert.strictEqual(series.samples[0].start, -2);
+ assert.strictEqual(series.samples[1].start, -1);
+ });
+
+
+ test('updateBounds', function() {
+ const series = new PowerSeries(new Model().device);
+
+ series.addPowerSample(0, 1);
+ series.addPowerSample(1, 2);
+ series.updateBounds();
+
+ assert.strictEqual(series.bounds.min, 0);
+ assert.strictEqual(series.bounds.max, 1);
+
+ series.addPowerSample(4, 3);
+ series.updateBounds();
+
+ assert.strictEqual(series.bounds.min, 0);
+ assert.strictEqual(series.bounds.max, 4);
+ });
+
+ test('childEvents_empty', function() {
+ const series = new PowerSeries(new Model().device);
+ const eventsInSeries = [];
+ for (const event of series.childEvents()) {
+ eventsInSeries.push(event);
+ }
+ assert.deepEqual(eventsInSeries, []);
+ });
+
+ test('childEvents_nonempty', function() {
+ const series = new PowerSeries(new Model().device);
+ const sample1 = series.addPowerSample(0, 1);
+ const sample2 = series.addPowerSample(1, 2);
+ const eventsInSeries = [];
+ for (const event of series.childEvents()) {
+ eventsInSeries.push(event);
+ }
+ assert.deepEqual(eventsInSeries, [sample1, sample2]);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/process.html b/chromium/third_party/catapult/tracing/tracing/model/process.html
new file mode 100644
index 00000000000..facaa3b1923
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/process.html
@@ -0,0 +1,175 @@
+<!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/process_base.html">
+<link rel="import" href="/tracing/model/process_memory_dump.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Provides the Process class.
+ */
+tr.exportTo('tr.model', function() {
+ const ProcessBase = tr.model.ProcessBase;
+ const ProcessInstantEvent = tr.model.ProcessInstantEvent;
+ const Frame = tr.model.Frame;
+ const ProcessMemoryDump = tr.model.ProcessMemoryDump;
+
+ /**
+ * The Process represents a single userland process in the
+ * trace.
+ * @constructor
+ */
+ function Process(model, pid) {
+ if (model === undefined) {
+ throw new Error('model must be provided');
+ }
+ if (pid === undefined) {
+ throw new Error('pid must be provided');
+ }
+ tr.model.ProcessBase.call(this, model);
+ this.pid = pid;
+ this.name = undefined;
+ this.labels = [];
+ this.uptime_seconds = 0;
+ this.instantEvents = [];
+ this.memoryDumps = [];
+ this.frames = [];
+ this.activities = [];
+ }
+
+ /**
+ * Comparison between processes that orders by pid.
+ */
+ Process.compare = function(x, y) {
+ let tmp = tr.model.ProcessBase.compare(x, y);
+ if (tmp) return tmp;
+
+ if (x.name !== undefined) {
+ if (y.name !== undefined) {
+ tmp = x.name.localeCompare(y.name);
+ } else {
+ tmp = -1;
+ }
+ } else if (y.name !== undefined) {
+ tmp = 1;
+ }
+ if (tmp) return tmp;
+
+ tmp = tr.b.compareArrays(x.labels, y.labels,
+ function(x, y) { return x.localeCompare(y); });
+ if (tmp) return tmp;
+
+ return x.pid - y.pid;
+ };
+
+ Process.prototype = {
+ __proto__: tr.model.ProcessBase.prototype,
+
+ get stableId() {
+ return this.pid;
+ },
+
+ compareTo(that) {
+ return Process.compare(this, that);
+ },
+
+ * childEvents() {
+ yield* ProcessBase.prototype.childEvents.call(this);
+ yield* this.instantEvents;
+ yield* this.frames;
+ yield* this.memoryDumps;
+ },
+
+ addLabelIfNeeded(labelName) {
+ for (let i = 0; i < this.labels.length; i++) {
+ if (this.labels[i] === labelName) return;
+ }
+ this.labels.push(labelName);
+ },
+
+ get userFriendlyName() {
+ let res;
+ if (this.name) {
+ res = this.name + ' (pid ' + this.pid + ')';
+ } else {
+ res = 'Process ' + this.pid;
+ }
+ if (this.labels.length) {
+ res += ': ' + this.labels.join(', ');
+ }
+ if (this.uptime_seconds) {
+ res += ', uptime:' + this.uptime_seconds + 's';
+ }
+ return res;
+ },
+
+ get userFriendlyDetails() {
+ if (this.name) {
+ return this.name + ' (pid ' + this.pid + ')';
+ }
+ return 'pid: ' + this.pid;
+ },
+
+ getSettingsKey() {
+ if (!this.name) return undefined;
+ if (!this.labels.length) return 'processes.' + this.name;
+ return 'processes.' + this.name + '.' + this.labels.join('.');
+ },
+
+ shiftTimestampsForward(amount) {
+ for (let i = 0; i < this.instantEvents.length; i++) {
+ this.instantEvents[i].start += amount;
+ }
+
+ for (let i = 0; i < this.frames.length; i++) {
+ this.frames[i].shiftTimestampsForward(amount);
+ }
+
+ for (let i = 0; i < this.memoryDumps.length; i++) {
+ this.memoryDumps[i].shiftTimestampsForward(amount);
+ }
+
+ for (let i = 0; i < this.activities.length; i++) {
+ this.activities[i].shiftTimestampsForward(amount);
+ }
+
+ tr.model.ProcessBase.prototype
+ .shiftTimestampsForward.apply(this, arguments);
+ },
+
+ updateBounds() {
+ tr.model.ProcessBase.prototype.updateBounds.apply(this);
+
+ for (let i = 0; i < this.frames.length; i++) {
+ this.frames[i].addBoundsToRange(this.bounds);
+ }
+
+ for (let i = 0; i < this.memoryDumps.length; i++) {
+ this.memoryDumps[i].addBoundsToRange(this.bounds);
+ }
+
+ for (let i = 0; i < this.activities.length; i++) {
+ this.activities[i].addBoundsToRange(this.bounds);
+ }
+ },
+
+ sortMemoryDumps() {
+ this.memoryDumps.sort(function(x, y) {
+ return x.start - y.start;
+ });
+ tr.model.ProcessMemoryDump.hookUpMostRecentVmRegionsLinks(
+ this.memoryDumps);
+ }
+ };
+
+ return {
+ Process,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/process_base.html b/chromium/third_party/catapult/tracing/tracing/model/process_base.html
new file mode 100644
index 00000000000..8ba031a764c
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/process_base.html
@@ -0,0 +1,244 @@
+<!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/guid.html">
+<link rel="import" href="/tracing/base/math/range.html">
+<link rel="import" href="/tracing/model/counter.html">
+<link rel="import" href="/tracing/model/event_container.html">
+<link rel="import" href="/tracing/model/object_collection.html">
+<link rel="import" href="/tracing/model/thread.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Provides the ProcessBase class.
+ */
+tr.exportTo('tr.model', function() {
+ const Thread = tr.model.Thread;
+ const Counter = tr.model.Counter;
+
+ /**
+ * The ProcessBase is a partial base class, upon which Kernel
+ * and Process are built.
+ *
+ * @constructor
+ * @extends {tr.model.EventContainer}
+ */
+ function ProcessBase(model) {
+ if (!model) {
+ throw new Error('Must provide a model');
+ }
+ tr.model.EventContainer.call(this);
+ this.model = model;
+ this.threads = {};
+ this.counters = {};
+ this.objects = new tr.model.ObjectCollection(this);
+ this.sortIndex = 0;
+ }
+
+ ProcessBase.compare = function(x, y) {
+ return x.sortIndex - y.sortIndex;
+ };
+
+ ProcessBase.prototype = {
+ __proto__: tr.model.EventContainer.prototype,
+
+ get stableId() {
+ throw new Error('Not implemented');
+ },
+
+ * childEventContainers() {
+ yield* Object.values(this.threads);
+ yield* Object.values(this.counters);
+ yield this.objects;
+ },
+
+ iterateAllPersistableObjects(cb) {
+ cb(this);
+ for (const tid in this.threads) {
+ this.threads[tid].iterateAllPersistableObjects(cb);
+ }
+ },
+
+ /**
+ * Gets the number of threads in this process.
+ */
+ get numThreads() {
+ let n = 0;
+ for (const p in this.threads) {
+ n++;
+ }
+ return n;
+ },
+
+ /**
+ * Shifts all the timestamps inside this process forward by the amount
+ * specified.
+ */
+ shiftTimestampsForward(amount) {
+ for (const child of this.childEventContainers()) {
+ child.shiftTimestampsForward(amount);
+ }
+ },
+
+ /**
+ * Closes any open slices.
+ */
+ autoCloseOpenSlices() {
+ for (const tid in this.threads) {
+ const thread = this.threads[tid];
+ thread.autoCloseOpenSlices();
+ }
+ },
+
+ autoDeleteObjects(maxTimestamp) {
+ this.objects.autoDeleteObjects(maxTimestamp);
+ },
+
+ /**
+ * Called by the model after finalizing imports,
+ * but before joining refs.
+ */
+ preInitializeObjects() {
+ this.objects.preInitializeAllObjects();
+ },
+
+ /**
+ * Called by the model after joining refs.
+ */
+ initializeObjects() {
+ this.objects.initializeAllObjects();
+ },
+
+ /**
+ * Merge slices from the kernel with those from userland for each thread.
+ */
+ mergeKernelWithUserland() {
+ for (const tid in this.threads) {
+ const thread = this.threads[tid];
+ thread.mergeKernelWithUserland();
+ }
+ },
+
+ updateBounds() {
+ this.bounds.reset();
+ for (const tid in this.threads) {
+ this.threads[tid].updateBounds();
+ this.bounds.addRange(this.threads[tid].bounds);
+ }
+ for (const id in this.counters) {
+ this.counters[id].updateBounds();
+ this.bounds.addRange(this.counters[id].bounds);
+ }
+ this.objects.updateBounds();
+ this.bounds.addRange(this.objects.bounds);
+ },
+
+ addCategoriesToDict(categoriesDict) {
+ for (const tid in this.threads) {
+ this.threads[tid].addCategoriesToDict(categoriesDict);
+ }
+ for (const id in this.counters) {
+ categoriesDict[this.counters[id].category] = true;
+ }
+ this.objects.addCategoriesToDict(categoriesDict);
+ },
+
+ findAllThreadsMatching(predicate, opt_this) {
+ const threads = [];
+ for (const tid in this.threads) {
+ const thread = this.threads[tid];
+ if (predicate.call(opt_this, thread)) {
+ threads.push(thread);
+ }
+ }
+ return threads;
+ },
+
+ /**
+ * @param {String} The name of the thread to find.
+ * @return {Array} An array of all the matched threads.
+ */
+ findAllThreadsNamed(name) {
+ const threads = this.findAllThreadsMatching(function(thread) {
+ if (!thread.name) return false;
+ return thread.name === name;
+ });
+ return threads;
+ },
+
+ findAtMostOneThreadNamed(name) {
+ const threads = this.findAllThreadsNamed(name);
+ if (threads.length === 0) return undefined;
+ if (threads.length > 1) {
+ throw new Error('Expected no more than one ' + name);
+ }
+ return threads[0];
+ },
+
+ /**
+ * Removes threads from the process that are fully empty.
+ */
+ pruneEmptyContainers() {
+ const threadsToKeep = {};
+ for (const tid in this.threads) {
+ const thread = this.threads[tid];
+ if (!thread.isEmpty) {
+ threadsToKeep[tid] = thread;
+ }
+ }
+ this.threads = threadsToKeep;
+ },
+
+ /**
+ * @return {TimelineThread} The thread identified by tid on this process,
+ * or undefined if it doesn't exist.
+ */
+ getThread(tid) {
+ return this.threads[tid];
+ },
+
+ /**
+ * @return {TimelineThread} The thread identified by tid on this process,
+ * creating it if it doesn't exist.
+ */
+ getOrCreateThread(tid) {
+ if (!this.threads[tid]) {
+ this.threads[tid] = new Thread(this, tid);
+ }
+ return this.threads[tid];
+ },
+
+ /**
+ * @return {Counter} The counter on this process with the given
+ * category/name combination, creating it if it doesn't exist.
+ */
+ getOrCreateCounter(cat, name) {
+ const id = cat + '.' + name;
+ if (!this.counters[id]) {
+ this.counters[id] = new Counter(this, id, cat, name);
+ }
+ return this.counters[id];
+ },
+
+ getSettingsKey() {
+ throw new Error('Not implemented');
+ },
+
+ createSubSlices() {
+ for (const tid in this.threads) {
+ this.threads[tid].createSubSlices();
+ }
+ }
+ };
+
+ return {
+ ProcessBase,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/process_memory_dump.html b/chromium/third_party/catapult/tracing/tracing/model/process_memory_dump.html
new file mode 100644
index 00000000000..1c9248e6eaf
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/process_memory_dump.html
@@ -0,0 +1,247 @@
+<!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/model/container_memory_dump.html">
+<link rel="import" href="/tracing/model/memory_allocator_dump.html">
+<link rel="import" href="/tracing/model/vm_region.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Provides the ProcessMemoryDump class.
+ */
+tr.exportTo('tr.model', function() {
+ // Names of MemoryAllocatorDump(s) from which tracing overhead should be
+ // discounted.
+ const DISCOUNTED_ALLOCATOR_NAMES = ['winheap', 'malloc'];
+
+ // The path to where the tracing overhead dump should be added to the
+ // winheap/malloc allocator dump tree.
+ const TRACING_OVERHEAD_PATH = ['allocated_objects', 'tracing_overhead'];
+
+ const SIZE_NUMERIC_NAME = tr.model.MemoryAllocatorDump.SIZE_NUMERIC_NAME;
+ const RESIDENT_SIZE_NUMERIC_NAME =
+ tr.model.MemoryAllocatorDump.RESIDENT_SIZE_NUMERIC_NAME;
+
+ function getSizeNumericValue(dump, sizeNumericName) {
+ const sizeNumeric = dump.numerics[sizeNumericName];
+ if (sizeNumeric === undefined) return 0;
+ return sizeNumeric.value;
+ }
+
+ /**
+ * The ProcessMemoryDump represents a memory dump of a single process.
+ * @constructor
+ */
+ function ProcessMemoryDump(globalMemoryDump, process, start) {
+ tr.model.ContainerMemoryDump.call(this, start);
+ this.process = process;
+ this.globalMemoryDump = globalMemoryDump;
+
+ // Process memory totals (optional object) with the following fields (also
+ // optional):
+ // - residentBytes: Total resident bytes (number)
+ // - peakResidentBytes: Peak resident bytes (number)
+ // - arePeakResidentBytesResettable: Flag whether peak resident bytes are
+ // resettable (boolean)
+ // - privateFootprintBytes: Private footprint bytes (number)
+ // - platformSpecific: Map from OS-specific total names (string) to sizes
+ // (number)
+ this.totals = undefined;
+
+ this.vmRegions = undefined;
+
+ // Map from allocator names to heap dumps.
+ this.heapDumps = undefined;
+
+ this.tracingOverheadOwnershipSetUp_ = false;
+ this.tracingOverheadDiscountedFromVmRegions_ = false;
+ }
+
+ ProcessMemoryDump.prototype = {
+ __proto__: tr.model.ContainerMemoryDump.prototype,
+
+ get userFriendlyName() {
+ return 'Process memory dump at ' +
+ tr.b.Unit.byName.timeStampInMs.format(this.start);
+ },
+
+ get containerName() {
+ return this.process.userFriendlyName;
+ },
+
+ get processMemoryDumps() {
+ const dumps = {};
+ dumps[this.process.pid] = this;
+ return dumps;
+ },
+
+ get hasOwnVmRegions() {
+ return this.vmRegions !== undefined;
+ },
+
+ setUpTracingOverheadOwnership(opt_model) {
+ // Make sure that calling this method twice won't lead to
+ // 'double-discounting'.
+ if (this.tracingOverheadOwnershipSetUp_) return;
+
+ this.tracingOverheadOwnershipSetUp_ = true;
+
+ const tracingDump = this.getMemoryAllocatorDumpByFullName('tracing');
+ if (tracingDump === undefined || tracingDump.owns !== undefined) {
+ // The tracing dump either doesn't exist, or it already owns another
+ // dump.
+ return;
+ }
+
+ if (tracingDump.owns !== undefined) return;
+
+ // Add an ownership link from tracing to
+ // malloc/allocated_objects/tracing_overhead or
+ // winheap/allocated_objects/tracing_overhead.
+ const hasDiscountedFromAllocatorDumps = DISCOUNTED_ALLOCATOR_NAMES.some(
+ function(allocatorName) {
+ // First check if the allocator root exists.
+ const allocatorDump = this.getMemoryAllocatorDumpByFullName(
+ allocatorName);
+ if (allocatorDump === undefined) {
+ return false; // Allocator doesn't exist, try another one.
+ }
+
+ let nextPathIndex = 0;
+ let currentDump = allocatorDump;
+ let currentFullName = allocatorName;
+
+ // Descend from the root towards tracing_overhead as long as the
+ // dumps on the path exist.
+ for (; nextPathIndex < TRACING_OVERHEAD_PATH.length;
+ nextPathIndex++) {
+ const childFullName = currentFullName + '/' +
+ TRACING_OVERHEAD_PATH[nextPathIndex];
+ const childDump = this.getMemoryAllocatorDumpByFullName(
+ childFullName);
+ if (childDump === undefined) break;
+
+ currentDump = childDump;
+ currentFullName = childFullName;
+ }
+
+ // Create the missing descendant dumps on the path from the root
+ // towards tracing_overhead.
+ for (; nextPathIndex < TRACING_OVERHEAD_PATH.length;
+ nextPathIndex++) {
+ const childFullName = currentFullName + '/' +
+ TRACING_OVERHEAD_PATH[nextPathIndex];
+ const childDump = new tr.model.MemoryAllocatorDump(
+ currentDump.containerMemoryDump, childFullName);
+ childDump.parent = currentDump;
+ currentDump.children.push(childDump);
+
+ currentFullName = childFullName;
+ currentDump = childDump;
+ }
+
+ // Add the ownership link.
+ const ownershipLink =
+ new tr.model.MemoryAllocatorDumpLink(tracingDump, currentDump);
+ tracingDump.owns = ownershipLink;
+ currentDump.ownedBy.push(ownershipLink);
+ return true;
+ }, this);
+
+ // Force rebuilding the memory allocator dump index (if we've just added
+ // a new memory allocator dump).
+ if (hasDiscountedFromAllocatorDumps) {
+ this.forceRebuildingMemoryAllocatorDumpByFullNameIndex();
+ }
+ },
+
+ discountTracingOverheadFromVmRegions(opt_model) {
+ // Make sure that calling this method twice won't lead to
+ // 'double-discounting'.
+ if (this.tracingOverheadDiscountedFromVmRegions_) return;
+ this.tracingOverheadDiscountedFromVmRegions_ = true;
+
+ const tracingDump = this.getMemoryAllocatorDumpByFullName('tracing');
+ if (tracingDump === undefined) return;
+
+ const discountedSize =
+ getSizeNumericValue(tracingDump, SIZE_NUMERIC_NAME);
+ const discountedResidentSize =
+ getSizeNumericValue(tracingDump, RESIDENT_SIZE_NUMERIC_NAME);
+
+ if (discountedSize <= 0 && discountedResidentSize <= 0) return;
+
+ // Subtract the tracing size from the totals.
+ if (this.totals !== undefined) {
+ if (this.totals.residentBytes !== undefined) {
+ this.totals.residentBytes -= discountedResidentSize;
+ }
+ if (this.totals.peakResidentBytes !== undefined) {
+ this.totals.peakResidentBytes -= discountedResidentSize;
+ }
+ }
+
+ // Subtract the tracing size from VM regions. More precisely, subtract
+ // tracing resident_size from byte stats (private dirty and PSS) and
+ // tracing size from virtual size by injecting a fake VM region with
+ // negative values.
+ if (this.vmRegions !== undefined) {
+ const hasSizeInBytes = this.vmRegions.sizeInBytes !== undefined;
+ const hasPrivateDirtyResident =
+ this.vmRegions.byteStats.privateDirtyResident !== undefined;
+ const hasProportionalResident =
+ this.vmRegions.byteStats.proportionalResident !== undefined;
+
+ if ((hasSizeInBytes && discountedSize > 0) ||
+ ((hasPrivateDirtyResident || hasProportionalResident) &&
+ discountedResidentSize > 0)) {
+ const byteStats = {};
+ if (hasPrivateDirtyResident) {
+ byteStats.privateDirtyResident = -discountedResidentSize;
+ }
+ if (hasProportionalResident) {
+ byteStats.proportionalResident = -discountedResidentSize;
+ }
+ this.vmRegions.addRegion(tr.model.VMRegion.fromDict({
+ mappedFile: '[discounted tracing overhead]',
+ sizeInBytes: hasSizeInBytes ? -discountedSize : undefined,
+ byteStats
+ }));
+ }
+ }
+ }
+ };
+
+ ProcessMemoryDump.hookUpMostRecentVmRegionsLinks = function(processDumps) {
+ let mostRecentVmRegions = undefined;
+
+ processDumps.forEach(function(processDump) {
+ // Update the most recent VM regions from the current dump.
+ if (processDump.vmRegions !== undefined) {
+ mostRecentVmRegions = processDump.vmRegions;
+ }
+
+ // Set the most recent VM regions of the current dump.
+ processDump.mostRecentVmRegions = mostRecentVmRegions;
+ });
+ };
+
+ tr.model.EventRegistry.register(
+ ProcessMemoryDump,
+ {
+ name: 'processMemoryDump',
+ pluralName: 'processMemoryDumps'
+ });
+
+ return {
+ ProcessMemoryDump,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/process_memory_dump_test.html b/chromium/third_party/catapult/tracing/tracing/model/process_memory_dump_test.html
new file mode 100644
index 00000000000..3d507e879c9
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/process_memory_dump_test.html
@@ -0,0 +1,561 @@
+<!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/core/test_utils.html">
+<link rel="import" href="/tracing/model/global_memory_dump.html">
+<link rel="import" href="/tracing/model/memory_dump_test_utils.html">
+<link rel="import" href="/tracing/model/model.html">
+<link rel="import" href="/tracing/model/process_memory_dump.html">
+<link rel="import" href="/tracing/model/vm_region.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ 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 unitlessNumber_smallerIsBetter =
+ tr.b.Unit.byName.unitlessNumber_smallerIsBetter;
+ const newAllocatorDump = tr.model.MemoryDumpTestUtils.newAllocatorDump;
+ const addChildDump = tr.model.MemoryDumpTestUtils.addChildDump;
+ const addOwnershipLink = tr.model.MemoryDumpTestUtils.addOwnershipLink;
+ const checkDumpNumericsAndDiagnostics =
+ tr.model.MemoryDumpTestUtils.checkDumpNumericsAndDiagnostics;
+ const checkVMRegions = tr.model.MemoryDumpTestUtils.checkVMRegions;
+
+ function createClassificationNode(opt_sizeInBytes, opt_byteStats) {
+ const node = new VMRegionClassificationNode();
+ if (opt_sizeInBytes !== undefined || opt_byteStats !== undefined) {
+ node.addRegion(VMRegion.fromDict({
+ mappedFile: 'mock.so',
+ sizeInBytes: opt_sizeInBytes,
+ byteStats: opt_byteStats
+ }));
+ }
+ return node;
+ }
+
+ function createProcessMemoryDump(timestamp, model) {
+ const gmd = new GlobalMemoryDump(model, timestamp);
+ model.globalMemoryDumps.push(gmd);
+ const p = model.getOrCreateProcess(123);
+ const pmd = new ProcessMemoryDump(gmd, p, timestamp + 1);
+ gmd.processMemoryDumps[123] = pmd;
+ p.memoryDumps.push(pmd);
+ return pmd;
+ }
+
+ function createFinalizedProcessMemoryDump(timestamp, opt_createdCallback) {
+ return createFinalizedProcessMemoryDumps([timestamp], function(pmds) {
+ if (opt_createdCallback !== undefined) {
+ opt_createdCallback(pmds[0]);
+ }
+ })[0];
+ }
+
+ function createFinalizedProcessMemoryDumps(timestamps, createdCallback) {
+ const model = tr.c.TestUtils.newModel(function(model) {
+ const pmds = timestamps.map(function(timestamp) {
+ return createProcessMemoryDump(timestamp, model);
+ });
+ createdCallback(pmds);
+ });
+ const pmds = model.getProcess(123).memoryDumps;
+ assert.lengthOf(pmds, timestamps.length);
+ return pmds;
+ }
+
+ test('processMemoryDumps', function() {
+ const pmd = createFinalizedProcessMemoryDump(42);
+ const pmds = pmd.processMemoryDumps;
+ assert.lengthOf(Object.keys(pmds), 1);
+ assert.strictEqual(pmds[123], pmd);
+ });
+
+ test('hookUpMostRecentVmRegionsLinks_emptyArray', function() {
+ const dumps = [];
+ ProcessMemoryDump.hookUpMostRecentVmRegionsLinks(dumps);
+ assert.lengthOf(dumps, 0);
+ });
+
+ test('hookUpMostRecentVmRegionsLinks_nonEmptyArray', function() {
+ const m = new tr.Model();
+
+ // A dump with no VM regions or allocator dumps.
+ const dump1 = createProcessMemoryDump(1, m);
+
+ // A dump with VM regions and malloc and Oilpan allocator dumps.
+ const dump2 = createProcessMemoryDump(2, m);
+ dump2.vmRegions = createClassificationNode();
+ dump2.memoryAllocatorDumps = [
+ newAllocatorDump(dump2, 'oilpan', {numerics: {
+ size: 1024,
+ objects_count: new Scalar(unitlessNumber_smallerIsBetter, 7),
+ inner_size: 768
+ }}),
+ newAllocatorDump(dump2, 'v8', {numerics: {
+ size: 2048,
+ objects_count: new Scalar(unitlessNumber_smallerIsBetter, 15),
+ inner_size: 1999
+ }})
+ ];
+
+ // A dump with malloc and V8 allocator dumps.
+ const dump3 = createProcessMemoryDump(3, m);
+ dump3.memoryAllocatorDumps = [
+ newAllocatorDump(dump3, 'malloc', {numerics: {
+ size: 1024,
+ objects_count: new Scalar(unitlessNumber_smallerIsBetter, 7),
+ inner_size: 768
+ }}),
+ newAllocatorDump(dump3, 'v8', {numerics: {
+ size: 2048,
+ objects_count: new Scalar(unitlessNumber_smallerIsBetter, 15),
+ inner_size: 1999
+ }})
+ ];
+
+ // A dump with VM regions.
+ const dump4 = createProcessMemoryDump(4, m);
+ dump4.vmRegions = createClassificationNode();
+
+ const dumps = [dump1, dump2, dump3, dump4];
+ ProcessMemoryDump.hookUpMostRecentVmRegionsLinks(dumps);
+
+ assert.lengthOf(dumps, 4);
+
+ assert.strictEqual(dumps[0], dump1);
+ assert.isUndefined(dump1.mostRecentVmRegions);
+
+ assert.strictEqual(dumps[1], dump2);
+ assert.strictEqual(dump2.mostRecentVmRegions, dump2.vmRegions);
+
+ assert.strictEqual(dumps[2], dump3);
+ assert.strictEqual(dump3.mostRecentVmRegions, dump2.vmRegions);
+
+ assert.strictEqual(dumps[3], dump4);
+ assert.strictEqual(dump4.mostRecentVmRegions, dump4.vmRegions);
+ });
+
+ test('checkDiscountTracingOverhead_undefinedFields', function() {
+ const pmd = createFinalizedProcessMemoryDump(42, function(pmd) {
+ pmd.memoryAllocatorDumps = [
+ newAllocatorDump(pmd, 'v8', {numerics: {size: 2048}}),
+ newAllocatorDump(pmd, 'tracing', {numerics: {size: 1024}})
+ ];
+ });
+
+ assert.isUndefined(pmd.totals);
+ assert.isUndefined(pmd.vmRegions);
+
+ const v8Dump = pmd.getMemoryAllocatorDumpByFullName('v8');
+ checkDumpNumericsAndDiagnostics(v8Dump, {
+ size: 2048,
+ effective_size: 2048
+ }, {});
+
+ const tracingDump = pmd.getMemoryAllocatorDumpByFullName('tracing');
+ checkDumpNumericsAndDiagnostics(tracingDump, {
+ size: 1024,
+ effective_size: 1024
+ }, {});
+ });
+
+ test('checkDiscountTracingOverhead_definedFields', function() {
+ const pmd = createFinalizedProcessMemoryDump(42, function(pmd) {
+ pmd.totals = {residentBytes: 10240};
+ pmd.vmRegions = createClassificationNode(6000, {
+ privateDirtyResident: 4096,
+ proportionalResident: 5120,
+ swapped: 1536
+ });
+
+ const mallocDump = newAllocatorDump(pmd, 'malloc',
+ {numerics: {size: 3072}});
+ addChildDump(mallocDump, 'allocated_objects', {numerics: {size: 2560}});
+
+ const tracingDump = newAllocatorDump(
+ pmd, 'tracing', {numerics: {size: 1024, resident_size: 1000}});
+
+ pmd.memoryAllocatorDumps = [mallocDump, tracingDump];
+ });
+
+ assert.strictEqual(pmd.totals.residentBytes, 9240);
+ assert.isUndefined(pmd.totals.peakResidentBytes);
+
+ const vmRegions = pmd.vmRegions;
+ assert.strictEqual(vmRegions.sizeInBytes, 4976);
+ assert.deepEqual(vmRegions.byteStats, {
+ privateDirtyResident: 3096,
+ proportionalResident: 4120,
+ swapped: 1536
+ });
+
+ checkVMRegions(vmRegions, [
+ {
+ mappedFile: 'mock.so',
+ sizeInBytes: 6000,
+ byteStats: {
+ privateDirtyResident: 4096,
+ proportionalResident: 5120,
+ swapped: 1536
+ }
+ },
+ {
+ mappedFile: '[discounted tracing overhead]',
+ sizeInBytes: -1024,
+ byteStats: {
+ privateDirtyResident: -1000,
+ proportionalResident: -1000
+ }
+ }
+ ]);
+
+ const mallocDump = pmd.getMemoryAllocatorDumpByFullName('malloc');
+ checkDumpNumericsAndDiagnostics(mallocDump, {
+ size: 3072,
+ effective_size: 2048
+ }, {});
+ assert.lengthOf(
+ mallocDump.children, 2 /* 'allocated_objects' and '<unspecified>' */);
+
+ const allocatedObjectsDump = pmd.getMemoryAllocatorDumpByFullName(
+ 'malloc/allocated_objects');
+ checkDumpNumericsAndDiagnostics(allocatedObjectsDump, {
+ size: 2560,
+ effective_size: 1536
+ }, {});
+ assert.lengthOf(
+ allocatedObjectsDump.children,
+ 2 /* 'tracing_overhead' and '<unspecified>' */);
+
+ const discountDump = pmd.getMemoryAllocatorDumpByFullName(
+ 'malloc/allocated_objects/tracing_overhead');
+ assert.strictEqual(discountDump.parent, allocatedObjectsDump);
+ assert.include(allocatedObjectsDump.children, discountDump);
+ checkDumpNumericsAndDiagnostics(discountDump, {
+ size: 1024,
+ effective_size: 0
+ }, {});
+
+ const tracingDump = pmd.getMemoryAllocatorDumpByFullName('tracing');
+ checkDumpNumericsAndDiagnostics(tracingDump, {
+ size: 1024,
+ effective_size: 1024,
+ resident_size: 1000
+ }, {});
+ assert.strictEqual(tracingDump.owns.target, discountDump);
+ });
+
+ test('checkDiscountTracingOverhead_winheap', function() {
+ const pmd = createFinalizedProcessMemoryDump(42, function(pmd) {
+ pmd.memoryAllocatorDumps = [
+ newAllocatorDump(pmd, 'tracing', {numerics: {size: 2048}}),
+ newAllocatorDump(pmd, 'winheap', {numerics: {size: 5120}})
+ ];
+ });
+
+ assert.isUndefined(pmd.totals);
+ assert.isUndefined(pmd.vmRegions);
+
+ const winheapDump = pmd.getMemoryAllocatorDumpByFullName('winheap');
+ checkDumpNumericsAndDiagnostics(winheapDump, {
+ size: 5120,
+ effective_size: 3072
+ }, {});
+ assert.lengthOf(winheapDump.children,
+ 2 /* 'allocated_objects' and '<unspecified>' */);
+
+ const allocatedObjectsDump = pmd.getMemoryAllocatorDumpByFullName(
+ 'winheap/allocated_objects');
+ checkDumpNumericsAndDiagnostics(allocatedObjectsDump, {
+ size: 2048,
+ effective_size: 0
+ }, {});
+ assert.lengthOf(
+ allocatedObjectsDump.children, 1 /* 'tracing_overhead' */);
+
+ const discountDump = pmd.getMemoryAllocatorDumpByFullName(
+ 'winheap/allocated_objects/tracing_overhead');
+ assert.strictEqual(discountDump.parent, allocatedObjectsDump);
+ assert.include(allocatedObjectsDump.children, discountDump);
+ checkDumpNumericsAndDiagnostics(discountDump, {
+ size: 2048,
+ effective_size: 0
+ }, {});
+
+ const tracingDump = pmd.getMemoryAllocatorDumpByFullName('tracing');
+ checkDumpNumericsAndDiagnostics(tracingDump, {
+ size: 2048,
+ effective_size: 2048
+ }, {});
+ assert.strictEqual(tracingDump.owns.target, discountDump);
+ });
+
+ test('checkDiscountTracingOverhead_withMostRecentVmRegionsLinks', function() {
+ const pmds = createFinalizedProcessMemoryDumps([42, 90], function(pmds) {
+ pmds[0].totals = {residentBytes: 1000, peakResidentBytes: 2000};
+ pmds[0].vmRegions = createClassificationNode(6000, {
+ privateDirtyResident: 4096
+ });
+ pmds[0].memoryAllocatorDumps = [
+ newAllocatorDump(pmds[0], 'tracing',
+ {numerics: {size: 300, resident_size: 100}})
+ ];
+
+ pmds[1].totals = {peakResidentBytes: 3000};
+ pmds[1].memoryAllocatorDumps = [
+ newAllocatorDump(pmds[0], 'tracing', {numerics: {resident_size: 200}})
+ ];
+ });
+
+ // First PMD: Both total resident and private dirty resident size should be
+ // reduced by 100. Virtual size should be reduced by 300.
+ assert.strictEqual(pmds[0].totals.residentBytes, 900);
+ assert.strictEqual(pmds[0].totals.peakResidentBytes, 1900);
+ assert.strictEqual(pmds[0].vmRegions.sizeInBytes, 5700);
+ assert.deepEqual(pmds[0].vmRegions.byteStats, {
+ privateDirtyResident: 3996
+ });
+ checkVMRegions(pmds[0].vmRegions, [
+ {
+ mappedFile: 'mock.so',
+ sizeInBytes: 6000,
+ byteStats: {
+ privateDirtyResident: 4096,
+ }
+ },
+ {
+ mappedFile: '[discounted tracing overhead]',
+ sizeInBytes: -300,
+ byteStats: {
+ privateDirtyResident: -100
+ }
+ }
+ ]);
+ assert.strictEqual(pmds[0].mostRecentVmRegions, pmds[0].vmRegions);
+
+ // Second PMD: Total resident size should be reduced by 200, whereas private
+ // dirty resident size should be reduced by 100 (because it comes from
+ // the VM regions in the first dump). Similarly, virtual size should be
+ // reduced by 300.
+ assert.isUndefined(pmds[1].totals.residentBytes);
+ assert.strictEqual(pmds[1].totals.peakResidentBytes, 2800);
+ assert.isUndefined(pmds[1].vmRegions);
+ assert.strictEqual(pmds[1].mostRecentVmRegions, pmds[0].vmRegions);
+ });
+
+ test('checkDiscountTracingOverhead_allDiscountedVmRegionFields', function() {
+ const pmd = createFinalizedProcessMemoryDump(42, function(pmd) {
+ pmd.vmRegions = createClassificationNode(10000, {
+ privateDirtyResident: 4096,
+ proportionalResident: 8192,
+ swapped: 1536
+ });
+ pmd.memoryAllocatorDumps = [
+ newAllocatorDump(pmd, 'tracing',
+ {numerics: {size: 1000, resident_size: 1024}})
+ ];
+ });
+
+ const vmRegions = pmd.vmRegions;
+ assert.strictEqual(vmRegions.sizeInBytes, 9000);
+ assert.deepEqual(vmRegions.byteStats, {
+ privateDirtyResident: 3072,
+ proportionalResident: 7168,
+ swapped: 1536
+ });
+ checkVMRegions(vmRegions, [
+ {
+ mappedFile: 'mock.so',
+ sizeInBytes: 10000,
+ byteStats: {
+ privateDirtyResident: 4096,
+ proportionalResident: 8192,
+ swapped: 1536
+ }
+ },
+ {
+ mappedFile: '[discounted tracing overhead]',
+ sizeInBytes: -1000,
+ byteStats: {
+ privateDirtyResident: -1024,
+ proportionalResident: -1024
+ }
+ }
+ ]);
+ });
+
+ test('checkDiscountTracingOverhead_twoDiscountedVmRegionField', function() {
+ const pmd = createFinalizedProcessMemoryDump(42, function(pmd) {
+ pmd.vmRegions = createClassificationNode(10000, {
+ privateDirtyResident: 4096,
+ swapped: 1536
+ });
+ pmd.memoryAllocatorDumps = [
+ newAllocatorDump(pmd, 'tracing',
+ {numerics: {size: 1000, resident_size: 1024}})
+ ];
+ });
+
+ const vmRegions = pmd.vmRegions;
+ assert.strictEqual(vmRegions.sizeInBytes, 9000);
+ assert.deepEqual(vmRegions.byteStats, {
+ privateDirtyResident: 3072,
+ swapped: 1536
+ });
+ checkVMRegions(vmRegions, [
+ {
+ mappedFile: 'mock.so',
+ sizeInBytes: 10000,
+ byteStats: {
+ privateDirtyResident: 4096,
+ swapped: 1536
+ }
+ },
+ {
+ mappedFile: '[discounted tracing overhead]',
+ sizeInBytes: -1000,
+ byteStats: {
+ privateDirtyResident: -1024
+ }
+ }
+ ]);
+ });
+
+ test('checkDiscountTracingOverhead_oneDiscountedVmRegionField', function() {
+ const pmd = createFinalizedProcessMemoryDump(42, function(pmd) {
+ pmd.vmRegions = createClassificationNode(10000);
+ pmd.memoryAllocatorDumps = [
+ newAllocatorDump(pmd, 'tracing',
+ {numerics: {size: 1000, resident_size: 1024}})
+ ];
+ });
+
+ const vmRegions = pmd.vmRegions;
+ assert.strictEqual(vmRegions.sizeInBytes, 9000);
+ assert.deepEqual(vmRegions.byteStats, {});
+ checkVMRegions(vmRegions, [
+ {
+ mappedFile: 'mock.so',
+ sizeInBytes: 10000
+ },
+ {
+ mappedFile: '[discounted tracing overhead]',
+ sizeInBytes: -1000
+ }
+ ]);
+ });
+
+ test('checkDiscountTracingOverhead_noDiscountedVmRegionFields', function() {
+ const pmd = createFinalizedProcessMemoryDump(42, function(pmd) {
+ pmd.vmRegions = createClassificationNode(undefined, {
+ swapped: 1536
+ });
+ pmd.memoryAllocatorDumps = [
+ newAllocatorDump(pmd, 'tracing',
+ {numerics: {size: 1000, resident_size: 1024}})
+ ];
+ });
+
+ const vmRegions = pmd.vmRegions;
+ assert.isUndefined(vmRegions.sizeInBytes);
+ assert.deepEqual(vmRegions.byteStats, {
+ swapped: 1536
+ });
+ checkVMRegions(vmRegions, [
+ {
+ mappedFile: 'mock.so',
+ byteStats: {
+ swapped: 1536
+ }
+ }
+ ]);
+ });
+
+ test('checkDiscountTracingOverhead_existingLink', function() {
+ const pmd = createFinalizedProcessMemoryDump(42, function(pmd) {
+ pmd.totals = {residentBytes: 10240};
+
+ pmd.vmRegions = createClassificationNode(6000, {
+ privateDirtyResident: 4096,
+ swapped: 1536,
+ proportionalResident: 5120
+ });
+
+ const mallocDump = newAllocatorDump(pmd, 'malloc',
+ {numerics: {size: 3072}});
+ const tracingDump = newAllocatorDump(pmd, 'tracing',
+ {numerics: {size: 1024, resident_size: 1000}});
+ const ownedDump = newAllocatorDump(pmd, 'owned');
+
+ // The code for discounting tracing overhead should *not* override an
+ // existing ownership.
+ addOwnershipLink(tracingDump, ownedDump);
+
+ pmd.memoryAllocatorDumps = [mallocDump, tracingDump, ownedDump];
+ });
+
+ assert.strictEqual(pmd.totals.residentBytes, 9240);
+ assert.isUndefined(pmd.totals.peakResidentBytes);
+
+ const vmRegions = pmd.vmRegions;
+ assert.strictEqual(vmRegions.sizeInBytes, 4976);
+ assert.deepEqual(vmRegions.byteStats, {
+ privateDirtyResident: 3096,
+ proportionalResident: 4120,
+ swapped: 1536
+ });
+ checkVMRegions(vmRegions, [
+ {
+ mappedFile: 'mock.so',
+ sizeInBytes: 6000,
+ byteStats: {
+ privateDirtyResident: 4096,
+ proportionalResident: 5120,
+ swapped: 1536
+ }
+ },
+ {
+ mappedFile: '[discounted tracing overhead]',
+ sizeInBytes: -1024,
+ byteStats: {
+ privateDirtyResident: -1000,
+ proportionalResident: -1000
+ }
+ }
+ ]);
+
+ const mallocDump = pmd.getMemoryAllocatorDumpByFullName('malloc');
+ checkDumpNumericsAndDiagnostics(mallocDump, {
+ size: 3072,
+ effective_size: 3072
+ }, {});
+ assert.lengthOf(mallocDump.children, 0);
+
+ const ownedDump = pmd.getMemoryAllocatorDumpByFullName('owned');
+ checkDumpNumericsAndDiagnostics(ownedDump, {
+ size: 1024,
+ effective_size: 0
+ }, {});
+ assert.lengthOf(ownedDump.children, 0);
+
+ const tracingDump = pmd.getMemoryAllocatorDumpByFullName('tracing');
+ checkDumpNumericsAndDiagnostics(tracingDump, {
+ size: 1024,
+ effective_size: 1024,
+ resident_size: 1000
+ }, {});
+ assert.strictEqual(tracingDump.owns.target, ownedDump);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/process_test.html b/chromium/third_party/catapult/tracing/tracing/model/process_test.html
new file mode 100644
index 00000000000..9355971c099
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/process_test.html
@@ -0,0 +1,127 @@
+<!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/frame.html">
+<link rel="import" href="/tracing/model/global_memory_dump.html">
+<link rel="import" href="/tracing/model/instant_event.html">
+<link rel="import" href="/tracing/model/model.html">
+<link rel="import" href="/tracing/model/process.html">
+<link rel="import" href="/tracing/model/thread_slice.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ test('getOrCreateCounter', function() {
+ const model = new tr.Model();
+ const process = new tr.model.Process(model, 7);
+ const ctrBar = process.getOrCreateCounter('foo', 'bar');
+ const ctrBar2 = process.getOrCreateCounter('foo', 'bar');
+ assert.strictEqual(ctrBar2, ctrBar);
+ });
+
+ test('shiftTimestampsForward', function() {
+ const model = new tr.Model();
+ const process = new tr.model.Process(model, 7);
+ const ctr = process.getOrCreateCounter('foo', 'bar');
+ const thread = process.getOrCreateThread(1);
+
+ const instantEvent = new tr.model.InstantEvent('cat', 'event1', 1, 100);
+ process.instantEvents.push(instantEvent);
+
+ const slice = new tr.model.ThreadSlice('', 'a', 0, 1, {}, 4);
+ const frame =
+ new tr.model.Frame([slice], [{thread, start: 100, end: 200}]);
+ process.frames.push(frame);
+
+ const memoryDump = new tr.model.GlobalMemoryDump(model, 100);
+ process.memoryDumps.push(memoryDump);
+
+ let shiftCount = 0;
+ thread.shiftTimestampsForward = function(ts) {
+ if (ts === 0.32) {
+ shiftCount++;
+ }
+ };
+ ctr.shiftTimestampsForward = function(ts) {
+ if (ts === 0.32) {
+ shiftCount++;
+ }
+ };
+
+ process.shiftTimestampsForward(0.32);
+ assert.strictEqual(shiftCount, 2);
+ assert.strictEqual(instantEvent.start, 100.32);
+ assert.strictEqual(frame.start, 100.32);
+ assert.strictEqual(frame.end, 200.32);
+ assert.strictEqual(memoryDump.start, 100.32);
+ });
+
+ test('compareOnPID', function() {
+ let model = new tr.Model();
+ const p1 = new tr.model.Process(model, 1);
+ p1.name = 'Renderer';
+
+ model = new tr.Model();
+ const p2 = new tr.model.Process(model, 2);
+ p2.name = 'Renderer';
+
+ assert.isBelow(p1.compareTo(p2), 0);
+ });
+
+ test('compareOnSortIndex', function() {
+ const model = new tr.Model();
+ const p1 = new tr.model.Process(model, 1);
+ p1.name = 'Renderer';
+ p1.sortIndex = 1;
+
+ const p2 = new tr.model.Process(model, 2);
+ p2.name = 'Renderer';
+
+ assert.isAbove(p1.compareTo(p2), 0);
+ });
+
+ test('compareOnName', function() {
+ const model = new tr.Model();
+ const p1 = new tr.model.Process(model, 1);
+ p1.name = 'Browser';
+
+ const p2 = new tr.model.Process(model, 2);
+ p2.name = 'Renderer';
+
+ assert.isBelow(p1.compareTo(p2), 0);
+ });
+
+ test('compareOnLabels', function() {
+ const model = new tr.Model();
+ const p1 = new tr.model.Process(model, 1);
+ p1.name = 'Renderer';
+ p1.labels = ['a'];
+
+ const p2 = new tr.model.Process(model, 2);
+ p2.name = 'Renderer';
+ p2.labels = ['b'];
+
+ assert.isBelow(p1.compareTo(p2), 0);
+ });
+
+ test('compareOnUptime', function() {
+ const model = new tr.Model();
+ const p1 = new tr.model.Process(model, 1);
+ p1.name = 'Renderer';
+ p1.uptime_seconds = 10;
+
+ const p2 = new tr.model.Process(model, 2);
+ p2.name = 'Renderer';
+ p2.uptime_seconds = 20;
+
+ assert.isBelow(p1.compareTo(p2), 0);
+ });
+});
+</script>
+
diff --git a/chromium/third_party/catapult/tracing/tracing/model/profile_node.html b/chromium/third_party/catapult/tracing/tracing/model/profile_node.html
new file mode 100644
index 00000000000..edb47b77852
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/profile_node.html
@@ -0,0 +1,90 @@
+<!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/color_scheme.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Provides the ProfileNode class.
+ */
+tr.exportTo('tr.model', function() {
+ /**
+ * A ProfileNode represents a node in the profile tree,
+ * it is essentially a frame in the stack when the sample gets recorded.
+ */
+ // TODO(lpy) Move V8 specific part out of ProfileNode.
+ function ProfileNode(id, title, parentNode) {
+ this.id_ = id;
+ this.title_ = title;
+ this.parentNode_ = parentNode;
+ this.colorId_ = -1;
+ // Cache the constructed call stack starting from this node to root.
+ this.userFriendlyStack_ = [];
+ }
+
+ ProfileNode.prototype = {
+ __proto__: Object.prototype,
+
+ get title() {
+ return this.title_;
+ },
+
+ get parentNode() {
+ return this.parentNode_;
+ },
+
+ set parentNode(value) {
+ this.parentNode_ = value;
+ },
+
+ get id() {
+ return this.id_;
+ },
+
+ get colorId() {
+ return this.colorId_;
+ },
+
+ set colorId(value) {
+ this.colorId_ = value;
+ },
+
+ get userFriendlyName() {
+ return this.title_;
+ },
+
+ get userFriendlyStack() {
+ if (this.userFriendlyStack_.length === 0) {
+ this.userFriendlyStack_ = [this.userFriendlyName];
+ if (this.parentNode_ !== undefined) {
+ this.userFriendlyStack_ =
+ this.userFriendlyStack_.concat(this.parentNode_.userFriendlyStack);
+ }
+ }
+ return this.userFriendlyStack_;
+ },
+
+ get sampleTitle() {
+ throw new Error('Not implemented.');
+ }
+ };
+
+ tr.model.EventRegistry.register(
+ ProfileNode,
+ {
+ name: 'Node',
+ pluralName: 'Nodes'
+ }
+ );
+
+ return {
+ ProfileNode,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/profile_tree.html b/chromium/third_party/catapult/tracing/tracing/model/profile_tree.html
new file mode 100644
index 00000000000..57e73ed27b0
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/profile_tree.html
@@ -0,0 +1,88 @@
+<!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/base.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Provides the Sample class.
+ */
+tr.exportTo('tr.model', function() {
+ /**
+ * A ProfileTree represents all call stack we collect in sampling
+ * in the form of a tree.
+ * By traversing from root to a leaf we get a call stack
+ * that belongs to some samples we collect.
+ */
+ function ProfileTree() {
+ this.startTime_ = undefined;
+ this.endTime_ = undefined;
+ this.tree_ = new Map();
+ this.pid_ = -1;
+ this.tid_ = -1;
+ }
+
+ ProfileTree.prototype = {
+ __proto__: Object.prototype,
+
+ get pid() {
+ return this.pid_;
+ },
+
+ set pid(value) {
+ this.pid_ = value;
+ },
+
+ get tid() {
+ return this.tid_;
+ },
+
+ set tid(value) {
+ this.tid_ = value;
+ },
+
+ get tree() {
+ return this.tree_;
+ },
+
+ get startTime() {
+ return this.startTime_;
+ },
+
+ set startTime(value) {
+ this.startTime_ = value;
+ this.endTime_ = value;
+ },
+
+ get endTime() {
+ return this.endTime_;
+ },
+
+ set endTime(value) {
+ this.endTime_ = value;
+ },
+
+ add(node) {
+ if (this.tree_.has(node.id)) {
+ throw new Error('Conflict id in the profile tree.');
+ }
+ this.tree_.set(node.id, node);
+ return node;
+ },
+
+ getNode(nodeId) {
+ return this.tree_.get(nodeId);
+ }
+ };
+
+ return {
+ ProfileTree,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/proxy_selectable_item.html b/chromium/third_party/catapult/tracing/tracing/model/proxy_selectable_item.html
new file mode 100644
index 00000000000..d61a944569b
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/proxy_selectable_item.html
@@ -0,0 +1,45 @@
+<!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/selectable_item.html">
+<link rel="import" href="/tracing/model/selection_state.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.model', function() {
+ const SelectableItem = tr.model.SelectableItem;
+ const SelectionState = tr.model.SelectionState;
+
+ /**
+ * A ProxySelectableItem is a selectable item which is not a model item itself
+ * but instead acts as a proxy for a model item.
+ *
+ * @constructor
+ * @extends {SelectableItem}
+ */
+ function ProxySelectableItem(modelItem) {
+ SelectableItem.call(this, modelItem);
+ }
+
+ ProxySelectableItem.prototype = {
+ __proto__: SelectableItem.prototype,
+
+ get selectionState() {
+ const modelItem = this.modelItem_;
+ if (modelItem === undefined) {
+ return SelectionState.NONE;
+ }
+ return modelItem.selectionState;
+ }
+ };
+
+ return {
+ ProxySelectableItem,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/proxy_selectable_item_test.html b/chromium/third_party/catapult/tracing/tracing/model/proxy_selectable_item_test.html
new file mode 100644
index 00000000000..c7dba2c88a8
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/proxy_selectable_item_test.html
@@ -0,0 +1,39 @@
+<!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/proxy_selectable_item.html">
+<link rel="import" href="/tracing/model/selection_state.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const ProxySelectableItem = tr.model.ProxySelectableItem;
+ const SelectionState = tr.model.SelectionState;
+
+ test('checkSelectionState_undefinedModelItem', function() {
+ const proxyItem = new ProxySelectableItem(undefined);
+ assert.isFalse(proxyItem.selected);
+ assert.strictEqual(proxyItem.selectionState, SelectionState.NONE);
+ });
+
+ test('checkSelectionState_definedModelItem', function() {
+ // Start with a selected model event.
+ const modelItem = {selectionState: SelectionState.SELECTED};
+ const proxyItem = new ProxySelectableItem(modelItem);
+ assert.isTrue(proxyItem.selected);
+ assert.strictEqual(proxyItem.selectionState, SelectionState.SELECTED);
+
+ // Change the selection state of the model event to highlighted.
+ modelItem.selectionState = SelectionState.HIGHLIGHTED;
+ assert.isFalse(proxyItem.selected);
+ assert.strictEqual(proxyItem.selectionState, SelectionState.HIGHLIGHTED);
+ });
+});
+</script>
+
diff --git a/chromium/third_party/catapult/tracing/tracing/model/rect_annotation.html b/chromium/third_party/catapult/tracing/tracing/model/rect_annotation.html
new file mode 100644
index 00000000000..0ba5daf3ca5
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/rect_annotation.html
@@ -0,0 +1,65 @@
+<!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/annotation.html">
+<link rel="import" href="/tracing/model/location.html">
+<link rel="import" href="/tracing/ui/annotations/rect_annotation_view.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.model', function() {
+ function RectAnnotation(start, end) {
+ tr.model.Annotation.apply(this, arguments);
+
+ this.startLocation_ = start; // Location of top-left corner.
+ this.endLocation_ = end; // Location of bottom-right corner.
+ this.fillStyle = 'rgba(255, 180, 0, 0.3)';
+ }
+
+ RectAnnotation.fromDict = function(dict) {
+ const args = dict.args;
+ const startLoc =
+ new tr.model.Location(args.start.xWorld, args.start.yComponents);
+ const endLoc =
+ new tr.model.Location(args.end.xWorld, args.end.yComponents);
+ return new tr.model.RectAnnotation(startLoc, endLoc);
+ };
+
+ RectAnnotation.prototype = {
+ __proto__: tr.model.Annotation.prototype,
+
+ get startLocation() {
+ return this.startLocation_;
+ },
+
+ get endLocation() {
+ return this.endLocation_;
+ },
+
+ toDict() {
+ return {
+ typeName: 'rect',
+ args: {
+ start: this.startLocation.toDict(),
+ end: this.endLocation.toDict()
+ }
+ };
+ },
+
+ createView_(viewport) {
+ return new tr.ui.annotations.RectAnnotationView(viewport, this);
+ }
+ };
+
+ tr.model.Annotation.register(RectAnnotation, {typeName: 'rect'});
+
+ return {
+ RectAnnotation,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/resource_usage_sample.html b/chromium/third_party/catapult/tracing/tracing/model/resource_usage_sample.html
new file mode 100644
index 00000000000..5ede6b97cff
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/resource_usage_sample.html
@@ -0,0 +1,74 @@
+<!DOCTYPE html>
+<!--
+Copyright 2017 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.html">
+<link rel="import" href="/tracing/model/event_registry.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.model', function() {
+ const Event = tr.model.Event;
+ const EventRegistry = tr.model.EventRegistry;
+
+ /**
+ * A sample containing data about what fraction of a resource
+ * (CPU or GPU) is being used at a given point in time.
+ */
+ class ResourceUsageSample extends Event {
+ /**
+ * @param {ResourceUsageSeries } series The ResourceUsageSeries that this
+ * sample will be a part of.
+ * @param {float} start Time of the sample.
+ * @param {float} usage Fraction of the resource (CPU or GPU) in use at the
+ * time of the sample.
+ */
+ constructor(series, start, usage) {
+ super();
+
+ this.series_ = series;
+ this.start_ = start;
+ this.usage_ = usage;
+ }
+
+ get series() {
+ return this.series_;
+ }
+
+ get start() {
+ return this.start_;
+ }
+
+ set start(value) {
+ this.start_ = value;
+ }
+
+ get usage() {
+ return this.usage_;
+ }
+
+ set usage(value) {
+ this.usage_ = value;
+ }
+
+ addBoundsToRange(range) {
+ range.addValue(this.start);
+ }
+ }
+
+ EventRegistry.register(
+ ResourceUsageSample,
+ {
+ name: 'resourceUsageSample',
+ pluralName: 'resourceUsageSamples'
+ });
+
+ return {
+ ResourceUsageSample,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/resource_usage_sample_test.html b/chromium/third_party/catapult/tracing/tracing/model/resource_usage_sample_test.html
new file mode 100644
index 00000000000..003ef6a327e
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/resource_usage_sample_test.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<!--
+Copyright 2017 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/model/model.html">
+<link rel="import" href="/tracing/model/resource_usage_sample.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const ResourceUsageSample = tr.model.ResourceUsageSample;
+
+ test('usageSample', function() {
+ const series = new tr.model.ResourceUsageSeries(new tr.Model().device);
+
+ const sample1 = new ResourceUsageSample(series, 0.0, 0.11);
+ const sample2 = new ResourceUsageSample(series, 1.0, 0.22);
+
+ assert.strictEqual(sample1.series, series);
+ assert.strictEqual(sample1.start, 0.0);
+ assert.strictEqual(sample1.usage, 0.11);
+
+ assert.strictEqual(sample2.series, series);
+ assert.strictEqual(sample2.start, 1.0);
+ assert.strictEqual(sample2.usage, 0.22);
+ });
+
+ test('addBoundsToRange', function() {
+ const series = new tr.model.ResourceUsageSeries(new tr.Model().device);
+
+ const sample1 = new ResourceUsageSample(series, 0.0, 0.11);
+ const sample2 = new ResourceUsageSample(series, 1.0, 0.22);
+
+ const range = new tr.b.math.Range();
+ sample1.addBoundsToRange(range);
+
+ assert.strictEqual(range.min, 0);
+ assert.strictEqual(range.max, 0);
+
+ sample2.addBoundsToRange(range);
+
+ assert.strictEqual(range.min, 0);
+ assert.strictEqual(range.max, 1);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/resource_usage_series.html b/chromium/third_party/catapult/tracing/tracing/model/resource_usage_series.html
new file mode 100644
index 00000000000..16f28c8d2a1
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/resource_usage_series.html
@@ -0,0 +1,121 @@
+<!DOCTYPE html>
+<!--
+Copyright 2017 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_scale.html">
+<link rel="import" href="/tracing/base/utils.html">
+<link rel="import" href="/tracing/model/event_container.html">
+<link rel="import" href="/tracing/model/resource_usage_sample.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.model', function() {
+ const ResourceUsageSample = tr.model.ResourceUsageSample;
+
+ /**
+ * A container holding a time series of samples giving the
+ * fraction of usage of a resource (e.g. CPU or GPU) at each
+ * sample time.
+ */
+
+ class ResourceUsageSeries extends tr.model.EventContainer {
+ constructor(device) {
+ super();
+
+ this.device_ = device;
+ this.samples_ = [];
+ }
+
+ get device() {
+ return this.device_;
+ }
+
+ get samples() {
+ return this.samples_;
+ }
+
+ get stableId() {
+ return this.device_.stableId + '.ResourceUsageSeries';
+ }
+
+ /**
+ * Adds a usage sample to the series and returns it.
+ *
+ * Note: Samples must be added in chronological order.
+ */
+ addUsageSample(ts, val) {
+ const sample = new ResourceUsageSample(this, ts, val);
+ this.samples_.push(sample);
+ return sample;
+ }
+
+ /**
+ * Returns the total time consumed by a resource (e.g. CPU or GPU) between
+ * the specified start and end timestamps (in milliseconds).
+ */
+ computeResourceTimeConsumedInMs(start, end) {
+ const measurementRange = tr.b.math.Range.fromExplicitRange(start, end);
+
+ let resourceTimeInMs = 0;
+ let startIndex = tr.b.findLowIndexInSortedArray(
+ this.samples, x => x.start, start) - 1;
+ const endIndex = tr.b.findLowIndexInSortedArray(
+ this.samples, x => x.start, end);
+
+ if (startIndex < 0) startIndex = 0;
+
+ for (let i = startIndex; i < endIndex; i++) {
+ const sample = this.samples[i];
+ const nextSample = this.samples[i + 1];
+
+ const sampleRange = new tr.b.math.Range();
+ sampleRange.addValue(sample.start);
+ sampleRange.addValue(nextSample ? nextSample.start : sample.start);
+
+ const intersectionRangeInMs = measurementRange.findIntersection(
+ sampleRange);
+
+ resourceTimeInMs += intersectionRangeInMs.duration * sample.usage;
+ }
+
+ return resourceTimeInMs;
+ }
+
+ getSamplesWithinRange(start, end) {
+ const startIndex = tr.b.findLowIndexInSortedArray(
+ this.samples, x => x.start, start);
+ const endIndex = tr.b.findLowIndexInSortedArray(
+ this.samples, x => x.start, end);
+ return this.samples.slice(startIndex, endIndex);
+ }
+
+ shiftTimestampsForward(amount) {
+ for (let i = 0; i < this.samples_.length; ++i) {
+ this.samples_[i].start += amount;
+ }
+ }
+
+ updateBounds() {
+ this.bounds.reset();
+
+ if (this.samples_.length === 0) return;
+
+ this.bounds.addValue(this.samples_[0].start);
+ this.bounds.addValue(this.samples_[this.samples_.length - 1].start);
+ }
+
+ * childEvents() {
+ yield* this.samples_;
+ }
+ }
+
+ return {
+ ResourceUsageSeries,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/resource_usage_series_test.html b/chromium/third_party/catapult/tracing/tracing/model/resource_usage_series_test.html
new file mode 100644
index 00000000000..80981e0ca26
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/resource_usage_series_test.html
@@ -0,0 +1,152 @@
+<!DOCTYPE html>
+<!--
+Copyright 2017 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/device.html">
+<link rel="import" href="/tracing/model/model.html">
+<link rel="import" href="/tracing/model/resource_usage_series.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const Model = tr.Model;
+ const ResourceUsageSeries = tr.model.ResourceUsageSeries;
+
+ test('stableId', function() {
+ const device = { stableId: 'test' };
+ const series = new ResourceUsageSeries(device);
+
+ assert.strictEqual(series.stableId, 'test.ResourceUsageSeries');
+ });
+
+ test('device', function() {
+ const device = new tr.model.Device(new Model());
+ const series = new ResourceUsageSeries(device);
+
+ assert.strictEqual(series.device, device);
+ });
+
+ test('addUsageSample', function() {
+ const series = new ResourceUsageSeries(new Model().device);
+
+ assert.strictEqual(series.samples.length, 0);
+
+ const sample1 = series.addUsageSample(0, 1);
+ const sample2 = series.addUsageSample(1, 2);
+
+ assert.deepEqual(series.samples, [sample1, sample2]);
+ });
+
+ test('getResourceTimeConsumed_oneInterval', function() {
+ const series = new ResourceUsageSeries(new Model().device);
+ series.addUsageSample(0, 1);
+ series.addUsageSample(1000, 2);
+
+ assert.strictEqual(series.computeResourceTimeConsumedInMs(0, 1000), 1000);
+ });
+
+ test('getResourceTimeConsumed_twoIntervals', function() {
+ const series = new ResourceUsageSeries(new Model().device);
+ series.addUsageSample(0, 1);
+ series.addUsageSample(1000, 2);
+ series.addUsageSample(2000, 2);
+
+ assert.strictEqual(series.computeResourceTimeConsumedInMs(0, 2000), 3000);
+ });
+
+ test('getResourceTimeConsumed_oneSample', function() {
+ const series = new ResourceUsageSeries(new Model().device);
+ series.addUsageSample(1000, 1);
+
+ assert.strictEqual(series.computeResourceTimeConsumedInMs(0, 2000), 0);
+ });
+
+ test('getResourceTimeConsumed_samplesAfterStart', function() {
+ const series = new ResourceUsageSeries(new Model().device);
+ series.addUsageSample(1000, 1);
+ series.addUsageSample(2000, 2);
+
+ assert.strictEqual(series.computeResourceTimeConsumedInMs(0, 2000), 1000);
+ });
+
+ test('getResourceTimeConsumed_extraSamplesBeforeStart', function() {
+ const series = new ResourceUsageSeries(new Model().device);
+ series.addUsageSample(0, 10);
+ series.addUsageSample(1000, 1);
+ series.addUsageSample(2000, 1);
+ series.addUsageSample(3000, 1);
+
+ assert.strictEqual(
+ series.computeResourceTimeConsumedInMs(2000, 4000), 1000);
+ });
+
+ test('getResourceTimeConsumed_extraSamplesAfterEnd', function() {
+ const series = new ResourceUsageSeries(new Model().device);
+ series.addUsageSample(0, 1);
+ series.addUsageSample(1000, 1);
+ series.addUsageSample(2000, 1);
+ series.addUsageSample(3000, 10);
+
+ assert.strictEqual(series.computeResourceTimeConsumedInMs(0, 2000), 2000);
+ });
+
+ test('shiftTimestampsForward', function() {
+ const series = new ResourceUsageSeries(new Model().device);
+
+ series.addUsageSample(0, 1);
+ series.addUsageSample(1, 2);
+
+ series.shiftTimestampsForward(2);
+
+ assert.strictEqual(series.samples[0].start, 2);
+ assert.strictEqual(series.samples[1].start, 3);
+
+ series.shiftTimestampsForward(-4);
+
+ assert.strictEqual(series.samples[0].start, -2);
+ assert.strictEqual(series.samples[1].start, -1);
+ });
+
+
+ test('updateBounds', function() {
+ const series = new ResourceUsageSeries(new Model().device);
+
+ series.addUsageSample(0, 1);
+ series.addUsageSample(1, 2);
+ series.updateBounds();
+
+ assert.strictEqual(series.bounds.min, 0);
+ assert.strictEqual(series.bounds.max, 1);
+
+ series.addUsageSample(4, 3);
+ series.updateBounds();
+
+ assert.strictEqual(series.bounds.min, 0);
+ assert.strictEqual(series.bounds.max, 4);
+ });
+
+ test('childEvents_empty', function() {
+ const series = new ResourceUsageSeries(new Model().device);
+ const eventsInSeries = [];
+ for (const event of series.childEvents()) {
+ eventsInSeries.push(event);
+ }
+ assert.deepEqual(eventsInSeries, []);
+ });
+
+ test('childEvents_nonempty', function() {
+ const series = new ResourceUsageSeries(new Model().device);
+ const sample1 = series.addUsageSample(0, 1);
+ const sample2 = series.addUsageSample(1, 2);
+ const eventsInSeries = [];
+ for (const event of series.childEvents()) {
+ eventsInSeries.push(event);
+ }
+ assert.deepEqual(eventsInSeries, [sample1, sample2]);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/sample.html b/chromium/third_party/catapult/tracing/tracing/model/sample.html
new file mode 100644
index 00000000000..59a34771d8b
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/sample.html
@@ -0,0 +1,97 @@
+<!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/model/timed_event.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Provides the Sample class.
+ */
+tr.exportTo('tr.model', function() {
+ /**
+ * A Sample represents a sample taken at an instant in time,
+ * plus its call stack and parameters associated with that sample.
+ *
+ * @constructor
+ */
+ function Sample(start, title, leafNode, thread, opt_cpu, opt_weight,
+ opt_args) {
+ tr.model.TimedEvent.call(this, start);
+
+ this.start_ = start;
+ this.title_ = title;
+ this.leafNode_ = leafNode;
+ this.thread_ = thread;
+ this.colorId_ = leafNode.colorId;
+
+ this.cpu_ = opt_cpu;
+ this.weight_ = opt_weight;
+ this.args = opt_args || {};
+ }
+
+ Sample.prototype = {
+ __proto__: tr.model.TimedEvent.prototype,
+
+ get title() {
+ return this.title_;
+ },
+
+ get colorId() {
+ return this.colorId_;
+ },
+
+ get thread() {
+ return this.thread_;
+ },
+
+ get leafNode() {
+ return this.leafNode_;
+ },
+
+ get userFriendlyName() {
+ return 'Sample at ' +
+ tr.b.Unit.byName.timeStampInMs.format(this.start);
+ },
+
+ get userFriendlyStack() {
+ return this.leafNode_.userFriendlyStack;
+ },
+
+ getNodesAsArray() {
+ const nodes = [];
+ let node = this.leafNode_;
+ while (node !== undefined) {
+ nodes.push(node);
+ node = node.parentNode;
+ }
+ return nodes;
+ },
+
+ get cpu() {
+ return this.cpu_;
+ },
+
+ get weight() {
+ return this.weight_;
+ },
+ };
+
+ tr.model.EventRegistry.register(
+ Sample,
+ {
+ name: 'Sample',
+ pluralName: 'Samples'
+ });
+
+ return {
+ Sample,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/sample_test.html b/chromium/third_party/catapult/tracing/tracing/model/sample_test.html
new file mode 100644
index 00000000000..f329f1096d9
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/sample_test.html
@@ -0,0 +1,31 @@
+<!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/model.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const Sample = tr.model.Sample;
+ const StackFrame = tr.model.StackFrame;
+ const Thread = tr.model.Thread;
+
+ test('sampleStackTrace', function() {
+ const thread = tr.c.TestUtils.newFakeThread();
+
+ const model = new tr.Model();
+ const node = tr.c.TestUtils.newProfileNodes(model, ['a', 'b', 'c']);
+
+ const s = new Sample(
+ 10, 'instructions_retired', node, thread, undefined, 10);
+ assert.deepEqual(s.userFriendlyStack, ['c', 'b', 'a']);
+ });
+});
+</script>
+
diff --git a/chromium/third_party/catapult/tracing/tracing/model/scoped_id.html b/chromium/third_party/catapult/tracing/tracing/model/scoped_id.html
new file mode 100644
index 00000000000..b7b64a2e443
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/scoped_id.html
@@ -0,0 +1,42 @@
+<!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/base.html">
+<link rel="import" href="/tracing/model/constants.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.model', function() {
+ function ScopedId(scope, id, pid) {
+ if (scope === undefined) {
+ throw new Error('Scope should be defined. Use \'' +
+ tr.model.OBJECT_DEFAULT_SCOPE +
+ '\' as the default scope.');
+ }
+ this.scope = scope;
+ this.id = id;
+ this.pid = pid;
+ }
+
+ ScopedId.prototype = {
+ toString() {
+ const pidStr = this.pid === undefined ? '' : 'pid: ' + this.pid + ', ';
+ return '{' + pidStr + 'scope: ' + this.scope + ', id: ' + this.id + '}';
+ },
+
+ toStringWithDelimiter(delim) {
+ return (this.pid === undefined ? '' : this.pid) + delim +
+ this.scope + delim + this.id;
+ }
+ };
+
+ return {
+ ScopedId,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/selectable_item.html b/chromium/third_party/catapult/tracing/tracing/model/selectable_item.html
new file mode 100644
index 00000000000..3492dc00b9f
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/selectable_item.html
@@ -0,0 +1,57 @@
+<!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/selection_state.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Provides the SelectableItem class.
+ */
+tr.exportTo('tr.model', function() {
+ const SelectionState = tr.model.SelectionState;
+
+ /**
+ * A SelectableItem is the abstract base class for any non-container data that
+ * has an associated model item in the trace model (possibly itself).
+ *
+ * Subclasses must provide a selectionState property (or getter).
+ *
+ * @constructor
+ */
+ function SelectableItem(modelItem) {
+ this.modelItem_ = modelItem;
+ }
+
+ SelectableItem.prototype = {
+ get modelItem() {
+ return this.modelItem_;
+ },
+
+ get selected() {
+ return this.selectionState === SelectionState.SELECTED;
+ },
+
+ addToSelection(selection) {
+ const modelItem = this.modelItem_;
+ if (!modelItem) return;
+ selection.push(modelItem);
+ },
+
+ addToTrackMap(eventToTrackMap, track) {
+ const modelItem = this.modelItem_;
+ if (!modelItem) return;
+ eventToTrackMap.addEvent(modelItem, track);
+ }
+ };
+
+ return {
+ SelectableItem,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/selectable_item_test.html b/chromium/third_party/catapult/tracing/tracing/model/selectable_item_test.html
new file mode 100644
index 00000000000..120773852d9
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/selectable_item_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.html">
+<link rel="import" href="/tracing/model/selectable_item.html">
+<link rel="import" href="/tracing/model/selection_state.html">
+<link rel="import" href="/tracing/ui/tracks/event_to_track_map.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const Event = tr.model.Event;
+ const EventToTrackMap = tr.ui.tracks.EventToTrackMap;
+ const SelectableItem = tr.model.SelectableItem;
+ const SelectionState = tr.model.SelectionState;
+
+ test('checkModelItem', function() {
+ const selectableItem1 = new SelectableItem(undefined);
+ assert.isUndefined(selectableItem1.modelItem);
+
+ const event = new Event();
+ const selectableItem2 = new SelectableItem(event);
+ assert.strictEqual(selectableItem2.modelItem, event);
+ });
+
+ test('checkSelected', function() {
+ const selectableItem = new SelectableItem(undefined);
+
+ selectableItem.selectionState = SelectionState.NONE;
+ assert.isFalse(selectableItem.selected);
+
+ selectableItem.selectionState = SelectionState.SELECTED;
+ assert.isTrue(selectableItem.selected);
+
+ selectableItem.selectionState = SelectionState.HIGHLIGHTED;
+ assert.isFalse(selectableItem.selected);
+ });
+
+ test('checkAddToSelection_undefinedModelItem', function() {
+ const selectableItem = new SelectableItem(undefined);
+ const selection = [];
+ selectableItem.addToSelection(selection);
+ assert.lengthOf(selection, 0);
+ });
+
+ test('checkAddToSelection_definedModelItem', function() {
+ const event = new Event();
+ const selectableItem = new SelectableItem(event);
+ const selection = [];
+ selectableItem.addToSelection(selection);
+ assert.lengthOf(selection, 1);
+ assert.strictEqual(selection[0], event);
+ });
+
+ test('checkAddToTrackMap_undefinedModelItem', function() {
+ const selectableItem = new SelectableItem(undefined);
+ const eventToTrackMap = new EventToTrackMap();
+ const track = {};
+ selectableItem.addToTrackMap(eventToTrackMap, track);
+ assert.lengthOf(Object.keys(eventToTrackMap), 0);
+ });
+
+ test('checkAddToTrackMap_definedModelItem', function() {
+ const event = new Event();
+ const selectableItem = new SelectableItem(event);
+ const eventToTrackMap = new EventToTrackMap();
+ const track = {};
+ selectableItem.addToTrackMap(eventToTrackMap, track);
+ assert.lengthOf(Object.keys(eventToTrackMap), 1);
+ assert.strictEqual(eventToTrackMap[event.guid], track);
+ });
+});
+</script>
+
diff --git a/chromium/third_party/catapult/tracing/tracing/model/selection_state.html b/chromium/third_party/catapult/tracing/tracing/model/selection_state.html
new file mode 100644
index 00000000000..3e68d65a72b
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/selection_state.html
@@ -0,0 +1,72 @@
+<!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/base.html">
+<link rel="import" href="/tracing/base/color_scheme.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Provides the SelectionState class.
+ */
+tr.exportTo('tr.model', function() {
+ const ColorScheme = tr.b.ColorScheme;
+
+ /**
+ * Describes the level of visual highlighting to apply to an event when shown.
+ *
+ * color_scheme.html defines N variations off of a base color palette,
+ * one for each selection state, all concatenated into one flat array. To
+ * pick the final colorId for a given variations, the SelectionState is
+ * multiplied by the number of base colors.
+ *
+ * Thus, the values here must be kept in sync with color_scheme's palette
+ * layout.
+ */
+ const SelectionState = {
+ NONE: 0,
+
+ // Legacy names.
+ SELECTED: ColorScheme.properties.brightenedOffsets[0],
+ HIGHLIGHTED: ColorScheme.properties.brightenedOffsets[1],
+ DIMMED: ColorScheme.properties.dimmedOffsets[0],
+
+ // Modern names.
+ BRIGHTENED0: ColorScheme.properties.brightenedOffsets[0],
+ BRIGHTENED1: ColorScheme.properties.brightenedOffsets[1],
+ BRIGHTENED2: ColorScheme.properties.brightenedOffsets[2],
+
+ DIMMED0: ColorScheme.properties.dimmedOffsets[0],
+ DIMMED1: ColorScheme.properties.dimmedOffsets[1],
+ DIMMED2: ColorScheme.properties.dimmedOffsets[2]
+ };
+
+ const brighteningLevels = [
+ SelectionState.NONE,
+ SelectionState.BRIGHTENED0,
+ SelectionState.BRIGHTENED1,
+ SelectionState.BRIGHTENED2
+ ];
+ SelectionState.getFromBrighteningLevel = function(level) {
+ return brighteningLevels[level];
+ };
+
+ const dimmingLevels = [
+ SelectionState.DIMMED0,
+ SelectionState.DIMMED1,
+ SelectionState.DIMMED2
+ ];
+ SelectionState.getFromDimmingLevel = function(level) {
+ return dimmingLevels[level];
+ };
+
+ return {
+ SelectionState,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/slice.html b/chromium/third_party/catapult/tracing/tracing/model/slice.html
new file mode 100644
index 00000000000..2ea422561e6
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/slice.html
@@ -0,0 +1,302 @@
+<!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/timed_event.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Provides the Slice class.
+ */
+tr.exportTo('tr.model', function() {
+ /**
+ * A Slice represents an interval of time plus parameters associated
+ * with that interval.
+ *
+ * @constructor
+ */
+ function Slice(category, title, colorId, start, args, opt_duration,
+ opt_cpuStart, opt_cpuDuration, opt_argsStripped,
+ opt_bindId) {
+ if (new.target) {
+ throw new Error('Can\'t instantiate pure virtual class Slice');
+ }
+ tr.model.TimedEvent.call(this, start);
+
+ this.category = category || '';
+ this.title = title;
+ this.colorId = colorId;
+ this.args = args;
+ this.startStackFrame = undefined;
+ this.endStackFrame = undefined;
+ this.didNotFinish = false;
+ this.inFlowEvents = [];
+ this.outFlowEvents = [];
+ this.subSlices = [];
+ this.selfTime = undefined;
+ this.cpuSelfTime = undefined;
+ this.important = false;
+ this.parentContainer = undefined;
+ this.argsStripped = false;
+
+ this.bind_id_ = opt_bindId;
+
+ // parentSlice and isTopLevel will be set by SliceGroup.
+ this.parentSlice = undefined;
+ this.isTopLevel = false;
+ // After SliceGroup processes Slices, isTopLevel should be equivalent to
+ // !parentSlice.
+
+ if (opt_duration !== undefined) {
+ this.duration = opt_duration;
+ }
+
+ if (opt_cpuStart !== undefined) {
+ this.cpuStart = opt_cpuStart;
+ }
+
+ if (opt_cpuDuration !== undefined) {
+ this.cpuDuration = opt_cpuDuration;
+ }
+
+ if (opt_argsStripped !== undefined) {
+ this.argsStripped = true;
+ }
+ }
+
+ Slice.prototype = {
+ __proto__: tr.model.TimedEvent.prototype,
+
+
+ get analysisTypeName() {
+ return this.title;
+ },
+
+ get userFriendlyName() {
+ return 'Slice ' + this.title + ' at ' +
+ tr.b.Unit.byName.timeStampInMs.format(this.start);
+ },
+
+ get stableId() {
+ const parentSliceGroup = this.parentContainer.sliceGroup;
+ return parentSliceGroup.stableId + '.' +
+ parentSliceGroup.slices.indexOf(this);
+ },
+
+ get bindId() {
+ return this.bind_id_;
+ },
+
+ findDescendentSlice(targetTitle) {
+ if (!this.subSlices) {
+ return undefined;
+ }
+
+ for (let i = 0; i < this.subSlices.length; i++) {
+ if (this.subSlices[i].title === targetTitle) {
+ return this.subSlices[i];
+ }
+ const slice = this.subSlices[i].findDescendentSlice(targetTitle);
+ if (slice) return slice;
+ }
+ return undefined;
+ },
+
+ get mostTopLevelSlice() {
+ if (!this.parentSlice) return this;
+ return this.parentSlice.mostTopLevelSlice;
+ },
+
+ getProcess() {
+ const thread = this.parentContainer;
+ if (thread && thread.getProcess) {
+ return thread.getProcess();
+ }
+ return undefined;
+ },
+
+ get model() {
+ const process = this.getProcess();
+ if (process !== undefined) {
+ return this.getProcess().model;
+ }
+ return undefined;
+ },
+
+ /**
+ * Finds all topmost slices relative to this slice.
+ *
+ * Slices may have multiple direct descendants which satisfy
+ * |eventPredicate|, and in this case, all of them are topmost as long as
+ * this slice does not satisfy the predicate.
+ *
+ * For instance, suppose we are passing a predicate that checks whether
+ * events titles begin with 'C'.
+ * C1.findTopmostSlicesRelativeToThisSlice() returns C1 in this example:
+ * [ C1 ]
+ * [ C2 ]
+ *
+ * and D.findTopmostSlicesRelativeToThisSlice() returns C1 and C2 in this
+ * example:
+ * [ D ]
+ * [C1] [C2]
+ */
+ * findTopmostSlicesRelativeToThisSlice(eventPredicate) {
+ if (eventPredicate(this)) {
+ yield this;
+ return;
+ }
+ for (const s of this.subSlices) {
+ yield* s.findTopmostSlicesRelativeToThisSlice(eventPredicate);
+ }
+ },
+
+ /**
+ * Obtains all subsequent slices of this slice.
+ *
+ * Subsequent slices are slices that get executed after a particular
+ * slice, i.e., all the functions that are called after the current one.
+ *
+ * For instance, E.iterateAllSubsequentSlices() in the following example:
+ * [ A ]
+ * [ B][ D ][ G ]
+ * [C] [E][F] [H]
+ * will pass F, G, then H to the provided callback.
+ *
+ * The reason we need subsequent slices of a particular slice is that
+ * when there is flow event goes into, e.g., E, we only want to highlight
+ * E's subsequent slices to indicate the execution order.
+ *
+ * The idea to calculate the subsequent slices of slice E is to view
+ * the slice group as a tree where the top-level slice A is the root node.
+ * The preorder depth-first-search (DFS) order is naturally equivalent
+ * to the function call order. We just need to perform a DFS, and start
+ * recording the slices after we see the occurance of E.
+ */
+ iterateAllSubsequentSlices(callback, opt_this) {
+ const parentStack = [];
+ let started = false;
+
+ // get the root node and push it to the DFS stack
+ const topmostSlice = this.mostTopLevelSlice;
+ parentStack.push(topmostSlice);
+
+ // Using the stack to perform DFS
+ while (parentStack.length !== 0) {
+ const curSlice = parentStack.pop();
+
+ if (started) {
+ callback.call(opt_this, curSlice);
+ } else {
+ started = (curSlice.guid === this.guid);
+ }
+
+ for (let i = curSlice.subSlices.length - 1; i >= 0; i--) {
+ parentStack.push(curSlice.subSlices[i]);
+ }
+ }
+ },
+
+ get subsequentSlices() {
+ const res = [];
+
+ this.iterateAllSubsequentSlices(function(subseqSlice) {
+ res.push(subseqSlice);
+ });
+
+ return res;
+ },
+
+ /**
+ * Obtains the parents of a slice, from the most immediate to the root.
+ *
+ * For instance, E.enumerateAllAncestors() in the following example:
+ * [ A ]
+ * [ B][ D ][ G ]
+ * [C] [E][F] [H]
+ * will yield D, then A, in the order from the leaves to the root.
+ */
+ * enumerateAllAncestors() {
+ let curSlice = this.parentSlice;
+ while (curSlice) {
+ yield curSlice;
+ curSlice = curSlice.parentSlice;
+ }
+ },
+
+ get ancestorSlices() {
+ return Array.from(this.enumerateAllAncestors());
+ },
+
+ iterateEntireHierarchy(callback, opt_this) {
+ const mostTopLevelSlice = this.mostTopLevelSlice;
+ callback.call(opt_this, mostTopLevelSlice);
+ mostTopLevelSlice.iterateAllSubsequentSlices(callback, opt_this);
+ },
+
+ get entireHierarchy() {
+ const res = [];
+
+ this.iterateEntireHierarchy(function(slice) {
+ res.push(slice);
+ });
+
+ return res;
+ },
+
+ /**
+ * Returns this slice, and its ancestor and subsequent slices.
+ *
+ * For instance, E.ancestorAndSubsequentSlices in the following example:
+ * [ A ]
+ * [ B][ D ][ G ]
+ * [C] [E][F] [H]
+ * will return E, D, A, F, G, and H, where E is itself, D and A are
+ * E's ancestors, and F, G, and H are subsequent slices of E
+ */
+ get ancestorAndSubsequentSlices() {
+ const res = [];
+
+ res.push(this);
+
+ for (const aSlice of this.enumerateAllAncestors()) {
+ res.push(aSlice);
+ }
+
+ this.iterateAllSubsequentSlices(function(sSlice) {
+ res.push(sSlice);
+ });
+
+ return res;
+ },
+
+ * enumerateAllDescendents() {
+ for (const slice of this.subSlices) {
+ yield slice;
+ }
+ for (const slice of this.subSlices) {
+ yield* slice.enumerateAllDescendents();
+ }
+ },
+
+ get descendentSlices() {
+ const res = [];
+ for (const slice of this.enumerateAllDescendents()) {
+ res.push(slice);
+ }
+ return res;
+ }
+
+ };
+
+ return {
+ Slice,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/slice_group.html b/chromium/third_party/catapult/tracing/tracing/model/slice_group.html
new file mode 100644
index 00000000000..82d24283cf4
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/slice_group.html
@@ -0,0 +1,720 @@
+<!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/guid.html">
+<link rel="import" href="/tracing/base/utils.html">
+<link rel="import" href="/tracing/core/filter.html">
+<link rel="import" href="/tracing/model/event_container.html">
+<link rel="import" href="/tracing/model/thread_slice.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Provides the SliceGroup class.
+ */
+tr.exportTo('tr.model', function() {
+ const ColorScheme = tr.b.ColorScheme;
+ const ThreadSlice = tr.model.ThreadSlice;
+
+ function getSliceLo(s) {
+ return s.start;
+ }
+
+ function getSliceHi(s) {
+ return s.end;
+ }
+
+ /**
+ * A group of Slices, plus code to create them from B/E events, as
+ * well as arrange them into subRows.
+ *
+ * Do not mutate the slices array directly. Modify it only by
+ * SliceGroup mutation methods.
+ *
+ * @constructor
+ * @param {function(new:Slice, category, title, colorId, start, args)=}
+ * opt_sliceConstructor The constructor to use when creating slices.
+ * @extends {tr.model.EventContainer}
+ */
+ function SliceGroup(parentContainer, opt_sliceConstructor, opt_name) {
+ tr.model.EventContainer.call(this);
+
+ this.parentContainer_ = parentContainer;
+
+ const sliceConstructor = opt_sliceConstructor || ThreadSlice;
+ this.sliceConstructor = sliceConstructor;
+ this.sliceConstructorSubTypes = this.sliceConstructor.subTypes;
+ if (!this.sliceConstructorSubTypes) {
+ throw new Error('opt_sliceConstructor must have a subtype registry.');
+ }
+
+ this.openPartialSlices_ = [];
+
+ this.slices = [];
+ this.topLevelSlices = [];
+ this.haveTopLevelSlicesBeenBuilt = false;
+ this.name_ = opt_name;
+
+ if (this.model === undefined) {
+ throw new Error('SliceGroup must have model defined.');
+ }
+ }
+
+ SliceGroup.prototype = {
+ __proto__: tr.model.EventContainer.prototype,
+
+ get parentContainer() {
+ return this.parentContainer_;
+ },
+
+ get model() {
+ return this.parentContainer_.model;
+ },
+
+ get stableId() {
+ return this.parentContainer_.stableId + '.SliceGroup';
+ },
+
+ getSettingsKey() {
+ if (!this.name_) return undefined;
+ const parentKey = this.parentContainer_.getSettingsKey();
+ if (!parentKey) return undefined;
+ return parentKey + '.' + this.name;
+ },
+
+ /**
+ * @return {Number} The number of slices in this group.
+ */
+ get length() {
+ return this.slices.length;
+ },
+
+ /**
+ * Helper function that pushes the provided slice onto the slices array.
+ * @param {Slice} slice The slice to be added to the slices array.
+ */
+ pushSlice(slice) {
+ this.haveTopLevelSlicesBeenBuilt = false;
+ slice.parentContainer = this.parentContainer_;
+ this.slices.push(slice);
+ return slice;
+ },
+
+ /**
+ * Helper function that pushes the provided slices onto the slices array.
+ * @param {Array.<Slice>} slices An array of slices to be added.
+ */
+ pushSlices(slices) {
+ this.haveTopLevelSlicesBeenBuilt = false;
+ slices.forEach(function(slice) {
+ slice.parentContainer = this.parentContainer_;
+ this.slices.push(slice);
+ }, this);
+ },
+
+ /**
+ * Opens a new slice in the group's slices.
+ *
+ * Calls to beginSlice and
+ * endSlice must be made with non-monotonically-decreasing timestamps.
+ *
+ * @param {String} category Category name of the slice to add.
+ * @param {String} title Title of the slice to add.
+ * @param {Number} ts The timetsamp of the slice, in milliseconds.
+ * @param {Object.<string, Object>=} opt_args Arguments associated with
+ * the slice.
+ * @param {Number=} opt_colorId The color of the slice, defined by
+ * its palette id (see base/color_scheme.html).
+ */
+ beginSlice(category, title, ts, opt_args, opt_tts,
+ opt_argsStripped, opt_colorId, opt_bindId) {
+ const colorId = opt_colorId ||
+ ColorScheme.getColorIdForGeneralPurposeString(title);
+ const sliceConstructorSubTypes = this.sliceConstructorSubTypes;
+ const sliceType = sliceConstructorSubTypes.getConstructor(
+ category, title);
+ const slice = new sliceType(category, title, colorId, ts,
+ opt_args ? opt_args : {}, null,
+ opt_tts, undefined,
+ opt_argsStripped, opt_bindId);
+ this.openPartialSlices_.push(slice);
+ slice.didNotFinish = true;
+ this.pushSlice(slice);
+
+ return slice;
+ },
+
+ isTimestampValidForBeginOrEnd(ts) {
+ if (!this.openPartialSlices_.length) return true;
+ const top = this.openPartialSlices_[this.openPartialSlices_.length - 1];
+ return ts >= top.start;
+ },
+
+ /**
+ * @return {Number} The number of beginSlices for which an endSlice has not
+ * been issued.
+ */
+ get openSliceCount() {
+ return this.openPartialSlices_.length;
+ },
+
+ get mostRecentlyOpenedPartialSlice() {
+ if (!this.openPartialSlices_.length) return undefined;
+ return this.openPartialSlices_[this.openPartialSlices_.length - 1];
+ },
+
+ /**
+ * Ends the last begun slice in this group and pushes it onto the slice
+ * array.
+ *
+ * @param {Number} ts Timestamp when the slice ended
+ * @param {Number=} opt_colorId The color of the slice, defined by
+ * its palette id (see base/color_scheme.html).
+ * @return {Slice} slice.
+ */
+ endSlice(ts, opt_tts, opt_colorId) {
+ if (!this.openSliceCount) {
+ throw new Error('endSlice called without an open slice');
+ }
+
+ const slice = this.openPartialSlices_[this.openSliceCount - 1];
+ this.openPartialSlices_.splice(this.openSliceCount - 1, 1);
+ if (ts < slice.start) {
+ throw new Error('Slice ' + slice.title +
+ ' end time is before its start.');
+ }
+
+ slice.duration = ts - slice.start;
+ slice.didNotFinish = false;
+ slice.colorId = opt_colorId || slice.colorId;
+
+ if (opt_tts && slice.cpuStart !== undefined) {
+ slice.cpuDuration = opt_tts - slice.cpuStart;
+ }
+
+ return slice;
+ },
+
+ /**
+ * Push a complete event as a Slice into the slice list.
+ * The timestamp can be in any order.
+ *
+ * @param {String} category Category name of the slice to add.
+ * @param {String} title Title of the slice to add.
+ * @param {Number} ts The timetsamp of the slice, in milliseconds.
+ * @param {Number} duration The duration of the slice, in milliseconds.
+ * @param {Object.<string, Object>=} opt_args Arguments associated with
+ * the slice.
+ * @param {Number=} opt_colorId The color of the slice, as defined by
+ * its palette id (see base/color_scheme.html).
+ */
+ pushCompleteSlice(category, title, ts, duration, tts,
+ cpuDuration, opt_args, opt_argsStripped,
+ opt_colorId, opt_bindId) {
+ const colorId = opt_colorId ||
+ ColorScheme.getColorIdForGeneralPurposeString(title);
+ const sliceConstructorSubTypes = this.sliceConstructorSubTypes;
+ const sliceType = sliceConstructorSubTypes.getConstructor(
+ category, title);
+ const slice = new sliceType(category, title, colorId, ts,
+ opt_args ? opt_args : {},
+ duration, tts, cpuDuration,
+ opt_argsStripped, opt_bindId);
+ if (duration === undefined) {
+ slice.didNotFinish = true;
+ }
+ this.pushSlice(slice);
+ return slice;
+ },
+
+ /**
+ * Closes any open slices.
+ * @param {Number=} opt_maxTimestamp The end time to use for the closed
+ * slices. If not provided,
+ * the max timestamp for this slice is provided.
+ */
+ autoCloseOpenSlices() {
+ this.updateBounds();
+ const maxTimestamp = this.bounds.max;
+ for (let sI = 0; sI < this.slices.length; sI++) {
+ const slice = this.slices[sI];
+ if (slice.didNotFinish) {
+ slice.duration = maxTimestamp - slice.start;
+ }
+ }
+ this.openPartialSlices_ = [];
+ },
+
+ /**
+ * Shifts all the timestamps inside this group forward by the amount
+ * specified.
+ */
+ shiftTimestampsForward(amount) {
+ for (let sI = 0; sI < this.slices.length; sI++) {
+ const slice = this.slices[sI];
+ slice.start = (slice.start + amount);
+ }
+ },
+
+ /**
+ * Updates the bounds for this group based on the slices it contains.
+ */
+ updateBounds() {
+ this.bounds.reset();
+ for (let i = 0; i < this.slices.length; i++) {
+ this.bounds.addValue(this.slices[i].start);
+ this.bounds.addValue(this.slices[i].end);
+ }
+ },
+
+ copySlice(slice) {
+ const sliceConstructorSubTypes = this.sliceConstructorSubTypes;
+ const sliceType = sliceConstructorSubTypes.getConstructor(slice.category,
+ slice.title);
+ const newSlice = new sliceType(slice.category, slice.title,
+ slice.colorId, slice.start,
+ slice.args, slice.duration, slice.cpuStart, slice.cpuDuration);
+ newSlice.didNotFinish = slice.didNotFinish;
+ return newSlice;
+ },
+
+ * findTopmostSlicesInThisContainer(eventPredicate, opt_this) {
+ if (!this.haveTopLevelSlicesBeenBuilt) {
+ throw new Error('Nope');
+ }
+
+ for (const s of this.topLevelSlices) {
+ yield* s.findTopmostSlicesRelativeToThisSlice(eventPredicate);
+ }
+ },
+
+ * childEvents() {
+ yield* this.slices;
+ },
+
+ * childEventContainers() {
+ },
+
+ /**
+ * Provides a more efficient implementation than the default implementation
+ * in event_container.html, since child events are sorted.
+ */
+ * getDescendantEventsInSortedRanges(ranges, opt_containerPredicate) {
+ if (opt_containerPredicate === undefined ||
+ opt_containerPredicate(this)) {
+ let rangeIndex = 0;
+ let range = ranges[rangeIndex];
+ for (const event of this.childEvents()) {
+ while (event.start > range.max) {
+ rangeIndex++;
+ if (rangeIndex >= ranges.length) return;
+ range = ranges[rangeIndex];
+ }
+ if (event.end >= range.min) yield event;
+ }
+ }
+ },
+
+ getSlicesOfName(title) {
+ const slices = [];
+ for (let i = 0; i < this.slices.length; i++) {
+ if (this.slices[i].title === title) {
+ slices.push(this.slices[i]);
+ }
+ }
+ return slices;
+ },
+
+ iterSlicesInTimeRange(callback, start, end) {
+ const ret = [];
+ tr.b.iterateOverIntersectingIntervals(
+ this.topLevelSlices,
+ function(s) { return s.start; },
+ function(s) { return s.duration; },
+ start,
+ end,
+ function(topLevelSlice) {
+ callback(topLevelSlice);
+ for (const slice of topLevelSlice.enumerateAllDescendents()) {
+ callback(slice);
+ }
+ });
+ return ret;
+ },
+
+ findFirstSlice() {
+ if (!this.haveTopLevelSlicesBeenBuilt) {
+ throw new Error('Nope');
+ }
+ if (0 === this.slices.length) return undefined;
+ return this.slices[0];
+ },
+
+ findSliceAtTs(ts) {
+ if (!this.haveTopLevelSlicesBeenBuilt) throw new Error('Nope');
+ let i = tr.b.findIndexInSortedClosedIntervals(
+ this.topLevelSlices,
+ getSliceLo, getSliceHi,
+ ts);
+ if (i === -1 || i === this.topLevelSlices.length) {
+ return undefined;
+ }
+
+ let curSlice = this.topLevelSlices[i];
+
+ // Now recurse on slice looking for subSlice of given ts.
+ while (true) {
+ i = tr.b.findIndexInSortedClosedIntervals(
+ curSlice.subSlices,
+ getSliceLo, getSliceHi,
+ ts);
+ if (i === -1 || i === curSlice.subSlices.length) {
+ return curSlice;
+ }
+ curSlice = curSlice.subSlices[i];
+ }
+ },
+
+ findNextSliceAfter(ts, refGuid) {
+ let i = tr.b.findLowIndexInSortedArray(this.slices, getSliceLo, ts);
+ if (i === this.slices.length) {
+ return undefined;
+ }
+ for (; i < this.slices.length; i++) {
+ const slice = this.slices[i];
+ if (slice.start > ts) return slice;
+ if (slice.guid <= refGuid) continue;
+ return slice;
+ }
+ return undefined;
+ },
+
+ /**
+ * This function assumes that if any slice has a cpu duration then
+ * then the group is considered to have cpu duration.
+ */
+ hasCpuDuration_() {
+ if (this.slices.some(function(slice) {
+ return slice.cpuDuration !== undefined;
+ })) return true;
+ return false;
+ },
+
+ /**
+ * Construct subSlices for this group.
+ * Populate the group topLevelSlices, parent slices get a subSlices[],
+ * a selfThreadTime and a selfTime, child slices get a parentSlice
+ * reference.
+ */
+ createSubSlices() {
+ this.haveTopLevelSlicesBeenBuilt = true;
+ this.createSubSlicesImpl_();
+ // If another source has cpu time, we can augment the cpuDuration of the
+ // slices in the group with that cpu time. This should be done only if
+ // the original source does not include cpuDuration.
+ if (!this.hasCpuDuration_() && this.parentContainer.timeSlices) {
+ this.addCpuTimeToSubslices_(this.parentContainer.timeSlices);
+ }
+ this.slices.forEach(function(slice) {
+ let selfTime = slice.duration;
+ for (let i = 0; i < slice.subSlices.length; i++) {
+ selfTime -= slice.subSlices[i].duration;
+ }
+ slice.selfTime = selfTime;
+
+ if (slice.cpuDuration === undefined) return;
+
+ let cpuSelfTime = slice.cpuDuration;
+ for (let i = 0; i < slice.subSlices.length; i++) {
+ if (slice.subSlices[i].cpuDuration !== undefined) {
+ cpuSelfTime -= slice.subSlices[i].cpuDuration;
+ }
+ }
+ slice.cpuSelfTime = cpuSelfTime;
+ });
+ },
+ createSubSlicesImpl_() {
+ const precisionUnit = this.model.intrinsicTimeUnit;
+
+
+ // Note that this doesn't check whether |child| should be added to
+ // |parent|'s descendant slices instead of |parent| directly.
+ function addSliceIfBounds(parent, child) {
+ if (parent.bounds(child, precisionUnit)) {
+ child.parentSlice = parent;
+ if (parent.subSlices === undefined) {
+ parent.subSlices = [];
+ }
+ parent.subSlices.push(child);
+ return true;
+ }
+ return false;
+ }
+
+ if (!this.slices.length) return;
+
+ const ops = [];
+ for (let i = 0; i < this.slices.length; i++) {
+ if (this.slices[i].subSlices) {
+ this.slices[i].subSlices.splice(0,
+ this.slices[i].subSlices.length);
+ }
+ ops.push(i);
+ }
+
+ const originalSlices = this.slices;
+ ops.sort(function(ix, iy) {
+ const x = originalSlices[ix];
+ const y = originalSlices[iy];
+ if (x.start !== y.start) {
+ return x.start - y.start;
+ }
+
+ // Elements get inserted into the slices array in order of when the
+ // slices start. Because slices must be properly nested, we break
+ // start-time ties by assuming that the elements appearing earlier
+ // in the slices array (and thus ending earlier) start earlier.
+ return ix - iy;
+ });
+
+ const slices = new Array(this.slices.length);
+ for (let i = 0; i < ops.length; i++) {
+ slices[i] = originalSlices[ops[i]];
+ }
+
+ // Actually build the subrows.
+ let rootSlice = slices[0];
+ this.topLevelSlices = [];
+ this.topLevelSlices.push(rootSlice);
+ rootSlice.isTopLevel = true;
+ for (let i = 1; i < slices.length; i++) {
+ const slice = slices[i];
+ while (rootSlice !== undefined &&
+ (!addSliceIfBounds(rootSlice, slice))) {
+ rootSlice = rootSlice.parentSlice;
+ }
+ if (rootSlice === undefined) {
+ this.topLevelSlices.push(slice);
+ slice.isTopLevel = true;
+ }
+ rootSlice = slice;
+ }
+
+ // Keep the slices in sorted form.
+ this.slices = slices;
+ },
+ addCpuTimeToSubslices_(timeSlices) {
+ const SCHEDULING_STATE = tr.model.SCHEDULING_STATE;
+ let sliceIdx = 0;
+ timeSlices.forEach(function(timeSlice) {
+ if (timeSlice.schedulingState === SCHEDULING_STATE.RUNNING) {
+ while (sliceIdx < this.topLevelSlices.length) {
+ if (this.addCpuTimeToSubslice_(this.topLevelSlices[sliceIdx],
+ timeSlice)) {
+ // The current top-level slice and children are fully
+ // accounted for, proceed to next top-level slice.
+ sliceIdx++;
+ } else {
+ // The current top-level runs beyond the time slice, break out
+ // so we can potentially add more time slices to it
+ break;
+ }
+ }
+ }
+ }, this);
+ },
+ /* Add run-time of this timeSlice to the passed in slice
+ * and all of it's children (recursively).
+ * Returns whether the slice ends before or at the end of the
+ * time slice, signaling we are done with this slice.
+ */
+ addCpuTimeToSubslice_(slice, timeSlice) {
+ // Make sure they overlap
+ if (slice.start > timeSlice.end || slice.end < timeSlice.start) {
+ return slice.end <= timeSlice.end;
+ }
+
+ // Compute actual overlap
+ let duration = timeSlice.duration;
+ if (slice.start > timeSlice.start) {
+ duration -= slice.start - timeSlice.start;
+ }
+ if (timeSlice.end > slice.end) {
+ duration -= timeSlice.end - slice.end;
+ }
+
+ if (slice.cpuDuration) {
+ slice.cpuDuration += duration;
+ } else {
+ slice.cpuDuration = duration;
+ }
+
+ for (let i = 0; i < slice.subSlices.length; i++) {
+ this.addCpuTimeToSubslice_(slice.subSlices[i], timeSlice);
+ }
+
+ return slice.end <= timeSlice.end;
+ }
+ };
+
+ /**
+ * Merge two slice groups.
+ *
+ * If the two groups do not nest properly some of the slices of groupB will
+ * be split to accomodate the improper nesting. This is done to accomodate
+ * combined kernel and userland call stacks on Android. Because userland
+ * tracing is done by writing to the trace_marker file, the kernel calls
+ * that get invoked as part of that write may not be properly nested with
+ * the userland call trace. For example the following sequence may occur:
+ *
+ * kernel enter sys_write (the write to trace_marker)
+ * user enter some_function
+ * kernel exit sys_write
+ * ...
+ * kernel enter sys_write (the write to trace_marker)
+ * user exit some_function
+ * kernel exit sys_write
+ *
+ * This is handled by splitting the sys_write call into two slices as
+ * follows:
+ *
+ * | sys_write | some_function | sys_write (cont.) |
+ * | sys_write (cont.) | | sys_write |
+ *
+ * The colorId of both parts of the split slices are kept the same, and the
+ * " (cont.)" suffix is appended to the later parts of a split slice.
+ *
+ * The two input SliceGroups are not modified by this, and the merged
+ * SliceGroup will contain a copy of each of the input groups' slices (those
+ * copies may be split).
+ */
+ SliceGroup.merge = function(groupA, groupB) {
+ // This is implemented by traversing the two slice groups in reverse
+ // order. The slices in each group are sorted by ascending end-time, so
+ // we must do the traversal from back to front in order to maintain the
+ // sorting.
+ //
+ // We traverse the two groups simultaneously, merging as we go. At each
+ // iteration we choose the group from which to take the next slice based
+ // on which group's next slice has the greater end-time. During this
+ // traversal we maintain a stack of currently "open" slices for each input
+ // group. A slice is considered "open" from the time it gets reached in
+ // our input group traversal to the time we reach an slice in this
+ // traversal with an end-time before the start time of the "open" slice.
+ //
+ // Each time a slice from groupA is opened or closed (events corresponding
+ // to the end-time and start-time of the input slice, respectively) we
+ // split all of the currently open slices from groupB.
+
+ if (groupA.openPartialSlices_.length > 0) {
+ throw new Error('groupA has open partial slices');
+ }
+
+ if (groupB.openPartialSlices_.length > 0) {
+ throw new Error('groupB has open partial slices');
+ }
+
+ if (groupA.parentContainer !== groupB.parentContainer) {
+ throw new Error('Different parent threads. Cannot merge');
+ }
+
+ if (groupA.sliceConstructor !== groupB.sliceConstructor) {
+ throw new Error('Different slice constructors. Cannot merge');
+ }
+
+ const result = new SliceGroup(groupA.parentContainer,
+ groupA.sliceConstructor,
+ groupA.name_);
+
+ const slicesA = groupA.slices;
+ const slicesB = groupB.slices;
+ let idxA = 0;
+ let idxB = 0;
+ const openA = [];
+ const openB = [];
+
+ const splitOpenSlices = function(when) {
+ for (let i = 0; i < openB.length; i++) {
+ const oldSlice = openB[i];
+ const oldEnd = oldSlice.end;
+ if (when < oldSlice.start || oldEnd < when) {
+ throw new Error('slice should not be split');
+ }
+
+ const newSlice = result.copySlice(oldSlice);
+ newSlice.start = when;
+ newSlice.duration = oldEnd - when;
+ if (newSlice.title.indexOf(' (cont.)') === -1) {
+ newSlice.title += ' (cont.)';
+ }
+ oldSlice.duration = when - oldSlice.start;
+ openB[i] = newSlice;
+ result.pushSlice(newSlice);
+ }
+ };
+
+ const closeOpenSlices = function(upTo) {
+ while (openA.length > 0 || openB.length > 0) {
+ const nextA = openA[openA.length - 1];
+ const nextB = openB[openB.length - 1];
+ const endA = nextA && nextA.end;
+ const endB = nextB && nextB.end;
+
+ if ((endA === undefined || endA > upTo) &&
+ (endB === undefined || endB > upTo)) {
+ return;
+ }
+
+ if (endB === undefined || endA < endB) {
+ splitOpenSlices(endA);
+ openA.pop();
+ } else {
+ openB.pop();
+ }
+ }
+ };
+
+ while (idxA < slicesA.length || idxB < slicesB.length) {
+ const sA = slicesA[idxA];
+ const sB = slicesB[idxB];
+ let nextSlice;
+ let isFromB;
+
+ if (sA === undefined || (sB !== undefined && sA.start > sB.start)) {
+ nextSlice = result.copySlice(sB);
+ isFromB = true;
+ idxB++;
+ } else {
+ nextSlice = result.copySlice(sA);
+ isFromB = false;
+ idxA++;
+ }
+
+ closeOpenSlices(nextSlice.start);
+
+ result.pushSlice(nextSlice);
+
+ if (isFromB) {
+ openB.push(nextSlice);
+ } else {
+ splitOpenSlices(nextSlice.start);
+ openA.push(nextSlice);
+ }
+ }
+
+ closeOpenSlices();
+
+ return result;
+ };
+
+ return {
+ SliceGroup,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/slice_group_test.html b/chromium/third_party/catapult/tracing/tracing/model/slice_group_test.html
new file mode 100644
index 00000000000..573456fba82
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/slice_group_test.html
@@ -0,0 +1,935 @@
+<!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/model.html">
+<link rel="import" href="/tracing/model/slice_group.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const Slice = tr.model.Slice;
+ const SliceGroup = tr.model.SliceGroup;
+ const newSliceEx = tr.c.TestUtils.newSliceEx;
+ const newThreadSlice = tr.c.TestUtils.newThreadSlice;
+ const newModel = tr.c.TestUtils.newModel;
+ const newFakeThread = tr.c.TestUtils.newFakeThread;
+
+ test('basicBeginEnd', function() {
+ const group = new SliceGroup(newFakeThread());
+ assert.strictEqual(group.openSliceCount, 0);
+ const sliceA = group.beginSlice('', 'a', 1, {a: 1});
+ assert.strictEqual(group.openSliceCount, 1);
+ assert.strictEqual(sliceA.title, 'a');
+ assert.strictEqual(sliceA.start, 1);
+ assert.strictEqual(sliceA.args.a, 1);
+
+ const sliceB = group.endSlice(3);
+ assert.strictEqual(sliceA, sliceB);
+ assert.strictEqual(sliceB.duration, 2);
+ });
+
+ test('subSlicesBuilderBasic', function() {
+ const group = new SliceGroup(newFakeThread());
+ const sA = group.pushSlice(newSliceEx({title: 'a', start: 1, duration: 2}));
+ const sB = group.pushSlice(newSliceEx({title: 'b', start: 3, duration: 1}));
+
+ group.createSubSlices();
+
+ assert.strictEqual(group.topLevelSlices.length, 2);
+ assert.deepEqual(group.topLevelSlices, [sA, sB]);
+ assert.strictEqual(group.findSliceAtTs(0), undefined);
+ assert.strictEqual(group.findSliceAtTs(1), sA);
+ assert.strictEqual(group.findSliceAtTs(3), sA);
+ assert.strictEqual(group.findSliceAtTs(3.1), sB);
+ assert.strictEqual(group.findSliceAtTs(4), sB);
+ assert.strictEqual(group.findSliceAtTs(5), undefined);
+ });
+
+ test('subSlicesBuilderBasic2', function() {
+ const group = new SliceGroup(newFakeThread());
+ const sA = group.pushSlice(newSliceEx({title: 'a', start: 1, duration: 4}));
+ const sB = group.pushSlice(newSliceEx({title: 'b', start: 3, duration: 1}));
+
+ group.createSubSlices();
+
+ assert.strictEqual(group.topLevelSlices.length, 1);
+ assert.deepEqual(group.topLevelSlices, [sA]);
+
+ assert.strictEqual(sA.subSlices.length, 1);
+ assert.deepEqual(sA.subSlices, [sB]);
+ assert.strictEqual(sA.selfTime, 3);
+
+ assert.strictEqual(sA, sB.parentSlice);
+ assert.isTrue(sA.isTopLevel);
+ assert.isFalse(sB.isTopLevel);
+ });
+
+ test('subSlicesBuilderNestedExactly', function() {
+ const group = new SliceGroup(newFakeThread());
+ const sB = group.pushSlice(newSliceEx({title: 'b', start: 1, duration: 4}));
+ const sA = group.pushSlice(newSliceEx({title: 'a', start: 1, duration: 4}));
+
+ group.createSubSlices();
+
+ assert.strictEqual(group.topLevelSlices.length, 1);
+ assert.deepEqual(group.topLevelSlices, [sB]);
+
+ assert.strictEqual(sB.subSlices.length, 1);
+ assert.deepEqual(sB.subSlices, [sA]);
+ assert.strictEqual(sB.selfTime, 0);
+
+ assert.strictEqual(sB, sA.parentSlice);
+ assert.isTrue(sB.isTopLevel);
+ assert.isFalse(sA.isTopLevel);
+
+ assert.strictEqual(group.findSliceAtTs(0), undefined);
+ assert.strictEqual(group.findSliceAtTs(1), sA);
+ assert.strictEqual(group.findSliceAtTs(2), sA);
+ assert.strictEqual(group.findSliceAtTs(5), sA);
+ assert.strictEqual(group.findSliceAtTs(6), undefined);
+ });
+
+ test('subSlicesBuilderInstantEvents', function() {
+ const group = new SliceGroup(newFakeThread());
+ const sA = group.pushSlice(newSliceEx({title: 'a', start: 1, duration: 0}));
+ const sB = group.pushSlice(newSliceEx({title: 'b', start: 2, duration: 0}));
+
+ group.createSubSlices();
+
+ assert.strictEqual(group.topLevelSlices.length, 2);
+ assert.deepEqual(group.topLevelSlices, [sA, sB]);
+ assert.strictEqual(group.findSliceAtTs(1), sA);
+ assert.strictEqual(group.findSliceAtTs(1.5), undefined);
+ assert.strictEqual(group.findSliceAtTs(2), sB);
+ });
+
+ test('subSlicesBuilderTwoInstantEvents', function() {
+ const group = new SliceGroup(newFakeThread());
+ const sA = group.pushSlice(newSliceEx({title: 'a', start: 1, duration: 0}));
+ const sB = group.pushSlice(newSliceEx({title: 'b', start: 1, duration: 0}));
+
+ group.createSubSlices();
+
+ assert.strictEqual(group.topLevelSlices.length, 1);
+ assert.deepEqual(group.topLevelSlices, [sA]);
+
+ assert.strictEqual(sA.subSlices.length, 1);
+ assert.deepEqual(sA.subSlices, [sB]);
+ assert.strictEqual(sA.selfTime, 0);
+
+ assert.strictEqual(sA, sB.parentSlice);
+ assert.isTrue(sA.isTopLevel);
+ assert.isFalse(sB.isTopLevel);
+ assert.strictEqual(group.findSliceAtTs(1), sB);
+ assert.strictEqual(group.findSliceAtTs(1.0001), undefined);
+ });
+
+ test('subSlicesBuilderOutOfOrderAddition', function() {
+ const group = new SliceGroup(newFakeThread());
+
+ // Pattern being tested:
+ // [ a ][ b ]
+ // Where insertion is done backward.
+ const sB = group.pushSlice(newSliceEx({title: 'b', start: 3, duration: 1}));
+ const sA = group.pushSlice(newSliceEx({title: 'a', start: 1, duration: 2}));
+
+ group.createSubSlices();
+
+ assert.strictEqual(group.topLevelSlices.length, 2);
+ assert.deepEqual(group.topLevelSlices, [sA, sB]);
+ assert.isTrue(sA.isTopLevel);
+ assert.isTrue(sB.isTopLevel);
+ assert.strictEqual(group.findSliceAtTs(3), sA);
+ });
+
+ test('subRowBuilderOutOfOrderAddition2', function() {
+ const group = new SliceGroup(newFakeThread());
+
+ // Pattern being tested:
+ // [ a ]
+ // [ b ]
+ // Where insertion is done backward.
+ const sB = group.pushSlice(newSliceEx({title: 'b', start: 3, duration: 1}));
+ const sA = group.pushSlice(newSliceEx({title: 'a', start: 1, duration: 5}));
+
+ group.createSubSlices();
+
+ assert.strictEqual(group.topLevelSlices.length, 1);
+ assert.deepEqual(group.topLevelSlices, [sA]);
+
+ assert.strictEqual(sA.subSlices.length, 1);
+ assert.deepEqual(sA.subSlices, [sB]);
+ assert.strictEqual(sA.selfTime, 4);
+
+ assert.strictEqual(sA, sB.parentSlice);
+ assert.isTrue(sA.isTopLevel);
+ assert.isFalse(sB.isTopLevel);
+ assert.strictEqual(group.findSliceAtTs(1.5), sA);
+ assert.strictEqual(group.findSliceAtTs(3), sB);
+ assert.strictEqual(group.findSliceAtTs(3.5), sB);
+ assert.strictEqual(group.findSliceAtTs(4), sB);
+ assert.strictEqual(group.findSliceAtTs(4.5), sA);
+ });
+
+ test('subSlicesBuilderOnNestedZeroLength', function() {
+ const group = new SliceGroup(newFakeThread());
+
+ // Pattern being tested:
+ // [ a ]
+ // [ b1 ] []<- b2 where b2.duration = 0 and b2.end === a.end.
+ const sA = group.pushSlice(newSliceEx({title: 'a', start: 1, duration: 3}));
+ const sB1 = group.pushSlice(newSliceEx(
+ {title: 'b1', start: 1, duration: 2}));
+ const sB2 = group.pushSlice(newSliceEx(
+ {title: 'b2', start: 4, duration: 0}));
+
+ group.createSubSlices();
+
+ assert.strictEqual(group.topLevelSlices.length, 1);
+ assert.deepEqual(group.topLevelSlices, [sA]);
+
+ assert.strictEqual(sA.subSlices.length, 2);
+ assert.deepEqual(sA.subSlices, [sB1, sB2]);
+ assert.strictEqual(sA.selfTime, 1);
+
+ assert.strictEqual(sA, sB1.parentSlice);
+ assert.strictEqual(sA, sB2.parentSlice);
+ assert.strictEqual(group.findSliceAtTs(1), sB1);
+ assert.strictEqual(group.findSliceAtTs(4), sB2);
+ });
+
+ test('subSlicesBuilderOnGroup1', function() {
+ const group = new SliceGroup(newFakeThread());
+
+ // Pattern being tested:
+ // [ a ] [ c ]
+ // [ b ]
+ const sA = group.pushSlice(newSliceEx({title: 'a', start: 1, duration: 3}));
+ const sB = group.pushSlice(newSliceEx(
+ {title: 'b', start: 1.5, duration: 1}));
+ const sC = group.pushSlice(newSliceEx({title: 'c', start: 5, duration: 0}));
+
+ group.createSubSlices();
+
+ assert.strictEqual(group.topLevelSlices.length, 2);
+ assert.deepEqual(group.topLevelSlices, [sA, sC]);
+
+ assert.strictEqual(sA.subSlices.length, 1);
+ assert.deepEqual(sA.subSlices, [sB]);
+ assert.strictEqual(sA.selfTime, 2);
+
+ assert.strictEqual(sA, sB.parentSlice);
+ assert.strictEqual(group.findSliceAtTs(1), sA);
+ assert.strictEqual(group.findSliceAtTs(2), sB);
+ assert.strictEqual(group.findSliceAtTs(3), sA);
+ assert.strictEqual(group.findSliceAtTs(4.5), undefined);
+ assert.strictEqual(group.findSliceAtTs(5), sC);
+ });
+
+ test('subSlicesBuilderOnGroup2', function() {
+ const group = new SliceGroup(newFakeThread());
+
+ // Pattern being tested:
+ // [ a ] [ d ]
+ // [ b ]
+ // [ c ]
+ const sA = group.pushSlice(newSliceEx({title: 'a', start: 1, duration: 3}));
+ const sB = group.pushSlice(newSliceEx(
+ {title: 'b', start: 1.5, duration: 1}));
+ const sC = group.pushSlice(newSliceEx(
+ {title: 'c', start: 1.75, duration: 0.5}));
+ const sD = group.pushSlice(newSliceEx(
+ {title: 'd', start: 5, duration: 0.25}));
+
+ group.createSubSlices();
+
+ assert.strictEqual(group.topLevelSlices.length, 2);
+ assert.deepEqual(group.topLevelSlices, [sA, sD]);
+
+ assert.strictEqual(sA.subSlices.length, 1);
+ assert.deepEqual(sA.subSlices, [sB]);
+ assert.strictEqual(sA.selfTime, 2);
+
+ assert.strictEqual(sA, sB.parentSlice);
+ assert.strictEqual(sB.subSlices.length, 1);
+ assert.deepEqual(sB.subSlices, [sC]);
+ assert.strictEqual(sB.selfTime, 0.5);
+
+ assert.strictEqual(sB, sC.parentSlice);
+ assert.strictEqual(group.findSliceAtTs(2), sC);
+ });
+
+ test('findFirstSlice', function() {
+ const group = new SliceGroup(newFakeThread());
+ // Pattern being tested:
+ // [ a ] [ d ]
+ // [b] [ c ] where b is dur=0
+ const sC = group.pushSlice(newSliceEx({title: 'c', start: 2, end: 3}));
+ const sD = group.pushSlice(newSliceEx({title: 'd', start: 5, end: 6}));
+ const sA = group.pushSlice(newSliceEx({title: 'a', start: 1, end: 4}));
+ const sB = group.pushSlice(newSliceEx({title: 'b', start: 1, end: 1}));
+
+ assert.throws(group.findFirstSlice);
+
+ group.createSubSlices();
+
+ assert.strictEqual(group.findFirstSlice(), sA);
+ });
+
+ test('findNextSliceAfterBasic', function() {
+ const group = new SliceGroup(newFakeThread());
+ // Pattern being tested:
+ // [ a ] [ d ]
+ // [b] [ c ] where b is dur=0
+ const sA = group.pushSlice(newSliceEx({title: 'a', start: 1, end: 4}));
+ const sB = group.pushSlice(newSliceEx({title: 'b', start: 1, end: 1}));
+ const sC = group.pushSlice(newSliceEx({title: 'c', start: 2, end: 3}));
+ const sD = group.pushSlice(newSliceEx({title: 'd', start: 5, end: 6}));
+
+ group.createSubSlices();
+
+ assert.strictEqual(group.findNextSliceAfter(0, 0), sA);
+ assert.strictEqual(group.findNextSliceAfter(1, sA.guid), sB);
+ assert.strictEqual(group.findNextSliceAfter(1, sB.guid), sC);
+ assert.strictEqual(group.findNextSliceAfter(2, sC.guid), sD);
+ assert.strictEqual(group.findNextSliceAfter(6, 0), undefined);
+ });
+
+ test('subSlicesBuilderTolerateFPInaccuracy', function() {
+ const group = new SliceGroup(newFakeThread());
+
+ // Pattern being tested:
+ // [ a ]
+ // [ b ] where b.end contains a tiny FP calculation error.
+ const sA = group.pushSlice(newSliceEx({title: 'a', start: 1, duration: 3}));
+ const sB = group.pushSlice(newSliceEx(
+ {title: 'b', start: 1, duration: 3.0000000001}));
+
+ group.createSubSlices();
+
+ assert.strictEqual(group.topLevelSlices.length, 1);
+ assert.deepEqual(group.topLevelSlices, [sA]);
+
+ assert.strictEqual(sA.subSlices.length, 1);
+ assert.deepEqual(sA.subSlices, [sB]);
+ assert.strictEqual(sA, sB.parentSlice);
+ assert.strictEqual(group.findSliceAtTs(1), sB);
+ assert.strictEqual(group.findSliceAtTs(3), sB);
+ });
+
+ test('basicMerge', function() {
+ function TestSlice(
+ cat, title, colorId, start, args, opt_duration,
+ opt_cpuStart, opt_cpuDuration) {
+ Slice.call(this, cat, title, colorId, start, args, opt_duration,
+ opt_cpuStart, opt_cpuDuration);
+ }
+ TestSlice.prototype = {
+ __proto__: Slice.prototype
+ };
+ TestSlice.subTypes = {
+ getConstructor(category, title) {
+ return TestSlice;
+ }
+ };
+
+ const thread = newFakeThread();
+ const a = new SliceGroup(thread, TestSlice);
+ const b = new SliceGroup(thread, TestSlice);
+ a.beginSlice('', 'one', 1);
+ a.endSlice(2);
+ b.beginSlice('', 'two', 3);
+ b.endSlice(5);
+
+ const m = SliceGroup.merge(a, b);
+ assert.strictEqual(m.slices.length, 2);
+
+ assert.strictEqual(m.slices[0].title, 'one');
+ assert.strictEqual(m.slices[0].start, 1);
+ assert.strictEqual(m.slices[0].duration, 1);
+
+ assert.strictEqual(m.slices[1].title, 'two');
+ assert.strictEqual(m.slices[1].start, 3);
+ assert.strictEqual(m.slices[1].duration, 2);
+
+ // ensure constructor merged correctly
+ assert.strictEqual(m.sliceConstructor, TestSlice);
+ });
+
+ test('nestedMerge', function() {
+ const thread = newFakeThread();
+ const a = new SliceGroup(thread);
+ const b = new SliceGroup(thread);
+ a.beginSlice('', 'one', 1);
+ a.endSlice(4);
+ b.beginSlice('', 'two', 2);
+ b.endSlice(3);
+
+ const m = SliceGroup.merge(a, b);
+ assert.strictEqual(m.slices.length, 2);
+
+ assert.strictEqual(m.slices[0].title, 'one');
+ assert.strictEqual(m.slices[0].start, 1);
+ assert.strictEqual(m.slices[0].duration, 3);
+
+ assert.strictEqual(m.slices[1].title, 'two');
+ assert.strictEqual(m.slices[1].start, 2);
+ assert.strictEqual(m.slices[1].duration, 1);
+ });
+
+ test('startSplitMerge', function() {
+ const thread = newFakeThread();
+ const a = new SliceGroup(thread);
+ const b = new SliceGroup(thread);
+ a.beginSlice('', 'one', 2);
+ a.endSlice(4);
+ b.beginSlice('', 'two', 1);
+ b.endSlice(3);
+
+ const m = SliceGroup.merge(a, b);
+ assert.strictEqual(m.slices.length, 3);
+
+ assert.strictEqual(m.slices[0].title, 'two');
+ assert.strictEqual(m.slices[0].start, 1);
+ assert.strictEqual(m.slices[0].duration, 1);
+
+ assert.strictEqual(m.slices[1].title, 'one');
+ assert.strictEqual(m.slices[1].start, 2);
+ assert.strictEqual(m.slices[1].duration, 2);
+
+ assert.strictEqual(m.slices[2].title, 'two (cont.)');
+ assert.strictEqual(m.slices[2].start, 2);
+ assert.strictEqual(m.slices[2].duration, 1);
+ });
+
+ test('startSplitTwoMerge', function() {
+ const thread = newFakeThread();
+ const a = new SliceGroup(thread);
+ const b = new SliceGroup(thread);
+ a.beginSlice('', 'one', 3);
+ a.endSlice(6);
+ b.beginSlice('', 'two', 1);
+ b.beginSlice('', 'three', 2);
+ b.endSlice(4);
+ b.endSlice(5);
+
+ const m = SliceGroup.merge(a, b);
+ assert.strictEqual(m.slices.length, 5);
+
+ assert.strictEqual(m.slices[0].title, 'two');
+ assert.strictEqual(m.slices[0].start, 1);
+ assert.strictEqual(m.slices[0].duration, 2);
+
+ assert.strictEqual(m.slices[1].title, 'three');
+ assert.strictEqual(m.slices[1].start, 2);
+ assert.strictEqual(m.slices[1].duration, 1);
+
+ assert.strictEqual(m.slices[2].title, 'one');
+ assert.strictEqual(m.slices[2].start, 3);
+ assert.strictEqual(m.slices[2].duration, 3);
+
+ assert.strictEqual(m.slices[3].title, 'two (cont.)');
+ assert.strictEqual(m.slices[3].start, 3);
+ assert.strictEqual(m.slices[3].duration, 2);
+
+ assert.strictEqual(m.slices[4].title, 'three (cont.)');
+ assert.strictEqual(m.slices[4].start, 3);
+ assert.strictEqual(m.slices[4].duration, 1);
+ });
+
+ test('startSplitTwiceMerge', function() {
+ const thread = newFakeThread();
+ const a = new SliceGroup(thread);
+ const b = new SliceGroup(thread);
+ a.beginSlice('', 'one', 2);
+ a.beginSlice('', 'two', 3);
+ a.endSlice(5);
+ a.endSlice(6);
+ b.beginSlice('', 'three', 1);
+ b.endSlice(4);
+
+ const m = SliceGroup.merge(a, b);
+ assert.strictEqual(m.slices.length, 5);
+
+ assert.strictEqual(m.slices[0].title, 'three');
+ assert.strictEqual(m.slices[0].start, 1);
+ assert.strictEqual(m.slices[0].duration, 1);
+
+ assert.strictEqual(m.slices[1].title, 'one');
+ assert.strictEqual(m.slices[1].start, 2);
+ assert.strictEqual(m.slices[1].duration, 4);
+
+ assert.strictEqual(m.slices[2].title, 'three (cont.)');
+ assert.strictEqual(m.slices[2].start, 2);
+ assert.strictEqual(m.slices[2].duration, 1);
+
+ assert.strictEqual(m.slices[3].title, 'two');
+ assert.strictEqual(m.slices[3].start, 3);
+ assert.strictEqual(m.slices[3].duration, 2);
+
+ assert.strictEqual(m.slices[4].title, 'three (cont.)');
+ assert.strictEqual(m.slices[4].start, 3);
+ assert.strictEqual(m.slices[4].duration, 1);
+ });
+
+ test('endSplitMerge', function() {
+ const thread = newFakeThread();
+ const a = new SliceGroup(thread);
+ const b = new SliceGroup(thread);
+ a.beginSlice('', 'one', 1);
+ a.endSlice(3);
+ b.beginSlice('', 'two', 2);
+ b.endSlice(4);
+
+ const m = SliceGroup.merge(a, b);
+ assert.strictEqual(m.slices.length, 3);
+
+ assert.strictEqual(m.slices[0].title, 'one');
+ assert.strictEqual(m.slices[0].start, 1);
+ assert.strictEqual(m.slices[0].duration, 2);
+
+ assert.strictEqual(m.slices[1].title, 'two');
+ assert.strictEqual(m.slices[1].start, 2);
+ assert.strictEqual(m.slices[1].duration, 1);
+
+ assert.strictEqual(m.slices[2].title, 'two (cont.)');
+ assert.strictEqual(m.slices[2].start, 3);
+ assert.strictEqual(m.slices[2].duration, 1);
+ });
+
+ test('endSplitTwoMerge', function() {
+ const thread = newFakeThread();
+ const a = new SliceGroup(thread);
+ const b = new SliceGroup(thread);
+ a.beginSlice('', 'one', 1);
+ a.endSlice(4);
+ b.beginSlice('', 'two', 2);
+ b.beginSlice('', 'three', 3);
+ b.endSlice(5);
+ b.endSlice(6);
+
+ const m = SliceGroup.merge(a, b);
+ assert.strictEqual(m.slices.length, 5);
+
+ assert.strictEqual(m.slices[0].title, 'one');
+ assert.strictEqual(m.slices[0].start, 1);
+ assert.strictEqual(m.slices[0].duration, 3);
+
+ assert.strictEqual(m.slices[1].title, 'two');
+ assert.strictEqual(m.slices[1].start, 2);
+ assert.strictEqual(m.slices[1].duration, 2);
+
+ assert.strictEqual(m.slices[2].title, 'three');
+ assert.strictEqual(m.slices[2].start, 3);
+ assert.strictEqual(m.slices[2].duration, 1);
+
+ assert.strictEqual(m.slices[3].title, 'two (cont.)');
+ assert.strictEqual(m.slices[3].start, 4);
+ assert.strictEqual(m.slices[3].duration, 2);
+
+ assert.strictEqual(m.slices[4].title, 'three (cont.)');
+ assert.strictEqual(m.slices[4].start, 4);
+ assert.strictEqual(m.slices[4].duration, 1);
+ });
+
+ test('endSplitTwiceMerge', function() {
+ const thread = newFakeThread();
+ const a = new SliceGroup(thread);
+ const b = new SliceGroup(thread);
+ a.beginSlice('', 'one', 1);
+ a.beginSlice('', 'two', 2);
+ a.endSlice(4);
+ a.endSlice(5);
+ b.beginSlice('', 'three', 3);
+ b.endSlice(6);
+
+ const m = SliceGroup.merge(a, b);
+ assert.strictEqual(m.slices.length, 5);
+
+ assert.strictEqual(m.slices[0].title, 'one');
+ assert.strictEqual(m.slices[0].start, 1);
+ assert.strictEqual(m.slices[0].duration, 4);
+
+ assert.strictEqual(m.slices[1].title, 'two');
+ assert.strictEqual(m.slices[1].start, 2);
+ assert.strictEqual(m.slices[1].duration, 2);
+
+ assert.strictEqual(m.slices[2].title, 'three');
+ assert.strictEqual(m.slices[2].start, 3);
+ assert.strictEqual(m.slices[2].duration, 1);
+
+ assert.strictEqual(m.slices[3].title, 'three (cont.)');
+ assert.strictEqual(m.slices[3].start, 4);
+ assert.strictEqual(m.slices[3].duration, 1);
+
+ assert.strictEqual(m.slices[4].title, 'three (cont.)');
+ assert.strictEqual(m.slices[4].start, 5);
+ assert.strictEqual(m.slices[4].duration, 1);
+ });
+
+ // Input:
+ // A: | one | | two |
+ //
+ // B: | three |
+ //
+ // Output:
+ // | one | three | two |
+ // | three | | three |
+ test('splitTwiceMerge', function() {
+ const thread = newFakeThread();
+ const a = new SliceGroup(thread);
+ const b = new SliceGroup(thread);
+ a.beginSlice('', 'one', 1);
+ a.endSlice(3);
+ a.beginSlice('', 'two', 4);
+ a.endSlice(6);
+ b.beginSlice('', 'three', 2);
+ b.endSlice(5);
+
+ const m = SliceGroup.merge(a, b);
+ assert.strictEqual(m.slices.length, 5);
+
+ assert.strictEqual(m.slices[0].title, 'one');
+ assert.strictEqual(m.slices[0].start, 1);
+ assert.strictEqual(m.slices[0].duration, 2);
+
+ assert.strictEqual(m.slices[1].title, 'three');
+ assert.strictEqual(m.slices[1].start, 2);
+ assert.strictEqual(m.slices[1].duration, 1);
+
+ assert.strictEqual(m.slices[2].title, 'three (cont.)');
+ assert.strictEqual(m.slices[2].start, 3);
+ assert.strictEqual(m.slices[2].duration, 1);
+
+ assert.strictEqual(m.slices[3].title, 'two');
+ assert.strictEqual(m.slices[3].start, 4);
+ assert.strictEqual(m.slices[3].duration, 2);
+
+ assert.strictEqual(m.slices[4].title, 'three (cont.)');
+ assert.strictEqual(m.slices[4].start, 4);
+ assert.strictEqual(m.slices[4].duration, 1);
+ });
+
+ test('bounds', function() {
+ const group = new SliceGroup(newFakeThread());
+ group.updateBounds();
+ assert.isUndefined(group.bounds.min);
+ assert.isUndefined(group.bounds.max);
+
+ group.pushSlice(newSliceEx({start: 1, duration: 3}));
+ group.pushSlice(newSliceEx({start: 7, duration: 2}));
+ group.updateBounds();
+ assert.strictEqual(group.bounds.min, 1);
+ assert.strictEqual(group.bounds.max, 9);
+ });
+
+ test('boundsWithPartial', function() {
+ const group = new SliceGroup(newFakeThread());
+ group.beginSlice('', 'a', 7);
+ group.updateBounds();
+ assert.strictEqual(group.bounds.min, 7);
+ assert.strictEqual(group.bounds.max, 7);
+ });
+
+ test('boundsWithTwoPartials', function() {
+ const group = new SliceGroup(newFakeThread());
+ group.beginSlice('', 'a', 0);
+ group.beginSlice('', 'a', 1);
+ group.updateBounds();
+ assert.strictEqual(group.bounds.min, 0);
+ assert.strictEqual(group.bounds.max, 1);
+ });
+
+ test('boundsWithBothPartialAndRegular', function() {
+ const group = new SliceGroup(newFakeThread());
+ group.updateBounds();
+ assert.isUndefined(group.bounds.min);
+ assert.isUndefined(group.bounds.max);
+
+ group.pushSlice(newSliceEx({start: 1, duration: 3}));
+ group.beginSlice('', 'a', 7);
+ group.updateBounds();
+ assert.strictEqual(group.bounds.min, 1);
+ assert.strictEqual(group.bounds.max, 7);
+ });
+
+ test('autocloserBasic', function() {
+ const group = new SliceGroup(newFakeThread());
+ assert.strictEqual(0, group.openSliceCount);
+
+ group.pushSlice(newSliceEx({title: 'a', start: 1, duration: 0.5}));
+
+ group.beginSlice('', 'b', 2);
+ group.beginSlice('', 'c', 2.5);
+ group.endSlice(3);
+
+ group.autoCloseOpenSlices();
+ group.updateBounds();
+
+ assert.strictEqual(group.bounds.min, 1);
+ assert.strictEqual(group.bounds.max, 3);
+ assert.strictEqual(group.slices.length, 3);
+
+ assert.strictEqual(group.slices[0].title, 'a');
+ assert.isFalse(group.slices[0].didNotFinish);
+
+ assert.strictEqual(group.slices[1].title, 'b');
+ assert.isTrue(group.slices[1].didNotFinish);
+ assert.strictEqual(group.slices[1].duration, 1);
+
+ assert.strictEqual(group.slices[2].title, 'c');
+ assert.isFalse(group.slices[2].didNotFinish);
+ });
+
+ test('autocloserWithSubTasks', function() {
+ const group = new SliceGroup(newFakeThread());
+ assert.strictEqual(0, group.openSliceCount);
+
+ group.beginSlice('', 'a', 1);
+ group.beginSlice('', 'b1', 2);
+ group.endSlice(3);
+ group.beginSlice('', 'b2', 3);
+
+ group.autoCloseOpenSlices();
+ assert.strictEqual(group.slices.length, 3);
+
+ assert.strictEqual(group.slices[0].title, 'a');
+ assert.isTrue(group.slices[0].didNotFinish);
+ assert.strictEqual(group.slices[0].duration, 2);
+
+ assert.strictEqual(group.slices[1].title, 'b1');
+ assert.isFalse(group.slices[1].didNotFinish);
+ assert.strictEqual(group.slices[1].duration, 1);
+
+ assert.strictEqual(group.slices[2].title, 'b2');
+ assert.isTrue(group.slices[2].didNotFinish);
+ assert.strictEqual(group.slices[2].duration, 0);
+ });
+
+ test('autocloseCompleteSlice', function() {
+ const group = new SliceGroup(newFakeThread());
+
+ group.pushCompleteSlice('', 'a', 1, undefined);
+ group.pushCompleteSlice('', 'b', 2, 3);
+
+ group.autoCloseOpenSlices();
+ assert.strictEqual(group.slices.length, 2);
+
+ assert.strictEqual(group.slices[0].title, 'a');
+ assert.isTrue(group.slices[0].didNotFinish);
+ assert.strictEqual(group.slices[0].duration, 4);
+
+ assert.strictEqual(group.slices[1].title, 'b');
+ assert.isFalse(group.slices[1].didNotFinish);
+ assert.strictEqual(group.slices[1].duration, 3);
+ });
+
+ test('sliceGroupStableId', function() {
+ const model = new tr.Model();
+ const process = model.getOrCreateProcess(123);
+ const thread = process.getOrCreateThread(456);
+ const group = new SliceGroup(thread);
+
+ assert.strictEqual(process.stableId, 123);
+ assert.strictEqual(thread.stableId, '123.456');
+ assert.strictEqual(group.stableId, '123.456.SliceGroup');
+ });
+
+ test('getSlicesOfName', function() {
+ const group = new SliceGroup(newFakeThread());
+ const expected = [];
+
+ for (let i = 0; i < 10; i++) {
+ const aSlice = newSliceEx({title: 'a', start: i, duration: i + 1});
+ group.pushSlice(aSlice);
+ group.pushSlice(newSliceEx({title: 'b', start: i + 1, duration: i + 2}));
+ expected.push(aSlice);
+ }
+
+ assert.deepEqual(group.getSlicesOfName('a'), expected);
+ });
+
+ test('iterSlicesInTimeRange', function() {
+ const group = new SliceGroup(newFakeThread());
+ const expected = [];
+
+ for (let i = 0; i < 10; i++) {
+ const slice = newSliceEx({title: 'a', start: i, duration: 1});
+ group.pushSlice(slice);
+ if (4 <= i && i <= 7) {
+ expected.push(slice);
+ }
+ }
+ group.createSubSlices();
+
+ const observed = [];
+ group.iterSlicesInTimeRange(function(slice) { observed.push(slice); },
+ 4.5, 7.5);
+ assert.deepEqual(observed, expected);
+ });
+
+ test('computeCpuDurationNoOverlap', function() {
+ const model = new tr.Model();
+ const SCHEDULING_STATE = tr.model.SCHEDULING_STATE;
+ const process = model.getOrCreateProcess(123);
+ const t = process.getOrCreateThread(456);
+ t.timeSlices = [newThreadSlice(t, SCHEDULING_STATE.RUNNING, 20, 20),
+ newThreadSlice(t, SCHEDULING_STATE.SLEEPING, 40, 10),
+ newThreadSlice(t, SCHEDULING_STATE.RUNNING, 50, 10)];
+ const group = new SliceGroup(t);
+ group.pushSlice(newSliceEx({title: 'draw', start: 0, duration: 20}));
+ group.pushSlice(newSliceEx({title: 'render', start: 60, duration: 20}));
+ group.createSubSlices();
+ assert.strictEqual(group.slices[0].cpuDuration, 0);
+ assert.strictEqual(group.slices[1].cpuDuration, 0);
+ });
+
+ test('computeCpuDurationPartialOverlap', function() {
+ const model = new tr.Model();
+ const SCHEDULING_STATE = tr.model.SCHEDULING_STATE;
+ const process = model.getOrCreateProcess(123);
+ const t = process.getOrCreateThread(456);
+ t.timeSlices = [newThreadSlice(t, SCHEDULING_STATE.RUNNING, 20, 20),
+ newThreadSlice(t, SCHEDULING_STATE.SLEEPING, 40, 10),
+ newThreadSlice(t, SCHEDULING_STATE.RUNNING, 50, 10)];
+ const group = new SliceGroup(t);
+ group.pushSlice(newSliceEx({title: 'draw', start: 10, duration: 30}));
+ group.pushSlice(newSliceEx({title: 'render', start: 50, duration: 20}));
+ group.createSubSlices();
+ assert.strictEqual(group.slices[0].cpuDuration, 20);
+ assert.strictEqual(group.slices[1].cpuDuration, 10);
+ });
+
+ test('computeCpuDurationFullOverlap', function() {
+ const model = new tr.Model();
+ const SCHEDULING_STATE = tr.model.SCHEDULING_STATE;
+ const process = model.getOrCreateProcess(123);
+ const t = process.getOrCreateThread(456);
+ t.timeSlices = [newThreadSlice(t, SCHEDULING_STATE.RUNNING, 20, 20),
+ newThreadSlice(t, SCHEDULING_STATE.SLEEPING, 40, 10),
+ newThreadSlice(t, SCHEDULING_STATE.RUNNING, 50, 20)];
+ const group = new SliceGroup(t);
+ group.pushSlice(newSliceEx({title: 'draw', start: 20, duration: 30}));
+ group.pushSlice(newSliceEx({title: 'render', start: 50, duration: 20}));
+ group.createSubSlices();
+ assert.strictEqual(group.slices[0].cpuDuration, 20);
+ assert.strictEqual(group.slices[1].cpuDuration, 20);
+ });
+
+ test('computeCpuSelfDurationWithSubslices', function() {
+ const model = new tr.Model();
+ const SCHEDULING_STATE = tr.model.SCHEDULING_STATE;
+ const process = model.getOrCreateProcess(123);
+ const t = process.getOrCreateThread(456);
+ t.timeSlices = [newThreadSlice(t, SCHEDULING_STATE.RUNNING, 20, 20),
+ newThreadSlice(t, SCHEDULING_STATE.SLEEPING, 40, 10),
+ newThreadSlice(t, SCHEDULING_STATE.RUNNING, 50, 20)];
+ const group = new SliceGroup(t);
+ group.pushSlice(newSliceEx({title: 'draw', start: 20, duration: 30}));
+ group.pushSlice(newSliceEx({title: 'render', start: 21, duration: 8}));
+ group.pushSlice(newSliceEx({title: 'flush', start: 29, duration: 11}));
+ group.createSubSlices();
+ assert.strictEqual(group.slices[0].cpuDuration, 20);
+ assert.strictEqual(group.slices[0].cpuSelfTime, 1);
+ assert.strictEqual(group.slices[1].cpuDuration, 8);
+ assert.strictEqual(group.slices[1].cpuSelfTime, 8);
+ assert.strictEqual(group.slices[2].cpuDuration, 11);
+ assert.strictEqual(group.slices[2].cpuSelfTime, 11);
+ });
+
+ test('computeCpuDurationSmallTimeslices', function() {
+ const model = new tr.Model();
+ const SCHEDULING_STATE = tr.model.SCHEDULING_STATE;
+ const process = model.getOrCreateProcess(123);
+ const t = process.getOrCreateThread(456);
+ t.timeSlices = [newThreadSlice(t, SCHEDULING_STATE.RUNNING, 20, 1),
+ newThreadSlice(t, SCHEDULING_STATE.SLEEPING, 21, 1),
+ newThreadSlice(t, SCHEDULING_STATE.RUNNING, 22, 1),
+ newThreadSlice(t, SCHEDULING_STATE.SLEEPING, 23, 1),
+ newThreadSlice(t, SCHEDULING_STATE.RUNNING, 24, 1),
+ newThreadSlice(t, SCHEDULING_STATE.SLEEPING, 25, 1),
+ newThreadSlice(t, SCHEDULING_STATE.RUNNING, 26, 1),
+ newThreadSlice(t, SCHEDULING_STATE.SLEEPING, 27, 1),
+ newThreadSlice(t, SCHEDULING_STATE.RUNNING, 28, 1),
+ newThreadSlice(t, SCHEDULING_STATE.SLEEPING, 29, 1),
+ newThreadSlice(t, SCHEDULING_STATE.RUNNING, 30, 1)];
+ const group = new SliceGroup(t);
+ group.pushSlice(newSliceEx(
+ {title: 'draw', start: 20, duration: 11})); // 20,[22,24,26,28],30
+ group.pushSlice(newSliceEx(
+ {title: 'render', start: 22, duration: 8})); // 22,[24, 26, 28]
+ group.pushSlice(newSliceEx(
+ {title: 'flush', start: 24, duration: 6})); // 24, 26, 28
+ group.createSubSlices();
+ assert.strictEqual(group.slices[0].cpuDuration, 6);
+ assert.strictEqual(group.slices[0].cpuSelfTime, 2);
+ assert.strictEqual(group.slices[1].cpuDuration, 4);
+ assert.strictEqual(group.slices[1].cpuSelfTime, 1);
+ assert.strictEqual(group.slices[2].cpuDuration, 3);
+ assert.strictEqual(group.slices[2].cpuSelfTime, 3);
+ });
+
+ test('sliceParentContainerSetAtPush', function() {
+ const m = newModel(function(m) {
+ m.process = m.getOrCreateProcess(123);
+ m.thread = m.process.getOrCreateThread(456);
+ m.group = new SliceGroup(m.thread);
+
+ m.sA = m.group.pushSlice(newSliceEx(
+ { title: 'sA', start: 0.0, duration: 10.0 }));
+
+ m.group.createSubSlices();
+ });
+
+ assert.deepEqual(m.sA.parentContainer, m.thread);
+ });
+
+ test('getDescendantEventsInSortedRanges', function() {
+ // Create the following slices:
+ // 0 1 2 3 4 5 6 7 8 9 10
+ // <------------- 0 ------------->
+ // <- 2 -> <---- 3 ---->
+ // <- 1 ->
+ const group = new SliceGroup(newFakeThread());
+ group.pushSlice(newSliceEx({title: 's0', start: 0, end: 10}));
+ group.pushSlice(newSliceEx({title: 's1', start: 7, end: 9}));
+ group.pushSlice(newSliceEx({title: 's2', start: 3, end: 5}));
+ group.pushSlice(newSliceEx({title: 's3', start: 6, end: 10}));
+ group.createSubSlices();
+
+ // [0, 5] intersects s0 and s2.
+ const r1 = new tr.b.math.Range.fromExplicitRange(0, 5);
+ let slices = [...group.getDescendantEventsInSortedRanges([r1])];
+ assert.strictEqual(slices.length, 2);
+ assert.strictEqual(slices[0].title, 's0');
+ assert.strictEqual(slices[1].title, 's2');
+
+ // [10, 11] intersects s0 and s3.
+ const r2 = new tr.b.math.Range.fromExplicitRange(10, 11);
+ slices = [...group.getDescendantEventsInSortedRanges([r2])];
+ assert.strictEqual(slices.length, 2);
+ assert.strictEqual(slices[0].title, 's0');
+ assert.strictEqual(slices[1].title, 's3');
+
+ // [0, 5], [10, 11] intersects s0, s2, and s3.
+ slices = [...group.getDescendantEventsInSortedRanges([r1, r2])];
+ assert.strictEqual(slices.length, 3);
+ assert.strictEqual(slices[0].title, 's0');
+ assert.strictEqual(slices[1].title, 's2');
+ assert.strictEqual(slices[2].title, 's3');
+
+ // Ranges can be nested, too.
+ const r3 = new tr.b.math.Range.fromExplicitRange(1, 2);
+ slices = [...group.getDescendantEventsInSortedRanges([r1, r3])];
+ assert.strictEqual(slices.length, 2);
+ assert.strictEqual(slices[0].title, 's0');
+ assert.strictEqual(slices[1].title, 's2');
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/slice_test.html b/chromium/third_party/catapult/tracing/tracing/model/slice_test.html
new file mode 100644
index 00000000000..a9d5b18ef7e
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/slice_test.html
@@ -0,0 +1,239 @@
+<!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/slice_group.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const Slice = tr.model.Slice;
+ const SliceGroup = tr.model.SliceGroup;
+ const newSliceEx = tr.c.TestUtils.newSliceEx;
+ const newFakeThread = tr.c.TestUtils.newFakeThread;
+
+ test('findDescendentSlice', function() {
+ const group = new SliceGroup(newFakeThread());
+
+ const sA = group.pushSlice(newSliceEx(
+ { title: 'sA', start: 0.0, duration: 10.0 }));
+ const sB = group.pushSlice(newSliceEx(
+ { title: 'sB', start: 0.0, duration: 4.0 }));
+ const sC = group.pushSlice(newSliceEx(
+ { title: 'sC', start: 0.0, duration: 2.0 }));
+
+ group.createSubSlices();
+
+ assert.deepEqual(sB, sA.findDescendentSlice('sB'));
+ assert.deepEqual(sC, sA.findDescendentSlice('sC'));
+ assert.isUndefined(sA.findDescendentSlice('sD'));
+ });
+
+ test('findTopmostSlicesRelativeToThisSliceBaseCase', function() {
+ const PREDICATE = slice => slice.title.startsWith('sC');
+
+ const group = new SliceGroup(newFakeThread());
+
+ const sC1 = group.pushSlice(newSliceEx(
+ { title: 'sC1', start: 0.0, duration: 10.0 }));
+ const sC2 = group.pushSlice(newSliceEx(
+ { title: 'sC2', start: 0.0, duration: 4.0 }));
+
+ group.createSubSlices();
+
+ const foundSlices = [];
+ for (const slice of sC1.findTopmostSlicesRelativeToThisSlice(PREDICATE)) {
+ foundSlices.push(slice);
+ }
+
+ assert.deepEqual([sC1], foundSlices);
+ });
+
+ test('findTopmostSlicesRelativeToThisSliceRecursive', function() {
+ const PREDICATE = slice => slice.title.startsWith('sC');
+
+ const group = new SliceGroup(newFakeThread());
+
+ const sD = group.pushSlice(newSliceEx(
+ { title: 'sD', start: 0.0, duration: 10.0 }));
+ const sC1 = group.pushSlice(newSliceEx(
+ { title: 'sC1', start: 0.0, duration: 4.0 }));
+ const sC2 = group.pushSlice(newSliceEx(
+ { title: 'sC2', start: 6.0, duration: 3.0 }));
+
+ group.createSubSlices();
+
+ const foundSlices = [];
+ for (const slice of sD.findTopmostSlicesRelativeToThisSlice(PREDICATE)) {
+ foundSlices.push(slice);
+ }
+ assert.deepEqual([sC1, sC2], foundSlices);
+ });
+
+ test('enumerateAllDescendents', function() {
+ const group = new SliceGroup(newFakeThread());
+
+ const sA = group.pushSlice(newSliceEx(
+ { title: 'sA', start: 0.0, duration: 10.0 }));
+ const sB = group.pushSlice(newSliceEx(
+ { title: 'sB', start: 0.0, duration: 4.0 }));
+ const sC = group.pushSlice(newSliceEx(
+ { title: 'sC', start: 0.0, duration: 2.0 }));
+
+ group.createSubSlices();
+
+ assert.deepEqual(sA.descendentSlices, [sB, sC]);
+ assert.deepEqual(sC.descendentSlices, []);
+ });
+
+ test('mostTopLevelSlice', function() {
+ const group = new SliceGroup(newFakeThread());
+
+ const sA = group.pushSlice(newSliceEx(
+ { title: 'sA', start: 0.0, duration: 10.0 }));
+ const sB = group.pushSlice(newSliceEx(
+ { title: 'sB', start: 0.0, duration: 4.0 }));
+ const sC = group.pushSlice(newSliceEx(
+ { title: 'sC', start: 0.0, duration: 2.0 }));
+
+ group.createSubSlices();
+
+ assert.strictEqual(sA.mostTopLevelSlice, sA);
+ assert.strictEqual(sB.mostTopLevelSlice, sA);
+ assert.strictEqual(sC.mostTopLevelSlice, sA);
+ });
+
+ test('enumerateAllAncestors', function() {
+ const group = new SliceGroup(newFakeThread());
+
+ const sA = group.pushSlice(newSliceEx(
+ { title: 'sA', start: 0.0, duration: 10.0 }));
+ const sB = group.pushSlice(newSliceEx(
+ { title: 'sB', start: 0.0, duration: 4.0 }));
+ const sC = group.pushSlice(newSliceEx(
+ { title: 'sC', start: 0.0, duration: 2.0 }));
+
+ group.createSubSlices();
+
+ // Note that we iterate ancestors from the leaves to the root
+ assert.deepEqual(sC.ancestorSlices, [sB, sA]);
+ assert.deepEqual(sA.ancestorSlices, []);
+ });
+
+ test('iterateAllSubsequentSlices', function() {
+ const group = new SliceGroup(newFakeThread());
+
+ // [ A ]
+ // [ B ][ D ][F]
+ // [C] [E]
+
+ const sA = group.pushSlice(newSliceEx(
+ { title: 'sA', start: 0.0, duration: 10.0 }));
+ const sB = group.pushSlice(newSliceEx(
+ { title: 'sB', start: 0.0, duration: 4.0 }));
+ const sC = group.pushSlice(newSliceEx(
+ { title: 'sC', start: 0.0, duration: 2.0 }));
+ const sD = group.pushSlice(newSliceEx(
+ { title: 'sD', start: 5.0, duration: 2.0 }));
+ const sE = group.pushSlice(newSliceEx(
+ { title: 'sE', start: 5.0, duration: 1.0 }));
+ const sF = group.pushSlice(newSliceEx(
+ { title: 'sF', start: 8.0, duration: 2.0 }));
+
+ group.createSubSlices();
+
+ assert.deepEqual(sA.subsequentSlices, [sB, sC, sD, sE, sF]);
+ assert.deepEqual(sD.subsequentSlices, [sE, sF]);
+ assert.deepEqual(sF.subsequentSlices, []);
+ });
+
+ test('ancestorAndSubsequentSlices', function() {
+ const group = new SliceGroup(newFakeThread());
+
+ // [ A ]
+ // [ B ][ D ][F]
+ // [C] [E]
+
+ const sA = group.pushSlice(newSliceEx(
+ { title: 'sA', start: 0.0, duration: 10.0 }));
+ const sB = group.pushSlice(newSliceEx(
+ { title: 'sB', start: 0.0, duration: 4.0 }));
+ const sC = group.pushSlice(newSliceEx(
+ { title: 'sC', start: 0.0, duration: 2.0 }));
+ const sD = group.pushSlice(newSliceEx(
+ { title: 'sD', start: 5.0, duration: 2.0 }));
+ const sE = group.pushSlice(newSliceEx(
+ { title: 'sE', start: 5.0, duration: 1.0 }));
+ const sF = group.pushSlice(newSliceEx(
+ { title: 'sF', start: 8.0, duration: 2.0 }));
+
+ group.createSubSlices();
+
+ assert.deepEqual(sD.ancestorAndSubsequentSlices, [sD, sA, sE, sF]);
+ });
+
+ test('entireHierarchy', function() {
+ const group = new SliceGroup(newFakeThread());
+
+ // [ A ]
+ // [ B ][ D ][F]
+ // [C] [E]
+
+ const sA = group.pushSlice(newSliceEx(
+ { title: 'sA', start: 0.0, duration: 10.0 }));
+ const sB = group.pushSlice(newSliceEx(
+ { title: 'sB', start: 0.0, duration: 4.0 }));
+ const sC = group.pushSlice(newSliceEx(
+ { title: 'sC', start: 0.0, duration: 2.0 }));
+ const sD = group.pushSlice(newSliceEx(
+ { title: 'sD', start: 5.0, duration: 2.0 }));
+ const sE = group.pushSlice(newSliceEx(
+ { title: 'sE', start: 5.0, duration: 1.0 }));
+ const sF = group.pushSlice(newSliceEx(
+ { title: 'sF', start: 8.0, duration: 2.0 }));
+
+ group.createSubSlices();
+
+ assert.deepEqual(sD.entireHierarchy, [sA, sB, sC, sD, sE, sF]);
+ });
+
+ test('stableId', function() {
+ const thread = newFakeThread();
+ const group = thread.sliceGroup;
+
+ const sA = group.pushSlice(newSliceEx(
+ { title: 'sA', start: 0.0, duration: 10.0 }));
+ const sB = group.pushSlice(newSliceEx(
+ { title: 'sB', start: 10.0, duration: 20.0 }));
+ const sC = group.pushSlice(newSliceEx(
+ { title: 'sC', start: 20.0, duration: 30.0 }));
+
+ assert.strictEqual(group.stableId + '.0', sA.stableId);
+ assert.strictEqual(group.stableId + '.1', sB.stableId);
+ assert.strictEqual(group.stableId + '.2', sC.stableId);
+ });
+
+ test('cantInstantiateDirectly', function() {
+ assert.throws(function() {
+ new Slice('', 'Test', 0, 0, { });
+ });
+ });
+
+ test('canInstantiateSubclasses', function() {
+ function TestSlice() {
+ Slice.call(this, '', 'Test', 0, 0, { });
+ }
+ TestSlice.prototype = {
+ __proto__: Slice.prototype
+ };
+ assert.doesNotThrow(function() {
+ new TestSlice();
+ });
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/source_info/js_source_info.html b/chromium/third_party/catapult/tracing/tracing/model/source_info/js_source_info.html
new file mode 100644
index 00000000000..37b975b0b6e
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/source_info/js_source_info.html
@@ -0,0 +1,57 @@
+<!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/source_info/source_info.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.model.source_info', function() {
+ function JSSourceInfo(file, line, column, isNative, scriptId, state) {
+ tr.model.source_info.SourceInfo.call(this, file, line, column);
+
+ this.isNative_ = isNative;
+ this.scriptId_ = scriptId;
+ this.state_ = state;
+ }
+
+ JSSourceInfo.prototype = {
+ __proto__: tr.model.source_info.SourceInfo.prototype,
+
+ get state() {
+ return this.state_;
+ },
+
+ get isNative() {
+ return this.isNative_;
+ },
+
+ get scriptId() {
+ return this.scriptId_;
+ },
+
+ toString() {
+ const str = this.isNative_ ? '[native v8] ' : '';
+ return str +
+ tr.model.source_info.SourceInfo.prototype.toString.call(this);
+ }
+ };
+
+ const JSSourceState = {
+ COMPILED: 'compiled',
+ OPTIMIZABLE: 'optimizable',
+ OPTIMIZED: 'optimized',
+ UNKNOWN: 'unknown',
+ };
+
+ return {
+ JSSourceInfo,
+ JSSourceState,
+ };
+});
+</script>
+
diff --git a/chromium/third_party/catapult/tracing/tracing/model/source_info/source_info.html b/chromium/third_party/catapult/tracing/tracing/model/source_info/source_info.html
new file mode 100644
index 00000000000..a8cf526d6c5
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/source_info/source_info.html
@@ -0,0 +1,60 @@
+<!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">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.model.source_info', function() {
+ function SourceInfo(file, opt_line, opt_column) {
+ this.file_ = file;
+ this.line_ = opt_line || -1;
+ this.column_ = opt_column || -1;
+ }
+
+ SourceInfo.prototype = {
+ get file() {
+ return this.file_;
+ },
+
+ get line() {
+ return this.line_;
+ },
+
+ get column() {
+ return this.column_;
+ },
+
+ get domain() {
+ if (!this.file_) return undefined;
+ const domain = this.file_.match(/(.*:\/\/[^:\/]*)/i);
+ return domain ? domain[1] : undefined;
+ },
+
+ toString() {
+ let str = '';
+
+ if (this.file_) {
+ str += this.file_;
+ }
+ if (this.line_ > 0) {
+ str += ':' + this.line_;
+ }
+ if (this.column_ > 0) {
+ str += ':' + this.column_;
+ }
+ return str;
+ }
+ };
+
+ return {
+ SourceInfo,
+ };
+});
+</script>
+
diff --git a/chromium/third_party/catapult/tracing/tracing/model/source_info/source_info_test.html b/chromium/third_party/catapult/tracing/tracing/model/source_info/source_info_test.html
new file mode 100644
index 00000000000..269de596d10
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/source_info/source_info_test.html
@@ -0,0 +1,27 @@
+<!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/source_info/source_info.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ test('domain', function() {
+ const urlDomains = {
+ 'http://www.google.com': 'http://www.google.com',
+ 'http://www.google.com/bla': 'http://www.google.com',
+ 'http://www.google.com:1234': 'http://www.google.com',
+ 'bad url': undefined
+ };
+ for (const url in urlDomains) {
+ const sourceInfo = new tr.model.source_info.SourceInfo(url);
+ assert.strictEqual(urlDomains[url], sourceInfo.domain);
+ }
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/stack_frame.html b/chromium/third_party/catapult/tracing/tracing/model/stack_frame.html
new file mode 100644
index 00000000000..b417d20f94c
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/stack_frame.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/base/base.html">
+<script>
+'use strict';
+
+tr.exportTo('tr.model', function() {
+ function StackFrame(parentFrame, id, title, colorId, opt_sourceInfo) {
+ if (id === undefined) {
+ throw new Error('id must be given');
+ }
+ this.parentFrame_ = parentFrame;
+ this.id = id;
+ this.title_ = title;
+ this.colorId = colorId;
+ this.children = [];
+ this.sourceInfo_ = opt_sourceInfo;
+
+ if (this.parentFrame_) {
+ this.parentFrame_.addChild(this);
+ }
+ }
+
+ StackFrame.prototype = {
+ get parentFrame() {
+ return this.parentFrame_;
+ },
+
+ get title() {
+ if (this.sourceInfo_) {
+ const src = this.sourceInfo_.toString();
+ return this.title_ + (src === '' ? '' : ' ' + src);
+ }
+ return this.title_;
+ },
+
+ /**
+ * Attempts to find the domain of the origin of the script either from this
+ * stack trace or from its ancestors.
+ */
+ get domain() {
+ let result = 'unknown';
+ if (this.sourceInfo_ && this.sourceInfo_.domain) {
+ result = this.sourceInfo_.domain;
+ }
+ if (result === 'unknown' && this.parentFrame) {
+ result = this.parentFrame.domain;
+ }
+ return result;
+ },
+
+ get sourceInfo() {
+ return this.sourceInfo_;
+ },
+
+ set parentFrame(parentFrame) {
+ if (this.parentFrame_) {
+ Polymer.dom(this.parentFrame_).removeChild(this);
+ }
+ this.parentFrame_ = parentFrame;
+ if (this.parentFrame_) {
+ this.parentFrame_.addChild(this);
+ }
+ },
+
+ addChild(child) {
+ this.children.push(child);
+ },
+
+ removeChild(child) {
+ const i = this.children.indexOf(child.id);
+ if (i === -1) {
+ throw new Error('omg');
+ }
+ this.children.splice(i, 1);
+ },
+
+ removeAllChildren() {
+ for (let i = 0; i < this.children.length; i++) {
+ this.children[i].parentFrame_ = undefined;
+ }
+ this.children.splice(0, this.children.length);
+ },
+
+ /**
+ * Returns stackFrames where the most specific frame is first.
+ */
+ get stackTrace() {
+ const stack = [this];
+ let cur = this.parentFrame;
+ while (cur) {
+ stack.push(cur);
+ cur = cur.parentFrame;
+ }
+ return stack;
+ },
+
+ getUserFriendlyStackTrace() {
+ return this.stackTrace.map(function(x) { return x.title; });
+ }
+ };
+
+ return {
+ StackFrame,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/stack_frame_test.html b/chromium/third_party/catapult/tracing/tracing/model/stack_frame_test.html
new file mode 100644
index 00000000000..d0b971ecb78
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/stack_frame_test.html
@@ -0,0 +1,29 @@
+<!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/source_info/source_info.html">
+<link rel="import" href="/tracing/model/stack_frame.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ test('domain', function() {
+ const stackFrame1 = new tr.model.StackFrame(undefined, 1, '1', 1);
+ assert.strictEqual('unknown', stackFrame1.domain);
+
+ const sourceInfo = new tr.model.source_info.SourceInfo(
+ 'http://www.google.com:1234');
+ const stackFrame2 = new tr.model.StackFrame(
+ stackFrame1, 2, '2', 2, sourceInfo);
+ assert.strictEqual('http://www.google.com', stackFrame2.domain);
+
+ const stackFrame3 = new tr.model.StackFrame(stackFrame2, 3, '3', 3);
+ assert.strictEqual('http://www.google.com', stackFrame3.domain);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/thread.html b/chromium/third_party/catapult/tracing/tracing/model/thread.html
new file mode 100644
index 00000000000..54826bd29db
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/thread.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/guid.html">
+<link rel="import" href="/tracing/base/math/range.html">
+<link rel="import" href="/tracing/model/async_slice_group.html">
+<link rel="import" href="/tracing/model/event_container.html">
+<link rel="import" href="/tracing/model/slice_group.html">
+<link rel="import" href="/tracing/model/thread_slice.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Provides the Thread class.
+ */
+tr.exportTo('tr.model', function() {
+ const AsyncSlice = tr.model.AsyncSlice;
+ const AsyncSliceGroup = tr.model.AsyncSliceGroup;
+ const SliceGroup = tr.model.SliceGroup;
+ const ThreadSlice = tr.model.ThreadSlice;
+ const ThreadTimeSlice = tr.model.ThreadTimeSlice;
+
+ /**
+ * A Thread stores all the trace events collected for a particular
+ * thread. We organize the synchronous slices on a thread by "subrows," where
+ * subrow 0 has all the root slices, subrow 1 those nested 1 deep, and so on.
+ * The asynchronous slices are stored in an AsyncSliceGroup object.
+ *
+ * The slices stored on a Thread should be instances of
+ * ThreadSlice.
+ *
+ * @constructor
+ * @extends {tr.model.EventContainer}
+ */
+ function Thread(parent, tid) {
+ if (!parent) {
+ throw new Error('Parent must be provided.');
+ }
+
+ tr.model.EventContainer.call(this);
+ this.parent = parent;
+ this.sortIndex = 0;
+ this.tid = tid;
+ this.name = undefined;
+ this.samples_ = undefined; // Set during createSubSlices
+
+ this.sliceGroup = new SliceGroup(this, ThreadSlice, 'slices');
+ this.timeSlices = undefined;
+ this.kernelSliceGroup = new SliceGroup(
+ this, ThreadSlice, 'kernel-slices');
+ this.asyncSliceGroup = new AsyncSliceGroup(this, 'async-slices');
+ }
+
+ Thread.prototype = {
+ __proto__: tr.model.EventContainer.prototype,
+
+ get model() {
+ return this.parent.model;
+ },
+
+ get stableId() {
+ return this.parent.stableId + '.' + this.tid;
+ },
+
+ compareTo(that) {
+ return Thread.compare(this, that);
+ },
+
+ * childEventContainers() {
+ if (this.sliceGroup.length) {
+ yield this.sliceGroup;
+ }
+ if (this.kernelSliceGroup.length) {
+ yield this.kernelSliceGroup;
+ }
+ if (this.asyncSliceGroup.length) {
+ yield this.asyncSliceGroup;
+ }
+ },
+
+ * childEvents() {
+ if (this.timeSlices) {
+ yield* this.timeSlices;
+ }
+ },
+
+ iterateAllPersistableObjects(cb) {
+ cb(this);
+ if (this.sliceGroup.length) {
+ cb(this.sliceGroup);
+ }
+ this.asyncSliceGroup.viewSubGroups.forEach(cb);
+ },
+
+ /**
+ * Shifts all the timestamps inside this thread forward by the amount
+ * specified.
+ */
+ shiftTimestampsForward(amount) {
+ this.sliceGroup.shiftTimestampsForward(amount);
+
+ if (this.timeSlices) {
+ for (let i = 0; i < this.timeSlices.length; i++) {
+ const slice = this.timeSlices[i];
+ slice.start += amount;
+ }
+ }
+
+ this.kernelSliceGroup.shiftTimestampsForward(amount);
+ this.asyncSliceGroup.shiftTimestampsForward(amount);
+ },
+
+ /**
+ * Determines whether this thread is empty. If true, it usually implies
+ * that it should be pruned from the model.
+ */
+ get isEmpty() {
+ if (this.sliceGroup.length) return false;
+ if (this.sliceGroup.openSliceCount) return false;
+ if (this.timeSlices && this.timeSlices.length) return false;
+ if (this.kernelSliceGroup.length) return false;
+ if (this.asyncSliceGroup.length) return false;
+ if (this.samples_.length) return false;
+ return true;
+ },
+
+ /**
+ * Updates the bounds based on the
+ * current objects associated with the thread.
+ */
+ updateBounds() {
+ this.bounds.reset();
+
+ this.sliceGroup.updateBounds();
+ this.bounds.addRange(this.sliceGroup.bounds);
+
+ this.kernelSliceGroup.updateBounds();
+ this.bounds.addRange(this.kernelSliceGroup.bounds);
+
+ this.asyncSliceGroup.updateBounds();
+ this.bounds.addRange(this.asyncSliceGroup.bounds);
+
+ if (this.timeSlices && this.timeSlices.length) {
+ this.bounds.addValue(this.timeSlices[0].start);
+ this.bounds.addValue(
+ this.timeSlices[this.timeSlices.length - 1].end);
+ }
+
+ if (this.samples_ && this.samples_.length) {
+ this.bounds.addValue(this.samples_[0].start);
+ this.bounds.addValue(
+ this.samples_[this.samples_.length - 1].end);
+ }
+ },
+
+ addCategoriesToDict(categoriesDict) {
+ for (let i = 0; i < this.sliceGroup.length; i++) {
+ categoriesDict[this.sliceGroup.slices[i].category] = true;
+ }
+ for (let i = 0; i < this.kernelSliceGroup.length; i++) {
+ categoriesDict[this.kernelSliceGroup.slices[i].category] = true;
+ }
+ for (let i = 0; i < this.asyncSliceGroup.length; i++) {
+ categoriesDict[this.asyncSliceGroup.slices[i].category] = true;
+ }
+ if (this.samples_) {
+ for (let i = 0; i < this.samples_.length; i++) {
+ categoriesDict[this.samples_[i].category] = true;
+ }
+ }
+ },
+
+ autoCloseOpenSlices() {
+ this.sliceGroup.autoCloseOpenSlices();
+ this.asyncSliceGroup.autoCloseOpenSlices();
+ this.kernelSliceGroup.autoCloseOpenSlices();
+ },
+
+ mergeKernelWithUserland() {
+ if (this.kernelSliceGroup.length > 0) {
+ const newSlices = SliceGroup.merge(
+ this.sliceGroup, this.kernelSliceGroup);
+ this.sliceGroup.slices = newSlices.slices;
+ this.kernelSliceGroup = new SliceGroup(this);
+ this.updateBounds();
+ }
+ },
+
+ createSubSlices() {
+ this.sliceGroup.createSubSlices();
+ this.samples_ = this.parent.model.samples.filter(sample =>
+ sample.thread === this);
+ },
+
+ /**
+ * @return {String} A user-friendly name for this thread.
+ */
+ get userFriendlyName() {
+ return this.name || this.tid;
+ },
+
+ /**
+ * @return {String} User friendly details about this thread.
+ */
+ get userFriendlyDetails() {
+ return 'tid: ' + this.tid +
+ (this.name ? ', name: ' + this.name : '');
+ },
+
+ getSettingsKey() {
+ if (!this.name) return undefined;
+ const parentKey = this.parent.getSettingsKey();
+ if (!parentKey) return undefined;
+ return parentKey + '.' + this.name;
+ },
+
+ getProcess() {
+ return this.parent;
+ },
+
+ /*
+ * Returns the index of the slice in the timeSlices array, or undefined.
+ */
+ indexOfTimeSlice(timeSlice) {
+ const i = tr.b.findLowIndexInSortedArray(
+ this.timeSlices,
+ function(slice) { return slice.start; },
+ timeSlice.start);
+ if (this.timeSlices[i] !== timeSlice) return undefined;
+ return i;
+ },
+
+ sumOverToplevelSlicesInRange(range, func) {
+ let sum = 0;
+ tr.b.iterateOverIntersectingIntervals(
+ this.sliceGroup.topLevelSlices,
+ slice => slice.start, slice => slice.end,
+ range.min, range.max,
+ slice => {
+ let fractionOfSliceInsideRangeOfInterest = 1;
+ if (slice.duration > 0) {
+ const intersection = range.findIntersection(slice.range);
+ fractionOfSliceInsideRangeOfInterest =
+ intersection.duration / slice.duration;
+ }
+ // We assume that if a slice doesn't lie entirely inside the range
+ // of interest, then |func| is evenly distributed inside of the
+ // slice.
+ sum += func(slice) * fractionOfSliceInsideRangeOfInterest;
+ });
+ return sum;
+ },
+
+ /**
+ * Returns the total cpu time consumed within |range| by this thread.
+ */
+ getCpuTimeForRange(range) {
+ return this.sumOverToplevelSlicesInRange(
+ range, slice => slice.cpuDuration || 0);
+ },
+
+ /**
+ * Returns the total number of top-level slices within |range| in this
+ * thread. If a slice overlaps with |range| and is not completely inside it,
+ * then we attribute the portion that is inside the range only. For example,
+ * |getNumToplevelSlicesForRange| will return 1 + 1/3 when we have:
+ *
+ * 01 02 03 04 05 06 07 08 09 10
+ * <---------- range ---------->
+ * <- slice #1 -> <- slice #2 ->
+ */
+ getNumToplevelSlicesForRange(range) {
+ return this.sumOverToplevelSlicesInRange(range, slice => 1);
+ },
+
+ getSchedulingStatsForRange(start, end) {
+ const stats = {};
+
+ if (!this.timeSlices) return stats;
+
+ function addStatsForSlice(threadTimeSlice) {
+ const overlapStart = Math.max(threadTimeSlice.start, start);
+ const overlapEnd = Math.min(threadTimeSlice.end, end);
+ const schedulingState = threadTimeSlice.schedulingState;
+
+ if (!(schedulingState in stats)) stats[schedulingState] = 0;
+ stats[schedulingState] += overlapEnd - overlapStart;
+ }
+
+ tr.b.iterateOverIntersectingIntervals(this.timeSlices,
+ function(x) { return x.start; },
+ function(x) { return x.end; },
+ start,
+ end,
+ addStatsForSlice);
+ return stats;
+ },
+
+ get samples() {
+ return this.samples_;
+ },
+
+ /**
+ * Returns substring of this.name from beginning to the first numeric
+ * character or the character '/'.
+ *
+ * Example:
+ * ThreadName12 -> ThreadName
+ * ThreadName/34123 -> ThreadName
+ * ThreadName1/34123 -> ThreadName
+ */
+ get type() {
+ const re = /^[^0-9|\/]+/;
+ const matches = re.exec(this.name);
+ if (matches && matches[0]) return matches[0];
+
+ // If a thread is named 42GPU, let's not try to find its type.
+ // We should fix the thread name.
+ throw new Error('Could not determine thread type for thread name ' +
+ this.name);
+ }
+ };
+
+ /**
+ * Comparison between threads that orders first by parent.compareTo,
+ * then by names, then by tid.
+ */
+ Thread.compare = function(x, y) {
+ let tmp = x.parent.compareTo(y.parent);
+ if (tmp) return tmp;
+
+ tmp = x.sortIndex - y.sortIndex;
+ if (tmp) return tmp;
+
+ if (x.name !== undefined) {
+ if (y.name !== undefined) {
+ tmp = x.name.localeCompare(y.name);
+ } else {
+ tmp = -1;
+ }
+ } else if (y.name !== undefined) {
+ tmp = 1;
+ }
+ if (tmp) return tmp;
+
+ return x.tid - y.tid;
+ };
+
+ return {
+ Thread,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/thread_slice.html b/chromium/third_party/catapult/tracing/tracing/model/thread_slice.html
new file mode 100644
index 00000000000..f67d20d3e5d
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/thread_slice.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/model/slice.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Provides the Thread class.
+ */
+tr.exportTo('tr.model', function() {
+ const Slice = tr.model.Slice;
+
+ /**
+ * A ThreadSlice represents an interval of time on a thread resource
+ * with associated nesting slice information.
+ *
+ * ThreadSlices are typically associated with a specific trace event pair on a
+ * specific thread.
+ * For example,
+ * TRACE_EVENT_BEGIN1("x","myArg", 7) at time=0.1ms
+ * TRACE_EVENT_END0() at time=0.3ms
+ * This results in a single slice from 0.1 with duration 0.2 on a
+ * specific thread.
+ *
+ * @constructor
+ */
+ function ThreadSlice(cat, title, colorId, start, args, opt_duration,
+ opt_cpuStart, opt_cpuDuration, opt_argsStripped,
+ opt_bindId) {
+ Slice.call(this, cat, title, colorId, start, args, opt_duration,
+ opt_cpuStart, opt_cpuDuration, opt_argsStripped, opt_bindId);
+ // Do not modify this directly.
+ // subSlices is configured by SliceGroup.rebuildSubRows_.
+ this.subSlices = [];
+ }
+
+ ThreadSlice.prototype = {
+ __proto__: Slice.prototype,
+
+ get overlappingSamples() {
+ const samples = new tr.model.EventSet();
+ if (!this.parentContainer || !this.parentContainer.samples) {
+ return samples;
+ }
+ this.parentContainer.samples.forEach(function(sample) {
+ if (this.start <= sample.start && sample.start <= this.end) {
+ samples.push(sample);
+ }
+ }, this);
+ return samples;
+ }
+ };
+
+ tr.model.EventRegistry.register(
+ ThreadSlice,
+ {
+ name: 'slice',
+ pluralName: 'slices'
+ });
+
+ return {
+ ThreadSlice,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/thread_slice_test.html b/chromium/third_party/catapult/tracing/tracing/model/thread_slice_test.html
new file mode 100644
index 00000000000..b69efb79ac4
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/thread_slice_test.html
@@ -0,0 +1,41 @@
+<!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/thread_slice.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const ThreadSlice = tr.model.ThreadSlice;
+ const newSliceEx = tr.c.TestUtils.newSliceEx;
+ const newFakeThread = tr.c.TestUtils.newFakeThread;
+
+ test('getOverlappingSamples', function() {
+ const model = 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']);
+ m.samples.push(
+ new tr.model.Sample(1, 'a_1', node, m.t2),
+ new tr.model.Sample(2, 'a_2', node, m.t2),
+ new tr.model.Sample(3, 'a_3', node, m.t2),
+ new tr.model.Sample(5, 'b', node, m.t2)
+ );
+ });
+ const threadSlice = newSliceEx({title: 'a', start: 0, end: 4,
+ type: tr.model.ThreadSlice});
+ threadSlice.parentContainer = model;
+ const samplesIter = threadSlice.overlappingSamples[Symbol.iterator]();
+ assert.strictEqual(samplesIter.next().value.title, 'a_1');
+ assert.strictEqual(samplesIter.next().value.title, 'a_2');
+ assert.strictEqual(samplesIter.next().value.title, 'a_3');
+ assert.strictEqual(samplesIter.next().done, true);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/thread_test.html b/chromium/third_party/catapult/tracing/tracing/model/thread_test.html
new file mode 100644
index 00000000000..4d2bccf0790
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/thread_test.html
@@ -0,0 +1,209 @@
+<!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/range.html">
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/model/model.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const ThreadSlice = tr.model.ThreadSlice;
+ const Process = tr.model.Process;
+ const Thread = tr.model.Thread;
+ const newSliceEx = tr.c.TestUtils.newSliceEx;
+ const newAsyncSlice = tr.c.TestUtils.newAsyncSlice;
+ const newThreadSlice = tr.c.TestUtils.newThreadSlice;
+ const SCHEDULING_STATE = tr.model.SCHEDULING_STATE;
+
+ test('threadBounds_Empty', function() {
+ const model = new tr.Model();
+ const t = new Thread(new Process(model, 7), 1);
+ t.updateBounds();
+ assert.isUndefined(t.bounds.min);
+ assert.isUndefined(t.bounds.max);
+ });
+
+ test('threadBounds_SubRow', function() {
+ const model = new tr.Model();
+ const t = new Thread(new Process(model, 7), 1);
+ t.sliceGroup.pushSlice(new ThreadSlice('', 'a', 0, 1, {}, 3));
+ t.updateBounds();
+ assert.strictEqual(t.bounds.min, 1);
+ assert.strictEqual(t.bounds.max, 4);
+ });
+
+ test('threadBounds_AsyncSliceGroup', function() {
+ const model = new tr.Model();
+ const t = new Thread(new Process(model, 7), 1);
+ t.sliceGroup.pushSlice(new ThreadSlice('', 'a', 0, 1, {}, 3));
+ t.asyncSliceGroup.push(newAsyncSlice(0.1, 5, t, t));
+ t.updateBounds();
+ assert.strictEqual(t.bounds.min, 0.1);
+ assert.strictEqual(t.bounds.max, 5.1);
+ });
+
+ test('threadBounds_Cpu', function() {
+ const model = new tr.Model();
+ const t = new Thread(new Process(model, 7), 1);
+ t.timeSlices = [newSliceEx({title: 'x', start: 0, duration: 1})];
+ t.updateBounds();
+ assert.strictEqual(t.bounds.min, 0);
+ assert.strictEqual(t.bounds.max, 1);
+ });
+
+ test('shiftTimestampsForwardWithCpu', function() {
+ const model = new tr.Model();
+ const t = new Thread(new Process(model, 7), 1);
+ t.sliceGroup.pushSlice(new ThreadSlice('', 'a', 0, 0, {}, 3));
+ t.asyncSliceGroup.push(newAsyncSlice(0, 5, t, t));
+ t.timeSlices = [newSliceEx({title: 'x', start: 0, duration: 1})];
+
+ let shiftCount = 0;
+ t.asyncSliceGroup.shiftTimestampsForward = function(ts) {
+ if (ts === 0.32) {
+ shiftCount++;
+ }
+ };
+
+ t.shiftTimestampsForward(0.32);
+
+ assert.strictEqual(shiftCount, 1);
+ assert.strictEqual(t.sliceGroup.slices[0].start, 0.32);
+ assert.strictEqual(t.timeSlices[0].start, 0.32);
+ });
+
+ test('shiftTimestampsForwardWithoutCpu', function() {
+ const model = new tr.Model();
+ const t = new Thread(new Process(model, 7), 1);
+ t.sliceGroup.pushSlice(new ThreadSlice('', 'a', 0, 0, {}, 3));
+ t.asyncSliceGroup.push(newAsyncSlice(0, 5, t, t));
+
+ let shiftCount = 0;
+ t.asyncSliceGroup.shiftTimestampsForward = function(ts) {
+ if (ts === 0.32) {
+ shiftCount++;
+ }
+ };
+
+ t.shiftTimestampsForward(0.32);
+
+ assert.strictEqual(shiftCount, 1);
+ assert.strictEqual(t.sliceGroup.slices[0].start, 0.32);
+ });
+
+ test('getSchedulingStatsForRange', function() {
+ let scheduledThread = undefined;
+ let unscheduledThread = undefined;
+ const model = tr.c.TestUtils.newModel(function(model) {
+ unscheduledThread = model.getOrCreateProcess(1).getOrCreateThread(1);
+ unscheduledThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'work', start: 0, duration: 20}));
+
+ scheduledThread = model.getOrCreateProcess(2).getOrCreateThread(2);
+ scheduledThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'work', start: 0, duration: 20}));
+ scheduledThread.timeSlices = [
+ newThreadSlice(scheduledThread, SCHEDULING_STATE.RUNNING, 0, 3),
+ newThreadSlice(scheduledThread, SCHEDULING_STATE.RUNNABLE, 3, 5),
+ newThreadSlice(scheduledThread, SCHEDULING_STATE.RUNNING, 8, 2),
+ newThreadSlice(scheduledThread, SCHEDULING_STATE.SLEEPING, 10, 10)
+ ];
+ });
+
+ // thread without scheduling states
+ let stats = unscheduledThread.getSchedulingStatsForRange(0, 20);
+ assert.deepEqual(stats, {});
+
+ // no scheduling info
+ stats = scheduledThread.getSchedulingStatsForRange(50, 100);
+ assert.deepEqual(stats, {});
+
+ // simple query
+ stats = scheduledThread.getSchedulingStatsForRange(0, 3);
+ let expected = {};
+ expected[SCHEDULING_STATE.RUNNING] = 3;
+ assert.deepEqual(stats, expected);
+
+ // aggregation
+ stats = scheduledThread.getSchedulingStatsForRange(0, 20);
+ expected = {};
+ expected[SCHEDULING_STATE.RUNNING] = 5;
+ expected[SCHEDULING_STATE.RUNNABLE] = 5;
+ expected[SCHEDULING_STATE.SLEEPING] = 10;
+ assert.deepEqual(stats, expected);
+ });
+
+ test('getCpuTimeForRange', function() {
+ const model = tr.c.TestUtils.newModel(function(model) {
+ const thread = model.getOrCreateProcess(1).getOrCreateThread(1);
+ const sliceSpecs = [
+ {wallTimeBounds: [100, 200], cpuStart: 120, cpuDuration: 50},
+ {wallTimeBounds: [300, 600], cpuStart: 350, cpuDuration: 150}
+ ];
+ for (const sliceSpec of sliceSpecs) {
+ thread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ type: tr.model.ThreadSlice,
+ isTopLevel: true,
+ start: sliceSpec.wallTimeBounds[0],
+ duration: sliceSpec.wallTimeBounds[1] - sliceSpec.wallTimeBounds[0],
+ cpuStart: sliceSpec.cpuStart,
+ cpuDuration: sliceSpec.cpuDuration,
+ }));
+ }
+ });
+
+ const thread = model.getOrCreateProcess(1).getOrCreateThread(1);
+ const bounds = new tr.b.math.Range.fromExplicitRange(150, 400);
+ // 1/2 of first slice + 1/3 of second slice
+ const expectedCpuTime = 25 + 50;
+
+ // Should be essentially equal, but choosing a very small epsilon 1e-7
+ // to allow for floating point errors.
+ assert.closeTo(thread.getCpuTimeForRange(bounds), expectedCpuTime, 1e-7);
+ });
+
+ test('typeGetterReturnsCorrectType', function() {
+ const model = tr.c.TestUtils.newModel(function(model) {
+ const process = model.getOrCreateProcess(1);
+ const thread1 = process.getOrCreateThread(1);
+ const thread2 = process.getOrCreateThread(2);
+ const thread3 = process.getOrCreateThread(3);
+ const thread4 = process.getOrCreateThread(4);
+
+ thread1.name = 'ThreadName12';
+ thread2.name = 'ThreadName/34123';
+ thread3.name = 'ThreadName1/34123';
+ thread4.name = 'ThreadName';
+
+ assert.strictEqual(thread1.type, 'ThreadName');
+ assert.strictEqual(thread2.type, 'ThreadName');
+ assert.strictEqual(thread3.type, 'ThreadName');
+ assert.strictEqual(thread4.type, 'ThreadName');
+ });
+ });
+
+ test('typeGetterThrowsIfThreadNameStartsWithNumberOrSlash', function() {
+ const model = tr.c.TestUtils.newModel(function(model) {
+ const process = model.getOrCreateProcess(1);
+ const thread1 = process.getOrCreateThread(1);
+ const thread2 = process.getOrCreateThread(2);
+ const thread3 = process.getOrCreateThread(3);
+
+ thread1.name = '123';
+ thread2.name = '42GPU';
+ thread3.name = '/123';
+
+ assert.throws(() => thread1.type);
+ assert.throws(() => thread2.type);
+ assert.throws(() => thread3.type);
+ });
+ });
+});
+</script>
+
diff --git a/chromium/third_party/catapult/tracing/tracing/model/thread_time_slice.html b/chromium/third_party/catapult/tracing/tracing/model/thread_time_slice.html
new file mode 100644
index 00000000000..69437c76819
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/thread_time_slice.html
@@ -0,0 +1,154 @@
+<!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/range.html">
+<link rel="import" href="/tracing/model/slice.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.model', function() {
+ const Slice = tr.model.Slice;
+
+
+ const SCHEDULING_STATE = {
+ DEBUG: 'Debug',
+ EXIT_DEAD: 'Exit Dead',
+ RUNNABLE: 'Runnable',
+ RUNNING: 'Running',
+ SLEEPING: 'Sleeping',
+ STOPPED: 'Stopped',
+ TASK_DEAD: 'Task Dead',
+ UNINTR_SLEEP: 'Uninterruptible Sleep',
+ UNINTR_SLEEP_WAKE_KILL: 'Uninterruptible Sleep | WakeKill',
+ UNINTR_SLEEP_WAKING: 'Uninterruptible Sleep | Waking',
+ UNINTR_SLEEP_IO: 'Uninterruptible Sleep - Block I/O',
+ UNINTR_SLEEP_WAKE_KILL_IO: 'Uninterruptible Sleep | WakeKill - Block I/O',
+ UNINTR_SLEEP_WAKING_IO: 'Uninterruptible Sleep | Waking - Block I/O',
+ UNKNOWN: 'UNKNOWN',
+ WAKE_KILL: 'Wakekill',
+ WAKING: 'Waking',
+ ZOMBIE: 'Zombie'
+ };
+
+ /**
+ * A ThreadTimeSlice is a slice of time on a specific thread where that thread
+ * was running on a specific CPU, or in a specific sleep state.
+ *
+ * As a thread switches moves through its life, it sometimes goes to sleep and
+ * can't run. Other times, its runnable but isn't actually assigned to a CPU.
+ * Finally, sometimes it gets put on a CPU to actually execute. Each of these
+ * states is represented by a ThreadTimeSlice:
+ *
+ * Sleeping or runnable: cpuOnWhichThreadWasRunning is undefined
+ * Running: cpuOnWhichThreadWasRunning is set.
+ *
+ * @constructor
+ */
+ function ThreadTimeSlice(thread, schedulingState, cat,
+ start, args, opt_duration) {
+ Slice.call(this, cat, schedulingState,
+ this.getColorForState_(schedulingState),
+ start, args, opt_duration);
+ this.thread = thread;
+ this.schedulingState = schedulingState;
+ this.cpuOnWhichThreadWasRunning = undefined;
+ }
+
+ ThreadTimeSlice.prototype = {
+ __proto__: Slice.prototype,
+
+ getColorForState_(state) {
+ const getColorIdForReservedName =
+ tr.b.ColorScheme.getColorIdForReservedName;
+
+ switch (state) {
+ case SCHEDULING_STATE.RUNNABLE:
+ return getColorIdForReservedName('thread_state_runnable');
+ case SCHEDULING_STATE.RUNNING:
+ return getColorIdForReservedName('thread_state_running');
+ case SCHEDULING_STATE.SLEEPING:
+ return getColorIdForReservedName('thread_state_sleeping');
+ case SCHEDULING_STATE.DEBUG:
+ case SCHEDULING_STATE.EXIT_DEAD:
+ case SCHEDULING_STATE.STOPPED:
+ case SCHEDULING_STATE.TASK_DEAD:
+ case SCHEDULING_STATE.UNINTR_SLEEP:
+ case SCHEDULING_STATE.UNINTR_SLEEP_WAKE_KILL:
+ case SCHEDULING_STATE.UNINTR_SLEEP_WAKING:
+ case SCHEDULING_STATE.UNKNOWN:
+ case SCHEDULING_STATE.WAKE_KILL:
+ case SCHEDULING_STATE.WAKING:
+ case SCHEDULING_STATE.ZOMBIE:
+ return getColorIdForReservedName('thread_state_uninterruptible');
+ case SCHEDULING_STATE.UNINTR_SLEEP_IO:
+ case SCHEDULING_STATE.UNINTR_SLEEP_WAKE_KILL_IO:
+ case SCHEDULING_STATE.UNINTR_SLEEP_WAKING_IO:
+ return getColorIdForReservedName('thread_state_iowait');
+ default:
+ return getColorIdForReservedName('thread_state_unknown');
+ }
+ },
+
+ get analysisTypeName() {
+ return 'tr.ui.analysis.ThreadTimeSlice';
+ },
+
+ getAssociatedCpuSlice() {
+ if (!this.cpuOnWhichThreadWasRunning) return undefined;
+ const cpuSlices = this.cpuOnWhichThreadWasRunning.slices;
+ for (let i = 0; i < cpuSlices.length; i++) {
+ const cpuSlice = cpuSlices[i];
+ if (cpuSlice.start !== this.start) continue;
+ if (cpuSlice.duration !== this.duration) continue;
+ return cpuSlice;
+ }
+ return undefined;
+ },
+
+ getCpuSliceThatTookCpu() {
+ if (this.cpuOnWhichThreadWasRunning) return undefined;
+ let curIndex = this.thread.indexOfTimeSlice(this);
+ let cpuSliceWhenLastRunning;
+ while (curIndex >= 0) {
+ const curSlice = this.thread.timeSlices[curIndex];
+ if (!curSlice.cpuOnWhichThreadWasRunning) {
+ curIndex--;
+ continue;
+ }
+ cpuSliceWhenLastRunning = curSlice.getAssociatedCpuSlice();
+ break;
+ }
+ if (!cpuSliceWhenLastRunning) return undefined;
+
+ const cpu = cpuSliceWhenLastRunning.cpu;
+ const indexOfSliceOnCpuWhenLastRunning =
+ cpu.indexOf(cpuSliceWhenLastRunning);
+ const nextRunningSlice = cpu.slices[indexOfSliceOnCpuWhenLastRunning + 1];
+ if (!nextRunningSlice) return undefined;
+ if (Math.abs(nextRunningSlice.start - cpuSliceWhenLastRunning.end) <
+ 0.00001) {
+ return nextRunningSlice;
+ }
+ return undefined;
+ }
+ };
+
+ tr.model.EventRegistry.register(
+ ThreadTimeSlice,
+ {
+ name: 'threadTimeSlice',
+ pluralName: 'threadTimeSlices'
+ });
+
+
+ return {
+ ThreadTimeSlice,
+ SCHEDULING_STATE,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/time_to_object_instance_map.html b/chromium/third_party/catapult/tracing/tracing/model/time_to_object_instance_map.html
new file mode 100644
index 00000000000..3c05b08d4a2
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/time_to_object_instance_map.html
@@ -0,0 +1,179 @@
+<!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/range.html">
+<link rel="import" href="/tracing/base/utils.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Provides the TimeToObjectInstanceMap class.
+ */
+tr.exportTo('tr.model', function() {
+ /**
+ * Tracks all the instances associated with a given ID over its lifetime.
+ *
+ * A scoped id can be used multiple times throughout a trace, referring to
+ * different objects at different times. This data structure does the
+ * bookkeeping to figure out what ObjectInstance is referred to at a given
+ * timestamp.
+ *
+ * @constructor
+ */
+ function TimeToObjectInstanceMap(
+ createObjectInstanceFunction, parent, scopedId) {
+ this.createObjectInstanceFunction_ = createObjectInstanceFunction;
+ this.parent = parent;
+ this.scopedId = scopedId;
+ this.instances = [];
+ }
+
+ TimeToObjectInstanceMap.prototype = {
+ idWasCreated(category, name, ts) {
+ if (this.instances.length === 0) {
+ this.instances.push(this.createObjectInstanceFunction_(
+ this.parent, this.scopedId, category, name, ts));
+ this.instances[0].creationTsWasExplicit = true;
+ return this.instances[0];
+ }
+
+ let lastInstance = this.instances[this.instances.length - 1];
+ if (ts < lastInstance.deletionTs) {
+ throw new Error('Mutation of the TimeToObjectInstanceMap must be ' +
+ 'done in ascending timestamp order.');
+ }
+ lastInstance = this.createObjectInstanceFunction_(
+ this.parent, this.scopedId, category, name, ts);
+ lastInstance.creationTsWasExplicit = true;
+ this.instances.push(lastInstance);
+ return lastInstance;
+ },
+
+ addSnapshot(category, name, ts, args, opt_baseTypeName) {
+ if (this.instances.length === 0) {
+ this.instances.push(this.createObjectInstanceFunction_(
+ this.parent, this.scopedId, category, name, ts, opt_baseTypeName));
+ }
+
+ const i = tr.b.findIndexInSortedIntervals(
+ this.instances,
+ function(inst) { return inst.creationTs; },
+ function(inst) { return inst.deletionTs - inst.creationTs; },
+ ts);
+
+ let instance;
+ if (i < 0) {
+ instance = this.instances[0];
+ if (ts > instance.deletionTs ||
+ instance.creationTsWasExplicit) {
+ throw new Error(
+ 'At the provided timestamp, no instance was still alive');
+ }
+
+ if (instance.snapshots.length !== 0) {
+ throw new Error(
+ 'Cannot shift creationTs forward, ' +
+ 'snapshots have been added. First snap was at ts=' +
+ instance.snapshots[0].ts + ' and creationTs was ' +
+ instance.creationTs);
+ }
+ instance.creationTs = ts;
+ } else if (i >= this.instances.length) {
+ instance = this.instances[this.instances.length - 1];
+ if (ts >= instance.deletionTs) {
+ // The snap is added after our oldest and deleted instance. This means
+ // that this is a new implicit instance.
+ instance = this.createObjectInstanceFunction_(
+ this.parent, this.scopedId, category, name, ts, opt_baseTypeName);
+ this.instances.push(instance);
+ } else {
+ // If the ts is before the last objects deletion time, then the caller
+ // is trying to add a snapshot when there may have been an instance
+ // alive. In that case, try to move an instance's creationTs to
+ // include this ts, provided that it has an implicit creationTs.
+
+ // Search backward from the right for an instance that was definitely
+ // deleted before this ts. Any time an instance is found that has a
+ // moveable creationTs
+ let lastValidIndex;
+ for (let i = this.instances.length - 1; i >= 0; i--) {
+ const tmp = this.instances[i];
+ if (ts >= tmp.deletionTs) break;
+ if (tmp.creationTsWasExplicit === false &&
+ tmp.snapshots.length === 0) {
+ lastValidIndex = i;
+ }
+ }
+ if (lastValidIndex === undefined) {
+ throw new Error(
+ 'Cannot add snapshot. No instance was alive that was mutable.');
+ }
+ instance = this.instances[lastValidIndex];
+ instance.creationTs = ts;
+ }
+ } else {
+ instance = this.instances[i];
+ }
+
+ return instance.addSnapshot(ts, args, name, opt_baseTypeName);
+ },
+
+ get lastInstance() {
+ if (this.instances.length === 0) return undefined;
+ return this.instances[this.instances.length - 1];
+ },
+
+ idWasDeleted(category, name, ts) {
+ if (this.instances.length === 0) {
+ this.instances.push(this.createObjectInstanceFunction_(
+ this.parent, this.scopedId, category, name, ts));
+ }
+ let lastInstance = this.instances[this.instances.length - 1];
+ if (ts < lastInstance.creationTs) {
+ throw new Error('Cannot delete an id before it was created');
+ }
+ if (lastInstance.deletionTs === Number.MAX_VALUE) {
+ lastInstance.wasDeleted(ts);
+ return lastInstance;
+ }
+
+ if (ts < lastInstance.deletionTs) {
+ throw new Error('id was already deleted earlier.');
+ }
+
+ // A new instance was deleted with no snapshots in-between.
+ // Create an instance then kill it.
+ lastInstance = this.createObjectInstanceFunction_(
+ this.parent, this.scopedId, category, name, ts);
+ this.instances.push(lastInstance);
+ lastInstance.wasDeleted(ts);
+ return lastInstance;
+ },
+
+ getInstanceAt(ts) {
+ const i = tr.b.findIndexInSortedIntervals(
+ this.instances,
+ function(inst) { return inst.creationTs; },
+ function(inst) { return inst.deletionTs - inst.creationTs; },
+ ts);
+ if (i < 0) {
+ if (this.instances[0].creationTsWasExplicit) {
+ return undefined;
+ }
+ return this.instances[0];
+ } else if (i >= this.instances.length) {
+ return undefined;
+ }
+ return this.instances[i];
+ }
+ };
+
+ return {
+ TimeToObjectInstanceMap,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/time_to_object_instance_map_test.html b/chromium/third_party/catapult/tracing/tracing/model/time_to_object_instance_map_test.html
new file mode 100644
index 00000000000..70cd9ad0d5f
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/time_to_object_instance_map_test.html
@@ -0,0 +1,164 @@
+<!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/object_instance.html">
+<link rel="import" href="/tracing/model/time_to_object_instance_map.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const createObjectInstance = function(
+ parent, id, category, name, creationTs) {
+ return new tr.model.ObjectInstance(
+ parent, id, category, name, creationTs);
+ };
+
+ test('timeToObjectInstanceMap', function() {
+ const m = new tr.model.TimeToObjectInstanceMap(
+ createObjectInstance, {}, 7);
+ m.addSnapshot('cat', 'name', 10, 'a1');
+ m.addSnapshot('cat', 'name', 20, 'a2');
+ m.idWasDeleted('cat', 'name', 30);
+ m.addSnapshot('cat', 'name', 40, 'b');
+
+ assert.strictEqual(m.instances.length, 2);
+
+ const i0 = m.getInstanceAt(0);
+ const i10 = m.getInstanceAt(10);
+ assert.strictEqual(i0, i10);
+
+ assert.isDefined(i10);
+ assert.strictEqual(i10.snapshots.length, 2);
+ assert.strictEqual(i10.snapshots[0].args, 'a1');
+ assert.strictEqual(i10.snapshots[1].args, 'a2');
+
+ assert.strictEqual(i10.deletionTs, 30);
+
+ const i15 = m.getInstanceAt(15);
+ assert.strictEqual(i15, i10);
+
+ const i20 = m.getInstanceAt(20);
+ assert.strictEqual(i20, i10);
+
+ const i30 = m.getInstanceAt(30);
+ assert.isUndefined(i30);
+
+ const i35 = m.getInstanceAt(35);
+ assert.isUndefined(i35);
+
+ const i40 = m.getInstanceAt(40);
+ assert.isDefined(i40);
+ assert.notEqual(i40, i10);
+ assert.strictEqual(i40.snapshots.length, 1);
+ assert.strictEqual(i40.creationTs, 40);
+ assert.strictEqual(i40.deletionTs, Number.MAX_VALUE);
+
+ const i41 = m.getInstanceAt(41);
+ assert.strictEqual(i40, i41);
+ });
+
+ test('timeToObjectInstanceMapsBoundsLogic', function() {
+ const m = new tr.model.TimeToObjectInstanceMap(
+ createObjectInstance, {}, 7);
+ m.addSnapshot('cat', 'name', 10, 'a1');
+ m.addSnapshot('cat', 'name', 20, 'a2');
+ m.idWasDeleted('cat', 'name', 30);
+ m.addSnapshot('cat', 'name', 40, 'b');
+ m.addSnapshot('cat', 'name', 41, 'b');
+
+ m.instances.forEach(function(i) { i.updateBounds(); });
+
+ const iA = m.getInstanceAt(10);
+ assert.strictEqual(iA.bounds.min, 10);
+ assert.strictEqual(iA.bounds.max, 30);
+
+ const iB = m.getInstanceAt(40);
+ assert.strictEqual(iB.bounds.min, 40);
+ assert.strictEqual(iB.bounds.max, 41);
+ });
+
+ test('earlySnapshot', function() {
+ const m = new tr.model.TimeToObjectInstanceMap(
+ createObjectInstance, {}, 7);
+ const i10 = m.idWasCreated('cat', 'name', 10, 'a1');
+ m.idWasDeleted('cat', 'name', 20);
+
+ assert.throws(function() {
+ m.addSnapshot('cat', 'name', 5, 'a1');
+ });
+ assert.strictEqual(i10.creationTs, 10);
+ assert.strictEqual(i10.deletionTs, 20);
+ });
+
+ test('earlySnapshotWithImplicitCreate', function() {
+ const m = new tr.model.TimeToObjectInstanceMap(
+ createObjectInstance, {}, 7);
+ const i10 = m.idWasDeleted('cat', 'name', 20);
+ m.addSnapshot('cat', 'name', 5, 'a1');
+ assert.strictEqual(i10.creationTs, 5);
+ assert.strictEqual(i10.deletionTs, 20);
+ });
+
+ test('getInstanceBeforeCreationImplicitCreate', function() {
+ const m = new tr.model.TimeToObjectInstanceMap(
+ createObjectInstance, {}, 7);
+ const i10 = m.idWasCreated('cat', 'name', 10, 'a1');
+ m.idWasDeleted('cat', 'name', 20);
+ assert.isUndefined(m.getInstanceAt(5));
+ });
+
+ test('getInstanceBeforeCreationImplicitCreateWithSnapshot', function() {
+ const m = new tr.model.TimeToObjectInstanceMap(
+ createObjectInstance, {}, 7);
+ const s5 = m.addSnapshot('cat', 'name', 5, 'a1');
+ const i10 = m.idWasDeleted('cat', 'name', 20);
+ assert.strictEqual(m.getInstanceAt(5), i10);
+ });
+
+ test('successiveDeletions', function() {
+ const m = new tr.model.TimeToObjectInstanceMap(
+ createObjectInstance, {}, 7);
+ const i20 = m.idWasDeleted('cat', 'name', 20);
+ const i30 = m.idWasDeleted('cat', 'name', 30);
+ const i40 = m.idWasDeleted('cat', 'name', 40);
+ assert.strictEqual(i20.creationTs, 20);
+ assert.isFalse(i20.creationTsWasExplicit);
+ assert.strictEqual(i20.deletionTs, 20);
+ assert.isTrue(i20.deletionTsWasExplicit);
+
+ assert.strictEqual(i30.creationTs, 30);
+ assert.isFalse(i30.creationTsWasExplicit);
+ assert.strictEqual(i30.deletionTs, 30);
+ assert.isTrue(i30.deletionTsWasExplicit);
+
+
+ assert.strictEqual(i40.creationTs, 40);
+ assert.isFalse(i40.creationTsWasExplicit);
+ assert.strictEqual(i40.deletionTs, 40);
+ assert.isTrue(i40.deletionTsWasExplicit);
+ });
+
+ test('snapshotAfterDeletion', function() {
+ const m = new tr.model.TimeToObjectInstanceMap(
+ createObjectInstance, {}, 7);
+ const i10 = m.idWasCreated('cat', 'name', 10, 'a1');
+ m.idWasDeleted('cat', 'name', 20);
+
+ const s25 = m.addSnapshot('cat', 'name', 25, 'a1');
+ const i25 = s25.objectInstance;
+
+ assert.strictEqual(i10.creationTs, 10);
+ assert.strictEqual(i10.deletionTs, 20);
+ assert.notEqual(i25, i10);
+ assert.strictEqual(i25.creationTs, 25);
+ assert.strictEqual(i25.deletionTs, Number.MAX_VALUE);
+ });
+});
+</script>
+
diff --git a/chromium/third_party/catapult/tracing/tracing/model/timed_event.html b/chromium/third_party/catapult/tracing/tracing/model/timed_event.html
new file mode 100644
index 00000000000..4916162a4e1
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/timed_event.html
@@ -0,0 +1,69 @@
+<!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/guid.html">
+<link rel="import" href="/tracing/base/math/range.html">
+<link rel="import" href="/tracing/base/time_display_modes.html">
+<link rel="import" href="/tracing/model/event.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.model', function() {
+ /**
+ * TimedEvent is a base type for any entity in the trace model with a specific
+ * start and duration.
+ *
+ * @constructor
+ */
+ function TimedEvent(start) {
+ tr.model.Event.call(this);
+ this.start = start;
+ this.duration = 0;
+ this.cpuStart = undefined;
+ this.cpuDuration = undefined;
+ // The set of contexts this event belongs to (order is unimportant). This
+ // array should never be modified.
+ this.contexts = Object.freeze([]);
+ }
+
+ TimedEvent.prototype = {
+ __proto__: tr.model.Event.prototype,
+
+ get end() {
+ return this.start + this.duration;
+ },
+
+ get boundsRange() {
+ return tr.b.math.Range.fromExplicitRange(this.start, this.end);
+ },
+
+ addBoundsToRange(range) {
+ range.addValue(this.start);
+ range.addValue(this.end);
+ },
+
+ // TODO(charliea): Can this be implemented in terms of Event.range()?
+ // Returns true if 'that' TimedEvent is fully contained within 'this' timed
+ // event.
+ bounds(that, opt_precisionUnit) {
+ if (opt_precisionUnit === undefined) {
+ opt_precisionUnit = tr.b.TimeDisplayModes.ms;
+ }
+
+ const startsBefore = opt_precisionUnit.roundedLess(
+ that.start, this.start);
+ const endsAfter = opt_precisionUnit.roundedLess(this.end, that.end);
+ return !startsBefore && !endsAfter;
+ }
+ };
+
+ return {
+ TimedEvent,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/timed_event_test.html b/chromium/third_party/catapult/tracing/tracing/model/timed_event_test.html
new file mode 100644
index 00000000000..f635dcf677d
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/timed_event_test.html
@@ -0,0 +1,43 @@
+<!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/timed_event.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ test('bounds_startPrecision', function() {
+ const unit = tr.b.TimeDisplayModes;
+
+ const outer = new tr.model.TimedEvent(10.0001);
+ outer.duration = 0.9999;
+ const inner = new tr.model.TimedEvent(10.0000);
+ inner.duration = 1.0000;
+
+ assert.isTrue(outer.bounds(inner));
+ assert.isTrue(outer.bounds(inner, unit.ms));
+
+ assert.isFalse(outer.bounds(inner, unit.ns));
+ });
+
+ test('bounds_endPrecision', function() {
+ const unit = tr.b.TimeDisplayModes;
+
+ const outer = new tr.model.TimedEvent(10.0000);
+ outer.duration = 0.9999;
+ const inner = new tr.model.TimedEvent(10.0000);
+ inner.duration = 1.0000;
+
+ assert.isTrue(outer.bounds(inner));
+ assert.isTrue(outer.bounds(inner, unit.ms));
+
+ assert.isFalse(outer.bounds(inner, unit.ns));
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/user_model/animation_expectation.html b/chromium/third_party/catapult/tracing/tracing/model/user_model/animation_expectation.html
new file mode 100644
index 00000000000..a3fced6de2c
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/user_model/animation_expectation.html
@@ -0,0 +1,51 @@
+<!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/user_model/user_expectation.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.model.um', function() {
+ function AnimationExpectation(
+ parentModel, initiatorTitle, start, duration) {
+ tr.model.um.UserExpectation.call(
+ this, parentModel, initiatorTitle, start, duration);
+ this.frameEvents_ = undefined;
+ }
+
+ AnimationExpectation.prototype = {
+ __proto__: tr.model.um.UserExpectation.prototype,
+ constructor: AnimationExpectation,
+
+ get frameEvents() {
+ if (this.frameEvents_) {
+ return this.frameEvents_;
+ }
+
+ this.frameEvents_ = new tr.model.EventSet();
+
+ this.associatedEvents.forEach(function(event) {
+ if (event.title === tr.model.helpers.IMPL_RENDERING_STATS) {
+ this.frameEvents_.push(event);
+ }
+ }, this);
+
+ return this.frameEvents_;
+ }
+ };
+
+ tr.model.um.UserExpectation.subTypes.register(AnimationExpectation, {
+ stageTitle: 'Animation',
+ colorId: tr.b.ColorScheme.getColorIdForReservedName('rail_animation')
+ });
+
+ return {
+ AnimationExpectation,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/user_model/idle_expectation.html b/chromium/third_party/catapult/tracing/tracing/model/user_model/idle_expectation.html
new file mode 100644
index 00000000000..2312d46b62a
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/user_model/idle_expectation.html
@@ -0,0 +1,34 @@
+<!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/user_model/user_expectation.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.model.um', function() {
+ function IdleExpectation(parentModel, start, duration) {
+ const initiatorTitle = '';
+ tr.model.um.UserExpectation.call(
+ this, parentModel, initiatorTitle, start, duration);
+ }
+
+ IdleExpectation.prototype = {
+ __proto__: tr.model.um.UserExpectation.prototype,
+ constructor: IdleExpectation
+ };
+
+ tr.model.um.UserExpectation.subTypes.register(IdleExpectation, {
+ stageTitle: 'Idle',
+ colorId: tr.b.ColorScheme.getColorIdForReservedName('rail_idle')
+ });
+
+ return {
+ IdleExpectation,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/user_model/load_expectation.html b/chromium/third_party/catapult/tracing/tracing/model/user_model/load_expectation.html
new file mode 100644
index 00000000000..fb5298a46ed
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/user_model/load_expectation.html
@@ -0,0 +1,93 @@
+<!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/user_model/user_expectation.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.model.um', function() {
+ const LOAD_SUBTYPE_NAMES = {
+ SUCCESSFUL: 'Successful',
+ FAILED: 'Failed',
+ };
+
+ const DOES_LOAD_SUBTYPE_NAME_EXIST = {};
+ for (const key in LOAD_SUBTYPE_NAMES) {
+ DOES_LOAD_SUBTYPE_NAME_EXIST[LOAD_SUBTYPE_NAMES[key]] = true;
+ }
+
+ function LoadExpectation(parentModel, initiatorTitle, start, duration,
+ renderer, navigationStart, fmpEvent, dclEndEvent, cpuIdleTime,
+ timeToInteractive, url, frameId) {
+ if (!DOES_LOAD_SUBTYPE_NAME_EXIST[initiatorTitle]) {
+ throw new Error(initiatorTitle + ' is not in LOAD_SUBTYPE_NAMES');
+ }
+
+ tr.model.um.UserExpectation.call(
+ this, parentModel, initiatorTitle, start, duration);
+
+ // |renderProcess| is the renderer process that contains the loading
+ // RenderFrame.
+ this.renderProcess = renderer;
+
+ // |renderMainThread| is the CrRendererMain thread in the |renderProcess|
+ // that contains the loading RenderFrame.
+ this.renderMainThread = undefined;
+
+ // |routingId| identifies the loading RenderFrame within the renderer
+ // process.
+ this.routingId = undefined;
+
+ // |parentRoutingId| identifies the RenderFrame that created and contains
+ // the loading RenderFrame.
+ this.parentRoutingId = undefined;
+
+ // |loadFinishedEvent|, if present, signals that this is a main frame.
+ this.loadFinishedEvent = undefined;
+
+ // Startup LoadExpectations do not have renderProcess, routingId, or
+ // parentRoutingId. Maybe RenderLoadExpectation should be a separate class?
+
+ // Navigation start event. The start of this event is the start time of
+ // load expectation.
+ this.navigationStart = navigationStart;
+
+ // First meaningful event corresponding to the navigation start event.
+ this.fmpEvent = fmpEvent;
+
+ // DomcontentLoadedEndEvent corresponding to the navigation start event.
+ this.domContentLoadedEndEvent = dclEndEvent;
+
+ // The computed firstCpuIdleTime. Please look at time_to_interactive.html
+ // for further details about this.
+ this.firstCpuIdleTime = cpuIdleTime;
+
+ // The time at which renderer is interactive. Please look at
+ // time_to_interactive.html for further details on how this is computed.
+ this.timeToInteractive = timeToInteractive;
+
+ this.url = url;
+ this.frameId = frameId;
+ }
+
+ LoadExpectation.prototype = {
+ __proto__: tr.model.um.UserExpectation.prototype,
+ constructor: LoadExpectation
+ };
+
+ tr.model.um.UserExpectation.subTypes.register(LoadExpectation, {
+ stageTitle: 'Load',
+ colorId: tr.b.ColorScheme.getColorIdForReservedName('rail_load')
+ });
+
+ return {
+ LOAD_SUBTYPE_NAMES,
+ LoadExpectation,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/user_model/response_expectation.html b/chromium/third_party/catapult/tracing/tracing/model/user_model/response_expectation.html
new file mode 100644
index 00000000000..83faacb27c3
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/user_model/response_expectation.html
@@ -0,0 +1,35 @@
+<!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/user_model/user_expectation.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.model.um', function() {
+ function ResponseExpectation(
+ parentModel, initiatorTitle, start, duration, opt_isAnimationBegin) {
+ tr.model.um.UserExpectation.call(
+ this, parentModel, initiatorTitle, start, duration);
+ this.isAnimationBegin = opt_isAnimationBegin || false;
+ }
+
+ ResponseExpectation.prototype = {
+ __proto__: tr.model.um.UserExpectation.prototype,
+ constructor: ResponseExpectation
+ };
+
+ tr.model.um.UserExpectation.subTypes.register(ResponseExpectation, {
+ stageTitle: 'Response',
+ colorId: tr.b.ColorScheme.getColorIdForReservedName('rail_response')
+ });
+
+ return {
+ ResponseExpectation,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/user_model/segment.html b/chromium/third_party/catapult/tracing/tracing/model/user_model/segment.html
new file mode 100644
index 00000000000..61da1e26363
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/user_model/segment.html
@@ -0,0 +1,49 @@
+<!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/timed_event.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.model.um', function() {
+ /**
+ * Segment represents a range of time during which the set of active
+ * UserExpectations does not change. Segments are guaranteed to not overlap,
+ * whereas UserExpectations can overlap. After UserModelBuilder builds the
+ * UserExpectations in the model, it segments the timeline into
+ * non-overlapping Segments and adds the constituent UserExpectations to each
+ * Segment.
+ */
+ class Segment extends tr.model.TimedEvent {
+ constructor(start, duration) {
+ super(start);
+ this.duration = duration;
+ this.expectations_ = [];
+ }
+
+ get expectations() {
+ return this.expectations_;
+ }
+
+ clone() {
+ const clone = new Segment(this.start, this.duration);
+ clone.expectations.push(...this.expectations);
+ return clone;
+ }
+
+ addSegment(other) {
+ this.duration += other.duration;
+ this.expectations.push(...other.expectations);
+ }
+ }
+
+ return {
+ Segment,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/user_model/startup_expectation.html b/chromium/third_party/catapult/tracing/tracing/model/user_model/startup_expectation.html
new file mode 100644
index 00000000000..809debc3fb3
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/user_model/startup_expectation.html
@@ -0,0 +1,33 @@
+<!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/user_model/user_expectation.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.model.um', function() {
+ function StartupExpectation(parentModel, start, duration) {
+ tr.model.um.UserExpectation.call(
+ this, parentModel, '', start, duration);
+ }
+
+ StartupExpectation.prototype = {
+ __proto__: tr.model.um.UserExpectation.prototype,
+ constructor: StartupExpectation
+ };
+
+ tr.model.um.UserExpectation.subTypes.register(StartupExpectation, {
+ stageTitle: 'Startup',
+ colorId: tr.b.ColorScheme.getColorIdForReservedName('startup')
+ });
+
+ return {
+ StartupExpectation,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/user_model/stub_expectation.html b/chromium/third_party/catapult/tracing/tracing/model/user_model/stub_expectation.html
new file mode 100644
index 00000000000..4bd48ffba58
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/user_model/stub_expectation.html
@@ -0,0 +1,76 @@
+<!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/user_model/user_expectation.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Stub version of UserExpectation for testing.
+ */
+tr.exportTo('tr.model.um', function() {
+ function StubExpectation(args) {
+ this.stageTitle_ = args.stageTitle || 'Idle';
+ this.initiatorTitle_ = args.initiatorTitle || '';
+
+ this.title_ = args.title;
+ if (!this.title_) {
+ const defaultTitle = [];
+ if (this.initiatorTitle_) {
+ defaultTitle.push(this.initiatorTitle_);
+ }
+ if (this.stageTitle_) {
+ defaultTitle.push(this.stageTitle_);
+ }
+ this.title_ = defaultTitle.join(' ') || 'title';
+ }
+
+ this.normalizedUserComfort_ = args.normalizedUserComfort || 0;
+ this.normalizedEfficiency_ = args.normalizedEfficiency || 0;
+
+ const sd = tr.c.TestUtils.getStartAndDurationFromDict(args);
+
+ tr.model.um.UserExpectation.call(
+ this, args.parentModel, this.initiatorTitle, sd.start, sd.duration);
+
+ // Must be set after base class call.
+ this.colorId_ = args.colorId || 0;
+
+ if (args.associatedEvents) {
+ args.associatedEvents.forEach(function(event) {
+ this.associatedEvents.push(event);
+ }, this);
+ }
+ }
+
+ StubExpectation.prototype = {
+ __proto__: tr.model.um.UserExpectation.prototype,
+
+ get colorId() {
+ return this.colorId_;
+ },
+
+ get title() {
+ return this.title_;
+ },
+
+ get stageTitle() {
+ return this.stageTitle_;
+ },
+
+ get initiatorTitle() {
+ return this.initiatorTitle_;
+ }
+ };
+
+ return {
+ StubExpectation,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/user_model/user_expectation.html b/chromium/third_party/catapult/tracing/tracing/model/user_model/user_expectation.html
new file mode 100644
index 00000000000..04363da142a
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/user_model/user_expectation.html
@@ -0,0 +1,177 @@
+<!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_utils.html">
+<link rel="import" href="/tracing/base/math/statistics.html">
+<link rel="import" href="/tracing/base/unit.html">
+<link rel="import" href="/tracing/model/compound_event_selection_state.html">
+<link rel="import" href="/tracing/model/event_set.html">
+<link rel="import" href="/tracing/model/timed_event.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.model.um', function() {
+ const CompoundEventSelectionState = tr.model.CompoundEventSelectionState;
+
+ function UserExpectation(parentModel, initiatorType, start, duration) {
+ tr.model.TimedEvent.call(this, start);
+ this.associatedEvents = new tr.model.EventSet();
+ this.duration = duration;
+ this.initiatorType_ = initiatorType;
+ this.parentModel = parentModel;
+ this.typeInfo_ = undefined;
+
+ // sourceEvents are the ones that caused the UserModelBuilder to create this
+ // UserExpectation.
+ this.sourceEvents = new tr.model.EventSet();
+ }
+
+ // Strings used to name UEs.
+ const INITIATOR_TYPE = {
+ KEYBOARD: 'Keyboard',
+ MOUSE: 'Mouse',
+ MOUSE_WHEEL: 'MouseWheel',
+ TAP: 'Tap',
+ PINCH: 'Pinch',
+ FLING: 'Fling',
+ TOUCH: 'Touch',
+ SCROLL: 'Scroll',
+ CSS: 'CSS',
+ WEBGL: 'WebGL',
+ VIDEO: 'Video',
+ VR: 'VR',
+ };
+
+ UserExpectation.prototype = {
+ __proto__: tr.model.TimedEvent.prototype,
+
+ computeCompoundEvenSelectionState(selection) {
+ let cess = CompoundEventSelectionState.NOT_SELECTED;
+ if (selection.contains(this)) {
+ cess |= CompoundEventSelectionState.EVENT_SELECTED;
+ }
+
+ if (this.associatedEvents.intersectionIsEmpty(selection)) {
+ return cess;
+ }
+
+ const allContained = this.associatedEvents.every(function(event) {
+ return selection.contains(event);
+ });
+
+ if (allContained) {
+ cess |= CompoundEventSelectionState.ALL_ASSOCIATED_EVENTS_SELECTED;
+ } else {
+ cess |= CompoundEventSelectionState.SOME_ASSOCIATED_EVENTS_SELECTED;
+ }
+ return cess;
+ },
+
+ // Returns samples which are overlapping with V8.Execute
+ get associatedSamples() {
+ const samples = new tr.model.EventSet();
+ this.associatedEvents.forEach(function(event) {
+ if (event instanceof tr.model.ThreadSlice) {
+ samples.addEventSet(event.overlappingSamples);
+ }
+ });
+ return samples;
+ },
+
+ get userFriendlyName() {
+ return this.title + ' User Expectation at ' +
+ tr.b.Unit.byName.timeStampInMs.format(this.start);
+ },
+
+ get stableId() {
+ return ('UserExpectation.' + this.guid);
+ },
+
+ get typeInfo() {
+ if (!this.typeInfo_) {
+ this.typeInfo_ = UserExpectation.subTypes.findTypeInfo(
+ this.constructor);
+ }
+
+ // If you set Subclass.prototype = {}, then you must explicitly specify
+ // constructor in that prototype object!
+ // http://javascript.info/tutorial/constructor
+
+ if (!this.typeInfo_) {
+ throw new Error('Unregistered UserExpectation');
+ }
+
+ return this.typeInfo_;
+ },
+
+ get colorId() {
+ return this.typeInfo.metadata.colorId;
+ },
+
+ get stageTitle() {
+ return this.typeInfo.metadata.stageTitle;
+ },
+
+ get initiatorType() {
+ return this.initiatorType_;
+ },
+
+ get title() {
+ if (!this.initiatorType) {
+ return this.stageTitle;
+ }
+
+ return this.initiatorType + ' ' + this.stageTitle;
+ },
+
+ /**
+ * Returns the sum of the number of CPU ms spent by this UserExpectation.
+ */
+ get totalCpuMs() {
+ let cpuMs = 0;
+ this.associatedEvents.forEach(function(event) {
+ if (event.cpuSelfTime) {
+ cpuMs += event.cpuSelfTime;
+ }
+ });
+ return cpuMs;
+ }
+ };
+
+ const subTypes = {};
+ const options = new tr.b.ExtensionRegistryOptions(tr.b.BASIC_REGISTRY_MODE);
+ tr.b.decorateExtensionRegistry(subTypes, options);
+
+ subTypes.addEventListener('will-register', function(e) {
+ const metadata = e.typeInfo.metadata;
+
+ if (metadata.stageTitle === undefined) {
+ throw new Error('Registered UserExpectations must provide ' +
+ 'stageTitle');
+ }
+
+ if (metadata.colorId === undefined) {
+ throw new Error('Registered UserExpectations must provide ' +
+ 'colorId');
+ }
+ });
+
+ tr.model.EventRegistry.register(
+ UserExpectation,
+ {
+ name: 'userExpectation',
+ pluralName: 'userExpectations',
+ subTypes
+ });
+
+ return {
+ UserExpectation,
+ INITIATOR_TYPE,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/user_model/user_model.html b/chromium/third_party/catapult/tracing/tracing/model/user_model/user_model.html
new file mode 100644
index 00000000000..38b6e13a00c
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/user_model/user_model.html
@@ -0,0 +1,95 @@
+<!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/model/event_container.html">
+<link rel="import" href="/tracing/model/user_model/segment.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.model.um', function() {
+ class UserModel extends tr.model.EventContainer {
+ constructor(parentModel) {
+ super();
+ this.parentModel_ = parentModel;
+ this.expectations_ = new tr.model.EventSet();
+ this.segments_ = [];
+ }
+
+ get stableId() {
+ return 'UserModel';
+ }
+
+ get parentModel() {
+ return this.parentModel_;
+ }
+
+ sortExpectations() {
+ this.expectations_.sortEvents((x, y) => (x.start - y.start));
+ }
+
+ get expectations() {
+ return this.expectations_;
+ }
+
+ shiftTimestampsForward(amount) {
+ }
+
+ addCategoriesToDict(categoriesDict) {
+ }
+
+ get segments() {
+ return this.segments_;
+ }
+
+ * childEvents() {
+ yield* this.expectations;
+ }
+
+ * childEventContainers() {
+ }
+
+ updateBounds() {
+ this.bounds.reset();
+ for (const expectation of this.expectations) {
+ expectation.addBoundsToRange(this.bounds);
+ }
+ }
+
+ /**
+ * Return a new array of new Segments by merging adjacent segments when
+ * |getKeyForSegment| returns identical keys.
+ * |getKeyForSegment| is called with each Segment and the index of that
+ * Segment.
+ *
+ * @param {!function(!tr.model.um.Segment, number):*} getKeyForSegment
+ * @return {!Array.<!tr.model.um.Segment>}
+ */
+ resegment(getKeyForSegment) {
+ const newSegments = [];
+ let prevKey = undefined;
+ let prevSegment = undefined;
+ for (let i = 0; i < this.segments.length; ++i) {
+ const segment = this.segments[i];
+ const key = getKeyForSegment(segment, i);
+ if (prevSegment !== undefined && key === prevKey) {
+ prevSegment.addSegment(segment);
+ } else {
+ prevSegment = segment.clone();
+ newSegments.push(prevSegment);
+ }
+ prevKey = key;
+ }
+ return newSegments;
+ }
+ }
+
+ return {
+ UserModel,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/user_model/user_model_test.html b/chromium/third_party/catapult/tracing/tracing/model/user_model/user_model_test.html
new file mode 100644
index 00000000000..c2a09c5074e
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/user_model/user_model_test.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<!--
+Copyright 2017 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/user_model/segment.html">
+<link rel="import" href="/tracing/model/user_model/user_model.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ test('resegment', function() {
+ const userModel = new tr.model.um.UserModel(undefined);
+ userModel.segments.push(new tr.model.um.Segment(0, 1));
+ userModel.segments.push(new tr.model.um.Segment(1, 1));
+ userModel.segments.push(new tr.model.um.Segment(2, 1));
+ userModel.segments.push(new tr.model.um.Segment(3, 1));
+
+ userModel.segments[0].expectations.push('a');
+ userModel.segments[1].expectations.push('b');
+ userModel.segments[2].expectations.push('c');
+ userModel.segments[3].expectations.push('d');
+
+ const newSegments = userModel.resegment(
+ (segment, index) => Math.floor(index / 2));
+
+ assert.lengthOf(newSegments, 2);
+ assert.strictEqual(0, newSegments[0].start);
+ assert.strictEqual(2, newSegments[0].end);
+ assert.strictEqual(2, newSegments[1].start);
+ assert.strictEqual(4, newSegments[1].end);
+
+ assert.lengthOf(newSegments[0].expectations, 2);
+ assert.lengthOf(newSegments[1].expectations, 2);
+
+ assert.strictEqual('a', newSegments[0].expectations[0]);
+ assert.strictEqual('b', newSegments[0].expectations[1]);
+ assert.strictEqual('c', newSegments[1].expectations[0]);
+ assert.strictEqual('d', newSegments[1].expectations[1]);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/vm_region.html b/chromium/third_party/catapult/tracing/tracing/model/vm_region.html
new file mode 100644
index 00000000000..4b02f7c24cc
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/vm_region.html
@@ -0,0 +1,444 @@
+<!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/base.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Provides classes for representing and classifying VM regions.
+ *
+ * See https://goo.gl/5SSPv0 for more details.
+ */
+tr.exportTo('tr.model', function() {
+ /**
+ * A single virtual memory region (also called a memory map).
+ *
+ * @constructor
+ */
+ function VMRegion(startAddress, sizeInBytes, protectionFlags,
+ mappedFile, byteStats) {
+ this.startAddress = startAddress;
+ this.sizeInBytes = sizeInBytes;
+ this.protectionFlags = protectionFlags;
+ this.mappedFile = mappedFile || '';
+ this.byteStats = byteStats || {};
+ }
+
+ VMRegion.PROTECTION_FLAG_READ = 4;
+ VMRegion.PROTECTION_FLAG_WRITE = 2;
+ VMRegion.PROTECTION_FLAG_EXECUTE = 1;
+ VMRegion.PROTECTION_FLAG_MAYSHARE = 128;
+
+ VMRegion.prototype = {
+ get uniqueIdWithinProcess() {
+ // This value is assumed to be unique within a process.
+ return this.mappedFile + '#' + this.startAddress;
+ },
+
+ get protectionFlagsToString() {
+ if (this.protectionFlags === undefined) return undefined;
+ return (
+ (this.protectionFlags & VMRegion.PROTECTION_FLAG_READ ? 'r' : '-') +
+ (this.protectionFlags & VMRegion.PROTECTION_FLAG_WRITE ? 'w' : '-') +
+ (this.protectionFlags & VMRegion.PROTECTION_FLAG_EXECUTE ?
+ 'x' : '-') +
+ (this.protectionFlags & VMRegion.PROTECTION_FLAG_MAYSHARE ? 's' : 'p')
+ );
+ }
+ };
+
+ VMRegion.fromDict = function(dict) {
+ return new VMRegion(
+ dict.startAddress,
+ dict.sizeInBytes,
+ dict.protectionFlags,
+ dict.mappedFile,
+ dict.byteStats);
+ };
+
+ /**
+ * Node in a VM region classification tree.
+ *
+ * Note: Most users of this class should use the
+ * VMRegionClassificationNode.fromRegions static method instead of this
+ * constructor because it leads to better performance due to fewer memory
+ * allocations.
+ *
+ * @constructor
+ */
+ function VMRegionClassificationNode(opt_rule) {
+ this.rule_ = opt_rule || VMRegionClassificationNode.CLASSIFICATION_RULES;
+
+ // True iff this node or any of its descendant classification nodes has at
+ // least one classified VM region.
+ this.hasRegions = false;
+
+ // Total virtual size and byte stats of all regions matching this node's
+ // rule (including its sub-rules).
+ this.sizeInBytes = undefined;
+ this.byteStats = {};
+
+ // Array of child classification nodes if this is an intermediate node.
+ this.children_ = undefined;
+
+ // Array of VM regions. If this is an intermediate node, then the regions
+ // are cached for lazy tree construction (i.e. its child classification
+ // nodes yet have to be built).
+ this.regions_ = [];
+ }
+
+ /**
+ * Rules for classifying memory maps.
+ *
+ * These rules are derived from core/jni/android_os_Debug.cpp in Android.
+ */
+ VMRegionClassificationNode.CLASSIFICATION_RULES = {
+ name: 'Total',
+ children: [
+ {
+ name: 'Android',
+ file: /^\/dev\/ashmem(?!\/libc malloc)/,
+ children: [
+ {
+ name: 'Java runtime',
+ file: /^\/dev\/ashmem\/dalvik-/,
+ children: [
+ {
+ name: 'Spaces',
+ file: /\/dalvik-(alloc|main|large object|non moving|zygote) space/, // @suppress longLineCheck
+ children: [
+ {
+ name: 'Normal',
+ file: /\/dalvik-(alloc|main)/
+ },
+ {
+ name: 'Large',
+ file: /\/dalvik-large object/
+ },
+ {
+ name: 'Zygote',
+ file: /\/dalvik-zygote/
+ },
+ {
+ name: 'Non-moving',
+ file: /\/dalvik-non moving/
+ }
+ ]
+ },
+ {
+ name: 'Linear Alloc',
+ file: /\/dalvik-LinearAlloc/
+ },
+ {
+ name: 'Indirect Reference Table',
+ file: /\/dalvik-indirect.ref/
+ },
+ {
+ name: 'Cache',
+ file: /\/dalvik-jit-code-cache/
+ },
+ {
+ name: 'Accounting'
+ }
+ ]
+ },
+ {
+ name: 'Cursor',
+ file: /\/CursorWindow/
+ },
+ {
+ name: 'Ashmem'
+ }
+ ]
+ },
+ {
+ name: 'Native heap',
+ file: /^((\[heap\])|(\[anon:)|(\/dev\/ashmem\/libc malloc)|(\[discounted tracing overhead\])|$)/ // @suppress longLineCheck
+ },
+ {
+ name: 'Stack',
+ file: /^\[stack/
+ },
+ {
+ name: 'Files',
+ file: /\.((((jar)|(apk)|(ttf)|(odex)|(oat)|(art))$)|(dex)|(so))/,
+ children: [
+ {
+ name: 'so',
+ file: /\.so/
+ },
+ {
+ name: 'jar',
+ file: /\.jar$/
+ },
+ {
+ name: 'apk',
+ file: /\.apk$/
+ },
+ {
+ name: 'ttf',
+ file: /\.ttf$/
+ },
+ {
+ name: 'dex',
+ file: /\.((dex)|(odex$))/
+ },
+ {
+ name: 'oat',
+ file: /\.oat$/
+ },
+ {
+ name: 'art',
+ file: /\.art$/
+ }
+ ]
+ },
+ {
+ name: 'Devices',
+ file: /(^\/dev\/)|(anon_inode:dmabuf)/,
+ children: [
+ {
+ name: 'GPU',
+ file: /\/((nv)|(mali)|(kgsl))/
+ },
+ {
+ name: 'DMA',
+ file: /anon_inode:dmabuf/
+ }
+ ]
+ }
+ ]
+ };
+ VMRegionClassificationNode.OTHER_RULE = { name: 'Other' };
+
+ VMRegionClassificationNode.fromRegions = function(regions, opt_rules) {
+ const tree = new VMRegionClassificationNode(opt_rules);
+ tree.regions_ = regions;
+ for (let i = 0; i < regions.length; i++) {
+ tree.addStatsFromRegion_(regions[i]);
+ }
+ return tree;
+ };
+
+ VMRegionClassificationNode.prototype = {
+ get title() {
+ return this.rule_.name;
+ },
+
+ get children() {
+ if (this.isLeafNode) {
+ return undefined; // Leaf nodes don't have children (by definition).
+ }
+ if (this.children_ === undefined) {
+ this.buildTree_(); // Lazily classify VM regions.
+ }
+ return this.children_;
+ },
+
+ get regions() {
+ if (!this.isLeafNode) {
+ // Intermediate nodes only temporarily cache VM regions for lazy tree
+ // construction.
+ return undefined;
+ }
+ return this.regions_;
+ },
+
+ get allRegionsForTesting() {
+ if (this.regions_ !== undefined) {
+ if (this.children_ !== undefined) {
+ throw new Error('Internal error: a VM region classification node ' +
+ 'cannot have both regions and children');
+ }
+ // Leaf node (or caching internal node).
+ return this.regions_;
+ }
+
+ // Intermediate node.
+ let regions = [];
+ this.children_.forEach(function(childNode) {
+ regions = regions.concat(childNode.allRegionsForTesting);
+ });
+ return regions;
+ },
+
+ get isLeafNode() {
+ const children = this.rule_.children;
+ return children === undefined || children.length === 0;
+ },
+
+ addRegion(region) {
+ this.addRegionRecursively_(region, true /* addStatsToThisNode */);
+ },
+
+ someRegion(fn, opt_this) {
+ if (this.regions_ !== undefined) {
+ // Leaf node (or caching internal node).
+ return this.regions_.some(fn, opt_this);
+ }
+
+ // Intermediate node.
+ return this.children_.some(function(childNode) {
+ return childNode.someRegion(fn, opt_this);
+ });
+ },
+
+ addRegionRecursively_(region, addStatsToThisNode) {
+ if (addStatsToThisNode) {
+ this.addStatsFromRegion_(region);
+ }
+
+ if (this.regions_ !== undefined) {
+ if (this.children_ !== undefined) {
+ throw new Error('Internal error: a VM region classification node ' +
+ 'cannot have both regions and children');
+ }
+ // Leaf node or an intermediate node caching VM regions (add the
+ // region to this node and don't classify further).
+ this.regions_.push(region);
+ return;
+ }
+
+ // Non-leaf rule (classify region row further down the tree).
+ function regionRowMatchesChildNide(child) {
+ const fileRegExp = child.rule_.file;
+ if (fileRegExp === undefined) return true;
+ return fileRegExp.test(region.mappedFile);
+ }
+
+ let matchedChild = this.children_.find(regionRowMatchesChildNide);
+ if (matchedChild === undefined) {
+ // Region belongs to the 'Other' node (created lazily).
+ if (this.children_.length !== this.rule_.children.length) {
+ throw new Error('Internal error');
+ }
+ matchedChild = new VMRegionClassificationNode(
+ VMRegionClassificationNode.OTHER_RULE);
+ this.children_.push(matchedChild);
+ }
+
+ matchedChild.addRegionRecursively_(region, true);
+ },
+
+ buildTree_() {
+ const cachedRegions = this.regions_;
+ this.regions_ = undefined;
+
+ this.buildChildNodesRecursively_();
+ for (let i = 0; i < cachedRegions.length; i++) {
+ // Note that we don't add the VM region's stats to this node because
+ // they have already been added to it.
+ this.addRegionRecursively_(
+ cachedRegions[i], false /* addStatsToThisNode */);
+ }
+ },
+
+ buildChildNodesRecursively_() {
+ if (this.children_ !== undefined) {
+ throw new Error(
+ 'Internal error: Classification node already has children');
+ }
+ if (this.regions_ !== undefined && this.regions_.length !== 0) {
+ throw new Error(
+ 'Internal error: Classification node should have no regions');
+ }
+
+ if (this.isLeafNode) {
+ return; // Leaf node: Nothing to do.
+ }
+
+ // Intermediate node: Clear regions and build children recursively.
+ this.regions_ = undefined;
+ this.children_ = this.rule_.children.map(function(childRule) {
+ const child = new VMRegionClassificationNode(childRule);
+ child.buildChildNodesRecursively_();
+ return child;
+ });
+ },
+
+ addStatsFromRegion_(region) {
+ this.hasRegions = true;
+
+ // Aggregate virtual size.
+ const regionSizeInBytes = region.sizeInBytes;
+ if (regionSizeInBytes !== undefined) {
+ this.sizeInBytes = (this.sizeInBytes || 0) + regionSizeInBytes;
+ }
+
+ // Aggregate byte stats.
+ const thisByteStats = this.byteStats;
+ const regionByteStats = region.byteStats;
+ for (const byteStatName in regionByteStats) {
+ const regionByteStatValue = regionByteStats[byteStatName];
+ if (regionByteStatValue === undefined) continue;
+ thisByteStats[byteStatName] =
+ (thisByteStats[byteStatName] || 0) + regionByteStatValue;
+ }
+
+ // Aggregate java base.* stats.
+ if (region.mappedFile.includes('/base.odex') ||
+ region.mappedFile.includes('/base.vdex')) {
+ if (region.byteStats.proportionalResident !== undefined) {
+ thisByteStats.javaBasePss =
+ (thisByteStats.javaBasePss || 0) +
+ region.byteStats.proportionalResident;
+ }
+ if (region.byteStats.privateCleanResident !== undefined) {
+ thisByteStats.javaBaseCleanResident =
+ (thisByteStats.javaBaseCleanResident || 0) +
+ region.byteStats.privateCleanResident;
+ }
+ if (region.byteStats.sharedCleanResident !== undefined) {
+ thisByteStats.javaBaseCleanResident =
+ (thisByteStats.javaBaseCleanResident || 0) +
+ region.byteStats.sharedCleanResident;
+ }
+ }
+
+ // Aggregate native library stats.
+ const textProtectionFlags = (VMRegion.PROTECTION_FLAG_READ |
+ VMRegion.PROTECTION_FLAG_EXECUTE);
+ // On post-M devices, the native library is mapped from /base.apk. On M
+ // and earlier devices, it's mapped from /libchrome.so. In both cases only
+ // the regions that are readable and executable should be counted.
+ // TODO(mattcary): if both mappings are seen, something has gone wrong and
+ // some sort of error or fatal should be done. This should be tracked
+ // across regions, which means adding state to |this|.
+ if ((region.protectionFlags === textProtectionFlags) &&
+ (region.mappedFile.includes('/base.apk') ||
+ region.mappedFile.includes('/libchrome.so'))) {
+ if (regionSizeInBytes !== undefined) {
+ this.nativeLibrarySizeInBytes =
+ (this.nativeLibrarySizeInBytes || 0) + regionSizeInBytes;
+ }
+ if (region.byteStats.privateCleanResident !== undefined) {
+ thisByteStats.nativeLibraryPrivateCleanResident =
+ (thisByteStats.nativeLibraryPrivateCleanResident || 0) +
+ region.byteStats.privateCleanResident;
+ }
+ if (region.byteStats.sharedCleanResident !== undefined) {
+ thisByteStats.nativeLibrarySharedCleanResident =
+ (thisByteStats.nativeLibrarySharedCleanResident || 0) +
+ region.byteStats.sharedCleanResident;
+ }
+ if (region.byteStats.proportionalResident !== undefined) {
+ thisByteStats.nativeLibraryProportionalResident =
+ (thisByteStats.nativeLibraryProportionalResident || 0) +
+ region.byteStats.proportionalResident;
+ }
+ }
+ }
+ };
+
+ return {
+ VMRegion,
+ VMRegionClassificationNode,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/vm_region_test.html b/chromium/third_party/catapult/tracing/tracing/model/vm_region_test.html
new file mode 100644
index 00000000000..93b8ad1647c
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/vm_region_test.html
@@ -0,0 +1,1216 @@
+<!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/core/test_utils.html">
+<link rel="import" href="/tracing/model/memory_dump_test_utils.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const VMRegion = tr.model.VMRegion;
+ const VMRegionClassificationNode = tr.model.VMRegionClassificationNode;
+ const checkVMRegions = tr.model.MemoryDumpTestUtils.checkVMRegions;
+
+ function checkProtectionFlagsToString(protectionFlags, expectedString) {
+ const vmRegion = VMRegion.fromDict({
+ startAddress: 256,
+ sizeInBytes: 336,
+ protectionFlags,
+ mappedFile: '[stack:20310]',
+ byteStats: {
+ privateDirtyResident: 96,
+ swapped: 144,
+ proportionalResident: 158
+ }
+ });
+ assert.strictEqual(vmRegion.protectionFlagsToString, expectedString);
+ }
+
+ const TEST_RULES = {
+ name: 'Root',
+ children: [
+ {
+ name: 'Words',
+ file: /^[a-zA-Z]/,
+ children: [
+ {
+ name: 'A-D',
+ file: /^[a-dA-D]/
+ },
+ {
+ name: 'E-H',
+ file: /^[e-hE-H]/
+ }
+ ]
+ },
+ {
+ name: 'Digits',
+ file: /\d$/,
+ children: []
+ }
+ ]
+ };
+
+ // Constant representing the expectation that the children of a
+ // VMRegionClassificationNode have not been built yet.
+ const CHILDREN_NOT_BUILT_YET = {};
+
+ function checkTree(node, expectedStructure) {
+ assert.strictEqual(node.title, expectedStructure.title);
+ assert.strictEqual(node.hasRegions, expectedStructure.hasRegions);
+ assert.strictEqual(node.sizeInBytes, expectedStructure.sizeInBytes);
+ assert.deepEqual(node.byteStats, expectedStructure.byteStats || {});
+ assert.strictEqual(node.isLeafNode, expectedStructure.isLeafNode);
+
+ const actualRegions = node.regions;
+ const expectedRegions = expectedStructure.regions;
+ if (expectedRegions === undefined) {
+ assert.isUndefined(actualRegions);
+ } else {
+ assert.instanceOf(actualRegions, Array);
+ checkVMRegions(actualRegions, expectedRegions);
+ }
+
+ const expectedChildren = expectedStructure.children;
+ if (expectedChildren === CHILDREN_NOT_BUILT_YET) {
+ assert.isUndefined(node.children_);
+ } else if (expectedChildren === undefined) {
+ assert.isUndefined(node.children);
+ } else {
+ const actualChildrenMap = new Map();
+ node.children.forEach(function(childNode) {
+ actualChildrenMap.set(childNode.title, childNode);
+ });
+ const expectedChildrenMap = new Map();
+ expectedChildren.forEach(function(childNode) {
+ expectedChildrenMap.set(childNode.title, childNode);
+ });
+ assert.strictEqual(actualChildrenMap.size, expectedChildrenMap.size);
+ for (const title of expectedChildrenMap.keys()) {
+ checkTree(actualChildrenMap.get(title),
+ expectedChildrenMap.get(title));
+ }
+ }
+ }
+
+ function checkClassificationRules(mappedFile, expectedPath) {
+ const region = VMRegion.fromDict({
+ mappedFile,
+ sizeInBytes: 16,
+ byteStats: {
+ privateDirtyResident: 7
+ }
+ });
+ let node = VMRegionClassificationNode.fromRegions([region]);
+ for (const title of expectedPath) {
+ node = node.children.find(c => c.title === title);
+ }
+ assert.deepEqual(node.regions, [region]);
+ }
+
+ test('vmRegion_protectionFlagsToString', function() {
+ checkProtectionFlagsToString(undefined, undefined);
+ checkProtectionFlagsToString(0, '---p');
+ checkProtectionFlagsToString(VMRegion.PROTECTION_FLAG_READ, 'r--p');
+ checkProtectionFlagsToString(
+ VMRegion.PROTECTION_FLAG_READ | VMRegion.PROTECTION_FLAG_MAYSHARE,
+ 'r--s');
+ checkProtectionFlagsToString(
+ VMRegion.PROTECTION_FLAG_READ | VMRegion.PROTECTION_FLAG_EXECUTE,
+ 'r-xp');
+ checkProtectionFlagsToString(
+ VMRegion.PROTECTION_FLAG_READ | VMRegion.PROTECTION_FLAG_WRITE,
+ 'rw-p');
+ checkProtectionFlagsToString(
+ VMRegion.PROTECTION_FLAG_READ | VMRegion.PROTECTION_FLAG_WRITE |
+ VMRegion.PROTECTION_FLAG_EXECUTE,
+ 'rwxp');
+ checkProtectionFlagsToString(
+ VMRegion.PROTECTION_FLAG_READ | VMRegion.PROTECTION_FLAG_WRITE |
+ VMRegion.PROTECTION_FLAG_MAYSHARE,
+ 'rw-s');
+ checkProtectionFlagsToString(
+ VMRegion.PROTECTION_FLAG_READ | VMRegion.PROTECTION_FLAG_EXECUTE |
+ VMRegion.PROTECTION_FLAG_MAYSHARE,
+ 'r-xs');
+ checkProtectionFlagsToString(
+ VMRegion.PROTECTION_FLAG_READ | VMRegion.PROTECTION_FLAG_WRITE |
+ VMRegion.PROTECTION_FLAG_EXECUTE |
+ VMRegion.PROTECTION_FLAG_MAYSHARE,
+ 'rwxs');
+ });
+
+ // The add(After|Before)Build tests below check that the classification tree
+ // has the correct structure regardless of the ordering of adding regions and
+ // the lazy construction.
+
+ test('vmRegionClassificationNode_constructor_addAfterBuild', function() {
+ const rootNode = new VMRegionClassificationNode(TEST_RULES);
+
+ // Check the root node and verify that the full tree structure has *not*
+ // been constructed yet.
+ checkTree(rootNode, {
+ title: 'Root',
+ hasRegions: false,
+ isLeafNode: false,
+ children: CHILDREN_NOT_BUILT_YET
+ });
+
+ // Reading the children of the root node *should* trigger building the
+ // full tree.
+ checkTree(rootNode, {
+ title: 'Root',
+ hasRegions: false,
+ isLeafNode: false,
+ children: [
+ {
+ title: 'Words',
+ hasRegions: false,
+ isLeafNode: false,
+ children: [
+ {
+ title: 'A-D',
+ hasRegions: false,
+ isLeafNode: true,
+ regions: []
+ },
+ {
+ title: 'E-H',
+ hasRegions: false,
+ isLeafNode: true,
+ regions: []
+ }
+ ]
+ },
+ {
+ title: 'Digits',
+ hasRegions: false,
+ isLeafNode: true,
+ regions: []
+ }
+ ]
+ });
+
+ // Add VM regions to the tree *after* it has been fully built.
+ rootNode.addRegion(VMRegion.fromDict({
+ mappedFile: 'W2', // Root/Words/Other.
+ sizeInBytes: 16,
+ byteStats: {
+ proportionalResident: 32,
+ swapped: 64
+ }
+ }));
+ rootNode.addRegion(VMRegion.fromDict({
+ mappedFile: '__42', // Root/Digits.
+ byteStats: {
+ proportionalResident: 33,
+ privateDirtyResident: 77
+ }
+ }));
+ checkTree(rootNode, {
+ title: 'Root',
+ hasRegions: true,
+ sizeInBytes: 16,
+ byteStats: {
+ proportionalResident: 32 + 33,
+ privateDirtyResident: 77,
+ swapped: 64
+ },
+ isLeafNode: false,
+ children: [
+ {
+ title: 'Words',
+ hasRegions: true,
+ sizeInBytes: 16,
+ byteStats: {
+ proportionalResident: 32,
+ swapped: 64
+ },
+ isLeafNode: false,
+ children: [
+ {
+ title: 'A-D',
+ hasRegions: false,
+ isLeafNode: true,
+ regions: []
+ },
+ {
+ title: 'E-H',
+ hasRegions: false,
+ isLeafNode: true,
+ regions: []
+ },
+ {
+ title: 'Other',
+ hasRegions: true,
+ sizeInBytes: 16,
+ byteStats: {
+ proportionalResident: 32,
+ swapped: 64
+ },
+ isLeafNode: true,
+ regions: [
+ {
+ mappedFile: 'W2',
+ sizeInBytes: 16,
+ byteStats: {
+ proportionalResident: 32,
+ swapped: 64
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ title: 'Digits',
+ hasRegions: true,
+ byteStats: {
+ proportionalResident: 33,
+ privateDirtyResident: 77
+ },
+ isLeafNode: true,
+ regions: [
+ {
+ mappedFile: '__42',
+ byteStats: {
+ proportionalResident: 33,
+ privateDirtyResident: 77
+ }
+ }
+ ]
+ }
+ ]
+ });
+ });
+
+ test('vmRegionClassificationNode_constructor_addBeforeBuild', function() {
+ const rootNode = new VMRegionClassificationNode(TEST_RULES);
+
+ // Add regions to the tree *before* it has been fully built. This should
+ // *not* trigger building the full tree (but the total sizeInBytes and
+ // byteStats should be updated accordingly).
+ rootNode.addRegion(VMRegion.fromDict({
+ mappedFile: '__42', // Root/Digits.
+ byteStats: {
+ proportionalResident: 33,
+ privateDirtyResident: 77
+ }
+ }));
+ rootNode.addRegion(VMRegion.fromDict({
+ mappedFile: 'W2', // Root/Words/Other.
+ sizeInBytes: 16,
+ byteStats: {
+ proportionalResident: 32,
+ swapped: 64
+ }
+ }));
+ checkTree(rootNode, {
+ title: 'Root',
+ hasRegions: true,
+ sizeInBytes: 16,
+ byteStats: {
+ proportionalResident: 32 + 33,
+ privateDirtyResident: 77,
+ swapped: 64
+ },
+ isLeafNode: false,
+ children: CHILDREN_NOT_BUILT_YET
+ });
+
+ // Reading the children of the root node should trigger building the full
+ // tree.
+ checkTree(rootNode, {
+ title: 'Root',
+ hasRegions: true,
+ sizeInBytes: 16,
+ byteStats: {
+ proportionalResident: 32 + 33,
+ privateDirtyResident: 77,
+ swapped: 64
+ },
+ isLeafNode: false,
+ children: [
+ {
+ title: 'Words',
+ hasRegions: true,
+ sizeInBytes: 16,
+ byteStats: {
+ proportionalResident: 32,
+ swapped: 64
+ },
+ isLeafNode: false,
+ children: [
+ {
+ title: 'A-D',
+ hasRegions: false,
+ isLeafNode: true,
+ regions: []
+ },
+ {
+ title: 'E-H',
+ hasRegions: false,
+ isLeafNode: true,
+ regions: []
+ },
+ {
+ title: 'Other',
+ hasRegions: true,
+ sizeInBytes: 16,
+ byteStats: {
+ proportionalResident: 32,
+ swapped: 64
+ },
+ isLeafNode: true,
+ regions: [
+ {
+ mappedFile: 'W2',
+ sizeInBytes: 16,
+ byteStats: {
+ proportionalResident: 32,
+ swapped: 64
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ title: 'Digits',
+ hasRegions: true,
+ byteStats: {
+ proportionalResident: 33,
+ privateDirtyResident: 77
+ },
+ isLeafNode: true,
+ regions: [
+ {
+ mappedFile: '__42',
+ byteStats: {
+ proportionalResident: 33,
+ privateDirtyResident: 77
+ }
+ }
+ ]
+ }
+ ]
+ });
+
+ // Add more VM regions *after* the tree has been fully built.
+ rootNode.addRegion(VMRegion.fromDict({
+ mappedFile: '%invalid%', // Root/Other.
+ sizeInBytes: 123
+ }));
+ rootNode.addRegion(VMRegion.fromDict({
+ mappedFile: '__43', // Root/Digits.
+ byteStats: {
+ swapped: 19
+ }
+ }));
+ rootNode.addRegion(VMRegion.fromDict({
+ mappedFile: 'free', // Root/Words/E-H.
+ sizeInBytes: undefined
+ }));
+ checkTree(rootNode, {
+ title: 'Root',
+ hasRegions: true,
+ sizeInBytes: 16 + 123,
+ byteStats: {
+ proportionalResident: 32 + 33,
+ privateDirtyResident: 77,
+ swapped: 64 + 19,
+ },
+ isLeafNode: false,
+ children: [
+ {
+ title: 'Words',
+ hasRegions: true,
+ sizeInBytes: 16,
+ byteStats: {
+ proportionalResident: 32,
+ swapped: 64
+ },
+ isLeafNode: false,
+ children: [
+ {
+ title: 'A-D',
+ hasRegions: false,
+ isLeafNode: true,
+ regions: []
+ },
+ {
+ title: 'E-H',
+ hasRegions: true,
+ isLeafNode: true,
+ regions: [
+ {
+ mappedFile: 'free'
+ }
+ ]
+ },
+ {
+ title: 'Other',
+ hasRegions: true,
+ sizeInBytes: 16,
+ byteStats: {
+ proportionalResident: 32,
+ swapped: 64
+ },
+ isLeafNode: true,
+ regions: [
+ {
+ mappedFile: 'W2',
+ sizeInBytes: 16,
+ byteStats: {
+ proportionalResident: 32,
+ swapped: 64
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ title: 'Digits',
+ hasRegions: true,
+ byteStats: {
+ proportionalResident: 33,
+ privateDirtyResident: 77,
+ swapped: 19
+ },
+ isLeafNode: true,
+ regions: [
+ {
+ mappedFile: '__42',
+ byteStats: {
+ proportionalResident: 33,
+ privateDirtyResident: 77
+ }
+ },
+ {
+ mappedFile: '__43',
+ byteStats: {
+ swapped: 19
+ }
+ }
+ ]
+ },
+ {
+ title: 'Other',
+ hasRegions: true,
+ sizeInBytes: 123,
+ isLeafNode: true,
+ regions: [
+ {
+ mappedFile: '%invalid%',
+ sizeInBytes: 123
+ }
+ ]
+ }
+ ]
+ });
+ });
+
+ test('vmRegionClassificationNode_fromRegions_addAfterBuild', function() {
+ // Construct the root node from a list of regions. This should *not*
+ // trigger building the full tree (but the total sizeInBytes and byteStats
+ // should be updated accordingly).
+ const rootNode = VMRegionClassificationNode.fromRegions([
+ VMRegion.fromDict({
+ mappedFile: '__42', // Root/Digits.
+ byteStats: {
+ proportionalResident: 33,
+ privateDirtyResident: 77
+ }
+ }),
+ VMRegion.fromDict({
+ mappedFile: '__43', // Root/Digits.
+ byteStats: {
+ swapped: 19
+ }
+ })
+ ], TEST_RULES);
+ checkTree(rootNode, {
+ title: 'Root',
+ hasRegions: true,
+ byteStats: {
+ proportionalResident: 33,
+ privateDirtyResident: 77,
+ swapped: 19
+ },
+ isLeafNode: false,
+ children: CHILDREN_NOT_BUILT_YET
+ });
+
+ // Reading the children of the root node should trigger building the full
+ // tree.
+ checkTree(rootNode, {
+ title: 'Root',
+ hasRegions: true,
+ byteStats: {
+ proportionalResident: 33,
+ privateDirtyResident: 77,
+ swapped: 19
+ },
+ isLeafNode: false,
+ children: [
+ {
+ title: 'Words',
+ hasRegions: false,
+ isLeafNode: false,
+ children: [
+ {
+ title: 'A-D',
+ hasRegions: false,
+ isLeafNode: true,
+ regions: []
+ },
+ {
+ title: 'E-H',
+ hasRegions: false,
+ isLeafNode: true,
+ regions: []
+ }
+ ]
+ },
+ {
+ title: 'Digits',
+ hasRegions: true,
+ byteStats: {
+ proportionalResident: 33,
+ privateDirtyResident: 77,
+ swapped: 19
+ },
+ isLeafNode: true,
+ regions: [
+ {
+ mappedFile: '__42',
+ byteStats: {
+ proportionalResident: 33,
+ privateDirtyResident: 77
+ }
+ },
+ {
+ mappedFile: '__43',
+ byteStats: {
+ swapped: 19
+ }
+ }
+ ]
+ }
+ ]
+ });
+
+ // Add more VM regions *after* the tree has been fully built.
+ rootNode.addRegion(VMRegion.fromDict({
+ mappedFile: 'W2', // Root/Words/Other.
+ sizeInBytes: 16,
+ byteStats: {
+ proportionalResident: 32,
+ swapped: 64
+ }
+ }));
+ checkTree(rootNode, {
+ title: 'Root',
+ hasRegions: true,
+ sizeInBytes: 16,
+ byteStats: {
+ proportionalResident: 32 + 33,
+ privateDirtyResident: 77,
+ swapped: 19 + 64,
+ },
+ isLeafNode: false,
+ children: [
+ {
+ title: 'Words',
+ hasRegions: true,
+ sizeInBytes: 16,
+ byteStats: {
+ proportionalResident: 32,
+ swapped: 64
+ },
+ isLeafNode: false,
+ children: [
+ {
+ title: 'A-D',
+ hasRegions: false,
+ isLeafNode: true,
+ regions: []
+ },
+ {
+ title: 'E-H',
+ hasRegions: false,
+ isLeafNode: true,
+ regions: []
+ },
+ {
+ title: 'Other',
+ hasRegions: true,
+ sizeInBytes: 16,
+ byteStats: {
+ proportionalResident: 32,
+ swapped: 64
+ },
+ isLeafNode: true,
+ regions: [
+ {
+ mappedFile: 'W2',
+ sizeInBytes: 16,
+ byteStats: {
+ proportionalResident: 32,
+ swapped: 64
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ title: 'Digits',
+ hasRegions: true,
+ byteStats: {
+ proportionalResident: 33,
+ privateDirtyResident: 77,
+ swapped: 19
+ },
+ isLeafNode: true,
+ regions: [
+ {
+ mappedFile: '__42',
+ byteStats: {
+ proportionalResident: 33,
+ privateDirtyResident: 77
+ }
+ },
+ {
+ mappedFile: '__43',
+ byteStats: {
+ swapped: 19
+ }
+ }
+ ]
+ }
+ ]
+ });
+ });
+
+ test('vmRegionClassificationNode_fromRegions_addBeforeBuild', function() {
+ // Construct the root node from a list of regions and then add another
+ // region. This should *not* trigger building the full tree (but the total
+ // sizeInBytes and byteStats should be updated accordingly).
+ const rootNode = VMRegionClassificationNode.fromRegions([
+ VMRegion.fromDict({
+ mappedFile: '__42', // Root/Digits.
+ byteStats: {
+ proportionalResident: 33,
+ privateDirtyResident: 77
+ }
+ }),
+ VMRegion.fromDict({
+ mappedFile: '__43', // Root/Digits.
+ byteStats: {
+ swapped: 19
+ }
+ })
+ ], TEST_RULES);
+ rootNode.addRegion(VMRegion.fromDict({
+ mappedFile: '__42', // Root/Digits.
+ startAddress: 2048, // Necessary to distinguish from the first region.
+ sizeInBytes: 1000,
+ byteStats: {
+ privateDirtyResident: 500
+ }
+ }));
+ checkTree(rootNode, {
+ title: 'Root',
+ hasRegions: true,
+ sizeInBytes: 1000,
+ byteStats: {
+ proportionalResident: 33,
+ privateDirtyResident: 77 + 500,
+ swapped: 19
+ },
+ isLeafNode: false,
+ children: CHILDREN_NOT_BUILT_YET
+ });
+
+ // Reading the children of the root node should trigger building the full
+ // tree.
+ checkTree(rootNode, {
+ title: 'Root',
+ hasRegions: true,
+ sizeInBytes: 1000,
+ byteStats: {
+ proportionalResident: 33,
+ privateDirtyResident: 77 + 500,
+ swapped: 19
+ },
+ isLeafNode: false,
+ children: [
+ {
+ title: 'Words',
+ hasRegions: false,
+ isLeafNode: false,
+ children: [
+ {
+ title: 'A-D',
+ hasRegions: false,
+ isLeafNode: true,
+ regions: []
+ },
+ {
+ title: 'E-H',
+ hasRegions: false,
+ isLeafNode: true,
+ regions: []
+ }
+ ]
+ },
+ {
+ title: 'Digits',
+ hasRegions: true,
+ sizeInBytes: 1000,
+ byteStats: {
+ proportionalResident: 33,
+ privateDirtyResident: 77 + 500,
+ swapped: 19
+ },
+ isLeafNode: true,
+ regions: [
+ {
+ mappedFile: '__42',
+ byteStats: {
+ proportionalResident: 33,
+ privateDirtyResident: 77
+ }
+ },
+ {
+ mappedFile: '__43',
+ byteStats: {
+ swapped: 19
+ }
+ },
+ {
+ mappedFile: '__42',
+ startAddress: 2048,
+ sizeInBytes: 1000,
+ byteStats: {
+ privateDirtyResident: 500
+ }
+ }
+ ]
+ }
+ ]
+ });
+
+ // Add more VM regions *after* the tree has been fully built.
+ rootNode.addRegion(VMRegion.fromDict({
+ mappedFile: 'W2', // Root/Words/Other.
+ sizeInBytes: 16,
+ byteStats: {
+ proportionalResident: 32,
+ swapped: 64
+ }
+ }));
+ checkTree(rootNode, {
+ title: 'Root',
+ hasRegions: true,
+ sizeInBytes: 1000 + 16,
+ byteStats: {
+ proportionalResident: 32 + 33,
+ privateDirtyResident: 500 + 77,
+ swapped: 19 + 64,
+ },
+ isLeafNode: false,
+ children: [
+ {
+ title: 'Words',
+ hasRegions: true,
+ sizeInBytes: 16,
+ byteStats: {
+ proportionalResident: 32,
+ swapped: 64
+ },
+ isLeafNode: false,
+ children: [
+ {
+ title: 'A-D',
+ hasRegions: false,
+ isLeafNode: true,
+ regions: []
+ },
+ {
+ title: 'E-H',
+ hasRegions: false,
+ isLeafNode: true,
+ regions: []
+ },
+ {
+ title: 'Other',
+ hasRegions: true,
+ sizeInBytes: 16,
+ byteStats: {
+ proportionalResident: 32,
+ swapped: 64
+ },
+ isLeafNode: true,
+ regions: [
+ {
+ mappedFile: 'W2',
+ sizeInBytes: 16,
+ byteStats: {
+ proportionalResident: 32,
+ swapped: 64
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ title: 'Digits',
+ hasRegions: true,
+ sizeInBytes: 1000,
+ byteStats: {
+ proportionalResident: 33,
+ privateDirtyResident: 500 + 77,
+ swapped: 19
+ },
+ isLeafNode: true,
+ regions: [
+ {
+ mappedFile: '__42',
+ byteStats: {
+ proportionalResident: 33,
+ privateDirtyResident: 77
+ }
+ },
+ {
+ mappedFile: '__43',
+ byteStats: {
+ swapped: 19
+ }
+ },
+ {
+ mappedFile: '__42',
+ startAddress: 2048,
+ sizeInBytes: 1000,
+ byteStats: {
+ privateDirtyResident: 500
+ }
+ }
+ ]
+ }
+ ]
+ });
+ });
+
+ test('vmRegionClassificationNode_someRegion', function() {
+ const rootNode = new VMRegionClassificationNode(TEST_RULES);
+
+ // There are no regions in the tree, so the method should always return
+ // false.
+ assert.isFalse(rootNode.someRegion(function(region) {
+ throw new Error('There are no regions in the tree!!!');
+ }));
+
+ rootNode.addRegion(VMRegion.fromDict({
+ mappedFile: 'W2', // Root/Words/Other.
+ sizeInBytes: 16,
+ byteStats: {
+ proportionalResident: 32,
+ swapped: 64
+ }
+ }));
+ rootNode.addRegion(VMRegion.fromDict({
+ mappedFile: '__42', // Root/Digits.
+ byteStats: {
+ proportionalResident: 33,
+ privateDirtyResident: 77
+ }
+ }));
+ rootNode.addRegion(VMRegion.fromDict({
+ mappedFile: '__43', // Root/Digits.
+ byteStats: {
+ proportionalResident: 33,
+ privateDirtyResident: 77
+ }
+ }));
+
+ function checkSomeRegion() {
+ // Find the order in which the regions are traversed and checked that all
+ // regions were visited.
+ const visitedRegionMappedFiles = [];
+ assert.isFalse(rootNode.someRegion(function(region) {
+ visitedRegionMappedFiles.push(region.mappedFile);
+ return false;
+ }));
+ assert.lengthOf(visitedRegionMappedFiles, 3);
+ assert.sameMembers(visitedRegionMappedFiles, ['W2', '__42', '__43']);
+
+ // Assuming the traversal order is deterministic, we check that once the
+ // callback returns true, no further regions are visited.
+ visitedRegionMappedFiles.forEach(
+ function(mappedFileToMatch, index) {
+ const visitedRegionMappedFiles2 = [];
+ assert.isTrue(rootNode.someRegion(function(region) {
+ this.files.push(region.mappedFile);
+ return region.mappedFile === mappedFileToMatch;
+ }, { files: visitedRegionMappedFiles2 } /* opt_this */));
+ assert.deepEqual(visitedRegionMappedFiles2,
+ visitedRegionMappedFiles.slice(0, index + 1));
+ });
+ }
+
+ // Before lazy construction (single node with a flat list of regions).
+ checkSomeRegion();
+ assert.isUndefined(rootNode.children_);
+
+ // After lazy construction (tree of nodes with lists of regions).
+ assert.isDefined(rootNode.children); // Force building the tree.
+ assert.isDefined(rootNode.children_);
+ checkSomeRegion();
+ });
+
+ test('vmRegionClassificationNode_libraryMemory', function() {
+ const regions = [
+ VMRegion.fromDict({
+ sizeInBytes: 20,
+ protectionFlags: VMRegion.PROTECTION_FLAG_READ,
+ mappedFile: '[stack:1234]',
+ byteStats: {
+ privateDirtyResident: 100,
+ proportionalResident: 124
+ }
+ }),
+ VMRegion.fromDict({
+ sizeInBytes: 500000,
+ protectionFlags: VMRegion.PROTECTION_FLAG_READ,
+ mappedFile: '/data/app/com.google.chrome/base.apk',
+ byteStats: {
+ privateCleanResident: 100000,
+ proportionalResident: 124000
+ }
+ }),
+ VMRegion.fromDict({
+ sizeInBytes: 1000,
+ protectionFlags: (VMRegion.PROTECTION_FLAG_READ |
+ VMRegion.PROTECTION_FLAG_EXECUTE),
+ mappedFile: '/data/app/com.google.chrome/base.apk',
+ byteStats: {
+ privateCleanResident: 100,
+ proportionalResident: 124
+ }
+ }),
+ VMRegion.fromDict({
+ sizeInBytes: 300,
+ protectionFlags: (VMRegion.PROTECTION_FLAG_READ |
+ VMRegion.PROTECTION_FLAG_EXECUTE),
+ mappedFile: '/data/app/com.google.chrome/base.apk',
+ byteStats: {
+ proportionalResident: 58,
+ }
+ }),
+ VMRegion.fromDict({
+ sizeInBytes: 400,
+ protectionFlags: (VMRegion.PROTECTION_FLAG_READ |
+ VMRegion.PROTECTION_FLAG_EXECUTE),
+ mappedFile: '/data/app/com.google.chrome/base.apk',
+ byteStats: {
+ privateCleanResident: 76,
+ }
+ }),
+ VMRegion.fromDict({
+ sizeInBytes: 50,
+ protectionFlags: (VMRegion.PROTECTION_FLAG_READ |
+ VMRegion.PROTECTION_FLAG_EXECUTE),
+ mappedFile: '/data/app/com.google.chrome/base.apk',
+ byteStats: {
+ privateCleanResident: 8,
+ proportionalResident: 24,
+ sharedCleanResident: 10,
+ }
+ })];
+ const node = VMRegionClassificationNode.fromRegions(regions);
+ assert.strictEqual(node.sizeInBytes, 20 + 500000 + 1000 + 300 + 400 + 50);
+ assert.strictEqual(node.nativeLibrarySizeInBytes,
+ 1000 + 300 + 400 + 50);
+
+ assert.deepEqual(node.byteStats, {
+ proportionalResident: 124 + 124000 + 124 + 58 + 24,
+ privateDirtyResident: 100,
+ privateCleanResident: 100000 + 100 + 76 + 8,
+ sharedCleanResident: 10,
+ nativeLibraryPrivateCleanResident: 100 + 76 + 8,
+ nativeLibrarySharedCleanResident: 10,
+ nativeLibraryProportionalResident: 124 + 58 + 24,
+ });
+ });
+
+ test('vmRegionClassificationNode_javaBaseMemory', function() {
+ const regions = [
+ VMRegion.fromDict({
+ sizeInBytes: 20,
+ protectionFlags: VMRegion.PROTECTION_FLAG_READ,
+ mappedFile: '[stack:1234]',
+ byteStats: {
+ privateDirtyResident: 100,
+ proportionalResident: 124
+ }
+ }),
+ VMRegion.fromDict({
+ sizeInBytes: 500000,
+ protectionFlags: VMRegion.PROTECTION_FLAG_READ,
+ mappedFile: '/data/app/com.google.chrome/oat/arm/base.vdex',
+ byteStats: {
+ privateCleanResident: 100000,
+ proportionalResident: 124000
+ }
+ }),
+ VMRegion.fromDict({
+ sizeInBytes: 1000,
+ protectionFlags: (VMRegion.PROTECTION_FLAG_READ |
+ VMRegion.PROTECTION_FLAG_EXECUTE),
+ mappedFile: '/data/app/com.google.chrome/some/other.odex',
+ byteStats: {
+ privateCleanResident: 100,
+ proportionalResident: 124
+ }
+ }),
+ VMRegion.fromDict({
+ sizeInBytes: 300,
+ protectionFlags: (VMRegion.PROTECTION_FLAG_READ),
+ mappedFile: '/data/app/com.google.chrome/oat/arm/base.odex',
+ byteStats: {
+ proportionalResident: 58,
+ }
+ }),
+ VMRegion.fromDict({
+ sizeInBytes: 400,
+ protectionFlags: (VMRegion.PROTECTION_FLAG_READ |
+ VMRegion.PROTECTION_FLAG_EXECUTE),
+ mappedFile: '/data/app/com.google.chrome/base.vdex',
+ byteStats: {
+ privateCleanResident: 76,
+ privateDirtyResident: 17,
+ }
+ }),
+ VMRegion.fromDict({
+ sizeInBytes: 50,
+ protectionFlags: (VMRegion.PROTECTION_FLAG_READ |
+ VMRegion.PROTECTION_FLAG_EXECUTE),
+ mappedFile: '/data/app/com.google.chrome/another/path/base.odex',
+ byteStats: {
+ privateCleanResident: 8,
+ proportionalResident: 24,
+ sharedCleanResident: 10,
+ }
+ })];
+ const node = VMRegionClassificationNode.fromRegions(regions);
+ assert.strictEqual(node.sizeInBytes, 20 + 500000 + 1000 + 300 + 400 + 50);
+
+ assert.deepEqual(node.byteStats, {
+ proportionalResident: 124 + 124000 + 124 + 58 + 24,
+ privateDirtyResident: 100 + 17,
+ privateCleanResident: 100000 + 100 + 76 + 8,
+ sharedCleanResident: 10,
+ javaBasePss: 124000 + 58 + 24,
+ javaBaseCleanResident: 100000 + 76 + 8 + 10,
+ });
+ });
+
+ test('classificationRules', function() {
+ checkClassificationRules('/dev/ashmem/dalvik-main space (deleted)',
+ ['Android', 'Java runtime', 'Spaces', 'Normal']);
+ checkClassificationRules('/dev/ashmem/dalvik-non moving space',
+ ['Android', 'Java runtime', 'Spaces', 'Non-moving']);
+ checkClassificationRules('/dev/ashmem/dalvik-zygote space (deleted)',
+ ['Android', 'Java runtime', 'Spaces', 'Zygote']);
+ checkClassificationRules('/dev/ashmem/dalvik-allocation stack (deleted)',
+ ['Android', 'Java runtime', 'Accounting']);
+ checkClassificationRules(
+ '/dev/ashmem/dalvik-allocspace main rosalloc space 1 live-bitmap 2',
+ ['Android', 'Java runtime', 'Accounting']);
+ checkClassificationRules(
+ '/dev/ashmem/dalvik-allocspace non moving space live-bitmap 4',
+ ['Android', 'Java runtime', 'Accounting']);
+ checkClassificationRules('/dev/ashmem/dalvik-allocspace zygote / ' +
+ 'non moving space live-bitmap 0 (deleted)',
+ ['Android', 'Java runtime', 'Accounting']);
+ checkClassificationRules('/dev/ashmem/dalvik-card table (deleted)',
+ ['Android', 'Java runtime', 'Accounting']);
+ checkClassificationRules('/dev/ashmem/dalvik-large live objects (deleted)',
+ ['Android', 'Java runtime', 'Accounting']);
+ checkClassificationRules('/dev/ashmem/dalvik-live stack (deleted)',
+ ['Android', 'Java runtime', 'Accounting']);
+ checkClassificationRules(
+ '/dev/ashmem/dalvik-mark sweep sweep array free buffer (deleted)',
+ ['Android', 'Java runtime', 'Accounting']);
+ checkClassificationRules('/dev/ashmem/dalvik-rosalloc page map (deleted)',
+ ['Android', 'Java runtime', 'Accounting']);
+ checkClassificationRules('/dev/ashmem/dalvik-indirect ref table (deleted)',
+ ['Android', 'Java runtime', 'Indirect Reference Table']);
+ checkClassificationRules('/dev/ashmem/dalvik-LinearAlloc (deleted)',
+ ['Android', 'Java runtime', 'Linear Alloc']);
+ checkClassificationRules('/dev/ashmem/dalvik-jit-code-cache (deleted)',
+ ['Android', 'Java runtime', 'Cache']);
+ checkClassificationRules('/dev/ashmem/CursorWindow (deleted)',
+ ['Android', 'Cursor']);
+ checkClassificationRules('/dev/ashmem (deleted)', ['Android', 'Ashmem']);
+ checkClassificationRules('/dev/ashmem/GFXStats-10082',
+ ['Android', 'Ashmem']);
+
+ checkClassificationRules('[stack:23164]', ['Stack']);
+ checkClassificationRules('[stack]', ['Stack']);
+
+ checkClassificationRules('[discounted tracing overhead]', ['Native heap']);
+ checkClassificationRules('', ['Native heap']);
+ checkClassificationRules('[heap]', ['Native heap']);
+ checkClassificationRules('[anon:libc_malloc]', ['Native heap']);
+ checkClassificationRules('[anon:thread signal stack]', ['Native heap']);
+ checkClassificationRules('/dev/ashmem/libc malloc (deleted)',
+ ['Native heap']);
+
+ checkClassificationRules('/usr/lib/nvidia-340/libGL.so.331.79',
+ ['Files', 'so']);
+ checkClassificationRules('/usr/lib/x86_64-linux-gnu/libibus-1.0.so.5.0.505',
+ ['Files', 'so']);
+ checkClassificationRules('/data/data/com.google.android.apps.chrome/' +
+ 'app_chrome/RELRO:libchrome.so (deleted)', ['Files', 'so']);
+ checkClassificationRules(
+ '/usr/share/fonts/truetype/msttcorefonts/Times_New_Roman.ttf',
+ ['Files', 'ttf']);
+ checkClassificationRules(
+ '/data/app/com.google.android.apps.chrome-2/base.apk',
+ ['Files', 'apk']);
+ checkClassificationRules(
+ '/data/app/com.google.android.apps.chrome-2/lib/arm/libchrome.so',
+ ['Files', 'so']);
+ checkClassificationRules(
+ '/data/app/com.google.android.apps.chrome-2/oat/arm/base.odex',
+ ['Files', 'dex']);
+ checkClassificationRules(
+ '/data/dalvik-cache/arm/system@framework@boot.art', ['Files', 'art']);
+ checkClassificationRules(
+ '/data/dalvik-cache/arm/system@framework@boot.oat', ['Files', 'oat']);
+
+ checkClassificationRules('/dev/nvidia0', ['Devices', 'GPU']);
+ checkClassificationRules('/dev/kgsl-3d0', ['Devices', 'GPU']);
+ checkClassificationRules('anon_inode:dmabuf', ['Devices', 'DMA']);
+ checkClassificationRules('/dev/binder', ['Devices', 'Other']);
+
+ checkClassificationRules('/src/out/Release/chrome', ['Other']);
+ checkClassificationRules('/tmp/gluY4SVp (deleted)', ['Other']);
+ checkClassificationRules('/src/out/Release/resources.pak', ['Other']);
+ checkClassificationRules('[vdso]', ['Other']);
+ checkClassificationRules('[vsyscall]', ['Other']);
+ checkClassificationRules('[vectors]', ['Other']);
+ checkClassificationRules('[vvar]', ['Other']);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/model/x_marker_annotation.html b/chromium/third_party/catapult/tracing/tracing/model/x_marker_annotation.html
new file mode 100644
index 00000000000..7eb79b1d78e
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/model/x_marker_annotation.html
@@ -0,0 +1,50 @@
+<!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/annotation.html">
+<link rel="import" href="/tracing/ui/annotations/x_marker_annotation_view.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.model', function() {
+ function XMarkerAnnotation(timestamp) {
+ tr.model.Annotation.apply(this, arguments);
+
+ this.timestamp = timestamp;
+ this.strokeStyle = 'rgba(0, 0, 255, 0.5)';
+ }
+
+ XMarkerAnnotation.fromDict = function(dict) {
+ return new XMarkerAnnotation(dict.args.timestamp);
+ };
+
+ XMarkerAnnotation.prototype = {
+ __proto__: tr.model.Annotation.prototype,
+
+ toDict() {
+ return {
+ typeName: 'xmarker',
+ args: {
+ timestamp: this.timestamp
+ }
+ };
+ },
+
+ createView_(viewport) {
+ return new tr.ui.annotations.XMarkerAnnotationView(viewport, this);
+ }
+ };
+
+ tr.model.Annotation.register(
+ XMarkerAnnotation, {typeName: 'xmarker'});
+
+ return {
+ XMarkerAnnotation,
+ };
+});
+</script>