Skip to content

Commit e4999fb

Browse files
committed
WIP
1 parent b881393 commit e4999fb

24 files changed

Lines changed: 2674 additions & 75 deletions

cpu_monitor.py

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
#!/usr/bin/env python3
2+
"""
3+
CPU Monitor Script
4+
5+
This script runs another process and monitors its CPU usage, reporting
6+
the average and highest CPU usage during the process execution.
7+
8+
Usage:
9+
python cpu_monitor.py <command> [args...]
10+
11+
Example:
12+
python cpu_monitor.py ls -la
13+
python cpu_monitor.py ./build/bin/elasticurl --help
14+
"""
15+
16+
import sys
17+
import subprocess
18+
import psutil
19+
import time
20+
import threading
21+
import argparse
22+
from typing import List, Tuple, Optional
23+
24+
25+
class CPUMonitor:
26+
def __init__(self, sampling_interval: float = 0.1):
27+
"""
28+
Initialize CPU monitor.
29+
30+
Args:
31+
sampling_interval: Time between CPU usage samples in seconds
32+
"""
33+
self.sampling_interval = sampling_interval
34+
self.cpu_samples: List[float] = []
35+
self.monitoring = False
36+
self.process: Optional[psutil.Process] = None
37+
38+
def _monitor_cpu(self) -> None:
39+
"""Monitor CPU usage in a separate thread."""
40+
while self.monitoring and self.process and self.process.is_running():
41+
try:
42+
# Get CPU percentage for the specific process
43+
cpu_percent = self.process.cpu_percent()
44+
if cpu_percent > 0: # Only record non-zero values
45+
self.cpu_samples.append(cpu_percent)
46+
time.sleep(self.sampling_interval)
47+
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
48+
# Process has ended or we can't access it
49+
break
50+
51+
def run_and_monitor(self, command: List[str]) -> Tuple[int, float, float]:
52+
"""
53+
Run a command and monitor its CPU usage.
54+
55+
Args:
56+
command: List of command and arguments to execute
57+
58+
Returns:
59+
Tuple of (return_code, average_cpu, max_cpu)
60+
"""
61+
print(f"Starting process: {' '.join(command)}")
62+
print(f"Monitoring CPU usage (sampling every {self.sampling_interval}s)...")
63+
print("-" * 50)
64+
65+
# Start the process
66+
try:
67+
process = subprocess.Popen(
68+
command,
69+
stdout=subprocess.PIPE,
70+
stderr=subprocess.PIPE,
71+
text=True
72+
)
73+
except FileNotFoundError:
74+
print(f"Error: Command '{command[0]}' not found")
75+
return 1, 0.0, 0.0
76+
except Exception as e:
77+
print(f"Error starting process: {e}")
78+
return 1, 0.0, 0.0
79+
80+
# Get psutil process object for monitoring
81+
try:
82+
self.process = psutil.Process(process.pid)
83+
except psutil.NoSuchProcess:
84+
print("Error: Could not attach to process for monitoring")
85+
return 1, 0.0, 0.0
86+
87+
# Start monitoring in a separate thread
88+
self.monitoring = True
89+
monitor_thread = threading.Thread(target=self._monitor_cpu, daemon=True)
90+
monitor_thread.start()
91+
92+
# Wait for process to complete and capture output
93+
try:
94+
stdout, stderr = process.communicate()
95+
return_code = process.returncode
96+
except KeyboardInterrupt:
97+
print("\nInterrupted by user")
98+
process.terminate()
99+
try:
100+
process.wait(timeout=5)
101+
except subprocess.TimeoutExpired:
102+
process.kill()
103+
return_code = 130 # Standard exit code for SIGINT
104+
stdout, stderr = "", ""
105+
finally:
106+
# Stop monitoring
107+
self.monitoring = False
108+
if monitor_thread.is_alive():
109+
monitor_thread.join(timeout=1)
110+
111+
# Print process output
112+
if stdout:
113+
print("Process output:")
114+
print(stdout)
115+
if stderr:
116+
print("Process errors:")
117+
print(stderr, file=sys.stderr)
118+
119+
# Calculate CPU statistics
120+
if self.cpu_samples:
121+
avg_cpu = sum(self.cpu_samples) / len(self.cpu_samples)
122+
max_cpu = max(self.cpu_samples)
123+
else:
124+
avg_cpu = 0.0
125+
max_cpu = 0.0
126+
127+
return return_code, avg_cpu, max_cpu
128+
129+
130+
def main():
131+
"""Main function to parse arguments and run the monitor."""
132+
if len(sys.argv) < 2:
133+
print("Usage: python cpu_monitor.py <command> [args...]")
134+
print("\nExample:")
135+
print(" python cpu_monitor.py ls -la")
136+
print(" python cpu_monitor.py ./build/bin/elasticurl --help")
137+
sys.exit(1)
138+
139+
# Parse command line arguments
140+
parser = argparse.ArgumentParser(
141+
description="Monitor CPU usage while running another process",
142+
add_help=False # We'll handle help manually since we need to pass through args
143+
)
144+
145+
# Check if user wants help
146+
if '--help' in sys.argv or '-h' in sys.argv:
147+
parser.print_help()
148+
sys.exit(0)
149+
150+
# Get the command to run (everything after the script name)
151+
command = sys.argv[1:]
152+
153+
# Create and run the monitor
154+
monitor = CPUMonitor(sampling_interval=0.1) # Sample every 100ms
155+
156+
try:
157+
return_code, avg_cpu, max_cpu = monitor.run_and_monitor(command)
158+
159+
# Print results
160+
print("-" * 50)
161+
print("CPU Usage Statistics:")
162+
print(f" Process exit code: {return_code}")
163+
print(f" Average CPU usage: {avg_cpu:.2f}%")
164+
print(f" Highest CPU usage: {max_cpu:.2f}%")
165+
print(f" Total samples collected: {len(monitor.cpu_samples)}")
166+
167+
if len(monitor.cpu_samples) == 0:
168+
print(" Note: No CPU usage data collected (process may have been too short)")
169+
170+
sys.exit(return_code)
171+
172+
except Exception as e:
173+
print(f"Error: {e}")
174+
sys.exit(1)
175+
176+
177+
if __name__ == "__main__":
178+
main()

0 commit comments

Comments
 (0)