share a single timestamp array between all graphData objects

This commit is contained in:
Nick Krecklow 2020-05-11 18:12:29 -05:00
parent 59ec7d151f
commit c2f6d04e72
No known key found for this signature in database
GPG Key ID: 5F149FDE156FFA94
10 changed files with 100 additions and 52 deletions

@ -344,7 +344,7 @@ footer a:hover {
} }
#big-graph { #big-graph {
padding-right: 60px; padding-right: 65px;
} }
#big-graph, #big-graph-controls, #big-graph-checkboxes { #big-graph, #big-graph-controls, #big-graph-checkboxes {

@ -37,6 +37,11 @@ export class GraphDisplayManager {
for (let i = 0; i < playerCounts.length; i++) { for (let i = 0; i < playerCounts.length; i++) {
this._graphData[i].push(playerCounts[i]) this._graphData[i].push(playerCounts[i])
} }
this._plotInstance.setData([
this._graphTimestamps,
...this._graphData
])
} }
loadLocalStorage () { loadLocalStorage () {
@ -183,7 +188,7 @@ export class GraphDisplayManager {
{ {
font: '14px "Open Sans", sans-serif', font: '14px "Open Sans", sans-serif',
stroke: '#FFF', stroke: '#FFF',
size: 60, size: 65,
grid: { grid: {
stroke: '#333', stroke: '#333',
width: 1 width: 1

@ -12,7 +12,7 @@ export class RelativeScale {
const ticks = (scaledMax - scaledMin) / scale const ticks = (scaledMax - scaledMin) / scale
if (ticks + 1 <= tickCount) { if (ticks < tickCount + 1) {
return [scaledMin, scaledMax, scale] return [scaledMin, scaledMax, scale]
} else { } else {
// Too many steps between min/max, increase factor and try again // Too many steps between min/max, increase factor and try again

@ -75,7 +75,7 @@ export class ServerRegistration {
} }
buildPlotInstance () { buildPlotInstance () {
const tickCount = 5 const tickCount = 4
// eslint-disable-next-line new-cap // eslint-disable-next-line new-cap
this._plotInstance = new uPlot({ this._plotInstance = new uPlot({

@ -41,18 +41,14 @@ class App {
if (config.logToDatabase) { if (config.logToDatabase) {
client.on('message', (message) => { client.on('message', (message) => {
if (message === 'requestHistoryGraph') { 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 // 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 // Send graphData in object wrapper to avoid needing to explicity filter
// any header data being appended by #MessageOf since the graph data is fed // any header data being appended by #MessageOf since the graph data is fed
// directly into the graphing system // directly into the graphing system
client.send(MessageOf('historyGraph', { client.send(MessageOf('historyGraph', {
timestamps, timestamps: this.timeTracker.getHistoricalPointsSeconds(),
graphData graphData
})) }))
} }
@ -78,7 +74,7 @@ class App {
} }
})(), })(),
mojangServices: this.mojangUpdater.getLastUpdate(), mojangServices: this.mojangUpdater.getLastUpdate(),
timestampPoints: this.timeTracker.getPointsSeconds(), timestampPoints: this.timeTracker.getServerPointsSeconds(),
servers: this.serverRegistrations.map(serverRegistration => serverRegistration.getPingHistory()) servers: this.serverRegistrations.map(serverRegistration => serverRegistration.getPingHistory())
} }

@ -20,33 +20,63 @@ class Database {
const startTime = endTime - graphDuration const startTime = endTime - graphDuration
this.getRecentPings(startTime, endTime, pingData => { this.getRecentPings(startTime, endTime, pingData => {
const graphPointsByIp = [] const relativeGraphData = []
for (const row of pingData) { for (const row of pingData) {
// Load into temporary array // Load into temporary array
// This will be culled prior to being pushed to the serverRegistration // This will be culled prior to being pushed to the serverRegistration
let graphPoints = graphPointsByIp[row.ip] let graphData = relativeGraphData[row.ip]
if (!graphPoints) { if (!graphData) {
graphPoints = graphPointsByIp[row.ip] = [] 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 // Match IPs to serverRegistration object
for (const serverRegistration of this._app.serverRegistrations) { for (const serverRegistration of this._app.serverRegistrations) {
if (serverRegistration.data.ip === ip) { if (serverRegistration.data.ip === ip) {
const graphPoints = graphPointsByIp[ip] const graphData = relativeGraphData[ip]
// Push the data into the instance and cull if needed // Push the data into the instance and cull if needed
serverRegistration.loadGraphPoints(graphPoints) serverRegistration.loadGraphPoints(startTime, graphData[0], graphData[1])
break 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() callback()
}) })
} }

@ -83,15 +83,7 @@ class PingController {
} }
pingAll = () => { pingAll = () => {
const timestamp = this._app.timeTracker.newTimestamp() const { timestamp, updateHistoryGraph } = this._app.timeTracker.newPingTimestamp()
// 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 => { this.startPingTasks(results => {
const updates = [] const updates = []

@ -14,7 +14,8 @@ class ServerRegistration {
recordData recordData
graphData = [] graphData = []
constructor (serverId, data) { constructor (app, serverId, data) {
this._app = app
this.serverId = serverId this.serverId = serverId
this.data = data this.data = data
this._pingHistory = [] this._pingHistory = []
@ -36,8 +37,7 @@ class ServerRegistration {
// if both the graphing behavior is enabled and the backend agrees // if both the graphing behavior is enabled and the backend agrees
// that the ping is eligible for addition // that the ping is eligible for addition
if (updateHistoryGraph) { if (updateHistoryGraph) {
// FIXME: update schema, remove timestamp this.graphData.push(playerCount)
this.graphData.push([timestamp, playerCount])
// Trim old graphPoints according to #getMaxGraphDataLength // Trim old graphPoints according to #getMaxGraphDataLength
if (this.graphData.length > TimeTracker.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 // Filter pings so each result is a minute apart
const minutePoints = [] let lastTimestamp = startTime
let lastTimestamp = 0
for (const point of points) { for (let i = 0; i < timestamps.length; i++) {
// 0 is the index of the timestamp const timestamp = timestamps[i]
if (point[0] - lastTimestamp >= 60 * 1000) {
lastTimestamp = point[0]
// FIXME: update schema, remove timestamp if (timestamp - lastTimestamp >= 60 * 1000) {
minutePoints.push(point) lastTimestamp = timestamp
this.graphData.push(points[i])
} }
} }
if (minutePoints.length > 0) {
this.graphData = minutePoints
}
} }
findNewGraphPeak () { findNewGraphPeak () {
let index = -1 let index = -1
for (let i = 0; i < this.graphData.length; i++) { for (let i = 0; i < this.graphData.length; i++) {
const point = this.graphData[i] const point = this.graphData[i]
if (index === -1 || point[1] > this.graphData[index][1]) { if (index === -1 || point > this.graphData[index]) {
index = i index = i
} }
} }
@ -173,10 +168,9 @@ class ServerRegistration {
if (this._graphPeakIndex === undefined) { if (this._graphPeakIndex === undefined) {
return return
} }
const graphPeak = this.graphData[this._graphPeakIndex]
return { return {
playerCount: graphPeak[1], playerCount: this.graphData[this._graphPeakIndex],
timestamp: Math.floor(graphPeak[0] / 1000) timestamp: this._app.timeTracker.getHistoricalPointSeconds(this._graphPeakIndex)
} }
} }

@ -4,9 +4,10 @@ class TimeTracker {
constructor (app) { constructor (app) {
this._app = app this._app = app
this._points = [] this._points = []
this._historicalTimestamps = []
} }
newTimestamp () { newPingTimestamp () {
const timestamp = new Date().getTime() const timestamp = new Date().getTime()
this._points.push(timestamp) this._points.push(timestamp)
@ -15,10 +16,40 @@ class TimeTracker {
this._points.shift() 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)) return this._points.map(value => Math.floor(value / 1000))
} }

@ -22,7 +22,7 @@ servers.forEach((server, serverId) => {
} }
// Init a ServerRegistration instance of each entry in servers.json // 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) { if (!config.serverGraphDuration) {