summaryrefslogtreecommitdiff
path: root/internal/collector
diff options
context:
space:
mode:
Diffstat (limited to 'internal/collector')
-rw-r--r--internal/collector/collector.go5
-rw-r--r--internal/collector/parse.go30
-rw-r--r--internal/collector/parse_test.go43
-rw-r--r--internal/collector/protocol.go3
-rw-r--r--internal/collector/scriptdata/loadbars-remote.sh10
-rw-r--r--internal/collector/types.go10
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
+}