Spaces:
Sleeping
Sleeping
| import time | |
| import json | |
| import sys | |
| import os | |
| import argparse | |
| from pathlib import Path | |
| import cProfile | |
| import pstats | |
| import io | |
| # Add the project root to sys.path so we can import expert_backend | |
| project_root = Path(__file__).resolve().parent.parent | |
| sys.path.append(str(project_root)) | |
| from expert_backend.services.network_service import network_service | |
| from expert_backend.services.recommender_service import recommender_service | |
| from expert_op4grid_recommender import config as recommender_config | |
| class Profiler: | |
| def __init__(self): | |
| self.timings = {} | |
| self.current_scenario = None | |
| def start_scenario(self, name): | |
| self.current_scenario = name | |
| self.timings[name] = {} | |
| print(f"\n>>> Starting Scenario: {name}") | |
| def time_block(self, name): | |
| return TimerContext(self, name) | |
| class TimerContext: | |
| def __init__(self, profiler, name): | |
| self.profiler = profiler | |
| self.name = name | |
| self.start_time = None | |
| def __enter__(self): | |
| self.start_time = time.perf_counter() | |
| return self | |
| def __exit__(self, exc_type, exc_val, exc_tb): | |
| end_time = time.perf_counter() | |
| elapsed = end_time - self.start_time | |
| self.profiler.timings[self.profiler.current_scenario][self.name] = elapsed | |
| print(f" {self.name}: {elapsed:.4f}s") | |
| def run_profiling(config_path, use_cprofile=False): | |
| p = Profiler() | |
| # Load config | |
| with open(config_path, 'r') as f: | |
| config_data = json.load(f) | |
| # --- Scenario 1: Load network & display N-state diagram --- | |
| p.start_scenario("1_InitialLoad_N_Diagram") | |
| with p.time_block("total_scenario_1"): | |
| with p.time_block("update_config"): | |
| from expert_backend.main import ConfigRequest | |
| req = ConfigRequest(**config_data) | |
| # Simulate the /api/config endpoint logic | |
| recommender_service.reset() | |
| network_service.load_network(req.network_path) | |
| recommender_service.update_config(req) | |
| with p.time_block("get_network_diagram"): | |
| with p.time_block("get_base_network"): | |
| n = recommender_service._get_base_network() | |
| with p.time_block("nad_generation"): | |
| # We time the internal _generate_diagram | |
| res = recommender_service.get_network_diagram() | |
| p.timings[p.current_scenario]["svg_size_bytes"] = len(res["svg"]) | |
| p.timings[p.current_scenario]["metadata_size_bytes"] = len(json.dumps(res["metadata"])) | |
| print(f" SVG size: {len(res['svg']) / 1024 / 1024:.2f} MB") | |
| # --- Scenario 2: Select contingency --- | |
| contingency_id = "P.SAOL31RONCI" | |
| p.start_scenario("2_ContingencySelection_N1_Diagram") | |
| with p.time_block("total_scenario_2"): | |
| with p.time_block("run_analysis_step1"): | |
| recommender_service.run_analysis_step1(contingency_id) | |
| with p.time_block("get_n1_diagram"): | |
| # This generates the N-1 diagram and computes flow deltas | |
| res_n1 = recommender_service.get_n1_diagram(contingency_id) | |
| p.timings[p.current_scenario]["svg_size_bytes"] = len(res_n1["svg"]) | |
| print(f" SVG size: {len(res_n1['svg']) / 1024 / 1024:.2f} MB") | |
| # --- Scenario 3: Simulate manual action --- | |
| # Manual action: "f344b395-9908-43c2-bca0-75c5f298465e_COUCHP6_coupling" | |
| action_id = "f344b395-9908-43c2-bca0-75c5f298465e_COUCHP6_coupling" | |
| p.start_scenario("3_ManualAction_Simulation_Diagram") | |
| with p.time_block("total_scenario_3"): | |
| with p.time_block("simulate_manual_action"): | |
| # This is the heavy simulation part | |
| sim_res = recommender_service.simulate_manual_action(action_id, contingency_id) | |
| with p.time_block("get_action_variant_diagram"): | |
| # This generates the post-action diagram and flow deltas | |
| res_act = recommender_service.get_action_variant_diagram(action_id) | |
| p.timings[p.current_scenario]["svg_size_bytes"] = len(res_act["svg"]) | |
| print(f" SVG size: {len(res_act['svg']) / 1024 / 1024:.2f} MB") | |
| # --- Output results --- | |
| output_file = Path("profiling_results.json") | |
| with open(output_file, 'w') as f: | |
| json.dump(p.timings, f, indent=4) | |
| print(f"\nProfiling results saved to {output_file}") | |
| # Summary table | |
| print("\n" + "="*50) | |
| print(f"{'Phase':<40} | {'Time (s)':>8}") | |
| print("-" * 50) | |
| for scenario, data in p.timings.items(): | |
| print(f"\nScenario: {scenario}") | |
| for k, v in data.items(): | |
| if isinstance(v, float): | |
| print(f" {k:<38} | {v:>8.4f}") | |
| print("="*50) | |
| if __name__ == "__main__": | |
| parser = argparse.ArgumentParser(description="Profile ExpertAssist diagram generation.") | |
| parser.add_argument("config", help="Path to the config JSON file") | |
| parser.add_argument("--cprofile", action="store_true", help="Run with cProfile") | |
| args = parser.parse_args() | |
| config_path = args.config | |
| if not os.path.exists(config_path): | |
| # Try relative to project root | |
| config_path = str(project_root / args.config) | |
| if not os.path.exists(config_path): | |
| print(f"Error: Config file not found: {args.config}") | |
| sys.exit(1) | |
| if args.cprofile: | |
| print("Running with cProfile...") | |
| pr = cProfile.Profile() | |
| pr.enable() | |
| run_profiling(config_path) | |
| pr.disable() | |
| s = io.StringIO() | |
| sortby = 'cumulative' | |
| ps = pstats.Stats(pr, stream=s).sort_stats(sortby) | |
| ps.print_stats(30) | |
| print(s.getvalue()) | |
| prof_file = "profiling.prof" | |
| pr.dump_stats(prof_file) | |
| print(f"Detailed profile saved to {prof_file}") | |
| else: | |
| run_profiling(config_path) | |