serverwatcher 5.8.dev5__py3-none-any.whl → 5.8.dev7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- serverwatcher/configclasses/config.py +1 -1
- serverwatcher/configclasses/messages.py +10 -10
- serverwatcher/defaultconfigs/config.yaml +1 -1
- serverwatcher/defaultconfigs/messages.yaml +1 -1
- serverwatcher/watcher.py +69 -21
- {serverwatcher-5.8.dev5.dist-info → serverwatcher-5.8.dev7.dist-info}/METADATA +1 -1
- serverwatcher-5.8.dev7.dist-info/RECORD +15 -0
- serverwatcher-5.8.dev5.dist-info/RECORD +0 -15
- {serverwatcher-5.8.dev5.dist-info → serverwatcher-5.8.dev7.dist-info}/WHEEL +0 -0
- {serverwatcher-5.8.dev5.dist-info → serverwatcher-5.8.dev7.dist-info}/licenses/LICENSE +0 -0
- {serverwatcher-5.8.dev5.dist-info → serverwatcher-5.8.dev7.dist-info}/top_level.txt +0 -0
|
@@ -6,6 +6,7 @@ def yaml_key(path: str, default=None):
|
|
|
6
6
|
@dataclass
|
|
7
7
|
class GlobalConfig:
|
|
8
8
|
watch_interval: int = yaml_key("watch_interval")
|
|
9
|
+
timezone: str = yaml_key("timezone")
|
|
9
10
|
|
|
10
11
|
panel_name: str = yaml_key("panel.name")
|
|
11
12
|
panel_url: str = yaml_key("panel.url")
|
|
@@ -25,7 +26,6 @@ class GlobalConfig:
|
|
|
25
26
|
enable_logging: bool = yaml_key("logger.enabled")
|
|
26
27
|
logger_name: str = yaml_key("logger.name")
|
|
27
28
|
log_path: str = yaml_key("logger.log_path")
|
|
28
|
-
timezone: str = yaml_key("logger.timezone")
|
|
29
29
|
|
|
30
30
|
console_backspaces: int = yaml_key("terminal.backspaces")
|
|
31
31
|
clear_terminal: bool = yaml_key("terminal.enable_clearing")
|
|
@@ -19,16 +19,16 @@ class MessagesConfig:
|
|
|
19
19
|
minute_1: str = yaml_key("broadcast_minutes.1")
|
|
20
20
|
|
|
21
21
|
# second messages
|
|
22
|
-
second_10: str = yaml_key("broadcast_seconds.10")
|
|
23
|
-
second_9:
|
|
24
|
-
second_8:
|
|
25
|
-
second_7:
|
|
26
|
-
second_6:
|
|
27
|
-
second_5:
|
|
28
|
-
second_4:
|
|
29
|
-
second_3:
|
|
30
|
-
second_2:
|
|
31
|
-
second_1:
|
|
22
|
+
second_10: str = yaml_key("broadcast_seconds.10", default="{prefix} Restart in 10 seconds!")
|
|
23
|
+
second_9: str = yaml_key("broadcast_seconds.9", default="{prefix} Restart in 9 seconds!")
|
|
24
|
+
second_8: str = yaml_key("broadcast_seconds.8", default="{prefix} Restart in 8 seconds!")
|
|
25
|
+
second_7: str = yaml_key("broadcast_seconds.7", default="{prefix} Restart in 7 seconds!")
|
|
26
|
+
second_6: str = yaml_key("broadcast_seconds.6", default="{prefix} Restart in 6 seconds!")
|
|
27
|
+
second_5: str = yaml_key("broadcast_seconds.5", default="{prefix} Restart in 5 seconds!")
|
|
28
|
+
second_4: str = yaml_key("broadcast_seconds.4", default="{prefix} Restart in 4 seconds!")
|
|
29
|
+
second_3: str = yaml_key("broadcast_seconds.3", default="{prefix} Restart in 3 seconds!")
|
|
30
|
+
second_2: str = yaml_key("broadcast_seconds.2", default="{prefix} Restart in 2 seconds!")
|
|
31
|
+
second_1: str = yaml_key("broadcast_seconds.1", default="{prefix} Restart in 1 second!")
|
|
32
32
|
|
|
33
33
|
# logging
|
|
34
34
|
startup: str = yaml_key("logging.startup")
|
|
@@ -46,7 +46,7 @@ reasons:
|
|
|
46
46
|
uptime: 'Uptime {uptime} exceeds {threshold}h'
|
|
47
47
|
tps: 'TPS {tps} is lower than {threshold}'
|
|
48
48
|
low_uptime: 'Uptime {uptime} is shorter than 30m'
|
|
49
|
-
players: 'There {verb} {count} {plural} online'
|
|
49
|
+
players: 'There {verb} {count} {plural} online' # produces: "There are 2 players online" or "There is 1 player online"
|
|
50
50
|
|
|
51
51
|
pro_restart_number: 'Pro-restart: '
|
|
52
52
|
anti_restart_number: 'Anti-restart:'
|
serverwatcher/watcher.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
# serverwatcher.py
|
|
2
1
|
import os
|
|
3
2
|
import time
|
|
4
3
|
import re
|
|
@@ -27,6 +26,7 @@ validate_all()
|
|
|
27
26
|
_T_EXPR = re.compile(r"{([^{}]+)}")
|
|
28
27
|
|
|
29
28
|
def t_eval(template: str, /, **ctx):
|
|
29
|
+
# evaluate inline expressions inside message templates
|
|
30
30
|
def repl(match):
|
|
31
31
|
expr = match.group(1).strip()
|
|
32
32
|
try:
|
|
@@ -37,7 +37,6 @@ def t_eval(template: str, /, **ctx):
|
|
|
37
37
|
|
|
38
38
|
class ServerWatcher:
|
|
39
39
|
def __init__(self):
|
|
40
|
-
|
|
41
40
|
self.config = loadConfig(
|
|
42
41
|
"config/config.yaml",
|
|
43
42
|
"/defaultconfigs/config.yaml",
|
|
@@ -79,10 +78,12 @@ class ServerWatcher:
|
|
|
79
78
|
tpsCommand=self.config.tps_command,
|
|
80
79
|
)
|
|
81
80
|
|
|
81
|
+
# formatted logger name
|
|
82
82
|
logger_name = self.config.logger_name.format(
|
|
83
83
|
server_name=self.config.server_name
|
|
84
84
|
)
|
|
85
85
|
|
|
86
|
+
# initialize message router
|
|
86
87
|
self.router = MessageRouter(
|
|
87
88
|
name=logger_name,
|
|
88
89
|
server=self.server,
|
|
@@ -91,44 +92,67 @@ class ServerWatcher:
|
|
|
91
92
|
console_backspaces=self.config.console_backspaces,
|
|
92
93
|
)
|
|
93
94
|
|
|
95
|
+
# timezone for scheduling
|
|
94
96
|
self.tz = ZoneInfo(self.config.timezone)
|
|
95
97
|
|
|
96
98
|
def fmt(self, template: str, **fmt):
|
|
99
|
+
# apply t-eval formatting to templates
|
|
97
100
|
return t_eval(template, self=self, **fmt)
|
|
101
|
+
|
|
102
|
+
def say(self, template, level="info", **fmt):
|
|
103
|
+
if not template:
|
|
104
|
+
return
|
|
105
|
+
self.router.say(
|
|
106
|
+
template,
|
|
107
|
+
level=level,
|
|
108
|
+
log=self.config.enable_logging,
|
|
109
|
+
**fmt
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
|
|
98
113
|
|
|
99
114
|
def shutdown(self):
|
|
100
|
-
self.
|
|
115
|
+
self.say(self.messages.shutdown)
|
|
101
116
|
raise SystemExit
|
|
102
117
|
|
|
103
118
|
def restart_and_wait(self):
|
|
119
|
+
# disable schedule if configured
|
|
104
120
|
if self.watcherconfig.schedule_control:
|
|
105
121
|
self.origin.disableSchedule(self.watcherconfig.restart_soon_id)
|
|
122
|
+
|
|
123
|
+
# send restart command
|
|
106
124
|
self.server.restart()
|
|
107
|
-
self.
|
|
125
|
+
self.say(self.messages.restart_action_sent)
|
|
108
126
|
time.sleep(self.watcherconfig.restart_wait_seconds)
|
|
109
127
|
|
|
110
|
-
|
|
128
|
+
# check server status
|
|
129
|
+
self.say(self.messages.status_check, level="warn")
|
|
111
130
|
alive = waitForOnline(
|
|
112
131
|
self.server,
|
|
113
132
|
timeout=self.watcherconfig.restart_timeout,
|
|
114
133
|
interval=self.watcherconfig.restart_online_interval,
|
|
115
134
|
)
|
|
116
135
|
|
|
136
|
+
# handle restart result
|
|
117
137
|
if alive:
|
|
118
|
-
self.
|
|
119
|
-
self.
|
|
138
|
+
self.say(self.messages.server_back_online)
|
|
139
|
+
self.say(self.messages.server_back_online_broadcast, broadcast=True)
|
|
120
140
|
else:
|
|
121
|
-
self.
|
|
141
|
+
self.say(self.messages.server_failed_restart, level="error")
|
|
122
142
|
|
|
123
143
|
def schedule_restart(self, minutes):
|
|
144
|
+
# compute scheduled restart time
|
|
124
145
|
info = snapSchedule(minimumMinutes=minutes)
|
|
125
146
|
scheduled = info["scheduled"]
|
|
126
147
|
|
|
148
|
+
# format local time
|
|
127
149
|
local_time = scheduled.astimezone(self.tz)
|
|
128
150
|
time_str = local_time.strftime("%I:%M %p")
|
|
129
151
|
|
|
152
|
+
# broadcast restart time
|
|
130
153
|
self.router.broadcast(self.fmt(self.messages.broadcast_restart_at, time=time_str))
|
|
131
154
|
|
|
155
|
+
# build minute callbacks
|
|
132
156
|
minute_callbacks = {
|
|
133
157
|
int(k.split("_")[1]): (
|
|
134
158
|
lambda msg=self.fmt(getattr(self.messages, k)):
|
|
@@ -138,6 +162,7 @@ class ServerWatcher:
|
|
|
138
162
|
if k.startswith("minute_")
|
|
139
163
|
}
|
|
140
164
|
|
|
165
|
+
# build second callbacks
|
|
141
166
|
second_callbacks = {
|
|
142
167
|
int(k.split("_")[1]): (
|
|
143
168
|
lambda msg=self.fmt(getattr(self.messages, k)):
|
|
@@ -147,6 +172,7 @@ class ServerWatcher:
|
|
|
147
172
|
if k.startswith("second_")
|
|
148
173
|
}
|
|
149
174
|
|
|
175
|
+
# run countdown events
|
|
150
176
|
runCountdownEvents(
|
|
151
177
|
target_time=scheduled,
|
|
152
178
|
minute_callbacks=minute_callbacks,
|
|
@@ -154,32 +180,40 @@ class ServerWatcher:
|
|
|
154
180
|
)
|
|
155
181
|
|
|
156
182
|
def evaluate(self):
|
|
157
|
-
|
|
183
|
+
# announce evaluation start
|
|
184
|
+
self.say(self.messages.startup)
|
|
158
185
|
|
|
186
|
+
# validate panel and server
|
|
159
187
|
if not validateAll(self.panel, self.server):
|
|
160
|
-
self.
|
|
188
|
+
self.say(self.messages.validation_fail, level="error")
|
|
161
189
|
self.shutdown()
|
|
162
190
|
|
|
191
|
+
# refresh server state
|
|
163
192
|
self.server.refresh()
|
|
164
193
|
snap = Snapshot(self.server, 2, True)
|
|
165
194
|
|
|
195
|
+
# scoring variables
|
|
166
196
|
pro = 0
|
|
167
197
|
anti = 0
|
|
168
198
|
restart_reasons = []
|
|
169
199
|
no_restart_reasons = []
|
|
170
200
|
|
|
201
|
+
# check schedule flag
|
|
171
202
|
if self.watcherconfig.schedule_control and self.server.getSchedule(self.watcherconfig.restart_soon_id)["is_active"]:
|
|
172
203
|
restart_reasons.append(self.messages.reason_restart_soon)
|
|
173
204
|
pro += self.watcherconfig.weight_restart_soon
|
|
174
205
|
|
|
206
|
+
# check RAM threshold
|
|
175
207
|
if snap.ram >= self.watcherconfig.threshold_ram:
|
|
176
208
|
restart_reasons.append(self.fmt(self.messages.reason_ram, ram=snap.ram, threshold=self.watcherconfig.threshold_ram))
|
|
177
209
|
pro += int(round(snap.ram, 0) - (self.watcherconfig.threshold_ram - 1))
|
|
178
210
|
|
|
211
|
+
# check CPU threshold
|
|
179
212
|
if snap.cpu >= self.watcherconfig.threshold_cpu:
|
|
180
213
|
restart_reasons.append(self.fmt(self.messages.reason_cpu, cpu=snap.cpu, threshold=self.watcherconfig.threshold_cpu))
|
|
181
214
|
pro += self.watcherconfig.weight_cpu
|
|
182
215
|
|
|
216
|
+
# check uptime threshold
|
|
183
217
|
if snap.uptime // 3600 >= self.watcherconfig.threshold_uptime:
|
|
184
218
|
restart_reasons.append(
|
|
185
219
|
self.fmt(self.messages.reason_uptime, uptime=snap.uptime_formatted,
|
|
@@ -187,55 +221,69 @@ class ServerWatcher:
|
|
|
187
221
|
)
|
|
188
222
|
pro += self.watcherconfig.weight_uptime
|
|
189
223
|
|
|
224
|
+
# check TPS threshold
|
|
190
225
|
if (snap.tps if snap.tps is not None else 20) <= self.watcherconfig.threshold_tps:
|
|
191
226
|
restart_reasons.append(self.fmt(self.messages.reason_tps, tps=snap.tps, threshold=self.watcherconfig.threshold_tps))
|
|
192
227
|
pro += self.watcherconfig.weight_tps
|
|
193
228
|
|
|
229
|
+
# check low uptime penalty
|
|
194
230
|
if snap.uptime // 60 < 30:
|
|
195
231
|
no_restart_reasons.append(self.fmt(self.messages.reason_low_uptime, uptime=snap.uptime_formatted))
|
|
196
232
|
anti += self.watcherconfig.weight_low_uptime
|
|
197
233
|
|
|
234
|
+
# check player count penalty
|
|
198
235
|
if snap.players > 0:
|
|
199
236
|
verb = "are" if snap.players != 1 else "is"
|
|
200
237
|
plural = "players" if snap.players != 1 else "player"
|
|
201
238
|
no_restart_reasons.append(self.fmt(self.messages.reason_players, verb=verb, count=snap.players, plural=plural))
|
|
202
239
|
anti += snap.players * self.watcherconfig.weight_per_player
|
|
203
240
|
|
|
241
|
+
# output pro-restart reasons
|
|
204
242
|
if restart_reasons:
|
|
205
|
-
self.
|
|
243
|
+
self.say(self.messages.pro_restart_splash, level="warn")
|
|
206
244
|
for r in restart_reasons:
|
|
207
|
-
self.
|
|
245
|
+
self.say(f"{self.messages.bullet} {r}", level="warn")
|
|
208
246
|
|
|
247
|
+
# output anti-restart reasons
|
|
209
248
|
if no_restart_reasons:
|
|
210
|
-
self.
|
|
249
|
+
self.say(self.messages.anti_restart_splash, level="warn")
|
|
211
250
|
for r in no_restart_reasons:
|
|
212
|
-
self.
|
|
251
|
+
self.say(f"{self.messages.bullet} {r}", level="warn")
|
|
213
252
|
|
|
214
|
-
|
|
215
|
-
self.
|
|
253
|
+
# output scores
|
|
254
|
+
self.say(f"{self.messages.pro_restart_number} {pro}", level="warn")
|
|
255
|
+
self.say(f"{self.messages.anti_restart_number} {anti}", level="warn")
|
|
216
256
|
|
|
257
|
+
# compute gap
|
|
217
258
|
gap = abs(pro - anti)
|
|
218
259
|
|
|
260
|
+
# no restart case
|
|
219
261
|
if pro == 0:
|
|
220
|
-
self.
|
|
262
|
+
self.say(self.messages.no_restart)
|
|
221
263
|
return
|
|
222
264
|
|
|
265
|
+
# immediate restart case
|
|
223
266
|
if pro > anti and snap.players == 0:
|
|
224
|
-
self.
|
|
267
|
+
self.say(self.messages.immediate_restart)
|
|
225
268
|
self.restart_and_wait()
|
|
226
269
|
return
|
|
227
270
|
|
|
228
|
-
|
|
271
|
+
# scheduled restart case
|
|
272
|
+
self.say(self.messages.scheduled)
|
|
229
273
|
|
|
274
|
+
# choose schedule window
|
|
230
275
|
if gap <= 2:
|
|
231
|
-
self.
|
|
276
|
+
self.say(self.messages.gap_low, level="warn", gap=gap)
|
|
232
277
|
self.schedule_restart(self.watcherconfig.low_gap_minutes)
|
|
233
278
|
else:
|
|
234
|
-
self.
|
|
279
|
+
self.say(self.messages.gap_high, level="warn", gap=gap)
|
|
235
280
|
self.schedule_restart(self.watcherconfig.high_gap_minutes)
|
|
236
281
|
|
|
282
|
+
# perform restart
|
|
237
283
|
self.restart_and_wait()
|
|
238
284
|
|
|
285
|
+
|
|
286
|
+
# main loop
|
|
239
287
|
def run(self):
|
|
240
288
|
if self.config.clear_terminal:
|
|
241
289
|
clearTerminal()
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
serverwatcher/__init__.py,sha256=Q_afN0XU29n48d6Gf0-LT2lLo3RiTWlHRT6rC_fYbjw,486
|
|
2
|
+
serverwatcher/validator.py,sha256=W1-RX8UQWeO2DKEe3vC1_U6xISe_WnAVXHYT0pf4uWc,4753
|
|
3
|
+
serverwatcher/watcher.py,sha256=t9NJ87eTQn0UUumkjudkwA9Z56G4RGciEpfqddtIYbw,9781
|
|
4
|
+
serverwatcher/configclasses/__init__.py,sha256=YS0KhxtAJGdMcaiFstqTRNxIPLnR7s9v3Tkt5h2uKE4,182
|
|
5
|
+
serverwatcher/configclasses/config.py,sha256=g5QF_QxyQxcKRYagBCoozBYWT70ibpP684bpFg74SGM,1103
|
|
6
|
+
serverwatcher/configclasses/messages.py,sha256=g3Ko1R7I3zTiAm7J58eYnNHEIncRuLWelmLudhW9N6Q,3208
|
|
7
|
+
serverwatcher/configclasses/watcher.py,sha256=urQvRr8b7OjK0sm5eovSg9S3vo0LWZxE0cqpwX6NXbE,1230
|
|
8
|
+
serverwatcher/defaultconfigs/config.yaml,sha256=7jAFWDkC6S68z6co0A_9H8vPYPDKDvDeopaaDHUX_Ks,459
|
|
9
|
+
serverwatcher/defaultconfigs/messages.yaml,sha256=OthW7v-KzORPOUQuhXVjG6n82pW4TM317f4dGLQPDvA,2169
|
|
10
|
+
serverwatcher/defaultconfigs/watcher.yaml,sha256=1XUr7p5qRiaZkSccULVgEAGvHMFOM2IPNBRxdnG86NA,380
|
|
11
|
+
serverwatcher-5.8.dev7.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
12
|
+
serverwatcher-5.8.dev7.dist-info/METADATA,sha256=VV_Yb8GPYBQQLNeW7fVRA-XObIMnVOAj2ro3OjZ-Yvc,1686
|
|
13
|
+
serverwatcher-5.8.dev7.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
14
|
+
serverwatcher-5.8.dev7.dist-info/top_level.txt,sha256=8DJAf8WmmglgtZHkp8aoTriRJ2YPcS4F3DfOIkytASo,14
|
|
15
|
+
serverwatcher-5.8.dev7.dist-info/RECORD,,
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
serverwatcher/__init__.py,sha256=Q_afN0XU29n48d6Gf0-LT2lLo3RiTWlHRT6rC_fYbjw,486
|
|
2
|
-
serverwatcher/validator.py,sha256=W1-RX8UQWeO2DKEe3vC1_U6xISe_WnAVXHYT0pf4uWc,4753
|
|
3
|
-
serverwatcher/watcher.py,sha256=B6Y23pbqqUPtl6a1j1c6XwGJ-EFRv91MoC8YcR6kUUw,8502
|
|
4
|
-
serverwatcher/configclasses/__init__.py,sha256=YS0KhxtAJGdMcaiFstqTRNxIPLnR7s9v3Tkt5h2uKE4,182
|
|
5
|
-
serverwatcher/configclasses/config.py,sha256=cnrRYaJ7W-SuUuVZI2tFgGWtmlLkMNNy51HxTud4tB8,1110
|
|
6
|
-
serverwatcher/configclasses/messages.py,sha256=JZawv8_Kn9rns_bbuw0cuk5DhwinlueCao63NJIttXo,2770
|
|
7
|
-
serverwatcher/configclasses/watcher.py,sha256=urQvRr8b7OjK0sm5eovSg9S3vo0LWZxE0cqpwX6NXbE,1230
|
|
8
|
-
serverwatcher/defaultconfigs/config.yaml,sha256=lq-eTwAG4QQKIJKniMeEj9eD_DG06zMNf-C7Scqa9KI,461
|
|
9
|
-
serverwatcher/defaultconfigs/messages.yaml,sha256=Zft8a1XrZj2YcdVg1njQM55RZTCZdDWzaxi2raIPeOo,2090
|
|
10
|
-
serverwatcher/defaultconfigs/watcher.yaml,sha256=1XUr7p5qRiaZkSccULVgEAGvHMFOM2IPNBRxdnG86NA,380
|
|
11
|
-
serverwatcher-5.8.dev5.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
12
|
-
serverwatcher-5.8.dev5.dist-info/METADATA,sha256=SqQIIAWImbrmG_IaZMY4QOG6dCap7o6SV2gjNjWx4Vw,1686
|
|
13
|
-
serverwatcher-5.8.dev5.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
14
|
-
serverwatcher-5.8.dev5.dist-info/top_level.txt,sha256=8DJAf8WmmglgtZHkp8aoTriRJ2YPcS4F3DfOIkytASo,14
|
|
15
|
-
serverwatcher-5.8.dev5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|