016f93ab4f
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
74 lines
2.3 KiB
Go
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)
|
|
}
|
|
}
|