import assert from 'node:assert/strict'; import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import { DatabaseSync } from 'node:sqlite'; import { performance } from 'node:perf_hooks'; import test from 'node:test'; import { openTrafficHistoryStore } from '../../dist/server/services/trafficHistoryStore.js'; import { createTrafficHistoryService } from '../../dist/server/services/trafficHistoryService.js'; import { parseTrafficHistoryQuery } from '../../dist/shared/trafficHistory.js'; test('large history preserves totals and serves concurrent readers through cleanup and filters', async (t) => { const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'harbor-history-load-')); const file = path.join(directory, 'traffic.sqlite'); const at = Math.floor(Date.now() / 60_000) * 60_000; const minutes = process.env.HARBOR_HISTORY_LOAD === '1' ? 3000 : 300; let service; t.after(async () => { await service?.close(); fs.rmSync(directory, { recursive: true, force: true }); }); openTrafficHistoryStore(file).close(); const db = new DatabaseSync(file); try { db.exec(`BEGIN; WITH RECURSIVE n(i) AS (VALUES(1) UNION ALL SELECT i+1 FROM n WHERE i<2000) INSERT INTO dimensions SELECT i,'dimension-'||i,'device-'||(i%20),'Device '||(i%20), '192.0.2.1','tproxy-in','service-'||(i%250),'domain-'||(i%250)||'.test', 'host-'||i||'.test','203.0.113.'||(i%250),'vpn','vpn-one' FROM n;`); db.prepare(`WITH RECURSIVE minutes(i) AS (VALUES(1) UNION ALL SELECT i+1 FROM minutes WHERE i1000').run(at - 91 * 86_400_000); db.exec('COMMIT'); // Exercise the additive index upgrade on a populated pre-fix database. db.exec('DROP INDEX buckets_dimension_time'); } finally { db.close(); } service = createTrafficHistoryService({ filePath: file, source: () => 'live' }); const query = parseTrafficHistoryQuery(new URLSearchParams(`range=7d&until=${at}`)); const start = performance.now(); const concurrent = await Promise.all(Array.from({ length: 4 }, () => service.query(query))); const concurrentMs = performance.now() - start; for (const result of concurrent) { assert.equal(result.storage.status, 'ready'); assert.equal(result.totals.downloadBytes, String(minutes * 1000 * 200)); assert.equal(result.origins.length, 20); assert.equal(result.nextOffset, 100); } const timings = []; for (const [filter, dimensions] of [ [{ originId: 'device-1' }, 50], [{ search: 'host-99' }, 11], [{ level: 'ip', service: 'service-1', domain: 'domain-1.test', hostname: 'host-1.test' }, 1], [{ offset: 300 }, 1000], ]) { const begin = performance.now(); const result = await service.query({ ...query, ...filter }); timings.push({ filter, ms: performance.now() - begin }); assert.equal(result.storage.status, 'ready'); assert.equal(result.totals.downloadBytes, String(minutes * dimensions * 200)); if (filter.offset) assert.equal(result.rows.length, 0); } t.diagnostic(JSON.stringify({ buckets: minutes * 1000, concurrentMs, timings })); });