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:
Greg Tatum 2019-08-02 14:06:26 +00:00
parent d9c1062c1c
commit 0c8eb702d9
3 changed files with 142 additions and 0 deletions

View File

@ -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);
}

View 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"
);
});

View File

@ -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]