LinuxMonitor 1.0.0__tar.gz → 1.0.2__tar.gz
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.
- {linuxmonitor-1.0.0 → linuxmonitor-1.0.2/LinuxMonitor.egg-info}/PKG-INFO +2 -2
- {linuxmonitor-1.0.0/LinuxMonitor.egg-info → linuxmonitor-1.0.2}/PKG-INFO +2 -2
- {linuxmonitor-1.0.0 → linuxmonitor-1.0.2}/README.md +1 -1
- {linuxmonitor-1.0.0 → linuxmonitor-1.0.2}/linuxmonitor/linuxmonitor.py +52 -38
- {linuxmonitor-1.0.0 → linuxmonitor-1.0.2}/setup.py +1 -1
- {linuxmonitor-1.0.0 → linuxmonitor-1.0.2}/LICENSE.md +0 -0
- {linuxmonitor-1.0.0 → linuxmonitor-1.0.2}/LinuxMonitor.egg-info/SOURCES.txt +0 -0
- {linuxmonitor-1.0.0 → linuxmonitor-1.0.2}/LinuxMonitor.egg-info/dependency_links.txt +0 -0
- {linuxmonitor-1.0.0 → linuxmonitor-1.0.2}/LinuxMonitor.egg-info/requires.txt +0 -0
- {linuxmonitor-1.0.0 → linuxmonitor-1.0.2}/LinuxMonitor.egg-info/top_level.txt +0 -0
- {linuxmonitor-1.0.0 → linuxmonitor-1.0.2}/linuxmonitor/__init__.py +0 -0
- {linuxmonitor-1.0.0 → linuxmonitor-1.0.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: LinuxMonitor
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.2
|
|
4
4
|
Summary: Get information and warning status of Linux server like service, port, ping, ssl certificate, disk/folder/cpu/ram/swap usage, ip connection, ... (Python and shell library, Linux ONLY)
|
|
5
5
|
Home-page: https://github.com/QuentinCG/Linux-Monitor-Python-Library
|
|
6
6
|
Author: Quentin Comte-Gaz
|
|
@@ -27,7 +27,7 @@ Requires-Dist: typing
|
|
|
27
27
|
Requires-Dist: asyncio
|
|
28
28
|
|
|
29
29
|
# Linux Monitor (Python library)
|
|
30
|
-
[](https://pypi.org/project/LinuxMonitor/) [](https://github.com/QuentinCG/Linux-Monitor-Python-Library/blob/master/LICENSE.md) [](https://paypal.me/QuentinCG) [](https://pepy.tech/project/LinuxMonitor) [](https://pepy.tech/project/LinuxMonitor)
|
|
31
31
|
|
|
32
32
|
## What is it
|
|
33
33
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: LinuxMonitor
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.2
|
|
4
4
|
Summary: Get information and warning status of Linux server like service, port, ping, ssl certificate, disk/folder/cpu/ram/swap usage, ip connection, ... (Python and shell library, Linux ONLY)
|
|
5
5
|
Home-page: https://github.com/QuentinCG/Linux-Monitor-Python-Library
|
|
6
6
|
Author: Quentin Comte-Gaz
|
|
@@ -27,7 +27,7 @@ Requires-Dist: typing
|
|
|
27
27
|
Requires-Dist: asyncio
|
|
28
28
|
|
|
29
29
|
# Linux Monitor (Python library)
|
|
30
|
-
[](https://pypi.org/project/LinuxMonitor/) [](https://github.com/QuentinCG/Linux-Monitor-Python-Library/blob/master/LICENSE.md) [](https://paypal.me/QuentinCG) [](https://pepy.tech/project/LinuxMonitor) [](https://pepy.tech/project/LinuxMonitor)
|
|
31
31
|
|
|
32
32
|
## What is it
|
|
33
33
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Linux Monitor (Python library)
|
|
2
|
-
[](https://pypi.org/project/LinuxMonitor/) [](https://github.com/QuentinCG/Linux-Monitor-Python-Library/blob/master/LICENSE.md) [](https://paypal.me/QuentinCG) [](https://pepy.tech/project/LinuxMonitor) [](https://pepy.tech/project/LinuxMonitor)
|
|
3
3
|
|
|
4
4
|
## What is it
|
|
5
5
|
|
|
@@ -28,14 +28,14 @@ __email__ = "quentin@comte-gaz.com"
|
|
|
28
28
|
__license__ = "MIT License"
|
|
29
29
|
__copyright__ = "Copyright Quentin Comte-Gaz (2024)"
|
|
30
30
|
__python_version__ = "3.+"
|
|
31
|
-
__version__ = "1.0 (2024/09/
|
|
31
|
+
__version__ = "1.0.2 (2024/09/13)"
|
|
32
32
|
__status__ = "Usable for any Linux project"
|
|
33
33
|
|
|
34
34
|
import json
|
|
35
35
|
import subprocess
|
|
36
36
|
import time
|
|
37
37
|
import shutil
|
|
38
|
-
from typing import Callable, Optional,
|
|
38
|
+
from typing import Callable, Optional, Dict, Set, List, Tuple, Awaitable, Any
|
|
39
39
|
import psutil
|
|
40
40
|
import os
|
|
41
41
|
import ssl
|
|
@@ -167,13 +167,13 @@ class LinuxMonitor:
|
|
|
167
167
|
|
|
168
168
|
#region Execute Command
|
|
169
169
|
|
|
170
|
-
def execute_and_verify(self, command: List[str], display_name: str,
|
|
170
|
+
def execute_and_verify(self, command: List[str], display_name: str, timeout_in_sec: Optional[int] = None, display_only_if_critical: bool = False, check_also_stdout_not_containing: Optional[str] = None) -> Tuple[Optional[bool], str]:
|
|
171
171
|
"""
|
|
172
172
|
Execute a shell command and verify its correct execution.
|
|
173
173
|
|
|
174
174
|
:param command: A list of strings representing the command and its arguments.
|
|
175
175
|
:param display_name: The name of the command to display in the output message.
|
|
176
|
-
:param
|
|
176
|
+
:param timeout_in_sec: Timeout in seconds for the command execution. None means no timeout.
|
|
177
177
|
:param display_only_if_critical: If True, the string result will only be returned if there is an error during execution.
|
|
178
178
|
:param check_also_stdout_not_containing: If not None, we check that the stdout does not contain this string, if found, we consider it as an execution error.
|
|
179
179
|
|
|
@@ -185,41 +185,48 @@ class LinuxMonitor:
|
|
|
185
185
|
logging.debug(msg=f"Executing command {display_name} (command: {command})...")
|
|
186
186
|
pipe = subprocess.Popen(args=command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=False)
|
|
187
187
|
try:
|
|
188
|
-
stdout, stderr = pipe.communicate(timeout=
|
|
188
|
+
stdout, stderr = pipe.communicate(timeout=timeout_in_sec)
|
|
189
189
|
returncode = pipe.returncode
|
|
190
190
|
except subprocess.TimeoutExpired:
|
|
191
191
|
logging.warning(msg=f"Timeout expired for {display_name} (command: {command})")
|
|
192
192
|
pipe.kill()
|
|
193
193
|
stdout, stderr = pipe.communicate()
|
|
194
194
|
except Exception as e:
|
|
195
|
+
# Failed to execute the command
|
|
195
196
|
out_msg = f"⚠️ **Error {display_name}**:\n```sh\n{e}\n```"
|
|
196
197
|
logging.exception(msg=out_msg)
|
|
197
|
-
return
|
|
198
|
+
return None, out_msg
|
|
198
199
|
|
|
199
200
|
# Decode and replace backticks with double quotes
|
|
200
201
|
stdout_str: str = stdout.decode(encoding='utf-8').replace('`', '"').strip()
|
|
201
202
|
stderr_str: str = stderr.decode(encoding='utf-8').replace('`', '"').strip()
|
|
202
203
|
|
|
203
204
|
# Check if the command executed successfully
|
|
204
|
-
res: bool = (returncode == 0)
|
|
205
|
+
res: Optional[bool] = (returncode == 0) if (returncode != -1) else None
|
|
205
206
|
if res and check_also_stdout_not_containing:
|
|
206
207
|
res = check_also_stdout_not_containing not in stdout_str
|
|
207
208
|
|
|
208
209
|
out_msg: str = ""
|
|
209
|
-
if
|
|
210
|
+
if res == False:
|
|
211
|
+
# Command returned an error code
|
|
210
212
|
out_msg = f"❌ **Error {display_name}**\n"
|
|
213
|
+
out_msg += f"- Error code: {returncode}"
|
|
214
|
+
|
|
211
215
|
if stderr_str != "":
|
|
212
216
|
out_msg += f"- Error log:\n```sh\n{stderr_str}\n```"
|
|
213
217
|
|
|
214
218
|
if stdout_str != "":
|
|
215
219
|
out_msg += f"- Info:\n```sh\n{stdout_str}\n```"
|
|
216
|
-
|
|
217
|
-
|
|
220
|
+
elif res == None:
|
|
221
|
+
# Command timed out
|
|
222
|
+
out_msg = f"⚠️ **Error {display_name}**\n"
|
|
223
|
+
out_msg += f"- Failed to wait for the command to finish (due to timeout of {timeout_in_sec} seconds)"
|
|
218
224
|
else:
|
|
225
|
+
# Command executed successfully
|
|
219
226
|
if not display_only_if_critical:
|
|
220
227
|
out_msg = f"✅ **{display_name} executed successfully**"
|
|
221
228
|
|
|
222
|
-
if res:
|
|
229
|
+
if res == True:
|
|
223
230
|
logging.info(msg=f"Command {display_name} (command {command}) executed successfully")
|
|
224
231
|
else:
|
|
225
232
|
logging.error(msg=f"Error executing command {display_name} (command {command}):\n{out_msg}")
|
|
@@ -239,7 +246,7 @@ class LinuxMonitor:
|
|
|
239
246
|
IMPORTANT: To be usable, the user must have the right to execute `sudo /sbin/reboot` command without password.
|
|
240
247
|
"""
|
|
241
248
|
logging.info(msg="Rebooting the server...")
|
|
242
|
-
_, out_msg = self.execute_and_verify(command=["sudo", "/sbin/reboot"], display_name="Server reboot",
|
|
249
|
+
_, out_msg = self.execute_and_verify(command=["sudo", "/sbin/reboot"], display_name="Server reboot", timeout_in_sec=None, display_only_if_critical=False)
|
|
243
250
|
return out_msg
|
|
244
251
|
|
|
245
252
|
#endregion
|
|
@@ -708,10 +715,10 @@ class LinuxMonitor:
|
|
|
708
715
|
display_name = f"[{display_name}](https://{website})"
|
|
709
716
|
|
|
710
717
|
start_time: float = time.time()
|
|
711
|
-
res, out_msg = self.execute_and_verify(command=ping_command, display_name=f"ping {display_name}",
|
|
718
|
+
res, out_msg = self.execute_and_verify(command=ping_command, display_name=f"ping {display_name}", timeout_in_sec=5, display_only_if_critical=display_only_if_critical)
|
|
712
719
|
end_time: float = time.time()
|
|
713
720
|
|
|
714
|
-
if res and not display_only_if_critical:
|
|
721
|
+
if res == True and not display_only_if_critical:
|
|
715
722
|
res_ping_sec: str = "{:.2f}sec".format(end_time - start_time)
|
|
716
723
|
out_msg: str = f"✅ **{display_name} answered in {res_ping_sec}**."
|
|
717
724
|
logging.info(msg=out_msg)
|
|
@@ -807,10 +814,10 @@ class LinuxMonitor:
|
|
|
807
814
|
logging.info(f"Trying to restart {display_name} (command: {service_call}) in less than {timeout_in_sec}sec...")
|
|
808
815
|
|
|
809
816
|
start_time: float = time.time()
|
|
810
|
-
res, out_msg = self.execute_and_verify(command=service_call, display_name=f"restart {display_name}",
|
|
817
|
+
res, out_msg = self.execute_and_verify(command=service_call, display_name=f"restart {display_name}", timeout_in_sec=timeout_in_sec, display_only_if_critical=False)
|
|
811
818
|
end_time: float = time.time()
|
|
812
819
|
|
|
813
|
-
if res:
|
|
820
|
+
if res == True:
|
|
814
821
|
readable_duration: str = "{:.2f}".format(end_time - start_time)
|
|
815
822
|
out_msg = f"✅ **{display_name} restarted with success** in {readable_duration}sec."
|
|
816
823
|
logging.info(msg=out_msg)
|
|
@@ -839,7 +846,7 @@ class LinuxMonitor:
|
|
|
839
846
|
|
|
840
847
|
return out_msg
|
|
841
848
|
|
|
842
|
-
def _get_service_status(self, is_private: bool, service_name: str) -> Tuple[
|
|
849
|
+
def _get_service_status(self, is_private: bool, service_name: str) -> Tuple[Optional[bool], str]:
|
|
843
850
|
"""
|
|
844
851
|
Get the status of a specific service.
|
|
845
852
|
|
|
@@ -854,6 +861,7 @@ class LinuxMonitor:
|
|
|
854
861
|
|
|
855
862
|
service = self.config["services"][service_name]
|
|
856
863
|
display_name = service.get('display_name', service_name)
|
|
864
|
+
timeout_in_sec = service.get('timeout_in_sec', 30)
|
|
857
865
|
|
|
858
866
|
if 'status_command' not in service:
|
|
859
867
|
logging.error(msg=f"Status command (status_command) not found for service {service_name}")
|
|
@@ -862,7 +870,7 @@ class LinuxMonitor:
|
|
|
862
870
|
status_command = service['status_command']
|
|
863
871
|
check_also_stdout_not_containing = service.get('check_also_stdout_not_containing', None)
|
|
864
872
|
|
|
865
|
-
res, out_msg = self.execute_and_verify(command=status_command, display_name=f"état de {display_name}",
|
|
873
|
+
res, out_msg = self.execute_and_verify(command=status_command, display_name=f"état de {display_name}", timeout_in_sec=timeout_in_sec, display_only_if_critical=False, check_also_stdout_not_containing=check_also_stdout_not_containing)
|
|
866
874
|
return res, out_msg
|
|
867
875
|
|
|
868
876
|
def check_all_services_status(self, is_private: bool) -> str:
|
|
@@ -900,7 +908,7 @@ class LinuxMonitor:
|
|
|
900
908
|
if out_msg != "":
|
|
901
909
|
out_msg += "\n"
|
|
902
910
|
out_msg += f"- {service_icon} {self.config['services'][service_name]['display_name']}: **{service_status}**"
|
|
903
|
-
if status
|
|
911
|
+
if status != True and status_msg != "":
|
|
904
912
|
out_msg += f"\n{status_msg}"
|
|
905
913
|
except Exception as e:
|
|
906
914
|
out_msg = f"**Internal error checking services status**:\n```sh\n{e}\n```"
|
|
@@ -929,6 +937,10 @@ class LinuxMonitor:
|
|
|
929
937
|
|
|
930
938
|
out_msg_full += out_msg + "\n"
|
|
931
939
|
out_msg_full += self.restart_service(is_private=is_private, service_name=service_name) + "\n"
|
|
940
|
+
elif status is None:
|
|
941
|
+
out_msg: str = f"⚠️ **Error checking status of {self.config['services'][service_name]['display_name']}** (not restarting it)."
|
|
942
|
+
logging.error(msg=out_msg)
|
|
943
|
+
out_msg_full += out_msg + "\n"
|
|
932
944
|
except Exception as e:
|
|
933
945
|
out_msg_full = f"**Internal error during service status check.**:\n```sh\n{e}\n```"
|
|
934
946
|
logging.exception(msg=out_msg_full)
|
|
@@ -1295,7 +1307,7 @@ class LinuxMonitor:
|
|
|
1295
1307
|
|
|
1296
1308
|
#region Ports
|
|
1297
1309
|
|
|
1298
|
-
def
|
|
1310
|
+
def _is_port_in_required_state(self, display_name: str, port: int, host: str = "localhost", timeout_in_sec: float = 2, want_port_to_be_open: bool = True) -> Tuple[bool, str]:
|
|
1299
1311
|
"""
|
|
1300
1312
|
Check if a specific port is in the required state (open or closed).
|
|
1301
1313
|
|
|
@@ -1355,14 +1367,14 @@ class LinuxMonitor:
|
|
|
1355
1367
|
port: int = port_config['port']
|
|
1356
1368
|
display_name: str = port_config.get('display_name', f"Port {port}")
|
|
1357
1369
|
host: str = port_config.get('host', 'localhost')
|
|
1358
|
-
timeout_in_sec: float = port_config.get('timeout_in_sec',
|
|
1370
|
+
timeout_in_sec: float = port_config.get('timeout_in_sec', 10)
|
|
1359
1371
|
want_port_to_be_open: bool = port_config.get('want_port_to_be_open', True)
|
|
1360
1372
|
|
|
1361
1373
|
service_name_to_restart: str = ""
|
|
1362
1374
|
if want_port_to_be_open and restart_if_down:
|
|
1363
1375
|
service_name_to_restart = port_config.get('service_name_to_restart', "")
|
|
1364
1376
|
|
|
1365
|
-
result, result_msg = self.
|
|
1377
|
+
result, result_msg = self._is_port_in_required_state(port=port, host=host, timeout_in_sec=timeout_in_sec, want_port_to_be_open=want_port_to_be_open, display_name=display_name)
|
|
1366
1378
|
if result_msg != "" and (not display_only_if_critical or not result):
|
|
1367
1379
|
if out_msg != "":
|
|
1368
1380
|
out_msg += "\n"
|
|
@@ -1489,17 +1501,19 @@ class LinuxMonitor:
|
|
|
1489
1501
|
|
|
1490
1502
|
# Attempt to terminate the process
|
|
1491
1503
|
terminate_command: List[str] = ['sudo', '/bin/kill', '-TERM', str(pid)]
|
|
1492
|
-
result_terminate, _ = self.execute_and_verify(command=terminate_command, display_name=f"stop process {pid} ({process_name})",
|
|
1504
|
+
result_terminate, _ = self.execute_and_verify(command=terminate_command, display_name=f"stop process {pid} ({process_name})", timeout_in_sec=timeout_in_sec, display_only_if_critical=False)
|
|
1493
1505
|
|
|
1494
|
-
if result_terminate:
|
|
1506
|
+
if result_terminate == True:
|
|
1495
1507
|
out_msg = f"✅ **Process {pid} ({process_name}) stopped with success** (nicely)).\n"
|
|
1496
|
-
|
|
1508
|
+
elif result_terminate == False:
|
|
1497
1509
|
# Termination did not complete in time or failed
|
|
1498
|
-
out_msg = f"⚠️ **Stopping nicely {pid} ({process_name})
|
|
1510
|
+
out_msg = f"⚠️ **Stopping nicely {pid} ({process_name}) failed**.\n"
|
|
1511
|
+
else:
|
|
1512
|
+
out_msg = f"⚠️ **Stopping nicely {pid} ({process_name}) expired**.\n"
|
|
1499
1513
|
|
|
1500
1514
|
# Attempt to kill the process
|
|
1501
1515
|
kill_command = ['sudo', '/bin/kill', '-KILL', str(pid)]
|
|
1502
|
-
_, strerror_kill = self.execute_and_verify(command=kill_command, display_name=f"kill process {pid} ({process_name})",
|
|
1516
|
+
_, strerror_kill = self.execute_and_verify(command=kill_command, display_name=f"kill process {pid} ({process_name})", timeout_in_sec=timeout_in_sec, display_only_if_critical=False)
|
|
1503
1517
|
out_msg += strerror_kill
|
|
1504
1518
|
|
|
1505
1519
|
if process_cpu_percent > 0:
|
|
@@ -1591,17 +1605,17 @@ class LinuxMonitor:
|
|
|
1591
1605
|
|
|
1592
1606
|
await asyncio.sleep(delay=10) # Sleep for 10 sec before lauching the scheduled tasks (to allow the lib to be ready)
|
|
1593
1607
|
|
|
1594
|
-
datetime_last_disk_usage_error_displayed:
|
|
1595
|
-
datetime_last_folder_usage_error_displayed:
|
|
1596
|
-
datetime_last_cpu_usage_error_displayed:
|
|
1597
|
-
datetime_last_ram_usage_error_displayed:
|
|
1598
|
-
datetime_last_swap_usage_error_displayed:
|
|
1599
|
-
datetime_last_cpu_temperature_error_displayed:
|
|
1600
|
-
datetime_last_ping_error_displayed:
|
|
1601
|
-
datetime_last_certificates_error_displayed:
|
|
1602
|
-
datetime_last_user_logins_error_displayed:
|
|
1603
|
-
datetime_last_port_error_displayed:
|
|
1604
|
-
datetime_last_services_error_displayed:
|
|
1608
|
+
datetime_last_disk_usage_error_displayed: Optional[datetime] = None
|
|
1609
|
+
datetime_last_folder_usage_error_displayed: Optional[datetime] = None
|
|
1610
|
+
datetime_last_cpu_usage_error_displayed: Optional[datetime] = None
|
|
1611
|
+
datetime_last_ram_usage_error_displayed: Optional[datetime] = None
|
|
1612
|
+
datetime_last_swap_usage_error_displayed: Optional[datetime] = None
|
|
1613
|
+
datetime_last_cpu_temperature_error_displayed: Optional[datetime] = None
|
|
1614
|
+
datetime_last_ping_error_displayed: Optional[datetime] = None
|
|
1615
|
+
datetime_last_certificates_error_displayed: Optional[datetime] = None
|
|
1616
|
+
datetime_last_user_logins_error_displayed: Optional[datetime] = None
|
|
1617
|
+
datetime_last_port_error_displayed: Optional[datetime] = None
|
|
1618
|
+
datetime_last_services_error_displayed: Optional[datetime] = None
|
|
1605
1619
|
|
|
1606
1620
|
if not self.start_scheduled_task_show_info_immediately:
|
|
1607
1621
|
logging.info(msg=f"Waiting for {self.duration_in_sec_wait_between_each_schedule_task_execution} seconds before starting the execution of {'private' if is_private else 'public'} scheduled tasks...")
|
|
@@ -6,7 +6,7 @@ with io.open(file='README.md', mode='r', encoding='utf-8') as readme_file:
|
|
|
6
6
|
|
|
7
7
|
setup(
|
|
8
8
|
name="LinuxMonitor",
|
|
9
|
-
version="1.0.
|
|
9
|
+
version="1.0.2",
|
|
10
10
|
description="Get information and warning status of Linux server like service, port, ping, ssl certificate, disk/folder/cpu/ram/swap usage, ip connection, ... (Python and shell library, Linux ONLY)",
|
|
11
11
|
long_description=readme,
|
|
12
12
|
long_description_content_type="text/markdown",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|