diff options
Diffstat (limited to 'internal/collector')
| -rw-r--r-- | internal/collector/collector.go | 5 | ||||
| -rw-r--r-- | internal/collector/parse.go | 30 | ||||
| -rw-r--r-- | internal/collector/parse_test.go | 43 | ||||
| -rw-r--r-- | internal/collector/protocol.go | 3 | ||||
| -rw-r--r-- | internal/collector/scriptdata/loadbars-remote.sh | 10 | ||||
| -rw-r--r-- | internal/collector/types.go | 10 |
6 files changed, 100 insertions, 1 deletions
diff --git a/internal/collector/collector.go b/internal/collector/collector.go index 0107d61..5c99347 100644 --- a/internal/collector/collector.go +++ b/internal/collector/collector.go @@ -18,6 +18,7 @@ type StatsStore interface { SetCPU(host, name string, line CPULine) SetMem(host, key string, value int64) SetNet(host, iface string, net NetLine, stamp float64) + SetDisk(host, device string, disk DiskLine, stamp float64) } // Run starts a collector for one host: runs the embedded remote script (local or over SSH) @@ -134,6 +135,10 @@ func dispatchCollectorLine(mode, line, hostKey string, store StatsStore) { store.SetNet(hostKey, net.Iface, net, float64(time.Now().UnixNano())/1e9) } } + case ModeDiskStats: + if d, err := ParseDiskLine(line); err == nil { + store.SetDisk(hostKey, d.Device, d, float64(time.Now().UnixNano())/1e9) + } case ModeCPUStats: if strings.HasPrefix(line, "cpu") { if cu, err := ParseCPULine(line); err == nil { diff --git a/internal/collector/parse.go b/internal/collector/parse.go index 4f49456..034a50b 100644 --- a/internal/collector/parse.go +++ b/internal/collector/parse.go @@ -100,3 +100,33 @@ func ParseLoadAvg(line string) LoadAvg { } return l } + +// ParseDiskLine parses "device:rs=N;ws=N;rt=N;wt=N;io=N" from the M DISKSTATS section. +func ParseDiskLine(line string) (DiskLine, error) { + parts := strings.SplitN(line, ":", 2) + if len(parts) != 2 { + return DiskLine{}, fmt.Errorf("disk line missing colon: %q", line) + } + d := DiskLine{Device: strings.TrimSpace(parts[0])} + for _, pair := range strings.Split(parts[1], ";") { + kv := strings.SplitN(pair, "=", 2) + if len(kv) != 2 { + continue + } + k := strings.TrimSpace(kv[0]) + v, _ := strconv.ParseInt(strings.TrimSpace(kv[1]), 10, 64) + switch k { + case "rs": + d.SectorsRead = v + case "ws": + d.SectorsWrite = v + case "rt": + d.ReadTicks = v + case "wt": + d.WriteTicks = v + case "io": + d.IoTicks = v + } + } + return d, nil +} diff --git a/internal/collector/parse_test.go b/internal/collector/parse_test.go index fe7a73c..8d569cd 100644 --- a/internal/collector/parse_test.go +++ b/internal/collector/parse_test.go @@ -115,3 +115,46 @@ func TestParseLoadAvg(t *testing.T) { t.Errorf("ParseLoadAvg(1.0) = %+v", got2) } } + +func TestParseDiskLine(t *testing.T) { + tests := []struct { + name string + line string + wantDevice string + wantSR int64 + wantSW int64 + wantRT int64 + wantWT int64 + wantIO int64 + wantErr bool + }{ + {"full", "sda:rs=1000;ws=2000;rt=50;wt=100;io=120", "sda", 1000, 2000, 50, 100, 120, false}, + {"nvme", "nvme0n1:rs=500;ws=300;rt=10;wt=20;io=30", "nvme0n1", 500, 300, 10, 20, 30, false}, + {"zeros", "vda:rs=0;ws=0;rt=0;wt=0;io=0", "vda", 0, 0, 0, 0, 0, false}, + {"large_counters", "sda:rs=9999999999;ws=8888888888;rt=100;wt=200;io=300", "sda", 9999999999, 8888888888, 100, 200, 300, false}, + {"partial_fields", "sda:rs=100;ws=200", "sda", 100, 200, 0, 0, 0, false}, + {"no_colon", "sda rs=100", "", 0, 0, 0, 0, 0, true}, + {"empty", "", "", 0, 0, 0, 0, 0, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ParseDiskLine(tt.line) + if (err != nil) != tt.wantErr { + t.Errorf("ParseDiskLine() error = %v, wantErr %v", err, tt.wantErr) + return + } + if tt.wantErr { + return + } + if got.Device != tt.wantDevice { + t.Errorf("Device = %q, want %q", got.Device, tt.wantDevice) + } + if got.SectorsRead != tt.wantSR || got.SectorsWrite != tt.wantSW { + t.Errorf("Sectors = read:%d write:%d, want read:%d write:%d", got.SectorsRead, got.SectorsWrite, tt.wantSR, tt.wantSW) + } + if got.ReadTicks != tt.wantRT || got.WriteTicks != tt.wantWT || got.IoTicks != tt.wantIO { + t.Errorf("Ticks = rt:%d wt:%d io:%d, want rt:%d wt:%d io:%d", got.ReadTicks, got.WriteTicks, got.IoTicks, tt.wantRT, tt.wantWT, tt.wantIO) + } + }) + } +} diff --git a/internal/collector/protocol.go b/internal/collector/protocol.go index 26e8a8d..92557e1 100644 --- a/internal/collector/protocol.go +++ b/internal/collector/protocol.go @@ -5,5 +5,6 @@ const ( ModeLoadAvg = "M LOADAVG" ModeMemStats = "M MEMSTATS" ModeNetStats = "M NETSTATS" - ModeCPUStats = "M CPUSTATS" + ModeDiskStats = "M DISKSTATS" + ModeCPUStats = "M CPUSTATS" ) diff --git a/internal/collector/scriptdata/loadbars-remote.sh b/internal/collector/scriptdata/loadbars-remote.sh index 9037ad8..1bdb661 100644 --- a/internal/collector/scriptdata/loadbars-remote.sh +++ b/internal/collector/scriptdata/loadbars-remote.sh @@ -26,6 +26,16 @@ while true; do fi done < <(tail -n +3 /proc/net/dev 2>/dev/null) + # Disk: /proc/diskstats, one line per block device with cumulative counters + echo "M DISKSTATS" + while IFS= read -r line; do + set -- $line + # $1=major $2=minor $3=device $4=reads_completed $5=reads_merged + # $6=sectors_read $7=ms_reading $8=writes_completed $9=writes_merged + # $10=sectors_written $11=ms_writing $12=ios_in_progress $13=ms_io + [ -n "$3" ] && echo "$3:rs=${6:-0};ws=${10:-0};rt=${7:-0};wt=${11:-0};io=${13:-0}" + done < /proc/diskstats 2>/dev/null + # CPU: /proc/stat, 20 times with INTERVAL sleep echo "M CPUSTATS" for _ in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20; do diff --git a/internal/collector/types.go b/internal/collector/types.go index 13bcb1f..6d1ecf0 100644 --- a/internal/collector/types.go +++ b/internal/collector/types.go @@ -34,3 +34,13 @@ type NetLine struct { type LoadAvg struct { Load1, Load5, Load15 string } + +// DiskLine is one device from /proc/diskstats with cumulative counters. +type DiskLine struct { + Device string + SectorsRead int64 // cumulative sectors read (each sector = 512 bytes) + SectorsWrite int64 // cumulative sectors written + ReadTicks int64 // cumulative ms spent reading + WriteTicks int64 // cumulative ms spent writing + IoTicks int64 // cumulative ms the device had I/O in progress +} |
