2026-10-25 16:35:47 +01:00
|
|
|
|
import type {
|
|
|
|
|
|
DailyRollupDocument,
|
|
|
|
|
|
FiveMinuteRollupDocument,
|
|
|
|
|
|
HourlyRollupDocument,
|
|
|
|
|
|
TrackStatsDocument
|
|
|
|
|
|
} from '../../src/index.js';
|
openvidu-dashboard: media node labels, and nothing about nodes in CE
The contract gains mediaNode on nodes, rooms and participants, stoppedAt on nodes and the
partial mediaNode index that replaces the nodeId one; the scenario's two servers are
labelled. The status reports the writer without node counts and with stoppedAt when the
server stopped, the rooms list filters by mediaNode, list items carry it, the overview
groups by it for editions and a node scope's media statistics coverage follows it.
CE renders nothing about nodes without per-node-breakdown: no nodes line, By node card,
Node select, scope selector or Node columns, not even locked; the Overview reads "Server
stopped at" for a stopped server. The generator gains --media-nodes, which node rollups
now require.
2026-11-04 12:13:58 +01:00
|
|
|
|
import { ACTIVE_ROOM, at, between, DAY, MEDIA_NODE_ALPHA, MEDIA_NODE_BETA, NODE_BETA, P, T } from './scenario.js';
|
2026-10-25 16:35:47 +01:00
|
|
|
|
|
|
|
|
|
|
const statsAt = at('10:35:00');
|
|
|
|
|
|
const daveUp = { roomSid: ACTIVE_ROOM.sid, participantSid: P.dave, kind: 'up', nodeId: NODE_BETA } as const;
|
|
|
|
|
|
const daveDown = { ...daveUp, kind: 'down' } as const;
|
|
|
|
|
|
|
|
|
|
|
|
const cameraUp = {
|
|
|
|
|
|
score: 4.2,
|
|
|
|
|
|
minScore: 4,
|
|
|
|
|
|
medianScore: 4.2,
|
|
|
|
|
|
bytes: 3_000_000,
|
|
|
|
|
|
packets: 2_900,
|
|
|
|
|
|
packetsLost: 2,
|
|
|
|
|
|
rttMs: 45,
|
|
|
|
|
|
jitterMs: 3.2,
|
|
|
|
|
|
nacks: 1,
|
|
|
|
|
|
plis: 0,
|
|
|
|
|
|
firs: 0,
|
|
|
|
|
|
frames: 900,
|
|
|
|
|
|
layers: [{ layer: 0, bytes: 3_000_000, packets: 2_900, frames: 900 }]
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const microphoneUp = {
|
|
|
|
|
|
score: 4,
|
|
|
|
|
|
minScore: 3.9,
|
|
|
|
|
|
medianScore: 4,
|
|
|
|
|
|
bytes: 150_000,
|
|
|
|
|
|
packets: 500,
|
|
|
|
|
|
packetsLost: 0,
|
|
|
|
|
|
rttMs: 44,
|
|
|
|
|
|
jitterMs: 1.1,
|
|
|
|
|
|
nacks: 0,
|
|
|
|
|
|
plis: 0,
|
|
|
|
|
|
firs: 0,
|
|
|
|
|
|
frames: 0,
|
|
|
|
|
|
layers: []
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const ingressDown = {
|
|
|
|
|
|
score: 4.3,
|
|
|
|
|
|
minScore: 4.3,
|
|
|
|
|
|
medianScore: 4.3,
|
|
|
|
|
|
bytes: 16_875_000,
|
|
|
|
|
|
packets: 12_000,
|
|
|
|
|
|
packetsLost: 0,
|
|
|
|
|
|
rttMs: 45,
|
|
|
|
|
|
jitterMs: 2.1,
|
|
|
|
|
|
nacks: 0,
|
|
|
|
|
|
plis: 0,
|
|
|
|
|
|
firs: 0,
|
|
|
|
|
|
frames: 900,
|
|
|
|
|
|
layers: [{ layer: 0, bytes: 16_875_000, packets: 12_000, frames: 900 }]
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/** The same flush in `participant` detail (first two documents) and in `track` detail (last three). */
|
|
|
|
|
|
export const trackStats: TrackStatsDocument[] = [
|
|
|
|
|
|
{
|
|
|
|
|
|
ts: statsAt,
|
|
|
|
|
|
meta: daveUp,
|
|
|
|
|
|
score: 4.1,
|
|
|
|
|
|
minScore: 3.9,
|
|
|
|
|
|
medianScore: 4.1,
|
|
|
|
|
|
bytes: 3_150_000,
|
|
|
|
|
|
packets: 3_400,
|
|
|
|
|
|
packetsLost: 2,
|
|
|
|
|
|
rttMs: 45,
|
|
|
|
|
|
jitterMs: 3.2,
|
|
|
|
|
|
nacks: 1,
|
|
|
|
|
|
plis: 0,
|
|
|
|
|
|
firs: 0,
|
|
|
|
|
|
frames: 900,
|
|
|
|
|
|
layers: [{ layer: 0, bytes: 3_000_000, packets: 2_900, frames: 900 }]
|
|
|
|
|
|
},
|
|
|
|
|
|
{ ts: statsAt, meta: daveDown, ...ingressDown },
|
|
|
|
|
|
{ ts: statsAt, meta: { ...daveUp, trackSid: T.daveCamera }, ...cameraUp, mimeType: 'video/H264' },
|
|
|
|
|
|
{ ts: statsAt, meta: { ...daveUp, trackSid: T.daveMic }, ...microphoneUp, mimeType: 'audio/opus' },
|
|
|
|
|
|
{ ts: statsAt, meta: { ...daveDown, trackSid: T.ingressVideo2 }, ...ingressDown, mimeType: 'video/H264' }
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
function rollupId(scope: string, bucket: Date): string {
|
|
|
|
|
|
return `${scope}:${bucket.toISOString().replace('.000Z', 'Z')}`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const fiveMinBucket = at('10:30:00');
|
|
|
|
|
|
const fiveMinCounters: Omit<FiveMinuteRollupDocument, '_id' | 'scope' | 'bucket'> = {
|
|
|
|
|
|
participants: { connected: 3, joined: 1 },
|
|
|
|
|
|
bytes: { up: 13_986_950, down: 12_811_470 },
|
|
|
|
|
|
quality: { scoreSum: 21.6, scoreCount: 5 }
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export const analytics5min: FiveMinuteRollupDocument[] = [
|
|
|
|
|
|
{
|
|
|
|
|
|
_id: rollupId('global', fiveMinBucket),
|
|
|
|
|
|
scope: 'global',
|
|
|
|
|
|
bucket: fiveMinBucket,
|
|
|
|
|
|
...fiveMinCounters,
|
|
|
|
|
|
internal: { connectedSids: [P.dave, P.ingress2, P.egressStreamer] }
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
openvidu-dashboard: media node labels, and nothing about nodes in CE
The contract gains mediaNode on nodes, rooms and participants, stoppedAt on nodes and the
partial mediaNode index that replaces the nodeId one; the scenario's two servers are
labelled. The status reports the writer without node counts and with stoppedAt when the
server stopped, the rooms list filters by mediaNode, list items carry it, the overview
groups by it for editions and a node scope's media statistics coverage follows it.
CE renders nothing about nodes without per-node-breakdown: no nodes line, By node card,
Node select, scope selector or Node columns, not even locked; the Overview reads "Server
stopped at" for a stopped server. The generator gains --media-nodes, which node rollups
now require.
2026-11-04 12:13:58 +01:00
|
|
|
|
_id: rollupId(`node:${MEDIA_NODE_BETA}`, fiveMinBucket),
|
|
|
|
|
|
scope: `node:${MEDIA_NODE_BETA}`,
|
2026-10-25 16:35:47 +01:00
|
|
|
|
bucket: fiveMinBucket,
|
|
|
|
|
|
...fiveMinCounters,
|
|
|
|
|
|
internal: { connectedSids: [P.dave, P.ingress2, P.egressStreamer] }
|
|
|
|
|
|
}
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
/** Participant minutes of the STANDARD sessions that left: (leftAt − joinedAt) / 60 000 each, as the writer adds them. */
|
|
|
|
|
|
function minutes(sessions: [Date, Date][]): number {
|
|
|
|
|
|
return sessions.reduce((sum, [joinedAt, leftAt]) => sum + between(joinedAt, leftAt) / 60_000, 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const closedRoomMinutes = minutes([
|
|
|
|
|
|
[at('09:00:05'), at('09:40:00')],
|
|
|
|
|
|
[at('09:01:00'), at('09:10:00')],
|
|
|
|
|
|
[at('09:10:02'), at('09:45:00')]
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
const hourBucket = at('09:00:00');
|
|
|
|
|
|
const closedRoomHour: Omit<HourlyRollupDocument, '_id' | 'scope' | 'bucket'> = {
|
|
|
|
|
|
rooms: { created: 1, ended: 1, endedByReason: { API_DELETE: 1 }, durationMsSum: 2_700_000, participantsSum: 4 },
|
|
|
|
|
|
participants: {
|
|
|
|
|
|
joined: 4,
|
|
|
|
|
|
active: 3,
|
|
|
|
|
|
aborted: 1,
|
|
|
|
|
|
left: 3,
|
|
|
|
|
|
byKind: { STANDARD: 4, EGRESS: 1, AGENT: 1, SIP: 1 },
|
|
|
|
|
|
bySdk: { JS: 4 },
|
|
|
|
|
|
byOs: { windows: 1, macos: 2, linux: 1 },
|
|
|
|
|
|
byBrowser: { chrome: 2, safari: 2 },
|
|
|
|
|
|
byConnectionType: { udp: 2, tcp: 1 },
|
|
|
|
|
|
disconnectReasons: {
|
|
|
|
|
|
CLIENT_INITIATED: 1,
|
|
|
|
|
|
DUPLICATE_IDENTITY: 1,
|
|
|
|
|
|
ROOM_DELETED: 1,
|
|
|
|
|
|
CONNECTION_TIMEOUT: 1
|
|
|
|
|
|
},
|
|
|
|
|
|
connectTimeSumMs: 3_700,
|
|
|
|
|
|
connectTimeCount: 3,
|
|
|
|
|
|
minutes: closedRoomMinutes
|
|
|
|
|
|
},
|
|
|
|
|
|
tracks: { published: 2, subscribed: 2, subscribeFailed: 1 },
|
|
|
|
|
|
egress: {
|
|
|
|
|
|
started: 2,
|
|
|
|
|
|
byType: { room_composite: 1, track: 1 },
|
|
|
|
|
|
endedByStatus: { complete: 1, failed: 1 },
|
|
|
|
|
|
durationMs: 2_340_000
|
|
|
|
|
|
},
|
|
|
|
|
|
bytes: { up: 385_000_000, down: 888_000_000 },
|
|
|
|
|
|
quality: { scoreSum: 1267, scoreCount: 320, poor: 6 },
|
|
|
|
|
|
apiCalls: {
|
|
|
|
|
|
total: 6,
|
|
|
|
|
|
byMethod: {
|
|
|
|
|
|
RoomService_CreateRoom: 1,
|
|
|
|
|
|
RoomService_DeleteRoom: 1,
|
|
|
|
|
|
RoomService_ListParticipants: 2,
|
|
|
|
|
|
Egress_StartRoomCompositeEgress: 1,
|
|
|
|
|
|
Egress_StartTrackEgress: 1
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
apiErrors: { total: 1, byMethod: { RoomService_ListParticipants: 1 } }
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export const analyticsHourly: HourlyRollupDocument[] = [
|
|
|
|
|
|
{
|
|
|
|
|
|
_id: rollupId('global', hourBucket),
|
|
|
|
|
|
scope: 'global',
|
|
|
|
|
|
bucket: hourBucket,
|
|
|
|
|
|
...closedRoomHour,
|
|
|
|
|
|
// The ingresses created at 09:50 and 09:55 on node beta.
|
|
|
|
|
|
apiCalls: {
|
|
|
|
|
|
total: 8,
|
|
|
|
|
|
byMethod: { ...closedRoomHour.apiCalls?.byMethod, Ingress_CreateIngress: 2 }
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
openvidu-dashboard: media node labels, and nothing about nodes in CE
The contract gains mediaNode on nodes, rooms and participants, stoppedAt on nodes and the
partial mediaNode index that replaces the nodeId one; the scenario's two servers are
labelled. The status reports the writer without node counts and with stoppedAt when the
server stopped, the rooms list filters by mediaNode, list items carry it, the overview
groups by it for editions and a node scope's media statistics coverage follows it.
CE renders nothing about nodes without per-node-breakdown: no nodes line, By node card,
Node select, scope selector or Node columns, not even locked; the Overview reads "Server
stopped at" for a stopped server. The generator gains --media-nodes, which node rollups
now require.
2026-11-04 12:13:58 +01:00
|
|
|
|
_id: rollupId(`node:${MEDIA_NODE_ALPHA}`, hourBucket),
|
|
|
|
|
|
scope: `node:${MEDIA_NODE_ALPHA}`,
|
2026-10-25 16:35:47 +01:00
|
|
|
|
bucket: hourBucket,
|
|
|
|
|
|
...closedRoomHour
|
|
|
|
|
|
}
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
const dayBucket = new Date(`${DAY}T00:00:00Z`);
|
|
|
|
|
|
|
|
|
|
|
|
export const analyticsDaily: DailyRollupDocument[] = [
|
|
|
|
|
|
{
|
|
|
|
|
|
_id: rollupId('global', dayBucket),
|
|
|
|
|
|
scope: 'global',
|
|
|
|
|
|
bucket: dayBucket,
|
|
|
|
|
|
rooms: { created: 2, ended: 1, endedByReason: { API_DELETE: 1 }, durationMsSum: 2_700_000, participantsSum: 4 },
|
|
|
|
|
|
participants: {
|
|
|
|
|
|
joined: 6,
|
|
|
|
|
|
active: 4,
|
|
|
|
|
|
aborted: 1,
|
|
|
|
|
|
left: 3,
|
|
|
|
|
|
byKind: { STANDARD: 6, INGRESS: 2, EGRESS: 2, SIP: 1, AGENT: 1 },
|
|
|
|
|
|
bySdk: { JS: 5, ANDROID: 1 },
|
|
|
|
|
|
byOs: { windows: 1, macos: 3, linux: 1, android: 1 },
|
2026-11-03 11:10:23 +01:00
|
|
|
|
byBrowser: { chrome: 2, safari: 2, firefox: 1, none: 1 },
|
2026-10-25 16:35:47 +01:00
|
|
|
|
byConnectionType: { udp: 2, tcp: 1, turn: 1 },
|
|
|
|
|
|
disconnectReasons: {
|
|
|
|
|
|
CLIENT_INITIATED: 1,
|
|
|
|
|
|
DUPLICATE_IDENTITY: 1,
|
|
|
|
|
|
ROOM_DELETED: 1,
|
|
|
|
|
|
CONNECTION_TIMEOUT: 1
|
|
|
|
|
|
},
|
|
|
|
|
|
connectTimeSumMs: 4_600,
|
|
|
|
|
|
connectTimeCount: 4,
|
|
|
|
|
|
minutes: closedRoomMinutes
|
|
|
|
|
|
},
|
|
|
|
|
|
tracks: { published: 6, subscribed: 4, subscribeFailed: 1 },
|
|
|
|
|
|
egress: {
|
|
|
|
|
|
started: 3,
|
|
|
|
|
|
byType: { room_composite: 2, track: 1 },
|
|
|
|
|
|
endedByStatus: { complete: 1, failed: 1 },
|
|
|
|
|
|
durationMs: 2_340_000
|
|
|
|
|
|
},
|
|
|
|
|
|
ingress: { sessionsStarted: 2, sessionsEnded: 1, byInputType: { rtmp: 2 }, durationMs: 1_190_000 },
|
|
|
|
|
|
bytes: { up: 483_800_000, down: 987_120_000 },
|
|
|
|
|
|
quality: { scoreSum: 1753, scoreCount: 440, poor: 6 },
|
|
|
|
|
|
apiCalls: {
|
|
|
|
|
|
total: 9,
|
|
|
|
|
|
byMethod: {
|
|
|
|
|
|
RoomService_CreateRoom: 1,
|
|
|
|
|
|
RoomService_DeleteRoom: 1,
|
|
|
|
|
|
RoomService_ListParticipants: 2,
|
|
|
|
|
|
Egress_StartRoomCompositeEgress: 2,
|
|
|
|
|
|
Egress_StartTrackEgress: 1,
|
|
|
|
|
|
Ingress_CreateIngress: 2
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
apiErrors: { total: 1, byMethod: { RoomService_ListParticipants: 1 } },
|
|
|
|
|
|
uniqueIdentities: 5
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
openvidu-dashboard: media node labels, and nothing about nodes in CE
The contract gains mediaNode on nodes, rooms and participants, stoppedAt on nodes and the
partial mediaNode index that replaces the nodeId one; the scenario's two servers are
labelled. The status reports the writer without node counts and with stoppedAt when the
server stopped, the rooms list filters by mediaNode, list items carry it, the overview
groups by it for editions and a node scope's media statistics coverage follows it.
CE renders nothing about nodes without per-node-breakdown: no nodes line, By node card,
Node select, scope selector or Node columns, not even locked; the Overview reads "Server
stopped at" for a stopped server. The generator gains --media-nodes, which node rollups
now require.
2026-11-04 12:13:58 +01:00
|
|
|
|
_id: rollupId(`node:${MEDIA_NODE_ALPHA}`, dayBucket),
|
|
|
|
|
|
scope: `node:${MEDIA_NODE_ALPHA}`,
|
2026-10-25 16:35:47 +01:00
|
|
|
|
bucket: dayBucket,
|
|
|
|
|
|
...closedRoomHour,
|
|
|
|
|
|
uniqueIdentities: 3
|
|
|
|
|
|
}
|
|
|
|
|
|
];
|