remove graph smoothing behavior, add null playerCount support

This commit is contained in:
Nick Krecklow 2020-05-11 03:10:23 -05:00
parent 19190f8d79
commit ef0c41ee1d
No known key found for this signature in database
GPG Key ID: 5F149FDE156FFA94
5 changed files with 89 additions and 51 deletions

@ -149,7 +149,7 @@ export class GraphDisplayManager {
let playerCount let playerCount
if (id >= serverGraphData.length) { if (id >= serverGraphData.length || typeof serverGraphData[id] !== 'number') {
playerCount = '-' playerCount = '-'
} else { } else {
playerCount = formatNumber(serverGraphData[id]) playerCount = formatNumber(serverGraphData[id])

@ -38,6 +38,10 @@ export class RelativeScale {
} }
} }
if (max === Number.MAX_VALUE) {
max = 0
}
return RelativeScale.scale([0, max], tickCount) return RelativeScale.scale([0, max], tickCount)
} }
@ -57,6 +61,7 @@ export class RelativeScale {
let max = Number.MIN_VALUE let max = Number.MIN_VALUE
for (const point of data) { for (const point of data) {
if (typeof point === 'number') {
if (point > max) { if (point > max) {
max = point max = point
} }
@ -64,6 +69,14 @@ export class RelativeScale {
min = point min = point
} }
} }
}
if (min === Number.MAX_VALUE) {
min = 0
}
if (max === Number.MIN_VALUE) {
max = 0
}
return [min, max] return [min, max]
} }

@ -63,7 +63,7 @@ export class ServerRegistration {
this._app = app this._app = app
this.serverId = serverId this.serverId = serverId
this.data = data this.data = data
this._graphData = [] this._graphData = [[], []]
this._failedSequentialPings = 0 this._failedSequentialPings = 0
} }
@ -82,7 +82,16 @@ export class ServerRegistration {
plugins: [ plugins: [
uPlotTooltipPlugin((pos, id, plot) => { uPlotTooltipPlugin((pos, id, plot) => {
if (pos) { if (pos) {
const text = formatNumber(plot.data[1][id]) + ' Players<br>' + 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<br>' + formatTimestamp(plot.data[0][id] * 1000)
this._app.tooltip.set(pos.left, pos.top, 10, 10, text) this._app.tooltip.set(pos.left, pos.top, 10, 10, text)
} else { } else {
@ -147,13 +156,23 @@ export class ServerRegistration {
} }
handlePing (payload, timestamp) { handlePing (payload, timestamp) {
if (typeof payload.playerCount !== 'undefined') { if (typeof payload.playerCount === 'number') {
this.playerCount = payload.playerCount this.playerCount = payload.playerCount
// Only update graph for successful pings // Reset failed ping counter to ensure the next connection error
// This intentionally pauses the server graph when pings begin to fail // doesn't instantly retrigger a layout change
this._failedSequentialPings = 0
} else {
// Attempt to retain a copy of the cached playerCount for up to N failed pings
// This prevents minor connection issues from constantly reshuffling the layout
if (++this._failedSequentialPings > 5) {
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[0].push(Math.floor(timestamp / 1000))
this._graphData[1].push(this.playerCount) this._graphData[1].push(payload.playerCount)
// Trim graphData to within the max length by shifting out the leading elements // Trim graphData to within the max length by shifting out the leading elements
for (const series of this._graphData) { for (const series of this._graphData) {
@ -164,14 +183,18 @@ export class ServerRegistration {
this.redraw() this.redraw()
// Reset failed ping counter to ensure the next connection error if (typeof payload.playerCount !== 'undefined') {
// doesn't instantly retrigger a layout change this.playerCount = payload.playerCount || 0
this._failedSequentialPings = 0
} else { // Use payload.playerCount so nulls WILL be pushed into the graphing data
// Attempt to retain a copy of the cached playerCount for up to N failed pings this._graphData[0].push(Math.floor(timestamp / 1000))
// This prevents minor connection issues from constantly reshuffling the layout this._graphData[1].push(payload.playerCount)
if (++this._failedSequentialPings > 5) {
this.playerCount = 0 // 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()
}
} }
} }
} }
@ -237,13 +260,23 @@ export class ServerRegistration {
const playerCountLabelElement = document.getElementById('player-count_' + this.serverId) const playerCountLabelElement = document.getElementById('player-count_' + this.serverId)
const errorElement = document.getElementById('error_' + 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 // Hide any visible player-count and show the error element
playerCountLabelElement.style.display = 'none' playerCountLabelElement.style.display = 'none'
errorElement.style.display = 'block' errorElement.style.display = 'block'
errorElement.innerText = ping.error.message let errorMessage
} else if (typeof ping.playerCount !== 'undefined') {
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 // Ensure the player-count element is visible and hide the error element
playerCountLabelElement.style.display = 'block' playerCountLabelElement.style.display = 'block'
errorElement.style.display = 'none' errorElement.style.display = 'none'

@ -92,8 +92,9 @@ class PingController {
const result = results[serverRegistration.serverId] const result = results[serverRegistration.serverId]
// Log to database if enabled // Log to database if enabled
// Use null to represent a failed ping
if (config.logToDatabase) { 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) this._app.database.insertPing(serverRegistration.data.ip, timestamp, playerCount)
} }
@ -130,6 +131,12 @@ class PingController {
const version = serverRegistration.getNextProtocolVersion() const version = serverRegistration.getNextProtocolVersion()
ping(serverRegistration, config.rates.connectTimeout, (err, resp) => { ping(serverRegistration, config.rates.connectTimeout, (err, resp) => {
if (Math.random() < 0.1) {
err = {
message: 'random fail'
}
resp = undefined
}
if (err) { if (err) {
logger.log('error', 'Failed to ping %s: %s', serverRegistration.data.ip, err.message) logger.log('error', 'Failed to ping %s: %s', serverRegistration.data.ip, err.message)
} }

@ -21,7 +21,8 @@ class ServerRegistration {
} }
handlePing (timestamp, resp, err, version) { 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 // Store into in-memory ping data
this._pingHistory.push(playerCount) this._pingHistory.push(playerCount)
@ -37,7 +38,7 @@ class ServerRegistration {
let updateHistoryGraph = false let updateHistoryGraph = false
if (config.logToDatabase) { if (config.logToDatabase) {
if (this.addGraphPoint(resp !== undefined, playerCount, timestamp)) { if (this.addGraphPoint(playerCount, timestamp)) {
updateHistoryGraph = true updateHistoryGraph = true
} }
} }
@ -142,37 +143,21 @@ class ServerRegistration {
for (const point of points) { for (const point of points) {
// 0 is the index of the timestamp // 0 is the index of the timestamp
if (point[0] - lastTimestamp >= 60 * 1000) { 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) { if (minutePoints.length > 0) {
this.graphData = minutePoints 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.graphData.push([timestamp, playerCount])
this._lastGraphDataPush = timestamp
// Trim old graphPoints according to #getMaxGraphDataLength // Trim old graphPoints according to #getMaxGraphDataLength
if (this.graphData.length > TimeTracker.getMaxGraphDataLength()) { if (this.graphData.length > TimeTracker.getMaxGraphDataLength()) {