Big graph, away!

This commit is contained in:
Cryptkeeper 2015-12-18 01:45:38 -06:00
parent b6a76a5ee7
commit 8f66475a28
7 changed files with 201 additions and 14 deletions

35
app.js

@ -10,6 +10,9 @@ var config = require('./config.json');
var networkHistory = []; var networkHistory = [];
var connectedClients = 0; var connectedClients = 0;
var graphData = [];
var lastGraphPush = [];
function pingAll() { function pingAll() {
var servers = config.servers; var servers = config.servers;
@ -79,6 +82,27 @@ function pingAll() {
if (config.logToDatabase) { if (config.logToDatabase) {
db.log(network.ip, util.getCurrentTimeMs(), res ? res.players.online : 0); db.log(network.ip, util.getCurrentTimeMs(), res ? res.players.online : 0);
} }
// Push it to our graphs.
var timeMs = util.getCurrentTimeMs();
if (!lastGraphPush[network.ip] || timeMs - lastGraphPush[network.ip] >= 60 * 1000) {
lastGraphPush[network.ip] = timeMs;
// Don't have too much data!
if (graphData[network.ip].length >= 24 * 60) {
graphData[network.ip].shift();
}
graphData[network.ip].push([timeMs, res ? res.players.online : 0]);
// Send the update.
server.io.sockets.emit('updateHistoryGraph', {
ip: network.ip,
players: (res ? res.players.online : 0),
timestamp: timeMs
});
}
}); });
})(servers[i]); })(servers[i]);
} }
@ -98,6 +122,14 @@ function startMainLoop() {
if (config.logToDatabase) { if (config.logToDatabase) {
// Setup our database. // Setup our database.
db.setup(); db.setup();
var timestamp = util.getCurrentTimeMs();
db.queryPings(24 * 60 * 60 * 1000, function(data) {
graphData = util.convertPingsToGraph(data);
logger.log('info', 'Queried and parsed ping history in %sms', util.getCurrentTimeMs() - timestamp);
});
} else { } else {
logger.warn('Database logging is not enabled. You can enable it by setting "logToDatabase" to true in config.json. This requires sqlite3 to be installed.'); logger.warn('Database logging is not enabled. You can enable it by setting "logToDatabase" to true in config.json. This requires sqlite3 to be installed.');
} }
@ -130,6 +162,9 @@ server.start(function() {
for (var i = 0; i < networkHistoryKeys.length; i++) { for (var i = 0; i < networkHistoryKeys.length; i++) {
client.emit('add', [networkHistory[networkHistoryKeys[i]]]); client.emit('add', [networkHistory[networkHistoryKeys[i]]]);
} }
// Send them the big 24h graph.
client.emit('historyGraph', graphData);
}, 1); }, 1);
// Attach our listeners. // Attach our listeners.

@ -177,3 +177,11 @@ h3 {
height: 42px; height: 42px;
width: 42px; width: 42px;
} }
/* The big graph */
#big-graph {
height: 500px;
width: 800px;
margin: 15px auto 0 auto;
padding-left: 500px;
}

@ -34,6 +34,8 @@
</div> </div>
<div id="big-graph"></div>
<div id="server-container" class="container"></div> <div id="server-container" class="container"></div>
<div id="quick-jump-container"></div> <div id="quick-jump-container"></div>

