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
|
- name: Memory Leak Test
|
||||||
run: |
|
run: |
|
||||||
node ./scripts/memory/mk.js
|
node ./scripts/memory/mk.js
|
||||||
node ./scripts/memory/index.js
|
node --expose-gc ./scripts/memory/index.js --ci
|
||||||
|
|
||||||
|
|
||||||
# Changelog can only run _after_ build.
|
# Changelog can only run _after_ build.
|
||||||
|
|
|
@ -1,87 +1,72 @@
|
||||||
import { execa } from 'execa';
|
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
import v8 from 'v8';
|
import v8 from 'v8';
|
||||||
import dev from '../../packages/astro/dist/core/dev/index.js';
|
import dev from '../../packages/astro/dist/core/dev/index.js';
|
||||||
import { loadConfig } from '../../packages/astro/dist/core/config.js';
|
import { loadConfig } from '../../packages/astro/dist/core/config.js';
|
||||||
import prettyBytes from 'pretty-bytes';
|
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. */
|
/** URL directory containing the entire project. */
|
||||||
const projDir = new URL('./project/', import.meta.url);
|
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({
|
let config = await loadConfig({
|
||||||
cwd: fileURLToPath(projDir),
|
cwd: fileURLToPath(projDir),
|
||||||
});
|
});
|
||||||
|
|
||||||
config.buildOptions.experimentalStaticBuild = true;
|
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
|
// Prime the server so initial memory is created
|
||||||
await fetch(`http://localhost:3000/page-0`);
|
await fetch(`http://localhost:3000/page-0`);
|
||||||
|
|
||||||
const sizes = [];
|
|
||||||
|
|
||||||
function addSize() {
|
|
||||||
sizes.push(v8.getHeapStatistics().total_heap_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function run() {
|
async function run() {
|
||||||
addSize();
|
|
||||||
for (let i = 0; i < 100; i++) {
|
for (let i = 0; i < 100; i++) {
|
||||||
let path = `/page-${i}`;
|
let path = `/page-${i}`;
|
||||||
await fetch(`http://localhost:3000${path}`);
|
await fetch(`http://localhost:3000${path}`);
|
||||||
}
|
}
|
||||||
addSize();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < 100; i++) {
|
global.gc();
|
||||||
await run();
|
const startSize = v8.getHeapStatistics().used_heap_size;
|
||||||
}
|
|
||||||
|
|
||||||
let lastThirthy = sizes.slice(sizes.length - 30);
|
// HUMAN mode: Runs forever. Optimized for accurate results on each snapshot Slower than CI.
|
||||||
let averageOfLastThirty = mean(lastThirthy);
|
if (!isCI) {
|
||||||
let medianOfAll = median(sizes);
|
console.log(`Greetings, human. This test will run forever. Run with the "--ci" flag to finish with a result.`);
|
||||||
|
let i = 1;
|
||||||
// If the trailing average is higher than the median, see if it's more than 5% higher
|
while (i++) {
|
||||||
if (averageOfLastThirty > medianOfAll) {
|
await run();
|
||||||
let percentage = Math.abs(averageOfLastThirty - medianOfAll) / medianOfAll;
|
global.gc();
|
||||||
if (percentage > 0.1) {
|
const checkpoint = v8.getHeapStatistics().used_heap_size;
|
||||||
throw new Error(
|
console.log(`Snapshot ${String(i).padStart(3, '0')}: ${(checkpoint / startSize) * 100}%`);
|
||||||
`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.`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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();
|
await server.stop();
|
||||||
|
process.exit(isPass ? 0 : 1);
|
||||||
|
|
Loading…
Add table
Reference in a new issue