Add basic player restart alerts
This commit is contained in:
parent
f1e04c3d8a
commit
7dc0facf73
2 changed files with 97 additions and 1 deletions
93
alerts/player.py
Normal file
93
alerts/player.py
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
# Any alerts produced by the player.py instances.
|
||||||
|
import json
|
||||||
|
from typing import Any, Dict, List, Optional
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from helpers.os_environment import resolve_external_file_path
|
||||||
|
from helpers.alert_manager import AlertProvider
|
||||||
|
from baps_types.alert import CRITICAL, WARNING, Alert
|
||||||
|
|
||||||
|
MODULE = "Player" # This should match the log file, so the UI will link to the logs page.
|
||||||
|
|
||||||
|
class PlayerAlertProvider(AlertProvider):
|
||||||
|
|
||||||
|
_server_state: Dict[str, Any]
|
||||||
|
_states: List[Optional[Dict[str,Any]]] = []
|
||||||
|
_player_count: int
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
# Player count only changes after server restart, may as well just load this once.
|
||||||
|
with open(resolve_external_file_path("state/BAPSicleServer.json")) as file:
|
||||||
|
self._server_state = json.loads(file.read())
|
||||||
|
|
||||||
|
self._player_count = int(self._server_state["num_channels"])
|
||||||
|
self._states = [None] * self._player_count
|
||||||
|
|
||||||
|
# To simplify monitoring (and allow detection of things going super weird), we are going to read from the state file to work out the alerts.
|
||||||
|
def get_alerts(self):
|
||||||
|
for channel in range(self._player_count):
|
||||||
|
with open(resolve_external_file_path("state/Player{}.json".format(channel))) as file:
|
||||||
|
self._states[channel] = json.loads(file.read())
|
||||||
|
|
||||||
|
funcs = [self._channel_count, self._initialised, self._start_time]
|
||||||
|
|
||||||
|
alerts: List[Alert] = []
|
||||||
|
|
||||||
|
for func in funcs:
|
||||||
|
func_alerts = func()
|
||||||
|
if func_alerts:
|
||||||
|
alerts.extend(func_alerts)
|
||||||
|
|
||||||
|
return alerts
|
||||||
|
|
||||||
|
def _channel_count(self):
|
||||||
|
if self._player_count <= 0:
|
||||||
|
return [Alert({
|
||||||
|
"start_time": -1, # Now
|
||||||
|
"id": "no_channels",
|
||||||
|
"title": "There are no players configured.",
|
||||||
|
"description": "The number of channels configured is {}. Please set to at least 1 on the 'Server Config' page.".format(self._player_count),
|
||||||
|
"module": MODULE+"Handler",
|
||||||
|
"severity": CRITICAL
|
||||||
|
})]
|
||||||
|
|
||||||
|
def _initialised(self):
|
||||||
|
alerts: List[Alert] = []
|
||||||
|
for channel in range(self._player_count):
|
||||||
|
if self._states[channel] and not self._states[channel]["initialised"]:
|
||||||
|
alerts.append(Alert({
|
||||||
|
"start_time": -1, # Now
|
||||||
|
"id": "player_{}_not_initialised".format(channel),
|
||||||
|
"title": "Player {} is not initialised.".format(channel),
|
||||||
|
"description": "This typically means the player channel was not able find the configured sound output on the system. Please check the 'Player Config' and Player logs to determine the cause.",
|
||||||
|
"module": MODULE+str(channel),
|
||||||
|
"severity": CRITICAL
|
||||||
|
}))
|
||||||
|
return alerts
|
||||||
|
|
||||||
|
def _start_time(self):
|
||||||
|
server_start_time = self._server_state["start_time"]
|
||||||
|
server_start_time = datetime.fromtimestamp(server_start_time)
|
||||||
|
delta = timedelta(
|
||||||
|
seconds=30,
|
||||||
|
)
|
||||||
|
|
||||||
|
alerts: List[Alert] = []
|
||||||
|
for channel in range(self._player_count):
|
||||||
|
start_time = self._states[channel]["start_time"]
|
||||||
|
start_time = datetime.fromtimestamp(start_time)
|
||||||
|
if (start_time > server_start_time + delta):
|
||||||
|
alerts.append(Alert({
|
||||||
|
"start_time": -1,
|
||||||
|
"id": "player_{}_restarted".format(channel),
|
||||||
|
"title": "Player {} restarted after the server started.".format(channel),
|
||||||
|
"description":
|
||||||
|
"""Player {} last restarted at {}, after the server first started at {}, suggesting a failure.
|
||||||
|
|
||||||
|
This likely means there was an unhandled exception in the player code, causing the server to restart the player.
|
||||||
|
|
||||||
|
Please check player logs to investigate the cause. Please restart the server to clear this warning."""
|
||||||
|
.format(channel, str(start_time).rsplit(".",1)[0], str(server_start_time).rsplit(".",1)[0]),
|
||||||
|
"module": MODULE+str(channel),
|
||||||
|
"severity": WARNING
|
||||||
|
}))
|
||||||
|
return alerts
|
|
@ -35,6 +35,7 @@ from pygame import mixer
|
||||||
from mutagen.mp3 import MP3
|
from mutagen.mp3 import MP3
|
||||||
from syncer import sync
|
from syncer import sync
|
||||||
from threading import Timer
|
from threading import Timer
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
from helpers.normalisation import get_normalised_filename_if_available
|
from helpers.normalisation import get_normalised_filename_if_available
|
||||||
from helpers.myradio_api import MyRadioAPI
|
from helpers.myradio_api import MyRadioAPI
|
||||||
|
@ -969,7 +970,7 @@ class Player:
|
||||||
self.out_q = out_q
|
self.out_q = out_q
|
||||||
|
|
||||||
self.logger = LoggingManager(
|
self.logger = LoggingManager(
|
||||||
"Player" + str(channel), debug=package.build_beta)
|
"Player" + str(channel), debug=package.BETA)
|
||||||
|
|
||||||
self.api = MyRadioAPI(self.logger, server_state)
|
self.api = MyRadioAPI(self.logger, server_state)
|
||||||
|
|
||||||
|
@ -980,6 +981,8 @@ class Player:
|
||||||
self.__rate_limited_params,
|
self.__rate_limited_params,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.state.update("start_time", datetime.now().timestamp())
|
||||||
|
|
||||||
self.state.add_callback(self._send_status)
|
self.state.add_callback(self._send_status)
|
||||||
|
|
||||||
self.state.update("channel", channel)
|
self.state.update("channel", channel)
|
||||||
|
|
Loading…
Reference in a new issue