diff --git a/src/app/api/metrics/route.ts b/src/app/api/metrics/route.ts new file mode 100644 index 0000000..7e15dc8 --- /dev/null +++ b/src/app/api/metrics/route.ts @@ -0,0 +1,103 @@ +import { NextRequest, NextResponse } from "next/server"; +import testAuth from "../auth"; +import { StatusCodes } from "http-status-codes"; +import si from "systeminformation"; + +function average(values: Array, p: (v: T) => number) { + return values.length > 0 + ? values.reduce((sum, value) => sum + p(value), 0) / values.length + : 0; +} + +type Usage = { + cpu: number; + memory: number; + in: number; + out: number; +}; + +const usageSecs: Usage[] = []; + +const usageMins: (Usage & { time: string })[] = []; + +const usageHours: (Usage & { time: string })[] = new Array(12).fill({ + time: "00:00", + cpu: 0, + memory: 0, + in: 0, + out: 0, +}); + +var hr = 0; + +setInterval(async () => { + const [mem, load, network] = await Promise.all([ + si.mem(), + si.currentLoad(), + si.networkStats(), + ]); + + usageSecs.push({ + cpu: load.currentLoad, + memory: ((mem.total - mem.available) / mem.total) * 100, + in: average(network, (n) => n.rx_bytes), + out: average(network, (n) => n.tx_bytes), + }); + + if (usageSecs.length >= 60) { + const now = new Date(); + const hours = String(now.getHours()).padStart(2, "0"); + const minutes = String(now.getMinutes()).padStart(2, "0"); + const time = `${hours}:${minutes}`; + + usageMins.push({ + time, + cpu: average(usageSecs, (s) => s.cpu), + memory: average(usageSecs, (s) => s.memory), + in: average(usageSecs, (s) => s.in), + out: average(usageSecs, (s) => s.out), + }); + + if (usageMins.length >= 60) { + if (hr >= 12) { + usageHours.shift(); + usageHours.push({ + time, + cpu: average(usageMins, (s) => s.cpu), + memory: average(usageMins, (s) => s.memory), + in: average(usageMins, (s) => s.in), + out: average(usageMins, (s) => s.out), + }); + } else { + usageHours[hr] = { + time, + cpu: average(usageMins, (s) => s.cpu), + memory: average(usageMins, (s) => s.memory), + in: average(usageMins, (s) => s.in), + out: average(usageMins, (s) => s.out), + }; + hr++; + } + + usageMins.splice(0, usageSecs.length); + } + + usageSecs.splice(0, usageSecs.length); + } +}, 1); + +export async function GET(req: NextRequest): Promise { + const auth = await testAuth(req); + + if (auth) { + return NextResponse.json( + { message: auth }, + { status: StatusCodes.UNAUTHORIZED } + ); + } + + return NextResponse.json({ + message: "ok", + metrics: usageHours, + }); +} diff --git a/src/app/monitoring/page.tsx b/src/app/monitoring/page.tsx index d58fcd9..c71284a 100644 --- a/src/app/monitoring/page.tsx +++ b/src/app/monitoring/page.tsx @@ -5,54 +5,8 @@ import { Progress } from "@/components/ui/progress"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { type ChartConfig } from "@/components/ui/chart"; import GradientAreaChart from "@/components/GradientAreaChart"; - -const cpuData = [ - { time: "00:00", cpu: 45 }, - { time: "01:00", cpu: 52 }, - { time: "02:00", cpu: 38 }, - { time: "03:00", cpu: 42 }, - { time: "04:00", cpu: 35 }, - { time: "05:00", cpu: 48 }, - { time: "06:00", cpu: 55 }, - { time: "07:00", cpu: 62 }, - { time: "08:00", cpu: 78 }, - { time: "09:00", cpu: 85 }, - { time: "10:00", cpu: 72 }, - { time: "11:00", cpu: 68 }, - { time: "12:00", cpu: 75 }, -]; - -const memoryData = [ - { time: "00:00", memory: 62 }, - { time: "01:00", memory: 64 }, - { time: "02:00", memory: 58 }, - { time: "03:00", memory: 61 }, - { time: "04:00", memory: 59 }, - { time: "05:00", memory: 63 }, - { time: "06:00", memory: 67 }, - { time: "07:00", memory: 72 }, - { time: "08:00", memory: 78 }, - { time: "09:00", memory: 82 }, - { time: "10:00", memory: 76 }, - { time: "11:00", memory: 74 }, - { time: "12:00", memory: 71 }, -]; - -const networkData = [ - { time: "00:00", in: 12, out: 8 }, - { time: "01:00", in: 15, out: 10 }, - { time: "02:00", in: 8, out: 5 }, - { time: "03:00", in: 10, out: 7 }, - { time: "04:00", in: 6, out: 4 }, - { time: "05:00", in: 14, out: 9 }, - { time: "06:00", in: 22, out: 15 }, - { time: "07:00", in: 35, out: 28 }, - { time: "08:00", in: 48, out: 38 }, - { time: "09:00", in: 52, out: 42 }, - { time: "10:00", in: 45, out: 35 }, - { time: "11:00", in: 42, out: 32 }, - { time: "12:00", in: 38, out: 28 }, -]; +import { getMetrics } from "@/core/client/metrics"; +import { useEffect, useState } from "react"; const containerMetrics = [ { name: "api-gateway-1", cpu: 45, memory: 62, network: "12.4 MB/s" }, @@ -79,6 +33,28 @@ const networkChartConfig = { } satisfies ChartConfig; export default function MonitoringPage() { + const [data, setData] = useState< + { + time: string; + cpu: number; + memory: number; + in: number; + out: number; + }[] + >([]); + + useEffect(() => { + async function fetchMetrics() { + setData(await getMetrics()); + } + + fetchMetrics(); + + const i = setInterval(fetchMetrics, 30000); + + return () => clearInterval(i); + }, []); + return ( @@ -143,7 +119,7 @@ export default function MonitoringPage() {
diff --git a/src/core/client/metrics.ts b/src/core/client/metrics.ts new file mode 100644 index 0000000..afddb07 --- /dev/null +++ b/src/core/client/metrics.ts @@ -0,0 +1,67 @@ +"use server"; + +import { Node } from "@/lib/types"; +import axios from "axios"; +import { getNodes } from "./node"; + +export async function getMetrics(): Promise< + { + time: string; + cpu: number; + memory: number; + in: number; + out: number; + }[] +> { + const nodes = await getNodes(); + const nodeMetrics = await Promise.all(nodes.map(getNodeMetrics)); + + if (nodeMetrics.length === 0) return []; + + const numNodes = nodeMetrics.length; + const numPoints = nodeMetrics[0].length; + + const averaged: (typeof nodeMetrics)[0] = []; + + for (let i = 0; i < numPoints; i++) { + const sum = nodeMetrics.reduce( + (acc, metrics) => { + const m = metrics[i]; + acc.cpu += m.cpu; + acc.memory += m.memory; + acc.in += m.in; + acc.out += m.out; + return acc; + }, + { cpu: 0, memory: 0, in: 0, out: 0 } + ); + + averaged.push({ + time: nodeMetrics[0][i].time, + cpu: sum.cpu / numNodes, + memory: sum.memory / numNodes, + in: sum.in / numNodes, + out: sum.out / numNodes, + }); + } + + return averaged; +} + +export async function getNodeMetrics(node: Node): Promise< + { + time: string; + cpu: number; + memory: number; + in: number; + out: number; + }[] +> { + const data = await ( + await axios.get(`http://${node.ip}:3000/api/metrics`, { + headers: { Authorization: `ApiKey ${node.key}` }, + }) + ).data; + + return data.metrics; +}