Merge pull request #170 from Cryptkeeper/dns-perf

5.4.3 release
This commit is contained in:
Nick Krecklow 2020-05-14 20:47:09 -05:00 committed by GitHub
commit 0e5859a829
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 94 additions and 77 deletions

@ -9,10 +9,6 @@
"pingAll": 3000, "pingAll": 3000,
"connectTimeout": 2500 "connectTimeout": 2500
}, },
"performance": {
"skipUnfurlSrv": false,
"unfurlSrvCacheTtl": 120000
},
"logToDatabase": false, "logToDatabase": false,
"graphDuration": 86400000, "graphDuration": 86400000,
"serverGraphDuration": 180000 "serverGraphDuration": 180000

@ -1,3 +1,7 @@
**5.4.3** *(May 14 2020)*
- Added support for the optional field `config->skipSrvTimeout` in `config.json`. If a configured server does not return a valid response when unfurling potential SRV records, it will avoid re-unfurling SRV records for this duration in milliseconds. Use a value of `0` to disable this feature altogether.
- Removes support for the `config->performance->skipUnfurlSrv` and `config->performance->unfurlSrvCacheTtl` fields in `config.json
**5.4.2** *(May 13 2020)* **5.4.2** *(May 13 2020)*
- Fixes a typo causing `_minecraft._tcp.*` SRV records to not resolve. - Fixes a typo causing `_minecraft._tcp.*` SRV records to not resolve.

@ -1,5 +1,7 @@
const sqlite = require('sqlite3') const sqlite = require('sqlite3')
const TimeTracker = require('./time')
class Database { class Database {
constructor (app) { constructor (app) {
this._app = app this._app = app
@ -16,7 +18,7 @@ class Database {
loadGraphPoints (graphDuration, callback) { loadGraphPoints (graphDuration, callback) {
// Query recent pings // Query recent pings
const endTime = new Date().getTime() const endTime = TimeTracker.getEpochMillis()
const startTime = endTime - graphDuration const startTime = endTime - graphDuration
this.getRecentPings(startTime, endTime, pingData => { this.getRecentPings(startTime, endTime, pingData => {

75
lib/dns.js Normal file

@ -0,0 +1,75 @@
const dns = require('dns')
const logger = require('./logger')
const TimeTracker = require('./time')
const config = require('../config')
const SKIP_SRV_TIMEOUT = config.skipSrvTimeout || 60 * 60 * 1000
class DNSResolver {
constructor (ip, port) {
this._ip = ip
this._port = port
}
_skipSrv () {
this._skipSrvUntil = TimeTracker.getEpochMillis() + SKIP_SRV_TIMEOUT
}
_isSkipSrv () {
return this._skipSrvUntil && TimeTracker.getEpochMillis() <= this._skipSrvUntil
}
resolve (callback) {
if (this._isSkipSrv()) {
callback(this._ip, this._port, config.rates.connectTimeout)
return
}
const startTime = TimeTracker.getEpochMillis()
let callbackFired = false
const fireCallback = (ip, port) => {
if (!callbackFired) {
callbackFired = true
// Send currentTime - startTime to provide remaining connectionTime allowance
callback(ip || this._ip, port || this._port, TimeTracker.getEpochMillis() - startTime)
}
}
const timeoutCallback = setTimeout(fireCallback, config.rates.connectTimeout)
dns.resolveSrv('_minecraft._tcp.' + this._ip, (err, records) => {
// Cancel the timeout handler if not already fired
if (!callbackFired) {
clearTimeout(timeoutCallback)
}
// Test if the error indicates a miss, or if the records returned are empty
if ((err && (err.code === 'ENOTFOUND' || err.code === 'ENODATA')) || !records || records.length === 0) {
// Compare config.skipSrvTimeout directly since SKIP_SRV_TIMEOUT has an or'd value
// isValidSkipSrvTimeout == whether the config has a valid skipSrvTimeout value set
const isValidSkipSrvTimeout = typeof config.skipSrvTimeout === 'number' && config.skipSrvTimeout > 0
// Only activate _skipSrv if the skipSrvTimeout value is either NaN or > 0
// 0 represents a disabled flag
if (!this._isSkipSrv() && isValidSkipSrvTimeout) {
this._skipSrv()
logger.log('warn', 'No SRV records were resolved for %s. Minetrack will skip attempting to resolve %s SRV records for %d minutes.', this._ip, this._ip, SKIP_SRV_TIMEOUT / (60 * 1000))
}
fireCallback()
} else {
// Only fires if !err && records.length > 0
fireCallback(records[0].name, records[0].port)
}
})
}
}
module.exports = DNSResolver

@ -9,7 +9,7 @@ winston.add(winston.transports.File, {
winston.add(winston.transports.Console, { winston.add(winston.transports.Console, {
timestamp: () => { timestamp: () => {
const date = new Date() const date = new Date()
return date.toLocaleTimeString() + ' ' + date.getDate() + '/' + (date.getMonth() + 1) + '/' + date.getFullYear().toString().substring(2, 4) return date.toLocaleTimeString() + ' ' + date.toLocaleDateString()
}, },
colorize: true colorize: true
}) })

@ -9,10 +9,10 @@ const config = require('../config')
function ping (serverRegistration, timeout, callback, version) { function ping (serverRegistration, timeout, callback, version) {
switch (serverRegistration.data.type) { switch (serverRegistration.data.type) {
case 'PC': case 'PC':
serverRegistration.unfurlSrv((host, port) => { serverRegistration.dnsResolver.unfurlSrv((host, port, remainingTimeout) => {
const server = new minecraftJavaPing.MinecraftServer(host, port || 25565) const server = new minecraftJavaPing.MinecraftServer(host, port || 25565)
server.ping(timeout, version, (err, res) => { server.ping(remainingTimeout, version, (err, res) => {
if (err) { if (err) {
callback(err) callback(err)
} else { } else {

@ -1,6 +1,6 @@
const crypto = require('crypto') const crypto = require('crypto')
const dns = require('dns')
const DNSResolver = require('./dns')
const TimeTracker = require('./time') const TimeTracker = require('./time')
const Server = require('./server') const Server = require('./server')
@ -18,6 +18,7 @@ class ServerRegistration {
this.serverId = serverId this.serverId = serverId
this.data = data this.data = data
this._pingHistory = [] this._pingHistory = []
this.dnsResolver = new DNSResolver(this.data.ip, this.data.port)
} }
handlePing (timestamp, resp, err, version) { handlePing (timestamp, resp, err, version) {
@ -310,62 +311,6 @@ class ServerRegistration {
color: this.data.color color: this.data.color
} }
} }
unfurlSrv (callback) {
// Skip unfurling SRV, instantly return pre-configured data
if (config.performance && config.performance.skipUnfurlSrv) {
callback(this.data.ip, this.data.port)
return
}
const timestamp = new Date().getTime()
// If a cached copy exists and is within its TTL, instantly return
if (this._lastUnfurlSrv && timestamp - this._lastUnfurlSrv.timestamp <= config.performance.unfurlSrvCacheTtl) {
callback(this._lastUnfurlSrv.ip, this._lastUnfurlSrv.port)
return
}
// Group callbacks into an array
// Once resolved, fire callbacks sequentially
// This avoids callbacks possibly executing out of order
if (!this._unfurlSrvCallbackQueue) {
this._unfurlSrvCallbackQueue = []
}
this._unfurlSrvCallbackQueue.push(callback)
// Prevent multiple #resolveSrv calls per ServerRegistration
if (!this._isUnfurlingSrv) {
this._isUnfurlingSrv = true
dns.resolveSrv('_minecraft._tcp.' + this.data.ip, (_, records) => {
this._lastUnfurlSrv = {
timestamp
}
if (records && records.length > 0) {
this._lastUnfurlSrv.ip = records[0].name
this._lastUnfurlSrv.port = records[0].port
} else {
// Provide fallback to pre-configured data
this._lastUnfurlSrv.ip = this.data.ip
this._lastUnfurlSrv.port = this.data.port
}
// Fire the waiting callbacks in queue
// Release blocking flag to allow new #resolveSrv calls
this._isUnfurlingSrv = false
for (const callback of this._unfurlSrvCallbackQueue) {
callback(this._lastUnfurlSrv.ip, this._lastUnfurlSrv.port)
}
// Reset the callback queue
this._unfurlSrvCallbackQueue = []
})
}
}
} }
module.exports = ServerRegistration module.exports = ServerRegistration

@ -7,7 +7,7 @@ class TimeTracker {
} }
newTimestamp () { newTimestamp () {
const timestamp = new Date().getTime() const timestamp = TimeTracker.getEpochMillis()
this._points.push(timestamp) this._points.push(timestamp)
@ -22,6 +22,10 @@ class TimeTracker {
return this._points return this._points
} }
static getEpochMillis () {
return new Date().getTime()
}
static getMaxServerGraphDataLength () { static getMaxServerGraphDataLength () {
return Math.ceil(config.serverGraphDuration / config.rates.pingAll) return Math.ceil(config.serverGraphDuration / config.rates.pingAll)
} }

@ -30,15 +30,6 @@ if (!config.serverGraphDuration) {
config.serverGraphDuration = 3 * 60 * 10000 config.serverGraphDuration = 3 * 60 * 10000
} }
if (config.performance && config.performance.skipUnfurlSrv) {
logger.log('warn', '"performance.skipUnfurlSrv" is enabled. Any configured hosts using SRV records may not properly resolve.')
}
if (!config.performance || typeof config.performance.unfurlSrvCacheTtl === 'undefined') {
logger.log('warn', '"performance.unfurlSrvCacheTtl" is not defined in config.json - defaulting to 120 seconds!')
config.performance.unfurlSrvCacheTtl = 2 * 60 * 1000
}
if (!config.logToDatabase) { if (!config.logToDatabase) {
logger.log('warn', 'Database logging is not enabled. You can enable it by setting "logToDatabase" to true in config.json. This requires sqlite3 to be installed.') logger.log('warn', 'Database logging is not enabled. You can enable it by setting "logToDatabase" to true in config.json. This requires sqlite3 to be installed.')

2
package-lock.json generated

@ -1,6 +1,6 @@
{ {
"name": "minetrack", "name": "minetrack",
"version": "5.4.2", "version": "5.4.3",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

@ -1,6 +1,6 @@
{ {
"name": "minetrack", "name": "minetrack",
"version": "5.4.2", "version": "5.4.3",
"description": "A Minecraft server tracker that lets you focus on the basics.", "description": "A Minecraft server tracker that lets you focus on the basics.",
"main": "main.js", "main": "main.js",
"dependencies": { "dependencies": {