☠☠ backed out by dec0a179f851 ☠ ☠ | |
author | Andrew Comminos <acomminos@fb.com> |
Sat, 17 Jul 2021 09:51:22 +0000 | |
changeset 585839 | 8339cb3e70c8293e98b851d9ff74da6d5b18484d |
parent 585838 | 1ef81c9e982b2eaeb1fcfc55bd336d25b6935beb |
child 585840 | 3ecf9f0828c31d51ab8ff79525036af92883d928 |
push id | 38620 |
push user | csabou@mozilla.com |
push date | Sun, 18 Jul 2021 09:08:29 +0000 |
treeherder | mozilla-central@cc4e5ea0c986 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | testonly |
bugs | 1720233, 29645, 956688, 3021557, 901174 |
milestone | 92.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/testing/web-platform/tests/js-self-profiling/__dir__.headers +++ b/testing/web-platform/tests/js-self-profiling/__dir__.headers @@ -1,3 +1,1 @@ -Cross-Origin-Opener-Policy: same-origin -Cross-Origin-Embedder-Policy: require-corp Document-Policy: js-profiling
new file mode 100644 --- /dev/null +++ b/testing/web-platform/tests/js-self-profiling/concurrent-profilers.https.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<html> +<head> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> +</head> +<body> + <script> + promise_test(async t => { + const profiler_a = new Profiler({ + sampleInterval: 10, + maxBufferSize: Number.MAX_SAFE_INTEGER, + }); + + const profiler_b = new Profiler({ + sampleInterval: 10, + maxBufferSize: Number.MAX_SAFE_INTEGER, + }); + + const trace_b = await profiler_b.stop(); + const trace_a = await profiler_a.stop(); + }, 'concurrent profilers should be supported'); + </script> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/testing/web-platform/tests/js-self-profiling/external-script.https.html @@ -0,0 +1,50 @@ +<!DOCTYPE html> +<html> +<head> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + + <script src="resources/profile-utils.js"></script> + + <script id="external-script" src="resources/external-script.js"></script> +</head> +<body> + <script> + promise_test(async t => { + const trace = await ProfileUtils.profileFunction(function trampoline(sample) { + externalScriptFunction(sample); + }); + + const scriptUrl = document.querySelector('#external-script').src; + assert_true(ProfileUtils.containsResource(trace, scriptUrl), + 'external resource is included'); + + const expectedTrampolineFrame = { + name: 'trampoline', + resourceId: trace.resources.indexOf( + location.href, + ), + }; + const expectedExternalFrame = { + name: 'externalScriptFunction', + resourceId: trace.resources.indexOf(scriptUrl), + line: EXTERNAL_SCRIPT_FUNCTION_LINE, + column: EXTERNAL_SCRIPT_FUNCTION_COLUMN, + }; + + assert_true(ProfileUtils.containsFrame(trace, expectedTrampolineFrame), + 'trampoline function included'); + + assert_true(ProfileUtils.containsFrame(trace, expectedExternalFrame), + 'external script function included'); + + assert_true(ProfileUtils.containsSubstack(trace, [ + externalScriptFunction, + expectedTrampolineFrame, + ]), + 'stack exists with external script function'); + + }, 'external script function details'); + </script> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/testing/web-platform/tests/js-self-profiling/function-names.https.html @@ -0,0 +1,84 @@ +<!DOCTYPE html> +<html> +<head> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + + <script src="resources/profile-utils.js"></script> +</head> +<body> + <script> + promise_test(async t => { + const f = function namedFunctionExpression(sample) { + sample(); + }; + await ProfileUtils.testFunction(f, { + name: 'namedFunctionExpression', + }); + }, 'function expression names are logged correctly'); + + promise_test(async t => { + const f = function(sample) { + sample(); + }; + await ProfileUtils.testFunction(f, { + name: '', + }); + }, 'anonymous function expression names are logged correctly'); + + promise_test(async t => { + function namedFunctionDeclaration(sample) { + sample(); + }; + await ProfileUtils.testFunction(namedFunctionDeclaration, { + name: 'namedFunctionDeclaration', + }); + }, 'function declaration names are logged correctly'); + + // Methods should use their label as the function/frame name. Source: + // https://www.ecma-international.org/ecma-262/#sec-method-definitions-runtime-semantics-propertydefinitionevaluation + promise_test(async t => { + class SomeClass { + method(sample) { + sample(); + } + } + let instance = new SomeClass(); + + await ProfileUtils.testFunction(instance.method.bind(instance), { + name: 'method', + }); + }, 'class method names are logged correctly'); + + // Getter methods should use `get ${label}` as the function/frame name. Source: + // https://www.ecma-international.org/ecma-262/#sec-method-definitions-runtime-semantics-propertydefinitionevaluation + promise_test(t => ProfileUtils.testFunction(sample => { + class SomeClass { + get someValue() { + sample(); + } + } + let instance = new SomeClass(); + instance.someValue; + }, { + name: 'get someValue', + } + ), 'class getter names are logged correctly'); + + // Setter methods should use `set ${label}` as the function/frame name. Source: + // https://www.ecma-international.org/ecma-262/#sec-method-definitions-runtime-semantics-propertydefinitionevaluation + promise_test(t => ProfileUtils.testFunction(sample => { + class SomeClass { + set someValue(_) { + sample(); + } + } + let instance = new SomeClass(); + instance.someValue = 5; + }, { + name: 'set someValue', + } + ), 'class setter names are logged correctly'); + </script> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/testing/web-platform/tests/js-self-profiling/iframe-context-filtration.https.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<html> +<head> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + + <script src="resources/profile-utils.js"></script> +</head> +<body> + <iframe src="resources/child-frame.html"></iframe> + + <script> + promise_test(_ => new Promise(res => window.addEventListener('load', res)), + 'wait for load event'); + + promise_test(async t => { + const profiler = new Profiler({ + sampleInterval: 10, + maxBufferSize: Number.MAX_SAFE_INTEGER, + }); + + const iframe = frames[0]; + await ProfileUtils.forceSampleFrame(iframe); + + const trace = await profiler.stop(); + + assert_false(ProfileUtils.containsFrame(trace, { name: 'sampleFromMessage' }), + 'function from child context not included in trace'); + + const childUrl = iframe.src; + assert_false(ProfileUtils.containsResource(trace, childUrl), + 'child resources are not included'); + }, 'functions from child frame are not included in profile created by parent frame'); + </script> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/testing/web-platform/tests/js-self-profiling/inline-script.html @@ -0,0 +1,46 @@ +<!DOCTYPE html> +<html> +<head> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + + <script src="resources/profile-utils.js"></script> +</head> +<body> + <script> + // Note: moving these function definitions will change the expected + // outcomes below. + function nestedInlineScriptFunction(sample) { + sample(); + } + + function inlineScriptFunction(sample) { + nestedInlineScriptFunction(sample); + } + </script> + + <script> + promise_test(async t => { + const trace = await ProfileUtils.profileFunction(inlineScriptFunction); + + assert_true(ProfileUtils.containsResource(trace, location.href), + 'inline script resource is included'); + + assert_true(ProfileUtils.containsSubstack(trace, [ + { + name: 'nestedInlineScriptFunction', + resourceId: trace.resources.indexOf(location.href), + line: 13, + column: 40, + }, + { + name: 'inlineScriptFunction', + resourceId: trace.resources.indexOf(location.href), + line: 17, + column: 34, + }, + ])); + }, 'inline script function details'); + </script> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/testing/web-platform/tests/js-self-profiling/max-buffer-size.https.html @@ -0,0 +1,55 @@ +<!DOCTYPE html> +<html> +<head> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + + <script src="resources/profile-utils.js"></script> +</head> +<body> + <script> + promise_test(async t => { + assert_throws_js(TypeError, () => { + new Profiler({ sampleInterval: 10 }); + }); + }, 'max buffer size must be defined'); + + promise_test(async t => { + const profiler = new Profiler({ + sampleInterval: 10, + maxBufferSize: 2, + }); + + // Force 3 samples with a max buffer size of 2. + for (let i = 0; i < 3; i++) { + ProfileUtils.forceSample(); + } + + const trace = await profiler.stop(); + assert_equals(trace.samples.length, 2); + }, 'max buffer size is not exceeded'); + + promise_test(async t => { + const pf = []; + pf[0] = new Profiler({ sampleInterval: 10, maxBufferSize: Number.MAX_SAFE_INTEGER }); + pf[1] = new Profiler({ sampleInterval: 10, maxBufferSize: 1 }); + + const watcher = new EventWatcher(t, pf[1], ['samplebufferfull']); + pf[0].addEventListener("samplebufferfull", ()=>{ + assert_unreached("samplebufferfull invoked on wrong profiler"); + }); + pf[1].addEventListener("samplebufferfull", async ()=>{ + pf[0].stop(); + pf[1].stop(); + assertTrue(true); + }); + + for (let i = 0; i < 2; i++) { + ProfileUtils.forceSample(); + } + + return watcher.wait_for('samplebufferfull'); + }, 'executed samplebufferfull function'); + </script> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/testing/web-platform/tests/js-self-profiling/resources/__dir__.headers @@ -0,0 +1,1 @@ +Document-Policy: js-profiling
new file mode 100644 --- /dev/null +++ b/testing/web-platform/tests/js-self-profiling/resources/child-frame.html @@ -0,0 +1,8 @@ +<!DOCTYPE html> +<html> +<head> +</head> +<body> + <script src="profile-utils.js"></script> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/testing/web-platform/tests/js-self-profiling/resources/external-script.js @@ -0,0 +1,9 @@ +// NOTE: Modifying the location of functions in this file will cause +// `external-script.html` to fail! Please update the following constants +// accordingly. +const EXTERNAL_SCRIPT_FUNCTION_LINE = 7; +const EXTERNAL_SCRIPT_FUNCTION_COLUMN = 32; + +function externalScriptFunction(sample) { + sample(); +}
new file mode 100644 --- /dev/null +++ b/testing/web-platform/tests/js-self-profiling/resources/external-script.js.headers @@ -0,0 +1,1 @@ +Access-Control-Allow-Origin: *
new file mode 100644 --- /dev/null +++ b/testing/web-platform/tests/js-self-profiling/resources/profile-utils.js @@ -0,0 +1,126 @@ +(function(global) { + const TEST_SAMPLE_INTERVAL = 10; + + function forceSample() { + // Spin for |TEST_SAMPLE_INTERVAL + 500|ms to ensure that a sample occurs + // before this function returns. As periodic sampling is enforced by a + // SHOULD clause, it is indeed testable. + // + // More reliable sampling will be handled in a future testdriver RFC + // (https://github.com/web-platform-tests/rfcs/pull/81). + for (const deadline = performance.now() + TEST_SAMPLE_INTERVAL + 500; performance.now() < deadline;); + } + + // Creates a new profile that captures the execution of when the given + // function calls the `sample` function passed to it. + async function profileFunction(func) { + const profiler = new Profiler({ + sampleInterval: TEST_SAMPLE_INTERVAL, + maxBufferSize: Number.MAX_SAFE_INTEGER, + }); + + func(() => forceSample()); + + const trace = await profiler.stop(); + + // Sanity check ensuring that we captured a sample. + assert_greater_than(trace.resources.length, 0); + assert_greater_than(trace.frames.length, 0); + assert_greater_than(trace.stacks.length, 0); + assert_greater_than(trace.samples.length, 0); + + return trace; + } + + async function testFunction(func, frame) { + const trace = await profileFunction(func); + assert_true(containsFrame(trace, frame), 'trace contains frame'); + } + + function substackMatches(trace, stackId, expectedStack) { + if (expectedStack.length === 0) { + return true; + } + if (stackId === undefined) { + return false; + } + + const stackElem = trace.stacks[stackId]; + const expectedFrame = expectedStack[0]; + + if (!frameMatches(trace.frames[stackElem.frameId], expectedFrame)) { + return false; + } + return substackMatches(trace, stackElem.parentId, expectedStack.slice(1)); + } + + // Returns true if the trace contains a frame matching the given specification. + // We define a "match" as follows: a frame A matches an expectation E if (and + // only if) for each field of E, A has the same value. + function containsFrame(trace, expectedFrame) { + return trace.frames.find(frame => { + return frameMatches(frame, expectedFrame); + }) !== undefined; + } + + // Returns true if a trace contains a substack in one of its samples, ordered + // leaf to root. + function containsSubstack(trace, expectedStack) { + return trace.samples.find(sample => { + let stackId = sample.stackId; + while (stackId !== undefined) { + if (substackMatches(trace, stackId, expectedStack)) { + return true; + } + stackId = trace.stacks[stackId].parentId; + } + return false; + }) !== undefined; + } + + function containsResource(trace, expectedResource) { + return trace.resources.includes(expectedResource); + } + + // Compares each set field of `expected` against the given frame `actual`. + function frameMatches(actual, expected) { + return (expected.name === undefined || expected.name === actual.name) && + (expected.resourceId === undefined || expected.resourceId === actual.resourceId) && + (expected.line === undefined || expected.line === actual.line) && + (expected.column === undefined || expected.column === actual.column); + } + + function forceSampleFrame(frame) { + const channel = new MessageChannel(); + const replyPromise = new Promise(res => { + channel.port1.onmessage = res; + }); + frame.postMessage('', '*', [channel.port2]); + return replyPromise; + } + + window.addEventListener('message', message => { + // Force sample in response to messages received. + (function sampleFromMessage() { + ProfileUtils.forceSample(); + message.ports[0].postMessage(''); + })(); + }); + + global.ProfileUtils = { + // Capturing + profileFunction, + forceSample, + + // Containment checks + containsFrame, + containsSubstack, + containsResource, + + // Cross-frame sampling + forceSampleFrame, + + // Assertions + testFunction, + }; +})(this);
new file mode 100644 --- /dev/null +++ b/testing/web-platform/tests/js-self-profiling/time-domain.window.js @@ -0,0 +1,19 @@ +// META: script=resources/profile-utils.js + +promise_test(async () => { + const start = performance.now(); + + const profiler = new Profiler({ + sampleInterval: 10, + maxBufferSize: Number.MAX_SAFE_INTEGER, + }); + ProfileUtils.forceSample(); + const trace = await profiler.stop(); + + const end = performance.now(); + + assert_greater_than(trace.samples.length, 0); + for (const sample of trace.samples) { + assert_between_inclusive(sample.timestamp, start, end); + } +}, 'sample timestamps use the current high-resolution time');
new file mode 100644 --- /dev/null +++ b/testing/web-platform/tests/js-self-profiling/without-document-policy/disabled.https.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<html> +<head> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> +</head> +<body> + <script> + test(t => { + assert_throws_dom('NotAllowedError', () => { + new Profiler({ sampleInterval: 10, maxBufferSize: Number.MAX_SAFE_INTEGER }); + }); + }, 'profiling should throw without passing document policy'); + </script> +</body> +</html>