Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

/* 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/. */
add_task(async () => {
if (!Services.profiler.GetFeatures().includes("nativeallocations")) {
Assert.ok(
true,
"Native allocations are not supported by this build, " +
"skip run the rest of the test."
);
return;
}
Assert.ok(
!Services.profiler.IsActive(),
"The profiler is not currently active"
);
info(
"Test that the profiler can install memory hooks and collect native allocation " +
"information in the marker payloads."
);
{
info("Start the profiler.");
await startProfiler({
// Only instrument the main thread.
threads: ["GeckoMain"],
features: ["js", "nativeallocations"],
});
info(
"Do some JS work for a little bit. This will increase the amount of allocations " +
"that take place."
);
doWork();
info("Get the profile data and analyze it.");
const profile = await waitSamplingAndStopAndGetProfile();
const {
allocationPayloads,
unmatchedAllocations,
logAllocationsAndDeallocations,
} = getAllocationInformation(profile);
Assert.greater(
allocationPayloads.length,
0,
"Native allocation payloads were recorded for the parent process' main thread when " +
"the Native Allocation feature was turned on."
);
if (unmatchedAllocations.length !== 0) {
info(
"There were unmatched allocations. Log all of the allocations and " +
"deallocations in order to aid debugging."
);
logAllocationsAndDeallocations();
ok(
false,
"Found a deallocation that did not have a matching allocation site. " +
"This could happen if balanced allocations is broken, or if the the " +
"buffer size of this test was too small, and some markers ended up " +
"rolling off."
);
}
ok(true, "All deallocation sites had matching allocations.");
}
info("Restart the profiler, to ensure that we get no more allocations.");
{
await startProfiler({ features: ["js"] });
info("Do some work again.");
doWork();
info("Wait for the periodic sampling.");
const profile = await waitSamplingAndStopAndGetProfile();
const allocationPayloads = getPayloadsOfType(
profile.threads[0],
"Native allocation"
);
Assert.equal(
allocationPayloads.length,
0,
"No native allocations were collected when the feature was disabled."
);
}
});
function doWork() {
this.n = 0;
for (let i = 0; i < 1e5; i++) {
this.n += Math.random();
}
}
/**
* Extract the allocation payloads, and find the unmatched allocations.
*/
function getAllocationInformation(profile) {
// Get all of the allocation payloads.
const allocationPayloads = getPayloadsOfType(
profile.threads[0],
"Native allocation"
);
// Decide what is an allocation and deallocation.
const allocations = allocationPayloads.filter(
payload => ensureIsNumber(payload.size) >= 0
);
const deallocations = allocationPayloads.filter(
payload => ensureIsNumber(payload.size) < 0
);
// Now determine the unmatched allocations by building a set
const allocationSites = new Set(
allocations.map(({ memoryAddress }) => memoryAddress)
);
const unmatchedAllocations = deallocations.filter(
({ memoryAddress }) => !allocationSites.has(memoryAddress)
);
// Provide a helper to log out the allocations and deallocations on failure.
function logAllocationsAndDeallocations() {
for (const { memoryAddress } of allocations) {
console.log("Allocations", formatHex(memoryAddress));
allocationSites.add(memoryAddress);
}
for (const { memoryAddress } of deallocations) {
console.log("Deallocations", formatHex(memoryAddress));
}
for (const { memoryAddress } of unmatchedAllocations) {
console.log("Deallocation with no allocation", formatHex(memoryAddress));
}
}
return {
allocationPayloads,
unmatchedAllocations,
logAllocationsAndDeallocations,
};
}
function ensureIsNumber(value) {
if (typeof value !== "number") {
throw new Error(`Expected a number: ${value}`);
}
return value;
}
function formatHex(number) {
return `0x${number.toString(16)}`;
}