Skip to content

Commit 41bfe8b

Browse files
feat: additional prom stats / relay protocol fix (#121)
* client type prom metrics / relay protocol fix * fix lint * fix user agent log
1 parent 356e675 commit 41bfe8b

21 files changed

+441
-333
lines changed

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
{
22
"name": "rtcstats-server",
3-
"version": "2.24.3",
3+
"version": "2.24.5",
44
"description": "The rtcstats-server represents the server side component of the rtcstats ecosystem, the client side being https://github.com/jitsi/rtcstats which collects and sends WebRTC related statistics.",
55
"main": "websocket.js",
66
"private": true,
77
"scripts": {
88
"lint:fix": "eslint --fix ./src/",
99
"lint": "eslint ./src/",
1010
"integration": "node ./src/test/client.js",
11-
"test": "jest",
11+
"test": "NODE_ENV=production jest",
1212
"test:fix": "jest ./src/test/jest/extract.test.js -- --fix",
1313
"start": "NODE_ENV=production node ./src/app.js",
1414
"watch:dev": "nodemon --exec 'NODE_ENV=debug node ./src/app.js'",

src/app.js

Lines changed: 31 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ const logger = require('./logging');
1818
const PromCollector = require('./metrics/PromCollector');
1919
const S3Manager = require('./store/S3Manager');
2020
const { saveEntryAssureUnique } = require('./store/dynamo');
21-
const { getStatsFormat } = require('./utils/stats-detection');
21+
const { ClientManager } = require('./utils/ClientManager');
2222
const { asyncDeleteFile,
2323
getEnvName,
2424
getIdealWorkerCount,
@@ -207,7 +207,6 @@ function setupFeaturesPublisher() {
207207
function setupWorkDirectory() {
208208
try {
209209
// Temporary path for stats dumps must be configured.
210-
tempPath = config.server.tempPath;
211210
assert(tempPath);
212211

213212
if (fs.existsSync(tempPath)) {
@@ -275,42 +274,37 @@ function wsConnectionHandler(client, upgradeReq) {
275274
try {
276275
PromCollector.connected.inc();
277276

278-
// the url the client is coming from
279-
const referer = upgradeReq.headers.origin + upgradeReq.url;
280-
const ua = upgradeReq.headers['user-agent'];
281-
282-
// During feature extraction we need information about the browser in order to decide which algorithms use.
283-
const connectionInfo = {
284-
path: upgradeReq.url,
285-
origin: upgradeReq.headers.origin,
286-
url: referer,
287-
userAgent: ua,
288-
clientProtocol: client.protocol
289-
};
277+
const clientManager = new ClientManager(client, upgradeReq);
278+
const clientDetails = clientManager.getDetails();
279+
const {
280+
userAgent,
281+
clientProtocol,
282+
url,
283+
statsFormat,
284+
clientType
285+
} = clientDetails;
290286

291287
logger.info(
292-
'[App] New app connected: ua: %s, protocol: %s, referer: %s',
293-
ua,
294-
client.protocol,
295-
referer
288+
'[App] New app connected: user-agent: %s, protocol: %s, url: %s',
289+
userAgent,
290+
clientProtocol,
291+
url
296292
);
297293

298294
// The if statement is used to maintain compatibility with the reconnect functionality on the client
299295
// it should be removed once the server also supports this functionality.
300296
// TODO: Remove once reconnect is added to server
301-
if (isSessionOngoing(referer, tempPath) || isSessionReconnect(referer)) {
302-
logger.warn(`[APP] Reconnect not supported, closing connection for ${referer}`);
297+
if (isSessionOngoing(url, tempPath) || isSessionReconnect(url)) {
298+
logger.warn(`[APP] Reconnect not supported, closing connection for ${url}`);
303299

304300
client.close(3001);
305301

306302
return;
307303
}
308304

309-
connectionInfo.statsFormat = getStatsFormat(connectionInfo);
310-
311305
const demuxSinkOptions = {
312-
connectionInfo,
313-
dumpFolder: './temp',
306+
clientDetails,
307+
dumpFolder: tempPath,
314308
log: logger
315309
};
316310

@@ -324,6 +318,7 @@ function wsConnectionHandler(client, upgradeReq) {
324318
const dumpData = {
325319
app: meta.applicationName || 'Undefined',
326320
clientId: id,
321+
clientType,
327322
conferenceId: meta.confName,
328323
conferenceUrl: meta.confID,
329324
dumpPath: meta.dumpPath,
@@ -335,30 +330,28 @@ function wsConnectionHandler(client, upgradeReq) {
335330
ampSessionId: meta.sessionId,
336331
ampUserId: meta.userId,
337332
ampDeviceId: meta.deviceId,
338-
statsFormat: connectionInfo.statsFormat,
333+
statsFormat,
339334
isBreakoutRoom: meta.isBreakoutRoom,
340335
breakoutRoomId: meta.roomId,
341336
parentStatsSessionId: meta.parentStatsSessionId,
342337
...tenantInfo
343338
};
344339

340+
PromCollector.collectClientDumpSizeMetrics(dumpData);
341+
345342
const obfuscatedDumpData = obfuscatePII(dumpData);
346343

347344
logger.info('[App] Processing dump id %s, metadata %o', id, obfuscatedDumpData);
348345

349346
// Don't process dumps generated by JVB & Jigasi, there should be a more formal process to
350-
if (config.features.disableFeatExtraction
351-
|| connectionInfo.clientProtocol?.includes('JVB')
352-
|| connectionInfo.clientProtocol?.includes('JIGASI')
353-
|| connectionInfo.clientProtocol?.includes('JICOFO')
354-
|| connectionInfo.userAgent?.includes('react-native')) {
355-
persistDumpData(dumpData);
356-
} else {
357-
// Add the clientId in the worker pool so it can process the associated dump file.
347+
if (clientManager.supportsFeatureExtraction()) {
348+
// Add the clientId in the worker pool so it can process the associated dump file.
358349
workerPool.addTask({
359350
type: RequestType.PROCESS,
360351
body: dumpData
361352
});
353+
} else {
354+
persistDumpData(dumpData);
362355
}
363356
});
364357

@@ -372,12 +365,12 @@ function wsConnectionHandler(client, upgradeReq) {
372365
// the whole pipeline does as well,
373366
PromCollector.sessionErrorCount.inc();
374367

375-
logger.error('[App] Connection pipeline: %o; error: %o', connectionInfo, err);
368+
logger.error('[App] Connection pipeline: %o; error: %o', clientDetails, err);
376369
}
377370
});
378371

379372
connectionPipeline.on('finish', () => {
380-
logger.info('[App] Connection pipeline successfully finished %o', connectionInfo);
373+
logger.info('[App] Connection pipeline successfully finished %o', clientDetails);
381374

382375
// We need to explicity close the ws, you might notice that we don't do the same in case of an error
383376
// that's because in that case the error will propagate up the pipeline chain and the ws stream will also
@@ -404,7 +397,7 @@ function wsConnectionHandler(client, upgradeReq) {
404397
PromCollector.connected.dec();
405398
});
406399
} catch (error) {
407-
logger.error('[App] Error while handling ws connection: %j', error);
400+
logger.error('[App] Error while handling ws connection: %o', error);
408401
}
409402
}
410403

@@ -526,6 +519,8 @@ function setupSecretManager() {
526519
async function startRtcstatsServer() {
527520
logger.info('[App] Initializing: %s; version: %s; env: %s ...', appName, appVersion, getEnvName());
528521

522+
tempPath = config.server.tempPath;
523+
529524
setupSecretManager();
530525
await setupWebhookSender();
531526
setupWorkDirectory();

src/demux.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,16 @@ class DemuxSink extends Writable {
2323
* C'tor
2424
*
2525
* @param {string} dumpFolder - Path to where sink files will be temporarily stored.
26-
* @param {Object} connectionInfo - Object containing information about the ws connection which stream the data.
26+
* @param {Object} clientDetails - Object containing information about the ws connection which stream the data.
2727
* @param {Object} log - Log object.
2828
* @param {boolean} persistDump - Flag used for generating a complete dump of the data coming to the stream.
2929
* Required when creating mock tests.
3030
*/
31-
constructor({ dumpFolder, connectionInfo, log, persistDump = false }) {
31+
constructor({ dumpFolder, clientDetails, log, persistDump = false }) {
3232
super({ objectMode: true });
3333

3434
this.dumpFolder = dumpFolder;
35-
this.connectionInfo = connectionInfo;
35+
this.clientDetails = clientDetails;
3636
this.log = log;
3737
this.timeoutId = -1;
3838
this.sinkMap = new Map();
@@ -179,7 +179,7 @@ class DemuxSink extends Writable {
179179
}
180180
}
181181

182-
this.log.info('[Demux] open-sink id: %s; path %s; connection: %o', id, filePath, this.connectionInfo);
182+
this.log.info('[Demux] open-sink id: %s; path %s; connection: %o', id, filePath, this.clientDetails);
183183

184184
const sink = fs.createWriteStream(idealPath, { fd });
185185

@@ -204,7 +204,7 @@ class DemuxSink extends Writable {
204204
// by visualizer tools for identifying the originating client (browser, jvb or other).
205205
this._sinkWrite(
206206
sink,
207-
JSON.stringify([ 'connectionInfo', null, JSON.stringify(this.connectionInfo), Date.now() ]));
207+
JSON.stringify([ 'connectionInfo', null, JSON.stringify(this.clientDetails), Date.now() ]));
208208

209209
return sinkData;
210210
}

src/metrics/PromCollector.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
// Initialize prometheus metrics.
2+
const fsPromises = require('fs').promises;
23
const getFolderSize = require('get-folder-size');
34
const prom = require('prom-client');
45

56
const logger = require('../logging');
7+
const { ClientType } = require('../utils/ClientManager');
68

79
const PromCollector = {
810
connected: new prom.Gauge({
@@ -169,6 +171,59 @@ const PromCollector = {
169171
help: 'number of open websocket connections that failed with an error'
170172
}),
171173

174+
rtcstatsDumpSizeBytes: new prom.Summary({
175+
name: 'rtcstats_dump_size_bytes',
176+
help: 'Size of processed dumps sent by rtcstats clients'
177+
}),
178+
jvbDumpSizeBytes: new prom.Summary({
179+
name: 'jvb_dump_size_bytes',
180+
help: 'Size of processed dumps sent by jvb clients'
181+
}),
182+
jicofoDumpSizeBytes: new prom.Summary({
183+
name: 'jicofo_dump_size_bytes',
184+
help: 'Size of processed dumps sent by jicofo clients'
185+
}),
186+
jibriDumpSizeBytes: new prom.Summary({
187+
name: 'jibri_dump_size_bytes',
188+
help: 'Size of processed dumps sent by jibri clients'
189+
}),
190+
jigasiDumpSizeBytes: new prom.Summary({
191+
name: 'jigasi_dump_size_bytes',
192+
help: 'Size of processed dumps sent by jigasi clients'
193+
}),
194+
unknownDumpSizeBytes: new prom.Summary({
195+
name: 'unknown_dump_size_bytes',
196+
help: 'Size of processed dumps sent by unknown clients'
197+
}),
198+
199+
collectClientDumpSizeMetrics: async dumpData => {
200+
const { dumpPath, clientType } = dumpData;
201+
202+
const dumpStats = await fsPromises.stat(dumpPath);
203+
const dumpSize = dumpStats.size;
204+
205+
switch (clientType) {
206+
case ClientType.RTCSTATS:
207+
PromCollector.rtcstatsDumpSizeBytes.observe(dumpSize);
208+
break;
209+
case ClientType.JVB:
210+
PromCollector.jvbDumpSizeBytes.observe(dumpSize);
211+
break;
212+
case ClientType.JICOFO:
213+
PromCollector.jicofoDumpSizeBytes.observe(dumpSize);
214+
break;
215+
case ClientType.JIBRI:
216+
PromCollector.jibriDumpSizeBytes.observe(dumpSize);
217+
break;
218+
case ClientType.JIGASI:
219+
PromCollector.jigasiDumpSizeBytes.observe(dumpSize);
220+
break;
221+
default:
222+
PromCollector.unknownDumpSizeBytes.observe(dumpSize);
223+
break;
224+
}
225+
},
226+
172227
metrics: () => prom.register.metrics(),
173228

174229
collectDefaultMetrics: () => prom.collectDefaultMetrics(),

src/test/client.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ const BrowserUASamples = Object.freeze({
2727
});
2828

2929
const ProtocolV = Object.freeze({
30-
LEGACY: '3_LEGACY',
31-
STANDARD: '3_STANDARD'
30+
LEGACY: '3.1_LEGACY',
31+
STANDARD: '3.1_STANDARD'
3232
});
3333

3434
/**

0 commit comments

Comments
 (0)