Skip to content

Commit b932756

Browse files
authored
Merge pull request #13 from systemswatch/memory_process_profiler
Memory process profiler
2 parents f1b0481 + d939182 commit b932756

File tree

5 files changed

+159
-15
lines changed

5 files changed

+159
-15
lines changed

.pylintrc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ disable=
44
C0114, # missing-module-docstring
55
C0116, # missing-function-docstring
66
W0141, # In Python 2, input() can be dangerous because it evaluates Python code, thus should be blacklisted for production code. In Python 3, input() behaves like Python 2's raw_input() which can only return a string.
7-
R1260 # Menu complexity is not avoidable at this time or worth fixing for this use case
7+
R1260, # Menu complexity is not avoidable at this time or worth fixing for this use case
8+
R0801 # By nature there will be some duplicate code because of the dynamic config generation and autonomy of configs
89

910
# Specify a configuration file.
1011
#rcfile=

process_watch.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
# Logging Configuration
1111
os.makedirs(os.getcwd() + "/logs", exist_ok=True)
1212
log_file = (f"{str(os.getcwd())}/logs/process_watch.log")
13+
error_file = (f"{str(os.getcwd())}/logs/error.log")
1314

1415
logging.basicConfig(
1516
handlers = [logging.FileHandler(log_file), logging.StreamHandler()],
@@ -27,7 +28,9 @@ def import_watch_list():
2728
module_name = filename[:-3] # Remove the .py extension
2829
gbl[module_name] = importlib.import_module(f"{module_name}")
2930
except Exception as e:
30-
logging.error("An error occurred in Process Watch: %s", e, exc_info=True)
31-
raise sys.exit(1)
31+
print(f"An error occurred in Process Watch: {e}")
32+
with open(error_file, "a", encoding="utf-8") as file:
33+
file.write(str(e) + "\n")
34+
raise sys.exit(1)
3235

3336
import_watch_list()

tools/config_tool.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import os
44
import sys
55
from general_process_profiler import general_process_profiler
6+
from memory_process_profiler import memory_process_profiler
67
sys.dont_write_bytecode = True
78

89
# Menu ANSI Colors
@@ -106,6 +107,7 @@ def display_menu():
106107
break
107108
if watch_list_choice == '2':
108109
clear_screen()
110+
memory_process_profiler()
109111
break
110112
if watch_list_choice == '3':
111113
break

tools/general_process_profiler.py

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import os
44
import textwrap
55
import subprocess
6+
import sys
7+
sys.dont_write_bytecode = True
68

79
# Menu ANSI Colors
810
BLACK = '\033[30m'
@@ -13,7 +15,7 @@
1315
BACKGROUND_BRIGHT_MAGENTA = '\033[105m'
1416
RESET = '\033[0m'
1517

16-
# Reset Sceen
18+
# Reset Screen
1719
def clear_screen():
1820
os.system('cls' if os.name == 'nt' else 'clear')
1921

@@ -44,12 +46,16 @@ def write_to_file(filename, template):
4446

4547
# General Process Profiler Menu
4648
def general_process_profiler():
47-
print(f"\n{BRIGHT_GREEN}SERVICE PROFILER GENERAL SETTINGS:{RESET}")
49+
print(f"\n{BRIGHT_GREEN}GENERAL PROCESS PROFILER SETTINGS:{RESET}")
4850
filename = input(f"\n{BRIGHT_CYAN}Enter the name of the configuration file:{RESET}\n")
4951
sanitized_filename = filename.replace(".", "-")
5052
process_name = input(f"\n{BRIGHT_CYAN}Enter the process name to monitor:{RESET}\n")
51-
interval = input(f"\n{BRIGHT_CYAN}Enter the monitoring interval in seconds:{RESET}\n")
52-
# General Process Profiler Template
53+
while True:
54+
try:
55+
interval = int(input(f"\n{BRIGHT_CYAN}Enter the monitoring interval in seconds:{RESET}\n"))
56+
break
57+
except ValueError:
58+
print(f"{BLACK}{BACKGROUND_BRIGHT_MAGENTA}\nInvalid input. Please enter a number of seconds.{RESET}")
5359
template = f"""
5460
# General Process Profiler
5561
import os
@@ -60,7 +66,8 @@ def general_process_profiler():
6066
import psutil
6167
import subprocess
6268
63-
output_file = (f"{{str(os.getcwd())}}/logs/process_watch.log")
69+
general_process_profiler_file = (f"{{str(os.getcwd())}}/logs/{sanitized_filename}-general-profile.log")
70+
error_file = (f"{{str(os.getcwd())}}/logs/error.log")
6471
6572
def find_pid_by_name(name):
6673
try:
@@ -96,22 +103,22 @@ def find_cpu_usage_by_pid(pid):
96103
return ("0.0")
97104
98105
def monitor():
99-
100106
while True:
101107
try:
102-
with open(output_file, "a", encoding="utf-8") as f:
108+
with open(general_process_profiler_file, "a", encoding="utf-8") as f:
103109
f.write(f"{process_name} running at local time {{time.ctime()}} PID: {{int(find_pid_by_name('{process_name}'))}}, Memory: {{find_memory_usage_by_pid(int(find_pid_by_name('{process_name}')))}}MB, CPU: {{find_cpu_usage_by_pid(int(find_pid_by_name('{process_name}')))}}%\\n")
104110
time.sleep({interval})
105111
except Exception as e:
106-
logging.error("An error occurred in Process Watch: %s", e, exc_info=True)
107-
raise sys.exit(1)
112+
print(f"An error occurred in Process Watch configuration file {sanitized_filename}: {{e}}")
113+
with open(error_file, "a", encoding="utf-8") as file:
114+
file.write(str(e) + (f" in {sanitized_filename}") + "\\n")
115+
raise sys.exit(1)
108116
109117
def worker():
110118
monitor_thread = threading.Thread(target=monitor)
111119
monitor_thread.start()
112120
113121
worker()
114122
"""
115-
# Write the template into a config
116-
write_to_file(os.path.abspath(f"../watch_list/{sanitized_filename}.py"), textwrap.dedent(template))
117-
123+
# Write the template into a config
124+
write_to_file(os.path.abspath(f"../watch_list/{sanitized_filename}.py"), textwrap.dedent(template))

tools/memory_process_profiler.py

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
#!/usr/bin/env python
2+
3+
import os
4+
import textwrap
5+
import subprocess
6+
import sys
7+
sys.dont_write_bytecode = True
8+
9+
# Base Directory
10+
base_dir = os.path.dirname(os.path.abspath(__file__))
11+
os.chdir(base_dir)
12+
13+
# Menu ANSI Colors
14+
BLACK = '\033[30m'
15+
GREEN = '\033[32m'
16+
BRIGHT_GREEN = '\033[92m'
17+
BRIGHT_CYAN = '\033[96m'
18+
BRIGHT_YELLOW = '\033[93m'
19+
BACKGROUND_BRIGHT_MAGENTA = '\033[105m'
20+
RESET = '\033[0m'
21+
22+
# Reset Screen
23+
def clear_screen():
24+
os.system('cls' if os.name == 'nt' else 'clear')
25+
26+
# Identify Process By Name Return PID
27+
def find_pid_by_name(name):
28+
try:
29+
with subprocess.Popen(['pgrep', '-f', name], shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) as all_pids:
30+
output, errors = all_pids.communicate()
31+
if errors:
32+
print(f"Errors:\n{errors.decode()}")
33+
return None
34+
all_pids_array = output.strip().split("\n")
35+
first_pid = all_pids_array[0]
36+
return first_pid
37+
except Exception as e:
38+
print(f"{BLACK}{BACKGROUND_BRIGHT_MAGENTA}\nAn error occurred: {e}{RESET}")
39+
return None
40+
41+
# Write configuration file to watch_list directory
42+
def write_to_file(filename, template):
43+
try:
44+
with open(os.path.join(os.pardir, "watch_list", filename), 'w', encoding="utf-8") as file:
45+
file.write(template)
46+
clear_screen()
47+
print(f"\n{GREEN}'{filename}' configuration created.{RESET}")
48+
except Exception as e:
49+
print(f"{BLACK}{BACKGROUND_BRIGHT_MAGENTA}\nAn error occurred: {e}{RESET}")
50+
51+
# Memory Process Profiler Menu
52+
def memory_process_profiler():
53+
print(f"\n{BRIGHT_GREEN}MEMORY PROCESS PROFILER SETTINGS:{RESET}")
54+
filename = input(f"\n{BRIGHT_CYAN}Enter the name of the configuration file:{RESET}\n")
55+
sanitized_filename = filename.replace(".", "-")
56+
process_name = input(f"\n{BRIGHT_CYAN}Enter the process name to monitor:{RESET}\n")
57+
while True:
58+
try:
59+
interval = int(input(f"\n{BRIGHT_CYAN}Enter the monitoring interval in seconds:{RESET}\n"))
60+
break
61+
except ValueError:
62+
print(f"{BLACK}{BACKGROUND_BRIGHT_MAGENTA}\nInvalid input. Please enter a number of seconds.{RESET}")
63+
while True:
64+
try:
65+
memory_threshold = int(input(f"\n{BRIGHT_CYAN}Enter the memory threshold of the process (in MB):{RESET}\n"))
66+
break
67+
except ValueError:
68+
print(f"{BLACK}{BACKGROUND_BRIGHT_MAGENTA}\nInvalid input. Please enter a number of Megabytes.{RESET}")
69+
action = input(f"\n{BRIGHT_CYAN}Enter the action to take upon the memory threshold being met:{RESET}\n")
70+
template = f"""
71+
# Memory Process Profiler
72+
import os
73+
import sys
74+
import time
75+
import logging
76+
import threading
77+
import psutil
78+
import subprocess
79+
80+
memory_process_profiler_file = (f"{{str(os.getcwd())}}/logs/{sanitized_filename}-memory-profile.log")
81+
error_file = (f"{{str(os.getcwd())}}/logs/error.log")
82+
83+
def find_pid_by_name(name):
84+
try:
85+
with subprocess.Popen(['pgrep', '-f', name], shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) as all_pids:
86+
output, errors = all_pids.communicate()
87+
if errors:
88+
logging.error(f"Errors:\\n{{errors.decode()}}")
89+
return None
90+
all_pids_array = output.strip().split("\\n")
91+
first_pid = all_pids_array[0]
92+
return first_pid
93+
except Exception as e:
94+
logging.error("An error occurred in Process Watch: %s", e, exc_info=True)
95+
raise sys.exit(1)
96+
return None
97+
98+
def find_memory_usage_by_pid(pid):
99+
process = psutil.Process(pid)
100+
memory_info = process.memory_info()
101+
rss_bytes = memory_info.rss
102+
rss_megabytes = int(rss_bytes / (1024 * 1024))
103+
if rss_megabytes:
104+
return rss_megabytes
105+
else:
106+
return ("Process N/A.")
107+
108+
def monitor():
109+
while True:
110+
try:
111+
process_pid = find_pid_by_name('{process_name}')
112+
process_memory_set = {{find_memory_usage_by_pid(int(find_pid_by_name('{process_name}')))}}
113+
process_memory = int(process_memory_set.pop())
114+
if int(process_memory) >= {memory_threshold}:
115+
process_action = subprocess.run(['{action}'], capture_output=True, text=True)
116+
time.sleep({interval})
117+
except Exception as e:
118+
print(f"An error occurred in Process Watch configuration file {sanitized_filename}: {{e}}")
119+
with open(error_file, "a", encoding="utf-8") as file:
120+
file.write(str(e) + (f" in {sanitized_filename}") + "\\n")
121+
raise sys.exit(1)
122+
123+
def worker():
124+
monitor_thread = threading.Thread(target=monitor)
125+
monitor_thread.start()
126+
127+
worker()
128+
"""
129+
# Write the template into a config
130+
write_to_file(os.path.abspath(f"../watch_list/{sanitized_filename}.py"), textwrap.dedent(template))
131+

0 commit comments

Comments
 (0)