Skip to content

Commit

Permalink
Refactor WebRTC stats
Browse files Browse the repository at this point in the history
  • Loading branch information
sergystepanov committed Apr 1, 2024
1 parent f557d16 commit 2fb7e40
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 179 deletions.
53 changes: 49 additions & 4 deletions web/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import {
RECORDING_STATUS_CHANGED,
RECORDING_TOGGLED,
SETTINGS_CHANGED,
STATS_TOGGLE,
WEBRTC_CONNECTION_CLOSED,
WEBRTC_CONNECTION_READY,
WEBRTC_ICE_CANDIDATE_FOUND,
Expand All @@ -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';
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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);
},
}]
1 change: 0 additions & 1 deletion web/js/event.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
5 changes: 4 additions & 1 deletion web/js/network/webrtc.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
209 changes: 36 additions & 173 deletions web/js/stats.js
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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');

/**
Expand Down Expand Up @@ -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);

Expand All @@ -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}
}

/**
Expand Down Expand Up @@ -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;
Expand All @@ -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.
Expand All @@ -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,
}

0 comments on commit 2fb7e40

Please sign in to comment.