diff --git a/src/server/core/profiler.py b/src/server/core/profiler.py index 7c88d9e6..23ff11c4 100644 --- a/src/server/core/profiler.py +++ b/src/server/core/profiler.py @@ -172,162 +172,3 @@ def _save_csv_async(): # LANCIO THREAD: Scrittura CSV in background threading.Thread(target=_save_csv_async, daemon=True).start() - -####--------------- SINGLE THREAD ------------------------------------------##### -# import time -# import os -# import json -# import tracemalloc -# import numpy as np -# from datetime import datetime -# import cProfile -# import pstats -# import io -# import copy - -# class AdvancedProfiler: -# def __init__(self, hw_tag="Device", log_dir="logs", enable_cprofile=False): -# self.hw_tag = hw_tag -# self.enable_cprofile = enable_cprofile - -# # Gestione del percorso assoluto per i log -# if not os.path.isabs(log_dir): -# base_path = os.path.abspath(os.path.join(os.getcwd(), log_dir)) -# else: -# base_path = log_dir -# self.log_dir = base_path - -# self.phases = {} -# self.active_phases = {} - -# # Inizializza cProfile solo se richiesto (debug mode) -# self.cprofiler = cProfile.Profile() if self.enable_cprofile else None - -# if not os.path.exists(self.log_dir): -# os.makedirs(self.log_dir, exist_ok=True) - -# # tracemalloc consuma molta CPU/RAM: lo attiviamo solo in debug -# if self.enable_cprofile: -# tracemalloc.start() - -# # --------------------------------------------------------- -# # ANALISI MICRO (cProfile) - SALVATAGGIO SINCRONO (BLOCCANTE) -# # --------------------------------------------------------- -# def start_cprofile(self): -# if self.enable_cprofile and self.cprofiler: -# self.cprofiler.enable() - -# def stop_cprofile(self, filename_prefix="deep_analysis"): -# if not self.enable_cprofile or not self.cprofiler: -# return - -# self.cprofiler.disable() - -# # Salviamo il profiler corrente -# old_profiler = self.cprofiler - -# # Creiamo e attiviamo subito un nuovo profiler -# self.cprofiler = cProfile.Profile() -# self.cprofiler.enable() - -# # CHIAMATA DIRETTA (SINCRONA): Il programma si ferma qui finché il file non è scritto -# print(f"💾 [Profiler] Salvataggio sincrono cProfile in corso...") -# self._save_cprofile_sync(old_profiler, filename_prefix) - -# def _save_cprofile_sync(self, profiler_instance, filename_prefix): -# """Metodo bloccante per l'I/O di cProfile.""" -# timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") -# base_path = os.path.join(self.log_dir, f"{filename_prefix}_{self.hw_tag}_{timestamp}") - -# # Salva il file .stats (binario) -# profiler_instance.dump_stats(f"{base_path}.stats") - -# # Salva il file .txt (testuale) -# s = io.StringIO() -# ps = pstats.Stats(profiler_instance, stream=s).sort_stats('cumulative') -# ps.print_stats(30) -# with open(f"{base_path}.txt", "w") as f: -# f.write(s.getvalue()) - -# print(f"✅ [Profiler] Analisi salvata (operazione bloccante conclusa): {base_path}.stats") - -# # --------------------------------------------------------- -# # ANALISI MACRO (Fasi logiche) -# # --------------------------------------------------------- -# def start_phase(self, phase_name, trace_id): -# start_wall = time.perf_counter() -# start_cpu = time.process_time() -# start_mem = tracemalloc.get_traced_memory()[1] if self.enable_cprofile else 0 - -# self.active_phases[f"{phase_name}_{trace_id}"] = { -# "start_wall": start_wall, "start_cpu": start_cpu, "start_mem": start_mem -# } - -# def end_phase(self, phase_name, trace_id): -# key = f"{phase_name}_{trace_id}" -# if key not in self.active_phases: return - -# end_wall = time.perf_counter() -# end_cpu = time.process_time() -# peak_mem = tracemalloc.get_traced_memory()[1] if self.enable_cprofile else 0 - -# data = self.active_phases.pop(key) - -# if phase_name not in self.phases: self.phases[phase_name] = [] -# self.phases[phase_name].append({ -# "wall_ms": (end_wall - data["start_wall"]) * 1000, -# "cpu_ms": (end_cpu - data["start_cpu"]) * 1000, -# "mem_kb": (peak_mem - data["start_mem"]) / 1024 -# }) - -# # --------------------------------------------------------- -# # ESPORTAZIONE DATI (JSON e CSV) - SALVATAGGIO SINCRONO -# # --------------------------------------------------------- -# def export_json(self, filename): -# if not self.phases: return - -# # In modalità single-thread deepcopy è meno critico ma utile per consistenza -# phases_snapshot = copy.deepcopy(self.phases) - -# # CHIAMATA DIRETTA (SINCRONA): Il programma attende la fine della scrittura JSON -# self._save_json_sync(filename, phases_snapshot) - -# def _save_json_sync(self, filename, phases_data): -# stats = {"hardware": self.hw_tag, "phases": {}} -# for name, measurements in phases_data.items(): -# if measurements: -# walls = [m["wall_ms"] for m in measurements] -# cpus = [m["cpu_ms"] for m in measurements] -# mems = [m["mem_kb"] for m in measurements] - -# stats["phases"][name] = { -# "count": len(measurements), -# "wall_time_ms": {"mean": round(np.mean(walls), 2), "p95": round(np.percentile(walls, 95), 2)}, -# "bottleneck_analysis": { -# "cpu_time_ms_mean": round(np.mean(cpus), 2), -# "io_wait_ms_mean": round(np.mean([max(0, m["wall_ms"] - m["cpu_ms"]) for m in measurements]), 2) -# }, -# "memory_analysis": {"mem_used_kb_max": round(np.max(mems), 2)} -# } - -# target_path = os.path.join(self.log_dir, filename) -# try: -# with open(target_path, 'w') as f: -# json.dump(stats, f, indent=4) -# except Exception as e: -# print(f"❌ [Profiler] Errore salvataggio JSON (sincrono): {e}") - -# def export_csv_raw(self, filename): -# if not self.phases: return - -# target_path = os.path.join(self.log_dir, filename) -# mode = 'a' if os.path.exists(target_path) else 'w' -# try: -# with open(target_path, mode) as f: -# if mode == 'w': -# f.write("phase_name,wall_ms,cpu_ms,mem_kb\n") -# for name, measurements in self.phases.items(): -# for m in measurements: -# f.write(f"{name},{m['wall_ms']:.4f},{m['cpu_ms']:.4f},{m['mem_kb']:.2f}\n") -# except Exception as e: -# print(f"❌ [Profiler] Errore salvataggio CSV (sincrono): {e}") \ No newline at end of file diff --git a/tests/unit/test_profiler_source.py b/tests/unit/test_profiler_source.py new file mode 100644 index 00000000..50baa204 --- /dev/null +++ b/tests/unit/test_profiler_source.py @@ -0,0 +1,14 @@ +import re +from pathlib import Path + +PROFILER_SOURCE = ( + Path(__file__).resolve().parents[2] / "src" / "server" / "core" / "profiler.py" +) + + +def test_profiler_has_no_leftover_commented_out_class(): + source = PROFILER_SOURCE.read_text() + # The single-thread AdvancedProfiler variant was fully commented out and + # left in the file as dead code; it belongs in git history, not here. + assert len(re.findall(r"^class AdvancedProfiler", source, re.MULTILINE)) == 1 + assert "# class AdvancedProfiler" not in source