diff --git a/CHANGELOG.md b/CHANGELOG.md index ab726a132d..358a3f39bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,31 @@ cut it is archived and compacted like every other version: Releases before 3.0.0 are not archived: those entries carry no prose to move. +## [Unreleased] + +### Fixed + +- **The by-CPU tools now actually rank by CPU** ([#3523]) - get_top_queries_by_cpu and get_top_procedures_by_cpu ordered by summed elapsed time in both SKUs, so on a wait-bound server the real CPU consumers could be missing from the page entirely - and attributed_cpu_ratio read as "hidden CPU" when it actually meant "wrong sort key". Every ranking site now orders by worker time, including the over-fetch cut that could drop a CPU-heavy query before the final sort ever saw it. The viewer's Duration grids keep their elapsed ranking, which is what they promise. +- **analyze_server no longer answers "all metrics are within normal ranges" when the analysis window collected nothing** ([#3524]) - The analysis gate passes on lifetime history, so a server whose collection died still reached the all-clear path with an empty window. Both SKUs' analysis services now flag the zero-facts window and analyze_server returns the "unavailable" envelope pointing at get_collection_health; the genuine all-clear (facts collected, zero findings) is unchanged. +- **The Performance Calendar, daily summary, and fleet sweep band deadlocks as a measured per-hour rate, not any-deadlock-is-Critical** ([#3525]) - The shared daily classifier routed the Deadlocks signal through the Overview card's store-backed rate tiers (#3368, V120) with each surface's real window as the denominator, so one deadlock no longer paints a calendar day red, sweep verdicts stop scaling with the cadence knob, sub-hour spans fall to Warning instead of a multiplied rate, and the day tooltip/reasons report the rate beside the count. The still-forming day clamps its window to the elapsed portion, so an active storm bands on its true in-progress rate instead of diluting against hours that have not happened yet. +- **Perfmon rates are honest per-second values in analysis** ([#3527]) - The PERFMON_*_SEC facts, the batch-request anomaly window, and both SKUs' batch-request baselines read the per-collection-interval delta as if per-second (60-300x overstatement); every read now divides by the measured sample_interval_seconds (Darling's baseline derives it from collection-time gaps, since the continuous aggregate stores no interval), and interval-0 rows - where no delta was knowable - are skipped instead of read as rates. +- **Floor the SQL count thresholds, give Store Disk Pressure a GB floor, and count measured metrics in the fleet Healthy label** ([#3528]) - The SQL Server deadlock and blocking count thresholds now floor at 1 on read like their PostgreSQL twins, so a store row hand-edited to 0 can't fire on a quiet server; Store Disk Pressure gains a self_alerts.disk_free_warn_gb floor (default 50, 0 disables) so a large store volume at a low percent stops paging CRITICAL with hundreds of GB of runway; and fleet cards carry measured_metric_count/metric_count so a Healthy label built off one measured metric of six says so ("1 of 6 measured" on the web fleet page). +- **get_memory_trend stops reporting granted memory as a hardcoded zero** ([#3529]) - The MCP payload shipped a literal total_granted_mb = 0.0 on both SKUs, steering agents away from memory grants during grant-pressure investigations. The field became an explicit null with the envelope naming get_memory_grants as the grants series' source, and both tool descriptions stopped promising granted memory. +- **Lite's Query Store time slicer reads physical reads from its own column** ([#3530]) - The reader mapped both read fields to the logical-reads ordinal and never read the SELECT's total_physical_reads column, so the physical aggregate was computed and dropped. Pinned with a test whose fixture rows carry distinct values per I/O column. +- **LCK_M_IS advice carries the same RCSI caveats as its LCK_M_S twin** ([#3531]) - The intent-shared-lock advice handed out the READ_COMMITTED_SNAPSHOT ALTER with no caveats. Both lock twins now name the brief exclusive lock the ALTER takes, the tempdb version-store cost, and the test-on-a-copy warning for NOLOCK-dependent code. +- **Collector schedules refuse cadences that would fabricate quiet** ([#3532]) - Delta-family collectors (wait/latch/spinlock/query/procedure/file I/O/memory-grant/perfmon stats, and the PostgreSQL wait/statement collectors) now cap at 30 minutes in both Lite's and Darling's schedule editors, with the store-side resolver ignoring out-of-policy rows: past the shared 60-minute delta gap policy every cycle re-baselined and recorded zeros forever, so the charts flatlined green precisely because collection stopped measuring. +- **get_pg_plans finds the plan you asked for, not just the plans in the top page** ([#3533]) - the queryid filter ran client-side over a fetched top-duration page: the reader had no query_id predicate, so the tool pulled the top limit x 10 shapes by total time and filtered them in C#, which made any plan ranked below that page unfindable at every window size - and the filtered-empty branch then told the caller capture was working, the plan was never captured, and the statement was "not the query to look at", while the plan sat in the store. The predicate now runs in the store's SQL over every capture in the window, the over-fetch is gone, and the miss text says what was actually searched and what a miss can mean, with get_pg_top_queries and get_pg_plan_capture_readiness cross-references. The web dashboard's read dispatch already passed query_id through, so it gains the server-side search with no wiring change. +- **get_pg_autovacuum_health classifies severity from the same axis it ranks by** ([#3534]) - The reader ranks tables by GREATEST(dead ratio, insert ratio) but severity only read the dead side, so an append-only worst_table ten times past its insert-vacuum threshold reported "ok"; severity now comes from the worse of the two ratios, the insert-side ratio is published as insert_threshold_ratio, a one-sample window reports growth as unknown instead of "flat" (with first_seen_at showing the window), and the page-scoped summary counts carry the sibling limit_reached flag. +- **get_pg_replication_slots headlines the worst-classified slot and never spells unknown WAL growth as stable** ([#3535]) - worst_slot was picked by retained size, so an active 45 GB keeping-pace slot ("ok") outranked an inactive growing orphan ("critical_orphan_filling_disk"); slots now rank by severity with size only breaking ties, growth is null when either endpoint is the collector's -1 sentinel or the window holds one sample, new unknown-growth severities say so explicitly, and raw retained_wal_bytes nulls the sentinel like its _gb sibling. +- **get_pg_io_stats no longer renders track_io_timing=off as an impossibly fast disk** ([#3536]) - track_io_timing is OFF by default in PostgreSQL, so on a stock server the read and write time counters are never populated - and this read divided the zeros out to 0.000 ms latencies while its trend sibling had shipped the honest contract all along. The single-window read now mirrors that contract exactly: the setting is read from the collected pg_server_config bounded by the window's end (inferred from the window's data when never collected, with io_timing_source saying which), io_timing_tracked and timing_note are published, and every time-derived figure is null when untracked rather than zero, while the operation counts, hit ratios and byte figures stand untouched. The new busiest_basis field states which key decided the "busiest" ranking. +- **The PostgreSQL xmin-horizon alert catches rotating holders and stops firing on single observations** ([#3537]) - The persistence gate gains a horizon arm that fires when the horizon sits past the age threshold in a majority of the window's real captures (counted from the collector's own collection_log runs) regardless of who holds it, naming the rotating-holder pattern under a stable dedup subject; a minimum-observations floor stops the first holder after quiet hours from reading 1-of-1 as chronic. +- **QueryStore slicer's physical-reads sort plots the physical series** ([#3547]) - Sorting the Query Store grid by physical reads relabeled the slicer but kept plotting logical reads, and the selected-row overlay had no physical series to draw at all. Bars and overlay both plot the real physical-reads aggregate now. +- **get_memory_trend joins the grants series so total_granted_mb carries real data** ([#3548]) - Completing #3529's honest null: both SKUs join the memory-grant series the viewers already chart into the trend payload, matching each memory point to the nearest grants snapshot within 30 seconds. A snapshot measuring nothing granted is a genuine 0.0, an uncovered point stays null, and the granted_note appears only when there is a gap to explain. +- **Viewer Recommendations tabs stop saying "All clear" when the analysis window collected nothing** ([#3551]) - Both viewers only branched on the insufficient-data message, so a server whose collection died still rendered the all-clear on a zero-finding read. Lite's Generate now consumes the analysis service's window-empty state directly; the Darling service persists it into the analysis-state marker (false-with-a-message, no schema change) so the viewer's read and Generate now doors both render a "nothing was measured - check Collection Health" notice. The genuine all-clear (facts measured, zero findings) is unchanged. +- **Viewer pass for the Store Disk Pressure GB floor and fleet measured-metric qualifier** ([#3563]) - The Settings window can now edit the Store Disk Pressure warning's GB floor (V126, 0 disables) next to its percent sibling, and the WPF fleet card's band label carries the web fleet page's "N of M measured" qualifier when a band folded over unmeasured metrics, so an Unknown-heavy server no longer reads as an unqualified green. +- **Deprecated Dashboard analysis reads perfmon counters as true per-second rates** ([#3561]) - The Dashboard's analysis facts, batch-request anomaly window, and batch-request baseline read the per-collection-interval perfmon delta as if it were per-second, overstating by 60-300x at common cadences; all three now divide by the row's measured sample interval and skip rows with no knowable delta, matching the Lite/Darling fix in #3560. +- **Sorting the Procedures or Query Store grid by physical reads plots the physical series, in both apps** ([#3556]) - In Lite and the Darling viewer, the Procedures grid's physical-reads sort plotted the logical aggregate under a physical label, and the Query Store slicer labeled execution-weighted slice totals "Avg". The Darling viewer's Query Store grid also still carried the original #3547 physical/logical swap, and none of its grids re-projected a selected row's overlay when the sort metric changed, so the bars and the overlay could show different metrics. Both apps now plot the series the sorted column names, label slice totals as totals, and keep the overlay on the bars' metric - pinned by distinct-per-column reader tests and source-text pins over the sorting handlers in both apps. + ## [3.8.0] - 2026-09-17 Full entries: [docs/changelog/3.8.md](docs/changelog/3.8.md) @@ -1227,6 +1252,26 @@ Full entries: [docs/changelog/3.0.md](docs/changelog/3.0.md) - **Failed SQL Agent job alert** ([#749]) - **Installer: optional custom data/log file locations** ([#768]) +[#3523]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/3523 +[#3524]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/3524 +[#3525]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/3525 +[#3527]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/3527 +[#3528]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/3528 +[#3529]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/3529 +[#3530]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/3530 +[#3531]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/3531 +[#3532]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/3532 +[#3533]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/3533 +[#3534]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/3534 +[#3535]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/3535 +[#3536]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/3536 +[#3537]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/3537 +[#3547]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/3547 +[#3548]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/3548 +[#3551]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/3551 +[#3556]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/3556 +[#3561]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/3561 +[#3563]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/3563 [#3514]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/3514 [#3477]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/3477 [#3495]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/3495