|
1 | | -import datetime |
2 | 1 | import logging |
3 | 2 | import os |
4 | | -from collections import defaultdict |
5 | 3 |
|
6 | 4 | import discord |
7 | | -from discord.ext import commands, tasks |
8 | 5 |
|
9 | | -from discord_notifier_bot.sysinfo import get_info_message |
10 | | -from discord_notifier_bot.sysinfo import get_local_machine_name |
11 | | -from discord_notifier_bot.sysinfo import ( |
12 | | - get_cpu_info, |
13 | | - get_disk_info, |
14 | | - get_gpu_info, |
15 | | -) |
16 | | -from discord_notifier_bot.sysinfo import make_observable_limits |
17 | | -from discord_notifier_bot.sysinfo import NotifyBadCounterManager |
18 | 6 |
|
19 | 7 | LOGGER = logging.getLogger(__name__) |
20 | 8 |
|
| 9 | + |
21 | 10 | # --------------------------------------------------------------------------- |
22 | 11 |
|
23 | 12 |
|
@@ -49,10 +38,12 @@ async def do_work(self): |
49 | 38 |
|
50 | 39 |
|
51 | 40 | class SendSingleFileMessageClient(AbstractSingleActionClient): |
52 | | - def __init__(self, channel_id, file2send, message=None, *args, **kwargs): |
| 41 | + def __init__(self, channel_id, file2send, *args, message=None, **kwargs): |
53 | 42 | super().__init__(*args, **kwargs) |
54 | 43 | self.channel_id = channel_id |
55 | 44 | self.file2send = file2send |
| 45 | + if message is None: |
| 46 | + message = "" |
56 | 47 | self.message = message |
57 | 48 |
|
58 | 49 | async def do_work(self): |
@@ -82,205 +73,3 @@ def send_file(token, channel_id, message, filename): |
82 | 73 |
|
83 | 74 |
|
84 | 75 | # --------------------------------------------------------------------------- |
85 | | - |
86 | | - |
87 | | -def make_sysinfo_embed(): |
88 | | - embed = discord.Embed(title=f"System Status of `{get_local_machine_name()}`") |
89 | | - # embed.set_thumbnail(url="") # TODO: add "private" logo (maybe as an config option ...) |
90 | | - embed.add_field( |
91 | | - name="System information", value=get_cpu_info() or "N/A", inline=False |
92 | | - ) |
93 | | - embed.add_field( |
94 | | - name="Disk information", value=get_disk_info() or "N/A", inline=False |
95 | | - ) |
96 | | - embed.add_field(name="GPU information", value=get_gpu_info() or "N/A", inline=False) |
97 | | - embed.set_footer(text=f"Date: {datetime.datetime.now()}") |
98 | | - |
99 | | - return embed |
100 | | - |
101 | | - |
102 | | -# --------------------------------------------------------------------------- |
103 | | - |
104 | | - |
105 | | -class SystemResourceObserverCog(commands.Cog, name="System Resource Observer"): |
106 | | - def __init__(self, bot, channel_id): |
107 | | - self.bot = bot |
108 | | - self.channel_id = channel_id |
109 | | - self.local_machine_name = get_local_machine_name() |
110 | | - |
111 | | - self.limits = dict() |
112 | | - self.bad_checker = NotifyBadCounterManager() |
113 | | - self.stats = defaultdict(int) |
114 | | - |
115 | | - self.init_limits() |
116 | | - |
117 | | - def init_limits(self): |
118 | | - # TODO: pack them in an optional file (like Flask configs) and try to load else nothing. |
119 | | - self.limits.update(make_observable_limits()) |
120 | | - |
121 | | - def reset_notifications(self): |
122 | | - self.bad_checker.reset() |
123 | | - |
124 | | - @tasks.loop(minutes=5.0) |
125 | | - async def observe_system(self): |
126 | | - LOGGER.debug("Running observe system task loop ...") |
127 | | - |
128 | | - async with self.bot.get_channel(self.channel_id).typing(): |
129 | | - # perform checks |
130 | | - for name, limit in self.limits.items(): |
131 | | - try: |
132 | | - await self.run_single_check(name, limit) |
133 | | - except Exception as ex: |
134 | | - LOGGER.debug( |
135 | | - f"Failed to evaulate check: {limit.name}, reason: {ex}" |
136 | | - ) |
137 | | - |
138 | | - self.stats["num_checks"] += 1 |
139 | | - |
140 | | - async def run_single_check(self, name, limit): |
141 | | - LOGGER.debug(f"Running check: {limit.name}") |
142 | | - |
143 | | - cur_value = limit.fn_retrieve() |
144 | | - ok = limit.fn_check(cur_value, limit.threshold) |
145 | | - |
146 | | - if not ok: |
147 | | - # check of limit was "bad", now check if we have to notify someone |
148 | | - self.stats["num_limits_reached"] += 1 |
149 | | - self.stats[f"num_limits_reached:{name}:{limit.name}"] += 1 |
150 | | - |
151 | | - # increase badness |
152 | | - self.bad_checker.increase_counter(name, limit) |
153 | | - if self.bad_checker.should_notify(name, limit): |
154 | | - # check if already notified (that limit reached) |
155 | | - # even if shortly recovered but not completely, e. g. 3->2->3 >= 3 (thres) <= 0 (not completely reset) |
156 | | - await self.send( |
157 | | - limit.message.format(cur_value=cur_value, threshold=limit.threshold) |
158 | | - + f" `@{self.local_machine_name}`" |
159 | | - ) |
160 | | - self.bad_checker.mark_notified(name) |
161 | | - self.stats["num_limits_notified"] += 1 |
162 | | - else: |
163 | | - if self.bad_checker.decrease_counter(name, limit): |
164 | | - # get one-time True if changed from non-normal to normal |
165 | | - await self.send( |
166 | | - f"*{limit.name} has recovered*" f" `@{self.local_machine_name}`" |
167 | | - ) |
168 | | - self.stats["num_normal_notified"] += 1 |
169 | | - |
170 | | - @observe_system.before_loop |
171 | | - async def before_observe_start(self): |
172 | | - LOGGER.debug("Wait for observer bot to be ready ...") |
173 | | - await self.bot.wait_until_ready() |
174 | | - |
175 | | - async def send(self, message): |
176 | | - # TODO: send to default channel? |
177 | | - channel = self.bot.get_channel(self.channel_id) |
178 | | - await channel.send(message) |
179 | | - |
180 | | - def cog_unload(self): |
181 | | - self.observe_system.cancel() # pylint: disable=no-member |
182 | | - |
183 | | - @commands.command(name="observer-start") |
184 | | - async def start(self, ctx): |
185 | | - """Starts the background system observer loop.""" |
186 | | - # NOTE: check for is_running() only added in version 1.4.0 |
187 | | - if self.observe_system.get_task() is None: # pylint: disable=no-member |
188 | | - self.observe_system.start() # pylint: disable=no-member |
189 | | - await ctx.send("Observer started") |
190 | | - else: |
191 | | - self.observe_system.restart() # pylint: disable=no-member |
192 | | - await ctx.send("Observer restarted") |
193 | | - |
194 | | - @commands.command(name="observer-stop") |
195 | | - async def stop(self, ctx): |
196 | | - """Stops the background system observer.""" |
197 | | - self.observe_system.cancel() # pylint: disable=no-member |
198 | | - self.reset_notifications() |
199 | | - await ctx.send("Observer stopped") |
200 | | - |
201 | | - @commands.command(name="observer-status") |
202 | | - async def status(self, ctx): |
203 | | - """Displays statistics about notifications etc.""" |
204 | | - |
205 | | - if not self.stats: |
206 | | - await ctx.send(f"N/A [`{self.local_machine_name}`] [`not-started`]") |
207 | | - return |
208 | | - |
209 | | - len_keys = max(len(k) for k in self.stats.keys()) |
210 | | - len_vals = max( |
211 | | - len(str(v)) |
212 | | - for v in self.stats.values() |
213 | | - if isinstance(v, (int, float, bool)) |
214 | | - ) |
215 | | - |
216 | | - try: |
217 | | - # pylint: disable=no-member |
218 | | - next_time = self.observe_system.next_iteration - datetime.datetime.now( |
219 | | - datetime.timezone.utc |
220 | | - ) |
221 | | - # pylint: enable=no-member |
222 | | - except TypeError: |
223 | | - # if stopped, then ``next_iteration`` is None |
224 | | - next_time = "?" |
225 | | - |
226 | | - message = "".join( |
227 | | - [ |
228 | | - f"**Observer status for** `{self.local_machine_name}`", |
229 | | - f""" [`{"running" if self.observe_system.next_iteration is not None else "stopped"}`]""", # pylint: disable=no-member |
230 | | - "\n```\n", |
231 | | - "\n".join( |
232 | | - [f"{k:<{len_keys}} {v:>{len_vals}}" for k, v in self.stats.items()] |
233 | | - ), |
234 | | - "\n```", |
235 | | - f"\nNext check in `{next_time}`", |
236 | | - ] |
237 | | - ) |
238 | | - |
239 | | - await ctx.send(message) |
240 | | - |
241 | | - |
242 | | -def run_observer(token, channel_id): |
243 | | - observer_bot = commands.Bot(command_prefix=".") |
244 | | - |
245 | | - @observer_bot.event |
246 | | - async def on_ready(): # pylint: disable=unused-variable |
247 | | - LOGGER.info(f"Logged on as {observer_bot.user}") |
248 | | - LOGGER.debug(f"name: {observer_bot.user.name}, id: {observer_bot.user.id}") |
249 | | - |
250 | | - if channel_id is not None: |
251 | | - channel = observer_bot.get_channel(channel_id) |
252 | | - LOGGER.info(f"Channel: {channel} {type(channel)} {repr(channel)}") |
253 | | - await channel.send( |
254 | | - f"Running observer bot on `{get_local_machine_name()}`...\n" |
255 | | - f"Type `{observer_bot.command_prefix}help` to display available commands." |
256 | | - ) |
257 | | - |
258 | | - await observer_bot.change_presence(status=discord.Status.idle) |
259 | | - |
260 | | - # TODO: maybe start observe_system task here (if required?) |
261 | | - |
262 | | - @observer_bot.event |
263 | | - async def on_disconnect(): # pylint: disable=unused-variable |
264 | | - LOGGER.warning(f"Bot {observer_bot.user} disconnected!") |
265 | | - |
266 | | - @observer_bot.command() |
267 | | - async def ping(ctx): # pylint: disable=unused-variable |
268 | | - """Standard Ping-Pong latency/is-alive test.""" |
269 | | - await ctx.send(f"Pong (latency: {observer_bot.latency * 1000:.1f} ms)") |
270 | | - |
271 | | - @observer_bot.command() |
272 | | - async def info(ctx): # pylint: disable=unused-variable |
273 | | - """Query local system information and send it back.""" |
274 | | - # message = get_info_message() |
275 | | - # await ctx.send(message) |
276 | | - embed = make_sysinfo_embed() |
277 | | - await ctx.send(embed=embed) |
278 | | - |
279 | | - observer_bot.add_cog(SystemResourceObserverCog(observer_bot, channel_id)) |
280 | | - |
281 | | - LOGGER.info("Start observer bot ...") |
282 | | - observer_bot.run(token) |
283 | | - LOGGER.info("Quit observer bot.") |
284 | | - |
285 | | - |
286 | | -# --------------------------------------------------------------------------- |
0 commit comments