persist gateway metrics across restarts
This commit is contained in:
+61
-4
@@ -360,6 +360,7 @@ function buildGatewayErrorBody(message) {
|
||||
function createMonitor() {
|
||||
return {
|
||||
started_at: new Date().toISOString(),
|
||||
persistent_since: null,
|
||||
next_log_seq: 1,
|
||||
next_request_seq: 1,
|
||||
log_entries: [],
|
||||
@@ -613,15 +614,70 @@ function buildRequestQueryFilters({ query, filter }) {
|
||||
};
|
||||
}
|
||||
|
||||
async function hydrateMonitorFromDisk(monitor, paths, requestHistoryLimit) {
|
||||
const requestEntries = await readJsonlFile(paths.requestsPath);
|
||||
async function hydrateMonitorFromDisk(monitor, paths, requestHistoryLimit, requestsDb = null) {
|
||||
monitor.request_entries = [];
|
||||
|
||||
if (requestsDb) {
|
||||
const totalsRow = requestsDb.prepare(`
|
||||
SELECT
|
||||
COUNT(*) AS total_proxy_request_count,
|
||||
COALESCE(SUM(CASE WHEN inspected = 1 THEN 1 ELSE 0 END), 0) AS inspected_response_count,
|
||||
COALESCE(SUM(CASE WHEN matched = 1 THEN 1 ELSE 0 END), 0) AS matched_response_count,
|
||||
COALESCE(SUM(COALESCE(input_tokens, 0)), 0) AS input_tokens,
|
||||
COALESCE(SUM(COALESCE(output_tokens, 0)), 0) AS output_tokens,
|
||||
COALESCE(SUM(COALESCE(total_tokens, 0)), 0) AS total_tokens,
|
||||
COALESCE(SUM(COALESCE(reasoning_tokens, 0)), 0) AS reasoning_tokens,
|
||||
COALESCE(SUM(COALESCE(cached_tokens, 0)), 0) AS cached_tokens,
|
||||
MIN(NULLIF(started_at, '')) AS persistent_since,
|
||||
MAX(seq) AS max_seq
|
||||
FROM requests
|
||||
`).get();
|
||||
|
||||
monitor.total_proxy_request_count = totalsRow?.total_proxy_request_count || 0;
|
||||
monitor.inspected_response_count = totalsRow?.inspected_response_count || 0;
|
||||
monitor.matched_response_count = totalsRow?.matched_response_count || 0;
|
||||
monitor.token_totals = {
|
||||
input_tokens: totalsRow?.input_tokens || 0,
|
||||
output_tokens: totalsRow?.output_tokens || 0,
|
||||
total_tokens: totalsRow?.total_tokens || 0,
|
||||
reasoning_tokens: totalsRow?.reasoning_tokens || 0,
|
||||
cached_tokens: totalsRow?.cached_tokens || 0,
|
||||
};
|
||||
monitor.persistent_since = totalsRow?.persistent_since || null;
|
||||
monitor.next_request_seq = Number.isInteger(totalsRow?.max_seq) ? totalsRow.max_seq + 1 : 1;
|
||||
monitor.observed_reasoning_counts = {};
|
||||
|
||||
const reasoningRows = requestsDb.prepare(`
|
||||
SELECT reasoning_tokens, COUNT(*) AS count
|
||||
FROM requests
|
||||
WHERE reasoning_tokens IS NOT NULL
|
||||
GROUP BY reasoning_tokens
|
||||
`).all();
|
||||
for (const row of reasoningRows) {
|
||||
if (!Number.isInteger(row?.reasoning_tokens)) {
|
||||
continue;
|
||||
}
|
||||
monitor.observed_reasoning_counts[`${row.reasoning_tokens}`] = row.count || 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const requestEntries = await readJsonlFile(paths.requestsPath);
|
||||
monitor.next_request_seq = requestEntries.reduce((maxSeq, entry) => {
|
||||
return Math.max(maxSeq, Number.isInteger(entry.seq) ? entry.seq + 1 : maxSeq);
|
||||
}, 1);
|
||||
for (const entry of requestEntries) {
|
||||
monitor.total_proxy_request_count += 1;
|
||||
if (entry.inspected) {
|
||||
recordInspectedResponse(monitor, entry.reasoning_tokens ?? entry.usage?.reasoning_tokens ?? null, Boolean(entry.matched));
|
||||
}
|
||||
addTokenTotals(monitor, entry.usage);
|
||||
}
|
||||
const firstStartedAt = requestEntries
|
||||
.map((entry) => `${entry?.started_at || ""}`.trim())
|
||||
.filter(Boolean)
|
||||
.sort()[0];
|
||||
monitor.persistent_since = firstStartedAt || null;
|
||||
}
|
||||
|
||||
function createMonitorRecorder(monitor) {
|
||||
@@ -980,6 +1036,7 @@ function buildMetricsSnapshot(monitor) {
|
||||
const inspectedResponseCount = monitor.inspected_response_count;
|
||||
return {
|
||||
started_at: monitor.started_at,
|
||||
persistent_since: monitor.persistent_since,
|
||||
total_proxy_request_count: monitor.total_proxy_request_count,
|
||||
inspected_response_count: inspectedResponseCount,
|
||||
matched_response_count: monitor.matched_response_count,
|
||||
@@ -2684,9 +2741,9 @@ async function main() {
|
||||
requestsDb,
|
||||
server: null,
|
||||
};
|
||||
await hydrateMonitorFromDisk(monitor, runtime.paths, config.request_history_limit);
|
||||
const importedCount = await importRequestsJsonlToDb(requestsDb, runtime.paths.requestsPath);
|
||||
logger(`[start] hydrated token totals from jsonl path=${runtime.paths.requestsPath}`);
|
||||
await hydrateMonitorFromDisk(monitor, runtime.paths, config.request_history_limit, requestsDb);
|
||||
logger(`[start] hydrated persistent request metrics from db path=${runtime.paths.requestsDbPath}`);
|
||||
logger(`[start] requests db ready path=${runtime.paths.requestsDbPath} imported_jsonl_rows=${importedCount}`);
|
||||
|
||||
const server = http.createServer(async (req, res) => {
|
||||
|
||||
Reference in New Issue
Block a user