diff --git a/assets/js/graph.js b/assets/js/graph.js index 72a99ac..b3f48c6 100644 --- a/assets/js/graph.js +++ b/assets/js/graph.js @@ -2,7 +2,7 @@ import uPlot from '../lib/uPlot.esm' import { RelativeScale } from './scale' -import { formatNumber, formatTimestamp, isMobileBrowser } from './util' +import { formatNumber, formatTimestampSeconds, isMobileBrowser } from './util' import { uPlotTooltipPlugin } from './tooltip' import { FAVORITE_SERVERS_STORAGE_KEY } from './favorites' @@ -23,7 +23,7 @@ export class GraphDisplayManager { this._showOnlyFavorites = false } - addGraphPoint (serverId, timestamp, playerCount) { + addGraphPoint (timestamp, playerCounts) { if (!this._hasLoadedSettings) { // _hasLoadedSettings is controlled by #setGraphData // It will only be true once the context has been loaded and initial payload received @@ -32,7 +32,11 @@ export class GraphDisplayManager { return } - // FIXME + this._graphTimestamps.push(timestamp) + + for (let i = 0; i < playerCounts.length; i++) { + this._graphData[i].push(playerCounts[i]) + } } loadLocalStorage () { @@ -106,7 +110,7 @@ export class GraphDisplayManager { } } - buildPlotInstance (graphData) { + buildPlotInstance (timestamps, data) { // Lazy load settings from localStorage, if any and if enabled if (!this._hasLoadedSettings) { this._hasLoadedSettings = true @@ -114,14 +118,8 @@ export class GraphDisplayManager { this.loadLocalStorage() } - // FIXME: timestamps are not shared! - this._graphTimestamps = graphData[0].map(val => Math.floor(val[0] / 1000)) - this._graphData = Object.values(graphData).map(val => { - return val.map(element => { - // Safely handle null data points, they represent gaps in the graph - return element === null ? null : element[1] - }) - }) + this._graphTimestamps = timestamps + this._graphData = data const series = this._app.serverRegistry.getServerRegistrations().map(serverRegistration => { return { @@ -141,7 +139,7 @@ export class GraphDisplayManager { uPlotTooltipPlugin((pos, id, plot) => { if (pos) { // FIXME - let text = '' + formatTimestamp(this._graphTimestamps[id] * 1000) + '

' + let text = '' + formatTimestampSeconds(this._graphTimestamps[id]) + '

' for (let i = 1; i < plot.series.length; i++) { const serverRegistration = this._app.serverRegistry.getServerRegistration(i - 1) diff --git a/assets/js/servers.js b/assets/js/servers.js index 68113f7..bca0d40 100644 --- a/assets/js/servers.js +++ b/assets/js/servers.js @@ -2,7 +2,7 @@ import uPlot from '../lib/uPlot.esm' import { RelativeScale } from './scale' -import { formatNumber, formatTimestamp, formatDate, formatMinecraftServerAddress, formatMinecraftVersions } from './util' +import { formatNumber, formatTimestampSeconds, formatDate, formatMinecraftServerAddress, formatMinecraftVersions } from './util' import { uPlotTooltipPlugin } from './tooltip' import MISSING_FAVICON from '../images/missing_favicon.svg' @@ -69,7 +69,7 @@ export class ServerRegistration { addGraphPoints (points, timestampPoints) { this._graphData = [ - timestampPoints.map(val => Math.floor(val / 1000)), + timestampPoints.slice(), points ] } @@ -90,8 +90,7 @@ export class ServerRegistration { return } - // FIXME: update timestamp schema - const text = formatNumber(playerCount) + ' Players
' + formatTimestamp(plot.data[0][id] * 1000) + const text = formatNumber(playerCount) + ' Players
' + formatTimestampSeconds(plot.data[0][id]) this._app.tooltip.set(pos.left, pos.top, 10, 10, text) } else { @@ -171,7 +170,7 @@ export class ServerRegistration { } // Use payload.playerCount so nulls WILL be pushed into the graphing data - this._graphData[0].push(Math.floor(timestamp / 1000)) + this._graphData[0].push(timestamp) this._graphData[1].push(payload.playerCount) // Trim graphData to within the max length by shifting out the leading elements @@ -182,21 +181,6 @@ export class ServerRegistration { } this.redraw() - - if (typeof payload.playerCount !== 'undefined') { - this.playerCount = payload.playerCount || 0 - - // Use payload.playerCount so nulls WILL be pushed into the graphing data - this._graphData[0].push(Math.floor(timestamp / 1000)) - this._graphData[1].push(payload.playerCount) - - // Trim graphData to within the max length by shifting out the leading elements - for (const series of this._graphData) { - if (series.length > this._app.publicConfig.serverGraphMaxLength) { - series.shift() - } - } - } } redraw () { @@ -219,7 +203,7 @@ export class ServerRegistration { const peakValueElement = document.getElementById('peak-value_' + this.serverId) peakValueElement.innerText = formatNumber(data.playerCount) - peakLabelElement.title = 'At ' + formatTimestamp(data.timestamp) + peakLabelElement.title = 'At ' + formatTimestampSeconds(data.timestamp) this.lastPeakData = data } @@ -245,7 +229,7 @@ export class ServerRegistration { // Safely handle legacy recordData that may not include the timestamp payload if (recordData.timestamp > 0) { recordValueElement.innerHTML = formatNumber(recordData.playerCount) + ' (' + formatDate(recordData.timestamp) + ')' - recordLabelElement.title = 'At ' + formatDate(recordData.timestamp) + ' ' + formatTimestamp(recordData.timestamp) + recordLabelElement.title = 'At ' + formatDate(recordData.timestamp) + ' ' + formatTimestampSeconds(recordData.timestamp) } else { recordValueElement.innerText = formatNumber(recordData.playerCount) } diff --git a/assets/js/socket.js b/assets/js/socket.js index b60645d..24e1049 100644 --- a/assets/js/socket.js +++ b/assets/js/socket.js @@ -81,8 +81,6 @@ export class SocketManager { break case 'updateServers': { - let requestGraphRedraw = false - for (let serverId = 0; serverId < payload.updates.length; serverId++) { // The backend may send "update" events prior to receiving all "add" events // A server has only been added once it's ServerRegistration is defined @@ -92,28 +90,18 @@ export class SocketManager { if (serverRegistration) { serverRegistration.handlePing(serverUpdate, payload.timestamp) - serverRegistration.updateServerStatus(serverUpdate, this._app.publicConfig.minecraftVersions) } - - // Use update payloads to conditionally append data to graph - // Skip any incoming updates if the graph is disabled - if (serverUpdate.updateHistoryGraph && this._app.graphDisplayManager.isVisible) { - // Update may not be successful, safely append 0 points - const playerCount = serverUpdate.playerCount || 0 - - this._app.graphDisplayManager.addGraphPoint(serverRegistration.serverId, payload.timestamp, playerCount) - - // Only redraw the graph if not mutating hidden data - if (serverRegistration.isVisible) { - requestGraphRedraw = true - } - } } - // Run redraw tasks after handling bulk updates - if (requestGraphRedraw) { - this._app.graphDisplayManager.redraw() + // Bulk add playerCounts into graph during #updateHistoryGraph + if (payload.updateHistoryGraph) { + this._app.graphDisplayManager.addGraphPoint(payload.timestamp, Object.values(payload.updates).map(update => update.playerCount)) + + // Run redraw tasks after handling bulk updates + if (this._app.graphDisplayManager.isVisible) { + this._app.graphDisplayManager.redraw() + } } this._app.percentageBar.redraw() @@ -132,7 +120,7 @@ export class SocketManager { // This is used for the manual graph load request behavior this._app.graphDisplayManager.isVisible = true - this._app.graphDisplayManager.buildPlotInstance(payload.graphData) + this._app.graphDisplayManager.buildPlotInstance(payload.timestamps, payload.graphData) // Build checkbox elements for graph controls let lastRowCounter = 0 diff --git a/assets/js/util.js b/assets/js/util.js index 6e55e42..31f89b3 100644 --- a/assets/js/util.js +++ b/assets/js/util.js @@ -99,15 +99,15 @@ export function formatMinecraftVersions (versions, knownVersions) { }).join(', ') } -export function formatTimestamp (millis) { +export function formatTimestampSeconds (secs) { const date = new Date(0) - date.setUTCSeconds(millis / 1000) + date.setUTCSeconds(secs) return date.toLocaleTimeString() } -export function formatDate (millis) { +export function formatDate (secs) { const date = new Date(0) - date.setUTCSeconds(millis / 1000) + date.setUTCSeconds(secs) return date.toLocaleDateString() } diff --git a/lib/app.js b/lib/app.js index 36fdc90..9ca1ce4 100644 --- a/lib/app.js +++ b/lib/app.js @@ -41,18 +41,19 @@ class App { if (config.logToDatabase) { client.on('message', (message) => { if (message === 'requestHistoryGraph') { - // Send historical graphData built from all serverRegistrations - const graphData = {} + // 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)) - this.serverRegistrations.forEach((serverRegistration) => { - graphData[serverRegistration.serverId] = serverRegistration.graphData - }) + // Send historical graphData built from all serverRegistrations + const graphData = this.serverRegistrations.map(serverRegistration => serverRegistration.graphData.map(point => point[1])) // 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', { - graphData: graphData + timestamps, + graphData })) } }) @@ -77,7 +78,7 @@ class App { } })(), mojangServices: this.mojangUpdater.getLastUpdate(), - timestampPoints: this.timeTracker.getPoints(), + timestampPoints: this.timeTracker.getPointsSeconds(), servers: this.serverRegistrations.map(serverRegistration => serverRegistration.getPingHistory()) } diff --git a/lib/database.js b/lib/database.js index 60f795a..c27e30d 100644 --- a/lib/database.js +++ b/lib/database.js @@ -63,8 +63,8 @@ class Database { // When complete increment completeTasks to know when complete this.getRecord(serverRegistration.data.ip, (playerCount, timestamp) => { serverRegistration.recordData = { - playerCount: playerCount, - timestamp: timestamp + playerCount, + timestamp: Math.floor(timestamp / 1000) } // Check if completedTasks hit the finish value diff --git a/lib/ping.js b/lib/ping.js index 58ef9c8..dcdd549 100644 --- a/lib/ping.js +++ b/lib/ping.js @@ -85,6 +85,14 @@ 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 + } + this.startPingTasks(results => { const updates = [] @@ -102,7 +110,7 @@ class PingController { // Generate a combined update payload // This includes any modified fields and flags used by the frontend // This will not be cached and can contain live metadata - const update = serverRegistration.handlePing(timestamp, result.resp, result.err, result.version) + const update = serverRegistration.handlePing(timestamp, result.resp, result.err, result.version, updateHistoryGraph) updates[serverRegistration.serverId] = update } @@ -110,7 +118,8 @@ class PingController { // Send object since updates uses serverIds as keys // Send a single timestamp entry since it is shared this._app.server.broadcast(MessageOf('updateServers', { - timestamp, + timestamp: Math.floor(timestamp / 1000), + updateHistoryGraph, updates })) }) diff --git a/lib/servers.js b/lib/servers.js index a0ee646..6ef2839 100644 --- a/lib/servers.js +++ b/lib/servers.js @@ -20,7 +20,7 @@ class ServerRegistration { this._pingHistory = [] } - handlePing (timestamp, resp, err, version) { + handlePing (timestamp, resp, err, version, updateHistoryGraph) { // Use null to represent a failed ping const playerCount = resp ? resp.players.online : null @@ -35,19 +35,21 @@ class ServerRegistration { // Only notify the frontend to append to the historical graph // if both the graphing behavior is enabled and the backend agrees // that the ping is eligible for addition - let updateHistoryGraph = false + if (updateHistoryGraph) { + // FIXME: update schema, remove timestamp + this.graphData.push([timestamp, playerCount]) - if (config.logToDatabase) { - if (this.addGraphPoint(playerCount, timestamp)) { - updateHistoryGraph = true + // Trim old graphPoints according to #getMaxGraphDataLength + if (this.graphData.length > TimeTracker.getMaxGraphDataLength()) { + this.graphData.shift() } } // Delegate out update payload generation - return this.getUpdate(timestamp, resp, err, version, updateHistoryGraph) + return this.getUpdate(timestamp, resp, err, version) } - getUpdate (timestamp, resp, err, version, updateHistoryGraph) { + getUpdate (timestamp, resp, err, version) { const update = {} if (resp) { @@ -59,7 +61,7 @@ class ServerRegistration { if (config.logToDatabase && (!this.recordData || resp.players.online > this.recordData.playerCount)) { this.recordData = { playerCount: resp.players.online, - timestamp: timestamp + timestamp: Math.floor(timestamp / 1000) } // Append an updated recordData @@ -80,12 +82,6 @@ class ServerRegistration { if (this.findNewGraphPeak()) { update.graphPeakData = this.getGraphPeak() } - - // Handled inside logToDatabase to validate logic from #getUpdate call - // Only append when true since an undefined value == false - if (updateHistoryGraph) { - update.updateHistoryGraph = true - } } } else if (err) { // Append a filtered copy of err @@ -155,18 +151,6 @@ class ServerRegistration { } } - addGraphPoint (playerCount, timestamp) { - // FIXME: update schema, remove timestamp - this.graphData.push([timestamp, playerCount]) - - // Trim old graphPoints according to #getMaxGraphDataLength - if (this.graphData.length > TimeTracker.getMaxGraphDataLength()) { - this.graphData.shift() - } - - return true - } - findNewGraphPeak () { let index = -1 for (let i = 0; i < this.graphData.length; i++) { @@ -192,7 +176,7 @@ class ServerRegistration { const graphPeak = this.graphData[this._graphPeakIndex] return { playerCount: graphPeak[1], - timestamp: graphPeak[0] + timestamp: Math.floor(graphPeak[0] / 1000) } } diff --git a/lib/time.js b/lib/time.js index 966272a..3556dfe 100644 --- a/lib/time.js +++ b/lib/time.js @@ -18,8 +18,8 @@ class TimeTracker { return timestamp } - getPoints () { - return this._points + getPointsSeconds () { + return this._points.map(value => Math.floor(value / 1000)) } static getMaxServerGraphDataLength () {