Files
2026-06-21 03:53:06 +03:00

313 lines
9.7 KiB
TypeScript

import type { AssignedVM } from "../vm/base.ts";
import { postgres } from "./postgres.ts";
import os from "node:os";
import { getRedisCountDay, getRedisCountDayDistinct, redis } from "./redis.ts";
import config from "../config.ts";
import { apps } from "../ecosystem.config.js";
export async function getStats() {
const now = Date.now();
let currentUsers = 0;
// Render each shard metrics as its own object
const shardMetrics: Record<string, ShardMetric> = {};
const shardKeys = new Set(
apps.map((app) => `shardMetrics:${app.env?.SHARD ?? 0}`),
);
for (let key of shardKeys) {
const resp2 = await redis?.get(key);
if (resp2) {
shardMetrics[key] = JSON.parse(resp2);
currentUsers += shardMetrics[key].users;
}
}
// Count these from postgres data
let currentHttp = 0;
let currentVBrowser = 0;
let currentVBrowserLarge = 0;
let currentScreenShare = 0;
let currentFileShare = 0;
const currentRoomSizes: Record<string, number> = {};
const result = await postgres?.query<{
roomId: string;
creationTime: Date;
lastUpdateTime: Date;
vanity: string;
isSubRoom: boolean;
roomTitle: string;
roomDescription: string;
mediaPath: string;
owner: string;
password: string;
video: string;
videoTS: number;
vBrowser: AssignedVM;
creator: string;
lock: string;
}>(
`SELECT "roomId", "creationTime", "lastUpdateTime", vanity, "isSubRoom", "roomTitle", "roomDescription", "mediaPath", owner, password,
data->'video' as video, data->'videoTS' as "videoTS", data->'vBrowser' as "vBrowser", data->'creator' as creator, data->'lock' as lock
FROM room
WHERE "lastUpdateTime" > NOW() - INTERVAL '7 day'
AND length(data->>'video') > 0
ORDER BY "creationTime" DESC`,
);
const currentRoomData = await Promise.all(
(result?.rows ?? []).map(async (dbRoom) => {
const vBrowser = dbRoom.vBrowser;
if (vBrowser) {
currentVBrowser += 1;
}
if (vBrowser?.large) {
currentVBrowserLarge += 1;
}
const rosterLength = Number(
await redis?.get(`roomCounts:${dbRoom.roomId}`),
);
let roster = [];
if (rosterLength) {
currentRoomSizes[rosterLength] =
(currentRoomSizes[rosterLength] ?? 0) + 1;
const resp = await redis?.get(`roomRosters:${dbRoom.roomId}`);
if (resp) {
roster = JSON.parse(resp);
}
}
const obj = {
roomId: dbRoom.roomId,
video: dbRoom.video || undefined,
videoTS: dbRoom.videoTS || undefined,
creationTime: dbRoom.creationTime || undefined,
lastUpdateTime: dbRoom.lastUpdateTime || undefined,
vanity: dbRoom.vanity || undefined,
isSubRoom: dbRoom.isSubRoom || undefined,
owner: dbRoom.owner || undefined,
password: dbRoom.password || undefined,
roomTitle: dbRoom.roomTitle || undefined,
roomDescription: dbRoom.roomDescription || undefined,
mediaPath: dbRoom.mediaPath || undefined,
vBrowser,
vBrowserElapsed: vBrowser?.assignTime && now - vBrowser?.assignTime,
lock: dbRoom.lock || undefined,
creator: dbRoom.creator || undefined,
rosterLength,
roster,
};
if (obj.video?.startsWith("http") && rosterLength) {
currentHttp += 1;
}
if (obj.video?.startsWith("screenshare://") && rosterLength) {
currentScreenShare += 1;
}
if (obj.video?.startsWith("fileshare://") && rosterLength) {
currentFileShare += 1;
}
return obj;
}),
);
// Singleton stats below (same for all shards)
const currentVideoChat = 0;
const cpuUsage = os.loadavg()[1] * 100;
const redisUsage = Number(
(await redis?.info())
?.split("\n")
.find((line) => line.startsWith("used_memory:"))
?.split(":")[1]
.trim(),
);
const postgresUsage = Number(
(await postgres?.query(`SELECT pg_database_size('postgres');`))?.rows[0]
.pg_database_size,
);
const numPermaRooms = Number(
(await postgres?.query("SELECT count(1) from room WHERE owner IS NOT NULL"))
?.rows[0].count,
);
const numAllRooms = Number(
(await postgres?.query("SELECT count(1) from room"))?.rows[0].count,
);
const numSubs = Number(
(await postgres?.query("SELECT count(1) from subscriber"))?.rows[0].count,
);
const discordBotWatch = await getRedisCountDay("discordBotWatch");
const createRoomErrors = await getRedisCountDay("createRoomError");
const deleteAccounts = await getRedisCountDay("deleteAccount");
const chatMessages = await getRedisCountDay("chatMessages");
const addReactions = await getRedisCountDay("addReaction");
const hetznerApiRemaining = Number(await redis?.get("hetznerApiRemaining"));
const vBrowserStarts = await getRedisCountDay("vBrowserStarts");
const vBrowserLaunches = await getRedisCountDay("vBrowserLaunches");
const vBrowserFails = await getRedisCountDay("vBrowserFails");
const vBrowserStagingFails = await getRedisCountDay("vBrowserStagingFails");
const vBrowserReimages = await getRedisCountDay("vBrowserReimage");
const vBrowserCleanups = await getRedisCountDay("vBrowserCleanup");
const vBrowserStopTimeout = await getRedisCountDay(
"vBrowserTerminateTimeout",
);
const vBrowserStopEmpty = await getRedisCountDay("vBrowserTerminateEmpty");
const vBrowserStopManual = await getRedisCountDay("vBrowserTerminateManual");
const vBrowserStartMS = await redis?.lrange("vBrowserStartMS", 0, -1);
const vBrowserStageRetries = await redis?.lrange(
"vBrowserStageRetries",
0,
-1,
);
const vBrowserStageFails = await redis?.lrange("vBrowserStageFails", 0, -1);
const vBrowserSessionMS = await redis?.lrange("vBrowserSessionMS", 0, -1);
// const vBrowserVMLifetime = await redis?.lrange('vBrowserVMLifetime', 0, -1);
const proxyReqs = await getRedisCountDay("proxyReqs");
const urlStarts = await getRedisCountDay("urlStarts");
const streamStarts = await getRedisCountDay("streamStarts");
const convertStarts = await getRedisCountDay("convertStarts");
const playlistAdds = await getRedisCountDay("playlistAdds");
const screenShareStarts = await getRedisCountDay("screenShareStarts");
const fileShareStarts = await getRedisCountDay("fileShareStarts");
const mediasoupStarts = await getRedisCountDay("mediasoupStarts");
const videoChatStarts = await getRedisCountDay("videoChatStarts");
const connectStarts = await getRedisCountDay("connectStarts");
const connectStartsDistinct = await getRedisCountDayDistinct(
"connectStartsDistinct",
);
const subUploads = await getRedisCountDay("subUploads");
const subDownloadsOS = await getRedisCountDay("subDownloadsOS");
const subSearchesOS = await getRedisCountDay("subSearchesOS");
const youtubeSearch = await getRedisCountDay("youtubeSearch");
const vBrowserClientIDsCard = await redis?.zcard("vBrowserClientIDs");
const vBrowserUIDsCard = await redis?.zcard("vBrowserUIDs");
const createRoomPreloads = await getRedisCountDay("createRoomPreload");
const vBrowserClientIDs = altArrayToObject(
await redis?.zrevrangebyscore(
"vBrowserClientIDs",
"+inf",
"0",
"WITHSCORES",
"LIMIT",
0,
20,
),
);
const vBrowserUIDs = altArrayToObject(
await redis?.zrevrangebyscore(
"vBrowserUIDs",
"+inf",
"0",
"WITHSCORES",
"LIMIT",
0,
20,
),
);
const vBrowserClientIDMinutes = altArrayToObject(
await redis?.zrevrangebyscore(
"vBrowserClientIDMinutes",
"+inf",
"0",
"WITHSCORES",
"LIMIT",
0,
20,
),
);
const vBrowserUIDMinutes = altArrayToObject(
await redis?.zrevrangebyscore(
"vBrowserUIDMinutes",
"+inf",
"0",
"WITHSCORES",
"LIMIT",
0,
20,
),
);
// Fetch VM stats from vmworker
const resp = await fetch(
"http://localhost:" + config.VMWORKER_PORT + "/stats",
);
const vmManagerStats = await resp.json();
return {
...shardMetrics,
currentRoomSizes,
counts: {
currentUsers,
currentVideoChat,
currentVBrowser,
currentVBrowserLarge,
currentHttp,
currentScreenShare,
currentFileShare,
cpuUsage,
redisUsage,
postgresUsage,
numPermaRooms,
numAllRooms,
numSubs,
discordBotWatch,
createRoomErrors,
createRoomPreloads,
deleteAccounts,
chatMessages,
addReactions,
proxyReqs,
urlStarts,
streamStarts,
convertStarts,
playlistAdds,
screenShareStarts,
fileShareStarts,
mediasoupStarts,
subUploads,
subDownloadsOS,
subSearchesOS,
youtubeSearch,
videoChatStarts,
connectStarts,
connectStartsDistinct,
hetznerApiRemaining,
vBrowserStarts,
vBrowserLaunches,
vBrowserFails,
vBrowserStagingFails,
vBrowserReimages,
vBrowserCleanups,
vBrowserStopManual,
vBrowserStopEmpty,
vBrowserStopTimeout,
vBrowserClientIDsCard,
vBrowserUIDsCard,
},
// Stats object from vmWorker (render as JSON)
vmManagerStats,
// Array of room data (render as JSON)
currentRoomData,
// Arrays of last values (render as one column table)
vBrowserStartMS,
vBrowserStageRetries,
vBrowserStageFails,
vBrowserSessionMS,
// Maps of vbrowser users
vBrowserClientIDs,
vBrowserClientIDMinutes,
vBrowserUIDs,
vBrowserUIDMinutes,
};
}
function altArrayToObject(arr: string[] | undefined) {
const result: Record<string, number> = {};
if (!arr) {
return result;
}
for (let i = 0; i < arr.length; i += 2) {
const k = arr[i];
const v = arr[i + 1];
result[k] = Number(v);
}
return result;
}