feat: initial public release (MAESTRO)
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user