244 lines
7.9 KiB
JavaScript
244 lines
7.9 KiB
JavaScript
/* 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/. */
|
|
|
|
/* import-globals-from ../shared-head.js */
|
|
|
|
// This Services declaration may shadow another from head.js, so define it as
|
|
// a var rather than a const.
|
|
|
|
const { AppConstants } = ChromeUtils.importESModule(
|
|
"resource://gre/modules/AppConstants.sys.mjs"
|
|
);
|
|
|
|
// Load the shared head
|
|
const sharedHead = do_get_file("shared-head.js", false);
|
|
if (!sharedHead) {
|
|
throw new Error("Could not load the shared head.");
|
|
}
|
|
Services.scriptloader.loadSubScript(
|
|
Services.io.newFileURI(sharedHead).spec,
|
|
this
|
|
);
|
|
|
|
/**
|
|
* 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;
|
|
let stringTable = thread.stringTable;
|
|
let SAMPLE_STACK_SLOT = thread.samples.schema.stack;
|
|
let STACK_PREFIX_SLOT = stackTable.schema.prefix;
|
|
let STACK_FRAME_SLOT = stackTable.schema.frame;
|
|
let FRAME_LOCATION_SLOT = frameTable.schema.location;
|
|
|
|
// Build the stack from the raw data and accumulate the locations in
|
|
// an array.
|
|
let stackIndex = sample[SAMPLE_STACK_SLOT];
|
|
let locations = [];
|
|
while (stackIndex !== null) {
|
|
let stackEntry = stackTable.data[stackIndex];
|
|
let frame = frameTable.data[stackEntry[STACK_FRAME_SLOT]];
|
|
locations.push(stringTable[frame[FRAME_LOCATION_SLOT]]);
|
|
stackIndex = stackEntry[STACK_PREFIX_SLOT];
|
|
}
|
|
|
|
// The profiler tree is inverted, so reverse the array.
|
|
return locations.reverse();
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
|
|
/**
|
|
* @param {Thread} thread
|
|
* @param {string} filename - The filename used to trigger FileIO.
|
|
* @returns {InflatedMarkers[]}
|
|
*/
|
|
function getInflatedFileIOMarkers(thread, filename) {
|
|
const markers = ProfilerTestUtils.getInflatedMarkerData(thread);
|
|
return markers.filter(
|
|
marker =>
|
|
marker.data?.type === "FileIO" &&
|
|
marker.data?.filename?.endsWith(filename)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Checks properties common to all FileIO markers.
|
|
*
|
|
* @param {InflatedMarkers[]} markers
|
|
* @param {string} filename
|
|
*/
|
|
function checkInflatedFileIOMarkers(markers, filename) {
|
|
greater(markers.length, 0, "Found some markers");
|
|
|
|
// See IOInterposeObserver::Observation::ObservedOperationString
|
|
const validOperations = new Set([
|
|
"write",
|
|
"fsync",
|
|
"close",
|
|
"stat",
|
|
"create/open",
|
|
"read",
|
|
]);
|
|
const validSources = new Set(["PoisonIOInterposer", "NSPRIOInterposer"]);
|
|
|
|
for (const marker of markers) {
|
|
try {
|
|
ok(
|
|
marker.name.startsWith("FileIO"),
|
|
"Has a marker.name that starts with FileIO"
|
|
);
|
|
equal(marker.data.type, "FileIO", "Has a marker.data.type");
|
|
ok(
|
|
ProfilerTestUtils.isIntervalMarker(marker),
|
|
"All FileIO markers are interval markers"
|
|
);
|
|
ok(
|
|
validOperations.has(marker.data.operation),
|
|
`The markers have a known operation - "${marker.data.operation}"`
|
|
);
|
|
ok(
|
|
validSources.has(marker.data.source),
|
|
`The FileIO marker has a known source "${marker.data.source}"`
|
|
);
|
|
ok(marker.data.filename.endsWith(filename));
|
|
ok(Boolean(marker.data.stack), "A stack was collected");
|
|
} catch (error) {
|
|
console.error("Failing inflated FileIO marker:", marker);
|
|
throw error;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Do deep equality checks for schema, but then surface nice errors for a user to know
|
|
* what to do if the check fails.
|
|
*/
|
|
function checkSchema(actual, expected) {
|
|
const schemaName = expected.name;
|
|
info(`Checking marker schema for "${schemaName}"`);
|
|
|
|
try {
|
|
ok(
|
|
actual,
|
|
`Schema was found for "${schemaName}". See the test output for more information.`
|
|
);
|
|
// Check individual properties to surface easier to debug errors.
|
|
deepEqual(
|
|
expected.display,
|
|
actual.display,
|
|
`The "display" property for ${schemaName} schema matches. See the test output for more information.`
|
|
);
|
|
if (expected.data) {
|
|
ok(actual.data, `Schema was found for "${schemaName}"`);
|
|
for (const expectedDatum of expected.data) {
|
|
const actualDatum = actual.data.find(d => d.key === expectedDatum.key);
|
|
deepEqual(
|
|
expectedDatum,
|
|
actualDatum,
|
|
`The "${schemaName}" field "${expectedDatum.key}" matches expectations. See the test output for more information.`
|
|
);
|
|
}
|
|
equal(
|
|
expected.data.length,
|
|
actual.data.length,
|
|
"The expected and actual data have the same number of items"
|
|
);
|
|
}
|
|
|
|
// Finally do a true deep equal.
|
|
deepEqual(expected, actual, "The entire schema is deepEqual");
|
|
} catch (error) {
|
|
// The test results are not very human readable. This is a bit of a hacky
|
|
// solution to make it more readable.
|
|
dump("-----------------------------------------------------\n");
|
|
dump("The expected marker schema:\n");
|
|
dump("-----------------------------------------------------\n");
|
|
dump(JSON.stringify(expected, null, 2));
|
|
dump("\n");
|
|
dump("-----------------------------------------------------\n");
|
|
dump("The actual marker schema:\n");
|
|
dump("-----------------------------------------------------\n");
|
|
dump(JSON.stringify(actual, null, 2));
|
|
dump("\n");
|
|
dump("-----------------------------------------------------\n");
|
|
dump("A marker schema was not equal to expectations. If you\n");
|
|
dump("are modifying the schema, then please copy and paste\n");
|
|
dump("the new schema into this test.\n");
|
|
dump("-----------------------------------------------------\n");
|
|
dump("Copy this: " + JSON.stringify(actual));
|
|
dump("\n");
|
|
dump("-----------------------------------------------------\n");
|
|
|
|
throw error;
|
|
}
|
|
}
|