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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: LinuxMonitor
3
- Version: 1.0.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.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/12)"
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, 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```"
@@ -1295,7 +1303,7 @@ class LinuxMonitor:
1295
1303
 
1296
1304
  #region Ports
1297
1305
 
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]:
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', 2)
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._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)
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})", timeout=timeout_in_sec, display_only_if_critical=False)
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
- else:
1504
+ elif result_terminate == False:
1497
1505
  # Termination did not complete in time or failed
1498
- out_msg = f"⚠️ **Stopping nicely {pid} ({process_name}) expired of failed**.\n"
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})", timeout=timeout_in_sec, display_only_if_critical=False)
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: 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
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.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