snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s

This commit is contained in:
upstream-mirror
2026-09-16 01:21:02 +05:00
commit 30810eecd7
10161 changed files with 987110 additions and 0 deletions

View File

@@ -0,0 +1,6 @@
import { metrics } from './lib/metrics';
import StatsTracker from './lib/statsTracker';
import './lib/collectMetrics';
export { metrics, StatsTracker };

View File

@@ -0,0 +1,213 @@
import http from 'node:http';
import { Statistics } from '@rocket.chat/models';
import { tracerSpan } from '@rocket.chat/tracing';
import connect from 'connect';
import { Facts } from 'meteor/facts-base';
import { Meteor } from 'meteor/meteor';
import client from 'prom-client';
import gcStats from 'prometheus-gc-stats';
import _ from 'underscore';
import { metrics } from './metrics';
import { Info } from '../../../../app/utils/rocketchat.info';
import { settings } from '../../../settings';
import { SystemLogger } from '../../logger/system';
import { getControl } from '../../migrations';
import { getAppsStatistics } from '../../statistics/lib/getAppsStatistics';
Facts.incrementServerFact = function (pkg: 'pkg' | 'fact', fact: string | number, increment: number): void {
metrics.meteorFacts.inc({ pkg, fact }, increment);
};
const setPrometheusData = async (): Promise<void> => {
metrics.info.set(
{
version: Info.version,
unique_id: settings.get<string>('uniqueID'),
site_url: settings.get<string>('Site_Url'),
},
1,
);
const sessions = Array.from<{ userId: string }>(Meteor.server.sessions.values());
const authenticatedSessions = sessions.filter((s) => s.userId);
metrics.ddpSessions.set(Meteor.server.sessions.size);
metrics.ddpAuthenticatedSessions.set(authenticatedSessions.length);
metrics.ddpConnectedUsers.set(_.unique(authenticatedSessions.map((s) => s.userId)).length);
// Apps metrics
const { totalInstalled, totalActive, totalFailed } = await getAppsStatistics();
metrics.totalAppsInstalled.set(totalInstalled || 0);
metrics.totalAppsEnabled.set(totalActive || 0);
metrics.totalAppsFailed.set(totalFailed || 0);
const statistics = await Statistics.findLast();
if (!statistics) {
return;
}
metrics.version.set({ version: statistics.version }, 1);
metrics.migration.set((await getControl()).version);
metrics.instanceCount.set(statistics.instanceCount);
// User statistics
metrics.totalUsers.set(statistics.totalUsers);
metrics.activeUsers.set(statistics.activeUsers);
metrics.nonActiveUsers.set(statistics.nonActiveUsers);
metrics.onlineUsers.set(statistics.onlineUsers);
metrics.awayUsers.set(statistics.awayUsers);
metrics.offlineUsers.set(statistics.offlineUsers);
// Room statistics
metrics.totalRooms.set(statistics.totalRooms);
metrics.totalChannels.set(statistics.totalChannels);
metrics.totalPrivateGroups.set(statistics.totalPrivateGroups);
metrics.totalDirect.set(statistics.totalDirect);
metrics.totalLivechat.set(statistics.totalLivechat);
// Message statistics
metrics.totalMessages.set(statistics.totalMessages);
metrics.totalChannelMessages.set(statistics.totalChannelMessages);
metrics.totalPrivateGroupMessages.set(statistics.totalPrivateGroupMessages);
metrics.totalDirectMessages.set(statistics.totalDirectMessages);
metrics.totalLivechatMessages.set(statistics.totalLivechatMessages);
// Livechat stats
metrics.totalLivechatVisitors.set(statistics.totalLivechatVisitors);
metrics.totalLivechatAgents.set(statistics.totalLivechatAgents);
metrics.pushQueue.set(statistics.pushQueue || 0);
};
const app = connect();
// const compression = require('compression');
// app.use(compression());
app.use('/metrics', (_req, res) => {
res.setHeader('Content-Type', 'text/plain');
client.register
.metrics()
.then((data) => {
metrics.metricsRequests.inc();
metrics.metricsRequestsTotal.inc();
metrics.metricsSize.set(data.length);
res.end(data);
})
.catch((err) => {
SystemLogger.error({ msg: 'Error while collecting metrics', err });
res.end();
});
});
app.use('/', (_req, res) => {
const html = `<html>
<head>
<title>Rocket.Chat Prometheus Exporter</title>
</head>
<body>
<h1>Rocket.Chat Prometheus Exporter</h1>
<p><a href="/metrics">Metrics</a></p>
</body>
</html>`;
res.write(html);
res.end();
});
const server = http.createServer(app);
let timer: NodeJS.Timeout;
let resetTimer: NodeJS.Timeout;
let defaultMetricsInitiated = false;
let gcStatsInitiated = false;
const was = {
enabled: false,
port: 9458,
resetInterval: 0,
collectGC: false,
};
const updatePrometheusConfig = async (): Promise<void> => {
const is = {
port: process.env.PROMETHEUS_PORT || settings.get('Prometheus_Port'),
enabled: settings.get<boolean>('Prometheus_Enabled'),
resetInterval: settings.get<number>('Prometheus_Reset_Interval'),
collectGC: settings.get<boolean>('Prometheus_Garbage_Collector'),
};
if (Object.values(is).some((s) => s == null)) {
return;
}
if (Object.entries(is).every(([k, v]) => v === was[k as keyof typeof was])) {
return;
}
if (!is.enabled) {
if (was.enabled) {
SystemLogger.info('Disabling Prometheus');
server.close();
clearInterval(timer);
}
Object.assign(was, is);
return;
}
SystemLogger.debug({ msg: 'Configuring Prometheus', is });
if (!was.enabled) {
server.listen({
port: is.port,
host: process.env.BIND_IP || '0.0.0.0',
});
timer = setInterval(async () => {
void tracerSpan(
'setPrometheusData',
{
attributes: {
port: is.port,
host: process.env.BIND_IP || '0.0.0.0',
},
},
() => {
void setPrometheusData();
},
);
}, 5000);
}
clearInterval(resetTimer);
if (is.resetInterval) {
resetTimer = setInterval(() => {
client.register.getMetricsAsArray().forEach((metric) => {
// @ts-expect-error Property 'hashMap' does not exist on type 'metric'.
metric.hashMap = {};
});
}, is.resetInterval);
}
// Prevent exceptions on calling those methods twice since
// it's not possible to stop them to be able to restart
try {
if (defaultMetricsInitiated === false) {
defaultMetricsInitiated = true;
client.collectDefaultMetrics();
}
if (is.collectGC && gcStatsInitiated === false) {
gcStatsInitiated = true;
gcStats(client.register)();
}
} catch (error) {
SystemLogger.error({ err: error });
}
Object.assign(was, is);
};
Meteor.startup(async () => {
settings.watchByRegex(/^Prometheus_.+/, updatePrometheusConfig);
});

