Skip to content

Commit d15ece9

Browse files
committed
feat: add pid profiler script
This commit adds a small profiler script developers can use while working on ComfyStream to check resource usage and potential bottlenecks.
1 parent 44ead39 commit d15ece9

File tree

3 files changed

+191
-10
lines changed

3 files changed

+191
-10
lines changed

src/comfystream/scripts/README.md

+51-10
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,32 @@
1-
# ComfyStream Model and Node Install Scripts
1+
# ComfyStream Scripts
2+
3+
This folder contains scripts for setting up ComfyUI nodes and models and profiling ComfyStream performance.
24

35
## Setup Scripts
46

57
1. **setup_nodes.py**: Installs custom ComfyUI nodes.
68
2. **setup_models.py**: Downloads model files and weights.
79

8-
## Configuration Files
10+
### Configuration Files
911

1012
- `configs/nodes.yaml`: Defines custom nodes to install.
1113
- `configs/models.yaml`: Defines model files to download.
1214

13-
## Basic Usage
15+
### Basic Usage
1416

1517
From the repository root:
1618

1719
```bash
1820
# Install both nodes and models (default workspace: ~/comfyui)
19-
python src/comfystream/scripts/setup_nodes.py --workspace /path/to/comfyui
20-
python src/comfystream/scripts/setup_models.py --workspace /path/to/comfyui
21+
python setup_nodes.py --workspace /path/to/comfyui
22+
python setup_models.py --workspace /path/to/comfyui
2123
```
2224

2325
> The `--workspace` flag is optional and will default to `$COMFY_UI_WORKSPACE` or `~/comfyui`.
2426
25-
## Configuration Examples
27+
### Configuration Examples
2628

27-
### Custom Nodes (nodes.yaml)
29+
#### Custom Nodes (nodes.yaml)
2830

2931
```yaml
3032
nodes:
@@ -36,7 +38,7 @@ nodes:
3638
- "tensorrt"
3739
```
3840
39-
### Models (models.yaml)
41+
#### Models (models.yaml)
4042
4143
```yaml
4244
models:
@@ -47,7 +49,7 @@ models:
4749
type: "checkpoint"
4850
```
4951
50-
## Directory Structure
52+
### Directory Structure
5153
5254
```sh
5355
workspace/
@@ -60,8 +62,47 @@ workspace/
6062
└── tensorrt/
6163
```
6264

63-
## Environment Variables
65+
### Environment Variables
6466

