Compare commits
No commits in common. "4c6ff1e437f038671a36b8aeda58c1d88793c032" and "46b28e8bdd29377b2d89d707d57dc16244125902" have entirely different histories.
4c6ff1e437
...
46b28e8bdd
8
.gitignore
vendored
8
.gitignore
vendored
@ -1,9 +1 @@
|
|||||||
/target
|
/target
|
||||||
|
|
||||||
# AOC Sync generated files
|
|
||||||
/data/
|
|
||||||
/output/
|
|
||||||
/repos/
|
|
||||||
*.db
|
|
||||||
*.db-journal
|
|
||||||
__pycache__
|
|
||||||
|
|||||||
3028
Cargo.lock
generated
Normal file
3028
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
16
Cargo.toml
Normal file
16
Cargo.toml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
[package]
|
||||||
|
name = "aocsync"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
rocket = "0.5.0-rc.2"
|
||||||
|
glog = {version = "0.1.0", git = "https://github.com/wathiede/glog-rs"}
|
||||||
|
log = "0.4.17"
|
||||||
|
serde = { version = "1.0.148", features = ["serde_derive"] }
|
||||||
|
rocket_anyhow = "0.1.1"
|
||||||
|
thiserror = "1.0.37"
|
||||||
|
rocket_dyn_templates = { version = "0.1.0-rc.2", features = ["tera"] }
|
||||||
|
anyhow = "1.0.66"
|
||||||
185
README.md
185
README.md
@ -1,174 +1,17 @@
|
|||||||
# AOC Sync
|
# `git-sync` webhook
|
||||||
|
|
||||||
A Python script that polls multiple git repositories containing Advent of Code implementations written in Rust using `cargo-aoc` format. It automatically updates repositories when changes are detected, runs benchmarks, and generates a beautiful HTML comparison page showing performance metrics across users, years, and days.
|
```
|
||||||
|
POST /-/reload?repo=akramer HTTP/1.1
|
||||||
## Features
|
Host: aocperf.z.xinu.tv
|
||||||
|
X-Real-IP: 172.17.0.7
|
||||||
- **Automatic Git Polling**: Monitors multiple repositories for changes
|
X-Forwarded-For: 172.17.0.7
|
||||||
- **Flexible Repository Structure**: Supports both single-repo (all years) and multi-repo (one per year) configurations
|
X-Forwarded-Proto: https
|
||||||
- **Automatic Runtime Measurement**: Runs `cargo aoc` for all implemented days and parses runtime information
|
X-Forwarded-Host: aocperf.z.xinu.tv
|
||||||
- **Performance Parsing**: Extracts timing data from cargo-aoc output
|
X-Forwarded-Server: aocperf.z.xinu.tv
|
||||||
- **Data Storage**: SQLite database for historical performance data
|
Connection: close
|
||||||
- **HTML Reports**: Beautiful, responsive HTML comparison pages
|
Content-Length: 0
|
||||||
- **Gap Handling**: Gracefully handles missing years, days, and parts
|
gitsync-hash: 6f498804bf7203ab69fa7aa8e9f441d20d07ac67
|
||||||
- **Configurable Comparisons**: Filter by specific years and days
|
accept-encoding: gzip
|
||||||
|
user-agent: Go-http-client/2.0
|
||||||
## Requirements
|
|
||||||
|
|
||||||
- Python 3.7+
|
|
||||||
- Git
|
|
||||||
- Rust and Cargo
|
|
||||||
- `cargo-aoc` installed (install with `cargo install cargo-aoc`)
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
1. Clone this repository:
|
|
||||||
```bash
|
|
||||||
git clone <repository-url>
|
|
||||||
cd aocsync
|
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Install Python dependencies:
|
|
||||||
```bash
|
|
||||||
pip install -r requirements.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Ensure `cargo-aoc` is installed:
|
|
||||||
```bash
|
|
||||||
cargo install cargo-aoc
|
|
||||||
```
|
|
||||||
|
|
||||||
Note: The script runs `cargo aoc` (not `cargo aoc bench`) and parses runtime information from the output.
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
Edit `config.yaml` to configure repositories to monitor:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
# Poll interval in seconds
|
|
||||||
poll_interval: 300
|
|
||||||
|
|
||||||
# Output directory for generated HTML
|
|
||||||
output_dir: "output"
|
|
||||||
|
|
||||||
# Data storage directory
|
|
||||||
data_dir: "data"
|
|
||||||
|
|
||||||
# Repositories to monitor
|
|
||||||
repositories:
|
|
||||||
# Single repository with all years
|
|
||||||
- name: "user1"
|
|
||||||
url: "https://github.com/user1/advent-of-code"
|
|
||||||
type: "single"
|
|
||||||
local_path: "repos/user1"
|
|
||||||
|
|
||||||
# Multiple repositories, one per year
|
|
||||||
- name: "user2"
|
|
||||||
type: "multi-year"
|
|
||||||
years:
|
|
||||||
- year: 2023
|
|
||||||
url: "https://github.com/user2/aoc-2023"
|
|
||||||
local_path: "repos/user2-2023"
|
|
||||||
- year: 2024
|
|
||||||
url: "https://github.com/user2/aoc-2024"
|
|
||||||
local_path: "repos/user2-2024"
|
|
||||||
|
|
||||||
# Optional: Filter specific years to compare
|
|
||||||
compare_years: [2023, 2024]
|
|
||||||
|
|
||||||
# Optional: Filter specific days to compare
|
|
||||||
# compare_days: [1, 2, 3, 4, 5]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
### Run Once
|
|
||||||
|
|
||||||
To sync repositories and generate a report once:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python aocsync.py --once
|
|
||||||
```
|
|
||||||
|
|
||||||
### Force Rerun All Days
|
|
||||||
|
|
||||||
To force rerun all days even if repositories haven't changed:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python aocsync.py --once --force
|
|
||||||
```
|
|
||||||
|
|
||||||
or
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python aocsync.py --once --rerun-all
|
|
||||||
```
|
|
||||||
|
|
||||||
This is useful for refreshing all data or testing changes to the script.
|
|
||||||
|
|
||||||
### Continuous Polling
|
|
||||||
|
|
||||||
To continuously poll repositories (default):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python aocsync.py
|
|
||||||
```
|
|
||||||
|
|
||||||
Or with a custom config file:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python aocsync.py --config myconfig.yaml
|
|
||||||
```
|
|
||||||
|
|
||||||
## Output
|
|
||||||
|
|
||||||
- **Database**: Performance data is stored in `data/results.db` (SQLite)
|
|
||||||
- **HTML Report**: Generated at `output/index.html` (configurable via `output_dir`)
|
|
||||||
|
|
||||||
The HTML report includes:
|
|
||||||
- Performance comparison tables for each year/day/part
|
|
||||||
- Visual highlighting of fastest and slowest implementations
|
|
||||||
- Relative speed comparisons (X times faster/slower)
|
|
||||||
- Responsive design for viewing on any device
|
|
||||||
|
|
||||||
## How It Works
|
|
||||||
|
|
||||||
1. **Git Polling**: Checks each configured repository for changes by comparing local and remote commits
|
|
||||||
2. **Repository Update**: Clones new repositories or updates existing ones when changes are detected
|
|
||||||
3. **Day Detection**: Automatically finds implemented days by scanning for `day*.rs` files and Cargo.toml entries
|
|
||||||
4. **Runtime Measurement**: Runs `cargo aoc --day X` for each implemented day
|
|
||||||
5. **Parsing**: Extracts timing data from cargo-aoc output (handles nanoseconds, microseconds, milliseconds, seconds)
|
|
||||||
6. **Storage**: Stores results in SQLite database with timestamps
|
|
||||||
7. **Report Generation**: Generates HTML comparison page showing latest results
|
|
||||||
|
|
||||||
## Repository Structure Detection
|
|
||||||
|
|
||||||
The script automatically detects:
|
|
||||||
- **Year**: From repository path, name, or Cargo.toml
|
|
||||||
- **Days**: From `src/bin/day*.rs`, `src/day*.rs`, or Cargo.toml entries
|
|
||||||
- **Parts**: From cargo-aoc benchmark output (Part 1, Part 2)
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Cargo-aoc not found
|
|
||||||
Ensure `cargo-aoc` is installed and in your PATH:
|
|
||||||
```bash
|
|
||||||
cargo install cargo-aoc
|
|
||||||
```
|
|
||||||
|
|
||||||
### Git authentication issues
|
|
||||||
For private repositories, ensure your git credentials are configured or use SSH URLs.
|
|
||||||
|
|
||||||
### Benchmark timeouts
|
|
||||||
If benchmarks take too long, the script has a 5-minute timeout per day. Adjust in the code if needed.
|
|
||||||
|
|
||||||
### Missing performance data
|
|
||||||
If some users/days/parts don't show up:
|
|
||||||
- Check that `cargo aoc --day X` runs successfully in the repository
|
|
||||||
- Verify the repository structure matches cargo-aoc conventions
|
|
||||||
- Ensure `cargo aoc` outputs timing information (check if it's configured to show runtime)
|
|
||||||
- Check logs for parsing errors
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
[Your License Here]
|
|
||||||
|
|||||||
14
Rocket.toml
Normal file
14
Rocket.toml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
[release]
|
||||||
|
address = "0.0.0.0"
|
||||||
|
port = 9346
|
||||||
|
|
||||||
|
[debug]
|
||||||
|
address = "0.0.0.0"
|
||||||
|
port = 9346
|
||||||
|
# Uncomment to make it production like.
|
||||||
|
#log_level = "critical"
|
||||||
|
|
||||||
|
build_root = "/tmp/aocsync/"
|
||||||
|
www_root = "/tmp/aocsync-serve/"
|
||||||
|
|
||||||
|
repos = { akramer = { url = "https://github.com/akramer/aoc2022", name = "aoc2022", branch = "main" }, ggriffiniii = { url = "https://github.com/ggriffiniii/aoc2022", name = "aoc2022", branch = "main" }, wathiede = { url = "https://github.com/wathiede/advent", name = "advent/2022", branch = "master", first_commit = "bc3feabb4669b7ad8167398a4d37eefe68ea0565", subdir = "2022" } }
|
||||||
1752
aocsync.py
1752
aocsync.py
File diff suppressed because it is too large
Load Diff
57
config.yaml
57
config.yaml
@ -1,57 +0,0 @@
|
|||||||
# Configuration for AOC Sync
|
|
||||||
# Poll interval in seconds
|
|
||||||
poll_interval: 300
|
|
||||||
|
|
||||||
# Output directory for generated HTML
|
|
||||||
output_dir: "output"
|
|
||||||
|
|
||||||
# Data storage
|
|
||||||
data_dir: "data"
|
|
||||||
|
|
||||||
# Optional: rsync output to remote server after successful run
|
|
||||||
rsync:
|
|
||||||
enabled: true
|
|
||||||
destination: "xinu.tv:/var/www/static/aoc/"
|
|
||||||
|
|
||||||
# Repositories to monitor
|
|
||||||
repositories:
|
|
||||||
# Example: Single repository with all years
|
|
||||||
- name: "wathiede"
|
|
||||||
url: "https://github.com/wathiede/advent"
|
|
||||||
type: "single" # single repo for all years
|
|
||||||
local_path: "repos/wathiede"
|
|
||||||
# Optional: specify years if auto-detection fails
|
|
||||||
# years: [2023, 2024]
|
|
||||||
|
|
||||||
# Example: Multiple repositories, one per year
|
|
||||||
- name: "ggriffiniii"
|
|
||||||
type: "multi-year" # one repo per year
|
|
||||||
years:
|
|
||||||
- year: 2021
|
|
||||||
url: "https://github.com/ggriffiniii/aoc2021"
|
|
||||||
local_path: "repos/ggriffiniii-2021"
|
|
||||||
- year: 2022
|
|
||||||
url: "https://github.com/ggriffiniii/aoc2022"
|
|
||||||
local_path: "repos/ggriffiniii-2022"
|
|
||||||
- year: 2025
|
|
||||||
url: "https://github.com/ggriffiniii/aoc2025"
|
|
||||||
local_path: "repos/ggriffiniii-2025"
|
|
||||||
#- year: 2024
|
|
||||||
# url: "https://github.com/user2/aoc-2024"
|
|
||||||
# local_path: "repos/user2-2024"
|
|
||||||
|
|
||||||
# Didn't use cargo aoc
|
|
||||||
# - name: "akramer"
|
|
||||||
# type: "multi-year" # one repo per year
|
|
||||||
# years:
|
|
||||||
# - year: 2021
|
|
||||||
# url: "https://github.com/akramer/aoc2021"
|
|
||||||
# local_path: "repos/akramer-2021"
|
|
||||||
# - year: 2022
|
|
||||||
# url: "https://github.com/akramer/aoc2022"
|
|
||||||
# local_path: "repos/akramer-2022"
|
|
||||||
# Years to compare (optional, if not specified, all years found will be used)
|
|
||||||
# compare_years: [2025]
|
|
||||||
|
|
||||||
# Days to compare (optional, if not specified, all days found will be used)
|
|
||||||
# compare_days: [1, 2, 3, 4, 5]
|
|
||||||
@ -1 +0,0 @@
|
|||||||
PyYAML>=6.0
|
|
||||||
34
src/bin/aocpoller.rs
Normal file
34
src/bin/aocpoller.rs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
use aocsync::{logging_run, Config};
|
||||||
|
use glog::Flags;
|
||||||
|
use log::info;
|
||||||
|
fn main() -> anyhow::Result<()> {
|
||||||
|
glog::new()
|
||||||
|
.init(Flags {
|
||||||
|
colorlogtostderr: true,
|
||||||
|
//alsologtostderr: true, // use logtostderr to only write to stderr and not to files
|
||||||
|
logtostderr: true,
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
let config = rocket::Config::figment()
|
||||||
|
.extract::<Config>()
|
||||||
|
.expect("Couldn't parse config");
|
||||||
|
info!("{:#?}", config);
|
||||||
|
for (name, repo) in config.repos.iter() {
|
||||||
|
let ts = logging_run(
|
||||||
|
Command::new("git")
|
||||||
|
.arg("ls-remote")
|
||||||
|
.arg(&repo.url)
|
||||||
|
.arg("-q")
|
||||||
|
.arg(&repo.name)
|
||||||
|
.arg("-h")
|
||||||
|
.arg(&repo.branch),
|
||||||
|
)?;
|
||||||
|
let out = String::from_utf8_lossy(&ts.output.stdout);
|
||||||
|
let head = &out[..out.find('\t').expect("No tab in output")];
|
||||||
|
info!("HEAD: '{}'", head);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
93
src/lib.rs
Normal file
93
src/lib.rs
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
io,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
process::{Command, Output},
|
||||||
|
time::{Duration, Instant},
|
||||||
|
};
|
||||||
|
|
||||||
|
use log::info;
|
||||||
|
use rocket::response::Responder;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Error, Debug, Responder)]
|
||||||
|
pub enum SyncError {
|
||||||
|
#[error("IO error")]
|
||||||
|
IoError(#[from] io::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct Config {
|
||||||
|
pub build_root: PathBuf,
|
||||||
|
pub www_root: PathBuf,
|
||||||
|
pub repos: HashMap<String, Repo>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct Repo {
|
||||||
|
pub name: String,
|
||||||
|
pub url: String,
|
||||||
|
pub branch: String,
|
||||||
|
pub first_commit: Option<String>,
|
||||||
|
pub subdir: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn indent_output(bytes: &[u8]) -> String {
|
||||||
|
let s = String::from_utf8_lossy(bytes);
|
||||||
|
let s = if s.is_empty() { "<NO OUTPUT>" } else { &s };
|
||||||
|
s.split('\n')
|
||||||
|
.map(|l| format!("\t{l}"))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn format_task_status(ts: &TaskStatus) -> String {
|
||||||
|
format!(
|
||||||
|
"Status {} ({}) {}s:\nStdout:\n{}\nStderr:\n{}",
|
||||||
|
ts.command,
|
||||||
|
ts.output.status,
|
||||||
|
ts.duration.as_secs_f32(),
|
||||||
|
indent_output(&ts.output.stdout),
|
||||||
|
indent_output(&ts.output.stderr)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TaskStatus {
|
||||||
|
pub command: String,
|
||||||
|
pub output: Output,
|
||||||
|
pub duration: Duration,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn logging_run(cmd: &mut Command) -> Result<TaskStatus, SyncError> {
|
||||||
|
info!(
|
||||||
|
"{}$ {} {}",
|
||||||
|
cmd.get_current_dir()
|
||||||
|
.unwrap_or(Path::new("<NO CWD>"))
|
||||||
|
.display(),
|
||||||
|
cmd.get_program().to_string_lossy(),
|
||||||
|
cmd.get_args()
|
||||||
|
.map(|a| a.to_string_lossy())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(" "),
|
||||||
|
);
|
||||||
|
let start = Instant::now();
|
||||||
|
let o = cmd.output()?;
|
||||||
|
let ts = TaskStatus {
|
||||||
|
output: o,
|
||||||
|
command: format!("{:?}", cmd),
|
||||||
|
duration: start.elapsed(),
|
||||||
|
};
|
||||||
|
info!("{}", format_task_status(&ts));
|
||||||
|
Ok(ts)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn git_checkout(build_path: &Path, rev: &str) -> Result<TaskStatus, SyncError> {
|
||||||
|
logging_run(
|
||||||
|
Command::new("git")
|
||||||
|
.current_dir(&build_path)
|
||||||
|
.arg("checkout")
|
||||||
|
.arg("-q")
|
||||||
|
.arg(rev),
|
||||||
|
)
|
||||||
|
}
|
||||||
229
src/main.rs
Normal file
229
src/main.rs
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
#[macro_use]
|
||||||
|
extern crate rocket;
|
||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
fs,
|
||||||
|
fs::File,
|
||||||
|
io,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
process::{Command, Output},
|
||||||
|
};
|
||||||
|
|
||||||
|
use aocsync::{format_task_status, git_checkout, logging_run, Config, SyncError, TaskStatus};
|
||||||
|
use glog::Flags;
|
||||||
|
use log::{error, info};
|
||||||
|
use rocket::{fairing::AdHoc, fs::FileServer, response::Responder, State};
|
||||||
|
use rocket_dyn_templates::{context, Template};
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
// For testing
|
||||||
|
#[get("/-/reload/<repo>")]
|
||||||
|
fn get_reload(
|
||||||
|
lock: &State<ReloadLock>,
|
||||||
|
config: &State<Config>,
|
||||||
|
repo: &str,
|
||||||
|
) -> Result<String, SyncError> {
|
||||||
|
reload(lock, config, repo)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/-/reload/<repo>")]
|
||||||
|
fn post_reload(
|
||||||
|
lock: &State<ReloadLock>,
|
||||||
|
config: &State<Config>,
|
||||||
|
repo: &str,
|
||||||
|
) -> Result<String, SyncError> {
|
||||||
|
reload(lock, config, repo)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bench_at_commit(
|
||||||
|
commit: &str,
|
||||||
|
build_path: &Path,
|
||||||
|
target_path: &Path,
|
||||||
|
) -> Result<Vec<TaskStatus>, SyncError> {
|
||||||
|
let mut output = Vec::new();
|
||||||
|
output.push(git_checkout(build_path, commit)?);
|
||||||
|
// Run `cargo aoc bench`
|
||||||
|
output.push(logging_run(
|
||||||
|
Command::new("cargo")
|
||||||
|
.env("CARGO_TARGET_DIR", target_path)
|
||||||
|
.current_dir(&build_path)
|
||||||
|
.arg("aoc")
|
||||||
|
.arg("bench"),
|
||||||
|
)?);
|
||||||
|
output.push(git_checkout(build_path, "HEAD")?);
|
||||||
|
|
||||||
|
Ok(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
fn reload(
|
||||||
|
lock: &State<ReloadLock>,
|
||||||
|
config: &State<Config>,
|
||||||
|
name: &str,
|
||||||
|
) -> Result<String, SyncError> {
|
||||||
|
let _locked = lock.0.lock().unwrap();
|
||||||
|
let repo = &config.repos[name];
|
||||||
|
info!("Need to reload '{}': {:?}\n{:#?}", name, config, repo);
|
||||||
|
|
||||||
|
let commits_root = config.build_root.join("commits");
|
||||||
|
let git_root = config.build_root.join("git").join(name);
|
||||||
|
let www_root = config.www_root.join(name);
|
||||||
|
let target_root = config.build_root.join("target");
|
||||||
|
|
||||||
|
let checkout_path = git_root.join(&repo.name);
|
||||||
|
let build_path = if let Some(subdir) = &repo.subdir {
|
||||||
|
git_root.join(&repo.name).join(subdir)
|
||||||
|
} else {
|
||||||
|
git_root.join(&repo.name)
|
||||||
|
};
|
||||||
|
let target_path = target_root.join(name);
|
||||||
|
|
||||||
|
dbg!(
|
||||||
|
&build_path,
|
||||||
|
&target_path,
|
||||||
|
&commits_root,
|
||||||
|
&git_root,
|
||||||
|
&www_root
|
||||||
|
);
|
||||||
|
|
||||||
|
if !commits_root.exists() {
|
||||||
|
info!("Creating {}", commits_root.display());
|
||||||
|
fs::create_dir_all(&commits_root)?;
|
||||||
|
}
|
||||||
|
if !git_root.exists() {
|
||||||
|
info!("Creating {}", git_root.display());
|
||||||
|
fs::create_dir_all(&git_root)?;
|
||||||
|
}
|
||||||
|
if !target_root.exists() {
|
||||||
|
info!("Creating {}", target_root.display());
|
||||||
|
fs::create_dir_all(&target_root)?;
|
||||||
|
}
|
||||||
|
if !www_root.exists() {
|
||||||
|
info!("Creating {}", www_root.display());
|
||||||
|
fs::create_dir_all(&www_root)?;
|
||||||
|
}
|
||||||
|
let mut output = vec![logging_run(Command::new("git").arg("version"))?];
|
||||||
|
let needs_clone = !checkout_path.exists();
|
||||||
|
if needs_clone {
|
||||||
|
output.push(logging_run(
|
||||||
|
Command::new("git")
|
||||||
|
.current_dir(&git_root)
|
||||||
|
.arg("clone")
|
||||||
|
.arg(&repo.url)
|
||||||
|
.arg(&checkout_path),
|
||||||
|
)?);
|
||||||
|
}
|
||||||
|
output.push(logging_run(
|
||||||
|
Command::new("git")
|
||||||
|
.current_dir(&checkout_path)
|
||||||
|
.arg("checkout")
|
||||||
|
.arg("-f")
|
||||||
|
.arg(&repo.branch),
|
||||||
|
)?);
|
||||||
|
output.push(logging_run(
|
||||||
|
Command::new("git")
|
||||||
|
.current_dir(&checkout_path)
|
||||||
|
.arg("checkout")
|
||||||
|
.arg("HEAD"),
|
||||||
|
)?);
|
||||||
|
dbg!(&checkout_path);
|
||||||
|
// Make sure buildable clone is up to date
|
||||||
|
output.push(logging_run(
|
||||||
|
Command::new("git").current_dir(&checkout_path).arg("pull"),
|
||||||
|
)?);
|
||||||
|
let commits = logging_run(Command::new("git").current_dir(&checkout_path).args([
|
||||||
|
"log",
|
||||||
|
"--format=%H",
|
||||||
|
&repo.branch,
|
||||||
|
]))?;
|
||||||
|
let binding = String::from_utf8_lossy(&commits.output.stdout).into_owned();
|
||||||
|
let mut unknown_commits: Vec<_> = binding
|
||||||
|
.lines()
|
||||||
|
.filter(|commit| !commits_root.join(commit).exists())
|
||||||
|
.collect();
|
||||||
|
unknown_commits.reverse();
|
||||||
|
output.push(commits);
|
||||||
|
info!("Need to bench commits: {:?}", unknown_commits);
|
||||||
|
for commit in unknown_commits {
|
||||||
|
match bench_at_commit(commit, &build_path, &target_path) {
|
||||||
|
Ok(outputs) => {
|
||||||
|
output.extend(outputs);
|
||||||
|
File::create(commits_root.join(commit))?;
|
||||||
|
}
|
||||||
|
Err(err) => error!("Failed to bench {}@{}: {}", name, commit, err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Copy files from `target/` to serving directory
|
||||||
|
let bench_path = target_path.join("criterion");
|
||||||
|
info!("Copying {} -> {}", bench_path.display(), www_root.display());
|
||||||
|
copy_dir_all(bench_path, www_root)?;
|
||||||
|
let response = output
|
||||||
|
.iter()
|
||||||
|
.map(|ts| format!("{}", format_task_status(ts)))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n");
|
||||||
|
Ok(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
// From https://stackoverflow.com/questions/26958489/how-to-copy-a-folder-recursively-in-rust
|
||||||
|
fn copy_dir_all(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> io::Result<()> {
|
||||||
|
fs::create_dir_all(&dst)?;
|
||||||
|
for entry in fs::read_dir(src)? {
|
||||||
|
let entry = entry?;
|
||||||
|
let ty = entry.file_type()?;
|
||||||
|
if ty.is_dir() {
|
||||||
|
copy_dir_all(entry.path(), dst.as_ref().join(entry.file_name()))?;
|
||||||
|
} else {
|
||||||
|
fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/")]
|
||||||
|
fn index(config: &State<Config>) -> Result<Template, SyncError> {
|
||||||
|
let dirs: Vec<_> = fs::read_dir(&config.www_root)?
|
||||||
|
.filter_map(|ent| ent.ok())
|
||||||
|
.map(|ent| ent.file_name().into_string())
|
||||||
|
.filter_map(|ent| ent.ok())
|
||||||
|
.collect();
|
||||||
|
Ok(Template::render(
|
||||||
|
"index",
|
||||||
|
context! {
|
||||||
|
dirs
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[catch(500)]
|
||||||
|
fn http500(req: &rocket::Request) -> String {
|
||||||
|
// TODO(wathiede): figure out a way to retrieve the Error that got us here?
|
||||||
|
format!("{:?}", req)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ReloadLock(Arc<Mutex<()>>);
|
||||||
|
|
||||||
|
#[launch]
|
||||||
|
fn rocket() -> _ {
|
||||||
|
glog::new()
|
||||||
|
.init(Flags {
|
||||||
|
colorlogtostderr: true,
|
||||||
|
//alsologtostderr: true, // use logtostderr to only write to stderr and not to files
|
||||||
|
logtostderr: true,
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let config = rocket::Config::figment()
|
||||||
|
.extract::<Config>()
|
||||||
|
.expect("Couldn't parse config");
|
||||||
|
info!("Config:\n{:#?}", config);
|
||||||
|
rocket::build()
|
||||||
|
.mount("/", routes![index, get_reload, post_reload])
|
||||||
|
.mount("/results/", FileServer::from(config.www_root))
|
||||||
|
.manage(ReloadLock(Arc::new(Mutex::new(()))))
|
||||||
|
//.register("/", catchers![http500])
|
||||||
|
.attach(AdHoc::config::<Config>())
|
||||||
|
.attach(Template::fairing())
|
||||||
|
}
|
||||||
4
templates/index.html.tera
Normal file
4
templates/index.html.tera
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{% for d in dirs %}
|
||||||
|
<div><a href="/results/{{ d }}/report/">{{ d }}</a></div>
|
||||||
|
{% endfor %}
|
||||||
|
<div>Source in <a href="https://git.z.xinu.tv/wathiede/aocsync">git.</a>
|
||||||
Loading…
x
Reference in New Issue
Block a user