Use glenng's cargo-aoc with bench --quick

This commit is contained in:
Bill Thiede 2025-12-08 14:31:40 -08:00
parent 24b343d3f8
commit fe6716b468
2 changed files with 78 additions and 20 deletions

View File

@ -1,7 +1,7 @@
FROM rust:latest FROM rust:latest
# Install cargo-aoc # Install cargo-aoc from specific GitHub repository
RUN cargo install cargo-aoc RUN cargo install --git https://github.com/ggriffiniii/cargo-aoc cargo-aoc
# Set working directory # Set working directory
WORKDIR /workspace WORKDIR /workspace

View File

@ -584,7 +584,7 @@ class CargoAOCRunner:
return commits return commits
@staticmethod @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 """Run cargo aoc in a Podman container for security
Args: Args:
@ -592,6 +592,8 @@ class CargoAOCRunner:
day: Day number to run day: Day number to run
repo_root: Absolute path to repository root repo_root: Absolute path to repository root
docker_config: Podman configuration dictionary docker_config: Podman configuration dictionary
user: User name for build cache directory organization
year: Year for build cache directory organization
Returns: Returns:
CompletedProcess with stdout, stderr, returncode CompletedProcess with stdout, stderr, returncode
@ -612,14 +614,16 @@ class CargoAOCRunner:
temp_build_dir = None temp_build_dir = None
if build_cache_dir: if build_cache_dir:
# Use persistent build cache directory # Use persistent build cache directory with user/year subdirectories
build_cache_path = Path(build_cache_dir).resolve() 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) 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: else:
# Create a temporary directory for cargo build artifacts (outside repo) # Create a temporary directory for cargo build artifacts (outside repo)
import tempfile 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) build_cache_path = Path(temp_build_dir)
use_temp_build = True use_temp_build = True
logger.info(f"Using temporary build cache: {build_cache_path}") 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 # Check if already installed to avoid reinstalling every time
podman_cmd.extend([ podman_cmd.extend([
podman_image, 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: else:
# Use pre-installed cargo-aoc (faster, requires aocsync:latest image) # Use pre-installed cargo-aoc (faster, requires aocsync:latest image)
podman_cmd.extend([ podman_cmd.extend([
podman_image, podman_image,
'cargo', 'aoc', '--day', str(day) 'cargo', 'aoc', 'bench', '-d', str(day), '--', '--quick'
]) ])
result = subprocess.run( result = subprocess.run(
@ -747,7 +751,7 @@ class CargoAOCRunner:
for day in days: for day in days:
try: 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 # Run cargo aoc in a Podman container for security
# Use default docker_config if not provided # Use default docker_config if not provided
if docker_config is None: if docker_config is None:
@ -758,7 +762,7 @@ class CargoAOCRunner:
'cpus': '2', 'cpus': '2',
'image': 'aocsync:latest' '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 # Write to log file if provided
if log_file: if log_file:
@ -770,7 +774,7 @@ class CargoAOCRunner:
with open(log_file, 'a', encoding='utf-8') as f: with open(log_file, 'a', encoding='utf-8') as f:
f.write(f"\n{'='*80}\n") f.write(f"\n{'='*80}\n")
f.write(f"[{timestamp}] {user} - Year {year} - Day {day}\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"Working Directory: {work_dir}\n")
f.write(f"Return Code: {result.returncode}\n") f.write(f"Return Code: {result.returncode}\n")
f.write(f"{'='*80}\n") f.write(f"{'='*80}\n")
@ -785,12 +789,12 @@ class CargoAOCRunner:
f.write(f"{'='*80}\n\n") f.write(f"{'='*80}\n\n")
if result.returncode != 0: 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 continue
# Log output for debugging if no results found # Log output for debugging if no results found
if not result.stdout.strip() and not result.stderr.strip(): 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) # Strip ANSI codes before parsing (for cleaner parsing)
stdout_clean = CargoAOCRunner._strip_ansi_codes(result.stdout or "") stdout_clean = CargoAOCRunner._strip_ansi_codes(result.stdout or "")
@ -812,14 +816,14 @@ class CargoAOCRunner:
results.extend(day_results) results.extend(day_results)
except subprocess.TimeoutExpired: 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) logger.error(error_msg)
if log_file: if log_file:
timestamp = datetime.now().isoformat() timestamp = datetime.now().isoformat()
with open(log_file, 'a', encoding='utf-8') as f: with open(log_file, 'a', encoding='utf-8') as f:
f.write(f"\n[{timestamp}] ERROR: {error_msg}\n") f.write(f"\n[{timestamp}] ERROR: {error_msg}\n")
except Exception as e: 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) logger.error(error_msg)
if log_file: if log_file:
timestamp = datetime.now().isoformat() timestamp = datetime.now().isoformat()
@ -831,9 +835,14 @@ class CargoAOCRunner:
@staticmethod @staticmethod
def _parse_runtime_output(stdout: str, stderr: str, day: int, year: int, 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]: 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 ms"
- "Day X - Part Y: XXX.XXX μs" - "Day X - Part Y: XXX.XXX μs"
- "Day X - Part Y: XXX.XXX ns" - "Day X - Part Y: XXX.XXX ns"
@ -846,6 +855,43 @@ class CargoAOCRunner:
# Combine stdout and stderr (timing info might be in either) # Combine stdout and stderr (timing info might be in either)
output = stdout + "\n" + stderr output = stdout + "\n" + stderr
# First, try to parse cargo aoc bench output format
# Pattern: Day<X> - Part<Y>/(default) time: [<val1> <unit> <val2> <unit> <val3> <unit>]
# 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 # Patterns to match various cargo-aoc output formats
# Common formats: # Common formats:
# "Day 1 - Part 1: 123.456 ms" # "Day 1 - Part 1: 123.456 ms"
@ -2229,20 +2275,32 @@ class AOCSync:
elif repo_type == 'multi-year': elif repo_type == 'multi-year':
# Multiple repositories, one per year # Multiple repositories, one per year
years_config = repo_config.get('years', []) 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: for year_config in years_config:
year = year_config['year'] year = year_config['year']
url = year_config['url'] url = year_config['url']
local_path = year_config['local_path'] 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: if self.force_rerun:
logger.info(f"Force rerun enabled, processing repository {user_name} year {year}...") logger.info(f"Force rerun enabled, processing repository {user_name} year {year}...")
else: else:
logger.info(f"Repository {user_name} year {year} has changes, updating...") 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) repo_path = Path(local_path)
self._run_and_store_benchmarks(repo_path, year, user_name, self._run_and_store_benchmarks(repo_path, year, user_name,
repo_url=url, is_multi_year=True) 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: def _check_year_in_repo(self, repo_path: Path, year: int) -> bool:
"""Check if a repository contains solutions for a specific year""" """Check if a repository contains solutions for a specific year"""