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
# 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

View File

@ -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<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
# 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"""