Ingest live 3proxy traffic from access logs
This commit is contained in:
104
server/lib/traffic.test.ts
Normal file
104
server/lib/traffic.test.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import fs from 'node:fs/promises';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { afterEach, describe, expect, it } from 'vitest';
|
||||
import type { ProxyUserRecord } from '../../src/shared/contracts';
|
||||
import { readObservedTraffic } from './traffic';
|
||||
|
||||
const cleanupDirs: string[] = [];
|
||||
|
||||
afterEach(async () => {
|
||||
await Promise.all(cleanupDirs.splice(0).map((dir) => fs.rm(dir, { recursive: true, force: true })));
|
||||
});
|
||||
|
||||
describe('readObservedTraffic', () => {
|
||||
it('derives current user usage, recent activity, and daily totals from 3proxy access logs', async () => {
|
||||
const dir = await fs.mkdtemp(path.join(os.tmpdir(), '3proxy-traffic-'));
|
||||
cleanupDirs.push(dir);
|
||||
|
||||
const logDir = path.join(dir, 'logs');
|
||||
await fs.mkdir(logDir, { recursive: true });
|
||||
await fs.writeFile(
|
||||
path.join(logDir, '3proxy.log.2026.04.02'),
|
||||
[
|
||||
'260402111500.000 1080 00000 night-shift 172.19.0.1:54402 104.18.27.120:80 100 900 0 GET http://example.com/ HTTP/1.1',
|
||||
'260402115900.000 2080 00000 lab-unlimited 172.19.0.1:53490 8.6.112.0:80 75 842 0 CONNECT example.com:80',
|
||||
'260402115930.000 1080 00000 - 0.0.0.0:1080 0.0.0.0:0 0 0 0 Accepting connections [18/1]',
|
||||
].join('\n'),
|
||||
'utf8',
|
||||
);
|
||||
await fs.writeFile(
|
||||
path.join(logDir, '3proxy.log.2026.04.01'),
|
||||
'260401230000.000 1080 00000 night-shift 172.19.0.1:50000 104.18.27.120:80 50 150 0 GET http://example.com/ HTTP/1.1\n',
|
||||
'utf8',
|
||||
);
|
||||
|
||||
const users: ProxyUserRecord[] = [
|
||||
{
|
||||
id: 'u-1',
|
||||
username: 'night-shift',
|
||||
password: 'secret',
|
||||
serviceId: 'socks-main',
|
||||
status: 'idle',
|
||||
usedBytes: 0,
|
||||
quotaBytes: 1024,
|
||||
},
|
||||
{
|
||||
id: 'u-2',
|
||||
username: 'lab-unlimited',
|
||||
password: 'secret',
|
||||
serviceId: 'socks-lab',
|
||||
status: 'idle',
|
||||
usedBytes: 0,
|
||||
quotaBytes: null,
|
||||
},
|
||||
];
|
||||
|
||||
const observed = await readObservedTraffic(
|
||||
{
|
||||
rootDir: dir,
|
||||
configPath: path.join(dir, 'generated', '3proxy.cfg'),
|
||||
counterPath: path.join(dir, 'state', 'counters.3cf'),
|
||||
reportDir: path.join(dir, 'state', 'reports'),
|
||||
logPath: path.join(logDir, '3proxy.log'),
|
||||
pidPath: path.join(dir, '3proxy.pid'),
|
||||
},
|
||||
users,
|
||||
new Date(2026, 3, 2, 12, 0, 0, 0),
|
||||
);
|
||||
|
||||
expect(observed.totalBytes).toBe(1917);
|
||||
expect(observed.liveConnections).toBe(1);
|
||||
expect(observed.activeUsers).toBe(2);
|
||||
expect(observed.userBytesByName.get('night-shift')).toBe(1000);
|
||||
expect(observed.userBytesByName.get('lab-unlimited')).toBe(917);
|
||||
expect(observed.recentUsers.has('night-shift')).toBe(false);
|
||||
expect(observed.recentUsers.has('lab-unlimited')).toBe(true);
|
||||
expect(observed.daily[observed.daily.length - 2].bytes).toBe(200);
|
||||
expect(observed.daily[observed.daily.length - 1].bytes).toBe(1917);
|
||||
});
|
||||
|
||||
it('returns zeroed metrics when no runtime logs exist yet', async () => {
|
||||
const dir = await fs.mkdtemp(path.join(os.tmpdir(), '3proxy-traffic-empty-'));
|
||||
cleanupDirs.push(dir);
|
||||
|
||||
const observed = await readObservedTraffic(
|
||||
{
|
||||
rootDir: dir,
|
||||
configPath: path.join(dir, 'generated', '3proxy.cfg'),
|
||||
counterPath: path.join(dir, 'state', 'counters.3cf'),
|
||||
reportDir: path.join(dir, 'state', 'reports'),
|
||||
logPath: path.join(dir, 'logs', '3proxy.log'),
|
||||
pidPath: path.join(dir, '3proxy.pid'),
|
||||
},
|
||||
[],
|
||||
new Date(2026, 3, 2, 12, 0, 0, 0),
|
||||
);
|
||||
|
||||
expect(observed.totalBytes).toBe(0);
|
||||
expect(observed.liveConnections).toBe(0);
|
||||
expect(observed.activeUsers).toBe(0);
|
||||
expect(observed.daily).toHaveLength(5);
|
||||
expect(observed.daily.every((entry) => entry.bytes === 0 && entry.share === 0)).toBe(true);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user