diff --git a/.gitignore b/.gitignore index 15201ac..abfd7ba 100644 --- a/.gitignore +++ b/.gitignore @@ -169,3 +169,6 @@ cython_debug/ # PyPI configuration file .pypirc + +# MAC +.DS_Store diff --git a/tools/config_tool.py b/tools/config_tool.py index ca48f9a..f2e746b 100755 --- a/tools/config_tool.py +++ b/tools/config_tool.py @@ -6,6 +6,7 @@ from general_process_profiler import general_process_profiler from memory_process_profiler import memory_process_profiler from file_descriptor_process_profiler import file_descriptor_process_profiler +from thread_process_profiler import thread_process_profiler # Menu ANSI Colors BLACK = '\033[30m' @@ -101,8 +102,9 @@ def display_menu(): print("1. Create General Process Profiler") print("2. Create Memory Process Profiler") print("3. Create File Descriptor Profiler") - print("4. Exit") - watch_list_choice = input(f"\n{BRIGHT_CYAN}Enter your choice (1-4):{RESET}\n") + print("4. Create Thread Descriptor Profiler") + print("5. Exit") + watch_list_choice = input(f"\n{BRIGHT_CYAN}Enter your choice (1-5):{RESET}\n") if watch_list_choice == '1': clear_screen() general_process_profiler() @@ -115,10 +117,12 @@ def display_menu(): clear_screen() file_descriptor_process_profiler() break - clear_screen() if watch_list_choice == '4': + clear_screen() + thread_process_profiler() + break + if watch_list_choice == '5': break - clear_screen() print(f"\n{BLACK}{BACKGROUND_BRIGHT_MAGENTA}Invalid choice. Please try again.{RESET}") elif top_choice == '4': print(f"\n{BRIGHT_GREEN}Quitting...{RESET}") diff --git a/tools/file_descriptor_process_profiler.py b/tools/file_descriptor_process_profiler.py index 3cc9a32..8667979 100755 --- a/tools/file_descriptor_process_profiler.py +++ b/tools/file_descriptor_process_profiler.py @@ -2,7 +2,6 @@ import os import textwrap -import subprocess # Base Directory base_dir = os.path.dirname(os.path.abspath(__file__)) @@ -21,21 +20,6 @@ def clear_screen(): os.system('cls' if os.name == 'nt' else 'clear') -# Identify Process By Name Return PID -def find_pid_by_name(name): - try: - with subprocess.Popen(['pgrep', '-f', name], shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) as all_pids: - output, errors = all_pids.communicate() - if errors: - print(f"Errors:\n{errors.decode()}") - return None - all_pids_array = output.strip().split("\n") - first_pid = all_pids_array[0] - return first_pid - except Exception as e: - print(f"{BLACK}{BACKGROUND_BRIGHT_MAGENTA}\nAn error occurred: {e}{RESET}") - return None - # Write configuration file to watch_list directory def write_to_file(filename, template): try: @@ -81,7 +65,7 @@ def file_descriptor_process_profiler(): def find_pid_by_name(name): try: - with subprocess.Popen(['pgrep', '-f', name], shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) as all_pids: + with subprocess.Popen(['pgrep', '-xf', name], shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) as all_pids: output, errors = all_pids.communicate() if errors: logging.error(f"Errors:\\n{{errors.decode()}}") diff --git a/tools/general_process_profiler.py b/tools/general_process_profiler.py index f365ca6..6905012 100755 --- a/tools/general_process_profiler.py +++ b/tools/general_process_profiler.py @@ -2,7 +2,6 @@ import os import textwrap -import subprocess # Menu ANSI Colors BLACK = '\033[30m' @@ -17,21 +16,6 @@ def clear_screen(): os.system('cls' if os.name == 'nt' else 'clear') -# Identify Process By Name Return PID -def find_pid_by_name(name): - try: - with subprocess.Popen(['pgrep', '-f', name], shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) as all_pids: - output, errors = all_pids.communicate() - if errors: - print(f"Errors:\n{errors.decode()}") - return None - all_pids_array = output.strip().split("\n") - first_pid = all_pids_array[0] - return first_pid - except Exception as e: - print(f"{BLACK}{BACKGROUND_BRIGHT_MAGENTA}\nAn error occurred: {e}{RESET}") - return None - # Write configuration file to watch_list directory def write_to_file(filename, template): try: @@ -69,7 +53,7 @@ def general_process_profiler(): def find_pid_by_name(name): try: - with subprocess.Popen(['pgrep', '-f', name], shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) as all_pids: + with subprocess.Popen(['pgrep', '-xf', name], shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) as all_pids: output, errors = all_pids.communicate() if errors: logging.error(f"Errors:\\n{{errors.decode()}}") diff --git a/tools/memory_process_profiler.py b/tools/memory_process_profiler.py index 513a261..e8c8c8b 100755 --- a/tools/memory_process_profiler.py +++ b/tools/memory_process_profiler.py @@ -2,7 +2,6 @@ import os import textwrap -import subprocess # Base Directory base_dir = os.path.dirname(os.path.abspath(__file__)) @@ -21,21 +20,6 @@ def clear_screen(): os.system('cls' if os.name == 'nt' else 'clear') -# Identify Process By Name Return PID -def find_pid_by_name(name): - try: - with subprocess.Popen(['pgrep', '-f', name], shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) as all_pids: - output, errors = all_pids.communicate() - if errors: - print(f"Errors:\n{errors.decode()}") - return None - all_pids_array = output.strip().split("\n") - first_pid = all_pids_array[0] - return first_pid - except Exception as e: - print(f"{BLACK}{BACKGROUND_BRIGHT_MAGENTA}\nAn error occurred: {e}{RESET}") - return None - # Write configuration file to watch_list directory def write_to_file(filename, template): try: @@ -81,7 +65,7 @@ def memory_process_profiler(): def find_pid_by_name(name): try: - with subprocess.Popen(['pgrep', '-f', name], shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) as all_pids: + with subprocess.Popen(['pgrep', '-xf', name], shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) as all_pids: output, errors = all_pids.communicate() if errors: logging.error(f"Errors:\\n{{errors.decode()}}") diff --git a/tools/thread_process_profiler.py b/tools/thread_process_profiler.py new file mode 100755 index 0000000..29ed8f2 --- /dev/null +++ b/tools/thread_process_profiler.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python3 + +import os +import textwrap + +# Base Directory +base_dir = os.path.dirname(os.path.abspath(__file__)) +os.chdir(base_dir) + +# Menu ANSI Colors +BLACK = '\033[30m' +GREEN = '\033[32m' +BRIGHT_GREEN = '\033[92m' +BRIGHT_CYAN = '\033[96m' +BRIGHT_YELLOW = '\033[93m' +BACKGROUND_BRIGHT_MAGENTA = '\033[105m' +RESET = '\033[0m' + +# Reset Screen +def clear_screen(): + os.system('cls' if os.name == 'nt' else 'clear') + +# Write configuration file to watch_list directory +def write_to_file(filename, template): + try: + with open(os.path.join(os.pardir, "watch_list", filename), 'w', encoding="utf-8") as file: + file.write(template) + clear_screen() + print(f"\n{GREEN}'{filename}' configuration created.{RESET}") + except Exception as e: + print(f"{BLACK}{BACKGROUND_BRIGHT_MAGENTA}\nAn error occurred: {e}{RESET}") + +# Thread Process Profiler Menu +def thread_process_profiler(): + print(f"\n{BRIGHT_GREEN}THREAD PROCESS PROFILER SETTINGS:{RESET}") + filename = input(f"\n{BRIGHT_CYAN}Enter the name of the configuration file:{RESET}\n") + sanitized_filename = filename.replace(".", "-") + process_name = input(f"\n{BRIGHT_CYAN}Enter the process name to monitor {GREEN}(use name in ps ouput){RESET}:{RESET}\n") + while True: + try: + interval = int(input(f"\n{BRIGHT_CYAN}Enter the monitoring interval {GREEN}(in seconds){RESET}:{RESET}\n")) + break + except ValueError: + print(f"{BLACK}{BACKGROUND_BRIGHT_MAGENTA}\nInvalid input. Please enter a number of seconds.{RESET}") + while True: + try: + thread_threshold = int(input(f"\n{BRIGHT_CYAN}Enter the thread threshold of the process:{RESET}\n")) + break + except ValueError: + print(f"{BLACK}{BACKGROUND_BRIGHT_MAGENTA}\nInvalid input. Please enter the number of threads.{RESET}") + action = input(f"\n{BRIGHT_CYAN}Enter the action to take upon the thread threshold being met {GREEN}(leave blank for no action){RESET}:{RESET}\n") or "echo" + + template = f""" + #Thread Process Profiler + import os + import sys + import time + import logging + import threading + import psutil + import subprocess + + thread_process_profiler_file = (f"{{str(os.getcwd())}}/logs/{sanitized_filename}-thrd-profiler.log") + error_file = (f"{{str(os.getcwd())}}/logs/error.log") + + def find_pid_by_name(name): + try: + with subprocess.Popen(['pgrep', '-xf', name], shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) as all_pids: + output, errors = all_pids.communicate() + if errors: + logging.error(f"Errors:\\n{{errors.decode()}}") + return None + all_pids_array = output.strip().split("\\n") + first_pid = all_pids_array[0] + return first_pid + except Exception as e: + logging.error("An error occurred in Process Watch: %s", e, exc_info=True) + raise sys.exit(1) + return None + + def find_thread_usage_by_pid(pid): + process = psutil.Process(pid) + thread_usage_info = process.num_threads() + if thread_usage_info: + return thread_usage_info + else: + return("0.0") + + def monitor(): + while True: + try: + process_pid = find_pid_by_name('{process_name}') + process_thread_set = {{find_thread_usage_by_pid(int(find_pid_by_name('{process_name}')))}} + process_thread = int(process_thread_set.pop()) + if int(process_thread) >= {thread_threshold}: + with open(thread_process_profiler_file, "a", encoding="utf-8") as f: + f.write(f"{{time.ctime()}} - Thread Alert - Above Threshold {thread_threshold} - Process: [ {process_name} ], PID: {{int(find_pid_by_name('{process_name}'))}}, Threads: {{find_thread_usage_by_pid(int(find_pid_by_name('{process_name}')))}}\\n") + process_action = subprocess.run(['{action}'], capture_output=True, text=True) + time.sleep(5) + f.write(f"{{time.ctime()}} - Thread Alert - After Remediation - Action Taken: [ {action} ], Process: [ {process_name} ], PID: {{int(find_pid_by_name('{process_name}'))}}, Threads: {{find_thread_usage_by_pid(int(find_pid_by_name('{process_name}')))}}\\n") + time.sleep({interval}) + except Exception as e: + print(f"An error occurred in Process Watch configuration file {sanitized_filename}: {{e}}") + with open(error_file, "a", encoding="utf-8") as file: + file.write(str(e) + (f" in {sanitized_filename}") + "\\n") + raise sys.exit(1) + + def worker(): + monitor_thread = threading.Thread(target=monitor) + monitor_thread.start() + + worker() + """ + # Write the template into a config + write_to_file(os.path.abspath(f"../watch_list/{sanitized_filename}_thrd.py"), textwrap.dedent(template)) \ No newline at end of file