spigotmc.chart.py 6.2 KB

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