LinuxMonitor 1.0.0__tar.gz → 1.0.1__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.1/LinuxMonitor.egg-info}/PKG-INFO +1 -1
- {linuxmonitor-1.0.0/LinuxMonitor.egg-info → linuxmonitor-1.0.1}/PKG-INFO +1 -1
- {linuxmonitor-1.0.0 → linuxmonitor-1.0.1}/linuxmonitor/linuxmonitor.py +48 -38
- {linuxmonitor-1.0.0 → linuxmonitor-1.0.1}/setup.py +1 -1
- {linuxmonitor-1.0.0 → linuxmonitor-1.0.1}/LICENSE.md +0 -0
- {linuxmonitor-1.0.0 → linuxmonitor-1.0.1}/LinuxMonitor.egg-info/SOURCES.txt +0 -0
- {linuxmonitor-1.0.0 → linuxmonitor-1.0.1}/LinuxMonitor.egg-info/dependency_links.txt +0 -0
- {linuxmonitor-1.0.0 → linuxmonitor-1.0.1}/LinuxMonitor.egg-info/requires.txt +0 -0
- {linuxmonitor-1.0.0 → linuxmonitor-1.0.1}/LinuxMonitor.egg-info/top_level.txt +0 -0
- {linuxmonitor-1.0.0 → linuxmonitor-1.0.1}/README.md +0 -0
- {linuxmonitor-1.0.0 → linuxmonitor-1.0.1}/linuxmonitor/__init__.py +0 -0
- {linuxmonitor-1.0.0 → linuxmonitor-1.0.1}/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.1
|
|
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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: LinuxMonitor
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.1
|
|
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
|
|
@@ -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.1 (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```"
|
|
@@ -1295,7 +1303,7 @@ class LinuxMonitor:
|
|
|
1295
1303
|
|
|
1296
1304
|
#region Ports
|
|
1297
1305
|
|
|
1298
|
-
def
|
|
1306
|
+
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
1307
|
"""
|
|
1300
1308
|
Check if a specific port is in the required state (open or closed).
|
|
1301
1309
|
|
|
@@ -1355,14 +1363,14 @@ class LinuxMonitor:
|
|
|
1355
1363
|
port: int = port_config['port']
|
|
1356
1364
|
display_name: str = port_config.get('display_name', f"Port {port}")
|
|
1357
1365
|
host: str = port_config.get('host', 'localhost')
|
|
1358
|
-
timeout_in_sec: float = port_config.get('timeout_in_sec',
|
|
1366
|
+
timeout_in_sec: float = port_config.get('timeout_in_sec', 10)
|
|
1359
1367
|
want_port_to_be_open: bool = port_config.get('want_port_to_be_open', True)
|
|
1360
1368
|
|
|
1361
1369
|
service_name_to_restart: str = ""
|
|
1362
1370
|
if want_port_to_be_open and restart_if_down:
|
|
1363
1371
|
service_name_to_restart = port_config.get('service_name_to_restart', "")
|
|
1364
1372
|
|
|
1365
|
-
result, result_msg = self.
|
|
1373
|
+
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
1374
|
if result_msg != "" and (not display_only_if_critical or not result):
|
|
1367
1375
|
if out_msg != "":
|
|
1368
1376
|
out_msg += "\n"
|
|
@@ -1489,17 +1497,19 @@ class LinuxMonitor:
|
|
|
1489
1497
|
|
|
1490
1498
|
# Attempt to terminate the process
|
|
1491
1499
|
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})",
|
|
1500
|
+
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
1501
|
|
|
1494
|
-
if result_terminate:
|
|
1502
|
+
if result_terminate == True:
|
|
1495
1503
|
out_msg = f"✅ **Process {pid} ({process_name}) stopped with success** (nicely)).\n"
|
|
1496
|
-
|
|
1504
|
+
elif result_terminate == False:
|
|
1497
1505
|
# Termination did not complete in time or failed
|
|
1498
|
-
out_msg = f"⚠️ **Stopping nicely {pid} ({process_name})
|
|
1506
|
+
out_msg = f"⚠️ **Stopping nicely {pid} ({process_name}) failed**.\n"
|
|
1507
|
+
else:
|
|
1508
|
+
out_msg = f"⚠️ **Stopping nicely {pid} ({process_name}) expired**.\n"
|
|
1499
1509
|
|
|
1500
1510
|
# Attempt to kill the process
|
|
1501
1511
|
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})",
|
|
1512
|
+
_, 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
1513
|
out_msg += strerror_kill
|
|
1504
1514
|
|
|
1505
1515
|
if process_cpu_percent > 0:
|
|
@@ -1591,17 +1601,17 @@ class LinuxMonitor:
|
|
|
1591
1601
|
|
|
1592
1602
|
await asyncio.sleep(delay=10) # Sleep for 10 sec before lauching the scheduled tasks (to allow the lib to be ready)
|
|
1593
1603
|
|
|
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:
|
|
1604
|
+
datetime_last_disk_usage_error_displayed: Optional[datetime] = None
|
|
1605
|
+
datetime_last_folder_usage_error_displayed: Optional[datetime] = None
|
|
1606
|
+
datetime_last_cpu_usage_error_displayed: Optional[datetime] = None
|
|
1607
|
+
datetime_last_ram_usage_error_displayed: Optional[datetime] = None
|
|
1608
|
+
datetime_last_swap_usage_error_displayed: Optional[datetime] = None
|
|
1609
|
+
datetime_last_cpu_temperature_error_displayed: Optional[datetime] = None
|
|
1610
|
+
datetime_last_ping_error_displayed: Optional[datetime] = None
|
|
1611
|
+
datetime_last_certificates_error_displayed: Optional[datetime] = None
|
|
1612
|
+
datetime_last_user_logins_error_displayed: Optional[datetime] = None
|
|
1613
|
+
datetime_last_port_error_displayed: Optional[datetime] = None
|
|
1614
|
+
datetime_last_services_error_displayed: Optional[datetime] = None
|
|
1605
1615
|
|
|
1606
1616
|
if not self.start_scheduled_task_show_info_immediately:
|
|
1607
1617
|
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.1",
|
|
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
|
|
File without changes
|