mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-21 17:25:36 +00:00
Bug 1567390 - Add a stackwalking test to the profiler; r=canaltinova
Differential Revision: https://phabricator.services.mozilla.com/D40308 --HG-- extra : moz-landing-system : lando
This commit is contained in:
parent
d9c1062c1c
commit
0c8eb702d9
@ -48,6 +48,14 @@ function wait(time) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This function takes a thread, and a sample tuple from the "data" array, and inflates
|
||||
* the frame to be an array of strings.
|
||||
*
|
||||
* @param {Object} thread - The thread from the profile.
|
||||
* @param {Array} sample - The tuple from the thread.samples.data array.
|
||||
* @returns {Array<string>} An array of function names.
|
||||
*/
|
||||
function getInflatedStackLocations(thread, sample) {
|
||||
let stackTable = thread.stackTable;
|
||||
let frameTable = thread.frameTable;
|
||||
@ -71,3 +79,85 @@ function getInflatedStackLocations(thread, sample) {
|
||||
// The profiler tree is inverted, so reverse the array.
|
||||
return locations.reverse();
|
||||
}
|
||||
|
||||
/**
|
||||
* It can be helpful to deterministically do at least one more profile sample. Sampling
|
||||
* is done based on a timer. This function spins on a while loop until at least one more
|
||||
* sample is collected.
|
||||
*
|
||||
* @return {number} The index of the collected sample.
|
||||
*/
|
||||
async function doAtLeastOnePeriodicSample() {
|
||||
async function getProfileSampleCount() {
|
||||
const profile = await Services.profiler.getProfileDataAsync();
|
||||
return profile.threads[0].samples.data.length;
|
||||
}
|
||||
|
||||
const sampleCount = await getProfileSampleCount();
|
||||
// Create an infinite loop until a sample has been collected.
|
||||
while (true) {
|
||||
if (sampleCount < (await getProfileSampleCount())) {
|
||||
return sampleCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This utility matches up stacks to see if they contain a certain sequence of stack
|
||||
* frames. A correctly functioning profiler will have a certain sequence of stacks,
|
||||
* but we can't always determine exactly which stacks will show up due to implementation
|
||||
* changes, as well as memory addresses being arbitrary to that particular build.
|
||||
*
|
||||
* This function triggers a test failure with a nice debug message when it fails.
|
||||
*
|
||||
* @param {Array<string>} actualStackFrames - As generated by inflatedStackFrames.
|
||||
* @param {Array<string | RegExp>} expectedStackFrames - Matches a subset of actualStackFrames
|
||||
*/
|
||||
function expectStackToContain(
|
||||
actualStackFrames,
|
||||
expectedStackFrames,
|
||||
message = "The actual stack and expected stack do not match."
|
||||
) {
|
||||
// Log the stacks that are being passed to this assertion, as it could be useful
|
||||
// for when these tests fail.
|
||||
console.log("Actual stack: ", actualStackFrames);
|
||||
console.log(
|
||||
"Expected to contain: ",
|
||||
expectedStackFrames.map(s => s.toString())
|
||||
);
|
||||
|
||||
let actualIndex = 0;
|
||||
|
||||
// Start walking the expected stack and look for matches.
|
||||
for (
|
||||
let expectedIndex = 0;
|
||||
expectedIndex < expectedStackFrames.length;
|
||||
expectedIndex++
|
||||
) {
|
||||
const expectedStackFrame = expectedStackFrames[expectedIndex];
|
||||
|
||||
while (true) {
|
||||
// Make sure that we haven't run out of actual stack frames.
|
||||
if (actualIndex >= actualStackFrames.length) {
|
||||
info(`Could not find a match for: "${expectedStackFrame.toString()}"`);
|
||||
Assert.ok(false, message);
|
||||
}
|
||||
|
||||
const actualStackFrame = actualStackFrames[actualIndex];
|
||||
actualIndex++;
|
||||
|
||||
const itMatches =
|
||||
typeof expectedStackFrame === "string"
|
||||
? expectedStackFrame === actualStackFrame
|
||||
: actualStackFrame.match(expectedStackFrame);
|
||||
|
||||
if (itMatches) {
|
||||
// We found a match, break out of this loop.
|
||||
break;
|
||||
}
|
||||
// Keep on looping looking for a match.
|
||||
}
|
||||
}
|
||||
|
||||
Assert.ok(true, message);
|
||||
}
|
||||
|
51
tools/profiler/tests/xpcshell/test_feature_stackwalking.js
Normal file
51
tools/profiler/tests/xpcshell/test_feature_stackwalking.js
Normal file
@ -0,0 +1,51 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/**
|
||||
* Do a basic test to see if native frames are being collected for stackwalking. This
|
||||
* test is fairly naive, as it does not attempt to check that these are valid symbols,
|
||||
* only that some kind of stack walking is happening. It does this by making sure at
|
||||
* least two native frames are collected.
|
||||
*/
|
||||
add_task(async () => {
|
||||
if (!AppConstants.MOZ_GECKO_PROFILER) {
|
||||
return;
|
||||
}
|
||||
const entries = 10000;
|
||||
const interval = 1;
|
||||
const threads = [];
|
||||
const features = ["stackwalk"];
|
||||
|
||||
Services.profiler.StartProfiler(entries, interval, features, threads);
|
||||
const sampleIndex = await doAtLeastOnePeriodicSample();
|
||||
|
||||
const profile = await Services.profiler.getProfileDataAsync();
|
||||
const [thread] = profile.threads;
|
||||
const { samples } = thread;
|
||||
|
||||
const inflatedStackFrames = getInflatedStackLocations(
|
||||
thread,
|
||||
samples.data[sampleIndex]
|
||||
);
|
||||
const nativeStack = /^0x[0-9a-f]+$/;
|
||||
|
||||
expectStackToContain(
|
||||
inflatedStackFrames,
|
||||
[
|
||||
"(root)",
|
||||
// There are probably more native stacks here.
|
||||
nativeStack,
|
||||
nativeStack,
|
||||
// Since this is an xpcshell test we know that JavaScript will run:
|
||||
"js::RunScript",
|
||||
// There are probably more native stacks here.
|
||||
nativeStack,
|
||||
nativeStack,
|
||||
],
|
||||
"Expected native stacks to be interleaved between some frame labels. There should" +
|
||||
"be more than one native stack if stack walking is working correctly. There " +
|
||||
"is no attempt here to determine if the memory addresses point to the correct " +
|
||||
"symbols"
|
||||
);
|
||||
});
|
@ -18,3 +18,4 @@ skip-if = !debug
|
||||
[test_asm.js]
|
||||
[test_feature_mainthreadio.js]
|
||||
skip-if = release_or_beta || (os == "win" && processor == "aarch64") # The IOInterposer is in an ifdef, aarch64 due to 1536657
|
||||
[test_feature_stackwalking.js]
|
||||
|
Loading…
Reference in New Issue
Block a user