diff options
Diffstat (limited to 'chromium/third_party/catapult/tracing/tracing')
156 files changed, 5196 insertions, 744 deletions
diff --git a/chromium/third_party/catapult/tracing/tracing/BUILD.gn b/chromium/third_party/catapult/tracing/tracing/BUILD.gn index 816fa752e0b..0df405e5cf7 100644 --- a/chromium/third_party/catapult/tracing/tracing/BUILD.gn +++ b/chromium/third_party/catapult/tracing/tracing/BUILD.gn @@ -11,7 +11,7 @@ config("common_config") { } source_set("histogram") { - configs += [ ":common_config" ] + public_configs = [ ":common_config" ] sources = [ "value/histogram.cc", "value/histogram.h", @@ -25,7 +25,7 @@ source_set("histogram") { } source_set("reserved_infos") { - configs += [ ":common_config" ] + public_configs = [ ":common_config" ] sources = [ "value/diagnostics/reserved_infos.cc", "value/diagnostics/reserved_infos.h", @@ -33,7 +33,6 @@ source_set("reserved_infos") { } test("histogram_unittests") { - configs += [ ":common_config" ] sources = [ "value/histogram_unittest.cc", "value/running_statistics_unittest.cc", @@ -46,7 +45,7 @@ test("histogram_unittests") { deps = [ ":histogram", "proto:histogram_proto", - "//testing/gtest:gtest_main", "//testing/gtest:gtest", + "//testing/gtest:gtest_main", ] } diff --git a/chromium/third_party/catapult/tracing/tracing/base/unittest/html_test_results.html b/chromium/third_party/catapult/tracing/tracing/base/unittest/html_test_results.html index 1642cd7ec2e..1387d0206c8 100644 --- a/chromium/third_party/catapult/tracing/tracing/base/unittest/html_test_results.html +++ b/chromium/third_party/catapult/tracing/tracing/base/unittest/html_test_results.html @@ -503,8 +503,7 @@ tr.exportTo('tr.b.unittest', function() { }, didAllTestsPass() { - // A test counts as failing if it failed or it was skipped. - return this.numTestsThatFailed_ + this.numSkippedTests_ === 0; + return this.numTestsThatFailed_ === 0; }, notifyTestResultToDevServer_(result, extraMsg) { diff --git a/chromium/third_party/catapult/tracing/tracing/base/unittest/suite_loader.html b/chromium/third_party/catapult/tracing/tracing/base/unittest/suite_loader.html index 6c14bd5afbf..0a092a0a79a 100644 --- a/chromium/third_party/catapult/tracing/tracing/base/unittest/suite_loader.html +++ b/chromium/third_party/catapult/tracing/tracing/base/unittest/suite_loader.html @@ -212,11 +212,11 @@ tr.exportTo('tr.b.unittest', function() { this.oldGlobalOnError_ = undefined; }, - constructAndRegisterTestSuite(suiteConstructor) { + constructAndRegisterTestSuite(suiteConstructor, opt_options) { const name = this.currentModuleLoader_.getCurrentlyExecutingModuleName(); const testSuite = new tr.b.unittest.TestSuite( - name, suiteConstructor); + name, suiteConstructor, opt_options); this.testSuites.push(testSuite); diff --git a/chromium/third_party/catapult/tracing/tracing/base/unittest/test_suite.html b/chromium/third_party/catapult/tracing/tracing/base/unittest/test_suite.html index 237403fbf05..f0e607273b4 100644 --- a/chromium/third_party/catapult/tracing/tracing/base/unittest/test_suite.html +++ b/chromium/third_party/catapult/tracing/tracing/base/unittest/test_suite.html @@ -19,7 +19,7 @@ tr.exportTo('tr.b.unittest', function() { const TestTypes = tr.b.unittest.TestTypes; - function TestSuite(name, suiteConstructor) { + function TestSuite(name, suiteConstructor, opt_suiteOptions) { this.guid = tr.b.GUID.allocateSimple(); this.name_ = name; this.tests_ = []; @@ -36,6 +36,17 @@ tr.exportTo('tr.b.unittest', function() { } }.bind(this); + global.skipTest = function(testCaseOrName, opt_testFn, opt_options) { + if (testCaseOrName instanceof TestCase) { + testCaseOrName.options.skipped = true; + test(testCaseOrName); + } else { + const options = Object.assign({}, opt_options || {}); + options.skipped = true; + test(testCaseOrName, opt_testFn, options); + } + }.bind(this); + global.test = function(testCaseOrName, opt_testFn, opt_options) { if (testCaseOrName instanceof TestCase) { if (opt_testFn !== undefined) { @@ -44,6 +55,9 @@ tr.exportTo('tr.b.unittest', function() { if (opt_options !== undefined) { throw new Error('opt_options cannot be given when giving a TestCase'); } + if (opt_suiteOptions && opt_suiteOptions.skipped) { + testCaseOrName.options.skipped = true; + } this.addTest(testCaseOrName); return; } @@ -51,6 +65,9 @@ tr.exportTo('tr.b.unittest', function() { let testName = testCaseOrName; const testFn = opt_testFn; const options = opt_options || {}; + if (opt_suiteOptions && opt_suiteOptions.skipped) { + options.skipped = true; + } if (testFn === undefined) { throw new Error('Must provide opt_testFn'); } @@ -100,6 +117,8 @@ tr.exportTo('tr.b.unittest', function() { } finally { global.test = undefined; global.timedPerfTest = undefined; + global.flakyTest = undefined; + global.skipTest = undefined; } } @@ -127,16 +146,22 @@ tr.exportTo('tr.b.unittest', function() { } }; - function testSuite(suiteConstructor) { + function testSuite(suiteConstructor, opt_suiteOptions) { if (!global._currentSuiteLoader) { throw new Error('testSuites can only be defined during suite loading'); } - global._currentSuiteLoader.constructAndRegisterTestSuite(suiteConstructor); + global._currentSuiteLoader.constructAndRegisterTestSuite( + suiteConstructor, opt_suiteOptions); + } + + function skippedTestSuite(suiteConstructor) { + testSuite(suiteConstructor, {skipped: true}); } return { TestSuite, testSuite, + skippedTestSuite, }; }); </script> diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/chrome_auditor.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/chrome_auditor.html index a010398a1cc..15a2e921d49 100644 --- a/chromium/third_party/catapult/tracing/tracing/extras/chrome/chrome_auditor.html +++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/chrome_auditor.html @@ -54,11 +54,12 @@ tr.exportTo('tr.e.audits', function() { for (const slice of asyncSlices) { if (slice.title === 'PipelineReporter' && slice.args.chrome_frame_reporter && - slice.args.chrome_frame_reporter.state === 'STATE_DROPPED') { + slice.args.chrome_frame_reporter.state === 'STATE_DROPPED' && + slice.args.chrome_frame_reporter.affects_smoothness) { const alertSlices = [slice].concat(slice.subSlices); alerts.push(new Alert( new EventInfo( - 'Dropped Frame', + 'Dropped Frame affecting smoothness', 'Frame was dropped (i.e. not produced/presented).'), slice.start, alertSlices)); diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/chrome_auditor_test.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/chrome_auditor_test.html index 8b41fa056ad..bbc0750b6b9 100644 --- a/chromium/third_party/catapult/tracing/tracing/extras/chrome/chrome_auditor_test.html +++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/chrome_auditor_test.html @@ -150,6 +150,30 @@ tr.b.unittest.testSuite(function() { } }); + const missedFrameSliceNotAffectingSmoothness = newAsyncSliceEx({ + title: 'PipelineReporter', + start: 200, + duration: 15, + args: { + chrome_frame_reporter: { + affects_smoothness: false, + state: 'STATE_DROPPED' + } + } + }); + + const missedFrameSliceAffectingSmoothness = newAsyncSliceEx({ + title: 'PipelineReporter', + start: 200, + duration: 15, + args: { + chrome_frame_reporter: { + affects_smoothness: true, + state: 'STATE_DROPPED' + } + } + }); + const missedFrameSlice = newAsyncSliceEx({ title: 'PipelineReporter', start: 200, @@ -161,6 +185,8 @@ tr.b.unittest.testSuite(function() { m.compositorThread.asyncSliceGroup.push(noArgsFrameSlice); m.compositorThread.asyncSliceGroup.push(submittedFrameSlice); + m.compositorThread.asyncSliceGroup.push(missedFrameSliceNotAffectingSmoothness); + m.compositorThread.asyncSliceGroup.push(missedFrameSliceAffectingSmoothness); m.compositorThread.asyncSliceGroup.push(missedFrameSlice); }, tr.e.audits.ChromeAuditor); diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/chrome_test_utils.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/chrome_test_utils.html index 69128010fa9..dc8483985a7 100644 --- a/chromium/third_party/catapult/tracing/tracing/extras/chrome/chrome_test_utils.html +++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/chrome_test_utils.html @@ -34,6 +34,9 @@ tr.exportTo('tr.e.chrome', function() { model.rasterWorker1 = model.rendererProcess.getOrCreateThread(5); model.rasterWorker1.name = 'CompositorTileWorker1'; + model.foregroundWorker1 = model.rendererProcess.getOrCreateThread(6); + model.foregroundWorker1.name = 'ThreadPoolForegroundWorker1'; + customizeModelCallback(model); }); }; diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/chrome_user_friendly_category_driver.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/chrome_user_friendly_category_driver.html index 6c046e34b18..f4f5671b022 100644 --- a/chromium/third_party/catapult/tracing/tracing/extras/chrome/chrome_user_friendly_category_driver.html +++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/chrome_user_friendly_category_driver.html @@ -82,6 +82,9 @@ tr.exportTo('tr.e.chrome', function() { 'HTMLDocumentParser::processParsedChunkFromBackgroundParser', 'HTMLDocumentParser::processTokenizedChunkFromBackgroundParser', 'ParseHTML', + 'HTMLDocumentParser::DeferredPumpTokenizerIfPossible', + 'HTMLDocumentParser::PumpTokenizer', + 'HTMLDocumentParser::appendBytes', ], raster: [ diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/chrome_user_friendly_category_driver_test.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/chrome_user_friendly_category_driver_test.html index 5251a7d2edc..4c346d1e2ba 100644 --- a/chromium/third_party/catapult/tracing/tracing/extras/chrome/chrome_user_friendly_category_driver_test.html +++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/chrome_user_friendly_category_driver_test.html @@ -42,7 +42,7 @@ tr.b.unittest.testSuite(function() { }), 'style'); assert.strictEqual(ufcFromEvent({ guid: tr.b.GUID.allocateSimple(), - title: 'HTMLDocumentParser::didReceiveParsedChunkFromBackgroundParser', + title: 'HTMLDocumentParser::DeferredPumpTokenizerIfPossible', category: 'cat' }), 'parseHTML'); diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/estimated_input_latency_test.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/estimated_input_latency_test.html index edae6767dfb..fc781613d4f 100644 --- a/chromium/third_party/catapult/tracing/tracing/extras/chrome/estimated_input_latency_test.html +++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/estimated_input_latency_test.html @@ -44,6 +44,7 @@ tr.b.unittest.testSuite(function() { function addTestFrame(rendererProcess) { rendererProcess.objects.addSnapshot( 'ptr', 'loading', 'FrameLoader', 300, { + isOutermostMainFrame: true, isLoadingMainFrame: true, frame: {id_ref: '0xdeadbeef'}, documentLoaderURL: 'http://example.com' diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/largest_contentful_paint.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/largest_contentful_paint.html index c61cca1047d..725106ed66e 100644 --- a/chromium/third_party/catapult/tracing/tracing/extras/chrome/largest_contentful_paint.html +++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/largest_contentful_paint.html @@ -89,9 +89,8 @@ tr.exportTo('tr.e.chrome', function() { findCandidates() { const finalLcpEvents = this.findFinalLcpEventOfEachNavigation( this.allBrowserEvents); - const finalCandidates = finalLcpEvents - .filter(finalLcpEvent => - !LcpInvalidateEvent.isLcpInvalidateEvent(finalLcpEvent)); + const finalCandidates = finalLcpEvents.filter(finalLcpEvent => + !(finalLcpEvent instanceof LcpInvalidateEvent)); return finalCandidates; } diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/fuchsia_importer.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/fuchsia_importer.html index 8fd2fcbf870..80079c7bb7a 100644 --- a/chromium/third_party/catapult/tracing/tracing/extras/importer/fuchsia_importer.html +++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/fuchsia_importer.html @@ -14,7 +14,6 @@ found in the LICENSE file. tr.exportTo('tr.e.importer.fuchsia', function() { const IMPORT_PRIORITY = 0; - const IDLE_THREAD_THRESHOLD = 6444000000; // Zircon thread state constants from: // https://fuchsia.googlesource.com/zircon/+/master/docs/syscalls/object_get_info.md @@ -64,19 +63,12 @@ tr.exportTo('tr.e.importer.fuchsia', function() { finalizeImport() { } - isIdleThread(prio, tid) { - if (prio === undefined) { - // If the "prio" field is not available (if we were, for example, - // using an old trace), then fall back to the legacy heuristic of - // assuming that large numbered threads are idle ones. - return tid > IDLE_THREAD_THRESHOLD; - } - // A thread is idle iff its priority is set to 0. - return prio === 0; + isIdleThread(pid, prio) { + return pid === 0 && prio === 0; } - recordThreadState_(tid, timestamp, state, prio) { - if (this.isIdleThread(prio, tid)) { + recordThreadState_(pid, tid, timestamp, state, prio) { + if (this.isIdleThread(pid, prio)) { return; } const states = @@ -104,6 +96,7 @@ tr.exportTo('tr.e.importer.fuchsia', function() { // }, processContextSwitchEvent_(event) { let tid = event.in.tid; + let pid = event.in.pid; let threadName = tid.toString(); let procName = ''; const prio = event.in.prio; @@ -119,7 +112,7 @@ tr.exportTo('tr.e.importer.fuchsia', function() { const name = procName + threadName; - if (this.isIdleThread(prio, tid)) { + if (this.isIdleThread(pid, prio)) { tid = undefined; // Fake kernel idle task } @@ -128,7 +121,7 @@ tr.exportTo('tr.e.importer.fuchsia', function() { cpu.switchActiveThread(timestamp, {}, tid, name, tid); const SCHEDULING_STATE = tr.model.SCHEDULING_STATE; - this.recordThreadState_(tid, timestamp, SCHEDULING_STATE.RUNNING, prio); + this.recordThreadState_(pid, tid, timestamp, SCHEDULING_STATE.RUNNING, prio); let outState = SCHEDULING_STATE.UNKNOWN; @@ -153,7 +146,7 @@ tr.exportTo('tr.e.importer.fuchsia', function() { outState = SCHEDULING_STATE.TASK_DEAD; break; } - this.recordThreadState_(event.out.tid, timestamp, outState, + this.recordThreadState_(event.out.pid, event.out.tid, timestamp, outState, event.out.prio); } diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/ftrace_importer.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/ftrace_importer.html index 048869e22d0..6e5e9c9ac67 100644 --- a/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/ftrace_importer.html +++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/ftrace_importer.html @@ -25,6 +25,7 @@ found in the LICENSE file. <link rel="import" href="/tracing/extras/importer/linux_perf/kfunc_parser.html"> <link rel="import" href="/tracing/extras/importer/linux_perf/mali_parser.html"> <link rel="import" href="/tracing/extras/importer/linux_perf/memreclaim_parser.html"> +<link rel="import" href="/tracing/extras/importer/linux_perf/msm_parser.html"> <link rel="import" href="/tracing/extras/importer/linux_perf/power_parser.html"> <link rel="import" href="/tracing/extras/importer/linux_perf/regulator_parser.html"> <link rel="import" href="/tracing/extras/importer/linux_perf/rss_parser.html"> diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/msm_parser.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/msm_parser.html new file mode 100644 index 00000000000..65b06999ddc --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/msm_parser.html @@ -0,0 +1,170 @@ +<!DOCTYPE html> +<!-- +Copyright 2020 The Chromium Authors. All rights reserved. +Use 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/importer/linux_perf/parser.html"> + +<script> +'use strict'; + +/** + * @fileoverview Parses drm/msm driver events in the Linux event trace format. + */ +tr.exportTo('tr.e.importer.linux_perf', function() { + const ColorScheme = tr.b.ColorScheme; + const Parser = tr.e.importer.linux_perf.Parser; + + /** + * Parses linux drm/msm trace events. + * @constructor + */ + function MSMParser(importer) { + Parser.call(this, importer); + + importer.registerEventHandler('msm_gpu_freq_change', + MSMParser.prototype.gpuFrequency.bind(this)); + + importer.registerEventHandler('msm_gpu_submit_flush', + MSMParser.prototype.gpuSubmitFlush.bind(this)); + + importer.registerEventHandler('msm_gpu_submit_retired', + MSMParser.prototype.gpuSubmitRetired.bind(this)); + + this.model_ = importer.model_; + this.submits = {}; + this.num_submits = 0; // # of in-flight submits + } + + MSMParser.prototype = { + __proto__: Parser.prototype, + + // msm_gpu_freq_change: new_freq=180 + gpuFrequency(eventName, cpuNumber, pid, ts, eventBase) { + const event = /new_freq=(\d+)/.exec(eventBase.details); + if (!event) return false; + const freq = parseInt(event[1]); + + const counter = + this.model_.kernel.getOrCreateCounter('GPU', 'GPU Frequency'); + if (counter.numSeries === 0) { + counter.addSeries(new tr.model.CounterSeries('frequency', + ColorScheme.getColorIdForGeneralPurposeString(counter.name))); + } + counter.series.forEach(function(series) { + series.addCounterSample(ts, freq); + }); + + return true; + }, + + // msm_gpu_submit_flush: id=348500 pid=29590 ring=0:348502 ticks=130364028 + gpuSubmitFlush(eventName, cpuNumber, pid, ts, eventBase) { + const event = /id=(\d+) pid=(\d+) ring=(\d+):(\d+) ticks=(\d+)/. + exec(eventBase.details); + if (!event) return false; + const id = parseInt(event[1]); + + const submit = {}; + submit.flushTS = ts; + submit.flushTicks = parseInt(event[5]); + submit.pid = parseInt(event[2]); + this.submits[id] = submit; + this.num_submits++; + + return true; + }, + + // msm_gpu_submit_retired: id=348500 pid=29590 ring=0:348501 elapsed=3405520 ns mhz=179 start=130055449 end=130120835 // @suppress longLineCheck + gpuSubmitRetired(eventName, cpuNumber, pid, ts, eventBase) { + const event = /id=(\d+) pid=(\d+) ring=(\d+):(\d+) elapsed=(\d+) ns mhz=(\d+) start=(\d+) end=(\d+)/. // @suppress longLineCheck + exec(eventBase.details); + if (!event) return false; + const id = parseInt(event[1]); + + // If we didn't see the start event (msm_gpu_submit_flush) then just + // skip this event: + if (!(id in this.submits)) return true; + + const submit = this.submits[id]; + delete this.submits[id]; + this.num_submits--; + + const gpuThread = this.importer.getOrCreatePseudoThread('GPU'); + + submit.elapsedNs = parseInt(event[5]); + submit.mhz = parseInt(event[6]); + submit.startTicks = parseInt(event[7]); + submit.endTicks = parseInt(event[8]); + + // Ticks are in units of 19.2Mhz alwayson counter, we can derive + // the time queued and running from this. The submit_flush trace + // captures both the CPU timestamp and the 19.2Mhz counter as read + // from the CPU. The submit_retired timestamp includes the value + // of the 19.2Mhz counter as read from the GPU before and after + // executing the submit. + // + // The trace timestamps (CPU based) are in ms. + + function ticks2ms(ticks) { + return ticks / 19200; + } + + const queuedDuration = ticks2ms(submit.startTicks - submit.flushTicks); + const runningDuration = ticks2ms(submit.endTicks - submit.startTicks); + + // for debug: + submit.queuedDuration = queuedDuration; + submit.runningDuration = runningDuration; + + // AsyncSlice's to show when the submit is queued, and begins to run: + const queued = new tr.model.AsyncSlice('', event[1] + ' queued', + tr.b.ColorScheme.getColorIdForReservedName('thread_state_runnable'), + submit.flushTS, + submit, + queuedDuration); + + const running = new tr.model.AsyncSlice('', event[1] + ' running', + tr.b.ColorScheme.getColorIdForReservedName('thread_state_running'), + submit.flushTS + queuedDuration, + submit, + runningDuration); + + const async = new tr.model.AsyncSlice('', 'pipeline', + ColorScheme.getColorIdForGeneralPurposeString('ongpu:' + submit.pid), + submit.flushTS, + submit, + queuedDuration + runningDuration); + + // We don't want the 'async' container slice visible, it just exists to + // group the 'queued' and 'running' subslices: + async.hidden = true; + + async.subSlices.push(queued); + async.subSlices.push(running); + gpuThread.thread.asyncSliceGroup.push(async); + + // And the synchronous ThreadSlice shows the time the submit is running + // on the gpu: + const onGpu = new tr.model.ThreadSlice('', event[1], + ColorScheme.getColorIdForGeneralPurposeString('ongpu:' + submit.pid), + submit.flushTS + queuedDuration, + submit, + runningDuration); + + gpuThread.thread.sliceGroup.pushSlice(onGpu); + + return true; + } + }; + + Parser.register(MSMParser); + + return { + MSMParser, + }; +}); +</script> + diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/msm_parser_test.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/msm_parser_test.html new file mode 100644 index 00000000000..fde592ac1af --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/msm_parser_test.html @@ -0,0 +1,106 @@ +<!DOCTYPE html> +<!-- +Copyright 2020 The Chromium Authors. All rights reserved. +Use 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"> + +<script> +'use strict'; + +tr.b.unittest.testSuite(function() { + test('msmImport', function() { + const lines = [ + ' UnityGfxDeviceW-20803 (20764) [007] .... 2569.637650: msm_gpu_submit_flush: id=164270 pid=20803 ring=0:164271 ticks=5854915894', // @suppress longLineCheck + ' kworker/u16:5-416 ( 416) [001] .... 2569.638074: msm_gpu_freq_change: new_freq=180', // @suppress longLineCheck + ' UnityGfxDeviceW-20803 (20764) [007] .... 2569.638521: msm_gpu_submit_flush: id=164271 pid=20803 ring=0:164272 ticks=5854932654', // @suppress longLineCheck + ' kworker/u16:5-416 ( 416) [001] .... 2569.650267: msm_gpu_submit_retired: id=164270 pid=20803 ring=0:164271 elapsed=11198437 ns mhz=179 start=5854916032 end=5855131042', // @suppress longLineCheck + ' kworker/u16:1-21183 (21183) [003] .... 2569.650297: msm_gpu_freq_change: new_freq=800', // @suppress longLineCheck + ' kworker/u16:5-416 ( 416) [001] .... 2569.652039: msm_gpu_submit_retired: id=164271 pid=20803 ring=0:164272 elapsed=1594895 ns mhz=216 start=5855131119 end=5855161741', // @suppress longLineCheck + ' UnityGfxDeviceW-20803 (20764) [007] .... 2569.654153: msm_gpu_submit_flush: id=164272 pid=20803 ring=0:164273 ticks=5855232953', // @suppress longLineCheck + ' UnityGfxDeviceW-20803 (20764) [007] .... 2569.654563: msm_gpu_submit_flush: id=164273 pid=20803 ring=0:164274 ticks=5855240817', // @suppress longLineCheck + ' kworker/u16:5-416 ( 416) [001] .... 2569.657140: msm_gpu_submit_retired: id=164272 pid=20803 ring=0:164273 elapsed=2771979 ns mhz=799 start=5855233005 end=5855286227', // @suppress longLineCheck + ' kworker/u16:5-416 ( 416) [001] .... 2569.659900: msm_gpu_submit_retired: id=164273 pid=20803 ring=0:164274 elapsed=549166 ns mhz=800 start=5855286249 end=5855296793', // @suppress longLineCheck + ' kworker/u16:5-416 ( 416) [001] .... 2569.663715: msm_gpu_freq_change: new_freq=267', // @suppress longLineCheck + ' UnityGfxDeviceW-20803 (20764) [007] .... 2569.670766: msm_gpu_submit_flush: id=164274 pid=20803 ring=0:164275 ticks=5855552051', // @suppress longLineCheck + ' UnityGfxDeviceW-20803 (20764) [007] .... 2569.671356: msm_gpu_submit_flush: id=164275 pid=20803 ring=0:164276 ticks=5855563392', // @suppress longLineCheck + ' kworker/u16:5-416 ( 416) [001] .... 2569.676930: msm_gpu_freq_change: new_freq=180', // @suppress longLineCheck + ' kworker/u16:5-416 ( 416) [001] .... 2569.679942: msm_gpu_submit_retired: id=164274 pid=20803 ring=0:164275 elapsed=8682343 ns mhz=241 start=5855552151 end=5855718852', // @suppress longLineCheck + ' kworker/u16:5-416 ( 416) [001] .... 2569.682724: msm_gpu_submit_retired: id=164275 pid=20803 ring=0:164276 elapsed=1663906 ns mhz=179 start=5855718929 end=5855750876', // @suppress longLineCheck + ' UnityGfxDeviceW-20803 (20764) [006] .... 2569.688592: msm_gpu_submit_flush: id=164276 pid=20803 ring=0:164277 ticks=5855894498', // @suppress longLineCheck + ' UnityGfxDeviceW-20803 (20764) [006] .... 2569.689192: msm_gpu_submit_flush: id=164277 pid=20803 ring=0:164278 ticks=5855906029', // @suppress longLineCheck + ' kworker/u16:5-416 ( 416) [001] .... 2569.693595: msm_gpu_freq_change: new_freq=180', // @suppress longLineCheck + ' kworker/u16:5-416 ( 416) [001] .... 2569.699896: msm_gpu_submit_retired: id=164276 pid=20803 ring=0:164277 elapsed=11185572 ns mhz=179 start=5855894608 end=5856109371', // @suppress longLineCheck + ' kworker/u16:5-416 ( 416) [001] .... 2569.701729: msm_gpu_submit_retired: id=164277 pid=20803 ring=0:164278 elapsed=1686666 ns mhz=180 start=5856109447 end=5856141831', // @suppress longLineCheck + ' kworker/u16:5-416 ( 416) [001] .... 2569.704179: msm_gpu_freq_change: new_freq=180', // @suppress longLineCheck + ' UnityGfxDeviceW-20803 (20764) [006] .... 2569.704660: msm_gpu_submit_flush: id=164278 pid=20803 ring=0:164279 ticks=5856203141', // @suppress longLineCheck + ' UnityGfxDeviceW-20803 (20764) [006] .... 2569.705313: msm_gpu_submit_flush: id=164279 pid=20803 ring=0:164280 ticks=5856215696', // @suppress longLineCheck + ' kworker/u16:5-416 ( 416) [001] .... 2569.715973: msm_gpu_freq_change: new_freq=800', // @suppress longLineCheck + ' kworker/u16:1-21183 (21183) [003] .... 2569.715991: msm_gpu_submit_retired: id=164278 pid=20803 ring=0:164279 elapsed=11180989 ns mhz=179 start=5856203256 end=5856417931', // @suppress longLineCheck + ' kworker/u16:1-21183 (21183) [003] .... 2569.716802: msm_gpu_submit_retired: id=164279 pid=20803 ring=0:164280 elapsed=876718 ns mhz=669 start=5856418010 end=5856434843', // @suppress longLineCheck + ' UnityGfxDeviceW-20803 (20764) [006] .... 2569.721188: msm_gpu_submit_flush: id=164280 pid=20803 ring=0:164281 ticks=5856520661', // @suppress longLineCheck + ' UnityGfxDeviceW-20803 (20764) [006] .... 2569.721710: msm_gpu_submit_flush: id=164281 pid=20803 ring=0:164282 ticks=5856530679', // @suppress longLineCheck + ' kworker/u16:5-416 ( 416) [001] .... 2569.725951: msm_gpu_submit_retired: id=164280 pid=20803 ring=0:164281 elapsed=2776406 ns mhz=799 start=5856520717 end=5856574024', // @suppress longLineCheck + ' kworker/u16:5-416 ( 416) [001] .... 2569.726539: msm_gpu_submit_retired: id=164281 pid=20803 ring=0:164282 elapsed=546458 ns mhz=800 start=5856574047 end=5856584539', // @suppress longLineCheck + ' kworker/u16:5-416 ( 416) [001] .... 2569.727104: msm_gpu_freq_change: new_freq=355', // @suppress longLineCheck + ' kworker/u16:5-416 ( 416) [001] .... 2569.738225: msm_gpu_freq_change: new_freq=180', // @suppress longLineCheck + ' UnityGfxDeviceW-20803 (20764) [006] .... 2569.742288: msm_gpu_submit_flush: id=164282 pid=20803 ring=0:164283 ticks=5856925966', // @suppress longLineCheck + ' UnityGfxDeviceW-20803 (20764) [006] .... 2569.742994: msm_gpu_submit_flush: id=164283 pid=20803 ring=0:164284 ticks=5856939530', // @suppress longLineCheck + ' kworker/u16:5-416 ( 416) [001] .... 2569.749148: msm_gpu_freq_change: new_freq=180', // @suppress longLineCheck + ' kworker/u16:5-416 ( 416) [001] .... 2569.753702: msm_gpu_submit_retired: id=164282 pid=20803 ring=0:164283 elapsed=11166041 ns mhz=179 start=5856926090 end=5857140478', // @suppress longLineCheck + ' UnityGfxDeviceW-20803 (20764) [006] .... 2569.754644: msm_gpu_submit_flush: id=164284 pid=20803 ring=0:164285 ticks=5857163320', // @suppress longLineCheck + ' kworker/u16:5-416 ( 416) [001] .... 2569.755242: msm_gpu_submit_retired: id=164283 pid=20803 ring=0:164284 elapsed=1697864 ns mhz=179 start=5857140557 end=5857173156', // @suppress longLineCheck + ' UnityGfxDeviceW-20803 (20764) [006] .... 2569.755419: msm_gpu_submit_flush: id=164285 pid=20803 ring=0:164286 ticks=5857178216', // @suppress longLineCheck + ' kworker/u16:5-416 ( 416) [001] .... 2569.760258: msm_gpu_freq_change: new_freq=800', // @suppress longLineCheck + ' kworker/u16:5-416 ( 416) [001] .... 2569.762807: msm_gpu_submit_retired: id=164284 pid=20803 ring=0:164285 elapsed=6796979 ns mhz=329 start=5857173223 end=5857303725', // @suppress longLineCheck + ' kworker/u16:5-416 ( 416) [001] .... 2569.763401: msm_gpu_submit_retired: id=164285 pid=20803 ring=0:164286 elapsed=550937 ns mhz=799 start=5857303747 end=5857314325', // @suppress longLineCheck + ' UnityGfxDeviceW-20803 (20764) [006] .... 2569.770442: msm_gpu_submit_flush: id=164286 pid=20803 ring=0:164287 ticks=5857466816', // @suppress longLineCheck + ' UnityGfxDeviceW-20803 (20764) [006] .... 2569.771292: msm_gpu_submit_flush: id=164287 pid=20803 ring=0:164288 ticks=5857483141', // @suppress longLineCheck + ' kworker/u16:5-416 ( 416) [001] .... 2569.773496: msm_gpu_submit_retired: id=164286 pid=20803 ring=0:164287 elapsed=2780833 ns mhz=799 start=5857466871 end=5857520263', // @suppress longLineCheck + ' kworker/u16:1-21183 (21183) [003] .... 2569.774162: msm_gpu_freq_change: new_freq=430', // @suppress longLineCheck + ]; + const m = tr.c.TestUtils.newModelWithEvents([lines.join('\n')], { + shiftWorldToZero: false + }); + assert.isFalse(m.hasImportWarnings); + + let gpuThread = undefined; + m.getAllThreads().forEach(function(t) { + switch (t.name) { + case 'GPU': + gpuThread = t; + break; + default: + assert.fail(t, undefined, 'Unexpected thread named ' + t.name); + } + }); + assert.isDefined(gpuThread); + + assert.strictEqual(gpuThread.sliceGroup.length, 17); + + function ticks2ms(ticks) { + return ticks / 19200; + } + + const queuedDuration = ticks2ms( + 5854916032 - /* start= from retire trace */ + 5854915894); /* ticks= from flush trace */ + const runningDuration = ticks2ms( + 5855131042 - /* end= from retire trace */ + 5854916032); /* start= from retire trace */ + + assert.closeTo( + (2569.637650 * 1000.0) + queuedDuration, + gpuThread.sliceGroup.slices[0].start, + 1e-5); + assert.closeTo( + runningDuration, + gpuThread.sliceGroup.slices[0].duration, + 1e-5); + }); +}); +</script> + diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/trace_event_importer.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/trace_event_importer.html index 54f4dcbaa15..8aa9d5b35ae 100644 --- a/chromium/third_party/catapult/tracing/tracing/extras/importer/trace_event_importer.html +++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/trace_event_importer.html @@ -772,7 +772,7 @@ tr.exportTo('tr.e.importer', function() { // in the main thread, in order to get the correct thread object, // we should use pid and tid from main thread. if (data.startTime !== undefined) { - this.profileInfo_.set(event.id, { + this.profileInfo_.set(`${event.pid} ${event.id}`, { startTime: data.startTime, pid: event.pid, tid: event.tid @@ -791,7 +791,8 @@ tr.exportTo('tr.e.importer', function() { throw new Error('samples and timeDeltas array should have same length'); } - const profileTree = this.getOrCreateProfileTree_(sampleType, event.id); + const profileTree = this.getOrCreateProfileTree_(sampleType, + `${event.pid} ${event.id}`); const nodes = data[sampleType].nodes; const samples = data[sampleType].samples; if (nodes !== undefined) { @@ -2853,6 +2854,7 @@ tr.exportTo('tr.e.importer', function() { // In Chromium under //services/resource_coordinator/public see // mojom/memory_instrumentation/memory_instrumentation.mojom and // cpp/memory_instrumentation/tracing_observer.cc + if (rawVmRegions === null) return; const vmRegions = new Array(rawVmRegions.length); for (let i = 0; i < rawVmRegions.length; i++) { const rawVmRegion = rawVmRegions[i]; diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/trace_event_importer_test.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/trace_event_importer_test.html index 7953ba7c488..133345b9134 100644 --- a/chromium/third_party/catapult/tracing/tracing/extras/importer/trace_event_importer_test.html +++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/trace_event_importer_test.html @@ -6627,6 +6627,97 @@ tr.b.unittest.testSuite(function() { checkParsedAndStreamInput(events, checkModel, false); }); + test('importV8SamplesInStreamingFormat_id_collision', function() { + function checkModel(m) { + const samples = m.samples; + assert.strictEqual(m.samples.length, 4); + assert.strictEqual(Object.keys(m.processes).length, 2); + assert.deepEqual(samples.map(sample => sample.start.toFixed(3)), [ + '0.011', + '0.012', + '0.013', + '0.016' + ]); + assert.deepEqual(samples.map(sample => sample.thread.getProcess().pid), [ + 1, + 1, + 2, + 2 + ]); + assert.deepEqual(samples[0].userFriendlyStack, [ + 'b url: b.js:1:2 Deoptimized reason: a reason', + 'a url: a.js:1:2' + ]); + assert.deepEqual(samples[1].userFriendlyStack, [ + 'a url: a.js:1:2' + ]); + assert.deepEqual(samples[2].userFriendlyStack, [ + 'd url: d.js:2:3 Deoptimized reason: another reason', + ]); + assert.deepEqual(samples[3].userFriendlyStack, [ + 'c url: c.js:2:3', + ]); + } + + const events = { + 'traceEvents': [ + {'pid': 1, 'tid': 2, 'ts': 3, 'ph': 'P', + 'cat': 'disabled-by-default-v8.cpu_profiler', + 'name': 'Profile', + 'args': {'data': {'startTime': 10}}, + 'id': 123 + }, + {'pid': 2, 'tid': 2, 'ts': 3, 'ph': 'P', + 'cat': 'disabled-by-default-v8.cpu_profiler', + 'name': 'Profile', + 'args': {'data': {'startTime': 10}}, + 'id': 123 + }, + {'pid': 1, 'tid': 3, 'ts': 4, 'ph': 'P', + 'cat': 'disabled-by-default-v8.cpu_profiler', + 'name': 'Profile', + 'args': { + 'data': { + 'timeDeltas': [1, 1], + 'cpuProfile': { + 'nodes': [ + // eslint-disable-next-line + {'callFrame': {'functionName': '(root)', 'scriptId': 0}, 'id': 1}, + // eslint-disable-next-line + {'callFrame': {'functionName': 'a', 'url': 'a.js', 'scriptId': 1, 'lineNumber': 1, 'columnNumber': 2}, 'id': 2, 'parent': 1}, + // eslint-disable-next-line + {'callFrame': {'functionName': 'b', 'url': 'b.js', 'scriptId': 2, 'lineNumber': 1, 'columnNumber': 2}, 'deoptReason': 'a reason', 'id': 3, 'parent': 2} + ], + 'samples': [3, 2] + } + } + }, + 'id': 123 + }, + {'pid': 2, 'tid': 3, 'ts': 4, 'ph': 'P', + 'cat': 'disabled-by-default-v8.cpu_profiler', + 'name': 'Profile', + 'args': { + 'data': { + 'timeDeltas': [3, 3], + 'cpuProfile': { + 'nodes': [ + // eslint-disable-next-line + {'callFrame': {'functionName': 'c', 'url': 'c.js', 'scriptId': 3, 'lineNumber': 2, 'columnNumber': 3}, 'id': 4, 'parent': 2}, + // eslint-disable-next-line + {'callFrame': {'functionName': 'd', 'url': 'd.js', 'scriptId': 4, 'lineNumber': 2, 'columnNumber': 3}, 'deoptReason': 'another reason', 'id': 5, 'parent': 3} + ], + 'samples': [5, 4] + } + } + }, + 'id': 123 + } + ] + }; + checkParsedAndStreamInput(events, checkModel, false); + }); + test('scopedIdForEvent_defaultScopeAsyncEvent', function() { const event = {pid: 1, ph: 'b', id: '0x1000'}; const id = tr.e.importer.TraceEventImporter.scopedIdForEvent_(event); diff --git a/chromium/third_party/catapult/tracing/tracing/extras/symbolizer/symbolize_trace.py b/chromium/third_party/catapult/tracing/tracing/extras/symbolizer/symbolize_trace.py index b15882a4ce6..cb5c4170b14 100755 --- a/chromium/third_party/catapult/tracing/tracing/extras/symbolizer/symbolize_trace.py +++ b/chromium/third_party/catapult/tracing/tracing/extras/symbolizer/symbolize_trace.py @@ -284,7 +284,6 @@ class NodeWrapper(object): 'modified' flag. """ - pass class MemoryMap(NodeWrapper): @@ -341,7 +340,7 @@ class MemoryMap(NodeWrapper): def file_offset(self, value): self._file_offset = value - def __cmp__(self, other): + def compare(self, other): # pylint: disable=invalid-name if isinstance(other, type(self)): other_start_address = other._start_address elif isinstance(other, six.integer_types): @@ -350,10 +349,31 @@ class MemoryMap(NodeWrapper): raise Exception('Cannot compare with %s' % type(other)) if self._start_address < other_start_address: return -1 - elif self._start_address > other_start_address: + if self._start_address > other_start_address: return 1 - else: - return 0 + return 0 + + if six.PY2: + def __cmp__(self, other): + return self.compare(other) + else: + def __eq__(self, other): + return self.compare(other) == 0 + + def __ne__(self, other): + return self.compare(other) != 0 + + def __lt__(self, other): + return self.compare(other) < 0 + + def __le__(self, other): + return self.compare(other) <= 0 + + def __gt__(self, other): + return self.compare(other) > 0 + + def __ge__(self, other): + return self.compare(other) >= 0 def __repr__(self): return 'Region(0x{:X} - 0x{:X}, {})'.format( @@ -369,7 +389,8 @@ class MemoryMap(NodeWrapper): file_offset) # Keep track of code-identifier when present. if 'ts' in region_node and 'sz' in region_node: - region._code_id = '%08X%X' % (int(region_node['ts'], 16), region.size) + region._code_id = '%08X%X' % (int(str(region_node['ts']), 16), + region.size) regions.append(region) regions.sort() @@ -433,7 +454,7 @@ class MemoryMap(NodeWrapper): region_index = bisect.bisect_right(self._regions, address) - 1 if region_index >= 0: region = self._regions[region_index] - if address >= region.start_address and address < region.end_address: + if region.start_address <= address < region.end_address: return region return None @@ -948,6 +969,7 @@ class Trace(NodeWrapper): self._version = None self._is_chromium = True self._is_64bit = False + self._os_arch = None self._is_win = False self._is_mac = False self._is_linux = False @@ -983,6 +1005,7 @@ class Trace(NodeWrapper): self._is_64bit = ( re.search('x86_64', metadata['os-arch'], re.IGNORECASE) and not re.search('WOW64', metadata['user-agent'], re.IGNORECASE)) + self._os_arch = metadata['os-arch'] # Android traces produced via 'chrome://inspect/?tracing#devices' are # just list of events. @@ -1093,6 +1116,10 @@ class Trace(NodeWrapper): return self._os @property + def os_arch(self): + return self._os_arch + + @property def is_chromium(self): return self._is_chromium @@ -1151,12 +1178,11 @@ class Trace(NodeWrapper): if self._heap_dump_version is None: self._heap_dump_version = version return version - elif self._heap_dump_version != version: + if self._heap_dump_version != version: raise Exception( ("Inconsistent trace file: first saw '{}' heap dump version, " "then '{}'.").format(self._heap_dump_version, version)) - else: - return version + return version class SymbolizableFile(object): @@ -1218,12 +1244,13 @@ def ResolveSymbolizableFiles(processes, trace_from_win, frame_as_object_type): continue ResolveSymbolizableFilesByNodes( symfile_by_path, process.memory_map, - process.stack_frame_map.frame_by_id.values(), trace_from_win) + list(process.stack_frame_map.frame_by_id.values()), trace_from_win) if frame_as_object_type: - ResolveSymbolizableFilesByNodes(symfile_by_path, process.memory_map, - process.type_name_map.type_by_id.values(), - trace_from_win) + ResolveSymbolizableFilesByNodes( + symfile_by_path, process.memory_map, + list(process.type_name_map.type_by_id.values()), + trace_from_win) return list(symfile_by_path.values()) @@ -1277,7 +1304,8 @@ class BreakpadSymbolsModule(object): class Symbolizer(object): """Encapsulates platform-specific symbolization logic.""" - def __init__(self, addr2line_executable): + def __init__(self, addr2line_executable, os_arch): + self._os_arch = os_arch self.is_mac = sys.platform == 'darwin' self.is_win = sys.platform == 'win32' if self.is_mac: @@ -1331,10 +1359,11 @@ class Symbolizer(object): for address in symfile.frames_by_address.keys(): address_file.write('{:x} '.format(address + load_address)) - cmd = [self.symbolizer_path, '-arch', 'x86_64', '-l', + architecture = 'arm64e' if self._os_arch == 'arm64' else 'x86_64' + cmd = [self.symbolizer_path, '-arch', architecture, '-l', '0x%x' % load_address, '-o', symfile.symbolizable_path, '-f', address_file_path] - output_array = subprocess.check_output(cmd).split('\n') + output_array = six.ensure_str(subprocess.check_output(cmd)).split('\n') for i, frames in enumerate(symfile.frames_by_address.values()): symbolized_name = self._matcher.Match(output_array[i]) @@ -1364,8 +1393,9 @@ class Symbolizer(object): stderr=None) addrs = ["%x" % relative_pc for relative_pc in symfile.frames_by_address.keys()] - (stdout_data, _) = proc.communicate('\n'.join(addrs)) + (stdout_data, _) = proc.communicate(six.ensure_binary('\n'.join(addrs))) # On windows, lines may contain '\r' character: e.g. "RtlUserThreadStart\r". + stdout_data = six.ensure_str(stdout_data) stdout_data.replace('\r', '') stdout_data = stdout_data.split('\n') @@ -1438,11 +1468,11 @@ class Symbolizer(object): if self.is_win: extension = os.path.splitext(file_path)[1].lower() return extension in ['.dll', '.exe'] - else: - result = subprocess.check_output(['file', '-0', file_path]) - type_string = result[result.find('\0') + 1:] - return bool(re.match(r'.*(ELF|Mach-O) (32|64)-bit\b.*', - type_string, re.DOTALL)) + result = six.ensure_str( + subprocess.check_output(['file', '-0', file_path])) + type_string = result[result.find('\0') + 1:] + return bool(re.match(r'.*(ELF|Mach-O) (32|64)-bit\b.*', + type_string, re.DOTALL)) def SymbolizeFiles(symfiles, symbolizer): @@ -1654,9 +1684,11 @@ def FetchAndExtractBreakpadSymbols(symbol_base_directory, def OpenTraceFile(file_path, mode): if file_path.endswith('.gz'): - return gzip.open(file_path, mode + 'b') - else: - return open(file_path, mode + 't') + if six.PY2: + return gzip.open(file_path, mode + 'b') + return gzip.open( # pylint: disable=unexpected-keyword-arg + file_path, mode + 't', encoding='utf-8', newline='') + return open(file_path, mode + 't') def FetchAndExtractSymbolsMac(symbol_base_directory, version, @@ -1710,6 +1742,7 @@ def FetchAndExtractSymbolsWin(symbol_base_directory, version, is64bit, target = open(os.path.join(destination, filename), 'wb') with source, target: shutil.copyfileobj(source, target) + return True folder = "win64" if is64bit else "win" # Clang build (M61+) @@ -1726,16 +1759,19 @@ def FetchAndExtractSymbolsWin(symbol_base_directory, version, is64bit, return True os.makedirs(symbol_sub_dir) - DownloadAndExtractZipFile( + if not DownloadAndExtractZipFile( os.path.join(symbol_base_directory, "chrome-" + folder + "-" + version + "-syms.zip"), gcs_folder + "chrome-win32-syms.zip", - symbol_sub_dir) - DownloadAndExtractZipFile( + symbol_sub_dir): + return False + + if not DownloadAndExtractZipFile( os.path.join(symbol_base_directory, "chrome-" + folder + "-" + version + ".zip"), gcs_folder + "chrome-" + folder + folder_suffix + ".zip", - symbol_sub_dir) + symbol_sub_dir): + return False return True @@ -1807,11 +1843,6 @@ def main(args): if options.frame_as_object_type and not options.is_cast: sys.exit("Frame-as-object-type is only supported for cast.") - symbolizer = Symbolizer(options.addr2line_executable) - if (symbolizer.symbolizer_path is None and - not options.use_breakpad_symbols): - sys.exit("Can't symbolize - no %s in PATH." % symbolizer.binary) - trace_file_path = options.file print('Reading trace file...') @@ -1819,6 +1850,11 @@ def main(args): trace = Trace(json.load(trace_file), options.frame_as_object_type) print('Trace loaded for %s/%s' % (trace.os, trace.version)) + symbolizer = Symbolizer(options.addr2line_executable, trace.os_arch) + if (symbolizer.symbolizer_path is None and + not options.use_breakpad_symbols): + sys.exit("Can't symbolize - no %s in PATH." % symbolizer.binary) + trace.is_chromium = options.is_local_build trace.is_cast = options.is_cast diff --git a/chromium/third_party/catapult/tracing/tracing/extras/symbolizer/symbolize_trace_macho_reader.py b/chromium/third_party/catapult/tracing/tracing/extras/symbolizer/symbolize_trace_macho_reader.py index ff05cc6ba86..344a2437aeb 100644 --- a/chromium/third_party/catapult/tracing/tracing/extras/symbolizer/symbolize_trace_macho_reader.py +++ b/chromium/third_party/catapult/tracing/tracing/extras/symbolizer/symbolize_trace_macho_reader.py @@ -7,6 +7,7 @@ from __future__ import division from __future__ import print_function import re import subprocess +import six from six.moves import range # pylint: disable=redefined-builtin @@ -16,7 +17,7 @@ def ReadMachOTextLoadAddress(file_name): """ regex = re.compile(r".* vmaddr 0x([\dabcdef]*)") cmd = ["otool", "-l", file_name] - output = subprocess.check_output(cmd).split('\n') + output = six.ensure_str(subprocess.check_output(cmd)).split('\n') for i in range(len(output) - 3): # It's possible to use a regex here instead, but these conditionals are much # clearer. diff --git a/chromium/third_party/catapult/tracing/tracing/extras/symbolizer/symbolize_trace_macho_reader_unittest.py b/chromium/third_party/catapult/tracing/tracing/extras/symbolizer/symbolize_trace_macho_reader_unittest.py index f0058abda51..6e812583c79 100644 --- a/chromium/third_party/catapult/tracing/tracing/extras/symbolizer/symbolize_trace_macho_reader_unittest.py +++ b/chromium/third_party/catapult/tracing/tracing/extras/symbolizer/symbolize_trace_macho_reader_unittest.py @@ -3,6 +3,7 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. +import os import sys import unittest @@ -14,12 +15,16 @@ class AtosRegexTest(unittest.TestCase): if sys.platform != "darwin": return file_name = "/System/Library/Frameworks/IOKit.framework/Versions/A/IOKit" - result = symbolize_trace_macho_reader.ReadMachOTextLoadAddress(file_name) - self.assertNotEqual(None, result) + # TODO(crbug.com/1275181) + if os.path.exists(file_name): + result = symbolize_trace_macho_reader.ReadMachOTextLoadAddress(file_name) + self.assertNotEqual(None, result) file_name = "/System/Library/Frameworks/Cocoa.framework/Versions/A/Cocoa" - result = symbolize_trace_macho_reader.ReadMachOTextLoadAddress(file_name) - self.assertNotEqual(None, result) + # TODO(crbug.com/1275181) + if os.path.exists(file_name): + result = symbolize_trace_macho_reader.ReadMachOTextLoadAddress(file_name) + self.assertNotEqual(None, result) if __name__ == '__main__': diff --git a/chromium/third_party/catapult/tracing/tracing/importer/find_load_expectations.html b/chromium/third_party/catapult/tracing/tracing/importer/find_load_expectations.html index b4db84afe45..5d83362b0f4 100644 --- a/chromium/third_party/catapult/tracing/tracing/importer/find_load_expectations.html +++ b/chromium/third_party/catapult/tracing/tracing/importer/find_load_expectations.html @@ -317,24 +317,29 @@ tr.exportTo('tr.importer', function() { for (let index = 0; index < navStartEvents.length; index++) { const currNavigation = navStartEvents[index]; let url; - let isLoadingMainFrame = false; + let isOutermostMainFrame = false; if (currNavigation.args.data) { url = currNavigation.args.data.documentLoaderURL; - isLoadingMainFrame = currNavigation.args.data.isLoadingMainFrame; + // If isOutermostMainFrame is available, use it, if not use + // isLoadingMainFrame. + isOutermostMainFrame = + (currNavigation.args.data.isOutermostMainFrame !== undefined) ? + currNavigation.args.data.isOutermostMainFrame : + currNavigation.args.data.isLoadingMainFrame; } else { // TODO(#4358): Delete old path of obtaining URL. const snapshot = findFrameLoaderSnapshotAt( rendererHelper, frameIdRef, currNavigation.start); if (snapshot) { url = snapshot.args.documentLoaderURL; - isLoadingMainFrame = snapshot.args.isLoadingMainFrame; + isOutermostMainFrame = snapshot.args.isLoadingMainFrame; } } // Filter navigationStartEvents that do not correspond to a loading main // frame, or has a URL that we do not care about. - if (!isLoadingMainFrame) continue; + if (!isOutermostMainFrame) continue; if (url === undefined || IGNORE_URLS.includes(url)) continue; if (prevNavigation.navigationEvent !== undefined) { diff --git a/chromium/third_party/catapult/tracing/tracing/importer/import.html b/chromium/third_party/catapult/tracing/tracing/importer/import.html index 8893120c2c5..00892071d69 100644 --- a/chromium/third_party/catapult/tracing/tracing/importer/import.html +++ b/chromium/third_party/catapult/tracing/tracing/importer/import.html @@ -235,11 +235,23 @@ tr.exportTo('tr.importer', function() { // Create auditors let auditors = []; addImportStage('Adding arbitrary data to model...', () => { - auditors = this.importOptions_.auditorConstructors.map( - auditorConstructor => new auditorConstructor(this.model_)); + for (const auditorConstructor of + this.importOptions_.auditorConstructors) { + try { + auditors.push(new auditorConstructor(this.model_)); + } catch (e) { + console.error('Failed to construct an auditor:'); + console.error(e); + } + } auditors.forEach((auditor) => { - auditor.runAnnotate(); - auditor.installUserFriendlyCategoryDriverIfNeeded(); + try { + auditor.runAnnotate(); + auditor.installUserFriendlyCategoryDriverIfNeeded(); + } catch (e) { + console.error('Failed to run an auditor:'); + console.error(e); + } }); }); @@ -275,8 +287,13 @@ tr.exportTo('tr.importer', function() { // Build the UserModel. addImportStage('Building UserModel...', () => { - const userModelBuilder = new tr.importer.UserModelBuilder(this.model_); - userModelBuilder.buildUserModel(); + try { + const userModelBuilder = new tr.importer.UserModelBuilder(this.model_); + userModelBuilder.buildUserModel(); + } catch (e) { + console.error('Failed to build user model'); + console.error(e); + } }); // Sort Expectations. diff --git a/chromium/third_party/catapult/tracing/tracing/importer/import_test.html b/chromium/third_party/catapult/tracing/tracing/importer/import_test.html index 77787402719..55d5eb797fe 100644 --- a/chromium/third_party/catapult/tracing/tracing/importer/import_test.html +++ b/chromium/third_party/catapult/tracing/tracing/importer/import_test.html @@ -213,7 +213,8 @@ tr.b.unittest.testSuite(function() { p.memoryDumps[2].mostRecentVmRegions); }); - test('setsModelStatsTraceImportDurationMs', function() { +// TODO(crbug.com/1315423): Fix and re-enable test to pass on all trybots. + skipTest('setsModelStatsTraceImportDurationMs', function() { const traceEvents = [ {ts: 1000, pid: 1, tid: 3, ph: 'B', cat: 'c', name: 'taskB', args: { my_object: {id_ref: '0x1000'} diff --git a/chromium/third_party/catapult/tracing/tracing/importer/proto_expectation.html b/chromium/third_party/catapult/tracing/tracing/importer/proto_expectation.html index 3deaac7d934..4efb9c079af 100644 --- a/chromium/third_party/catapult/tracing/tracing/importer/proto_expectation.html +++ b/chromium/third_party/catapult/tracing/tracing/importer/proto_expectation.html @@ -83,8 +83,8 @@ tr.exportTo('tr.importer', function() { if (this.type !== ProtoExpectation.IGNORED_TYPE && !this.isValid) { model.importWarning({ type: 'ProtoExpectation', - message: 'Please file a bug with this trace. ' + this.debug(), - showToUser: true + message: this.debug(), + showToUser: false }); return undefined; } diff --git a/chromium/third_party/catapult/tracing/tracing/importer/user_model_builder_test.html b/chromium/third_party/catapult/tracing/tracing/importer/user_model_builder_test.html index e4eeaa972fd..f26b7a19241 100644 --- a/chromium/third_party/catapult/tracing/tracing/importer/user_model_builder_test.html +++ b/chromium/third_party/catapult/tracing/tracing/importer/user_model_builder_test.html @@ -278,8 +278,9 @@ tr.b.unittest.testSuite(function() { const verifier = new UserExpectationVerifier(); verifier.customizeModelCallback = function(model) { model.rendererProcess.objects.addSnapshot('ptr', 'loading', 'FrameLoader', - 25, {isLoadingMainFrame: true, frame: {id_ref: '0xdeadbeef'}, - documentLoaderURL: 'http://example.com'}); + 25, { isOutermostMainFrame: true, isLoadingMainFrame: true, + frame: {id_ref: '0xdeadbeef'}, + documentLoaderURL: 'http://example.com'}); ChromeTestUtils.addFrameEvent(model, {start: 0, end: 10}); model.rendererMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({ cat: 'blink.user_timing', @@ -321,8 +322,9 @@ tr.b.unittest.testSuite(function() { verifier.customizeModelCallback = function(model) { ChromeTestUtils.addFrameEvent(model, {start: 0, end: 10}); model.rendererProcess.objects.addSnapshot('ptr', 'loading', 'FrameLoader', - 15, {isLoadingMainFrame: true, frame: {id_ref: '0xdeadbeef'}, - documentLoaderURL: 'http://example.com'}); + 15, { isOutermostMainFrame: true, isLoadingMainFrame: true, + frame: {id_ref: '0xdeadbeef'}, + documentLoaderURL: 'http://example.com'}); ChromeTestUtils.addFrameEvent(model, {start: 20, end: 40}); model.rendererMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({ cat: 'blink.user_timing', @@ -1111,8 +1113,9 @@ tr.b.unittest.testSuite(function() { const verifier = new UserExpectationVerifier(); verifier.customizeModelCallback = function(model) { model.rendererProcess.objects.addSnapshot('ptr', 'loading', 'FrameLoader', - 25, {isLoadingMainFrame: true, frame: {id_ref: '0xdeadbeef'}, - documentLoaderURL: 'http://example.com'}); + 25, { isOutermostMainFrame: true, isLoadingMainFrame: true, + frame: {id_ref: '0xdeadbeef'}, + documentLoaderURL: 'http://example.com'}); model.rendererMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({ cat: 'blink.user_timing', title: 'navigationStart', @@ -1207,8 +1210,9 @@ tr.b.unittest.testSuite(function() { verifier.customizeModelCallback = function(model) { ChromeTestUtils.addFrameEvent(model, {start: 0, end: 10}); model.rendererProcess.objects.addSnapshot('ptr', 'loading', 'FrameLoader', - 25, {isLoadingMainFrame: true, frame: {id_ref: '0xdeadbeef'}, - documentLoaderURL: 'http://example.com'}); + 25, { isOutermostMainFrame: true, isLoadingMainFrame: true, + frame: {id_ref: '0xdeadbeef'}, + documentLoaderURL: 'http://example.com'}); ChromeTestUtils.addFrameEvent(model, {start: 10, end: 20}); model.rendererMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({ cat: 'blink.user_timing', diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/__init__.py b/chromium/third_party/catapult/tracing/tracing/metrics/__init__.py index cffcee63c42..c115b7fe69d 100644 --- a/chromium/third_party/catapult/tracing/tracing/metrics/__init__.py +++ b/chromium/third_party/catapult/tracing/tracing/metrics/__init__.py @@ -2,6 +2,7 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. +from __future__ import absolute_import import os import sys diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/all_metrics.html b/chromium/third_party/catapult/tracing/tracing/metrics/all_metrics.html index 694f443fab7..6ed6dd188fd 100644 --- a/chromium/third_party/catapult/tracing/tracing/metrics/all_metrics.html +++ b/chromium/third_party/catapult/tracing/tracing/metrics/all_metrics.html @@ -9,10 +9,14 @@ found in the LICENSE file. <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/blink/resource_metric.html"> <link rel="import" href="/tracing/metrics/console_error_metric.html"> +<link rel="import" href="/tracing/metrics/core_web_vitals_metric.html"> +<link rel="import" href="/tracing/metrics/count_sum_metric.html"> <link rel="import" href="/tracing/metrics/cpu_process_metric.html"> +<link rel="import" href="/tracing/metrics/custom_metric.html"> <link rel="import" href="/tracing/metrics/media_metric.html"> -<link rel="import" href="/tracing/metrics/memory_ablation_metric.html"> +<link rel="import" href="/tracing/metrics/partition_alloc/pcscan_metric.html"> <link rel="import" href="/tracing/metrics/rendering/rendering_metric.html"> <link rel="import" href="/tracing/metrics/reported_by_page_metric.html"> <link rel="import" href="/tracing/metrics/sample_exception_metric.html"> @@ -32,6 +36,7 @@ found in the LICENSE file. <link rel="import" href="/tracing/metrics/system_health/rects_based_speed_index_metric.html"> <link rel="import" href="/tracing/metrics/system_health/responsiveness_metric.html"> <link rel="import" href="/tracing/metrics/system_health/screenshots_based_speed_index_metric.html"> +<link rel="import" href="/tracing/metrics/system_health/weblayer_startup_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"> 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 index 702cdbf5fea..390b02df412 100644 --- a/chromium/third_party/catapult/tracing/tracing/metrics/android_startup_metric.html +++ b/chromium/third_party/catapult/tracing/tracing/metrics/android_startup_metric.html @@ -5,55 +5,113 @@ Use 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/event_finder_utils.html"> <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: +// The |androidStartupMetric| produces metrics that (unless noted otherwise) +// 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. experimental_content_start_time - time when the renderer enters the main -// entrypoint. -// 3. experimental_navigation_start_time - This roughly corresponds to the time -// the initial navigation network request is sent. -// 4. navigation_commit_time - This is when the renderer has received the first -// part of the response from the network and started loading the page. -// 5. first_contentful_paint_time - time to the first contentful paint of the -// page loaded at startup +// * messageloop_start_time - Time till the message loop of the browser main +// starts processing posted tasks (after having loaded the Profile) +// +// * experimental_navigation_start_time - This roughly corresponds to the time +// the initial navigation network request is sent on the UI thread +// +// * experimental_network_request_start_time - Time until the first network +// request starts in the netlog. The actual bytes will be sent to the network +// slightly later: after disk cache responds. This metric can be used even for +// netowrk loads that are served from the disk cache. Currently relies on +// NetworkService running in the browser process. +// +// * navigation_commit_time - This is when the renderer has received the first +// part of the response from the network after all redirects and started +// loading the page. Recorded when the confirmation is received on the browser +// UI thread. +// +// * first_contentful_paint_time - Time to the first 'contentful' paint of the +// page loaded at startup +// +// * process_create_to_app_start_time - Time from process creation until the +// application starts. The end time of this event will be the same as the +// start time of messageloop_start_time. // // 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.BrowserMessageLoopStartTime'; - const CONTENT_START_EVENT_NAME = 'content::Start'; const NAVIGATION_EVENT_NAME = 'Navigation StartToCommit'; const FIRST_CONTENTFUL_PAINT_EVENT_NAME = 'firstContentfulPaint'; + const APPLICATION_START_EVENT_NAME = + 'Startup.LoadTime.ProcessCreateToApplicationStart'; + const REQUEST_ALIVE = 'REQUEST_ALIVE'; + + function isGetRequest(event) { + if (event.args && + event.args.params && + event.args.params.method == 'GET') { + return true; + } + return false; + } + + function findRequestEventAfterNavigation(networkEvents, range) { + let requestStart = range.max; + let requestEvent = null; + for (const event of networkEvents) { + if (isGetRequest(event)) { + for (const subSlice of event.subSlices) { + if (subSlice.title === REQUEST_ALIVE && + range.min <= subSlice.start && subSlice.start < requestStart) { + requestStart = subSlice.start; + requestEvent = subSlice; + } + } + } + } + return requestEvent; + } + function androidStartupMetric(histograms, model) { // Walk the browser slices, extract timestamps for the browser start, // message loop start. let messageLoopStartEvents = []; - let navigationEvents = []; + let applicationStartEvents = []; + let navigationPairs = []; + + function organizeBrowserEventByTitle(event, process) { + if (event.title === MESSAGE_LOOP_EVENT_NAME) { + messageLoopStartEvents.push(event); + } else if (event.title === APPLICATION_START_EVENT_NAME) { + applicationStartEvents.push(event); + } else if (event.title === NAVIGATION_EVENT_NAME) { + const networkEvents = + tr.e.chrome.EventFinderUtils.getNetworkEventsInRange(process, + event.range); + const requestEvent = findRequestEventAfterNavigation( + networkEvents, event.range); + navigationPairs.push({ + 'nav': event, + 'req': requestEvent, + }); + } + } + 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); - } else if (ev.title === NAVIGATION_EVENT_NAME) { - navigationEvents.push(ev); - } + organizeBrowserEventByTitle(ev, helper.process); } } // Walk the renderer slices and extract the 'first contentful paint' // histogram samples. - let contentStartEvents = []; let firstContentfulPaintEvents = []; const rendererHelpers = chromeHelper.rendererHelpers; const pids = Object.keys(rendererHelpers); @@ -65,38 +123,28 @@ tr.exportTo('tr.metrics.sh', function() { // There are usually several 'First Contentful Paint' events recorded // for each page load. Take only the first one per renderer. break; - } else if (ev.title === CONTENT_START_EVENT_NAME) { - contentStartEvents.push(ev); } } } // Fallback to scanning all processes if important events are not found. let totalBrowserStarts = messageLoopStartEvents.length; - let totalContentStartEvents = contentStartEvents.length; let totalFcpEvents = firstContentfulPaintEvents.length; - let totalNavigations = navigationEvents.length; - if (totalFcpEvents !== totalBrowserStarts || - totalNavigations !== totalBrowserStarts || - totalContentStartEvents !== totalBrowserStarts || + if (totalBrowserStarts !== totalFcpEvents || + totalBrowserStarts !== navigationPairs.length || + totalBrowserStarts !== applicationStartEvents.length || totalBrowserStarts === 0) { - messageLoopStartEvents = []; - contentStartEvents = []; - navigationEvents = []; - firstContentfulPaintEvents = []; + messageLoopStartEvents.length = 0; + navigationPairs.length = 0; + firstContentfulPaintEvents.length = 0; + applicationStartEvents.length = 0; // 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); - } else if (ev.title === NAVIGATION_EVENT_NAME) { - navigationEvents.push(ev); - } else if (ev.title === CONTENT_START_EVENT_NAME) { - contentStartEvents.push(ev); - } + organizeBrowserEventByTitle(ev, proc); } for (const ev of proc.getDescendantEvents()) { if (ev.title === FIRST_CONTENTFUL_PAINT_EVENT_NAME) { @@ -106,25 +154,65 @@ tr.exportTo('tr.metrics.sh', function() { } } totalBrowserStarts = messageLoopStartEvents.length; - totalContentStartEvents = contentStartEvents.length; - totalNavigations = navigationEvents.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. Navigation and content start events are - // recorded on a best effort basis if they are found in the trace; their - // absence doesn't cause FCP events to be dropped. + // Sort the events by start time. function orderEvents(event1, event2) { return event1.start - event2.start; } messageLoopStartEvents.sort(orderEvents); - contentStartEvents.sort(orderEvents); - navigationEvents.sort(orderEvents); + applicationStartEvents.sort(orderEvents); firstContentfulPaintEvents.sort(orderEvents); + function orderPairs(pair1, pair2) { + return pair1['nav'].start - pair2['nav'].start; + } + navigationPairs.sort(orderPairs); + + // Remove spurious navigation events. Only one 'Navigation StartToCommit' + // event should remain for each browser session. When multiple events are + // encountered for a browsing session, remove all except the first one. The + // first navigation event looked more realistic for the story + // maps_pwa:with_http_cache (http://crbug.com/1307774). + let filteredNavigationPairs = []; + let filteredRequestsFollowingNavigation = []; + let navigation_index = 0; + let messageloop_index = 0; + do { + // Set |browserStart| as the upper limit for the set of navigations to + // select from. + let browserStart = Number.POSITIVE_INFINITY; + if (messageloop_index < messageLoopStartEvents.length) { + browserStart = messageLoopStartEvents[messageloop_index].start; + } + // From navigation events preceding |browserStart| take the first one, + // skip others. + let navigationFound = false; + for (; navigation_index < navigationPairs.length; navigation_index++) { + // Each request event has to be at the same index as its corresponding + // navigation event. Take either both of them or none. + const navPair = navigationPairs[navigation_index]; + const navEvent = navPair['nav']; + const requestEvent = navPair['req']; + if (navEvent.start >= browserStart) { + break; + } + if (!navigationFound) { + if (messageloop_index === 0) { + throw new Error('Found a navigation event before browser start'); + } + filteredNavigationPairs.push(navPair); + navigationFound = true; + } + } + // Run the last iteration with |browserStart| as positive infinity. + } while (messageloop_index++ <= messageLoopStartEvents.length); + navigationPairs = filteredNavigationPairs; + + // Verify the number of FCP events to avoid pairing them to browser starts + // incorrectly. Absence of navigation events does not cause FCP events to be + // dropped. if (totalFcpEvents < totalBrowserStarts) { throw new Error('Found fewer FCP events (' + totalFcpEvents + ') than browser starts (' + totalBrowserStarts + ')'); @@ -135,47 +223,44 @@ tr.exportTo('tr.metrics.sh', function() { const messageLoopStartHistogram = histograms.createHistogram( 'messageloop_start_time', tr.b.Unit.byName.timeDurationInMs_smallerIsBetter, []); - const contentStartHistogram = histograms.createHistogram( - 'experimental_content_start_time', - tr.b.Unit.byName.timeDurationInMs_smallerIsBetter, []); const navigationStartHistogram = histograms.createHistogram( 'experimental_navigation_start_time', tr.b.Unit.byName.timeDurationInMs_smallerIsBetter, []); + const requestStartHistogram = histograms.createHistogram( + 'experimental_network_request_start_time', + tr.b.Unit.byName.timeDurationInMs_smallerIsBetter, []); const navigationCommitHistogram = histograms.createHistogram( 'navigation_commit_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 contentIndex = 0; + const applicationStartHistogram = histograms.createHistogram( + 'process_create_to_app_start_time', + tr.b.Unit.byName.timeDurationInMs_smallerIsBetter, []); + // The earliest 2 browser starts are skipped to reduce measurement noise. + // See http://crbug.com/891797. let navIndex = 0; let fcpIndex = 0; for (let loopStartIndex = 0; loopStartIndex < totalBrowserStarts;) { - const startEvent = messageLoopStartEvents[loopStartIndex]; + const loopStartEvent = messageLoopStartEvents[loopStartIndex]; + // There should be a corresponding application start event for each + // message loop start event. + const applicationStartEvent = applicationStartEvents[loopStartIndex]; if (fcpIndex === totalFcpEvents) { break; } // Skip all events that appear before the next browser start. - const contentStartEvent = contentIndex < contentStartEvents.length ? - contentStartEvents[contentIndex] : null; - if (contentStartEvent && contentStartEvent.start < startEvent.start) { - contentIndex++; - continue; - } - const navEvent = navIndex < navigationEvents.length ? - navigationEvents[navIndex] : null; - if (navEvent && navEvent.start < startEvent.start) { + const navPair = navIndex < navigationPairs.length ? + navigationPairs[navIndex] : null; + const navEvent = navPair ? navPair['nav'] : null; + if (navEvent && navEvent.start < loopStartEvent.start) { navIndex++; continue; } const fcpEvent = firstContentfulPaintEvents[fcpIndex]; - if (fcpEvent.start < startEvent.start) { + if (fcpEvent.start < loopStartEvent.start) { fcpIndex++; continue; } @@ -190,25 +275,30 @@ tr.exportTo('tr.metrics.sh', function() { } // Record the histograms. - messageLoopStartHistogram.addSample(startEvent.duration, - {events: new tr.v.d.RelatedEventSet([startEvent])}); - if (contentStartEvent) { - contentStartHistogram.addSample( - contentStartEvent.start - startEvent.start, - {events: new tr.v.d.RelatedEventSet([startEvent, - contentStartEvent])}); - } + messageLoopStartHistogram.addSample(loopStartEvent.duration, + {events: new tr.v.d.RelatedEventSet([loopStartEvent])}); if (navEvent) { navigationStartHistogram.addSample( - navEvent.start - startEvent.start, - {events: new tr.v.d.RelatedEventSet([startEvent, navEvent])}); + navEvent.start - loopStartEvent.start, + {events: new tr.v.d.RelatedEventSet([loopStartEvent, navEvent])}); navigationCommitHistogram.addSample( - navEvent.end - startEvent.start, - {events: new tr.v.d.RelatedEventSet([startEvent, navEvent])}); + navEvent.end - loopStartEvent.start, + {events: new tr.v.d.RelatedEventSet([loopStartEvent, navEvent])}); + const requestEvent = navPair['req']; + if (!requestEvent) { + throw new Error('No network request after navigation'); + } + requestStartHistogram.addSample( + requestEvent.start - loopStartEvent.start, + {events: new tr.v.d.RelatedEventSet([navEvent, requestEvent])}); } firstContentfulPaintHistogram.addSample( - fcpEvent.end - startEvent.start, - {events: new tr.v.d.RelatedEventSet([startEvent, fcpEvent])}); + fcpEvent.end - loopStartEvent.start, + {events: new tr.v.d.RelatedEventSet([loopStartEvent, fcpEvent])}); + if (applicationStartEvent) { + applicationStartHistogram.addSample(applicationStartEvent.duration, + {events: new tr.v.d.RelatedEventSet([applicationStartEvent])}); + } } } 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 index af1dca98488..076cbfef83f 100644 --- 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 @@ -32,6 +32,34 @@ tr.b.unittest.testSuite(function() { return rendererMainThread; } + function addNetworkEvents(startTime, durationTime, process) { + const slice = tr.c.TestUtils.newAsyncSliceEx({ + cat: 'netlog', + title: 'http://bbc.co.uk', + start: startTime, + args: { params: { method: 'GET' } }, + duration: durationTime}); + slice.args.params['method'] = 'GET'; + const subSlice = tr.c.TestUtils.newAsyncSliceEx({ + cat: 'netlog', + title: 'REQUEST_ALIVE', + start: startTime, + duration: durationTime}); + slice.subSlices.push(subSlice); + const netThread = process.getOrCreateThread(tr.b.GUID.allocateSimple()); + netThread.name = 'NetLog'; + netThread.asyncSliceGroup.push(slice); + } + + function addNavigationEvent(threadForAdding, startTime, durationTime) { + threadForAdding.asyncSliceGroup.push( + tr.c.TestUtils.newAsyncSliceEx({ + cat: 'navigation', + title: 'Navigation StartToCommit', + start: startTime, + duration: durationTime})); + } + // 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 @@ -43,22 +71,20 @@ tr.b.unittest.testSuite(function() { browserMainThread.asyncSliceGroup.push( tr.c.TestUtils.newAsyncSliceEx({ cat: 'startup', + title: 'Startup.LoadTime.ProcessCreateToApplicationStart', + start: (offset + 5000), + duration: (incrementForMetrics + 100)})); + browserMainThread.asyncSliceGroup.push( + tr.c.TestUtils.newAsyncSliceEx({ + cat: 'startup', title: 'Startup.BrowserMessageLoopStartTime', start: (offset + 6800.125), duration: (incrementForMetrics + 1700.0625)})); - browserMainThread.asyncSliceGroup.push( - tr.c.TestUtils.newAsyncSliceEx({ - cat: 'navigation', - title: 'Navigation StartToCommit', - start: (offset + 9000.5 + incrementForMetrics), - duration: (incrementForMetrics + 2000.5)})); + const navigationStart = offset + 9000.5 + incrementForMetrics; + addNavigationEvent(browserMainThread, navigationStart, + incrementForMetrics + 2000.5); const rendererMainThread = createRendererThread(model); rendererMainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({ - cat: 'startup', - title: 'content::Start', - start: (offset + 9500.125 + incrementForMetrics), - duration: 10000.0})); - rendererMainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({ cat: 'loading,rail,devtools.timeline', title: 'firstContentfulPaint', start: (offset + 8400.125 + incrementForMetrics), @@ -73,6 +99,11 @@ tr.b.unittest.testSuite(function() { start: (offset + 8400.125 + incrementForMetrics + 0.125), duration: 0.0, args: {frame: '0x0'}})); + + // The metrics rely on having network access in netlog soon after the + // navigation starts. + addNetworkEvents(navigationStart + 110, 2, browserMainThread.parent); + return browserMainThread; } // Adds early messageloop and FCP events. The metric should ignore these very @@ -83,6 +114,12 @@ tr.b.unittest.testSuite(function() { browserMainThread.asyncSliceGroup.push( tr.c.TestUtils.newAsyncSliceEx({ cat: 'startup', + title: 'Startup.LoadTime.ProcessCreateToApplicationStart', + start: offset, + duration: 1.0})); + browserMainThread.asyncSliceGroup.push( + tr.c.TestUtils.newAsyncSliceEx({ + cat: 'startup', title: 'Startup.BrowserMessageLoopStartTime', start: (offset + 1.0), duration: 10.0})); @@ -123,9 +160,11 @@ tr.b.unittest.testSuite(function() { verifyHistogram(histograms, 'messageloop_start_time', 1, 1700.0625); verifyHistogram(histograms, 'experimental_navigation_start_time', 1, 2200.375); + verifyHistogram(histograms, 'experimental_network_request_start_time', 1, + 2200.375 + 110); verifyHistogram(histograms, 'navigation_commit_time', 1, 4200.875); - verifyHistogram(histograms, 'experimental_content_start_time', 1, 2700); verifyHistogram(histograms, 'first_contentful_paint_time', 1, 1600); + verifyHistogram(histograms, 'process_create_to_app_start_time', 1, 100); }); // Emulates loss of the initial message loop start event. Checks that this @@ -150,18 +189,10 @@ tr.b.unittest.testSuite(function() { title: 'Startup.BrowserMessageLoopStartTime', start: (20.0 + 1.0), duration: 10.0})); - browserMainThread.asyncSliceGroup.push( - tr.c.TestUtils.newAsyncSliceEx({ - cat: 'navigation', - title: 'Navigation StartToCommit', - start: (20.0 + 11.0), - duration: 10.0})); + const navigationStartTime = 20.0 + 11.0; + addNavigationEvent(browserMainThread, navigationStartTime, 10.0); + addNetworkEvents(navigationStartTime + 2, 2, browserMainThread.parent); const rendererMainThread = createRendererThread(model); - rendererMainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({ - cat: 'startup', - title: 'content::Start', - start: (20.0 + 1.75), - duration: 10.0})); rendererMainThread.sliceGroup.pushSlice( tr.c.TestUtils.newSliceEx({ cat: 'loading,rail,devtools.timeline', @@ -178,8 +209,9 @@ tr.b.unittest.testSuite(function() { verifyHistogram(histograms, 'messageloop_start_time', 1, 1700.0625); verifyHistogram(histograms, 'experimental_navigation_start_time', 1, 2200.375); + verifyHistogram(histograms, 'experimental_network_request_start_time', 1, + 2200.375 + 110); verifyHistogram(histograms, 'navigation_commit_time', 1, 4200.875); - verifyHistogram(histograms, 'experimental_content_start_time', 1, 2700); verifyHistogram(histograms, 'first_contentful_paint_time', 1, 1600); }); @@ -212,10 +244,12 @@ tr.b.unittest.testSuite(function() { verifyHistogram(histograms, 'messageloop_start_time', 1, 10); verifyHistogram(histograms, 'experimental_navigation_start_time', 0, undefined); - verifyHistogram(histograms, 'navigation_commit_time', 0, undefined); - verifyHistogram(histograms, 'experimental_content_start_time', 0, + verifyHistogram(histograms, 'experimental_network_request_start_time', 0, undefined); + verifyHistogram(histograms, 'navigation_commit_time', 0, undefined); verifyHistogram(histograms, 'first_contentful_paint_time', 1, 2); + verifyHistogram(histograms, 'process_create_to_app_start_time', 0, + undefined); }); // Checks the metrics after adding an offset to events in the model, and @@ -226,9 +260,11 @@ tr.b.unittest.testSuite(function() { verifyHistogram(histograms, 'messageloop_start_time', 1, 1707.0625); verifyHistogram(histograms, 'experimental_navigation_start_time', 1, 2207.375); + verifyHistogram(histograms, 'experimental_network_request_start_time', 1, + 2207.375 + 110); verifyHistogram(histograms, 'navigation_commit_time', 1, 4214.875); - verifyHistogram(histograms, 'experimental_content_start_time', 1, 2707); verifyHistogram(histograms, 'first_contentful_paint_time', 1, 1607); + verifyHistogram(histograms, 'process_create_to_app_start_time', 1, 107); }); test('androidStartupMetric_twoSessions', function() { @@ -249,12 +285,35 @@ tr.b.unittest.testSuite(function() { 2, 1700.0625, 1700.0625 + delta); verifyHistogramRange(histograms, 'experimental_navigation_start_time', 2, 2200.375, 2200.375 + delta); + verifyHistogramRange(histograms, 'experimental_network_request_start_time', + 2, 2200.375 + 110, 2200.375 + delta + 110); verifyHistogramRange(histograms, 'navigation_commit_time', 2, 4200.875, 4200.875 + delta * 2); - verifyHistogramRange(histograms, 'experimental_content_start_time', - 2, 2700, 2700 + delta); verifyHistogramRange(histograms, 'first_contentful_paint_time', 2, 1600, 1600 + delta); + verifyHistogramRange(histograms, 'process_create_to_app_start_time', 2, 100, + 100 + delta); + }); + + test('androidStartupMetric_extraNavigations', function() { + const histograms = new tr.v.HistogramSet(); + function makeTestModelWithExtraNavigations() { + return tr.c.TestUtils.newModel(function(model) { + const mainThread = fillModelWithOneBrowserSession(model, 0.0, 0.0); + addEarlyEventsToBeIgnored(model, 0.0); + addEarlyEventsToBeIgnored(model, 20.0); + // The first commit time is hardcoded to match how it is added in + // fillModelWithOneBrowserSession(). + const veryFirstCommitTime = 9000.5 + 2000.5; + addNavigationEvent(mainThread, veryFirstCommitTime + 20.0, 5.0); + }); + } + tr.metrics.sh.androidStartupMetric(histograms, makeTestModelWithExtraNavigations()); + verifyHistogram(histograms, 'messageloop_start_time', 1, 1700.0625); + verifyHistogram(histograms, 'experimental_navigation_start_time', 1, + 2200.375); + verifyHistogram(histograms, 'experimental_network_request_start_time', 1, + 2200.375 + 110); }); }); </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 index 0c20030edc1..fba89459e00 100644 --- 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 @@ -6,6 +6,7 @@ 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/blink/gc_metric.html"> <link rel="import" href="/tracing/metrics/v8/utils.html"> diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/blink/resource_metric.html b/chromium/third_party/catapult/tracing/tracing/metrics/blink/resource_metric.html new file mode 100644 index 00000000000..bb186a74e9d --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/metrics/blink/resource_metric.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<!-- +Copyright 2021 The Chromium Authors. All rights reserved. +Use 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/event_finder_utils.html"> +<link rel="import" href="/tracing/metrics/metric_registry.html"> + +<script> +'use strict'; + +/** + * This metric is used for for blink network metrics + * - blinkRequestResourceCount: the count of requested resources + */ +tr.exportTo('tr.metrics', function() { + function blinkResourceMetric(histograms, model, opt_options) { + const chromeHelper = model.getOrCreateHelper( + tr.model.helpers.ChromeModelHelper); + if (!chromeHelper) { + // Chrome isn't present. + return; + } + const CATEGORY = 'blink.resource'; + const NAME = 'ResourceFetcher::requestResource'; + let count = 0; + // Collect trace events. + for (const helper of Object.values(chromeHelper.rendererHelpers)) { + if (helper.isChromeTracingUI) continue; + const events = tr.e.chrome.EventFinderUtils.getMainThreadEvents( + helper, NAME, CATEGORY); + for (const event of events) { + count++; + } + } + // Generate histograms. + histograms.createHistogram( + 'blinkRequestResourceCount', tr.b.Unit.byName.count, count); + } + + tr.metrics.MetricRegistry.register(blinkResourceMetric, { + supportsRangeOfInterest: false, + }); + + return { + blinkResourceMetric, + }; +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/blink/resource_metric_test.html b/chromium/third_party/catapult/tracing/tracing/metrics/blink/resource_metric_test.html new file mode 100644 index 00000000000..c11530c46fe --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/metrics/blink/resource_metric_test.html @@ -0,0 +1,45 @@ +<!DOCTYPE html> +<!-- +Copyright 2021 The Chromium Authors. All rights reserved. +Use 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/blink/resource_metric.html"> +<link rel="import" href="/tracing/value/histogram_set.html"> + +<script> +'use strict'; + +tr.b.unittest.testSuite(function() { + test('blinkRequestResourceCount_general', function() { + const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) { + const rendererProcess = model.rendererProcess; + const mainThread = model.rendererMain; + const mainFrame = { id: '0xdeadbeef', is_main: true }; + const emitEvent = (time, cat, title, url, duration) => { + mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({ + cat, + title, + start: time, + duration, + args: {url}, + })); + }; + emitEvent(1000, 'blink.resource', 'ResourceFetcher::requestResource', 'A.js', 0.1); + emitEvent(2001, 'blink.resource', 'ignore', 'A.js', 0.1); + emitEvent(2002, 'ignore', 'ignore', 'A.js', 0.1); + emitEvent(3200, 'blink.resource', 'ResourceFetcher::requestResource', 'b.js', 0.1); + emitEvent(4201, 'ignore', 'ResourceFetcher::requestResource', 'b.js', 0.1); + }); + + const histograms = new tr.v.HistogramSet(); + tr.metrics.blinkResourceMetric(histograms, model); + const histogram = histograms.getHistogramNamed('blinkRequestResourceCount'); + assert.strictEqual(histogram.sampleValues.length, 1); + assert.strictEqual(histogram.running.count, 1); + assert.strictEqual(histogram.running.mean, 2); + }); +}); 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 index 2a766b2477c..c5a9817c522 100644 --- a/chromium/third_party/catapult/tracing/tracing/metrics/compare_samples_unittest.py +++ b/chromium/third_party/catapult/tracing/tracing/metrics/compare_samples_unittest.py @@ -91,7 +91,7 @@ class CompareSamplesUnittest(unittest.TestCase): prev_bucket += width for value in values: for bucket in buckets: - if value >= bucket['low'] and value < bucket['high']: + if bucket['low'] <= value < bucket['high']: bucket['count'] += 1 break charts = { diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/core_web_vitals_metric.html b/chromium/third_party/catapult/tracing/tracing/metrics/core_web_vitals_metric.html new file mode 100644 index 00000000000..34382936e44 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/metrics/core_web_vitals_metric.html @@ -0,0 +1,111 @@ +<!DOCTYPE html> +<!-- +Copyright 2022 The Chromium Authors. All rights reserved. +Use of this source code is governed by a BSD-style license that can be +found in the LICENSE file. +--> + +<!-- +These metrics are designed to read off Core Web Vitals metrics (and their +important diagnostic metrics like First Contentful Paint) from the trace events +generated by the UkmPageLoadMetricsObserver, doing as little post-processing as +possible. These should therefore automatically stay updated as the definitions +in chromium C++ code evolve. See http://go/cwv-ukm-lab-metrics for more details. +--> + +<link rel="import" href="/tracing/base/unit.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/diagnostics/alert_groups.html"> +<link rel="import" href="/tracing/value/histogram.html"> + + +<script> +'use strict'; + +tr.exportTo('tr.metrics', function() { + const CWV_HISTOGRAM_NAMES = { + FCP_HISTOGRAM_NAME: 'cwv:first_contentful_paint', + LCP_HISTOGRAM_NAME: 'cwv:largest_contentful_paint', + CLS_HISTOGRAM_NAME: 'cwv:cumulative_layout_shift', + }; + + function coreWebVitalsMetric(histograms, model) { + const chromeHelper = model.getOrCreateHelper( + tr.model.helpers.ChromeModelHelper); + if (chromeHelper === undefined) return; + + const firstContentfulPaintHistogram = + histograms.createHistogram(CWV_HISTOGRAM_NAMES.FCP_HISTOGRAM_NAME, + tr.b.Unit.byName.timeDurationInMs_smallerIsBetter, [], { + alertGrouping: [tr.v.d.ALERT_GROUPS.LOADING_PAINT], + }); + const largestContentfulPaintHistogram = + histograms.createHistogram(CWV_HISTOGRAM_NAMES.LCP_HISTOGRAM_NAME, + tr.b.Unit.byName.timeDurationInMs_smallerIsBetter, [], { + alertGrouping: [tr.v.d.ALERT_GROUPS.LOADING_PAINT], + }); + const cumulativeLayoutShiftHistogram = + histograms.createHistogram(CWV_HISTOGRAM_NAMES.CLS_HISTOGRAM_NAME, + tr.b.Unit.byName.unitlessNumber_smallerIsBetter, [], { + alertGrouping: [tr.v.d.ALERT_GROUPS.LOADING_LAYOUT], + }); + + for (const browserHelper of Object.values(chromeHelper.browserHelpers)) { + const mainThread = browserHelper.mainThread; + const latestTimingBySourceId = new Map(); + + for (const event of mainThread.sliceGroup.childEvents()) { + if (event.category === 'loading' && + event.title === 'UkmPageLoadTimingUpdate') { + const timingUpdate = event.args['ukm_page_load_timing_update']; + const sourceId = timingUpdate['ukm_source_id']; + latestTimingBySourceId.set(sourceId, timingUpdate); + } + } + + for (const timingUpdate of latestTimingBySourceId.values()) { + const timingUpdateDiagnostic = { + 'page_load_timing': new tr.v.d.GenericSet([timingUpdate]), + }; + + // Note: This might always be a perfect round number because as of + // 2022/09/13, FCP values are clamped to 1ms resolution when sent to the + // browser process to prevent timing attacks. + const firstContentfulPaint = + timingUpdate['first_contentful_paint_ms']; + + // Do not add any core web vitals metrics if page did not reach FCP. + if (firstContentfulPaint === undefined) continue; + + firstContentfulPaintHistogram.addSample( + firstContentfulPaint, timingUpdateDiagnostic); + + // Note: this could be undefined if somehow the browser determined the + // LCP data is invalid. They should be exactly the same cases where we + // also do not report data to UKM. + largestContentfulPaintHistogram.addSample( + timingUpdate['latest_largest_contentful_paint_ms'], + timingUpdateDiagnostic); + + // Note: There is no need to specially handle the case when there are no + // layout shifts on the page. The latest_cumulative_layout_shift value + // should be reported as 0 in the trace event by the browser in that + // case, not undefined. Not that CLS can still be undefined if data + // is invalidated for some reason, e.g. see + // https://crsrc.org/c/components/page_load_metrics/browser/layout_shift_normalization.cc;l=25;drc=8d399817282e3c12ed54eb23ec42a5e418298ec6 + cumulativeLayoutShiftHistogram.addSample( + timingUpdate['latest_cumulative_layout_shift'], + timingUpdateDiagnostic); + } + } + } + + tr.metrics.MetricRegistry.register(coreWebVitalsMetric); + + return { + coreWebVitalsMetric, + CWV_HISTOGRAM_NAMES, + }; +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/core_web_vitals_metric_test.html b/chromium/third_party/catapult/tracing/tracing/metrics/core_web_vitals_metric_test.html new file mode 100644 index 00000000000..25354674ae1 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/metrics/core_web_vitals_metric_test.html @@ -0,0 +1,174 @@ +<!DOCTYPE html> +<!-- +Copyright 2022 The Chromium Authors. All rights reserved. +Use 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/chrome_test_utils.html"> +<link rel="import" href="/tracing/metrics/core_web_vitals_metric.html"> +<link rel="import" href="/tracing/value/histogram_set.html"> + +<script> +'use strict'; + +tr.b.unittest.testSuite(function() { + const { + FCP_HISTOGRAM_NAME, + LCP_HISTOGRAM_NAME, + CLS_HISTOGRAM_NAME, + } = tr.metrics.CWV_HISTOGRAM_NAMES; + + const timingUpdateEvent = (args) => { + return tr.c.TestUtils.newSliceEx({ + title: 'UkmPageLoadTimingUpdate', + cat: 'loading', + start: args.ts, + duration: 0, + args: { + ukm_page_load_timing_update: { + ukm_source_id: args.sourceId, + first_contentful_paint_ms: args.fcp, + latest_largest_contentful_paint_ms: args.lcp, + latest_cumulative_layout_shift: args.cls, + }, + }, + }); + }; + + test('histogramsAreAlwaysCreatedButCanBeEmpty', () => { + const model = tr.e.chrome.ChromeTestUtils.newChromeModel((model) => { + console.log(model); + model.browserMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx( + {title: 'SomeRandomEvent', start: 2, duration: 0})); + }); + + const histograms = new tr.v.HistogramSet(); + tr.metrics.coreWebVitalsMetric(histograms, model); + + assert.strictEqual( + 0, histograms.getHistogramNamed(FCP_HISTOGRAM_NAME).numValues); + assert.strictEqual( + 0, histograms.getHistogramNamed(LCP_HISTOGRAM_NAME).numValues); + assert.strictEqual( + 0, histograms.getHistogramNamed(CLS_HISTOGRAM_NAME).numValues); + }); + + test('metricIsReportedFromLatestEvent', () => { + const model = tr.e.chrome.ChromeTestUtils.newChromeModel((model) => { + model.browserMain.sliceGroup.pushSlice(timingUpdateEvent( + {sourceId: 42, ts: 11, fcp: 123, lcp: 300, cls: 2})); + model.browserMain.sliceGroup.pushSlice(timingUpdateEvent( + {sourceId: 42, ts: 12, fcp: 124, lcp: 301, cls: 3})); + }); + + const histograms = new tr.v.HistogramSet(); + tr.metrics.coreWebVitalsMetric(histograms, model); + + assert.strictEqual( + 1, histograms.getHistogramNamed(FCP_HISTOGRAM_NAME).numValues); + assert.strictEqual( + 124, histograms.getHistogramNamed(FCP_HISTOGRAM_NAME).max); + assert.strictEqual( + 1, histograms.getHistogramNamed(LCP_HISTOGRAM_NAME).numValues); + assert.strictEqual( + 301, histograms.getHistogramNamed(LCP_HISTOGRAM_NAME).max); + assert.strictEqual( + 1, histograms.getHistogramNamed(CLS_HISTOGRAM_NAME).numValues); + assert.strictEqual( + 3, histograms.getHistogramNamed(CLS_HISTOGRAM_NAME).max); + }); + + test('noMetricIsReportedIfFCPIsNotReached', () => { + const model = tr.e.chrome.ChromeTestUtils.newChromeModel((model) => { + model.browserMain.sliceGroup.pushSlice(timingUpdateEvent( + {sourceId: 42, ts: 11, lcp: 300, cls: 2})); // FCP missing. + }); + + const histograms = new tr.v.HistogramSet(); + tr.metrics.coreWebVitalsMetric(histograms, model); + + assert.strictEqual( + 0, histograms.getHistogramNamed(FCP_HISTOGRAM_NAME).numValues); + assert.strictEqual( + 0, histograms.getHistogramNamed(LCP_HISTOGRAM_NAME).numValues); + assert.strictEqual( + 0, histograms.getHistogramNamed(CLS_HISTOGRAM_NAME).numValues); + }); + + test('multipleNavigations', () => { + const modelClsMissing = + tr.e.chrome.ChromeTestUtils.newChromeModel((model) => { + // First navigation. + model.browserMain.sliceGroup.pushSlice(timingUpdateEvent( + {sourceId: 42, ts: 11, fcp: 123, lcp: 300, cls: 2})); + model.browserMain.sliceGroup.pushSlice(timingUpdateEvent( + {sourceId: 42, ts: 11, fcp: 123, lcp: 301, cls: 3})); + + // Second navigation. + model.browserMain.sliceGroup.pushSlice(timingUpdateEvent( + {sourceId: 43, ts: 11, fcp: 223, lcp: 400, cls: 12})); + model.browserMain.sliceGroup.pushSlice(timingUpdateEvent( + {sourceId: 43, ts: 11, fcp: 223, lcp: 401, cls: 13})); + }); + + const histograms = new tr.v.HistogramSet(); + tr.metrics.coreWebVitalsMetric(histograms, modelClsMissing); + + assert.strictEqual( + 2, histograms.getHistogramNamed(FCP_HISTOGRAM_NAME).numValues); + assert.strictEqual( + 123, histograms.getHistogramNamed(FCP_HISTOGRAM_NAME).min); + assert.strictEqual( + 223, histograms.getHistogramNamed(FCP_HISTOGRAM_NAME).max); + assert.strictEqual( + 2, histograms.getHistogramNamed(LCP_HISTOGRAM_NAME).numValues); + assert.strictEqual( + 301, histograms.getHistogramNamed(LCP_HISTOGRAM_NAME).min); + assert.strictEqual( + 401, histograms.getHistogramNamed(LCP_HISTOGRAM_NAME).max); + assert.strictEqual( + 2, histograms.getHistogramNamed(CLS_HISTOGRAM_NAME).numValues); + assert.strictEqual( + 3, histograms.getHistogramNamed(CLS_HISTOGRAM_NAME).min); + assert.strictEqual( + 13, histograms.getHistogramNamed(CLS_HISTOGRAM_NAME).max); + }); + + test('metricIsNotReportedIfMissingInLatestButPresentInEarlierEvent', () => { + // Metric can become missing in later update events if it's invalidated / + // tainted somehow in the browser. We do not report the metric when that + // happens (as opposed to reporting the last valid value of the metric.) + + const modelClsMissing = + tr.e.chrome.ChromeTestUtils.newChromeModel((model) => { + // First navigation. + model.browserMain.sliceGroup.pushSlice(timingUpdateEvent( + {sourceId: 42, ts: 11, fcp: 123, lcp: 300, cls: 2})); + model.browserMain.sliceGroup.pushSlice(timingUpdateEvent( + {sourceId: 42, ts: 11, fcp: 123, lcp: 301})); // CLS missing. + + // Second navigation. + model.browserMain.sliceGroup.pushSlice(timingUpdateEvent( + {sourceId: 43, ts: 11, fcp: 123, lcp: 300, cls: 2})); + model.browserMain.sliceGroup.pushSlice(timingUpdateEvent( + {sourceId: 43, ts: 11, fcp: 123, cls: 3})); // LCP missing. + }); + + const histograms = new tr.v.HistogramSet(); + tr.metrics.coreWebVitalsMetric(histograms, modelClsMissing); + + assert.strictEqual( + 2, histograms.getHistogramNamed(FCP_HISTOGRAM_NAME).numValues); + assert.strictEqual( + 1, histograms.getHistogramNamed(LCP_HISTOGRAM_NAME).numValues); + assert.strictEqual( + 301, histograms.getHistogramNamed(LCP_HISTOGRAM_NAME).max); + assert.strictEqual( + 1, histograms.getHistogramNamed(CLS_HISTOGRAM_NAME).numValues); + assert.strictEqual( + 3, histograms.getHistogramNamed(CLS_HISTOGRAM_NAME).max); + }); +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/count_sum_metric.html b/chromium/third_party/catapult/tracing/tracing/metrics/count_sum_metric.html new file mode 100644 index 00000000000..35148fcce85 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/metrics/count_sum_metric.html @@ -0,0 +1,81 @@ +<!DOCTYPE html> +<!-- +Copyright 2021 The Chromium Authors. All rights reserved. +Use 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/event_finder_utils.html"> +<link rel="import" href="/tracing/metrics/metric_registry.html"> + +<script> +'use strict'; + +/** + * This metric is used for custom benchmarks which need to record counts + * or sums of events ocurring in the renderer threads of traces. This is useful + * for one-off cluster telemetry analysis. + * For trace events with category "benchmark", and name "count_sum" and a + * "counter" field with the name of the counter, it records one or two metrics: + * - [counter]_count: the count of trace events with the same counter field. + * - [counter]_sum: the sum of values of trace events with that field, if any + * have a value field. + */ +tr.exportTo('tr.metrics', function() { + function countSumMetric(histograms, model, opt_options) { + const chromeHelper = model.getOrCreateHelper( + tr.model.helpers.ChromeModelHelper); + if (!chromeHelper) { + // Chrome isn't present. + return; + } + + const CATEGORY = 'benchmark'; + const NAME = 'count_sum'; + const counts = new Map(); + const sums = new Map(); + // Collect trace events. + for (const pid in chromeHelper.rendererHelpers) { + const helper = chromeHelper.rendererHelpers[pid]; + if (helper.isChromeTracingUI) continue; + + const events = tr.e.chrome.EventFinderUtils.getMainThreadEvents( + helper, NAME, CATEGORY); + for (const event of events) { + const c = event.args.counter; + if (!c) { + continue; + } + if (!counts.get(c)) { + counts.set(c, 0); + } + counts.set(c, counts.get(c) + 1); + if (event.args.value) { + if (!sums.get(c)) { + sums.set(c, 0); + } + sums.set(c, sums.get(c) + event.args.value); + } + } + } + + // Generate histograms. + counts.forEach((value, key) => { + histograms.createHistogram( + 'count_' + key, tr.b.Unit.byName.count, value); + }); + sums.forEach((value, key) => { + histograms.createHistogram( + 'sum_' + key, tr.b.Unit.byName.unitlessNumber, value); + }); + } + + tr.metrics.MetricRegistry.register(countSumMetric, { + supportsRangeOfInterest: false, + }); + + return { + countSumMetric, + }; +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/count_sum_metric_test.html b/chromium/third_party/catapult/tracing/tracing/metrics/count_sum_metric_test.html new file mode 100644 index 00000000000..a34fb9c7b84 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/metrics/count_sum_metric_test.html @@ -0,0 +1,61 @@ +<!DOCTYPE html> +<!-- +Copyright 2020 The Chromium Authors. All rights reserved. +Use 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/count_sum_metric.html"> +<link rel="import" href="/tracing/value/histogram_set.html"> + +<script> +'use strict'; + +tr.b.unittest.testSuite(function() { + test('countSumMetric_general', function() { + const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) { + const rendererProcess = model.rendererProcess; + const mainThread = model.rendererMain; + const mainFrame = { id: '0xdeadbeef', is_main: true }; + const subframe = { id: '0xdeadb33f', is_main: false }; + const emitEvent = (time, cat, title, counter, value) => { + mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({ + cat, + title, + start: time, + duration: 0.0, + args: {counter, value} + })); + }; + emitEvent(1000, 'benchmark', 'count_sum', 'A', 2.1); + emitEvent(3000, 'benchmark', 'count_sum', 'B', null); + emitEvent(2000, 'benchmark', 'count_sum', 'A', 19.4); + emitEvent(4000, 'benchmark', 'count_sum', 'B', null); + emitEvent(4000, 'benchmark', 'count_sum', 'B', null); + emitEvent(4000, 'donotcount', 'count_sum', 'C', null); + emitEvent(4000, 'benchmark', 'foo', 'D', null); + }); + + const histograms = new tr.v.HistogramSet(); + tr.metrics.countSumMetric(histograms, model); + const histAc = histograms.getHistogramNamed('count_A'); + assert.strictEqual(histAc.sampleValues.length, 1); + assert.strictEqual(histAc.running.count, 1); + assert.strictEqual(histAc.running.mean, 2); + const histAs = histograms.getHistogramNamed('sum_A'); + assert.strictEqual(histAs.sampleValues.length, 1); + assert.strictEqual(histAs.running.count, 1); + assert.strictEqual(histAs.running.mean, 21.5); + const histBc = histograms.getHistogramNamed('count_B'); + assert.strictEqual(histBc.sampleValues.length, 1); + assert.strictEqual(histBc.running.count, 1); + assert.strictEqual(histBc.running.mean, 3); + assert.isUndefined(histograms.getHistogramNamed('sum_B')); + assert.isUndefined(histograms.getHistogramNamed('count_C')); + assert.isUndefined(histograms.getHistogramNamed('sum_C')); + assert.isUndefined(histograms.getHistogramNamed('count_D')); + assert.isUndefined(histograms.getHistogramNamed('sum_D')); + }); +}); diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/custom_metric.html b/chromium/third_party/catapult/tracing/tracing/metrics/custom_metric.html new file mode 100644 index 00000000000..63823b49cee --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/metrics/custom_metric.html @@ -0,0 +1,155 @@ +<!DOCTYPE html> +<!-- +Copyright 2020 The Chromium Authors. All rights reserved. +Use 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'; + +/** + * This metric is used for custom performance benchmark. + * It captures the following: + * - Metrics reported by telemetry in the format of + * performance.mark('custom_metric:manifest:[{"name":<METRIC_NAME1>},{"name":<METRIC_NAME2>}...]'); + * - Duration between performance.mark('<METRIC_NAME>:metric_begin') + * and performance.mark('<METRIC_NAME>:metric_end') on the JS side. + * - performance.mark('<METRIC_NAME>:<METRIC_VALUE>:metric_value') + * on the JS side. + */ +tr.exportTo('tr.metrics', function() { + function customMetric(histograms, model, opt_options) { + const chromeHelper = model.getOrCreateHelper( + tr.model.helpers.ChromeModelHelper); + if (!chromeHelper) { + // Chrome isn't present. + return; + } + + const metrics = getMetrics(chromeHelper); + const traces = new Map(); + const benchmarkValues = new Map(); + // Collect trace events. + for (const helper of chromeHelper.browserHelpers) { + helper.iterAllThreads(function(thread) { + for (const slice of thread.sliceGroup.slices.concat( + thread.asyncSliceGroup.slices)) { + if (!slice.error && metrics.has(slice.title)) { + if (!traces.has(slice.title)) { + traces.set(slice.title, []); + } + traces.get(slice.title).push(slice.duration); + } + } + }); + } + + // Collect performance.mark(). + const METRIC_BEGIN = 'metric_begin'; + const METRIC_END = 'metric_end'; + const METRIC_VALUE = 'metric_value'; + const marks = new Map(); + for (const helper of Object.values(chromeHelper.rendererHelpers)) { + if (!helper.mainThread) continue; + for (const event of helper.mainThread.sliceGroup.childEvents()) { + if (!event.category.includes('blink.user_timing')) continue; + const {title} = event; + const index = title.lastIndexOf(':'); + if (index === -1) { + continue; + } + const name = title.substring(0, index); + const lastPart = title.substring(index + 1); + if (lastPart === METRIC_BEGIN) { + marks.set(name, event); + } else if (lastPart === METRIC_END) { + if (!marks.has(name)) { + continue; + } + const range = tr.b.math.Range.fromExplicitRange( + marks.get(name).start, event.start); + if (!traces.has(name)) { + traces.set(name, []); + } + traces.get(name).push(range.duration); + marks.delete(name); + } else if (lastPart === METRIC_VALUE) { + const index2 = name.lastIndexOf(':'); + if (index2 === -1) { + continue; + } + const key = name.substring(0, index2); + const value = Number(name.substring(index2 + 1)); + if (key && !isNaN(value)) { + if (!benchmarkValues.has(key)) { + benchmarkValues.set(key, []); + } + benchmarkValues.get(key).push(value); + } + } + } + } + + // Generate histograms. + traces.forEach((value, key) => { + createMetricHistogram(histograms, + key, + tr.b.Unit.byName.timeDurationInMs_smallerIsBetter, + value, + metrics.get(key)); + }); + benchmarkValues.forEach((value, key) => { + createMetricHistogram(histograms, + key, + tr.b.Unit.byName.unitlessNumber_smallerIsBetter, + value, + metrics.get(key)); + }); + } + + // Get metrics from performance.mark() + function getMetrics(chromeHelper) { + const CUSTOM_METRIC_MANIFEST_PREFIX = 'custom_metric:manifest:' + for (const helper of Object.values(chromeHelper.rendererHelpers)) { + if (!helper.mainThread) continue; + for (const event of helper.mainThread.sliceGroup.childEvents()) { + if (!event.category.includes('blink.user_timing')) continue; + const {title} = event; + if (title.startsWith(CUSTOM_METRIC_MANIFEST_PREFIX)) { + const result = JSON.parse(title.substring(CUSTOM_METRIC_MANIFEST_PREFIX.length)); + return new Map(result.map(event => [event.name, event])); + } + } + } + return new Map(); + } + + // Create histogram with default unit and + // read unit and description from the metric manifest if available. + function createMetricHistogram(histograms, key, defaultUnit, value, metric) { + let unit = metric && metric.unit; + if (unit) { + unit = tr.b.Unit.fromJSON(unit); + } + const description = metric && metric.description; + const histogram = histograms.createHistogram(key, + unit || defaultUnit, + value); + if (description) { + histogram.description = description; + } + return histogram; + } + + tr.metrics.MetricRegistry.register(customMetric, { + supportsRangeOfInterest: false, + }); + + return { + customMetric, + }; +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/custom_metric_test.html b/chromium/third_party/catapult/tracing/tracing/metrics/custom_metric_test.html new file mode 100644 index 00000000000..4a90395a6ef --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/metrics/custom_metric_test.html @@ -0,0 +1,273 @@ +<!DOCTYPE html> +<!-- +Copyright 2020 The Chromium Authors. All rights reserved. +Use 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/custom_metric.html"> +<link rel="import" href="/tracing/value/histogram_set.html"> + +<script> +'use strict'; + +tr.b.unittest.testSuite(function() { + test('customMetric_collectTrace', function() { + const model = tr.c.TestUtils.newModel((model) => { + const browserThread = setUpBrowserThread(model); + addTraces(browserThread); + addSingleTrace(setUpOtherBrowserThread(model)); + addSingleTrace(setUpOtherRendererThread(model)); + const rendererMainThread = setUpRendererMainThread(model); + setMetrics(rendererMainThread); + }); + const histograms = new tr.v.HistogramSet(); + tr.metrics.customMetric(histograms, model); + const hist1 = histograms.getHistogramNamed('metric1'); + assert.strictEqual(hist1.numValues, 2); + assert.strictEqual(hist1.unit.asJSON(), 'ms_smallerIsBetter'); + const hist2 = histograms.getHistogramNamed('metric2'); + assert.strictEqual(hist2.numValues, 1); + assert.strictEqual(hist2.unit.asJSON(), 'ms_biggerIsBetter'); + assert.strictEqual(hist2.description, 'This is metric2'); + const hist3 = histograms.getHistogramNamed('metric3'); + // metric3 is not collected because its name isn't reported. + assert.isUndefined(hist3); + const hist4 = histograms.getHistogramNamed('metric4'); + assert.strictEqual(hist4.numValues, 2); + // metric5 is collected from both other browser thread and + // other renderer thread. + const hist5 = histograms.getHistogramNamed('metric5'); + assert.strictEqual(hist5.numValues, 2); + }); + + test('customMetric_collectPerformanceMark', function() { + const model = tr.c.TestUtils.newModel((model) => { + const rendererMainThread = setUpRendererMainThread(model); + addPerformanceMark(rendererMainThread); + }); + const histograms = new tr.v.HistogramSet(); + tr.metrics.customMetric(histograms, model); + const hist1 = histograms.getHistogramNamed('metric1'); + assert.strictEqual(hist1.numValues, 2); + assert.strictEqual(hist1.max, 5.0); + assert.strictEqual(hist1.min, 2.0); + assert.strictEqual(hist1.average, 3.5); + const hist2 = histograms.getHistogramNamed('metric2'); + assert.strictEqual(hist2.numValues, 1); + assert.strictEqual(hist2.max, 18.0); + assert.strictEqual(hist2.min, 18.0); + assert.strictEqual(hist2.average, 18.0); + // metric3 is not collected because + // ":metric_begin" and ":metric_end" are missing. + assert.isUndefined(histograms.getHistogramNamed('metric3')); + // metric4 is not collected because + // ":metric_end" is missing. + assert.isUndefined(histograms.getHistogramNamed('metric4')); + // metric5 is not collected because + // ":metric_begin" is missing. + assert.isUndefined(histograms.getHistogramNamed('metric5')); + const hist3 = histograms.getHistogramNamed('metric6'); + assert.strictEqual(hist3.numValues, 2); + assert.strictEqual(hist3.max, 51); + assert.strictEqual(hist3.min, 49); + assert.strictEqual(hist3.average, 50); + // metric7 is not collected because + // benmark value is invalid. + assert.isUndefined(histograms.getHistogramNamed('metric7')); + // metric8 is not collected because + // benchmark value is missing. + assert.isUndefined(histograms.getHistogramNamed('metric8')); + }); + + 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 setUpOtherBrowserThread(model) { + const BROWSER_PROCESS_ID = 1234; + const browserProcess = model.getOrCreateProcess(BROWSER_PROCESS_ID); + const thread = browserProcess.getOrCreateThread(3); + thread.name = 'Other'; + return thread; + } + + function setUpOtherRendererThread(model) { + const RENDERER_PROCESS_ID = 2345; + const rendererProcess = model.getOrCreateProcess(RENDERER_PROCESS_ID); + const thread = rendererProcess.getOrCreateThread(4); + thread.name = 'Other'; + return thread; + } + + function addEvent(mainThread, event) { + mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({ + cat: 'blink.user_timing', + title: event.title, + start: event.start, + duration: 0.0, + })); + } + + function addTraces(thread) { + [ + { + cat: 'browser', + title: 'metric1', + start: 1, + duration: 1, + }, + { + cat: 'browser', + title: 'metric1', + start: 2, + duration: 2, + }, + { + cat: 'browser', + title: 'metric2', + start: 3, + duration: 3, + }, + { + cat: 'browser', + title: 'metric3', + start: 4, + duration: 4, + } + ].forEach(slice => { + thread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(slice)); + }); + + [ + { + cat: 'browser', + title: 'metric4', + start: 1, + duration: 1, + }, + { + cat: 'browser', + title: 'metric4', + start: 2, + duration: 2, + } + ].forEach(slice => { + thread.asyncSliceGroup.push(tr.c.TestUtils.newAsyncSliceEx(slice)); + }); + } + + function addSingleTrace(thread) { + const slice = { + cat: 'browser', + title: 'metric5', + start: 1, + duration: 1, + }; + thread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx(slice)); + } + + function setMetrics(thread) { + const traceEvents = [ + { + name: "metric1", + }, + { + name: "metric2", + unit: "ms_biggerIsBetter", + description: "This is metric2" + }, + { + name: "metric4", + }, + { + name: "metric5", + }, + ] + const report = `custom_metric:manifest:${JSON.stringify(traceEvents)}`; + thread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({ + cat: 'blink.user_timing', + title: report, + start: 0, + duration: 0, + })); + } + + function addPerformanceMark(thread) { + [ + { + title: 'metric1:metric_begin', + start: 10, + }, + { + title: 'metric1:metric_end', + start: 15, + }, + { + title: 'metric1:metric_begin', + start: 17, + }, + { + title: 'metric1:metric_end', + start: 19, + }, + { + title: 'metric2:metric_begin', + start: 2, + }, + { + title: 'metric2:metric_end', + start: 20, + }, + { + title: 'metric3', + start: 25, + }, + { + title: 'metric4:metric_begin', + start: 5, + }, + { + title: 'metric5:metric_end', + start: 10, + }, + { + title: 'metric6:49:metric_value', + start: 10, + }, + { + title: 'metric6:51:metric_value', + start: 20, + }, + { + title: 'metric7:invalid value:metric_value', + start: 10, + }, + { + title: 'metric8:metric_value', + start: 10, + } + ].forEach(slice => { + thread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({ + cat: 'blink.user_timing', + title: slice.title, + start: slice.start, + duration: 0, + })); + }); + } +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/discover.py b/chromium/third_party/catapult/tracing/tracing/metrics/discover.py index ef532c6d0df..75802db5d06 100644 --- a/chromium/third_party/catapult/tracing/tracing/metrics/discover.py +++ b/chromium/third_party/catapult/tracing/tracing/metrics/discover.py @@ -30,5 +30,4 @@ def DiscoverMetrics(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)] + return [str(m) for m in json.loads(res.stdout)] diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/discover_unittest.py b/chromium/third_party/catapult/tracing/tracing/metrics/discover_unittest.py index 10b81b547c8..978a9146d28 100644 --- a/chromium/third_party/catapult/tracing/tracing/metrics/discover_unittest.py +++ b/chromium/third_party/catapult/tracing/tracing/metrics/discover_unittest.py @@ -11,7 +11,7 @@ class MetricsDiscoverUnittest(unittest.TestCase): self.assertFalse(discover.DiscoverMetrics([])) def testMetricsDiscoverNonEmpty(self): - self.assertEquals(['sampleMetric'], discover.DiscoverMetrics( + self.assertEqual(['sampleMetric'], discover.DiscoverMetrics( ['/tracing/metrics/sample_metric.html'])) def testMetricsDiscoverMultipleMetrics(self): diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/media_metric.html b/chromium/third_party/catapult/tracing/tracing/metrics/media_metric.html index 9ec388ec862..0b11d30142b 100644 --- a/chromium/third_party/catapult/tracing/tracing/metrics/media_metric.html +++ b/chromium/third_party/catapult/tracing/tracing/metrics/media_metric.html @@ -71,6 +71,14 @@ tr.exportTo('tr.metrics', function() { processData.calculateSeekTimes(mainThread); processData.calculateBufferingTimes(mainThread); + // Roughness metrics are output from a thread pool worker, so we don't + // have a convenient name for the thread. Check all threads. + const allThreads = + rendererHelper.process.findAllThreadsMatching(function() { + return true; + }); + processData.calculateVideoPlaybackQuality(allThreads); + processData.addMetricToHistograms(histograms); } } @@ -160,6 +168,20 @@ tr.exportTo('tr.metrics', function() { } } + calculateVideoPlaybackQuality(threads) { + for (const thread of threads) { + for (const event of thread.sliceGroup.getDescendantEvents()) { + if (event.title === 'VideoPlaybackRoughness') { + this.getPerPlaybackObject_(event.args.id) + .processVideoRoughness(event.args.roughness); + } else if (event.title === 'VideoPlaybackFreezing') { + this.getPerPlaybackObject_(event.args.id) + .processVideoFreezing(event.args.freezing); + } + } + } + } + addMetricToHistograms(histograms) { for (const [id, playbackData] of this.playbackIdToDataMap_) { playbackData.addMetricToHistograms(histograms); @@ -191,6 +213,8 @@ tr.exportTo('tr.metrics', function() { this.seekError_ = false; this.seekTimes_ = new Map(); this.currentSeek_ = undefined; + this.roughness_ = undefined; + this.freezing_ = undefined; } // API methods for retrieving metric values. Each method returns undefined @@ -245,6 +269,16 @@ tr.exportTo('tr.metrics', function() { return this.seekTimes_; } + // Reports aggregate roughness. + get roughness() { + return this.roughness_; + } + + // Reports aggregate freezing. + get freezing() { + return this.freezing_; + } + // API methods for processing data from trace events. processVideoRenderTime(videoRenderTime) { @@ -359,6 +393,20 @@ tr.exportTo('tr.metrics', function() { } } + processVideoRoughness(roughness) { + // Record the worst roughness. + if (this.roughness_ === undefined || this.roughness_ > roughness) { + this.roughness_ = roughness; + } + } + + processVideoFreezing(freezing) { + // Record the worst freezing. + if (this.freezing_ === undefined || this.freezing_ > freezing) { + this.freezing_ = freezing; + } + } + addMetricToHistograms(histograms) { this.addSample_(histograms, 'time_to_video_play', tr.b.Unit.byName.timeDurationInMs_smallerIsBetter, @@ -384,6 +432,12 @@ tr.exportTo('tr.metrics', function() { this.addSample_(histograms, 'buffering_time', tr.b.Unit.byName.timeDurationInMs_smallerIsBetter, this.bufferingTime); + this.addSample_(histograms, 'roughness', + tr.b.Unit.byName.count_smallerIsBetter, + this.roughness); + this.addSample_(histograms, 'freezing', + tr.b.Unit.byName.timeDurationInMs_smallerIsBetter, + this.freezing); } // @private 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 index 02f5dcf02b5..0cb63b11c8d 100644 --- a/chromium/third_party/catapult/tracing/tracing/metrics/metric_map_function.html +++ b/chromium/third_party/catapult/tracing/tracing/metrics/metric_map_function.html @@ -147,6 +147,12 @@ tr.exportTo('tr.metrics', function() { function validateDiagnosticNames(histograms) { for (const hist of histograms) { for (const name of hist.diagnostics.keys()) { + if (name === tr.v.d.RESERVED_NAMES.ALERT_GROUPING) { + // Metrics can set alert grouping when they create histogram. It's + // still a reserved diagnostic because that helps us enforce the right + // diagnostic shape. + continue; + } if (tr.v.d.RESERVED_NAMES_SET.has(name)) { throw new Error( `Illegal diagnostic name "${name}" on Histogram "${hist.name}"`); diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/partition_alloc/OWNERS b/chromium/third_party/catapult/tracing/tracing/metrics/partition_alloc/OWNERS new file mode 100644 index 00000000000..228f0c3184b --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/metrics/partition_alloc/OWNERS @@ -0,0 +1,2 @@ +bikineev@chromium.org +mlippautz@chromium.org diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/partition_alloc/pcscan_metric.html b/chromium/third_party/catapult/tracing/tracing/metrics/partition_alloc/pcscan_metric.html new file mode 100644 index 00000000000..33743f757a5 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/metrics/partition_alloc/pcscan_metric.html @@ -0,0 +1,195 @@ +<!DOCTYPE html> +<!-- +Copyright 2020 The Chromium Authors. All rights reserved. +Use 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/chrome_processes.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'; + +/** + * @fileoverview This file contains implementations of PCScan metrics. PCScan + * is the algorithm that eliminates use-after-free bugs by verifying that there + * are no pointers in memory which point to explicitly freed objects before + * actually releasing their memory. + * + * pa:pcscan:<process_name>:<scanner|mutator> + * ======================== + * The overall time spent on scanning the partition alloc heap for a specific + * process either in the scanner or mutator thread (process_name can be either 'browser_process' or 'renderer_processes'). + * + * pa:pcscan:<process_name>:<scanner|mutator>:<phase> + * ======================== + * Time spent on a certain PCScan phase ('clear', 'scan' or 'sweep'). + */ +tr.exportTo('tr.metrics.pa', function() { + function pcscanMetric(histograms, model) { + function createTimeNumericForProcess(name, processName, context, desc) { + function createNumeric(name, desc) { + const n = new tr.v.Histogram(name, + tr.b.Unit.byName.timeDurationInMs_smallerIsBetter); + n.description = desc; + n.customizeSummaryOptions({ + avg: true, + count: true, + max: true, + min: true, + std: true, + sum: true}); + return n; + } + const scheme = ['pa', 'pcscan', processName, context]; + if (name) scheme.push(name); + return createNumeric(scheme.join(':'), desc); + } + + function createSizeNumericForProcess(name, processName, desc) { + function createNumeric(name, desc) { + const n = new tr.v.Histogram(name, + tr.b.Unit.byName.sizeInBytes_smallerIsBetter); + n.description = desc; + n.customizeSummaryOptions({ + avg: true, + count: true, + max: true, + min: true, + std: true, + sum: true}); + return n; + } + const scheme = ['pa', 'pcscan', processName, name]; + return createNumeric(scheme.join(':'), desc); + } + + function createPercentNumericForProcess(name, processName, desc) { + function createNumeric(name, desc) { + const n = new tr.v.Histogram(name, + tr.b.Unit.byName.normalizedPercentage_smallerIsBetter); + n.description = desc; + n.customizeSummaryOptions({ + avg: true, + count: true, + max: true, + min: true, + std: true, + sum: true}); + return n; + } + const scheme = ['pa', 'pcscan', processName, name]; + return createNumeric(scheme.join(':'), desc); + } + + + function createHistsForProcess(processName) { + return { + scanner_scan: createTimeNumericForProcess('scan', processName, 'scanner', + 'Time for scanning heap for quarantine pointers on concurrent threads'), + scanner_sweep: createTimeNumericForProcess('sweep', processName, 'scanner', + 'Time for sweeping quarantine'), + scanner_clear: createTimeNumericForProcess('clear', processName, 'scanner', + 'Time for clearing quarantine entries'), + scanner_total: createTimeNumericForProcess('', processName, 'scanner', + 'Total time for PCScan execution on concurrent threads'), + mutator_scan_stack: createTimeNumericForProcess('scan_stack', processName, 'mutator', + 'Time for scanning stack for quarantine pointers on mutator threads'), + mutator_scan: createTimeNumericForProcess('scan', processName, 'mutator', + 'Time for scanning heap for quarantine pointers on mutator threads'), + mutator_clear: createTimeNumericForProcess('clear', processName, 'mutator', + 'Time for clearing heap quarantine entries on mutator threads'), + mutator_total: createTimeNumericForProcess('', processName, 'mutator', + 'Total time for PCScan execution on mutator threads (inside safepoints)'), + survived_quarantine_size: createSizeNumericForProcess( + 'survived_quarantine_size', processName, + 'Size in bytes of survived quarantined objects after each *Scan cycle'), + survived_quarantine_percent: createPercentNumericForProcess( + 'survived_quarantine_percent', processName, + 'Percent of survived quarantined objects after a *Scan cycle relative ' + + 'to the size of quarantined objects before the cycle'), + }; + } + + function addSliceSample(hists, slice) { + if (slice.category !== 'partition_alloc') return; + if (!(slice instanceof tr.model.ThreadSlice)) return; + + if (slice.title === 'PCScan.Scanner.Scan') { + hists.scanner_scan.addSample(slice.duration); + } else if (slice.title === 'PCScan.Scanner.Sweep') { + hists.scanner_sweep.addSample(slice.duration); + } else if (slice.title === 'PCScan.Scanner.Clear') { + hists.scanner_clear.addSample(slice.duration); + } else if (slice.title === 'PCScan.Scanner') { + hists.scanner_total.addSample(slice.duration); + } else if (slice.title === 'PCScan.Mutator.ScanStack') { + hists.mutator_scan_stack.addSample(slice.duration); + } else if (slice.title === 'PCScan.Mutator.Scan') { + hists.mutator_scan.addSample(slice.duration); + } else if (slice.title === 'PCScan.Mutator.Clear') { + hists.mutator_clear.addSample(slice.duration); + } else if (slice.title === 'PCScan.Mutator') { + hists.mutator_total.addSample(slice.duration); + } + } + + function addCounterSample(hists, counter) { + if (counter.category !== 'partition_alloc') return; + if (!(counter instanceof tr.model.Counter)) return; + + for (const series of counter.series) { + for (const sample of series.samples) { + if (counter.name === 'PCScan.SurvivedQuarantineSize') { + hists.survived_quarantine_size.addSample(sample.value); + } else if (counter.name === 'PCScan.SurvivedQuarantinePercent') { + // Divide by 1000, since StatsCollector multiplies it by 1000. + hists.survived_quarantine_percent.addSample(sample.value / 1000); + } + } + } + } + + function addHistsForProcess(processHists, processHelpers) { + for (const helper of Object.values(processHelpers)) { + const processName = tr.e.chrome.chrome_processes. + canonicalizeProcessName(helper.process.name); + if (!processHists.has(processName)) { + processHists.set(processName, createHistsForProcess(processName)); + } + for (const slice of helper.process.getDescendantEvents()) { + addSliceSample(processHists.get(processName), slice); + } + for (const tid in helper.process.counters) { + addCounterSample(processHists.get(processName), helper.process.counters[tid]); + } + } + } + + const helper = + model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper); + + const processHists = new Map(); + addHistsForProcess(processHists, helper.browserHelpers); + addHistsForProcess(processHists, helper.rendererHelpers); + + for (const hists of processHists.values()) { + for (const hist of Object.values(hists)) { + histograms.addHistogram(hist); + } + } + } + + tr.metrics.MetricRegistry.register(pcscanMetric); + + return { + pcscanMetric, + }; +}); + +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/partition_alloc/pcscan_metric_test.html b/chromium/third_party/catapult/tracing/tracing/metrics/partition_alloc/pcscan_metric_test.html new file mode 100644 index 00000000000..ca43e546b56 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/metrics/partition_alloc/pcscan_metric_test.html @@ -0,0 +1,138 @@ +<!DOCTYPE html> +<!-- +Copyright 2020 The Chromium Authors. All rights reserved. +Use 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_processes.html"> +<link rel="import" href="/tracing/extras/chrome/chrome_test_utils.html"> +<link rel="import" href="/tracing/metrics/partition_alloc/pcscan_metric.html"> +<link rel="import" href="/tracing/value/histogram_set.html"> + +<script> +'use strict'; + +tr.b.unittest.testSuite(function() { + const CHROME_PROCESS_NAMES = + tr.e.chrome.chrome_processes.CHROME_PROCESS_NAMES; + + function makeTestModelFor(processName) { + return tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) { + const process = (processName === CHROME_PROCESS_NAMES.RENDERER ? + model.rendererProcess : model.browserProcess); + const thread = process.getOrCreateThread(2); + // Create slices. + thread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({ + cat: 'partition_alloc', + title: 'PCScan.Scanner', + start: 200, + duration: 100, + cpuStart: 200, + cpuDuration: 100 + })); + thread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({ + cat: 'partition_alloc', + title: 'PCScan.Mutator', + start: 200, + duration: 50, + cpuStart: 200, + cpuDuration: 50 + })); + thread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({ + cat: 'partition_alloc', + title: 'PCScan.Scanner.Clear', + start: 200, + duration: 16, + cpuStart: 200, + cpuDuration: 16 + })); + thread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({ + cat: 'partition_alloc', + title: 'PCScan.Scanner.Scan', + start: 216, + duration: 32, + cpuStart: 216, + cpuDuration: 32 + })); + thread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({ + cat: 'partition_alloc', + title: 'PCScan.Mutator.Clear', + start: 210, + duration: 8, + cpuStart: 210, + cpuDuration: 8 + })); + thread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({ + cat: 'partition_alloc', + title: 'PCScan.Mutator.ScanStack', + start: 218, + duration: 14, + cpuStart: 218, + cpuDuration: 14 + })); + thread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({ + cat: 'partition_alloc', + title: 'PCScan.Mutator.Scan', + start: 232, + duration: 16, + cpuStart: 232, + cpuDuration: 16 + })); + thread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({ + cat: 'partition_alloc', + title: 'PCScan.Scanner.Sweep', + start: 248, + duration: 42, + cpuStart: 248, + cpuDuration: 42 + })); + // Create counters. + const quarantineSizeCounter = process.getOrCreateCounter('partition_alloc', 'PCScan.SurvivedQuarantineSize'); + const quarantineSizeSeries = new tr.model.CounterSeries('value', 0); + quarantineSizeSeries.addCounterSample(1, 200000); + quarantineSizeSeries.addCounterSample(3, 300000); + quarantineSizeCounter.addSeries(quarantineSizeSeries); + const quarantineRateCounter = process.getOrCreateCounter('partition_alloc', 'PCScan.SurvivedQuarantinePercent'); + const quarantineRateSeries = new tr.model.CounterSeries('value', 0); + quarantineRateSeries.addCounterSample(1, 0.14 * 1000); + quarantineRateSeries.addCounterSample(3, 0.21 * 1000); + quarantineRateCounter.addSeries(quarantineRateSeries); + }); + } + + function testPCScanMetrics(processName) { + const histograms = new tr.v.HistogramSet(); + tr.metrics.pa.pcscanMetric(histograms, makeTestModelFor(processName)); + assert.closeTo(100, histograms.getHistogramNamed( + 'pa:pcscan:' + processName + ':scanner').average, 1e-2); + assert.closeTo(50, histograms.getHistogramNamed( + 'pa:pcscan:' + processName + ':mutator').average, 1e-2); + assert.closeTo(16, histograms.getHistogramNamed( + 'pa:pcscan:' + processName + ':scanner:clear').average, 1e-2); + assert.closeTo(32, histograms.getHistogramNamed( + 'pa:pcscan:' + processName + ':scanner:scan').average, 1e-2); + assert.closeTo(8, histograms.getHistogramNamed( + 'pa:pcscan:' + processName + ':mutator:clear').average, 1e-2); + assert.closeTo(14, histograms.getHistogramNamed( + 'pa:pcscan:' + processName + ':mutator:scan_stack').average, 1e-2); + assert.closeTo(16, histograms.getHistogramNamed( + 'pa:pcscan:' + processName + ':mutator:scan').average, 1e-2); + assert.closeTo(42, histograms.getHistogramNamed( + 'pa:pcscan:' + processName + ':scanner:sweep').average, 1e-2); + assert.closeTo(250000, histograms.getHistogramNamed( + 'pa:pcscan:' + processName + ':survived_quarantine_size').average, 1e-2); + assert.closeTo(0.175, histograms.getHistogramNamed( + 'pa:pcscan:' + processName + ':survived_quarantine_percent').average, 1e-2); + } + + test('pcscanMetricForBrowser', function() { + testPCScanMetrics(CHROME_PROCESS_NAMES.BROWSER); + }); + + test('pcscanMetricForRenderer', function() { + testPCScanMetrics(CHROME_PROCESS_NAMES.RENDERER); + }); +}); +</script> 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 index 16e185c172e..e6d55984e27 100644 --- a/chromium/third_party/catapult/tracing/tracing/metrics/rendering/cpu_utilization.html +++ b/chromium/third_party/catapult/tracing/tracing/metrics/rendering/cpu_utilization.html @@ -7,6 +7,9 @@ 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"> +<link rel="import" href="/tracing/model/user_model/segment.html"> + <link rel="import" href="/tracing/value/histogram.html"> <script> @@ -15,7 +18,7 @@ found in the LICENSE file. /** * @fileoverview This file contains implementations of the following metrics. * - * The addCpuUtilizationHistograms method can generate the following sets of + * The addCpuSegmentCostHistograms method can generate the following sets of * metrics, dependeing on the input values for segments, and segmentCostFunc. * * thread_{thread group}_cpu_time_per_frame @@ -34,6 +37,13 @@ found in the LICENSE file. * This set of metrics show the distribution of the number of task in each * display compositor's frame of a thread group. * + * The addCpuWallTimeHistogram method generates the metric: + * cpu_wall_time_ratio + * ================== + * segments: display compositor's frames + * + * This metric shows the ratio of cpu usage to wall time. + * * 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 @@ -86,7 +96,7 @@ tr.exportTo('tr.metrics.rendering', function() { if (isOther) yield 'other'; } - function addCpuUtilizationHistograms( + function addCpuSegmentCostHistograms( histograms, model, segments, segmentCostFunc, histogramNameFunc, description) { const categoryValues = new Map(); @@ -120,14 +130,26 @@ tr.exportTo('tr.metrics.rendering', function() { } } - const SUMMARY_OPTIONS = { - percentile: [0.90, 0.95], - ci: [0.95], - }; + function addCpuWallTimeHistogram(histograms, model, segments) { + let totalWallTime = 0; + let totalCpuTime = 0; + for (const segment of segments) { + for (const thread of model.getAllThreads()) { + totalCpuTime += thread.getCpuTimeForRange(segment.boundsRange); + totalWallTime += thread.getWallTimeForRange(segment.boundsRange); + } + } + histograms.createHistogram('cpu_wall_time_ratio', + tr.b.Unit.byName.unitlessNumber_biggerIsBetter, + totalWallTime ? totalCpuTime / totalWallTime : NaN, + { description: 'Ratio of total cpu-time vs. wall-time.', + summaryOptions: {}, + }); + } return { - addCpuUtilizationHistograms, - SUMMARY_OPTIONS, + addCpuSegmentCostHistograms, + addCpuWallTimeHistogram, }; }); </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 index 9c3e0d21def..a5aab4912fc 100644 --- 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 @@ -38,7 +38,7 @@ tr.b.unittest.testSuite(function() { { start: 5, end: 6, cpuStart: 1, cpuEnd: 2 })); }); const histograms = new tr.v.HistogramSet(); - tr.metrics.rendering.addCpuUtilizationHistograms( + tr.metrics.rendering.addCpuSegmentCostHistograms( histograms, model, [new tr.model.um.Segment(0, 10)], (thread, segment) => thread.getCpuTimeForRange(segment.boundsRange), category => `thread_${category}_cpu_time_per_frame`, 'description'); @@ -95,7 +95,7 @@ tr.b.unittest.testSuite(function() { { start: 5, end: 6, cpuStart: 1, cpuEnd: 2 })); }); const histograms = new tr.v.HistogramSet(); - tr.metrics.rendering.addCpuUtilizationHistograms( + tr.metrics.rendering.addCpuSegmentCostHistograms( histograms, model, [new tr.model.um.Segment(0, 5), new tr.model.um.Segment(5, 5)], (thread, segment) => thread.getCpuTimeForRange(segment.boundsRange), @@ -134,7 +134,7 @@ tr.b.unittest.testSuite(function() { { start: 5, end: 6, cpuStart: 1, cpuEnd: 2 })); }); const histograms = new tr.v.HistogramSet(); - tr.metrics.rendering.addCpuUtilizationHistograms( + tr.metrics.rendering.addCpuSegmentCostHistograms( histograms, model, [new tr.model.um.Segment(0, 10)], (thread, segment) => thread.getCpuTimeForRange(segment.boundsRange), category => `thread_${category}_cpu_time_per_frame`, 'description'); @@ -176,7 +176,7 @@ tr.b.unittest.testSuite(function() { { start: 5, end: 6, cpuStart: 1, cpuEnd: 2 })); }); const histograms = new tr.v.HistogramSet(); - tr.metrics.rendering.addCpuUtilizationHistograms( + tr.metrics.rendering.addCpuSegmentCostHistograms( histograms, model, [new tr.model.um.Segment(0, 10)], (thread, segment) => thread.getNumToplevelSlicesForRange(segment.boundsRange), diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/rendering/frame_segment_filter.html b/chromium/third_party/catapult/tracing/tracing/metrics/rendering/frame_segment_filter.html new file mode 100644 index 00000000000..9ebaa87d003 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/metrics/rendering/frame_segment_filter.html @@ -0,0 +1,112 @@ +<!DOCTYPE html> +<!-- +Copyright 2022 The Chromium Authors. All rights reserved. +Use 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/diagnostics/generic_set.html"> +<link rel="import" href="/tracing/value/diagnostics/related_event_set.html"> +<link rel="import" href="/tracing/value/histogram.html"> + +<script> +'use strict'; + +/** + * @fileoverview This file contains helper methods to filter Segments to just + * those for the displaying of frames. + */ +tr.exportTo('tr.metrics.rendering', function() { + // Various tracing events. + const DISPLAY_EVENT = 'BenchmarkInstrumentation::DisplayRenderingStats'; + const DRM_EVENT = 'DrmEventFlipComplete'; + const SURFACE_FLINGER_EVENT = 'vsync_before'; + + + // The least number of frames needed to report frame_times. + // Measurements done with very few frames tend to be unstable. + // See crbug.com/954984 for example. + const MIN_FRAME_COUNT = 10; + + + class FrameEvent { + constructor(event) { + this.event_ = event; + } + + get eventStart() { + return this.event_.start; + } + + get frameStart() { + if (this.event_.title !== DRM_EVENT) return this.event_.start; + const data = this.event_.args.data; + const TIME = tr.b.UnitScale.TIME; + return tr.b.convertUnit(data['vblank.tv_sec'], TIME.SEC, TIME.MILLI_SEC) + + tr.b.convertUnit( + data['vblank.tv_usec'], TIME.MICRO_SEC, TIME.MILLI_SEC); + } + + get event() { return this.event_; } + } + + + function getDisplayCompositorPresentationEvents_(model) { + const modelHelper = model.getOrCreateHelper( + tr.model.helpers.ChromeModelHelper); + 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 computeFrameSegments(model, segments) { + const events = getDisplayCompositorPresentationEvents_(model); + if (!events) return []; + // 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 frameEvents = events.map(e => new FrameEvent(e)); + const frameSegments = []; + for (const segment of segments) { + const filtered = segment.boundsRange.filterArray( + frameEvents, x => x.eventStart); + if (filtered.length < MIN_FRAME_COUNT) continue; + for (let i = 1; i < filtered.length; i++) { + const duration = filtered[i].frameStart - filtered[i - 1].frameStart; + frameSegments.push(new tr.model.um.Segment(filtered[i - 1].eventStart, duration)); + } + } + return frameSegments; + } + + return { + computeFrameSegments, + }; +}); +</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 index 386bd444658..aa690649f66 100644 --- a/chromium/third_party/catapult/tracing/tracing/metrics/rendering/queueing_duration.html +++ b/chromium/third_party/catapult/tracing/tracing/metrics/rendering/queueing_duration.html @@ -22,8 +22,8 @@ found in the LICENSE file. * 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? + * For OOPIF they might send a BeginMainFrame with the same ID, in those cases + * the earliest slice's time is used. */ tr.exportTo('tr.metrics.rendering', function() { // Various tracing events. @@ -38,7 +38,12 @@ tr.exportTo('tr.metrics.rendering', function() { 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}`); + // If we've already seen an even with this name, we only track the first. + // Multiple ThreadProxy::BeginMainFrames can occur when OOPIF share the + // same origin and get routed to the same RendererMain. + if (out.has(id) && out[id] <= slice.start) { + continue; + } out.set(id, slice.start); } return out; 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 index b7d292eec14..0944c00c049 100644 --- a/chromium/third_party/catapult/tracing/tracing/metrics/rendering/rendering_metric.html +++ b/chromium/third_party/catapult/tracing/tracing/metrics/rendering/rendering_metric.html @@ -6,7 +6,8 @@ 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/cpu_utilization.html"> +<link rel="import" href="/tracing/metrics/rendering/frame_segment_filter.html"> <link rel="import" href="/tracing/metrics/rendering/image_decode_time.html"> <link rel="import" href="/tracing/metrics/rendering/pixels.html"> <link rel="import" href="/tracing/metrics/rendering/queueing_duration.html"> @@ -28,19 +29,28 @@ tr.exportTo('tr.metrics.rendering', function() { segments = chromeHelper.telemetryHelper.animationSegments; } if (segments.length > 0) { - tr.metrics.rendering.addFrameTimeHistograms(histograms, model, segments); + const frameSegments = tr.metrics.rendering.computeFrameSegments(model, segments); + if (frameSegments.length > 0) { + tr.metrics.rendering.addCpuSegmentCostHistograms( + histograms, model, frameSegments, + (thread, segment) => thread.getCpuTimeForRange(segment.boundsRange), + category => `thread_${category}_cpu_time_per_frame`, + 'CPU cores of a thread group per frame'); + tr.metrics.rendering.addCpuSegmentCostHistograms( + histograms, model, frameSegments, + (thread, segment) => + thread.getNumToplevelSlicesForRange(segment.boundsRange), + category => `tasks_per_frame_${category}`, + 'Number of tasks of a thread group per frame'); + tr.metrics.rendering.addCpuWallTimeHistogram(histograms, model, frameSegments); + } + tr.metrics.rendering.addImageDecodeTimeHistograms(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, { 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 index 1d30078252a..8cdf4b42b5c 100644 --- 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 @@ -39,11 +39,11 @@ tr.b.unittest.testSuite(function() { // The gesture interaction record should be adjusted to [10, 30]. So, the // first two frames and the last frame are outside the interaction record // and should be discarded. The remaining frames are 11 to 29 which result - // in 9 frame times of 2. - 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); + // in 9 frames which have 1 task each. + const hist = histograms.getHistogramNamed('tasks_per_frame_total_all'); + assert.closeTo(1, hist.min, 1e-6); + assert.closeTo(1, hist.max, 1e-6); + assert.closeTo(1, hist.average, 1e-6); }); }); </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 index b40db0341b4..30f9dbe2cee 100644 --- 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 @@ -33,7 +33,7 @@ tr.b.unittest.testSuite(function() { // Add layout categories mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({ cat: 'blink', - title: 'HTMLDocumentParser::didReceiveParsedChunkFromBackgroundParser', + title: 'HTMLDocumentParser::DeferredPumpTokenizerIfPossible', start: 200, duration: 200, cpuStart: 1160, @@ -102,7 +102,7 @@ tr.b.unittest.testSuite(function() { assert.deepEqual({ total: 150, events: { - 'HTMLDocumentParser::didReceiveParsedChunkFromBackgroundParser': 150 + 'HTMLDocumentParser::DeferredPumpTokenizerIfPossible': 150 } }, breakdownTree.parseHTML); assert.deepEqual({ @@ -147,7 +147,7 @@ tr.b.unittest.testSuite(function() { assert.deepEqual({ total: 100, events: { - 'HTMLDocumentParser::didReceiveParsedChunkFromBackgroundParser': 100 + 'HTMLDocumentParser::DeferredPumpTokenizerIfPossible': 100 } }, breakdownTree.parseHTML); assert.deepEqual({ @@ -181,7 +181,7 @@ tr.b.unittest.testSuite(function() { assert.deepEqual({ total: 120, events: { - 'HTMLDocumentParser::didReceiveParsedChunkFromBackgroundParser': 120 + 'HTMLDocumentParser::DeferredPumpTokenizerIfPossible': 120 } }, breakdownTree.parseHTML); assert.deepEqual({ @@ -226,7 +226,7 @@ tr.b.unittest.testSuite(function() { assert.deepEqual({ total: 80, events: { - 'HTMLDocumentParser::didReceiveParsedChunkFromBackgroundParser': 80 + 'HTMLDocumentParser::DeferredPumpTokenizerIfPossible': 80 } }, breakdownTree.parseHTML); assert.deepEqual({ 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 index f8a63e84ba7..2d22034dbad 100644 --- 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 @@ -8,6 +8,7 @@ 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/diagnostics/alert_groups.html"> <link rel="import" href="/tracing/value/histogram.html"> <script> @@ -78,6 +79,7 @@ tr.exportTo('tr.metrics.sh', function() { cpuTimeHist.description = 'Percent CPU utilization, normalized against a single core. Can be ' + 'greater than 100% if machine has multiple cores.'; + cpuTimeHist.setAlertGrouping([tr.v.d.ALERT_GROUPS.CPU_USAGE]); cpuTimeHist.customizeSummaryOptions({ avg: true, count: false, 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 index d6884fa43c4..2c5ab4b2d03 100644 --- 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 @@ -18,6 +18,7 @@ found in the LICENSE file. <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/alert_groups.html"> <link rel="import" href="/tracing/value/diagnostics/diagnostic_map.html"> <link rel="import" href="/tracing/value/histogram.html"> @@ -186,7 +187,14 @@ tr.exportTo('tr.metrics.sh', function() { const frameIdRef = targetEvent.args.frame; const snapshot = findFrameLoaderSnapshotAt( rendererHelper, frameIdRef, targetEvent.start); - if (snapshot === undefined || !snapshot.args.isLoadingMainFrame) continue; + if (snapshot === undefined) continue; + // If isOutermostMainFrame is available, use it, if not use + // isLoadingMainFrame. + const isOutermostMainFrame = + (snapshot.args.isOutermostMainFrame !== undefined) ? + snapshot.args.isOutermostMainFrame : + snapshot.args.isLoadingMainFrame; + if (!isOutermostMainFrame) continue; const url = snapshot.args.documentLoaderURL; if (tr.e.chrome.CHROME_INTERNAL_URLS.includes(url)) continue; let navigationStartEvent; @@ -264,7 +272,7 @@ tr.exportTo('tr.metrics.sh', function() { return samples; } - function findLayoutShiftSamples(rendererHelper) { + function findMainFrameLayoutShiftSamples(rendererHelper) { let sample; EventFinderUtils.getSortedMainThreadEventsByFrame(rendererHelper, 'LayoutShift', 'loading').forEach((events) => { @@ -274,7 +282,34 @@ tr.exportTo('tr.metrics.sh', function() { sample = {value: evData.cumulative_score}; } }); - return sample ? [sample] : []; + // When there is no layout shift event on found on the page, CLS is 0. + return sample ? [sample] : [{value: 0}]; + } + + function findAllLayoutShiftSamples(chromeHelper) { + let total = 0; + let foundMainFrame = false; + for (const pid in chromeHelper.rendererHelpers) { + const rendererHelper = chromeHelper.rendererHelpers[pid]; + if (rendererHelper.isChromeTracingUI) continue; + + tr.e.chrome.EventFinderUtils.getSortedMainThreadEventsByFrame( + rendererHelper, 'LayoutShift', 'loading').forEach((events) => { + // Add up the scores for main frames and weighted scores for subframes. + for (const event of events) { + const evData = event.args.data; + if (evData.is_main_frame) { + total += evData.score; + foundMainFrame = true; + } else { + total += evData.weighted_score_delta; + } + } + }); + } + + // If no CLS is found, report value as 0 instead of null. + return foundMainFrame ? [{value: total}] : [{value: 0}]; } function addFirstMeaningfulPaintSample(samples, rendererHelper, @@ -487,7 +522,8 @@ tr.exportTo('tr.metrics.sh', function() { const largestTextPaintSamples = findLargestTextPaintSamples( rendererHelper, frameToNavStartEvents, navIdToNavStartEvents); - const layoutShiftSamples = findLayoutShiftSamples(rendererHelper); + const mainFrameLayoutShiftSamples = findMainFrameLayoutShiftSamples( + rendererHelper); const navigationStartSamples = timeToFCPEntries.map(entry => { return { value: entry.navigationStartEvent.start}; }); @@ -502,7 +538,7 @@ tr.exportTo('tr.metrics.sh', function() { firstViewportReadySamples, largestImagePaintSamples, largestTextPaintSamples, - layoutShiftSamples, + mainFrameLayoutShiftSamples, navigationStartSamples, }; } @@ -568,40 +604,6 @@ tr.exportTo('tr.metrics.sh', function() { 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)), - }); - } } } @@ -611,18 +613,21 @@ tr.exportTo('tr.metrics.sh', function() { binBoundaries: LOADING_METRIC_BOUNDARIES, description: 'time to first paint', summaryOptions: SUMMARY_OPTIONS, + alertGrouping: [tr.v.d.ALERT_GROUPS.LOADING_PAINT], }); const firstContentfulPaintHistogram = histograms.createHistogram( 'timeToFirstContentfulPaint', timeDurationInMs_smallerIsBetter, [], { binBoundaries: LOADING_METRIC_BOUNDARIES, description: 'time to first contentful paint', summaryOptions: SUMMARY_OPTIONS, + alertGrouping: [tr.v.d.ALERT_GROUPS.LOADING_PAINT], }); const firstContentfulPaintCpuTimeHistogram = histograms.createHistogram( 'cpuTimeToFirstContentfulPaint', timeDurationInMs_smallerIsBetter, [], { binBoundaries: LOADING_METRIC_BOUNDARIES, description: 'CPU time to first contentful paint', summaryOptions: SUMMARY_OPTIONS, + alertGrouping: [tr.v.d.ALERT_GROUPS.LOADING_PAINT], }); const onLoadHistogram = histograms.createHistogram( 'timeToOnload', timeDurationInMs_smallerIsBetter, [], { @@ -636,30 +641,36 @@ tr.exportTo('tr.metrics.sh', function() { binBoundaries: LOADING_METRIC_BOUNDARIES, description: 'time to first meaningful paint', summaryOptions: SUMMARY_OPTIONS, + alertGrouping: [tr.v.d.ALERT_GROUPS.LOADING_PAINT], }); const firstMeaningfulPaintCpuTimeHistogram = histograms.createHistogram( 'cpuTimeToFirstMeaningfulPaint', timeDurationInMs_smallerIsBetter, [], { binBoundaries: LOADING_METRIC_BOUNDARIES, description: 'CPU time to first meaningful paint', summaryOptions: SUMMARY_OPTIONS, + alertGrouping: [tr.v.d.ALERT_GROUPS.LOADING_PAINT], }); const timeToInteractiveHistogram = histograms.createHistogram( 'timeToInteractive', timeDurationInMs_smallerIsBetter, [], { binBoundaries: TIME_TO_INTERACTIVE_BOUNDARIES, description: 'Time to Interactive', summaryOptions: SUMMARY_OPTIONS, + alertGrouping: [tr.v.d.ALERT_GROUPS.LOADING_PAINT], + alertGrouping: [tr.v.d.ALERT_GROUPS.LOADING_INTERACTIVITY], }); const totalBlockingTimeHistogram = histograms.createHistogram( 'totalBlockingTime', timeDurationInMs_smallerIsBetter, [], { binBoundaries: TIME_TO_INTERACTIVE_BOUNDARIES, description: 'Total Blocking Time', summaryOptions: SUMMARY_OPTIONS, + alertGrouping: [tr.v.d.ALERT_GROUPS.LOADING_INTERACTIVITY], }); const timeToFirstCpuIdleHistogram = histograms.createHistogram( 'timeToFirstCpuIdle', timeDurationInMs_smallerIsBetter, [], { binBoundaries: TIME_TO_INTERACTIVE_BOUNDARIES, description: 'Time to First CPU Idle', summaryOptions: SUMMARY_OPTIONS, + alertGrouping: [tr.v.d.ALERT_GROUPS.LOADING_INTERACTIVITY], }); const aboveTheFoldLoadedToVisibleHistogram = histograms.createHistogram( 'aboveTheFoldLoadedToVisible', timeDurationInMs_smallerIsBetter, [], { @@ -690,12 +701,21 @@ tr.exportTo('tr.metrics.sh', function() { binBoundaries: LOADING_METRIC_BOUNDARIES, description: 'Time to Largest Contentful Paint', summaryOptions: SUMMARY_OPTIONS, + alertGrouping: [tr.v.d.ALERT_GROUPS.LOADING_PAINT], }); - const layoutShiftHistogram = histograms.createHistogram( + const mainFrameLayoutShiftHistogram = histograms.createHistogram( 'mainFrameCumulativeLayoutShift', unitlessNumber_smallerIsBetter, [], { binBoundaries: LAYOUT_SHIFT_SCORE_BOUNDARIES, description: 'Main Frame Document Cumulative Layout Shift Score', summaryOptions: SUMMARY_OPTIONS, + alertGrouping: [tr.v.d.ALERT_GROUPS.LOADING_LAYOUT], + }); + const allLayoutShiftHistogram = histograms.createHistogram( + 'overallCumulativeLayoutShift', unitlessNumber_smallerIsBetter, [], { + binBoundaries: LAYOUT_SHIFT_SCORE_BOUNDARIES, + description: 'Document Cumulative Layout Shift Score with iframes', + summaryOptions: SUMMARY_OPTIONS, + alertGrouping: [tr.v.d.ALERT_GROUPS.LOADING_LAYOUT], }); const navigationStartHistogram = histograms.createHistogram( 'navigationStart', timeDurationInMs_smallerIsBetter, [], { @@ -707,6 +727,20 @@ tr.exportTo('tr.metrics.sh', function() { const chromeHelper = model.getOrCreateHelper( tr.model.helpers.ChromeModelHelper); + + // Layout Shift and LCP are reported in the browser process, so we do not + // need to loop over the renderers to add samples. + const allLayoutShiftSamples = findAllLayoutShiftSamples(chromeHelper); + addSamplesToHistogram( + allLayoutShiftSamples, + allLayoutShiftHistogram, + histograms); + + const lcpSamples = findLargestContentfulPaintHistogramSamples( + chromeHelper.browserHelper.mainThread.sliceGroup.slices); + addSamplesToHistogram( + lcpSamples, largestContentfulPaintHistogram, histograms); + for (const pid in chromeHelper.rendererHelpers) { const rendererHelper = chromeHelper.rendererHelpers[pid]; if (rendererHelper.isChromeTracingUI) continue; @@ -714,11 +748,6 @@ tr.exportTo('tr.metrics.sh', function() { const samplesSet = collectLoadingMetricsForRenderer(rendererHelper); - const lcpSamples = findLargestContentfulPaintHistogramSamples( - chromeHelper.browserHelper.mainThread.sliceGroup.slices); - addSamplesToHistogram( - lcpSamples, largestContentfulPaintHistogram, histograms); - addSamplesToHistogram( samplesSet.firstPaintSamples, firstPaintHistogram, histograms); addSamplesToHistogram( @@ -748,7 +777,9 @@ tr.exportTo('tr.metrics.sh', function() { largestTextPaintHistogram, histograms); addSamplesToHistogram( - samplesSet.layoutShiftSamples, layoutShiftHistogram, histograms); + samplesSet.mainFrameLayoutShiftSamples, + mainFrameLayoutShiftHistogram, + histograms); addSamplesToHistogram( samplesSet.navigationStartSamples, navigationStartHistogram, 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 index f27187f5cb0..8d7f4eb53ef 100644 --- 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 @@ -30,6 +30,7 @@ tr.b.unittest.testSuite(function() { })); rendererProcess.objects.addSnapshot('ptr', 'loading', 'FrameLoader', 300, { + isOutermostMainFrame: true, isLoadingMainFrame: true, frame: {id_ref: '0xdeadbeef'}, documentLoaderURL: 'http://example.com' @@ -63,6 +64,7 @@ tr.b.unittest.testSuite(function() { })); rendererProcess.objects.addSnapshot('ptr', 'loading', 'FrameLoader', 300, { + isOutermostMainFrame: true, isLoadingMainFrame: true, frame: {id_ref: '0xdeadbeef'}, documentLoaderURL: 'http://example.com' @@ -88,15 +90,6 @@ tr.b.unittest.testSuite(function() { 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('timeToLargestImagePaint', function() { @@ -112,6 +105,7 @@ tr.b.unittest.testSuite(function() { })); rendererProcess.objects.addSnapshot('ptr', 'loading', 'FrameLoader', 300, { + isOutermostMainFrame: true, isLoadingMainFrame: true, frame: {id_ref: '0xdeadbeef'}, documentLoaderURL: 'http://example.com' @@ -147,6 +141,7 @@ tr.b.unittest.testSuite(function() { })); rendererProcess.objects.addSnapshot('ptr', 'loading', 'FrameLoader', 300, { + isOutermostMainFrame: true, isLoadingMainFrame: true, frame: {id_ref: '0xdeadbeef'}, documentLoaderURL: 'http://example.com' @@ -190,6 +185,7 @@ tr.b.unittest.testSuite(function() { })); rendererProcess.objects.addSnapshot('ptr', 'loading', 'FrameLoader', 300, { + isOutermostMainFrame: true, isLoadingMainFrame: true, frame: {id_ref: '0xdeadbeef'}, documentLoaderURL: 'http://example.com' @@ -232,6 +228,7 @@ tr.b.unittest.testSuite(function() { })); rendererProcess.objects.addSnapshot('ptr', 'loading', 'FrameLoader', 300, { + isOutermostMainFrame: true, isLoadingMainFrame: true, frame: {id_ref: '0xdeadbeef'}, documentLoaderURL: 'http://example.com' @@ -275,6 +272,45 @@ tr.b.unittest.testSuite(function() { assert.strictEqual(100, hist.running.mean); }); + test('timeToLargestContentfulPaint_NoMultipleReporting', function() { + // Test that we do not report the same sample value multiple times if there + // is more than one renderer. + const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) { + const browerMain = model.browserMain; + browerMain.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({ + cat: 'loading', + title: tr.e.chrome.LCP_CANDIDATE_EVENT_TITLE, + start: 400, + duration: 0.0, + args: { + data: { durationInMilliseconds: 100, type: 'text', size: 1000, + inMainFrame: true }, + main_frame_tree_node_id: 12345, + } + })); + + const rendererProcess1 = model.rendererProcess; + const mainThread1 = model.rendererMain; + addNavigationStart_(mainThread1, 50, + { isOutermostMainFrame: true, isLoadingMainFrame: true, + documentLoaderURL: 'http://example.com'}); + + const rendererProcess2 = model.getOrCreateProcess(10); + const mainThread2 = rendererProcess2.getOrCreateThread(10); + mainThread2.name = 'CrRendererMain'; + + addNavigationStart_(mainThread2, 100, + { isOutermostMainFrame: true, isLoadingMainFrame: true, + documentLoaderURL: 'http://example.com'}); + }); + + const histograms = new tr.v.HistogramSet(); + tr.metrics.sh.loadingMetric(histograms, model); + const hist = histograms.getHistogramNamed('largestContentfulPaint'); + assert.strictEqual(1, hist.running.count); + assert.strictEqual(100, hist.running.mean); + }); + test('timeToLargestContentfulPaint_LastCandidate', function() { const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) { const browerMain = model.browserMain; @@ -452,6 +488,7 @@ tr.b.unittest.testSuite(function() { })); rendererProcess.objects.addSnapshot('ptr', 'loading', 'FrameLoader', 300, { + isOutermostMainFrame: true, isLoadingMainFrame: true, frame: {id_ref: '0xdeadbeef'}, documentLoaderURL: 'http://example.com' @@ -504,6 +541,7 @@ tr.b.unittest.testSuite(function() { })); rendererProcess.objects.addSnapshot('ptr', 'loading', 'FrameLoader', 300, { + isOutermostMainFrame: true, isLoadingMainFrame: true, frame: {id_ref: 'firstframeid'}, documentLoaderURL: 'http://example.com' @@ -526,6 +564,7 @@ tr.b.unittest.testSuite(function() { })); rendererProcess.objects.addSnapshot('ptr', 'loading', 'FrameLoader', 2300, { + isOutermostMainFrame: true, isLoadingMainFrame: true, frame: {id_ref: 'secondframeid'}, documentLoaderURL: 'http://example.com' @@ -636,6 +675,7 @@ tr.b.unittest.testSuite(function() { })); rendererProcess.objects.addSnapshot('ptr', 'loading', 'FrameLoader', 300, { + isOutermostMainFrame: true, isLoadingMainFrame: true, frame: {id_ref: '0xdeadbeef'}, documentLoaderURL: 'http://example.com' @@ -664,6 +704,7 @@ tr.b.unittest.testSuite(function() { })); rendererProcess.objects.addSnapshot('ptr', 'loading', 'FrameLoader', 2100, { + isOutermostMainFrame: true, isLoadingMainFrame: true, frame: {id_ref: '0xdeadbeef'}, documentLoaderURL: 'http://example.com' @@ -696,6 +737,7 @@ tr.b.unittest.testSuite(function() { })); rendererProcess.objects.addSnapshot('ptr', 'loading', 'FrameLoader', 300, { + isOutermostMainFrame: true, isLoadingMainFrame: true, frame: {id_ref: '0xdeadbeef'}, documentLoaderURL: 'http://example.com' @@ -745,6 +787,7 @@ tr.b.unittest.testSuite(function() { })); rendererProcess.objects.addSnapshot('ptr', 'loading', 'FrameLoader', 300, { + isOutermostMainFrame: true, isLoadingMainFrame: true, frame: {id_ref: '0xdeadbeef'}, documentLoaderURL: 'http://example.com' @@ -770,15 +813,6 @@ tr.b.unittest.testSuite(function() { 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() { @@ -794,6 +828,7 @@ tr.b.unittest.testSuite(function() { })); rendererProcess.objects.addSnapshot('ptr', 'loading', 'FrameLoader', 300, { + isOutermostMainFrame: true, isLoadingMainFrame: true, frame: {id_ref: '0xdeadbeef'}, documentLoaderURL: 'http://example.com' @@ -842,6 +877,7 @@ tr.b.unittest.testSuite(function() { })); rendererProcess.objects.addSnapshot('ptr', 'loading', 'FrameLoader', 300, { + isOutermostMainFrame: true, isLoadingMainFrame: true, frame: {id_ref: '0xdeadbeef'}, documentLoaderURL: 'http://example.com' @@ -904,6 +940,7 @@ tr.b.unittest.testSuite(function() { })); rendererProcess.objects.addSnapshot('ptr', 'loading', 'FrameLoader', 300, { + isOutermostMainFrame: true, isLoadingMainFrame: true, frame: {id_ref: '0xdeadbeef'}, documentLoaderURL: 'http://example.com' @@ -973,6 +1010,7 @@ tr.b.unittest.testSuite(function() { })); rendererProcess1.objects.addSnapshot('ptr', 'loading', 'FrameLoader', 300, { + isOutermostMainFrame: true, isLoadingMainFrame: true, frame: {id_ref: '0xdeadbeef'}, documentLoaderURL: 'http://example.com' @@ -1009,6 +1047,7 @@ tr.b.unittest.testSuite(function() { })); rendererProcess2.objects.addSnapshot('ptr', 'loading', 'FrameLoader', 300, { + isOutermostMainFrame: true, isLoadingMainFrame: true, frame: {id_ref: '0xdeadbeef'}, documentLoaderURL: 'http://example.com' @@ -1046,6 +1085,7 @@ tr.b.unittest.testSuite(function() { function addFrameLoaderObject_(rendererProcess, timestamp) { rendererProcess.objects.addSnapshot( 'ptr', 'loading', 'FrameLoader', timestamp, { + isOutermostMainFrame: true, isLoadingMainFrame: true, frame: {id_ref: '0xdeadbeef'}, documentLoaderURL: 'http://example.com', }); @@ -1119,13 +1159,15 @@ tr.b.unittest.testSuite(function() { const mainThread = model.rendererMain; addNavigationStart_(mainThread, 200, - {isLoadingMainFrame: true, documentLoaderURL: 'http://example.com'}); + { isOutermostMainFrame: true, 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, { + isOutermostMainFrame: true, isLoadingMainFrame: true, frame: {id_ref: '0xdeadbeef'}, documentLoaderURL: 'http://example.com', }); @@ -1136,7 +1178,8 @@ tr.b.unittest.testSuite(function() { // New navigation to close the search window. addNavigationStart_(mainThread, 7000, - {isLoadingMainFrame: true, documentLoaderURL: 'http://example.com'}); + { isOutermostMainFrame: true, isLoadingMainFrame: true, + documentLoaderURL: 'http://example.com'}); }); const histograms = new tr.v.HistogramSet(); @@ -1401,6 +1444,7 @@ tr.b.unittest.testSuite(function() { // DomContentLoadedEnd and NavigationStart for a different frame. rendererProcess.objects.addSnapshot( 'ptr', 'loading', 'FrameLoader', 4000, { + isOutermostMainFrame: true, isLoadingMainFrame: true, frame: {id_ref: '0xffffffff'}, documentLoaderURL: 'http://example.com' }); @@ -1474,6 +1518,7 @@ tr.b.unittest.testSuite(function() { })); process.objects.addSnapshot('ptr', 'loading', 'FrameLoader', 300, { + isOutermostMainFrame: true, isLoadingMainFrame: true, frame: {id_ref: '0xdeadbeef'}, documentLoaderURL: 'http://example.com' @@ -1600,6 +1645,32 @@ tr.b.unittest.testSuite(function() { partialNetworkEvents); }); + test('noLayoutShiftEventMeansZeroCLS', 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: 10, + duration: 510, + args: {frame: '0xdeadbeef'} + })); + }); + const histograms = new tr.v.HistogramSet(); + tr.metrics.sh.loadingMetric(histograms, model); + const mainHist = + histograms.getHistogramNamed('mainFrameCumulativeLayoutShift'); + assert.strictEqual(1, mainHist.sampleValues.length); + assert.strictEqual(1, mainHist.running.count); + assert.strictEqual(0, mainHist.running.mean); + const overallHist = + histograms.getHistogramNamed('overallCumulativeLayoutShift'); + assert.strictEqual(1, overallHist.sampleValues.length); + assert.strictEqual(1, overallHist.running.count); + assert.strictEqual(0, overallHist.running.mean); + }); + test('mainFrameCumulativeLayoutShift', function() { const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) { const rendererProcess = model.rendererProcess; @@ -1632,6 +1703,44 @@ tr.b.unittest.testSuite(function() { assert.strictEqual(3.5, hist.running.mean); }); + test('overallCumulativeLayoutShift', function() { + const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) { + const rendererProcess = model.rendererProcess; + const mainThread = model.rendererMain; + const mainFrame = {id: '0xdeadbeef', is_main: true}; + const subframe = {id: '0xdeadb33f', is_main: false}; + const emitEvent = (thread, time, score, weightedScore, frame) => { + const data = { + is_main_frame: frame.is_main, + score, + weighted_score_delta: weightedScore + }; + thread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({ + cat: 'loading', + title: 'LayoutShift', + start: time, + duration: 0.0, + args: {frame: frame.id, data} + })); + }; + emitEvent(mainThread, 1000, 1.5, 1000, mainFrame); + emitEvent(mainThread, 3000, 3.5, 1000, mainFrame); + emitEvent(mainThread, 2000, 3.0, 1000, mainFrame); + emitEvent(mainThread, 4000, 4000, 4.0, subframe); + const rendererProcess2 = model.getOrCreateProcess(10); + const mainThread2 = rendererProcess2.getOrCreateThread(20); + mainThread2.name = 'CrRendererMain'; + const subframe2 = { id: '0xdeadb33f', is_main: false }; + emitEvent(mainThread2, 2500, 1000, 2.0, subframe2); + }); + const histograms = new tr.v.HistogramSet(); + tr.metrics.sh.loadingMetric(histograms, model); + const hist = histograms.getHistogramNamed('overallCumulativeLayoutShift'); + assert.strictEqual(1, hist.sampleValues.length); + assert.strictEqual(1, hist.running.count); + assert.strictEqual(14, hist.running.mean); + }); + test('speedIndexIsAddedToHistograms', function() { // The speed index code is fully tested in // rects_based_speed_index_metric_test. 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 index fea57533ba6..188000809ea 100644 --- 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 @@ -390,6 +390,28 @@ tr.exportTo('tr.metrics.sh', function() { } }; + const MAX_ALLOCATED_SIZE = { + name: 'max_allocated_size', + unit: sizeInBytes_smallerIsBetter, + buildDescriptionPrefix(componentPath, processName) { + return buildChromeValueDescriptionPrefix(componentPath, processName, { + userFriendlyPropertyName: 'max size of all allocations', + componentPreposition: 'of' + }); + } + }; + + const MAX_COMMITTED_SIZE = { + name: 'max_committed_size', + unit: sizeInBytes_smallerIsBetter, + buildDescriptionPrefix(componentPath, processName) { + return buildChromeValueDescriptionPrefix(componentPath, processName, { + userFriendlyPropertyName: 'max size of all committed memory', + componentPreposition: 'of' + }); + } + }; + const SHIM_ALLOCATED_OBJECTS_SIZE = { name: 'shim_allocated_objects_size', unit: sizeInBytes_smallerIsBetter, @@ -451,6 +473,8 @@ tr.exportTo('tr.metrics.sh', function() { EFFECTIVE_SIZE, ALLOCATED_OBJECTS_SIZE, SHIM_ALLOCATED_OBJECTS_SIZE, + MAX_ALLOCATED_SIZE, + MAX_COMMITTED_SIZE, LOCKED_SIZE, PEAK_SIZE ]; 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 index e3a6c40d327..6cac038fad7 100644 --- 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 @@ -397,6 +397,8 @@ tr.b.unittest.testSuite(function() { size: 8, allocated_objects_size: 4, shim_allocated_objects_size: 3, + max_allocated_size: 5, + max_committed_size: 8 }}) ]; pmdBrowser1.totals = { @@ -565,7 +567,9 @@ tr.b.unittest.testSuite(function() { newAllocatorDump(pmdBrowser3, 'malloc', {numerics: { size: 8000, allocated_objects_size: 4000, - shim_allocated_objects_size: 3000 + shim_allocated_objects_size: 3000, + max_allocated_size: 4000, + max_committed_size: 8000 }}) ]; const pmdRendererB3 = addProcessMemoryDump(gmd3, pRendererB, {ts: 61}); @@ -656,6 +660,18 @@ tr.b.unittest.testSuite(function() { description: 'total size of all allocated objects reported by Chrome ' + 'for all processes in Chrome' }, + 'memory:chrome:all_processes:reported_by_chrome:max_allocated_size': { + value: [5, 0, 4000, 0], + unit: sizeInBytes_smallerIsBetter, + description: 'total max size of all allocations reported by Chrome ' + + 'for all processes in Chrome' + }, + 'memory:chrome:all_processes:reported_by_chrome:max_committed_size': { + value: [8, 0, 8000, 0], + unit: sizeInBytes_smallerIsBetter, + description: 'total max size of all committed memory 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], @@ -765,6 +781,18 @@ tr.b.unittest.testSuite(function() { description: 'size of all objects allocated through shim by malloc in ' + 'all processes in Chrome' }, + 'memory:chrome:all_processes:reported_by_chrome:malloc:max_allocated_size': { + value: [5, 0, 4000, 0], + unit: sizeInBytes_smallerIsBetter, + description: 'max size of all allocations of malloc ' + + 'in all processes in Chrome' + }, + 'memory:chrome:all_processes:reported_by_chrome:malloc:max_committed_size': { + value: [8, 0, 8000, 0], + unit: sizeInBytes_smallerIsBetter, + description: 'max size of all committed memory of malloc ' + + 'in all processes in Chrome' + }, 'memory:chrome:all_processes:reported_by_chrome:malloc:allocated_objects_size': { value: [4, 40 + 750, 4000, 0], @@ -994,6 +1022,18 @@ tr.b.unittest.testSuite(function() { 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:max_allocated_size': { + value: [5, 0, 4000, 0], + unit: sizeInBytes_smallerIsBetter, + description: 'total max size of all allocations reported by Chrome ' + + 'for the browser process in Chrome' + }, + 'memory:chrome:browser_process:reported_by_chrome:max_committed_size': { + value: [8, 0, 8000, 0], + unit: sizeInBytes_smallerIsBetter, + description: 'total max size of all committed memory 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, @@ -1020,6 +1060,18 @@ tr.b.unittest.testSuite(function() { description: 'size of all objects allocated through shim by malloc in ' + 'the browser process in Chrome' }, + 'memory:chrome:browser_process:reported_by_chrome:malloc:max_allocated_size': { + value: [5, 0, 4000, 0], + unit: sizeInBytes_smallerIsBetter, + description: 'max size of all allocations of malloc ' + + 'in the browser process in Chrome' + }, + 'memory:chrome:browser_process:reported_by_chrome:malloc:max_committed_size': { + value: [8, 0, 8000, 0], + unit: sizeInBytes_smallerIsBetter, + description: 'max size of all committed memory of 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, diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/system_health/persecond_metric_test.html b/chromium/third_party/catapult/tracing/tracing/metrics/system_health/persecond_metric_test.html index f57d06da7dd..b34e7399322 100644 --- a/chromium/third_party/catapult/tracing/tracing/metrics/system_health/persecond_metric_test.html +++ b/chromium/third_party/catapult/tracing/tracing/metrics/system_health/persecond_metric_test.html @@ -29,6 +29,7 @@ tr.b.unittest.testSuite(function() { })); rendererProcess.objects.addSnapshot('ptr', 'loading', 'FrameLoader', 300, { + isOutermostMainFrame: true, isLoadingMainFrame: true, frame: {id_ref: '0xdeadbeef'}, documentLoaderURL: 'http://example.com' @@ -112,6 +113,7 @@ tr.b.unittest.testSuite(function() { })); rendererProcess.objects.addSnapshot('ptr', 'loading', 'FrameLoader', 300, { + isOutermostMainFrame: true, isLoadingMainFrame: true, frame: {id_ref: '0xdeadbeef'}, documentLoaderURL: 'http://example.com' @@ -164,6 +166,7 @@ tr.b.unittest.testSuite(function() { })); rendererProcess.objects.addSnapshot('ptr', 'loading', 'FrameLoader', 300, { + isOutermostMainFrame: true, isLoadingMainFrame: true, frame: {id_ref: '0xdeadbeef'}, documentLoaderURL: 'http://example.com' @@ -188,6 +191,7 @@ tr.b.unittest.testSuite(function() { })); rendererProcess.objects.addSnapshot('ptr2', 'loading', 'FrameLoader', 1200, { + isOutermostMainFrame: true, isLoadingMainFrame: true, frame: {id_ref: '0xlivebeef'}, documentLoaderURL: 'http://example.com' @@ -240,6 +244,7 @@ tr.b.unittest.testSuite(function() { })); rendererProcess.objects.addSnapshot('ptr', 'loading', 'FrameLoader', 300, { + isOutermostMainFrame: true, isLoadingMainFrame: true, frame: {id_ref: '0xdeadbeef'}, documentLoaderURL: 'http://example.com' @@ -295,6 +300,7 @@ tr.b.unittest.testSuite(function() { })); rendererProcess.objects.addSnapshot('ptr', 'loading', 'FrameLoader', 300, { + isOutermostMainFrame: true, isLoadingMainFrame: true, frame: {id_ref: '0xdeadbeef'}, documentLoaderURL: 'http://example.com' diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/system_health/weblayer_startup_metric.html b/chromium/third_party/catapult/tracing/tracing/metrics/system_health/weblayer_startup_metric.html new file mode 100644 index 00000000000..914f4cef743 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/metrics/system_health/weblayer_startup_metric.html @@ -0,0 +1,54 @@ +<!DOCTYPE html> +<!-- +Copyright 2020 The Chromium Authors. All rights reserved. +Use 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 weblayerStartupMetric(histograms, model) { + const startupWallHist = new tr.v.Histogram('weblayer_startup_wall_time', + tr.b.Unit.byName.timeDurationInMs_smallerIsBetter); + startupWallHist.description = 'WebLayer startup wall time'; + const loadWallHist = new tr.v.Histogram('weblayer_url_load_wall_time', + tr.b.Unit.byName.timeDurationInMs_smallerIsBetter); + loadWallHist.description = 'WebLayer blank URL load wall 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; + + // WebLayerStartupInterval is the title of the section of code that is + // entered (via android.os.Trace.beginSection) when WebLayer is started + // up. This value is defined in TelemetryActivity.java. + if (slice.title === 'WebLayerStartupInterval') { + startupWallHist.addSample(slice.duration); + } + + // WebLayerBlankUrlLoadInterval is the title of the section of code + // that is entered (via android.os.Trace.beginSection) when WebLayer + // is started up. This value is defined in TelemetryActivity.java. + if (slice.title === 'WebLayerBlankUrlLoadInterval') { + loadWallHist.addSample(slice.duration); + } + } + + histograms.addHistogram(startupWallHist); + histograms.addHistogram(loadWallHist); + } + + tr.metrics.MetricRegistry.register(weblayerStartupMetric); + + return { + weblayerStartupMetric, + }; +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/system_health/weblayer_startup_metric_test.html b/chromium/third_party/catapult/tracing/tracing/metrics/system_health/weblayer_startup_metric_test.html new file mode 100644 index 00000000000..acc5a8dc1c1 --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/metrics/system_health/weblayer_startup_metric_test.html @@ -0,0 +1,46 @@ +<!DOCTYPE html> +<!-- +Copyright 2020 The Chromium Authors. All rights reserved. +Use 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/weblayer_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: 'weblayer', + title: 'WebLayerStartupInterval', + start: 200, + duration: 42.0, + cpuStart: 150, + cpuDuration: 32.0 + })); + mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({ + cat: 'weblayer', + title: 'WebLayerBlankUrlLoadInterval', + start: 250, + duration: 27.0, + cpuStart: 190, + cpuDuration: 17.0 + })); + }); + } + + test('weblayerStartupMetric', function() { + const histograms = new tr.v.HistogramSet(); + tr.metrics.sh.weblayerStartupMetric(histograms, makeTestModel()); + assert.closeTo(42, histograms.getHistogramNamed( + 'weblayer_startup_wall_time').average, 1e-2); + assert.closeTo(27, histograms.getHistogramNamed( + 'weblayer_url_load_wall_time').average, 1e-2); + }); +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/uma_metric.html b/chromium/third_party/catapult/tracing/tracing/metrics/uma_metric.html index 8f741b9cf7f..959006e9749 100644 --- a/chromium/third_party/catapult/tracing/tracing/metrics/uma_metric.html +++ b/chromium/third_party/catapult/tracing/tracing/metrics/uma_metric.html @@ -20,15 +20,12 @@ found in the LICENSE file. * * UMA histograms are logged in trace events titled 'UMAHistogramSamples'. The * event arguments contain the histogram name and the base-64 coded of an - * snapshot of histogram samples serialized in a pickle. + * snapshot of histogram samples serialized in a pickle. These are emitted at + * the end of tracing, and represent the difference in the UMA histograms from + * when the tracing began. * * If there are several processes that have snapshots of the same histogram, * the snapshots will be merged. - * - * If there are two snapshots of the same histogram in the same process, we - * assume that the first snapshot is taken when tracing started and the second - * snapshot is taken when tracing is stopped. So, we compute the difference to - * show the samples added during the tracing session. */ tr.exportTo('tr.metrics', function() { function parseBuckets_(event, processName) { @@ -78,36 +75,16 @@ tr.exportTo('tr.metrics', function() { } } - function subtractBins_(x, y) { - x.sum -= y.sum; - let p1 = 0; - let p2 = 0; - while (p2 < y.bins.length) { - while (p1 < x.bins.length && x.bins[p1].min !== y.bins[p2].min) { - p1++; - } - if (p1 === x.bins.length) throw new Error('Cannot subtract'); - if (x.bins[p1].max !== y.bins[p2].max) { - throw new Error('Incompatible bins'); - } - if (x.bins[p1].count < y.bins[p2].count) { - throw new Error('Cannot subtract'); - } - x.bins[p1].count -= y.bins[p2].count; - for (const event of y.bins[p2].events) { - x.bins[p1].events.add(event); - } - const processName = tr.b.getOnlyElement(x.bins[p1].processes)[0]; - x.bins[p1].processes.set(processName, x.bins[p1].count); - p2++; - } - } - function getHistogramUnit_(name) { // Customize histogram units here. return tr.b.Unit.byName.unitlessNumber_smallerIsBetter; } + function getIsHistogramBinsLinear_(histogramName) { + return histogramName.startsWith('Graphics.Smoothness.Throughput') || + histogramName.startsWith('Memory.Memory.GPU.PeakMemoryUsage'); + } + function getHistogramBoundaries_(name) { // Customize histogram boundaries here. Ideally, this would not be // necessary. @@ -150,13 +127,10 @@ tr.exportTo('tr.metrics', function() { if (!histogramValues.has(name)) histogramValues.set(name, values); const endValues = parseBuckets_(events[events.length - 1], processName); if (events.length === 1) { - mergeBins_(values, endValues); - } else if (events.length === 2) { - subtractBins_(endValues, parseBuckets_(events[0], processName)); - mergeBins_(values, endValues); + mergeBins_(values, endValues, name); } else { - throw new Error('There should be at most two snapshots of an UMA ' + - 'histogram in each process'); + throw new Error('There should be at most one snapshot of UMA ' + + `histogram for ${name} in each process.`); } } } @@ -164,6 +138,7 @@ tr.exportTo('tr.metrics', function() { for (const [name, values] of histogramValues) { const histogram = new tr.v.Histogram( name, getHistogramUnit_(name), getHistogramBoundaries_(name)); + const isLinear = getIsHistogramBinsLinear_(name); // If we just put samples at the middle of the bins, their sum may not // match the sum we read from traces. Compute how much samples should be // shifted so that their sum matches what we expect. @@ -173,16 +148,45 @@ tr.exportTo('tr.metrics', function() { sumOfMiddles += bin.count * (bin.min + bin.max) / 2; sumOfBinLengths += bin.count * (bin.max - bin.min); } + + if (name.startsWith('CompositorLatency.Type')) { + let histogramBoundaries = tr.v.HistogramBinBoundaries.createLinear(0, 100, 101); + let histogramUnit = getHistogramUnit_(name); + let presentedCount = values.bins[0] ? values.bins[0].count : 0; + let delayedCount = values.bins[1] ? values.bins[1].count : 0; + let droppedCount = values.bins[2] ? values.bins[2].count : 0; + let inTimeCount = presentedCount - delayedCount; + let totalCount = presentedCount + droppedCount; + + const inTimeHistogram = new tr.v.Histogram( + name+'.Percentage_of_in_time_frames', histogramUnit, histogramBoundaries); + inTimeHistogram.addSample(100.0 * inTimeCount / totalCount); + histograms.addHistogram(inTimeHistogram); + + const delayedHistogram = new tr.v.Histogram( + name+'.Percentage_of_delayed_frames', histogramUnit, histogramBoundaries); + delayedHistogram.addSample(100.0 * delayedCount / totalCount); + histograms.addHistogram(delayedHistogram); + + const droppedHistogram = new tr.v.Histogram( + name+'.Percentage_of_dropped_frames', histogramUnit, histogramBoundaries); + droppedHistogram.addSample(100.0 * droppedCount / totalCount); + histograms.addHistogram(droppedHistogram); + } + const shift = (values.sum - sumOfMiddles) / sumOfBinLengths; - // Note: if shift is less than -0.5, it means that even if we put all - // samples at the lowest value of their bins their sum will be less than - // the sum we read from traces. So, there is an inconsistency: either the - // bins are reported incorrectly, or the sum is reported incorrectly. + // Note: for linear bins, if shift is less than -0.5, it means that even + // if we put all samples at the lowest value of their bins their sum will + // be less than the sum we read from traces. So, there is an + // inconsistency: either the bins are reported incorrectly, or the sum is + // reported incorrectly. // // Similarly, if shift is greater than 0.5, the sum of samples cannot add // up to the sum we read from traces, even if we put all samples at the // highest value of their bins. - if (Math.abs(shift) > 0.5) throw new Error('Samples sum is wrong'); + if (isLinear && Math.abs(shift) > 0.5) { + throw new Error(`Samples sum is wrong for ${name}.`); + } for (const bin of values.bins) { if (bin.count === 0) continue; diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/uma_metric_test.html b/chromium/third_party/catapult/tracing/tracing/metrics/uma_metric_test.html index ae512c6b4ef..e11aca5c4f8 100644 --- a/chromium/third_party/catapult/tracing/tracing/metrics/uma_metric_test.html +++ b/chromium/third_party/catapult/tracing/tracing/metrics/uma_metric_test.html @@ -15,13 +15,11 @@ found in the LICENSE file. tr.b.unittest.testSuite(function() { // 1 sample in [1, 3) and 2 samples in [3, 7). - const BROWSER_BUCKETS1 = + const BROWSER_BUCKETS = 'LAAAAAwAAAAAAAAAAwAAAAEAAAADAAAAAAAAAAEAAAADAAAABwAAAAAAAAACAAAA'; // 1 sample in [3, 7). - const BROWSER_BUCKETS2 = 'HAAAAAUAAAAAAAAAAQAAAAMAAAAHAAAAAAAAAAEAAAA='; - // 2 samples in [7, 9). const RENDERER_BUCKETS = 'HAAAABAAAAAAAAAAAgAAAAcAAAAJAAAAAAAAAAIAAAA='; - // 1 sample in [8, 10). + // 2 samples in [8, 10). const RENDERER_INCOMPATIBLE_BUCKETS = 'HAAAAAkAAAAAAAAAAQAAAAgAAAAKAAAAAAAAAAEAAAA='; // 1 sample in [1, 3) and 2 samples in [3, 7). The sum is 9. @@ -38,7 +36,7 @@ tr.b.unittest.testSuite(function() { title: 'UMAHistogramSamples', start: 2, args: { name: 'metric1', - buckets: BROWSER_BUCKETS1}})); + buckets: BROWSER_BUCKETS}})); }); const histograms = new tr.v.HistogramSet(); tr.metrics.umaMetric(histograms, model); @@ -57,47 +55,15 @@ tr.b.unittest.testSuite(function() { assert.closeTo(2, processes.get('browser_process_1'), 1e-6); }); - test('twoUMASnapshots', function() { - const model = tr.c.TestUtils.newModel((model) => { - const browserProcess = model.getOrCreateProcess(0); - browserProcess.getOrCreateThread(0).name = 'CrBrowserMain'; - browserProcess.instantEvents.push(tr.c.TestUtils.newInstantEvent({ - title: 'UMAHistogramSamples', start: 1, - args: { - name: 'metric1', - buckets: BROWSER_BUCKETS2}})); - browserProcess.instantEvents.push(tr.c.TestUtils.newInstantEvent({ - title: 'UMAHistogramSamples', start: 2, - args: { - name: 'metric1', - buckets: BROWSER_BUCKETS1}})); - }); - const histograms = new tr.v.HistogramSet(); - tr.metrics.umaMetric(histograms, model); - const hist = histograms.getHistogramNamed('metric1'); - - // (BROWSER_BUCKETS1 - BROWSER_BUCKETS2) looks like - // * * - // 1 2 3 4 5 6 7 - assert.closeTo(2, hist.min, 1e-6); - assert.closeTo(5, hist.max, 1e-6); - assert.closeTo(3.5, hist.average, 1e-6); - }); - test('twoUMASnapshotsInDifferentProcesses', function() { const model = tr.c.TestUtils.newModel((model) => { const browserProcess = model.getOrCreateProcess(0); browserProcess.getOrCreateThread(0).name = 'CrBrowserMain'; browserProcess.instantEvents.push(tr.c.TestUtils.newInstantEvent({ - title: 'UMAHistogramSamples', start: 1, - args: { - name: 'metric1', - buckets: BROWSER_BUCKETS2}})); - browserProcess.instantEvents.push(tr.c.TestUtils.newInstantEvent({ title: 'UMAHistogramSamples', start: 2, args: { name: 'metric1', - buckets: BROWSER_BUCKETS1}})); + buckets: BROWSER_BUCKETS}})); const process = model.getOrCreateProcess(1); process.instantEvents.push(tr.c.TestUtils.newInstantEvent({ title: 'UMAHistogramSamples', start: 2, @@ -110,12 +76,12 @@ tr.b.unittest.testSuite(function() { const hist = histograms.getHistogramNamed('metric1'); // The aggregated histogram looks like - // * + // * * // * * * // 1 2 3 4 5 6 7 8 9 assert.closeTo(2, hist.min, 1e-6); assert.closeTo(8, hist.max, 1e-6); - assert.closeTo(5.75, hist.average, 1e-6); + assert.closeTo(5.6, hist.average, 1e-6); const bin = hist.getBinForValue(8); for (const diagnostics of bin.diagnosticMaps) { @@ -188,27 +154,5 @@ tr.b.unittest.testSuite(function() { tr.metrics.umaMetric(histograms, model); }, 'Incompatible bins'); }); - - test('badStartStopUMASnapshots', function() { - const model = tr.c.TestUtils.newModel((model) => { - const browserProcess = model.getOrCreateProcess(0); - browserProcess.getOrCreateThread(0).name = 'CrBrowserMain'; - browserProcess.instantEvents.push(tr.c.TestUtils.newInstantEvent({ - title: 'UMAHistogramSamples', start: 1, - args: { - name: 'metric1', - buckets: BROWSER_BUCKETS1}})); - browserProcess.instantEvents.push(tr.c.TestUtils.newInstantEvent({ - title: 'UMAHistogramSamples', start: 2, - args: { - name: 'metric1', - buckets: BROWSER_BUCKETS2}})); - }); - const histograms = new tr.v.HistogramSet(); - - assert.throws(function() { - tr.metrics.umaMetric(histograms, model); - }, 'Cannot subtract'); - }); }); </script> diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/v8/OWNERS b/chromium/third_party/catapult/tracing/tracing/metrics/v8/OWNERS index 3ecc1606516..1de8cc86062 100644 --- a/chromium/third_party/catapult/tracing/tracing/metrics/v8/OWNERS +++ b/chromium/third_party/catapult/tracing/tracing/metrics/v8/OWNERS @@ -1,3 +1,2 @@ mlippautz@chromium.org -mythria@chromium.org -ulan@chromium.org +omerkatz@chromium.org 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 index 75cae51c2e1..5390adb2eb2 100644 --- a/chromium/third_party/catapult/tracing/tracing/metrics/v8/gc_metric.html +++ b/chromium/third_party/catapult/tracing/tracing/metrics/v8/gc_metric.html @@ -22,6 +22,795 @@ tr.exportTo('tr.metrics.v8', function() { const TARGET_FPS = 60; const MS_PER_SECOND = 1000; const WINDOW_SIZE_MS = MS_PER_SECOND / TARGET_FPS; + const EPSILON = 1e-6; + + /** + * A list of metrics that are measured and tracked. + * + * See https://bit.ly/v8-gc-stats-collection for the metric naming convention. + * You can add a variant of an existing metric by simply adding its name to + * this list. E.g. v8:gc:cycle:background_threads:full:atomic:mark:cpp. + * + * If you want to add a completely new metric with its own TRACE_EVENT then + * you also need to add the corresponding rule to the RULES list below. + * + * @const {!Array<string>} + */ + const METRICS = [ + 'v8:gc:cycle:full', + 'v8:gc:cycle:full:cpp', + 'v8:gc:cycle:full:mark', + 'v8:gc:cycle:full:mark:cpp', + 'v8:gc:cycle:full:weak', + 'v8:gc:cycle:full:weak:cpp', + 'v8:gc:cycle:full:sweep', + 'v8:gc:cycle:full:sweep:cpp', + 'v8:gc:cycle:full:compact', + 'v8:gc:cycle:full:compact:cpp', + 'v8:gc:cycle:main_thread:full', + 'v8:gc:cycle:main_thread:full:cpp', + 'v8:gc:cycle:main_thread:full:mark', + 'v8:gc:cycle:main_thread:full:mark:cpp', + 'v8:gc:cycle:main_thread:full:weak', + 'v8:gc:cycle:main_thread:full:weak:cpp', + 'v8:gc:cycle:main_thread:full:sweep', + 'v8:gc:cycle:main_thread:full:sweep:cpp', + 'v8:gc:cycle:main_thread:full:compact', + 'v8:gc:cycle:main_thread:full:compact:cpp', + 'v8:gc:cycle:main_thread:full:atomic', + 'v8:gc:cycle:main_thread:full:atomic:cpp', + 'v8:gc:cycle:main_thread:full:atomic:mark', + 'v8:gc:cycle:main_thread:full:atomic:mark:cpp', + 'v8:gc:cycle:main_thread:full:atomic:weak', + 'v8:gc:cycle:main_thread:full:atomic:weak:cpp', + 'v8:gc:cycle:main_thread:full:atomic:sweep', + 'v8:gc:cycle:main_thread:full:atomic:sweep:cpp', + 'v8:gc:cycle:main_thread:full:atomic:compact', + 'v8:gc:cycle:main_thread:full:atomic:compact:cpp', + 'v8:gc:cycle:main_thread:full:incremental', + 'v8:gc:cycle:main_thread:full:incremental:cpp', + 'v8:gc:cycle:main_thread:full:incremental:mark', + 'v8:gc:cycle:main_thread:full:incremental:mark:cpp', + 'v8:gc:cycle:main_thread:full:incremental:sweep', + 'v8:gc:cycle:main_thread:full:incremental:sweep:cpp', + 'v8:gc:event:main_thread:full:atomic', + 'v8:gc:event:main_thread:full:atomic:cpp', + 'v8:gc:event:main_thread:full:atomic:mark', + 'v8:gc:event:main_thread:full:atomic:mark:cpp', + 'v8:gc:event:main_thread:full:atomic:weak', + 'v8:gc:event:main_thread:full:atomic:weak:cpp', + 'v8:gc:event:main_thread:full:atomic:sweep', + 'v8:gc:event:main_thread:full:atomic:sweep:cpp', + 'v8:gc:event:main_thread:full:atomic:compact', + 'v8:gc:event:main_thread:full:atomic:compact:cpp', + 'v8:gc:event:main_thread:full:incremental', + 'v8:gc:event:main_thread:full:incremental:cpp', + 'v8:gc:event:main_thread:full:incremental:mark', + 'v8:gc:event:main_thread:full:incremental:mark:cpp', + 'v8:gc:event:main_thread:full:incremental:sweep', + 'v8:gc:event:main_thread:full:incremental:sweep:cpp', + 'v8:gc:cycle:young', + 'v8:gc:cycle:young:mark', + 'v8:gc:cycle:young:weak', + 'v8:gc:cycle:young:sweep', + 'v8:gc:cycle:young:compact', + 'v8:gc:cycle:main_thread:young', + 'v8:gc:cycle:main_thread:young:mark', + 'v8:gc:cycle:main_thread:young:weak', + 'v8:gc:cycle:main_thread:young:sweep', + 'v8:gc:cycle:main_thread:young:compact', + 'v8:gc:cycle:main_thread:young:atomic', + 'v8:gc:cycle:main_thread:young:atomic:mark', + 'v8:gc:cycle:main_thread:young:atomic:weak', + 'v8:gc:cycle:main_thread:young:atomic:sweep', + 'v8:gc:cycle:main_thread:young:atomic:compact', + 'v8:gc:cycle:main_thread:young:incremental', + 'v8:gc:cycle:main_thread:young:incremental:mark', + 'v8:gc:cycle:main_thread:young:incremental:sweep', + 'v8:gc:event:main_thread:young:atomic', + 'v8:gc:event:main_thread:young:atomic:mark', + 'v8:gc:event:main_thread:young:atomic:weak', + 'v8:gc:event:main_thread:young:atomic:sweep', + 'v8:gc:event:main_thread:young:atomic:compact', + 'v8:gc:event:main_thread:young:incremental', + 'v8:gc:event:main_thread:young:incremental:mark', + 'v8:gc:event:main_thread:young:incremental:sweep', + ]; + + /** + * Shorthands for various event groups to be used in RULES below. + */ + const V8_FULL_ATOMIC_EVENTS = [ + 'V8.GC_MARK_COMPACTOR' + ]; + + const V8_FULL_MARK_EVENTS = [ + 'V8.GC_MC_BACKGROUND_MARKING', + 'V8.GC_MC_MARK', + 'V8.GC_MC_INCREMENTAL', + 'V8.GC_MC_INCREMENTAL_START', + ]; + + const V8_FULL_COMPACT_EVENTS = [ + 'V8.GC_MC_BACKGROUND_EVACUATE_COPY', + 'V8.GC_MC_BACKGROUND_EVACUATE_UPDATE_POINTERS', + 'V8.GC_MC_EVACUATE', + ]; + + const V8_FULL_SWEEP_EVENTS = [ + 'V8.GC_MC_BACKGROUND_SWEEPING', + 'V8.GC_MC_SWEEP', + 'V8.GC_MC_COMPLETE_SWEEPING', + ]; + + const V8_FULL_WEAK_EVENTS = [ + 'V8.GC_MC_CLEAR', + ]; + + const V8_YOUNG_ATOMIC_EVENTS = [ + 'V8.GC_SCAVENGER_BACKGROUND_SCAVENGE_PARALLEL', + 'V8.GC_SCAVENGER', + 'V8.GC_MINOR_MARK_COMPACTOR', + ]; + + const V8_YOUNG_MARK_EVENTS = [ + 'V8.GC_MINOR_MC_BACKGROUND_MARKING', + 'V8.GC_MINOR_MC_MARK', + 'V8.GC_MINOR_MC_INCREMENTAL', + 'V8.GC_MINOR_MC_INCREMENTAL_START', + ]; + + const V8_YOUNG_COMPACT_EVENTS = [ + 'V8.GC_MINOR_MC_BACKGROUND_EVACUATE_COPY', + 'V8.GC_MINOR_MC_BACKGROUND_EVACUATE_UPDATE_POINTERS', + 'V8.GC_MINOR_MC_EVACUATE', + ]; + + const V8_YOUNG_SWEEP_EVENTS = [ + 'V8.GC_MINOR_MC_BACKGROUND_SWEEPING', + 'V8.GC_MINOR_MC_SWEEP', + ]; + + const V8_YOUNG_WEAK_EVENTS = [ + 'V8.GC_MINOR_MC_CLEAR', + ]; + + const CPP_GC_FULL_MARK_EVENTS = [ + 'BlinkGC.AtomicPauseMarkEpilogue', + 'BlinkGC.AtomicPauseMarkPrologue', + 'BlinkGC.AtomicPauseMarkRoots', + 'BlinkGC.AtomicPauseMarkTransitiveClosure', + 'BlinkGC.ConcurrentMarkingStep', + 'BlinkGC.IncrementalMarkingStartMarking', + 'BlinkGC.IncrementalMarkingStep', + 'BlinkGC.MarkBailOutObjects', + 'BlinkGC.MarkFlushEphemeronPairs', + 'BlinkGC.MarkFlushV8References', + 'BlinkGC.UnifiedMarkingStep', + 'CppGC.AtomicMark', + 'CppGC.IncrementalMark', + 'CppGC.ConcurrentMark', + ]; + + const CPP_GC_FULL_COMPACT_EVENTS = [ + 'BlinkGC.AtomicPauseSweepAndCompact', + 'CppGC.AtomicCompact', + ]; + + const CPP_GC_FULL_SWEEP_EVENTS = [ + 'BlinkGC.CompleteSweep', + 'BlinkGC.ConcurrentSweepingStep', + 'BlinkGC.LazySweepInIdle', + 'BlinkGC.LazySweepOnAllocation', + 'CppGC.AtomicSweep', + 'CppGC.IncrementalSweep', + 'CppGC.ConcurrentSweep', + ]; + + const CPP_GC_FULL_WEAK_EVENTS = [ + 'BlinkGC.MarkWeakProcessing', + 'CppGC.AtomicWeak', + ]; + + /** + * A Rule object describes a mapping from events to metrics. + * + * For each event in a single GC cycle there can be at most one rule that + * that applies to that event. An event E applies to a rule R if all the + * following conditions hold: + * + * 1. R.events contains E.title. + * + * 2. if R.inside is not empty, then at least one event X specified by + * R.inside exists on some thread and fully encloses E: + * X.start <= E.start && E.end <= X.end. + * Note that X and E don't have to be on the same thread, which allows + * us to find background thread events that happen during atomic pause. + * + * 3. if R.outside is not empty, then there is no such event X specified + * by R.outside that fully encloses E. This is useful for background + * events that happen during incremental phases. + * + * TODO(chromium:1056170): Currently event names of V8 and CppGC do not + * collide because V8 events are prefixed with 'V8' and CppGC events are + * prefixed with 'CppGC'. Revisit this it before switching to the library. + * We may need to either include the category to rules or keep the prefixes. + * + * @typedef {Object} Rule + * @property {!Array<string>} events - Event names selected by this rule. + * @property {?Array<string>} inside - Allowlist of enclosing event names. + * @property {?Array<string>} outside - Blocklist of enclosing event names. + * @property {string} contribute_to - The most specific target metric name. + * The rule automatically applies to all more general metrics that can + * be derived by dropping parts of the target metric name. + * Note that the metric name does not include granularity and threads. + */ + + /** + * @const {!Array<!Rule>} + */ + const RULES = [ + { + events: V8_FULL_ATOMIC_EVENTS, + contribute_to: 'full:atomic', + }, + { + events: V8_FULL_MARK_EVENTS, + inside: V8_FULL_ATOMIC_EVENTS, + contribute_to: 'full:atomic:mark', + }, + { + events: CPP_GC_FULL_MARK_EVENTS, + inside: V8_FULL_ATOMIC_EVENTS, + contribute_to: 'full:atomic:mark:cpp', + }, + { + events: V8_FULL_MARK_EVENTS, + outside: V8_FULL_ATOMIC_EVENTS, + contribute_to: 'full:incremental:mark', + }, + { + events: CPP_GC_FULL_MARK_EVENTS, + outside: V8_FULL_ATOMIC_EVENTS, + contribute_to: 'full:incremental:mark:cpp', + }, + { + events: V8_FULL_COMPACT_EVENTS, + inside: V8_FULL_ATOMIC_EVENTS, + contribute_to: 'full:atomic:compact', + }, + { + events: CPP_GC_FULL_COMPACT_EVENTS, + inside: V8_FULL_ATOMIC_EVENTS, + contribute_to: 'full:atomic:compact:cpp', + }, + { + events: V8_FULL_SWEEP_EVENTS, + inside: V8_FULL_ATOMIC_EVENTS, + outside: V8_FULL_COMPACT_EVENTS, + contribute_to: 'full:atomic:sweep', + }, + { + events: CPP_GC_FULL_SWEEP_EVENTS, + inside: V8_FULL_ATOMIC_EVENTS, + contribute_to: 'full:atomic:sweep:cpp', + }, + { + events: V8_FULL_WEAK_EVENTS, + inside: V8_FULL_ATOMIC_EVENTS, + contribute_to: 'full:atomic:weak', + }, + { + events: CPP_GC_FULL_WEAK_EVENTS, + inside: V8_FULL_ATOMIC_EVENTS, + contribute_to: 'full:atomic:weak:cpp', + }, + { + events: V8_FULL_SWEEP_EVENTS, + outside: V8_FULL_ATOMIC_EVENTS.concat(V8_FULL_COMPACT_EVENTS), + contribute_to: 'full:incremental:sweep', + }, + { + events: CPP_GC_FULL_SWEEP_EVENTS, + outside: V8_FULL_ATOMIC_EVENTS, + contribute_to: 'full:incremental:sweep:cpp', + }, + { + events: V8_YOUNG_ATOMIC_EVENTS, + contribute_to: 'young:atomic', + }, + { + events: V8_YOUNG_MARK_EVENTS, + inside: V8_YOUNG_ATOMIC_EVENTS, + contribute_to: 'young:atomic:mark', + }, + { + events: V8_YOUNG_MARK_EVENTS, + outside: V8_YOUNG_ATOMIC_EVENTS, + contribute_to: 'young:incremental:mark', + }, + { + events: V8_YOUNG_COMPACT_EVENTS, + inside: V8_YOUNG_ATOMIC_EVENTS, + contribute_to: 'young:atomic:compact', + }, + { + events: V8_YOUNG_SWEEP_EVENTS, + inside: V8_YOUNG_ATOMIC_EVENTS, + outside: V8_YOUNG_COMPACT_EVENTS, + contribute_to: 'young:atomic:sweep', + }, + { + events: V8_YOUNG_WEAK_EVENTS, + inside: V8_YOUNG_ATOMIC_EVENTS, + contribute_to: 'young:atomic:weak', + }, + { + events: V8_YOUNG_SWEEP_EVENTS, + outside: V8_YOUNG_ATOMIC_EVENTS.concat(V8_YOUNG_COMPACT_EVENTS), + contribute_to: 'young:incremental:sweep', + }, + ]; + + /** + * A part of the metric name that defines how the events contributing to + * the metric are aggregated. See Metric.apply() below. + * @enum {string} + */ + const Granularity = { + CYCLE: 'cycle', + EVENT: 'event', + }; + + /** + * A thread of a V8 isolate is considered a main thread including: + * + * - the main thread of the renderer. + * - the main thread of a dedicated worker. + * - the main thread of a service worker. + * + * A thread that runs background tasks is considered a background + * thread. See jsExecutionThreadsWithTypes() below. + * + * @enum {string} + */ + const ThreadType = { + MAIN: 'main', + BACKGROUND: 'background', + ALL_THREADS: 'all_threads', + }; + + /** + * Represents a single metric together with its histogram of measurements. + */ + class Metric { + /** + * @param{string} name The name of the metric. + * See https://bit.ly/v8-gc-stats-collection for the metric naming + * convention and the meanining of name parts. + */ + constructor(name) { + const parts = name.split(':'); + + /** @private @const {Granularity} */ + this.granularity_ = parts[2]; + assert(this.granularity_ === Granularity.CYCLE || + this.granularity_ === Granularity.EVENT); + + /** @private @const {?ThreadType} */ + this.thread_ = ThreadType.ALL_THREADS; + let phasesIndex = 3; + if (parts[3] === 'main_thread') { + this.thread_ = ThreadType.MAIN; + phasesIndex = 4; + } + if (parts[3] === 'background_threads') { + this.thread_ = ThreadType.BACKGROUND; + phasesIndex = 4; + } + + /** @private @const {!Array<string>} */ + this.phases_ = parts.slice(phasesIndex); + + const maxValue = this.isPerCycleMetric() ? 10000 : 1000; + const boundaries = + tr.v.HistogramBinBoundaries.createExponential(0.1, maxValue, 100); + + /** @package @const {!tr.v.Histogram} */ + this.histogram = new tr.v.Histogram(name, + tr.b.Unit.byName.timeDurationInMs_smallerIsBetter, boundaries); + this.histogram.customizeSummaryOptions({ + avg: true, + count: true, + max: true, + min: false, + std: false, + sum: this.isPerCycleMetric(), + }); + } + + /** + * @return {boolean} Whether the granularity of this metric is per cycle. + */ + isPerCycleMetric() { + return this.granularity_ === Granularity.CYCLE; + } + + /** + * @param {!Array<string>} phases - A list of metric phases. + * @return {boolean} Whether the phases of this metric are more general + * than (or equal to) the given phases. + */ + isMoreGeneralThanOrEqualTo(phases) { + // Check that this.phases_ is a subset of phases. + const phasesSet = new Set(phases.split(':')); + return this.phases_.every(phase => phasesSet.has(phase)); + } + + /** + * @param {!Array<!Rule>} rules - Rules that map events to metrics. + * @param {!Array<!tr.model.Slice>} events - All events of a single GC cycle + * including the events of the main and background threads. + * @return {!Array<!tr.model.Slice>} A subset of the given events that + * contribute to this metric. + */ + contributingEvents(rules, events) { + // A map from an event name to the events with that name. + // It is used to speed up enclosing checks below. + const eventsByName = groupBy(events, e => e.title); + + // Checks if the given rule matches (or applies) to the given event. + function matches(rule, event) { + // Checks if there is an event with the given name that encloses + // |event|. + function isEnclosing(name) { + if (!eventsByName.has(name)) return false; + return eventsByName.get(name).some(e => encloses(e, event)); + } + if (!rule.events.includes(event.title)) { + return false; + } + if (rule.inside && !rule.inside.some(isEnclosing)) { + return false; + } + if (rule.outside && rule.outside.some(isEnclosing)) { + return false; + } + return true; + } + + // For each event find the applying rule and check if the rule also + // applies to this metric. + const result = []; + for (const event of events) { + const matching = rules.filter(r => matches(r, event)); + if (matching.length === 0) { + continue; + } + assert(matching.length === 1, + `${event.userFriendlyName} matches more than one rule: ` + + JSON.stringify(matching)); + if (this.isMoreGeneralThanOrEqualTo(matching[0].contribute_to)) { + result.push(event); + } + } + return result; + } + + /** + * Finds all events that contribute to this metric and aggregates them + * in the metric's histogram. + * + * @param {!Array<!Rule>} rules - Rules that map events to metrics. + * @param {!Array<!tr.model.Slice>} events - All events of a single GC cycle + * including the events of the main and background threads. + * @param {!Map<number, ThreadType>} threadTypes - A map from a thread-id + * to a thread type. + */ + apply(rules, events, threadTypes) { + // Find all events that are relevant for this metric. + const filtered = this.contributingEvents(rules, events); + + // Group the events by thread. + const eventsByThread = groupBy(filtered, e => e.parentContainer.tid); + + // Drop events nested in other events and also drop events from + // the irrelevant threads. + let flattened = []; + for (const [tid, threadEvents] of eventsByThread) { + if (this.thread_ === ThreadType.ALL_THREADS || + this.thread_ === threadTypes.get(tid)) { + flattened = flattened.concat(flatten(threadEvents)); + } + } + + // Aggregate events in the histogram. + if (this.isPerCycleMetric()) { + let sum = 0; + for (const event of flattened) { + sum += event.cpuDuration; + } + if (flattened.length > 0) { + this.histogram.addSample(sum); + } + } else { + for (const event of flattened) { + this.histogram.addSample(event.cpuDuration); + } + } + } + } + + /** + * A helper for checking the condition. + * @param {boolean} condition + * @param {string} message + */ + function assert(condition, message) { + if (!condition) { + throw new Error(message); + } + } + + /** + * A helper for grouping objects by the custom key. + * @param {!Array<!Object>} objects + * @param {!function(!Object): !Object} keyCallback + * @param {!Map<!Object, !Array<!Object>>} Objects grouped by the key. + */ + function groupBy(objects, keyCallback) { + const result = new Map(); + for (const object of objects) { + const group = keyCallback(object); + if (result.has(group)) { + result.get(group).push(object); + } else { + result.set(group, [object]); + } + } + return result; + } + + /** + * A helper for getting all events relevant for the rules. + * @param {!Array<Rule>} rules + * @return {!Array<string>} Event names. + */ + function eventsMentionedIn(rules) { + let result = []; + for (const rule of rules) { + result = result.concat(rule.events); + if (rule.inside) { + result = result.concat(rule.inside); + } + if (rule.outside) { + result = result.concat(rule.outside); + } + } + return result; + } + + + /** + * Performs thread-independent check for event nesting. + * + * @param {!Array<!tr.model.Slice>} event1 + * @param {!Array<!tr.model.Slice>} event2 + * @return {boolean} Whether the first event encloses the second event. + */ + function encloses(event1, event2) { + return (event1.start - EPSILON <= event2.start && + event2.end <= event1.end + EPSILON); + } + + /** + * Finds all threads that may execute JS code in the given renderer + * and return them together with the thread-id to thread type mapping. + * + * @param {!tr.model.helpers.ChromeRendererHelper} rendererHelper + * @return {[!Array<tr.model.Thread>, !Map<number, ThreadType>] A pair + * of a thread list and a thread type mapping. + */ + function jsExecutionThreadsWithTypes(rendererHelper) { + const mainThreads = ([rendererHelper.mainThread] + .concat(rendererHelper.dedicatedWorkerThreads) + .concat(rendererHelper.serviceWorkerThreads)); + const backgroundThreads = rendererHelper.foregroundWorkerThreads; + const threadTypes = new Map(); + for (const thread of mainThreads) { + threadTypes.set(thread.tid, ThreadType.MAIN); + } + for (const thread of backgroundThreads) { + threadTypes.set(thread.tid, ThreadType.BACKGROUND); + } + return [mainThreads.concat(backgroundThreads), threadTypes]; + } + + /** + * Drops all events that are nested in other events. + * + * @param {!Array<!tr.model.Slice>} events - Events on the same thread. + * @return {!Array<!tr.model.Slice>} Top-level events. + */ + function flatten(events) { + function compareWithEpsilon(a, b) { + if (a.start < b.start - EPSILON) return -1; + if (a.start > b.start + EPSILON) return 1; + return b.end - a.end; + } + events.sort(compareWithEpsilon); + let last = events[0]; + const result = [last]; + for (const e of events) { + if (e.end > last.end + EPSILON) { + assert(e.start >= last.end - EPSILON, + 'Overlapping events: ' + + e.userFriendlyName + ' ' + + last.userFriendlyName); + result.push(e); + last = e; + } + } + return result; + } + + /** + * Groups the events by GC cycle using the epoch argument of events. + * + * The function is more complex than expected for two reasons: + * + * 1. V8 and CppGC do not syncronize their epoch counters (yet). + * 2. V8 adds the epoch argument only to the top-level events. Nested + * events are not requred to have the epoch. + * + * The function first finds the mapping from CppGC epoch to V8 epoch assuming + * that there will always be a CppGC event nested in a V8 event. + * Then it finds the V8 epoch for each nested V8 event and CppGC event. + * Finally, it groups the events by their V8 epoch. + * + * @param {!Array<!tr.model.Slice>} events - Events from multiple GC cycles + * and multiple threads. + * @return {!Map<string, !Array<!tr.model.Slice>>} Grouped events. + */ + function groupByEpoch(events) { + function isV8Event(event) { + // TODO(1056170): Adjust this if the CppGC library uses a v8 category + // for trace events. + return event.category && event.category.includes('v8'); + } + + // Finds the V8 and CppGC epoch arguments in the given event and its + // ancestors. + function getEpoch(event) { + function checkEpochConsistency(epoch, event) { + if (epoch === null) return; + assert(epoch === event.args.epoch, + `${event.userFriendlyName} has epoch ${event.args.epoch} ` + + `which contradicts the epoch of nested events ${epoch}`); + } + const result = {v8: null, cpp: null}; + while (event) { + if ('epoch' in event.args) { + if (isV8Event(event)) { + checkEpochConsistency(result.v8, event); + result.v8 = event.args.epoch; + } else { + checkEpochConsistency(result.cpp, event); + result.cpp = event.args.epoch; + } + } + event = event.parentSlice; + } + return result; + } + + // The following two functions combine V8 and CppGC epoch into a single + // global epoch. We give V8 even global epochs and CppGC odd global epochs. + function GlobalEpochFromV8(v8Epoch) { + return 2 * v8Epoch; + } + + function GlobalEpochFromCpp(cppEpoch) { + return 2 * cppEpoch + 1; + } + + // Find the mapping from a CppGC epoch to a V8 epoch. + const cppToV8 = new Map(); + for (const event of events) { + const epoch = getEpoch(event); + if (epoch.cpp !== null && epoch.v8 !== null) { + // The start of V8 incremental marking may finalize GppGC sweeping + // of the previous garbage collection cycle. Thus it may happen that + // the same CppGC epoch maps to two V8 epoch. In such a conflict + // the smaller V8 epoch wins, essentially mapping the event back to + // the previous V8 cycle. + if (!cppToV8.has(epoch.cpp) || cppToV8.get(epoch.cpp) > epoch.v8) { + cppToV8.set(epoch.cpp, epoch.v8); + } + } + } + + // For each event infer its V8 epoch and group by that. + const result = new Map(); + for (const event of events) { + const epoch = getEpoch(event); + if (epoch.cpp === null && epoch.v8 === null) { + continue; + } + let globalEpoch; + if (epoch.v8 !== null) { + // Case 1: either a V8 event or a CppGC event nested in a V8 event. + globalEpoch = GlobalEpochFromV8(epoch.v8); + } else if (cppToV8.has(epoch.cpp)) { + // Case 2: A CppGC event of a unified garbage collection. + globalEpoch = GlobalEpochFromV8(cppToV8.get(epoch.cpp)); + } else { + // Case 3: An event from a standalone CppGC garbage collection. + globalEpoch = GlobalEpochFromCpp(epoch.cpp); + } + if (result.has(globalEpoch)) { + result.get(globalEpoch).push(event); + } else { + result.set(globalEpoch, [event]); + } + } + return result; + } + + /** + * The main entry point of GC metric computation. + * + * @param {!Array<string} metricNames - A list of metric names to be computed. + * @param {!tr.v.HistogramSet} histograms - The histogram set where the new + * histograms will be added. + * @param {!tr.Model} model + */ + function addGarbageCollectionMetrics(metricNames, histograms, model) { + // Parse the metric names and store them in a list. + const metrics = metricNames.map(name => new Metric(name)); + + // Compute the set of GC related event names. + const gcEventNames = new Set(eventsMentionedIn(RULES)); + + // Iterate all renderer processes. + const chromeHelper = model.getOrCreateHelper( + tr.model.helpers.ChromeModelHelper); + for (const rendererHelper of Object.values(chromeHelper.rendererHelpers)) { + if (rendererHelper.isChromeTracingUI) continue; + + const [threads, threadTypes] = + jsExecutionThreadsWithTypes(rendererHelper); + + // Get all GC related events. + const events = []; + for (const thread of threads) { + for (const event of thread.sliceGroup.childEvents()) { + if (gcEventNames.has(event.title)) { + events.push(event); + } + } + } + + // Group events by GC cycle and consider each cycle separately. + for (const cycleEvents of groupByEpoch(events).values()) { + // If any event is in the cycle is forced, then the whole cycle + // is considered forced. Skip all events in the cycle. + if (cycleEvents.some( + tr.metrics.v8.utils.isForcedGarbageCollectionEvent)) { + continue; + } + + for (const metric of metrics) { + metric.apply(RULES, cycleEvents, threadTypes); + } + } + } + + // Add the new histograms to the resulting histogram set. + for (const metric of metrics) { + histograms.addHistogram(metric.histogram); + } + } function gcMetric(histograms, model, options) { options = options || {}; @@ -36,6 +825,7 @@ tr.exportTo('tr.metrics.v8', function() { addTotalMarkCompactorTime(histograms, model); addTotalMarkCompactorMarkingTime(histograms, model); addScavengerSurvivedFromStackEvents(histograms, model); + addGarbageCollectionMetrics(METRICS, histograms, model); } tr.metrics.MetricRegistry.register(gcMetric); @@ -380,6 +1170,7 @@ tr.exportTo('tr.metrics.v8', function() { return { gcMetric, WINDOW_SIZE_MS, // For testing purposes only. + addGarbageCollectionMetrics, // 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 index 4d0d3beca88..d542c1e25dd 100644 --- 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 @@ -16,16 +16,21 @@ found in the LICENSE file. '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; + function createModel(slices, backgroundSlices) { + function addSlices(thread, slices) { + const group = thread.sliceGroup; for (const slice of slices) { group.pushSlice(tr.c.TestUtils.newSliceEx(slice)); } group.createSubSlices(); - mainThread.updateBounds(); + thread.updateBounds(); + } + return tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) { + const rendererProcess = model.rendererProcess; + addSlices(model.rendererMain, slices); + if (backgroundSlices) { + addSlices(model.foregroundWorker1, backgroundSlices); + } }); } @@ -35,21 +40,24 @@ tr.b.unittest.testSuite(function() { 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); + const model = createModel(slices); tr.metrics.v8.gcMetric(histograms, model, {include_sub_events: true}); return histograms; } + function addGarbageCollectionMetrics(metrics, slices, backgroundSlices) { + const histograms = new tr.v.HistogramSet(); + const model = createModel(slices, backgroundSlices); + tr.metrics.v8.addGarbageCollectionMetrics(metrics, 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' @@ -147,11 +155,6 @@ tr.b.unittest.testSuite(function() { 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 @@ -160,7 +163,7 @@ tr.b.unittest.testSuite(function() { const histograms = run(slices); const mmu = histograms.getHistogramNamed( 'v8-gc-mark-compactor-mmu-100ms_window'); - assert.closeTo(0.3, mmu.min, 1e-3); + assert.closeTo(0.5, mmu.min, 1e-3); assert.strictEqual(mmu.summaryOptions.get('min'), true); assert.strictEqual(mmu.summaryOptions.get('max'), false); }); @@ -244,5 +247,685 @@ tr.b.unittest.testSuite(function() { 'v8-gc-scavenger-survived-percentage-from-stack'); assert.closeTo(0.375, percentageFromStack.average, 1e-3); }); + + test('cycleAndEvent', function() { + const mainSlices = [ + { + title: 'V8.GC_MC_INCREMENTAL', + cat: 'v8', + args: {'epoch': 0}, + start: 100, cpuStart: 100, end: 110, cpuEnd: 110, + }, + { + title: 'V8.GC_MC_INCREMENTAL', + cat: 'v8', + args: {'epoch': 0}, + start: 200, cpuStart: 200, end: 220, cpuEnd: 220, + }, + { + title: 'V8.GC_MARK_COMPACTOR', + cat: 'v8', + args: {'epoch': 0}, + start: 300, cpuStart: 300, end: 400, cpuEnd: 400, + }, + { + title: 'V8.GC_MC_MARK', + cat: 'v8.gc', + start: 300, cpuStart: 300, end: 340, cpuEnd: 340, + }, + ]; + const backgroundSlices = []; + const expectedAverages = [ + ['v8:gc:cycle:main_thread:full:atomic:mark', 40], + ['v8:gc:cycle:main_thread:full:incremental:mark', 10 + 20], + ['v8:gc:event:main_thread:full:atomic:mark', 40], + ['v8:gc:event:main_thread:full:incremental:mark', (10 + 20) / 2], + ]; + const metrics = expectedAverages.map(pair => pair[0]); + const histograms = addGarbageCollectionMetrics( + metrics, mainSlices, backgroundSlices); + for (const [name, expectedAverage] of expectedAverages) { + const h = histograms.getHistogramNamed(name); + assert.closeTo(expectedAverage, h.average, 1e-3); + } + }); + + test('nested', function() { + const mainSlices = [ + { + title: 'V8.GC_MARK_COMPACTOR', + cat: 'v8', + args: {'epoch': 0}, + start: 100, cpuStart: 100, end: 900, cpuEnd: 900, + }, + { + title: 'V8.GC_MC_MARK', + cat: 'v8.gc', + start: 100, cpuStart: 100, end: 300, cpuEnd: 300, + }, + { + title: 'V8.GC_MC_MARK', + cat: 'v8.gc', + start: 110, cpuStart: 110, end: 120, cpuEnd: 120, + }, + { + title: 'BlinkGC.AtomicPauseMarkTransitiveClosure', + cat: 'blink.gc', + args: {'epoch': 1}, + start: 110, cpuStart: 110, end: 111, cpuEnd: 111, + }, + { + title: 'CppGC.AtomicMark', + cat: 'cpp.gc', + args: {'epoch': 1}, + start: 111, cpuStart: 111, end: 112, cpuEnd: 112, + }, + { + title: 'V8.GC_MC_MARK', + cat: 'v8.gc', + start: 230, cpuStart: 230, end: 250, cpuEnd: 250, + }, + { + title: 'BlinkGC.AtomicPauseMarkTransitiveClosure', + cat: 'blink.gc', + args: {'epoch': 1}, + start: 230, cpuStart: 230, end: 232, cpuEnd: 232, + }, + { + title: 'CppGC.AtomicMark', + cat: 'cpp.gc', + args: {'epoch': 1}, + start: 232, cpuStart: 232, end: 234, cpuEnd: 234, + }, + { + title: 'V8.GC_MC_MARK', + cat: 'v8.gc', + start: 400, cpuStart: 400, end: 500, cpuEnd: 500, + }, + { + title: 'BlinkGC.AtomicPauseMarkTransitiveClosure', + cat: 'blink.gc', + args: {'epoch': 1}, + start: 400, cpuStart: 400, end: 403, cpuEnd: 403, + }, + { + title: 'CppGC.AtomicMark', + cat: 'cpp.gc', + args: {'epoch': 1}, + start: 403, cpuStart: 403, end: 406, cpuEnd: 406, + }, + ]; + const backgroundSlices = []; + const expectedAverages = [ + ['v8:gc:cycle:main_thread:full:atomic:mark', 200 + 100], + ['v8:gc:event:main_thread:full:atomic:mark', (200 + 100) / 2], + ['v8:gc:cycle:main_thread:full:atomic:mark:cpp', 1 + 2 + 3 + 1 + 2 + 3], + ['v8:gc:event:main_thread:full:atomic:mark:cpp', + (1 + 2 + 3 + 1 + 2 + 3) / 6], + ]; + const metrics = expectedAverages.map(pair => pair[0]); + const histograms = addGarbageCollectionMetrics( + metrics, mainSlices, backgroundSlices); + for (const [name, expectedAverage] of expectedAverages) { + const h = histograms.getHistogramNamed(name); + assert.closeTo(expectedAverage, h.average, 1e-3); + } + }); + + test('phases', function() { + const mainSlices = [ + { + title: 'V8.GC_MARK_COMPACTOR', + cat: 'v8', + args: {'epoch': 0}, + start: 100, cpuStart: 100, end: 900, cpuEnd: 900, + }, + { + title: 'V8.GC_MC_MARK', + cat: 'v8.gc', + start: 100, cpuStart: 100, end: 110, cpuEnd: 110, + }, + { + title: 'V8.GC_MC_CLEAR', + cat: 'v8.gc', + start: 200, cpuStart: 200, end: 220, cpuEnd: 220, + }, + { + title: 'V8.GC_MC_EVACUATE', + cat: 'v8.gc', + start: 300, cpuStart: 300, end: 330, cpuEnd: 330, + }, + { + title: 'V8.GC_MC_SWEEP', + cat: 'v8.gc', + start: 400, cpuStart: 400, end: 440, cpuEnd: 440, + }, + ]; + const backgroundSlices = []; + const expectedAverages = [ + ['v8:gc:cycle:main_thread:full:atomic:mark', 10], + ['v8:gc:cycle:main_thread:full:atomic:weak', 20], + ['v8:gc:cycle:main_thread:full:atomic:compact', 30], + ['v8:gc:cycle:main_thread:full:atomic:sweep', 40], + ]; + const metrics = expectedAverages.map(pair => pair[0]); + const histograms = addGarbageCollectionMetrics( + metrics, mainSlices, backgroundSlices); + for (const [name, expectedAverage] of expectedAverages) { + const h = histograms.getHistogramNamed(name); + assert.closeTo(expectedAverage, h.average, 1e-3); + } + }); + + test('minorPhases', function() { + const mainSlices = [ + { + title: 'V8.GC_MINOR_MARK_COMPACTOR', + cat: 'v8', + args: {'epoch': 0}, + start: 100, cpuStart: 100, end: 900, cpuEnd: 900, + }, + { + title: 'V8.GC_MINOR_MC_MARK', + cat: 'v8.gc', + start: 100, cpuStart: 100, end: 110, cpuEnd: 110, + }, + { + title: 'V8.GC_MINOR_MC_CLEAR', + cat: 'v8.gc', + start: 200, cpuStart: 200, end: 220, cpuEnd: 220, + }, + { + title: 'V8.GC_MINOR_MC_EVACUATE', + cat: 'v8.gc', + start: 300, cpuStart: 300, end: 330, cpuEnd: 330, + }, + { + title: 'V8.GC_MINOR_MC_SWEEP', + cat: 'v8.gc', + start: 400, cpuStart: 400, end: 440, cpuEnd: 440, + }, + ]; + const backgroundSlices = []; + const expectedAverages = [ + ['v8:gc:cycle:main_thread:young:atomic:mark', 10], + ['v8:gc:cycle:main_thread:young:atomic:weak', 20], + ['v8:gc:cycle:main_thread:young:atomic:compact', 30], + ['v8:gc:cycle:main_thread:young:atomic:sweep', 40], + ]; + const metrics = expectedAverages.map(pair => pair[0]); + const histograms = addGarbageCollectionMetrics( + metrics, mainSlices, backgroundSlices); + for (const [name, expectedAverage] of expectedAverages) { + const h = histograms.getHistogramNamed(name); + assert.closeTo(expectedAverage, h.average, 1e-3); + } + }); + + test('fullAndYoung', function() { + const mainSlices = [ + { + title: 'V8.GC_MARK_COMPACTOR', + cat: 'v8', + args: {'epoch': 0}, + start: 100, cpuStart: 100, end: 110, cpuEnd: 110, + }, + { + title: 'V8.GC_SCAVENGER', + cat: 'v8', + args: {'epoch': 1}, + start: 200, cpuStart: 200, end: 220, cpuEnd: 220, + }, + { + title: 'V8.GC_SCAVENGER', + cat: 'v8', + args: {'epoch': 2}, + start: 300, cpuStart: 300, end: 330, cpuEnd: 330, + }, + { + title: 'V8.GC_MARK_COMPACTOR', + cat: 'v8', + args: {'epoch': 4}, + start: 400, cpuStart: 400, end: 440, cpuEnd: 440, + }, + { + title: 'V8.GC_MINOR_MARK_COMPACTOR', + cat: 'v8', + args: {'epoch': 5}, + start: 500, cpuStart: 500, end: 550, cpuEnd: 550, + }, + ]; + const backgroundSlices = []; + const expectedAverages = [ + ['v8:gc:cycle:main_thread:full:atomic', (10 + 40) / 2], + ['v8:gc:cycle:main_thread:full', (10 + 40) / 2], + ['v8:gc:cycle:full', (10 + 40) / 2], + ['v8:gc:cycle:main_thread:young:atomic', (20 + 30 + 50) / 3], + ['v8:gc:cycle:main_thread:young', (20 + 30 + 50) / 3], + ['v8:gc:cycle:young', (20 + 30 + 50) / 3], + ['v8:gc:cycle:main_thread', (10 + 20 + 30 + 40 + 50) / 5], + ['v8:gc:cycle', (10 + 20 + 30 + 40 + 50) / 5], + ]; + const metrics = expectedAverages.map(pair => pair[0]); + const histograms = addGarbageCollectionMetrics( + metrics, mainSlices, backgroundSlices); + for (const [name, expectedAverage] of expectedAverages) { + const h = histograms.getHistogramNamed(name); + assert.closeTo(expectedAverage, h.average, 1e-3); + } + }); + + test('cpp', function() { + const mainSlices = [ + { + title: 'V8.GC_MC_INCREMENTAL', + cat: 'v8', + args: {'epoch': 0}, + start: 100, cpuStart: 100, end: 200, cpuEnd: 200, + }, + { + title: 'BlinkGC.IncrementalMarkingStep', + cat: 'blink.gc', + args: {'epoch': 1}, + start: 100, cpuStart: 100, end: 110, cpuEnd: 110, + }, + { + title: 'CppGC.IncrementalMark', + cat: 'cpp.gc', + args: {'epoch': 1}, + start: 110, cpuStart: 110, end: 120, cpuEnd: 120, + }, + { + title: 'V8.GC_MARK_COMPACTOR', + cat: 'v8', + args: {'epoch': 0}, + start: 200, cpuStart: 200, end: 300, cpuEnd: 300, + }, + { + title: 'BlinkGC.AtomicPauseMarkTransitiveClosure', + cat: 'blink.gc', + args: {'epoch': 1}, + start: 200, cpuStart: 200, end: 220, cpuEnd: 220, + }, + { + title: 'CppGC.AtomicMark', + cat: 'cpp.gc', + args: {'epoch': 1}, + start: 220, cpuStart: 220, end: 240, cpuEnd: 240, + }, + ]; + const backgroundSlices = [ + { + title: 'BlinkGC.ConcurrentMarkingStep', + cat: 'blink.gc', + args: {'epoch': 1}, + start: 100, cpuStart: 100, end: 130, cpuEnd: 130, + }, + { + title: 'CppGC.ConcurrentMark', + cat: 'cpp.gc', + args: {'epoch': 1}, + start: 130, cpuStart: 130, end: 160, cpuEnd: 160, + }, + { + title: 'BlinkGC.ConcurrentMarkingStep', + cat: 'blink.gc', + args: {'epoch': 1}, + start: 200, cpuStart: 200, end: 240, cpuEnd: 240, + }, + { + title: 'CppGC.ConcurrentMark', + cat: 'cpp.gc', + args: {'epoch': 1}, + start: 240, cpuStart: 240, end: 280, cpuEnd: 280, + }, + ]; + const expectedAverages = [ + ['v8:gc:cycle:main_thread:full:atomic:mark:cpp', 20 + 20], + ['v8:gc:cycle:main_thread:full:incremental:mark:cpp', 10 + 10], + ['v8:gc:cycle:full:atomic:mark:cpp', 20 + 40 + 20 + 40], + ['v8:gc:cycle:full:incremental:mark:cpp', 10 + 30 + 10 + 30], + ]; + const metrics = expectedAverages.map(pair => pair[0]); + const histograms = addGarbageCollectionMetrics( + metrics, mainSlices, backgroundSlices); + for (const [name, expectedAverage] of expectedAverages) { + const h = histograms.getHistogramNamed(name); + assert.closeTo(expectedAverage, h.average, 1e-3); + } + }); + + test('atomicAndIncremental', function() { + const mainSlices = [ + { + title: 'V8.GC_MC_COMPLETE_SWEEPING', + cat: 'v8', + args: {'epoch': 0}, + start: 80, cpuStart: 80, end: 100, cpuEnd: 100, + }, + { + title: 'V8.GC_MC_INCREMENTAL', + cat: 'v8', + args: {'epoch': 1}, + start: 100, cpuStart: 100, end: 110, cpuEnd: 110, + }, + { + title: 'V8.GC_MARK_COMPACTOR', + cat: 'v8', + args: {'epoch': 1}, + start: 200, cpuStart: 200, end: 300, cpuEnd: 300, + }, + { + title: 'V8.GC_MC_MARK', + cat: 'v8', + start: 200, cpuStart: 200, end: 220, cpuEnd: 220, + }, + ]; + const backgroundSlices = [ + { + title: 'V8.GC_MC_BACKGROUND_MARKING', + cat: 'v8', + args: {'epoch': 1}, + start: 100, cpuStart: 100, end: 130, cpuEnd: 130, + }, + { + title: 'V8.GC_MC_BACKGROUND_MARKING', + cat: 'v8', + args: {'epoch': 1}, + start: 200, cpuStart: 200, end: 240, cpuEnd: 240, + }, + ]; + const expectedAverages = [ + ['v8:gc:cycle:main_thread:full:incremental:sweep', 20], + ['v8:gc:cycle:main_thread:full:atomic:mark', 20], + ['v8:gc:cycle:main_thread:full:incremental:mark', 10], + ['v8:gc:cycle:background_threads:full:atomic:mark', 40], + ['v8:gc:cycle:background_threads:full:incremental:mark', 30], + ['v8:gc:cycle:full:atomic:mark', 20 + 40], + ['v8:gc:cycle:full:incremental:mark', 10 + 30], + ]; + const metrics = expectedAverages.map(pair => pair[0]); + const histograms = addGarbageCollectionMetrics( + metrics, mainSlices, backgroundSlices); + for (const [name, expectedAverage] of expectedAverages) { + const h = histograms.getHistogramNamed(name); + assert.closeTo(expectedAverage, h.average, 1e-3); + } + }); + + test('multipleCycles', function() { + const mainSlices = [ + { + title: 'V8.GC_MC_INCREMENTAL', + cat: 'v8', + args: {'epoch': 0}, + start: 100, cpuStart: 100, end: 110, cpuEnd: 110, + }, + { + title: 'BlinkGC.IncrementalMarkingStep', + cat: 'blink.gc', + args: {'epoch': 1}, + start: 200, cpuStart: 200, end: 220, cpuEnd: 220, + }, + { + title: 'CppGC.IncrementalMark', + cat: 'cpp.gc', + args: {'epoch': 1}, + start: 220, cpuStart: 220, end: 240, cpuEnd: 240, + }, + { + title: 'V8.GC_MARK_COMPACTOR', + cat: 'v8', + args: {'epoch': 0}, + start: 300, cpuStart: 300, end: 400, cpuEnd: 400, + }, + { + title: 'BlinkGC.AtomicPauseMarkTransitiveClosure', + cat: 'blink.gc', + args: {'epoch': 1}, + start: 300, cpuStart: 300, end: 330, cpuEnd: 330, + }, + { + title: 'CppGC.AtomicMark', + cat: 'cpp.gc', + args: {'epoch': 1}, + start: 330, cpuStart: 330, end: 360, cpuEnd: 360, + }, + { + title: 'V8.GC_MC_INCREMENTAL', + cat: 'v8', + args: {'epoch': 1}, + start: 600, cpuStart: 600, end: 640, cpuEnd: 640, + }, + { + title: 'V8.GC_MARK_COMPACTOR', + cat: 'v8', + args: {'epoch': 1}, + start: 700, cpuStart: 700, end: 900, cpuEnd: 900, + }, + { + title: 'BlinkGC.AtomicPauseMarkTransitiveClosure', + cat: 'blink.gc', + args: {'epoch': 2}, + start: 700, cpuStart: 700, end: 750, cpuEnd: 750, + }, + { + title: 'CppGC.AtomicMark', + cat: 'cpp.gc', + args: {'epoch': 2}, + start: 750, cpuStart: 750, end: 800, cpuEnd: 800, + }, + ]; + const expectedAverages = [ + ['v8:gc:cycle:full', ((10 + 20 + 20 + 100) + (40 + 200)) / 2], + ['v8:gc:cycle:full:cpp', ((20 + 30 + 20 + 30) + (50 + 50)) / 2], + ]; + const metrics = expectedAverages.map(pair => pair[0]); + const histograms = addGarbageCollectionMetrics(metrics, mainSlices, []); + for (const [name, expectedAverage] of expectedAverages) { + const h = histograms.getHistogramNamed(name); + assert.closeTo(expectedAverage, h.average, 1e-3); + } + }); + + test('standaloneCppGC', function() { + const mainSlices = [ + { + title: 'BlinkGC.IncrementalMarkingStep', + cat: 'blink.gc', + args: {'epoch': 1}, + start: 200, cpuStart: 200, end: 220, cpuEnd: 220, + }, + { + title: 'CppGC.IncrementalMark', + cat: 'cpp.gc', + args: {'epoch': 1}, + start: 220, cpuStart: 220, end: 240, cpuEnd: 240, + }, + { + title: 'BlinkGC.AtomicPauseMarkTransitiveClosure', + cat: 'blink.gc', + args: {'epoch': 1}, + start: 300, cpuStart: 300, end: 330, cpuEnd: 330, + }, + { + title: 'CppGC.AtomicMark', + cat: 'cpp.gc', + args: {'epoch': 1}, + start: 330, cpuStart: 330, end: 360, cpuEnd: 360, + }, + { + title: 'BlinkGC.AtomicPauseMarkTransitiveClosure', + cat: 'blink.gc', + args: {'epoch': 2}, + start: 700, cpuStart: 700, end: 750, cpuEnd: 750, + }, + { + title: 'CppGC.AtomicMark', + cat: 'cpp.gc', + args: {'epoch': 2}, + start: 750, cpuStart: 750, end: 800, cpuEnd: 800, + }, + ]; + const expectedAverages = [ + ['v8:gc:cycle:full', ((20 + 30 + 20 + 30) + (50 + 50)) / 2], + ['v8:gc:cycle:full:cpp', ((20 + 30 + 20 + 30) + (50 + 50)) / 2], + ]; + const metrics = expectedAverages.map(pair => pair[0]); + const histograms = addGarbageCollectionMetrics(metrics, mainSlices, []); + for (const [name, expectedAverage] of expectedAverages) { + const h = histograms.getHistogramNamed(name); + assert.closeTo(expectedAverage, h.average, 1e-3); + } + }); + + test('ambigousEpoch', function() { + const mainSlices = [ + { + title: 'V8.GC_MARK_COMPACTOR', + cat: 'v8', + args: {'epoch': 0}, + start: 300, cpuStart: 300, end: 400, cpuEnd: 400, + }, + { + title: 'BlinkGC.AtomicPauseMarkTransitiveClosure', + cat: 'blink.gc', + args: {'epoch': 1}, + start: 300, cpuStart: 300, end: 330, cpuEnd: 330, + }, + { + title: 'V8.GC_MC_INCREMENTAL', + cat: 'v8', + args: {'epoch': 3}, + start: 600, cpuStart: 600, end: 640, cpuEnd: 640, + }, + { + title: 'BlinkGC.CompleteSweep', + cat: 'blink.gc', + args: {'epoch': 1}, + start: 600, cpuStart: 600, end: 610, cpuEnd: 610, + }, + { + title: 'CppGC.IncrementalMark', + cat: 'cpp.gc', + args: {'epoch': 2}, + start: 610, cpuStart: 610, end: 640, cpuEnd: 640, + }, + ]; + const expectedAverages = [ + ['v8:gc:cycle:full', ((100) + (40)) / 2], + ['v8:gc:cycle:full:cpp', ((30 + 10) + (30)) / 2], + ]; + const metrics = expectedAverages.map(pair => pair[0]); + const histograms = addGarbageCollectionMetrics(metrics, mainSlices, []); + for (const [name, expectedAverage] of expectedAverages) { + const h = histograms.getHistogramNamed(name); + assert.closeTo(expectedAverage, h.average, 1e-3); + } + }); + + test('sweepingInEvacuation', function() { + const mainSlices = [ + { + title: 'V8.GC_MARK_COMPACTOR', + cat: 'v8', + args: {'epoch': 0}, + start: 100, cpuStart: 100, end: 200, cpuEnd: 200, + }, + { + title: 'V8.GC_MC_EVACUATE', + cat: 'v8', + args: {'epoch': 0}, + start: 110, cpuStart: 110, end: 150, cpuEnd: 150, + }, + { + title: 'V8.GC_MC_SWEEP', + cat: 'v8', + args: {'epoch': 0}, + start: 120, cpuStart: 120, end: 140, cpuEnd: 140, + }, + { + title: 'V8.GC_MC_SWEEP', + cat: 'v8', + args: {'epoch': 0}, + start: 160, cpuStart: 160, end: 190, cpuEnd: 190, + }, + { + title: 'V8.GC_MINOR_MARK_COMPACTOR', + cat: 'v8', + args: {'epoch': 1}, + start: 200, cpuStart: 200, end: 300, cpuEnd: 300, + }, + { + title: 'V8.GC_MINOR_MC_EVACUATE', + cat: 'v8', + args: {'epoch': 1}, + start: 210, cpuStart: 210, end: 250, cpuEnd: 250, + }, + { + title: 'V8.GC_MINOR_MC_SWEEP', + cat: 'v8', + args: {'epoch': 1}, + start: 220, cpuStart: 220, end: 240, cpuEnd: 240, + }, + { + title: 'V8.GC_MINOR_MC_SWEEP', + cat: 'v8', + args: {'epoch': 1}, + start: 260, cpuStart: 260, end: 290, cpuEnd: 290, + }, + ]; + const backgroundSlices = [ + { + title: 'V8.GC_MC_BACKGROUND_EVACUATE_COPY', + cat: 'v8', + args: {'epoch': 0}, + start: 110, cpuStart: 110, end: 150, cpuEnd: 150, + }, + { + title: 'V8.GC_MC_BACKGROUND_SWEEPING', + cat: 'v8', + args: {'epoch': 0}, + start: 120, cpuStart: 120, end: 140, cpuEnd: 140, + }, + { + title: 'V8.GC_MC_BACKGROUND_SWEEPING', + cat: 'v8', + args: {'epoch': 0}, + start: 160, cpuStart: 160, end: 190, cpuEnd: 190, + }, + { + title: 'V8.GC_MINOR_MC_BACKGROUND_EVACUATE_COPY', + cat: 'v8', + args: {'epoch': 1}, + start: 210, cpuStart: 210, end: 250, cpuEnd: 250, + }, + { + title: 'V8.GC_MINOR_MC_BACKGROUND_SWEEPING', + cat: 'v8', + args: {'epoch': 1}, + start: 220, cpuStart: 220, end: 240, cpuEnd: 240, + }, + { + title: 'V8.GC_MINOR_MC_BACKGROUND_SWEEPING', + cat: 'v8', + args: {'epoch': 1}, + start: 260, cpuStart: 260, end: 290, cpuEnd: 290, + }, + ]; + const expectedAverages = [ + ['v8:gc:cycle:full:compact', 40 + 40], + ['v8:gc:cycle:full:sweep', 30 + 30], + ['v8:gc:cycle:young:compact', 40 + 40], + ['v8:gc:cycle:young:sweep', 30 + 30], + ]; + const metrics = expectedAverages.map(pair => pair[0]); + const histograms = addGarbageCollectionMetrics( + metrics, mainSlices, backgroundSlices); + for (const [name, expectedAverage] of expectedAverages) { + const h = histograms.getHistogramNamed(name); + assert.closeTo(expectedAverage, h.average, 1e-3); + } + }); }); </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 index cb5e9fe2f3a..1c06d3a590a 100644 --- a/chromium/third_party/catapult/tracing/tracing/metrics/v8/utils.html +++ b/chromium/third_party/catapult/tracing/tracing/metrics/v8/utils.html @@ -41,7 +41,6 @@ tr.exportTo('tr.metrics.v8.utils', function() { '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' @@ -52,7 +51,6 @@ tr.exportTo('tr.metrics.v8.utils', function() { 'V8.GCFinalizeMC', 'V8.GCFinalizeMCReduceMemory', 'V8.GCIncrementalMarking', - 'V8.GCIncrementalMarkingFinalize', 'V8.GCIncrementalMarkingStart', 'V8.GCPhantomHandleProcessingCallback' ]); 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 index 507d23e6119..524814d55af 100644 --- a/chromium/third_party/catapult/tracing/tracing/metrics/v8/v8_metrics.html +++ b/chromium/third_party/catapult/tracing/tracing/metrics/v8/v8_metrics.html @@ -9,6 +9,7 @@ found in the LICENSE file. <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"> +<link rel="import" href="/tracing/metrics/v8/runtime_stats_metric.html"> <script> 'use strict'; @@ -17,6 +18,7 @@ tr.exportTo('tr.metrics.v8', function() { function v8AndMemoryMetrics(histograms, model) { tr.metrics.v8.executionMetric(histograms, model); tr.metrics.v8.gcMetric(histograms, model); + tr.metrics.v8.runtimeStatsTotalMetric(histograms, model); tr.metrics.sh.memoryMetric(histograms, model, {rangeOfInterest: tr.metrics.v8.utils.rangeForMemoryDumps(model)}); } diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/v8/wasm_metric.html b/chromium/third_party/catapult/tracing/tracing/metrics/v8/wasm_metric.html index b9e500c50fd..6e12457e664 100644 --- a/chromium/third_party/catapult/tracing/tracing/metrics/v8/wasm_metric.html +++ b/chromium/third_party/catapult/tracing/tracing/metrics/v8/wasm_metric.html @@ -38,6 +38,52 @@ tr.exportTo('tr.metrics.v8', function() { histograms.addHistogram(wasmSyncInstantiationTimeWall); } + function computeSyncCompileTimeMetric(histograms, wasmEvents) { + if (!wasmEvents.hasOwnProperty('wasm.SyncCompile')) return; + + const wasmSyncCompileTimeCPU = new tr.v.Histogram( + 'v8:wasm:sync_compile:cpu_time', + tr.b.Unit.byName.timeDurationInMs_smallerIsBetter); + wasmSyncCompileTimeCPU.description = + 'cpu time spent compiling a WebAssembly module synchronously'; + const wasmSyncCompileTimeWall = new tr.v.Histogram( + 'v8:wasm:sync_compile:wall_time', + tr.b.Unit.byName.timeDurationInMs_smallerIsBetter); + wasmSyncCompileTimeWall.description = + 'wall time spent compiling a WebAssembly module synchronously'; + + for (const e of wasmEvents['wasm.SyncCompile']) { + wasmSyncCompileTimeCPU.addSample(e.cpuDuration); + wasmSyncCompileTimeWall.addSample(e.duration); + } + + histograms.addHistogram(wasmSyncCompileTimeCPU); + histograms.addHistogram(wasmSyncCompileTimeWall); + } + + function computeDeserializeTimeMetric(histograms, wasmEvents) { + if (!wasmEvents.hasOwnProperty('wasm.Deserialize')) return; + + const wasmDeserializeTimeCPU = new tr.v.Histogram( + 'v8:wasm:deserialize:cpu_time', + tr.b.Unit.byName.timeDurationInMs_smallerIsBetter); + wasmDeserializeTimeCPU.description = + 'cpu time spent deserializing a WebAssembly module'; + const wasmDeserializeTimeWall = new tr.v.Histogram( + 'v8:wasm:deserialize:wall_time', + tr.b.Unit.byName.timeDurationInMs_smallerIsBetter); + wasmDeserializeTimeWall.description = + 'wall time spent deserializing a WebAssembly module'; + + for (const e of wasmEvents['wasm.Deserialize']) { + wasmDeserializeTimeCPU.addSample(e.cpuDuration); + wasmDeserializeTimeWall.addSample(e.duration); + } + + histograms.addHistogram(wasmDeserializeTimeCPU); + histograms.addHistogram(wasmDeserializeTimeWall); + } + function computeStreamingBaselineCompileTimeMetric(histograms, wasmEvents) { // With streaming compilation, baseline compilation happens from when the // first bytes get received until when baseline compilation finishes. If @@ -51,12 +97,14 @@ tr.exportTo('tr.metrics.v8', function() { 'v8:wasm:streaming_baseline_compilation:wall_time', tr.b.Unit.byName.timeDurationInMs_smallerIsBetter); - const compilationStart = - wasmEvents['wasm.StartStreamingCompilation'][0].start; - - const compilationEnd = wasmEvents['wasm.BaselineFinished'][0].end; - - histogram.addSample(compilationEnd - compilationStart); + for (const endEvent of wasmEvents['wasm.BaselineFinished']) { + const compilationEnd = endEvent.end; + const startEvent = wasmEvents['wasm.StartStreamingCompilation'].find( + e => e.args.id === endEvent.args.id); + if (!startEvent) continue; + const compilationStart = startEvent.start; + histogram.addSample(compilationEnd - compilationStart); + } histograms.addHistogram(histogram); } @@ -70,10 +118,14 @@ tr.exportTo('tr.metrics.v8', function() { 'v8:wasm:compilation_tierup:wall_time', tr.b.Unit.byName.timeDurationInMs_smallerIsBetter); - const tierupStart = wasmEvents['wasm.BaselineFinished'][0].start; - const tierupEnd = wasmEvents['wasm.TopTierFinished'][0].end; - - histogram.addSample(tierupEnd - tierupStart); + for (const endEvent of wasmEvents['wasm.TopTierFinished']) { + const tierupEnd = endEvent.end; + const startEvent = wasmEvents['wasm.BaselineFinished'].find( + e => e.args.id === endEvent.args.id); + if (!startEvent) continue; + const tierupStart = startEvent.start; + histogram.addSample(tierupEnd - tierupStart); + } histograms.addHistogram(histogram); } @@ -90,6 +142,8 @@ tr.exportTo('tr.metrics.v8', function() { const wasmEvents = collectWasmEvents(model); computeSyncInstantiationTimeMetric(histograms, wasmEvents); + computeSyncCompileTimeMetric(histograms, wasmEvents); + computeDeserializeTimeMetric(histograms, wasmEvents); computeStreamingBaselineCompileTimeMetric(histograms, wasmEvents); computeCompilationTierupWallTimeMetric(histograms, wasmEvents); } diff --git a/chromium/third_party/catapult/tracing/tracing/metrics/v8/wasm_metric_test.html b/chromium/third_party/catapult/tracing/tracing/metrics/v8/wasm_metric_test.html index 0681b6ddf3b..573c02d4ff3 100644 --- a/chromium/third_party/catapult/tracing/tracing/metrics/v8/wasm_metric_test.html +++ b/chromium/third_party/catapult/tracing/tracing/metrics/v8/wasm_metric_test.html @@ -67,14 +67,70 @@ tr.b.unittest.testSuite(function() { assert.strictEqual(wallValue.running.max, 30); }); + test('testSyncCompileMetric', function() { + const slices = [ + { + title: 'wasm.SyncCompile', args: {}, start: 10, end: 20, + cpuStart: 100, cpuEnd: 200 + }, + { + title: 'wasm.SyncCompile', args: {}, start: 30, end: 60, + cpuStart: 300, cpuEnd: 600 + } + ]; + const histograms = runMetrics(slices); + + const cpuValue = + histograms.getHistogramNamed('v8:wasm:sync_compile:cpu_time'); + assert.strictEqual(cpuValue.running.sum, 400); + assert.strictEqual(cpuValue.numValues, 2); + assert.strictEqual(cpuValue.average, 200); + assert.strictEqual(cpuValue.running.max, 300); + + const wallValue = + histograms.getHistogramNamed('v8:wasm:sync_compile:wall_time'); + assert.strictEqual(wallValue.running.sum, 40); + assert.strictEqual(wallValue.numValues, 2); + assert.strictEqual(wallValue.average, 20); + assert.strictEqual(wallValue.running.max, 30); + }); + + test('testDeserializeMetric', function() { + const slices = [ + { + title: 'wasm.Deserialize', args: {}, start: 10, end: 20, + cpuStart: 100, cpuEnd: 200 + }, + { + title: 'wasm.Deserialize', args: {}, start: 30, end: 60, + cpuStart: 300, cpuEnd: 600 + } + ]; + const histograms = runMetrics(slices); + + const cpuValue = + histograms.getHistogramNamed('v8:wasm:deserialize:cpu_time'); + assert.strictEqual(cpuValue.running.sum, 400); + assert.strictEqual(cpuValue.numValues, 2); + assert.strictEqual(cpuValue.average, 200); + assert.strictEqual(cpuValue.running.max, 300); + + const wallValue = + histograms.getHistogramNamed('v8:wasm:deserialize:wall_time'); + assert.strictEqual(wallValue.running.sum, 40); + assert.strictEqual(wallValue.numValues, 2); + assert.strictEqual(wallValue.average, 20); + assert.strictEqual(wallValue.running.max, 30); + }); + test('testStreamingBaselineCompileTime', function() { const slices = [ { - title: 'wasm.StartStreamingCompilation', args: {}, start: 10, end: 20, - cpuStart: 10, cpuEnd: 20 + title: 'wasm.StartStreamingCompilation', args: {id: 0}, start: 10, + end: 20, cpuStart: 10, cpuEnd: 20 }, { - title: 'wasm.BaselineFinished', args: {}, start: 100, end: 110, + title: 'wasm.BaselineFinished', args: {id: 0}, start: 100, end: 110, cpuStart: 100, cpuEnd: 110 }, ]; @@ -87,10 +143,38 @@ tr.b.unittest.testSuite(function() { assert.strictEqual(compileTime.running.max, 100); }); + test('testStreamingBaselineCompileTimeTwoCompilations', function() { + const slices = [ + { + title: 'wasm.StartStreamingCompilation', args: {id: 0}, start: 10, + end: 20, cpuStart: 10, cpuEnd: 20 + }, + { + title: 'wasm.StartStreamingCompilation', args: {id: 1}, start: 110, + end: 120, cpuStart: 110, cpuEnd: 120 + }, + { + title: 'wasm.BaselineFinished', args: {id: 1}, start: 200, end: 210, + cpuStart: 200, cpuEnd: 210 + }, + { + title: 'wasm.BaselineFinished', args: {id: 0}, start: 100, end: 110, + cpuStart: 100, cpuEnd: 110 + }, + ]; + const histograms = runMetrics(slices); + + const compileTime = histograms.getHistogramNamed( + 'v8:wasm:streaming_baseline_compilation:wall_time'); + assert.strictEqual(compileTime.numValues, 2); + assert.strictEqual(compileTime.average, 100); + assert.strictEqual(compileTime.running.max, 100); + }); + test('testStreamingBaselineCompileTimeNoStartEvent', function() { const slices = [ { - title: 'wasm.BaselineFinished', args: {}, start: 100, end: 110, + title: 'wasm.BaselineFinished', args: {id: 0}, start: 100, end: 110, cpuStart: 100, cpuEnd: 110 }, ]; @@ -104,8 +188,8 @@ tr.b.unittest.testSuite(function() { test('testStreamingBaselineCompileTimeNoEndEvent', function() { const slices = [ { - title: 'wasm.StartStreamingCompilation', args: {}, start: 100, end: 110, - cpuStart: 100, cpuEnd: 110 + title: 'wasm.StartStreamingCompilation', args: {id: 0}, start: 100, + end: 110, cpuStart: 100, cpuEnd: 110 }, ]; const histograms = runMetrics(slices); @@ -118,11 +202,11 @@ tr.b.unittest.testSuite(function() { test('testTierupWallTime', function() { const slices = [ { - title: 'wasm.BaselineFinished', args: {}, start: 100, end: 110, + title: 'wasm.BaselineFinished', args: {id: 0}, start: 100, end: 110, cpuStart: 100, cpuEnd: 110 }, { - title: 'wasm.TopTierFinished', args: {}, start: 200, end: 210, + title: 'wasm.TopTierFinished', args: {id: 0}, start: 200, end: 210, cpuStart: 200, cpuEnd: 210 }, ]; @@ -136,10 +220,39 @@ tr.b.unittest.testSuite(function() { assert.strictEqual(tierupTime.running.max, 110); }); + test('testTierupWallTimeTwoTierups', function() { + const slices = [ + { + title: 'wasm.BaselineFinished', args: {id: 0}, start: 100, end: 110, + cpuStart: 100, cpuEnd: 110 + }, + { + title: 'wasm.BaselineFinished', args: {id: 1}, start: 200, end: 210, + cpuStart: 200, cpuEnd: 210 + }, + { + title: 'wasm.TopTierFinished', args: {id: 1}, start: 300, end: 310, + cpuStart: 200, cpuEnd: 210 + }, + { + title: 'wasm.TopTierFinished', args: {id: 0}, start: 200, end: 210, + cpuStart: 200, cpuEnd: 210 + }, + ]; + + const histograms = runMetrics(slices); + + const tierupTime = histograms.getHistogramNamed( + 'v8:wasm:compilation_tierup:wall_time'); + assert.strictEqual(tierupTime.numValues, 2); + assert.strictEqual(tierupTime.average, 110); + assert.strictEqual(tierupTime.running.max, 110); + }); + test('testTierupNoStartEvent', function() { const slices = [ { - title: 'wasm.TopTierFinished', args: {}, start: 200, end: 210, + title: 'wasm.TopTierFinished', args: {id: 0}, start: 200, end: 210, cpuStart: 200, cpuEnd: 210 }, ]; @@ -154,7 +267,7 @@ tr.b.unittest.testSuite(function() { test('testTierupNoEndEvent', function() { const slices = [ { - title: 'wasm.BaselineFinished', args: {}, start: 100, end: 110, + title: 'wasm.BaselineFinished', args: {id: 0}, start: 100, end: 110, cpuStart: 100, cpuEnd: 110 }, ]; diff --git a/chromium/third_party/catapult/tracing/tracing/model/clock_sync_manager.html b/chromium/third_party/catapult/tracing/tracing/model/clock_sync_manager.html index 8e9e6be8170..57a4da9ea90 100644 --- a/chromium/third_party/catapult/tracing/tracing/model/clock_sync_manager.html +++ b/chromium/third_party/catapult/tracing/tracing/model/clock_sync_manager.html @@ -18,6 +18,7 @@ tr.exportTo('tr.model', function() { // didn't explicitly specify the clock being used. UNKNOWN_CHROME_LEGACY: 'UNKNOWN_CHROME_LEGACY', + FUCHSIA_ZX_CLOCK_MONOTONIC: 'FUCHSIA_ZX_CLOCK_MONOTONIC', LINUX_CLOCK_MONOTONIC: 'LINUX_CLOCK_MONOTONIC', LINUX_FTRACE_GLOBAL: 'LINUX_FTRACE_GLOBAL', MAC_MACH_ABSOLUTE_TIME: 'MAC_MACH_ABSOLUTE_TIME', diff --git a/chromium/third_party/catapult/tracing/tracing/model/helpers/telemetry_helper.html b/chromium/third_party/catapult/tracing/tracing/model/helpers/telemetry_helper.html index 64ff046b3ef..415400638a9 100644 --- a/chromium/third_party/catapult/tracing/tracing/model/helpers/telemetry_helper.html +++ b/chromium/third_party/catapult/tracing/tracing/model/helpers/telemetry_helper.html @@ -117,7 +117,6 @@ tr.exportTo('tr.model.helpers', function() { this.irSegments_.push(new tr.model.um.Segment( gestureEvent.start, gestureEvent.duration)); gestureEventFound = true; - break; } } } else if (parts[1].startsWith('ui_')) { diff --git a/chromium/third_party/catapult/tracing/tracing/model/helpers/telemetry_helper_test.html b/chromium/third_party/catapult/tracing/tracing/model/helpers/telemetry_helper_test.html index f4a47ef74d1..705ab281dfc 100644 --- a/chromium/third_party/catapult/tracing/tracing/model/helpers/telemetry_helper_test.html +++ b/chromium/third_party/catapult/tracing/tracing/model/helpers/telemetry_helper_test.html @@ -71,6 +71,36 @@ tr.b.unittest.testSuite(function() { assert.deepEqual([6, 8], [segments[2].start, segments[2].end]); }); + test('irSegmentWithMultipleGestures', function() { + const m = tr.c.TestUtils.newModel((m) => { + // There is no IR in this renderer process. + const r = m.getOrCreateProcess(1).getOrCreateThread(1); + r.name = 'CrRendererMain'; + r.asyncSliceGroup.push(tr.c.TestUtils.newAsyncSliceNamed( + 'SyntheticGestureController::running', 1, 1)); + r.asyncSliceGroup.push(tr.c.TestUtils.newAsyncSliceNamed( + 'SyntheticGestureController::running', 3, 1)); + r.asyncSliceGroup.push(tr.c.TestUtils.newAsyncSliceNamed( + 'SyntheticGestureController::running', 5, 2)); + r.asyncSliceGroup.push( + tr.c.TestUtils.newAsyncSliceNamed('Interaction.Gesture_A', 1, 7)); + }); + + // Async slices are: + // + // 1 2 3 4 5 6 7 8 + // Interactions <---------------------- A ------------------------------> + // Gestures <-------> <-------> <---------------> + // + // Segments should be: [1, 2], [3, 4], and [6, 8]. + const modelHelper = m.getOrCreateHelper(tr.model.helpers.ChromeModelHelper); + const segments = modelHelper.telemetryHelper.irSegments; + assert.strictEqual(3, segments.length); + assert.deepEqual([1, 2], [segments[0].start, segments[0].end]); + assert.deepEqual([3, 4], [segments[1].start, segments[1].end]); + assert.deepEqual([5, 7], [segments[2].start, segments[2].end]); + }); + test('uiSegments', function() { const m = tr.c.TestUtils.newModel((m) => { // There is no IR in this renderer process. diff --git a/chromium/third_party/catapult/tracing/tracing/model/thread.html b/chromium/third_party/catapult/tracing/tracing/model/thread.html index 0822a6140b3..b31de4aadd6 100644 --- a/chromium/third_party/catapult/tracing/tracing/model/thread.html +++ b/chromium/third_party/catapult/tracing/tracing/model/thread.html @@ -239,7 +239,8 @@ tr.exportTo('tr.model', function() { let sum = 0; tr.b.iterateOverIntersectingIntervals( this.sliceGroup.topLevelSlices, - slice => slice.start, slice => slice.end, + slice => slice.start, // mapLoFn + slice => slice.duration, // mapWidthFn measures width not end of slice range.min, range.max, slice => { let fractionOfSliceInsideRangeOfInterest = 1; diff --git a/chromium/third_party/catapult/tracing/tracing/mre/cloud_storage.py b/chromium/third_party/catapult/tracing/tracing/mre/cloud_storage.py index 877ddaa0645..34e8c431bb0 100644 --- a/chromium/third_party/catapult/tracing/tracing/mre/cloud_storage.py +++ b/chromium/third_party/catapult/tracing/tracing/mre/cloud_storage.py @@ -22,6 +22,8 @@ class CloudStorageError(Exception): ' 3. For the project-id, just enter 0.' % command) +# TODO(https://1277796): Rename this once we're Python 3-only. +# pylint: disable=redefined-builtin class PermissionError(CloudStorageError): def __init__(self): diff --git a/chromium/third_party/catapult/tracing/tracing/mre/failure.py b/chromium/third_party/catapult/tracing/tracing/mre/failure.py index 8c0713e1d8b..cc70f4ee215 100644 --- a/chromium/third_party/catapult/tracing/tracing/mre/failure.py +++ b/chromium/third_party/catapult/tracing/tracing/mre/failure.py @@ -21,7 +21,7 @@ class Failure(object): def __str__(self): return ( 'Failure for job %s with function handle %s and trace handle %s:\n' - 'of type %s wtih description %s. Stack:\n\n%s' % ( + 'of type "%s" with description "%s". Stack:\n\n%s' % ( self.job.guid, self.function_handle_string, self.trace_canonical_url, self.failure_type_name, self.description, self.stack)) diff --git a/chromium/third_party/catapult/tracing/tracing/mre/failure_unittest.py b/chromium/third_party/catapult/tracing/tracing/mre/failure_unittest.py index 1140b17aa66..348b63ecf42 100644 --- a/chromium/third_party/catapult/tracing/tracing/mre/failure_unittest.py +++ b/chromium/third_party/catapult/tracing/tracing/mre/failure_unittest.py @@ -24,7 +24,7 @@ class FailureTests(unittest.TestCase): 'file://foo.html', 'err', 'desc', 'stack') - self.assertEquals(failure.AsDict(), { + self.assertEqual(failure.AsDict(), { 'job_guid': '1', 'function_handle_string': 'foo.html:Foo', 'trace_canonical_url': 'file://foo.html', @@ -48,9 +48,9 @@ class FailureTests(unittest.TestCase): failure = failure_module.Failure.FromDict(failure_dict, job) - self.assertEquals(failure.job.guid, '1') - self.assertEquals(failure.function_handle_string, 'foo.html:Foo') - self.assertEquals(failure.trace_canonical_url, 'file://foo.html') - self.assertEquals(failure.failure_type_name, 'err') - self.assertEquals(failure.description, 'desc') - self.assertEquals(failure.stack, 'stack') + self.assertEqual(failure.job.guid, '1') + self.assertEqual(failure.function_handle_string, 'foo.html:Foo') + self.assertEqual(failure.trace_canonical_url, 'file://foo.html') + self.assertEqual(failure.failure_type_name, 'err') + self.assertEqual(failure.description, 'desc') + self.assertEqual(failure.stack, 'stack') diff --git a/chromium/third_party/catapult/tracing/tracing/mre/function_handle.py b/chromium/third_party/catapult/tracing/tracing/mre/function_handle.py index 1efa5d647a9..f000e46edef 100644 --- a/chromium/third_party/catapult/tracing/tracing/mre/function_handle.py +++ b/chromium/third_party/catapult/tracing/tracing/mre/function_handle.py @@ -99,7 +99,7 @@ class FunctionHandle(object): abspath = module.filename if not abspath: - raise AbspathInvalidError('Filename %s invalid', abspath) + raise AbspathInvalidError('Filename %s invalid' % abspath) new_modules_to_load.append(ModuleToLoad(filename=abspath)) @@ -136,4 +136,3 @@ class FunctionHandle(object): return FunctionHandle(modules_to_load=modules_to_load, function_name=parts[-1]) - diff --git a/chromium/third_party/catapult/tracing/tracing/mre/function_handle_unittest.py b/chromium/third_party/catapult/tracing/tracing/mre/function_handle_unittest.py index d3bf71dfe66..522ec69eade 100644 --- a/chromium/third_party/catapult/tracing/tracing/mre/function_handle_unittest.py +++ b/chromium/third_party/catapult/tracing/tracing/mre/function_handle_unittest.py @@ -20,18 +20,18 @@ class ModuleToLoadTests(unittest.TestCase): mtl0 = function_handle.ModuleToLoad(href='/foo') mtl1 = function_handle.ModuleToLoad(filename='foo.html') - self.assertEquals(str(mtl0), 'ModuleToLoad(href="/foo")') - self.assertEquals(str(mtl1), 'ModuleToLoad(filename="foo.html")') + self.assertEqual(str(mtl0), 'ModuleToLoad(href="/foo")') + self.assertEqual(str(mtl1), 'ModuleToLoad(filename="foo.html")') def testAsDict(self): mtl0 = function_handle.ModuleToLoad(href='/foo') mtl1 = function_handle.ModuleToLoad(filename='foo.html') - self.assertEquals(mtl0.AsDict(), { + self.assertEqual(mtl0.AsDict(), { 'href': '/foo' }) - self.assertEquals(mtl1.AsDict(), { + self.assertEqual(mtl1.AsDict(), { 'filename': 'foo.html' }) @@ -47,9 +47,9 @@ class ModuleToLoadTests(unittest.TestCase): mtl0 = function_handle.ModuleToLoad.FromDict(module_dict0) mtl1 = function_handle.ModuleToLoad.FromDict(module_dict1) - self.assertEquals(mtl0.href, '/foo') + self.assertEqual(mtl0.href, '/foo') self.assertIsNone(mtl0.filename) - self.assertEquals(mtl1.filename, 'foo.html') + self.assertEqual(mtl1.filename, 'foo.html') self.assertIsNone(mtl1.href) @@ -59,7 +59,7 @@ class FunctionHandleTests(unittest.TestCase): module = function_handle.ModuleToLoad(href='/foo') handle = function_handle.FunctionHandle([module], 'Bar') - self.assertEquals( + self.assertEqual( str(handle), 'FunctionHandle(modules_to_load=[ModuleToLoad(href="/foo")], ' 'function_name="Bar")') @@ -68,7 +68,7 @@ class FunctionHandleTests(unittest.TestCase): module = function_handle.ModuleToLoad(href='/foo') handle = function_handle.FunctionHandle([module], 'Bar') - self.assertEquals( + self.assertEqual( handle.AsDict(), { 'modules_to_load': [{'href': '/foo'}], 'function_name': 'Bar' @@ -81,6 +81,6 @@ class FunctionHandleTests(unittest.TestCase): } handle = function_handle.FunctionHandle.FromDict(handle_dict) - self.assertEquals(len(handle.modules_to_load), 1) - self.assertEquals(handle.modules_to_load[0].href, '/foo') - self.assertEquals(handle.function_name, 'Bar') + self.assertEqual(len(handle.modules_to_load), 1) + self.assertEqual(handle.modules_to_load[0].href, '/foo') + self.assertEqual(handle.function_name, 'Bar') diff --git a/chromium/third_party/catapult/tracing/tracing/mre/json_output_formatter.py b/chromium/third_party/catapult/tracing/tracing/mre/json_output_formatter.py index aafc8838de3..6d5e86c72da 100644 --- a/chromium/third_party/catapult/tracing/tracing/mre/json_output_formatter.py +++ b/chromium/third_party/catapult/tracing/tracing/mre/json_output_formatter.py @@ -13,8 +13,8 @@ class JSONOutputFormatter(output_formatter.OutputFormatter): super(JSONOutputFormatter, self).__init__(output_file) self.output_file = output_file - def Format(self, result_list): - d = [result.AsDict() for result in result_list] + def Format(self, results): + d = [result.AsDict() for result in results] json.dump(d, self.output_file, indent=2) if hasattr(self.output_file, 'flush'): self.output_file.flush() diff --git a/chromium/third_party/catapult/tracing/tracing/mre/local_directory_corpus_driver.py b/chromium/third_party/catapult/tracing/tracing/mre/local_directory_corpus_driver.py index a235f916baf..ee78dd585ca 100644 --- a/chromium/third_party/catapult/tracing/tracing/mre/local_directory_corpus_driver.py +++ b/chromium/third_party/catapult/tracing/tracing/mre/local_directory_corpus_driver.py @@ -66,4 +66,3 @@ class LocalDirectoryCorpusDriver(corpus_driver.CorpusDriver): trace_handles.append(th) return trace_handles - diff --git a/chromium/third_party/catapult/tracing/tracing/mre/map_single_trace.py b/chromium/third_party/catapult/tracing/tracing/mre/map_single_trace.py index f035b693758..92a366062ba 100644 --- a/chromium/third_party/catapult/tracing/tracing/mre/map_single_trace.py +++ b/chromium/third_party/catapult/tracing/tracing/mre/map_single_trace.py @@ -120,7 +120,7 @@ def MapSingleTrace(trace_handle, job, trace_handle.canonical_url, 'Error', 'vinn runtime error while mapping trace.', - e.message, 'Unknown stack')) + str(e), 'Unknown stack')) return result stdout = res.stdout diff --git a/chromium/third_party/catapult/tracing/tracing/mre/map_single_trace_unittest.py b/chromium/third_party/catapult/tracing/tracing/mre/map_single_trace_unittest.py index 6f0f2f43c5d..3cf0658c943 100644 --- a/chromium/third_party/catapult/tracing/tracing/mre/map_single_trace_unittest.py +++ b/chromium/third_party/catapult/tracing/tracing/mre/map_single_trace_unittest.py @@ -51,7 +51,7 @@ class MapSingleTraceTests(unittest.TestCase): self.assertFalse(result.failures) r = result.pairs['result'] - self.assertEquals(r['numProcesses'], 1) + self.assertEqual(r['numProcesses'], 1) def testProcessingGiantTrace(self): @@ -87,7 +87,7 @@ class MapSingleTraceTests(unittest.TestCase): self.assertFalse(result.failures, msg='\n'.join(str(f) for f in result.failures)) r = result.pairs['result'] - self.assertEquals(r['numEvents'], 2000000) + self.assertEqual(r['numEvents'], 2000000) @@ -104,8 +104,8 @@ class MapSingleTraceTests(unittest.TestCase): result = map_single_trace.MapSingleTrace(trace_handle, _Handle(map_script.filename)) - self.assertEquals(len(result.failures), 1) - self.assertEquals(len(result.pairs), 0) + self.assertEqual(len(result.failures), 1) + self.assertEqual(len(result.pairs), 0) f = result.failures[0] self.assertIsInstance(f, map_single_trace.TraceImportFailure) @@ -128,8 +128,8 @@ class MapSingleTraceTests(unittest.TestCase): result = map_single_trace.MapSingleTrace(trace_handle, _Handle(map_script.filename)) - self.assertEquals(len(result.failures), 1) - self.assertEquals(len(result.pairs), 0) + self.assertEqual(len(result.failures), 1) + self.assertEqual(len(result.pairs), 0) f = result.failures[0] self.assertIsInstance(f, map_single_trace.MapFunctionFailure) @@ -149,8 +149,8 @@ class MapSingleTraceTests(unittest.TestCase): result = map_single_trace.MapSingleTrace(trace_handle, _Handle(map_script.filename)) - self.assertEquals(len(result.failures), 1) - self.assertEquals(len(result.pairs), 0) + self.assertEqual(len(result.failures), 1) + self.assertEqual(len(result.pairs), 0) f = result.failures[0] self.assertIsInstance(f, map_single_trace.FunctionLoadingFailure) @@ -168,8 +168,8 @@ class MapSingleTraceTests(unittest.TestCase): result = map_single_trace.MapSingleTrace(trace_handle, _Handle(map_script.filename)) - self.assertEquals(len(result.failures), 1) - self.assertEquals(len(result.pairs), 0) + self.assertEqual(len(result.failures), 1) + self.assertEqual(len(result.pairs), 0) f = result.failures[0] self.assertIsInstance(f, failure.Failure) @@ -188,8 +188,8 @@ class MapSingleTraceTests(unittest.TestCase): result = map_single_trace.MapSingleTrace(trace_handle, _Handle(map_script.filename)) - self.assertEquals(len(result.failures), 1) - self.assertEquals(len(result.pairs), 0) + self.assertEqual(len(result.failures), 1) + self.assertEqual(len(result.pairs), 0) f = result.failures[0] self.assertIsInstance(f, map_single_trace.FunctionNotDefinedFailure) @@ -211,8 +211,8 @@ class MapSingleTraceTests(unittest.TestCase): result = map_single_trace.MapSingleTrace(trace_handle, _Handle(map_script.filename)) - self.assertEquals(len(result.failures), 1) - self.assertEquals(len(result.pairs), 0) + self.assertEqual(len(result.failures), 1) + self.assertEqual(len(result.pairs), 0) f = result.failures[0] self.assertIsInstance(f, map_single_trace.NoResultsAddedFailure) @@ -226,7 +226,7 @@ class MapSingleTraceTests(unittest.TestCase): results.addPair('numProcesses', model.getAllProcesses().length); }; """) - self.assertEquals(results['numProcesses'], 2) + self.assertEqual(results['numProcesses'], 2) def testExecuteTraceMappingCodeWithError(self): test_trace_path = os.path.join(os.path.dirname(__file__), 'test_trace.json') diff --git a/chromium/third_party/catapult/tracing/tracing/mre/map_traces.py b/chromium/third_party/catapult/tracing/tracing/mre/map_traces.py index 749e7acab39..b87c76c57f2 100644 --- a/chromium/third_party/catapult/tracing/tracing/mre/map_traces.py +++ b/chromium/third_party/catapult/tracing/tracing/mre/map_traces.py @@ -60,8 +60,7 @@ def Main(argv): results = runner.Run() if not any(result.failures for result in results): return 0 - else: - return 255 + return 255 finally: if ofile != sys.stdout: ofile.close() diff --git a/chromium/third_party/catapult/tracing/tracing/mre/mre_result.py b/chromium/third_party/catapult/tracing/tracing/mre/mre_result.py index f3d128e23a8..572b3114ccf 100644 --- a/chromium/third_party/catapult/tracing/tracing/mre/mre_result.py +++ b/chromium/third_party/catapult/tracing/tracing/mre/mre_result.py @@ -38,7 +38,7 @@ class MreResult(object): def AddFailure(self, failure): if not isinstance(failure, failure_module.Failure): - raise ValueError('Attempted to add %s as Failure', failure) + raise ValueError('Attempted to add %s as Failure' % failure) self._failures.append(failure) diff --git a/chromium/third_party/catapult/tracing/tracing/mre/mre_result_unittest.py b/chromium/third_party/catapult/tracing/tracing/mre/mre_result_unittest.py index 2bdb5a8f19a..88ab596405e 100644 --- a/chromium/third_party/catapult/tracing/tracing/mre/mre_result_unittest.py +++ b/chromium/third_party/catapult/tracing/tracing/mre/mre_result_unittest.py @@ -37,8 +37,8 @@ class MreResultTests(unittest.TestCase): result_dict = result.AsDict() - self.assertEquals(result_dict['failures'], [failure.AsDict()]) - self.assertEquals(result_dict['pairs'], {'foo': 'bar'}) + self.assertEqual(result_dict['failures'], [failure.AsDict()]) + self.assertEqual(result_dict['pairs'], {'foo': 'bar'}) def testAddingNonFailure(self): result = mre_result.MreResult() diff --git a/chromium/third_party/catapult/tracing/tracing/mre/threaded_work_queue_unittest.py b/chromium/third_party/catapult/tracing/tracing/mre/threaded_work_queue_unittest.py index 9bb086e05e0..e4b98a42a28 100644 --- a/chromium/third_party/catapult/tracing/tracing/mre/threaded_work_queue_unittest.py +++ b/chromium/third_party/catapult/tracing/tracing/mre/threaded_work_queue_unittest.py @@ -23,7 +23,7 @@ class ThreadedWorkQueueTests(unittest.TestCase): wq = threaded_work_queue.ThreadedWorkQueue(num_threads=1) wq.PostAnyThreadTask(Ex) res = wq.Run() - self.assertEquals(res, None) + self.assertEqual(res, None) def _RunSimpleDecrementingTest(self, wq): @@ -39,4 +39,4 @@ class ThreadedWorkQueueTests(unittest.TestCase): wq.PostAnyThreadTask(Decrement) res = wq.Run() - self.assertEquals(res, 314) + self.assertEqual(res, 314) diff --git a/chromium/third_party/catapult/tracing/tracing/proto/histogram_proto.py b/chromium/third_party/catapult/tracing/tracing/proto/histogram_proto.py index 087454926df..de0eedb8e08 100644 --- a/chromium/third_party/catapult/tracing/tracing/proto/histogram_proto.py +++ b/chromium/third_party/catapult/tracing/tracing/proto/histogram_proto.py @@ -2,14 +2,25 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. +from __future__ import absolute_import try: # Note: from tracing.proto import histogram_pb2 would make more sense here, # but unfortunately protoc does not generate __init__.py files if you specify # an out package (at least for the gn proto_library rule). - import histogram_pb2 # pylint:disable=relative-import + import histogram_pb2 HAS_PROTO = True -except ImportError: - HAS_PROTO = False +except ImportError as e: + try: + # crbug/1234919 + # Catapult put the generated histogram_pb2.py in the same source folder, + # while the others (e.g., webrtc) put it in output path. By default we + # try to import from the sys.path. Here allows to try import from the + # source folder as well. + # TODO(wenbinzhang): Clean up import paths to work consistently. + from . import histogram_pb2 + HAS_PROTO = True + except ImportError: + HAS_PROTO = False def _EnsureProto(): @@ -51,14 +62,14 @@ if HAS_PROTO: histogram_pb2.COUNT: 'count', histogram_pb2.SIGMA: 'sigma', } - UNIT_PROTO_MAP = {v: k for k, v in PROTO_UNIT_MAP.iteritems()} + UNIT_PROTO_MAP = {v: k for k, v in PROTO_UNIT_MAP.items()} PROTO_IMPROVEMENT_DIRECTION_MAP = { histogram_pb2.BIGGER_IS_BETTER: 'biggerIsBetter', histogram_pb2.SMALLER_IS_BETTER: 'smallerIsBetter', } IMPROVEMENT_DIRECTION_PROTO_MAP = { - v: k for k, v in PROTO_IMPROVEMENT_DIRECTION_MAP.iteritems() + v: k for k, v in PROTO_IMPROVEMENT_DIRECTION_MAP.items() } @@ -87,4 +98,3 @@ def ProtoFromUnit(unit): proto_unit.improvement_direction = IMPROVEMENT_DIRECTION_PROTO_MAP[parts[1]] return proto_unit - diff --git a/chromium/third_party/catapult/tracing/tracing/proto/histogram_proto_unittest.py b/chromium/third_party/catapult/tracing/tracing/proto/histogram_proto_unittest.py index 0c0edf1a6ac..3bba48eee13 100644 --- a/chromium/third_party/catapult/tracing/tracing/proto/histogram_proto_unittest.py +++ b/chromium/third_party/catapult/tracing/tracing/proto/histogram_proto_unittest.py @@ -3,8 +3,16 @@ # found in the LICENSE file. import unittest +import sys +from six.moves import reload_module -from tracing.proto import histogram_proto +# Some packages, such as protobuf, clobber the google +# namespace package. This prevents that. +# crbug/1233198 +if 'google' in sys.modules: + reload_module(sys.modules['google']) + +from tracing.proto import histogram_proto# pylint: disable=wrong-import-position class HistogramProtoUnittest(unittest.TestCase): diff --git a/chromium/third_party/catapult/tracing/tracing/tests.html b/chromium/third_party/catapult/tracing/tracing/tests.html index 2851bfae495..b2cb2cfd7b9 100644 --- a/chromium/third_party/catapult/tracing/tracing/tests.html +++ b/chromium/third_party/catapult/tracing/tracing/tests.html @@ -14,8 +14,6 @@ found in the LICENSE file. <!-- WebComponents V0 origin trial token for 127.0.0.1:8003. Expires 1 Feb 2020. See https://crbug.com/1021137. --> <meta http-equiv="origin-trial" content="AuwWuUBIJgUHKi5rgEkqYaWR/Rbl/WrQPklxP3Lb9JBLQwdU4ykouPn0hTva5pHumlKNc1TqeZD1GEWo3YXTtQUAAABSeyJvcmlnaW4iOiJodHRwOi8vMTI3LjAuMC4xOjgwMDMiLCJmZWF0dXJlIjoiV2ViQ29tcG9uZW50c1YwIiwiZXhwaXJ5IjoxNjEyMjIzOTk5fQ=="> - <script src="/components/webcomponentsjs/webcomponents.js"></script> - <link rel="shortcut icon" href="data:image/x-icon;base64," type="image/x-icon"> diff --git a/chromium/third_party/catapult/tracing/tracing/trace_data/__init__.py b/chromium/third_party/catapult/tracing/tracing/trace_data/__init__.py index bdb1f26b0b7..a22a6ee39a9 100644 --- a/chromium/third_party/catapult/tracing/tracing/trace_data/__init__.py +++ b/chromium/third_party/catapult/tracing/tracing/trace_data/__init__.py @@ -1,4 +1,3 @@ # 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. - diff --git a/chromium/third_party/catapult/tracing/tracing/trace_data/trace_data.py b/chromium/third_party/catapult/tracing/tracing/trace_data/trace_data.py index e599502c1d4..693f5c110d4 100644 --- a/chromium/third_party/catapult/tracing/tracing/trace_data/trace_data.py +++ b/chromium/third_party/catapult/tracing/tracing/trace_data/trace_data.py @@ -6,11 +6,11 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function import collections +import sys import gzip import json import logging import os -import platform import shutil import subprocess import tempfile @@ -18,6 +18,10 @@ import time import traceback import six +if sys.version_info.major == 3: + JSON_FILE_MODE = 'w+' +else: + JSON_FILE_MODE = 'w+b' try: StringTypes = six.string_types # pylint: disable=invalid-name @@ -126,7 +130,7 @@ class TraceDataBuilder(object): def __exit__(self, *args): self.CleanUpTraceData() - def OpenTraceHandleFor(self, part, suffix): + def OpenTraceHandleFor(self, part, suffix, mode=None): """Open a file handle for writing trace data into it. Args: @@ -139,10 +143,16 @@ class TraceDataBuilder(object): raise TypeError('part must be a TraceDataPart instance') if self._frozen: raise RuntimeError('trace data builder is no longer open for writing') - trace = _TraceItem( - part_name=part.raw_field_name, - handle=tempfile.NamedTemporaryFile( - delete=False, dir=self._temp_dir, suffix=suffix)) + if mode: + trace = _TraceItem( + part_name=part.raw_field_name, + handle=tempfile.NamedTemporaryFile( + mode=mode, delete=False, dir=self._temp_dir, suffix=suffix)) + else: + trace = _TraceItem( + part_name=part.raw_field_name, + handle=tempfile.NamedTemporaryFile( + delete=False, dir=self._temp_dir, suffix=suffix)) self._traces.append(trace) return trace.handle @@ -190,7 +200,7 @@ class TraceDataBuilder(object): suffix = '.json' else: raise TypeError('invalid trace data type') - with self.OpenTraceHandleFor(part, suffix) as handle: + with self.OpenTraceHandleFor(part, suffix, JSON_FILE_MODE) as handle: do_write(data, handle) def Freeze(self): @@ -324,14 +334,7 @@ def SerializeAsHtml(trace_files, html_file, trace_title=None): input_size = sum(os.path.getsize(trace_file) for trace_file in trace_files) - cmd = [] - if platform.system() == 'Windows': - version_cmd = ['python', '-c', - 'import sys\nprint(sys.version_info.major)'] - version = subprocess.check_output(version_cmd) - if version.strip() == '3': - raise RuntimeError('trace2html cannot run with python 3.') - cmd.append('python') + cmd = [sys.executable] cmd.append(_TRACE2HTML_PATH) cmd.extend(trace_files) cmd.extend(['--output', html_file]) diff --git a/chromium/third_party/catapult/tracing/tracing/trace_data/trace_data_unittest.py b/chromium/third_party/catapult/tracing/tracing/trace_data/trace_data_unittest.py index 37874841140..b59bd86e55f 100644 --- a/chromium/third_party/catapult/tracing/tracing/trace_data/trace_data_unittest.py +++ b/chromium/third_party/catapult/tracing/tracing/trace_data/trace_data_unittest.py @@ -59,7 +59,8 @@ class TraceDataBuilderTest(unittest.TestCase): def testAddTraceFileFor(self): original_data = {'msg': 'The answer is 42'} - with tempfile.NamedTemporaryFile(delete=False, suffix='.json') as source: + with tempfile.NamedTemporaryFile( + delete=False, suffix='.json', mode='w+') as source: json.dump(original_data, source) with trace_data.TraceDataBuilder() as builder: builder.AddTraceFileFor(trace_data.CHROME_TRACE_PART, source.name) @@ -72,7 +73,7 @@ class TraceDataBuilderTest(unittest.TestCase): original_data = {'msg': 'The answer is 42'} with trace_data.TraceDataBuilder() as builder: with builder.OpenTraceHandleFor( - trace_data.CHROME_TRACE_PART, suffix='.json') as handle: + trace_data.CHROME_TRACE_PART, suffix='.json', mode='w+') as handle: handle.write(json.dumps(original_data)) out_data = builder.AsData().GetTraceFor(trace_data.CHROME_TRACE_PART) diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/alert_sub_view_test.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/alert_sub_view_test.html index 574cf5f0b86..0f639da1572 100644 --- a/chromium/third_party/catapult/tracing/tracing/ui/analysis/alert_sub_view_test.html +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/alert_sub_view_test.html @@ -15,7 +15,8 @@ found in the LICENSE file. <script> 'use strict'; -tr.b.unittest.testSuite(function() { +// See https://crbug.com/1143376. +tr.b.unittest.skippedTestSuite(function() { const newSliceEx = tr.c.TestUtils.newSliceEx; test('instantiate', function() { @@ -47,7 +48,8 @@ tr.b.unittest.testSuite(function() { assert.lengthOf(columns, 2); }); - test('instantiate_twoAlertsWithRelatedEvents', function() { + // See https://crbug.com/1143376. + skipTest('instantiate_twoAlertsWithRelatedEvents', function() { const slice1 = newSliceEx({title: 'b', start: 0, duration: 0.002}); const slice2 = newSliceEx({title: 'b', start: 1, duration: 0.002}); diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/container_memory_dump_sub_view_test.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/container_memory_dump_sub_view_test.html index 974837f545e..92be2bedc27 100644 --- a/chromium/third_party/catapult/tracing/tracing/ui/analysis/container_memory_dump_sub_view_test.html +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/container_memory_dump_sub_view_test.html @@ -300,7 +300,8 @@ tr.b.unittest.testSuite(function() { }, containerEl); }); - test('instantiate_differentProcessMemoryDumps', function() { + // See https://crbug.com/1143376. + skipTest('instantiate_differentProcessMemoryDumps', function() { const globalMemoryDumps = tr.ui.analysis.createMultipleTestGlobalMemoryDumps(); // 2 dumps in Process 1, 3 dumps in Process 2, and 1 dump in Process 4 diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/frame_power_usage_chart_test.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/frame_power_usage_chart_test.html index 04ba9388852..43772870d97 100644 --- a/chromium/third_party/catapult/tracing/tracing/ui/analysis/frame_power_usage_chart_test.html +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/frame_power_usage_chart_test.html @@ -47,7 +47,8 @@ tr.b.unittest.testSuite(function() { assert.isUndefined(chart.chart); }); - test('instantiate_oneFrame', function() { + // See https://crbug.com/1143376. + skipTest('instantiate_oneFrame', function() { const series = new tr.model.PowerSeries(new tr.Model().device); const vSyncTimestamps = [0]; @@ -70,7 +71,8 @@ tr.b.unittest.testSuite(function() { assert.sameDeepMembers(chart.chart.data, expectedChartData); }); - test('instantiate_twoFrames', function() { + // See https://crbug.com/1143376. + skipTest('instantiate_twoFrames', function() { const series = new tr.model.PowerSeries(new tr.Model().device); const vSyncTimestamps = [0, 4]; @@ -101,7 +103,8 @@ tr.b.unittest.testSuite(function() { assert.sameDeepMembers(chart.chart.data, expectedChartData); }); - test('instantiate_twoFramesDifferentXValues', function() { + // See https://crbug.com/1143376. + skipTest('instantiate_twoFramesDifferentXValues', function() { const series = new tr.model.PowerSeries(new tr.Model().device); // Power samples taken at 0, 1, 2, and 3s after frame start. @@ -134,7 +137,8 @@ tr.b.unittest.testSuite(function() { assert.sameDeepMembers(chart.chart.data, expectedChartData); }); - test('instantiate_samplesBeforeFirstVSync', function() { + // See https://crbug.com/1143376. + skipTest('instantiate_samplesBeforeFirstVSync', function() { const series = new tr.model.PowerSeries(new tr.Model().device); const vSyncTimestamps = [4]; @@ -182,7 +186,8 @@ tr.b.unittest.testSuite(function() { assert.isUndefined(chart.chart); }); - test('instantiate_vSyncsAfterLastPowerSample', function() { + // See https://crbug.com/1143376. + skipTest('instantiate_vSyncsAfterLastPowerSample', function() { const series = new tr.model.PowerSeries(new tr.Model().device); const vSyncTimestamps = [0, 4, 8, 12]; @@ -232,8 +237,8 @@ tr.b.unittest.testSuite(function() { assert.isUndefined(chart.chart); }); - - test('instantiate_samplesNotInChronologicalOrder', function() { + // See https://crbug.com/1143376. + skipTest('instantiate_samplesNotInChronologicalOrder', function() { const series = new tr.model.PowerSeries(new tr.Model().device); const vSyncTimestamps = [0, 4]; diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/generic_object_view.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/generic_object_view.html index e0c33df1231..969a9871a90 100644 --- a/chromium/third_party/catapult/tracing/tracing/ui/analysis/generic_object_view.html +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/generic_object_view.html @@ -95,7 +95,7 @@ Polymer({ if (!(object instanceof Object)) { const type = typeof object; if (type !== 'string') { - return this.appendSimpleText_(label, indent, object, suffix); + return this.appendSimpleText_(label, indent, String(object), suffix); } let objectReplaced = false; if ((object[0] === '{' && object[object.length - 1] === '}') || diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/generic_object_view_test.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/generic_object_view_test.html index 2e7812e2730..cca52ae3ae2 100644 --- a/chromium/third_party/catapult/tracing/tracing/ui/analysis/generic_object_view_test.html +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/generic_object_view_test.html @@ -62,7 +62,8 @@ tr.b.unittest.testSuite(function() { assert.strictEqual(view.$.content.children.length, 3); }); - test('booleanValue', function() { + // See https://crbug.com/1143376. + skipTest('booleanValue', function() { const view = document.createElement('tr-ui-a-generic-object-view'); view.object = false; assert.strictEqual(Polymer.dom(view.$.content).textContent, 'false'); @@ -196,7 +197,8 @@ tr.b.unittest.testSuite(function() { view.$.content, 'tr-v-ui-scalar-span')); }); - test('scalarValue', function() { + // See https://crbug.com/1143376. + skipTest('scalarValue', function() { const view = document.createElement('tr-ui-a-generic-object-view'); view.object = new tr.b.Scalar(tr.b.Unit.byName.normalizedPercentage, .3); diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_heap_details_breakdown_view.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_heap_details_breakdown_view.html index 9d17e39ce85..15ad35863a1 100644 --- a/chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_heap_details_breakdown_view.html +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/memory_dump_heap_details_breakdown_view.html @@ -141,7 +141,7 @@ tr.exportTo('tr.ui.analysis', function() { if (!this.displayedNode_.parentNode) break; // Enter the parent node upon pressing backspace. - const viewEvent = new tr.b.Event('enter-node'); + const viewEvent = new tr.b.Event('enter-node', true /*bubbles*/); viewEvent.node = this.displayedNode_.parentNode; this.dispatchEvent(viewEvent); keyHandled = true; @@ -180,7 +180,7 @@ tr.exportTo('tr.ui.analysis', function() { ready() { this.$.table.addEventListener('step-into', function(tableEvent) { - const viewEvent = new tr.b.Event('enter-node'); + const viewEvent = new tr.b.Event('enter-node', true /*bubbles*/); viewEvent.node = tableEvent.tableRow; this.dispatchEvent(viewEvent); }.bind(this)); diff --git a/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_event_sub_view_test.html b/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_event_sub_view_test.html index 9958b7db81c..edabfc7fe54 100644 --- a/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_event_sub_view_test.html +++ b/chromium/third_party/catapult/tracing/tracing/ui/analysis/multi_event_sub_view_test.html @@ -14,7 +14,8 @@ found in the LICENSE file. <script> 'use strict'; -tr.b.unittest.testSuite(function() { +// See https://crbug.com/1143376. +tr.b.unittest.skippedTestSuite(function() { const Model = tr.Model; const Thread = tr.model.Thread; const EventSet = tr.model.EventSet; diff --git a/chromium/third_party/catapult/tracing/tracing/ui/base/base.html b/chromium/third_party/catapult/tracing/tracing/ui/base/base.html index e0ca9e23c6b..e24c88c653a 100644 --- a/chromium/third_party/catapult/tracing/tracing/ui/base/base.html +++ b/chromium/third_party/catapult/tracing/tracing/ui/base/base.html @@ -5,6 +5,8 @@ Use 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" data-suppress-import-order> + <link rel="import" href="/tracing/ui/base/polymer_preload.html" data-suppress-import-order> <!-- diff --git a/chromium/third_party/catapult/tracing/tracing/ui/base/camera_test.html b/chromium/third_party/catapult/tracing/tracing/ui/base/camera_test.html index 7083856c539..090d8f70743 100644 --- a/chromium/third_party/catapult/tracing/tracing/ui/base/camera_test.html +++ b/chromium/third_party/catapult/tracing/tracing/ui/base/camera_test.html @@ -11,7 +11,8 @@ found in the LICENSE file. <script> 'use strict'; -tr.b.unittest.testSuite(function() { +// See https://crbug.com/1111537 +tr.b.unittest.skippedTestSuite(function() { function createQuads() { const quads = [ tr.b.math.Quad.fromXYWH(-500, -500, 30, 30), // 4 corners diff --git a/chromium/third_party/catapult/tracing/tracing/ui/base/container_that_decorates_its_children_test.html b/chromium/third_party/catapult/tracing/tracing/ui/base/container_that_decorates_its_children_test.html index 54417b3ded6..72d66adfd15 100644 --- a/chromium/third_party/catapult/tracing/tracing/ui/base/container_that_decorates_its_children_test.html +++ b/chromium/third_party/catapult/tracing/tracing/ui/base/container_that_decorates_its_children_test.html @@ -45,7 +45,8 @@ tr.b.unittest.testSuite(function() { assert.isTrue(container.children[2].decorated); }); - test('clearUsingTextContent', function() { + // See https://crbug.com/1111537 + skipTest('clearUsingTextContent', function() { const c0 = createChild(); const container = new SimpleContainer(); Polymer.dom(container).appendChild(c0); @@ -61,7 +62,8 @@ tr.b.unittest.testSuite(function() { assert.isFalse(c0.decorated); }); - test('insertNewBefore', function() { + // See https://crbug.com/1111537 + skipTest('insertNewBefore', function() { const c0 = createChild(); const c1 = createChild(); const container = new SimpleContainer(); @@ -82,7 +84,8 @@ tr.b.unittest.testSuite(function() { assert.isTrue(c1.decorated); }); - test('testReplace', function() { + // See https://crbug.com/1111537 + skipTest('testReplace', function() { const c0 = createChild(); const c1 = createChild(); const container = new SimpleContainer(); diff --git a/chromium/third_party/catapult/tracing/tracing/ui/base/dom_helpers_test.html b/chromium/third_party/catapult/tracing/tracing/ui/base/dom_helpers_test.html index 46313143710..4f35f0467cd 100644 --- a/chromium/third_party/catapult/tracing/tracing/ui/base/dom_helpers_test.html +++ b/chromium/third_party/catapult/tracing/tracing/ui/base/dom_helpers_test.html @@ -9,7 +9,7 @@ found in the LICENSE file. 'use strict'; tr.b.unittest.testSuite(function() { - const THIS_DOC = document.currentScript.ownerDocument; + const THIS_DOC = document._currentScript.ownerDocument; test('simpleSpanAndDiv', function() { const divEl = tr.ui.b.createDiv({ @@ -145,25 +145,25 @@ tr.b.unittest.testSuite(function() { let node = tr.ui.b.asHTMLOrTextNode('Hello, World!'); assert.instanceOf(node, Node); assert.strictEqual(Polymer.dom(node).textContent, 'Hello, World!'); - assert.strictEqual(node.ownerDocument, document); + assert.strictEqual(unwrap(node.ownerDocument), document); // Custom owner document. node = tr.ui.b.asHTMLOrTextNode('Bye, World!', THIS_DOC); assert.instanceOf(node, Node); assert.strictEqual(Polymer.dom(node).textContent, 'Bye, World!'); - assert.strictEqual(node.ownerDocument, THIS_DOC); + assert.strictEqual(unwrap(node.ownerDocument), unwrap(THIS_DOC)); }); test('asHTMLOrTextNode_node', function() { // Node object. Owner document should NOT be modified. let node = document.createTextNode('Hi', THIS_DOC); assert.strictEqual(tr.ui.b.asHTMLOrTextNode(node), node); - assert.strictEqual(node.ownerDocument, document); + assert.strictEqual(unwrap(node.ownerDocument), unwrap(document)); // HTMLElement object. Owner document should NOT be modified. node = THIS_DOC.createElement('div'); assert.strictEqual(tr.ui.b.asHTMLOrTextNode(node), node); - assert.strictEqual(node.ownerDocument, THIS_DOC); + assert.strictEqual(unwrap(node.ownerDocument), unwrap(THIS_DOC)); }); }); </script> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/base/dropdown_test.html b/chromium/third_party/catapult/tracing/tracing/ui/base/dropdown_test.html index bdc118a4d09..e0953f00e84 100644 --- a/chromium/third_party/catapult/tracing/tracing/ui/base/dropdown_test.html +++ b/chromium/third_party/catapult/tracing/tracing/ui/base/dropdown_test.html @@ -27,7 +27,8 @@ tr.b.unittest.testSuite(function() { elem.dispatchEvent(clickEvent); } - test('basic', function() { + // See https://crbug.com/1143376. + skipTest('basic', function() { const dd = document.createElement('tr-ui-b-dropdown'); dd.style.marginLeft = '50px'; dd.style.width = '50px'; diff --git a/chromium/third_party/catapult/tracing/tracing/ui/base/hotkey_controller_test.html b/chromium/third_party/catapult/tracing/tracing/ui/base/hotkey_controller_test.html index cff10cb775c..ab7a018009e 100644 --- a/chromium/third_party/catapult/tracing/tracing/ui/base/hotkey_controller_test.html +++ b/chromium/third_party/catapult/tracing/tracing/ui/base/hotkey_controller_test.html @@ -11,7 +11,8 @@ found in the LICENSE file. <script> 'use strict'; -tr.b.unittest.testSuite(function() { +// See https://crbug.com/1143376. +tr.b.unittest.skippedTestSuite(function() { const KeyEventManager = tr.b.KeyEventManager; function newKeyEvent(eventType, dict) { diff --git a/chromium/third_party/catapult/tracing/tracing/ui/base/info_bar_group_test.html b/chromium/third_party/catapult/tracing/tracing/ui/base/info_bar_group_test.html index 304d48ede37..7dc93a60272 100644 --- a/chromium/third_party/catapult/tracing/tracing/ui/base/info_bar_group_test.html +++ b/chromium/third_party/catapult/tracing/tracing/ui/base/info_bar_group_test.html @@ -21,7 +21,8 @@ tr.b.unittest.testSuite(function() { this.addHTMLOutput(infoBarGroup); }); - test('group-populate-then-clear', function() { + // See https://crbug.com/1143376. + skipTest('group-populate-then-clear', function() { const infoBarGroup = document.createElement('tr-ui-b-info-bar-group'); infoBarGroup.addMessage( 'Message 1', diff --git a/chromium/third_party/catapult/tracing/tracing/ui/base/tab_view_test.html b/chromium/third_party/catapult/tracing/tracing/ui/base/tab_view_test.html index d5e9c19e672..7912b58d344 100644 --- a/chromium/third_party/catapult/tracing/tracing/ui/base/tab_view_test.html +++ b/chromium/third_party/catapult/tracing/tracing/ui/base/tab_view_test.html @@ -28,7 +28,8 @@ Polymer({ behaviors: [nonSubViewBehavior] }); -tr.b.unittest.testSuite(function() { +// See https://crbug.com/1143376. +tr.b.unittest.skippedTestSuite(function() { function createPowerSampleSubView() { const model = tr.c.TestUtils.newModel(function(m) { m.device.powerSeries = new tr.model.PowerSeries(m.device); diff --git a/chromium/third_party/catapult/tracing/tracing/ui/base/table_test.html b/chromium/third_party/catapult/tracing/tracing/ui/base/table_test.html index 73e8aca4418..4f768817c9d 100644 --- a/chromium/third_party/catapult/tracing/tracing/ui/base/table_test.html +++ b/chromium/third_party/catapult/tracing/tracing/ui/base/table_test.html @@ -1052,7 +1052,8 @@ tr.b.unittest.testSuite(function() { assert.isFalse(table.getExpandedForTableRow(rows[0])); }); - test('focus_empty', function() { + // See https://crbug.com/1143376. + skipTest('focus_empty', function() { const table = createSimpleOneColumnNestedTable(); table.tableRows = []; table.emptyValue = 'This table is left intentionally empty'; @@ -1105,7 +1106,8 @@ tr.b.unittest.testSuite(function() { assert.isUndefined(table.selectedColumnIndex); }); - test('focus_rows', function() { + // See https://crbug.com/1143376. + skipTest('focus_rows', function() { const table = createSimpleOneColumnNestedTable(); table.selectionMode = SelectionMode.ROW; this.addHTMLOutput(table); @@ -1165,7 +1167,8 @@ tr.b.unittest.testSuite(function() { assert.isUndefined(table.selectedColumnIndex); }); - test('focus_cells', function() { + // See https://crbug.com/1143376. + skipTest('focus_cells', function() { const table = createMultiColumnNestedTable(); table.selectionMode = SelectionMode.CELL; this.addHTMLOutput(table); @@ -1232,7 +1235,8 @@ tr.b.unittest.testSuite(function() { assert.strictEqual(table.selectedColumnIndex, 1); }); - test('focus_allCellsUnselectable', function() { + // See https://crbug.com/1143376. + skipTest('focus_allCellsUnselectable', function() { const table = createMultiColumnNestedTable(); table.selectionMode = SelectionMode.CELL; for (const c of table.tableColumns) { diff --git a/chromium/third_party/catapult/tracing/tracing/ui/base/utils.html b/chromium/third_party/catapult/tracing/tracing/ui/base/utils.html index cca1003cc06..87b2f344b1f 100644 --- a/chromium/third_party/catapult/tracing/tracing/ui/base/utils.html +++ b/chromium/third_party/catapult/tracing/tracing/ui/base/utils.html @@ -60,19 +60,7 @@ tr.exportTo('tr.ui.b', function() { undefined, {minimumFractionDigits: 3, maximumFractionDigits: 3}); } - /** - * Returns true if |name| is the name of an unknown HTML element. Registered - * polymer elements are known, so this returns false. Typos of registered - * polymer element names are unknown, so this returns true for typos. - * - * @return {boolean} - */ - function isUnknownElementName(name) { - return document.createElement(name) instanceof HTMLUnknownElement; - } - return { - isUnknownElementName, toThreeDigitLocaleString, instantiateTemplate, windowRectForElement, diff --git a/chromium/third_party/catapult/tracing/tracing/ui/base/utils_test.html b/chromium/third_party/catapult/tracing/tracing/ui/base/utils_test.html index 4ab76e1d221..aa84b3b40df 100644 --- a/chromium/third_party/catapult/tracing/tracing/ui/base/utils_test.html +++ b/chromium/third_party/catapult/tracing/tracing/ui/base/utils_test.html @@ -37,7 +37,8 @@ Polymer({ tr.b.unittest.testSuite(function() { const THIS_DOC = document.currentScript.ownerDocument; - test('instantiateTemplatePolymer', function() { + // See https://crbug.com/1143376. + skipTest('instantiateTemplatePolymer', function() { const e = tr.ui.b.instantiateTemplate( '#instantiate-template-polymer-test', THIS_DOC); @@ -45,7 +46,8 @@ tr.b.unittest.testSuite(function() { assert.strictEqual(e.children[0].testProperty, 'Test'); }); - test('instantiateTemplateMultipleTemplates', function() { + // See https://crbug.com/1143376. + skipTest('instantiateTemplateMultipleTemplates', function() { const outerElement = tr.ui.b.instantiateTemplate( '#multiple-template-test', THIS_DOC); diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/profiling_view.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/profiling_view.html index 26d01ad27cf..08c5b67c69f 100644 --- a/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/profiling_view.html +++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/profiling_view.html @@ -45,6 +45,21 @@ x-timeline-view-buttons { display: flex; align-items: center; } + +#perfetto-banner-outer { + display: flex; + align-items: center; + flex-direction: column; + margin: auto 0; +} + +#perfetto-banner-inner { + margin: auto 0; +} + +#perfetto-logo-caption { + margin: 50px; +} </style> <template id="profiling-view-template"> @@ -55,7 +70,18 @@ x-timeline-view-buttons { <button id="load-button">Load</button> </x-timeline-view-buttons> <tr-ui-timeline-view> - <track-view-container id='track_view_container'></track-view-container> + <track-view-container id='track_view_container'> + <div id="perfetto-banner-outer"> + <div id="perfetto-banner-inner"> + <img + src="data:image/jpeg;base64,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"></a> + <div id="perfetto-logo-caption"> + Try the new <a href="https://ui.perfetto.dev">Perfetto UI</a>! + <a href="https://chromium.googlesource.com/catapult/+/refs/heads/main/tracing/docs/perfetto.md">Learn more</a>. + </div> + </div> + </div> + </track-view-container> </tr-ui-timeline-view> </template> diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/profiling_view_test.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/profiling_view_test.html index 1344c1a36f9..11de7bca881 100644 --- a/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/profiling_view_test.html +++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/profiling_view_test.html @@ -15,7 +15,8 @@ found in the LICENSE file. <script> 'use strict'; -tr.b.unittest.testSuite(function() { +// See https://crbug.com/1143376. +tr.b.unittest.skippedTestSuite(function() { const Base64 = tr.b.Base64; const testData = [ {name: 'a', args: {}, pid: 52, ts: 15000, cat: 'foo', tid: 53, ph: 'B'}, diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/record_controller_test.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/record_controller_test.html index e3e0438f3a2..1f6aaf36da7 100644 --- a/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/record_controller_test.html +++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/record_controller_test.html @@ -13,7 +13,8 @@ found in the LICENSE file. <script> 'use strict'; -tr.b.unittest.testSuite(function() { +// See https://crbug.com/1143376. +tr.b.unittest.skippedTestSuite(function() { const testData = [ {name: 'a', args: {}, pid: 52, ts: 15000, cat: 'foo', tid: 53, ph: 'B'}, {name: 'a', args: {}, pid: 52, ts: 19000, cat: 'foo', tid: 53, ph: 'E'}, diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/record_selection_dialog_test.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/record_selection_dialog_test.html index 7c62b487305..b286c065ec1 100644 --- a/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/record_selection_dialog_test.html +++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/record_selection_dialog_test.html @@ -12,7 +12,8 @@ found in the LICENSE file. <script> 'use strict'; -tr.b.unittest.testSuite(function() { +// See https://crbug.com/1143376. +tr.b.unittest.skippedTestSuite(function() { test('instantitate', function() { const showButton = document.createElement('button'); Polymer.dom(showButton).textContent = 'Show record selection dialog'; diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/display_item_debugger_test.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/display_item_debugger_test.html index c10d6995db3..067e8dc820d 100644 --- a/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/display_item_debugger_test.html +++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/display_item_debugger_test.html @@ -11,7 +11,8 @@ found in the LICENSE file. <script> 'use strict'; -tr.b.unittest.testSuite(function() { +// See https://crbug.com/1143376. +tr.b.unittest.skippedTestSuite(function() { test('instantiate', function() { const displayItemList = new tr.e.cc.DisplayItemListSnapshot( {id: '31415'}, diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/layer_tree_host_impl_view_test.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/layer_tree_host_impl_view_test.html index 1831be24618..bc2b35939d5 100644 --- a/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/layer_tree_host_impl_view_test.html +++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/layer_tree_host_impl_view_test.html @@ -18,7 +18,8 @@ found in the LICENSE file. <script> 'use strict'; -tr.b.unittest.testSuite(function() { +// See https://crbug.com/1143376. +tr.b.unittest.skippedTestSuite(function() { test('instantiate', function() { const m = tr.c.TestUtils.newModelWithEvents([g_catLTHIEvents]); const p = Object.values(m.processes)[0]; diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/layer_tree_quad_stack_view_test.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/layer_tree_quad_stack_view_test.html index 66932ae785f..e8d31358ee5 100644 --- a/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/layer_tree_quad_stack_view_test.html +++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/layer_tree_quad_stack_view_test.html @@ -17,7 +17,8 @@ found in the LICENSE file. <script> 'use strict'; -tr.b.unittest.testSuite(function() { +// See https://crbug.com/1143376. +tr.b.unittest.skippedTestSuite(function() { test('tileCoverageRectCount', function() { const m = tr.c.TestUtils.newModelWithEvents([g_catLTHIEvents]); const p = m.processes[1]; diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/layer_view_test.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/layer_view_test.html index ed3de7b87e1..0694f977a4b 100644 --- a/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/layer_view_test.html +++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/layer_view_test.html @@ -17,7 +17,8 @@ found in the LICENSE file. <script> 'use strict'; -tr.b.unittest.testSuite(function() { +// See https://crbug.com/1143376. +tr.b.unittest.skippedTestSuite(function() { test('instantiate', function() { const m = tr.c.TestUtils.newModelWithEvents([g_catLTHIEvents]); const p = m.processes[1]; diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/raster_task_view_test.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/raster_task_view_test.html index 56767cfcc89..90488f46b5f 100644 --- a/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/raster_task_view_test.html +++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/raster_task_view_test.html @@ -47,7 +47,8 @@ tr.b.unittest.testSuite(function() { this.addHTMLOutput(view); }); - test('analysisViewIntegration', function() { + // See https://crbug.com/1143376. + skipTest('analysisViewIntegration', function() { const selection = createSelection(); const timelineView = {model: selection.model}; diff --git a/chromium/third_party/catapult/tracing/tracing/ui/timeline_view.html b/chromium/third_party/catapult/tracing/tracing/ui/timeline_view.html index 723e120ebc6..288ab5df807 100644 --- a/chromium/third_party/catapult/tracing/tracing/ui/timeline_view.html +++ b/chromium/third_party/catapult/tracing/tracing/ui/timeline_view.html @@ -120,7 +120,13 @@ found in the LICENSE file. <div id="control"> <div id="bar"> <div id="left_controls"></div> - <div id="title">^_^</div> + <div id="title"> + ^_^ + <div style="padding-left: 3em"> + Try the new <a href="https://ui.perfetto.dev">Perfetto UI</a>! + <a href="https://chromium.googlesource.com/catapult/+/refs/heads/main/tracing/docs/perfetto.md">Learn more</a> + </div> + </div> <div id="right_controls"> <tr-ui-b-dropdown id="flow_event_filter_dropdown" label="Flow events"></tr-ui-b-dropdown> <tr-ui-b-dropdown id="process_filter_dropdown" label="Processes"></tr-ui-b-dropdown> @@ -140,8 +146,6 @@ found in the LICENSE file. <div id="collapsing_controls"></div> <tr-ui-b-info-bar-group id="import-warnings"> </tr-ui-b-info-bar-group> - <tr-ui-b-info-bar-group id="polyfill-warning"> - </tr-ui-b-info-bar-group> </div> <middle-container> <slot></slot> @@ -161,9 +165,7 @@ found in the LICENSE file. const POLYFILL_WARNING_MESSAGE = 'Trace Viewer is running with WebComponentsV0 polyfill, and some ' + - 'features may be broken. As a workaround, you may try running chrome ' + - 'with "--enable-blink-features=ShadowDOMV0,CustomElementsV0,HTMLImports" ' + - 'flag. See crbug.com/1036492.'; + 'features may be broken. See crbug.com/1036492.'; Polymer({ is: 'tr-ui-timeline-view', @@ -278,19 +280,6 @@ Polymer({ if (this.polyfillWarnedOnce_) return; console.warn(POLYFILL_WARNING_MESSAGE); // eslint-disable-line no-console this.polyfillWarnedOnce_ = true; - - // You can set window.__hideTraceViewerPolyfillWarning = true somewhere - // in your setup code if you embed trace viewer and want to hide the - // UI polyfill warning. - if (!window.__hideTraceViewerPolyfillWarning) { - const polyfillWarningsEl = - Polymer.dom(this.root).querySelector('#polyfill-warning'); - polyfillWarningsEl.addMessage( - POLYFILL_WARNING_MESSAGE, [{ - buttonText: 'Hide', - onClick: () => polyfillWarningsEl.clearMessages() - }]); - } }, updateDocumentFavicon() { diff --git a/chromium/third_party/catapult/tracing/tracing/ui/tracks/async_slice_group_track.html b/chromium/third_party/catapult/tracing/tracing/ui/tracks/async_slice_group_track.html index d922030ce70..49a31c0e1a0 100644 --- a/chromium/third_party/catapult/tracing/tracing/ui/tracks/async_slice_group_track.html +++ b/chromium/third_party/catapult/tracing/tracing/ui/tracks/async_slice_group_track.html @@ -30,6 +30,9 @@ tr.exportTo('tr.ui.tracks', function() { tr.ui.tracks.MultiRowTrack.prototype.decorate.call(this, viewport); Polymer.dom(this).classList.add('async-slice-group-track'); this.group_ = undefined; + // Set the collapse threshold so we don't collapse by default, but the + // user can explicitly collapse if they want it. + this.defaultToCollapsedWhenSubRowCountMoreThan = 30; }, addSubTrack_(slices) { @@ -94,7 +97,8 @@ tr.exportTo('tr.ui.tracks', function() { * a single track but provide no information themselves. */ function stripSlice_(slice) { - if (slice.subSlices !== undefined && slice.subSlices.length === 1) { + if (slice.subSlices !== undefined && slice.subSlices.length === 1 + && !slice.args) { const subSlice = slice.subSlices[0]; if (tr.b.math.approximately(subSlice.start, slice.start, 1) && tr.b.math.approximately(subSlice.duration, slice.duration, 1)) { @@ -112,6 +116,9 @@ tr.exportTo('tr.ui.tracks', function() { function makeLevelSubRows_(slices) { const rows = []; const putSlice = (slice, level) => { + if (slice.hidden) { + return; + } while (rows.length <= level) { rows.push([]); } diff --git a/chromium/third_party/catapult/tracing/tracing/value/diagnostics/alert_groups.html b/chromium/third_party/catapult/tracing/tracing/value/diagnostics/alert_groups.html new file mode 100644 index 00000000000..b7d1382c3ac --- /dev/null +++ b/chromium/third_party/catapult/tracing/tracing/value/diagnostics/alert_groups.html @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<!-- +Copyright 2020 The Chromium Authors. All rights reserved. +Use of this source code is governed by a BSD-style license that can be +found in the LICENSE file. +--> + +<link rel="import" href="/tracing/base/base.html"> + +<script> +'use strict'; + +tr.exportTo('tr.v.d', function() { + // All alert groups used in TBMv2 metrics. If you add a new group, add it + // here first to make sure you're not accidentally reusing a name. + // The keys and values are same here by convention. + const ALERT_GROUPS = { + // For metrics measuring cpu usage in general, like cpu time percentage + // metric. + CPU_USAGE: 'cpu_usage', + // For paint related loading metrics like First Contentful Paint and Largest + // Contentful Paint + LOADING_PAINT: 'loading_paint', + // For interactivity related loading metrics like Time to Interactive and + // Total Blocking Time. + LOADING_INTERACTIVITY: 'loading_interactivity', + // For layour related metrics like Cumulative Layout Shift. + LOADING_LAYOUT: 'loading_layout', + }; + + return { + ALERT_GROUPS, + }; +}); +</script> diff --git a/chromium/third_party/catapult/tracing/tracing/value/diagnostics/breakdown.py b/chromium/third_party/catapult/tracing/tracing/value/diagnostics/breakdown.py index e37d85753b4..2605e6c343d 100644 --- a/chromium/third_party/catapult/tracing/tracing/value/diagnostics/breakdown.py +++ b/chromium/third_party/catapult/tracing/tracing/value/diagnostics/breakdown.py @@ -53,10 +53,10 @@ class Breakdown(diagnostic.Diagnostic): return breakdown @staticmethod - def FromDict(d): + def FromDict(dct): result = Breakdown() - result._color_scheme = d.get('colorScheme') - for name, value in d['values'].items(): + result._color_scheme = dct.get('colorScheme') + for name, value in dct['values'].items(): if value in ['NaN', 'Infinity', '-Infinity']: value = float(value) result.Set(name, value) diff --git a/chromium/third_party/catapult/tracing/tracing/value/diagnostics/date_range.py b/chromium/third_party/catapult/tracing/tracing/value/diagnostics/date_range.py index 1fe181319fb..eb30a71efe8 100644 --- a/chromium/third_party/catapult/tracing/tracing/value/diagnostics/date_range.py +++ b/chromium/third_party/catapult/tracing/tracing/value/diagnostics/date_range.py @@ -1,16 +1,17 @@ # 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. +from __future__ import absolute_import import datetime +from tracing.value import histogram from tracing.value.diagnostics import diagnostic class DateRange(diagnostic.Diagnostic): - __slots__ = '_range', + __slots__ = ('_range',) def __init__(self, ms): - from tracing.value import histogram super(DateRange, self).__init__() self._range = histogram.Range() self._range.AddValue(ms) @@ -88,4 +89,3 @@ class DateRange(diagnostic.Diagnostic): def AddDiagnostic(self, other_diagnostic): self._range.AddRange(other_diagnostic._range) - diff --git a/chromium/third_party/catapult/tracing/tracing/value/diagnostics/diagnostic.py b/chromium/third_party/catapult/tracing/tracing/value/diagnostics/diagnostic.py index 457c3fb9645..074bfd0175d 100644 --- a/chromium/third_party/catapult/tracing/tracing/value/diagnostics/diagnostic.py +++ b/chromium/third_party/catapult/tracing/tracing/value/diagnostics/diagnostic.py @@ -15,7 +15,7 @@ from tracing.value.diagnostics import all_diagnostics class Diagnostic(object): - __slots__ = '_guid', + __slots__ = ('_guid',) # Ensure that new subclasses remember to specify __slots__ in order to prevent # regressing memory consumption: diff --git a/chromium/third_party/catapult/tracing/tracing/value/diagnostics/generic_set.py b/chromium/third_party/catapult/tracing/tracing/value/diagnostics/generic_set.py index 31966b02b66..8fd24f5e6c2 100644 --- a/chromium/third_party/catapult/tracing/tracing/value/diagnostics/generic_set.py +++ b/chromium/third_party/catapult/tracing/tracing/value/diagnostics/generic_set.py @@ -3,6 +3,7 @@ # found in the LICENSE file. import json +import six from tracing.proto import histogram_proto from tracing.value.diagnostics import diagnostic @@ -106,12 +107,13 @@ class GenericSet(diagnostic.Diagnostic): for value_json in d.values: try: values.append(json.loads(value_json)) - except (TypeError, ValueError): - raise TypeError('The value %s is not valid JSON. You cannot pass naked ' - 'strings as a GenericSet value, for instance; they ' - 'have to be quoted. Therefore, 1234 is a valid value ' - '(int), "abcd" is a valid value (string), but abcd is ' - 'not valid.' % value_json) + except (TypeError, ValueError) as e: + six.raise_from( + TypeError('The value %s is not valid JSON. You cannot pass naked ' + 'strings as a GenericSet value, for instance; they ' + 'have to be quoted. Therefore, 1234 is a valid value ' + '(int), "abcd" is a valid value (string), but abcd is ' + 'not valid.' % value_json), e) return GenericSet(values) diff --git a/chromium/third_party/catapult/tracing/tracing/value/diagnostics/related_event_set.py b/chromium/third_party/catapult/tracing/tracing/value/diagnostics/related_event_set.py index be422e9f788..590ea5c22cc 100644 --- a/chromium/third_party/catapult/tracing/tracing/value/diagnostics/related_event_set.py +++ b/chromium/third_party/catapult/tracing/tracing/value/diagnostics/related_event_set.py @@ -11,7 +11,7 @@ from tracing.value.diagnostics import diagnostic class RelatedEventSet(diagnostic.Diagnostic): - __slots__ = '_events_by_stable_id', + __slots__ = ('_events_by_stable_id',) def __init__(self, events=()): super(RelatedEventSet, self).__init__() @@ -47,9 +47,9 @@ class RelatedEventSet(diagnostic.Diagnostic): return events @staticmethod - def FromDict(d): + def FromDict(dct): result = RelatedEventSet() - for event in d['events']: + for event in dct['events']: result.Add(event) return result @@ -68,7 +68,7 @@ class RelatedEventSet(diagnostic.Diagnostic): for e in self] def _AsDictInto(self, d): - d['events'] = [event for event in self] + d['events'] = list(self) def _AsProto(self): raise NotImplementedError() diff --git a/chromium/third_party/catapult/tracing/tracing/value/diagnostics/related_name_map.py b/chromium/third_party/catapult/tracing/tracing/value/diagnostics/related_name_map.py index c644da95bf3..7f17b9890b0 100644 --- a/chromium/third_party/catapult/tracing/tracing/value/diagnostics/related_name_map.py +++ b/chromium/third_party/catapult/tracing/tracing/value/diagnostics/related_name_map.py @@ -10,7 +10,7 @@ from tracing.value.diagnostics import diagnostic class RelatedNameMap(diagnostic.Diagnostic): - __slots__ = '_map', + __slots__ = ('_map',) def __init__(self, entries=None): super(RelatedNameMap, self).__init__() diff --git a/chromium/third_party/catapult/tracing/tracing/value/diagnostics/reserved_infos.py b/chromium/third_party/catapult/tracing/tracing/value/diagnostics/reserved_infos.py index e3087e5719f..ca975908acc 100644 --- a/chromium/third_party/catapult/tracing/tracing/value/diagnostics/reserved_infos.py +++ b/chromium/third_party/catapult/tracing/tracing/value/diagnostics/reserved_infos.py @@ -25,11 +25,13 @@ class _Info(object): return self._entry_type +ALERT_GROUPING = _Info('alertGrouping', 'GenericSet', str) ANGLE_REVISIONS = _Info('angleRevisions', 'GenericSet', str) ARCHITECTURES = _Info('architectures', 'GenericSet', str) BENCHMARKS = _Info('benchmarks', 'GenericSet', str) BENCHMARK_START = _Info('benchmarkStart', 'DateRange') BENCHMARK_DESCRIPTIONS = _Info('benchmarkDescriptions', 'GenericSet', str) +BOT_ID = _Info('botId', 'GenericSet', str) BOTS = _Info('bots', 'GenericSet', str) BUG_COMPONENTS = _Info('bugComponents', 'GenericSet', str) BUILD_URLS = _Info('buildUrls', 'GenericSet', str) @@ -54,6 +56,7 @@ MASTERS = _Info('masters', 'GenericSet', str) MEMORY_AMOUNTS = _Info('memoryAmounts', 'GenericSet', int) OS_NAMES = _Info('osNames', 'GenericSet', str) OS_VERSIONS = _Info('osVersions', 'GenericSet', str) +OS_DETAILED_VERSIONS = _Info('osDetailedVersions', 'GenericSet', str) OWNERS = _Info('owners', 'GenericSet', str) POINT_ID = _Info('pointId', 'GenericSet', int) PRODUCT_VERSIONS = _Info('productVersions', 'GenericSet', str) @@ -70,7 +73,16 @@ TRACE_URLS = _Info('traceUrls', 'GenericSet', str) V8_COMMIT_POSITIONS = _Info('v8CommitPositions', 'DateRange') V8_REVISIONS = _Info('v8Revisions', 'GenericSet', str) WEBRTC_REVISIONS = _Info('webrtcRevisions', 'GenericSet', str) -WEBRTC_INTERNAL_REVISIONS = _Info('webrtcInternalRevisions', 'GenericSet', str) +WEBRTC_INTERNAL_SIRIUS_REVISIONS = _Info( + 'webrtcInternalSiriusRevisions', 'GenericSet', str) +WEBRTC_INTERNAL_VEGA_REVISIONS = _Info( + 'webrtcInternalVegaRevisions', 'GenericSet', str) +WEBRTC_INTERNAL_CANOPUS_REVISIONS = _Info( + 'webrtcInternalCanopusRevisions', 'GenericSet', str) +WEBRTC_INTERNAL_ARCTURUS_REVISIONS = _Info( + 'webrtcInternalArcturusRevisions', 'GenericSet', str) +WEBRTC_INTERNAL_RIGEL_REVISIONS = _Info( + 'webrtcInternalRigelRevisions', 'GenericSet', str) def _CreateCachedInfoTypes(): @@ -86,6 +98,7 @@ def GetTypeForName(name): info = _CACHED_INFO_TYPES.get(name) if info: return info.type + return None def AllInfos(): for info in _CACHED_INFO_TYPES.values(): diff --git a/chromium/third_party/catapult/tracing/tracing/value/diagnostics/reserved_names.html b/chromium/third_party/catapult/tracing/tracing/value/diagnostics/reserved_names.html index e1f7c686d23..b25f946ec6d 100644 --- a/chromium/third_party/catapult/tracing/tracing/value/diagnostics/reserved_names.html +++ b/chromium/third_party/catapult/tracing/tracing/value/diagnostics/reserved_names.html @@ -17,12 +17,14 @@ tr.exportTo('tr.v.d', function() { // Diagnostics that are produced outside of metrics (e.g. by telemetry) use // reserved names. const RESERVED_INFOS = { + ALERT_GROUPING: { name: 'alertGrouping', type: tr.v.d.GenericSet }, ANGLE_REVISIONS: {name: 'angleRevisions', type: tr.v.d.GenericSet}, ARCHITECTURES: {name: 'architectures', type: tr.v.d.GenericSet}, BENCHMARKS: {name: 'benchmarks', type: tr.v.d.GenericSet}, BENCHMARK_START: {name: 'benchmarkStart', type: tr.v.d.DateRange}, BENCHMARK_DESCRIPTIONS: {name: 'benchmarkDescriptions', type: tr.v.d.GenericSet}, + BOT_ID: {name: 'botId', type: tr.v.d.GenericSet}, BOTS: {name: 'bots', type: tr.v.d.GenericSet}, BUG_COMPONENTS: {name: 'bugComponents', type: tr.v.d.GenericSet}, BUILDS: {name: 'builds', type: tr.v.d.GenericSet}, @@ -32,7 +34,7 @@ tr.exportTo('tr.v.d', function() { CHROMIUM_REVISIONS: {name: 'chromiumRevisions', type: tr.v.d.GenericSet}, DESCRIPTION: {name: 'description', type: tr.v.d.GenericSet}, DEVICE_IDS: {name: 'deviceIds', type: tr.v.d.GenericSet}, - DOCUMENTATION_URLS: {name: 'documentationUrls', type: tr.v.d.GenericSet}, + DOCUMENTATION_URLS: {name: 'documentationLinks', type: tr.v.d.GenericSet}, INFO_BLURB: {name: 'infoBlurb', type: tr.v.d.GenericSet}, FUCHSIA_GARNET_REVISIONS: { name: 'fuchsiaGarnetRevisions', type: tr.v.d.GenericSet}, @@ -50,6 +52,7 @@ tr.exportTo('tr.v.d', function() { MEMORY_AMOUNTS: {name: 'memoryAmounts', type: tr.v.d.GenericSet}, OS_NAMES: {name: 'osNames', type: tr.v.d.GenericSet}, OS_VERSIONS: {name: 'osVersions', type: tr.v.d.GenericSet}, + OS_DETAILED_VERSIONS: {name: 'osDetailedVersions', type: tr.v.d.GenericSet}, OWNERS: {name: 'owners', type: tr.v.d.GenericSet}, POINT_ID: {name: 'pointId', type: tr.v.d.GenericSet}, PRODUCT_VERSIONS: {name: 'productVersions', type: tr.v.d.GenericSet}, @@ -66,8 +69,16 @@ tr.exportTo('tr.v.d', function() { V8_COMMIT_POSITIONS: {name: 'v8CommitPositions', type: tr.v.d.DateRange}, V8_REVISIONS: {name: 'v8Revisions', type: tr.v.d.GenericSet}, WEBRTC_REVISIONS: {name: 'webrtcRevisions', type: tr.v.d.GenericSet}, - WEBRTC_INTERNAL_REVISIONS: { - name: 'webrtcInternalRevisions', type: tr.v.d.GenericSet}, + WEBRTC_INTERNAL_SIRIUS_REVISIONS: { + name:'webrtcInternalSiriusRevisions', type: tr.v.d.GenericSet}, + WEBRTC_INTERNAL_VEGA_REVISIONS: { + name:'webrtcInternalVegaRevisions', type: tr.v.d.GenericSet}, + WEBRTC_INTERNAL_CANOPUS_REVISIONS: { + name:'webrtcInternalCanopusRevisions', type: tr.v.d.GenericSet}, + WEBRTC_INTERNAL_ARCTURUS_REVISIONS: { + name:'webrtcInternalArcturusRevisions', type: tr.v.d.GenericSet}, + WEBRTC_INTERNAL_RIGEL_REVISIONS: { + name:'webrtcInternalRigelRevisions', type: tr.v.d.GenericSet}, }; const RESERVED_NAMES = {}; diff --git a/chromium/third_party/catapult/tracing/tracing/value/diagnostics/unmergeable_diagnostic_set.py b/chromium/third_party/catapult/tracing/tracing/value/diagnostics/unmergeable_diagnostic_set.py index c6de25894d5..db5e20965c4 100644 --- a/chromium/third_party/catapult/tracing/tracing/value/diagnostics/unmergeable_diagnostic_set.py +++ b/chromium/third_party/catapult/tracing/tracing/value/diagnostics/unmergeable_diagnostic_set.py @@ -16,7 +16,7 @@ except NameError: class UnmergeableDiagnosticSet(diagnostic.Diagnostic): - __slots__ = '_diagnostics', + __slots__ = ('_diagnostics',) def __init__(self, diagnostics): super(UnmergeableDiagnosticSet, self).__init__() diff --git a/chromium/third_party/catapult/tracing/tracing/value/gtest_json_converter.py b/chromium/third_party/catapult/tracing/tracing/value/gtest_json_converter.py index 4238c4b8bd4..fae316177a5 100644 --- a/chromium/third_party/catapult/tracing/tracing/value/gtest_json_converter.py +++ b/chromium/third_party/catapult/tracing/tracing/value/gtest_json_converter.py @@ -107,7 +107,7 @@ def _ConvertUnit(unit): # back to checking the histogram units directly, and defaulting to unitless if # the unit is unrecognized. legacy_unit = legacy_unit_info.LEGACY_UNIT_INFO.get(unit) - if legacy_unit != None: + if legacy_unit is not None: return legacy_unit.AsTuple() if unit in histogram.UNIT_NAMES: return unit, 1 diff --git a/chromium/third_party/catapult/tracing/tracing/value/heap_profiler.py b/chromium/third_party/catapult/tracing/tracing/value/heap_profiler.py index 0ffd17b3a4b..9a85031112d 100644 --- a/chromium/third_party/catapult/tracing/tracing/value/heap_profiler.py +++ b/chromium/third_party/catapult/tracing/tracing/value/heap_profiler.py @@ -23,7 +23,7 @@ from tracing_build import render_histograms_viewer def _IsUserDefinedInstance(obj): - return str(type(obj)).startswith('<class ') + return type(obj).__module__ != six.moves.builtins.__name__ class _HeapProfiler(object): @@ -199,7 +199,7 @@ def Profile(root, label=None, html_filename=None, html_stream=None, render_histograms_viewer.RenderHistogramsViewer( histograms.AsDicts(), html_stream, reset_results, vulcanized_viewer) else: - from tracing_build import vulcanize_histograms_viewer + from tracing_build import vulcanize_histograms_viewer # pylint: disable=import-outside-toplevel vulcanize_histograms_viewer.VulcanizeAndRenderHistogramsViewer( histograms.AsDicts(), html_stream) diff --git a/chromium/third_party/catapult/tracing/tracing/value/heap_profiler_unittest.py b/chromium/third_party/catapult/tracing/tracing/value/heap_profiler_unittest.py index e5d1ea8d05b..556ca6c5902 100644 --- a/chromium/third_party/catapult/tracing/tracing/value/heap_profiler_unittest.py +++ b/chromium/third_party/catapult/tracing/tracing/value/heap_profiler_unittest.py @@ -26,38 +26,41 @@ class HeapProfilerUnitTest(unittest.TestCase): histograms = heap_profiler.Profile(test_data) set_size_hist = histograms.GetHistogramNamed('heap:HistogramSet') - self.assertEquals(set_size_hist.num_values, 1) + self.assertEqual(set_size_hist.num_values, 1) # The exact sizes of python objects can vary between platforms and versions. self.assertGreater(set_size_hist.sum, 10000) hist_size_hist = histograms.GetHistogramNamed('heap:Histogram') - self.assertEquals(hist_size_hist.num_values, 10) + self.assertEqual(hist_size_hist.num_values, 10) self.assertGreater(hist_size_hist.sum, 10000) related_names = hist_size_hist.diagnostics['types'] - self.assertEquals(related_names.Get('HistogramBin'), 'heap:HistogramBin') - self.assertEquals(related_names.Get('DiagnosticMap'), 'heap:DiagnosticMap') + self.assertEqual(related_names.Get('HistogramBin'), 'heap:HistogramBin') + self.assertEqual(related_names.Get('DiagnosticMap'), 'heap:DiagnosticMap') - properties = hist_size_hist.bins[33].diagnostic_maps[0]['properties'] - types = hist_size_hist.bins[33].diagnostic_maps[0]['types'] + diagnostic_bin = [ + hist_bin for hist_bin in hist_size_hist.bins + if len(hist_bin.diagnostic_maps)][-1] + properties = diagnostic_bin.diagnostic_maps[0]['properties'] + types = diagnostic_bin.diagnostic_maps[0]['types'] self.assertGreater(len(properties), 3) self.assertGreater(properties.Get('_bins'), 1000) - self.assertEquals(len(types), 4) + self.assertEqual(len(types), 4) self.assertGreater(types.Get('HistogramBin'), 1000) self.assertGreater(types.Get('(builtin types)'), 1000) bin_size_hist = histograms.GetHistogramNamed('heap:HistogramBin') - self.assertEquals(bin_size_hist.num_values, 32) + self.assertEqual(bin_size_hist.num_values, 32) self.assertGreater(bin_size_hist.sum, 1000) diag_map_size_hist = histograms.GetHistogramNamed('heap:DiagnosticMap') - self.assertEquals(diag_map_size_hist.num_values, 10) + self.assertEqual(diag_map_size_hist.num_values, 10) self.assertGreater(diag_map_size_hist.sum, 1000) range_size_hist = histograms.GetHistogramNamed('heap:Range') - self.assertEquals(range_size_hist.num_values, 22) + self.assertEqual(range_size_hist.num_values, 22) self.assertGreater(range_size_hist.sum, 1000) stats_size_hist = histograms.GetHistogramNamed('heap:RunningStatistics') - self.assertEquals(stats_size_hist.num_values, 10) + self.assertEqual(stats_size_hist.num_values, 10) self.assertGreater(stats_size_hist.sum, 1000) diff --git a/chromium/third_party/catapult/tracing/tracing/value/histogram.h b/chromium/third_party/catapult/tracing/tracing/value/histogram.h index d29eac31de0..ff99d3311fe 100644 --- a/chromium/third_party/catapult/tracing/tracing/value/histogram.h +++ b/chromium/third_party/catapult/tracing/tracing/value/histogram.h @@ -5,6 +5,7 @@ #include <map> #include <memory> #include <string> +#include <unordered_map> #include <vector> #include "tracing/tracing/proto/histogram.pb.h" diff --git a/chromium/third_party/catapult/tracing/tracing/value/histogram.html b/chromium/third_party/catapult/tracing/tracing/value/histogram.html index 0a652d081fd..df47f36c554 100644 --- a/chromium/third_party/catapult/tracing/tracing/value/histogram.html +++ b/chromium/third_party/catapult/tracing/tracing/value/histogram.html @@ -12,6 +12,7 @@ found in the LICENSE file. <link rel="import" href="/tracing/base/unit.html"> <link rel="import" href="/tracing/base/utils.html"> <link rel="import" href="/tracing/value/diagnostics/diagnostic_map.html"> +<link rel="import" href="/tracing/value/diagnostics/reserved_names.html"> <script> 'use strict'; @@ -234,6 +235,9 @@ tr.exportTo('tr.v', function() { static create(name, unit, samples, opt_options) { const options = opt_options || {}; const hist = new Histogram(name, unit, options.binBoundaries); + if (options.alertGrouping !== undefined) { + hist.setAlertGrouping(options.alertGrouping); + } if (options.description) hist.description = options.description; @@ -275,6 +279,26 @@ tr.exportTo('tr.v', function() { return this.diagnostics_; } + /* Set alert grouping for a Histogram. + * See https://go/chromeperf-alert-grouping-dd + * @param {Array.< String >=} opt_alertGrouping + */ + setAlertGrouping(alertGrouping) { + if (alertGrouping === undefined || + alertGrouping === null || + alertGrouping.length === undefined) { + throw Error('alertGrouping must be an array'); + } + for (const alertGroup of alertGrouping) { + if (!Object.values(tr.v.d.ALERT_GROUPS).includes(alertGroup)) { + throw Error(`Alert group ${alertGroup} must be added to ` + + '/tracing/value/diagnostics/alert_groups.html'); + } + } + this.diagnostics.set(tr.v.d.RESERVED_NAMES.ALERT_GROUPING, + new tr.v.d.GenericSet(alertGrouping)); + } + get running() { return this.running_; } diff --git a/chromium/third_party/catapult/tracing/tracing/value/histogram.py b/chromium/third_party/catapult/tracing/tracing/value/histogram.py index d94f1a12327..28863bdda84 100644 --- a/chromium/third_party/catapult/tracing/tracing/value/histogram.py +++ b/chromium/third_party/catapult/tracing/tracing/value/histogram.py @@ -150,7 +150,6 @@ def Percentile(ary, percent): class HistogramError(ValueError): """Base execption type for Histogram related exceptions.""" - pass class InvalidBucketError(HistogramError): @@ -434,7 +433,7 @@ class RunningStatistics(object): class DiagnosticMap(dict): - __slots__ = '_allow_reserved_names', + __slots__ = ('_allow_reserved_names',) def __init__(self, *args, **kwargs): self._allow_reserved_names = True @@ -752,8 +751,8 @@ class Histogram(object): if summary_options: hist.CustomizeSummaryOptions(summary_options) if diagnostics: - for name, diag in diagnostics.items(): - hist.diagnostics[name] = diag + for diag_name, diag in diagnostics.items(): + hist.diagnostics[diag_name] = diag if not isinstance(samples, list): samples = [samples] @@ -824,7 +823,7 @@ class Histogram(object): upper = PercentFromString(stat_name[8:]) self._summary_options.get('iprs').push( Range.FromExplicitRange(lower, upper)) - for stat_name in self._summary_options.keys(): + for stat_name in self._summary_options: if stat_name in ['percentile', 'iprs']: continue self._summary_options[stat_name] = stat_name in statistics_names @@ -1030,10 +1029,9 @@ class Histogram(object): continue if hbin.range.min == -JS_MAX_VALUE: return hbin.range.max - elif hbin.range.max == JS_MAX_VALUE: + if hbin.range.max == JS_MAX_VALUE: return hbin.range.min - else: - return hbin.range.center + return hbin.range.center return self._bins[len(self._bins) - 1].range.min def _ResampleMean(self, percent): @@ -1215,6 +1213,8 @@ class Histogram(object): return Scalar(self.unit, (self.GetApproximatePercentile(upper) - self.GetApproximatePercentile(lower))) + return None + @property def statistics_scalars(self): results = {} @@ -1256,7 +1256,7 @@ class Histogram(object): dct['maxNumSampleValues'] = self.max_num_sample_values if self.num_nans: dct['numNans'] = self.num_nans - if len(self.nan_diagnostic_maps): + if self.nan_diagnostic_maps: dct['nanDiagnostics'] = [m.AsDict() for m in self.nan_diagnostic_maps] if self.num_values: dct['sampleValues'] = list(self.sample_values) @@ -1268,7 +1268,7 @@ class Histogram(object): summary_options = {} any_overridden_summary_options = False for name, option in self._summary_options.items(): - if name == 'percentile' or name == 'ci': + if name in ('percentile', 'ci'): if len(option) == 0: continue elif name == 'iprs': @@ -1313,8 +1313,8 @@ class Histogram(object): self._GetAllBinsAsProto(proto.all_bins) any_overridden_summary_options = any( - [self._summary_options[k] != DEFAULT_SUMMARY_OPTIONS[k] - for k in DEFAULT_SUMMARY_OPTIONS]) + self._summary_options[k] != DEFAULT_SUMMARY_OPTIONS[k] + for k in DEFAULT_SUMMARY_OPTIONS) if any_overridden_summary_options or self._summary_options['percentile']: # Note: iprs and ci are not supported in the proto format yet. diff --git a/chromium/third_party/catapult/tracing/tracing/value/histogram_importer.html b/chromium/third_party/catapult/tracing/tracing/value/histogram_importer.html index c9a36824d73..49f1ecc7e87 100644 --- a/chromium/third_party/catapult/tracing/tracing/value/histogram_importer.html +++ b/chromium/third_party/catapult/tracing/tracing/value/histogram_importer.html @@ -51,26 +51,26 @@ tr.exportTo('tr.v', function() { * The |view| should have 'display:none' so that it doesn't obnoxiously * display "zero Histograms" while they are being imported. * - * @param {!String} string + * @param {!String[]} strings * @param {!Element} view A histogram-set-view. * @return {Promise} resolves when |view| is displayed. */ - async importHistograms(string, view) { + async importHistograms(strings, view) { this.histograms_ = new tr.v.HistogramSet(); - this.string_ = string; this.view_ = view; tr.b.Timing.instant('HistogramImporter', 'string', this.string_.length); - - if (this.string_.length > 0) { + const loadMark = tr.b.Timing.mark('HistogramImporter', 'loadHistograms'); + for (const string of strings) { + this.string_ = string; + this.dataOffset_ = 0; + if (this.string_.length == 0) continue; await this.update_('Loading Histogram 0'); - const loadMark = tr.b.Timing.mark( - 'HistogramImporter', 'loadHistograms'); - if (!this.findDataStart_()) return; + if (!this.findDataStart_()) continue await this.loadSomeHistograms_(); - loadMark.end(); - tr.b.Timing.instant('HistogramImporter', 'nsPerJson', - parseInt(1e3 * loadMark.durationMs / this.histograms_.length)); } + loadMark.end(); + tr.b.Timing.instant('HistogramImporter', 'nsPerJson', + parseInt(1e3 * loadMark.durationMs / this.histograms_.length)); await this.update_('Displaying Histogram table...'); await this.displayHistograms_(); diff --git a/chromium/third_party/catapult/tracing/tracing/value/histogram_serializer_unittest.py b/chromium/third_party/catapult/tracing/tracing/value/histogram_serializer_unittest.py index 6b834056313..abd40370205 100644 --- a/chromium/third_party/catapult/tracing/tracing/value/histogram_serializer_unittest.py +++ b/chromium/third_party/catapult/tracing/tracing/value/histogram_serializer_unittest.py @@ -41,42 +41,84 @@ class HistogramSerializerUnittest(unittest.TestCase): hist.diagnostics['hhh'] = generic_set.GenericSet(['ggg']) hist.AddSample(0, { 'bbb': breakdown.Breakdown.FromEntries({ - 'ccc': 11, - 'ddd': 31, + 'ccc': 100, + 'ddd': 200, }), 'eee': related_event_set.RelatedEventSet([{ 'stableId': 'fff', 'title': 'ggg', - 'start': 3, - 'duration': 4, + 'start': 500, + 'duration': 600, }]), }) - data = histogram_serializer.Serialize([hist]) - self.assertEqual(data, [ + + serializer_objects = data[0] + diagnostics = data[1] + + self.assertIn('Breakdown', diagnostics) + self.assertIn('bbb', diagnostics['Breakdown']) + _, breakdown_indexes = list( + diagnostics['Breakdown']['bbb'].items())[0] + name_indexes = serializer_objects[breakdown_indexes[1]] + self.assertEqual( [ - 'aaa', - [1, [1, 1000.0, 20]], - '', - 'ccc', - 'ddd', - [3, 4], - 'ggg', - 'avg', 'count', 'max', 'min', 'std', 'sum', - 'lorem ipsum', - 'a:c', - 'a:d', + serializer_objects[breakdown_indexes[0]], + [serializer_objects[i] for i in name_indexes], + breakdown_indexes[2], + breakdown_indexes[3] ], - { - 'Breakdown': {'bbb': {5: [2, 5, 11, 31]}}, - 'GenericSet': { - 'hhh': {0: 6}, - 'statisticsNames': {1: [7, 8, 9, 10, 11, 12]}, - 'description': {3: 13}, - }, - 'RelatedEventSet': {'eee': {4: [['fff', 6, 3, 4]]}}, - 'RelatedNameMap': {'bbb': {2: [5, 14, 15]}}, - }, + ['', ['ccc', 'ddd'], 100, 200] + ) + + self.assertIn('GenericSet', diagnostics) + self.assertIn('hhh', diagnostics['GenericSet']) + _, added_diagnostic = list( + diagnostics['GenericSet']['hhh'].items())[0] + self.assertEqual(serializer_objects[added_diagnostic], 'ggg') + self.assertIn('statisticsNames', diagnostics['GenericSet']) + _, stat_indexes = list( + diagnostics['GenericSet']['statisticsNames'].items())[0] + self.assertEqual( + ['avg', 'count', 'max', 'min', 'std', 'sum'], + [serializer_objects[i] for i in stat_indexes] + ) + self.assertIn('description', diagnostics['GenericSet']) + _, description_index = list( + diagnostics['GenericSet']['description'].items())[0] + self.assertEqual(serializer_objects[description_index], 'lorem ipsum') + + self.assertIn('RelatedEventSet', diagnostics) + self.assertIn('eee', diagnostics['RelatedEventSet']) + _, event_set_values = list( + diagnostics['RelatedEventSet']['eee'].items())[0] + self.assertEqual( + [ + event_set_values[0][0], + serializer_objects[event_set_values[0][1]], + event_set_values[0][2], + event_set_values[0][3] + ], + ['fff', 'ggg', 500, 600] + ) + + self.assertIn('RelatedNameMap', diagnostics) + self.assertIn('bbb', diagnostics['RelatedNameMap']) + _, relative_name_values = list( + diagnostics['RelatedNameMap']['bbb'].items())[0] + name_indexes = relative_name_values[0] + self.assertEqual( + [ + [serializer_objects[i] for i in serializer_objects[name_indexes]], + serializer_objects[relative_name_values[1]], + serializer_objects[relative_name_values[2]] + ], + [['ccc', 'ddd'], 'a:c', 'a:d'] + ) + + histograms = data[2:] + self.assertEqual( + histograms[0], [ 0, 'count+', @@ -86,4 +128,4 @@ class HistogramSerializerUnittest(unittest.TestCase): {0: [1, [None, 4, 5]]}, 0, ] - ]) + ) diff --git a/chromium/third_party/catapult/tracing/tracing/value/histogram_set.py b/chromium/third_party/catapult/tracing/tracing/value/histogram_set.py index f9c96032ac7..bed920344b0 100644 --- a/chromium/third_party/catapult/tracing/tracing/value/histogram_set.py +++ b/chromium/third_party/catapult/tracing/tracing/value/histogram_set.py @@ -5,7 +5,7 @@ import collections from tracing.proto import histogram_proto -from tracing.value import histogram as histogram +from tracing.value import histogram from tracing.value import histogram_deserializer from tracing.value.diagnostics import all_diagnostics from tracing.value.diagnostics import diagnostic diff --git a/chromium/third_party/catapult/tracing/tracing/value/histogram_set_unittest.py b/chromium/third_party/catapult/tracing/tracing/value/histogram_set_unittest.py index 7d3111490bd..99e413549ad 100644 --- a/chromium/third_party/catapult/tracing/tracing/value/histogram_set_unittest.py +++ b/chromium/third_party/catapult/tracing/tracing/value/histogram_set_unittest.py @@ -4,6 +4,7 @@ import math import unittest +import six from tracing.proto import histogram_proto from tracing.value import histogram @@ -11,6 +12,7 @@ from tracing.value import histogram_set from tracing.value.diagnostics import date_range from tracing.value.diagnostics import diagnostic_ref from tracing.value.diagnostics import generic_set +from six.moves import zip def _AddHist(hist_set, name=None, unit=None): @@ -145,7 +147,7 @@ class HistogramSetUnittest(unittest.TestCase): hists2 = histogram_set.HistogramSet() hists2.ImportDicts(ds) self.assertEqual(len(hists2), 1) - hist2 = [h for h in hists2][0] + hist2 = list(hists2)[0] self.assertIsInstance( hist2.diagnostics.get('generic'), generic_set.GenericSet) @@ -280,10 +282,10 @@ class HistogramSetUnittest(unittest.TestCase): # The order of the histograms isn't guaranteed. self.assertEqual(len(hists), 2) - self.assertItemsEqual( - [hists[0].name, hists[1].name], ['metric1', 'metric2']) - self.assertItemsEqual( - [hists[0].unit, hists[1].unit], ['tsMs', 'sigma_biggerIsBetter']) + six.assertCountEqual( + self, [hists[0].name, hists[1].name], ['metric1', 'metric2']) + six.assertCountEqual( + self, [hists[0].unit, hists[1].unit], ['tsMs', 'sigma_biggerIsBetter']) def testSimpleFieldsFromProto(self): hist_set = histogram_proto.Pb2().HistogramSet() @@ -428,11 +430,19 @@ class HistogramSetUnittest(unittest.TestCase): # See the histogram spec in docs/histogram-set-json-format.md. # Serializing to proto leads to funny rounding errors. - self.assertEqual( - parsed_hist.statistics_names, - set(['pct_099_0000009537', 'pct_089_9999976158', 'pct_094_9999988079', - 'nans', 'avg', 'geometricMean']), - msg=str(parsed_hist.statistics_names)) + # The rounding works different from Python 2 and Python 3. + actual_statistics_names = sorted(parsed_hist.statistics_names) + expected_statistics_names = sorted( + ['pct_099_0000009537', 'pct_089_9999976158', 'pct_094_9999988079', + 'nans', 'avg', 'geometricMean'] + ) + self.assertTrue( + all(actual[:17] == expected[:17] + for (actual, expected) + in zip(actual_statistics_names, expected_statistics_names)), + msg='Statistics names are different. Expected: %s. Actual: %s ' % ( + expected_statistics_names, actual_statistics_names) + ) def testImportSharedDiagnosticsFromProto(self): guid1 = 'f7f17394-fa4a-481e-86bd-a82cd55935a7' diff --git a/chromium/third_party/catapult/tracing/tracing/value/histogram_test.html b/chromium/third_party/catapult/tracing/tracing/value/histogram_test.html index 8b2e9d8c6a4..28ec21885a5 100644 --- a/chromium/third_party/catapult/tracing/tracing/value/histogram_test.html +++ b/chromium/third_party/catapult/tracing/tracing/value/histogram_test.html @@ -6,6 +6,7 @@ found in the LICENSE file. --> <link rel="import" href="/tracing/base/assert_utils.html"> +<link rel="import" href="/tracing/value/diagnostics/alert_groups.html"> <link rel="import" href="/tracing/value/diagnostics/generic_set.html"> <link rel="import" href="/tracing/value/histogram.html"> @@ -858,5 +859,61 @@ tr.b.unittest.testSuite(function() { tr.b.assertRangeEquals( mergedIprs[2], tr.b.math.Range.fromExplicitRange(0.2, 0.8)); }); + + test('alertGrouping', function() { + const hist0 = new tr.v.Histogram('', unitlessNumber); + hist0.setAlertGrouping([tr.v.d.ALERT_GROUPS.CPU_USAGE]); + const hist1 = tr.v.Histogram.create('', unitlessNumber, [], { + alertGrouping: [tr.v.d.ALERT_GROUPS.LOADING_PAINT] + }); + const hist2 = tr.v.Histogram.create('', unitlessNumber, [], { + alertGrouping: [ + tr.v.d.ALERT_GROUPS.LOADING_PAINT, + tr.v.d.ALERT_GROUPS.LOADING_INTERACTIVITY, + ]}); + + assert.isTrue(hist0.diagnostics.get('alertGrouping').equals( + new tr.v.d.GenericSet([tr.v.d.ALERT_GROUPS.CPU_USAGE]))); + assert.isTrue(hist1.diagnostics.get('alertGrouping').equals( + new tr.v.d.GenericSet([tr.v.d.ALERT_GROUPS.LOADING_PAINT]))); + assert.isTrue(hist2.diagnostics.get('alertGrouping').equals( + new tr.v.d.GenericSet([ + tr.v.d.ALERT_GROUPS.LOADING_PAINT, + tr.v.d.ALERT_GROUPS.LOADING_INTERACTIVITY]))); + + const merged = hist0.clone(); + merged.addHistogram(hist1); + merged.addHistogram(hist2); + + assert.isTrue(merged.diagnostics.get('alertGrouping').equals( + new tr.v.d.GenericSet([ + tr.v.d.ALERT_GROUPS.CPU_USAGE, + tr.v.d.ALERT_GROUPS.LOADING_PAINT, + tr.v.d.ALERT_GROUPS.LOADING_INTERACTIVITY]))); + }); + + test('alertGroupingErrors', function() { + const hist0 = new tr.v.Histogram('', unitlessNumber); + + // Must be from tr.v.d.ALERT_GROUPS. + assert.throws(() => { + hist0.setAlertGrouping(['foo']); + }); + assert.throws(() => { + tr.v.Histogram.create('', unitlessNumber, [], { + alertGrouping: ['foo'], + }); + }); + + // Must be an array. + assert.throws(() => { + hist0.setAlertGrouping(tr.v.d.ALERT_GROUPS.CPU_USAGE); + }); + assert.throws(() => { + tr.v.Histogram.create('', unitlessNumber, [], { + alertGrouping: tr.v.d.ALERT_GROUPS.CPU_USAGE, + }); + }); + }); }); </script> diff --git a/chromium/third_party/catapult/tracing/tracing/value/histogram_unittest.py b/chromium/third_party/catapult/tracing/tracing/value/histogram_unittest.py index f7d6578ca29..4b1f2ca102b 100644 --- a/chromium/third_party/catapult/tracing/tracing/value/histogram_unittest.py +++ b/chromium/third_party/catapult/tracing/tracing/value/histogram_unittest.py @@ -704,8 +704,8 @@ class HistogramUnittest(unittest.TestCase): self.assertEqual(hist.description, clone.description) self.assertEqual(len(hist.diagnostics), len(clone.diagnostics)) self.assertEqual(hist.diagnostics['foo'], clone.diagnostics['foo']) - self.assertEqual(hist.statistics_scalars.keys(), - clone.statistics_scalars.keys()) + self.assertEqual(list(hist.statistics_scalars.keys()), + list(clone.statistics_scalars.keys())) self.assertEqual(hist.max_num_sample_values, clone.max_num_sample_values) class DiagnosticMapUnittest(unittest.TestCase): @@ -808,4 +808,3 @@ class DiagnosticMapUnittest(unittest.TestCase): self.assertIs(related_map, diagnostics[1]) self.assertIs(events, diagnostics[2]) self.assertIs(generic2, diagnostics[3]) - diff --git a/chromium/third_party/catapult/tracing/tracing/value/running_statistics.cc b/chromium/third_party/catapult/tracing/tracing/value/running_statistics.cc index 786f4765e36..0346def1fdd 100644 --- a/chromium/third_party/catapult/tracing/tracing/value/running_statistics.cc +++ b/chromium/third_party/catapult/tracing/tracing/value/running_statistics.cc @@ -6,6 +6,7 @@ #include <algorithm> #include <cassert> +#include <cmath> namespace catapult { @@ -16,7 +17,7 @@ void RunningStatistics::Add(double value) { min_ = std::min(min_, value); sum_ += value; - if (value < 0.0) { + if (std::islessequal(value, 0.0)) { meanlogs_valid_ = false; } else if (meanlogs_valid_) { meanlogs_ += (std::log(std::abs(value)) - meanlogs_) / count_; diff --git a/chromium/third_party/catapult/tracing/tracing/value/ui/breakdown_span_test.html b/chromium/third_party/catapult/tracing/tracing/value/ui/breakdown_span_test.html index 71079cdc856..a1d04efa600 100644 --- a/chromium/third_party/catapult/tracing/tracing/value/ui/breakdown_span_test.html +++ b/chromium/third_party/catapult/tracing/tracing/value/ui/breakdown_span_test.html @@ -88,7 +88,8 @@ tr.b.unittest.testSuite(function() { this.addHTMLOutput(span); }); - test('correlate', function() { + // See https://crbug.com/1143376. + skipTest('correlate', function() { const histograms = new tr.v.HistogramSet(); const sample0Breakdown = new tr.v.d.Breakdown(); sample0Breakdown.set('a', 5); diff --git a/chromium/third_party/catapult/tracing/tracing/value/ui/diagnostic_span.html b/chromium/third_party/catapult/tracing/tracing/value/ui/diagnostic_span.html index 741fc07f58e..cb9785e1969 100644 --- a/chromium/third_party/catapult/tracing/tracing/value/ui/diagnostic_span.html +++ b/chromium/third_party/catapult/tracing/tracing/value/ui/diagnostic_span.html @@ -41,13 +41,7 @@ tr.exportTo('tr.v.ui', function() { ' or a base class must have a registered elementName'); } - const tagName = typeInfo.metadata.elementName; - - if (tr.ui.b.isUnknownElementName(tagName)) { - throw new Error('Element not registered: ' + tagName); - } - - return tagName; + return typeInfo.metadata.elementName; } /** @@ -61,6 +55,9 @@ tr.exportTo('tr.v.ui', function() { function createDiagnosticSpan(diagnostic, name, histogram) { const tagName = findElementNameForDiagnostic(diagnostic); const span = document.createElement(tagName); + if (span instanceof HTMLUnknownElement) { + throw new Error('Element not registered: ' + tagName); + } if (span.build === undefined) throw new Error(tagName); span.build(diagnostic, name, histogram); return span; diff --git a/chromium/third_party/catapult/tracing/tracing/value/ui/generic_set_span_test.html b/chromium/third_party/catapult/tracing/tracing/value/ui/generic_set_span_test.html index 6bddec81aa6..e8632b513d3 100644 --- a/chromium/third_party/catapult/tracing/tracing/value/ui/generic_set_span_test.html +++ b/chromium/third_party/catapult/tracing/tracing/value/ui/generic_set_span_test.html @@ -13,7 +13,8 @@ found in the LICENSE file. <script> 'use strict'; -tr.b.unittest.testSuite(function() { +// See https://crbug.com/1143376. +tr.b.unittest.skippedTestSuite(function() { test('link_tuple', function() { const diagnostic = new tr.v.d.GenericSet([ ['label', 'http://example.com/'], @@ -94,7 +95,7 @@ tr.b.unittest.testSuite(function() { test('traceUrls', function() { const urls = [ - 'https://console.developers.google.com/m/cloudstorage/b/chromium-telemetry/o/c.html', + 'https://storage.cloud.google.com/chromium-telemetry/c.html', 'file://d/e/f.html', ]; const span = tr.v.ui.createDiagnosticSpan( diff --git a/chromium/third_party/catapult/tracing/tracing/value/ui/histogram_importer_test.html b/chromium/third_party/catapult/tracing/tracing/value/ui/histogram_importer_test.html index bb4e1e11ed0..96473519609 100644 --- a/chromium/third_party/catapult/tracing/tracing/value/ui/histogram_importer_test.html +++ b/chromium/third_party/catapult/tracing/tracing/value/ui/histogram_importer_test.html @@ -28,6 +28,37 @@ tr.b.unittest.testSuite(() => { return histogram; } + test('importZeroHistogramsEmptyArray', async function() { + const loadingEl = document.createElement('div'); + this.addHTMLOutput(loadingEl); + const importer = new tr.v.HistogramImporter(loadingEl); + + const view = document.createElement('tr-v-ui-histogram-set-view'); + view.style.display = 'none'; + this.addHTMLOutput(view); + + await importer.importHistograms([], view); + + assert.strictEqual('block', view.style.display); + assert.strictEqual(undefined, view.histograms); + }); + + test('importZeroHistogramsEmptyString', async function() { + const loadingEl = document.createElement('div'); + this.addHTMLOutput(loadingEl); + const importer = new tr.v.HistogramImporter(loadingEl); + const histogramData = ''; + + const view = document.createElement('tr-v-ui-histogram-set-view'); + view.style.display = 'none'; + this.addHTMLOutput(view); + + await importer.importHistograms([histogramData], view); + + assert.strictEqual('block', view.style.display); + assert.strictEqual(undefined, view.histograms); + }); + test('importZeroHistograms', async function() { const loadingEl = document.createElement('div'); this.addHTMLOutput(loadingEl); @@ -38,7 +69,7 @@ tr.b.unittest.testSuite(() => { view.style.display = 'none'; this.addHTMLOutput(view); - await importer.importHistograms(histogramData, view); + await importer.importHistograms([histogramData], view); assert.strictEqual('block', view.style.display); assert.strictEqual(undefined, view.histograms); @@ -56,7 +87,7 @@ tr.b.unittest.testSuite(() => { view.style.display = 'none'; this.addHTMLOutput(view); - await importer.importHistograms(histogramData, view); + await importer.importHistograms([histogramData], view); assert.strictEqual('none', loadingEl.style.display); assert.strictEqual('block', view.style.display); @@ -67,7 +98,31 @@ tr.b.unittest.testSuite(() => { assert.deepEqual([42], histogram.sampleValues); }); - test('importNHistogram', async function() { + test('importOneHistogramAndEmptyStrings', async function() { + const loadingEl = document.createElement('div'); + this.addHTMLOutput(loadingEl); + const importer = new tr.v.HistogramImporter(loadingEl); + + const hist = createHistogram(42); + const histogramDataArray = [ + '', '\n' + JSON.stringify(hist.asDict()) + '\n', '\n', '']; + + const view = document.createElement('tr-v-ui-histogram-set-view'); + view.style.display = 'none'; + this.addHTMLOutput(view); + + await importer.importHistograms(histogramDataArray, view); + + assert.strictEqual('none', loadingEl.style.display); + assert.strictEqual('block', view.style.display); + assert.lengthOf(view.histograms, 1); + const histogram = view.histograms.getHistogramNamed('name<42>'); + assert.strictEqual(kHtmlString, tr.b.getOnlyElement( + histogram.diagnostics.get('html'))); + assert.deepEqual([42], histogram.sampleValues); + }); + + test('importNHistogramsSingleString', async function() { const loadingEl = document.createElement('div'); this.addHTMLOutput(loadingEl); const importer = new tr.v.HistogramImporter(loadingEl); @@ -84,7 +139,39 @@ tr.b.unittest.testSuite(() => { view.style.display = 'none'; this.addHTMLOutput(view); - await importer.importHistograms(histogramData, view); + await importer.importHistograms([histogramData], view); + + assert.strictEqual('none', loadingEl.style.display); + assert.strictEqual('block', view.style.display); + assert.lengthOf(view.histograms, kNofHistograms); + for (let i = 0; i < kNofHistograms; i++) { + const id = kNofHistograms * 100 + i; + const histogram = view.histograms.getHistogramNamed('name<' + id + '>'); + assert.strictEqual(kHtmlString, tr.b.getOnlyElement( + histogram.diagnostics.get('html'))); + assert.deepEqual([id], histogram.sampleValues); + } + }); + + test('importNHistogramsSeparateStrings', async function() { + const loadingEl = document.createElement('div'); + this.addHTMLOutput(loadingEl); + const importer = new tr.v.HistogramImporter(loadingEl); + + const kNofHistograms = 1000; + let histogramDataArray = ['', '\n' ]; + for (let i = 0; i < kNofHistograms; i++) { + const id = kNofHistograms * 100 + i; + const histogram = createHistogram(id); + histogramDataArray.push( + '\n' + JSON.stringify(histogram.asDict()) + '\n'); + } + + const view = document.createElement('tr-v-ui-histogram-set-view'); + view.style.display = 'none'; + this.addHTMLOutput(view); + + await importer.importHistograms(histogramDataArray, view); assert.strictEqual('none', loadingEl.style.display); assert.strictEqual('block', view.style.display); diff --git a/chromium/third_party/catapult/tracing/tracing/value/ui/histogram_set_controls_test.html b/chromium/third_party/catapult/tracing/tracing/value/ui/histogram_set_controls_test.html index 9783059ccbe..236a2b337ac 100644 --- a/chromium/third_party/catapult/tracing/tracing/value/ui/histogram_set_controls_test.html +++ b/chromium/third_party/catapult/tracing/tracing/value/ui/histogram_set_controls_test.html @@ -134,7 +134,8 @@ tr.b.unittest.testSuite(function() { assert.strictEqual(showAll.checked, false); }); - test('controlShowAll', function() { + // See https://crbug.com/1143376. + skipTest('controlShowAll', function() { const controls = buildControls(this); const showAll = tr.ui.b.findDeepElementMatchingPredicate( controls, e => e.id === 'show_all'); diff --git a/chromium/third_party/catapult/tracing/tracing/value/ui/histogram_set_table_row.html b/chromium/third_party/catapult/tracing/tracing/value/ui/histogram_set_table_row.html index b4cb1a54020..68c080876a8 100644 --- a/chromium/third_party/catapult/tracing/tracing/value/ui/histogram_set_table_row.html +++ b/chromium/third_party/catapult/tracing/tracing/value/ui/histogram_set_table_row.html @@ -221,6 +221,9 @@ tr.exportTo('tr.v.ui', function() { const scalarA = cellA.getStatisticScalar(statisticA, referenceCellA); if (scalarA) { valueA = scalarA.value; + if (scalarA.unit.unitName == "normalizedPercentageDelta_smallerIsBetter") { + valueA = -valueA; + } } } @@ -232,6 +235,9 @@ tr.exportTo('tr.v.ui', function() { const scalarB = cellB.getStatisticScalar(statisticB, referenceCellB); if (scalarB) { valueB = scalarB.value; + if (scalarB.unit.unitName == "normalizedPercentageDelta_smallerIsBetter") { + valueB = -valueB; + } } } diff --git a/chromium/third_party/catapult/tracing/tracing/value/ui/histogram_set_table_test.html b/chromium/third_party/catapult/tracing/tracing/value/ui/histogram_set_table_test.html index f8d76afc72b..480a96dfe7c 100644 --- a/chromium/third_party/catapult/tracing/tracing/value/ui/histogram_set_table_test.html +++ b/chromium/third_party/catapult/tracing/tracing/value/ui/histogram_set_table_test.html @@ -213,7 +213,8 @@ tr.b.unittest.testSuite(function() { assert.strictEqual(baseTable.sortColumnIndex, 1); }); - test('controlSortColumnIndex', async function() { + // See https://crbug.com/1143376. + skipTest('controlSortColumnIndex', async function() { const histograms = new tr.v.HistogramSet(); histograms.createHistogram('a', tr.b.Unit.byName.count, [1]); histograms.createHistogram('b', tr.b.Unit.byName.count, [2]); @@ -243,7 +244,8 @@ tr.b.unittest.testSuite(function() { await table.viewState.update({sortDescending: false}); }); - test('controlSortDescending', async function() { + // See https://crbug.com/1143376. + skipTest('controlSortDescending', async function() { const histograms = new tr.v.HistogramSet(); histograms.createHistogram('a', tr.b.Unit.byName.count, [1]); histograms.createHistogram('b', tr.b.Unit.byName.count, [2]); @@ -440,7 +442,8 @@ tr.b.unittest.testSuite(function() { assert.lengthOf(getTableCells(table), 1); }); - test('controlRowExpanded', async function() { + // See https://crbug.com/1143376. + skipTest('controlRowExpanded', async function() { const histograms = new tr.v.HistogramSet(); const aHist = histograms.createHistogram('a', tr.b.Unit.byName.count, [1]); aHist.diagnostics.set(tr.v.d.RESERVED_NAMES.STORIES, @@ -1184,7 +1187,8 @@ tr.b.unittest.testSuite(function() { assert.strictEqual('-90.0%', barContent.textContent); }); - test('requestSelectionChange', async function() { + // See https://crbug.com/1143376. + skipTest('requestSelectionChange', async function() { const histograms = new tr.v.HistogramSet(); const barHist = histograms.createHistogram( 'bar', tr.b.Unit.byName.timeDurationInMs_smallerIsBetter, [1], { @@ -1395,7 +1399,8 @@ tr.b.unittest.testSuite(function() { table, e => e.textContent === '(missing)')); }); - test('instantiate_1x1', async function() { + // See https://crbug.com/1143376. + skipTest('instantiate_1x1', async function() { const histograms = new tr.v.HistogramSet(); const hist = histograms.createHistogram( 'foo', tr.b.Unit.byName.timeDurationInMs_smallerIsBetter, diff --git a/chromium/third_party/catapult/tracing/tracing/value/ui/histogram_span_test.html b/chromium/third_party/catapult/tracing/tracing/value/ui/histogram_span_test.html index 2ca360b3348..5e3b0163744 100644 --- a/chromium/third_party/catapult/tracing/tracing/value/ui/histogram_span_test.html +++ b/chromium/third_party/catapult/tracing/tracing/value/ui/histogram_span_test.html @@ -241,7 +241,8 @@ tr.b.unittest.testSuite(function() { this.addHTMLOutput(span); }); - test('breakdownUnit', async function() { + // See https://crbug.com/1143376. + skipTest('breakdownUnit', async function() { const root = new tr.v.Histogram('root', tr.b.Unit.byName.sizeInBytes); const sampleBreakdown = new tr.v.d.Breakdown(); sampleBreakdown.set('x', 30 << 20); @@ -266,7 +267,8 @@ tr.b.unittest.testSuite(function() { span, e => e.textContent === '70.0 MiB')); }); - test('diagnosticsTabs', async function() { + // See https://crbug.com/1143376. + skipTest('diagnosticsTabs', async function() { const span = document.createElement('tr-v-ui-histogram-span'); span.build(tr.v.Histogram.create( '', tr.b.Unit.byName.count, [ diff --git a/chromium/third_party/catapult/tracing/tracing/value/ui/related_event_set_span_test.html b/chromium/third_party/catapult/tracing/tracing/value/ui/related_event_set_span_test.html index b0058a3c97b..81c99544388 100644 --- a/chromium/third_party/catapult/tracing/tracing/value/ui/related_event_set_span_test.html +++ b/chromium/third_party/catapult/tracing/tracing/value/ui/related_event_set_span_test.html @@ -12,7 +12,8 @@ found in the LICENSE file. <script> 'use strict'; -tr.b.unittest.testSuite(function() { +// See https://crbug.com/1143376. +tr.b.unittest.skippedTestSuite(function() { test('instantiate_RelatedEventSet0', function() { const diagnostic = new tr.v.d.RelatedEventSet(); const span = tr.v.ui.createDiagnosticSpan(diagnostic); diff --git a/chromium/third_party/catapult/tracing/tracing/value/ui/scalar_context_controller_test.html b/chromium/third_party/catapult/tracing/tracing/value/ui/scalar_context_controller_test.html index ccb3f61d6f4..a4fdc85aaac 100644 --- a/chromium/third_party/catapult/tracing/tracing/value/ui/scalar_context_controller_test.html +++ b/chromium/third_party/catapult/tracing/tracing/value/ui/scalar_context_controller_test.html @@ -29,7 +29,8 @@ tr.b.unittest.testSuite(function() { is: 'tr-v-ui-scalar-context-controller-mock-host' }); - test('getScalarContextControllerForElement', function() { + // See https://crbug.com/1143376. + skipTest('getScalarContextControllerForElement', function() { const root = document.createElement('div'); Polymer.dom(document.body).appendChild(root); try { diff --git a/chromium/third_party/catapult/tracing/tracing/value/ui/scalar_span_test.html b/chromium/third_party/catapult/tracing/tracing/value/ui/scalar_span_test.html index 56ca6917d71..403b3cd10c2 100644 --- a/chromium/third_party/catapult/tracing/tracing/value/ui/scalar_span_test.html +++ b/chromium/third_party/catapult/tracing/tracing/value/ui/scalar_span_test.html @@ -284,7 +284,8 @@ tr.b.unittest.testSuite(function() { {expectedColor: 'rgb(255, 0, 0)'}); }); - test('createScalarSpan', function() { + // See https://crbug.com/1143376. + skipTest('createScalarSpan', function() { // No config. let span = tr.v.ui.createScalarSpan( new Scalar(Unit.byName.powerInWatts, 3.14)); @@ -477,7 +478,8 @@ tr.b.unittest.testSuite(function() { assert.strictEqual(rectA.height, rectB.height); }); - test('respectCurrentDisplayUnit', function() { + // See https://crbug.com/1143376. + skipTest('respectCurrentDisplayUnit', function() { try { Unit.currentTimeDisplayMode = tr.b.TimeDisplayModes.ns; @@ -514,7 +516,8 @@ tr.b.unittest.testSuite(function() { expectation.classList || []); } - test('customContextRange', function() { + // See https://crbug.com/1143376. + skipTest('customContextRange', function() { const div = document.createElement('div'); div.style.width = '101px'; // One extra pixel for sparkline border. this.addHTMLOutput(div); @@ -603,7 +606,8 @@ tr.b.unittest.testSuite(function() { assert.strictEqual(tr.v.ui.createScalarSpan(), ''); }); - test('contextControllerChanges', function() { + // See https://crbug.com/1143376. + skipTest('contextControllerChanges', function() { const div = document.createElement('div'); div.style.width = '101px'; // One extra pixel for sparkline border. this.addHTMLOutput(div); @@ -721,7 +725,8 @@ tr.b.unittest.testSuite(function() { checkSparkline(s1, {left: 0, width: 51, classList: ['positive']}); }); - test('deltaSparkline_noImprovementDirection', function() { + // See https://crbug.com/1143376. + skipTest('deltaSparkline_noImprovementDirection', function() { const div = document.createElement('div'); div.style.width = '101px'; // One extra pixel for sparkline border. this.addHTMLOutput(div); @@ -781,7 +786,8 @@ tr.b.unittest.testSuite(function() { checkSparkline(span8, {left: 0, width: 26, classList: ['positive']}); }); - test('deltaSparkline_smallerIsBetter', function() { + // See https://crbug.com/1143376. + skipTest('deltaSparkline_smallerIsBetter', function() { const div = document.createElement('div'); div.style.width = '101px'; // One extra pixel for sparkline border. this.addHTMLOutput(div); @@ -842,7 +848,8 @@ tr.b.unittest.testSuite(function() { checkSparkline(span8, {left: 0, width: 76, classList: ['better']}); }); - test('deltaSparkline_biggerIsBetter', function() { + // See https://crbug.com/1143376. + skipTest('deltaSparkline_biggerIsBetter', function() { const div = document.createElement('div'); div.style.width = '101px'; // One extra pixel for sparkline border. this.addHTMLOutput(div); @@ -902,7 +909,8 @@ tr.b.unittest.testSuite(function() { checkSparkline(span8, {left: 0, width: 101, classList: ['worse']}); }); - test('classListChanges', function() { + // See https://crbug.com/1143376. + skipTest('classListChanges', function() { const div = document.createElement('div'); div.style.width = '200px'; this.addHTMLOutput(div); |