angerami's picture
package directory restructure
0f39f0f
Raw
History Blame Contribute Delete
2.81 kB
"""Performance logger for tracking resource usage across analysis phases."""
import time
from contextlib import contextmanager
from datetime import datetime
import numpy as np
import psutil
class PerfLogger:
def __init__(self, job_id):
self.job_id = job_id
self.process = psutil.Process()
self.phases = {}
self.loop_stats = []
@contextmanager
def phase(self, name):
"""Track major phases: load, configure, loop, finalize, write"""
start_time = time.perf_counter()
start_mem = self.process.memory_info().rss / 1024**2
yield
self.phases[name] = {
"elapsed_sec": time.perf_counter() - start_time,
"memory_start_mb": start_mem,
"memory_end_mb": self.process.memory_info().rss / 1024**2,
}
self.phases[name]["memory_delta_mb"] = (
self.phases[name]["memory_end_mb"] - start_mem
)
@contextmanager
def loop_item(self, iteration, log_every=10):
"""Track individual loop iterations, conditionally"""
if iteration % log_every != 0:
yield
return
start_time = time.perf_counter()
yield
self.loop_stats.append(
{
"iteration": iteration,
"elapsed_sec": time.perf_counter() - start_time,
"memory_mb": self.process.memory_info().rss / 1024**2,
}
)
def log_report(self, phase=None, context=""):
"""Generate formatted report for specific phase or most recent"""
if not self.phases:
return "No phases completed yet"
phase_name = phase or list(self.phases.keys())[-1]
if phase_name not in self.phases:
return f"Phase '{phase_name}' not found"
stats = self.phases[phase_name]
prefix = f"[{context}] " if context else ""
return (
f"{prefix}{phase_name}: "
f"{stats['elapsed_sec']:.2f}s, "
f"Δmem: {stats['memory_delta_mb']:+.1f}MB, "
f"mem: {stats['memory_end_mb']:.1f}MB"
)
def to_metadata(self):
"""Export as dict for dataframe row"""
meta = {
"job_id": self.job_id,
"timestamp": datetime.now().isoformat(),
}
# Flatten phases
for phase_name, stats in self.phases.items():
for stat_name, value in stats.items():
meta[f"{phase_name}_{stat_name}"] = value
# Loop summary stats
if self.loop_stats:
loop_times = [s["elapsed_sec"] for s in self.loop_stats]
meta["loop_mean_sec"] = np.mean(loop_times)
meta["loop_max_sec"] = np.max(loop_times)
meta["loop_n_samples"] = len(self.loop_stats)
return meta