Skip to content

feat(scan): redesigned, trustworthy scan UX — cards, full distribution, OOM-safe memory, concurrency#16

Merged
mayur-tolexo merged 8 commits into
mainfrom
feat/usage-distribution-explain
Jun 2, 2026
Merged

feat(scan): redesigned, trustworthy scan UX — cards, full distribution, OOM-safe memory, concurrency#16
mayur-tolexo merged 8 commits into
mainfrom
feat/usage-distribution-explain

Conversation

@mayur-tolexo

@mayur-tolexo mayur-tolexo commented Jun 1, 2026

Copy link
Copy Markdown
Owner

A end-to-end overhaul of the scan experience, driven by live real-cluster feedback. Every recommendation is now self-explanatory, memory recommendations are OOM-safe, and the scan is fast and quiet.

Output: a card per recommendation

kubetidy · kind-kubetidy

  Efficiency  35/100  ▇▇▇░░░░░░░
  Waste       $117/mo  potential savings · 3 workloads to rightsize
  Source      Prometheus (Tier 1) · 1h history, ~12 samples/workload

▸ search-indexer · Deployment/kubetidy-demo   save $52/mo · ▒ low 31%
  ┌─────┬───────┬───────┬───────┬───────┬────────────────────┐
  │     │ avg   │ p95   │ p99   │ peak  │ request → proposed │
  ├─────┼───────┼───────┼───────┼───────┼────────────────────┤
  │ cpu │ 30m   │ 30m   │ 30m   │ 30m   │ 2000m → 35m        │
  │ mem │ 209Mi │ 224Mi │ 224Mi │ 224Mi │ 2Gi → 479Mi        │
  └─────┴───────┴───────┴───────┴───────┴────────────────────┘
  basis  1h history · 12 samples · memory kept conservative (short history — extra OOM safety)
  • Full usage distribution per recommendation — avg / p95 / p99 / peak for CPU and memory — in a bordered table, beside the request → proposed change. The long workload name sits on its own line.
  • --explain reuses the same bordered table + a clean key/value block ("how it's sized").
  • Adaptive columns: avg/p99 show only when there's data, so an older operator (p95/peak only) renders a clean table instead of a column of .

Memory is OOM-safe

Memory is the dangerous resource and a short window can miss the true peak. The memory headroom now scales inversely with data maturity: a young history keeps a large cushion (up to ~peak×2), tightening to peak+15% only once a representative window of samples backs it. CPU stays lean (it only throttles). New Policy.MemoryImmatureSafety; maturity shared with the confidence model.

Honest confidence

Data-maturity-gated score shown as a band + % (▒ low 34%); a fresh operator reads low, not a false 85%. The data: tier reflects what actually backed the findings.

Fast & quiet

  • Concurrent scan (bounded worker pool) — ~10× faster on a large cluster; the spinner shows live progress (it was previously never updated).
  • Geeky progress while it runs: ⟦▰▰▰▰▰▱▱▱▱▱⟧ 51/102 · pricing the waste in dollars….
  • Raised client-go QPS/burst and silenced klog, so internal rate-limit logs no longer pollute output.

Plumbing

model.Percentiles gains Avg+P99; model.Recommendation carries the observed Usage. Providers populate them — Prometheus (avg_over_time + quantile_over_time(0.99)), operator histogram (Mean() + p99; UsageProfile CRD MetricHistory gains additive avg/p99, regenerated — operator redeploy needed to populate), snapshot (single reading). Sub-hour windows render as 12m.

Docs & demo

README (rightsizing policy + trust), CHANGELOG.md (v0.1.2), ARCHITECTURE.md updated. Demo workloads are now tiny load generators (real cpu+mem) and the README GIF is re-recorded against the new UX.

Tests

report ~92%, rightsizer ~91%, scan ~85%, model 100%; race-tested concurrency; full go build/vet/gofmt/test ./... + golangci-lint (0 issues) green.

Builds on released v0.1.1 — candidate for v0.1.2.

…D + rich --explain

Real-cluster feedback: with no per-row detail you couldn't tell WHY a reduction
was recommended; with the dense distribution under every row the table was a wall
of text. This makes every row self-explanatory while staying clean.

Table (clean, one line per workload):

  WORKLOAD                 REQUESTED  USES       PROPOSED   SAVE    CONF
  neevai-rackbank-console  2000m/4Gi  13m/147Mi  15m/170Mi  $60/mo  ▒ low

Reads left-to-right: asks 2000m/4Gi, actually uses 13m/147Mi → propose 15m/170Mi.
USES is the p95 cpu / peak mem the new request is sized to. No per-row evidence
wall; a footer points to --explain.

--explain gains a "why this recommendation" block: requested vs the full observed
distribution (avg/p95/p99/peak for cpu & mem) vs proposed, plus a verdict
("~133× over-allocated on cpu"), then savings/confidence/tier/derivation.

Plumbing to support it:
- model.Percentiles gains Avg + P99; model.Recommendation carries the observed Usage.
- histogram.Mean(); operator records avg/p99 (UsageProfile CRD MetricHistory gains
  avg/p99 — additive, regenerated via controller-gen; needs operator redeploy to
  populate, snapshot/Prometheus populate immediately).
- Prometheus adds avg_over_time + quantile_over_time(0.99); snapshot sets all stats
  to its single reading.
- robust display: an unpopulated stat renders "—", never a misleading "0m".

Coverage: report 92.6%, scan 85.6%, histogram 91.7%, model 100%, operator 84.8%.
Full build/vet/fmt/test/lint green.
…on-explain

