mirror of https://github.com/calzoneman/sync.git
Deprecate stats table in favor of prometheus integration
This commit is contained in:
parent
c7bec6251e
commit
e780e7dadb
21
NEWS.md
21
NEWS.md
|
@ -1,3 +1,24 @@
|
|||
2017-07-17
|
||||
==========
|
||||
|
||||
The `stats` database table and associated ACP subpage have been removed in favor
|
||||
of integration with [Prometheus](https://prometheus.io/). You can enable
|
||||
Prometheus reporting by copying `conf/example/prometheus.toml` to
|
||||
`conf/prometheus.toml` and editing it to your liking. I recommend integrating
|
||||
Prometheus with [Grafana](https://grafana.com/) for dashboarding needs.
|
||||
|
||||
The particular metrics that were saved in the `stats` table are reported by the
|
||||
following Prometheus metrics:
|
||||
|
||||
* Channel count: `cytube_channels_num_active` gauge.
|
||||
* User count: `cytube_sockets_num_connected` gauge (labeled by socket.io
|
||||
transport).
|
||||
* CPU/Memory: default metrics emitted by the
|
||||
[`prom-client`](https://github.com/siimon/prom-client) module.
|
||||
|
||||
More Prometheus metrics will be added in the future to make CyTube easier to
|
||||
monitor :)
|
||||
|
||||
2017-07-15
|
||||
==========
|
||||
|
||||
|
|
|
@ -166,13 +166,6 @@ channel-save-interval: 5
|
|||
channel-storage:
|
||||
type: 'file'
|
||||
|
||||
# Configure statistics tracking
|
||||
stats:
|
||||
# Interval (in milliseconds) between data points - default 1h
|
||||
interval: 3600000
|
||||
# Maximum age of a datapoint (ms) before it is deleted - default 24h
|
||||
max-age: 86400000
|
||||
|
||||
# Configure periodic clearing of old alias data
|
||||
aliases:
|
||||
# Interval (in milliseconds) between subsequent runs of clearing
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"author": "Calvin Montgomery",
|
||||
"name": "CyTube",
|
||||
"description": "Online media synchronizer and chat",
|
||||
"version": "3.41.1",
|
||||
"version": "3.42.0",
|
||||
"repository": {
|
||||
"url": "http://github.com/calzoneman/sync"
|
||||
},
|
||||
|
|
|
@ -257,12 +257,6 @@ function handleForceUnload(user, data) {
|
|||
Logger.eventlog.log("[acp] " + eventUsername(user) + " forced unload of " + name);
|
||||
}
|
||||
|
||||
function handleListStats(user) {
|
||||
db.listStats(function (err, rows) {
|
||||
user.socket.emit("acp-list-stats", rows);
|
||||
});
|
||||
}
|
||||
|
||||
function init(user) {
|
||||
var s = user.socket;
|
||||
s.on("acp-announce", handleAnnounce.bind(this, user));
|
||||
|
@ -276,7 +270,6 @@ function init(user) {
|
|||
s.on("acp-delete-channel", handleDeleteChannel.bind(this, user));
|
||||
s.on("acp-list-activechannels", handleListActiveChannels.bind(this, user));
|
||||
s.on("acp-force-unload", handleForceUnload.bind(this, user));
|
||||
s.on("acp-list-stats", handleListStats.bind(this, user));
|
||||
|
||||
const globalBanDB = db.getGlobalBanDB();
|
||||
globalBanDB.listGlobalBans().then(bans => {
|
||||
|
|
|
@ -13,26 +13,6 @@ const LOGGER = require('@calzoneman/jsli')('bgtask');
|
|||
|
||||
var init = null;
|
||||
|
||||
/* Stats */
|
||||
function initStats(Server) {
|
||||
var STAT_INTERVAL = parseInt(Config.get("stats.interval"));
|
||||
var STAT_EXPIRE = parseInt(Config.get("stats.max-age"));
|
||||
|
||||
setInterval(function () {
|
||||
var chancount = Server.channels.length;
|
||||
var usercount = 0;
|
||||
Server.channels.forEach(function (chan) {
|
||||
usercount += chan.users.length;
|
||||
});
|
||||
|
||||
var mem = process.memoryUsage().rss;
|
||||
|
||||
db.addStatPoint(Date.now(), usercount, chancount, mem, function () {
|
||||
db.pruneStats(Date.now() - STAT_EXPIRE);
|
||||
});
|
||||
}, STAT_INTERVAL);
|
||||
}
|
||||
|
||||
/* Alias cleanup */
|
||||
function initAliasCleanup(Server) {
|
||||
var CLEAN_INTERVAL = parseInt(Config.get("aliases.purge-interval"));
|
||||
|
@ -91,7 +71,6 @@ module.exports = function (Server) {
|
|||
}
|
||||
|
||||
init = Server;
|
||||
initStats(Server);
|
||||
initAliasCleanup(Server);
|
||||
initChannelDumper(Server);
|
||||
initPasswordResetCleanup(Server);
|
||||
|
|
|
@ -82,10 +82,6 @@ var defaults = {
|
|||
"max-channels-per-user": 5,
|
||||
"max-accounts-per-ip": 5,
|
||||
"guest-login-delay": 60,
|
||||
stats: {
|
||||
interval: 3600000,
|
||||
"max-age": 86400000
|
||||
},
|
||||
aliases: {
|
||||
"purge-interval": 3600000,
|
||||
"max-age": 2592000000
|
||||
|
|
|
@ -360,37 +360,6 @@ module.exports.getIPs = function (name, callback) {
|
|||
|
||||
/* END REGION */
|
||||
|
||||
/* REGION stats */
|
||||
|
||||
module.exports.addStatPoint = function (time, ucount, ccount, mem, callback) {
|
||||
if (typeof callback !== "function") {
|
||||
callback = blackHole;
|
||||
}
|
||||
|
||||
var query = "INSERT INTO stats VALUES (?, ?, ?, ?)";
|
||||
module.exports.query(query, [time, ucount, ccount, mem], callback);
|
||||
};
|
||||
|
||||
module.exports.pruneStats = function (before, callback) {
|
||||
if (typeof callback !== "function") {
|
||||
callback = blackHole;
|
||||
}
|
||||
|
||||
var query = "DELETE FROM stats WHERE time < ?";
|
||||
module.exports.query(query, [before], callback);
|
||||
};
|
||||
|
||||
module.exports.listStats = function (callback) {
|
||||
if (typeof callback !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
var query = "SELECT * FROM stats ORDER BY time ASC";
|
||||
module.exports.query(query, callback);
|
||||
};
|
||||
|
||||
/* END REGION */
|
||||
|
||||
/* Misc */
|
||||
module.exports.loadAnnouncement = function () {
|
||||
var query = "SELECT * FROM `meta` WHERE `key`='announcement'";
|
||||
|
|
|
@ -65,15 +65,6 @@ const TBL_ALIASES = "" +
|
|||
"PRIMARY KEY (`visit_id`), INDEX (`ip`)" +
|
||||
")";
|
||||
|
||||
const TBL_STATS = "" +
|
||||
"CREATE TABLE IF NOT EXISTS `stats` (" +
|
||||
"`time` BIGINT NOT NULL," +
|
||||
"`usercount` INT NOT NULL," +
|
||||
"`chancount` INT NOT NULL," +
|
||||
"`mem` INT NOT NULL," +
|
||||
"PRIMARY KEY (`time`))" +
|
||||
"CHARACTER SET utf8";
|
||||
|
||||
const TBL_META = "" +
|
||||
"CREATE TABLE IF NOT EXISTS `meta` (" +
|
||||
"`key` VARCHAR(255) NOT NULL," +
|
||||
|
@ -132,7 +123,6 @@ module.exports.init = function (queryfn, cb) {
|
|||
password_reset: TBL_PASSWORD_RESET,
|
||||
user_playlists: TBL_USER_PLAYLISTS,
|
||||
aliases: TBL_ALIASES,
|
||||
stats: TBL_STATS,
|
||||
meta: TBL_META,
|
||||
channel_data: TBL_CHANNEL_DATA
|
||||
};
|
||||
|
|
|
@ -19,6 +19,7 @@ const verifySession = Promise.promisify(session.verifySession);
|
|||
const getAliases = Promise.promisify(db.getAliases);
|
||||
import { CachingGlobalBanlist } from './globalban';
|
||||
import proxyaddr from 'proxy-addr';
|
||||
import { Counter, Gauge } from 'prom-client';
|
||||
|
||||
const LOGGER = require('@calzoneman/jsli')('ioserver');
|
||||
|
||||
|
@ -187,6 +188,54 @@ function isIPGlobalBanned(ip) {
|
|||
return globalIPBanlist.isIPGlobalBanned(ip);
|
||||
}
|
||||
|
||||
const promSocketCount = new Gauge({
|
||||
name: 'cytube_sockets_num_connected',
|
||||
help: 'Gauge of connected socket.io clients',
|
||||
labelNames: ['transport']
|
||||
});
|
||||
const promSocketAccept = new Counter({
|
||||
name: 'cytube_sockets_accept_count',
|
||||
help: 'Counter for number of connections accepted. Excludes rejected connections.'
|
||||
});
|
||||
const promSocketDisconnect = new Counter({
|
||||
name: 'cytube_sockets_disconnect_count',
|
||||
help: 'Counter for number of connections disconnected.'
|
||||
});
|
||||
function emitMetrics(sock) {
|
||||
try {
|
||||
let transportName = sock.client.conn.transport.name;
|
||||
promSocketCount.inc({ transport: transportName });
|
||||
promSocketAccept.inc(1, new Date());
|
||||
|
||||
sock.client.conn.on('upgrade', newTransport => {
|
||||
try {
|
||||
// Sanity check
|
||||
if (newTransport !== transportName) {
|
||||
promSocketCount.dec({ transport: transportName });
|
||||
transportName = newTransport.name;
|
||||
promSocketCount.inc({ transport: transportName });
|
||||
}
|
||||
} catch (error) {
|
||||
LOGGER.error('Error emitting transport upgrade metrics for socket (ip=%s): %s',
|
||||
sock._realip, error.stack);
|
||||
}
|
||||
});
|
||||
|
||||
sock.on('disconnect', () => {
|
||||
try {
|
||||
promSocketCount.dec({ transport: transportName });
|
||||
promSocketDisconnect.inc(1, new Date());
|
||||
} catch (error) {
|
||||
LOGGER.error('Error emitting disconnect metrics for socket (ip=%s): %s',
|
||||
sock._realip, error.stack);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
LOGGER.error('Error emitting metrics for socket (ip=%s): %s',
|
||||
sock._realip, error.stack);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after a connection is accepted
|
||||
*/
|
||||
|
@ -227,6 +276,8 @@ function handleConnection(sock) {
|
|||
return;
|
||||
}
|
||||
|
||||
emitMetrics(sock);
|
||||
|
||||
LOGGER.info("Accepted socket from " + ip);
|
||||
counters.add("socket.io:accept", 1);
|
||||
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import http from 'http';
|
||||
import { register } from 'prom-client';
|
||||
import { register, collectDefaultMetrics } from 'prom-client';
|
||||
import { parse as parseURL } from 'url';
|
||||
|
||||
const LOGGER = require('@calzoneman/jsli')('prometheus-server');
|
||||
|
||||
let server = null;
|
||||
let defaultMetricsTimer = null;
|
||||
|
||||
export function init(prometheusConfig) {
|
||||
if (server !== null) {
|
||||
|
@ -13,6 +14,8 @@ export function init(prometheusConfig) {
|
|||
return;
|
||||
}
|
||||
|
||||
defaultMetricsTimer = collectDefaultMetrics();
|
||||
|
||||
server = http.createServer((req, res) => {
|
||||
if (req.method !== 'GET'
|
||||
|| parseURL(req.url).pathname !== prometheusConfig.getPath()) {
|
||||
|
@ -44,4 +47,6 @@ export function init(prometheusConfig) {
|
|||
export function shutdown() {
|
||||
server.close();
|
||||
server = null;
|
||||
clearInterval(defaultMetricsTimer);
|
||||
defaultMetricsTimer = null;
|
||||
}
|
||||
|
|
|
@ -51,6 +51,7 @@ import session from './session';
|
|||
import { LegacyModule } from './legacymodule';
|
||||
import { PartitionModule } from './partition/partitionmodule';
|
||||
import * as Switches from './switches';
|
||||
import { Gauge } from 'prom-client';
|
||||
|
||||
var Server = function () {
|
||||
var self = this;
|
||||
|
@ -226,6 +227,10 @@ Server.prototype.isChannelLoaded = function (name) {
|
|||
return false;
|
||||
};
|
||||
|
||||
const promActiveChannels = new Gauge({
|
||||
name: 'cytube_channels_num_active',
|
||||
help: 'Number of channels currently active'
|
||||
});
|
||||
Server.prototype.getChannel = function (name) {
|
||||
var cname = name.toLowerCase();
|
||||
if (this.partitionDecider &&
|
||||
|
@ -242,6 +247,7 @@ Server.prototype.getChannel = function (name) {
|
|||
}
|
||||
|
||||
var c = new Channel(name);
|
||||
promActiveChannels.inc();
|
||||
c.on("empty", function () {
|
||||
self.unloadChannel(c);
|
||||
});
|
||||
|
@ -310,6 +316,7 @@ Server.prototype.unloadChannel = function (chan, options) {
|
|||
}
|
||||
}
|
||||
chan.dead = true;
|
||||
promActiveChannels.dec();
|
||||
};
|
||||
|
||||
Server.prototype.packChannelList = function (publicOnly, isAdmin) {
|
||||
|
|
|
@ -11,7 +11,7 @@ import csrf from './csrf';
|
|||
import * as HTTPStatus from './httpstatus';
|
||||
import { CSRFError, HTTPError } from '../errors';
|
||||
import counters from '../counters';
|
||||
import { Summary } from 'prom-client';
|
||||
import { Summary, Counter } from 'prom-client';
|
||||
|
||||
const LOGGER = require('@calzoneman/jsli')('webserver');
|
||||
|
||||
|
@ -35,6 +35,11 @@ function initPrometheus(app) {
|
|||
+ 'until the "finish" event on the response object.',
|
||||
labelNames: ['method', 'statusCode']
|
||||
});
|
||||
const requests = new Counter({
|
||||
name: 'cytube_http_req_count',
|
||||
help: 'HTTP Request count',
|
||||
labelNames: ['method', 'statusCode']
|
||||
});
|
||||
|
||||
app.use((req, res, next) => {
|
||||
const startTime = process.hrtime();
|
||||
|
@ -43,6 +48,7 @@ function initPrometheus(app) {
|
|||
const diff = process.hrtime(startTime);
|
||||
const diffMs = diff[0]*1e3 + diff[1]*1e-6;
|
||||
latency.labels(req.method, res.statusCode).observe(diffMs);
|
||||
requests.labels(req.method, res.statusCode).inc(1, new Date());
|
||||
} catch (error) {
|
||||
LOGGER.error('Failed to record HTTP Prometheus metrics: %s', error.stack);
|
||||
}
|
||||
|
|
|
@ -34,7 +34,6 @@ addMenuItem("#acp-user-lookup", "Users");
|
|||
addMenuItem("#acp-channel-lookup", "Channels");
|
||||
addMenuItem("#acp-loaded-channels", "Active Channels");
|
||||
addMenuItem("#acp-eventlog", "Event Log");
|
||||
addMenuItem("#acp-stats", "Stats");
|
||||
|
||||
/* Log Viewer */
|
||||
function readSyslog() {
|
||||
|
@ -541,79 +540,6 @@ function filterEventLog() {
|
|||
$("#acp-eventlog-filter").change(filterEventLog);
|
||||
$("#acp-eventlog-refresh").click(readEventlog);
|
||||
|
||||
/* Stats */
|
||||
|
||||
$("a:contains('Stats')").click(function () {
|
||||
socket.emit("acp-list-stats");
|
||||
});
|
||||
|
||||
socket.on("acp-list-stats", function (rows) {
|
||||
var labels = [];
|
||||
var ucounts = [];
|
||||
var ccounts = [];
|
||||
var mcounts = [];
|
||||
var lastdate = "";
|
||||
rows.forEach(function (r) {
|
||||
var d = new Date(parseInt(r.time));
|
||||
var t = "";
|
||||
if (d.toDateString() !== lastdate) {
|
||||
lastdate = d.toDateString();
|
||||
t = d.getFullYear()+"-"+(d.getMonth()+1)+"-"+d.getDate();
|
||||
t += " " + d.toTimeString().split(" ")[0];
|
||||
} else {
|
||||
t = d.toTimeString().split(" ")[0];
|
||||
}
|
||||
|
||||
labels.push(t);
|
||||
ucounts.push(r.usercount);
|
||||
ccounts.push(r.chancount);
|
||||
mcounts.push(r.mem / 1048576);
|
||||
});
|
||||
|
||||
var userdata = {
|
||||
labels: labels,
|
||||
datasets: [
|
||||
{
|
||||
fillColor: "rgba(151, 187, 205, 0.5)",
|
||||
strokeColor: "rgba(151, 187, 205, 1)",
|
||||
pointColor: "rgba(151, 187, 205, 1)",
|
||||
pointStrokeColor: "#fff",
|
||||
data: ucounts
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var channeldata = {
|
||||
labels: labels,
|
||||
datasets: [
|
||||
{
|
||||
fillColor: "rgba(151, 187, 205, 0.5)",
|
||||
strokeColor: "rgba(151, 187, 205, 1)",
|
||||
pointColor: "rgba(151, 187, 205, 1)",
|
||||
pointStrokeColor: "#fff",
|
||||
data: ccounts
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var memdata = {
|
||||
labels: labels,
|
||||
datasets: [
|
||||
{
|
||||
fillColor: "rgba(151, 187, 205, 0.5)",
|
||||
strokeColor: "rgba(151, 187, 205, 1)",
|
||||
pointColor: "rgba(151, 187, 205, 1)",
|
||||
pointStrokeColor: "#fff",
|
||||
data: mcounts
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
new Chart($("#stat_users")[0].getContext("2d")).Line(userdata);
|
||||
new Chart($("#stat_channels")[0].getContext("2d")).Line(channeldata);
|
||||
new Chart($("#stat_mem")[0].getContext("2d")).Line(memdata);
|
||||
});
|
||||
|
||||
/* Initialize keyed table sorts */
|
||||
$("table").each(function () {
|
||||
var table = $(this);
|
||||
|
|
1426
www/js/chart.js
1426
www/js/chart.js
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue