diff --git a/lib/cjs/generated/rum.d.ts b/lib/cjs/generated/rum.d.ts index dde1ceab..1eb60b91 100644 --- a/lib/cjs/generated/rum.d.ts +++ b/lib/cjs/generated/rum.d.ts @@ -895,7 +895,7 @@ export type RumViewUpdateEvent = ViewContainerSchema & StreamSchema & ViewProper }; [k: string]: unknown; }; -export type RumVitalEvent = RumVitalDurationEvent | RumVitalOperationStepEvent | RumVitalAppLaunchEvent; +export type RumVitalEvent = RumVitalDurationEvent | RumVitalOperationStepEvent | RumVitalAppLaunchEvent | RumVitalWebsocketConnectingEvent | RumVitalWebsocketOpenEvent | RumVitalWebsocketClosingEvent | RumVitalWebsocketClosedEvent; /** * Schema for a duration vital event. */ @@ -1021,6 +1021,188 @@ export type RumVitalAppLaunchEvent = RumVitalEventCommonProperties & { }; [k: string]: unknown; }; +/** + * Schema for the vital reported when a WebSocket connection is created. Reported exactly once per connection, when the WebSocket is constructed. Reported with vital.name websocket_connecting + */ +export type RumVitalWebsocketConnectingEvent = RumVitalWebsocketCommonProperties & { + /** + * Vital properties + */ + readonly vital?: { + /** + * WebSocket properties. The identity of the connection is reported on this vital only, and is not repeated on the vitals of the later phases + */ + readonly websocket?: { + /** + * URL the connection was opened on, without its query string + */ + readonly url: string; + /** + * Subprotocols the connection was requested with. Omitted when none was requested + */ + readonly requested_protocols?: string[]; + /** + * Date the WebSocket was constructed, in ms from epoch + */ + readonly connecting_date: number; + [k: string]: unknown; + }; + [k: string]: unknown; + }; + [k: string]: unknown; +}; +/** + * Schema of common properties for a WebSocket vital event. One connection reports a stream of vitals over its lifetime, one per phase, all sharing the same vital.websocket.id + */ +export type RumVitalWebsocketCommonProperties = RumVitalEventCommonProperties & { + /** + * Vital properties + */ + readonly vital?: { + /** + * Type of the vital. + */ + readonly type: 'websocket'; + /** + * Name of the vital, identifying the phase of the connection it reports: websocket_connecting, websocket_open, websocket_closing or websocket_closed + */ + readonly name: string; + /** + * WebSocket properties + */ + readonly websocket: { + /** + * UUID of the WebSocket connection, stable across every vital reported for that connection. It is not vital.id, which identifies a single vital + */ + readonly id: string; + [k: string]: unknown; + }; + [k: string]: unknown; + }; + [k: string]: unknown; +}; +/** + * Schema for the vital reported when a WebSocket connection opens, then repeatedly as a heartbeat while it stays open. Reported with vital.name websocket_open + */ +export type RumVitalWebsocketOpenEvent = RumVitalWebsocketCommonProperties & { + /** + * Vital properties + */ + readonly vital?: { + /** + * WebSocket properties + */ + readonly websocket?: { + /** + * Whether the opening handshake succeeded + */ + readonly open_handshake_succeeded: boolean; + /** + * Interval between the connecting date and the open date of the connection, in nanoseconds + */ + readonly connecting_duration: number; + /** + * Date the connection opened, in ms from epoch + */ + readonly open_date: number; + /** + * Subprotocol the server selected. Omitted when the server selected none + */ + readonly selected_protocol?: string; + /** + * Extensions the server negotiated, as the single string the connection exposes them as. Omitted when none was negotiated + */ + readonly selected_extensions?: string; + /** + * Version of the snapshot, incremented on every vital reporting one, so that heartbeats can be ordered when they are received out of sequence + */ + readonly snapshot_version: number; + /** + * Values of the connection at the time the vital was reported + */ + readonly snapshot: RumVitalWebsocketSnapshot; + [k: string]: unknown; + }; + [k: string]: unknown; + }; + [k: string]: unknown; +}; +/** + * Schema for the vital reported when a WebSocket connection starts closing. Reported at most once per connection. Reported with vital.name websocket_closing + */ +export type RumVitalWebsocketClosingEvent = RumVitalWebsocketCommonProperties & { + /** + * Vital properties + */ + readonly vital?: { + /** + * WebSocket properties + */ + readonly websocket?: { + /** + * Date the connection was asked to close, in ms from epoch + */ + readonly closing_date: number; + /** + * Side that initiated the close. Only reported on this vital, and only as client, since a close observed from the application is always initiated by it. The other values are reported by the connection-level data derived from these vitals + */ + readonly close_initiator: 'client' | 'server' | 'unknown'; + [k: string]: unknown; + }; + [k: string]: unknown; + }; + [k: string]: unknown; +}; +/** + * Schema for the vital reported when a WebSocket connection stops being tracked, whether because it closed or because tracking ended first. Reported at most once per connection. Reported with vital.name websocket_closed + */ +export type RumVitalWebsocketClosedEvent = RumVitalWebsocketCommonProperties & { + /** + * Vital properties + */ + readonly vital?: { + /** + * WebSocket properties + */ + readonly websocket?: { + /** + * Date tracking of the connection ended, in ms from epoch. It is the date the connection closed when the tracking end reason is close_event, and the date tracking stopped otherwise + */ + readonly closed_date: number; + /** + * Interval between the connecting date and the closed date of the connection, in nanoseconds. It is not vital.duration, which is reported by duration vitals + */ + readonly duration: number; + /** + * Why tracking of the connection ended + */ + readonly tracking_end_reason: 'close_event' | 'session_end' | 'page_unloaded'; + /** + * Code the connection was closed with. Reported when, and only when, the tracking end reason is close_event + */ + readonly close_code?: number; + /** + * Reason the connection was closed with, as an empty string when the other side supplied none. Reported when, and only when, the tracking end reason is close_event + */ + readonly close_reason?: string; + /** + * Whether the connection closed cleanly. Reported when, and only when, the tracking end reason is close_event + */ + readonly was_clean?: boolean; + /** + * Version of the snapshot, incremented on every vital reporting one, so that heartbeats can be ordered when they are received out of sequence + */ + readonly snapshot_version: number; + /** + * Values of the connection when tracking ended. Omitted when the connection never opened + */ + readonly snapshot?: RumVitalWebsocketSnapshot; + [k: string]: unknown; + }; + [k: string]: unknown; + }; + [k: string]: unknown; +}; /** * Schema of common properties of RUM events */ @@ -2283,3 +2465,61 @@ export interface ViewAccessibilityProperties { readonly rtl_enabled?: boolean; [k: string]: unknown; } +/** + * Schema of the WebSocket connection values that change from one vital of a connection to the next. Both directions are present whenever the snapshot is, and are zero-filled while no message has been observed + */ +export interface RumVitalWebsocketSnapshot { + /** + * Messages received from the server + */ + readonly inbound: RumVitalWebsocketMessageDirection; + /** + * Messages sent to the server + */ + readonly outbound: RumVitalWebsocketMessageDirection & { + /** + * Largest amount of bytes ever observed queued in the send buffer, sampled after each send is enqueued + */ + readonly buffered_amount_max: number; + /** + * Number of sends issued while the send buffer was already deep, that is while it held at least 65536 bytes right before the send + */ + readonly backpressured_message_count: number; + /** + * Amount of bytes still queued in the send buffer when tracking ended, in bytes. Set on the websocket_closed vital only + */ + readonly buffered_amount_at_close?: number; + [k: string]: unknown; + }; + [k: string]: unknown; +} +/** + * Schema of the messages observed in one direction of a WebSocket connection + */ +export interface RumVitalWebsocketMessageDirection { + /** + * Number of messages observed in this direction since the connection opened + */ + readonly message_count: number; + /** + * Sum of the payload sizes of the messages observed in this direction, in bytes + */ + readonly message_size_total: number; + /** + * Size of the largest single payload observed in this direction, in bytes. 0 while no message has been observed + */ + readonly message_size_max: number; + /** + * Longest interval between two consecutive messages observed in this direction, in nanoseconds, including the still-open interval since the last message. 0 until two messages have been observed. The interval before the first message is excluded from it, as it is reported by time_to_first_message + */ + readonly longest_silence: number; + /** + * Interval between the open date of the connection and the first message observed in this direction, in nanoseconds. Omitted until a first message has been observed + */ + readonly time_to_first_message?: number; + /** + * Interval between the last message observed in this direction and the closed date of the connection, in nanoseconds. Set on the websocket_closed vital only, and only when at least one message has been observed in this direction + */ + readonly silence_before_close?: number; + [k: string]: unknown; +} diff --git a/lib/esm/generated/rum.d.ts b/lib/esm/generated/rum.d.ts index dde1ceab..1eb60b91 100644 --- a/lib/esm/generated/rum.d.ts +++ b/lib/esm/generated/rum.d.ts @@ -895,7 +895,7 @@ export type RumViewUpdateEvent = ViewContainerSchema & StreamSchema & ViewProper }; [k: string]: unknown; }; -export type RumVitalEvent = RumVitalDurationEvent | RumVitalOperationStepEvent | RumVitalAppLaunchEvent; +export type RumVitalEvent = RumVitalDurationEvent | RumVitalOperationStepEvent | RumVitalAppLaunchEvent | RumVitalWebsocketConnectingEvent | RumVitalWebsocketOpenEvent | RumVitalWebsocketClosingEvent | RumVitalWebsocketClosedEvent; /** * Schema for a duration vital event. */ @@ -1021,6 +1021,188 @@ export type RumVitalAppLaunchEvent = RumVitalEventCommonProperties & { }; [k: string]: unknown; }; +/** + * Schema for the vital reported when a WebSocket connection is created. Reported exactly once per connection, when the WebSocket is constructed. Reported with vital.name websocket_connecting + */ +export type RumVitalWebsocketConnectingEvent = RumVitalWebsocketCommonProperties & { + /** + * Vital properties + */ + readonly vital?: { + /** + * WebSocket properties. The identity of the connection is reported on this vital only, and is not repeated on the vitals of the later phases + */ + readonly websocket?: { + /** + * URL the connection was opened on, without its query string + */ + readonly url: string; + /** + * Subprotocols the connection was requested with. Omitted when none was requested + */ + readonly requested_protocols?: string[]; + /** + * Date the WebSocket was constructed, in ms from epoch + */ + readonly connecting_date: number; + [k: string]: unknown; + }; + [k: string]: unknown; + }; + [k: string]: unknown; +}; +/** + * Schema of common properties for a WebSocket vital event. One connection reports a stream of vitals over its lifetime, one per phase, all sharing the same vital.websocket.id + */ +export type RumVitalWebsocketCommonProperties = RumVitalEventCommonProperties & { + /** + * Vital properties + */ + readonly vital?: { + /** + * Type of the vital. + */ + readonly type: 'websocket'; + /** + * Name of the vital, identifying the phase of the connection it reports: websocket_connecting, websocket_open, websocket_closing or websocket_closed + */ + readonly name: string; + /** + * WebSocket properties + */ + readonly websocket: { + /** + * UUID of the WebSocket connection, stable across every vital reported for that connection. It is not vital.id, which identifies a single vital + */ + readonly id: string; + [k: string]: unknown; + }; + [k: string]: unknown; + }; + [k: string]: unknown; +}; +/** + * Schema for the vital reported when a WebSocket connection opens, then repeatedly as a heartbeat while it stays open. Reported with vital.name websocket_open + */ +export type RumVitalWebsocketOpenEvent = RumVitalWebsocketCommonProperties & { + /** + * Vital properties + */ + readonly vital?: { + /** + * WebSocket properties + */ + readonly websocket?: { + /** + * Whether the opening handshake succeeded + */ + readonly open_handshake_succeeded: boolean; + /** + * Interval between the connecting date and the open date of the connection, in nanoseconds + */ + readonly connecting_duration: number; + /** + * Date the connection opened, in ms from epoch + */ + readonly open_date: number; + /** + * Subprotocol the server selected. Omitted when the server selected none + */ + readonly selected_protocol?: string; + /** + * Extensions the server negotiated, as the single string the connection exposes them as. Omitted when none was negotiated + */ + readonly selected_extensions?: string; + /** + * Version of the snapshot, incremented on every vital reporting one, so that heartbeats can be ordered when they are received out of sequence + */ + readonly snapshot_version: number; + /** + * Values of the connection at the time the vital was reported + */ + readonly snapshot: RumVitalWebsocketSnapshot; + [k: string]: unknown; + }; + [k: string]: unknown; + }; + [k: string]: unknown; +}; +/** + * Schema for the vital reported when a WebSocket connection starts closing. Reported at most once per connection. Reported with vital.name websocket_closing + */ +export type RumVitalWebsocketClosingEvent = RumVitalWebsocketCommonProperties & { + /** + * Vital properties + */ + readonly vital?: { + /** + * WebSocket properties + */ + readonly websocket?: { + /** + * Date the connection was asked to close, in ms from epoch + */ + readonly closing_date: number; + /** + * Side that initiated the close. Only reported on this vital, and only as client, since a close observed from the application is always initiated by it. The other values are reported by the connection-level data derived from these vitals + */ + readonly close_initiator: 'client' | 'server' | 'unknown'; + [k: string]: unknown; + }; + [k: string]: unknown; + }; + [k: string]: unknown; +}; +/** + * Schema for the vital reported when a WebSocket connection stops being tracked, whether because it closed or because tracking ended first. Reported at most once per connection. Reported with vital.name websocket_closed + */ +export type RumVitalWebsocketClosedEvent = RumVitalWebsocketCommonProperties & { + /** + * Vital properties + */ + readonly vital?: { + /** + * WebSocket properties + */ + readonly websocket?: { + /** + * Date tracking of the connection ended, in ms from epoch. It is the date the connection closed when the tracking end reason is close_event, and the date tracking stopped otherwise + */ + readonly closed_date: number; + /** + * Interval between the connecting date and the closed date of the connection, in nanoseconds. It is not vital.duration, which is reported by duration vitals + */ + readonly duration: number; + /** + * Why tracking of the connection ended + */ + readonly tracking_end_reason: 'close_event' | 'session_end' | 'page_unloaded'; + /** + * Code the connection was closed with. Reported when, and only when, the tracking end reason is close_event + */ + readonly close_code?: number; + /** + * Reason the connection was closed with, as an empty string when the other side supplied none. Reported when, and only when, the tracking end reason is close_event + */ + readonly close_reason?: string; + /** + * Whether the connection closed cleanly. Reported when, and only when, the tracking end reason is close_event + */ + readonly was_clean?: boolean; + /** + * Version of the snapshot, incremented on every vital reporting one, so that heartbeats can be ordered when they are received out of sequence + */ + readonly snapshot_version: number; + /** + * Values of the connection when tracking ended. Omitted when the connection never opened + */ + readonly snapshot?: RumVitalWebsocketSnapshot; + [k: string]: unknown; + }; + [k: string]: unknown; + }; + [k: string]: unknown; +}; /** * Schema of common properties of RUM events */ @@ -2283,3 +2465,61 @@ export interface ViewAccessibilityProperties { readonly rtl_enabled?: boolean; [k: string]: unknown; } +/** + * Schema of the WebSocket connection values that change from one vital of a connection to the next. Both directions are present whenever the snapshot is, and are zero-filled while no message has been observed + */ +export interface RumVitalWebsocketSnapshot { + /** + * Messages received from the server + */ + readonly inbound: RumVitalWebsocketMessageDirection; + /** + * Messages sent to the server + */ + readonly outbound: RumVitalWebsocketMessageDirection & { + /** + * Largest amount of bytes ever observed queued in the send buffer, sampled after each send is enqueued + */ + readonly buffered_amount_max: number; + /** + * Number of sends issued while the send buffer was already deep, that is while it held at least 65536 bytes right before the send + */ + readonly backpressured_message_count: number; + /** + * Amount of bytes still queued in the send buffer when tracking ended, in bytes. Set on the websocket_closed vital only + */ + readonly buffered_amount_at_close?: number; + [k: string]: unknown; + }; + [k: string]: unknown; +} +/** + * Schema of the messages observed in one direction of a WebSocket connection + */ +export interface RumVitalWebsocketMessageDirection { + /** + * Number of messages observed in this direction since the connection opened + */ + readonly message_count: number; + /** + * Sum of the payload sizes of the messages observed in this direction, in bytes + */ + readonly message_size_total: number; + /** + * Size of the largest single payload observed in this direction, in bytes. 0 while no message has been observed + */ + readonly message_size_max: number; + /** + * Longest interval between two consecutive messages observed in this direction, in nanoseconds, including the still-open interval since the last message. 0 until two messages have been observed. The interval before the first message is excluded from it, as it is reported by time_to_first_message + */ + readonly longest_silence: number; + /** + * Interval between the open date of the connection and the first message observed in this direction, in nanoseconds. Omitted until a first message has been observed + */ + readonly time_to_first_message?: number; + /** + * Interval between the last message observed in this direction and the closed date of the connection, in nanoseconds. Set on the websocket_closed vital only, and only when at least one message has been observed in this direction + */ + readonly silence_before_close?: number; + [k: string]: unknown; +} diff --git a/samples/rum-events/vital-websocket-closed-never-opened.json b/samples/rum-events/vital-websocket-closed-never-opened.json new file mode 100644 index 00000000..06dbe9e5 --- /dev/null +++ b/samples/rum-events/vital-websocket-closed-never-opened.json @@ -0,0 +1,35 @@ +{ + "type": "vital", + "date": 1706792747000, + "application": { + "id": "ac8218cf-498b-4d33-bd44-151095959547" + }, + "session": { + "id": "cacbf45c-3a05-48ce-b066-d76349460599", + "type": "user" + }, + "source": "browser", + "view": { + "id": "623d50fd-75cf-4025-97d2-e51ff94171f6", + "referrer": "", + "url": "https://app.datadoghq.com/rum/explorer?live=1h&query=&tab=view" + }, + "vital": { + "id": "f0e1d2c3-b4a5-4968-8877-665544332211", + "type": "websocket", + "name": "websocket_closed", + "websocket": { + "id": "1a2b3c4d-5e6f-4708-8192-a3b4c5d6e7f8", + "closed_date": 1706792747000, + "duration": 424000000, + "tracking_end_reason": "close_event", + "close_code": 1006, + "close_reason": "", + "was_clean": false, + "snapshot_version": 1 + } + }, + "_dd": { + "format_version": 2 + } +} diff --git a/samples/rum-events/vital-websocket-closed-session-end.json b/samples/rum-events/vital-websocket-closed-session-end.json new file mode 100644 index 00000000..5dbf4ce5 --- /dev/null +++ b/samples/rum-events/vital-websocket-closed-session-end.json @@ -0,0 +1,51 @@ +{ + "type": "vital", + "date": 1706792760000, + "application": { + "id": "ac8218cf-498b-4d33-bd44-151095959547" + }, + "session": { + "id": "cacbf45c-3a05-48ce-b066-d76349460599", + "type": "user" + }, + "source": "browser", + "view": { + "id": "623d50fd-75cf-4025-97d2-e51ff94171f6", + "referrer": "", + "url": "https://app.datadoghq.com/rum/explorer?live=1h&query=&tab=view" + }, + "vital": { + "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", + "type": "websocket", + "name": "websocket_closed", + "websocket": { + "id": "9e9f4b3a-1c2d-4e5f-8a9b-0c1d2e3f4a5b", + "closed_date": 1706792760000, + "duration": 13424000000, + "tracking_end_reason": "session_end", + "snapshot_version": 9, + "snapshot": { + "inbound": { + "message_count": 40, + "message_size_total": 81920, + "message_size_max": 4096, + "longest_silence": 2000000000, + "time_to_first_message": 120000000, + "silence_before_close": 1200000000 + }, + "outbound": { + "message_count": 0, + "message_size_total": 0, + "message_size_max": 0, + "longest_silence": 0, + "buffered_amount_max": 0, + "backpressured_message_count": 0, + "buffered_amount_at_close": 0 + } + } + } + }, + "_dd": { + "format_version": 2 + } +} diff --git a/samples/rum-events/vital-websocket-closed.json b/samples/rum-events/vital-websocket-closed.json new file mode 100644 index 00000000..184a5041 --- /dev/null +++ b/samples/rum-events/vital-websocket-closed.json @@ -0,0 +1,56 @@ +{ + "type": "vital", + "date": 1706792752100, + "application": { + "id": "ac8218cf-498b-4d33-bd44-151095959547" + }, + "session": { + "id": "cacbf45c-3a05-48ce-b066-d76349460599", + "type": "user" + }, + "source": "browser", + "view": { + "id": "623d50fd-75cf-4025-97d2-e51ff94171f6", + "referrer": "", + "url": "https://app.datadoghq.com/rum/explorer?live=1h&query=&tab=view" + }, + "vital": { + "id": "d4e5f6a7-b8c9-4d0e-9f1a-2b3c4d5e6f70", + "type": "websocket", + "name": "websocket_closed", + "websocket": { + "id": "9e9f4b3a-1c2d-4e5f-8a9b-0c1d2e3f4a5b", + "closed_date": 1706792752100, + "duration": 5524000000, + "tracking_end_reason": "close_event", + "close_code": 1000, + "close_reason": "", + "was_clean": true, + "snapshot_version": 4, + "snapshot": { + "inbound": { + "message_count": 18, + "message_size_total": 36864, + "message_size_max": 4096, + "longest_silence": 1500000000, + "time_to_first_message": 120000000, + "silence_before_close": 400000000 + }, + "outbound": { + "message_count": 6, + "message_size_total": 768, + "message_size_max": 256, + "longest_silence": 3000000000, + "time_to_first_message": 80000000, + "silence_before_close": 900000000, + "buffered_amount_max": 1024, + "backpressured_message_count": 0, + "buffered_amount_at_close": 0 + } + } + } + }, + "_dd": { + "format_version": 2 + } +} diff --git a/samples/rum-events/vital-websocket-closing.json b/samples/rum-events/vital-websocket-closing.json new file mode 100644 index 00000000..b357c0a9 --- /dev/null +++ b/samples/rum-events/vital-websocket-closing.json @@ -0,0 +1,30 @@ +{ + "type": "vital", + "date": 1706792752000, + "application": { + "id": "ac8218cf-498b-4d33-bd44-151095959547" + }, + "session": { + "id": "cacbf45c-3a05-48ce-b066-d76349460599", + "type": "user" + }, + "source": "browser", + "view": { + "id": "623d50fd-75cf-4025-97d2-e51ff94171f6", + "referrer": "", + "url": "https://app.datadoghq.com/rum/explorer?live=1h&query=&tab=view" + }, + "vital": { + "id": "7b5c8d91-2e3f-4a5b-8c7d-9e0f1a2b3c4d", + "type": "websocket", + "name": "websocket_closing", + "websocket": { + "id": "9e9f4b3a-1c2d-4e5f-8a9b-0c1d2e3f4a5b", + "closing_date": 1706792752000, + "close_initiator": "client" + } + }, + "_dd": { + "format_version": 2 + } +} diff --git a/samples/rum-events/vital-websocket-connecting.json b/samples/rum-events/vital-websocket-connecting.json new file mode 100644 index 00000000..0766c82b --- /dev/null +++ b/samples/rum-events/vital-websocket-connecting.json @@ -0,0 +1,31 @@ +{ + "type": "vital", + "date": 1706792746576, + "application": { + "id": "ac8218cf-498b-4d33-bd44-151095959547" + }, + "session": { + "id": "cacbf45c-3a05-48ce-b066-d76349460599", + "type": "user" + }, + "source": "browser", + "view": { + "id": "623d50fd-75cf-4025-97d2-e51ff94171f6", + "referrer": "", + "url": "https://app.datadoghq.com/rum/explorer?live=1h&query=&tab=view" + }, + "vital": { + "id": "47af0abb-3de1-4170-a491-4473716df979", + "type": "websocket", + "name": "websocket_connecting", + "websocket": { + "id": "9e9f4b3a-1c2d-4e5f-8a9b-0c1d2e3f4a5b", + "url": "wss://example.datadoghq.com/socket", + "requested_protocols": ["graphql-ws"], + "connecting_date": 1706792746576 + } + }, + "_dd": { + "format_version": 2 + } +} diff --git a/samples/rum-events/vital-websocket-open.json b/samples/rum-events/vital-websocket-open.json new file mode 100644 index 00000000..03aae2dc --- /dev/null +++ b/samples/rum-events/vital-websocket-open.json @@ -0,0 +1,52 @@ +{ + "type": "vital", + "date": 1706792746776, + "application": { + "id": "ac8218cf-498b-4d33-bd44-151095959547" + }, + "session": { + "id": "cacbf45c-3a05-48ce-b066-d76349460599", + "type": "user" + }, + "source": "browser", + "view": { + "id": "623d50fd-75cf-4025-97d2-e51ff94171f6", + "referrer": "", + "url": "https://app.datadoghq.com/rum/explorer?live=1h&query=&tab=view" + }, + "vital": { + "id": "2c1a6de4-5b7f-4a3e-9d81-6f0b2c3d4e5f", + "type": "websocket", + "name": "websocket_open", + "websocket": { + "id": "9e9f4b3a-1c2d-4e5f-8a9b-0c1d2e3f4a5b", + "open_handshake_succeeded": true, + "connecting_duration": 200000000, + "open_date": 1706792746776, + "selected_protocol": "graphql-ws", + "selected_extensions": "permessage-deflate; client_max_window_bits=10", + "snapshot_version": 3, + "snapshot": { + "inbound": { + "message_count": 12, + "message_size_total": 24576, + "message_size_max": 4096, + "longest_silence": 1500000000, + "time_to_first_message": 120000000 + }, + "outbound": { + "message_count": 4, + "message_size_total": 512, + "message_size_max": 256, + "longest_silence": 3000000000, + "time_to_first_message": 80000000, + "buffered_amount_max": 1024, + "backpressured_message_count": 0 + } + } + } + }, + "_dd": { + "format_version": 2 + } +} diff --git a/schemas/rum-events-browser-schema.json b/schemas/rum-events-browser-schema.json index ba995614..e9657056 100644 --- a/schemas/rum-events-browser-schema.json +++ b/schemas/rum-events-browser-schema.json @@ -34,6 +34,18 @@ }, { "$ref": "rum/vital-operation-step-schema.json" + }, + { + "$ref": "rum/vital-websocket-connecting-schema.json" + }, + { + "$ref": "rum/vital-websocket-open-schema.json" + }, + { + "$ref": "rum/vital-websocket-closing-schema.json" + }, + { + "$ref": "rum/vital-websocket-closed-schema.json" } ] } diff --git a/schemas/rum-events-schema.json b/schemas/rum-events-schema.json index fdd4757f..7dc9071b 100644 --- a/schemas/rum-events-schema.json +++ b/schemas/rum-events-schema.json @@ -37,6 +37,18 @@ }, { "$ref": "rum/vital-app-launch-schema.json" + }, + { + "$ref": "rum/vital-websocket-connecting-schema.json" + }, + { + "$ref": "rum/vital-websocket-open-schema.json" + }, + { + "$ref": "rum/vital-websocket-closing-schema.json" + }, + { + "$ref": "rum/vital-websocket-closed-schema.json" } ] } diff --git a/schemas/rum/_vital-websocket-common-schema.json b/schemas/rum/_vital-websocket-common-schema.json new file mode 100644 index 00000000..b024edb6 --- /dev/null +++ b/schemas/rum/_vital-websocket-common-schema.json @@ -0,0 +1,49 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "rum/_vital-websocket-common-schema.json", + "title": "RumVitalWebsocketCommonProperties", + "type": "object", + "description": "Schema of common properties for a WebSocket vital event. One connection reports a stream of vitals over its lifetime, one per phase, all sharing the same vital.websocket.id", + "allOf": [ + { + "$ref": "_vital-common-schema.json" + }, + { + "properties": { + "vital": { + "type": "object", + "description": "Vital properties", + "required": ["type", "name", "websocket"], + "properties": { + "type": { + "type": "string", + "description": "Type of the vital.", + "const": "websocket", + "readOnly": true + }, + "name": { + "type": "string", + "description": "Name of the vital, identifying the phase of the connection it reports: websocket_connecting, websocket_open, websocket_closing or websocket_closed", + "readOnly": true + }, + "websocket": { + "type": "object", + "description": "WebSocket properties", + "required": ["id"], + "properties": { + "id": { + "type": "string", + "description": "UUID of the WebSocket connection, stable across every vital reported for that connection. It is not vital.id, which identifies a single vital", + "pattern": "^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$", + "readOnly": true + } + }, + "readOnly": true + } + }, + "readOnly": true + } + } + } + ] +} diff --git a/schemas/rum/_vital-websocket-message-direction-schema.json b/schemas/rum/_vital-websocket-message-direction-schema.json new file mode 100644 index 00000000..b6f02149 --- /dev/null +++ b/schemas/rum/_vital-websocket-message-direction-schema.json @@ -0,0 +1,47 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "rum/_vital-websocket-message-direction-schema.json", + "title": "RumVitalWebsocketMessageDirection", + "type": "object", + "description": "Schema of the messages observed in one direction of a WebSocket connection", + "required": ["message_count", "message_size_total", "message_size_max", "longest_silence"], + "properties": { + "message_count": { + "type": "integer", + "description": "Number of messages observed in this direction since the connection opened", + "minimum": 0, + "readOnly": true + }, + "message_size_total": { + "type": "integer", + "description": "Sum of the payload sizes of the messages observed in this direction, in bytes", + "minimum": 0, + "readOnly": true + }, + "message_size_max": { + "type": "integer", + "description": "Size of the largest single payload observed in this direction, in bytes. 0 while no message has been observed", + "minimum": 0, + "readOnly": true + }, + "longest_silence": { + "type": "integer", + "description": "Longest interval between two consecutive messages observed in this direction, in nanoseconds, including the still-open interval since the last message. 0 until two messages have been observed. The interval before the first message is excluded from it, as it is reported by time_to_first_message", + "minimum": 0, + "readOnly": true + }, + "time_to_first_message": { + "type": "integer", + "description": "Interval between the open date of the connection and the first message observed in this direction, in nanoseconds. Omitted until a first message has been observed", + "minimum": 0, + "readOnly": true + }, + "silence_before_close": { + "type": "integer", + "description": "Interval between the last message observed in this direction and the closed date of the connection, in nanoseconds. Set on the websocket_closed vital only, and only when at least one message has been observed in this direction", + "minimum": 0, + "readOnly": true + } + }, + "readOnly": true +} diff --git a/schemas/rum/_vital-websocket-snapshot-schema.json b/schemas/rum/_vital-websocket-snapshot-schema.json new file mode 100644 index 00000000..ecf8eb2f --- /dev/null +++ b/schemas/rum/_vital-websocket-snapshot-schema.json @@ -0,0 +1,48 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "rum/_vital-websocket-snapshot-schema.json", + "title": "RumVitalWebsocketSnapshot", + "type": "object", + "description": "Schema of the WebSocket connection values that change from one vital of a connection to the next. Both directions are present whenever the snapshot is, and are zero-filled while no message has been observed", + "required": ["inbound", "outbound"], + "properties": { + "inbound": { + "type": "object", + "description": "Messages received from the server", + "allOf": [{ "$ref": "_vital-websocket-message-direction-schema.json" }], + "readOnly": true + }, + "outbound": { + "type": "object", + "description": "Messages sent to the server", + "allOf": [ + { "$ref": "_vital-websocket-message-direction-schema.json" }, + { + "required": ["buffered_amount_max", "backpressured_message_count"], + "properties": { + "buffered_amount_max": { + "type": "integer", + "description": "Largest amount of bytes ever observed queued in the send buffer, sampled after each send is enqueued", + "minimum": 0, + "readOnly": true + }, + "backpressured_message_count": { + "type": "integer", + "description": "Number of sends issued while the send buffer was already deep, that is while it held at least 65536 bytes right before the send", + "minimum": 0, + "readOnly": true + }, + "buffered_amount_at_close": { + "type": "integer", + "description": "Amount of bytes still queued in the send buffer when tracking ended, in bytes. Set on the websocket_closed vital only", + "minimum": 0, + "readOnly": true + } + } + } + ], + "readOnly": true + } + }, + "readOnly": true +} diff --git a/schemas/rum/vital-websocket-closed-schema.json b/schemas/rum/vital-websocket-closed-schema.json new file mode 100644 index 00000000..ff587198 --- /dev/null +++ b/schemas/rum/vital-websocket-closed-schema.json @@ -0,0 +1,80 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "rum/vital-websocket-closed-schema.json", + "title": "RumVitalWebsocketClosedEvent", + "type": "object", + "description": "Schema for the vital reported when a WebSocket connection stops being tracked, whether because it closed or because tracking ended first. Reported at most once per connection. Reported with vital.name websocket_closed", + "allOf": [ + { + "$ref": "_vital-websocket-common-schema.json" + }, + { + "properties": { + "vital": { + "type": "object", + "description": "Vital properties", + "properties": { + "websocket": { + "type": "object", + "description": "WebSocket properties", + "required": ["closed_date", "duration", "tracking_end_reason", "snapshot_version"], + "properties": { + "closed_date": { + "type": "integer", + "description": "Date tracking of the connection ended, in ms from epoch. It is the date the connection closed when the tracking end reason is close_event, and the date tracking stopped otherwise", + "minimum": 0, + "readOnly": true + }, + "duration": { + "type": "integer", + "description": "Interval between the connecting date and the closed date of the connection, in nanoseconds. It is not vital.duration, which is reported by duration vitals", + "minimum": 0, + "readOnly": true + }, + "tracking_end_reason": { + "type": "string", + "description": "Why tracking of the connection ended", + "enum": ["close_event", "session_end", "page_unloaded"], + "readOnly": true + }, + "close_code": { + "type": "number", + "description": "Code the connection was closed with. Reported when, and only when, the tracking end reason is close_event", + "readOnly": true + }, + "close_reason": { + "type": "string", + "description": "Reason the connection was closed with, as an empty string when the other side supplied none. Reported when, and only when, the tracking end reason is close_event", + "readOnly": true + }, + "was_clean": { + "type": "boolean", + "description": "Whether the connection closed cleanly. Reported when, and only when, the tracking end reason is close_event", + "readOnly": true + }, + "snapshot_version": { + "type": "integer", + "description": "Version of the snapshot, incremented on every vital reporting one, so that heartbeats can be ordered when they are received out of sequence", + "minimum": 0, + "readOnly": true + }, + "snapshot": { + "type": "object", + "description": "Values of the connection when tracking ended. Omitted when the connection never opened", + "allOf": [ + { + "$ref": "_vital-websocket-snapshot-schema.json" + } + ], + "readOnly": true + } + }, + "readOnly": true + } + }, + "readOnly": true + } + } + } + ] +} diff --git a/schemas/rum/vital-websocket-closing-schema.json b/schemas/rum/vital-websocket-closing-schema.json new file mode 100644 index 00000000..80c8e754 --- /dev/null +++ b/schemas/rum/vital-websocket-closing-schema.json @@ -0,0 +1,43 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "rum/vital-websocket-closing-schema.json", + "title": "RumVitalWebsocketClosingEvent", + "type": "object", + "description": "Schema for the vital reported when a WebSocket connection starts closing. Reported at most once per connection. Reported with vital.name websocket_closing", + "allOf": [ + { + "$ref": "_vital-websocket-common-schema.json" + }, + { + "properties": { + "vital": { + "type": "object", + "description": "Vital properties", + "properties": { + "websocket": { + "type": "object", + "description": "WebSocket properties", + "required": ["closing_date", "close_initiator"], + "properties": { + "closing_date": { + "type": "integer", + "description": "Date the connection was asked to close, in ms from epoch", + "minimum": 0, + "readOnly": true + }, + "close_initiator": { + "type": "string", + "description": "Side that initiated the close. Only reported on this vital, and only as client, since a close observed from the application is always initiated by it. The other values are reported by the connection-level data derived from these vitals", + "enum": ["client", "server", "unknown"], + "readOnly": true + } + }, + "readOnly": true + } + }, + "readOnly": true + } + } + } + ] +} diff --git a/schemas/rum/vital-websocket-connecting-schema.json b/schemas/rum/vital-websocket-connecting-schema.json new file mode 100644 index 00000000..b05e6447 --- /dev/null +++ b/schemas/rum/vital-websocket-connecting-schema.json @@ -0,0 +1,51 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "rum/vital-websocket-connecting-schema.json", + "title": "RumVitalWebsocketConnectingEvent", + "type": "object", + "description": "Schema for the vital reported when a WebSocket connection is created. Reported exactly once per connection, when the WebSocket is constructed. Reported with vital.name websocket_connecting", + "allOf": [ + { + "$ref": "_vital-websocket-common-schema.json" + }, + { + "properties": { + "vital": { + "type": "object", + "description": "Vital properties", + "properties": { + "websocket": { + "type": "object", + "description": "WebSocket properties. The identity of the connection is reported on this vital only, and is not repeated on the vitals of the later phases", + "required": ["url", "connecting_date"], + "properties": { + "url": { + "type": "string", + "description": "URL the connection was opened on, without its query string", + "readOnly": true + }, + "requested_protocols": { + "type": "array", + "description": "Subprotocols the connection was requested with. Omitted when none was requested", + "items": { + "type": "string", + "readOnly": true + }, + "readOnly": true + }, + "connecting_date": { + "type": "integer", + "description": "Date the WebSocket was constructed, in ms from epoch", + "minimum": 0, + "readOnly": true + } + }, + "readOnly": true + } + }, + "readOnly": true + } + } + } + ] +} diff --git a/schemas/rum/vital-websocket-open-schema.json b/schemas/rum/vital-websocket-open-schema.json new file mode 100644 index 00000000..1a862e2b --- /dev/null +++ b/schemas/rum/vital-websocket-open-schema.json @@ -0,0 +1,80 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "rum/vital-websocket-open-schema.json", + "title": "RumVitalWebsocketOpenEvent", + "type": "object", + "description": "Schema for the vital reported when a WebSocket connection opens, then repeatedly as a heartbeat while it stays open. Reported with vital.name websocket_open", + "allOf": [ + { + "$ref": "_vital-websocket-common-schema.json" + }, + { + "properties": { + "vital": { + "type": "object", + "description": "Vital properties", + "properties": { + "websocket": { + "type": "object", + "description": "WebSocket properties", + "required": [ + "open_handshake_succeeded", + "connecting_duration", + "open_date", + "snapshot_version", + "snapshot" + ], + "properties": { + "open_handshake_succeeded": { + "type": "boolean", + "description": "Whether the opening handshake succeeded", + "readOnly": true + }, + "connecting_duration": { + "type": "integer", + "description": "Interval between the connecting date and the open date of the connection, in nanoseconds", + "minimum": 0, + "readOnly": true + }, + "open_date": { + "type": "integer", + "description": "Date the connection opened, in ms from epoch", + "minimum": 0, + "readOnly": true + }, + "selected_protocol": { + "type": "string", + "description": "Subprotocol the server selected. Omitted when the server selected none", + "readOnly": true + }, + "selected_extensions": { + "type": "string", + "description": "Extensions the server negotiated, as the single string the connection exposes them as. Omitted when none was negotiated", + "readOnly": true + }, + "snapshot_version": { + "type": "integer", + "description": "Version of the snapshot, incremented on every vital reporting one, so that heartbeats can be ordered when they are received out of sequence", + "minimum": 0, + "readOnly": true + }, + "snapshot": { + "type": "object", + "description": "Values of the connection at the time the vital was reported", + "allOf": [ + { + "$ref": "_vital-websocket-snapshot-schema.json" + } + ], + "readOnly": true + } + }, + "readOnly": true + } + }, + "readOnly": true + } + } + } + ] +}