import { collectDefaultMetrics, register, Gauge, Counter } from "prom-client"; import config from "./config/index.js"; import { queues } from "./queue/queues.js"; import cluster from "node:cluster"; import Xev from "xev"; const xev = new Xev(); if (config.metrics?.enable) { if (cluster.isPrimary) { collectDefaultMetrics(); new Gauge({ name: "iceshrimp_queue_jobs", help: "Amount of jobs in the bull queues", labelNames: [ "queue", "status" ], async collect () { for (const queue of queues){ const counts = await queue.getJobCounts(); this.set({ queue: queue.name, status: "completed" }, counts.completed); this.set({ queue: queue.name, status: "waiting" }, counts.waiting); this.set({ queue: queue.name, status: "active" }, counts.active); this.set({ queue: queue.name, status: "delayed" }, counts.delayed); this.set({ queue: queue.name, status: "failed" }, counts.failed); } } }); } } if (cluster.isPrimary) { xev.on("registry-request", async ()=>{ try { const metrics = await register.metrics(); xev.emit("registry-response", { contentType: register.contentType, body: metrics }); } catch (error) { xev.emit("registry-response", { error }); } }); } export const handleMetrics = async (ctx)=>{ if (config.metrics?.token !== undefined) { if (ctx.query.token === undefined) { ctx.res.statusCode = 401; ctx.body = "Missing token parameter"; return; } const correct = config.metrics.token === ctx.query.token; if (!correct) { ctx.res.statusCode = 403; ctx.body = "Incorrect token"; return; } } try { if (cluster.isPrimary) { ctx.set("content-type", register.contentType); ctx.body = await register.metrics(); } else { const wait = new Promise((resolve, reject)=>{ const timeout = setTimeout(()=>reject("Timeout while waiting for cluster master"), 1000 * 60); xev.once("registry-response", (response)=>{ clearTimeout(timeout); if (response.error) reject(response.error); ctx.set("content-type", response.contentType); ctx.body = response.body; resolve(); }); }); xev.emit("registry-request"); await wait; } } catch (err) { ctx.res.statusCode = 500; ctx.body = err; } }; const counter = (configuration)=>{ if (config.metrics?.enable) { if (cluster.isPrimary) { const counter = new Counter(configuration); counter.reset(); // initialize internal hashmap xev.on(`metrics-counter-${configuration.name}`, ()=>counter.inc()); return ()=>counter.inc(); } else { return ()=>xev.emit(`metrics-counter-${configuration.name}`); } } else { return ()=>{}; } }; export const tickOutbox = counter({ name: "iceshrimp_outbox_total", help: "Total AP outbox calls" }); export const tickInbox = counter({ name: "iceshrimp_inbox_total", help: "Total AP inbox calls" }); export const tickFetch = counter({ name: "iceshrimp_fetch_total", help: "Total AP fetch calls" }); export const tickResolve = counter({ name: "iceshrimp_resolve_total", help: "Total AP resolve calls" }); export const tickBiteIncoming = counter({ name: "iceshrimp_bite_remote_incoming_total", help: "Total bites received from remote" }); export const tickBiteOutgoing = counter({ name: "iceshrimp_bite_remote_outgoing_total", help: "Total bites sent to remote" }); export const tickBiteLocal = counter({ name: "iceshrimp_bite_local_total", help: "Total local bites" });