openvibers/openvibe.live Commits
Real-user measurements on every page load (not automated browsers): a quarter gave no page kind five samples in 45 minutes at this traffic; the privacy page says when (#227)
Changes
@@@ -9,7 +9,7 @@ says so.| Tool | What it does ||---|---|| `scripts/perf/measure-page.js <url> [--runs N] [--phone] [--phone-cpu]` | cold load in headless Chrome over the DevTools protocol, fresh profile and HTTP cache disabled per run; reports medians of transfer, requests, DOM nodes, running animations, FCP/LCP/CLS, long tasks, TBT, script/style/layout time, the LCP element (`lcpEl`) and the element behind the largest layout shift (`clsEl`) |--| real users (`public/js/ov-rum.js` → `server/web/rum.js`) | one page load in four reports its own TTFB/FCP/LCP/CLS/INP/blocking time and script errors; aggregates per page kind × device class at `GET /api/admin/rum` (staff) and `live_rum_p75` / `live_rum_errors` on `/metrics` |++| real users (`public/js/ov-rum.js` → `server/web/rum.js`) | every page load (not automated browsers) reports its own TTFB/FCP/LCP/CLS/INP/blocking time and script errors; aggregates per page kind × device class at `GET /api/admin/rum` (staff) and `live_rum_p75` / `live_rum_errors` on `/metrics` || `scripts/perf/ab.js <urlA> <urlB> --runs N` | the same, **interleaved** A,B,A,B… so machine load affects both sides equally || `scripts/perf/trace-page.js <url> [--tasks]` | Chrome trace; attributes style/layout work to the JavaScript that forced it; `--tasks` lists each main-thread task over 100 ms with what ran inside it || `test/browser/smoke.js` | routes × widths, errors, overflow, navigation, duplicate scripts, resource growth over repeated navigation |@@@ -2390,8 +2390,8 @@ document.addEventListener('DOMContentLoaded', async () => {if (!location.pathname.startsWith('/popout')) {routeFromURL();}-- // Real-user timings and errors on one page load in four, once idle (js/ov-rum.js, server/web/rum.js).-- if (Math.random() < 0.25 && window.ov) (window.requestIdleCallback || setTimeout)(() => ov.loadScript('/js/ov-rum.js'));++ // Real-user timings and errors, once idle (js/ov-rum.js, server/web/rum.js).++ if (window.ov) (window.requestIdleCallback || setTimeout)(() => ov.loadScript('/js/ov-rum.js'));});// Handle browser back/forward — wait for auth to be resolved first@@@ -1,6 +1,6 @@/* 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++ * hidden. Loaded by app.js on every page load, 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 () {@@@ -220,7 +220,7 @@<li class="yes"><span class="rule-icon"><i class="fa-solid fa-user"></i></span><span><strong>Account info.</strong> Your username, email, and profile details — handled through the OpenVibe single sign-on (openvibe.network). We use it to identify you and keep you logged in.</span></li><li class="yes"><span class="rule-icon"><i class="fa-solid fa-video"></i></span><span><strong>Content you create.</strong> Streams, VODs, clips, pastes, chat messages, themes, and anything else you post. You choose whether each is public, unlisted, or private.</span></li><li class="yes"><span class="rule-icon"><i class="fa-solid fa-server"></i></span><span><strong>Technical basics.</strong> IP address, browser/user-agent, and timestamps — used for security, abuse prevention, rate limiting, and rough view counts. We don't build advertising profiles.</span></li>-- <li class="yes"><span class="rule-icon"><i class="fa-solid fa-gauge-high"></i></span><span><strong>Page speed.</strong> On some page loads on openvibe.live, your browser reports how quickly the page loaded and the text of any script error, with the page's address and your screen width. Nothing that identifies you goes with it, and we keep only totals per kind of page.</span></li>++ <li class="yes"><span class="rule-icon"><i class="fa-solid fa-gauge-high"></i></span><span><strong>Page speed.</strong> When you open a page on openvibe.live, your browser reports how quickly the page loaded and the text of any script error, with the page's address and your screen width. Nothing that identifies you goes with it, and we keep only totals per kind of page.</span></li><li class="yes"><span class="rule-icon"><i class="fa-solid fa-coins"></i></span><span><strong>Payments.</strong> If you buy Vibes or tip, payment processors handle the card details — we never see or store your full card number.</span></li></ul></div>@@@ -3,7 +3,7 @@* Real-user measurements: how fast pages are on visitors' own devices, and the script errors they hit. Synthetic* 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++ * public/js/ov-rum.js runs on every page load (not in automated browsers), 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, 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@@@ -6,7 +6,7 @@* - page kinds and device classes; out-of-range or unmeasured values are left out; CLS keeps 3 decimals;* - percentiles in the staff snapshot and the p75 gauge (only once a reservoir has 5 samples);* - error messages are counted (at most 3 per beacon, at most 100 distinct); junk and floods are dropped;-- * - app.js loads the script on a quarter of page loads, after the page is idle.++ * - app.js loads the script on every page load, after the page is idle.** node test/rum.test.js*/@@@ -76,9 +76,10 @@ const { createRum, pageKind, deviceClass } = require('../server/web/rum');assert.ok(rum.snapshot().errors.length <= 25 && rum.snapshot().errors[0].message === 'TypeError: x is undefined');server.close();-- // app.js: a quarter of page loads, once idle, through the runtime's versioned loader.++ // app.js: every page load, once idle, through the runtime's versioned loader (a quarter of them gave too few++ // samples at this traffic; automated browsers are skipped in the script itself).const app_js = fs.readFileSync(path.join(__dirname, '../public/js/app.js'), 'utf8');-- assert.ok(app_js.includes("if (Math.random() < 0.25 && window.ov) (window.requestIdleCallback || setTimeout)(() => ov.loadScript('/js/ov-rum.js'));"));++ assert.ok(app_js.includes("if (window.ov) (window.requestIdleCallback || setTimeout)(() => ov.loadScript('/js/ov-rum.js'));"));const client = fs.readFileSync(path.join(__dirname, '../public/js/ov-rum.js'), 'utf8');assert.ok(client.includes("navigator.sendBeacon('/api/rum'") && !/cookie|localStorage/i.test(client.replace(/\/\*[\s\S]*?\*\//g, '')), 'the beacon carries no identifier');assert.ok(/navigator\.webdriver \|\| \/bot\|crawl\|spider[^/]*headless/.test(client), 'automated browsers (crawlers, headless runs) send nothing');
@@ -9,7 +9,7 @@ says so.
@@ -9,7 +9,7 @@ says so.
| Tool | What it does |
| Tool | What it does |
|---|---|
|---|---|
| `scripts/perf/measure-page.js <url> [--runs N] [--phone] [--phone-cpu]` | cold load in headless Chrome over the DevTools protocol, fresh profile and HTTP cache disabled per run; reports medians of transfer, requests, DOM nodes, running animations, FCP/LCP/CLS, long tasks, TBT, script/style/layout time, the LCP element (`lcpEl`) and the element behind the largest layout shift (`clsEl`) |
| `scripts/perf/measure-page.js <url> [--runs N] [--phone] [--phone-cpu]` | cold load in headless Chrome over the DevTools protocol, fresh profile and HTTP cache disabled per run; reports medians of transfer, requests, DOM nodes, running animations, FCP/LCP/CLS, long tasks, TBT, script/style/layout time, the LCP element (`lcpEl`) and the element behind the largest layout shift (`clsEl`) |
-| real users (`public/js/ov-rum.js` → `server/web/rum.js`) | one page load in four reports its own TTFB/FCP/LCP/CLS/INP/blocking time and script errors; aggregates per page kind × device class at `GET /api/admin/rum` (staff) and `live_rum_p75` / `live_rum_errors` on `/metrics` |
+| real users (`public/js/ov-rum.js` → `server/web/rum.js`) | every page load (not automated browsers) reports its own TTFB/FCP/LCP/CLS/INP/blocking time and script errors; aggregates per page kind × device class at `GET /api/admin/rum` (staff) and `live_rum_p75` / `live_rum_errors` on `/metrics` |
| `scripts/perf/ab.js <urlA> <urlB> --runs N` | the same, **interleaved** A,B,A,B… so machine load affects both sides equally |
| `scripts/perf/ab.js <urlA> <urlB> --runs N` | the same, **interleaved** A,B,A,B… so machine load affects both sides equally |
| `scripts/perf/trace-page.js <url> [--tasks]` | Chrome trace; attributes style/layout work to the JavaScript that forced it; `--tasks` lists each main-thread task over 100 ms with what ran inside it |
| `scripts/perf/trace-page.js <url> [--tasks]` | Chrome trace; attributes style/layout work to the JavaScript that forced it; `--tasks` lists each main-thread task over 100 ms with what ran inside it |
| `test/browser/smoke.js` | routes × widths, errors, overflow, navigation, duplicate scripts, resource growth over repeated navigation |
| `test/browser/smoke.js` | routes × widths, errors, overflow, navigation, duplicate scripts, resource growth over repeated navigation |
@@ -2390,8 +2390,8 @@ document.addEventListener('DOMContentLoaded', async () => {
@@ -2390,8 +2390,8 @@ document.addEventListener('DOMContentLoaded', async () => {
if (!location.pathname.startsWith('/popout')) {
if (!location.pathname.startsWith('/popout')) {
routeFromURL();
routeFromURL();
}
}
- // Real-user timings and errors on one page load in four, once idle (js/ov-rum.js, server/web/rum.js).
- if (Math.random() < 0.25 && window.ov) (window.requestIdleCallback || setTimeout)(() => ov.loadScript('/js/ov-rum.js'));
+ // Real-user timings and errors, once idle (js/ov-rum.js, server/web/rum.js).
+ if (window.ov) (window.requestIdleCallback || setTimeout)(() => ov.loadScript('/js/ov-rum.js'));
});
});
// Handle browser back/forward — wait for auth to be resolved first
// Handle browser back/forward — wait for auth to be resolved first
@@ -1,6 +1,6 @@
@@ -1,6 +1,6 @@
/* Real-user measurements (server/web/rum.js): this page load's own timings, the time to a live channel's first
/* 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
* 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
+ * hidden. Loaded by app.js on every page load, 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.
* account, no id, no cookie). The observers read buffered entries, so the paints before this script loaded count.
*/
*/
(function () {
(function () {
@@ -220,7 +220,7 @@
@@ -220,7 +220,7 @@
<li class="yes"><span class="rule-icon"><i class="fa-solid fa-user"></i></span><span><strong>Account info.</strong> Your username, email, and profile details — handled through the OpenVibe single sign-on (openvibe.network). We use it to identify you and keep you logged in.</span></li>
<li class="yes"><span class="rule-icon"><i class="fa-solid fa-user"></i></span><span><strong>Account info.</strong> Your username, email, and profile details — handled through the OpenVibe single sign-on (openvibe.network). We use it to identify you and keep you logged in.</span></li>
<li class="yes"><span class="rule-icon"><i class="fa-solid fa-video"></i></span><span><strong>Content you create.</strong> Streams, VODs, clips, pastes, chat messages, themes, and anything else you post. You choose whether each is public, unlisted, or private.</span></li>
<li class="yes"><span class="rule-icon"><i class="fa-solid fa-video"></i></span><span><strong>Content you create.</strong> Streams, VODs, clips, pastes, chat messages, themes, and anything else you post. You choose whether each is public, unlisted, or private.</span></li>
<li class="yes"><span class="rule-icon"><i class="fa-solid fa-server"></i></span><span><strong>Technical basics.</strong> IP address, browser/user-agent, and timestamps — used for security, abuse prevention, rate limiting, and rough view counts. We don't build advertising profiles.</span></li>
<li class="yes"><span class="rule-icon"><i class="fa-solid fa-server"></i></span><span><strong>Technical basics.</strong> IP address, browser/user-agent, and timestamps — used for security, abuse prevention, rate limiting, and rough view counts. We don't build advertising profiles.</span></li>
- <li class="yes"><span class="rule-icon"><i class="fa-solid fa-gauge-high"></i></span><span><strong>Page speed.</strong> On some page loads on openvibe.live, your browser reports how quickly the page loaded and the text of any script error, with the page's address and your screen width. Nothing that identifies you goes with it, and we keep only totals per kind of page.</span></li>
+ <li class="yes"><span class="rule-icon"><i class="fa-solid fa-gauge-high"></i></span><span><strong>Page speed.</strong> When you open a page on openvibe.live, your browser reports how quickly the page loaded and the text of any script error, with the page's address and your screen width. Nothing that identifies you goes with it, and we keep only totals per kind of page.</span></li>
<li class="yes"><span class="rule-icon"><i class="fa-solid fa-coins"></i></span><span><strong>Payments.</strong> If you buy Vibes or tip, payment processors handle the card details — we never see or store your full card number.</span></li>
<li class="yes"><span class="rule-icon"><i class="fa-solid fa-coins"></i></span><span><strong>Payments.</strong> If you buy Vibes or tip, payment processors handle the card details — we never see or store your full card number.</span></li>
</ul>
</ul>
</div>
</div>
@@ -3,7 +3,7 @@
@@ -3,7 +3,7 @@
* Real-user measurements: how fast pages are on visitors' own devices, and the script errors they hit. Synthetic
* Real-user measurements: how fast pages are on visitors' own devices, and the script errors they hit. Synthetic
* checks (scripts/perf, the browser smoke) run on one emulated phone; this is what people actually get.
* 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
+ * public/js/ov-rum.js runs on every page load (not in automated browsers), 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, and the time to a
* 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
* 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
* three error messages. Nothing about the visitor is kept: no IP, no account, no user agent. Each beacon becomes
@@ -6,7 +6,7 @@
@@ -6,7 +6,7 @@
* - page kinds and device classes; out-of-range or unmeasured values are left out; CLS keeps 3 decimals;
* - page kinds and device classes; out-of-range or unmeasured values are left out; CLS keeps 3 decimals;
* - percentiles in the staff snapshot and the p75 gauge (only once a reservoir has 5 samples);
* - percentiles in the staff snapshot and the p75 gauge (only once a reservoir has 5 samples);
* - error messages are counted (at most 3 per beacon, at most 100 distinct); junk and floods are dropped;
* - error messages are counted (at most 3 per beacon, at most 100 distinct); junk and floods are dropped;
- * - app.js loads the script on a quarter of page loads, after the page is idle.
+ * - app.js loads the script on every page load, after the page is idle.
*
*
* node test/rum.test.js
* node test/rum.test.js
*/
*/
@@ -76,9 +76,10 @@ const { createRum, pageKind, deviceClass } = require('../server/web/rum');
@@ -76,9 +76,10 @@ const { createRum, pageKind, deviceClass } = require('../server/web/rum');
assert.ok(rum.snapshot().errors.length <= 25 && rum.snapshot().errors[0].message === 'TypeError: x is undefined');
assert.ok(rum.snapshot().errors.length <= 25 && rum.snapshot().errors[0].message === 'TypeError: x is undefined');
server.close();
server.close();
- // app.js: a quarter of page loads, once idle, through the runtime's versioned loader.
+ // app.js: every page load, once idle, through the runtime's versioned loader (a quarter of them gave too few
+ // samples at this traffic; automated browsers are skipped in the script itself).
const app_js = fs.readFileSync(path.join(__dirname, '../public/js/app.js'), 'utf8');
const app_js = fs.readFileSync(path.join(__dirname, '../public/js/app.js'), 'utf8');
- assert.ok(app_js.includes("if (Math.random() < 0.25 && window.ov) (window.requestIdleCallback || setTimeout)(() => ov.loadScript('/js/ov-rum.js'));"));
+ assert.ok(app_js.includes("if (window.ov) (window.requestIdleCallback || setTimeout)(() => ov.loadScript('/js/ov-rum.js'));"));
const client = fs.readFileSync(path.join(__dirname, '../public/js/ov-rum.js'), 'utf8');
const client = fs.readFileSync(path.join(__dirname, '../public/js/ov-rum.js'), 'utf8');
assert.ok(client.includes("navigator.sendBeacon('/api/rum'") && !/cookie|localStorage/i.test(client.replace(/\/\*[\s\S]*?\*\//g, '')), 'the beacon carries no identifier');
assert.ok(client.includes("navigator.sendBeacon('/api/rum'") && !/cookie|localStorage/i.test(client.replace(/\/\*[\s\S]*?\*\//g, '')), 'the beacon carries no identifier');
assert.ok(/navigator\.webdriver \|\| \/bot\|crawl\|spider[^/]*headless/.test(client), 'automated browsers (crawlers, headless runs) send nothing');
assert.ok(/navigator\.webdriver \|\| \/bot\|crawl\|spider[^/]*headless/.test(client), 'automated browsers (crawlers, headless runs) send nothing');