From 3c8d93debc157c3de5c6dfd5ce352e866ea3bb5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20K=C3=B6rner?= Date: Sun, 26 Apr 2020 23:18:01 +0200 Subject: [PATCH] Clean up code. Essential messaging function left. --- README.rst | 45 ---- discord_notifier_bot/bot.py | 219 +-------------- discord_notifier_bot/cli.py | 88 +----- discord_notifier_bot/sysinfo.py | 461 -------------------------------- setup.py | 5 +- 5 files changed, 13 insertions(+), 805 deletions(-) delete mode 100644 discord_notifier_bot/sysinfo.py diff --git a/README.rst b/README.rst index 18a0b8f..6a955f4 100644 --- a/README.rst +++ b/README.rst @@ -35,20 +35,12 @@ It registers the following commands: * ``dbot-run`` - main CLI entry-point * ``dbot-message`` - (short-hand) to send a message, or even pipe `-` message contents * ``dbot-file`` - (short-hand) to send a file with an message -* ``dbot-info`` - (short-hand) to send a message with system information - (*extra dependencies have to be installed!*) -* ``dbot-observe`` - a blocking script, that runs periodic system checks and notifies about shortages - (*requires extra dependencies to be installed*) Requirements ------------ * Python >= 3.6 (*see badges above*) * `discord.py `_ -* Extra: - - * ``cpu``: `psutil `_ - * ``gpu``: `GPUtil `_ Installation ------------ @@ -59,13 +51,6 @@ Installation Optionally, install it locally with ``--user``. -For system info messages using ``dbot-info`` or ``dbot-run info [...]``, you have to install extra dependencies. -You can choose between cpu (cpu + disk information) and gpu (``nvidia-smi`` information): - -.. code-block:: bash - - python3 -m pip install discord-notifier-bot[cpu,gpu] - Configuration ------------- @@ -149,36 +134,6 @@ You may also run the bot with the python module notation. But it will only run t python -m discord_notifier_bot [...] -System Observer Bot -~~~~~~~~~~~~~~~~~~~ - -As of version **0.2.***, I have included some basic system observation code. -Besides the ``dbot-info`` command that sends a summary about system information to a Discord channel, -an *observation service* with ``dbot-observe`` is included. -The command runs a looping Discord task that checks every **5 min** some predefined system conditions, -and sends a notification if a ``badness`` value is over a threshold. -This ``badness`` value serves to either immediatly notify a channel if a system resource is exhausted or after some repeated limit exceedances. - -The code (checks and limits) can be found in `discord_notifier_bot.sysinfo `_. -The current limits are some less-than educated guesses, and are subject to change. -Dynamic configuration is currently not an main issue, so users may need to clone the repo, change values and install the python package from source: - -.. code-block:: bash - - git clone https://github.com/Querela/discord-notifier-bot.git - cd discord-notifier-bot/ - # [do the modifications in discord_notifier_bot/sysinfo.py] - python3 -m pip install --user --upgrade --editable .[cpu,gpu] - -The system information gathering requires the extra dependencies to be installed, at least ``cpu``, optionally ``gpu``. - -I suggest that you provide a different Discord channel for those notifications and create an extra ``.dbot-observer.conf`` configuration file that can then be used like this: - -.. code-block:: bash - - dbot-observe [-d] -c ~/.dbot-observer.conf - - Embedded in other scripts ~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/discord_notifier_bot/bot.py b/discord_notifier_bot/bot.py index 431f542..1c4376a 100644 --- a/discord_notifier_bot/bot.py +++ b/discord_notifier_bot/bot.py @@ -1,23 +1,12 @@ -import datetime import logging import os -from collections import defaultdict import discord -from discord.ext import commands, tasks -from discord_notifier_bot.sysinfo import get_info_message -from discord_notifier_bot.sysinfo import get_local_machine_name -from discord_notifier_bot.sysinfo import ( - get_cpu_info, - get_disk_info, - get_gpu_info, -) -from discord_notifier_bot.sysinfo import make_observable_limits -from discord_notifier_bot.sysinfo import NotifyBadCounterManager LOGGER = logging.getLogger(__name__) + # --------------------------------------------------------------------------- @@ -49,10 +38,12 @@ async def do_work(self): class SendSingleFileMessageClient(AbstractSingleActionClient): - def __init__(self, channel_id, file2send, message=None, *args, **kwargs): + def __init__(self, channel_id, file2send, *args, message=None, **kwargs): super().__init__(*args, **kwargs) self.channel_id = channel_id self.file2send = file2send + if message is None: + message = "" self.message = message async def do_work(self): @@ -82,205 +73,3 @@ def send_file(token, channel_id, message, filename): # --------------------------------------------------------------------------- - - -def make_sysinfo_embed(): - embed = discord.Embed(title=f"System Status of `{get_local_machine_name()}`") - # embed.set_thumbnail(url="") # TODO: add "private" logo (maybe as an config option ...) - embed.add_field( - name="System information", value=get_cpu_info() or "N/A", inline=False - ) - embed.add_field( - name="Disk information", value=get_disk_info() or "N/A", inline=False - ) - embed.add_field(name="GPU information", value=get_gpu_info() or "N/A", inline=False) - embed.set_footer(text=f"Date: {datetime.datetime.now()}") - - return embed - - -# --------------------------------------------------------------------------- - - -class SystemResourceObserverCog(commands.Cog, name="System Resource Observer"): - def __init__(self, bot, channel_id): - self.bot = bot - self.channel_id = channel_id - self.local_machine_name = get_local_machine_name() - - self.limits = dict() - self.bad_checker = NotifyBadCounterManager() - self.stats = defaultdict(int) - - self.init_limits() - - def init_limits(self): - # TODO: pack them in an optional file (like Flask configs) and try to load else nothing. - self.limits.update(make_observable_limits()) - - def reset_notifications(self): - self.bad_checker.reset() - - @tasks.loop(minutes=5.0) - async def observe_system(self): - LOGGER.debug("Running observe system task loop ...") - - async with self.bot.get_channel(self.channel_id).typing(): - # perform checks - for name, limit in self.limits.items(): - try: - await self.run_single_check(name, limit) - except Exception as ex: - LOGGER.debug( - f"Failed to evaulate check: {limit.name}, reason: {ex}" - ) - - self.stats["num_checks"] += 1 - - async def run_single_check(self, name, limit): - LOGGER.debug(f"Running check: {limit.name}") - - cur_value = limit.fn_retrieve() - ok = limit.fn_check(cur_value, limit.threshold) - - if not ok: - # check of limit was "bad", now check if we have to notify someone - self.stats["num_limits_reached"] += 1 - self.stats[f"num_limits_reached:{name}:{limit.name}"] += 1 - - # increase badness - self.bad_checker.increase_counter(name, limit) - if self.bad_checker.should_notify(name, limit): - # check if already notified (that limit reached) - # even if shortly recovered but not completely, e. g. 3->2->3 >= 3 (thres) <= 0 (not completely reset) - await self.send( - limit.message.format(cur_value=cur_value, threshold=limit.threshold) - + f" `@{self.local_machine_name}`" - ) - self.bad_checker.mark_notified(name) - self.stats["num_limits_notified"] += 1 - else: - if self.bad_checker.decrease_counter(name, limit): - # get one-time True if changed from non-normal to normal - await self.send( - f"*{limit.name} has recovered*" f" `@{self.local_machine_name}`" - ) - self.stats["num_normal_notified"] += 1 - - @observe_system.before_loop - async def before_observe_start(self): - LOGGER.debug("Wait for observer bot to be ready ...") - await self.bot.wait_until_ready() - - async def send(self, message): - # TODO: send to default channel? - channel = self.bot.get_channel(self.channel_id) - await channel.send(message) - - def cog_unload(self): - self.observe_system.cancel() # pylint: disable=no-member - - @commands.command(name="observer-start") - async def start(self, ctx): - """Starts the background system observer loop.""" - # NOTE: check for is_running() only added in version 1.4.0 - if self.observe_system.get_task() is None: # pylint: disable=no-member - self.observe_system.start() # pylint: disable=no-member - await ctx.send("Observer started") - else: - self.observe_system.restart() # pylint: disable=no-member - await ctx.send("Observer restarted") - - @commands.command(name="observer-stop") - async def stop(self, ctx): - """Stops the background system observer.""" - self.observe_system.cancel() # pylint: disable=no-member - self.reset_notifications() - await ctx.send("Observer stopped") - - @commands.command(name="observer-status") - async def status(self, ctx): - """Displays statistics about notifications etc.""" - - if not self.stats: - await ctx.send(f"N/A [`{self.local_machine_name}`] [`not-started`]") - return - - len_keys = max(len(k) for k in self.stats.keys()) - len_vals = max( - len(str(v)) - for v in self.stats.values() - if isinstance(v, (int, float, bool)) - ) - - try: - # pylint: disable=no-member - next_time = self.observe_system.next_iteration - datetime.datetime.now( - datetime.timezone.utc - ) - # pylint: enable=no-member - except TypeError: - # if stopped, then ``next_iteration`` is None - next_time = "?" - - message = "".join( - [ - f"**Observer status for** `{self.local_machine_name}`", - f""" [`{"running" if self.observe_system.next_iteration is not None else "stopped"}`]""", # pylint: disable=no-member - "\n```\n", - "\n".join( - [f"{k:<{len_keys}} {v:>{len_vals}}" for k, v in self.stats.items()] - ), - "\n```", - f"\nNext check in `{next_time}`", - ] - ) - - await ctx.send(message) - - -def run_observer(token, channel_id): - observer_bot = commands.Bot(command_prefix=".") - - @observer_bot.event - async def on_ready(): # pylint: disable=unused-variable - LOGGER.info(f"Logged on as {observer_bot.user}") - LOGGER.debug(f"name: {observer_bot.user.name}, id: {observer_bot.user.id}") - - if channel_id is not None: - channel = observer_bot.get_channel(channel_id) - LOGGER.info(f"Channel: {channel} {type(channel)} {repr(channel)}") - await channel.send( - f"Running observer bot on `{get_local_machine_name()}`...\n" - f"Type `{observer_bot.command_prefix}help` to display available commands." - ) - - await observer_bot.change_presence(status=discord.Status.idle) - - # TODO: maybe start observe_system task here (if required?) - - @observer_bot.event - async def on_disconnect(): # pylint: disable=unused-variable - LOGGER.warning(f"Bot {observer_bot.user} disconnected!") - - @observer_bot.command() - async def ping(ctx): # pylint: disable=unused-variable - """Standard Ping-Pong latency/is-alive test.""" - await ctx.send(f"Pong (latency: {observer_bot.latency * 1000:.1f} ms)") - - @observer_bot.command() - async def info(ctx): # pylint: disable=unused-variable - """Query local system information and send it back.""" - # message = get_info_message() - # await ctx.send(message) - embed = make_sysinfo_embed() - await ctx.send(embed=embed) - - observer_bot.add_cog(SystemResourceObserverCog(observer_bot, channel_id)) - - LOGGER.info("Start observer bot ...") - observer_bot.run(token) - LOGGER.info("Quit observer bot.") - - -# --------------------------------------------------------------------------- diff --git a/discord_notifier_bot/cli.py b/discord_notifier_bot/cli.py index 2bf5ec9..19f97e7 100644 --- a/discord_notifier_bot/cli.py +++ b/discord_notifier_bot/cli.py @@ -5,15 +5,13 @@ import pathlib import sys -from discord_notifier_bot.bot import send_message as bot_send_message +from discord_notifier_bot.bot import send_message from discord_notifier_bot.bot import send_file as bot_send_file -from discord_notifier_bot.bot import run_observer -from discord_notifier_bot.sysinfo import has_extra_cpu -from discord_notifier_bot.sysinfo import has_extra_gpu -from discord_notifier_bot.sysinfo import get_info_message + LOGGER = logging.getLogger(__name__) + # --------------------------------------------------------------------------- @@ -58,7 +56,7 @@ def load_config_file(filename): } except KeyError as ex: LOGGER.error(f"Missing configuration key! >>{ex.args[0]}<<") - except: + except: # pylint: disable=bare-except LOGGER.exception("Loading configuration failed!") return None @@ -88,11 +86,6 @@ def load_config(filename=None, **kwargs): # --------------------------------------------------------------------------- -def send_message(bot_token, channel_id, message): - LOGGER.info(f"Send message: {message} ...") - bot_send_message(bot_token, channel_id, message) - - def send_file(bot_token, channel_id, message, filename): if not os.path.isfile(filename): raise Exception(f"filename '{filename}' is not a file!") @@ -108,8 +101,6 @@ def parse_args(args=None): parser = argparse.ArgumentParser() actions = ("message", "file") - if has_extra_cpu() or has_extra_gpu(): - actions += ("info",) parser.add_argument("action", choices=actions, help="Bot action") parser.add_argument("message", help="Message to send") @@ -172,15 +163,10 @@ def main(args=None): message = f"```{args.type}\n{message}\n```" if args.action == "message": + LOGGER.info(f"Send message: {message} ...") send_message(configs["token"], configs["channel"], message) elif args.action == "file": send_file(configs["token"], configs["channel"], message, args.file) - elif args.action == "info": - # gather cpu/gpu infos and send message - if message: - message += "\n\n" - message += get_info_message() - send_message(configs["token"], configs["channel"], message) LOGGER.info("Done.") @@ -217,8 +203,9 @@ def main_message(): LOGGER.info(f"Wrap message in markdown, type={args.type}") message = f"```{args.type}\n{message}\n```" + LOGGER.info(f"Send message: {message} ...") send_message(configs["token"], configs["channel"], message) - except: + except: # pylint: disable=bare-except sys.exit(1) @@ -236,66 +223,7 @@ def main_file(): LOGGER.debug(f"Run bot with configs: {configs}") send_file(configs["token"], configs["channel"], args.message, args.file) - except: - sys.exit(1) - - -def main_info(): - try: - parser = argparse.ArgumentParser() - parser.add_argument( - "-c", "--config", type=str, default=None, help="Config file" - ) - args = parser.parse_args() - - configs = load_config(filename=args.config) - LOGGER.debug(f"Run bot with configs: {configs}") - - if not has_extra_cpu() and not has_extra_gpu(): - import warnings - - warnings.warn( - "discord-notifier-bot has no extra dependencies installed to support the 'info' function!" - ) - sys.exit(1) - - message = get_info_message() - send_message(configs["token"], configs["channel"], message) - except: - sys.exit(1) - - -def main_observe(): - try: - parser = argparse.ArgumentParser() - parser.add_argument( - "-c", "--config", type=str, default=None, help="Config file" - ) - parser.add_argument( - "-d", "--debug", action="store_true", help="Enable debug logging" - ) - args = parser.parse_args() - - setup_logging(args.debug) - - configs = load_config(filename=args.config) - LOGGER.debug(f"Run bot with configs: {configs}") - - if not has_extra_cpu(): - raise Exception( - "Missing extra dependencies for this command! Please install psutil!" - ) - - if not has_extra_gpu(): - import warnings - - warnings.warn( - "discord-notifier-bot has no extra dependencies installed to support the 'info' function!" - ) - sys.exit(1) - - run_observer(configs["token"], configs["channel"]) - except: + except: # pylint: disable=bare-except sys.exit(1) diff --git a/discord_notifier_bot/sysinfo.py b/discord_notifier_bot/sysinfo.py deleted file mode 100644 index 1bfd36e..0000000 --- a/discord_notifier_bot/sysinfo.py +++ /dev/null @@ -1,461 +0,0 @@ -import datetime -import os -import time -from collections import defaultdict, namedtuple -from datetime import timedelta -from functools import lru_cache - -# --------------------------------------------------------------------------- - - -@lru_cache(maxsize=1) -def has_extra_cpu(): - try: - import psutil - except ImportError: - return False - return True - - -@lru_cache(maxsize=1) -def has_extra_gpu(): - try: - import GPUtil - except ImportError: - return False - return True - - -def get_cpu_info(): - if not has_extra_cpu(): - return None - - import psutil - - meminfo = psutil.virtual_memory() - GB_div = 1024 ** 3 - - info = ( - "```\n" - + "\n".join( - [ - f"Uptime: {timedelta(seconds=int(time.time() - psutil.boot_time()))}", - f"CPUs: {psutil.cpu_count()}", - f"RAM: {meminfo.total / GB_div:.1f} GB", - "", - "Load: 1min: {0[0]:.1f}%, 5min: {0[1]:.1f}%, 15min: {0[2]:.1f}%".format( - [x / psutil.cpu_count() * 100 for x in psutil.getloadavg()] - ), - f"Memory: {(meminfo.used / meminfo.total) * 100:.1f}% [used: {meminfo.used / GB_div:.1f} / {meminfo.total / GB_div:.1f} GB] [available: {meminfo.available / GB_div:.1f} GB]", - ] - ) - + "\n```" - ) - - return info - - -def get_disk_info(): - if not has_extra_cpu(): - return None - - import psutil - from psutil._common import bytes2human - - info = "" - - disks = [ - disk - for disk in psutil.disk_partitions(all=False) - if "loop" not in disk.device and not disk.mountpoint.startswith("/boot") - ] - - header = ("Device", "Mount", "Use", "Total", "Used", "Free") - rows = list() - for disk in disks: - usage = psutil.disk_usage(disk.mountpoint) - rows.append( - ( - disk.device, - disk.mountpoint, - f"{usage.percent:.1f} %", - bytes2human(usage.total), - bytes2human(usage.used), - bytes2human(usage.free), - # disk.fstype, - ) - ) - - lengths = [ - max(len(row[field_idx]) for row in [header] + rows) - for field_idx in range(len(rows[0])) - ] - - info = ( - "```\n" - + "\n".join( - # header - [ - # "| " + - " | ".join( - [ - f"{field:{field_len}s}" - for field, field_len in zip(header, lengths) - ] - ) - # + " |" - ] - # separator - + [ - # "| " + - " | ".join(["-" * field_len for field_len in lengths]) - # + " |" - ] - # rows - + [ - # "| " + - " | ".join( - # text fields - [ - f"{field:<{field_len}s}" - for field, field_len in list(zip(row, lengths))[:2] - ] - # values/number - + [ - f"{field:>{field_len}s}" - for field, field_len in list(zip(row, lengths))[2:] - ] - ) - # + " |" - for row in rows - ] - ) - + "\n```" - ) - - return info - - -def get_gpu_info(): - if not has_extra_gpu(): - return None - - import GPUtil - - rows = list() - fields = ["ID", "Util", "Mem", "Temp", "Memory (Used)"] # , "Name"] - rows.append(fields) - - for gpu in GPUtil.getGPUs(): - fields = [ - f"{gpu.id}", - f"{gpu.load * 100:.0f} %", - f"{gpu.memoryUtil * 100:.1f} %", - f"{gpu.temperature:.1f} °C", - f"{int(gpu.memoryUsed)} / {int(gpu.memoryTotal)} MB", - # f"{gpu.name}", - ] - rows.append(fields) - - lengths = [ - max(len(row[field_idx]) for row in rows) for field_idx in range(len(rows[0])) - ] - - info = ( - "```\n" - + "\n".join( - # header - [ - # "| " + - " | ".join( - [ - f"{field:{field_len}s}" - for field, field_len in zip(rows[0], lengths) - ] - ) - # + " |" - ] - # separator - + [ - # "| " + - " | ".join(["-" * field_len for field_len in lengths]) - # + " |" - ] - # rows - + [ - # "| " + - " | ".join( - [f"{field:>{field_len}s}" for field, field_len in zip(row, lengths)] - ) - # + " |" - for row in rows[1:] - ] - ) - + "\n```" - ) - - return info - - -def get_local_machine_name(): - return os.uname().nodename - - -def get_info_message(with_cpu=True, with_gpu=True): - message = f"**Status of `{get_local_machine_name()}`**\n" - message += f"Date: `{datetime.datetime.now()}`\n\n" - - if with_cpu and has_extra_cpu(): - message += "System information:" - ret = get_cpu_info() - if ret is not None: - message += "\n" + ret + "\n" - else: - message += " N/A\n" - - message += "Disk information:" - ret = get_disk_info() - if ret is not None: - message += "\n" + ret + "\n" - else: - message += " N/A\n" - - if with_gpu and has_extra_gpu(): - message += "GPU information:" - ret = get_gpu_info() - if ret is not None: - message += "\n" + ret + "\n" - else: - message += " N/A\n" - - return message - - -# --------------------------------------------------------------------------- - -# --------------------------------------------------------------------------- - - -ObservableLimit = namedtuple( - "ObservableLimit", - ( - #: visible name of the check/limit/... - "name", - #: function that returns a numeric value - "fn_retrieve", - #: function that get current and threshold value (may be ignored) - #: and returns True if current value is ok - "fn_check", - #: threshold, numeric (for visibility purposes) - "threshold", - #: message to send if check failed (e. g. resource exhausted) - "message", - #: badness increment for each failed check, None for default - #: can be smaller than threshold to allow for multiple consecutive failed checks - #: or same as threshold to immediatly notify - "badness_inc", - #: badness threshold if reached, a message is sent, None for default - #: allows for fluctuations until message is sent - "badness_threshold", - ), -) - - -class BadCounterManager: - """Manager that gathers badness values for keys with - individual thresholds and increments.""" - - def __init__(self, default_threshold=3, default_increase=3): - self.bad_counters = defaultdict(int) - self.default_increase = default_increase - self.default_threshold = default_threshold - - def reset(self, name=None): - """Reset counters etc. to normal/default levels.""" - if name is not None: - self.bad_counters[name] = 0 - else: - for name in self.bad_counters.keys(): - self.bad_counters[name] = 0 - - def increase_counter(self, name, limit): - """Increse the badness level and return True if threshold reached.""" - bad_threshold = ( - limit.badness_threshold - if limit.badness_threshold is not None - else self.default_threshold - ) - bad_inc = ( - limit.badness_inc - if limit.badness_inc is not None - else self.default_increase - ) - - # increse value - self.bad_counters[name] = min(bad_threshold, self.bad_counters[name] + bad_inc) - - return self.threshold_reached(name, limit) - - def decrease_counter(self, name, limit): - """Decrease the badness counter and return True if normal.""" - if self.bad_counters[name] > 0: - self.bad_counters[name] = max(0, self.bad_counters[name] - 1) - - return self.is_normal(name) - - def threshold_reached(self, name, limit): - """Return True if the badness counter has reached the threshold.""" - bad_threshold = ( - limit.badness_threshold - if limit.badness_threshold is not None - else self.default_threshold - ) - - return self.bad_counters[name] >= bad_threshold - - def is_normal(self, name): - """Return True if the badness counter is zero/normal.""" - return self.bad_counters[name] == 0 - - -class NotifyBadCounterManager(BadCounterManager): - """Manager that collects badness values and notification statuses.""" - - def __init__(self, default_threshold=3, default_increase=3): - super().__init__( - default_threshold=default_threshold, default_increase=default_increase - ) - self.notified = defaultdict(bool) - - def reset(self, name=None): - super().reset(name=name) - - if name is not None: - self.notified[name] = False - else: - for name in self.notified.keys(): - self.notified[name] = False - - def decrease_counter(self, name, limit): - """Decrease the counter and reset the notification flag - if the normal level has been reached. - Returns True on change from non-normal to normal - (for a one-time notification setup).""" - was_normal_before = self.is_normal(name) - has_notified_before = self.notified[name] - is_normal = super().decrease_counter(name, limit) - if is_normal: - self.notified[name] = False - # return True if changed, else False if it was already normal - # additionally require a limit exceeded message to be sent, else ignore the change - return was_normal_before != is_normal and has_notified_before - - def should_notify(self, name, limit): - """Return True if a notification should be sent.""" - if not self.threshold_reached(name, limit): - return False - - if not self.notified[name]: - return True - - def mark_notified(self, name): - """Mark this counter as already notified.""" - self.notified[name] = True - - -def make_observable_limits(): - if not has_extra_cpu(): - return dict() - - # ---------------------------------------------------- - - import psutil - - def _get_loadavg(): - return [x / psutil.cpu_count() * 100 for x in psutil.getloadavg()] - - def _get_mem_util(): - mem = psutil.virtual_memory() - return mem.used / mem.total * 100 - - def _get_disk_paths(): - disks = [ - disk - for disk in psutil.disk_partitions(all=False) - if "loop" not in disk.device and not disk.mountpoint.startswith("/boot") - ] - paths = [disk.mountpoint for disk in disks] - return paths - - def _get_disk_usage(path): - return psutil.disk_usage(path).percent - - def _get_disk_free_gb(path): - return psutil.disk_usage(path).free / 1024 / 1024 / 1024 - - # ---------------------------------------------------- - - limits = dict() - - limits["cpu_load_5min"] = ObservableLimit( - name="CPU-Load-Avg-5min", - fn_retrieve=lambda: _get_loadavg()[1], - fn_check=lambda cur, thres: cur < thres, - threshold=95.0, - message="**CPU Load Avg [5min]** is too high! (value: `{cur_value}%`, threshold: `{threshold})`", - # increase badness level by 1 - badness_inc=1, - # notify, when badness counter reached 3 - badness_threshold=3, - ) - limits["mem_util"] = ObservableLimit( - name="Memory-Utilisation", - fn_retrieve=lambda: _get_mem_util(), - fn_check=lambda cur, thres: cur < thres, - threshold=85.0, - message="**Memory Usage** is too high! (value: `{cur_value}%`, threshold: `{threshold})`", - # increase badness level by 1 - badness_inc=1, - # notify, when badness counter reached 3 - badness_threshold=3, - ) - - for i, path in enumerate(_get_disk_paths()): - limits[f"disk_util_perc{i}"] = ObservableLimit( - name=f"Disk-Usage-{path}", - fn_retrieve=lambda: _get_disk_usage(path), - fn_check=lambda cur, thres: cur < thres, - threshold=95.0, - message=( - f"**Disk Usage for `{path}`** is too high! " - "(value: `{cur_value}%`, threshold: `{threshold})`" - ), - # use default increment amount - badness_inc=None, - # notify immediately - badness_threshold=None, - ) - # TODO: disable the static values test if system has less or not significantly more total disk space - limits[f"disk_util_gb{i}"] = ObservableLimit( - name=f"Disk-Space-Free-{path}", - fn_retrieve=lambda: _get_disk_free_gb(path), - fn_check=lambda cur, thres: cur > thres, - # currently a hard-coded limit of 30GB (for smaller systems (non-servers) unneccessary?) - threshold=30.0, - message=( - "No more **Disk Space for `{path}`**! " - "(value: `{cur_value}GB`, threshold: `{threshold})`" - ), - # use default increment amount - badness_inc=None, - # notify immediately - badness_threshold=None, - ) - - # TODO: GPU checks - # NOTE: may be useful if you just want to know when GPU is free for new stuff ... - - return limits - - -# --------------------------------------------------------------------------- diff --git a/setup.py b/setup.py index fd4253c..a505e0a 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ def load_content(filename): setup( name="discord-notifier-bot", - version="0.2.1", + version="0.3.0", license="MIT License", author="Erik Körner", author_email="koerner@informatik.uni-leipzig.de", @@ -31,14 +31,11 @@ def load_content(filename): packages=["discord_notifier_bot"], python_requires=">=3.6", install_requires=["discord.py"], - extras_require={"cpu": ["psutil"], "gpu": ["gputil"],}, entry_points={ "console_scripts": [ "dbot-run = discord_notifier_bot.cli:main", "dbot-message = discord_notifier_bot.cli:main_message", "dbot-file = discord_notifier_bot.cli:main_file", - "dbot-info = discord_notifier_bot.cli:main_info", - "dbot-observe = discord_notifier_bot.cli:main_observe", ] }, )