From e27cc8a12babcbe8f64b072403b5dfb9a14cdb08 Mon Sep 17 00:00:00 2001 From: Owaise Date: Tue, 12 Aug 2025 23:48:47 +0530 Subject: [PATCH] Added delta instead of avg, and formatted the code to re-use already existing code. --- .../Components/Charts/Utils/chartUtils.jsx | 18 +-- .../Components/NetworkStats/NetworkCharts.jsx | 35 +++-- .../NetworkStats/NetworkStatBoxes.jsx | 19 +-- .../Details/Components/NetworkStats/index.jsx | 31 +++-- .../Details/Hooks/useHardwareUtils.jsx | 25 +++- server/src/db/mongo/modules/monitorModule.js | 11 ++ .../db/mongo/modules/monitorModuleQueries.js | 122 +++++++++++++++++- 7 files changed, 201 insertions(+), 60 deletions(-) diff --git a/client/src/Components/Charts/Utils/chartUtils.jsx b/client/src/Components/Charts/Utils/chartUtils.jsx index 40eb5b539..a7db3ea93 100644 --- a/client/src/Components/Charts/Utils/chartUtils.jsx +++ b/client/src/Components/Charts/Utils/chartUtils.jsx @@ -101,16 +101,17 @@ const getFormattedPercentage = (value) => { * @param {number} props.index - The index of the tick. * @returns {JSX.Element|null} The rendered tick component or null for the first tick. */ -export const NetworkTick = ({ x, y, payload, index }) => { +export const NetworkTick = ({ x, y, payload, index, formatter}) => { const theme = useTheme(); if (index === 0) return null; - const formatBytes = (bytes) => { - if (bytes >= 1_000_000_000) return `${(bytes / 1_000_000_000).toFixed(1)} GB/s`; - if (bytes >= 1_000_000) return `${(bytes / 1_000_000).toFixed(1)} MB/s`; - if (bytes >= 1_000) return `${(bytes / 1_000).toFixed(1)} KB/s`; - return `${bytes} B/s`; - }; + if (formatter === undefined) { + formatter = (value, space=false) => { + if (typeof value !== "number") return value; + // need to add space between value and unit + return `${(value / 1024).toFixed(2)}${space ? " " : ""}Kbps`; + }; + } return ( { fontSize={11} fontWeight={400} > - {formatBytes(payload?.value)} + {formatter(payload?.value, true)} ); }; @@ -131,6 +132,7 @@ NetworkTick.propTypes = { y: PropTypes.number, payload: PropTypes.object, index: PropTypes.number, + formatter: PropTypes.func, }; /** diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx index 5dd9feaad..84325c29e 100644 --- a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx +++ b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx @@ -1,9 +1,7 @@ -// NetworkCharts.jsx import PropTypes from "prop-types"; -import { Stack } from "@mui/material"; +import { Stack, Typography } from "@mui/material"; import InfraAreaChart from "../../../../../Pages/Infrastructure/Details/Components/AreaChartBoxes/InfraAreaChart"; -// Utils import { TzTick, InfrastructureTooltip, @@ -11,43 +9,42 @@ import { } from "../../../../../Components/Charts/Utils/chartUtils"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; +import { useHardwareUtils } from "../../Hooks/useHardwareUtils"; -const getFormattedNetworkMetric = (value) => { - if (typeof value !== "number" || isNaN(value)) return "0"; - if (value >= 1024 ** 3) return `${(value / 1024 ** 3).toFixed(1)} GB/s`; - if (value >= 1024 ** 2) return `${(value / 1024 ** 2).toFixed(1)} MB/s`; - if (value >= 1024) return `${(value / 1024).toFixed(1)} KB/s`; - return `${Math.round(value)} B/s`; -}; - -const NetworkCharts = ({ eth0Data, dateRange }) => { +const NetworkCharts = ({ ethernetData, dateRange }) => { const theme = useTheme(); const { t } = useTranslation(); + const {formatBytesString} = useHardwareUtils(); + + if (!ethernetData?.length) { + return {t("noNetworkStatsAvailable")}; + } + const configs = [ { type: "network-bytes", - data: eth0Data, + data: ethernetData, dataKeys: ["bytesPerSec"], heading: t("bytesPerSecond"), strokeColor: theme.palette.info.main, gradientStartColor: theme.palette.info.main, yLabel: t("bytesPerSecond"), xTick: , - yTick: , + yTick: , toolTip: ( ), }, { type: "network-packets", - data: eth0Data, + data: ethernetData, dataKeys: ["packetsPerSec"], heading: t("packetsPerSecond"), strokeColor: theme.palette.success.main, @@ -66,7 +63,7 @@ const NetworkCharts = ({ eth0Data, dateRange }) => { }, { type: "network-errors", - data: eth0Data, + data: ethernetData, dataKeys: ["errors"], heading: t("errors"), strokeColor: theme.palette.error.main, @@ -85,7 +82,7 @@ const NetworkCharts = ({ eth0Data, dateRange }) => { }, { type: "network-drops", - data: eth0Data, + data: ethernetData, dataKeys: ["drops"], heading: t("drops"), strokeColor: theme.palette.warning.main, @@ -127,7 +124,7 @@ const NetworkCharts = ({ eth0Data, dateRange }) => { }; NetworkCharts.propTypes = { - eth0Data: PropTypes.array.isRequired, + ethernetData: PropTypes.array.isRequired, dateRange: PropTypes.string.isRequired, }; diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx index 3125e86a9..98ba90dae 100644 --- a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx +++ b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx @@ -1,27 +1,19 @@ -// NetworkStatBoxes.jsx import PropTypes from "prop-types"; import StatusBoxes from "../../../../../Components/StatusBoxes"; import StatBox from "../../../../../Components/StatBox"; import { Typography } from "@mui/material"; import { useTranslation } from "react-i18next"; +import { useHardwareUtils } from "../../Hooks/useHardwareUtils"; -function formatBytes(bytes) { - if (bytes === 0 || bytes == null) return "0 B"; - const k = 1024; - const sizes = ["B", "KB", "MB", "GB", "TB"]; - const i = Math.floor(Math.log(bytes) / Math.log(k)); - return `${(bytes / Math.pow(k, i)).toFixed(2)} ${sizes[i]}`; -} - -// Format numbers with commas function formatNumber(num) { return num != null ? num.toLocaleString() : "0"; } -const NetworkStatBoxes = ({ shouldRender, net }) => { +const NetworkStatBoxes = ({ shouldRender, net, ifaceName }) => { const { t } = useTranslation(); - const filtered = - net?.filter((iface) => iface.name === "en0" || iface.name === "wlan0") || []; + const { formatBytes } = useHardwareUtils(); + + const filtered = net?.filter((iface) => iface.name === ifaceName) || []; if (!net?.length) { return {t("noNetworkStatsAvailable")}; @@ -83,6 +75,7 @@ NetworkStatBoxes.propTypes = { err_out: PropTypes.number, }) ), + ifaceName: PropTypes.string.isRequired, }; export default NetworkStatBoxes; diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx index 22a6c3112..623720aba 100644 --- a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx +++ b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx @@ -3,37 +3,40 @@ import NetworkStatBoxes from "./NetworkStatBoxes"; import NetworkCharts from "./NetworkCharts"; import MonitorTimeFrameHeader from "../../../../../Components/MonitorTimeFrameHeader"; -const getNetworkInterfaceData = (checks) => { +const getInterfaceName = (net) => { const interfaceNames = ["eth0", "Ethernet", "en0"]; + const found = (net || []).find((iface) => interfaceNames.includes(iface.name)); + return found ? found.name : null; +}; +const getNetworkInterfaceData = (checks, ifaceName) => { return (checks || []) .map((check) => { - const networkInterface = (check.net || []).find((iface) => - interfaceNames.includes(iface.name) + const networkInterface = (check.net || []).find( + (iface) => iface.name === ifaceName ); - - if (!networkInterface) { - return null; - } - + if (!networkInterface) return null; return { _id: check._id, - bytesPerSec: networkInterface.avgBytesRecv, - packetsPerSec: networkInterface.avgPacketsRecv, - errors: networkInterface.avgErrOut ?? 0, - drops: networkInterface.avgDropOut ?? 0, + bytesPerSec: networkInterface.deltaBytesRecv, + packetsPerSec: networkInterface.deltaPacketsRecv, + errors: networkInterface.deltaErrOut ?? 0, + drops: networkInterface.deltaDropOut ?? 0, }; }) .filter(Boolean); }; const Network = ({ net, checks, isLoading, dateRange, setDateRange }) => { - const eth0Data = getNetworkInterfaceData(checks); + const ifaceName = getInterfaceName(net); + const ethernetData = getNetworkInterfaceData(checks, ifaceName); + return ( <> { setDateRange={setDateRange} /> diff --git a/client/src/Pages/Infrastructure/Details/Hooks/useHardwareUtils.jsx b/client/src/Pages/Infrastructure/Details/Hooks/useHardwareUtils.jsx index a2e4c5153..930ca7ba3 100644 --- a/client/src/Pages/Infrastructure/Details/Hooks/useHardwareUtils.jsx +++ b/client/src/Pages/Infrastructure/Details/Hooks/useHardwareUtils.jsx @@ -57,7 +57,7 @@ const useHardwareUtils = () => { if (GB >= 1) { return ( <> - {Number(GB.toFixed(0))} + {Number(GB.toFixed(2))} {space ? " " : ""} {t("gb")} @@ -65,7 +65,7 @@ const useHardwareUtils = () => { } else { return ( <> - {Number(MB.toFixed(0))} + {Number(MB.toFixed(2))} {space ? " " : ""} {t("mb")} @@ -73,6 +73,26 @@ const useHardwareUtils = () => { } }; + const formatBytesString = (bytes, space = false) => { + if ( + bytes === undefined || + bytes === null || + typeof bytes !== "number" || + bytes === 0 + ) { + return `0${space ? " " : ""}${t("gb")}`; + } + + const GB = bytes / (1024 * 1024 * 1024); + const MB = bytes / (1024 * 1024); + + if (GB >= 1) { + return `${Number(GB.toFixed(2))}${space ? " " : ""}${t("gb")}`; + } else { + return `${Number(MB.toFixed(2))}${space ? " " : ""}${t("mb")}`; + } + }; + /** * Converts a decimal value to a percentage * @@ -134,6 +154,7 @@ const useHardwareUtils = () => { decimalToPercentage, buildTemps, getDimensions, + formatBytesString, }; }; diff --git a/server/src/db/mongo/modules/monitorModule.js b/server/src/db/mongo/modules/monitorModule.js index 0e301cdcf..3afe664f6 100755 --- a/server/src/db/mongo/modules/monitorModule.js +++ b/server/src/db/mongo/modules/monitorModule.js @@ -343,6 +343,17 @@ class MonitorModule { const stats = hardwareStats[0]; + if (stats?.net?.length) { + const elapsedSeconds = (new Date(dates.end).getTime() - new Date(dates.start).getTime()) / 1000; + stats.net = stats.net.map((iface) => ({ + ...iface, + bytesSentPerSec: iface.deltaBytesSent / elapsedSeconds, + bytesRecvPerSec: iface.deltaBytesRecv / elapsedSeconds, + packetsSentPerSec: iface.deltaPacketsSent / elapsedSeconds, + packetsRecvPerSec: iface.deltaPacketsRecv / elapsedSeconds, + })); + } + return { ...monitor.toObject(), stats, diff --git a/server/src/db/mongo/modules/monitorModuleQueries.js b/server/src/db/mongo/modules/monitorModuleQueries.js index c6812b1bc..91fb83444 100755 --- a/server/src/db/mongo/modules/monitorModuleQueries.js +++ b/server/src/db/mongo/modules/monitorModuleQueries.js @@ -395,7 +395,7 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { "$$netIndex", ], }, - avgBytesSent: { + deltaBytesSent: { $subtract: [ { $arrayElemAt: [ @@ -417,7 +417,7 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { }, ], }, - avgBytesRecv: { + deltaBytesRecv: { $subtract: [ { $arrayElemAt: [ @@ -439,7 +439,7 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { }, ], }, - avgPacketsSent: { + deltaPacketsSent: { $subtract: [ { $arrayElemAt: [ @@ -461,7 +461,7 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { }, ], }, - avgPacketsRecv: { + deltaPacketsRecv: { $subtract: [ { $arrayElemAt: [ @@ -483,6 +483,120 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { }, ], }, + deltaErrIn: { + $subtract: [ + { + $arrayElemAt: [ + { + $map: { + input: { $arrayElemAt: ["$net", { $subtract: [{ $size: "$net" }, 1] }] }, + as: "iface", + in: "$$iface.err_in", + }, + }, + "$$netIndex", + ], + }, + { + $arrayElemAt: [{ $map: { input: { $arrayElemAt: ["$net", 0] }, as: "iface", in: "$$iface.err_in" } }, "$$netIndex"], + }, + ], + }, + deltaErrOut: { + $subtract: [ + { + $arrayElemAt: [ + { + $map: { + input: { $arrayElemAt: ["$net", { $subtract: [{ $size: "$net" }, 1] }] }, + as: "iface", + in: "$$iface.err_out", + }, + }, + "$$netIndex", + ], + }, + { + $arrayElemAt: [{ $map: { input: { $arrayElemAt: ["$net", 0] }, as: "iface", in: "$$iface.err_out" } }, "$$netIndex"], + }, + ], + }, + deltaDropIn: { + $subtract: [ + { + $arrayElemAt: [ + { + $map: { + input: { $arrayElemAt: ["$net", { $subtract: [{ $size: "$net" }, 1] }] }, + as: "iface", + in: "$$iface.drop_in", + }, + }, + "$$netIndex", + ], + }, + { + $arrayElemAt: [{ $map: { input: { $arrayElemAt: ["$net", 0] }, as: "iface", in: "$$iface.drop_in" } }, "$$netIndex"], + }, + ], + }, + deltaDropOut: { + $subtract: [ + { + $arrayElemAt: [ + { + $map: { + input: { $arrayElemAt: ["$net", { $subtract: [{ $size: "$net" }, 1] }] }, + as: "iface", + in: "$$iface.drop_out", + }, + }, + "$$netIndex", + ], + }, + { + $arrayElemAt: [{ $map: { input: { $arrayElemAt: ["$net", 0] }, as: "iface", in: "$$iface.drop_out" } }, "$$netIndex"], + }, + ], + }, + deltaFifoIn: { + $subtract: [ + { + $arrayElemAt: [ + { + $map: { + input: { $arrayElemAt: ["$net", { $subtract: [{ $size: "$net" }, 1] }] }, + as: "iface", + in: "$$iface.fifo_in", + }, + }, + "$$netIndex", + ], + }, + { + $arrayElemAt: [{ $map: { input: { $arrayElemAt: ["$net", 0] }, as: "iface", in: "$$iface.fifo_in" } }, "$$netIndex"], + }, + ], + }, + deltaFifoOut: { + $subtract: [ + { + $arrayElemAt: [ + { + $map: { + input: { $arrayElemAt: ["$net", { $subtract: [{ $size: "$net" }, 1] }] }, + as: "iface", + in: "$$iface.fifo_out", + }, + }, + "$$netIndex", + ], + }, + { + $arrayElemAt: [{ $map: { input: { $arrayElemAt: ["$net", 0] }, as: "iface", in: "$$iface.fifo_out" } }, "$$netIndex"], + }, + ], + }, }, }, },