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 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);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
};
|
};
|
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() {
|
exports.getCurrentTimeMs = function() {
|
||||||
return new Date().getTime();
|
return new Date().getTime();
|
||||||
};
|
};
|
||||||
@ -8,4 +36,23 @@ exports.setIntervalNoDelay = function(func, delay) {
|
|||||||
func();
|
func();
|
||||||
|
|
||||||
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;
|
||||||
};
|
};
|
Loading…
Reference in New Issue
Block a user