mirror of https://github.com/OpenVidu/openvidu.git
openvidu-dashboard: archive media-stats-availability, keep only the specs
parent
3058e6af0b
commit
cf39d6a2ee
|
|
@ -1,2 +0,0 @@
|
||||||
schema: spec-driven
|
|
||||||
created: 2026-11-02
|
|
||||||
|
|
@ -1,123 +0,0 @@
|
||||||
# Design
|
|
||||||
|
|
||||||
## Context
|
|
||||||
|
|
||||||
- Telemetry stats reach the writer from two sources: per-track media stats, built from the RTP buffers of the server's
|
|
||||||
media path, and the signal (`TR_SG…`) and data (`TR_DT…`) pseudo-tracks, built from WebSocket and data channel byte
|
|
||||||
counters. A media engine that does not move RTP through those buffers produces only the second kind.
|
|
||||||
- `projection/stats.go` adds every stat to the participant (bytes, `lastStatsAt`) and skips pseudo-tracks before the
|
|
||||||
room delta, so the room gets `lastStatsAt`, bytes and quality only from media stats.
|
|
||||||
- `nodes` documents are written by `store.Heartbeat` on every batch tick and expire 30 days after `lastSeenAt`. Node ids
|
|
||||||
are generated per process, so a restarted node writes a new document and the old one expires on its own.
|
|
||||||
- Rooms take `nodeId` from `ROOM_CREATED` (and from the first event of any kind while it is unknown); participant
|
|
||||||
sessions take it from their first event. Entities are kept for `retention.entities` (90 days by default).
|
|
||||||
- `analytics.Node{ID, Region}` is built in `openvidu/openvidu.go`; the projection options in `analytics.go`. A server
|
|
||||||
edition adjusts analytics defaults through `openviduconfig` package variables before the analytics start
|
|
||||||
(`DefaultStatsDetail`).
|
|
||||||
|
|
||||||
## Goals / Non-Goals
|
|
||||||
|
|
||||||
**Goals:**
|
|
||||||
- Every view that shows bytes or quality can tell "not reported" apart from "zero", for the whole entity retention.
|
|
||||||
- The CE code and specs stay engine-agnostic: the server states a capability, not an engine name.
|
|
||||||
|
|
||||||
**Non-Goals:**
|
|
||||||
- Hiding views or features: only the values and charts that depend on media statistics change.
|
|
||||||
- Marking track or subscription documents: views reach them through their participant.
|
|
||||||
|
|
||||||
## Decisions
|
|
||||||
|
|
||||||
### D1. A capability flag set by the server
|
|
||||||
|
|
||||||
`openviduconfig.MediaStats` (default `true`) says whether this server's media engine reports media statistics. A server
|
|
||||||
whose engine does not sets it to `false` before the analytics start, like `DefaultStatsDetail`. `openvidu.Start` passes
|
|
||||||
it as `analytics.Node.MediaStats`. There is no configuration key: the value is a fact of the build and the engine, not
|
|
||||||
an operator choice.
|
|
||||||
|
|
||||||
Rejected: the engine name in the data (`rtcEngine: "…"`). It ties the CE contract to an edition's engines and must be
|
|
||||||
reinterpreted when an engine gains statistics; the capability flips by itself.
|
|
||||||
|
|
||||||
### D2. Mark both the node and the entities
|
|
||||||
|
|
||||||
The heartbeat `$set`s `mediaStats` (true or false) on every tick. Rooms and participant sessions created by a node with
|
|
||||||
`MediaStats: false` get `mediaStats: false`; others get no field. Views of rooms and sessions read the entity, so the
|
|
||||||
mark lives as long as the entity; analytics read the nodes, since rollups have no per-engine dimension.
|
|
||||||
|
|
||||||
Rejected: nodes only, with the dashboard joining `nodeId`. Node documents expire 30 days after their last heartbeat and
|
|
||||||
every restart creates a new node id, so rooms between 30 and 90 days old would fall back to "0 B".
|
|
||||||
Rejected: entities only. A range or scope of the Analytics page has no entity to read the mark from.
|
|
||||||
|
|
||||||
### D3. Only `false` is stored on entities
|
|
||||||
|
|
||||||
Absent means "reports media statistics", which is also what every document written before the change says. No
|
|
||||||
backfill, no extra bytes on the common case, and the API omits the property unless it is `false`.
|
|
||||||
|
|
||||||
### D4. The mark follows `nodeId`
|
|
||||||
|
|
||||||
The mark is computed where `nodeId` is: `ROOM_CREATED` sets `nodeId` and sets `mediaStats: false` or removes it
|
|
||||||
(`$$REMOVE`); the touch of other events sets `mediaStats` only when the document has no `nodeId` yet, in the same stage,
|
|
||||||
so the first writer of the room decides until `ROOM_CREATED` arrives. Participants follow their own `nodeId` rule. The
|
|
||||||
legacy migration replays with `MediaStats: true`: the engine of legacy nodes is unknown.
|
|
||||||
|
|
||||||
### D5. Pseudo-track stats advance the room's `lastStatsAt`
|
|
||||||
|
|
||||||
A pseudo-track stat creates the room delta with `statsAt` and zero bytes and quality, exactly as a media stat of zero
|
|
||||||
bytes would. Every connected participant flushes signal stats every 30 s, so the room's `lastStatsAt` becomes a
|
|
||||||
liveness signal under any engine: the room stale flag, the stale hints of the overview and the reconciler's
|
|
||||||
empty-listing guard (`Untrusted live listing`) cover these rooms. With media stats nothing changes, since the room
|
|
||||||
already gets the same flush time from them.
|
|
||||||
|
|
||||||
### D6. Analytics coverage from heartbeat spans, on the summary only
|
|
||||||
|
|
||||||
The summary reads `nodes` documents whose span overlaps the range (`firstSeenAt <= to`, `lastSeenAt >= from`, plus
|
|
||||||
`_id` for a node scope) with a `mediaStats` projection and folds them into `none`, `partial` or nothing. The page
|
|
||||||
applies the summary's coverage to the Data up / down tile and the Bandwidth section, and exposes it to the cards of the
|
|
||||||
page's extension slots as `AnalyticsPageContext.mediaStats` (a signal of `'partial' | 'none' | undefined`), so the
|
|
||||||
series and breakdown responses do not change.
|
|
||||||
|
|
||||||
Rejected: a coverage field on every series response (the page already holds the summary of the same range and scope).
|
|
||||||
Rejected: inferring coverage from rollup bytes at 0 (a quiet range reads the same).
|
|
||||||
|
|
||||||
### D7. Values stay as stored
|
|
||||||
|
|
||||||
The rooms list keeps `bytes` next to `mediaStats: false`, and the summary keeps `bytesUp` and `bytesDown`; only the UI
|
|
||||||
replaces them. API clients see the same shapes plus one optional property.
|
|
||||||
|
|
||||||
### D8. A generator option for nodes without media statistics
|
|
||||||
|
|
||||||
`--nodes-without-media-stats <n>` (default 0) marks the last `n` generated nodes: their node documents carry
|
|
||||||
`mediaStats: false`, their rooms and sessions are stamped and get no media `track_stats`, room bytes or quality, and their
|
|
||||||
rollup contributions lose bytes and quality. The other nodes get `mediaStats: true`. The keep-alive preserves the flag.
|
|
||||||
|
|
||||||
## Data model
|
|
||||||
|
|
||||||
- `nodes.mediaStats`: bool, written on every heartbeat. `rooms.mediaStats` and `participants.mediaStats`: bool, only
|
|
||||||
`false`, set by the projections. Contract schemas of the three collections gain the optional property; the dashboard
|
|
||||||
mirror and its typings follow.
|
|
||||||
- No index, retention or schema version change (the v2 schema has not shipped). No mapping from legacy events (D4).
|
|
||||||
|
|
||||||
## API
|
|
||||||
|
|
||||||
- `RoomListItem.mediaStats?: false` (`GET /api/v1/rooms`).
|
|
||||||
- `AnalyticsSummary.mediaStats?: "partial" | "none"` (`GET /api/v1/analytics/summary`).
|
|
||||||
- The coverage query runs on `nodes`, backed by the `lastSeenAt` TTL index; the collection holds one document per node
|
|
||||||
process of the last 30 days.
|
|
||||||
|
|
||||||
## Performance budget
|
|
||||||
|
|
||||||
One extra query per summary on a collection of tens of documents: under 5 ms, verified by an integration test with an
|
|
||||||
`explain()` plan (`IXSCAN` on `lastSeenAt`) next to the existing analytics plan tests. The summary p95 budget (150 ms at
|
|
||||||
scenario L) is unchanged. The pseudo-track room delta adds at most one room update per room and batch, and only for
|
|
||||||
rooms whose flush carries no media stat; the writer benchmark of the batch path checks it stays within its budget.
|
|
||||||
|
|
||||||
## Risks / Trade-offs
|
|
||||||
|
|
||||||
- [A room touched first by an egress or ingress event of another node] → it takes that node's mark until its
|
|
||||||
`ROOM_CREATED` is applied, which then sets the host's mark (D4).
|
|
||||||
- [Coverage misses nodes whose documents expired] → ranges older than 30 days may read as fully reported; the entity
|
|
||||||
marks keep rooms and sessions right.
|
|
||||||
- [A pseudo-track-only room delta on a room not written yet] → the same upsert as a media stat delta today.
|
|
||||||
|
|
||||||
## Migration Plan
|
|
||||||
|
|
||||||
Ships with the v2 writer and dashboard. Rollback: dashboards ignore the property and writers without it never write it.
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
# Proposal
|
|
||||||
|
|
||||||
## Why
|
|
||||||
|
|
||||||
A server node whose media engine does not hand RTP statistics to the telemetry sends only the signal and data
|
|
||||||
pseudo-track stats: no `track_stats`, room and rollup bytes at 0, no quality samples and no final publish or subscribe
|
|
||||||
statistics. The dashboard cannot tell that apart from a room where nothing was sent, so it shows "↑ 0 B ↓ 0 B",
|
|
||||||
empty charts and "No stats received" as if they were measurements. The same gap leaves `rooms.lastStatsAt` unset, so
|
|
||||||
the stale cue of a room never fires and the reconciler's empty-listing guard does not see those rooms.
|
|
||||||
|
|
||||||
## What Changes
|
|
||||||
|
|
||||||
- The server tells the writer whether the node reports media statistics (true unless the server says otherwise).
|
|
||||||
- The writer publishes it on the node heartbeat (`nodes.mediaStats`) and stamps `mediaStats: false` on the rooms and
|
|
||||||
participant sessions created on a node that does not report them, so the mark outlives the node document.
|
|
||||||
- Signal and data pseudo-track stats also advance the room's `lastStatsAt`, as the stats delta requirement already
|
|
||||||
states for every flush.
|
|
||||||
- The rooms list returns `mediaStats: false` for those rooms, and the Rooms table shows a dash with a tooltip instead
|
|
||||||
of "↑ 0 B ↓ 0 B".
|
|
||||||
- The analytics summary returns `mediaStats: "partial" | "none"` when some or all nodes seen in the range do not
|
|
||||||
report media statistics. The Data up / down tile and the Bandwidth section say the data is not reported (`none`) or
|
|
||||||
covers only some servers (`partial`).
|
|
||||||
|
|
||||||
Scope excludes the following:
|
|
||||||
- Producing media statistics on such a node (a server concern).
|
|
||||||
- Views outside the rooms list and the Analytics page: they read the same marks, through the seam the Analytics page
|
|
||||||
exposes to its extension cards (`AnalyticsPageContext.mediaStats`) and the entity documents.
|
|
||||||
- Backfilling the mark on documents written before the change.
|
|
||||||
|
|
||||||
Edition: CE and PRO (writer and the CE views every edition shows). Repositories: openvidu-livekit
|
|
||||||
(`openvidu/analytics`, `openvidu/openviduconfig`, `openvidu/openvidu.go`) and the dashboard (typings, backend, frontend,
|
|
||||||
data generator, specs).
|
|
||||||
|
|
||||||
CHANGELOG lines: none. The v2 data model, its writer and these views have not shipped in a release, and a CE server
|
|
||||||
always reports media statistics.
|
|
||||||
|
|
||||||
## Capabilities
|
|
||||||
|
|
||||||
### New Capabilities
|
|
||||||
|
|
||||||
None.
|
|
||||||
|
|
||||||
### Modified Capabilities
|
|
||||||
|
|
||||||
- `server-analytics-writer`: the node heartbeat carries `mediaStats`, rooms and sessions of such a node are stamped,
|
|
||||||
and pseudo-track stats advance the room's `lastStatsAt`.
|
|
||||||
- `data-model`: `mediaStats` on `nodes`, `rooms` and `participants`.
|
|
||||||
- `rooms-api`: the room list item carries `mediaStats: false`.
|
|
||||||
- `rooms-ui`: the Data up / down cell of such a room reads as not reported.
|
|
||||||
- `analytics-api`: the summary carries the media statistics coverage of its range and scope.
|
|
||||||
- `analytics-ui`: the Data up / down tile and the Bandwidth section follow that coverage.
|
|
||||||
|
|
||||||
## Impact
|
|
||||||
|
|
||||||
- Writer: a `MediaStats` setting on `openviduconfig`, passed through `analytics.Node` to the heartbeat and to the
|
|
||||||
projection options; room and participant projections; the stats projection; the contract schemas of `nodes`,
|
|
||||||
`rooms` and `participants`. Unit and integration tests in `openvidu/analytics`.
|
|
||||||
- Dashboard: typings and their schema mirror, rooms mapper, analytics summary (one `nodes` query per summary), Rooms
|
|
||||||
table and Analytics page, the data generator (an option for nodes without media statistics), unit and e2e tests.
|
|
||||||
- No schema version bump: the v2 schema has not shipped.
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
## ADDED Requirements
|
|
||||||
|
|
||||||
### Requirement: Media statistics coverage
|
|
||||||
|
|
||||||
Edition: CE. The summary SHALL carry `mediaStats: "none"` when every `nodes` document of its scope whose heartbeat span
|
|
||||||
(`firstSeenAt` to `lastSeenAt`) overlaps the range has `mediaStats: false`, `mediaStats: "partial"` when only some do,
|
|
||||||
and no `mediaStats` property when none does or no node document overlaps the range. The `global` scope covers every
|
|
||||||
node, a `node:<id>` scope that node only.
|
|
||||||
|
|
||||||
#### Scenario: No server reports media statistics
|
|
||||||
|
|
||||||
- **GIVEN** two nodes seen during the range, both with `mediaStats: false`
|
|
||||||
- **WHEN** the summary is requested
|
|
||||||
- **THEN** the body has `mediaStats: "none"` and still carries `bytesUp` and `bytesDown`
|
|
||||||
|
|
||||||
#### Scenario: Some servers report media statistics
|
|
||||||
|
|
||||||
- **GIVEN** a node with `mediaStats: false` and a node with `mediaStats: true` seen during the range
|
|
||||||
- **WHEN** the summary is requested with the `global` scope
|
|
||||||
- **THEN** the body has `mediaStats: "partial"`
|
|
||||||
|
|
||||||
#### Scenario: Node scope
|
|
||||||
|
|
||||||
- **GIVEN** the same two nodes and `scope=node:<id>` of the node with `mediaStats: true`
|
|
||||||
- **WHEN** the summary is requested
|
|
||||||
- **THEN** the body has no `mediaStats` property
|
|
||||||
|
|
||||||
#### Scenario: Node seen outside the range
|
|
||||||
|
|
||||||
- **GIVEN** a node with `mediaStats: false` whose `lastSeenAt` is earlier than `from`, and a node with
|
|
||||||
`mediaStats: true` seen during the range
|
|
||||||
- **WHEN** the summary is requested
|
|
||||||
- **THEN** the body has no `mediaStats` property
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
## ADDED Requirements
|
|
||||||
|
|
||||||
### Requirement: Media statistics coverage notes
|
|
||||||
|
|
||||||
Edition: CE. With `mediaStats: "none"` in the summary, the Data up / down tile SHALL read "—" with the hint "Not
|
|
||||||
reported by the servers of this range" and the Bandwidth section SHALL show the notice "The servers of this range do
|
|
||||||
not report media statistics" instead of its chart and tiles. With `"partial"`, the tile and the section SHALL keep their
|
|
||||||
values and add the note "Only from the servers that report media statistics".
|
|
||||||
|
|
||||||
#### Scenario: No server reports media statistics
|
|
||||||
|
|
||||||
- **GIVEN** a summary with `mediaStats: "none"`
|
|
||||||
- **WHEN** the page renders
|
|
||||||
- **THEN** the Data up / down tile reads "—" with its hint, and the Bandwidth section shows the notice and no chart
|
|
||||||
|
|
||||||
#### Scenario: Some servers report media statistics
|
|
||||||
|
|
||||||
- **GIVEN** a summary with `mediaStats: "partial"`, `bytesUp: 1200000000` and `bytesDown: 3400000000`
|
|
||||||
- **WHEN** the page renders
|
|
||||||
- **THEN** the tile reads "↑ 1.2 GB" over "↓ 3.4 GB" with the hint "Only from the servers that report media
|
|
||||||
statistics", and the Bandwidth chart renders with the same note under its heading
|
|
||||||
|
|
||||||
#### Scenario: Every server reports media statistics
|
|
||||||
|
|
||||||
- **GIVEN** a summary without `mediaStats`
|
|
||||||
- **WHEN** the page renders
|
|
||||||
- **THEN** the tile and the Bandwidth section render as before, without the note
|
|
||||||
|
|
@ -1,78 +0,0 @@
|
||||||
## MODIFIED Requirements
|
|
||||||
|
|
||||||
### Requirement: Room document
|
|
||||||
Edition: CE and PRO. A `rooms` document SHALL describe one room session: identity and lifecycle, node,
|
|
||||||
configuration, participant/track/egress/ingress counters, traffic, quality, feature flags and retention
|
|
||||||
fields, with the field list of the shared contract (`dashboard-ce/typings/src/data-model`).
|
|
||||||
`participants.sessions`, `unique`, `active`, `peakActive` and `aborted` SHALL count STANDARD sessions only;
|
|
||||||
`participants.byKind` SHALL count the sessions of every kind.
|
|
||||||
|
|
||||||
#### Scenario: Fields of a room document
|
|
||||||
- **WHEN** a room document is read
|
|
||||||
- **THEN** it has `name`, `nameLower`, `status` (`active` | `closed`), `createdAt`, `endedAt`, `durationMs`, `endReason`, `closedBy`, `nodeId`, `config` (max participants, empty and departure timeouts, metadata when allowed), `participants { sessions, unique, active, peakActive, aborted, byKind }`, `tracks`, `egress` (by type), `ingress`, `bytes { up, down }`, `quality { scoreSum, scoreCount, minScore, poor, lost }`, `features { egress, ingress, agent, sip }`, `lastEventAt`, `lastStatsAt`, `mediaStats` (only `false`, when its node does not report media statistics), `schemaVersion`, `expiresAt`
|
|
||||||
|
|
||||||
#### Scenario: Room lifecycle
|
|
||||||
- **GIVEN** `ROOM_CREATED`, three `PARTICIPANT_JOINED`/`ACTIVE`, one `PARTICIPANT_LEFT`, `ROOM_ENDED (API_DELETE)`
|
|
||||||
- **WHEN** all are processed
|
|
||||||
- **THEN** the room has `status: "closed"`, `endReason: "API_DELETE"`, `participants.sessions: 3`, `participants.active: 0`, `participants.peakActive: 3`, `durationMs = endedAt - createdAt`, `closedBy: "event"`
|
|
||||||
|
|
||||||
#### Scenario: Dependent participants do not count as room size
|
|
||||||
- **GIVEN** a room with two STANDARD participants and one EGRESS participant
|
|
||||||
- **WHEN** counters are read
|
|
||||||
- **THEN** `participants.byKind.EGRESS` is 1 and `participants.active`/`peakActive` count only the two STANDARD participants
|
|
||||||
|
|
||||||
#### Scenario: Only standard participants count as participants
|
|
||||||
- **GIVEN** an active room with two active STANDARD participants of different identities, one EGRESS, one INGRESS and one AGENT participant, and one aborted SIP session
|
|
||||||
- **WHEN** counters are read
|
|
||||||
- **THEN** `participants.sessions` is 2, `unique` is 2, `active` is 2, `peakActive` is 2, `aborted` is 0 and `participants.byKind` is `{ STANDARD: 2, EGRESS: 1, INGRESS: 1, AGENT: 1, SIP: 1 }`
|
|
||||||
|
|
||||||
### Requirement: Participant session document
|
|
||||||
Edition: CE and PRO. A `participants` document SHALL represent one participant session (one SID) with its
|
|
||||||
identity, room, kind, lifecycle status, connection facts, client facts, track and subscription counters,
|
|
||||||
traffic, quality and retention fields, with the field list of the shared contract.
|
|
||||||
|
|
||||||
#### Scenario: Fields of a participant document
|
|
||||||
- **WHEN** a participant document is read
|
|
||||||
- **THEN** it has `identity`, `identityLower`, `name`, `roomSid`, `roomName`, `kind`, `kindDetails`, `status` (`joining` | `active` | `left` | `aborted`), `joinedAt`, `activeAt`, `leftAt`, `durationMs`, `disconnectReason`, `closedBy`, `connectTimeMs`, `connectionType`, `region`, `nodeId`, `resumes`, `client { sdk, version, os, osVersion, browser, browserVersion, deviceModel, network, address }`, `permission`, `isPublisher`, `metadata` and `attributes` (when allowed), `tracks { published, bySource, subscribed, subscribeFailures }`, `bytes`, `quality`, `lastEventAt`, `lastStatsAt`, `mediaStats` (only `false`, when its node does not report media statistics), `schemaVersion`, `expiresAt`
|
|
||||||
|
|
||||||
#### Scenario: Connection aborted
|
|
||||||
- **GIVEN** `PARTICIPANT_JOINED` followed by `PARTICIPANT_CONNECTION_ABORTED`
|
|
||||||
- **WHEN** both are processed
|
|
||||||
- **THEN** the document has `status: "aborted"`, `activeAt` absent, `leftAt` set and `disconnectReason` from the event
|
|
||||||
|
|
||||||
#### Scenario: Reconnect creates a new session
|
|
||||||
- **GIVEN** a participant that reconnects with the same identity and receives a new SID
|
|
||||||
- **WHEN** `PARTICIPANT_LEFT (DUPLICATE_IDENTITY)` and the new `PARTICIPANT_JOINED` are processed
|
|
||||||
- **THEN** two documents exist with the same `identity` and `roomSid`, the first `left` and the second `joining` or `active`
|
|
||||||
|
|
||||||
#### Scenario: Resume keeps the session
|
|
||||||
- **GIVEN** an active participant that resumes its signal connection
|
|
||||||
- **WHEN** `PARTICIPANT_RESUMED` is processed
|
|
||||||
- **THEN** the same document stays `active` and `resumes` increases by one
|
|
||||||
|
|
||||||
### Requirement: Nodes
|
|
||||||
Edition: CE and PRO. The `nodes` collection SHALL hold one document per server node with `firstSeenAt`,
|
|
||||||
`lastSeenAt`, `serverVersion`, `region`, `batchIntervalMs` (int32 milliseconds, at least 1 000: the node's
|
|
||||||
configured batch interval) and `mediaStats`, updated by every writer batch, and SHALL expire documents 30 days after
|
|
||||||
their last heartbeat so decommissioned nodes disappear. A document without `batchIntervalMs` SHALL be read as a 10 s
|
|
||||||
interval, and one without `mediaStats` as `true`.
|
|
||||||
|
|
||||||
#### Scenario: Node heartbeat
|
|
||||||
- **GIVEN** a node writing batches every 10 s
|
|
||||||
- **WHEN** `nodes` is read
|
|
||||||
- **THEN** its document has `lastSeenAt` within the last batch interval and `batchIntervalMs: 10000`
|
|
||||||
|
|
||||||
#### Scenario: Node document without the interval
|
|
||||||
- **GIVEN** a `nodes` document without `batchIntervalMs`
|
|
||||||
- **WHEN** the dashboard derives that node's freshness windows
|
|
||||||
- **THEN** it uses a 10 000 ms interval
|
|
||||||
|
|
||||||
#### Scenario: Interval below the minimum
|
|
||||||
- **GIVEN** the test configuration
|
|
||||||
- **WHEN** a `nodes` document with `batchIntervalMs: 500` is written
|
|
||||||
- **THEN** the write is rejected by the validator
|
|
||||||
|
|
||||||
#### Scenario: Node document without media statistics
|
|
||||||
- **GIVEN** a `nodes` document without `mediaStats`
|
|
||||||
- **WHEN** the dashboard reads it
|
|
||||||
- **THEN** it treats the node as reporting media statistics
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
## MODIFIED Requirements
|
|
||||||
|
|
||||||
### Requirement: Room list item
|
|
||||||
|
|
||||||
Edition: CE. Each rooms list item SHALL be the room document's public projection: identity, lifecycle, node,
|
|
||||||
participant, egress and ingress counters, bandwidth, feature flags, the end cause behind the "estimated" cue, the last
|
|
||||||
stats flush, the media statistics mark and the `stale` flag of `api-conventions`, never internal id arrays,
|
|
||||||
`schemaVersion` or `expiresAt`.
|
|
||||||
Participant counters count STANDARD sessions only; `participants.unique` counts distinct STANDARD identities.
|
|
||||||
|
|
||||||
#### Scenario: Fields of an item
|
|
||||||
|
|
||||||
- **WHEN** an item is read
|
|
||||||
- **THEN** it has `sid`, `name`, `status` (`active` | `closed`), `createdAt`, `endedAt?`, `durationMs?`, `closedBy?`
|
|
||||||
(`event` | `reconciler`), `nodeId?`, `participants { sessions, unique, active, peakActive }`,
|
|
||||||
`egress { total, active }`, `ingress { sessions, active }`, `bytes { up, down }`,
|
|
||||||
`features { egress, ingress, agent, sip }`, `lastStatsAt?`, `stale?`, `mediaStats?` and no `participantSids`,
|
|
||||||
`identities` or similar array
|
|
||||||
|
|
||||||
#### Scenario: Active room
|
|
||||||
|
|
||||||
- **GIVEN** a room still open with three active STANDARD participants and one active EGRESS participant
|
|
||||||
- **WHEN** it is returned
|
|
||||||
- **THEN** `status` is `active`, `endedAt`, `durationMs` and `closedBy` are absent and `participants.active` is 3
|
|
||||||
|
|
||||||
#### Scenario: Unique identities
|
|
||||||
|
|
||||||
- **GIVEN** a closed room where STANDARD identity `alice` had three sessions, `bob` one, and an AGENT session joined
|
|
||||||
- **WHEN** it is returned
|
|
||||||
- **THEN** `participants.sessions` is 4 and `participants.unique` is 2
|
|
||||||
|
|
||||||
#### Scenario: Stale active room
|
|
||||||
|
|
||||||
- **GIVEN** the default writer batch interval of 10 s and an active room whose `lastStatsAt` is 3 minutes old at the
|
|
||||||
server's clock
|
|
||||||
- **WHEN** it is returned
|
|
||||||
- **THEN** it has `stale: true`, and an active room whose `lastStatsAt` is 20 s old has no `stale` property
|
|
||||||
|
|
||||||
#### Scenario: Room of a node without media statistics
|
|
||||||
|
|
||||||
- **GIVEN** a room document with `mediaStats: false` and `bytes { up: 0, down: 0 }`
|
|
||||||
- **WHEN** it is returned
|
|
||||||
- **THEN** it has `mediaStats: false` and `bytes { up: 0, down: 0 }`, and an item of a room without the field has no
|
|
||||||
`mediaStats` property
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
## MODIFIED Requirements
|
|
||||||
|
|
||||||
### Requirement: Rooms table
|
|
||||||
|
|
||||||
Edition: CE. The Rooms table SHALL render, in order, the columns SID (detail link with copy button), Name (copy
|
|
||||||
button), Status (estimated and stale cues), Started, Ended, Duration (live), Unique identities (`participants.unique`),
|
|
||||||
Active, Data up / down, Features (badges E, I, A) and Node (hidden by default); the sortable headers are Started
|
|
||||||
(`createdAt`, the default), Ended (`endedAt`), Duration (`duration`), Unique identities (`uniqueIdentities`) and Active
|
|
||||||
(`active`).
|
|
||||||
|
|
||||||
#### Scenario: Row of an active room
|
|
||||||
|
|
||||||
- **GIVEN** an active room `demo-room` / `RM_ab12cd` with 6 unique identities, 7 sessions, 5 active, 12 MB up, 48 MB
|
|
||||||
down, an egress and an agent
|
|
||||||
- **WHEN** the row renders
|
|
||||||
- **THEN** it shows, in order, `RM_ab12cd` once, as a link to `/rooms/RM_ab12cd` with its copy button, `demo-room` with
|
|
||||||
its copy button, the active badge without a LIVE chip, the start time, a dash for Ended, a live duration, 6, 5,
|
|
||||||
"↑ 12.0 MB ↓ 48.0 MB" and the E and A badges, under headers that include "Unique identities" and "Data up / down"
|
|
||||||
|
|
||||||
#### Scenario: Sort by Ended
|
|
||||||
|
|
||||||
- **GIVEN** `status=all` and the default 24 h range
|
|
||||||
- **WHEN** the user activates the Ended header
|
|
||||||
- **THEN** the URL gets `sort=endedAt&order=desc` without `cursor`, the list holds the rooms started in the range,
|
|
||||||
and the active rooms, which have no end yet, come first
|
|
||||||
|
|
||||||
#### Scenario: Sort by unique identities
|
|
||||||
|
|
||||||
- **GIVEN** the default 24 h range
|
|
||||||
- **WHEN** the user activates the Unique identities header
|
|
||||||
- **THEN** the URL gets `sort=uniqueIdentities&order=desc` without `cursor` and the rooms with the most distinct
|
|
||||||
STANDARD identities come first
|
|
||||||
|
|
||||||
#### Scenario: Reconciler-closed room
|
|
||||||
|
|
||||||
- **GIVEN** a room with `closedBy: "reconciler"`
|
|
||||||
- **WHEN** the row renders
|
|
||||||
- **THEN** the status badge has the dotted outline and the Ended cell carries the tooltip "Closed by the reconciler:
|
|
||||||
the end time is estimated"
|
|
||||||
|
|
||||||
#### Scenario: Room of a node without media statistics
|
|
||||||
|
|
||||||
- **GIVEN** a room item with `mediaStats: false`
|
|
||||||
- **WHEN** the row renders
|
|
||||||
- **THEN** the Data up / down cell reads "—" with the tooltip "Not reported: this room's server does not report media
|
|
||||||
statistics" instead of "↑ 0 B ↓ 0 B"
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
## MODIFIED Requirements
|
|
||||||
|
|
||||||
### Requirement: Signal and data pseudo-tracks
|
|
||||||
Edition: CE and PRO. Stats whose track id starts with `TR_SG` or `TR_DT` SHALL NOT produce `track_stats`
|
|
||||||
documents; their bytes SHALL be added to the participant's `bytes` for their direction and `lastStatsAt`
|
|
||||||
SHALL be updated on the participant and on its room, without adding bytes or quality samples to the room.
|
|
||||||
|
|
||||||
#### Scenario: Signal-only participant
|
|
||||||
- **GIVEN** a participant whose flush contains only `TR_SG…` upstream and downstream stats
|
|
||||||
- **WHEN** it is stored
|
|
||||||
- **THEN** no `track_stats` document is written, the participant's `bytes.up` and `bytes.down` grow by the sample bytes, and the participant's and the room's `lastStatsAt` equal the flush time while the room's `bytes` and `quality` are unchanged
|
|
||||||
|
|
||||||
### Requirement: Node heartbeat
|
|
||||||
Edition: CE and PRO. On every batch tick, including empty batches, the writer SHALL upsert the node's
|
|
||||||
`nodes` document with `lastSeenAt`, `serverVersion`, `region`, `batchIntervalMs` (the configured
|
|
||||||
`openvidu.analytics.interval` in milliseconds) and `mediaStats`, setting `firstSeenAt` on insert.
|
|
||||||
|
|
||||||
#### Scenario: Idle node
|
|
||||||
- **GIVEN** a node with no traffic for ten minutes
|
|
||||||
- **WHEN** `nodes` is read
|
|
||||||
- **THEN** its document has `lastSeenAt` within the last batch interval and `firstSeenAt` unchanged
|
|
||||||
|
|
||||||
#### Scenario: Configured interval published
|
|
||||||
- **GIVEN** `openvidu.analytics.interval: 30s`
|
|
||||||
- **WHEN** a batch tick runs
|
|
||||||
- **THEN** the node's document has `batchIntervalMs: 30000`
|
|
||||||
|
|
||||||
#### Scenario: Interval changed on restart
|
|
||||||
- **GIVEN** a node document with `batchIntervalMs: 10000`
|
|
||||||
- **WHEN** the node restarts with `openvidu.analytics.interval: 60s` and runs a batch tick
|
|
||||||
- **THEN** its document has `batchIntervalMs: 60000` and `firstSeenAt` unchanged
|
|
||||||
|
|
||||||
#### Scenario: Node reporting media statistics
|
|
||||||
- **GIVEN** a server that reports media statistics
|
|
||||||
- **WHEN** a batch tick runs
|
|
||||||
- **THEN** the node's document has `mediaStats: true`
|
|
||||||
|
|
||||||
## ADDED Requirements
|
|
||||||
|
|
||||||
### Requirement: Nodes without media statistics
|
|
||||||
Edition: CE and PRO. A server whose media engine does not report media statistics SHALL tell the writer so, and the
|
|
||||||
writer SHALL then publish `mediaStats: false` on its node document and stamp `mediaStats: false` on the rooms and
|
|
||||||
participant sessions it creates, following their `nodeId`; the rooms and sessions of a node that reports them carry no
|
|
||||||
`mediaStats` field.
|
|
||||||
|
|
||||||
#### Scenario: Room created on such a node
|
|
||||||
- **GIVEN** a node whose server does not report media statistics
|
|
||||||
- **WHEN** it writes `ROOM_CREATED` and `PARTICIPANT_JOINED` of a new room
|
|
||||||
- **THEN** its `nodes` document has `mediaStats: false`, and the room and the participant session both have `nodeId` of that node and `mediaStats: false`
|
|
||||||
|
|
||||||
#### Scenario: Room created on a node reporting media statistics
|
|
||||||
- **GIVEN** a node whose server reports media statistics
|
|
||||||
- **WHEN** it writes `ROOM_CREATED` and `PARTICIPANT_JOINED`
|
|
||||||
- **THEN** neither the room nor the participant session has a `mediaStats` field
|
|
||||||
|
|
||||||
#### Scenario: Room touched first by another node
|
|
||||||
- **GIVEN** a room document created by an egress event written by a node reporting media statistics, without `mediaStats`
|
|
||||||
- **WHEN** a node that does not report them writes its `ROOM_CREATED`
|
|
||||||
- **THEN** the room has that node's `nodeId` and `mediaStats: false`
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
# Tasks
|
|
||||||
|
|
||||||
## 1. Contract and typings
|
|
||||||
|
|
||||||
- [x] 1.1 Add the optional `mediaStats` bool to the `nodes`, `rooms` and `participants` schemas of
|
|
||||||
`dashboard-ce/typings/contract/`, the document types of `src/data-model/` and the fixtures, then regenerate
|
|
||||||
`schema/` and `fixtures/`. Verify with the typings contract tests and the typecheck.
|
|
||||||
- [x] 1.2 Add `mediaStats?: false` to `RoomListItem` and `mediaStats?: 'partial' | 'none'` to `AnalyticsSummary` in
|
|
||||||
`src/api/`. Verify with the typecheck of every package.
|
|
||||||
|
|
||||||
## 2. Writer (openvidu-livekit)
|
|
||||||
|
|
||||||
- [x] 2.1 Vendor the contract into `openvidu/analytics/schema/contract/` with `contract/vendor.sh`. Verify with the
|
|
||||||
writer's contract tests and `./dashboard.sh contract-check`.
|
|
||||||
- [x] 2.2 Add `openviduconfig.MediaStats` (default true) and `analytics.Node.MediaStats`, set from it in
|
|
||||||
`openvidu/openvidu.go`, and write `mediaStats` on every heartbeat. Verify with a store integration test of the
|
|
||||||
heartbeat for both values.
|
|
||||||
- [x] 2.3 Stamp `mediaStats: false` on rooms and participant sessions created by a node without media statistics,
|
|
||||||
following `nodeId` (`ROOM_CREATED` sets or removes it; other events only while `nodeId` is unknown), and keep the
|
|
||||||
legacy migration at `MediaStats: true`. Verify with projection unit tests and writer integration tests of the three
|
|
||||||
scenarios of "Nodes without media statistics".
|
|
||||||
- [x] 2.4 Make signal and data pseudo-track stats advance the room's `lastStatsAt` without bytes or quality. Verify with
|
|
||||||
a projection unit test of the signal-only flush, the writer integration tests and the batch benchmark.
|
|
||||||
- [x] 2.5 Lint the fork with the Dockerfile Go toolchain and run its unit and integration tests. Verify that both pass.
|
|
||||||
|
|
||||||
## 3. Backend
|
|
||||||
|
|
||||||
- [x] 3.1 Return `mediaStats: false` on rooms list items of stamped rooms. Verify with a mapper unit test and a rooms
|
|
||||||
route integration test.
|
|
||||||
- [x] 3.2 Compute the summary's `mediaStats` coverage from the `nodes` documents whose span overlaps the range, for the
|
|
||||||
`global` and `node:<id>` scopes, and declare it in the OpenAPI document. Verify with unit tests of the fold, an
|
|
||||||
integration test of the four "Media statistics coverage" scenarios with an `explain()` assertion on the `nodes`
|
|
||||||
query, and the OpenAPI test.
|
|
||||||
|
|
||||||
## 4. Frontend
|
|
||||||
|
|
||||||
- [x] 4.1 Render "—" with the tooltip in the Data up / down cell of stamped rooms in the Rooms table. Verify with a
|
|
||||||
component test and an e2e test against the mocked API.
|
|
||||||
- [x] 4.2 Apply the summary coverage to the Data up / down tile and the Bandwidth section (notice for `none`, note for
|
|
||||||
`partial`), and expose it to the extension cards as `AnalyticsPageContext.mediaStats`. Verify with component tests,
|
|
||||||
a test of the context signal and an e2e test of the three "Media statistics coverage notes" scenarios.
|
|
||||||
|
|
||||||
## 5. Data generator
|
|
||||||
|
|
||||||
- [x] 5.1 Add `--nodes-without-media-stats <n>` (default 0) to the data generator and keep the flag in the keep-alive,
|
|
||||||
let a calling demo pass extra generator options (`GENERATOR_ARGS` of `demo-lib.sh`), and update the README. Verify with a generator test that the marked node's rooms and sessions are stamped and have no
|
|
||||||
media `track_stats`, room bytes or quality, and that the other nodes report `mediaStats: true`.
|
|
||||||
|
|
||||||
## 6. Docs and CHANGELOG
|
|
||||||
|
|
||||||
- [x] 6.1 No CHANGELOG line: unreleased, and a CE server always reports media statistics. Update the `AGENTS.md` notes
|
|
||||||
that list the node heartbeat fields or the generator options, if any. Verify by reading the change against the
|
|
||||||
CHANGELOG and the `AGENTS.md` files.
|
|
||||||
|
|
||||||
## 7. Integration check
|
|
||||||
|
|
||||||
- [x] 7.1 Run `./dashboard.sh check` and `./dashboard.sh contract-check ../openvidu-livekit`. Verify that both pass.
|
|
||||||
- [x] 7.2 Run the analytics writer suite of `openvidu-test-integration` against a local deployment with the new
|
|
||||||
server image. Verify that the node document has `mediaStats: true` and that rooms of signal-only participants get
|
|
||||||
`lastStatsAt`.
|
|
||||||
|
|
@ -357,3 +357,35 @@ range by input type. It is not gated by any feature.
|
||||||
- **GIVEN** no ingress session started in the range
|
- **GIVEN** no ingress session started in the range
|
||||||
- **WHEN** `dimensions=ingressInputType` is requested
|
- **WHEN** `dimensions=ingressInputType` is requested
|
||||||
- **THEN** the breakdown has `total: 0` and `items: []`
|
- **THEN** the breakdown has `total: 0` and `items: []`
|
||||||
|
|
||||||
|
### Requirement: Media statistics coverage
|
||||||
|
|
||||||
|
Edition: CE. The summary SHALL carry `mediaStats: "none"` when every `nodes` document of its scope whose heartbeat span
|
||||||
|
(`firstSeenAt` to `lastSeenAt`) overlaps the range has `mediaStats: false`, `mediaStats: "partial"` when only some do,
|
||||||
|
and no `mediaStats` property when none does or no node document overlaps the range. The `global` scope covers every
|
||||||
|
node, a `node:<id>` scope that node only.
|
||||||
|
|
||||||
|
#### Scenario: No server reports media statistics
|
||||||
|
|
||||||
|
- **GIVEN** two nodes seen during the range, both with `mediaStats: false`
|
||||||
|
- **WHEN** the summary is requested
|
||||||
|
- **THEN** the body has `mediaStats: "none"` and still carries `bytesUp` and `bytesDown`
|
||||||
|
|
||||||
|
#### Scenario: Some servers report media statistics
|
||||||
|
|
||||||
|
- **GIVEN** a node with `mediaStats: false` and a node with `mediaStats: true` seen during the range
|
||||||
|
- **WHEN** the summary is requested with the `global` scope
|
||||||
|
- **THEN** the body has `mediaStats: "partial"`
|
||||||
|
|
||||||
|
#### Scenario: Node scope
|
||||||
|
|
||||||
|
- **GIVEN** the same two nodes and `scope=node:<id>` of the node with `mediaStats: true`
|
||||||
|
- **WHEN** the summary is requested
|
||||||
|
- **THEN** the body has no `mediaStats` property
|
||||||
|
|
||||||
|
#### Scenario: Node seen outside the range
|
||||||
|
|
||||||
|
- **GIVEN** a node with `mediaStats: false` whose `lastSeenAt` is earlier than `from`, and a node with
|
||||||
|
`mediaStats: true` seen during the range
|
||||||
|
- **WHEN** the summary is requested
|
||||||
|
- **THEN** the body has no `mediaStats` property
|
||||||
|
|
|
||||||
|
|
@ -361,3 +361,29 @@ and the time zone of its time axis; the unique identities chart names UTC and ke
|
||||||
- **GIVEN** `granularity=1d` and `tz=Europe/Madrid`
|
- **GIVEN** `granularity=1d` and `tz=Europe/Madrid`
|
||||||
- **WHEN** the unique identities chart renders
|
- **WHEN** the unique identities chart renders
|
||||||
- **THEN** its note reads "per UTC day" and names UTC, not Europe/Madrid
|
- **THEN** its note reads "per UTC day" and names UTC, not Europe/Madrid
|
||||||
|
|
||||||
|
### Requirement: Media statistics coverage notes
|
||||||
|
|
||||||
|
Edition: CE. With `mediaStats: "none"` in the summary, the Data up / down tile SHALL read "—" with the hint "Not
|
||||||
|
reported by the servers of this range" and the Bandwidth section SHALL show the notice "The servers of this range do
|
||||||
|
not report media statistics" instead of its chart and tiles. With `"partial"`, the tile and the section SHALL keep their
|
||||||
|
values and add the note "Only from the servers that report media statistics".
|
||||||
|
|
||||||
|
#### Scenario: No server reports media statistics
|
||||||
|
|
||||||
|
- **GIVEN** a summary with `mediaStats: "none"`
|
||||||
|
- **WHEN** the page renders
|
||||||
|
- **THEN** the Data up / down tile reads "—" with its hint, and the Bandwidth section shows the notice and no chart
|
||||||
|
|
||||||
|
#### Scenario: Some servers report media statistics
|
||||||
|
|
||||||
|
- **GIVEN** a summary with `mediaStats: "partial"`, `bytesUp: 1200000000` and `bytesDown: 3400000000`
|
||||||
|
- **WHEN** the page renders
|
||||||
|
- **THEN** the tile reads "↑ 1.2 GB" over "↓ 3.4 GB" with the hint "Only from the servers that report media
|
||||||
|
statistics", and the Bandwidth chart renders with the same note under its heading
|
||||||
|
|
||||||
|
#### Scenario: Every server reports media statistics
|
||||||
|
|
||||||
|
- **GIVEN** a summary without `mediaStats`
|
||||||
|
- **WHEN** the page renders
|
||||||
|
- **THEN** the tile and the Bandwidth section render as before, without the note
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ fields, with the field list of the shared contract (`dashboard-ce/typings/src/da
|
||||||
|
|
||||||
#### Scenario: Fields of a room document
|
#### Scenario: Fields of a room document
|
||||||
- **WHEN** a room document is read
|
- **WHEN** a room document is read
|
||||||
- **THEN** it has `name`, `nameLower`, `status` (`active` | `closed`), `createdAt`, `endedAt`, `durationMs`, `endReason`, `closedBy`, `nodeId`, `config` (max participants, empty and departure timeouts, metadata when allowed), `participants { sessions, unique, active, peakActive, aborted, byKind }`, `tracks`, `egress` (by type), `ingress`, `bytes { up, down }`, `quality { scoreSum, scoreCount, minScore, poor, lost }`, `features { egress, ingress, agent, sip }`, `lastEventAt`, `lastStatsAt`, `schemaVersion`, `expiresAt`
|
- **THEN** it has `name`, `nameLower`, `status` (`active` | `closed`), `createdAt`, `endedAt`, `durationMs`, `endReason`, `closedBy`, `nodeId`, `config` (max participants, empty and departure timeouts, metadata when allowed), `participants { sessions, unique, active, peakActive, aborted, byKind }`, `tracks`, `egress` (by type), `ingress`, `bytes { up, down }`, `quality { scoreSum, scoreCount, minScore, poor, lost }`, `features { egress, ingress, agent, sip }`, `lastEventAt`, `lastStatsAt`, `mediaStats` (only `false`, when its node does not report media statistics), `schemaVersion`, `expiresAt`
|
||||||
|
|
||||||
#### Scenario: Room lifecycle
|
#### Scenario: Room lifecycle
|
||||||
- **GIVEN** `ROOM_CREATED`, three `PARTICIPANT_JOINED`/`ACTIVE`, one `PARTICIPANT_LEFT`, `ROOM_ENDED (API_DELETE)`
|
- **GIVEN** `ROOM_CREATED`, three `PARTICIPANT_JOINED`/`ACTIVE`, one `PARTICIPANT_LEFT`, `ROOM_ENDED (API_DELETE)`
|
||||||
|
|
@ -106,7 +106,7 @@ traffic, quality and retention fields, with the field list of the shared contrac
|
||||||
|
|
||||||
#### Scenario: Fields of a participant document
|
#### Scenario: Fields of a participant document
|
||||||
- **WHEN** a participant document is read
|
- **WHEN** a participant document is read
|
||||||
- **THEN** it has `identity`, `identityLower`, `name`, `roomSid`, `roomName`, `kind`, `kindDetails`, `status` (`joining` | `active` | `left` | `aborted`), `joinedAt`, `activeAt`, `leftAt`, `durationMs`, `disconnectReason`, `closedBy`, `connectTimeMs`, `connectionType`, `region`, `nodeId`, `resumes`, `client { sdk, version, os, osVersion, browser, browserVersion, deviceModel, network, address }`, `permission`, `isPublisher`, `metadata` and `attributes` (when allowed), `tracks { published, bySource, subscribed, subscribeFailures }`, `bytes`, `quality`, `lastEventAt`, `lastStatsAt`, `schemaVersion`, `expiresAt`
|
- **THEN** it has `identity`, `identityLower`, `name`, `roomSid`, `roomName`, `kind`, `kindDetails`, `status` (`joining` | `active` | `left` | `aborted`), `joinedAt`, `activeAt`, `leftAt`, `durationMs`, `disconnectReason`, `closedBy`, `connectTimeMs`, `connectionType`, `region`, `nodeId`, `resumes`, `client { sdk, version, os, osVersion, browser, browserVersion, deviceModel, network, address }`, `permission`, `isPublisher`, `metadata` and `attributes` (when allowed), `tracks { published, bySource, subscribed, subscribeFailures }`, `bytes`, `quality`, `lastEventAt`, `lastStatsAt`, `mediaStats` (only `false`, when its node does not report media statistics), `schemaVersion`, `expiresAt`
|
||||||
|
|
||||||
#### Scenario: Connection aborted
|
#### Scenario: Connection aborted
|
||||||
- **GIVEN** `PARTICIPANT_JOINED` followed by `PARTICIPANT_CONNECTION_ABORTED`
|
- **GIVEN** `PARTICIPANT_JOINED` followed by `PARTICIPANT_CONNECTION_ABORTED`
|
||||||
|
|
@ -287,9 +287,10 @@ participant counter SHALL count STANDARD sessions only, except `participants.byK
|
||||||
|
|
||||||
### Requirement: Nodes
|
### Requirement: Nodes
|
||||||
Edition: CE and PRO. The `nodes` collection SHALL hold one document per server node with `firstSeenAt`,
|
Edition: CE and PRO. The `nodes` collection SHALL hold one document per server node with `firstSeenAt`,
|
||||||
`lastSeenAt`, `serverVersion`, `region` and `batchIntervalMs` (int32 milliseconds, at least 1 000: the node's
|
`lastSeenAt`, `serverVersion`, `region`, `batchIntervalMs` (int32 milliseconds, at least 1 000: the node's
|
||||||
configured batch interval), updated by every writer batch, and SHALL expire documents 30 days after their last
|
configured batch interval) and `mediaStats`, updated by every writer batch, and SHALL expire documents 30 days after
|
||||||
heartbeat so decommissioned nodes disappear. A document without `batchIntervalMs` SHALL be read as a 10 s interval.
|
their last heartbeat so decommissioned nodes disappear. A document without `batchIntervalMs` SHALL be read as a 10 s
|
||||||
|
interval, and one without `mediaStats` as `true`.
|
||||||
|
|
||||||
#### Scenario: Node heartbeat
|
#### Scenario: Node heartbeat
|
||||||
- **GIVEN** a node writing batches every 10 s
|
- **GIVEN** a node writing batches every 10 s
|
||||||
|
|
@ -306,6 +307,11 @@ heartbeat so decommissioned nodes disappear. A document without `batchIntervalMs
|
||||||
- **WHEN** a `nodes` document with `batchIntervalMs: 500` is written
|
- **WHEN** a `nodes` document with `batchIntervalMs: 500` is written
|
||||||
- **THEN** the write is rejected by the validator
|
- **THEN** the write is rejected by the validator
|
||||||
|
|
||||||
|
#### Scenario: Node document without media statistics
|
||||||
|
- **GIVEN** a `nodes` document without `mediaStats`
|
||||||
|
- **WHEN** the dashboard reads it
|
||||||
|
- **THEN** it treats the node as reporting media statistics
|
||||||
|
|
||||||
### Requirement: Retention
|
### Requirement: Retention
|
||||||
Edition: CE and PRO. Retention SHALL be configurable per class with defaults events 7 d, stats 7 d,
|
Edition: CE and PRO. Retention SHALL be configurable per class with defaults events 7 d, stats 7 d,
|
||||||
tracks (tracks and subscriptions) 7 d, entities (rooms, participants, egress, ingress) 90 d, 5-minute rollups 7 d,
|
tracks (tracks and subscriptions) 7 d, entities (rooms, participants, egress, ingress) 90 d, 5-minute rollups 7 d,
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,8 @@ filters `status`, `q`, `features` and `nodeId`, the sort keys `createdAt` (defau
|
||||||
|
|
||||||
Edition: CE. Each rooms list item SHALL be the room document's public projection: identity, lifecycle, node,
|
Edition: CE. Each rooms list item SHALL be the room document's public projection: identity, lifecycle, node,
|
||||||
participant, egress and ingress counters, bandwidth, feature flags, the end cause behind the "estimated" cue, the last
|
participant, egress and ingress counters, bandwidth, feature flags, the end cause behind the "estimated" cue, the last
|
||||||
stats flush and the `stale` flag of `api-conventions`, never internal id arrays, `schemaVersion` or `expiresAt`.
|
stats flush, the media statistics mark and the `stale` flag of `api-conventions`, never internal id arrays,
|
||||||
|
`schemaVersion` or `expiresAt`.
|
||||||
Participant counters count STANDARD sessions only; `participants.unique` counts distinct STANDARD identities.
|
Participant counters count STANDARD sessions only; `participants.unique` counts distinct STANDARD identities.
|
||||||
|
|
||||||
#### Scenario: Fields of an item
|
#### Scenario: Fields of an item
|
||||||
|
|
@ -52,8 +53,8 @@ Participant counters count STANDARD sessions only; `participants.unique` counts
|
||||||
- **THEN** it has `sid`, `name`, `status` (`active` | `closed`), `createdAt`, `endedAt?`, `durationMs?`, `closedBy?`
|
- **THEN** it has `sid`, `name`, `status` (`active` | `closed`), `createdAt`, `endedAt?`, `durationMs?`, `closedBy?`
|
||||||
(`event` | `reconciler`), `nodeId?`, `participants { sessions, unique, active, peakActive }`,
|
(`event` | `reconciler`), `nodeId?`, `participants { sessions, unique, active, peakActive }`,
|
||||||
`egress { total, active }`, `ingress { sessions, active }`, `bytes { up, down }`,
|
`egress { total, active }`, `ingress { sessions, active }`, `bytes { up, down }`,
|
||||||
`features { egress, ingress, agent, sip }`, `lastStatsAt?`, `stale?` and no `participantSids`, `identities` or
|
`features { egress, ingress, agent, sip }`, `lastStatsAt?`, `stale?`, `mediaStats?` and no `participantSids`,
|
||||||
similar array
|
`identities` or similar array
|
||||||
|
|
||||||
#### Scenario: Active room
|
#### Scenario: Active room
|
||||||
|
|
||||||
|
|
@ -74,6 +75,13 @@ Participant counters count STANDARD sessions only; `participants.unique` counts
|
||||||
- **WHEN** it is returned
|
- **WHEN** it is returned
|
||||||
- **THEN** it has `stale: true`, and an active room whose `lastStatsAt` is 20 s old has no `stale` property
|
- **THEN** it has `stale: true`, and an active room whose `lastStatsAt` is 20 s old has no `stale` property
|
||||||
|
|
||||||
|
#### Scenario: Room of a node without media statistics
|
||||||
|
|
||||||
|
- **GIVEN** a room document with `mediaStats: false` and `bytes { up: 0, down: 0 }`
|
||||||
|
- **WHEN** it is returned
|
||||||
|
- **THEN** it has `mediaStats: false` and `bytes { up: 0, down: 0 }`, and an item of a room without the field has no
|
||||||
|
`mediaStats` property
|
||||||
|
|
||||||
### Requirement: Status filter
|
### Requirement: Status filter
|
||||||
|
|
||||||
Edition: CE. The rooms list SHALL accept `status` as `active`, `closed` or `all` (default `all`) and reject any other
|
Edition: CE. The rooms list SHALL accept `status` as `active`, `closed` or `all` (default `all`) and reject any other
|
||||||
|
|
|
||||||
|
|
@ -122,6 +122,13 @@ Active, Data up / down, Features (badges E, I, A) and Node (hidden by default);
|
||||||
- **THEN** the status badge has the dotted outline and the Ended cell carries the tooltip "Closed by the reconciler:
|
- **THEN** the status badge has the dotted outline and the Ended cell carries the tooltip "Closed by the reconciler:
|
||||||
the end time is estimated"
|
the end time is estimated"
|
||||||
|
|
||||||
|
#### Scenario: Room of a node without media statistics
|
||||||
|
|
||||||
|
- **GIVEN** a room item with `mediaStats: false`
|
||||||
|
- **WHEN** the row renders
|
||||||
|
- **THEN** the Data up / down cell reads "—" with the tooltip "Not reported: this room's server does not report media
|
||||||
|
statistics" instead of "↑ 0 B ↓ 0 B"
|
||||||
|
|
||||||
### Requirement: Rooms pagination
|
### Requirement: Rooms pagination
|
||||||
|
|
||||||
Edition: CE. The Rooms page SHALL use the cursor paginator with Prev/Next bound to `prevCursor`/`nextCursor` for every
|
Edition: CE. The Rooms page SHALL use the cursor paginator with Prev/Next bound to `prevCursor`/`nextCursor` for every
|
||||||
|
|
|
||||||
|
|
@ -186,12 +186,12 @@ per track and direction. Both modes produce the same document shape.
|
||||||
### Requirement: Signal and data pseudo-tracks
|
### Requirement: Signal and data pseudo-tracks
|
||||||
Edition: CE and PRO. Stats whose track id starts with `TR_SG` or `TR_DT` SHALL NOT produce `track_stats`
|
Edition: CE and PRO. Stats whose track id starts with `TR_SG` or `TR_DT` SHALL NOT produce `track_stats`
|
||||||
documents; their bytes SHALL be added to the participant's `bytes` for their direction and `lastStatsAt`
|
documents; their bytes SHALL be added to the participant's `bytes` for their direction and `lastStatsAt`
|
||||||
SHALL be updated.
|
SHALL be updated on the participant and on its room, without adding bytes or quality samples to the room.
|
||||||
|
|
||||||
#### Scenario: Signal-only participant
|
#### Scenario: Signal-only participant
|
||||||
- **GIVEN** a participant whose flush contains only `TR_SG…` upstream and downstream stats
|
- **GIVEN** a participant whose flush contains only `TR_SG…` upstream and downstream stats
|
||||||
- **WHEN** it is stored
|
- **WHEN** it is stored
|
||||||
- **THEN** no `track_stats` document is written, the participant's `bytes.up` and `bytes.down` grow by the sample bytes and `lastStatsAt` equals the flush time
|
- **THEN** no `track_stats` document is written, the participant's `bytes.up` and `bytes.down` grow by the sample bytes, and the participant's and the room's `lastStatsAt` equal the flush time while the room's `bytes` and `quality` are unchanged
|
||||||
|
|
||||||
### Requirement: Stats deltas into entities and rollups
|
### Requirement: Stats deltas into entities and rollups
|
||||||
Edition: CE and PRO. Each stats batch SHALL add its byte totals and quality samples (score sum, sample
|
Edition: CE and PRO. Each stats batch SHALL add its byte totals and quality samples (score sum, sample
|
||||||
|
|
@ -381,8 +381,8 @@ OpenVidu 3.9 or earlier (one holding a document without `ts` while `schema_meta`
|
||||||
|
|
||||||
### Requirement: Node heartbeat
|
### Requirement: Node heartbeat
|
||||||
Edition: CE and PRO. On every batch tick, including empty batches, the writer SHALL upsert the node's
|
Edition: CE and PRO. On every batch tick, including empty batches, the writer SHALL upsert the node's
|
||||||
`nodes` document with `lastSeenAt`, `serverVersion`, `region` and `batchIntervalMs` (the configured
|
`nodes` document with `lastSeenAt`, `serverVersion`, `region`, `batchIntervalMs` (the configured
|
||||||
`openvidu.analytics.interval` in milliseconds), setting `firstSeenAt` on insert.
|
`openvidu.analytics.interval` in milliseconds) and `mediaStats`, setting `firstSeenAt` on insert.
|
||||||
|
|
||||||
#### Scenario: Idle node
|
#### Scenario: Idle node
|
||||||
- **GIVEN** a node with no traffic for ten minutes
|
- **GIVEN** a node with no traffic for ten minutes
|
||||||
|
|
@ -399,6 +399,11 @@ Edition: CE and PRO. On every batch tick, including empty batches, the writer SH
|
||||||
- **WHEN** the node restarts with `openvidu.analytics.interval: 60s` and runs a batch tick
|
- **WHEN** the node restarts with `openvidu.analytics.interval: 60s` and runs a batch tick
|
||||||
- **THEN** its document has `batchIntervalMs: 60000` and `firstSeenAt` unchanged
|
- **THEN** its document has `batchIntervalMs: 60000` and `firstSeenAt` unchanged
|
||||||
|
|
||||||
|
#### Scenario: Node reporting media statistics
|
||||||
|
- **GIVEN** a server that reports media statistics
|
||||||
|
- **WHEN** a batch tick runs
|
||||||
|
- **THEN** the node's document has `mediaStats: true`
|
||||||
|
|
||||||
### Requirement: Write path metrics
|
### Requirement: Write path metrics
|
||||||
Edition: CE and PRO. The writer SHALL expose Prometheus metrics on the server's metrics endpoint with the
|
Edition: CE and PRO. The writer SHALL expose Prometheus metrics on the server's metrics endpoint with the
|
||||||
`openvidu_analytics_` prefix: queue depth per queue, batches by result, batch duration, dropped entries by
|
`openvidu_analytics_` prefix: queue depth per queue, batches by result, batch duration, dropped entries by
|
||||||
|
|
@ -541,3 +546,24 @@ failure SHALL still stop the server.
|
||||||
- **GIVEN** `mongo_url` pointing to a MongoDB that does not answer
|
- **GIVEN** `mongo_url` pointing to a MongoDB that does not answer
|
||||||
- **WHEN** the server starts
|
- **WHEN** the server starts
|
||||||
- **THEN** it refuses to start, as before
|
- **THEN** it refuses to start, as before
|
||||||
|
|
||||||
|
### Requirement: Nodes without media statistics
|
||||||
|
Edition: CE and PRO. A server whose media engine does not report media statistics SHALL tell the writer so, and the
|
||||||
|
writer SHALL then publish `mediaStats: false` on its node document and stamp `mediaStats: false` on the rooms and
|
||||||
|
participant sessions it creates, following their `nodeId`; the rooms and sessions of a node that reports them carry no
|
||||||
|
`mediaStats` field.
|
||||||
|
|
||||||
|
#### Scenario: Room created on such a node
|
||||||
|
- **GIVEN** a node whose server does not report media statistics
|
||||||
|
- **WHEN** it writes `ROOM_CREATED` and `PARTICIPANT_JOINED` of a new room
|
||||||
|
- **THEN** its `nodes` document has `mediaStats: false`, and the room and the participant session both have `nodeId` of that node and `mediaStats: false`
|
||||||
|
|
||||||
|
#### Scenario: Room created on a node reporting media statistics
|
||||||
|
- **GIVEN** a node whose server reports media statistics
|
||||||
|
- **WHEN** it writes `ROOM_CREATED` and `PARTICIPANT_JOINED`
|
||||||
|
- **THEN** neither the room nor the participant session has a `mediaStats` field
|
||||||
|
|
||||||
|
#### Scenario: Room touched first by another node
|
||||||
|
- **GIVEN** a room document created by an egress event written by a node reporting media statistics, without `mediaStats`
|
||||||
|
- **WHEN** a node that does not report them writes its `ROOM_CREATED`
|
||||||
|
- **THEN** the room has that node's `nodeId` and `mediaStats: false`
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue