Skip to content

⚗️ Collect WebSocket resource events#4718

Open
bdibon wants to merge 21 commits into
mainfrom
boris.dibon/ws-5-collection
Open

⚗️ Collect WebSocket resource events#4718
bdibon wants to merge 21 commits into
mainfrom
boris.dibon/ws-5-collection

Conversation

@bdibon

@bdibon bdibon commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Motivation

WebSocket connections are a blind spot in RUM resource monitoring. This PR adds WebSocket tracking behind the track_web_sockets experimental feature flag, collecting per-connection metrics (message counts, sizes, timing, handshake duration, close codes) and reporting them as resource events with resource.type: "websocket".

Important

This PR introduces a prototype for collecting WebSocket data, the model is not stable, some fields might be removed / renamed or added in a future PR.

Changes

packages/browser-core

  • webSocketObservable: Instruments the WebSocket constructor and send method via instrumentConstructor/instrumentMethod to emit lifecycle events: connecting, open, message-in, message-out, closed. Tracks payload sizes and clock timestamps for each event.
  • Exports the observable from the package index.
  • Adds ResourceType.WEBSOCKET = 'websocket' to the resource type enum.
  • Adds ExperimentalFeature.TRACK_WEB_SOCKETS = 'track_web_sockets' experimental flag.

packages/browser-rum-core

  • webSocketCollection: Consumes webSocketObservable and maintains an in-memory registry of open connections. Aggregates metrics (message counts/sizes, first-message offsets, longest inbound silence, buffered amount peaks) and emits WEBSOCKET_COMPLETED lifecycle events on close or session expiry.
  • resourceCollection: Subscribes to WEBSOCKET_COMPLETED and assembles RumResourceEvent objects with resource.type: "websocket" and a resource.websocket sub-object containing the full connection metrics.
  • lifeCycle: Adds the WEBSOCKET_COMPLETED event type and its payload type.
  • rawRumEvent.types: Adds WebSocketResourceProperties interface and wires it into RawRumResourceEvent.
  • startRum: Starts webSocketCollection when the TRACK_WEB_SOCKETS experimental feature is enabled.

scripts/dev-server

  • Adds Access-Control-Allow-Origin: * response header to support cross-origin WebSocket testing from the sandbox.

Test instructions

Run unit tests:

yarn test:unit --spec packages/browser-core/src/browser/webSocketObservable.spec.ts
yarn test:unit --spec packages/browser-rum-core/src/domain/webSocketCollection.spec.ts
yarn test:unit --spec packages/browser-rum-core/src/domain/resource/resourceCollection.spec.ts

Manual end-to-end test:

# Terminal 1: Start dev server
yarn dev-server start

# Terminal 2: Spin up a local WebSocket echo server (uses the ws package already in the repo)
yarn node -e "
  const WebSocket = require('ws');
  const wss = new WebSocket.Server({ port: 8765 });
  wss.on('connection', ws => { ws.on('message', msg => ws.send(msg)); });
  console.log('WS echo server on :8765');
"

Open http://localhost:8080 and replace sandbox/index.html with:

<script>
  DD_RUM.init({
    applicationId: '...',
    clientToken: '...',
    trackResources: true,
    enableExperimentalFeatures: ['track_web_sockets'],
    proxy: '/proxy',
  })
</script>
<button onclick="
  let ws = new WebSocket('ws://localhost:8765');
  ws.onopen = () => { ws.send('hello'); ws.send('world'); };
  ws.onmessage = e => console.log('in:', e.data);
  setTimeout(() => ws.close(1000, 'done'), 2000);
">Test WebSocket</button>

Click the button, wait ~3 seconds, then open a new tab to flush events:

yarn dev-server intake rum-resources | jq 'select(.resource.type == "websocket")'

Expected output — a resource event with:

{
  "resource": {
    "type": "websocket",
    "url": "ws://localhost:8765",
    "websocket": {
      "handshake_succeeded": true,
      "messages_in": { "count": 2, "size": 22 },
      "messages_out": { "count": 2, "size": 22 },
      "close_code": 1000,
      "was_clean": true,
      "tracking_end_reason": "close_event"
    }
  }
}

Checklist

  • Tested locally
  • Tested on staging
  • Added unit tests for this change.
  • Added e2e/integration tests for this change.
  • Updated documentation and/or relevant AGENTS.md file

