Skip to content

perf(telemetry): only recompute the dashboard when data changed - #458

Merged
HugoRCD merged 1 commit into
mainfrom
perf/telemetry-dashboard
Jul 27, 2026
Merged

perf(telemetry): only recompute the dashboard when data changed#458
HugoRCD merged 1 commit into
mainfrom
perf/telemetry-dashboard

Conversation

@HugoRCD

@HugoRCD HugoRCD commented Jul 27, 2026

Copy link
Copy Markdown
Owner

The dashboard re-ran every query on a fixed 5s cadence whether or not anything had happened. A stats response is ~14 aggregate queries over the whole selected window — measured at ~800ms of database CPU for a 30-day range against 200k rows — so an idle tab was burning that every 5 seconds, as was every MCP telemetry-stats call.

Live refresh is now two-tiered. Each tick polls /api/telemetry/cursor, two indexed max() lookups, and only refetches the real payloads when the token moved (plus a 60s heartbeat, since the range is a sliding window). Costing almost nothing per tick is what buys the headroom to poll more often, so the cadence drops from 5s to 3s and new runs surface faster.

Server-side, stats are memoized per filter behind the same cursor, with a 5s floor, a 60s ceiling and single-flight so simultaneous misses — an SSR render, a second tab, an agent — collapse into one aggregation. The telemetry-stats MCP tool shares it.

Also on the request path:

  • shouldUseMockData() ran select id from runs limit 1 on every single request; it is now memoized, permanently once real runs exist.
  • The page awaited its three useFetch calls in sequence, so each request only started once the previous had returned — three serialised database round trips in the server-rendered response. They now run as one wave.
  • The live feed asked for a range-wide count(*) it never displays.
  • The cursor probe is excluded from request logging.

Measured and rejected along the way: collapsing the 14 aggregates into one scan (a materialized CTE ran 1.5x slower, grouping sets broke even — the cost is in the aggregation, not the repeated scans), and composite or covering indexes (within noise on every path, and the covering variant was both slower on filtered ranges and 5x the disk).

Summary by CodeRabbit

  • New Features

    • Added faster live dashboard updates that refresh only when telemetry data changes.
    • Added protected cursor-based monitoring for new telemetry runs.
    • Added optional total-count calculation for run listings.
  • Performance

    • Improved dashboard and telemetry statistics response times through shared caching.
    • Reduced redundant database checks and concurrent computations.
  • Bug Fixes

    • Improved fallback behavior when telemetry data is unavailable.
    • Prevented failed statistics calculations from remaining cached.
  • Tests

    • Added coverage for caching, refresh behavior, concurrency, and mock-data fallback scenarios.

The dashboard re-ran every query on a fixed 5s cadence whether or not
anything had happened. A stats response is ~14 aggregate queries over the
whole selected window — measured at ~800ms of database CPU for a 30-day
range against 200k rows — so an idle tab was burning that every 5 seconds,
as was every MCP `telemetry-stats` call.

Live refresh is now two-tiered. Each tick polls `/api/telemetry/cursor`,
two indexed `max()` lookups, and only refetches the real payloads when the
token moved (plus a 60s heartbeat, since the range is a sliding window).
Costing almost nothing per tick is what buys the headroom to poll more
often, so the cadence drops from 5s to 3s and new runs surface faster.

Server-side, stats are memoized per filter behind the same cursor, with a
5s floor, a 60s ceiling and single-flight so simultaneous misses — an SSR
render, a second tab, an agent — collapse into one aggregation. The
`telemetry-stats` MCP tool shares it.

Also on the request path:

- `shouldUseMockData()` ran `select id from runs limit 1` on every single
  request; it is now memoized, permanently once real runs exist.
- The page awaited its three `useFetch` calls in sequence, so each request
  only started once the previous had returned — three serialised database
  round trips in the server-rendered response. They now run as one wave.
- The live feed asked for a range-wide `count(*)` it never displays.
- The cursor probe is excluded from request logging.

Measured and rejected along the way: collapsing the 14 aggregates into one
scan (a materialized CTE ran 1.5x slower, grouping sets broke even — the
cost is in the aggregation, not the repeated scans), and composite or
covering indexes (within noise on every path, and the covering variant was
both slower on filtered ranges and 5x the disk).
@HugoRCD HugoRCD self-assigned this Jul 27, 2026
@vercel

