Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add setting and expose prometheus on port 9100 #10766

Merged
merged 7 commits into from
May 15, 2018
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion packages/rocketchat-api/package.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ Package.onUse(function(api) {

//Add default routes
api.addFiles('server/default/info.js', 'server');
api.addFiles('server/default/metrics.js', 'server');

//Add v1 routes
api.addFiles('server/v1/channels.js', 'server');
Expand Down
12 changes: 11 additions & 1 deletion packages/rocketchat-api/server/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,10 @@ class API extends Restivus {
//Add a try/catch for each endpoint
const originalAction = endpoints[method].action;
endpoints[method].action = function _internalRouteActionHandler() {
const rocketchatRestApiEnd = RocketChat.metrics.rocketchatRestApi.startTimer({
method,
entrypoint: route
});
this.logger.debug(`${ this.request.method.toUpperCase() }: ${ this.request.url }`);
let result;
try {
Expand All @@ -155,7 +159,13 @@ class API extends Restivus {
return RocketChat.API.v1.failure(e.message, e.error);
}

return result ? result : RocketChat.API.v1.success();
result = result ? result : RocketChat.API.v1.success();

rocketchatRestApiEnd({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what happens if an error happens and the return is calling within catch without calling rocketchatRestApiEnd?

status: result.statusCode
});

return result;
};

for (const [name, helperMethod] of this.getHelperMethods()) {
Expand Down
8 changes: 0 additions & 8 deletions packages/rocketchat-api/server/default/metrics.js

This file was deleted.

5 changes: 5 additions & 0 deletions packages/rocketchat-lib/lib/callbacks.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ RocketChat.callbacks.run = function(hook, item, constant) {
const result = _.sortBy(callbacks, function(callback) {
return callback.priority || RocketChat.callbacks.priority.MEDIUM;
}).reduce(function(result, callback) {
let rocketchatCallbacksEnd;
if (Meteor.isServer) {
rocketchatCallbacksEnd = RocketChat.metrics.rocketchatCallbacks.startTimer({hook, callback: callback.id});
}
let time = 0;
if (RocketChat.callbacks.showTime === true || RocketChat.callbacks.showTotalTime === true) {
time = Date.now();
Expand All @@ -90,6 +94,7 @@ RocketChat.callbacks.run = function(hook, item, constant) {
totalTime += currentTime;
if (RocketChat.callbacks.showTime === true) {
if (Meteor.isServer) {
rocketchatCallbacksEnd();
RocketChat.statsTracker.timing('callbacks.time', currentTime, [`hook:${ hook }`, `callback:${ callback.id }`]);
} else {
let stack = callback.stack && typeof callback.stack.split === 'function' && callback.stack.split('\n');
Expand Down
6 changes: 5 additions & 1 deletion packages/rocketchat-lib/server/lib/debug.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,13 @@ const logger = new Logger('Meteor', {

const wrapMethods = function(name, originalHandler, methodsMap) {
methodsMap[name] = function() {
const end = RocketChat.metrics.meteorMethods.startTimer({method: name});
const args = name === 'ufsWrite' ? Array.prototype.slice.call(arguments, 1) : arguments;
logger.method(name, '-> userId:', Meteor.userId(), ', arguments: ', args);

return originalHandler.apply(this, arguments);
const result = originalHandler.apply(this, arguments);
end();
return result;
};
};

Expand All @@ -35,6 +38,7 @@ const originalMeteorPublish = Meteor.publish;
Meteor.publish = function(name, func) {
return originalMeteorPublish(name, function() {
logger.publish(name, '-> userId:', this.userId, ', arguments: ', arguments);
RocketChat.metrics.meteorSubscriptions.inc({subscription: name}, 1, new Date());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this does not give pub/sub response time as you marked my check 😛

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, the response time would be the initial response time?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's the initial time until ready() being called.


return func.apply(this, arguments);
});
Expand Down
136 changes: 136 additions & 0 deletions packages/rocketchat-lib/server/lib/metrics.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,145 @@
import client from 'prom-client';
import connect from 'connect';
import http from 'http';
import _ from 'underscore';

RocketChat.promclient = client;
client.collectDefaultMetrics();

RocketChat.metrics = {};

// one sample metrics only - a counter

RocketChat.metrics.meteorMethods = new client.Summary({
name: 'meteor_methods',
help: 'summary of meteor methods count and time',
labelNames: ['method']
});
RocketChat.metrics.meteorMethods.observe(10);

RocketChat.metrics.rocketchatCallbacks = new client.Summary({
name: 'rocketchat_callbacks',
help: 'summary of rocketchat callbacks count and time',
labelNames: ['hook', 'callback']
});
RocketChat.metrics.meteorMethods.observe(10);

RocketChat.metrics.rocketchatRestApi = new client.Summary({
name: 'rocketchat_rest_api',
help: 'summary of rocketchat rest api count and time',
labelNames: ['method', 'entrypoint', 'status']
});
RocketChat.metrics.meteorMethods.observe(10);

RocketChat.metrics.meteorSubscriptions = new client.Counter({
name: 'meteor_subscriptions',
help: 'summary of meteor subscriptions count and time',
labelNames: ['subscription']
});

RocketChat.metrics.messagesSent = new client.Counter({'name': 'message_sent', 'help': 'cumulated number of messages sent'});
RocketChat.metrics.ddpSessions = new client.Gauge({'name': 'ddp_sessions_count', 'help': 'number of open ddp sessions'});
RocketChat.metrics.ddpConnectedUsers = new client.Gauge({'name': 'ddp_connected_users', 'help': 'number of connected users'});

RocketChat.metrics.version = new client.Gauge({'name': 'version', labelNames: ['version'], 'help': 'Rocket.Chat version'});
RocketChat.metrics.migration = new client.Gauge({'name': 'migration', 'help': 'migration versoin'});
RocketChat.metrics.instanceCount = new client.Gauge({'name': 'instance_count', 'help': 'instances running'});
RocketChat.metrics.oplogEnabled = new client.Gauge({'name': 'oplog_enabled', labelNames: ['enabled'], 'help': 'oplog enabled'});

// User statistics
RocketChat.metrics.totalUsers = new client.Gauge({'name': 'users_total', 'help': 'total of users'});
RocketChat.metrics.activeUsers = new client.Gauge({'name': 'users_active', 'help': 'total of active users'});
RocketChat.metrics.nonActiveUsers = new client.Gauge({'name': 'users_non_active', 'help': 'total of non active users'});
RocketChat.metrics.onlineUsers = new client.Gauge({'name': 'users_online', 'help': 'total of users online'});
RocketChat.metrics.awayUsers = new client.Gauge({'name': 'users_away', 'help': 'total of users away'});
RocketChat.metrics.offlineUsers = new client.Gauge({'name': 'users_offline', 'help': 'total of users offline'});

// Room statistics
RocketChat.metrics.totalRooms = new client.Gauge({'name': 'rooms_total', 'help': 'total of rooms'});
RocketChat.metrics.totalChannels = new client.Gauge({'name': 'channels_total', 'help': 'total of public rooms/channels'});
RocketChat.metrics.totalPrivateGroups = new client.Gauge({'name': 'private_groups_total', 'help': 'total of private rooms'});
RocketChat.metrics.totalDirect = new client.Gauge({'name': 'direct_total', 'help': 'total of direct rooms'});
RocketChat.metrics.totalLivechat = new client.Gauge({'name': 'livechat_total', 'help': 'total of livechat rooms'});

// Message statistics
RocketChat.metrics.totalMessages = new client.Gauge({'name': 'messages_total', 'help': 'total of messages'});
RocketChat.metrics.totalChannelMessages = new client.Gauge({'name': 'channel_messages_total', 'help': 'total of messages in public rooms'});
RocketChat.metrics.totalPrivateGroupMessages = new client.Gauge({'name': 'private_group_messages_total', 'help': 'total of messages in private rooms'});
RocketChat.metrics.totalDirectMessages = new client.Gauge({'name': 'direct_messages_total', 'help': 'total of messages in direct rooms'});
RocketChat.metrics.totalLivechatMessages = new client.Gauge({'name': 'livechat_messages_total', 'help': 'total of messages in livechat rooms'});

client.register.setDefaultLabels({
uniqueId: RocketChat.settings.get('uniqueID'),
siteUrl: RocketChat.settings.get('Site_Url')
});

const setPrometheusData = () => {
const date = new Date();

client.register.setDefaultLabels({
unique_id: RocketChat.settings.get('uniqueID'),
site_url: RocketChat.settings.get('Site_Url'),
version: RocketChat.Info.version
});

RocketChat.metrics.ddpSessions.set(Object.keys(Meteor.server.sessions).length, date);
RocketChat.metrics.ddpConnectedUsers.set(_.compact(_.unique(Object.values(Meteor.server.sessions).map(s => s.userId))).length, date);

const statistics = RocketChat.models.Statistics.findLast();
if (!statistics) {
return;
}

RocketChat.metrics.version.set({version: statistics.version}, 1, date);
RocketChat.metrics.migration.set(RocketChat.Migrations._getControl().version, date);
RocketChat.metrics.instanceCount.set(statistics.instanceCount, date);
RocketChat.metrics.oplogEnabled.set({enabled: statistics.oplogEnabled}, 1, date);

// User statistics
RocketChat.metrics.totalUsers.set(statistics.totalUsers, date);
RocketChat.metrics.activeUsers.set(statistics.activeUsers, date);
RocketChat.metrics.nonActiveUsers.set(statistics.nonActiveUsers, date);
RocketChat.metrics.onlineUsers.set(statistics.onlineUsers, date);
RocketChat.metrics.awayUsers.set(statistics.awayUsers, date);
RocketChat.metrics.offlineUsers.set(statistics.offlineUsers, date);

// Room statistics
RocketChat.metrics.totalRooms.set(statistics.totalRooms, date);
RocketChat.metrics.totalChannels.set(statistics.totalChannels, date);
RocketChat.metrics.totalPrivateGroups.set(statistics.totalPrivateGroups, date);
RocketChat.metrics.totalDirect.set(statistics.totalDirect, date);
RocketChat.metrics.totalLivechat.set(statistics.totlalLivechat, date);

// Message statistics
RocketChat.metrics.totalMessages.set(statistics.totalMessages, date);
RocketChat.metrics.totalChannelMessages.set(statistics.totalChannelMessages, date);
RocketChat.metrics.totalPrivateGroupMessages.set(statistics.totalPrivateGroupMessages, date);
RocketChat.metrics.totalDirectMessages.set(statistics.totalDirectMessages, date);
RocketChat.metrics.totalLivechatMessages.set(statistics.totalLivechatMessages, date);
};

const app = connect();

// const compression = require('compression');
// app.use(compression());

app.use('/metrics', (req, res) => {
res.setHeader('Content-Type', 'text/plain');
res.end(RocketChat.promclient.register.metrics());
});

const server = http.createServer(app);

let timer;
RocketChat.settings.get('Prometheus_Enabled', (key, value) => {
if (value === true) {
server.listen({
port: 9100,
host: process.env.BIND_IP || '0.0.0.0'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we re-use instance ip? I don't think we are using bind ip anywhere

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@geekgonecrazy that is a copy from internal meteor code

});
timer = Meteor.setInterval(setPrometheusData, 5000);
} else {
server.close();
Meteor.clearInterval(timer);
}
});
9 changes: 8 additions & 1 deletion packages/rocketchat-lib/server/startup/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -1632,9 +1632,16 @@ RocketChat.settings.addGroup('Logs', function() {
type: 'boolean',
'public': true
});
return this.add('Log_View_Limit', 1000, {
this.add('Log_View_Limit', 1000, {
type: 'int'
});

this.section('Prometheus', function() {
this.add('Prometheus_Enabled', false, {
type: 'boolean',
i18nLabel: 'Enabled'
});
});
});

RocketChat.settings.addGroup('Setup_Wizard', function() {
Expand Down