From 98e57cb9f845157fce1fccfea5d7e8fcaf962bd5 Mon Sep 17 00:00:00 2001 From: Steven Miller Date: Fri, 30 Jan 2026 16:55:57 -0500 Subject: [PATCH] Add disk io to resources output --- go.mod | 2 +- go.sum | 4 ++-- pkg/cmd/resourcecmd.go | 37 +++++++++++++++++++++++++++++++++--- pkg/cmd/resourcecmd_test.go | 38 ++++++++++++++++++++++++++++++++++--- 4 files changed, 72 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 700d094..e48cf2b 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/google/go-containerregistry v0.20.7 github.com/gorilla/websocket v1.5.3 github.com/itchyny/json2yaml v0.1.4 - github.com/kernel/hypeman-go v0.9.3 + github.com/kernel/hypeman-go v0.9.6 github.com/muesli/reflow v0.3.0 github.com/stretchr/testify v1.11.1 github.com/tidwall/gjson v1.18.0 diff --git a/go.sum b/go.sum index 9b21f1a..fe414c4 100644 --- a/go.sum +++ b/go.sum @@ -74,8 +74,8 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnV github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= github.com/itchyny/json2yaml v0.1.4 h1:/pErVOXGG5iTyXHi/QKR4y3uzhLjGTEmmJIy97YT+k8= github.com/itchyny/json2yaml v0.1.4/go.mod h1:6iudhBZdarpjLFRNj+clWLAkGft+9uCcjAZYXUH9eGI= -github.com/kernel/hypeman-go v0.9.3 h1:UtlynELXJJ9Znnuq5mLWXCc1ymPh7PPYamEd6fb3UXM= -github.com/kernel/hypeman-go v0.9.3/go.mod h1:guRrhyP9QW/ebUS1UcZ0uZLLJeGAAhDNzSi68U4M9hI= +github.com/kernel/hypeman-go v0.9.6 h1:gkKbUiTYPWVDa9GwX/xaf9+z+eiTYIj6oglPlir4Xbo= +github.com/kernel/hypeman-go v0.9.6/go.mod h1:guRrhyP9QW/ebUS1UcZ0uZLLJeGAAhDNzSi68U4M9hI= github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co= github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= diff --git a/pkg/cmd/resourcecmd.go b/pkg/cmd/resourcecmd.go index 63ed599..fe1495f 100644 --- a/pkg/cmd/resourcecmd.go +++ b/pkg/cmd/resourcecmd.go @@ -71,6 +71,7 @@ func showResourcesTable(data []byte) error { printResourceRow("cpu", obj.Get("cpu"), "cores") printResourceRow("memory", obj.Get("memory"), "bytes") printResourceRow("disk", obj.Get("disk"), "bytes") + printResourceRow("disk_io", obj.Get("disk_io"), "disk_bps") printResourceRow("network", obj.Get("network"), "bps") // Print GPU information if available @@ -104,8 +105,8 @@ func showResourcesTable(data []byte) error { if allocations.Exists() && allocations.IsArray() && len(allocations.Array()) > 0 { fmt.Println() fmt.Println("ALLOCATIONS:") - fmt.Println("INSTANCE CPU MEMORY DISK NET DOWN NET UP") - fmt.Println(strings.Repeat("-", 80)) + fmt.Println("INSTANCE CPU MEMORY DISK DISK I/O NET DOWN NET UP") + fmt.Println(strings.Repeat("-", 95)) allocations.ForEach(func(key, value gjson.Result) bool { name := value.Get("instance_name").String() if len(name) > 28 { @@ -114,9 +115,10 @@ func showResourcesTable(data []byte) error { cpu := value.Get("cpu").Int() mem := formatBytes(value.Get("memory_bytes").Int()) disk := formatBytes(value.Get("disk_bytes").Int()) + diskIO := formatDiskBps(value.Get("disk_io_bps").Int()) netDown := formatBps(value.Get("network_download_bps").Int()) netUp := formatBps(value.Get("network_upload_bps").Int()) - fmt.Printf("%-28s %3d %-9s %-9s %-10s %s\n", name, cpu, mem, disk, netDown, netUp) + fmt.Printf("%-28s %3d %-9s %-9s %-10s %-10s %s\n", name, cpu, mem, disk, diskIO, netDown, netUp) return true }) } @@ -148,6 +150,11 @@ func printResourceRow(name string, res gjson.Result, unit string) { effStr = formatBps(effective) allocStr = formatBps(allocated) availStr = formatBps(available) + case "disk_bps": + capStr = formatDiskBps(capacity) + effStr = formatDiskBps(effective) + allocStr = formatDiskBps(allocated) + availStr = formatDiskBps(available) default: capStr = fmt.Sprintf("%d", capacity) effStr = fmt.Sprintf("%d", effective) @@ -257,3 +264,27 @@ func formatBps(bytesPerSec int64) string { return fmt.Sprintf("%d bps", bps) } } + +// formatDiskBps formats disk I/O bandwidth in bytes per second to a human-readable +// string (KB/s, MB/s, GB/s). Unlike network bandwidth which uses bits, disk I/O +// is conventionally displayed in bytes per second. +func formatDiskBps(bytesPerSec int64) string { + const ( + KBps = 1000 + MBps = KBps * 1000 + GBps = MBps * 1000 + ) + + switch { + case bytesPerSec >= GBps: + return fmt.Sprintf("%.1f GB/s", float64(bytesPerSec)/GBps) + case bytesPerSec >= MBps: + return fmt.Sprintf("%.0f MB/s", float64(bytesPerSec)/MBps) + case bytesPerSec >= KBps: + return fmt.Sprintf("%.0f KB/s", float64(bytesPerSec)/KBps) + case bytesPerSec == 0: + return "-" + default: + return fmt.Sprintf("%d B/s", bytesPerSec) + } +} diff --git a/pkg/cmd/resourcecmd_test.go b/pkg/cmd/resourcecmd_test.go index 43a9e2e..4882adb 100644 --- a/pkg/cmd/resourcecmd_test.go +++ b/pkg/cmd/resourcecmd_test.go @@ -53,9 +53,9 @@ func TestFormatBps(t *testing.T) { // The CLI should convert to bits and display as Mbps/Gbps // Formula: bytes/sec * 8 = bits/sec tests := []struct { - name string - bytesPerSec int64 - expected string + name string + bytesPerSec int64 + expected string }{ // 30 Mbps = 30,000,000 bits/sec = 3,750,000 bytes/sec // This is the user's reported bug: they set 30Mbps, API stores 3750000 bytes/sec, @@ -94,3 +94,35 @@ func TestFormatBps(t *testing.T) { }) } } + +func TestFormatDiskBps(t *testing.T) { + // Disk I/O is displayed in bytes/sec (KB/s, MB/s, GB/s), not bits/sec + tests := []struct { + name string + bytesPerSec int64 + expected string + }{ + // Common disk I/O limits + {"100 MB/s SSD limit", 100000000, "100 MB/s"}, + {"500 MB/s NVMe limit", 500000000, "500 MB/s"}, + {"1 GB/s high-perf limit", 1000000000, "1.0 GB/s"}, + {"3.5 GB/s NVMe Gen4", 3500000000, "3.5 GB/s"}, + + // Smaller values + {"50 MB/s HDD limit", 50000000, "50 MB/s"}, + {"10 MB/s throttled", 10000000, "10 MB/s"}, + {"1 MB/s minimal", 1000000, "1 MB/s"}, + {"500 KB/s very slow", 500000, "500 KB/s"}, + + // Edge cases + {"zero (no limit or disabled)", 0, "-"}, + {"tiny value", 500, "500 B/s"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := formatDiskBps(tt.bytesPerSec) + assert.Equal(t, tt.expected, result) + }) + } +}