Files
benvin-utils/podgap/main.go
T
Ben Vin 016f93ab4f Initial benvin-utils monorepo with podgap tool
Add a monorepo for small single-purpose CLI tools that share a common
library, so tools reuse TUI primitives and Kubernetes client bootstrapping
instead of duplicating them.

- common/tui: direction markers, style palette, width-aware padding/layout
- common/kube: in-cluster→kubeconfig client loading + core/metrics clients
- podgap: live TUI comparing pod requests/limits vs actual usage, with
  up/down markers showing per-pod change since the last refresh
- Makefile auto-discovers tool directories: make <tool> | all | install
2026-07-12 22:04:29 +10:00

74 lines
2.3 KiB
Go

// Command podgap shows the gap between what Kubernetes pods reserve
// (requests/limits) and what they actually use, live in a terminal UI.
//
// The default mode opens an interactive TUI that refreshes in place and marks
// whether each pod's usage rose or fell since the last refresh. The json/csv
// output modes are one-shot dumps for scripting.
package main
import (
"context"
"fmt"
"os"
"time"
"git.unkin.net/unkin/benvin-utils/common/kube"
"github.com/spf13/pflag"
)
type options struct {
namespace string
sortKey string
top int
interval time.Duration
output string
kubeconfig string
overOnly bool
underOnly bool
threshold float64
}
func main() {
opts := options{}
pflag.StringVarP(&opts.namespace, "namespace", "n", "", "namespace to inspect (default: all namespaces)")
pflag.StringVarP(&opts.sortKey, "sort", "s", "cpu-waste", "sort by: cpu-waste, mem-waste, cpu-util, mem-util, cpu-use, mem-use, name")
pflag.IntVar(&opts.top, "top", 0, "show only the top N rows after sorting (0 = all)")
pflag.DurationVarP(&opts.interval, "interval", "i", 0, "TUI refresh interval (e.g. 5s); 0 = default 3s")
pflag.StringVarP(&opts.output, "output", "o", "tui", "output: tui (interactive), json, csv")
pflag.StringVar(&opts.kubeconfig, "kubeconfig", "", "path to kubeconfig (default: $KUBECONFIG or ~/.kube/config)")
pflag.BoolVar(&opts.overOnly, "over", false, "show only over-provisioned pods (util below --threshold)")
pflag.BoolVar(&opts.underOnly, "under", false, "show only under-provisioned pods (usage above request or limit)")
pflag.Float64Var(&opts.threshold, "threshold", 0.5, "utilization fraction below which a pod is 'over-provisioned' (with --over)")
pflag.Parse()
if err := run(opts); err != nil {
fmt.Fprintln(os.Stderr, "error:", err)
os.Exit(1)
}
}
func run(opts options) error {
core, mc, err := kube.Clients(opts.kubeconfig)
if err != nil {
return err
}
// json/csv are one-shot, script-friendly dumps; anything else opens the TUI.
switch opts.output {
case "json", "csv":
rows, err := collect(context.Background(), core, mc, opts.namespace)
if err != nil {
return err
}
rows = filterRows(rows, opts)
sortRows(rows, opts.sortKey)
if opts.top > 0 && len(rows) > opts.top {
rows = rows[:opts.top]
}
return render(os.Stdout, rows, opts.output)
default:
return runTUI(core, mc, opts)
}
}