mem_checker/mem_checker/mem_checker.py

200 lines
5.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

"""
A script for monitoring available RAM and free disk space.
It uses the free and df utilites.
When the threshold is exceeded, it sends a post request to the api.
The request contains the following json format:
[
{
type_msg: str,
message: str
detail: str
}
]
:copyright: Sergey Vanyushkin <pi3c@yandex.ru>
:git: https://git.pi3c.ru/pi3c/mem_checker.git
:license: MIT
2024г.
"""
import json
import logging
import subprocess
import sys
import urllib.parse
import urllib.request
from abc import ABC, abstractmethod
from dataclasses import dataclass
from time import sleep
ENDPOINT_URL = "http://127.0.0.1:8000/user" # endpoint for send alerts
FREE_IN_MB = 500 # free: the value of the trigger in megabytes
FREE_IN_GB = 0.5 # free: the value of the trigger in gigabytes
DF_IN_PERCENT = 50 # df: the value of the trigger in percents
SLEEPTIME = 5 # main_loop sleep time per seconds
logger = logging.getLogger("mem_checker_logger")
logger.setLevel(logging.DEBUG)
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG)
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
console_handler.setFormatter(formatter)
# Добавляем обработчик в логгер
logger.addHandler(console_handler)
@dataclass()
class MessageDTO:
type_msg: str
message: str
detail: str
class Command(ABC):
@property
def command(self):
return self.__command
@command.setter
def command(self, cmd: list):
self.__command = cmd
@abstractmethod
def _stdout_parser(self, stdout: str) -> list[MessageDTO]:
raise NotImplementedError
@abstractmethod
def _filter_alerts(self, stat: list[MessageDTO]) -> list[MessageDTO]:
raise NotImplementedError
def _execute(self, command: list) -> tuple[str, str]:
try:
result = subprocess.run(
command,
capture_output=True,
check=True,
text=True,
)
except Exception as e:
return "", str(e)
return result.stdout, result.stderr
def get_alerts(self) -> list[MessageDTO]:
stdout, stderr = self._execute(command=self.command)
if stderr:
return [
MessageDTO(
type_msg="Error",
message="Free command execution error",
detail=stderr,
)
]
stat = self._stdout_parser(stdout=stdout)
return self._filter_alerts(stat)
class FreeMem(Command):
def __init__(
self,
) -> None:
self.command = ["free", "-h"]
def _filter_alerts(self, stat: list[MessageDTO]) -> list[MessageDTO]:
filtered_alerts = []
for obj in stat:
if obj.type_msg == "Error":
filtered_alerts.append(obj)
elif obj.type_msg == "Status":
val, unit = obj.detail.split()
if (float(val) < FREE_IN_MB and unit == "Mi") or (float(val) < FREE_IN_GB and unit == "Gi"):
obj.type_msg = "Alert"
filtered_alerts.append(obj)
return filtered_alerts
def _stdout_parser(self, stdout: str) -> list[MessageDTO]:
try:
memory = stdout.split()[12]
memory_units = memory[-2:]
memory = memory[:-2].replace(",", ".")
memory_value = float(memory) if "." in memory else int(memory)
except Exception as e:
logger.error(e)
return [
MessageDTO(
type_msg="ParserError",
message="Stdout parsing runtime error",
detail=str(e),
),
]
return [
MessageDTO(
type_msg="Status",
message="free mem available",
detail=f"{memory_value} {memory_units}",
)
]
class FreeHdd(Command):
def __init__(self) -> None:
self.command = ["df", "-h"]
def _filter_alerts(self, stat: list[MessageDTO]) -> list[MessageDTO]:
filtered_alerts = list()
for obj in stat:
persents = int(obj.detail.split()[4][:-1])
if DF_IN_PERCENT < persents:
obj.type_msg = "Alert"
filtered_alerts.append(obj)
return filtered_alerts
def _stdout_parser(self, stdout) -> list[MessageDTO]:
def row_to_dto(row: list):
return MessageDTO(type_msg="Status", message="hdd mem available", detail=" ".join(row))
stat = [row.split() for row in stdout.split("\n")]
stat = list(map(row_to_dto, stat[1:-1]))
return self._filter_alerts(stat)
def send_alert(alerts: list[MessageDTO]) -> None:
data = [{"type_msg": item.type_msg, "message": item.message, "detail": item.detail} for item in alerts]
json_data = json.dumps(data).encode("utf-8")
req = urllib.request.Request(
ENDPOINT_URL,
data=json_data,
headers={"Content-Type": "application/json"},
method="POST",
)
try:
with urllib.request.urlopen(req) as response:
response.read().decode("utf-8")
except Exception as e:
logger.error(e)
def main_loop() -> None:
free_mem = FreeMem()
hdd_mem = FreeHdd()
alerts: list[MessageDTO] = list()
alerts.extend(free_mem.get_alerts())
alerts.extend(hdd_mem.get_alerts())
if alerts:
send_alert(alerts=alerts)
if __name__ == "__main__":
if "--test-mode" in sys.argv:
while True:
main_loop()
sleep(SLEEPTIME)
main_loop()