Big graph, away!
This commit is contained in:
parent
b6a76a5ee7
commit
8f66475a28
35
app.js
35
app.js
@ -10,6 +10,9 @@ var config = require('./config.json');
|
||||
var networkHistory = [];
|
||||
var connectedClients = 0;
|
||||
|
||||
var graphData = [];
|
||||
var lastGraphPush = [];
|
||||
|
||||
function pingAll() {
|
||||
var servers = config.servers;
|
||||
|
||||
@ -79,6 +82,27 @@ function pingAll() {
|
||||
if (config.logToDatabase) {
|
||||
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]);
|
||||
}
|
||||
@ -98,6 +122,14 @@ function startMainLoop() {
|
||||
if (config.logToDatabase) {
|
||||
// Setup our database.
|
||||
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 {
|
||||
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++) {
|
||||
client.emit('add', [networkHistory[networkHistoryKeys[i]]]);
|
||||
}
|
||||
|
||||
// Send them the big 24h graph.
|
||||
client.emit('historyGraph', graphData);
|
||||
}, 1);
|
||||
|
||||
// Attach our listeners.
|
||||
|
@ -177,3 +177,11 @@ h3 {
|
||||
height: 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 id="big-graph"></div>
|
||||
|
||||
<div id="server-container" class="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 graphs = {};
|
||||
@ -169,6 +199,38 @@ function safeName(name) {
|
||||
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() {
|
||||
var socket = io.connect({
|
||||
reconnect: true,
|
||||
@ -179,6 +241,9 @@ $(document).ready(function() {
|
||||
var mojangServicesUpdater;
|
||||
var sortServersTask;
|
||||
|
||||
var historyPlot;
|
||||
var historyData;
|
||||
|
||||
socket.on('connect', function() {
|
||||
$('#tagline-text').text('Loading...');
|
||||
});
|
||||
@ -203,6 +268,27 @@ $(document).ready(function() {
|
||||
$('#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) {
|
||||
for (var i = 0; i < servers.length; i++) {
|
||||
var history = servers[i];
|
||||
@ -265,15 +351,7 @@ $(document).ready(function() {
|
||||
|
||||
updateServerStatus(lastEntry);
|
||||
|
||||
$('#chart_' + safeName(info.name)).bind('plothover', function(event, pos, item) {
|
||||
if (item) {
|
||||
renderTooltip(item.pageX + 5, item.pageY + 5, getTimestamp(item.datapoint[0] / 1000) + '\
|
||||
<br />\
|
||||
' + formatNumber(item.datapoint[1]) + ' Players');
|
||||
} else {
|
||||
hideTooltip();
|
||||
}
|
||||
});
|
||||
$('#chart_' + safeName(info.name)).bind('plothover', handlePlotHover);
|
||||
}
|
||||
|
||||
sortServers();
|
||||
@ -323,7 +401,7 @@ $(document).ready(function() {
|
||||
|
||||
sortServersTask = setInterval(function() {
|
||||
sortServers();
|
||||
}, 30 * 1000);
|
||||
}, 10 * 1000);
|
||||
|
||||
// Our super fancy scrolly thing!
|
||||
$(document).on('click', '.quick-jump-icon', function(e) {
|
||||
|
@ -190,8 +190,8 @@
|
||||
"rates": {
|
||||
"upateMojangStatus": 5000,
|
||||
"mojangStatusTimeout": 3500,
|
||||
"pingAll": 3000,
|
||||
"connectTimeout": 2500
|
||||
"pingAll": 2000,
|
||||
"connectTimeout": 1500
|
||||
},
|
||||
"logToDatabase": false
|
||||
"logToDatabase": true
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
var util = require('./util');
|
||||
|
||||
exports.setup = function() {
|
||||
var sqlite = require('sqlite3');
|
||||
|
||||
@ -10,6 +12,21 @@ exports.setup = function() {
|
||||
exports.log = function(ip, timestamp, playerCount) {
|
||||
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);
|
||||
});
|
||||
};
|
||||
};
|
47
lib/util.js
47
lib/util.js
@ -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() {
|
||||
return new Date().getTime();
|
||||
};
|
||||
@ -8,4 +36,23 @@ exports.setIntervalNoDelay = function(func, delay) {
|
||||
func();
|
||||
|
||||
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;
|
||||
};
|
Loading…
Reference in New Issue
Block a user