@ -30,6 +30,36 @@ var smallChartOptions = {
] ]
}; };
var bigChartOptions = {
series: {
shadowSize: 0
},
xaxis: {
font: {
color: "#E3E3E3"
},
show: false
},
yaxis: {
show: true,
tickLength: 10,
tickFormatter: function(value) {
return formatNumber(value);
},
font: {
color: "#E3E3E3"
},
labelWidth: -5
},
grid: {
hoverable: true,
color: "#696969"
},
legend: {
show: false
}
};
var lastMojangServiceUpdate; var lastMojangServiceUpdate;
var graphs = {}; var graphs = {};
@ -169,6 +199,38 @@ function safeName(name) {
return name.replace(/ /g, ''); return name.replace(/ /g, '');
} }
function handlePlotHover(event, pos, item) {
if (item) {
var text = getTimestamp(item.datapoint[0] / 1000) + '\
<br />\
' + formatNumber(item.datapoint[1]) + ' Players';
if (item.series && item.series.label) {
text = item.series.label + '<br />' + text;
}
renderTooltip(item.pageX + 5, item.pageY + 5, text);
} else {
hideTooltip();
}
}
function convertGraphData(rawData) {
var data = [];
var keys = Object.keys(rawData);
for (var i = 0; i < keys.length; i++) {
data.push({
data: rawData[keys[i]],
yaxis: 1,
label: keys[i]
});
}
return data;
}
$(document).ready(function() { $(document).ready(function() {
var socket = io.connect({ var socket = io.connect({
reconnect: true, reconnect: true,
@ -179,6 +241,9 @@ $(document).ready(function() {
var mojangServicesUpdater; var mojangServicesUpdater;
var sortServersTask; var sortServersTask;
var historyPlot;
var historyData;
socket.on('connect', function() { socket.on('connect', function() {
$('#tagline-text').text('Loading...'); $('#tagline-text').text('Loading...');
}); });
@ -203,6 +268,27 @@ $(document).ready(function() {
$('#quick-jump-container').html(''); $('#quick-jump-container').html('');
}); });
socket.on('historyGraph', function(rawData) {
historyData = rawData;
historyPlot = $.plot('#big-graph', convertGraphData(rawData), bigChartOptions);
$('#big-graph').bind('plothover', handlePlotHover);
});
socket.on('updateHistoryGraph', function(rawData) {
if (historyData[rawData.ip].length > 24 * 60) {
historyData[rawData.ip].shift();
}
historyData[rawData.ip].push([rawData.timestamp, rawData.players]);
historyPlot.setData(convertGraphData(historyData));
historyPlot.setupGrid();
historyPlot.draw();
});
socket.on('add', function(servers) { socket.on('add', function(servers) {
for (var i = 0; i < servers.length; i++) { for (var i = 0; i < servers.length; i++) {
var history = servers[i]; var history = servers[i];
@ -265,15 +351,7 @@ $(document).ready(function() {
updateServerStatus(lastEntry); updateServerStatus(lastEntry);
$('#chart_' + safeName(info.name)).bind('plothover', function(event, pos, item) { $('#chart_' + safeName(info.name)).bind('plothover', handlePlotHover);
if (item) {
renderTooltip(item.pageX + 5, item.pageY + 5, getTimestamp(item.datapoint[0] / 1000) + '\
<br />\
' + formatNumber(item.datapoint[1]) + ' Players');
} else {
hideTooltip();
}
});
} }
sortServers(); sortServers();
@ -323,7 +401,7 @@ $(document).ready(function() {
sortServersTask = setInterval(function() { sortServersTask = setInterval(function() {
sortServers(); sortServers();
}, 30 * 1000); }, 10 * 1000);
// Our super fancy scrolly thing! // Our super fancy scrolly thing!
$(document).on('click', '.quick-jump-icon', function(e) { $(document).on('click', '.quick-jump-icon', function(e) {

@ -190,8 +190,8 @@
"rates": { "rates": {
"upateMojangStatus": 5000, "upateMojangStatus": 5000,
"mojangStatusTimeout": 3500, "mojangStatusTimeout": 3500,
"pingAll": 3000, "pingAll": 2000,
"connectTimeout": 2500 "connectTimeout": 1500
}, },
"logToDatabase": false "logToDatabase": true
} }

@ -1,3 +1,5 @@
var util = require('./util');
exports.setup = function() { exports.setup = function() {
var sqlite = require('sqlite3'); var sqlite = require('sqlite3');
@ -10,6 +12,21 @@ exports.setup = function() {
exports.log = function(ip, timestamp, playerCount) { exports.log = function(ip, timestamp, playerCount) {
var insertStatement = db.prepare('INSERT INTO pings (timestamp, ip, playerCount) VALUES (?, ?, ?)'); var insertStatement = db.prepare('INSERT INTO pings (timestamp, ip, playerCount) VALUES (?, ?, ?)');
insertStatement.run(timestamp, ip, playerCount); db.serialize(function() {
insertStatement.run(timestamp, ip, playerCount);
});
insertStatement.finalize();
};
exports.queryPings = function(duration, callback) {
var currentTime = util.getCurrentTimeMs();
db.all("SELECT * FROM pings WHERE timestamp >= ? AND timestamp <= ?", [
currentTime - duration,
currentTime
], function(err, data) {
callback(data);
});
}; };
}; };

@ -1,3 +1,31 @@
// This method is a monstrosity.
// Since we loaded ALL pings from the database, we need to filter out the pings so each entry is a minute apart.
// This is done by iterating over the list, since the time between each ping can be completely arbitrary.
function trimUselessPings(data) {
var keys = Object.keys(data);
for (var i = 0; i < keys.length; i++) {
var listing = data[keys[i]];
var lastTimestamp = 0;
var filteredListing = [];
for (var x = 0; x < listing.length; x++) {
var entry = listing[x];
// 0 is the index of the timestamp.
// See the convertPingsToGraph method.
if (entry[0] - lastTimestamp >= 60 * 1000) {
filteredListing.push(entry);
lastTimestamp = entry[0];
}
}
data[keys[i]] = filteredListing;
}
}
exports.getCurrentTimeMs = function() { exports.getCurrentTimeMs = function() {
return new Date().getTime(); return new Date().getTime();
}; };
@ -9,3 +37,22 @@ exports.setIntervalNoDelay = function(func, delay) {
return task; return task;
}; };
exports.convertPingsToGraph = function(sqlData) {
var graphData = {};
for (var i = 0; i < sqlData.length; i++) {
var entry = sqlData[i];
if (!graphData[entry.ip]) {
graphData[entry.ip] = [];
}
graphData[entry.ip].push([entry.timestamp, entry.playerCount]);
}
// Break it into minutes.
trimUselessPings(graphData);
return graphData;
};