Skip to content

feat: verbose mode for the top summary panels — instance overview (010)#145

Merged
lesovsky merged 54 commits into
developfrom
feature/010-overview-dashboard
Jun 25, 2026
Merged

feat: verbose mode for the top summary panels — instance overview (010)#145
lesovsky merged 54 commits into
developfrom
feature/010-overview-dashboard

Conversation

@lesovsky

Copy link
Copy Markdown
Owner

Что

Добавляет режим verbose для двух верхних сводных панелей pgcenter top (sysstat слева / pgstat справа), переключаемый клавишей v. Compact — как сейчас (по 4 строки); verbose разворачивает панели: sysstat +3 строки (iostat / nicstat / filesyst) и pgstat +5 (workload / databases / workers / replication / bgwr-ckpt). Это режим, а не новый экран — переиспользует free-form Fprintf-путь рендеринга, persistent между экранами, держит расширенную сводку здоровья над любой таблицей.

Зачем

Две верхние панели — это уже мини-обзор инстанса; отдельный экран дублировал бы их (антипаттерн). Реальный пробел — панели по 4 строки и не показывают production-сигналы (насыщение диска/сети/ФС, агрегированную нагрузку, суммарный размер БД + рост + cache hit, насыщение воркеров, репликацию/слоты/архивный бэклог, стоимость чекпоинтов), которые сегодня требуют обхода сайд-панелей и экранов. Дифференциатор vs pg_activity: всё работает по сети через PL/Perl, и сводка едет с DBA по экранам.

Как

  • Новый файл top/verbose.go (toggle), top/layout.go (чистая topBandLayout + height-guard), internal/query/overview.go (агрегатные SQL).
  • view.View.Verbose + config.verbose; collectStat Reset-skip; verbose-gated all-three system collection (R1) с ==nil-guards, не трогая mutual-exclusion сайд-панелей.
  • verboseCollectState: first-tick n/a + cmdline collecting... с ре-армом на OFF→ON (без Reset()), latency-guard max(refresh/4, 500ms) (throttle только дорогих агрегатов без панель-двойника → stale, не n/a).
  • Новые форматтеры (ceil / reserved-digit / dynamic unit suffix MB/s→GB/s).
  • Консистентность с полными панелями (переиспользование %util/Pused/struct-математики).

Тесты / QA

  • 9 задач реализации в 5 волнах + Pre-deploy QA. Полный go test ./... зелёный (race+coverage, live PG 14–18, 0 FAIL/0 SKIP). gosec 0 issues, go vet/gofmt чисто. golangci-lint — в CI (v1-конфиг).
  • Каждая задача: TDD + ревью (code/security/test). Визуальная приёмка пройдена (геометрия, консистентность, first-tick/stale, формат-полировка).

Границы / отложено

  • Host IO/disk в verbose — variant A в v1; точный состав/резервы pgstat-полей — дорабатываются (MVP-курация).
  • record/report — не поддерживается (TUI-only, NotRecordable).
  • recovery-t WAL-шаблоны (standby) — проверены подстановкой, не live (нет standby-кластера, техдолг [006]).

Полная история решений — в docs/features/010-feat-overview-dashboard/.

🤖 Generated with Claude Code

Alexey Lesovsky and others added 30 commits June 23, 2026 23:02
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reframe from a separate overview screen to a verbose toggle of the
sysstat/pgstat top panels (no duplication antipattern). Full data
layout pinned (iostat/nicstat/filesyst + workload/databases/workers/
replication/bgwr-ckpt). R1 + tiering return; size L.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Apply non-blocking findings: weight the layout() geometry rework as the
invasive core (not the printers); clarify guard throttles only non-panel
aggregates (system rows stay fresh for consistency); note net-new GUC
reads; precise height-guard threshold; explicit integration-test rationale;
implementation-sequencing note.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Verbose-panel mode: 11 tasks across 3 waves + QA. Invasive core =
layout() geometry + all-three collection branch; no new view (zero
view-count test churn). 10 decisions, full data/test/risk plan.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Re-decompose into 5 file-disjoint waves (10 tasks): no two parallel
  tasks touch the same file; hot files sequenced across waves.
