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
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user