95 lines
4.0 KiB
TypeScript
95 lines
4.0 KiB
TypeScript
import { describe, it, expect, beforeEach } from 'vitest';
|
|
import { Registry } from 'prom-client';
|
|
import { createGatewayMetrics } from './gateway-metrics.js';
|
|
|
|
describe('metrics/gateway-metrics', () => {
|
|
let reg: Registry;
|
|
|
|
beforeEach(() => {
|
|
reg = new Registry();
|
|
});
|
|
|
|
it('registers all 11 metrics under the supplied prefix', async () => {
|
|
createGatewayMetrics(reg, 'aao_gateway_t');
|
|
const out = await reg.metrics();
|
|
for (const name of [
|
|
'aao_gateway_t_requests_total',
|
|
'aao_gateway_t_request_duration_seconds',
|
|
'aao_gateway_t_tokens_total',
|
|
'aao_gateway_t_backend_busy_slots',
|
|
'aao_gateway_t_backend_total_slots',
|
|
'aao_gateway_t_backend_online',
|
|
'aao_gateway_t_cache_hit_total',
|
|
'aao_gateway_t_cache_miss_total',
|
|
'aao_gateway_t_virtual_key_budget_used_ratio',
|
|
'aao_gateway_t_rate_limit_rejections_total',
|
|
'aao_gateway_t_active_streams',
|
|
]) {
|
|
expect(out).toContain(name);
|
|
}
|
|
});
|
|
|
|
it('counters emit a series per unique label set', async () => {
|
|
const m = createGatewayMetrics(reg, 'aao_gateway_c');
|
|
m.requestsTotal.inc({ team: 'alpha', backend: 'gpu-a', model: 'qwen3:8b', status: 'success' }, 3);
|
|
m.requestsTotal.inc({ team: 'alpha', backend: 'gpu-a', model: 'qwen3:8b', status: 'success' }, 2);
|
|
m.requestsTotal.inc({ team: 'bravo', backend: 'gpu-b', model: 'qwen3:8b', status: 'success' }, 1);
|
|
const out = await reg.metrics();
|
|
// 3 + 2 = 5 on the alpha series
|
|
expect(out).toMatch(/aao_gateway_c_requests_total\{[^}]*team="alpha"[^}]*\} 5/);
|
|
expect(out).toMatch(/aao_gateway_c_requests_total\{[^}]*team="bravo"[^}]*\} 1/);
|
|
});
|
|
|
|
it('histogram observe records a single sample', async () => {
|
|
const m = createGatewayMetrics(reg, 'aao_gateway_h');
|
|
m.requestDurationSeconds.observe(
|
|
{ team: 'alpha', backend: 'gpu-a', model: 'qwen3:8b', status: 'success' },
|
|
0.42,
|
|
);
|
|
const out = await reg.metrics();
|
|
expect(out).toContain('aao_gateway_h_request_duration_seconds_count{');
|
|
expect(out).toMatch(/aao_gateway_h_request_duration_seconds_sum\{[^}]+\} 0\.42/);
|
|
});
|
|
|
|
it('gauge set + reset returns latest value', async () => {
|
|
const m = createGatewayMetrics(reg, 'aao_gateway_g');
|
|
m.backendBusySlots.set({ backend: 'gpu-a' }, 4);
|
|
m.backendBusySlots.set({ backend: 'gpu-a' }, 7);
|
|
const out = await reg.metrics();
|
|
expect(out).toMatch(/aao_gateway_g_backend_busy_slots\{backend="gpu-a"\} 7/);
|
|
});
|
|
|
|
it('activeStreams gauge has no labels', async () => {
|
|
const m = createGatewayMetrics(reg, 'aao_gateway_s');
|
|
m.activeStreams.set(5);
|
|
const out = await reg.metrics();
|
|
expect(out).toContain('aao_gateway_s_active_streams 5');
|
|
});
|
|
|
|
it('resetMetrics zeroes counters without unregistering', async () => {
|
|
const m = createGatewayMetrics(reg, 'aao_gateway_r');
|
|
m.cacheHitTotal.inc({ cache: 'key' }, 10);
|
|
reg.resetMetrics();
|
|
const out = await reg.metrics();
|
|
expect(out).toContain('aao_gateway_r_cache_hit_total');
|
|
// No data line after reset (counter not yet observed post-reset)
|
|
expect(out).not.toMatch(/aao_gateway_r_cache_hit_total\{cache="key"\} 10/);
|
|
});
|
|
|
|
it('is idempotent on (registry, prefix) — returns the cached handle, does NOT throw', () => {
|
|
// CRITICAL-1 fix: prom-client throws on duplicate metric name
|
|
// registration, but createGatewayMetrics is memoized so callers
|
|
// (e.g. same-process gateway bounces with a shared bridge
|
|
// Registry) can safely call it again. The cache is per (registry,
|
|
// prefix) so a different prefix on the same registry still
|
|
// registers fresh counters.
|
|
const first = createGatewayMetrics(reg, 'aao_gateway_dup');
|
|
expect(() => createGatewayMetrics(reg, 'aao_gateway_dup')).not.toThrow();
|
|
const second = createGatewayMetrics(reg, 'aao_gateway_dup');
|
|
expect(second).toBe(first); // same handle, no fresh Counter instances
|
|
// Different prefix → fresh registration, no cache hit
|
|
const other = createGatewayMetrics(reg, 'aao_gateway_other');
|
|
expect(other).not.toBe(first);
|
|
});
|
|
});
|