diff --git a/.changeset/perfect-pianos-yawn.md b/.changeset/perfect-pianos-yawn.md new file mode 100644 index 000000000000..349bca33ecf7 --- /dev/null +++ b/.changeset/perfect-pianos-yawn.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/presence': minor +--- + +Add peak connections monitoring and methods to get and reset the counter diff --git a/.changeset/slow-coats-shout.md b/.changeset/slow-coats-shout.md new file mode 100644 index 000000000000..4a226e84d161 --- /dev/null +++ b/.changeset/slow-coats-shout.md @@ -0,0 +1,7 @@ +--- +"@rocket.chat/meteor": minor +--- + +Add the daily and monthly peaks of concurrent connections to statistics + - Added `dailyPeakConnections` statistic for monitoring the daily peak of concurrent connections in a workspace; + - Added `maxMonthlyPeakConnections` statistic for monitoring the last 30 days peak of concurrent connections in a workspace; diff --git a/apps/meteor/app/statistics/server/lib/statistics.ts b/apps/meteor/app/statistics/server/lib/statistics.ts index 64543deb88a1..1d60def846b5 100644 --- a/apps/meteor/app/statistics/server/lib/statistics.ts +++ b/apps/meteor/app/statistics/server/lib/statistics.ts @@ -1,7 +1,7 @@ import { log } from 'console'; import os from 'os'; -import { Analytics, Team, VideoConf } from '@rocket.chat/core-services'; +import { Analytics, Team, VideoConf, Presence } from '@rocket.chat/core-services'; import type { IRoom, IStats } from '@rocket.chat/core-typings'; import { UserStatus } from '@rocket.chat/core-typings'; import { @@ -579,6 +579,11 @@ export const statistics = { const defaultLoggedInCustomScript = (await Settings.findOneById('Custom_Script_Logged_In'))?.packageValue; statistics.loggedInCustomScriptChanged = settings.get('Custom_Script_Logged_In') !== defaultLoggedInCustomScript; + statistics.dailyPeakConnections = await Presence.getPeakConnections(true); + + const peak = await Statistics.findMonthlyPeakConnections(); + statistics.maxMonthlyPeakConnections = Math.max(statistics.dailyPeakConnections, peak?.dailyPeakConnections || 0); + statistics.matrixFederation = await getMatrixFederationStatistics(); // Omnichannel call stats diff --git a/apps/meteor/client/views/admin/info/DeploymentCard.stories.tsx b/apps/meteor/client/views/admin/info/DeploymentCard.stories.tsx index 41709d247f8b..d3242e68ae2c 100644 --- a/apps/meteor/client/views/admin/info/DeploymentCard.stories.tsx +++ b/apps/meteor/client/views/admin/info/DeploymentCard.stories.tsx @@ -272,6 +272,8 @@ export default { totalWebRTCCalls: 0, uncaughtExceptionsCount: 0, push: 0, + dailyPeakConnections: 0, + maxMonthlyPeakConnections: 0, matrixFederation: { enabled: false, }, diff --git a/apps/meteor/client/views/admin/info/InformationPage.stories.tsx b/apps/meteor/client/views/admin/info/InformationPage.stories.tsx index 29c0c00d5814..d05c0f7372a2 100644 --- a/apps/meteor/client/views/admin/info/InformationPage.stories.tsx +++ b/apps/meteor/client/views/admin/info/InformationPage.stories.tsx @@ -302,6 +302,8 @@ export default { totalWebRTCCalls: 0, uncaughtExceptionsCount: 0, push: 0, + dailyPeakConnections: 0, + maxMonthlyPeakConnections: 0, matrixFederation: { enabled: false, }, diff --git a/apps/meteor/client/views/admin/info/UsageCard.stories.tsx b/apps/meteor/client/views/admin/info/UsageCard.stories.tsx index a65e645b17d0..4c6cc871ede7 100644 --- a/apps/meteor/client/views/admin/info/UsageCard.stories.tsx +++ b/apps/meteor/client/views/admin/info/UsageCard.stories.tsx @@ -250,6 +250,8 @@ export default { totalWebRTCCalls: 0, uncaughtExceptionsCount: 0, push: 0, + dailyPeakConnections: 0, + maxMonthlyPeakConnections: 0, matrixFederation: { enabled: false, }, diff --git a/apps/meteor/server/cron/statistics.ts b/apps/meteor/server/cron/statistics.ts index 27c1fc064e25..7e7dea6adbc7 100644 --- a/apps/meteor/server/cron/statistics.ts +++ b/apps/meteor/server/cron/statistics.ts @@ -37,5 +37,7 @@ export async function statsCron(logger: Logger): Promise { const now = new Date(); - await cronJobs.add(name, `12 ${now.getHours()} * * *`, async () => generateStatistics(logger)); + await cronJobs.add(name, `12 ${now.getHours()} * * *`, async () => { + await generateStatistics(logger); + }); } diff --git a/apps/meteor/server/models/raw/Statistics.ts b/apps/meteor/server/models/raw/Statistics.ts index 1ad0ab993910..bad44ee07c23 100644 --- a/apps/meteor/server/models/raw/Statistics.ts +++ b/apps/meteor/server/models/raw/Statistics.ts @@ -25,4 +25,25 @@ export class StatisticsRaw extends BaseRaw implements IStatisticsModel { ).toArray(); return records?.[0]; } + + async findMonthlyPeakConnections() { + const oneMonthAgo = new Date(); + oneMonthAgo.setDate(oneMonthAgo.getDate() - 30); + oneMonthAgo.setHours(0, 0, 0, 0); + + return this.findOne>( + { + createdAt: { $gte: oneMonthAgo }, + }, + { + sort: { + dailyPeakConnections: -1, + }, + projection: { + dailyPeakConnections: 1, + createdAt: 1, + }, + }, + ); + } } diff --git a/ee/packages/presence/src/Presence.ts b/ee/packages/presence/src/Presence.ts index fb656fc3e158..5bd69e1f4fc8 100755 --- a/ee/packages/presence/src/Presence.ts +++ b/ee/packages/presence/src/Presence.ts @@ -19,6 +19,8 @@ export class Presence extends ServiceClass implements IPresence { private connsPerInstance = new Map(); + private peakConnections = 0; + constructor() { super(); @@ -35,6 +37,7 @@ export class Presence extends ServiceClass implements IPresence { if (diff?.hasOwnProperty('extraInformation.conns')) { this.connsPerInstance.set(id, diff['extraInformation.conns']); + this.peakConnections = Math.max(this.peakConnections, this.getTotalConnections()); this.validateAvailability(); } }); @@ -251,4 +254,16 @@ export class Presence extends ServiceClass implements IPresence { private getTotalConnections(): number { return Array.from(this.connsPerInstance.values()).reduce((acc, conns) => acc + conns, 0); } + + getPeakConnections(reset = false): number { + const peak = this.peakConnections; + if (reset) { + this.resetPeakConnections(); + } + return peak; + } + + resetPeakConnections(): void { + this.peakConnections = 0; + } } diff --git a/packages/core-services/src/types/IPresence.ts b/packages/core-services/src/types/IPresence.ts index 197f9b685cf8..5f7c57d67995 100644 --- a/packages/core-services/src/types/IPresence.ts +++ b/packages/core-services/src/types/IPresence.ts @@ -19,4 +19,6 @@ export interface IPresence extends IServiceClass { updateUserPresence(uid: string): Promise; toggleBroadcast(enabled: boolean): void; getConnectionCount(): { current: number; max: number }; + getPeakConnections(reset?: boolean): number; + resetPeakConnections(): void; } diff --git a/packages/core-typings/src/IStats.ts b/packages/core-typings/src/IStats.ts index 443cbfb23957..7fd5cd8218bc 100644 --- a/packages/core-typings/src/IStats.ts +++ b/packages/core-typings/src/IStats.ts @@ -219,6 +219,8 @@ export interface IStats { totalWebRTCCalls: number; uncaughtExceptionsCount: number; push: number; + dailyPeakConnections: number; + maxMonthlyPeakConnections: number; matrixFederation: { enabled: boolean; }; diff --git a/packages/model-typings/src/models/IStatisticsModel.ts b/packages/model-typings/src/models/IStatisticsModel.ts index ac84a49525b3..fe4534eaee0f 100644 --- a/packages/model-typings/src/models/IStatisticsModel.ts +++ b/packages/model-typings/src/models/IStatisticsModel.ts @@ -4,4 +4,5 @@ import type { IBaseModel } from './IBaseModel'; export interface IStatisticsModel extends IBaseModel { findLast(): Promise; + findMonthlyPeakConnections(): Promise | null>; }