# -*- coding: utf-8 -*- # Description: spigotmc netdata python.d module # Author: Austin S. Hemmelgarn (Ferroin) # SPDX-License-Identifier: GPL-3.0-or-later import socket import platform from bases.FrameworkServices.SimpleService import SimpleService from third_party import mcrcon # Update only every 5 seconds because collection takes in excess of # 100ms sometimes, and mos tpeople won't care about second-by-second data. update_every = 5 PRECISION = 100 ORDER = [ 'tps', 'users', ] CHARTS = { 'tps': { 'options': [None, 'Spigot Ticks Per Second', 'ticks', 'spigotmc', 'spigotmc.tps', 'line'], 'lines': [ ['tps1', '1 Minute Average', 'absolute', 1, PRECISION], ['tps5', '5 Minute Average', 'absolute', 1, PRECISION], ['tps15', '15 Minute Average', 'absolute', 1, PRECISION] ] }, 'users': { 'options': [None, 'Minecraft Users', 'users', 'spigotmc', 'spigotmc.users', 'area'], 'lines': [ ['users', 'Users', 'absolute', 1, 1] ] } } class Service(SimpleService): def __init__(self, configuration=None, name=None): SimpleService.__init__(self, configuration=configuration, name=name) self.order = ORDER self.definitions = CHARTS self.host = self.configuration.get('host', 'localhost') self.port = self.configuration.get('port', 25575) self.password = self.configuration.get('password', '') self.console = mcrcon.MCRcon() self.alive = True def check(self): if platform.system() != 'Linux': self.error('Only supported on Linux.') return False try: self.connect() except (mcrcon.MCRconException, socket.error) as err: self.error('Error connecting.') self.error(repr(err)) return False return True def connect(self): self.console.connect(self.host, self.port, self.password) def reconnect(self): try: try: self.console.disconnect() except mcrcon.MCRconException: pass self.console.connect(self.host, self.port, self.password) self.alive = True except (mcrcon.MCRconException, socket.error) as err: self.error('Error connecting.') self.error(repr(err)) return False return True def is_alive(self): if (not self.alive) or \ self.console.socket.getsockopt(socket.IPPROTO_TCP, socket.TCP_INFO, 0) != 1: return self.reconnect() return True def _get_data(self): if not self.is_alive(): return None data = {} try: raw = self.console.command('tps') # The above command returns a string that looks like this: # '§6TPS from last 1m, 5m, 15m: §a19.99, §a19.99, §a19.99\n' # The values we care about are the three numbers after the : tmp = raw.split(':')[1].split(',') data['tps1'] = float(tmp[0].lstrip(u' §a*')) * PRECISION data['tps5'] = float(tmp[1].lstrip(u' §a*')) * PRECISION data['tps15'] = float(tmp[2].lstrip(u' §a*').rstrip()) * PRECISION except mcrcon.MCRconException: self.error('Unable to fetch TPS values.') except socket.error: self.error('Connection is dead.') self.alive = False return None except (TypeError, LookupError): self.error('Unable to process TPS values.') try: raw = self.console.command('list') # The above command returns a string that looks like this: # 'There are 0/20 players online:' # We care about the first number here. data['users'] = int(raw.split()[2].split('/')[0]) except mcrcon.MCRconException: self.error('Unable to fetch user counts.') except socket.error: self.error('Connection is dead.') self.alive = False return None except (TypeError, LookupError): self.error('Unable to process user counts.') return data