6567
- `COMFY_UI_WORKSPACE`: Base directory for installation
6668
- `PYTHONPATH`: Defaults to workspace directory
6769
- `CUSTOM_NODES_PATH`: Custom nodes directory
70+
71+
## Profiling Scripts
72+
73+
- **monitor_pid_resources.py**: Profiles the resource usage of a running ComfyStream server.
74+
75+
### Usage Instructions
76+
77+
1. Start the ComfySTream server and execute a streaming workflow.
78+
2. Retrieve the process ID (PID) of the server using:
79+
80+
```bash
81+
ps aux | grep app.py
82+
```
83+
84+
3. Run the profiling script:
85+
86+
```bash
87+
python profile_comfystream.py --pid <PID>
88+
```
89+
90+
After you have the PID, run the profiling script:
91+
92+
```bash
93+
python monitor_pid_resources.py --pid <PID>
94+
```
95+
96+
The script will continuously monitor the server process, capturing CPU and memory usage
97+
at specified intervals. If the `--spy` flag is used, it will also generate a detailed
98+
Py-Spy profiler report for deeper performance analysis.
99+
100+
```bash
101+
python monitor_pid_resources.py --help
102+
```
103+
104+
For a full list of available options, run:
105+
106+
```bash
107+
python monitor_pid_resources.py --help
108+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
"""A Python script to monitor system resources for a given PID and optionally create
2+
a py-spy profiler report."""
3+
4+
import psutil
5+
import pynvml
6+
import time
7+
import subprocess
8+
import click
9+
import threading
10+
11+
12+
@click.command()
13+
@click.option("--pid", type=int, required=True, help="Process ID of the Python script")
14+
@click.option("--interval", type=int, default=2, help="Monitoring interval (seconds)")
15+
@click.option(
16+
"--duration", type=int, default=30, help="Total monitoring duration (seconds)"
17+
)
18+
@click.option(
19+
"--output",
20+
type=str,
21+
default=None,
22+
help="File to save system resource logs (optional)",
23+
)
24+
@click.option("--spy", is_flag=True, help="Enable py-spy profiling")
25+
@click.option(
26+
"--spy-output", type=str, default="pyspy_profile.svg", help="Py-Spy output file"
27+
)
28+
def monitor_resources(
29+
pid: int, interval: int, duration: int, output: str, spy: bool, spy_output: str
30+
):
31+
"""Monitor system resources for a given PID and optionally create a py-spy profiler
32+
report.
33+
34+
Args:
35+
pid (int): Process ID of the Python script.
36+
interval (int): Monitoring interval in seconds.
37+
duration (int): Total monitoring duration in seconds.
38+
output (str): File to save logs (optional).
39+
spy (bool): Enable py-spy profiling.
40+
spy_output (str): Py-Spy output file.
41+
"""
42+
if not psutil.pid_exists(pid):
43+
click.echo(click.style(f"Error: Process with PID {pid} not found.", fg="red"))
44+
return
45+
46+
click.echo(
47+
click.style(
48+
f"Monitoring system resources for PID {pid} for {duration} seconds...",
49+
fg="green",
50+
)
51+
)
52+
53+
def run_py_spy():
54+
"""Run py-spy profiler for deep profiling."""
55+
click.echo(click.style("Running py-spy for deep profiling...", fg="green"))
56+
spy_cmd = f"py-spy record -o {spy_output} --pid {pid} --duration {duration}"
57+
try:
58+
subprocess.run(
59+
spy_cmd, shell=True, check=True, capture_output=True, text=True
60+
)
61+
click.echo(
62+
click.style(f"Py-Spy flame graph saved to {spy_output}", fg="green")
63+
)
64+
except subprocess.CalledProcessError as e:
65+
click.echo(click.style(f"Error running py-spy: {e.stderr}", fg="red"))
66+
67+
# Start py-spy profiling in a separate thread if enabled.
68+
if spy:
69+
spy_thread = threading.Thread(target=run_py_spy)
70+
spy_thread.start()
71+
72+
# Start main resources monitoring loop.
73+
pynvml.nvmlInit()
74+
end_time = time.time() + duration
75+
logs, cpu_usages, ram_usages, gpu_usages, vram_usages = [], [], [], [], []
76+
while time.time() < end_time:
77+
try:
78+
# General system usage.
79+
process = psutil.Process(pid)
80+
cpu_usage = process.cpu_percent(interval=interval)
81+
ram_usage = process.memory_info().rss / (1024 * 1024) # MB
82+
83+
# GPU usage.
84+
process_gpu_usage = 0
85+
process_vram_usage = 0
86+
device_count = pynvml.nvmlDeviceGetCount()
87+
for i in range(device_count):
88+
handle = pynvml.nvmlDeviceGetHandleByIndex(i)
89+
processes = pynvml.nvmlDeviceGetComputeRunningProcesses(handle)
90+
for proc_info in processes:
91+
if proc_info.pid == pid:
92+
process_gpu_usage = pynvml.nvmlDeviceGetUtilizationRates(
93+
handle
94+
).gpu
95+
process_vram_usage = proc_info.usedGpuMemory / (
96+
1024 * 1024
97+
) # MB
98+
break
99+
100+
# Collect and log resource usage.
101+
log_entry = f"CPU: {cpu_usage:.2f}%, RAM: {ram_usage:.2f}MB, GPU: {process_gpu_usage:.2f}%, VRAM: {process_vram_usage:.2f}MB"
102+
click.echo(log_entry)
103+
logs.append(log_entry)
104+
cpu_usages.append(cpu_usage)
105+
ram_usages.append(ram_usage)
106+
gpu_usages.append(process_gpu_usage)
107+
vram_usages.append(process_vram_usage)
108+
except psutil.NoSuchProcess:
109+
click.echo(click.style("Error: Process terminated!"))
110+
break
111+
112+
pynvml.nvmlShutdown()
113+
114+
# Calculate and log average resource usage.
115+
avg_cpu_usage = sum(cpu_usages) / len(cpu_usages) if cpu_usages else 0
116+
avg_ram_usage = sum(ram_usages) / len(ram_usages) if ram_usages else 0
117+
avg_gpu_usage = sum(gpu_usages) / len(gpu_usages) if gpu_usages else 0
118+
avg_vram_usage = sum(vram_usages) / len(vram_usages) if vram_usages else 0
119+
avg_log_entry = f"AVERAGE - CPU: {avg_cpu_usage:.2f}%, RAM: {avg_ram_usage:.2f}MB, GPU: {avg_gpu_usage:.2f}%, VRAM: {avg_vram_usage:.2f}MB"
120+
click.echo(avg_log_entry)
121+
logs.append(avg_log_entry)
122+
123+
# Save logs if output file is provided.
124+
if output:
125+
with open(output, "w") as f:
126+
f.write("\n".join(logs))
127+
click.echo(click.style(f"Logs saved to {output}", fg="green"))
128+
129+
# Wait for py-spy thread to finish if it was started.
130+
if spy:
131+
spy_thread.join()
132+
133+
134+
if __name__ == "__main__":
135+
monitor_resources()
+5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
11
requests
22
tqdm
33
pyyaml
4+
5+
# Profiler
6+
psutil
7+
pynvml
8+
click

0 commit comments

Comments
 (0)