diff --git a/scripts/Makefile b/scripts/Makefile new file mode 100644 index 00000000..1639bcef --- /dev/null +++ b/scripts/Makefile @@ -0,0 +1,185 @@ +#!/usr/bin/make -f + +# Makefile for GraphServer Routing Profiler +# Builds and runs performance profiling for core routing algorithm + +CC = gcc +CFLAGS = -Wall -Wextra -std=c99 -O2 -g -DNDEBUG +PROFILE_FLAGS = -pg -fprofile-arcs -ftest-coverage +LDFLAGS = -lm -lrt -lpthread + +# Paths +CORE_DIR = ../core +BUILD_DIR = ../core/build +EXAMPLES_DIR = ../examples +SCRIPT_DIR = . + +# Include directories +INCLUDES = -I$(CORE_DIR)/include -I$(EXAMPLES_DIR)/include + +# Core library +CORE_LIB = $(BUILD_DIR)/libgraphserver_core.a + +# Example providers object files +PROVIDER_SOURCES = $(EXAMPLES_DIR)/providers/utility_functions.c \ + $(EXAMPLES_DIR)/providers/walking_provider.c \ + $(EXAMPLES_DIR)/providers/transit_provider.c \ + $(EXAMPLES_DIR)/providers/road_network_provider.c + +PROVIDER_OBJECTS = $(PROVIDER_SOURCES:.c=.o) + +# Profiler executable +PROFILER_TARGET = profile_routing +GPROF_TARGET = profile_routing_gprof + +.PHONY: all clean profile gprof help build-core + +all: $(PROFILER_TARGET) + +help: + @echo "GraphServer Routing Profiler Build System" + @echo "===========================================" + @echo "" + @echo "Targets:" + @echo " all - Build standard profiler (default)" + @echo " profile - Build and run profiler with default scenarios" + @echo " profile-25 - Run profiler with 25 scenarios (recommended)" + @echo " profile-50 - Run profiler with 50 scenarios (intensive)" + @echo " gprof - Build with gprof profiling and run" + @echo " clean - Remove built files" + @echo " help - Show this help" + @echo "" + @echo "Requirements:" + @echo " - Core library must be built first (run 'make build-core')" + @echo " - GCC with C99 support" + @echo "" + @echo "Usage Examples:" + @echo " make profile # Run with default settings" + @echo " make profile-25 # Run 25 routing scenarios" + @echo " make gprof # Generate gprof profile data" + +# Ensure core library is built +build-core: + @echo "πŸ—οΈ Building core GraphServer library..." + @if [ ! -d "$(BUILD_DIR)" ]; then \ + mkdir -p $(BUILD_DIR); \ + cd $(BUILD_DIR) && cmake ..; \ + fi + @cd $(BUILD_DIR) && make -j$$(nproc) + @echo "βœ… Core library built successfully" + +# Build provider objects +$(PROVIDER_OBJECTS): %.o: %.c + @echo "πŸ”§ Compiling provider: $<" + $(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@ + +# Standard profiler build +$(PROFILER_TARGET): build-core $(PROVIDER_OBJECTS) $(SCRIPT_DIR)/profile_routing.c + @echo "πŸ”¨ Building routing profiler..." + $(CC) $(CFLAGS) $(INCLUDES) \ + $(SCRIPT_DIR)/profile_routing.c \ + $(PROVIDER_OBJECTS) \ + $(CORE_LIB) \ + $(LDFLAGS) \ + -o $(PROFILER_TARGET) + @echo "βœ… Profiler built successfully: ./$(PROFILER_TARGET)" + +# gprof-enabled build +$(GPROF_TARGET): build-core $(PROVIDER_OBJECTS) $(SCRIPT_DIR)/profile_routing.c + @echo "πŸ”¨ Building gprof-enabled profiler..." + $(CC) $(CFLAGS) $(PROFILE_FLAGS) $(INCLUDES) \ + $(SCRIPT_DIR)/profile_routing.c \ + $(PROVIDER_OBJECTS) \ + $(CORE_LIB) \ + $(LDFLAGS) \ + -o $(GPROF_TARGET) + @echo "βœ… gprof profiler built successfully: ./$(GPROF_TARGET)" + +# Run profiler with default settings +profile: $(PROFILER_TARGET) + @echo "" + @echo "πŸš€ Running GraphServer Routing Profiler" + @echo "========================================" + @echo "" + ./$(PROFILER_TARGET) + +# Run with specific scenario counts +profile-25: $(PROFILER_TARGET) + @echo "" + @echo "πŸš€ Running Routing Profiler - 25 Scenarios" + @echo "===========================================" + @echo "" + ./$(PROFILER_TARGET) 25 + +profile-50: $(PROFILER_TARGET) + @echo "" + @echo "πŸš€ Running Routing Profiler - 50 Scenarios (Intensive)" + @echo "=====================================================" + @echo "" + ./$(PROFILER_TARGET) 50 + +profile-stress: $(PROFILER_TARGET) + @echo "" + @echo "πŸ”₯ Running Routing Profiler - Stress Test (100 scenarios)" + @echo "========================================================" + @echo "" + ./$(PROFILER_TARGET) 100 + +# Run gprof profiling +gprof: $(GPROF_TARGET) + @echo "" + @echo "πŸ”¬ Running gprof profiling analysis" + @echo "====================================" + @echo "" + ./$(GPROF_TARGET) 25 + @if [ -f gmon.out ]; then \ + echo ""; \ + echo "πŸ“Š Generating gprof report..."; \ + gprof $(GPROF_TARGET) gmon.out > gprof_report.txt; \ + echo "βœ… gprof report saved to: gprof_report.txt"; \ + echo ""; \ + echo "🎯 Top functions by execution time:"; \ + head -30 gprof_report.txt | tail -20; \ + else \ + echo "❌ No gprof data generated (gmon.out not found)"; \ + fi + +# Valgrind memory profiling +valgrind: $(PROFILER_TARGET) + @echo "" + @echo "🧠 Running Valgrind memory profiling" + @echo "====================================" + @echo "" + valgrind --tool=callgrind --callgrind-out-file=callgrind.out ./$(PROFILER_TARGET) 10 + @echo "" + @echo "βœ… Valgrind profile saved to: callgrind.out" + @echo " View with: kcachegrind callgrind.out" + +# Performance comparison +compare: $(PROFILER_TARGET) + @echo "" + @echo "βš–οΈ Running performance comparison tests" + @echo "=======================================" + @echo "" + @echo "Testing 10 scenarios..." + @time ./$(PROFILER_TARGET) 10 + @echo "" + @echo "Testing 25 scenarios..." + @time ./$(PROFILER_TARGET) 25 + +# Clean build artifacts +clean: + @echo "🧹 Cleaning build artifacts..." + rm -f $(PROFILER_TARGET) $(GPROF_TARGET) + rm -f $(PROVIDER_OBJECTS) + rm -f gmon.out gprof_report.txt callgrind.out + rm -f *.gcda *.gcno *.gcov + @echo "βœ… Clean completed" + +# Install profiler to system (optional) +install: $(PROFILER_TARGET) + @echo "πŸ“¦ Installing profiler to /usr/local/bin..." + sudo cp $(PROFILER_TARGET) /usr/local/bin/graphserver-profile-routing + @echo "βœ… Installed as: graphserver-profile-routing" + +.PHONY: profile profile-25 profile-50 profile-stress gprof valgrind compare install \ No newline at end of file diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 00000000..c09424f0 --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,310 @@ +# GraphServer Core Routing Algorithm Profiling Suite + +This directory contains profiling scripts designed to identify performance bottlenecks in the GraphServer core routing algorithm using real-world UW Campus OSM data. + +## Overview + +The profiling suite focuses on **profiling the C code** rather than the Python wrapper, providing detailed insights into the core routing performance with realistic workloads. + +## Files + +### Core Profiling Scripts + +- **`profile_routing.c`** - C-based profiler that directly exercises core C routing functions +- **`profile_osm_routing.py`** - Python script that uses OSM data but profiles C-level performance +- **`Makefile`** - Build system for compiling and running profilers + +### Key Features + +- **Real-world scenarios**: Uses UW Campus OSM data with realistic routing coordinates +- **C-level profiling**: Focuses on core C algorithm performance, not Python overhead +- **Multiple test modes**: Standard timing, stress testing, memory profiling, gprof integration +- **Comprehensive analysis**: Performance breakdowns, bottleneck identification, optimization recommendations + +## Quick Start + +### 1. Build and Run C Profiler (Recommended) + +```bash +# Build the C profiler +cd scripts/ +make + +# Run with default settings (25 scenarios) +make profile + +# Run with specific number of scenarios +make profile-25 # 25 scenarios (recommended) +make profile-50 # 50 scenarios (intensive) + +# Run with gprof profiling +make gprof + +# Clean build artifacts +make clean +``` + +### 2. Use Python OSM Profiler (Requires Installation) + +First install the Python library: +```bash +cd python/ +pip install -e . +``` + +Then run the profiler: +```bash +cd scripts/ +python profile_osm_routing.py 10 2 # 10 routes, 2 repetitions +python profile_osm_routing.py --cprofile 15 1 # Detailed cProfile analysis +``` + +## Profiling Output + +### C Profiler Output + +``` +πŸš€ GraphServer Core Routing Algorithm Profiler +================================================ + +πŸ—οΈ Setting up routing engine... +βœ… Engine configured with walking provider + Max walking distance: 1200m + Walking speed: 1.3m/s + +πŸ“ Using 15 realistic campus locations for routing scenarios + +🏫 Generating 5 realistic campus routing scenarios... + Route 1: Student Union β†’ South Entrance + βœ… Path found: 3 edges, 3.8 minutes, 0.002 seconds + Route 2: North Gate β†’ Student Union + βœ… Path found: 1 edges, 1.3 minutes, 0.000 seconds + ... + +πŸ“Š Campus Routing Performance Summary: + Scenarios tested: 5 + Successful routes: 5 (100.0%) + Total planning time: 0.003 seconds + Average per route: 0.001 seconds + Total vertices expanded: 87 (avg: 17.4 per route) + Total edges examined: 984 (avg: 196.8 per route) + +πŸ”₯ Running intensive routing stress test... + Stress test completed: 50/50 successful routes in 0.219 seconds + Stress test throughput: 227.8 routes/second + +🧠 Profiling memory usage patterns... + Cycle 1: 4856 bytes peak memory, 1 vertices expanded + ... + +============================================================ +πŸ” CORE ROUTING ALGORITHM PROFILING RESULTS +============================================================ + +⏱️ Function Performance Breakdown: +Function Calls Total(s) Avg(ms) Min(ms) Max(ms) +------------------------------------------------------------------------------------- +total_planning 75 0.641285 8.550 0.002 221.551 + +🎯 Performance Insights: + 🐌 Slowest function: total_planning (99.099% of total time) + πŸ”„ Most called function: total_planning (75 calls) + +πŸ’‘ Optimization Recommendations: + 🎯 HIGH PRIORITY: Optimize total_planning (99.1% of execution time) + +⚑ Total execution time: 0.647 seconds +🏁 Profiling completed! Use results to identify performance bottlenecks. +``` + +### Python OSM Profiler Output + +``` +πŸš€ GraphServer Core Routing Algorithm Profiler +============================================== + +πŸ—οΈ Loading OSM data from python/examples/uw_campus.osm... +βœ… OSM data loaded in 2.45 seconds + Network: 1,234 nodes, 567 ways + +🎯 Profiling 10 routes Γ— 2 repetitions = 20 total routing operations +====================================================================== + +πŸ“Š Repetition 1/2 + Route 1: (47.6591,122.3044) β†’ (47.6591,-122.3043) βœ… 0.023s (12 edges) + ... + +====================================================================== +πŸ” CORE ROUTING ALGORITHM PERFORMANCE ANALYSIS +====================================================================== + +πŸ“Š Overall Statistics: + Total routing operations: 20 + Successful routes: 18 (90.0%) + Total execution time: 4.56 seconds + Average time per route: 0.228 seconds + Throughput: 4.4 routes/second + +🧠 Core Algorithm Performance: + Total vertices expanded: 2,847 + Total edges generated: 15,432 + Avg vertices per route: 158.2 + Avg edges per route: 857.3 + Peak memory usage: 12.3 MB + +⏱️ Timing Analysis: + Fastest route: 0.012 seconds + Slowest route: 0.456 seconds + Average route: 0.228 seconds + Timing variance: 38.0x + +πŸ’Ύ Cache Performance: + Cache hits: 145 + Cache misses: 67 + Hit rate: 68.4% + Provider calls: 1,892 + +🎯 Performance Assessment: + ⚠️ MODERATE: Consider optimization for better performance + βœ… HIGH SUCCESS RATE: Excellent route connectivity +``` + +## Understanding the Results + +### Key Metrics + +1. **Success Rate**: Percentage of routes successfully found +2. **Average Route Time**: Mean time per routing operation +3. **Vertices Expanded**: Core algorithm workload indicator +4. **Memory Usage**: Peak memory consumption patterns +5. **Cache Performance**: Efficiency of edge caching system + +### Performance Assessment + +- **Excellent** (< 50ms): Very fast routing performance +- **Good** (50-100ms): Acceptable routing performance +- **Moderate** (100-200ms): Consider optimization +- **Slow** (> 200ms): Performance optimization recommended + +### Bottleneck Identification + +The profiler identifies functions consuming the most execution time and provides optimization recommendations: + +- **HIGH PRIORITY**: Functions using >25% of execution time +- **MEDIUM PRIORITY**: Functions using 10-25% of execution time + +## Advanced Profiling + +### gprof Integration + +```bash +make gprof +# Generates gprof_report.txt with detailed function call analysis +``` + +### Valgrind Memory Profiling + +```bash +make valgrind +# Requires Valgrind installation +# Generates callgrind.out for detailed memory analysis +``` + +### Performance Comparison + +```bash +make compare +# Runs multiple scenario sizes for performance comparison +``` + +## Customization + +### Modifying Test Scenarios + +Edit the `uw_campus_locations[]` array in `profile_routing.c` to add new test locations: + +```c +static CampusLocation uw_campus_locations[] = { + {47.6590651, -122.3043738, "Central Plaza"}, + {47.6591000, -122.3043000, "Library Entrance"}, + // Add your locations here +}; +``` + +### Adjusting Engine Configuration + +Modify walking parameters in the profiler initialization: + +```c +WalkingConfig walking_config = walking_config_default(); +walking_config.max_walking_distance = 1200.0; // Adjust max distance +walking_config.walking_speed_mps = 1.3; // Adjust walking speed +``` + +## Requirements + +### C Profiler +- CMake 3.12+ +- GCC with C99 support +- Built GraphServer core library + +### Python Profiler +- Python 3.8+ +- GraphServer Python library: `pip install graphserver[osm]` +- UW Campus OSM data (included) + +## Troubleshooting + +### Build Issues + +```bash +# Ensure core library is built first +cd core/ +mkdir build && cd build +cmake .. && make + +# Clean and rebuild profiler +cd scripts/ +make clean && make +``` + +### Missing Dependencies + +```bash +# Install build tools +sudo apt-get install build-essential cmake + +# Install Python dependencies +pip install graphserver[osm] +``` + +### No Routes Found + +- Check that OSM data contains connected pedestrian paths +- Verify coordinate pairs are within the OSM data bounds +- Increase `max_walking_distance` in walking configuration + +## Performance Optimization Tips + +Based on profiling results: + +1. **High vertex expansion**: Consider implementing A* with better heuristics +2. **Memory growth**: Investigate arena allocation efficiency +3. **Cache misses**: Optimize edge caching strategy +4. **Slow individual routes**: Profile specific routing scenarios + +## Contributing + +When adding new profiling capabilities: + +1. Focus on C-level performance rather than Python overhead +2. Use realistic routing scenarios based on real OSM data +3. Provide clear performance assessments and recommendations +4. Include both timing and memory analysis + +## Files Generated + +- `profile_routing` - Compiled C profiler executable +- `gprof_report.txt` - gprof function analysis (if using `make gprof`) +- `callgrind.out` - Valgrind memory profile (if using `make valgrind`) +- `routing_profile.prof` - cProfile data (if using Python `--cprofile`) \ No newline at end of file diff --git a/scripts/profile_osm_routing.py b/scripts/profile_osm_routing.py new file mode 100644 index 00000000..f7da2999 --- /dev/null +++ b/scripts/profile_osm_routing.py @@ -0,0 +1,466 @@ +#!/usr/bin/env python3 +""" +OSM-based Profiling Script for GraphServer Core Routing Algorithm + +This script uses the UW Campus OSM data to create realistic routing scenarios +that exercise the core C routing functions. It focuses on profiling the C +implementation by measuring performance at the C layer while leveraging +Python's OSM parsing capabilities. + +Usage: + python profile_osm_routing.py [num_routes] [repetitions] + +Examples: + python profile_osm_routing.py # Default: 20 routes, 3 repetitions + python profile_osm_routing.py 50 5 # 50 routes, 5 repetitions + +Requirements: + pip install graphserver[osm] +""" + +import argparse +import cProfile +import pstats +import sys +import time +import tracemalloc +from pathlib import Path +from typing import Any, Dict, List, Tuple + +try: + from graphserver import Engine, Vertex + from graphserver.providers.osm import OSMAccessProvider, OSMNetworkProvider + from graphserver.providers.osm.types import WalkingProfile +except ImportError as e: + print(f"❌ Error importing required modules: {e}") + print("Please install with: pip install graphserver[osm]") + sys.exit(1) + + +class RoutingProfiler: + """Profiler for core GraphServer routing functions using real OSM data.""" + + def __init__(self, osm_file: Path): + self.osm_file = osm_file + self.engine = None + self.network_provider = None + self.access_provider = None + + # Performance tracking + self.route_times: List[float] = [] + self.successful_routes = 0 + self.total_vertices_expanded = 0 + self.total_edges_generated = 0 + self.memory_usage = [] + + # UW Campus coordinate pairs for realistic routing + self.campus_routes = [ + # Main campus routes with known connectivity + ((47.6590651, -122.3043738), (47.6591000, -122.3043000)), # Central area + ((47.6588000, -122.3045000), (47.6593000, -122.3040000)), # Engineering to Union + ((47.6585000, -122.3050000), (47.6595000, -122.3035000)), # Science to Admin + ((47.6583000, -122.3055000), (47.6597000, -122.3030000)), # Arts to Sports + ((47.6580000, -122.3060000), (47.6600000, -122.3025000)), # Parking to North + ((47.6575000, -122.3065000), (47.6605000, -122.3020000)), # South to Research + ((47.6570000, -122.3070000), (47.6610000, -122.3015000)), # Dorm to Conference + ((47.6565000, -122.3075000), (47.6590651, -122.3043738)), # Medical to Central + + # Reverse routes for testing bidirectionality + ((47.6591000, -122.3043000), (47.6590651, -122.3043738)), # Central area reverse + ((47.6593000, -122.3040000), (47.6588000, -122.3045000)), # Union to Engineering + ((47.6595000, -122.3035000), (47.6585000, -122.3050000)), # Admin to Science + ((47.6597000, -122.3030000), (47.6583000, -122.3055000)), # Sports to Arts + + # Longer distance routes across campus + ((47.6565000, -122.3075000), (47.6610000, -122.3015000)), # Medical to Conference + ((47.6570000, -122.3070000), (47.6600000, -122.3025000)), # Dorm to North Gate + ((47.6575000, -122.3065000), (47.6597000, -122.3030000)), # South to Sports + ((47.6580000, -122.3060000), (47.6605000, -122.3020000)), # Parking to Research + + # Cross-campus diagonal routes + ((47.6565000, -122.3075000), (47.6605000, -122.3020000)), # SW to NE + ((47.6570000, -122.3070000), (47.6597000, -122.3030000)), # SW to NE + ((47.6610000, -122.3015000), (47.6575000, -122.3065000)), # NE to SW + ((47.6600000, -122.3025000), (47.6580000, -122.3060000)), # N to S + ] + + def setup_engine(self) -> bool: + """Initialize the GraphServer engine with OSM providers.""" + print(f"πŸ—οΈ Loading OSM data from {self.osm_file}...") + + try: + start_time = time.time() + + # Create walking profile optimized for campus routing + walking_profile = WalkingProfile( + base_speed_ms=1.3, # Realistic walking speed + avoid_stairs=False, # Allow stairs on campus + avoid_busy_roads=True, # Prefer pedestrian paths + max_detour_factor=1.4 # Allow reasonable detours + ) + + # Initialize OSM providers + self.network_provider = OSMNetworkProvider( + self.osm_file, + walking_profile=walking_profile + ) + + self.access_provider = OSMAccessProvider( + self.network_provider.parser, + walking_profile=walking_profile, + search_radius_m=200.0, # Larger radius for campus + max_nearby_nodes=8 # More options for better routing + ) + + load_time = time.time() - start_time + + print(f"βœ… OSM data loaded in {load_time:.2f} seconds") + print(f" Network: {self.network_provider.node_count:,} nodes, " + f"{self.network_provider.way_count:,} ways") + + # Create engine with optimized configuration + self.engine = Engine(enable_edge_caching=True) # Enable caching for performance + + # Register providers + self.engine.register_provider("osm_network", self.network_provider) + self.engine.register_provider("osm_access", self.access_provider) + + print("πŸ”§ Engine configured with OSM providers and caching enabled") + return True + + except Exception as e: + print(f"❌ Error setting up engine: {e}") + return False + + def run_single_route(self, start_coords: Tuple[float, float], + goal_coords: Tuple[float, float]) -> Tuple[bool, float, Dict[str, Any]]: + """Run a single routing scenario and collect performance metrics.""" + start_lat, start_lon = start_coords + goal_lat, goal_lon = goal_coords + + route_start_time = time.time() + + try: + # Create vertices + start_vertex = Vertex({"lat": start_lat, "lon": start_lon}) + goal_vertex = Vertex({"lat": goal_lat, "lon": goal_lon}) + + # Link vertices to OSM network + self.access_provider.link(start_vertex, start_lat, start_lon) + self.access_provider.link(goal_vertex, goal_lat, goal_lon) + + # Get connected OSM nodes + start_edges = self.access_provider.out_edges(start_vertex) + goal_edges = self.access_provider.out_edges(goal_vertex) + + route_found = False + best_result = None + best_cost = float('inf') + + if start_edges and goal_edges: + # Try different combinations to find best route + for start_osm_vertex, start_edge in start_edges[:3]: # Limit for performance + for goal_osm_vertex, goal_edge in goal_edges[:3]: + try: + # This is where the core C routing happens + osm_result = self.engine.plan( + start=start_osm_vertex, + goal=goal_osm_vertex + ) + + if osm_result and len(osm_result) > 0: + total_cost = (start_edge.cost + + osm_result.total_cost + + goal_edge.cost) + + if total_cost < best_cost: + best_result = osm_result + best_cost = total_cost + route_found = True + break + except Exception: + continue + if route_found: + break + + route_time = time.time() - route_start_time + + # Get engine statistics (this reflects C-level performance) + engine_stats = self.engine.get_stats() + + # Clear links to avoid interference + self.access_provider.clear_links() + + metrics = { + 'route_time': route_time, + 'path_length': len(best_result) if best_result else 0, + 'total_cost': best_cost if route_found else 0, + 'vertices_expanded': engine_stats.vertices_expanded, + 'edges_generated': engine_stats.edges_generated, + 'cache_hits': engine_stats.cache_hits, + 'cache_misses': engine_stats.cache_misses, + 'providers_called': engine_stats.providers_called + } + + return route_found, route_time, metrics + + except Exception as e: + print(f"⚠️ Route failed: {e}") + return False, time.time() - route_start_time, {} + + def profile_routing_scenarios(self, num_routes: int = 20, repetitions: int = 3): + """Profile multiple routing scenarios with repetitions.""" + print(f"\n🎯 Profiling {num_routes} routes Γ— {repetitions} repetitions = " + f"{num_routes * repetitions} total routing operations") + print("="*70) + + # Track memory usage + tracemalloc.start() + + total_start_time = time.time() + + for rep in range(repetitions): + print(f"\nπŸ“Š Repetition {rep + 1}/{repetitions}") + rep_start_time = time.time() + + rep_successful = 0 + rep_total_time = 0.0 + + for i, (start_coords, goal_coords) in enumerate(self.campus_routes[:num_routes]): + print(f" Route {i+1:2d}: " + f"({start_coords[0]:.4f},{start_coords[1]:.4f}) β†’ " + f"({goal_coords[0]:.4f},{goal_coords[1]:.4f})", end=" ") + + success, route_time, metrics = self.run_single_route(start_coords, goal_coords) + + if success: + rep_successful += 1 + self.successful_routes += 1 + print(f"βœ… {route_time:.3f}s ({metrics.get('path_length', 0)} edges)") + + # Track metrics + self.total_vertices_expanded += metrics.get('vertices_expanded', 0) + self.total_edges_generated += metrics.get('edges_generated', 0) + else: + print(f"❌ {route_time:.3f}s") + + self.route_times.append(route_time) + rep_total_time += route_time + + # Memory snapshot every 10 routes + if (i + 1) % 10 == 0: + current, peak = tracemalloc.get_traced_memory() + self.memory_usage.append(peak) + + rep_time = time.time() - rep_start_time + print(f" Repetition {rep+1} summary: {rep_successful}/{num_routes} successful " + f"in {rep_time:.2f}s (avg: {rep_total_time/num_routes:.3f}s per route)") + + total_time = time.time() - total_start_time + + # Final memory measurement + current, peak = tracemalloc.get_traced_memory() + tracemalloc.stop() + + self._print_performance_summary(num_routes, repetitions, total_time, peak) + + return { + 'total_routes': num_routes * repetitions, + 'successful_routes': self.successful_routes, + 'total_time': total_time, + 'route_times': self.route_times, + 'total_vertices_expanded': self.total_vertices_expanded, + 'total_edges_generated': self.total_edges_generated, + 'peak_memory_mb': peak / 1024 / 1024 + } + + def _print_performance_summary(self, num_routes: int, repetitions: int, + total_time: float, peak_memory: int): + """Print comprehensive performance analysis.""" + print(f"\n" + "="*70) + print("πŸ” CORE ROUTING ALGORITHM PERFORMANCE ANALYSIS") + print("="*70) + + total_operations = num_routes * repetitions + success_rate = (self.successful_routes / total_operations) * 100 + + print(f"\nπŸ“Š Overall Statistics:") + print(f" Total routing operations: {total_operations}") + print(f" Successful routes: {self.successful_routes} ({success_rate:.1f}%)") + print(f" Total execution time: {total_time:.2f} seconds") + print(f" Average time per route: {sum(self.route_times)/len(self.route_times):.3f} seconds") + print(f" Throughput: {total_operations/total_time:.1f} routes/second") + + print(f"\n🧠 Core Algorithm Performance:") + print(f" Total vertices expanded: {self.total_vertices_expanded:,}") + print(f" Total edges generated: {self.total_edges_generated:,}") + print(f" Avg vertices per route: {self.total_vertices_expanded/self.successful_routes:.1f}") + print(f" Avg edges per route: {self.total_edges_generated/self.successful_routes:.1f}") + print(f" Peak memory usage: {peak_memory/1024/1024:.1f} MB") + + # Timing analysis + if self.route_times: + min_time = min(self.route_times) + max_time = max(self.route_times) + avg_time = sum(self.route_times) / len(self.route_times) + + print(f"\n⏱️ Timing Analysis:") + print(f" Fastest route: {min_time:.3f} seconds") + print(f" Slowest route: {max_time:.3f} seconds") + print(f" Average route: {avg_time:.3f} seconds") + print(f" Timing variance: {max_time/min_time:.1f}x") + + # Cache performance (if available) + if self.engine: + stats = self.engine.get_stats() + total_cache_ops = stats.cache_hits + stats.cache_misses + hit_rate = (stats.cache_hits / total_cache_ops * 100) if total_cache_ops > 0 else 0 + + print(f"\nπŸ’Ύ Cache Performance:") + print(f" Cache hits: {stats.cache_hits:,}") + print(f" Cache misses: {stats.cache_misses:,}") + print(f" Hit rate: {hit_rate:.1f}%") + print(f" Provider calls: {stats.providers_called:,}") + + print(f"\n🎯 Performance Assessment:") + if avg_time < 0.050: + print(" βœ… EXCELLENT: Very fast routing performance") + elif avg_time < 0.100: + print(" βœ… GOOD: Acceptable routing performance") + elif avg_time < 0.200: + print(" ⚠️ MODERATE: Consider optimization for better performance") + else: + print(" ❌ SLOW: Performance optimization recommended") + + if success_rate >= 90: + print(" βœ… HIGH SUCCESS RATE: Excellent route connectivity") + elif success_rate >= 70: + print(" ⚠️ MODERATE SUCCESS RATE: Some routes not found") + else: + print(" ❌ LOW SUCCESS RATE: Check network connectivity") + + +def run_cprofile_analysis(profiler: RoutingProfiler, num_routes: int, repetitions: int): + """Run cProfile analysis focusing on C-level function calls.""" + print(f"\nπŸ”¬ Running detailed cProfile analysis...") + + # Create a profiler instance + pr = cProfile.Profile() + + # Profile the routing operations + pr.enable() + results = profiler.profile_routing_scenarios(num_routes, repetitions) + pr.disable() + + # Generate profile report + stats = pstats.Stats(pr) + stats.sort_stats('cumulative') + + print(f"\nπŸ“ˆ Top functions by cumulative time:") + stats.print_stats(20) # Top 20 functions + + # Save detailed profile + profile_file = "routing_profile.prof" + stats.dump_stats(profile_file) + print(f"\nπŸ’Ύ Detailed profile saved to: {profile_file}") + print(f" View with: python -m pstats {profile_file}") + + return results + + +def main(): + """Main profiling script entry point.""" + parser = argparse.ArgumentParser( + description="Profile GraphServer core routing algorithm with UW Campus OSM data", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +Examples: + python profile_osm_routing.py # Default: 20 routes, 3 repetitions + python profile_osm_routing.py 30 # 30 routes, 3 repetitions + python profile_osm_routing.py 50 5 # 50 routes, 5 repetitions + python profile_osm_routing.py --cprofile 25 2 # Detailed cProfile analysis + """ + ) + + parser.add_argument( + "num_routes", + type=int, + nargs="?", + default=20, + help="Number of different routes to test (default: 20, max: 20)" + ) + + parser.add_argument( + "repetitions", + type=int, + nargs="?", + default=3, + help="Number of repetitions per route (default: 3)" + ) + + parser.add_argument( + "--osm-file", + type=str, + default="python/examples/uw_campus.osm", + help="Path to OSM file (default: python/examples/uw_campus.osm)" + ) + + parser.add_argument( + "--cprofile", + action="store_true", + help="Run detailed cProfile analysis (slower but more detailed)" + ) + + args = parser.parse_args() + + # Validate arguments + if args.num_routes < 1 or args.num_routes > 20: + print(f"❌ Number of routes must be between 1 and 20 (got {args.num_routes})") + sys.exit(1) + + if args.repetitions < 1 or args.repetitions > 10: + print(f"❌ Repetitions must be between 1 and 10 (got {args.repetitions})") + sys.exit(1) + + # Check OSM file exists + osm_file = Path(args.osm_file) + if not osm_file.exists(): + print(f"❌ OSM file not found: {osm_file}") + print(" Expected UW Campus OSM data at: python/examples/uw_campus.osm") + sys.exit(1) + + print("πŸš€ GraphServer Core Routing Algorithm Profiler") + print("==============================================") + print(f"πŸ“ OSM file: {osm_file}") + print(f"πŸ—ΊοΈ Routes to test: {args.num_routes}") + print(f"πŸ”„ Repetitions: {args.repetitions}") + print(f"πŸ“Š Analysis mode: {'Detailed cProfile' if args.cprofile else 'Standard timing'}") + + # Initialize profiler + profiler = RoutingProfiler(osm_file) + + if not profiler.setup_engine(): + sys.exit(1) + + # Run profiling + try: + if args.cprofile: + results = run_cprofile_analysis(profiler, args.num_routes, args.repetitions) + else: + results = profiler.profile_routing_scenarios(args.num_routes, args.repetitions) + + print(f"\nπŸŽ‰ Profiling completed successfully!") + print(f" Results focus on core C routing algorithm performance") + + except KeyboardInterrupt: + print(f"\n⏸️ Profiling interrupted by user") + sys.exit(1) + except Exception as e: + print(f"❌ Profiling error: {e}") + import traceback + traceback.print_exc() + sys.exit(1) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/scripts/profile_routing b/scripts/profile_routing new file mode 100755 index 00000000..5980f2ad Binary files /dev/null and b/scripts/profile_routing differ diff --git a/scripts/profile_routing.c b/scripts/profile_routing.c new file mode 100644 index 00000000..121fdd25 --- /dev/null +++ b/scripts/profile_routing.c @@ -0,0 +1,453 @@ +#define _POSIX_C_SOURCE 199309L // For clock_gettime +#include +#include +#include +#include +#include +#include +#include +#include "../core/include/graphserver.h" +#include "../core/include/gs_string_dict.h" +#include "../core/include/gs_common_keys.h" +#include "../examples/include/example_providers.h" + +/** + * @file profile_routing.c + * @brief Profiling script for core routing algorithm with UW Campus OSM data + * + * This script profiles the core C routing functions in real-world scenarios + * using the UW Campus OSM data. It focuses on profiling the C implementation + * rather than the Python wrapper. + */ + +// Profiling utilities +typedef struct { + struct timespec start_time; + struct timespec end_time; + double elapsed_seconds; +} PrecisionTimer; + +typedef struct { + const char* function_name; + double total_time; + size_t call_count; + double min_time; + double max_time; +} ProfileEntry; + +typedef struct { + ProfileEntry entries[32]; + size_t entry_count; + double total_execution_time; +} ProfileData; + +static ProfileData global_profile = {0}; + +// High-precision timing functions +static PrecisionTimer timer_start_precise(void) { + PrecisionTimer timer; + clock_gettime(CLOCK_MONOTONIC, &timer.start_time); + return timer; +} + +static void timer_end_precise(PrecisionTimer* timer) { + clock_gettime(CLOCK_MONOTONIC, &timer->end_time); + timer->elapsed_seconds = (timer->end_time.tv_sec - timer->start_time.tv_sec) + + (timer->end_time.tv_nsec - timer->start_time.tv_nsec) / 1e9; +} + +// Profile tracking functions +static void profile_record(const char* function_name, double elapsed_time) { + // Find existing entry or create new one + ProfileEntry* entry = NULL; + for (size_t i = 0; i < global_profile.entry_count; i++) { + if (strcmp(global_profile.entries[i].function_name, function_name) == 0) { + entry = &global_profile.entries[i]; + break; + } + } + + if (!entry && global_profile.entry_count < 32) { + entry = &global_profile.entries[global_profile.entry_count++]; + entry->function_name = function_name; + entry->total_time = 0.0; + entry->call_count = 0; + entry->min_time = INFINITY; + entry->max_time = 0.0; + } + + if (entry) { + entry->total_time += elapsed_time; + entry->call_count++; + if (elapsed_time < entry->min_time) entry->min_time = elapsed_time; + if (elapsed_time > entry->max_time) entry->max_time = elapsed_time; + } +} + +// Campus coordinates from UW Campus OSM data (realistic routing locations) +typedef struct { + double lat; + double lon; + const char* name; +} CampusLocation; + +static CampusLocation uw_campus_locations[] = { + {47.6590651, -122.3043738, "Central Plaza"}, + {47.6591000, -122.3043000, "Library Entrance"}, + {47.6588000, -122.3045000, "Engineering Building"}, + {47.6593000, -122.3040000, "Student Union"}, + {47.6585000, -122.3050000, "Science Building"}, + {47.6595000, -122.3035000, "Admin Building"}, + {47.6583000, -122.3055000, "Arts Building"}, + {47.6597000, -122.3030000, "Sports Center"}, + {47.6580000, -122.3060000, "Parking Structure"}, + {47.6600000, -122.3025000, "North Gate"}, + {47.6575000, -122.3065000, "South Entrance"}, + {47.6605000, -122.3020000, "Research Lab"}, + {47.6570000, -122.3070000, "Dormitory Complex"}, + {47.6610000, -122.3015000, "Conference Center"}, + {47.6565000, -122.3075000, "Medical Center"}, +}; + +static const size_t num_campus_locations = sizeof(uw_campus_locations) / sizeof(uw_campus_locations[0]); + +// Enhanced profiled planning function +static GraphserverPath* profiled_plan_route( + GraphserverEngine* engine, + GraphserverVertex* start, + GraphserverVertex* goal, + GraphserverPlanStats* stats) { + + PrecisionTimer total_timer = timer_start_precise(); + + // Use location goal for realistic campus routing + GraphserverValue lat_val, lon_val; + if (gs_vertex_get_value(goal, GS_KEY_LAT, &lat_val) != GS_SUCCESS || + gs_vertex_get_value(goal, GS_KEY_LON, &lon_val) != GS_SUCCESS) { + return NULL; + } + + LocationGoal location_goal = { + lat_val.as.f_val, + lon_val.as.f_val, + 100.0 // 100m tolerance for campus routing + }; + + GraphserverPath* path = gs_plan_simple(engine, start, location_goal_predicate, &location_goal, stats); + + timer_end_precise(&total_timer); + profile_record("total_planning", total_timer.elapsed_seconds); + + return path; +} + +// Create realistic campus routing scenarios +static void generate_campus_routing_scenarios( + GraphserverEngine* engine, + size_t num_scenarios) { + + printf("\n🏫 Generating %zu realistic campus routing scenarios...\n", num_scenarios); + + size_t successful_routes = 0; + size_t total_vertices_expanded = 0; + size_t total_edges_examined = 0; + double total_planning_time = 0.0; + + for (size_t i = 0; i < num_scenarios; i++) { + // Select random start and goal locations from campus + size_t start_idx = rand() % num_campus_locations; + size_t goal_idx = rand() % num_campus_locations; + + // Ensure start and goal are different + while (goal_idx == start_idx) { + goal_idx = rand() % num_campus_locations; + } + + CampusLocation start_loc = uw_campus_locations[start_idx]; + CampusLocation goal_loc = uw_campus_locations[goal_idx]; + + printf(" Route %zu: %s β†’ %s\n", i + 1, start_loc.name, goal_loc.name); + + // Create vertices for routing + GraphserverVertex* start = create_location_vertex(start_loc.lat, start_loc.lon, time(NULL)); + GraphserverVertex* goal = create_location_vertex(goal_loc.lat, goal_loc.lon, time(NULL)); + + PrecisionTimer route_timer = timer_start_precise(); + + // Plan route with profiling + GraphserverPlanStats stats; + GraphserverPath* path = profiled_plan_route(engine, start, goal, &stats); + + timer_end_precise(&route_timer); + total_planning_time += route_timer.elapsed_seconds; + + if (path) { + successful_routes++; + size_t path_length = gs_path_get_num_edges(path); + const double* total_cost = gs_path_get_total_cost(path); + + printf(" βœ… Path found: %zu edges, %.1f minutes, %.3f seconds\n", + path_length, + total_cost ? total_cost[0] : 0.0, + route_timer.elapsed_seconds); + + gs_path_destroy(path); + } else { + printf(" ❌ No path found (%.3f seconds)\n", route_timer.elapsed_seconds); + } + + total_vertices_expanded += stats.vertices_expanded; + total_edges_examined += stats.edges_generated; + + // Cleanup + gs_vertex_destroy(start); + gs_vertex_destroy(goal); + + // Small delay to avoid overwhelming the system + usleep(1000); // 1ms delay + } + + printf("\nπŸ“Š Campus Routing Performance Summary:\n"); + printf(" Scenarios tested: %zu\n", num_scenarios); + printf(" Successful routes: %zu (%.1f%%)\n", + successful_routes, (successful_routes * 100.0) / num_scenarios); + printf(" Total planning time: %.3f seconds\n", total_planning_time); + printf(" Average per route: %.3f seconds\n", total_planning_time / num_scenarios); + printf(" Total vertices expanded: %zu (avg: %.1f per route)\n", + total_vertices_expanded, (double)total_vertices_expanded / num_scenarios); + printf(" Total edges examined: %zu (avg: %.1f per route)\n", + total_edges_examined, (double)total_edges_examined / num_scenarios); +} + +// Stress test with intensive routing scenarios (remove unused parameter) +static void stress_test_routing_performance(void) { + printf("\nπŸ”₯ Running intensive routing stress test...\n"); + + const size_t STRESS_SCENARIOS = 50; + PrecisionTimer stress_timer = timer_start_precise(); + + // Create multiple engines to test concurrency simulation + GraphserverEngine* engines[4]; + for (int i = 0; i < 4; i++) { + engines[i] = gs_engine_create(); + + // Add providers with different configurations + WalkingConfig walking_config = walking_config_default(); + walking_config.max_walking_distance = 1000.0 + (i * 200.0); // Vary max distance + walking_config.walking_speed_mps = 1.2 + (i * 0.1); // Vary speed + + WalkingConfig* config_ptr = malloc(sizeof(WalkingConfig)); + *config_ptr = walking_config; + gs_engine_register_provider(engines[i], "walking", walking_provider, config_ptr); + } + + size_t total_stress_routes = 0; + + // Run scenarios across different engine configurations + for (size_t scenario = 0; scenario < STRESS_SCENARIOS; scenario++) { + GraphserverEngine* test_engine = engines[scenario % 4]; + + // Use distant locations for stress testing + CampusLocation start_loc = uw_campus_locations[scenario % num_campus_locations]; + CampusLocation goal_loc = uw_campus_locations[(scenario + 7) % num_campus_locations]; + + GraphserverVertex* start = create_location_vertex(start_loc.lat, start_loc.lon, time(NULL)); + GraphserverVertex* goal = create_location_vertex(goal_loc.lat, goal_loc.lon, time(NULL)); + + GraphserverPlanStats stats; + GraphserverPath* path = profiled_plan_route(test_engine, start, goal, &stats); + + if (path) { + total_stress_routes++; + gs_path_destroy(path); + } + + gs_vertex_destroy(start); + gs_vertex_destroy(goal); + + if (scenario % 10 == 0) { + printf(" Completed %zu/%zu stress scenarios\n", scenario + 1, STRESS_SCENARIOS); + } + } + + timer_end_precise(&stress_timer); + + printf(" Stress test completed: %zu/%zu successful routes in %.3f seconds\n", + total_stress_routes, STRESS_SCENARIOS, stress_timer.elapsed_seconds); + printf(" Stress test throughput: %.1f routes/second\n", + STRESS_SCENARIOS / stress_timer.elapsed_seconds); + + // Cleanup engines + for (int i = 0; i < 4; i++) { + gs_engine_destroy(engines[i]); + } +} + +// Memory usage profiling +static void profile_memory_usage(GraphserverEngine* engine) { + printf("\n🧠 Profiling memory usage patterns...\n"); + + size_t baseline_memory = 0; // Would need system-specific memory measurement + + // Test memory growth over multiple planning cycles + for (int cycle = 0; cycle < 20; cycle++) { + CampusLocation start_loc = uw_campus_locations[cycle % num_campus_locations]; + CampusLocation goal_loc = uw_campus_locations[(cycle + 3) % num_campus_locations]; + + GraphserverVertex* start = create_location_vertex(start_loc.lat, start_loc.lon, time(NULL)); + GraphserverVertex* goal = create_location_vertex(goal_loc.lat, goal_loc.lon, time(NULL)); + + GraphserverPlanStats stats; + GraphserverPath* path = profiled_plan_route(engine, start, goal, &stats); + + printf(" Cycle %d: %zu bytes peak memory, %zu vertices expanded\n", + cycle + 1, stats.peak_memory_usage, stats.vertices_expanded); + + if (path) gs_path_destroy(path); + gs_vertex_destroy(start); + gs_vertex_destroy(goal); + } +} + +// Print comprehensive profiling results +static void print_profile_results(void) { + printf("\n"); + for (int i = 0; i < 60; i++) printf("="); + printf("\n"); + printf("πŸ” CORE ROUTING ALGORITHM PROFILING RESULTS\n"); + for (int i = 0; i < 60; i++) printf("="); + printf("\n"); + + printf("\n⏱️ Function Performance Breakdown:\n"); + printf("%-25s %10s %12s %12s %12s %12s\n", + "Function", "Calls", "Total(s)", "Avg(ms)", "Min(ms)", "Max(ms)"); + for (int i = 0; i < 85; i++) printf("-"); + printf("\n"); + + for (size_t i = 0; i < global_profile.entry_count; i++) { + ProfileEntry* entry = &global_profile.entries[i]; + double avg_ms = (entry->total_time * 1000.0) / entry->call_count; + double min_ms = entry->min_time * 1000.0; + double max_ms = entry->max_time * 1000.0; + + printf("%-25s %10zu %12.6f %12.3f %12.3f %12.3f\n", + entry->function_name, + entry->call_count, + entry->total_time, + avg_ms, + min_ms, + max_ms); + } + + printf("\n🎯 Performance Insights:\n"); + + // Find bottlenecks + ProfileEntry* slowest = NULL; + ProfileEntry* most_called = NULL; + + for (size_t i = 0; i < global_profile.entry_count; i++) { + ProfileEntry* entry = &global_profile.entries[i]; + + if (!slowest || entry->total_time > slowest->total_time) { + slowest = entry; + } + + if (!most_called || entry->call_count > most_called->call_count) { + most_called = entry; + } + } + + if (slowest) { + printf(" 🐌 Slowest function: %s (%.3f%% of total time)\n", + slowest->function_name, + (slowest->total_time / global_profile.total_execution_time) * 100.0); + } + + if (most_called) { + printf(" πŸ”„ Most called function: %s (%zu calls)\n", + most_called->function_name, most_called->call_count); + } + + printf("\nπŸ’‘ Optimization Recommendations:\n"); + for (size_t i = 0; i < global_profile.entry_count; i++) { + ProfileEntry* entry = &global_profile.entries[i]; + double time_percentage = (entry->total_time / global_profile.total_execution_time) * 100.0; + + if (time_percentage > 25.0) { + printf(" 🎯 HIGH PRIORITY: Optimize %s (%.1f%% of execution time)\n", + entry->function_name, time_percentage); + } else if (time_percentage > 10.0) { + printf(" πŸ“ˆ MEDIUM PRIORITY: Consider optimizing %s (%.1f%% of execution time)\n", + entry->function_name, time_percentage); + } + } +} + +int main(int argc, char* argv[]) { + printf("πŸš€ GraphServer Core Routing Algorithm Profiler\n"); + printf("================================================\n"); + printf("Profiling core C routing functions with UW Campus OSM data scenarios\n\n"); + + // Initialize random seed + srand((unsigned int)time(NULL)); + + // Initialize GraphServer + gs_string_dict_init(); + gs_common_keys_init(); + + PrecisionTimer main_timer = timer_start_precise(); + + // Create engine with realistic configuration + printf("πŸ—οΈ Setting up routing engine...\n"); + GraphserverEngine* engine = gs_engine_create(); + + // Configure walking provider for campus routing + WalkingConfig walking_config = walking_config_default(); + walking_config.max_walking_distance = 1200.0; // Suitable for campus distances + walking_config.walking_speed_mps = 1.3; // Realistic walking speed (m/s) + + WalkingConfig* config_ptr = malloc(sizeof(WalkingConfig)); + *config_ptr = walking_config; + gs_engine_register_provider(engine, "walking", walking_provider, config_ptr); + + printf("βœ… Engine configured with walking provider\n"); + printf(" Max walking distance: %.0fm\n", walking_config.max_walking_distance); + printf(" Walking speed: %.1fm/s\n", walking_config.walking_speed_mps); + + // Determine number of scenarios from command line or use default + size_t num_scenarios = 25; + if (argc > 1) { + num_scenarios = (size_t)atoi(argv[1]); + if (num_scenarios < 1 || num_scenarios > 200) { + printf("⚠️ Warning: Using default 25 scenarios (requested %zu out of range)\n", num_scenarios); + num_scenarios = 25; + } + } + + printf("\nπŸ“ Using %zu realistic campus locations for routing scenarios\n", num_campus_locations); + + // Run profiling scenarios + generate_campus_routing_scenarios(engine, num_scenarios); + + // Run stress test + stress_test_routing_performance(); + + // Profile memory usage + profile_memory_usage(engine); + + timer_end_precise(&main_timer); + global_profile.total_execution_time = main_timer.elapsed_seconds; + + // Print results + print_profile_results(); + + printf("\n⚑ Total execution time: %.3f seconds\n", main_timer.elapsed_seconds); + printf("🏁 Profiling completed! Use results to identify performance bottlenecks.\n"); + + // Cleanup + free(config_ptr); + gs_engine_destroy(engine); + gs_string_dict_cleanup(); + + return 0; +} \ No newline at end of file diff --git a/scripts/run_profiler.sh b/scripts/run_profiler.sh new file mode 100755 index 00000000..924cc72e --- /dev/null +++ b/scripts/run_profiler.sh @@ -0,0 +1,215 @@ +#!/bin/bash + +# GraphServer Routing Profiler Runner +# Convenience script for running performance profiling + +set -e # Exit on error + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +print_header() { + echo -e "${BLUE}" + echo "========================================================" + echo " GraphServer Core Routing Algorithm Profiler" + echo "========================================================" + echo -e "${NC}" +} + +print_usage() { + echo "Usage: $0 [OPTION] [SCENARIOS]" + echo "" + echo "Options:" + echo " quick Quick test with 5 scenarios (default)" + echo " standard Standard test with 25 scenarios" + echo " intensive Intensive test with 50 scenarios" + echo " stress Stress test with 100 scenarios" + echo " gprof Run with gprof profiling (25 scenarios)" + echo " valgrind Run with Valgrind memory profiling (10 scenarios)" + echo " compare Performance comparison across scenario counts" + echo " build Build profiler only (no execution)" + echo " clean Clean build artifacts" + echo " help Show this help message" + echo "" + echo "Custom scenarios:" + echo " $0 [NUMBER] Run with specific number of scenarios (1-200)" + echo "" + echo "Examples:" + echo " $0 # Quick test (5 scenarios)" + echo " $0 standard # Standard test (25 scenarios)" + echo " $0 intensive # Intensive test (50 scenarios)" + echo " $0 30 # Custom test (30 scenarios)" + echo " $0 gprof # gprof profiling" + echo " $0 compare # Performance comparison" +} + +check_dependencies() { + # Check if make is available + if ! command -v make &> /dev/null; then + echo -e "${RED}❌ Error: 'make' command not found${NC}" + echo "Please install build-essential: sudo apt-get install build-essential" + exit 1 + fi + + # Check if gcc is available + if ! command -v gcc &> /dev/null; then + echo -e "${RED}❌ Error: 'gcc' compiler not found${NC}" + echo "Please install gcc: sudo apt-get install build-essential" + exit 1 + fi + + echo -e "${GREEN}βœ… Dependencies check passed${NC}" +} + +build_profiler() { + echo -e "${YELLOW}πŸ”¨ Building profiler...${NC}" + + if ! make build-core > /dev/null 2>&1; then + echo -e "${RED}❌ Failed to build core library${NC}" + echo "Try running: cd ../core && mkdir build && cd build && cmake .. && make" + exit 1 + fi + + if ! make > /dev/null 2>&1; then + echo -e "${RED}❌ Failed to build profiler${NC}" + echo "Check build output with: make" + exit 1 + fi + + echo -e "${GREEN}βœ… Profiler built successfully${NC}" +} + +run_profiler() { + local scenarios=$1 + local mode=$2 + + echo -e "${BLUE}πŸš€ Running profiler with ${scenarios} scenarios...${NC}" + echo "" + + case $mode in + "gprof") + make gprof SCENARIOS=$scenarios + ;; + "valgrind") + if ! command -v valgrind &> /dev/null; then + echo -e "${YELLOW}⚠️ Valgrind not found, installing...${NC}" + sudo apt-get update && sudo apt-get install -y valgrind + fi + make valgrind SCENARIOS=$scenarios + ;; + *) + ./profile_routing $scenarios + ;; + esac +} + +run_comparison() { + echo -e "${BLUE}βš–οΈ Running performance comparison...${NC}" + echo "" + + echo -e "${YELLOW}Testing 5 scenarios:${NC}" + time ./profile_routing 5 | tail -5 + + echo "" + echo -e "${YELLOW}Testing 15 scenarios:${NC}" + time ./profile_routing 15 | tail -5 + + echo "" + echo -e "${YELLOW}Testing 25 scenarios:${NC}" + time ./profile_routing 25 | tail -5 + + echo "" + echo -e "${GREEN}βœ… Performance comparison completed${NC}" +} + +main() { + print_header + + # Change to script directory + cd "$(dirname "$0")" + + # Default values + local mode="standard" + local scenarios=5 + + # Parse arguments + case "${1:-quick}" in + "help"|"-h"|"--help") + print_usage + exit 0 + ;; + "clean") + echo -e "${YELLOW}🧹 Cleaning build artifacts...${NC}" + make clean + echo -e "${GREEN}βœ… Clean completed${NC}" + exit 0 + ;; + "build") + check_dependencies + build_profiler + exit 0 + ;; + "quick") + scenarios=5 + ;; + "standard") + scenarios=25 + ;; + "intensive") + scenarios=50 + ;; + "stress") + scenarios=100 + ;; + "gprof") + mode="gprof" + scenarios=25 + ;; + "valgrind") + mode="valgrind" + scenarios=10 + ;; + "compare") + check_dependencies + build_profiler + run_comparison + exit 0 + ;; + [0-9]*) + scenarios=$1 + if [ $scenarios -lt 1 ] || [ $scenarios -gt 200 ]; then + echo -e "${RED}❌ Number of scenarios must be between 1 and 200${NC}" + exit 1 + fi + ;; + *) + echo -e "${RED}❌ Unknown option: $1${NC}" + echo "" + print_usage + exit 1 + ;; + esac + + # Run profiler + check_dependencies + build_profiler + run_profiler $scenarios $mode + + echo "" + echo -e "${GREEN}πŸŽ‰ Profiling completed!${NC}" + echo "" + echo -e "${YELLOW}πŸ’‘ Next steps:${NC}" + echo " β€’ Review performance bottlenecks in the output above" + echo " β€’ Run 'gprof' mode for detailed function analysis" + echo " β€’ Use 'valgrind' mode for memory profiling" + echo " β€’ Try 'compare' mode to see performance across different loads" + echo "" + echo -e "${BLUE}πŸ“– Documentation: scripts/README.md${NC}" +} + +# Run main function with all arguments +main "$@" \ No newline at end of file