View File

@@ -0,0 +1,340 @@
import { performance } from 'node:perf_hooks';
import client from 'prom-client';
const percentiles = [0.01, 0.1, 0.5, 0.9, 0.95, 0.99, 1];
const latencyBuckets = [0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1.0, 2.5, 5.0, 7.5, 10.0];
const queueWaitBuckets = [1, 5, 10, 30, 60, 120, 300, 600, 900, 1800, 3600];
export const metrics = {
deprecations: new client.Counter({
name: 'rocketchat_deprecations',
labelNames: ['type', 'kind', 'name', 'params'],
help: 'cumulated number of deprecations being used',
}),
deprecationsTotal: new client.Counter({
name: 'rocketchat_deprecations_total',
labelNames: ['type', 'kind', 'name', 'params'],
help: 'cumulated number of deprecations being used',
}),
metricsRequests: new client.Counter({
name: 'rocketchat_metrics_requests',
labelNames: ['notification_type'],
help: 'cumulated number of calls to the metrics endpoint',
}),
metricsRequestsTotal: new client.Counter({
name: 'rocketchat_metrics_requests_total',
labelNames: ['notification_type'],
help: 'cumulated number of calls to the metrics endpoint',
}),
metricsSize: new client.Gauge({
name: 'rocketchat_metrics_size',
help: 'size of the metrics response in chars',
}),
info: new client.Gauge({
name: 'rocketchat_info',
labelNames: ['version', 'unique_id', 'site_url'],
help: 'Rocket.Chat info',
}),
meteorMethods: new client.Summary({
name: 'rocketchat_meteor_methods',
help: 'summary of meteor methods count and time',
labelNames: ['method', 'has_connection', 'has_user'],
percentiles,
}),
meteorMethodsSeconds: new client.Histogram({
name: 'rocketchat_meteor_methods_seconds',
help: 'histogram of meteor methods count and time in seconds',
labelNames: ['method', 'has_connection', 'has_user'],
buckets: latencyBuckets,
}),
rocketchatCallbacks: new client.Summary({
name: 'rocketchat_callbacks',
help: 'summary of rocketchat callbacks count and time',
labelNames: ['hook', 'callback'],
percentiles,
}),
rocketchatCallbacksSeconds: new client.Histogram({
name: 'rocketchat_callbacks_seconds',
help: 'histogram of rocketchat callbacks count and time in seconds',
labelNames: ['hook', 'callback'],
buckets: latencyBuckets,
}),
rocketchatHooks: new client.Summary({
name: 'rocketchat_hooks',
help: 'summary of rocketchat hooks count and time',
labelNames: ['hook', 'callbacks_length'],
percentiles,
}),
rocketchatHooksSeconds: new client.Histogram({
name: 'rocketchat_hooks_seconds',
help: 'histogram of rocketchat hooks count and time in seconds',
labelNames: ['hook', 'callbacks_length'],
buckets: latencyBuckets,
}),
rocketchatRestApi: new client.Summary({
name: 'rocketchat_rest_api',
help: 'summary of rocketchat rest api count and time',
labelNames: ['method', 'entrypoint', 'user_agent', 'status', 'version'],
percentiles,
}),
rocketchatRestApiSeconds: new client.Histogram({
name: 'rocketchat_rest_api_seconds',
help: 'histogram of rocketchat rest api count and time in seconds',
labelNames: ['method', 'entrypoint', 'status', 'version'],
buckets: latencyBuckets,
}),
rocketchatRestApiResponseSizeBytes: new client.Histogram({
name: 'rocketchat_rest_api_response_size_bytes',
help: 'histogram of rocketchat rest api response sizes in bytes',
labelNames: ['method', 'entrypoint', 'status', 'version'],
buckets: [0, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000],
}),
rocketchatRestApiActiveRequests: new client.Gauge({
name: 'rocketchat_rest_api_active_requests',
help: 'number of currently active rest api requests',
labelNames: ['method'],
}),
meteorSubscriptions: new client.Summary({
name: 'rocketchat_meteor_subscriptions',
help: 'summary of meteor subscriptions count and time',
labelNames: ['subscription'],
percentiles,
}),
meteorSubscriptionsSeconds: new client.Histogram({
name: 'rocketchat_meteor_subscriptions_seconds',
help: 'histogram of meteor subscriptions count and time in seconds',
labelNames: ['subscription'],
buckets: latencyBuckets,
}),
messagesSent: new client.Counter({
name: 'rocketchat_message_sent',
help: 'cumulated number of messages sent',
}),
messagesSentTotal: new client.Counter({
name: 'rocketchat_messages_sent_total',
help: 'cumulated number of messages sent',
}),
notificationsSent: new client.Counter({
name: 'rocketchat_notification_sent',
labelNames: ['notification_type'],
help: 'cumulated number of notifications sent',
}),
notificationsSentTotal: new client.Counter({
name: 'rocketchat_notifications_sent_total',
labelNames: ['notification_type'],
help: 'cumulated number of notifications sent',
}),
messageRoundtripTime: new client.Summary({
name: 'rocketchat_messages_roundtrip_time_summary',
help: 'time spent by a message from save to receive back',
percentiles,
maxAgeSeconds: 60,
ageBuckets: 5,
// pruneAgedBuckets: true, // Type not added to prom-client on 14.2 https://github.com/siimon/prom-client/pull/558
}),
messageRoundtripTimeSeconds: new client.Histogram({
name: 'rocketchat_messages_roundtrip_time_seconds',
help: 'time in seconds spent by a message from save to receive back',
buckets: latencyBuckets,
}),
ddpSessions: new client.Gauge({
name: 'rocketchat_ddp_sessions_count',
help: 'number of open ddp sessions',
}),
ddpAuthenticatedSessions: new client.Gauge({
name: 'rocketchat_ddp_sessions_auth',
help: 'number of authenticated open ddp sessions',
}),
ddpConnectedUsers: new client.Gauge({
name: 'rocketchat_ddp_connected_users',
help: 'number of unique connected users',
}),
ddpRateLimitExceeded: new client.Counter({
name: 'rocketchat_ddp_rate_limit_exceeded',
labelNames: ['limit_name', 'user_id', 'client_address', 'type', 'name', 'connection_id'],
help: 'number of times a ddp rate limiter was exceeded',
}),
ddpRateLimitExceededTotal: new client.Counter({
name: 'rocketchat_ddp_rate_limit_exceeded_total',
labelNames: ['limit_name', 'user_id', 'client_address', 'type', 'name', 'connection_id'],
help: 'number of times a ddp rate limiter was exceeded',
}),
version: new client.Gauge({
name: 'rocketchat_version',
labelNames: ['version'],
help: 'Rocket.Chat version',
}),
migration: new client.Gauge({ name: 'rocketchat_migration', help: 'migration versoin' }),
instanceCount: new client.Gauge({
name: 'rocketchat_instance_count',
help: 'instances running',
}),
pushQueue: new client.Gauge({
name: 'rocketchat_push_queue',
labelNames: ['queue'],
help: 'push queue',
}),
// User statistics
totalUsers: new client.Gauge({ name: 'rocketchat_users_total', help: 'total of users' }),
activeUsers: new client.Gauge({
name: 'rocketchat_users_active',
help: 'total of active users',
}),
nonActiveUsers: new client.Gauge({
name: 'rocketchat_users_non_active',
help: 'total of non active users',
}),
onlineUsers: new client.Gauge({
name: 'rocketchat_users_online',
help: 'total of users online',
}),
awayUsers: new client.Gauge({
name: 'rocketchat_users_away',
help: 'total of users away',
}),
offlineUsers: new client.Gauge({
name: 'rocketchat_users_offline',
help: 'total of users offline',
}),
// Room statistics
totalRooms: new client.Gauge({ name: 'rocketchat_rooms_total', help: 'total of rooms' }),
totalChannels: new client.Gauge({
name: 'rocketchat_channels_total',
help: 'total of public rooms/channels',
}),
totalPrivateGroups: new client.Gauge({
name: 'rocketchat_private_groups_total',
help: 'total of private rooms',
}),
totalDirect: new client.Gauge({
name: 'rocketchat_direct_total',
help: 'total of direct rooms',
}),
totalLivechat: new client.Gauge({
name: 'rocketchat_livechat_total',
help: 'total of livechat rooms',
}),
// Message statistics
totalMessages: new client.Gauge({
name: 'rocketchat_messages_total',
help: 'total of messages',
}),
totalChannelMessages: new client.Gauge({
name: 'rocketchat_channel_messages_total',
help: 'total of messages in public rooms',
}),
totalPrivateGroupMessages: new client.Gauge({
name: 'rocketchat_private_group_messages_total',
help: 'total of messages in private rooms',
}),
totalDirectMessages: new client.Gauge({
name: 'rocketchat_direct_messages_total',
help: 'total of messages in direct rooms',
}),
totalLivechatMessages: new client.Gauge({
name: 'rocketchat_livechat_messages_total',
help: 'total of messages in livechat rooms',
}),
// Apps metrics
totalAppsInstalled: new client.Gauge({
name: 'rocketchat_apps_installed',
help: 'total apps installed',
}),
totalAppsEnabled: new client.Gauge({
name: 'rocketchat_apps_enabled',
help: 'total apps enabled',
}),
totalAppsFailed: new client.Gauge({
name: 'rocketchat_apps_failed',
help: 'total apps that failed to load',
}),
appBridgeMethods: new client.Summary({
name: 'rocketchat_apps_bridge_methods',
help: 'summary of app bridge method calls count and time',
labelNames: ['bridge', 'method', 'app_id'],
percentiles,
}),
appBridgeMethodsSeconds: new client.Histogram({
name: 'rocketchat_apps_bridge_methods_seconds',
help: 'histogram of app bridge method calls count and time in seconds',
labelNames: ['bridge', 'method', 'app_id'],
buckets: latencyBuckets,
}),
// Meteor Facts
meteorFacts: new client.Gauge({
name: 'rocketchat_meteor_facts',
labelNames: ['pkg', 'fact'],
help: 'internal meteor facts',
}),
// Livechat metrics
totalLivechatVisitors: new client.Gauge({ name: 'rocketchat_visitors_total', help: 'total of visitors' }),
totalLivechatAgents: new client.Gauge({ name: 'rocketchat_agents_total', help: 'total of agents' }),
totalLivechatWebhooksSuccess: new client.Counter({
name: 'rocketchat_livechat_webhooks_success',
help: 'successful livechat webhooks',
}),
totalLivechatWebhooksSuccessTotal: new client.Counter({
name: 'rocketchat_livechat_webhooks_success_total',
help: 'successful livechat webhooks',
}),
totalLivechatWebhooksFailures: new client.Counter({
name: 'rocketchat_livechat_webhooks_failures',
help: 'failed livechat webhooks',
}),
totalLivechatWebhooksFailuresTotal: new client.Counter({
name: 'rocketchat_livechat_webhooks_failures_total',
help: 'failed livechat webhooks',
}),
totalItemsProcessedByQueue: new client.Counter({
name: 'rocketchat_queue_items_processed_total',
labelNames: ['queue'],
help: 'Total number of items processed by Rocket.Chat Omni queues',
}),
totalItemsFailedByQueue: new client.Counter({
name: 'rocketchat_queue_items_failed_total',
labelNames: ['queue'],
help: 'Total number of items failed by Rocket.Chat Omni queues',
}),
totalItemsProcessedByReconciliationQueue: new client.Counter({
name: 'rocketchat_reconciliation_queue_items_processed_total',
labelNames: ['queue', 'action'],
help: 'Total number of items processed by the Omnichannel queue that were reconciled before processing',
}),
timeToQueueProcessingByQueue: new client.Summary({
name: 'rocketchat_queue_wait_time_seconds',
labelNames: ['queue'],
help: 'Time taken in seconds for an item to be processed for the first time by Omni queues',
percentiles,
}),
timeToQueueProcessingByQueueHistogram: new client.Histogram({
name: 'rocketchat_queue_wait_duration_seconds',
labelNames: ['queue'],
help: 'Histogram of time taken in seconds for an item to be processed for the first time by Omni queues',
buckets: queueWaitBuckets,
}),
};
// Metrics
let eluBase = performance.eventLoopUtilization();
new client.Gauge({
name: 'nodejs_event_loop_utilization_ratio',
help: 'Event Loop Utilization (ELU) as reported by NodeJS',
collect() {
this.set(performance.eventLoopUtilization(eluBase).utilization);
eluBase = performance.eventLoopUtilization();
},
});

View File

@@ -0,0 +1,47 @@
import { StatsD } from 'node-dogstatsd';
class StatsTracker {
constructor() {
this.StatsD = StatsD;
this.dogstatsd = new this.StatsD();
}
track(type, stats, ...args) {
this.dogstatsd[type](`RocketChat.${stats}`, ...args);
}
now() {
const hrtime = process.hrtime();
return hrtime[0] * 1000000 + hrtime[1] / 1000;
}
timing(stats, time, tags) {
this.track('timing', stats, time, tags);
}
increment(stats, time, tags) {
this.track('increment', stats, time, tags);
}
decrement(stats, time, tags) {
this.track('decrement', stats, time, tags);
}
histogram(stats, time, tags) {
this.track('histogram', stats, time, tags);
}
gauge(stats, time, tags) {
this.track('gauge', stats, time, tags);
}
unique(stats, time, tags) {
this.track('unique', stats, time, tags);
}
set(stats, time, tags) {
this.track('set', stats, time, tags);
}
}
export default new StatsTracker();