bdibon commented Jun 3, 2026

Copy link
Copy Markdown
Contributor Author

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more

This stack of pull requests is managed by Graphite. Learn more about stacking.

@datadog-datadog-prod-us1-2

datadog-datadog-prod-us1-2 Bot commented Jun 3, 2026

Copy link
Copy Markdown

Tests

🎉 All green!

🧪 All tests passed
❄️ No new flaky tests detected

🎯 Code Coverage (details)
Patch Coverage: 79.67%
Overall Coverage: 76.90% (+0.03%)

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 9bced06 | Docs | Datadog PR Page | Give us feedback!

@cit-pr-commenter-54b7da

cit-pr-commenter-54b7da Bot commented Jun 3, 2026

Copy link
Copy Markdown

Bundles Sizes Evolution

📦 Bundle Name Base Size Local Size 𝚫 𝚫% Status
Rum 172.46 KiB 177.88 KiB +5.42 KiB +3.14%
Rum Profiler 8.22 KiB 8.22 KiB 0 B 0.00%
Rum Recorder 21.09 KiB 21.09 KiB -1 B -0.00%
Logs 54.47 KiB 54.51 KiB +40 B +0.07%
Rum Slim 129.97 KiB 135.17 KiB +5.19 KiB +4.00%
Worker 22.96 KiB 22.96 KiB 0 B 0.00%

@bdibon bdibon force-pushed the boris.dibon/ws-5-collection branch from f9f774b to f67e33e Compare June 4, 2026 09:16
@bdibon bdibon force-pushed the boris.dibon/ws-4-track-config branch from ec073a8 to 98e75c8 Compare June 4, 2026 09:16
@bdibon bdibon force-pushed the boris.dibon/ws-5-collection branch from f67e33e to adaf9f3 Compare June 4, 2026 12:30
@bdibon bdibon force-pushed the boris.dibon/ws-4-track-config branch from 98e75c8 to 1afcc3a Compare June 4, 2026 12:30
@bdibon bdibon changed the base branch from boris.dibon/ws-4-track-config to graphite-base/4718 June 8, 2026 14:48
@bdibon bdibon force-pushed the boris.dibon/ws-5-collection branch from adaf9f3 to cb90533 Compare June 10, 2026 11:39
@bdibon bdibon force-pushed the graphite-base/4718 branch from 1afcc3a to 9b239da Compare June 12, 2026 14:54
@bdibon bdibon force-pushed the boris.dibon/ws-5-collection branch from cb90533 to 7fc9595 Compare June 12, 2026 14:54
@bdibon bdibon changed the base branch from graphite-base/4718 to boris.dibon/ws-4-track-config June 12, 2026 14:54
@bdibon bdibon force-pushed the boris.dibon/ws-5-collection branch from 7fc9595 to 770842b Compare June 12, 2026 15:10
@bdibon bdibon changed the title ✨ Collect WebSocket resource events ⚗️ Collect WebSocket resource events Jun 12, 2026
@bdibon bdibon changed the base branch from boris.dibon/ws-4-track-config to main June 12, 2026 15:12
@bdibon bdibon marked this pull request as ready for review June 12, 2026 15:23
@bdibon bdibon requested a review from a team as a code owner June 12, 2026 15:23

@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: 770842b8f7

ℹ️ 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 packages/browser-rum-core/src/domain/resource/resourceCollection.ts
Comment thread packages/browser-rum-core/src/domain/resource/resourceCollection.ts Outdated
Comment thread packages/browser-core/src/browser/webSocketObservable.ts Outdated
Comment thread packages/browser-rum-core/src/domain/resource/resourceCollection.ts
Comment thread packages/browser-rum-core/src/domain/resource/resourceCollection.ts Outdated
Comment thread packages/browser-core/src/browser/webSocketObservable.ts
Comment thread packages/browser-rum-core/src/boot/startRum.ts
@bdibon bdibon changed the base branch from main to graphite-base/4718 June 15, 2026 11:27
@bdibon bdibon force-pushed the boris.dibon/ws-5-collection branch from 770842b to 4251e33 Compare June 15, 2026 11:27
@bdibon bdibon changed the base branch from graphite-base/4718 to boris.dibon/ws-4-track-config June 15, 2026 11:27
@bdibon bdibon changed the base branch from boris.dibon/ws-4-track-config to main June 15, 2026 14:33
@bdibon

