diff --git a/client/src/Components/Charts/Utils/chartUtils.jsx b/client/src/Components/Charts/Utils/chartUtils.jsx
index af5f27c66..e2560aa2e 100644
--- a/client/src/Components/Charts/Utils/chartUtils.jsx
+++ b/client/src/Components/Charts/Utils/chartUtils.jsx
@@ -91,6 +91,49 @@ const getFormattedPercentage = (value) => {
return `${(value * 100).toFixed(2)}%`;
};
+/**
+ * Custom tick component for rendering network bytes per second.
+ *
+ * @param {Object} props - The properties object.
+ * @param {number} props.x - The x-coordinate for the tick.
+ * @param {number} props.y - The y-coordinate for the tick.
+ * @param {Object} props.payload - The payload object containing tick data.
+ * @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 }) => {
+ 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`;
+ };
+
+ return (
+
+ {formatBytes(payload?.value)}
+
+ );
+};
+
+NetworkTick.propTypes = {
+ x: PropTypes.number,
+ y: PropTypes.number,
+ payload: PropTypes.object,
+ index: PropTypes.number,
+};
+
+
/**
* Custom tooltip component for displaying infrastructure data.
*
diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx
index 7aad494ab..74ddb6bae 100644
--- a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx
+++ b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx
@@ -7,6 +7,7 @@ import InfraAreaChart from "../../../../../Pages/Infrastructure/Details/Componen
import {
TzTick,
InfrastructureTooltip,
+ NetworkTick,
} from "../../../../../Components/Charts/Utils/chartUtils";
import { useTheme } from "@emotion/react";
import { useTranslation } from "react-i18next";
@@ -23,6 +24,7 @@ const NetworkCharts = ({ eth0Data, dateRange }) => {
const theme = useTheme();
const { t } = useTranslation();
+
const configs = [
{
type: "network-bytes",
@@ -33,6 +35,7 @@ const NetworkCharts = ({ eth0Data, dateRange }) => {
gradientStartColor: theme.palette.info.main,
yLabel: t("bytesPerSecond"),
xTick: ,
+ yTick: ,
toolTip: (
{
- const eth0Data = getEth0TimeSeries(checks);
+ const eth0Data = (checks || [])
+ .map((check) => {
+ const en0 = (check.net || []).find((iface) => iface.name === "en0");
+ if (!en0) return null;
+
+ return {
+ _id: check._id,
+ bytesPerSec: en0.avgBytesRecv,
+ packetsPerSec: en0.avgPacketsRecv,
+ errors: (en0.avgErrOut ?? 0),
+ drops: (en0.avgDropOut ?? 0)
+ };
+ })
+ .filter(Boolean);
+
+ console.log(eth0Data);
return (
<>
-
+
-
+
>
);
};
@@ -34,65 +43,3 @@ Network.propTypes = {
};
export default Network;
-
-/* ---------- Helper functions ---------- */
-function getEth0TimeSeries(checks) {
- const sorted = [...(checks || [])].sort((a, b) => new Date(a._id) - new Date(b._id));
- const series = [];
- let prev = null;
-
- for (const check of sorted) {
- const eth = (check.net || []).find((iface) => iface.name === "en0");
- if (!eth) {
- prev = check;
- continue;
- }
-
- if (prev) {
- const prevEth = (prev.net || []).find((iface) => iface.name === "en0");
- const t1 = new Date(check._id);
- const t0 = new Date(prev._id);
-
- if (!prevEth || isNaN(t1) || isNaN(t0)) {
- prev = check;
- continue;
- }
-
- const dt = (t1 - t0) / 1000;
-
- if (dt > 0) {
- const bytesField = eth.avgBytesSent;
- const prevBytesField = prevEth.avgBytesSent;
-
- if (bytesField !== undefined && prevBytesField !== undefined) {
- const dataPoint = {
- _id: check._id,
- bytesPerSec: (bytesField - prevBytesField) / dt,
- packetsPerSec: (eth.avgPacketsSent - prevEth.avgPacketsSent) / dt,
- errors: (eth.avgErrIn ?? 0) + (eth.avgErrOut ?? 0),
- drops: 0,
- };
- series.push(dataPoint);
- }
- }
- }
- prev = check;
- }
-
- // If we only have one check, create a single data point with absolute values
- if (series.length === 0 && sorted.length === 1) {
- const check = sorted[0];
- const eth = (check.net || []).find((iface) => iface.name === "en0");
- if (eth) {
- series.push({
- _id: check._id,
- bytesPerSec: eth.avgBytesSent || 0,
- packetsPerSec: eth.avgPacketsSent || 0,
- errors: (eth.avgErrIn ?? 0) + (eth.avgErrOut ?? 0),
- drops: 0,
- });
- }
- }
-
- return series;
-}
diff --git a/server/src/db/mongo/modules/monitorModule.js b/server/src/db/mongo/modules/monitorModule.js
index 76f055f94..4cd28ea9d 100755
--- a/server/src/db/mongo/modules/monitorModule.js
+++ b/server/src/db/mongo/modules/monitorModule.js
@@ -326,10 +326,53 @@ class MonitorModule {
}
};
+ processNetworkRates = (checks) => {
+ if (!Array.isArray(checks) || checks.length === 0) return [];
+
+ const sorted = [...checks].sort((a, b) => new Date(a._id) - new Date(b._id));
+ const lastSeen = {};
+
+ for (const check of sorted) {
+ if (!Array.isArray(check.net)) {
+ check.net = [];
+ continue;
+ }
+
+ const newNet = [];
+
+ for (const iface of check.net) {
+ const prev = lastSeen[iface.name];
+ const t1 = new Date(check._id);
+
+ if (prev) {
+ const t0 = new Date(prev._id);
+ const dt = (t1 - t0) / 1000;
+
+ if (dt > 0) {
+ newNet.push({
+ name: iface.name,
+ avgBytesRecv: (iface.avgBytesRecv - prev.avgBytesRecv),
+ avgPacketsRecv: (iface.avgPacketsRecv - prev.avgPacketsRecv),
+ avgErrOut: iface.avgErrOut - prev.avgErrOut,
+ avgDropOut: iface.avgDropOut,
+ });
+ }
+ }
+
+ lastSeen[iface.name] = { ...iface, _id: check._id };
+ }
+
+ check.net = newNet;
+ }
+
+ return sorted;
+ };
+
getHardwareDetailsById = async ({ monitorId, dateRange }) => {
try {
const monitor = await this.Monitor.findById(monitorId);
const dates = this.getDateRange(dateRange);
+
const formatLookup = {
recent: "%Y-%m-%dT%H:%M:00Z",
day: "%Y-%m-%dT%H:00:00Z",
@@ -337,13 +380,19 @@ class MonitorModule {
month: "%Y-%m-%dT00:00:00Z",
};
const dateString = formatLookup[dateRange];
+
const hardwareStats = await this.HardwareCheck.aggregate(buildHardwareDetailsPipeline(monitor, dates, dateString));
- const monitorStats = {
+ const stats = hardwareStats[0];
+ if (stats && stats.checks) {
+ // Replace net with per-second rates
+ stats.checks = this.processNetworkRates(stats.checks);
+ }
+
+ return {
...monitor.toObject(),
- stats: hardwareStats[0],
+ stats,
};
- return monitorStats;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "getHardwareDetailsById";
diff --git a/server/src/db/mongo/modules/monitorModuleQueries.js b/server/src/db/mongo/modules/monitorModuleQueries.js
index df15593e2..79804f183 100755
--- a/server/src/db/mongo/modules/monitorModuleQueries.js
+++ b/server/src/db/mongo/modules/monitorModuleQueries.js
@@ -393,7 +393,7 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => {
0,
],
},
- bytesSent: {
+ avgBytesSent: {
$avg: {
$map: {
input: "$net",
@@ -404,7 +404,7 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => {
},
},
},
- bytesRecv: {
+ avgBytesRecv: {
$avg: {
$map: {
input: "$net",
@@ -415,7 +415,7 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => {
},
},
},
- packetsSent: {
+ avgPacketsSent: {
$avg: {
$map: {
input: "$net",
@@ -426,7 +426,7 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => {
},
},
},
- packetsRecv: {
+ avgPacketsRecv: {
$avg: {
$map: {
input: "$net",
@@ -437,7 +437,7 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => {
},
},
},
- errIn: {
+ avgErrIn: {
$avg: {
$map: {
input: "$net",
@@ -448,7 +448,7 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => {
},
},
},
- errOut: {
+ avgErrOut: {
$avg: {
$map: {
input: "$net",
@@ -459,6 +459,28 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => {
},
},
},
+ avgDropIn: {
+ $avg: {
+ $map: {
+ input: "$net",
+ as: "netArray",
+ in: {
+ $arrayElemAt: ["$$netArray.drop_in", "$$netIndex"],
+ },
+ },
+ },
+ },
+ avgDropOut: {
+ $avg: {
+ $map: {
+ input: "$net",
+ as: "netArray",
+ in: {
+ $arrayElemAt: ["$$netArray.drop_out", "$$netIndex"],
+ },
+ },
+ },
+ },
},
},
},