diff options
| author | Paul Buetow <paul@buetow.org> | 2026-02-24 08:38:19 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-02-24 08:38:19 +0200 |
| commit | b01e24374398eb3d343e9472f3262668039db56c (patch) | |
| tree | 139a9e02946f635adaeedb8a61fa150c874c17ff /internal/tui/dashboard/syscalls.go | |
| parent | 24b401ac9c6a1f80b5ba7f446f1fd3e3ddf02b5c (diff) | |
tui: add dashboard syscalls table tab
Diffstat (limited to 'internal/tui/dashboard/syscalls.go')
| -rw-r--r-- | internal/tui/dashboard/syscalls.go | 132 |
1 files changed, 132 insertions, 0 deletions
diff --git a/internal/tui/dashboard/syscalls.go b/internal/tui/dashboard/syscalls.go new file mode 100644 index 0000000..f25781e --- /dev/null +++ b/internal/tui/dashboard/syscalls.go @@ -0,0 +1,132 @@ +package dashboard + +import ( + "fmt" + "ior/internal/statsengine" + "sort" + "strconv" + "time" + + "github.com/charmbracelet/bubbles/table" +) + +func renderSyscalls(snap *statsengine.Snapshot, width, height int) string { + return renderSyscallsWithOffset(snap, width, height, 0) +} + +func renderSyscallsWithOffset(snap *statsengine.Snapshot, width, height, offset int) string { + if snap == nil { + return "Syscalls: waiting for stats..." + } + + rows := syscallRows(snap.Syscalls()) + if len(rows) == 0 { + return "Syscalls: no data" + } + + columns := []table.Column{ + {Title: "Syscall", Width: 16}, + {Title: "Count", Width: 8}, + {Title: "Rate/s", Width: 8}, + {Title: "Avg", Width: 9}, + {Title: "Min", Width: 9}, + {Title: "Max", Width: 9}, + {Title: "p50", Width: 9}, + {Title: "p95", Width: 9}, + {Title: "p99", Width: 9}, + {Title: "Bytes", Width: 10}, + {Title: "Errors", Width: 8}, + } + + tbl := table.New( + table.WithColumns(columns), + table.WithRows(rows), + table.WithFocused(true), + ) + tbl.SetHeight(syscallTableHeight(height)) + tbl.SetWidth(tableWidth(width)) + tbl.SetCursor(clampOffset(offset, len(rows))) + return tbl.View() +} + +func syscallRows(syscalls []statsengine.SyscallSnapshot) []table.Row { + ordered := append([]statsengine.SyscallSnapshot(nil), syscalls...) + sort.SliceStable(ordered, func(i, j int) bool { + if ordered[i].Count == ordered[j].Count { + return ordered[i].Name < ordered[j].Name + } + return ordered[i].Count > ordered[j].Count + }) + + rows := make([]table.Row, 0, len(ordered)) + for _, s := range ordered { + rows = append(rows, table.Row{ + s.Name, + strconv.FormatUint(s.Count, 10), + fmt.Sprintf("%.1f", s.RatePerSec), + formatDurationNs(s.LatencyMeanNs), + formatDurationUintNs(s.LatencyMinNs), + formatDurationUintNs(s.LatencyMaxNs), + formatDurationUintNs(s.LatencyP50Ns), + formatDurationUintNs(s.LatencyP95Ns), + formatDurationUintNs(s.LatencyP99Ns), + formatBytes(float64(s.Bytes)), + strconv.FormatUint(s.Errors, 10), + }) + } + return rows +} + +func formatDurationUintNs(v uint64) string { + return formatDurationNs(float64(v)) +} + +func formatDurationNs(v float64) string { + if v < 1000 { + return fmt.Sprintf("%.0fns", v) + } + us := v / 1000 + if us < 1000 { + return fmt.Sprintf("%.1fµs", us) + } + ms := us / 1000 + if ms < 1000 { + return fmt.Sprintf("%.1fms", ms) + } + s := ms / 1000 + return (time.Duration(s * float64(time.Second))).String() +} + +func syscallTableHeight(height int) int { + if height <= 0 { + return 10 + } + h := height - 6 + if h < 5 { + return 5 + } + return h +} + +func tableWidth(width int) int { + if width <= 0 { + return 80 + } + if width < 60 { + return 60 + } + return width +} + +func clampOffset(offset, size int) int { + if size == 0 { + return 0 + } + if offset < 0 { + return 0 + } + if offset >= size { + return size - 1 + } + return offset +} |
