313 lines
9.7 KiB
TypeScript
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;
|
|
}
|