fix: parse Block I/O and Network I/O stats to numeric MB so monitoring charts render

This commit is contained in:
Mauricio Siu
2026-08-06 14:20:29 -06:00
parent 309dd567d6
commit b3e7a8a74c
5 changed files with 57 additions and 12 deletions
@@ -1,3 +1,4 @@
import { formatMb, toMb } from "@dokploy/server/monitoring/units";
import { format } from "date-fns"; import { format } from "date-fns";
import { Area, AreaChart, CartesianGrid, YAxis } from "recharts"; import { Area, AreaChart, CartesianGrid, YAxis } from "recharts";
import { import {
@@ -29,8 +30,8 @@ export const DockerBlockChart = ({ accumulativeData }: Props) => {
const transformedData = accumulativeData.map((item, index) => ({ const transformedData = accumulativeData.map((item, index) => ({
time: item.time, time: item.time,
name: `Point ${index + 1}`, name: `Point ${index + 1}`,
readMb: item.value.readMb, readMb: toMb(item.value.readMb),
writeMb: item.value.writeMb, writeMb: toMb(item.value.writeMb),
})); }));
return ( return (
@@ -77,7 +78,7 @@ export const DockerBlockChart = ({ accumulativeData }: Props) => {
}} }}
formatter={(value, name) => { formatter={(value, name) => {
const label = name === "readMb" ? "Read" : "Write"; const label = name === "readMb" ? "Read" : "Write";
return [`${value} MB`, label]; return [formatMb(Number(value)), label];
}} }}
/> />
} }
@@ -1,3 +1,4 @@
import { formatMb, toMb } from "@dokploy/server/monitoring/units";
import { format } from "date-fns"; import { format } from "date-fns";
import { Area, AreaChart, CartesianGrid, YAxis } from "recharts"; import { Area, AreaChart, CartesianGrid, YAxis } from "recharts";
import { import {
@@ -29,8 +30,8 @@ export const DockerNetworkChart = ({ accumulativeData }: Props) => {
const transformedData = accumulativeData.map((item, index) => ({ const transformedData = accumulativeData.map((item, index) => ({
time: item.time, time: item.time,
name: `Point ${index + 1}`, name: `Point ${index + 1}`,
inMB: item.value.inputMb, inMB: toMb(item.value.inputMb),
outMB: item.value.outputMb, outMB: toMb(item.value.outputMb),
})); }));
return ( return (
@@ -73,7 +74,7 @@ export const DockerNetworkChart = ({ accumulativeData }: Props) => {
}} }}
formatter={(value, name) => { formatter={(value, name) => {
const label = name === "inMB" ? "In" : "Out"; const label = name === "inMB" ? "In" : "Out";
return [`${value} MB`, label]; return [formatMb(Number(value)), label];
}} }}
/> />
} }
@@ -1,3 +1,4 @@
import { formatMb } from "@dokploy/server/monitoring/units";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Progress } from "@/components/ui/progress"; import { Progress } from "@/components/ui/progress";
@@ -305,7 +306,7 @@ export const ContainerFreeMonitoring = ({
<CardContent> <CardContent>
<div className="flex flex-col gap-2 w-full"> <div className="flex flex-col gap-2 w-full">
<span className="text-sm text-muted-foreground"> <span className="text-sm text-muted-foreground">
{`Read: ${currentData.block.value.readMb} / Write: ${currentData.block.value.writeMb} `} {`Read: ${formatMb(currentData.block.value.readMb)} / Write: ${formatMb(currentData.block.value.writeMb)}`}
</span> </span>
<DockerBlockChart accumulativeData={accumulativeData.block} /> <DockerBlockChart accumulativeData={accumulativeData.block} />
</div> </div>
@@ -318,7 +319,7 @@ export const ContainerFreeMonitoring = ({
<CardContent> <CardContent>
<div className="flex flex-col gap-2 w-full"> <div className="flex flex-col gap-2 w-full">
<span className="text-sm text-muted-foreground"> <span className="text-sm text-muted-foreground">
{`In MB: ${currentData.network.value.inputMb} / Out MB: ${currentData.network.value.outputMb} `} {`In: ${formatMb(currentData.network.value.inputMb)} / Out: ${formatMb(currentData.network.value.outputMb)}`}
</span> </span>
<DockerNetworkChart accumulativeData={accumulativeData.network} /> <DockerNetworkChart accumulativeData={accumulativeData.network} />
</div> </div>
+41
View File
@@ -0,0 +1,41 @@
const IO_UNIT_FACTORS: Record<string, number> = {
B: 1 / 1e6,
kB: 1e-3,
KB: 1e-3,
MB: 1,
GB: 1e3,
TB: 1e6,
KiB: 1024 / 1e6,
MiB: 1024 ** 2 / 1e6,
GiB: 1024 ** 3 / 1e6,
TiB: 1024 ** 4 / 1e6,
};
export const toMb = (value: number | string | undefined): number => {
if (typeof value === "number") {
return value;
}
if (!value) {
return 0;
}
const parsed = Number.parseFloat(value);
if (Number.isNaN(parsed)) {
return 0;
}
const unit = value.replace(/[0-9.\s]/g, "");
return parsed * (IO_UNIT_FACTORS[unit] ?? 1);
};
export const parseIoToMb = (raw: string | undefined): number =>
Number(toMb(raw).toFixed(6));
export const formatMb = (value: number | string | undefined): string => {
const mb = toMb(value);
if (mb >= 1000) {
return `${(mb / 1000).toFixed(2)} GB`;
}
if (mb < 0.01 && mb > 0) {
return `${(mb * 1000).toFixed(2)} kB`;
}
return `${mb.toFixed(2)} MB`;
};
+5 -4
View File
@@ -1,6 +1,7 @@
import { promises } from "node:fs"; import { promises } from "node:fs";
import { OSUtils } from "node-os-utils"; import { OSUtils } from "node-os-utils";
import { paths } from "../constants"; import { paths } from "../constants";
import { parseIoToMb } from "./units";
export interface Container { export interface Container {
BlockIO: string; BlockIO: string;
@@ -28,13 +29,13 @@ export const recordAdvancedStats = async (
}); });
await updateStatsFile(appName, "block", { await updateStatsFile(appName, "block", {
readMb: stats.BlockIO.split(" ")[0], readMb: parseIoToMb(stats.BlockIO.split(" ")[0]),
writeMb: stats.BlockIO.split(" ")[2], writeMb: parseIoToMb(stats.BlockIO.split(" ")[2]),
}); });
await updateStatsFile(appName, "network", { await updateStatsFile(appName, "network", {
inputMb: stats.NetIO.split(" ")[0], inputMb: parseIoToMb(stats.NetIO.split(" ")[0]),
outputMb: stats.NetIO.split(" ")[2], outputMb: parseIoToMb(stats.NetIO.split(" ")[2]),
}); });
if (appName === "dokploy") { if (appName === "dokploy") {