Skip to content

Commit fd4d4d3

Browse files
committed
review: add readme for cdp monitor
1 parent bca495b commit fd4d4d3

2 files changed

Lines changed: 79 additions & 8 deletions

File tree

server/lib/cdpmonitor/README.md

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# CDP Monitor
2+
3+
The monitor is the browser-facing layer of the kernel browser logging pipeline. It connects to Chrome's DevTools endpoint, tracks all page sessions via CDP's `Target.setAutoAttach`, and converts raw CDP notifications into typed `events.Event` values for downstream consumers.
4+
5+
## Overview
6+
7+
`cdpmonitor` manages a Chrome DevTools Protocol (CDP) WebSocket connection to a running Chrome browser. It subscribes to CDP events across all attached tabs, translates them into structured `events.Event` values, and publishes them via a caller-supplied `PublishFunc`. It also derives synthetic events from sequences of CDP events and takes screenshots on significant page activity.
8+
9+
Chrome can restart independently of the monitor. When that happens, `UpstreamProvider` pushes a new DevTools URL and the monitor reconnects automatically, emitting lifecycle events so consumers can track continuity.
10+
11+
## Event taxonomy
12+
13+
**CDP-derived** (1-to-1 with a CDP notification): `console_log`, `console_error`, `network_request`, `network_response`, `network_loading_failed`, `navigation`, `dom_content_loaded`, `page_load`, `layout_shift`
14+
15+
**Computed** (inferred from sequences of CDP events): `network_idle` (fires when in-flight requests drop to zero), `layout_settled` (1 s after `page_load` with no intervening layout shifts), `navigation_settled` (fires once `dom_content_loaded`, `network_idle`, and `layout_settled` have all fired for the same navigation).
16+
17+
**Interaction** (fired by `interaction.js` via `Runtime.bindingCalled`): `interaction_click`, `interaction_key`, `scroll_settled`
18+
19+
**Monitor lifecycle** (emitted by the monitor itself, not by Chrome): `screenshot`, `monitor_disconnected`, `monitor_reconnected`, `monitor_reconnect_failed`, `monitor_init_failed`
20+
21+
## Responsibilities
22+
23+
| Concern | Where |
24+
| --- | --- |
25+
| WebSocket lifecycle (connect, read, reconnect) | `monitor.go` |
26+
| CDP domain setup per session | `domains.go` |
27+
| Event translation (CDP params to `events.Event`) | `handlers.go` |
28+
| Synthetic event state machines | `computed.go` |
29+
| Screenshot capture via ffmpeg | `screenshot.go` |
30+
| CDP protocol types | `cdp_proto.go`, `types.go` |
31+
| Interaction tracking injected into the page | `interaction.js` |
32+
| Body/MIME capture sizing and text truncation helpers | `util.go` |
33+
34+
## Internals
35+
36+
### Reconnect model
37+
38+
`subscribeToUpstream` listens to `UpstreamProvider.Subscribe()` for new DevTools URLs. On each URL change (indicating Chrome restarted), `handleUpstreamRestart` tears down the existing connection, dials the new URL with capped-exponential backoff (250 ms → 500 ms → 1 s → 2 s, up to 10 attempts), then restarts `readLoop` and re-initializes all CDP sessions. `restartMu` serializes concurrent restart signals so rapid Chrome restarts do not produce overlapping reconnects.
39+
40+
### Goroutines
41+
42+
| Goroutine | Lifetime | Tracked by |
43+
| --- | --- | --- |
44+
| `readLoop` | one per WebSocket connection | `done` channel |
45+
| `subscribeToUpstream` | same as `lifecycleCtx` | `asyncWg` |
46+
| `sweepPendingRequests` | same as `lifecycleCtx` | `asyncWg` |
47+
| `initSession` | short-lived, one per connect or reconnect | `asyncWg` |
48+
| `attachExistingTargets` wrapper | short-lived, one per existing target on reconnect | `asyncWg` |
49+
| `enableDomains` + `injectScript` | short-lived, one per target attach | `asyncWg` |
50+
| `fetchResponseBody` | one per completed network request | `asyncWg` |
51+
| `captureScreenshot` | one per screenshot trigger | `asyncWg` |
52+
53+
`Stop()` cancels `lifecycleCtx`, waits for `readLoop` via `done`, then waits for all other goroutines via `asyncWg` before closing the connection.
54+
55+
### Lock ordering
56+
57+
Locks must be acquired left to right. Never hold a lock on the left while acquiring one further right.
58+
59+
```
60+
restartMu -> lifeMu -> pendReqMu -> computed.mu -> pendMu -> sessionsMu
61+
```
62+
63+
`bindingRateMu` is independent of this ordering and is always acquired alone.
64+
65+
| Lock | Protects |
66+
| --- | --- |
67+
| `restartMu` | Serializes `handleUpstreamRestart` to prevent overlapping reconnects from rapid Chrome restarts |
68+
| `lifeMu` | `conn`, `lifecycleCtx`, `cancel`, `done`, `readReady` -- all fields that change during Start / Stop / reconnect |
69+
| `pendReqMu` | `pendingRequests` (requestId -> `networkReqState`): in-flight network requests accumulating request/response metadata until `loadingFinished` |
70+
| `computed.mu` | All `computedState` fields: counters and timers for the `network_idle`, `layout_settled`, and `navigation_settled` state machines |
71+
| `pendMu` | `pending` (id -> reply channel): in-flight CDP commands waiting for a response from Chrome |
72+
| `sessionsMu` | `sessions` (sessionID -> `targetInfo`): the set of currently attached CDP targets (tabs, iframes, workers) |
73+
| `bindingRateMu` | `bindingLastSeen` (sessionID:eventType -> time): rate-limit state for `__kernelEvent` binding calls |
74+
75+
Fields that need no mutex use `sync/atomic`: `nextID`, `mainSessionID`, `running`, `lastScreenshotAt`, `screenshotInFlight`.
76+
77+
### WebSocket concurrency
78+
79+
`coder/websocket` guarantees one concurrent `Read` and one concurrent `Write` are safe on the same connection. `readLoop` is the sole reader. All writes go through `send`, which calls `conn.Write` directly -- `conn.Write` is internally serialized by the library, so no external write mutex is needed.

server/lib/cdpmonitor/monitor.go

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,6 @@ type PublishFunc func(ev events.Event)
2525
const wsReadLimit = 8 * 1024 * 1024
2626

2727
// Monitor manages a CDP WebSocket connection with auto-attach session fan-out.
28-
//
29-
// Lock ordering (outer → inner):
30-
//
31-
// restartMu → lifeMu → pendReqMu → computed.mu → pendMu → sessionsMu
32-
//
33-
// Never acquire a lock that appears later in this order while holding an
34-
// earlier one, to prevent deadlock.
35-
//
3628
// WebSocket concurrency: coder/websocket guarantees that one concurrent Read
3729
// and one concurrent Write are safe. The readLoop holds the sole Read; all
3830
// writes go through send, which serialises them with conn.Write's internal

0 commit comments

Comments
 (0)