- Fold architect MAJOR: toggleVerbose skips collectStat Reset
  (prev.Verbose!=v.Verbose continue branch) — Decision 2 + Task 2.
- Security: archiving as own QueryRow (per-row n/a), EvalSymlinks
  robustness->n/a, no raw error text — Risks + Tasks 5/8.
- Decision 9: concrete guard threshold + verboseCollectState sub-struct.
- Trim rationale from task descriptions; add dev-security-auditor to
  the path/symlink task; field-layout note delegates to user-spec.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Resolve last minor: drop duplicate fsstat.go from Task 8 Files-to-read.
Wave conflicts + Reset-skip confirmed fixed; tech-spec-validator and
architect re-approved.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Task 05 depends_on ["04"]->["02","04"] (gates on view.Verbose).
- First-tick n/a no longer relies on nil slice (collect* sets prev=curr
  on mismatch): explicit verboseFirstTick flag in Task 07, consumed by
  Task 08, grouped into verboseCollectState + cmdline hint in Task 09.
- Minor: Task 01 Mbps divisor pointer -> top/stat.go:741; Task 02
  continue precedes both Resets; link conventions normalized.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
verboseFirstTick must re-arm on every verbose OFF->ON re-enable
(toggleVerbose skips Reset per Decision 2), else a re-enabled session
computes deltas vs a stale prev. Task 07 adds prevVerboseActive and
sets the flag when verbose runs and was off last tick (no Reset
dependency); Task 09 carries both fields into verboseCollectState and
re-shows the collecting hint. Minor: corrected connect-or-fail test
pattern wording.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…amic unit suffix)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Split printSysstat/printPgstat into thin *gocui.View wrappers that
delegate to writer-based renderSysstat/renderPgstat(w io.Writer, …),
mirroring the printDbstat → renderDbstat precedent. Compact output
stays byte-identical (golden tests on bytes.Buffer encode this).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extend SelectCommonProperties with five new reads (max_worker_processes,
max_logical_replication_workers, max_parallel_workers, wal_segment_size as
int64 bytes via pg_size_bytes, data_directory) and propagate them into
PostgresProperties via the GetPostgresProperties scan in lockstep order.
Extend TestGetPostgresProperties to cover the new fields.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
All three reviewers (code, security, test) approved round 1 with zero
critical/major findings.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wave 1 (01 formatters, 02 toggle plumbing, 03 printer refactor, 04 GUC
reads) implemented, reviewed, committed by teammates. Build + affected
test packages green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add the data layer for the five verbose pgstat panel rows (workload,
databases, workers, replication, bgwr/ckpt): version-aware aggregate
queries, flat structs, Go-side rates vs the previous snapshot, and wire
collection into Collector.Update gated on view.Verbose.

New internal/query/overview.go holds the workload/databases/workers/
replication aggregates; replication lag and slots are recovery-aware
templates run through Format/NewOptions. bgwr/ckpt reuses the existing
SelectStatBgwriterQuery (PG14-16/17/18 layout) with no new SQL.

PgstatOverview + collectOverviewStat (internal/stat/postgres.go) run each
privileged/expensive aggregate (database-size sum, archiving backlog) as
its OWN QueryRow so a 42501 privilege error or archive_mode=off degrades
just that field to n/a via an availability flag, without aborting the
sample or surfacing raw PG error text. cache hit ratio is per-interval
(Δhit/Δ(hit+read)) with division-by-zero -> n/a; tps = commit+rollback;
others is an interval delta.

