diff --git a/assets/js/graph.js b/assets/js/graph.js
index 5449aa0..72a99ac 100644
--- a/assets/js/graph.js
+++ b/assets/js/graph.js
@@ -149,7 +149,7 @@ export class GraphDisplayManager {
let playerCount
- if (id >= serverGraphData.length) {
+ if (id >= serverGraphData.length || typeof serverGraphData[id] !== 'number') {
playerCount = '-'
} else {
playerCount = formatNumber(serverGraphData[id])
diff --git a/assets/js/scale.js b/assets/js/scale.js
index 4a727d6..4b877e1 100644
--- a/assets/js/scale.js
+++ b/assets/js/scale.js
@@ -38,6 +38,10 @@ export class RelativeScale {
}
}
+ if (max === Number.MAX_VALUE) {
+ max = 0
+ }
+
return RelativeScale.scale([0, max], tickCount)
}
@@ -57,14 +61,23 @@ export class RelativeScale {
let max = Number.MIN_VALUE
for (const point of data) {
- if (point > max) {
- max = point
- }
- if (point < min) {
- min = point
+ if (typeof point === 'number') {
+ if (point > max) {
+ max = point
+ }
+ if (point < min) {
+ min = point
+ }
}
}
+ if (min === Number.MAX_VALUE) {
+ min = 0
+ }
+ if (max === Number.MIN_VALUE) {
+ max = 0
+ }
+
return [min, max]
}
}
diff --git a/assets/js/servers.js b/assets/js/servers.js
index fba696a..68113f7 100644
--- a/assets/js/servers.js
+++ b/assets/js/servers.js
@@ -63,7 +63,7 @@ export class ServerRegistration {
this._app = app
this.serverId = serverId
this.data = data
- this._graphData = []
+ this._graphData = [[], []]
this._failedSequentialPings = 0
}
@@ -82,7 +82,16 @@ export class ServerRegistration {
plugins: [
uPlotTooltipPlugin((pos, id, plot) => {
if (pos) {
- const text = formatNumber(plot.data[1][id]) + ' Players
' + formatTimestamp(plot.data[0][id] * 1000)
+ const playerCount = plot.data[1][id]
+
+ if (typeof playerCount !== 'number') {
+ this._app.tooltip.hide()
+
+ return
+ }
+
+ // FIXME: update timestamp schema
+ const text = formatNumber(playerCount) + ' Players
' + formatTimestamp(plot.data[0][id] * 1000)
this._app.tooltip.set(pos.left, pos.top, 10, 10, text)
} else {
@@ -147,23 +156,9 @@ export class ServerRegistration {
}
handlePing (payload, timestamp) {
- if (typeof payload.playerCount !== 'undefined') {
+ if (typeof payload.playerCount === 'number') {
this.playerCount = payload.playerCount
- // Only update graph for successful pings
- // This intentionally pauses the server graph when pings begin to fail
- this._graphData[0].push(Math.floor(timestamp / 1000))
- this._graphData[1].push(this.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()
- }
- }
-
- this.redraw()
-
// Reset failed ping counter to ensure the next connection error
// doesn't instantly retrigger a layout change
this._failedSequentialPings = 0
@@ -174,6 +169,34 @@ export class ServerRegistration {
this.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()
+ }
+ }
+
+ 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 () {
@@ -237,13 +260,23 @@ export class ServerRegistration {
const playerCountLabelElement = document.getElementById('player-count_' + this.serverId)
const errorElement = document.getElementById('error_' + this.serverId)
- if (ping.error) {
+ if (ping.error || typeof ping.playerCount !== 'number') {
// Hide any visible player-count and show the error element
playerCountLabelElement.style.display = 'none'
errorElement.style.display = 'block'
- errorElement.innerText = ping.error.message
- } else if (typeof ping.playerCount !== 'undefined') {
+ let errorMessage
+
+ if (ping.error) {
+ errorMessage = ping.error.message
+ } else if (typeof ping.playerCount !== 'number') {
+ // If the frontend has freshly connection, and the server's last ping was in error, it may not contain an error object
+ // In this case playerCount will safely be null, so provide a generic error message instead
+ errorMessage = 'Failed to ping'
+ }
+
+ errorElement.innerText = errorMessage
+ } else if (typeof ping.playerCount === 'number') {
// Ensure the player-count element is visible and hide the error element
playerCountLabelElement.style.display = 'block'
errorElement.style.display = 'none'
diff --git a/lib/ping.js b/lib/ping.js
index 82d7e33..58ef9c8 100644
--- a/lib/ping.js
+++ b/lib/ping.js
@@ -92,8 +92,9 @@ class PingController {
const result = results[serverRegistration.serverId]
// Log to database if enabled
+ // Use null to represent a failed ping
if (config.logToDatabase) {
- const playerCount = result.resp ? result.resp.players.online : 0
+ const playerCount = result.resp ? result.resp.players.online : null
this._app.database.insertPing(serverRegistration.data.ip, timestamp, playerCount)
}
@@ -130,6 +131,12 @@ class PingController {
const version = serverRegistration.getNextProtocolVersion()
ping(serverRegistration, config.rates.connectTimeout, (err, resp) => {
+ if (Math.random() < 0.1) {
+ err = {
+ message: 'random fail'
+ }
+ resp = undefined
+ }
if (err) {
logger.log('error', 'Failed to ping %s: %s', serverRegistration.data.ip, err.message)
}
diff --git a/lib/servers.js b/lib/servers.js
index 4f06507..a0ee646 100644
--- a/lib/servers.js
+++ b/lib/servers.js
@@ -21,7 +21,8 @@ class ServerRegistration {
}
handlePing (timestamp, resp, err, version) {
- const playerCount = resp ? resp.players.online : 0
+ // Use null to represent a failed ping
+ const playerCount = resp ? resp.players.online : null
// Store into in-memory ping data
this._pingHistory.push(playerCount)
@@ -37,7 +38,7 @@ class ServerRegistration {
let updateHistoryGraph = false
if (config.logToDatabase) {
- if (this.addGraphPoint(resp !== undefined, playerCount, timestamp)) {
+ if (this.addGraphPoint(playerCount, timestamp)) {
updateHistoryGraph = true
}
}
@@ -142,37 +143,21 @@ class ServerRegistration {
for (const point of points) {
// 0 is the index of the timestamp
if (point[0] - lastTimestamp >= 60 * 1000) {
- // This check tries to smooth out randomly dropped pings
- // By default only filter pings that are online (playerCount > 0)
- // This will keep looking forward until it finds a ping that is online
- // If it can't find one within a reasonable timeframe, it will select a failed ping
- if (point[0] - lastTimestamp >= 120 * 1000 || point[1] > 0) {
- minutePoints.push(point)
- lastTimestamp = point[0]
- }
+ lastTimestamp = point[0]
+
+ // FIXME: update schema, remove timestamp
+ minutePoints.push(point)
}
}
if (minutePoints.length > 0) {
this.graphData = minutePoints
-
- // Select the last entry to use for lastGraphDataPush
- this._lastGraphDataPush = minutePoints[minutePoints.length - 1][0]
}
}
- addGraphPoint (isSuccess, playerCount, timestamp) {
- // If the ping failed, then to avoid destroying the graph, ignore it
- // However if it's been too long since the last successful ping, push it anyways
- if (this._lastGraphDataPush) {
- const timeSince = timestamp - this._lastGraphDataPush
- if ((isSuccess && timeSince < 60 * 1000) || (!isSuccess && timeSince < 70 * 1000)) {
- return false
- }
- }
-
+ addGraphPoint (playerCount, timestamp) {
+ // FIXME: update schema, remove timestamp
this.graphData.push([timestamp, playerCount])
- this._lastGraphDataPush = timestamp
// Trim old graphPoints according to #getMaxGraphDataLength
if (this.graphData.length > TimeTracker.getMaxGraphDataLength()) {