diff --git a/assets/css/main.css b/assets/css/main.css index 9a9fb62..7a66d37 100644 --- a/assets/css/main.css +++ b/assets/css/main.css @@ -344,7 +344,7 @@ footer a:hover { } #big-graph { - padding-right: 60px; + padding-right: 65px; } #big-graph, #big-graph-controls, #big-graph-checkboxes { diff --git a/assets/js/graph.js b/assets/js/graph.js index b3f48c6..13343c9 100644 --- a/assets/js/graph.js +++ b/assets/js/graph.js @@ -37,6 +37,11 @@ export class GraphDisplayManager { for (let i = 0; i < playerCounts.length; i++) { this._graphData[i].push(playerCounts[i]) } + + this._plotInstance.setData([ + this._graphTimestamps, + ...this._graphData + ]) } loadLocalStorage () { @@ -183,7 +188,7 @@ export class GraphDisplayManager { { font: '14px "Open Sans", sans-serif', stroke: '#FFF', - size: 60, + size: 65, grid: { stroke: '#333', width: 1 diff --git a/assets/js/scale.js b/assets/js/scale.js index 4b877e1..7a82368 100644 --- a/assets/js/scale.js +++ b/assets/js/scale.js @@ -12,7 +12,7 @@ export class RelativeScale { const ticks = (scaledMax - scaledMin) / scale - if (ticks + 1 <= tickCount) { + if (ticks < tickCount + 1) { return [scaledMin, scaledMax, scale] } else { // Too many steps between min/max, increase factor and try again diff --git a/assets/js/servers.js b/assets/js/servers.js index bca0d40..b39623b 100644 --- a/assets/js/servers.js +++ b/assets/js/servers.js @@ -75,7 +75,7 @@ export class ServerRegistration { } buildPlotInstance () { - const tickCount = 5 + const tickCount = 4 // eslint-disable-next-line new-cap this._plotInstance = new uPlot({ diff --git a/lib/app.js b/lib/app.js index 9ca1ce4..de640cd 100644 --- a/lib/app.js +++ b/lib/app.js @@ -41,18 +41,14 @@ class App { if (config.logToDatabase) { client.on('message', (message) => { if (message === 'requestHistoryGraph') { - // FIXME: update schema, remove timestamp - // Since all history graph updates share timestamps, pull the timestamps from a single ServerRegistration - const timestamps = this.serverRegistrations[0].graphData.map(point => Math.floor(point[0] / 1000)) - // Send historical graphData built from all serverRegistrations - const graphData = this.serverRegistrations.map(serverRegistration => serverRegistration.graphData.map(point => point[1])) + const graphData = this.serverRegistrations.map(serverRegistration => serverRegistration.graphData) // Send graphData in object wrapper to avoid needing to explicity filter // any header data being appended by #MessageOf since the graph data is fed // directly into the graphing system client.send(MessageOf('historyGraph', { - timestamps, + timestamps: this.timeTracker.getHistoricalPointsSeconds(), graphData })) } @@ -78,7 +74,7 @@ class App { } })(), mojangServices: this.mojangUpdater.getLastUpdate(), - timestampPoints: this.timeTracker.getPointsSeconds(), + timestampPoints: this.timeTracker.getServerPointsSeconds(), servers: this.serverRegistrations.map(serverRegistration => serverRegistration.getPingHistory()) } diff --git a/lib/database.js b/lib/database.js index c27e30d..e3b3b49 100644 --- a/lib/database.js +++ b/lib/database.js @@ -20,33 +20,63 @@ class Database { const startTime = endTime - graphDuration this.getRecentPings(startTime, endTime, pingData => { - const graphPointsByIp = [] + const relativeGraphData = [] for (const row of pingData) { // Load into temporary array // This will be culled prior to being pushed to the serverRegistration - let graphPoints = graphPointsByIp[row.ip] - if (!graphPoints) { - graphPoints = graphPointsByIp[row.ip] = [] + let graphData = relativeGraphData[row.ip] + if (!graphData) { + relativeGraphData[row.ip] = graphData = [[], []] } - graphPoints.push([row.timestamp, row.playerCount]) + // DANGER! + // This will pull the timestamp from each row into memory + // This is built under the assumption that each round of pings shares the same timestamp + // This enables all timestamp arrays to have consistent point selection and graph correctly + graphData[0].push(row.timestamp) + graphData[1].push(row.playerCount) } - Object.keys(graphPointsByIp).forEach(ip => { + Object.keys(relativeGraphData).forEach(ip => { // Match IPs to serverRegistration object for (const serverRegistration of this._app.serverRegistrations) { if (serverRegistration.data.ip === ip) { - const graphPoints = graphPointsByIp[ip] + const graphData = relativeGraphData[ip] // Push the data into the instance and cull if needed - serverRegistration.loadGraphPoints(graphPoints) + serverRegistration.loadGraphPoints(startTime, graphData[0], graphData[1]) break } } }) + // Since all timestamps are shared, use the array from the first ServerRegistration + // This is very dangerous and can break if data is out of sync + if (Object.keys(relativeGraphData).length > 0) { + const serverIp = Object.keys(relativeGraphData)[0] + const timestamps = relativeGraphData[serverIp][0] + + // This is a copy of ServerRegistration#loadGraphPoints + // relativeGraphData contains original timestamp data and needs to be filtered into minutes + const sharedTimestamps = [] + + let lastTimestamp = startTime + + for (let i = 0; i < timestamps.length; i++) { + const timestamp = timestamps[i] + + if (timestamp - lastTimestamp >= 60 * 1000) { + lastTimestamp = timestamp + + sharedTimestamps.push(timestamp) + } + } + + this._app.timeTracker.loadHistoricalTimestamps(sharedTimestamps) + } + callback() }) } diff --git a/lib/ping.js b/lib/ping.js index 1ebbedf..00fca63 100644 --- a/lib/ping.js +++ b/lib/ping.js @@ -83,15 +83,7 @@ class PingController { } pingAll = () => { - const timestamp = this._app.timeTracker.newTimestamp() - - // Flag each group as history graph additions each minute - // This is sent to the frontend for graph updates - const updateHistoryGraph = config.logToDatabase && (!this._lastHistoryGraphUpdate || timestamp - this._lastHistoryGraphUpdate >= 60 * 1000) - - if (updateHistoryGraph) { - this._lastHistoryGraphUpdate = timestamp - } + const { timestamp, updateHistoryGraph } = this._app.timeTracker.newPingTimestamp() this.startPingTasks(results => { const updates = [] diff --git a/lib/servers.js b/lib/servers.js index 6ef2839..412983e 100644 --- a/lib/servers.js +++ b/lib/servers.js @@ -14,7 +14,8 @@ class ServerRegistration { recordData graphData = [] - constructor (serverId, data) { + constructor (app, serverId, data) { + this._app = app this.serverId = serverId this.data = data this._pingHistory = [] @@ -36,8 +37,7 @@ class ServerRegistration { // if both the graphing behavior is enabled and the backend agrees // that the ping is eligible for addition if (updateHistoryGraph) { - // FIXME: update schema, remove timestamp - this.graphData.push([timestamp, playerCount]) + this.graphData.push(playerCount) // Trim old graphPoints according to #getMaxGraphDataLength if (this.graphData.length > TimeTracker.getMaxGraphDataLength()) { @@ -131,31 +131,26 @@ class ServerRegistration { } } - loadGraphPoints (points) { + loadGraphPoints (startTime, timestamps, points) { // Filter pings so each result is a minute apart - const minutePoints = [] - let lastTimestamp = 0 + let lastTimestamp = startTime - for (const point of points) { - // 0 is the index of the timestamp - if (point[0] - lastTimestamp >= 60 * 1000) { - lastTimestamp = point[0] + for (let i = 0; i < timestamps.length; i++) { + const timestamp = timestamps[i] - // FIXME: update schema, remove timestamp - minutePoints.push(point) + if (timestamp - lastTimestamp >= 60 * 1000) { + lastTimestamp = timestamp + + this.graphData.push(points[i]) } } - - if (minutePoints.length > 0) { - this.graphData = minutePoints - } } findNewGraphPeak () { let index = -1 for (let i = 0; i < this.graphData.length; i++) { const point = this.graphData[i] - if (index === -1 || point[1] > this.graphData[index][1]) { + if (index === -1 || point > this.graphData[index]) { index = i } } @@ -173,10 +168,9 @@ class ServerRegistration { if (this._graphPeakIndex === undefined) { return } - const graphPeak = this.graphData[this._graphPeakIndex] return { - playerCount: graphPeak[1], - timestamp: Math.floor(graphPeak[0] / 1000) + playerCount: this.graphData[this._graphPeakIndex], + timestamp: this._app.timeTracker.getHistoricalPointSeconds(this._graphPeakIndex) } } diff --git a/lib/time.js b/lib/time.js index 3556dfe..1710644 100644 --- a/lib/time.js +++ b/lib/time.js @@ -4,9 +4,10 @@ class TimeTracker { constructor (app) { this._app = app this._points = [] + this._historicalTimestamps = [] } - newTimestamp () { + newPingTimestamp () { const timestamp = new Date().getTime() this._points.push(timestamp) @@ -15,10 +16,40 @@ class TimeTracker { this._points.shift() } - return timestamp + // Flag each group as history graph additions each minute + // This is sent to the frontend for graph updates + const updateHistoryGraph = config.logToDatabase && (!this._lastHistoryGraphUpdate || timestamp - this._lastHistoryGraphUpdate >= 60 * 1000) + + if (updateHistoryGraph) { + this._lastHistoryGraphUpdate = timestamp + + // Push into timestamps array to update backend state + this._historicalTimestamps.push(timestamp) + + if (this._historicalTimestamps.length > TimeTracker.getMaxGraphDataLength()) { + this._historicalTimestamps.shift() + } + } + + return { + timestamp, + updateHistoryGraph + } } - getPointsSeconds () { + loadHistoricalTimestamps (timestamps) { + this._historicalTimestamps = timestamps + } + + getHistoricalPointsSeconds () { + return this._historicalTimestamps.map(value => Math.floor(value / 1000)) + } + + getHistoricalPointSeconds (index) { + return Math.floor(this._historicalTimestamps[index] / 1000) + } + + getServerPointsSeconds () { return this._points.map(value => Math.floor(value / 1000)) } diff --git a/main.js b/main.js index e0518f7..fec55ae 100644 --- a/main.js +++ b/main.js @@ -22,7 +22,7 @@ servers.forEach((server, serverId) => { } // Init a ServerRegistration instance of each entry in servers.json - app.serverRegistrations.push(new ServerRegistration(serverId, server)) + app.serverRegistrations.push(new ServerRegistration(app, serverId, server)) }) if (!config.serverGraphDuration) {