Live-PG tests cover PG 14-18, template recovery substitution, archiving
backlog degradation, and the verbose on/off gate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Code review (critical): split the databases aggregate so the expensive
sum(pg_database_size) runs as its OWN QueryRow (OverviewDatabasesSize),
matching AC/Decision 6/10. A size failure now degrades only TotalSize/
GrowthPerSec to n/a while count and the per-interval cache-hit ratio stay
populated; the previous combined query would have cascaded. Corrected the
inline comment that falsely claimed the split already existed.

Extracted cacheHitRatio() as a pure helper (deterministic n/a on
Δ(hit+read) <= 0, never NaN) and dropped redundant `_ = err` noise.

Test review:
- query tests now QueryRow().Scan(...) into the exact receivers
  collectOverviewStat uses, so column-count/type drift fails here, not at
  runtime; added OverviewDatabasesSize coverage.
- replaced tautological tps/others `>= 0` checks with an exact-formula
  assertion against a synthetic prev (itv=2 makes the /itv division
  observable; others asserted as a raw interval delta, not /s).
- Test_cacheHitRatio table test covers the division-by-zero edge directly;
  the live cache-hit test now uses a deterministic synthetic prev.
- degradation test asserts the archiving-backlog field and that degraded
  replication rows do not blank count/size/cache.
- new Test_OverviewBgwriterColumns verifies the by-name bgwriter columns
  exist on PG14-18 (the real risk of the scan-by-name approach).
- verbose-off test now asserts the compact path (Result/Activity) is
  untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Alexey Lesovsky and others added 24 commits June 25, 2026 14:35
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extract pure topBandLayout(verbose, maxY) into top/layout.go: compact
reproduces today's literals byte-identically; verbose grows sysstat +3,
pgstat +5 and pushes cmdline/dbstat down to clear the taller panel; a
height-guard falls back to compact on terminals too short to fit the
expanded band + cmdline + dbstat header + >=1 data row. layout() in
top/ui.go consumes the result instead of hard-coded literals and emits a
one-shot cmdline hint when verbose is requested but the guard tripped.
Table test in top/layout_test.go covers compact / verbose / height-guard /
boundary cases, gocui-free (mirrors the visibleColumns precedent).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- name the height-guard's header+data-row reservation (minDbstatRows = 2)
  so the off-by-one is self-documenting at the comparison site
  (dev-code-reviewer minor).
- repurpose the redundant height-guard test row (was maxY=12, identical to
  boundary-fallback) to a distinct tiny maxY=5, and add a verbose-zero-maxY
  case pinning that non-positive maxY degrades to compact (dev-test-reviewer
  minors: redundant_testing + missing_coverage).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wave 2 (05 aggregate SQL + verbose collection, 06 layout geometry)
implemented, reviewed, committed. Full `go test ./...` green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Render the verbose-mode extended rows for both top panels, gated on the
verbose flag, inside the writer cores renderSysstat/renderPgstat (Task 3).

sysstat (+3 rows): iostat/nicstat select the max-%util / max-Utilization
device among active ones (Completed!=0 / Packets!=0), reusing the struct
math from count*Usage verbatim (never recomputed) so the rows match the
full B/N panels (Decision 5). nicstat rMbps/wMbps replicate printNetdev's
print-time Rbytes/1024/128 conversion. filesyst shows the data_directory
filesystem via the new MatchDataDirFs longest-mount-prefix matcher
(internal/stat/fsstat.go), resolving the symlink with EvalSymlinks only
when local; any match/EvalSymlinks failure -> n/a (no panic, no raw-path
logging). mounted truncated to 10.

pgstat (+5 rows): workload/databases/workers/replication/bgwr-ckpt from
the PgstatOverview aggregate (Task 5), using the Task 1 formatters
(Ceil/ReserveWidth/RateUnit-family). Unavailable sources and first-tick
deltas render the n/a literal, distinguishable from a real 0.

