diff --git a/cmd/pulse-agent/main.go b/cmd/pulse-agent/main.go index e964d2b53..5abde3d2a 100644 --- a/cmd/pulse-agent/main.go +++ b/cmd/pulse-agent/main.go @@ -1062,7 +1062,7 @@ func loadConfig(args []string, getenv func(string) string) (Config, error) { var kubeExcludeNamespaceFlags multiValue fs.Var(&kubeExcludeNamespaceFlags, "kube-exclude-namespace", "Namespace to exclude (repeatable)") var diskExcludeFlags multiValue - fs.Var(&diskExcludeFlags, "disk-exclude", "Mount point or path prefix to exclude from disk monitoring (repeatable)") + fs.Var(&diskExcludeFlags, "disk-exclude", "Device name/path or mount point pattern to exclude from disk monitoring (repeatable)") if err := fs.Parse(args); err != nil { return Config{}, err diff --git a/cmd/pulse-agent/main_test.go b/cmd/pulse-agent/main_test.go index 0d1a87786..9182609a2 100644 --- a/cmd/pulse-agent/main_test.go +++ b/cmd/pulse-agent/main_test.go @@ -1147,10 +1147,19 @@ func TestLoadConfig(t *testing.T) { } }) - t.Run("tags and csv", func(t *testing.T) { - cfg, err := loadConfig([]string{"-token", "T", "-tag", "t1", "-tag", "t2", "-disk-exclude", "d1"}, func(s string) string { - if s == "PULSE_TAGS" { + t.Run("tags and repeated disk exclusions", func(t *testing.T) { + cfg, err := loadConfig([]string{ + "-token", "T", + "-tag", "t1", + "-tag", "t2", + "-disk-exclude", "sdb", + "-disk-exclude", "/mnt/pve/local-backup", + }, func(s string) string { + switch s { + case "PULSE_TAGS": return "e1,e2" + case "PULSE_DISK_EXCLUDE": + return "/dev/sda,/var/run/samba/fd" } return "" }) @@ -1161,7 +1170,7 @@ func TestLoadConfig(t *testing.T) { if !reflect.DeepEqual(cfg.Tags, expectedTags) { t.Errorf("expected tags %v, got %v", expectedTags, cfg.Tags) } - expectedDisk := []string{"d1"} + expectedDisk := []string{"/dev/sda", "/var/run/samba/fd", "sdb", "/mnt/pve/local-backup"} if !reflect.DeepEqual(cfg.DiskExclude, expectedDisk) { t.Errorf("expected disk exclude %v, got %v", expectedDisk, cfg.DiskExclude) } diff --git a/docs/UNIFIED_AGENT.md b/docs/UNIFIED_AGENT.md index 1f7a17a1d..adab2e388 100644 --- a/docs/UNIFIED_AGENT.md +++ b/docs/UNIFIED_AGENT.md @@ -104,7 +104,7 @@ curl -fsSL http://:7655/install.sh | \ | `--proxmox-type` | `PULSE_PROXMOX_TYPE` | Proxmox type: `pve` or `pbs` | *(auto-detect)* | | `--enable-commands` | `PULSE_ENABLE_COMMANDS` | Enable AI command execution (disabled by default) | `false` | | `--disable-commands` | `PULSE_DISABLE_COMMANDS` | **Deprecated** (commands are disabled by default) | - | -| `--disk-exclude` | `PULSE_DISK_EXCLUDE` | Mount point patterns to exclude from disk monitoring (repeatable or CSV) | *(none)* | +| `--disk-exclude` | `PULSE_DISK_EXCLUDE` | Device name/path or mount point patterns to exclude from disk and S.M.A.R.T. monitoring (repeatable or CSV) | *(none)* | | `--kubeconfig` | `PULSE_KUBECONFIG` | Kubeconfig path (optional) | *(auto)* | | `--kube-context` | `PULSE_KUBE_CONTEXT` | Kubeconfig context (optional) | *(auto)* | | `--kube-include-namespace` | `PULSE_KUBE_INCLUDE_NAMESPACES` | Limit namespaces (repeatable or CSV, wildcards supported) | *(all)* | @@ -258,8 +258,11 @@ curl -fsSL http://:7655/install.sh | \ ### Exclude Specific Disks from Monitoring ```bash +# Exclude whole block devices by name or path +pulse-agent --disk-exclude sda --disk-exclude /dev/sdb + # Exclude specific mount points -pulse-agent --disk-exclude /mnt/backup --disk-exclude /media/external +pulse-agent --disk-exclude /mnt/backup --disk-exclude /var/run/samba/fd # Exclude using patterns (prefix match) pulse-agent --disk-exclude '/mnt/pbs*' # Matches /mnt/pbs-data, /mnt/pbs-backup, etc. @@ -268,13 +271,17 @@ pulse-agent --disk-exclude '/mnt/pbs*' # Matches /mnt/pbs-data, /mnt/pbs-backup pulse-agent --disk-exclude '*pbs*' # Matches any path containing 'pbs' # Via environment variable (comma-separated) -PULSE_DISK_EXCLUDE=/mnt/backup,*pbs*,/media/external +PULSE_DISK_EXCLUDE=/dev/sda,*pbs*,/var/run/samba/fd ``` **Pattern types:** -- Exact: `/mnt/backup` - matches only that exact path -- Prefix: `/mnt/ext*` - matches paths starting with `/mnt/ext` -- Contains: `*pbs*` - matches paths containing `pbs` +- Exact: `/dev/sda`, `sda`, or `/mnt/backup` - matches that device path, device name, or mount point +- Prefix: `/dev/nvme*` or `/mnt/ext*` - matches device paths or mount points with that prefix +- Contains: `*cache*` or `*pbs*` - matches device paths, device names, or mount points containing that text + +Exclusions are applied before filesystem usage, disk I/O, and S.M.A.R.T. collection. +On linked Proxmox hosts, matching physical-disk health and SSD wear alerts are +also suppressed. ## S.M.A.R.T. Disk Health diff --git a/internal/hostmetrics/collector_test.go b/internal/hostmetrics/collector_test.go index 5e73a08ec..26d78fd68 100644 --- a/internal/hostmetrics/collector_test.go +++ b/internal/hostmetrics/collector_test.go @@ -113,6 +113,41 @@ func TestCollectDisks_DeviceDeduplication(t *testing.T) { } } +func TestCollectDisksExcludesFreeBSDFdescfsBeforeUsage(t *testing.T) { + origPartitions := diskPartitions + origUsage := diskUsage + t.Cleanup(func() { + diskPartitions = origPartitions + diskUsage = origUsage + }) + + diskPartitions = func(context.Context, bool) ([]godisk.PartitionStat, error) { + return []godisk.PartitionStat{ + {Device: "fdescfs", Mountpoint: "/var/run/samba/fd", Fstype: "fdescfs"}, + {Device: "/dev/ada0p2", Mountpoint: "/", Fstype: "ufs"}, + }, nil + } + + usageCalls := make([]string, 0, 1) + diskUsage = func(_ context.Context, path string) (*godisk.UsageStat, error) { + usageCalls = append(usageCalls, path) + return &godisk.UsageStat{ + Total: 100, + Used: 25, + Free: 75, + UsedPercent: 25, + }, nil + } + + disks := collectDisks(context.Background(), []string{"/var/run/samba/fd"}) + if len(disks) != 1 || disks[0].Mountpoint != "/" { + t.Fatalf("excluded fdescfs mount was reported: %+v", disks) + } + if len(usageCalls) != 1 || usageCalls[0] != "/" { + t.Fatalf("disk usage was queried for an excluded fdescfs mount: %v", usageCalls) + } +} + func TestCollectSplitsReclaimableCache(t *testing.T) { origVirtualMemory := virtualMemory t.Cleanup(func() { virtualMemory = origVirtualMemory }) diff --git a/scripts/install.sh b/scripts/install.sh index 3b09be39b..f1efbcb42 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -20,7 +20,7 @@ # --disable-proxmox Disable Proxmox integration even if detected # --interval Reporting interval (default: 30s) # --agent-id Custom agent identifier (default: auto-generated) -# --disk-exclude Exclude mount points matching pattern (repeatable) +# --disk-exclude Exclude device names/paths or mount points (repeatable) # --insecure Skip TLS certificate verification # --server-fingerprint Pin the Pulse server leaf certificate # --observers-file Report to additional observer Pulse instances @@ -334,7 +334,7 @@ Options: --hostname Override hostname reported to Pulse --report-ip IP address to report to Pulse (for multi-NIC systems) --state-dir Override persistent state directory - --disk-exclude Exclude mount point (repeatable) + --disk-exclude Exclude device names/paths or mount points (repeatable) --insecure Skip TLS verification (auto-enabled for http:// URLs) --cacert Custom CA certificate for TLS (used by curl and agent) --server-fingerprint Pin the Pulse server leaf certificate for agent connections diff --git a/scripts/installtests/install_sh_test.go b/scripts/installtests/install_sh_test.go index e5c2271e6..0b48daff2 100644 --- a/scripts/installtests/install_sh_test.go +++ b/scripts/installtests/install_sh_test.go @@ -811,6 +811,9 @@ deadbeef agent-123 --hostname pve-one +--disk-exclude +/dev/sda +--disk-exclude=/var/run/samba/fd ARGS build_exec_args printf 'URL=%s\nTOKEN=%s\nDOCKER=%s\nDOCKER_EXPLICIT=%s\nINSECURE=%s\nAGENT_ID=%s\nHOSTNAME=%s\nEXEC_ARGS=%s\n' \ @@ -832,6 +835,8 @@ ARGS "HOSTNAME=pve-one", "--token-file /var/lib/pulse-agent/token", "--enable-docker", + "--disk-exclude /dev/sda", + "--disk-exclude /var/run/samba/fd", } for _, needle := range required { if !strings.Contains(got, needle) {