update instancejson more

MY HEAD HURTS NOW ;(

.

.

more work

Finish rewrite

all finished
This commit is contained in:
MathMan05 2024-09-16 19:11:05 -05:00 committed by Scott Gould
parent 3a61417de2
commit 758bd7cc7a
No known key found for this signature in database
113 changed files with 12520 additions and 20427 deletions

247
src/stats.ts Normal file
View file

@ -0,0 +1,247 @@
import fs from "node:fs";
import path from "path";
import fetch from "node-fetch";
import { getApiUrls } from "./utils.js";
interface UptimeEntry {
time: number;
online: boolean;
}
interface UptimeObject {
[key: string]: UptimeEntry[];
}
interface Instance {
name: string;
urls?: { api: string };
url?: string;
online?: boolean;
uptime?: {
daytime: number;
weektime: number;
alltime: number;
};
}
let uptimeObject: UptimeObject = loadUptimeObject();
export { uptimeObject as uptime };
function loadUptimeObject(): UptimeObject {
const filePath = path.join(__dirname, "..", "uptime.json");
if (fs.existsSync(filePath)) {
try {
return JSON.parse(fs.readFileSync(filePath, "utf8"));
} catch (error) {
console.error("Error reading uptime.json:", error);
return {};
}
}
return {};
}
function saveUptimeObject(): void {
fs.writeFile(
`${__dirname}/uptime.json`,
JSON.stringify(uptimeObject),
(error) => {
if (error) {
console.error("Error saving uptime.json:", error);
}
}
);
}
function removeUndefinedKey(): void {
if (uptimeObject.undefined) {
delete uptimeObject.undefined;
saveUptimeObject();
}
}
removeUndefinedKey();
export async function observe(instances: Instance[]): Promise<void> {
const activeInstances = new Set<string>();
const instancePromises = instances.map((instance) =>
resolveInstance(instance, activeInstances)
);
await Promise.allSettled(instancePromises);
updateInactiveInstances(activeInstances);
}
async function resolveInstance(
instance: Instance,
activeInstances: Set<string>
): Promise<void> {
try {
calcStats(instance);
const api = await getApiUrl(instance);
if (!api) {
handleUnresolvedApi(instance);
return;
}
activeInstances.add(instance.name);
scheduleHealthCheck(instance, api);
} catch (error) {
console.error("Error resolving instance:", error);
}
}
async function getApiUrl(instance: Instance): Promise<string | null> {
if (instance.urls) {
return instance.urls.api;
}
if (instance.url) {
const urls = await getApiUrls(instance.url);
return urls ? urls.api : null;
}
return null;
}
function handleUnresolvedApi(instance: Instance): void {
setStatus(instance, false);
console.warn(`${instance.name} does not resolve api URL`, instance);
setTimeout(() => resolveInstance(instance, new Set()), 1000 * 60 * 30);
}
function scheduleHealthCheck(instance: Instance, api: string): void {
const checkInterval = 1000 * 60 * 30;
const initialDelay = Math.random() * 1000 * 60 * 10;
setTimeout(() => {
checkHealth(instance, api);
setInterval(() => checkHealth(instance, api), checkInterval);
}, initialDelay);
}
async function checkHealth(
instance: Instance,
api: string,
tries = 0
): Promise<void> {
try {
const response = await fetch(`${api}ping`, { method: "HEAD" });
if (response.ok || tries > 3) {
setStatus(instance, response.ok);
} else {
retryHealthCheck(instance, api, tries);
}
} catch (error) {
console.error("Error checking health:", error);
if (tries > 3) {
setStatus(instance, false);
} else {
retryHealthCheck(instance, api, tries);
}
}
}
function retryHealthCheck(
instance: Instance,
api: string,
tries: number
): void {
setTimeout(() => checkHealth(instance, api, tries + 1), 30000);
}
function updateInactiveInstances(activeInstances: Set<string>): void {
for (const key of Object.keys(uptimeObject)) {
if (!activeInstances.has(key)) {
setStatus(key, false);
}
}
}
function calcStats(instance: Instance): void {
const obj = uptimeObject[instance.name];
if (!obj) return;
const now = Date.now();
const day = now - 1000 * 60 * 60 * 24;
const week = now - 1000 * 60 * 60 * 24 * 7;
let totalTimePassed = 0;
let alltime = 0;
let daytime = 0;
let weektime = 0;
let online = false;
for (let i = 0; i < obj.length; i++) {
const entry = obj[i];
online = entry.online;
const stamp = entry.time;
const nextStamp = obj[i + 1]?.time || now;
const timePassed = nextStamp - stamp;
totalTimePassed += timePassed;
alltime += Number(online) * timePassed;
if (stamp + timePassed > week) {
const weekTimePassed = Math.min(timePassed, nextStamp - week);
weektime += Number(online) * weekTimePassed;
if (stamp + timePassed > day) {
const dayTimePassed = Math.min(weekTimePassed, nextStamp - day);
daytime += Number(online) * dayTimePassed;
}
}
}
instance.online = online;
instance.uptime = calculateUptimeStats(
totalTimePassed,
alltime,
daytime,
weektime,
online
);
}
function calculateUptimeStats(
totalTimePassed: number,
alltime: number,
daytime: number,
weektime: number,
online: boolean
): { daytime: number; weektime: number; alltime: number } {
const dayInMs = 1000 * 60 * 60 * 24;
const weekInMs = dayInMs * 7;
alltime /= totalTimePassed;
if (totalTimePassed > dayInMs) {
daytime = daytime || (online ? dayInMs : 0);
daytime /= dayInMs;
if (totalTimePassed > weekInMs) {
weektime = weektime || (online ? weekInMs : 0);
weektime /= weekInMs;
} else {
weektime = alltime;
}
} else {
weektime = alltime;
daytime = alltime;
}
return { daytime, weektime, alltime };
}
function setStatus(instance: string | Instance, status: boolean): void {
const name = typeof instance === "string" ? instance : instance.name;
let obj = uptimeObject[name];
if (!obj) {
obj = [];
uptimeObject[name] = obj;
}
if (obj.at(-1)?.online !== status) {
obj.push({ time: Date.now(), online: status });
saveUptimeObject();
}
if (typeof instance !== "string") {
calcStats(instance);
}
}