bdibon commented Jun 16, 2026

Copy link
Copy Markdown
Contributor Author

/to-staging

@gh-worker-devflow-routing-ef8351

gh-worker-devflow-routing-ef8351 Bot commented Jun 16, 2026

Copy link
Copy Markdown

View all feedbacks in Devflow UI.

2026-06-16 08:11:45 UTC ℹ️ Start processing command /to-staging


2026-06-16 08:11:51 UTC ℹ️ Branch Integration: starting soon, merge expected in approximately 0s (p90)

Commit 4251e33ae1 will soon be integrated into staging-25.


2026-06-16 08:30:42 UTC ℹ️ Branch Integration: this commit was successfully integrated

Commit 4251e33ae1 has been merged into staging-25 in merge commit 07d8c51b44.

If you need to revert this integration, you can use the following command: /code revert-integration -b staging-25

gh-worker-dd-mergequeue-cf854d Bot added a commit that referenced this pull request Jun 16, 2026
Integrated commit sha: 4251e33

Co-authored-by: bdibon <boris.dibon@datadoghq.com>
Comment thread packages/browser-core/src/browser/webSocketObservable.ts Outdated
Comment thread packages/browser-core/src/browser/webSocketObservable.ts Outdated
Comment thread packages/browser-core/src/browser/webSocketObservable.ts Outdated

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

💬 suggestion: ‏what about moving this file and its tests to the resource directory?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Should we move packages/browser-rum-core/src/domain/requestCollection.ts as well?

Comment thread packages/browser-core/src/domain/resourceUtils.ts
startClocks: event.startClocks,
duration,
rawRumEvent,
domainContext: {},

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

💭 thought: ‏could it be worth providing the web socket instance in the domain context?
if yes, it could be added later down the road

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I don't see any value right now. What do you have in mind?

Comment thread packages/browser-rum-core/src/rawRumEvent.types.ts Outdated
Comment thread packages/browser-core/src/index.ts Outdated
Comment on lines +68 to +70
const sessionExpiredSubscription = lifeCycle.subscribe(LifeCycleEventType.SESSION_EXPIRED, () => {
tracker.flushOpenConnections('session_end')
})

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

❓ question: ‏what do we want to do for websocket connection that spans across multiple sessions?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

For the prototype we'll just cut the tracking with the reason "session_end".

Comment thread packages/browser-rum-core/src/domain/webSocketCollection.ts
@bdibon bdibon requested review from a team as code owners June 19, 2026 14:45
@bdibon bdibon force-pushed the boris.dibon/ws-5-collection branch from a67b7fb to 9e9b3ca Compare June 19, 2026 14:55

@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: 9e9b3cad80

ℹ️ 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 packages/browser-rum-core/src/domain/webSocketCollection.ts
Comment thread packages/browser-rum-core/src/domain/webSocketCollection.ts
Comment thread packages/browser-core/src/browser/webSocketObservable.ts Outdated
Comment thread packages/browser-rum-core/src/domain/lifeCycle.ts
Comment thread packages/browser-core/src/domain/resourceUtils.ts
@bdibon bdibon force-pushed the boris.dibon/ws-5-collection branch from 9e9b3ca to 6ffe09f Compare June 19, 2026 16:03

@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: 6ffe09f7cb

ℹ️ 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".

const tracker = trackWebSocket(lifeCycle, initWebSocketObservable(), viewHistory, addDurationVital)

const sessionExpiredSubscription = lifeCycle.subscribe(LifeCycleEventType.SESSION_EXPIRED, () => {
tracker.flushOpenConnections('session_end')

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep tracking open sockets after renewal

When a session expires while a WebSocket remains open, this call emits a session_end resource and flushOpenConnections clears the registry. The browser keeps using the same WebSocket instance, so after the session is renewed later message-* and close contexts have no new connecting event to re-register the socket and are ignored, causing long-lived sockets to stop producing websocket resources for the rest of the page. Keep or re-seed open connections across the session boundary so the renewed session can still be measured.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We will address this in a future PR.

Comment thread packages/browser-rum-core/src/domain/resource/resourceCollection.ts
Comment thread packages/browser-rum-core/src/domain/webSocketCollection.ts Outdated
@bdibon bdibon requested a review from bcaudan June 22, 2026 14:35
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.

2 participants