openvibers/openvibe.live Commits

Real-user measurements: time to a live channel's first picture (ttff, set by the player when live media first renders on a directly opened channel); beacons name the page they were loaded on, not where the SPA went since (#220)

OP OpenVibers committed 27 days ago · f872c62aa0 · 4 files, +18 −7

Changes

ddiff --git a/public/js/ov-rum.js b/public/js/ov-rum.js
iindex 3fc04e7b..6da485d2 100644
---- a/public/js/ov-rum.js
++++ b/public/js/ov-rum.js
@@@ -1,7 +1,7 @@
--/* Real-user measurements (server/web/rum.js): this page load's own timings and up to three script errors, sent once
-- * when the page is hidden. Loaded by app.js on one page load in four, after the page is idle; nothing identifies
-- * the visitor (no account, no id, no cookie). The observers read buffered entries, so the paints before this
-- * script loaded still count.
++/* Real-user measurements (server/web/rum.js): this page load's own timings, the time to a live channel's first
++ * picture (stream-player.js sets window.__ovFirstFrameAt) and up to three script errors, sent once when the page is
++ * hidden. Loaded by app.js on one page load in four, after the page is idle; nothing identifies the visitor (no
++ * account, no id, no cookie). The observers read buffered entries, so the paints before this script loaded count.
*/
(function () {
'use strict';
@@@ -31,8 +31,10 @@
sent = true;
const nav = performance.getEntriesByType && performance.getEntriesByType('navigation')[0];
const r = (v) => Math.round(v);
++ let path = location.pathname; // the page these timings are for: the one loaded, not where the SPA went since
++ try { if (nav) path = new URL(nav.name).pathname; } catch (e) { /* keep */ }
navigator.sendBeacon('/api/rum', JSON.stringify({
-- path: location.pathname, w: innerWidth, ttfb: nav ? r(nav.responseStart) : 0,
++ path, w: innerWidth, ttfb: nav ? r(nav.responseStart) : 0, ttff: r(window.__ovFirstFrameAt || 0),
fcp: r(m.fcp), lcp: r(m.lcp), cls: Math.round(m.cls * 1000) / 1000, inp: r(m.inp), tbt: r(m.tbt), errs: m.errs,
}));
};
ddiff --git a/public/js/stream-player.js b/public/js/stream-player.js
iindex 9679123f..84b0f07c 100644
---- a/public/js/stream-player.js
++++ b/public/js/stream-player.js
@@@ -593,6 +593,10 @@ function _hidePlayerPlaceholder(surface = (playerType === 'jsmpeg' ? 'canvas' :
playerLoadState.isSlow = false;
_pushPlayerDiagnostic('phase.live', 'Playback is rendering live media');
}
++ // Real-user measurements (js/ov-rum.js): when the first live picture showed, for a channel opened directly.
++ if (window.__ovFirstFrameAt == null) {
++ try { const nav = performance.getEntriesByType('navigation')[0]; if (nav && new URL(nav.name).pathname === location.pathname) window.__ovFirstFrameAt = Math.round(performance.now()); } catch { /* */ }
++ }
}
function _isVideoPlaybackLive(video) {
ddiff --git a/server/web/rum.js b/server/web/rum.js
iindex da3ff30f..5d2b0fd5 100644
---- a/server/web/rum.js
++++ b/server/web/rum.js
@@@ -4,7 +4,8 @@
* checks (scripts/perf, the browser smoke) run on one emulated phone; this is what people actually get.
*
* public/js/ov-rum.js runs on one page load in four, after the page is idle, and sends one beacon when the page is
-- * hidden: the path, the viewport width, and its own timings (TTFB, FCP, LCP, CLS, INP, blocking time) plus at most
++ * hidden: the path, the viewport width, and its own timings (TTFB, FCP, LCP, CLS, INP, blocking time, and the time to a
++ * live channel's first picture) plus at most
* three error messages. Nothing about the visitor is kept: no IP, no account, no user agent. Each beacon becomes
* numbers in a reservoir per page kind and device class, and errors are counted by message.
*
@@@ -16,7 +17,8 @@
*/
const express = require('express');
--const METRICS = { ttfb: 60000, fcp: 60000, lcp: 60000, inp: 60000, tbt: 120000, cls: 10 };
++// ttff: a live channel's first picture (stream-player.js), from navigation; 0 when no live picture showed.
++const METRICS = { ttfb: 60000, fcp: 60000, lcp: 60000, inp: 60000, tbt: 120000, cls: 10, ttff: 120000 };
const KEEP = 400; // samples per reservoir (the newest)
const MAX_ERRORS = 100; // distinct error messages kept
const PER_MINUTE = 20; // beacons per address per minute (held in memory for that minute only)
ddiff --git a/test/rum.test.js b/test/rum.test.js
iindex 58328b3a..824703ef 100644
---- a/test/rum.test.js
++++ b/test/rum.test.js
@@@ -51,6 +51,9 @@ const { createRum, pageKind, deviceClass } = require('../server/web/rum');
assert.strictEqual(home.cls.p75, 0.012);
assert.strictEqual(home.inp.n, 10, 'INP 0 is a real value (no slow interaction)');
assert.strictEqual(snap.pages['channel|desktop'].lcp.p50, 2500);
++ assert.strictEqual(snap.pages['channel|desktop'].ttff, undefined, 'no live picture: no ttff');
++ await beacon({ path: '/@goosely', w: 1440, ttff: 1800 }, '10.0.1.4');
++ assert.strictEqual(rum.snapshot().pages['channel|desktop'].ttff.p50, 1800, 'time to the first live picture');
assert.deepStrictEqual(snap.errors.map((e) => e.message), ['TypeError: x is undefined', 'b', 'c'], 'at most three errors per beacon');
// The gauge: p75 per metric, only for reservoirs with at least 5 samples.
const g = rum.p75Gauge();