feat(speedtest): full speedtest-tracker-style automation (2.9.0)
Switch worker to the Ookla CLI (jitter, packet loss, server, ISP, shareable result URL, bytes). Migration 028 enriches speedtest_results + adds a generic app_settings store. New /speedtest page: KPIs, throughput + latency charts, window stats, configurable schedule (reschedulable cron) & low-speed alert threshold, history table. SV card gains ping/jitter + a link through to the page. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -6,18 +6,42 @@ const pexec = promisify(execFile);
|
||||
|
||||
export const NAME = 'speedtest';
|
||||
|
||||
// Default runner uses speedtest-cli --json (bits/s → Mbps). Swap binary/flags
|
||||
// here if the box has the Ookla `speedtest -f json` CLI instead.
|
||||
async function defaultRunner() {
|
||||
const { stdout } = await pexec('speedtest-cli', ['--json'], { timeout: 120000 });
|
||||
// Ookla CLI gives the full metric set (jitter, packet loss, server, ISP,
|
||||
// shareable result URL). Override the binary via SPEEDTEST_BIN if needed.
|
||||
const OOKLA_BIN = process.env.SPEEDTEST_BIN || 'ookla-speedtest';
|
||||
|
||||
async function ooklaRunner() {
|
||||
const { stdout } = await pexec(OOKLA_BIN,
|
||||
['-f', 'json', '--accept-license', '--accept-gdpr'], { timeout: 120000 });
|
||||
const j = JSON.parse(stdout);
|
||||
return { down_mbps: j.download / 1e6, up_mbps: j.upload / 1e6, ping_ms: j.ping };
|
||||
const mbps = bw => (Number(bw) || 0) * 8 / 1e6; // Ookla bandwidth is bytes/s
|
||||
return {
|
||||
down_mbps: mbps(j.download?.bandwidth),
|
||||
up_mbps: mbps(j.upload?.bandwidth),
|
||||
ping_ms: j.ping?.latency ?? null,
|
||||
jitter_ms: j.ping?.jitter ?? null,
|
||||
packet_loss: j.packetLoss ?? null,
|
||||
server_name: j.server ? [j.server.name, j.server.location].filter(Boolean).join(' · ') : null,
|
||||
server_id: j.server?.id != null ? String(j.server.id) : null,
|
||||
isp: j.isp ?? null,
|
||||
result_url: j.result?.url ?? null,
|
||||
down_bytes: j.download?.bytes ?? null,
|
||||
up_bytes: j.upload?.bytes ?? null,
|
||||
ok: true
|
||||
};
|
||||
}
|
||||
let runner = defaultRunner;
|
||||
let runner = ooklaRunner;
|
||||
export function _setRunner(fn) { runner = fn; }
|
||||
|
||||
export async function handler(_job) {
|
||||
const r = await runner();
|
||||
await repo.record(r);
|
||||
log.info(r, 'speedtest recorded');
|
||||
try {
|
||||
const r = await runner();
|
||||
const saved = await repo.record(r);
|
||||
log.info({ down: r.down_mbps, up: r.up_mbps, ping: r.ping_ms }, 'speedtest recorded');
|
||||
return saved;
|
||||
} catch (e) {
|
||||
await repo.record({ ok: false, error: String(e?.message || e).slice(0, 300) });
|
||||
log.error({ err: e }, 'speedtest failed');
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user