diff --git a/src/internal/qmmonitor/block.go b/src/internal/qmmonitor/block.go index 693f680..fa51f3a 100644 --- a/src/internal/qmmonitor/block.go +++ b/src/internal/qmmonitor/block.go @@ -17,8 +17,10 @@ type DiskInfo struct { Labels map[string]string // additional labels: vol_name, pool, device, etc. } -// blockHeaderRe matches: "disk_name (#blockN): /path/to/disk (type, mode)" -var blockHeaderRe = regexp.MustCompile(`^(\w+) \(#block(\d+)\): (.+) \(([\w, -]+)\)$`) +// blockHeaderRe matches block device headers in both old and new QEMU formats: +// Old: "disk_name (#blockN): /path/to/disk (type, mode)" +// New: "disk_name: /path/to/disk (type, mode)" +var blockHeaderRe = regexp.MustCompile(`^(\w+)(?:\s+\(#block(\d+)\))?: (.+) \(([\w, -]+)\)$`) // lvmRe matches: /dev/{vg_name}/vm-{N}-disk-{N} var lvmRe = regexp.MustCompile(`^/dev/([^/]+)/(vm-\d+-disk-\d+)$`) @@ -98,7 +100,8 @@ func ParseBlockInfo(raw string) map[string]DiskInfo { val := strings.TrimSpace(strings.TrimPrefix(line, "Cache mode:")) info.Labels["cache_mode"] = val } else if strings.HasPrefix(line, "Detect zeroes:") { - info.Labels["detect_zeroes"] = "on" + val := strings.TrimSpace(strings.TrimPrefix(line, "Detect zeroes:")) + info.Labels["detect_zeroes"] = val } } diff --git a/src/internal/qmmonitor/block_test.go b/src/internal/qmmonitor/block_test.go index 7cb350b..7cc259a 100644 --- a/src/internal/qmmonitor/block_test.go +++ b/src/internal/qmmonitor/block_test.go @@ -172,6 +172,52 @@ func TestParseBlockInfo_JSONError(t *testing.T) { } } +func TestParseBlockInfo_Throttle(t *testing.T) { + // PVE 9.x / newer QEMU format: no (#blockN) + raw := `drive-scsi0: json:{"driver":"raw","file":{"driver":"host_device","filename":"/dev/zvol/rpool/data/vm-100-disk-0"}} (throttle, read-write) + Attached to: /machine/peripheral/virtioscsi0/virtio-backend + Cache mode: writeback, direct + Detect zeroes: unmap +` + disks := ParseBlockInfo(raw) + if len(disks) != 1 { + t.Fatalf("expected 1 disk, got %d", len(disks)) + } + d := disks["scsi0"] + if d.BlockID != "" { + t.Errorf("block_id = %q, want empty", d.BlockID) + } + if d.DiskType != "zvol" { + t.Errorf("type = %q, want zvol", d.DiskType) + } + if d.DiskPath != "/dev/zvol/rpool/data/vm-100-disk-0" { + t.Errorf("path = %q", d.DiskPath) + } + if d.Labels["pool"] != "rpool/data" { + t.Errorf("pool = %q", d.Labels["pool"]) + } + if d.Labels["vol_name"] != "vm-100-disk-0" { + t.Errorf("vol_name = %q", d.Labels["vol_name"]) + } + if d.Labels["detect_zeroes"] != "unmap" { + t.Errorf("detect_zeroes = %q, want unmap", d.Labels["detect_zeroes"]) + } + if d.Labels["cache_mode"] != "writeback, direct" { + t.Errorf("cache_mode = %q", d.Labels["cache_mode"]) + } +} + +func TestParseBlockInfo_DetectZeroesUnmap(t *testing.T) { + raw := `drive-scsi0 (#block100): /dev/zvol/rpool/data/vm-100-disk-0 (raw, read-write) + Detect zeroes: unmap +` + disks := ParseBlockInfo(raw) + d := disks["scsi0"] + if d.Labels["detect_zeroes"] != "unmap" { + t.Errorf("detect_zeroes = %q, want unmap", d.Labels["detect_zeroes"]) + } +} + func TestParseBlockInfo_MultiDisk(t *testing.T) { raw := `drive-scsi0 (#block100): /dev/zvol/rpool/data/vm-100-disk-0 (raw, read-write) Attached to: /machine/peripheral/virtioscsi0/virtio-backend