# Conflicts:
#	internal/report/report.go
… USES header, per-row samples

Real-cluster feedback: the table showed a USES number with no indication of what
it was (p95? peak? avg?) or how much data backed it. Now it's self-documenting:
- banner: '... data: 0 (kubetidy operator) · observed over 48m' (the time span)
- header: 'USES (cpu p95 · mem peak)' names exactly what the proposal is sized to
- CONF column: '▒ low 31% · 31 smp' — the sample count behind each recommendation
Legend trimmed accordingly; full avg/p95/p99/peak still under --explain.
… tighter rows

Per real-cluster feedback: window and sample count are uniform across a scan, so
repeating '· 202 smp' on all 102 rows was clutter that widened the table.

- banner: '... data: 0 (kubetidy operator) · 5h history, ~202 samples/workload'
  (stated once; median sample count is robust to multi-pod outliers)
- CONF: back to a compact '▒ low 34%' (no per-row samples)
- USES header shortened (defined in the legend) to reclaim width
- exact per-workload samples remain in --explain

The table now carries only what varies per workload; provenance is in the header.
Two changes from real-cluster feedback.

1. UX — a flat table can't hold the full distribution (avg/p95/p99/peak for cpu
   AND mem) plus the change, so render one CARD per recommendation. The workload
   name goes on its own line (fixing long-name width), then an aligned block:

     ▸ neevai-rackbank-console · Deployment/neevai-system   save $60/mo · ▒ low 34%
            avg    p95    p99    peak   request → proposed
       cpu  1m     2m     2m     2m     2000m → 10m
       mem  140Mi  148Mi  149Mi  149Mi  4Gi → 311Mi
       basis  5h history · 201 samples · memory kept conservative (short history — extra OOM safety)

   Uniform provenance (window, typical samples) stays in the banner; cards show
   top-N with a "showing top N of M" note. --explain keeps the full derivation.

2. Safety — memory sized to peak+15% over a SHORT window can OOM (the window may
   not have seen the true peak). Memory headroom now scales inversely with data
   maturity: a young history keeps a large cushion (e.g. peak×2), shrinking to the
   base only once a representative window of samples backs it. CPU stays lean (it
   only throttles). New Policy.MemoryImmatureSafety (default +100%); maturity calc
   shared with the confidence model.

Coverage: report 92%+, rightsizer 90%+, model 100%. Full gate + lint green.
Per feedback, the borderless space-aligned block read ambiguously. Each card's
usage block is now a proper box-drawing table (grid lines), for both the operator
tier (avg/p95/p99/peak) and snapshot tier (now), e.g.:

  ┌─────┬───────┬───────┬───────┬───────┬────────────────────┐
  │     │ avg   │ p95   │ p99   │ peak  │ request → proposed │
  ├─────┼───────┼───────┼───────┼───────┼────────────────────┤
  │ cpu │ 1m    │ 2m    │ 2m    │ 2m    │ 2000m → 10m        │
  │ mem │ 140Mi │ 148Mi │ 149Mi │ 149Mi │ 4Gi → 311Mi        │
  └─────┴───────┴───────┴───────┴───────┴────────────────────┘

Column widths measured in runes so the ·/→ glyphs don't skew alignment.
…lumns

Real-cluster feedback on speed, noise, and empty columns.

- Concurrency: workloads are analyzed in a bounded worker pool (was a sequential
  loop), so a large cluster scans ~10× faster and the spinner shows live X/N
  progress instead of sitting frozen. (Progress was also never being called.)
- Quiet output: raise client-go QPS/Burst (50/100) so concurrent read-only
  queries aren't client-side throttled, and silence klog so internal k8s logs
  (e.g. rate-limit warnings) never pollute the CLI output.
- Adaptive columns: the avg/p99 distribution columns are shown only when there's
  data for them — an older operator that records just p95/peak now renders a clean
  table instead of a column of "—". They appear once the new operator/Prometheus
  provides them.
- --explain reuses the same bordered table + a clean key/value block ("how it's
  sized"), consistent with the scan cards.
- docs: README rightsizing policy + trust section updated; CHANGELOG v0.1.2 entry
  (cards, distribution, OOM-safe memory, concurrency); demo workloads are now tiny
  load generators (real cpu+mem) so the demo shows a real distribution.

Full gate + race + lint green.
…sh demo GIF

- Header: short banner (`kubetidy · <context>`) with the data source moved to an
  aligned hero "Source" line using a friendly tier name (e.g. "kubetidy operator
  (Tier 0, no Prometheus)") instead of the bare "0 (kubetidy operator)". Waste line
  now also carries the workload count.
- Adaptive columns: avg/p99 columns appear only when there's data, so an older
  operator (p95/peak only) renders a clean table, not a column of "—".
- Geeky progress: while the (now concurrent) scan runs, show a live bar + count +
  a phrase that advances with progress — "⟦▰▰▰▰▰▱▱▱▱▱⟧ 51/102 · pricing the waste…".
- Demo GIF re-recorded against the new card UX with real cpu+mem load generators.

Full gate + lint green.
@mayur-tolexo mayur-tolexo changed the title feat(scan): show WHY each recommendation — REQUESTED → USES → PROPOSED + rich --explain feat(scan): redesigned, trustworthy scan UX — cards, full distribution, OOM-safe memory, concurrency Jun 2, 2026
@mayur-tolexo
mayur-tolexo merged commit 87a21a4 into main Jun 2, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant