profile-utils.js (4897B)
1 (function(global) { 2 const TEST_SAMPLE_INTERVAL = 10; 3 const ENSURE_SAMPLE_SPIN_WAIT_MS = 500; 4 5 function forceSample() { 6 // Spin for |TEST_SAMPLE_INTERVAL + 500|ms to ensure that a sample occurs 7 // before this function returns. As periodic sampling is enforced by a 8 // SHOULD clause, it is indeed testable. 9 // 10 // More reliable sampling will be handled in a future testdriver RFC 11 // (https://github.com/web-platform-tests/rfcs/pull/81). 12 for (const deadline = performance.now() + TEST_SAMPLE_INTERVAL + 13 ENSURE_SAMPLE_SPIN_WAIT_MS; 14 performance.now() < deadline;) 15 ; 16 } 17 18 // Creates a new profile that captures the execution of when the given 19 // function calls the `sample` function passed to it. 20 async function profileFunction(func) { 21 const profiler = new Profiler({ 22 sampleInterval: TEST_SAMPLE_INTERVAL, 23 maxBufferSize: Number.MAX_SAFE_INTEGER, 24 }); 25 26 func(() => forceSample()); 27 28 const trace = await profiler.stop(); 29 30 // Sanity check ensuring that we captured a sample. 31 assert_greater_than(trace.resources.length, 0); 32 assert_greater_than(trace.frames.length, 0); 33 assert_greater_than(trace.stacks.length, 0); 34 assert_greater_than(trace.samples.length, 0); 35 36 return trace; 37 } 38 39 async function testFunction(func, frame) { 40 const trace = await profileFunction(func); 41 assert_true(containsFrame(trace, frame), 'trace contains frame'); 42 } 43 44 function substackMatches(trace, stackId, expectedStack) { 45 if (expectedStack.length === 0) { 46 return true; 47 } 48 if (stackId === undefined) { 49 return false; 50 } 51 52 const stackElem = trace.stacks[stackId]; 53 const expectedFrame = expectedStack[0]; 54 55 if (!frameMatches(trace.frames[stackElem.frameId], expectedFrame)) { 56 return false; 57 } 58 return substackMatches(trace, stackElem.parentId, expectedStack.slice(1)); 59 } 60 61 // Returns true if the trace contains a frame matching the given specification. 62 // We define a "match" as follows: a frame A matches an expectation E if (and 63 // only if) for each field of E, A has the same value. 64 function containsFrame(trace, expectedFrame) { 65 return trace.frames.find(frame => { 66 return frameMatches(frame, expectedFrame); 67 }) !== undefined; 68 } 69 70 // Returns true if a trace contains a substack in one of its samples, ordered 71 // leaf to root. 72 function containsSubstack(trace, expectedStack) { 73 return trace.samples.find(sample => { 74 let stackId = sample.stackId; 75 while (stackId !== undefined) { 76 if (substackMatches(trace, stackId, expectedStack)) { 77 return true; 78 } 79 stackId = trace.stacks[stackId].parentId; 80 } 81 return false; 82 }) !== undefined; 83 } 84 85 function containsResource(trace, expectedResource) { 86 return trace.resources.includes(expectedResource); 87 } 88 89 // Returns true if a trace contains a sample matching the given specification. 90 // We define a "match" as follows: a sample A matches an expectation E if (and 91 // only if) for each field of E, A has the same value. 92 function containsSample(trace, expectedSample) { 93 return trace.samples.find(sample => { 94 return sampleMatches(sample, expectedSample); 95 }) !== undefined; 96 } 97 98 // Compares each set field of `expected` against the given frame `actual`. 99 function sampleMatches(actual, expected) { 100 return (expected.timestamp === undefined || 101 expected.timestamp === actual.timestamp) && 102 (expected.stackId === undefined || 103 expected.stackId === actual.stackId) && 104 (expected.marker === undefined || expected.marker === actual.marker); 105 } 106 107 // Compares each set field of `expected` against the given frame `actual`. 108 function frameMatches(actual, expected) { 109 return (expected.name === undefined || expected.name === actual.name) && 110 (expected.resourceId === undefined || expected.resourceId === actual.resourceId) && 111 (expected.line === undefined || expected.line === actual.line) && 112 (expected.column === undefined || expected.column === actual.column); 113 } 114 115 function forceSampleFrame(frame) { 116 const channel = new MessageChannel(); 117 const replyPromise = new Promise(res => { 118 channel.port1.onmessage = res; 119 }); 120 frame.postMessage('', '*', [channel.port2]); 121 return replyPromise; 122 } 123 124 window.addEventListener('message', message => { 125 // Force sample in response to messages received. 126 (function sampleFromMessage() { 127 ProfileUtils.forceSample(); 128 message.ports[0].postMessage(''); 129 })(); 130 }); 131 132 global.ProfileUtils = { 133 // Capturing 134 profileFunction, 135 forceSample, 136 137 // Containment checks 138 containsFrame, 139 containsSubstack, 140 containsResource, 141 containsSample, 142 143 // Cross-frame sampling 144 forceSampleFrame, 145 146 // Assertions 147 testFunction, 148 }; 149 })(this);