summaryrefslogtreecommitdiff
path: root/chromium/third_party/catapult/tracing/tracing/metrics
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/third_party/catapult/tracing/tracing/metrics')
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/__init__.py15
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/accessibility_metric.html83
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/accessibility_metric_test.html74
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/all_fixed_color_schemes.html10
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/all_metrics.html39
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/android_startup_metric.html153
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/android_startup_metric_test.html191
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/android_systrace_metric.html224
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/android_systrace_metric_test.html146
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/blink/gc_metric.html265
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/blink/gc_metric_test.html347
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/blink/leak_detection_metric.html86
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/blink/leak_detection_metric_test.html179
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/buildbot_output_for_compare_samples_test.txt187
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/compare_samples.py54
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/compare_samples_cmdline.html225
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/compare_samples_unittest.py336
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/console_error_metric.html72
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/console_error_metric_unittest.html100
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/cpu_process_metric.html92
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/cpu_process_metric_test.html119
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/discover.py34
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/discover_cmdline.html32
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/discover_unittest.py20
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/media_metric.html395
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/media_metric_test.html423
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/metric_map_function.html216
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/metric_map_function_test.html214
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/metric_registry.html116
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/metric_registry_test.html93
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/metric_runner.py75
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/rendering/cpu_utilization.html195
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/rendering/cpu_utilization_test.html280
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/rendering/frame_time.html272
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/rendering/frame_time_test.html301
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/rendering/latency.html138
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/rendering/latency_test.html119
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/rendering/pipeline.html258
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/rendering/pipeline_test.html366
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/rendering/pixels.html105
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/rendering/pixels_test.html64
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/rendering/queueing_duration.html88
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/rendering/queueing_duration_test.html54
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/rendering/rendering_metric.html52
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/rendering/rendering_metric_test.html62
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/sample_exception_metric.html46
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/sample_metric.html45
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/spa_navigation_helper.html248
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/spa_navigation_helper_test.html258
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/spa_navigation_metric.html103
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/spa_navigation_metric_test.html234
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/system_health/breakdown_tree_helpers.html275
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/system_health/breakdown_tree_helpers_test.html337
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/system_health/clock_sync_latency_metric.html54
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/system_health/clock_sync_latency_metric_test.html65
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/system_health/cpu_time_metric.html101
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/system_health/cpu_time_metric_test.html163
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/system_health/cpu_time_tree_data_reporter.html182
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/system_health/cpu_time_tree_data_reporter_test.html155
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/system_health/expected_queueing_time_metric.html462
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/system_health/expected_queueing_time_metric_test.html409
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/system_health/limited_cpu_time_metric.html62
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/system_health/loading_metric.html623
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/system_health/loading_metric_test.html1142
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/system_health/long_tasks_metric.html137
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/system_health/long_tasks_metric_test.html111
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/system_health/memory_metric.html1332
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/system_health/memory_metric_test.html4249
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/system_health/new_cpu_time_metric.html79
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/system_health/new_cpu_time_metric_test.html186
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/system_health/power_metric.html348
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/system_health/power_metric_test.html742
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/system_health/responsiveness_metric.html162
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/system_health/responsiveness_metric_test.html68
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/system_health/utils.html152
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/system_health/webview_startup_metric.html64
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/system_health/webview_startup_metric_test.html50
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/tabs_metric.html89
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/tabs_metric_test.html122
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/tracing_metric.html230
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/tracing_metric_test.html168
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/v8/execution_metric.html228
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/v8/execution_metric_test.html71
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/v8/gc_metric.html297
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/v8/gc_metric_test.html210
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/v8/runtime_stats_metric.html374
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/v8/runtime_stats_metric_test.html718
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/v8/utils.html490
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/v8/utils_test.html189
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/v8/v8_metrics.html30
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/vr/frame_cycle_duration_metric.html261
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/vr/frame_cycle_duration_metric_test.html333
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/vr/webvr_metric.html117
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/vr/webvr_metric_test.html86
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/webrtc/webrtc_rendering_metric.html364
-rw-r--r--chromium/third_party/catapult/tracing/tracing/metrics/webrtc/webrtc_rendering_metric_test.html456
96 files changed, 24446 insertions, 0 deletions
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/__init__.py b/chromium/third_party/catapult/tracing/tracing/metrics/__init__.py
new file mode 100644
index 00000000000..cffcee63c42
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/__init__.py
@@ -0,0 +1,15 @@
+# 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.
+
+import os
+import sys
+
+
+_CATAPULT_DIR = os.path.abspath(os.path.join(
+ os.path.dirname(os.path.abspath(__file__)), '..', '..', '..'))
+
+_PI_PATH = os.path.join(_CATAPULT_DIR, 'perf_insights')
+
+if _PI_PATH not in sys.path:
+ sys.path.insert(1, _PI_PATH)
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/accessibility_metric.html b/chromium/third_party/catapult/tracing/tracing/metrics/accessibility_metric.html
new file mode 100644
index 00000000000..43c6b393dbe
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/accessibility_metric.html
@@ -0,0 +1,83 @@
+<!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/metrics/metric_registry.html">
+<link rel="import" href="/tracing/model/helpers/chrome_model_helper.html">
+<link rel="import" href="/tracing/value/histogram.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.metrics', function() {
+ function accessibilityMetric(histograms, model) {
+ const browserAccessibilityEventsHist = new tr.v.Histogram(
+ 'browser_accessibility_events',
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter);
+ browserAccessibilityEventsHist.description =
+ 'Browser accessibility events time';
+
+ const renderAccessibilityEventsHist = new tr.v.Histogram(
+ 'render_accessibility_events',
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter);
+ renderAccessibilityEventsHist.description =
+ 'Render accessibility events time';
+
+ const renderAccessibilityLocationsHist = new tr.v.Histogram(
+ 'render_accessibility_locations',
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter);
+ renderAccessibilityLocationsHist.description =
+ 'Render accessibility locations time';
+
+ const chromeHelper = model.getOrCreateHelper(
+ tr.model.helpers.ChromeModelHelper);
+ if (chromeHelper === undefined) return;
+
+ for (const rendererHelper of Object.values(chromeHelper.rendererHelpers)) {
+ const mainThread = rendererHelper.mainThread;
+ if (mainThread === undefined) continue;
+
+ for (const slice of mainThread.getDescendantEvents()) {
+ if (!(slice instanceof tr.model.ThreadSlice)) continue;
+
+ if (slice.title ===
+ 'RenderAccessibilityImpl::SendPendingAccessibilityEvents') {
+ renderAccessibilityEventsHist.addSample(slice.duration,
+ {event: new tr.v.d.RelatedEventSet(slice)});
+ }
+ if (slice.title ===
+ 'RenderAccessibilityImpl::SendLocationChanges') {
+ renderAccessibilityLocationsHist.addSample(slice.duration,
+ {event: new tr.v.d.RelatedEventSet(slice)});
+ }
+ }
+ }
+
+ for (const browserHelper of Object.values(chromeHelper.browserHelpers)) {
+ const mainThread = browserHelper.mainThread;
+ if (mainThread === undefined) continue;
+
+ for (const slice of mainThread.getDescendantEvents()) {
+ if (slice.title ===
+ 'BrowserAccessibilityManager::OnAccessibilityEvents') {
+ browserAccessibilityEventsHist.addSample(slice.duration,
+ {event: new tr.v.d.RelatedEventSet(slice)});
+ }
+ }
+ }
+
+ histograms.addHistogram(browserAccessibilityEventsHist);
+ histograms.addHistogram(renderAccessibilityEventsHist);
+ histograms.addHistogram(renderAccessibilityLocationsHist);
+ }
+
+ tr.metrics.MetricRegistry.register(accessibilityMetric);
+
+ return {
+ accessibilityMetric,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/accessibility_metric_test.html b/chromium/third_party/catapult/tracing/tracing/metrics/accessibility_metric_test.html
new file mode 100644
index 00000000000..27dbf178637
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/accessibility_metric_test.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/core/test_utils.html">
+<link rel="import" href="/tracing/metrics/accessibility_metric.html">
+<link rel="import" href="/tracing/value/histogram_set.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ function makeTestModel() {
+ return tr.c.TestUtils.newModel(function(model) {
+ const browserProcess = model.getOrCreateProcess(99);
+ browserProcess.name = 'Browser';
+ const browserThread = browserProcess.getOrCreateThread(2);
+ browserThread.name = 'CrBrowserMain';
+ browserThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'accessibility',
+ title: 'BrowserAccessibilityManager::OnAccessibilityEvents',
+ start: 1000,
+ dur: 71,
+ end: 1071,
+ }));
+ browserThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'accessibility',
+ title: 'BrowserAccessibilityManager::OnAccessibilityEvents',
+ start: 1500,
+ dur: 22,
+ end: 1522,
+ }));
+
+ const renderThread = model.getOrCreateProcess(1).getOrCreateThread(2);
+ renderThread.name = 'CrRendererMain';
+ renderThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'accessibility',
+ title: 'RenderAccessibilityImpl::SendPendingAccessibilityEvents',
+ start: 800,
+ dur: 228,
+ end: 1028,
+ }));
+ renderThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'accessibility',
+ title: 'RenderAccessibilityImpl::SendLocationChanges',
+ start: 900,
+ dur: 12,
+ end: 912,
+ }));
+ renderThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'accessibility',
+ title: 'RenderAccessibilityImpl::SendPendingAccessibilityEvents',
+ start: 1400,
+ dur: 188,
+ end: 1588,
+ }));
+ });
+ }
+
+ test('accessibilityMetric', function() {
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.accessibilityMetric(histograms, makeTestModel());
+ assert.closeTo(93, histograms.getHistogramNamed(
+ 'browser_accessibility_events').sum, 1e-2);
+ assert.closeTo(416, histograms.getHistogramNamed(
+ 'render_accessibility_events').sum, 1e-2);
+ assert.closeTo(12, histograms.getHistogramNamed(
+ 'render_accessibility_locations').sum, 1e-2);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/all_fixed_color_schemes.html b/chromium/third_party/catapult/tracing/tracing/metrics/all_fixed_color_schemes.html
new file mode 100644
index 00000000000..be843ee9aca
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/all_fixed_color_schemes.html
@@ -0,0 +1,10 @@
+<!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.
+-->
+
+<!-- We import all files that register fixed color schemes which are used by
+ metrics so that metrics code can depend on a single place. -->
+<link rel="import" href="/tracing/extras/chrome/chrome_processes.html">
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/all_metrics.html b/chromium/third_party/catapult/tracing/tracing/metrics/all_metrics.html
new file mode 100644
index 00000000000..68c1b2f34ca
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/all_metrics.html
@@ -0,0 +1,39 @@
+<!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/metrics/accessibility_metric.html">
+<link rel="import" href="/tracing/metrics/android_startup_metric.html">
+<link rel="import" href="/tracing/metrics/android_systrace_metric.html">
+<link rel="import" href="/tracing/metrics/blink/gc_metric.html">
+<link rel="import" href="/tracing/metrics/blink/leak_detection_metric.html">
+<link rel="import" href="/tracing/metrics/console_error_metric.html">
+<link rel="import" href="/tracing/metrics/cpu_process_metric.html">
+<link rel="import" href="/tracing/metrics/media_metric.html">
+<link rel="import" href="/tracing/metrics/rendering/rendering_metric.html">
+<link rel="import" href="/tracing/metrics/sample_exception_metric.html">
+<link rel="import" href="/tracing/metrics/sample_metric.html">
+<link rel="import" href="/tracing/metrics/spa_navigation_metric.html">
+<link rel="import" href="/tracing/metrics/system_health/clock_sync_latency_metric.html">
+<link rel="import" href="/tracing/metrics/system_health/cpu_time_metric.html">
+<link rel="import" href="/tracing/metrics/system_health/expected_queueing_time_metric.html">
+<link rel="import" href="/tracing/metrics/system_health/limited_cpu_time_metric.html">
+<link rel="import" href="/tracing/metrics/system_health/loading_metric.html">
+<link rel="import" href="/tracing/metrics/system_health/long_tasks_metric.html">
+<link rel="import" href="/tracing/metrics/system_health/memory_metric.html">
+<link rel="import" href="/tracing/metrics/system_health/new_cpu_time_metric.html">
+<link rel="import" href="/tracing/metrics/system_health/power_metric.html">
+<link rel="import" href="/tracing/metrics/system_health/responsiveness_metric.html">
+<link rel="import" href="/tracing/metrics/system_health/webview_startup_metric.html">
+<link rel="import" href="/tracing/metrics/tabs_metric.html">
+<link rel="import" href="/tracing/metrics/tracing_metric.html">
+<link rel="import" href="/tracing/metrics/v8/execution_metric.html">
+<link rel="import" href="/tracing/metrics/v8/gc_metric.html">
+<link rel="import" href="/tracing/metrics/v8/runtime_stats_metric.html">
+<link rel="import" href="/tracing/metrics/v8/v8_metrics.html">
+<link rel="import" href="/tracing/metrics/vr/frame_cycle_duration_metric.html">
+<link rel="import" href="/tracing/metrics/vr/webvr_metric.html">
+<link rel="import" href="/tracing/metrics/webrtc/webrtc_rendering_metric.html">
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/android_startup_metric.html b/chromium/third_party/catapult/tracing/tracing/metrics/android_startup_metric.html
new file mode 100644
index 00000000000..4b6bc49ee68
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/android_startup_metric.html
@@ -0,0 +1,153 @@
+<!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/metrics/metric_registry.html">
+<link rel="import" href="/tracing/value/histogram.html">
+
+<script>
+'use strict';
+
+// The |androidStartupMetric| produces metrics that start counting at the
+// earliest moment the Chrome code on Android is executed.
+// A few histograms are produced with the names as described below:
+// 1. messageloop_start_time - time till the message loop of the browser main
+// starts processing posted tasts (after having loaded the Profile)
+// 2. first_contentful_paint_time - time to the first contentful paint of the
+// page loaded at startup
+// The metric also supports multiple browser restarts, in this case multiple
+// samples would be added to the histograms above.
+tr.exportTo('tr.metrics.sh', function() {
+ const MESSAGE_LOOP_EVENT_NAME =
+ 'Startup.BrowserMessageLoopStartTimeFromMainEntry3';
+ const FIRST_CONTENTFUL_PAINT_EVENT_NAME = 'firstContentfulPaint';
+ function androidStartupMetric(histograms, model) {
+ // Walk the browser slices, extract timestamps for the browser start,
+ // message loop start. TODO(crbug.com/883290): re-introduce
+ // request_start_time.
+ let messageLoopStartEvents = [];
+ const chromeHelper =
+ model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);
+ if (!chromeHelper) return;
+ for (const helper of chromeHelper.browserHelpers) {
+ for (const ev of helper.mainThread.asyncSliceGroup.childEvents()) {
+ if (ev.title === MESSAGE_LOOP_EVENT_NAME) {
+ messageLoopStartEvents.push(ev);
+ }
+ }
+ }
+
+ // Walk the renderer slices and extract the 'first contentful paint'
+ // histogram samples.
+ let firstContentfulPaintEvents = [];
+ const rendererHelpers = chromeHelper.rendererHelpers;
+ const pids = Object.keys(rendererHelpers);
+ for (const rendererHelper of Object.values(chromeHelper.rendererHelpers)) {
+ if (!rendererHelper.mainThread) continue;
+ for (const ev of rendererHelper.mainThread.sliceGroup.childEvents()) {
+ if (ev.title === FIRST_CONTENTFUL_PAINT_EVENT_NAME) {
+ firstContentfulPaintEvents.push(ev);
+ // There are usually several 'First Contentful Paint' events recorded
+ // for each page load. Take only the first one per renderer.
+ break;
+ }
+ }
+ }
+
+ // Fallback to scanning all processes if important events are not found.
+ let totalBrowserStarts = messageLoopStartEvents.length;
+ let totalFcpEvents = firstContentfulPaintEvents.length;
+ if (totalFcpEvents !== totalBrowserStarts || totalBrowserStarts === 0) {
+ messageLoopStartEvents = [];
+ firstContentfulPaintEvents = [];
+ // Sometimes either the browser process or the renderer process does not
+ // have the proper name attached. This often happens when both chrome
+ // trace and systrace are merged. Other multi-process trickery, like
+ // Perfetto, may also cause this.
+ for (const proc of Object.values(model.processes)) {
+ for (const ev of proc.getDescendantEvents()) {
+ if (ev.title === MESSAGE_LOOP_EVENT_NAME) {
+ messageLoopStartEvents.push(ev);
+ }
+ }
+ for (const ev of proc.getDescendantEvents()) {
+ if (ev.title === FIRST_CONTENTFUL_PAINT_EVENT_NAME) {
+ firstContentfulPaintEvents.push(ev);
+ break;
+ }
+ }
+ }
+ totalBrowserStarts = messageLoopStartEvents.length;
+ totalFcpEvents = firstContentfulPaintEvents.length;
+ }
+
+ // Sometimes a number of early trace events are not recorded because tracing
+ // takes time to start. This leads to having more FCP events than
+ // messageloop_start events. As a workaround ignore the FCP events for which
+ // there are no browser starts.
+ function orderEvents(event1, event2) {
+ return event1.start - event2.start;
+ }
+ messageLoopStartEvents.sort(orderEvents);
+ firstContentfulPaintEvents.sort(orderEvents);
+
+ if (totalFcpEvents < totalBrowserStarts) {
+ throw new Error('Found less FCP events (' + totalFcpEvents +
+ ') than browser starts (' + totalBrowserStarts + ')');
+ }
+
+ // Group the relevant events with the corresponding browser starts and emit
+ // the metrics.
+ const messageLoopStartHistogram = histograms.createHistogram(
+ 'messageloop_start_time',
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter, []);
+ const firstContentfulPaintHistogram = histograms.createHistogram(
+ 'first_contentful_paint_time',
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter, []);
+ // The earliest browser start is skipped because it is affected by the state
+ // of the system coming from the time before the benchmark started. Removing
+ // these influencing factors allows reducing measurement noise.
+ // Note: Two early starts are ignored below, the reasons for spurious
+ // slowdowns of the 2nd run are not known yet, see http://crbug.com/891797.
+ let fcpIndex = 0;
+ for (let loopStartIndex = 0; loopStartIndex < totalBrowserStarts;) {
+ const startEvent = messageLoopStartEvents[loopStartIndex];
+ if (fcpIndex === totalFcpEvents) {
+ break;
+ }
+
+ // Skip all FCP events that appear before the next browser start.
+ const fcpEvent = firstContentfulPaintEvents[fcpIndex];
+ if (fcpEvent.start < startEvent.start) {
+ fcpIndex++;
+ continue;
+ }
+
+ // The pair of matching events is found.
+ loopStartIndex++;
+
+ // Skip the two initial FCP events and (potentially missing) browser
+ // starts.
+ if (fcpIndex < 2) {
+ continue;
+ }
+
+ // Record the histograms.
+ messageLoopStartHistogram.addSample(startEvent.duration,
+ {events: new tr.v.d.RelatedEventSet([startEvent])});
+ firstContentfulPaintHistogram.addSample(
+ fcpEvent.end - startEvent.start,
+ {events: new tr.v.d.RelatedEventSet([startEvent, fcpEvent])});
+ }
+ }
+
+ tr.metrics.MetricRegistry.register(androidStartupMetric);
+
+ return {
+ androidStartupMetric,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/android_startup_metric_test.html b/chromium/third_party/catapult/tracing/tracing/metrics/android_startup_metric_test.html
new file mode 100644
index 00000000000..5ebf601fec6
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/android_startup_metric_test.html
@@ -0,0 +1,191 @@
+<!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/metrics/android_startup_metric.html">
+<link rel="import" href="/tracing/value/histogram_set.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ function createBrowserThread(model) {
+ const browserProcess = model.getOrCreateProcess(tr.b.GUID.allocateSimple());
+ const mainThread = browserProcess.getOrCreateThread(
+ tr.b.GUID.allocateSimple());
+ // Initializing the thread name helps passing validation checks made by the
+ // ChromeModelHelper.
+ mainThread.name = 'CrBrowserMain';
+ return mainThread;
+ }
+
+ function createRendererThread(model) {
+ const rendererProcess = model.getOrCreateProcess(
+ tr.b.GUID.allocateSimple());
+ const rendererMainThread =
+ rendererProcess.getOrCreateThread(tr.b.GUID.allocateSimple());
+ rendererMainThread.name = 'CrRendererMain';
+ return rendererMainThread;
+ }
+
+ // Adds a browser and renderer to the process, with a few key events necessary
+ // to calculate the |androidStartupMetric|. An |offset| can be added to all
+ // events and the length of a few events can be extended by
+ // |incrementForMetrics|.
+ function fillModelWithOneBrowserSession(model, offset, incrementForMetrics) {
+ // In order for the tests below to succeed with strictEqual, the floating
+ // point values should have exact representation as IEEE754 float.
+ createBrowserThread(model).asyncSliceGroup.push(
+ tr.c.TestUtils.newAsyncSliceEx({
+ cat: 'startup',
+ title: 'Startup.BrowserMessageLoopStartTimeFromMainEntry3',
+ start: (offset + 6800.125),
+ duration: (incrementForMetrics + 1700.0625)}));
+ const rendererMainThread = createRendererThread(model);
+ rendererMainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'loading,rail,devtools.timeline',
+ title: 'firstContentfulPaint',
+ start: (offset + 8400.125 + incrementForMetrics),
+ duration: 0.0,
+ args: {frame: '0x0'}}));
+
+ // Add an extra FCP event in the same renderer process appearing after the
+ // initial FCP even to check that it is ignored by metric computations.
+ rendererMainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'loading,rail,devtools.timeline',
+ title: 'firstContentfulPaint',
+ start: (offset + 8400.125 + incrementForMetrics + 0.125),
+ duration: 0.0,
+ args: {frame: '0x0'}}));
+ }
+
+ // Adds early messageloop and FCP events. The metric should ignore these very
+ // first messageloop start and FCP events in the trace. The specific lengths
+ // are not important.
+ function addEarlyEventsToBeIgnored(model, offset) {
+ createBrowserThread(model).asyncSliceGroup.push(
+ tr.c.TestUtils.newAsyncSliceEx({
+ cat: 'startup',
+ title: 'Startup.BrowserMessageLoopStartTimeFromMainEntry3',
+ start: (offset + 1.0),
+ duration: 10.0}));
+ createRendererThread(model).sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'loading,rail,devtools.timeline',
+ title: 'firstContentfulPaint',
+ start: (offset + 2.0),
+ duration: 1.0,
+ args: {frame: '0x0'}}));
+ }
+
+ function makeTestModel(offset, incrementForMetrics) {
+ return tr.c.TestUtils.newModel(function(model) {
+ fillModelWithOneBrowserSession(model, offset, incrementForMetrics);
+ addEarlyEventsToBeIgnored(model, offset);
+ addEarlyEventsToBeIgnored(model, offset + 20.0);
+ });
+ }
+
+ // Checks recording of the main histograms in the simplest case.
+ test('androidStartupMetric_simple', function() {
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.androidStartupMetric(histograms, makeTestModel(0.0, 0.0));
+ const messageLoopStartHistogram = histograms.getHistogramNamed(
+ 'messageloop_start_time');
+ assert.strictEqual(1, messageLoopStartHistogram.numValues);
+ assert.strictEqual(1700.0625, messageLoopStartHistogram.average);
+ const firstContentfulPaintHistogram = histograms.getHistogramNamed(
+ 'first_contentful_paint_time');
+ assert.strictEqual(1, firstContentfulPaintHistogram.numValues);
+ assert.strictEqual(1600.0, firstContentfulPaintHistogram.average);
+ });
+
+ // Emulates loss of the initial message loop start event. Checks that this
+ // event is ignored and the |androidStartupMetric| does not crash.
+ test('androidStartupMetric_missingOneBrowserStart', function() {
+ function makeTestModelWithOneEventMissing() {
+ return tr.c.TestUtils.newModel(function(model) {
+ fillModelWithOneBrowserSession(model, 0.0, 0.0);
+ // Note: the initial Startup.BrowserMessageLoopStartTimeFromMainEntry3'
+ // is intentionally missing.
+ createRendererThread(model).sliceGroup.pushSlice(
+ tr.c.TestUtils.newSliceEx({
+ cat: 'loading,rail,devtools.timeline',
+ title: 'firstContentfulPaint',
+ start: 2.0,
+ duration: 1.0,
+ args: {frame: '0x0'}}));
+ createBrowserThread(model).asyncSliceGroup.push(
+ tr.c.TestUtils.newAsyncSliceEx({
+ cat: 'startup',
+ title: 'Startup.BrowserMessageLoopStartTimeFromMainEntry3',
+ start: (20.0 + 1.0),
+ duration: 10.0}));
+ createRendererThread(model).sliceGroup.pushSlice(
+ tr.c.TestUtils.newSliceEx({
+ cat: 'loading,rail,devtools.timeline',
+ title: 'firstContentfulPaint',
+ start: (20.0 + 2.0),
+ duration: 1.0,
+ args: {frame: '0x0'}}));
+ });
+ }
+
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.androidStartupMetric(histograms,
+ makeTestModelWithOneEventMissing(0.0));
+ const messageLoopStartHistogram = histograms.getHistogramNamed(
+ 'messageloop_start_time');
+ assert.strictEqual(1, messageLoopStartHistogram.numValues);
+ assert.strictEqual(1700.0625, messageLoopStartHistogram.average);
+ const firstContentfulPaintHistogram = histograms.getHistogramNamed(
+ 'first_contentful_paint_time');
+ assert.strictEqual(1, firstContentfulPaintHistogram.numValues);
+ assert.strictEqual(1600.0, firstContentfulPaintHistogram.average);
+ });
+
+ // Checks the metrics after adding an offset to events in the model, and
+ // making a few durations longer by a constant.
+ test('androidStartupMetric_withOffsetAndLongerTask', function() {
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.androidStartupMetric(histograms, makeTestModel(5.0, 7.0));
+ const messageLoopStartHistogram = histograms.getHistogramNamed(
+ 'messageloop_start_time');
+ assert.strictEqual(1, messageLoopStartHistogram.numValues);
+ assert.strictEqual(1707.0625, messageLoopStartHistogram.average);
+ const firstContentfulPaintHistogram = histograms.getHistogramNamed(
+ 'first_contentful_paint_time');
+ assert.strictEqual(1, firstContentfulPaintHistogram.numValues);
+ assert.strictEqual(1607.0, firstContentfulPaintHistogram.average);
+ });
+
+ test('androidStartupMetric_twoSessions', function() {
+ function makeTestModelWithTwoSessionsOneDelayed(
+ offset, incrementForMetrics) {
+ return tr.c.TestUtils.newModel(function(model) {
+ fillModelWithOneBrowserSession(model, 0.0, 0.0);
+ fillModelWithOneBrowserSession(model, offset, incrementForMetrics);
+ addEarlyEventsToBeIgnored(model, 0.0, 0.0);
+ addEarlyEventsToBeIgnored(model, 0.0, 1.0);
+ });
+ }
+ const delta = 0.125;
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.androidStartupMetric(histograms,
+ makeTestModelWithTwoSessionsOneDelayed(10000.0, delta));
+ const messageLoopStartHistogram = histograms.getHistogramNamed(
+ 'messageloop_start_time');
+ assert.strictEqual(2, messageLoopStartHistogram.numValues);
+ assert.strictEqual(1700.0625, messageLoopStartHistogram.min);
+ assert.strictEqual(1700.0625 + delta, messageLoopStartHistogram.max);
+ const firstContentfulPaintHistogram = histograms.getHistogramNamed(
+ 'first_contentful_paint_time');
+ assert.strictEqual(2, firstContentfulPaintHistogram.numValues);
+ assert.strictEqual(1600.0, firstContentfulPaintHistogram.min);
+ assert.strictEqual(1600.0 + delta, firstContentfulPaintHistogram.max);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/android_systrace_metric.html b/chromium/third_party/catapult/tracing/tracing/metrics/android_systrace_metric.html
new file mode 100644
index 00000000000..96c1ca0295c
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/android_systrace_metric.html
@@ -0,0 +1,224 @@
+<!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/metrics/metric_registry.html">
+<link rel="import" href="/tracing/value/histogram.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.metrics.sh', function() {
+ const MAX_INPUT_EVENT_TO_STARTUP_DELAY_IN_MS = 2000;
+ // Post-startup activity draw delay.
+ const MIN_DRAW_DELAY_IN_MS = 80;
+ const MAX_DRAW_DELAY_IN_MS = 2000;
+
+ function findProcess(processName, model) {
+ for (const pid in model.processes) {
+ const process = model.processes[pid];
+ if (process.name === processName) {
+ return process;
+ }
+ }
+ return undefined;
+ }
+
+ function findThreads(process, threadPrefix) {
+ if (process === undefined) return undefined;
+ const threads = [];
+ for (const tid in process.threads) {
+ const thread = process.threads[tid];
+ if (thread.name.startsWith(threadPrefix)) {
+ threads.push(thread);
+ }
+ }
+ return threads;
+ }
+
+ function findUIThread(process) {
+ if (process === undefined) return undefined;
+ const threads = findThreads(process, 'UI Thread');
+ if (threads !== undefined && threads.length === 1) {
+ return threads[0];
+ }
+ return process.threads[process.pid];
+ }
+
+ // Returns slices with actual app's process startup, excluding other delays.
+ function findLaunchSlices(model) {
+ const launches = [];
+ const binders = findThreads(findProcess('system_server', model), 'Binder');
+ for (const binderId in binders) {
+ const binder = binders[binderId];
+ for (const sliceId in binder.asyncSliceGroup.slices) {
+ const slice = binder.asyncSliceGroup.slices[sliceId];
+ if (slice.title.startsWith('launching:')) {
+ launches.push(slice);
+ }
+ }
+ }
+ return launches;
+ }
+
+ // Try to find draw event when activity just shown.
+ function findDrawSlice(appName, startNotBefore, model) {
+ let drawSlice = undefined;
+ const thread = findUIThread(findProcess(appName, model));
+ if (thread === undefined) return undefined;
+
+ for (const sliceId in thread.sliceGroup.slices) {
+ const slice = thread.sliceGroup.slices[sliceId];
+ if (slice.start < startNotBefore + MIN_DRAW_DELAY_IN_MS ||
+ slice.start > startNotBefore + MAX_DRAW_DELAY_IN_MS) continue;
+ if (slice.title !== 'draw') continue;
+ // TODO(kraynov): Add reportFullyDrawn() support.
+ if (drawSlice === undefined || slice.start < drawSlice.start) {
+ drawSlice = slice;
+ }
+ }
+ return drawSlice;
+ }
+
+ // Try to find input event before a process starts.
+ function findInputEventSlice(endNotAfter, model) {
+ const endNotBefore = endNotAfter - MAX_INPUT_EVENT_TO_STARTUP_DELAY_IN_MS;
+ let inputSlice = undefined;
+ const systemUi = findUIThread(findProcess('com.android.systemui', model));
+ if (systemUi === undefined) return undefined;
+
+ for (const sliceId in systemUi.asyncSliceGroup.slices) {
+ const slice = systemUi.asyncSliceGroup.slices[sliceId];
+ if (slice.end > endNotAfter || slice.end < endNotBefore) continue;
+ if (slice.title !== 'deliverInputEvent') continue;
+ if (inputSlice === undefined || slice.end > inputSlice.end) {
+ inputSlice = slice;
+ }
+ }
+ return inputSlice;
+ }
+
+ function computeStartupTimeInMs(appName, launchSlice, model) {
+ let startupStart = launchSlice.start;
+ let startupEnd = launchSlice.end;
+ const drawSlice = findDrawSlice(appName, launchSlice.end, model);
+ if (drawSlice !== undefined) {
+ startupEnd = drawSlice.end;
+ }
+ const inputSlice = findInputEventSlice(launchSlice.start, model);
+ if (inputSlice !== undefined) {
+ startupStart = inputSlice.start;
+ }
+ return startupEnd - startupStart;
+ }
+
+ // App startup time metric.
+ function measureStartup(histograms, model) {
+ const launches = findLaunchSlices(model);
+ for (const sliceId in launches) {
+ const launchSlice = launches[sliceId];
+ const appName = launchSlice.title.split(': ')[1];
+ const startupMs = computeStartupTimeInMs(appName, launchSlice, model);
+ histograms.createHistogram(`android:systrace:startup:${appName}`,
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter, startupMs);
+ }
+ }
+
+ // Metric which measures time spent by process threads in each thread state.
+ // The value of metric is a time percentage relative to the length of selected
+ // range of interest.
+ function measureThreadStates(histograms, model, rangeOfInterest) {
+ for (const pid in model.processes) {
+ const process = model.processes[pid];
+ if (process.name === undefined) continue;
+
+ let hasSlices = false;
+ let timeRunning = 0;
+ let timeRunnable = 0;
+ let timeSleeping = 0;
+ let timeUninterruptible = 0;
+ let timeBlockIO = 0;
+ let timeUnknown = 0;
+
+ for (const tid in process.threads) {
+ const thread = process.threads[tid];
+ if (thread.timeSlices === undefined) continue;
+
+ for (const sliceId in thread.timeSlices) {
+ const slice = thread.timeSlices[sliceId];
+ const sliceRange =
+ tr.b.math.Range.fromExplicitRange(slice.start, slice.end);
+ const intersection = rangeOfInterest.findIntersection(sliceRange);
+ const duration = intersection.duration;
+ if (duration === 0) continue;
+ hasSlices = true;
+
+ if (slice.title === 'Running') {
+ timeRunning += duration;
+ } else if (slice.title === 'Runnable') {
+ timeRunnable += duration;
+ } else if (slice.title === 'Sleeping') {
+ timeSleeping += duration;
+ } else if (slice.title.startsWith('Uninterruptible')) {
+ timeUninterruptible += duration;
+ if (slice.title.includes('Block I/O')) timeBlockIO += duration;
+ } else {
+ timeUnknown += duration;
+ }
+ }
+ }
+
+ if (hasSlices) {
+ // For sake of simplicity we don't count wall time for each
+ // thread/process and just calculate relative values against selected
+ // range of interest.
+ const wall = rangeOfInterest.max - rangeOfInterest.min;
+ histograms.createHistogram(
+ `android:systrace:threadtime:${process.name}:running`,
+ tr.b.Unit.byName.normalizedPercentage, timeRunning / wall);
+ histograms.createHistogram(
+ `android:systrace:threadtime:${process.name}:runnable`,
+ tr.b.Unit.byName.normalizedPercentage, timeRunnable / wall);
+ histograms.createHistogram(
+ `android:systrace:threadtime:${process.name}:sleeping`,
+ tr.b.Unit.byName.normalizedPercentage, timeSleeping / wall);
+ histograms.createHistogram(
+ `android:systrace:threadtime:${process.name}:blockio`,
+ tr.b.Unit.byName.normalizedPercentage, timeBlockIO / wall);
+ histograms.createHistogram(
+ `android:systrace:threadtime:${process.name}:uninterruptible`,
+ tr.b.Unit.byName.normalizedPercentage, timeUninterruptible / wall);
+
+ // In case of changing names in systrace and importer.
+ if (timeUnknown > 0) {
+ histograms.createHistogram(
+ `android:systrace:threadtime:${process.name}:unknown`,
+ tr.b.Unit.byName.normalizedPercentage, timeUnknown / wall);
+ }
+ }
+ }
+ }
+
+ function androidSystraceMetric(histograms, model, options) {
+ let rangeOfInterest = model.bounds;
+ if (options !== undefined && options.rangeOfInterest !== undefined) {
+ rangeOfInterest = options.rangeOfInterest;
+ }
+
+ measureStartup(histograms, model);
+ measureThreadStates(histograms, model, rangeOfInterest);
+ }
+
+ tr.metrics.MetricRegistry.register(androidSystraceMetric, {
+ supportsRangeOfInterest: true
+ });
+
+ return {
+ androidSystraceMetric,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/android_systrace_metric_test.html b/chromium/third_party/catapult/tracing/tracing/metrics/android_systrace_metric_test.html
new file mode 100644
index 00000000000..76936416503
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/android_systrace_metric_test.html
@@ -0,0 +1,146 @@
+<!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/extras/importer/linux_perf/ftrace_importer.html">
+<link rel="import" href="/tracing/extras/importer/trace_event_importer.html">
+<link rel="import" href="/tracing/metrics/android_systrace_metric.html">
+<link rel="import" href="/tracing/value/histogram_set.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const SYSTRACE_CLOCK_SYNC =
+ '<100>-100 (-----) [000] ...1 0.000000: tracing_mark_write: ' +
+ 'trace_event_clock_sync: parent_ts=0\n' +
+ '<100>-100 (-----) [000] ...1 0.000000: tracing_mark_write: ' +
+ 'trace_event_clock_sync: realtime_ts=1487002000000';
+
+ // Some event is required to help importer detect a parent process.
+ const SYSTRACE_SYSTEM_SERVER_ANNOTATION =
+ 'system_server-101 ( 101) [000] ...1 0.550000: ' +
+ 'tracing_mark_write: S|101|dummyEvent|201\n' +
+ 'system_server-101 ( 101) [000] ...1 0.551000: ' +
+ 'tracing_mark_write: F|101|dummyEvent|201';
+
+ const SYSTRACE_TOUCH_SLICE =
+ 'com.android.systemui-102 ( 102) [000] ...1 2.500000: ' +
+ 'tracing_mark_write: S|102|deliverInputEvent|202\n' +
+ 'com.android.systemui-102 ( 102) [000] ...1 2.510000: ' +
+ 'tracing_mark_write: F|102|deliverInputEvent|202';
+
+ const SYSTRACE_LAUNCH_SLICE =
+ 'Binder:101_C-103 ( 101) [000] ...1 2.750000: ' +
+ 'tracing_mark_write: S|101|launching: com.android.apps.sms|203\n' +
+ 'android.display-104 ( 101) [000] ...1 4.250000: ' +
+ 'tracing_mark_write: F|101|launching: com.android.apps.sms|203';
+
+ const SYSTRACE_DRAW_SLICE =
+ 'com.android.apps.sms-105 ( 105) [000] ...1 4.450000: ' +
+ 'tracing_mark_write: B|105|draw\n' +
+ 'com.android.apps.sms-105 ( 105) [000] ...1 4.455000: ' +
+ 'tracing_mark_write: E';
+
+ function makeModel(systraceLines) {
+ const events = JSON.stringify({
+ traceEvents: [],
+ systemTraceEvents: SYSTRACE_CLOCK_SYNC + '\n' + systraceLines.join('\n')
+ });
+ const model = tr.c.TestUtils.newModelWithEvents([events]);
+ // Fix missing process names.
+ for (const pid in model.processes) {
+ const process = model.processes[pid];
+ if (process.name !== undefined) continue;
+ if (process.threads[pid] !== undefined) {
+ process.name = process.threads[pid].name;
+ }
+ }
+ return model;
+ }
+
+ function testStartup(systrace, expectedTimeInMs) {
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.androidSystraceMetric(histograms, makeModel(systrace));
+ assert.lengthOf(histograms, 1);
+ const startupHist = histograms.getHistogramNamed(
+ 'android:systrace:startup:com.android.apps.sms').running;
+ assert.strictEqual(startupHist.count, 1);
+ assert.closeTo(startupHist.mean, expectedTimeInMs, 1e-5);
+ }
+
+ test('androidSystraceMetric_startup_noData', function() {
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.androidSystraceMetric(histograms, makeModel([]));
+ assert.lengthOf(histograms, 0);
+ });
+
+ test('androidSystraceMetric_startup_simple', function() {
+ const systrace = [
+ SYSTRACE_SYSTEM_SERVER_ANNOTATION,
+ SYSTRACE_TOUCH_SLICE,
+ SYSTRACE_LAUNCH_SLICE,
+ SYSTRACE_DRAW_SLICE
+ ];
+ testStartup(systrace, 1955);
+ });
+
+ test('androidSystraceMetric_startup_noTouch', function() {
+ const systrace = [
+ SYSTRACE_SYSTEM_SERVER_ANNOTATION,
+ SYSTRACE_LAUNCH_SLICE,
+ SYSTRACE_DRAW_SLICE
+ ];
+ testStartup(systrace, 1705);
+ });
+
+ test('androidSystraceMetric_startup_noDraw', function() {
+ const systrace = [
+ SYSTRACE_SYSTEM_SERVER_ANNOTATION,
+ SYSTRACE_TOUCH_SLICE,
+ SYSTRACE_LAUNCH_SLICE,
+ ];
+ testStartup(systrace, 1750);
+ });
+
+ test('androidSystraceMetric_startup_noTouchNoDraw', function() {
+ const systrace = [
+ SYSTRACE_SYSTEM_SERVER_ANNOTATION,
+ SYSTRACE_LAUNCH_SLICE,
+ ];
+ testStartup(systrace, 1500);
+ });
+
+ test('androidSystraceMetric_threadtime_simple', function() {
+ const model = tr.c.TestUtils.newModel(model => {
+ const process = model.getOrCreateProcess(42);
+ process.name = 'garbage_producer';
+ const thread = process.getOrCreateThread(42);
+ thread.timeSlices = [
+ tr.c.TestUtils.newThreadSlice(thread, 'Sleeping', 0, 100),
+ tr.c.TestUtils.newThreadSlice(thread, 'Running', 100, 400)
+ ];
+ });
+
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.androidSystraceMetric(histograms, model);
+ assert.lengthOf(histograms, 5);
+
+ const assertHistValue = function(name, expectedValue) {
+ const hist = histograms.getHistogramNamed(
+ `android:systrace:threadtime:garbage_producer:${name}`);
+ assert.strictEqual(hist.running.count, 1);
+ assert.closeTo(hist.running.mean, expectedValue, 1e-5);
+ };
+ assertHistValue('running', 0.8);
+ assertHistValue('runnable', 0);
+ assertHistValue('sleeping', 0.2);
+ assertHistValue('blockio', 0);
+ assertHistValue('uninterruptible', 0);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/blink/gc_metric.html b/chromium/third_party/catapult/tracing/tracing/metrics/blink/gc_metric.html
new file mode 100644
index 00000000000..641c8bd0c9f
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/blink/gc_metric.html
@@ -0,0 +1,265 @@
+<!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/math/range.html">
+<link rel="import" href="/tracing/base/unit.html">
+<link rel="import" href="/tracing/metrics/metric_registry.html">
+<link rel="import" href="/tracing/metrics/v8/utils.html">
+<link rel="import" href="/tracing/value/histogram.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.metrics.blink', function() {
+ // Maps the Blink GC events in timeline to telemetry friendly names.
+ const BLINK_TOP_GC_EVENTS = {
+ 'BlinkGC.AtomicPhase': 'blink-gc-atomic-phase',
+ 'BlinkGC.CompleteSweep': 'blink-gc-complete-sweep',
+ 'BlinkGC.IncrementalMarkingStartMarking': 'blink-gc-incremental-start',
+ 'BlinkGC.IncrementalMarkingStep': 'blink-gc-incremental-step',
+ 'BlinkGC.LazySweepInIdle': 'blink-gc-lazy-sweep-idle',
+ 'BlinkGC.LazySweepOnAllocation': 'blink-gc-lazy-sweep-allocation'
+ };
+
+ function blinkGarbageCollectionEventName(event) {
+ return BLINK_TOP_GC_EVENTS[event.title];
+ }
+
+ function isNonForcedBlinkGarbageCollectionEvent(event) {
+ return event.title in BLINK_TOP_GC_EVENTS &&
+ (!event.args || !event.args.forced) &&
+ !tr.metrics.v8.utils.isForcedGarbageCollectionEvent(event);
+ }
+
+ function isNonNestedNonForcedBlinkGarbageCollectionEvent(event) {
+ return isNonForcedBlinkGarbageCollectionEvent(event) &&
+ !tr.metrics.v8.utils.findParent(event,
+ tr.metrics.v8.utils.isGarbageCollectionEvent);
+ }
+
+ function blinkGcMetric(histograms, model) {
+ addDurationOfTopEvents(histograms, model);
+ addTotalDurationOfTopEvents(histograms, model);
+ addIdleTimesOfTopEvents(histograms, model);
+ addTotalIdleTimesOfTopEvents(histograms, model);
+ addTotalDurationOfBlinkAndV8TopEvents(histograms, model);
+ }
+
+ tr.metrics.MetricRegistry.register(blinkGcMetric);
+
+ const timeDurationInMs_smallerIsBetter =
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter;
+ const percentage_biggerIsBetter =
+ tr.b.Unit.byName.normalizedPercentage_biggerIsBetter;
+
+ // 0.1 steps from 0 to 20 since it is the most common range.
+ // Exponentially increasing steps from 20 to 200.
+ const CUSTOM_BOUNDARIES = tr.v.HistogramBinBoundaries.createLinear(0, 20, 200)
+ .addExponentialBins(200, 100);
+
+ function createNumericForTopEventTime(name) {
+ const n = new tr.v.Histogram(name,
+ timeDurationInMs_smallerIsBetter, CUSTOM_BOUNDARIES);
+ n.customizeSummaryOptions({
+ avg: true,
+ count: true,
+ max: true,
+ min: false,
+ std: true,
+ sum: true,
+ percentile: [0.90]});
+ return n;
+ }
+
+ function createNumericForTotalEventTime(name) {
+ const n = new tr.v.Histogram(name,
+ timeDurationInMs_smallerIsBetter, CUSTOM_BOUNDARIES);
+ n.customizeSummaryOptions({
+ avg: false,
+ count: true,
+ max: false,
+ min: false,
+ std: false,
+ sum: true,
+ percentile: [0.90]});
+ return n;
+ }
+
+ function createNumericForUnifiedEventTime(name) {
+ const n = new tr.v.Histogram(name,
+ timeDurationInMs_smallerIsBetter, CUSTOM_BOUNDARIES);
+ n.customizeSummaryOptions({
+ avg: false,
+ count: true,
+ max: true,
+ min: false,
+ std: false,
+ sum: true,
+ percentile: [0.90]});
+ return n;
+ }
+
+ function createNumericForIdleTime(name) {
+ const n = new tr.v.Histogram(name,
+ timeDurationInMs_smallerIsBetter, CUSTOM_BOUNDARIES);
+ n.customizeSummaryOptions({
+ avg: true,
+ count: false,
+ max: true,
+ min: false,
+ std: false,
+ sum: true,
+ percentile: []
+ });
+ return n;
+ }
+
+ function createPercentage(name, numerator, denominator) {
+ const histogram = new tr.v.Histogram(name, percentage_biggerIsBetter);
+ if (denominator === 0) {
+ histogram.addSample(0);
+ } else {
+ histogram.addSample(numerator / denominator);
+ }
+ return histogram;
+ }
+
+ /**
+ * Example output:
+ * - blink-gc-atomic-phase
+ */
+ function addDurationOfTopEvents(histograms, model) {
+ tr.metrics.v8.utils.groupAndProcessEvents(model,
+ isNonForcedBlinkGarbageCollectionEvent,
+ blinkGarbageCollectionEventName,
+ function(name, events) {
+ const cpuDuration = createNumericForTopEventTime(name);
+ for (const event of events) {
+ cpuDuration.addSample(event.cpuDuration);
+ }
+ histograms.addHistogram(cpuDuration);
+ }
+ );
+ }
+
+ /**
+ * Example output:
+ * - blink-gc-total_sum
+ */
+ function addTotalDurationOfTopEvents(histograms, model) {
+ tr.metrics.v8.utils.groupAndProcessEvents(model,
+ isNonForcedBlinkGarbageCollectionEvent,
+ event => 'blink-gc-total',
+ function(name, events) {
+ const cpuDuration = createNumericForTotalEventTime(name);
+ for (const event of events) {
+ cpuDuration.addSample(event.cpuDuration);
+ }
+ histograms.addHistogram(cpuDuration);
+ }
+ );
+ }
+
+ /**
+ * Example output:
+ * - blink-gc-atomic-phase_idle_deadline_overrun,
+ * - blink-gc-atomic-phase_outside_idle,
+ * - blink-gc-atomic-phase_percentage_idle.
+ */
+ function addIdleTimesOfTopEvents(histograms, model) {
+ tr.metrics.v8.utils.groupAndProcessEvents(model,
+ isNonForcedBlinkGarbageCollectionEvent,
+ blinkGarbageCollectionEventName,
+ function(name, events) {
+ addIdleTimes(histograms, model, name, events);
+ }
+ );
+ }
+
+ /**
+ * Example output:
+ * - blink-gc-total_idle_deadline_overrun,
+ * - blink-gc-total_outside_idle,
+ * - blink-gc-total_percentage_idle.
+ */
+ function addTotalIdleTimesOfTopEvents(histograms, model) {
+ tr.metrics.v8.utils.groupAndProcessEvents(model,
+ isNonForcedBlinkGarbageCollectionEvent,
+ event => 'blink-gc-total',
+ function(name, events) {
+ addIdleTimes(histograms, model, name, events);
+ }
+ );
+ }
+
+ function addIdleTimes(histograms, model, name, events) {
+ const cpuDuration = createNumericForIdleTime(name + '_cpu');
+ const insideIdle = createNumericForIdleTime(name + '_inside_idle');
+ const outsideIdle = createNumericForIdleTime(name + '_outside_idle');
+ const idleDeadlineOverrun = createNumericForIdleTime(
+ name + '_idle_deadline_overrun');
+ for (const event of events) {
+ const idleTask = tr.metrics.v8.utils.findParent(
+ event, tr.metrics.v8.utils.isIdleTask);
+ let inside = 0;
+ let overrun = 0;
+ if (idleTask) {
+ const allottedTime = idleTask.args.allotted_time_ms;
+ if (event.duration > allottedTime) {
+ overrun = event.duration - allottedTime;
+ // Don't count time over the deadline as being inside idle time.
+ // Since the deadline should be relative to wall clock we
+ // compare allotted_time_ms with wall duration instead of thread
+ // duration, and then assume the thread duration was inside idle
+ // for the same percentage of time.
+ inside = event.cpuDuration * allottedTime / event.duration;
+ } else {
+ inside = event.cpuDuration;
+ }
+ }
+ cpuDuration.addSample(event.cpuDuration);
+ insideIdle.addSample(inside);
+ outsideIdle.addSample(event.cpuDuration - inside);
+ idleDeadlineOverrun.addSample(overrun);
+ }
+ histograms.addHistogram(idleDeadlineOverrun);
+ histograms.addHistogram(outsideIdle);
+ const percentage = createPercentage(
+ name + '_percentage_idle', insideIdle.sum, cpuDuration.sum);
+ histograms.addHistogram(percentage);
+ }
+
+ function isV8OrBlinkTopLevelGarbageCollectionEvent(event) {
+ return tr.metrics.v8.utils.isNotForcedTopGarbageCollectionEvent(event) ||
+ isNonNestedNonForcedBlinkGarbageCollectionEvent(event);
+ }
+
+ /**
+ * Example output:
+ * - unified-gc-total_sum
+ * - unified-gc-total_max
+ * - unified-gc-total_count
+ */
+ function addTotalDurationOfBlinkAndV8TopEvents(histograms, model) {
+ tr.metrics.v8.utils.groupAndProcessEvents(model,
+ isV8OrBlinkTopLevelGarbageCollectionEvent,
+ event => 'unified-gc-total',
+ function(name, events) {
+ const cpuDuration = createNumericForUnifiedEventTime(name);
+ for (const event of events) {
+ cpuDuration.addSample(event.cpuDuration);
+ }
+ histograms.addHistogram(cpuDuration);
+ }
+ );
+ }
+
+ return {
+ blinkGcMetric,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/blink/gc_metric_test.html b/chromium/third_party/catapult/tracing/tracing/metrics/blink/gc_metric_test.html
new file mode 100644
index 00000000000..924527f126b
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/blink/gc_metric_test.html
@@ -0,0 +1,347 @@
+<!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/extras/importer/trace_event_importer.html">
+<link rel="import" href="/tracing/metrics/blink/gc_metric.html">
+<link rel="import" href="/tracing/metrics/v8/utils.html">
+<link rel="import" href="/tracing/model/slice_group.html">
+<link rel="import" href="/tracing/value/histogram_set.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ function createModel(start, end, slices) {
+ const opts = {
+ customizeModelCallback(model) {
+ const process = model.getOrCreateProcess(1);
+ const thread = process.getOrCreateThread(2);
+ const group = thread.sliceGroup;
+ slices.forEach(function(slice) {
+ group.pushSlice(tr.c.TestUtils.newSliceEx(slice));
+ });
+ group.createSubSlices();
+ }
+ };
+ const model = tr.c.TestUtils.newModelWithEvents([], opts);
+ return model;
+ }
+
+ function constructName(name, suffix) {
+ return name + '_' + suffix;
+ }
+
+ function run(slices) {
+ const histograms = new tr.v.HistogramSet();
+ const startTime = slices.reduce(
+ (acc, slice) => (Math.min(acc, slice.start)));
+ const endTime = slices.reduce((acc, slice) => (Math.max(acc, slice.end)));
+ const model = createModel(startTime - 1, endTime + 1, slices);
+ tr.metrics.blink.blinkGcMetric(histograms, model);
+ return histograms;
+ }
+
+ test('topEvents', function() {
+ const events = {
+ 'BlinkGC.AtomicPhase': 'blink-gc-atomic-phase',
+ 'BlinkGC.CompleteSweep': 'blink-gc-complete-sweep',
+ 'BlinkGC.LazySweepInIdle': 'blink-gc-lazy-sweep-idle'
+ };
+ for (const [timelineName, telemetryName] of Object.entries(events)) {
+ const slices = [
+ {
+ title: timelineName, args: {}, start: 100, end: 200,
+ cpuStart: 100, cpuEnd: 200
+ }
+ ];
+ const actual = run(slices);
+
+ let value = actual.getHistogramNamed(telemetryName);
+ assert.strictEqual(value.running.sum, 100);
+ assert.strictEqual(value.numValues, 1);
+ assert.strictEqual(value.average, 100);
+ assert.strictEqual(value.running.max, 100);
+ assert.closeTo(value.getApproximatePercentile(0.90), 100, 1);
+
+ value = actual.getHistogramNamed(
+ `${telemetryName}_idle_deadline_overrun`);
+ assert.strictEqual(value.running.sum, 0);
+ assert.strictEqual(value.numValues, 1);
+ assert.strictEqual(value.average, 0);
+ assert.strictEqual(value.running.max, 0);
+
+ value = actual.getHistogramNamed(`${telemetryName}_outside_idle`);
+ assert.strictEqual(value.running.sum, 100);
+ assert.strictEqual(value.numValues, 1);
+ assert.strictEqual(value.average, 100);
+
+ value = actual.getHistogramNamed(`${telemetryName}_percentage_idle`);
+ assert.strictEqual(value.average, 0);
+ }
+ });
+
+ test('idleTimes', function() {
+ const histograms = new tr.v.HistogramSet();
+ const slices = [
+ {
+ title: 'SingleThreadIdleTaskRunner::RunTask',
+ args: {'allotted_time_ms': 100}, start: 100, end: 200,
+ cpuStart: 100, cpuEnd: 200
+ },
+ {
+ title: 'BlinkGC.AtomicPhase', args: {}, start: 110, end: 190,
+ cpuStart: 110, cpuEnd: 190
+ }
+ ];
+ const actual = run(slices);
+
+ let value = actual.getHistogramNamed('blink-gc-atomic-phase');
+ assert.strictEqual(value.running.sum, 80);
+ assert.strictEqual(value.numValues, 1);
+ assert.strictEqual(value.average, 80);
+ assert.strictEqual(value.running.max, 80);
+
+ value = actual.getHistogramNamed(
+ 'blink-gc-atomic-phase_idle_deadline_overrun');
+ assert.strictEqual(value.running.sum, 0);
+ assert.strictEqual(value.average, 0);
+ assert.strictEqual(value.running.max, 0);
+
+ value = actual.getHistogramNamed('blink-gc-atomic-phase_outside_idle');
+ assert.strictEqual(value.running.sum, 0);
+ assert.strictEqual(value.average, 0);
+ assert.strictEqual(value.running.max, 0);
+
+ value = actual.getHistogramNamed('blink-gc-atomic-phase_percentage_idle');
+ assert.strictEqual(value.average, 1);
+ });
+
+ test('idleTimeOverrun', function() {
+ const histograms = new tr.v.HistogramSet();
+ const slices = [
+ {
+ title: 'SingleThreadIdleTaskRunner::RunTask',
+ args: {'allotted_time_ms': 10}, start: 100, end: 200,
+ cpuStart: 100, cpuEnd: 200
+ },
+ {
+ title: 'BlinkGC.AtomicPhase', args: {}, start: 110, end: 190,
+ cpuStart: 110, cpuEnd: 190
+ }
+ ];
+ const actual = run(slices);
+
+ let value = actual.getHistogramNamed('blink-gc-atomic-phase');
+ assert.strictEqual(value.running.sum, 80);
+ assert.strictEqual(value.numValues, 1);
+ assert.strictEqual(value.average, 80);
+ assert.strictEqual(value.running.max, 80);
+
+ value = actual.getHistogramNamed(
+ 'blink-gc-atomic-phase_idle_deadline_overrun');
+ assert.strictEqual(value.running.sum, 70);
+ assert.strictEqual(value.average, 70);
+ assert.strictEqual(value.running.max, 70);
+
+ value = actual.getHistogramNamed('blink-gc-atomic-phase_outside_idle');
+ assert.strictEqual(value.running.sum, 70);
+ assert.strictEqual(value.average, 70);
+ assert.strictEqual(value.running.max, 70);
+
+ value = actual.getHistogramNamed('blink-gc-atomic-phase_percentage_idle');
+ assert.closeTo(value.average, 1 / 8, 1e-6);
+ });
+
+ test('totalTimeForBlinkGC', function() {
+ const histograms = new tr.v.HistogramSet();
+ const slices = [
+ {
+ title: 'BlinkGC.AtomicPhase', args: {}, start: 100, end: 200,
+ cpuStart: 100, cpuEnd: 200
+ },
+ {
+ title: 'BlinkGC.LazySweepInIdle', args: {}, start: 210,
+ end: 290, cpuStart: 210, cpuEnd: 290
+ }
+ ];
+ const actual = run(slices);
+
+ let value = actual.getHistogramNamed('blink-gc-total');
+ assert.strictEqual(value.running.sum, 180);
+ assert.strictEqual(value.numValues, 2);
+ assert.strictEqual(value.average, 90);
+ assert.strictEqual(value.running.max, 100);
+
+ value = actual.getHistogramNamed('blink-gc-total_idle_deadline_overrun');
+ assert.strictEqual(value.running.sum, 0);
+ assert.strictEqual(value.average, 0);
+ assert.strictEqual(value.running.max, 0);
+
+ value = actual.getHistogramNamed('blink-gc-total_outside_idle');
+ assert.strictEqual(value.running.sum, 180);
+ assert.strictEqual(value.average, 90);
+ assert.strictEqual(value.running.max, 100);
+
+ value = actual.getHistogramNamed('blink-gc-total_percentage_idle');
+ assert.strictEqual(value.average, 0);
+ });
+
+ test('totalTimeForUnifiedGC', function() {
+ const histograms = new tr.v.HistogramSet();
+ const slices = [
+ {
+ title: 'V8.GCFinalizeMC', args: {},
+ start: 100, end: 300, cpuStart: 100, cpuEnd: 300
+ },
+ {
+ title: 'BlinkGC.AtomicPhase', args: {},
+ start: 310, end: 410, cpuStart: 310, cpuEnd: 410
+ }
+ ];
+ const actual = run(slices);
+
+ const value = actual.getHistogramNamed('unified-gc-total');
+ assert.strictEqual(value.running.sum, 300);
+ assert.strictEqual(value.numValues, 2);
+ assert.strictEqual(value.average, 150);
+ assert.strictEqual(value.running.max, 200);
+ });
+
+ test('totalTimeForUnifiedGCBlinkNestedInV8', function() {
+ // Nested Blink GC in V8 top-level GC can happen during unified garbage
+ // collection, or when callbacks that trigger e.g. sweeping are fired
+ // from V8's GC. These should only be accounted once.
+ const histograms = new tr.v.HistogramSet();
+ const slices = [
+ {
+ title: 'V8.GCFinalizeMC', args: {},
+ start: 100, end: 300, cpuStart: 100, cpuEnd: 300
+ },
+ // Nested events should be ignored.
+ {
+ title: 'BlinkGC.CompleteSweep', args: {},
+ start: 200, end: 270, cpuStart: 200, cpuEnd: 270
+ },
+ {
+ title: 'BlinkGC.IncrementalMarkingStartMarking', args: {},
+ start: 280, end: 290, cpuStart: 280, cpuEnd: 290
+ },
+ // Next event is outside of nesting and should be accounted for.
+ {
+ title: 'BlinkGC.IncrementalMarkingStartMarking', args: {},
+ start: 310, end: 320, cpuStart: 310, cpuEnd: 320
+ },
+ ];
+ const actual = run(slices);
+
+ const value = actual.getHistogramNamed('unified-gc-total');
+ assert.strictEqual(value.running.sum, 210);
+ assert.strictEqual(value.numValues, 2);
+ assert.strictEqual(value.average, 105);
+ assert.strictEqual(value.running.max, 200);
+ });
+
+ function getSlicesWithForcedV8GCs() {
+ return [
+ {
+ title: tr.metrics.v8.utils.forcedGCEventName(), args: {},
+ start: 100, end: 300, cpuStart: 100, cpuEnd: 300
+ },
+ // Following nested events should be ignored.
+ {
+ title: 'V8.GCFinalizeMC', args: {},
+ start: 100, end: 300, cpuStart: 100, cpuEnd: 300
+ },
+ {
+ title: 'BlinkGC.CompleteSweep', args: {},
+ start: 200, end: 270, cpuStart: 200, cpuEnd: 270
+ },
+ {
+ title: 'BlinkGC.IncrementalMarkingStartMarking', args: {},
+ start: 280, end: 290, cpuStart: 280, cpuEnd: 290
+ },
+ // Next event happens after the forced GC and should be accounted for.
+ {
+ title: 'BlinkGC.IncrementalMarkingStartMarking', args: {},
+ start: 310, end: 320, cpuStart: 310, cpuEnd: 320
+ },
+ {
+ title: 'BlinkGC.AtomicPhase', args: {'forced': false},
+ start: 320, end: 330, cpuStart: 320, cpuEnd: 330
+ },
+ ];
+ }
+
+ test('ignoreForcedV8GCEventsForUnifiedMetric', function() {
+ // Any events nested in a forced GC should be ignored.
+ const histograms = new tr.v.HistogramSet();
+ const actual = run(getSlicesWithForcedV8GCs());
+ const value = actual.getHistogramNamed('unified-gc-total');
+ assert.strictEqual(value.running.sum, 20);
+ assert.strictEqual(value.numValues, 2);
+ assert.strictEqual(value.average, 10);
+ assert.strictEqual(value.running.max, 10);
+ });
+
+ test('ignoreForcedV8GCEventsForBlinkMetric', function() {
+ // Any events nested in a forced GC should be ignored.
+ const histograms = new tr.v.HistogramSet();
+ const actual = run(getSlicesWithForcedV8GCs());
+ const value = actual.getHistogramNamed('blink-gc-total');
+ assert.strictEqual(value.running.sum, 20);
+ assert.strictEqual(value.numValues, 2);
+ assert.strictEqual(value.average, 10);
+ assert.strictEqual(value.running.max, 10);
+ });
+
+ function getSlicesWithForcedBlinkGCs() {
+ return [
+ // Following nested events should be ignored.
+ {
+ title: 'BlinkGC.CompleteSweep', args: {'forced': true},
+ start: 200, end: 270, cpuStart: 200, cpuEnd: 270
+ },
+ {
+ title: 'BlinkGC.AtomicPhase', args: {'forced': true},
+ start: 280, end: 290, cpuStart: 280, cpuEnd: 290
+ },
+ // Next events are not forced and should be accounted for.
+ {
+ title: 'BlinkGC.AtomicPhase', args: {},
+ start: 310, end: 320, cpuStart: 310, cpuEnd: 320
+ },
+ {
+ title: 'BlinkGC.AtomicPhase', args: {'forced': false},
+ start: 320, end: 330, cpuStart: 320, cpuEnd: 330
+ },
+ ];
+ }
+
+ test('ignoreForcedBlinkGCEventsForUnifiedMetric', function() {
+ // Any forced Blink GC events should be ignored.
+ const histograms = new tr.v.HistogramSet();
+ const actual = run(getSlicesWithForcedBlinkGCs());
+ const value = actual.getHistogramNamed('unified-gc-total');
+ assert.strictEqual(value.running.sum, 20);
+ assert.strictEqual(value.numValues, 2);
+ assert.strictEqual(value.average, 10);
+ assert.strictEqual(value.running.max, 10);
+ });
+
+ test('ignoreForcedBlinkGCEventsForBlinkMetric', function() {
+ // Any forced Blink GC events should be ignored.
+ const histograms = new tr.v.HistogramSet();
+ const actual = run(getSlicesWithForcedBlinkGCs());
+ const value = actual.getHistogramNamed('blink-gc-total');
+ assert.strictEqual(value.running.sum, 20);
+ assert.strictEqual(value.numValues, 2);
+ assert.strictEqual(value.average, 10);
+ assert.strictEqual(value.running.max, 10);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/blink/leak_detection_metric.html b/chromium/third_party/catapult/tracing/tracing/metrics/blink/leak_detection_metric.html
new file mode 100644
index 00000000000..432b41d5d73
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/blink/leak_detection_metric.html
@@ -0,0 +1,86 @@
+<!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/unit.html">
+<link rel="import" href="/tracing/metrics/metric_registry.html">
+<link rel="import" href="/tracing/metrics/system_health/utils.html">
+<link rel="import" href="/tracing/value/histogram.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.metrics.blink', function() {
+ function leakDetectionMetric(histograms, model) {
+ // Extract renderer pids.
+ const modelHelper = model.getOrCreateHelper(
+ tr.model.helpers.ChromeModelHelper);
+ if (modelHelper === undefined) {
+ throw new Error('Chrome is not present.');
+ }
+ const rendererHelpers = modelHelper.rendererHelpers;
+ if (Object.keys(rendererHelpers).length === 0) {
+ throw new Error('Renderer process is not present.');
+ }
+ const pids = Object.keys(rendererHelpers);
+
+ // Get the dumps.
+ const chromeDumps = tr.metrics.sh
+ .splitGlobalDumpsByBrowserName(model, undefined).get('chrome');
+
+ const sumCounter = new Map();
+ // Add up counters for all the renderer processes.
+
+ for (const pid of pids) {
+ for (const [key, count] of countLeakedBlinkObjects(chromeDumps, pid)) {
+ sumCounter.set(key, (sumCounter.get(key) || 0) + count);
+ }
+ }
+
+ for (const [key, count] of sumCounter) {
+ histograms.createHistogram('Leaked ' + key,
+ tr.b.Unit.byName.count_smallerIsBetter, count);
+ }
+
+ for (const [key, count] of sumCounter) {
+ if (count > 0) {
+ throw new Error('Memory leak is found.');
+ }
+ }
+ }
+
+ tr.metrics.MetricRegistry.register(leakDetectionMetric);
+
+ function countLeakedBlinkObjects(dumps, pid) {
+ if (dumps === undefined || dumps.length < 2) {
+ throw new Error('Expected at least two memory dumps.');
+ }
+ const firstCounter = countBlinkObjects(dumps[0], pid);
+ const lastCounter = countBlinkObjects(dumps[dumps.length - 1], pid);
+ const diffCounter = new Map();
+ for (const [key, lastCount] of lastCounter) {
+ diffCounter.set(key, lastCount - firstCounter.get(key));
+ }
+ return diffCounter;
+ }
+
+ function countBlinkObjects(dump, pid) {
+ const counter = new Map();
+ const processesMemoryDumps = dump.processMemoryDumps;
+ if (processesMemoryDumps[pid] === undefined) return counter;
+ const blinkObjectsDump = processesMemoryDumps[pid].memoryAllocatorDumps
+ .find(dump => dump.fullName === 'blink_objects');
+ for (const v of blinkObjectsDump.children) {
+ counter.set(v.name, v.numerics.object_count.value);
+ }
+ return counter;
+ }
+
+ return {
+ leakDetectionMetric,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/blink/leak_detection_metric_test.html b/chromium/third_party/catapult/tracing/tracing/metrics/blink/leak_detection_metric_test.html
new file mode 100644
index 00000000000..f5b2e4add39
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/blink/leak_detection_metric_test.html
@@ -0,0 +1,179 @@
+<!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/metrics/blink/leak_detection_metric.html">
+<link rel="import" href="/tracing/model/memory_dump_test_utils.html">
+<link rel="import" href="/tracing/value/histogram_set.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const BLINK_OBJECT_LIST = ['AudioHandler', 'Document', 'Frame',
+ 'JSEventListener', 'LayoutObject', 'MediaKeys', 'MediaKeySession', 'Node',
+ 'Resource', 'ScriptPromise', 'PausableObject', 'V8PerContextData',
+ 'WorkerGlobalScope'];
+ const addProcessMemoryDump =
+ tr.model.MemoryDumpTestUtils.addProcessMemoryDump;
+ const addGlobalMemoryDump = tr.model.MemoryDumpTestUtils.addGlobalMemoryDump;
+ const newAllocatorDump = tr.model.MemoryDumpTestUtils.newAllocatorDump;
+ const allZeroArray = new Array(BLINK_OBJECT_LIST.length).fill(0);
+ const oneLeakArray = new Array(BLINK_OBJECT_LIST.length).fill(0);
+ oneLeakArray[1] = 1;
+ const multipleLeaksArray = new Array(BLINK_OBJECT_LIST.length).fill(1);
+
+ function createProcessWithName(model, name) {
+ const uniquePid =
+ Math.max.apply(null, Object.keys(model.processes).concat([0])) + 1;
+ const process = model.getOrCreateProcess(uniquePid);
+ process.name = name;
+ process.getOrCreateThread(1).name = 'Cr' + name + 'Main';
+ return process;
+ }
+
+ function createTimestamp(model, browser, rendererValuePairs, timestamp) {
+ const gmd1 = addGlobalMemoryDump(model, {ts: timestamp});
+ const pmdBrowser1 = addProcessMemoryDump(gmd1, browser, {ts: timestamp});
+ for (const pair of rendererValuePairs) {
+ addDumpsToRenderer(gmd1, pair.renderer, pair.values, timestamp);
+ }
+ }
+
+ function addDumpsToRenderer(gmd, renderer, values, timestamp) {
+ const pmdRenderer = addProcessMemoryDump(gmd, renderer, {ts: timestamp});
+ pmdRenderer.memoryAllocatorDumps = [
+ newAllocatorDump(pmdRenderer, 'blink_objects', { children:
+ createBlinkObjectCountList(pmdRenderer, values)})];
+ }
+
+ function getNumericLeakCount(histograms, index) {
+ return histograms.getHistogramNamed('Leaked ' +
+ BLINK_OBJECT_LIST[index]).statisticsScalars.get('sum').value;
+ }
+
+ function createBlinkObjectCountList(renderer, values) {
+ const blinkObjectCountList = [];
+ for (let i = 0; i < values.length; i++) {
+ blinkObjectCountList.push(newAllocatorDump(renderer,
+ 'blink_objects/' + BLINK_OBJECT_LIST[i], { numerics:
+ { object_count: values[i] }}));
+ }
+ return blinkObjectCountList;
+ }
+
+ test('testNoRenderer', function() {
+ const model = tr.c.TestUtils.newModel(function(model) {
+ createProcessWithName(model, 'Browser');
+ });
+ const histograms = new tr.v.HistogramSet();
+ assert.throws(
+ function() {tr.metrics.blink.leakDetectionMetric(histograms, model);},
+ 'Renderer process is not present.');
+ });
+
+ test('testZeroDump', function() {
+ const model = tr.c.TestUtils.newModel(function(model) {
+ createProcessWithName(model, 'Browser');
+ createProcessWithName(model, 'Renderer');
+ });
+ const histograms = new tr.v.HistogramSet();
+ assert.throws(
+ function() {tr.metrics.blink.leakDetectionMetric(histograms, model);},
+ 'Expected at least two memory dumps.');
+ });
+
+ test('testOnlyOneDump', function() {
+ const model = tr.c.TestUtils.newModel(function(model) {
+ const browser = createProcessWithName(model, 'Browser');
+ const renderer = createProcessWithName(model, 'Renderer');
+ const pair = [{renderer, values: allZeroArray}];
+ createTimestamp(model, browser, pair, 40);
+ });
+ const histograms = new tr.v.HistogramSet();
+ assert.throws(
+ function() {tr.metrics.blink.leakDetectionMetric(histograms, model);},
+ 'Expected at least two memory dumps.');
+ });
+
+ test('testNoLeak', function() {
+ const model = tr.c.TestUtils.newModel(function(model) {
+ const browser = createProcessWithName(model, 'Browser');
+ const renderer = createProcessWithName(model, 'Renderer');
+ const pair = [{renderer, 'values': allZeroArray}];
+ createTimestamp(model, browser, pair, 20);
+ createTimestamp(model, browser, pair, 40);
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.blink.leakDetectionMetric(histograms, model);
+ for (let i = 0; i < BLINK_OBJECT_LIST.length; i++) {
+ assert.strictEqual(getNumericLeakCount(histograms, i), 0);
+ }
+ });
+
+ test('testOneLeakDetection', function() {
+ const model = tr.c.TestUtils.newModel(function(model) {
+ const browser = createProcessWithName(model, 'Browser');
+ const renderer = createProcessWithName(model, 'Renderer');
+ const pair1 = [{renderer, 'values': allZeroArray}];
+ const pair2 = [{renderer, 'values': oneLeakArray}];
+ createTimestamp(model, browser, pair1, 20);
+ createTimestamp(model, browser, pair2, 40);
+ });
+ const histograms = new tr.v.HistogramSet();
+ assert.throws(
+ function() {tr.metrics.blink.leakDetectionMetric(histograms, model);},
+ 'Memory leak is found.');
+ for (let i = 0; i < BLINK_OBJECT_LIST.length; i++) {
+ if (i === 1) {
+ assert.strictEqual(getNumericLeakCount(histograms, i), 1);
+ } else {
+ assert.strictEqual(getNumericLeakCount(histograms, i), 0);
+ }
+ }
+ });
+
+ test('testMultipleLeakDetections', function() {
+ const model = tr.c.TestUtils.newModel(function(model) {
+ const browser = createProcessWithName(model, 'Browser');
+ const renderer = createProcessWithName(model, 'Renderer');
+ const pair1 = [{renderer, 'values': allZeroArray}];
+ const pair2 = [{renderer, 'values': multipleLeaksArray}];
+ createTimestamp(model, browser, pair1, 20);
+ createTimestamp(model, browser, pair2, 40);
+ });
+ const histograms = new tr.v.HistogramSet();
+ assert.throws(
+ function() {tr.metrics.blink.leakDetectionMetric(histograms, model);},
+ 'Memory leak is found.');
+ for (let i = 0; i < BLINK_OBJECT_LIST.length; i++) {
+ assert.strictEqual(getNumericLeakCount(histograms, i), 1);
+ }
+ });
+
+ test('testMultipleRendererWithLeaks', function() {
+ const model = tr.c.TestUtils.newModel(function(model) {
+ const browser = createProcessWithName(model, 'Browser');
+ const renderer1 = createProcessWithName(model, 'Renderer');
+ const renderer2 = createProcessWithName(model, 'Renderer');
+ const pair1 = [{'renderer': renderer1, 'values': allZeroArray},
+ {'renderer': renderer2, 'values': allZeroArray}];
+ const pair2 = [{'renderer': renderer1, 'values': multipleLeaksArray},
+ {'renderer': renderer2, 'values': multipleLeaksArray}];
+ createTimestamp(model, browser, pair1, 20);
+ createTimestamp(model, browser, pair2, 40);
+ });
+ const histograms = new tr.v.HistogramSet();
+ assert.throws(
+ function() {tr.metrics.blink.leakDetectionMetric(histograms, model);},
+ 'Memory leak is found.');
+ for (let i = 0; i < BLINK_OBJECT_LIST.length; i++) {
+ assert.strictEqual(getNumericLeakCount(histograms, i), 2);
+ }
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/buildbot_output_for_compare_samples_test.txt b/chromium/third_party/catapult/tracing/tracing/metrics/buildbot_output_for_compare_samples_test.txt
new file mode 100644
index 00000000000..a8d19745dae
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/buildbot_output_for_compare_samples_test.txt
@@ -0,0 +1,187 @@
+IMPORTANT DEBUGGING NOTE: batches of tests are run inside their
+own process. For debugging a test inside a debugger, use the
+--gtest_filter=<your_test_name> flag along with
+--single-process-tests.
+Using sharding settings from environment. This is shard 0/1
+Using 1 parallel jobs.
+Still waiting for the following processes to finish:
+ "C:\b\c\b\Win_7_ATI_GPU_Perf__2_\src\out\Release_x64\angle_perftests.exe" --gtest_flagfile="C:\Users\chrome-bot\AppData\Local\Temp\scoped_dir3192_23891\8D31.tmp" --single-process-tests --test-launcher-jobs=1 --test-launcher-output="C:\Users\CHROME~1\AppData\Local\Temp\3192_13273\test_results.xml" --test-launcher-print-test-stdio=always
+Still waiting for the following processes to finish:
+ "C:\b\c\b\Win_7_ATI_GPU_Perf__2_\src\out\Release_x64\angle_perftests.exe" --gtest_flagfile="C:\Users\chrome-bot\AppData\Local\Temp\scoped_dir3192_23891\8D31.tmp" --single-process-tests --test-launcher-jobs=1 --test-launcher-output="C:\Users\CHROME~1\AppData\Local\Temp\3192_13273\test_results.xml" --test-launcher-print-test-stdio=always
+Still waiting for the following processes to finish:
+ "C:\b\c\b\Win_7_ATI_GPU_Perf__2_\src\out\Release_x64\angle_perftests.exe" --gtest_flagfile="C:\Users\chrome-bot\AppData\Local\Temp\scoped_dir3192_23891\8D31.tmp" --single-process-tests --test-launcher-jobs=1 --test-launcher-output="C:\Users\CHROME~1\AppData\Local\Temp\3192_13273\test_results.xml" --test-launcher-print-test-stdio=always
+Still waiting for the following processes to finish:
+ "C:\b\c\b\Win_7_ATI_GPU_Perf__2_\src\out\Release_x64\angle_perftests.exe" --gtest_flagfile="C:\Users\chrome-bot\AppData\Local\Temp\scoped_dir3192_23891\8D31.tmp" --single-process-tests --test-launcher-jobs=1 --test-launcher-output="C:\Users\CHROME~1\AppData\Local\Temp\3192_13273\test_results.xml" --test-launcher-print-test-stdio=always
+Still waiting for the following processes to finish:
+ "C:\b\c\b\Win_7_ATI_GPU_Perf__2_\src\out\Release_x64\angle_perftests.exe" --gtest_flagfile="C:\Users\chrome-bot\AppData\Local\Temp\scoped_dir3192_23891\8D31.tmp" --single-process-tests --test-launcher-jobs=1 --test-launcher-output="C:\Users\CHROME~1\AppData\Local\Temp\3192_13273\test_results.xml" --test-launcher-print-test-stdio=always
+[ RUN ] IndexDataManagerPerfTest.Run
+*RESULT IndexDataManger_run: score= 281471 score
+[ OK ] IndexDataManagerPerfTest.Run (5008 ms)
+[1/37] IndexDataManagerPerfTest.Run (5008 ms)
+[ RUN ] BufferSubDataBenchmark.Run/d3d11_float4_every1
+*RESULT BufferSubData_d3d11_float4_every1: score= 232 score
+[ OK ] BufferSubDataBenchmark.Run/d3d11_float4_every1 (5117 ms)
+[2/37] BufferSubDataBenchmark.Run/d3d11_float4_every1 (5117 ms)
+[ RUN ] BufferSubDataBenchmark.Run/d3d9_float4_every1
+*RESULT BufferSubData_d3d9_float4_every1: score= 237 score
+[ OK ] BufferSubDataBenchmark.Run/d3d9_float4_every1 (5386 ms)
+[3/37] BufferSubDataBenchmark.Run/d3d9_float4_every1 (5386 ms)
+[ RUN ] BufferSubDataBenchmark.Run/gl_float4_every1
+*RESULT BufferSubData_gl_float4_every1: score= 245 score
+[ OK ] BufferSubDataBenchmark.Run/gl_float4_every1 (5195 ms)
+[4/37] BufferSubDataBenchmark.Run/gl_float4_every1 (5195 ms)
+[ RUN ] DrawCallPerfBenchmark.Run/d3d9
+*RESULT DrawCallPerf_d3d9: score= 2993 score
+[ OK ] DrawCallPerfBenchmark.Run/d3d9 (10062 ms)
+[5/37] DrawCallPerfBenchmark.Run/d3d9 (10062 ms)
+[ RUN ] DrawCallPerfBenchmark.Run/d3d9_null
+*RESULT DrawCallPerf_d3d9_null: score= 25046 score
+[ OK ] DrawCallPerfBenchmark.Run/d3d9_null (10047 ms)
+[6/37] DrawCallPerfBenchmark.Run/d3d9_null (10047 ms)
+[ RUN ] DrawCallPerfBenchmark.Run/d3d11
+*RESULT DrawCallPerf_d3d11: score= 2741 score
+[ OK ] DrawCallPerfBenchmark.Run/d3d11 (10015 ms)
+[7/37] DrawCallPerfBenchmark.Run/d3d11 (10015 ms)
+[ RUN ] DrawCallPerfBenchmark.Run/d3d11_null
+*RESULT DrawCallPerf_d3d11_null: score= 28607 score
+[ OK ] DrawCallPerfBenchmark.Run/d3d11_null (9999 ms)
+[8/37] DrawCallPerfBenchmark.Run/d3d11_null (9999 ms)
+[ RUN ] DrawCallPerfBenchmark.Run/d3d11_render_to_texture_null
+*RESULT DrawCallPerf_d3d11_render_to_texture_null: score= 25868 score
+[ OK ] DrawCallPerfBenchmark.Run/d3d11_render_to_texture_null (10015 ms)
+[9/37] DrawCallPerfBenchmark.Run/d3d11_render_to_texture_null (10015 ms)
+[ RUN ] DrawCallPerfBenchmark.Run/gl
+*RESULT DrawCallPerf_gl: score= 4123 score
+[ OK ] DrawCallPerfBenchmark.Run/gl (10031 ms)
+[10/37] DrawCallPerfBenchmark.Run/gl (10031 ms)
+Still waiting for the following processes to finish:
+ "C:\b\c\b\Win_7_ATI_GPU_Perf__2_\src\out\Release_x64\angle_perftests.exe" --gtest_flagfile="C:\Users\chrome-bot\AppData\Local\Temp\scoped_dir3192_23891\8D32.tmp" --single-process-tests --test-launcher-jobs=1 --test-launcher-output="C:\Users\CHROME~1\AppData\Local\Temp\3192_8457\test_results.xml" --test-launcher-print-test-stdio=always
+Still waiting for the following processes to finish:
+ "C:\b\c\b\Win_7_ATI_GPU_Perf__2_\src\out\Release_x64\angle_perftests.exe" --gtest_flagfile="C:\Users\chrome-bot\AppData\Local\Temp\scoped_dir3192_23891\8D32.tmp" --single-process-tests --test-launcher-jobs=1 --test-launcher-output="C:\Users\CHROME~1\AppData\Local\Temp\3192_8457\test_results.xml" --test-launcher-print-test-stdio=always
+Still waiting for the following processes to finish:
+ "C:\b\c\b\Win_7_ATI_GPU_Perf__2_\src\out\Release_x64\angle_perftests.exe" --gtest_flagfile="C:\Users\chrome-bot\AppData\Local\Temp\scoped_dir3192_23891\8D32.tmp" --single-process-tests --test-launcher-jobs=1 --test-launcher-output="C:\Users\CHROME~1\AppData\Local\Temp\3192_8457\test_results.xml" --test-launcher-print-test-stdio=always
+Still waiting for the following processes to finish:
+ "C:\b\c\b\Win_7_ATI_GPU_Perf__2_\src\out\Release_x64\angle_perftests.exe" --gtest_flagfile="C:\Users\chrome-bot\AppData\Local\Temp\scoped_dir3192_23891\8D32.tmp" --single-process-tests --test-launcher-jobs=1 --test-launcher-output="C:\Users\CHROME~1\AppData\Local\Temp\3192_8457\test_results.xml" --test-launcher-print-test-stdio=always
+[ RUN ] DrawCallPerfBenchmark.Run/gl_null
+*RESULT DrawCallPerf_gl_null: score= 189804 score
+[ OK ] DrawCallPerfBenchmark.Run/gl_null (10015 ms)
+[11/37] DrawCallPerfBenchmark.Run/gl_null (10015 ms)
+[ RUN ] DrawCallPerfBenchmark.Run/gl_render_to_texture_null
+*RESULT DrawCallPerf_gl_render_to_texture_null: score= 189155 score
+[ OK ] DrawCallPerfBenchmark.Run/gl_render_to_texture_null (10031 ms)
+[12/37] DrawCallPerfBenchmark.Run/gl_render_to_texture_null (10031 ms)
+[ RUN ] DrawCallPerfBenchmark.Run/default_validation_only
+*RESULT DrawCallPerf_default_validation_only: score= 2690 score
+[ OK ] DrawCallPerfBenchmark.Run/default_validation_only (5023 ms)
+[13/37] DrawCallPerfBenchmark.Run/default_validation_only (5023 ms)
+[ RUN ] DynamicPromotionPerfTest.Run/d3d11
+*RESULT DynamicPromotion_d3d11: score= 39354 score
+[ OK ] DynamicPromotionPerfTest.Run/d3d11 (6552 ms)
+[14/37] DynamicPromotionPerfTest.Run/d3d11 (6552 ms)
+[ RUN ] DynamicPromotionPerfTest.Run/d3d9
+*RESULT DynamicPromotion_d3d9: score= 21060 score
+[ OK ] DynamicPromotionPerfTest.Run/d3d9 (5522 ms)
+[15/37] DynamicPromotionPerfTest.Run/d3d9 (5522 ms)
+[ RUN ] EGLInitializePerfTest.Run/ES2_D3D11
+*RESULT EGLInitialize_run: score= 155 score
+*RESULT EGLInitialize_run: LoadDLLs= 0.0000000000 ms
+*RESULT EGLInitialize_run: D3D11CreateDevice= 4.0051480051 ms
+*RESULT EGLInitialize_run: InitResources= 0.0000000000 ms
+[ OK ] EGLInitializePerfTest.Run/ES2_D3D11 (5008 ms)
+[16/37] EGLInitializePerfTest.Run/ES2_D3D11 (5008 ms)
+[ RUN ] IndexConversionPerfTest.Run/d3d11
+*RESULT IndexConversionPerfTest_d3d11: score= 5135 score
+[ OK ] IndexConversionPerfTest.Run/d3d11 (3166 ms)
+[17/37] IndexConversionPerfTest.Run/d3d11 (3166 ms)
+[ RUN ] IndexConversionPerfTest.Run/index_range_d3d11
+*RESULT IndexConversionPerfTest_index_range_d3d11: score= 68785 score
+[ OK ] IndexConversionPerfTest.Run/index_range_d3d11 (3011 ms)
+[18/37] IndexConversionPerfTest.Run/index_range_d3d11 (3011 ms)
+[ RUN ] InstancingPerfBenchmark.Run/d3d11
+*RESULT InstancingPerf_d3d11: score= 479 score
+[ OK ] InstancingPerfBenchmark.Run/d3d11 (10046 ms)
+[19/37] InstancingPerfBenchmark.Run/d3d11 (10046 ms)
+[ RUN ] InstancingPerfBenchmark.Run/d3d9
+*RESULT InstancingPerf_d3d9: score= 464 score
+[ OK ] InstancingPerfBenchmark.Run/d3d9 (10057 ms)
+[20/37] InstancingPerfBenchmark.Run/d3d9 (10057 ms)
+Still waiting for the following processes to finish:
+ "C:\b\c\b\Win_7_ATI_GPU_Perf__2_\src\out\Release_x64\angle_perftests.exe" --gtest_flagfile="C:\Users\chrome-bot\AppData\Local\Temp\scoped_dir3192_23891\8D44.tmp" --single-process-tests --test-launcher-jobs=1 --test-launcher-output="C:\Users\CHROME~1\AppData\Local\Temp\3192_12336\test_results.xml" --test-launcher-print-test-stdio=always
+Still waiting for the following processes to finish:
+ "C:\b\c\b\Win_7_ATI_GPU_Perf__2_\src\out\Release_x64\angle_perftests.exe" --gtest_flagfile="C:\Users\chrome-bot\AppData\Local\Temp\scoped_dir3192_23891\8D44.tmp" --single-process-tests --test-launcher-jobs=1 --test-launcher-output="C:\Users\CHROME~1\AppData\Local\Temp\3192_12336\test_results.xml" --test-launcher-print-test-stdio=always
+Still waiting for the following processes to finish:
+ "C:\b\c\b\Win_7_ATI_GPU_Perf__2_\src\out\Release_x64\angle_perftests.exe" --gtest_flagfile="C:\Users\chrome-bot\AppData\Local\Temp\scoped_dir3192_23891\8D44.tmp" --single-process-tests --test-launcher-jobs=1 --test-launcher-output="C:\Users\CHROME~1\AppData\Local\Temp\3192_12336\test_results.xml" --test-launcher-print-test-stdio=always
+[ RUN ] InstancingPerfBenchmark.Run/gl
+*RESULT InstancingPerf_gl: score= 427 score
+[ OK ] InstancingPerfBenchmark.Run/gl (10124 ms)
+[21/37] InstancingPerfBenchmark.Run/gl (10124 ms)
+[ RUN ] InterleavedAttributeDataBenchmark.Run/d3d11
+*RESULT InterleavedAttributeData_d3d11: score= 19 score
+[ OK ] InterleavedAttributeDataBenchmark.Run/d3d11 (5117 ms)
+[22/37] InterleavedAttributeDataBenchmark.Run/d3d11 (5117 ms)
+[ RUN ] InterleavedAttributeDataBenchmark.Run/d3d11_9_3
+*RESULT InterleavedAttributeData_d3d11: score= 25 score
+[ OK ] InterleavedAttributeDataBenchmark.Run/d3d11_9_3 (5225 ms)
+[23/37] InterleavedAttributeDataBenchmark.Run/d3d11_9_3 (5225 ms)
+[ RUN ] InterleavedAttributeDataBenchmark.Run/d3d9
+*RESULT InterleavedAttributeData_d3d9: score= 25 score
+[ OK ] InterleavedAttributeDataBenchmark.Run/d3d9 (5211 ms)
+[24/37] InterleavedAttributeDataBenchmark.Run/d3d9 (5211 ms)
+[ RUN ] InterleavedAttributeDataBenchmark.Run/gl
+*RESULT InterleavedAttributeData_gl: score= 25 score
+[ OK ] InterleavedAttributeDataBenchmark.Run/gl (5101 ms)
+[25/37] InterleavedAttributeDataBenchmark.Run/gl (5101 ms)
+[ RUN ] PointSpritesBenchmark.Run/d3d11_10_3px_3vars
+*RESULT PointSprites_d3d11_10_3px_3vars: score= 644 score
+[ OK ] PointSpritesBenchmark.Run/d3d11_10_3px_3vars (5023 ms)
+[26/37] PointSpritesBenchmark.Run/d3d11_10_3px_3vars (5023 ms)
+[ RUN ] PointSpritesBenchmark.Run/d3d9_10_3px_3vars
+*RESULT PointSprites_d3d9_10_3px_3vars: score= 730 score
+[ OK ] PointSpritesBenchmark.Run/d3d9_10_3px_3vars (5141 ms)
+[27/37] PointSpritesBenchmark.Run/d3d9_10_3px_3vars (5141 ms)
+[ RUN ] PointSpritesBenchmark.Run/gl_10_3px_3vars
+*RESULT PointSprites_gl_10_3px_3vars: score= 2159 score
+[ OK ] PointSpritesBenchmark.Run/gl_10_3px_3vars (5086 ms)
+[28/37] PointSpritesBenchmark.Run/gl_10_3px_3vars (5086 ms)
+[ RUN ] TexSubImageBenchmark.Run/d3d11
+*RESULT TexSubImage_d3d11: score= 294 score
+[ OK ] TexSubImageBenchmark.Run/d3d11 (5023 ms)
+[29/37] TexSubImageBenchmark.Run/d3d11 (5023 ms)
+[ RUN ] TexSubImageBenchmark.Run/d3d9
+*RESULT TexSubImage_d3d9: score= 298 score
+[ OK ] TexSubImageBenchmark.Run/d3d9 (5305 ms)
+[30/37] TexSubImageBenchmark.Run/d3d9 (5305 ms)
+Still waiting for the following processes to finish:
+ "C:\b\c\b\Win_7_ATI_GPU_Perf__2_\src\out\Release_x64\angle_perftests.exe" --gtest_flagfile="C:\Users\chrome-bot\AppData\Local\Temp\scoped_dir3192_23891\8D45.tmp" --single-process-tests --test-launcher-jobs=1 --test-launcher-output="C:\Users\CHROME~1\AppData\Local\Temp\3192_4706\test_results.xml" --test-launcher-print-test-stdio=always
+Still waiting for the following processes to finish:
+ "C:\b\c\b\Win_7_ATI_GPU_Perf__2_\src\out\Release_x64\angle_perftests.exe" --gtest_flagfile="C:\Users\chrome-bot\AppData\Local\Temp\scoped_dir3192_23891\8D45.tmp" --single-process-tests --test-launcher-jobs=1 --test-launcher-output="C:\Users\CHROME~1\AppData\Local\Temp\3192_4706\test_results.xml" --test-launcher-print-test-stdio=always
+[ RUN ] TexSubImageBenchmark.Run/gl
+*RESULT TexSubImage_gl: score= 323 score
+[ OK ] TexSubImageBenchmark.Run/gl (5070 ms)
+[31/37] TexSubImageBenchmark.Run/gl (5070 ms)
+[ RUN ] TextureSamplingBenchmark.Run/d3d11_2samplers
+*RESULT TextureSampling_d3d11_2samplers: score= 128 score
+[ OK ] TextureSamplingBenchmark.Run/d3d11_2samplers (5070 ms)
+[32/37] TextureSamplingBenchmark.Run/d3d11_2samplers (5070 ms)
+[ RUN ] TextureSamplingBenchmark.Run/d3d9_2samplers
+*RESULT TextureSampling_d3d9_2samplers: score= 128 score
+[ OK ] TextureSamplingBenchmark.Run/d3d9_2samplers (5668 ms)
+[33/37] TextureSamplingBenchmark.Run/d3d9_2samplers (5668 ms)
+[ RUN ] TextureSamplingBenchmark.Run/gl_2samplers
+*RESULT TextureSampling_gl_2samplers: score= 136 score
+[ OK ] TextureSamplingBenchmark.Run/gl_2samplers (5054 ms)
+[34/37] TextureSamplingBenchmark.Run/gl_2samplers (5054 ms)
+[ RUN ] UniformsBenchmark.Run/d3d11_200_vertex_uniforms_200_fragment_uniforms
+*RESULT Uniforms_d3d11_200_vertex_uniforms_200_fragment_uniforms: score= 1797 score
+[ OK ] UniformsBenchmark.Run/d3d11_200_vertex_uniforms_200_fragment_uniforms (5195 ms)
+[35/37] UniformsBenchmark.Run/d3d11_200_vertex_uniforms_200_fragment_uniforms (5195 ms)
+[ RUN ] UniformsBenchmark.Run/d3d9_200_vertex_uniforms_200_fragment_uniforms
+*RESULT Uniforms_d3d9_200_vertex_uniforms_200_fragment_uniforms: score= 1912 score
+[ OK ] UniformsBenchmark.Run/d3d9_200_vertex_uniforms_200_fragment_uniforms (5223 ms)
+[36/37] UniformsBenchmark.Run/d3d9_200_vertex_uniforms_200_fragment_uniforms (5223 ms)
+[ RUN ] UniformsBenchmark.Run/gl_200_vertex_uniforms_200_fragment_uniforms
+*RESULT Uniforms_gl_200_vertex_uniforms_200_fragment_uniforms: score= 5509 score
+[ OK ] UniformsBenchmark.Run/gl_200_vertex_uniforms_200_fragment_uniforms (5070 ms)
+[37/37] UniformsBenchmark.Run/gl_200_vertex_uniforms_200_fragment_uniforms (5070 ms)
+SUCCESS: all tests passed.
+Tests took 243 seconds.
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/compare_samples.py b/chromium/third_party/catapult/tracing/tracing/metrics/compare_samples.py
new file mode 100644
index 00000000000..6fb10a34b6f
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/compare_samples.py
@@ -0,0 +1,54 @@
+# 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.
+
+import os
+
+import tracing_project
+import vinn
+
+FORMAT_TO_METHOD = {
+ 'chartjson': 'compareCharts',
+ 'buildbot': 'compareBuildbotOutputs'
+}
+
+_COMPARE_SAMPLES_CMD_LINE = os.path.join(
+ os.path.dirname(__file__), 'compare_samples_cmdline.html')
+
+
+def CompareSamples(sample_a, sample_b, metric, data_format='chartjson'):
+ """Compare the values of a metric from two samples from benchmark output.
+
+ Args:
+ sample_a, sample_b (str): comma-separated lists of paths to the benchmark
+ output.
+ metric (str): Metric name in slash-separated format [2 or 3 part].
+ data_format (str): The format the samples are in. Supported values are:
+ 'chartjson', 'valueset', 'buildbot'.
+ Returns:
+ JSON encoded dict with the values parsed form the samples and the result of
+ the hypothesis testing comparison of the samples under the 'result' key.
+ Possible values for the result key are:
+ 'NEED_MORE_DATA', 'REJECT' and 'FAIL_TO_REJECT'.
+ Where the null hypothesis is that the samples belong to the same population.
+ i.e. a 'REJECT' result would make it reasonable to conclude that
+ there is a significant difference between the samples. (e.g. a perf
+ regression).
+ """
+
+ method = FORMAT_TO_METHOD[data_format]
+ project = tracing_project.TracingProject()
+ all_source_paths = list(project.source_paths)
+
+ def MakeAbsPaths(l):
+ return ','.join(map(os.path.abspath, l.split(',')))
+
+ return vinn.RunFile(
+ _COMPARE_SAMPLES_CMD_LINE,
+ source_paths=all_source_paths,
+ js_args=[
+ method,
+ MakeAbsPaths(sample_a),
+ MakeAbsPaths(sample_b),
+ metric
+ ])
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/compare_samples_cmdline.html b/chromium/third_party/catapult/tracing/tracing/metrics/compare_samples_cmdline.html
new file mode 100644
index 00000000000..f774f03f1bd
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/compare_samples_cmdline.html
@@ -0,0 +1,225 @@
+<!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/math/statistics.html">
+<link rel="import" href="/tracing/base/utils.html">
+<link rel="import" href="/tracing/base/xhr.html">
+
+<script>
+'use strict';
+/* eslint-disable no-console */
+
+const escapeChars = s => s.replace(/[\:|=\/#&,]/g, '_');
+
+function findUnescapedKey(escaped, d) {
+ if (!d) {
+ return undefined;
+ }
+
+ for (const k of Object.keys(d)) {
+ if (escapeChars(k) === escapeChars(escaped)) {
+ return k;
+ }
+ }
+}
+
+function geoMeanFromHistogram(h) {
+ if (!h.hasOwnProperty('buckets')) return 0.0;
+ let count = 0;
+ let sumOfLogs = 0;
+ for (const bucket of h.buckets) {
+ if (bucket.hasOwnProperty('high')) {
+ bucket.mean = (bucket.low + bucket.high) / 2.0;
+ } else {
+ bucket.mean = bucket.low;
+ }
+
+ if (bucket.mean > 0) {
+ sumOfLogs += Math.log(bucket.mean) * bucket.count;
+ count += bucket.count;
+ }
+ }
+ if (count === 0) return 0.0;
+ return Math.exp(sumOfLogs / count);
+}
+
+function guessFullTIRMetricName(metricName) {
+ const parts = metricName.split('/');
+ if (parts.length === 2) {
+ return metricName + '/summary';
+ }
+ return undefined;
+}
+
+function splitMetric(metricName) {
+ const parts = metricName.split('/');
+ let interactionName;
+ let traceName = 'summary';
+ let chartName = parts[0];
+ if (parts.length === 3) {
+ // parts[1] is the interactionName
+ if (parts[1]) chartName = parts[1] + '@@' + chartName;
+ traceName = parts[2];
+ } else if (parts.length === 2) {
+ if (chartName !== parts[1]) traceName = parts[1];
+ } else {
+ throw new Error('Could not parse metric name.');
+ }
+ return [chartName, traceName];
+}
+
+function valuesFromCharts(listOfCharts, metricName) {
+ const allValues = [];
+ const chartAndTrace = splitMetric(metricName);
+ for (const charts of listOfCharts) {
+ const chartName = findUnescapedKey(chartAndTrace[0], charts.charts);
+ if (chartName) {
+ const traceName = findUnescapedKey(
+ chartAndTrace[1], charts.charts[chartName]);
+ if (traceName) {
+ if (charts.charts[chartName][traceName].type ===
+ 'list_of_scalar_values') {
+ if (charts.charts[chartName][traceName].values === null) continue;
+ allValues.push(tr.b.math.Statistics.mean(
+ charts.charts[chartName][traceName].values));
+ }
+ if (charts.charts[chartName][traceName].type === 'histogram') {
+ allValues.push(
+ geoMeanFromHistogram(charts.charts[chartName][traceName]));
+ }
+ if (charts.charts[chartName][traceName].type === 'scalar') {
+ allValues.push(charts.charts[chartName][traceName].value);
+ }
+ }
+ }
+ }
+ return allValues;
+}
+
+function valuesFromChartsWithFallback(listOfCharts, metricName) {
+ const allValues = valuesFromCharts(listOfCharts, metricName);
+ if (allValues.length > 0) return allValues;
+
+ // If this had a tir_label, the "summary" part may have been stripped by
+ // the dashboard during upload. We can re-add it here.
+ const fullMetricName = guessFullTIRMetricName(metricName);
+ if (!fullMetricName) return [];
+
+ return valuesFromCharts(listOfCharts, fullMetricName);
+}
+
+function parseFiles(files) {
+ const results = [];
+ for (const path of files) {
+ const current = tr.b.getSync('file://' + path);
+ results.push(JSON.parse(current));
+ }
+ return results;
+}
+
+const escapeForRegExp = s => s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
+
+const strFromRE = re => re.toString().split('/')[1];
+
+function valuesFromBuildbotOutput(out, metric) {
+ if (!out) return [];
+
+ let stringVals = [];
+ const floatVals = [];
+ const chartAndTrace = splitMetric(metric);
+ const metricRE = escapeForRegExp(
+ 'RESULT ' + chartAndTrace[0] + ': ' + chartAndTrace[1] + '=');
+ const singleResultRE = new RegExp(metricRE +
+ strFromRE(/\s*([-]?[\d\.]+)/), 'g');
+ const multiResultsRE = new RegExp(metricRE +
+ strFromRE(/\s*\[\s*([\d\., -]+)\s*\]/), 'g');
+ const meanStdDevRE = new RegExp(metricRE +
+ strFromRE(/\s*\{\s*([-]?\d*(?:\.\d*)?),\s*([-]?\d*(?:\.\d*)?)\}/), 'g');
+ for (const line of out.split(/\r?\n/)) {
+ const singleResultMatch = singleResultRE.exec(line);
+ const multiResultsMatch = multiResultsRE.exec(line);
+ const meanStdDevMatch = meanStdDevRE.exec(line);
+ if (singleResultMatch && singleResultMatch.length > 1) {
+ stringVals.push(singleResultMatch[1]);
+ } else if (multiResultsMatch && multiResultsMatch.length > 1) {
+ const values = multiResultsMatch[1].split(',');
+ stringVals = stringVals.concat(values);
+ } else if (meanStdDevMatch && meanStdDevMatch.length > 1) {
+ stringVals.push(meanStdDevMatch[1]);
+ }
+ }
+ for (const val of stringVals) {
+ const f = parseFloat(val);
+ if (!isNaN(f)) floatVals.push(f);
+ }
+ return floatVals;
+}
+
+function parseMultipleBuildbotStreams(files, metric) {
+ let allValues = [];
+ for (const path of files) {
+ let contents;
+ try {
+ contents = tr.b.getSync('file://' + path);
+ } catch (ex) {
+ const err = new Error('Could not open' + path);
+ err.name = 'File loading error';
+ throw err;
+ }
+ allValues = allValues.concat(valuesFromBuildbotOutput(contents, metric));
+ }
+ return allValues;
+}
+
+const buildComparisonResultOutput = function(a, b) {
+ let comparisonResult;
+ if (!a.length || !b.length) {
+ comparisonResult = {
+ significance: tr.b.math.Statistics.Significance.NEED_MORE_DATA
+ };
+ } else {
+ comparisonResult = tr.b.math.Statistics.mwu(
+ a, b, tr.b.math.Statistics.DEFAULT_ALPHA,
+ tr.b.math.Statistics.MAX_SUGGESTED_SAMPLE_SIZE).asDict();
+ }
+ return {
+ sampleA: a,
+ sampleB: b,
+ result: comparisonResult
+ };
+};
+
+const SampleComparison = {
+
+ compareBuildbotOutputs(
+ buildbotOutputAPathList, buildbotOutputBPathList, metric) {
+ const aPaths = buildbotOutputAPathList.split(',');
+ const bPaths = buildbotOutputBPathList.split(',');
+ const sampleA = parseMultipleBuildbotStreams(aPaths, metric);
+ const sampleB = parseMultipleBuildbotStreams(bPaths, metric);
+ return buildComparisonResultOutput(sampleA, sampleB);
+ },
+
+ compareCharts(chartPathListA, chartPathListB, metric) {
+ const aPaths = chartPathListA.split(',');
+ const bPaths = chartPathListB.split(',');
+ const chartsA = parseFiles(aPaths);
+ const chartsB = parseFiles(bPaths);
+ const sampleA = valuesFromChartsWithFallback(chartsA, metric);
+ const sampleB = valuesFromChartsWithFallback(chartsB, metric);
+ return buildComparisonResultOutput(sampleA, sampleB);
+ }
+
+};
+
+if (tr.isHeadless) {
+ const [method, ...rest] = sys.argv.slice(1);
+ if (SampleComparison[method]) {
+ console.log(JSON.stringify(SampleComparison[method](...rest)));
+ }
+}
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/compare_samples_unittest.py b/chromium/third_party/catapult/tracing/tracing/metrics/compare_samples_unittest.py
new file mode 100644
index 00000000000..df58e8ba8ce
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/compare_samples_unittest.py
@@ -0,0 +1,336 @@
+# 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.
+
+from __future__ import print_function
+
+import json
+import math
+import os
+import random
+import tempfile
+import unittest
+
+from tracing.metrics import compare_samples
+
+
+REJECT = 'REJECT'
+FAIL_TO_REJECT = 'FAIL_TO_REJECT'
+NEED_MORE_DATA = 'NEED_MORE_DATA'
+
+
+def Mean(l):
+ if len(l):
+ return float(sum(l))/len(l)
+ return 0
+
+
+class CompareSamplesUnittest(unittest.TestCase):
+ def setUp(self):
+ self._tempfiles = []
+ self._tempdir = tempfile.mkdtemp()
+
+ def tearDown(self):
+ for tf in self._tempfiles:
+ try:
+ os.remove(tf)
+ except OSError:
+ pass
+ try:
+ os.rmdir(self._tempdir)
+ except OSError:
+ pass
+
+ def NewJsonTempfile(self, jsonable_contents):
+ f_handle, new_json_file = tempfile.mkstemp(
+ suffix='.json',
+ dir=self._tempdir,
+ text=True)
+ os.close(f_handle)
+ self._tempfiles.append(new_json_file)
+ with open(new_json_file, 'w') as f:
+ json.dump(jsonable_contents, f)
+ return new_json_file
+
+ def MakeMultipleChartJSONHistograms(self, metric, seed, mu, sigma, n, m):
+ result = []
+ random.seed(seed)
+ for _ in range(m):
+ result.append(self.MakeChartJSONHistogram(metric, mu, sigma, n))
+ return result
+
+ def MakeChartJSONHistogram(self, metric, mu, sigma, n):
+ """Creates a histogram for a normally distributed pseudo-random sample.
+
+ This function creates a deterministic pseudo-random sample and stores it in
+ chartjson histogram format to facilitate the testing of the sample
+ comparison logic.
+
+ For simplicity we use sqrt(n) buckets with equal widths.
+
+ Args:
+ metric (str pair): name of chart, name of the trace.
+ seed (hashable obj): to make the sequences deterministic we seed the RNG.
+ mu (float): desired mean for the sample
+ sigma (float): desired standard deviation for the sample
+ n (int): number of values to generate.
+ """
+ chart_name, trace_name = metric
+ values = [random.gauss(mu, sigma) for _ in range(n)]
+ bucket_count = int(math.ceil(math.sqrt(len(values))))
+ width = (max(values) - min(values))/(bucket_count - 1)
+ prev_bucket = min(values)
+ buckets = []
+ for _ in range(bucket_count):
+ buckets.append({'low': prev_bucket,
+ 'high': prev_bucket + width,
+ 'count': 0})
+ prev_bucket += width
+ for value in values:
+ for bucket in buckets:
+ if value >= bucket['low'] and value < bucket['high']:
+ bucket['count'] += 1
+ break
+ charts = {
+ 'charts': {
+ chart_name: {
+ trace_name: {
+ 'type': 'histogram',
+ 'buckets': buckets
+ }
+ }
+ }
+ }
+ return self.NewJsonTempfile(charts)
+
+ def MakeChart(self, metric, seed, mu, sigma, n, keys=None):
+ """Creates a normally distributed pseudo-random sample. (continuous).
+
+ This function creates a deterministic pseudo-random sample and stores it in
+ chartjson format to facilitate the testing of the sample comparison logic.
+
+ Args:
+ metric (str pair): name of chart, name of the trace.
+ seed (hashable obj): to make the sequences deterministic we seed the RNG.
+ mu (float): desired mean for the sample
+ sigma (float): desired standard deviation for the sample
+ n (int): number of values to generate.
+ """
+ chart_name, trace_name = metric
+ random.seed(seed)
+ values = [random.gauss(mu, sigma) for _ in range(n)]
+ charts = {
+ 'charts': {
+ chart_name: {
+ trace_name: {
+ 'type': 'list_of_scalar_values',
+ 'values': values}
+ }
+ }
+ }
+ if keys:
+ grouping_keys = dict(enumerate(keys))
+ charts['charts'][chart_name][trace_name]['grouping_keys'] = grouping_keys
+ return self.NewJsonTempfile(charts)
+
+ def MakeNoneValuesChart(self, metric, keys=None):
+ """Creates a chart with merged None values.
+
+ Args:
+ metric (str pair): name of chart, name of the trace.
+ """
+ chart_name, trace_name = metric
+ charts = {
+ 'charts': {
+ chart_name: {
+ trace_name: {
+ 'type': 'list_of_scalar_values',
+ 'values': None
+ }
+ }
+ }
+ }
+ if keys:
+ grouping_keys = dict(enumerate(keys))
+ charts['charts'][chart_name][trace_name]['grouping_keys'] = grouping_keys
+ return self.NewJsonTempfile(charts)
+
+ def MakeCharts(self, metric, seed, mu, sigma, n, keys=None):
+ return [
+ self.MakeChartJSONScalar(metric, seed + '%d' % i, mu, sigma, keys)
+ for i in range(n)]
+
+ def MakeChartJSONScalar(self, metric, seed, mu, sigma, keys=None):
+ """Creates a normally distributed pseudo-random sample. (continuous).
+
+ This function creates a deterministic pseudo-random sample and stores it in
+ chartjson format to facilitate the testing of the sample comparison logic.
+
+ Args:
+ metric (str pair): name of chart, name of the trace.
+ seed (hashable obj): to make the sequences deterministic we seed the RNG.
+ mu (float): desired mean for the sample
+ sigma (float): desired standard deviation for the sample
+ """
+ chart_name, trace_name = metric
+ random.seed(seed)
+ charts = {
+ 'charts': {
+ chart_name: {
+ trace_name: {
+ 'type': 'scalar',
+ 'value': random.gauss(mu, sigma)}
+ }
+ }
+ }
+ if keys:
+ grouping_keys = dict(enumerate(keys))
+ charts['charts'][chart_name][trace_name]['grouping_keys'] = grouping_keys
+ return self.NewJsonTempfile(charts)
+
+ def testCompareClearRegressionListOfScalars(self):
+ metric = ('some_chart', 'some_trace')
+ lower_values = ','.join(self.MakeCharts(metric=metric, seed='lower',
+ mu=10, sigma=1, n=10))
+ higher_values = ','.join(self.MakeCharts(metric=metric, seed='higher',
+ mu=20, sigma=2, n=10))
+ result = json.loads(compare_samples.CompareSamples(
+ lower_values, higher_values, '/'.join(metric)).stdout)
+ self.assertEqual(result['result']['significance'], REJECT)
+
+ def testCompareListOfScalarsWithNoneValue(self):
+ metric = ('some_chart', 'some_trace')
+ lower_values = ','.join(self.MakeCharts(metric=metric, seed='lower',
+ mu=10, sigma=1, n=10))
+ lower_values += ',' + self.MakeNoneValuesChart(metric=metric)
+ higher_values = ','.join(self.MakeCharts(metric=metric, seed='higher',
+ mu=20, sigma=2, n=10))
+ result = json.loads(compare_samples.CompareSamples(
+ lower_values, higher_values, '/'.join(metric)).stdout)
+ self.assertEqual(result['result']['significance'], REJECT)
+
+ def testCompareClearRegressionScalars(self):
+ metric = ('some_chart', 'some_trace')
+ lower_values = ','.join(
+ [self.MakeChartJSONScalar(
+ metric=metric, seed='lower', mu=10, sigma=1) for _ in range(10)])
+ higher_values = ','.join(
+ [self.MakeChartJSONScalar(
+ metric=metric, seed='higher', mu=20, sigma=2) for _ in range(10)])
+ result = json.loads(compare_samples.CompareSamples(
+ lower_values, higher_values, '/'.join(metric)).stdout)
+ self.assertEqual(result['result']['significance'], REJECT)
+
+ def testCompareUnlikelyRegressionWithMultipleRuns(self):
+ metric = ('some_chart', 'some_trace')
+ lower_values = ','.join(
+ self.MakeCharts(
+ metric=metric, seed='lower', mu=10, sigma=1, n=20))
+ higher_values = ','.join(
+ self.MakeCharts(
+ metric=metric, seed='higher', mu=10.01, sigma=0.95, n=20))
+ result = json.loads(compare_samples.CompareSamples(
+ lower_values, higher_values, '/'.join(metric)).stdout)
+ self.assertEqual(result['result']['significance'], FAIL_TO_REJECT)
+
+ def testCompareTIRLabel(self):
+ tir_metric = ('some_chart', 'some_label', 'some_trace')
+ tir_metric_name = ('%s@@%s' % (tir_metric[1], tir_metric[0]), tir_metric[2])
+ lower_values = ','.join(self.MakeCharts(
+ metric=tir_metric_name, seed='lower', mu=10, sigma=1, n=10))
+ higher_values = ','.join(self.MakeCharts(
+ metric=tir_metric_name, seed='higher', mu=20, sigma=2, n=10))
+ result = json.loads(compare_samples.CompareSamples(
+ lower_values, higher_values, '/'.join(tir_metric)).stdout)
+ self.assertEqual(result['result']['significance'], REJECT)
+
+ def testCompareTIRLabelMissingSummary(self):
+ tir_metric = ('some_chart', 'some_label')
+ tir_metric_name = ('%s@@%s' % (tir_metric[1], tir_metric[0]), 'summary')
+ lower_values = ','.join(self.MakeCharts(
+ metric=tir_metric_name, seed='lower', mu=10, sigma=1, n=10))
+ higher_values = ','.join(self.MakeCharts(
+ metric=tir_metric_name, seed='higher', mu=20, sigma=2, n=10))
+ result = json.loads(compare_samples.CompareSamples(
+ lower_values, higher_values, '/'.join(tir_metric)).stdout)
+ self.assertEqual(result['result']['significance'], REJECT)
+
+ def testCompareInsufficientData(self):
+ metric = ('some_chart', 'some_trace')
+ lower_values = ','.join([self.MakeChart(metric=metric, seed='lower',
+ mu=10, sigma=1, n=5)])
+ higher_values = ','.join([self.MakeChart(metric=metric, seed='higher',
+ mu=10.40, sigma=0.95, n=5)])
+ result = json.loads(compare_samples.CompareSamples(
+ lower_values, higher_values, '/'.join(metric)).stdout)
+ self.assertEqual(result['result']['significance'], NEED_MORE_DATA)
+
+ def testCompareMissingFile(self):
+ metric = ('some_chart', 'some_trace')
+ lower_values = ','.join([self.MakeChart(metric=metric, seed='lower',
+ mu=10, sigma=1, n=5)])
+ higher_values = '/path/does/not/exist.json'
+ with self.assertRaises(RuntimeError):
+ compare_samples.CompareSamples(
+ lower_values, higher_values, '/'.join(metric))
+
+ def testCompareMissingMetric(self):
+ metric = ('some_chart', 'some_trace')
+ lower_values = ','.join([self.MakeChart(metric=metric, seed='lower',
+ mu=10, sigma=1, n=5)])
+ higher_values = ','.join([self.MakeChart(metric=metric, seed='higher',
+ mu=20, sigma=2, n=5)])
+ metric = ('some_chart', 'missing_trace')
+ result = json.loads(compare_samples.CompareSamples(
+ lower_values, higher_values, '/'.join(metric)).stdout)
+ self.assertEqual(result['result']['significance'], NEED_MORE_DATA)
+
+ def testCompareBadChart(self):
+ metric = ('some_chart', 'some_trace')
+ lower_values = ','.join([self.MakeChart(metric=metric, seed='lower',
+ mu=10, sigma=1, n=5)])
+ higher_values = self.NewJsonTempfile(['obviously', 'not', 'a', 'chart]'])
+ result = json.loads(compare_samples.CompareSamples(
+ lower_values, higher_values, '/'.join(metric)).stdout)
+ self.assertEqual(result['result']['significance'], NEED_MORE_DATA)
+
+ def testCompareBuildbotOutput(self):
+ bb = os.path.join(os.path.dirname(__file__),
+ 'buildbot_output_for_compare_samples_test.txt')
+ result = compare_samples.CompareSamples(
+ bb, bb, 'DrawCallPerf_gl/score',
+ data_format='buildbot')
+ result = json.loads(result.stdout)
+ self.assertEqual(result['result']['significance'], NEED_MORE_DATA)
+ self.assertEqual(Mean(result['sampleA']), 4123)
+ self.assertEqual(Mean(result['sampleB']), 4123)
+
+ def testCompareChartJsonHistogram(self):
+ metric = ('some_chart', 'some_trace')
+ lower_values = ','.join(self.MakeMultipleChartJSONHistograms(
+ metric=metric, seed='lower', mu=10, sigma=1, n=100, m=10))
+ higher_values = ','.join(self.MakeMultipleChartJSONHistograms(
+ metric=metric, seed='higher', mu=20, sigma=2, n=100, m=10))
+ result = json.loads(compare_samples.CompareSamples(
+ lower_values, higher_values, '/'.join(metric)).stdout)
+ self.assertEqual(result['result']['significance'], REJECT)
+
+ def testParseComplexMetricName(self):
+ full_metric_name = ('memory:chrome:all_processes:reported_by_os:'
+ 'system_memory:native_heap:'
+ 'proportional_resident_size_avg/blank_about/'
+ 'blank_about_blank')
+ chart_name = ('blank_about@@memory:chrome:all_processes:reported_by_os:'
+ 'system_memory:native_heap:proportional_resident_size_avg')
+ trace_name = 'blank:about:blank'
+ metric = chart_name, trace_name
+ keys = 'blank', 'about'
+ lower_values = ','.join(self.MakeCharts(metric=metric, seed='lower',
+ mu=10, sigma=1, n=10, keys=keys))
+ higher_values = ','.join(self.MakeCharts(metric=metric, seed='higher',
+ mu=20, sigma=2, n=10, keys=keys))
+ result = compare_samples.CompareSamples(
+ lower_values, higher_values, full_metric_name).stdout
+ print(result)
+ result = json.loads(result)
+ self.assertEqual(result['result']['significance'], REJECT)
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/console_error_metric.html b/chromium/third_party/catapult/tracing/tracing/metrics/console_error_metric.html
new file mode 100644
index 00000000000..5ed27d0f6be
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/console_error_metric.html
@@ -0,0 +1,72 @@
+<!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/unit.html">
+<link rel="import" href="/tracing/metrics/metric_registry.html">
+<link rel="import" href="/tracing/value/histogram.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.metrics.console', function() {
+ const COUNT_BOUNDARIES = tr.v.HistogramBinBoundaries.createExponential(
+ 1, 1e4, 30);
+ // We store a single value, so we only need one of the statistics to keep
+ // track. We choose the average for that.
+ const SUMMARY_OPTIONS = tr.v.Histogram.AVERAGE_ONLY_SUMMARY_OPTIONS;
+
+ // Sources of console error message that we are interested in.
+ const SOURCES = ['all', 'js', 'network'];
+
+ /**
+ * This metric counts slices named 'ConsoleErrorMessage' and adds:
+ * - console:error:all - all console error messages.
+ * - console:error:js - console error messages coming from JS.
+ * - console:error:network - console error messages coming from network.
+ * If a console error message does not come from JS or network, it is still
+ * accounted in the console_error_all.
+ */
+ function consoleErrorMetric(histograms, model) {
+ const counts = {};
+ for (const source of SOURCES) {
+ counts[source] = 0;
+ }
+ for (const slice of model.getDescendantEvents()) {
+ if (slice.category === 'blink.console' &&
+ slice.title === 'ConsoleMessage::Error') {
+ const source = slice.args.source.toLowerCase();
+ counts.all++;
+ if (source in counts) {
+ counts[source]++;
+ }
+ }
+ if (slice.category === 'v8.console' && (
+ slice.title === 'V8ConsoleMessage::Exception' ||
+ slice.title === 'V8ConsoleMessage::Error' ||
+ slice.title === 'V8ConsoleMessage::Assert')) {
+ counts.all++;
+ counts.js++;
+ }
+ }
+ for (const source of SOURCES) {
+ histograms.createHistogram(
+ `console:error:${source}`,
+ tr.b.Unit.byName.count_smallerIsBetter,
+ counts[source], {
+ description: `Number of ${source} console error messages`,
+ summaryOptions: SUMMARY_OPTIONS
+ });
+ }
+ }
+
+ tr.metrics.MetricRegistry.register(consoleErrorMetric);
+
+ return {
+ consoleErrorMetric,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/console_error_metric_unittest.html b/chromium/third_party/catapult/tracing/tracing/metrics/console_error_metric_unittest.html
new file mode 100644
index 00000000000..eff2917fc47
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/console_error_metric_unittest.html
@@ -0,0 +1,100 @@
+<!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/extras/importer/trace_event_importer.html">
+<link rel="import" href="/tracing/metrics/console_error_metric.html">
+<link rel="import" href="/tracing/value/histogram_set.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ function makeEvent(cat, name, source, timestamp) {
+ return {
+ cat,
+ name,
+ args: {source},
+ ts: timestamp,
+ pid: 52,
+ tid: 53,
+ ph: 'B'
+ };
+ }
+
+ test('consoleErrorMetric_noErrors', function() {
+ const histograms = new tr.v.HistogramSet();
+ const events = [
+ makeEvent('foo', '', 10),
+ makeEvent('bar', '', 20)
+ ];
+ tr.metrics.console.consoleErrorMetric(histograms,
+ tr.c.TestUtils.newModelWithEvents([events]));
+ const all = histograms.getHistogramNamed('console:error:all').running;
+ assert.strictEqual(all.count, 1);
+ assert.strictEqual(all.mean, 0);
+ const js = histograms.getHistogramNamed('console:error:js').running;
+ assert.strictEqual(js.count, 1);
+ assert.strictEqual(js.mean, 0);
+ const net = histograms.getHistogramNamed('console:error:network').running;
+ assert.strictEqual(net.count, 1);
+ assert.strictEqual(net.mean, 0);
+ });
+
+ test('consoleErrorMetric_Errors', function() {
+ const histograms = new tr.v.HistogramSet();
+ const events = [
+ makeEvent('blink.console', 'foo', '', 10),
+ makeEvent('blink.console', 'ConsoleMessage::Error', 'XML', 20),
+ makeEvent('blink.console', 'bar', '', 30),
+ makeEvent('blink.console', 'ConsoleMessage::Error', 'Network', 40),
+ makeEvent('blink.console', 'ConsoleMessage::Error', 'JS', 50),
+ makeEvent('blink.console', 'ConsoleMessage::Error', 'Network', 60),
+ makeEvent('blink.console', 'ConsoleMessage::Error', 'Network', 70),
+ makeEvent('blink.console', 'ConsoleMessage::Error', 'JS', 80),
+ makeEvent('blink.console', 'ConsoleMessage::Error', 'Network', 90),
+ makeEvent('bar', '', 300)
+ ];
+ tr.metrics.console.consoleErrorMetric(histograms,
+ tr.c.TestUtils.newModelWithEvents([events]));
+ const all = histograms.getHistogramNamed('console:error:all').running;
+ assert.strictEqual(all.count, 1);
+ assert.strictEqual(all.mean, 7);
+ const js = histograms.getHistogramNamed('console:error:js').running;
+ assert.strictEqual(js.count, 1);
+ assert.strictEqual(js.mean, 2);
+ const net = histograms.getHistogramNamed('console:error:network').running;
+ assert.strictEqual(net.count, 1);
+ assert.strictEqual(net.mean, 4);
+ });
+
+ test('consoleErrorMetric_V8', function() {
+ const histograms = new tr.v.HistogramSet();
+ const events = [
+ makeEvent('v8.console', 'foo', '', 10),
+ makeEvent('v8.console', 'V8ConsoleMessage::Error', '', 20),
+ makeEvent('v8.console', 'bar', '', 30),
+ makeEvent('v8.console', 'V8ConsoleMessage::Exception', '', 40),
+ makeEvent('v8.console', 'V8ConsoleMessage::Assert', '', 50),
+ makeEvent('v8.console', 'V8ConsoleMessage::Ignore', '', 60),
+ makeEvent('v8.console', 'V8ConsoleMessage::Ignore', '', 70),
+ makeEvent('v8.console', 'bar', '', 300)
+ ];
+ tr.metrics.console.consoleErrorMetric(histograms,
+ tr.c.TestUtils.newModelWithEvents([events]));
+ const all = histograms.getHistogramNamed('console:error:all').running;
+ assert.strictEqual(all.count, 1);
+ assert.strictEqual(all.mean, 3);
+ const js = histograms.getHistogramNamed('console:error:js').running;
+ assert.strictEqual(js.count, 1);
+ assert.strictEqual(js.mean, 3);
+ const net = histograms.getHistogramNamed('console:error:network').running;
+ assert.strictEqual(net.count, 1);
+ assert.strictEqual(net.mean, 0);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/cpu_process_metric.html b/chromium/third_party/catapult/tracing/tracing/metrics/cpu_process_metric.html
new file mode 100644
index 00000000000..c858a8bcb2d
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/cpu_process_metric.html
@@ -0,0 +1,92 @@
+<!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/metrics/metric_registry.html">
+<link rel="import" href="/tracing/value/histogram.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.metrics.sh', function() {
+ function getCpuSnapshotsFromModel(model) {
+ const snapshots = [];
+ for (const pid in model.processes) {
+ const snapshotInstances =
+ model.processes[pid].objects.getAllInstancesNamed('CPUSnapshots');
+ if (!snapshotInstances) continue;
+
+ for (const object of snapshotInstances[0].snapshots) {
+ snapshots.push(object.args.processes);
+ }
+ }
+ return snapshots;
+ }
+
+ function getProcessSumsFromSnapshot(snapshot) {
+ const processSums = new Map();
+ for (const processData of snapshot) {
+ const processName = processData.name;
+ if (!(processSums.has(processName))) {
+ processSums.set(processName, {sum: 0.0, paths: new Set()});
+ }
+ processSums.get(processName).sum += parseFloat(processData.pCpu);
+ // The process path may be missing on Windows because of AccessDenied
+ // error thrown by psutil package used by CPU tracing agent.
+ if (processData.path) {
+ processSums.get(processName).paths.add(processData.path);
+ }
+ }
+ return processSums;
+ }
+
+ function buildNumericsFromSnapshots(snapshots) {
+ const processNumerics = new Map();
+ for (const snapshot of snapshots) {
+ const processSums = getProcessSumsFromSnapshot(snapshot);
+ for (const [processName, processData] of processSums.entries()) {
+ if (!(processNumerics.has(processName))) {
+ processNumerics.set(processName, {
+ numeric: new tr.v.Histogram('cpu:percent:' + processName,
+ tr.b.Unit.byName.normalizedPercentage_smallerIsBetter),
+ paths: new Set()
+ });
+ }
+ processNumerics.get(processName).numeric.addSample(
+ processData.sum / 100.0);
+ for (const path of processData.paths) {
+ processNumerics.get(processName).paths.add(path);
+ }
+ }
+ }
+ return processNumerics;
+ }
+
+ function cpuProcessMetric(histograms, model) {
+ const snapshots = getCpuSnapshotsFromModel(model);
+ const processNumerics = buildNumericsFromSnapshots(snapshots);
+ for (const [processName, processData] of processNumerics) {
+ const numeric = processData.numeric;
+ // Treat missing snapshots as zeros. A process is missing from a snapshots
+ // when its CPU usage was below minimum threshold when the snapshot was
+ // taken.
+ const missingSnapshotCount = snapshots.length - numeric.numValues;
+ for (let i = 0; i < missingSnapshotCount; i++) {
+ numeric.addSample(0);
+ }
+ numeric.diagnostics.set('paths', new
+ tr.v.d.GenericSet([...processData.paths]));
+ histograms.addHistogram(numeric);
+ }
+ }
+
+ tr.metrics.MetricRegistry.register(cpuProcessMetric);
+
+ return {
+ cpuProcessMetric,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/cpu_process_metric_test.html b/chromium/third_party/catapult/tracing/tracing/metrics/cpu_process_metric_test.html
new file mode 100644
index 00000000000..55e404c5b4d
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/cpu_process_metric_test.html
@@ -0,0 +1,119 @@
+<!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/extras/importer/trace_event_importer.html">
+<link rel="import" href="/tracing/metrics/cpu_process_metric.html">
+<link rel="import" href="/tracing/value/histogram_set.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ function makeModel(events) {
+ return tr.c.TestUtils.newModelWithEvents([events]);
+ }
+
+ test('cpuProcessMetric_noData', function() {
+ const histograms = new tr.v.HistogramSet();
+ const events = [
+ {name: 'a', args: {}, pid: 52, ts: 524, cat: 'foo', tid: 53, ph: 'B'},
+ {name: 'a', args: {}, pid: 52, ts: 560, cat: 'foo', tid: 53, ph: 'B'}
+ ];
+ tr.metrics.sh.cpuProcessMetric(histograms, makeModel(events));
+ assert.lengthOf(histograms, 0);
+ });
+
+ test('cpuProcessMetric_singleSnapshots', function() {
+ const histograms = new tr.v.HistogramSet();
+ const events = [
+ {
+ 'name': 'CPUSnapshots',
+ 'args': {
+ 'snapshot': {
+ 'processes': [
+ {'path': '/usr/sbin/crudd', 'pCpu': '99.0', 'pid': '13495',
+ 'pMem': '0.0', 'name': 'crudd'},
+ {'path': '/opt/chrome/chrome', 'pCpu': '0.8',
+ 'pid': '29660', 'pMem': '0.9', 'name': 'chrome'},
+ {'path': '/opt/chrome/chrome', 'pCpu': '0.3',
+ 'pid': '29661', 'pMem': '0.9', 'name': 'chrome'}
+ ]
+ }
+ },
+ 'pid': 52, 'ts': '2226221225693.658', 'tid': 53, 'ph': 'O',
+ 'local': true, 'id': '0x1000'
+ }
+ ];
+ tr.metrics.sh.cpuProcessMetric(histograms, makeModel(events));
+
+ assert.isDefined(histograms.getHistogramNamed('cpu:percent:chrome'));
+ assert.isDefined(histograms.getHistogramNamed('cpu:percent:crudd'));
+ const chromeValue = histograms.getHistogramNamed('cpu:percent:chrome');
+ const chromeStatistics = chromeValue.running;
+ assert.strictEqual(chromeStatistics.count, 1);
+ assert.closeTo(chromeStatistics.mean, 0.011, 1e-5);
+ assert.closeTo(chromeStatistics.max, 0.011, 1e-5);
+ assert.instanceOf(chromeValue.diagnostics.get('paths'), tr.v.d.GenericSet);
+ const paths = tr.b.getOnlyElement(chromeValue.diagnostics.get('paths'));
+ assert.strictEqual(paths, '/opt/chrome/chrome');
+ });
+
+ test('cpuProcessMetric_multipleSnapshots', function() {
+ const histograms = new tr.v.HistogramSet();
+ const events = [
+ {
+ 'name': 'CPUSnapshots',
+ 'args': {
+ 'snapshot': {
+ 'processes': [
+ {'path': '/usr/sbin/crudd', 'pCpu': '99.0', 'pid': '13495',
+ 'pMem': '0.0', 'name': 'crudd'},
+ {'path': '/opt/chrome/chrome', 'pCpu': '0.8',
+ 'pid': '29660', 'pMem': '0.9', 'name': 'chrome'}
+ ]
+ }
+ },
+ 'pid': 52, 'ts': '2226221225693.658', 'tid': 53,
+ 'ph': 'O', 'local': true, 'id': '0x1000'
+ },
+ {
+ 'name': 'CPUSnapshots',
+ 'args': {
+ 'snapshot': {
+ 'processes': [
+ {'path': '/usr/sbin/crudd', 'pCpu': '1.3', 'pid': '13495',
+ 'pMem': '0.0', 'name': 'crudd'},
+ {'path': '/opt/chrome/chrome', 'pCpu': '0.6',
+ 'pid': '29660', 'pMem': '0.9', 'name': 'chrome'},
+ {'path': '/opt/chromium/chrome', 'pCpu': '0.1',
+ 'pid': '29660', 'pMem': '0.9', 'name': 'chrome'},
+ {'path': '/usr/sbin/mnp_logger', 'pCpu': '0.2', 'pid': '6543',
+ 'pMem': '0.1', 'name': 'mnp_logger'}
+ ]
+ }
+ },
+ 'pid': 52, 'ts': '2226222262064.4473', 'tid': 53,
+ 'ph': 'O', 'local': true, 'id': '0x1000'
+ }
+ ];
+ tr.metrics.sh.cpuProcessMetric(histograms, makeModel(events));
+ assert.isDefined(histograms.getHistogramNamed('cpu:percent:chrome'));
+ assert.isDefined(histograms.getHistogramNamed('cpu:percent:crudd'));
+ assert.isDefined(histograms.getHistogramNamed('cpu:percent:mnp_logger'));
+ const chromeValue = histograms.getHistogramNamed('cpu:percent:chrome');
+ const chromeStatistics = chromeValue.running;
+ assert.strictEqual(chromeStatistics.count, 2);
+ assert.closeTo(chromeStatistics.mean, 0.0075, 1e-5);
+ assert.strictEqual(chromeStatistics.max, 0.008);
+ assert.instanceOf(chromeValue.diagnostics.get('paths'), tr.v.d.GenericSet);
+ const paths = Array.from(chromeValue.diagnostics.get('paths'));
+ assert.lengthOf(paths, 2);
+ assert.strictEqual(paths[0], '/opt/chrome/chrome');
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/discover.py b/chromium/third_party/catapult/tracing/tracing/metrics/discover.py
new file mode 100644
index 00000000000..ef532c6d0df
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/discover.py
@@ -0,0 +1,34 @@
+# 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.
+
+import json
+import os
+
+import tracing_project
+import vinn
+
+
+_DISCOVER_CMD_LINE = os.path.join(
+ os.path.dirname(__file__), 'discover_cmdline.html')
+
+
+def DiscoverMetrics(modules_to_load):
+ """ Returns a list of registered metrics.
+
+ Args:
+ modules_to_load: a list of modules (string) to be loaded before discovering
+ the registered metrics.
+ """
+ assert isinstance(modules_to_load, list)
+ project = tracing_project.TracingProject()
+ all_source_paths = list(project.source_paths)
+
+ res = vinn.RunFile(
+ _DISCOVER_CMD_LINE, source_paths=all_source_paths,
+ js_args=modules_to_load)
+
+ if res.returncode != 0:
+ raise RuntimeError('Error running metrics_discover_cmdline: ' + res.stdout)
+ else:
+ return [str(m) for m in json.loads(res.stdout)]
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/discover_cmdline.html b/chromium/third_party/catapult/tracing/tracing/metrics/discover_cmdline.html
new file mode 100644
index 00000000000..c8aab5cd98d
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/discover_cmdline.html
@@ -0,0 +1,32 @@
+<!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/metrics/metric_registry.html">
+
+<script>
+'use strict';
+/* eslint-disable no-console */
+
+function discoverMetrics(args) {
+ for (let i = 0; i < args.length; i++) {
+ const filename = args[i];
+ HTMLImportsLoader.loadHTML(filename);
+ }
+
+ const metrics = tr.metrics.MetricRegistry.getAllRegisteredTypeInfos();
+ const discoveredMetricNames = [];
+ for (let i = 0; i < metrics.length; i++) {
+ discoveredMetricNames.push(metrics[i].constructor.name);
+ }
+ console.log(JSON.stringify(discoveredMetricNames));
+ return 0;
+}
+
+if (tr.isHeadless) {
+ quit(discoverMetrics(sys.argv.slice(1)));
+}
+
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/discover_unittest.py b/chromium/third_party/catapult/tracing/tracing/metrics/discover_unittest.py
new file mode 100644
index 00000000000..10b81b547c8
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/discover_unittest.py
@@ -0,0 +1,20 @@
+# 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.
+
+import unittest
+
+from tracing.metrics import discover
+
+class MetricsDiscoverUnittest(unittest.TestCase):
+ def testMetricsDiscoverEmpty(self):
+ self.assertFalse(discover.DiscoverMetrics([]))
+
+ def testMetricsDiscoverNonEmpty(self):
+ self.assertEquals(['sampleMetric'], discover.DiscoverMetrics(
+ ['/tracing/metrics/sample_metric.html']))
+
+ def testMetricsDiscoverMultipleMetrics(self):
+ self.assertGreater(
+ len(discover.DiscoverMetrics(
+ ['/tracing/metrics/all_metrics.html'])), 1)
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/media_metric.html b/chromium/third_party/catapult/tracing/tracing/metrics/media_metric.html
new file mode 100644
index 00000000000..2e1ec342a5e
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/media_metric.html
@@ -0,0 +1,395 @@
+<!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.
+-->
+
+<!--
+media_metric uses Chrome trace events to calculate metrics about video
+and audio playback. It is meant to be used for pages with <video> and/or
+<audio> elements. It is used by videostack-eng@google.com team for
+regression testing.
+
+See class PerPlaybackData for details on each of the values that are measured.
+
+This metric supports media playbacks happening simultaneously over multiple
+pages, supports multiple media elements on a page, and supports multiple
+playbacks with each element. It does not support media playback using
+flash or any other technology not provided by Chrome videostack team.
+
+Please inform crouleau@chromium.org and johnchen@chromium.org about
+changes to this file.
+-->
+
+<link rel="import" href="/tracing/metrics/metric_registry.html">
+<link rel="import" href="/tracing/model/helpers/chrome_model_helper.html">
+<link rel="import" href="/tracing/value/histogram.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.metrics', function() {
+ function mediaMetric(histograms, model) {
+ const chromeHelper = model.getOrCreateHelper(
+ tr.model.helpers.ChromeModelHelper);
+ if (chromeHelper === undefined) return;
+
+ for (const rendererHelper of Object.values(chromeHelper.rendererHelpers)) {
+ // Find the threads we're interested in, and if a needed thread
+ // is missing, no need to look further in this process.
+ const mainThread = rendererHelper.mainThread;
+ if (mainThread === undefined) continue;
+
+ const compositorThread = rendererHelper.compositorThread;
+ const audioThreads =
+ rendererHelper.process.findAllThreadsNamed('AudioOutputDevice');
+ if (compositorThread === undefined && audioThreads.length === 0) continue;
+
+ const processData = new PerProcessData();
+
+ processData.recordPlayStarts(mainThread);
+ if (!processData.hasPlaybacks) continue;
+
+ if (compositorThread !== undefined) {
+ processData.calculateTimeToVideoPlays(compositorThread);
+ processData.calculateDroppedFrameCounts(compositorThread);
+ }
+
+ if (audioThreads.length !== 0) {
+ processData.calculateTimeToAudioPlays(audioThreads);
+ }
+
+ processData.calculateSeekTimes(mainThread);
+ processData.calculateBufferingTimes(mainThread);
+
+ processData.addMetricToHistograms(histograms);
+ }
+ }
+
+ // PerProcessData manages all metric values associated with a renderer
+ // process. The process can have multiple media playbacks.
+ class PerProcessData {
+ constructor() {
+ // All the perf data we collect for a process are stored in a Map.
+ // Each key of the map is an ID of a media playback, and the value
+ // associated with each key is a PerPlaybackData object containing
+ // all the perf data for that playback.
+ this.playbackIdToDataMap_ = new Map();
+ }
+
+ recordPlayStarts(mainThread) {
+ for (const event of mainThread.sliceGroup.getDescendantEvents()) {
+ if (event.title === 'WebMediaPlayerImpl::DoLoad') {
+ const id = event.args.id;
+ if (this.playbackIdToDataMap_.has(id)) {
+ throw new Error(
+ 'Unexpected multiple initialization of a media playback');
+ }
+ this.playbackIdToDataMap_.set(id, new PerPlaybackData(event.start));
+ }
+ }
+ }
+
+ get hasPlaybacks() {
+ return this.playbackIdToDataMap_.size > 0;
+ }
+
+ calculateTimeToVideoPlays(compositorThread) {
+ for (const event of compositorThread.sliceGroup.getDescendantEvents()) {
+ if (event.title === 'VideoRendererImpl::Render') {
+ this.getPerPlaybackObject_(event.args.id)
+ .processVideoRenderTime(event.start);
+ }
+ }
+ }
+
+ calculateTimeToAudioPlays(audioThreads) {
+ for (const audioThread of audioThreads) {
+ for (const event of audioThread.sliceGroup.getDescendantEvents()) {
+ if (event.title === 'AudioRendererImpl::Render') {
+ this.getPerPlaybackObject_(event.args.id)
+ .processAudioRenderTime(event.start);
+ }
+ }
+ }
+ }
+
+ calculateSeekTimes(mainThread) {
+ for (const event of mainThread.sliceGroup.getDescendantEvents()) {
+ if (event.title === 'WebMediaPlayerImpl::DoSeek') {
+ this.getPerPlaybackObject_(event.args.id)
+ .processDoSeek(event.args.target, event.start);
+ } else if (event.title === 'WebMediaPlayerImpl::OnPipelineSeeked') {
+ this.getPerPlaybackObject_(event.args.id)
+ .processOnPipelineSeeked(event.args.target, event.start);
+ } else if (event.title === 'WebMediaPlayerImpl::BufferingHaveEnough') {
+ this.getPerPlaybackObject_(event.args.id)
+ .processBufferingHaveEnough(event.start);
+ }
+ }
+ }
+
+ calculateBufferingTimes(mainThread) {
+ for (const event of mainThread.sliceGroup.getDescendantEvents()) {
+ if (event.title === 'WebMediaPlayerImpl::OnEnded') {
+ this.getPerPlaybackObject_(event.args.id)
+ .processOnEnded(event.start, event.args.duration);
+ }
+ }
+ }
+
+ calculateDroppedFrameCounts(compositorThread) {
+ for (const event of compositorThread.sliceGroup.getDescendantEvents()) {
+ if (event.title === 'VideoFramesDropped') {
+ this.getPerPlaybackObject_(event.args.id)
+ .processVideoFramesDropped(event.args.count);
+ }
+ }
+ }
+
+ addMetricToHistograms(histograms) {
+ for (const [id, playbackData] of this.playbackIdToDataMap_) {
+ playbackData.addMetricToHistograms(histograms);
+ }
+ }
+
+ // @private
+ getPerPlaybackObject_(playbackId) {
+ let perPlaybackObject = this.playbackIdToDataMap_.get(playbackId);
+ if (perPlaybackObject === undefined) {
+ // The trace isn't complete, and didn't contain the DoLoad event for
+ // this playback. Create a new PerPlaybackData object for this playback.
+ perPlaybackObject = new PerPlaybackData(undefined);
+ this.playbackIdToDataMap_.set(playbackId, perPlaybackObject);
+ }
+ return perPlaybackObject;
+ }
+ }
+
+ // PerPlaybackData contains all metric values associated with a single
+ // media playback.
+ class PerPlaybackData {
+ constructor(playStartTime) {
+ this.playStart_ = playStartTime;
+ this.timeToVideoPlay_ = undefined;
+ this.timeToAudioPlay_ = undefined;
+ this.bufferingTime_ = undefined;
+ this.droppedFrameCount_ = 0;
+ this.seekError_ = false;
+ this.seekTimes_ = new Map();
+ this.currentSeek_ = undefined;
+ }
+
+ // API methods for retrieving metric values. Each method returns undefined
+ // if no value is available (e.g., timeToVideoPlay() returns undefined for
+ // an audio-only playback).
+
+ // Returns how long after a video is requested to start playing before
+ // the video actually starts. If time_to_video_play regresses, then users
+ // will click to play videos and then have to wait longer before the videos
+ // start actually playing.
+ get timeToVideoPlay() {
+ return this.timeToVideoPlay_;
+ }
+
+ // Similar to timeToVideoPlay, but measures the time delay before audio
+ // starts playing.
+ get timeToAudioPlay() {
+ return this.timeToAudioPlay_;
+ }
+
+ // Returns the difference between the actual play time of media vs its
+ // expected play time. Ideally the two should be the same. If actual play
+ // time is significantly longer than expected play time, it indicates that
+ // there were stalls during the play for buffering or some other reasons.
+ // Current limitation: Buffering time isn't calculated if seek occurred
+ // during playback, and it gives incorrect value if the playback isn't
+ // from beginning to end without pauses.
+ get bufferingTime() {
+ return this.bufferingTime_;
+ }
+
+ // Reports the number of video frames that were dropped. Ideally this
+ // should be 0. If a large number of frames are dropped, the video playback
+ // will not be smooth.
+ get droppedFrameCount() {
+ // We should report dropped frame count as long as video was played.
+ return (this.timeToVideoPlay_ !== undefined) ?
+ this.droppedFrameCount_ : undefined;
+ }
+
+ // Returns a Map containing seek times. The keys of the map are numerical
+ // values indicating the target location of the seek, in unit of seconds.
+ // The values of the map are objects with the following public properties:
+ // * pipelineSeekTime: amount of time taken by media pipeline to process
+ // this seek operation, from when the seek request is received, to when
+ // the pipeline starts processing at the new location, in milliseconds.
+ // * seekTime: how long after a user requests a seek operation before the
+ // seek completes and the media starts playing at the new location, as
+ // perceived by the user, in milliseconds.
+ get seekTimes() {
+ if (this.seekError_ || this.currentSeek_ !== undefined) return new Map();
+ return this.seekTimes_;
+ }
+
+ // API methods for processing data from trace events.
+
+ processVideoRenderTime(videoRenderTime) {
+ // Each video playback can generate multiple Render events, one for
+ // each frame. For calculating time to video play, we only use the
+ // first Render event.
+ if (this.playStart_ !== undefined &&
+ this.timeToVideoPlay_ === undefined) {
+ this.timeToVideoPlay_ = videoRenderTime - this.playStart_;
+ }
+ }
+
+ processAudioRenderTime(audioRenderTime) {
+ if (this.playStart_ !== undefined &&
+ this.timeToAudioPlay_ === undefined) {
+ this.timeToAudioPlay_ = audioRenderTime - this.playStart_;
+ }
+ }
+
+ processVideoFramesDropped(count) {
+ this.droppedFrameCount_ += count;
+ }
+
+ // We support multiple seeks per element, as long as they seek to different
+ // target time. Thus the seek times are stored in a Map instead of a scalar
+ // property. The key of the map is event.args.target, which is a numerical
+ // value indicating the target location of the seek, in unit of seconds.
+ // For example, with a seek to 5 seconds mark, event.args.target === 5.
+ // The value of the map is an object with 4 properties (the first two are
+ // added during object creation, the latter two are added as the data
+ // become available):
+ // * target: seek target time (same as the map key)
+ // * startTime: timestamp of the event marking start of seek
+ // * pipelineSeekTime: amount of time taken by media pipeline to process
+ // this seek (milliseconds)
+ // * seekTime: amount of seek time perceived by the user (milliseconds)
+ // If any unexpected conditions occur, we stop processing and set an error
+ // flag this.seekError_.
+ // TODO(https://github.com/catapult-project/catapult/issues/3976):
+ // Emit detailed warnings.
+ processDoSeek(target, startTime) {
+ // currentSeek_ refers to the object associated with the
+ // seek that is currently being processed for this media element.
+ // It is used to match seek end events against seek start events.
+ if (this.currentSeek_ !== undefined) {
+ // TODO(https://github.com/catapult-project/catapult/issues/3976):
+ // Warning 'Overlapping seek not supported'.
+ this.seekError_ = true;
+ return;
+ }
+ this.currentSeek_ = { target, startTime };
+ this.seekTimes_.set(target, this.currentSeek_);
+ }
+
+ processOnPipelineSeeked(target, time) {
+ if (this.seekError_) return;
+ const currentSeek = this.currentSeek_;
+ if (currentSeek === undefined) {
+ // OK to have this event when there is no active seek, as this event
+ // can be generated for other reasons, e.g., initial loading of media
+ // generates this event with target of 0 seconds.
+ return;
+ }
+ if (currentSeek.target !== target) {
+ // TODO(https://github.com/catapult-project/catapult/issues/3976):
+ // Warning 'WebMediaPlayerImpl::OnPipelineSeeked to unexpected target'.
+ this.seekError_ = true;
+ return;
+ }
+ if (currentSeek.pipelineSeekTime !== undefined) {
+ // TODO(https://github.com/catapult-project/catapult/issues/3976):
+ // Warning 'Multiple WebMediaPlayerImpl::OnPipelineSeeked events'.
+ this.seekError_ = true;
+ return;
+ }
+ currentSeek.pipelineSeekTime = time - currentSeek.startTime;
+ }
+
+ processBufferingHaveEnough(time) {
+ if (this.seekError_) return;
+ const currentSeek = this.currentSeek_;
+ if (currentSeek === undefined) {
+ // No current seek means this event is generated by non-seek related
+ // events, e.g., initial loading of media.
+ return;
+ }
+ if (currentSeek.pipelineSeekTime === undefined) {
+ // Since we haven't seen WebMediaPlayerImpl::OnPipelineSeeked event
+ // event yet, this event is triggered by something else, e.g., a
+ // have_nothing->have_enough cycle due to underflow from decoders.
+ return;
+ }
+ currentSeek.seekTime = time - currentSeek.startTime;
+ // Finished processing current seek.
+ this.currentSeek_ = undefined;
+ }
+
+ processOnEnded(playEndTime, duration) {
+ if (this.playStart_ === undefined) return;
+ // Can't calculate buffering time if there were any seeks.
+ if (this.seekTimes_.size !== 0 || this.seekError_) return;
+ // Play was resumed after it ended previously.
+ if (this.bufferingTime_ !== undefined) return;
+ // Convert duration from seconds to milliseconds.
+ duration = tr.b.convertUnit(duration, tr.b.UnitPrefixScale.METRIC.NONE,
+ tr.b.UnitPrefixScale.METRIC.MILLI);
+ const playTime = playEndTime - this.playStart_;
+ if (this.timeToVideoPlay_ !== undefined) {
+ this.bufferingTime_ = playTime - duration - this.timeToVideoPlay_;
+ } else if (this.timeToAudioPlay !== undefined) {
+ this.bufferingTime_ = playTime - duration - this.timeToAudioPlay_;
+ }
+ }
+
+ addMetricToHistograms(histograms) {
+ this.addSample_(histograms, 'time_to_video_play',
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,
+ this.timeToVideoPlay);
+ this.addSample_(histograms, 'time_to_audio_play',
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,
+ this.timeToAudioPlay);
+ this.addSample_(histograms, 'dropped_frame_count',
+ tr.b.Unit.byName.count_smallerIsBetter,
+ this.droppedFrameCount);
+ for (const [key, value] of this.seekTimes.entries()) {
+ // key is a numerical value that can have '.' when converted to
+ // string. However, '.' causes problems in histogram names, so
+ // replace with '_'.
+ const keyString = key.toString().replace('.', '_');
+ this.addSample_(histograms, 'pipeline_seek_time_' + keyString,
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,
+ value.pipelineSeekTime);
+ this.addSample_(histograms, 'seek_time_' + keyString,
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,
+ value.seekTime);
+ }
+ this.addSample_(histograms, 'buffering_time',
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,
+ this.bufferingTime);
+ }
+
+ // @private
+ addSample_(histograms, name, unit, sample) {
+ if (sample === undefined) return;
+ const histogram = histograms.getHistogramNamed(name);
+ if (histogram === undefined) {
+ histograms.createHistogram(name, unit, sample);
+ } else {
+ histogram.addSample(sample);
+ }
+ }
+ }
+
+ tr.metrics.MetricRegistry.register(mediaMetric);
+
+ return {
+ mediaMetric,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/media_metric_test.html b/chromium/third_party/catapult/tracing/tracing/metrics/media_metric_test.html
new file mode 100644
index 00000000000..a404b8e1980
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/media_metric_test.html
@@ -0,0 +1,423 @@
+<!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/extras/importer/trace_event_importer.html">
+<link rel="import" href="/tracing/metrics/media_metric.html">
+<link rel="import" href="/tracing/value/histogram_set.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ // Arbitrarily selected process ID and thread IDs we'll use in test data
+ const procId = 52;
+ const tidMain = 1;
+ const tidCompositor = 53;
+ const tidAudio = 55;
+
+ function doLoadEvent(timestamp, opt_id) {
+ return {name: 'WebMediaPlayerImpl::DoLoad', args: {id: opt_id || 0},
+ pid: procId, ts: timestamp, cat: 'media', tid: tidMain, ph: 'X'};
+ }
+
+ function videoRenderEvent(timestamp, opt_id) {
+ return {name: 'VideoRendererImpl::Render', args: {id: opt_id || 0},
+ pid: procId, ts: timestamp, cat: 'media', tid: tidCompositor, ph: 'X'};
+ }
+
+ function audioRenderEvent(timestamp, opt_id) {
+ return {name: 'AudioRendererImpl::Render', args: {id: opt_id || 0},
+ pid: procId, ts: timestamp, cat: 'media', tid: tidAudio, ph: 'X'};
+ }
+
+ function videoFramesDroppedEvent(timestamp, frameCount, opt_id) {
+ return {name: 'VideoFramesDropped',
+ args: {count: frameCount, id: opt_id || 0},
+ pid: procId, ts: timestamp, cat: 'media', tid: tidCompositor, ph: 'X'};
+ }
+
+ function onEndedEvent(timestamp, mediaDuration, opt_id) {
+ return {name: 'WebMediaPlayerImpl::OnEnded',
+ args: {duration: mediaDuration, id: opt_id || 0},
+ pid: procId, ts: timestamp, cat: 'media', tid: tidMain, ph: 'X'};
+ }
+
+ function doSeekEvent(timestamp, targetTime, opt_id) {
+ return {name: 'WebMediaPlayerImpl::DoSeek',
+ args: {target: targetTime, id: opt_id || 0},
+ pid: procId, ts: timestamp, cat: 'media', tid: tidMain, ph: 'X'};
+ }
+
+ function seekedEvent(timestamp, targetTime, opt_id) {
+ return {name: 'WebMediaPlayerImpl::OnPipelineSeeked',
+ args: {target: targetTime, id: opt_id || 0},
+ pid: procId, ts: timestamp, cat: 'media', tid: tidMain, ph: 'X'};
+ }
+
+ function bufferEnoughEvent(timestamp, opt_id) {
+ return {name: 'WebMediaPlayerImpl::BufferingHaveEnough',
+ args: {id: opt_id || 0},
+ pid: procId, ts: timestamp, cat: 'media', tid: tidMain, ph: 'X'};
+ }
+
+ function threadMarker(threadName, threadId) {
+ return {name: 'thread_name', args: {name: threadName},
+ pid: procId, ts: 0, cat: '__metadata', tid: threadId, ph: 'M'};
+ }
+
+ const mainThreadMarker = threadMarker('CrRendererMain', tidMain);
+ const compositorThreadMarker = threadMarker('Compositor', tidCompositor);
+ const audioThreadMarker = threadMarker('AudioOutputDevice', tidAudio);
+
+ function makeModel(events) {
+ return tr.c.TestUtils.newModelWithEvents([events]);
+ }
+
+ function checkCloseTo(histograms, histogramName, expectedValue) {
+ assert.isDefined(histograms.getHistogramNamed(histogramName));
+ const value = histograms.getHistogramNamed(histogramName);
+ const statistics = value.running;
+ assert.strictEqual(statistics.count, 1);
+ assert.closeTo(statistics.mean, expectedValue, 1e-5);
+ }
+
+ function checkCloseToMultiple(histograms, histogramName, expectedCount,
+ expectedMin, expectedMean, expectedMax) {
+ assert.isDefined(histograms.getHistogramNamed(histogramName));
+ const value = histograms.getHistogramNamed(histogramName);
+ const statistics = value.running;
+ assert.strictEqual(statistics.count, expectedCount);
+ assert.closeTo(statistics.min, expectedMin, 1e-5);
+ assert.closeTo(statistics.mean, expectedMean, 1e-5);
+ assert.closeTo(statistics.max, expectedMax, 1e-5);
+ }
+
+ function checkEqual(histograms, histogramName, expectedValue) {
+ assert.isDefined(histograms.getHistogramNamed(histogramName));
+ const value = histograms.getHistogramNamed(histogramName);
+ const statistics = value.running;
+ assert.strictEqual(statistics.count, 1);
+ assert.strictEqual(statistics.mean, expectedValue);
+ }
+
+ test('mediaMetric_noData', function() {
+ const histograms = new tr.v.HistogramSet();
+ const events = [];
+ tr.metrics.mediaMetric(histograms, makeModel(events));
+ assert.lengthOf(histograms, 0);
+ });
+
+ test('mediaMetric_videoTimeToPlay', function() {
+ const histograms = new tr.v.HistogramSet();
+ const events = [
+ doLoadEvent(100),
+ videoRenderEvent(300),
+ // Video renderer always generate multiple render events,
+ // one for each frame. For calculation of time-to-play,
+ // only the first render event is relevant. Here we put in
+ // a second render event to make sure it's ignored by the
+ // metric computation code.
+ videoRenderEvent(400),
+ mainThreadMarker,
+ compositorThreadMarker,
+ ];
+ tr.metrics.mediaMetric(histograms, makeModel(events));
+ checkCloseTo(histograms, 'time_to_video_play', 0.2);
+ });
+
+ test('mediaMetric_audioTimeToPlay', function() {
+ const histograms = new tr.v.HistogramSet();
+ const events = [
+ mainThreadMarker,
+ audioThreadMarker,
+ doLoadEvent(1000),
+ audioRenderEvent(1100),
+ ];
+ tr.metrics.mediaMetric(histograms, makeModel(events));
+ checkCloseTo(histograms, 'time_to_audio_play', 0.1);
+ });
+
+ test('mediaMetric_bufferingTimeVideo', function() {
+ const histograms = new tr.v.HistogramSet();
+ const events = [
+ doLoadEvent(1000),
+ videoRenderEvent(1500),
+ videoRenderEvent(1600),
+ onEndedEvent(10051500, 10),
+ mainThreadMarker,
+ compositorThreadMarker,
+ ];
+ tr.metrics.mediaMetric(histograms, makeModel(events));
+ checkCloseTo(histograms, 'buffering_time', 50);
+ });
+
+ test('mediaMetric_bufferingTimeAudio', function() {
+ const histograms = new tr.v.HistogramSet();
+ const events = [
+ mainThreadMarker,
+ audioThreadMarker,
+ doLoadEvent(1000),
+ audioRenderEvent(1500),
+ onEndedEvent(5002500, 5),
+ ];
+ tr.metrics.mediaMetric(histograms, makeModel(events));
+ checkCloseTo(histograms, 'buffering_time', 1);
+ });
+
+ // With seek, no buffering time should be reported
+ test('mediaMetric_noBufferingTime', function() {
+ const histograms = new tr.v.HistogramSet();
+ const events = [
+ doLoadEvent(1000),
+ videoRenderEvent(1500),
+ videoRenderEvent(1600),
+ onEndedEvent(10066666, 10),
+ doSeekEvent(525, 1.2),
+ seekedEvent(719, 1.2),
+ bufferEnoughEvent(825),
+ mainThreadMarker,
+ compositorThreadMarker,
+ ];
+ tr.metrics.mediaMetric(histograms, makeModel(events));
+ assert.isUndefined(histograms.getHistogramNamed('buffering_time'));
+ });
+
+ test('mediaMetric_droppedFrameCount', function() {
+ const histograms = new tr.v.HistogramSet();
+ const events = [
+ doLoadEvent(1000),
+ videoRenderEvent(1500),
+ videoFramesDroppedEvent(123456, 3),
+ videoFramesDroppedEvent(234567, 6),
+ videoFramesDroppedEvent(345678, 1),
+ mainThreadMarker,
+ compositorThreadMarker,
+ ];
+ tr.metrics.mediaMetric(histograms, makeModel(events));
+ checkEqual(histograms, 'dropped_frame_count', 10);
+ });
+
+ test('mediaMetric_droppedFrameCountZero', function() {
+ const histograms = new tr.v.HistogramSet();
+ const events = [
+ doLoadEvent(1000),
+ videoRenderEvent(1500),
+ mainThreadMarker,
+ compositorThreadMarker,
+ ];
+ tr.metrics.mediaMetric(histograms, makeModel(events));
+ checkEqual(histograms, 'dropped_frame_count', 0);
+ });
+
+ test('mediaMetric_droppedFrameCountNoVideo', function() {
+ const histograms = new tr.v.HistogramSet();
+ const events = [
+ doLoadEvent(1000),
+ audioRenderEvent(1500),
+ mainThreadMarker,
+ audioThreadMarker,
+ ];
+ tr.metrics.mediaMetric(histograms, makeModel(events));
+ assert.isUndefined(histograms.getHistogramNamed('dropped_frame_count'));
+ });
+
+ test('mediaMetric_seekTimeVideo', function() {
+ const histograms = new tr.v.HistogramSet();
+ const events = [
+ doLoadEvent(1000),
+ videoRenderEvent(1500),
+ doSeekEvent(2000, 1.2),
+ seekedEvent(2500, 1.2),
+ bufferEnoughEvent(3000),
+ doSeekEvent(15000, 3.7),
+ seekedEvent(75000, 3.7),
+ bufferEnoughEvent(95000),
+ mainThreadMarker,
+ compositorThreadMarker,
+ ];
+ tr.metrics.mediaMetric(histograms, makeModel(events));
+ checkCloseTo(histograms, 'pipeline_seek_time_1_2', 0.5);
+ checkCloseTo(histograms, 'seek_time_1_2', 1);
+ checkCloseTo(histograms, 'pipeline_seek_time_3_7', 60);
+ checkCloseTo(histograms, 'seek_time_3_7', 80);
+ });
+
+ test('mediaMetric_seekTimeAudio', function() {
+ const histograms = new tr.v.HistogramSet();
+ const events = [
+ doLoadEvent(1000),
+ audioRenderEvent(1500),
+ doSeekEvent(2000, 1.2),
+ seekedEvent(2500, 1.2),
+ bufferEnoughEvent(3000),
+ doSeekEvent(15000, 3.7),
+ seekedEvent(75000, 3.7),
+ bufferEnoughEvent(95000),
+ mainThreadMarker,
+ audioThreadMarker,
+ ];
+ tr.metrics.mediaMetric(histograms, makeModel(events));
+ checkCloseTo(histograms, 'pipeline_seek_time_1_2', 0.5);
+ checkCloseTo(histograms, 'seek_time_1_2', 1);
+ checkCloseTo(histograms, 'pipeline_seek_time_3_7', 60);
+ checkCloseTo(histograms, 'seek_time_3_7', 80);
+ });
+
+ test('mediaMetric_seekOverlap', function() {
+ const histograms = new tr.v.HistogramSet();
+ const events = [
+ doLoadEvent(1000),
+ videoRenderEvent(1500),
+ doSeekEvent(2000, 1.2),
+ seekedEvent(2500, 1.2),
+ doSeekEvent(2800, 3.7), // Out of order
+ bufferEnoughEvent(3000),
+ seekedEvent(75000, 3.7),
+ bufferEnoughEvent(95000),
+ mainThreadMarker,
+ compositorThreadMarker,
+ ];
+ tr.metrics.mediaMetric(histograms, makeModel(events));
+ assert.isDefined(histograms.getHistogramNamed('time_to_video_play'));
+ assert.isUndefined(histograms.getHistogramNamed('seek_time_1_2'));
+ });
+
+ test('mediaMetric_seekIncomplete', function() {
+ const histograms = new tr.v.HistogramSet();
+ const events = [
+ doLoadEvent(1000),
+ videoRenderEvent(1500),
+ doSeekEvent(2000, 1.2),
+ seekedEvent(2500, 1.2),
+ // Missing bufferEnoughEvent
+ mainThreadMarker,
+ compositorThreadMarker,
+ ];
+ tr.metrics.mediaMetric(histograms, makeModel(events));
+ assert.isDefined(histograms.getHistogramNamed('time_to_video_play'));
+ assert.isUndefined(histograms.getHistogramNamed('seek_time_1_2'));
+ });
+
+ test('mediaMetric_seekWrongTarget', function() {
+ const histograms = new tr.v.HistogramSet();
+ const events = [
+ doLoadEvent(1000),
+ videoRenderEvent(1500),
+ doSeekEvent(2000, 1.2),
+ seekedEvent(2500, 2.7), // Wrong target, should be 1.2
+ mainThreadMarker,
+ compositorThreadMarker,
+ ];
+ tr.metrics.mediaMetric(histograms, makeModel(events));
+ assert.isDefined(histograms.getHistogramNamed('time_to_video_play'));
+ assert.isUndefined(histograms.getHistogramNamed('seek_time_1_2'));
+ });
+
+ test('mediaMetric_multiplePlaybacks', function() {
+ const histograms = new tr.v.HistogramSet();
+ const events = [
+ doLoadEvent(1000, 0),
+ doLoadEvent(1100, 1),
+ videoRenderEvent(1400, 1),
+ videoRenderEvent(1500, 0),
+ doLoadEvent(2000, 2),
+ doSeekEvent(2000, 1.2, 0),
+ doSeekEvent(2100, 1.2, 1),
+ videoRenderEvent(2100, 2),
+ doSeekEvent(2200, 1.2, 2),
+ seekedEvent(2500, 1.2, 2),
+ seekedEvent(2600, 1.2, 1),
+ seekedEvent(2700, 1.2, 0),
+ bufferEnoughEvent(3000, 0),
+ bufferEnoughEvent(3100, 1),
+ bufferEnoughEvent(3800, 2),
+ mainThreadMarker,
+ compositorThreadMarker,
+ ];
+ tr.metrics.mediaMetric(histograms, makeModel(events));
+ // Values are 0.5, 0.3, 0.1, with min 0.1, mean 0.3, max 0.5.
+ checkCloseToMultiple(histograms, 'time_to_video_play', 3, 0.1, 0.3, 0.5);
+ // Values are 0.7, 0.5, 0.3, with min 0.3, mean 0.5, max 0.7.
+ checkCloseToMultiple(histograms,
+ 'pipeline_seek_time_1_2', 3, 0.3, 0.5, 0.7);
+ // Values are 1, 1, 1.6, with min 1, mean 1.2, max 1.6
+ checkCloseToMultiple(histograms, 'seek_time_1_2', 3, 1, 1.2, 1.6);
+ });
+
+ // Scenario: Play mixed audio/video from start to finish
+ test('mediaMetric_playVideoScenario', function() {
+ const histograms = new tr.v.HistogramSet();
+ const events = [
+ doLoadEvent(2000),
+ videoRenderEvent(3000),
+ audioRenderEvent(3200),
+ videoRenderEvent(3300),
+ videoFramesDroppedEvent(123456, 4),
+ videoFramesDroppedEvent(234567, 2),
+ onEndedEvent(10013000, 10),
+ mainThreadMarker,
+ compositorThreadMarker,
+ audioThreadMarker,
+ ];
+ tr.metrics.mediaMetric(histograms, makeModel(events));
+ checkCloseTo(histograms, 'time_to_video_play', 1);
+ checkCloseTo(histograms, 'time_to_audio_play', 1.2);
+ checkCloseTo(histograms, 'buffering_time', 10);
+ checkEqual(histograms, 'dropped_frame_count', 6);
+ });
+
+ // Scenario: Play audio from start to finish
+ test('mediaMetric_playAudioScenario', function() {
+ const histograms = new tr.v.HistogramSet();
+ const events = [
+ doLoadEvent(1000),
+ audioRenderEvent(1500),
+ onEndedEvent(10002500, 10),
+ mainThreadMarker,
+ audioThreadMarker,
+ ];
+ tr.metrics.mediaMetric(histograms, makeModel(events));
+ assert.isUndefined(histograms.getHistogramNamed('time_to_video_play'));
+ checkCloseTo(histograms, 'time_to_audio_play', 0.5);
+ checkCloseTo(histograms, 'buffering_time', 1);
+ assert.isUndefined(histograms.getHistogramNamed('dropped_frame_count'));
+ });
+
+ // Scenario: Play audio/video with two seeks
+ test('mediaMetric_seekScenario', function() {
+ const histograms = new tr.v.HistogramSet();
+ const events = [
+ doLoadEvent(1000),
+ videoRenderEvent(2000),
+ audioRenderEvent(2020),
+ videoRenderEvent(2040),
+ doSeekEvent(5000, 0.5),
+ seekedEvent(5200, 0.5),
+ bufferEnoughEvent(6000),
+ videoFramesDroppedEvent(123456, 4),
+ doSeekEvent(200000, 9),
+ seekedEvent(210000, 9),
+ bufferEnoughEvent(220000),
+ videoFramesDroppedEvent(234567, 2),
+ onEndedEvent(300000, 10),
+ mainThreadMarker,
+ compositorThreadMarker,
+ audioThreadMarker,
+ ];
+ tr.metrics.mediaMetric(histograms, makeModel(events));
+ checkCloseTo(histograms, 'time_to_video_play', 1);
+ checkCloseTo(histograms, 'time_to_audio_play', 1.02);
+ assert.isUndefined(histograms.getHistogramNamed('buffering_time'));
+ checkEqual(histograms, 'dropped_frame_count', 6);
+ checkCloseTo(histograms, 'pipeline_seek_time_0_5', 0.2);
+ checkCloseTo(histograms, 'seek_time_0_5', 1);
+ checkCloseTo(histograms, 'pipeline_seek_time_9', 10);
+ checkCloseTo(histograms, 'seek_time_9', 20);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/metric_map_function.html b/chromium/third_party/catapult/tracing/tracing/metrics/metric_map_function.html
new file mode 100644
index 00000000000..1b9ac12e1cc
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/metric_map_function.html
@@ -0,0 +1,216 @@
+<!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/metrics/all_metrics.html">
+<link rel="import" href="/tracing/metrics/metric_registry.html">
+<link rel="import" href="/tracing/mre/failure.html">
+<link rel="import" href="/tracing/mre/function_handle.html">
+<link rel="import" href="/tracing/value/diagnostics/reserved_names.html">
+<link rel="import" href="/tracing/value/histogram_set.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.metrics', function() {
+ /**
+ * @param {!tr.model.Model} model
+ * @param {!Object} options
+ * @param {!Array.<string>} options.metrics
+ * @param {!Function} addFailureCb
+ * @return {!tr.v.HistogramSet}
+ */
+ function runMetrics(model, options, addFailureCb) {
+ if (options === undefined) {
+ throw new Error('Options are required.');
+ }
+
+ const metricNames = options.metrics;
+ if (!metricNames) {
+ throw new Error('Metric names should be specified.');
+ }
+
+ const allMetricsStart = new Date();
+ const durationBreakdown = new tr.v.d.Breakdown();
+
+ const categories = getTraceCategories(model);
+
+ const histograms = new tr.v.HistogramSet();
+
+ histograms.createHistogram('trace_import_duration',
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,
+ model.stats.traceImportDurationMs, {
+ binBoundaries: tr.v.HistogramBinBoundaries.createExponential(
+ 1e-3, 1e5, 30),
+ description:
+ 'Duration that trace viewer required to import the trace',
+ summaryOptions: tr.v.Histogram.AVERAGE_ONLY_SUMMARY_OPTIONS,
+ });
+
+ for (const metricName of metricNames) {
+ const metricStart = new Date();
+ const metric = tr.metrics.MetricRegistry.findTypeInfoWithName(metricName);
+ if (metric === undefined) {
+ throw new Error(`"${metricName}" is not a registered metric.`);
+ }
+ validateTraceCategories(metric.metadata.requiredCategories, categories);
+ try {
+ metric.constructor(histograms, model, options);
+ } catch (e) {
+ const err = tr.b.normalizeException(e);
+ addFailureCb(new tr.mre.Failure(
+ undefined, 'metricMapFunction', model.canonicalUrl, err.typeName,
+ err.message, err.stack));
+ }
+ const metricMs = new Date() - metricStart;
+ histograms.createHistogram(
+ metricName + '_duration',
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,
+ [metricMs]);
+ durationBreakdown.set(metricName, metricMs);
+ }
+
+ validateDiagnosticNames(histograms);
+
+ const allMetricsMs = new Date() - allMetricsStart +
+ model.stats.traceImportDurationMs;
+ durationBreakdown.set('traceImport', model.stats.traceImportDurationMs);
+ durationBreakdown.set('other', allMetricsMs - tr.b.math.Statistics.sum(
+ durationBreakdown, ([metricName, metricMs]) => metricMs));
+ const breakdownNames = tr.v.d.RelatedNameMap.fromEntries(new Map(
+ metricNames.map(metricName => [metricName, metricName + '_duration'])));
+ breakdownNames.set('traceImport', 'trace_import_duration');
+ histograms.createHistogram(
+ 'metrics_duration',
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,
+ [
+ {
+ value: allMetricsMs,
+ diagnostics: {breakdown: durationBreakdown},
+ },
+ ], {
+ diagnostics: {breakdown: breakdownNames},
+ });
+
+ return histograms;
+ }
+
+ function getTraceCategories(model) {
+ for (const metadata of model.metadata) {
+ let config;
+ if (metadata.name === 'TraceConfig' && metadata.value) {
+ config = metadata.value;
+ }
+ if (metadata.name === 'metadata' && metadata.value &&
+ metadata.value['trace-config'] &&
+ metadata.value['trace-config'] !== '__stripped__') {
+ config = JSON.parse(metadata.value['trace-config']);
+ }
+ if (config) {
+ return {
+ excluded: config.excluded_categories || [],
+ included: config.included_categories || [],
+ };
+ }
+ }
+ }
+
+ function validateTraceCategories(requiredCategories, categories) {
+ if (!requiredCategories) return;
+
+ if (!categories) throw new Error('Missing trace config metadata');
+
+ for (const cat of requiredCategories) {
+ const isDisabledByDefault = (cat.indexOf('disabled-by-default') === 0);
+ let missing = false;
+ if (isDisabledByDefault) {
+ if (!categories.included.includes(cat)) {
+ missing = true;
+ }
+ } else if (categories.excluded.includes(cat)) {
+ missing = true;
+ }
+ if (missing) {
+ throw new Error(`Trace is missing required category "${cat}"`);
+ }
+ }
+ }
+
+ /**
+ * Ensure that metrics don't use reserved diagnostic names.
+ *
+ * @param {!tr.v.HistogramSet} histograms
+ */
+ function validateDiagnosticNames(histograms) {
+ for (const hist of histograms) {
+ for (const name of hist.diagnostics.keys()) {
+ if (tr.v.d.RESERVED_NAMES_SET.has(name)) {
+ throw new Error(
+ `Illegal diagnostic name "${name}" on Histogram "${hist.name}"`);
+ }
+ }
+ }
+ }
+
+ /**
+ * @param {!tr.v.HistogramSet} histograms
+ * @param {!tr.model.Model} model
+ */
+ function addTelemetryInfo(histograms, model) {
+ for (const metadata of model.metadata) {
+ if (!metadata.value || !metadata.value.telemetry) continue;
+
+ const traceUrls = metadata.value.telemetry[
+ tr.v.d.RESERVED_NAMES.TRACE_URLS];
+ if (traceUrls && model.canonicalUrl !== traceUrls[0]) {
+ throw new Error(`canonicalUrl "${model.canonicalUrl}" != ` +
+ `traceUrl "${traceUrls[0]}"`);
+ }
+
+ for (const [name, value] of Object.entries(metadata.value.telemetry)) {
+ const type = tr.v.d.RESERVED_NAMES_TO_TYPES.get(name);
+ if (type === undefined) {
+ throw new Error(`Unexpected telemetry.${name}`);
+ }
+ histograms.addSharedDiagnosticToAllHistograms(name, new type(value));
+ }
+ }
+ }
+
+ /**
+ * @param {!tr.mre.MreResult} result
+ * @param {!tr.model.Model} model
+ * @param {!Object} options
+ * @param {!Array.<string>} options.metrics
+ */
+ function metricMapFunction(result, model, options) {
+ const histograms = runMetrics(
+ model, options, result.addFailure.bind(result));
+ addTelemetryInfo(histograms, model);
+
+ result.addPair('histograms', histograms.asDicts());
+
+ const scalarDicts = [];
+ for (const value of histograms) {
+ for (const [statName, scalar] of value.statisticsScalars) {
+ scalarDicts.push({
+ name: value.name + '_' + statName,
+ numeric: scalar.asDict(),
+ description: value.description,
+ });
+ }
+ }
+ result.addPair('scalars', scalarDicts);
+ }
+
+ tr.mre.FunctionRegistry.register(metricMapFunction);
+
+ return {
+ metricMapFunction,
+ runMetrics,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/metric_map_function_test.html b/chromium/third_party/catapult/tracing/tracing/metrics/metric_map_function_test.html
new file mode 100644
index 00000000000..5be41925424
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/metric_map_function_test.html
@@ -0,0 +1,214 @@
+<!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/extras/importer/trace_event_importer.html">
+<link rel="import" href="/tracing/metrics/metric_map_function.html">
+<link rel="import" href="/tracing/metrics/sample_metric.html">
+<link rel="import" href="/tracing/mre/mre_result.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const TestUtils = tr.c.TestUtils;
+ const ThreadSlice = tr.model.ThreadSlice;
+
+ test('metricMapTest', function() {
+ const events = [
+ {name: 'a', args: {}, pid: 52, ts: 524, cat: 'foo', tid: 53, ph: 'B'},
+ {name: 'a', args: {}, pid: 52, ts: 560, cat: 'foo', tid: 53, ph: 'E'}
+ ];
+ const m = TestUtils.newModelWithEvents(JSON.stringify(events), {
+ shiftWorldToZero: false,
+ pruneEmptyContainers: false,
+ trackDetailedModelStats: true,
+ customizeModelCallback(m) {
+ const p1 = m.getOrCreateProcess(1);
+ const t2 = p1.getOrCreateThread(2);
+ t2.sliceGroup.pushSlice(TestUtils.newSliceEx({
+ type: ThreadSlice,
+ name: 'some_slice',
+ start: 0, end: 10
+ }));
+ t2.sliceGroup.pushSlice(TestUtils.newSliceEx({
+ type: ThreadSlice,
+ name: 'some_slice',
+ start: 20, end: 30
+ }));
+ }
+ });
+
+ assert.throw(function() {
+ const result = new tr.mre.MreResult();
+ tr.metrics.metricMapFunction(result, m, {});
+ }, Error, 'Metric names should be specified.');
+
+ assert.throw(function() {
+ const result = new tr.mre.MreResult();
+ tr.metrics.metricMapFunction(result, m, {'metrics': ['wrongMetric']});
+ }, Error, '"wrongMetric" is not a registered metric.');
+
+ const result = new tr.mre.MreResult();
+ tr.metrics.metricMapFunction(
+ result, m, {'metrics': ['sampleMetric']});
+ assert.property(result.pairs, 'histograms');
+ assert.strictEqual(result.pairs.histograms.length, 4);
+ assert.property(result.pairs, 'scalars');
+ assert.strictEqual(result.pairs.scalars.length, 19);
+ assert.lengthOf(result.failures, 0);
+ });
+
+ test('exceptionMetric', function() {
+ const events = [
+ {name: 'a', args: {}, pid: 52, ts: 524, cat: 'foo', tid: 53, ph: 'B'},
+ {name: 'a', args: {}, pid: 52, ts: 560, cat: 'foo', tid: 53, ph: 'E'}
+ ];
+ const m = TestUtils.newModelWithEvents(JSON.stringify(events), {
+ shiftWorldToZero: false,
+ pruneEmptyContainers: false,
+ trackDetailedModelStats: true,
+ customizeModelCallback(m) {
+ const p1 = m.getOrCreateProcess(1);
+ const t2 = p1.getOrCreateThread(2);
+ t2.sliceGroup.pushSlice(TestUtils.newSliceEx({
+ type: ThreadSlice,
+ name: 'some_slice',
+ start: 0, end: 10
+ }));
+ t2.sliceGroup.pushSlice(TestUtils.newSliceEx({
+ type: ThreadSlice,
+ name: 'some_slice',
+ start: 20, end: 30
+ }));
+ }
+ });
+
+ const result = new tr.mre.MreResult();
+ tr.metrics.metricMapFunction(
+ result, m, {'metrics': ['sampleExceptionMetric']});
+ assert.property(result.pairs, 'histograms');
+ assert.strictEqual(result.pairs.histograms.length, 4);
+ assert.property(result.pairs, 'scalars');
+ assert.strictEqual(result.pairs.scalars.length, 19);
+ assert.lengthOf(result.failures, 1);
+ });
+
+ function invalidDiagnosticNameMetric(histograms, model) {
+ histograms.createHistogram('a', tr.b.Unit.byName.count, [], {diagnostics: {
+ [tr.v.d.RESERVED_NAMES.BOTS]: new tr.v.d.GenericSet([]),
+ }});
+ }
+
+ tr.metrics.MetricRegistry.register(invalidDiagnosticNameMetric);
+
+ test('validateDiagnosticNames', function() {
+ const result = new tr.mre.MreResult();
+ const m = TestUtils.newModel();
+
+ assert.throw(function() {
+ tr.metrics.metricMapFunction(result, m, {
+ 'metrics': ['invalidDiagnosticNameMetric'],
+ });
+ }, Error, 'Illegal diagnostic name ' +
+ `"${tr.v.d.RESERVED_NAMES.BOTS}" on Histogram "a"`);
+ });
+
+ test('validateCanonicalUrl', function() {
+ const result = new tr.mre.MreResult();
+ const m = TestUtils.newModel(model => {
+ model.canonicalUrl = 'canonical url';
+ model.metadata = [{
+ value: {
+ telemetry: {
+ traceUrls: ['trace url'],
+ },
+ },
+ }];
+ });
+ assert.throw(function() {
+ tr.metrics.metricMapFunction(result, m, {metrics: ['sampleMetric']});
+ }, Error, 'canonicalUrl "canonical url" != traceUrl "trace url"');
+ });
+
+ function requiresDefaultCategoryMetric(histograms, model) {
+ }
+
+ tr.metrics.MetricRegistry.register(requiresDefaultCategoryMetric, {
+ requiredCategories: ['foo'],
+ });
+
+ function requiresDisabledCategoryMetric(histograms, model) {
+ }
+
+ tr.metrics.MetricRegistry.register(requiresDisabledCategoryMetric, {
+ requiredCategories: ['disabled-by-default-foo'],
+ });
+
+ test('validateRequiredCategories', function() {
+ const result = new tr.mre.MreResult();
+ let m = TestUtils.newModel(model => {
+ model.metadata = [{
+ name: 'metadata',
+ value: {
+ 'trace-config': JSON.stringify({
+ excluded_categories: ['foo'],
+ }),
+ },
+ }];
+ });
+
+ assert.throw(function() {
+ tr.metrics.metricMapFunction(result, m, {metrics:
+ ['requiresDefaultCategoryMetric']});
+ }, Error, 'Trace is missing required category "foo"');
+
+ m = TestUtils.newModel(model => {
+ model.metadata = [{
+ name: 'TraceConfig',
+ value: {
+ },
+ }];
+ });
+
+ assert.throw(function() {
+ tr.metrics.metricMapFunction(result, m, {metrics:
+ ['requiresDisabledCategoryMetric']});
+ }, Error, 'Trace is missing required category "disabled-by-default-foo"');
+ });
+
+ test('processStrippedConfig', function() {
+ const result = new tr.mre.MreResult();
+ const m = TestUtils.newModel(model => {
+ model.metadata = [{
+ name: 'metadata',
+ value: {
+ 'trace-config': '__stripped__'
+ },
+ }];
+ });
+ tr.metrics.metricMapFunction(
+ result, m, {'metrics': ['sampleMetric']});
+ assert.lengthOf(result.failures, 0);
+ });
+
+ test('metricMetrics', function() {
+ const model = new tr.Model();
+ // We can't customize the model the normal way using
+ // test_utils.newModel(customizeModelCallback) because that callback is run
+ // before the end of the import phase, so our import duration will be
+ // overwritten.
+ model.stats.traceImportDurationMs = 10;
+
+ const histograms = tr.metrics.runMetrics(
+ model, {'metrics': ['sampleMetric']});
+
+ assert.strictEqual(
+ histograms.getHistogramNamed('trace_import_duration').average, 10);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/metric_registry.html b/chromium/third_party/catapult/tracing/tracing/metrics/metric_registry.html
new file mode 100644
index 00000000000..05ea36dd12f
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/metric_registry.html
@@ -0,0 +1,116 @@
+<!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">
+<link rel="import" href="/tracing/base/extension_registry.html">
+<link rel="import" href="/tracing/base/utils.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.metrics', function() {
+ function MetricRegistry() {}
+
+ const options = new tr.b.ExtensionRegistryOptions(tr.b.BASIC_REGISTRY_MODE);
+ options.defaultMetadata = {};
+ tr.b.decorateExtensionRegistry(MetricRegistry, options);
+
+ function camelCaseToHackerString(camelCase) {
+ let hackerString = '';
+ for (const c of camelCase) {
+ const lowered = c.toLocaleLowerCase();
+ if (lowered === c) {
+ hackerString += c;
+ } else {
+ hackerString += '_' + lowered;
+ }
+ }
+ return hackerString;
+ }
+
+ function getCallStack() {
+ try {
+ throw new Error();
+ } catch (error) {
+ return error.stack;
+ }
+ }
+
+ function getPathsFromStack(stack) {
+ return stack.split('\n').map(line => {
+ line = line.replace(/^ */, '').split(':');
+ if (line.length < 4) return '';
+ return line[line.length - 3].split('/');
+ }).filter(x => x);
+ }
+
+ MetricRegistry.checkFilename = function(metricName, opt_metricPathForTest) {
+ if (metricName === 'runtimeStatsTotalMetric' ||
+ metricName === 'v8AndMemoryMetrics') {
+ // TODO(crbug.com/688342) Remove the runtimeStatsTotalMetric exception.
+ // TODO(3275) Remove the v8AndMemoryMetrics exception.
+ // https://github.com/catapult-project/catapult/issues/3275
+ return;
+ }
+
+ const expectedFilename = camelCaseToHackerString(metricName) + '.html';
+ const stack = getCallStack();
+
+ let metricPath = opt_metricPathForTest;
+ if (metricPath === undefined) {
+ const paths = getPathsFromStack(stack);
+ const METRIC_STACK_INDEX = 5;
+
+ // This filename is in paths[0]. If this file is not vulcanized, then the
+ // metric's filename is in paths[METRIC_STACK_INDEX]. If this file is
+ // vulcanized, then they are the same, and paths[METRIC_STACK_INDEX] is
+ // not the metric's filename.
+ if (paths.length <= METRIC_STACK_INDEX ||
+ paths[METRIC_STACK_INDEX].join('/') === paths[0].join('/')) {
+ return;
+ }
+
+ metricPath = paths[METRIC_STACK_INDEX].slice(
+ paths[METRIC_STACK_INDEX].length - 2);
+ }
+
+ if (!metricPath[1].endsWith('_test.html') &&
+ !metricPath[1].endsWith('_test.html.js') &&
+ metricPath[1] !== expectedFilename &&
+ metricPath[1] !== expectedFilename + '.js' &&
+ metricPath.join('_') !== expectedFilename &&
+ metricPath.join('_') !== expectedFilename + '.js') {
+ throw new Error(
+ 'Expected ' + metricName + ' to be in a file named ' +
+ expectedFilename + '; actual: ' + metricPath.join('/') +
+ '; stack: ' + stack.replace(/\n/g, '\n '));
+ }
+ };
+
+ MetricRegistry.addEventListener('will-register', function(e) {
+ const metric = e.typeInfo.constructor;
+ if (!(metric instanceof Function)) {
+ throw new Error('Metrics must be functions.');
+ }
+
+ if (!metric.name.endsWith('Metric') &&
+ !metric.name.endsWith('Metrics')) {
+ throw new Error('Metric names must end with "Metric" or "Metrics".');
+ }
+
+ if (metric.length < 2) {
+ throw new Error('Metrics take a HistogramSet and a Model and ' +
+ 'optionally an options dictionary.');
+ }
+
+ MetricRegistry.checkFilename(metric.name);
+ });
+
+ return {
+ MetricRegistry,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/metric_registry_test.html b/chromium/third_party/catapult/tracing/tracing/metrics/metric_registry_test.html
new file mode 100644
index 00000000000..c4123024ae6
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/metric_registry_test.html
@@ -0,0 +1,93 @@
+<!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/metrics/metric_registry.html">
+<link rel="import" href="/tracing/value/histogram.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ test('FindMetricByName', function() {
+ function aMetric(histograms, model) {
+ const n1 = new tr.v.Histogram('foo', tr.b.Unit.byName.count);
+ n1.addSample(1);
+ histograms.addHistogram(n1);
+ }
+ tr.metrics.MetricRegistry.register(aMetric);
+
+ function bMetric(histograms, model) {
+ const n1 = new tr.v.Histogram('foo', tr.b.Unit.byName.count);
+ const n2 = new tr.v.Histogram('bar', tr.b.Unit.byName.count);
+ n1.addSample(1);
+ n2.addSample(2);
+ histograms.addHistogram(n1);
+ histograms.addHistogram(n2);
+ }
+ tr.metrics.MetricRegistry.register(bMetric);
+
+ function cMetric(histograms, model) {
+ const n1 = new tr.v.Histogram('foo', tr.b.Unit.byName.count);
+ const n2 = new tr.v.Histogram('bar', tr.b.Unit.byName.count);
+ const n3 = new tr.v.Histogram('baz', tr.b.Unit.byName.count);
+ n1.addSample(1);
+ n2.addSample(2);
+ n3.addSample(3);
+ histograms.addHistogram(n1);
+ histograms.addHistogram(n2);
+ histograms.addHistogram(n3);
+ }
+ tr.metrics.MetricRegistry.register(cMetric);
+
+ const typeInfo = tr.metrics.MetricRegistry.findTypeInfoWithName(
+ 'bMetric');
+ assert.strictEqual(typeInfo.constructor, bMetric);
+ });
+
+ test('registerNonFunctionThrows', function() {
+ // Metrics must be functions.
+ assert.throws(function() {
+ tr.metrics.MetricRegistry.register('not a function');
+ });
+
+ // Metric names must end with "Metric" or "Metrics".
+ assert.throws(function() {
+ tr.metrics.MetricRegistry.register(function foo(histograms, model) {});
+ });
+
+ // Metrics take a HistogramSet and a Model and optionally an options
+ // dictionary.
+ assert.throws(function() {
+ tr.metrics.MetricRegistry.register(function fooMetric() {});
+ });
+
+ // Metrics take a HistogramSet and a Model and optionally an options
+ // dictionary.
+ assert.throws(function() {
+ tr.metrics.MetricRegistry.register(function fooMetric(a) {});
+ });
+ });
+
+ test('checkFilename', function() {
+ tr.metrics.MetricRegistry.checkFilename(
+ 'aMetric', ['metrics', 'a_metric.html']);
+ tr.metrics.MetricRegistry.checkFilename(
+ 'fooBarMetric', ['foo', 'bar_metric.html']);
+
+ // Expected fooMetric to be in a file named foo_metric.html; actual:
+ // foo/bar_metric.html
+ assert.throws(() => tr.metrics.MetricRegistry.checkFilename(
+ 'fooMetric', ['foo', 'bar_metric.html']));
+
+ // Expected fooMetric to be in a file named foo_metric.html; actual:
+ // cat/bar_metric.html
+ assert.throws(() => tr.metrics.MetricRegistry.checkFilename(
+ 'fooMetric', ['cat', 'bar_metric.html']));
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/metric_runner.py b/chromium/third_party/catapult/tracing/tracing/metrics/metric_runner.py
new file mode 100644
index 00000000000..b8cc2af8dde
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/metric_runner.py
@@ -0,0 +1,75 @@
+# 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.
+import os
+import sys
+
+from tracing.mre import function_handle
+from tracing.mre import gtest_progress_reporter
+from tracing.mre import map_runner
+from tracing.mre import file_handle
+from tracing.mre import job as job_module
+
+
+try:
+ StringTypes = basestring
+except NameError:
+ StringTypes = str
+
+
+_METRIC_MAP_FUNCTION_FILENAME = 'metric_map_function.html'
+
+_METRIC_MAP_FUNCTION_NAME = 'metricMapFunction'
+
+def _GetMetricsDir():
+ return os.path.dirname(os.path.abspath(__file__))
+
+def _GetMetricRunnerHandle(metrics):
+ assert isinstance(metrics, list)
+ for metric in metrics:
+ assert isinstance(metric, StringTypes)
+ metrics_dir = _GetMetricsDir()
+ metric_mapper_path = os.path.join(metrics_dir, _METRIC_MAP_FUNCTION_FILENAME)
+
+ modules_to_load = [function_handle.ModuleToLoad(filename=metric_mapper_path)]
+ options = {'metrics': metrics}
+ map_function_handle = function_handle.FunctionHandle(
+ modules_to_load, _METRIC_MAP_FUNCTION_NAME, options)
+
+ return job_module.Job(map_function_handle, None)
+
+def RunMetric(filename, metrics, extra_import_options=None,
+ report_progress=True, canonical_url=None):
+ filename_url = 'file://' + filename
+ if canonical_url is None:
+ canonical_url = filename_url
+ trace_handle = file_handle.URLFileHandle(canonical_url, filename_url)
+ result = RunMetricOnTraceHandles(
+ [trace_handle], metrics, extra_import_options, report_progress)
+ return result[canonical_url]
+
+def RunMetricOnTraceHandles(trace_handles, metrics, extra_import_options=None,
+ report_progress=True):
+ job = _GetMetricRunnerHandle(metrics)
+ with open(os.devnull, 'w') as devnull_f:
+ o_stream = sys.stdout
+ if not report_progress:
+ o_stream = devnull_f
+
+ runner = map_runner.MapRunner(
+ trace_handles, job, extra_import_options=extra_import_options,
+ progress_reporter=gtest_progress_reporter.GTestProgressReporter(
+ output_stream=o_stream))
+ map_results = runner.RunMapper()
+
+ return map_results
+
+def RunMetricOnTraces(filenames, metrics,
+ extra_import_options=None, report_progress=True):
+ trace_handles = []
+ for filename in filenames:
+ filename_url = 'file://' + filename
+ trace_handles.append(file_handle.URLFileHandle(filename_url, filename_url))
+
+ return RunMetricOnTraceHandles(trace_handles, metrics, extra_import_options,
+ report_progress)
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/rendering/cpu_utilization.html b/chromium/third_party/catapult/tracing/tracing/metrics/rendering/cpu_utilization.html
new file mode 100644
index 00000000000..e1ea92fd04a
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/rendering/cpu_utilization.html
@@ -0,0 +1,195 @@
+<!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/statistics.html">
+<link rel="import" href="/tracing/base/unit.html">
+<link rel="import" href="/tracing/value/diagnostics/breakdown.html">
+<link rel="import" href="/tracing/value/histogram.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview This file contains implementations of the following metrics.
+ *
+ * The addCpuUtilizationHistograms method can generate the following sets of
+ * metrics, dependeing on the input values for segments, shouldNormalize, and
+ * segmentCostFunc.
+ *
+ * thread_{thread group}_cpu_time_per_second
+ * =========================================
+ * segments: any set of interesting segments
+ * shouldNormalize: True
+ * segmentCostFunc: thread.getCpuTimeForRange
+ *
+ * This set of metrics show how much a thread group was busy during specific
+ * segments marked by a test. More precisely, it shows the average amount of CPU
+ * cores per seconds the thread group consumes during the segments.
+ *
+ * thread_{thread group}_cpu_time_per_frame
+ * ========================================
+ * segments: display compositor's frames
+ * shouldNormalize: False
+ * segmentCostFunc: thread.getCpuTimeForRange
+ *
+ * This set of metrics show the distribution of CPU usage of a thread
+ * group in each display compositor's frame.
+ *
+ * tasks_per_second_{thread group}
+ * ===============================
+ * segments: any set of interesting segments
+ * shouldNormalize: True
+ * segmentCostFunc: thread.getNumToplevelSlicesForRange
+ *
+ * This set of metrics show the average number of tasks per second of a thread
+ * group during specific segments marked by a test.
+ *
+ * tasks_per_frame_{thread group}
+ * ==============================
+ * segments: display compositor's frames
+ * shouldNormalize: False
+ * segmentCostFunc: thread.getNumToplevelSlicesForRange
+ *
+ * This set of metrics show the distribution of the number of task in each
+ * display compositor's frame of a thread group.
+ *
+ * Note: the CPU usage in all above-mentioned metrics, is approximated from
+ * top-level trace events in each thread; it does not come from the OS. So, the
+ * metric may be noisy and not be very meaningful for threads that do not have a
+ * message loop.
+ */
+tr.exportTo('tr.metrics.rendering', function() {
+ const UNKNOWN_THREAD_NAME = 'Unknown';
+
+ const CATEGORY_THREAD_MAP = new Map();
+ CATEGORY_THREAD_MAP.set('total_all', [/.*/]);
+ CATEGORY_THREAD_MAP.set(
+ 'browser', [/^Browser Compositor$/, /^CrBrowserMain$/]);
+ CATEGORY_THREAD_MAP.set('display_compositor', [/^VizCompositorThread$/]);
+ CATEGORY_THREAD_MAP.set(
+ 'total_fast_path', [
+ /^Browser Compositor$/, /^Chrome_InProcGpuThread$/, /^Compositor$/,
+ /^CrBrowserMain$/, /^CrGpuMain$/, /IOThread/, /^VizCompositorThread$/]);
+ CATEGORY_THREAD_MAP.set('GPU', [/^Chrome_InProcGpuThread$/, /^CrGpuMain$/]);
+ CATEGORY_THREAD_MAP.set('IO', [/IOThread/]);
+ CATEGORY_THREAD_MAP.set('raster', [/CompositorTileWorker/]);
+ CATEGORY_THREAD_MAP.set('renderer_compositor', [/^Compositor$/]);
+ CATEGORY_THREAD_MAP.set('renderer_main', [/^CrRendererMain$/]);
+
+ const ALL_CATEGORIES = [...CATEGORY_THREAD_MAP.keys(), 'other'];
+
+ function addValueToMap_(map, key, value) {
+ const oldValue = map.get(key) || 0;
+ map.set(key, oldValue + value);
+ }
+
+ function* getCategories_(threadName) {
+ let isOther = true;
+ for (const [category, regexps] of CATEGORY_THREAD_MAP) {
+ for (const regexp of regexps) {
+ if (regexp.test(threadName)) {
+ if (category !== 'total_all') isOther = false;
+ yield category;
+ break;
+ }
+ }
+ }
+ if (isOther) yield 'other';
+ }
+
+ function isSubset_(regexps1, regexps2) {
+ for (const r1 of regexps1) {
+ if (regexps2.find(r2 => r2.toString() === r1.toString()) === undefined) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ function addCpuUtilizationHistograms(
+ histograms, model, segments, shouldNormalize, segmentCostFunc,
+ histogramNameFunc, description, unit) {
+ if (!unit) unit = tr.b.Unit.byName.unitlessNumber;
+ const histogramMap = new Map();
+ for (const category of ALL_CATEGORIES) {
+ const histogram = histograms.createHistogram(
+ histogramNameFunc(category), unit, [], {
+ binBoundaries:
+ tr.v.HistogramBinBoundaries.createExponential(1, 50, 20),
+ description,
+ summaryOptions: tr.metrics.rendering.SUMMARY_OPTIONS,
+ });
+ histogramMap.set(category, histogram);
+ }
+
+ // Add histogram breakdowns.
+ for (const [category, regexps] of CATEGORY_THREAD_MAP) {
+ const relatedCategories = new tr.v.d.RelatedNameMap();
+ const histogram = histogramMap.get(category);
+ for (const [otherCategory, otherRegexps] of CATEGORY_THREAD_MAP) {
+ if (otherCategory === category) continue;
+ if (category !== 'all' && !isSubset_(otherRegexps, regexps)) continue;
+ const otherHistogram = histogramMap.get(otherCategory);
+ relatedCategories.set(otherCategory, otherHistogram.name);
+ }
+ if ([...relatedCategories.values()].length > 0) {
+ histogram.diagnostics.set('breakdown', relatedCategories);
+ }
+ }
+
+ for (const segment of segments) {
+ const threadValues = new Map();
+ // Compute and store CPU times per categories and thread name.
+ for (const thread of model.getAllThreads()) {
+ addValueToMap_(
+ threadValues,
+ thread.name || UNKNOWN_THREAD_NAME,
+ segmentCostFunc(thread, segment));
+ }
+ const categoryValues = new Map();
+ const breakdowns = new Map();
+ for (const [threadName, coresPerSec] of threadValues) {
+ for (const category of getCategories_(threadName)) {
+ addValueToMap_(categoryValues, category, coresPerSec);
+ if (!breakdowns.has(category)) {
+ breakdowns.set(category, new tr.v.d.Breakdown());
+ }
+ // TODO(chiniforooshan): We break down the CPU usage of each category
+ // by the thread name here. It will be more useful if we could add
+ // task names too. On the other hand, breaking down at task level may
+ // be too granular and we may end up with a ton of tiny slices that
+ // will not be that useful. Maybe we can break down by just top x
+ // tasks, or top x (thread, task) pairs?
+ //
+ // Another possbility to investigate is to break down by initiator
+ // type of the animation expectation.
+ breakdowns.get(category).set(threadName, coresPerSec);
+ }
+ }
+
+ for (const category of ALL_CATEGORIES) {
+ let value = categoryValues.get(category) || 0;
+ if (shouldNormalize) value /= segment.duration;
+ const diagnostics = new tr.v.d.DiagnosticMap();
+ const breakdown = breakdowns.get(category);
+ if (breakdown) diagnostics.set('breakdown', breakdown);
+ const histogram = histogramMap.get(category);
+ histogram.addSample(value, diagnostics);
+ }
+ }
+ }
+
+ const SUMMARY_OPTIONS = {
+ percentile: [0.90, 0.95],
+ };
+
+ return {
+ addCpuUtilizationHistograms,
+ SUMMARY_OPTIONS,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/rendering/cpu_utilization_test.html b/chromium/third_party/catapult/tracing/tracing/metrics/rendering/cpu_utilization_test.html
new file mode 100644
index 00000000000..3f58d2868f0
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/rendering/cpu_utilization_test.html
@@ -0,0 +1,280 @@
+<!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/metrics/rendering/cpu_utilization.html">
+<link rel="import" href="/tracing/model/user_model/segment.html">
+<link rel="import" href="/tracing/value/histogram_set.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ test('cpuPerSecond', function() {
+ const model = tr.c.TestUtils.newModel((model) => {
+ const browserMain = model.getOrCreateProcess(0).getOrCreateThread(0);
+ browserMain.name = 'CrBrowserMain';
+ // A slice during the animation with CPU duration 2.
+ browserMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { start: 1, end: 4, cpuStart: 0, cpuEnd: 2 }));
+ // A slice after the animation.
+ browserMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { start: 10, end: 12, cpuStart: 2, cpuEnd: 3 }));
+
+
+ const rendererMain = model.getOrCreateProcess(1).getOrCreateThread(0);
+ rendererMain.name = 'CrRendererMain';
+ // A slice half of which intersects with the animation.
+ rendererMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { start: 5, end: 15, cpuStart: 0, cpuEnd: 8 }));
+
+ const rendererIO = model.getOrCreateProcess(1).getOrCreateThread(1);
+ rendererIO.name = 'Chrome_ChildIOThread';
+ rendererIO.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { start: 5, end: 6, cpuStart: 1, cpuEnd: 2 }));
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.rendering.addCpuUtilizationHistograms(
+ histograms, model, [new tr.model.um.Segment(0, 10)], true,
+ (thread, segment) => thread.getCpuTimeForRange(segment.boundsRange),
+ category => `thread_${category}_cpu_time_per_second`, 'description');
+
+ // Verify the browser and renderer main threads and IO threads CPU usage.
+ let hist = histograms.getHistogramNamed(
+ 'thread_browser_cpu_time_per_second');
+ assert.closeTo(0.2, hist.min, 1e-6);
+ assert.closeTo(0.2, hist.max, 1e-6);
+ assert.closeTo(0.2, hist.average, 1e-6);
+
+ hist = histograms.getHistogramNamed(
+ 'thread_renderer_main_cpu_time_per_second');
+ assert.closeTo(0.4, hist.min, 1e-6);
+ assert.closeTo(0.4, hist.max, 1e-6);
+ assert.closeTo(0.4, hist.average, 1e-6);
+
+ hist = histograms.getHistogramNamed('thread_IO_cpu_time_per_second');
+ assert.closeTo(0.1, hist.min, 1e-6);
+ assert.closeTo(0.1, hist.max, 1e-6);
+ assert.closeTo(0.1, hist.average, 1e-6);
+
+ // Also, verify fast_path threads, that includs IO threads and the browser
+ // main thread, but not the renderer main thread.
+ hist = histograms.getHistogramNamed(
+ 'thread_total_fast_path_cpu_time_per_second');
+ assert.closeTo(0.3, hist.min, 1e-6);
+ assert.closeTo(0.3, hist.max, 1e-6);
+ assert.closeTo(0.3, hist.average, 1e-6);
+
+ // Verify sum of all threads.
+ hist = histograms.getHistogramNamed('thread_total_all_cpu_time_per_second');
+ assert.closeTo(0.7, hist.min, 1e-6);
+ assert.closeTo(0.7, hist.max, 1e-6);
+ assert.closeTo(0.7, hist.average, 1e-6);
+ });
+
+ test('cpuPerFrame', function() {
+ const model = tr.c.TestUtils.newModel((model) => {
+ const browserMain = model.getOrCreateProcess(0).getOrCreateThread(0);
+ browserMain.name = 'CrBrowserMain';
+ // A slice during the animation with CPU duration 2.
+ browserMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { start: 1, end: 4, cpuStart: 0, cpuEnd: 2 }));
+ // A slice after the animation.
+ browserMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { start: 10, end: 12, cpuStart: 2, cpuEnd: 3 }));
+
+
+ const rendererMain = model.getOrCreateProcess(1).getOrCreateThread(0);
+ rendererMain.name = 'CrRendererMain';
+ // A slice half of which intersects with the animation.
+ rendererMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { start: 5, end: 15, cpuStart: 0, cpuEnd: 8 }));
+
+ const rendererIO = model.getOrCreateProcess(1).getOrCreateThread(1);
+ rendererIO.name = 'Chrome_ChildIOThread';
+ rendererIO.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { start: 5, end: 6, cpuStart: 1, cpuEnd: 2 }));
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.rendering.addCpuUtilizationHistograms(
+ histograms, model, [new tr.model.um.Segment(0, 10)], false,
+ (thread, segment) => thread.getCpuTimeForRange(segment.boundsRange),
+ category => `thread_${category}_cpu_time_per_frame`, 'description');
+
+ // Verify the browser and renderer main threads and IO threads CPU usage.
+ let hist = histograms.getHistogramNamed(
+ 'thread_browser_cpu_time_per_frame');
+ assert.closeTo(2, hist.min, 1e-6);
+ assert.closeTo(2, hist.max, 1e-6);
+ assert.closeTo(2, hist.average, 1e-6);
+
+ hist = histograms.getHistogramNamed(
+ 'thread_renderer_main_cpu_time_per_frame');
+ assert.closeTo(4, hist.min, 1e-6);
+ assert.closeTo(4, hist.max, 1e-6);
+ assert.closeTo(4, hist.average, 1e-6);
+
+ hist = histograms.getHistogramNamed('thread_IO_cpu_time_per_frame');
+ assert.closeTo(1, hist.min, 1e-6);
+ assert.closeTo(1, hist.max, 1e-6);
+ assert.closeTo(1, hist.average, 1e-6);
+
+ // Also, verify fast_path threads, that includs IO threads and the browser
+ // main thread, but not the renderer main thread.
+ hist = histograms.getHistogramNamed(
+ 'thread_total_fast_path_cpu_time_per_frame');
+ assert.closeTo(3, hist.min, 1e-6);
+ assert.closeTo(3, hist.max, 1e-6);
+ assert.closeTo(3, hist.average, 1e-6);
+
+ // Verify sum of all threads.
+ hist = histograms.getHistogramNamed('thread_total_all_cpu_time_per_frame');
+ assert.closeTo(7, hist.min, 1e-6);
+ assert.closeTo(7, hist.max, 1e-6);
+ assert.closeTo(7, hist.average, 1e-6);
+ });
+
+ test('multipleSegments', function() {
+ const model = tr.c.TestUtils.newModel((model) => {
+ const browserMain = model.getOrCreateProcess(0).getOrCreateThread(0);
+ browserMain.name = 'CrBrowserMain';
+ browserMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { start: 1, end: 4, cpuStart: 0, cpuEnd: 2 }));
+ browserMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { start: 8, end: 12, cpuStart: 2, cpuEnd: 4 }));
+
+
+ const rendererMain = model.getOrCreateProcess(1).getOrCreateThread(0);
+ rendererMain.name = 'CrRendererMain';
+ rendererMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { start: 5, end: 15, cpuStart: 0, cpuEnd: 8 }));
+
+ const rendererIO = model.getOrCreateProcess(1).getOrCreateThread(1);
+ rendererIO.name = 'Chrome_ChildIOThread';
+ rendererIO.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { start: 5, end: 6, cpuStart: 1, cpuEnd: 2 }));
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.rendering.addCpuUtilizationHistograms(
+ histograms, model,
+ [new tr.model.um.Segment(0, 5), new tr.model.um.Segment(5, 5)], false,
+ (thread, segment) => thread.getCpuTimeForRange(segment.boundsRange),
+ category => `thread_${category}_cpu_time_per_frame`, 'description');
+
+ // The first slice is in the first segment, using 2ms of CPU. The rest are
+ // in the second segment, using 1 + 4 + 1 = 6ms of CPU.
+ const hist = histograms.getHistogramNamed(
+ 'thread_total_all_cpu_time_per_frame');
+ assert.closeTo(2, hist.min, 1e-6);
+ assert.closeTo(6, hist.max, 1e-6);
+ assert.closeTo(4, hist.average, 1e-6);
+ });
+
+ test('otherThreads', function() {
+ const model = tr.c.TestUtils.newModel((model) => {
+ const browserMain = model.getOrCreateProcess(0).getOrCreateThread(0);
+ browserMain.name = 'CrBrowserMain';
+ // A slice during the animation with CPU duration 2.
+ browserMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { start: 1, end: 4, cpuStart: 0, cpuEnd: 2 }));
+ // A slice after the animation.
+ browserMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { start: 10, end: 12, cpuStart: 2, cpuEnd: 3 }));
+
+
+ const thread1 = model.getOrCreateProcess(1).getOrCreateThread(0);
+ thread1.name = 'Thread1';
+ // A slice half of which intersects with the animation.
+ thread1.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { start: 5, end: 15, cpuStart: 0, cpuEnd: 8 }));
+
+ const thread2 = model.getOrCreateProcess(1).getOrCreateThread(1);
+ thread2.name = 'Thread2';
+ thread2.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { start: 5, end: 6, cpuStart: 1, cpuEnd: 2 }));
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.rendering.addCpuUtilizationHistograms(
+ histograms, model, [new tr.model.um.Segment(0, 10)], false,
+ (thread, segment) => thread.getCpuTimeForRange(segment.boundsRange),
+ category => `thread_${category}_cpu_time_per_frame`, 'description');
+
+ // Verify the browser and renderer main threads and IO threads CPU usage.
+ let hist = histograms.getHistogramNamed(
+ 'thread_browser_cpu_time_per_frame');
+ assert.closeTo(2, hist.min, 1e-6);
+ assert.closeTo(2, hist.max, 1e-6);
+ assert.closeTo(2, hist.average, 1e-6);
+
+ hist = histograms.getHistogramNamed('thread_other_cpu_time_per_frame');
+ assert.closeTo(5, hist.min, 1e-6);
+ assert.closeTo(5, hist.max, 1e-6);
+ assert.closeTo(5, hist.average, 1e-6);
+ });
+
+ test('tasksPerFrame', function() {
+ const model = tr.c.TestUtils.newModel((model) => {
+ const browserMain = model.getOrCreateProcess(0).getOrCreateThread(0);
+ browserMain.name = 'CrBrowserMain';
+ // A slice during the animation with CPU duration 2.
+ browserMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { start: 1, end: 4, cpuStart: 0, cpuEnd: 2 }));
+ // A slice after the animation.
+ browserMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { start: 10, end: 12, cpuStart: 2, cpuEnd: 3 }));
+
+
+ const rendererMain = model.getOrCreateProcess(1).getOrCreateThread(0);
+ rendererMain.name = 'CrRendererMain';
+ // A slice half of which intersects with the animation.
+ rendererMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { start: 5, end: 15, cpuStart: 0, cpuEnd: 8 }));
+
+ const rendererIO = model.getOrCreateProcess(1).getOrCreateThread(1);
+ rendererIO.name = 'Chrome_ChildIOThread';
+ rendererIO.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { start: 5, end: 6, cpuStart: 1, cpuEnd: 2 }));
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.rendering.addCpuUtilizationHistograms(
+ histograms, model, [new tr.model.um.Segment(0, 10)], false,
+ (thread, segment) =>
+ thread.getNumToplevelSlicesForRange(segment.boundsRange),
+ category => `tasks_per_frame_${category}`, 'description');
+
+ // Verify the browser and renderer main threads and IO threads number of
+ // tasks.
+ let hist = histograms.getHistogramNamed('tasks_per_frame_browser');
+ assert.closeTo(1, hist.min, 1e-6);
+ assert.closeTo(1, hist.max, 1e-6);
+ assert.closeTo(1, hist.average, 1e-6);
+
+ hist = histograms.getHistogramNamed('tasks_per_frame_renderer_main');
+ assert.closeTo(0.5, hist.min, 1e-6);
+ assert.closeTo(0.5, hist.max, 1e-6);
+ assert.closeTo(0.5, hist.average, 1e-6);
+
+ hist = histograms.getHistogramNamed('tasks_per_frame_IO');
+ assert.closeTo(1, hist.min, 1e-6);
+ assert.closeTo(1, hist.max, 1e-6);
+ assert.closeTo(1, hist.average, 1e-6);
+
+ // Also, verify fast_path threads, that includs IO threads and the browser
+ // main thread, but not the renderer main thread.
+ hist = histograms.getHistogramNamed('tasks_per_frame_total_fast_path');
+ assert.closeTo(2, hist.min, 1e-6);
+ assert.closeTo(2, hist.max, 1e-6);
+ assert.closeTo(2, hist.average, 1e-6);
+
+ // Verify sum of all threads.
+ hist = histograms.getHistogramNamed('tasks_per_frame_total_all');
+ assert.closeTo(2.5, hist.min, 1e-6);
+ assert.closeTo(2.5, hist.max, 1e-6);
+ assert.closeTo(2.5, hist.average, 1e-6);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/rendering/frame_time.html b/chromium/third_party/catapult/tracing/tracing/metrics/rendering/frame_time.html
new file mode 100644
index 00000000000..1c465423654
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/rendering/frame_time.html
@@ -0,0 +1,272 @@
+<!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/statistics.html">
+<link rel="import" href="/tracing/base/unit.html">
+<link rel="import" href="/tracing/base/unit_scale.html">
+<link rel="import" href="/tracing/metrics/rendering/cpu_utilization.html">
+<link rel="import" href="/tracing/model/helpers/chrome_model_helper.html">
+<link rel="import" href="/tracing/model/user_model/segment.html">
+<link rel="import" href="/tracing/value/histogram.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview This file contains implementations of the following metrics.
+ *
+ * frame_times
+ * ===========
+ * The distribution of durations between consecutive display compositor's swap
+ * buffer calls, or DRM page flips on ChromeOS devices, during animations.
+ *
+ * percentage_smooth
+ * =================
+ * The percentage of frame_times that are less than 17ms.
+ *
+ * TODO(chiniforooshan): This metric weighs all frames equally. So, e.g.
+ * percentage_smooth is lower (worse) if we have 10 100ms-frames instead of 5
+ * 1s-frames. I think it makes more sense to compute the percentage of
+ * non-smooth time during animation.
+ *
+ * frame_lengths (Android only)
+ * ============================
+ * frame_times in vsyncs instead of milli-seconds. In other words, frame_lengths
+ * is the distribution of durations between consecutive display compositor's
+ * swap buffer calls, in terms of vsyncs. Short frames (ones shorter than half
+ * of the refresh period) are filtered out, unlike in frame_times.
+ *
+ * avg_surface_fps (Android only)
+ * ==============================
+ * Average number of frames, ignoring short frames, per second during
+ * animations.
+ *
+ * jank_count (Android only)
+ * =========================
+ * The number of times that frame lengths are increased, during animations. For
+ * example, if frame lengths are 1, 1, 1, 2, 3, 1, 1 vsyncs, then jank_count
+ * will be 2 (1 -> 2 and 2 -> 3).
+ *
+ * ui_frame_times
+ * ==============
+ * The distribution of durations between consecutive UI compositor frame's
+ * presentation times, during UI animations. In ChromeOS, if Ash uses its own
+ * instance of ui::compositor, then frames submitted by that compositor will be
+ * used. Otherwise, frames submitted by the browser's ui::compositor will be
+ * used.
+ *
+ * TODO(crbug.com/896231): we may want to consider reporting frame_times for all
+ * ui::compositors separately, e.g. as ash_frame_times and browser_frame_times.
+ *
+ * ui_percentage_smooth
+ * ====================
+ * The percentage of ui_frame_times that are less than 17ms.
+ */
+tr.exportTo('tr.metrics.rendering', function() {
+ // Various tracing events.
+ const DISPLAY_EVENT = 'BenchmarkInstrumentation::DisplayRenderingStats';
+ const DRM_EVENT = 'DrmEventFlipComplete';
+ const SURFACE_FLINGER_EVENT = 'vsync_before';
+ const COMPOSITOR_FRAME_PRESENTED_EVENT = 'FramePresented';
+
+ // In computing frame_lengths, avg_surface_fps, and jank_count, frames that
+ // are shorter than half a vsync are ignored.
+ const MIN_FRAME_LENGTH = 0.5;
+
+ // In computing the number of janks, frame length differences that are at
+ // least PAUSE_THRESHOLD vsyncs are considered pauses, not janks.
+ const PAUSE_THRESHOLD = 20;
+
+ const ASH_ENVIRONMENT = 'ash';
+ const BROWSER_ENVIRONMENT = 'browser';
+
+ function getDisplayCompositorPresentationEvents_(modelHelper) {
+ if (!modelHelper || !modelHelper.browserProcess) return [];
+ // On ChromeOS, DRM events, if they exist, are the source of truth. On
+ // Android, Surface Flinger events are the source of truth. Otherwise, look
+ // for display rendering stats. With viz, display rendering stats are
+ // emitted from the GPU process; otherwise, they are emitted from the
+ // browser process.
+ let events = [];
+ if (modelHelper.surfaceFlingerProcess) {
+ events = [...modelHelper.surfaceFlingerProcess.findTopmostSlicesNamed(
+ SURFACE_FLINGER_EVENT)];
+ if (events.length > 0) return events;
+ }
+ if (modelHelper.gpuHelper) {
+ const gpuProcess = modelHelper.gpuHelper.process;
+ events = [...gpuProcess.findTopmostSlicesNamed(DRM_EVENT)];
+ if (events.length > 0) return events;
+ events = [...gpuProcess.findTopmostSlicesNamed(DISPLAY_EVENT)];
+ if (events.length > 0) return events;
+ }
+ return [...modelHelper.browserProcess.findTopmostSlicesNamed(
+ DISPLAY_EVENT)];
+ }
+
+ function getUIPresentationEvents_(modelHelper) {
+ if (!modelHelper || !modelHelper.browserProcess) return [];
+
+ const legacyEvents = [];
+ const eventsByEnvironment = {};
+ eventsByEnvironment[ASH_ENVIRONMENT] = [];
+ eventsByEnvironment[BROWSER_ENVIRONMENT] = [];
+ for (const event of modelHelper.browserProcess.findTopmostSlicesNamed(
+ COMPOSITOR_FRAME_PRESENTED_EVENT)) {
+ if (!('environment' in event.args)) {
+ // For chrome versions before crrev.com/c/1282039.
+ legacyEvents.push(event);
+ } else {
+ eventsByEnvironment[event.args.environment].push(event);
+ }
+ }
+
+ if (eventsByEnvironment[ASH_ENVIRONMENT].length > 0) {
+ return eventsByEnvironment[ASH_ENVIRONMENT];
+ }
+ if (eventsByEnvironment[BROWSER_ENVIRONMENT].length > 0) {
+ return eventsByEnvironment[BROWSER_ENVIRONMENT];
+ }
+ return legacyEvents;
+ }
+
+ function addSurfaceFlingerHistograms_(
+ histograms, frameSegments, refreshPeriod) {
+ let frameLengths = frameSegments.map(x => x.duration / refreshPeriod);
+ frameLengths = frameLengths.filter(length => length >= MIN_FRAME_LENGTH);
+ histograms.createHistogram(
+ 'frame_lengths',
+ tr.b.Unit.byName.unitlessNumber_smallerIsBetter,
+ frameLengths,
+ { binBoundaries: tr.v.HistogramBinBoundaries.createLinear(0, 5, 20),
+ summaryOptions: tr.metrics.rendering.SUMMARY_OPTIONS,
+ description: 'Frame times in vsyncs.' });
+
+ histograms.createHistogram(
+ 'avg_surface_fps',
+ tr.b.Unit.byName.unitlessNumber_biggerIsBetter,
+ frameLengths.length / tr.b.convertUnit(
+ tr.b.math.Statistics.sum(frameSegments, x => x.duration),
+ tr.b.UnitScale.TIME.MILLI_SEC, tr.b.UnitScale.TIME.SEC),
+ { description: 'Average frames per second.',
+ summaryOptions: tr.metrics.rendering.SUMMARY_OPTIONS,
+ });
+
+ let jankCount = 0;
+ for (let i = 1; i < frameLengths.length; i++) {
+ const change = Math.round(frameLengths[i] - frameLengths[i - 1]);
+ if (change > 0 && change < PAUSE_THRESHOLD) jankCount++;
+ }
+ histograms.createHistogram(
+ 'jank_count',
+ tr.b.Unit.byName.unitlessNumber_smallerIsBetter,
+ jankCount,
+ { description: 'Number of changes in frame rate.',
+ summaryOptions: tr.metrics.rendering.SUMMARY_OPTIONS,
+ });
+ }
+
+ function computeFrameSegments_(timestamps, segments) {
+ // We use filterArray for the sake of a cleaner code. The time complexity
+ // will be O(m + n log m), where m is |timestamps| and n is |segments|.
+ // Alternatively, we could directly loop through the timestamps and segments
+ // here for a slightly better time complexity of O(m + n).
+ const frameSegments = [];
+ for (const segment of segments) {
+ const filtered = segment.boundsRange.filterArray(timestamps, x => x[0]);
+ for (let i = 1; i < filtered.length; i++) {
+ const duration = filtered[i][1] - filtered[i - 1][1];
+ frameSegments.push(
+ new tr.model.um.Segment(filtered[i - 1][0], duration));
+ }
+ }
+ return frameSegments;
+ }
+
+ function addBasicFrameTimeHistograms_(histograms, frameSegments, prefix) {
+ // TODO(chiniforooshan): Figure out what kind of break down makes sense
+ // here. Perhaps break down by tasks in the Viz/Browser process?
+ const frameTimes = frameSegments.map(x => x.duration);
+ histograms.createHistogram(
+ `${prefix}frame_times`,
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,
+ frameTimes,
+ { binBoundaries: tr.v.HistogramBinBoundaries.createLinear(0, 50, 20),
+ description: 'Raw frame times.',
+ summaryOptions: tr.metrics.rendering.SUMMARY_OPTIONS,
+ });
+ histograms.createHistogram(
+ `${prefix}percentage_smooth`,
+ tr.b.Unit.byName.unitlessNumber_biggerIsBetter,
+ 100 * tr.b.math.Statistics.sum(frameTimes, (x => (x < 17 ? 1 : 0))) /
+ frameTimes.length,
+ { description: 'Percentage of frames that were hitting 60 FPS.',
+ summaryOptions: tr.metrics.rendering.SUMMARY_OPTIONS,
+ });
+ }
+
+ function addFrameTimeHistograms(histograms, model, segments) {
+ const events = getDisplayCompositorPresentationEvents_(
+ model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper));
+ if (!events) return;
+
+ // Presentation Timestamps should be sorted.
+ const timestamps = events.map(
+ event => [event.start,
+ event.title !== DRM_EVENT ? event.start : (
+ tr.b.convertUnit(
+ event.args.data['vblank.tv_sec'],
+ tr.b.UnitScale.TIME.SEC, tr.b.UnitScale.TIME.MILLI_SEC) +
+ tr.b.convertUnit(
+ event.args.data['vblank.tv_usec'],
+ tr.b.UnitScale.TIME.MICRO_SEC, tr.b.UnitScale.TIME.MILLI_SEC))]
+ );
+ const frameSegments = computeFrameSegments_(timestamps, segments);
+ addBasicFrameTimeHistograms_(histograms, frameSegments, '');
+ tr.metrics.rendering.addCpuUtilizationHistograms(
+ histograms, model, frameSegments, false,
+ (thread, segment) => thread.getCpuTimeForRange(segment.boundsRange),
+ category => `thread_${category}_cpu_time_per_frame`,
+ 'CPU cores of a thread group per frame',
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter);
+ tr.metrics.rendering.addCpuUtilizationHistograms(
+ histograms, model, frameSegments, false,
+ (thread, segment) =>
+ thread.getNumToplevelSlicesForRange(segment.boundsRange),
+ category => `tasks_per_frame_${category}`,
+ 'Number of tasks of a thread group per frame',
+ tr.b.Unit.byName.unitlessNumber_smallerIsBetter);
+
+ // If Surface Flinger information is captured, add Surface Flinger
+ // histograms.
+ for (const metadata of model.metadata) {
+ if (metadata.value && metadata.value.surface_flinger) {
+ addSurfaceFlingerHistograms_(
+ histograms, frameSegments,
+ metadata.value.surface_flinger.refresh_period);
+ return;
+ }
+ }
+ }
+
+ function addUIFrameTimeHistograms(histograms, model, segments) {
+ const events = getUIPresentationEvents_(
+ model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper));
+ if (!events) return;
+
+ // Presentation Timestamps should be sorted.
+ const timestamps = events.map(event => [event.start, event.start]);
+ const frameSements = computeFrameSegments_(timestamps, segments);
+ addBasicFrameTimeHistograms_(histograms, frameSements, 'ui_');
+ }
+
+ return {
+ addFrameTimeHistograms,
+ addUIFrameTimeHistograms,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/rendering/frame_time_test.html b/chromium/third_party/catapult/tracing/tracing/metrics/rendering/frame_time_test.html
new file mode 100644
index 00000000000..0304a5cc0c5
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/rendering/frame_time_test.html
@@ -0,0 +1,301 @@
+<!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/metrics/rendering/frame_time.html">
+<link rel="import" href="/tracing/model/user_model/segment.html">
+<link rel="import" href="/tracing/value/histogram_set.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ test('frameTimes', function() {
+ const model = tr.c.TestUtils.newModel((model) => {
+ const browserMain = model.getOrCreateProcess(0).getOrCreateThread(0);
+ browserMain.name = 'CrBrowserMain';
+ // Add four swap buffer events, at times 1, 2, 19, 21
+ browserMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { title: 'BenchmarkInstrumentation::DisplayRenderingStats',
+ start: 1, end: 1 }));
+ browserMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { title: 'BenchmarkInstrumentation::DisplayRenderingStats',
+ start: 2, end: 2 }));
+ browserMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { title: 'BenchmarkInstrumentation::DisplayRenderingStats',
+ start: 19, end: 19 }));
+ browserMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { title: 'BenchmarkInstrumentation::DisplayRenderingStats',
+ start: 21, end: 21 }));
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.rendering.addFrameTimeHistograms(
+ histograms, model, [new tr.model.um.Segment(0, 20)]);
+
+ // The fourth frame is outside the interaction perdiod and should be
+ // discarded. The durations between the remaining three frames are 1 and 17.
+ let hist = histograms.getHistogramNamed('frame_times');
+ assert.closeTo(1, hist.min, 1e-6);
+ assert.closeTo(17, hist.max, 1e-6);
+ assert.closeTo(9, hist.average, 1e-6);
+
+ // One of the two frame times is not smooth.
+ hist = histograms.getHistogramNamed('percentage_smooth');
+ assert.closeTo(50, hist.min, 1e-6);
+ assert.closeTo(50, hist.max, 1e-6);
+ assert.closeTo(50, hist.average, 1e-6);
+ });
+
+ test('frameTimes_drmEvents', function() {
+ const model = tr.c.TestUtils.newModel((model) => {
+ const browserMain = model.getOrCreateProcess(0).getOrCreateThread(0);
+ browserMain.name = 'CrBrowserMain';
+
+ const gpuMain = model.getOrCreateProcess(1).getOrCreateThread(0);
+ gpuMain.name = 'CrGpuMain';
+ gpuMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { title: 'DrmEventFlipComplete', start: 1, end: 1,
+ args: { data: { 'vblank.tv_sec': 0, 'vblank.tv_usec': 1000 } } }));
+ gpuMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { title: 'DrmEventFlipComplete', start: 3, end: 3,
+ args: { data: { 'vblank.tv_sec': 0, 'vblank.tv_usec': 2000 } } }));
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.rendering.addFrameTimeHistograms(
+ histograms, model, [new tr.model.um.Segment(0, 6)]);
+
+ // When computing frame times from DRM events, VBlank times should be used.
+ const hist = histograms.getHistogramNamed('frame_times');
+ assert.closeTo(1, hist.min, 1e-6);
+ assert.closeTo(1, hist.max, 1e-6);
+ assert.closeTo(1, hist.average, 1e-6);
+ });
+
+ test('frameTimes_surfaceFlingerEvents', function() {
+ const model = tr.c.TestUtils.newModel((model) => {
+ const browserMain = model.getOrCreateProcess(0).getOrCreateThread(0);
+ browserMain.name = 'CrBrowserMain';
+ browserMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { title: 'BenchmarkInstrumentation::DisplayRenderingStats',
+ start: 1, end: 1 }));
+ browserMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { title: 'BenchmarkInstrumentation::DisplayRenderingStats',
+ start: 2, end: 2 }));
+
+ const surfaceFlingerProcess = model.getOrCreateProcess(2);
+ surfaceFlingerProcess.name = 'SurfaceFlinger';
+ const surfaceFlingerThread = surfaceFlingerProcess.getOrCreateThread(2);
+ surfaceFlingerThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { title: 'vsync_before', start: 1, end: 1}));
+ surfaceFlingerThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { title: 'vsync_before', start: 3, end: 3}));
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.rendering.addFrameTimeHistograms(
+ histograms, model, [new tr.model.um.Segment(0, 6)]);
+
+ // Data from the Surface Flinger process should be used if it exists.
+ const hist = histograms.getHistogramNamed('frame_times');
+ assert.closeTo(2, hist.min, 1e-6);
+ assert.closeTo(2, hist.max, 1e-6);
+ assert.closeTo(2, hist.average, 1e-6);
+ });
+
+ test('frameLengths', function() {
+ const model = tr.c.TestUtils.newModel((model) => {
+ model.metadata = [{
+ name: 'metadata',
+ value: {
+ surface_flinger: {
+ refresh_period: 3,
+ },
+ },
+ }];
+
+ const browserMain = model.getOrCreateProcess(0).getOrCreateThread(0);
+ browserMain.name = 'CrBrowserMain';
+
+ const surfaceFlingerProcess = model.getOrCreateProcess(2);
+ surfaceFlingerProcess.name = 'SurfaceFlinger';
+ const surfaceFlingerThread = surfaceFlingerProcess.getOrCreateThread(2);
+ surfaceFlingerThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { title: 'vsync_before', start: 1, end: 1}));
+ surfaceFlingerThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { title: 'vsync_before', start: 4, end: 4}));
+ surfaceFlingerThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { title: 'vsync_before', start: 10, end: 10}));
+ surfaceFlingerThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { title: 'vsync_before', start: 13, end: 13}));
+ surfaceFlingerThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { title: 'vsync_before', start: 14, end: 14}));
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.rendering.addFrameTimeHistograms(
+ histograms, model, [new tr.model.um.Segment(0, 20)]);
+
+ // Frame lengths are 3/3, 6/3, 3/3, and 1/3. The last one is too small and
+ // should be filtered out. So, the final result is [1, 2, 1].
+ const hist = histograms.getHistogramNamed('frame_lengths');
+ assert.closeTo(3, hist.numValues, 1e-6);
+ assert.closeTo(1, hist.min, 1e-6);
+ assert.closeTo(2, hist.max, 1e-6);
+ assert.closeTo(4, hist.sum, 1e-6);
+ });
+
+ test('avgSurfaceFPS', function() {
+ const model = tr.c.TestUtils.newModel((model) => {
+ model.metadata = [{
+ name: 'metadata',
+ value: {
+ surface_flinger: {
+ refresh_period: 3,
+ },
+ },
+ }];
+
+ const browserMain = model.getOrCreateProcess(0).getOrCreateThread(0);
+ browserMain.name = 'CrBrowserMain';
+
+ const surfaceFlingerProcess = model.getOrCreateProcess(2);
+ surfaceFlingerProcess.name = 'SurfaceFlinger';
+ const surfaceFlingerThread = surfaceFlingerProcess.getOrCreateThread(2);
+ surfaceFlingerThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { title: 'vsync_before', start: 1, end: 1}));
+ surfaceFlingerThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { title: 'vsync_before', start: 4, end: 4}));
+ surfaceFlingerThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { title: 'vsync_before', start: 10, end: 10}));
+ surfaceFlingerThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { title: 'vsync_before', start: 13, end: 13}));
+ surfaceFlingerThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { title: 'vsync_before', start: 14, end: 14}));
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.rendering.addFrameTimeHistograms(
+ histograms, model, [new tr.model.um.Segment(0, 20)]);
+
+ // We have 3 frames (ignoring the very short ones) in 13 milliseconds.
+ const hist = histograms.getHistogramNamed('avg_surface_fps');
+ assert.closeTo(1, hist.numValues, 1e-6);
+ assert.closeTo(3 / 0.013, hist.min, 1e-6);
+ });
+
+ test('jankCount', function() {
+ const model = tr.c.TestUtils.newModel((model) => {
+ model.metadata = [{
+ name: 'metadata',
+ value: {
+ surface_flinger: {
+ refresh_period: 3,
+ },
+ },
+ }];
+
+ const browserMain = model.getOrCreateProcess(0).getOrCreateThread(0);
+ browserMain.name = 'CrBrowserMain';
+
+ const surfaceFlingerProcess = model.getOrCreateProcess(2);
+ surfaceFlingerProcess.name = 'SurfaceFlinger';
+ const surfaceFlingerThread = surfaceFlingerProcess.getOrCreateThread(2);
+ surfaceFlingerThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { title: 'vsync_before', start: 1, end: 1}));
+ surfaceFlingerThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { title: 'vsync_before', start: 4, end: 4}));
+ surfaceFlingerThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { title: 'vsync_before', start: 7, end: 7}));
+ surfaceFlingerThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { title: 'vsync_before', start: 13, end: 13}));
+ surfaceFlingerThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { title: 'vsync_before', start: 16, end: 16}));
+ surfaceFlingerThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { title: 'vsync_before', start: 79, end: 79}));
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.rendering.addFrameTimeHistograms(
+ histograms, model, [new tr.model.um.Segment(0, 100)]);
+
+ // There is 1 jank in [1, 1, 2, 1, 21]. The last long frame is a pause.
+ const hist = histograms.getHistogramNamed('jank_count');
+ assert.closeTo(1, hist.numValues, 1e-6);
+ assert.closeTo(1, hist.min, 1e-6);
+ });
+
+ test('uiFrameTimes', function() {
+ const model = tr.c.TestUtils.newModel((model) => {
+ const browserMain = model.getOrCreateProcess(0).getOrCreateThread(0);
+ browserMain.name = 'CrBrowserMain';
+ // Add four swap buffer events, at times 1, 2, 19, 21
+ browserMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { title: 'FramePresented', start: 1, end: 1 }));
+ browserMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { title: 'FramePresented', start: 2, end: 2 }));
+ browserMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { title: 'FramePresented', start: 19, end: 19 }));
+ browserMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { title: 'FramePresented', start: 21, end: 21 }));
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.rendering.addUIFrameTimeHistograms(
+ histograms, model, [new tr.model.um.Segment(0, 20)]);
+
+ // The fourth frame is outside the interaction perdiod and should be
+ // discarded. The durations between the remaining three frames are 1 and 17.
+ let hist = histograms.getHistogramNamed('ui_frame_times');
+ assert.closeTo(1, hist.min, 1e-6);
+ assert.closeTo(17, hist.max, 1e-6);
+ assert.closeTo(9, hist.average, 1e-6);
+
+ // One of the two frame times is not smooth.
+ hist = histograms.getHistogramNamed('ui_percentage_smooth');
+ assert.closeTo(50, hist.min, 1e-6);
+ assert.closeTo(50, hist.max, 1e-6);
+ assert.closeTo(50, hist.average, 1e-6);
+ });
+
+ test('uiFrameTimesWithEnvironmentArg', function() {
+ const model = tr.c.TestUtils.newModel((model) => {
+ const browserMain = model.getOrCreateProcess(0).getOrCreateThread(0);
+ browserMain.name = 'CrBrowserMain';
+ // Add four swap buffer events, at times 1, 2, 3, 19
+ browserMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ title: 'FramePresented',
+ start: 1,
+ end: 1,
+ args: { environment: 'ash' }
+ }));
+ browserMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ title: 'FramePresented',
+ start: 2,
+ end: 2,
+ args: { environment: 'ash' }
+ }));
+ browserMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ title: 'FramePresented',
+ start: 3,
+ end: 3,
+ args: { environment: 'browser' }
+ }));
+ browserMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ title: 'FramePresented',
+ start: 19,
+ end: 19,
+ args: { environment: 'ash' }
+ }));
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.rendering.addUIFrameTimeHistograms(
+ histograms, model, [new tr.model.um.Segment(0, 20)]);
+
+ // The 3rd frame is from 'browser' and should be discarded. Remaining
+ // timestamps are 1, 2, and 19 which indicate frame times of 1 and 17.
+ const hist = histograms.getHistogramNamed('ui_frame_times');
+ assert.closeTo(1, hist.min, 1e-6);
+ assert.closeTo(17, hist.max, 1e-6);
+ assert.closeTo(2, hist.numValues, 1e-6);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/rendering/latency.html b/chromium/third_party/catapult/tracing/tracing/metrics/rendering/latency.html
new file mode 100644
index 00000000000..cbd691dacf3
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/rendering/latency.html
@@ -0,0 +1,138 @@
+<!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/unit.html">
+<link rel="import" href="/tracing/extras/chrome/cc/input_latency_async_slice.html">
+<link rel="import" href="/tracing/model/async_slice_group.html">
+<link rel="import" href="/tracing/model/helpers/chrome_model_helper.html">
+<link rel="import" href="/tracing/value/histogram.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview This file contains implementations of the following metrics.
+ *
+ * input_event_latency
+ * ===================
+ * The distribution of durations between the time when an input event is
+ * generated to the time when the GPU service swaps buffers due to the input
+ * event. The time when an input is generated is the first of the following
+ * three timestamps that we can get:
+ *
+ * 1. Original kernel timestamp of the input event.
+ * 2. Timestamp when the UI event is created.
+ * 3. Timestamp when the input event is sent from RenderWidgetHost to the
+ * renderer.
+ *
+ * main_thread_scroll_latency
+ * ==========================
+ * The distribution of durations between the time when the main thread scroll
+ * listener update is begun to the time when the GPU service swaps buffers due
+ * to the scroll event.
+ *
+ * first_gesture_scroll_update_latency
+ * ===================================
+ * This shows the latency, as defined in input_event_latency, of the first
+ * gesture scroll update input event. The first event can often get delayed by
+ * work related to page loading.
+ */
+tr.exportTo('tr.metrics.rendering', function() {
+ // Interesting latency info component names.
+ const BEGIN_SCROLL_UPDATE_COMP_NAME =
+ 'LATENCY_BEGIN_SCROLL_LISTENER_UPDATE_MAIN_COMPONENT';
+ const END_COMP_NAME = 'INPUT_EVENT_GPU_SWAP_BUFFER_COMPONENT';
+
+ function* iterAsyncEvents_(processHelpers, ranges, processEventFn) {
+ for (const processHelper of processHelpers) {
+ const process = processHelper.process;
+ for (const event of process.getDescendantEventsInSortedRanges(
+ ranges, container => container instanceof tr.model.AsyncSliceGroup)) {
+ yield* processEventFn(event);
+ }
+ }
+ }
+
+ function* processInputLatencyEvent(event) {
+ if (!(event instanceof tr.e.cc.InputLatencyAsyncSlice)) return;
+ const latency = event.inputLatency;
+ if (latency === undefined) return;
+ yield tr.b.Unit.timestampFromUs(latency);
+ }
+
+ function* processLatencyEvent(event) {
+ if (event.title !== 'Latency::ScrollUpdate' ||
+ !('data' in event.args) || !(END_COMP_NAME in event.args.data)) {
+ return;
+ }
+ const data = event.args.data;
+ const endTime = data[END_COMP_NAME].time;
+ if (BEGIN_SCROLL_UPDATE_COMP_NAME in data) {
+ yield tr.b.Unit.timestampFromUs(
+ endTime - data[BEGIN_SCROLL_UPDATE_COMP_NAME].time);
+ } else {
+ throw new Error('LatencyInfo has no begin component');
+ }
+ }
+
+ function* processGestureScrollUpdateLatencyEvent(event) {
+ if (event.title !== 'InputLatency::GestureScrollUpdate') return;
+ if (!(event instanceof tr.e.cc.InputLatencyAsyncSlice)) {
+ throw new Error('Gesture scroll update latency event is not an ' +
+ 'instance of tr.e.cc.InputLatencyAsyncSlice');
+ }
+ const latency = event.inputLatency;
+ if (latency === undefined) return;
+ yield [event.start, tr.b.Unit.timestampFromUs(latency)];
+ }
+
+ function addLatencyHistograms(histograms, model, segments) {
+ const modelHelper = model.getOrCreateHelper(
+ tr.model.helpers.ChromeModelHelper);
+ if (!modelHelper) return;
+
+ const ranges = segments.map(s => s.boundsRange);
+ const inputEventLatencies = [...iterAsyncEvents_(
+ modelHelper.browserHelpers, ranges, processInputLatencyEvent)];
+ histograms.createHistogram(
+ 'input_event_latency',
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,
+ inputEventLatencies,
+ { binBoundaries: tr.v.HistogramBinBoundaries.createLinear(0, 50, 20),
+ summaryOptions: tr.metrics.rendering.SUMMARY_OPTIONS,
+ description: 'Input event latencies.' });
+
+ const mainThreadScrollLatencies = [...iterAsyncEvents_(
+ Object.values(modelHelper.rendererHelpers), ranges,
+ processLatencyEvent)];
+ histograms.createHistogram(
+ 'main_thread_scroll_latency',
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,
+ mainThreadScrollLatencies,
+ { binBoundaries: tr.v.HistogramBinBoundaries.createLinear(0, 50, 50),
+ summaryOptions: tr.metrics.rendering.SUMMARY_OPTIONS,
+ description: 'Main thread scroll latencies.' });
+
+ const gestureScrollUpdateLatencies = [...iterAsyncEvents_(
+ modelHelper.browserHelpers, ranges,
+ processGestureScrollUpdateLatencyEvent)].sort((x, y) => x[0] - y[0]);
+ if (gestureScrollUpdateLatencies.length) {
+ histograms.createHistogram(
+ 'first_gesture_scroll_update_latency',
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,
+ gestureScrollUpdateLatencies[0][1],
+ { binBoundaries: tr.v.HistogramBinBoundaries.createLinear(0, 50, 20),
+ summaryOptions: tr.metrics.rendering.SUMMARY_OPTIONS,
+ description: 'Latency of the first gesture scroll update.' });
+ }
+ }
+
+ return {
+ addLatencyHistograms,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/rendering/latency_test.html b/chromium/third_party/catapult/tracing/tracing/metrics/rendering/latency_test.html
new file mode 100644
index 00000000000..4a68b8e4655
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/rendering/latency_test.html
@@ -0,0 +1,119 @@
+<!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/metrics/rendering/latency.html">
+<link rel="import" href="/tracing/model/user_model/segment.html">
+<link rel="import" href="/tracing/value/histogram_set.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ test('inputEventLatency', function() {
+ const model = tr.c.TestUtils.newModel((model) => {
+ const browser = model.getOrCreateProcess(0).getOrCreateThread(0);
+ browser.name = 'CrBrowserMain';
+ browser.asyncSliceGroup.push(tr.c.TestUtils.newAsyncSliceEx({
+ title: 'InputLatency::GestureScrollUpdate',
+ start: 9, end: 10,
+ args: {
+ data: {
+ INPUT_EVENT_GPU_SWAP_BUFFER_COMPONENT: {time: 10000},
+ INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT: {time: 9000}
+ }
+ }
+ }));
+ browser.asyncSliceGroup.push(tr.c.TestUtils.newAsyncSliceEx({
+ title: 'InputLatency::GestureScrollUpdate',
+ start: 7, end: 8,
+ args: {
+ data: {
+ INPUT_EVENT_GPU_SWAP_BUFFER_COMPONENT: {time: 8000},
+ INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT: {time: 7000},
+ INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT: {time: 6000},
+ }
+ }
+ }));
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.rendering.addLatencyHistograms(
+ histograms, model, [new tr.model.um.Segment(0, 10)]);
+
+ // The first input latency is 10 - 9 = 1. The second input latency is
+ // 8 - 6 = 2.
+ const hist = histograms.getHistogramNamed('input_event_latency');
+ assert.closeTo(1, hist.min, 1e-6);
+ assert.closeTo(2, hist.max, 1e-6);
+ assert.closeTo(1.5, hist.average, 1e-6);
+ });
+
+ test('mainThreadScrollLatency', function() {
+ const model = tr.c.TestUtils.newModel((model) => {
+ model.getOrCreateProcess(0).getOrCreateThread(0).name = 'CrBrowserMain';
+ const renderer = model.getOrCreateProcess(1).getOrCreateThread(0);
+ renderer.name = 'CrRendererMain';
+ renderer.asyncSliceGroup.push(tr.c.TestUtils.newAsyncSliceEx({
+ title: 'Latency::ScrollUpdate',
+ start: 9, end: 10,
+ args: {
+ data: {
+ INPUT_EVENT_GPU_SWAP_BUFFER_COMPONENT: {time: 10000},
+ LATENCY_BEGIN_SCROLL_LISTENER_UPDATE_MAIN_COMPONENT: {time: 9000}
+ }
+ }
+ }));
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.rendering.addLatencyHistograms(
+ histograms, model, [new tr.model.um.Segment(0, 10)]);
+
+ const hist = histograms.getHistogramNamed('main_thread_scroll_latency');
+ assert.closeTo(1, hist.min, 1e-6);
+ assert.closeTo(1, hist.max, 1e-6);
+ assert.closeTo(1, hist.average, 1e-6);
+ });
+
+ test('firstGestureScrollUpdateLatency', function() {
+ const model = tr.c.TestUtils.newModel((model) => {
+ const browser = model.getOrCreateProcess(0).getOrCreateThread(0);
+ browser.name = 'CrBrowserMain';
+ browser.asyncSliceGroup.push(tr.c.TestUtils.newAsyncSliceEx({
+ title: 'InputLatency::GestureScrollUpdate',
+ start: 10, end: 11,
+ args: {
+ data: {
+ INPUT_EVENT_GPU_SWAP_BUFFER_COMPONENT: {time: 10000},
+ INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT: {time: 9000}
+ }
+ }
+ }));
+ browser.asyncSliceGroup.push(tr.c.TestUtils.newAsyncSliceEx({
+ title: 'InputLatency::GestureScrollUpdate',
+ start: 7, end: 8,
+ args: {
+ data: {
+ INPUT_EVENT_GPU_SWAP_BUFFER_COMPONENT: {time: 8000},
+ INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT: {time: 7000},
+ INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT: {time: 6000},
+ }
+ }
+ }));
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.rendering.addLatencyHistograms(
+ histograms, model, [new tr.model.um.Segment(0, 20)]);
+
+ // The chronologically first gesture scroll update latency is 8 - 6 = 2.
+ const hist = histograms.getHistogramNamed(
+ 'first_gesture_scroll_update_latency');
+ assert.closeTo(2, hist.min, 1e-6);
+ assert.closeTo(2, hist.max, 1e-6);
+ assert.closeTo(2, hist.average, 1e-6);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/rendering/pipeline.html b/chromium/third_party/catapult/tracing/tracing/metrics/rendering/pipeline.html
new file mode 100644
index 00000000000..1b68e5d6a31
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/rendering/pipeline.html
@@ -0,0 +1,258 @@
+<!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/unit.html">
+<link rel="import" href="/tracing/model/helpers/chrome_model_helper.html">
+<link rel="import" href="/tracing/value/diagnostics/breakdown.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview This file contains implementations of the following metrics.
+ *
+ * TODO(crbug.com/872334): document pipeline:* metrics here.
+ */
+tr.exportTo('tr.metrics.rendering', function() {
+ function eventIsValidGraphicsEvent_(event, eventMap) {
+ if (event.title !== 'Graphics.Pipeline' || !event.bindId || !event.args ||
+ !event.args.step) {
+ return false;
+ }
+ const bindId = event.bindId;
+ if (eventMap.has(bindId) && event.args.step in eventMap.get(bindId)) {
+ // It is possible for a client to submit multiple compositor frames for
+ // one begin-message. So most steps can be present multiple times.
+ // However, a begin-frame is issued only once, and received only once. So
+ // these steps should not be repeated.
+ if (event.args.step === 'IssueBeginFrame' ||
+ event.args.step === 'ReceiveBeginFrame') {
+ throw new Error('Unexpected duplicate step: ' + event.args.step);
+ }
+ return false;
+ }
+ return true;
+ }
+
+ function generateBreakdownForCompositorPipelineInClient_(flow) {
+ const breakdown = new tr.v.d.Breakdown();
+ breakdown.set('time before GenerateRenderPass',
+ flow.GenerateRenderPass.start - flow.ReceiveBeginFrame.start);
+ breakdown.set('GenerateRenderPass duration',
+ flow.GenerateRenderPass.duration);
+ breakdown.set('GenerateCompositorFrame duration',
+ flow.GenerateCompositorFrame.duration);
+ breakdown.set('SubmitCompositorFrame duration',
+ flow.SubmitCompositorFrame.duration);
+ return breakdown;
+ }
+
+ function generateBreakdownForCompositorPipelineInService_(flow) {
+ const breakdown = new tr.v.d.Breakdown();
+ breakdown.set('Processing CompositorFrame on reception',
+ flow.ReceiveCompositorFrame.duration);
+ breakdown.set('Delay before SurfaceAggregation',
+ flow.SurfaceAggregation.start - flow.ReceiveCompositorFrame.end);
+ breakdown.set('SurfaceAggregation duration',
+ flow.SurfaceAggregation.duration);
+ return breakdown;
+ }
+
+ function generateBreakdownForDraw_(drawEvent) {
+ const breakdown = new tr.v.d.Breakdown();
+ for (const slice of drawEvent.subSlices) {
+ breakdown.set(slice.title, slice.duration);
+ }
+ return breakdown;
+ }
+
+ function getDisplayCompositorThread_(model) {
+ const chromeHelper = model.getOrCreateHelper(
+ tr.model.helpers.ChromeModelHelper);
+ const gpuHelper = chromeHelper.gpuHelper;
+ if (gpuHelper) {
+ const thread =
+ gpuHelper.process.findAtMostOneThreadNamed('VizCompositorThread');
+ if (thread) {
+ return thread;
+ }
+ }
+ if (!chromeHelper.browserProcess) return null;
+ return chromeHelper.browserProcess.findAtMostOneThreadNamed(
+ 'CrBrowserMain');
+ }
+
+ function getRasterTaskTimes(sourceFrameNumber, model) {
+ const modelHelper = model.getOrCreateHelper(
+ tr.model.helpers.ChromeModelHelper);
+
+ const renderers = modelHelper.telemetryHelper.renderersWithIR;
+ const rasterThreads = renderers[0].rasterWorkerThreads;
+
+ let earliestStart = undefined;
+ let lastEnd = undefined;
+ for (const rasterThread of rasterThreads) {
+ for (const slice of [...rasterThread.
+ findTopmostSlicesNamed('TaskGraphRunner::RunTask')]) {
+ if (slice.args &&
+ slice.args.source_frame_number_ &&
+ slice.args.source_frame_number_ === sourceFrameNumber) {
+ if (earliestStart === undefined || slice.start < earliestStart) {
+ earliestStart = slice.start;
+ }
+ if (lastEnd === undefined || slice.end > lastEnd) {
+ lastEnd = slice.end;
+ }
+ }
+ }
+ }
+ return {start: earliestStart, end: lastEnd};
+ }
+
+ function addPipelineHistograms(histograms, model, segments) {
+ const ranges = segments.map(s => s.boundsRange);
+ const bindEvents = new Map();
+ for (const thread of model.getAllThreads()) {
+ for (const event of thread.sliceGroup.childEvents()) {
+ if (!eventIsValidGraphicsEvent_(event, bindEvents)) continue;
+ for (const range of ranges) {
+ if (range.containsExplicitRangeInclusive(event.start, event.end)) {
+ if (!bindEvents.has(event.bindId)) bindEvents.set(event.bindId, {});
+ break;
+ }
+ }
+ if (bindEvents.has(event.bindId)) {
+ bindEvents.get(event.bindId)[event.args.step] = event;
+ }
+ }
+ }
+
+ const dcThread = getDisplayCompositorThread_(model);
+ const drawEvents = {};
+ if (dcThread) {
+ const events =
+ [...dcThread.findTopmostSlicesNamed('Graphics.Pipeline.DrawAndSwap')];
+ for (const segment of segments) {
+ const filteredEvents = segment.boundsRange.filterArray(events,
+ evt => evt.start);
+ for (const event of filteredEvents) {
+ if ((event.args && event.args.status === 'canceled') ||
+ !event.id.startsWith(':ptr:')) {
+ continue;
+ }
+ const id = parseInt(event.id.substring(5), 16);
+ if (id in drawEvents) {
+ throw new Error('Duplicate draw events: ' + id);
+ }
+ drawEvents[id] = event;
+ }
+ }
+ }
+
+ const issueToReceipt = histograms.createHistogram(
+ 'pipeline:begin_frame_transport',
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,
+ [], {
+ description: 'Latency of begin-frame message from the display ' +
+ 'compositor to the client, including the IPC latency and task-' +
+ 'queue time in the client.',
+ summaryOptions: tr.metrics.rendering.SUMMARY_OPTIONS,
+ });
+ const issueToRasterStart = histograms.createHistogram(
+ 'pipeline:begin_frame_to_raster_start',
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,
+ [], {
+ description: 'Latency between begin-frame message and ' +
+ 'the beginning of the first CompositorTask run in the compositor.',
+ summaryOptions: tr.metrics.rendering.SUMMARY_OPTIONS,
+ });
+ const issueToRasterEnd = histograms.createHistogram(
+ 'pipeline:begin_frame_to_raster_end',
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,
+ [], {
+ description: 'Latency between begin-frame message and ' +
+ 'the end of the last CompositorTask run in the compositor.',
+ summaryOptions: tr.metrics.rendering.SUMMARY_OPTIONS,
+ });
+ const receiptToSubmit = histograms.createHistogram(
+ 'pipeline:begin_frame_to_frame_submission',
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,
+ [], {
+ description: 'Latency between begin-frame reception and ' +
+ 'CompositorFrame submission in the renderer.',
+ summaryOptions: tr.metrics.rendering.SUMMARY_OPTIONS,
+ });
+ const submitToAggregate = histograms.createHistogram(
+ 'pipeline:frame_submission_to_display',
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,
+ [], {
+ description: 'Latency between CompositorFrame submission in the ' +
+ 'renderer to display in the display-compositor, including IPC ' +
+ 'latency, task-queue time in the display-compositor, and ' +
+ 'additional processing (e.g. surface-sync etc.)',
+ summaryOptions: tr.metrics.rendering.SUMMARY_OPTIONS,
+ });
+ const aggregateToDraw = histograms.createHistogram(
+ 'pipeline:draw',
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,
+ [], {
+ description: 'How long it takes for the gpu-swap step.',
+ summaryOptions: tr.metrics.rendering.SUMMARY_OPTIONS,
+ });
+
+ for (const flow of bindEvents.values()) {
+ // Report only for the cases that go through the complete pipeline.
+ if (!flow.IssueBeginFrame || !flow.ReceiveBeginFrame ||
+ !flow.SubmitCompositorFrame || !flow.SurfaceAggregation) {
+ continue;
+ }
+
+ const sourceFrameNumber = flow.SubmitCompositorFrame.parentSlice
+ .args.source_frame_number_;
+
+ const rasterDuration =
+ getRasterTaskTimes(sourceFrameNumber, model);
+
+ issueToReceipt.addSample(flow.ReceiveBeginFrame.start -
+ flow.IssueBeginFrame.start);
+ receiptToSubmit.addSample(
+ flow.SubmitCompositorFrame.end - flow.ReceiveBeginFrame.start,
+ {breakdown: generateBreakdownForCompositorPipelineInClient_(flow)});
+ submitToAggregate.addSample(
+ flow.SurfaceAggregation.end - flow.SubmitCompositorFrame.end,
+ {breakdown: generateBreakdownForCompositorPipelineInService_(flow)});
+
+ if (rasterDuration.start && rasterDuration.end) {
+ const receiveToStart = rasterDuration.start -
+ flow.ReceiveBeginFrame.start;
+ const receiveToEnd = rasterDuration.end - flow.ReceiveBeginFrame.end;
+
+ // receiveToStart can be negative if the earliest raster task for
+ // a frame starts before receiveBeginFrame starts.
+ // The same is true for receiveToEnd
+ // Only positive samples are added.
+ if (receiveToEnd > 0) {
+ issueToRasterStart.addSample(receiveToStart > 0 ? receiveToStart : 0);
+ issueToRasterEnd.addSample(receiveToEnd);
+ }
+ }
+ if (flow.SurfaceAggregation.args &&
+ flow.SurfaceAggregation.args.display_trace) {
+ const displayTrace = flow.SurfaceAggregation.args.display_trace;
+ if (!(displayTrace in drawEvents)) continue;
+ const drawEvent = drawEvents[displayTrace];
+ aggregateToDraw.addSample(drawEvent.duration,
+ {breakdown: generateBreakdownForDraw_(drawEvent)});
+ }
+ }
+ }
+
+ return {
+ addPipelineHistograms,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/rendering/pipeline_test.html b/chromium/third_party/catapult/tracing/tracing/metrics/rendering/pipeline_test.html
new file mode 100644
index 00000000000..332767c0e79
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/rendering/pipeline_test.html
@@ -0,0 +1,366 @@
+<!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/metrics/rendering/pipeline.html">
+<link rel="import" href="/tracing/model/user_model/segment.html">
+<link rel="import" href="/tracing/value/histogram_set.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ function addPipelineForOneFrame(compositor, renderer, rasterWorker,
+ id, frame, displayTrace) {
+ const EVENT_NAME = 'Graphics.Pipeline';
+ if (frame.IssueBeginFrame) {
+ compositor.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { title: EVENT_NAME,
+ start: frame.IssueBeginFrame, duration: 1, bindId: id,
+ args: {step: 'IssueBeginFrame'}}));
+ }
+ if (frame.ReceiveBeginFrame) {
+ renderer.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { title: EVENT_NAME,
+ start: frame.ReceiveBeginFrame, duration: 1, bindId: id,
+ args: {step: 'ReceiveBeginFrame'}}));
+ }
+ renderer.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { title: EVENT_NAME,
+ start: frame.GenerateRenderPass, duration: 1, bindId: id,
+ args: {step: 'GenerateRenderPass'}}));
+ renderer.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { title: EVENT_NAME,
+ start: frame.GenerateCompositorFrame, duration: 1, bindId: id,
+ args: {step: 'GenerateCompositorFrame'}}));
+ renderer.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { title: EVENT_NAME,
+ start: frame.SubmitCompositorFrame, duration: 1, bindId: id,
+ args: {source_frame_number_: id}}));
+ renderer.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { title: EVENT_NAME,
+ start: frame.SubmitCompositorFrame, duration: 1, bindId: id,
+ args: {step: 'SubmitCompositorFrame'}}));
+ compositor.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { title: EVENT_NAME,
+ start: frame.ReceiveCompositorFrame, duration: 1, bindId: id,
+ args: {step: 'ReceiveCompositorFrame'}}));
+ compositor.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { title: EVENT_NAME,
+ start: frame.SurfaceAggregation, duration: 1, bindId: id,
+ args: {step: 'SurfaceAggregation', display_trace: displayTrace}}));
+ renderer.sliceGroup.createSubSlices();
+ rasterWorker.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { title: 'TaskGraphRunner::RunTask',
+ start: frame.rasterTaskStart, duration: 6, bindId: id,
+ args: {source_frame_number_: id}}));
+ }
+
+ function addDrawSlice(compositor, displayTrace, start, steps, opt_args) {
+ const EVENT_NAME = 'Graphics.Pipeline.DrawAndSwap';
+ let totalDuration = 0;
+ for (const duration of Object.values(steps)) {
+ totalDuration += duration;
+ }
+ const slice = tr.c.TestUtils.newAsyncSliceNamed(
+ EVENT_NAME, start, totalDuration);
+ slice.id = ':ptr:' + displayTrace;
+ slice.args = opt_args;
+ compositor.sliceGroup.pushSlice(slice);
+ totalDuration = 0;
+ for (const step in steps) {
+ slice.subSlices.push(tr.c.TestUtils.newAsyncSliceNamed(
+ step, start + totalDuration, steps[step]));
+ totalDuration += steps[step];
+ }
+ }
+
+ test('graphicsPipeline', function() {
+ const model = tr.c.TestUtils.newModel((model) => {
+ const browserMain = model.getOrCreateProcess(0).getOrCreateThread(0);
+ browserMain.name = 'CrBrowserMain';
+ const rendererCompositor =
+ model.getOrCreateProcess(1).getOrCreateThread(1);
+ rendererCompositor.name = 'Compositor';
+
+ // Creates a renderer thread
+ const renderer = model.getOrCreateProcess(2).getOrCreateThread(2);
+ renderer.name = 'CrRendererMain';
+ renderer.asyncSliceGroup.push(
+ tr.c.TestUtils.newAsyncSliceNamed('Interaction.Action', 1, 1));
+
+ const rasterWorker = model.getOrCreateProcess(2).getOrCreateThread(5);
+ rasterWorker.name = 'CompositorTileWorker';
+
+ addPipelineForOneFrame(browserMain, rendererCompositor,
+ rasterWorker, 1, {
+ IssueBeginFrame: 1, ReceiveBeginFrame: 2, GenerateRenderPass: 3,
+ GenerateCompositorFrame: 4, SubmitCompositorFrame: 5,
+ ReceiveCompositorFrame: 6, SurfaceAggregation: 10,
+ rasterTaskStart: 2
+ });
+ addPipelineForOneFrame(browserMain, rendererCompositor,
+ rasterWorker, 2, {
+ IssueBeginFrame: 15, ReceiveBeginFrame: 16,
+ GenerateRenderPass: 17, GenerateCompositorFrame: 18,
+ SubmitCompositorFrame: 19, ReceiveCompositorFrame: 20,
+ SurfaceAggregation: 21, rasterTaskStart: 22
+ });
+ addPipelineForOneFrame(browserMain, rendererCompositor,
+ rasterWorker, 3, {
+ IssueBeginFrame: 32, ReceiveBeginFrame: 34,
+ GenerateRenderPass: 35, GenerateCompositorFrame: 36,
+ SubmitCompositorFrame: 37, SubmitCompositorFrame: 38,
+ ReceiveCompositorFrame: 41, SurfaceAggregation: 44,
+ rasterTaskStart: 25
+ });
+ });
+
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.rendering.addPipelineHistograms(
+ histograms, model, [new tr.model.um.Segment(0, 50)]);
+
+
+ const beginFrameTransport = histograms.getHistogramNamed(
+ 'pipeline:begin_frame_transport');
+ const frameSubmission = histograms.getHistogramNamed(
+ 'pipeline:begin_frame_to_frame_submission');
+ const surfaceAggregation = histograms.getHistogramNamed(
+ 'pipeline:frame_submission_to_display');
+ const rasterStart = histograms.getHistogramNamed(
+ 'pipeline:begin_frame_to_raster_start');
+ const rasterEnd = histograms.getHistogramNamed(
+ 'pipeline:begin_frame_to_raster_end');
+ assert.closeTo(beginFrameTransport.average, 4 / 3, 1e-6);
+ assert.closeTo(frameSubmission.average, 13 / 3, 1e-6);
+ assert.closeTo(surfaceAggregation.average, 13 / 3, 1e-6);
+ assert.closeTo(rasterStart.average, 6 / 2, 1e-6);
+ assert.closeTo(rasterEnd.average, 16 / 2, 1e-6);
+ });
+
+ test('graphicsPipeline_duplicateSteps', function() {
+ const model = tr.c.TestUtils.newModel((model) => {
+ const browserMain = model.getOrCreateProcess(0).getOrCreateThread(0);
+ browserMain.name = 'CrBrowserMain';
+ const rendererCompositor =
+ model.getOrCreateProcess(1).getOrCreateThread(1);
+ rendererCompositor.name = 'Compositor';
+
+ // Creates a renderer thread
+ const renderer = model.getOrCreateProcess(2).getOrCreateThread(2);
+ renderer.name = 'CrRendererMain';
+ renderer.asyncSliceGroup.push(
+ tr.c.TestUtils.newAsyncSliceNamed('Interaction.Action', 1, 1));
+
+ const rasterWorker = model.getOrCreateProcess(2).getOrCreateThread(5);
+ rasterWorker.name = 'CompositorTileWorker';
+
+ addPipelineForOneFrame(browserMain, rendererCompositor,
+ rasterWorker, 1, {
+ IssueBeginFrame: 1, ReceiveBeginFrame: 2, GenerateRenderPass: 3,
+ GenerateCompositorFrame: 4, SubmitCompositorFrame: 5,
+ ReceiveCompositorFrame: 6, SurfaceAggregation: 9,
+ rasterTaskStart: 3
+ });
+
+ // Add duplicate steps for SubmitCompositorFrame and the subsequent
+ // steps for the same trace-id.
+ addPipelineForOneFrame(browserMain, rendererCompositor,
+ rasterWorker, 1, {
+ GenerateRenderPass: 10, GenerateCompositorFrame: 11,
+ SubmitCompositorFrame: 12, ReceiveCompositorFrame: 15,
+ SurfaceAggregation: 18, rasterTaskStart: 3
+ });
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.rendering.addPipelineHistograms(
+ histograms, model,
+ [new tr.model.um.Segment(0, 50), new tr.model.um.Segment(0, 20)]);
+
+ const beginFrameTransport = histograms.getHistogramNamed(
+ 'pipeline:begin_frame_transport');
+ const frameSubmission = histograms.getHistogramNamed(
+ 'pipeline:begin_frame_to_frame_submission');
+ const surfaceAggregation = histograms.getHistogramNamed(
+ 'pipeline:frame_submission_to_display');
+ const rasterStart = histograms.getHistogramNamed(
+ 'pipeline:begin_frame_to_raster_start');
+ const rasterEnd = histograms.getHistogramNamed(
+ 'pipeline:begin_frame_to_raster_end');
+ assert.strictEqual(beginFrameTransport.average, 1);
+ assert.strictEqual(frameSubmission.average, 4);
+ assert.strictEqual(surfaceAggregation.average, 4);
+ assert.strictEqual(rasterStart.average, 1);
+ assert.strictEqual(rasterEnd.average, 6);
+ });
+
+ test('graphicsPipeline_duplicateRenderers', function() {
+ const model = tr.c.TestUtils.newModel((model) => {
+ const browserMain = model.getOrCreateProcess(0).getOrCreateThread(0);
+ browserMain.name = 'CrBrowserMain';
+ const rendererCompositor =
+ model.getOrCreateProcess(1).getOrCreateThread(1);
+ rendererCompositor.name = 'Compositor';
+
+ // Creates a renderer thread
+ const rendererWithIR = model.getOrCreateProcess(2).getOrCreateThread(2);
+ rendererWithIR.name = 'CrRendererMain';
+ rendererWithIR.asyncSliceGroup.push(
+ tr.c.TestUtils.newAsyncSliceNamed('Interaction.Action', 1, 1));
+
+ const rasterWorkerWithIR = model.getOrCreateProcess(2)
+ .getOrCreateThread(5);
+ rasterWorkerWithIR.name = 'CompositorTileWorker';
+
+ const rendererWithoutIR = model.getOrCreateProcess(3)
+ .getOrCreateThread(2);
+ rendererWithoutIR.name = 'CrRendererMain';
+
+ const rasterWorkerWithoutIR = model.getOrCreateProcess(3)
+ .getOrCreateThread(5);
+ rasterWorkerWithoutIR.name = 'CompositorTileWorker';
+
+ addPipelineForOneFrame(browserMain, rendererCompositor,
+ rasterWorkerWithIR, 1, {
+ IssueBeginFrame: 1, ReceiveBeginFrame: 2, GenerateRenderPass: 3,
+ GenerateCompositorFrame: 4, SubmitCompositorFrame: 5,
+ ReceiveCompositorFrame: 6, SurfaceAggregation: 10,
+ rasterTaskStart: 3
+ });
+ addPipelineForOneFrame(browserMain, rendererCompositor,
+ rasterWorkerWithoutIR, 2, {
+ IssueBeginFrame: 1, ReceiveBeginFrame: 2, GenerateRenderPass: 3,
+ GenerateCompositorFrame: 4, SubmitCompositorFrame: 5,
+ ReceiveCompositorFrame: 6, SurfaceAggregation: 10,
+ rasterTaskStart: 4
+ });
+ });
+
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.rendering.addPipelineHistograms(
+ histograms, model, [new tr.model.um.Segment(0, 50)]);
+
+ const rasterStart = histograms.getHistogramNamed(
+ 'pipeline:begin_frame_to_raster_start');
+ const rasterEnd = histograms.getHistogramNamed(
+ 'pipeline:begin_frame_to_raster_end');
+ assert.strictEqual(rasterStart.average, 1);
+ assert.strictEqual(rasterEnd.average, 6);
+ });
+
+ test('graphicsPipeline_drawSteps', function() {
+ const model = tr.c.TestUtils.newModel((model) => {
+ const browserMain = model.getOrCreateProcess(0).getOrCreateThread(0);
+ browserMain.name = 'CrBrowserMain';
+ const rendererCompositor =
+ model.getOrCreateProcess(1).getOrCreateThread(1);
+ rendererCompositor.name = 'Compositor';
+
+ // Creates a renderer thread
+ const renderer = model.getOrCreateProcess(2).getOrCreateThread(2);
+ renderer.name = 'CrRendererMain';
+ renderer.asyncSliceGroup.push(
+ tr.c.TestUtils.newAsyncSliceNamed('Interaction.Action', 1, 1));
+
+ const rasterWorker = model.getOrCreateProcess(2).getOrCreateThread(5);
+ rasterWorker.name = 'CompositorTileWorker';
+
+ const displayTrace = '1';
+ addPipelineForOneFrame(browserMain, rendererCompositor,
+ rasterWorker, 1, {
+ IssueBeginFrame: 1, ReceiveBeginFrame: 2, GenerateRenderPass: 3,
+ GenerateCompositorFrame: 4, SubmitCompositorFrame: 5,
+ ReceiveCompositorFrame: 6, SurfaceAggregation: 9,
+ rasterTaskStart: 3
+ }, displayTrace);
+
+ addDrawSlice(browserMain, displayTrace, 10,
+ {Draw: 2, Swap: 1, WaitForAck: 5}, {});
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.rendering.addPipelineHistograms(
+ histograms, model, [new tr.model.um.Segment(0, 50)]);
+
+ const drawHistogram = histograms.getHistogramNamed('pipeline:draw');
+ assert.strictEqual(drawHistogram.average, 8);
+ });
+
+ test('graphicsPipeline_drawCanceled', function() {
+ const model = tr.c.TestUtils.newModel((model) => {
+ const browserMain = model.getOrCreateProcess(0).getOrCreateThread(0);
+ browserMain.name = 'CrBrowserMain';
+ const rendererCompositor =
+ model.getOrCreateProcess(1).getOrCreateThread(1);
+ rendererCompositor.name = 'Compositor';
+
+ // Creates a renderer thread
+ const renderer = model.getOrCreateProcess(2).getOrCreateThread(2);
+ renderer.name = 'CrRendererMain';
+ renderer.asyncSliceGroup.push(
+ tr.c.TestUtils.newAsyncSliceNamed('Interaction.Action', 1, 1));
+
+ const rasterWorker = model.getOrCreateProcess(2).getOrCreateThread(5);
+ rasterWorker.name = 'CompositorTileWorker';
+
+ const displayTrace = '1';
+ addPipelineForOneFrame(browserMain, rendererCompositor,
+ rasterWorker, 1, {
+ IssueBeginFrame: 1, ReceiveBeginFrame: 2, GenerateRenderPass: 3,
+ GenerateCompositorFrame: 4, SubmitCompositorFrame: 5,
+ ReceiveCompositorFrame: 6, SurfaceAggregation: 9,
+ rasterTaskStart: 3
+ }, displayTrace);
+
+ addDrawSlice(browserMain, displayTrace, 10,
+ {Draw: 2, Swap: 1, WaitForAck: 5}, {status: 'canceled'});
+ addDrawSlice(browserMain, displayTrace, 15,
+ {Draw: 2, Swap: 1, WaitForAck: 5});
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.rendering.addPipelineHistograms(
+ histograms, model, [new tr.model.um.Segment(0, 50)]);
+
+ const drawHistogram = histograms.getHistogramNamed('pipeline:draw');
+ assert.strictEqual(drawHistogram.average, 8);
+ });
+
+ test('graphicsPipeline_receiveCFAfterAnimation', function() {
+ const model = tr.c.TestUtils.newModel((model) => {
+ const browserMain = model.getOrCreateProcess(0).getOrCreateThread(0);
+ browserMain.name = 'CrBrowserMain';
+ const rendererCompositor =
+ model.getOrCreateProcess(1).getOrCreateThread(1);
+ rendererCompositor.name = 'Compositor';
+
+ // Creates a renderer thread
+ const renderer = model.getOrCreateProcess(2).getOrCreateThread(2);
+ renderer.name = 'CrRendererMain';
+ renderer.asyncSliceGroup.push(
+ tr.c.TestUtils.newAsyncSliceNamed('Interaction.Action', 1, 1));
+
+ const rasterWorker = model.getOrCreateProcess(2).getOrCreateThread(5);
+ rasterWorker.name = 'CompositorTileWorker';
+
+ addPipelineForOneFrame(browserMain, rendererCompositor,
+ rasterWorker, 1, {
+ IssueBeginFrame: 1, ReceiveBeginFrame: 2, GenerateRenderPass: 3,
+ GenerateCompositorFrame: 4, SubmitCompositorFrame: 5,
+ ReceiveCompositorFrame: 11, SurfaceAggregation: 15,
+ rasterTaskStart: 2
+ });
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.rendering.addPipelineHistograms(
+ histograms, model,
+ [new tr.model.um.Segment(0, 10), new tr.model.um.Segment(14, 16)]);
+
+ const surfaceAggregation = histograms.getHistogramNamed(
+ 'pipeline:frame_submission_to_display');
+ assert.strictEqual(surfaceAggregation.average, 10);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/rendering/pixels.html b/chromium/third_party/catapult/tracing/tracing/metrics/rendering/pixels.html
new file mode 100644
index 00000000000..7f3060561d8
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/rendering/pixels.html
@@ -0,0 +1,105 @@
+<!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/statistics.html">
+<link rel="import" href="/tracing/base/unit.html">
+<link rel="import" href="/tracing/model/helpers/chrome_model_helper.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview This file contains implementations of the following metrics.
+ *
+ * mean_pixels_approximated
+ * ========================
+ * The percentage of tiles which are missing or of low or non-ideal resolution.
+ *
+ * TODO(crbug.com/875010): The documentation claims that
+ * mean_pixels_approximated should be greater than or equal to
+ * mean_pixels_checkerboarded. This claim is not consistent with numbers from
+ * the perf dashboard. We should either correct the documentations or fix the
+ * metrics.
+ *
+ * mean_pixels_checkerboarded
+ * ==========================
+ * The percentage of tiles which are only missing. It does not take into
+ * consideration tiles which are of low or non-ideal resolution.
+ */
+tr.exportTo('tr.metrics.rendering', function() {
+ const IMPL_THREAD_RENDERING_STATS_EVENT =
+ 'BenchmarkInstrumentation::ImplThreadRenderingStats';
+ const VISIBLE_CONTENT_DATA = 'visible_content_area';
+ const APPROXIMATED_VISIBLE_CONTENT_DATA = 'approximated_visible_content_area';
+ const CHECKERBOARDED_VISIBLE_CONTENT_DATA =
+ 'checkerboarded_visible_content_area';
+
+ function addPixelsHistograms(histograms, model, segments) {
+ const chromeHelper = model.getOrCreateHelper(
+ tr.model.helpers.ChromeModelHelper);
+ if (!chromeHelper) return;
+
+ const approximatedPixelPercentages = [];
+ const checkerboardedPixelPercentages = [];
+ const ranges = segments.map(s => s.boundsRange);
+ for (const rendererHelper of Object.values(chromeHelper.rendererHelpers)) {
+ if (rendererHelper.compositorThread === undefined) continue;
+ const slices = rendererHelper.compositorThread.sliceGroup;
+ for (const slice of slices.getDescendantEventsInSortedRanges(ranges)) {
+ if (slice.title !== IMPL_THREAD_RENDERING_STATS_EVENT) continue;
+ const data = slice.args.data;
+ if (!(VISIBLE_CONTENT_DATA in data)) {
+ throw new Error(`${VISIBLE_CONTENT_DATA} is missing`);
+ }
+ const visibleContentArea = data[VISIBLE_CONTENT_DATA];
+ if (visibleContentArea === 0) {
+ // TODO(crbug.com/877056): This is reported as an error in the legacy
+ // code, which indicates there should be no rendering stats event with
+ // zero visible content area. But, the TBMv2 implementation encounters
+ // such events. Maybe they are coming from OOPIFs which are ignored in
+ // the legacy code? Investigate why they exist and what should be
+ // done.
+ //
+ // throw new Error(`${VISIBLE_CONTENT_DATA} is zero`);
+ continue;
+ }
+ if (APPROXIMATED_VISIBLE_CONTENT_DATA in data) {
+ approximatedPixelPercentages.push(
+ data[APPROXIMATED_VISIBLE_CONTENT_DATA] / visibleContentArea);
+ }
+ if (CHECKERBOARDED_VISIBLE_CONTENT_DATA in data) {
+ checkerboardedPixelPercentages.push(
+ data[CHECKERBOARDED_VISIBLE_CONTENT_DATA] / visibleContentArea);
+ }
+ }
+ }
+
+ // TODO(crbug.com/892501): The averages are multiplied by 100 to match an
+ // error in legacy code so that we have continuity in graphs in the perf
+ // dashboard. We should fix historic data and remove the multiplication.
+ histograms.createHistogram(
+ 'mean_pixels_approximated',
+ tr.b.Unit.byName.normalizedPercentage_smallerIsBetter,
+ 100 * tr.b.math.Statistics.mean(approximatedPixelPercentages),
+ { description: 'Percentage of pixels that were approximated ' +
+ '(checkerboarding, low-resolution tiles, etc.).',
+ summaryOptions: tr.metrics.rendering.SUMMARY_OPTIONS,
+ });
+ histograms.createHistogram(
+ 'mean_pixels_checkerboarded',
+ tr.b.Unit.byName.normalizedPercentage_smallerIsBetter,
+ 100 * tr.b.math.Statistics.mean(checkerboardedPixelPercentages),
+ { description: 'Percentage of pixels that were checkerboarded.',
+ summaryOptions: tr.metrics.rendering.SUMMARY_OPTIONS,
+ });
+ }
+
+ return {
+ addPixelsHistograms,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/rendering/pixels_test.html b/chromium/third_party/catapult/tracing/tracing/metrics/rendering/pixels_test.html
new file mode 100644
index 00000000000..10a77215c47
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/rendering/pixels_test.html
@@ -0,0 +1,64 @@
+<!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/metrics/rendering/pixels.html">
+<link rel="import" href="/tracing/model/user_model/segment.html">
+<link rel="import" href="/tracing/value/histogram_set.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ test('pixelsApproximated', function() {
+ const model = tr.c.TestUtils.newModel((model) => {
+ // Metric computation assumes that there is always a browser process.
+ model.getOrCreateProcess(0).getOrCreateThread(0).name = 'CrBrowserMain';
+
+ const compositor = model.getOrCreateProcess(1).getOrCreateThread(1);
+ compositor.name = 'Compositor';
+ compositor.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ title: 'BenchmarkInstrumentation::ImplThreadRenderingStats',
+ start: 1, end: 1,
+ args: {
+ data: {
+ visible_content_area: 50,
+ approximated_visible_content_area: 8,
+ checkerboarded_visible_content_area: 3
+ }
+ }
+ }));
+ compositor.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ title: 'BenchmarkInstrumentation::ImplThreadRenderingStats',
+ start: 2, end: 2,
+ args: {
+ data: {
+ visible_content_area: 25,
+ approximated_visible_content_area: 6,
+ checkerboarded_visible_content_area: 5
+ }
+ }
+ }));
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.rendering.addPixelsHistograms(
+ histograms, model, [new tr.model.um.Segment(0, 10)]);
+
+ // The mean of 8/50 and 6/25 is 0.2.
+ let hist = histograms.getHistogramNamed('mean_pixels_approximated');
+ assert.closeTo(20, hist.min, 1e-6);
+ assert.closeTo(20, hist.max, 1e-6);
+ assert.closeTo(20, hist.average, 1e-6);
+
+ // The mean of 3/50 and 5/25 is 0.13.
+ hist = histograms.getHistogramNamed('mean_pixels_checkerboarded');
+ assert.closeTo(13, hist.min, 1e-6);
+ assert.closeTo(13, hist.max, 1e-6);
+ assert.closeTo(13, hist.average, 1e-6);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/rendering/queueing_duration.html b/chromium/third_party/catapult/tracing/tracing/metrics/rendering/queueing_duration.html
new file mode 100644
index 00000000000..386bd444658
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/rendering/queueing_duration.html
@@ -0,0 +1,88 @@
+<!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/unit.html">
+<link rel="import" href="/tracing/model/helpers/chrome_model_helper.html">
+<link rel="import" href="/tracing/value/histogram.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview This file contains implementations of the following metrics.
+ *
+ * queueing_durations
+ * ==================
+ * This quantifies how out of sync the compositor and renderer threads are. It
+ * is the amount of wall time that elapses between a
+ * ScheduledActionSendBeginMainFrame event in the compositor thread and the
+ * corresponding BeginMainFrame event in the main thread.
+ *
+ * TODO(chiniforooshan): Does it make sense to just ignore data from OOPIF
+ * processes, like what we are doing here?
+ */
+tr.exportTo('tr.metrics.rendering', function() {
+ // Various tracing events.
+ const BEGIN_MAIN_FRAME_EVENT = 'ThreadProxy::BeginMainFrame';
+ const SEND_BEGIN_FRAME_EVENT =
+ 'ThreadProxy::ScheduledActionSendBeginMainFrame';
+
+ function getEventTimesByBeginFrameId_(thread, title, ranges) {
+ const out = new Map();
+ const slices = thread.sliceGroup;
+ for (const slice of slices.getDescendantEventsInSortedRanges(ranges)) {
+ if (slice.title !== title) continue;
+ const id = slice.args.begin_frame_id;
+ if (id === undefined) throw new Error('Event is missing begin_frame_id');
+ if (out.has(id)) throw new Error(`There must be exactly one ${title}`);
+ out.set(id, slice.start);
+ }
+ return out;
+ }
+
+ function addQueueingDurationHistograms(histograms, model, segments) {
+ const chromeHelper = model.getOrCreateHelper(
+ tr.model.helpers.ChromeModelHelper);
+ if (!chromeHelper) return;
+
+ let targetRenderers = chromeHelper.telemetryHelper.renderersWithIR;
+ if (targetRenderers.length === 0) {
+ targetRenderers = Object.values(chromeHelper.rendererHelpers);
+ }
+ const queueingDurations = [];
+ const ranges = segments.map(s => s.boundsRange);
+ for (const rendererHelper of targetRenderers) {
+ const mainThread = rendererHelper.mainThread;
+ const compositorThread = rendererHelper.compositorThread;
+ if (mainThread === undefined || compositorThread === undefined) continue;
+
+ const beginMainFrameTimes = getEventTimesByBeginFrameId_(
+ mainThread, BEGIN_MAIN_FRAME_EVENT, ranges);
+ const sendBeginFrameTimes = getEventTimesByBeginFrameId_(
+ compositorThread, SEND_BEGIN_FRAME_EVENT, ranges);
+ for (const [id, time] of sendBeginFrameTimes) {
+ queueingDurations.push(beginMainFrameTimes.get(id) - time);
+ }
+ }
+
+ histograms.createHistogram(
+ 'queueing_durations',
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter, queueingDurations, {
+ binBoundaries:
+ tr.v.HistogramBinBoundaries.createExponential(0.01, 2, 20),
+ summaryOptions: tr.metrics.rendering.SUMMARY_OPTIONS,
+ description: 'Time between ScheduledActionSendBeginMainFrame in ' +
+ 'the compositor thread and the corresponding ' +
+ 'BeginMainFrame in the main thread.'
+ });
+ }
+
+ return {
+ addQueueingDurationHistograms,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/rendering/queueing_duration_test.html b/chromium/third_party/catapult/tracing/tracing/metrics/rendering/queueing_duration_test.html
new file mode 100644
index 00000000000..2ca870810dd
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/rendering/queueing_duration_test.html
@@ -0,0 +1,54 @@
+<!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/metrics/rendering/queueing_duration.html">
+<link rel="import" href="/tracing/model/user_model/segment.html">
+<link rel="import" href="/tracing/value/histogram_set.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ test('queueingDurations', function() {
+ const model = tr.c.TestUtils.newModel((model) => {
+ // Metric computation assumes that there is always a browser process.
+ model.getOrCreateProcess(0).getOrCreateThread(0).name = 'CrBrowserMain';
+
+ const compositor = model.getOrCreateProcess(1).getOrCreateThread(1);
+ compositor.name = 'Compositor';
+ compositor.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { title: 'ThreadProxy::ScheduledActionSendBeginMainFrame',
+ start: 3, end: 3,
+ args: { begin_frame_id: 2 }}));
+ compositor.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { title: 'ThreadProxy::ScheduledActionSendBeginMainFrame',
+ start: 4, end: 4,
+ args: { begin_frame_id: 1 }}));
+
+ const main = model.getOrCreateProcess(1).getOrCreateThread(0);
+ main.name = 'CrRendererMain';
+ main.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { title: 'ThreadProxy::BeginMainFrame',
+ start: 5, end: 5,
+ args: { begin_frame_id: 1 }}));
+ main.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { title: 'ThreadProxy::BeginMainFrame',
+ start: 6, end: 6,
+ args: { begin_frame_id: 2 }}));
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.rendering.addQueueingDurationHistograms(
+ histograms, model, [new tr.model.um.Segment(0, 10)]);
+
+ const hist = histograms.getHistogramNamed('queueing_durations');
+ assert.closeTo(1, hist.min, 1e-6);
+ assert.closeTo(3, hist.max, 1e-6);
+ assert.closeTo(2, hist.average, 1e-6);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/rendering/rendering_metric.html b/chromium/third_party/catapult/tracing/tracing/metrics/rendering/rendering_metric.html
new file mode 100644
index 00000000000..1024c9baa87
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/rendering/rendering_metric.html
@@ -0,0 +1,52 @@
+<!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/metrics/metric_registry.html">
+<link rel="import" href="/tracing/metrics/rendering/frame_time.html">
+<link rel="import" href="/tracing/metrics/rendering/latency.html">
+<link rel="import" href="/tracing/metrics/rendering/pipeline.html">
+<link rel="import" href="/tracing/metrics/rendering/pixels.html">
+<link rel="import" href="/tracing/metrics/rendering/queueing_duration.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.metrics.rendering', function() {
+ // Various tracing events.
+ const GESTURE_EVENT = 'SyntheticGestureController::running';
+
+ function renderingMetric(histograms, model) {
+ const chromeHelper = model.getOrCreateHelper(
+ tr.model.helpers.ChromeModelHelper);
+ if (!chromeHelper) return;
+
+ const segments = chromeHelper.telemetryHelper.segments;
+ if (segments.length > 0) {
+ tr.metrics.rendering.addFrameTimeHistograms(histograms, model, segments);
+ tr.metrics.rendering.addLatencyHistograms(histograms, model, segments);
+ tr.metrics.rendering.addPipelineHistograms(histograms, model, segments);
+ tr.metrics.rendering.addPixelsHistograms(histograms, model, segments);
+ tr.metrics.rendering.addQueueingDurationHistograms(
+ histograms, model, segments);
+ }
+
+ const uiSegments = chromeHelper.telemetryHelper.uiSegments;
+ if (uiSegments.length > 0) {
+ tr.metrics.rendering.addUIFrameTimeHistograms(
+ histograms, model, chromeHelper.telemetryHelper.uiSegments);
+ }
+ }
+
+ tr.metrics.MetricRegistry.register(renderingMetric, {
+ requiredCategories: ['benchmark', 'toplevel'],
+ });
+
+ return {
+ renderingMetric,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/rendering/rendering_metric_test.html b/chromium/third_party/catapult/tracing/tracing/metrics/rendering/rendering_metric_test.html
new file mode 100644
index 00000000000..77e0a2d9dd1
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/rendering/rendering_metric_test.html
@@ -0,0 +1,62 @@
+<!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/metrics/rendering/rendering_metric.html">
+<link rel="import" href="/tracing/value/histogram_set.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ test('renderingMetric_gestureIR', function() {
+ const model = tr.c.TestUtils.newModel((model) => {
+ const rendererMain = model.getOrCreateProcess(1).getOrCreateThread(0);
+ rendererMain.name = 'CrRendererMain';
+ rendererMain.asyncSliceGroup.push(
+ tr.c.TestUtils.newAsyncSliceNamed(
+ 'Interaction.Gesture_LoadAction', 10, 10));
+
+ const browserMain = model.getOrCreateProcess(0).getOrCreateThread(0);
+ browserMain.name = 'CrBrowserMain';
+ browserMain.asyncSliceGroup.push(
+ tr.c.TestUtils.newAsyncSliceNamed(
+ 'SyntheticGestureController::running', 0, 5));
+ browserMain.asyncSliceGroup.push(
+ tr.c.TestUtils.newAsyncSliceNamed(
+ 'SyntheticGestureController::running', 10, 5));
+ // Add four swap buffer events, at times 1, 2, 11, 13, 16
+ browserMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { title: 'BenchmarkInstrumentation::DisplayRenderingStats',
+ start: 1, end: 1 }));
+ browserMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { title: 'BenchmarkInstrumentation::DisplayRenderingStats',
+ start: 2, end: 2 }));
+ browserMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { title: 'BenchmarkInstrumentation::DisplayRenderingStats',
+ start: 11, end: 11 }));
+ browserMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { title: 'BenchmarkInstrumentation::DisplayRenderingStats',
+ start: 13, end: 13 }));
+ browserMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(
+ { title: 'BenchmarkInstrumentation::DisplayRenderingStats',
+ start: 16, end: 16 }));
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.rendering.renderingMetric(histograms, model);
+
+ // The gesture interaction record should be adjusted to [10, 15]. So, the
+ // first two frames and the fifth frame are outside the interaction record
+ // and should be discarded. The remaining frames are 11 and 13 which result
+ // in a single frame time of 2 = 13 - 11.
+ const hist = histograms.getHistogramNamed('frame_times');
+ assert.closeTo(2, hist.min, 1e-6);
+ assert.closeTo(2, hist.max, 2e-6);
+ assert.closeTo(2, hist.average, 1e-6);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/sample_exception_metric.html b/chromium/third_party/catapult/tracing/tracing/metrics/sample_exception_metric.html
new file mode 100644
index 00000000000..01dd58cdf9c
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/sample_exception_metric.html
@@ -0,0 +1,46 @@
+<!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/metrics/metric_registry.html">
+<link rel="import" href="/tracing/value/histogram.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.metrics', function() {
+ function sampleExceptionMetric(histograms, model) {
+ const hist = new tr.v.Histogram(
+ 'foo', tr.b.Unit.byName.sizeInBytes_smallerIsBetter);
+ hist.addSample(9);
+ hist.addSample(91, {bar: new tr.v.d.GenericSet([{hello: 42}])});
+
+ for (const expectation of model.userModel.expectations) {
+ if (expectation instanceof tr.model.um.ResponseExpectation) {
+ } else if (expectation instanceof tr.model.um.AnimationExpectation) {
+ } else if (expectation instanceof tr.model.um.IdleExpectation) {
+ } else if (expectation instanceof tr.model.um.LoadExpectation) {
+ }
+ }
+
+ const chromeHelper = model.getOrCreateHelper(
+ tr.model.helpers.ChromeModelHelper);
+
+ for (const [pid, process] of Object.entries(model.processes)) {
+ }
+
+ histograms.addHistogram(hist);
+ throw new Error('There was an error');
+ }
+
+ tr.metrics.MetricRegistry.register(sampleExceptionMetric);
+
+ return {
+ sampleExceptionMetric,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/sample_metric.html b/chromium/third_party/catapult/tracing/tracing/metrics/sample_metric.html
new file mode 100644
index 00000000000..4a8be86b0d7
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/sample_metric.html
@@ -0,0 +1,45 @@
+<!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/math/range.html">
+<link rel="import" href="/tracing/metrics/metric_registry.html">
+<link rel="import" href="/tracing/value/histogram.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.metrics', function() {
+ function sampleMetric(histograms, model) {
+ const hist = new tr.v.Histogram(
+ 'foo', tr.b.Unit.byName.sizeInBytes_smallerIsBetter);
+ hist.addSample(9);
+ hist.addSample(91, {bar: new tr.v.d.GenericSet([{hello: 42}])});
+
+ for (const expectation of model.userModel.expectations) {
+ if (expectation instanceof tr.model.um.ResponseExpectation) {
+ } else if (expectation instanceof tr.model.um.AnimationExpectation) {
+ } else if (expectation instanceof tr.model.um.IdleExpectation) {
+ } else if (expectation instanceof tr.model.um.LoadExpectation) {
+ }
+ }
+
+ const chromeHelper = model.getOrCreateHelper(
+ tr.model.helpers.ChromeModelHelper);
+
+ for (const [pid, process] of Object.entries(model.processes)) {
+ }
+
+ histograms.addHistogram(hist);
+ }
+
+ tr.metrics.MetricRegistry.register(sampleMetric);
+
+ return {
+ sampleMetric,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/spa_navigation_helper.html b/chromium/third_party/catapult/tracing/tracing/metrics/spa_navigation_helper.html
new file mode 100644
index 00000000000..fcc5c8df4ca
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/spa_navigation_helper.html
@@ -0,0 +1,248 @@
+<!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';
+
+/**
+ * @fileoverview This file contains helper functions to identify
+ * FrameLoader::updateForSameDocumentNavigation events on all renderer
+ * processes and find their preceding navigation start events.
+ *_______________________________________________________________
+ * browser: InputLatency/NavigationControllerImpl::GoToIndex |
+ *----------------------------------------
+ * renderer: LatencyInfo.Flow
+ * WebViewImpl::handleInputEvent
+ * FrameLoader::updateForSameDocumentNavigation
+ *----------------------------------------------------
+ * FrameLoader::updateForSameDocumentNavigation is called when SPA
+ * in-app navigation occurs.
+ * For details about how SPA in-app navigation is defined and
+ * how it is found based on FrameLoader::updateForSameDocumentNavigation,
+ * read the doc: https://goo.gl/1I3tqd.
+ */
+tr.exportTo('tr.metrics', function() {
+ const HANDLE_INPUT_EVENT_TITLE = 'WebViewImpl::handleInputEvent';
+
+ /**
+ * @returns {Map.<tr.model.Slice, tr.model.Slice>} A map of the
+ * elements in eventsB which immediately precede events in eventsA.
+ * For instance:
+ * eventsA: A1 A2 A3 A4
+ * eventsB: B1 B2 B3 B4 B5
+ * output: {A1: B2, A2: B3, A3: B4, A4: B5}
+ * or
+ * eventsA: A1 A2 A3 A4
+ * eventsB: B1
+ * output: {A1: B1, A2: B1, A3: B1, A4: B1}
+ */
+ function findPrecedingEvents_(eventsA, eventsB) {
+ const events = new Map();
+ let eventsBIndex = 0;
+ for (const eventA of eventsA) {
+ for (; eventsBIndex < eventsB.length; eventsBIndex++) {
+ if (eventsB[eventsBIndex].start > eventA.start) break;
+ }
+ // If statement prevents the situation when eventsB is empty.
+ if (eventsBIndex > 0) {
+ events.set(eventA, eventsB[eventsBIndex - 1]);
+ }
+ }
+ return events;
+ }
+
+ /**
+ * @returns {Map.<tr.model.Slice, tr.model.Slice>} A map of
+ * the elements in eventsB which immediately follow events
+ * in eventsA.
+ * For instance:
+ * eventsA: A1 A2 A3 A4
+ * eventsB: B1 B2 B3 B4 B5
+ * output: {A1:B2, A2:B3, A3:B4, A4:B5}
+ * or
+ * eventsA: A1 A2 A3 A4
+ * eventsB: B1
+ * output: {A1:B1, A2:B1, A3:B1}
+ */
+ function findFollowingEvents_(eventsA, eventsB) {
+ const events = new Map();
+ let eventsBIndex = 0;
+ for (const eventA of eventsA) {
+ for (; eventsBIndex < eventsB.length; eventsBIndex++) {
+ if (eventsB[eventsBIndex].start >= eventA.start) break;
+ }
+ // If statement prevents the situation when eventsB is empty
+ // and when it reaches the end of loop.
+ if (eventsBIndex >= 0 && eventsBIndex < eventsB.length) {
+ events.set(eventA, eventsB[eventsBIndex]);
+ }
+ }
+ return events;
+ }
+
+ /**
+ * @return {Array.<tr.model.Slice>} An array of events that may
+ * be qualified as a SPA navigation start candidate such as
+ * WebViewImpl::handleInputEvent and NavigationControllerImpl::GoToIndex.
+ */
+ function getSpaNavigationStartCandidates_(rendererHelper, browserHelper) {
+ const isNavStartEvent = e => {
+ if (e.title === HANDLE_INPUT_EVENT_TITLE && e.args.type === 'MouseUp') {
+ return true;
+ }
+ return e.title === 'NavigationControllerImpl::GoToIndex';
+ };
+
+ return [
+ ...rendererHelper.mainThread.sliceGroup.getDescendantEvents(),
+ ...browserHelper.mainThread.sliceGroup.getDescendantEvents()
+ ].filter(isNavStartEvent);
+ }
+
+ /**
+ * @return {Array.<tr.model.Slice>} An array of SPA navigation events.
+ * A SPA navigation event indicates the happening of a SPA navigation.
+ */
+ function getSpaNavigationEvents_(rendererHelper) {
+ const isNavEvent = e => e.category === 'blink' &&
+ e.title === 'FrameLoader::updateForSameDocumentNavigation';
+
+ return [...rendererHelper.mainThread.sliceGroup.getDescendantEvents()]
+ .filter(isNavEvent);
+ }
+
+ /**
+ * @return {Array.<tr.model.AsyncSlice>} An array of InputLatency events from
+ * the browser main thread.
+ */
+ function getInputLatencyEvents_(browserHelper) {
+ const isInputLatencyEvent = e => e.title === 'InputLatency::MouseUp';
+
+ return browserHelper.getAllAsyncSlicesMatching(isInputLatencyEvent);
+ }
+
+ /**
+ * @return {Map.<number, tr.model.Slice>} A mapping of trace_id value
+ * in each InputLatency event to the respective InputLatency event itself.
+ */
+ function getInputLatencyEventByBindIdMap_(browserHelper) {
+ const inputLatencyEventByBindIdMap = new Map();
+ for (const event of getInputLatencyEvents_(browserHelper)) {
+ inputLatencyEventByBindIdMap.set(event.args.data.trace_id, event);
+ }
+ return inputLatencyEventByBindIdMap;
+ }
+
+ /**
+ * @returns {Map.<tr.model.Slice, tr.model.AsyncSlice>} A mapping
+ * from a FrameLoader update navigation slice to its respective
+ * navigation start event, which can be an InputLatency async
+ * slice or a NavigationControllerImpl::GoToIndex slice.
+ */
+ function getSpaNavigationEventToNavigationStartMap_(
+ rendererHelper, browserHelper) {
+ const mainThread = rendererHelper.mainThread;
+ const spaNavEvents = getSpaNavigationEvents_(rendererHelper);
+ const navStartCandidates = getSpaNavigationStartCandidates_(
+ rendererHelper, browserHelper).sort(tr.importer.compareEvents);
+ const spaNavEventToNavStartCandidateMap =
+ findPrecedingEvents_(spaNavEvents, navStartCandidates);
+
+ const inputLatencyEventByBindIdMap =
+ getInputLatencyEventByBindIdMap_(browserHelper);
+ const spaNavEventToNavStartEventMap = new Map();
+ for (const [spaNavEvent, navStartCandidate] of
+ spaNavEventToNavStartCandidateMap) {
+ if (navStartCandidate.title === HANDLE_INPUT_EVENT_TITLE) {
+ const inputLatencySlice = inputLatencyEventByBindIdMap.get(
+ Number(navStartCandidate.parentSlice.bindId));
+ if (inputLatencySlice) {
+ spaNavEventToNavStartEventMap.set(spaNavEvent, inputLatencySlice);
+ }
+ } else {
+ spaNavEventToNavStartEventMap.set(spaNavEvent, navStartCandidate);
+ }
+ }
+ return spaNavEventToNavStartEventMap;
+ }
+
+ /**
+ * @return {Array.<tr.model.Slice>} An array of first paint events.
+ */
+ function getFirstPaintEvents_(rendererHelper) {
+ const isFirstPaintEvent = e => e.category === 'blink' &&
+ e.title === 'PaintLayerCompositor::updateIfNeededRecursive';
+
+ return [...rendererHelper.mainThread.sliceGroup.getDescendantEvents()]
+ .filter(isFirstPaintEvent);
+ }
+
+ /**
+ * @returns {Map.<tr.model.Slice, tr.model.Slice>} A mapping
+ * from a FrameLoader update navigation slice to its respective
+ * first paint slice.
+ */
+ function getSpaNavigationEventToFirstPaintEventMap_(rendererHelper) {
+ const spaNavEvents = getSpaNavigationEvents_(
+ rendererHelper).sort(tr.importer.compareEvents);
+ const firstPaintEvents = getFirstPaintEvents_(
+ rendererHelper).sort(tr.importer.compareEvents);
+
+ return findFollowingEvents_(spaNavEvents, firstPaintEvents);
+ }
+
+ /**
+ * @typedef {NavStartCandidates}
+ * @property {tr.model.AsyncSlice} inputLatencyAsyncSlice
+ * @property {tr.model.Slice} goToIndexSlice
+ */
+
+ /**
+ * @typedef {SpaNavObject}
+ * @property {NavStartCandidates} navStartCandidates
+ * @property {tr.model.Slice} firstPaintEvent
+ * @property {string} url
+ */
+
+ /**
+ * @returns {Array.<SpaNavObject>}
+ */
+ function findSpaNavigationsOnRenderer(rendererHelper, browserHelper) {
+ const spaNavEventToNavStartMap =
+ getSpaNavigationEventToNavigationStartMap_(
+ rendererHelper, browserHelper);
+ const spaNavEventToFirstPaintEventMap =
+ getSpaNavigationEventToFirstPaintEventMap_(rendererHelper);
+ const spaNavigations = [];
+ for (const [spaNavEvent, navStartEvent] of
+ spaNavEventToNavStartMap) {
+ if (spaNavEventToFirstPaintEventMap.has(spaNavEvent)) {
+ const firstPaintEvent =
+ spaNavEventToFirstPaintEventMap.get(spaNavEvent);
+ const isNavStartAsyncSlice =
+ navStartEvent instanceof tr.model.AsyncSlice;
+ spaNavigations.push({
+ navStartCandidates: {
+ inputLatencyAsyncSlice:
+ isNavStartAsyncSlice ? navStartEvent : undefined,
+ goToIndexSlice: isNavStartAsyncSlice ? undefined : navStartEvent
+ },
+ firstPaintEvent,
+ url: spaNavEvent.args.url
+ });
+ }
+ }
+ return spaNavigations;
+ }
+
+ return {
+ findSpaNavigationsOnRenderer,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/spa_navigation_helper_test.html b/chromium/third_party/catapult/tracing/tracing/metrics/spa_navigation_helper_test.html
new file mode 100644
index 00000000000..ae87c1834e7
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/spa_navigation_helper_test.html
@@ -0,0 +1,258 @@
+<!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/metrics/spa_navigation_helper.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const RENDERER_PROCESS_ID = 1234;
+ const RENDERER_PROCESS_MAIN_THREAD_ID = 1;
+ const BROWSER_PROCESS_ID = 1;
+ const BROWSER_PROCESS_MAIN_THREAD_ID = 12;
+ const PAINT_UPDATE_TITLE =
+ 'PaintLayerCompositor::updateIfNeededRecursive';
+ const SPA_NAVIGATION_EVENT_TITLE =
+ 'FrameLoader::updateForSameDocumentNavigation';
+
+ function createChromeProcessesOnModel(model) {
+ const rendererProcess = model.getOrCreateProcess(RENDERER_PROCESS_ID);
+ const mainThread = rendererProcess.getOrCreateThread(
+ RENDERER_PROCESS_MAIN_THREAD_ID);
+ mainThread.name = 'CrRendererMain';
+ const browserProcess = model.getOrCreateProcess(BROWSER_PROCESS_ID);
+ const browserMainThread = browserProcess.getOrCreateThread(
+ BROWSER_PROCESS_MAIN_THREAD_ID);
+ browserMainThread.name = 'CrBrowserMain';
+ }
+
+ function addThreadSlice(model, title, timestamp, args) {
+ const rendererProcess = model.getOrCreateProcess(RENDERER_PROCESS_ID);
+ const mainThread = rendererProcess.getOrCreateThread(
+ RENDERER_PROCESS_MAIN_THREAD_ID);
+
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink',
+ title,
+ start: timestamp,
+ duration: 0.1,
+ args
+ }));
+ }
+
+ function addLatencyInfoFlowEvent(model, timestamp, bindId) {
+ const latencyInfoFlowSlice = tr.c.TestUtils.newSliceEx({
+ cat: 'input,benchmark',
+ title: 'LatencyInfo.Flow',
+ start: timestamp,
+ duration: 0.1,
+ bindId,
+ args: {step: 'handleInputEventMain'}
+ });
+ const handleInputEventSlice = tr.c.TestUtils.newSliceEx({
+ cat: 'blink,rail',
+ title: 'WebViewImpl::handleInputEvent',
+ start: timestamp + 1, // Assume handleInputEvent always delays 1ms.
+ duration: 0.1,
+ args: {type: 'MouseUp'}
+ });
+
+ const rendererProcess = model.getOrCreateProcess(RENDERER_PROCESS_ID);
+ const mainThread = rendererProcess.getOrCreateThread(
+ RENDERER_PROCESS_MAIN_THREAD_ID);
+
+ handleInputEventSlice.parentSlice = latencyInfoFlowSlice;
+ mainThread.sliceGroup.pushSlice(latencyInfoFlowSlice);
+ mainThread.sliceGroup.pushSlice(handleInputEventSlice);
+ }
+
+ function addInputLatencySlice(model, start, traceId) {
+ const browserProcess = model.getOrCreateProcess(BROWSER_PROCESS_ID);
+ const browserMainThread = browserProcess.getOrCreateThread(
+ BROWSER_PROCESS_MAIN_THREAD_ID);
+
+ browserMainThread.sliceGroup.pushSlice(tr.c.TestUtils.newAsyncSliceEx({
+ cat: 'benchmark,latencyInfo,rail',
+ title: 'InputLatency::MouseUp',
+ start,
+ duration: 0.1,
+ args: {
+ data: {
+ trace_id: traceId
+ }
+ }
+ }));
+ }
+
+ function addGoToIndexSlice(model, timestamp) {
+ const browserProcess = model.getOrCreateProcess(BROWSER_PROCESS_ID);
+ const browserMainThread = browserProcess.getOrCreateThread(
+ BROWSER_PROCESS_MAIN_THREAD_ID);
+
+ browserMainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'browser,navigation,benchmark',
+ title: 'NavigationControllerImpl::GoToIndex',
+ start: timestamp,
+ duration: 0.1
+ }));
+ }
+
+ function getSpaNavigations(model) {
+ const modelHelper = model.getOrCreateHelper(
+ tr.model.helpers.ChromeModelHelper);
+ const rendererHelpers = modelHelper.rendererHelpers;
+ const browserHelper = modelHelper.browserHelper;
+ let spaNavigations = [];
+ for (const rendererHelper of Object.values(rendererHelpers)) {
+ spaNavigations = spaNavigations.concat(
+ tr.metrics.findSpaNavigationsOnRenderer(
+ rendererHelper, browserHelper));
+ }
+ return spaNavigations;
+ }
+
+ test('findSpaNavigations_noSpaNavEvent', function() {
+ const model = tr.c.TestUtils.newModel(model => {
+ createChromeProcessesOnModel(model);
+ addLatencyInfoFlowEvent(model, 75, '0x600000057');
+ addInputLatencySlice(model, 55, 25769803863);
+ addThreadSlice(model, PAINT_UPDATE_TITLE, 101);
+ });
+ const spaNavigations = getSpaNavigations(model);
+ assert.lengthOf(spaNavigations, 0);
+ });
+
+ test('findSpaNavigations_noLatencyInfoFlowEvent', function() {
+ const model = tr.c.TestUtils.newModel(model => {
+ createChromeProcessesOnModel(model);
+ addThreadSlice(model, SPA_NAVIGATION_EVENT_TITLE, 100);
+ addInputLatencySlice(model, 55, 25769803863);
+ addThreadSlice(model, PAINT_UPDATE_TITLE, 101);
+ });
+ const spaNavigations = getSpaNavigations(model);
+ assert.lengthOf(spaNavigations, 0);
+ });
+
+ test('findSpaNavigations_noNavStartEvent', function() {
+ const model = tr.c.TestUtils.newModel(model => {
+ createChromeProcessesOnModel(model);
+ addThreadSlice(model, SPA_NAVIGATION_EVENT_TITLE, 100);
+ addLatencyInfoFlowEvent(model, 75, '0x600000057');
+ addThreadSlice(model, PAINT_UPDATE_TITLE, 101);
+ });
+ const spaNavigations = getSpaNavigations(model);
+ assert.lengthOf(spaNavigations, 0);
+ });
+
+ test('findSpaNavigations_noFirstPaintEvent', function() {
+ const model = tr.c.TestUtils.newModel(model => {
+ createChromeProcessesOnModel(model);
+ addThreadSlice(model, SPA_NAVIGATION_EVENT_TITLE, 100);
+ addLatencyInfoFlowEvent(model, 75, '0x600000057');
+ addInputLatencySlice(model, 55, 25769803863);
+ });
+ const spaNavigations = getSpaNavigations(model);
+ assert.lengthOf(spaNavigations, 0);
+ });
+
+ test('findSpaNavigations_inputLatencyAsNavStart', function() {
+ const URL = 'https://11111';
+ const model = tr.c.TestUtils.newModel(model => {
+ createChromeProcessesOnModel(model);
+ addThreadSlice(model, SPA_NAVIGATION_EVENT_TITLE, 100,
+ {url: URL});
+ addLatencyInfoFlowEvent(model, 75, '0x600000057');
+ addInputLatencySlice(model, 55, 25769803863);
+ addThreadSlice(model, PAINT_UPDATE_TITLE, 101);
+ });
+ const spaNavigations = getSpaNavigations(model);
+ assert.lengthOf(spaNavigations, 1);
+ assert.strictEqual(
+ spaNavigations[0].navStartCandidates.inputLatencyAsyncSlice.start, 55);
+ assert.strictEqual(
+ spaNavigations[0].navStartCandidates.goToIndexSlice, undefined);
+ assert.strictEqual(spaNavigations[0].firstPaintEvent.start, 101);
+ assert.strictEqual(spaNavigations[0].url, URL);
+ });
+
+ test('findSpaNavigations_goToIndexAsNavStart', function() {
+ const URL = 'https://11111';
+ const model = tr.c.TestUtils.newModel(model => {
+ createChromeProcessesOnModel(model);
+ addThreadSlice(model, SPA_NAVIGATION_EVENT_TITLE, 100,
+ {url: URL});
+ addGoToIndexSlice(model, 55);
+ addThreadSlice(model, PAINT_UPDATE_TITLE, 101);
+ });
+ const spaNavigations = getSpaNavigations(model);
+ assert.lengthOf(spaNavigations, 1);
+ assert.strictEqual(
+ spaNavigations[0].navStartCandidates.goToIndexSlice.start, 55);
+ assert.strictEqual(
+ spaNavigations[0].navStartCandidates.inputLatencyAsyncSlice, undefined);
+ assert.strictEqual(spaNavigations[0].firstPaintEvent.start, 101);
+ assert.strictEqual(spaNavigations[0].url, URL);
+ });
+
+ test('findSpaNavigations_multipleSpaNavs', function() {
+ const URL1 = 'https://11111';
+ const URL2 = 'https://22222';
+ const URL3 = 'https://33333';
+ const model = tr.c.TestUtils.newModel(model => {
+ createChromeProcessesOnModel(model);
+ addThreadSlice(model, SPA_NAVIGATION_EVENT_TITLE, 100,
+ {url: URL1});
+ addThreadSlice(model, SPA_NAVIGATION_EVENT_TITLE, 200,
+ {url: URL2});
+ addThreadSlice(model, SPA_NAVIGATION_EVENT_TITLE, 300,
+ {url: URL3});
+
+ addLatencyInfoFlowEvent(model, 75, '0x600000057');
+ addLatencyInfoFlowEvent(model, 175, '0x6000000c2');
+ addLatencyInfoFlowEvent(model, 275, '0x60000010d');
+
+ addInputLatencySlice(model, 55, 25769803863);
+ addGoToIndexSlice(model, 65);
+ addInputLatencySlice(model, 155, 25769803970);
+ addInputLatencySlice(model, 255, 25769804045);
+
+ addThreadSlice(model, PAINT_UPDATE_TITLE, 101);
+ addThreadSlice(model, PAINT_UPDATE_TITLE, 102);
+ addThreadSlice(model, PAINT_UPDATE_TITLE, 201);
+ addThreadSlice(model, PAINT_UPDATE_TITLE, 301);
+ });
+ const spaNavigations = getSpaNavigations(model);
+ assert.lengthOf(spaNavigations, 3);
+ spaNavigations.sort((spa1, spa2) =>
+ spa1.navStartCandidates.inputLatencyAsyncSlice.start -
+ spa2.navStartCandidates.inputLatencyAsyncSlice.start);
+ assert.strictEqual(
+ spaNavigations[0].navStartCandidates.inputLatencyAsyncSlice.start, 55);
+ assert.strictEqual(
+ spaNavigations[1].navStartCandidates.inputLatencyAsyncSlice.start, 155);
+ assert.strictEqual(
+ spaNavigations[2].navStartCandidates.inputLatencyAsyncSlice.start, 255);
+
+ assert.strictEqual(
+ spaNavigations[0].navStartCandidates.goToIndexSlice, undefined);
+ assert.strictEqual(
+ spaNavigations[1].navStartCandidates.goToIndexSlice, undefined);
+ assert.strictEqual(
+ spaNavigations[2].navStartCandidates.goToIndexSlice, undefined);
+
+ assert.strictEqual(spaNavigations[0].firstPaintEvent.start, 101);
+ assert.strictEqual(spaNavigations[1].firstPaintEvent.start, 201);
+ assert.strictEqual(spaNavigations[2].firstPaintEvent.start, 301);
+
+ assert.strictEqual(spaNavigations[0].url, URL1);
+ assert.strictEqual(spaNavigations[1].url, URL2);
+ assert.strictEqual(spaNavigations[2].url, URL3);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/spa_navigation_metric.html b/chromium/third_party/catapult/tracing/tracing/metrics/spa_navigation_metric.html
new file mode 100644
index 00000000000..f8d6bb537f6
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/spa_navigation_metric.html
@@ -0,0 +1,103 @@
+<!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/metrics/metric_registry.html">
+<link rel="import" href="/tracing/metrics/spa_navigation_helper.html">
+<link rel="import" href="/tracing/metrics/system_health/breakdown_tree_helpers.html">
+<link rel="import" href="/tracing/metrics/system_health/loading_metric.html">
+<link rel="import" href="/tracing/value/histogram.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.metrics', function() {
+ const SPA_NAVIGATION_START_TO_FIRST_PAINT_DURATION_BIN_BOUNDARY =
+ tr.v.HistogramBinBoundaries.createExponential(1, 1000, 50);
+
+ /**
+ * This metric measures the duration between the input event
+ * causing a SPA navigation and the first paint event after it.
+ */
+ function spaNavigationMetric(histograms, model) {
+ const histogram = new tr.v.Histogram(
+ 'spaNavigationStartToFpDuration',
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,
+ SPA_NAVIGATION_START_TO_FIRST_PAINT_DURATION_BIN_BOUNDARY);
+ histogram.description = 'Latency between the input event causing' +
+ ' a SPA navigation and the first paint event after it';
+ histogram.customizeSummaryOptions({
+ count: false,
+ sum: false,
+ });
+
+ const modelHelper = model.getOrCreateHelper(
+ tr.model.helpers.ChromeModelHelper);
+ if (!modelHelper) {
+ // Chrome isn't present.
+ return;
+ }
+ const rendererHelpers = modelHelper.rendererHelpers;
+ if (!rendererHelpers) {
+ // We couldn't find any renderer processes.
+ return;
+ }
+ const browserHelper = modelHelper.browserHelper;
+ for (const rendererHelper of Object.values(rendererHelpers)) {
+ const spaNavigations = tr.metrics.findSpaNavigationsOnRenderer(
+ rendererHelper, browserHelper);
+ for (const spaNav of spaNavigations) {
+ let beginTs = 0;
+ if (spaNav.navStartCandidates.inputLatencyAsyncSlice) {
+ const beginData =
+ spaNav.navStartCandidates.inputLatencyAsyncSlice.args.data;
+ // TODO(sunjian): rename convertTimestampToModelTime to something like
+ // convertTraceEventTsToModelTs and get rid of the first parameter.
+ beginTs = model.convertTimestampToModelTime(
+ 'traceEventClock',
+ beginData.INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT.time);
+ } else {
+ beginTs = spaNav.navStartCandidates.goToIndexSlice.start;
+ }
+ const rangeOfInterest = tr.b.math.Range.fromExplicitRange(
+ beginTs, spaNav.firstPaintEvent.start);
+ const networkEvents = tr.metrics.sh.getNetworkEventsInRange(
+ rendererHelper.process, rangeOfInterest);
+ const breakdownDict = tr.metrics.sh.generateWallClockTimeBreakdownTree(
+ rendererHelper.mainThread, networkEvents, rangeOfInterest);
+ const breakdownDiagnostic = new tr.v.d.Breakdown();
+ breakdownDiagnostic.colorScheme =
+ tr.v.d.COLOR_SCHEME_CHROME_USER_FRIENDLY_CATEGORY_DRIVER;
+ for (const label in breakdownDict) {
+ breakdownDiagnostic.set(label,
+ parseInt(breakdownDict[label].total * 1e3) / 1e3);
+ }
+ histogram.addSample(
+ rangeOfInterest.duration,
+ {
+ 'Breakdown of [navStart, firstPaint]': breakdownDiagnostic,
+ 'Start': new tr.v.d.RelatedEventSet(spaNav.navigationStart),
+ 'End': new tr.v.d.RelatedEventSet(spaNav.firstPaintEvent),
+ 'Navigation infos': new tr.v.d.GenericSet([{
+ url: spaNav.url,
+ pid: rendererHelper.pid,
+ navStart: beginTs,
+ firstPaint: spaNav.firstPaintEvent.start
+ }]),
+ });
+ }
+ }
+ histograms.addHistogram(histogram);
+ }
+
+ tr.metrics.MetricRegistry.register(spaNavigationMetric);
+
+ return {
+ spaNavigationMetric,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/spa_navigation_metric_test.html b/chromium/third_party/catapult/tracing/tracing/metrics/spa_navigation_metric_test.html
new file mode 100644
index 00000000000..44c38038d3b
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/spa_navigation_metric_test.html
@@ -0,0 +1,234 @@
+<!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/metrics/spa_navigation_metric.html">
+<link rel="import" href="/tracing/value/histogram_set.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const RENDERER_PROCESS_ID = 1234;
+ const RENDERER_PROCESS_MAIN_THREAD_ID = 1;
+ const BROWSER_PROCESS_ID = 1;
+ const BROWSER_PROCESS_MAIN_THREAD_ID = 12;
+ const SPA_NAVIGATION_START_TO_FIRST_PAINT_DURATION =
+ 'spaNavigationStartToFpDuration';
+
+ function createChromeProcessesOnModel(model) {
+ const rendererProcess = model.getOrCreateProcess(RENDERER_PROCESS_ID);
+ const mainThread = rendererProcess.getOrCreateThread(
+ RENDERER_PROCESS_MAIN_THREAD_ID);
+ mainThread.name = 'CrRendererMain';
+ const browserProcess = model.getOrCreateProcess(BROWSER_PROCESS_ID);
+ const browserMainThread = browserProcess.getOrCreateThread(
+ BROWSER_PROCESS_MAIN_THREAD_ID);
+ browserMainThread.name = 'CrBrowserMain';
+ }
+
+ function addSpaNavigationEvent(model, timestamp, args) {
+ const rendererProcess = model.getOrCreateProcess(RENDERER_PROCESS_ID);
+ const mainThread = rendererProcess.getOrCreateThread(
+ RENDERER_PROCESS_MAIN_THREAD_ID);
+
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink',
+ title: 'FrameLoader::updateForSameDocumentNavigation',
+ start: timestamp,
+ duration: 0.1,
+ args
+ }));
+ }
+
+ function addGoToIndexSlice(model, timestamp) {
+ const browserProcess = model.getOrCreateProcess(BROWSER_PROCESS_ID);
+ const browserMainThread = browserProcess.getOrCreateThread(
+ BROWSER_PROCESS_MAIN_THREAD_ID);
+ browserMainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'browser,navigation,benchmark',
+ title: 'NavigationControllerImpl::GoToIndex',
+ start: timestamp,
+ duration: 0.1
+ }));
+ }
+
+ function addFirstPaintSlice(model, timestamp) {
+ const rendererProcess = model.getOrCreateProcess(RENDERER_PROCESS_ID);
+ const mainThread = rendererProcess.getOrCreateThread(
+ RENDERER_PROCESS_MAIN_THREAD_ID);
+
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink',
+ title: 'PaintLayerCompositor::updateIfNeededRecursive',
+ start: timestamp,
+ duration: 0.1
+ }));
+ }
+
+ function addInputLatencyRelatedSlices(model, timestamp) {
+ const latencyInfoFlowSlice = tr.c.TestUtils.newSliceEx({
+ cat: 'input,benchmark',
+ title: 'LatencyInfo.Flow',
+ start: timestamp - 2,
+ duration: 0.1,
+ bindId: '0x600000057',
+ args: {step: 'handleInputEventMain'}
+ });
+ const handleInputEventSlice = tr.c.TestUtils.newSliceEx({
+ cat: 'blink,rail',
+ title: 'WebViewImpl::handleInputEvent',
+ start: timestamp - 1, // Assume handleInputEvent always delays 1ms.
+ duration: 0.1,
+ args: {type: 'MouseUp'}
+ });
+
+ const rendererProcess = model.getOrCreateProcess(RENDERER_PROCESS_ID);
+ const mainThread = rendererProcess.getOrCreateThread(
+ RENDERER_PROCESS_MAIN_THREAD_ID);
+
+ handleInputEventSlice.parentSlice = latencyInfoFlowSlice;
+ mainThread.sliceGroup.pushSlice(latencyInfoFlowSlice);
+ mainThread.sliceGroup.pushSlice(handleInputEventSlice);
+
+ const browserProcess = model.getOrCreateProcess(BROWSER_PROCESS_ID);
+ const browserMainThread = browserProcess.getOrCreateThread(
+ BROWSER_PROCESS_MAIN_THREAD_ID);
+ browserMainThread.sliceGroup.pushSlice(tr.c.TestUtils.newAsyncSliceEx({
+ cat: 'benchmark,latencyInfo,rail',
+ title: 'InputLatency::MouseUp',
+ start: timestamp - 3,
+ duration: 0.1,
+ args: {
+ data: {
+ trace_id: 25769803863,
+ INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT: {
+ time: timestamp - 3 + 0.1
+ }
+ }
+ }
+ }));
+ }
+
+ function getHistogramNamed(name, model) {
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.spaNavigationMetric(histograms, model);
+ const spaNavigationStartToFpDurationHist = histograms.getHistogramNamed(
+ name);
+ return spaNavigationStartToFpDurationHist;
+ }
+
+ test('spaNavStartToFirstPaintDuration_noChromeProcess', function() {
+ const model = tr.c.TestUtils.newModel();
+ const histogram = getHistogramNamed(
+ SPA_NAVIGATION_START_TO_FIRST_PAINT_DURATION, model);
+ assert(!histogram);
+ });
+
+ test('spaNavStartToFirstPaintDuration_noSpaNavStart', function() {
+ const model = tr.c.TestUtils.newModel(model => {
+ createChromeProcessesOnModel(model);
+ addSpaNavigationEvent(model, 100);
+ addFirstPaintSlice(model, 101);
+ });
+ const histogram = getHistogramNamed(
+ SPA_NAVIGATION_START_TO_FIRST_PAINT_DURATION, model);
+ assert.strictEqual(0, histogram.numValues);
+ });
+
+ test('spaNavStartToFirstPaintDuration_noFirstPaint', function() {
+ const model = tr.c.TestUtils.newModel(model => {
+ createChromeProcessesOnModel(model);
+ addInputLatencyRelatedSlices(model, 99);
+ addSpaNavigationEvent(model, 100);
+ });
+ const histogram = getHistogramNamed(
+ SPA_NAVIGATION_START_TO_FIRST_PAINT_DURATION, model);
+ assert.strictEqual(0, histogram.numValues);
+ });
+
+ test('spaNavStartToFirstPaintDuration_inputLatencyAsNavStart', function() {
+ const URL = 'https://11111';
+ const model = tr.c.TestUtils.newModel(model => {
+ createChromeProcessesOnModel(model);
+ addInputLatencyRelatedSlices(model, 99);
+ addSpaNavigationEvent(model, 100, {url: URL});
+ addFirstPaintSlice(model, 101);
+ });
+ const histogram = getHistogramNamed(
+ SPA_NAVIGATION_START_TO_FIRST_PAINT_DURATION, model);
+ assert.strictEqual(1, histogram.running.count);
+ const navStartTs = model.convertTimestampToModelTime(
+ 'traceEventClock', 99 - 3 + 0.1);
+ const expectedDuration = 101 - navStartTs;
+ assert.closeTo(expectedDuration, histogram.running.sum, 0.5);
+
+ const binsWithSampleDiagnosticMaps =
+ histogram.allBins.filter(bin => bin.diagnosticMaps.length > 0);
+ const diagnostic = tr.b.getOnlyElement(binsWithSampleDiagnosticMaps[0]
+ .diagnosticMaps[0].get('Navigation infos'));
+ assert.strictEqual(diagnostic.url, URL);
+ assert.strictEqual(diagnostic.pid, RENDERER_PROCESS_ID);
+ assert.strictEqual(diagnostic.navStart, navStartTs);
+ assert.strictEqual(diagnostic.firstPaint, 101);
+ });
+
+ test('spaNavStartToFirstPaintDuration_goToIndexAsNavStart', function() {
+ const URL = 'https://11111';
+ const model = tr.c.TestUtils.newModel(model => {
+ createChromeProcessesOnModel(model);
+ addGoToIndexSlice(model, 99);
+ addSpaNavigationEvent(model, 100, {url: URL});
+ addFirstPaintSlice(model, 101);
+ });
+ const histogram = getHistogramNamed(
+ SPA_NAVIGATION_START_TO_FIRST_PAINT_DURATION, model);
+ assert.strictEqual(1, histogram.running.count);
+ const expectedDuration = 101 - 99;
+ assert.closeTo(expectedDuration, histogram.running.sum, 0.5);
+
+ const binsWithSampleDiagnosticMaps =
+ histogram.allBins.filter(bin => bin.diagnosticMaps.length > 0);
+ const diagnostic = tr.b.getOnlyElement(binsWithSampleDiagnosticMaps[0]
+ .diagnosticMaps[0].get('Navigation infos'));
+ assert.strictEqual(diagnostic.url, URL);
+ assert.strictEqual(diagnostic.pid, RENDERER_PROCESS_ID);
+ assert.strictEqual(diagnostic.navStart, 99);
+ assert.strictEqual(diagnostic.firstPaint, 101);
+ });
+
+ test('spaNavStartToFirstPaintDuration_multipleSpaNavs', function() {
+ const model = tr.c.TestUtils.newModel(model => {
+ createChromeProcessesOnModel(model);
+ addInputLatencyRelatedSlices(model, 99);
+ addSpaNavigationEvent(model, 100);
+ addFirstPaintSlice(model, 101);
+
+ addInputLatencyRelatedSlices(model, 198);
+ addSpaNavigationEvent(model, 200);
+ addFirstPaintSlice(model, 201);
+
+ addInputLatencyRelatedSlices(model, 297);
+ addSpaNavigationEvent(model, 300);
+ addFirstPaintSlice(model, 301);
+ });
+ const histogram = getHistogramNamed(
+ SPA_NAVIGATION_START_TO_FIRST_PAINT_DURATION, model);
+ assert.strictEqual(3, histogram.running.count);
+ const expectedDuration1 = 101 - model.convertTimestampToModelTime(
+ 'traceEventClock', 99 - 3 + 0.1);
+ const expectedDuration2 = 201 - model.convertTimestampToModelTime(
+ 'traceEventClock', 198 - 3 + 0.1);
+ const expectedDuration3 = 301 - model.convertTimestampToModelTime(
+ 'traceEventClock', 297 - 3 + 0.1);
+ assert.closeTo(expectedDuration1 + expectedDuration2 + expectedDuration3,
+ histogram.running.sum, 0.5);
+ assert.closeTo(expectedDuration3, histogram.running.max, 0.5);
+ assert.closeTo(expectedDuration1, histogram.running.min, 0.5);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/system_health/breakdown_tree_helpers.html b/chromium/third_party/catapult/tracing/tracing/metrics/system_health/breakdown_tree_helpers.html
new file mode 100644
index 00000000000..4bbe2a694f4
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/system_health/breakdown_tree_helpers.html
@@ -0,0 +1,275 @@
+<!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/category_util.html">
+<link rel="import" href="/tracing/base/math/statistics.html">
+<link rel="import" href="/tracing/extras/chrome/chrome_user_friendly_category_driver.html">
+<link rel="import" href="/tracing/metrics/metric_registry.html">
+<link rel="import" href="/tracing/metrics/system_health/utils.html">
+<link rel="import" href="/tracing/model/helpers/chrome_model_helper.html">
+<link rel="import" href="/tracing/model/timed_event.html">
+<link rel="import" href="/tracing/value/histogram.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.metrics.sh', function() {
+ /**
+ * Returns the total self time of |event| within |rangeOfInterest|. Total self
+ * time is computed by finding time ranges that do not contain a descendant
+ * slice. Example:
+ *
+ * [ A ]
+ * | [ B ] [ C ] |
+ * | | [ D ] | | | |
+ * | | | | | |
+ * v v v v v v
+ * Ts : 0 50 80 100 150 180 200
+ * RoI : [ ]
+ *
+ * Total self time for A within |rangeOfInterest| is 100 - 30 = 70.
+ *
+ * @param {!tr.b.Event} event
+ * @param {!tr.b.math.Range} rangeOfInterest
+ * @return {number}
+ */
+ function getWallClockSelfTime_(event, rangeOfInterest) {
+ if (event.duration === 0) return 0;
+
+ const selfTimeRanges = [rangeOfInterest.findIntersection(event.range)];
+ for (const subSlice of event.subSlices) {
+ if (selfTimeRanges.length === 0) return 0;
+
+ const lastRange = selfTimeRanges.pop();
+ selfTimeRanges.push(
+ ...tr.b.math.Range.findDifference(lastRange, subSlice.range));
+ }
+
+ return tr.b.math.Statistics.sum(selfTimeRanges, r => r.duration);
+ }
+
+ /**
+ * Returns the CPU self time of |event| within |rangeOfInterest|. CPU self
+ * time of a slice is assumed to be evenly distributed over the wall clock
+ * self time ranges of the slice.
+ *
+ * @param {!tr.b.Event} event
+ * @param {!tr.b.math.Range} rangeOfInterest
+ * @return {number}
+ */
+ function getCPUSelfTime_(event, rangeOfInterest) {
+ if (event.duration === 0 || event.selfTime === 0) return 0;
+ if (event.cpuSelfTime === undefined) return 0;
+ const cpuTimeDensity = event.cpuSelfTime / event.selfTime;
+ return getWallClockSelfTime_(event, rangeOfInterest) * cpuTimeDensity;
+ }
+
+ /**
+ * @callback getEventAttributeCallback
+ * @param {!tr.b.Event} event The event to read an attribute from.
+ * @return {number} The value of the attribute.
+ */
+
+ /**
+ * Generate a breakdown tree from all slices of |mainThread| in
+ * |rangeOfInterest|. The callback function |getEventSelfTime| specify how to
+ * get self time from a given event.
+ *
+ * @param {!tr.model.Thread} mainThread
+ * @param {!tr.b.math.Range} rangeOfInterest
+ * @callback {getEventAttributeCallback} getEventSelfTime
+ * @return {Object.<string, Object>} A time breakdown object whose keys are
+ * Chrome userfriendly title & values are an object that show the total spent
+ * in |rangeOfInterest|, and the list of event labels of the
+ * group and their total time in |rangeOfInterest|.
+ *
+ * Example:
+ * {
+ * layout: {
+ * total: 100,
+ * events: {'FrameView::performPreLayoutTasks': 20,..}},
+ * v8_runtime: {
+ * total: 500,
+ * events: {'String::NewExternalTwoByte': 0.5,..}},
+ * ...
+ * }
+ */
+ function generateTimeBreakdownTree(mainThread, rangeOfInterest,
+ getEventSelfTime) {
+ if (mainThread === null) return;
+ const breakdownTree = {};
+ for (const title of
+ tr.e.chrome.ChromeUserFriendlyCategoryDriver.ALL_TITLES) {
+ breakdownTree[title] = {total: 0, events: {}};
+ }
+ // We do not look at async slices here.
+ for (const event of mainThread.sliceGroup.childEvents()) {
+ if (!rangeOfInterest.intersectsRangeExclusive(event.range)) continue;
+ const eventSelfTime = getEventSelfTime(event, rangeOfInterest);
+ const title =
+ tr.e.chrome.ChromeUserFriendlyCategoryDriver.fromEvent(event);
+
+ breakdownTree[title].total += eventSelfTime;
+ if (breakdownTree[title].events[event.title] === undefined) {
+ breakdownTree[title].events[event.title] = 0;
+ }
+ breakdownTree[title].events[event.title] +=
+ eventSelfTime;
+
+ let timeIntersectionRatio = 0;
+ if (event.duration > 0) {
+ timeIntersectionRatio =
+ rangeOfInterest.findExplicitIntersectionDuration(
+ event.start, event.end) / event.duration;
+ }
+
+ // TODO(#3846): v8_runtime is being double counted.
+ // TODO(#3846): v8_runtime slice name is wrong. It's 'stats'.
+ // TODO(#4296): v8_runtime should not be added for cpu time.
+ const v8Runtime = event.args['runtime-call-stat'];
+ if (v8Runtime !== undefined) {
+ const v8RuntimeObject = JSON.parse(v8Runtime);
+ for (const runtimeCall in v8RuntimeObject) {
+ // When the V8 Runtime Object contains 2 values, the 2nd value
+ // always represents the V8 Runtime duration.
+ if (v8RuntimeObject[runtimeCall].length === 2) {
+ if (breakdownTree.v8_runtime.events[runtimeCall] === undefined) {
+ breakdownTree.v8_runtime.events[runtimeCall] = 0;
+ }
+ const runtimeTime = tr.b.Unit.timestampFromUs(
+ v8RuntimeObject[runtimeCall][1] * timeIntersectionRatio);
+ breakdownTree.v8_runtime.total += runtimeTime;
+ breakdownTree.v8_runtime.events[runtimeCall] += runtimeTime;
+ }
+ }
+ }
+ }
+ return breakdownTree;
+ }
+
+ /**
+ * Adds 'blocked_on_network' and 'idle' to the |breakdownTree| that has been
+ * generated by |generateTimeBreakdownTree|. Taking into account the
+ * |networkEvents|, this function is able to distinguish between these two
+ * types of cpu idle time during the range |rangeOfInterest| not used by
+ * events of the main thread |mainThreadEvents|.
+ *
+ * @param {!Object.<string, Object>} breakdownTree The breakdownTree that has
+ * been generated by |generateTimeBreakdownTree|.
+ * @param {!tr.b.Event} mainThreadEvents The top level events of the main
+ * thread.
+ * @param {!tr.b.Event} networkEvents The network events in the renderer.
+ * @param {!tr.b.math.Range} rangeOfInterest The range for which
+ * |breakdownTree| is calculated.
+ */
+ function addIdleAndBlockByNetworkBreakdown_(breakdownTree, mainThreadEvents,
+ networkEvents, rangeOfInterest) {
+ const mainThreadEventRanges = tr.b.math.convertEventsToRanges(
+ mainThreadEvents);
+ const networkEventRanges = tr.b.math.convertEventsToRanges(
+ networkEvents);
+ const eventRanges = mainThreadEventRanges.concat(networkEventRanges);
+ const idleRanges =
+ tr.b.math.findEmptyRangesBetweenRanges(eventRanges, rangeOfInterest);
+ const totalFreeDuration = tr.b.math.Statistics.sum(idleRanges,
+ range => range.duration);
+ breakdownTree.idle = {total: totalFreeDuration, events: {}};
+
+ let totalBlockedDuration = rangeOfInterest.duration;
+ for (const [title, component] of Object.entries(breakdownTree)) {
+ // v8_runtime is a subcategory of script_execute.
+ // See github.com/catapult-project/catapult/commit/f3881d commit message.
+ // TODO(#2572) Make user friendly category hierarchy friend so this is not
+ // needed.
+ if (title === 'v8_runtime') continue;
+ totalBlockedDuration -= component.total;
+ }
+
+ breakdownTree.blocked_on_network = {
+ // Clamp breakdown at 0 to prevent negative values.
+ // TODO(#4299): Since we do not explicitly prevent overlapping slices on
+ // the same thread, slices can end up with negative wall clock self time
+ // and thus breakdown values can be negative. If we can prevent
+ // overlapping slices in the model, we can do this clamping for very small
+ // (e.g. < -0.1) values, accounting only for floating point errors, and
+ // fail loudly otherwise.
+ total: Math.max(totalBlockedDuration, 0),
+ events: {}
+ };
+ }
+
+ /**
+ * Generate a breakdown that attributes where wall clock time goes in
+ * |rangeOfInterest| on the renderer thread.
+ *
+ * @param {!tr.model.Thread} mainThread
+ * @param {!tr.b.math.Range} rangeOfInterest
+ * @return {Object.<string, Object>} A time breakdown object whose keys are
+ * Chrome userfriendly titles & values are an object that shows the total
+ * wall clock time spent in |rangeOfInterest|, and the list of event
+ * labels of the group and their total wall clock time in |rangeOfInterest|.
+ *
+ * Example:
+ * {
+ * layout: {
+ * total: 100,
+ * events: {'FrameView::performPreLayoutTasks': 20,..}},
+ * v8_runtime: {
+ * total: 500,
+ * events: {'String::NewExternalTwoByte': 0.5,..}},
+ * ...
+ * }
+ */
+ function generateWallClockTimeBreakdownTree(
+ mainThread, networkEvents, rangeOfInterest) {
+ const breakdownTree = generateTimeBreakdownTree(
+ mainThread, rangeOfInterest, getWallClockSelfTime_);
+ const mainThreadEventsInRange = tr.model.helpers.getSlicesIntersectingRange(
+ rangeOfInterest, mainThread.sliceGroup.topLevelSlices);
+ addIdleAndBlockByNetworkBreakdown_(
+ breakdownTree, mainThreadEventsInRange, networkEvents, rangeOfInterest);
+ return breakdownTree;
+ }
+
+ /**
+ * Generate a breakdown that attributes where CPU time goes in
+ * |rangeOfInterest| on the renderer thread.
+ *
+ * Due to approximations, it is possible for breakdowns to not add up to total
+ * CPU time in |rangeOfInterest|. Note that |rangeOfInterest| is a range of
+ * wall times, not CPU time.
+ *
+ * @param {!tr.model.Thread} mainThread
+ * @param {!tr.b.math.Range} rangeOfInterest
+ * @return {Object.<string, Object>} A time breakdown object whose keys are
+ * Chrome userfriendly titles & values are an object that shows the total
+ * CPU time spent in |rangeOfInterestCpuTime|, and the list of event labels
+ * of the group and their total durations in |rangeOfInterestCpuTime|.
+ *
+ * Example:
+ * {
+ * layout: {
+ * total: 100,
+ * events: {'FrameView::performPreLayoutTasks': 20,..}},
+ * v8_runtime: {
+ * total: 500,
+ * events: {'String::NewExternalTwoByte': 0.5,..}},
+ * ...
+ * }
+ */
+ function generateCpuTimeBreakdownTree(mainThread, rangeOfInterest) {
+ return generateTimeBreakdownTree(mainThread, rangeOfInterest,
+ getCPUSelfTime_);
+ }
+
+ return {
+ generateTimeBreakdownTree,
+ generateWallClockTimeBreakdownTree,
+ generateCpuTimeBreakdownTree,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/system_health/breakdown_tree_helpers_test.html b/chromium/third_party/catapult/tracing/tracing/metrics/system_health/breakdown_tree_helpers_test.html
new file mode 100644
index 00000000000..883c915c07f
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/system_health/breakdown_tree_helpers_test.html
@@ -0,0 +1,337 @@
+<!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/metrics/system_health/loading_metric.html">
+<link rel="import" href="/tracing/value/histogram_set.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const rendererPid = 12345;
+ const model = tr.c.TestUtils.newModel(function(model) {
+ const rendererProcess = model.getOrCreateProcess(rendererPid);
+ const mainThread = rendererProcess.getOrCreateThread(2);
+ mainThread.name = 'CrRendererMain';
+
+ // Our main thread looks like:
+ //
+ // [ parseHTML ] [ layout ] [ V8.Exec ]
+ // | [ V8.Exec ] | | | | [ layout ] |
+ // | | | | | | | | | |
+ // | | | | | | | | | |
+ // v v v v v v v v v v
+ // Ts: 200 250 300 400 450 550 570 600 620 650
+ // Cpu:1160 1200 1240 1320 1360 1440 1456 1480 1496 1520
+
+ // Add layout categories
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink',
+ title: 'HTMLDocumentParser::didReceiveParsedChunkFromBackgroundParser',
+ start: 200,
+ duration: 200,
+ cpuStart: 1160,
+ cpuDuration: 160,
+ }));
+
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'devtools.timeline',
+ title: 'Script',
+ start: 250,
+ duration: 50,
+ cpuStart: 1200,
+ cpuDuration: 40,
+ }));
+
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'v8',
+ title: 'V8.Execute',
+ start: 250,
+ duration: 50,
+ args: {'runtime-call-stat': '{"ICMiss": [3, 150], "GC": [10, 60]}'},
+ cpuStart: 1200,
+ cpuDuration: 40,
+ }));
+
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink',
+ title: 'LocalFrameView::layout',
+ start: 450,
+ duration: 100,
+ cpuStart: 1360,
+ cpuDuration: 80,
+ }));
+
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'v8',
+ title: 'V8.Execute',
+ start: 570,
+ duration: 80,
+ args: {'runtime-call-stat': '{"DeOptimize": [1, 42], "GC": [3, 50]}'},
+ cpuStart: 1456,
+ cpuDuration: 64,
+ }));
+
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink',
+ title: 'WebViewImpl::updateAllLifecyclePhases',
+ start: 600,
+ duration: 20,
+ cpuStart: 1480,
+ cpuDuration: 16,
+ }));
+ });
+
+ const chromeHelper = model.getOrCreateHelper(
+ tr.model.helpers.ChromeModelHelper);
+
+ const rendererHelper = chromeHelper.rendererHelpers[rendererPid];
+
+ test('testWallClockTimeBreakdownNoIntersectingBoundary', function() {
+ const rangeOfInterest = tr.b.math.Range.fromExplicitRange(0, 1000);
+ const networkEvents = tr.metrics.sh.getNetworkEventsInRange(
+ rendererHelper.process, rangeOfInterest);
+ const breakdownTree = tr.metrics.sh.generateWallClockTimeBreakdownTree(
+ rendererHelper.mainThread, networkEvents, rangeOfInterest);
+ assert.deepEqual({
+ total: 150,
+ events: {
+ 'HTMLDocumentParser::didReceiveParsedChunkFromBackgroundParser': 150
+ }
+ }, breakdownTree.parseHTML);
+ assert.deepEqual({
+ total: 120,
+ events: {
+ 'LocalFrameView::layout': 100,
+ 'WebViewImpl::updateAllLifecyclePhases': 20,
+ }
+ }, breakdownTree.layout);
+ assert.deepEqual({
+ total: 110,
+ events: {
+ 'V8.Execute': 110,
+ }
+ }, breakdownTree.script_execute);
+ assert.deepEqual({
+ total: 0.302,
+ events: {
+ 'DeOptimize': 0.042,
+ 'GC': 0.11,
+ 'ICMiss': 0.15,
+ }
+ }, breakdownTree.v8_runtime);
+ });
+
+ test('testWallClockTimeBreakdownIntersectingBoundary', function() {
+ // Our main thread looks like:
+ //
+ // [ parseHTML ] [ layout ] [ V8.Exec ]
+ // | [ V8.Exec ] | | | | [ layout ] |
+ // | | | | | | | | | |
+ // | | | | | | | | | |
+ // v v v v v v v v v v
+ // Ts: 200 250 300 400 450 550 570 600 620 650
+ // | |
+ // 275 610
+ const rangeOfInterest = tr.b.math.Range.fromExplicitRange(275, 610);
+ const networkEvents = tr.metrics.sh.getNetworkEventsInRange(
+ rendererHelper.process, rangeOfInterest);
+ const breakdownTree = tr.metrics.sh.generateWallClockTimeBreakdownTree(
+ rendererHelper.mainThread, networkEvents, rangeOfInterest);
+ assert.deepEqual({
+ total: 100,
+ events: {
+ 'HTMLDocumentParser::didReceiveParsedChunkFromBackgroundParser': 100
+ }
+ }, breakdownTree.parseHTML);
+ assert.deepEqual({
+ total: 110,
+ events: {
+ 'LocalFrameView::layout': 100,
+ 'WebViewImpl::updateAllLifecyclePhases': 10,
+ }
+ }, breakdownTree.layout);
+ assert.deepEqual({
+ total: 55,
+ events: {
+ 'V8.Execute': 55,
+ }
+ }, breakdownTree.script_execute);
+ assert.deepEqual({
+ total: 0.151,
+ events: {
+ 'DeOptimize': 0.021,
+ 'GC': 0.055,
+ 'ICMiss': 0.075,
+ }
+ }, breakdownTree.v8_runtime);
+ });
+
+ test('testCpuTimeBreakdownNoIntersectingBoundary', function() {
+ const rangeOfInterest = tr.b.math.Range.fromExplicitRange(100, 800);
+ const breakdownTree = tr.metrics.sh.generateCpuTimeBreakdownTree(
+ rendererHelper.mainThread,
+ rangeOfInterest);
+ assert.deepEqual({
+ total: 120,
+ events: {
+ 'HTMLDocumentParser::didReceiveParsedChunkFromBackgroundParser': 120
+ }
+ }, breakdownTree.parseHTML);
+ assert.deepEqual({
+ total: 96,
+ events: {
+ 'LocalFrameView::layout': 80,
+ 'WebViewImpl::updateAllLifecyclePhases': 16,
+ }
+ }, breakdownTree.layout);
+ assert.deepEqual({
+ total: 88,
+ events: {
+ 'V8.Execute': 88,
+ }
+ }, breakdownTree.script_execute);
+ assert.deepEqual({
+ total: 0.302,
+ events: {
+ 'DeOptimize': 0.042,
+ 'GC': 0.11,
+ 'ICMiss': 0.15,
+ }
+ }, breakdownTree.v8_runtime);
+ });
+
+ test('testCpuTimeBreakdownIntersectingBoundary', function() {
+ // Our main thread looks like:
+ //
+ // [ parseHTML ] [ layout ] [ V8.Exec ]
+ // | [ V8.Exec ] | | | | [ layout ] |
+ // | | | | | | | | | |
+ // | | | | | | | | | |
+ // v v v v v v v v v v
+ // Ts: 200 250 300 400 450 550 570 600 620 650
+ // Cpu:1160 1200 1240 1320 1360 1440 1456 1480 1496 1520
+ // [ ]
+ // Ts RoI 275 610
+ const rangeOfInterest = tr.b.math.Range.fromExplicitRange(275, 610);
+ const breakdownTree = tr.metrics.sh.generateCpuTimeBreakdownTree(
+ rendererHelper.mainThread,
+ rangeOfInterest);
+ assert.deepEqual({
+ total: 80,
+ events: {
+ 'HTMLDocumentParser::didReceiveParsedChunkFromBackgroundParser': 80
+ }
+ }, breakdownTree.parseHTML);
+ assert.deepEqual({
+ total: 88,
+ events: {
+ 'LocalFrameView::layout': 80,
+ 'WebViewImpl::updateAllLifecyclePhases': 8,
+ }
+ }, breakdownTree.layout);
+ assert.deepEqual({
+ total: 44,
+ events: {
+ 'V8.Execute': 44,
+ }
+ }, breakdownTree.script_execute);
+ assert.deepEqual({
+ total: 0.151,
+ events: {
+ 'DeOptimize': 0.021,
+ 'GC': 0.055,
+ 'ICMiss': 0.075
+ }
+ }, breakdownTree.v8_runtime);
+ });
+
+ function createNetworkEvent(start, end, cat) {
+ return tr.c.TestUtils.newAsyncSliceEx({
+ cat,
+ title: 'network events',
+ start,
+ duration: end - start,
+ });
+ }
+
+ test('testBlockedOnNetwork_onlyNetEvent', function() {
+ const model = tr.c.TestUtils.newModel(model => {
+ const mainThread = model.getOrCreateProcess(0)
+ .getOrCreateThread(0);
+ mainThread.name = 'CrRendererMain';
+ const networkEvent = createNetworkEvent(100, 200, 'net');
+ mainThread.asyncSliceGroup.push(networkEvent);
+ });
+ const rendererHelper = model.getOrCreateHelper(
+ tr.model.helpers.ChromeModelHelper)
+ .rendererHelpers[0];
+ const rangeOfInterest = tr.b.math.Range.fromExplicitRange(0, 150);
+ const networkEvents = tr.metrics.sh.getNetworkEventsInRange(
+ rendererHelper.process, rangeOfInterest);
+ const breakdownTree = tr.metrics.sh.generateWallClockTimeBreakdownTree(
+ rendererHelper.mainThread, networkEvents, rangeOfInterest);
+ assert.strictEqual(breakdownTree.blocked_on_network.total, 50);
+ assert.strictEqual(breakdownTree.idle.total, 100);
+ });
+
+ test('testBlockedOnNetwork_netEventAndMainThreadEvent', function() {
+ const model = tr.c.TestUtils.newModel(model => {
+ const mainThread = model.getOrCreateProcess(0)
+ .getOrCreateThread(0);
+ mainThread.name = 'CrRendererMain';
+ const networkEvent = createNetworkEvent(100, 200, 'net');
+ mainThread.asyncSliceGroup.push(networkEvent);
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink',
+ title: 'LocalFrameView::layout',
+ start: 160,
+ duration: 140,
+ }));
+ });
+ const rendererHelper = model.getOrCreateHelper(
+ tr.model.helpers.ChromeModelHelper)
+ .rendererHelpers[0];
+ const rangeOfInterest = tr.b.math.Range.fromExplicitRange(150, 320);
+ const networkEvents = tr.metrics.sh.getNetworkEventsInRange(
+ rendererHelper.process, rangeOfInterest);
+ const breakdownTree = tr.metrics.sh.generateWallClockTimeBreakdownTree(
+ rendererHelper.mainThread, networkEvents, rangeOfInterest);
+ assert.strictEqual(breakdownTree.layout.total, 140);
+ assert.strictEqual(breakdownTree.blocked_on_network.total, 10);
+ assert.strictEqual(breakdownTree.idle.total, 20);
+ });
+
+ test('testBlockedOnNetwork_rangeEmpty', function() {
+ const model = tr.c.TestUtils.newModel(model => {
+ const mainThread = model.getOrCreateProcess(0)
+ .getOrCreateThread(0);
+ mainThread.name = 'CrRendererMain';
+ const networkEvent = createNetworkEvent(100, 200, 'net');
+ mainThread.asyncSliceGroup.push(networkEvent);
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink',
+ title: 'FrameView::layout',
+ start: 160,
+ duration: 140,
+ }));
+ });
+ const rendererHelper = model.getOrCreateHelper(
+ tr.model.helpers.ChromeModelHelper)
+ .rendererHelpers[0];
+ const rangeOfInterest = new tr.b.math.Range();
+ const networkEvents = tr.metrics.sh.getNetworkEventsInRange(
+ rendererHelper.process, rangeOfInterest);
+ const breakdownTree = tr.metrics.sh.generateWallClockTimeBreakdownTree(
+ rendererHelper.mainThread, networkEvents, rangeOfInterest);
+ assert.strictEqual(breakdownTree.layout.total, 0);
+ assert.strictEqual(breakdownTree.blocked_on_network.total, 0);
+ assert.strictEqual(breakdownTree.idle.total, 0);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/system_health/clock_sync_latency_metric.html b/chromium/third_party/catapult/tracing/tracing/metrics/system_health/clock_sync_latency_metric.html
new file mode 100644
index 00000000000..8493e899025
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/system_health/clock_sync_latency_metric.html
@@ -0,0 +1,54 @@
+<!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/metrics/metric_registry.html">
+<link rel="import" href="/tracing/metrics/system_health/utils.html">
+<link rel="import" href="/tracing/model/model.html">
+<link rel="import" href="/tracing/value/histogram.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.metrics.sh', function() {
+ // Bin boundaries for clock sync latency. 0-20 ms with 0.2 ms bins.
+ // 20 ms is a good upper limit because the highest latencies we've seen are
+ // around 10-15 ms, and we expect average latency to go down as we improve
+ // the clock sync mechanism.
+ const LATENCY_BOUNDS = tr.v.HistogramBinBoundaries.createLinear(0, 20, 100);
+
+ function clockSyncLatencyMetric(values, model) {
+ const domains = Array.from(model.clockSyncManager.domainsSeen).sort();
+ for (let i = 0; i < domains.length; i++) {
+ for (let j = i + 1; j < domains.length; j++) {
+ const latency = model.clockSyncManager.getTimeTransformerError(
+ domains[i], domains[j]);
+ const hist = new tr.v.Histogram('clock_sync_latency_' +
+ domains[i].toLowerCase() + '_to_' + domains[j].toLowerCase(),
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter, LATENCY_BOUNDS);
+ hist.customizeSummaryOptions({
+ avg: true,
+ count: false,
+ max: false,
+ min: false,
+ std: false,
+ sum: false,
+ });
+ hist.description = 'Clock sync latency for domain ' + domains[i] +
+ ' to domain ' + domains[j];
+ hist.addSample(latency);
+ values.addHistogram(hist);
+ }
+ }
+ }
+
+ tr.metrics.MetricRegistry.register(clockSyncLatencyMetric);
+
+ return {
+ clockSyncLatencyMetric,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/system_health/clock_sync_latency_metric_test.html b/chromium/third_party/catapult/tracing/tracing/metrics/system_health/clock_sync_latency_metric_test.html
new file mode 100644
index 00000000000..27b5371763e
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/system_health/clock_sync_latency_metric_test.html
@@ -0,0 +1,65 @@
+<!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/metrics/system_health/clock_sync_latency_metric.html">
+<link rel="import" href="/tracing/model/model.html">
+<link rel="import" href="/tracing/value/histogram_set.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ test('clockSyncLatencyMetric', function() {
+ const model = new tr.Model();
+ model.clockSyncManager.addClockSyncMarker(
+ tr.model.ClockDomainId.TELEMETRY, 'ID01', 1.0, 4.0);
+ model.clockSyncManager.addClockSyncMarker(
+ tr.model.ClockDomainId.TELEMETRY, 'ID02', 2.0, 8.0);
+ model.clockSyncManager.addClockSyncMarker(
+ tr.model.ClockDomainId.BATTOR, 'ID01', 2.5);
+ model.clockSyncManager.addClockSyncMarker(
+ tr.model.ClockDomainId.WIN_QPC, 'ID02', 5.0);
+
+ const battorToWinQpcName = 'clock_sync_latency_' +
+ tr.model.ClockDomainId.BATTOR.toLowerCase() + '_to_' +
+ tr.model.ClockDomainId.WIN_QPC.toLowerCase();
+ const winQpcToTelemetryName = 'clock_sync_latency_' +
+ tr.model.ClockDomainId.TELEMETRY.toLowerCase() + '_to_' +
+ tr.model.ClockDomainId.WIN_QPC.toLowerCase();
+ const battorToTelemetryName = 'clock_sync_latency_' +
+ tr.model.ClockDomainId.BATTOR.toLowerCase() + '_to_' +
+ tr.model.ClockDomainId.TELEMETRY.toLowerCase();
+
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.clockSyncLatencyMetric(histograms, model);
+
+ let battorToWinQpcValue = undefined;
+ let winQpcToTelemetryValue = undefined;
+ let battorToTelemetryValue = undefined;
+ for (const value of histograms) {
+ if (value.name === battorToWinQpcName) {
+ battorToWinQpcValue = value;
+ } else if (value.name === winQpcToTelemetryName) {
+ winQpcToTelemetryValue = value;
+ } else if (value.name === battorToTelemetryName) {
+ battorToTelemetryValue = value;
+ }
+ }
+
+ // Clock sync graph is:
+ // [WIN_QPC] --6ms-> [TELEMETRY] --3ms-> [BATTOR]
+
+ assert.isDefined(battorToWinQpcValue);
+ assert.isDefined(winQpcToTelemetryValue);
+ assert.isDefined(battorToTelemetryValue);
+ assert.closeTo(battorToWinQpcValue.average, 9.0, 1e-5);
+ assert.closeTo(winQpcToTelemetryValue.average, 6.0, 1e-5);
+ assert.closeTo(battorToTelemetryValue.average, 3.0, 1e-5);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/system_health/cpu_time_metric.html b/chromium/third_party/catapult/tracing/tracing/metrics/system_health/cpu_time_metric.html
new file mode 100644
index 00000000000..f8a63e84ba7
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/system_health/cpu_time_metric.html
@@ -0,0 +1,101 @@
+<!DOCTYPE html>
+<!--
+Copyright 2016 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/metrics/metric_registry.html">
+<link rel="import" href="/tracing/model/helpers/chrome_model_helper.html">
+<link rel="import" href="/tracing/model/helpers/chrome_renderer_helper.html">
+<link rel="import" href="/tracing/value/histogram.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.metrics.sh', function() {
+ // Use a lower bound of 0.01 for the metric boundaries (when no CPU time
+ // is consumed) and an upper bound of 50 (fifty cores are all active
+ // for the entire time). We can't use zero exactly for the lower bound with an
+ // exponential histogram.
+ const CPU_TIME_PERCENTAGE_BOUNDARIES =
+ tr.v.HistogramBinBoundaries.createExponential(0.01, 50, 200);
+
+ /**
+ * This metric measures total CPU time for Chrome processes, per second of
+ * clock time.
+ * This metric requires only the 'toplevel' tracing category.
+ *
+ * @param {!tr.v.HistogramSet} histograms
+ * @param {!tr.model.Model} model
+ * @param {!Object=} opt_options
+ */
+ function cpuTimeMetric(histograms, model, opt_options) {
+ let rangeOfInterest = model.bounds;
+
+ if (opt_options && opt_options.rangeOfInterest) {
+ rangeOfInterest = opt_options.rangeOfInterest;
+ } else {
+ // If no range of interest is provided, limit the relevant range to
+ // Chrome processes. This prevents us from normalizing against non-Chrome
+ // related slices in the trace.
+ const chromeHelper = model.getOrCreateHelper(
+ tr.model.helpers.ChromeModelHelper);
+ if (chromeHelper) {
+ const chromeBounds = chromeHelper.chromeBounds;
+ if (chromeBounds) {
+ rangeOfInterest = chromeBounds;
+ }
+ }
+ }
+
+ let allProcessCpuTime = 0;
+
+ for (const pid in model.processes) {
+ const process = model.processes[pid];
+ if (tr.model.helpers.ChromeRendererHelper.isTracingProcess(process)) {
+ continue;
+ }
+
+ let processCpuTime = 0;
+ for (const tid in process.threads) {
+ const thread = process.threads[tid];
+ processCpuTime += thread.getCpuTimeForRange(rangeOfInterest);
+ }
+ allProcessCpuTime += processCpuTime;
+ }
+
+ // Normalize cpu time by clock time.
+ let normalizedAllProcessCpuTime = 0;
+ if (rangeOfInterest.duration > 0) {
+ normalizedAllProcessCpuTime =
+ allProcessCpuTime / rangeOfInterest.duration;
+ }
+
+ const unit = tr.b.Unit.byName.normalizedPercentage_smallerIsBetter;
+ const cpuTimeHist = new tr.v.Histogram(
+ 'cpu_time_percentage', unit, CPU_TIME_PERCENTAGE_BOUNDARIES);
+ cpuTimeHist.description =
+ 'Percent CPU utilization, normalized against a single core. Can be ' +
+ 'greater than 100% if machine has multiple cores.';
+ cpuTimeHist.customizeSummaryOptions({
+ avg: true,
+ count: false,
+ max: false,
+ min: false,
+ std: false,
+ sum: false
+ });
+ cpuTimeHist.addSample(normalizedAllProcessCpuTime);
+ histograms.addHistogram(cpuTimeHist);
+ }
+
+ tr.metrics.MetricRegistry.register(cpuTimeMetric, {
+ supportsRangeOfInterest: true
+ });
+
+ return {
+ cpuTimeMetric,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/system_health/cpu_time_metric_test.html b/chromium/third_party/catapult/tracing/tracing/metrics/system_health/cpu_time_metric_test.html
new file mode 100644
index 00000000000..e68b452f4f7
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/system_health/cpu_time_metric_test.html
@@ -0,0 +1,163 @@
+<!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/extras/chrome/chrome_test_utils.html">
+<link rel="import" href="/tracing/metrics/system_health/cpu_time_metric.html">
+<link rel="import" href="/tracing/value/histogram_set.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ function computeCpuTime(customizeModelCallback, opt_options) {
+ const model = tr.c.TestUtils.newModel(function(model) {
+ customizeModelCallback(model);
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.cpuTimeMetric(histograms, model, opt_options);
+ return tr.b.getOnlyElement(histograms).average;
+ }
+
+ // There are two slices, each of length 50. The total bounds is 3000.
+ // This yields total CPU time of 100ms, averaged over 3 seconds is 33ms.
+ test('cpuTimeMetric_oneProcess', function() {
+ const sliceDuration = 50;
+ const totalDuration = 3000;
+ const value = computeCpuTime(function(model) {
+ model.rendererProcess = model.getOrCreateProcess(2);
+ model.rendererMain = model.rendererProcess.getOrCreateThread(3);
+ model.rendererMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ type: tr.model.ThreadSlice,
+ isTopLevel: true,
+ start: 0,
+ duration: sliceDuration,
+ cpuStart: 0,
+ cpuDuration: sliceDuration,
+ }));
+ model.rendererMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ type: tr.model.ThreadSlice,
+ isTopLevel: true,
+ start: totalDuration - sliceDuration,
+ duration: sliceDuration,
+ cpuStart: totalDuration - sliceDuration,
+ cpuDuration: sliceDuration,
+ }));
+ });
+ assert.closeTo(value, 0.033, 0.001);
+ });
+
+ // Normalize against chrome processes, whose slices (both with and without
+ // CPU data) go from 2900 to 3000.
+ test('cpuTimeMetric_browserProcess', function() {
+ const sliceDuration = 50;
+ const totalDuration = 3000;
+ const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
+ model.browserMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ type: tr.model.ThreadSlice,
+ isTopLevel: true,
+ start: totalDuration - 2 * sliceDuration,
+ duration: 2 * sliceDuration,
+ }));
+ model.rendererMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ type: tr.model.ThreadSlice,
+ isTopLevel: true,
+ start: totalDuration - sliceDuration,
+ duration: sliceDuration,
+ cpuStart: totalDuration - sliceDuration,
+ cpuDuration: sliceDuration,
+ }));
+
+ 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: 0,
+ duration: sliceDuration,
+ cpuStart: 0,
+ cpuDuration: sliceDuration,
+ }));
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.cpuTimeMetric(histograms, model);
+ const value = tr.b.getOnlyElement(histograms).average;
+ assert.closeTo(value, 0.5, 0.001);
+ });
+
+ // Makes sure that rangeOfInterest works correctly.
+ test('cpuTimeMetric_oneProcess_rangeOfInterest', function() {
+ const sliceDuration = 50;
+ const totalDuration = 3000;
+ const rangeOfInterest = new tr.b.math.Range.fromExplicitRange(-10, 30);
+ const options = {};
+ options.rangeOfInterest = rangeOfInterest;
+ const value = computeCpuTime(function(model) {
+ model.rendererProcess = model.getOrCreateProcess(2);
+ model.rendererMain = model.rendererProcess.getOrCreateThread(3);
+ model.rendererMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ type: tr.model.ThreadSlice,
+ isTopLevel: true,
+ start: 0,
+ duration: sliceDuration,
+ cpuStart: 0,
+ cpuDuration: sliceDuration,
+ }));
+ model.rendererMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ type: tr.model.ThreadSlice,
+ isTopLevel: true,
+ start: totalDuration - sliceDuration,
+ duration: sliceDuration,
+ cpuStart: totalDuration - sliceDuration,
+ cpuDuration: sliceDuration,
+ }));
+ }, options);
+ assert.closeTo(value, 0.75, 0.001);
+ });
+
+ // Process 1: There are two slices, each of length 50. The total bounds is
+ // 3000. Process 2: There is one slice of length 50.
+ // This yields total CPU time of 150ms, averaged over 3 seconds is 50ms.
+ test('cpuTimeMetric_twoProcesses', function() {
+ const sliceDuration = 50;
+ const totalDuration = 3000;
+ const value = computeCpuTime(function(model) {
+ model.rendererProcess = model.getOrCreateProcess(2);
+ model.rendererMain = model.rendererProcess.getOrCreateThread(3);
+ model.rendererMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ type: tr.model.ThreadSlice,
+ isTopLevel: true,
+ start: 0,
+ duration: sliceDuration,
+ cpuStart: 0,
+ cpuDuration: sliceDuration,
+ }));
+ model.rendererMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ type: tr.model.ThreadSlice,
+ isTopLevel: true,
+ start: totalDuration - sliceDuration,
+ duration: sliceDuration,
+ cpuStart: totalDuration - sliceDuration,
+ cpuDuration: sliceDuration,
+ }));
+
+ const otherProcess = model.getOrCreateProcess(3);
+ const otherThread = otherProcess.getOrCreateThread(4);
+ otherThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ type: tr.model.ThreadSlice,
+ isTopLevel: true,
+ start: 0,
+ duration: sliceDuration,
+ cpuStart: 0,
+ cpuDuration: sliceDuration,
+ }));
+ });
+ assert.closeTo(value, 0.05, 0.001);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/system_health/cpu_time_tree_data_reporter.html b/chromium/third_party/catapult/tracing/tracing/metrics/system_health/cpu_time_tree_data_reporter.html
new file mode 100644
index 00000000000..6d88c1b81ca
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/system_health/cpu_time_tree_data_reporter.html
@@ -0,0 +1,182 @@
+<!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/value/histogram.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.metrics.sh', function() {
+ const CPU_PERCENTAGE_UNIT =
+ tr.b.Unit.byName.normalizedPercentage_smallerIsBetter;
+ const CPU_TIME_UNIT = tr.b.Unit.byName.timeDurationInMs_smallerIsBetter;
+
+ /**
+ * Returns a deep clone of CPU time multidimensional view path object.
+ *
+ * @param {!Array.<!Array.<string>>} previousPath
+ * @returns {!Array.<!Array.<string>>}
+ */
+ function clonePath_(previousPath) {
+ return previousPath.map(subPath => subPath.map(x => x));
+ }
+
+
+ /**
+ * Returns an object containing the processType, threadType, railStage, and
+ * initiatorType encoded in the provided CPU time multidimensional view path
+ * object. Decoding the path into this object can make code easier to
+ * understand than indexing directly into the path array.
+ *
+ * @param {!Array.<!Array.<string>>} path - A path in a CPU time
+ * multidimensional tree view.
+ * @returns {Object.<string, string>}
+ */
+ function decodePath_(path) {
+ return {
+ processType: path[0][0],
+ threadType: path[1][0],
+ railStage: path[2][0],
+ initiatorType: path[2][1]
+ };
+ }
+
+ /**
+ * Returns a unique string representation of |path|.
+ *
+ * Paths are of the following form in CPU time multidimensional trees:
+ * [[processType], [threadType], [railStage, initiatorType]]
+ *
+ * The returned string is of the form
+ * "$processtype:$threadType:$railStage:$initiatorType".
+ *
+ * @param {Array.<!Array.<string>>} path
+ * @returns {string}
+ */
+ function stringifyPathName_(path) {
+ const decodedPath = decodePath_(path);
+ return [
+ decodedPath.processType,
+ decodedPath.threadType,
+ decodedPath.railStage,
+ decodedPath.initiatorType
+ ].join(':');
+ }
+
+ /**
+ * This class is used to traverse a multidimensional tree view and report CPU
+ * percentage and CPU time from the tree as histograms.
+ */
+ class CpuTimeTreeDataReporter {
+ constructor() {
+ this.visitedSet_ = new Set();
+ }
+
+ /**
+ * Extracts CPU percentage and CPU time values from |node| located at |path|
+ * and adds values as histograms to |this.histogramSet_|. Each value is
+ * added as a single sample histogram.
+ *
+ * @param {!tr.b.MultiDimensionalViewNode} node
+ * @param {!Array.<!Array.<string>>} path
+ */
+ reportValuesFromNode_(node, path) {
+ const decodedPath = decodePath_(path);
+ const processType = decodedPath.processType || 'all_processes';
+ const threadType = decodedPath.threadType || 'all_threads';
+
+ // We need some RAIL stage and some initiator type to process a node.
+ // All RAIL stages and all initiator types are handled by the special
+ // 'all_stages' and 'all_initiators' nodes respectively.
+ if (!decodedPath.railStage || !decodedPath.initiatorType) return;
+ const {railStage, initiatorType} = decodedPath;
+
+ const serializedPathName =
+ [processType, threadType, railStage, initiatorType].join(':');
+
+ // node.values is a two element array. The first element holds
+ // cpuPercentage data and the second holds cpuTime data. The final
+ // '.total' (as opposed to '.self') signifies we're including all the data
+ // from children nodes. This is an artifact of how the multidimensional
+ // view data structure works and is not very relevant - we exclusively use
+ // '.total' for CPU time.
+ const cpuPercentageValue = node.values[0].total;
+ const cpuTimeValue = node.values[1].total;
+
+ this.histogramSet_.createHistogram(`cpuPercentage:${serializedPathName}`,
+ CPU_PERCENTAGE_UNIT, cpuPercentageValue);
+ this.histogramSet_.createHistogram(`cpuTime:${serializedPathName}`,
+ CPU_TIME_UNIT, cpuTimeValue);
+ }
+
+
+ /**
+ * Traverses all the paths of a multidimensional view subtree and reports
+ * node data to |this.histogramSet_|.
+ *
+ * @param {!tr.b.MultiDimensionalViewNode} root - Root of the subtree.
+ * @param {!Array.<!Array.<string>>} rootPath - Path of the subtree root
+ * node with respect to |this.rootNode_|.
+ */
+ reportDataFromTree_(root, rootPath) {
+ const rootPathString = stringifyPathName_(rootPath);
+ if (this.visitedSet_.has(rootPathString)) return;
+ this.visitedSet_.add(rootPathString);
+
+ this.reportValuesFromNode_(root, rootPath);
+
+ for (let dimension = 0; dimension < root.children.length; dimension++) {
+ const children = root.children[dimension];
+ for (const [name, node] of children) {
+ const childPath = clonePath_(rootPath);
+ childPath[dimension].push(name);
+ this.reportDataFromTree_(node, childPath);
+ }
+ }
+ }
+
+ /**
+ * Adds values from the multidimensional tree view rooted at |rootNode| as
+ * single value histograms in |histogramSet|.
+ *
+ * @param {!tr.b.MultiDimensionalViewNode} rootNode
+ * @param {!tr.v.HistogramSet} histogramSet
+ */
+ addTreeValuesToHistogramSet(rootNode, histogramSet) {
+ const rootPath = [[], [], []];
+ this.rootNode_ = rootNode;
+ this.histogramSet_ = histogramSet;
+ this.reportDataFromTree_(this.rootNode_, rootPath);
+ }
+
+ /**
+ * Reports values from the multidimensional tree view rooted at |rootNode|
+ * as single value histograms in |histogramSet|.
+ *
+ * The histograms are dynamically generated from the tree. The histogram
+ * names are of the form
+ * "${cpuTime|cpuPercentage}:${processType}:${threadType}:" +
+ * "${railStage}:${railStageInitiator}"
+ *
+ * cpuTime histograms contain total consumed cpu time, while cpuPercentage
+ * histograms contain cpu time as a percentage of wall time. In multicore
+ * situations, this percentage can be larger than 100.
+ *
+ * @param {!tr.b.MultiDimensionalViewNode} rootNode
+ * @param {!tr.v.HistogramSet} histogramSet
+ */
+ static reportToHistogramSet(rootNode, histogramSet) {
+ const reporter = new CpuTimeTreeDataReporter();
+ reporter.addTreeValuesToHistogramSet(rootNode, histogramSet);
+ }
+ }
+
+ return {
+ CpuTimeTreeDataReporter,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/system_health/cpu_time_tree_data_reporter_test.html b/chromium/third_party/catapult/tracing/tracing/metrics/system_health/cpu_time_tree_data_reporter_test.html
new file mode 100644
index 00000000000..cd72b21868d
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/system_health/cpu_time_tree_data_reporter_test.html
@@ -0,0 +1,155 @@
+<!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/multi_dimensional_view.html">
+<link rel="import" href="/tracing/extras/chrome/chrome_processes.html">
+<link rel="import" href="/tracing/metrics/system_health/cpu_time_tree_data_reporter.html">
+<link rel="import" href="/tracing/value/histogram_set.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const reportToHistogramSet =
+ tr.metrics.sh.CpuTimeTreeDataReporter.reportToHistogramSet;
+
+ test('reportToHistogramSet_reportsLeafNodes', () => {
+ const mdvBuilder = new tr.b.MultiDimensionalViewBuilder(
+ 3 /* dimensions (process, thread and rail stage / initiator) */,
+ 2 /* valueCount (cpuPercentage and cpuTime) */);
+ mdvBuilder.addPath(
+ [['browser_process'], ['CrBrowserMain'], ['Animation', 'CSS']],
+ [42, 43], tr.b.MultiDimensionalViewBuilder.ValueKind.TOTAL);
+ const rootNode = mdvBuilder.buildTopDownTreeView();
+
+ const histograms = new tr.v.HistogramSet();
+ reportToHistogramSet(rootNode, histograms);
+
+ const cpuPercentageHistogram = histograms.getHistogramNamed(
+ 'cpuPercentage:browser_process:CrBrowserMain:Animation:CSS');
+ const cpuTimeHistogram = histograms.getHistogramNamed(
+ 'cpuTime:browser_process:CrBrowserMain:Animation:CSS');
+
+ // Histograms exist.
+ assert.isDefined(cpuPercentageHistogram);
+ assert.isDefined(cpuTimeHistogram);
+
+ // Each histogram contains a single sample.
+ assert.strictEqual(cpuPercentageHistogram.running.count, 1);
+ assert.strictEqual(cpuTimeHistogram.running.count, 1);
+
+ // Histogram sample value is correct.
+ assert.closeTo(cpuPercentageHistogram.sum, 42, 1e-7);
+ assert.closeTo(cpuTimeHistogram.sum, 43, 1e-7);
+ });
+
+ test('reportToHistogramSet_reportsAllProcesses', () => {
+ const mdvBuilder = new tr.b.MultiDimensionalViewBuilder(
+ 3 /* dimensions (process, thread and rail stage / initiator) */,
+ 2 /* valueCount (cpuPercentage and cpuTime) */);
+ mdvBuilder.addPath(
+ [['browser_process'], ['CrBrowserMain'], ['Animation', 'CSS']],
+ [42, 43], tr.b.MultiDimensionalViewBuilder.ValueKind.TOTAL);
+ const rootNode = mdvBuilder.buildTopDownTreeView();
+
+ const histograms = new tr.v.HistogramSet();
+ reportToHistogramSet(rootNode, histograms);
+
+ const cpuPercentageHistogram = histograms.getHistogramNamed(
+ 'cpuPercentage:all_processes:CrBrowserMain:Animation:CSS');
+ const cpuTimeHistogram = histograms.getHistogramNamed(
+ 'cpuTime:all_processes:CrBrowserMain:Animation:CSS');
+
+ // Histograms exist.
+ assert.isDefined(cpuPercentageHistogram);
+ assert.isDefined(cpuTimeHistogram);
+
+ // Each histogram contains a single sample.
+ assert.strictEqual(cpuPercentageHistogram.running.count, 1);
+ assert.strictEqual(cpuTimeHistogram.running.count, 1);
+
+ // Histogram sample value is correct.
+ assert.closeTo(cpuPercentageHistogram.sum, 42, 1e-7);
+ assert.closeTo(cpuTimeHistogram.sum, 43, 1e-7);
+ });
+
+ test('reportToHistogramSet_reportsAllThreads', () => {
+ const mdvBuilder = new tr.b.MultiDimensionalViewBuilder(
+ 3 /* dimensions (process, thread and rail stage / initiator) */,
+ 2 /* valueCount (cpuPercentage and cpuTime) */);
+ mdvBuilder.addPath(
+ [['browser_process'], ['CrBrowserMain'], ['Animation', 'CSS']],
+ [42, 43], tr.b.MultiDimensionalViewBuilder.ValueKind.TOTAL);
+ const rootNode = mdvBuilder.buildTopDownTreeView();
+
+ const histograms = new tr.v.HistogramSet();
+ reportToHistogramSet(rootNode, histograms);
+
+ const cpuPercentageHistogram = histograms.getHistogramNamed(
+ 'cpuPercentage:browser_process:all_threads:Animation:CSS');
+ const cpuTimeHistogram = histograms.getHistogramNamed(
+ 'cpuTime:browser_process:all_threads:Animation:CSS');
+
+ // Histograms exist.
+ assert.isDefined(cpuPercentageHistogram);
+ assert.isDefined(cpuTimeHistogram);
+
+ // Each histogram contains a single sample.
+ assert.strictEqual(cpuPercentageHistogram.running.count, 1);
+ assert.strictEqual(cpuTimeHistogram.running.count, 1);
+
+ // Histogram sample value is correct.
+ assert.closeTo(cpuPercentageHistogram.sum, 42, 1e-7);
+ assert.closeTo(cpuTimeHistogram.sum, 43, 1e-7);
+ });
+
+ test('reportToHistogramSet_doesNotAggregateStagesWithoutAllStagesNode',
+ () => {
+ const mdvBuilder = new tr.b.MultiDimensionalViewBuilder(
+ 3 /* dimensions (process, thread and rail stage / initiator) */,
+ 2 /* valueCount (cpuPercentage and cpuTime) */);
+ mdvBuilder.addPath(
+ [['browser_process'], ['CrBrowserMain'], ['Animation', 'CSS']],
+ [42, 43], tr.b.MultiDimensionalViewBuilder.ValueKind.TOTAL);
+ const rootNode = mdvBuilder.buildTopDownTreeView();
+
+ const histograms = new tr.v.HistogramSet();
+ reportToHistogramSet(rootNode, histograms);
+
+ // all_stages:CSS does not make sense (e.g. we will never
+ // aggregate Scroll Response and Scroll Animation, even if that's a
+ // thing), so we use all_stages:all_initiators here.
+ assert.isUndefined(histograms.getHistogramNamed(
+ 'cpuPercentage:' +
+ 'browser_process:CrBrowserMain:all_stages:all_initiators'));
+ assert.isUndefined(histograms.getHistogramNamed(
+ 'cpuTime:' +
+ 'browser_process:CrBrowserMain:all_stages:all_initiators'));
+ });
+
+ test('reportToHistogramSet_' +
+ 'doesNotAggregateInitiatorsWithoutAllInitiatorsNode',
+ () => {
+ const mdvBuilder = new tr.b.MultiDimensionalViewBuilder(
+ 3 /* dimensions (process, thread and rail stage / initiator) */,
+ 2 /* valueCount (cpuPercentage and cpuTime) */);
+ mdvBuilder.addPath(
+ [['browser_process'], ['CrBrowserMain'], ['Animation', 'CSS']],
+ [42, 43], tr.b.MultiDimensionalViewBuilder.ValueKind.TOTAL);
+ const rootNode = mdvBuilder.buildTopDownTreeView();
+
+ const histograms = new tr.v.HistogramSet();
+ reportToHistogramSet(rootNode, histograms);
+
+ assert.isUndefined(histograms.getHistogramNamed(
+ 'cpuPercentage:' +
+ 'browser_process:CrBrowserMain:Animation:all_initiators'));
+ assert.isUndefined(histograms.getHistogramNamed(
+ 'cpuTime:browser_process:CrBrowserMain:Animation:all_initiators'));
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/system_health/expected_queueing_time_metric.html b/chromium/third_party/catapult/tracing/tracing/metrics/system_health/expected_queueing_time_metric.html
new file mode 100644
index 00000000000..b9ef3de7ebd
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/system_health/expected_queueing_time_metric.html
@@ -0,0 +1,462 @@
+<!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/utils.html">
+<link rel="import" href="/tracing/extras/chrome/estimated_input_latency.html">
+<link rel="import" href="/tracing/extras/chrome/event_finder_utils.html">
+<link rel="import" href="/tracing/extras/v8/runtime_stats_entry.html">
+<link rel="import" href="/tracing/metrics/v8/utils.html">
+<link rel="import" href="/tracing/model/helpers/chrome_model_helper.html">
+<link rel="import" href="/tracing/value/histogram.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview
+ * This file defines the input latency metric estimated as the maximum
+ * expected queueing time (EQT) in sliding window of size 500ms.
+ *
+ * The EQT is defined as the average queueing time of a hypothetical input
+ * event arriving at a random time in the given time window.
+ * For more information see:
+ * - https://goo.gl/OQ2bX6
+ * - https://goo.gl/jmWpMl
+ * - https://goo.gl/lga4iO
+ */
+tr.exportTo('tr.metrics.sh', function() {
+ // The size of the sliding window is chosen arbitrarily (see
+ // https://goo.gl/lga4iO).
+ const WINDOW_SIZE_MS = 500;
+ const EQT_BOUNDARIES = tr.v.HistogramBinBoundaries
+ .createExponential(0.01, WINDOW_SIZE_MS, 50);
+
+ /**
+ * Returns true if the slice contains a forced GC event. Some stories force
+ * garbage collection before sampling memory usage. Since a forced GC takes
+ * long time we need to ignore it to avoid biasing the input latency results.
+ */
+ function containsForcedGC_(slice) {
+ return slice.findTopmostSlicesRelativeToThisSlice(
+ tr.metrics.v8.utils.isForcedGarbageCollectionEvent).length > 0;
+ }
+
+ /**
+ * @param {!string} name Name of the histogram.
+ * @param {!string} description Description of the histogram.
+ * @returns {!tr.v.Histogram}
+ */
+ function getOrCreateHistogram_(histograms, name, description) {
+ return histograms.getHistogramNamed(name) || histograms.createHistogram(
+ name, tr.b.Unit.byName.timeDurationInMs_smallerIsBetter, [], {
+ binBoundaries: EQT_BOUNDARIES,
+ description,
+ summaryOptions: {
+ avg: false,
+ count: false,
+ max: true,
+ min: false,
+ std: false,
+ sum: false,
+ },
+ });
+ }
+
+ /**
+ * Computes the maximum expected queueing time in the sliding time window
+ * of size 500ms (WINDOW_SIZE_MS). The function produces four Histograms:
+ * - total:500ms_window:renderer_eqt,
+ * - total:500ms_window:renderer_eqt_cpu,
+ * - interactive:500ms_window:renderer_eqt.
+ * - interactive:500ms_window:renderer_eqt_cpu.
+ * The 'total' histograms are computed for the whole trace. The 'interactive'
+ * histograms are computed for the time while the page is interactive.
+ * The 'cpu' histograms use the CPU time of the events instead of the wall-
+ * clock times. Each renderer process adds one sample to the histograms.
+ */
+ function expectedQueueingTimeMetric(histograms, model) {
+ const chromeHelper = model.getOrCreateHelper(
+ tr.model.helpers.ChromeModelHelper);
+ const rendererHelpers = Object.values(chromeHelper.rendererHelpers);
+ const rendererToInteractiveTimestamps =
+ tr.e.chrome.getInteractiveTimestamps(model);
+ addExpectedQueueingTimeMetric_(
+ 'renderer_eqt',
+ event => {return {start: event.start, duration: event.duration};},
+ false, rendererHelpers, rendererToInteractiveTimestamps, histograms,
+ model);
+ addExpectedQueueingTimeMetric_(
+ 'renderer_eqt_cpu',
+ event => {return {start: event.cpuStart, duration: event.cpuDuration};},
+ true, rendererHelpers, rendererToInteractiveTimestamps, histograms,
+ model);
+ }
+
+ /**
+ * @callback EventTimesCallback
+ * @param {!tr.b.Event} event
+ * @return {{start: !number, duration: !number}} event start time and duration.
+ */
+
+ /**
+ * The actual implementation of the EQT metric.
+ * @param {!string} eqtName the metric name part of the histogram name.
+ * @param {!EventTimesCallback} getEventTimes.
+ * @param {!Array.<tr.model.helpers.ChromeRendererHelper>} rendererHelpers.
+ * @param {!Map.<number, Array.<number>>} rendererToInteractiveTimestamps
+ * a map from renderer pid to an array of interactive timestamps.
+ */
+ function addExpectedQueueingTimeMetric_(eqtName, getEventTimes, isCpuTime,
+ rendererHelpers, rendererToInteractiveTimestamps, histograms, model) {
+ /**
+ * Extracts tasks for EQT computation from the given renderer.
+ * A task is a pair of {start, end} times.
+ */
+ function getTasks(rendererHelper) {
+ const tasks = [];
+ for (const slice of
+ tr.e.chrome.EventFinderUtils.findToplevelSchedulerTasks(
+ rendererHelper.mainThread)) {
+ const times = getEventTimes(slice);
+ if (times.duration > 0 && !containsForcedGC_(slice)) {
+ tasks.push({start: times.start, end: times.start + times.duration});
+ }
+ }
+ return tasks;
+ }
+ const totalHistogram = getOrCreateHistogram_(
+ histograms,
+ `total:${WINDOW_SIZE_MS}ms_window:${eqtName}`,
+ `The maximum EQT in a ${WINDOW_SIZE_MS}ms sliding window` +
+ ' for a given renderer');
+ const interactiveHistogram = getOrCreateHistogram_(
+ histograms,
+ `interactive:${WINDOW_SIZE_MS}ms_window:${eqtName}`,
+ `The maximum EQT in a ${WINDOW_SIZE_MS}ms sliding window` +
+ ' for a given renderer while the page is interactive');
+ for (const rendererHelper of rendererHelpers) {
+ if (rendererHelper.isChromeTracingUI) continue;
+ // Renderers with lifetime smaller than WINDOW_SIZE_MS do not have
+ // meaningful EQT.
+ if (rendererHelper.mainThread.bounds.duration < WINDOW_SIZE_MS) continue;
+
+ const tasks = getTasks(rendererHelper);
+ const {totalBreakdown, interactiveBreakdown} = getV8Contribution_(
+ eqtName,
+ getEventTimes,
+ isCpuTime,
+ totalHistogram,
+ interactiveHistogram,
+ rendererToInteractiveTimestamps,
+ histograms,
+ rendererHelper,
+ model);
+ totalHistogram.addSample(
+ tr.e.chrome.maxExpectedQueueingTimeInSlidingWindow(
+ rendererHelper.mainThread.bounds.min,
+ rendererHelper.mainThread.bounds.max,
+ WINDOW_SIZE_MS, tasks), {v8: totalBreakdown});
+ const interactiveTimestamps =
+ rendererToInteractiveTimestamps.get(rendererHelper.pid);
+ if (interactiveTimestamps.length === 0) continue;
+ if (interactiveTimestamps.length > 1) {
+ // TODO(ulan): Support multiple interactive time windows when
+ // https://crbug.com/692112 is fixed.
+ continue;
+ }
+ const interactiveWindow =
+ tr.b.math.Range.fromExplicitRange(interactiveTimestamps[0], Infinity)
+ .findIntersection(rendererHelper.mainThread.bounds);
+ interactiveHistogram.addSample(
+ tr.e.chrome.maxExpectedQueueingTimeInSlidingWindow(
+ interactiveWindow.min, interactiveWindow.max,
+ WINDOW_SIZE_MS, tasks), {v8: interactiveBreakdown});
+ }
+ }
+
+ /**
+ * Computes the contribution of the selected events to the expected queueing
+ * time. We define the contribution as the maximum expected queueing time in
+ * the sliding time window of size 500ms (WINDOW_SIZE_MS) for the trace that
+ * is modified as follows:
+ * - from each top-level task remove all subevents except the selected events.
+ * - removing subevents shrinks a task by shifting its end time closer to
+ * the start time. The start time does not change.
+ *
+ * Similar to the expectedQueueingTime this function computes two histograms:
+ * total and interactive. For example:
+ * - total:500ms_window:renderer_eqt:v8,
+ * - interactive:500ms_window:renderer_eqt:v8.
+ * Each renderer process adds one sample to the histograms.
+ * Both histograms are added to the given histogram set.
+ *
+ * @param {!string} eqtName the metric name part of the histogram name.
+ * @param {!EventTimesCallback} getEventTimes.
+ * @param {boolean} isCpuTime
+ * @param {!tr.v.Histogram} totalEqtHistogram
+ * @param {!tr.v.Histogram} interactiveEqtHistogram
+ * @param {!Map.<number, Array.<number>>} rendererToInteractiveTimestamps
+ * a map from renderer pid to an array of interactive timestamps.
+ * @param {!tr.v.HistogramSet} histograms
+ * @param {!tr.model.helpers.ChromeRendererHelper} rendererHelper
+ * @param {!tr.model.Model} model
+ * @return {{totalBreakdown: !tr.v.d.Breakdown,
+ * interactiveBreakdown: !tr.v.d.Breakdown}}
+ */
+ function getV8Contribution_(
+ eqtName, getEventTimes, isCpuTime, totalEqtHistogram,
+ interactiveEqtHistogram, rendererToInteractiveTimestamps, histograms,
+ rendererHelper, model) {
+ if (!model.categories.includes('v8')) return {};
+
+ const totalBreakdown = new tr.v.d.Breakdown();
+ const interactiveBreakdown = new tr.v.d.Breakdown();
+ // Include task extractors that use tracing.
+ const eventNamesWithTaskExtractors =
+ getV8EventNamesWithTaskExtractors_(getEventTimes);
+ if (!isCpuTime) {
+ // Include task extractors that use RCS. RCS does not provide cpu time
+ // so include these only for wall clock time.
+ const taskExtractorsUsingRCS =
+ getV8EventNamesWithTaskExtractorsUsingRCS_(getEventTimes);
+ for (const [eventName, getTasks] of taskExtractorsUsingRCS) {
+ eventNamesWithTaskExtractors.set(eventName, getTasks);
+ }
+ }
+
+ let totalNames = totalEqtHistogram.diagnostics.get('v8');
+ if (!totalNames) {
+ totalNames = new tr.v.d.RelatedNameMap();
+ totalEqtHistogram.diagnostics.set('v8', totalNames);
+ }
+ let interactiveNames = interactiveEqtHistogram.diagnostics.get('v8');
+ if (!interactiveNames) {
+ interactiveNames = new tr.v.d.RelatedNameMap();
+ interactiveEqtHistogram.diagnostics.set('v8', interactiveNames);
+ }
+
+ for (const [eventName, getTasks] of eventNamesWithTaskExtractors) {
+ const totalHistogram = getOrCreateHistogram_(histograms,
+ `total:${WINDOW_SIZE_MS}ms_window:${eqtName}:${eventName}`,
+ `Contribution to the expected queueing time by ${eventName}` +
+ ' for a given renderer. It is computed as the maximum EQT in' +
+ ` a ${WINDOW_SIZE_MS}ms sliding window after shrinking top-level` +
+ ` tasks to contain only ${eventName} subevents`);
+ const interactiveHistogram = getOrCreateHistogram_(histograms,
+ `interactive:${WINDOW_SIZE_MS}ms_window:${eqtName}:${eventName}`,
+ `Contribution to the expected queueing time by ${eventName}` +
+ ' for a given renderer while the page is interactive. It is' +
+ ` computed as the maximum EQT in a ${WINDOW_SIZE_MS}ms sliding` +
+ ' window after shrinking top-level tasks to contain only' +
+ ` ${eventName} subevents`);
+
+ const tasks = getTasks(rendererHelper);
+ const totalSample = tr.e.chrome.maxExpectedQueueingTimeInSlidingWindow(
+ rendererHelper.mainThread.bounds.min,
+ rendererHelper.mainThread.bounds.max,
+ WINDOW_SIZE_MS, tasks);
+ totalHistogram.addSample(totalSample);
+ totalBreakdown.set(eventName, totalSample);
+ totalNames.set(eventName, totalHistogram.name);
+
+ const interactiveTimestamps =
+ rendererToInteractiveTimestamps.get(rendererHelper.pid);
+ if (interactiveTimestamps.length === 0) continue;
+ if (interactiveTimestamps.length > 1) {
+ // TODO(ulan): Support multiple interactive time windows when
+ // https://crbug.com/692112 is fixed.
+ continue;
+ }
+ const interactiveWindow =
+ tr.b.math.Range.fromExplicitRange(interactiveTimestamps[0], Infinity)
+ .findIntersection(rendererHelper.mainThread.bounds);
+ const interactiveSample =
+ tr.e.chrome.maxExpectedQueueingTimeInSlidingWindow(
+ interactiveWindow.min, interactiveWindow.max,
+ WINDOW_SIZE_MS, tasks);
+ interactiveHistogram.addSample(interactiveSample);
+ interactiveBreakdown.set(eventName, interactiveSample);
+ interactiveNames.set(eventName, interactiveHistogram.name);
+ }
+ return {totalBreakdown, interactiveBreakdown};
+ }
+
+ /**
+ * @callback TaskExtractor
+ * @param {!tr.model.helpers.ChromeRendererHelper} rendererHelper
+ * @return {Array.<{start: !number, end: !number}>}
+ */
+
+ /**
+ * @param {!EventTimesCallback} getEventTimes.
+ * @returns {!Map.<string, TaskExtractor>} a map from V8 event names to
+ * the corresponding task extractor functions.
+ */
+ function getV8EventNamesWithTaskExtractors_(getEventTimes, cpuMetrics) {
+ /**
+ * @param {!tr.b.Event} slice.
+ * @param {!function(tr.b.Event): boolean} predicate that selects V8 events.
+ * @param {function(tr.b.Event): boolean} excludePredicate that excludes
+ * V8 events.
+ * @returns {!number} the total duration of topmost subslices of the given
+ * slice that satisfy the given |predicate| after filtering out any
+ * events that satisfy the |excludePredicate| in the subslices.
+ */
+ function durationOfTopmostSubSlices(slice, predicate, excludePredicate) {
+ let duration = 0;
+ for (const sub of slice.findTopmostSlicesRelativeToThisSlice(predicate)) {
+ duration += getEventTimes(sub).duration;
+ if (excludePredicate !== null && excludePredicate !== undefined) {
+ duration -= durationOfTopmostSubSlices(sub, excludePredicate);
+ }
+ }
+ return duration;
+ }
+
+ /**
+ * @param {!function(tr.b.Event): boolean} predicate that selects V8 events.
+ * @param {function(tr.b.Event): boolean} excludePredicate that excludes
+ * V8 events.
+ * @returns {!TaskExtractor} a function that extracts tasks from the given
+ * renderer. Each task is a pair of {start, end} times and its duration
+ * represents the contribution of the events selected by the
+ * given |predicate| and |excludePredicate|.
+ */
+ function taskExtractor(predicate, excludePredicate) {
+ return function(rendererHelper) {
+ const slices = tr.e.chrome.EventFinderUtils.findToplevelSchedulerTasks(
+ rendererHelper.mainThread);
+ const result = [];
+ for (const slice of slices) {
+ const times = getEventTimes(slice);
+ if (times.duration > 0 && !containsForcedGC_(slice)) {
+ const duration = durationOfTopmostSubSlices(
+ slice, predicate, excludePredicate);
+ result.push({start: times.start, end: times.start + duration});
+ }
+ }
+ return result;
+ };
+ }
+
+ return new Map([
+ [
+ 'v8',
+ taskExtractor(tr.metrics.v8.utils.isV8Event)
+ ],
+ [
+ 'v8:execute',
+ taskExtractor(tr.metrics.v8.utils.isV8ExecuteEvent)
+ ],
+ [
+ 'v8:gc',
+ taskExtractor(tr.metrics.v8.utils.isGarbageCollectionEvent)
+ ],
+ [
+ 'v8:gc:full-mark-compactor',
+ taskExtractor(tr.metrics.v8.utils.isFullMarkCompactorEvent)
+ ],
+ [
+ 'v8:gc:incremental-marking',
+ taskExtractor(tr.metrics.v8.utils.isIncrementalMarkingEvent)
+ ],
+ [
+ 'v8:gc:latency-mark-compactor',
+ taskExtractor(tr.metrics.v8.utils.isLatencyMarkCompactorEvent)
+ ],
+ [
+ 'v8:gc:memory-mark-compactor',
+ taskExtractor(tr.metrics.v8.utils.isMemoryMarkCompactorEvent)
+ ],
+ [
+ 'v8:gc:scavenger',
+ taskExtractor(tr.metrics.v8.utils.isScavengerEvent)
+ ]
+ ]);
+ }
+
+ /**
+ * @param {!EventTimesCallback} getEventTimes.
+ * @param {!function(!string): boolean} predicate that selects RCS category.
+ * @param {!tr.model.helpers.ChromeRendererHelper} rendererHelper
+ * @returns {Array.<{start: !number, end: !number}>} a list of tasks. Each
+ * task is a pair of {start, end} times and its duration represents the
+ * the contribution of the events selected by the given |predicate|.
+ */
+ function extractTaskRCS(getEventTimes, predicate, rendererHelper) {
+ const result = [];
+ for (const topSlice of
+ rendererHelper.mainThread.sliceGroup.topLevelSlices) {
+ const times = getEventTimes(topSlice);
+ if (times.duration <= 0 || containsForcedGC_(topSlice)) {
+ continue;
+ }
+ // Find all V8ThreadSlices in the top level slice.
+ const v8ThreadSlices = [];
+ for (const slice of topSlice.descendentSlices) {
+ if (tr.metrics.v8.utils.isV8RCSEvent(slice)) {
+ v8ThreadSlices.push(slice);
+ }
+ }
+
+ // Find the event specified by predicate.
+ const runtimeGroupCollection =
+ new tr.e.v8.RuntimeStatsGroupCollection();
+ runtimeGroupCollection.addSlices(v8ThreadSlices);
+ let duration = 0;
+ for (const runtimeGroup of runtimeGroupCollection.runtimeGroups) {
+ if (predicate(runtimeGroup.name)) {
+ duration += runtimeGroup.time;
+ }
+ }
+
+ duration = tr.b.convertUnit(
+ duration,
+ tr.b.UnitPrefixScale.METRIC.MICRO,
+ tr.b.UnitPrefixScale.METRIC.MILLI);
+ result.push({start: times.start, end: times.start + duration});
+ }
+ return result;
+ }
+
+ /**
+ * @param {!EventTimesCallback} getEventTimes.
+ * @returns {!Map.<string, TaskExtractor>} a map from V8 event names to
+ * the corresponding task extractor functions.
+ */
+ function getV8EventNamesWithTaskExtractorsUsingRCS_(getEventTimes) {
+ const extractors = new Map();
+ extractors.set('v8:compile_rcs',
+ rendererHelper => extractTaskRCS(
+ getEventTimes,
+ tr.metrics.v8.utils.isCompileRCSCategory,
+ rendererHelper));
+ extractors.set('v8:compile:optimize_rcs',
+ rendererHelper => extractTaskRCS(
+ getEventTimes,
+ tr.metrics.v8.utils.isCompileOptimizeRCSCategory,
+ rendererHelper));
+ extractors.set('v8:compile:parse_rcs',
+ rendererHelper => extractTaskRCS(
+ getEventTimes,
+ tr.metrics.v8.utils.isCompileParseRCSCategory,
+ rendererHelper));
+ extractors.set('v8:compile:compile-unoptimize_rcs',
+ rendererHelper => extractTaskRCS(
+ getEventTimes,
+ tr.metrics.v8.utils.isCompileUnoptimizeRCSCategory,
+ rendererHelper));
+ return extractors;
+ }
+
+ tr.metrics.MetricRegistry.register(expectedQueueingTimeMetric);
+
+ return {
+ expectedQueueingTimeMetric,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/system_health/expected_queueing_time_metric_test.html b/chromium/third_party/catapult/tracing/tracing/metrics/system_health/expected_queueing_time_metric_test.html
new file mode 100644
index 00000000000..b300c523964
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/system_health/expected_queueing_time_metric_test.html
@@ -0,0 +1,409 @@
+<!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/extras/chrome/chrome_test_utils.html">
+<link rel="import"
+ href="/tracing/extras/chrome/chrome_user_friendly_category_driver.html">
+<link rel="import" href="/tracing/metrics/system_health/expected_queueing_time_metric.html">
+<link rel="import" href="/tracing/value/histogram_set.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ function addInteractiveTimestamp(rendererProcess, mainThread, timestamp) {
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: timestamp,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ mainThread.asyncSliceGroup.push(tr.c.TestUtils.newSliceEx({
+ cat: 'disabled-by-default-network',
+ title: 'ResourceLoad',
+ start: timestamp,
+ duration: 5.0,
+ }));
+ rendererProcess.objects.addSnapshot(
+ 'ptr', 'loading', 'FrameLoader', timestamp, {
+ isLoadingMainFrame: true,
+ frame: {id_ref: '0xdeadbeef'},
+ documentLoaderURL: 'http://example.com'
+ });
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'loading',
+ title: 'firstMeaningfulPaintCandidate',
+ start: timestamp + 1,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'domContentLoadedEventEnd',
+ start: timestamp + 1,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ }
+
+ test('expectedQueueingTime', function() {
+ const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
+ const rendererProcess = model.rendererProcess;
+ const mainThread = model.rendererMain;
+ addInteractiveTimestamp(rendererProcess, mainThread, 200);
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'toplevel',
+ title: tr.e.chrome.SCHEDULER_TOP_LEVEL_TASK_TITLE,
+ start: 0,
+ duration: 100,
+ cpuStart: 0,
+ cpuDuration: 50,
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'toplevel',
+ title: tr.e.chrome.SCHEDULER_TOP_LEVEL_TASK_TITLE,
+ start: 3000,
+ duration: 10,
+ cpuStart: 3000,
+ cpuDuration: 5,
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'toplevel',
+ title: tr.e.chrome.SCHEDULER_TOP_LEVEL_TASK_TITLE,
+ start: 9000,
+ duration: 10,
+ cpuStart: 9000,
+ cpuDuration: 5,
+ }));
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.expectedQueueingTimeMetric(histograms, model);
+ const interactiveEqt = histograms.getHistogramNamed(
+ 'interactive:500ms_window:renderer_eqt');
+ assert.strictEqual(0.1, interactiveEqt.average);
+ const totalEqt = histograms.getHistogramNamed(
+ 'total:500ms_window:renderer_eqt');
+ assert.strictEqual(10, totalEqt.average);
+ const interactiveEqtCpu = histograms.getHistogramNamed(
+ 'interactive:500ms_window:renderer_eqt_cpu');
+ assert.strictEqual(0.025, interactiveEqtCpu.average);
+ const totalEqtCpu = histograms.getHistogramNamed(
+ 'total:500ms_window:renderer_eqt_cpu');
+ assert.strictEqual(2.5, totalEqtCpu.average);
+ });
+
+ test('expectedQueueingTime_noInteractive', function() {
+ const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
+ const rendererProcess = model.rendererProcess;
+ const mainThread = model.rendererMain;
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'toplevel',
+ title: tr.e.chrome.SCHEDULER_TOP_LEVEL_TASK_TITLE,
+ start: 0,
+ duration: 100,
+ cpuStart: 0,
+ cpuDuration: 100,
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'toplevel',
+ title: tr.e.chrome.SCHEDULER_TOP_LEVEL_TASK_TITLE,
+ start: 3000,
+ duration: 10,
+ cpuStart: 3000,
+ cpuDuration: 10,
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'toplevel',
+ title: tr.e.chrome.SCHEDULER_TOP_LEVEL_TASK_TITLE,
+ start: 9000,
+ duration: 10,
+ cpuStart: 9000,
+ cpuDuration: 10,
+ }));
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.expectedQueueingTimeMetric(histograms, model);
+ const interactiveEQT = histograms.getHistogramNamed(
+ 'interactive:500ms_window:renderer_eqt');
+ assert.strictEqual(0, interactiveEQT.numValues);
+ });
+
+ test('expectedQueueingTime_multipleInteractive', function() {
+ const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
+ const rendererProcess = model.rendererProcess;
+ const mainThread = model.rendererMain;
+ addInteractiveTimestamp(rendererProcess, mainThread, 200);
+ addInteractiveTimestamp(rendererProcess, mainThread, 6000);
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'toplevel',
+ title: tr.e.chrome.SCHEDULER_TOP_LEVEL_TASK_TITLE,
+ start: 0,
+ duration: 100,
+ cpuStart: 0,
+ cpuDuration: 100,
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'toplevel',
+ title: tr.e.chrome.SCHEDULER_TOP_LEVEL_TASK_TITLE,
+ start: 3000,
+ duration: 10,
+ cpuStart: 3000,
+ cpuDuration: 10,
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'toplevel',
+ title: tr.e.chrome.SCHEDULER_TOP_LEVEL_TASK_TITLE,
+ start: 12000,
+ duration: 10,
+ cpuStart: 9000,
+ cpuDuration: 10,
+ }));
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.expectedQueueingTimeMetric(histograms, model);
+ const interactiveEQT = histograms.getHistogramNamed(
+ 'interactive:500ms_window:renderer_eqt');
+ // TODO(ulan): Support multiple interactive time windows when
+ // https://crbug.com/692112 is fixed.
+ assert.strictEqual(0, interactiveEQT.numValues);
+ });
+
+ test('expectedQueueingTime_multipleRenderersAggregates', function() {
+ const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
+ const rendererProcess = model.rendererProcess;
+ const mainThread = model.rendererMain;
+ addInteractiveTimestamp(rendererProcess, mainThread, 200);
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'toplevel',
+ title: tr.e.chrome.SCHEDULER_TOP_LEVEL_TASK_TITLE,
+ start: 0,
+ duration: 100,
+ cpuStart: 0,
+ cpuDuration: 100,
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'toplevel',
+ title: tr.e.chrome.SCHEDULER_TOP_LEVEL_TASK_TITLE,
+ start: 3000,
+ duration: 10,
+ cpuStart: 3000,
+ cpuDuration: 10,
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'toplevel',
+ title: tr.e.chrome.SCHEDULER_TOP_LEVEL_TASK_TITLE,
+ start: 9000,
+ duration: 10,
+ cpuStart: 9000,
+ cpuDuration: 10,
+ }));
+ const rendererProcess2 = model.getOrCreateProcess(1985);
+ const mainThread2 = rendererProcess2.getOrCreateThread(2);
+ mainThread2.name = 'CrRendererMain';
+ addInteractiveTimestamp(rendererProcess2, mainThread2, 200);
+ mainThread2.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'toplevel',
+ title: tr.e.chrome.SCHEDULER_TOP_LEVEL_TASK_TITLE,
+ start: 9000,
+ duration: 0,
+ cpuStart: 9000,
+ cpuDuration: 0,
+ }));
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.expectedQueueingTimeMetric(histograms, model);
+ const eqt = histograms.getHistogramNamed(
+ 'interactive:500ms_window:renderer_eqt');
+ assert.strictEqual(0.05, eqt.average);
+ });
+
+ test('expectedQueueingTime_relatedV8Histograms', function() {
+ const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
+ const rendererProcess = model.rendererProcess;
+ const mainThread = model.rendererMain;
+ addInteractiveTimestamp(rendererProcess, mainThread, 200);
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'toplevel',
+ title: tr.e.chrome.SCHEDULER_TOP_LEVEL_TASK_TITLE,
+ start: 0,
+ duration: 100,
+ cpuStart: 0,
+ cpuDuration: 100,
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'v8',
+ title: 'V8.GCFinalizeMC',
+ start: 50,
+ duration: 50,
+ cpuStart: 50,
+ cpuDuration: 50,
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'toplevel',
+ title: tr.e.chrome.SCHEDULER_TOP_LEVEL_TASK_TITLE,
+ start: 3000,
+ duration: 10,
+ cpuStart: 3000,
+ cpuDuration: 10,
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'disabled-by-default-v8.compile',
+ title: 'V8.RecompileSynchronous',
+ start: 3000,
+ duration: 5,
+ cpuStart: 3000,
+ cpuDuration: 5,
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'toplevel',
+ title: tr.e.chrome.SCHEDULER_TOP_LEVEL_TASK_TITLE,
+ start: 9000,
+ duration: 10,
+ cpuStart: 9000,
+ cpuDuration: 10,
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'v8',
+ title: 'V8.GCFinalizeMC',
+ start: 9000,
+ duration: 5,
+ cpuStart: 9000,
+ cpuDuration: 5,
+ }));
+ mainThread.sliceGroup.createSubSlices();
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.expectedQueueingTimeMetric(histograms, model);
+
+ const eqt = histograms.getHistogramNamed(
+ 'interactive:500ms_window:renderer_eqt');
+ assert.strictEqual('interactive:500ms_window:renderer_eqt:v8',
+ eqt.diagnostics.get('v8').get('v8'));
+ assert.strictEqual(0.025,
+ eqt.getBinForValue(0.1).diagnosticMaps[0].get('v8').get('v8'));
+
+ const eqtCpu = histograms.getHistogramNamed(
+ 'interactive:500ms_window:renderer_eqt_cpu');
+ assert.strictEqual('interactive:500ms_window:renderer_eqt_cpu:v8',
+ eqtCpu.diagnostics.get('v8').get('v8'));
+ assert.strictEqual(0.025,
+ eqtCpu.getBinForValue(0.1).diagnosticMaps[0].get('v8').get('v8'));
+ });
+
+ test('expectedQueueingTimeRCS', function() {
+ const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
+ const rendererProcess = model.rendererProcess;
+ const mainThread = model.rendererMain;
+ addInteractiveTimestamp(rendererProcess, mainThread, 200);
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'toplevel',
+ title: tr.e.chrome.SCHEDULER_TOP_LEVEL_TASK_TITLE,
+ start: 0,
+ duration: 100,
+ cpuStart: 0,
+ cpuDuration: 100,
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'v8',
+ title: 'V8.newInstance',
+ type: tr.e.v8.V8ThreadSlice,
+ start: 12555,
+ duration: 990,
+ args: {
+ 'runtime-call-stats': {
+ JS_Execution: [1, 11],
+ ParseLazy: [5, 3],
+ }
+ }
+ }));
+
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'toplevel',
+ title: tr.e.chrome.SCHEDULER_TOP_LEVEL_TASK_TITLE,
+ start: 3000,
+ duration: 100,
+ cpuStart: 3000,
+ cpuDuration: 100,
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'v8',
+ title: 'V8.Execute',
+ type: tr.e.v8.V8ThreadSlice,
+ start: 3000,
+ duration: 100,
+ cpuStart: 3000,
+ cpuDuration: 100,
+ args: {
+ 'runtime-call-stats': {
+ CompileIgnition: [3, 5000],
+ OptimizeCode: [6, 40000],
+ JS_Execution: [1, 11000],
+ }
+ }
+ }));
+
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'toplevel',
+ title: tr.e.chrome.SCHEDULER_TOP_LEVEL_TASK_TITLE,
+ start: 4000,
+ duration: 100,
+ cpuStart: 4000,
+ cpuDuration: 100,
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'v8',
+ title: 'V8.Execute',
+ type: tr.e.v8.V8ThreadSlice,
+ start: 4000,
+ duration: 100,
+ cpuStart: 4000,
+ cpuDuration: 100,
+ args: {
+ 'runtime-call-stats': {
+ CompileIgnition: [20, 20000],
+ ParseLazy: [5, 10000],
+ CompileBackgroundIgnition: [3, 30000]
+ }
+ }
+ }));
+
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'toplevel',
+ title: tr.e.chrome.SCHEDULER_TOP_LEVEL_TASK_TITLE,
+ start: 9000,
+ duration: 10,
+ cpuStart: 9000,
+ cpuDuration: 10,
+ }));
+ mainThread.sliceGroup.createSubSlices();
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.expectedQueueingTimeMetric(histograms, model);
+
+ const eqt = histograms.getHistogramNamed(
+ 'total:500ms_window:renderer_eqt');
+ assert.strictEqual(
+ 'total:500ms_window:renderer_eqt:v8:compile:optimize_rcs',
+ eqt.diagnostics.get('v8').get('v8:compile:optimize_rcs'));
+ assert.strictEqual(1.6, eqt.getBinForValue(10).diagnosticMaps[0].get(
+ 'v8').get('v8:compile:optimize_rcs'));
+
+ assert.strictEqual('total:500ms_window:renderer_eqt:v8:compile:parse_rcs',
+ eqt.diagnostics.get('v8').get('v8:compile:parse_rcs'));
+ assert.strictEqual(0.1, eqt.getBinForValue(10).diagnosticMaps[0].get(
+ 'v8').get('v8:compile:parse_rcs'));
+
+ assert.strictEqual(
+ 'total:500ms_window:renderer_eqt:v8:compile:compile-unoptimize_rcs',
+ eqt.diagnostics.get('v8').get('v8:compile:compile-unoptimize_rcs'));
+ assert.strictEqual(0.4, eqt.getBinForValue(10).diagnosticMaps[0].get(
+ 'v8').get('v8:compile:compile-unoptimize_rcs'));
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/system_health/limited_cpu_time_metric.html b/chromium/third_party/catapult/tracing/tracing/metrics/system_health/limited_cpu_time_metric.html
new file mode 100644
index 00000000000..40d5722c1a4
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/system_health/limited_cpu_time_metric.html
@@ -0,0 +1,62 @@
+<!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/metrics/metric_registry.html">
+<link rel="import" href="/tracing/metrics/system_health/new_cpu_time_metric.html">
+<link rel="import" href="/tracing/value/histogram_set.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.metrics.sh', function() {
+ const includeHistogramNames = [
+ 'cpuTime:all_processes:all_threads:all_stages:all_initiators',
+ 'cpuPercentage:all_processes:all_threads:all_stages:all_initiators',
+ 'cpuTime:browser_process:all_threads:all_stages:all_initiators',
+ 'cpuPercentage:browser_process:all_threads:all_stages:all_initiators',
+ 'cpuTime:renderer_processes:all_threads:all_stages:all_initiators',
+ 'cpuPercentage:renderer_processes:all_threads:all_stages:all_initiators',
+ 'cpuTime:gpu_process:all_threads:all_stages:all_initiators',
+ 'cpuPercentage:gpu_process:all_threads:all_stages:all_initiators',
+ 'cpuTime:renderer_processes:CrRendererMain:all_stages:all_initiators',
+ 'cpuPercentage:renderer_processes:CrRendererMain:all_stages:all_initiators',
+ 'cpuTime:browser_process:CrBrowserMain:all_stages:all_initiators',
+ 'cpuPercentage:browser_process:CrBrowserMain:all_stages:all_initiators',
+ 'cpuTime:all_processes:all_threads:Load:Successful',
+ 'cpuPercentage:all_processes:all_threads:Load:Successful',
+ ];
+
+ /**
+ * This metric is a limited version of new CPU Time metric. The new cpu time
+ * metric produces 300-500 histograms for a trace, which is overwhelming for
+ * some systems. This file exposes a subset of those histograms.
+ *
+ * TODO(#4044): Remove this metric once histogram pipeline is ready.
+ *
+ * @param {!tr.v.HistogramSet} histograms
+ * @param {!tr.model.Model} model
+ * @param {!Object=} opt_options
+ */
+ function limitedCpuTimeMetric(histograms, model, opt_options) {
+ const allCpuHistograms = new tr.v.HistogramSet();
+ tr.metrics.sh.newCpuTimeMetric(allCpuHistograms, model, opt_options);
+
+ for (const histogramName of includeHistogramNames) {
+ const histogram = allCpuHistograms.getHistogramNamed(histogramName);
+ if (histogram) histograms.addHistogram(histogram);
+ }
+ }
+
+ tr.metrics.MetricRegistry.register(limitedCpuTimeMetric, {
+ supportsRangeOfInterest: true
+ });
+
+ return {
+ limitedCpuTimeMetric,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/system_health/loading_metric.html b/chromium/third_party/catapult/tracing/tracing/metrics/system_health/loading_metric.html
new file mode 100644
index 00000000000..b439f2e7d15
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/system_health/loading_metric.html
@@ -0,0 +1,623 @@
+<!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/category_util.html">
+<link rel="import" href="/tracing/base/math/statistics.html">
+<link rel="import" href="/tracing/extras/chrome/event_finder_utils.html">
+<link rel="import" href="/tracing/extras/chrome/time_to_interactive.html">
+<link rel="import" href="/tracing/metrics/metric_registry.html">
+<link rel="import" href="/tracing/metrics/system_health/breakdown_tree_helpers.html">
+<link rel="import" href="/tracing/metrics/system_health/utils.html">
+<link rel="import" href="/tracing/model/helpers/chrome_model_helper.html">
+<link rel="import" href="/tracing/model/helpers/chrome_thread_helper.html">
+<link rel="import" href="/tracing/model/timed_event.html">
+<link rel="import" href="/tracing/value/diagnostics/diagnostic_map.html">
+<link rel="import" href="/tracing/value/histogram.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.metrics.sh', function() {
+ const LONG_TASK_THRESHOLD_MS = 50;
+ const timeDurationInMs_smallerIsBetter =
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter;
+ const RelatedEventSet = tr.v.d.RelatedEventSet;
+ const hasCategoryAndName = tr.metrics.sh.hasCategoryAndName;
+ const EventFinderUtils = tr.e.chrome.EventFinderUtils;
+
+ /**
+ * @param {!tr.model.Process} process
+ * @param {!tr.b.math.Range} range
+ * @return {Array.<tr.model.Event>} An array of network events of a process
+ * and that are intersecting a range.
+ */
+ function getNetworkEventsInRange(process, range) {
+ const networkEvents = [];
+ for (const thread of Object.values(process.threads)) {
+ const threadHelper = new tr.model.helpers.ChromeThreadHelper(thread);
+ const events = threadHelper.getNetworkEvents();
+ for (const event of events) {
+ if (range.intersectsExplicitRangeInclusive(event.start, event.end)) {
+ networkEvents.push(event);
+ }
+ }
+ }
+ return networkEvents;
+ }
+
+ /**
+ * @param {!Object.<string, Object>} breakdownTree
+ * @return {tr.v.d.Breakdown} A breakdown with categories and the total time
+ * (ms) spent under each category.
+ */
+ function createBreakdownDiagnostic(breakdownTree) {
+ const breakdownDiagnostic = new tr.v.d.Breakdown();
+ breakdownDiagnostic.colorScheme =
+ tr.v.d.COLOR_SCHEME_CHROME_USER_FRIENDLY_CATEGORY_DRIVER;
+
+ for (const label in breakdownTree) {
+ breakdownDiagnostic.set(label, breakdownTree[label].total);
+ }
+ return breakdownDiagnostic;
+ }
+
+ const LOADING_METRIC_BOUNDARIES = tr.v.HistogramBinBoundaries
+ .createLinear(0, 1e3, 20) // 50ms step to 1s
+ .addLinearBins(3e3, 20) // 100ms step to 3s
+ .addExponentialBins(20e3, 20);
+
+ const TIME_TO_INTERACTIVE_BOUNDARIES = tr.v.HistogramBinBoundaries
+ // 90-th percentiile of TTI is around 40 seconds, across warm and cold
+ // loads. Data obtained through Cluster Telemetry analysis.
+ .createExponential(1, 40e3, 35)
+ .addExponentialBins(80e3, 15);
+
+ const SUMMARY_OPTIONS = {
+ avg: true,
+ count: false,
+ max: true,
+ min: true,
+ std: true,
+ sum: false,
+ };
+
+ function findFrameLoaderSnapshotAt(rendererHelper, frameIdRef, ts) {
+ const objects = rendererHelper.process.objects;
+ const frameLoaderInstances = objects.instancesByTypeName_.FrameLoader;
+ if (frameLoaderInstances === undefined) return undefined;
+
+ let snapshot;
+ for (const instance of frameLoaderInstances) {
+ if (!instance.isAliveAt(ts)) continue;
+ const maybeSnapshot = instance.getSnapshotAt(ts);
+ if (frameIdRef !== maybeSnapshot.args.frame.id_ref) continue;
+ snapshot = maybeSnapshot;
+ }
+
+ return snapshot;
+ }
+
+ function findAllEvents(rendererHelper, category, title) {
+ const targetEvents = [];
+
+ for (const ev of rendererHelper.process.getDescendantEvents()) {
+ if (!hasCategoryAndName(ev, category, title)) continue;
+ targetEvents.push(ev);
+ }
+
+ return targetEvents;
+ }
+
+ function getMostRecentValidEvent(rendererHelper, category, title) {
+ const targetEvents = findAllEvents(rendererHelper, category, title);
+ // Want to keep the event with the most recent timestamp
+ let validEvent;
+ for (const targetEvent of targetEvents) {
+ if (rendererHelper.isTelemetryInternalEvent(targetEvent)) continue;
+ if (validEvent === undefined) {
+ validEvent = targetEvent;
+ } else {
+ // Want to keep the event with the most recent timestamp
+ if (validEvent.start < targetEvent.start) {
+ validEvent = targetEvent;
+ }
+ }
+ }
+ return validEvent;
+ }
+
+ function getAboveTheFoldLoadedToVisibleSamples(rendererHelper,
+ navIdToNavStartEvents) {
+ const samples = [];
+ // Want to measure the time from when navigation starts to when the load
+ // event fired for all non-ad resources. This done with the associated
+ // navigation start event to the pc mark in the amp code, correlated by
+ // navigation id.
+ const pcEvent = getMostRecentValidEvent(
+ rendererHelper, 'blink.user_timing', 'pc');
+ if (pcEvent === undefined) return samples;
+
+ if (rendererHelper.isTelemetryInternalEvent(pcEvent)) return samples;
+ const navigationStartEvent = navIdToNavStartEvents.get(
+ pcEvent.args.data.navigationId);
+ if (navigationStartEvent === undefined) return samples;
+ const navStartToEventRange = tr.b.math.Range.fromExplicitRange(
+ navigationStartEvent.start, pcEvent.start);
+ const networkEvents = getNetworkEventsInRange(
+ rendererHelper.process, navStartToEventRange);
+ const breakdownTree = tr.metrics.sh.generateWallClockTimeBreakdownTree(
+ rendererHelper.mainThread, networkEvents, navStartToEventRange);
+ samples.push({
+ value: navStartToEventRange.duration,
+ breakdownTree,
+ diagnostics: {
+ breakdown: createBreakdownDiagnostic(breakdownTree),
+ Start: new RelatedEventSet(navigationStartEvent),
+ End: new RelatedEventSet(pcEvent)
+ }
+ });
+
+ return samples;
+ }
+
+ function getFirstViewportReadySamples(rendererHelper) {
+ const samples = [];
+ // Want to measure the time from when the document is visible to the time
+ // when the load event fired for all non-ad resources. This is done with
+ // two marks in the amp code: pc and visible.
+ const pcEvent = getMostRecentValidEvent(
+ rendererHelper, 'blink.user_timing', 'pc');
+ const visibleEvent = getMostRecentValidEvent(
+ rendererHelper, 'blink.user_timing', 'visible');
+ if (pcEvent !== undefined && visibleEvent !== undefined) {
+ samples.push({
+ value: pcEvent.start - visibleEvent.start,
+ diagnostics: {
+ Start: new RelatedEventSet(visibleEvent),
+ End: new RelatedEventSet(pcEvent)
+ }
+ });
+ }
+ return samples;
+ }
+
+ function findTimeToXEntries(
+ category, eventName, rendererHelper, frameToNavStartEvents,
+ navIdToNavStartEvents) {
+ const targetEvents = findAllEvents(rendererHelper, category, eventName);
+ const entries = [];
+ for (const targetEvent of targetEvents) {
+ if (rendererHelper.isTelemetryInternalEvent(targetEvent)) continue;
+ const frameIdRef = targetEvent.args.frame;
+ const snapshot = findFrameLoaderSnapshotAt(
+ rendererHelper, frameIdRef, targetEvent.start);
+ if (snapshot === undefined || !snapshot.args.isLoadingMainFrame) continue;
+ const url = snapshot.args.documentLoaderURL;
+ if (tr.e.chrome.CHROME_INTERNAL_URLS.includes(url)) continue;
+ let navigationStartEvent;
+ if (targetEvent.args.data === undefined ||
+ targetEvent.args.data.navigationId === undefined) {
+ navigationStartEvent =
+ EventFinderUtils.findLastEventStartingOnOrBeforeTimestamp(
+ frameToNavStartEvents.get(frameIdRef) || [], targetEvent.start);
+ } else {
+ navigationStartEvent = navIdToNavStartEvents.get(
+ targetEvent.args.data.navigationId);
+ }
+
+ // Ignore layout w/o preceding navigationStart, as they are not
+ // attributed to any time-to-X metric.
+ if (navigationStartEvent === undefined) continue;
+ entries.push({
+ navigationStartEvent,
+ targetEvent,
+ url,
+ });
+ }
+ return entries;
+ }
+
+ function collectTimeToEvent(rendererHelper, timeToXEntries) {
+ const samples = [];
+ for (const { targetEvent, navigationStartEvent, url } of timeToXEntries) {
+ const navStartToEventRange = tr.b.math.Range.fromExplicitRange(
+ navigationStartEvent.start, targetEvent.start);
+ const networkEvents = getNetworkEventsInRange(
+ rendererHelper.process, navStartToEventRange);
+ const breakdownTree = tr.metrics.sh.generateWallClockTimeBreakdownTree(
+ rendererHelper.mainThread, networkEvents, navStartToEventRange);
+ samples.push({
+ value: navStartToEventRange.duration,
+ breakdownTree,
+ diagnostics: {
+ breakdown: createBreakdownDiagnostic(breakdownTree),
+ url: new tr.v.d.GenericSet([url]),
+ Start: new RelatedEventSet(navigationStartEvent),
+ End: new RelatedEventSet(targetEvent)
+ }
+ });
+ }
+ return samples;
+ }
+
+ function collectTimeToEventInCpuTime(rendererHelper, timeToXEntries) {
+ const samples = [];
+ for (const { targetEvent, navigationStartEvent, url } of timeToXEntries) {
+ const navStartToEventRange = tr.b.math.Range.fromExplicitRange(
+ navigationStartEvent.start, targetEvent.start);
+
+ const mainThreadCpuTime =
+ rendererHelper.mainThread.getCpuTimeForRange(navStartToEventRange);
+
+ const breakdownTree = tr.metrics.sh.generateCpuTimeBreakdownTree(
+ rendererHelper.mainThread, navStartToEventRange);
+ samples.push({
+ value: mainThreadCpuTime,
+ breakdownTree,
+ diagnostics: {
+ breakdown: createBreakdownDiagnostic(breakdownTree),
+ start: new RelatedEventSet(navigationStartEvent),
+ end: new RelatedEventSet(targetEvent),
+ infos: new tr.v.d.GenericSet([{
+ pid: rendererHelper.pid,
+ start: navigationStartEvent.start,
+ event: targetEvent.start,
+ }]),
+ }
+ });
+ }
+ return samples;
+ }
+
+ function addFirstMeaningfulPaintSample(samples, rendererHelper,
+ navigationStart, fmpMarkerEvent, url) {
+ const navStartToFMPRange = tr.b.math.Range.fromExplicitRange(
+ navigationStart.start, fmpMarkerEvent.start);
+ const networkEvents = getNetworkEventsInRange(
+ rendererHelper.process, navStartToFMPRange);
+ const timeToFirstMeaningfulPaint = navStartToFMPRange.duration;
+ const breakdownTree = tr.metrics.sh.generateWallClockTimeBreakdownTree(
+ rendererHelper.mainThread, networkEvents, navStartToFMPRange);
+ samples.push({
+ value: timeToFirstMeaningfulPaint,
+ breakdownTree,
+ diagnostics: {
+ breakdown: createBreakdownDiagnostic(breakdownTree),
+ start: new RelatedEventSet(navigationStart),
+ end: new RelatedEventSet(fmpMarkerEvent),
+ infos: new tr.v.d.GenericSet([{
+ url,
+ pid: rendererHelper.pid,
+ start: navigationStart.start,
+ fmp: fmpMarkerEvent.start,
+ }]),
+ }
+ });
+ }
+
+ function addFirstMeaningfulPaintCpuTimeSample(samples, rendererHelper,
+ navigationStart, fmpMarkerEvent, url) {
+ const navStartToFMPRange = tr.b.math.Range.fromExplicitRange(
+ navigationStart.start, fmpMarkerEvent.start);
+
+ const mainThreadCpuTime =
+ rendererHelper.mainThread.getCpuTimeForRange(navStartToFMPRange);
+
+ const breakdownTree = tr.metrics.sh.generateCpuTimeBreakdownTree(
+ rendererHelper.mainThread, navStartToFMPRange);
+ samples.push({
+ value: mainThreadCpuTime,
+ breakdownTree,
+ diagnostics: {
+ breakdown: createBreakdownDiagnostic(breakdownTree),
+ start: new RelatedEventSet(navigationStart),
+ end: new RelatedEventSet(fmpMarkerEvent),
+ infos: new tr.v.d.GenericSet([{
+ url,
+ pid: rendererHelper.pid,
+ start: navigationStart.start,
+ fmp: fmpMarkerEvent.start,
+ }]),
+ }
+ });
+ }
+
+ /**
+ * Object containing one value and associated diagnostics info for that value
+ * for a metric.
+ * @typedef {{value: number, diagnostics: !tr.v.d.DiagnosticMap}} MetricSample
+ */
+
+ /**
+ * Returns a MetricSample for interactivity metrics - First CPU Idle and Time
+ * to Interactive.
+ *
+ * @param {tr.model.helpers.ChromeRendererHelper} rendererHelper
+ * @param {?number} eventTimestamp - Timestamp of the event for which the
+ * sample is being generated.
+ * @param {tr.model.ThreadSlice} navigationStartEvent
+ * @param {number} firstMeaningfulPaintTime
+ * @param {number} domContentLoadedEndTime
+ * @param {string} url - URL of the current main frame document.
+ * @returns {MetricSample|undefined}
+ */
+ function decorateInteractivitySampleWithDiagnostics_(rendererHelper,
+ eventTimestamp, navigationStartEvent, firstMeaningfulPaintTime,
+ domContentLoadedEndTime, url) {
+ if (eventTimestamp === undefined) return undefined;
+ const navigationStartTime = navigationStartEvent.start;
+ const navStartToEventTimeRange =
+ tr.b.math.Range.fromExplicitRange(
+ navigationStartTime, eventTimestamp);
+ const networkEvents = getNetworkEventsInRange(
+ rendererHelper.process, navStartToEventTimeRange);
+ const breakdownTree = tr.metrics.sh.generateWallClockTimeBreakdownTree(
+ rendererHelper.mainThread, networkEvents,
+ navStartToEventTimeRange);
+ const breakdownDiagnostic = createBreakdownDiagnostic(breakdownTree);
+ return {
+ value: navStartToEventTimeRange.duration,
+ diagnostics: tr.v.d.DiagnosticMap.fromObject({
+ 'Start': new RelatedEventSet(navigationStartEvent),
+ 'Navigation infos': new tr.v.d.GenericSet([{
+ url,
+ pid: rendererHelper.pid,
+ navigationStartTime,
+ firstMeaningfulPaintTime,
+ domContentLoadedEndTime,
+ // eventTimestamp can be derived from value and navigationStartEvent,
+ // but it's useful to directly see the value in the UI.
+ eventTimestamp,
+ }]),
+ 'Breakdown of [navStart, eventTimestamp]': breakdownDiagnostic,
+ }),
+ };
+ }
+
+ function collectLoadingMetricsForRenderer(rendererHelper) {
+ const frameToNavStartEvents =
+ EventFinderUtils.getSortedMainThreadEventsByFrame(
+ rendererHelper, 'navigationStart', 'blink.user_timing');
+ const navIdToNavStartEvents =
+ EventFinderUtils.getSortedMainThreadEventsByNavId(
+ rendererHelper, 'navigationStart', 'blink.user_timing');
+ const firstPaintSamples = collectTimeToEvent(rendererHelper,
+ findTimeToXEntries('loading', 'firstPaint', rendererHelper,
+ frameToNavStartEvents, navIdToNavStartEvents));
+ const timeToFCPEntries = findTimeToXEntries('loading',
+ 'firstContentfulPaint', rendererHelper, frameToNavStartEvents,
+ navIdToNavStartEvents);
+ const firstContentfulPaintSamples = collectTimeToEvent(rendererHelper,
+ timeToFCPEntries);
+ const firstContentfulPaintCpuTimeSamples = collectTimeToEventInCpuTime(
+ rendererHelper, timeToFCPEntries);
+ const onLoadSamples = collectTimeToEvent(rendererHelper, findTimeToXEntries(
+ 'blink.user_timing', 'loadEventStart', rendererHelper,
+ frameToNavStartEvents, navIdToNavStartEvents));
+ const aboveTheFoldLoadedToVisibleSamples =
+ getAboveTheFoldLoadedToVisibleSamples(
+ rendererHelper, navIdToNavStartEvents);
+ const firstViewportReadySamples = getFirstViewportReadySamples(
+ rendererHelper);
+
+ return {
+ frameToNavStartEvents,
+ firstPaintSamples,
+ firstContentfulPaintSamples,
+ firstContentfulPaintCpuTimeSamples,
+ onLoadSamples,
+ aboveTheFoldLoadedToVisibleSamples,
+ firstViewportReadySamples,
+ };
+ }
+
+ function collectMetricsFromLoadExpectations(model, chromeHelper) {
+ // Add FMP, firstCpuIdle and interactive samples from load UE
+ const interactiveSamples = [];
+ const firstCpuIdleSamples = [];
+ const firstMeaningfulPaintSamples = [];
+ const firstMeaningfulPaintCpuTimeSamples = [];
+ for (const expectation of model.userModel.expectations) {
+ if (!(expectation instanceof tr.model.um.LoadExpectation)) continue;
+ if (tr.e.chrome.CHROME_INTERNAL_URLS.includes(expectation.url)) {
+ continue;
+ }
+ const rendererHelper = chromeHelper.rendererHelpers[
+ expectation.renderProcess.pid];
+ if (expectation.fmpEvent !== undefined) {
+ addFirstMeaningfulPaintSample(firstMeaningfulPaintSamples,
+ rendererHelper, expectation.navigationStart, expectation.fmpEvent,
+ expectation.url);
+ addFirstMeaningfulPaintCpuTimeSample(firstMeaningfulPaintCpuTimeSamples,
+ rendererHelper, expectation.navigationStart, expectation.fmpEvent,
+ expectation.url);
+ }
+ if (expectation.firstCpuIdleTime !== undefined) {
+ firstCpuIdleSamples.push(decorateInteractivitySampleWithDiagnostics_(
+ rendererHelper, expectation.firstCpuIdleTime,
+ expectation.navigationStart,
+ expectation.fmpEvent.start,
+ expectation.domContentLoadedEndEvent.start, expectation.url));
+ }
+ if (expectation.timeToInteractive !== undefined) {
+ interactiveSamples.push(decorateInteractivitySampleWithDiagnostics_(
+ rendererHelper, expectation.timeToInteractive,
+ expectation.navigationStart,
+ expectation.fmpEvent.start,
+ expectation.domContentLoadedEndEvent.start, expectation.url));
+ }
+ }
+
+ return {
+ firstMeaningfulPaintSamples,
+ firstMeaningfulPaintCpuTimeSamples,
+ firstCpuIdleSamples,
+ interactiveSamples,
+ };
+ }
+
+ function addSamplesToHistogram(samples, histogram, histograms) {
+ for (const sample of samples) {
+ histogram.addSample(sample.value, sample.diagnostics);
+
+ // Only add breakdown histograms for FCP.
+ // http://crbug.com/771610
+ if (histogram.name !== 'timeToFirstContentfulPaint') continue;
+
+ if (!sample.breakdownTree) continue;
+ for (const [category, breakdown] of Object.entries(
+ sample.breakdownTree)) {
+ const relatedName = `${histogram.name}:${category}`;
+ let relatedHist = histograms.getHistogramsNamed(relatedName)[0];
+ if (!relatedHist) {
+ relatedHist = histograms.createHistogram(
+ relatedName, histogram.unit, [], {
+ binBoundaries: LOADING_METRIC_BOUNDARIES,
+ summaryOptions: {
+ count: false,
+ max: false,
+ min: false,
+ sum: false,
+ },
+ });
+
+ let relatedNames = histogram.diagnostics.get('breakdown');
+ if (!relatedNames) {
+ relatedNames = new tr.v.d.RelatedNameMap();
+ histogram.diagnostics.set('breakdown', relatedNames);
+ }
+ relatedNames.set(category, relatedName);
+ }
+ relatedHist.addSample(breakdown.total, {
+ breakdown: tr.v.d.Breakdown.fromEntries(
+ Object.entries(breakdown.events)),
+ });
+ }
+ }
+ }
+
+ function loadingMetric(histograms, model) {
+ const firstPaintHistogram = histograms.createHistogram(
+ 'timeToFirstPaint', timeDurationInMs_smallerIsBetter, [], {
+ binBoundaries: LOADING_METRIC_BOUNDARIES,
+ description: 'time to first paint',
+ summaryOptions: SUMMARY_OPTIONS,
+ });
+ const firstContentfulPaintHistogram = histograms.createHistogram(
+ 'timeToFirstContentfulPaint', timeDurationInMs_smallerIsBetter, [], {
+ binBoundaries: LOADING_METRIC_BOUNDARIES,
+ description: 'time to first contentful paint',
+ summaryOptions: SUMMARY_OPTIONS,
+ });
+ const firstContentfulPaintCpuTimeHistogram = histograms.createHistogram(
+ 'cpuTimeToFirstContentfulPaint', timeDurationInMs_smallerIsBetter, [], {
+ binBoundaries: LOADING_METRIC_BOUNDARIES,
+ description: 'CPU time to first contentful paint',
+ summaryOptions: SUMMARY_OPTIONS,
+ });
+ const onLoadHistogram = histograms.createHistogram(
+ 'timeToOnload', timeDurationInMs_smallerIsBetter, [], {
+ binBoundaries: LOADING_METRIC_BOUNDARIES,
+ description: 'time to onload. ' +
+ 'This is temporary metric used for PCv1/v2 sanity checking',
+ summaryOptions: SUMMARY_OPTIONS,
+ });
+ const firstMeaningfulPaintHistogram = histograms.createHistogram(
+ 'timeToFirstMeaningfulPaint', timeDurationInMs_smallerIsBetter, [], {
+ binBoundaries: LOADING_METRIC_BOUNDARIES,
+ description: 'time to first meaningful paint',
+ summaryOptions: SUMMARY_OPTIONS,
+ });
+ const firstMeaningfulPaintCpuTimeHistogram = histograms.createHistogram(
+ 'cpuTimeToFirstMeaningfulPaint', timeDurationInMs_smallerIsBetter, [], {
+ binBoundaries: LOADING_METRIC_BOUNDARIES,
+ description: 'CPU time to first meaningful paint',
+ summaryOptions: SUMMARY_OPTIONS,
+ });
+ const timeToInteractiveHistogram = histograms.createHistogram(
+ 'timeToInteractive', timeDurationInMs_smallerIsBetter, [], {
+ binBoundaries: TIME_TO_INTERACTIVE_BOUNDARIES,
+ description: 'Time to Interactive',
+ summaryOptions: SUMMARY_OPTIONS,
+ });
+ const timeToFirstCpuIdleHistogram = histograms.createHistogram(
+ 'timeToFirstCpuIdle', timeDurationInMs_smallerIsBetter, [], {
+ binBoundaries: TIME_TO_INTERACTIVE_BOUNDARIES,
+ description: 'Time to First CPU Idle',
+ summaryOptions: SUMMARY_OPTIONS,
+ });
+ const aboveTheFoldLoadedToVisibleHistogram = histograms.createHistogram(
+ 'aboveTheFoldLoadedToVisible', timeDurationInMs_smallerIsBetter, [], {
+ binBoundaries: TIME_TO_INTERACTIVE_BOUNDARIES,
+ description: 'Time from first visible to load for AMP pages only.',
+ summaryOptions: SUMMARY_OPTIONS,
+ });
+ const firstViewportReadyHistogram = histograms.createHistogram(
+ 'timeToFirstViewportReady', timeDurationInMs_smallerIsBetter, [], {
+ binBoundaries: TIME_TO_INTERACTIVE_BOUNDARIES,
+ description: 'Time from navigation to load for AMP pages only. ',
+ summaryOptions: SUMMARY_OPTIONS,
+ });
+
+ const chromeHelper = model.getOrCreateHelper(
+ tr.model.helpers.ChromeModelHelper);
+ for (const pid in chromeHelper.rendererHelpers) {
+ const rendererHelper = chromeHelper.rendererHelpers[pid];
+ if (rendererHelper.isChromeTracingUI) continue;
+
+ const samplesSet =
+ collectLoadingMetricsForRenderer(rendererHelper);
+
+ addSamplesToHistogram(
+ samplesSet.firstPaintSamples, firstPaintHistogram, histograms);
+ addSamplesToHistogram(
+ samplesSet.firstContentfulPaintSamples,
+ firstContentfulPaintHistogram,
+ histograms);
+ addSamplesToHistogram(
+ samplesSet.firstContentfulPaintCpuTimeSamples,
+ firstContentfulPaintCpuTimeHistogram,
+ histograms);
+ addSamplesToHistogram(
+ samplesSet.onLoadSamples, onLoadHistogram, histograms);
+ addSamplesToHistogram(
+ samplesSet.aboveTheFoldLoadedToVisibleSamples,
+ aboveTheFoldLoadedToVisibleHistogram,
+ histograms);
+ addSamplesToHistogram(
+ samplesSet.firstViewportReadySamples,
+ firstViewportReadyHistogram,
+ histograms);
+ }
+
+ const samplesSet = collectMetricsFromLoadExpectations(model, chromeHelper);
+ addSamplesToHistogram(
+ samplesSet.firstMeaningfulPaintSamples,
+ firstMeaningfulPaintHistogram,
+ histograms);
+ addSamplesToHistogram(
+ samplesSet.firstMeaningfulPaintCpuTimeSamples,
+ firstMeaningfulPaintCpuTimeHistogram,
+ histograms);
+ addSamplesToHistogram(
+ samplesSet.interactiveSamples,
+ timeToInteractiveHistogram,
+ histograms);
+ addSamplesToHistogram(
+ samplesSet.firstCpuIdleSamples,
+ timeToFirstCpuIdleHistogram,
+ histograms);
+ }
+
+ tr.metrics.MetricRegistry.register(loadingMetric);
+
+ return {
+ loadingMetric,
+ getNetworkEventsInRange,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/system_health/loading_metric_test.html b/chromium/third_party/catapult/tracing/tracing/metrics/system_health/loading_metric_test.html
new file mode 100644
index 00000000000..16b2dad797f
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/system_health/loading_metric_test.html
@@ -0,0 +1,1142 @@
+<!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/extras/chrome/chrome_test_utils.html">
+<link rel="import"
+ href="/tracing/extras/chrome/chrome_user_friendly_category_driver.html">
+<link rel="import" href="/tracing/metrics/system_health/loading_metric.html">
+<link rel="import" href="/tracing/value/histogram_set.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ test('timeToFirstPaint', function() {
+ const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
+ const rendererProcess = model.rendererProcess;
+ const mainThread = model.rendererMain;
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: 200,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ rendererProcess.objects.addSnapshot('ptr', 'loading', 'FrameLoader', 300,
+ {
+ isLoadingMainFrame: true,
+ frame: {id_ref: '0xdeadbeef'},
+ documentLoaderURL: 'http://example.com'
+ });
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'rail,loading,devtools.timeline',
+ title: 'firstPaint',
+ start: 1001,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.loadingMetric(histograms, model);
+ const hist = histograms.getHistogramNamed('timeToFirstPaint');
+ assert.strictEqual(1, hist.running.count);
+ assert.strictEqual(801, hist.running.mean);
+ });
+
+
+ test('timeToFirstContentfulPaint', function() {
+ const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
+ const rendererProcess = model.rendererProcess;
+ const mainThread = model.rendererMain;
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: 200,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ rendererProcess.objects.addSnapshot('ptr', 'loading', 'FrameLoader', 300,
+ {
+ isLoadingMainFrame: true,
+ frame: {id_ref: '0xdeadbeef'},
+ documentLoaderURL: 'http://example.com'
+ });
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'loading,rail,devtools.timeline',
+ title: 'firstContentfulPaint',
+ start: 1000,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'loading',
+ title: 'ResourceDispatcher::OnRequestComplete',
+ start: 200,
+ duration: 100,
+ cpuStart: 210,
+ cpuDuration: 25,
+ }));
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.loadingMetric(histograms, model);
+ const hist = histograms.getHistogramNamed('timeToFirstContentfulPaint');
+ assert.strictEqual(1, hist.running.count);
+ assert.strictEqual(800, hist.running.mean);
+ const fcpResourceLoading = histograms.getHistogramNamed(
+ 'timeToFirstContentfulPaint:resource_loading');
+ assert.strictEqual(
+ hist.diagnostics.get('breakdown').get(
+ 'resource_loading'),
+ 'timeToFirstContentfulPaint:resource_loading');
+ assert.strictEqual(fcpResourceLoading.sum, 100);
+ assert.strictEqual(tr.b.getOnlyElement(hist.getBinForValue(
+ 800).diagnosticMaps).get('breakdown').get('resource_loading'), 100);
+ });
+
+
+ test('timeToFirstViewportReady', function() {
+ const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
+ const rendererProcess = model.rendererProcess;
+ const mainThread = model.rendererMain;
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'pc',
+ start: 1200,
+ duration: 0.0,
+ args: {data: {navigationId: '0xsecondnav'}}
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'pc',
+ start: 1300,
+ duration: 0.0,
+ args: {data: {navigationId: '0xsecondnav'}}
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'visible',
+ start: 300,
+ duration: 0.0,
+ }));
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.loadingMetric(histograms, model);
+ const hist = histograms.getHistogramNamed('timeToFirstViewportReady');
+ assert.strictEqual(1, hist.running.count);
+ assert.strictEqual(1000, hist.running.mean);
+ });
+
+
+ test('aboveTheFoldLoadedToVisibleHistogram', function() {
+ const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
+ const rendererProcess = model.rendererProcess;
+ const mainThread = model.rendererMain;
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'pc',
+ start: 1300,
+ duration: 0.0,
+ args: {data: {navigationId: '0xsecondnav'}}
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: 300,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef', data: {navigationId: '0xfirstnav'}}
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: 400,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef', data: {navigationId: '0xsecondnav'}}
+ }));
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.loadingMetric(histograms, model);
+ const hist = histograms.getHistogramNamed(
+ 'aboveTheFoldLoadedToVisible');
+ assert.strictEqual(1, hist.running.count);
+ assert.strictEqual(900, hist.running.mean);
+ });
+
+
+ test('timeToFirstContentfulPaintIgnoringWarmCache', function() {
+ const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
+ const rendererProcess = model.rendererProcess;
+ const mainThread = model.rendererMain;
+
+ // warm cache navigation
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: 200,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ mainThread.asyncSliceGroup.push(tr.c.TestUtils.newAsyncSliceEx({
+ cat: 'blink.console',
+ title: 'telemetry.internal.warm_cache.warm.start',
+ start: 250,
+ duration: 0.0
+ }));
+ rendererProcess.objects.addSnapshot('ptr', 'loading', 'FrameLoader', 300,
+ {
+ isLoadingMainFrame: true,
+ frame: {id_ref: '0xdeadbeef'},
+ documentLoaderURL: 'http://example.com'
+ });
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'loading,rail,devtools.timeline',
+ title: 'firstContentfulPaint',
+ start: 1000,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ mainThread.asyncSliceGroup.push(tr.c.TestUtils.newAsyncSliceEx({
+ cat: 'blink.console',
+ title: 'telemetry.internal.warm_cache.warm.end',
+ start: 1250,
+ duration: 0.0
+ }));
+
+ // measurement navigation
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: 2000,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ rendererProcess.objects.addSnapshot('ptr', 'loading', 'FrameLoader', 2100,
+ {
+ isLoadingMainFrame: true,
+ frame: {id_ref: '0xdeadbeef'},
+ documentLoaderURL: 'http://example.com'
+ });
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'loading,rail,devtools.timeline',
+ title: 'firstContentfulPaint',
+ start: 2400,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.loadingMetric(histograms, model);
+ const hist = histograms.getHistogramNamed('timeToFirstContentfulPaint');
+ assert.strictEqual(1, hist.running.count);
+ assert.strictEqual(400, hist.running.mean);
+ });
+
+ test('timeToFirstContentfulPaintInCpuTime', function() {
+ const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
+ const rendererProcess = model.rendererProcess;
+ const mainThread = model.rendererMain;
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: 200,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ rendererProcess.objects.addSnapshot('ptr', 'loading', 'FrameLoader', 300,
+ {
+ isLoadingMainFrame: true,
+ frame: {id_ref: '0xdeadbeef'},
+ documentLoaderURL: 'http://example.com'
+ });
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'loading,rail,devtools.timeline',
+ title: 'firstContentfulPaint',
+ start: 1000,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'loading',
+ title: 'ResourceDispatcher::OnRequestComplete',
+ start: 200,
+ duration: 100,
+ cpuStart: 210,
+ cpuDuration: 25,
+ }));
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.loadingMetric(histograms, model);
+ const hist = histograms.getHistogramNamed('cpuTimeToFirstContentfulPaint');
+ assert.strictEqual(1, hist.running.count);
+ assert.strictEqual(25, hist.running.mean);
+ assert.strictEqual(tr.b.getOnlyElement(hist.getBinForValue(
+ 25).diagnosticMaps).get('breakdown').get('resource_loading'), 25);
+ });
+
+ test('timeToFirstContentfulPaintWithNavId', function() {
+ const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
+ const rendererProcess = model.rendererProcess;
+ const mainThread = model.rendererMain;
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: 200,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef', data: {navigationId: '0xfirstnav'}}
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: 400,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef', data: {navigationId: '0xsecondnav'}}
+ }));
+ rendererProcess.objects.addSnapshot('ptr', 'loading', 'FrameLoader', 300,
+ {
+ isLoadingMainFrame: true,
+ frame: {id_ref: '0xdeadbeef'},
+ documentLoaderURL: 'http://example.com'
+ });
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'loading,rail,devtools.timeline',
+ title: 'firstContentfulPaint',
+ start: 1000,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef', data: {navigationId: '0xfirstnav'}}
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'loading',
+ title: 'ResourceDispatcher::OnRequestComplete',
+ start: 200,
+ duration: 100,
+ cpuStart: 210,
+ cpuDuration: 25,
+ }));
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.loadingMetric(histograms, model);
+ const hist = histograms.getHistogramNamed('timeToFirstContentfulPaint');
+ assert.strictEqual(1, hist.running.count);
+ assert.strictEqual(800, hist.running.mean);
+ const fcpResourceLoading = histograms.getHistogramNamed(
+ 'timeToFirstContentfulPaint:resource_loading');
+ assert.strictEqual(
+ hist.diagnostics.get('breakdown').get(
+ 'resource_loading'),
+ 'timeToFirstContentfulPaint:resource_loading');
+ assert.strictEqual(fcpResourceLoading.sum, 100);
+ assert.strictEqual(tr.b.getOnlyElement(hist.getBinForValue(
+ 800).diagnosticMaps).get('breakdown').get('resource_loading'), 100);
+ });
+
+ test('timeToFirstMeaningfulPaint', function() {
+ const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
+ const rendererProcess = model.rendererProcess;
+ const mainThread = model.rendererMain;
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: 200,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ rendererProcess.objects.addSnapshot('ptr', 'loading', 'FrameLoader', 300,
+ {
+ isLoadingMainFrame: true,
+ frame: {id_ref: '0xdeadbeef'},
+ documentLoaderURL: 'http://example.com'
+ });
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'loading',
+ title: 'firstMeaningfulPaintCandidate',
+ start: 600,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'loading',
+ title: 'firstMeaningfulPaintCandidate',
+ start: 1000,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.loadingMetric(histograms, model);
+ const hist = histograms.getHistogramNamed('timeToFirstMeaningfulPaint');
+ assert.strictEqual(1, hist.running.count);
+ assert.strictEqual(800, hist.running.mean);
+ });
+
+ // [-------CPU: 300-----------]
+ // | [---CPU: 100---] |
+ // | | | |
+ // v v v v
+ // Ts: 100 200 Start 300 500 600
+ // | |
+ // Start FMP
+ test('cpuTimeToFirstMeaningfulPaint_withEmbeddedSlices', function() {
+ const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
+ const rendererProcess = model.rendererProcess;
+ const mainThread = model.rendererMain;
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: 200,
+ duration: 0.0,
+ cpuStart: 1160,
+ cpuDuration: 0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ rendererProcess.objects.addSnapshot('ptr', 'loading', 'FrameLoader', 300,
+ {
+ isLoadingMainFrame: true,
+ frame: {id_ref: '0xdeadbeef'},
+ documentLoaderURL: 'http://example.com'
+ });
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'loading',
+ title: 'firstMeaningfulPaintCandidate',
+ start: 600,
+ duration: 0.0,
+ cpuStart: 1480,
+ cpuDuration: 0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'toplevel',
+ title: tr.e.chrome.SCHEDULER_TOP_LEVEL_TASK_TITLE,
+ start: 100,
+ duration: 400,
+ cpuStart: 1000,
+ cpuDuration: 300,
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'loading',
+ title: 'ResourceDispatcher::OnRequestComplete',
+ start: 200,
+ duration: 100,
+ cpuStart: 1150,
+ cpuDuration: 100,
+ }));
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.loadingMetric(histograms, model);
+ const hist = histograms.getHistogramNamed('cpuTimeToFirstMeaningfulPaint');
+ assert.deepEqual(hist.sampleValues, [225]);
+ const histBin = hist.getBinForValue(225);
+ assert.closeTo(histBin.diagnosticMaps[0]
+ .get('breakdown').get('other'), 400 / 3, 0.1);
+ assert.strictEqual(histBin.diagnosticMaps[0]
+ .get('breakdown').get('resource_loading'), 100);
+ });
+
+ // [-------------] [------------------]
+ // | | | |
+ // v v v v
+ // Ts: 100 Start 300 500 FMP 700
+ // | |
+ // 200 600
+ test('cpuTimeToFirstMeaningfulPaint_withIntersectingBoundary', function() {
+ const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
+ const rendererProcess = model.rendererProcess;
+ const mainThread = model.rendererMain;
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: 200,
+ duration: 0.0,
+ cpuStart: 1160,
+ cpuDuration: 0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ rendererProcess.objects.addSnapshot('ptr', 'loading', 'FrameLoader', 300,
+ {
+ isLoadingMainFrame: true,
+ frame: {id_ref: '0xdeadbeef'},
+ documentLoaderURL: 'http://example.com'
+ });
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'loading',
+ title: 'firstMeaningfulPaintCandidate',
+ start: 600,
+ duration: 0.0,
+ cpuStart: 1280,
+ cpuDuration: 0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'toplevel',
+ title: tr.e.chrome.SCHEDULER_TOP_LEVEL_TASK_TITLE,
+ start: 100,
+ duration: 200,
+ cpuStart: 1060,
+ cpuDuration: 180,
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'toplevel',
+ title: tr.e.chrome.SCHEDULER_TOP_LEVEL_TASK_TITLE,
+ start: 500,
+ duration: 200,
+ cpuStart: 1250,
+ cpuDuration: 100,
+ }));
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.loadingMetric(histograms, model);
+ const hist = histograms.getHistogramNamed('cpuTimeToFirstMeaningfulPaint');
+ assert.strictEqual(1, hist.running.count);
+ assert.strictEqual(140, hist.running.mean);
+ });
+
+ // Render 1:
+ //
+ // [--------]
+ // | |
+ // v v
+ // CPU Time: Start 1180 1230 FMP
+ // | |
+ // 1160 1280
+ //
+ // Render 2:
+ //
+ // [-------------]
+ // | |
+ // v v
+ // CPU Time: Start 1170 1270 FMP
+ // | |
+ // 1160 1280
+ test('cpuTimeToFirstMeaningfulPaint_multipleRenderers', function() {
+ const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
+ const rendererProcess1 = model.rendererProcess;
+ let mainThread = model.rendererMain;
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: 200,
+ duration: 0.0,
+ cpuStart: 1160,
+ cpuDuration: 0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ rendererProcess1.objects.addSnapshot('ptr', 'loading', 'FrameLoader', 300,
+ {
+ isLoadingMainFrame: true,
+ frame: {id_ref: '0xdeadbeef'},
+ documentLoaderURL: 'http://example.com'
+ });
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'loading',
+ title: 'firstMeaningfulPaintCandidate',
+ start: 600,
+ duration: 0.0,
+ cpuStart: 1280,
+ cpuDuration: 0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'loader',
+ title: 'ResourceDispatcher::OnRequestComplete',
+ start: 300,
+ duration: 200,
+ cpuStart: 1180,
+ cpuDuration: 50
+ }));
+
+ const rendererProcess2 = model.getOrCreateProcess(10);
+ mainThread = rendererProcess2.getOrCreateThread(20);
+ mainThread.name = 'CrRendererMain';
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: 200,
+ duration: 0.0,
+ cpuStart: 1160,
+ cpuDuration: 0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ rendererProcess2.objects.addSnapshot('ptr', 'loading', 'FrameLoader', 300,
+ {
+ isLoadingMainFrame: true,
+ frame: {id_ref: '0xdeadbeef'},
+ documentLoaderURL: 'http://example.com'
+ });
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'loading',
+ title: 'firstMeaningfulPaintCandidate',
+ start: 600,
+ duration: 0.0,
+ cpuStart: 1280,
+ cpuDuration: 0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'toplevel',
+ title: tr.e.chrome.SCHEDULER_TOP_LEVEL_TASK_TITLE,
+ start: 300,
+ duration: 200,
+ cpuStart: 1170,
+ cpuDuration: 100,
+ }));
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.loadingMetric(histograms, model);
+ const hist = histograms.getHistogramNamed('cpuTimeToFirstMeaningfulPaint');
+ assert.deepEqual(hist.sampleValues, [50, 100]);
+ const histBin1 = hist.getBinForValue(50);
+ assert.strictEqual(histBin1.diagnosticMaps[0]
+ .get('breakdown').get('resource_loading'), 50);
+ const histBin2 = hist.getBinForValue(100);
+ assert.strictEqual(histBin2.diagnosticMaps[0]
+ .get('breakdown').get('other'), 100);
+ });
+
+ function addFrameLoaderObject_(rendererProcess, timestamp) {
+ rendererProcess.objects.addSnapshot(
+ 'ptr', 'loading', 'FrameLoader', timestamp, {
+ isLoadingMainFrame: true, frame: {id_ref: '0xdeadbeef'},
+ documentLoaderURL: 'http://example.com',
+ });
+ }
+
+ function addNavigationStart_(rendererMain, timestamp, opt_data) {
+ rendererMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: timestamp,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef', data: opt_data}
+ }));
+ }
+
+ // Some utility functions to make tests easier to read.
+ function addFirstMeaningfulPaint_(rendererMain, timestamp) {
+ rendererMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'loading',
+ title: 'firstMeaningfulPaintCandidate',
+ start: timestamp,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ }
+
+ function addDomContentLoadedEnd_(rendererMain, timestamp) {
+ rendererMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'domContentLoadedEventEnd',
+ start: timestamp,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ }
+
+ function addTopLevelTask_(rendererMain, start, duration) {
+ rendererMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'toplevel',
+ title: tr.e.chrome.SCHEDULER_TOP_LEVEL_TASK_TITLE,
+ start,
+ duration,
+ }));
+ }
+
+ function addNetworkRequest_(rendererMain, start, duration) {
+ const networkEvents = [];
+ rendererMain.asyncSliceGroup.push(tr.c.TestUtils.newSliceEx({
+ cat: 'disabled-by-default-network',
+ title: 'ResourceLoad',
+ start,
+ duration,
+ }));
+ }
+
+ test('loadExpectationMetrics_urlDirectlyFromNavStartEvent', function() {
+ // If the navigation start event contains the URL, we should not require a
+ // FrameLoader snapshot before the event. See https://crbug.com/824761.
+ const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
+ const rendererProcess = model.rendererProcess;
+ const mainThread = model.rendererMain;
+
+ addNavigationStart_(mainThread, 200,
+ {isLoadingMainFrame: true, documentLoaderURL: 'http://example.com'});
+ addNetworkRequest_(mainThread, 200, 250);
+ // FrameLoader creation time after navigation start.
+ rendererProcess.objects.idWasCreated(
+ 'ptr', 'loading', 'FrameLoader', 250);
+ rendererProcess.objects.addSnapshot(
+ 'ptr', 'loading', 'FrameLoader', 300, {
+ isLoadingMainFrame: true, frame: {id_ref: '0xdeadbeef'},
+ documentLoaderURL: 'http://example.com',
+ });
+
+ addFirstMeaningfulPaint_(mainThread, 500);
+ addDomContentLoadedEnd_(mainThread, 600);
+
+ // New navigation to close the search window.
+ addNavigationStart_(mainThread, 7000,
+ {isLoadingMainFrame: true, documentLoaderURL: 'http://example.com'});
+ });
+
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.loadingMetric(histograms, model);
+ const fmpHist = histograms.getHistogramNamed('timeToFirstMeaningfulPaint');
+ assert.strictEqual(1, fmpHist.running.count);
+ assert.strictEqual(300, fmpHist.running.mean);
+ // Both TTI and First CPU Idle firing at DCL.
+ const ttiHist = histograms.getHistogramNamed('timeToInteractive');
+ assert.strictEqual(1, ttiHist.running.count);
+ assert.strictEqual(400, ttiHist.running.mean);
+ const firstCpuIdleHist = histograms.getHistogramNamed('timeToFirstCpuIdle');
+ assert.strictEqual(1, firstCpuIdleHist.running.count);
+ assert.strictEqual(400, firstCpuIdleHist.running.mean);
+ });
+
+ test('interactivityMetrics_notAffectedByShortTasks', function() {
+ const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
+ const rendererProcess = model.rendererProcess;
+ const mainThread = model.rendererMain;
+
+ addNavigationStart_(mainThread, 200);
+ addNetworkRequest_(mainThread, 200, 250);
+ addFrameLoaderObject_(rendererProcess, 300);
+ addFirstMeaningfulPaint_(mainThread, 500);
+ addDomContentLoadedEnd_(mainThread, 600);
+
+ const mainThreadTopLevelTasks = [
+ {start: 800, duration: 100}, // Long task
+ {start: 1500, duration: 200}, // Last long task. TTI at 1700.
+ {start: 2000, duration: 49}, // Short task.
+ ];
+ for (const task of mainThreadTopLevelTasks) {
+ addTopLevelTask_(mainThread, task.start, task.duration);
+ }
+
+ // New navigation to close the search window.
+ addNavigationStart_(mainThread, 7000);
+ });
+
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.loadingMetric(histograms, model);
+ const ttiHist = histograms.getHistogramNamed('timeToInteractive');
+ assert.strictEqual(1, ttiHist.running.count);
+ // 1700 - 200 (navStart) = 1500.
+ assert.strictEqual(1500, ttiHist.running.mean);
+ const firstCpuIdleHist = histograms.getHistogramNamed('timeToFirstCpuIdle');
+ assert.strictEqual(1, firstCpuIdleHist.running.count);
+ // 1700 - 200 (navStart) = 1500.
+ assert.strictEqual(1500, firstCpuIdleHist.running.mean);
+ });
+
+ test('interactivityMetrics_longTaskBeforeFMP', function() {
+ const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
+ const rendererProcess = model.rendererProcess;
+ const mainThread = model.rendererMain;
+
+ addNavigationStart_(mainThread, 200);
+ addNetworkRequest_(mainThread, 200, 50);
+ addFrameLoaderObject_(rendererProcess, 300);
+ addDomContentLoadedEnd_(mainThread, 600);
+ addTopLevelTask_(mainThread, 600, 200);
+ addFirstMeaningfulPaint_(mainThread, 1000); // TTI at FMP.
+ // New navigation to close the search window.
+ addNavigationStart_(mainThread, 7000);
+ });
+
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.loadingMetric(histograms, model);
+ const ttiHist = histograms.getHistogramNamed('timeToInteractive');
+ assert.strictEqual(1, ttiHist.running.count);
+ // 1000 - 200 (navStart) = 800.
+ assert.strictEqual(800, ttiHist.running.mean);
+ const firstCpuIdleHist = histograms.getHistogramNamed('timeToFirstCpuIdle');
+ assert.strictEqual(1, firstCpuIdleHist.running.count);
+ // 1000 - 200 (navStart) = 800.
+ assert.strictEqual(800, firstCpuIdleHist.running.mean);
+ });
+
+ test('interactivityMetrics_interactiveAtDCL', function() {
+ const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
+ const rendererProcess = model.rendererProcess;
+ const mainThread = model.rendererMain;
+
+ addNavigationStart_(mainThread, 200);
+ addNetworkRequest_(mainThread, 200, 50);
+ addFrameLoaderObject_(rendererProcess, 300);
+ addFirstMeaningfulPaint_(mainThread, 1000);
+ addDomContentLoadedEnd_(mainThread, 3000);
+ // New navigation to close the search window.
+ addNavigationStart_(mainThread, 7000);
+ });
+
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.loadingMetric(histograms, model);
+ const ttiHist = histograms.getHistogramNamed('timeToInteractive');
+ assert.strictEqual(1, ttiHist.running.count);
+ // 3000 - 200 (navStart) = 2800.
+ assert.strictEqual(2800, ttiHist.running.mean);
+ const firstCpuIdleHist = histograms.getHistogramNamed('timeToFirstCpuIdle');
+ assert.strictEqual(1, firstCpuIdleHist.running.count);
+ // 3000 - 200 (navStart) = 2800.
+ assert.strictEqual(2800, firstCpuIdleHist.running.mean);
+ });
+
+ test('interactivityMetrics_networkBusyBlocksInteractivity', function() {
+ const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
+ const rendererProcess = model.rendererProcess;
+ const mainThread = model.rendererMain;
+
+ addNavigationStart_(mainThread, 200);
+ addFrameLoaderObject_(rendererProcess, 300);
+ addFirstMeaningfulPaint_(mainThread, 1000);
+ addDomContentLoadedEnd_(mainThread, 1100);
+ // Network busy requires at least three network requests.
+ addNetworkRequest_(mainThread, 1000, 7000);
+ addNetworkRequest_(mainThread, 1001, 7001);
+ addNetworkRequest_(mainThread, 1002, 7002);
+ // 400ms task makes a "heavy task cluster" for idle.
+ addTopLevelTask_(mainThread, 1200, 400);
+ // Next long task is more than five seconds away, but TTI is not reached
+ // yet since network is busy. TTI is at the at of this task.
+ addTopLevelTask_(mainThread, 6800, 200);
+ // New navigation to close the search window.
+ addNavigationStart_(mainThread, 13000);
+ });
+
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.loadingMetric(histograms, model);
+ const ttiHist = histograms.getHistogramNamed('timeToInteractive');
+ assert.strictEqual(1, ttiHist.running.count);
+ // 7000 - 200 (navStart) = 6800.
+ assert.strictEqual(6800, ttiHist.running.mean);
+ const firstCpuIdleHist = histograms.getHistogramNamed('timeToFirstCpuIdle');
+ assert.strictEqual(1, firstCpuIdleHist.running.count);
+ // 1600 - 200 (navStart) = 1400. CPU Idle is not affected by network.
+ assert.strictEqual(1400, firstCpuIdleHist.running.mean);
+ });
+
+ test('interactivityMetrics_notReportedIfTracingEndsEarly', function() {
+ const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
+ const rendererProcess = model.rendererProcess;
+ const mainThread = model.rendererMain;
+
+ addNavigationStart_(mainThread, 200);
+ addNetworkRequest_(mainThread, 200, 50);
+ addFrameLoaderObject_(rendererProcess, 300);
+ addDomContentLoadedEnd_(mainThread, 600);
+ addFirstMeaningfulPaint_(mainThread, 1000);
+ addTopLevelTask_(mainThread, 2000, 400);
+ // Last task in the model. 2501 will be considered end of tracing.
+ addTopLevelTask_(mainThread, 2500, 1);
+ });
+
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.loadingMetric(histograms, model);
+ const ttiHist = histograms.getHistogramNamed('timeToInteractive');
+ assert.strictEqual(0, ttiHist.numValues);
+ const firstCpuIdleHist = histograms.getHistogramNamed('timeToFirstCpuIdle');
+ assert.strictEqual(0, firstCpuIdleHist.numValues);
+ });
+
+ test('interactivityMetrics_notReportedIfNextNavigationIsEarly', function() {
+ const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
+ const rendererProcess = model.rendererProcess;
+ const mainThread = model.rendererMain;
+
+ addNavigationStart_(mainThread, 200);
+ addNetworkRequest_(mainThread, 200, 50);
+ addFrameLoaderObject_(rendererProcess, 300);
+ addDomContentLoadedEnd_(mainThread, 600);
+ addFirstMeaningfulPaint_(mainThread, 1000);
+ addTopLevelTask_(mainThread, 2000, 400);
+ // New navigation to close the search window. The window is not big enough
+ // to reach TTI.
+ addNavigationStart_(mainThread, 3000);
+ });
+
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.loadingMetric(histograms, model);
+ const ttiHist = histograms.getHistogramNamed('timeToInteractive');
+ assert.strictEqual(0, ttiHist.numValues);
+ const firstCpuIdleHist = histograms.getHistogramNamed('timeToFirstCpuIdle');
+ assert.strictEqual(0, firstCpuIdleHist.numValues);
+ });
+
+ test('interactivityMetrics_reportsValueForLastNavigation', function() {
+ const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
+ const rendererProcess = model.rendererProcess;
+ const mainThread = model.rendererMain;
+
+ addNavigationStart_(mainThread, 200);
+ addNetworkRequest_(mainThread, 200, 50);
+ addFrameLoaderObject_(rendererProcess, 300);
+ addDomContentLoadedEnd_(mainThread, 600);
+ addFirstMeaningfulPaint_(mainThread, 1000);
+ addTopLevelTask_(mainThread, 2000, 400);
+ // Last task in the model. 8001 will be considered end of tracing, so
+ // there is sufficiently large window to detect TTI.
+ addTopLevelTask_(mainThread, 8000, 1);
+ });
+
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.loadingMetric(histograms, model);
+ const ttiHist = histograms.getHistogramNamed('timeToInteractive');
+ assert.strictEqual(1, ttiHist.running.count);
+ // 2400 - 200 (navStart) = 2200.
+ assert.strictEqual(2200, ttiHist.running.mean);
+ const firstCpuIdleHist = histograms.getHistogramNamed('timeToFirstCpuIdle');
+ assert.strictEqual(1, firstCpuIdleHist.running.count);
+ // 2400 - 200 (navStart) = 2200.
+ assert.strictEqual(2200, firstCpuIdleHist.running.mean);
+ });
+
+ test('interactivityMetrics_multipleRenderers', function() {
+ const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
+ const renderers =
+ [model.getOrCreateProcess(1984), model.getOrCreateProcess(1985)];
+
+ for (const i of [0, 1]) {
+ const rendererProcess = renderers[i];
+ const mainThread = rendererProcess.getOrCreateThread(2);
+ mainThread.name = 'CrRendererMain';
+ addNavigationStart_(mainThread, 200);
+ addNetworkRequest_(mainThread, 200, 50);
+ addFrameLoaderObject_(rendererProcess, 300);
+ addDomContentLoadedEnd_(mainThread, 600);
+ addFirstMeaningfulPaint_(mainThread, 1000);
+ // Durations are 400 and 800 for i value of 0 an 1.
+ addTopLevelTask_(mainThread, 2000, (i + 1) * 400);
+ // New navigation to close the search window.
+ addNavigationStart_(mainThread, 10000);
+ }
+ });
+
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.loadingMetric(histograms, model);
+ const ttiHist = histograms.getHistogramNamed('timeToInteractive');
+ assert.strictEqual(2, ttiHist.running.count);
+ // 2800 - 200 (navStart) = 2200, and 2400 - 200 = 2200.
+ assert.strictEqual(2600, ttiHist.running.max);
+ assert.strictEqual(2200, ttiHist.running.min);
+ const firstCpuIdleHist = histograms.getHistogramNamed('timeToFirstCpuIdle');
+ assert.strictEqual(2, firstCpuIdleHist.running.count);
+ // 2800 - 200 (navStart) = 2200, and 2400 - 200 = 2200.
+ assert.strictEqual(2600, firstCpuIdleHist.running.max);
+ assert.strictEqual(2200, firstCpuIdleHist.running.min);
+ });
+
+ test('interactivityMetrics_eventsFromMultipleFrame', function() {
+ const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
+ const rendererProcess = model.rendererProcess;
+ const mainThread = model.rendererMain;
+
+ addNavigationStart_(mainThread, 200);
+ addNetworkRequest_(mainThread, 200, 50);
+ addFrameLoaderObject_(rendererProcess, 300);
+ addFirstMeaningfulPaint_(mainThread, 1000);
+ // No long task. TTI is reached at 3000.
+ addDomContentLoadedEnd_(mainThread, 3000);
+
+ // DomContentLoadedEnd and NavigationStart for a different frame.
+ rendererProcess.objects.addSnapshot(
+ 'ptr', 'loading', 'FrameLoader', 4000, {
+ isLoadingMainFrame: true, frame: {id_ref: '0xffffffff'},
+ documentLoaderURL: 'http://example.com'
+ });
+
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: 4000,
+ duration: 0.0,
+ args: {frame: '0xffffffff'},
+ }));
+
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'domContentLoadedEventEnd',
+ start: 4500,
+ duration: 0.0,
+ args: {frame: '0xffffffff'}
+ }));
+
+ // Last task in the model. 8001 will be considered end of tracing, so
+ // there is sufficiently large window to detect TTI.
+ addTopLevelTask_(mainThread, 8000, 1);
+ });
+
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.loadingMetric(histograms, model);
+ const ttiHist = histograms.getHistogramNamed('timeToInteractive');
+ assert.strictEqual(1, ttiHist.running.count);
+ // 3000 - 200 (navStart) = 2800.
+ assert.strictEqual(2800, ttiHist.running.mean);
+ const firstCpuIdleHist = histograms.getHistogramNamed('timeToFirstCpuIdle');
+ assert.strictEqual(1, firstCpuIdleHist.running.count);
+ // 3000 - 200 (navStart) = 2800.
+ assert.strictEqual(2800, firstCpuIdleHist.running.mean);
+ });
+
+ test('timeToInteractive_notReportedWithoutResourceLoadEvents', function() {
+ const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
+ const rendererProcess = model.rendererProcess;
+ const mainThread = model.rendererMain;
+
+ addNavigationStart_(mainThread, 200);
+ addFrameLoaderObject_(rendererProcess, 300);
+ addDomContentLoadedEnd_(mainThread, 600);
+ addFirstMeaningfulPaint_(mainThread, 1000);
+ addTopLevelTask_(mainThread, 2000, 400);
+ // Last task in the model. 8001 will be considered end of tracing, so
+ // there is sufficiently large window to detect TTI.
+ addTopLevelTask_(mainThread, 8000, 1);
+ });
+
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.loadingMetric(histograms, model);
+ const ttiHist = histograms.getHistogramNamed('timeToInteractive');
+ assert.strictEqual(0, ttiHist.numValues);
+ });
+
+
+ test('webView', function() {
+ const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
+ const process = model.rendererProcess;
+ const rendererThread = model.rendererMain;
+ rendererThread.name = 'Chrome_InProcRendererThread';
+ rendererThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: 200,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ process.objects.addSnapshot('ptr', 'loading', 'FrameLoader', 300,
+ {
+ isLoadingMainFrame: true,
+ frame: {id_ref: '0xdeadbeef'},
+ documentLoaderURL: 'http://example.com'
+ });
+ rendererThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'loading,rail,devtools.timeline',
+ title: 'firstContentfulPaint',
+ start: 600,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ rendererThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'loading',
+ title: 'firstMeaningfulPaintCandidate',
+ start: 1000,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.loadingMetric(histograms, model);
+ const fcp = histograms.getHistogramNamed('timeToFirstContentfulPaint');
+ assert.strictEqual(1, fcp.running.count);
+ assert.strictEqual(400, fcp.running.mean);
+ const fmp = histograms.getHistogramNamed('timeToFirstMeaningfulPaint');
+ assert.strictEqual(1, fmp.running.count);
+ assert.strictEqual(800, fmp.running.mean);
+ });
+
+ test('testGetNetworkEvents', function() {
+ // Our renderer looks like:
+ // [ irrelevant syncEvent ]
+ // [ irrelevant asyncEvent ]
+ // | [ d..netlog]
+ // [ netlog ] [ d..network] [ net ]
+ // | | | | | |
+ // | | | | | |
+ // | | | | | |
+ // v v v v v v
+ // Ts: 100 200 400 450 510 520
+ const rendererPid = 245;
+ const netEvent1 = tr.c.TestUtils.newAsyncSliceEx({
+ cat: 'netlog',
+ title: 'Generic Network event',
+ start: 100,
+ duration: 100,
+ });
+ const netEvent2 = tr.c.TestUtils.newAsyncSliceEx({
+ cat: 'disabled-by-default-network',
+ title: 'ResourceLoad',
+ start: 400,
+ duration: 50,
+ });
+ const netEvent3 = tr.c.TestUtils.newAsyncSliceEx({
+ cat: 'net',
+ title: 'ResourceLoad',
+ start: 510,
+ duration: 10,
+ });
+ const netEvent4 = tr.c.TestUtils.newAsyncSliceEx({
+ cat: 'disabled-by-default-netlog',
+ title: 'ResourceLoad',
+ start: 510,
+ duration: 10,
+ });
+ const irrelevantAsyncEvent = tr.c.TestUtils.newAsyncSliceEx({
+ cat: 'irrelevant',
+ title: 'ResourceLoad',
+ start: 0,
+ duration: 510,
+ });
+ const irrelevantSyncEvent = tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: 0,
+ duration: 510,
+ args: {frame: '0xdeadbeef'}
+ });
+
+ const model = tr.c.TestUtils.newModel(function(model) {
+ const rendererProcess = model.getOrCreateProcess(rendererPid);
+ const thread1 = rendererProcess.getOrCreateThread(1);
+ thread1.name = 'CrRendererMain';
+ thread1.asyncSliceGroup.push(netEvent1);
+ const thread2 = rendererProcess.getOrCreateThread(2);
+ thread2.name = 'thread2';
+ thread2.asyncSliceGroup.push(netEvent2);
+ const thread3 = rendererProcess.getOrCreateThread(3);
+ thread3.name = 'thread2';
+ thread3.asyncSliceGroup.push(netEvent3);
+ const thread4 = rendererProcess.getOrCreateThread(4);
+ thread4.name = 'thread2';
+ thread4.asyncSliceGroup.push(netEvent4);
+ const thread5 = rendererProcess.getOrCreateThread(5);
+ thread5.name = 'thread5';
+ thread5.asyncSliceGroup.push(irrelevantAsyncEvent);
+ const thread6 = rendererProcess.getOrCreateThread(6);
+ thread6.name = 'thread6';
+ thread6.sliceGroup.pushSlice(irrelevantSyncEvent);
+ });
+ const chromeHelper = model.getOrCreateHelper(
+ tr.model.helpers.ChromeModelHelper);
+ const rendererHelper = chromeHelper.rendererHelpers[rendererPid];
+ const allNetworkEvents = tr.metrics.sh.getNetworkEventsInRange(
+ rendererHelper.process, tr.b.math.Range.fromExplicitRange(0, 550));
+ assert.sameDeepMembers(
+ [netEvent1, netEvent2, netEvent3, netEvent4],
+ allNetworkEvents);
+
+ const partialNetworkEvents = tr.metrics.sh.getNetworkEventsInRange(
+ rendererHelper.process, tr.b.math.Range.fromExplicitRange(0, 460));
+ assert.strictEqual(2, partialNetworkEvents.length);
+ assert.sameDeepMembers(
+ [netEvent1, netEvent2],
+ partialNetworkEvents);
+
+ const networkEventsWithIntersecting =
+ tr.metrics.sh.getNetworkEventsInRange(
+ rendererHelper.process, tr.b.math.Range.fromExplicitRange(0, 410));
+ assert.sameDeepMembers(
+ [netEvent1, netEvent2],
+ partialNetworkEvents);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/system_health/long_tasks_metric.html b/chromium/third_party/catapult/tracing/tracing/metrics/system_health/long_tasks_metric.html
new file mode 100644
index 00000000000..e42683d3164
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/system_health/long_tasks_metric.html
@@ -0,0 +1,137 @@
+<!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/extras/chrome/chrome_user_friendly_category_driver.html">
+<link rel="import" href="/tracing/metrics/metric_registry.html">
+<link rel="import" href="/tracing/model/helpers/chrome_model_helper.html">
+<link rel="import" href="/tracing/value/histogram.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.metrics.sh', function() {
+ const LONG_TASK_MS = 50;
+
+ // Anything longer than this should be so rare that its length beyond this is
+ // uninteresting.
+ const LONGEST_TASK_MS = 1000;
+
+ /**
+ * This helper function calls |cb| for each of the top-level tasks on the
+ * given thread in the given range whose duration is longer than LONG_TASK_MS.
+ *
+ * @param {tr.model.Thread} thread
+ * @param {tr.b.math.Range=} opt_range
+ * @param {function()} cb
+ * @param {Object=} opt_this
+ */
+ function iterateLongTopLevelTasksOnThreadInRange(
+ thread, opt_range, cb, opt_this) {
+ thread.sliceGroup.topLevelSlices.forEach(function(slice) {
+ if (opt_range &&
+ !opt_range.intersectsExplicitRangeInclusive(slice.start, slice.end)) {
+ return;
+ }
+
+ if (slice.duration < LONG_TASK_MS) return;
+
+ cb.call(opt_this, slice);
+ });
+ }
+
+ /**
+ * This helper function calls |cb| for each of the main renderer threads in
+ * the model.
+ *
+ * @param {tr.model.Model} model The model.
+ * @param {function()} cb Callback.
+ * @param {Object=} opt_this Context object.
+ */
+ function iterateRendererMainThreads(model, cb, opt_this) {
+ const modelHelper = model.getOrCreateHelper(
+ tr.model.helpers.ChromeModelHelper);
+ if (modelHelper !== undefined) {
+ Object.values(modelHelper.rendererHelpers).forEach(
+ function(rendererHelper) {
+ if (!rendererHelper.mainThread) return;
+
+ cb.call(opt_this, rendererHelper.mainThread);
+ });
+ }
+ }
+
+ const BIN_BOUNDARIES = tr.v.HistogramBinBoundaries.createLinear(
+ LONG_TASK_MS, LONGEST_TASK_MS, 40);
+
+ /**
+ * This metric directly measures long tasks on renderer main threads.
+ * This metric requires only the 'toplevel' tracing category.
+ *
+ * @param {!tr.v.HistogramSet} histograms
+ * @param {!tr.model.Model} model
+ * @param {!Object=} opt_options
+ */
+ function longTasksMetric(histograms, model, opt_options) {
+ const rangeOfInterest = opt_options ? opt_options.rangeOfInterest :
+ undefined;
+ const longTaskHist = histograms.createHistogram(
+ 'longTasks', tr.b.Unit.byName.timeDurationInMs_smallerIsBetter, [], {
+ binBoundaries: BIN_BOUNDARIES,
+ description: 'durations of long tasks',
+ });
+
+ const relatedNames = new tr.v.d.RelatedNameMap();
+ longTaskHist.diagnostics.set('categories', relatedNames);
+
+ iterateRendererMainThreads(model, function(thread) {
+ iterateLongTopLevelTasksOnThreadInRange(
+ thread, rangeOfInterest, function(task) {
+ const breakdown = new tr.v.d.Breakdown();
+ breakdown.colorScheme =
+ tr.v.d.COLOR_SCHEME_CHROME_USER_FRIENDLY_CATEGORY_DRIVER;
+ for (const slice of task.descendentSlices) {
+ const sample = slice.cpuSelfTime;
+ if (sample === undefined) continue;
+
+ const category = model.getUserFriendlyCategoryFromEvent(slice);
+ const histName = 'longTasks:' + category;
+ let hist = histograms.getHistogramNamed(histName);
+ if (hist === undefined) {
+ hist = histograms.createHistogram(histName,
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter, [], {
+ binBoundaries: BIN_BOUNDARIES,
+ });
+ relatedNames.set(category, hist.name);
+ }
+ hist.addSample(sample, {
+ events: new tr.v.d.RelatedEventSet([slice]),
+ });
+ breakdown.set(category, sample + breakdown.get(category));
+ }
+
+ longTaskHist.addSample(task.duration, {
+ events: new tr.v.d.RelatedEventSet([task]),
+ categories: breakdown,
+ });
+ });
+ });
+ }
+
+ tr.metrics.MetricRegistry.register(longTasksMetric, {
+ supportsRangeOfInterest: true,
+ requiredCategories: ['toplevel'],
+ });
+
+ return {
+ longTasksMetric,
+ iterateLongTopLevelTasksOnThreadInRange,
+ iterateRendererMainThreads,
+ LONG_TASK_MS,
+ LONGEST_TASK_MS,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/system_health/long_tasks_metric_test.html b/chromium/third_party/catapult/tracing/tracing/metrics/system_health/long_tasks_metric_test.html
new file mode 100644
index 00000000000..ecfeabd2a5a
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/system_health/long_tasks_metric_test.html
@@ -0,0 +1,111 @@
+<!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/extras/chrome/chrome_user_friendly_category_driver.html">
+<link rel="import" href="/tracing/metrics/system_health/long_tasks_metric.html">
+<link rel="import" href="/tracing/value/histogram_set.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ test('longTasksMetric', function() {
+ const model = tr.c.TestUtils.newModel(function(model) {
+ const proc = model.getOrCreateProcess(1);
+ const thread = proc.getOrCreateThread(2);
+ thread.name = 'CrRendererMain';
+ const longTask = tr.c.TestUtils.newSliceEx({
+ title: 'foo',
+ start: 0,
+ duration: 50,
+ });
+ thread.sliceGroup.pushSlice(longTask);
+
+ thread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ title: 'UpdateLayerTree',
+ start: 0,
+ duration: 1,
+ cpuStart: 0,
+ cpuDuration: 1,
+ }));
+
+ thread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ title: 'minorGC',
+ start: 1,
+ duration: 1,
+ cpuStart: 1,
+ cpuDuration: 1,
+ }));
+
+ thread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ title: 'Decode Image',
+ start: 2,
+ duration: 1,
+ cpuStart: 2,
+ cpuDuration: 1,
+ }));
+
+ thread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ title: 'Layout',
+ start: 3,
+ duration: 1,
+ cpuStart: 3,
+ cpuDuration: 1,
+ }));
+
+ model.addUserFriendlyCategoryDriver(
+ tr.e.chrome.ChromeUserFriendlyCategoryDriver);
+ });
+
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.longTasksMetric(histograms, model);
+
+ const longTaskHist = histograms.getHistogramNamed('longTasks');
+ assert.strictEqual(1, longTaskHist.numValues);
+
+ const relatedNames = longTaskHist.diagnostics.get('categories');
+ assert.instanceOf(relatedNames, tr.v.d.RelatedNameMap);
+ assert.strictEqual(relatedNames.get('layout'), 'longTasks:layout');
+ assert.strictEqual(relatedNames.get('gc'), 'longTasks:gc');
+ assert.strictEqual(relatedNames.get('composite'), 'longTasks:composite');
+ assert.strictEqual(relatedNames.get('imageDecode'),
+ 'longTasks:imageDecode');
+
+ const bin = longTaskHist.getBinForValue(longTaskHist.average);
+ const diagnostics = tr.b.getOnlyElement(bin.diagnosticMaps);
+ const breakdown = diagnostics.get('categories');
+ assert.instanceOf(breakdown, tr.v.d.Breakdown);
+ assert.strictEqual(breakdown.size, 4);
+
+ const taskEventSet = diagnostics.get('events');
+ assert.instanceOf(taskEventSet, tr.v.d.RelatedEventSet);
+ assert.lengthOf(taskEventSet, 1);
+
+ const layoutHist = histograms.getHistogramNamed('longTasks:layout');
+ assert.strictEqual(1, layoutHist.numValues);
+ assert.lengthOf(tr.b.getOnlyElement(layoutHist.getBinForValue(
+ layoutHist.average).diagnosticMaps).get('events'), 1);
+
+ const gcHist = histograms.getHistogramNamed('longTasks:gc');
+ assert.strictEqual(1, gcHist.numValues);
+ assert.lengthOf(tr.b.getOnlyElement(gcHist.getBinForValue(
+ gcHist.average).diagnosticMaps).get('events'), 1);
+
+ const compositeHist = histograms.getHistogramNamed('longTasks:composite');
+ assert.strictEqual(1, compositeHist.numValues);
+ assert.lengthOf(tr.b.getOnlyElement(compositeHist.getBinForValue(
+ compositeHist.average).diagnosticMaps).get('events'), 1);
+
+ const imageDecodeHist = histograms.getHistogramNamed(
+ 'longTasks:imageDecode');
+ assert.strictEqual(1, imageDecodeHist.numValues);
+ assert.lengthOf(tr.b.getOnlyElement(imageDecodeHist.getBinForValue(
+ imageDecodeHist.average).diagnosticMaps).get('events'), 1);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/system_health/memory_metric.html b/chromium/third_party/catapult/tracing/tracing/metrics/system_health/memory_metric.html
new file mode 100644
index 00000000000..68a90814bd0
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/system_health/memory_metric.html
@@ -0,0 +1,1332 @@
+<!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/math/range.html">
+<link rel="import" href="/tracing/base/multi_dimensional_view.html">
+<link rel="import" href="/tracing/base/unit.html">
+<link rel="import" href="/tracing/base/utils.html">
+<link rel="import" href="/tracing/extras/chrome/chrome_processes.html">
+<link rel="import" href="/tracing/metrics/metric_registry.html">
+<link rel="import" href="/tracing/metrics/system_health/utils.html">
+<link rel="import" href="/tracing/model/container_memory_dump.html">
+<link rel="import" href="/tracing/model/helpers/chrome_model_helper.html">
+<link rel="import" href="/tracing/model/memory_allocator_dump.html">
+<link rel="import" href="/tracing/value/diagnostics/breakdown.html">
+<link rel="import" href="/tracing/value/histogram.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.metrics.sh', function() {
+ const BACKGROUND = tr.model.ContainerMemoryDump.LevelOfDetail.BACKGROUND;
+ const LIGHT = tr.model.ContainerMemoryDump.LevelOfDetail.LIGHT;
+ const DETAILED = tr.model.ContainerMemoryDump.LevelOfDetail.DETAILED;
+ const sizeInBytes_smallerIsBetter =
+ tr.b.Unit.byName.sizeInBytes_smallerIsBetter;
+ const count_smallerIsBetter = tr.b.Unit.byName.count_smallerIsBetter;
+ const DISPLAYED_SIZE_NUMERIC_NAME =
+ tr.model.MemoryAllocatorDump.DISPLAYED_SIZE_NUMERIC_NAME;
+
+ const LEVEL_OF_DETAIL_NAMES = new Map();
+ LEVEL_OF_DETAIL_NAMES.set(BACKGROUND, 'background');
+ LEVEL_OF_DETAIL_NAMES.set(LIGHT, 'light');
+ LEVEL_OF_DETAIL_NAMES.set(DETAILED, 'detailed');
+
+ // Some detailed dumps contain heap profiler information.
+ const HEAP_PROFILER_DETAIL_NAME = 'heap_profiler';
+
+ const BOUNDARIES_FOR_UNIT_MAP = new WeakMap();
+ // For unitless numerics (process counts), we use 20 linearly scaled bins
+ // from 0 to 20.
+ BOUNDARIES_FOR_UNIT_MAP.set(count_smallerIsBetter,
+ tr.v.HistogramBinBoundaries.createLinear(0, 20, 20));
+ // For size numerics (subsystem and vm stats), we use 1 bin from 0 B to
+ // 1 KiB and 4*24 exponentially scaled bins from 1 KiB to 16 GiB (=2^24 KiB).
+ BOUNDARIES_FOR_UNIT_MAP.set(sizeInBytes_smallerIsBetter,
+ new tr.v.HistogramBinBoundaries(0)
+ .addBinBoundary(1024 /* 1 KiB */)
+ .addExponentialBins(16 * 1024 * 1024 * 1024 /* 16 GiB */, 4 * 24));
+
+ const CHROME_PROCESS_NAMES =
+ tr.e.chrome.chrome_processes.CHROME_PROCESS_NAMES;
+
+ function memoryMetric(values, model, opt_options) {
+ const rangeOfInterest =
+ opt_options ? opt_options.rangeOfInterest : undefined;
+ const browserNameToGlobalDumps =
+ tr.metrics.sh.splitGlobalDumpsByBrowserName(model, rangeOfInterest);
+ addGeneralMemoryDumpValues(browserNameToGlobalDumps, values);
+ addDetailedMemoryDumpValues(browserNameToGlobalDumps, values);
+ addMemoryDumpCountValues(browserNameToGlobalDumps, values);
+ }
+
+ const USER_FRIENDLY_BROWSER_NAMES = {
+ 'chrome': 'Chrome',
+ 'webview': 'WebView',
+ 'unknown_browser': 'an unknown browser'
+ };
+
+ /**
+ * Convert a canonical browser name used in value names to a user-friendly
+ * name used in value descriptions.
+ *
+ * Examples:
+ *
+ * CANONICAL BROWSER NAME -> USER-FRIENDLY NAME
+ * chrome -> Chrome
+ * unknown_browser -> an unknown browser
+ * webview2 -> WebView(2)
+ * unexpected -> 'unexpected' browser
+ */
+ function convertBrowserNameToUserFriendlyName(browserName) {
+ for (const baseName in USER_FRIENDLY_BROWSER_NAMES) {
+ if (!browserName.startsWith(baseName)) continue;
+
+ const userFriendlyBaseName = USER_FRIENDLY_BROWSER_NAMES[baseName];
+ const suffix = browserName.substring(baseName.length);
+ if (suffix.length === 0) {
+ return userFriendlyBaseName;
+ } else if (/^\d+$/.test(suffix)) {
+ return userFriendlyBaseName + '(' + suffix + ')';
+ }
+ }
+ return '\'' + browserName + '\' browser';
+ }
+
+
+ /**
+ * Convert a canonical process name used in value names to a user-friendly
+ * name used in value descriptions.
+ */
+ function convertProcessNameToUserFriendlyName(processName,
+ opt_requirePlural) {
+ switch (processName) {
+ case CHROME_PROCESS_NAMES.BROWSER:
+ return opt_requirePlural ? 'browser processes' : 'the browser process';
+ case CHROME_PROCESS_NAMES.RENDERER:
+ return 'renderer processes';
+ case CHROME_PROCESS_NAMES.GPU:
+ return opt_requirePlural ? 'GPU processes' : 'the GPU process';
+ case CHROME_PROCESS_NAMES.PPAPI:
+ return opt_requirePlural ? 'PPAPI processes' : 'the PPAPI process';
+ case CHROME_PROCESS_NAMES.ALL:
+ return 'all processes';
+ case CHROME_PROCESS_NAMES.UNKNOWN:
+ return 'unknown processes';
+ default:
+ return '\'' + processName + '\' processes';
+ }
+ }
+
+ /**
+ * Add general memory dump values calculated from all global memory dumps to
+ * |values|. In particular, this function adds the following values:
+ *
+ * * PROCESS COUNTS
+ * memory:{chrome, webview}:
+ * {browser_process, renderer_processes, ..., all_processes}:
+ * process_count
+ * type: tr.v.Histogram (over all matching global memory dumps)
+ * unit: count_smallerIsBetter
+ *
+ * * MEMORY USAGE REPORTED BY CHROME
+ * memory:{chrome, webview}:
+ * {browser_process, renderer_processes, ..., all_processes}:
+ * reported_by_chrome[:{v8, malloc, ...}]:
+ * {effective_size, allocated_objects_size, locked_size}
+ * type: tr.v.Histogram (over all matching global memory dumps)
+ * unit: sizeInBytes_smallerIsBetter
+ */
+ function addGeneralMemoryDumpValues(browserNameToGlobalDumps, values) {
+ addMemoryDumpValues(browserNameToGlobalDumps,
+ gmd => true /* process all global memory dumps */,
+ function(processDump, addProcessScalar) {
+ // Increment memory:<browser-name>:<process-name>:process_count value.
+ addProcessScalar({
+ source: 'process_count',
+ property: PROCESS_COUNT,
+ value: 1
+ });
+
+ if (processDump.totals !== undefined) {
+ addProcessScalar({
+ source: 'reported_by_os',
+ property: RESIDENT_SIZE,
+ component: ['system_memory'],
+ value: processDump.totals.residentBytes
+ });
+ addProcessScalar({
+ source: 'reported_by_os',
+ property: PEAK_RESIDENT_SIZE,
+ component: ['system_memory'],
+ value: processDump.totals.peakResidentBytes
+ });
+ addProcessScalar({
+ source: 'reported_by_os',
+ property: PRIVATE_FOOTPRINT_SIZE,
+ component: ['system_memory'],
+ value: processDump.totals.privateFootprintBytes,
+ });
+ }
+
+ // Add memory:<browser-name>:<process-name>:reported_by_chrome:...
+ // values.
+ if (processDump.memoryAllocatorDumps === undefined) return;
+
+ processDump.memoryAllocatorDumps.forEach(function(rootAllocatorDump) {
+ CHROME_VALUE_PROPERTIES.forEach(function(property) {
+ addProcessScalar({
+ source: 'reported_by_chrome',
+ component: [rootAllocatorDump.name],
+ property,
+ value: rootAllocatorDump.numerics[property.name]
+ });
+ });
+ // Some dump providers add allocated objects size as
+ // "allocated_objects" child dump.
+ if (rootAllocatorDump.numerics.allocated_objects_size ===
+ undefined) {
+ const allocatedObjectsDump =
+ rootAllocatorDump.getDescendantDumpByFullName(
+ 'allocated_objects');
+ if (allocatedObjectsDump !== undefined) {
+ addProcessScalar({
+ source: 'reported_by_chrome',
+ component: [rootAllocatorDump.name],
+ property: ALLOCATED_OBJECTS_SIZE,
+ value: allocatedObjectsDump.numerics.size
+ });
+ }
+ }
+ });
+
+ // Add memory:<browser-name>:<process-name>:reported_by_chrome:
+ // {malloc, blinkgc, partitionalloc}:<largestCategory>:...
+ addTopHeapDumpCategoryValue(processDump, addProcessScalar);
+
+ // Add memory:<browser-name>:<process-name>:reported_by_chrome:v8:
+ // {heap, allocated_by_malloc}:...
+ addV8MemoryDumpValues(processDump, addProcessScalar);
+ },
+ function(componentTree) {
+ // Subtract memory:<browser-name>:<process-name>:reported_by_chrome:
+ // tracing:<size-property> from memory:<browser-name>:<process-name>:
+ // reported_by_chrome:<size-property> if applicable.
+ const tracingNode = componentTree.children[1].get('tracing');
+ if (tracingNode === undefined) return;
+
+ for (let i = 0; i < componentTree.values.length; i++) {
+ componentTree.values[i].total -= tracingNode.values[i].total;
+ }
+ }, values);
+ }
+
+ /**
+ * Add memory dump values for the top category in each allocator heap dump in
+ * the process dump.
+ *
+ * @param {!tr.model.ProcessMemoryDump} processDump The process memory dump.
+ * @param {!function} addProcessScalar The callback for adding a scalar value.
+ */
+ function addTopHeapDumpCategoryValue(processDump, addProcessScalar) {
+ if (!processDump.heapDumps) {
+ return;
+ }
+ for (const allocatorName in processDump.heapDumps) {
+ const heapDump = processDump.heapDumps[allocatorName];
+ if (heapDump.entries === undefined || heapDump.entries.length === 0) {
+ return;
+ }
+ // Create a map of category to total size.
+ const typeToSize = {};
+ for (let i = 0; i < heapDump.entries.length; i += 1) {
+ const entry = heapDump.entries[i];
+ // Count only the entries with empty backtrace which contains totals for
+ // the object type.
+ if (!entry.objectTypeName || entry.leafStackFrame) {
+ continue;
+ }
+ if (!typeToSize[entry.objectTypeName]) {
+ typeToSize[entry.objectTypeName] = 0;
+ }
+ typeToSize[entry.objectTypeName] += entry.size;
+ }
+
+ // Find the largest type in the heap dump.
+ let largestValue = 0;
+ let largestType = '';
+ for (const key in typeToSize) {
+ if (largestValue < typeToSize[key]) {
+ largestValue = typeToSize[key];
+ largestType = key;
+ }
+ }
+ addProcessScalar({
+ source: 'reported_by_chrome',
+ component: [allocatorName, largestType],
+ property: HEAP_CATEGORY_SIZE,
+ value: largestValue
+ });
+ }
+ }
+
+ /**
+ * Add memory dump values calculated from V8 components excluding
+ * 'heap_spaces/other_spaces'.
+ *
+ * @param {!tr.model.ProcessMemoryDump} processDump The process memory dump.
+ * @param {!function} addProcessScalar The callback for adding a scalar value.
+ */
+ function addV8MemoryDumpValues(processDump, addProcessScalar) {
+ const v8Dump = processDump.getMemoryAllocatorDumpByFullName('v8');
+ if (v8Dump === undefined) return;
+
+ v8Dump.children.forEach(function(isolateDump) {
+ // v8:allocated_by_malloc:...
+ const mallocDump = isolateDump.getDescendantDumpByFullName('malloc');
+ if (mallocDump !== undefined) {
+ addV8ComponentValues(mallocDump, ['v8', 'allocated_by_malloc'],
+ addProcessScalar);
+ }
+ // v8:heap:...
+ let heapDump = isolateDump.getDescendantDumpByFullName('heap');
+ if (heapDump === undefined) {
+ // Old V8 memory dumps call this 'heap_spaces'.
+ heapDump = isolateDump.getDescendantDumpByFullName('heap_spaces');
+ }
+ if (heapDump !== undefined) {
+ addV8ComponentValues(heapDump, ['v8', 'heap'], addProcessScalar);
+ heapDump.children.forEach(function(spaceDump) {
+ if (spaceDump.name === 'other_spaces') return;
+
+ addV8ComponentValues(spaceDump, ['v8', 'heap', spaceDump.name],
+ addProcessScalar);
+ });
+ }
+ });
+
+ // V8 generates bytecode when interpreting and code objects when
+ // compiling the javascript. Total code size includes the size
+ // of code and bytecode objects.
+ addProcessScalar({
+ source: 'reported_by_chrome',
+ component: ['v8'],
+ property: CODE_AND_METADATA_SIZE,
+ value: v8Dump.numerics.code_and_metadata_size
+ });
+ addProcessScalar({
+ source: 'reported_by_chrome',
+ component: ['v8'],
+ property: CODE_AND_METADATA_SIZE,
+ value: v8Dump.numerics.bytecode_and_metadata_size
+ });
+ }
+
+ /**
+ * Add memory dump values calculated from the specified V8 component.
+ *
+ * @param {!tr.model.MemoryAllocatorDump} v8Dump The V8 memory dump.
+ * @param {!Array<string>} componentPath The component path for reporting.
+ * @param {!function} addProcessScalar The callback for adding a scalar value.
+ */
+ function addV8ComponentValues(componentDump, componentPath,
+ addProcessScalar) {
+ CHROME_VALUE_PROPERTIES.forEach(function(property) {
+ addProcessScalar({
+ source: 'reported_by_chrome',
+ component: componentPath,
+ property,
+ value: componentDump.numerics[property.name]
+ });
+ });
+ }
+
+ const PROCESS_COUNT = {
+ unit: count_smallerIsBetter,
+ buildDescriptionPrefix(componentPath, processName) {
+ if (componentPath.length > 0) {
+ throw new Error('Unexpected process count non-empty component path: ' +
+ componentPath.join(':'));
+ }
+ return 'total number of ' + convertProcessNameToUserFriendlyName(
+ processName, true /* opt_requirePlural */);
+ }
+ };
+
+ const EFFECTIVE_SIZE = {
+ name: 'effective_size',
+ unit: sizeInBytes_smallerIsBetter,
+ buildDescriptionPrefix(componentPath, processName) {
+ return buildChromeValueDescriptionPrefix(componentPath, processName, {
+ userFriendlyPropertyName: 'effective size',
+ componentPreposition: 'of'
+ });
+ }
+ };
+
+ const ALLOCATED_OBJECTS_SIZE = {
+ name: 'allocated_objects_size',
+ unit: sizeInBytes_smallerIsBetter,
+ buildDescriptionPrefix(componentPath, processName) {
+ return buildChromeValueDescriptionPrefix(componentPath, processName, {
+ userFriendlyPropertyName: 'size of all objects allocated',
+ totalUserFriendlyPropertyName: 'size of all allocated objects',
+ componentPreposition: 'by'
+ });
+ }
+ };
+
+ const SHIM_ALLOCATED_OBJECTS_SIZE = {
+ name: 'shim_allocated_objects_size',
+ unit: sizeInBytes_smallerIsBetter,
+ buildDescriptionPrefix(componentPath, processName) {
+ return buildChromeValueDescriptionPrefix(componentPath, processName, {
+ userFriendlyPropertyName: 'size of all objects allocated through shim',
+ totalUserFriendlyPropertyName:
+ 'size of all allocated objects through shim',
+ componentPreposition: 'by'
+ });
+ }
+ };
+
+ const LOCKED_SIZE = {
+ name: 'locked_size',
+ unit: sizeInBytes_smallerIsBetter,
+ buildDescriptionPrefix(componentPath, processName) {
+ return buildChromeValueDescriptionPrefix(componentPath, processName, {
+ userFriendlyPropertyName: 'locked (pinned) size',
+ componentPreposition: 'of'
+ });
+ }
+ };
+
+ const PEAK_SIZE = {
+ name: 'peak_size',
+ unit: sizeInBytes_smallerIsBetter,
+ buildDescriptionPrefix(componentPath, processName) {
+ return buildChromeValueDescriptionPrefix(componentPath, processName, {
+ userFriendlyPropertyName: 'peak size',
+ componentPreposition: 'of'
+ });
+ }
+ };
+
+ const HEAP_CATEGORY_SIZE = {
+ name: 'heap_category_size',
+ unit: sizeInBytes_smallerIsBetter,
+ buildDescriptionPrefix(componentPath, processName) {
+ return buildChromeValueDescriptionPrefix(componentPath, processName, {
+ userFriendlyPropertyName: 'heap profiler category size',
+ componentPreposition: 'for'
+ });
+ }
+ };
+
+ const CODE_AND_METADATA_SIZE = {
+ name: 'code_and_metadata_size',
+ unit: sizeInBytes_smallerIsBetter,
+ buildDescriptionPrefix(componentPath, processName) {
+ return buildChromeValueDescriptionPrefix(componentPath, processName, {
+ userFriendlyPropertyNamePrefix: 'size of',
+ userFriendlyPropertyName: 'code and metadata'
+ });
+ }
+ };
+
+ const CHROME_VALUE_PROPERTIES = [
+ EFFECTIVE_SIZE,
+ ALLOCATED_OBJECTS_SIZE,
+ SHIM_ALLOCATED_OBJECTS_SIZE,
+ LOCKED_SIZE,
+ PEAK_SIZE
+ ];
+
+ /**
+ * Build a description prefix for a memory:<browser-name>:<process-name>:
+ * reported_by_chrome:... value.
+ *
+ * @param {!Array<string>} componentPath The underlying component path (e.g.
+ * ['malloc']).
+ * @param {string} processName The canonical name of the process.
+ * @param {{
+ * userFriendlyPropertyName: string,
+ * userFriendlyPropertyNamePrefix: (string|undefined),
+ * totalUserFriendlyPropertyName: (string|undefined),
+ * componentPreposition: (string|undefined) }}
+ * formatSpec Specification of how the property should be formatted.
+ * @return {string} Prefix for the value's description (e.g.
+ * 'effective size of malloc in the browser process').
+ */
+ function buildChromeValueDescriptionPrefix(
+ componentPath, processName, formatSpec) {
+ const nameParts = [];
+ if (componentPath.length === 0) {
+ nameParts.push('total');
+ if (formatSpec.totalUserFriendlyPropertyName) {
+ nameParts.push(formatSpec.totalUserFriendlyPropertyName);
+ } else {
+ if (formatSpec.userFriendlyPropertyNamePrefix) {
+ nameParts.push(formatSpec.userFriendlyPropertyNamePrefix);
+ }
+ nameParts.push(formatSpec.userFriendlyPropertyName);
+ }
+ nameParts.push('reported by Chrome for');
+ } else {
+ if (formatSpec.componentPreposition === undefined) {
+ // Use component name as an adjective
+ // (e.g. 'size of V8 code and metadata').
+ if (formatSpec.userFriendlyPropertyNamePrefix) {
+ nameParts.push(formatSpec.userFriendlyPropertyNamePrefix);
+ }
+ nameParts.push(componentPath.join(':'));
+ nameParts.push(formatSpec.userFriendlyPropertyName);
+ } else {
+ // Use component name as a noun with a preposition
+ // (e.g. 'size of all objects allocated BY MALLOC').
+ if (formatSpec.userFriendlyPropertyNamePrefix) {
+ nameParts.push(formatSpec.userFriendlyPropertyNamePrefix);
+ }
+ nameParts.push(formatSpec.userFriendlyPropertyName);
+ nameParts.push(formatSpec.componentPreposition);
+ if (componentPath[componentPath.length - 1] === 'allocated_by_malloc') {
+ nameParts.push('objects allocated by malloc for');
+ nameParts.push(
+ componentPath.slice(0, componentPath.length - 1).join(':'));
+ } else {
+ nameParts.push(componentPath.join(':'));
+ }
+ }
+ nameParts.push('in');
+ }
+ nameParts.push(convertProcessNameToUserFriendlyName(processName));
+ return nameParts.join(' ');
+ }
+
+ const RESIDENT_SIZE = {
+ name: 'resident_size',
+ unit: sizeInBytes_smallerIsBetter,
+ buildDescriptionPrefix(componentPath, processName) {
+ return buildOsValueDescriptionPrefix(componentPath, processName,
+ 'resident set size (RSS)');
+ }
+ };
+
+ const PEAK_RESIDENT_SIZE = {
+ name: 'peak_resident_size',
+ unit: sizeInBytes_smallerIsBetter,
+ buildDescriptionPrefix(componentPath, processName) {
+ return buildOsValueDescriptionPrefix(componentPath, processName,
+ 'peak resident set size');
+ }
+ };
+
+ const PROPORTIONAL_RESIDENT_SIZE = {
+ name: 'proportional_resident_size',
+ unit: sizeInBytes_smallerIsBetter,
+ buildDescriptionPrefix(componentPath, processName) {
+ return buildOsValueDescriptionPrefix(componentPath, processName,
+ 'proportional resident size (PSS)');
+ }
+ };
+
+ const PRIVATE_DIRTY_SIZE = {
+ name: 'private_dirty_size',
+ unit: sizeInBytes_smallerIsBetter,
+ buildDescriptionPrefix(componentPath, processName) {
+ return buildOsValueDescriptionPrefix(componentPath, processName,
+ 'private dirty size');
+ }
+ };
+
+ const PRIVATE_FOOTPRINT_SIZE = {
+ name: 'private_footprint_size',
+ unit: sizeInBytes_smallerIsBetter,
+ buildDescriptionPrefix(componentPath, processName) {
+ return buildOsValueDescriptionPrefix(componentPath, processName,
+ 'private footprint size');
+ }
+ };
+
+ const JAVA_BASE_CLEAN_RESIDENT = {
+ name: 'java_base_clean_resident',
+ unit: sizeInBytes_smallerIsBetter,
+ buildDescriptionPrefix(componentPath, processName) {
+ return buildOsValueDescriptionPrefix(componentPath, processName,
+ 'java base odex and vdex total clean resident size');
+ }
+ };
+
+ const JAVA_BASE_PSS = {
+ name: 'java_base_pss',
+ unit: sizeInBytes_smallerIsBetter,
+ buildDescriptionPrefix(componentPath, processName) {
+ return buildOsValueDescriptionPrefix(componentPath, processName,
+ 'java base odex and vdex proportional resident size');
+ }
+ };
+
+ const NATIVE_LIBRARY_PRIVATE_CLEAN_RESIDENT = {
+ name: 'native_library_private_clean_resident',
+ unit: sizeInBytes_smallerIsBetter,
+ buildDescriptionPrefix(componentPath, processName) {
+ return buildOsValueDescriptionPrefix(componentPath, processName,
+ 'native library private clean resident size');
+ }
+ };
+
+ const NATIVE_LIBRARY_SHARED_CLEAN_RESIDENT = {
+ name: 'native_library_shared_clean_resident',
+ unit: sizeInBytes_smallerIsBetter,
+ buildDescriptionPrefix(componentPath, processName) {
+ return buildOsValueDescriptionPrefix(componentPath, processName,
+ 'native library shared clean resident size');
+ }
+ };
+
+ const NATIVE_LIBRARY_PROPORTIONAL_RESIDENT = {
+ name: 'native_library_proportional_resident',
+ unit: sizeInBytes_smallerIsBetter,
+ buildDescriptionPrefix(componentPath, processName) {
+ return buildOsValueDescriptionPrefix(componentPath, processName,
+ 'native library proportional resident size');
+ }
+ };
+
+ /**
+ * Build a description prefix for a memory:<browser-name>:<process-name>:
+ * reported_by_os:... value.
+ *
+ * @param {!Array<string>} componentPath The underlying component path (e.g.
+ * ['system', 'java_heap']).
+ * @param {string} processName The canonical name of the process.
+ * @param {string} userFriendlyPropertyName User-friendly name of the
+ * underlying property (e.g. 'private dirty size').
+ * @return {string} Prefix for the value's description (e.g.
+ * 'total private dirty size of the Java heal in the GPU process').
+ */
+ function buildOsValueDescriptionPrefix(
+ componentPath, processName, userFriendlyPropertyName) {
+ if (componentPath.length > 2) {
+ throw new Error('OS value component path for \'' +
+ userFriendlyPropertyName + '\' too long: ' + componentPath.join(':'));
+ }
+
+ const nameParts = [];
+ if (componentPath.length < 2) {
+ nameParts.push('total');
+ }
+
+ nameParts.push(userFriendlyPropertyName);
+
+ if (componentPath.length > 0) {
+ switch (componentPath[0]) {
+ case 'system_memory':
+ if (componentPath.length > 1) {
+ const userFriendlyComponentName =
+ SYSTEM_VALUE_COMPONENTS[componentPath[1]].userFriendlyName;
+ if (userFriendlyComponentName === undefined) {
+ throw new Error('System value sub-component for \'' +
+ userFriendlyPropertyName + '\' unknown: ' +
+ componentPath.join(':'));
+ }
+ nameParts.push('of', userFriendlyComponentName, 'in');
+ } else {
+ nameParts.push('of system memory (RAM) used by');
+ }
+ break;
+
+ case 'gpu_memory':
+ if (componentPath.length > 1) {
+ nameParts.push('of the', componentPath[1]);
+ nameParts.push('Android memtrack component in');
+ } else {
+ nameParts.push('of GPU memory (Android memtrack) used by');
+ }
+ break;
+
+ default:
+ throw new Error('OS value component for \'' +
+ userFriendlyPropertyName + '\' unknown: ' +
+ componentPath.join(':'));
+ }
+ } else {
+ nameParts.push('reported by the OS for');
+ }
+
+ nameParts.push(convertProcessNameToUserFriendlyName(processName));
+ return nameParts.join(' ');
+ }
+
+ /**
+ * Add heavy memory dump values calculated from heavy global memory dumps to
+ * |values|. In particular, this function adds the following values:
+ *
+ * * MEMORY USAGE REPORTED BY THE OS
+ * memory:{chrome, webview}:
+ * {browser_process, renderer_processes, ..., all_processes}:
+ * reported_by_os:system_memory:[{ashmem, native_heap, java_heap}:]
+ * {proportional_resident_size, private_dirty_size}
+ * memory:{chrome, webview}:
+ * {browser_process, renderer_processes, ..., all_processes}:
+ * reported_by_os:gpu_memory:[{gl, graphics, ...}:]
+ * proportional_resident_size
+ * type: tr.v.Histogram (over matching heavy global memory dumps)
+ * unit: sizeInBytes_smallerIsBetter
+ *
+ * * MEMORY USAGE REPORTED BY CHROME
+ * memory:{chrome, webview}:
+ * {browser_process, renderer_processes, ..., all_processes}:
+ * reported_by_chrome:v8:code_and_metadata_size
+ * type: tr.v.Histogram (over matching heavy global memory dumps)
+ * unit: sizeInBytes_smallerIsBetter
+ */
+ function addDetailedMemoryDumpValues(browserNameToGlobalDumps, values) {
+ addMemoryDumpValues(browserNameToGlobalDumps,
+ g => g.levelOfDetail === DETAILED,
+ function(processDump, addProcessScalar) {
+ // Add memory:<browser-name>:<process-name>:reported_by_os:
+ // system_memory:... values.
+ for (const [componentName, componentSpec] of
+ Object.entries(SYSTEM_VALUE_COMPONENTS)) {
+ const node = getDescendantVmRegionClassificationNode(
+ processDump.vmRegions, componentSpec.classificationPath);
+ const componentPath = ['system_memory'];
+ if (componentName) componentPath.push(componentName);
+ addProcessScalar({
+ source: 'reported_by_os',
+ component: componentPath,
+ property: PROPORTIONAL_RESIDENT_SIZE,
+ value: node === undefined ?
+ 0 : (node.byteStats.proportionalResident || 0)
+ });
+ addProcessScalar({
+ source: 'reported_by_os',
+ component: componentPath,
+ property: PRIVATE_DIRTY_SIZE,
+ value: node === undefined ?
+ 0 : (node.byteStats.privateDirtyResident || 0)
+ });
+
+ // Only add java base stats when they are nonzero.
+ if (node) {
+ if (node.byteStats.javaBasePss) {
+ addProcessScalar({
+ source: 'reported_by_os',
+ component: componentPath,
+ property: JAVA_BASE_PSS,
+ value: node.byteStats.javaBasePss
+ });
+ }
+ if (node.byteStats.javaBaseCleanResident) {
+ addProcessScalar({
+ source: 'reported_by_os',
+ component: componentPath,
+ property: JAVA_BASE_CLEAN_RESIDENT,
+ value: node.byteStats.javaBaseCleanResident
+ });
+ }
+ }
+
+ // Only add native library stats when they are nonzero.
+ if (node) {
+ if (node.byteStats.nativeLibraryPrivateCleanResident) {
+ addProcessScalar({
+ source: 'reported_by_os',
+ component: componentPath,
+ property: NATIVE_LIBRARY_PRIVATE_CLEAN_RESIDENT,
+ value: node.byteStats.nativeLibraryPrivateCleanResident
+ });
+ }
+ if (node.byteStats.nativeLibrarySharedCleanResident) {
+ addProcessScalar({
+ source: 'reported_by_os',
+ component: componentPath,
+ property: NATIVE_LIBRARY_SHARED_CLEAN_RESIDENT,
+ value: node.byteStats.nativeLibrarySharedCleanResident
+ });
+ }
+ if (node.byteStats.nativeLibraryProportionalResident) {
+ addProcessScalar({
+ source: 'reported_by_os',
+ component: componentPath,
+ property: NATIVE_LIBRARY_PROPORTIONAL_RESIDENT,
+ value: node.byteStats.nativeLibraryProportionalResident
+ });
+ }
+ }
+ }
+
+ // Add memory:<browser-name>:<process-name>:reported_by_os:
+ // gpu_memory:... values.
+ const memtrackDump = processDump.getMemoryAllocatorDumpByFullName(
+ 'gpu/android_memtrack');
+ if (memtrackDump !== undefined) {
+ memtrackDump.children.forEach(function(memtrackChildDump) {
+ addProcessScalar({
+ source: 'reported_by_os',
+ component: ['gpu_memory', memtrackChildDump.name],
+ property: PROPORTIONAL_RESIDENT_SIZE,
+ value: memtrackChildDump.numerics.memtrack_pss
+ });
+ });
+ }
+ }, function(componentTree) {}, values);
+ }
+
+ // Specifications of components reported by the system.
+ const SYSTEM_VALUE_COMPONENTS = {
+ '': {
+ classificationPath: [],
+ },
+ 'java_heap': {
+ classificationPath: ['Android', 'Java runtime', 'Spaces'],
+ userFriendlyName: 'the Java heap'
+ },
+ 'ashmem': {
+ classificationPath: ['Android', 'Ashmem'],
+ userFriendlyName: 'ashmem'
+ },
+ 'native_heap': {
+ classificationPath: ['Native heap'],
+ userFriendlyName: 'the native heap'
+ },
+ 'stack': {
+ classificationPath: ['Stack'],
+ userFriendlyName: 'the thread stacks'
+ }
+ };
+
+ /**
+ * Get the descendant of a VM region classification |node| specified by the
+ * given |path| of child node titles. If |node| is undefined or such a
+ * descendant does not exist, this function returns undefined.
+ */
+ function getDescendantVmRegionClassificationNode(node, path) {
+ for (let i = 0; i < path.length; i++) {
+ if (node === undefined) break;
+
+ node = node.children.find(c => c.title === path[i]);
+ }
+ return node;
+ }
+
+ /**
+ * Add global memory dump counts to |values|. In particular, this function
+ * adds the following values:
+ *
+ * * DUMP COUNTS
+ * memory:{chrome, webview}:all_processes:dump_count
+ * [:{light, detailed, heap_profiler}]
+ * type: tr.v.Histogram
+ * unit: count_smallerIsBetter
+ *
+ * Note that unlike all other values generated by the memory metric, the
+ * global memory dump counts are NOT instances of tr.v.Histogram
+ * because it doesn't make sense to aggregate them (they are already counts
+ * over all global dumps associated with the relevant browser).
+ */
+ function addMemoryDumpCountValues(browserNameToGlobalDumps, values) {
+ browserNameToGlobalDumps.forEach(function(globalDumps, browserName) {
+ let totalDumpCount = 0;
+ const levelOfDetailNameToDumpCount = {};
+ LEVEL_OF_DETAIL_NAMES.forEach(function(levelOfDetailName) {
+ levelOfDetailNameToDumpCount[levelOfDetailName] = 0;
+ });
+ levelOfDetailNameToDumpCount[HEAP_PROFILER_DETAIL_NAME] = 0;
+
+ globalDumps.forEach(function(globalDump) {
+ totalDumpCount++;
+
+ // Increment the level-of-detail-specific dump count (if possible).
+ const levelOfDetailName =
+ LEVEL_OF_DETAIL_NAMES.get(globalDump.levelOfDetail);
+ if (levelOfDetailName === undefined) {
+ return; // Unknown level of detail.
+ }
+ levelOfDetailNameToDumpCount[levelOfDetailName]++;
+ if (globalDump.levelOfDetail === DETAILED) {
+ if (detectHeapProfilerInMemoryDump(globalDump)) {
+ levelOfDetailNameToDumpCount[HEAP_PROFILER_DETAIL_NAME]++;
+ }
+ }
+ });
+
+ // Add memory:<browser-name>:all_processes:dump_count[:<level>] values.
+ reportMemoryDumpCountAsValue(browserName, undefined /* total */,
+ totalDumpCount, values);
+ for (const [levelOfDetailName, levelOfDetailDumpCount] of
+ Object.entries(levelOfDetailNameToDumpCount)) {
+ reportMemoryDumpCountAsValue(browserName, levelOfDetailName,
+ levelOfDetailDumpCount, values);
+ }
+ });
+ }
+
+ /**
+ * Check whether detailed global dump has heap profiler information or not.
+ */
+ function detectHeapProfilerInMemoryDump(globalDump) {
+ for (const processDump of Object.values(globalDump.processMemoryDumps)) {
+ if (processDump.heapDumps && processDump.heapDumps.malloc) {
+ const mallocDump = processDump.heapDumps.malloc;
+ if (mallocDump.entries && mallocDump.entries.length > 0) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Add a tr.v.Histogram value to |values| reporting that the number of
+ * |levelOfDetailName| memory dumps added by |browserName| was
+ * |levelOfDetailCount|.
+ */
+ function reportMemoryDumpCountAsValue(
+ browserName, levelOfDetailName, levelOfDetailDumpCount, values) {
+ // Construct the name of the memory value.
+ const nameParts = ['memory', browserName, 'all_processes', 'dump_count'];
+ if (levelOfDetailName !== undefined) {
+ nameParts.push(levelOfDetailName);
+ }
+ const name = nameParts.join(':');
+
+ // Build the underlying histogram for the memory value.
+ const histogram = new tr.v.Histogram(name, count_smallerIsBetter,
+ BOUNDARIES_FOR_UNIT_MAP.get(count_smallerIsBetter));
+ histogram.addSample(levelOfDetailDumpCount);
+
+ // If |levelOfDetail| argument is undefined it means a total value.
+ const userFriendlyLevelOfDetail =
+ (levelOfDetailName || 'all').replace('_', ' ');
+
+ // Build the options for the memory value.
+ histogram.description = [
+ 'total number of',
+ userFriendlyLevelOfDetail,
+ 'memory dumps added by',
+ convertBrowserNameToUserFriendlyName(browserName),
+ 'to the trace'
+ ].join(' ');
+
+ // Report the memory value.
+ values.addHistogram(histogram);
+ }
+
+ /**
+ * Add generic values extracted from process memory dumps and aggregated by
+ * process name and component path into |values|.
+ *
+ * For each browser and set of global dumps in |browserNameToGlobalDumps|,
+ * |customProcessDumpValueExtractor| is applied to every process memory dump
+ * associated with the global memory dump. The second argument provided to the
+ * callback is a function for adding extracted values:
+ *
+ * function sampleProcessDumpCallback(processDump, addProcessValue) {
+ * ...
+ * addProcessScalar({
+ * source: 'reported_by_chrome',
+ * component: ['system', 'native_heap'],
+ * property: 'proportional_resident_size',
+ * value: pssExtractedFromProcessDump2,
+ * descriptionPrefixBuilder(componentPath) {
+ * return 'PSS of ' + componentPath.join('/') + ' in';
+ * }
+ * });
+ * ...
+ * }
+ *
+ * For each global memory dump, the extracted values are summed by process
+ * name (browser_process, renderer_processes, ..., all_processes) and
+ * component path (e.g. gpu is a sum of gpu:gl, gpu:graphics, ...). The sums
+ * are then aggregated over all global memory dumps associated with the given
+ * browser. For example, assuming that |customProcessDumpValueExtractor|
+ * extracts 'proportional_resident_size' values for component paths
+ * ['X', 'A'], ['X', 'B'] and ['Y'] under the same 'source' from each process
+ * memory dump, the following values will be reported (for Chrome):
+ *
+ * memory:chrome:browser_process:source:X:A:proportional_resident_size :
+ * Histogram aggregated over [
+ * sum of X:A in all 'browser' process dumps in global dump 1,
+ * ...
+ * sum of X:A in all 'browser' process dumps in global dump N
+ * ]
+ *
+ * memory:chrome:browser_process:source:X:B:proportional_resident_size :
+ * Histogram aggregated over [
+ * sum of X:B in all 'browser' process dumps in global dump 1,
+ * ...
+ * sum of X:B in all 'browser' process dumps in global dump N
+ * ]
+ *
+ * memory:chrome:browser_process:source:X:proportional_resident_size :
+ * Histogram aggregated over [
+ * sum of X:A+X:B in all 'browser' process dumps in global dump 1,
+ * ...
+ * sum of X:A+X:B in all 'browser' process dumps in global dump N
+ * ]
+ *
+ * memory:chrome:browser_process:source:Y:proportional_resident_size :
+ * Histogram aggregated over [
+ * sum of Y in all 'browser' process dumps in global dump 1,
+ * ...
+ * sum of Y in all 'browser' process dumps in global dump N
+ * ]
+ *
+ * memory:chrome:browser_process:source:proportional_resident_size :
+ * Histogram aggregated over [
+ * sum of X:A+X:B+Y in all 'browser' process dumps in global dump 1,
+ * ...
+ * sum of X:A+X:B+Y in all 'browser' process dumps in global dump N
+ * ]
+ *
+ * ...
+ *
+ * memory:chrome:all_processes:source:X:A:proportional_resident_size :
+ * Histogram aggregated over [
+ * sum of X:A in all process dumps in global dump 1,
+ * ...
+ * sum of X:A in all process dumps in global dump N,
+ * ]
+ *
+ * memory:chrome:all_processes:source:X:B:proportional_resident_size :
+ * Histogram aggregated over [
+ * sum of X:B in all process dumps in global dump 1,
+ * ...
+ * sum of X:B in all process dumps in global dump N,
+ * ]
+ *
+ * memory:chrome:all_processes:source:X:proportional_resident_size :
+ * Histogram aggregated over [
+ * sum of X:A+X:B in all process dumps in global dump 1,
+ * ...
+ * sum of X:A+X:B in all process dumps in global dump N,
+ * ]
+ *
+ * memory:chrome:all_processes:source:Y:proportional_resident_size :
+ * Histogram aggregated over [
+ * sum of Y in all process dumps in global dump 1,
+ * ...
+ * sum of Y in all process dumps in global dump N
+ * ]
+ *
+ * memory:chrome:all_processes:source:proportional_resident_size :
+ * Histogram aggregated over [
+ * sum of X:A+X:B+Y in all process dumps in global dump 1,
+ * ...
+ * sum of X:A+X:B+Y in all process dumps in global dump N
+ * ]
+ *
+ * where global dumps 1 to N are the global dumps associated with the given
+ * browser.
+ *
+ * @param {!Map<string, !Array<!tr.model.GlobalMemoryDump>}
+ * browserNameToGlobalDumps Map from browser names to arrays of global
+ * memory dumps. The generic values will be extracted from the associated
+ * process memory dumps.
+ * @param {!function(!tr.model.GlobalMemoryDump): boolean}
+ * customGlobalDumpFilter Predicate for filtering global memory dumps.
+ * @param {!function(
+ * !tr.model.ProcessMemoryDump,
+ * !function(!{
+ * source: string,
+ * componentPath: (!Array<string>|undefined),
+ * property: !{name: string, unit: !tr.b.Unit, buildDescriptionPrefix:
+ * !function(!Array<string>, string): string},
+ * value: (!tr.v.Histogram|number|undefined)
+ * }))}
+ * customProcessDumpValueExtractor Callback for extracting values from a
+ * process memory dump.
+ * @param {!function(!tr.b.MultiDimensionalViewNode)}
+ * customComponentTreeModifier Callback applied to every component tree
+ * wrt each process name.
+ * @param {!tr.v.HistogramSet} values List of values to which the
+ * resulting aggregated values are added.
+ */
+ function addMemoryDumpValues(browserNameToGlobalDumps, customGlobalDumpFilter,
+ customProcessDumpValueExtractor, customComponentTreeModifier,
+ values) {
+ browserNameToGlobalDumps.forEach(function(globalDumps, browserName) {
+ const filteredGlobalDumps = globalDumps.filter(customGlobalDumpFilter);
+ const sourceToPropertyToBuilder = extractDataFromGlobalDumps(
+ filteredGlobalDumps, customProcessDumpValueExtractor);
+ reportDataAsValues(sourceToPropertyToBuilder, browserName,
+ customComponentTreeModifier, values);
+ });
+ }
+
+ /**
+ * For each global memory dump in |globalDumps|, calculate per-process-name
+ * sums of values extracted by |customProcessDumpValueExtractor| from the
+ * associated process memory dumps.
+ *
+ * This function returns the following nested map structure:
+ *
+ * Source name (Map key, e.g. 'reported_by_os')
+ * -> Property (Map key, e.g. PROPORTIONAL_RESIDENT_SIZE)
+ * -> processAndComponentTreeBuilder
+ *
+ * where |processAndComponentTreeBuilder| is a
+ * tr.b.MultiDimensionalViewBuilder:
+ *
+ * Process name (0th dimension key, e.g. 'browser_process') x
+ * Component path (1st dimension keys, e.g. ['system', 'native_heap'])
+ * -> Sum of value over the processes (number).
+ *
+ * See addMemoryDumpValues for more details.
+ */
+ function extractDataFromGlobalDumps(
+ globalDumps, customProcessDumpValueExtractor) {
+ const sourceToPropertyToBuilder = new Map();
+ const dumpCount = globalDumps.length;
+ globalDumps.forEach(function(globalDump, dumpIndex) {
+ for (const processDump of Object.values(globalDump.processMemoryDumps)) {
+ extractDataFromProcessDump(
+ processDump, sourceToPropertyToBuilder, dumpIndex, dumpCount,
+ customProcessDumpValueExtractor);
+ }
+ });
+ return sourceToPropertyToBuilder;
+ }
+
+ function extractDataFromProcessDump(processDump, sourceToPropertyToBuilder,
+ dumpIndex, dumpCount, customProcessDumpValueExtractor) {
+ // Process name is typically 'browser', 'renderer', etc.
+ const rawProcessName = processDump.process.name;
+ const processNamePath =
+ [tr.e.chrome.chrome_processes.canonicalizeProcessName(rawProcessName)];
+
+ customProcessDumpValueExtractor(
+ processDump,
+ function addProcessScalar(spec) {
+ if (spec.value === undefined) return;
+
+ const component = spec.component || [];
+ function createDetailsForErrorMessage() {
+ return ['source=', spec.source, ', property=',
+ spec.property.name || '(undefined)', ', component=',
+ component.length === 0 ? '(empty)' : component.join(':'),
+ ' in ', processDump.process.userFriendlyName].join('');
+ }
+
+ let value;
+ if (spec.value instanceof tr.b.Scalar) {
+ value = spec.value.value;
+ if (spec.value.unit !== spec.property.unit) {
+ throw new Error('Scalar unit for ' +
+ createDetailsForErrorMessage() + ' (' +
+ spec.value.unit.unitName +
+ ') doesn\'t match the unit of the property (' +
+ spec.property.unit.unitName + ')');
+ }
+ } else {
+ value = spec.value;
+ }
+
+ let propertyToBuilder = sourceToPropertyToBuilder.get(spec.source);
+ if (propertyToBuilder === undefined) {
+ propertyToBuilder = new Map();
+ sourceToPropertyToBuilder.set(spec.source, propertyToBuilder);
+ }
+
+ let builder = propertyToBuilder.get(spec.property);
+ if (builder === undefined) {
+ builder = new tr.b.MultiDimensionalViewBuilder(
+ 2 /* dimensions (process name and component path) */,
+ dumpCount /* valueCount */),
+ propertyToBuilder.set(spec.property, builder);
+ }
+
+ const values = new Array(dumpCount);
+ values[dumpIndex] = value;
+
+ builder.addPath(
+ [processNamePath, component] /* path */, values,
+ tr.b.MultiDimensionalViewBuilder.ValueKind.TOTAL /* valueKind */);
+ });
+ }
+
+ function reportDataAsValues(sourceToPropertyToBuilder, browserName,
+ customComponentTreeModifier, values) {
+ // For each source name (e.g. 'reported_by_os')...
+ sourceToPropertyToBuilder.forEach(function(propertyToBuilder, sourceName) {
+ // For each property (e.g. EFFECTIVE_SIZE)...
+ propertyToBuilder.forEach(function(builders, property) {
+ const tree = builders.buildTopDownTreeView();
+ reportComponentDataAsValues(browserName, sourceName, property,
+ [] /* processPath */, [] /* componentPath */, tree, values,
+ customComponentTreeModifier);
+ });
+ });
+ }
+
+ /**
+ * For the given |browserName| (e.g. 'chrome'), |property|
+ * (e.g. EFFECTIVE_SIZE), |processPath| (e.g. ['browser_process']),
+ * |componentPath| (e.g. ['v8']), add
+ * a tr.v.Histogram with |unit| aggregating the total
+ * values of the associated |componentNode| across all timestamps
+ * (corresponding to global memory dumps associated with the given browser)
+ * |values| for each process (e.g. 'gpu_process', 'browser_process', etc).
+ * We also report a special 'all_processes' histogram which agregates all
+ * others, this has a RelatedNameMap diagnostic explaining
+ * how it is built from the other histograms.
+ *
+ * See addMemoryDumpValues for more details.
+ */
+ function reportComponentDataAsValues(browserName, sourceName, property,
+ processPath, componentPath, tree, values, customComponentTreeModifier,
+ opt_cachedHistograms) {
+ const cachedHistograms = opt_cachedHistograms || new Map();
+ function recurse(processPath, componentPath, node) {
+ return reportComponentDataAsValues(browserName, sourceName, property,
+ processPath, componentPath, node, values,
+ customComponentTreeModifier, cachedHistograms);
+ }
+
+ function buildHistogram(processPath, componentPath, node) {
+ return buildNamedMemoryNumericFromNode(
+ browserName,
+ sourceName,
+ property,
+ processPath.length === 0 ? 'all_processes' : processPath[0],
+ componentPath,
+ node);
+ }
+
+ customComponentTreeModifier(tree);
+ const histogram = buildHistogram(processPath, componentPath, tree);
+ if (cachedHistograms.has(histogram.name)) {
+ return cachedHistograms.get(histogram.name);
+ }
+ cachedHistograms.set(histogram.name, histogram);
+
+ const processNames = new tr.v.d.RelatedNameMap();
+ for (const [childProcessName, childProcessNode] of tree.children[0]) {
+ processPath.push(childProcessName);
+ const childProcessHistogram =
+ recurse(processPath, componentPath, childProcessNode);
+ processNames.set(childProcessName, childProcessHistogram.name);
+ processPath.pop();
+ }
+
+ const componentNames = new tr.v.d.RelatedNameMap();
+ for (const [childComponentName, childComponentNode] of tree.children[1]) {
+ componentPath.push(childComponentName);
+ const childComponentHistogram =
+ recurse(processPath, componentPath, childComponentNode);
+ componentNames.set(childComponentName, childComponentHistogram.name);
+ componentPath.pop();
+ }
+
+ values.addHistogram(histogram);
+ if (tree.children[0].size > 0) {
+ histogram.diagnostics.set('processes', processNames);
+ }
+ if (tree.children[1].size > 0) {
+ histogram.diagnostics.set('components', componentNames);
+ }
+
+ return histogram;
+ }
+
+ /**
+ * Gets the name for a histogram.
+ * The histograms have the following naming scheme:
+ * memory:chrome:browser_process:reported_by_chrome:v8:heap:effective_size_avg
+ * ^browser ^process ^source ^component ^property
+ */
+ function getNumericName(
+ browserName, sourceName, propertyName, processName, componentPath) {
+ // Construct the name of the memory value.
+ const nameParts = ['memory', browserName, processName, sourceName].concat(
+ componentPath);
+ if (propertyName !== undefined) nameParts.push(propertyName);
+ return nameParts.join(':');
+ }
+
+ /**
+ * Gets the description of a histogram.
+ */
+ function getNumericDescription(
+ property, browserName, processName, componentPath) {
+ return [
+ property.buildDescriptionPrefix(componentPath, processName),
+ 'in',
+ convertBrowserNameToUserFriendlyName(browserName)
+ ].join(' ');
+ }
+
+ /**
+ * Create a memory tr.v.Histogram with |unit| and add all total values in
+ * |node| to it. Names and describes the histogram according to the
+ * |browserName|, |sourceName|, |property|, |processName| and
+ * |componentPath|.
+ */
+ function buildNamedMemoryNumericFromNode(
+ browserName, sourceName, property, processName, componentPath, node) {
+ const name = getNumericName(
+ browserName, sourceName, property.name, processName, componentPath);
+ const description = getNumericDescription(
+ property, browserName, processName, componentPath);
+
+ // Build the underlying numeric for the memory value.
+ const numeric = buildMemoryNumericFromNode(name, node, property.unit);
+ numeric.description = description;
+ return numeric;
+ }
+
+ function buildSampleDiagnostics(value, node) {
+ if (node.children.length < 2) return undefined;
+ const diagnostics = new Map();
+ const i = node.values.indexOf(value);
+
+ const processBreakdown = new tr.v.d.Breakdown();
+ processBreakdown.colorScheme =
+ tr.e.chrome.chrome_processes.PROCESS_COLOR_SCHEME_NAME;
+ for (const [name, subNode] of node.children[0]) {
+ processBreakdown.set(name, subNode.values[i].total);
+ }
+ if (processBreakdown.size > 0) {
+ diagnostics.set('processes', processBreakdown);
+ }
+
+ const componentBreakdown = new tr.v.d.Breakdown();
+ for (const [name, subNode] of node.children[1]) {
+ componentBreakdown.set(name, subNode.values[i].total);
+ }
+ if (componentBreakdown.size > 0) {
+ diagnostics.set('components', componentBreakdown);
+ }
+
+ if (diagnostics.size === 0) return undefined;
+ return diagnostics;
+ }
+
+ /**
+ * Create a memory tr.v.Histogram with |unit| and add all total values in
+ * |node| to it.
+ */
+ function buildMemoryNumericFromNode(name, node, unit) {
+ const histogram = new tr.v.Histogram(
+ name, unit, BOUNDARIES_FOR_UNIT_MAP.get(unit));
+
+ node.values.forEach(v => histogram.addSample(
+ v.total, buildSampleDiagnostics(v, node)));
+
+ return histogram;
+ }
+
+ tr.metrics.MetricRegistry.register(memoryMetric, {
+ supportsRangeOfInterest: true
+ });
+
+ return {
+ memoryMetric,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/system_health/memory_metric_test.html b/chromium/third_party/catapult/tracing/tracing/metrics/system_health/memory_metric_test.html
new file mode 100644
index 00000000000..e3a6c40d327
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/system_health/memory_metric_test.html
@@ -0,0 +1,4249 @@
+<!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/math/range.html">
+<link rel="import" href="/tracing/base/utils.html">
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/metrics/system_health/memory_metric.html">
+<link rel="import" href="/tracing/model/container_memory_dump.html">
+<link rel="import" href="/tracing/model/heap_dump.html">
+<link rel="import" href="/tracing/model/memory_dump_test_utils.html">
+<link rel="import" href="/tracing/model/vm_region.html">
+<link rel="import" href="/tracing/value/histogram_set.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const VMRegion = tr.model.VMRegion;
+ const VMRegionClassificationNode = tr.model.VMRegionClassificationNode;
+ const BACKGROUND = tr.model.ContainerMemoryDump.LevelOfDetail.BACKGROUND;
+ const LIGHT = tr.model.ContainerMemoryDump.LevelOfDetail.LIGHT;
+ const DETAILED = tr.model.ContainerMemoryDump.LevelOfDetail.DETAILED;
+ const SIZE_DELTA = tr.model.MemoryDumpTestUtils.SIZE_DELTA;
+ const addProcessMemoryDump =
+ tr.model.MemoryDumpTestUtils.addProcessMemoryDump;
+ const addGlobalMemoryDump = tr.model.MemoryDumpTestUtils.addGlobalMemoryDump;
+ const newAllocatorDump = tr.model.MemoryDumpTestUtils.newAllocatorDump;
+ const addChildDump = tr.model.MemoryDumpTestUtils.addChildDump;
+ const addOwnershipLink = tr.model.MemoryDumpTestUtils.addOwnershipLink;
+ const count_smallerIsBetter = tr.b.Unit.byName.count_smallerIsBetter;
+ const sizeInBytes_smallerIsBetter =
+ tr.b.Unit.byName.sizeInBytes_smallerIsBetter;
+ const StackFrame = tr.model.StackFrame;
+ const HeapEntry = tr.model.HeapEntry;
+ const HeapDump = tr.model.HeapDump;
+
+ function memoryMetricTest(
+ name, modelCallback, opt_options, expectedNumerics) {
+ test(name, function() {
+ // Create a model and a fake value list.
+ const model = tr.c.TestUtils.newModel(modelCallback);
+ const valueNameToValues = {};
+ const fakeValueList = {
+ addHistogram(value) {
+ let values = valueNameToValues[value.name];
+ if (values === undefined) {
+ valueNameToValues[value.name] = values = [];
+ }
+ values.push(value);
+ }
+ };
+
+ // Run the memory metric on the model.
+ tr.metrics.sh.memoryMetric(fakeValueList, model, opt_options);
+
+ // Check that the names of the added values match expectations.
+ const actualValueNames = Object.keys(valueNameToValues).sort();
+ const expectedValueNames = Object.keys(expectedNumerics).sort();
+ assert.deepEqual(actualValueNames, expectedValueNames, {
+ // Build the long error message lazily.
+ toString() {
+ const errorMessageParts = [];
+ function addValueNamesToError(type, valueNames, otherValueNames) {
+ const otherValueNamesSet = new Set(otherValueNames);
+ errorMessageParts.push(type, ' value names:');
+ if (valueNames.length === 0) {
+ errorMessageParts.push('\n(empty)');
+ } else {
+ valueNames.forEach(function(valueName) {
+ errorMessageParts.push('\n');
+ if (!otherValueNamesSet.has(valueName)) {
+ errorMessageParts.push('+++');
+ }
+ errorMessageParts.push(valueName);
+ });
+ }
+ }
+ addValueNamesToError('Expected', expectedValueNames,
+ actualValueNames);
+ errorMessageParts.push('\n');
+ addValueNamesToError('Actual', actualValueNames, expectedValueNames);
+ return errorMessageParts.join('');
+ }
+ });
+
+ // Check that the numeric values of the added values match expectations.
+ for (const [valueName, actualValues] of
+ Object.entries(valueNameToValues)) {
+ assert.lengthOf(actualValues, 1,
+ 'Multiple \'' + valueName + '\' values');
+ const actualHistogram = actualValues[0];
+ assert.instanceOf(actualHistogram, tr.v.Histogram);
+
+ const expectedHistogram = expectedNumerics[valueName];
+ assert.strictEqual(actualHistogram.unit, expectedHistogram.unit,
+ 'Invalid \'' + valueName + '\' unit (expected: ' +
+ expectedHistogram.unit.unitName, + ', actual: ' +
+ actualHistogram.unit.unitName + ')');
+
+ if (!(expectedHistogram.value instanceof Array)) {
+ assert.fail('Test sanity check: expected value must be an array');
+ }
+
+ assert.instanceOf(actualHistogram, tr.v.Histogram,
+ 'Invalid \'' + valueName + '\' class');
+ assert.strictEqual(actualHistogram.numValues,
+ expectedHistogram.value.length,
+ 'Invalid \'' + valueName + '\' Histogram numValues');
+ assert.closeTo(actualHistogram.sum,
+ expectedHistogram.value.reduce((a, b) => a + b, 0), SIZE_DELTA,
+ 'Invalid \'' + valueName + '\' Histogram sum');
+
+ // Check that the bin counts match.
+ const binToCount = new Map();
+ expectedHistogram.value.forEach(function(value) {
+ const bin = actualHistogram.getBinForValue(value);
+ binToCount.set(bin, (binToCount.get(bin) || 0) + 1);
+ });
+ actualHistogram.allBins.forEach(function(bin) {
+ binToCount.set(bin, (binToCount.get(bin) || 0) - bin.count);
+ });
+ binToCount.forEach(function(count, bin) {
+ assert.strictEqual(count, 0, 'Invalid \'' + valueName +
+ '\' bin count for range ' + bin.min + '-' + bin.max);
+ });
+
+ // Check that the description matches expectations.
+ assert.strictEqual(
+ actualHistogram.description, expectedHistogram.description,
+ 'Invalid \'' + valueName + '\' description');
+ }
+ });
+ }
+
+ function createProcessWithName(model, name) {
+ const uniquePid =
+ Math.max.apply(null, Object.keys(model.processes).concat([0])) + 1;
+ const process = model.getOrCreateProcess(uniquePid);
+ process.name = name;
+ return process;
+ }
+
+ function createChromeBrowserProcess(model) {
+ const process = createProcessWithName(model, 'Browser');
+ process.getOrCreateThread(1).name = 'CrBrowserMain';
+ return process;
+ }
+
+ function createWebViewProcess(model) {
+ const process = createChromeBrowserProcess(model);
+ process.getOrCreateThread(2).name = 'Chrome_InProcRendererThread';
+ return process;
+ }
+
+ memoryMetricTest('noDumps_noBrowser', function(model) {
+ createProcessWithName(model, 'Non-browser');
+ }, undefined /* opt_options */, {
+ /* no values */
+ });
+
+ memoryMetricTest('noDumps_chrome', function(model) {
+ createChromeBrowserProcess(model);
+ }, undefined /* opt_options */, {
+ 'memory:chrome:all_processes:dump_count:detailed': {
+ value: [0],
+ unit: count_smallerIsBetter,
+ description: 'total number of detailed memory dumps added by Chrome to ' +
+ 'the trace'
+ },
+ 'memory:chrome:all_processes:dump_count:light': {
+ value: [0],
+ unit: count_smallerIsBetter,
+ description: 'total number of light memory dumps added by Chrome to ' +
+ 'the trace'
+ },
+ 'memory:chrome:all_processes:dump_count:background': {
+ value: [0],
+ unit: count_smallerIsBetter,
+ description: 'total number of background memory dumps added by Chrome ' +
+ 'to the trace'
+ },
+ 'memory:chrome:all_processes:dump_count:heap_profiler': {
+ value: [0],
+ unit: count_smallerIsBetter,
+ description: 'total number of heap profiler memory dumps added by ' +
+ 'Chrome to the trace'
+ },
+ 'memory:chrome:all_processes:dump_count': {
+ value: [0],
+ unit: count_smallerIsBetter,
+ description: 'total number of all memory dumps added by Chrome to the ' +
+ 'trace'
+ }
+ });
+
+ memoryMetricTest('noDumps_multipleBrowsers', function(model) {
+ createChromeBrowserProcess(model);
+ createWebViewProcess(model);
+ createProcessWithName(model, 'Non-browser');
+ createChromeBrowserProcess(model);
+ }, undefined /* opt_options */, {
+ 'memory:chrome2:all_processes:dump_count:detailed': {
+ value: [0],
+ unit: count_smallerIsBetter,
+ description: 'total number of detailed memory dumps added by Chrome(2) ' +
+ 'to the trace'
+ },
+ 'memory:chrome2:all_processes:dump_count:light': {
+ value: [0],
+ unit: count_smallerIsBetter,
+ description: 'total number of light memory dumps added by Chrome(2) to ' +
+ 'the trace'
+ },
+ 'memory:chrome2:all_processes:dump_count:background': {
+ value: [0],
+ unit: count_smallerIsBetter,
+ description: 'total number of background memory dumps added by ' +
+ 'Chrome(2) to the trace'
+ },
+ 'memory:chrome2:all_processes:dump_count:heap_profiler': {
+ value: [0],
+ unit: count_smallerIsBetter,
+ description: 'total number of heap profiler memory dumps added by ' +
+ 'Chrome(2) to the trace'
+ },
+ 'memory:chrome2:all_processes:dump_count': {
+ value: [0],
+ unit: count_smallerIsBetter,
+ description: 'total number of all memory dumps added by Chrome(2) to ' +
+ 'the trace'
+ },
+ 'memory:chrome:all_processes:dump_count:detailed': {
+ value: [0],
+ unit: count_smallerIsBetter,
+ description: 'total number of detailed memory dumps added by Chrome to ' +
+ 'the trace'
+ },
+ 'memory:chrome:all_processes:dump_count:light': {
+ value: [0],
+ unit: count_smallerIsBetter,
+ description: 'total number of light memory dumps added by Chrome to ' +
+ 'the trace'
+ },
+ 'memory:chrome:all_processes:dump_count:background': {
+ value: [0],
+ unit: count_smallerIsBetter,
+ description: 'total number of background memory dumps added by Chrome ' +
+ 'to the trace'
+ },
+ 'memory:chrome:all_processes:dump_count:heap_profiler': {
+ value: [0],
+ unit: count_smallerIsBetter,
+ description: 'total number of heap profiler memory dumps added by ' +
+ 'Chrome to the trace'
+ },
+ 'memory:chrome:all_processes:dump_count': {
+ value: [0],
+ unit: count_smallerIsBetter,
+ description: 'total number of all memory dumps added by Chrome to the ' +
+ 'trace'
+ },
+ 'memory:webview:all_processes:dump_count:detailed': {
+ value: [0],
+ unit: count_smallerIsBetter,
+ description: 'total number of detailed memory dumps added by WebView ' +
+ 'to the trace'
+ },
+ 'memory:webview:all_processes:dump_count:light': {
+ value: [0],
+ unit: count_smallerIsBetter,
+ description: 'total number of light memory dumps added by WebView to ' +
+ 'the trace'
+ },
+ 'memory:webview:all_processes:dump_count:background': {
+ value: [0],
+ unit: count_smallerIsBetter,
+ description: 'total number of background memory dumps added by WebView ' +
+ 'to the trace'
+ },
+ 'memory:webview:all_processes:dump_count:heap_profiler': {
+ value: [0],
+ unit: count_smallerIsBetter,
+ description: 'total number of heap profiler memory dumps added by ' +
+ 'WebView to the trace'
+ },
+ 'memory:webview:all_processes:dump_count': {
+ value: [0],
+ unit: count_smallerIsBetter,
+ description: 'total number of all memory dumps added by WebView to the ' +
+ 'trace'
+ }
+ });
+
+ memoryMetricTest('dumpCountsOnly_unknownBrowser', function(model) {
+ addGlobalMemoryDump(model, {ts: 45, levelOfDetail: DETAILED});
+ addGlobalMemoryDump(model, {ts: 65, levelOfDetail: BACKGROUND});
+ addGlobalMemoryDump(model, {ts: 68, levelOfDetail: LIGHT});
+ addGlobalMemoryDump(model, {ts: 89, levelOfDetail: DETAILED});
+ }, undefined /* opt_options */, {
+ 'memory:unknown_browser:all_processes:dump_count:detailed': {
+ value: [2],
+ unit: count_smallerIsBetter,
+ description: 'total number of detailed memory dumps added by an ' +
+ 'unknown browser to the trace'
+ },
+ 'memory:unknown_browser:all_processes:dump_count:light': {
+ value: [1],
+ unit: count_smallerIsBetter,
+ description: 'total number of light memory dumps added by an unknown ' +
+ 'browser to the trace'
+ },
+ 'memory:unknown_browser:all_processes:dump_count:background': {
+ value: [1],
+ unit: count_smallerIsBetter,
+ description: 'total number of background memory dumps added by an ' +
+ 'unknown browser to the trace'
+ },
+ 'memory:unknown_browser:all_processes:dump_count:heap_profiler': {
+ value: [0],
+ unit: count_smallerIsBetter,
+ description: 'total number of heap profiler memory dumps added by an ' +
+ 'unknown browser to the trace'
+ },
+ 'memory:unknown_browser:all_processes:dump_count': {
+ value: [4],
+ unit: count_smallerIsBetter,
+ description: 'total number of all memory dumps added by an unknown ' +
+ 'browser to the trace'
+ }
+ });
+
+ memoryMetricTest('dumpCountsOnly_webview', function(model) {
+ const p = createWebViewProcess(model);
+ addProcessMemoryDump(addGlobalMemoryDump(
+ model, {ts: 45, levelOfDetail: LIGHT}), p, {ts: 45});
+ addProcessMemoryDump(addGlobalMemoryDump(
+ model, {ts: 68, levelOfDetail: LIGHT}), p, {ts: 68});
+ }, undefined /* opt_options */, {
+ 'memory:webview:all_processes:dump_count:detailed': {
+ value: [0],
+ unit: count_smallerIsBetter,
+ description: 'total number of detailed memory dumps added by WebView ' +
+ 'to the trace'
+ },
+ 'memory:webview:all_processes:dump_count:light': {
+ value: [2],
+ unit: count_smallerIsBetter,
+ description: 'total number of light memory dumps added by WebView to ' +
+ 'the trace'
+ },
+ 'memory:webview:all_processes:dump_count:background': {
+ value: [0],
+ unit: count_smallerIsBetter,
+ description: 'total number of background memory dumps added by WebView ' +
+ 'to the trace'
+ },
+ 'memory:webview:all_processes:dump_count:heap_profiler': {
+ value: [0],
+ unit: count_smallerIsBetter,
+ description: 'total number of heap profiler memory dumps added by ' +
+ 'WebView to the trace'
+ },
+ 'memory:webview:all_processes:dump_count': {
+ value: [2],
+ unit: count_smallerIsBetter,
+ description: 'total number of all memory dumps added by WebView to the ' +
+ 'trace'
+ },
+ 'memory:webview:all_processes:process_count': {
+ value: [1, 1],
+ unit: count_smallerIsBetter,
+ description: 'total number of all processes in WebView'
+ },
+ 'memory:webview:browser_process:process_count': {
+ value: [1, 1],
+ unit: count_smallerIsBetter,
+ description: 'total number of browser processes in WebView'
+ }
+ });
+
+ memoryMetricTest('generalValues_chrome', function(model) {
+ const pBrowser = createChromeBrowserProcess(model);
+ const pRendererA = createProcessWithName(model, 'Renderer');
+ const pRendererB = createProcessWithName(model, 'Renderer');
+ const pPpapi = createProcessWithName(model, 'PPAPI Process');
+ const pUnknown = createProcessWithName(model, undefined);
+
+ // Timestamp 1.
+ const gmd1 = addGlobalMemoryDump(model, {ts: 20});
+ const pmdBrowser1 = addProcessMemoryDump(gmd1, pBrowser, {ts: 19});
+ pmdBrowser1.memoryAllocatorDumps = [
+ newAllocatorDump(pmdBrowser1, 'malloc', {numerics: {
+ size: 8,
+ allocated_objects_size: 4,
+ shim_allocated_objects_size: 3,
+ }})
+ ];
+ pmdBrowser1.totals = {
+ residentBytes: 200,
+ peakResidentBytes: 230,
+ privateFootprintBytes: 240,
+ };
+ const browserHeapDump = new HeapDump(pmdBrowser1);
+ const rootFrame1 = new StackFrame(
+ undefined, tr.b.GUID.allocateSimple(), undefined);
+ const childFrame1 = new StackFrame(
+ rootFrame1, tr.b.GUID.allocateSimple(), 'draw');
+ rootFrame1.addChild(childFrame1);
+ browserHeapDump.addEntry(
+ undefined, 'HTMLImportLoader', 1024, undefined);
+ browserHeapDump.addEntry(
+ rootFrame1, 'HTMLImportLoader', 1048576, undefined);
+ browserHeapDump.addEntry(undefined, '[unknown]', 17332, 42);
+ browserHeapDump.addEntry(childFrame1, '[unknown]', 26309, 10);
+ pmdBrowser1.heapDumps = {};
+ pmdBrowser1.heapDumps.malloc = browserHeapDump;
+
+ const pmdRendererA1 = addProcessMemoryDump(gmd1, pRendererA, {ts: 20});
+ pmdRendererA1.memoryAllocatorDumps = (function() {
+ const mallocDump =
+ newAllocatorDump(pmdRendererA1, 'malloc', {numerics: {size: 16}});
+ const partitionAllocDump =
+ newAllocatorDump(pmdRendererA1, 'partition_alloc');
+ const v8Dump = newAllocatorDump(pmdRendererA1, 'v8',
+ {numerics: {code_and_metadata_size: 16}});
+ addOwnershipLink(
+ addChildDump(partitionAllocDump, 'allocated_objects',
+ {numerics: {size: 32}}),
+ addChildDump(partitionAllocDump, 'partitions',
+ {numerics: {size: 24}}));
+ return [mallocDump, partitionAllocDump, v8Dump];
+ })();
+ const pmdGpu1 = addProcessMemoryDump(gmd1, pPpapi, {ts: 21});
+ pmdGpu1.memoryAllocatorDumps = [
+ newAllocatorDump(pmdGpu1, 'gpu', {numerics: {
+ size: 30,
+ allocated_objects_size: 25
+ }})
+ ];
+
+ // Timestamp 2.
+ const gmd2 = addGlobalMemoryDump(model, {ts: 40});
+ const pmdBrowser2 = addProcessMemoryDump(gmd2, pBrowser, {ts: 41});
+ pmdBrowser2.memoryAllocatorDumps = (function() {
+ const mallocDump = newAllocatorDump(pmdBrowser2, 'malloc',
+ {numerics: {size: 120}});
+ const tracingDump =
+ newAllocatorDump(pmdBrowser2, 'tracing', {numerics: {size: 40}});
+ return [mallocDump, tracingDump];
+ })();
+ const pmdRendererA2 = addProcessMemoryDump(gmd2, pRendererA, {ts: 39});
+ pmdRendererA2.memoryAllocatorDumps = (function() {
+ const partitionAllocDump =
+ newAllocatorDump(pmdRendererA2, 'partition_alloc');
+ addOwnershipLink(
+ addChildDump(partitionAllocDump, 'allocated_objects',
+ {numerics: {size: 320}}),
+ addChildDump(partitionAllocDump, 'partitions',
+ {numerics: {size: 240}}));
+ const v8Dump = newAllocatorDump(pmdRendererA2, 'v8',
+ {numerics: {size: 650}});
+ const isolateDumpA = addChildDump(v8Dump, 'isolate_A');
+ addChildDump(isolateDumpA, 'malloc', {numerics: {
+ size: 1,
+ peak_size: 2
+ }});
+ const heapDumpA = addChildDump(isolateDumpA, 'heap_spaces', {numerics: {
+ size: 42,
+ allocated_objects_size: 36
+ }});
+ addChildDump(heapDumpA, 'code_space', {numerics: {
+ allocated_objects_size: 1,
+ size: 2
+ }});
+ addChildDump(heapDumpA, 'large_object_space', {numerics: {
+ allocated_objects_size: 3,
+ size: 4
+ }});
+ addChildDump(heapDumpA, 'map_space', {numerics: {
+ allocated_objects_size: 5,
+ size: 6,
+ }});
+ addChildDump(heapDumpA, 'new_space', {numerics: {
+ allocated_objects_size: 7,
+ size: 8
+ }});
+ addChildDump(heapDumpA, 'old_space', {numerics: {
+ allocated_objects_size: 9,
+ size: 10
+ }});
+ addChildDump(heapDumpA, 'other_spaces', {numerics: {
+ allocated_objects_size: 11,
+ size: 12
+ }});
+ const isolateDumpB = addChildDump(v8Dump, 'isolate_B');
+ addChildDump(isolateDumpB, 'malloc', {numerics: {
+ size: 10,
+ peak_size: 20
+ }});
+ const heapDumpB = addChildDump(isolateDumpB, 'heap_spaces', {numerics: {
+ size: 12,
+ allocated_objects_size: 6
+ }});
+ addChildDump(heapDumpB, 'code_space', {numerics: {
+ allocated_objects_size: 1,
+ size: 2
+ }});
+ addChildDump(heapDumpB, 'large_object_space', {numerics: {
+ allocated_objects_size: 1,
+ size: 2
+ }});
+ addChildDump(heapDumpB, 'map_space', {numerics: {
+ allocated_objects_size: 1,
+ size: 2,
+ }});
+ addChildDump(heapDumpB, 'new_space', {numerics: {
+ allocated_objects_size: 1,
+ size: 2
+ }});
+ addChildDump(heapDumpB, 'old_space', {numerics: {
+ allocated_objects_size: 1,
+ size: 2
+ }});
+ addChildDump(heapDumpB, 'other_spaces', {numerics: {
+ allocated_objects_size: 1,
+ size: 2
+ }});
+ const isolateDumpC = addChildDump(v8Dump, 'isolate_C');
+ addChildDump(isolateDumpC, 'malloc', {numerics: {
+ size: 100,
+ }});
+ addChildDump(isolateDumpC, 'heap_spaces', {numerics: {
+ size: 2,
+ allocated_objects_size: 1
+ }});
+ const isolateDumpD = addChildDump(v8Dump, 'isolate_D');
+ addChildDump(isolateDumpD, 'malloc', {numerics: {
+ peak_size: 200,
+ }});
+ return [partitionAllocDump, v8Dump];
+ })();
+ const pmdRendererB2 = addProcessMemoryDump(gmd2, pRendererB, {ts: 40});
+ pmdRendererB2.memoryAllocatorDumps = [
+ newAllocatorDump(pmdRendererB2, 'v8', {numerics: {
+ size: 970,
+ allocated_objects_size: 860,
+ bytecode_and_metadata_size: 678
+ }}),
+ newAllocatorDump(pmdRendererB2, 'malloc',
+ {numerics: {allocated_objects_size: 750}})
+ ];
+ const pmdUnknown = addProcessMemoryDump(gmd2, pUnknown, {ts: 42});
+ pmdUnknown.memoryAllocatorDumps = [
+ newAllocatorDump(pmdRendererB2, 'v8', {numerics: {size: 111}})
+ ];
+
+ // Timestamp 3.
+ const gmd3 = addGlobalMemoryDump(model, {ts: 60});
+ const pmdBrowser3 = addProcessMemoryDump(gmd3, pBrowser, {ts: 60});
+ pmdBrowser3.memoryAllocatorDumps = [
+ newAllocatorDump(pmdBrowser3, 'malloc', {numerics: {
+ size: 8000,
+ allocated_objects_size: 4000,
+ shim_allocated_objects_size: 3000
+ }})
+ ];
+ const pmdRendererB3 = addProcessMemoryDump(gmd3, pRendererB, {ts: 61});
+ // Intentionally pmdRendererB3.memoryAllocatorDumps undefined.
+ const pmdGpu3 = addProcessMemoryDump(gmd3, pPpapi, {ts: 59});
+ pmdGpu3.memoryAllocatorDumps = [
+ newAllocatorDump(pmdGpu3, 'gpu', {numerics: {size: 300}})
+ ];
+
+ // Timestamp 4.
+ const gmd4 = addGlobalMemoryDump(model, {ts: 80});
+ const pmdBrowser4 = addProcessMemoryDump(gmd4, pBrowser, {ts: 81});
+ pmdBrowser4.memoryAllocatorDumps = [
+ newAllocatorDump(pmdBrowser4, 'malloc', {numerics: {size: 80000}})
+ ];
+ const pmdRendererB4 = addProcessMemoryDump(gmd4, pRendererB, {ts: 79});
+ pmdRendererB4.memoryAllocatorDumps = (function() {
+ const v8Dump = newAllocatorDump(pmdRendererB4, 'v8', {numerics: {
+ code_and_metadata_size: 21,
+ bytecode_and_metadata_size: 35,
+ size: 9e5
+ }});
+ const partitionAllocDump = newAllocatorDump(pmdRendererB4,
+ 'partition_alloc', {numerics: {size: 5e5}});
+ addOwnershipLink(partitionAllocDump, v8Dump);
+ return [v8Dump, partitionAllocDump];
+ })();
+ const rendererBHeapDump4 = new HeapDump(pmdRendererB4);
+ rendererBHeapDump4.addEntry(
+ undefined, 'BlinkObject', 1687992, undefined);
+ rendererBHeapDump4.addEntry(undefined, undefined, 2243546, 42);
+ rendererBHeapDump4.addEntry(undefined, 'BlinkObject', 1252376, 10);
+
+ pmdRendererB4.heapDumps = {};
+ pmdRendererB4.heapDumps.blinkgc = rendererBHeapDump4;
+
+ const pmdGpu4 = addProcessMemoryDump(gmd4, pPpapi, {ts: 80});
+ pmdGpu4.memoryAllocatorDumps = [
+ newAllocatorDump(pmdGpu4, 'gpu',
+ {numerics: {memtrack_pss: 666 /* ignored */}})
+ ];
+ }, undefined /* opt_options */, {
+ 'memory:chrome:all_processes:dump_count:detailed': {
+ value: [0],
+ unit: count_smallerIsBetter,
+ description: 'total number of detailed memory dumps added by Chrome to ' +
+ 'the trace'
+ },
+ 'memory:chrome:all_processes:dump_count:light': {
+ value: [4],
+ unit: count_smallerIsBetter,
+ description: 'total number of light memory dumps added by Chrome to ' +
+ 'the trace'
+ },
+ 'memory:chrome:all_processes:dump_count:background': {
+ value: [0],
+ unit: count_smallerIsBetter,
+ description: 'total number of background memory dumps added by Chrome ' +
+ 'to the trace'
+ },
+ 'memory:chrome:all_processes:dump_count:heap_profiler': {
+ value: [0],
+ unit: count_smallerIsBetter,
+ description: 'total number of heap profiler memory dumps added by ' +
+ 'Chrome to the trace'
+ },
+ 'memory:chrome:all_processes:dump_count': {
+ value: [4],
+ unit: count_smallerIsBetter,
+ description: 'total number of all memory dumps added by Chrome to the ' +
+ 'trace'
+ },
+ 'memory:chrome:all_processes:process_count': {
+ value: [3, 4, 3, 3],
+ unit: count_smallerIsBetter,
+ description: 'total number of all processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_chrome:effective_size': {
+ value: [30 + (8 + 16) + 32, (120 - 40) + 320 + (650 + 970) + 111,
+ 300 + 8000, 80000 + 5e5 + 4e5],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total effective size reported by Chrome for all ' +
+ 'processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_chrome:allocated_objects_size': {
+ value: [25 + 4 + 32, (36 + 6 + 1) + 750 + 860 + 320 + 40, 4000, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total size of all allocated objects reported by Chrome ' +
+ 'for all processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_chrome:blinkgc:BlinkObject:heap_category_size':
+ {
+ value: [0, 0, 1687992 + 1252376, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'heap profiler category size for blinkgc:BlinkObject in ' +
+ 'all processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_chrome:blinkgc:heap_category_size':
+ {
+ value: [0, 0, 1687992 + 1252376, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'heap profiler category size for blinkgc in all processes ' +
+ 'in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_chrome:shim_allocated_objects_size':
+ {
+ value: [3, 0, 3000, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total size of all allocated objects through shim ' +
+ 'reported by Chrome for all processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_chrome:peak_size':
+ {
+ value: [0, 2 + 20 + 200, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total peak size reported by Chrome for all ' +
+ 'processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_os:resident_size': {
+ value: [200, 0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total resident set size (RSS) reported by the OS for all' +
+ ' processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_os:peak_resident_size': {
+ value: [230, 0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total peak resident set size reported by the OS for all ' +
+ 'processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_os:private_footprint_size': {
+ value: [240, 0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total private footprint size reported by the OS for all ' +
+ 'processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_os:system_memory:resident_size': {
+ value: [200, 0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total resident set size (RSS) of system memory (RAM) ' +
+ 'used by all processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_os:system_memory:peak_resident_size':
+ {
+ value: [230, 0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total peak resident set size of system memory (RAM) ' +
+ 'used by all processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_os:system_memory:private_footprint_size': { // eslint-disable-line max-len
+ value: [240, 0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total private footprint size of system memory (RAM) ' +
+ 'used by all processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_chrome:gpu:effective_size': {
+ value: [30, 0, 300, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of gpu in all processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_chrome:heap_category_size': {
+ value: [17332, 0, 1687992 + 1252376, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total heap profiler category size reported by Chrome for ' +
+ 'all processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_chrome:malloc:[unknown]:heap_category_size':
+ {
+ value: [17332, 0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'heap profiler category size for malloc:[unknown] in all ' +
+ 'processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_chrome:gpu:allocated_objects_size':
+ {
+ value: [25, 0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'size of all objects allocated by gpu in all ' +
+ 'processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_chrome:malloc:effective_size': {
+ value: [8 + 16, 120 - 40, 8000, 80000],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of malloc in all processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_chrome:malloc:heap_category_size':
+ {
+ value: [17332, 0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'heap profiler category size for malloc in all processes ' +
+ 'in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_chrome:malloc:shim_allocated_objects_size':
+ {
+ value: [3, 0, 3000, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'size of all objects allocated through shim by malloc in ' +
+ 'all processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_chrome:malloc:allocated_objects_size':
+ {
+ value: [4, 40 + 750, 4000, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'size of all objects allocated by malloc in all ' +
+ 'processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_chrome:partition_alloc:allocated_objects_size':
+ {
+ value: [32, 320, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'size of all objects allocated by partition_alloc in ' +
+ 'all processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_chrome:partition_alloc:effective_size':
+ {
+ value: [32, 320, 0, 5e5],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of partition_alloc in all processes ' +
+ 'in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_chrome:tracing:effective_size': {
+ value: [0, 40, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of tracing in all processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_chrome:v8:allocated_by_malloc:effective_size':
+ {
+ value: [0, 1 + 10 + 100, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of objects allocated by malloc for v8 ' +
+ 'in all processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_chrome:v8:allocated_by_malloc:peak_size':
+ {
+ value: [0, 2 + 20 + 200, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'peak size of objects allocated by malloc for v8 ' +
+ 'in all processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_chrome:code_and_metadata_size':
+ {
+ value: [16, 678, 0, 21 + 35],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total size of code and metadata reported by Chrome ' +
+ 'for all processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_chrome:v8:code_and_metadata_size':
+ {
+ value: [16, 678, 0, 21 + 35],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'size of v8 code and metadata in all processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_chrome:v8:heap:allocated_objects_size':
+ {
+ value: [0, 36 + 6 + 1, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'size of all objects allocated by v8:heap in all ' +
+ 'processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_chrome:v8:heap:effective_size': {
+ value: [0, 42 + 12 + 2, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of v8:heap in all processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_chrome:v8:heap:code_space:allocated_objects_size':
+ {
+ value: [0, 1 + 1, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'size of all objects allocated by v8:heap:code_space ' +
+ 'in all processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_chrome:v8:heap:code_space:effective_size':
+ {
+ value: [0, 2 + 2, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of v8:heap:code_space in all ' +
+ 'processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_chrome:v8:heap:large_object_space:allocated_objects_size':
+ {
+ value: [0, 3 + 1, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'size of all objects allocated by ' +
+ 'v8:heap:large_object_space in all processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_chrome:v8:heap:large_object_space:effective_size':
+ {
+ value: [0, 4 + 2, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of v8:heap:large_object_space in all ' +
+ 'processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_chrome:v8:heap:map_space:allocated_objects_size':
+ {
+ value: [0, 5 + 1, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'size of all objects allocated by v8:heap:map_space ' +
+ 'in all processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_chrome:v8:heap:map_space:effective_size':
+ {
+ value: [0, 6 + 2, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of v8:heap:map_space in all ' +
+ 'processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_chrome:v8:heap:new_space:allocated_objects_size':
+ {
+ value: [0, 7 + 1, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'size of all objects allocated by v8:heap:new_space ' +
+ 'in all processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_chrome:v8:heap:new_space:effective_size':
+ {
+ value: [0, 8 + 2, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of v8:heap:new_space in all ' +
+ 'processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_chrome:v8:heap:old_space:allocated_objects_size':
+ {
+ value: [0, 9 + 1, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'size of all objects allocated by v8:heap:old_space ' +
+ 'in all processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_chrome:v8:heap:old_space:effective_size':
+ {
+ value: [0, 10 + 2, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of v8:heap:old_space in all ' +
+ 'processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_chrome:v8:effective_size': {
+ value: [0, 650 + 970 + 111, 0, 4e5],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of v8 in all processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_chrome:v8:allocated_objects_size':
+ {
+ value: [0, (36 + 6 + 1) + 860, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'size of all objects allocated by v8 in all processes ' +
+ 'in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_chrome:v8:peak_size':
+ {
+ value: [0, 2 + 20 + 200, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'peak size of v8 in all processes in Chrome'
+ },
+ 'memory:chrome:browser_process:reported_by_os:peak_resident_size': {
+ value: [230, 0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total peak resident set size reported by the OS for the ' +
+ 'browser process in Chrome'
+ },
+ 'memory:chrome:browser_process:reported_by_os:system_memory:peak_resident_size':
+ {
+ value: [230, 0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total peak resident set size of system memory (RAM) ' +
+ 'used by the browser process in Chrome'
+ },
+ 'memory:chrome:browser_process:reported_by_os:private_footprint_size': {
+ value: [240, 0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total private footprint size reported by the OS for the ' +
+ 'browser process in Chrome'
+ },
+ 'memory:chrome:browser_process:reported_by_os:system_memory:private_footprint_size':
+ {
+ value: [240, 0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total private footprint size of system memory (RAM) ' +
+ 'used by the browser process in Chrome'
+ },
+ 'memory:chrome:browser_process:reported_by_os:resident_size': {
+ value: [200, 0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total resident set size (RSS) reported by the OS for the ' +
+ 'browser process in Chrome'
+ },
+ 'memory:chrome:browser_process:reported_by_os:system_memory:resident_size':
+ {
+ value: [200, 0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total resident set size (RSS) of system memory (RAM) ' +
+ 'used by the browser process in Chrome'
+ },
+ 'memory:chrome:browser_process:process_count': {
+ value: [1, 1, 1, 1],
+ unit: count_smallerIsBetter,
+ description: 'total number of browser processes in Chrome'
+ },
+ 'memory:chrome:browser_process:reported_by_chrome:effective_size': {
+ value: [8, (120 - 40), 8000, 80000],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total effective size reported by Chrome for the browser ' +
+ 'process in Chrome'
+ },
+ 'memory:chrome:browser_process:reported_by_chrome:heap_category_size': {
+ value: [17332, 0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total heap profiler category size reported by Chrome for ' +
+ 'the browser process in Chrome'
+ },
+ 'memory:chrome:browser_process:reported_by_chrome:malloc:[unknown]:heap_category_size':
+ {
+ value: [17332, 0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'heap profiler category size for malloc:[unknown] in the ' +
+ 'browser process in Chrome'
+ },
+ 'memory:chrome:browser_process:reported_by_chrome:allocated_objects_size': {
+ value: [4 + 40, 0, 4000, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total size of all allocated objects reported by Chrome ' +
+ 'for the browser process in Chrome'
+ },
+ 'memory:chrome:browser_process:reported_by_chrome:shim_allocated_objects_size':
+ {
+ value: [3, 0, 3000, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total size of all allocated objects through shim ' +
+ 'reported by Chrome for the browser process in Chrome'
+ },
+ 'memory:chrome:browser_process:reported_by_chrome:malloc:effective_size': {
+ value: [8, 120 - 40, 8000, 80000],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of malloc in the browser process in Chrome'
+ },
+ 'memory:chrome:browser_process:reported_by_chrome:malloc:heap_category_size':
+ {
+ value: [17332, 0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'heap profiler category size for malloc in the browser ' +
+ 'process in Chrome'
+ },
+ 'memory:chrome:browser_process:reported_by_chrome:malloc:allocated_objects_size':
+ {
+ value: [4 + 40, 0, 4000, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'size of all objects allocated by malloc in the ' +
+ 'browser process in Chrome'
+ },
+ 'memory:chrome:browser_process:reported_by_chrome:malloc:shim_allocated_objects_size':
+ {
+ value: [3, 0, 3000, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'size of all objects allocated through shim by malloc in ' +
+ 'the browser process in Chrome'
+ },
+ 'memory:chrome:browser_process:reported_by_chrome:tracing:effective_size': {
+ value: [0, 40, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of tracing in the browser process in Chrome'
+ },
+ 'memory:chrome:ppapi_process:process_count': {
+ value: [1, 0, 1, 1],
+ unit: count_smallerIsBetter,
+ description: 'total number of PPAPI processes in Chrome'
+ },
+ 'memory:chrome:ppapi_process:reported_by_chrome:effective_size': {
+ value: [30, 0, 300, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total effective size reported by Chrome for the PPAPI ' +
+ 'process in Chrome'
+ },
+ 'memory:chrome:ppapi_process:reported_by_chrome:allocated_objects_size': {
+ value: [25, 0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total size of all allocated objects reported by Chrome ' +
+ 'for the PPAPI process in Chrome'
+ },
+ 'memory:chrome:ppapi_process:reported_by_chrome:gpu:effective_size': {
+ value: [30, 0, 300, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of gpu in the PPAPI process in Chrome'
+ },
+ 'memory:chrome:ppapi_process:reported_by_chrome:gpu:allocated_objects_size':
+ {
+ value: [25, 0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'size of all objects allocated by gpu in the PPAPI ' +
+ 'process in Chrome'
+ },
+ 'memory:chrome:renderer_processes:process_count': {
+ value: [1, 2, 1, 1],
+ unit: count_smallerIsBetter,
+ description: 'total number of renderer processes in Chrome'
+ },
+ 'memory:chrome:renderer_processes:reported_by_chrome:effective_size': {
+ value: [16 + 32, 320 + 650 + 970, 0, 5e5 + 4e5],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total effective size reported by Chrome for renderer ' +
+ 'processes in Chrome'
+ },
+ 'memory:chrome:renderer_processes:reported_by_chrome:allocated_objects_size':
+ {
+ value: [32, (36 + 6 + 1) + 750 + 860 + 320, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total size of all allocated objects reported by ' +
+ 'Chrome for renderer processes in Chrome'
+ },
+ 'memory:chrome:renderer_processes:reported_by_chrome:blinkgc:BlinkObject:heap_category_size':
+ {
+ value: [0, 0, 1687992 + 1252376, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'heap profiler category size for blinkgc:BlinkObject in ' +
+ 'renderer processes in Chrome'
+ },
+ 'memory:chrome:renderer_processes:reported_by_chrome:blinkgc:heap_category_size':
+ {
+ value: [0, 0, 1687992 + 1252376, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'heap profiler category size for blinkgc in renderer ' +
+ 'processes in Chrome'
+ },
+ 'memory:chrome:renderer_processes:reported_by_chrome:peak_size':
+ {
+ value: [0, 2 + 20 + 200, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total peak size reported by Chrome ' +
+ 'for renderer processes in Chrome'
+ },
+ 'memory:chrome:renderer_processes:reported_by_chrome:malloc:effective_size':
+ {
+ value: [16, 0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of malloc in renderer processes in ' +
+ 'Chrome'
+ },
+ 'memory:chrome:renderer_processes:reported_by_chrome:heap_category_size': {
+ value: [0, 0, 1687992 + 1252376, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total heap profiler category size reported by Chrome for ' +
+ 'renderer processes in Chrome'
+ },
+ 'memory:chrome:renderer_processes:reported_by_chrome:malloc:allocated_objects_size':
+ {
+ value: [0, 750, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'size of all objects allocated by malloc in renderer ' +
+ 'processes in Chrome'
+ },
+ 'memory:chrome:renderer_processes:reported_by_chrome:partition_alloc:allocated_objects_size':
+ {
+ value: [32, 320, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'size of all objects allocated by partition_alloc in ' +
+ 'renderer processes in Chrome'
+ },
+ 'memory:chrome:renderer_processes:reported_by_chrome:partition_alloc:effective_size':
+ {
+ value: [32, 320, 0, 5e5],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of partition_alloc in renderer ' +
+ 'processes in Chrome'
+ },
+ 'memory:chrome:renderer_processes:reported_by_chrome:v8:allocated_by_malloc:effective_size':
+ {
+ value: [0, 1 + 10 + 100, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of objects allocated by malloc for v8 ' +
+ 'in renderer processes in Chrome'
+ },
+ 'memory:chrome:renderer_processes:reported_by_chrome:v8:allocated_by_malloc:peak_size':
+ {
+ value: [0, 2 + 20 + 200, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'peak size of objects allocated by malloc for v8 ' +
+ 'in renderer processes in Chrome'
+ },
+ 'memory:chrome:renderer_processes:reported_by_chrome:v8:heap:allocated_objects_size':
+ {
+ value: [0, 36 + 6 + 1, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'size of all objects allocated by v8:heap in renderer ' +
+ 'processes in Chrome'
+ },
+ 'memory:chrome:renderer_processes:reported_by_chrome:code_and_metadata_size':
+ {
+ value: [16, 678, 0, 21 + 35],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total size of code and metadata reported by Chrome ' +
+ 'for renderer processes in Chrome'
+ },
+ 'memory:chrome:renderer_processes:reported_by_chrome:v8:code_and_metadata_size':
+ {
+ value: [16, 678, 0, 21 + 35],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'size of v8 code and metadata in renderer processes ' +
+ 'in Chrome'
+ },
+ 'memory:chrome:renderer_processes:reported_by_chrome:v8:heap:effective_size':
+ {
+ value: [0, 42 + 12 + 2, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of v8:heap in renderer processes ' +
+ 'in Chrome'
+ },
+ 'memory:chrome:renderer_processes:reported_by_chrome:v8:heap:code_space:allocated_objects_size':
+ {
+ value: [0, 1 + 1, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'size of all objects allocated by v8:heap:code_space ' +
+ 'in renderer processes in Chrome'
+ },
+ 'memory:chrome:renderer_processes:reported_by_chrome:v8:heap:code_space:effective_size':
+ {
+ value: [0, 2 + 2, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of v8:heap:code_space in renderer ' +
+ 'processes in Chrome'
+ },
+ 'memory:chrome:renderer_processes:reported_by_chrome:v8:heap:large_object_space:allocated_objects_size':
+ {
+ value: [0, 3 + 1, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'size of all objects allocated by ' +
+ 'v8:heap:large_object_space in renderer processes in Chrome'
+ },
+ 'memory:chrome:renderer_processes:reported_by_chrome:v8:heap:large_object_space:effective_size':
+ {
+ value: [0, 4 + 2, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of v8:heap:large_object_space in ' +
+ 'renderer processes in Chrome'
+ },
+ 'memory:chrome:renderer_processes:reported_by_chrome:v8:heap:map_space:allocated_objects_size':
+ {
+ value: [0, 5 + 1, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'size of all objects allocated by v8:heap:map_space ' +
+ 'in renderer processes in Chrome'
+ },
+ 'memory:chrome:renderer_processes:reported_by_chrome:v8:heap:map_space:effective_size':
+ {
+ value: [0, 6 + 2, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of v8:heap:map_space in renderer ' +
+ 'processes in Chrome'
+ },
+ 'memory:chrome:renderer_processes:reported_by_chrome:v8:heap:new_space:allocated_objects_size':
+ {
+ value: [0, 7 + 1, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'size of all objects allocated by v8:heap:new_space ' +
+ 'in renderer processes in Chrome'
+ },
+ 'memory:chrome:renderer_processes:reported_by_chrome:v8:heap:new_space:effective_size':
+ {
+ value: [0, 8 + 2, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of v8:heap:new_space in renderer ' +
+ 'processes in Chrome'
+ },
+ 'memory:chrome:renderer_processes:reported_by_chrome:v8:heap:old_space:allocated_objects_size':
+ {
+ value: [0, 9 + 1, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'size of all objects allocated by v8:heap:old_space ' +
+ 'in renderer processes in Chrome'
+ },
+ 'memory:chrome:renderer_processes:reported_by_chrome:v8:heap:old_space:effective_size':
+ {
+ value: [0, 10 + 2, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of v8:heap:old_space in renderer ' +
+ 'processes in Chrome'
+ },
+ 'memory:chrome:renderer_processes:reported_by_chrome:v8:effective_size': {
+ value: [0, 650 + 970, 0, 4e5],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of v8 in renderer processes in Chrome'
+ },
+ 'memory:chrome:renderer_processes:reported_by_chrome:v8:allocated_objects_size':
+ {
+ value: [0, (36 + 6 + 1) + 860, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'size of all objects allocated by v8 in renderer ' +
+ 'processes in Chrome'
+ },
+ 'memory:chrome:renderer_processes:reported_by_chrome:v8:peak_size':
+ {
+ value: [0, 2 + 20 + 200, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'peak size of v8 in renderer processes in Chrome'
+ },
+ 'memory:chrome:unknown_processes:process_count': {
+ value: [0, 1, 0, 0],
+ unit: count_smallerIsBetter,
+ description: 'total number of unknown processes in Chrome'
+ },
+ 'memory:chrome:unknown_processes:reported_by_chrome:effective_size': {
+ value: [0, 111, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total effective size reported by Chrome for unknown ' +
+ 'processes in Chrome'
+ },
+ 'memory:chrome:unknown_processes:reported_by_chrome:v8:effective_size': {
+ value: [0, 111, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of v8 in unknown processes in Chrome'
+ },
+ });
+
+
+ memoryMetricTest('newV8Values', function(model) {
+ const pRendererA = createProcessWithName(model, 'Renderer');
+ const gmd1 = addGlobalMemoryDump(model, {ts: 20});
+ const pmdRendererA = addProcessMemoryDump(gmd1, pRendererA, {ts: 20});
+ pmdRendererA.memoryAllocatorDumps = (function() {
+ const v8Dump = newAllocatorDump(pmdRendererA, 'v8',
+ {numerics: {size: 0}});
+ const isolateDumpA = addChildDump(v8Dump, 'main');
+ const heapDumpA = addChildDump(isolateDumpA, 'heap');
+ addChildDump(heapDumpA, 'code_space', {numerics: {
+ allocated_objects_size: 10,
+ size: 20
+ }});
+ const workersDump = addChildDump(v8Dump, 'workers');
+ const heapDumpB = addChildDump(workersDump, 'heap');
+ const codeSpaceDumpB = addChildDump(heapDumpB, 'code_space');
+ addChildDump(codeSpaceDumpB, 'isolate_0x1234', {numerics: {
+ allocated_objects_size: 1,
+ size: 2
+ }});
+ addChildDump(codeSpaceDumpB, 'isolate_0x5678', {numerics: {
+ allocated_objects_size: 3,
+ size: 4
+ }});
+ return [v8Dump];
+ })();
+ }, undefined /* opt_options */, {
+ 'memory:unknown_browser:all_processes:dump_count:detailed': {
+ value: [0],
+ unit: count_smallerIsBetter,
+ description: 'total number of detailed memory dumps added by an ' +
+ 'unknown browser to the trace'
+ },
+ 'memory:unknown_browser:all_processes:dump_count:light': {
+ value: [1],
+ unit: count_smallerIsBetter,
+ description: 'total number of light memory dumps added by an ' +
+ 'unknown browser to the trace'
+ },
+ 'memory:unknown_browser:all_processes:dump_count:background': {
+ value: [0],
+ unit: count_smallerIsBetter,
+ description: 'total number of background memory dumps added by an ' +
+ 'unknown browser to the trace'
+ },
+ 'memory:unknown_browser:all_processes:dump_count:heap_profiler': {
+ value: [0],
+ unit: count_smallerIsBetter,
+ description: 'total number of heap profiler memory dumps added by ' +
+ 'an unknown browser to the trace'
+ },
+ 'memory:unknown_browser:all_processes:dump_count': {
+ value: [1],
+ unit: count_smallerIsBetter,
+ description: 'total number of all memory dumps added by an unknown ' +
+ 'browser to the trace'
+ },
+ 'memory:unknown_browser:all_processes:process_count': {
+ value: [1],
+ unit: count_smallerIsBetter,
+ description: 'total number of all processes in an unknown browser'
+ },
+ 'memory:unknown_browser:all_processes:reported_by_chrome:effective_size': {
+ value: [26],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total effective size reported by Chrome for all ' +
+ 'processes in an unknown browser'
+ },
+ 'memory:unknown_browser:all_processes:reported_by_chrome:allocated_objects_size':
+ {
+ value: [14],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total size of all allocated objects reported by Chrome ' +
+ 'for all processes in an unknown browser'
+ },
+ 'memory:unknown_browser:all_processes:reported_by_chrome:v8:heap:allocated_objects_size':
+ {
+ value: [14],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'size of all objects allocated by v8:heap in all ' +
+ 'processes in an unknown browser'
+ },
+ 'memory:unknown_browser:all_processes:reported_by_chrome:v8:heap:effective_size':
+ {
+ value: [26],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of v8:heap in all processes in an ' +
+ 'unknown browser'
+ },
+ 'memory:unknown_browser:all_processes:reported_by_chrome:v8:heap:code_space:allocated_objects_size':
+ {
+ value: [14],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'size of all objects allocated by v8:heap:code_space ' +
+ 'in all processes in an unknown browser'
+ },
+ 'memory:unknown_browser:all_processes:reported_by_chrome:v8:heap:code_space:effective_size':
+ {
+ value: [26],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of v8:heap:code_space in all ' +
+ 'processes in an unknown browser'
+ },
+ 'memory:unknown_browser:all_processes:reported_by_chrome:v8:effective_size':
+ {
+ value: [26],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of v8 in all processes in an unknown browser'
+ },
+ 'memory:unknown_browser:all_processes:reported_by_chrome:v8:allocated_objects_size':
+ {
+ value: [14],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'size of all objects allocated by v8 in all processes ' +
+ 'in an unknown browser'
+ },
+ 'memory:unknown_browser:renderer_processes:process_count': {
+ value: [1],
+ unit: count_smallerIsBetter,
+ description: 'total number of renderer processes in an unknown browser'
+ },
+ 'memory:unknown_browser:renderer_processes:reported_by_chrome:effective_size':
+ {
+ value: [26],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total effective size reported by Chrome for renderer ' +
+ 'processes in an unknown browser'
+ },
+ 'memory:unknown_browser:renderer_processes:reported_by_chrome:allocated_objects_size':
+ {
+ value: [14],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total size of all allocated objects reported by Chrome ' +
+ 'for renderer processes in an unknown browser'
+ },
+ 'memory:unknown_browser:renderer_processes:reported_by_chrome:v8:heap:allocated_objects_size':
+ {
+ value: [14],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'size of all objects allocated by v8:heap in renderer ' +
+ 'processes in an unknown browser'
+ },
+ 'memory:unknown_browser:renderer_processes:reported_by_chrome:v8:heap:effective_size':
+ {
+ value: [26],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of v8:heap in renderer processes in ' +
+ 'an unknown browser'
+ },
+ 'memory:unknown_browser:renderer_processes:reported_by_chrome:v8:heap:code_space:allocated_objects_size':
+ {
+ value: [14],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'size of all objects allocated by v8:heap:code_space ' +
+ 'in renderer processes in an unknown browser'
+ },
+ 'memory:unknown_browser:renderer_processes:reported_by_chrome:v8:heap:code_space:effective_size':
+ {
+ value: [26],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of v8:heap:code_space in renderer ' +
+ 'processes in an unknown browser'
+ },
+ 'memory:unknown_browser:renderer_processes:reported_by_chrome:v8:effective_size':
+ {
+ value: [26],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of v8 in renderer processes in an ' +
+ 'unknown browser'
+ },
+ 'memory:unknown_browser:renderer_processes:reported_by_chrome:v8:allocated_objects_size':
+ {
+ value: [14],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'size of all objects allocated by v8 in renderer ' +
+ 'processes in an unknown browser'
+ }
+ });
+
+
+ memoryMetricTest('detailedValues_unknownBrowser', function(model) {
+ const pBrowser = createProcessWithName(model, 'Browser');
+ const pRendererA = createProcessWithName(model, 'Renderer');
+ const pRendererB = createProcessWithName(model, 'Renderer');
+ const pRendererC = createProcessWithName(model, 'Renderer');
+ const pGpu = createProcessWithName(model, 'GPU Process');
+
+ // Timestamp 1.
+ const gmd1 = addGlobalMemoryDump(model, {ts: 10, levelOfDetail: DETAILED});
+ const pmdBrowser1 = addProcessMemoryDump(gmd1, pBrowser, {ts: 9});
+ pmdBrowser1.vmRegions = VMRegionClassificationNode.fromRegions([
+ new VMRegion(0xABCD, 128, 0, '/dev/ashmem/dalvik-non moving space',
+ {privateDirtyResident: 8}),
+ new VMRegion(0xFBCD, 64, 5, '/data/chrome-WXYZ/base.apk',
+ { privateCleanResident: 8, sharedCleanResident: 4,
+ proportionalResident: 10}),
+ new VMRegion(0xFCCD, 64, 5, '/data/chrome-WXYZ/out/arm/base.odex',
+ { privateCleanResident: 0, sharedCleanResident: 6,
+ proportionalResident: 5})
+ ]);
+ pmdBrowser1.heapDumps = (function() {
+ const mallocDump = new tr.model.HeapDump(pmdBrowser1, 'malloc');
+ mallocDump.addEntry(undefined, undefined, 100, 500);
+ return {'malloc': mallocDump};
+ })();
+
+ const pmdRendererA1 = addProcessMemoryDump(gmd1, pRendererA, {ts: 10});
+ pmdRendererA1.vmRegions = VMRegionClassificationNode.fromRegions([
+ new VMRegion(0xEF01, 256, 0, '[anon:libc_malloc]',
+ {privateDirtyResident: 17}),
+ new VMRegion(0xFBCD, 64, 5, '/data/chrome-WXYZ/base.apk',
+ { privateCleanResident: 3, sharedCleanResident: 4,
+ proportionalResident: 5})
+ ]);
+ const pmdRendererB1 = addProcessMemoryDump(gmd1, pRendererB, {ts: 11});
+ pmdRendererB1.vmRegions = VMRegionClassificationNode.fromRegions([
+ new VMRegion(0x2345, 512, 0, '[heap]',
+ {proportionalResident: 67, privateDirtyResident: 34}),
+ new VMRegion(0x7f29, 128, 0, '[stack:25451]',
+ {proportionalResident: 20, privateDirtyResident: 16})
+ ]);
+ const pmdGpu1 = addProcessMemoryDump(gmd1, pGpu, {ts: 10});
+ pmdGpu1.memoryAllocatorDumps = (function() {
+ const gpuDump = newAllocatorDump(pmdGpu1, 'gpu');
+ const memtrackDump = addChildDump(gpuDump, 'android_memtrack');
+ addChildDump(memtrackDump, 'gl', {numerics: {memtrack_pss: 100}});
+ addChildDump(memtrackDump, 'graphics', {numerics: {memtrack_pss: 200}});
+ return [gpuDump];
+ })();
+
+ // Timestamp 2 (light global memory dump, so it should be skipped for
+ // mmaps_* values).
+ const gmd2 = addGlobalMemoryDump(model, {ts: 20, levelOfDetail: LIGHT});
+ const pmdBrowser2 = addProcessMemoryDump(gmd2, pBrowser, {ts: 18});
+ pmdBrowser2.vmRegions = VMRegionClassificationNode.fromRegions([
+ new VMRegion(0x999, 999, 999, '/dev/ashmem/dalvik-main space',
+ {proportionalResident: 999})
+ ]);
+ const pmdRendererB2 = addProcessMemoryDump(gmd2, pRendererB, {ts: 21});
+ const pmdRendererC2 = addProcessMemoryDump(gmd2, pRendererC, {ts: 22});
+ const pmdGpu2 = addProcessMemoryDump(gmd2, pGpu, {ts: 20});
+ pmdGpu2.memoryAllocatorDumps = (function() {
+ const gpuDump = newAllocatorDump(pmdGpu2, 'gpu');
+ const memtrackDump = addChildDump(gpuDump, 'android_memtrack');
+ addChildDump(memtrackDump, 'gl', {numerics: {memtrack_pss: 12345}});
+ return [gpuDump];
+ })();
+
+ // Timestamp 3.
+ const gmd3 = addGlobalMemoryDump(model, {ts: 30, levelOfDetail: DETAILED});
+ const pmdBrowser3 = addProcessMemoryDump(gmd3, pBrowser, {ts: 30});
+ pmdBrowser3.vmRegions = VMRegionClassificationNode.fromRegions([
+ new VMRegion(0xABCD, 1024, 0, '/dev/ashmem/dalvik-non moving space',
+ {proportionalResident: 3, privateDirtyResident: 80})
+ ]);
+ const pmdRendererA3 = addProcessMemoryDump(gmd3, pRendererA, {ts: 29});
+ // Intentionally pmdRendererA3.vmRegions undefined.
+ const pmdRendererC3 = addProcessMemoryDump(gmd3, pRendererC, {ts: 31});
+ pmdRendererC3.vmRegions = VMRegionClassificationNode.fromRegions([
+ new VMRegion(0x2345, 2048, 0, '/no/matching/category',
+ {proportionalResident: 200}),
+ new VMRegion(0x2345, 2048, 0, '/dev/ashmem', {proportionalResident: 500}),
+ ]);
+ const pmdGpu3 = addProcessMemoryDump(gmd3, pGpu, {ts: 30});
+ pmdGpu3.memoryAllocatorDumps = (function() {
+ const gpuDump = newAllocatorDump(pmdGpu3, 'gpu',
+ {numerics: {memtrack_pss: 6000 /* ignored */}});
+ const memtrackDump = addChildDump(gpuDump, 'android_memtrack',
+ {numerics: {memtrack_pss: 5000 /* ignored */}});
+ addChildDump(memtrackDump, 'gl', {numerics: {memtrack_pss: 3000}});
+ addChildDump(memtrackDump, 'graphics', {numerics: {ignored: 2000}});
+ addChildDump(memtrackDump, 'gfx', {numerics: {memtrack_pss: 1000}});
+ return [gpuDump];
+ })();
+ pmdGpu3.vmRegions = VMRegionClassificationNode.fromRegions([
+ new VMRegion(0xCDCD, 4096, 0, '/dev/ashmem/dalvik-zygote space',
+ {proportionalResident: 150, privateDirtyResident: 90})
+ ]);
+
+ // Timestamp 4.
+ const gmd4 = addGlobalMemoryDump(model, {ts: 40, levelOfDetail: DETAILED});
+ const pmdBrowser4 = addProcessMemoryDump(gmd4, pBrowser, {ts: 40});
+ }, undefined /* opt_options */, {
+ 'memory:unknown_browser:all_processes:dump_count:detailed': {
+ value: [3],
+ unit: count_smallerIsBetter,
+ description: 'total number of detailed memory dumps added by an ' +
+ 'unknown browser to the trace'
+ },
+ 'memory:unknown_browser:all_processes:dump_count:light': {
+ value: [1],
+ unit: count_smallerIsBetter,
+ description: 'total number of light memory dumps added by an unknown ' +
+ 'browser to the trace'
+ },
+ 'memory:unknown_browser:all_processes:dump_count:background': {
+ value: [0],
+ unit: count_smallerIsBetter,
+ description: 'total number of background memory dumps added by an ' +
+ 'unknown browser to the trace'
+ },
+ 'memory:unknown_browser:all_processes:dump_count:heap_profiler': {
+ value: [1],
+ unit: count_smallerIsBetter,
+ description: 'total number of heap profiler memory dumps added by an ' +
+ 'unknown browser to the trace'
+ },
+ 'memory:unknown_browser:all_processes:dump_count': {
+ value: [4],
+ unit: count_smallerIsBetter,
+ description: 'total number of all memory dumps added by an unknown ' +
+ 'browser to the trace'
+ },
+ 'memory:unknown_browser:all_processes:process_count': {
+ value: [4, 4, 4, 1],
+ unit: count_smallerIsBetter,
+ description: 'total number of all processes in an unknown browser'
+ },
+ 'memory:unknown_browser:all_processes:reported_by_os:gpu_memory:proportional_resident_size':
+ {
+ value: [100 + 200, 3000 + 1000, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total proportional resident size (PSS) of GPU memory ' +
+ '(Android memtrack) used by all processes in an unknown browser'
+ },
+ 'memory:unknown_browser:all_processes:reported_by_os:java_base_clean_resident':
+ {
+ value: [6, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total java base odex and vdex total clean resident size ' +
+ 'reported by the OS for all processes in an unknown browser'
+ },
+ 'memory:unknown_browser:all_processes:reported_by_os:java_base_pss':
+ {
+ value: [5, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total java base odex and vdex proportional resident size ' +
+ 'reported by the OS for all processes in an unknown browser'
+ },
+ 'memory:unknown_browser:all_processes:reported_by_os:native_library_private_clean_resident':
+ {
+ value: [8, 3, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total native library private clean resident size ' +
+ 'reported by the OS for all processes in an unknown browser'
+ },
+ 'memory:unknown_browser:all_processes:reported_by_os:native_library_proportional_resident':
+ {
+ value: [10, 5, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total native library proportional resident size ' +
+ 'reported by the OS for all processes in an unknown browser'
+ },
+ 'memory:unknown_browser:all_processes:reported_by_os:native_library_shared_clean_resident':
+ {
+ value: [4, 4, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total native library shared clean resident size ' +
+ 'reported by the OS for all processes in an unknown browser'
+ },
+ 'memory:unknown_browser:all_processes:reported_by_os:gpu_memory:gfx:proportional_resident_size':
+ {
+ value: [0, 1000, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of the gfx Android ' +
+ 'memtrack component in all processes in an unknown browser'
+ },
+ 'memory:unknown_browser:all_processes:reported_by_os:gpu_memory:gl:proportional_resident_size':
+ {
+ value: [100, 3000, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of the gl Android ' +
+ 'memtrack component in all processes in an unknown browser'
+ },
+ 'memory:unknown_browser:all_processes:reported_by_os:gpu_memory:graphics:proportional_resident_size':
+ {
+ value: [200, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of the graphics ' +
+ 'Android memtrack component in all processes in an unknown ' +
+ 'browser'
+ },
+ 'memory:unknown_browser:all_processes:reported_by_os:private_dirty_size': {
+ value: [17 + 34 + 16 + 8, 80 + 90, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total private dirty size reported by the OS for all ' +
+ 'processes in an unknown browser'
+ },
+ 'memory:unknown_browser:all_processes:reported_by_os:proportional_resident_size':
+ {
+ value: [67 + 20 + 100 + 200, 700 + 3 + 1000 + 150 + 3000, 15 + 5],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total proportional resident size (PSS) reported by ' +
+ 'the OS for all processes in an unknown browser'
+ },
+ 'memory:unknown_browser:all_processes:reported_by_os:system_memory:private_dirty_size':
+ {
+ value: [17 + 34 + 16 + 8, 80 + 90, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total private dirty size of system memory (RAM) used ' +
+ 'by all processes in an unknown browser'
+ },
+ 'memory:unknown_browser:all_processes:reported_by_os:system_memory:proportional_resident_size':
+ {
+ value: [67 + 20, 700 + 3 + 150, 15 + 5],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total proportional resident size (PSS) of system ' +
+ 'memory (RAM) used by all processes in an unknown browser'
+ },
+ 'memory:unknown_browser:all_processes:reported_by_os:system_memory:ashmem:private_dirty_size':
+ {
+ value: [0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of ashmem in all processes in an ' +
+ 'unknown browser'
+ },
+ 'memory:unknown_browser:all_processes:reported_by_os:system_memory:ashmem:proportional_resident_size':
+ {
+ value: [0, 500, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of ashmem in all ' +
+ 'processes in an unknown browser'
+ },
+ 'memory:unknown_browser:all_processes:reported_by_os:system_memory:java_base_clean_resident':
+ {
+ value: [6, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total java base odex and vdex total clean resident size ' +
+ 'of system memory (RAM) used by all processes in an unknown ' +
+ 'browser'
+ },
+ 'memory:unknown_browser:all_processes:reported_by_os:system_memory:java_base_pss':
+ {
+ value: [5, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total java base odex and vdex proportional resident size ' +
+ 'of system memory (RAM) used by all processes in an unknown ' +
+ 'browser'
+ },
+ 'memory:unknown_browser:all_processes:reported_by_os:system_memory:java_heap:private_dirty_size':
+ {
+ value: [8, 80 + 90, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of the Java heap in all processes ' +
+ 'in an unknown browser'
+ },
+ 'memory:unknown_browser:all_processes:reported_by_os:system_memory:java_heap:proportional_resident_size':
+ {
+ value: [0, 3 + 150, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of the Java heap in ' +
+ 'all processes in an unknown browser'
+ },
+ 'memory:unknown_browser:all_processes:reported_by_os:system_memory:native_heap:private_dirty_size':
+ {
+ value: [17 + 34, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of the native heap in all ' +
+ 'processes in an unknown browser'
+ },
+ 'memory:unknown_browser:all_processes:reported_by_os:system_memory:native_heap:proportional_resident_size':
+ {
+ value: [67, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of the native heap ' +
+ 'in all processes in an unknown browser'
+ },
+ 'memory:unknown_browser:all_processes:reported_by_os:system_memory:native_library_private_clean_resident':
+ {
+ value: [8, 3, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total native library private clean resident size of ' +
+ 'system memory (RAM) used by all processes in an unknown browser'
+ },
+ 'memory:unknown_browser:all_processes:reported_by_os:system_memory:native_library_proportional_resident':
+ {
+ value: [10, 5, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total native library proportional resident size of ' +
+ 'system memory (RAM) used by all processes in an unknown browser'
+ },
+ 'memory:unknown_browser:all_processes:reported_by_os:system_memory:native_library_shared_clean_resident':
+ {
+ value: [4, 4, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total native library shared clean resident size of ' +
+ 'system memory (RAM) used by all processes in an unknown browser'
+ },
+ 'memory:unknown_browser:all_processes:reported_by_os:system_memory:stack:private_dirty_size':
+ {
+ value: [16, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of the thread stacks in all ' +
+ 'processes in an unknown browser'
+ },
+ 'memory:unknown_browser:all_processes:reported_by_os:system_memory:stack:proportional_resident_size':
+ {
+ value: [20, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of the thread stacks ' +
+ 'in all processes in an unknown browser'
+ },
+ 'memory:unknown_browser:browser_process:process_count': {
+ value: [1, 1, 1, 1],
+ unit: count_smallerIsBetter,
+ description: 'total number of browser processes in an unknown browser'
+ },
+ 'memory:unknown_browser:browser_process:reported_by_os:java_base_clean_resident':
+ {
+ value: [6, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total java base odex and vdex total clean resident size ' +
+ 'reported by the OS for the browser process in an unknown browser'
+ },
+ 'memory:unknown_browser:browser_process:reported_by_os:java_base_pss':
+ {
+ value: [5, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total java base odex and vdex proportional resident size ' +
+ 'reported by the OS for the browser process in an unknown browser'
+ },
+ 'memory:unknown_browser:browser_process:reported_by_os:native_library_private_clean_resident':
+ {
+ value: [8, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total native library private clean resident size ' +
+ 'reported by the OS for the browser process in an unknown browser'
+ },
+ 'memory:unknown_browser:browser_process:reported_by_os:native_library_proportional_resident':
+ {
+ value: [10, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total native library proportional resident size ' +
+ 'reported by the OS for the browser process in an unknown browser'
+ },
+ 'memory:unknown_browser:browser_process:reported_by_os:native_library_shared_clean_resident':
+ {
+ value: [4, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total native library shared clean resident size ' +
+ 'reported by the OS for the browser process in an unknown browser'
+ },
+ 'memory:unknown_browser:browser_process:reported_by_os:private_dirty_size':
+ {
+ value: [8, 80, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total private dirty size reported by the OS for the ' +
+ 'browser process in an unknown browser'
+ },
+ 'memory:unknown_browser:browser_process:reported_by_os:proportional_resident_size':
+ {
+ value: [15, 3, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total proportional resident size (PSS) reported by ' +
+ 'the OS for the browser process in an unknown browser'
+ },
+ 'memory:unknown_browser:browser_process:reported_by_os:system_memory:private_dirty_size':
+ {
+ value: [8, 80, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total private dirty size of system memory (RAM) used ' +
+ 'by the browser process in an unknown browser'
+ },
+ 'memory:unknown_browser:browser_process:reported_by_os:system_memory:proportional_resident_size':
+ {
+ value: [13, 5, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total proportional resident size (PSS) of system ' +
+ 'memory (RAM) used by the browser process in an unknown browser'
+ },
+ 'memory:unknown_browser:browser_process:reported_by_os:system_memory:ashmem:private_dirty_size':
+ {
+ value: [0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of ashmem in the browser process ' +
+ 'in an unknown browser'
+ },
+ 'memory:unknown_browser:browser_process:reported_by_os:system_memory:ashmem:proportional_resident_size':
+ {
+ value: [0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of ashmem in the ' +
+ 'browser process in an unknown browser'
+ },
+ 'memory:unknown_browser:browser_process:reported_by_os:system_memory:java_base_clean_resident':
+ {
+ value: [6, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total java base odex and vdex total clean resident size ' +
+ 'of system memory (RAM) used by the browser process in an ' +
+ 'unknown browser'
+ },
+ 'memory:unknown_browser:browser_process:reported_by_os:system_memory:java_base_pss':
+ {
+ value: [5, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total java base odex and vdex proportional resident size ' +
+ 'of system memory (RAM) used by the browser process in an ' +
+ 'unknown browser'
+ },
+ 'memory:unknown_browser:browser_process:reported_by_os:system_memory:java_heap:private_dirty_size':
+ {
+ value: [8, 80, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of the Java heap in the browser ' +
+ 'process in an unknown browser'
+ },
+ 'memory:unknown_browser:browser_process:reported_by_os:system_memory:java_heap:proportional_resident_size':
+ {
+ value: [0, 3, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of the Java heap in ' +
+ 'the browser process in an unknown browser'
+ },
+ 'memory:unknown_browser:browser_process:reported_by_os:system_memory:native_heap:private_dirty_size':
+ {
+ value: [0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of the native heap in the browser ' +
+ 'process in an unknown browser'
+ },
+ 'memory:unknown_browser:browser_process:reported_by_os:system_memory:native_heap:proportional_resident_size':
+ {
+ value: [0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of the native heap ' +
+ 'in the browser process in an unknown browser'
+ },
+ 'memory:unknown_browser:browser_process:reported_by_os:system_memory:native_library_private_clean_resident':
+ {
+ value: [8, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total native library private clean resident size of ' +
+ 'system memory (RAM) used by the browser process in an ' +
+ 'unknown browser'
+ },
+ 'memory:unknown_browser:browser_process:reported_by_os:system_memory:native_library_proportional_resident':
+ {
+ value: [10, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total native library proportional resident size of ' +
+ 'system memory (RAM) used by the browser process in an ' +
+ 'unknown browser'
+ },
+ 'memory:unknown_browser:browser_process:reported_by_os:system_memory:native_library_shared_clean_resident':
+ {
+ value: [4, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total native library shared clean resident size of ' +
+ 'system memory (RAM) used by the browser process in an ' +
+ 'unknown browser'
+ },
+ 'memory:unknown_browser:browser_process:reported_by_os:system_memory:stack:private_dirty_size':
+ {
+ value: [0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of the thread stacks in the browser ' +
+ 'process in an unknown browser'
+ },
+ 'memory:unknown_browser:browser_process:reported_by_os:system_memory:stack:proportional_resident_size':
+ {
+ value: [0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of the thread stacks ' +
+ 'in the browser process in an unknown browser'
+ },
+ 'memory:unknown_browser:gpu_process:process_count': {
+ value: [1, 1, 1, 0],
+ unit: count_smallerIsBetter,
+ description: 'total number of GPU processes in an unknown browser'
+ },
+ 'memory:unknown_browser:gpu_process:reported_by_os:proportional_resident_size':
+ {
+ value: [100 + 200, 3000 + 1000 + 150, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total proportional resident size (PSS) reported by ' +
+ 'the OS for the GPU process in an unknown browser'
+ },
+ 'memory:unknown_browser:gpu_process:reported_by_os:private_dirty_size': {
+ value: [0, 90, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total private dirty size reported by the OS for the GPU ' +
+ 'process in an unknown browser'
+ },
+ 'memory:unknown_browser:gpu_process:reported_by_os:gpu_memory:proportional_resident_size':
+ {
+ value: [100 + 200, 3000 + 1000, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total proportional resident size (PSS) of GPU memory ' +
+ '(Android memtrack) used by the GPU process in an unknown browser'
+ },
+ 'memory:unknown_browser:gpu_process:reported_by_os:gpu_memory:gfx:proportional_resident_size':
+ {
+ value: [0, 1000, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of the gfx Android ' +
+ 'memtrack component in the GPU process in an unknown browser'
+ },
+ 'memory:unknown_browser:gpu_process:reported_by_os:gpu_memory:gl:proportional_resident_size':
+ {
+ value: [100, 3000, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of the gl Android ' +
+ 'memtrack component in the GPU process in an unknown browser'
+ },
+ 'memory:unknown_browser:gpu_process:reported_by_os:gpu_memory:graphics:proportional_resident_size':
+ {
+ value: [200, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of the graphics ' +
+ 'Android memtrack component in the GPU process in an unknown ' +
+ 'browser'
+ },
+ 'memory:unknown_browser:gpu_process:reported_by_os:system_memory:private_dirty_size':
+ {
+ value: [0, 90, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total private dirty size of system memory (RAM) used ' +
+ 'by the GPU process in an unknown browser'
+ },
+ 'memory:unknown_browser:gpu_process:reported_by_os:system_memory:proportional_resident_size':
+ {
+ value: [0, 150, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total proportional resident size (PSS) of system ' +
+ 'memory (RAM) used by the GPU process in an unknown browser'
+ },
+ 'memory:unknown_browser:gpu_process:reported_by_os:system_memory:ashmem:private_dirty_size':
+ {
+ value: [0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of ashmem in the GPU process in ' +
+ 'an unknown browser'
+ },
+ 'memory:unknown_browser:gpu_process:reported_by_os:system_memory:ashmem:proportional_resident_size':
+ {
+ value: [0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of ashmem in the ' +
+ 'GPU process in an unknown browser'
+ },
+ 'memory:unknown_browser:gpu_process:reported_by_os:system_memory:java_heap:private_dirty_size':
+ {
+ value: [0, 90, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of the Java heap in the GPU ' +
+ 'process in an unknown browser'
+ },
+ 'memory:unknown_browser:gpu_process:reported_by_os:system_memory:java_heap:proportional_resident_size':
+ {
+ value: [0, 150, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of the Java heap in ' +
+ 'the GPU process in an unknown browser'
+ },
+ 'memory:unknown_browser:gpu_process:reported_by_os:system_memory:native_heap:private_dirty_size':
+ {
+ value: [0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of the native heap in the GPU ' +
+ 'process in an unknown browser'
+ },
+ 'memory:unknown_browser:gpu_process:reported_by_os:system_memory:native_heap:proportional_resident_size':
+ {
+ value: [0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of the native heap ' +
+ 'in the GPU process in an unknown browser'
+ },
+ 'memory:unknown_browser:gpu_process:reported_by_os:system_memory:stack:private_dirty_size':
+ {
+ value: [0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of the thread stacks in the GPU ' +
+ 'process in an unknown browser'
+ },
+ 'memory:unknown_browser:gpu_process:reported_by_os:system_memory:stack:proportional_resident_size':
+ {
+ value: [0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of the thread stacks ' +
+ 'in the GPU process in an unknown browser'
+ },
+ 'memory:unknown_browser:renderer_processes:process_count': {
+ value: [2, 2, 2, 0],
+ unit: count_smallerIsBetter,
+ description: 'total number of renderer processes in an unknown browser'
+ },
+ 'memory:unknown_browser:renderer_processes:reported_by_os:native_library_private_clean_resident':
+ {
+ value: [3, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total native library private clean resident size ' +
+ 'reported by the OS for renderer processes in an unknown browser'
+ },
+ 'memory:unknown_browser:renderer_processes:reported_by_os:native_library_proportional_resident':
+ {
+ value: [5, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total native library proportional resident size ' +
+ 'reported by the OS for renderer processes in an unknown browser'
+ },
+ 'memory:unknown_browser:renderer_processes:reported_by_os:native_library_shared_clean_resident':
+ {
+ value: [4, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total native library shared clean resident size ' +
+ 'reported by the OS for renderer processes in an unknown browser'
+ },
+ 'memory:unknown_browser:renderer_processes:reported_by_os:private_dirty_size':
+ {
+ value: [17 + 34 + 16, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total private dirty size reported by the OS for ' +
+ 'renderer processes in an unknown browser'
+ },
+ 'memory:unknown_browser:renderer_processes:reported_by_os:proportional_resident_size':
+ {
+ value: [67 + 20, 700, 5],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total proportional resident size (PSS) reported by ' +
+ 'the OS for renderer processes in an unknown browser'
+ },
+ 'memory:unknown_browser:renderer_processes:reported_by_os:system_memory:private_dirty_size':
+ {
+ value: [17 + 34 + 16, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total private dirty size of system memory (RAM) used ' +
+ 'by renderer processes in an unknown browser'
+ },
+ 'memory:unknown_browser:renderer_processes:reported_by_os:system_memory:proportional_resident_size':
+ {
+ value: [67 + 20, 700, 5],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total proportional resident size (PSS) of system ' +
+ 'memory (RAM) used by renderer processes in an unknown browser'
+ },
+ 'memory:unknown_browser:renderer_processes:reported_by_os:system_memory:ashmem:private_dirty_size':
+ {
+ value: [0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of ashmem in renderer processes ' +
+ 'in an unknown browser'
+ },
+ 'memory:unknown_browser:renderer_processes:reported_by_os:system_memory:ashmem:proportional_resident_size':
+ {
+ value: [0, 500, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of ashmem in ' +
+ 'renderer processes in an unknown browser'
+ },
+ 'memory:unknown_browser:renderer_processes:reported_by_os:system_memory:java_heap:private_dirty_size':
+ {
+ value: [0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of the Java heap in renderer ' +
+ 'processes in an unknown browser'
+ },
+ 'memory:unknown_browser:renderer_processes:reported_by_os:system_memory:java_heap:proportional_resident_size':
+ {
+ value: [0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of the Java heap in ' +
+ 'renderer processes in an unknown browser'
+ },
+ 'memory:unknown_browser:renderer_processes:reported_by_os:system_memory:native_heap:private_dirty_size':
+ {
+ value: [17 + 34, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of the native heap in renderer ' +
+ 'processes in an unknown browser'
+ },
+ 'memory:unknown_browser:renderer_processes:reported_by_os:system_memory:native_heap:proportional_resident_size':
+ {
+ value: [67, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of the native heap ' +
+ 'in renderer processes in an unknown browser'
+ },
+ 'memory:unknown_browser:renderer_processes:reported_by_os:system_memory:native_library_private_clean_resident':
+ {
+ value: [3, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total native library private clean resident size of ' +
+ 'system memory (RAM) used by renderer processes in an ' +
+ 'unknown browser'
+ },
+ 'memory:unknown_browser:renderer_processes:reported_by_os:system_memory:native_library_proportional_resident':
+ {
+ value: [5, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total native library proportional resident size of ' +
+ 'system memory (RAM) used by renderer processes in an ' +
+ 'unknown browser'
+ },
+ 'memory:unknown_browser:renderer_processes:reported_by_os:system_memory:native_library_shared_clean_resident':
+ {
+ value: [4, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total native library shared clean resident size of ' +
+ 'system memory (RAM) used by renderer processes in an ' +
+ 'unknown browser'
+ },
+ 'memory:unknown_browser:renderer_processes:reported_by_os:system_memory:stack:private_dirty_size':
+ {
+ value: [16, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of the thread stacks in renderer ' +
+ 'processes in an unknown browser'
+ },
+ 'memory:unknown_browser:renderer_processes:reported_by_os:system_memory:stack:proportional_resident_size':
+ {
+ value: [20, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of the thread stacks ' +
+ 'in renderer processes in an unknown browser'
+ }
+ });
+
+ memoryMetricTest('combined_chrome', function(model) {
+ const pBrowser = createChromeBrowserProcess(model);
+
+ // Timestamp 1.
+ const gmd1 = addGlobalMemoryDump(model, {ts: 10, levelOfDetail: DETAILED});
+ const pmdBrowser1 = addProcessMemoryDump(gmd1, pBrowser, {ts: 10});
+ pmdBrowser1.vmRegions = VMRegionClassificationNode.fromRegions([
+ new VMRegion(0xABCD, 128, 0, '/dev/ashmem/dalvik-non moving space',
+ {privateDirtyResident: 100})
+ ]);
+
+ // Timestamp 2 (light global memory dump, so it should be skipped for
+ // mmaps_* values).
+ const gmd2 = addGlobalMemoryDump(model, {ts: 20, levelOfDetail: LIGHT});
+ const pmdBrowser2 = addProcessMemoryDump(gmd2, pBrowser, {ts: 20});
+ pmdBrowser2.memoryAllocatorDumps = [
+ newAllocatorDump(pmdBrowser2, 'malloc', {numerics: {size: 32}})
+ ];
+
+ // Timestamp 3.
+ const gmd3 = addGlobalMemoryDump(model, {ts: 30, levelOfDetail: DETAILED});
+ const pmdBrowser3 = addProcessMemoryDump(gmd3, pBrowser, {ts: 30});
+ pmdBrowser3.memoryAllocatorDumps = [
+ newAllocatorDump(pmdBrowser3, 'malloc', {numerics: {size: 48}})
+ ];
+ pmdBrowser3.vmRegions = VMRegionClassificationNode.fromRegions([
+ new VMRegion(0xABCD, 1024, 0, '/dev/ashmem/dalvik-non moving space',
+ {privateDirtyResident: 150})
+ ]);
+ }, {} /* opt_options */, {
+ 'memory:chrome:all_processes:dump_count:detailed': {
+ value: [2],
+ unit: count_smallerIsBetter,
+ description: 'total number of detailed memory dumps added by Chrome to ' +
+ 'the trace'
+ },
+ 'memory:chrome:all_processes:dump_count:light': {
+ value: [1],
+ unit: count_smallerIsBetter,
+ description: 'total number of light memory dumps added by Chrome to ' +
+ 'the trace'
+ },
+ 'memory:chrome:all_processes:dump_count:background': {
+ value: [0],
+ unit: count_smallerIsBetter,
+ description: 'total number of background memory dumps added by Chrome ' +
+ 'to the trace'
+ },
+ 'memory:chrome:all_processes:dump_count:heap_profiler': {
+ value: [0],
+ unit: count_smallerIsBetter,
+ description: 'total number of heap profiler memory dumps added by ' +
+ 'Chrome to the trace'
+ },
+ 'memory:chrome:all_processes:dump_count': {
+ value: [3],
+ unit: count_smallerIsBetter,
+ description: 'total number of all memory dumps added by Chrome to the ' +
+ 'trace'
+ },
+ 'memory:chrome:all_processes:process_count': {
+ value: [1, 1, 1],
+ unit: count_smallerIsBetter,
+ description: 'total number of all processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_chrome:effective_size': {
+ value: [0, 32, 48],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total effective size reported by Chrome for all ' +
+ 'processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_chrome:malloc:effective_size': {
+ value: [0, 32, 48],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of malloc in all processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_os:private_dirty_size': {
+ value: [100, 150],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total private dirty size reported by the OS for all ' +
+ 'processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_os:proportional_resident_size': {
+ value: [0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total proportional resident size (PSS) reported by the ' +
+ 'OS for all processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_os:system_memory:ashmem:private_dirty_size':
+ {
+ value: [0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of ashmem in all processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_os:system_memory:ashmem:proportional_resident_size':
+ {
+ value: [0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of ashmem in all ' +
+ 'processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_os:system_memory:java_heap:private_dirty_size':
+ {
+ value: [100, 150],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of the Java heap in all processes ' +
+ 'in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_os:system_memory:java_heap:proportional_resident_size':
+ {
+ value: [0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of the Java heap in ' +
+ 'all processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_os:system_memory:native_heap:private_dirty_size':
+ {
+ value: [0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of the native heap in all ' +
+ 'processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_os:system_memory:native_heap:proportional_resident_size':
+ {
+ value: [0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of the native heap ' +
+ 'in all processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_os:system_memory:stack:private_dirty_size':
+ {
+ value: [0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of the thread stacks in all ' +
+ 'processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_os:system_memory:stack:proportional_resident_size':
+ {
+ value: [0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of the thread stacks ' +
+ 'in all processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_os:system_memory:private_dirty_size':
+ {
+ value: [100, 150],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total private dirty size of system memory (RAM) used ' +
+ 'by all processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_os:system_memory:proportional_resident_size':
+ {
+ value: [0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total proportional resident size (PSS) of system ' +
+ 'memory (RAM) used by all processes in Chrome'
+ },
+ 'memory:chrome:browser_process:process_count': {
+ value: [1, 1, 1],
+ unit: count_smallerIsBetter,
+ description: 'total number of browser processes in Chrome'
+ },
+ 'memory:chrome:browser_process:reported_by_chrome:effective_size': {
+ value: [0, 32, 48],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total effective size reported by Chrome for the browser ' +
+ 'process in Chrome'
+ },
+ 'memory:chrome:browser_process:reported_by_chrome:malloc:effective_size': {
+ value: [0, 32, 48],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of malloc in the browser process in Chrome'
+ },
+ 'memory:chrome:browser_process:reported_by_os:private_dirty_size': {
+ value: [100, 150],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total private dirty size reported by the OS for the ' +
+ 'browser process in Chrome'
+ },
+ 'memory:chrome:browser_process:reported_by_os:proportional_resident_size': {
+ value: [0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total proportional resident size (PSS) reported by the ' +
+ 'OS for the browser process in Chrome'
+ },
+ 'memory:chrome:browser_process:reported_by_os:system_memory:ashmem:private_dirty_size':
+ {
+ value: [0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of ashmem in the browser process ' +
+ 'in Chrome'
+ },
+ 'memory:chrome:browser_process:reported_by_os:system_memory:ashmem:proportional_resident_size':
+ {
+ value: [0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of ashmem in the ' +
+ 'browser process in Chrome'
+ },
+ 'memory:chrome:browser_process:reported_by_os:system_memory:java_heap:private_dirty_size':
+ {
+ value: [100, 150],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of the Java heap in the browser ' +
+ 'process in Chrome'
+ },
+ 'memory:chrome:browser_process:reported_by_os:system_memory:java_heap:proportional_resident_size':
+ {
+ value: [0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of the Java heap in ' +
+ 'the browser process in Chrome'
+ },
+ 'memory:chrome:browser_process:reported_by_os:system_memory:native_heap:private_dirty_size':
+ {
+ value: [0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of the native heap in the browser ' +
+ 'process in Chrome'
+ },
+ 'memory:chrome:browser_process:reported_by_os:system_memory:native_heap:proportional_resident_size':
+ {
+ value: [0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of the native heap ' +
+ 'in the browser process in Chrome'
+ },
+ 'memory:chrome:browser_process:reported_by_os:system_memory:stack:private_dirty_size':
+ {
+ value: [0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of the thread stacks in the browser ' +
+ 'process in Chrome'
+ },
+ 'memory:chrome:browser_process:reported_by_os:system_memory:stack:proportional_resident_size':
+ {
+ value: [0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of the thread stacks ' +
+ 'in the browser process in Chrome'
+ },
+ 'memory:chrome:browser_process:reported_by_os:system_memory:private_dirty_size':
+ {
+ value: [100, 150],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total private dirty size of system memory (RAM) used ' +
+ 'by the browser process in Chrome'
+ },
+ 'memory:chrome:browser_process:reported_by_os:system_memory:proportional_resident_size':
+ {
+ value: [0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total proportional resident size (PSS) of system ' +
+ 'memory (RAM) used by the browser process in Chrome'
+ }
+ });
+
+ memoryMetricTest('combined_multipleBrowsers', function(model) {
+ const pWebView = createWebViewProcess(model);
+ const pChrome1 = createChromeBrowserProcess(model);
+ const pRenderer1 = createProcessWithName(model, 'Renderer');
+ const pGpu1 = createProcessWithName(model, 'GPU Process');
+ const pUnknownBrowser = createProcessWithName(model, 'Browser');
+ const pChrome2 = createChromeBrowserProcess(model);
+ const pRenderer2 = createProcessWithName(model, 'Renderer');
+
+ // Timestamp 1 (WebView).
+ const gmd1 = addGlobalMemoryDump(model, {ts: 0, levelOfDetail: LIGHT});
+ const pmdBrowser1 = addProcessMemoryDump(gmd1, pWebView, {ts: 0});
+ pmdBrowser1.memoryAllocatorDumps = [
+ newAllocatorDump(pmdBrowser1, 'malloc', {numerics: {size: 2}}),
+ newAllocatorDump(pmdBrowser1, 'v8', {numerics: {size: 4}})
+ ];
+
+ // Timestamp 2 (Chrome 1 + Renderer + GPU Process).
+ const gmd2 = addGlobalMemoryDump(model, {ts: 10, levelOfDetail: DETAILED});
+ const pmdBrowser2 = addProcessMemoryDump(gmd2, pChrome1, {ts: 12});
+ pmdBrowser2.vmRegions = VMRegionClassificationNode.fromRegions([
+ new VMRegion(0xABCD, 9999, 0, '/dev/ashmem/dalvik-non moving space',
+ {privateDirtyResident: 8})
+ ]);
+ const pmdGpu2 = addProcessMemoryDump(gmd2, pGpu1, {ts: 8});
+ pmdGpu2.vmRegions = VMRegionClassificationNode.fromRegions([
+ new VMRegion(0xABCD, 9999, 0, '/dev/ashmem/dalvik-non moving space',
+ {privateDirtyResident: 16})
+ ]);
+ const pmdRenderer2 = addProcessMemoryDump(gmd2, pRenderer1, {ts: 8});
+ pmdRenderer2.memoryAllocatorDumps = [
+ newAllocatorDump(pmdBrowser2, 'malloc', {numerics: {size: 32}})
+ ];
+
+ // Timestamp 3 (Chrome 2).
+ const gmd3 = addGlobalMemoryDump(model, {ts: 20, levelOfDetail: DETAILED});
+ const pmdBrowser3 = addProcessMemoryDump(gmd3, pChrome2, {ts: 20});
+ pmdBrowser3.memoryAllocatorDumps = [
+ newAllocatorDump(pmdBrowser3, 'malloc', {numerics: {size: 64}}),
+ newAllocatorDump(pmdBrowser3, 'sqlite', {numerics: {size: 128}}),
+ newAllocatorDump(pmdBrowser3, 'discardable', {numerics: {
+ size: 8388608,
+ locked_size: 4194304
+ }})
+ ];
+ pmdBrowser3.vmRegions = VMRegionClassificationNode.fromRegions([
+ new VMRegion(0xABCD, 99, 0, '/dev/ashmem/dalvik-non moving space',
+ {privateDirtyResident: 256})
+ ]);
+
+ // Timestamp 4 (Chrome 2 + Renderer).
+ const gmd4 = addGlobalMemoryDump(model, {ts: 30, levelOfDetail: LIGHT});
+ const pmdBrowser4 = addProcessMemoryDump(gmd4, pChrome2, {ts: 28});
+ pmdBrowser4.memoryAllocatorDumps = [
+ newAllocatorDump(pmdBrowser4, 'malloc', {numerics: {size: 512}}),
+ newAllocatorDump(pmdBrowser3, 'discardable', {numerics: {size: 16777216}})
+ ];
+ const pmdRenderer4 = addProcessMemoryDump(gmd4, pRenderer2, {ts: 32});
+ pmdRenderer4.memoryAllocatorDumps = [
+ newAllocatorDump(pmdRenderer4, 'malloc', {numerics: {size: 1024}}),
+ newAllocatorDump(pmdRenderer4, 'v8', {numerics: {size: 2048}})
+ ];
+
+ // Timestamp 5 (Unknown browser).
+ const gmd5 = addGlobalMemoryDump(model, {ts: 40, levelOfDetail: LIGHT});
+ const pmdBrowser5 = addProcessMemoryDump(gmd5, pUnknownBrowser, {ts: 40});
+ pmdBrowser5.memoryAllocatorDumps = [
+ newAllocatorDump(pmdBrowser5, 'malloc', {numerics: {size: 4096}}),
+ newAllocatorDump(pmdBrowser5, 'sqlite', {numerics: {size: 8192}}),
+ ];
+
+ // Timestamp 6 (WebView).
+ const gmd6 = addGlobalMemoryDump(model, {ts: 50, levelOfDetail: DETAILED});
+ const pmdBrowser6 = addProcessMemoryDump(gmd6, pWebView, {ts: 50});
+ pmdBrowser6.memoryAllocatorDumps = (function() {
+ const mallocDump = newAllocatorDump(pmdBrowser6, 'malloc',
+ {numerics: {size: 16384}});
+ const v8Dump = newAllocatorDump(pmdBrowser6, 'v8', {numerics: {
+ allocated_objects_size: 32768,
+ code_and_metadata_size: 33554432,
+ size: 67108864
+ }});
+ const isolateDump = addChildDump(v8Dump, 'isolateA');
+ addChildDump(isolateDump, 'malloc', {numerics: {
+ size: 1,
+ peak_size: 2
+ }});
+ return [mallocDump, v8Dump];
+ })();
+ pmdBrowser6.vmRegions = VMRegionClassificationNode.fromRegions([
+ new VMRegion(0xABCD, 99999, 0, '/dev/ashmem/dalvik-non moving space',
+ {privateDirtyResident: 65536})
+ ]);
+
+ // Timestamp 7 (Chrome 1 + GPU Process).
+ const gmd7 = addGlobalMemoryDump(model, {ts: 60, levelOfDetail: DETAILED});
+ const pmdBrowser7 = addProcessMemoryDump(gmd7, pChrome1, {ts: 63});
+ pmdBrowser7.memoryAllocatorDumps = [
+ newAllocatorDump(pmdBrowser4, 'malloc', {numerics: {size: 131072}}),
+ newAllocatorDump(pmdBrowser4, 'sqlite', {numerics: {size: 262144}})
+ ];
+ pmdBrowser7.vmRegions = VMRegionClassificationNode.fromRegions([
+ new VMRegion(0xABCD, 999999, 0, '/dev/ashmem/dalvik-non moving space',
+ {privateDirtyResident: 524288})
+ ]);
+ const pmdGpu7 = addProcessMemoryDump(gmd7, pGpu1, {ts: 57});
+ pmdGpu7.memoryAllocatorDumps = (function() {
+ const gpuDump = newAllocatorDump(pmdGpu7, 'gpu',
+ {numerics: {size: 1048576}});
+ const memtrackDump = addChildDump(gpuDump, 'android_memtrack');
+ addChildDump(memtrackDump, 'gl', {numerics: {memtrack_pss: 2097152}});
+ return [gpuDump];
+ })();
+
+ // Timestamp 8 (Chrome 1).
+ const gmd8 = addGlobalMemoryDump(
+ model, {ts: 76, levelOfDetail: BACKGROUND});
+ const pmdBrowser8 = addProcessMemoryDump(gmd8, pChrome1, {ts: 80});
+ pmdBrowser8.memoryAllocatorDumps = [
+ newAllocatorDump(pmdBrowser5, 'malloc', {numerics: {size: 1024}}),
+ newAllocatorDump(pmdBrowser5, 'sqlite', {numerics: {size: 2048}}),
+ ];
+
+ // Timestamp 9 (WebView).
+ const gmd9 = addGlobalMemoryDump(
+ model, {ts: 90, levelOfDetail: BACKGROUND});
+ const pmdBrowser9 = addProcessMemoryDump(gmd9, pWebView, {ts: 90});
+ pmdBrowser9.memoryAllocatorDumps = [
+ newAllocatorDump(pmdBrowser5, 'v8', {numerics: {size: 4096}}),
+ newAllocatorDump(pmdBrowser5, 'sqlite', {numerics: {size: 2048}}),
+ ];
+ }, undefined /* opt_options */, {
+ // WebView (GMD1, GMD6).
+ 'memory:webview:all_processes:dump_count:detailed': {
+ value: [1],
+ unit: count_smallerIsBetter,
+ description: 'total number of detailed memory dumps added by WebView ' +
+ 'to the trace'
+ },
+ 'memory:webview:all_processes:dump_count:light': {
+ value: [1],
+ unit: count_smallerIsBetter,
+ description: 'total number of light memory dumps added by WebView to ' +
+ 'the trace'
+ },
+ 'memory:webview:all_processes:dump_count:background': {
+ value: [1],
+ unit: count_smallerIsBetter,
+ description: 'total number of background memory dumps added by WebView ' +
+ 'to the trace'
+ },
+ 'memory:webview:all_processes:dump_count:heap_profiler': {
+ value: [0],
+ unit: count_smallerIsBetter,
+ description: 'total number of heap profiler memory dumps added by ' +
+ 'WebView to the trace'
+ },
+ 'memory:webview:all_processes:dump_count': {
+ value: [3],
+ unit: count_smallerIsBetter,
+ description: 'total number of all memory dumps added by WebView to the ' +
+ 'trace'
+ },
+ 'memory:webview:all_processes:process_count': {
+ value: [1, 1, 1],
+ unit: count_smallerIsBetter,
+ description: 'total number of all processes in WebView'
+ },
+ 'memory:webview:all_processes:reported_by_chrome:effective_size': {
+ value: [4 + 2, 16384 + 67108864, 4096 + 2048],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total effective size reported by Chrome for all ' +
+ 'processes in WebView'
+ },
+ 'memory:webview:all_processes:reported_by_chrome:code_and_metadata_size': {
+ value: [0, 33554432, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total size of code and metadata reported by Chrome for ' +
+ 'all processes in WebView'
+ },
+ 'memory:webview:all_processes:reported_by_chrome:allocated_objects_size': {
+ value: [0, 32768, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total size of all allocated objects reported by Chrome ' +
+ 'for all processes in WebView'
+ },
+ 'memory:webview:all_processes:reported_by_chrome:peak_size':
+ {
+ value: [0, 2, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total peak size reported by Chrome for all ' +
+ 'processes in WebView'
+ },
+ 'memory:webview:all_processes:reported_by_chrome:malloc:effective_size': {
+ value: [2, 16384, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of malloc in all processes in WebView'
+ },
+ 'memory:webview:all_processes:reported_by_chrome:sqlite:effective_size': {
+ value: [0, 2048, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of sqlite in all processes in WebView'
+ },
+ 'memory:webview:all_processes:reported_by_chrome:v8:allocated_by_malloc:effective_size':
+ {
+ value: [0, 1, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of objects allocated by malloc for v8 ' +
+ 'in all processes in WebView'
+ },
+ 'memory:webview:all_processes:reported_by_chrome:v8:allocated_by_malloc:peak_size':
+ {
+ value: [0, 2, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'peak size of objects allocated by malloc for v8 ' +
+ 'in all processes in WebView'
+ },
+ 'memory:webview:all_processes:reported_by_chrome:v8:effective_size': {
+ value: [4, 67108864, 4096],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of v8 in all processes in WebView'
+ },
+ 'memory:webview:all_processes:reported_by_chrome:v8:code_and_metadata_size':
+ {
+ value: [0, 33554432, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'size of v8 code and metadata in all processes in ' +
+ 'WebView'
+ },
+ 'memory:webview:all_processes:reported_by_chrome:v8:allocated_objects_size':
+ {
+ value: [0, 32768, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'size of all objects allocated by v8 in all processes ' +
+ 'in WebView'
+ },
+ 'memory:webview:all_processes:reported_by_chrome:v8:peak_size':
+ {
+ value: [0, 2, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'peak size of v8 in all processes in WebView'
+ },
+ 'memory:webview:all_processes:reported_by_os:private_dirty_size': {
+ value: [65536],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total private dirty size reported by the OS for all ' +
+ 'processes in WebView'
+ },
+ 'memory:webview:all_processes:reported_by_os:proportional_resident_size': {
+ value: [0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total proportional resident size (PSS) reported by the ' +
+ 'OS for all processes in WebView'
+ },
+ 'memory:webview:all_processes:reported_by_os:system_memory:ashmem:private_dirty_size':
+ {
+ value: [0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of ashmem in all processes in ' +
+ 'WebView'
+ },
+ 'memory:webview:all_processes:reported_by_os:system_memory:ashmem:proportional_resident_size':
+ {
+ value: [0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of ashmem in all ' +
+ 'processes in WebView'
+ },
+ 'memory:webview:all_processes:reported_by_os:system_memory:java_heap:private_dirty_size':
+ {
+ value: [65536],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of the Java heap in all processes ' +
+ 'in WebView'
+ },
+ 'memory:webview:all_processes:reported_by_os:system_memory:java_heap:proportional_resident_size':
+ {
+ value: [0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of the Java heap in ' +
+ 'all processes in WebView'
+ },
+ 'memory:webview:all_processes:reported_by_os:system_memory:native_heap:private_dirty_size':
+ {
+ value: [0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of the native heap in all ' +
+ 'processes in WebView'
+ },
+ 'memory:webview:all_processes:reported_by_os:system_memory:native_heap:proportional_resident_size':
+ {
+ value: [0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of the native heap ' +
+ 'in all processes in WebView'
+ },
+ 'memory:webview:all_processes:reported_by_os:system_memory:stack:private_dirty_size':
+ {
+ value: [0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of the thread stacks in all ' +
+ 'processes in WebView'
+ },
+ 'memory:webview:all_processes:reported_by_os:system_memory:stack:proportional_resident_size':
+ {
+ value: [0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of the thread stacks ' +
+ 'in all processes in WebView'
+ },
+ 'memory:webview:all_processes:reported_by_os:system_memory:private_dirty_size':
+ {
+ value: [65536],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total private dirty size of system memory (RAM) used ' +
+ 'by all processes in WebView'
+ },
+ 'memory:webview:all_processes:reported_by_os:system_memory:proportional_resident_size':
+ {
+ value: [0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total proportional resident size (PSS) of system ' +
+ 'memory (RAM) used by all processes in WebView'
+ },
+ 'memory:webview:browser_process:process_count': {
+ value: [1, 1, 1],
+ unit: count_smallerIsBetter,
+ description: 'total number of browser processes in WebView'
+ },
+ 'memory:webview:browser_process:reported_by_chrome:effective_size': {
+ value: [4 + 2, 16384 + 67108864, 4096 + 2048],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total effective size reported by Chrome for the browser ' +
+ 'process in WebView'
+ },
+ 'memory:webview:browser_process:reported_by_chrome:allocated_objects_size':
+ {
+ value: [0, 32768, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total size of all allocated objects reported by ' +
+ 'Chrome for the browser process in WebView'
+ },
+ 'memory:webview:browser_process:reported_by_chrome:code_and_metadata_size':
+ {
+ value: [0, 33554432, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total size of code and metadata reported by Chrome ' +
+ 'for the browser process in WebView'
+ },
+ 'memory:webview:browser_process:reported_by_chrome:peak_size':
+ {
+ value: [0, 2, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total peak size reported by Chrome ' +
+ 'for the browser process in WebView'
+ },
+ 'memory:webview:browser_process:reported_by_chrome:malloc:effective_size': {
+ value: [2, 16384, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of malloc in the browser process in WebView'
+ },
+ 'memory:webview:browser_process:reported_by_chrome:v8:allocated_by_malloc:effective_size':
+ {
+ value: [0, 1, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of objects allocated by malloc for v8 ' +
+ 'in the browser process in WebView'
+ },
+ 'memory:webview:browser_process:reported_by_chrome:v8:allocated_by_malloc:peak_size':
+ {
+ value: [0, 2, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'peak size of objects allocated by malloc for v8 ' +
+ 'in the browser process in WebView'
+ },
+ 'memory:webview:browser_process:reported_by_chrome:sqlite:effective_size': {
+ value: [0, 0, 2048],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of sqlite in the browser process in WebView'
+ },
+ 'memory:webview:browser_process:reported_by_chrome:v8:effective_size': {
+ value: [4, 67108864, 4096],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of v8 in the browser process in WebView'
+ },
+ 'memory:webview:browser_process:reported_by_chrome:v8:allocated_objects_size':
+ {
+ value: [0, 32768, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'size of all objects allocated by v8 in the browser ' +
+ 'process in WebView'
+ },
+ 'memory:webview:browser_process:reported_by_chrome:v8:code_and_metadata_size':
+ {
+ value: [0, 33554432, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'size of v8 code and metadata in the browser process ' +
+ 'in WebView'
+ },
+ 'memory:webview:browser_process:reported_by_chrome:v8:peak_size':
+ {
+ value: [0, 2, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'peak size of v8 in the browser process in WebView'
+ },
+ 'memory:webview:browser_process:reported_by_os:private_dirty_size': {
+ value: [65536],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total private dirty size reported by the OS for the ' +
+ 'browser process in WebView'
+ },
+ 'memory:webview:browser_process:reported_by_os:proportional_resident_size':
+ {
+ value: [0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total proportional resident size (PSS) reported by ' +
+ 'the OS for the browser process in WebView'
+ },
+ 'memory:webview:browser_process:reported_by_os:system_memory:ashmem:private_dirty_size':
+ {
+ value: [0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of ashmem in the browser process ' +
+ 'in WebView'
+ },
+ 'memory:webview:browser_process:reported_by_os:system_memory:ashmem:proportional_resident_size':
+ {
+ value: [0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of ashmem in the ' +
+ 'browser process in WebView'
+ },
+ 'memory:webview:browser_process:reported_by_os:system_memory:java_heap:private_dirty_size':
+ {
+ value: [65536],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of the Java heap in the browser ' +
+ 'process in WebView'
+ },
+ 'memory:webview:browser_process:reported_by_os:system_memory:java_heap:proportional_resident_size':
+ {
+ value: [0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of the Java heap in ' +
+ 'the browser process in WebView'
+ },
+ 'memory:webview:browser_process:reported_by_os:system_memory:native_heap:private_dirty_size':
+ {
+ value: [0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of the native heap in the browser ' +
+ 'process in WebView'
+ },
+ 'memory:webview:browser_process:reported_by_os:system_memory:native_heap:proportional_resident_size':
+ {
+ value: [0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of the native heap ' +
+ 'in the browser process in WebView'
+ },
+ 'memory:webview:browser_process:reported_by_os:system_memory:stack:private_dirty_size':
+ {
+ value: [0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of the thread stacks in the browser ' +
+ 'process in WebView'
+ },
+ 'memory:webview:browser_process:reported_by_os:system_memory:stack:proportional_resident_size':
+ {
+ value: [0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of the thread stacks ' +
+ 'in the browser process in WebView'
+ },
+ 'memory:webview:browser_process:reported_by_os:system_memory:private_dirty_size':
+ {
+ value: [65536],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total private dirty size of system memory (RAM) used ' +
+ 'by the browser process in WebView'
+ },
+ 'memory:webview:browser_process:reported_by_os:system_memory:proportional_resident_size':
+ {
+ value: [0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total proportional resident size (PSS) of system ' +
+ 'memory (RAM) used by the browser process in WebView'
+ },
+
+ // Chrome 1 (GMD3, GMD4).
+ 'memory:chrome:all_processes:reported_by_os:gpu_memory:gl:proportional_resident_size':
+ {
+ value: [0, 2097152],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of the gl Android ' +
+ 'memtrack component in all processes in Chrome'
+ },
+ 'memory:chrome:all_processes:dump_count:detailed': {
+ value: [2],
+ unit: count_smallerIsBetter,
+ description: 'total number of detailed memory dumps added by Chrome to ' +
+ 'the trace'
+ },
+ 'memory:chrome:all_processes:dump_count:light': {
+ value: [0],
+ unit: count_smallerIsBetter,
+ description: 'total number of light memory dumps added by Chrome to ' +
+ 'the trace'
+ },
+ 'memory:chrome:all_processes:dump_count:background': {
+ value: [1],
+ unit: count_smallerIsBetter,
+ description: 'total number of background memory dumps added by Chrome ' +
+ 'to the trace'
+ },
+ 'memory:chrome:all_processes:dump_count:heap_profiler': {
+ value: [0],
+ unit: count_smallerIsBetter,
+ description: 'total number of heap profiler memory dumps added by ' +
+ 'Chrome to the trace'
+ },
+ 'memory:chrome:all_processes:dump_count': {
+ value: [3],
+ unit: count_smallerIsBetter,
+ description: 'total number of all memory dumps added by Chrome to the ' +
+ 'trace'
+ },
+ 'memory:chrome:all_processes:process_count': {
+ value: [3, 2, 1],
+ unit: count_smallerIsBetter,
+ description: 'total number of all processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_chrome:effective_size': {
+ value: [32, 1048576 + 131072 + 262144, 1024 + 2048],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total effective size reported by Chrome for all ' +
+ 'processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_chrome:gpu:effective_size': {
+ value: [0, 1048576, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of gpu in all processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_chrome:malloc:effective_size': {
+ value: [32, 131072, 1024],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of malloc in all processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_chrome:sqlite:effective_size': {
+ value: [0, 262144, 2048],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of sqlite in all processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_os:private_dirty_size': {
+ value: [8 + 16, 524288],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total private dirty size reported by the OS for all ' +
+ 'processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_os:proportional_resident_size': {
+ value: [0, 2097152],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total proportional resident size (PSS) reported by the ' +
+ 'OS for all processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_os:gpu_memory:proportional_resident_size':
+ {
+ value: [0, 2097152],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total proportional resident size (PSS) of GPU memory ' +
+ '(Android memtrack) used by all processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_os:system_memory:ashmem:private_dirty_size':
+ {
+ value: [0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of ashmem in all processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_os:system_memory:ashmem:proportional_resident_size':
+ {
+ value: [0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of ashmem in all ' +
+ 'processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_os:system_memory:java_heap:private_dirty_size':
+ {
+ value: [8 + 16, 524288],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of the Java heap in all processes ' +
+ 'in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_os:system_memory:java_heap:proportional_resident_size':
+ {
+ value: [0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of the Java heap in ' +
+ 'all processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_os:system_memory:native_heap:private_dirty_size':
+ {
+ value: [0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of the native heap in all ' +
+ 'processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_os:system_memory:native_heap:proportional_resident_size':
+ {
+ value: [0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of the native heap ' +
+ 'in all processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_os:system_memory:stack:private_dirty_size':
+ {
+ value: [0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of the thread stacks in all ' +
+ 'processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_os:system_memory:stack:proportional_resident_size':
+ {
+ value: [0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of the thread stacks ' +
+ 'in all processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_os:system_memory:private_dirty_size':
+ {
+ value: [8 + 16, 524288],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total private dirty size of system memory (RAM) used ' +
+ 'by all processes in Chrome'
+ },
+ 'memory:chrome:all_processes:reported_by_os:system_memory:proportional_resident_size':
+ {
+ value: [0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total proportional resident size (PSS) of system ' +
+ 'memory (RAM) used by all processes in Chrome'
+ },
+ 'memory:chrome:browser_process:process_count': {
+ value: [1, 1, 1],
+ unit: count_smallerIsBetter,
+ description: 'total number of browser processes in Chrome'
+ },
+ 'memory:chrome:browser_process:reported_by_chrome:effective_size': {
+ value: [0, 131072 + 262144, 1024 + 2048],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total effective size reported by Chrome for the browser ' +
+ 'process in Chrome'
+ },
+ 'memory:chrome:browser_process:reported_by_chrome:malloc:effective_size': {
+ value: [0, 131072, 1024],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of malloc in the browser process in Chrome'
+ },
+ 'memory:chrome:browser_process:reported_by_chrome:sqlite:effective_size': {
+ value: [0, 262144, 2048],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of sqlite in the browser process in Chrome'
+ },
+ 'memory:chrome:browser_process:reported_by_os:private_dirty_size': {
+ value: [8, 524288],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total private dirty size reported by the OS for the ' +
+ 'browser process in Chrome'
+ },
+ 'memory:chrome:browser_process:reported_by_os:proportional_resident_size': {
+ value: [0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total proportional resident size (PSS) reported by the ' +
+ 'OS for the browser process in Chrome'
+ },
+ 'memory:chrome:browser_process:reported_by_os:system_memory:ashmem:private_dirty_size':
+ {
+ value: [0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of ashmem in the browser process ' +
+ 'in Chrome'
+ },
+ 'memory:chrome:browser_process:reported_by_os:system_memory:ashmem:proportional_resident_size':
+ {
+ value: [0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of ashmem in the ' +
+ 'browser process in Chrome'
+ },
+ 'memory:chrome:browser_process:reported_by_os:system_memory:java_heap:private_dirty_size':
+ {
+ value: [8, 524288],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of the Java heap in the browser ' +
+ 'process in Chrome'
+ },
+ 'memory:chrome:browser_process:reported_by_os:system_memory:java_heap:proportional_resident_size':
+ {
+ value: [0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of the Java heap in ' +
+ 'the browser process in Chrome'
+ },
+ 'memory:chrome:browser_process:reported_by_os:system_memory:native_heap:private_dirty_size':
+ {
+ value: [0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of the native heap in the browser ' +
+ 'process in Chrome'
+ },
+ 'memory:chrome:browser_process:reported_by_os:system_memory:native_heap:proportional_resident_size':
+ {
+ value: [0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of the native heap ' +
+ 'in the browser process in Chrome'
+ },
+ 'memory:chrome:browser_process:reported_by_os:system_memory:stack:private_dirty_size':
+ {
+ value: [0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of the thread stacks in the browser ' +
+ 'process in Chrome'
+ },
+ 'memory:chrome:browser_process:reported_by_os:system_memory:stack:proportional_resident_size':
+ {
+ value: [0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of the thread stacks ' +
+ 'in the browser process in Chrome'
+ },
+ 'memory:chrome:browser_process:reported_by_os:system_memory:private_dirty_size':
+ {
+ value: [8, 524288],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total private dirty size of system memory (RAM) used ' +
+ 'by the browser process in Chrome'
+ },
+ 'memory:chrome:browser_process:reported_by_os:system_memory:proportional_resident_size':
+ {
+ value: [0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total proportional resident size (PSS) of system ' +
+ 'memory (RAM) used by the browser process in Chrome'
+ },
+ 'memory:chrome:gpu_process:reported_by_chrome:effective_size': {
+ value: [0, 1048576, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total effective size reported by Chrome for the GPU ' +
+ 'process in Chrome'
+ },
+ 'memory:chrome:gpu_process:reported_by_os:gpu_memory:proportional_resident_size':
+ {
+ value: [0, 2097152],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total proportional resident size (PSS) of GPU memory ' +
+ '(Android memtrack) used by the GPU process in Chrome'
+ },
+ 'memory:chrome:gpu_process:reported_by_os:gpu_memory:gl:proportional_resident_size':
+ {
+ value: [0, 2097152],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of the gl Android ' +
+ 'memtrack component in the GPU process in Chrome'
+ },
+ 'memory:chrome:gpu_process:process_count': {
+ value: [1, 1, 0],
+ unit: count_smallerIsBetter,
+ description: 'total number of GPU processes in Chrome'
+ },
+ 'memory:chrome:gpu_process:reported_by_chrome:gpu:effective_size': {
+ value: [0, 1048576, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of gpu in the GPU process in Chrome'
+ },
+ 'memory:chrome:gpu_process:reported_by_os:private_dirty_size': {
+ value: [16, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total private dirty size reported by the OS for the GPU ' +
+ 'process in Chrome'
+ },
+ 'memory:chrome:gpu_process:reported_by_os:proportional_resident_size': {
+ value: [0, 2097152],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total proportional resident size (PSS) reported by the ' +
+ 'OS for the GPU process in Chrome'
+ },
+ 'memory:chrome:gpu_process:reported_by_os:system_memory:ashmem:private_dirty_size':
+ {
+ value: [0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of ashmem in the GPU process in ' +
+ 'Chrome'
+ },
+ 'memory:chrome:gpu_process:reported_by_os:system_memory:ashmem:proportional_resident_size':
+ {
+ value: [0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of ashmem in the ' +
+ 'GPU process in Chrome'
+ },
+ 'memory:chrome:gpu_process:reported_by_os:system_memory:java_heap:private_dirty_size':
+ {
+ value: [16, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of the Java heap in the GPU ' +
+ 'process in Chrome'
+ },
+ 'memory:chrome:gpu_process:reported_by_os:system_memory:java_heap:proportional_resident_size':
+ {
+ value: [0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of the Java heap in ' +
+ 'the GPU process in Chrome'
+ },
+ 'memory:chrome:gpu_process:reported_by_os:system_memory:native_heap:private_dirty_size':
+ {
+ value: [0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of the native heap in the GPU ' +
+ 'process in Chrome'
+ },
+ 'memory:chrome:gpu_process:reported_by_os:system_memory:native_heap:proportional_resident_size':
+ {
+ value: [0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of the native heap ' +
+ 'in the GPU process in Chrome'
+ },
+ 'memory:chrome:gpu_process:reported_by_os:system_memory:stack:private_dirty_size':
+ {
+ value: [0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of the thread stacks in the GPU ' +
+ 'process in Chrome'
+ },
+ 'memory:chrome:gpu_process:reported_by_os:system_memory:stack:proportional_resident_size':
+ {
+ value: [0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of the thread stacks ' +
+ 'in the GPU process in Chrome'
+ },
+ 'memory:chrome:gpu_process:reported_by_os:system_memory:private_dirty_size':
+ {
+ value: [16, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total private dirty size of system memory (RAM) used ' +
+ 'by the GPU process in Chrome'
+ },
+ 'memory:chrome:gpu_process:reported_by_os:system_memory:proportional_resident_size':
+ {
+ value: [0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total proportional resident size (PSS) of system ' +
+ 'memory (RAM) used by the GPU process in Chrome'
+ },
+ 'memory:chrome:renderer_processes:process_count': {
+ value: [1, 0, 0],
+ unit: count_smallerIsBetter,
+ description: 'total number of renderer processes in Chrome'
+ },
+ 'memory:chrome:renderer_processes:reported_by_chrome:effective_size': {
+ value: [32, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total effective size reported by Chrome for renderer ' +
+ 'processes in Chrome'
+ },
+ 'memory:chrome:renderer_processes:reported_by_chrome:malloc:effective_size':
+ {
+ value: [32, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of malloc in renderer processes in ' +
+ 'Chrome'
+ },
+ 'memory:chrome:renderer_processes:reported_by_os:private_dirty_size': {
+ value: [0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total private dirty size reported by the OS for renderer ' +
+ 'processes in Chrome'
+ },
+ 'memory:chrome:renderer_processes:reported_by_os:proportional_resident_size':
+ {
+ value: [0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total proportional resident size (PSS) reported by ' +
+ 'the OS for renderer processes in Chrome'
+ },
+ 'memory:chrome:renderer_processes:reported_by_os:system_memory:ashmem:private_dirty_size':
+ {
+ value: [0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of ashmem in renderer processes ' +
+ 'in Chrome'
+ },
+ 'memory:chrome:renderer_processes:reported_by_os:system_memory:ashmem:proportional_resident_size':
+ {
+ value: [0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of ashmem in ' +
+ 'renderer processes in Chrome'
+ },
+ 'memory:chrome:renderer_processes:reported_by_os:system_memory:java_heap:private_dirty_size':
+ {
+ value: [0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of the Java heap in renderer ' +
+ 'processes in Chrome'
+ },
+ 'memory:chrome:renderer_processes:reported_by_os:system_memory:java_heap:proportional_resident_size':
+ {
+ value: [0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of the Java heap in ' +
+ 'renderer processes in Chrome'
+ },
+ 'memory:chrome:renderer_processes:reported_by_os:system_memory:native_heap:private_dirty_size':
+ {
+ value: [0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of the native heap in renderer ' +
+ 'processes in Chrome'
+ },
+ 'memory:chrome:renderer_processes:reported_by_os:system_memory:native_heap:proportional_resident_size':
+ {
+ value: [0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of the native heap ' +
+ 'in renderer processes in Chrome'
+ },
+ 'memory:chrome:renderer_processes:reported_by_os:system_memory:stack:private_dirty_size':
+ {
+ value: [0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of the thread stacks in renderer ' +
+ 'processes in Chrome'
+ },
+ 'memory:chrome:renderer_processes:reported_by_os:system_memory:stack:proportional_resident_size':
+ {
+ value: [0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of the thread stacks ' +
+ 'in renderer processes in Chrome'
+ },
+ 'memory:chrome:renderer_processes:reported_by_os:system_memory:private_dirty_size':
+ {
+ value: [0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total private dirty size of system memory (RAM) used ' +
+ 'by renderer processes in Chrome'
+ },
+ 'memory:chrome:renderer_processes:reported_by_os:system_memory:proportional_resident_size':
+ {
+ value: [0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total proportional resident size (PSS) of system ' +
+ 'memory (RAM) used by renderer processes in Chrome'
+ },
+
+ // Chrome 2 (GMD2, GMD7).
+ 'memory:chrome2:all_processes:dump_count:detailed': {
+ value: [1],
+ unit: count_smallerIsBetter,
+ description: 'total number of detailed memory dumps added by Chrome(2) ' +
+ 'to the trace'
+ },
+ 'memory:chrome2:all_processes:dump_count:light': {
+ value: [1],
+ unit: count_smallerIsBetter,
+ description: 'total number of light memory dumps added by Chrome(2) to ' +
+ 'the trace'
+ },
+ 'memory:chrome2:all_processes:dump_count:background': {
+ value: [0],
+ unit: count_smallerIsBetter,
+ description: 'total number of background memory dumps added by ' +
+ 'Chrome(2) to the trace'
+ },
+ 'memory:chrome2:all_processes:dump_count:heap_profiler': {
+ value: [0],
+ unit: count_smallerIsBetter,
+ description: 'total number of heap profiler memory dumps added by ' +
+ 'Chrome(2) to the trace'
+ },
+ 'memory:chrome2:all_processes:dump_count': {
+ value: [2],
+ unit: count_smallerIsBetter,
+ description: 'total number of all memory dumps added by Chrome(2) to ' +
+ 'the trace'
+ },
+ 'memory:chrome2:all_processes:process_count': {
+ value: [1, 2],
+ unit: count_smallerIsBetter,
+ description: 'total number of all processes in Chrome(2)'
+ },
+ 'memory:chrome2:all_processes:reported_by_chrome:discardable:effective_size':
+ {
+ value: [8388608, 16777216],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of discardable in all processes in ' +
+ 'Chrome(2)'
+ },
+ 'memory:chrome2:all_processes:reported_by_chrome:locked_size': {
+ value: [4194304, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total locked (pinned) size reported by Chrome for all ' +
+ 'processes in Chrome(2)'
+ },
+ 'memory:chrome2:all_processes:reported_by_chrome:discardable:locked_size': {
+ value: [4194304, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'locked (pinned) size of discardable in all processes in ' +
+ 'Chrome(2)'
+ },
+ 'memory:chrome2:all_processes:reported_by_chrome:effective_size': {
+ value: [64 + 8388608 + 128, 512 + 1024 + 2048 + 16777216],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total effective size reported by Chrome for all ' +
+ 'processes in Chrome(2)'
+ },
+ 'memory:chrome2:all_processes:reported_by_chrome:malloc:effective_size': {
+ value: [64, 512 + 1024],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of malloc in all processes in Chrome(2)'
+ },
+ 'memory:chrome2:all_processes:reported_by_chrome:sqlite:effective_size': {
+ value: [128, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of sqlite in all processes in Chrome(2)'
+ },
+ 'memory:chrome2:all_processes:reported_by_chrome:v8:effective_size': {
+ value: [0, 2048],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of v8 in all processes in Chrome(2)'
+ },
+ 'memory:chrome2:all_processes:reported_by_os:private_dirty_size': {
+ value: [256],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total private dirty size reported by the OS for all ' +
+ 'processes in Chrome(2)'
+ },
+ 'memory:chrome2:all_processes:reported_by_os:proportional_resident_size': {
+ value: [0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total proportional resident size (PSS) reported by the ' +
+ 'OS for all processes in Chrome(2)'
+ },
+ 'memory:chrome2:all_processes:reported_by_os:system_memory:ashmem:private_dirty_size':
+ {
+ value: [0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of ashmem in all processes in ' +
+ 'Chrome(2)'
+ },
+ 'memory:chrome2:all_processes:reported_by_os:system_memory:ashmem:proportional_resident_size':
+ {
+ value: [0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of ashmem in all ' +
+ 'processes in Chrome(2)'
+ },
+ 'memory:chrome2:all_processes:reported_by_os:system_memory:java_heap:private_dirty_size':
+ {
+ value: [256],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of the Java heap in all processes ' +
+ 'in Chrome(2)'
+ },
+ 'memory:chrome2:all_processes:reported_by_os:system_memory:java_heap:proportional_resident_size':
+ {
+ value: [0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of the Java heap in ' +
+ 'all processes in Chrome(2)'
+ },
+ 'memory:chrome2:all_processes:reported_by_os:system_memory:native_heap:private_dirty_size':
+ {
+ value: [0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of the native heap in all ' +
+ 'processes in Chrome(2)'
+ },
+ 'memory:chrome2:all_processes:reported_by_os:system_memory:native_heap:proportional_resident_size':
+ {
+ value: [0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of the native heap ' +
+ 'in all processes in Chrome(2)'
+ },
+ 'memory:chrome2:all_processes:reported_by_os:system_memory:stack:private_dirty_size':
+ {
+ value: [0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of the thread stacks in all ' +
+ 'processes in Chrome(2)'
+ },
+ 'memory:chrome2:all_processes:reported_by_os:system_memory:stack:proportional_resident_size':
+ {
+ value: [0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of the thread stacks ' +
+ 'in all processes in Chrome(2)'
+ },
+ 'memory:chrome2:all_processes:reported_by_os:system_memory:private_dirty_size':
+ {
+ value: [256],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total private dirty size of system memory (RAM) used ' +
+ 'by all processes in Chrome(2)'
+ },
+ 'memory:chrome2:all_processes:reported_by_os:system_memory:proportional_resident_size':
+ {
+ value: [0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total proportional resident size (PSS) of system ' +
+ 'memory (RAM) used by all processes in Chrome(2)'
+ },
+ 'memory:chrome2:browser_process:process_count': {
+ value: [1, 1],
+ unit: count_smallerIsBetter,
+ description: 'total number of browser processes in Chrome(2)'
+ },
+ 'memory:chrome2:browser_process:reported_by_chrome:effective_size': {
+ value: [64 + 8388608 + 128, 512 + 16777216],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total effective size reported by Chrome for the browser ' +
+ 'process in Chrome(2)'
+ },
+ 'memory:chrome2:browser_process:reported_by_chrome:locked_size': {
+ value: [4194304, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total locked (pinned) size reported by Chrome for the ' +
+ 'browser process in Chrome(2)'
+ },
+ 'memory:chrome2:browser_process:reported_by_chrome:malloc:effective_size': {
+ value: [64, 512],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of malloc in the browser process in ' +
+ 'Chrome(2)'
+ },
+ 'memory:chrome2:browser_process:reported_by_chrome:discardable:effective_size':
+ {
+ value: [8388608, 16777216],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of discardable in the browser process ' +
+ 'in Chrome(2)'
+ },
+ 'memory:chrome2:browser_process:reported_by_chrome:discardable:locked_size':
+ {
+ value: [4194304, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'locked (pinned) size of discardable in the browser ' +
+ 'process in Chrome(2)'
+ },
+ 'memory:chrome2:browser_process:reported_by_chrome:sqlite:effective_size': {
+ value: [128, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of sqlite in the browser process in ' +
+ 'Chrome(2)'
+ },
+ 'memory:chrome2:browser_process:reported_by_os:private_dirty_size': {
+ value: [256],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total private dirty size reported by the OS for the ' +
+ 'browser process in Chrome(2)'
+ },
+ 'memory:chrome2:browser_process:reported_by_os:proportional_resident_size':
+ {
+ value: [0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total proportional resident size (PSS) reported by ' +
+ 'the OS for the browser process in Chrome(2)'
+ },
+ 'memory:chrome2:browser_process:reported_by_os:system_memory:ashmem:private_dirty_size':
+ {
+ value: [0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of ashmem in the browser process ' +
+ 'in Chrome(2)'
+ },
+ 'memory:chrome2:browser_process:reported_by_os:system_memory:ashmem:proportional_resident_size':
+ {
+ value: [0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of ashmem in the ' +
+ 'browser process in Chrome(2)'
+ },
+ 'memory:chrome2:browser_process:reported_by_os:system_memory:java_heap:private_dirty_size':
+ {
+ value: [256],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of the Java heap in the browser ' +
+ 'process in Chrome(2)'
+ },
+ 'memory:chrome2:browser_process:reported_by_os:system_memory:java_heap:proportional_resident_size':
+ {
+ value: [0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of the Java heap in ' +
+ 'the browser process in Chrome(2)'
+ },
+ 'memory:chrome2:browser_process:reported_by_os:system_memory:native_heap:private_dirty_size':
+ {
+ value: [0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of the native heap in the browser ' +
+ 'process in Chrome(2)'
+ },
+ 'memory:chrome2:browser_process:reported_by_os:system_memory:native_heap:proportional_resident_size':
+ {
+ value: [0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of the native heap ' +
+ 'in the browser process in Chrome(2)'
+ },
+ 'memory:chrome2:browser_process:reported_by_os:system_memory:stack:private_dirty_size':
+ {
+ value: [0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of the thread stacks in the browser ' +
+ 'process in Chrome(2)'
+ },
+ 'memory:chrome2:browser_process:reported_by_os:system_memory:stack:proportional_resident_size':
+ {
+ value: [0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of the thread stacks ' +
+ 'in the browser process in Chrome(2)'
+ },
+ 'memory:chrome2:browser_process:reported_by_os:system_memory:private_dirty_size':
+ {
+ value: [256],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total private dirty size of system memory (RAM) used ' +
+ 'by the browser process in Chrome(2)'
+ },
+ 'memory:chrome2:browser_process:reported_by_os:system_memory:proportional_resident_size':
+ {
+ value: [0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total proportional resident size (PSS) of system ' +
+ 'memory (RAM) used by the browser process in Chrome(2)'
+ },
+ 'memory:chrome2:renderer_processes:process_count': {
+ value: [0, 1],
+ unit: count_smallerIsBetter,
+ description: 'total number of renderer processes in Chrome(2)'
+ },
+ 'memory:chrome2:renderer_processes:reported_by_chrome:effective_size': {
+ value: [0, 1024 + 2048],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total effective size reported by Chrome for renderer ' +
+ 'processes in Chrome(2)'
+ },
+ 'memory:chrome2:renderer_processes:reported_by_chrome:malloc:effective_size':
+ {
+ value: [0, 1024],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of malloc in renderer processes in ' +
+ 'Chrome(2)'
+ },
+ 'memory:chrome2:renderer_processes:reported_by_chrome:v8:effective_size': {
+ value: [0, 2048],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of v8 in renderer processes in Chrome(2)'
+ },
+
+ // Unknown browser (GMD5).
+ 'memory:unknown_browser:all_processes:dump_count:detailed': {
+ value: [0],
+ unit: count_smallerIsBetter,
+ description: 'total number of detailed memory dumps added by an ' +
+ 'unknown browser to the trace'
+ },
+ 'memory:unknown_browser:all_processes:dump_count:light': {
+ value: [1],
+ unit: count_smallerIsBetter,
+ description: 'total number of light memory dumps added by an unknown ' +
+ 'browser to the trace'
+ },
+ 'memory:unknown_browser:all_processes:dump_count:background': {
+ value: [0],
+ unit: count_smallerIsBetter,
+ description: 'total number of background memory dumps added by an ' +
+ 'unknown browser to the trace'
+ },
+ 'memory:unknown_browser:all_processes:dump_count:heap_profiler': {
+ value: [0],
+ unit: count_smallerIsBetter,
+ description: 'total number of heap profiler memory dumps added by an ' +
+ 'unknown browser to the trace'
+ },
+ 'memory:unknown_browser:all_processes:dump_count': {
+ value: [1],
+ unit: count_smallerIsBetter,
+ description: 'total number of all memory dumps added by an unknown ' +
+ 'browser to the trace'
+ },
+ 'memory:unknown_browser:all_processes:process_count': {
+ value: [1],
+ unit: count_smallerIsBetter,
+ description: 'total number of all processes in an unknown browser'
+ },
+ 'memory:unknown_browser:all_processes:reported_by_chrome:effective_size': {
+ value: [4096 + 8192],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total effective size reported by Chrome for all ' +
+ 'processes in an unknown browser'
+ },
+ 'memory:unknown_browser:all_processes:reported_by_chrome:malloc:effective_size':
+ {
+ value: [4096],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of malloc in all processes in an ' +
+ 'unknown browser'
+ },
+ 'memory:unknown_browser:all_processes:reported_by_chrome:sqlite:effective_size':
+ {
+ value: [8192],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of sqlite in all processes in an ' +
+ 'unknown browser'
+ },
+ 'memory:unknown_browser:browser_process:process_count': {
+ value: [1],
+ unit: count_smallerIsBetter,
+ description: 'total number of browser processes in an unknown browser'
+ },
+ 'memory:unknown_browser:browser_process:reported_by_chrome:effective_size':
+ {
+ value: [4096 + 8192],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total effective size reported by Chrome for the ' +
+ 'browser process in an unknown browser'
+ },
+ 'memory:unknown_browser:browser_process:reported_by_chrome:malloc:effective_size':
+ {
+ value: [4096],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of malloc in the browser process in ' +
+ 'an unknown browser'
+ },
+ 'memory:unknown_browser:browser_process:reported_by_chrome:sqlite:effective_size':
+ {
+ value: [8192],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'effective size of sqlite in the browser process in ' +
+ 'an unknown browser'
+ }
+ });
+
+ memoryMetricTest('rangeOfInterest', function(model) {
+ const pChrome = createChromeBrowserProcess(model);
+ const pWebView = createWebViewProcess(model);
+
+ // Chrome: only the LIGHT dumps should be kept.
+ addProcessMemoryDump(addGlobalMemoryDump(
+ model, {ts: 5, duration: 4, levelOfDetail: DETAILED}), pChrome);
+ addProcessMemoryDump(addGlobalMemoryDump(
+ model, {ts: 10, duration: 2, levelOfDetail: LIGHT}), pChrome);
+ addProcessMemoryDump(addGlobalMemoryDump(
+ model, {ts: 13, duration: 3, levelOfDetail: LIGHT}), pChrome);
+ addProcessMemoryDump(addGlobalMemoryDump(
+ model, {ts: 20, duration: 1, levelOfDetail: BACKGROUND}), pChrome);
+ addProcessMemoryDump(addGlobalMemoryDump(
+ model, {ts: 22, duration: 5, levelOfDetail: DETAILED}), pChrome);
+
+ // WebView: only the DETAILED dumps should be kept.
+ addProcessMemoryDump(addGlobalMemoryDump(
+ model, {ts: 4, duration: 1, levelOfDetail: LIGHT}), pWebView);
+ addProcessMemoryDump(addGlobalMemoryDump(
+ model, {ts: 5, duration: 5, levelOfDetail: DETAILED}), pWebView);
+ addProcessMemoryDump(addGlobalMemoryDump(
+ model, {ts: 10, duration: 0, levelOfDetail: DETAILED}), pWebView);
+ addProcessMemoryDump(addGlobalMemoryDump(
+ model, {ts: 11, duration: 7, levelOfDetail: BACKGROUND}), pWebView);
+ addProcessMemoryDump(addGlobalMemoryDump(
+ model, {ts: 19, duration: 2, levelOfDetail: DETAILED}), pWebView);
+ addProcessMemoryDump(addGlobalMemoryDump(
+ model, {ts: 21, duration: 5, levelOfDetail: LIGHT}), pWebView);
+
+ // Unknown browser: only the LIGHT dump should be kept.
+ addGlobalMemoryDump(model, {ts: 5, duration: 3, levelOfDetail: DETAILED});
+ addGlobalMemoryDump(model, {ts: 9, duration: 12, levelOfDetail: LIGHT});
+ addGlobalMemoryDump(model, {ts: 22, duration: 3, levelOfDetail: DETAILED});
+ }, { /* opt_options */
+ rangeOfInterest: tr.b.math.Range.fromExplicitRange(10, 20)
+ }, {
+ 'memory:chrome:all_processes:dump_count': {
+ value: [3],
+ unit: count_smallerIsBetter,
+ description: 'total number of all memory dumps added by Chrome to the ' +
+ 'trace'
+ },
+ 'memory:chrome:all_processes:dump_count:detailed': {
+ value: [0],
+ unit: count_smallerIsBetter,
+ description: 'total number of detailed memory dumps added by Chrome to ' +
+ 'the trace'
+ },
+ 'memory:chrome:all_processes:dump_count:light': {
+ value: [2],
+ unit: count_smallerIsBetter,
+ description: 'total number of light memory dumps added by Chrome to ' +
+ 'the trace'
+ },
+ 'memory:chrome:all_processes:dump_count:background': {
+ value: [1],
+ unit: count_smallerIsBetter,
+ description: 'total number of background memory dumps added by Chrome ' +
+ 'to the trace'
+ },
+ 'memory:chrome:all_processes:dump_count:heap_profiler': {
+ value: [0],
+ unit: count_smallerIsBetter,
+ description: 'total number of heap profiler memory dumps added by ' +
+ 'Chrome to the trace'
+ },
+ 'memory:chrome:all_processes:process_count': {
+ value: [1, 1, 1],
+ unit: count_smallerIsBetter,
+ description: 'total number of all processes in Chrome'
+ },
+ 'memory:chrome:browser_process:process_count': {
+ value: [1, 1, 1],
+ unit: count_smallerIsBetter,
+ description: 'total number of browser processes in Chrome'
+ },
+
+ 'memory:webview:all_processes:dump_count': {
+ value: [4],
+ unit: count_smallerIsBetter,
+ description: 'total number of all memory dumps added by WebView to the ' +
+ 'trace'
+ },
+ 'memory:webview:all_processes:dump_count:detailed': {
+ value: [3],
+ unit: count_smallerIsBetter,
+ description: 'total number of detailed memory dumps added by WebView ' +
+ 'to the trace'
+ },
+ 'memory:webview:all_processes:dump_count:light': {
+ value: [0],
+ unit: count_smallerIsBetter,
+ description: 'total number of light memory dumps added by WebView to ' +
+ 'the trace'
+ },
+ 'memory:webview:all_processes:dump_count:background': {
+ value: [1],
+ unit: count_smallerIsBetter,
+ description: 'total number of background memory dumps added by WebView ' +
+ 'to the trace'
+ },
+ 'memory:webview:all_processes:dump_count:heap_profiler': {
+ value: [0],
+ unit: count_smallerIsBetter,
+ description: 'total number of heap profiler memory dumps added by ' +
+ 'WebView to the trace'
+ },
+ 'memory:webview:all_processes:process_count': {
+ value: [1, 1, 1, 1],
+ unit: count_smallerIsBetter,
+ description: 'total number of all processes in WebView'
+ },
+ 'memory:webview:all_processes:reported_by_os:private_dirty_size': {
+ value: [0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total private dirty size reported by the OS for all ' +
+ 'processes in WebView'
+ },
+ 'memory:webview:all_processes:reported_by_os:proportional_resident_size': {
+ value: [0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total proportional resident size (PSS) reported by the ' +
+ 'OS for all processes in WebView'
+ },
+ 'memory:webview:all_processes:reported_by_os:system_memory:ashmem:private_dirty_size':
+ {
+ value: [0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of ashmem in all processes in ' +
+ 'WebView'
+ },
+ 'memory:webview:all_processes:reported_by_os:system_memory:ashmem:proportional_resident_size':
+ {
+ value: [0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of ashmem in all ' +
+ 'processes in WebView'
+ },
+ 'memory:webview:all_processes:reported_by_os:system_memory:java_heap:private_dirty_size':
+ {
+ value: [0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of the Java heap in all processes ' +
+ 'in WebView'
+ },
+ 'memory:webview:all_processes:reported_by_os:system_memory:java_heap:proportional_resident_size':
+ {
+ value: [0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of the Java heap in ' +
+ 'all processes in WebView'
+ },
+ 'memory:webview:all_processes:reported_by_os:system_memory:native_heap:private_dirty_size':
+ {
+ value: [0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of the native heap in all ' +
+ 'processes in WebView'
+ },
+ 'memory:webview:all_processes:reported_by_os:system_memory:native_heap:proportional_resident_size':
+ {
+ value: [0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of the native heap ' +
+ 'in all processes in WebView'
+ },
+ 'memory:webview:all_processes:reported_by_os:system_memory:stack:private_dirty_size':
+ {
+ value: [0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of the thread stacks in all ' +
+ 'processes in WebView'
+ },
+ 'memory:webview:all_processes:reported_by_os:system_memory:stack:proportional_resident_size':
+ {
+ value: [0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of the thread stacks ' +
+ 'in all processes in WebView'
+ },
+ 'memory:webview:all_processes:reported_by_os:system_memory:private_dirty_size':
+ {
+ value: [0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total private dirty size of system memory (RAM) used ' +
+ 'by all processes in WebView'
+ },
+ 'memory:webview:all_processes:reported_by_os:system_memory:proportional_resident_size':
+ {
+ value: [0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total proportional resident size (PSS) of system ' +
+ 'memory (RAM) used by all processes in WebView'
+ },
+ 'memory:webview:browser_process:process_count': {
+ value: [1, 1, 1, 1],
+ unit: count_smallerIsBetter,
+ description: 'total number of browser processes in WebView'
+ },
+ 'memory:webview:browser_process:reported_by_os:private_dirty_size': {
+ value: [0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total private dirty size reported by the OS for the ' +
+ 'browser process in WebView'
+ },
+ 'memory:webview:browser_process:reported_by_os:proportional_resident_size':
+ {
+ value: [0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total proportional resident size (PSS) reported by ' +
+ 'the OS for the browser process in WebView'
+ },
+ 'memory:webview:browser_process:reported_by_os:system_memory:ashmem:private_dirty_size':
+ {
+ value: [0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of ashmem in the browser process ' +
+ 'in WebView'
+ },
+ 'memory:webview:browser_process:reported_by_os:system_memory:ashmem:proportional_resident_size':
+ {
+ value: [0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of ashmem in the ' +
+ 'browser process in WebView'
+ },
+ 'memory:webview:browser_process:reported_by_os:system_memory:java_heap:private_dirty_size':
+ {
+ value: [0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of the Java heap in the browser ' +
+ 'process in WebView'
+ },
+ 'memory:webview:browser_process:reported_by_os:system_memory:java_heap:proportional_resident_size':
+ {
+ value: [0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of the Java heap in ' +
+ 'the browser process in WebView'
+ },
+ 'memory:webview:browser_process:reported_by_os:system_memory:native_heap:private_dirty_size':
+ {
+ value: [0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of the native heap in the browser ' +
+ 'process in WebView'
+ },
+ 'memory:webview:browser_process:reported_by_os:system_memory:native_heap:proportional_resident_size':
+ {
+ value: [0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of the native heap ' +
+ 'in the browser process in WebView'
+ },
+ 'memory:webview:browser_process:reported_by_os:system_memory:stack:private_dirty_size':
+ {
+ value: [0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'private dirty size of the thread stacks in the browser ' +
+ 'process in WebView'
+ },
+ 'memory:webview:browser_process:reported_by_os:system_memory:stack:proportional_resident_size':
+ {
+ value: [0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'proportional resident size (PSS) of the thread stacks ' +
+ 'in the browser process in WebView'
+ },
+ 'memory:webview:browser_process:reported_by_os:system_memory:private_dirty_size':
+ {
+ value: [0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total private dirty size of system memory (RAM) used ' +
+ 'by the browser process in WebView'
+ },
+ 'memory:webview:browser_process:reported_by_os:system_memory:proportional_resident_size':
+ {
+ value: [0, 0, 0],
+ unit: sizeInBytes_smallerIsBetter,
+ description: 'total proportional resident size (PSS) of system ' +
+ 'memory (RAM) used by the browser process in WebView'
+ },
+ 'memory:unknown_browser:all_processes:dump_count': {
+ value: [1],
+ unit: count_smallerIsBetter,
+ description: 'total number of all memory dumps added by an unknown ' +
+ 'browser to the trace'
+ },
+ 'memory:unknown_browser:all_processes:dump_count:detailed': {
+ value: [0],
+ unit: count_smallerIsBetter,
+ description: 'total number of detailed memory dumps added by an ' +
+ 'unknown browser to the trace'
+ },
+ 'memory:unknown_browser:all_processes:dump_count:light': {
+ value: [1],
+ unit: count_smallerIsBetter,
+ description: 'total number of light memory dumps added by an unknown ' +
+ 'browser to the trace'
+ },
+ 'memory:unknown_browser:all_processes:dump_count:background': {
+ value: [0],
+ unit: count_smallerIsBetter,
+ description: 'total number of background memory dumps added by an ' +
+ 'unknown browser to the trace'
+ },
+ 'memory:unknown_browser:all_processes:dump_count:heap_profiler': {
+ value: [0],
+ unit: count_smallerIsBetter,
+ description: 'total number of heap profiler memory dumps added by an ' +
+ 'unknown browser to the trace'
+ }
+ });
+
+ test('allProcessesHasBreakdown', function() {
+ const model = tr.c.TestUtils.newModel(function(model) {
+ const pBrowser = createChromeBrowserProcess(model);
+ const pRenderer = createProcessWithName(model, 'Renderer');
+ const pGpu = createProcessWithName(model, 'GPU Process');
+
+ const gmd = addGlobalMemoryDump(model, {ts: 20});
+ const pmdBrowser = addProcessMemoryDump(gmd, pBrowser, {ts: 19});
+ pmdBrowser.memoryAllocatorDumps = [
+ newAllocatorDump(pmdBrowser, 'malloc', {numerics: {
+ size: 8,
+ allocated_objects_size: 4
+ }})
+ ];
+
+ const pmdRenderer = addProcessMemoryDump(gmd, pRenderer, {ts: 20});
+ pmdRenderer.memoryAllocatorDumps = [
+ newAllocatorDump(pmdRenderer, 'malloc', {numerics: {size: 16}})
+ ];
+
+ const pmdGpu = addProcessMemoryDump(gmd, pGpu, {ts: 21});
+ pmdGpu.memoryAllocatorDumps = [
+ newAllocatorDump(pmdGpu, 'gpu', {numerics: {
+ size: 30,
+ allocated_objects_size: 25
+ }})
+ ];
+ });
+
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.memoryMetric(histograms, model);
+ const prefix = 'memory:chrome:';
+ const suffix = ':reported_by_chrome:malloc:effective_size';
+ const allHistogram =
+ histograms.getHistogramNamed(`${prefix}all_processes${suffix}`);
+ const rendererHistogram =
+ histograms.getHistogramNamed(`${prefix}renderer_processes${suffix}`);
+ const browserHistogram =
+ histograms.getHistogramNamed(`${prefix}browser_process${suffix}`);
+
+ const relatedNameMap = allHistogram.diagnostics.get('processes');
+ assert.strictEqual(relatedNameMap.get('browser_process'),
+ browserHistogram.name);
+ assert.strictEqual(relatedNameMap.get('renderer_processes'),
+ rendererHistogram.name);
+ assert.strictEqual(relatedNameMap.get('gpu_process'), undefined);
+
+ const bin = allHistogram.getBinForValue(allHistogram.average);
+ const breakdown = tr.b.getOnlyElement(bin.diagnosticMaps).get('processes');
+ assert.strictEqual(breakdown.get('browser_process'),
+ browserHistogram.average);
+ assert.strictEqual(breakdown.get('renderer_processes'),
+ rendererHistogram.average);
+ });
+
+ test('componentsHaveBreakdowns', function() {
+ const model = tr.c.TestUtils.newModel(function(model) {
+ const pBrowser = createChromeBrowserProcess(model);
+
+ const gmd = addGlobalMemoryDump(model, {ts: 20});
+ const pmdBrowser = addProcessMemoryDump(gmd, pBrowser, {ts: 19});
+ const v8Dump = newAllocatorDump(pmdBrowser, 'v8', {numerics: {
+ size: 10,
+ }});
+
+ const isolateDumpA = addChildDump(v8Dump, 'isolate_A');
+ addChildDump(isolateDumpA, 'malloc', {numerics: {
+ size: 1,
+ peak_size: 2
+ }});
+
+ const heapDumpA = addChildDump(isolateDumpA, 'heap_spaces', {numerics: {
+ size: 42,
+ allocated_objects_size: 36
+ }});
+
+ addChildDump(heapDumpA, 'code_space', {numerics: {
+ allocated_objects_size: 1,
+ size: 2
+ }});
+
+ pmdBrowser.memoryAllocatorDumps = [v8Dump];
+ });
+
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.memoryMetric(histograms, model);
+ const prefix = 'memory:chrome:browser_process:reported_by_chrome';
+ const suffix = 'effective_size';
+ const browserHistogram = histograms.getHistogramNamed(
+ `${prefix}:v8:${suffix}`);
+ const heapHistogram = histograms.getHistogramNamed(
+ `${prefix}:v8:heap:${suffix}`);
+ const codeSpaceHistogram = histograms.getHistogramNamed(
+ `${prefix}:v8:heap:code_space:${suffix}`);
+
+ assert.strictEqual(browserHistogram.diagnostics.get('components').get(
+ 'heap'), heapHistogram.name);
+ assert.strictEqual(heapHistogram.diagnostics.get('components').get(
+ 'code_space'), codeSpaceHistogram.name);
+
+ const browserBin = browserHistogram.getBinForValue(
+ browserHistogram.average);
+ const browserBreakdown = tr.b.getOnlyElement(
+ browserBin.diagnosticMaps).get('components');
+ assert.strictEqual(browserBreakdown.get('heap'), heapHistogram.average);
+
+ const heapBin = heapHistogram.getBinForValue(heapHistogram.average);
+ const heapBreakdown = tr.b.getOnlyElement(
+ heapBin.diagnosticMaps).get('components');
+ assert.strictEqual(heapBreakdown.get('code_space'),
+ codeSpaceHistogram.average);
+ });
+
+ test('dumpIdBrowserClashThrows', function() {
+ const model = tr.c.TestUtils.newModel(function(model) {
+ const pWebView = createWebViewProcess(model);
+ const pChrome = createChromeBrowserProcess(model);
+
+ const gmd = addGlobalMemoryDump(model, {ts: 10});
+ addProcessMemoryDump(gmd, pWebView, {ts: 9});
+ addProcessMemoryDump(gmd, pChrome, {ts: 11});
+ });
+ const histograms = new tr.v.HistogramSet();
+
+ assert.throws(function() {
+ tr.metrics.sh.memoryMetric(histograms, model);
+ }, 'Memory dump ID clash across multiple browsers with PIDs: 1 and 2');
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/system_health/new_cpu_time_metric.html b/chromium/third_party/catapult/tracing/tracing/metrics/system_health/new_cpu_time_metric.html
new file mode 100644
index 00000000000..81b4f65ca79
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/system_health/new_cpu_time_metric.html
@@ -0,0 +1,79 @@
+<!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/extras/chrome/cpu_time.html">
+<link rel="import" href="/tracing/metrics/metric_registry.html">
+<link rel="import" href="/tracing/metrics/system_health/cpu_time_tree_data_reporter.html">
+<link rel="import" href="/tracing/value/histogram.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Implements the new CPU time metric. This will eventually
+ * replace the current cpu_time_metric.html, but we're running this alongside
+ * the existing metric while we monitor its quality.
+ *
+ */
+tr.exportTo('tr.metrics.sh', function() {
+ /**
+ * This metric measures total CPU time and CPU time per unit of wall clock
+ * time for all combinations of process type, thread type, RAIL
+ * stage, and RAIL stage initiator present in the model.
+ *
+ * The metric generates histograms of the form
+ * ${cpuTime|cpuPercentage}:${process_type}:${thread_type}:
+ * ${rail_stage}:${rail_stage_initiator}
+ *
+ * 'cpuTime' histograms represent total consumed CPU time, while
+ * 'cpuPercentage' histograms represent CPU time as a percentage of wall clock
+ * time.
+ *
+ * Example histograms generated by this metric:
+ * - cpuTime:browser_process:CrBrowserMain:Animation:CSS
+ * - cpuPercentage:gpu_process:CrGpuMain:Response:Scroll
+
+ * For a given model, a single sample is generated for each histogram. For
+ * example, if the model contains three renderer processes, and 10 different
+ * Scroll Response ranges, the histogram
+ * cpuPercentage:renderer_process:CrRendererMain:Response:Scroll will still
+ * contain a single sample: the total CPU time consumed by all three renderer
+ * main threads over all 10 Scroll Response phases, divided by the total
+ * duration of those ranges. Since the three different main threads can
+ * potentially be running on three different CPU cores, the sample value of a
+ * cpuPercentage histogram can be more than 100%.
+ *
+ * The histograms are created as needed from the model - if a certain
+ * combination of process, thread, RAIL stage and initiator does not occur in
+ * the model, the histogram for that combination is not added.
+ *
+ * This metric requires only the 'toplevel' tracing category.
+ *
+ * @param {!tr.v.HistogramSet} histograms
+ * @param {!tr.model.Model} model
+ * @param {!Object=} opt_options
+ */
+ function newCpuTimeMetric(histograms, model, opt_options) {
+ const rangeOfInterest = opt_options && opt_options.rangeOfInterest ?
+ opt_options.rangeOfInterest : model.bounds;
+
+ const rootNode = tr.e.chrome.CpuTime.constructMultiDimensionalView(
+ model, rangeOfInterest);
+
+ tr.metrics.sh.CpuTimeTreeDataReporter.reportToHistogramSet(
+ rootNode, histograms);
+ }
+
+ tr.metrics.MetricRegistry.register(newCpuTimeMetric, {
+ supportsRangeOfInterest: true
+ });
+
+ return {
+ newCpuTimeMetric,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/system_health/new_cpu_time_metric_test.html b/chromium/third_party/catapult/tracing/tracing/metrics/system_health/new_cpu_time_metric_test.html
new file mode 100644
index 00000000000..a21b6b83ca6
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/system_health/new_cpu_time_metric_test.html
@@ -0,0 +1,186 @@
+<!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/multi_dimensional_view.html">
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/metrics/system_health/new_cpu_time_metric.html">
+<link rel="import" href="/tracing/value/histogram_set.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const cpuTimeMetric = tr.metrics.sh.newCpuTimeMetric;
+ const CHROME_PROCESS_NAMES =
+ tr.e.chrome.chrome_processes.CHROME_PROCESS_NAMES;
+
+ test('cpuTimeMetric_customRangeOfInterest', () => {
+ const model = tr.c.TestUtils.newModel(model => {
+ const process = model.getOrCreateProcess(1);
+ process.name = 'Browser';
+ const thread = process.getOrCreateThread(1);
+ thread.name = 'CrBrowserMain';
+
+ // Slice from 0 to 50.
+ thread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ type: tr.model.ThreadSlice,
+ isTopLevel: true,
+ start: 0,
+ duration: 50,
+ cpuStart: 0,
+ cpuDuration: 25
+ }));
+
+ // Slice from 300 to 350.
+ thread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ type: tr.model.ThreadSlice,
+ isTopLevel: true,
+ start: 300,
+ duration: 50,
+ cpuStart: 100,
+ cpuDuration: 25
+ }));
+ });
+
+ const metricNameSuffix = CHROME_PROCESS_NAMES.BROWSER +
+ ':CrBrowserMain:all_stages:all_initiators';
+ const rangeOfInterest = new tr.b.math.Range.fromExplicitRange(0, 200);
+ const histograms = new tr.v.HistogramSet();
+ cpuTimeMetric(histograms, model, {rangeOfInterest});
+
+ const cpuPercentageHistogram = histograms.getHistogramNamed(
+ 'cpuPercentage:' + metricNameSuffix);
+ const cpuTimeHistogram = histograms.getHistogramNamed(
+ 'cpuTime:' + metricNameSuffix);
+
+ // Histograms exist.
+ assert.isDefined(cpuPercentageHistogram);
+ assert.isDefined(cpuTimeHistogram);
+
+ // Each histogram contains a single sample.
+ assert.strictEqual(cpuPercentageHistogram.running.count, 1);
+ assert.strictEqual(cpuTimeHistogram.running.count, 1);
+
+ // Assert histogram sample value is correct:
+ // rangeOfInterest only contains the first slice (with a CPU time of 25),
+ // and total duration of the range is 200.
+ assert.closeTo(cpuPercentageHistogram.sum, 25 / 200, 1e-7);
+ assert.closeTo(cpuTimeHistogram.sum, 25, 1e-7);
+ });
+
+ test('cpuTimeMetric_rangeOfInterestBeyondModelBounds', () => {
+ const model = tr.c.TestUtils.newModel(model => {
+ const process = model.getOrCreateProcess(1);
+ process.name = 'Browser';
+ const thread = process.getOrCreateThread(1);
+ thread.name = 'CrBrowserMain';
+
+ // Slice from 0 to 50.
+ thread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ type: tr.model.ThreadSlice,
+ isTopLevel: true,
+ start: 0,
+ duration: 50,
+ cpuStart: 0,
+ cpuDuration: 25
+ }));
+
+ // Slice from 300 to 350.
+ thread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ type: tr.model.ThreadSlice,
+ isTopLevel: true,
+ start: 300,
+ duration: 50,
+ cpuStart: 100,
+ cpuDuration: 25
+ }));
+ });
+
+ const metricNameSuffix = CHROME_PROCESS_NAMES.BROWSER +
+ ':CrBrowserMain:all_stages:all_initiators';
+ const rangeOfInterest = new tr.b.math.Range.fromExplicitRange(-100, 200);
+ const histograms = new tr.v.HistogramSet();
+ cpuTimeMetric(histograms, model, {rangeOfInterest});
+
+ const cpuPercentageHistogram = histograms.getHistogramNamed(
+ 'cpuPercentage:' + metricNameSuffix);
+ const cpuTimeHistogram = histograms.getHistogramNamed(
+ 'cpuTime:' + metricNameSuffix);
+
+ // Histograms exist.
+ assert.isDefined(cpuPercentageHistogram);
+ assert.isDefined(cpuTimeHistogram);
+
+ // Each histogram contains a single sample.
+ assert.strictEqual(cpuPercentageHistogram.running.count, 1);
+ assert.strictEqual(cpuTimeHistogram.running.count, 1);
+
+ // Assert histogram sample value is correct:
+ // model.bounds is [0, 350] here. If rangeOfInterest is beyond these bounds,
+ // the effective range for calculating total duration for cpuPercentage is
+ // the intersection of rangeOfInterest and model.bounds, which is [0, 200]
+ // here. rangeOfInterest only contains the first slice, with a CPU time of
+ // 25, and the total effective duration is 200, not 300.
+ assert.closeTo(cpuPercentageHistogram.sum, 25 / 200, 1e-7);
+ assert.closeTo(cpuTimeHistogram.sum, 25, 1e-7);
+ });
+
+ test('cpuTimeMetric_defaultRangeOfInterest', () => {
+ const model = tr.c.TestUtils.newModel(model => {
+ const process = model.getOrCreateProcess(1);
+ process.name = 'Browser';
+ const thread = process.getOrCreateThread(1);
+ thread.name = 'CrBrowserMain';
+
+ // Slice from 0 to 50.
+ thread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ type: tr.model.ThreadSlice,
+ isTopLevel: true,
+ start: 0,
+ duration: 50,
+ cpuStart: 0,
+ cpuDuration: 25
+ }));
+
+ // Slice from 300 to 350.
+ thread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ type: tr.model.ThreadSlice,
+ isTopLevel: true,
+ start: 300,
+ duration: 50,
+ cpuStart: 100,
+ cpuDuration: 25
+ }));
+ });
+
+ const metricNameSuffix = CHROME_PROCESS_NAMES.BROWSER +
+ ':CrBrowserMain:all_stages:all_initiators';
+ const histograms = new tr.v.HistogramSet();
+ cpuTimeMetric(histograms, model);
+
+ const cpuPercentageHistogram = histograms.getHistogramNamed(
+ 'cpuPercentage:' + metricNameSuffix);
+ const cpuTimeHistogram = histograms.getHistogramNamed(
+ 'cpuTime:' + metricNameSuffix);
+
+ // Histograms exist.
+ assert.isDefined(cpuPercentageHistogram);
+ assert.isDefined(cpuTimeHistogram);
+
+ // Each histogram contains a single sample.
+ assert.strictEqual(cpuPercentageHistogram.running.count, 1);
+ assert.strictEqual(cpuTimeHistogram.running.count, 1);
+
+ // Assert histogram sample value is correct:
+ // When no custom rangeOfInterest is set, the effective range for
+ // calculating CPU usage should be model.bounds, which is [0, 350] here.
+ // Total CPU time is 25 + 25 from the two slices.
+ assert.closeTo(cpuPercentageHistogram.sum, (25 + 25) / 350, 1e-7);
+ assert.closeTo(cpuTimeHistogram.sum, 25 + 25, 1e-7);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/system_health/power_metric.html b/chromium/third_party/catapult/tracing/tracing/metrics/system_health/power_metric.html
new file mode 100644
index 00000000000..8b343e07f49
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/system_health/power_metric.html
@@ -0,0 +1,348 @@
+<!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/math/range.html">
+<link rel="import" href="/tracing/base/math/statistics.html">
+<link rel="import" href="/tracing/base/unit_scale.html">
+<link rel="import" href="/tracing/importer/find_input_expectations.html">
+<link rel="import" href="/tracing/metrics/metric_registry.html">
+<link rel="import" href="/tracing/value/histogram.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.metrics.sh', function() {
+ // If the power series doesn't cover the entire Chrome trace, then
+ // the results from Chrome tracing metrics will likely be inaccurate,
+ // so we don't report them. However, we allow the power series bounds
+ // to be up to 1 ms inside the Chrome trace and still count as
+ // covering the Chrome trace. This is to allow for small deviations
+ // due to clock sync latency and the finite sampling rate of the
+ // BattOr.
+ const CHROME_POWER_GRACE_PERIOD_MS = 1;
+
+ /**
+ * Creates an empty histogram to hold data for a given time interval.
+ *
+ * @returns {Object} An object of the form:
+ *
+ * {
+ * perSecond {boolean}: Whether the data for this time interval is given
+ * as per second, If not, it's given as an integral over the
+ * whole interval.
+ * energy {tr.v.Histogram}: Histogram giving energy use (i.e. energy in J
+ * if perSecond = False, power in W if perSecond = True)
+ * }
+ */
+ function createEmptyHistogram_(interval, histograms) {
+ if (interval.perSecond) {
+ return {
+ perSecond: true,
+ energy: histograms.createHistogram(`${interval.name}:power`,
+ tr.b.Unit.byName.powerInWatts_smallerIsBetter, [], {
+ description:
+ `Energy consumption rate for ${interval.description}`,
+ summaryOptions: {
+ avg: true,
+ count: false,
+ max: true,
+ min: true,
+ std: false,
+ sum: false,
+ },
+ }),
+ };
+ }
+ return {
+ perSecond: false,
+ energy: histograms.createHistogram(`${interval.name}:energy`,
+ tr.b.Unit.byName.energyInJoules_smallerIsBetter, [], {
+ description: `Energy consumed in ${interval.description}`,
+ summaryOptions: {
+ avg: false,
+ count: false,
+ max: true,
+ min: true,
+ std: false,
+ sum: true,
+ },
+ }),
+ };
+ }
+
+ function createHistograms_(data, interval, histograms) {
+ if (data.histograms[interval.name] === undefined) {
+ data.histograms[interval.name] = createEmptyHistogram_(interval,
+ histograms);
+ }
+ if (data.histograms[interval.name].perSecond) {
+ for (const sample of data.model.device.powerSeries.getSamplesWithinRange(
+ interval.bounds.min, interval.bounds.max)) {
+ data.histograms[interval.name].energy.addSample(sample.powerInW);
+ }
+ } else {
+ const energyInJ = data.model.device.powerSeries.getEnergyConsumedInJ(
+ interval.bounds.min, interval.bounds.max);
+ data.histograms[interval.name].energy.addSample(energyInJ);
+ }
+ }
+
+ /**
+ * Returns the intervals of time between navigation event and time to
+ * interactive.
+ */
+ function getNavigationTTIIntervals_(model) {
+ const chromeHelper = model.getOrCreateHelper(
+ tr.model.helpers.ChromeModelHelper);
+ const intervals = [];
+ for (const expectation of model.userModel.expectations) {
+ if (!(expectation instanceof tr.model.um.LoadExpectation)) continue;
+ if (tr.e.chrome.CHROME_INTERNAL_URLS.includes(
+ expectation.url)) {
+ continue;
+ }
+ if (expectation.timeToInteractive !== undefined) {
+ intervals.push(tr.b.math.Range.fromExplicitRange(
+ expectation.navigationStart.start, expectation.timeToInteractive));
+ }
+ }
+ return intervals.sort((x, y) => x.min - y.min);
+ }
+
+ /**
+ * Generates the set of time intervals that metrics should be run over.
+ * Time intervals include each UE (for UE-based metrics), the whole
+ * story (for the full-story metric), etc. Each time interval is given
+ * in the following form:
+ *
+ * {
+ * bounds {tr.b.math.Range}: Boundaries of the time interval.
+ * name {string}: Name of this interval. Used to generate the
+ * metric names.
+ * description {string}: Human readable description of the interval.
+ * Used to generate the metric descriptions.
+ * perSecond {boolean}: Whether metrics over this interval should be
+ * reported as per-second values (e.g. power). If not, integrated values
+ * over the whole interval (e.g. energy) are reported.
+ * }
+ *
+ */
+ function* computeTimeIntervals_(model) {
+ const chromeHelper = model.getOrCreateHelper(
+ tr.model.helpers.ChromeModelHelper);
+ const powerSeries = model.device.powerSeries;
+ if (powerSeries === undefined ||
+ powerSeries.samples.length === 0) {
+ return;
+ }
+ // Output the full story power metrics, which exists regardless of
+ // the presence of a Chrome trace.
+ yield {
+ bounds: model.bounds,
+ name: 'story',
+ description: 'user story',
+ perSecond: true
+ };
+
+ const chromeBounds = computeChromeBounds_(model);
+ if (chromeBounds.isEmpty) return;
+
+ const powerSeriesBoundsWithGracePeriod = tr.b.math.Range.fromExplicitRange(
+ powerSeries.bounds.min - CHROME_POWER_GRACE_PERIOD_MS,
+ powerSeries.bounds.max + CHROME_POWER_GRACE_PERIOD_MS);
+ if (!powerSeriesBoundsWithGracePeriod.containsRangeExclusive(
+ chromeBounds)) {
+ return;
+ }
+
+ // If Chrome bounds are good and the power trace covers the Chrome bounds,
+ // then output the Chrome specific metrics (loading and RAIL stages). Note
+ // that only the part of the time interval that overlaps the Chrome bounds
+ // should be included.
+ for (const interval of getRailStageIntervals_(model)) {
+ yield {
+ bounds: interval.bounds.findIntersection(chromeBounds),
+ name: interval.name,
+ description: interval.description,
+ perSecond: interval.perSecond
+ };
+ }
+
+ for (const interval of getLoadingIntervals_(model, chromeBounds)) {
+ yield {
+ bounds: interval.bounds.findIntersection(chromeBounds),
+ name: interval.name,
+ description: interval.description,
+ perSecond: interval.perSecond
+ };
+ }
+ }
+
+ /**
+ * Gets a list of time intervals for the RAIL stages. Each RAIL stage
+ * generates a time interval with the name equal to the name of the RAIL
+ * stage except made into lower case and with spaces replaced bu underscores
+ * e.g. "Scroll Animation" --> "scroll_animation". Each time interval is given
+ * in the following form:
+ *
+ * {
+ * bounds {tr.b.math.Range}: Boundaries of the time interval.
+ * name {string}: Name of this interval. Used to generate the
+ * metric names.
+ * description {string}: Human readable description of the interval.
+ * Used to generate the metric descriptions.
+ * perSecond {boolean}: Whether metrics over this interval should be
+ * reported as per-second values (e.g. power). If not, integrated values
+ * over the whole interval (e.g. energy) are reported.
+ * }
+ *
+ */
+ function* getRailStageIntervals_(model) {
+ for (const exp of model.userModel.expectations) {
+ const histogramName = exp.title.toLowerCase().replace(' ', '_');
+ const energyHist = undefined;
+ if (histogramName.includes('response')) {
+ yield {
+ bounds: tr.b.math.Range.fromExplicitRange(exp.start, exp.end),
+ name: histogramName,
+ description: 'RAIL stage ' + histogramName,
+ perSecond: false
+ };
+ } else if (histogramName.includes('animation') ||
+ histogramName.includes('idle')) {
+ yield {
+ bounds: tr.b.math.Range.fromExplicitRange(exp.start, exp.end),
+ name: histogramName,
+ description: 'RAIL stage ' + histogramName,
+ perSecond: true
+ };
+ }
+ }
+ }
+
+ /**
+ * Gets a list of time intervals for the RAIL stages. Each RAIL stage
+ * generates a time interval with the name equal to the name of the RAIL
+ * stage except made into lower case and with spaces replaced bu underscores
+ * e.g. "Scroll Animation" --> "scroll_animation". Each time interval is given
+ * in the following form:
+ *
+ * {
+ * bounds {tr.b.math.Range}: Boundaries of the time interval.
+ * name {string}: Name of this interval. Used to generate the
+ * metric names.
+ * description {string}: Human readable description of the interval.
+ * Used to generate the metric descriptions.
+ * perSecond {boolean}: Whether metrics over this interval should be
+ * reported as per-second values (e.g. power). If not, integrated values
+ * over the whole interval (e.g. energy) are reported.
+ * }
+ *
+ */
+ function* getLoadingIntervals_(model, chromeBounds) {
+ const ttiIntervals = getNavigationTTIIntervals_(model);
+ for (const ttiInterval of ttiIntervals) {
+ yield {
+ bounds: ttiInterval,
+ name: 'load',
+ description: 'page loads',
+ perSecond: false
+ };
+ }
+ }
+
+ /**
+ * @returns {tr.b.math.Range} The boundaries of the Chrome portion of the
+ * trace.
+ */
+ function computeChromeBounds_(model) {
+ const chromeBounds = new tr.b.math.Range();
+ const chromeHelper = model.getOrCreateHelper(
+ tr.model.helpers.ChromeModelHelper);
+ if (chromeHelper === undefined) return chromeBounds;
+ for (const helper of chromeHelper.browserHelpers) {
+ if (helper.mainThread) {
+ chromeBounds.addRange(helper.mainThread.bounds);
+ }
+ }
+ for (const pid in chromeHelper.rendererHelpers) {
+ if (chromeHelper.rendererHelpers[pid].mainThread) {
+ chromeBounds.addRange(
+ chromeHelper.rendererHelpers[pid].mainThread.bounds);
+ }
+ }
+ return chromeBounds;
+ }
+
+ /**
+ * Adds the power histograms to the histogram set.
+ *
+ * Each power histogram is based on a specific time interval, and is named as
+ * follows:
+ *
+ * - [time_interval_name]:power, which contains a sample for each power
+ * sample data point during any time interval with the given type. Each
+ * sample represents the power draw during the period covered by that
+ * power sample.
+ *
+ * - [time_interval_name]:energy, which contains a sample for each time
+ * interval with the given type present in the trace. Each sample
+ * represents the total energy used over that time interval.
+ *
+ * The time intervals are as follows:
+ *
+ * - "story": The time interval covering the entire user story. There is
+ * always exactly one "story" interval.
+ *
+ * - "load" : The time interval covered by a page load, from navigationStart
+ * to timeToInteractive. There is one "load" interval for each page load
+ * in the trace.
+ *
+ * - "[user_expectation_type]" : Each Response, Animation, or Idle
+ * UE in the trace generates a time interval whose name is that of the UE,
+ * converted to lower case and with underscores in place of spaces.
+ * For instance, if there are three "Scroll Response" UEs in the trace,
+ * then there will be three "scroll_response" time intervals, so the
+ * histogram scroll_response:energy will contain three samples.
+ *
+ * Note that each time interval type only generates ONE of the "power" or
+ * "energy" histograms. Power histograms are generated for time intervals
+ * that represent events that occur over a period of time. This includes
+ * the following intervals
+ *
+ * - "story"
+ * - Any Animation or Idle UE
+ *
+ * For instance, "the energy it takes to play a video"
+ * does not have meaning because it depends on how long the video
+ * is; thus a more meaningful metric is power. In contrast, energy histograms
+ * are generated for time intervals that represent particular tasks
+ * which must be completed. This includes the following intervals:
+ *
+ * - "load"
+ * - Any Response UE
+ *
+ * For instance, if a change causes a response to take longer to process, then
+ * we want to count that as taking the energy over a longer period of time.
+ */
+ function powerMetric(histograms, model) {
+ const data = {
+ model,
+ histograms: {}
+ };
+ for (const interval of computeTimeIntervals_(model)) {
+ createHistograms_(data, interval, histograms);
+ }
+ }
+
+ tr.metrics.MetricRegistry.register(powerMetric);
+
+ return {
+ powerMetric
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/system_health/power_metric_test.html b/chromium/third_party/catapult/tracing/tracing/metrics/system_health/power_metric_test.html
new file mode 100644
index 00000000000..9e8d47ea258
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/system_health/power_metric_test.html
@@ -0,0 +1,742 @@
+<!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/extras/chrome/chrome_test_utils.html">
+<link rel="import"
+ href="/tracing/extras/chrome/chrome_user_friendly_category_driver.html">
+<link rel="import" href="/tracing/importer/find_input_expectations.html">
+<link rel="import" href="/tracing/metrics/system_health/power_metric.html">
+<link rel="import" href="/tracing/model/user_model/idle_expectation.html">
+<link rel="import" href="/tracing/model/user_model/load_expectation.html">
+<link rel="import" href="/tracing/model/user_model/user_expectation.html">
+<link rel="import" href="/tracing/value/histogram_set.html">
+
+<script>
+'use strict';
+
+function containsData(histograms, name) {
+ for (const value of histograms) {
+ if (value.name === name) {
+ return (value.running !== undefined);
+ }
+ }
+
+ return undefined;
+}
+
+function getMetricValueCount(histograms, name) {
+ for (const value of histograms) {
+ if (value.name === name && value.running !== undefined) {
+ return value.running.count;
+ }
+ }
+
+ return undefined;
+}
+
+function getMetricValueSum(histograms, name) {
+ for (const value of histograms) {
+ if (value.name === name && value.running !== undefined) {
+ return value.running.sum;
+ }
+ }
+
+ return undefined;
+}
+
+function getMetricValueMin(histograms, name) {
+ for (const value of histograms) {
+ if (value.name === name && value.running !== undefined) {
+ return value.running.min;
+ }
+ }
+
+ return undefined;
+}
+
+function getMetricValueAvg(histograms, name) {
+ for (const value of histograms) {
+ if (value.name === name && value.running !== undefined) {
+ return value.running.mean;
+ }
+ }
+
+ return undefined;
+}
+
+function getMetricValueMax(histograms, name) {
+ for (const value of histograms) {
+ if (value.name === name && value.running !== undefined) {
+ return value.running.max;
+ }
+ }
+
+ return undefined;
+}
+
+tr.b.unittest.testSuite(function() {
+ test('powerMetric_computesChromeBoundsCorrectly', function() {
+ // Tests if Chrome bounds are computed correctly when there
+ // are both browser and renderer threads in the trace.
+ const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
+ const rendererProcess = model.rendererProcess;
+ const rendererThread = model.rendererMain;
+ const browserProcess = model.browserProcess;
+ const browserThread = model.browserMain;
+ rendererThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: 200.0,
+ duration: 500.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ browserThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: 1000.0,
+ duration: 500.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ model.device.powerSeries = new tr.model.PowerSeries(model.device);
+ for (let i = 0; i <= 2000; i++) {
+ model.device.powerSeries.addPowerSample(i, i);
+ }
+ model.userModel.expectations.push(new tr.model.um.IdleExpectation(
+ model, 0, 2000));
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.powerMetric(histograms, model);
+
+ assert.closeTo(getMetricValueMin(histograms, 'idle:power'), 200.0, .01);
+ assert.closeTo(getMetricValueAvg(histograms, 'idle:power'), 849.5, .01);
+ assert.closeTo(getMetricValueMax(histograms, 'idle:power'), 1499.0, .01);
+ });
+
+ test('powerMetric_noPowerSeries', function() {
+ const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
+ const rendererProcess = model.rendererProcess;
+ const mainThread = model.rendererMain;
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: 0,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: 1000,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.powerMetric(histograms, model);
+
+ assert.lengthOf(histograms, 0);
+ });
+
+ test('powerMetric_emptyPowerSeries', function() {
+ const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
+ const rendererProcess = model.rendererProcess;
+ const mainThread = model.rendererMain;
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: 0,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: 1000,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ model.device.powerSeries = new tr.model.PowerSeries(model.device);
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.powerMetric(histograms, model);
+
+ assert.lengthOf(histograms, 0);
+ });
+
+ test('powerMetric_noChromeTrace', function() {
+ const model = tr.c.TestUtils.newModel(function(model) {
+ model.device.powerSeries = new tr.model.PowerSeries(model.device);
+ for (let i = 0; i <= 1000; i++) {
+ model.device.powerSeries.addPowerSample(i, i);
+ }
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.powerMetric(histograms, model);
+
+ // Only the whole trace power metric should contain data.
+ assert(containsData(histograms, 'story:power'));
+ assert(!containsData(histograms, 'load:energy'));
+ assert(!containsData(histograms, 'after_load:power'));
+ });
+
+ test('powerMetric_emptyChromeTrace', function() {
+ const histograms = new tr.v.HistogramSet();
+ const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
+ model.device.powerSeries = new tr.model.PowerSeries(model.device);
+ for (let i = 0; i <= 1000; i++) {
+ model.device.powerSeries.addPowerSample(i, i);
+ }
+ model.userModel.expectations.push(new tr.model.um.LoadExpectation(
+ model, tr.model.um.LOAD_SUBTYPE_NAMES.SUCCESSFUL, 0, 500));
+ model.userModel.expectations.push(new tr.model.um.IdleExpectation(
+ model, 500, 500));
+ });
+ tr.metrics.sh.powerMetric(histograms, model);
+
+ assert(containsData(histograms, 'story:power'));
+ assert(!containsData(histograms, 'load:energy'));
+ assert(!containsData(histograms, 'after_load:power'));
+ });
+
+ test('powerMetric_powerSeriesStartsLate', function() {
+ const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
+ const rendererProcess = model.rendererProcess;
+ const mainThread = model.rendererMain;
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: 0,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: 1000,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ model.device.powerSeries = new tr.model.PowerSeries(model.device);
+ for (let i = 300; i <= 1000; i++) {
+ model.device.powerSeries.addPowerSample(i, i);
+ }
+ model.userModel.expectations.push(new tr.model.um.LoadExpectation(
+ model, tr.model.um.LOAD_SUBTYPE_NAMES.SUCCESSFUL, 0, 500));
+ model.userModel.expectations.push(new tr.model.um.IdleExpectation(
+ model, 500, 500));
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.powerMetric(histograms, model);
+
+ assert(containsData(histograms, 'story:power'));
+ assert(!containsData(histograms, 'load:energy'));
+ assert(!containsData(histograms, 'after_load:power'));
+ });
+
+ test('powerMetric_powerSeriesEndsEarly', function() {
+ const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
+ const rendererProcess = model.rendererProcess;
+ const mainThread = model.rendererMain;
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: 0,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: 1000,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ model.device.powerSeries = new tr.model.PowerSeries(model.device);
+ for (let i = 0; i <= 700; i++) {
+ model.device.powerSeries.addPowerSample(i, i);
+ }
+ model.userModel.expectations.push(new tr.model.um.LoadExpectation(
+ model, tr.model.um.LOAD_SUBTYPE_NAMES.SUCCESSFUL, 0, 500));
+ model.userModel.expectations.push(new tr.model.um.IdleExpectation(
+ model, 500, 500));
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.powerMetric(histograms, model);
+
+ assert(containsData(histograms, 'story:power'));
+ assert(!containsData(histograms, 'load:energy'));
+ assert(!containsData(histograms, 'after_load:power'));
+ });
+
+ test('powerMetric_generic_oneStageEachType_irBeyondChrome', function() {
+ const model = tr.c.TestUtils.newModel(function(model) {
+ const rendererProcess = model.getOrCreateProcess(1234);
+ const mainThread = rendererProcess.getOrCreateThread(1);
+ mainThread.name = 'CrRendererMain';
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: 0,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: 1000,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ model.device.powerSeries = new tr.model.PowerSeries(model.device);
+ for (let i = 0; i <= 1000; i++) {
+ model.device.powerSeries.addPowerSample(i, i);
+ }
+ model.userModel.expectations.push(new tr.model.um.ResponseExpectation(
+ model, tr.model.um.INITIATOR_TYPE.SCROLL, 0, 500));
+ model.userModel.expectations.push(new tr.model.um.IdleExpectation(
+ model, 500, 1500));
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.powerMetric(histograms, model);
+
+ assert.closeTo(getMetricValueSum(histograms,
+ 'scroll_response:energy'), 125, 0.5);
+ assert.closeTo(getMetricValueAvg(histograms,
+ 'idle:power'), 750, 0.5);
+ assert.closeTo(getMetricValueAvg(histograms,
+ 'story:power'), 500, 0.5);
+ });
+
+ test('powerMetric_story_minAvgMax', function() {
+ const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
+ const rendererProcess = model.rendererProcess;
+ const mainThread = model.rendererMain;
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: 0,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: 1000,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ model.device.powerSeries = new tr.model.PowerSeries(model.device);
+ for (let i = 0; i <= 1000; i++) {
+ model.device.powerSeries.addPowerSample(i, i);
+ }
+ model.userModel.expectations.push(new tr.model.um.ResponseExpectation(
+ model, tr.model.um.INITIATOR_TYPE.SCROLL, 0, 500));
+ model.userModel.expectations.push(new tr.model.um.IdleExpectation(
+ model, 500, 1500));
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.powerMetric(histograms, model);
+
+ assert.closeTo(getMetricValueMin(histograms, 'story:power'), 0, .01);
+ assert.closeTo(getMetricValueAvg(histograms, 'story:power'), 500, .01);
+ assert.closeTo(getMetricValueMax(histograms, 'story:power'), 1000, .01);
+ });
+
+ test('powerMetric_generic_oneUEBeforeChrome', function() {
+ const model = tr.c.TestUtils.newModel(function(model) {
+ const rendererProcess = model.getOrCreateProcess(1234);
+ const mainThread = rendererProcess.getOrCreateThread(1);
+ mainThread.name = 'CrRendererMain';
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: 500,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: 1000,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ model.device.powerSeries = new tr.model.PowerSeries(model.device);
+ for (let i = 0; i <= 1000; i++) {
+ model.device.powerSeries.addPowerSample(i, i);
+ }
+ model.userModel.expectations.push(new tr.model.um.IdleExpectation(
+ model, 0, 300));
+ model.userModel.expectations.push(new tr.model.um.IdleExpectation(
+ model, 300, 1000));
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.powerMetric(histograms, model);
+
+ assert.closeTo(getMetricValueAvg(histograms,
+ 'idle:power'), 750, 0.5);
+ });
+
+ test('powerMetric_generic_multipleStagesEachType', function() {
+ const model = tr.c.TestUtils.newModel(function(model) {
+ const rendererProcess = model.getOrCreateProcess(1234);
+ const mainThread = rendererProcess.getOrCreateThread(1);
+ mainThread.name = 'CrRendererMain';
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: 0,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: 1000,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ model.device.powerSeries = new tr.model.PowerSeries(model.device);
+ for (let i = 0; i <= 1000; i++) {
+ model.device.powerSeries.addPowerSample(i, i);
+ }
+ model.userModel.expectations.push(new tr.model.um.ResponseExpectation(
+ model, tr.model.um.INITIATOR_TYPE.SCROLL, 0, 200));
+ model.userModel.expectations.push(new tr.model.um.IdleExpectation(
+ model, 200, 300));
+ model.userModel.expectations.push(new tr.model.um.ResponseExpectation(
+ model, tr.model.um.INITIATOR_TYPE.SCROLL, 500, 400));
+ model.userModel.expectations.push(new tr.model.um.IdleExpectation(
+ model, 900, 100));
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.powerMetric(histograms, model);
+
+ assert.closeTo(getMetricValueSum(histograms,
+ 'scroll_response:energy'), 300, 0.6);
+ assert.closeTo(getMetricValueAvg(histograms,
+ 'idle:power'), 500, 0.6);
+ assert.strictEqual(getMetricValueCount(histograms,
+ 'scroll_response:energy'), 2);
+ assert.strictEqual(getMetricValueCount(histograms,
+ 'idle:power'), 400);
+ });
+
+ test('powerMetric_loading_oneInterval_samplesBeyondChrome', function() {
+ // Interval of load is [200, 15400].
+ // Trace goes until 22150.
+ const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
+ const rendererProcess = model.rendererProcess;
+ const mainThread = model.rendererMain;
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: 200,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ model.rendererMain.asyncSliceGroup.push(tr.c.TestUtils.newSliceEx({
+ cat: 'disabled-by-default-network',
+ title: 'ResourceLoad',
+ start: 200,
+ duration: 5.0
+ }));
+ rendererProcess.objects.addSnapshot('ptr', 'loading', 'FrameLoader', 300,
+ {isLoadingMainFrame: true, frame: {id_ref: '0xdeadbeef'},
+ documentLoaderURL: 'http://example.com'});
+
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'loading',
+ title: 'firstMeaningfulPaintCandidate',
+ start: 9180,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'loading',
+ title: 'firstMeaningfulPaintCandidate',
+ start: 9200,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'domContentLoadedEventEnd',
+ start: 9200,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'toplevel',
+ title: tr.e.chrome.SCHEDULER_TOP_LEVEL_TASK_TITLE,
+ start: 9350,
+ duration: 100,
+ }));
+
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'toplevel',
+ title: tr.e.chrome.SCHEDULER_TOP_LEVEL_TASK_TITLE,
+ start: 11150,
+ duration: 100,
+ }));
+
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'toplevel',
+ title: tr.e.chrome.SCHEDULER_TOP_LEVEL_TASK_TITLE,
+ start: 12550,
+ duration: 100,
+ }));
+
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'toplevel',
+ title: tr.e.chrome.SCHEDULER_TOP_LEVEL_TASK_TITLE,
+ start: 14900,
+ duration: 500,
+ }));
+
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'toplevel',
+ title: tr.e.chrome.SCHEDULER_TOP_LEVEL_TASK_TITLE,
+ start: 22150,
+ duration: 10,
+ }));
+
+ model.device.powerSeries = new tr.model.PowerSeries(model.device);
+ for (let i = 0; i <= 15400; i++) {
+ model.device.powerSeries.addPowerSample(i, 20);
+ }
+ for (let i = 15401; i <= 22160; i++) {
+ model.device.powerSeries.addPowerSample(i, 10);
+ }
+ for (let i = 22160; i <= 30000; i++) {
+ model.device.powerSeries.addPowerSample(i, 10);
+ }
+ });
+
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.powerMetric(histograms, model);
+ // Energy for first load is 20 W * 15.2 s
+ // (interval from 0.2 s to 15.4 s)
+ assert.closeTo(
+ getMetricValueAvg(histograms, 'load:energy'), 304, 0.1);
+ });
+
+ test('powerMetric_loading_noMeaningfulPaint', function() {
+ const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
+ const rendererProcess = model.rendererProcess;
+ const mainThread = model.rendererMain;
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: 200,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ rendererProcess.objects.addSnapshot('ptr', 'loading', 'FrameLoader', 300,
+ {isLoadingMainFrame: true, frame: {id_ref: '0xdeadbeef'},
+ documentLoaderURL: 'http://example.com'});
+
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'toplevel',
+ title: tr.e.chrome.SCHEDULER_TOP_LEVEL_TASK_TITLE,
+ start: 9350,
+ duration: 100,
+ }));
+
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'toplevel',
+ title: tr.e.chrome.SCHEDULER_TOP_LEVEL_TASK_TITLE,
+ start: 11150,
+ duration: 100,
+ }));
+
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'toplevel',
+ title: tr.e.chrome.SCHEDULER_TOP_LEVEL_TASK_TITLE,
+ start: 12550,
+ duration: 100,
+ }));
+
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'toplevel',
+ title: tr.e.chrome.SCHEDULER_TOP_LEVEL_TASK_TITLE,
+ start: 14950,
+ duration: 500,
+ }));
+
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'toplevel',
+ title: tr.e.chrome.SCHEDULER_TOP_LEVEL_TASK_TITLE,
+ start: 22150,
+ duration: 10,
+ }));
+
+ model.device.powerSeries = new tr.model.PowerSeries(model.device);
+ for (let i = 0; i <= 15400; i++) {
+ model.device.powerSeries.addPowerSample(i, 20);
+ }
+ for (let i = 15401; i <= 22160; i++) {
+ model.device.powerSeries.addPowerSample(i, 10);
+ }
+ });
+
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.powerMetric(histograms, model);
+ // Energy for first load is 20 W * 15.2 s
+ // (interval from 0.2 s to 15.4 s)
+ assert.isUndefined(getMetricValueCount(histograms, 'after_load:power'));
+ });
+
+ test('powerMetric_scroll_oneStageEachType', function() {
+ const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
+ const rendererProcess = model.rendererProcess;
+ const mainThread = model.rendererMain;
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: 0,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: 1000,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ model.device.powerSeries = new tr.model.PowerSeries(model.device);
+ for (let i = 0; i <= 1000; i++) {
+ model.device.powerSeries.addPowerSample(i, i);
+ }
+ model.userModel.expectations.push(new tr.model.um.AnimationExpectation(
+ model, tr.model.um.INITIATOR_TYPE.SCROLL, 0, 500));
+ model.userModel.expectations.push(new tr.model.um.IdleExpectation(
+ model, 500, 500));
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.powerMetric(histograms, model);
+
+ assert.closeTo(getMetricValueAvg(histograms,
+ 'scroll_animation:power'), 250, 0.5);
+ });
+
+ test('powerMetric_scroll_multipleStagesEachType', function() {
+ const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
+ const rendererProcess = model.rendererProcess;
+ const mainThread = model.rendererMain;
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: 0,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: 1000,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ model.device.powerSeries = new tr.model.PowerSeries(model.device);
+ for (let i = 0; i <= 1000; i++) {
+ model.device.powerSeries.addPowerSample(i, i);
+ }
+ model.userModel.expectations.push(new tr.model.um.AnimationExpectation(
+ model, tr.model.um.INITIATOR_TYPE.SCROLL, 0, 200));
+ model.userModel.expectations.push(new tr.model.um.IdleExpectation(
+ model, 200, 300));
+ model.userModel.expectations.push(new tr.model.um.AnimationExpectation(
+ model, tr.model.um.INITIATOR_TYPE.SCROLL, 500, 200));
+ model.userModel.expectations.push(new tr.model.um.IdleExpectation(
+ model, 700, 300));
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.powerMetric(histograms, model);
+
+ assert.closeTo(getMetricValueAvg(histograms,
+ 'scroll_animation:power'), 350, 0.6);
+ });
+
+ test('powerMetric_video_oneStageEachType', function() {
+ const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
+ const rendererProcess = model.rendererProcess;
+ const mainThread = model.rendererMain;
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: 0,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: 1000,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ model.device.powerSeries = new tr.model.PowerSeries(model.device);
+ for (let i = 0; i <= 1000; i++) {
+ model.device.powerSeries.addPowerSample(i, i);
+ }
+ model.userModel.expectations.push(new tr.model.um.AnimationExpectation(
+ model, tr.model.um.INITIATOR_TYPE.VIDEO, 0, 500));
+ model.userModel.expectations.push(new tr.model.um.IdleExpectation(
+ model, 500, 500));
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.powerMetric(histograms, model);
+
+ assert.closeTo(getMetricValueAvg(histograms, 'video_animation:power'),
+ 250, 0.5);
+ });
+
+ test('powerMetric_video_multipleStagesEachType', function() {
+ const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
+ const rendererProcess = model.rendererProcess;
+ const mainThread = model.rendererMain;
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: 0,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: 1000,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ model.device.powerSeries = new tr.model.PowerSeries(model.device);
+ for (let i = 0; i <= 1000; i++) {
+ model.device.powerSeries.addPowerSample(i, i);
+ }
+ model.userModel.expectations.push(new tr.model.um.AnimationExpectation(
+ model, tr.model.um.INITIATOR_TYPE.VIDEO, 0, 200));
+ model.userModel.expectations.push(new tr.model.um.IdleExpectation(
+ model, 200, 300));
+ model.userModel.expectations.push(new tr.model.um.AnimationExpectation(
+ model, tr.model.um.INITIATOR_TYPE.VIDEO, 500, 200));
+ model.userModel.expectations.push(new tr.model.um.IdleExpectation(
+ model, 700, 300));
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.powerMetric(histograms, model);
+
+ assert.closeTo(getMetricValueAvg(histograms, 'video_animation:power'),
+ 350, 0.6);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/system_health/responsiveness_metric.html b/chromium/third_party/catapult/tracing/tracing/metrics/system_health/responsiveness_metric.html
new file mode 100644
index 00000000000..911c2d66d48
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/system_health/responsiveness_metric.html
@@ -0,0 +1,162 @@
+<!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/statistics.html">
+<link rel="import" href="/tracing/metrics/metric_registry.html">
+<link rel="import" href="/tracing/metrics/system_health/utils.html">
+<link rel="import" href="/tracing/model/user_model/animation_expectation.html">
+<link rel="import" href="/tracing/model/user_model/load_expectation.html">
+<link rel="import" href="/tracing/model/user_model/response_expectation.html">
+<link rel="import" href="/tracing/value/histogram.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.metrics.sh', function() {
+ // In the case of Response, Load, and DiscreteAnimation UEs, Responsiveness is
+ // derived from the time between when the user thinks they begin an interation
+ // (expectedStart) and the time when the screen first changes to reflect the
+ // interaction (actualEnd). There may be a delay between expectedStart and
+ // when chrome first starts processing the interaction (actualStart) if the
+ // main thread is busy. The user doesn't know when actualStart is, they only
+ // know when expectedStart is. User responsiveness, by definition, considers
+ // only what the user experiences, so "duration" is defined as actualEnd -
+ // expectedStart.
+
+ function computeAnimationThroughput(animationExpectation) {
+ if (animationExpectation.frameEvents === undefined ||
+ animationExpectation.frameEvents.length === 0) {
+ throw new Error('Animation missing frameEvents ' +
+ animationExpectation.stableId);
+ }
+
+ const durationInS = tr.b.convertUnit(animationExpectation.duration,
+ tr.b.UnitPrefixScale.METRIC.MILLI,
+ tr.b.UnitPrefixScale.METRIC.NONE);
+ return animationExpectation.frameEvents.length / durationInS;
+ }
+
+ function computeAnimationframeTimeDiscrepancy(animationExpectation) {
+ if (animationExpectation.frameEvents === undefined ||
+ animationExpectation.frameEvents.length === 0) {
+ throw new Error('Animation missing frameEvents ' +
+ animationExpectation.stableId);
+ }
+
+ let frameTimestamps = animationExpectation.frameEvents;
+ frameTimestamps = frameTimestamps.toArray().map(function(event) {
+ return event.start;
+ });
+
+ const absolute = true;
+ return tr.b.math.Statistics.timestampsDiscrepancy(
+ frameTimestamps, absolute);
+ }
+
+ /**
+ * @param {!tr.v.HistogramSet} histograms
+ * @param {!tr.model.Model} model
+ * @param {!Object=} opt_options
+ */
+ function responsivenessMetric(histograms, model, opt_options) {
+ const responseNumeric = new tr.v.Histogram('response latency',
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,
+ tr.v.HistogramBinBoundaries.createLinear(100, 1e3, 50));
+ const throughputNumeric = new tr.v.Histogram('animation throughput',
+ tr.b.Unit.byName.unitlessNumber_biggerIsBetter,
+ tr.v.HistogramBinBoundaries.createLinear(10, 60, 10));
+ const frameTimeDiscrepancyNumeric = new tr.v.Histogram(
+ 'animation frameTimeDiscrepancy',
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,
+ tr.v.HistogramBinBoundaries.createLinear(0, 1e3, 50).
+ addExponentialBins(1e4, 10));
+ const latencyNumeric = new tr.v.Histogram('animation latency',
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,
+ tr.v.HistogramBinBoundaries.createLinear(0, 300, 60));
+
+ model.userModel.expectations.forEach(function(ue) {
+ if (opt_options && opt_options.rangeOfInterest &&
+ !opt_options.rangeOfInterest.intersectsExplicitRangeInclusive(
+ ue.start, ue.end)) {
+ return;
+ }
+
+ const sampleDiagnosticMap = tr.v.d.DiagnosticMap.fromObject(
+ {relatedEvents: new tr.v.d.RelatedEventSet([ue])});
+
+ // Responsiveness is not defined for Idle or Startup expectations.
+ if (ue instanceof tr.model.um.IdleExpectation) {
+ return;
+ } else if (ue instanceof tr.model.um.StartupExpectation) {
+ return;
+ } else if (ue instanceof tr.model.um.LoadExpectation) {
+ // This is already covered by loadingMetric.
+ } else if (ue instanceof tr.model.um.ResponseExpectation) {
+ responseNumeric.addSample(ue.duration, sampleDiagnosticMap);
+ } else if (ue instanceof tr.model.um.AnimationExpectation) {
+ if (ue.frameEvents === undefined || ue.frameEvents.length === 0) {
+ // Ignore animation stages that do not have associated frames:
+ // https://github.com/catapult-project/catapult/issues/2446
+ return;
+ }
+ const throughput = computeAnimationThroughput(ue);
+ if (throughput === undefined) {
+ throw new Error('Missing throughput for ' +
+ ue.stableId);
+ }
+
+ throughputNumeric.addSample(throughput, sampleDiagnosticMap);
+
+ const frameTimeDiscrepancy = computeAnimationframeTimeDiscrepancy(ue);
+ if (frameTimeDiscrepancy === undefined) {
+ throw new Error('Missing frameTimeDiscrepancy for ' +
+ ue.stableId);
+ }
+
+ frameTimeDiscrepancyNumeric.addSample(
+ frameTimeDiscrepancy, sampleDiagnosticMap);
+
+ ue.associatedEvents.forEach(function(event) {
+ if (!(event instanceof tr.e.cc.InputLatencyAsyncSlice)) {
+ return;
+ }
+
+ latencyNumeric.addSample(event.duration, sampleDiagnosticMap);
+ });
+ } else {
+ throw new Error('Unrecognized stage for ' + ue.stableId);
+ }
+ });
+
+ [
+ responseNumeric, throughputNumeric, frameTimeDiscrepancyNumeric,
+ latencyNumeric
+ ].forEach(function(numeric) {
+ numeric.customizeSummaryOptions({
+ avg: true,
+ max: true,
+ min: true,
+ std: true
+ });
+ });
+
+ histograms.addHistogram(responseNumeric);
+ histograms.addHistogram(throughputNumeric);
+ histograms.addHistogram(frameTimeDiscrepancyNumeric);
+ histograms.addHistogram(latencyNumeric);
+ }
+
+ tr.metrics.MetricRegistry.register(responsivenessMetric, {
+ supportsRangeOfInterest: true,
+ requiredCategories: ['rail'],
+ });
+
+ return {
+ responsivenessMetric,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/system_health/responsiveness_metric_test.html b/chromium/third_party/catapult/tracing/tracing/metrics/system_health/responsiveness_metric_test.html
new file mode 100644
index 00000000000..dbe10addd27
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/system_health/responsiveness_metric_test.html
@@ -0,0 +1,68 @@
+<!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/extras/importer/trace_event_importer.html">
+<link rel="import" href="/tracing/metrics/system_health/responsiveness_metric.html">
+<link rel="import" href="/tracing/model/slice_group.html">
+<link rel="import" href="/tracing/value/histogram_set.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ function createModel(collectionOfFrameTimestamps) {
+ const opts = {
+ customizeModelCallback(model) {
+ const thread = tr.c.TestUtils.newFakeThread();
+ collectionOfFrameTimestamps.forEach(function(frameTimestamps) {
+ frameTimestamps.sort((a, b) => a - b);
+ const ue = new tr.model.um.AnimationExpectation(
+ model, 'test', frameTimestamps[0],
+ frameTimestamps[frameTimestamps.length - 1]);
+ const group = new tr.model.SliceGroup(thread);
+ frameTimestamps.forEach(function(time) {
+ group.pushSlice(tr.c.TestUtils.newSliceEx({
+ title: tr.model.helpers.IMPL_RENDERING_STATS,
+ start: time,
+ end: time,
+ cpuStart: time,
+ cpuEnd: time
+ }));
+ });
+ group.createSubSlices();
+ group.slices.forEach(function(slice) {
+ ue.associatedEvents.push(slice);
+ });
+ model.userModel.expectations.push(ue);
+ model.userModel.expectations.push(
+ new tr.model.um.StartupExpectation(model, 0, 1));
+ model.userModel.expectations.push(
+ new tr.model.um.ResponseExpectation(model, 'test', 1, 1));
+ });
+ }
+ };
+ return tr.c.TestUtils.newModelWithEvents([], opts);
+ }
+
+ test('animationThroughputNoFrames', function() {
+ const model = createModel([[]]);
+ const valueList = new tr.v.HistogramSet();
+ tr.metrics.sh.responsivenessMetric(valueList, model);
+ const value = valueList.getHistogramNamed('animation throughput');
+ assert.strictEqual(value.numValues, 0);
+ });
+
+ test('animationThroughputFramesAndNoFrames', function() {
+ const model = createModel([[0, 100], []]);
+ const valueList = new tr.v.HistogramSet();
+ tr.metrics.sh.responsivenessMetric(valueList, model);
+ const value = valueList.getHistogramNamed('animation throughput');
+ assert.strictEqual(value.average, 20);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/system_health/utils.html b/chromium/third_party/catapult/tracing/tracing/metrics/system_health/utils.html
new file mode 100644
index 00000000000..92bb7c71fb1
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/system_health/utils.html
@@ -0,0 +1,152 @@
+<!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/extras/chrome/chrome_processes.html">
+<link rel="import" href="/tracing/model/user_model/user_expectation.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.metrics.sh', function() {
+ // Returns a weight for this score.
+ // score should be a number between 0 and 1 inclusive.
+ // This function is expected to be passed to tr.b.math.Statistics.weightedMean
+ // as its weightCallback.
+ function perceptualBlend(ir, index, score) {
+ // Lower scores are exponentially more important than higher scores
+ // due to the Peak-end rule.
+ // Other than that general rule, there is no specific reasoning behind this
+ // specific formula -- it is fairly arbitrary.
+ return Math.exp(1 - score);
+ }
+
+ function filterExpectationsByRange(irs, opt_range) {
+ const filteredExpectations = [];
+ irs.forEach(function(ir) {
+ if (!(ir instanceof tr.model.um.UserExpectation)) return;
+
+ if (!opt_range ||
+ opt_range.intersectsExplicitRangeInclusive(ir.start, ir.end)) {
+ filteredExpectations.push(ir);
+ }
+ });
+ return filteredExpectations;
+ }
+
+ /**
+ * Splits the global memory dumps in |model| by browser name.
+ *
+ * @param {!tr.Model} model The trace model from which the global dumps
+ * should be extracted.
+ * @param {!tr.b.math.Range=} opt_rangeOfInterest If provided, global memory
+ * dumps that do not inclusively intersect the range will be skipped.
+ * @return {!Map<string, !Array<!tr.model.GlobalMemoryDump>} A map from
+ * browser names to the associated global memory dumps.
+ */
+ function splitGlobalDumpsByBrowserName(model, opt_rangeOfInterest) {
+ const chromeModelHelper =
+ model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);
+ const browserNameToGlobalDumps = new Map();
+ const globalDumpToBrowserHelper = new WeakMap();
+
+ // 1. For each browser process in the model, add its global memory dumps to
+ // |browserNameToGlobalDumps|. |chromeModelHelper| can be undefined if
+ // it fails to find any browser, renderer or GPU process (see
+ // tr.model.helpers.ChromeModelHelper.supportsModel).
+
+ if (chromeModelHelper) {
+ chromeModelHelper.browserHelpers.forEach(function(helper) {
+ // Retrieve the associated global memory dumps and check that they
+ // haven't been classified as belonging to another browser process.
+ const globalDumps = skipDumpsThatDoNotIntersectRange(
+ helper.process.memoryDumps.map(d => d.globalMemoryDump),
+ opt_rangeOfInterest);
+ globalDumps.forEach(function(globalDump) {
+ const existingHelper = globalDumpToBrowserHelper.get(globalDump);
+ if (existingHelper !== undefined) {
+ throw new Error('Memory dump ID clash across multiple browsers ' +
+ 'with PIDs: ' + existingHelper.pid + ' and ' + helper.pid);
+ }
+ globalDumpToBrowserHelper.set(globalDump, helper);
+ });
+
+ makeKeyUniqueAndSet(browserNameToGlobalDumps,
+ tr.e.chrome.chrome_processes.canonicalizeName(helper.browserName),
+ globalDumps);
+ });
+ }
+
+ // 2. If any global memory dump does not have any associated browser
+ // process for some reason, associate it with an 'unknown_browser' browser
+ // so that we don't lose the data.
+
+ const unclassifiedGlobalDumps = skipDumpsThatDoNotIntersectRange(
+ model.globalMemoryDumps.filter(g => !globalDumpToBrowserHelper.has(g)),
+ opt_rangeOfInterest);
+ if (unclassifiedGlobalDumps.length > 0) {
+ makeKeyUniqueAndSet(
+ browserNameToGlobalDumps, 'unknown_browser', unclassifiedGlobalDumps);
+ }
+
+ return browserNameToGlobalDumps;
+ }
+
+ /**
+ * Function for adding entries with duplicate keys to a map without
+ * overriding existing entries.
+ *
+ * This is achieved by appending numeric indices (2, 3, 4, ...) to duplicate
+ * keys. Example:
+ *
+ * const map = new Map();
+ * // map = Map {}.
+ *
+ * makeKeyUniqueAndSet(map, 'key', 'a');
+ * // map = Map {"key" => "a"}.
+ *
+ * makeKeyUniqueAndSet(map, 'key', 'b');
+ * // map = Map {"key" => "a", "key2" => "b"}.
+ * ^^^^
+ * makeKeyUniqueAndSet(map, 'key', 'c');
+ * // map = Map {"key" => "a", "key2" => "b", "key3" => "c"}.
+ * ^^^^ ^^^^
+ */
+ function makeKeyUniqueAndSet(map, key, value) {
+ let uniqueKey = key;
+ let nextIndex = 2;
+ while (map.has(uniqueKey)) {
+ uniqueKey = key + nextIndex;
+ nextIndex++;
+ }
+ map.set(uniqueKey, value);
+ }
+
+ function skipDumpsThatDoNotIntersectRange(dumps, opt_range) {
+ if (!opt_range) return dumps;
+ return dumps.filter(d => opt_range.intersectsExplicitRangeInclusive(
+ d.start, d.end));
+ }
+
+ /**
+ * Returns true if |category| is one of the categories of |event|, and |event|
+ * has title |title|.
+ *
+ * TODO(dproy): Make this a method on a suitable parent class of the
+ * event/slice classes that are used with this function.
+ */
+ function hasCategoryAndName(event, category, title) {
+ return event.title === title && event.category &&
+ tr.b.getCategoryParts(event.category).includes(category);
+ }
+
+ return {
+ hasCategoryAndName,
+ filterExpectationsByRange,
+ perceptualBlend,
+ splitGlobalDumpsByBrowserName
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/system_health/webview_startup_metric.html b/chromium/third_party/catapult/tracing/tracing/metrics/system_health/webview_startup_metric.html
new file mode 100644
index 00000000000..f7bea627333
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/system_health/webview_startup_metric.html
@@ -0,0 +1,64 @@
+<!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/metrics/metric_registry.html">
+<link rel="import" href="/tracing/metrics/system_health/utils.html">
+<link rel="import" href="/tracing/value/histogram.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.metrics.sh', function() {
+ function webviewStartupMetric(histograms, model) {
+ const startupWallHist = new tr.v.Histogram('webview_startup_wall_time',
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter);
+ startupWallHist.description = 'WebView startup wall time';
+ const startupCPUHist = new tr.v.Histogram('webview_startup_cpu_time',
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter);
+ startupCPUHist.description = 'WebView startup CPU time';
+ const loadWallHist = new tr.v.Histogram('webview_url_load_wall_time',
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter);
+ loadWallHist.description = 'WebView blank URL load wall time';
+ const loadCPUHist = new tr.v.Histogram('webview_url_load_cpu_time',
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter);
+ loadCPUHist.description = 'WebView blank URL load CPU time';
+
+ // TODO(alexandermont): Only iterate over the processes and threads that
+ // could contain these events.
+ for (const slice of model.getDescendantEvents()) {
+ if (!(slice instanceof tr.model.ThreadSlice)) continue;
+
+ // WebViewStartupInterval is the title of the section of code that is
+ // entered (via android.os.Trace.beginSection) when WebView is started
+ // up. This value is defined in TelemetryActivity.java.
+ if (slice.title === 'WebViewStartupInterval') {
+ startupWallHist.addSample(slice.duration);
+ startupCPUHist.addSample(slice.cpuDuration);
+ }
+
+ // WebViewBlankUrlLoadInterval is the title of the section of code
+ // that is entered (via android.os.Trace.beginSection) when WebView
+ // is started up. This value is defined in TelemetryActivity.java.
+ if (slice.title === 'WebViewBlankUrlLoadInterval') {
+ loadWallHist.addSample(slice.duration);
+ loadCPUHist.addSample(slice.cpuDuration);
+ }
+ }
+
+ histograms.addHistogram(startupWallHist);
+ histograms.addHistogram(startupCPUHist);
+ histograms.addHistogram(loadWallHist);
+ histograms.addHistogram(loadCPUHist);
+ }
+
+ tr.metrics.MetricRegistry.register(webviewStartupMetric);
+
+ return {
+ webviewStartupMetric,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/system_health/webview_startup_metric_test.html b/chromium/third_party/catapult/tracing/tracing/metrics/system_health/webview_startup_metric_test.html
new file mode 100644
index 00000000000..9f3c15224d9
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/system_health/webview_startup_metric_test.html
@@ -0,0 +1,50 @@
+<!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/metrics/system_health/webview_startup_metric.html">
+<link rel="import" href="/tracing/value/histogram_set.html">
+
+<script>
+'use strict';
+tr.b.unittest.testSuite(function() {
+ function makeTestModel() {
+ return tr.c.TestUtils.newModel(function(model) {
+ const mainThread = model.getOrCreateProcess(1).getOrCreateThread(2);
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'webview',
+ title: 'WebViewStartupInterval',
+ start: 200,
+ duration: 42.0,
+ cpuStart: 150,
+ cpuDuration: 32.0
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'webview',
+ title: 'WebViewBlankUrlLoadInterval',
+ start: 250,
+ duration: 27.0,
+ cpuStart: 190,
+ cpuDuration: 17.0
+ }));
+ });
+ }
+
+ test('webviewStartupMetric', function() {
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.sh.webviewStartupMetric(histograms, makeTestModel());
+ assert.closeTo(42, histograms.getHistogramNamed(
+ 'webview_startup_wall_time').average, 1e-2);
+ assert.closeTo(32, histograms.getHistogramNamed(
+ 'webview_startup_cpu_time').average, 1e-2);
+ assert.closeTo(27, histograms.getHistogramNamed(
+ 'webview_url_load_wall_time').average, 1e-2);
+ assert.closeTo(17, histograms.getHistogramNamed(
+ 'webview_url_load_cpu_time').average, 1e-2);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/tabs_metric.html b/chromium/third_party/catapult/tracing/tracing/metrics/tabs_metric.html
new file mode 100644
index 00000000000..a4b456353c3
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/tabs_metric.html
@@ -0,0 +1,89 @@
+<!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/metrics/metric_registry.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.metrics.tabs', function() {
+ function tabsMetric(histograms, model, opt_options) {
+ const chromeHelper = model.getOrCreateHelper(
+ tr.model.helpers.ChromeModelHelper);
+ if (!chromeHelper) {
+ // Chrome isn't present.
+ return;
+ }
+
+ const tabSwitchRequestDelays = [];
+ const TAB_SWITCHING_REQUEST_TITLE = 'TabSwitchVisibilityRequest';
+ // initialization, *startTabSwitchVisibilityRequest stores the
+ // first legal TabSwitchVisibilityRequest's start time.
+ let startTabSwitchVisibilityRequest = Number.MAX_SAFE_INTEGER;
+ for (const helper of chromeHelper.browserHelpers) {
+ if (!helper.mainThread) continue;
+ for (const slice of helper.mainThread.asyncSliceGroup.slices) {
+ if (slice.title === TAB_SWITCHING_REQUEST_TITLE && !slice.error) {
+ tabSwitchRequestDelays.push(slice.duration);
+ // find the first legal TabSwitchVisibilityRequest's start time
+ if (slice.start < startTabSwitchVisibilityRequest) {
+ startTabSwitchVisibilityRequest = slice.start;
+ }
+ }
+ }
+ }
+
+ histograms.createHistogram('tab_switching_request_delay',
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,
+ tabSwitchRequestDelays, {
+ description: 'Delay before tab-request is made',
+ summaryOptions: {sum: false}});
+
+ const tabSwitchLatencies = [];
+ const TAB_SWITCHING_SLICE_TITLE = 'TabSwitching::Latency';
+
+ function extractLatencyFromHelpers(helpers, legacy) {
+ for (const helper of helpers) {
+ if (!helper.mainThread) {
+ continue;
+ }
+ const thread = helper.mainThread;
+ for (const slice of thread.asyncSliceGroup.slices) {
+ if (slice.title === TAB_SWITCHING_SLICE_TITLE &&
+ (legacy || slice.args.latency) &&
+ slice.start > startTabSwitchVisibilityRequest) {
+ // push legal tabSwitchLatency which only starts after the
+ // first legal TabSwitchVisibilityRequest's start time
+ tabSwitchLatencies.push(
+ legacy ? slice.duration : slice.args.latency);
+ }
+ }
+ }
+ }
+
+ extractLatencyFromHelpers(chromeHelper.browserHelpers);
+ extractLatencyFromHelpers(Object.values(chromeHelper.rendererHelpers));
+
+ if (tabSwitchLatencies.length === 0) {
+ extractLatencyFromHelpers(chromeHelper.browserHelpers, true);
+ }
+
+ histograms.createHistogram('tab_switching_latency',
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,
+ tabSwitchLatencies, { description: 'Tab switching time in ms',
+ summaryOptions: {sum: false}});
+ }
+
+ tr.metrics.MetricRegistry.register(tabsMetric, {
+ supportsRangeOfInterest: false,
+ });
+
+ return {
+ tabsMetric,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/tabs_metric_test.html b/chromium/third_party/catapult/tracing/tracing/metrics/tabs_metric_test.html
new file mode 100644
index 00000000000..a6d3f6b9606
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/tabs_metric_test.html
@@ -0,0 +1,122 @@
+<!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/metrics/tabs_metric.html">
+<link rel="import" href="/tracing/value/histogram_set.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ test('tabsMetric_tabSwitching', function() {
+ const model = tr.c.TestUtils.newModel((model) => {
+ const browserThread = setUpBrowserThread(model);
+ addTabSwitchingData(browserThread);
+
+ const rendererMainThread = setUpRendererMainThread(model);
+ addTabSwitchingData(rendererMainThread);
+
+ const group = browserThread.asyncSliceGroup;
+ group.push(tr.c.TestUtils.newAsyncSliceEx({cat: 'latency',
+ title: 'TabSwitchVisibilityRequest', start: 3, duration: 10}));
+ group.push(tr.c.TestUtils.newAsyncSliceEx({cat: 'latency',
+ title: 'TabSwitchVisibilityRequest', start: 15, duration: 6}));
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.tabs.tabsMetric(histograms, model);
+ const hist = histograms.getHistogramNamed('tab_switching_latency');
+ assert.strictEqual(hist.max, 43.0);
+ assert.strictEqual(hist.min, 24.0);
+ assert.strictEqual(hist.average, 33.0);
+ assert.strictEqual(hist.numValues, 6);
+ });
+
+ test('tabsMetric_tabSwitching_legacy', function() {
+ const model = tr.c.TestUtils.newModel((model) => {
+ const browserThread = setUpBrowserThread(model);
+ addTabSwitchingData(browserThread, true);
+
+ const group = browserThread.asyncSliceGroup;
+ group.push(tr.c.TestUtils.newAsyncSliceEx({cat: 'latency',
+ title: 'TabSwitchVisibilityRequest', start: 3, duration: 10}));
+ group.push(tr.c.TestUtils.newAsyncSliceEx({cat: 'latency',
+ title: 'TabSwitchVisibilityRequest', start: 15, duration: 6}));
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.tabs.tabsMetric(histograms, model);
+ const hist = histograms.getHistogramNamed('tab_switching_latency');
+ assert.strictEqual(hist.max, 37.0);
+ assert.strictEqual(hist.min, 14.0);
+ assert.strictEqual(hist.average, 25.0);
+ assert.strictEqual(hist.numValues, 3);
+ });
+
+ test('tabsMetric_tabSwitchRequestDelay', function() {
+ const model = tr.c.TestUtils.newModel((model) => {
+ const browserThread = setUpBrowserThread(model);
+ const group = browserThread.asyncSliceGroup;
+ group.push(tr.c.TestUtils.newAsyncSliceEx({cat: 'latency',
+ title: 'TabSwitchVisibilityRequest', start: 0, duration: 10}));
+ group.push(tr.c.TestUtils.newAsyncSliceEx({cat: 'latency',
+ title: 'TabSwitchVisibilityRequest', start: 15, duration: 6}));
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.tabs.tabsMetric(histograms, model);
+ const hist = histograms.getHistogramNamed('tab_switching_request_delay');
+ assert.strictEqual(hist.average, 8);
+ });
+
+ function setUpBrowserThread(model) {
+ const BROWSER_PROCESS_ID = 1234;
+ const browserProcess = model.getOrCreateProcess(BROWSER_PROCESS_ID);
+ const browserThread = browserProcess.getOrCreateThread(2);
+ browserThread.name = 'CrBrowserMain';
+ return browserThread;
+ }
+
+ function setUpRendererMainThread(model) {
+ const RENDERER_PROCESS_ID = 2345;
+ const rendererProcess = model.getOrCreateProcess(RENDERER_PROCESS_ID);
+ const mainThread = rendererProcess.getOrCreateThread(23);
+ mainThread.name = 'CrRendererMain';
+ return mainThread;
+ }
+
+ function addTabSwitchingData(thread, legacy) {
+ const group = thread.asyncSliceGroup;
+ group.push(tr.c.TestUtils.newAsyncSliceEx({
+ cat: 'latency',
+ title: 'TabSwitching::Latency',
+ start: 2,
+ duration: 24,
+ args: legacy ? {} : {latency: 24 },
+ }));
+ group.push(tr.c.TestUtils.newAsyncSliceEx({
+ cat: 'latency',
+ title: 'TabSwitching::Latency',
+ start: 4,
+ duration: 24,
+ args: legacy ? {} : {latency: 24 },
+ }));
+ group.push(tr.c.TestUtils.newAsyncSliceEx({
+ cat: 'latency',
+ title: 'TabSwitching::Latency',
+ start: 5,
+ duration: 14,
+ args: legacy ? {} : {latency: 32 },
+ }));
+ group.push(tr.c.TestUtils.newAsyncSliceEx({
+ cat: 'latency',
+ title: 'TabSwitching::Latency',
+ start: 6,
+ duration: 37,
+ args: legacy ? {} : {latency: 43 },
+ }));
+ }
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/tracing_metric.html b/chromium/third_party/catapult/tracing/tracing/metrics/tracing_metric.html
new file mode 100644
index 00000000000..332a2b1cd04
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/tracing_metric.html
@@ -0,0 +1,230 @@
+<!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">
+<link rel="import" href="/tracing/metrics/metric_registry.html">
+<link rel="import" href="/tracing/value/diagnostics/diagnostic_map.html">
+<link rel="import" href="/tracing/value/histogram.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.metrics', function() {
+ const MEMORY_INFRA_TRACING_CATEGORY = 'disabled-by-default-memory-infra';
+
+ const TIME_BOUNDARIES = tr.v.HistogramBinBoundaries.createExponential(
+ 1e-3, 1e5, 30);
+
+ const BYTE_BOUNDARIES = tr.v.HistogramBinBoundaries.createExponential(
+ 1, 1e9, 30);
+
+ const COUNT_BOUNDARIES = tr.v.HistogramBinBoundaries.createExponential(
+ 1, 1e5, 30);
+
+ // By default, we store a single value, so we only need one of the
+ // statistics to keep track. We choose the average for that.
+ const SUMMARY_OPTIONS = tr.v.Histogram.AVERAGE_ONLY_SUMMARY_OPTIONS;
+
+ // Adds histograms specific to memory-infra dumps.
+ function addMemoryInfraHistograms(
+ histograms, model, categoryNamesToTotalEventSizes) {
+ const memoryDumpCount = model.globalMemoryDumps.length;
+ if (memoryDumpCount === 0) return;
+
+ let totalOverhead = 0;
+ let nonMemoryInfraThreadOverhead = 0;
+ const overheadByProvider = {};
+ for (const process of Object.values(model.processes)) {
+ for (const thread of Object.values(process.threads)) {
+ for (const slice of Object.values(thread.sliceGroup.slices)) {
+ if (slice.category !== MEMORY_INFRA_TRACING_CATEGORY) continue;
+
+ totalOverhead += slice.duration;
+ if (thread.name !== 'MemoryInfra') {
+ nonMemoryInfraThreadOverhead += slice.duration;
+ }
+ if (slice.args && slice.args['dump_provider.name']) {
+ const providerName = slice.args['dump_provider.name'];
+ let durationAndCount = overheadByProvider[providerName];
+ if (durationAndCount === undefined) {
+ overheadByProvider[providerName] = durationAndCount =
+ {duration: 0, count: 0};
+ }
+ durationAndCount.duration += slice.duration;
+ durationAndCount.count++;
+ }
+ }
+ }
+ }
+
+ histograms.createHistogram('memory_dump_cpu_overhead',
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,
+ totalOverhead / memoryDumpCount, {
+ binBoundaries: TIME_BOUNDARIES,
+ description:
+ 'Average CPU overhead on all threads per memory-infra dump',
+ summaryOptions: SUMMARY_OPTIONS,
+ });
+
+ histograms.createHistogram('nonmemory_thread_memory_dump_cpu_overhead',
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,
+ nonMemoryInfraThreadOverhead / memoryDumpCount, {
+ binBoundaries: TIME_BOUNDARIES,
+ description: 'Average CPU overhead on non-memory-infra threads ' +
+ 'per memory-infra dump',
+ summaryOptions: SUMMARY_OPTIONS,
+ });
+
+ for (const [providerName, overhead] of Object.entries(overheadByProvider)) {
+ histograms.createHistogram(`${providerName}_memory_dump_cpu_overhead`,
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,
+ overhead.duration / overhead.count, {
+ binBoundaries: TIME_BOUNDARIES,
+ description:
+ `Average CPU overhead of ${providerName} per OnMemoryDump call`,
+ summaryOptions: SUMMARY_OPTIONS,
+ });
+ }
+
+ const memoryInfraEventsSize =
+ categoryNamesToTotalEventSizes.get(MEMORY_INFRA_TRACING_CATEGORY);
+ const memoryInfraTraceBytesValue = new tr.v.Histogram(
+ 'total_memory_dump_size',
+ tr.b.Unit.byName.sizeInBytes_smallerIsBetter, BYTE_BOUNDARIES);
+ memoryInfraTraceBytesValue.description =
+ 'Total trace size of memory-infra dumps in bytes';
+ memoryInfraTraceBytesValue.customizeSummaryOptions(SUMMARY_OPTIONS);
+ memoryInfraTraceBytesValue.addSample(memoryInfraEventsSize);
+ histograms.addHistogram(memoryInfraTraceBytesValue);
+
+ const traceBytesPerDumpValue = new tr.v.Histogram(
+ 'memory_dump_size',
+ tr.b.Unit.byName.sizeInBytes_smallerIsBetter, BYTE_BOUNDARIES);
+ traceBytesPerDumpValue.description =
+ 'Average trace size of memory-infra dumps in bytes';
+ traceBytesPerDumpValue.customizeSummaryOptions(SUMMARY_OPTIONS);
+ traceBytesPerDumpValue.addSample(memoryInfraEventsSize / memoryDumpCount);
+ histograms.addHistogram(traceBytesPerDumpValue);
+ }
+
+ // TODO(charliea): The metrics in this file should be renamed to have names
+ // more consistent with those in the rest of the codebase
+ // (e.g. 'trace_size_growth_per_second', not 'Max event size in bytes per
+ // second').
+ // https://github.com/catapult-project/catapult/issues/3233
+ function tracingMetric(histograms, model) {
+ if (!model.stats.hasEventSizesinBytes) return;
+
+ const eventStats = model.stats.allTraceEventStatsInTimeIntervals;
+ eventStats.sort((a, b) => a.timeInterval - b.timeInterval);
+
+ const totalTraceBytes = eventStats.reduce(
+ (a, b) => a + b.totalEventSizeinBytes, 0);
+
+ // We maintain a sliding window of records [start ... end-1] where end
+ // increments each time through the loop, and we move start just far enough
+ // to keep the window less than 1 second wide. Note that we need to compute
+ // the number of time intervals (i.e. units that timeInterval is given in)
+ // in one second to know how wide the sliding window should be.
+ let maxEventCountPerSec = 0;
+ let maxEventBytesPerSec = 0;
+ const INTERVALS_PER_SEC = Math.floor(
+ 1000 / model.stats.TIME_INTERVAL_SIZE_IN_MS);
+
+ let runningEventNumPerSec = 0;
+ let runningEventBytesPerSec = 0;
+ let start = 0;
+ let end = 0;
+
+ while (end < eventStats.length) {
+ // Slide the end marker forward. Moving the end marker from N
+ // to N+1 adds eventStats[N] to the sliding window.
+ runningEventNumPerSec += eventStats[end].numEvents;
+ runningEventBytesPerSec += eventStats[end].totalEventSizeinBytes;
+ end++;
+
+ // Slide the start marker forward so that the time interval covered
+ // by the window is less than 1 second wide.
+ while ((eventStats[end - 1].timeInterval -
+ eventStats[start].timeInterval) >= INTERVALS_PER_SEC) {
+ runningEventNumPerSec -= eventStats[start].numEvents;
+ runningEventBytesPerSec -= eventStats[start].totalEventSizeinBytes;
+ start++;
+ }
+
+ // Update maximum values.
+ maxEventCountPerSec = Math.max(maxEventCountPerSec,
+ runningEventNumPerSec);
+ maxEventBytesPerSec = Math.max(maxEventBytesPerSec,
+ runningEventBytesPerSec);
+ }
+
+ const stats = model.stats.allTraceEventStats;
+ const categoryNamesToTotalEventSizes = (
+ stats.reduce((map, stat) => (
+ map.set(stat.category,
+ ((map.get(stat.category) || 0) +
+ stat.totalEventSizeinBytes))), new Map()));
+
+ // Determine the category with the highest total event size.
+ const maxCatNameAndBytes = Array.from(
+ categoryNamesToTotalEventSizes.entries()).reduce(
+ (a, b) => ((b[1] >= a[1]) ? b : a));
+ const maxEventBytesPerCategory = maxCatNameAndBytes[1];
+ const categoryWithMaxEventBytes = maxCatNameAndBytes[0];
+
+ const maxEventCountPerSecValue = new tr.v.Histogram(
+ 'peak_event_rate', tr.b.Unit.byName.count_smallerIsBetter,
+ COUNT_BOUNDARIES);
+ maxEventCountPerSecValue.description = 'Max number of events per second';
+ maxEventCountPerSecValue.customizeSummaryOptions(SUMMARY_OPTIONS);
+ maxEventCountPerSecValue.addSample(maxEventCountPerSec);
+
+ const maxEventBytesPerSecValue = new tr.v.Histogram(
+ 'peak_event_size_rate', tr.b.Unit.byName.sizeInBytes_smallerIsBetter,
+ BYTE_BOUNDARIES);
+ maxEventBytesPerSecValue.description = 'Max event size in bytes per second';
+ maxEventBytesPerSecValue.customizeSummaryOptions(SUMMARY_OPTIONS);
+ maxEventBytesPerSecValue.addSample(maxEventBytesPerSec);
+
+ const totalTraceBytesValue = new tr.v.Histogram('trace_size',
+ tr.b.Unit.byName.sizeInBytes_smallerIsBetter, BYTE_BOUNDARIES);
+ totalTraceBytesValue.customizeSummaryOptions(SUMMARY_OPTIONS);
+ totalTraceBytesValue.addSample(totalTraceBytes);
+
+ const biggestCategory = {
+ name: categoryWithMaxEventBytes,
+ size_in_bytes: maxEventBytesPerCategory
+ };
+
+ totalTraceBytesValue.diagnostics.set(
+ 'category_with_max_event_size',
+ new tr.v.d.GenericSet([biggestCategory]));
+ histograms.addHistogram(totalTraceBytesValue);
+
+ maxEventCountPerSecValue.diagnostics.set(
+ 'category_with_max_event_size',
+ new tr.v.d.GenericSet([biggestCategory]));
+ histograms.addHistogram(maxEventCountPerSecValue);
+
+ maxEventBytesPerSecValue.diagnostics.set(
+ 'category_with_max_event_size',
+ new tr.v.d.GenericSet([biggestCategory]));
+ histograms.addHistogram(maxEventBytesPerSecValue);
+
+ addMemoryInfraHistograms(histograms, model, categoryNamesToTotalEventSizes);
+ }
+
+ tr.metrics.MetricRegistry.register(tracingMetric);
+
+ return {
+ tracingMetric,
+ // For testing only:
+ MEMORY_INFRA_TRACING_CATEGORY,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/tracing_metric_test.html b/chromium/third_party/catapult/tracing/tracing/metrics/tracing_metric_test.html
new file mode 100644
index 00000000000..5fbffe98f94
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/tracing_metric_test.html
@@ -0,0 +1,168 @@
+<!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/extras/importer/trace_event_importer.html">
+<link rel="import" href="/tracing/metrics/tracing_metric.html">
+<link rel="import" href="/tracing/model/memory_dump_test_utils.html">
+<link rel="import" href="/tracing/value/histogram_set.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ function makeModel(events, opt_track) {
+ return tr.c.TestUtils.newModelWithEvents([events], {
+ trackDetailedModelStats: opt_track
+ });
+ }
+
+ function getEventStringSize(events, indices) {
+ return indices.reduce(function(sum, index) {
+ return sum + JSON.stringify(events[index]).length;
+ }, 0);
+ }
+
+ function checkDurationHistogram(histograms, metricName, expected) {
+ const histogram = histograms.getHistogramNamed(metricName);
+ assert.closeTo(1000 * histogram.average, expected, 0.1);
+ }
+
+ test('tracingMetric_hasEventSizesInBytes', function() {
+ const histograms = new tr.v.HistogramSet();
+ const events = [
+ {name: 'a', args: {}, pid: 52, ts: 524, cat: 'foo', tid: 53, ph: 'B'},
+ {name: 'a', args: {}, pid: 52, ts: 560, cat: 'foo', tid: 53, ph: 'B'}
+ ];
+
+ const model = makeModel(JSON.stringify(events), true);
+ assert.isTrue(model.importOptions.trackDetailedModelStats);
+ tr.metrics.tracingMetric(histograms, model);
+ });
+
+ test('tracingMetric_totalTraceSize', function() {
+ const histograms = new tr.v.HistogramSet();
+ const events = [
+ {name: 'a', args: {}, pid: 52, ts: 524, cat: 'foo', tid: 53, ph: 'B'},
+ {name: 'a', args: {}, pid: 52, ts: 560, cat: 'foo', tid: 53, ph: 'B'}
+ ];
+ const model = makeModel(JSON.stringify(events), true);
+ tr.metrics.tracingMetric(histograms, model);
+
+ const eventStringSize = getEventStringSize(events, [0, 1]);
+ const histogram = histograms.getHistogramNamed('trace_size');
+ assert.strictEqual(histogram.average, eventStringSize);
+ });
+
+ test('tracingMetric_maxValuePerSec', function() {
+ const ONE_SEC_IN_US = 1000000;
+ const events = [
+ {name: 'a', pid: 52, ts: 1, cat: 'foo', ph: 'B'},
+ {name: 'a', pid: 52, ts: ONE_SEC_IN_US + 1, cat: 'foo', ph: 'B'},
+ {name: 'a', pid: 52, ts: 2 * ONE_SEC_IN_US + 1, cat: 'foo', ph: 'B'},
+ {name: 'a', pid: 52, ts: 2 * ONE_SEC_IN_US + 3, cat: 'foo', ph: 'B'},
+ {name: 'a', pid: 52, ts: ONE_SEC_IN_US + 2, cat: 'foo', ph: 'B'},
+ {name: 'a', pid: 52, ts: 2 * ONE_SEC_IN_US + 2, cat: 'foo', ph: 'B'}
+ ];
+ const model = makeModel(JSON.stringify(events), true);
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.tracingMetric(histograms, model);
+
+ const maxEventCountPerSec = 3;
+ let histogram = histograms.getHistogramNamed('peak_event_rate');
+ assert.strictEqual(histogram.average, maxEventCountPerSec);
+
+ const maxEventBytesPerSec = getEventStringSize(events, [2, 3, 5]);
+ histogram = histograms.getHistogramNamed('peak_event_size_rate');
+ assert.strictEqual(histogram.average, maxEventBytesPerSec);
+ });
+
+ test('tracingMetric_diagnostics', function() {
+ const histograms = new tr.v.HistogramSet();
+ const events = [
+ {name: 'a', args: {}, pid: 52, ts: 524, cat: 'foo', tid: 53, ph: 'B'},
+ {name: 'a', args: {}, pid: 52, ts: 535, cat: 'foo', tid: 53, ph: 'B'},
+ {name: 'bb', args: {}, pid: 52, ts: 546, cat: 'bar', tid: 53, ph: 'E'},
+ {name: 'a', args: {}, pid: 52, ts: 560, cat: 'foo', tid: 53, ph: 'B'},
+ {name: 'bb', args: {}, pid: 52, ts: 578, cat: 'bar', tid: 53, ph: 'E'}
+ ];
+ const model = makeModel(JSON.stringify(events), true);
+ tr.metrics.tracingMetric(histograms, model);
+
+ const DIAGNOSTIC_HISTOGRAMS = [
+ 'Max number of events per second',
+ 'Max event size in bytes per second',
+ 'Total trace size in bytes'
+ ];
+ for (const histogram of histograms) {
+ if (!DIAGNOSTIC_HISTOGRAMS.includes(histogram.name)) continue;
+
+ const d = histogram.diagnostics.get('category_with_max_event_size').value;
+ assert.strictEqual(d.name, 'foo');
+ assert.strictEqual(d.size_in_bytes, getEventStringSize(
+ events, [0, 1, 3]));
+ }
+ });
+
+ test('tracingMetric_memoryInfraTracingMetrics', function() {
+ const MEMORY_INFRA_TRACING_CATEGORY =
+ tr.metrics.MEMORY_INFRA_TRACING_CATEGORY;
+ const histograms = new tr.v.HistogramSet();
+ const events = [
+ {name: 'OnMemoryDump', pid: 1, ts: 510, tid: 1, ph: 'X', cat: MEMORY_INFRA_TRACING_CATEGORY, args: {'dump_provider.name': 'mdp1'}, dur: 4}, // @suppress longLineCheck
+ {name: 'OnMemoryDump', pid: 1, ts: 520, tid: 7, ph: 'X', cat: MEMORY_INFRA_TRACING_CATEGORY, args: {'dump_provider.name': 'mdp2'}, dur: 15}, // @suppress longLineCheck
+ {name: 'OnMemoryDump', pid: 1, ts: 530, tid: 7, ph: 'X', cat: MEMORY_INFRA_TRACING_CATEGORY, args: {'dump_provider.name': 'mdp3'}, dur: 5}, // @suppress longLineCheck
+ {name: 'OnMemoryDump', pid: 2, ts: 510, tid: 2, ph: 'X', cat: MEMORY_INFRA_TRACING_CATEGORY, args: {'dump_provider.name': 'mdp1'}, dur: 9}, // @suppress longLineCheck
+ {name: 'OnMemoryDump', pid: 2, ts: 520, tid: 8, ph: 'X', cat: MEMORY_INFRA_TRACING_CATEGORY, args: {'dump_provider.name': 'mdp2'}, dur: 17}, // @suppress longLineCheck
+ {name: 'OnMemoryDump', pid: 2, ts: 530, tid: 8, ph: 'X', cat: MEMORY_INFRA_TRACING_CATEGORY, args: {'dump_provider.name': 'mdp3'}, dur: 7}, // @suppress longLineCheck
+ {name: 'OnMemoryDump', pid: 2, ts: 540, tid: 3, ph: 'X', cat: MEMORY_INFRA_TRACING_CATEGORY, args: {'dump_provider.name': 'mdp4'}, dur: 8}, // @suppress longLineCheck
+ {name: 'ProcessDumps', pid: 1, ts: 550, tid: 1, ph: 'X', cat: MEMORY_INFRA_TRACING_CATEGORY, args: {guid: 4}, dur: 8}, // @suppress longLineCheck
+ {name: 'ProcessDumps', pid: 2, ts: 540, tid: 2, ph: 'X', cat: MEMORY_INFRA_TRACING_CATEGORY, args: {guid: 4}, dur: 18}, // @suppress longLineCheck
+ {name: 'thread_name', pid: 1, ts: 0, tid: 1, ph: 'M', cat: '__metadata', args: {name: 'CrBrowsermain'}}, // @suppress longLineCheck
+ {name: 'thread_name', pid: 1, ts: 0, tid: 7, ph: 'M', cat: '__metadata', args: {name: 'MemoryInfra'}}, // @suppress longLineCheck
+ {name: 'thread_name', pid: 2, ts: 0, tid: 2, ph: 'M', cat: '__metadata', args: {name: 'CrRendererMain'}}, // @suppress longLineCheck
+ {name: 'thread_name', pid: 2, ts: 0, tid: 8, ph: 'M', cat: '__metadata', args: {name: 'MemoryInfra'}}, // @suppress longLineCheck
+ {name: 'thread_name', pid: 2, ts: 0, tid: 3, ph: 'M', cat: '__metadata', args: {name: 'Compositor'}} // @suppress longLineCheck
+ ];
+
+ const model = makeModel(JSON.stringify(events), true);
+ tr.model.MemoryDumpTestUtils.addGlobalMemoryDump(model, {ts: 550});
+ tr.metrics.tracingMetric(histograms, model);
+
+ const memoryCategorySize = events.filter(
+ slice => slice.cat === MEMORY_INFRA_TRACING_CATEGORY).reduce(
+ (acc, slice) => acc + JSON.stringify(slice).length, 0);
+ const totalSizeHistogram = histograms.getHistogramNamed(
+ 'total_memory_dump_size');
+ assert.strictEqual(totalSizeHistogram.average, memoryCategorySize);
+ const sizePerDumpHistogram = histograms.getHistogramNamed(
+ 'memory_dump_size');
+ assert.strictEqual(sizePerDumpHistogram.average, memoryCategorySize);
+
+ checkDurationHistogram(histograms, 'mdp1_memory_dump_cpu_overhead', 6.5);
+ checkDurationHistogram(histograms, 'mdp2_memory_dump_cpu_overhead', 16);
+ checkDurationHistogram(histograms, 'mdp3_memory_dump_cpu_overhead', 6);
+ checkDurationHistogram(histograms, 'mdp4_memory_dump_cpu_overhead', 8);
+ checkDurationHistogram(
+ histograms, 'nonmemory_thread_memory_dump_cpu_overhead', 47);
+ checkDurationHistogram(histograms, 'memory_dump_cpu_overhead', 91);
+ });
+
+ test('tracingMetric_traceImportDurationMetricWithoutTrackDetailedModelStats',
+ function() {
+ const histograms = new tr.v.HistogramSet();
+ const events = [
+ {name: 'a', args: {}, pid: 52, ts: 524, cat: 'foo', tid: 53, ph: 'B'},
+ {name: 'a', args: {}, pid: 52, ts: 560, cat: 'foo', tid: 53, ph: 'B'}
+ ];
+
+ const model = makeModel(JSON.stringify(events), false);
+ assert.isFalse(model.importOptions.trackDetailedModelStats);
+ tr.metrics.tracingMetric(histograms, model);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/v8/execution_metric.html b/chromium/third_party/catapult/tracing/tracing/metrics/v8/execution_metric.html
new file mode 100644
index 00000000000..87992515679
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/v8/execution_metric.html
@@ -0,0 +1,228 @@
+<!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/math/range.html">
+<link rel="import" href="/tracing/base/unit.html">
+<link rel="import" href="/tracing/metrics/metric_registry.html">
+<link rel="import" href="/tracing/value/histogram.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.metrics.v8', function() {
+ const CUSTOM_BOUNDARIES = tr.v.HistogramBinBoundaries.createLinear(
+ 4, 200, 100);
+
+ function computeExecuteMetrics(histograms, model) {
+ const cpuTotalExecution = new tr.v.Histogram('v8_execution_cpu_total',
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter, CUSTOM_BOUNDARIES);
+ cpuTotalExecution.description = 'cpu total time spent in script execution';
+ const wallTotalExecution = new tr.v.Histogram('v8_execution_wall_total',
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter, CUSTOM_BOUNDARIES);
+ wallTotalExecution.description =
+ 'wall total time spent in script execution';
+ const cpuSelfExecution = new tr.v.Histogram('v8_execution_cpu_self',
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter, CUSTOM_BOUNDARIES);
+ cpuSelfExecution.description = 'cpu self time spent in script execution';
+ const wallSelfExecution = new tr.v.Histogram('v8_execution_wall_self',
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter, CUSTOM_BOUNDARIES);
+ wallSelfExecution.description = 'wall self time spent in script execution';
+
+ for (const e of model.findTopmostSlicesNamed('V8.Execute')) {
+ cpuTotalExecution.addSample(e.cpuDuration);
+ wallTotalExecution.addSample(e.duration);
+ cpuSelfExecution.addSample(e.cpuSelfTime);
+ wallSelfExecution.addSample(e.selfTime);
+ }
+
+ histograms.addHistogram(cpuTotalExecution);
+ histograms.addHistogram(wallTotalExecution);
+ histograms.addHistogram(cpuSelfExecution);
+ histograms.addHistogram(wallSelfExecution);
+ }
+
+ function computeParseLazyMetrics(histograms, model) {
+ const cpuSelfParseLazy = new tr.v.Histogram('v8_parse_lazy_cpu_self',
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter, CUSTOM_BOUNDARIES);
+ cpuSelfParseLazy.description =
+ 'cpu self time spent performing lazy parsing';
+ const wallSelfParseLazy = new tr.v.Histogram('v8_parse_lazy_wall_self',
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter, CUSTOM_BOUNDARIES);
+ wallSelfParseLazy.description =
+ 'wall self time spent performing lazy parsing';
+
+ for (const e of model.findTopmostSlicesNamed('V8.ParseLazyMicroSeconds')) {
+ cpuSelfParseLazy.addSample(e.cpuSelfTime);
+ wallSelfParseLazy.addSample(e.selfTime);
+ }
+ for (const e of model.findTopmostSlicesNamed('V8.ParseLazy')) {
+ cpuSelfParseLazy.addSample(e.cpuSelfTime);
+ wallSelfParseLazy.addSample(e.selfTime);
+ }
+
+ histograms.addHistogram(cpuSelfParseLazy);
+ histograms.addHistogram(wallSelfParseLazy);
+ }
+
+ function computeCompileFullCodeMetrics(histograms, model) {
+ const cpuSelfCompileFullCode = new tr.v.Histogram(
+ 'v8_compile_full_code_cpu_self',
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter, CUSTOM_BOUNDARIES);
+ cpuSelfCompileFullCode.description =
+ 'cpu self time spent performing compiling full code';
+ const wallSelfCompileFullCode = new tr.v.Histogram(
+ 'v8_compile_full_code_wall_self',
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter, CUSTOM_BOUNDARIES);
+ wallSelfCompileFullCode.description =
+ 'wall self time spent performing compiling full code';
+
+ for (const e of model.findTopmostSlicesNamed('V8.CompileFullCode')) {
+ cpuSelfCompileFullCode.addSample(e.cpuSelfTime);
+ wallSelfCompileFullCode.addSample(e.selfTime);
+ }
+
+ histograms.addHistogram(cpuSelfCompileFullCode);
+ histograms.addHistogram(wallSelfCompileFullCode);
+ }
+
+ function computeCompileIgnitionMetrics(histograms, model) {
+ const cpuSelfCompileIgnition = new tr.v.Histogram(
+ 'v8_compile_ignition_cpu_self',
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter, CUSTOM_BOUNDARIES);
+ cpuSelfCompileIgnition.description =
+ 'cpu self time spent in compile ignition';
+ const wallSelfCompileIgnition = new tr.v.Histogram(
+ 'v8_compile_ignition_wall_self',
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter, CUSTOM_BOUNDARIES);
+ wallSelfCompileIgnition.description =
+ 'wall self time spent in compile ignition';
+
+ for (const e of model.findTopmostSlicesNamed('V8.CompileIgnition')) {
+ cpuSelfCompileIgnition.addSample(e.cpuSelfTime);
+ wallSelfCompileIgnition.addSample(e.selfTime);
+ }
+
+ histograms.addHistogram(cpuSelfCompileIgnition);
+ histograms.addHistogram(wallSelfCompileIgnition);
+ }
+
+ function computeRecompileMetrics(histograms, model) {
+ const cpuTotalRecompileSynchronous = new tr.v.Histogram(
+ 'v8_recompile_synchronous_cpu_total',
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter, CUSTOM_BOUNDARIES);
+ cpuTotalRecompileSynchronous.description =
+ 'cpu total time spent in synchronous recompilation';
+ const wallTotalRecompileSynchronous = new tr.v.Histogram(
+ 'v8_recompile_synchronous_wall_total',
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter, CUSTOM_BOUNDARIES);
+ wallTotalRecompileSynchronous.description =
+ 'wall total time spent in synchronous recompilation';
+ const cpuTotalRecompileConcurrent = new tr.v.Histogram(
+ 'v8_recompile_concurrent_cpu_total',
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter, CUSTOM_BOUNDARIES);
+ cpuTotalRecompileConcurrent.description =
+ 'cpu total time spent in concurrent recompilation';
+ const wallTotalRecompileConcurrent = new tr.v.Histogram(
+ 'v8_recompile_concurrent_wall_total',
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter, CUSTOM_BOUNDARIES);
+ wallTotalRecompileConcurrent.description =
+ 'wall total time spent in concurrent recompilation';
+ // TODO(eakuefner): Stop computing overall histograms once dash v2 is ready.
+ // https://github.com/catapult-project/catapult/issues/2180
+ const cpuTotalRecompileOverall = new tr.v.Histogram(
+ 'v8_recompile_overall_cpu_total',
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter, CUSTOM_BOUNDARIES);
+ cpuTotalRecompileOverall.description =
+ 'cpu total time spent in synchronous or concurrent recompilation';
+ const wallTotalRecompileOverall = new tr.v.Histogram(
+ 'v8_recompile_overall_wall_total',
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter, CUSTOM_BOUNDARIES);
+ wallTotalRecompileOverall.description =
+ 'wall total time spent in synchronous or concurrent recompilation';
+
+ for (const e of model.findTopmostSlicesNamed('V8.RecompileSynchronous')) {
+ cpuTotalRecompileSynchronous.addSample(e.cpuDuration);
+ wallTotalRecompileSynchronous.addSample(e.duration);
+ cpuTotalRecompileOverall.addSample(e.cpuDuration);
+ wallTotalRecompileOverall.addSample(e.duration);
+ }
+
+ histograms.addHistogram(cpuTotalRecompileSynchronous);
+ histograms.addHistogram(wallTotalRecompileSynchronous);
+
+ for (const e of model.findTopmostSlicesNamed('V8.RecompileConcurrent')) {
+ cpuTotalRecompileConcurrent.addSample(e.cpuDuration);
+ wallTotalRecompileConcurrent.addSample(e.duration);
+ cpuTotalRecompileOverall.addSample(e.cpuDuration);
+ wallTotalRecompileOverall.addSample(e.duration);
+ }
+
+ histograms.addHistogram(cpuTotalRecompileConcurrent);
+ histograms.addHistogram(wallTotalRecompileConcurrent);
+ histograms.addHistogram(cpuTotalRecompileOverall);
+ histograms.addHistogram(wallTotalRecompileOverall);
+ }
+
+ function computeOptimizeCodeMetrics(histograms, model) {
+ const cpuTotalOptimizeCode = new tr.v.Histogram(
+ 'v8_optimize_code_cpu_total',
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter, CUSTOM_BOUNDARIES);
+ cpuTotalOptimizeCode.description =
+ 'cpu total time spent in code optimization';
+ const wallTotalOptimizeCode = new tr.v.Histogram(
+ 'v8_optimize_code_wall_total',
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter, CUSTOM_BOUNDARIES);
+ wallTotalOptimizeCode.description =
+ 'wall total time spent in code optimization';
+
+ for (const e of model.findTopmostSlicesNamed('V8.OptimizeCode')) {
+ cpuTotalOptimizeCode.addSample(e.cpuDuration);
+ wallTotalOptimizeCode.addSample(e.duration);
+ }
+
+ histograms.addHistogram(cpuTotalOptimizeCode);
+ histograms.addHistogram(wallTotalOptimizeCode);
+ }
+
+ function computeDeoptimizeCodeMetrics(histograms, model) {
+ const cpuTotalDeoptimizeCode = new tr.v.Histogram(
+ 'v8_deoptimize_code_cpu_total',
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter, CUSTOM_BOUNDARIES);
+ cpuTotalDeoptimizeCode.description =
+ 'cpu total time spent in code deoptimization';
+ const wallTotalDeoptimizeCode = new tr.v.Histogram(
+ 'v8_deoptimize_code_wall_total',
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter, CUSTOM_BOUNDARIES);
+ wallTotalDeoptimizeCode.description =
+ 'wall total time spent in code deoptimization';
+
+ for (const e of model.findTopmostSlicesNamed('V8.DeoptimizeCode')) {
+ cpuTotalDeoptimizeCode.addSample(e.cpuDuration);
+ wallTotalDeoptimizeCode.addSample(e.duration);
+ }
+
+ histograms.addHistogram(cpuTotalDeoptimizeCode);
+ histograms.addHistogram(wallTotalDeoptimizeCode);
+ }
+
+ function executionMetric(histograms, model) {
+ computeExecuteMetrics(histograms, model);
+ computeParseLazyMetrics(histograms, model);
+ computeCompileIgnitionMetrics(histograms, model);
+ computeCompileFullCodeMetrics(histograms, model);
+ computeRecompileMetrics(histograms, model);
+ computeOptimizeCodeMetrics(histograms, model);
+ computeDeoptimizeCodeMetrics(histograms, model);
+ }
+
+ tr.metrics.MetricRegistry.register(executionMetric);
+
+ return {
+ executionMetric,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/v8/execution_metric_test.html b/chromium/third_party/catapult/tracing/tracing/metrics/v8/execution_metric_test.html
new file mode 100644
index 00000000000..d42c5476706
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/v8/execution_metric_test.html
@@ -0,0 +1,71 @@
+<!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/extras/importer/trace_event_importer.html">
+<link rel="import" href="/tracing/metrics/v8/execution_metric.html">
+<link rel="import" href="/tracing/value/histogram_set.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ test('executionMetricBasic', function() {
+ const model = tr.c.TestUtils.newModel();
+ const histograms = new tr.v.HistogramSet();
+
+ tr.metrics.v8.executionMetric(histograms, model);
+
+ [
+ 'v8_execution_cpu_total',
+ 'v8_execution_wall_total',
+ 'v8_execution_cpu_self',
+ 'v8_execution_wall_self',
+ 'v8_parse_lazy_cpu_self',
+ 'v8_parse_lazy_wall_self',
+ 'v8_compile_full_code_cpu_self',
+ 'v8_compile_full_code_wall_self',
+ 'v8_compile_ignition_cpu_self',
+ 'v8_compile_ignition_wall_self',
+ 'v8_recompile_synchronous_cpu_total',
+ 'v8_recompile_synchronous_wall_total',
+ 'v8_recompile_concurrent_cpu_total',
+ 'v8_recompile_concurrent_wall_total',
+ 'v8_recompile_overall_cpu_total',
+ 'v8_recompile_overall_wall_total',
+ 'v8_optimize_code_cpu_total',
+ 'v8_optimize_code_wall_total',
+ 'v8_deoptimize_code_cpu_total',
+ 'v8_deoptimize_code_wall_total',
+ ].forEach(function(name) {
+ assert.isDefined(histograms.getHistogramNamed(name));
+ });
+ });
+
+ test('noDoubleCounting', function() {
+ const events = [
+ {name: 'V8.Execute', args: {}, pid: 52, ts: 0, cat: 'foo', tid: 53,
+ ph: 'B'},
+ {name: 'V8.Execute', args: {}, pid: 52, ts: 100, cat: 'foo', tid: 53,
+ ph: 'E'},
+ {name: 'V8.Execute', args: {}, pid: 52, ts: 20, cat: 'foo', tid: 53,
+ ph: 'B'},
+ {name: 'V8.Execute', args: {}, pid: 52, ts: 40, cat: 'foo', tid: 53,
+ ph: 'E'}
+ ];
+
+ const model = tr.c.TestUtils.newModelWithEvents(JSON.stringify(events), {
+ shiftWorldToZero: false
+ });
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.v8.executionMetric(histograms, model);
+
+ const value = histograms.getHistogramNamed('v8_execution_wall_total');
+ assert.closeTo(value.running.sum, 0.1, 1e-5);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/v8/gc_metric.html b/chromium/third_party/catapult/tracing/tracing/metrics/v8/gc_metric.html
new file mode 100644
index 00000000000..c3d90cc2786
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/v8/gc_metric.html
@@ -0,0 +1,297 @@
+<!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/math/range.html">
+<link rel="import" href="/tracing/base/unit.html">
+<link rel="import" href="/tracing/metrics/metric_registry.html">
+<link rel="import" href="/tracing/metrics/v8/utils.html">
+<link rel="import" href="/tracing/model/helpers/chrome_model_helper.html">
+<link rel="import" href="/tracing/value/diagnostics/breakdown.html">
+<link rel="import" href="/tracing/value/histogram.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.metrics.v8', function() {
+ // The time window size for mutator utilization computation.
+ // It is equal to the duration of one frame corresponding to 60 FPS rendering.
+ const TARGET_FPS = 60;
+ const MS_PER_SECOND = 1000;
+ const WINDOW_SIZE_MS = MS_PER_SECOND / TARGET_FPS;
+
+ function gcMetric(histograms, model) {
+ addDurationOfTopEvents(histograms, model);
+ addTotalDurationOfTopEvents(histograms, model);
+ addDurationOfSubEvents(histograms, model);
+ addPercentageInV8ExecuteOfTopEvents(histograms, model);
+ addTotalPercentageInV8Execute(histograms, model);
+ addMarkCompactorMutatorUtilization(histograms, model);
+ addTotalMarkCompactorTime(histograms, model);
+ addTotalMarkCompactorMarkingTime(histograms, model);
+ }
+
+ tr.metrics.MetricRegistry.register(gcMetric);
+
+ const timeDurationInMs_smallerIsBetter =
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter;
+ const percentage_biggerIsBetter =
+ tr.b.Unit.byName.normalizedPercentage_biggerIsBetter;
+ const percentage_smallerIsBetter =
+ tr.b.Unit.byName.normalizedPercentage_smallerIsBetter;
+
+ // 0.1 steps from 0 to 20 since it is the most common range.
+ // Exponentially increasing steps from 20 to 200.
+ const CUSTOM_BOUNDARIES = tr.v.HistogramBinBoundaries.createLinear(0, 20, 200)
+ .addExponentialBins(200, 100);
+
+ function createNumericForTopEventTime(name) {
+ const n = new tr.v.Histogram(name,
+ timeDurationInMs_smallerIsBetter, CUSTOM_BOUNDARIES);
+ n.customizeSummaryOptions({
+ avg: true,
+ count: true,
+ max: true,
+ min: false,
+ std: true,
+ sum: true,
+ percentile: [0.90]});
+ return n;
+ }
+
+ function createNumericForSubEventTime(name) {
+ const n = new tr.v.Histogram(name,
+ timeDurationInMs_smallerIsBetter, CUSTOM_BOUNDARIES);
+ n.customizeSummaryOptions({
+ avg: true,
+ count: false,
+ max: true,
+ min: false,
+ std: false,
+ sum: false,
+ percentile: [0.90]
+ });
+ return n;
+ }
+
+ function createNumericForIdleTime(name) {
+ const n = new tr.v.Histogram(name,
+ timeDurationInMs_smallerIsBetter, CUSTOM_BOUNDARIES);
+ n.customizeSummaryOptions({
+ avg: true,
+ count: false,
+ max: true,
+ min: false,
+ std: false,
+ sum: true,
+ percentile: []
+ });
+ return n;
+ }
+
+ function createPercentage(name, numerator, denominator, unit) {
+ const hist = new tr.v.Histogram(name, unit);
+ if (denominator === 0) {
+ hist.addSample(0);
+ } else {
+ hist.addSample(numerator / denominator);
+ }
+ hist.customizeSummaryOptions({
+ avg: true,
+ count: false,
+ max: false,
+ min: false,
+ std: false,
+ sum: false,
+ percentile: []
+ });
+ return hist;
+ }
+
+ /**
+ * Example output:
+ * - v8-gc-full-mark-compactor.
+ */
+ function addDurationOfTopEvents(histograms, model) {
+ tr.metrics.v8.utils.groupAndProcessEvents(model,
+ tr.metrics.v8.utils.isNotForcedTopGarbageCollectionEvent,
+ tr.metrics.v8.utils.topGarbageCollectionEventName,
+ function(name, events) {
+ const cpuDuration = createNumericForTopEventTime(name);
+ events.forEach(function(event) {
+ cpuDuration.addSample(event.cpuDuration);
+ });
+ histograms.addHistogram(cpuDuration);
+ }
+ );
+ }
+
+ /**
+ * Example output:
+ * - v8-gc-total
+ */
+ function addTotalDurationOfTopEvents(histograms, model) {
+ tr.metrics.v8.utils.groupAndProcessEvents(model,
+ tr.metrics.v8.utils.isNotForcedTopGarbageCollectionEvent,
+ event => 'v8-gc-total',
+ function(name, events) {
+ const cpuDuration = createNumericForTopEventTime(name);
+ events.forEach(function(event) {
+ cpuDuration.addSample(event.cpuDuration);
+ });
+ histograms.addHistogram(cpuDuration);
+ }
+ );
+ }
+
+ function isV8MarkCompactorSummary(event) {
+ return !tr.metrics.v8.utils.isForcedGarbageCollectionEvent(event) &&
+ tr.metrics.v8.utils.isMarkCompactorSummaryEvent(event);
+ }
+
+ function isV8MarkCompactorMarkingSummary(event) {
+ return !tr.metrics.v8.utils.isForcedGarbageCollectionEvent(event) &&
+ tr.metrics.v8.utils.isMarkCompactorMarkingSummaryEvent(event);
+ }
+
+ function createHistogramFromSummary(histograms, name, events) {
+ const foregroundDuration =
+ createNumericForTopEventTime(name + '-foreground');
+ const backgroundDuration =
+ createNumericForTopEventTime(name + '-background');
+ const totalDuration =
+ createNumericForTopEventTime(name + '-total');
+ const relatedNames = new tr.v.d.RelatedNameMap();
+ relatedNames.set('foreground', foregroundDuration.name);
+ relatedNames.set('background', backgroundDuration.name);
+ for (const event of events) {
+ foregroundDuration.addSample(event.args.duration);
+ backgroundDuration.addSample(event.args.background_duration);
+ const breakdownForTotal = new tr.v.d.Breakdown();
+ breakdownForTotal.set('foreground', event.args.duration);
+ breakdownForTotal.set('background', event.args.background_duration);
+ totalDuration.addSample(
+ event.args.duration + event.args.background_duration,
+ {breakdown: breakdownForTotal});
+ }
+ histograms.addHistogram(foregroundDuration);
+ histograms.addHistogram(backgroundDuration);
+ histograms.addHistogram(totalDuration, {breakdown: relatedNames});
+ }
+
+ /**
+ * Example output:
+ * - v8-gc-mark-compactor-foreground
+ * - v8-gc-mark-compactor-background
+ * - v8-gc-mark-compactor-total
+ */
+ function addTotalMarkCompactorTime(histograms, model) {
+ tr.metrics.v8.utils.groupAndProcessEvents(model,
+ isV8MarkCompactorSummary,
+ event => 'v8-gc-mark-compactor',
+ (name, events) => createHistogramFromSummary(histograms, name, events)
+ );
+ }
+
+ /**
+ * Example output:
+ * - v8-gc-mark-compactor-marking-foreground
+ * - v8-gc-mark-compactor-marking-background
+ * - v8-gc-mark-compactor-marking-total
+ */
+ function addTotalMarkCompactorMarkingTime(histograms, model) {
+ tr.metrics.v8.utils.groupAndProcessEvents(model,
+ isV8MarkCompactorMarkingSummary,
+ event => 'v8-gc-mark-compactor-marking',
+ (name, events) => createHistogramFromSummary(histograms, name, events)
+ );
+ }
+
+ /**
+ * Example output:
+ * - v8-gc-full-mark-compactor-evacuate.
+ */
+ function addDurationOfSubEvents(histograms, model) {
+ tr.metrics.v8.utils.groupAndProcessEvents(model,
+ tr.metrics.v8.utils.isNotForcedSubGarbageCollectionEvent,
+ tr.metrics.v8.utils.subGarbageCollectionEventName,
+ function(name, events) {
+ const cpuDuration = createNumericForSubEventTime(name);
+ events.forEach(function(event) {
+ cpuDuration.addSample(event.cpuDuration);
+ });
+ histograms.addHistogram(cpuDuration);
+ }
+ );
+ }
+
+ /**
+ * Example output:
+ * - v8-gc-full-mark-compactor_percentage_in_v8_execute.
+ */
+ function addPercentageInV8ExecuteOfTopEvents(histograms, model) {
+ tr.metrics.v8.utils.groupAndProcessEvents(model,
+ tr.metrics.v8.utils.isNotForcedTopGarbageCollectionEvent,
+ tr.metrics.v8.utils.topGarbageCollectionEventName,
+ function(name, events) {
+ addPercentageInV8Execute(histograms, model, name, events);
+ }
+ );
+ }
+
+ /**
+ * Example output:
+ * - v8-gc-total_percentage_in_v8_execute.
+ */
+ function addTotalPercentageInV8Execute(histograms, model) {
+ tr.metrics.v8.utils.groupAndProcessEvents(model,
+ tr.metrics.v8.utils.isNotForcedTopGarbageCollectionEvent,
+ event => 'v8-gc-total',
+ function(name, events) {
+ addPercentageInV8Execute(histograms, model, name, events);
+ }
+ );
+ }
+
+ function addPercentageInV8Execute(histograms, model, name, events) {
+ let cpuDurationInV8Execute = 0;
+ let cpuDurationTotal = 0;
+ events.forEach(function(event) {
+ const v8Execute = tr.metrics.v8.utils.findParent(
+ event, tr.metrics.v8.utils.isV8ExecuteEvent);
+ if (v8Execute) {
+ cpuDurationInV8Execute += event.cpuDuration;
+ }
+ cpuDurationTotal += event.cpuDuration;
+ });
+ const percentage = createPercentage(
+ name + '_percentage_in_v8_execute', cpuDurationInV8Execute,
+ cpuDurationTotal, percentage_smallerIsBetter);
+ histograms.addHistogram(percentage);
+ }
+
+ /**
+ * Example output:
+ * - v8-gc-mark-compactor-mmu-100ms_window.
+ */
+ function addMarkCompactorMutatorUtilization(histograms, model) {
+ const chromeHelper = model.getOrCreateHelper(
+ tr.model.helpers.ChromeModelHelper);
+ const rendererHelpers = Object.values(chromeHelper.rendererHelpers);
+ tr.metrics.v8.utils.addMutatorUtilization(
+ 'v8-gc-mark-compactor-mmu',
+ tr.metrics.v8.utils.isNotForcedMarkCompactorEvent,
+ [100],
+ rendererHelpers,
+ histograms);
+ }
+
+ return {
+ gcMetric,
+ WINDOW_SIZE_MS, // For testing purposes only.
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/v8/gc_metric_test.html b/chromium/third_party/catapult/tracing/tracing/metrics/v8/gc_metric_test.html
new file mode 100644
index 00000000000..8c38b563e62
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/v8/gc_metric_test.html
@@ -0,0 +1,210 @@
+<!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/extras/chrome/chrome_test_utils.html">
+<link rel="import" href="/tracing/extras/importer/trace_event_importer.html">
+<link rel="import" href="/tracing/metrics/v8/gc_metric.html">
+<link rel="import" href="/tracing/model/slice_group.html">
+<link rel="import" href="/tracing/value/histogram_set.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ function createModel(start, end, slices) {
+ return tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
+ const rendererProcess = model.rendererProcess;
+ const mainThread = model.rendererMain;
+ const group = mainThread.sliceGroup;
+ for (const slice of slices) {
+ group.pushSlice(tr.c.TestUtils.newSliceEx(slice));
+ }
+ group.createSubSlices();
+ mainThread.updateBounds();
+ });
+ }
+
+ function constructName(name, suffix) {
+ return name + '_' + suffix;
+ }
+
+ function run(slices) {
+ const histograms = new tr.v.HistogramSet();
+ const startTime = slices.reduce(
+ (acc, slice) => (Math.min(acc, slice.start)));
+ const endTime = slices.reduce((acc, slice) => (Math.max(acc, slice.end)));
+ const model = createModel(startTime - 1, endTime + 1, slices);
+ tr.metrics.v8.gcMetric(histograms, model);
+ return histograms;
+ }
+
+ test('topEvents', function() {
+ const events = {
+ 'V8.GCCompactor': 'v8-gc-full-mark-compactor',
+ 'V8.GCFinalizeMC': 'v8-gc-latency-mark-compactor',
+ 'V8.GCFinalizeMCReduceMemory': 'v8-gc-memory-mark-compactor',
+ 'V8.GCIncrementalMarking': 'v8-gc-incremental-step',
+ 'V8.GCIncrementalMarkingFinalize': 'v8-gc-incremental-finalize',
+ 'V8.GCIncrementalMarkingStart': 'v8-gc-incremental-start',
+ 'V8.GCPhantomHandleProcessingCallback': 'v8-gc-phantom-handle-callback',
+ 'V8.GCScavenger': 'v8-gc-scavenger'
+ };
+ for (const [timelineName, telemetryName] of Object.entries(events)) {
+ const slices = [
+ {
+ title: timelineName, args: {}, start: 100, end: 200,
+ cpuStart: 100, cpuEnd: 200
+ }
+ ];
+ const actual = run(slices);
+
+ const value = actual.getHistogramNamed(telemetryName);
+ assert.strictEqual(value.running.sum, 100);
+ assert.strictEqual(value.numValues, 1);
+ assert.strictEqual(value.average, 100);
+ assert.strictEqual(value.running.max, 100);
+ assert.closeTo(value.getApproximatePercentile(0.90), 100, 1);
+ }
+ });
+
+ test('subEvents', function() {
+ const slices = [
+ {
+ title: 'V8.GCFinalizeMC', args: {}, start: 100, end: 200,
+ cpuStart: 100, cpuEnd: 200
+ },
+ {
+ title: 'V8.GC_MC_MARK', args: {}, start: 110, end: 190,
+ cpuStart: 110, cpuEnd: 190
+ },
+ ];
+ const actual = run(slices);
+ const telemetryName = 'v8-gc-latency-mark-compactor-mark';
+ const value = actual.getHistogramNamed(telemetryName);
+ assert.strictEqual(value.average, 80);
+ assert.strictEqual(value.running.max, 80);
+ assert.closeTo(value.getApproximatePercentile(0.90), 80, 1);
+ });
+
+ test('total', function() {
+ const slices = [
+ {
+ title: 'V8.GCFinalizeMC', args: {}, start: 100, end: 200,
+ cpuStart: 100, cpuEnd: 200
+ },
+ {
+ title: 'V8.GCIncrementalMarking', args: {}, start: 210, end: 290,
+ cpuStart: 210, cpuEnd: 290
+ }
+ ];
+ const actual = run(slices);
+
+ const value = actual.getHistogramNamed('v8-gc-total');
+ assert.strictEqual(value.running.sum, 180);
+ assert.strictEqual(value.numValues, 2);
+ assert.strictEqual(value.average, 90);
+ assert.strictEqual(value.running.max, 100);
+ });
+
+ test('percentageInV8Execute', function() {
+ const slices = [
+ {
+ title: 'V8.Execute',
+ args: {}, start: 100, end: 200,
+ cpuStart: 100, cpuEnd: 200
+ },
+ {
+ title: 'V8.GCFinalizeMC', args: {}, start: 110, end: 190,
+ cpuStart: 110, cpuEnd: 190
+ },
+ {
+ title: 'V8.GCFinalizeMC', args: {}, start: 210, end: 220,
+ cpuStart: 210, cpuEnd: 220
+ }
+ ];
+ const actual = run(slices);
+ const value = actual.getHistogramNamed(
+ 'v8-gc-latency-mark-compactor_percentage_in_v8_execute');
+ assert.strictEqual(value.average,
+ (190 - 110) / ((190 - 110) + (220 - 210)));
+ });
+
+ test('markCompactorMutatorUtilization', function() {
+ const slices = [
+ {
+ title: 'V8.GCIncrementalMarkingStart',
+ args: {}, start: 100, end: 110,
+ cpuStart: 100, cpuEnd: 110
+ },
+ {
+ title: 'V8.GCIncrementalMarking',
+ args: {}, start: 150, end: 160,
+ cpuStart: 150, cpuEnd: 160
+ },
+ {
+ title: 'V8.GCIncrementalMarkingFinalize',
+ args: {}, start: 200, end: 220,
+ cpuStart: 200, cpuEnd: 220
+ },
+ {
+ title: 'V8.GCFinalizeMC',
+ args: {}, start: 250, end: 300,
+ cpuStart: 250, cpuEnd: 300
+ }
+ ];
+ const histograms = run(slices);
+ const mmu = histograms.getHistogramNamed(
+ 'v8-gc-mark-compactor-mmu-100ms_window');
+ assert.closeTo(0.3, mmu.min, 1e-3);
+ assert.strictEqual(mmu.summaryOptions.get('min'), true);
+ assert.strictEqual(mmu.summaryOptions.get('max'), false);
+ });
+
+ test('markCompactorSummary', function() {
+ const slices = [
+ {
+ title: 'V8.GCMarkCompactorSummary',
+ args: {'duration': 3.1, 'background_duration': 1.3},
+ start: 100, end: 100,
+ cpuStart: 100, cpuEnd: 100
+ },
+ ];
+ const histograms = run(slices);
+ const markCompactorForeground = histograms.getHistogramNamed(
+ 'v8-gc-mark-compactor-foreground');
+ assert.closeTo(3.1, markCompactorForeground.sum, 1e-3);
+ const markCompactorBackground = histograms.getHistogramNamed(
+ 'v8-gc-mark-compactor-background');
+ assert.closeTo(1.3, markCompactorBackground.sum, 1e-3);
+ const markCompactorTotal = histograms.getHistogramNamed(
+ 'v8-gc-mark-compactor-total');
+ assert.closeTo(4.4, markCompactorTotal.sum, 1e-3);
+ });
+
+ test('markCompactorMarkingSummary', function() {
+ const slices = [
+ {
+ title: 'V8.GCMarkCompactorMarkingSummary',
+ args: {'duration': 4.1, 'background_duration': 1.4},
+ start: 100, end: 100,
+ cpuStart: 100, cpuEnd: 100
+ },
+ ];
+ const histograms = run(slices);
+ const markCompactorForeground = histograms.getHistogramNamed(
+ 'v8-gc-mark-compactor-marking-foreground');
+ assert.closeTo(4.1, markCompactorForeground.sum, 1e-3);
+ const markCompactorBackground = histograms.getHistogramNamed(
+ 'v8-gc-mark-compactor-marking-background');
+ assert.closeTo(1.4, markCompactorBackground.sum, 1e-3);
+ const markCompactorTotal = histograms.getHistogramNamed(
+ 'v8-gc-mark-compactor-marking-total');
+ assert.closeTo(5.5, markCompactorTotal.sum, 1e-3);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/v8/runtime_stats_metric.html b/chromium/third_party/catapult/tracing/tracing/metrics/v8/runtime_stats_metric.html
new file mode 100644
index 00000000000..286cbaa32f1
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/v8/runtime_stats_metric.html
@@ -0,0 +1,374 @@
+<!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/math/range.html">
+<link rel="import" href="/tracing/base/unit.html">
+<link rel="import" href="/tracing/extras/chrome/event_finder_utils.html">
+<link rel="import" href="/tracing/extras/v8/runtime_stats_entry.html">
+<link rel="import" href="/tracing/metrics/metric_registry.html">
+<link rel="import" href="/tracing/value/diagnostics/related_name_map.html">
+<link rel="import" href="/tracing/value/histogram.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.metrics.v8', function() {
+ const COUNT_CUSTOM_BOUNDARIES = tr.v.HistogramBinBoundaries
+ .createExponential(1, 1000000, 50);
+ const DURATION_CUSTOM_BOUNDARIES = tr.v.HistogramBinBoundaries
+ .createExponential(0.1, 10000, 50);
+ const SUMMARY_OPTIONS = {
+ std: false,
+ count: false,
+ sum: false,
+ min: false,
+ max: false,
+ };
+
+ function computeDomContentLoadedTime_(model) {
+ const chromeHelper = model.getOrCreateHelper(
+ tr.model.helpers.ChromeModelHelper);
+ let domContentLoadedTime = 0;
+
+ for (const rendererHelper of Object.values(chromeHelper.rendererHelpers)) {
+ for (const ev of rendererHelper.mainThread.sliceGroup.childEvents()) {
+ if (ev.title === 'domContentLoadedEventEnd' &&
+ ev.start > domContentLoadedTime) {
+ domContentLoadedTime = ev.start;
+ }
+ }
+ }
+ return domContentLoadedTime;
+ }
+
+ function computeInteractiveTime_(model) {
+ const chromeHelper = model.getOrCreateHelper(
+ tr.model.helpers.ChromeModelHelper);
+ let interactiveTime = 0;
+ for (const expectation of model.userModel.expectations) {
+ if (tr.e.chrome.CHROME_INTERNAL_URLS.includes(
+ expectation.url)) {
+ continue;
+ }
+ if (!(expectation instanceof tr.model.um.LoadExpectation)) continue;
+ if (expectation.timeToInteractive === undefined) continue;
+ // TODO(fmeawad): Support multiple navigations.
+ if (interactiveTime !== 0) throw new Error('Too many navigations');
+ interactiveTime = expectation.timeToInteractive;
+ }
+ return interactiveTime;
+ }
+
+ function convertMicroToMilli_(time) {
+ return tr.b.convertUnit(time,
+ tr.b.UnitPrefixScale.METRIC.MICRO, tr.b.UnitPrefixScale.METRIC.MILLI);
+ }
+
+ // TODO(crbug.com/688342): Remove this function when runtimeStatsMetric is
+ // removed.
+ function computeRuntimeStats(histograms, slices) {
+ const runtimeGroupCollection = new tr.e.v8.RuntimeStatsGroupCollection();
+ runtimeGroupCollection.addSlices(slices);
+
+ function addHistogramsForRuntimeGroup(runtimeGroup, optRelatedNameMaps) {
+ histograms.createHistogram(
+ `${runtimeGroup.name}:duration`,
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter, {
+ value: convertMicroToMilli_(runtimeGroup.time),
+ diagnostics: optRelatedNameMaps ?
+ {samples: optRelatedNameMaps.durationBreakdown} : {}
+ }, {
+ binBoundaries: DURATION_CUSTOM_BOUNDARIES,
+ summaryOptions: SUMMARY_OPTIONS,
+ diagnostics: optRelatedNameMaps ?
+ {samples: optRelatedNameMaps.durationNames} : {}
+ });
+
+ histograms.createHistogram(
+ `${runtimeGroup.name}:count`,
+ tr.b.Unit.byName.count_smallerIsBetter, {
+ value: runtimeGroup.count,
+ diagnostics: optRelatedNameMaps ?
+ {samples: optRelatedNameMaps.countBreakdown} : {}
+ }, {
+ binBoundaries: COUNT_CUSTOM_BOUNDARIES,
+ summaryOptions: SUMMARY_OPTIONS,
+ diagnostics: optRelatedNameMaps ?
+ {samples: optRelatedNameMaps.countNames} : {}
+ });
+ }
+
+ function addDetailedHistogramsForRuntimeGroup(runtimeGroup) {
+ const durationNames = new tr.v.d.RelatedNameMap();
+ const durationBreakdown = new tr.v.d.Breakdown();
+ const countNames = new tr.v.d.RelatedNameMap();
+ const countBreakdown = new tr.v.d.Breakdown();
+
+ for (const entry of runtimeGroup.values) {
+ const durationSampleHistogram = histograms.createHistogram(
+ `${entry.name}:duration`,
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,
+ convertMicroToMilli_(entry.time), {
+ binBoundaries: DURATION_CUSTOM_BOUNDARIES,
+ summaryOptions: SUMMARY_OPTIONS,
+ });
+ durationNames.set(entry.name, durationSampleHistogram.name);
+ durationBreakdown.set(entry.name, convertMicroToMilli_(entry.time));
+
+ const countSampleHistogram = histograms.createHistogram(
+ `${entry.name}:count`,
+ tr.b.Unit.byName.count_smallerIsBetter,
+ entry.count, {
+ binBoundaries: COUNT_CUSTOM_BOUNDARIES,
+ summaryOptions: SUMMARY_OPTIONS,
+ });
+ countNames.set(entry.name, countSampleHistogram.name);
+ countBreakdown.set(entry.name, entry.count);
+ }
+
+ addHistogramsForRuntimeGroup(runtimeGroup, {
+ durationNames,
+ durationBreakdown,
+ countNames,
+ countBreakdown
+ });
+ }
+
+ for (const runtimeGroup of runtimeGroupCollection.runtimeGroups) {
+ addHistogramsForRuntimeGroup(runtimeGroup);
+ }
+
+ const blinkGroupCollection = runtimeGroupCollection.blinkRCSGroupCollection;
+ if (blinkGroupCollection.totalTime > 0) {
+ blinkGroupCollection.runtimeGroups.forEach(
+ addDetailedHistogramsForRuntimeGroup);
+ }
+ }
+
+ // TODO(crbug.com/688342): Remove this metric and use runtimeStatsTotalMetric
+ // instead when the runtimeStatsTotalMetric is stable.
+ function runtimeStatsMetric(histograms, model) {
+ const interactiveTime = computeInteractiveTime_(model);
+ const domContentLoadedTime = computeDomContentLoadedTime_(model);
+ const endTime = Math.max(interactiveTime, domContentLoadedTime);
+ const slices = [...model.getDescendantEvents()].filter(event =>
+ event instanceof tr.e.v8.V8ThreadSlice && event.start <= endTime);
+ computeRuntimeStats(histograms, slices);
+ }
+
+ function addDurationHistogram(railStageName, runtimeGroupName, sampleValue,
+ histograms, durationNamesByGroupName) {
+ const histName = `${railStageName}_${runtimeGroupName}:duration`;
+ histograms.createHistogram(
+ histName,
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,
+ convertMicroToMilli_(sampleValue), {
+ binBoundaries: DURATION_CUSTOM_BOUNDARIES,
+ summaryOptions: SUMMARY_OPTIONS,
+ });
+ let relatedNames = durationNamesByGroupName.get(runtimeGroupName);
+ if (!relatedNames) {
+ relatedNames = new tr.v.d.RelatedNameMap();
+ durationNamesByGroupName.set(runtimeGroupName, relatedNames);
+ }
+ relatedNames.set(railStageName, histName);
+ }
+
+ function addCountHistogram(railStageName, runtimeGroupName, sampleValue,
+ histograms, countNamesByGroupName) {
+ const histName = `${railStageName}_${runtimeGroupName}:count`;
+ histograms.createHistogram(
+ histName,
+ tr.b.Unit.byName.count_smallerIsBetter, sampleValue, {
+ binBoundaries: COUNT_CUSTOM_BOUNDARIES,
+ summaryOptions: SUMMARY_OPTIONS,
+ });
+ let relatedNames = countNamesByGroupName.get(runtimeGroupName);
+ if (!relatedNames) {
+ relatedNames = new tr.v.d.RelatedNameMap();
+ countNamesByGroupName.set(runtimeGroupName, relatedNames);
+ }
+ relatedNames.set(railStageName, histName);
+ }
+
+ function addTotalDurationHistogram(histogramName, time, histograms,
+ relatedNames) {
+ const value = convertMicroToMilli_(time);
+ const breakdown = new tr.v.d.Breakdown();
+ if (relatedNames) {
+ for (const [cat, histName] of relatedNames) {
+ breakdown.set(cat, histograms.getHistogramNamed(histName).average);
+ }
+ }
+ histograms.createHistogram(
+ `${histogramName}:duration`,
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,
+ {value, diagnostics: {'RAIL stages': breakdown}}, {
+ binBoundaries: DURATION_CUSTOM_BOUNDARIES,
+ summaryOptions: SUMMARY_OPTIONS,
+ diagnostics: {'RAIL stages': relatedNames},
+ });
+ }
+
+ function addTotalCountHistogram(histogramName, value, histograms,
+ relatedNames) {
+ const breakdown = new tr.v.d.Breakdown();
+ if (relatedNames) {
+ for (const [cat, histName] of relatedNames) {
+ breakdown.set(cat, histograms.getHistogramNamed(histName).average);
+ }
+ }
+ histograms.createHistogram(
+ `${histogramName}:count`,
+ tr.b.Unit.byName.count_smallerIsBetter,
+ {value, diagnostics: {'RAIL stages': breakdown}}, {
+ binBoundaries: COUNT_CUSTOM_BOUNDARIES,
+ summaryOptions: SUMMARY_OPTIONS,
+ diagnostics: {'RAIL stages': relatedNames},
+ });
+ }
+
+ function computeRuntimeStatsBucketOnUE(histograms, slices,
+ v8SlicesBucketOnUEMap) {
+ const durationNamesByGroupName = new Map();
+ const countNamesByGroupName = new Map();
+
+ // Compute runtimeStats in each of the UE buckets. Also record the
+ // histograms in RelatedNameMap. These histograms are added to the
+ // corresponding histograms in the total bucket as a diagnostic. This keeps
+ // the data grouped.
+ for (const [name, slicesUE] of v8SlicesBucketOnUEMap) {
+ const runtimeGroupCollection = new tr.e.v8.RuntimeStatsGroupCollection();
+ runtimeGroupCollection.addSlices(slicesUE);
+
+ let overallV8Time = runtimeGroupCollection.totalTime;
+ let overallV8Count = runtimeGroupCollection.totalCount;
+ for (const runtimeGroup of runtimeGroupCollection.runtimeGroups) {
+ addDurationHistogram(name, runtimeGroup.name, runtimeGroup.time,
+ histograms, durationNamesByGroupName);
+ if (runtimeGroup.name === 'Blink C++') {
+ overallV8Time -= runtimeGroup.time;
+ }
+
+ addCountHistogram(name, runtimeGroup.name, runtimeGroup.count,
+ histograms, countNamesByGroupName);
+ if (runtimeGroup.name === 'Blink C++') {
+ overallV8Count -= runtimeGroup.count;
+ }
+ }
+
+ if (runtimeGroupCollection.blinkRCSGroupCollection.totalTime > 0) {
+ const blinkRCSGroupCollection =
+ runtimeGroupCollection.blinkRCSGroupCollection;
+ for (const group of blinkRCSGroupCollection.runtimeGroups) {
+ addDurationHistogram(name, group.name, group.time, histograms,
+ durationNamesByGroupName);
+ addCountHistogram(name, group.name, group.count, histograms,
+ countNamesByGroupName);
+ }
+ }
+
+ // Add V8 only time that is Total - Blink C++ duration.
+ addDurationHistogram(name, 'V8-Only', overallV8Time, histograms,
+ durationNamesByGroupName);
+ addCountHistogram(name, 'V8-Only', overallV8Count, histograms,
+ countNamesByGroupName);
+ }
+
+ // Add the runtimeStats for all the samples. Please note, the values in
+ // the UE buckets may not add upto the values computed here. Since UEs
+ // can overlap, we count some of the samples in multiple UE buckets.
+ const runtimeGroupCollection = new tr.e.v8.RuntimeStatsGroupCollection();
+ runtimeGroupCollection.addSlices(slices);
+
+ let overallV8Time = runtimeGroupCollection.totalTime;
+ let overallV8Count = runtimeGroupCollection.totalCount;
+ for (const runtimeGroup of runtimeGroupCollection.runtimeGroups) {
+ addTotalDurationHistogram(runtimeGroup.name, runtimeGroup.time,
+ histograms, durationNamesByGroupName.get(runtimeGroup.name));
+ if (runtimeGroup.name === 'Blink C++') {
+ overallV8Time -= runtimeGroup.time;
+ }
+
+ addTotalCountHistogram(runtimeGroup.name, runtimeGroup.count, histograms,
+ countNamesByGroupName.get(runtimeGroup.name));
+ if (runtimeGroup.name === 'Blink C++') {
+ overallV8Count -= runtimeGroup.count;
+ }
+ }
+
+ if (runtimeGroupCollection.blinkRCSGroupCollection.totalTime > 0) {
+ const blinkRCSGroupCollection =
+ runtimeGroupCollection.blinkRCSGroupCollection;
+ for (const group of blinkRCSGroupCollection.runtimeGroups) {
+ addTotalDurationHistogram(group.name, group.time, histograms,
+ durationNamesByGroupName.get(group.name));
+ addTotalCountHistogram(group.name, group.count, histograms,
+ countNamesByGroupName.get(group.name));
+ }
+ }
+
+ // Add V8 only time that is Total - Blink C++ duration.
+ addTotalDurationHistogram('V8-Only', overallV8Time, histograms,
+ durationNamesByGroupName.get('V8-Only'));
+ addTotalCountHistogram('V8-Only', overallV8Count, histograms,
+ countNamesByGroupName.get('V8-Only'));
+ }
+
+ function runtimeStatsTotalMetric(histograms, model) {
+ const v8ThreadSlices = [...model.getDescendantEvents()].filter(event =>
+ event instanceof tr.e.v8.V8ThreadSlice).sort((e1, e2) =>
+ e1.start - e2.start);
+ const v8SlicesBucketOnUEMap = new Map();
+ // User expectations can sometime overlap. So, certain v8 slices can be
+ // included in more than one expectation. We count such slices in each
+ // of the expectations. This is done so as to minimize the noise due to
+ // the differences in the extent of overlap between the runs.
+ for (const expectation of model.userModel.expectations) {
+ if (tr.e.chrome.CHROME_INTERNAL_URLS.includes(
+ expectation.url)) {
+ continue;
+ }
+ const slices = expectation.range.filterArray(v8ThreadSlices,
+ event => event.start);
+ if (slices.length === 0) continue;
+ // filterArray filters the array that intersects the range inclusively.
+ // Expectations are not inclusive i.e. expectations are like [0, 1),
+ // [1, 2). v8ThreadSlices that start at 1 should be counted only in [1,2)
+ // bucket. Filter out sample at the boundary so that they are not counted
+ // twice.
+ const lastSlice = slices[slices.length - 1];
+ if (!expectation.range.intersectsRangeExclusive(lastSlice.range)) {
+ slices.pop();
+ }
+
+ if (v8SlicesBucketOnUEMap.get(expectation.stageTitle) === undefined) {
+ v8SlicesBucketOnUEMap.set(expectation.stageTitle, slices);
+ } else {
+ const totalSlices = v8SlicesBucketOnUEMap.get(expectation.stageTitle)
+ .concat(slices);
+ v8SlicesBucketOnUEMap.set(expectation.stageTitle, totalSlices);
+ }
+ }
+
+ // Compute runtimeStats in each of the UE buckets and also compute
+ // runtimeStats on all of the samples. The values in UE buckets do not add
+ // up to the total of all samples, since we duplicate some of the samples in
+ // multiple buckets when the UEs overlap.
+ computeRuntimeStatsBucketOnUE(histograms, v8ThreadSlices,
+ v8SlicesBucketOnUEMap);
+ }
+
+ tr.metrics.MetricRegistry.register(runtimeStatsTotalMetric);
+ tr.metrics.MetricRegistry.register(runtimeStatsMetric);
+
+ return {
+ runtimeStatsMetric,
+ runtimeStatsTotalMetric,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/v8/runtime_stats_metric_test.html b/chromium/third_party/catapult/tracing/tracing/metrics/v8/runtime_stats_metric_test.html
new file mode 100644
index 00000000000..91bc8b7a10e
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/v8/runtime_stats_metric_test.html
@@ -0,0 +1,718 @@
+<!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/extras/chrome/chrome_test_utils.html">
+<link rel="import"
+ href="/tracing/extras/chrome/chrome_user_friendly_category_driver.html">
+<link rel="import" href="/tracing/extras/v8/v8_thread_slice.html">
+<link rel="import" href="/tracing/metrics/v8/runtime_stats_metric.html">
+<link rel="import" href="/tracing/value/histogram.html">
+<link rel="import" href="/tracing/value/histogram_set.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ function checkRuntimeHistogram_(histograms, name, count, duration,
+ breakdownHistograms) {
+ const countHistogram = histograms.getHistogramNamed(`${name}:count`);
+ assert.strictEqual(tr.b.getOnlyElement(countHistogram.sampleValues), count);
+ const durationHistogram = histograms.getHistogramNamed(`${name}:duration`);
+ assert.strictEqual(
+ tr.b.getOnlyElement(durationHistogram.sampleValues), duration);
+
+ if (breakdownHistograms === undefined) return;
+ const countBin = tr.b.getOnlyElement(countHistogram.allBins.filter(
+ bin => bin.diagnosticMaps.length > 0));
+ const durationBin = tr.b.getOnlyElement(durationHistogram.allBins.filter(
+ bin => bin.diagnosticMaps.length > 0));
+ for (const name of breakdownHistograms) {
+ assert.notEqual(tr.b.getOnlyElement(countBin.diagnosticMaps)
+ .get('samples').get(name + ':count'), undefined);
+ assert.notEqual(tr.b.getOnlyElement(durationBin.diagnosticMaps)
+ .get('samples').get(name + ':duration'), undefined);
+ }
+ }
+
+ test('runtimeStatsMetricUsingTTI', function() {
+ // The renderer thread timeline looks like:
+ //
+ // * [V8.NewInstance] * [BlinkRuntimeCallStats,V8.Execute] * ...[V8.Ignored]
+ // | | |
+ // | | |
+ // v v v
+ // First navigation FMP TTI
+ // 200 9200 15400
+ const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
+ const rendererProcess = model.rendererProcess;
+ const mainThread = model.rendererMain;
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: 200,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ model.rendererMain.asyncSliceGroup.push(tr.c.TestUtils.newSliceEx({
+ cat: 'disabled-by-default-network',
+ title: 'ResourceLoad',
+ start: 200,
+ duration: 5.0
+ }));
+ rendererProcess.objects.addSnapshot('ptr', 'loading', 'FrameLoader', 300,
+ {isLoadingMainFrame: true, frame: {id_ref: '0xdeadbeef'},
+ documentLoaderURL: 'http://example.com'});
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'loading',
+ title: 'firstMeaningfulPaintCandidate',
+ start: 9200,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'domContentLoadedEventEnd',
+ start: 10000,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'v8',
+ title: 'V8.newInstance',
+ type: tr.e.v8.V8ThreadSlice,
+ start: 12555,
+ duration: 990,
+ args: {
+ 'runtime-call-stats': {
+ JS_Execution: [1, 11],
+ HandleApiCall: [2, 22],
+ CompileFullCode: [3, 33],
+ LoadIC_Miss: [4, 44],
+ ParseLazy: [5, 55],
+ OptimizeCode: [6, 66],
+ FunctionCallback: [7, 77],
+ AllocateInTargetSpace: [8, 88],
+ API_Object_Get: [9, 99]
+ }
+ }
+ }));
+
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'loading',
+ title: 'firstMeaningfulPaintCandidate',
+ start: 9180,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'loading',
+ title: 'firstMeaningfulPaintCandidate',
+ start: 9200,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'domContentLoadedEventEnd',
+ start: 9200,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'toplevel',
+ title: tr.e.chrome.SCHEDULER_TOP_LEVEL_TASK_TITLE,
+ start: 9350,
+ duration: 100,
+ }));
+
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'toplevel',
+ title: tr.e.chrome.SCHEDULER_TOP_LEVEL_TASK_TITLE,
+ start: 11150,
+ duration: 100,
+ }));
+
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'toplevel',
+ title: tr.e.chrome.SCHEDULER_TOP_LEVEL_TASK_TITLE,
+ start: 12550,
+ duration: 1000,
+ }));
+
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'disabled-by-default-v8.runtime_stats',
+ title: 'runtime-call-stats',
+ type: tr.e.v8.V8ThreadSlice,
+ start: 13550,
+ duration: 1000,
+ args: {
+ 'runtime-call-stats': {
+ Blink_Layout_UpdateLayout: [15, 60],
+ }
+ }
+ }));
+
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'v8',
+ title: 'V8.Execute',
+ type: tr.e.v8.V8ThreadSlice,
+ start: 13555,
+ duration: 990,
+ args: {
+ 'runtime-call-stats': {
+ JS_Execution: [1, 11],
+ HandleApiCall: [2, 22],
+ CompileFullCode: [3, 33],
+ StoreIC_Miss: [4, 44],
+ ParseLazy: [5, 55],
+ OptimizeCode: [6, 66],
+ FunctionCallback: [7, 77],
+ AllocateInTargetSpace: [8, 88],
+ API_Object_Get: [9, 99],
+ ParseBackgroundFunctionLiteral: [2, 22],
+ CompileBackgroundIgnition: [3, 33]
+ }
+ }
+ }));
+
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'toplevel',
+ title: tr.e.chrome.SCHEDULER_TOP_LEVEL_TASK_TITLE,
+ start: 14950,
+ duration: 500,
+ }));
+
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'toplevel',
+ title: tr.e.chrome.SCHEDULER_TOP_LEVEL_TASK_TITLE,
+ start: 22150,
+ duration: 10,
+ }));
+
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'v8',
+ title: 'V8.Ignored',
+ type: tr.e.v8.V8ThreadSlice,
+ start: 30000,
+ duration: 1000,
+ args: {
+ 'runtime-call-stats': {
+ JS_Execution: [1, 11],
+ HandleApiCall: [2, 22],
+ CompileFullCode: [3, 33],
+ LoadIC_Miss: [4, 44],
+ ParseLazy: [5, 55],
+ OptimizeCode: [6, 66],
+ FunctionCallback: [7, 77],
+ AllocateInTargetSpace: [8, 88],
+ API_Object_Get: [9, 99],
+ ParseBackgroundFunctionLiteral: [2, 22],
+ CompileBackgroundIgnition: [3, 33]
+ }
+ }
+ }));
+
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'disabled-by-default-v8.runtime_stats',
+ title: 'V8.Ignored',
+ type: tr.e.v8.V8ThreadSlice,
+ start: 30000,
+ duration: 1000,
+ args: {
+ 'runtime-call-stats': {
+ Blink_Layout_UpdateLayout: [15, 60],
+ }
+ }
+ }));
+ });
+
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.v8.runtimeStatsMetric(histograms, model);
+ assert.strictEqual(histograms.length, 54);
+ assert.strictEqual(histograms.sourceHistograms.length, 52);
+
+ // A few of the top level ones.
+ checkRuntimeHistogram_(histograms, 'IC', 8, 0.088);
+ checkRuntimeHistogram_(histograms, 'API', 18, 0.198);
+ checkRuntimeHistogram_(histograms, 'Compile', 6, 0.066);
+ checkRuntimeHistogram_(histograms, 'Total', 95, 1.045);
+ // Lower level one for Blink.
+ checkRuntimeHistogram_(histograms, 'Blink_UpdateLayout', 15, 0.06);
+ });
+
+ test('runtimeStatsMetricUsingDomContentLoaded', function() {
+ // The renderer thread timeline looks like:
+ //
+ // * [V8.NewInstance] * [ V8.Execute ] * [V8.Ignored]
+ // | | |
+ // | | |
+ // v v v
+ // First navigation DCL DCL
+ // 200 1300 2400
+ const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
+ const rendererProcess = model.rendererProcess;
+ const mainThread = model.rendererMain;
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: 200,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'v8',
+ title: 'V8.newInstance',
+ type: tr.e.v8.V8ThreadSlice,
+ start: 300,
+ duration: 990,
+ args: {
+ 'runtime-call-stats': {
+ JS_Execution: [1, 11],
+ HandleApiCall: [2, 22],
+ CompileFullCode: [3, 33],
+ LoadIC_Miss: [4, 44],
+ ParseLazy: [5, 55],
+ OptimizeCode: [6, 66],
+ FunctionCallback: [7, 77],
+ AllocateInTargetSpace: [8, 88],
+ API_Object_Get: [9, 99]
+ }
+ }
+ }));
+
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'domContentLoadedEventEnd',
+ start: 1300,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'v8',
+ title: 'V8.Execute',
+ type: tr.e.v8.V8ThreadSlice,
+ start: 1400,
+ duration: 990,
+ args: {
+ 'runtime-call-stats': {
+ JS_Execution: [1, 11],
+ HandleApiCall: [2, 22],
+ CompileFullCode: [3, 33],
+ StoreIC_Miss: [4, 44],
+ ParseLazy: [5, 55],
+ OptimizeCode: [6, 66],
+ FunctionCallback: [7, 77],
+ AllocateInTargetSpace: [8, 88],
+ API_Object_Get: [9, 99]
+ }
+ }
+ }));
+
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'domContentLoadedEventEnd',
+ start: 2400,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'v8',
+ title: 'V8.Ignored',
+ type: tr.e.v8.V8ThreadSlice,
+ start: 2450,
+ duration: 1000,
+ args: {
+ 'runtime-call-stats': {
+ JS_Execution: [1, 11],
+ HandleApiCall: [2, 22],
+ CompileFullCode: [3, 33],
+ LoadIC_Miss: [4, 44],
+ ParseLazy: [5, 55],
+ OptimizeCode: [6, 66],
+ FunctionCallback: [7, 77],
+ AllocateInTargetSpace: [8, 88],
+ API_Object_Get: [9, 99]
+ }
+ }
+ }));
+ });
+
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.v8.runtimeStatsMetric(histograms, model);
+ assert.strictEqual(histograms.length, 38);
+ assert.strictEqual(histograms.sourceHistograms.length, 38);
+
+ // A few of the top level ones.
+ checkRuntimeHistogram_(histograms, 'IC', 8, 0.088);
+ checkRuntimeHistogram_(histograms, 'API', 18, 0.198);
+ checkRuntimeHistogram_(histograms, 'Compile', 6, 0.066);
+ checkRuntimeHistogram_(histograms, 'Total', 90, 0.99);
+ });
+
+ test('runtimeStatsMetricBucketOnUE', function() {
+ // Test that v8 statistics are properly bucketed when UEs overlap.
+ // The renderer thread timeline looks like:
+ //
+ // * * [ V8 ] * [ V8 ] * [ V8 ] * [ V8 ] * [ V8 ] *
+ // | | | | | | |
+ // | | | | | | |
+ // v v v v v v v
+ // First LoadStart LoadEnd AnimStart RespEnd AnimEnd IdleEnd
+ // nav RespStart IdleStart
+ // 200 300 1000 2000 2100 3000 3500
+
+ const model = tr.c.TestUtils.newModel(function(model) {
+ const rendererProcess = model.getOrCreateProcess(1984);
+ const mainThread = rendererProcess.getOrCreateThread(2);
+ mainThread.name = 'CrRendererMain';
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: 200,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+
+ // Add User expectations.
+ model.userModel.expectations.push(new tr.model.um.LoadExpectation(
+ model, tr.model.um.LOAD_SUBTYPE_NAMES.SUCCESSFUL, 0, 1000));
+
+ model.userModel.expectations.push(new tr.model.um.ResponseExpectation(
+ model, tr.model.um.INITIATOR_TYPE.SCROLL, 1000, 1100));
+
+ model.userModel.expectations.push(new tr.model.um.AnimationExpectation(
+ model, tr.model.um.INITIATOR_TYPE.VIDEO, 2000, 1000));
+
+ model.userModel.expectations.push(new tr.model.um.IdleExpectation(
+ model, 3000, 500));
+
+ // Add V8 ThreadSlices corresponding to Load UE.
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'v8',
+ title: 'V8.newInstance',
+ type: tr.e.v8.V8ThreadSlice,
+ start: 300,
+ duration: 600,
+ args: {
+ 'runtime-call-stats': {
+ JS_Execution: [1, 10],
+ HandleApiCall: [2, 11],
+ CompileFullCode: [3, 12],
+ LoadIC_Miss: [4, 13],
+ ParseLazy: [5, 14],
+ OptimizeCode: [6, 15],
+ FunctionCallback: [7, 16],
+ AllocateInTargetSpace: [8, 17],
+ API_Object_Get: [9, 18]
+ }
+ }
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'v8',
+ title: 'disabled-by-default-v8.runtime_stats',
+ type: tr.e.v8.V8ThreadSlice,
+ start: 350,
+ duration: 400,
+ args: {
+ 'runtime-call-stats': {
+ Blink_Layout_UpdateLayout: [30, 150],
+ Blink_Style_UpdateStyle: [20, 100],
+ }
+ }
+ }));
+
+ // Add V8 Thread slices corresponding to Response UE
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'v8',
+ title: 'V8.Execute',
+ type: tr.e.v8.V8ThreadSlice,
+ start: 1000,
+ duration: 800,
+ args: {
+ 'runtime-call-stats': {
+ JS_Execution: [1, 11],
+ HandleApiCall: [2, 22],
+ CompileFullCode: [3, 33],
+ LoadIC_Miss: [4, 44],
+ ParseLazy: [5, 55],
+ OptimizeCode: [6, 66],
+ FunctionCallback: [7, 77],
+ AllocateInTargetSpace: [8, 88],
+ API_Object_Get: [9, 99]
+ }
+ }
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'v8',
+ title: 'disabled-by-default-v8.runtime_stats',
+ type: tr.e.v8.V8ThreadSlice,
+ start: 1100,
+ duration: 600,
+ args: {
+ 'runtime-call-stats': {
+ Blink_Layout_UpdateLayout: [10, 300],
+ Blink_Style_UpdateStyle: [15, 200],
+ }
+ }
+ }));
+
+ // V8 slices in the overlap range of animation + response
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'v8',
+ title: 'V8.Execute',
+ type: tr.e.v8.V8ThreadSlice,
+ start: 2000,
+ duration: 99,
+ args: {
+ 'runtime-call-stats': {
+ JS_Execution: [1, 1],
+ HandleApiCall: [2, 2],
+ CompileFunctionLiteral: [3, 7],
+ LoadIC_Miss: [4, 4],
+ ParseLazy: [5, 5],
+ OptimizeCode: [6, 6],
+ FunctionCallback: [7, 7],
+ AllocateInTargetSpace: [8, 8],
+ API_Object_Get: [9, 9]
+ }
+ }
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'v8',
+ title: 'disabled-by-default-v8.runtime_stats',
+ type: tr.e.v8.V8ThreadSlice,
+ start: 2010,
+ duration: 79,
+ args: {
+ 'runtime-call-stats': {
+ Blink_Layout_UpdateLayout: [1, 10],
+ Blink_Style_UpdateStyle: [1, 10],
+ }
+ }
+ }));
+
+ // V8 slices in animation UE range.
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'v8',
+ title: 'V8.Execute',
+ type: tr.e.v8.V8ThreadSlice,
+ start: 2200,
+ duration: 700,
+ args: {
+ 'runtime-call-stats': {
+ JS_Execution: [1, 1],
+ HandleApiCall: [2, 2],
+ CompileFullCode: [3, 3],
+ StoreIC_Miss: [4, 4],
+ ParseLazy: [5, 5],
+ OptimizeCode: [6, 6],
+ FunctionCallback: [7, 7],
+ AllocateInTargetSpace: [8, 8],
+ API_Object_Get: [9, 9]
+ }
+ }
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'v8',
+ title: 'disabled-by-default-v8.runtime_stats',
+ type: tr.e.v8.V8ThreadSlice,
+ start: 2300,
+ duration: 600,
+ args: {
+ 'runtime-call-stats': {
+ Blink_Layout_UpdateLayout: [20, 200],
+ Blink_Style_UpdateStyle: [15, 150],
+ }
+ }
+ }));
+
+ // Add V8 slices corresponding to Idle UE.
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'v8',
+ title: 'V8.Execute',
+ type: tr.e.v8.V8ThreadSlice,
+ start: 3001,
+ duration: 499,
+ args: {
+ 'runtime-call-stats': {
+ JS_Execution: [1, 11],
+ HandleApiCall: [2, 22],
+ CompileFullCode: [3, 33],
+ LoadIC_Miss: [4, 44],
+ ParseLazy: [5, 55],
+ OptimizeCode: [6, 66],
+ FunctionCallback: [7, 77],
+ AllocateInTargetSpace: [8, 88],
+ API_Object_Get: [9, 99]
+ }
+ }
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'v8',
+ title: 'disabled-by-default-v8.runtime_stats',
+ type: tr.e.v8.V8ThreadSlice,
+ start: 3100,
+ duration: 300,
+ args: {
+ 'runtime-call-stats': {
+ Blink_Layout_UpdateLayout: [1, 10],
+ Blink_Style_UpdateStyle: [10, 100],
+ }
+ }
+ }));
+ });
+
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.v8.runtimeStatsTotalMetric(histograms, model);
+ assert.strictEqual(histograms.length, 270);
+
+ // Check total:
+ checkRuntimeHistogram_(histograms, 'IC', 20, 0.109);
+ checkRuntimeHistogram_(histograms, 'API', 45, 0.234);
+ checkRuntimeHistogram_(histograms, 'Total', 225, 1.21);
+ checkRuntimeHistogram_(histograms, 'Blink C++', 35, 0.184);
+ checkRuntimeHistogram_(histograms, 'V8-Only', 190, 1.026);
+ checkRuntimeHistogram_(histograms, 'Blink_Layout', 62, 0.67);
+
+ // Check Load bucket:
+ checkRuntimeHistogram_(histograms, 'Load_Parse', 5, 0.014);
+ checkRuntimeHistogram_(histograms, 'Load_JavaScript', 1, 0.01);
+ checkRuntimeHistogram_(histograms, 'Load_Blink C++', 7, 0.016);
+ checkRuntimeHistogram_(histograms, 'Load_V8-Only', 38, 0.11);
+ checkRuntimeHistogram_(histograms, 'Load_Blink_Style', 20, 0.1);
+
+ // Check Response bucket:
+ checkRuntimeHistogram_(histograms, 'Response_Parse', 10, 0.06);
+ checkRuntimeHistogram_(histograms, 'Response_Compile', 6, 0.04);
+ checkRuntimeHistogram_(histograms, 'Response_Blink_Layout', 11, 0.31);
+
+ // Check Animation bucket:
+ checkRuntimeHistogram_(histograms, 'Animation_Parse', 10, 0.01);
+ checkRuntimeHistogram_(histograms, 'Animation_Blink_Style', 16, 0.16);
+
+ // Check Idle bucket:
+ checkRuntimeHistogram_(histograms, 'Idle_Parse', 5, 0.055);
+ checkRuntimeHistogram_(histograms, 'Idle_Blink_Parsing', 0, 0);
+ });
+
+ test('runtimeStatsMetricTotalNoUE', function() {
+ // Test that total v8 count works even without UE.
+ // The renderer thread timeline looks like:
+ //
+ // * [V8.NewInstance] * [ V8.Execute ] *
+ // |
+ // v
+ // First navigation
+ // 200
+
+ const model = tr.c.TestUtils.newModel(function(model) {
+ const rendererProcess = model.getOrCreateProcess(1984);
+ const mainThread = rendererProcess.getOrCreateThread(2);
+ mainThread.name = 'CrRendererMain';
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: 200,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'v8',
+ title: 'V8.newInstance',
+ type: tr.e.v8.V8ThreadSlice,
+ start: 300,
+ duration: 600,
+ args: {
+ 'runtime-call-stats': {
+ JS_Execution: [1, 10],
+ HandleApiCall: [2, 11],
+ CompileFullCode: [3, 12],
+ StoreIC_Miss: [4, 13],
+ ParseLazy: [5, 14],
+ OptimizeCode: [6, 15],
+ FunctionCallback: [7, 16],
+ AllocateInTargetSpace: [8, 17],
+ API_Object_Get: [9, 18]
+ }
+ }
+ }));
+
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'v8',
+ title: 'disabled-by-default-v8.runtime_stats',
+ type: tr.e.v8.V8ThreadSlice,
+ start: 350,
+ duration: 400,
+ args: {
+ 'runtime-call-stats': {
+ Blink_Layout_UpdateLayout: [5, 100],
+ Blink_Style_UpdateStyle: [2, 50],
+ }
+ }
+ }));
+
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'v8',
+ title: 'V8.Execute',
+ type: tr.e.v8.V8ThreadSlice,
+ start: 1100,
+ duration: 800,
+ args: {
+ 'runtime-call-stats': {
+ JS_Execution: [1, 11],
+ HandleApiCall: [2, 22],
+ CompileFullCode: [3, 33],
+ LoadIC_Miss: [4, 44],
+ ParseFunctionLiteral: [5, 55],
+ OptimizeCode: [6, 66],
+ FunctionCallback: [7, 77],
+ AllocateInTargetSpace: [8, 88],
+ API_Context_New: [9, 99]
+ }
+ }
+ }));
+
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'v8',
+ title: 'disabled-by-default-v8.runtime_stats',
+ type: tr.e.v8.V8ThreadSlice,
+ start: 1200,
+ duration: 500,
+ args: {
+ 'runtime-call-stats': {
+ Blink_Layout_UpdateLayout: [10, 200],
+ Blink_Style_UpdateStyle: [10, 100],
+ }
+ }
+ }));
+ });
+
+ const histograms = new tr.v.HistogramSet();
+ tr.metrics.v8.runtimeStatsTotalMetric(histograms, model);
+ assert.strictEqual(histograms.length, 54);
+
+ // Check total:
+ checkRuntimeHistogram_(histograms, 'IC', 8, 0.057);
+ checkRuntimeHistogram_(histograms, 'API', 18, 0.117);
+ checkRuntimeHistogram_(histograms, 'Parse', 10, 0.069);
+ checkRuntimeHistogram_(histograms, 'Total', 90, 0.621);
+ checkRuntimeHistogram_(histograms, 'Blink C++', 14, 0.093);
+ checkRuntimeHistogram_(histograms, 'V8-Only', 76, 0.528);
+ checkRuntimeHistogram_(histograms, 'Blink_Layout', 15, 0.3);
+ checkRuntimeHistogram_(histograms, 'Blink_Style', 12, 0.15);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/v8/utils.html b/chromium/third_party/catapult/tracing/tracing/metrics/v8/utils.html
new file mode 100644
index 00000000000..b9ec7ed80bd
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/v8/utils.html
@@ -0,0 +1,490 @@
+<!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/category_util.html">
+<link rel="import" href="/tracing/base/math/piecewise_linear_function.html">
+<link rel="import" href="/tracing/base/math/range.html">
+<link rel="import" href="/tracing/base/math/range_utils.html">
+<link rel="import" href="/tracing/base/unit.html">
+<link rel="import" href="/tracing/extras/v8/v8_thread_slice.html">
+<link rel="import" href="/tracing/metrics/metric_registry.html">
+<link rel="import" href="/tracing/value/histogram.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.metrics.v8.utils', function() {
+ // The title of the idle task event.
+ const IDLE_TASK_EVENT = 'SingleThreadIdleTaskRunner::RunTask';
+
+ // V8 execution event.
+ const V8_EXECUTE = 'V8.Execute';
+
+ // GC events start with this prefix.
+ const GC_EVENT_PREFIX = 'V8.GC';
+
+ // Special handling is required for full GCs inside low memory notification.
+ const FULL_GC_EVENT = 'V8.GCCompactor';
+
+ const LOW_MEMORY_EVENT = 'V8.GCLowMemoryNotification';
+
+ const MAJOR_GC_EVENT = 'MajorGC';
+ const MINOR_GC_EVENT = 'MinorGC';
+
+ // Maps the top-level GC events in timeline to telemetry friendly names.
+ const TOP_GC_EVENTS = {
+ 'V8.GCCompactor': 'v8-gc-full-mark-compactor',
+ 'V8.GCFinalizeMC': 'v8-gc-latency-mark-compactor',
+ 'V8.GCFinalizeMCReduceMemory': 'v8-gc-memory-mark-compactor',
+ 'V8.GCIncrementalMarking': 'v8-gc-incremental-step',
+ 'V8.GCIncrementalMarkingFinalize': 'v8-gc-incremental-finalize',
+ 'V8.GCIncrementalMarkingStart': 'v8-gc-incremental-start',
+ 'V8.GCPhantomHandleProcessingCallback': 'v8-gc-phantom-handle-callback',
+ 'V8.GCScavenger': 'v8-gc-scavenger'
+ };
+
+ const MARK_COMPACTOR_EVENTS = new Set([
+ 'V8.GCCompactor',
+ 'V8.GCFinalizeMC',
+ 'V8.GCFinalizeMCReduceMemory',
+ 'V8.GCIncrementalMarking',
+ 'V8.GCIncrementalMarkingFinalize',
+ 'V8.GCIncrementalMarkingStart',
+ 'V8.GCPhantomHandleProcessingCallback'
+ ]);
+
+ const LOW_MEMORY_MARK_COMPACTOR = 'v8-gc-low-memory-mark-compactor';
+
+ /**
+ * Finds the first parent of the |event| for which the |predicate| holds.
+ */
+ function findParent(event, predicate) {
+ let parent = event.parentSlice;
+ while (parent) {
+ if (predicate(parent)) {
+ return parent;
+ }
+ parent = parent.parentSlice;
+ }
+ return null;
+ }
+
+ function isIdleTask(event) {
+ return event.title === IDLE_TASK_EVENT;
+ }
+
+ function isLowMemoryEvent(event) {
+ return event.title === LOW_MEMORY_EVENT;
+ }
+
+ function isV8Event(event) {
+ return event.title.startsWith('V8.');
+ }
+
+ function isV8ExecuteEvent(event) {
+ return event.title === V8_EXECUTE;
+ }
+
+ function isTopV8ExecuteEvent(event) {
+ return isV8ExecuteEvent(event) && findParent(isV8ExecuteEvent) === null;
+ }
+
+ function isGarbageCollectionEvent(event) {
+ // Low memory notification is handled specially because it contains
+ // several full mark compact events.
+ return event.title && event.title.startsWith(GC_EVENT_PREFIX) &&
+ event.title !== LOW_MEMORY_EVENT;
+ }
+
+ function isTopGarbageCollectionEvent(event) {
+ return event.title in TOP_GC_EVENTS;
+ }
+
+ function isForcedGarbageCollectionEvent(event) {
+ return findParent(event, isLowMemoryEvent) !== null;
+ }
+
+ function isSubGarbageCollectionEvent(event) {
+ // To reduce number of results, we return only the first level of GC
+ // subevents. Some subevents are nested in MajorGC or MinorGC events, so
+ // we have to check for it explicitly.
+ return isGarbageCollectionEvent(event) &&
+ event.parentSlice &&
+ (isTopGarbageCollectionEvent(event.parentSlice) ||
+ event.parentSlice.title === MAJOR_GC_EVENT ||
+ event.parentSlice.title === MINOR_GC_EVENT);
+ }
+
+ function isNotForcedTopGarbageCollectionEvent(event) {
+ // We exclude garbage collection events forced by benchmark runner,
+ // because they cannot happen in real world.
+ return tr.metrics.v8.utils.isTopGarbageCollectionEvent(event) &&
+ !tr.metrics.v8.utils.isForcedGarbageCollectionEvent(event);
+ }
+
+ function isNotForcedSubGarbageCollectionEvent(event) {
+ // We exclude garbage collection events forced by benchmark runner,
+ // because they cannot happen in real world.
+ return tr.metrics.v8.utils.isSubGarbageCollectionEvent(event) &&
+ !tr.metrics.v8.utils.isForcedGarbageCollectionEvent(event);
+ }
+
+ function isFullMarkCompactorEvent(event) {
+ return event.title === 'V8.GCCompactor';
+ }
+
+ function isMarkCompactorSummaryEvent(event) {
+ return event.title === 'V8.GCMarkCompactorSummary';
+ }
+
+ function isMarkCompactorMarkingSummaryEvent(event) {
+ return event.title === 'V8.GCMarkCompactorMarkingSummary';
+ }
+
+ function isIncrementalMarkingEvent(event) {
+ return event.title.startsWith('V8.GCIncrementalMarking');
+ }
+
+ function isLatencyMarkCompactorEvent(event) {
+ return event.title === 'V8.GCFinalizeMC';
+ }
+
+ function isMemoryMarkCompactorEvent(event) {
+ return event.title === 'V8.GCFinalizeMCReduceMemory';
+ }
+
+ function isScavengerEvent(event) {
+ return event.title === 'V8.GCScavenger';
+ }
+
+ function isCompileOptimizeRCSCategory(name) {
+ return name === 'Optimize';
+ }
+
+ function isCompileUnoptimizeRCSCategory(name) {
+ return name === 'Compile';
+ }
+
+ function isCompileParseRCSCategory(name) {
+ return name === 'Parse';
+ }
+
+ function isCompileRCSCategory(name) {
+ return name === 'Compile' || name === 'Optimize' || name === 'Parse';
+ }
+
+ function isV8RCSEvent(event) {
+ return event instanceof tr.e.v8.V8ThreadSlice;
+ }
+
+ function isMarkCompactorEvent(event) {
+ return MARK_COMPACTOR_EVENTS.has(event.title);
+ }
+
+ function isNotForcedMarkCompactorEvent(event) {
+ return !isForcedGarbageCollectionEvent(event) &&
+ isMarkCompactorEvent(event);
+ }
+
+ function forcedGCEventName() {
+ return LOW_MEMORY_EVENT;
+ }
+
+ function topGarbageCollectionEventName(event) {
+ if (event.title === FULL_GC_EVENT) {
+ // Full mark compact events inside a low memory notification
+ // are counted as low memory mark compacts.
+ if (findParent(event, isLowMemoryEvent)) {
+ return LOW_MEMORY_MARK_COMPACTOR;
+ }
+ }
+ return TOP_GC_EVENTS[event.title];
+ }
+
+ function subGarbageCollectionEventName(event) {
+ const topEvent = findParent(event, isTopGarbageCollectionEvent);
+ const prefix = topEvent ? topGarbageCollectionEventName(topEvent) :
+ 'unknown';
+ // Remove redundant prefixes and convert to lower case.
+ const name = event.title.replace('V8.GC_MC_', '')
+ .replace('V8.GC_SCAVENGER_', '')
+ .replace('V8.GC_', '')
+ .replace(/_/g, '-').toLowerCase();
+ return prefix + '-' + name;
+ }
+
+ /**
+ * Filters events using the |filterCallback|, then groups events by the user
+ * the name computed using the |nameCallback|, and then invokes
+ * the |processCallback| with the grouped events.
+ * @param {Function} filterCallback Takes an event and returns a boolean.
+ * @param {Function} nameCallback Takes event and returns a string.
+ * @param {Function} processCallback Takes a name, and an array of events.
+ */
+ function groupAndProcessEvents(model, filterCallback,
+ nameCallback, processCallback) {
+ // Map: name -> [events].
+ const nameToEvents = {};
+ for (const event of model.getDescendantEvents()) {
+ if (!filterCallback(event)) continue;
+ const name = nameCallback(event);
+ nameToEvents[name] = nameToEvents[name] || [];
+ nameToEvents[name].push(event);
+ }
+ for (const [name, events] of Object.entries(nameToEvents)) {
+ processCallback(name, events);
+ }
+ }
+
+ /**
+ * Given a list of intervals, returns a new list with all overalapping
+ * intervals merged into a single interval.
+ */
+ function unionOfIntervals(intervals) {
+ if (intervals.length === 0) return [];
+ return tr.b.math.mergeRanges(
+ intervals.map(x => { return { min: x.start, max: x.end }; }), 1e-6,
+ function(ranges) {
+ return {
+ start: ranges.reduce(
+ (acc, x) => Math.min(acc, x.min), ranges[0].min),
+ end: ranges.reduce((acc, x) => Math.max(acc, x.max), ranges[0].max)
+ };
+ }
+ );
+ }
+
+ function hasV8Stats(globalMemoryDump) {
+ let v8stats = undefined;
+ globalMemoryDump.iterateContainerDumps(function(dump) {
+ v8stats = v8stats || dump.getMemoryAllocatorDumpByFullName('v8');
+ });
+ return !!v8stats;
+ }
+
+ function rangeForMemoryDumps(model) {
+ const startOfFirstDumpWithV8 =
+ model.globalMemoryDumps.filter(hasV8Stats).reduce(
+ (start, dump) => Math.min(start, dump.start), Infinity);
+ // Empty range.
+ if (startOfFirstDumpWithV8 === Infinity) return new tr.b.math.Range();
+ return tr.b.math.Range.fromExplicitRange(startOfFirstDumpWithV8, Infinity);
+ }
+
+ /**
+ * An end-point of a window that is sliding from left to right
+ * over |points| starting from time |start|.
+ * It is intended to be used only by the |mutatorUtilization| function.
+ * @constructor
+ */
+ class WindowEndpoint {
+ constructor(start, points) {
+ this.points = points;
+ // The index of the last passed point.
+ this.lastIndex = -1;
+ // The position of the end-point in the time line.
+ this.position = start;
+ this.distanceUntilNextPoint = points[0].position - start;
+ // The cumulative duration of GC pauses until this position.
+ this.cummulativePause = 0;
+ // The number of entered GC intervals.
+ this.stackDepth = 0;
+ }
+
+ // Advance the end-point by the given |delta|.
+ advance(delta) {
+ if (delta < this.distanceUntilNextPoint) {
+ this.position += delta;
+ this.cummulativePause += this.stackDepth > 0 ? delta : 0;
+ this.distanceUntilNextPoint =
+ this.points[this.lastIndex + 1].position - this.position;
+ } else {
+ this.position += this.distanceUntilNextPoint;
+ this.cummulativePause +=
+ this.stackDepth > 0 ? this.distanceUntilNextPoint : 0;
+ this.distanceUntilNextPoint = 0;
+ this.lastIndex++;
+ if (this.lastIndex < this.points.length) {
+ this.stackDepth += this.points[this.lastIndex].delta;
+ if (this.lastIndex + 1 < this.points.length) {
+ this.distanceUntilNextPoint =
+ this.points[this.lastIndex + 1].position - this.position;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns mutator utilization as a piecewise linear function.
+ * Mutator utilization for a window size w is a function of time mu_w(t)
+ * that shows how much time in [t, t+w] is left for the mutator relative
+ * to the time window size.
+ * More formally:
+ * mu_w(t) = (w - total_time_spent_in_gc_in(t, t + w)) / w.
+ * The range of mu_w(t) is [0..1].
+ * See "A Parallel, Real-Time Garbage Collector" by Cheng et. al. for
+ * more info [https://www.cs.cmu.edu/~guyb/papers/gc2001.pdf].
+ *
+ * All parameters must use the same time unit.
+ * @param {number} start The start time of execution.
+ * @param {number} end The end time of execution.
+ * @param {number} timeWindow The size of the time window.
+ * @param {!Array<!{start: number, end: number}>} intervals The list of
+ * GC pauses.
+ */
+ function mutatorUtilization(start, end, timeWindow, intervals) {
+ const mu = new tr.b.math.PiecewiseLinearFunction();
+ // If the interval is smaller than the time window, then the function is
+ // empty.
+ if (end - start <= timeWindow) {
+ return mu;
+ }
+
+ // If there are GC pauses then the mutator utilization is 1.0.
+ if (intervals.length === 0) {
+ mu.push(start, 1.0, end - timeWindow, 1.0);
+ return mu;
+ }
+
+ intervals = unionOfIntervals(intervals);
+
+ // Create a point for the start and the end of each interval.
+ const points = [];
+ for (const interval of intervals) {
+ points.push({position: interval.start, delta: 1});
+ points.push({position: interval.end, delta: -1});
+ }
+ points.sort((a, b) => a.position - b.position);
+ points.push({position: end, delta: 0});
+
+ // The left and the right limit of the sliding window.
+ const left = new WindowEndpoint(start, points);
+ const right = new WindowEndpoint(start, points);
+
+ // Advance the right end-point until we get the correct window size.
+ // Allow the floating-point precision errors of this magnitude.
+ const EPSILON = 1e-6;
+ while (right.position - left.position < timeWindow - EPSILON) {
+ right.advance(timeWindow - (right.position - left.position));
+ }
+
+ while (right.lastIndex < points.length) {
+ // Advance the window end-points by the largest possible amount
+ // without jumping over a point.
+ const distanceUntilNextPoint =
+ Math.min(left.distanceUntilNextPoint, right.distanceUntilNextPoint);
+ const position1 = left.position;
+ const value1 = right.cummulativePause - left.cummulativePause;
+ left.advance(distanceUntilNextPoint);
+ right.advance(distanceUntilNextPoint);
+ // Add a new mutator utilization segment only if it is non-trivial.
+ if (distanceUntilNextPoint > 0) {
+ const position2 = left.position;
+ const value2 = right.cummulativePause - left.cummulativePause;
+ mu.push(position1, 1.0 - value1 / timeWindow,
+ position2, 1.0 - value2 / timeWindow);
+ }
+ }
+ return mu;
+ }
+
+ /**
+ * Computes the minimum mutator utilization (MMU) metric for the given time
+ * windows and the given renderers. The results are added as histograms to
+ * the given histogram set.
+ *
+ * For example, passing 'v8-gc-mark-compactor-mmu' as the metric name and
+ * [16, 50, 100] as the time windows will produce the following:
+ * - v8-gc-mark-compactor-mmu-16ms_window
+ * - v8-gc-mark-compactor-mmu-50ms_window
+ * - v8-gc-mark-compactor-mmu-100ms_window
+ *
+ * @param {!string} metricName the name of the metric.
+ * @param {!function(tr.b.Event): boolean} eventFilter the predicate for
+ * filtering the events that will be used for computing the MMU.
+ * @param {!Array.<tr.model.helpers.ChromeRendererHelper>} rendererHelpers
+ * @param {!tr.v.HistogramSet} histograms
+ */
+ function addMutatorUtilization(
+ metricName, eventFilter, timeWindows, rendererHelpers, histograms) {
+ const histogramMap = new Map();
+
+ for (const timeWindow of timeWindows) {
+ const summaryOptions = {
+ avg: false,
+ count: false,
+ max: false,
+ min: true,
+ std: false,
+ sum: false
+ };
+ const description =
+ `The minimum mutator utilization in ${timeWindow}ms time window`;
+ const histogram = histograms.createHistogram(
+ `${metricName}-${timeWindow}ms_window`,
+ tr.b.Unit.byName.normalizedPercentage_biggerIsBetter,
+ [], {summaryOptions, description});
+ histogramMap.set(timeWindow, histogram);
+ }
+
+ for (const rendererHelper of rendererHelpers) {
+ if (rendererHelper.isChromeTracingUI) continue;
+ const pauses = [];
+ for (const event of rendererHelper.mainThread.sliceGroup.childEvents()) {
+ if (eventFilter(event) && event.end > event.start) {
+ pauses.push({start: event.start, end: event.end});
+ }
+ }
+ pauses.sort((a, b) => a.start - b.start);
+ const start = rendererHelper.mainThread.bounds.min;
+ const end = rendererHelper.mainThread.bounds.max;
+ for (const timeWindow of timeWindows) {
+ const mu = mutatorUtilization(start, end, timeWindow, pauses);
+ histogramMap.get(timeWindow).addSample(mu.min);
+ }
+ }
+ }
+
+
+ return {
+ addMutatorUtilization,
+ findParent,
+ forcedGCEventName,
+ groupAndProcessEvents,
+ isForcedGarbageCollectionEvent,
+ isFullMarkCompactorEvent,
+ isGarbageCollectionEvent,
+ isIdleTask,
+ isIncrementalMarkingEvent,
+ isLatencyMarkCompactorEvent,
+ isLowMemoryEvent,
+ isMarkCompactorSummaryEvent,
+ isMarkCompactorMarkingSummaryEvent,
+ isMemoryMarkCompactorEvent,
+ isNotForcedMarkCompactorEvent,
+ isNotForcedTopGarbageCollectionEvent,
+ isNotForcedSubGarbageCollectionEvent,
+ isScavengerEvent,
+ isSubGarbageCollectionEvent,
+ isTopGarbageCollectionEvent,
+ isTopV8ExecuteEvent,
+ isV8Event,
+ isV8ExecuteEvent,
+ isV8RCSEvent,
+ isCompileRCSCategory,
+ isCompileOptimizeRCSCategory,
+ isCompileUnoptimizeRCSCategory,
+ isCompileParseRCSCategory,
+ mutatorUtilization,
+ rangeForMemoryDumps,
+ subGarbageCollectionEventName,
+ topGarbageCollectionEventName,
+ unionOfIntervals,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/v8/utils_test.html b/chromium/third_party/catapult/tracing/tracing/metrics/v8/utils_test.html
new file mode 100644
index 00000000000..dc45524508a
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/v8/utils_test.html
@@ -0,0 +1,189 @@
+<!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/metrics/v8/utils.html">
+<link rel="import" href="/tracing/model/memory_dump_test_utils.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ function addDumpWithAllocator(model, process, allocator, start) {
+ const gmd = tr.model.MemoryDumpTestUtils.addGlobalMemoryDump(
+ model, {ts: start});
+ const pmd = tr.model.MemoryDumpTestUtils.addProcessMemoryDump(gmd, process);
+ pmd.memoryAllocatorDumps =
+ [tr.model.MemoryDumpTestUtils.newAllocatorDump(pmd, allocator)];
+ }
+
+ const unionOfIntervals = tr.metrics.v8.utils.unionOfIntervals;
+
+ function interval(start, end) {
+ return {start, end};
+ }
+
+ /**
+ * Brute force computation of the mutator utilization.
+ * The function steps from the start to the end and for each step
+ * computes the mutator utilization.
+ */
+ function mutatorUtilizationSlow(start, end, timeWindow, intervals) {
+ const STEP = 0.001;
+ function timeToIndex(time) {
+ return Math.floor((time - start) / STEP);
+ }
+ const N = timeToIndex(end) + 1;
+ // bitmap[i] === true means that GC is active at time i.
+ const bitmap = new Array(N);
+ for (let i = 0; i < N; i++) {
+ bitmap[i] = false;
+ }
+ intervals.forEach(function(interval) {
+ const start = timeToIndex(interval.start);
+ const end = timeToIndex(interval.end);
+ for (let i = start; i < end; i++) {
+ bitmap[i] = true;
+ }
+ });
+ const pause = new Array(N);
+ for (let i = 0; i < N; i++) {
+ pause[i] = (i > 0 ? pause[i - 1] : 0) + (bitmap[i] ? 1 : 0);
+ }
+ const windowWidth = timeToIndex(timeWindow);
+ const mu = new Array(Math.max(N - windowWidth, 0));
+ for (let i = 0; i < mu.length; i++) {
+ const value = pause[i + windowWidth] - pause[i];
+ mu[i] = 1.0 - value / windowWidth;
+ }
+ mu.sort((a, b) => a - b);
+ return {
+ average: mu.reduce((acc, x) => (acc + x), 0) / mu.length,
+ min: mu.reduce((acc, x) => Math.min(acc, x), 0),
+ max: mu.reduce((acc, x) => Math.max(acc, x), 0),
+ percentile(percent) {
+ return mu[Math.floor(percent * (mu.length - 1))];
+ }
+ };
+ }
+
+ /**
+ * Constructs PiecewiseLinearFunction from pieces.
+ * @param {!Array<!{x1: number, y1: number, x2: number, y2: number}>} pieces
+ * The list of pieces ordered by the x coordinate.
+ */
+ function createExpectedFunction(pieces) {
+ const f = new tr.b.math.PiecewiseLinearFunction();
+ pieces.forEach(function(p) {
+ f.push(p.x1, p.y1, p.x2, p.y2);
+ });
+ return f;
+ }
+
+ test('unionOfIntervals', function() {
+ assert.deepEqual(unionOfIntervals([]), []);
+ assert.deepEqual(unionOfIntervals([interval(1, 1)]), [interval(1, 1)]);
+ assert.deepEqual(
+ unionOfIntervals([interval(0, 1), interval(1, 2), interval(2, 3)]),
+ [interval(0, 3)]);
+ assert.deepEqual(
+ unionOfIntervals([interval(0, 1), interval(1, 2), interval(3, 3)]),
+ [interval(0, 2), interval(3, 3)]);
+ assert.deepEqual(
+ unionOfIntervals([interval(0, 10), interval(1, 2), interval(3, 3)]),
+ [interval(0, 10)]);
+ assert.deepEqual(
+ unionOfIntervals([interval(0, 10), interval(1, 2), interval(3, 11)]),
+ [interval(0, 11)]);
+ assert.deepEqual(
+ unionOfIntervals([interval(3, 10), interval(1, 2), interval(11, 11)]),
+ [interval(1, 2), interval(3, 10), interval(11, 11)]);
+ });
+
+ test('basicMutatorUtilization', function() {
+ assert.deepEqual(
+ tr.metrics.v8.utils.mutatorUtilization(0, 40, 10, [interval(10, 20)]),
+ createExpectedFunction([
+ { x1: 0, y1: 1.0, x2: 10, y2: 0.0 },
+ { x1: 10, y1: 0.0, x2: 20, y2: 1.0 },
+ { x1: 20, y1: 1.0, x2: 30, y2: 1.0 }])
+ );
+ assert.deepEqual(
+ tr.metrics.v8.utils.mutatorUtilization(0, 40, 10, [interval(10, 15)]),
+ createExpectedFunction([
+ { x1: 0, y1: 1.0, x2: 5, y2: 0.5 },
+ { x1: 5, y1: 0.5, x2: 10, y2: 0.5 },
+ { x1: 10, y1: 0.5, x2: 15, y2: 1.0 },
+ { x1: 15, y1: 1.0, x2: 30, y2: 1.0 }])
+ );
+ assert.deepEqual(
+ tr.metrics.v8.utils.mutatorUtilization(0, 60, 20,
+ [interval(30, 35), interval(40, 45)]),
+ createExpectedFunction([
+ { x1: 0, y1: 1.0, x2: 10, y2: 1.0 },
+ { x1: 10, y1: 1.0, x2: 15, y2: 0.75 },
+ { x1: 15, y1: 0.75, x2: 20, y2: 0.75 },
+ { x1: 20, y1: 0.75, x2: 25, y2: 0.5 },
+ { x1: 25, y1: 0.5, x2: 30, y2: 0.5 },
+ { x1: 30, y1: 0.5, x2: 35, y2: 0.75 },
+ { x1: 35, y1: 0.75, x2: 40, y2: 0.75 }])
+ );
+ });
+
+ test('mutatorUtilization', function() {
+ const pauses = [
+ interval(10, 20),
+ interval(15, 23),
+ interval(30, 31),
+ interval(33, 34),
+ interval(60, 61),
+ interval(61, 63),
+ interval(80, 88)];
+ const actual = tr.metrics.v8.utils.mutatorUtilization(0, 100, 7, pauses);
+ const expected = mutatorUtilizationSlow(0, 100, 7, pauses);
+ assert.closeTo(expected.average, actual.average, 1e-3);
+ assert.closeTo(expected.max, actual.max, 1e-3);
+ assert.closeTo(expected.min, actual.min, 1e-3);
+ assert.closeTo(expected.percentile(0.5), actual.percentile(0.5), 1e-3);
+ assert.closeTo(expected.percentile(0.9), actual.percentile(0.9), 1e-3);
+ });
+
+ test('mutatorUtilizationMakesProgress', function() {
+ const start = 44173.32375717163;
+ const end = 80378.0457572937;
+ const pauses = [
+ interval(500, 700),
+ interval(900, 990)];
+ const actual = tr.metrics.v8.utils.mutatorUtilization(
+ start, end, 16.67, pauses);
+ assert.closeTo(0.9548, actual.average, 1e-3);
+ });
+
+
+ test('rangeForMemoryDumps', function() {
+ const model = tr.c.TestUtils.newModel(function(model) {
+ const process = model.getOrCreateProcess(1);
+ addDumpWithAllocator(model, process, 'dummy', 10);
+ addDumpWithAllocator(model, process, 'v8', 20);
+ });
+ const range = tr.metrics.v8.utils.rangeForMemoryDumps(model);
+ assert.isFalse(range.isEmpty);
+ assert.strictEqual(range.min, 20);
+ assert.strictEqual(range.max, Infinity);
+ });
+
+ test('rangeForMemoryDumpsEmpty', function() {
+ const model = tr.c.TestUtils.newModel(function(model) {
+ const process = model.getOrCreateProcess(1);
+ addDumpWithAllocator(model, process, 'dummy', 10);
+ addDumpWithAllocator(model, process, 'dummy', 20);
+ });
+ const range = tr.metrics.v8.utils.rangeForMemoryDumps(model);
+ assert.isTrue(range.isEmpty);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/v8/v8_metrics.html b/chromium/third_party/catapult/tracing/tracing/metrics/v8/v8_metrics.html
new file mode 100644
index 00000000000..507d23e6119
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/v8/v8_metrics.html
@@ -0,0 +1,30 @@
+<!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/metrics/metric_registry.html">
+<link rel="import" href="/tracing/metrics/system_health/memory_metric.html">
+<link rel="import" href="/tracing/metrics/v8/execution_metric.html">
+<link rel="import" href="/tracing/metrics/v8/gc_metric.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.metrics.v8', function() {
+ function v8AndMemoryMetrics(histograms, model) {
+ tr.metrics.v8.executionMetric(histograms, model);
+ tr.metrics.v8.gcMetric(histograms, model);
+ tr.metrics.sh.memoryMetric(histograms, model,
+ {rangeOfInterest: tr.metrics.v8.utils.rangeForMemoryDumps(model)});
+ }
+
+ tr.metrics.MetricRegistry.register(v8AndMemoryMetrics);
+
+ return {
+ v8AndMemoryMetrics,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/vr/frame_cycle_duration_metric.html b/chromium/third_party/catapult/tracing/tracing/metrics/vr/frame_cycle_duration_metric.html
new file mode 100644
index 00000000000..1a3f4b4d5af
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/vr/frame_cycle_duration_metric.html
@@ -0,0 +1,261 @@
+<!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/metrics/metric_registry.html">
+<link rel="import" href="/tracing/value/histogram.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.metrics.vr', function() {
+ function createHistograms(histograms, name, options, hasCpuTime) {
+ const createdHistograms = {
+ wall: histograms.createHistogram(
+ name + '_wall', tr.b.Unit.byName.timeDurationInMs_smallerIsBetter, [],
+ options)
+ };
+ if (hasCpuTime) {
+ createdHistograms.cpu = histograms.createHistogram(name + '_cpu',
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter, [], options);
+ }
+ return createdHistograms;
+ }
+
+ function frameCycleDurationMetric(histograms, model, opt_options) {
+ const histogramsByEventTitle = new Map();
+ histogramsByEventTitle.set('Vr.DrawFrame',
+ createHistograms(histograms, 'draw_frame',
+ {description: 'Duration to render one frame'}, true));
+ histogramsByEventTitle.set('Vr.AcquireGvrFrame',
+ createHistograms(histograms, 'acquire_frame',
+ {description: 'Duration acquire a frame from GVR'}, true));
+ histogramsByEventTitle.set(
+ 'Vr.ProcessControllerInput',
+ createHistograms(
+ histograms, 'update_controller',
+ {description: 'Duration to query input from the controller'},
+ true));
+ histogramsByEventTitle.set(
+ 'Vr.ProcessControllerInputForWebXr',
+ createHistograms(
+ histograms, 'update_controller_webxr',
+ {description: 'Duration to query input from the controller ' +
+ 'for WebXR'},
+ true));
+ histogramsByEventTitle.set('Vr.SubmitFrameNow',
+ createHistograms(histograms, 'submit_frame',
+ {description: 'Duration to submit a frame to GVR'}, true));
+ histogramsByEventTitle.set('Vr.PostSubmitDrawOnGpu',
+ createHistograms(histograms, 'post_submit_draw_on_gpu',
+ {description: 'Duration to draw a frame on GPU post submit to ' +
+ 'GVR. Note this duration may include time spent on ' +
+ 'reprojection'}, false));
+
+ // TODO(crbug/884259): Remove the following aliases after some time.
+ histogramsByEventTitle.set('VrShellGl::DrawFrame',
+ histogramsByEventTitle.get('Vr.DrawFrame'));
+ histogramsByEventTitle.set('VrShellGl::AcquireFrame',
+ histogramsByEventTitle.get('Vr.AcquireGvrFrame'));
+ histogramsByEventTitle.set('VrShellGl::UpdateController',
+ histogramsByEventTitle.get('Vr.ProcessControllerInput'));
+ histogramsByEventTitle.set('VrShellGl::DrawFrameSubmitNow',
+ histogramsByEventTitle.get('Vr.SubmitFrameNow'));
+ histogramsByEventTitle.set('VrShellGl::PostSubmitDrawOnGpu',
+ histogramsByEventTitle.get('Vr.PostSubmitDrawOnGpu'));
+
+ // Scene update metrics.
+ histogramsByEventTitle.set(
+ 'UiScene::OnBeginFrame.UpdateAnimationsAndOpacity',
+ createHistograms(
+ histograms, 'update_animations_and_opacity',
+ {description: 'Duration to apply animation and opacity changes'},
+ true));
+ histogramsByEventTitle.set(
+ 'UiScene::OnBeginFrame.UpdateBindings',
+ createHistograms(
+ histograms, 'update_bindings',
+ {description: 'Duration to push binding values'}, true));
+ histogramsByEventTitle.set(
+ 'UiScene::OnBeginFrame.UpdateLayout',
+ createHistograms(histograms, 'update_layout', {
+ description: 'Duration to compute element sizes, layout and textures'
+ }, true));
+ histogramsByEventTitle.set(
+ 'UiScene::OnBeginFrame.UpdateWorldSpaceTransform',
+ createHistograms(histograms, 'update_world_space_transforms', {
+ description: 'Duration to calculate element transforms in world space'
+ }, true));
+ // Draw metrics.
+ histogramsByEventTitle.set(
+ 'UiRenderer::DrawUiView',
+ createHistograms(histograms, 'draw_ui', {
+ description: 'Duration to draw the UI'
+ }, true));
+ histogramsByEventTitle.set(
+ 'UiElementRenderer::DrawTexturedQuad',
+ createHistograms(histograms, 'draw_textured_quad', {
+ description: 'Duration to draw a textured element'
+ }, true));
+ histogramsByEventTitle.set(
+ 'UiElementRenderer::DrawGradientQuad',
+ createHistograms(histograms, 'draw_gradient_quad', {
+ description: 'Duration to draw a gradient element'
+ }, true));
+ histogramsByEventTitle.set(
+ 'UiElementRenderer::DrawGradientGridQuad',
+ createHistograms(histograms, 'draw_gradient_grid_quad', {
+ description: 'Duration to draw a gradient grid element'
+ }, true));
+ histogramsByEventTitle.set(
+ 'UiElementRenderer::DrawController',
+ createHistograms(histograms, 'draw_controller', {
+ description: 'Duration to draw the controller'
+ }, true));
+ histogramsByEventTitle.set(
+ 'UiElementRenderer::DrawLaser',
+ createHistograms(histograms, 'draw_laser', {
+ description: 'Duration to draw the laser'
+ }, true));
+ histogramsByEventTitle.set(
+ 'UiElementRenderer::DrawReticle',
+ createHistograms(histograms, 'draw_reticle', {
+ description: 'Duration to draw the reticle'
+ }, true));
+ histogramsByEventTitle.set(
+ 'UiElementRenderer::DrawShadow',
+ createHistograms(histograms, 'draw_shadow', {
+ description: 'Duration to draw a shadow element'
+ }, true));
+ histogramsByEventTitle.set(
+ 'UiElementRenderer::DrawStars',
+ createHistograms(histograms, 'draw_stars', {
+ description: 'Duration to draw the stars'
+ }, true));
+ histogramsByEventTitle.set(
+ 'UiElementRenderer::DrawBackground',
+ createHistograms(histograms, 'draw_background', {
+ description: 'Duration to draw the textured background'
+ }, true));
+ histogramsByEventTitle.set(
+ 'UiElementRenderer::DrawKeyboard',
+ createHistograms(histograms, 'draw_keyboard', {
+ description: 'Duration to draw the keyboard'
+ }, true));
+
+ // Maps from the GUID of a UiRenderer::DrawUiView slice to each of its
+ // children slices.
+ const drawUiSubSlicesMap = new Map();
+
+ const chromeHelper = model.getOrCreateHelper(
+ tr.model.helpers.ChromeModelHelper);
+
+ let rangeOfInterest = model.bounds;
+ const userExpectationsOfInterest = [tr.model.um.AnimationExpectation];
+
+ if (opt_options && opt_options.rangeOfInterest) {
+ rangeOfInterest = opt_options.rangeOfInterest;
+ userExpectationsOfInterest.push(tr.model.um.ResponseExpectation);
+ }
+
+ for (const ue of model.userModel.expectations) {
+ // Skip user expecations not of the right type or not inside the range of
+ // interest.
+ if (ue.initiatorType !== tr.model.um.INITIATOR_TYPE.VR) {
+ continue;
+ }
+ if (!userExpectationsOfInterest.some(function(ueOfInterest) {
+ return ue instanceof ueOfInterest;
+ })) {
+ continue;
+ }
+ if (!rangeOfInterest.intersectsExplicitRangeInclusive(
+ ue.start, ue.end)) {
+ continue;
+ }
+
+ for (const helper of chromeHelper.browserHelpers) {
+ // The events are traced on the GL thread in the browser process.
+ // Unfortunately, this thread has no name.
+ // TODO(tiborg): Give GL thread a name and reference the thread by
+ // the given name.
+ const glThreads = helper.process.findAllThreadsMatching(
+ thread => !thread.name);
+
+ for (const glThread of glThreads) {
+ for (const event of glThread.getDescendantEvents()) {
+ // Skip events that are neither in the user expecation, range of
+ // interest nor part of the frame cycle durations.
+ if (!(histogramsByEventTitle.has(event.title))) {
+ continue;
+ }
+ if (event.start < ue.start || event.end > ue.end) {
+ continue;
+ }
+ if (event.start < rangeOfInterest.min ||
+ event.end > rangeOfInterest.max) {
+ continue;
+ }
+
+ // There can be multiple UiElementRenderer::Draw... events per frame
+ // but this metric should calculate the across frame average of
+ // durations. Thus, collect UiElementRenderer::Draw... slices here
+ // in order to process them below.
+ if (event.parentSlice &&
+ event.parentSlice.title === 'UiRenderer::DrawUiView') {
+ const guid = event.parentSlice.guid;
+ if (!drawUiSubSlicesMap.has(guid)) {
+ drawUiSubSlicesMap.set(guid, []);
+ }
+ drawUiSubSlicesMap.get(guid).push(event);
+ continue;
+ }
+
+ const {wall: wallHist, cpu: cpuHist} =
+ histogramsByEventTitle.get(event.title);
+ wallHist.addSample(event.duration);
+ if (cpuHist !== undefined) {
+ cpuHist.addSample(event.cpuDuration);
+ }
+ }
+ }
+ }
+ }
+
+ // Calculate the average of the per frame sums of UiElementRenderer::Draw...
+ // events.
+ for (const subSlices of drawUiSubSlicesMap.values()) {
+ const eventMap = new Map();
+ for (const event of subSlices) {
+ if (!eventMap.has(event.title)) {
+ eventMap.set(event.title, {
+ wall: 0,
+ cpu: 0
+ });
+ }
+ eventMap.get(event.title).wall += event.duration;
+ eventMap.get(event.title).cpu += event.cpuDuration;
+ }
+ for (const [title, values] of eventMap.entries()) {
+ const {wall: wallHist, cpu: cpuHist} =
+ histogramsByEventTitle.get(title);
+ wallHist.addSample(values.wall);
+ if (cpuHist !== undefined) {
+ cpuHist.addSample(values.cpu);
+ }
+ }
+ }
+ }
+
+ tr.metrics.MetricRegistry.register(frameCycleDurationMetric, {
+ supportsRangeOfInterest: true,
+ });
+
+ return {
+ frameCycleDurationMetric,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/vr/frame_cycle_duration_metric_test.html b/chromium/third_party/catapult/tracing/tracing/metrics/vr/frame_cycle_duration_metric_test.html
new file mode 100644
index 00000000000..e6500713167
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/vr/frame_cycle_duration_metric_test.html
@@ -0,0 +1,333 @@
+<!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/metrics/vr/frame_cycle_duration_metric.html">
+<link rel="import" href="/tracing/model/model.html">
+<link rel="import" href="/tracing/value/histogram_set.html">
+
+<script>
+'use strict';
+tr.b.unittest.testSuite(function() {
+ const TOLERANCE = 1e-6;
+
+ function createSubSlices(durations, currentTime, sliceGroup) {
+ if (durations === undefined) {
+ return [];
+ }
+ const slices = [];
+ for (const duration of durations) {
+ const option = {
+ cat: 'gpu',
+ title: duration.title,
+ start: currentTime,
+ end: currentTime + duration.wall,
+ };
+ if (duration.cpu !== undefined) {
+ option.cpuStart = currentTime;
+ option.cpuEnd = currentTime + duration.cpu;
+ }
+ const slice = tr.c.TestUtils.newSliceEx(option);
+ slice.subSlices = createSubSlices(
+ duration.sub, currentTime + 0.1, sliceGroup);
+ sliceGroup.pushSlice(slice);
+ slices.push(slice);
+ const maxDuration = duration.cpu === undefined ?
+ duration.wall : Math.max(duration.wall, duration.cpu);
+ currentTime += maxDuration + 1;
+ }
+ return slices;
+ }
+
+ test('frameCycleDurationMetric', function() {
+ const durations = [
+ {
+ title: 'Vr.DrawFrame',
+ wall: 390,
+ cpu: 340,
+ sub: [
+ {title: 'Vr.AcquireGvrFrame', wall: 2, cpu: 2},
+ {title: 'Vr.AcquireGvrFrame', wall: 2.5, cpu: 1.5},
+ {
+ title: 'UiScene::OnBeginFrame.UpdateAnimationsAndOpacity',
+ wall: 3,
+ cpu: 2
+ },
+ {
+ title: 'UiScene::OnBeginFrame.UpdateAnimationsAndOpacity',
+ wall: 2,
+ cpu: 1
+ },
+ {title: 'UiScene::OnBeginFrame.UpdateBindings', wall: 3, cpu: 2},
+ {title: 'UiScene::OnBeginFrame.UpdateBindings', wall: 2, cpu: 1},
+ {title: 'UiScene::OnBeginFrame.UpdateLayout', wall: 3, cpu: 2},
+ {title: 'UiScene::OnBeginFrame.UpdateLayout', wall: 2, cpu: 1},
+ {
+ title: 'UiScene::OnBeginFrame.UpdateWorldSpaceTransform',
+ wall: 3,
+ cpu: 2
+ },
+ {
+ title: 'UiScene::OnBeginFrame.UpdateWorldSpaceTransform',
+ wall: 2,
+ cpu: 1
+ },
+ {title: 'Vr.ProcessControllerInput', wall: 1, cpu: 0.5},
+ {title: 'Vr.ProcessControllerInput', wall: 0.5, cpu: 0.4},
+ {title: 'Vr.ProcessControllerInputForWebXr', wall: 1, cpu: 0.5},
+ {title: 'Vr.ProcessControllerInputForWebXr', wall: 1.5, cpu: 0.6},
+ {
+ title: 'UiRenderer::DrawUiView',
+ wall: 250,
+ cpu: 230,
+ sub: [
+ {title: 'UiElementRenderer::DrawTexturedQuad', wall: 1, cpu: 0.5},
+ {title: 'UiElementRenderer::DrawGradientQuad', wall: 3, cpu: 2},
+ {title: 'UiElementRenderer::DrawGradientQuad', wall: 4, cpu: 3},
+ {title: 'UiElementRenderer::DrawTexturedQuad', wall: 2, cpu: 1},
+ {
+ title: 'UiElementRenderer::DrawGradientGridQuad',
+ wall: 5,
+ cpu: 4
+ },
+ {
+ title: 'UiElementRenderer::DrawGradientGridQuad',
+ wall: 6,
+ cpu: 5
+ },
+ {title: 'UiElementRenderer::DrawController', wall: 7, cpu: 6},
+ {title: 'UiElementRenderer::DrawController', wall: 8, cpu: 7},
+ {title: 'UiElementRenderer::DrawLaser', wall: 9, cpu: 8},
+ {title: 'UiElementRenderer::DrawLaser', wall: 10, cpu: 9},
+ {title: 'UiElementRenderer::DrawReticle', wall: 11, cpu: 10},
+ {title: 'UiElementRenderer::DrawReticle', wall: 12, cpu: 11},
+ {title: 'UiElementRenderer::DrawShadow', wall: 13, cpu: 12},
+ {title: 'UiElementRenderer::DrawShadow', wall: 14, cpu: 13},
+ {title: 'UiElementRenderer::DrawStars', wall: 15, cpu: 14},
+ {title: 'UiElementRenderer::DrawStars', wall: 16, cpu: 15},
+ {title: 'UiElementRenderer::DrawBackground', wall: 17, cpu: 16},
+ {title: 'UiElementRenderer::DrawBackground', wall: 18, cpu: 17},
+ {title: 'UiElementRenderer::DrawKeyboard', wall: 19, cpu: 18},
+ {title: 'UiElementRenderer::DrawKeyboard', wall: 20, cpu: 19},
+ ]
+ },
+ {
+ title: 'UiRenderer::DrawUiView',
+ wall: 80,
+ cpu: 75,
+ sub: [
+ {title: 'UiElementRenderer::DrawTexturedQuad', wall: 2, cpu: 1},
+ {title: 'UiElementRenderer::DrawGradientQuad', wall: 3, cpu: 2},
+ {
+ title: 'UiElementRenderer::DrawGradientGridQuad',
+ wall: 4,
+ cpu: 3
+ },
+ {title: 'UiElementRenderer::DrawController', wall: 5, cpu: 4},
+ {title: 'UiElementRenderer::DrawLaser', wall: 6, cpu: 5},
+ {title: 'UiElementRenderer::DrawReticle', wall: 7, cpu: 6},
+ {title: 'UiElementRenderer::DrawShadow', wall: 8, cpu: 7},
+ {title: 'UiElementRenderer::DrawStars', wall: 9, cpu: 8},
+ {title: 'UiElementRenderer::DrawBackground', wall: 10, cpu: 9},
+ {title: 'UiElementRenderer::DrawKeyboard', wall: 11, cpu: 10},
+ ]
+ },
+ {title: 'Vr.SubmitFrameNow', wall: 3, cpu: 0.5},
+ {title: 'Vr.SubmitFrameNow', wall: 3.5, cpu: 0.5},
+ {title: 'Vr.PostSubmitDrawOnGpu', wall: 3},
+ {title: 'Vr.PostSubmitDrawOnGpu', wall: 4},
+ ]
+ },
+ {title: 'Vr.DrawFrame', wall: 20, cpu: 10},
+ // This event should be filtered out by the user expecation.
+ {title: 'Vr.DrawFrame', wall: 20, cpu: 10},
+ ];
+ const histograms = new tr.v.HistogramSet();
+ const model = tr.c.TestUtils.newModel(function(model) {
+ model.userModel.expectations.push(
+ new tr.model.um.AnimationExpectation(model,
+ tr.model.um.INITIATOR_TYPE.VR, 0, 411));
+ const browserProcess = model.getOrCreateProcess(0);
+ const browserMainThread = browserProcess.getOrCreateThread(0);
+ browserMainThread.name = 'CrBrowserMain';
+ const browserGlThread = browserProcess.getOrCreateThread(1);
+ const group = browserGlThread.sliceGroup;
+ createSubSlices(durations, 0, group);
+ group.createSubSlices();
+ });
+
+ tr.metrics.vr.frameCycleDurationMetric(histograms, model);
+
+ assert.closeTo(histograms.getHistogramNamed('draw_frame_wall').average,
+ 205, TOLERANCE);
+ assert.closeTo(histograms.getHistogramNamed('draw_frame_cpu').average,
+ 175, TOLERANCE);
+
+ assert.closeTo(
+ histograms.getHistogramNamed('acquire_frame_wall').average,
+ 2.25, TOLERANCE);
+ assert.closeTo(histograms.getHistogramNamed('acquire_frame_cpu').average,
+ 1.75, TOLERANCE);
+
+ assert.isUndefined(
+ histograms.getHistogramNamed('post_submit_draw_on_gpu_cpu'));
+ assert.closeTo(
+ histograms.getHistogramNamed('post_submit_draw_on_gpu_wall').average,
+ 3.5, TOLERANCE);
+
+ assert.closeTo(
+ histograms.getHistogramNamed('update_controller_wall').average,
+ 0.75, TOLERANCE);
+ assert.closeTo(
+ histograms.getHistogramNamed('update_controller_cpu').average,
+ 0.45, TOLERANCE);
+
+ assert.closeTo(
+ histograms.getHistogramNamed('update_controller_webxr_wall').average,
+ 1.25, TOLERANCE);
+ assert.closeTo(
+ histograms.getHistogramNamed('update_controller_webxr_cpu').average,
+ 0.55, TOLERANCE);
+
+ assert.closeTo(histograms.getHistogramNamed('submit_frame_wall').average,
+ 3.25, TOLERANCE);
+ assert.closeTo(histograms.getHistogramNamed('submit_frame_cpu').average,
+ 0.5, TOLERANCE);
+
+ assert.closeTo(
+ histograms.getHistogramNamed('update_animations_and_opacity_wall')
+ .average,
+ 2.5, TOLERANCE);
+ assert.closeTo(
+ histograms.getHistogramNamed('update_animations_and_opacity_cpu')
+ .average,
+ 1.5, TOLERANCE);
+
+ assert.closeTo(
+ histograms.getHistogramNamed('update_bindings_wall').average, 2.5,
+ TOLERANCE);
+ assert.closeTo(
+ histograms.getHistogramNamed('update_bindings_cpu').average, 1.5,
+ TOLERANCE);
+
+ assert.closeTo(
+ histograms.getHistogramNamed('update_layout_wall').average, 2.5,
+ TOLERANCE);
+ assert.closeTo(
+ histograms.getHistogramNamed('update_layout_cpu').average, 1.5,
+ TOLERANCE);
+
+ assert.closeTo(
+ histograms.getHistogramNamed('update_world_space_transforms_wall')
+ .average,
+ 2.5, TOLERANCE);
+ assert.closeTo(
+ histograms.getHistogramNamed('update_world_space_transforms_cpu')
+ .average,
+ 1.5, TOLERANCE);
+
+ assert.closeTo(
+ histograms.getHistogramNamed('draw_ui_wall')
+ .average,
+ 165, TOLERANCE);
+ assert.closeTo(
+ histograms.getHistogramNamed('draw_ui_cpu')
+ .average,
+ 152.5, TOLERANCE);
+
+ assert.closeTo(
+ histograms.getHistogramNamed('draw_textured_quad_wall')
+ .average,
+ 2.5, TOLERANCE);
+ assert.closeTo(
+ histograms.getHistogramNamed('draw_textured_quad_cpu')
+ .average,
+ 1.25, TOLERANCE);
+
+ assert.closeTo(
+ histograms.getHistogramNamed('draw_gradient_quad_wall')
+ .average,
+ 5, TOLERANCE);
+ assert.closeTo(
+ histograms.getHistogramNamed('draw_gradient_quad_cpu')
+ .average,
+ 3.5, TOLERANCE);
+
+ assert.closeTo(
+ histograms.getHistogramNamed('draw_gradient_grid_quad_wall')
+ .average,
+ 7.5, TOLERANCE);
+ assert.closeTo(
+ histograms.getHistogramNamed('draw_gradient_grid_quad_cpu')
+ .average,
+ 6, TOLERANCE);
+
+ assert.closeTo(
+ histograms.getHistogramNamed('draw_controller_wall')
+ .average,
+ 10, TOLERANCE);
+ assert.closeTo(
+ histograms.getHistogramNamed('draw_controller_cpu')
+ .average,
+ 8.5, TOLERANCE);
+
+ assert.closeTo(
+ histograms.getHistogramNamed('draw_laser_wall')
+ .average,
+ 12.5, TOLERANCE);
+ assert.closeTo(
+ histograms.getHistogramNamed('draw_laser_cpu')
+ .average,
+ 11, TOLERANCE);
+
+ assert.closeTo(
+ histograms.getHistogramNamed('draw_reticle_wall')
+ .average,
+ 15, TOLERANCE);
+ assert.closeTo(
+ histograms.getHistogramNamed('draw_reticle_cpu')
+ .average,
+ 13.5, TOLERANCE);
+
+ assert.closeTo(
+ histograms.getHistogramNamed('draw_shadow_wall')
+ .average,
+ 17.5, TOLERANCE);
+ assert.closeTo(
+ histograms.getHistogramNamed('draw_shadow_cpu')
+ .average,
+ 16.0, TOLERANCE);
+
+ assert.closeTo(
+ histograms.getHistogramNamed('draw_stars_wall')
+ .average,
+ 20, TOLERANCE);
+ assert.closeTo(
+ histograms.getHistogramNamed('draw_stars_cpu')
+ .average,
+ 18.5, TOLERANCE);
+
+ assert.closeTo(
+ histograms.getHistogramNamed('draw_background_wall')
+ .average,
+ 22.5, TOLERANCE);
+ assert.closeTo(
+ histograms.getHistogramNamed('draw_background_cpu')
+ .average,
+ 21, TOLERANCE);
+
+ assert.closeTo(
+ histograms.getHistogramNamed('draw_keyboard_wall')
+ .average,
+ 25, TOLERANCE);
+ assert.closeTo(
+ histograms.getHistogramNamed('draw_keyboard_cpu')
+ .average,
+ 23.5, TOLERANCE);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/vr/webvr_metric.html b/chromium/third_party/catapult/tracing/tracing/metrics/vr/webvr_metric.html
new file mode 100644
index 00000000000..254267053ea
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/vr/webvr_metric.html
@@ -0,0 +1,117 @@
+<!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/metrics/metric_registry.html">
+<link rel="import" href="/tracing/value/histogram.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.metrics.vr', function() {
+ function webvrMetric(histograms, model, opt_options) {
+ // Maps VR trace counters to histogram.
+ const WEBVR_COUNTERS = new Map([
+ ['gpu.WebVR FPS', {
+ name: 'webvr_fps',
+ unit: tr.b.Unit.byName.count_biggerIsBetter,
+ samples: {},
+ options: {
+ description: 'WebVR frame per second',
+ binBoundaries: tr.v.HistogramBinBoundaries.createLinear(20, 120, 25),
+ },
+ }],
+ ['gpu.WebVR frame time (ms)', {
+ name: 'webvr_frame_time',
+ unit: tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,
+ samples: {},
+ options: {
+ description: 'WebVR frame time in ms',
+ binBoundaries: tr.v.HistogramBinBoundaries.createLinear(20, 120, 25),
+ },
+ }],
+ ['gpu.WebVR pose prediction (ms)', {
+ name: 'webvr_pose_prediction',
+ unit: tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,
+ samples: {},
+ options: {
+ description: 'WebVR pose prediction in ms',
+ binBoundaries: tr.v.HistogramBinBoundaries.createLinear(20, 120, 25),
+ },
+ }],
+ ]);
+
+ for (const ue of model.userModel.expectations) {
+ const rangeOfInterestEnabled = opt_options && opt_options.rangeOfInterest;
+ if (rangeOfInterestEnabled &&
+ !opt_options.rangeOfInterest.intersectsExplicitRangeInclusive(
+ ue.start, ue.end)) {
+ continue;
+ }
+
+ // By default, only do calculations in the VR animation expectation, i.e.
+ // some time after we've entered VR, in order to avoid skewed results
+ // caused by VR entry, but allow calculation on the response expectation
+ // if we've manually selected it as a range of interest
+ if (ue.initiatorType !== tr.model.um.INITIATOR_TYPE.VR) continue;
+ if (!rangeOfInterestEnabled) {
+ if (!(ue instanceof tr.model.um.AnimationExpectation)) continue;
+ } else {
+ if (!(ue instanceof tr.model.um.AnimationExpectation ||
+ ue instanceof tr.model.um.ResponseExpectation)) continue;
+ }
+
+ for (const counter of model.getAllCounters()) {
+ if (!(WEBVR_COUNTERS.has(counter.id))) continue;
+
+ for (const series of counter.series) {
+ if (!(series.name in WEBVR_COUNTERS.get(counter.id).samples)) {
+ WEBVR_COUNTERS.get(counter.id).samples[series.name] = [];
+ }
+ for (const sample of series.samples) {
+ if (sample.timestamp < ue.start || sample.timestamp >= ue.end) {
+ continue;
+ }
+ if (rangeOfInterestEnabled &&
+ !opt_options.rangeOfInterest.intersectsExplicitRangeInclusive(
+ sample.timestamp, sample.timestamp)) {
+ continue;
+ }
+
+ WEBVR_COUNTERS.get(counter.id).samples[series.name].push(
+ sample.value);
+ }
+ }
+ }
+ }
+
+ // Make sure we always report a value for WebVR FPS so that failing to
+ // submit frames will show up as a regression
+ if (!('value' in WEBVR_COUNTERS.get('gpu.WebVR FPS').samples)) {
+ WEBVR_COUNTERS.get('gpu.WebVR FPS').samples.value = [0];
+ }
+
+ for (const [key, value] of WEBVR_COUNTERS) {
+ for (const [seriesName, samples] of Object.entries(value.samples)) {
+ let histogramName = value.name;
+ if (seriesName !== 'value') {
+ histogramName = `${histogramName}_${seriesName}`;
+ }
+ histograms.createHistogram(histogramName, value.unit,
+ samples, value.options);
+ }
+ }
+ }
+
+ tr.metrics.MetricRegistry.register(webvrMetric, {
+ supportsRangeOfInterest: true,
+ });
+
+ return {
+ webvrMetric,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/vr/webvr_metric_test.html b/chromium/third_party/catapult/tracing/tracing/metrics/vr/webvr_metric_test.html
new file mode 100644
index 00000000000..75d73aeeb8a
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/vr/webvr_metric_test.html
@@ -0,0 +1,86 @@
+<!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/metrics/vr/webvr_metric.html">
+<link rel="import" href="/tracing/model/counter.html">
+<link rel="import" href="/tracing/model/model.html">
+<link rel="import" href="/tracing/value/histogram_set.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ function createModel() {
+ const model = tr.c.TestUtils.newModel(
+ function(model) {
+ model.userModel.expectations.push(
+ new tr.model.um.AnimationExpectation(model,
+ tr.model.um.INITIATOR_TYPE.VR, 0, 5));
+ const process = model.getOrCreateProcess(1);
+ const fpsCounter = process.getOrCreateCounter('gpu', 'WebVR FPS');
+ const fpsSeries = new tr.model.CounterSeries('value', 0);
+ fpsSeries.addCounterSample(1, 59);
+ fpsSeries.addCounterSample(3, 60);
+ fpsCounter.addSeries(fpsSeries);
+
+ const frameTimeCounter = process.getOrCreateCounter(
+ 'gpu', 'WebVR frame time (ms)');
+ const renderingSeries = new tr.model.CounterSeries('rendering', 0);
+ renderingSeries.addCounterSample(1, 3);
+ renderingSeries.addCounterSample(2, 4);
+ frameTimeCounter.addSeries(renderingSeries);
+ const javascriptSeries = new tr.model.CounterSeries('javascript', 0);
+ javascriptSeries.addCounterSample(1, 5);
+ javascriptSeries.addCounterSample(2, 6);
+ frameTimeCounter.addSeries(javascriptSeries);
+ });
+ return model;
+ }
+
+ test('webvrMetric', function() {
+ const histograms = new tr.v.HistogramSet();
+ const model = createModel();
+ tr.metrics.vr.webvrMetric(histograms, model);
+
+ const fpsValue = histograms.getHistogramNamed('webvr_fps');
+ assert.strictEqual(fpsValue.max, 60);
+ assert.strictEqual(fpsValue.min, 59);
+ assert.strictEqual(fpsValue.average, 59.5);
+
+ const renderingValue = histograms.getHistogramNamed(
+ 'webvr_frame_time_rendering');
+ assert.strictEqual(renderingValue.max, 4);
+ assert.strictEqual(renderingValue.min, 3);
+ assert.strictEqual(renderingValue.average, 3.5);
+
+ const javascriptValue = histograms.getHistogramNamed(
+ 'webvr_frame_time_javascript');
+ assert.strictEqual(javascriptValue.max, 6);
+ assert.strictEqual(javascriptValue.min, 5);
+ assert.strictEqual(javascriptValue.average, 5.5);
+ });
+
+ test('webvrMetric_alwaysReportFps', function() {
+ const histograms = new tr.v.HistogramSet();
+ const model = tr.c.TestUtils.newModel();
+ tr.metrics.vr.webvrMetric(histograms, model);
+
+ const fpsValue = histograms.getHistogramNamed('webvr_fps');
+ assert.strictEqual(fpsValue.max, 0);
+ assert.strictEqual(fpsValue.min, 0);
+
+ const renderingValue = histograms.getHistogramNamed(
+ 'webvr_frame_time_rendering');
+ assert.strictEqual(renderingValue, undefined);
+
+ const javascriptValue = histograms.getHistogramNamed(
+ 'webvr_frame_time_javascript');
+ assert.strictEqual(javascriptValue, undefined);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/webrtc/webrtc_rendering_metric.html b/chromium/third_party/catapult/tracing/tracing/metrics/webrtc/webrtc_rendering_metric.html
new file mode 100644
index 00000000000..a8414ec46d4
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/webrtc/webrtc_rendering_metric.html
@@ -0,0 +1,364 @@
+<!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.html">
+<link rel="import" href="/tracing/base/utils.html">
+<link rel="import" href="/tracing/metrics/metric_registry.html">
+<link rel="import" href="/tracing/metrics/v8/utils.html">
+<link rel="import" href="/tracing/model/helpers/chrome_model_helper.html">
+<link rel="import" href="/tracing/value/histogram.html">
+
+
+<script>
+'use strict';
+
+tr.exportTo('tr.metrics.webrtc', function() {
+ const DISPLAY_HERTZ = 60.0;
+ const VSYNC_DURATION_US = 1e6 / DISPLAY_HERTZ;
+ // How much more severe is a 'Badly out of sync' render event compared to an
+ // 'Out of sync' one when calculating the smoothness score.
+ const SEVERITY = 3;
+ // How many vsyncs a frame should be displayed to be considered frozen.
+ const FROZEN_FRAME_VSYNC_COUNT_THRESHOLD = 6;
+
+ const WEB_MEDIA_PLAYER_UPDATE_TITLE = 'UpdateCurrentFrame';
+ // These four are args for WebMediaPlayerMS update events.
+ const IDEAL_RENDER_INSTANT_NAME = 'Ideal Render Instant';
+ const ACTUAL_RENDER_BEGIN_NAME = 'Actual Render Begin';
+ const ACTUAL_RENDER_END_NAME = 'Actual Render End';
+ // The events of interest have a 'Serial' argument which represents the
+ // stream ID.
+ const STREAM_ID_NAME = 'Serial';
+
+ const REQUIRED_EVENT_ARGS_NAMES = [
+ IDEAL_RENDER_INSTANT_NAME, ACTUAL_RENDER_BEGIN_NAME, ACTUAL_RENDER_END_NAME,
+ STREAM_ID_NAME
+ ];
+
+ // By default, we store a single value, so we only need one of the
+ // statistics to keep track. We choose the average for that.
+ const SUMMARY_OPTIONS = tr.v.Histogram.AVERAGE_ONLY_SUMMARY_OPTIONS;
+
+ const count_smallerIsBetter =
+ tr.b.Unit.byName.count_smallerIsBetter;
+ const percentage_biggerIsBetter =
+ tr.b.Unit.byName.normalizedPercentage_biggerIsBetter;
+ const percentage_smallerIsBetter =
+ tr.b.Unit.byName.normalizedPercentage_smallerIsBetter;
+ const timeDurationInMs_smallerIsBetter =
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter;
+ const unitlessNumber_biggerIsBetter =
+ tr.b.Unit.byName.unitlessNumber_biggerIsBetter;
+
+ /*
+ * Verify that the event is a valid event.
+ *
+ * An event is valid if it is a UpdateCurrentFrame event,
+ * and has all of the mandatory arguments. See MANDATORY above.
+ */
+ function isValidEvent(event) {
+ if (event.title !== WEB_MEDIA_PLAYER_UPDATE_TITLE || !event.args) {
+ return false;
+ }
+ for (const parameter of REQUIRED_EVENT_ARGS_NAMES) {
+ if (!(parameter in event.args)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ function webrtcRenderingMetric(histograms, model) {
+ const modelHelper = model.getOrCreateHelper(
+ tr.model.helpers.ChromeModelHelper);
+ let webMediaPlayerMSEvents = [];
+ for (const rendererPid in modelHelper.rendererHelpers) {
+ const rendererHelper = modelHelper.rendererHelpers[rendererPid];
+ const compositorThread = rendererHelper.compositorThread;
+ if (compositorThread !== undefined) {
+ webMediaPlayerMSEvents = webMediaPlayerMSEvents.concat(
+ compositorThread.sliceGroup.slices.filter(isValidEvent));
+ }
+ }
+ const eventsByStreamName = tr.b.groupIntoMap(
+ webMediaPlayerMSEvents,
+ event => event.args[STREAM_ID_NAME]
+ );
+ for (const [streamName, events] of eventsByStreamName) {
+ getTimeStats(histograms, streamName, events);
+ }
+ }
+
+ tr.metrics.MetricRegistry.register(webrtcRenderingMetric);
+
+ function getTimeStats(histograms, streamName, events) {
+ const frameHist = getFrameDistribution(histograms, events);
+ addFpsFromFrameDistribution(histograms, frameHist);
+ addFreezingScore(histograms, frameHist);
+
+ const driftTimeStats = getDriftStats(events);
+ histograms.createHistogram('WebRTCRendering_drift_time',
+ timeDurationInMs_smallerIsBetter, driftTimeStats.driftTime, {
+ summaryOptions: {
+ count: false,
+ min: false,
+ percentile: [0.75, 0.9],
+ },
+ });
+ histograms.createHistogram('WebRTCRendering_rendering_length_error',
+ percentage_smallerIsBetter,
+ driftTimeStats.renderingLengthError, {
+ summaryOptions: SUMMARY_OPTIONS,
+ });
+
+ const smoothnessStats = getSmoothnessStats(driftTimeStats.driftTime);
+ histograms.createHistogram('WebRTCRendering_percent_badly_out_of_sync',
+ percentage_smallerIsBetter, smoothnessStats.percentBadlyOutOfSync, {
+ summaryOptions: SUMMARY_OPTIONS,
+ });
+ histograms.createHistogram('WebRTCRendering_percent_out_of_sync',
+ percentage_smallerIsBetter, smoothnessStats.percentOutOfSync, {
+ summaryOptions: SUMMARY_OPTIONS,
+ });
+ histograms.createHistogram('WebRTCRendering_smoothness_score',
+ percentage_biggerIsBetter, smoothnessStats.smoothnessScore, {
+ summaryOptions: SUMMARY_OPTIONS,
+ });
+ histograms.createHistogram('WebRTCRendering_frames_out_of_sync',
+ count_smallerIsBetter, smoothnessStats.framesOutOfSync, {
+ summaryOptions: SUMMARY_OPTIONS,
+ });
+ histograms.createHistogram('WebRTCRendering_frames_badly_out_of_sync',
+ count_smallerIsBetter, smoothnessStats.framesSeverelyOutOfSync, {
+ summaryOptions: SUMMARY_OPTIONS,
+ });
+ }
+
+ const FRAME_DISTRIBUTION_BIN_BOUNDARIES =
+ tr.v.HistogramBinBoundaries.createLinear(1, 50, 49);
+
+ /**
+ * Create the frame distribution.
+ *
+ * If the overall display distribution is A1:A2:..:An, this will tell how
+ * many times a frame stays displayed during Ak*VSYNC_DURATION_US, also known
+ * as 'source to output' distribution.
+ *
+ * In other terms, a distribution B where
+ * B[k] = number of frames that are displayed k times.
+ *
+ * @param {tr.v.HistogramSet} histograms
+ * @param {Array.<event>} events - An array of events.
+ * @returns {tr.v.Histogram} frameHist - The frame distribution.
+ */
+ function getFrameDistribution(histograms, events) {
+ const cadence = tr.b.runLengthEncoding(
+ events.map(e => e.args[IDEAL_RENDER_INSTANT_NAME]));
+ return histograms.createHistogram('WebRTCRendering_frame_distribution',
+ count_smallerIsBetter, cadence.map(ticks => ticks.count), {
+ binBoundaries: FRAME_DISTRIBUTION_BIN_BOUNDARIES,
+ summaryOptions: {
+ percentile: [0.75, 0.9],
+ },
+ });
+ }
+
+ /**
+ * Calculate the apparent FPS from frame distribution.
+ *
+ * Knowing the display frequency and the frame distribution, it is possible to
+ * calculate the video apparent frame rate as played by WebMediaPlayerMs
+ * module.
+ *
+ * @param {tr.v.HistogramSet} histograms
+ * @param {tr.v.Histogram} frameHist - The frame distribution. See
+ * getFrameDistribution.
+ */
+ function addFpsFromFrameDistribution(histograms, frameHist) {
+ let numberFrames = 0;
+ let numberVsyncs = 0;
+ for (let ticks = 1; ticks < frameHist.allBins.length; ++ticks) {
+ const count = frameHist.allBins[ticks].count;
+ numberFrames += count;
+ numberVsyncs += ticks * count;
+ }
+ const meanRatio = numberVsyncs / numberFrames;
+ histograms.createHistogram('WebRTCRendering_fps',
+ unitlessNumber_biggerIsBetter, DISPLAY_HERTZ / meanRatio, {
+ summaryOptions: SUMMARY_OPTIONS,
+ });
+ }
+
+ /**
+ * Returns the weighted penalty for a number of frozen frames.
+ *
+ * In a series of repeated frames of length > 5, all frames after the first
+ * are considered frozen. Conversely, no frames in a series of repeated frames
+ * of length <= 5 will be considered frozen.
+ *
+ * This means the weight for 0 to 4 frozen frames is 0.
+ *
+ * @param {Number} numberFrozenFrames - The number of frozen frames.
+ * @returns {Number} - The weight penalty for the number of frozen frames.
+ */
+ function frozenPenaltyWeight(numberFrozenFrames) {
+ const penalty = {
+ 5: 1,
+ 6: 5,
+ 7: 15,
+ 8: 25
+ };
+ return penalty[numberFrozenFrames] || (8 * (numberFrozenFrames - 4));
+ }
+
+ /**
+ * Adds the freezing score.
+ *
+ * @param {tr.v.HistogramSet} histograms
+ * @param {tr.v.Histogram} frameHist - The frame distribution.
+ * See getFrameDistribution.
+ */
+ function addFreezingScore(histograms, frameHist) {
+ let numberVsyncs = 0;
+ let freezingScore = 0;
+ let frozenFramesCount = 0;
+ for (let ticks = 1; ticks < frameHist.allBins.length; ++ticks) {
+ const count = frameHist.allBins[ticks].count;
+ numberVsyncs += ticks * count;
+ if (ticks >= FROZEN_FRAME_VSYNC_COUNT_THRESHOLD) {
+ // The first frame of the series is not considered frozen.
+ frozenFramesCount += count * (ticks - 1);
+ freezingScore += count * frozenPenaltyWeight(ticks - 1);
+ }
+ }
+ freezingScore = 1 - freezingScore / numberVsyncs;
+ if (freezingScore < 0) {
+ freezingScore = 0;
+ }
+ histograms.createHistogram('WebRTCRendering_frozen_frames_count',
+ count_smallerIsBetter, frozenFramesCount, {
+ summaryOptions: SUMMARY_OPTIONS,
+ });
+ histograms.createHistogram('WebRTCRendering_freezing_score',
+ percentage_biggerIsBetter, freezingScore, {
+ summaryOptions: SUMMARY_OPTIONS,
+ });
+ }
+
+ /**
+ * Get the drift time statistics.
+ *
+ * This method will calculate:
+ * - Drift Time: The difference between the Actual Render Begin and the Ideal
+ * Render Instant for each event.
+ * - Rendering Length Error: The alignment error of the Ideal Render
+ * Instants. The Ideal Render Instants should be equally spaced by
+ * intervals of length VSYNC_DURATION_US. The Rendering Length error
+ * measures how much they are misaligned.
+ *
+ * @param {Array.<event>} events - An array of events.
+ * @returns {Object.<Array.<Number>, Number>} - The drift time and rendering
+ * length error.
+ */
+ function getDriftStats(events) {
+ const driftTime = [];
+ const discrepancy = [];
+ let oldIdealRender = 0;
+ let expectedIdealRender = 0;
+
+ for (const event of events) {
+ const currentIdealRender = event.args[IDEAL_RENDER_INSTANT_NAME];
+ // The expected time of the next 'Ideal Render' event begins as the
+ // current 'Ideal Render' time and increases by VSYNC_DURATION_US on every
+ // frame.
+ expectedIdealRender += VSYNC_DURATION_US;
+ if (currentIdealRender === oldIdealRender) {
+ continue;
+ }
+ const actualRenderBegin = event.args[ACTUAL_RENDER_BEGIN_NAME];
+ // When was the frame rendered vs. when it would've been ideal.
+ driftTime.push(actualRenderBegin - currentIdealRender);
+ // The discrepancy is the absolute difference between the current Ideal
+ // Render and the expected Ideal Render.
+ discrepancy.push(Math.abs(currentIdealRender - expectedIdealRender));
+ expectedIdealRender = currentIdealRender;
+ oldIdealRender = currentIdealRender;
+ }
+
+ const discrepancySum = tr.b.math.Statistics.sum(discrepancy) -
+ discrepancy[0];
+ const lastIdealRender =
+ events[events.length - 1].args[IDEAL_RENDER_INSTANT_NAME];
+ const firstIdealRender = events[0].args[IDEAL_RENDER_INSTANT_NAME];
+ const idealRenderSpan = lastIdealRender - firstIdealRender;
+
+ const renderingLengthError = discrepancySum / idealRenderSpan;
+
+ return {driftTime, renderingLengthError};
+ }
+
+ /**
+ * Get the smoothness stats from the normalized drift time.
+ *
+ * This method will calculate the smoothness score, along with the percentage
+ * of frames badly out of sync and the percentage of frames out of sync.
+ * To be considered badly out of sync, a frame has to have missed rendering by
+ * at least 2 * VSYNC_DURATION_US.
+ * To be considered out of sync, a frame has to have missed rendering by at
+ * least one VSYNC_DURATION_US.
+ * The smoothness score is a measure of how out of sync the frames are.
+ *
+ * @param {Array.<Number>} driftTimes - See getDriftStats.
+ * @returns {Object.<Number, Number, Number>} - The percentBadlyOutOfSync,
+ * percentOutOfSync and smoothnesScore calculated from the driftTimes array.
+ */
+ function getSmoothnessStats(driftTimes) {
+ const meanDriftTime = tr.b.math.Statistics.mean(driftTimes);
+ const normDriftTimes = driftTimes.map(driftTime =>
+ Math.abs(driftTime - meanDriftTime));
+
+ // How many times is a frame later/earlier than T=2*VSYNC_DURATION_US. Time
+ // is in microseconds
+ const framesSeverelyOutOfSync = normDriftTimes
+ .filter(driftTime => driftTime > 2 * VSYNC_DURATION_US)
+ .length;
+ // How many times is a frame later/earlier than VSYNC_DURATION_US.
+ const framesOutOfSync = normDriftTimes
+ .filter(driftTime => driftTime > VSYNC_DURATION_US)
+ .length;
+
+ const percentBadlyOutOfSync = framesSeverelyOutOfSync /
+ driftTimes.length;
+ const percentOutOfSync = framesOutOfSync / driftTimes.length;
+
+ const framesOutOfSyncOnlyOnce = framesOutOfSync - framesSeverelyOutOfSync;
+
+ // Calculate smoothness metric. From the formula, we can see that smoothness
+ // score can be negative.
+ let smoothnessScore = 1 - (framesOutOfSyncOnlyOnce +
+ SEVERITY * framesSeverelyOutOfSync) / driftTimes.length;
+
+ // Minimum smoothness_score value allowed is zero.
+ if (smoothnessScore < 0) {
+ smoothnessScore = 0;
+ }
+
+ return {
+ framesOutOfSync,
+ framesSeverelyOutOfSync,
+ percentBadlyOutOfSync,
+ percentOutOfSync,
+ smoothnessScore
+ };
+ }
+
+ return {
+ webrtcRenderingMetric,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/webrtc/webrtc_rendering_metric_test.html b/chromium/third_party/catapult/tracing/tracing/metrics/webrtc/webrtc_rendering_metric_test.html
new file mode 100644
index 00000000000..17505faa9d7
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/metrics/webrtc/webrtc_rendering_metric_test.html
@@ -0,0 +1,456 @@
+<!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/extras/importer/trace_event_importer.html">
+<link rel="import" href="/tracing/metrics/webrtc/webrtc_rendering_metric.html">
+<link rel="import" href="/tracing/model/slice_group.html">
+<link rel="import" href="/tracing/value/histogram.html">
+<link rel="import" href="/tracing/value/histogram_set.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const DISPLAY_HERTZ = 60.0;
+ const VSYNC_DURATION_US = 1e6 / DISPLAY_HERTZ;
+
+ /**
+ * @param {Array.<Number>} pair - An array of length 2, from which a valid
+ * WebMediaPlayerMS event will be generated.
+ * @returns {event} A valid WebMediaPlayerMS event where the Ideal Render
+ * Instant is the first element and the Actual Render Begin is the second
+ * element.
+ */
+ function eventFromPair(pair) {
+ return {
+ title: 'UpdateCurrentFrame',
+ start: pair[1],
+ duration: 1,
+ args: {
+ 'Ideal Render Instant': pair[0],
+ 'Actual Render Begin': pair[1],
+ 'Actual Render End': 0,
+ 'Serial': 0,
+ }
+ };
+ }
+
+ /**
+ * @param {Array.<Number>} driftTimes - An array with the desired driftTimes.
+ * @return {Array.<event>} An array of events such that the drift times
+ * computed by webrtcRenderingMetric is the same as driftTimes.
+ */
+ function eventsFromDriftTimes(driftTimes) {
+ const pairs = [];
+ for (let i = 1; i <= driftTimes.length; ++i) {
+ pairs.push([i, i + driftTimes[i - 1]]);
+ }
+ return pairs.map(eventFromPair);
+ }
+
+ /**
+ * @param {Array.<Number>} normDriftTimes - To decide if a frame is out of
+ * sync or badly out of sync, we use the normalized drift times, that we get
+ * by subtracting the mean from each entry of the drift times array. The sum
+ * of the normDriftTimes must equal 0.
+ * @return {Array.<event>) An array of events such that when we normalize the
+ * drift times computed by webrtcRenderingMetric, we get the normDriftTimes
+ * array.
+ */
+ function eventsFromNormDriftTimes(normDriftTimes) {
+ /* Let
+ * B[i] = normDriftTimes[i]
+ * A[i] = driftTimes[i] be the array we want to find.
+ *
+ * We require that:
+ * sum(B[i]) = 0
+ *
+ * Then
+ * B[i] = A[i] - mean(A)
+ * => B[i] - B[0] = A[i] - mean(A) - A[0] + mean(A)
+ * => B[i] - B[0] = A[i] - A[0]
+ * => A[i] = B[i] - B[0] + A[0]
+ *
+ * We can fix A[0] to any number we want.
+ *
+ * Let's make sure that the array A we found generates the array B when
+ * normalized:
+ * A[i] - mean(A)
+ * = A[i] - sum(A[j]) / n
+ * = B[i] - B[0] + A[0] - sum(B[j] - B[0] + A[0]) / n
+ * = B[i] - B[0] + A[0] - (sum(B[j]) - n B[0] / n + n A[0] / n)
+ * = B[i] - B[0] + A[0] - sum(B[j]) + B[0] - A[0]
+ * = B[i] - sum(B[j])
+ * = B[i] since we require sum(B[j]) = 0
+ */
+ const driftTimes = [10000];
+ for (let i = 1; i < normDriftTimes.length; ++i) {
+ driftTimes.push(normDriftTimes[i] - normDriftTimes[0] + driftTimes[0]);
+ }
+ return eventsFromDriftTimes(driftTimes);
+ }
+
+ /**
+ * @param {Array.<Array.<Number>>} frameDistribution - An array of pairs
+ * encoding the source to output distribution. That is an array where each
+ * [ticks, count] entry says that there are 'count' frames that are displayed
+ * 'ticks' times.
+ * @returns {Array.<events>} The events that give rise to the given
+ * frameDistribution.
+ */
+ function eventsFromFrameDistribution(frameDistribution) {
+ let frameId = 0;
+ const pairs = [];
+ for (const [ticks, count] of frameDistribution) {
+ // We need 'count' runs, each run consisting of 'ticks' repeated elements.
+ for (let i = 0; i < count; ++i) {
+ frameId += 1;
+ for (let j = 0; j < ticks; ++j) {
+ // Frames are decided by the Ideal Render Instant.
+ pairs.push([frameId, 0]);
+ }
+ }
+ }
+ return pairs.map(eventFromPair);
+ }
+
+ function newModel(fakeEvents) {
+ function customizeModelCallback(model) {
+ const rendererProcess = model.getOrCreateProcess(1);
+ const mainThread = rendererProcess.getOrCreateThread(2);
+ mainThread.name = 'Compositor';
+ for (const event of fakeEvents) {
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(event));
+ }
+ }
+ return tr.c.TestUtils.newModelWithEvents([], {customizeModelCallback});
+ }
+
+ function runWebrtcRenderingMetric(fakeEvents) {
+ const histograms = new tr.v.HistogramSet();
+ const model = newModel(fakeEvents);
+ tr.metrics.webrtc.webrtcRenderingMetric(histograms, model);
+ return histograms;
+ }
+
+ test('frameDistribution', function() {
+ // These numbers don't mean anything, we just want to make sure we can
+ // recover them after running webrtcRenderingMetric.
+ const frameDistribution = [[10, 3], [5, 15], [3, 146], [1, 546], [2, 10]];
+ const frameHist = new tr.v.Histogram('', tr.b.Unit.byName.unitlessNumber);
+ for (const [ticks, count] of frameDistribution) {
+ for (let i = 0; i < count; ++i) {
+ frameHist.addSample(ticks);
+ }
+ }
+ const fakeEvents = eventsFromFrameDistribution(frameDistribution);
+ const histograms = runWebrtcRenderingMetric(fakeEvents);
+
+ // We don't have access to the values stored in the histogram, so we check
+ // for equality in the summary statistics.
+ const hist =
+ histograms.getHistogramNamed('WebRTCRendering_frame_distribution');
+ assert.strictEqual(hist.sum, frameHist.sum);
+ assert.strictEqual(hist.numValues, frameHist.numValues);
+ assert.strictEqual(hist.running.min, frameHist.running.min);
+ assert.strictEqual(hist.running.max, frameHist.running.max);
+ assert.closeTo(hist.standardDeviation, frameHist.standardDeviation, 1e-2);
+ });
+
+ test('driftTime', function() {
+ // These numbers don't mean anything. We just want to make sure we can
+ // recover them after running the metric.
+ const fakeDriftTimes = [16700, 17640, 15000, 24470, 16700, 14399, 17675];
+ const fakeEvents = eventsFromDriftTimes(fakeDriftTimes);
+ const histograms = runWebrtcRenderingMetric(fakeEvents);
+
+ // We don't have access to the values stored in the histogram, so we check
+ // for equality in the summary statistics.
+ const hist = histograms.getHistogramNamed('WebRTCRendering_drift_time');
+ assert.strictEqual(hist.sum, tr.b.math.Statistics.sum(fakeDriftTimes));
+ assert.strictEqual(hist.numValues, fakeDriftTimes.length);
+ assert.strictEqual(hist.running.min,
+ tr.b.math.Statistics.min(fakeDriftTimes));
+ assert.strictEqual(hist.running.max,
+ tr.b.math.Statistics.max(fakeDriftTimes));
+ assert.closeTo(hist.standardDeviation,
+ tr.b.math.Statistics.stddev(fakeDriftTimes), 1e-2);
+ });
+
+ test('framesBadlyOutOfSyncPerfect', function() {
+ // None of these will exceed the threshold for badly out of sync events,
+ // which is about 33 333.
+ const normDriftTimes = [-16700, 17640, 15000, -17640, -15000, 16700];
+ assert.strictEqual(tr.b.math.Statistics.sum(normDriftTimes), 0);
+ const fakeEvents = eventsFromNormDriftTimes(normDriftTimes);
+ const histograms = runWebrtcRenderingMetric(fakeEvents);
+
+ const hist =
+ histograms.getHistogramNamed('WebRTCRendering_frames_badly_out_of_sync');
+ assert.strictEqual(hist.numValues, 1);
+ assert.strictEqual(hist.running.mean, 0);
+ });
+
+ test('framesBadylOutOfSync', function() {
+ // Only 34 000 will exceed the threshold for badly out of sync events,
+ // which is about 33 333.
+ const normDriftTimes = [-34000, 10000, 10000, 10000, 4000];
+ assert.strictEqual(tr.b.math.Statistics.sum(normDriftTimes), 0);
+ const fakeEvents = eventsFromNormDriftTimes(normDriftTimes);
+ const histograms = runWebrtcRenderingMetric(fakeEvents);
+
+ const hist =
+ histograms.getHistogramNamed('WebRTCRendering_frames_badly_out_of_sync');
+ assert.strictEqual(hist.numValues, 1);
+ assert.strictEqual(hist.running.mean, 1);
+ });
+
+ test('framesOutOfSyncPerfect', function() {
+ // None of these will exceed the threshold for badly out of sync, which is
+ // about 16 667.
+ const normDriftTimes = [-16600, 15640, 15000, -15640, -15000, 16600];
+ assert.strictEqual(tr.b.math.Statistics.sum(normDriftTimes), 0);
+ const fakeEvents = eventsFromNormDriftTimes(normDriftTimes);
+ const histograms = runWebrtcRenderingMetric(fakeEvents);
+
+ const hist =
+ histograms.getHistogramNamed('WebRTCRendering_frames_out_of_sync');
+ assert.strictEqual(hist.numValues, 1);
+ assert.strictEqual(hist.running.mean, 0);
+ });
+
+ test('framesOutOfSync', function() {
+ // Only 17000 will exceed the threshold for badly out of sync, which is
+ // about 16 667.
+ const normDriftTimes = [-17000, 5000, 5000, 5000, 2000];
+ assert.strictEqual(tr.b.math.Statistics.sum(normDriftTimes), 0);
+ const fakeEvents = eventsFromNormDriftTimes(normDriftTimes);
+ const histograms = runWebrtcRenderingMetric(fakeEvents);
+
+ const hist =
+ histograms.getHistogramNamed('WebRTCRendering_frames_out_of_sync');
+ assert.strictEqual(hist.numValues, 1);
+ assert.strictEqual(hist.running.mean, 1);
+ });
+
+ test('percentBadlyOutOfSyncPerfect', function() {
+ // None of these will exceed the threshold for badly out of sync events,
+ // which is about 33 333.
+ const normDriftTimes = [-16700, 17640, 15000, -17640, -15000, 16700];
+ assert.strictEqual(tr.b.math.Statistics.sum(normDriftTimes), 0);
+ const fakeEvents = eventsFromNormDriftTimes(normDriftTimes);
+ const histograms = runWebrtcRenderingMetric(fakeEvents);
+
+ const hist =
+ histograms.getHistogramNamed('WebRTCRendering_percent_badly_out_of_sync');
+ assert.strictEqual(hist.numValues, 1);
+ assert.strictEqual(hist.running.mean, 0);
+ });
+
+ test('percentBadylOutOfSync', function() {
+ // Only 34 000 will exceed the threshold for badly out of sync events,
+ // which is about 33 333.
+ const normDriftTimes = [-34000, 10000, 10000, 10000, 4000];
+ assert.strictEqual(tr.b.math.Statistics.sum(normDriftTimes), 0);
+ const fakeEvents = eventsFromNormDriftTimes(normDriftTimes);
+ const histograms = runWebrtcRenderingMetric(fakeEvents);
+
+ const hist =
+ histograms.getHistogramNamed('WebRTCRendering_percent_badly_out_of_sync');
+ assert.strictEqual(hist.numValues, 1);
+ assert.strictEqual(hist.running.mean, .2);
+ });
+
+ test('percentOutOfSyncPerfect', function() {
+ // None of these will exceed the threshold for badly out of sync, which is
+ // about 16 667.
+ const normDriftTimes = [-16600, 15640, 15000, -15640, -15000, 16600];
+ assert.strictEqual(tr.b.math.Statistics.sum(normDriftTimes), 0);
+ const fakeEvents = eventsFromNormDriftTimes(normDriftTimes);
+ const histograms = runWebrtcRenderingMetric(fakeEvents);
+
+ const hist =
+ histograms.getHistogramNamed('WebRTCRendering_percent_out_of_sync');
+ assert.strictEqual(hist.numValues, 1);
+ assert.strictEqual(hist.running.mean, 0);
+ });
+
+ test('percentOutOfSync', function() {
+ // Only 17000 will exceed the threshold for badly out of sync, which is
+ // about 16 667.
+ const normDriftTimes = [-17000, 5000, 5000, 5000, 2000];
+ assert.strictEqual(tr.b.math.Statistics.sum(normDriftTimes), 0);
+ const fakeEvents = eventsFromNormDriftTimes(normDriftTimes);
+ const histograms = runWebrtcRenderingMetric(fakeEvents);
+
+ const hist =
+ histograms.getHistogramNamed('WebRTCRendering_percent_out_of_sync');
+ assert.strictEqual(hist.numValues, 1);
+ assert.strictEqual(hist.running.mean, .2);
+ });
+
+ test('smoothnessScorePerfect', function() {
+ // None of these will exceed the threshold for badly out of sync, which is
+ // about 16 667, so the smoothnessScore wil be perfect.
+ const normDriftTimes = [-16600, 15640, 15000, -15640, -15000, 16600];
+ assert.strictEqual(tr.b.math.Statistics.sum(normDriftTimes), 0);
+ const fakeEvents = eventsFromNormDriftTimes(normDriftTimes);
+ const histograms = runWebrtcRenderingMetric(fakeEvents);
+
+ const hist =
+ histograms.getHistogramNamed('WebRTCRendering_smoothness_score');
+ assert.strictEqual(hist.numValues, 1);
+ assert.strictEqual(hist.running.mean, 1);
+ });
+
+ test('smoothnessScore', function() {
+ // One will exceed the threshold for frames badly out of sync (33 333) and
+ // another two the threshold for frames out of sync (16 667). So the
+ // smoothness score is
+ // 1 - (frames out of sync + 3 * frames badly out of sync) / n
+ // = 1 - (2 + 3) / 5 = 0
+ const normDriftTimes = [-17000, 34000, -17000, -10000, 10000];
+ assert.strictEqual(tr.b.math.Statistics.sum(normDriftTimes), 0);
+ const fakeEvents = eventsFromNormDriftTimes(normDriftTimes);
+ const histograms = runWebrtcRenderingMetric(fakeEvents);
+
+ const hist =
+ histograms.getHistogramNamed('WebRTCRendering_smoothness_score');
+ assert.strictEqual(hist.numValues, 1);
+ assert.strictEqual(hist.running.mean, 0);
+ });
+
+ test('fpsPerfect', function() {
+ // Every frame is displayed once. This is a perfect FPS of 60.
+ const frameDistribution = [[1, 10]];
+ const fakeEvents = eventsFromFrameDistribution(frameDistribution);
+ const histograms = runWebrtcRenderingMetric(fakeEvents);
+
+ const hist = histograms.getHistogramNamed('WebRTCRendering_fps');
+ assert.strictEqual(hist.numValues, 1);
+ assert.strictEqual(hist.running.mean, 60);
+ });
+
+ test('fps', function() {
+ // Every frame is displayed 15 times. This means an FPS of 4.
+ const frameDistribution = [[15, 10]];
+ const fakeEvents = eventsFromFrameDistribution(frameDistribution);
+ const histograms = runWebrtcRenderingMetric(fakeEvents);
+
+ const hist = histograms.getHistogramNamed('WebRTCRendering_fps');
+ assert.strictEqual(hist.numValues, 1);
+ assert.strictEqual(hist.running.mean, 4);
+ });
+
+ test('frozenFramesCountPerfect', function() {
+ // 10 frames are displayed one time, and 10 frames are displayed twice.
+ // This means no frames exceed the threshold of 6, and so no frames are
+ // considered frozen.
+ const frameDistribution = [[1, 10], [2, 10]];
+ const fakeEvents = eventsFromFrameDistribution(frameDistribution);
+ const histograms = runWebrtcRenderingMetric(fakeEvents);
+
+ const hist =
+ histograms.getHistogramNamed('WebRTCRendering_frozen_frames_count');
+ assert.strictEqual(hist.numValues, 1);
+ assert.strictEqual(hist.running.mean, 0);
+ });
+
+ test('frozenFramesCount', function() {
+ // 82 frames are displayed 1 time, 5 frames are displayed 2 times,
+ // and 1 frame is displayed 6 times.
+ // Only the drame displayed 6 times satisfies the threshold of 6. Since the
+ // first appearance is not considered frozen, there are 5 frozen frames.
+ const frameDistribution = [[1, 82], [2, 5], [6, 1]];
+ const fakeEvents = eventsFromFrameDistribution(frameDistribution);
+ const histograms = runWebrtcRenderingMetric(fakeEvents);
+
+ const hist =
+ histograms.getHistogramNamed('WebRTCRendering_frozen_frames_count');
+ assert.strictEqual(hist.numValues, 1);
+ assert.strictEqual(hist.running.mean, 5);
+ });
+
+ test('freezingScorePerfect', function() {
+ // Every frame is displayed 1 times. This means a perfect freezing score of
+ // 100.
+ const frameDistribution = [[1, 10]];
+ const fakeEvents = eventsFromFrameDistribution(frameDistribution);
+ const histograms = runWebrtcRenderingMetric(fakeEvents);
+
+ const hist = histograms.getHistogramNamed('WebRTCRendering_freezing_score');
+ assert.strictEqual(hist.numValues, 1);
+ assert.strictEqual(hist.running.mean, 1);
+ });
+
+ test('freezingScore', function() {
+ // 82 frames are displayed 1 time, 5 frames are displayed 2 times,
+ // and 1 frame is displayed 8 times.
+ // This means that the total number of frames displayed is
+ // 82 * 1 + 5 * 2 + 1 * 8 = 100
+ // And the freezing score is
+ // 1 - 82 / 100 * weight[0]
+ // - 5 / 100 * weight[1]
+ // - 1 / 100 * weight[7]
+ // = 1 - .82 * 0 since weight[0] = 0
+ // - .05 * 0 since weight[1] = 0 too
+ // - .01 * 15 since weight[7] = 15
+ // = 1 - .15 = .85
+ // See frozenPenaltyWeight for information on the weights and
+ // addFreezingScore for the definition of the freezingScore.
+ const frameDistribution = [[1, 82], [2, 5], [8, 1]];
+ const fakeEvents = eventsFromFrameDistribution(frameDistribution);
+ const histograms = runWebrtcRenderingMetric(fakeEvents);
+
+ const hist = histograms.getHistogramNamed('WebRTCRendering_freezing_score');
+ assert.strictEqual(hist.numValues, 1);
+ assert.strictEqual(hist.running.mean, .85);
+ });
+
+ test('renderingLengthErrorPerfect', function() {
+ const fakePairs = [];
+ for (let i = 1; i < 10; ++i) {
+ // Each frame's Ideal Render Instant is exactly VSYNC_DURATION_US after
+ // the previous one, so that the rendering length error is 0.
+ fakePairs.push([VSYNC_DURATION_US * i, 0]);
+ }
+ const fakeEvents = fakePairs.map(eventFromPair);
+ const histograms = runWebrtcRenderingMetric(fakeEvents);
+
+ const hist =
+ histograms.getHistogramNamed('WebRTCRendering_rendering_length_error');
+ assert.strictEqual(hist.numValues, 1);
+ assert.closeTo(hist.running.mean, 0, 1e-3);
+ });
+
+ test('renderingLengthError', function() {
+ const errors = [1000, 3000, 0, 5000, 0, 2000];
+ const fakePairs = [[1, 0]];
+ for (let i = 0; i < errors.length; ++i) {
+ // Each frame's Ideal Render Instant is close to VSYNC_DURATION_US after
+ // the previous one, but with a known delay.
+ fakePairs.push([fakePairs[i][0] + VSYNC_DURATION_US + errors[i], 0]);
+ }
+
+ // The rendering length error is then the sum of the errors, normalized by
+ // the span between the first and the last Ideal Render Instants.
+ const idealRenderSpan = fakePairs[fakePairs.length - 1][0] -
+ fakePairs[0][0];
+ const expectedRenderingLengthError = tr.b.math.Statistics.sum(errors) /
+ idealRenderSpan;
+
+ const fakeEvents = fakePairs.map(eventFromPair);
+ const histograms = runWebrtcRenderingMetric(fakeEvents);
+
+ const hist =
+ histograms.getHistogramNamed('WebRTCRendering_rendering_length_error');
+ assert.strictEqual(hist.numValues, 1);
+ assert.closeTo(hist.running.mean, expectedRenderingLengthError, 1e-3);
+ });
+});
+</script>