Skip to content

Commit 8c53923

Browse files
committed
fix(status): improve disk card display refs #551
1 parent 24da1e2 commit 8c53923

File tree

4 files changed

+203
-28
lines changed

4 files changed

+203
-28
lines changed

cmd/status/metrics_disk.go

Lines changed: 46 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,23 @@ var skipDiskMounts = map[string]bool{
2222
"/dev": true,
2323
}
2424

25+
var skipDiskFSTypes = map[string]bool{
26+
"afpfs": true,
27+
"autofs": true,
28+
"cifs": true,
29+
"devfs": true,
30+
"fuse": true,
31+
"fuseblk": true,
32+
"fusefs": true,
33+
"macfuse": true,
34+
"nfs": true,
35+
"osxfuse": true,
36+
"procfs": true,
37+
"smbfs": true,
38+
"tmpfs": true,
39+
"webdav": true,
40+
}
41+
2542
func collectDisks() ([]DiskStatus, error) {
2643
partitions, err := disk.Partitions(false)
2744
if err != nil {
@@ -34,17 +51,7 @@ func collectDisks() ([]DiskStatus, error) {
3451
seenVolume = make(map[string]bool)
3552
)
3653
for _, part := range partitions {
37-
if strings.HasPrefix(part.Device, "/dev/loop") {
38-
continue
39-
}
40-
if skipDiskMounts[part.Mountpoint] {
41-
continue
42-
}
43-
if strings.HasPrefix(part.Mountpoint, "/System/Volumes/") {
44-
continue
45-
}
46-
// Skip /private mounts.
47-
if strings.HasPrefix(part.Mountpoint, "/private/") {
54+
if shouldSkipDiskPartition(part) {
4855
continue
4956
}
5057
baseDevice := baseDeviceName(part.Device)
@@ -97,6 +104,34 @@ func collectDisks() ([]DiskStatus, error) {
97104
return disks, nil
98105
}
99106

107+
func shouldSkipDiskPartition(part disk.PartitionStat) bool {
108+
if strings.HasPrefix(part.Device, "/dev/loop") {
109+
return true
110+
}
111+
if skipDiskMounts[part.Mountpoint] {
112+
return true
113+
}
114+
if strings.HasPrefix(part.Mountpoint, "/System/Volumes/") {
115+
return true
116+
}
117+
if strings.HasPrefix(part.Mountpoint, "/private/") {
118+
return true
119+
}
120+
121+
fstype := strings.ToLower(part.Fstype)
122+
if skipDiskFSTypes[fstype] || strings.Contains(fstype, "fuse") {
123+
return true
124+
}
125+
126+
// On macOS, local disks should come from /dev. This filters sshfs/macFUSE-style
127+
// mounts that can mirror the root volume and show up as duplicate internal disks.
128+
if runtime.GOOS == "darwin" && part.Device != "" && !strings.HasPrefix(part.Device, "/dev/") {
129+
return true
130+
}
131+
132+
return false
133+
}
134+
100135
var (
101136
// External disk cache.
102137
lastDiskCacheAt time.Time

cmd/status/metrics_disk_test.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package main
2+
3+
import (
4+
"testing"
5+
6+
"github.com/shirou/gopsutil/v4/disk"
7+
)
8+
9+
func TestShouldSkipDiskPartition(t *testing.T) {
10+
tests := []struct {
11+
name string
12+
part disk.PartitionStat
13+
want bool
14+
}{
15+
{
16+
name: "keep local apfs root volume",
17+
part: disk.PartitionStat{
18+
Device: "/dev/disk3s1s1",
19+
Mountpoint: "/",
20+
Fstype: "apfs",
21+
},
22+
want: false,
23+
},
24+
{
25+
name: "skip macfuse mirror mount",
26+
part: disk.PartitionStat{
27+
Device: "kaku-local:/",
28+
Mountpoint: "/Users/tw93/Library/Caches/dev.kaku/sshfs/kaku-local",
29+
Fstype: "macfuse",
30+
},
31+
want: true,
32+
},
33+
{
34+
name: "skip smb share",
35+
part: disk.PartitionStat{
36+
Device: "//server/share",
37+
Mountpoint: "/Volumes/share",
38+
Fstype: "smbfs",
39+
},
40+
want: true,
41+
},
42+
{
43+
name: "skip system volume",
44+
part: disk.PartitionStat{
45+
Device: "/dev/disk3s5",
46+
Mountpoint: "/System/Volumes/Data",
47+
Fstype: "apfs",
48+
},
49+
want: true,
50+
},
51+
}
52+
53+
for _, tt := range tests {
54+
t.Run(tt.name, func(t *testing.T) {
55+
if got := shouldSkipDiskPartition(tt.part); got != tt.want {
56+
t.Fatalf("shouldSkipDiskPartition(%+v) = %v, want %v", tt.part, got, tt.want)
57+
}
58+
})
59+
}
60+
}

cmd/status/view.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,8 @@ func renderDiskCard(disks []DiskStatus, io DiskIOStatus) cardData {
365365
addGroup("EXTR", external)
366366
if len(lines) == 0 {
367367
lines = append(lines, subtleStyle.Render("No disks detected"))
368+
} else if len(disks) == 1 {
369+
lines = append(lines, formatDiskMetaLine(disks[0]))
368370
}
369371
}
370372
readBar := ioBar(io.ReadRate)
@@ -398,8 +400,19 @@ func formatDiskLine(label string, d DiskStatus) string {
398400
}
399401
bar := progressBar(d.UsedPercent)
400402
used := humanBytesShort(d.Used)
401-
total := humanBytesShort(d.Total)
402-
return fmt.Sprintf("%-6s %s %5.1f%%, %s/%s", label, bar, d.UsedPercent, used, total)
403+
free := uint64(0)
404+
if d.Total > d.Used {
405+
free = d.Total - d.Used
406+
}
407+
return fmt.Sprintf("%-6s %s %s used, %s free", label, bar, used, humanBytesShort(free))
408+
}
409+
410+
func formatDiskMetaLine(d DiskStatus) string {
411+
parts := []string{humanBytesShort(d.Total)}
412+
if d.Fstype != "" {
413+
parts = append(parts, strings.ToUpper(d.Fstype))
414+
}
415+
return fmt.Sprintf("Total %s", strings.Join(parts, " · "))
403416
}
404417

405418
func ioBar(rate float64) string {

cmd/status/view_test.go

Lines changed: 82 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -749,29 +749,52 @@ func TestMiniBar(t *testing.T) {
749749

750750
func TestFormatDiskLine(t *testing.T) {
751751
tests := []struct {
752-
name string
753-
label string
754-
disk DiskStatus
752+
name string
753+
label string
754+
disk DiskStatus
755+
wantUsed string
756+
wantFree string
757+
wantNoSubstr string
755758
}{
756759
{
757-
name: "empty label defaults to DISK",
758-
label: "",
759-
disk: DiskStatus{UsedPercent: 50.5, Used: 100 << 30, Total: 200 << 30},
760+
name: "empty label defaults to DISK",
761+
label: "",
762+
disk: DiskStatus{UsedPercent: 50.5, Used: 100 << 30, Total: 200 << 30},
763+
wantUsed: "100G used",
764+
wantFree: "100G free",
765+
wantNoSubstr: "%",
760766
},
761767
{
762-
name: "internal disk",
763-
label: "INTR",
764-
disk: DiskStatus{UsedPercent: 67.2, Used: 336 << 30, Total: 500 << 30},
768+
name: "internal disk",
769+
label: "INTR",
770+
disk: DiskStatus{UsedPercent: 67.2, Used: 336 << 30, Total: 500 << 30},
771+
wantUsed: "336G used",
772+
wantFree: "164G free",
773+
wantNoSubstr: "%",
765774
},
766775
{
767-
name: "external disk",
768-
label: "EXTR1",
769-
disk: DiskStatus{UsedPercent: 85.0, Used: 850 << 30, Total: 1000 << 30},
776+
name: "external disk",
777+
label: "EXTR1",
778+
disk: DiskStatus{UsedPercent: 85.0, Used: 850 << 30, Total: 1000 << 30},
779+
wantUsed: "850G used",
780+
wantFree: "150G free",
781+
wantNoSubstr: "%",
770782
},
771783
{
772-
name: "low usage",
773-
label: "INTR",
774-
disk: DiskStatus{UsedPercent: 15.3, Used: 15 << 30, Total: 100 << 30},
784+
name: "low usage",
785+
label: "INTR",
786+
disk: DiskStatus{UsedPercent: 15.3, Used: 15 << 30, Total: 100 << 30},
787+
wantUsed: "15G used",
788+
wantFree: "85G free",
789+
wantNoSubstr: "%",
790+
},
791+
{
792+
name: "used exceeds total clamps free to zero",
793+
label: "INTR",
794+
disk: DiskStatus{UsedPercent: 110.0, Used: 110 << 30, Total: 100 << 30},
795+
wantUsed: "110G used",
796+
wantFree: "0 free",
797+
wantNoSubstr: "%",
775798
},
776799
}
777800

@@ -789,10 +812,54 @@ func TestFormatDiskLine(t *testing.T) {
789812
if !contains(got, expectedLabel) {
790813
t.Errorf("formatDiskLine(%q, ...) = %q, should contain label %q", tt.label, got, expectedLabel)
791814
}
815+
if !contains(got, tt.wantUsed) {
816+
t.Errorf("formatDiskLine(%q, ...) = %q, should contain used value %q", tt.label, got, tt.wantUsed)
817+
}
818+
if !contains(got, tt.wantFree) {
819+
t.Errorf("formatDiskLine(%q, ...) = %q, should contain free value %q", tt.label, got, tt.wantFree)
820+
}
821+
if tt.wantNoSubstr != "" && contains(got, tt.wantNoSubstr) {
822+
t.Errorf("formatDiskLine(%q, ...) = %q, should not contain %q", tt.label, got, tt.wantNoSubstr)
823+
}
792824
})
793825
}
794826
}
795827

828+
func TestRenderDiskCardAddsMetaLineForSingleDisk(t *testing.T) {
829+
card := renderDiskCard([]DiskStatus{{
830+
UsedPercent: 28.4,
831+
Used: 263 << 30,
832+
Total: 926 << 30,
833+
Fstype: "apfs",
834+
}}, DiskIOStatus{ReadRate: 0, WriteRate: 0.1})
835+
836+
if len(card.lines) != 4 {
837+
t.Fatalf("renderDiskCard() single disk expected 4 lines, got %d", len(card.lines))
838+
}
839+
840+
meta := stripANSI(card.lines[1])
841+
if meta != "Total 926G · APFS" {
842+
t.Fatalf("renderDiskCard() single disk meta line = %q, want %q", meta, "Total 926G · APFS")
843+
}
844+
}
845+
846+
func TestRenderDiskCardDoesNotAddMetaLineForMultipleDisks(t *testing.T) {
847+
card := renderDiskCard([]DiskStatus{
848+
{UsedPercent: 28.4, Used: 263 << 30, Total: 926 << 30, Fstype: "apfs"},
849+
{UsedPercent: 50.0, Used: 500 << 30, Total: 1000 << 30, Fstype: "apfs"},
850+
}, DiskIOStatus{})
851+
852+
if len(card.lines) != 4 {
853+
t.Fatalf("renderDiskCard() multiple disks expected 4 lines, got %d", len(card.lines))
854+
}
855+
856+
for _, line := range card.lines {
857+
if stripANSI(line) == "Total 926G · APFS" || stripANSI(line) == "Total 1000G · APFS" {
858+
t.Fatalf("renderDiskCard() multiple disks should not add meta line, got %q", line)
859+
}
860+
}
861+
}
862+
796863
func TestGetScoreStyle(t *testing.T) {
797864
tests := []struct {
798865
name string

0 commit comments

Comments
 (0)