First-tick n/a for the system rows is keyed on the collector's
verboseFirstTick flag, propagated into Stat.System.VerboseFirstTick (the
collector and renderer share state only via Stat) — NOT on len(slice)==0,
since collect* populates a zero-delta slice on the first tick.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Apply the converging code+test review findings (all minor):
- Lock the verbose-row layout with full-line golden assertions (reserve
  width / field order) for iostat/nicstat/filesyst and all five pgstat
  rows, replacing the substring Contains assertions — the reserve-digit
  layout is spec-load-bearing.
- Strengthen the *_compactUnchanged tests: instead of comparing
  verbose=false to itself (tautological), assert that the verbose output's
  first compact rows are byte-identical to the compact render (verbose
  only appends, never perturbs).
- Replace the coarse global NotContains(n/a) in the pgstat available case
  with positive per-field golden, pinning the bgwr write/sync ms deltas
  and the maxwritten delta value.
- Document the intentional matchDataDirFs -> MatchDataDirFs rename
  (exported: called across the package boundary from package top) so the
  TDD anchor name mismatch is traceable.

Security review: 0 findings. Code/test reviews: approved, only minors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Verbose row composers (both panels) implemented, reviewed, committed.
Note: task 08 added a public System.VerboseFirstTick bridge field on
stat.Stat to carry Collector's private first-tick flag to the renderer
across the channel (reviewer-confirmed minimal). Full go test ./... green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Group verbose-mode collection state into a verboseCollectState sub-struct on
Collector (Decision 9), moving the Task-7 first-tick re-arm flags
(verboseFirstTick + prevVerboseActive) into it and updating the
System.VerboseFirstTick bridge accordingly, preserving the OFF->ON re-arm
semantics (no reliance on c.Reset()).

Add a per-source latency guard for the dear no-twin db-size/growth aggregate:
when its last query exceeded max(25% of refresh, 500ms floor), skip the next
collection and reuse the cached stale value (not n/a). System rows and the
cheap aggregates collect every tick (consistency). All behind the single z
knob — no new user knob. Auto-resumes when latency recovers.

collectOverviewStat gains a skipDatabasesSize parameter so the guard can avoid
running the expensive sum(pg_database_size) query (same-package, minimal).

Wire the first-tick "collecting..." cmdline hint in top/stat.go off the same
flag (via Stat.System.VerboseFirstTick), respecting printCmdline mutual
exclusion (one call per path). It clears after the first successful refresh and
reappears on every OFF->ON re-enable.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Code-reviewer critical: the latency guard was a one-way latch — skipSize
depended only on dbSizeLastLatency, which is updated only on a real collection,
so once a query was slow the source was skipped forever and auto-resume never
happened. Add a per-source cadence (dbSizeLastRun) so the dear db-size source is
throttled only until the cadence budget (one refresh interval) elapses, then
force-collected to re-probe its latency — a genuine throttle that auto-resumes
when latency recovers. dbSizeThrottled is now a pure 3-arg decision
(threshold, budget, sinceLastRun) including the genuine-first-tick guard.

Code-reviewer minor: measure the db-size query latency narrowly inside
collectOverviewStat (returned as a second value) instead of around the whole
overview collection, so a slow neighbour aggregate cannot trip the guard for the
wrong source.

Code-reviewer minor: document at the printStat call site that the cmdline is
event-driven and the hint self-clears via printCmdline's 2s timer.

Test-reviewer minors: add Test_verboseCollectState_firstTickNotThrottled (pure
genuine-first-tick guard) and drive auto-resume end-to-end through the real
Update path (backdating dbSizeLastRun past the budget) in
TestCollector_Update_DbSizeThrottle.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Both round-2 reviewers approved/passed (0 critical/major). Applied the two
optional minors:

- code-reviewer: note at the dbSizeThrottled call site that the zero-time
  first-tick time.Since(...) is deliberately unused (short-circuited by the
  dbSizeCacheValid guard).
