Files
miles/tools/visualize/cpu_memory_profiler_visualize.py
T
da35d084eb [tool] CPU mem: add CPU profiler and visualizer (#717)
Co-authored-by: maocheng23 <35615230+maocheng23@users.noreply.github.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2026-03-09 21:55:19 -07:00

169 lines
5.4 KiB
Python

# CPU Memory Profiler Visualization
#
# CLI usage:
# python -m tools.visualize.cpu_memory_profiler_visualize visualize profile.csv
# python -m tools.visualize.cpu_memory_profiler_visualize visualize profile.csv -o chart.png --title "My Run"
# python -m tools.visualize.cpu_memory_profiler_visualize visualize-multiple a.csv b.csv -o compare.png
import logging
import typer
logger = logging.getLogger(__name__)
app = typer.Typer()
app.callback()(lambda: None)
@app.command()
def visualize(csv_path: str, output_path: str = None, title: str = None, show: bool = False):
"""Visualize a CPU memory profile CSV as a timeline chart with phase markers."""
import csv as csv_mod
from pathlib import Path
import matplotlib.pyplot as plt
csv_path = Path(csv_path)
output_path = Path(output_path) if output_path else csv_path.with_suffix(".png")
title = title or f"CPU Memory Profile — {csv_path.name}"
# Parse CSV
times, used, markers = [], [], []
with open(csv_path) as f:
for row in csv_mod.DictReader(f):
t = float(row["time_s"])
u = float(row["used_gb"])
m = row["marker"].strip()
times.append(t)
used.append(u)
if m:
markers.append((t, u, m))
# Phase colors
phase_colors = {
"generate": "#16a34a",
"train": "#dc2626",
"offload_train": "#f59e0b",
"offload_rollout": "#8b5cf6",
"onload_weights": "#0891b2",
"onload_kv": "#06b6d4",
"update_weights": "#ec4899",
}
def get_color(label):
phase = label.split("/", 1)[-1] if "/" in label else label
base = phase.rsplit("_start", 1)[0].rsplit("_end", 1)[0]
return phase_colors.get(base, "#6b7280")
# Plot
fig, ax = plt.subplots(figsize=(max(16, len(times) * 0.02), 7))
ax.plot(times, used, linewidth=1.0, color="#2563eb", zorder=2)
ax.fill_between(times, used, alpha=0.15, color="#2563eb", zorder=1)
for t, u, label in markers:
color = get_color(label)
ax.axvline(x=t, color=color, linestyle="--", alpha=0.5, linewidth=0.8, zorder=3)
ax.plot(t, u, "o", color=color, markersize=5, zorder=4)
if markers:
y_min, y_max = min(used), max(used)
y_range = y_max - y_min
ax.set_ylim(y_min - y_range * 0.05, y_max + y_range * 0.45)
n_levels = 4
for i, (t, u, label) in enumerate(markers):
color = get_color(label)
level = i % n_levels
y_pos = y_max + y_range * (0.08 + level * 0.09)
ax.annotate(
label,
xy=(t, u),
xytext=(t + (times[-1] - times[0]) * 0.005, y_pos),
fontsize=7,
color=color,
fontweight="bold",
rotation=45,
ha="left",
va="bottom",
arrowprops=dict(arrowstyle="-", color=color, alpha=0.4, linewidth=0.6),
)
ax.set_xlabel("Time (s)", fontsize=11)
ax.set_ylabel("CPU Memory Used (GB)", fontsize=11)
ax.set_title(title, fontsize=13)
ax.grid(True, alpha=0.3)
peak_idx = used.index(max(used))
ax.annotate(
f"Peak: {used[peak_idx]:.1f} GB",
xy=(times[peak_idx], used[peak_idx]),
xytext=(times[peak_idx] + (times[-1] - times[0]) * 0.03, used[peak_idx]),
fontsize=9,
fontweight="bold",
color="#dc2626",
arrowprops=dict(arrowstyle="->", color="#dc2626"),
)
plt.tight_layout()
fig.savefig(output_path, dpi=150, bbox_inches="tight")
logger.info(f"CPU memory profile chart saved to {output_path}")
if show:
plt.show()
plt.close(fig)
return output_path
@app.command()
def visualize_multiple(
csv_paths: list[str],
output_path: str = typer.Option(None, "-o", "--output-path"),
title: str = typer.Option(None, "--title"),
show: bool = typer.Option(False, "--show"),
):
"""Overlay multiple CPU memory profiles on one chart with different colors."""
import csv as csv_mod
from pathlib import Path
import matplotlib.pyplot as plt
line_colors = ["#2563eb", "#dc2626", "#16a34a", "#f59e0b", "#8b5cf6", "#ec4899", "#0891b2"]
traces = []
for p in csv_paths:
times, used = [], []
with open(p) as f:
for row in csv_mod.DictReader(f):
times.append(float(row["time_s"]))
used.append(float(row["used_gb"]))
traces.append((times, used))
out = Path(output_path) if output_path else Path(csv_paths[0]).with_name("compare.png")
title = title or "CPU Memory Profile Comparison"
fig, ax = plt.subplots(figsize=(max(16, max(len(t) for t, _ in traces) * 0.02), 7))
for i, (times, used) in enumerate(traces):
color = line_colors[i % len(line_colors)]
label = Path(csv_paths[i]).stem
ax.plot(times, used, linewidth=1.0, color=color, zorder=2, label=label)
ax.fill_between(times, used, alpha=0.10, color=color, zorder=1)
ax.set_xlabel("Time (s)", fontsize=11)
ax.set_ylabel("CPU Memory Used (GB)", fontsize=11)
ax.set_title(title, fontsize=13)
ax.grid(True, alpha=0.3)
ax.legend(fontsize=10)
plt.tight_layout()
fig.savefig(out, dpi=150, bbox_inches="tight")
logger.info(f"Comparison chart saved to {out}")
if show:
plt.show()
plt.close(fig)
return out
if __name__ == "__main__":
app()