in src/lib/server/metrics.ts [41:193]
private constructor() {
const app = express();
const port = Number(config.METRICS_PORT || "5565");
if (isNaN(port) || port < 0 || port > 65535) {
logger.warn(`Invalid value for METRICS_PORT: ${config.METRICS_PORT}`);
}
if (config.METRICS_ENABLED !== "false" && config.METRICS_ENABLED !== "true") {
logger.warn(`Invalid value for METRICS_ENABLED: ${config.METRICS_ENABLED}`);
}
if (config.METRICS_ENABLED === "true") {
const server = app.listen(port, () => {
logger.info(`Metrics server listening on port ${port}`);
});
const closeServer = promisify(server.close);
onExit(async () => {
logger.info("Disconnecting metrics server ...");
await closeServer();
logger.info("Server stopped ...");
});
}
const register = new Registry();
collectDefaultMetrics({ register });
this.metrics = {
model: {
conversationsTotal: new Counter({
name: "model_conversations_total",
help: "Total number of conversations",
labelNames: ["model"],
registers: [register],
}),
messagesTotal: new Counter({
name: "model_messages_total",
help: "Total number of messages",
labelNames: ["model"],
registers: [register],
}),
tokenCountTotal: new Counter({
name: "model_token_count_total",
help: "Total number of tokens",
labelNames: ["model"],
registers: [register],
}),
timePerOutputToken: new Summary({
name: "model_time_per_output_token_ms",
help: "Time per output token in ms",
labelNames: ["model"],
registers: [register],
maxAgeSeconds: 5 * 60,
ageBuckets: 5,
}),
timeToFirstToken: new Summary({
name: "model_time_to_first_token_ms",
help: "Time to first token",
labelNames: ["model"],
registers: [register],
maxAgeSeconds: 5 * 60,
ageBuckets: 5,
}),
latency: new Summary({
name: "model_latency_ms",
help: "Total latency until end of answer",
labelNames: ["model"],
registers: [register],
maxAgeSeconds: 5 * 60,
ageBuckets: 5,
}),
votesPositive: new Counter({
name: "model_votes_positive",
help: "Total number of positive votes on messages generated by the model",
labelNames: ["model"],
registers: [register],
}),
votesNegative: new Counter({
name: "model_votes_negative",
help: "Total number of negative votes on messages generated by the model",
labelNames: ["model"],
registers: [register],
}),
},
webSearch: {
requestCount: new Counter({
name: "web_search_request_count",
help: "Total number of web search requests",
registers: [register],
}),
pageFetchCount: new Counter({
name: "web_search_page_fetch_count",
help: "Total number of web search page fetches",
registers: [register],
}),
pageFetchCountError: new Counter({
name: "web_search_page_fetch_count_error",
help: "Total number of web search page fetch errors",
registers: [register],
}),
pageFetchDuration: new Summary({
name: "web_search_page_fetch_duration_ms",
help: "Web search page fetch duration",
registers: [register],
maxAgeSeconds: 5 * 60,
ageBuckets: 5,
}),
embeddingDuration: new Summary({
name: "web_search_embedding_duration_ms",
help: "Web search embedding duration",
registers: [register],
maxAgeSeconds: 5 * 60,
ageBuckets: 5,
}),
},
tool: {
toolUseCount: new Counter({
name: "tool_use_count",
help: "Total number of tool uses",
labelNames: ["tool"],
registers: [register],
}),
toolUseCountError: new Counter({
name: "tool_use_count_error",
help: "Total number of tool use errors",
labelNames: ["tool"],
registers: [register],
}),
toolUseDuration: new Summary({
name: "tool_use_duration_ms",
help: "Tool use duration",
labelNames: ["tool"],
registers: [register],
maxAgeSeconds: 30 * 60, // longer duration since we use this to give feedback to the user
ageBuckets: 5,
}),
timeToChooseTools: new Summary({
name: "time_to_choose_tools_ms",
help: "Time to choose tools",
labelNames: ["model"],
registers: [register],
maxAgeSeconds: 5 * 60,
ageBuckets: 5,
}),
},
};
app.get("/metrics", (req, res) => {
register.metrics().then((metrics) => {
res.set("Content-Type", "text/plain");
res.send(metrics);
});
});
}