summaryrefslogtreecommitdiffstats
path: root/tools/profiler/tests/xpcshell/head.js
diff options
context:
space:
mode:
Diffstat (limited to 'tools/profiler/tests/xpcshell/head.js')
-rw-r--r--tools/profiler/tests/xpcshell/head.js244
1 files changed, 244 insertions, 0 deletions
diff --git a/tools/profiler/tests/xpcshell/head.js b/tools/profiler/tests/xpcshell/head.js
new file mode 100644
index 0000000000..ce87b32fd5
--- /dev/null
+++ b/tools/profiler/tests/xpcshell/head.js
@@ -0,0 +1,244 @@
+/* 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"
+);
+const { setTimeout } = ChromeUtils.importESModule(
+ "resource://gre/modules/Timer.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 = 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(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;
+ }
+}