// 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) } }