From 2fb7e4010dad8c7584eba942c7f7274e6e38ac72 Mon Sep 17 00:00:00 2001 From: Sergey Stepanov Date: Tue, 2 Apr 2024 00:55:25 +0300 Subject: [PATCH] Refactor WebRTC stats --- web/js/app.js | 53 +++++++++- web/js/event.js | 1 - web/js/network/webrtc.js | 5 +- web/js/stats.js | 209 +++++++-------------------------------- 4 files changed, 89 insertions(+), 179 deletions(-) diff --git a/web/js/app.js b/web/js/app.js index 408125dca..e573677b9 100644 --- a/web/js/app.js +++ b/web/js/app.js @@ -29,7 +29,6 @@ import { RECORDING_STATUS_CHANGED, RECORDING_TOGGLED, SETTINGS_CHANGED, - STATS_TOGGLE, WEBRTC_CONNECTION_CLOSED, WEBRTC_CONNECTION_READY, WEBRTC_ICE_CANDIDATE_FOUND, @@ -38,9 +37,10 @@ import { WEBRTC_NEW_CONNECTION, WEBRTC_SDP_ANSWER, WEBRTC_SDP_OFFER, - WORKER_LIST_FETCHED + WORKER_LIST_FETCHED, } from 'event'; import {gui} from 'gui'; +import {env} from 'env'; import {keyboard, KEY, joystick, retropad, touch} from 'input'; import {socket, webrtc} from 'network'; import {debounce} from 'utils'; @@ -395,7 +395,7 @@ const app = { message.show('Saving the game.'); break; case KEY.STATS: - pub(STATS_TOGGLE); + stats.toggle(); break; case KEY.SETTINGS: break; @@ -449,7 +449,7 @@ const app = { window.location = window.location.pathname; break; case KEY.STATS: - pub(STATS_TOGGLE); + stats.toggle(); break; case KEY.DTOGGLE: handleToggle(); @@ -527,3 +527,48 @@ const wid = new URLSearchParams(document.location.search).get('wid'); // if from URL -> start game immediately! socket.init(roomId, wid, zone); api.transport = socket; + +// stats +const WEBRTC_STATS_FRAME = 'STATS_WEBRTC_FRAME_STATS'; +const WEBRTC_STATS_RTT = 'STATS_WEBRTC_ICE_RTT'; + +stats.modules = [ + { + mui: stats.mui('Ping', true, () => 'ms'), + enable() { + this.s = sub(WEBRTC_STATS_RTT, (v) => (this.val = v)); + }, + }, + { + mui: stats.mui(env.getBrowser() === 'firefox' ? 'FramerateMean' : 'FrameDelay', false, () => ''), + enable() { + this.s = sub(WEBRTC_STATS_FRAME, (v) => (this.val = v)); + } + }, + { + async stats() { + const stats = await webrtc.stats(); + if (!stats) return; + + let frameStatValue = '?'; + stats.forEach(report => { + if (report["framesReceived"] !== undefined && report["framesDecoded"] !== undefined && report["framesDropped"] !== undefined) { + frameStatValue = report["framesReceived"] - report["framesDecoded"] - report["framesDropped"]; + pub(WEBRTC_STATS_FRAME, frameStatValue) + } else if (report["framerateMean"] !== undefined) { + frameStatValue = Math.round(report["framerateMean"] * 100) / 100; + pub(WEBRTC_STATS_FRAME, frameStatValue) + } + + if (report["nominated"] && report["currentRoundTripTime"] !== undefined) { + pub(WEBRTC_STATS_RTT, report["currentRoundTripTime"] * 1000); + } + }); + }, + enable() { + this.interval = window.setInterval(this.stats, 999); + }, + disable() { + window.clearInterval(this.interval); + }, + }] diff --git a/web/js/event.js b/web/js/event.js index e2ae32a11..6a742af1c 100644 --- a/web/js/event.js +++ b/web/js/event.js @@ -87,7 +87,6 @@ export const AXIS_CHANGED = 'axisChanged'; export const CONTROLLER_UPDATED = 'controllerUpdated'; export const DPAD_TOGGLE = 'dpadToggle'; -export const STATS_TOGGLE = 'statsToggle'; export const HELP_OVERLAY_TOGGLED = 'helpOverlayToggled'; export const SETTINGS_CHANGED = 'settingsChanged'; diff --git a/web/js/network/webrtc.js b/web/js/network/webrtc.js index 5e8ae47d4..53e029882 100644 --- a/web/js/network/webrtc.js +++ b/web/js/network/webrtc.js @@ -168,7 +168,10 @@ export const webrtc = { input: (data) => dataChannel.send(data), isConnected: () => connected, isInputReady: () => inputReady, - getConnection: () => connection, + stats: async () => { + if (!connected) return Promise.resolve(); + return await connection.getStats() + }, stop, set onData(fn) { onData = fn diff --git a/web/js/stats.js b/web/js/stats.js index fdd2c4441..4a9ab413a 100644 --- a/web/js/stats.js +++ b/web/js/stats.js @@ -1,12 +1,7 @@ -import {env} from 'env'; import { - pub, sub, - STATS_TOGGLE, HELP_OVERLAY_TOGGLED } from 'event'; -import {log} from 'log'; -import {webrtc} from 'network'; const _modules = []; let tempHide = false; @@ -18,10 +13,6 @@ let active = false; // !to add connection drop notice -// internal events -const WEBRTC_STATS_FRAME = 'STATS_WEBRTC_FRAME_STATS'; -const WEBRTC_STATS_RTT = 'STATS_WEBRTC_ICE_RTT'; - const statsOverlayEl = document.getElementById('stats-overlay'); /** @@ -75,15 +66,12 @@ const graph = (parent, opts = { * Draws a bar graph on the canvas. */ const render = () => { - // 0,0 w,0 0,0 w,0 0,0 w,0 // +-------+ +-------+ +---------+ // | | |+---+ | |+---+ | // | | |||||| | ||||||+---+ // | | |||||| | ||||||||||| // +-------+ +----+--+ +---------+ - // 0,h w,h 0,h w,h 0,h w,h // [] [3] [3, 2] - // _context.clearRect(0, 0, _canvas.width, _canvas.height); @@ -109,7 +97,12 @@ const graph = (parent, opts = { _context.fillRect(x, y, w, h); } - return {add, get, max, render} + const clear = () => { + data = []; + render(); + } + + return {add, get, max, render, clear} } /** @@ -148,155 +141,33 @@ const moduleUi = (label = '', withGraph = false, postfix = () => 'ms') => { _value.textContent = `${value < 1 ? '<1' : value} ${_graph ? `(${_graph.max()}) ` : ''}${postfix_(value)}`; } - return {el: ui, update, withPostfix} -} - -/** - * User agent memory stats. - * - * ?Interface: - * HTMLElement get() - * void enable() - * void disable() - * void render() - * - * @version 1 - */ -const clientMemory = (() => { - let active = false; - - const measures = ['B', 'KB', 'MB', 'GB']; - const precision = 1; - let mLog = 0; - - const ui = moduleUi('Memory', false, (x) => (x > 0) ? measures[mLog] : ''); - - const get = () => ui.el; - - const enable = () => { - active = true; - render(); + const clear = () => { + _graph && _graph.clear(); } - const disable = () => active = false; - - const render = () => { - if (!active) return; - - const m = performance.memory.usedJSHeapSize; - let newValue = 'N/A'; - - if (m > 0) { - mLog = Math.floor(Math.log(m) / Math.log(1000)); - newValue = Math.round(m * precision / Math.pow(1000, mLog)) / precision; - } - - ui.update(newValue); - } - - if (window.performance && !performance.memory) performance.memory = {usedJSHeapSize: 0, totalJSHeapSize: 0}; - - return {get, enable, disable, render} -})(moduleUi, performance, window); - -const webRTCStats_ = (() => { - let interval = null - - function getStats() { - if (!webrtc.isConnected()) return; - - webrtc.getConnection().getStats().then(stats => { - let frameStatValue = '?'; - stats.forEach(report => { - if (report["framesReceived"] !== undefined && report["framesDecoded"] !== undefined && report["framesDropped"] !== undefined) { - frameStatValue = report["framesReceived"] - report["framesDecoded"] - report["framesDropped"]; - pub(WEBRTC_STATS_FRAME, frameStatValue) - } else if (report["framerateMean"] !== undefined) { - frameStatValue = Math.round(report["framerateMean"] * 100) / 100; - pub(WEBRTC_STATS_FRAME, frameStatValue) - } - - if (report["nominated"] && report["currentRoundTripTime"] !== undefined) { - pub(WEBRTC_STATS_RTT, report["currentRoundTripTime"] * 1000); - } - }); - }); - } - - const enable = () => { - interval = window.setInterval(getStats, 1000); - } - - const disable = () => window.clearInterval(interval); - - return {enable, disable, internal: true} -})(event, webrtc, window); - -/** - * User agent frame stats. - * - * ?Interface: - * HTMLElement get() - * void enable() - * void disable() - * void render() - * - * @version 1 - */ -const webRTCFrameStats = (() => { - let value = 0; - let listener; - - const label = env.getBrowser() === 'firefox' ? 'FramerateMean' : 'FrameDelay'; - const ui = moduleUi(label, false, () => ''); - - const get = () => ui.el; - - const enable = () => { - listener = sub('STATS_WEBRTC_FRAME_STATS', onStats); - } - - const disable = () => { - value = 0; - if (listener) listener.unsub(); - } - - const render = () => ui.update(value); - - function onStats(val) { - value = val; - } - - return {get, enable, disable, render} -})(env, event, moduleUi); - -const webRTCRttStats = (() => { - let value = 0; - let listener; - - const ui = moduleUi('Ping', true, () => 'ms'); - - const get = () => ui.el; - - const enable = () => { - listener = sub(WEBRTC_STATS_RTT, onStats); - } - - const disable = () => { - value = 0; - if (listener) listener.unsub(); - } - - const render = () => ui.update(value); - - function onStats(val) { - value = val; - } - - return {get, enable, disable, render} -})(event, moduleUi); + return {el: ui, update, withPostfix, clear} +} -const modules = (fn, force = true) => _modules.forEach(m => (force || !m.internal) && fn(m)) +const modules = (fn, force = true) => _modules.forEach(m => (force || m.get) && fn(m)) + +const module = (mod) => { + mod = { + val: 0, + ...mod, + _disable: function () { + mod.s && mod.s.unsub() + mod.val = 0; + mod.disable && mod.disable(); + mod.mui && mod.mui.clear(); + }, + ...(mod.mui && { + get: () => mod.mui.el, + render: () => mod.mui.update(mod.val) + }) + } + _modules.push(mod); + modules(m => m.get && statsOverlayEl.append(m.get()), false); +} const enable = () => { active = true; @@ -321,15 +192,13 @@ function draw(timestamp) { const disable = () => { active = false; - modules(m => m.disable()); + modules(m => m._disable()); _hide(); } const _show = () => statsOverlayEl.style.visibility = 'visible'; const _hide = () => statsOverlayEl.style.visibility = 'hidden'; -const onToggle = () => active ? disable() : enable(); - /** * Handles help overlay toggle event. * Workaround for a not normal app layout layering. @@ -354,21 +223,15 @@ const onHelpOverlayToggle = (overlay) => { const render = () => modules(m => m.render(), false); // add submodules -_modules.push( - webRTCRttStats, - clientMemory, - webRTCStats_, - webRTCFrameStats -); -modules(m => statsOverlayEl.append(m.get()), false); - -sub(STATS_TOGGLE, onToggle); sub(HELP_OVERLAY_TOGGLED, onHelpOverlayToggle) /** * App statistics module. */ export const stats = { - enable, - disable + toggle: () => active ? disable() : enable(), + set modules(m) { + m && m.forEach(mod => module(mod)) + }, + mui: moduleUi, }