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,
"connectTimeout": 2500
},
"performance": {
"skipUnfurlSrv": false,
"unfurlSrvCacheTtl": 120000
},
"logToDatabase": false,
"graphDuration": 86400000,
"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)*
- Fixes a typo causing `_minecraft._tcp.*` SRV records to not resolve.

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

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

@ -1,6 +1,6 @@
const crypto = require('crypto')
const dns = require('dns')
const DNSResolver = require('./dns')
const TimeTracker = require('./time')
const Server = require('./server')
@ -18,6 +18,7 @@ class ServerRegistration {
this.serverId = serverId
this.data = data
this._pingHistory = []
this.dnsResolver = new DNSResolver(this.data.ip, this.data.port)
}
handlePing (timestamp, resp, err, version) {
@ -310,62 +311,6 @@ class ServerRegistration {
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

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

@ -30,15 +30,6 @@ if (!config.serverGraphDuration) {
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) {
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",
"version": "5.4.2",
"version": "5.4.3",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

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