Skip to content

perf(daemon): bridge tracing events to /ws/logs (partial #66)#111

Merged
zackees merged 1 commit intomainfrom
perf/daemon-broadcast-log-layer
Apr 18, 2026
Merged

perf(daemon): bridge tracing events to /ws/logs (partial #66)#111
zackees merged 1 commit intomainfrom
perf/daemon-broadcast-log-layer

Conversation

@zackees
Copy link
Copy Markdown
Member

@zackees zackees commented Apr 18, 2026

Summary

  • Adds BroadcastLogLayer (crates/fbuild-daemon/src/log_layer.rs), a tracing-subscriber Layer that forwards events to the existing BroadcastHub::log_tx channel so /ws/logs subscribers see the same output the daemon writes to stderr.
  • Native ESP32 write-flash progress already emits via tracing::info!() in fbuild-deploy::esp32_native; this layer makes those per-region and 10%-throttled progress lines visible on the WebSocket stream — closing the follow-up from the perf(deploy): replace esptool subprocess with espflash native crate #66 code comments / issue comment without adding a separate progress API.
  • BroadcastHub is constructed in main.rs before tracing init and threaded into DaemonContext::with_hub(...) so the layer can be registered against log_tx before the first tracing::info!.

Behavior notes

Test plan

  • uv run cargo check -p fbuild-daemon --all-targets
  • uv run cargo clippy -p fbuild-daemon --all-targets -- -D warnings
  • uv run cargo test -p fbuild-daemon --lib (111 passed; 4 new log_layer tests)
  • Manual: open /ws/logs against a local daemon during an ESP32 deploy with FBUILD_USE_ESPFLASH_WRITE=1 and confirm progress frames stream live.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Refactor
    • Enhanced logging pipeline architecture to better support live log streaming via WebSocket.
    • Improved flexibility in how broadcast channels are initialized and supplied within the daemon infrastructure.

Adds `BroadcastLogLayer`, a `tracing-subscriber` layer that forwards
events to the existing `BroadcastHub::log_tx` channel so WebSocket
clients subscribed to `/ws/logs` see the same events the daemon
writes to stderr. The native ESP32 `write-flash` path in
`fbuild-deploy::esp32_native` already emits per-region and
10%-throttled progress via `tracing::info!()`; this layer surfaces
that progress on the WebSocket stream without any new API surface —
closing the follow-up noted in #66 comments.

Design notes:

- Layer early-outs on `receiver_count() == 0`, so the common "no
  `/ws/logs` clients attached" case pays a single atomic load per
  event — no JSON serialization, no allocation.
- Events from `handlers::websockets` are dropped by module filter to
  avoid feeding client connect/disconnect notices back onto the
  channel they announce.
- `BroadcastHub` is now built in `main.rs` before tracing init and
  passed into `DaemonContext::with_hub(...)` so the layer can be
  registered against `log_tx` before the first `tracing::info!`.
  The original `DaemonContext::new(...)` is retained as a thin
  wrapper for tests.
- Payload shape matches the welcome frame `/ws/logs` already emits
  (`{"type":"log","level":..,"message":..,"timestamp":..,"module":..}`)
  so clients parse every line with one schema.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@zackees zackees merged commit e7aad0e into main Apr 18, 2026
7 of 77 checks passed
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 18, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e4e86f92-af01-4fef-8470-432ae3e77a32

📥 Commits

Reviewing files that changed from the base of the PR and between 53a077a and 5ad1d78.

📒 Files selected for processing (4)
  • crates/fbuild-daemon/src/context.rs
  • crates/fbuild-daemon/src/lib.rs
  • crates/fbuild-daemon/src/log_layer.rs
  • crates/fbuild-daemon/src/main.rs

📝 Walkthrough

Walkthrough

The changes introduce a broadcast logging layer that intercepts tracing events and publishes them to a shared channel, enabling external components to subscribe to log messages. A new BroadcastLogLayer is implemented, DaemonContext gains a constructor accepting an external BroadcastHub, and main.rs wires these components together with the tracing subscriber pipeline.

Changes

Cohort / File(s) Summary
Logging Layer Implementation
crates/fbuild-daemon/src/log_layer.rs
New BroadcastLogLayer struct implementing tracing_subscriber::Layer to intercept events, render them as JSON with metadata, and broadcast via a shared channel. Includes optimization to skip serialization when no receivers exist and filters out WebSocket module events.
Daemon Infrastructure
crates/fbuild-daemon/src/context.rs, crates/fbuild-daemon/src/lib.rs
Added DaemonContext::with_hub() constructor accepting external BroadcastHub; refactored new() to delegate to it. Exported log_layer module for public access.
Daemon Initialization
crates/fbuild-daemon/src/main.rs
Updated tracing subscriber setup to include BroadcastLogLayer wired to a shared broadcast channel. Switched DaemonContext construction to use with_hub(), passing the configured BroadcastHub instance.

Sequence Diagram

sequenceDiagram
    participant App as Application
    participant TS as Tracing System
    participant BLL as BroadcastLogLayer
    participant Hub as BroadcastHub
    participant WS as WebSocket Handler

    App->>TS: Emit tracing event
    TS->>BLL: Invoke on_event()
    alt Receivers exist
        BLL->>BLL: Render message + fields
        BLL->>BLL: Serialize to JSON
        BLL->>Hub: Broadcast message
        Hub->>WS: Send to subscribers
    else No receivers
        BLL->>BLL: Skip (early return)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A hop through the logs we now shall take,
Tracing events dance in the wake,
Broadcast channels sing with glee,
WebSocket friends shall always see,
The daemon's heart laid bare and free! 📡✨

✨ 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/daemon-broadcast-log-layer

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 and usage tips.

@zackees zackees deleted the perf/daemon-broadcast-log-layer branch April 18, 2026 18:52
zackees added a commit that referenced this pull request Apr 18, 2026
Two orthogonal micro-optimizations that shave the remaining server-side
cost off the session-trusted verify-skip path (#116). Targets the best-case
warm-deploy budget of < 1 s end-to-end (see #114 comment):

## `ImageHashMemo` on `DaemonContext`

Warm redeploys re-read bootloader + partitions + firmware (~2–4 MB) and
re-hash them on every call, costing ~5–15 ms. The memo keys on firmware
path + the three files' `mtime` tuple — when all three match, reuse the
stored hash instead of touching disk. Self-invalidates when any file's
`mtime` advances (i.e. the next build produced new output).

## `DeviceManager::refresh_devices_if_stale`

`refresh_devices()` costs ~20–30 ms on Windows (OS port enumeration).
The trust-hash path called it on every deploy to keep
`last_disconnect_at` up to date, but a 2 s freshness window is plenty
to catch a physical unplug/replug between two warm deploys. The new
`refresh_devices_if_stale(Duration)` short-circuits inside the window.

## Impact on the best-case arithmetic

Server-side cost on a warm trust-skip deploy drops from ~50 ms
(refresh + SHA-256 + lookup + early return) to ~1–2 ms (DashMap
probe + metadata stat for the 3 files). Combined with the #116
trust-skip early return and #111 /ws/logs stream, that puts the
<1 s best-case target (#114 comment) squarely in reach once the
in-memory build fingerprint (#91 follow-up) lands.

## Tests

5 new unit tests:
- `device_manager::refresh_devices_if_stale_skips_inside_window`
- `device_manager::refresh_devices_if_stale_reruns_when_expired`
- `image_hash_memo_tests::memo_hit_reuses_hash`
- `image_hash_memo_tests::memo_miss_on_firmware_mtime_change`
- `image_hash_memo_tests::memo_skipped_when_inputs_missing`

All 121 `fbuild-daemon` lib tests pass; clippy `-D warnings` clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
zackees added a commit that referenced this pull request Apr 18, 2026
…dow (#118)

Two orthogonal micro-optimizations that shave the remaining server-side
cost off the session-trusted verify-skip path (#116). Targets the best-case
warm-deploy budget of < 1 s end-to-end (see #114 comment):

## `ImageHashMemo` on `DaemonContext`

Warm redeploys re-read bootloader + partitions + firmware (~2–4 MB) and
re-hash them on every call, costing ~5–15 ms. The memo keys on firmware
path + the three files' `mtime` tuple — when all three match, reuse the
stored hash instead of touching disk. Self-invalidates when any file's
`mtime` advances (i.e. the next build produced new output).

## `DeviceManager::refresh_devices_if_stale`

`refresh_devices()` costs ~20–30 ms on Windows (OS port enumeration).
The trust-hash path called it on every deploy to keep
`last_disconnect_at` up to date, but a 2 s freshness window is plenty
to catch a physical unplug/replug between two warm deploys. The new
`refresh_devices_if_stale(Duration)` short-circuits inside the window.

## Impact on the best-case arithmetic

Server-side cost on a warm trust-skip deploy drops from ~50 ms
(refresh + SHA-256 + lookup + early return) to ~1–2 ms (DashMap
probe + metadata stat for the 3 files). Combined with the #116
trust-skip early return and #111 /ws/logs stream, that puts the
<1 s best-case target (#114 comment) squarely in reach once the
in-memory build fingerprint (#91 follow-up) lands.

## Tests

5 new unit tests:
- `device_manager::refresh_devices_if_stale_skips_inside_window`
- `device_manager::refresh_devices_if_stale_reruns_when_expired`
- `image_hash_memo_tests::memo_hit_reuses_hash`
- `image_hash_memo_tests::memo_miss_on_firmware_mtime_change`
- `image_hash_memo_tests::memo_skipped_when_inputs_missing`

All 121 `fbuild-daemon` lib tests pass; clippy `-D warnings` clean.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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