1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184
|
# -*- coding: utf-8 -*-
# Description: spigotmc netdata python.d module
# Author: Austin S. Hemmelgarn (Ferroin)
# SPDX-License-Identifier: GPL-3.0-or-later
import platform
import re
import socket
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 most people won't care about second-by-second data.
update_every = 5
PRECISION = 100
COMMAND_TPS = 'tps'
COMMAND_LIST = 'list'
COMMAND_ONLINE = 'online'
ORDER = [
'tps',
'mem',
'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]
]
},
'mem': {
'options': [None, 'Minecraft Memory Usage', 'MiB', 'spigotmc', 'spigotmc.mem', 'line'],
'lines': [
['mem_used', 'used', 'absolute', 1, 1],
['mem_alloc', 'allocated', 'absolute', 1, 1],
['mem_max', 'max', 'absolute', 1, 1]
]
}
}
_TPS_REGEX = re.compile(
# Examples:
# §6TPS from last 1m, 5m, 15m: §a*20.0, §a*20.0, §a*20.0
# §6Current Memory Usage: §a936/65536 mb (Max: 65536 mb)
r'^.*: .*?' # Message lead-in
r'(\d{1,2}.\d+), .*?' # 1-minute TPS value
r'(\d{1,2}.\d+), .*?' # 5-minute TPS value
r'(\d{1,2}\.\d+).*?' # 15-minute TPS value
r'(\s.*?(\d+)\/(\d+).*?: (\d+).*)?', # Current Memory Usage / Total Memory (Max Memory)
re.MULTILINE
)
_LIST_REGEX = re.compile(
# Examples:
# There are 4 of a max 50 players online: player1, player2, player3, player4
# §6There are §c4§6 out of maximum §c50§6 players online.
# §6There are §c3§6/§c1§6 out of maximum §c50§6 players online.
# §6当前有 §c4§6 个玩家在线,最大在线人数为 §c50§6 个玩家.
# §c4§6 人のプレイヤーが接続中です。最大接続可能人数\:§c 50
r'[^§](\d+)(?:.*?(?=/).*?[^§](\d+))?', # Current user count.
re.X
)
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 self._get_data()
def connect(self):
self.console.connect(self.host, self.port, self.password)
def reconnect(self):
self.error('try reconnect.')
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 any(
[
not self.alive,
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(COMMAND_TPS)
match = _TPS_REGEX.match(raw)
if match:
data['tps1'] = int(float(match.group(1)) * PRECISION)
data['tps5'] = int(float(match.group(2)) * PRECISION)
data['tps15'] = int(float(match.group(3)) * PRECISION)
if match.group(4):
data['mem_used'] = int(match.group(5))
data['mem_alloc'] = int(match.group(6))
data['mem_max'] = int(match.group(7))
else:
self.error('Unable to process TPS values.')
if not raw:
self.error(
"'{0}' command returned no value, make sure you set correct password".format(COMMAND_TPS))
except mcrcon.MCRconException:
self.error('Unable to fetch TPS values.')
except socket.error:
self.error('Connection is dead.')
self.alive = False
return None
try:
raw = self.console.command(COMMAND_LIST)
match = _LIST_REGEX.search(raw)
if not match:
raw = self.console.command(COMMAND_ONLINE)
match = _LIST_REGEX.search(raw)
if match:
users = int(match.group(1))
hidden_users = match.group(2)
if hidden_users:
hidden_users = int(hidden_users)
else:
hidden_users = 0
data['users'] = users + hidden_users
else:
if not raw:
self.error("'{0}' and '{1}' commands returned no value, make sure you set correct password".format(
COMMAND_LIST, COMMAND_ONLINE))
self.error('Unable to process user counts.')
except mcrcon.MCRconException:
self.error('Unable to fetch user counts.')
except socket.error:
self.error('Connection is dead.')
self.alive = False
return None
return data
|