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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: LinuxMonitor
3
- Version: 1.0.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
- [![PyPI version](https://badge.fury.io/py/Linux-Monitor.svg)](https://badge.fury.io/py/Linux-Monitor) [![License: MIT](https://img.shields.io/badge/License-MIT-brightgreen.svg)](https://github.com/QuentinCG/Linux-Monitor-Python-Library/blob/master/LICENSE.md) [![Donate](https://img.shields.io/badge/Donate-PayPal-blue.svg)](https://paypal.me/QuentinCG)
30
+ [![PyPI version](https://badge.fury.io/py/LinuxMonitor.svg)](https://pypi.org/project/LinuxMonitor/) [![License: MIT](https://img.shields.io/badge/License-MIT-brightgreen.svg)](https://github.com/QuentinCG/Linux-Monitor-Python-Library/blob/master/LICENSE.md) [![Donate](https://img.shields.io/badge/Donate-PayPal-blue.svg)](https://paypal.me/QuentinCG) [![Downloads](https://static.pepy.tech/badge/LinuxMonitor)](https://pepy.tech/project/LinuxMonitor) [![Downloads](https://static.pepy.tech/badge/LinuxMonitor/month)](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.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
- [![PyPI version](https://badge.fury.io/py/Linux-Monitor.svg)](https://badge.fury.io/py/Linux-Monitor) [![License: MIT](https://img.shields.io/badge/License-MIT-brightgreen.svg)](https://github.com/QuentinCG/Linux-Monitor-Python-Library/blob/master/LICENSE.md) [![Donate](https://img.shields.io/badge/Donate-PayPal-blue.svg)](https://paypal.me/QuentinCG)
30
+ [![PyPI version](https://badge.fury.io/py/LinuxMonitor.svg)](https://pypi.org/project/LinuxMonitor/) [![License: MIT](https://img.shields.io/badge/License-MIT-brightgreen.svg)](https://github.com/QuentinCG/Linux-Monitor-Python-Library/blob/master/LICENSE.md) [![Donate](https://img.shields.io/badge/Donate-PayPal-blue.svg)](https://paypal.me/QuentinCG) [![Downloads](https://static.pepy.tech/badge/LinuxMonitor)](https://pepy.tech/project/LinuxMonitor) [![Downloads](https://static.pepy.tech/badge/LinuxMonitor/month)](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
- [![PyPI version](https://badge.fury.io/py/Linux-Monitor.svg)](https://badge.fury.io/py/Linux-Monitor) [![License: MIT](https://img.shields.io/badge/License-MIT-brightgreen.svg)](https://github.com/QuentinCG/Linux-Monitor-Python-Library/blob/master/LICENSE.md) [![Donate](https://img.shields.io/badge/Donate-PayPal-blue.svg)](https://paypal.me/QuentinCG)
2
+ [![PyPI version](https://badge.fury.io/py/LinuxMonitor.svg)](https://pypi.org/project/LinuxMonitor/) [![License: MIT](https://img.shields.io/badge/License-MIT-brightgreen.svg)](https://github.com/QuentinCG/Linux-Monitor-Python-Library/blob/master/LICENSE.md) [![Donate](https://img.shields.io/badge/Donate-PayPal-blue.svg)](https://paypal.me/QuentinCG) [![Downloads](https://static.pepy.tech/badge/LinuxMonitor)](https://pepy.tech/project/LinuxMonitor) [![Downloads](https://static.pepy.tech/badge/LinuxMonitor/month)](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/12)"
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, Union, Dict, Set, List, Tuple, Awaitable, Any
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, timeout: Optional[int] = None, display_only_if_critical: bool = False, check_also_stdout_not_containing: Optional[str] = None) -> Tuple[bool, 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 timeout: Timeout in seconds for the command execution. None means no timeout.
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=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 False, out_msg
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 not res:
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
- out_msg += f"- Error code: {returncode}"
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", timeout=None, display_only_if_critical=False)
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}", timeout=5, display_only_if_critical=display_only_if_critical)
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}", timeout=timeout_in_sec, display_only_if_critical=False)
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[Union[None,bool], str]:
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}", timeout=5, display_only_if_critical=False, check_also_stdout_not_containing=check_also_stdout_not_containing)
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 == False and status_msg != "":
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 _is_port_is_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]:
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', 2)
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._is_port_is_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)
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})", timeout=timeout_in_sec, display_only_if_critical=False)
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
- else:
1508
+ elif result_terminate == False:
1497
1509
  # Termination did not complete in time or failed
1498
- out_msg = f"⚠️ **Stopping nicely {pid} ({process_name}) expired of failed**.\n"
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})", timeout=timeout_in_sec, display_only_if_critical=False)
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: Union[datetime,None] = None
1595
- datetime_last_folder_usage_error_displayed: Union[datetime,None] = None
1596
- datetime_last_cpu_usage_error_displayed: Union[datetime,None] = None
1597
- datetime_last_ram_usage_error_displayed: Union[datetime,None] = None
1598
- datetime_last_swap_usage_error_displayed: Union[datetime,None] = None
1599
- datetime_last_cpu_temperature_error_displayed: Union[datetime,None] = None
1600
- datetime_last_ping_error_displayed: Union[datetime,None] = None
1601
- datetime_last_certificates_error_displayed: Union[datetime,None] = None
1602
- datetime_last_user_logins_error_displayed: Union[datetime,None] = None
1603
- datetime_last_port_error_displayed: Union[datetime,None] = None
1604
- datetime_last_services_error_displayed: Union[datetime,None] = None
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.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