Files
MatchMaster/tools/test-level-perf.cjs

89 lines
5.9 KiB
JavaScript

const test = require('node:test'), assert = require('node:assert/strict');
const { create } = require('../packages/level-resources/perf');
test('diagnostics preserve values and errors, aggregate hot paths and bound retained output', async () => {
let time = 0; const lines = [];
const perf = create({ enabled: true, now: () => time, output: line => lines.push(JSON.parse(line.slice('[LevelPerf] '.length))) });
perf.session({ version: 'test' });
const end = perf.begin('level.load', { id: 222 });
const sentinel = Error('sentinel');
for (let i = 0; i < 2000; i++) assert.equal(perf.measure('hash', () => { time += 2; return 7; }, 100), 7);
assert.throws(() => perf.measure('failed', () => { throw sentinel; }), e => e === sentinel);
await assert.rejects(perf.measureAsync('async.failed', () => Promise.reject(sentinel)), e => e === sentinel);
assert.equal(await perf.measureAsync('read', async () => 'bytes'), 'bytes');
end({ status: 'ok' }); end(); perf.flush();
assert(lines.length < 10, 'hot paths must not print one log per resource');
const summary = lines.find(l => l.event === 'summary' && l.metrics.hash);
assert.equal(summary.metrics.hash.count, 2000); assert.equal(summary.metrics.hash.totalMs, 4000);
assert.equal(summary.metrics.hash.maxMs, 2); assert.equal(summary.metrics.hash.bytes, 200000);
assert.equal(summary.metrics.failed.errors, 1);
assert.equal(lines.filter(l => l.event === 'end').length, 1);
assert.equal(lines.find(l => l.event === 'begin').spanId, lines.find(l => l.event === 'end').spanId);
for (let i = 0; i < 500; i++) perf.emit('sample');
assert.equal(perf.dump().split('\n').length, 400);
perf.setEnabled(false); const count = lines.length;
perf.emit('hidden'); perf.measure('hash', () => 1); perf.flush(); assert.equal(lines.length, count);
});
test('event-loop lag excludes background time and logging failures do not affect tasks', () => {
let time = 0; const lines = [];
const perf = create({ enabled: true, now: () => time, output: line => lines.push(line) });
time = 500; perf.tick(); assert.equal(lines.filter(l => l.includes('eventLoopLag')).length, 1);
perf.setActive(false); time += 10000; perf.tick(); perf.setActive(true); time += 250; perf.tick();
assert.equal(lines.filter(l => l.includes('eventLoopLag')).length, 1);
const broken = create({ enabled: true, output() { throw Error('console unavailable'); } });
assert.equal(broken.measure('ok', () => 42), 42); broken.begin('ok')();
});
test('image details aggregate thousands of loads with bounded slow and failure samples', () => {
let time = 0; const lines = [];
const perf = create({ enabled: true, now: () => time, output: line => lines.push(JSON.parse(line.slice('[LevelPerf] '.length))) });
const group = perf.begin('images.group', { kind: 'block', count: 1000 });
for (let i = 0; i < 1000; i++) {
for (const stage of ['frame', 'engineLoad', 'createFrame', 'reuse']) {
const end = perf.begin('images.' + stage, { path: 'image-' + i }); time++; end({ status: 'ok' });
}
const end = perf.begin('images.asset', { path: 'image-' + i, kind: 'png' }); time += 10 + i;
end({ status: i % 2 ? 'ok' : 'error', source: '本地缓存', lookupMs: 2, cachePrepareMs: 0, engineLoadMs: 8 + i, validateMs: 0 }); end();
const download = perf.begin('images.download', { path: 'image-' + i, queueMs: 5 }); time += 20;
download({ status: 'error', networkMs: 20, installMs: 0 });
}
group({ status: 'ok' }); perf.flush();
assert.equal(lines.length, 4, 'enabled + group begin/end + one summary, independent of image count');
const summary = lines.find(row => row.event === 'summary');
assert.equal(summary.metrics['images.asset'].count, 1000);
assert.equal(summary.metrics['images.asset'].errors, 500);
assert.equal(summary.metrics['images.asset.source.local'].count, 1000);
assert.equal(summary.metrics['images.asset.lookupMs'].totalMs, 2000);
assert.equal(summary.metrics['images.download.queueMs'].totalMs, 5000);
assert.equal(summary.metrics['images.download'].errors, 1000);
assert.equal(summary.images.assets.length, 3); assert.equal(summary.images.assets[0].path, 'image-999');
assert.equal(summary.images.downloads.length, 3); assert.equal(summary.images.failures.length, 3);
assert.equal(lines.find(row => row.event === 'end').kind, 'block');
const count = lines.length; perf.flush(); assert.equal(lines.length, count, 'samples reset after flush');
});
test('silent image stages remain observable while pending and disabled diagnostics stay silent', () => {
let time = 0; const lines = [];
const perf = create({ enabled: true, now: () => time, output: line => lines.push(JSON.parse(line.slice('[LevelPerf] '.length))) });
const end = perf.begin('images.engineLoad', { path: 'door' }); time = 3000; perf.flush();
const summary = lines.find(row => row.event === 'summary');
assert.equal(summary.pendingCount, 1); assert.equal(summary.pending[0].stage, 'images.engineLoad');
perf.setEnabled(false); const count = lines.length; end({ status: 'error' }); perf.flush();
assert.equal(lines.length, count);
});
test('scene resource diagnostics aggregate thousands of dependencies into three slow samples', () => {
let time = 0; const rows = [];
const perf = create({ enabled: true, now: () => time, output: line => rows.push(JSON.parse(line.slice(12))) });
for (let i = 0; i < 1000; i++) {
const end = perf.begin('scene.resource.parse', { resource: 'asset-' + i, requestId: 9 });
time += i; end({ status: 'ok' });
}
perf.flush(); assert.equal(rows.length, 2);
assert.equal(rows[1].metrics['scene.resource.parse'].count, 1000);
assert.equal(rows[1].scene['scene.resource.parse'].length, 3);
assert.equal(rows[1].scene['scene.resource.parse'][0].resource, 'asset-999');
perf.flush(); assert.equal(rows.length, 2);
});