- test-reviewer: make the end-to-end auto-resume assertion deterministic by
  also asserting dbSizeLastRun advanced past the backdated value (proves a real
  collection ran, independent of host timing); the latency-recovery assertion is
  kept as a coarse smoke check with a note on its wide headroom.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Tiering + latency guard + first-tick grouping (verboseCollectState)
implemented, reviewed (round-1 critical: one-way-latch guard fixed with
dbSizeLastRun cadence), committed. Guard threshold max(refresh/4, 500ms).
All 9 implementation tasks done; build + stat/top tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
All automated gates green (build, test race+coverage 69.7%, gosec 0 issues,
go vet + gofmt clean). 6 automated acceptance criteria PASS; 6 visual TUI
criteria deferred to user manual verification. govulncheck flags one stdlib
advisory (GO-2026-5037, toolchain go1.25.10 -> CI bump) — not feature code.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ty, senders/receivers label

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…y static

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lesovsky lesovsky merged commit 08fac07 into develop Jun 25, 2026
1 check failed
@lesovsky lesovsky deleted the feature/010-overview-dashboard branch June 25, 2026 11:55
lesovsky pushed a commit that referenced this pull request Jun 25, 2026
…ch debt, metrics, archive

Post-merge finalization of the verbose-panel feature (merged via PR #145):
- project-knowledge: verbose-mode capability (overview), Verbose Top-Panel
  Mode architecture subsection, 4 reusable patterns.
- features-catalog: [010] entry.
- decisions-log: 8 ADRs (mode-not-view, dual-boolean, topBandLayout,
  all-three branch, verboseCollectState re-arm, .ready backlog, databases
  split, latency guard).
- tech-debt: [010]-[015] (recovery-t substitution, rateField/RateUnit dup,
  Size-field width, golangci-lint v1/v2, tracked bin/pgcenter, GO-2026-5037).
- user-spec Post-implementation section; metrics finalized + copied to
  docs/metrics/; feature dir archived.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
lesovsky added a commit that referenced this pull request Jun 25, 2026
* feat: verbose mode for the top summary panels — instance overview (010) (#145)

Adds a verbose toggle (`v`) for the sysstat/pgstat top panels: sysstat +3
rows (iostat/nicstat/filesyst), pgstat +5 rows (workload/databases/workers/
replication/bgwr-ckpt). A display mode (no new view), reusing the free-form
render path, persistent across screens. Includes verbose-gated all-three
system collection, verboseCollectState (first-tick n/a + collecting hint +
re-arm without Reset, latency guard max(refresh/4,500ms)), new formatters
(ceil/reserved-digit/dynamic unit suffix), and panel-consistent aggregates.

Full go test ./... green (live PG 14-18), gosec clean. TUI-only (NotRecordable).
Decision history under docs/features/010-feat-overview-dashboard/.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: finalize 010-feat-overview-dashboard — PK, catalog, ADR log, tech debt, metrics, archive

Post-merge finalization of the verbose-panel feature (merged via PR #145):
- project-knowledge: verbose-mode capability (overview), Verbose Top-Panel
  Mode architecture subsection, 4 reusable patterns.
- features-catalog: [010] entry.
- decisions-log: 8 ADRs (mode-not-view, dual-boolean, topBandLayout,
  all-three branch, verboseCollectState re-arm, .ready backlog, databases
  split, latency guard).
- tech-debt: [010]-[015] (recovery-t substitution, rateField/RateUnit dup,
  Size-field width, golangci-lint v1/v2, tracked bin/pgcenter, GO-2026-5037).
- user-spec Post-implementation section; metrics finalized + copied to
  docs/metrics/; feature dir archived.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(lint): rename builtin-shadowing vars to satisfy revive

revive redefines-builtin-id flagged local vars `max` (Go 1.21+ builtin)
in pretty.RateUnit and top.rateField, and `real` (complex builtin) in
an fsstat test. Rename to maxFit / realPath. No behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Alexey Lesovsky <a.lesovsky@postgrespro.ru>
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