From fe6716b46834c5ee6cbe1278b822df8877fa6806 Mon Sep 17 00:00:00 2001 From: Bill Thiede Date: Mon, 8 Dec 2025 14:31:40 -0800 Subject: [PATCH] Use glenng's cargo-aoc with bench --quick --- Dockerfile | 4 +-- aocsync.py | 94 +++++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 78 insertions(+), 20 deletions(-) diff --git a/Dockerfile b/Dockerfile index c72ff10..5c804da 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ FROM rust:latest -# Install cargo-aoc -RUN cargo install cargo-aoc +# Install cargo-aoc from specific GitHub repository +RUN cargo install --git https://github.com/ggriffiniii/cargo-aoc cargo-aoc # Set working directory WORKDIR /workspace diff --git a/aocsync.py b/aocsync.py index 517feab..e4be062 100755 --- a/aocsync.py +++ b/aocsync.py @@ -584,7 +584,7 @@ class CargoAOCRunner: return commits @staticmethod - def _run_cargo_aoc_in_container(work_dir: Path, day: int, repo_root: Path, docker_config: dict) -> subprocess.CompletedProcess: + def _run_cargo_aoc_in_container(work_dir: Path, day: int, repo_root: Path, docker_config: dict, user: str = "unknown", year: int = 0) -> subprocess.CompletedProcess: """Run cargo aoc in a Podman container for security Args: @@ -592,6 +592,8 @@ class CargoAOCRunner: day: Day number to run repo_root: Absolute path to repository root docker_config: Podman configuration dictionary + user: User name for build cache directory organization + year: Year for build cache directory organization Returns: CompletedProcess with stdout, stderr, returncode @@ -612,14 +614,16 @@ class CargoAOCRunner: temp_build_dir = None if build_cache_dir: - # Use persistent build cache directory - build_cache_path = Path(build_cache_dir).resolve() + # Use persistent build cache directory with user/year subdirectories + base_cache_path = Path(build_cache_dir).resolve() + # Create user/year specific directory: build_cache_dir/user/year/ + build_cache_path = base_cache_path / user / str(year) build_cache_path.mkdir(parents=True, exist_ok=True) - logger.info(f"Using persistent build cache: {build_cache_path}") + logger.info(f"Using persistent build cache: {build_cache_path} (user: {user}, year: {year})") else: # Create a temporary directory for cargo build artifacts (outside repo) import tempfile - temp_build_dir = tempfile.mkdtemp(prefix='cargo-aoc-build-') + temp_build_dir = tempfile.mkdtemp(prefix=f'cargo-aoc-build-{user}-{year}-') build_cache_path = Path(temp_build_dir) use_temp_build = True logger.info(f"Using temporary build cache: {build_cache_path}") @@ -677,13 +681,13 @@ class CargoAOCRunner: # Check if already installed to avoid reinstalling every time podman_cmd.extend([ podman_image, - 'sh', '-c', 'if ! command -v cargo-aoc >/dev/null 2>&1; then cargo install --quiet cargo-aoc 2>/dev/null || true; fi; cargo aoc --day ' + str(day) + 'sh', '-c', f'if ! command -v cargo-aoc >/dev/null 2>&1; then cargo install --quiet --git https://github.com/ggriffiniii/cargo-aoc cargo-aoc 2>/dev/null || true; fi; cargo aoc bench -d {day} -- --quick' ]) else: # Use pre-installed cargo-aoc (faster, requires aocsync:latest image) podman_cmd.extend([ podman_image, - 'cargo', 'aoc', '--day', str(day) + 'cargo', 'aoc', 'bench', '-d', str(day), '--', '--quick' ]) result = subprocess.run( @@ -747,7 +751,7 @@ class CargoAOCRunner: for day in days: try: - logger.info(f"Running cargo aoc for {user} year {year} day {day} in {work_dir} (in Podman container)") + logger.info(f"Running cargo aoc bench for {user} year {year} day {day} in {work_dir} (in Podman container)") # Run cargo aoc in a Podman container for security # Use default docker_config if not provided if docker_config is None: @@ -758,7 +762,7 @@ class CargoAOCRunner: 'cpus': '2', 'image': 'aocsync:latest' } - result = CargoAOCRunner._run_cargo_aoc_in_container(work_dir, day, repo_root, docker_config) + result = CargoAOCRunner._run_cargo_aoc_in_container(work_dir, day, repo_root, docker_config, user, year) # Write to log file if provided if log_file: @@ -770,7 +774,7 @@ class CargoAOCRunner: with open(log_file, 'a', encoding='utf-8') as f: f.write(f"\n{'='*80}\n") f.write(f"[{timestamp}] {user} - Year {year} - Day {day}\n") - f.write(f"Command: cargo aoc --day {day} (in Podman container)\n") + f.write(f"Command: cargo aoc bench -d {day} -- --quick (in Podman container)\n") f.write(f"Working Directory: {work_dir}\n") f.write(f"Return Code: {result.returncode}\n") f.write(f"{'='*80}\n") @@ -785,12 +789,12 @@ class CargoAOCRunner: f.write(f"{'='*80}\n\n") if result.returncode != 0: - logger.warning(f"cargo aoc failed for day {day} in {work_dir}: {result.stderr}") + logger.warning(f"cargo aoc bench failed for day {day} in {work_dir}: {result.stderr}") continue # Log output for debugging if no results found if not result.stdout.strip() and not result.stderr.strip(): - logger.warning(f"No output from cargo aoc for {user} year {year} day {day}") + logger.warning(f"No output from cargo aoc bench for {user} year {year} day {day}") # Strip ANSI codes before parsing (for cleaner parsing) stdout_clean = CargoAOCRunner._strip_ansi_codes(result.stdout or "") @@ -812,14 +816,14 @@ class CargoAOCRunner: results.extend(day_results) except subprocess.TimeoutExpired: - error_msg = f"Timeout running cargo aoc for day {day}" + error_msg = f"Timeout running cargo aoc bench for day {day}" logger.error(error_msg) if log_file: timestamp = datetime.now().isoformat() with open(log_file, 'a', encoding='utf-8') as f: f.write(f"\n[{timestamp}] ERROR: {error_msg}\n") except Exception as e: - error_msg = f"Error running cargo aoc for day {day}: {e}" + error_msg = f"Error running cargo aoc bench for day {day}: {e}" logger.error(error_msg) if log_file: timestamp = datetime.now().isoformat() @@ -831,9 +835,14 @@ class CargoAOCRunner: @staticmethod def _parse_runtime_output(stdout: str, stderr: str, day: int, year: int, user: str, git_rev: str = "", repo_url: str = "", output_bytes: int = 0) -> List[PerformanceResult]: - """Parse cargo-aoc runtime output + """Parse cargo aoc bench runtime output - cargo aoc typically outputs timing information like: + cargo aoc bench -- --quick outputs timing information in format like: + - "Day8 - Part1/(default) time: [15.127 ms 15.168 ms 15.331 ms]" + - "Day8 - Part2/(default) time: [15.141 ms 15.160 ms 15.164 ms]" + Extracts the middle measurement (second value) from the three measurements. + + Also supports legacy cargo-aoc custom format for backward compatibility: - "Day X - Part Y: XXX.XXX ms" - "Day X - Part Y: XXX.XXX μs" - "Day X - Part Y: XXX.XXX ns" @@ -846,6 +855,43 @@ class CargoAOCRunner: # Combine stdout and stderr (timing info might be in either) output = stdout + "\n" + stderr + # First, try to parse cargo aoc bench output format + # Pattern: Day - Part/(default) time: [ ] + # Example: Day8 - Part1/(default) time: [15.127 ms 15.168 ms 15.331 ms] + # Extract the middle measurement (val2) + cargo_bench_pattern = r'Day\s*(\d+)\s*-\s*Part\s*(\d+).*?time:\s*\[([\d.]+)\s+(\w+)\s+([\d.]+)\s+(\w+)\s+([\d.]+)\s+(\w+)\]' + for match in re.finditer(cargo_bench_pattern, output, re.IGNORECASE | re.MULTILINE): + bench_day = int(match.group(1)) + bench_part = int(match.group(2)) + # Extract middle value (group 5) and its unit (group 6) + time_str = match.group(5) + unit = match.group(6).lower() + + try: + time_val = float(time_str) + time_ns = CargoAOCRunner._convert_to_nanoseconds(time_val, unit) + + # Avoid duplicates + if not any(r.day == bench_day and r.part == bench_part + for r in results): + results.append(PerformanceResult( + user=user, + year=year, + day=bench_day, + part=bench_part, + time_ns=time_ns, + output_bytes=output_bytes, + git_rev=git_rev, + repo_url=repo_url, + timestamp=timestamp + )) + except ValueError: + logger.warning(f"Could not parse cargo bench time: {time_str} {unit}") + + # If we found results with cargo bench format, return them + if results: + return results + # Patterns to match various cargo-aoc output formats # Common formats: # "Day 1 - Part 1: 123.456 ms" @@ -2229,20 +2275,32 @@ class AOCSync: elif repo_type == 'multi-year': # Multiple repositories, one per year years_config = repo_config.get('years', []) + if not years_config: + logger.warning(f"No years configured for multi-year repository {user_name}") + return + + logger.info(f"Processing multi-year repository {user_name} with {len(years_config)} year(s)") for year_config in years_config: year = year_config['year'] url = year_config['url'] local_path = year_config['local_path'] - if self.force_rerun or self.git_manager.has_changes(url, local_path): + logger.debug(f"Checking {user_name} year {year} at {local_path}") + has_changes_result = GitManager.has_changes(url, local_path) + + if self.force_rerun or has_changes_result: if self.force_rerun: logger.info(f"Force rerun enabled, processing repository {user_name} year {year}...") else: logger.info(f"Repository {user_name} year {year} has changes, updating...") - if self.git_manager.clone_or_update_repo(url, local_path): + if GitManager.clone_or_update_repo(url, local_path): repo_path = Path(local_path) self._run_and_store_benchmarks(repo_path, year, user_name, repo_url=url, is_multi_year=True) + else: + logger.error(f"Failed to clone/update repository {user_name} year {year} at {local_path}") + else: + logger.info(f"Repository {user_name} year {year} has no changes, skipping...") def _check_year_in_repo(self, repo_path: Path, year: int) -> bool: """Check if a repository contains solutions for a specific year"""