vercel Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
evlog-telemetry Ready Ready Preview, Comment Jul 27, 2026 8:56pm
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
evlog-docs Skipped Skipped Open in v0 Jul 27, 2026 8:56pm
just-use-evlog Skipped Skipped Jul 27, 2026 8:56pm

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

Thank you for following the naming conventions! 🙏

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 2a99734c-95d8-4e0b-8b04-9b12b24d5944

📥 Commits

Reviewing files that changed from the base of the PR and between 104a908 and c414bc5.

📒 Files selected for processing (12)
  • apps/telemetry/app/pages/index.vue
  • apps/telemetry/nuxt.config.ts
  • apps/telemetry/server/api/telemetry/cursor.get.ts
  • apps/telemetry/server/api/telemetry/runs.get.ts
  • apps/telemetry/server/api/telemetry/stats.get.ts
  • apps/telemetry/server/mcp/tools/telemetry-stats.ts
  • apps/telemetry/server/utils/mock-mode.ts
  • apps/telemetry/server/utils/stats-cache.ts
  • apps/telemetry/server/utils/telemetry-queries.ts
  • apps/telemetry/shared/types/telemetry.ts
  • apps/telemetry/test/mock-mode.test.ts
  • apps/telemetry/test/stats-cache.test.ts

📝 Walkthrough

Walkthrough

Telemetry adds cursor-based live refresh, parallel dashboard fetches, cached statistics, mock-mode caching, and optional total-count queries for the runs feed.

Changes

Telemetry refresh and caching

Layer / File(s) Summary
Cursor contract and endpoint
apps/telemetry/shared/types/telemetry.ts, apps/telemetry/server/utils/telemetry-queries.ts, apps/telemetry/server/api/telemetry/cursor.get.ts, apps/telemetry/nuxt.config.ts
Defines RunsCursor, retrieves the latest run cursor, exposes it through a protected endpoint, and excludes cursor probes from event logging.
Statistics cache and consumers
apps/telemetry/server/utils/stats-cache.ts, apps/telemetry/server/api/telemetry/stats.get.ts, apps/telemetry/server/mcp/tools/telemetry-stats.ts, apps/telemetry/test/stats-cache.test.ts
Adds bounded, cursor-aware statistics caching with concurrent request deduplication, failure cleanup, eviction, and coverage for freshness and filter behavior.
Mock-mode verdict caching
apps/telemetry/server/utils/mock-mode.ts, apps/telemetry/test/mock-mode.test.ts
Caches mock-mode decisions, deduplicates database checks, retries empty-table checks after a TTL, and tests fallback behavior.
Dashboard fetch and conditional refresh
apps/telemetry/server/api/telemetry/runs.get.ts, apps/telemetry/server/utils/telemetry-queries.ts, apps/telemetry/app/pages/index.vue
Starts dashboard fetches in parallel, polls the cursor for conditional updates, refreshes stats on a heartbeat, and allows feed requests to skip total-count computation.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Dashboard
  participant CursorAPI
  participant RunsAPI
  participant StatsAPI
  participant TelemetryQueries
  Dashboard->>CursorAPI: Poll telemetry cursor
  CursorAPI->>TelemetryQueries: getRunsCursor()
  TelemetryQueries-->>CursorAPI: RunsCursor
  CursorAPI-->>Dashboard: latestId
  alt Cursor advanced
    Dashboard->>RunsAPI: Refresh runs with optional total
    Dashboard->>RunsAPI: Refresh feed withTotal=false
  end
  alt Cursor advanced or stats heartbeat expired
    Dashboard->>StatsAPI: Refresh cached stats
  end
Loading

Possibly related PRs

  • HugoRCD/evlog#446: Both changes modify the telemetry dashboard’s live-refresh polling flow.

Suggested labels: feature

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch perf/telemetry-dashboard

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@HugoRCD
HugoRCD merged commit e6ccff0 into main Jul 27, 2026
18 of 19 checks passed
@HugoRCD
HugoRCD deleted the perf/telemetry-dashboard branch July 27, 2026 20:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant