Skip to content

ENG-10164 Add event-loop performance benchmark suite#6751

Open
Alek99 wants to merge 17 commits into
mainfrom
agent/event-loop-performance-benchmarks
Open

ENG-10164 Add event-loop performance benchmark suite#6751
Alek99 wants to merge 17 commits into
mainfrom
agent/event-loop-performance-benchmarks

Conversation

@Alek99

@Alek99 Alek99 commented Jul 13, 2026

Copy link
Copy Markdown
Member

What changed

  • add a documented, owned performance-testing structure with deterministic CodSpeed benchmarks and opt-in wall-time suites
  • expand event processing into cold/warm, same-token, independent-token, and burst scenarios
  • benchmark event-loop queueing, task lifecycle, streaming updates, payloads, serialization, state deltas, state managers, routes, headers, proxies, and Var operations
  • add wall-time event-loop attribution with loop-lag percentiles, queue/task gauges, 80/20 hot-token load, handler-shape coverage, ordering checks, trace output, and failure diagnostics
  • add manual real-Redis latency/contention, production Socket.IO load, memory retention, compiler/hot-reload lifecycle, and browser/bundle suites
  • run event-loop and browser smoke coverage in the existing PR performance workflow

Review fixes

  • make loop-probe reset coverage independent of platform timer resolution
  • preserve every repeated pipeline-stage pair for streaming handlers
  • keep the event loop's default executor usable after load-client runs
  • exercise the real BaseStateToken hierarchy and persisted warm-state path in memory and Redis benchmarks
  • measure hot reload through one persistent reflex run --backend-only watcher and an observable live source-version endpoint

Review fixes (round 2)

  • drop the out-of-scope nightly workflow, comparison script, and PERFORMANCE.md
  • guard the Redis helper: REFLEX_PERF_REDIS_URL must name an explicit db index and the database must be empty; cleanup is exception-safe
  • measure the bundle budget against the shipped client bundle (.web/build/client, measured 238 KB gzip) instead of all of .web; 500 KB budget overridable via REFLEX_PERFORMANCE_JS_GZIP_BUDGET
  • bound all PR-smoke event-future waits with scale-aware timeouts and give the benchmarks CI job a timeout-minutes
  • benchmark real product code: reflex.app._decode_asgi_headers (extracted, behavior identical) instead of a local copy, and re.purge() per round so route-table construction is actually cold
  • move processor lifecycle out of measured regions, assert shutdown drain actually drains, and raise the drain budget so instrumentation slowdown cannot flip drain into cancel
  • de-flake the wall-time suites: token-cleanup polling instead of a fixed sleep, concurrency-scaled client timeouts with one retry, holder-process kill fallback, untraced warmup cycles before the leak gate, dev-backend child sweep + port-race retry
  • move the browser smoke into its own CI job and upload performance smoke artifacts from both jobs

Benchmark model

tests/benchmarks/ contains deterministic CPU work suitable for CodSpeed instrumentation. tests/performance/ contains wall-time, service, process, memory, compiler, load, and browser scenarios; it is skipped unless --run-performance is supplied.

Shared schema-v1 reporting, environment metadata, percentiles, loop probes, pipeline traces, state/app fixtures, Redis isolation, Socket.IO clients, and diagnostics live under tests/benchmarks/support/.

CI scope

This draft adds pull-request smoke coverage to the existing performance workflow, plus artifact upload of the smoke reports. Per scope decision, it intentionally excludes all other former step-4 work: nightly or release workflows, automated profiler collection, runner validation, comparison tooling, and standalone external-load tooling.

Validation

  • uv run pytest tests/benchmarks --codspeed -q — 88 passed
  • uv run pytest tests/units -q — 6645 passed, 78.04% coverage
  • uv run pytest tests/performance -q — skipped by default; event-loop and memory smoke ran for real (2 passed), plus a real production export to measure the shipped bundle
  • Ruff format/check passed; Pyright passed with 0 errors; workflow YAML validated

Review notes

The wall-time suites emit measurements without performance thresholds; regression gating should only be enabled after enough comparable baseline runs establish variance. Two narrow correctness gates are intentional: the shipped-bundle gzip budget (2x measured headroom, env-overridable) and the loop-probe detection bound (2 ms against a deliberately induced 10 ms block).

@greptile-apps

greptile-apps Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a comprehensive performance benchmark suite for Reflex. The main changes are:

  • Deterministic benchmarks for event processing, state management, serialization, routes, proxies, and variables.
  • Opt-in wall-time suites for event-loop behavior, Redis, memory, browser bundles, Socket.IO load, and compiler lifecycle.
  • Shared reporting, diagnostics, tracing, fixtures, clients, and service helpers.
  • Pull-request smoke jobs with bounded execution and artifact uploads.
  • Event-loop probe fixes for terminal blocking and instrumentation task counts.

Confidence Score: 5/5

The latest fixes look safe to merge.

  • Terminal synchronous blocking is sampled before probe cancellation.
  • The sampler task is excluded from workload task counts.
  • Tests cover both fixes and probe reuse.
  • No remaining blocking issue was confirmed in the updated paths.

Important Files Changed

Filename Overview
tests/benchmarks/support/loop_probe.py Adds event-loop lag, task-count, and gauge sampling with fixes for terminal blocking and sampler exclusion.
tests/units/benchmarks/test_loop_probe.py Covers blocking detection, terminal sampling, sampler exclusion, and probe reuse.
tests/performance/test_event_loop.py Adds wall-time event-loop scenarios, bounded waits, workload attribution, and diagnostics.
.github/workflows/performance.yml Adds bounded event-loop and browser smoke jobs with artifact uploads.
reflex/app.py Extracts ASGI header decoding into a reusable helper without changing connection behavior.

Reviews (14): Last reviewed commit: "Fix more benchmark accuracy findings: re..." | Re-trigger Greptile

Comment thread tests/benchmarks/support/loop_probe.py
Comment thread tests/benchmarks/loop_probe.py Outdated
@codspeed-hq

codspeed-hq Bot commented Jul 13, 2026

Copy link
Copy Markdown

Merging this PR will degrade performance by 69.31%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

❌ 1 regressed benchmark
✅ 25 untouched benchmarks
🆕 62 new benchmarks
⏩ 8 skipped benchmarks1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
test_process_event 7.6 ms 24.7 ms -69.31%
🆕 test_process_event_cold N/A 20.2 ms N/A
🆕 test_process_event_warm N/A 2.8 ms N/A
🆕 test_emit_update N/A 174.7 µs N/A
🆕 test_event_future_tree N/A 515.3 µs N/A
🆕 test_event_queue_dispatch N/A 368.1 µs N/A
🆕 test_event_shutdown_cancel N/A 618.7 µs N/A
🆕 test_event_shutdown_drain N/A 1.9 ms N/A
🆕 test_yield_intermediate_update N/A 661.6 µs N/A
🆕 test_event_payload_mutable_proxy N/A 7.2 ms N/A
🆕 test_event_payload_plain_collection N/A 68.7 ms N/A
🆕 test_process_event_burst_independent_tokens[1] N/A 2.8 ms N/A
🆕 test_process_event_burst_independent_tokens[10] N/A 22.7 ms N/A
🆕 test_process_event_burst_independent_tokens[100] N/A 222.8 ms N/A
🆕 test_process_event_burst_same_token[1] N/A 2.8 ms N/A
🆕 test_process_event_burst_same_token[10] N/A 29 ms N/A
🆕 test_process_event_burst_same_token[100] N/A 974.4 ms N/A
🆕 test_route_argument_extraction N/A 56.8 µs N/A
🆕 test_route_matching[10] N/A 55.2 µs N/A
🆕 test_route_matching[100] N/A 186.4 µs N/A
... ... ... ... ...

ℹ️ Only the first 20 benchmarks are displayed. Go to the app to view all benchmarks.

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing agent/event-loop-performance-benchmarks (8745f9e) with main (48f229f)

Open in CodSpeed

Footnotes

  1. 8 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@Alek99 Alek99 changed the title Add event loop performance benchmarks Add event-loop performance benchmark suite Jul 13, 2026
Alek99 and others added 2 commits July 12, 2026 19:29
…nchmarks

Close the measurement gaps identified against peer frameworks
(Phoenix LiveView, Blazor Server, Streamlit, Gradio, krausest):

- wire-size suite recording serialized delta bytes per canonical
  interaction, plus received payload bytes in the load client
- framework-overhead baseline against a bare Starlette + python-socketio
  echo server with byte-matched responses
- latency-throughput curve with saturation-knee detection in event load
- reconnect-storm scenario (simultaneous reconnect after all clients drop)
- per-session backend memory with clients held in a subprocess
- keyed-list browser ops (create/partial-update/select/swap) timed
  through paint on a new /rows page
- compare_performance_results.py for baseline-vs-current regression
  gating with percentage thresholds and absolute floors
- nightly release-scale workflow with a rolling cached baseline

The production app harness is now a session fixture shared by the load,
memory, and browser suites to avoid repeated production builds.
@FarhanAliRaza FarhanAliRaza marked this pull request as ready for review July 13, 2026 16:26
@FarhanAliRaza FarhanAliRaza requested a review from a team as a code owner July 13, 2026 16:26

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1d88639608

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread tests/benchmarks/support/events.py Outdated
Alek99 and others added 7 commits July 13, 2026 11:12
- guard the perf Redis helper: require an explicit db index in
  REFLEX_PERF_REDIS_URL and refuse to run against a non-empty database;
  make cleanup exception-safe and flush before close
- measure the bundle budget against the shipped client bundle
  (.web/build/client) instead of all of .web; budget 500KB gzip (~2x a
  measured 238KB export), overridable via REFLEX_PERFORMANCE_JS_GZIP_BUDGET
- bound all event-future waits in the PR-smoke event-loop suite with
  scale-aware timeouts; add timeout-minutes to the benchmarks CI job
- benchmark the real header-decode path via reflex.app._decode_asgi_headers
  instead of a local copy; re.purge() per round so route-table construction
  is actually cold; drop asyncio.get_event_loop() from sync benchmarks
- move processor construction/lifecycle out of measured regions in the
  event-loop benchmarks; assert shutdown drain actually drains; raise the
  drain budget so instrumentation slowdown cannot flip drain to cancel
- replace the reconnect-storm settling sleep with token-cleanup polling,
  scale client timeouts with concurrency, and retry transient storm
  failures once before asserting
- kill and report the session holder on wait timeout; add untraced warmup
  cycles before the memory leak gate; sweep pre-collected dev-backend
  children on teardown and retry lost port races
- format each async stack frame once in diagnostics; integer Chrome trace
  tids with thread_name metadata; surface baseline-server thread crashes;
  use the public api_transformer seam; add tests/units/benchmarks/__init__.py
- move the browser smoke into its own CI job and upload performance smoke
  artifacts
…enchmarks' into agent/event-loop-performance-benchmarks
@FarhanAliRaza FarhanAliRaza changed the title Add event-loop performance benchmark suite ENG-10164 Add event-loop performance benchmark suite Jul 13, 2026
@linear-code

linear-code Bot commented Jul 13, 2026

Copy link
Copy Markdown

ENG-10164

Alek99 and others added 3 commits July 13, 2026 13:59
…asserts

Address review findings that several benchmarks did not measure what they
claim:

- socket load client: prime + drain the hydrate/on_load deltas a fresh token
  emits so measured events are 1:1 request/response instead of pipelined two
  deep, fixing the event-load latency curve and framework-overhead comparison
  (which could otherwise report negative overhead).
- browser hydration: gate on the websocket-driven is_hydrated marker instead of
  the prerendered #count text, so the timing spans real hydration; add the
  marker to the load app's index page.
- route matching: seed a cached rxconfig so get_config() is a dict lookup as in
  production, instead of rebuilding Config (sys.path scan + env parse) on every
  match, which was 55-97% of the measured region.
- process_event: purge the shared token per pedantic round so the cold path is
  actually exercised; CodSpeed's warmup otherwise left every round all-warm.
- event-loop leak asserts: capture orphan task/future counts before stop()
  (which clears them), so the checks and metrics can catch a real leak; wait for
  the backend-exception task to drain in the failed-pipeline case.
- wire size: encode with socket.io's compact separators so wire_bytes equals the
  real payload size instead of overstating it ~20%.
- redis helper: close the manager before flushing (via a fresh client) so oplock
  lease write-backs land before the wipe and the next run's empty-db guard holds.
- compiler lifecycle: keep zipping on the backend export so it measures real
  work rather than a no-op.
…th, ordering

Second review pass:

- state-manager cold get: run the measured construction inside an isolated
  RegistrationContext registering only PerformanceState, so it instantiates that
  state's subtree instead of every state in the collected session. Adding or
  removing an unrelated benchmark state no longer shifts this CodSpeed result.
- event queue dispatch / future tree: run the measured dispatch inside the same
  context where start() attached the EventContext, so enqueue takes the
  production EventContext.get().fork() path instead of the LookupError fallback;
  build the event in per-round setup so only queueing and dispatch are measured.
- wall-time ordering scenario: track handlers active per token and assert the
  peak stays 1. Completion-order alone can't catch a regression to concurrent
  same-token dispatch (equal sleeps still finish in order); the overlap counter
  can, and it's keyed per token so independent tokens still run concurrently.
- compiler lifecycle: disable the live PyPI latest-version check so `init`
  timing is not contaminated by a network round-trip.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

skip-changelog For doc/internal changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants