0.2.6-dev: traffic counters — HTTP body bytes by category (assets / audio / code / data) and WebSocket wire / payload bytes since the start at GET /healthz?traffic=1, and the page's own downloads (Resource Timing transferSize) as a traffic line in the 问题反馈 diagnostics (host bandwidth layer 0.4); golden: unchanged
This commit is contained in:
1 parent
05b0dba8b5
commit
be8d382c05
14 files changed
+516
-23
No files matched your search
@@ -0,0 +1,105 @@
|
||||
// The server's traffic counters (server/http/traffic.js, GET /healthz?traffic=1; host bandwidth layer 0): the body
|
||||
// bytes every HTTP response wrote, by category, 304s apart, and the WebSocket's wire and payload bytes both ways.
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import http from 'node:http';
|
||||
import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { randomBytes } from 'node:crypto';
|
||||
import { startServer } from '../server/index.js';
|
||||
import { createTrafficCounter } from '../server/http/traffic.js';
|
||||
import { TestClient } from './helpers/wsClient.js';
|
||||
|
||||
function get(port, p, headers = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = http.request({ host: '127.0.0.1', port, path: p, headers, agent: false }, (res) => {
|
||||
const chunks = [];
|
||||
res.on('data', (c) => chunks.push(c));
|
||||
res.on('end', () => resolve({ status: res.statusCode, headers: res.headers, body: Buffer.concat(chunks) }));
|
||||
});
|
||||
req.on('error', reject);
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
test('HTTP: body bytes by category as written (compressed), 304s counted, /healthz reports them only when asked', async () => {
|
||||
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'sp-traffic-'));
|
||||
const w = (rel, content) => { fs.mkdirSync(path.dirname(path.join(tmp, rel)), { recursive: true }); fs.writeFileSync(path.join(tmp, rel), content); };
|
||||
const js = `// app\n${'export const x = 1;\n'.repeat(400)}`;
|
||||
const png = randomBytes(3000);
|
||||
const mp3 = randomBytes(5000);
|
||||
w('public/index.html', '<!doctype html><p>hi</p>');
|
||||
w('public/js/app.js', js);
|
||||
w('public/assets/img/a.png', png);
|
||||
w('public/assets/audio/bgm/a.mp3', mp3);
|
||||
w('data/config.json', JSON.stringify({ a: 1 }));
|
||||
w('shared/x.js', 'export const y = 2;');
|
||||
const srv = await startServer({ port: 0, host: '127.0.0.1', quiet: true, publicDir: path.join(tmp, 'public'), dataDir: path.join(tmp, 'data'), sharedDir: path.join(tmp, 'shared') });
|
||||
try {
|
||||
const plain = await get(srv.port, '/health');
|
||||
assert.equal(plain.status, 404);
|
||||
const gz = await get(srv.port, '/js/app.js', { 'accept-encoding': 'gzip' });
|
||||
assert.ok(gz.body.length < js.length, 'compressed');
|
||||
await get(srv.port, '/js/app.js', { 'if-none-match': (await get(srv.port, '/js/app.js')).headers.etag });
|
||||
await get(srv.port, '/assets/img/a.png');
|
||||
await get(srv.port, '/media/bgm/a');
|
||||
await get(srv.port, '/data/config.json');
|
||||
const before = await get(srv.port, '/healthz');
|
||||
assert.equal(JSON.parse(before.body).traffic, undefined, 'plain /healthz stays as it was');
|
||||
const t = JSON.parse((await get(srv.port, '/healthz?traffic=1')).body).traffic;
|
||||
assert.equal(t.http.code.requests, 3);
|
||||
assert.equal(t.http.code.bytes, gz.body.length + js.length, 'what was written: the gzip body, the plain body, nothing for the 304');
|
||||
assert.equal(t.http.code.notModified, 1);
|
||||
assert.deepEqual(t.http.assets, { requests: 1, bytes: png.length, notModified: 0 });
|
||||
assert.deepEqual(t.http.audio, { requests: 1, bytes: mp3.length, notModified: 0 });
|
||||
assert.equal(t.http.data.requests, 1);
|
||||
assert.equal(t.http.other.requests, 2, 'the 404 page and the first /healthz');
|
||||
assert.ok(t.http.other.bytes > 0);
|
||||
assert.equal(t.http.total.requests, 8);
|
||||
assert.equal(typeof t.since, 'string');
|
||||
assert.equal(srv.traffic.snapshot().http.total.requests, 9, 'the traffic request itself once it has finished');
|
||||
} finally {
|
||||
await srv.close();
|
||||
fs.rmSync(tmp, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('WebSocket: wire bytes both ways (after permessage-deflate) and the payloads, live and after the close', async () => {
|
||||
const srv = await startServer({ port: 0, host: '127.0.0.1', quiet: true });
|
||||
try {
|
||||
const c = await TestClient.connect(`ws://127.0.0.1:${srv.port}/ws`);
|
||||
await c.hello('流量计数');
|
||||
const live = srv.traffic.snapshot().ws;
|
||||
assert.equal(live.connections, 1);
|
||||
assert.equal(live.open, 1);
|
||||
assert.ok(live.messagesOut >= 1 && live.payloadOut > 0, JSON.stringify(live));
|
||||
assert.ok(live.messagesIn >= 1 && live.payloadIn > 0);
|
||||
assert.ok(live.wireOut > 0 && live.wireIn > 0);
|
||||
await c.close();
|
||||
for (let i = 0; i < 50 && srv.traffic.snapshot().ws.open; i++) await new Promise((r) => setTimeout(r, 20));
|
||||
const done = srv.traffic.snapshot().ws;
|
||||
assert.equal(done.open, 0);
|
||||
assert.ok(done.wireOut >= live.wireOut, 'the closed socket\'s bytes stay in the totals');
|
||||
} finally {
|
||||
await srv.close();
|
||||
}
|
||||
});
|
||||
|
||||
test('the counter on its own: a string body in another encoding, an aborted response, the payload of a fragmented message', () => {
|
||||
const tc = createTrafficCounter({ now: () => 1000 });
|
||||
const listeners = {};
|
||||
const res = { statusCode: 200, write() { return true; }, end() {}, once(ev, fn) { listeners[ev] = fn; } };
|
||||
tc.meter(res, '/data/x.json');
|
||||
res.write('aGk=', 'base64');
|
||||
res.end('é');
|
||||
listeners.close();
|
||||
const s = tc.snapshot();
|
||||
assert.deepEqual(s.http.data, { requests: 1, bytes: 2 + 2, notModified: 0 });
|
||||
tc.wsSent('{"t":"pong"}');
|
||||
tc.wsReceived([Buffer.alloc(3), Buffer.alloc(4)]);
|
||||
const ws = tc.snapshot().ws;
|
||||
assert.equal(ws.payloadOut, 12);
|
||||
assert.equal(ws.payloadIn, 7);
|
||||
assert.equal(tc.snapshot().seconds, 0);
|
||||
});
|
||||
Reference in new issue
Block a user