Extract filesystem filtering logic into pkg/fsfilters

Move the inline filesystem skip logic from pollVMsAndContainersEfficient
into a reusable ShouldSkipFilesystem function. This consolidates filtering
for virtual filesystems (tmpfs, cgroup, etc.), network mounts (nfs, cifs,
fuse), and special mountpoints (/dev, /proc, /snap, etc.) into one tested
location.

Reduces cyclomatic complexity of pollVMsAndContainersEfficient and adds
28 test cases covering virtual fs types, network mounts, special mounts,
Windows paths, and edge cases.
This commit is contained in:
rcourtman 2025-11-29 16:38:08 +00:00
parent 69a7d96e4b
commit 61bb124692
3 changed files with 159 additions and 53 deletions

View file

@ -35,6 +35,7 @@ import (
"github.com/rcourtman/pulse-go-rewrite/internal/websocket"
agentsdocker "github.com/rcourtman/pulse-go-rewrite/pkg/agents/docker"
agentshost "github.com/rcourtman/pulse-go-rewrite/pkg/agents/host"
"github.com/rcourtman/pulse-go-rewrite/pkg/fsfilters"
"github.com/rcourtman/pulse-go-rewrite/pkg/pbs"
"github.com/rcourtman/pulse-go-rewrite/pkg/pmg"
"github.com/rcourtman/pulse-go-rewrite/pkg/proxmox"
@ -6585,62 +6586,25 @@ func (m *Monitor) pollVMsAndContainersEfficient(ctx context.Context, instanceNam
for _, fs := range fsInfo {
// Skip special filesystems and mounts
skipReasons := []string{}
reasonReadOnly := ""
shouldSkip := false
// Check filesystem type
fsTypeLower := strings.ToLower(fs.Type)
if reason, skip := readOnlyFilesystemReason(fs.Type, fs.TotalBytes, fs.UsedBytes); skip {
skipReasons = append(skipReasons, fmt.Sprintf("read-only-%s", reason))
reasonReadOnly = reason
shouldSkip = true
}
if fs.Type == "tmpfs" || fs.Type == "devtmpfs" ||
fs.Type == "cgroup" || fs.Type == "cgroup2" ||
fs.Type == "sysfs" || fs.Type == "proc" ||
fs.Type == "devpts" || fs.Type == "securityfs" ||
fs.Type == "debugfs" || fs.Type == "tracefs" ||
fs.Type == "fusectl" || fs.Type == "configfs" ||
fs.Type == "pstore" || fs.Type == "hugetlbfs" ||
fs.Type == "mqueue" || fs.Type == "bpf" ||
strings.Contains(fsTypeLower, "fuse") || // Skip FUSE mounts (often network/special)
strings.Contains(fsTypeLower, "9p") || // Skip 9p mounts (VM shared folders)
strings.Contains(fsTypeLower, "nfs") || // Skip NFS mounts
strings.Contains(fsTypeLower, "cifs") || // Skip CIFS/SMB mounts
strings.Contains(fsTypeLower, "smb") { // Skip SMB mounts
skipReasons = append(skipReasons, "special-fs-type")
shouldSkip = true
}
// Check mountpoint patterns
if strings.HasPrefix(fs.Mountpoint, "/dev") ||
strings.HasPrefix(fs.Mountpoint, "/proc") ||
strings.HasPrefix(fs.Mountpoint, "/sys") ||
strings.HasPrefix(fs.Mountpoint, "/run") ||
strings.HasPrefix(fs.Mountpoint, "/var/lib/docker") || // Skip Docker volumes
strings.HasPrefix(fs.Mountpoint, "/snap") || // Skip snap mounts
fs.Mountpoint == "/boot/efi" ||
fs.Mountpoint == "System Reserved" || // Windows System Reserved partition
strings.Contains(fs.Mountpoint, "System Reserved") { // Various Windows reserved formats
skipReasons = append(skipReasons, "special-mountpoint")
shouldSkip = true
}
shouldSkip, reasons := fsfilters.ShouldSkipFilesystem(fs.Type, fs.Mountpoint, fs.TotalBytes, fs.UsedBytes)
if shouldSkip {
if reasonReadOnly != "" {
log.Debug().
Str("instance", instanceName).
Str("vm", res.Name).
Int("vmid", res.VMID).
Str("mountpoint", fs.Mountpoint).
Str("type", fs.Type).
Float64("total_gb", float64(fs.TotalBytes)/1073741824).
Float64("used_gb", float64(fs.UsedBytes)/1073741824).
Msg("Skipping read-only filesystem from disk aggregation")
// Check if any reason is read-only for detailed logging
for _, r := range reasons {
if strings.HasPrefix(r, "read-only-") {
log.Debug().
Str("instance", instanceName).
Str("vm", res.Name).
Int("vmid", res.VMID).
Str("mountpoint", fs.Mountpoint).
Str("type", fs.Type).
Float64("total_gb", float64(fs.TotalBytes)/1073741824).
Float64("used_gb", float64(fs.UsedBytes)/1073741824).
Msg("Skipping read-only filesystem from disk aggregation")
break
}
}
skippedFS = append(skippedFS, fmt.Sprintf("%s(%s,%s)",
fs.Mountpoint, fs.Type, strings.Join(skipReasons, ",")))
fs.Mountpoint, fs.Type, strings.Join(reasons, ",")))
continue
}

View file

@ -54,3 +54,83 @@ func ShouldIgnoreReadOnlyFilesystem(fsType string, totalBytes, usedBytes uint64)
_, skip := ReadOnlyFilesystemReason(fsType, totalBytes, usedBytes)
return skip
}
// virtualFSTypes are filesystem types that represent virtual/pseudo filesystems
// which should not be counted toward disk usage.
var virtualFSTypes = map[string]bool{
"tmpfs": true,
"devtmpfs": true,
"cgroup": true,
"cgroup2": true,
"sysfs": true,
"proc": true,
"devpts": true,
"securityfs": true,
"debugfs": true,
"tracefs": true,
"fusectl": true,
"configfs": true,
"pstore": true,
"hugetlbfs": true,
"mqueue": true,
"bpf": true,
}
// networkFSPatterns are substrings that indicate network/remote filesystems.
var networkFSPatterns = []string{"fuse", "9p", "nfs", "cifs", "smb"}
// specialMountPrefixes are mountpoint prefixes that indicate system mounts.
var specialMountPrefixes = []string{
"/dev",
"/proc",
"/sys",
"/run",
"/var/lib/docker",
"/snap",
}
// ShouldSkipFilesystem determines if a filesystem should be excluded from disk
// usage aggregation. It checks for read-only filesystems, virtual/pseudo filesystems,
// network mounts, and special system mountpoints. Returns skip=true if the filesystem
// should be excluded, along with a list of reason strings.
func ShouldSkipFilesystem(fsType, mountpoint string, totalBytes, usedBytes uint64) (skip bool, reasons []string) {
fsTypeLower := strings.ToLower(strings.TrimSpace(fsType))
// Check read-only filesystems (existing logic)
if reason, isReadOnly := ReadOnlyFilesystemReason(fsType, totalBytes, usedBytes); isReadOnly {
reasons = append(reasons, "read-only-"+reason)
}
// Check virtual filesystem types
if virtualFSTypes[fsTypeLower] {
reasons = append(reasons, "special-fs-type")
}
// Check network filesystem patterns
for _, pattern := range networkFSPatterns {
if strings.Contains(fsTypeLower, pattern) {
reasons = append(reasons, "special-fs-type")
break
}
}
// Check special mountpoint prefixes
for _, prefix := range specialMountPrefixes {
if strings.HasPrefix(mountpoint, prefix) {
reasons = append(reasons, "special-mountpoint")
break
}
}
// Check specific special mountpoints
if mountpoint == "/boot/efi" {
reasons = append(reasons, "special-mountpoint")
}
// Windows System Reserved partition
if mountpoint == "System Reserved" || strings.Contains(mountpoint, "System Reserved") {
reasons = append(reasons, "special-mountpoint")
}
return len(reasons) > 0, reasons
}

View file

@ -141,3 +141,65 @@ func TestShouldIgnoreReadOnlyFilesystem(t *testing.T) {
t.Fatalf("expected ext4 to be included")
}
}
func TestShouldSkipFilesystem(t *testing.T) {
tests := []struct {
name string
fsType string
mountpoint string
totalBytes uint64
usedBytes uint64
expectSkip bool
}{
// Virtual filesystem types
{"tmpfs", "tmpfs", "/tmp", 1024, 512, true},
{"devtmpfs", "devtmpfs", "/dev", 1024, 100, true},
{"cgroup2", "cgroup2", "/sys/fs/cgroup", 0, 0, true},
{"sysfs", "sysfs", "/sys", 0, 0, true},
{"proc", "proc", "/proc", 0, 0, true},
{"virtual fs case insensitive", "TMPFS", "/tmp", 1024, 512, true},
// Network filesystem types
{"nfs mount", "nfs4", "/mnt/nas", 1000000, 500000, true},
{"cifs mount", "cifs", "/mnt/share", 1000000, 500000, true},
{"fuse.sshfs", "fuse.sshfs", "/mnt/remote", 1000000, 500000, true},
{"9p VM shared folder", "9p", "/mnt/host", 1000000, 500000, true},
// Special mountpoint prefixes
{"/dev prefix", "ext4", "/dev/shm", 1024, 100, true},
{"/proc prefix", "ext4", "/proc/sys", 1024, 100, true},
{"/sys prefix", "ext4", "/sys/kernel", 1024, 100, true},
{"/run prefix", "ext4", "/run/user/1000", 1024, 100, true},
{"/var/lib/docker", "ext4", "/var/lib/docker/overlay2", 1000000, 500000, true},
{"/snap prefix", "ext4", "/snap/core/12345", 1000000, 500000, true},
{"/boot/efi exact", "vfat", "/boot/efi", 512 * 1024 * 1024, 50 * 1024 * 1024, true},
// Windows paths
{"Windows System Reserved", "NTFS", "System Reserved", 500 * 1024 * 1024, 100 * 1024 * 1024, true},
{"Windows C drive - should NOT skip", "NTFS", "C:\\", 500 * 1024 * 1024 * 1024, 200 * 1024 * 1024 * 1024, false},
{"Windows D drive - should NOT skip", "NTFS", "D:\\", 1000 * 1024 * 1024 * 1024, 500 * 1024 * 1024 * 1024, false},
// Regular filesystems that should NOT be skipped
{"ext4 root", "ext4", "/", 100 * 1024 * 1024 * 1024, 50 * 1024 * 1024 * 1024, false},
{"xfs data", "xfs", "/data", 500 * 1024 * 1024 * 1024, 200 * 1024 * 1024 * 1024, false},
{"btrfs home", "btrfs", "/home", 200 * 1024 * 1024 * 1024, 100 * 1024 * 1024 * 1024, false},
{"zfs pool", "zfs", "/tank", 10 * 1024 * 1024 * 1024 * 1024, 5 * 1024 * 1024 * 1024 * 1024, false},
// Edge cases
{"empty fsType", "", "/mnt/data", 1000000, 500000, false},
{"empty mountpoint", "ext4", "", 1000000, 500000, false},
{"whitespace fsType", " tmpfs ", "/tmp", 1024, 512, true},
// Read-only filesystems (should still work through new function)
{"squashfs via ShouldSkipFilesystem", "squashfs", "/snap/firefox/123", 100 * 1024 * 1024, 100 * 1024 * 1024, true},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
skip, reasons := ShouldSkipFilesystem(tc.fsType, tc.mountpoint, tc.totalBytes, tc.usedBytes)
if skip != tc.expectSkip {
t.Errorf("expected skip=%t, got skip=%t (reasons: %v)", tc.expectSkip, skip, reasons)
}
})
}
}