Repository navigation
fix(performance): keep history snapshots by when they were taken - #272
Conversation
Performance history was always empty in production. Snapshots were selected and pruned by system.uptime as if it were a timestamp, but uptime is a duration since start (~3.6e6 ms after an hour) and the cut-offs are epoch times (~1.8e12). getHistoricalData(start) matched nothing, and cleanupOldData() and PerformanceAnalytics.addDataPoint() deleted every snapshot as soon as it was stored, so wp_performance_history had no data points and trend/anomaly analysis never got its 5 samples. Snapshots were also shallow copies that shared the live request counters. getMetrics() now returns a deep copy stamped with `timestamp` (epoch ms), and history is selected, pruned and charted by it. The timer's work is recordSnapshot(), so it can be tested. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keeps it apart from the scope bullet the cache hit-rate PR edits. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
Reviewer's GuideFix performance history by stamping deep-copied metric snapshots with epoch timestamps and using those timestamps consistently for selection, retention, analytics lookback pruning, and chart output; add focused regression tests and documentation. Sequence diagram for timestamped performance history collectionsequenceDiagram
participant Timer
participant Monitor as PerformanceMonitor
participant Metrics as LiveMetrics
participant History as HistoricalData
Timer->>Monitor: recordSnapshot()
Monitor->>Metrics: getMetrics()
Metrics-->>Monitor: structuredClone(metrics, timestamp: Date.now())
Monitor->>History: push(snapshot)
Monitor->>History: cleanupOldData()
History-->>Monitor: retain snapshots where timestamp > cutoff
Flow diagram for performance history time-window processingflowchart LR
A[Metric snapshot] --> B[Epoch timestamp]
B --> C[getHistoricalData]
B --> D[Analytics lookback pruning]
B --> E[Chart point timestamp]
U[system.uptime duration] -. not used for history filtering .-> C
U -. not used for retention .-> D
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
🟢 Approval recommended
The implementation consistently corrects timestamp handling and snapshot isolation with focused regression coverage.
0 open findings
What changed in this PR
Fixes performance history retention by using epoch timestamps and immutable snapshots.
Changes:
- Adds timestamped, deep-cloned metric snapshots.
- Uses timestamps for retention, filtering, analytics, and charts.
- Adds regression tests and documents the history contract.
| File | Description |
|---|---|
src/performance/PerformanceMonitor.ts |
Records and retains timestamped snapshots. |
src/performance/PerformanceAnalytics.ts |
Prunes analytics data by timestamp. |
src/tools/performance/PerformanceHelpers.ts |
Charts points using snapshot timestamps. |
src/performance/AGENTS.md |
Documents history semantics. |
tests/performance/HistoryWindow.test.js |
Covers history regressions. |
tests/performance/PerformanceAnalytics.test.js |
Corrects timestamp-based fixtures and assertions. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Review follow-up. With history collected, code paths that never ran
before became live, and three misbehaved on real data:
- Size: wp_performance_export returned every snapshot (a day is 2880,
measured 5.7-12.9 MB) and wp_performance_history charted all of them.
Both now carry an even sample of at most 100 points (first and newest
kept) plus the full count; stored snapshots drop the per-tool maps
(PerformanceSnapshot), which nothing reading history uses.
- False anomalies: each point was part of its own baseline, and a flat
baseline has zero spread, so 320 -> 321 ms scored as an anomaly; they
never expired and pushed wp_performance_alerts to "Performance
Warnings" on a healthy server. A point is now judged against the
points before it, the spread has a per-metric noise floor, a zero
mean gives a finite deviation, and anomalies expire after the
lookback period.
- NaN/Infinity: a flat series made R-squared 0/0 and a zero previous
value made the change rate Infinity ("declining at Infinity% rate").
Both are guarded.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Anomaly z-scores were absolute values, so an improvement (response time 2000 -> 1000 ms, a cache warming up from no lookups) scored as a "spike" or "drop" -- critical at z > 4 -- and wp_performance_alerts reported "Critical Issues Detected" for the 24 h lookback. This only became reachable now that history is no longer empty. - Only a move in the worse direction counts: response time and error rate up, hit rate down. - A cache with no lookups is neutral: such points are neither judged nor part of the hit-rate baseline (idle zeros also hid real drops). - Prune to the lookback period before judging a new point, so it is never compared with readings older than 24 h. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ry-window # Conflicts: # src/tools/performance/PerformanceHelpers.ts
## [4.1.4](v4.1.3...v4.1.4) (2026-10-10) ### 🐛 Bug Fixes * **performance:** keep history snapshots by when they were taken ([#272](#272)) ([bcdbb2a](bcdbb2a))
|
🎉 This PR is included in version 4.1.4 🎉 The release is available on: Your semantic-release bot 📦🚀 |
… data (#274) * fix(performance): fit trends against time and treat idle caches as no data Follow-ups from the review of #270/#272, now that history is live: - Trends regressed over the sample index, but snapshots are unevenly spaced (the analytics timer and every wp_performance_history call add one), and "one sample ahead" was labelled a 30-minute prediction; predictPerformance(timeframeMinutes) ignored its horizon. Trends now fit against timestamps and predict prediction.timeframe past the newest point; a direction needs a fitted change above the metric's noise floor (the fixed 0.1 slope meant ms for one metric and 10 percentage points for another). - A cache with no lookups is neutral, not 0%: idle points are left out of the hit-rate trend and of history's averageCacheHitRate (null if none had lookups), and hit-rate strings read "n/a (no lookups yet)" in wp_cache_stats, wp_cache_info and wp_performance_stats. - History windows are inclusive at both ends: pruning used ">" while getHistoricalData(start) used ">=". - History/export scope labels say the chart series are sampled to at most 100 points; downsample(items, 0) returns no items; the supported timeframes come from the parseTimeframe map instead of a second list. Tests: 18 new; suite 2778 passed before, 2796 after, 0 failed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(performance): no cache efficiency before the first lookup Review follow-up (Codex on #274): wp_performance_stats showed the hit rate as "n/a (no lookups yet)" next to efficiency "Poor", because calculateCacheEfficiency() scored the undefined 0% hit rate. calculateCacheEfficiency() now returns "n/a (no lookups yet)" when the cache has had no lookups, like formatHitRate(). Tests: 1 new (failed before the fix); suite 2797 passed, 0 failed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
## [4.1.6](v4.1.5...v4.1.6) (2026-10-12) ### 🐛 Bug Fixes * **performance:** fit trends against time and treat idle caches as no data ([#274](#274)) ([2996b29](2996b29)), closes [270/#272](https://github.com/270/mcp-wordpress/issues/272)
Pull Request
Description
Performance history was always empty in production. I found this while checking a Copilot comment on #270 about how the history summary is labelled.
Snapshots were selected and pruned by
system.uptimeas if it were a timestamp. Butuptimeis a duration since start (about 3.6e6 ms after an hour), while the cut-offs are epoch times (about 1.8e12):PerformanceMonitor.getHistoricalData(start)keptuptime >= Date.now() - timeframe, which is never true, so it always returned[];cleanupOldData()keptuptime > Date.now() - retention, which is never true, so every collection tick deleted all history, including the snapshot it had just stored;PerformanceAnalytics.addDataPoint()pruned the same way, soanalyzeTrends()(needs 5 points), anomaly detection and predictions never had data.Result:
wp_performance_historyalways returneddataPoints: 0, the export'shistoricalDatawas empty, and trends were never computed.Snapshots were also shallow copies.
recordRequest()updatesmetrics.requestsin place, so every stored snapshot shared the live request counters.Type of Change
Changes Made
PerformanceMetrics.timestamp(epoch ms).getMetrics()returns a deep copy (structuredClone) stamped with the time it was read.getHistoricalData(),cleanupOldData()andPerformanceAnalytics.addDataPoint()select and prune bytimestampprocessHistoricalDataForChart()usestimestampfor each chart point (it used uptime ms)recordSnapshot(), so it can be testedsrc/performance/AGENTS.md: select and prune history bytimestamp, never byuptimetests/performance/HistoryWindow.test.js(6 tests): timestamp, snapshot independence, time-window selection, 24h retention, chart timestamps, and trend analysis now getting its samplestests/performance/PerformanceAnalytics.test.js: the fixtures setsystem.uptime: Date.now()(an epoch value posing as uptime), which is why they survived the broken filter. They now carrytimestamp, and the lookback test asserts what is kept instead ofexpect(true).toBe(true).Testing
expected 2 to be +0for the shared counters;analyzeTrends()returned[])npm test: 90 files / 2717 tests; lint, typecheck, prettierwp_performance_history timeframe=1hreturned 2 data points with epoch timestamps; before the fix it returned 0.src/performance/andPerformanceHelpers.ts, is conflict-free, and the combined performance tests pass (247)Notes for reviewers
currentMetrics) gain atimestampfield; history chart points carry epoch ms instead of uptime ms.Documentation
src/performance/AGENTS.md)Security
Independent review follow-up
Turning history on made dead code live; three paths misbehaved on real data and are fixed here:
downsample(), newest kept) and report the full count. A day used to export 2880 snapshots (5.7–12.9 MB). Stored snapshots drop the per-tool maps (PerformanceSnapshot).tests/performance/HistoryWindow.test.js. Merge before fix(performance): round alert values and label cache hit-rate scope #270.🤖 Generated with Claude Code
Summary by Sourcery
Fix performance history and analytics so collected snapshots remain available, accurate, bounded, and actionable in long-running sessions.
Bug Fixes:
Enhancements:
Documentation:
Tests: