Files
2026-07-26 18:25:37 +09:00

139 lines
4.4 KiB
JavaScript

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"
});