test: Consolidate memory baseline specs and add no-mcp impact test (#30745)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Declan Carroll 2026-05-20 11:16:00 +01:00 committed by GitHub
parent c8ac2fb1f2
commit 06e78f39be
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 105 additions and 113 deletions

View File

@ -101,6 +101,38 @@ await test.info().attach('performance-metrics', {
});
```
### "I want to measure how much memory a module adds to the instance"
Idle heap baselines are collected by the `memory-consumption-*.spec.ts` files via the
shared `runMemoryBaseline()` helper. Each spec disables one or more modules through
`N8N_DISABLED_MODULES` and stabilises memory post-GC, so the diff between specs
reveals a module's footprint.
```typescript
// memory-consumption-no-mcp-only.spec.ts
import { runMemoryBaseline } from './memory-baseline';
import { test } from '../../fixtures/base';
test.use({
capability: {
services: ['victoriaLogs', 'victoriaMetrics', 'vector'],
env: { N8N_DISABLED_MODULES: 'mcp' },
},
});
runMemoryBaseline({ name: 'no-mcp-only', owner: 'AI' });
```
Run a focused subset and compare `heap-used` (the only metric stable enough for
single-shot diffs — `heap-total` / `RSS` / `non-heap` are noisy):
```bash
pnpm test:performance --grep "no-mcp|memory"
```
Each spec emits `${name}-heap-used-baseline`, `${name}-rss-baseline`, etc. Add the
series to `.github/workflows/ci-pull-requests.yml` if you want it surfaced in PR
comments. Run twice and average to shake out variance before quoting numbers.
## API Reference
### `measurePerformance(page, actionName, actionFn)`

View File

@ -0,0 +1,37 @@
import { test, expect } from '../../fixtures/base';
import { attachMetric, getStableHeap } from '../../utils/performance-helper';
export function runMemoryBaseline({ name, owner }: { name: string; owner: string }) {
test.describe(
`Module Memory Impact · ${name} @capability:observability`,
{ annotation: [{ type: 'owner', description: owner }] },
() => {
test(`Idle baseline · ${name}`, async ({ n8nContainer, services }, testInfo) => {
const obs = services.observability;
const result = await getStableHeap(n8nContainer.baseUrl, obs.metrics);
await attachMetric(testInfo, `${name}-heap-used-baseline`, result.heapUsedMB, 'MB');
await attachMetric(testInfo, `${name}-heap-total-baseline`, result.heapTotalMB, 'MB');
await attachMetric(testInfo, `${name}-rss-baseline`, result.rssMB, 'MB');
await attachMetric(testInfo, `${name}-pss-baseline`, result.pssMB ?? 0, 'MB');
await attachMetric(
testInfo,
`${name}-non-heap-overhead-baseline`,
result.nonHeapOverheadMB,
'MB',
);
expect(result.heapUsedMB).toBeGreaterThan(0);
expect(result.heapTotalMB).toBeGreaterThan(0);
expect(result.rssMB).toBeGreaterThan(0);
console.log(
`[${name.toUpperCase()} IDLE] Heap used: ${result.heapUsedMB.toFixed(1)} MB | ` +
`Heap total: ${result.heapTotalMB.toFixed(1)} MB | ` +
`RSS: ${result.rssMB.toFixed(1)} MB | ` +
`Non-heap overhead: ${result.nonHeapOverheadMB.toFixed(1)} MB`,
);
});
},
);
}

View File

@ -1,5 +1,5 @@
import { test, expect } from '../../fixtures/base';
import { attachMetric, getStableHeap } from '../../utils/performance-helper';
import { runMemoryBaseline } from './memory-baseline';
import { test } from '../../fixtures/base';
test.use({
capability: {
@ -11,41 +11,4 @@ test.use({
},
});
test.describe(
'Agents Memory Consumption @capability:observability',
{
annotation: [{ type: 'owner', description: 'Agent' }],
},
() => {
test('Idle baseline with Agents module loaded', async ({
n8nContainer,
services,
}, testInfo) => {
const obs = services.observability;
const result = await getStableHeap(n8nContainer.baseUrl, obs.metrics);
await attachMetric(testInfo, 'agents-heap-used-baseline', result.heapUsedMB, 'MB');
await attachMetric(testInfo, 'agents-heap-total-baseline', result.heapTotalMB, 'MB');
await attachMetric(testInfo, 'agents-rss-baseline', result.rssMB, 'MB');
await attachMetric(testInfo, 'agents-pss-baseline', result.pssMB ?? 0, 'MB');
await attachMetric(
testInfo,
'agents-non-heap-overhead-baseline',
result.nonHeapOverheadMB,
'MB',
);
expect(result.heapUsedMB).toBeGreaterThan(0);
expect(result.heapTotalMB).toBeGreaterThan(0);
expect(result.rssMB).toBeGreaterThan(0);
console.log(
`[AGENTS IDLE] Heap used: ${result.heapUsedMB.toFixed(1)} MB | ` +
`Heap total: ${result.heapTotalMB.toFixed(1)} MB | ` +
`RSS: ${result.rssMB.toFixed(1)} MB | ` +
`Non-heap overhead: ${result.nonHeapOverheadMB.toFixed(1)} MB`,
);
});
},
);
runMemoryBaseline({ name: 'agents', owner: 'AI' });

View File

@ -1,6 +1,7 @@
import { test, expect } from '../../fixtures/base';
import { attachMetric, getStableHeap } from '../../utils/performance-helper';
import { runMemoryBaseline } from './memory-baseline';
import { test } from '../../fixtures/base';
// Emits `memory-*-baseline` series consumed by .github/workflows/ci-pull-requests.yml — do not rename without updating that workflow.
test.use({
capability: {
resourceQuota: { memory: 0.75, cpu: 0.5 },
@ -8,34 +9,4 @@ test.use({
},
});
test.describe(
'Memory Consumption @capability:observability',
{
annotation: [{ type: 'owner', description: 'Catalysts' }],
},
() => {
test('Memory consumption baseline with starter plan resources', async ({
n8nContainer,
services,
}, testInfo) => {
const obs = services.observability;
const result = await getStableHeap(n8nContainer.baseUrl, obs.metrics);
await attachMetric(testInfo, 'memory-heap-used-baseline', result.heapUsedMB, 'MB');
await attachMetric(testInfo, 'memory-heap-total-baseline', result.heapTotalMB, 'MB');
await attachMetric(testInfo, 'memory-rss-baseline', result.rssMB, 'MB');
await attachMetric(testInfo, 'memory-pss-baseline', result.pssMB ?? 0, 'MB');
await attachMetric(
testInfo,
'memory-non-heap-overhead-baseline',
result.nonHeapOverheadMB,
'MB',
);
expect(result.heapUsedMB).toBeGreaterThan(0);
expect(result.heapTotalMB).toBeGreaterThan(0);
expect(result.rssMB).toBeGreaterThan(0);
});
},
);
runMemoryBaseline({ name: 'memory', owner: 'Catalysts' });

View File

@ -1,5 +1,5 @@
import { test, expect } from '../../fixtures/base';
import { attachMetric, getStableHeap } from '../../utils/performance-helper';
import { runMemoryBaseline } from './memory-baseline';
import { test } from '../../fixtures/base';
test.use({
capability: {
@ -12,41 +12,4 @@ test.use({
},
});
test.describe(
'Instance AI Memory Consumption @capability:observability',
{
annotation: [{ type: 'owner', description: 'Catalysts' }],
},
() => {
test('Idle baseline with Instance AI module loaded', async ({
n8nContainer,
services,
}, testInfo) => {
const obs = services.observability;
const result = await getStableHeap(n8nContainer.baseUrl, obs.metrics);
await attachMetric(testInfo, 'instance-ai-heap-used-baseline', result.heapUsedMB, 'MB');
await attachMetric(testInfo, 'instance-ai-heap-total-baseline', result.heapTotalMB, 'MB');
await attachMetric(testInfo, 'instance-ai-rss-baseline', result.rssMB, 'MB');
await attachMetric(testInfo, 'instance-ai-pss-baseline', result.pssMB ?? 0, 'MB');
await attachMetric(
testInfo,
'instance-ai-non-heap-overhead-baseline',
result.nonHeapOverheadMB,
'MB',
);
expect(result.heapUsedMB).toBeGreaterThan(0);
expect(result.heapTotalMB).toBeGreaterThan(0);
expect(result.rssMB).toBeGreaterThan(0);
console.log(
`[INSTANCE AI IDLE] Heap used: ${result.heapUsedMB.toFixed(1)} MB | ` +
`Heap total: ${result.heapTotalMB.toFixed(1)} MB | ` +
`RSS: ${result.rssMB.toFixed(1)} MB | ` +
`Non-heap overhead: ${result.nonHeapOverheadMB.toFixed(1)} MB`,
);
});
},
);
runMemoryBaseline({ name: 'instance-ai', owner: 'Instance AI' });

View File

@ -0,0 +1,13 @@
import { runMemoryBaseline } from './memory-baseline';
import { test } from '../../fixtures/base';
test.use({
capability: {
services: ['victoriaLogs', 'victoriaMetrics', 'vector'],
env: {
N8N_DISABLED_MODULES: 'mcp',
},
},
});
runMemoryBaseline({ name: 'no-mcp-only', owner: 'AI' });

View File

@ -0,0 +1,13 @@
import { runMemoryBaseline } from './memory-baseline';
import { test } from '../../fixtures/base';
test.use({
capability: {
services: ['victoriaLogs', 'victoriaMetrics', 'vector'],
env: {
N8N_DISABLED_MODULES: 'mcp-registry',
},
},
});
runMemoryBaseline({ name: 'no-mcp-registry', owner: 'AI' });