feat: implement ControlTower TUI for cluster and host monitoring
Add complete TUI application for monitoring Kubernetes clusters and host systems. Features include: Core features: - Collector framework with concurrent scheduling - Host collectors: disk, memory, load, network - Kubernetes collectors: pods, nodes, workloads, events with informers - Issue deduplication, state management, and resolve-after logic - Bubble Tea TUI with table view, details pane, and filtering - JSON export functionality UX improvements: - Help overlay with keybindings - Priority/category filters with visual indicators - Direct priority jump (0/1/2/3) - Bulk acknowledge (Shift+A) - Clipboard copy (y) - Theme toggle (T) - Age format toggle (d) - Wide title toggle (t) - Vi-style navigation (j/k) - Home/End jump (g/G) - Rollup drill-down in details Robustness: - Grace period for unreachable clusters - Rollups for high-volume issues - Flap suppression - RBAC error handling Files: All core application code with tests for host collectors, engine, store, model, and export packages.
This commit is contained in:
80
internal/collectors/host/disk_test.go
Normal file
80
internal/collectors/host/disk_test.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package host
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseProcMounts_UnescapesAndParses(t *testing.T) {
|
||||
in := "dev1 / ext4 rw 0 0\n" +
|
||||
"dev2 /path\\040with\\040space xfs rw 0 0\n" +
|
||||
"badline\n"
|
||||
|
||||
ms := parseProcMounts(in)
|
||||
if len(ms) != 2 {
|
||||
t.Fatalf("expected 2 mounts, got %d", len(ms))
|
||||
}
|
||||
if ms[0].MountPoint != "/" || ms[0].FSType != "ext4" {
|
||||
t.Fatalf("unexpected first mount: %+v", ms[0])
|
||||
}
|
||||
if ms[1].MountPoint != "/path with space" {
|
||||
t.Fatalf("expected unescaped mountpoint, got %q", ms[1].MountPoint)
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldSkipMount_FiltersPseudo(t *testing.T) {
|
||||
cases := []procMount{
|
||||
{MountPoint: "/proc", FSType: "proc"},
|
||||
{MountPoint: "/sys", FSType: "sysfs"},
|
||||
{MountPoint: "/dev", FSType: "tmpfs"},
|
||||
{MountPoint: "/dev/shm", FSType: "tmpfs"},
|
||||
}
|
||||
for _, c := range cases {
|
||||
if !shouldSkipMount(c) {
|
||||
t.Fatalf("expected skip for %+v", c)
|
||||
}
|
||||
}
|
||||
if shouldSkipMount(procMount{MountPoint: "/home", FSType: "ext4"}) {
|
||||
t.Fatalf("did not expect skip for /home ext4")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDiskPriority(t *testing.T) {
|
||||
if p, ok := diskPriority(91.9, -1); ok {
|
||||
t.Fatalf("expected no issue, got %v", p)
|
||||
}
|
||||
if p, ok := diskPriority(92.0, -1); !ok || p != "P1" {
|
||||
t.Fatalf("expected P1 at 92%%, got %v ok=%v", p, ok)
|
||||
}
|
||||
if p, ok := diskPriority(97.9, 98.0); !ok || p != "P0" {
|
||||
t.Fatalf("expected P0 if either crosses 98%%, got %v ok=%v", p, ok)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStatfsCalculations(t *testing.T) {
|
||||
st := syscall.Statfs_t{}
|
||||
st.Bsize = 1
|
||||
st.Blocks = 100
|
||||
st.Bfree = 8
|
||||
st.Bavail = 8
|
||||
|
||||
pct, free := statfsBlockUsedPct(st)
|
||||
if free != 8 {
|
||||
t.Fatalf("expected free=8 bytes, got %d", free)
|
||||
}
|
||||
if pct < 91.9 || pct > 92.1 {
|
||||
t.Fatalf("expected ~92%% used, got %f", pct)
|
||||
}
|
||||
|
||||
st.Files = 100
|
||||
st.Ffree = 2
|
||||
ipct := statfsInodeUsedPct(st)
|
||||
if ipct < 97.9 || ipct > 98.1 {
|
||||
t.Fatalf("expected ~98%% inode used, got %f", ipct)
|
||||
}
|
||||
|
||||
st.Files = 0
|
||||
if statfsInodeUsedPct(st) != -1 {
|
||||
t.Fatalf("expected -1 when inode info unavailable")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user