Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { shouldIncludeDiskStat } from "@dokploy/server/monitoring/utils";
import { describe, expect, it } from "vitest";

describe("shouldIncludeDiskStat", () => {
it("excludes partitions when their parent disk is present (every byte would be counted twice)", () => {
// /proc/diskstats lists partition I/O in both the partition row and the
// parent disk row; summing both double-counts.
const devices = ["sda", "sda1", "sda2", "sdb", "sdb1"];
expect(shouldIncludeDiskStat("sda", devices)).toBe(true);
expect(shouldIncludeDiskStat("sdb", devices)).toBe(true);
expect(shouldIncludeDiskStat("sda1", devices)).toBe(false);
expect(shouldIncludeDiskStat("sda2", devices)).toBe(false);
expect(shouldIncludeDiskStat("sdb1", devices)).toBe(false);
});

it("handles nvme/mmc/md partition naming (pN suffix)", () => {
const devices = [
"nvme0n1",
"nvme0n1p1",
"nvme0n1p2",
"mmcblk0",
"mmcblk0p1",
];
expect(shouldIncludeDiskStat("nvme0n1", devices)).toBe(true);
expect(shouldIncludeDiskStat("nvme0n1p1", devices)).toBe(false);
expect(shouldIncludeDiskStat("nvme0n1p2", devices)).toBe(false);
expect(shouldIncludeDiskStat("mmcblk0", devices)).toBe(true);
expect(shouldIncludeDiskStat("mmcblk0p1", devices)).toBe(false);
});

it("keeps whole disks whose names end in a digit", () => {
const devices = ["nvme0n1", "mmcblk0"];
expect(shouldIncludeDiskStat("nvme0n1", devices)).toBe(true);
expect(shouldIncludeDiskStat("mmcblk0", devices)).toBe(true);
});

it("excludes virtual devices", () => {
const devices = ["loop0", "loop1", "ram0", "sr0", "sda"];
expect(shouldIncludeDiskStat("loop0", devices)).toBe(false);
expect(shouldIncludeDiskStat("ram0", devices)).toBe(false);
expect(shouldIncludeDiskStat("sr0", devices)).toBe(false);
expect(shouldIncludeDiskStat("sda", devices)).toBe(true);
});

it("keeps a partition-looking row if its parent disk is absent (defensive)", () => {
expect(shouldIncludeDiskStat("sda1", ["sda1"])).toBe(true);
});
});
47 changes: 40 additions & 7 deletions packages/server/src/monitoring/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,39 @@ export const recordAdvancedStats = async (
}
};

// Virtual devices that never represent real disk I/O.
const virtualDiskPatterns = [/^loop/, /^ram/, /^sr\d+$/, /^fd\d+$/];

// Partition suffix patterns: sdX1, vdX1, xvdX1, hdX1 (trailing digits) and
// nvme0n1p1 / mmcblk0p1 / md0p1 (p<digits> after a digit-bearing base).
const partitionSuffixPatterns = [/p\d+$/, /\d+$/];

/**
* Decides whether a /proc/diskstats row should be summed into host disk I/O.
* Virtual devices (loop, ram, cdrom, floppy) are excluded, and a partition
* row is excluded whenever its parent disk is also present, because the
* parent disk's counters already include all of its partitions' I/O -
* summing both counts every byte twice.
*/
export const shouldIncludeDiskStat = (
device: string,
allDevices: string[],
): boolean => {
if (virtualDiskPatterns.some((pattern) => pattern.test(device))) {
return false;
}
for (const suffixPattern of partitionSuffixPatterns) {
if (!suffixPattern.test(device)) {
continue;
}
const parent = device.replace(suffixPattern, "");
if (parent !== device && allDevices.includes(parent)) {
return false;
}
}
return true;
};

/**
* Get host system statistics using node-os-utils
* This is used when monitoring "dokploy" to show host stats instead of container stats
Expand Down Expand Up @@ -99,14 +132,14 @@ export const getHostSystemStats = async (): Promise<Container> => {
let blockWriteBytes = 0;
const diskStats = await osutils.disk.stats();
if (diskStats.success && diskStats.data.length > 0) {
// Filter out virtual devices (loop, ram, sr, etc.) - only include real disk devices
const excludePatterns = [/^loop/, /^ram/, /^sr\d+$/, /^fd\d+$/];
const devices = diskStats.data
.map((stat) => stat.device)
.filter((device): device is string => !!device);
for (const stat of diskStats.data) {
// Skip virtual devices
if (
stat.device &&
excludePatterns.some((pattern) => pattern.test(stat.device))
) {
// Skip virtual devices and partitions: /proc/diskstats lists a
// partition's I/O in both its own row and its parent disk's row,
// so summing both counts every byte twice.
if (!stat.device || !shouldIncludeDiskStat(stat.device, devices)) {
continue;
}
// readBytes and writeBytes are DataSize objects with .toBytes() method
Expand Down