improve memory leak test (#2621)
This commit is contained in:
parent
91e5b268d0
commit
6edf47a890
2 changed files with 43 additions and 58 deletions
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
|
@ -208,8 +208,8 @@ jobs:
|
|||
|
||||
- name: Memory Leak Test
|
||||
run: |
|
||||
node ./scripts/memory/mk.js
|
||||
node ./scripts/memory/index.js
|
||||
node ./scripts/memory/mk.js
|
||||
node --expose-gc ./scripts/memory/index.js --ci
|
||||
|
||||
|
||||
# Changelog can only run _after_ build.
|
||||
|
|
|
@ -1,87 +1,72 @@
|
|||
import { execa } from 'execa';
|
||||
import { fileURLToPath } from 'url';
|
||||
import v8 from 'v8';
|
||||
import dev from '../../packages/astro/dist/core/dev/index.js';
|
||||
import { loadConfig } from '../../packages/astro/dist/core/config.js';
|
||||
import prettyBytes from 'pretty-bytes';
|
||||
|
||||
if (!global.gc) {
|
||||
console.error('ERROR: Node must be run with --expose-gc');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const isCI = process.argv.includes('--ci');
|
||||
|
||||
/** URL directory containing the entire project. */
|
||||
const projDir = new URL('./project/', import.meta.url);
|
||||
|
||||
function mean(numbers) {
|
||||
var total = 0,
|
||||
i;
|
||||
for (i = 0; i < numbers.length; i += 1) {
|
||||
total += numbers[i];
|
||||
}
|
||||
return total / numbers.length;
|
||||
}
|
||||
|
||||
function median(numbers) {
|
||||
// median of [3, 5, 4, 4, 1, 1, 2, 3] = 3
|
||||
var median = 0,
|
||||
numsLen = numbers.length;
|
||||
numbers.sort();
|
||||
|
||||
if (
|
||||
numsLen % 2 ===
|
||||
0 // is even
|
||||
) {
|
||||
// average of two middle numbers
|
||||
median = (numbers[numsLen / 2 - 1] + numbers[numsLen / 2]) / 2;
|
||||
} else {
|
||||
// is odd
|
||||
// middle number only
|
||||
median = numbers[(numsLen - 1) / 2];
|
||||
}
|
||||
|
||||
return median;
|
||||
}
|
||||
|
||||
let config = await loadConfig({
|
||||
cwd: fileURLToPath(projDir),
|
||||
});
|
||||
|
||||
config.buildOptions.experimentalStaticBuild = true;
|
||||
|
||||
const server = await dev(config, { logging: 'error' });
|
||||
const server = await dev(config, { logging: { level: 'error' } });
|
||||
|
||||
// Prime the server so initial memory is created
|
||||
await fetch(`http://localhost:3000/page-0`);
|
||||
|
||||
const sizes = [];
|
||||
|
||||
function addSize() {
|
||||
sizes.push(v8.getHeapStatistics().total_heap_size);
|
||||
}
|
||||
|
||||
async function run() {
|
||||
addSize();
|
||||
for (let i = 0; i < 100; i++) {
|
||||
let path = `/page-${i}`;
|
||||
await fetch(`http://localhost:3000${path}`);
|
||||
}
|
||||
addSize();
|
||||
}
|
||||
|
||||
for (let i = 0; i < 100; i++) {
|
||||
await run();
|
||||
}
|
||||
global.gc();
|
||||
const startSize = v8.getHeapStatistics().used_heap_size;
|
||||
|
||||
let lastThirthy = sizes.slice(sizes.length - 30);
|
||||
let averageOfLastThirty = mean(lastThirthy);
|
||||
let medianOfAll = median(sizes);
|
||||
|
||||
// If the trailing average is higher than the median, see if it's more than 5% higher
|
||||
if (averageOfLastThirty > medianOfAll) {
|
||||
let percentage = Math.abs(averageOfLastThirty - medianOfAll) / medianOfAll;
|
||||
if (percentage > 0.1) {
|
||||
throw new Error(
|
||||
`The average towards the end (${prettyBytes(averageOfLastThirty)}) is more than 10% higher than the median of all runs (${prettyBytes(
|
||||
medianOfAll
|
||||
)}). This tells us that memory continues to grow and a leak is likely.`
|
||||
);
|
||||
// HUMAN mode: Runs forever. Optimized for accurate results on each snapshot Slower than CI.
|
||||
if (!isCI) {
|
||||
console.log(`Greetings, human. This test will run forever. Run with the "--ci" flag to finish with a result.`);
|
||||
let i = 1;
|
||||
while (i++) {
|
||||
await run();
|
||||
global.gc();
|
||||
const checkpoint = v8.getHeapStatistics().used_heap_size;
|
||||
console.log(`Snapshot ${String(i).padStart(3, '0')}: ${(checkpoint / startSize) * 100}%`);
|
||||
}
|
||||
}
|
||||
|
||||
// CI mode: Runs 100 times. Optimized for speed with an accurate final result.
|
||||
for (let i = 0; i < 100; i++) {
|
||||
await run();
|
||||
const checkpoint = v8.getHeapStatistics().used_heap_size;
|
||||
console.log(`Estimate ${String(i).padStart(3, '0')}/100: ${(checkpoint / startSize) * 100}%`);
|
||||
}
|
||||
|
||||
console.log(`Test complete. Running final garbage collection...`);
|
||||
global.gc();
|
||||
const endSize = v8.getHeapStatistics().used_heap_size;
|
||||
|
||||
// If the trailing average is higher than the median, see if it's more than 5% higher
|
||||
let percentage = endSize / startSize;
|
||||
const TEST_THRESHOLD = 1.2;
|
||||
const isPass = percentage < TEST_THRESHOLD;
|
||||
console.log(``);
|
||||
console.log(`Result: ${isPass ? 'PASS' : 'FAIL'} (${percentage * 100}%)`);
|
||||
console.log(`Memory usage began at ${prettyBytes(startSize)} and finished at ${prettyBytes(endSize)}.`);
|
||||
console.log(`The threshold for a probable memory leak is ${TEST_THRESHOLD * 100}%`);
|
||||
console.log(``);
|
||||
console.log(`Exiting...`);
|
||||
await server.stop();
|
||||
process.exit(isPass ? 0 : 1);
|
||||
|
|
Loading…
Reference in a new issue