diff --git a/bigbluebutton-html5/imports/startup/server/redis.js b/bigbluebutton-html5/imports/startup/server/redis.js index d18f7302cef386f4a88aa14679f3001991d83733..c28446c3193b2311e31fe502056c91c54315abfd 100755 --- a/bigbluebutton-html5/imports/startup/server/redis.js +++ b/bigbluebutton-html5/imports/startup/server/redis.js @@ -3,11 +3,20 @@ import Redis from 'redis'; import { Meteor } from 'meteor/meteor'; import { EventEmitter2 } from 'eventemitter2'; import { check } from 'meteor/check'; +import fs from 'fs'; import Logger from './logger'; // Fake meetingId used for messages that have no meetingId const NO_MEETING_ID = '_'; +const metrics = {}; + +const { + metricsDumpIntervalMs, + metricsFolderPath, + queueMetrics, +} = Meteor.settings.private.redis.metrics; + const makeEnvelope = (channel, eventName, header, body, routing) => { const envelope = { envelope: { @@ -34,6 +43,15 @@ class MeetingMessageQueue { this.queue = new PowerQueue(); this.redisDebugEnabled = redisDebugEnabled; + Meteor.setInterval(() => { + try { + fs.writeFileSync(`${metricsFolderPath}/${new Date().getTime()}-metrics.json`, JSON.stringify(metrics)); + Logger.info('Metric file successfully writen'); + } catch (err) { + Logger.error('Error on writing metrics to disk.', err); + } + }, metricsDumpIntervalMs); + this.handleTask = this.handleTask.bind(this); this.queue.taskHandler = this.handleTask; } @@ -76,6 +94,35 @@ class MeetingMessageQueue { Logger.debug(`Redis: ${JSON.stringify(data.parsedMessage.core)} emitted`); } + if (queueMetrics) { + const queueId = meetingId || NO_MEETING_ID; + + const dataLength = JSON.stringify(data).length; + if (!metrics[queueId].wasInQueue.hasOwnProperty(eventName)) { + metrics[queueId].wasInQueue[eventName] = { + count: 1, + payloadSize: { + min: dataLength, + max: dataLength, + last: dataLength, + total: dataLength, + avg: dataLength, + }, + } + } else { + metrics[queueId].currentlyInQueue[eventName].count -= 1; + + metrics[queueId].wasInQueue[eventName].count += 1; + + metrics[queueId].wasInQueue[eventName].payloadSize.last = dataLength; + metrics[queueId].wasInQueue[eventName].payloadSize.total += dataLength; + metrics[queueId].wasInQueue[eventName].payloadSize.min > dataLength ? metrics[queueId].wasInQueue[eventName].payloadSize.min = dataLength : null + metrics[queueId].wasInQueue[eventName].payloadSize.max < dataLength ? metrics[queueId].wasInQueue[eventName].payloadSize.max = dataLength : null + + metrics[queueId].wasInQueue[eventName].payloadSize.avg = metrics[queueId].wasInQueue[eventName].payloadSize.total / metrics[queueId].wasInQueue[eventName].count; + } + } + if (isAsync) { callNext(); } @@ -182,6 +229,25 @@ class RedisPubSub { const queueId = meetingId || NO_MEETING_ID; + if (queueMetrics) { + if (!metrics.hasOwnProperty(queueId)) { + metrics[queueId] = { + currentlyInQueue: {}, + wasInQueue: {}, + }; + } + + if (!metrics[queueId].currentlyInQueue.hasOwnProperty(eventName)) { + metrics[queueId].currentlyInQueue[eventName] = { + count: 1, + payloadSize: message.length, + }; + } else { + metrics[queueId].currentlyInQueue[eventName].count += 1; + metrics[queueId].currentlyInQueue[eventName].payloadSize += message.length; + } + } + if (!(queueId in this.mettingsQueues)) { this.mettingsQueues[meetingId] = new MeetingMessageQueue(this.emitter, async, this.redisDebugEnabled); } diff --git a/bigbluebutton-html5/private/config/settings.yml b/bigbluebutton-html5/private/config/settings.yml index 92b6aa661c4b4d4405250c9d08ae011695a84d05..a0245dc89f32d36ec36de1047a7a13c4c5b3e04e 100755 --- a/bigbluebutton-html5/private/config/settings.yml +++ b/bigbluebutton-html5/private/config/settings.yml @@ -442,6 +442,10 @@ private: timeout: 5000 password: null debug: false + metrics: + queueMetrics: true + metricsDumpIntervalMs: 30000 + metricsFolderPath: "/tmp/metrics" channels: toAkkaApps: to-akka-apps-redis-channel toThirdParty: to-third-party-redis-channel