Compare commits
121 Commits
dev_1.1_xu
...
de4f40eb2c
| Author | SHA1 | Date | |
|---|---|---|---|
| de4f40eb2c | |||
| 87607a3991 | |||
| 96d82957a2 | |||
| 63ac3ee254 | |||
| b0f76738e5 | |||
| c00d9390dc | |||
| a5b194ce35 | |||
| a501cfb709 | |||
| f8988b18cd | |||
| a67640db83 | |||
| d5270308d6 | |||
| 6c45cc94f3 | |||
| 371c819126 | |||
| a329fed3df | |||
| 860bad2113 | |||
| 0610a8291c | |||
| b83acb281b | |||
| 8d15188ea8 | |||
| 4ca08a12a4 | |||
| fe8d4fcb14 | |||
| f9f91f072a | |||
| 4b98a9cd37 | |||
| 6d9646de2c | |||
| 9f98ac6394 | |||
| a87a73559b | |||
| 79a77f6f6e | |||
| f1a73bd2d1 | |||
| 9e339e63e7 | |||
| af25316d68 | |||
| adb68fd469 | |||
| 8c4a677ffa | |||
| 574e40a4d4 | |||
| 4f41fbe04e | |||
| 6ab044480a | |||
| 4e37b96aff | |||
| 92d184681b | |||
| 4546d90c33 | |||
| 61d3e636ae | |||
| 184d3cb95b | |||
| bd266ef2a5 | |||
| d4d227ce60 | |||
| 261d652123 | |||
| b6344074ce | |||
| ad2a25aede | |||
| 68006467f7 | |||
| 56f0d10dc6 | |||
| 6ef2c98890 | |||
| 4e6bd687db | |||
| 4047214ddf | |||
| 74012c686d | |||
| d0224d53f6 | |||
| 8e6e2f1318 | |||
| 4045f9a3a5 | |||
| e597c7c124 | |||
| 9579f65501 | |||
| 902f3ba889 | |||
| 938ef53173 | |||
| ae00e676d7 | |||
| bf8c924326 | |||
| 00d7109503 | |||
| a29fe266a4 | |||
| 2154870a43 | |||
| 9f7981e117 | |||
| 57c23f19aa | |||
| 7e1c24fcc3 | |||
| cda7fdb135 | |||
| fcd0bac070 | |||
| 8c6fb44352 | |||
| 615f8709af | |||
| 137b3516d0 | |||
| f2fbb20a29 | |||
| 51e483d924 | |||
| 265b6f65d0 | |||
| c6b408c0f9 | |||
| 752e746b97 | |||
| 51663a9410 | |||
| f843eedda9 | |||
| b3b44d50c6 | |||
| ad61caf271 | |||
| 3b2a160c5c | |||
| 0295fd3262 | |||
| e2901df3ac | |||
| 26aec5ff35 | |||
| 87275b5c57 | |||
| eaa375c5ec | |||
| 944ea6ca00 | |||
| 542bd7be80 | |||
| f6a601e16c | |||
| 417c82bef6 | |||
| f485786470 | |||
| 224ae10052 | |||
| 38421b1ae7 | |||
| 585c41b83b | |||
| af1c2c7f88 | |||
| e45830d79f | |||
| 77fc2f1a41 | |||
| e083fe4daa | |||
| c9ef5aaf4c | |||
| 34fb9657f1 | |||
| 6edc420ce2 | |||
| cd0ba15286 | |||
| fe8b89d829 | |||
| d6d44669b4 | |||
| 557a69cbd2 | |||
| 73d8ebb5c1 | |||
| cf764c35bb | |||
| e3339533b8 | |||
| afc50364f8 | |||
| 6276d13814 | |||
| 0f3d65254e | |||
| 13516c4f73 | |||
| 68cbd3c500 | |||
| 1cc3351bef | |||
| 2a6a963ecd | |||
| 6e135b9d7a | |||
| afc31c02ab | |||
| 7be865d7f1 | |||
| a58e507f92 | |||
| cc9f3f21de | |||
| 56ae2ca4fc | |||
| bcd4829486 |
17
.dockerignore
Normal file
17
.dockerignore
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
target/
|
||||||
|
.git/
|
||||||
|
.gitignore
|
||||||
|
perf.*
|
||||||
|
**/* copy.excalidraw
|
||||||
|
ui/rpki-explorer/node_modules/
|
||||||
|
ui/rpki-explorer/dist/
|
||||||
|
ui/rpki-explorer/playwright-report/
|
||||||
|
ui/rpki-explorer/test-results/
|
||||||
|
ui/rpki-explorer/.vite/
|
||||||
|
deploy/arm64-compose/.env
|
||||||
|
target/
|
||||||
|
*.profraw
|
||||||
|
*.profdata
|
||||||
|
*.tar
|
||||||
|
*.tar.gz
|
||||||
|
*.zip
|
||||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -1,2 +1,9 @@
|
|||||||
target/
|
target/
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
|
perf.*
|
||||||
|
specs/* copy.excalidraw
|
||||||
|
ui/rpki-explorer/node_modules/
|
||||||
|
ui/rpki-explorer/dist/
|
||||||
|
ui/rpki-explorer/playwright-report/
|
||||||
|
ui/rpki-explorer/test-results/
|
||||||
|
ui/rpki-explorer/.vite/
|
||||||
|
|||||||
30
Cargo.toml
30
Cargo.toml
@@ -3,13 +3,35 @@ name = "rpki"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["full"]
|
||||||
|
# Full build used by the main RP implementation (includes RocksDB-backed storage).
|
||||||
|
full = ["dep:rocksdb"]
|
||||||
|
profile = ["dep:pprof", "dep:flate2"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
der-parser = "10.0.0"
|
asn1-rs = "0.7.1"
|
||||||
|
der-parser = { version = "10.0.0", features = ["serialize"] }
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
|
base64 = "0.22.1"
|
||||||
|
sha2 = "0.10.8"
|
||||||
thiserror = "2.0.18"
|
thiserror = "2.0.18"
|
||||||
time = "0.3.45"
|
time = "0.3.45"
|
||||||
|
ring = "0.17.14"
|
||||||
x509-parser = { version = "0.18.0", features = ["verify"] }
|
x509-parser = { version = "0.18.0", features = ["verify"] }
|
||||||
url = "2.5.8"
|
url = "2.5.8"
|
||||||
asn1-rs = "0.7.1"
|
serde = { version = "1.0.218", features = ["derive"] }
|
||||||
asn1-rs-derive = "0.6.0"
|
serde_json = { version = "1.0.140", features = ["raw_value"] }
|
||||||
asn1 = "0.23.0"
|
toml = "0.8.20"
|
||||||
|
rocksdb = { version = "0.22.0", optional = true, default-features = false, features = ["lz4"] }
|
||||||
|
serde_cbor = "0.11.2"
|
||||||
|
memmap2 = "0.9.10"
|
||||||
|
roxmltree = "0.20.0"
|
||||||
|
quick-xml = "0.37.2"
|
||||||
|
uuid = { version = "1.7.0", features = ["v4"] }
|
||||||
|
reqwest = { version = "0.12.12", default-features = false, features = ["blocking", "rustls-tls", "gzip", "brotli", "deflate"] }
|
||||||
|
pprof = { version = "0.14.1", optional = true, features = ["flamegraph", "prost-codec"] }
|
||||||
|
flate2 = { version = "1.0.35", optional = true }
|
||||||
|
tempfile = "3.16.0"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
|||||||
52
README.md
52
README.md
@@ -9,3 +9,55 @@ cargo test
|
|||||||
cargo test -- --nocapture
|
cargo test -- --nocapture
|
||||||
```
|
```
|
||||||
|
|
||||||
|
# 覆盖率(cargo-llvm-cov)
|
||||||
|
|
||||||
|
安装工具:
|
||||||
|
|
||||||
|
```
|
||||||
|
rustup component add llvm-tools-preview
|
||||||
|
cargo install cargo-llvm-cov --locked
|
||||||
|
```
|
||||||
|
|
||||||
|
统计行覆盖率并要求 >=90%:
|
||||||
|
|
||||||
|
```
|
||||||
|
./scripts/coverage.sh
|
||||||
|
# 或
|
||||||
|
cargo llvm-cov --fail-under-lines 90
|
||||||
|
```
|
||||||
|
|
||||||
|
默认会复用现有插桩产物,不会先 clean。需要强制全量重编译时:
|
||||||
|
|
||||||
|
```
|
||||||
|
COVERAGE_FORCE_CLEAN=1 ./scripts/coverage.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
说明:
|
||||||
|
- 默认行为适合本地重复确认覆盖率,避免每次都重编译整套插桩目标;
|
||||||
|
- 默认还会设置 `RPKI_SKIP_HEAVY_SCRIPT_REPLAY_TESTS=1`,跳过会拉起 shell replay pipeline 的重型集成测试,避免 coverage 期间额外触发 `target/release` 构建;
|
||||||
|
- 默认还会设置 `RPKI_SKIP_HEAVY_BLACKBOX_TESTS=1`,跳过更慢的 blackbox CLI / CIR record 脚本测试,进一步降低日常 coverage 成本;
|
||||||
|
- 默认还会设置 `RPKI_SKIP_HEAVY_CRYPTO_TESTS=1`,跳过需要大量 OpenSSL 生成证书/CRL 的重型密码学测试,进一步压缩日常 coverage 时长;
|
||||||
|
- 如需把这批脚本回放测试也纳入 coverage,可显式关闭该开关:
|
||||||
|
|
||||||
|
```
|
||||||
|
RPKI_SKIP_HEAVY_SCRIPT_REPLAY_TESTS=0 ./scripts/coverage.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
如需连同第二批 blackbox 测试一起跑:
|
||||||
|
|
||||||
|
```
|
||||||
|
RPKI_SKIP_HEAVY_BLACKBOX_TESTS=0 ./scripts/coverage.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
如需连同重型 OpenSSL 证书路径测试一起跑:
|
||||||
|
|
||||||
|
```
|
||||||
|
RPKI_SKIP_HEAVY_CRYPTO_TESTS=0 ./scripts/coverage.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
- replay 脚本现在也支持通过环境变量注入现成二进制,避免找不到二进制时自动 `cargo build --release`:
|
||||||
|
- `RPKI_BIN`
|
||||||
|
- `CIR_MATERIALIZE_BIN`
|
||||||
|
- `CIR_EXTRACT_INPUTS_BIN`
|
||||||
|
- `CCR_TO_COMPARE_VIEWS_BIN`
|
||||||
|
- `COVERAGE_FORCE_CLEAN=1` 适合需要完全从零重建插桩目标时使用。
|
||||||
|
|||||||
8
benchmark/ours_manifest_bench/Cargo.toml
Normal file
8
benchmark/ours_manifest_bench/Cargo.toml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
[package]
|
||||||
|
name = "ours-manifest-bench"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
rpki = { path = "../..", default-features = false }
|
||||||
|
|
||||||
145
benchmark/ours_manifest_bench/src/main.rs
Normal file
145
benchmark/ours_manifest_bench/src/main.rs
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
use rpki::data_model::manifest::ManifestObject;
|
||||||
|
use std::hint::black_box;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct Config {
|
||||||
|
sample: Option<String>,
|
||||||
|
manifest_path: Option<PathBuf>,
|
||||||
|
iterations: u64,
|
||||||
|
warmup_iterations: u64,
|
||||||
|
repeats: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage_and_exit() -> ! {
|
||||||
|
eprintln!(
|
||||||
|
"Usage:\n ours-manifest-bench (--sample <name> | --manifest <path>) [--iterations N] [--warmup-iterations N] [--repeats N]\n\nExamples:\n cargo run --release -- --sample small-01 --iterations 20000 --warmup-iterations 2000 --repeats 3\n cargo run --release -- --manifest ../../tests/benchmark/selected_der/small-01.mft"
|
||||||
|
);
|
||||||
|
std::process::exit(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_args() -> Config {
|
||||||
|
let mut sample: Option<String> = None;
|
||||||
|
let mut manifest_path: Option<PathBuf> = None;
|
||||||
|
let mut iterations: u64 = 20_000;
|
||||||
|
let mut warmup_iterations: u64 = 2_000;
|
||||||
|
let mut repeats: u32 = 3;
|
||||||
|
|
||||||
|
let mut args = std::env::args().skip(1);
|
||||||
|
while let Some(arg) = args.next() {
|
||||||
|
match arg.as_str() {
|
||||||
|
"--sample" => sample = Some(args.next().unwrap_or_else(|| usage_and_exit())),
|
||||||
|
"--manifest" => {
|
||||||
|
manifest_path = Some(PathBuf::from(args.next().unwrap_or_else(|| usage_and_exit())))
|
||||||
|
}
|
||||||
|
"--iterations" => {
|
||||||
|
iterations = args
|
||||||
|
.next()
|
||||||
|
.unwrap_or_else(|| usage_and_exit())
|
||||||
|
.parse()
|
||||||
|
.unwrap_or_else(|_| usage_and_exit())
|
||||||
|
}
|
||||||
|
"--warmup-iterations" => {
|
||||||
|
warmup_iterations = args
|
||||||
|
.next()
|
||||||
|
.unwrap_or_else(|| usage_and_exit())
|
||||||
|
.parse()
|
||||||
|
.unwrap_or_else(|_| usage_and_exit())
|
||||||
|
}
|
||||||
|
"--repeats" => {
|
||||||
|
repeats = args
|
||||||
|
.next()
|
||||||
|
.unwrap_or_else(|| usage_and_exit())
|
||||||
|
.parse()
|
||||||
|
.unwrap_or_else(|_| usage_and_exit())
|
||||||
|
}
|
||||||
|
"-h" | "--help" => usage_and_exit(),
|
||||||
|
_ => usage_and_exit(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if sample.is_none() && manifest_path.is_none() {
|
||||||
|
usage_and_exit();
|
||||||
|
}
|
||||||
|
if sample.is_some() && manifest_path.is_some() {
|
||||||
|
usage_and_exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
Config {
|
||||||
|
sample,
|
||||||
|
manifest_path,
|
||||||
|
iterations,
|
||||||
|
warmup_iterations,
|
||||||
|
repeats,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn derive_manifest_path(sample: &str) -> PathBuf {
|
||||||
|
// Assumes current working directory is `rpki/benchmark/ours_manifest_bench`.
|
||||||
|
PathBuf::from(format!("../../tests/benchmark/selected_der/{sample}.mft"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let cfg = parse_args();
|
||||||
|
let manifest_path = cfg
|
||||||
|
.manifest_path
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| derive_manifest_path(cfg.sample.as_deref().unwrap()));
|
||||||
|
|
||||||
|
let bytes = std::fs::read(&manifest_path).unwrap_or_else(|e| {
|
||||||
|
eprintln!("read manifest fixture failed: {e}; path={}", manifest_path.display());
|
||||||
|
std::process::exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
let decoded_once = ManifestObject::decode_der(&bytes).unwrap_or_else(|e| {
|
||||||
|
eprintln!("decode failed: {e}; path={}", manifest_path.display());
|
||||||
|
std::process::exit(1);
|
||||||
|
});
|
||||||
|
let file_count = decoded_once.manifest.file_count();
|
||||||
|
|
||||||
|
let mut round_ns_per_op: Vec<f64> = Vec::with_capacity(cfg.repeats as usize);
|
||||||
|
let mut round_ops_per_s: Vec<f64> = Vec::with_capacity(cfg.repeats as usize);
|
||||||
|
|
||||||
|
for _round in 0..cfg.repeats {
|
||||||
|
for _ in 0..cfg.warmup_iterations {
|
||||||
|
let obj = ManifestObject::decode_der(black_box(&bytes)).expect("warmup decode");
|
||||||
|
black_box(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
let start = Instant::now();
|
||||||
|
for _ in 0..cfg.iterations {
|
||||||
|
let obj = ManifestObject::decode_der(black_box(&bytes)).expect("timed decode");
|
||||||
|
black_box(obj);
|
||||||
|
}
|
||||||
|
let elapsed = start.elapsed();
|
||||||
|
|
||||||
|
let ns_per_op = (elapsed.as_secs_f64() * 1e9) / (cfg.iterations as f64);
|
||||||
|
let ops_per_s = (cfg.iterations as f64) / elapsed.as_secs_f64();
|
||||||
|
round_ns_per_op.push(ns_per_op);
|
||||||
|
round_ops_per_s.push(ops_per_s);
|
||||||
|
}
|
||||||
|
|
||||||
|
let avg_ns_per_op = round_ns_per_op.iter().sum::<f64>() / (round_ns_per_op.len() as f64);
|
||||||
|
let avg_ops_per_s = round_ops_per_s.iter().sum::<f64>() / (round_ops_per_s.len() as f64);
|
||||||
|
|
||||||
|
let sample_name = cfg.sample.clone().unwrap_or_else(|| {
|
||||||
|
manifest_path
|
||||||
|
.file_name()
|
||||||
|
.map(|s| s.to_string_lossy().to_string())
|
||||||
|
.unwrap_or_else(|| manifest_path.display().to_string())
|
||||||
|
});
|
||||||
|
let sample_name = sample_name
|
||||||
|
.strip_suffix(".mft")
|
||||||
|
.unwrap_or(&sample_name)
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
println!("fixture: {}", manifest_path.display());
|
||||||
|
println!();
|
||||||
|
println!("| sample | avg ns/op | ops/s | file count |");
|
||||||
|
println!("|---|---:|---:|---:|");
|
||||||
|
println!(
|
||||||
|
"| {} | {:.2} | {:.2} | {} |",
|
||||||
|
sample_name, avg_ns_per_op, avg_ops_per_s, file_count
|
||||||
|
);
|
||||||
|
}
|
||||||
8
benchmark/routinator_object_bench/Cargo.toml
Normal file
8
benchmark/routinator_object_bench/Cargo.toml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
[package]
|
||||||
|
name = "routinator-object-bench"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
rpki = { version = "=0.19.1", features = ["repository"] }
|
||||||
552
benchmark/routinator_object_bench/src/main.rs
Normal file
552
benchmark/routinator_object_bench/src/main.rs
Normal file
@@ -0,0 +1,552 @@
|
|||||||
|
use rpki::repository::cert::Cert;
|
||||||
|
use rpki::repository::crl::Crl;
|
||||||
|
use rpki::repository::manifest::Manifest;
|
||||||
|
use rpki::repository::roa::Roa;
|
||||||
|
use rpki::repository::aspa::Aspa;
|
||||||
|
use rpki::repository::resources::{AsResources, IpResources};
|
||||||
|
|
||||||
|
use std::hint::black_box;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
enum ObjType {
|
||||||
|
Cer,
|
||||||
|
Crl,
|
||||||
|
Manifest,
|
||||||
|
Roa,
|
||||||
|
Aspa,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ObjType {
|
||||||
|
fn parse(s: &str) -> Result<Self, String> {
|
||||||
|
match s {
|
||||||
|
"cer" => Ok(Self::Cer),
|
||||||
|
"crl" => Ok(Self::Crl),
|
||||||
|
"manifest" => Ok(Self::Manifest),
|
||||||
|
"roa" => Ok(Self::Roa),
|
||||||
|
"aspa" => Ok(Self::Aspa),
|
||||||
|
_ => Err("type must be one of: cer, crl, manifest, roa, aspa".into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_str(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
ObjType::Cer => "cer",
|
||||||
|
ObjType::Crl => "crl",
|
||||||
|
ObjType::Manifest => "manifest",
|
||||||
|
ObjType::Roa => "roa",
|
||||||
|
ObjType::Aspa => "aspa",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ext(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
ObjType::Cer => "cer",
|
||||||
|
ObjType::Crl => "crl",
|
||||||
|
ObjType::Manifest => "mft",
|
||||||
|
ObjType::Roa => "roa",
|
||||||
|
ObjType::Aspa => "asa",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct Sample {
|
||||||
|
obj_type: ObjType,
|
||||||
|
name: String,
|
||||||
|
path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct Config {
|
||||||
|
dir: PathBuf,
|
||||||
|
type_filter: Option<ObjType>,
|
||||||
|
sample_filter: Option<String>,
|
||||||
|
fixed_iters: Option<u64>,
|
||||||
|
warmup_iters: u64,
|
||||||
|
rounds: u64,
|
||||||
|
min_round_ms: u64,
|
||||||
|
max_adaptive_iters: u64,
|
||||||
|
strict: bool,
|
||||||
|
cert_inspect: bool,
|
||||||
|
out_csv: Option<PathBuf>,
|
||||||
|
out_md: Option<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage_and_exit(err: Option<&str>) -> ! {
|
||||||
|
if let Some(err) = err {
|
||||||
|
eprintln!("error: {err}");
|
||||||
|
eprintln!();
|
||||||
|
}
|
||||||
|
eprintln!(
|
||||||
|
"Usage:\n\
|
||||||
|
cargo run --release --manifest-path rpki/benchmark/routinator_object_bench/Cargo.toml -- [OPTIONS]\n\
|
||||||
|
\n\
|
||||||
|
Options:\n\
|
||||||
|
--dir <PATH> Fixtures root dir (default: ../../tests/benchmark/selected_der_v2)\n\
|
||||||
|
--type <cer|crl|manifest|roa|aspa> Filter by type\n\
|
||||||
|
--sample <NAME> Filter by sample name (e.g. p50)\n\
|
||||||
|
--iters <N> Fixed iterations per round (optional; otherwise adaptive)\n\
|
||||||
|
--warmup-iters <N> Warmup iterations (default: 50)\n\
|
||||||
|
--rounds <N> Rounds (default: 5)\n\
|
||||||
|
--min-round-ms <MS> Adaptive: minimum round time (default: 200)\n\
|
||||||
|
--max-iters <N> Adaptive: maximum iters (default: 1_000_000)\n\
|
||||||
|
--strict <true|false> Strict DER where applicable (default: true)\n\
|
||||||
|
--cert-inspect Also run Cert::inspect_ca/inspect_ee where applicable (default: false)\n\
|
||||||
|
--out-csv <PATH> Write CSV output\n\
|
||||||
|
--out-md <PATH> Write Markdown output\n\
|
||||||
|
"
|
||||||
|
);
|
||||||
|
std::process::exit(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_bool(s: &str, name: &str) -> bool {
|
||||||
|
match s {
|
||||||
|
"1" | "true" | "TRUE" | "yes" | "YES" => true,
|
||||||
|
"0" | "false" | "FALSE" | "no" | "NO" => false,
|
||||||
|
_ => usage_and_exit(Some(&format!("{name} must be true/false"))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_u64(s: &str, name: &str) -> u64 {
|
||||||
|
s.parse::<u64>()
|
||||||
|
.unwrap_or_else(|_| usage_and_exit(Some(&format!("{name} must be an integer"))))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_samples_dir() -> PathBuf {
|
||||||
|
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../tests/benchmark/selected_der_v2")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_args() -> Config {
|
||||||
|
let mut dir: PathBuf = default_samples_dir();
|
||||||
|
let mut type_filter: Option<ObjType> = None;
|
||||||
|
let mut sample_filter: Option<String> = None;
|
||||||
|
let mut fixed_iters: Option<u64> = None;
|
||||||
|
let mut warmup_iters: u64 = 50;
|
||||||
|
let mut rounds: u64 = 5;
|
||||||
|
let mut min_round_ms: u64 = 200;
|
||||||
|
let mut max_adaptive_iters: u64 = 1_000_000;
|
||||||
|
let mut strict: bool = true;
|
||||||
|
let mut cert_inspect: bool = false;
|
||||||
|
let mut out_csv: Option<PathBuf> = None;
|
||||||
|
let mut out_md: Option<PathBuf> = None;
|
||||||
|
|
||||||
|
let mut args = std::env::args().skip(1);
|
||||||
|
while let Some(arg) = args.next() {
|
||||||
|
match arg.as_str() {
|
||||||
|
"--dir" => dir = PathBuf::from(args.next().unwrap_or_else(|| usage_and_exit(None))),
|
||||||
|
"--type" => {
|
||||||
|
type_filter = Some(ObjType::parse(
|
||||||
|
&args.next().unwrap_or_else(|| usage_and_exit(None)),
|
||||||
|
)
|
||||||
|
.unwrap_or_else(|e| usage_and_exit(Some(&e))))
|
||||||
|
}
|
||||||
|
"--sample" => {
|
||||||
|
sample_filter = Some(args.next().unwrap_or_else(|| usage_and_exit(None)))
|
||||||
|
}
|
||||||
|
"--iters" => {
|
||||||
|
fixed_iters = Some(parse_u64(
|
||||||
|
&args.next().unwrap_or_else(|| usage_and_exit(None)),
|
||||||
|
"--iters",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
"--warmup-iters" => {
|
||||||
|
warmup_iters = parse_u64(
|
||||||
|
&args.next().unwrap_or_else(|| usage_and_exit(None)),
|
||||||
|
"--warmup-iters",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
"--rounds" => {
|
||||||
|
rounds = parse_u64(&args.next().unwrap_or_else(|| usage_and_exit(None)), "--rounds")
|
||||||
|
}
|
||||||
|
"--min-round-ms" => {
|
||||||
|
min_round_ms = parse_u64(
|
||||||
|
&args.next().unwrap_or_else(|| usage_and_exit(None)),
|
||||||
|
"--min-round-ms",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
"--max-iters" => {
|
||||||
|
max_adaptive_iters = parse_u64(
|
||||||
|
&args.next().unwrap_or_else(|| usage_and_exit(None)),
|
||||||
|
"--max-iters",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
"--strict" => {
|
||||||
|
strict = parse_bool(
|
||||||
|
&args.next().unwrap_or_else(|| usage_and_exit(None)),
|
||||||
|
"--strict",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
"--cert-inspect" => cert_inspect = true,
|
||||||
|
"--out-csv" => out_csv = Some(PathBuf::from(args.next().unwrap_or_else(|| usage_and_exit(None)))),
|
||||||
|
"--out-md" => out_md = Some(PathBuf::from(args.next().unwrap_or_else(|| usage_and_exit(None)))),
|
||||||
|
"-h" | "--help" => usage_and_exit(None),
|
||||||
|
_ => usage_and_exit(Some(&format!("unknown argument: {arg}"))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if warmup_iters == 0 {
|
||||||
|
usage_and_exit(Some("--warmup-iters must be > 0"));
|
||||||
|
}
|
||||||
|
if rounds == 0 {
|
||||||
|
usage_and_exit(Some("--rounds must be > 0"));
|
||||||
|
}
|
||||||
|
if min_round_ms == 0 {
|
||||||
|
usage_and_exit(Some("--min-round-ms must be > 0"));
|
||||||
|
}
|
||||||
|
if max_adaptive_iters == 0 {
|
||||||
|
usage_and_exit(Some("--max-iters must be > 0"));
|
||||||
|
}
|
||||||
|
if let Some(n) = fixed_iters {
|
||||||
|
if n == 0 {
|
||||||
|
usage_and_exit(Some("--iters must be > 0"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Config {
|
||||||
|
dir,
|
||||||
|
type_filter,
|
||||||
|
sample_filter,
|
||||||
|
fixed_iters,
|
||||||
|
warmup_iters,
|
||||||
|
rounds,
|
||||||
|
min_round_ms,
|
||||||
|
max_adaptive_iters,
|
||||||
|
strict,
|
||||||
|
cert_inspect,
|
||||||
|
out_csv,
|
||||||
|
out_md,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_samples(root: &Path) -> Vec<Sample> {
|
||||||
|
let mut out = Vec::new();
|
||||||
|
for obj_type in [
|
||||||
|
ObjType::Cer,
|
||||||
|
ObjType::Crl,
|
||||||
|
ObjType::Manifest,
|
||||||
|
ObjType::Roa,
|
||||||
|
ObjType::Aspa,
|
||||||
|
] {
|
||||||
|
let dir = root.join(obj_type.as_str());
|
||||||
|
let rd = match std::fs::read_dir(&dir) {
|
||||||
|
Ok(rd) => rd,
|
||||||
|
Err(_) => continue,
|
||||||
|
};
|
||||||
|
for ent in rd.flatten() {
|
||||||
|
let path = ent.path();
|
||||||
|
if path.extension().and_then(|s| s.to_str()) != Some(obj_type.ext()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let name = path
|
||||||
|
.file_stem()
|
||||||
|
.and_then(|s| s.to_str())
|
||||||
|
.unwrap_or("unknown")
|
||||||
|
.to_string();
|
||||||
|
out.push(Sample { obj_type, name, path });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out.sort_by(|a, b| a.obj_type.cmp(&b.obj_type).then_with(|| a.name.cmp(&b.name)));
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
fn choose_iters_adaptive<F: FnMut()>(mut op: F, min_round_ms: u64, max_iters: u64) -> u64 {
|
||||||
|
let min_secs = (min_round_ms as f64) / 1e3;
|
||||||
|
let mut iters: u64 = 1;
|
||||||
|
loop {
|
||||||
|
let start = Instant::now();
|
||||||
|
for _ in 0..iters {
|
||||||
|
op();
|
||||||
|
}
|
||||||
|
let elapsed = start.elapsed().as_secs_f64();
|
||||||
|
if elapsed >= min_secs {
|
||||||
|
return iters;
|
||||||
|
}
|
||||||
|
if iters >= max_iters {
|
||||||
|
return iters;
|
||||||
|
}
|
||||||
|
iters = (iters.saturating_mul(2)).min(max_iters);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn count_ip(res: &IpResources) -> u64 {
|
||||||
|
if res.is_inherited() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
let Ok(blocks) = res.to_blocks() else {
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
blocks.iter().count() as u64
|
||||||
|
}
|
||||||
|
|
||||||
|
fn count_as(res: &AsResources) -> u64 {
|
||||||
|
if res.is_inherited() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
let Ok(blocks) = res.to_blocks() else {
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
blocks.iter().count() as u64
|
||||||
|
}
|
||||||
|
|
||||||
|
fn complexity(obj_type: ObjType, bytes: &[u8], strict: bool, cert_inspect: bool) -> u64 {
|
||||||
|
match obj_type {
|
||||||
|
ObjType::Cer => {
|
||||||
|
let cert = Cert::decode(bytes).expect("decode cert");
|
||||||
|
if cert_inspect {
|
||||||
|
if cert.is_ca() {
|
||||||
|
cert.inspect_ca(strict).expect("inspect ca");
|
||||||
|
} else {
|
||||||
|
cert.inspect_ee(strict).expect("inspect ee");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
count_ip(cert.v4_resources())
|
||||||
|
.saturating_add(count_ip(cert.v6_resources()))
|
||||||
|
.saturating_add(count_as(cert.as_resources()))
|
||||||
|
}
|
||||||
|
ObjType::Crl => {
|
||||||
|
let crl = Crl::decode(bytes).expect("decode crl");
|
||||||
|
crl.revoked_certs().iter().count() as u64
|
||||||
|
}
|
||||||
|
ObjType::Manifest => {
|
||||||
|
let mft = Manifest::decode(bytes, strict).expect("decode manifest");
|
||||||
|
if cert_inspect {
|
||||||
|
mft.cert().inspect_ee(strict).expect("inspect ee");
|
||||||
|
}
|
||||||
|
mft.content().len() as u64
|
||||||
|
}
|
||||||
|
ObjType::Roa => {
|
||||||
|
let roa = Roa::decode(bytes, strict).expect("decode roa");
|
||||||
|
if cert_inspect {
|
||||||
|
roa.cert().inspect_ee(strict).expect("inspect ee");
|
||||||
|
}
|
||||||
|
roa.content().iter().count() as u64
|
||||||
|
}
|
||||||
|
ObjType::Aspa => {
|
||||||
|
let asa = Aspa::decode(bytes, strict).expect("decode aspa");
|
||||||
|
if cert_inspect {
|
||||||
|
asa.cert().inspect_ee(strict).expect("inspect ee");
|
||||||
|
}
|
||||||
|
asa.content().provider_as_set().len() as u64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_profile(obj_type: ObjType, bytes: &[u8], strict: bool, cert_inspect: bool) {
|
||||||
|
match obj_type {
|
||||||
|
ObjType::Cer => {
|
||||||
|
let cert = Cert::decode(black_box(bytes)).expect("decode cert");
|
||||||
|
if cert_inspect {
|
||||||
|
if cert.is_ca() {
|
||||||
|
cert.inspect_ca(strict).expect("inspect ca");
|
||||||
|
} else {
|
||||||
|
cert.inspect_ee(strict).expect("inspect ee");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
black_box(cert);
|
||||||
|
}
|
||||||
|
ObjType::Crl => {
|
||||||
|
let crl = Crl::decode(black_box(bytes)).expect("decode crl");
|
||||||
|
black_box(crl);
|
||||||
|
}
|
||||||
|
ObjType::Manifest => {
|
||||||
|
let mft = Manifest::decode(black_box(bytes), strict).expect("decode manifest");
|
||||||
|
if cert_inspect {
|
||||||
|
mft.cert().inspect_ee(strict).expect("inspect ee");
|
||||||
|
}
|
||||||
|
black_box(mft);
|
||||||
|
}
|
||||||
|
ObjType::Roa => {
|
||||||
|
let roa = Roa::decode(black_box(bytes), strict).expect("decode roa");
|
||||||
|
if cert_inspect {
|
||||||
|
roa.cert().inspect_ee(strict).expect("inspect ee");
|
||||||
|
}
|
||||||
|
black_box(roa);
|
||||||
|
}
|
||||||
|
ObjType::Aspa => {
|
||||||
|
let asa = Aspa::decode(black_box(bytes), strict).expect("decode aspa");
|
||||||
|
if cert_inspect {
|
||||||
|
asa.cert().inspect_ee(strict).expect("inspect ee");
|
||||||
|
}
|
||||||
|
black_box(asa);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct ResultRow {
|
||||||
|
obj_type: String,
|
||||||
|
sample: String,
|
||||||
|
size_bytes: usize,
|
||||||
|
complexity: u64,
|
||||||
|
avg_ns_per_op: f64,
|
||||||
|
ops_per_sec: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_markdown(title: &str, rows: &[ResultRow]) -> String {
|
||||||
|
let mut out = String::new();
|
||||||
|
out.push_str(&format!("# {title}\n\n"));
|
||||||
|
out.push_str("| type | sample | size_bytes | complexity | avg ns/op | ops/s |\n");
|
||||||
|
out.push_str("|---|---|---:|---:|---:|---:|\n");
|
||||||
|
for r in rows {
|
||||||
|
out.push_str(&format!(
|
||||||
|
"| {} | {} | {} | {} | {:.2} | {:.2} |\n",
|
||||||
|
r.obj_type, r.sample, r.size_bytes, r.complexity, r.avg_ns_per_op, r.ops_per_sec
|
||||||
|
));
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_csv(rows: &[ResultRow]) -> String {
|
||||||
|
let mut out = String::new();
|
||||||
|
out.push_str("type,sample,size_bytes,complexity,avg_ns_per_op,ops_per_sec\n");
|
||||||
|
for r in rows {
|
||||||
|
let sample = r.sample.replace('"', "\"\"");
|
||||||
|
out.push_str(&format!(
|
||||||
|
"{},{},{},{},{:.6},{:.6}\n",
|
||||||
|
r.obj_type,
|
||||||
|
format!("\"{}\"", sample),
|
||||||
|
r.size_bytes,
|
||||||
|
r.complexity,
|
||||||
|
r.avg_ns_per_op,
|
||||||
|
r.ops_per_sec
|
||||||
|
));
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_parent_dirs(path: &Path) {
|
||||||
|
if let Some(parent) = path.parent() {
|
||||||
|
std::fs::create_dir_all(parent).unwrap_or_else(|e| {
|
||||||
|
panic!("create_dir_all {}: {e}", parent.display());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_text_file(path: &Path, content: &str) {
|
||||||
|
create_parent_dirs(path);
|
||||||
|
std::fs::write(path, content).unwrap_or_else(|e| panic!("write {}: {e}", path.display()));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let cfg = parse_args();
|
||||||
|
let mut samples = read_samples(&cfg.dir);
|
||||||
|
if samples.is_empty() {
|
||||||
|
usage_and_exit(Some(&format!(
|
||||||
|
"no samples found under: {}",
|
||||||
|
cfg.dir.display()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(t) = cfg.type_filter {
|
||||||
|
samples.retain(|s| s.obj_type == t);
|
||||||
|
if samples.is_empty() {
|
||||||
|
usage_and_exit(Some(&format!("no sample matched --type {}", t.as_str())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(filter) = cfg.sample_filter.as_deref() {
|
||||||
|
samples.retain(|s| s.name == filter);
|
||||||
|
if samples.is_empty() {
|
||||||
|
usage_and_exit(Some(&format!("no sample matched --sample {filter}")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("# Routinator baseline (rpki crate) decode benchmark (selected_der_v2)");
|
||||||
|
println!();
|
||||||
|
println!("- dir: {}", cfg.dir.display());
|
||||||
|
println!("- strict: {}", cfg.strict);
|
||||||
|
println!("- cert_inspect: {}", cfg.cert_inspect);
|
||||||
|
if let Some(t) = cfg.type_filter {
|
||||||
|
println!("- type: {}", t.as_str());
|
||||||
|
}
|
||||||
|
if let Some(s) = cfg.sample_filter.as_deref() {
|
||||||
|
println!("- sample: {}", s);
|
||||||
|
}
|
||||||
|
if let Some(n) = cfg.fixed_iters {
|
||||||
|
println!("- iters: {} (fixed)", n);
|
||||||
|
} else {
|
||||||
|
println!(
|
||||||
|
"- warmup: {} iters, rounds: {}, min_round: {}ms (adaptive iters, max {})",
|
||||||
|
cfg.warmup_iters, cfg.rounds, cfg.min_round_ms, cfg.max_adaptive_iters
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if let Some(p) = cfg.out_csv.as_ref() {
|
||||||
|
println!("- out_csv: {}", p.display());
|
||||||
|
}
|
||||||
|
if let Some(p) = cfg.out_md.as_ref() {
|
||||||
|
println!("- out_md: {}", p.display());
|
||||||
|
}
|
||||||
|
println!();
|
||||||
|
|
||||||
|
println!("| type | sample | size_bytes | complexity | avg ns/op | ops/s |");
|
||||||
|
println!("|---|---|---:|---:|---:|---:|");
|
||||||
|
|
||||||
|
let mut rows: Vec<ResultRow> = Vec::with_capacity(samples.len());
|
||||||
|
for sample in &samples {
|
||||||
|
let bytes = std::fs::read(&sample.path)
|
||||||
|
.unwrap_or_else(|e| panic!("read {}: {e}", sample.path.display()));
|
||||||
|
let size_bytes = bytes.len();
|
||||||
|
let complexity = complexity(sample.obj_type, bytes.as_slice(), cfg.strict, cfg.cert_inspect);
|
||||||
|
|
||||||
|
for _ in 0..cfg.warmup_iters {
|
||||||
|
decode_profile(sample.obj_type, bytes.as_slice(), cfg.strict, cfg.cert_inspect);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut per_round_ns_per_op = Vec::with_capacity(cfg.rounds as usize);
|
||||||
|
for _round in 0..cfg.rounds {
|
||||||
|
let iters = if let Some(n) = cfg.fixed_iters {
|
||||||
|
n
|
||||||
|
} else {
|
||||||
|
choose_iters_adaptive(
|
||||||
|
|| decode_profile(sample.obj_type, bytes.as_slice(), cfg.strict, cfg.cert_inspect),
|
||||||
|
cfg.min_round_ms,
|
||||||
|
cfg.max_adaptive_iters,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
let start = Instant::now();
|
||||||
|
for _ in 0..iters {
|
||||||
|
decode_profile(sample.obj_type, bytes.as_slice(), cfg.strict, cfg.cert_inspect);
|
||||||
|
}
|
||||||
|
let elapsed = start.elapsed();
|
||||||
|
let total_ns = elapsed.as_secs_f64() * 1e9;
|
||||||
|
per_round_ns_per_op.push(total_ns / (iters as f64));
|
||||||
|
}
|
||||||
|
|
||||||
|
let avg_ns = per_round_ns_per_op.iter().sum::<f64>() / (per_round_ns_per_op.len() as f64);
|
||||||
|
let ops_per_sec = 1e9_f64 / avg_ns;
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"| {} | {} | {} | {} | {:.2} | {:.2} |",
|
||||||
|
sample.obj_type.as_str(),
|
||||||
|
sample.name,
|
||||||
|
size_bytes,
|
||||||
|
complexity,
|
||||||
|
avg_ns,
|
||||||
|
ops_per_sec
|
||||||
|
);
|
||||||
|
|
||||||
|
rows.push(ResultRow {
|
||||||
|
obj_type: sample.obj_type.as_str().to_string(),
|
||||||
|
sample: sample.name.clone(),
|
||||||
|
size_bytes,
|
||||||
|
complexity,
|
||||||
|
avg_ns_per_op: avg_ns,
|
||||||
|
ops_per_sec,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(path) = cfg.out_md.as_ref() {
|
||||||
|
let md = render_markdown(
|
||||||
|
"Routinator baseline (rpki crate) decode+inspect (selected_der_v2)",
|
||||||
|
&rows,
|
||||||
|
);
|
||||||
|
write_text_file(path, &md);
|
||||||
|
eprintln!("Wrote {}", path.display());
|
||||||
|
}
|
||||||
|
if let Some(path) = cfg.out_csv.as_ref() {
|
||||||
|
let csv = render_csv(&rows);
|
||||||
|
write_text_file(path, &csv);
|
||||||
|
eprintln!("Wrote {}", path.display());
|
||||||
|
}
|
||||||
|
}
|
||||||
108
deploy/docker-installer/.env.example
Normal file
108
deploy/docker-installer/.env.example
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
# ours RP Docker installer configuration
|
||||||
|
# `build_docker_installer_package.sh` rewrites package-specific placeholders
|
||||||
|
# when producing amd64/arm64 release packages.
|
||||||
|
# 中文说明见 docs/README.zh-CN.md。English guide: docs/README.en.md
|
||||||
|
|
||||||
|
# Package metadata and architecture guardrails.
|
||||||
|
PACKAGE_ARCH=__PACKAGE_ARCH__
|
||||||
|
PACKAGE_PLATFORM=__PACKAGE_PLATFORM__
|
||||||
|
ALLOW_CROSS_ARCH=0
|
||||||
|
|
||||||
|
# Compose project name.
|
||||||
|
COMPOSE_PROJECT_NAME=ours-rp-__PACKAGE_ARCH__-installer
|
||||||
|
|
||||||
|
# Runtime image loaded from images/*.tar.gz by install.sh.
|
||||||
|
RPKI_IMAGE=ours-rp-runtime-__PACKAGE_ARCH__:dev
|
||||||
|
RPKI_PLATFORM=__PACKAGE_PLATFORM__
|
||||||
|
|
||||||
|
# Metrics image loaded from images/*.tar.gz by install.sh.
|
||||||
|
METRICS_IMAGE=ours-rp-metrics-__PACKAGE_ARCH__:dev
|
||||||
|
METRICS_PLATFORM=__PACKAGE_PLATFORM__
|
||||||
|
|
||||||
|
# Restart policy for the soak container. Production default keeps the daemon alive.
|
||||||
|
# For finite acceptance tests such as MAX_RUNS=3, set SOAK_RESTART_POLICY=no to avoid an extra restarted run.
|
||||||
|
SOAK_RESTART_POLICY=unless-stopped
|
||||||
|
|
||||||
|
# Host-side persistent data directory. All state/runs/logs/monitoring data are bind-mounted here.
|
||||||
|
HOST_DATA_DIR=__HOST_DATA_DIR__
|
||||||
|
|
||||||
|
# RIR list. Options: afrinic,apnic,arin,lacnic,ripe
|
||||||
|
RIRS=afrinic,apnic,arin,lacnic,ripe
|
||||||
|
|
||||||
|
# Negative MAX_RUNS means keep running forever. Default production interval is 10 minutes.
|
||||||
|
MAX_RUNS=-1
|
||||||
|
INTERVAL_SECS=600
|
||||||
|
RETAIN_RUNS=100
|
||||||
|
|
||||||
|
# TAL/TA input mode:
|
||||||
|
# file-with-ta: use packaged fixture TAL + TA only.
|
||||||
|
# file-live-ta: use packaged fixture TAL; snapshot waits for live TA refresh, delta refreshes TA in background.
|
||||||
|
# url: pass TAL URL to child process.
|
||||||
|
TAL_INPUT_MODE=file-live-ta
|
||||||
|
LIVE_TA_REFRESH_BEFORE_SNAPSHOT=1
|
||||||
|
LIVE_TA_REFRESH_CONNECT_TIMEOUT_SECS=15
|
||||||
|
LIVE_TA_REFRESH_MAX_TIME_SECS=120
|
||||||
|
|
||||||
|
# Sync and runtime behavior.
|
||||||
|
RSYNC_SCOPE=module-root
|
||||||
|
DISABLE_COMPETING_RPS=0
|
||||||
|
RUN_ROOT=/var/lib/ours-rp
|
||||||
|
DB_DIR=/var/lib/ours-rp/state/db
|
||||||
|
RSYNC_MIRROR_ROOT=/var/lib/ours-rp/state/rsync-mirror
|
||||||
|
# 每轮完成并复制出正式 run 产物后清理 tmp/daemon-run_*,避免长跑占满磁盘。
|
||||||
|
CLEAN_TMP_AFTER_RUN=1
|
||||||
|
OUTPUT_COMPACT_REPORT=1
|
||||||
|
ALLOW_RSYNC_MIRROR_REUSE=1
|
||||||
|
FAILURE_SNAPSHOT_RESET=1
|
||||||
|
# Periodic snapshot reset of active state DB.
|
||||||
|
# 0: keep existing behavior.
|
||||||
|
# 1: after one successful snapshot, allow at most N successful delta runs;
|
||||||
|
# the next run is forced to snapshot and active state/db is rebuilt from empty.
|
||||||
|
# Lifecycle run state is persisted independently at:
|
||||||
|
# ${HOST_DATA_DIR}/state/run-lifecycle-state.json
|
||||||
|
# It is not affected by run retention or state/db reset.
|
||||||
|
PERIODIC_SNAPSHOT_RESET=0
|
||||||
|
PERIODIC_SNAPSHOT_MAX_DELTAS=100
|
||||||
|
DB_STATS_EXACT_EVERY=0
|
||||||
|
|
||||||
|
# Resource certificate validation mode passed to the rpki child process.
|
||||||
|
# Options: validation-update-03 | rfc6487
|
||||||
|
RESOURCE_VALIDATION_MODE=validation-update-03
|
||||||
|
|
||||||
|
# Validation and performance options aligned with current optimized soak defaults.
|
||||||
|
ENABLE_CHILD_CERTIFICATE_VALIDATION_CACHE=1
|
||||||
|
RPKI_ANALYZE=1
|
||||||
|
RPKI_EXTRA_ARGS="--enable-transport-request-prefetch --enable-publication-point-validation-cache --enable-roa-validation-cache --parallel-max-repo-sync-workers-global 4 --parallel-phase2-object-workers 4 --memory-trim-after-validation"
|
||||||
|
|
||||||
|
# Progress logs.
|
||||||
|
RPKI_PROGRESS_LOG=1
|
||||||
|
RPKI_PROGRESS_SLOW_SECS=20
|
||||||
|
RPKI_PROGRESS_STAGE_FRESH_SLOW_MS=2000
|
||||||
|
RPKI_PROGRESS_PP_CONTROL_SLOW_MS=200
|
||||||
|
RPKI_PROGRESS_PP_CACHE_SLOW_MS=100
|
||||||
|
RPKI_PROGRESS_CONTROL_LOOP_SLOW_MS=2000
|
||||||
|
|
||||||
|
# Metrics sidecar.
|
||||||
|
METRICS_INSTANCE=__PACKAGE_ARCH__-installer
|
||||||
|
METRICS_PORT=9556
|
||||||
|
METRICS_POLL_SECS=10
|
||||||
|
# Optional external RTR report directory produced by a separately deployed RTR service.
|
||||||
|
# Default points to an installer-managed empty fallback directory. To enable real RTR
|
||||||
|
# metrics, set this to the host-side report directory, for example:
|
||||||
|
# RTR_REPORT_DIR=/root/rpki/report
|
||||||
|
RTR_REPORT_DIR=__HOST_DATA_DIR__/empty-rtr-report
|
||||||
|
RTR_REPORT_CONTAINER_DIR=/var/lib/ours-rp/rtr-report
|
||||||
|
|
||||||
|
# Prometheus / Grafana.
|
||||||
|
# Monitor images are packaged as docker-save archives and loaded by install.sh.
|
||||||
|
MONITOR_PLATFORM=__PACKAGE_PLATFORM__
|
||||||
|
PROMETHEUS_IMAGE=prom/prometheus:v2.55.1
|
||||||
|
GRAFANA_IMAGE=grafana/grafana:11.3.1
|
||||||
|
PROMETHEUS_PORT=9090
|
||||||
|
PROMETHEUS_RETENTION=7d
|
||||||
|
GRAFANA_PORT=3000
|
||||||
|
GRAFANA_ADMIN_USER=admin
|
||||||
|
GRAFANA_ADMIN_PASSWORD=admin
|
||||||
|
|
||||||
|
# First snapshot waiting timeout used by start.sh.
|
||||||
|
FIRST_RUN_WAIT_TIMEOUT_SECS=7200
|
||||||
56
deploy/docker-installer/cleanup.sh
Executable file
56
deploy/docker-installer/cleanup.sh
Executable file
@@ -0,0 +1,56 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
# shellcheck source=scripts/common.sh
|
||||||
|
source "$SCRIPT_DIR/scripts/common.sh"
|
||||||
|
|
||||||
|
DRY_RUN=1
|
||||||
|
KEEP_RUNS=""
|
||||||
|
usage() {
|
||||||
|
cat <<'USAGE'
|
||||||
|
Usage: ./cleanup.sh [--execute] [--keep-runs N]
|
||||||
|
|
||||||
|
By default this is a dry-run. It removes old run_* directories beyond KEEP_RUNS
|
||||||
|
and clears tmp contents.
|
||||||
|
USAGE
|
||||||
|
}
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--execute)
|
||||||
|
DRY_RUN=0
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--keep-runs)
|
||||||
|
KEEP_RUNS="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
die "unknown option: $1"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
load_env
|
||||||
|
keep="${KEEP_RUNS:-${RETAIN_RUNS:-100}}"
|
||||||
|
mapfile -t runs < <(find "$HOST_DATA_DIR/runs" -maxdepth 1 -type d -name 'run_*' 2>/dev/null | sort)
|
||||||
|
delete_count=$(( ${#runs[@]} - keep ))
|
||||||
|
if (( delete_count > 0 )); then
|
||||||
|
for ((i=0; i<delete_count; i++)); do
|
||||||
|
if [[ "$DRY_RUN" == "1" ]]; then
|
||||||
|
echo "DRY-RUN rm -rf ${runs[$i]}"
|
||||||
|
else
|
||||||
|
rm -rf "${runs[$i]}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
if [[ "$DRY_RUN" == "1" ]]; then
|
||||||
|
echo "DRY-RUN rm -rf $HOST_DATA_DIR/tmp/*"
|
||||||
|
else
|
||||||
|
find "$HOST_DATA_DIR/tmp" -mindepth 1 -maxdepth 1 -exec rm -rf {} +
|
||||||
|
fi
|
||||||
|
df -h "$HOST_DATA_DIR" 2>/dev/null || true
|
||||||
93
deploy/docker-installer/compose/docker-compose.yml
Normal file
93
deploy/docker-installer/compose/docker-compose.yml
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
services:
|
||||||
|
ours-rp-soak:
|
||||||
|
image: ${RPKI_IMAGE:?RPKI_IMAGE is required}
|
||||||
|
platform: ${RPKI_PLATFORM:?RPKI_PLATFORM is required}
|
||||||
|
container_name: ${COMPOSE_PROJECT_NAME:-ours-rp-package-installer}-soak
|
||||||
|
env_file:
|
||||||
|
- ../.env
|
||||||
|
environment:
|
||||||
|
PACKAGE_ROOT: /opt/ours-rp
|
||||||
|
ENV_FILE: /opt/ours-rp/.env
|
||||||
|
RUN_ROOT: /var/lib/ours-rp
|
||||||
|
BIN_DIR: /opt/ours-rp/bin
|
||||||
|
FIXTURE_DIR: /opt/ours-rp/fixtures
|
||||||
|
volumes:
|
||||||
|
- ../.env:/opt/ours-rp/.env:ro
|
||||||
|
- ${HOST_DATA_DIR:-/var/lib/ours-rp-package-installer}/state:/var/lib/ours-rp/state
|
||||||
|
- ${HOST_DATA_DIR:-/var/lib/ours-rp-package-installer}/runs:/var/lib/ours-rp/runs
|
||||||
|
- ${HOST_DATA_DIR:-/var/lib/ours-rp-package-installer}/logs:/var/lib/ours-rp/logs
|
||||||
|
- ${HOST_DATA_DIR:-/var/lib/ours-rp-package-installer}/tmp:/var/lib/ours-rp/tmp
|
||||||
|
restart: ${SOAK_RESTART_POLICY:-unless-stopped}
|
||||||
|
profiles:
|
||||||
|
- core
|
||||||
|
|
||||||
|
artifact-metrics:
|
||||||
|
image: ${METRICS_IMAGE:?METRICS_IMAGE is required}
|
||||||
|
platform: ${METRICS_PLATFORM:?METRICS_PLATFORM is required}
|
||||||
|
container_name: ${COMPOSE_PROJECT_NAME:-ours-rp-package-installer}-artifact-metrics
|
||||||
|
env_file:
|
||||||
|
- ../.env
|
||||||
|
environment:
|
||||||
|
RPKI_METRICS_RTR_REPORT_DIR: ${RTR_REPORT_CONTAINER_DIR:-/var/lib/ours-rp/rtr-report}
|
||||||
|
command:
|
||||||
|
- /opt/ours-rp/bin/rpki_artifact_metrics
|
||||||
|
- --run-root
|
||||||
|
- /var/lib/ours-rp
|
||||||
|
- --listen
|
||||||
|
- 0.0.0.0:9556
|
||||||
|
- --poll-secs
|
||||||
|
- ${METRICS_POLL_SECS:-10}
|
||||||
|
- --instance
|
||||||
|
- ${METRICS_INSTANCE:-package-installer}
|
||||||
|
ports:
|
||||||
|
- "${METRICS_PORT:-9556}:9556"
|
||||||
|
volumes:
|
||||||
|
- ${HOST_DATA_DIR:-/var/lib/ours-rp-package-installer}/state:/var/lib/ours-rp/state:ro
|
||||||
|
- ${HOST_DATA_DIR:-/var/lib/ours-rp-package-installer}/runs:/var/lib/ours-rp/runs:ro
|
||||||
|
- ${HOST_DATA_DIR:-/var/lib/ours-rp-package-installer}/logs:/var/lib/ours-rp/logs:ro
|
||||||
|
- ${RTR_REPORT_DIR:-/var/lib/ours-rp-package-installer/empty-rtr-report}:${RTR_REPORT_CONTAINER_DIR:-/var/lib/ours-rp/rtr-report}:ro
|
||||||
|
restart: unless-stopped
|
||||||
|
profiles:
|
||||||
|
- sidecar
|
||||||
|
|
||||||
|
prometheus:
|
||||||
|
image: ${PROMETHEUS_IMAGE:-prom/prometheus:v2.55.1}
|
||||||
|
platform: ${MONITOR_PLATFORM:?MONITOR_PLATFORM is required}
|
||||||
|
container_name: ${COMPOSE_PROJECT_NAME:-ours-rp-package-installer}-prometheus
|
||||||
|
command:
|
||||||
|
- --config.file=/etc/prometheus/prometheus.yml
|
||||||
|
- --storage.tsdb.path=/prometheus
|
||||||
|
- --storage.tsdb.retention.time=${PROMETHEUS_RETENTION:-7d}
|
||||||
|
- --web.enable-lifecycle
|
||||||
|
depends_on:
|
||||||
|
- artifact-metrics
|
||||||
|
user: "0:0"
|
||||||
|
ports:
|
||||||
|
- "${PROMETHEUS_PORT:-9090}:9090"
|
||||||
|
volumes:
|
||||||
|
- ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
|
||||||
|
- ${HOST_DATA_DIR:-/var/lib/ours-rp-package-installer}/prometheus:/prometheus
|
||||||
|
restart: unless-stopped
|
||||||
|
profiles:
|
||||||
|
- monitor
|
||||||
|
|
||||||
|
grafana:
|
||||||
|
image: ${GRAFANA_IMAGE:-grafana/grafana:11.3.1}
|
||||||
|
platform: ${MONITOR_PLATFORM:?MONITOR_PLATFORM is required}
|
||||||
|
container_name: ${COMPOSE_PROJECT_NAME:-ours-rp-package-installer}-grafana
|
||||||
|
depends_on:
|
||||||
|
- prometheus
|
||||||
|
user: "0:0"
|
||||||
|
ports:
|
||||||
|
- "${GRAFANA_PORT:-3000}:3000"
|
||||||
|
environment:
|
||||||
|
GF_SECURITY_ADMIN_USER: ${GRAFANA_ADMIN_USER:-admin}
|
||||||
|
GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_ADMIN_PASSWORD:-admin}
|
||||||
|
GF_USERS_ALLOW_SIGN_UP: "false"
|
||||||
|
volumes:
|
||||||
|
- ${HOST_DATA_DIR:-/var/lib/ours-rp-package-installer}/grafana:/var/lib/grafana
|
||||||
|
- ./grafana/provisioning:/etc/grafana/provisioning:ro
|
||||||
|
- ./grafana/dashboards:/var/lib/grafana/dashboards:ro
|
||||||
|
restart: unless-stopped
|
||||||
|
profiles:
|
||||||
|
- monitor
|
||||||
@@ -0,0 +1,761 @@
|
|||||||
|
{
|
||||||
|
"annotations": {
|
||||||
|
"list": []
|
||||||
|
},
|
||||||
|
"editable": true,
|
||||||
|
"fiscalYearStartMonth": 0,
|
||||||
|
"graphTooltip": 0,
|
||||||
|
"id": null,
|
||||||
|
"links": [],
|
||||||
|
"panels": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "short"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 4,
|
||||||
|
"w": 6,
|
||||||
|
"x": 0,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"id": 1,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"pluginVersion": "11.3.1",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_publication_points",
|
||||||
|
"legendFormat": "publication points",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Publication Points",
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "short"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 4,
|
||||||
|
"w": 6,
|
||||||
|
"x": 6,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"id": 2,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"pluginVersion": "11.3.1",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_repo_sync_phase_count{phase=\"rrdp_ok\"}",
|
||||||
|
"legendFormat": "rrdp ok",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "RRDP OK Points",
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "short"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 4,
|
||||||
|
"w": 6,
|
||||||
|
"x": 12,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"id": 3,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"pluginVersion": "11.3.1",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "sum(ours_rp_repo_sync_phase_count{phase=\"rrdp_failed_rsync_ok\"}) or vector(0)",
|
||||||
|
"legendFormat": "fallback",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Rsync Fallback Points",
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "short"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 4,
|
||||||
|
"w": 6,
|
||||||
|
"x": 18,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"id": 4,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"pluginVersion": "11.3.1",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_repo_terminal_state_count{terminal_state=\"failed_no_cache\"}",
|
||||||
|
"legendFormat": "failed no cache",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Failed No Cache Points",
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "s"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 24,
|
||||||
|
"x": 0,
|
||||||
|
"y": 4
|
||||||
|
},
|
||||||
|
"id": 5,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "multi",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_run_stage_duration_seconds{stage=\"repo_sync_total\"}",
|
||||||
|
"legendFormat": "repo sync total",
|
||||||
|
"refId": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_run_stage_duration_seconds{stage=\"rrdp_download_total\"}",
|
||||||
|
"legendFormat": "rrdp download",
|
||||||
|
"refId": "B"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_run_stage_duration_seconds{stage=\"rsync_download_total\"}",
|
||||||
|
"legendFormat": "rsync download",
|
||||||
|
"refId": "C"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Repo Sync Download Durations",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "short"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 12,
|
||||||
|
"w": 12,
|
||||||
|
"h": 8
|
||||||
|
},
|
||||||
|
"id": 6,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "multi",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_repo_sync_phase_count",
|
||||||
|
"legendFormat": "{{phase}}",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Repo Sync Phase Counts",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "short"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"x": 12,
|
||||||
|
"y": 12,
|
||||||
|
"w": 12,
|
||||||
|
"h": 8
|
||||||
|
},
|
||||||
|
"id": 7,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "multi",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_repo_sync_phase_count{phase=\"rrdp_failed_rsync_ok\"}",
|
||||||
|
"legendFormat": "rrdp failed, rsync ok",
|
||||||
|
"refId": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_repo_sync_phase_count{phase=\"rrdp_failed_rsync_failed\"}",
|
||||||
|
"legendFormat": "rrdp failed, rsync failed",
|
||||||
|
"refId": "B"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_repo_terminal_state_count{terminal_state=\"failed_no_cache\"}",
|
||||||
|
"legendFormat": "failed no cache",
|
||||||
|
"refId": "C"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_tree_instances{state=\"failed\"}",
|
||||||
|
"legendFormat": "tree failed",
|
||||||
|
"refId": "D"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Repo Failure / Fallback Counts",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "s"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 20,
|
||||||
|
"w": 12,
|
||||||
|
"h": 8
|
||||||
|
},
|
||||||
|
"id": 8,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "multi",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_repo_sync_phase_duration_seconds_total{phase=\"rrdp_failed_rsync_ok\"}",
|
||||||
|
"legendFormat": "rsync fallback duration",
|
||||||
|
"refId": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_repo_sync_phase_duration_seconds_total{phase=\"rrdp_failed_rsync_failed\"}",
|
||||||
|
"legendFormat": "failed duration",
|
||||||
|
"refId": "B"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_repo_terminal_state_duration_seconds_total{terminal_state=\"failed_no_cache\"}",
|
||||||
|
"legendFormat": "failed no cache duration",
|
||||||
|
"refId": "C"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Repo Failure / Fallback Durations",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "none"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 29,
|
||||||
|
"w": 12,
|
||||||
|
"h": 9
|
||||||
|
},
|
||||||
|
"id": 9,
|
||||||
|
"options": {
|
||||||
|
"showHeader": true,
|
||||||
|
"cellHeight": "sm",
|
||||||
|
"footer": {
|
||||||
|
"show": false,
|
||||||
|
"reducer": [
|
||||||
|
"sum"
|
||||||
|
],
|
||||||
|
"countRows": false,
|
||||||
|
"fields": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pluginVersion": "11.3.1",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_rrdp_rsync_failed_repository_duration_seconds",
|
||||||
|
"format": "table",
|
||||||
|
"instant": true,
|
||||||
|
"legendFormat": "",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "RRDP + Rsync Failed Repositories",
|
||||||
|
"type": "table",
|
||||||
|
"transformations": [
|
||||||
|
{
|
||||||
|
"id": "organize",
|
||||||
|
"options": {
|
||||||
|
"excludeByName": {
|
||||||
|
"job": true,
|
||||||
|
"terminal_state": true,
|
||||||
|
"rank": true,
|
||||||
|
"transport": true,
|
||||||
|
"__name__": true,
|
||||||
|
"publication_points": true,
|
||||||
|
"instance": true,
|
||||||
|
"repo_id": true,
|
||||||
|
"pp_id": true,
|
||||||
|
"exported_instance": true,
|
||||||
|
"rp": true,
|
||||||
|
"source": true
|
||||||
|
},
|
||||||
|
"indexByName": {
|
||||||
|
"Time": 0,
|
||||||
|
"host": 1,
|
||||||
|
"phase": 2,
|
||||||
|
"uri": 3,
|
||||||
|
"Value": 4
|
||||||
|
},
|
||||||
|
"renameByName": {
|
||||||
|
"Value": "duration"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "none"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"x": 12,
|
||||||
|
"y": 29,
|
||||||
|
"w": 12,
|
||||||
|
"h": 9
|
||||||
|
},
|
||||||
|
"id": 11,
|
||||||
|
"options": {
|
||||||
|
"showHeader": true,
|
||||||
|
"cellHeight": "sm",
|
||||||
|
"footer": {
|
||||||
|
"show": false,
|
||||||
|
"reducer": [
|
||||||
|
"sum"
|
||||||
|
],
|
||||||
|
"countRows": false,
|
||||||
|
"fields": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pluginVersion": "11.3.1",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "topk(20, ours_rp_top_repository_sync_duration_seconds)",
|
||||||
|
"format": "table",
|
||||||
|
"instant": true,
|
||||||
|
"legendFormat": "",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Top 20 Repositories by Sync Duration",
|
||||||
|
"type": "table",
|
||||||
|
"transformations": [
|
||||||
|
{
|
||||||
|
"id": "organize",
|
||||||
|
"options": {
|
||||||
|
"excludeByName": {
|
||||||
|
"job": true,
|
||||||
|
"terminal_state": true,
|
||||||
|
"__name__": true,
|
||||||
|
"publication_points": true,
|
||||||
|
"instance": true,
|
||||||
|
"repo_id": true,
|
||||||
|
"phase": true,
|
||||||
|
"pp_id": true,
|
||||||
|
"exported_instance": true,
|
||||||
|
"rp": true,
|
||||||
|
"source": true
|
||||||
|
},
|
||||||
|
"indexByName": {
|
||||||
|
"Time": 0,
|
||||||
|
"host": 1,
|
||||||
|
"rank": 2,
|
||||||
|
"transport": 3,
|
||||||
|
"uri": 4,
|
||||||
|
"Value": 5
|
||||||
|
},
|
||||||
|
"renameByName": {
|
||||||
|
"Value": "value"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "none"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 38,
|
||||||
|
"w": 24,
|
||||||
|
"h": 9
|
||||||
|
},
|
||||||
|
"id": 10,
|
||||||
|
"options": {
|
||||||
|
"showHeader": true,
|
||||||
|
"cellHeight": "sm",
|
||||||
|
"footer": {
|
||||||
|
"show": false,
|
||||||
|
"reducer": [
|
||||||
|
"sum"
|
||||||
|
],
|
||||||
|
"countRows": false,
|
||||||
|
"fields": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pluginVersion": "11.3.1",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "topk(20, ours_rp_top_publication_point_object_count)",
|
||||||
|
"format": "table",
|
||||||
|
"instant": true,
|
||||||
|
"legendFormat": "",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Top Publication Points by Objects",
|
||||||
|
"type": "table",
|
||||||
|
"transformations": [
|
||||||
|
{
|
||||||
|
"id": "organize",
|
||||||
|
"options": {
|
||||||
|
"excludeByName": {
|
||||||
|
"job": true,
|
||||||
|
"__name__": true,
|
||||||
|
"publication_points": true,
|
||||||
|
"instance": true,
|
||||||
|
"repo_id": true,
|
||||||
|
"phase": true,
|
||||||
|
"pp_id": true,
|
||||||
|
"exported_instance": true,
|
||||||
|
"rp": true,
|
||||||
|
"source": true
|
||||||
|
},
|
||||||
|
"indexByName": {
|
||||||
|
"Time": 0,
|
||||||
|
"host": 1,
|
||||||
|
"rank": 2,
|
||||||
|
"terminal_state": 3,
|
||||||
|
"transport": 4,
|
||||||
|
"uri": 5,
|
||||||
|
"Value": 6
|
||||||
|
},
|
||||||
|
"renameByName": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"description": "Per-repository sync success in the latest successful run; 1 means successful, 0 means failed or failed_no_cache.",
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "bool"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 24,
|
||||||
|
"x": 0,
|
||||||
|
"y": 47
|
||||||
|
},
|
||||||
|
"id": 12,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "multi",
|
||||||
|
"sort": "desc"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_repository_sync_success",
|
||||||
|
"legendFormat": "{{host}} {{repo_id}}",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Repository Sync Success by Repo",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"description": "Per-repository total sync duration aggregated from publication point repo_sync_duration_ms.",
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "s"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 24,
|
||||||
|
"x": 0,
|
||||||
|
"y": 55
|
||||||
|
},
|
||||||
|
"id": 13,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "multi",
|
||||||
|
"sort": "desc"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_repository_sync_duration_seconds{stat=\"sum\"}",
|
||||||
|
"legendFormat": "{{host}} {{repo_id}}",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Repository Sync Duration by Repo",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"description": "Per-repository downloaded bytes attributed from report.json downloads events.",
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "bytes"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 24,
|
||||||
|
"x": 0,
|
||||||
|
"y": 63
|
||||||
|
},
|
||||||
|
"id": 14,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "multi",
|
||||||
|
"sort": "desc"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_repository_download_bytes",
|
||||||
|
"legendFormat": "{{host}} {{repo_id}}",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Repository Download Bytes by Repo",
|
||||||
|
"type": "timeseries"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"refresh": "5s",
|
||||||
|
"schemaVersion": 40,
|
||||||
|
"tags": [
|
||||||
|
"ours-rp",
|
||||||
|
"rpki",
|
||||||
|
"soak",
|
||||||
|
"repo-sync"
|
||||||
|
],
|
||||||
|
"templating": {
|
||||||
|
"list": []
|
||||||
|
},
|
||||||
|
"time": {
|
||||||
|
"from": "now-30m",
|
||||||
|
"to": "now"
|
||||||
|
},
|
||||||
|
"timepicker": {},
|
||||||
|
"timezone": "browser",
|
||||||
|
"title": "Ours RP Repo Sync",
|
||||||
|
"uid": "ours-rp-repo-sync",
|
||||||
|
"version": 3,
|
||||||
|
"weekStart": ""
|
||||||
|
}
|
||||||
@@ -0,0 +1,666 @@
|
|||||||
|
{
|
||||||
|
"annotations": {
|
||||||
|
"list": []
|
||||||
|
},
|
||||||
|
"editable": true,
|
||||||
|
"fiscalYearStartMonth": 0,
|
||||||
|
"graphTooltip": 0,
|
||||||
|
"id": null,
|
||||||
|
"links": [],
|
||||||
|
"liveNow": false,
|
||||||
|
"panels": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"title": "RTR Metrics Enabled",
|
||||||
|
"type": "stat",
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 4,
|
||||||
|
"w": 4,
|
||||||
|
"x": 0,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "short",
|
||||||
|
"decimals": 0
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "max(ours_rp_rtr_metrics_enabled)",
|
||||||
|
"legendFormat": "enabled",
|
||||||
|
"refId": "A",
|
||||||
|
"instant": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"title": "Refresh Success",
|
||||||
|
"type": "stat",
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 4,
|
||||||
|
"w": 5,
|
||||||
|
"x": 4,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "short",
|
||||||
|
"decimals": 0
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "max(ours_rp_rtr_source_refresh_status{status=\"success\"})",
|
||||||
|
"legendFormat": "success",
|
||||||
|
"refId": "A",
|
||||||
|
"instant": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"title": "Consecutive Failures",
|
||||||
|
"type": "stat",
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 4,
|
||||||
|
"w": 5,
|
||||||
|
"x": 9,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "short",
|
||||||
|
"decimals": 0
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "max(ours_rp_rtr_source_refresh_consecutive_failures)",
|
||||||
|
"legendFormat": "failures",
|
||||||
|
"refId": "A",
|
||||||
|
"instant": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"title": "Last Success Age",
|
||||||
|
"type": "stat",
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 4,
|
||||||
|
"w": 5,
|
||||||
|
"x": 14,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "s",
|
||||||
|
"decimals": 0,
|
||||||
|
"min": 0
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "max(ours_rp_rtr_source_last_success_age_seconds)",
|
||||||
|
"legendFormat": "age",
|
||||||
|
"refId": "A",
|
||||||
|
"instant": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 5,
|
||||||
|
"title": "RTR RSS",
|
||||||
|
"type": "stat",
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 4,
|
||||||
|
"w": 5,
|
||||||
|
"x": 19,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "bytes",
|
||||||
|
"decimals": 0,
|
||||||
|
"min": 0
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "max(ours_rp_rtr_process_rss_bytes)",
|
||||||
|
"legendFormat": "rss",
|
||||||
|
"refId": "A",
|
||||||
|
"instant": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 6,
|
||||||
|
"title": "Data Quality Totals",
|
||||||
|
"type": "timeseries",
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 0,
|
||||||
|
"y": 4
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "short",
|
||||||
|
"min": 0
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_rtr_data_quality_items{stage=~\"ccr_input|before_slurm|after_slurm\",type=\"total\"}",
|
||||||
|
"legendFormat": "{{stage}} total",
|
||||||
|
"refId": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_rtr_data_quality_items{stage=\"after_slurm\",type=\"vrp\"}",
|
||||||
|
"legendFormat": "after_slurm vrp",
|
||||||
|
"refId": "B"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_rtr_data_quality_items{stage=\"after_slurm\",type=\"aspa\"}",
|
||||||
|
"legendFormat": "after_slurm aspa",
|
||||||
|
"refId": "C"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 7,
|
||||||
|
"title": "SLURM Filters / Assertions",
|
||||||
|
"type": "timeseries",
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 12,
|
||||||
|
"y": 4
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "short",
|
||||||
|
"min": 0
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_rtr_slurm_filters",
|
||||||
|
"legendFormat": "filter {{type}}",
|
||||||
|
"refId": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_rtr_slurm_assertions",
|
||||||
|
"legendFormat": "assert {{type}}",
|
||||||
|
"refId": "B"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 8,
|
||||||
|
"title": "Cache Ready / Delta Window",
|
||||||
|
"type": "timeseries",
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 8,
|
||||||
|
"x": 0,
|
||||||
|
"y": 12
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "short",
|
||||||
|
"min": 0
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_rtr_cache_ready",
|
||||||
|
"legendFormat": "ready",
|
||||||
|
"refId": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_rtr_cache_delta_window_length",
|
||||||
|
"legendFormat": "v{{version}} length",
|
||||||
|
"refId": "B"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 9,
|
||||||
|
"title": "Cache Snapshot Items",
|
||||||
|
"type": "timeseries",
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 8,
|
||||||
|
"x": 8,
|
||||||
|
"y": 12
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "short",
|
||||||
|
"min": 0
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_rtr_cache_snapshot_items",
|
||||||
|
"legendFormat": "v{{version}} {{type}}",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 10,
|
||||||
|
"title": "Latest Delta Items",
|
||||||
|
"type": "timeseries",
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 8,
|
||||||
|
"x": 16,
|
||||||
|
"y": 12
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "short",
|
||||||
|
"min": 0
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_rtr_cache_delta_items",
|
||||||
|
"legendFormat": "v{{version}} {{direction}} {{type}}",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 11,
|
||||||
|
"title": "Connections",
|
||||||
|
"type": "timeseries",
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 8,
|
||||||
|
"x": 0,
|
||||||
|
"y": 20
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "short",
|
||||||
|
"min": 0
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_rtr_active_connections",
|
||||||
|
"legendFormat": "active",
|
||||||
|
"refId": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_rtr_connections",
|
||||||
|
"legendFormat": "{{transport}}",
|
||||||
|
"refId": "B"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 12,
|
||||||
|
"title": "Connection Utilization / Max",
|
||||||
|
"type": "timeseries",
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 8,
|
||||||
|
"x": 8,
|
||||||
|
"y": 20
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"min": 0
|
||||||
|
},
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"matcher": {
|
||||||
|
"id": "byRegexp",
|
||||||
|
"options": ".*utilization.*"
|
||||||
|
},
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": "unit",
|
||||||
|
"value": "percentunit"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matcher": {
|
||||||
|
"id": "byRegexp",
|
||||||
|
"options": ".*max.*"
|
||||||
|
},
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": "unit",
|
||||||
|
"value": "short"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_rtr_connection_utilization",
|
||||||
|
"legendFormat": "utilization",
|
||||||
|
"refId": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_rtr_max_connections",
|
||||||
|
"legendFormat": "max",
|
||||||
|
"refId": "B"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 13,
|
||||||
|
"title": "Report Age",
|
||||||
|
"type": "timeseries",
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 8,
|
||||||
|
"x": 16,
|
||||||
|
"y": 20
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "s",
|
||||||
|
"min": 0
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_rtr_source_report_age_seconds",
|
||||||
|
"legendFormat": "source",
|
||||||
|
"refId": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_rtr_runtime_report_age_seconds",
|
||||||
|
"legendFormat": "runtime",
|
||||||
|
"refId": "B"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_rtr_clients_report_age_seconds",
|
||||||
|
"legendFormat": "clients",
|
||||||
|
"refId": "C"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"refresh": "10s",
|
||||||
|
"schemaVersion": 40,
|
||||||
|
"tags": [
|
||||||
|
"rpki",
|
||||||
|
"inter-rp",
|
||||||
|
"routinator"
|
||||||
|
],
|
||||||
|
"templating": {
|
||||||
|
"list": []
|
||||||
|
},
|
||||||
|
"time": {
|
||||||
|
"from": "now-6h",
|
||||||
|
"to": "now"
|
||||||
|
},
|
||||||
|
"timezone": "browser",
|
||||||
|
"title": "RTR Service Overview",
|
||||||
|
"uid": "ours-rp-rtr-overview",
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
@@ -0,0 +1,875 @@
|
|||||||
|
{
|
||||||
|
"annotations": {
|
||||||
|
"list": []
|
||||||
|
},
|
||||||
|
"editable": true,
|
||||||
|
"fiscalYearStartMonth": 0,
|
||||||
|
"graphTooltip": 0,
|
||||||
|
"id": null,
|
||||||
|
"links": [],
|
||||||
|
"panels": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"decimals": 0,
|
||||||
|
"unit": "none"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"w": 6,
|
||||||
|
"h": 4
|
||||||
|
},
|
||||||
|
"id": 1,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"pluginVersion": "11.3.1",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_cir_trust_anchors",
|
||||||
|
"legendFormat": "RIRs",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Current Run RIRs",
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "s"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"x": 6,
|
||||||
|
"y": 0,
|
||||||
|
"w": 6,
|
||||||
|
"h": 4
|
||||||
|
},
|
||||||
|
"id": 2,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"pluginVersion": "11.3.1",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_run_duration_seconds",
|
||||||
|
"legendFormat": "wall",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Latest Wall Time",
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "bytes"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"x": 12,
|
||||||
|
"y": 0,
|
||||||
|
"w": 6,
|
||||||
|
"h": 4
|
||||||
|
},
|
||||||
|
"id": 3,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"pluginVersion": "11.3.1",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_run_max_rss_bytes",
|
||||||
|
"legendFormat": "rss",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Latest Max RSS",
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"decimals": 0,
|
||||||
|
"unit": "none"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"x": 18,
|
||||||
|
"y": 0,
|
||||||
|
"w": 6,
|
||||||
|
"h": 4
|
||||||
|
},
|
||||||
|
"id": 4,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"pluginVersion": "11.3.1",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_publication_points",
|
||||||
|
"legendFormat": "publication points",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Publication Points",
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"decimals": 0,
|
||||||
|
"unit": "none"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 4,
|
||||||
|
"w": 6,
|
||||||
|
"h": 4
|
||||||
|
},
|
||||||
|
"id": 9,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"pluginVersion": "11.3.1",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_run_sequence",
|
||||||
|
"legendFormat": "seq",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Latest Run Sequence",
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"decimals": 2,
|
||||||
|
"unit": "percent",
|
||||||
|
"min": 0,
|
||||||
|
"max": 100,
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "orange",
|
||||||
|
"value": 90
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": 98
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"x": 6,
|
||||||
|
"y": 4,
|
||||||
|
"w": 6,
|
||||||
|
"h": 4
|
||||||
|
},
|
||||||
|
"id": 10,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"pluginVersion": "11.3.1",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "100 * sum by (job, instance, exported_instance) (ours_rp_repo_terminal_state_count{terminal_state=\"publication_point_cache\"}) / sum by (job, instance, exported_instance) (ours_rp_publication_points)",
|
||||||
|
"legendFormat": "PP cache hit ratio",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Latest PP Cache Hit Ratio",
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "none",
|
||||||
|
"decimals": 0
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"x": 12,
|
||||||
|
"y": 4,
|
||||||
|
"w": 6,
|
||||||
|
"h": 4
|
||||||
|
},
|
||||||
|
"id": 11,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"pluginVersion": "11.3.1",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_vrps{kind=\"total\"}",
|
||||||
|
"legendFormat": "VRPs raw",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "VRPs",
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "none",
|
||||||
|
"decimals": 0
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"x": 18,
|
||||||
|
"y": 4,
|
||||||
|
"w": 6,
|
||||||
|
"h": 4
|
||||||
|
},
|
||||||
|
"id": 12,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"pluginVersion": "11.3.1",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_vaps",
|
||||||
|
"legendFormat": "VAPs",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "VAPs",
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "s"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 8,
|
||||||
|
"w": 12,
|
||||||
|
"h": 8
|
||||||
|
},
|
||||||
|
"id": 5,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_run_duration_seconds",
|
||||||
|
"legendFormat": "wall",
|
||||||
|
"refId": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_stage_duration_seconds{stage=\"validation\"}",
|
||||||
|
"legendFormat": "validation",
|
||||||
|
"refId": "B"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Run / Validation Duration",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"decimals": 0,
|
||||||
|
"unit": "none"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"x": 12,
|
||||||
|
"y": 8,
|
||||||
|
"w": 12,
|
||||||
|
"h": 8
|
||||||
|
},
|
||||||
|
"id": 6,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "multi",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_vrps{kind=\"total\"}",
|
||||||
|
"legendFormat": "VRPs raw",
|
||||||
|
"refId": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_vrps{kind=\"unique\"}",
|
||||||
|
"legendFormat": "VRPs unique",
|
||||||
|
"refId": "D"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_vaps",
|
||||||
|
"legendFormat": "VAPs",
|
||||||
|
"refId": "B"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_cir_objects",
|
||||||
|
"legendFormat": "CIR objects",
|
||||||
|
"refId": "C"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Output and Input Sizes",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"decimals": 0,
|
||||||
|
"unit": "none"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 16,
|
||||||
|
"w": 12,
|
||||||
|
"h": 8
|
||||||
|
},
|
||||||
|
"id": 8,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "multi",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_large_publication_points",
|
||||||
|
"legendFormat": "> {{object_count_gt}} objects",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Large Publication Points by Object Count",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "s"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"x": 12,
|
||||||
|
"y": 16,
|
||||||
|
"w": 12,
|
||||||
|
"h": 8
|
||||||
|
},
|
||||||
|
"id": 13,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "multi",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_run_stage_duration_seconds{stage=\"validation\"}",
|
||||||
|
"legendFormat": "validation",
|
||||||
|
"refId": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_run_stage_duration_seconds{stage=\"report_write\"}",
|
||||||
|
"legendFormat": "report write",
|
||||||
|
"refId": "E"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_run_stage_duration_seconds{stage=\"ccr_write\"}",
|
||||||
|
"legendFormat": "ccr write",
|
||||||
|
"refId": "F"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_run_stage_duration_seconds{stage=\"cir_write\"}",
|
||||||
|
"legendFormat": "cir write",
|
||||||
|
"refId": "G"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Output Stage Durations",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "bytes",
|
||||||
|
"decimals": 2
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 24,
|
||||||
|
"w": 12,
|
||||||
|
"h": 8
|
||||||
|
},
|
||||||
|
"id": 14,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull",
|
||||||
|
"max"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "multi",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_run_max_rss_bytes",
|
||||||
|
"legendFormat": "Max RSS",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Max RSS Over Time",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "percent",
|
||||||
|
"decimals": 2,
|
||||||
|
"min": 0,
|
||||||
|
"max": 100,
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "orange",
|
||||||
|
"value": 90
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": 98
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"x": 12,
|
||||||
|
"y": 24,
|
||||||
|
"w": 12,
|
||||||
|
"h": 8
|
||||||
|
},
|
||||||
|
"id": 17,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull",
|
||||||
|
"min",
|
||||||
|
"max"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "100 * sum by (job, instance, exported_instance) (ours_rp_repo_terminal_state_count{terminal_state=\"publication_point_cache\"}) / sum by (job, instance, exported_instance) (ours_rp_publication_points)",
|
||||||
|
"legendFormat": "PP cache hit ratio",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "PP Cache Hit Ratio",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "bytes",
|
||||||
|
"decimals": 2
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 32,
|
||||||
|
"w": 24,
|
||||||
|
"h": 8
|
||||||
|
},
|
||||||
|
"id": 15,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull",
|
||||||
|
"max"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "multi",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_state_db_size_bytes",
|
||||||
|
"legendFormat": "{{db}}",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "State DB Size Over Time",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "none",
|
||||||
|
"decimals": 0
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 40,
|
||||||
|
"w": 24,
|
||||||
|
"h": 8
|
||||||
|
},
|
||||||
|
"id": 16,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull",
|
||||||
|
"max"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "multi",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_state_db_files",
|
||||||
|
"legendFormat": "{{db}}",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "State DB File Count Over Time",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "none",
|
||||||
|
"decimals": 0,
|
||||||
|
"min": 0
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 48,
|
||||||
|
"w": 24,
|
||||||
|
"h": 8
|
||||||
|
},
|
||||||
|
"id": 18,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull",
|
||||||
|
"max"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "multi",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "sum by (job, instance, exported_instance) (ours_rp_repo_terminal_state_count{terminal_state=\"fresh\"})",
|
||||||
|
"legendFormat": "fresh pp",
|
||||||
|
"refId": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "sum by (job, instance, exported_instance) (ours_rp_cir_objects_by_source_type{exported_source=\"fresh\",object_type=\"roa\"})",
|
||||||
|
"legendFormat": "fresh roa",
|
||||||
|
"refId": "B"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "sum by (job, instance, exported_instance) (ours_rp_cir_objects_by_source_type{exported_source=\"fresh\",object_type=\"manifest\"})",
|
||||||
|
"legendFormat": "fresh mft",
|
||||||
|
"refId": "C"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "sum by (job, instance, exported_instance) (ours_rp_cir_objects_by_source_type{exported_source=\"fresh\",object_type=\"certificate\"})",
|
||||||
|
"legendFormat": "fresh crt",
|
||||||
|
"refId": "D"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "sum by (job, instance, exported_instance) (ours_rp_cir_objects_by_source_type{exported_source=\"fresh\",object_type=\"crl\"})",
|
||||||
|
"legendFormat": "fresh crl",
|
||||||
|
"refId": "E"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Fresh PP / Object Counts by Run",
|
||||||
|
"type": "timeseries"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"refresh": "5s",
|
||||||
|
"schemaVersion": 40,
|
||||||
|
"tags": [
|
||||||
|
"ours-rp",
|
||||||
|
"rpki",
|
||||||
|
"soak"
|
||||||
|
],
|
||||||
|
"templating": {
|
||||||
|
"list": []
|
||||||
|
},
|
||||||
|
"time": {
|
||||||
|
"from": "now-30m",
|
||||||
|
"to": "now"
|
||||||
|
},
|
||||||
|
"timepicker": {},
|
||||||
|
"timezone": "browser",
|
||||||
|
"title": "Ours RP Soak Overview",
|
||||||
|
"uid": "ours-rp-soak-overview",
|
||||||
|
"version": 4,
|
||||||
|
"weekStart": ""
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
apiVersion: 1
|
||||||
|
|
||||||
|
providers:
|
||||||
|
- name: ours-rp-docker-installer
|
||||||
|
orgId: 1
|
||||||
|
folder: Ours RP Docker Installer
|
||||||
|
type: file
|
||||||
|
disableDeletion: false
|
||||||
|
updateIntervalSeconds: 10
|
||||||
|
allowUiUpdates: true
|
||||||
|
options:
|
||||||
|
path: /var/lib/grafana/dashboards
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
apiVersion: 1
|
||||||
|
|
||||||
|
datasources:
|
||||||
|
- name: Prometheus
|
||||||
|
uid: Prometheus
|
||||||
|
type: prometheus
|
||||||
|
access: proxy
|
||||||
|
url: http://prometheus:9090
|
||||||
|
isDefault: true
|
||||||
|
editable: true
|
||||||
13
deploy/docker-installer/compose/prometheus.yml
Normal file
13
deploy/docker-installer/compose/prometheus.yml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
global:
|
||||||
|
scrape_interval: 5s
|
||||||
|
evaluation_interval: 5s
|
||||||
|
|
||||||
|
scrape_configs:
|
||||||
|
- job_name: ours-rp-artifact-metrics
|
||||||
|
metrics_path: /metrics
|
||||||
|
static_configs:
|
||||||
|
- targets:
|
||||||
|
- artifact-metrics:9556
|
||||||
|
labels:
|
||||||
|
rp: ours-rp
|
||||||
|
source: docker-installer-artifact-sidecar
|
||||||
193
deploy/docker-installer/docs/README.en.md
Normal file
193
deploy/docker-installer/docs/README.en.md
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
# ours RP Docker Installer Guide (`__PACKAGE_ARCH__` / `__PACKAGE_PLATFORM__`)
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
This package deploys ours RP on Linux servers whose host architecture matches the packaged installer metadata, using Docker Compose and continuous all-five RIR validation.
|
||||||
|
|
||||||
|
The package includes four architecture-matched images: ours RP runtime, artifact metrics, Prometheus, and Grafana. Deployment does not need to pull application images on the target host. Runtime state, run artifacts, logs, Prometheus data and Grafana data are persisted through host bind mounts.
|
||||||
|
|
||||||
|
By default the host architecture must match `PACKAGE_ARCH` from `PACKAGE-MANIFEST.env`. If you intentionally want to run this package on a different host architecture through QEMU/binfmt, set:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ALLOW_CROSS_ARCH=1
|
||||||
|
```
|
||||||
|
|
||||||
|
## Compose Template Source
|
||||||
|
|
||||||
|
`compose/` is the single production Docker Compose template source. The Docker installer build script copies this directory directly. Do not maintain a second copy of the Compose, Prometheus, or Grafana dashboard files elsewhere.
|
||||||
|
|
||||||
|
The legacy direct Compose deployment entry point has been removed. For either amd64 or arm64, build the matching installer package with `scripts/docker/build_docker_installer_package.sh`, then use `install.sh`, `start.sh`, and `status.sh` as described here.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
tar -xzf ours-rp-docker-installer-__PACKAGE_ARCH__-*.tar.gz
|
||||||
|
cd ours-rp-docker-installer-__PACKAGE_ARCH__-*
|
||||||
|
|
||||||
|
./install.sh
|
||||||
|
cp .env.example .env # install.sh creates .env automatically if missing
|
||||||
|
vim .env
|
||||||
|
./start.sh
|
||||||
|
./status.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Defaults:
|
||||||
|
|
||||||
|
- `PACKAGE_ARCH=__PACKAGE_ARCH__`
|
||||||
|
- `PACKAGE_PLATFORM=__PACKAGE_PLATFORM__`
|
||||||
|
- `RIRS=afrinic,apnic,arin,lacnic,ripe`
|
||||||
|
- `MAX_RUNS=-1`
|
||||||
|
- `INTERVAL_SECS=600`
|
||||||
|
- `TAL_INPUT_MODE=file-live-ta`
|
||||||
|
- `RESOURCE_VALIDATION_MODE=validation-update-03`
|
||||||
|
- `LIVE_TA_REFRESH_BEFORE_SNAPSHOT=1`
|
||||||
|
- `PERIODIC_SNAPSHOT_RESET=0`
|
||||||
|
- `PERIODIC_SNAPSHOT_MAX_DELTAS=100`
|
||||||
|
- `HOST_DATA_DIR=__HOST_DATA_DIR__`
|
||||||
|
- `SOAK_RESTART_POLICY=unless-stopped`
|
||||||
|
- `RPKI_IMAGE=ours-rp-runtime-__PACKAGE_ARCH__:dev`
|
||||||
|
- `METRICS_IMAGE=ours-rp-metrics-__PACKAGE_ARCH__:dev`
|
||||||
|
- `METRICS_PLATFORM=__PACKAGE_PLATFORM__`
|
||||||
|
- `MONITOR_PLATFORM=__PACKAGE_PLATFORM__`
|
||||||
|
- `ALLOW_CROSS_ARCH=0`
|
||||||
|
- `RTR_REPORT_DIR=__HOST_DATA_DIR__/empty-rtr-report`, which mounts an empty fallback directory by default; set it to a real host directory such as `/root/rpki/report` to ingest reports from a separately deployed RTR service
|
||||||
|
|
||||||
|
## First Start Semantics
|
||||||
|
|
||||||
|
If there is no successful run under `HOST_DATA_DIR/runs`, `start.sh` starts the core `ours-rp-soak` service first and waits for the first snapshot to succeed before starting metrics, Prometheus and Grafana.
|
||||||
|
|
||||||
|
Container/image mapping:
|
||||||
|
|
||||||
|
- `ours-rp-soak` uses `RPKI_IMAGE`
|
||||||
|
- `artifact-metrics` uses `METRICS_IMAGE`
|
||||||
|
- `prometheus` / `grafana` use their monitor images
|
||||||
|
|
||||||
|
The first snapshot refreshes live TA certificates before starting the RP process.
|
||||||
|
|
||||||
|
## Resource Validation Mode
|
||||||
|
|
||||||
|
New knob:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
RESOURCE_VALIDATION_MODE=validation-update-03
|
||||||
|
```
|
||||||
|
|
||||||
|
Supported values:
|
||||||
|
|
||||||
|
- `validation-update-03`: default behavior, using the current validation-update draft semantics;
|
||||||
|
- `rfc6487`: switch back to the original RFC 6487 resource containment behavior.
|
||||||
|
|
||||||
|
The installer / soak runner passes `--resource-validation-mode` to every `rpki` child start.
|
||||||
|
When an upgraded deployment reuses an older `.env` that does not contain this key, the installer appends the new default `validation-update-03` automatically.
|
||||||
|
|
||||||
|
## Architecture Guardrails
|
||||||
|
|
||||||
|
The key scripts read:
|
||||||
|
|
||||||
|
- `PACKAGE_ARCH` / `PACKAGE_PLATFORM` from `.env`
|
||||||
|
- `PACKAGE-MANIFEST.env`
|
||||||
|
- the current host `uname -m`
|
||||||
|
|
||||||
|
Default behavior:
|
||||||
|
|
||||||
|
1. matching host and package architectures: run natively;
|
||||||
|
2. mismatched host and package architectures: fail with an explicit error;
|
||||||
|
3. only when `ALLOW_CROSS_ARCH=1` is set do the scripts attempt to enable matching `binfmt/qemu`.
|
||||||
|
|
||||||
|
That means an `arm64` package on an `x86_64` host does not silently switch to emulation by default.
|
||||||
|
|
||||||
|
## Ports
|
||||||
|
|
||||||
|
Default ports:
|
||||||
|
|
||||||
|
- metrics: `http://<host>:9556/metrics`
|
||||||
|
- Prometheus: `http://<host>:9090`
|
||||||
|
- Grafana: `http://<host>:3000`
|
||||||
|
|
||||||
|
Grafana credentials come from `.env`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
GRAFANA_ADMIN_USER=admin
|
||||||
|
GRAFANA_ADMIN_PASSWORD=admin
|
||||||
|
```
|
||||||
|
|
||||||
|
Change the password and restrict public access for production deployments.
|
||||||
|
|
||||||
|
## Optional RTR report metrics input
|
||||||
|
|
||||||
|
If a separately deployed RTR service on the same host continuously writes `rtr-source-*`, `rtr-runtime-*`, and `rtr-clients-*` JSON reports, configure:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
RTR_REPORT_DIR=/root/rpki/report
|
||||||
|
RTR_REPORT_CONTAINER_DIR=/var/lib/ours-rp/rtr-report
|
||||||
|
```
|
||||||
|
|
||||||
|
The `artifact-metrics` container mounts the host directory read-only and reads it through `RPKI_METRICS_RTR_REPORT_DIR`, producing `ours_rp_rtr_*` metrics. By default, `RTR_REPORT_DIR` points at an empty fallback directory under the installer data root, so the metrics container also starts cleanly without an RTR service.
|
||||||
|
|
||||||
|
## Data Directory
|
||||||
|
|
||||||
|
Default host directory:
|
||||||
|
|
||||||
|
```text
|
||||||
|
__HOST_DATA_DIR__/
|
||||||
|
state/
|
||||||
|
runs/
|
||||||
|
logs/
|
||||||
|
tmp/
|
||||||
|
prometheus/
|
||||||
|
grafana/
|
||||||
|
```
|
||||||
|
|
||||||
|
Each `runs/run_XXXX/` directory contains `report.json`, `result.ccr`, `input.cir`, `vrps.csv`, `vaps.csv`, `stage-timing.json`, logs and metadata.
|
||||||
|
|
||||||
|
## Periodic Snapshot Reset
|
||||||
|
|
||||||
|
New knobs:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PERIODIC_SNAPSHOT_RESET=0
|
||||||
|
PERIODIC_SNAPSHOT_MAX_DELTAS=100
|
||||||
|
```
|
||||||
|
|
||||||
|
Semantics:
|
||||||
|
|
||||||
|
- disabled by default, keeping previous behavior unchanged;
|
||||||
|
- when enabled, one successful snapshot is followed by at most `N` successful delta runs;
|
||||||
|
- after the threshold is reached, the next run is forced to snapshot;
|
||||||
|
- before that forced snapshot, only the active `state/db` is reset, while `runs/`, `logs/`, `state/rsync-mirror`, `.env`, and Prometheus/Grafana data are preserved;
|
||||||
|
- the counter is persisted independently in `HOST_DATA_DIR/state/run-lifecycle-state.json`, so it does not depend on retained `runs/` history;
|
||||||
|
- if that lifecycle file is corrupt, it is backed up as `run-lifecycle-state.json.corrupt.<timestamp>.<pid>` before best-effort bootstrap from retained runs;
|
||||||
|
- after a successful forced snapshot, the old DB staging is deleted so disk usage does not keep growing elsewhere.
|
||||||
|
|
||||||
|
Check the latest `run-meta.json` for:
|
||||||
|
|
||||||
|
- `sync_mode`
|
||||||
|
- `snapshot_reason`
|
||||||
|
- `periodic_snapshot_delta_count`
|
||||||
|
- `periodic_snapshot_forced`
|
||||||
|
- `reset_db_cleanup_status`
|
||||||
|
|
||||||
|
## Common Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./status.sh
|
||||||
|
./logs.sh ours-rp-soak --tail 200
|
||||||
|
./restart.sh
|
||||||
|
./stop.sh
|
||||||
|
./cleanup.sh --keep-runs 100 --execute
|
||||||
|
./uninstall.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
For finite acceptance tests, for example `MAX_RUNS=3`, also set:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
SOAK_RESTART_POLICY=no
|
||||||
|
```
|
||||||
|
|
||||||
|
Otherwise Compose `unless-stopped` will restart the container after it exits successfully.
|
||||||
|
|
||||||
|
`uninstall.sh` keeps data by default. Use the following only when you really want to delete `HOST_DATA_DIR`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./uninstall.sh --purge-data
|
||||||
|
```
|
||||||
195
deploy/docker-installer/docs/README.zh-CN.md
Normal file
195
deploy/docker-installer/docs/README.zh-CN.md
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
# ours RP Docker 安装包使用说明(`__PACKAGE_ARCH__` / `__PACKAGE_PLATFORM__`)
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
本安装包用于在目标架构与包元数据匹配的 Linux 服务器上,通过 Docker Compose 部署 ours RP,并持续运行 all5 RIR 同步验证任务。
|
||||||
|
|
||||||
|
安装包内置与包架构一致的四类镜像:ours RP runtime、artifact metrics、Prometheus、Grafana。部署时不需要现场拉取应用镜像。运行产物、状态数据库、日志、Prometheus 和 Grafana 数据均通过宿主机目录挂载保存。
|
||||||
|
|
||||||
|
默认情况下,宿主架构必须与 `PACKAGE-MANIFEST.env` 中的 `PACKAGE_ARCH` 匹配;若确需在异构主机上通过 QEMU/binfmt 运行,必须显式设置:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ALLOW_CROSS_ARCH=1
|
||||||
|
```
|
||||||
|
|
||||||
|
## Compose 模板来源
|
||||||
|
|
||||||
|
`compose/` 是唯一的生产 Docker Compose 模板来源,Docker 安装包构建脚本会直接复制该目录。不要在其他目录维护第二份 compose、Prometheus 或 Grafana dashboard 文件。
|
||||||
|
|
||||||
|
旧的直接 Compose 部署入口已经移除。需要部署 amd64 或 arm64 时,统一先用 `scripts/docker/build_docker_installer_package.sh` 构建对应架构安装包,再按本说明执行 `install.sh`、`start.sh` 和 `status.sh`。
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
```bash
|
||||||
|
tar -xzf ours-rp-docker-installer-__PACKAGE_ARCH__-*.tar.gz
|
||||||
|
cd ours-rp-docker-installer-__PACKAGE_ARCH__-*
|
||||||
|
|
||||||
|
./install.sh
|
||||||
|
cp .env.example .env # 如 install.sh 已自动创建,可直接编辑现有 .env
|
||||||
|
vim .env
|
||||||
|
./start.sh
|
||||||
|
./status.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
默认配置:
|
||||||
|
|
||||||
|
- `PACKAGE_ARCH=__PACKAGE_ARCH__`
|
||||||
|
- `PACKAGE_PLATFORM=__PACKAGE_PLATFORM__`
|
||||||
|
- `RIRS=afrinic,apnic,arin,lacnic,ripe`
|
||||||
|
- `MAX_RUNS=-1`
|
||||||
|
- `INTERVAL_SECS=600`
|
||||||
|
- `TAL_INPUT_MODE=file-live-ta`
|
||||||
|
- `RESOURCE_VALIDATION_MODE=validation-update-03`
|
||||||
|
- `LIVE_TA_REFRESH_BEFORE_SNAPSHOT=1`
|
||||||
|
- `PERIODIC_SNAPSHOT_RESET=0`
|
||||||
|
- `PERIODIC_SNAPSHOT_MAX_DELTAS=100`
|
||||||
|
- `HOST_DATA_DIR=__HOST_DATA_DIR__`
|
||||||
|
- `SOAK_RESTART_POLICY=unless-stopped`
|
||||||
|
- `RPKI_IMAGE=ours-rp-runtime-__PACKAGE_ARCH__:dev`
|
||||||
|
- `METRICS_IMAGE=ours-rp-metrics-__PACKAGE_ARCH__:dev`
|
||||||
|
- `METRICS_PLATFORM=__PACKAGE_PLATFORM__`
|
||||||
|
- `MONITOR_PLATFORM=__PACKAGE_PLATFORM__`
|
||||||
|
- `ALLOW_CROSS_ARCH=0`
|
||||||
|
- `RTR_REPORT_DIR=__HOST_DATA_DIR__/empty-rtr-report`,默认挂载空目录;如需接入独立 RTR 服务 report,可设置为宿主机真实目录,例如 `/root/rpki/report`
|
||||||
|
|
||||||
|
## 首次启动语义
|
||||||
|
|
||||||
|
如果 `HOST_DATA_DIR/runs` 下没有成功 run,`start.sh` 会先启动核心 `ours-rp-soak`,等待第一轮 snapshot 成功后再启动 metrics、Prometheus 和 Grafana。
|
||||||
|
|
||||||
|
容器镜像分工:
|
||||||
|
|
||||||
|
- `ours-rp-soak` 使用 `RPKI_IMAGE`
|
||||||
|
- `artifact-metrics` 使用 `METRICS_IMAGE`
|
||||||
|
- `prometheus` / `grafana` 使用各自 monitor image
|
||||||
|
|
||||||
|
第一轮 snapshot 会先拉取 live TA,避免 clean state 使用旧 fixture TA。
|
||||||
|
|
||||||
|
## 资源验证模式
|
||||||
|
|
||||||
|
新增配置:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
RESOURCE_VALIDATION_MODE=validation-update-03
|
||||||
|
```
|
||||||
|
|
||||||
|
可选值:
|
||||||
|
|
||||||
|
- `validation-update-03`:默认,按当前 validation update draft 语义运行;
|
||||||
|
- `rfc6487`:切换为 RFC 6487 原始资源包含判定语义。
|
||||||
|
|
||||||
|
installer / soak runner 每次启动 `rpki` 子进程时都会显式传入 `--resource-validation-mode`。
|
||||||
|
如果升级时复用旧 `.env` 且缺少该变量,installer 会自动补入默认值 `validation-update-03`,不会破坏旧 `.env` 复用流程。
|
||||||
|
|
||||||
|
## 架构检查
|
||||||
|
|
||||||
|
关键脚本会读取:
|
||||||
|
|
||||||
|
- `.env` 中的 `PACKAGE_ARCH` / `PACKAGE_PLATFORM`
|
||||||
|
- `PACKAGE-MANIFEST.env`
|
||||||
|
- 当前宿主 `uname -m`
|
||||||
|
|
||||||
|
默认行为:
|
||||||
|
|
||||||
|
1. 宿主与包架构匹配:直接运行;
|
||||||
|
2. 宿主与包架构不匹配:明确报错并停止;
|
||||||
|
3. 仅当 `ALLOW_CROSS_ARCH=1` 时,脚本才会尝试启用对应架构的 `binfmt/qemu`。
|
||||||
|
|
||||||
|
因此在 `x86_64` 主机上运行 `arm64` 包,不会默认静默进入模拟执行。
|
||||||
|
|
||||||
|
## 访问端口
|
||||||
|
|
||||||
|
默认端口:
|
||||||
|
|
||||||
|
- metrics: `http://<host>:9556/metrics`
|
||||||
|
- Prometheus: `http://<host>:9090`
|
||||||
|
- Grafana: `http://<host>:3000`
|
||||||
|
|
||||||
|
Grafana 默认账号密码来自 `.env`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
GRAFANA_ADMIN_USER=admin
|
||||||
|
GRAFANA_ADMIN_PASSWORD=admin
|
||||||
|
```
|
||||||
|
|
||||||
|
生产部署时应修改密码并限制外部访问。
|
||||||
|
|
||||||
|
## 可选 RTR report 监控接入
|
||||||
|
|
||||||
|
如果同一台机器上已经有独立部署的 RTR 服务,并且它持续输出 `rtr-source-*`、`rtr-runtime-*`、`rtr-clients-*` JSON report,可在 `.env` 中配置:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
RTR_REPORT_DIR=/root/rpki/report
|
||||||
|
RTR_REPORT_CONTAINER_DIR=/var/lib/ours-rp/rtr-report
|
||||||
|
```
|
||||||
|
|
||||||
|
`artifact-metrics` 容器会以只读方式挂载该目录,并通过 `RPKI_METRICS_RTR_REPORT_DIR` 读取 report,输出 `ours_rp_rtr_*` 指标。默认 `RTR_REPORT_DIR` 指向安装包数据目录下的空 fallback 目录,因此未接入 RTR 服务时 metrics 容器也能稳定启动。
|
||||||
|
|
||||||
|
## 数据目录
|
||||||
|
|
||||||
|
默认宿主机目录:
|
||||||
|
|
||||||
|
```text
|
||||||
|
__HOST_DATA_DIR__/
|
||||||
|
state/
|
||||||
|
runs/
|
||||||
|
logs/
|
||||||
|
tmp/
|
||||||
|
prometheus/
|
||||||
|
grafana/
|
||||||
|
```
|
||||||
|
|
||||||
|
`runs/run_XXXX/` 中包含每轮 `report.json`、`result.ccr`、`input.cir`、`vrps.csv`、`vaps.csv`、`stage-timing.json`、日志和元数据。
|
||||||
|
|
||||||
|
## 定期 snapshot reset
|
||||||
|
|
||||||
|
新增配置:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PERIODIC_SNAPSHOT_RESET=0
|
||||||
|
PERIODIC_SNAPSHOT_MAX_DELTAS=100
|
||||||
|
```
|
||||||
|
|
||||||
|
语义:
|
||||||
|
|
||||||
|
- 默认关闭,行为与旧版本一致;
|
||||||
|
- 开启后,一次成功 snapshot 后最多连续执行 `N` 个成功 delta;
|
||||||
|
- 达到阈值后,下一轮强制跑 snapshot;
|
||||||
|
- 强制 snapshot 前只重置 active `state/db`,保留 `runs/`、`logs/`、`state/rsync-mirror`、`.env`、Prometheus/Grafana 数据;
|
||||||
|
- 周期计数保存在独立 lifecycle 文件 `HOST_DATA_DIR/state/run-lifecycle-state.json`,不依赖 `runs/` 保留窗口;
|
||||||
|
- lifecycle 文件损坏时会先备份为 `run-lifecycle-state.json.corrupt.<timestamp>.<pid>`,再从当前保留 run 尽力 bootstrap;
|
||||||
|
- 强制 snapshot 成功后旧 DB staging 会被删除,避免磁盘只是换目录继续增长。
|
||||||
|
|
||||||
|
可通过最新 `run-meta.json` 中的以下字段确认:
|
||||||
|
|
||||||
|
- `sync_mode`
|
||||||
|
- `snapshot_reason`
|
||||||
|
- `periodic_snapshot_delta_count`
|
||||||
|
- `periodic_snapshot_forced`
|
||||||
|
- `reset_db_cleanup_status`
|
||||||
|
|
||||||
|
## 常用命令
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./status.sh
|
||||||
|
./logs.sh ours-rp-soak --tail 200
|
||||||
|
./restart.sh
|
||||||
|
./stop.sh
|
||||||
|
./cleanup.sh --keep-runs 100 --execute
|
||||||
|
./uninstall.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
如果做有限轮次验收,例如 `MAX_RUNS=3`,建议同时设置:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
SOAK_RESTART_POLICY=no
|
||||||
|
```
|
||||||
|
|
||||||
|
否则 Compose 的 `unless-stopped` 策略会在容器正常退出后再次拉起下一轮。
|
||||||
|
|
||||||
|
`uninstall.sh` 默认不删除数据。只有显式执行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./uninstall.sh --purge-data
|
||||||
|
```
|
||||||
|
|
||||||
|
才会删除 `HOST_DATA_DIR`。
|
||||||
132
deploy/docker-installer/docs/operations.en.md
Normal file
132
deploy/docker-installer/docs/operations.en.md
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
# Operations Guide (`__PACKAGE_ARCH__`)
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./install.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
The installer is idempotent:
|
||||||
|
|
||||||
|
- existing `.env` is kept;
|
||||||
|
- existing Docker/Compose installation is reused;
|
||||||
|
- repeated loading of packaged runtime, metrics, Prometheus and Grafana images for the package architecture is safe;
|
||||||
|
- existing data directory is reused;
|
||||||
|
- matching host/package architecture is required by default.
|
||||||
|
|
||||||
|
## Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./start.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Start without waiting for the first snapshot:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./start.sh --no-wait-first-run
|
||||||
|
```
|
||||||
|
|
||||||
|
If you intentionally want to run this package on a different host architecture through QEMU/binfmt, first set:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ALLOW_CROSS_ARCH=1
|
||||||
|
```
|
||||||
|
|
||||||
|
Then rerun `./install.sh` or `./self-check.sh`, and only then will the scripts attempt to enable the matching binfmt handler.
|
||||||
|
|
||||||
|
## Stop and Restart
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./stop.sh
|
||||||
|
./restart.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Status Checks
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./status.sh
|
||||||
|
./self-check.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Important checks:
|
||||||
|
|
||||||
|
- Docker/Compose availability;
|
||||||
|
- runtime, metrics, Prometheus and Grafana images exist;
|
||||||
|
- `HOST_DATA_DIR` is writable;
|
||||||
|
- Compose config is valid;
|
||||||
|
- latest run status;
|
||||||
|
- metrics, Prometheus and Grafana endpoints;
|
||||||
|
- whether `host_arch`, `package_arch`, and `arch_mode` match expectations.
|
||||||
|
|
||||||
|
`status.sh` also prints:
|
||||||
|
|
||||||
|
- `package_arch`
|
||||||
|
- `package_platform`
|
||||||
|
- `runtime_image`
|
||||||
|
- `metrics_image`
|
||||||
|
- `allow_cross_arch`
|
||||||
|
- `periodic_snapshot_reset`
|
||||||
|
- `periodic_snapshot_max_deltas`
|
||||||
|
- `rtr_report_dir`
|
||||||
|
- `rtr_report_container_dir`
|
||||||
|
|
||||||
|
To verify RTR report ingestion:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s "http://127.0.0.1:${METRICS_PORT:-9556}/metrics" | grep '^ours_rp_rtr_' | head
|
||||||
|
```
|
||||||
|
|
||||||
|
## Upgrade
|
||||||
|
|
||||||
|
Extract the new package into a new directory and explicitly reuse the existing `.env` through the upgrade script:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./upgrade.sh --reuse-env-from /path/to/old-installer/.env
|
||||||
|
```
|
||||||
|
|
||||||
|
If the new package directory already has a `.env`, the upgrade script keeps it.
|
||||||
|
|
||||||
|
If an older `.env` does not contain `METRICS_IMAGE` or `METRICS_PLATFORM`, the install/upgrade flow backfills package-matched defaults automatically.
|
||||||
|
|
||||||
|
Upgrade does not delete:
|
||||||
|
|
||||||
|
- `runs/`
|
||||||
|
- `logs/`
|
||||||
|
- `state/rsync-mirror`
|
||||||
|
- runtime configuration referenced by `.env`
|
||||||
|
- Prometheus / Grafana data
|
||||||
|
|
||||||
|
To validate periodic forced snapshot behavior, temporarily set:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PERIODIC_SNAPSHOT_RESET=1
|
||||||
|
PERIODIC_SNAPSHOT_MAX_DELTAS=2
|
||||||
|
```
|
||||||
|
|
||||||
|
Then confirm the latest `run-meta.json` contains:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
snapshot_reason=periodic_snapshot_delta_limit
|
||||||
|
```
|
||||||
|
|
||||||
|
And inspect the independent lifecycle state:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
jq '{last_run,last_success_snapshot,successful_deltas_since_snapshot,state_health}' \
|
||||||
|
"${HOST_DATA_DIR}/state/run-lifecycle-state.json"
|
||||||
|
```
|
||||||
|
|
||||||
|
After validation, restore:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PERIODIC_SNAPSHOT_MAX_DELTAS=100
|
||||||
|
```
|
||||||
|
|
||||||
|
## Cleanup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./cleanup.sh --keep-runs 100
|
||||||
|
./cleanup.sh --keep-runs 100 --execute
|
||||||
|
```
|
||||||
|
|
||||||
|
Cleanup is dry-run by default. Add `--execute` after reviewing the output.
|
||||||
132
deploy/docker-installer/docs/operations.zh-CN.md
Normal file
132
deploy/docker-installer/docs/operations.zh-CN.md
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
# 运维手册(`__PACKAGE_ARCH__`)
|
||||||
|
|
||||||
|
## 安装
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./install.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
安装脚本是幂等的:
|
||||||
|
|
||||||
|
- 已有 `.env` 不覆盖;
|
||||||
|
- 已安装 Docker/Compose 则跳过;
|
||||||
|
- 包内 runtime、metrics、Prometheus、Grafana 对应架构镜像重复加载是安全的;
|
||||||
|
- 数据目录已存在则复用;
|
||||||
|
- 默认要求宿主与包架构匹配。
|
||||||
|
|
||||||
|
## 启动
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./start.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
如需后台启动后不等待首轮 snapshot:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./start.sh --no-wait-first-run
|
||||||
|
```
|
||||||
|
|
||||||
|
如需显式允许异构主机通过 QEMU/binfmt 跑包,先在 `.env` 中设置:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ALLOW_CROSS_ARCH=1
|
||||||
|
```
|
||||||
|
|
||||||
|
然后重新执行 `./install.sh` 或 `./self-check.sh`,脚本才会尝试启用对应 binfmt。
|
||||||
|
|
||||||
|
## 停止和重启
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./stop.sh
|
||||||
|
./restart.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## 状态检查
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./status.sh
|
||||||
|
./self-check.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
重点检查项:
|
||||||
|
|
||||||
|
- Docker/Compose 可用;
|
||||||
|
- runtime、metrics、Prometheus、Grafana 镜像存在;
|
||||||
|
- `HOST_DATA_DIR` 可写;
|
||||||
|
- Compose 配置合法;
|
||||||
|
- 最新 run 状态;
|
||||||
|
- metrics、Prometheus、Grafana endpoint;
|
||||||
|
- `host_arch`、`package_arch`、`arch_mode` 是否符合预期。
|
||||||
|
|
||||||
|
`status.sh` 还会显示:
|
||||||
|
|
||||||
|
- `package_arch`
|
||||||
|
- `package_platform`
|
||||||
|
- `runtime_image`
|
||||||
|
- `metrics_image`
|
||||||
|
- `allow_cross_arch`
|
||||||
|
- `periodic_snapshot_reset`
|
||||||
|
- `periodic_snapshot_max_deltas`
|
||||||
|
- `rtr_report_dir`
|
||||||
|
- `rtr_report_container_dir`
|
||||||
|
|
||||||
|
如需确认 RTR report 已接入:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s "http://127.0.0.1:${METRICS_PORT:-9556}/metrics" | grep '^ours_rp_rtr_' | head
|
||||||
|
```
|
||||||
|
|
||||||
|
## 升级
|
||||||
|
|
||||||
|
把新安装包解压到新目录后,推荐通过升级脚本显式复用旧 `.env`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./upgrade.sh --reuse-env-from /path/to/old-installer/.env
|
||||||
|
```
|
||||||
|
|
||||||
|
如果新目录已经存在 `.env`,升级脚本会保留它,不覆盖。
|
||||||
|
|
||||||
|
如果旧 `.env` 缺失 `METRICS_IMAGE` 或 `METRICS_PLATFORM`,升级/安装链路会按当前包架构自动补齐默认值。
|
||||||
|
|
||||||
|
升级不会删除以下数据:
|
||||||
|
|
||||||
|
- `runs/`
|
||||||
|
- `logs/`
|
||||||
|
- `state/rsync-mirror`
|
||||||
|
- `.env` 对应的运行配置
|
||||||
|
- Prometheus / Grafana 数据
|
||||||
|
|
||||||
|
验证定期 forced snapshot 时,可临时设置:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PERIODIC_SNAPSHOT_RESET=1
|
||||||
|
PERIODIC_SNAPSHOT_MAX_DELTAS=2
|
||||||
|
```
|
||||||
|
|
||||||
|
然后检查最新 `run-meta.json` 应出现:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
snapshot_reason=periodic_snapshot_delta_limit
|
||||||
|
```
|
||||||
|
|
||||||
|
并检查独立 lifecycle 状态:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
jq '{last_run,last_success_snapshot,successful_deltas_since_snapshot,state_health}' \
|
||||||
|
"${HOST_DATA_DIR}/state/run-lifecycle-state.json"
|
||||||
|
```
|
||||||
|
|
||||||
|
验证完成后恢复:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PERIODIC_SNAPSHOT_MAX_DELTAS=100
|
||||||
|
```
|
||||||
|
|
||||||
|
## 清理
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./cleanup.sh --keep-runs 100
|
||||||
|
./cleanup.sh --keep-runs 100 --execute
|
||||||
|
```
|
||||||
|
|
||||||
|
默认 dry-run,确认后加 `--execute`。
|
||||||
142
deploy/docker-installer/docs/troubleshooting.en.md
Normal file
142
deploy/docker-installer/docs/troubleshooting.en.md
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
# Troubleshooting (`__PACKAGE_ARCH__` / `__PACKAGE_PLATFORM__`)
|
||||||
|
|
||||||
|
## Docker or Compose Is Unavailable
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker version
|
||||||
|
docker compose version
|
||||||
|
```
|
||||||
|
|
||||||
|
If missing, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./install.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Package Architecture Mismatch
|
||||||
|
|
||||||
|
If `status.sh`, `install.sh`, or `self-check.sh` reports:
|
||||||
|
|
||||||
|
```text
|
||||||
|
package architecture mismatch
|
||||||
|
```
|
||||||
|
|
||||||
|
the host `uname -m` does not match the packaged `PACKAGE_ARCH=__PACKAGE_ARCH__`. The default behavior is to fail clearly rather than silently switching to emulation.
|
||||||
|
|
||||||
|
Only if you intentionally accept QEMU/binfmt cross-architecture execution should you set:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ALLOW_CROSS_ARCH=1
|
||||||
|
```
|
||||||
|
|
||||||
|
Then rerun install or self-check.
|
||||||
|
|
||||||
|
## `__PACKAGE_ARCH__` Image Cannot Run
|
||||||
|
|
||||||
|
Running `__PACKAGE_PLATFORM__` images on a different host architecture requires binfmt/qemu. The installer only attempts this automatically when `ALLOW_CROSS_ARCH=1`; you can also enable it manually:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run --rm --privileged tonistiigi/binfmt --install __PACKAGE_ARCH__
|
||||||
|
docker run --rm --platform __PACKAGE_PLATFORM__ debian:bookworm-slim uname -m
|
||||||
|
```
|
||||||
|
|
||||||
|
For `arm64` packages the expected output is `aarch64`; for `amd64` packages the expected output is `x86_64`.
|
||||||
|
|
||||||
|
To confirm the metrics image is also loaded correctly:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker image inspect "$(grep '^METRICS_IMAGE=' .env | cut -d= -f2-)"
|
||||||
|
docker run --rm --platform __PACKAGE_PLATFORM__ \
|
||||||
|
"$(grep '^METRICS_IMAGE=' .env | cut -d= -f2-)" \
|
||||||
|
/opt/ours-rp/bin/rpki_artifact_metrics --help
|
||||||
|
```
|
||||||
|
|
||||||
|
## RTR report metrics are empty
|
||||||
|
|
||||||
|
Check `.env`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
grep '^RTR_REPORT_DIR=' .env
|
||||||
|
test -d "$(grep '^RTR_REPORT_DIR=' .env | cut -d= -f2-)" && echo ok
|
||||||
|
```
|
||||||
|
|
||||||
|
Check the metrics container mount:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker inspect "$(grep '^COMPOSE_PROJECT_NAME=' .env | cut -d= -f2-)-artifact-metrics" \
|
||||||
|
| jq '.[0].Mounts[] | select(.Destination=="/var/lib/ours-rp/rtr-report")'
|
||||||
|
```
|
||||||
|
|
||||||
|
## First Snapshot Times Out
|
||||||
|
|
||||||
|
All-five snapshot can be slow, especially under QEMU. Increase timeout:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./start.sh --timeout-secs 14400
|
||||||
|
```
|
||||||
|
|
||||||
|
## Output Counts Are Too Low
|
||||||
|
|
||||||
|
Check:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
grep LIVE_TA_REFRESH_BEFORE_SNAPSHOT .env
|
||||||
|
ls -l __HOST_DATA_DIR__/state/live-ta
|
||||||
|
tail -100 __HOST_DATA_DIR__/logs/live-ta-refresh-*.log
|
||||||
|
```
|
||||||
|
|
||||||
|
In `file-live-ta` mode, snapshot should wait until live TA refresh succeeds.
|
||||||
|
|
||||||
|
## Grafana Login Fails
|
||||||
|
|
||||||
|
Check `.env`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
GRAFANA_ADMIN_USER=admin
|
||||||
|
GRAFANA_ADMIN_PASSWORD=admin
|
||||||
|
```
|
||||||
|
|
||||||
|
If Grafana has already started, changing `.env` may not reset the existing Grafana database. Stop services and back up/clean `${HOST_DATA_DIR}/grafana` if needed.
|
||||||
|
|
||||||
|
## A Finite Acceptance Test Starts an Extra Run
|
||||||
|
|
||||||
|
If `.env` sets a finite `MAX_RUNS=3` while `SOAK_RESTART_POLICY=unless-stopped`, Docker Compose restarts the soak container after it exits successfully.
|
||||||
|
|
||||||
|
For finite tests, set:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
SOAK_RESTART_POLICY=no
|
||||||
|
```
|
||||||
|
|
||||||
|
## How to Confirm a Periodic Forced Snapshot
|
||||||
|
|
||||||
|
Check the latest run metadata:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
latest="$(find ${HOST_DATA_DIR}/runs -maxdepth 1 -type d -name 'run_*' | sort | tail -1)"
|
||||||
|
jq '{run_id,sync_mode,snapshot_reason,periodic_snapshot_delta_count,periodic_snapshot_forced,reset_db_cleanup_status}' "$latest/run-meta.json"
|
||||||
|
```
|
||||||
|
|
||||||
|
For a threshold-triggered reset you should see:
|
||||||
|
|
||||||
|
- `sync_mode: "snapshot"`
|
||||||
|
- `snapshot_reason: "periodic_snapshot_delta_limit"`
|
||||||
|
- `periodic_snapshot_forced: true`
|
||||||
|
|
||||||
|
To confirm delta counting still advances after retained runs are pruned, also inspect:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
jq '{last_success_snapshot,successful_deltas_since_snapshot,recent_runs_count:(.recent_runs|length)}' \
|
||||||
|
"${HOST_DATA_DIR}/state/run-lifecycle-state.json"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Lifecycle State File Is Corrupt
|
||||||
|
|
||||||
|
The script backs up the corrupt file before best-effort bootstrap from retained runs:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ls -1 "${HOST_DATA_DIR}/state"/run-lifecycle-state.json.corrupt.*
|
||||||
|
jq . "${HOST_DATA_DIR}/state/run-lifecycle-state.json"
|
||||||
|
```
|
||||||
142
deploy/docker-installer/docs/troubleshooting.zh-CN.md
Normal file
142
deploy/docker-installer/docs/troubleshooting.zh-CN.md
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
# 故障排查(`__PACKAGE_ARCH__` / `__PACKAGE_PLATFORM__`)
|
||||||
|
|
||||||
|
## Docker 或 Compose 不可用
|
||||||
|
|
||||||
|
执行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker version
|
||||||
|
docker compose version
|
||||||
|
```
|
||||||
|
|
||||||
|
如果缺失,重新执行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./install.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## 架构不匹配报错
|
||||||
|
|
||||||
|
当 `status.sh` / `install.sh` / `self-check.sh` 报出:
|
||||||
|
|
||||||
|
```text
|
||||||
|
package architecture mismatch
|
||||||
|
```
|
||||||
|
|
||||||
|
说明宿主 `uname -m` 与包的 `PACKAGE_ARCH=__PACKAGE_ARCH__` 不一致。默认行为就是失败而不是静默模拟运行。
|
||||||
|
|
||||||
|
只有在你明确接受 QEMU/binfmt 跨架构运行时,才设置:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ALLOW_CROSS_ARCH=1
|
||||||
|
```
|
||||||
|
|
||||||
|
然后重新执行安装或自检。
|
||||||
|
|
||||||
|
## `__PACKAGE_ARCH__` 镜像无法运行
|
||||||
|
|
||||||
|
在异构主机上运行 `__PACKAGE_PLATFORM__` 镜像需要 binfmt/qemu。安装脚本只有在 `ALLOW_CROSS_ARCH=1` 时才会自动尝试启用;也可以手动执行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run --rm --privileged tonistiigi/binfmt --install __PACKAGE_ARCH__
|
||||||
|
docker run --rm --platform __PACKAGE_PLATFORM__ debian:bookworm-slim uname -m
|
||||||
|
```
|
||||||
|
|
||||||
|
对于 `arm64` 包,预期输出 `aarch64`;对于 `amd64` 包,预期输出 `x86_64`。
|
||||||
|
|
||||||
|
如需进一步确认 metrics 镜像也已正确加载:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker image inspect "$(grep '^METRICS_IMAGE=' .env | cut -d= -f2-)"
|
||||||
|
docker run --rm --platform __PACKAGE_PLATFORM__ \
|
||||||
|
"$(grep '^METRICS_IMAGE=' .env | cut -d= -f2-)" \
|
||||||
|
/opt/ours-rp/bin/rpki_artifact_metrics --help
|
||||||
|
```
|
||||||
|
|
||||||
|
## RTR report 指标为空
|
||||||
|
|
||||||
|
检查 `.env`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
grep '^RTR_REPORT_DIR=' .env
|
||||||
|
test -d "$(grep '^RTR_REPORT_DIR=' .env | cut -d= -f2-)" && echo ok
|
||||||
|
```
|
||||||
|
|
||||||
|
检查 metrics 容器是否挂载该目录:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker inspect "$(grep '^COMPOSE_PROJECT_NAME=' .env | cut -d= -f2-)-artifact-metrics" \
|
||||||
|
| jq '.[0].Mounts[] | select(.Destination=="/var/lib/ours-rp/rtr-report")'
|
||||||
|
```
|
||||||
|
|
||||||
|
## 首轮 snapshot 超时
|
||||||
|
|
||||||
|
all5 snapshot 可能很慢,尤其在 QEMU 环境。可以提高超时:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./start.sh --timeout-secs 14400
|
||||||
|
```
|
||||||
|
|
||||||
|
## 产物数量异常偏低
|
||||||
|
|
||||||
|
检查:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
grep LIVE_TA_REFRESH_BEFORE_SNAPSHOT .env
|
||||||
|
ls -l __HOST_DATA_DIR__/state/live-ta
|
||||||
|
tail -100 __HOST_DATA_DIR__/logs/live-ta-refresh-*.log
|
||||||
|
```
|
||||||
|
|
||||||
|
`file-live-ta` 模式下,snapshot 应等待 live TA 成功刷新。
|
||||||
|
|
||||||
|
## Grafana 无法登录
|
||||||
|
|
||||||
|
确认 `.env` 中:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
GRAFANA_ADMIN_USER=admin
|
||||||
|
GRAFANA_ADMIN_PASSWORD=admin
|
||||||
|
```
|
||||||
|
|
||||||
|
如果曾经启动过 Grafana,修改 `.env` 不一定重置已有 Grafana 数据库账号。可以停止服务后按需备份并清理 `${HOST_DATA_DIR}/grafana`。
|
||||||
|
|
||||||
|
## 有限轮次验收后又多跑了一轮
|
||||||
|
|
||||||
|
如果 `.env` 中设置了 `MAX_RUNS=3` 这类有限轮次,同时 `SOAK_RESTART_POLICY=unless-stopped`,Docker Compose 会在 soak 容器正常退出后重新启动容器。
|
||||||
|
|
||||||
|
有限验收建议设置:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
SOAK_RESTART_POLICY=no
|
||||||
|
```
|
||||||
|
|
||||||
|
## 如何确认触发了定期 forced snapshot
|
||||||
|
|
||||||
|
检查最新 run metadata:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
latest="$(find ${HOST_DATA_DIR}/runs -maxdepth 1 -type d -name 'run_*' | sort | tail -1)"
|
||||||
|
jq '{run_id,sync_mode,snapshot_reason,periodic_snapshot_delta_count,periodic_snapshot_forced,reset_db_cleanup_status}' "$latest/run-meta.json"
|
||||||
|
```
|
||||||
|
|
||||||
|
阈值触发时应看到:
|
||||||
|
|
||||||
|
- `sync_mode: "snapshot"`
|
||||||
|
- `snapshot_reason: "periodic_snapshot_delta_limit"`
|
||||||
|
- `periodic_snapshot_forced: true`
|
||||||
|
|
||||||
|
如需确认 retain 裁剪后仍在累计 delta,可继续检查:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
jq '{last_success_snapshot,successful_deltas_since_snapshot,recent_runs_count:(.recent_runs|length)}' \
|
||||||
|
"${HOST_DATA_DIR}/state/run-lifecycle-state.json"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Lifecycle 状态文件损坏
|
||||||
|
|
||||||
|
脚本会先备份损坏文件,再从当前保留的 `runs/` 尽力 bootstrap:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ls -1 "${HOST_DATA_DIR}/state"/run-lifecycle-state.json.corrupt.*
|
||||||
|
jq . "${HOST_DATA_DIR}/state/run-lifecycle-state.json"
|
||||||
|
```
|
||||||
49
deploy/docker-installer/install.sh
Executable file
49
deploy/docker-installer/install.sh
Executable file
@@ -0,0 +1,49 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
# shellcheck source=scripts/common.sh
|
||||||
|
source "$SCRIPT_DIR/scripts/common.sh"
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'USAGE'
|
||||||
|
Usage: ./install.sh [--skip-dep-install]
|
||||||
|
|
||||||
|
Install or update the ours RP Docker installer package idempotently.
|
||||||
|
USAGE
|
||||||
|
}
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--skip-dep-install)
|
||||||
|
export SKIP_DEP_INSTALL=1
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
die "unknown option: $1"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
load_env
|
||||||
|
assert_arch_compatibility
|
||||||
|
install_docker_if_missing
|
||||||
|
require_cmd curl
|
||||||
|
require_cmd jq
|
||||||
|
require_cmd rsync
|
||||||
|
require_cmd gzip
|
||||||
|
require_cmd tar
|
||||||
|
create_data_dirs
|
||||||
|
load_installer_images
|
||||||
|
ensure_binfmt_if_needed
|
||||||
|
verify_runtime_image
|
||||||
|
verify_metrics_image
|
||||||
|
verify_monitor_images
|
||||||
|
compose_config_path="$(mktemp "${TMPDIR:-/tmp}/ours-rp-compose-config.XXXXXX.yml")"
|
||||||
|
compose_cmd --profile core --profile sidecar --profile monitor config >"$compose_config_path"
|
||||||
|
"$SCRIPT_DIR/self-check.sh" --quick
|
||||||
|
rm -f "$compose_config_path"
|
||||||
|
log "install complete"
|
||||||
7
deploy/docker-installer/logs.sh
Executable file
7
deploy/docker-installer/logs.sh
Executable file
@@ -0,0 +1,7 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
# shellcheck source=scripts/common.sh
|
||||||
|
source "$SCRIPT_DIR/scripts/common.sh"
|
||||||
|
load_env
|
||||||
|
compose_cmd --profile core --profile sidecar --profile monitor logs "$@"
|
||||||
5
deploy/docker-installer/restart.sh
Executable file
5
deploy/docker-installer/restart.sh
Executable file
@@ -0,0 +1,5 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
"$SCRIPT_DIR/stop.sh" || true
|
||||||
|
"$SCRIPT_DIR/start.sh" "$@"
|
||||||
521
deploy/docker-installer/scripts/common.sh
Normal file
521
deploy/docker-installer/scripts/common.sh
Normal file
@@ -0,0 +1,521 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
INSTALLER_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
ENV_FILE="${ENV_FILE:-$INSTALLER_ROOT/.env}"
|
||||||
|
ENV_EXAMPLE="$INSTALLER_ROOT/.env.example"
|
||||||
|
COMPOSE_FILE="$INSTALLER_ROOT/compose/docker-compose.yml"
|
||||||
|
MANIFEST_FILE="${MANIFEST_FILE:-$INSTALLER_ROOT/PACKAGE-MANIFEST.env}"
|
||||||
|
|
||||||
|
log() {
|
||||||
|
printf '[ours-rp-installer] %s\n' "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
warn() {
|
||||||
|
printf '[ours-rp-installer][WARN] %s\n' "$*" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
die() {
|
||||||
|
printf '[ours-rp-installer][ERROR] %s\n' "$*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
require_cmd() {
|
||||||
|
command -v "$1" >/dev/null 2>&1 || die "missing command: $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
normalize_arch() {
|
||||||
|
case "$1" in
|
||||||
|
amd64|x86_64|linux/amd64)
|
||||||
|
printf 'amd64\n'
|
||||||
|
;;
|
||||||
|
arm64|aarch64|linux/arm64)
|
||||||
|
printf 'arm64\n'
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
platform_for_arch() {
|
||||||
|
printf 'linux/%s\n' "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
env_file_has_key() {
|
||||||
|
local env_path="$1"
|
||||||
|
local key="$2"
|
||||||
|
[[ -f "$env_path" ]] && grep -Eq "^${key}=" "$env_path"
|
||||||
|
}
|
||||||
|
|
||||||
|
persist_env_default() {
|
||||||
|
local env_path="$1"
|
||||||
|
local key="$2"
|
||||||
|
local value="$3"
|
||||||
|
local tmp_env
|
||||||
|
if env_file_has_key "$env_path" "$key"; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
tmp_env="$(mktemp)"
|
||||||
|
cat "$env_path" > "$tmp_env"
|
||||||
|
printf '%s=%s\n' "$key" "$value" >> "$tmp_env"
|
||||||
|
mv "$tmp_env" "$env_path"
|
||||||
|
}
|
||||||
|
|
||||||
|
env_flag_enabled() {
|
||||||
|
case "${1:-0}" in
|
||||||
|
1|true|TRUE|yes|YES|on|ON)
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
detect_host_arch() {
|
||||||
|
local raw_arch
|
||||||
|
raw_arch="$(uname -m)"
|
||||||
|
normalize_arch "$raw_arch" || die "unsupported host architecture: $raw_arch"
|
||||||
|
}
|
||||||
|
|
||||||
|
load_manifest() {
|
||||||
|
MANIFEST_PACKAGE_ARCH_RAW=""
|
||||||
|
MANIFEST_PACKAGE_PLATFORM_RAW=""
|
||||||
|
if [[ -f "$MANIFEST_FILE" ]]; then
|
||||||
|
set -a
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "$MANIFEST_FILE"
|
||||||
|
set +a
|
||||||
|
MANIFEST_PACKAGE_ARCH_RAW="${PACKAGE_ARCH:-${package_arch:-}}"
|
||||||
|
MANIFEST_PACKAGE_PLATFORM_RAW="${PACKAGE_PLATFORM:-${package_platform:-}}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
effective_package_arch() {
|
||||||
|
if [[ -n "${PACKAGE_ARCH:-}" ]]; then
|
||||||
|
normalize_arch "$PACKAGE_ARCH" || die "unsupported PACKAGE_ARCH=${PACKAGE_ARCH}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [[ -n "${PACKAGE_PLATFORM:-}" ]]; then
|
||||||
|
normalize_arch "$PACKAGE_PLATFORM" || die "unsupported PACKAGE_PLATFORM=${PACKAGE_PLATFORM}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [[ -n "${RPKI_PLATFORM:-}" ]]; then
|
||||||
|
normalize_arch "$RPKI_PLATFORM" || die "unsupported RPKI_PLATFORM=${RPKI_PLATFORM}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [[ -n "${METRICS_PLATFORM:-}" ]]; then
|
||||||
|
normalize_arch "$METRICS_PLATFORM" || die "unsupported METRICS_PLATFORM=${METRICS_PLATFORM}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [[ -n "${MONITOR_PLATFORM:-}" ]]; then
|
||||||
|
normalize_arch "$MONITOR_PLATFORM" || die "unsupported MONITOR_PLATFORM=${MONITOR_PLATFORM}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
die "unable to determine package architecture from manifest or env"
|
||||||
|
}
|
||||||
|
|
||||||
|
arch_mode() {
|
||||||
|
local host_arch package_arch
|
||||||
|
host_arch="$(detect_host_arch)"
|
||||||
|
package_arch="$(effective_package_arch)"
|
||||||
|
if [[ "$host_arch" == "$package_arch" ]]; then
|
||||||
|
printf 'native\n'
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if env_flag_enabled "${ALLOW_CROSS_ARCH:-0}"; then
|
||||||
|
printf 'cross-arch-enabled\n'
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
printf 'mismatch-blocked\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_arch_compatibility() {
|
||||||
|
local host_arch package_arch package_platform mode
|
||||||
|
host_arch="$(detect_host_arch)"
|
||||||
|
package_arch="$(effective_package_arch)"
|
||||||
|
package_platform="${PACKAGE_PLATFORM:-${RPKI_PLATFORM:-$(platform_for_arch "$package_arch")}}"
|
||||||
|
mode="$(arch_mode)"
|
||||||
|
case "$mode" in
|
||||||
|
native)
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
cross-arch-enabled)
|
||||||
|
log "cross-arch execution explicitly enabled: host_arch=$host_arch package_arch=$package_arch package_platform=$package_platform"
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
mismatch-blocked)
|
||||||
|
die "package architecture mismatch: host_arch=$host_arch package_arch=$package_arch package_platform=$package_platform. Set ALLOW_CROSS_ARCH=1 in $ENV_FILE to allow explicit QEMU/binfmt emulation."
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
die "unknown arch compatibility mode: $mode"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
load_env() {
|
||||||
|
load_manifest
|
||||||
|
if [[ ! -f "$ENV_FILE" ]]; then
|
||||||
|
[[ -f "$ENV_EXAMPLE" ]] || die "missing $ENV_EXAMPLE"
|
||||||
|
cp "$ENV_EXAMPLE" "$ENV_FILE"
|
||||||
|
log "created .env from .env.example"
|
||||||
|
fi
|
||||||
|
set -a
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "$ENV_FILE"
|
||||||
|
set +a
|
||||||
|
|
||||||
|
if [[ -n "${MANIFEST_PACKAGE_ARCH_RAW:-}" ]]; then
|
||||||
|
manifest_package_arch="$(normalize_arch "$MANIFEST_PACKAGE_ARCH_RAW")" || die "unsupported manifest package arch: $MANIFEST_PACKAGE_ARCH_RAW"
|
||||||
|
if [[ -n "${PACKAGE_ARCH:-}" ]]; then
|
||||||
|
env_package_arch="$(normalize_arch "$PACKAGE_ARCH")" || die "unsupported env package arch: $PACKAGE_ARCH"
|
||||||
|
[[ "$env_package_arch" == "$manifest_package_arch" ]] || die "env PACKAGE_ARCH=$env_package_arch mismatches manifest PACKAGE_ARCH=$manifest_package_arch"
|
||||||
|
fi
|
||||||
|
PACKAGE_ARCH="$manifest_package_arch"
|
||||||
|
else
|
||||||
|
if [[ -z "${PACKAGE_ARCH:-}" ]]; then
|
||||||
|
PACKAGE_ARCH="$(effective_package_arch)"
|
||||||
|
else
|
||||||
|
PACKAGE_ARCH="$(normalize_arch "$PACKAGE_ARCH")"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if [[ -n "${MANIFEST_PACKAGE_PLATFORM_RAW:-}" ]]; then
|
||||||
|
manifest_package_platform="$MANIFEST_PACKAGE_PLATFORM_RAW"
|
||||||
|
if [[ -n "${PACKAGE_PLATFORM:-}" && "$PACKAGE_PLATFORM" != "$manifest_package_platform" ]]; then
|
||||||
|
die "env PACKAGE_PLATFORM=$PACKAGE_PLATFORM mismatches manifest PACKAGE_PLATFORM=$manifest_package_platform"
|
||||||
|
fi
|
||||||
|
PACKAGE_PLATFORM="$manifest_package_platform"
|
||||||
|
else
|
||||||
|
PACKAGE_PLATFORM="${PACKAGE_PLATFORM:-$(platform_for_arch "$PACKAGE_ARCH")}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
HOST_DATA_DIR="${HOST_DATA_DIR:-/var/lib/ours-rp-${PACKAGE_ARCH}-installer}"
|
||||||
|
COMPOSE_PROJECT_NAME="${COMPOSE_PROJECT_NAME:-ours-rp-${PACKAGE_ARCH}-installer}"
|
||||||
|
RPKI_IMAGE="${RPKI_IMAGE:-ours-rp-runtime-${PACKAGE_ARCH}:dev}"
|
||||||
|
RPKI_PLATFORM="${RPKI_PLATFORM:-$PACKAGE_PLATFORM}"
|
||||||
|
METRICS_IMAGE="${METRICS_IMAGE:-ours-rp-metrics-${PACKAGE_ARCH}:dev}"
|
||||||
|
METRICS_PLATFORM="${METRICS_PLATFORM:-$PACKAGE_PLATFORM}"
|
||||||
|
RTR_REPORT_DIR="${RTR_REPORT_DIR:-$HOST_DATA_DIR/empty-rtr-report}"
|
||||||
|
RTR_REPORT_CONTAINER_DIR="${RTR_REPORT_CONTAINER_DIR:-/var/lib/ours-rp/rtr-report}"
|
||||||
|
MONITOR_PLATFORM="${MONITOR_PLATFORM:-$PACKAGE_PLATFORM}"
|
||||||
|
PROMETHEUS_IMAGE="${PROMETHEUS_IMAGE:-prom/prometheus:v2.55.1}"
|
||||||
|
GRAFANA_IMAGE="${GRAFANA_IMAGE:-grafana/grafana:11.3.1}"
|
||||||
|
METRICS_INSTANCE="${METRICS_INSTANCE:-${PACKAGE_ARCH}-installer}"
|
||||||
|
ALLOW_CROSS_ARCH="${ALLOW_CROSS_ARCH:-0}"
|
||||||
|
RESOURCE_VALIDATION_MODE="${RESOURCE_VALIDATION_MODE:-validation-update-03}"
|
||||||
|
FIRST_RUN_WAIT_TIMEOUT_SECS="${FIRST_RUN_WAIT_TIMEOUT_SECS:-7200}"
|
||||||
|
|
||||||
|
persist_env_default "$ENV_FILE" "METRICS_IMAGE" "$METRICS_IMAGE"
|
||||||
|
persist_env_default "$ENV_FILE" "METRICS_PLATFORM" "$METRICS_PLATFORM"
|
||||||
|
persist_env_default "$ENV_FILE" "RTR_REPORT_DIR" "$RTR_REPORT_DIR"
|
||||||
|
persist_env_default "$ENV_FILE" "RTR_REPORT_CONTAINER_DIR" "$RTR_REPORT_CONTAINER_DIR"
|
||||||
|
persist_env_default "$ENV_FILE" "RESOURCE_VALIDATION_MODE" "$RESOURCE_VALIDATION_MODE"
|
||||||
|
}
|
||||||
|
|
||||||
|
compose_cmd() {
|
||||||
|
docker compose --env-file "$ENV_FILE" -f "$COMPOSE_FILE" -p "$COMPOSE_PROJECT_NAME" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_rtr_report_dir() {
|
||||||
|
load_env
|
||||||
|
[[ -n "${RTR_REPORT_DIR:-}" ]] || return 0
|
||||||
|
[[ "$RTR_REPORT_DIR" = /* ]] || die "RTR_REPORT_DIR must be an absolute host path: $RTR_REPORT_DIR"
|
||||||
|
[[ -d "$RTR_REPORT_DIR" ]] || die "RTR_REPORT_DIR does not exist or is not a directory: $RTR_REPORT_DIR"
|
||||||
|
}
|
||||||
|
|
||||||
|
create_data_dirs() {
|
||||||
|
load_env
|
||||||
|
mkdir -p \
|
||||||
|
"$HOST_DATA_DIR/state" \
|
||||||
|
"$HOST_DATA_DIR/runs" \
|
||||||
|
"$HOST_DATA_DIR/logs" \
|
||||||
|
"$HOST_DATA_DIR/tmp" \
|
||||||
|
"$HOST_DATA_DIR/empty-rtr-report" \
|
||||||
|
"$HOST_DATA_DIR/prometheus" \
|
||||||
|
"$HOST_DATA_DIR/grafana"
|
||||||
|
validate_rtr_report_dir
|
||||||
|
chmod 755 "$HOST_DATA_DIR" "$HOST_DATA_DIR/state" "$HOST_DATA_DIR/runs" "$HOST_DATA_DIR/logs" "$HOST_DATA_DIR/tmp" || true
|
||||||
|
chmod 777 "$HOST_DATA_DIR/prometheus" "$HOST_DATA_DIR/grafana" || true
|
||||||
|
}
|
||||||
|
|
||||||
|
latest_run_dir() {
|
||||||
|
load_env
|
||||||
|
find "$HOST_DATA_DIR/runs" -maxdepth 1 -mindepth 1 -type d -name 'run_*' 2>/dev/null | sort | tail -1
|
||||||
|
}
|
||||||
|
|
||||||
|
latest_success_run_dir() {
|
||||||
|
load_env
|
||||||
|
find "$HOST_DATA_DIR/runs" -maxdepth 2 -type f -path '*/run-summary.json' 2>/dev/null \
|
||||||
|
| while read -r summary; do
|
||||||
|
if jq -e '.status == "success"' "$summary" >/dev/null 2>&1; then
|
||||||
|
dirname "$summary"
|
||||||
|
fi
|
||||||
|
done | sort | tail -1
|
||||||
|
}
|
||||||
|
|
||||||
|
has_success_run() {
|
||||||
|
[[ -n "$(latest_success_run_dir)" ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
print_run_summary() {
|
||||||
|
local run_dir="$1"
|
||||||
|
local summary="$run_dir/run-summary.json"
|
||||||
|
local meta="$run_dir/run-meta.json"
|
||||||
|
local timing="$run_dir/stage-timing.json"
|
||||||
|
local process_time="$run_dir/process-time.txt"
|
||||||
|
local vrps_file="$run_dir/vrps.csv"
|
||||||
|
local vaps_file="$run_dir/vaps.csv"
|
||||||
|
local status="unknown"
|
||||||
|
local sync_mode="unknown"
|
||||||
|
local wall_ms="null"
|
||||||
|
local validation_ms="null"
|
||||||
|
local repo_sync_ms="null"
|
||||||
|
local max_rss_kb="null"
|
||||||
|
local publication_points="null"
|
||||||
|
local vrps="null"
|
||||||
|
local vaps="null"
|
||||||
|
local warnings="null"
|
||||||
|
[[ -f "$summary" ]] || {
|
||||||
|
warn "missing run-summary.json in $run_dir"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
status="$(jq -r '.status // "unknown"' "$summary" 2>/dev/null || echo unknown)"
|
||||||
|
wall_ms="$(jq -r '.wallMs // .wall_ms // "null"' "$summary" 2>/dev/null || echo null)"
|
||||||
|
warnings="$(jq -r '.warningCount // .warnings // "null"' "$summary" 2>/dev/null || echo null)"
|
||||||
|
if [[ -f "$meta" ]]; then
|
||||||
|
sync_mode="$(jq -r '.sync_mode // .syncMode // "unknown"' "$meta" 2>/dev/null || echo unknown)"
|
||||||
|
status="$(jq -r --arg fallback "$status" '.status // $fallback' "$meta" 2>/dev/null || echo "$status")"
|
||||||
|
fi
|
||||||
|
if [[ -f "$timing" ]]; then
|
||||||
|
validation_ms="$(jq -r '.validation_ms // "null"' "$timing" 2>/dev/null || echo null)"
|
||||||
|
repo_sync_ms="$(jq -r '.repo_sync_ms_total // "null"' "$timing" 2>/dev/null || echo null)"
|
||||||
|
publication_points="$(jq -r '.publication_points // "null"' "$timing" 2>/dev/null || echo null)"
|
||||||
|
fi
|
||||||
|
if [[ -f "$process_time" ]]; then
|
||||||
|
max_rss_kb="$(awk -F': ' '/Maximum resident set size/ {print $2; found=1} END {if (!found) print "null"}' "$process_time")"
|
||||||
|
fi
|
||||||
|
if [[ -f "$vrps_file" ]]; then
|
||||||
|
vrps="$(( $(wc -l < "$vrps_file") > 0 ? $(wc -l < "$vrps_file") - 1 : 0 ))"
|
||||||
|
fi
|
||||||
|
if [[ -f "$vaps_file" ]]; then
|
||||||
|
vaps="$(( $(wc -l < "$vaps_file") > 0 ? $(wc -l < "$vaps_file") - 1 : 0 ))"
|
||||||
|
fi
|
||||||
|
jq -n \
|
||||||
|
--arg run "$(basename "$run_dir")" \
|
||||||
|
--arg status "$status" \
|
||||||
|
--arg syncMode "$sync_mode" \
|
||||||
|
--argjson wallMs "$wall_ms" \
|
||||||
|
--argjson validationMs "$validation_ms" \
|
||||||
|
--argjson repoSyncMs "$repo_sync_ms" \
|
||||||
|
--argjson maxRssKb "$max_rss_kb" \
|
||||||
|
--argjson vrps "$vrps" \
|
||||||
|
--argjson vaps "$vaps" \
|
||||||
|
--argjson publicationPoints "$publication_points" \
|
||||||
|
--argjson warnings "$warnings" \
|
||||||
|
'{run:$run,status:$status,syncMode:$syncMode,wallMs:$wallMs,validationMs:$validationMs,repoSyncMs:$repoSyncMs,maxRssKb:$maxRssKb,vrps:$vrps,vaps:$vaps,publicationPoints:$publicationPoints,warnings:$warnings}'
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_for_new_success_run() {
|
||||||
|
local before_latest="$1"
|
||||||
|
local timeout_secs="$2"
|
||||||
|
local start_epoch now run_dir summary meta status meta_status
|
||||||
|
start_epoch="$(date +%s)"
|
||||||
|
while true; do
|
||||||
|
run_dir="$(latest_run_dir || true)"
|
||||||
|
if [[ -n "$run_dir" && "$run_dir" != "$before_latest" ]]; then
|
||||||
|
summary="$run_dir/run-summary.json"
|
||||||
|
meta="$run_dir/run-meta.json"
|
||||||
|
if [[ -f "$summary" ]]; then
|
||||||
|
status="$(jq -r '.status // "unknown"' "$summary" 2>/dev/null || echo unknown)"
|
||||||
|
if [[ "$status" == "success" ]]; then
|
||||||
|
meta_status="unknown"
|
||||||
|
if [[ -f "$meta" ]]; then
|
||||||
|
meta_status="$(jq -r '.status // "unknown"' "$meta" 2>/dev/null || echo unknown)"
|
||||||
|
fi
|
||||||
|
if [[ "$meta_status" == "success" ]]; then
|
||||||
|
print_run_summary "$run_dir" || true
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if [[ "$status" == "failed" || "$status" == "error" ]]; then
|
||||||
|
print_run_summary "$run_dir" || true
|
||||||
|
die "run failed: $run_dir"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
now="$(date +%s)"
|
||||||
|
if (( now - start_epoch > timeout_secs )); then
|
||||||
|
die "timed out waiting for first successful run after ${timeout_secs}s"
|
||||||
|
fi
|
||||||
|
sleep 10
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
docker_compose_available() {
|
||||||
|
docker compose version >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
install_docker_if_missing() {
|
||||||
|
if command -v docker >/dev/null 2>&1 && docker_compose_available && command -v jq >/dev/null 2>&1 && command -v rsync >/dev/null 2>&1 && command -v curl >/dev/null 2>&1; then
|
||||||
|
log "docker and docker compose are already installed"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [[ "${SKIP_DEP_INSTALL:-0}" == "1" ]]; then
|
||||||
|
die "docker/docker compose missing and SKIP_DEP_INSTALL=1"
|
||||||
|
fi
|
||||||
|
if ! command -v apt-get >/dev/null 2>&1; then
|
||||||
|
die "docker/docker compose missing; automatic install currently supports apt-get only"
|
||||||
|
fi
|
||||||
|
log "installing missing runtime packages via apt"
|
||||||
|
apt-get update
|
||||||
|
DEBIAN_FRONTEND=noninteractive apt-get install -y ca-certificates curl jq rsync gzip tar docker.io
|
||||||
|
if ! docker_compose_available; then
|
||||||
|
if apt-cache show docker-compose-v2 >/dev/null 2>&1; then
|
||||||
|
DEBIAN_FRONTEND=noninteractive apt-get install -y docker-compose-v2
|
||||||
|
elif apt-cache show docker-compose-plugin >/dev/null 2>&1; then
|
||||||
|
DEBIAN_FRONTEND=noninteractive apt-get install -y docker-compose-plugin
|
||||||
|
elif apt-cache show docker-compose >/dev/null 2>&1; then
|
||||||
|
DEBIAN_FRONTEND=noninteractive apt-get install -y docker-compose
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
systemctl enable --now docker >/dev/null 2>&1 || true
|
||||||
|
docker_compose_available || die "docker compose is still unavailable after install"
|
||||||
|
}
|
||||||
|
|
||||||
|
load_installer_images() {
|
||||||
|
load_env
|
||||||
|
require_cmd docker
|
||||||
|
shopt -s nullglob
|
||||||
|
local image load_output desired_tag
|
||||||
|
local found=0
|
||||||
|
for image in "$INSTALLER_ROOT"/images/*.tar "$INSTALLER_ROOT"/images/*.tar.gz; do
|
||||||
|
found=1
|
||||||
|
log "loading docker image: $image"
|
||||||
|
if [[ "$image" == *.gz ]]; then
|
||||||
|
load_output="$(gzip -dc "$image" | docker load)"
|
||||||
|
else
|
||||||
|
load_output="$(docker load -i "$image")"
|
||||||
|
fi
|
||||||
|
printf '%s\n' "$load_output"
|
||||||
|
desired_tag=""
|
||||||
|
case "$(basename "$image")" in
|
||||||
|
"${image_tar:-}")
|
||||||
|
desired_tag="${image_tag:-$RPKI_IMAGE}"
|
||||||
|
;;
|
||||||
|
"${metrics_image_tar:-}")
|
||||||
|
desired_tag="${metrics_image:-$METRICS_IMAGE}"
|
||||||
|
;;
|
||||||
|
"${prometheus_image_tar:-}")
|
||||||
|
desired_tag="${prometheus_image:-$PROMETHEUS_IMAGE}"
|
||||||
|
;;
|
||||||
|
"${grafana_image_tar:-}")
|
||||||
|
desired_tag="${grafana_image:-$GRAFANA_IMAGE}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
if [[ -n "$desired_tag" ]]; then
|
||||||
|
ensure_loaded_image_tag "$desired_tag" "$load_output" "$image"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
shopt -u nullglob
|
||||||
|
(( found == 1 )) || warn "no image tar found under $INSTALLER_ROOT/images"
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_loaded_image_tag() {
|
||||||
|
local desired_tag="$1"
|
||||||
|
local load_output="$2"
|
||||||
|
local image_path="$3"
|
||||||
|
local loaded_ref=""
|
||||||
|
local loaded_id=""
|
||||||
|
local desired_id=""
|
||||||
|
loaded_ref="$(printf '%s\n' "$load_output" | awk -F'Loaded image: ' '/Loaded image: / {print $2; exit}')"
|
||||||
|
if [[ -z "$loaded_ref" ]]; then
|
||||||
|
loaded_ref="$(printf '%s\n' "$load_output" | awk -F'Loaded image ID: ' '/Loaded image ID: / {print $2; exit}')"
|
||||||
|
fi
|
||||||
|
[[ -n "$loaded_ref" ]] || die "unable to determine loaded image reference for $image_path"
|
||||||
|
loaded_id="$(docker image inspect --format '{{.Id}}' "$loaded_ref" 2>/dev/null || true)"
|
||||||
|
desired_id="$(docker image inspect --format '{{.Id}}' "$desired_tag" 2>/dev/null || true)"
|
||||||
|
if [[ -n "$loaded_id" && -n "$desired_id" && "$loaded_id" == "$desired_id" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
log "tagging loaded image for package use: source=$loaded_ref target=$desired_tag"
|
||||||
|
docker tag "$loaded_ref" "$desired_tag"
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_binfmt_if_needed() {
|
||||||
|
require_cmd docker
|
||||||
|
load_env
|
||||||
|
local host_arch package_arch
|
||||||
|
host_arch="$(detect_host_arch)"
|
||||||
|
package_arch="$(effective_package_arch)"
|
||||||
|
if [[ "$host_arch" == "$package_arch" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
assert_arch_compatibility
|
||||||
|
log "host arch is $host_arch; enabling binfmt/qemu for package arch $package_arch"
|
||||||
|
docker run --rm --privileged tonistiigi/binfmt --install "$package_arch"
|
||||||
|
}
|
||||||
|
|
||||||
|
verify_runtime_image() {
|
||||||
|
load_env
|
||||||
|
require_cmd docker
|
||||||
|
log "verifying runtime image $RPKI_IMAGE on $RPKI_PLATFORM"
|
||||||
|
verify_image_platform "$RPKI_IMAGE" "$RPKI_PLATFORM" "runtime"
|
||||||
|
verify_image_usage_output "$RPKI_IMAGE" "$RPKI_PLATFORM" "runtime" /opt/ours-rp/bin/rpki --help
|
||||||
|
}
|
||||||
|
|
||||||
|
verify_metrics_image() {
|
||||||
|
load_env
|
||||||
|
require_cmd docker
|
||||||
|
log "verifying metrics image $METRICS_IMAGE on $METRICS_PLATFORM"
|
||||||
|
verify_image_platform "$METRICS_IMAGE" "$METRICS_PLATFORM" "metrics"
|
||||||
|
verify_image_usage_output "$METRICS_IMAGE" "$METRICS_PLATFORM" "metrics" /opt/ours-rp/bin/rpki_artifact_metrics --help
|
||||||
|
}
|
||||||
|
|
||||||
|
verify_image_platform() {
|
||||||
|
local image="$1"
|
||||||
|
local expected_platform="$2"
|
||||||
|
local role="$3"
|
||||||
|
local actual_platform
|
||||||
|
docker image inspect "$image" >/dev/null
|
||||||
|
actual_platform="$(docker image inspect --format '{{.Os}}/{{.Architecture}}' "$image" 2>/dev/null || echo unknown)"
|
||||||
|
[[ "$actual_platform" == "$expected_platform" ]] || die "$role image platform mismatch: image=$image expected=$expected_platform actual=$actual_platform"
|
||||||
|
}
|
||||||
|
|
||||||
|
verify_image_usage_output() {
|
||||||
|
local image="$1"
|
||||||
|
local expected_platform="$2"
|
||||||
|
local role="$3"
|
||||||
|
shift 3
|
||||||
|
local help_path
|
||||||
|
local status
|
||||||
|
help_path="$(mktemp "${TMPDIR:-/tmp}/ours-rp-image-help.XXXXXX")"
|
||||||
|
set +e
|
||||||
|
docker run --rm --platform "$expected_platform" "$image" "$@" >"$help_path" 2>&1
|
||||||
|
status=$?
|
||||||
|
set -e
|
||||||
|
if [[ "$status" != "0" && "$status" != "1" ]]; then
|
||||||
|
cat "$help_path" >&2 || true
|
||||||
|
rm -f "$help_path"
|
||||||
|
die "$role image command failed: image=$image platform=$expected_platform exit=$status"
|
||||||
|
fi
|
||||||
|
grep -q '^Usage' "$help_path" || {
|
||||||
|
cat "$help_path" >&2 || true
|
||||||
|
rm -f "$help_path"
|
||||||
|
die "$role image did not emit usage output: image=$image platform=$expected_platform exit=$status"
|
||||||
|
}
|
||||||
|
head -5 "$help_path" || true
|
||||||
|
rm -f "$help_path"
|
||||||
|
}
|
||||||
|
|
||||||
|
verify_monitor_images() {
|
||||||
|
load_env
|
||||||
|
require_cmd docker
|
||||||
|
verify_image_platform "$PROMETHEUS_IMAGE" "$MONITOR_PLATFORM" "prometheus"
|
||||||
|
verify_image_platform "$GRAFANA_IMAGE" "$MONITOR_PLATFORM" "grafana"
|
||||||
|
}
|
||||||
|
|
||||||
|
endpoint_ok() {
|
||||||
|
local url="$1"
|
||||||
|
curl -fsS --max-time 5 "$url" >/dev/null 2>&1
|
||||||
|
}
|
||||||
41
deploy/docker-installer/self-check.sh
Executable file
41
deploy/docker-installer/self-check.sh
Executable file
@@ -0,0 +1,41 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
# shellcheck source=scripts/common.sh
|
||||||
|
source "$SCRIPT_DIR/scripts/common.sh"
|
||||||
|
|
||||||
|
QUICK=0
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--quick)
|
||||||
|
QUICK=1
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
echo "Usage: ./self-check.sh [--quick]"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
die "unknown option: $1"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
load_env
|
||||||
|
assert_arch_compatibility
|
||||||
|
require_cmd docker
|
||||||
|
require_cmd jq
|
||||||
|
docker compose version >/dev/null
|
||||||
|
[[ -f "$COMPOSE_FILE" ]] || die "missing compose file"
|
||||||
|
[[ -f "$ENV_FILE" ]] || die "missing .env"
|
||||||
|
create_data_dirs
|
||||||
|
[[ -w "$HOST_DATA_DIR" ]] || die "data dir is not writable: $HOST_DATA_DIR"
|
||||||
|
compose_cmd --profile core --profile sidecar --profile monitor config >/dev/null
|
||||||
|
verify_image_platform "$RPKI_IMAGE" "$RPKI_PLATFORM" "runtime"
|
||||||
|
verify_image_platform "$METRICS_IMAGE" "$METRICS_PLATFORM" "metrics"
|
||||||
|
verify_monitor_images
|
||||||
|
if [[ "$QUICK" == "0" ]]; then
|
||||||
|
verify_runtime_image
|
||||||
|
verify_metrics_image
|
||||||
|
fi
|
||||||
|
log "self-check ok"
|
||||||
59
deploy/docker-installer/start.sh
Executable file
59
deploy/docker-installer/start.sh
Executable file
@@ -0,0 +1,59 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
# shellcheck source=scripts/common.sh
|
||||||
|
source "$SCRIPT_DIR/scripts/common.sh"
|
||||||
|
|
||||||
|
WAIT_FIRST_RUN=1
|
||||||
|
TIMEOUT_SECS=""
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'USAGE'
|
||||||
|
Usage: ./start.sh [--no-wait-first-run] [--timeout-secs N]
|
||||||
|
|
||||||
|
Start ours RP. If no successful run exists, wait for the first snapshot to succeed
|
||||||
|
before starting metrics, Prometheus and Grafana.
|
||||||
|
USAGE
|
||||||
|
}
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--no-wait-first-run)
|
||||||
|
WAIT_FIRST_RUN=0
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--timeout-secs)
|
||||||
|
TIMEOUT_SECS="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
die "unknown option: $1"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
load_env
|
||||||
|
assert_arch_compatibility
|
||||||
|
create_data_dirs
|
||||||
|
timeout_secs="${TIMEOUT_SECS:-$FIRST_RUN_WAIT_TIMEOUT_SECS}"
|
||||||
|
before_latest="$(latest_run_dir || true)"
|
||||||
|
had_success=0
|
||||||
|
if has_success_run; then
|
||||||
|
had_success=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "starting core soak service"
|
||||||
|
compose_cmd --profile core up -d ours-rp-soak
|
||||||
|
|
||||||
|
if [[ "$had_success" == "0" && "$WAIT_FIRST_RUN" == "1" ]]; then
|
||||||
|
log "no previous successful run found; waiting for first run timeout=${timeout_secs}s"
|
||||||
|
wait_for_new_success_run "$before_latest" "$timeout_secs"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "starting metrics and monitor services"
|
||||||
|
compose_cmd --profile sidecar --profile monitor up -d artifact-metrics prometheus grafana
|
||||||
|
"$SCRIPT_DIR/status.sh" --brief || true
|
||||||
70
deploy/docker-installer/status.sh
Executable file
70
deploy/docker-installer/status.sh
Executable file
@@ -0,0 +1,70 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
# shellcheck source=scripts/common.sh
|
||||||
|
source "$SCRIPT_DIR/scripts/common.sh"
|
||||||
|
|
||||||
|
BRIEF=0
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--brief)
|
||||||
|
BRIEF=1
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
echo "Usage: ./status.sh [--brief]"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
die "unknown option: $1"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
load_env
|
||||||
|
host_arch="$(detect_host_arch)"
|
||||||
|
mode="$(arch_mode)"
|
||||||
|
echo "installer_root=$INSTALLER_ROOT"
|
||||||
|
echo "manifest_file=$MANIFEST_FILE"
|
||||||
|
echo "host_data_dir=$HOST_DATA_DIR"
|
||||||
|
echo "host_arch=$host_arch"
|
||||||
|
echo "package_arch=$PACKAGE_ARCH"
|
||||||
|
echo "package_platform=$PACKAGE_PLATFORM"
|
||||||
|
echo "allow_cross_arch=$ALLOW_CROSS_ARCH"
|
||||||
|
echo "arch_mode=$mode"
|
||||||
|
echo "runtime_image=$RPKI_IMAGE"
|
||||||
|
echo "runtime_platform=$RPKI_PLATFORM"
|
||||||
|
echo "metrics_image=$METRICS_IMAGE"
|
||||||
|
echo "metrics_platform=$METRICS_PLATFORM"
|
||||||
|
echo "rtr_report_dir=$RTR_REPORT_DIR"
|
||||||
|
echo "rtr_report_container_dir=$RTR_REPORT_CONTAINER_DIR"
|
||||||
|
echo "monitor_platform=$MONITOR_PLATFORM"
|
||||||
|
echo "rirs=${RIRS:-}"
|
||||||
|
echo "max_runs=${MAX_RUNS:-}"
|
||||||
|
echo "interval_secs=${INTERVAL_SECS:-}"
|
||||||
|
echo "periodic_snapshot_reset=${PERIODIC_SNAPSHOT_RESET:-0}"
|
||||||
|
echo "periodic_snapshot_max_deltas=${PERIODIC_SNAPSHOT_MAX_DELTAS:-100}"
|
||||||
|
echo
|
||||||
|
if command -v docker >/dev/null 2>&1; then
|
||||||
|
docker version --format 'docker={{.Server.Version}}' 2>/dev/null || echo "docker=unavailable"
|
||||||
|
docker compose version 2>/dev/null || true
|
||||||
|
compose_cmd --profile core --profile sidecar --profile monitor ps || true
|
||||||
|
else
|
||||||
|
echo "docker=missing"
|
||||||
|
fi
|
||||||
|
echo
|
||||||
|
df -h "$HOST_DATA_DIR" 2>/dev/null || true
|
||||||
|
echo
|
||||||
|
latest="$(latest_run_dir || true)"
|
||||||
|
if [[ -n "$latest" ]]; then
|
||||||
|
echo "latest_run=$latest"
|
||||||
|
print_run_summary "$latest" || true
|
||||||
|
else
|
||||||
|
echo "latest_run=none"
|
||||||
|
fi
|
||||||
|
if [[ "$BRIEF" == "0" ]]; then
|
||||||
|
echo
|
||||||
|
endpoint_ok "http://127.0.0.1:${METRICS_PORT:-9556}/metrics" && echo "metrics=ok" || echo "metrics=unavailable"
|
||||||
|
endpoint_ok "http://127.0.0.1:${PROMETHEUS_PORT:-9090}/-/ready" && echo "prometheus=ok" || echo "prometheus=unavailable"
|
||||||
|
endpoint_ok "http://127.0.0.1:${GRAFANA_PORT:-3000}/api/health" && echo "grafana=ok" || echo "grafana=unavailable"
|
||||||
|
fi
|
||||||
7
deploy/docker-installer/stop.sh
Executable file
7
deploy/docker-installer/stop.sh
Executable file
@@ -0,0 +1,7 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
# shellcheck source=scripts/common.sh
|
||||||
|
source "$SCRIPT_DIR/scripts/common.sh"
|
||||||
|
load_env
|
||||||
|
compose_cmd --profile core --profile sidecar --profile monitor stop "$@"
|
||||||
32
deploy/docker-installer/uninstall.sh
Executable file
32
deploy/docker-installer/uninstall.sh
Executable file
@@ -0,0 +1,32 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
# shellcheck source=scripts/common.sh
|
||||||
|
source "$SCRIPT_DIR/scripts/common.sh"
|
||||||
|
|
||||||
|
PURGE_DATA=0
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--purge-data)
|
||||||
|
PURGE_DATA=1
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
echo "Usage: ./uninstall.sh [--purge-data]"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
die "unknown option: $1"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
load_env
|
||||||
|
compose_cmd --profile core --profile sidecar --profile monitor down --remove-orphans || true
|
||||||
|
if [[ "$PURGE_DATA" == "1" ]]; then
|
||||||
|
[[ "$HOST_DATA_DIR" == "/" || -z "$HOST_DATA_DIR" ]] && die "refuse to purge unsafe HOST_DATA_DIR=$HOST_DATA_DIR"
|
||||||
|
rm -rf "$HOST_DATA_DIR"
|
||||||
|
log "purged data dir $HOST_DATA_DIR"
|
||||||
|
else
|
||||||
|
log "containers removed; data kept at $HOST_DATA_DIR"
|
||||||
|
fi
|
||||||
110
deploy/docker-installer/upgrade.sh
Executable file
110
deploy/docker-installer/upgrade.sh
Executable file
@@ -0,0 +1,110 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
# shellcheck source=scripts/common.sh
|
||||||
|
source "$SCRIPT_DIR/scripts/common.sh"
|
||||||
|
|
||||||
|
REUSE_ENV_FROM=""
|
||||||
|
UPDATE_PACKAGE_IMAGE=1
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'USAGE'
|
||||||
|
Usage: ./upgrade.sh [--reuse-env-from /path/to/.env] [--keep-reused-image]
|
||||||
|
|
||||||
|
By default, --reuse-env-from creates the new .env from this package's
|
||||||
|
.env.example first, then overlays existing user settings from the old .env.
|
||||||
|
Image tags are intentionally kept from the new package so the upgraded service
|
||||||
|
actually runs the new packaged runtime and monitor images. Use
|
||||||
|
--keep-reused-image only when you intentionally want to keep previous image tags.
|
||||||
|
USAGE
|
||||||
|
}
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--reuse-env-from)
|
||||||
|
REUSE_ENV_FROM="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--keep-reused-image)
|
||||||
|
UPDATE_PACKAGE_IMAGE=0
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
die "unknown option: $1"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
env_get_key() {
|
||||||
|
local env_path="$1"
|
||||||
|
local key="$2"
|
||||||
|
awk -F= -v key="$key" '$1 == key {sub(/^[^=]*=/, ""); print; exit}' "$env_path"
|
||||||
|
}
|
||||||
|
|
||||||
|
env_set_key() {
|
||||||
|
local env_path="$1"
|
||||||
|
local key="$2"
|
||||||
|
local value="$3"
|
||||||
|
local tmp_env
|
||||||
|
if grep -q "^${key}=" "$env_path"; then
|
||||||
|
tmp_env="$(mktemp)"
|
||||||
|
awk -v key="$key" -v value="$value" '
|
||||||
|
BEGIN { done=0 }
|
||||||
|
$0 ~ "^" key "=" { print key "=" value; done=1; next }
|
||||||
|
{ print }
|
||||||
|
END { if (!done) print key "=" value }
|
||||||
|
' "$env_path" > "$tmp_env"
|
||||||
|
mv "$tmp_env" "$env_path"
|
||||||
|
else
|
||||||
|
printf '%s=%s\n' "$key" "$value" >> "$env_path"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
overlay_reused_env() {
|
||||||
|
local source_env="$1"
|
||||||
|
local target_env="$2"
|
||||||
|
local key
|
||||||
|
local value
|
||||||
|
while IFS='=' read -r key _; do
|
||||||
|
[[ -n "$key" ]] || continue
|
||||||
|
[[ "$key" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]] || continue
|
||||||
|
case "$key" in
|
||||||
|
PACKAGE_ARCH|PACKAGE_PLATFORM|RPKI_PLATFORM|METRICS_PLATFORM|MONITOR_PLATFORM)
|
||||||
|
continue
|
||||||
|
;;
|
||||||
|
RPKI_IMAGE|METRICS_IMAGE|PROMETHEUS_IMAGE|GRAFANA_IMAGE)
|
||||||
|
[[ "$UPDATE_PACKAGE_IMAGE" == "0" ]] || continue
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
value="$(env_get_key "$source_env" "$key")"
|
||||||
|
env_set_key "$target_env" "$key" "$value"
|
||||||
|
done < <(grep -E '^[A-Za-z_][A-Za-z0-9_]*=' "$source_env" || true)
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ -n "$REUSE_ENV_FROM" ]]; then
|
||||||
|
[[ -f "$REUSE_ENV_FROM" ]] || die "missing reuse env file: $REUSE_ENV_FROM"
|
||||||
|
if [[ ! -f "$ENV_FILE" ]]; then
|
||||||
|
[[ -f "$ENV_EXAMPLE" ]] || die "missing $ENV_EXAMPLE"
|
||||||
|
cp "$ENV_EXAMPLE" "$ENV_FILE"
|
||||||
|
overlay_reused_env "$REUSE_ENV_FROM" "$ENV_FILE"
|
||||||
|
log "created new package env from .env.example and overlaid user settings from $REUSE_ENV_FROM"
|
||||||
|
else
|
||||||
|
log "keeping existing env at $ENV_FILE; reuse source ignored: $REUSE_ENV_FROM"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
load_env
|
||||||
|
assert_arch_compatibility
|
||||||
|
create_data_dirs
|
||||||
|
install_docker_if_missing
|
||||||
|
load_installer_images
|
||||||
|
ensure_binfmt_if_needed
|
||||||
|
verify_runtime_image
|
||||||
|
verify_metrics_image
|
||||||
|
verify_monitor_images
|
||||||
|
compose_cmd --profile core --profile sidecar --profile monitor up -d --force-recreate
|
||||||
|
"$SCRIPT_DIR/status.sh" --brief || true
|
||||||
110
docker/ours-rp-metrics.Dockerfile
Normal file
110
docker/ours-rp-metrics.Dockerfile
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
ARG BUILDER_IMAGE=rust:1-bookworm
|
||||||
|
ARG RUNTIME_IMAGE=debian:bookworm-slim
|
||||||
|
|
||||||
|
FROM --platform=$BUILDPLATFORM ${BUILDER_IMAGE} AS builder
|
||||||
|
|
||||||
|
ARG BUILDARCH
|
||||||
|
ARG TARGETARCH
|
||||||
|
ARG TARGETPLATFORM
|
||||||
|
|
||||||
|
WORKDIR /src
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends \
|
||||||
|
build-essential \
|
||||||
|
ca-certificates \
|
||||||
|
clang \
|
||||||
|
cmake \
|
||||||
|
git \
|
||||||
|
libclang-dev \
|
||||||
|
make \
|
||||||
|
perl \
|
||||||
|
pkg-config \
|
||||||
|
python3 \
|
||||||
|
&& if [ "$TARGETARCH" != "$BUILDARCH" ]; then \
|
||||||
|
case "$TARGETARCH" in \
|
||||||
|
arm64) \
|
||||||
|
apt-get install -y --no-install-recommends \
|
||||||
|
g++-aarch64-linux-gnu \
|
||||||
|
gcc-aarch64-linux-gnu \
|
||||||
|
libc6-dev-arm64-cross \
|
||||||
|
;; \
|
||||||
|
amd64) \
|
||||||
|
apt-get install -y --no-install-recommends \
|
||||||
|
g++-x86-64-linux-gnu \
|
||||||
|
gcc-x86-64-linux-gnu \
|
||||||
|
libc6-dev-amd64-cross \
|
||||||
|
;; \
|
||||||
|
*) \
|
||||||
|
echo "unsupported TARGETARCH=$TARGETARCH" >&2; exit 2 \
|
||||||
|
;; \
|
||||||
|
esac; \
|
||||||
|
fi \
|
||||||
|
&& rm -rf /var/lib/apt/lists/* \
|
||||||
|
&& case "$TARGETARCH" in \
|
||||||
|
amd64|arm64) ;; \
|
||||||
|
*) echo "unsupported TARGETARCH=$TARGETARCH" >&2; exit 2 ;; \
|
||||||
|
esac
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
||||||
|
--mount=type=cache,target=/usr/local/cargo/git \
|
||||||
|
--mount=type=cache,target=/src/target \
|
||||||
|
set -eux; \
|
||||||
|
case "$TARGETARCH" in \
|
||||||
|
amd64) target_triple=x86_64-unknown-linux-gnu ;; \
|
||||||
|
arm64) target_triple=aarch64-unknown-linux-gnu ;; \
|
||||||
|
*) echo "unsupported TARGETARCH=$TARGETARCH" >&2; exit 2 ;; \
|
||||||
|
esac; \
|
||||||
|
rustup target add "$target_triple"; \
|
||||||
|
if [ "$TARGETARCH" != "$BUILDARCH" ]; then \
|
||||||
|
case "$TARGETARCH" in \
|
||||||
|
arm64) \
|
||||||
|
export \
|
||||||
|
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc \
|
||||||
|
CC_aarch64_unknown_linux_gnu=aarch64-linux-gnu-gcc \
|
||||||
|
CXX_aarch64_unknown_linux_gnu=aarch64-linux-gnu-g++ \
|
||||||
|
AR_aarch64_unknown_linux_gnu=aarch64-linux-gnu-ar \
|
||||||
|
PKG_CONFIG_ALLOW_CROSS=1 \
|
||||||
|
;; \
|
||||||
|
amd64) \
|
||||||
|
export \
|
||||||
|
CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER=x86_64-linux-gnu-gcc \
|
||||||
|
CC_x86_64_unknown_linux_gnu=x86_64-linux-gnu-gcc \
|
||||||
|
CXX_x86_64_unknown_linux_gnu=x86_64-linux-gnu-g++ \
|
||||||
|
AR_x86_64_unknown_linux_gnu=x86_64-linux-gnu-ar \
|
||||||
|
PKG_CONFIG_ALLOW_CROSS=1 \
|
||||||
|
;; \
|
||||||
|
*) \
|
||||||
|
echo "unsupported TARGETARCH=$TARGETARCH" >&2; exit 2 \
|
||||||
|
;; \
|
||||||
|
esac; \
|
||||||
|
fi; \
|
||||||
|
cargo build --release --target "$target_triple" --bin rpki_artifact_metrics; \
|
||||||
|
mkdir -p /build-out/bin; \
|
||||||
|
cp "target/$target_triple/release/rpki_artifact_metrics" /build-out/bin/
|
||||||
|
|
||||||
|
FROM --platform=$TARGETPLATFORM ${RUNTIME_IMAGE} AS runtime
|
||||||
|
|
||||||
|
LABEL org.opencontainers.image.title="ours-rp-metrics" \
|
||||||
|
org.opencontainers.image.description="Ours RP artifact metrics image for multi-arch Docker Compose deployment"
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends \
|
||||||
|
ca-certificates \
|
||||||
|
tzdata \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
WORKDIR /opt/ours-rp
|
||||||
|
|
||||||
|
COPY --from=builder /build-out/bin/ /opt/ours-rp/bin/
|
||||||
|
|
||||||
|
RUN chmod +x /opt/ours-rp/bin/* \
|
||||||
|
&& mkdir -p /var/lib/ours-rp/state /var/lib/ours-rp/runs /var/lib/ours-rp/logs
|
||||||
|
|
||||||
|
ENV RUN_ROOT=/var/lib/ours-rp \
|
||||||
|
BIN_DIR=/opt/ours-rp/bin \
|
||||||
|
RUST_BACKTRACE=1
|
||||||
|
|
||||||
|
VOLUME ["/var/lib/ours-rp"]
|
||||||
138
docker/ours-rp-runtime.Dockerfile
Normal file
138
docker/ours-rp-runtime.Dockerfile
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
ARG BUILDER_IMAGE=rust:1-bookworm
|
||||||
|
ARG RUNTIME_IMAGE=debian:bookworm-slim
|
||||||
|
|
||||||
|
FROM --platform=$BUILDPLATFORM ${BUILDER_IMAGE} AS builder
|
||||||
|
|
||||||
|
ARG BUILDARCH
|
||||||
|
ARG TARGETARCH
|
||||||
|
ARG TARGETPLATFORM
|
||||||
|
|
||||||
|
WORKDIR /src
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends \
|
||||||
|
build-essential \
|
||||||
|
ca-certificates \
|
||||||
|
clang \
|
||||||
|
cmake \
|
||||||
|
git \
|
||||||
|
libclang-dev \
|
||||||
|
make \
|
||||||
|
perl \
|
||||||
|
pkg-config \
|
||||||
|
python3 \
|
||||||
|
&& if [ "$TARGETARCH" != "$BUILDARCH" ]; then \
|
||||||
|
case "$TARGETARCH" in \
|
||||||
|
arm64) \
|
||||||
|
apt-get install -y --no-install-recommends \
|
||||||
|
g++-aarch64-linux-gnu \
|
||||||
|
gcc-aarch64-linux-gnu \
|
||||||
|
libc6-dev-arm64-cross \
|
||||||
|
;; \
|
||||||
|
amd64) \
|
||||||
|
apt-get install -y --no-install-recommends \
|
||||||
|
g++-x86-64-linux-gnu \
|
||||||
|
gcc-x86-64-linux-gnu \
|
||||||
|
libc6-dev-amd64-cross \
|
||||||
|
;; \
|
||||||
|
*) \
|
||||||
|
echo "unsupported TARGETARCH=$TARGETARCH" >&2; exit 2 \
|
||||||
|
;; \
|
||||||
|
esac; \
|
||||||
|
fi \
|
||||||
|
&& rm -rf /var/lib/apt/lists/* \
|
||||||
|
&& case "$TARGETARCH" in \
|
||||||
|
amd64|arm64) ;; \
|
||||||
|
*) echo "unsupported TARGETARCH=$TARGETARCH" >&2; exit 2 ;; \
|
||||||
|
esac
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
||||||
|
--mount=type=cache,target=/usr/local/cargo/git \
|
||||||
|
--mount=type=cache,target=/src/target \
|
||||||
|
set -eux; \
|
||||||
|
case "$TARGETARCH" in \
|
||||||
|
amd64) target_triple=x86_64-unknown-linux-gnu ;; \
|
||||||
|
arm64) target_triple=aarch64-unknown-linux-gnu ;; \
|
||||||
|
*) echo "unsupported TARGETARCH=$TARGETARCH" >&2; exit 2 ;; \
|
||||||
|
esac; \
|
||||||
|
rustup target add "$target_triple"; \
|
||||||
|
if [ "$TARGETARCH" != "$BUILDARCH" ]; then \
|
||||||
|
case "$TARGETARCH" in \
|
||||||
|
arm64) \
|
||||||
|
export \
|
||||||
|
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc \
|
||||||
|
CC_aarch64_unknown_linux_gnu=aarch64-linux-gnu-gcc \
|
||||||
|
CXX_aarch64_unknown_linux_gnu=aarch64-linux-gnu-g++ \
|
||||||
|
AR_aarch64_unknown_linux_gnu=aarch64-linux-gnu-ar \
|
||||||
|
PKG_CONFIG_ALLOW_CROSS=1 \
|
||||||
|
;; \
|
||||||
|
amd64) \
|
||||||
|
export \
|
||||||
|
CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER=x86_64-linux-gnu-gcc \
|
||||||
|
CC_x86_64_unknown_linux_gnu=x86_64-linux-gnu-gcc \
|
||||||
|
CXX_x86_64_unknown_linux_gnu=x86_64-linux-gnu-g++ \
|
||||||
|
AR_x86_64_unknown_linux_gnu=x86_64-linux-gnu-ar \
|
||||||
|
PKG_CONFIG_ALLOW_CROSS=1 \
|
||||||
|
;; \
|
||||||
|
*) \
|
||||||
|
echo "unsupported TARGETARCH=$TARGETARCH" >&2; exit 2 \
|
||||||
|
;; \
|
||||||
|
esac; \
|
||||||
|
fi; \
|
||||||
|
cargo build --release --target "$target_triple" \
|
||||||
|
--bin rpki \
|
||||||
|
--bin rpki_daemon \
|
||||||
|
--bin db_stats; \
|
||||||
|
mkdir -p /build-out/bin; \
|
||||||
|
cp \
|
||||||
|
"target/$target_triple/release/rpki" \
|
||||||
|
"target/$target_triple/release/rpki_daemon" \
|
||||||
|
"target/$target_triple/release/db_stats" \
|
||||||
|
/build-out/bin/
|
||||||
|
|
||||||
|
FROM --platform=$TARGETPLATFORM ${RUNTIME_IMAGE} AS runtime
|
||||||
|
|
||||||
|
LABEL org.opencontainers.image.title="ours-rp-runtime" \
|
||||||
|
org.opencontainers.image.description="Ours RP runtime image for multi-arch Docker Compose deployment"
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends \
|
||||||
|
bash \
|
||||||
|
ca-certificates \
|
||||||
|
coreutils \
|
||||||
|
curl \
|
||||||
|
findutils \
|
||||||
|
iputils-ping \
|
||||||
|
jq \
|
||||||
|
procps \
|
||||||
|
python3 \
|
||||||
|
rsync \
|
||||||
|
time \
|
||||||
|
tzdata \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
WORKDIR /opt/ours-rp
|
||||||
|
|
||||||
|
COPY --from=builder /build-out/bin/ /opt/ours-rp/bin/
|
||||||
|
COPY scripts/soak/run_soak.sh /opt/ours-rp/run_soak.sh
|
||||||
|
COPY scripts/soak/portable-soak.env.example /opt/ours-rp/portable-soak.env.example
|
||||||
|
COPY tests/fixtures/tal/ /opt/ours-rp/fixtures/tal/
|
||||||
|
COPY tests/fixtures/ta/ /opt/ours-rp/fixtures/ta/
|
||||||
|
COPY fixtures/live_20260619/tal/ /opt/ours-rp/fixtures/live_20260619/tal/
|
||||||
|
COPY fixtures/live_20260619/ta/ /opt/ours-rp/fixtures/live_20260619/ta/
|
||||||
|
|
||||||
|
RUN chmod +x /opt/ours-rp/run_soak.sh /opt/ours-rp/bin/* \
|
||||||
|
&& mkdir -p /var/lib/ours-rp/state /var/lib/ours-rp/runs /var/lib/ours-rp/logs /var/lib/ours-rp/tmp
|
||||||
|
|
||||||
|
ENV PACKAGE_ROOT=/opt/ours-rp \
|
||||||
|
ENV_FILE=/opt/ours-rp/.env \
|
||||||
|
RUN_ROOT=/var/lib/ours-rp \
|
||||||
|
BIN_DIR=/opt/ours-rp/bin \
|
||||||
|
FIXTURE_DIR=/opt/ours-rp/fixtures \
|
||||||
|
RUST_BACKTRACE=1
|
||||||
|
|
||||||
|
VOLUME ["/var/lib/ours-rp"]
|
||||||
|
|
||||||
|
CMD ["/opt/ours-rp/run_soak.sh"]
|
||||||
80
fixtures/live_20260619/manifest.json
Normal file
80
fixtures/live_20260619/manifest.json
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
{
|
||||||
|
"created_at_utc": "2026-06-19T08:08:03Z",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"rir": "afrinic",
|
||||||
|
"ta_bytes": 1216,
|
||||||
|
"ta_download": "200 1216 1.351147",
|
||||||
|
"ta_elapsed_s": 1.227,
|
||||||
|
"ta_path": "rpki_2/rpki/fixtures/live_20260619/ta/afrinic-ta.cer",
|
||||||
|
"ta_sha256": "43a26fd28bafb9398e5b2ab19e036b450bd04f4973a7f5ad151cebdee0edac36",
|
||||||
|
"ta_uri": "https://rpki.afrinic.net/repository/AfriNIC.cer",
|
||||||
|
"tal_bytes": 496,
|
||||||
|
"tal_download": "200 496 1.361326",
|
||||||
|
"tal_elapsed_s": 1.238,
|
||||||
|
"tal_path": "rpki_2/rpki/fixtures/live_20260619/tal/afrinic.tal",
|
||||||
|
"tal_sha256": "2838ef30ea27ce5705abf5f5adb131d8c35b1f50858338a2f3c84bb207c2fa35",
|
||||||
|
"tal_url": "https://rpki.afrinic.net/tal/afrinic.tal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rir": "apnic",
|
||||||
|
"ta_bytes": 1222,
|
||||||
|
"ta_download": "200 1222 1.012321",
|
||||||
|
"ta_elapsed_s": 0.921,
|
||||||
|
"ta_path": "rpki_2/rpki/fixtures/live_20260619/ta/apnic-ta.cer",
|
||||||
|
"ta_sha256": "2014230ad49b2777ac2bde0948ddfa4b8f207114c549e26d755de88c3593e3af",
|
||||||
|
"ta_uri": "https://rpki.apnic.net/repository/apnic-rpki-root-iana-origin.cer",
|
||||||
|
"tal_bytes": 532,
|
||||||
|
"tal_download": "200 466 1.007155",
|
||||||
|
"tal_elapsed_s": 0.917,
|
||||||
|
"tal_path": "rpki_2/rpki/fixtures/live_20260619/tal/apnic.tal",
|
||||||
|
"tal_sha256": "472e551f7c551c2e999e582b7c9437d3bee4900fe53afff62aeb28d4940ade94",
|
||||||
|
"tal_url": "https://tal.apnic.net/apnic.tal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rir": "arin",
|
||||||
|
"ta_bytes": 1143,
|
||||||
|
"ta_download": "200 1143 0.816714",
|
||||||
|
"ta_elapsed_s": 0.743,
|
||||||
|
"ta_path": "rpki_2/rpki/fixtures/live_20260619/ta/arin-ta.cer",
|
||||||
|
"ta_sha256": "5b3c2f6f04abd19261084487a43c156f778b0b8926d63801d48c7a93ed349492",
|
||||||
|
"ta_uri": "https://rrdp.arin.net/arin-rpki-ta.cer",
|
||||||
|
"tal_bytes": 1258,
|
||||||
|
"tal_download": "200 1258 0.848581",
|
||||||
|
"tal_elapsed_s": 0.774,
|
||||||
|
"tal_path": "rpki_2/rpki/fixtures/live_20260619/tal/arin.tal",
|
||||||
|
"tal_sha256": "1f8bdb03bcc30a3b8e11fd9a87102fba250c22137a3c8baa9c81b139cb412639",
|
||||||
|
"tal_url": "https://www.arin.net/resources/manage/rpki/arin.tal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rir": "lacnic",
|
||||||
|
"ta_bytes": 1166,
|
||||||
|
"ta_download": "200 1166 1.025273",
|
||||||
|
"ta_elapsed_s": 0.932,
|
||||||
|
"ta_path": "rpki_2/rpki/fixtures/live_20260619/ta/lacnic-ta.cer",
|
||||||
|
"ta_sha256": "f44bc51008fd6998de7597b72a79b07bf3ebcb0f14daa7c7c022c0e9d66e0ad0",
|
||||||
|
"ta_uri": "https://rrdp.lacnic.net/ta/rta-lacnic-rpki.cer",
|
||||||
|
"tal_bytes": 502,
|
||||||
|
"tal_download": "200 502 1.729122",
|
||||||
|
"tal_elapsed_s": 1.565,
|
||||||
|
"tal_path": "rpki_2/rpki/fixtures/live_20260619/tal/lacnic.tal",
|
||||||
|
"tal_sha256": "d44bb9394ab009c8b53e5efebf2a1c9450bab61a27efe00de5a3e4587a3a2f6a",
|
||||||
|
"tal_url": "https://www.lacnic.net/innovaportal/file/4983/1/lacnic.tal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rir": "ripe",
|
||||||
|
"ta_bytes": 1036,
|
||||||
|
"ta_download": "200 1036 1.060992",
|
||||||
|
"ta_elapsed_s": 4.536,
|
||||||
|
"ta_path": "rpki_2/rpki/fixtures/live_20260619/ta/ripe-ncc-ta.cer",
|
||||||
|
"ta_sha256": "3e3f7e4efc8d0cea03d9cc1fde6e168b45c26d7b3272e14abd8da4871886e539",
|
||||||
|
"ta_uri": "https://rpki.ripe.net/ta/ripe-ncc-ta.cer",
|
||||||
|
"tal_bytes": 482,
|
||||||
|
"tal_download": "200 482 1.092082",
|
||||||
|
"tal_elapsed_s": 0.992,
|
||||||
|
"tal_path": "rpki_2/rpki/fixtures/live_20260619/tal/ripe-ncc.tal",
|
||||||
|
"tal_sha256": "59ca27ef93f23682749fcefe7c6d70fbc723343549ff9e4d3996acaff79817fb",
|
||||||
|
"tal_url": "https://tal.rpki.ripe.net/ripe-ncc.tal"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
fixtures/live_20260619/ta/afrinic-ta.cer
Normal file
BIN
fixtures/live_20260619/ta/afrinic-ta.cer
Normal file
Binary file not shown.
BIN
fixtures/live_20260619/ta/apnic-ta.cer
Normal file
BIN
fixtures/live_20260619/ta/apnic-ta.cer
Normal file
Binary file not shown.
BIN
fixtures/live_20260619/ta/arin-ta.cer
Normal file
BIN
fixtures/live_20260619/ta/arin-ta.cer
Normal file
Binary file not shown.
BIN
fixtures/live_20260619/ta/lacnic-ta.cer
Normal file
BIN
fixtures/live_20260619/ta/lacnic-ta.cer
Normal file
Binary file not shown.
BIN
fixtures/live_20260619/ta/ripe-ncc-ta.cer
Normal file
BIN
fixtures/live_20260619/ta/ripe-ncc-ta.cer
Normal file
Binary file not shown.
10
fixtures/live_20260619/tal/afrinic.tal
Normal file
10
fixtures/live_20260619/tal/afrinic.tal
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
rsync://rpki.afrinic.net/repository/AfriNIC.cer
|
||||||
|
https://rpki.afrinic.net/repository/AfriNIC.cer
|
||||||
|
|
||||||
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxsAqAhWIO+ON2Ef9oRDM
|
||||||
|
pKxv+AfmSLIdLWJtjrvUyDxJPBjgR+kVrOHUeTaujygFUp49tuN5H2C1rUuQavTH
|
||||||
|
vve6xNF5fU3OkTcqEzMOZy+ctkbde2SRMVdvbO22+TH9gNhKDc9l7Vu01qU4LeJH
|
||||||
|
k3X0f5uu5346YrGAOSv6AaYBXVgXxa0s9ZvgqFpim50pReQe/WI3QwFKNgpPzfQL
|
||||||
|
6Y7fDPYdYaVOXPXSKtx7P4s4KLA/ZWmRL/bobw/i2fFviAGhDrjqqqum+/9w1hEl
|
||||||
|
L/vqihVnV18saKTnLvkItA/Bf5i11Yhw2K7qv573YWxyuqCknO/iYLTR1DToBZcZ
|
||||||
|
UQIDAQAB
|
||||||
10
fixtures/live_20260619/tal/apnic-rfc7730-https.tal
Normal file
10
fixtures/live_20260619/tal/apnic-rfc7730-https.tal
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
https://rpki.apnic.net/repository/apnic-rpki-root-iana-origin.cer
|
||||||
|
rsync://rpki.apnic.net/repository/apnic-rpki-root-iana-origin.cer
|
||||||
|
|
||||||
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx9RWSL61YAAYumEiU8z8
|
||||||
|
qH2ETVIL01ilxZlzIL9JYSORMN5Cmtf8V2JblIealSqgOTGjvSjEsiV73s67zYQI
|
||||||
|
7C/iSOb96uf3/s86NqbxDiFQGN8qG7RNcdgVuUlAidl8WxvLNI8VhqbAB5uSg/Mr
|
||||||
|
LeSOvXRja041VptAxIhcGzDMvlAJRwkrYK/Mo8P4E2rSQgwqCgae0ebY1CsJ3Cjf
|
||||||
|
i67C1nw7oXqJJovvXJ4apGmEv8az23OLC6Ki54Ul/E6xk227BFttqFV3YMtKx42H
|
||||||
|
cCcDVZZy01n7JjzvO8ccaXmHIgR7utnqhBRNNq5Xc5ZhbkrUsNtiJmrZzVlgU6Ou
|
||||||
|
0wIDAQAB
|
||||||
10
fixtures/live_20260619/tal/apnic.tal
Normal file
10
fixtures/live_20260619/tal/apnic.tal
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
https://rpki.apnic.net/repository/apnic-rpki-root-iana-origin.cer
|
||||||
|
rsync://rpki.apnic.net/repository/apnic-rpki-root-iana-origin.cer
|
||||||
|
|
||||||
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx9RWSL61YAAYumEiU8z8
|
||||||
|
qH2ETVIL01ilxZlzIL9JYSORMN5Cmtf8V2JblIealSqgOTGjvSjEsiV73s67zYQI
|
||||||
|
7C/iSOb96uf3/s86NqbxDiFQGN8qG7RNcdgVuUlAidl8WxvLNI8VhqbAB5uSg/Mr
|
||||||
|
LeSOvXRja041VptAxIhcGzDMvlAJRwkrYK/Mo8P4E2rSQgwqCgae0ebY1CsJ3Cjf
|
||||||
|
i67C1nw7oXqJJovvXJ4apGmEv8az23OLC6Ki54Ul/E6xk227BFttqFV3YMtKx42H
|
||||||
|
cCcDVZZy01n7JjzvO8ccaXmHIgR7utnqhBRNNq5Xc5ZhbkrUsNtiJmrZzVlgU6Ou
|
||||||
|
0wIDAQAB
|
||||||
19
fixtures/live_20260619/tal/arin.tal
Normal file
19
fixtures/live_20260619/tal/arin.tal
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# THIS TRUST ANCHOR LOCATOR IS PROVIDED BY THE AMERICAN REGISTRY FOR
|
||||||
|
# INTERNET NUMBERS (ARIN) "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||||
|
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||||
|
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||||
|
# IN NO EVENT SHALL ARIN BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
# OF THIS PUBLIC KEY, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
rsync://rpki.arin.net/repository/arin-rpki-ta.cer
|
||||||
|
https://rrdp.arin.net/arin-rpki-ta.cer
|
||||||
|
|
||||||
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3lZPjbHvMRV5sDDqfLc/685th5FnreHMJjg8
|
||||||
|
pEZUbG8Y8TQxSBsDebbsDpl3Ov3Cj1WtdrJ3CIfQODCPrrJdOBSrMATeUbPC+JlNf2SRP3UB+VJFgtTj
|
||||||
|
0RN8cEYIuhBW5t6AxQbHhdNQH+A1F/OJdw0q9da2U29Lx85nfFxvnC1EpK9CbLJS4m37+RlpNbT1cba+
|
||||||
|
b+loXpx0Qcb1C4UpJCGDy7uNf5w6/+l7RpATAHqqsX4qCtwwDYlbHzp2xk9owF3mkCxzl0HwncO+sEHH
|
||||||
|
eaL3OjtwdIGrRGeHi2Mpt+mvWHhtQqVG+51MHTyg+nIjWFKKGx1Q9+KDx4wJStwveQIDAQAB
|
||||||
4
fixtures/live_20260619/tal/lacnic.tal
Normal file
4
fixtures/live_20260619/tal/lacnic.tal
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
https://rrdp.lacnic.net/ta/rta-lacnic-rpki.cer
|
||||||
|
rsync://repository.lacnic.net/rpki/lacnic/rta-lacnic-rpki.cer
|
||||||
|
|
||||||
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqZEzhYK0+PtDOPfub/KRc3MeWx3neXx4/wbnJWGbNAtbYqXg3uU5J4HFzPgk/VIppgSKAhlO0H60DRP48by9gr5/yDHu2KXhOmnMg46sYsUIpfgtBS9+VtrqWziJfb+pkGtuOWeTnj6zBmBNZKK+5AlMCW1WPhrylIcB+XSZx8tk9GS/3SMQ+YfMVwwAyYjsex14Uzto4GjONALE5oh1M3+glRQduD6vzSwOD+WahMbc9vCOTED+2McLHRKgNaQf0YJ9a1jG9oJIvDkKXEqdfqDRktwyoD74cV57bW3tBAexB7GglITbInyQAsmdngtfg2LUMrcROHHP86QPZINjDQIDAQAB
|
||||||
10
fixtures/live_20260619/tal/ripe-ncc.tal
Normal file
10
fixtures/live_20260619/tal/ripe-ncc.tal
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
https://rpki.ripe.net/ta/ripe-ncc-ta.cer
|
||||||
|
rsync://rpki.ripe.net/ta/ripe-ncc-ta.cer
|
||||||
|
|
||||||
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0URYSGqUz2myBsOzeW1j
|
||||||
|
Q6NsxNvlLMyhWknvnl8NiBCs/T/S2XuNKQNZ+wBZxIgPPV2pFBFeQAvoH/WK83Hw
|
||||||
|
A26V2siwm/MY2nKZ+Olw+wlpzlZ1p3Ipj2eNcKrmit8BwBC8xImzuCGaV0jkRB0G
|
||||||
|
Z0hoH6Ml03umLprRsn6v0xOP0+l6Qc1ZHMFVFb385IQ7FQQTcVIxrdeMsoyJq9eM
|
||||||
|
kE6DoclHhF/NlSllXubASQ9KUWqJ0+Ot3QCXr4LXECMfkpkVR2TZT+v5v658bHVs
|
||||||
|
6ZxRD1b6Uk1uQKAyHUbn/tXvP8lrjAibGzVsXDT2L0x4Edx+QdixPgOji3gBMyL2
|
||||||
|
VwIDAQAB
|
||||||
220
monitor/README.md
Normal file
220
monitor/README.md
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
# Ours RP Prometheus / Grafana Monitor
|
||||||
|
|
||||||
|
本目录提供本地开发监控栈,用于采集 `rpki_artifact_metrics` 暴露的 ours RP soak 指标。
|
||||||
|
|
||||||
|
## 前置条件
|
||||||
|
|
||||||
|
1. Docker + Docker Compose v2;
|
||||||
|
2. 宿主机已启动 `rpki_artifact_metrics`,并监听 Docker 网桥可访问的地址,例如 `0.0.0.0:9556`;
|
||||||
|
3. Prometheus 容器通过 `host.docker.internal:9556` 访问宿主 sidecar。
|
||||||
|
|
||||||
|
Linux Docker 下 compose 已配置:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
extra_hosts:
|
||||||
|
- host.docker.internal:host-gateway
|
||||||
|
```
|
||||||
|
|
||||||
|
## 启动
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd rpki_2/rpki/monitor
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
默认镜像使用官方 Docker Hub 镜像:
|
||||||
|
|
||||||
|
```text
|
||||||
|
prom/prometheus:v2.55.1
|
||||||
|
grafana/grafana:11.3.1
|
||||||
|
```
|
||||||
|
|
||||||
|
如需切到其它镜像源:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PROMETHEUS_IMAGE=<mirror>/prom/prometheus:v2.55.1 \
|
||||||
|
GRAFANA_IMAGE=<mirror>/grafana/grafana:11.3.1 \
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
默认端口:
|
||||||
|
|
||||||
|
- Prometheus: <http://localhost:9090>
|
||||||
|
- Grafana: <http://localhost:3000>
|
||||||
|
- Grafana 默认账号密码:`admin` / `admin`
|
||||||
|
|
||||||
|
如端口冲突:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PROMETHEUS_PORT=19090 GRAFANA_PORT=13000 docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
Prometheus 默认保留 7 天数据;可通过 `PROMETHEUS_RETENTION` 覆盖:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PROMETHEUS_RETENTION=7d docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
## 长期稳定性测试
|
||||||
|
|
||||||
|
portable soak package 内置 `run_24h_soak_with_metrics.sh`,用于连续运行 ours RP、启动 metrics sidecar、启动本监控栈,并每小时生成报告:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /path/to/portable-soak
|
||||||
|
SOAK_DURATION_SECS=0 \
|
||||||
|
HOURLY_REPORT_INTERVAL_SECS=3600 \
|
||||||
|
SOAK_RETAIN_RUNS=100 \
|
||||||
|
CLEAN_TMP_AFTER_RUN=1 \
|
||||||
|
PROMETHEUS_RETENTION=7d \
|
||||||
|
STOP_MONITOR_STACK_ON_EXIT=0 \
|
||||||
|
FEISHU_WEBHOOK_SCRIPT=/home/yuyr/.codex/skills/user/feishu-webhook/scripts/send_feishu_text.py \
|
||||||
|
./run_24h_soak_with_metrics.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
`SOAK_DURATION_SECS=0` 表示持续运行不自动停止;如需 24 小时自然停止,可设置为 `86400`,脚本会等当前 run 完成后退出,不会直接 kill 半轮验证。
|
||||||
|
|
||||||
|
关键产物:
|
||||||
|
|
||||||
|
- `runs/run_xxxx/`:最近 100 个 run 原始产物;
|
||||||
|
- `hourly_reports/hour_*.md`:小时级报告;
|
||||||
|
- `hourly_reports/hourly_summary.jsonl`:小时级结构化汇总;
|
||||||
|
- `incident_runs/run_xxxx/`:异常 run 固化副本;
|
||||||
|
- `logs/metrics.*`、`logs/24h-soak.*`、`logs/hourly-reporter.*`:运行日志。
|
||||||
|
|
||||||
|
短周期联调可把 `SOAK_DURATION_SECS` 和 `HOURLY_REPORT_INTERVAL_SECS` 调小,并设置 `FEISHU_DRY_RUN=1` 避免真实飞书通知。
|
||||||
|
|
||||||
|
## 停止
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd rpki_2/rpki/monitor
|
||||||
|
docker compose down
|
||||||
|
```
|
||||||
|
|
||||||
|
保留数据 volume。若要清理数据:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose down -v
|
||||||
|
```
|
||||||
|
|
||||||
|
## 典型本地联调命令
|
||||||
|
|
||||||
|
先启动 APNIC soak 和 metrics sidecar,例如:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# soak .env 关键配置
|
||||||
|
MAX_RUNS=-1
|
||||||
|
RIRS=apnic
|
||||||
|
RETAIN_RUNS=5
|
||||||
|
INTERVAL_SECS=0
|
||||||
|
|
||||||
|
# metrics sidecar
|
||||||
|
rpki_artifact_metrics \
|
||||||
|
--run-root /path/to/portable-soak \
|
||||||
|
--listen 0.0.0.0:9556 \
|
||||||
|
--poll-secs 5 \
|
||||||
|
--instance local-apnic-continuous
|
||||||
|
```
|
||||||
|
|
||||||
|
再启动监控栈:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd rpki_2/rpki/monitor
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
## 验证
|
||||||
|
|
||||||
|
Prometheus target:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s 'http://localhost:9090/api/v1/targets' | python3 -m json.tool
|
||||||
|
```
|
||||||
|
|
||||||
|
Prometheus query:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -G 'http://localhost:9090/api/v1/query' \
|
||||||
|
--data-urlencode 'query=up{job="ours-rp-artifact-metrics"}'
|
||||||
|
|
||||||
|
curl -G 'http://localhost:9090/api/v1/query' \
|
||||||
|
--data-urlencode 'query=ours_rp_run_completed_total{status="success"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
Grafana health:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s http://localhost:3000/api/health | python3 -m json.tool
|
||||||
|
```
|
||||||
|
|
||||||
|
Grafana dashboard:
|
||||||
|
|
||||||
|
- 打开 <http://localhost:3000/d/ours-rp-soak-overview/ours-rp-soak-overview>
|
||||||
|
|
||||||
|
## 主要指标
|
||||||
|
|
||||||
|
- `ours_rp_metrics_service_up`
|
||||||
|
- `ours_rp_run_completed_total`
|
||||||
|
- `ours_rp_run_duration_seconds`
|
||||||
|
- `ours_rp_run_max_rss_bytes`
|
||||||
|
- `ours_rp_vrps{kind="total|unique"}`:`total` 为去重前 VRP 条目数,`unique` 按 `(ASN, IP Prefix, Max Length)` 去重。
|
||||||
|
- `ours_rp_vaps`
|
||||||
|
- `ours_rp_publication_points`
|
||||||
|
- `ours_rp_repo_sync_phase_count`
|
||||||
|
- `ours_rp_large_publication_points{object_count_gt="10|50|100|..."}`
|
||||||
|
- `ours_rp_cir_objects`
|
||||||
|
- `ours_rp_ccr_state_items`
|
||||||
|
|
||||||
|
## Inter-RP 持续对比监控
|
||||||
|
|
||||||
|
`rpki_inter_rp_metrics` 用于汇总三方 RP 的最新产物:
|
||||||
|
|
||||||
|
- ours RP:读取当前 portable soak 的 `runs/run_xxxx/run-summary.json`、`result.ccr`、CSV 产物;
|
||||||
|
- Routinator:读取远端200同步来的 `routinator/latest/run-meta.json`、`vrps.csv`、`vaps.csv`;
|
||||||
|
- rpki-client 9.8:读取远端200同步来的 `rpki-client/latest/run-meta.json`、`vrps.csv`、`vaps.csv`、`result.ccr`。
|
||||||
|
|
||||||
|
远端231 启动 sidecar 示例:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rpki_inter_rp_metrics \
|
||||||
|
--ours-run-root /root/rpki_20260608_2_feature062_24h_20260608T075547Z/portable-soak \
|
||||||
|
--peer-root /root/inter-rp-aggregator/synced-from-200 \
|
||||||
|
--listen 0.0.0.0:9557 \
|
||||||
|
--poll-secs 30 \
|
||||||
|
--instance remote231-inter-rp
|
||||||
|
```
|
||||||
|
|
||||||
|
Prometheus 已新增 `ours-rp-inter-rp-metrics` scrape job,默认访问 `host.docker.internal:9557`。
|
||||||
|
|
||||||
|
远端200 runner 与远端231同步脚本位于:
|
||||||
|
|
||||||
|
```text
|
||||||
|
scripts/inter_rp/run_remote200_rp_loops.sh
|
||||||
|
scripts/inter_rp/run_single_rp_with_rss.sh
|
||||||
|
scripts/inter_rp/sync_remote200_to_231.sh
|
||||||
|
scripts/inter_rp/run_inter_rp_metrics_sidecar.sh
|
||||||
|
scripts/inter_rp/inter-rp.env.example
|
||||||
|
```
|
||||||
|
|
||||||
|
如需从本机独立开关远端200上的 Routinator 或 rpki-client,使用:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scripts/inter_rp/control_remote200_rp.sh status all
|
||||||
|
scripts/inter_rp/control_remote200_rp.sh stop routinator
|
||||||
|
scripts/inter_rp/control_remote200_rp.sh start routinator
|
||||||
|
scripts/inter_rp/control_remote200_rp.sh restart rpki-client
|
||||||
|
```
|
||||||
|
|
||||||
|
默认远端为 `root@43.110.128.200`,可通过 `REMOTE_HOST=...` 覆盖;脚本只管理指定 RP 的 loop 和当前子进程,不会自动影响另一个 RP。
|
||||||
|
|
||||||
|
关键指标:
|
||||||
|
|
||||||
|
- `inter_rp_run_wall_seconds{rp="ours-rp|routinator|rpki-client"}`
|
||||||
|
- `inter_rp_run_max_rss_bytes{rp="...",kind="aggregate_peak"}`
|
||||||
|
- `inter_rp_vrps{rp="..."}`:按 `(ASN, IP Prefix, Max Length)` 去重。
|
||||||
|
- `inter_rp_vaps{rp="..."}`:按 `(Customer ASN, Providers)` 去重,Routinator 使用 `--enable-aspa` JSON 输出转换,rpki-client 使用 `-j` JSON 输出转换。
|
||||||
|
- `inter_rp_ccr_digest_match{left="ours-rp",right="rpki-client",state="overall|mfts|vrps|vaps|tas|rks"}`
|
||||||
|
- `inter_rp_sync_age_seconds`
|
||||||
|
|
||||||
|
Grafana dashboard:
|
||||||
|
|
||||||
|
- <http://localhost:3000/d/ours-rp-inter-rp/ours-rp-inter-rp>
|
||||||
38
monitor/docker-compose.yml
Normal file
38
monitor/docker-compose.yml
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
services:
|
||||||
|
prometheus:
|
||||||
|
image: ${PROMETHEUS_IMAGE:-prom/prometheus:v2.55.1}
|
||||||
|
container_name: ours-rp-prometheus
|
||||||
|
command:
|
||||||
|
- --config.file=/etc/prometheus/prometheus.yml
|
||||||
|
- --storage.tsdb.path=/prometheus
|
||||||
|
- --storage.tsdb.retention.time=${PROMETHEUS_RETENTION:-7d}
|
||||||
|
- --web.enable-lifecycle
|
||||||
|
extra_hosts:
|
||||||
|
- host.docker.internal:host-gateway
|
||||||
|
ports:
|
||||||
|
- "${PROMETHEUS_PORT:-9090}:9090"
|
||||||
|
volumes:
|
||||||
|
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
|
||||||
|
- prometheus-data:/prometheus
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
grafana:
|
||||||
|
image: ${GRAFANA_IMAGE:-grafana/grafana:11.3.1}
|
||||||
|
container_name: ours-rp-grafana
|
||||||
|
depends_on:
|
||||||
|
- prometheus
|
||||||
|
ports:
|
||||||
|
- "${GRAFANA_PORT:-3000}:3000"
|
||||||
|
environment:
|
||||||
|
GF_SECURITY_ADMIN_USER: ${GRAFANA_ADMIN_USER:-admin}
|
||||||
|
GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_ADMIN_PASSWORD:-admin}
|
||||||
|
GF_USERS_ALLOW_SIGN_UP: "false"
|
||||||
|
volumes:
|
||||||
|
- grafana-data:/var/lib/grafana
|
||||||
|
- ./grafana/provisioning:/etc/grafana/provisioning:ro
|
||||||
|
- ./grafana/dashboards:/var/lib/grafana/dashboards:ro
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
prometheus-data:
|
||||||
|
grafana-data:
|
||||||
826
monitor/grafana/dashboards/ours-rp-inter-rp.json
Normal file
826
monitor/grafana/dashboards/ours-rp-inter-rp.json
Normal file
@@ -0,0 +1,826 @@
|
|||||||
|
{
|
||||||
|
"annotations": {
|
||||||
|
"list": []
|
||||||
|
},
|
||||||
|
"editable": true,
|
||||||
|
"fiscalYearStartMonth": 0,
|
||||||
|
"graphTooltip": 0,
|
||||||
|
"id": null,
|
||||||
|
"links": [],
|
||||||
|
"liveNow": false,
|
||||||
|
"panels": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"title": "Ours Only Repo Count",
|
||||||
|
"type": "stat",
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 4,
|
||||||
|
"w": 6,
|
||||||
|
"x": 0,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "short",
|
||||||
|
"decimals": 0
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "max(inter_rp_repo_sync_overlap_total{exported_instance=~\".*inter-rp\",left=\"ours-rp\",right=\"routinator\",class=\"only_ours\"})",
|
||||||
|
"legendFormat": "only ours",
|
||||||
|
"refId": "A",
|
||||||
|
"instant": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"title": "Routinator Only Repo Count",
|
||||||
|
"type": "stat",
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 4,
|
||||||
|
"w": 6,
|
||||||
|
"x": 6,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "short",
|
||||||
|
"decimals": 0
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "max(inter_rp_repo_sync_overlap_total{exported_instance=~\".*inter-rp\",left=\"ours-rp\",right=\"routinator\",class=\"only_routinator\"})",
|
||||||
|
"legendFormat": "only routinator",
|
||||||
|
"refId": "A",
|
||||||
|
"instant": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"title": "Ours vs Routinator VAP Diff",
|
||||||
|
"type": "stat",
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 4,
|
||||||
|
"w": 6,
|
||||||
|
"x": 12,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "short",
|
||||||
|
"decimals": 0
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "max(inter_rp_vaps_diff{exported_instance=~\".*inter-rp\",left=\"ours-rp\",right=\"routinator\"})",
|
||||||
|
"legendFormat": "vap diff",
|
||||||
|
"refId": "A",
|
||||||
|
"instant": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"title": "Ours vs Routinator VRP Diff",
|
||||||
|
"type": "stat",
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 4,
|
||||||
|
"w": 6,
|
||||||
|
"x": 18,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "short",
|
||||||
|
"decimals": 0
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "max(inter_rp_vrps_diff{exported_instance=~\".*inter-rp\",left=\"ours-rp\",right=\"routinator\"})",
|
||||||
|
"legendFormat": "vrp diff",
|
||||||
|
"refId": "A",
|
||||||
|
"instant": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 5,
|
||||||
|
"title": "Wall Time by RP",
|
||||||
|
"type": "timeseries",
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 0,
|
||||||
|
"y": 4
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "s",
|
||||||
|
"min": 0
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "inter_rp_run_wall_seconds{exported_instance=~\".*inter-rp\",rp=~\"ours-rp|routinator\"}",
|
||||||
|
"legendFormat": "{{rp}}",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 6,
|
||||||
|
"title": "Max RSS Aggregate Peak by RP",
|
||||||
|
"type": "timeseries",
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 12,
|
||||||
|
"y": 4
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "bytes",
|
||||||
|
"min": 0
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "inter_rp_run_max_rss_bytes{exported_instance=~\".*inter-rp\",kind=\"aggregate_peak\",rp=~\"ours-rp|routinator\"}",
|
||||||
|
"legendFormat": "{{rp}}",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 7,
|
||||||
|
"title": "VRPs by RP (unique ASN/Prefix/MaxLen)",
|
||||||
|
"type": "timeseries",
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 0,
|
||||||
|
"y": 12
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "none",
|
||||||
|
"min": 0
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "inter_rp_vrps{exported_instance=~\".*inter-rp\",rp=~\"ours-rp|routinator\"}",
|
||||||
|
"legendFormat": "{{rp}}",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 8,
|
||||||
|
"title": "VAPs / ASPAs by RP (unique Customer/Providers)",
|
||||||
|
"type": "timeseries",
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 12,
|
||||||
|
"y": 12
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "none",
|
||||||
|
"min": 0
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "inter_rp_vaps{exported_instance=~\".*inter-rp\",rp=~\"ours-rp|routinator\"}",
|
||||||
|
"legendFormat": "{{rp}}",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 9,
|
||||||
|
"title": "Latest RP Runs",
|
||||||
|
"type": "table",
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 0,
|
||||||
|
"y": 20
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "none",
|
||||||
|
"decimals": 0
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"showHeader": true,
|
||||||
|
"sortBy": []
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "inter_rp_run_seq{exported_instance=~\".*inter-rp\",rp=~\"ours-rp|routinator\"}",
|
||||||
|
"format": "table",
|
||||||
|
"instant": true,
|
||||||
|
"legendFormat": "{{rp}} seq",
|
||||||
|
"refId": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "inter_rp_run_success{exported_instance=~\".*inter-rp\",rp=~\"ours-rp|routinator\"}",
|
||||||
|
"format": "table",
|
||||||
|
"instant": true,
|
||||||
|
"legendFormat": "{{rp}} success",
|
||||||
|
"refId": "B"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "inter_rp_run_wall_seconds{exported_instance=~\".*inter-rp\",rp=~\"ours-rp|routinator\"}",
|
||||||
|
"format": "table",
|
||||||
|
"instant": true,
|
||||||
|
"legendFormat": "{{rp}} wall",
|
||||||
|
"refId": "C"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 10,
|
||||||
|
"title": "Output Count Diffs (unique)",
|
||||||
|
"type": "table",
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 12,
|
||||||
|
"y": 20
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "none",
|
||||||
|
"decimals": 0
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"showHeader": true,
|
||||||
|
"sortBy": []
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "inter_rp_vrps_diff{exported_instance=~\".*inter-rp\",left=\"ours-rp\",right=\"routinator\"}",
|
||||||
|
"format": "table",
|
||||||
|
"instant": true,
|
||||||
|
"legendFormat": "vrps ours-rp-routinator",
|
||||||
|
"refId": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "inter_rp_vaps_diff{exported_instance=~\".*inter-rp\",left=\"ours-rp\",right=\"routinator\"}",
|
||||||
|
"format": "table",
|
||||||
|
"instant": true,
|
||||||
|
"legendFormat": "vaps ours-rp-routinator",
|
||||||
|
"refId": "B"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 15,
|
||||||
|
"title": "VRP Diff Trend by Class",
|
||||||
|
"type": "timeseries",
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 0,
|
||||||
|
"y": 28
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "none",
|
||||||
|
"min": 0,
|
||||||
|
"decimals": 0
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "multi",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "inter_rp_vrps_diff_by_class{exported_instance=~\".*inter-rp\",left=\"ours-rp\",right=\"routinator\",class=\"total\"}",
|
||||||
|
"legendFormat": "total diff",
|
||||||
|
"refId": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "inter_rp_vrps_diff_by_class{exported_instance=~\".*inter-rp\",left=\"ours-rp\",right=\"routinator\",class=\"only_ours\"}",
|
||||||
|
"legendFormat": "only ours",
|
||||||
|
"refId": "B"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "inter_rp_vrps_diff_by_class{exported_instance=~\".*inter-rp\",left=\"ours-rp\",right=\"routinator\",class=\"only_routinator\"}",
|
||||||
|
"legendFormat": "only routinator",
|
||||||
|
"refId": "C"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 16,
|
||||||
|
"title": "VAP Diff Trend by Class",
|
||||||
|
"type": "timeseries",
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 12,
|
||||||
|
"y": 28
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "none",
|
||||||
|
"min": 0,
|
||||||
|
"decimals": 0
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "multi",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "inter_rp_vaps_diff_by_class{exported_instance=~\".*inter-rp\",left=\"ours-rp\",right=\"routinator\",class=\"total\"}",
|
||||||
|
"legendFormat": "total diff",
|
||||||
|
"refId": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "inter_rp_vaps_diff_by_class{exported_instance=~\".*inter-rp\",left=\"ours-rp\",right=\"routinator\",class=\"only_ours\"}",
|
||||||
|
"legendFormat": "only ours",
|
||||||
|
"refId": "B"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "inter_rp_vaps_diff_by_class{exported_instance=~\".*inter-rp\",left=\"ours-rp\",right=\"routinator\",class=\"only_routinator\"}",
|
||||||
|
"legendFormat": "only routinator",
|
||||||
|
"refId": "C"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 11,
|
||||||
|
"title": "Artifact Age by RP",
|
||||||
|
"type": "timeseries",
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 24,
|
||||||
|
"x": 0,
|
||||||
|
"y": 36
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "s",
|
||||||
|
"min": 0
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "inter_rp_artifact_age_seconds{exported_instance=~\".*inter-rp\",rp=~\"ours-rp|routinator\"}",
|
||||||
|
"legendFormat": "{{rp}}",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 12,
|
||||||
|
"title": "Repo Sync Availability by RP",
|
||||||
|
"type": "timeseries",
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 0,
|
||||||
|
"y": 44
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "none",
|
||||||
|
"min": 0,
|
||||||
|
"decimals": 0
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "inter_rp_repo_sync_total{exported_instance=~\".*inter-rp\",state=~\"available|failed\",rp=~\"ours-rp|routinator\"}",
|
||||||
|
"legendFormat": "{{rp}} {{state}}",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 13,
|
||||||
|
"title": "Repo Sync Overlap Classes",
|
||||||
|
"type": "timeseries",
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 12,
|
||||||
|
"y": 44
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "none",
|
||||||
|
"min": 0,
|
||||||
|
"decimals": 0
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "inter_rp_repo_sync_overlap_total{exported_instance=~\".*inter-rp\",left=\"ours-rp\",right=\"routinator\"}",
|
||||||
|
"legendFormat": "{{class}}",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 14,
|
||||||
|
"title": "Repo Sync Diff URIs",
|
||||||
|
"type": "table",
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 9,
|
||||||
|
"w": 24,
|
||||||
|
"x": 0,
|
||||||
|
"y": 52
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "none",
|
||||||
|
"decimals": 0
|
||||||
|
},
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"matcher": {
|
||||||
|
"id": "byName",
|
||||||
|
"options": "uri"
|
||||||
|
},
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": "custom.width",
|
||||||
|
"value": 760
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matcher": {
|
||||||
|
"id": "byName",
|
||||||
|
"options": "class"
|
||||||
|
},
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": "custom.width",
|
||||||
|
"value": 140
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matcher": {
|
||||||
|
"id": "byRegexp",
|
||||||
|
"options": "^(mft|crl|crt|roa|aspa)$"
|
||||||
|
},
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": "custom.align",
|
||||||
|
"value": "right"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "custom.width",
|
||||||
|
"value": 80
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"showHeader": true,
|
||||||
|
"sortBy": []
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "inter_rp_repo_sync_diff_info{exported_instance=~\".*inter-rp\",left=\"ours-rp\",right=\"routinator\"}",
|
||||||
|
"format": "table",
|
||||||
|
"instant": true,
|
||||||
|
"legendFormat": "{{class}} #{{rank}}",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"transformations": [
|
||||||
|
{
|
||||||
|
"id": "organize",
|
||||||
|
"options": {
|
||||||
|
"excludeByName": {
|
||||||
|
"Time": true,
|
||||||
|
"Value": true,
|
||||||
|
"__name__": true,
|
||||||
|
"exported_instance": true,
|
||||||
|
"instance": true,
|
||||||
|
"job": true,
|
||||||
|
"left": true,
|
||||||
|
"right": true,
|
||||||
|
"rank": true,
|
||||||
|
"routinator_duration": true
|
||||||
|
},
|
||||||
|
"indexByName": {
|
||||||
|
"class": 0,
|
||||||
|
"uri": 1,
|
||||||
|
"mft": 2,
|
||||||
|
"crl": 3,
|
||||||
|
"crt": 4,
|
||||||
|
"roa": 5,
|
||||||
|
"aspa": 6
|
||||||
|
},
|
||||||
|
"renameByName": {
|
||||||
|
"class": "class",
|
||||||
|
"uri": "uri",
|
||||||
|
"mft": "mft",
|
||||||
|
"crl": "crl",
|
||||||
|
"crt": "crt",
|
||||||
|
"roa": "roa",
|
||||||
|
"aspa": "aspa"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"refresh": "10s",
|
||||||
|
"schemaVersion": 40,
|
||||||
|
"tags": [
|
||||||
|
"rpki",
|
||||||
|
"inter-rp",
|
||||||
|
"routinator"
|
||||||
|
],
|
||||||
|
"templating": {
|
||||||
|
"list": []
|
||||||
|
},
|
||||||
|
"time": {
|
||||||
|
"from": "now-6h",
|
||||||
|
"to": "now"
|
||||||
|
},
|
||||||
|
"timezone": "browser",
|
||||||
|
"title": "Ours RP vs Routinator",
|
||||||
|
"uid": "ours-rp-inter-rp",
|
||||||
|
"version": 4
|
||||||
|
}
|
||||||
761
monitor/grafana/dashboards/ours-rp-repo-sync.json
Normal file
761
monitor/grafana/dashboards/ours-rp-repo-sync.json
Normal file
@@ -0,0 +1,761 @@
|
|||||||
|
{
|
||||||
|
"annotations": {
|
||||||
|
"list": []
|
||||||
|
},
|
||||||
|
"editable": true,
|
||||||
|
"fiscalYearStartMonth": 0,
|
||||||
|
"graphTooltip": 0,
|
||||||
|
"id": null,
|
||||||
|
"links": [],
|
||||||
|
"panels": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "short"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 4,
|
||||||
|
"w": 6,
|
||||||
|
"x": 0,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"id": 1,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"pluginVersion": "11.3.1",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_publication_points",
|
||||||
|
"legendFormat": "publication points",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Publication Points",
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "short"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 4,
|
||||||
|
"w": 6,
|
||||||
|
"x": 6,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"id": 2,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"pluginVersion": "11.3.1",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_repo_sync_phase_count{phase=\"rrdp_ok\"}",
|
||||||
|
"legendFormat": "rrdp ok",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "RRDP OK Points",
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "short"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 4,
|
||||||
|
"w": 6,
|
||||||
|
"x": 12,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"id": 3,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"pluginVersion": "11.3.1",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "sum(ours_rp_repo_sync_phase_count{phase=\"rrdp_failed_rsync_ok\"}) or vector(0)",
|
||||||
|
"legendFormat": "fallback",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Rsync Fallback Points",
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "short"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 4,
|
||||||
|
"w": 6,
|
||||||
|
"x": 18,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"id": 4,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"pluginVersion": "11.3.1",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_repo_terminal_state_count{terminal_state=\"failed_no_cache\"}",
|
||||||
|
"legendFormat": "failed no cache",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Failed No Cache Points",
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "s"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 24,
|
||||||
|
"x": 0,
|
||||||
|
"y": 4
|
||||||
|
},
|
||||||
|
"id": 5,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "multi",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_run_stage_duration_seconds{stage=\"repo_sync_total\"}",
|
||||||
|
"legendFormat": "repo sync total",
|
||||||
|
"refId": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_run_stage_duration_seconds{stage=\"rrdp_download_total\"}",
|
||||||
|
"legendFormat": "rrdp download",
|
||||||
|
"refId": "B"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_run_stage_duration_seconds{stage=\"rsync_download_total\"}",
|
||||||
|
"legendFormat": "rsync download",
|
||||||
|
"refId": "C"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Repo Sync Download Durations",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "short"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 12,
|
||||||
|
"w": 12,
|
||||||
|
"h": 8
|
||||||
|
},
|
||||||
|
"id": 6,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "multi",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_repo_sync_phase_count",
|
||||||
|
"legendFormat": "{{phase}}",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Repo Sync Phase Counts",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "short"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"x": 12,
|
||||||
|
"y": 12,
|
||||||
|
"w": 12,
|
||||||
|
"h": 8
|
||||||
|
},
|
||||||
|
"id": 7,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "multi",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_repo_sync_phase_count{phase=\"rrdp_failed_rsync_ok\"}",
|
||||||
|
"legendFormat": "rrdp failed, rsync ok",
|
||||||
|
"refId": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_repo_sync_phase_count{phase=\"rrdp_failed_rsync_failed\"}",
|
||||||
|
"legendFormat": "rrdp failed, rsync failed",
|
||||||
|
"refId": "B"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_repo_terminal_state_count{terminal_state=\"failed_no_cache\"}",
|
||||||
|
"legendFormat": "failed no cache",
|
||||||
|
"refId": "C"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_tree_instances{state=\"failed\"}",
|
||||||
|
"legendFormat": "tree failed",
|
||||||
|
"refId": "D"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Repo Failure / Fallback Counts",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "s"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 20,
|
||||||
|
"w": 12,
|
||||||
|
"h": 8
|
||||||
|
},
|
||||||
|
"id": 8,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "multi",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_repo_sync_phase_duration_seconds_total{phase=\"rrdp_failed_rsync_ok\"}",
|
||||||
|
"legendFormat": "rsync fallback duration",
|
||||||
|
"refId": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_repo_sync_phase_duration_seconds_total{phase=\"rrdp_failed_rsync_failed\"}",
|
||||||
|
"legendFormat": "failed duration",
|
||||||
|
"refId": "B"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_repo_terminal_state_duration_seconds_total{terminal_state=\"failed_no_cache\"}",
|
||||||
|
"legendFormat": "failed no cache duration",
|
||||||
|
"refId": "C"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Repo Failure / Fallback Durations",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "none"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 29,
|
||||||
|
"w": 12,
|
||||||
|
"h": 9
|
||||||
|
},
|
||||||
|
"id": 9,
|
||||||
|
"options": {
|
||||||
|
"showHeader": true,
|
||||||
|
"cellHeight": "sm",
|
||||||
|
"footer": {
|
||||||
|
"show": false,
|
||||||
|
"reducer": [
|
||||||
|
"sum"
|
||||||
|
],
|
||||||
|
"countRows": false,
|
||||||
|
"fields": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pluginVersion": "11.3.1",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_rrdp_rsync_failed_repository_duration_seconds",
|
||||||
|
"format": "table",
|
||||||
|
"instant": true,
|
||||||
|
"legendFormat": "",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "RRDP + Rsync Failed Repositories",
|
||||||
|
"type": "table",
|
||||||
|
"transformations": [
|
||||||
|
{
|
||||||
|
"id": "organize",
|
||||||
|
"options": {
|
||||||
|
"excludeByName": {
|
||||||
|
"job": true,
|
||||||
|
"terminal_state": true,
|
||||||
|
"rank": true,
|
||||||
|
"transport": true,
|
||||||
|
"__name__": true,
|
||||||
|
"publication_points": true,
|
||||||
|
"instance": true,
|
||||||
|
"repo_id": true,
|
||||||
|
"pp_id": true,
|
||||||
|
"exported_instance": true,
|
||||||
|
"rp": true,
|
||||||
|
"source": true
|
||||||
|
},
|
||||||
|
"indexByName": {
|
||||||
|
"Time": 0,
|
||||||
|
"host": 1,
|
||||||
|
"phase": 2,
|
||||||
|
"uri": 3,
|
||||||
|
"Value": 4
|
||||||
|
},
|
||||||
|
"renameByName": {
|
||||||
|
"Value": "duration"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "none"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"x": 12,
|
||||||
|
"y": 29,
|
||||||
|
"w": 12,
|
||||||
|
"h": 9
|
||||||
|
},
|
||||||
|
"id": 11,
|
||||||
|
"options": {
|
||||||
|
"showHeader": true,
|
||||||
|
"cellHeight": "sm",
|
||||||
|
"footer": {
|
||||||
|
"show": false,
|
||||||
|
"reducer": [
|
||||||
|
"sum"
|
||||||
|
],
|
||||||
|
"countRows": false,
|
||||||
|
"fields": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pluginVersion": "11.3.1",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "topk(20, ours_rp_top_repository_sync_duration_seconds)",
|
||||||
|
"format": "table",
|
||||||
|
"instant": true,
|
||||||
|
"legendFormat": "",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Top 20 Repositories by Sync Duration",
|
||||||
|
"type": "table",
|
||||||
|
"transformations": [
|
||||||
|
{
|
||||||
|
"id": "organize",
|
||||||
|
"options": {
|
||||||
|
"excludeByName": {
|
||||||
|
"job": true,
|
||||||
|
"terminal_state": true,
|
||||||
|
"__name__": true,
|
||||||
|
"publication_points": true,
|
||||||
|
"instance": true,
|
||||||
|
"repo_id": true,
|
||||||
|
"phase": true,
|
||||||
|
"pp_id": true,
|
||||||
|
"exported_instance": true,
|
||||||
|
"rp": true,
|
||||||
|
"source": true
|
||||||
|
},
|
||||||
|
"indexByName": {
|
||||||
|
"Time": 0,
|
||||||
|
"host": 1,
|
||||||
|
"rank": 2,
|
||||||
|
"transport": 3,
|
||||||
|
"uri": 4,
|
||||||
|
"Value": 5
|
||||||
|
},
|
||||||
|
"renameByName": {
|
||||||
|
"Value": "value"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "none"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 38,
|
||||||
|
"w": 24,
|
||||||
|
"h": 9
|
||||||
|
},
|
||||||
|
"id": 10,
|
||||||
|
"options": {
|
||||||
|
"showHeader": true,
|
||||||
|
"cellHeight": "sm",
|
||||||
|
"footer": {
|
||||||
|
"show": false,
|
||||||
|
"reducer": [
|
||||||
|
"sum"
|
||||||
|
],
|
||||||
|
"countRows": false,
|
||||||
|
"fields": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pluginVersion": "11.3.1",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "topk(20, ours_rp_top_publication_point_object_count)",
|
||||||
|
"format": "table",
|
||||||
|
"instant": true,
|
||||||
|
"legendFormat": "",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Top Publication Points by Objects",
|
||||||
|
"type": "table",
|
||||||
|
"transformations": [
|
||||||
|
{
|
||||||
|
"id": "organize",
|
||||||
|
"options": {
|
||||||
|
"excludeByName": {
|
||||||
|
"job": true,
|
||||||
|
"__name__": true,
|
||||||
|
"publication_points": true,
|
||||||
|
"instance": true,
|
||||||
|
"repo_id": true,
|
||||||
|
"phase": true,
|
||||||
|
"pp_id": true,
|
||||||
|
"exported_instance": true,
|
||||||
|
"rp": true,
|
||||||
|
"source": true
|
||||||
|
},
|
||||||
|
"indexByName": {
|
||||||
|
"Time": 0,
|
||||||
|
"host": 1,
|
||||||
|
"rank": 2,
|
||||||
|
"terminal_state": 3,
|
||||||
|
"transport": 4,
|
||||||
|
"uri": 5,
|
||||||
|
"Value": 6
|
||||||
|
},
|
||||||
|
"renameByName": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"description": "Per-repository sync success in the latest successful run; 1 means successful, 0 means failed or failed_no_cache.",
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "bool"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 24,
|
||||||
|
"x": 0,
|
||||||
|
"y": 47
|
||||||
|
},
|
||||||
|
"id": 12,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "multi",
|
||||||
|
"sort": "desc"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_repository_sync_success",
|
||||||
|
"legendFormat": "{{host}} {{repo_id}}",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Repository Sync Success by Repo",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"description": "Per-repository total sync duration aggregated from publication point repo_sync_duration_ms.",
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "s"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 24,
|
||||||
|
"x": 0,
|
||||||
|
"y": 55
|
||||||
|
},
|
||||||
|
"id": 13,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "multi",
|
||||||
|
"sort": "desc"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_repository_sync_duration_seconds{stat=\"sum\"}",
|
||||||
|
"legendFormat": "{{host}} {{repo_id}}",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Repository Sync Duration by Repo",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"description": "Per-repository downloaded bytes attributed from report.json downloads events.",
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "bytes"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 24,
|
||||||
|
"x": 0,
|
||||||
|
"y": 63
|
||||||
|
},
|
||||||
|
"id": 14,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "multi",
|
||||||
|
"sort": "desc"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_repository_download_bytes",
|
||||||
|
"legendFormat": "{{host}} {{repo_id}}",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Repository Download Bytes by Repo",
|
||||||
|
"type": "timeseries"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"refresh": "5s",
|
||||||
|
"schemaVersion": 40,
|
||||||
|
"tags": [
|
||||||
|
"ours-rp",
|
||||||
|
"rpki",
|
||||||
|
"soak",
|
||||||
|
"repo-sync"
|
||||||
|
],
|
||||||
|
"templating": {
|
||||||
|
"list": []
|
||||||
|
},
|
||||||
|
"time": {
|
||||||
|
"from": "now-30m",
|
||||||
|
"to": "now"
|
||||||
|
},
|
||||||
|
"timepicker": {},
|
||||||
|
"timezone": "browser",
|
||||||
|
"title": "Ours RP Repo Sync",
|
||||||
|
"uid": "ours-rp-repo-sync",
|
||||||
|
"version": 3,
|
||||||
|
"weekStart": ""
|
||||||
|
}
|
||||||
666
monitor/grafana/dashboards/ours-rp-rtr-overview.json
Normal file
666
monitor/grafana/dashboards/ours-rp-rtr-overview.json
Normal file
@@ -0,0 +1,666 @@
|
|||||||
|
{
|
||||||
|
"annotations": {
|
||||||
|
"list": []
|
||||||
|
},
|
||||||
|
"editable": true,
|
||||||
|
"fiscalYearStartMonth": 0,
|
||||||
|
"graphTooltip": 0,
|
||||||
|
"id": null,
|
||||||
|
"links": [],
|
||||||
|
"liveNow": false,
|
||||||
|
"panels": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"title": "RTR Metrics Enabled",
|
||||||
|
"type": "stat",
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 4,
|
||||||
|
"w": 4,
|
||||||
|
"x": 0,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "short",
|
||||||
|
"decimals": 0
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "max(ours_rp_rtr_metrics_enabled)",
|
||||||
|
"legendFormat": "enabled",
|
||||||
|
"refId": "A",
|
||||||
|
"instant": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"title": "Refresh Success",
|
||||||
|
"type": "stat",
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 4,
|
||||||
|
"w": 5,
|
||||||
|
"x": 4,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "short",
|
||||||
|
"decimals": 0
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "max(ours_rp_rtr_source_refresh_status{status=\"success\"})",
|
||||||
|
"legendFormat": "success",
|
||||||
|
"refId": "A",
|
||||||
|
"instant": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"title": "Consecutive Failures",
|
||||||
|
"type": "stat",
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 4,
|
||||||
|
"w": 5,
|
||||||
|
"x": 9,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "short",
|
||||||
|
"decimals": 0
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "max(ours_rp_rtr_source_refresh_consecutive_failures)",
|
||||||
|
"legendFormat": "failures",
|
||||||
|
"refId": "A",
|
||||||
|
"instant": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"title": "Last Success Age",
|
||||||
|
"type": "stat",
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 4,
|
||||||
|
"w": 5,
|
||||||
|
"x": 14,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "s",
|
||||||
|
"decimals": 0,
|
||||||
|
"min": 0
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "max(ours_rp_rtr_source_last_success_age_seconds)",
|
||||||
|
"legendFormat": "age",
|
||||||
|
"refId": "A",
|
||||||
|
"instant": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 5,
|
||||||
|
"title": "RTR RSS",
|
||||||
|
"type": "stat",
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 4,
|
||||||
|
"w": 5,
|
||||||
|
"x": 19,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "bytes",
|
||||||
|
"decimals": 0,
|
||||||
|
"min": 0
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "max(ours_rp_rtr_process_rss_bytes)",
|
||||||
|
"legendFormat": "rss",
|
||||||
|
"refId": "A",
|
||||||
|
"instant": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 6,
|
||||||
|
"title": "Data Quality Totals",
|
||||||
|
"type": "timeseries",
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 0,
|
||||||
|
"y": 4
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "short",
|
||||||
|
"min": 0
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_rtr_data_quality_items{stage=~\"ccr_input|before_slurm|after_slurm\",type=\"total\"}",
|
||||||
|
"legendFormat": "{{stage}} total",
|
||||||
|
"refId": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_rtr_data_quality_items{stage=\"after_slurm\",type=\"vrp\"}",
|
||||||
|
"legendFormat": "after_slurm vrp",
|
||||||
|
"refId": "B"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_rtr_data_quality_items{stage=\"after_slurm\",type=\"aspa\"}",
|
||||||
|
"legendFormat": "after_slurm aspa",
|
||||||
|
"refId": "C"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 7,
|
||||||
|
"title": "SLURM Filters / Assertions",
|
||||||
|
"type": "timeseries",
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 12,
|
||||||
|
"y": 4
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "short",
|
||||||
|
"min": 0
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_rtr_slurm_filters",
|
||||||
|
"legendFormat": "filter {{type}}",
|
||||||
|
"refId": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_rtr_slurm_assertions",
|
||||||
|
"legendFormat": "assert {{type}}",
|
||||||
|
"refId": "B"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 8,
|
||||||
|
"title": "Cache Ready / Delta Window",
|
||||||
|
"type": "timeseries",
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 8,
|
||||||
|
"x": 0,
|
||||||
|
"y": 12
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "short",
|
||||||
|
"min": 0
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_rtr_cache_ready",
|
||||||
|
"legendFormat": "ready",
|
||||||
|
"refId": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_rtr_cache_delta_window_length",
|
||||||
|
"legendFormat": "v{{version}} length",
|
||||||
|
"refId": "B"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 9,
|
||||||
|
"title": "Cache Snapshot Items",
|
||||||
|
"type": "timeseries",
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 8,
|
||||||
|
"x": 8,
|
||||||
|
"y": 12
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "short",
|
||||||
|
"min": 0
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_rtr_cache_snapshot_items",
|
||||||
|
"legendFormat": "v{{version}} {{type}}",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 10,
|
||||||
|
"title": "Latest Delta Items",
|
||||||
|
"type": "timeseries",
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 8,
|
||||||
|
"x": 16,
|
||||||
|
"y": 12
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "short",
|
||||||
|
"min": 0
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_rtr_cache_delta_items",
|
||||||
|
"legendFormat": "v{{version}} {{direction}} {{type}}",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 11,
|
||||||
|
"title": "Connections",
|
||||||
|
"type": "timeseries",
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 8,
|
||||||
|
"x": 0,
|
||||||
|
"y": 20
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "short",
|
||||||
|
"min": 0
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_rtr_active_connections",
|
||||||
|
"legendFormat": "active",
|
||||||
|
"refId": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_rtr_connections",
|
||||||
|
"legendFormat": "{{transport}}",
|
||||||
|
"refId": "B"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 12,
|
||||||
|
"title": "Connection Utilization / Max",
|
||||||
|
"type": "timeseries",
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 8,
|
||||||
|
"x": 8,
|
||||||
|
"y": 20
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"min": 0
|
||||||
|
},
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"matcher": {
|
||||||
|
"id": "byRegexp",
|
||||||
|
"options": ".*utilization.*"
|
||||||
|
},
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": "unit",
|
||||||
|
"value": "percentunit"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matcher": {
|
||||||
|
"id": "byRegexp",
|
||||||
|
"options": ".*max.*"
|
||||||
|
},
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": "unit",
|
||||||
|
"value": "short"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_rtr_connection_utilization",
|
||||||
|
"legendFormat": "utilization",
|
||||||
|
"refId": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_rtr_max_connections",
|
||||||
|
"legendFormat": "max",
|
||||||
|
"refId": "B"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 13,
|
||||||
|
"title": "Report Age",
|
||||||
|
"type": "timeseries",
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 8,
|
||||||
|
"x": 16,
|
||||||
|
"y": 20
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "s",
|
||||||
|
"min": 0
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_rtr_source_report_age_seconds",
|
||||||
|
"legendFormat": "source",
|
||||||
|
"refId": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_rtr_runtime_report_age_seconds",
|
||||||
|
"legendFormat": "runtime",
|
||||||
|
"refId": "B"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_rtr_clients_report_age_seconds",
|
||||||
|
"legendFormat": "clients",
|
||||||
|
"refId": "C"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"refresh": "10s",
|
||||||
|
"schemaVersion": 40,
|
||||||
|
"tags": [
|
||||||
|
"rpki",
|
||||||
|
"inter-rp",
|
||||||
|
"routinator"
|
||||||
|
],
|
||||||
|
"templating": {
|
||||||
|
"list": []
|
||||||
|
},
|
||||||
|
"time": {
|
||||||
|
"from": "now-6h",
|
||||||
|
"to": "now"
|
||||||
|
},
|
||||||
|
"timezone": "browser",
|
||||||
|
"title": "RTR Service Overview",
|
||||||
|
"uid": "ours-rp-rtr-overview",
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
875
monitor/grafana/dashboards/ours-rp-soak-overview.json
Normal file
875
monitor/grafana/dashboards/ours-rp-soak-overview.json
Normal file
@@ -0,0 +1,875 @@
|
|||||||
|
{
|
||||||
|
"annotations": {
|
||||||
|
"list": []
|
||||||
|
},
|
||||||
|
"editable": true,
|
||||||
|
"fiscalYearStartMonth": 0,
|
||||||
|
"graphTooltip": 0,
|
||||||
|
"id": null,
|
||||||
|
"links": [],
|
||||||
|
"panels": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"decimals": 0,
|
||||||
|
"unit": "none"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"w": 6,
|
||||||
|
"h": 4
|
||||||
|
},
|
||||||
|
"id": 1,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"pluginVersion": "11.3.1",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_cir_trust_anchors",
|
||||||
|
"legendFormat": "RIRs",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Current Run RIRs",
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "s"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"x": 6,
|
||||||
|
"y": 0,
|
||||||
|
"w": 6,
|
||||||
|
"h": 4
|
||||||
|
},
|
||||||
|
"id": 2,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"pluginVersion": "11.3.1",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_run_duration_seconds",
|
||||||
|
"legendFormat": "wall",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Latest Wall Time",
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "bytes"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"x": 12,
|
||||||
|
"y": 0,
|
||||||
|
"w": 6,
|
||||||
|
"h": 4
|
||||||
|
},
|
||||||
|
"id": 3,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"pluginVersion": "11.3.1",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_run_max_rss_bytes",
|
||||||
|
"legendFormat": "rss",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Latest Max RSS",
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"decimals": 0,
|
||||||
|
"unit": "none"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"x": 18,
|
||||||
|
"y": 0,
|
||||||
|
"w": 6,
|
||||||
|
"h": 4
|
||||||
|
},
|
||||||
|
"id": 4,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"pluginVersion": "11.3.1",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_publication_points",
|
||||||
|
"legendFormat": "publication points",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Publication Points",
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"decimals": 0,
|
||||||
|
"unit": "none"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 4,
|
||||||
|
"w": 6,
|
||||||
|
"h": 4
|
||||||
|
},
|
||||||
|
"id": 9,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"pluginVersion": "11.3.1",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_run_sequence",
|
||||||
|
"legendFormat": "seq",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Latest Run Sequence",
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"decimals": 2,
|
||||||
|
"unit": "percent",
|
||||||
|
"min": 0,
|
||||||
|
"max": 100,
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "orange",
|
||||||
|
"value": 90
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": 98
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"x": 6,
|
||||||
|
"y": 4,
|
||||||
|
"w": 6,
|
||||||
|
"h": 4
|
||||||
|
},
|
||||||
|
"id": 10,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"pluginVersion": "11.3.1",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "100 * sum by (job, instance, exported_instance) (ours_rp_repo_terminal_state_count{terminal_state=\"publication_point_cache\"}) / sum by (job, instance, exported_instance) (ours_rp_publication_points)",
|
||||||
|
"legendFormat": "PP cache hit ratio",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Latest PP Cache Hit Ratio",
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "none",
|
||||||
|
"decimals": 0
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"x": 12,
|
||||||
|
"y": 4,
|
||||||
|
"w": 6,
|
||||||
|
"h": 4
|
||||||
|
},
|
||||||
|
"id": 11,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"pluginVersion": "11.3.1",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_vrps{kind=\"total\"}",
|
||||||
|
"legendFormat": "VRPs raw",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "VRPs",
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "none",
|
||||||
|
"decimals": 0
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"x": 18,
|
||||||
|
"y": 4,
|
||||||
|
"w": 6,
|
||||||
|
"h": 4
|
||||||
|
},
|
||||||
|
"id": 12,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"pluginVersion": "11.3.1",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_vaps",
|
||||||
|
"legendFormat": "VAPs",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "VAPs",
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "s"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 8,
|
||||||
|
"w": 12,
|
||||||
|
"h": 8
|
||||||
|
},
|
||||||
|
"id": 5,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_run_duration_seconds",
|
||||||
|
"legendFormat": "wall",
|
||||||
|
"refId": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_stage_duration_seconds{stage=\"validation\"}",
|
||||||
|
"legendFormat": "validation",
|
||||||
|
"refId": "B"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Run / Validation Duration",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"decimals": 0,
|
||||||
|
"unit": "none"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"x": 12,
|
||||||
|
"y": 8,
|
||||||
|
"w": 12,
|
||||||
|
"h": 8
|
||||||
|
},
|
||||||
|
"id": 6,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "multi",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_vrps{kind=\"total\"}",
|
||||||
|
"legendFormat": "VRPs raw",
|
||||||
|
"refId": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_vrps{kind=\"unique\"}",
|
||||||
|
"legendFormat": "VRPs unique",
|
||||||
|
"refId": "D"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_vaps",
|
||||||
|
"legendFormat": "VAPs",
|
||||||
|
"refId": "B"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_cir_objects",
|
||||||
|
"legendFormat": "CIR objects",
|
||||||
|
"refId": "C"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Output and Input Sizes",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"decimals": 0,
|
||||||
|
"unit": "none"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 16,
|
||||||
|
"w": 12,
|
||||||
|
"h": 8
|
||||||
|
},
|
||||||
|
"id": 8,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "multi",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_large_publication_points",
|
||||||
|
"legendFormat": "> {{object_count_gt}} objects",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Large Publication Points by Object Count",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "s"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"x": 12,
|
||||||
|
"y": 16,
|
||||||
|
"w": 12,
|
||||||
|
"h": 8
|
||||||
|
},
|
||||||
|
"id": 13,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "multi",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_run_stage_duration_seconds{stage=\"validation\"}",
|
||||||
|
"legendFormat": "validation",
|
||||||
|
"refId": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_run_stage_duration_seconds{stage=\"report_write\"}",
|
||||||
|
"legendFormat": "report write",
|
||||||
|
"refId": "E"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_run_stage_duration_seconds{stage=\"ccr_write\"}",
|
||||||
|
"legendFormat": "ccr write",
|
||||||
|
"refId": "F"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_run_stage_duration_seconds{stage=\"cir_write\"}",
|
||||||
|
"legendFormat": "cir write",
|
||||||
|
"refId": "G"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Output Stage Durations",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "bytes",
|
||||||
|
"decimals": 2
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 24,
|
||||||
|
"w": 12,
|
||||||
|
"h": 8
|
||||||
|
},
|
||||||
|
"id": 14,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull",
|
||||||
|
"max"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "multi",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_run_max_rss_bytes",
|
||||||
|
"legendFormat": "Max RSS",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Max RSS Over Time",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "percent",
|
||||||
|
"decimals": 2,
|
||||||
|
"min": 0,
|
||||||
|
"max": 100,
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "orange",
|
||||||
|
"value": 90
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": 98
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"x": 12,
|
||||||
|
"y": 24,
|
||||||
|
"w": 12,
|
||||||
|
"h": 8
|
||||||
|
},
|
||||||
|
"id": 17,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull",
|
||||||
|
"min",
|
||||||
|
"max"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "100 * sum by (job, instance, exported_instance) (ours_rp_repo_terminal_state_count{terminal_state=\"publication_point_cache\"}) / sum by (job, instance, exported_instance) (ours_rp_publication_points)",
|
||||||
|
"legendFormat": "PP cache hit ratio",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "PP Cache Hit Ratio",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "bytes",
|
||||||
|
"decimals": 2
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 32,
|
||||||
|
"w": 24,
|
||||||
|
"h": 8
|
||||||
|
},
|
||||||
|
"id": 15,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull",
|
||||||
|
"max"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "multi",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_state_db_size_bytes",
|
||||||
|
"legendFormat": "{{db}}",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "State DB Size Over Time",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "none",
|
||||||
|
"decimals": 0
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 40,
|
||||||
|
"w": 24,
|
||||||
|
"h": 8
|
||||||
|
},
|
||||||
|
"id": 16,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull",
|
||||||
|
"max"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "multi",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ours_rp_state_db_files",
|
||||||
|
"legendFormat": "{{db}}",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "State DB File Count Over Time",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "none",
|
||||||
|
"decimals": 0,
|
||||||
|
"min": 0
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 48,
|
||||||
|
"w": 24,
|
||||||
|
"h": 8
|
||||||
|
},
|
||||||
|
"id": 18,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull",
|
||||||
|
"max"
|
||||||
|
],
|
||||||
|
"displayMode": "table",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "multi",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "sum by (job, instance, exported_instance) (ours_rp_repo_terminal_state_count{terminal_state=\"fresh\"})",
|
||||||
|
"legendFormat": "fresh pp",
|
||||||
|
"refId": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "sum by (job, instance, exported_instance) (ours_rp_cir_objects_by_source_type{exported_source=\"fresh\",object_type=\"roa\"})",
|
||||||
|
"legendFormat": "fresh roa",
|
||||||
|
"refId": "B"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "sum by (job, instance, exported_instance) (ours_rp_cir_objects_by_source_type{exported_source=\"fresh\",object_type=\"manifest\"})",
|
||||||
|
"legendFormat": "fresh mft",
|
||||||
|
"refId": "C"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "sum by (job, instance, exported_instance) (ours_rp_cir_objects_by_source_type{exported_source=\"fresh\",object_type=\"certificate\"})",
|
||||||
|
"legendFormat": "fresh crt",
|
||||||
|
"refId": "D"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "sum by (job, instance, exported_instance) (ours_rp_cir_objects_by_source_type{exported_source=\"fresh\",object_type=\"crl\"})",
|
||||||
|
"legendFormat": "fresh crl",
|
||||||
|
"refId": "E"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Fresh PP / Object Counts by Run",
|
||||||
|
"type": "timeseries"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"refresh": "5s",
|
||||||
|
"schemaVersion": 40,
|
||||||
|
"tags": [
|
||||||
|
"ours-rp",
|
||||||
|
"rpki",
|
||||||
|
"soak"
|
||||||
|
],
|
||||||
|
"templating": {
|
||||||
|
"list": []
|
||||||
|
},
|
||||||
|
"time": {
|
||||||
|
"from": "now-30m",
|
||||||
|
"to": "now"
|
||||||
|
},
|
||||||
|
"timepicker": {},
|
||||||
|
"timezone": "browser",
|
||||||
|
"title": "Ours RP Soak Overview",
|
||||||
|
"uid": "ours-rp-soak-overview",
|
||||||
|
"version": 4,
|
||||||
|
"weekStart": ""
|
||||||
|
}
|
||||||
12
monitor/grafana/provisioning/dashboards/dashboard.yml
Normal file
12
monitor/grafana/provisioning/dashboards/dashboard.yml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
apiVersion: 1
|
||||||
|
|
||||||
|
providers:
|
||||||
|
- name: ours-rp
|
||||||
|
orgId: 1
|
||||||
|
folder: Ours RP
|
||||||
|
type: file
|
||||||
|
disableDeletion: false
|
||||||
|
updateIntervalSeconds: 10
|
||||||
|
allowUiUpdates: true
|
||||||
|
options:
|
||||||
|
path: /var/lib/grafana/dashboards
|
||||||
10
monitor/grafana/provisioning/datasources/prometheus.yml
Normal file
10
monitor/grafana/provisioning/datasources/prometheus.yml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
apiVersion: 1
|
||||||
|
|
||||||
|
datasources:
|
||||||
|
- name: Prometheus
|
||||||
|
uid: Prometheus
|
||||||
|
type: prometheus
|
||||||
|
access: proxy
|
||||||
|
url: http://prometheus:9090
|
||||||
|
isDefault: true
|
||||||
|
editable: true
|
||||||
20
monitor/prometheus/prometheus.yml
Normal file
20
monitor/prometheus/prometheus.yml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
global:
|
||||||
|
scrape_interval: 5s
|
||||||
|
evaluation_interval: 5s
|
||||||
|
|
||||||
|
scrape_configs:
|
||||||
|
- job_name: ours-rp-artifact-metrics
|
||||||
|
metrics_path: /metrics
|
||||||
|
static_configs:
|
||||||
|
- targets:
|
||||||
|
- host.docker.internal:9556
|
||||||
|
labels:
|
||||||
|
rp: ours-rp
|
||||||
|
source: artifact-sidecar
|
||||||
|
- job_name: ours-rp-inter-rp-metrics
|
||||||
|
metrics_path: /metrics
|
||||||
|
static_configs:
|
||||||
|
- targets:
|
||||||
|
- host.docker.internal:9557
|
||||||
|
labels:
|
||||||
|
source: inter-rp-sidecar
|
||||||
70
scripts/benchmark/README.md
Normal file
70
scripts/benchmark/README.md
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
# RPKI Benchmarks (Stage2, selected_der_v2)
|
||||||
|
|
||||||
|
This directory contains a reproducible, one-click benchmark to measure **decode + profile validate**
|
||||||
|
performance for all supported object types and compare **OURS** against the **Routinator baseline**
|
||||||
|
(`rpki` crate `=0.19.1` with `repository` feature).
|
||||||
|
|
||||||
|
## What it measures
|
||||||
|
|
||||||
|
Dataset:
|
||||||
|
|
||||||
|
- Fixtures: `rpki/tests/benchmark/selected_der_v2/`
|
||||||
|
- Objects: `cer`, `crl`, `manifest` (`.mft`), `roa`, `aspa` (`.asa`)
|
||||||
|
- Samples: 10 quantiles per type (`min/p01/p10/p25/p50/p75/p90/p95/p99/max`) → 50 files total
|
||||||
|
|
||||||
|
Metrics:
|
||||||
|
|
||||||
|
- **decode+validate**: `decode_der` (parse + profile validate) for each object file
|
||||||
|
- **landing** (OURS only): `PackFile::from_bytes_compute_sha256` + CBOR encode + `RocksDB put_raw`
|
||||||
|
- **compare**: ratio `ours_ns/op ÷ rout_ns/op` for decode+validate
|
||||||
|
|
||||||
|
## Default benchmark settings
|
||||||
|
|
||||||
|
Both OURS and Routinator baseline use the same run settings:
|
||||||
|
|
||||||
|
- warmup: `10` iterations
|
||||||
|
- rounds: `3`
|
||||||
|
- adaptive loop target: `min_round_ms=200` (with an internal max of `1_000_000` iters)
|
||||||
|
- strict DER: `true` (baseline)
|
||||||
|
- cert inspect: `false` (baseline)
|
||||||
|
|
||||||
|
You can override the settings via environment variables in the runner script:
|
||||||
|
|
||||||
|
- `BENCH_WARMUP_ITERS` (default `10`)
|
||||||
|
- `BENCH_ROUNDS` (default `3`)
|
||||||
|
- `BENCH_MIN_ROUND_MS` (default `200`)
|
||||||
|
|
||||||
|
## One-click run (OURS + Routinator compare)
|
||||||
|
|
||||||
|
From the `rpki/` crate directory:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/benchmark/run_stage2_selected_der_v2_release.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Outputs are written under:
|
||||||
|
|
||||||
|
- `rpki/target/bench/`
|
||||||
|
- OURS decode+validate: `stage2_selected_der_v2_decode_release_<TS>.{md,csv}`
|
||||||
|
- OURS landing: `stage2_selected_der_v2_landing_release_<TS>.{md,csv}`
|
||||||
|
- Routinator: `stage2_selected_der_v2_routinator_decode_release_<TS>.{md,csv}`
|
||||||
|
- Compare: `stage2_selected_der_v2_compare_ours_vs_routinator_decode_release_<TS>.{md,csv}`
|
||||||
|
- Summary: `stage2_selected_der_v2_compare_summary_<TS>.md`
|
||||||
|
|
||||||
|
### Why decode and landing are separated
|
||||||
|
|
||||||
|
The underlying benchmark can run in `BENCH_MODE=both`, but the **landing** part writes to RocksDB
|
||||||
|
and may trigger background work (e.g., compactions) that can **skew subsequent decode timings**.
|
||||||
|
For a fair OURS-vs-Routinator comparison, the runner script:
|
||||||
|
|
||||||
|
- runs `BENCH_MODE=decode_validate` for comparison, and
|
||||||
|
- runs `BENCH_MODE=landing` separately for landing-only numbers.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- The Routinator baseline benchmark is implemented in-repo under:
|
||||||
|
- `rpki/benchmark/routinator_object_bench/`
|
||||||
|
- It pins `rpki = "=0.19.1"` in its `Cargo.toml`.
|
||||||
|
- This benchmark is implemented as an `#[ignore]` integration test:
|
||||||
|
- `rpki/tests/bench_stage2_decode_profile_selected_der_v2.rs`
|
||||||
|
- The runner script invokes it with `cargo test --release ... -- --ignored --nocapture`.
|
||||||
123
scripts/benchmark/run_stage2_selected_der_v2_release.sh
Executable file
123
scripts/benchmark/run_stage2_selected_der_v2_release.sh
Executable file
@@ -0,0 +1,123 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Stage2 (selected_der_v2) decode+profile validate benchmark.
|
||||||
|
# Runs:
|
||||||
|
# 1) OURS decode+validate benchmark and writes MD/CSV.
|
||||||
|
# 2) OURS landing benchmark and writes MD/CSV.
|
||||||
|
# 3) Routinator baseline decode benchmark (rpki crate =0.19.1).
|
||||||
|
# 4) Produces a joined compare CSV/MD and a short geomean summary.
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
|
cd "$ROOT_DIR"
|
||||||
|
|
||||||
|
OUT_DIR="$ROOT_DIR/target/bench"
|
||||||
|
mkdir -p "$OUT_DIR"
|
||||||
|
|
||||||
|
TS="$(date -u +%Y%m%dT%H%M%SZ)"
|
||||||
|
|
||||||
|
WARMUP_ITERS="${BENCH_WARMUP_ITERS:-10}"
|
||||||
|
ROUNDS="${BENCH_ROUNDS:-3}"
|
||||||
|
MIN_ROUND_MS="${BENCH_MIN_ROUND_MS:-200}"
|
||||||
|
|
||||||
|
OURS_MD="$OUT_DIR/stage2_selected_der_v2_decode_release_${TS}.md"
|
||||||
|
OURS_CSV="$OUT_DIR/stage2_selected_der_v2_decode_release_${TS}.csv"
|
||||||
|
|
||||||
|
OURS_LANDING_MD="$OUT_DIR/stage2_selected_der_v2_landing_release_${TS}.md"
|
||||||
|
OURS_LANDING_CSV="$OUT_DIR/stage2_selected_der_v2_landing_release_${TS}.csv"
|
||||||
|
|
||||||
|
ROUT_MD="$OUT_DIR/stage2_selected_der_v2_routinator_decode_release_${TS}.md"
|
||||||
|
ROUT_CSV="$OUT_DIR/stage2_selected_der_v2_routinator_decode_release_${TS}.csv"
|
||||||
|
|
||||||
|
COMPARE_MD="$OUT_DIR/stage2_selected_der_v2_compare_ours_vs_routinator_decode_release_${TS}.md"
|
||||||
|
COMPARE_CSV="$OUT_DIR/stage2_selected_der_v2_compare_ours_vs_routinator_decode_release_${TS}.csv"
|
||||||
|
|
||||||
|
SUMMARY_MD="$OUT_DIR/stage2_selected_der_v2_compare_summary_${TS}.md"
|
||||||
|
|
||||||
|
echo "[1/4] OURS: decode+validate benchmark (release)..." >&2
|
||||||
|
BENCH_MODE="decode_validate" \
|
||||||
|
BENCH_WARMUP_ITERS="$WARMUP_ITERS" \
|
||||||
|
BENCH_ROUNDS="$ROUNDS" \
|
||||||
|
BENCH_MIN_ROUND_MS="$MIN_ROUND_MS" \
|
||||||
|
BENCH_OUT_MD="$OURS_MD" \
|
||||||
|
BENCH_OUT_CSV="$OURS_CSV" \
|
||||||
|
cargo test --release --test bench_stage2_decode_profile_selected_der_v2 -- --ignored --nocapture >/dev/null
|
||||||
|
|
||||||
|
echo "[2/4] OURS: landing benchmark (release)..." >&2
|
||||||
|
BENCH_MODE="landing" \
|
||||||
|
BENCH_WARMUP_ITERS="$WARMUP_ITERS" \
|
||||||
|
BENCH_ROUNDS="$ROUNDS" \
|
||||||
|
BENCH_MIN_ROUND_MS="$MIN_ROUND_MS" \
|
||||||
|
BENCH_OUT_MD_LANDING="$OURS_LANDING_MD" \
|
||||||
|
BENCH_OUT_CSV_LANDING="$OURS_LANDING_CSV" \
|
||||||
|
cargo test --release --test bench_stage2_decode_profile_selected_der_v2 -- --ignored --nocapture >/dev/null
|
||||||
|
|
||||||
|
echo "[3/4] Routinator baseline + compare join..." >&2
|
||||||
|
OURS_CSV="$OURS_CSV" \
|
||||||
|
ROUT_CSV="$ROUT_CSV" \
|
||||||
|
ROUT_MD="$ROUT_MD" \
|
||||||
|
COMPARE_CSV="$COMPARE_CSV" \
|
||||||
|
COMPARE_MD="$COMPARE_MD" \
|
||||||
|
WARMUP_ITERS="$WARMUP_ITERS" \
|
||||||
|
ROUNDS="$ROUNDS" \
|
||||||
|
MIN_ROUND_MS="$MIN_ROUND_MS" \
|
||||||
|
scripts/stage2_perf_compare_m4.sh >/dev/null
|
||||||
|
|
||||||
|
echo "[4/4] Summary (geomean ratios)..." >&2
|
||||||
|
python3 - "$COMPARE_CSV" "$SUMMARY_MD" <<'PY'
|
||||||
|
import csv
|
||||||
|
import math
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
|
in_csv = Path(sys.argv[1])
|
||||||
|
out_md = Path(sys.argv[2])
|
||||||
|
|
||||||
|
rows = list(csv.DictReader(in_csv.open(newline="")))
|
||||||
|
ratios = {}
|
||||||
|
for r in rows:
|
||||||
|
ratios.setdefault(r["type"], []).append(float(r["ratio_ours_over_rout"]))
|
||||||
|
|
||||||
|
def geomean(vals):
|
||||||
|
return math.exp(sum(math.log(v) for v in vals) / len(vals))
|
||||||
|
|
||||||
|
def p50(vals):
|
||||||
|
v = sorted(vals)
|
||||||
|
n = len(v)
|
||||||
|
if n % 2 == 1:
|
||||||
|
return v[n // 2]
|
||||||
|
return (v[n // 2 - 1] + v[n // 2]) / 2.0
|
||||||
|
|
||||||
|
all_vals = [float(r["ratio_ours_over_rout"]) for r in rows]
|
||||||
|
types = ["all"] + sorted(ratios.keys())
|
||||||
|
|
||||||
|
now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||||
|
lines = []
|
||||||
|
lines.append("# Stage2 selected_der_v2 compare summary (release)\n\n")
|
||||||
|
lines.append(f"- recorded_at_utc: `{now}`\n")
|
||||||
|
lines.append(f"- inputs_csv: `{in_csv}`\n\n")
|
||||||
|
lines.append("| type | n | min | p50 | geomean | max | >1 count |\n")
|
||||||
|
lines.append("|---|---:|---:|---:|---:|---:|---:|\n")
|
||||||
|
|
||||||
|
for t in types:
|
||||||
|
vals = all_vals if t == "all" else ratios[t]
|
||||||
|
vals_sorted = sorted(vals)
|
||||||
|
lines.append(
|
||||||
|
f"| {t} | {len(vals_sorted)} | {vals_sorted[0]:.4f} | {p50(vals_sorted):.4f} | "
|
||||||
|
f"{geomean(vals_sorted):.4f} | {vals_sorted[-1]:.4f} | {sum(1 for v in vals_sorted if v>1.0)} |\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
out_md.write_text("".join(lines), encoding="utf-8")
|
||||||
|
print(out_md)
|
||||||
|
PY
|
||||||
|
|
||||||
|
echo "Done." >&2
|
||||||
|
echo "- OURS decode MD: $OURS_MD" >&2
|
||||||
|
echo "- OURS decode CSV: $OURS_CSV" >&2
|
||||||
|
echo "- OURS landing MD: $OURS_LANDING_MD" >&2
|
||||||
|
echo "- OURS landing CSV: $OURS_LANDING_CSV" >&2
|
||||||
|
echo "- Routinator: $ROUT_MD" >&2
|
||||||
|
echo "- Compare MD: $COMPARE_MD" >&2
|
||||||
|
echo "- Compare CSV: $COMPARE_CSV" >&2
|
||||||
|
echo "- Summary MD: $SUMMARY_MD" >&2
|
||||||
56
scripts/cir/README.md
Normal file
56
scripts/cir/README.md
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
# CIR Scripts
|
||||||
|
|
||||||
|
## `cir-rsync-wrapper`
|
||||||
|
|
||||||
|
一个用于 CIR 黑盒 replay 的 rsync wrapper。
|
||||||
|
|
||||||
|
### 环境变量
|
||||||
|
|
||||||
|
- `REAL_RSYNC_BIN`
|
||||||
|
- 真实 rsync 二进制路径
|
||||||
|
- 默认优先 `/usr/bin/rsync`
|
||||||
|
- `CIR_MIRROR_ROOT`
|
||||||
|
- 本地镜像树根目录
|
||||||
|
- 当命令行中出现 `rsync://...` source 时必需
|
||||||
|
|
||||||
|
### 语义
|
||||||
|
|
||||||
|
- 仅改写 `rsync://host/path` 类型参数
|
||||||
|
- 其它参数原样透传给真实 rsync
|
||||||
|
- 改写目标:
|
||||||
|
- `rsync://example.net/repo/a.roa`
|
||||||
|
- →
|
||||||
|
- `<CIR_MIRROR_ROOT>/example.net/repo/a.roa`
|
||||||
|
|
||||||
|
### 兼容目标
|
||||||
|
|
||||||
|
- Routinator `--rsync-command`
|
||||||
|
- `rpki-client -e rsync_prog`
|
||||||
|
|
||||||
|
## 其它脚本
|
||||||
|
|
||||||
|
- `run_cir_replay_ours.sh`
|
||||||
|
- `run_cir_replay_routinator.sh`
|
||||||
|
- `run_cir_replay_rpki_client.sh`
|
||||||
|
- `run_cir_replay_matrix.sh`
|
||||||
|
|
||||||
|
## `cir-local-link-sync.py`
|
||||||
|
|
||||||
|
当 `CIR_LOCAL_LINK_MODE=1` 且 wrapper 检测到 source 已经被改写为本地 mirror 路径时,
|
||||||
|
wrapper 不再调用真实 `rsync`,而是调用这个 helper 完成:
|
||||||
|
|
||||||
|
- `hardlink` 优先的本地树同步
|
||||||
|
- 失败时回退到 copy
|
||||||
|
- 支持 `--delete`
|
||||||
|
|
||||||
|
`run_cir_replay_matrix.sh` 会顺序执行:
|
||||||
|
|
||||||
|
- `ours`
|
||||||
|
- Routinator
|
||||||
|
- `rpki-client`
|
||||||
|
|
||||||
|
并汇总生成:
|
||||||
|
|
||||||
|
- `summary.json`
|
||||||
|
- `summary.md`
|
||||||
|
- `detail.md`
|
||||||
136
scripts/cir/cir-local-link-sync.py
Executable file
136
scripts/cir/cir-local-link-sync.py
Executable file
@@ -0,0 +1,136 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import argparse
|
||||||
|
import errno
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def _same_inode(src: Path, dst: Path) -> bool:
|
||||||
|
try:
|
||||||
|
src_stat = src.stat()
|
||||||
|
dst_stat = dst.stat()
|
||||||
|
except FileNotFoundError:
|
||||||
|
return False
|
||||||
|
return (src_stat.st_dev, src_stat.st_ino) == (dst_stat.st_dev, dst_stat.st_ino)
|
||||||
|
|
||||||
|
|
||||||
|
def _remove_path(path: Path) -> None:
|
||||||
|
if not path.exists() and not path.is_symlink():
|
||||||
|
return
|
||||||
|
if path.is_dir() and not path.is_symlink():
|
||||||
|
shutil.rmtree(path)
|
||||||
|
else:
|
||||||
|
path.unlink()
|
||||||
|
|
||||||
|
|
||||||
|
def _prune_empty_dirs(root: Path) -> None:
|
||||||
|
if not root.exists():
|
||||||
|
return
|
||||||
|
for path in sorted((p for p in root.rglob("*") if p.is_dir()), key=lambda p: len(p.parts), reverse=True):
|
||||||
|
try:
|
||||||
|
path.rmdir()
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def _link_or_copy(src: Path, dst: Path) -> str:
|
||||||
|
dst.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
if dst.exists() or dst.is_symlink():
|
||||||
|
if _same_inode(src, dst):
|
||||||
|
return "reused"
|
||||||
|
_remove_path(dst)
|
||||||
|
try:
|
||||||
|
os.link(src, dst)
|
||||||
|
return "linked"
|
||||||
|
except OSError as err:
|
||||||
|
if err.errno not in (errno.EXDEV, errno.EPERM, errno.EMLINK, errno.ENOTSUP, errno.EACCES):
|
||||||
|
raise
|
||||||
|
shutil.copy2(src, dst)
|
||||||
|
return "copied"
|
||||||
|
|
||||||
|
|
||||||
|
def _file_map(src_arg: str, dest_arg: str) -> tuple[Path, dict[str, Path]]:
|
||||||
|
src = Path(src_arg.rstrip(os.sep))
|
||||||
|
if not src.exists():
|
||||||
|
raise FileNotFoundError(src)
|
||||||
|
mapping: dict[str, Path] = {}
|
||||||
|
if src.is_dir():
|
||||||
|
copy_contents = src_arg.endswith(os.sep)
|
||||||
|
if copy_contents:
|
||||||
|
root = src
|
||||||
|
for path in root.rglob("*"):
|
||||||
|
if path.is_file():
|
||||||
|
mapping[path.relative_to(root).as_posix()] = path
|
||||||
|
else:
|
||||||
|
root = src
|
||||||
|
base = src.name
|
||||||
|
for path in root.rglob("*"):
|
||||||
|
if path.is_file():
|
||||||
|
rel = Path(base) / path.relative_to(root)
|
||||||
|
mapping[rel.as_posix()] = path
|
||||||
|
else:
|
||||||
|
dest_path = Path(dest_arg)
|
||||||
|
if dest_arg.endswith(os.sep) or dest_path.is_dir():
|
||||||
|
mapping[src.name] = src
|
||||||
|
else:
|
||||||
|
mapping[dest_path.name] = src
|
||||||
|
return Path(dest_arg), mapping
|
||||||
|
|
||||||
|
|
||||||
|
def sync_local_tree(src_arg: str, dst_arg: str, delete: bool) -> dict[str, int]:
|
||||||
|
dst_root, mapping = _file_map(src_arg, dst_arg)
|
||||||
|
dst_root.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
expected = {dst_root / rel for rel in mapping.keys()}
|
||||||
|
|
||||||
|
deleted = 0
|
||||||
|
if delete and dst_root.exists():
|
||||||
|
for path in sorted(dst_root.rglob("*"), key=lambda p: len(p.parts), reverse=True):
|
||||||
|
if path.is_dir():
|
||||||
|
continue
|
||||||
|
if path not in expected:
|
||||||
|
_remove_path(path)
|
||||||
|
deleted += 1
|
||||||
|
_prune_empty_dirs(dst_root)
|
||||||
|
|
||||||
|
linked = 0
|
||||||
|
copied = 0
|
||||||
|
reused = 0
|
||||||
|
for rel, src in mapping.items():
|
||||||
|
dst = dst_root / rel
|
||||||
|
result = _link_or_copy(src, dst)
|
||||||
|
if result == "linked":
|
||||||
|
linked += 1
|
||||||
|
elif result == "copied":
|
||||||
|
copied += 1
|
||||||
|
else:
|
||||||
|
reused += 1
|
||||||
|
|
||||||
|
return {
|
||||||
|
"files": len(mapping),
|
||||||
|
"linked": linked,
|
||||||
|
"copied": copied,
|
||||||
|
"reused": reused,
|
||||||
|
"deleted": deleted,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser(description="Sync a local CIR mirror tree using hardlinks when possible.")
|
||||||
|
parser.add_argument("--delete", action="store_true", help="Delete target files not present in source")
|
||||||
|
parser.add_argument("source")
|
||||||
|
parser.add_argument("dest")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
summary = sync_local_tree(args.source, args.dest, args.delete)
|
||||||
|
print(
|
||||||
|
"local-link-sync files={files} linked={linked} copied={copied} reused={reused} deleted={deleted}".format(
|
||||||
|
**summary
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
127
scripts/cir/cir-rsync-wrapper
Executable file
127
scripts/cir/cir-rsync-wrapper
Executable file
@@ -0,0 +1,127 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
|
||||||
|
def real_rsync_bin() -> str:
|
||||||
|
env = os.environ.get("REAL_RSYNC_BIN")
|
||||||
|
if env:
|
||||||
|
return env
|
||||||
|
default = "/usr/bin/rsync"
|
||||||
|
if Path(default).exists():
|
||||||
|
return default
|
||||||
|
found = shutil.which("rsync")
|
||||||
|
if found:
|
||||||
|
return found
|
||||||
|
raise SystemExit("cir-rsync-wrapper: REAL_RSYNC_BIN is not set and rsync was not found")
|
||||||
|
|
||||||
|
|
||||||
|
def rewrite_arg(arg: str, mirror_root: str | None) -> str:
|
||||||
|
if not arg.startswith("rsync://"):
|
||||||
|
return arg
|
||||||
|
if not mirror_root:
|
||||||
|
raise SystemExit(
|
||||||
|
"cir-rsync-wrapper: CIR_MIRROR_ROOT is required when an rsync:// source is present"
|
||||||
|
)
|
||||||
|
parsed = urlparse(arg)
|
||||||
|
if parsed.scheme != "rsync" or not parsed.hostname:
|
||||||
|
raise SystemExit(f"cir-rsync-wrapper: invalid rsync URI: {arg}")
|
||||||
|
path = parsed.path.lstrip("/")
|
||||||
|
local = Path(mirror_root).resolve() / parsed.hostname
|
||||||
|
if path:
|
||||||
|
local = local / path
|
||||||
|
local_str = str(local)
|
||||||
|
if local.exists() and local.is_dir() and not local_str.endswith("/"):
|
||||||
|
local_str += "/"
|
||||||
|
elif arg.endswith("/") and not local_str.endswith("/"):
|
||||||
|
local_str += "/"
|
||||||
|
return local_str
|
||||||
|
|
||||||
|
|
||||||
|
def filter_args(args: list[str]) -> list[str]:
|
||||||
|
mirror_root = os.environ.get("CIR_MIRROR_ROOT")
|
||||||
|
rewritten_any = any(arg.startswith("rsync://") for arg in args)
|
||||||
|
out: list[str] = []
|
||||||
|
i = 0
|
||||||
|
while i < len(args):
|
||||||
|
arg = args[i]
|
||||||
|
if rewritten_any:
|
||||||
|
if arg == "--address":
|
||||||
|
i += 2
|
||||||
|
continue
|
||||||
|
if arg.startswith("--address="):
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
if arg == "--contimeout":
|
||||||
|
i += 2
|
||||||
|
continue
|
||||||
|
if arg.startswith("--contimeout="):
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
out.append(rewrite_arg(arg, mirror_root))
|
||||||
|
i += 1
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def local_link_mode_enabled() -> bool:
|
||||||
|
value = os.environ.get("CIR_LOCAL_LINK_MODE", "")
|
||||||
|
return value.lower() in {"1", "true", "yes", "on"}
|
||||||
|
|
||||||
|
|
||||||
|
def extract_source_and_dest(args: list[str]) -> tuple[str, str]:
|
||||||
|
expects_value = {
|
||||||
|
"--timeout",
|
||||||
|
"--min-size",
|
||||||
|
"--max-size",
|
||||||
|
"--include",
|
||||||
|
"--exclude",
|
||||||
|
"--compare-dest",
|
||||||
|
}
|
||||||
|
positionals: list[str] = []
|
||||||
|
i = 0
|
||||||
|
while i < len(args):
|
||||||
|
arg = args[i]
|
||||||
|
if arg in expects_value:
|
||||||
|
i += 2
|
||||||
|
continue
|
||||||
|
if any(arg.startswith(prefix + "=") for prefix in expects_value):
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
if arg.startswith("-"):
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
positionals.append(arg)
|
||||||
|
i += 1
|
||||||
|
if len(positionals) < 2:
|
||||||
|
raise SystemExit("cir-rsync-wrapper: expected source and destination arguments")
|
||||||
|
return positionals[-2], positionals[-1]
|
||||||
|
|
||||||
|
|
||||||
|
def maybe_exec_local_link_sync(args: list[str], rewritten_any: bool) -> None:
|
||||||
|
if not rewritten_any or not local_link_mode_enabled():
|
||||||
|
return
|
||||||
|
source, dest = extract_source_and_dest(args)
|
||||||
|
if source.startswith("rsync://"):
|
||||||
|
raise SystemExit("cir-rsync-wrapper: expected rewritten local source for CIR_LOCAL_LINK_MODE")
|
||||||
|
helper = Path(__file__).with_name("cir-local-link-sync.py")
|
||||||
|
cmd = [sys.executable, str(helper)]
|
||||||
|
if "--delete" in args:
|
||||||
|
cmd.append("--delete")
|
||||||
|
cmd.extend([source, dest])
|
||||||
|
os.execv(sys.executable, cmd)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
args = sys.argv[1:]
|
||||||
|
rewritten_any = any(arg.startswith("rsync://") for arg in args)
|
||||||
|
rewritten = filter_args(args)
|
||||||
|
maybe_exec_local_link_sync(rewritten, rewritten_any)
|
||||||
|
os.execv(real_rsync_bin(), [real_rsync_bin(), *rewritten])
|
||||||
|
return 127
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
32
scripts/cir/fetch_cir_sequence_from_remote.sh
Executable file
32
scripts/cir/fetch_cir_sequence_from_remote.sh
Executable file
@@ -0,0 +1,32 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage:
|
||||||
|
./scripts/cir/fetch_cir_sequence_from_remote.sh \
|
||||||
|
--ssh-target <user@host> \
|
||||||
|
--remote-path <path> \
|
||||||
|
--local-path <path>
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
SSH_TARGET=""
|
||||||
|
REMOTE_PATH=""
|
||||||
|
LOCAL_PATH=""
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--ssh-target) SSH_TARGET="$2"; shift 2 ;;
|
||||||
|
--remote-path) REMOTE_PATH="$2"; shift 2 ;;
|
||||||
|
--local-path) LOCAL_PATH="$2"; shift 2 ;;
|
||||||
|
-h|--help) usage; exit 0 ;;
|
||||||
|
*) echo "unknown argument: $1" >&2; usage; exit 2 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
[[ -n "$SSH_TARGET" && -n "$REMOTE_PATH" && -n "$LOCAL_PATH" ]] || { usage >&2; exit 2; }
|
||||||
|
|
||||||
|
mkdir -p "$(dirname "$LOCAL_PATH")"
|
||||||
|
rsync -a "$SSH_TARGET:$REMOTE_PATH/" "$LOCAL_PATH/"
|
||||||
|
echo "done: $LOCAL_PATH"
|
||||||
50
scripts/cir/json_to_vaps_csv.py
Executable file
50
scripts/cir/json_to_vaps_csv.py
Executable file
@@ -0,0 +1,50 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import csv
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_asn(value: str | int) -> str:
|
||||||
|
text = str(value).strip().upper()
|
||||||
|
if text.startswith("AS"):
|
||||||
|
text = text[2:]
|
||||||
|
return f"AS{int(text)}"
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("--input", required=True, type=Path)
|
||||||
|
parser.add_argument("--csv-out", required=True, type=Path)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
obj = json.loads(args.input.read_text(encoding="utf-8"))
|
||||||
|
rows: list[tuple[str, str, str]] = []
|
||||||
|
for aspa in obj.get("aspas", []):
|
||||||
|
providers = sorted(
|
||||||
|
{normalize_asn(item) for item in aspa.get("providers", [])},
|
||||||
|
key=lambda s: int(s[2:]),
|
||||||
|
)
|
||||||
|
rows.append(
|
||||||
|
(
|
||||||
|
normalize_asn(aspa["customer"]),
|
||||||
|
";".join(providers),
|
||||||
|
str(aspa.get("ta", "")).strip().lower(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
rows.sort(key=lambda row: (int(row[0][2:]), row[1], row[2]))
|
||||||
|
|
||||||
|
args.csv_out.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
with args.csv_out.open("w", encoding="utf-8", newline="") as fh:
|
||||||
|
writer = csv.writer(fh)
|
||||||
|
writer.writerow(["Customer ASN", "Providers", "Trust Anchor"])
|
||||||
|
writer.writerows(rows)
|
||||||
|
print(args.csv_out)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
77
scripts/cir/run_cir_drop_sequence.sh
Executable file
77
scripts/cir/run_cir_drop_sequence.sh
Executable file
@@ -0,0 +1,77 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage:
|
||||||
|
./scripts/cir/run_cir_drop_sequence.sh \
|
||||||
|
--sequence-root <path> \
|
||||||
|
[--drop-bin <path>]
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
|
SEQUENCE_ROOT=""
|
||||||
|
DROP_BIN="${DROP_BIN:-$ROOT_DIR/target/release/cir_drop_report}"
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--sequence-root) SEQUENCE_ROOT="$2"; shift 2 ;;
|
||||||
|
--drop-bin) DROP_BIN="$2"; shift 2 ;;
|
||||||
|
-h|--help) usage; exit 0 ;;
|
||||||
|
*) echo "unknown argument: $1" >&2; usage; exit 2 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
[[ -n "$SEQUENCE_ROOT" ]] || { usage >&2; exit 2; }
|
||||||
|
|
||||||
|
python3 - <<'PY' "$SEQUENCE_ROOT" "$DROP_BIN"
|
||||||
|
import json
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
sequence_root = Path(sys.argv[1]).resolve()
|
||||||
|
drop_bin = sys.argv[2]
|
||||||
|
sequence = json.loads((sequence_root / "sequence.json").read_text(encoding="utf-8"))
|
||||||
|
repo_bytes_db = sequence_root / sequence["repoBytesDbPath"]
|
||||||
|
|
||||||
|
summaries = []
|
||||||
|
for step in sequence["steps"]:
|
||||||
|
step_id = step["stepId"]
|
||||||
|
out_dir = sequence_root / "drop" / step_id
|
||||||
|
out_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
cmd = [
|
||||||
|
drop_bin,
|
||||||
|
"--cir",
|
||||||
|
str(sequence_root / step["cirPath"]),
|
||||||
|
"--ccr",
|
||||||
|
str(sequence_root / step["ccrPath"]),
|
||||||
|
"--report-json",
|
||||||
|
str(sequence_root / step["reportPath"]),
|
||||||
|
"--json-out",
|
||||||
|
str(out_dir / "drop.json"),
|
||||||
|
"--md-out",
|
||||||
|
str(out_dir / "drop.md"),
|
||||||
|
]
|
||||||
|
cmd.extend(["--repo-bytes-db", str(repo_bytes_db)])
|
||||||
|
proc = subprocess.run(cmd, capture_output=True, text=True)
|
||||||
|
if proc.returncode != 0:
|
||||||
|
raise SystemExit(
|
||||||
|
f"drop report failed for {step_id}: stdout={proc.stdout} stderr={proc.stderr}"
|
||||||
|
)
|
||||||
|
result = json.loads((out_dir / "drop.json").read_text(encoding="utf-8"))
|
||||||
|
summaries.append(
|
||||||
|
{
|
||||||
|
"stepId": step_id,
|
||||||
|
"droppedVrpCount": result["summary"]["droppedVrpCount"],
|
||||||
|
"droppedObjectCount": result["summary"]["droppedObjectCount"],
|
||||||
|
"reportPath": str(out_dir / "drop.json"),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
summary = {"version": 1, "steps": summaries}
|
||||||
|
(sequence_root / "drop-summary.json").write_text(json.dumps(summary, indent=2), encoding="utf-8")
|
||||||
|
PY
|
||||||
|
|
||||||
|
echo "done: $SEQUENCE_ROOT"
|
||||||
173
scripts/cir/run_cir_record_full_delta.sh
Executable file
173
scripts/cir/run_cir_record_full_delta.sh
Executable file
@@ -0,0 +1,173 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage:
|
||||||
|
./scripts/cir/run_cir_record_full_delta.sh \
|
||||||
|
--out-dir <path> \
|
||||||
|
--tal-path <path> \
|
||||||
|
--ta-path <path> \
|
||||||
|
--cir-tal-uri <url> \
|
||||||
|
--payload-replay-archive <path> \
|
||||||
|
--payload-replay-locks <path> \
|
||||||
|
--payload-base-archive <path> \
|
||||||
|
--payload-base-locks <path> \
|
||||||
|
--payload-delta-archive <path> \
|
||||||
|
--payload-delta-locks <path> \
|
||||||
|
[--base-validation-time <rfc3339>] \
|
||||||
|
[--delta-validation-time <rfc3339>] \
|
||||||
|
[--max-depth <n>] \
|
||||||
|
[--max-instances <n>] \
|
||||||
|
[--rpki-bin <path>]
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
|
|
||||||
|
OUT_DIR=""
|
||||||
|
TAL_PATH=""
|
||||||
|
TA_PATH=""
|
||||||
|
CIR_TAL_URI=""
|
||||||
|
PAYLOAD_REPLAY_ARCHIVE=""
|
||||||
|
PAYLOAD_REPLAY_LOCKS=""
|
||||||
|
PAYLOAD_BASE_ARCHIVE=""
|
||||||
|
PAYLOAD_BASE_LOCKS=""
|
||||||
|
PAYLOAD_DELTA_ARCHIVE=""
|
||||||
|
PAYLOAD_DELTA_LOCKS=""
|
||||||
|
BASE_VALIDATION_TIME=""
|
||||||
|
DELTA_VALIDATION_TIME=""
|
||||||
|
MAX_DEPTH=0
|
||||||
|
MAX_INSTANCES=1
|
||||||
|
RPKI_BIN="${RPKI_BIN:-$ROOT_DIR/target/release/rpki}"
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--out-dir) OUT_DIR="$2"; shift 2 ;;
|
||||||
|
--tal-path) TAL_PATH="$2"; shift 2 ;;
|
||||||
|
--ta-path) TA_PATH="$2"; shift 2 ;;
|
||||||
|
--cir-tal-uri) CIR_TAL_URI="$2"; shift 2 ;;
|
||||||
|
--payload-replay-archive) PAYLOAD_REPLAY_ARCHIVE="$2"; shift 2 ;;
|
||||||
|
--payload-replay-locks) PAYLOAD_REPLAY_LOCKS="$2"; shift 2 ;;
|
||||||
|
--payload-base-archive) PAYLOAD_BASE_ARCHIVE="$2"; shift 2 ;;
|
||||||
|
--payload-base-locks) PAYLOAD_BASE_LOCKS="$2"; shift 2 ;;
|
||||||
|
--payload-delta-archive) PAYLOAD_DELTA_ARCHIVE="$2"; shift 2 ;;
|
||||||
|
--payload-delta-locks) PAYLOAD_DELTA_LOCKS="$2"; shift 2 ;;
|
||||||
|
--base-validation-time) BASE_VALIDATION_TIME="$2"; shift 2 ;;
|
||||||
|
--delta-validation-time) DELTA_VALIDATION_TIME="$2"; shift 2 ;;
|
||||||
|
--max-depth) MAX_DEPTH="$2"; shift 2 ;;
|
||||||
|
--max-instances) MAX_INSTANCES="$2"; shift 2 ;;
|
||||||
|
--rpki-bin) RPKI_BIN="$2"; shift 2 ;;
|
||||||
|
-h|--help) usage; exit 0 ;;
|
||||||
|
*) echo "unknown argument: $1" >&2; usage; exit 2 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
[[ -n "$OUT_DIR" && -n "$TAL_PATH" && -n "$TA_PATH" && -n "$CIR_TAL_URI" && -n "$PAYLOAD_REPLAY_ARCHIVE" && -n "$PAYLOAD_REPLAY_LOCKS" && -n "$PAYLOAD_BASE_ARCHIVE" && -n "$PAYLOAD_BASE_LOCKS" && -n "$PAYLOAD_DELTA_ARCHIVE" && -n "$PAYLOAD_DELTA_LOCKS" ]] || {
|
||||||
|
usage >&2
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ ! -x "$RPKI_BIN" ]]; then
|
||||||
|
(
|
||||||
|
cd "$ROOT_DIR"
|
||||||
|
cargo build --release --bin rpki
|
||||||
|
)
|
||||||
|
fi
|
||||||
|
|
||||||
|
resolve_validation_time() {
|
||||||
|
local path="$1"
|
||||||
|
python3 - <<'PY' "$path"
|
||||||
|
import json, sys
|
||||||
|
print(json.load(open(sys.argv[1], 'r', encoding='utf-8'))['validationTime'])
|
||||||
|
PY
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ -z "$BASE_VALIDATION_TIME" ]]; then
|
||||||
|
BASE_VALIDATION_TIME="$(resolve_validation_time "$PAYLOAD_REPLAY_LOCKS")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "$DELTA_VALIDATION_TIME" ]]; then
|
||||||
|
DELTA_VALIDATION_TIME="$(resolve_validation_time "$PAYLOAD_DELTA_LOCKS")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -rf "$OUT_DIR"
|
||||||
|
mkdir -p "$OUT_DIR/full" "$OUT_DIR/delta-001"
|
||||||
|
REPO_BYTES_DB="$OUT_DIR/repo-bytes.db"
|
||||||
|
|
||||||
|
FULL_DB="$OUT_DIR/full/db"
|
||||||
|
DELTA_DB="$OUT_DIR/delta-001/db"
|
||||||
|
|
||||||
|
"$RPKI_BIN" \
|
||||||
|
--db "$FULL_DB" \
|
||||||
|
--tal-path "$TAL_PATH" \
|
||||||
|
--ta-path "$TA_PATH" \
|
||||||
|
--payload-replay-archive "$PAYLOAD_REPLAY_ARCHIVE" \
|
||||||
|
--payload-replay-locks "$PAYLOAD_REPLAY_LOCKS" \
|
||||||
|
--validation-time "$BASE_VALIDATION_TIME" \
|
||||||
|
--max-depth "$MAX_DEPTH" \
|
||||||
|
--max-instances "$MAX_INSTANCES" \
|
||||||
|
--ccr-out "$OUT_DIR/full/result.ccr" \
|
||||||
|
--report-json "$OUT_DIR/full/report.json" \
|
||||||
|
--cir-enable \
|
||||||
|
--cir-out "$OUT_DIR/full/input.cir" \
|
||||||
|
--repo-bytes-db "$REPO_BYTES_DB" \
|
||||||
|
--cir-tal-uri "$CIR_TAL_URI" \
|
||||||
|
>"$OUT_DIR/full/run.stdout.log" 2>"$OUT_DIR/full/run.stderr.log"
|
||||||
|
|
||||||
|
"$RPKI_BIN" \
|
||||||
|
--db "$DELTA_DB" \
|
||||||
|
--tal-path "$TAL_PATH" \
|
||||||
|
--ta-path "$TA_PATH" \
|
||||||
|
--payload-base-archive "$PAYLOAD_BASE_ARCHIVE" \
|
||||||
|
--payload-base-locks "$PAYLOAD_BASE_LOCKS" \
|
||||||
|
--payload-delta-archive "$PAYLOAD_DELTA_ARCHIVE" \
|
||||||
|
--payload-delta-locks "$PAYLOAD_DELTA_LOCKS" \
|
||||||
|
--payload-base-validation-time "$BASE_VALIDATION_TIME" \
|
||||||
|
--validation-time "$DELTA_VALIDATION_TIME" \
|
||||||
|
--max-depth "$MAX_DEPTH" \
|
||||||
|
--max-instances "$MAX_INSTANCES" \
|
||||||
|
--ccr-out "$OUT_DIR/delta-001/result.ccr" \
|
||||||
|
--report-json "$OUT_DIR/delta-001/report.json" \
|
||||||
|
--cir-enable \
|
||||||
|
--cir-out "$OUT_DIR/delta-001/input.cir" \
|
||||||
|
--repo-bytes-db "$REPO_BYTES_DB" \
|
||||||
|
--cir-tal-uri "$CIR_TAL_URI" \
|
||||||
|
>"$OUT_DIR/delta-001/run.stdout.log" 2>"$OUT_DIR/delta-001/run.stderr.log"
|
||||||
|
|
||||||
|
python3 - <<'PY' "$OUT_DIR" "$BASE_VALIDATION_TIME" "$DELTA_VALIDATION_TIME"
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
out = Path(sys.argv[1])
|
||||||
|
base_validation_time = sys.argv[2]
|
||||||
|
delta_validation_time = sys.argv[3]
|
||||||
|
summary = {
|
||||||
|
"version": 1,
|
||||||
|
"kind": "cir_pair",
|
||||||
|
"baseValidationTime": base_validation_time,
|
||||||
|
"deltaValidationTime": delta_validation_time,
|
||||||
|
"repoBytesDbPath": "repo-bytes.db",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"kind": "full",
|
||||||
|
"cirPath": "full/input.cir",
|
||||||
|
"ccrPath": "full/result.ccr",
|
||||||
|
"reportPath": "full/report.json",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "delta",
|
||||||
|
"cirPath": "delta-001/input.cir",
|
||||||
|
"ccrPath": "delta-001/result.ccr",
|
||||||
|
"reportPath": "delta-001/report.json",
|
||||||
|
"previous": "full",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"repoBytesDbExists": (out / "repo-bytes.db").exists(),
|
||||||
|
}
|
||||||
|
(out / "summary.json").write_text(json.dumps(summary, indent=2), encoding="utf-8")
|
||||||
|
PY
|
||||||
|
|
||||||
|
echo "done: $OUT_DIR"
|
||||||
129
scripts/cir/run_cir_record_sequence_multi_rir_offline.sh
Executable file
129
scripts/cir/run_cir_record_sequence_multi_rir_offline.sh
Executable file
@@ -0,0 +1,129 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage:
|
||||||
|
./scripts/cir/run_cir_record_sequence_multi_rir_offline.sh \
|
||||||
|
[--bundle-root <path>] \
|
||||||
|
[--rir <afrinic,apnic,arin,lacnic,ripe>] \
|
||||||
|
[--delta-count <n>] \
|
||||||
|
[--full-repo] \
|
||||||
|
[--out-root <path>] \
|
||||||
|
[--rpki-bin <path>]
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
|
CASE_INFO="$ROOT_DIR/scripts/payload_replay/multi_rir_case_info.py"
|
||||||
|
SINGLE_SCRIPT="$ROOT_DIR/scripts/cir/run_cir_record_sequence_offline.sh"
|
||||||
|
|
||||||
|
BUNDLE_ROOT="/home/yuyr/dev/rust_playground/routinator/bench/multi_rir_demo/runs/20260316-112341-multi-final3"
|
||||||
|
RIRS="afrinic,apnic,arin,lacnic,ripe"
|
||||||
|
DELTA_COUNT=2
|
||||||
|
FULL_REPO=0
|
||||||
|
OUT_ROOT="$ROOT_DIR/target/replay/cir_sequence_multi_rir_offline_$(date -u +%Y%m%dT%H%M%SZ)"
|
||||||
|
RPKI_BIN="${RPKI_BIN:-$ROOT_DIR/target/release/rpki}"
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--bundle-root) BUNDLE_ROOT="$2"; shift 2 ;;
|
||||||
|
--rir) RIRS="$2"; shift 2 ;;
|
||||||
|
--delta-count) DELTA_COUNT="$2"; shift 2 ;;
|
||||||
|
--full-repo) FULL_REPO=1; shift 1 ;;
|
||||||
|
--out-root) OUT_ROOT="$2"; shift 2 ;;
|
||||||
|
--rpki-bin) RPKI_BIN="$2"; shift 2 ;;
|
||||||
|
-h|--help) usage; exit 0 ;;
|
||||||
|
*) echo "unknown argument: $1" >&2; usage; exit 2 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
mkdir -p "$OUT_ROOT"
|
||||||
|
SUMMARY_JSON="$OUT_ROOT/summary.json"
|
||||||
|
SUMMARY_MD="$OUT_ROOT/summary.md"
|
||||||
|
|
||||||
|
IFS=',' read -r -a RIR_ITEMS <<< "$RIRS"
|
||||||
|
|
||||||
|
for rir in "${RIR_ITEMS[@]}"; do
|
||||||
|
CASE_JSON="$(python3 "$CASE_INFO" --bundle-root "$BUNDLE_ROOT" --repo-root "$ROOT_DIR" --rir "$rir")"
|
||||||
|
TAL_PATH="$(python3 - <<'PY' "$CASE_JSON"
|
||||||
|
import json,sys
|
||||||
|
print(json.loads(sys.argv[1])['tal_path'])
|
||||||
|
PY
|
||||||
|
)"
|
||||||
|
TA_PATH="$(python3 - <<'PY' "$CASE_JSON"
|
||||||
|
import json,sys
|
||||||
|
print(json.loads(sys.argv[1])['ta_path'])
|
||||||
|
PY
|
||||||
|
)"
|
||||||
|
BASE_ARCHIVE="$(python3 - <<'PY' "$CASE_JSON"
|
||||||
|
import json,sys
|
||||||
|
print(json.loads(sys.argv[1])['base_archive'])
|
||||||
|
PY
|
||||||
|
)"
|
||||||
|
BASE_LOCKS="$(python3 - <<'PY' "$CASE_JSON"
|
||||||
|
import json,sys
|
||||||
|
print(json.loads(sys.argv[1])['base_locks'])
|
||||||
|
PY
|
||||||
|
)"
|
||||||
|
DELTA_ARCHIVE="$(python3 - <<'PY' "$CASE_JSON"
|
||||||
|
import json,sys
|
||||||
|
print(json.loads(sys.argv[1])['delta_archive'])
|
||||||
|
PY
|
||||||
|
)"
|
||||||
|
DELTA_LOCKS="$(python3 - <<'PY' "$CASE_JSON"
|
||||||
|
import json,sys
|
||||||
|
print(json.loads(sys.argv[1])['delta_locks'])
|
||||||
|
PY
|
||||||
|
)"
|
||||||
|
OUT_DIR="$OUT_ROOT/$rir"
|
||||||
|
args=(
|
||||||
|
"$SINGLE_SCRIPT"
|
||||||
|
--out-dir "$OUT_DIR" \
|
||||||
|
--tal-path "$TAL_PATH" \
|
||||||
|
--ta-path "$TA_PATH" \
|
||||||
|
--cir-tal-uri "https://example.test/$rir.tal" \
|
||||||
|
--payload-replay-archive "$BASE_ARCHIVE" \
|
||||||
|
--payload-replay-locks "$BASE_LOCKS" \
|
||||||
|
--payload-base-archive "$BASE_ARCHIVE" \
|
||||||
|
--payload-base-locks "$BASE_LOCKS" \
|
||||||
|
--payload-delta-archive "$DELTA_ARCHIVE" \
|
||||||
|
--payload-delta-locks "$DELTA_LOCKS" \
|
||||||
|
--delta-count "$DELTA_COUNT" \
|
||||||
|
--rpki-bin "$RPKI_BIN"
|
||||||
|
)
|
||||||
|
if [[ "$FULL_REPO" -ne 1 ]]; then
|
||||||
|
args+=(--max-depth 0 --max-instances 1)
|
||||||
|
else
|
||||||
|
args+=(--full-repo)
|
||||||
|
fi
|
||||||
|
"${args[@]}"
|
||||||
|
done
|
||||||
|
|
||||||
|
python3 - <<'PY' "$OUT_ROOT" "$RIRS" "$SUMMARY_JSON" "$SUMMARY_MD"
|
||||||
|
import json, sys
|
||||||
|
from pathlib import Path
|
||||||
|
out_root = Path(sys.argv[1])
|
||||||
|
rirs = [item for item in sys.argv[2].split(',') if item]
|
||||||
|
summary_json = Path(sys.argv[3])
|
||||||
|
summary_md = Path(sys.argv[4])
|
||||||
|
items = []
|
||||||
|
for rir in rirs:
|
||||||
|
root = out_root / rir
|
||||||
|
seq = json.loads((root / "sequence.json").read_text(encoding="utf-8"))
|
||||||
|
summ = json.loads((root / "summary.json").read_text(encoding="utf-8"))
|
||||||
|
items.append({
|
||||||
|
"rir": rir,
|
||||||
|
"root": str(root),
|
||||||
|
"stepCount": len(seq["steps"]),
|
||||||
|
"repoBytesDbExists": summ.get("repoBytesDbExists", False),
|
||||||
|
})
|
||||||
|
summary = {"version": 1, "rirs": items}
|
||||||
|
summary_json.write_text(json.dumps(summary, indent=2), encoding="utf-8")
|
||||||
|
lines = ["# Multi-RIR Offline CIR Sequence Summary", ""]
|
||||||
|
for item in items:
|
||||||
|
lines.append(f"- `{item['rir']}`: `stepCount={item['stepCount']}` `repoBytesDbExists={item['repoBytesDbExists']}` `root={item['root']}`")
|
||||||
|
summary_md.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
||||||
|
PY
|
||||||
|
|
||||||
|
echo "done: $OUT_ROOT"
|
||||||
208
scripts/cir/run_cir_record_sequence_offline.sh
Executable file
208
scripts/cir/run_cir_record_sequence_offline.sh
Executable file
@@ -0,0 +1,208 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage:
|
||||||
|
./scripts/cir/run_cir_record_sequence_offline.sh \
|
||||||
|
--out-dir <path> \
|
||||||
|
--tal-path <path> \
|
||||||
|
--ta-path <path> \
|
||||||
|
--cir-tal-uri <url> \
|
||||||
|
--payload-replay-archive <path> \
|
||||||
|
--payload-replay-locks <path> \
|
||||||
|
--payload-base-archive <path> \
|
||||||
|
--payload-base-locks <path> \
|
||||||
|
--payload-delta-archive <path> \
|
||||||
|
--payload-delta-locks <path> \
|
||||||
|
[--delta-count <n>] \
|
||||||
|
[--base-validation-time <rfc3339>] \
|
||||||
|
[--delta-validation-time <rfc3339>] \
|
||||||
|
[--full-repo] \
|
||||||
|
[--max-depth <n>] \
|
||||||
|
[--max-instances <n>] \
|
||||||
|
[--rpki-bin <path>]
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
|
|
||||||
|
OUT_DIR=""
|
||||||
|
TAL_PATH=""
|
||||||
|
TA_PATH=""
|
||||||
|
CIR_TAL_URI=""
|
||||||
|
PAYLOAD_REPLAY_ARCHIVE=""
|
||||||
|
PAYLOAD_REPLAY_LOCKS=""
|
||||||
|
PAYLOAD_BASE_ARCHIVE=""
|
||||||
|
PAYLOAD_BASE_LOCKS=""
|
||||||
|
PAYLOAD_DELTA_ARCHIVE=""
|
||||||
|
PAYLOAD_DELTA_LOCKS=""
|
||||||
|
BASE_VALIDATION_TIME=""
|
||||||
|
DELTA_VALIDATION_TIME=""
|
||||||
|
DELTA_COUNT=2
|
||||||
|
FULL_REPO=0
|
||||||
|
MAX_DEPTH=0
|
||||||
|
MAX_INSTANCES=1
|
||||||
|
RPKI_BIN="${RPKI_BIN:-$ROOT_DIR/target/release/rpki}"
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--out-dir) OUT_DIR="$2"; shift 2 ;;
|
||||||
|
--tal-path) TAL_PATH="$2"; shift 2 ;;
|
||||||
|
--ta-path) TA_PATH="$2"; shift 2 ;;
|
||||||
|
--cir-tal-uri) CIR_TAL_URI="$2"; shift 2 ;;
|
||||||
|
--payload-replay-archive) PAYLOAD_REPLAY_ARCHIVE="$2"; shift 2 ;;
|
||||||
|
--payload-replay-locks) PAYLOAD_REPLAY_LOCKS="$2"; shift 2 ;;
|
||||||
|
--payload-base-archive) PAYLOAD_BASE_ARCHIVE="$2"; shift 2 ;;
|
||||||
|
--payload-base-locks) PAYLOAD_BASE_LOCKS="$2"; shift 2 ;;
|
||||||
|
--payload-delta-archive) PAYLOAD_DELTA_ARCHIVE="$2"; shift 2 ;;
|
||||||
|
--payload-delta-locks) PAYLOAD_DELTA_LOCKS="$2"; shift 2 ;;
|
||||||
|
--base-validation-time) BASE_VALIDATION_TIME="$2"; shift 2 ;;
|
||||||
|
--delta-validation-time) DELTA_VALIDATION_TIME="$2"; shift 2 ;;
|
||||||
|
--delta-count) DELTA_COUNT="$2"; shift 2 ;;
|
||||||
|
--full-repo) FULL_REPO=1; shift 1 ;;
|
||||||
|
--max-depth) MAX_DEPTH="$2"; shift 2 ;;
|
||||||
|
--max-instances) MAX_INSTANCES="$2"; shift 2 ;;
|
||||||
|
--rpki-bin) RPKI_BIN="$2"; shift 2 ;;
|
||||||
|
-h|--help) usage; exit 0 ;;
|
||||||
|
*) echo "unknown argument: $1" >&2; usage; exit 2 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
[[ -n "$OUT_DIR" && -n "$TAL_PATH" && -n "$TA_PATH" && -n "$CIR_TAL_URI" && -n "$PAYLOAD_REPLAY_ARCHIVE" && -n "$PAYLOAD_REPLAY_LOCKS" && -n "$PAYLOAD_BASE_ARCHIVE" && -n "$PAYLOAD_BASE_LOCKS" && -n "$PAYLOAD_DELTA_ARCHIVE" && -n "$PAYLOAD_DELTA_LOCKS" ]] || {
|
||||||
|
usage >&2
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ ! -x "$RPKI_BIN" ]]; then
|
||||||
|
(
|
||||||
|
cd "$ROOT_DIR"
|
||||||
|
cargo build --release --bin rpki
|
||||||
|
)
|
||||||
|
fi
|
||||||
|
|
||||||
|
resolve_validation_time() {
|
||||||
|
local path="$1"
|
||||||
|
python3 - <<'PY' "$path"
|
||||||
|
import json, sys
|
||||||
|
print(json.load(open(sys.argv[1], 'r', encoding='utf-8'))['validationTime'])
|
||||||
|
PY
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ -z "$BASE_VALIDATION_TIME" ]]; then
|
||||||
|
BASE_VALIDATION_TIME="$(resolve_validation_time "$PAYLOAD_REPLAY_LOCKS")"
|
||||||
|
fi
|
||||||
|
if [[ -z "$DELTA_VALIDATION_TIME" ]]; then
|
||||||
|
DELTA_VALIDATION_TIME="$(resolve_validation_time "$PAYLOAD_DELTA_LOCKS")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -rf "$OUT_DIR"
|
||||||
|
mkdir -p "$OUT_DIR/full"
|
||||||
|
REPO_BYTES_DB="$OUT_DIR/repo-bytes.db"
|
||||||
|
|
||||||
|
run_step() {
|
||||||
|
local kind="$1"
|
||||||
|
local step_dir="$2"
|
||||||
|
local db_dir="$3"
|
||||||
|
shift 3
|
||||||
|
mkdir -p "$step_dir"
|
||||||
|
local -a cmd=(
|
||||||
|
"$RPKI_BIN"
|
||||||
|
--db "$db_dir" \
|
||||||
|
--tal-path "$TAL_PATH" \
|
||||||
|
--ta-path "$TA_PATH" \
|
||||||
|
--ccr-out "$step_dir/result.ccr" \
|
||||||
|
--report-json "$step_dir/report.json" \
|
||||||
|
--cir-enable \
|
||||||
|
--cir-out "$step_dir/input.cir" \
|
||||||
|
--repo-bytes-db "$REPO_BYTES_DB" \
|
||||||
|
--cir-tal-uri "$CIR_TAL_URI"
|
||||||
|
)
|
||||||
|
if [[ "$FULL_REPO" -ne 1 ]]; then
|
||||||
|
cmd+=(--max-depth "$MAX_DEPTH" --max-instances "$MAX_INSTANCES")
|
||||||
|
fi
|
||||||
|
cmd+=("$@")
|
||||||
|
"${cmd[@]}" >"$step_dir/run.stdout.log" 2>"$step_dir/run.stderr.log"
|
||||||
|
}
|
||||||
|
|
||||||
|
run_step \
|
||||||
|
full \
|
||||||
|
"$OUT_DIR/full" \
|
||||||
|
"$OUT_DIR/full/db" \
|
||||||
|
--payload-replay-archive "$PAYLOAD_REPLAY_ARCHIVE" \
|
||||||
|
--payload-replay-locks "$PAYLOAD_REPLAY_LOCKS" \
|
||||||
|
--validation-time "$BASE_VALIDATION_TIME"
|
||||||
|
|
||||||
|
for idx in $(seq 1 "$DELTA_COUNT"); do
|
||||||
|
step_id="$(printf 'delta-%03d' "$idx")"
|
||||||
|
run_step \
|
||||||
|
delta \
|
||||||
|
"$OUT_DIR/$step_id" \
|
||||||
|
"$OUT_DIR/$step_id/db" \
|
||||||
|
--payload-base-archive "$PAYLOAD_BASE_ARCHIVE" \
|
||||||
|
--payload-base-locks "$PAYLOAD_BASE_LOCKS" \
|
||||||
|
--payload-delta-archive "$PAYLOAD_DELTA_ARCHIVE" \
|
||||||
|
--payload-delta-locks "$PAYLOAD_DELTA_LOCKS" \
|
||||||
|
--payload-base-validation-time "$BASE_VALIDATION_TIME" \
|
||||||
|
--validation-time "$DELTA_VALIDATION_TIME"
|
||||||
|
done
|
||||||
|
|
||||||
|
python3 - <<'PY' "$OUT_DIR" "$BASE_VALIDATION_TIME" "$DELTA_VALIDATION_TIME" "$DELTA_COUNT"
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
out = Path(sys.argv[1])
|
||||||
|
base_validation_time = sys.argv[2]
|
||||||
|
delta_validation_time = sys.argv[3]
|
||||||
|
delta_count = int(sys.argv[4])
|
||||||
|
|
||||||
|
steps = [
|
||||||
|
{
|
||||||
|
"stepId": "full",
|
||||||
|
"kind": "full",
|
||||||
|
"validationTime": base_validation_time,
|
||||||
|
"cirPath": "full/input.cir",
|
||||||
|
"ccrPath": "full/result.ccr",
|
||||||
|
"reportPath": "full/report.json",
|
||||||
|
"previousStepId": None,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
previous = "full"
|
||||||
|
for idx in range(1, delta_count + 1):
|
||||||
|
step_id = f"delta-{idx:03d}"
|
||||||
|
steps.append(
|
||||||
|
{
|
||||||
|
"stepId": step_id,
|
||||||
|
"kind": "delta",
|
||||||
|
"validationTime": delta_validation_time,
|
||||||
|
"cirPath": f"{step_id}/input.cir",
|
||||||
|
"ccrPath": f"{step_id}/result.ccr",
|
||||||
|
"reportPath": f"{step_id}/report.json",
|
||||||
|
"previousStepId": previous,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
previous = step_id
|
||||||
|
|
||||||
|
summary = {
|
||||||
|
"version": 1,
|
||||||
|
"kind": "cir_sequence_offline",
|
||||||
|
"repoBytesDbPath": "repo-bytes.db",
|
||||||
|
"steps": steps,
|
||||||
|
}
|
||||||
|
(out / "sequence.json").write_text(json.dumps(summary, indent=2), encoding="utf-8")
|
||||||
|
(out / "summary.json").write_text(
|
||||||
|
json.dumps(
|
||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"stepCount": len(steps),
|
||||||
|
"repoBytesDbPath": "repo-bytes.db",
|
||||||
|
"repoBytesDbExists": (out / "repo-bytes.db").exists(),
|
||||||
|
},
|
||||||
|
indent=2,
|
||||||
|
),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
PY
|
||||||
|
|
||||||
|
echo "done: $OUT_DIR"
|
||||||
246
scripts/cir/run_cir_record_sequence_remote.sh
Executable file
246
scripts/cir/run_cir_record_sequence_remote.sh
Executable file
@@ -0,0 +1,246 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage:
|
||||||
|
./scripts/cir/run_cir_record_sequence_remote.sh \
|
||||||
|
--rir <name> \
|
||||||
|
--remote-root <path> \
|
||||||
|
[--ssh-target <user@host>] \
|
||||||
|
[--out-subdir <path>] \
|
||||||
|
[--delta-count <n>] \
|
||||||
|
[--sleep-secs <n>] \
|
||||||
|
[--full-repo] \
|
||||||
|
[--max-depth <n>] \
|
||||||
|
[--max-instances <n>]
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
|
SSH_TARGET="${SSH_TARGET:-root@47.77.183.68}"
|
||||||
|
RIR=""
|
||||||
|
REMOTE_ROOT=""
|
||||||
|
OUT_SUBDIR=""
|
||||||
|
DELTA_COUNT=2
|
||||||
|
SLEEP_SECS=30
|
||||||
|
FULL_REPO=0
|
||||||
|
MAX_DEPTH=0
|
||||||
|
MAX_INSTANCES=1
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--rir) RIR="$2"; shift 2 ;;
|
||||||
|
--remote-root) REMOTE_ROOT="$2"; shift 2 ;;
|
||||||
|
--ssh-target) SSH_TARGET="$2"; shift 2 ;;
|
||||||
|
--out-subdir) OUT_SUBDIR="$2"; shift 2 ;;
|
||||||
|
--delta-count) DELTA_COUNT="$2"; shift 2 ;;
|
||||||
|
--sleep-secs) SLEEP_SECS="$2"; shift 2 ;;
|
||||||
|
--full-repo) FULL_REPO=1; shift 1 ;;
|
||||||
|
--max-depth) MAX_DEPTH="$2"; shift 2 ;;
|
||||||
|
--max-instances) MAX_INSTANCES="$2"; shift 2 ;;
|
||||||
|
-h|--help) usage; exit 0 ;;
|
||||||
|
*) echo "unknown argument: $1" >&2; usage; exit 2 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
[[ -n "$RIR" && -n "$REMOTE_ROOT" ]] || { usage >&2; exit 2; }
|
||||||
|
|
||||||
|
case "$RIR" in
|
||||||
|
afrinic) TAL_REL="tests/fixtures/tal/afrinic.tal"; TA_REL="tests/fixtures/ta/afrinic-ta.cer" ;;
|
||||||
|
apnic) TAL_REL="tests/fixtures/tal/apnic-rfc7730-https.tal"; TA_REL="tests/fixtures/ta/apnic-ta.cer" ;;
|
||||||
|
arin) TAL_REL="tests/fixtures/tal/arin.tal"; TA_REL="tests/fixtures/ta/arin-ta.cer" ;;
|
||||||
|
lacnic) TAL_REL="tests/fixtures/tal/lacnic.tal"; TA_REL="tests/fixtures/ta/lacnic-ta.cer" ;;
|
||||||
|
ripe) TAL_REL="tests/fixtures/tal/ripe-ncc.tal"; TA_REL="tests/fixtures/ta/ripe-ncc-ta.cer" ;;
|
||||||
|
*) echo "unsupported rir: $RIR" >&2; exit 2 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
rsync -a --delete \
|
||||||
|
--exclude target \
|
||||||
|
--exclude .git \
|
||||||
|
"$ROOT_DIR/" "$SSH_TARGET:$REMOTE_ROOT/"
|
||||||
|
ssh "$SSH_TARGET" "mkdir -p '$REMOTE_ROOT/target/release'"
|
||||||
|
rsync -a "$ROOT_DIR/target/release/rpki" "$SSH_TARGET:$REMOTE_ROOT/target/release/"
|
||||||
|
|
||||||
|
ssh "$SSH_TARGET" \
|
||||||
|
RIR="$RIR" \
|
||||||
|
REMOTE_ROOT="$REMOTE_ROOT" \
|
||||||
|
OUT_SUBDIR="$OUT_SUBDIR" \
|
||||||
|
DELTA_COUNT="$DELTA_COUNT" \
|
||||||
|
SLEEP_SECS="$SLEEP_SECS" \
|
||||||
|
FULL_REPO="$FULL_REPO" \
|
||||||
|
MAX_DEPTH="$MAX_DEPTH" \
|
||||||
|
MAX_INSTANCES="$MAX_INSTANCES" \
|
||||||
|
TAL_REL="$TAL_REL" \
|
||||||
|
TA_REL="$TA_REL" \
|
||||||
|
'bash -s' <<'EOS'
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
cd "$REMOTE_ROOT"
|
||||||
|
|
||||||
|
if [[ -n "${OUT_SUBDIR}" ]]; then
|
||||||
|
OUT="${OUT_SUBDIR}"
|
||||||
|
else
|
||||||
|
OUT="target/replay/cir_sequence_remote_${RIR}_$(date -u +%Y%m%dT%H%M%SZ)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$OUT"
|
||||||
|
DB="$OUT/work-db"
|
||||||
|
RAW_STORE_DB="$OUT/raw-store.db"
|
||||||
|
REPO_BYTES_DB="$OUT/repo-bytes.db"
|
||||||
|
ROWS="$OUT/.sequence_rows.tsv"
|
||||||
|
: > "$ROWS"
|
||||||
|
|
||||||
|
write_step_timing() {
|
||||||
|
local path="$1"
|
||||||
|
local start_ms="$2"
|
||||||
|
local end_ms="$3"
|
||||||
|
local started_at="$4"
|
||||||
|
local finished_at="$5"
|
||||||
|
python3 - <<'PY' "$path" "$start_ms" "$end_ms" "$started_at" "$finished_at"
|
||||||
|
import json, sys
|
||||||
|
path, start_ms, end_ms, started_at, finished_at = sys.argv[1:]
|
||||||
|
start_ms = int(start_ms)
|
||||||
|
end_ms = int(end_ms)
|
||||||
|
with open(path, "w", encoding="utf-8") as fh:
|
||||||
|
json.dump(
|
||||||
|
{
|
||||||
|
"durationMs": end_ms - start_ms,
|
||||||
|
"startedAt": started_at,
|
||||||
|
"finishedAt": finished_at,
|
||||||
|
},
|
||||||
|
fh,
|
||||||
|
indent=2,
|
||||||
|
)
|
||||||
|
PY
|
||||||
|
}
|
||||||
|
|
||||||
|
run_step() {
|
||||||
|
local step_id="$1"
|
||||||
|
local kind="$2"
|
||||||
|
local previous_step_id="$3"
|
||||||
|
shift 3
|
||||||
|
|
||||||
|
local started_at_iso started_at_ms finished_at_iso finished_at_ms prefix
|
||||||
|
started_at_iso="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||||
|
started_at_ms="$(python3 - <<'PY'
|
||||||
|
import time
|
||||||
|
print(int(time.time() * 1000))
|
||||||
|
PY
|
||||||
|
)"
|
||||||
|
prefix="${started_at_iso}-test"
|
||||||
|
|
||||||
|
local cir_out="$OUT/${prefix}.cir"
|
||||||
|
local ccr_out="$OUT/${prefix}.ccr"
|
||||||
|
local report_out="$OUT/${prefix}.report.json"
|
||||||
|
local timing_out="$OUT/${prefix}.timing.json"
|
||||||
|
local stdout_out="$OUT/${prefix}.stdout.log"
|
||||||
|
local stderr_out="$OUT/${prefix}.stderr.log"
|
||||||
|
|
||||||
|
local -a cmd=(
|
||||||
|
target/release/rpki
|
||||||
|
--db "$DB"
|
||||||
|
--raw-store-db "$RAW_STORE_DB"
|
||||||
|
--repo-bytes-db "$REPO_BYTES_DB"
|
||||||
|
--tal-path "$TAL_REL"
|
||||||
|
--ta-path "$TA_REL"
|
||||||
|
--ccr-out "$ccr_out"
|
||||||
|
--report-json "$report_out"
|
||||||
|
--cir-enable
|
||||||
|
--cir-out "$cir_out"
|
||||||
|
--cir-tal-uri "https://example.test/${RIR}.tal"
|
||||||
|
)
|
||||||
|
if [[ "$FULL_REPO" -ne 1 ]]; then
|
||||||
|
cmd+=(--max-depth "$MAX_DEPTH" --max-instances "$MAX_INSTANCES")
|
||||||
|
fi
|
||||||
|
cmd+=("$@")
|
||||||
|
env RPKI_PROGRESS_LOG=1 "${cmd[@]}" >"$stdout_out" 2>"$stderr_out"
|
||||||
|
|
||||||
|
finished_at_ms="$(python3 - <<'PY'
|
||||||
|
import time
|
||||||
|
print(int(time.time() * 1000))
|
||||||
|
PY
|
||||||
|
)"
|
||||||
|
finished_at_iso="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||||
|
write_step_timing "$timing_out" "$started_at_ms" "$finished_at_ms" "$started_at_iso" "$finished_at_iso"
|
||||||
|
|
||||||
|
local validation_time
|
||||||
|
validation_time="$(python3 - <<'PY' "$report_out"
|
||||||
|
import json, sys
|
||||||
|
print(json.load(open(sys.argv[1], 'r', encoding='utf-8'))['meta']['validation_time_rfc3339_utc'])
|
||||||
|
PY
|
||||||
|
)"
|
||||||
|
|
||||||
|
printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \
|
||||||
|
"$step_id" \
|
||||||
|
"$kind" \
|
||||||
|
"$validation_time" \
|
||||||
|
"$(basename "$cir_out")" \
|
||||||
|
"$(basename "$ccr_out")" \
|
||||||
|
"$(basename "$report_out")" \
|
||||||
|
"$(basename "$timing_out")" \
|
||||||
|
"$(basename "$stdout_out")" \
|
||||||
|
"$(basename "$stderr_out")" >> "$ROWS"
|
||||||
|
}
|
||||||
|
|
||||||
|
run_step "full" "full" ""
|
||||||
|
|
||||||
|
prev="full"
|
||||||
|
for idx in $(seq 1 "$DELTA_COUNT"); do
|
||||||
|
sleep "$SLEEP_SECS"
|
||||||
|
step="$(printf 'delta-%03d' "$idx")"
|
||||||
|
run_step "$step" "delta" "$prev"
|
||||||
|
prev="$step"
|
||||||
|
done
|
||||||
|
|
||||||
|
python3 - <<'PY' "$OUT" "$ROWS" "$RIR"
|
||||||
|
import json, sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
out = Path(sys.argv[1])
|
||||||
|
rows = Path(sys.argv[2]).read_text(encoding='utf-8').splitlines()
|
||||||
|
rir = sys.argv[3]
|
||||||
|
steps = []
|
||||||
|
for idx, row in enumerate(rows):
|
||||||
|
step_id, kind, validation_time, cir_name, ccr_name, report_name, timing_name, stdout_name, stderr_name = row.split('\t')
|
||||||
|
steps.append({
|
||||||
|
"stepId": step_id,
|
||||||
|
"kind": kind,
|
||||||
|
"validationTime": validation_time,
|
||||||
|
"cirPath": cir_name,
|
||||||
|
"ccrPath": ccr_name,
|
||||||
|
"reportPath": report_name,
|
||||||
|
"timingPath": timing_name,
|
||||||
|
"stdoutLogPath": stdout_name,
|
||||||
|
"stderrLogPath": stderr_name,
|
||||||
|
"artifactPrefix": cir_name[:-4], # strip .cir
|
||||||
|
"previousStepId": None if idx == 0 else steps[idx - 1]["stepId"],
|
||||||
|
})
|
||||||
|
|
||||||
|
(out / "sequence.json").write_text(
|
||||||
|
json.dumps({"version": 1, "repoBytesDbPath": "repo-bytes.db", "steps": steps}, indent=2),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
summary = {
|
||||||
|
"version": 1,
|
||||||
|
"rir": rir,
|
||||||
|
"stepCount": len(steps),
|
||||||
|
"steps": [],
|
||||||
|
}
|
||||||
|
for step in steps:
|
||||||
|
timing = json.loads((out / step["timingPath"]).read_text(encoding="utf-8"))
|
||||||
|
summary["steps"].append({
|
||||||
|
"stepId": step["stepId"],
|
||||||
|
"kind": step["kind"],
|
||||||
|
"validationTime": step["validationTime"],
|
||||||
|
"artifactPrefix": step["artifactPrefix"],
|
||||||
|
**timing,
|
||||||
|
})
|
||||||
|
|
||||||
|
(out / "summary.json").write_text(json.dumps(summary, indent=2), encoding="utf-8")
|
||||||
|
PY
|
||||||
|
|
||||||
|
rm -f "$ROWS"
|
||||||
|
echo "$OUT"
|
||||||
|
EOS
|
||||||
72
scripts/cir/run_cir_record_sequence_remote_multi_rir.sh
Executable file
72
scripts/cir/run_cir_record_sequence_remote_multi_rir.sh
Executable file
@@ -0,0 +1,72 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage:
|
||||||
|
./scripts/cir/run_cir_record_sequence_remote_multi_rir.sh \
|
||||||
|
--remote-root <path> \
|
||||||
|
[--rir <afrinic,apnic,arin,lacnic,ripe>] \
|
||||||
|
[--ssh-target <user@host>] \
|
||||||
|
[--out-subdir-root <path>] \
|
||||||
|
[--delta-count <n>] \
|
||||||
|
[--sleep-secs <n>] \
|
||||||
|
[--full-repo] \
|
||||||
|
[--max-depth <n>] \
|
||||||
|
[--max-instances <n>]
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
|
SSH_TARGET="${SSH_TARGET:-root@47.77.183.68}"
|
||||||
|
REMOTE_ROOT=""
|
||||||
|
RIRS="afrinic,apnic,arin,lacnic,ripe"
|
||||||
|
OUT_SUBDIR_ROOT=""
|
||||||
|
DELTA_COUNT=2
|
||||||
|
SLEEP_SECS=30
|
||||||
|
FULL_REPO=0
|
||||||
|
MAX_DEPTH=0
|
||||||
|
MAX_INSTANCES=1
|
||||||
|
SINGLE="$ROOT_DIR/scripts/cir/run_cir_record_sequence_remote.sh"
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--remote-root) REMOTE_ROOT="$2"; shift 2 ;;
|
||||||
|
--rir) RIRS="$2"; shift 2 ;;
|
||||||
|
--ssh-target) SSH_TARGET="$2"; shift 2 ;;
|
||||||
|
--out-subdir-root) OUT_SUBDIR_ROOT="$2"; shift 2 ;;
|
||||||
|
--delta-count) DELTA_COUNT="$2"; shift 2 ;;
|
||||||
|
--sleep-secs) SLEEP_SECS="$2"; shift 2 ;;
|
||||||
|
--full-repo) FULL_REPO=1; shift 1 ;;
|
||||||
|
--max-depth) MAX_DEPTH="$2"; shift 2 ;;
|
||||||
|
--max-instances) MAX_INSTANCES="$2"; shift 2 ;;
|
||||||
|
-h|--help) usage; exit 0 ;;
|
||||||
|
*) echo "unknown argument: $1" >&2; usage; exit 2 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
[[ -n "$REMOTE_ROOT" ]] || { usage >&2; exit 2; }
|
||||||
|
if [[ -z "$OUT_SUBDIR_ROOT" ]]; then
|
||||||
|
OUT_SUBDIR_ROOT="target/replay/cir_sequence_remote_multi_rir_$(date -u +%Y%m%dT%H%M%SZ)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
IFS=',' read -r -a ITEMS <<< "$RIRS"
|
||||||
|
for rir in "${ITEMS[@]}"; do
|
||||||
|
args=(
|
||||||
|
"$SINGLE"
|
||||||
|
--rir "$rir" \
|
||||||
|
--remote-root "$REMOTE_ROOT" \
|
||||||
|
--ssh-target "$SSH_TARGET" \
|
||||||
|
--out-subdir "$OUT_SUBDIR_ROOT/$rir" \
|
||||||
|
--delta-count "$DELTA_COUNT" \
|
||||||
|
--sleep-secs "$SLEEP_SECS" \
|
||||||
|
)
|
||||||
|
if [[ "$FULL_REPO" -eq 1 ]]; then
|
||||||
|
args+=(--full-repo)
|
||||||
|
else
|
||||||
|
args+=(--max-depth "$MAX_DEPTH" --max-instances "$MAX_INSTANCES")
|
||||||
|
fi
|
||||||
|
"${args[@]}"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "$OUT_SUBDIR_ROOT"
|
||||||
119
scripts/cir/run_cir_record_sequence_ta_only_multi_rir.sh
Executable file
119
scripts/cir/run_cir_record_sequence_ta_only_multi_rir.sh
Executable file
@@ -0,0 +1,119 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage:
|
||||||
|
./scripts/cir/run_cir_record_sequence_ta_only_multi_rir.sh \
|
||||||
|
[--rir <afrinic,apnic,arin,lacnic,ripe>] \
|
||||||
|
[--delta-count <n>] \
|
||||||
|
[--out-root <path>] \
|
||||||
|
[--rpki-bin <path>]
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
|
HELPER_BIN="${HELPER_BIN:-$ROOT_DIR/target/release/cir_ta_only_fixture}"
|
||||||
|
MATERIALIZE_BIN="${MATERIALIZE_BIN:-$ROOT_DIR/target/release/cir_materialize}"
|
||||||
|
EXTRACT_BIN="${EXTRACT_BIN:-$ROOT_DIR/target/release/cir_extract_inputs}"
|
||||||
|
WRAPPER="$ROOT_DIR/scripts/cir/cir-rsync-wrapper"
|
||||||
|
RIRS="afrinic,apnic,arin,lacnic,ripe"
|
||||||
|
DELTA_COUNT=2
|
||||||
|
OUT_ROOT="$ROOT_DIR/target/replay/cir_sequence_multi_rir_ta_only_$(date -u +%Y%m%dT%H%M%SZ)"
|
||||||
|
RPKI_BIN="${RPKI_BIN:-$ROOT_DIR/target/release/rpki}"
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--rir) RIRS="$2"; shift 2 ;;
|
||||||
|
--delta-count) DELTA_COUNT="$2"; shift 2 ;;
|
||||||
|
--out-root) OUT_ROOT="$2"; shift 2 ;;
|
||||||
|
--rpki-bin) RPKI_BIN="$2"; shift 2 ;;
|
||||||
|
-h|--help) usage; exit 0 ;;
|
||||||
|
*) echo "unknown argument: $1" >&2; usage; exit 2 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ ! -x "$HELPER_BIN" ]]; then
|
||||||
|
(
|
||||||
|
cd "$ROOT_DIR"
|
||||||
|
cargo build --release --bin cir_ta_only_fixture --bin rpki --bin cir_materialize --bin cir_extract_inputs
|
||||||
|
)
|
||||||
|
fi
|
||||||
|
|
||||||
|
case_paths() {
|
||||||
|
case "$1" in
|
||||||
|
afrinic) echo "tests/fixtures/tal/afrinic.tal tests/fixtures/ta/afrinic-ta.cer" ;;
|
||||||
|
apnic) echo "tests/fixtures/tal/apnic-rfc7730-https.tal tests/fixtures/ta/apnic-ta.cer" ;;
|
||||||
|
arin) echo "tests/fixtures/tal/arin.tal tests/fixtures/ta/arin-ta.cer" ;;
|
||||||
|
lacnic) echo "tests/fixtures/tal/lacnic.tal tests/fixtures/ta/lacnic-ta.cer" ;;
|
||||||
|
ripe) echo "tests/fixtures/tal/ripe-ncc.tal tests/fixtures/ta/ripe-ncc-ta.cer" ;;
|
||||||
|
*) return 1 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
mkdir -p "$OUT_ROOT"
|
||||||
|
IFS=',' read -r -a ITEMS <<< "$RIRS"
|
||||||
|
for rir in "${ITEMS[@]}"; do
|
||||||
|
read -r tal_rel ta_rel < <(case_paths "$rir")
|
||||||
|
rir_root="$OUT_ROOT/$rir"
|
||||||
|
mkdir -p "$rir_root/full"
|
||||||
|
repo_bytes_db="$rir_root/repo-bytes.db"
|
||||||
|
"$HELPER_BIN" \
|
||||||
|
--tal-path "$ROOT_DIR/$tal_rel" \
|
||||||
|
--ta-path "$ROOT_DIR/$ta_rel" \
|
||||||
|
--tal-uri "https://example.test/$rir.tal" \
|
||||||
|
--validation-time "2026-04-09T00:00:00Z" \
|
||||||
|
--cir-out "$rir_root/full/input.cir" \
|
||||||
|
--repo-bytes-db "$repo_bytes_db"
|
||||||
|
"$EXTRACT_BIN" --cir "$rir_root/full/input.cir" --tals-dir "$rir_root/.tmp/tals" --meta-json "$rir_root/.tmp/meta.json"
|
||||||
|
"$MATERIALIZE_BIN" --cir "$rir_root/full/input.cir" --repo-bytes-db "$repo_bytes_db" --mirror-root "$rir_root/.tmp/mirror"
|
||||||
|
FIRST_TAL="$(python3 - <<'PY' "$rir_root/.tmp/meta.json"
|
||||||
|
import json,sys
|
||||||
|
print(json.load(open(sys.argv[1]))["talFiles"][0]["path"])
|
||||||
|
PY
|
||||||
|
)"
|
||||||
|
export CIR_MIRROR_ROOT="$rir_root/.tmp/mirror"
|
||||||
|
export REAL_RSYNC_BIN=/usr/bin/rsync
|
||||||
|
export CIR_LOCAL_LINK_MODE=1
|
||||||
|
"$RPKI_BIN" \
|
||||||
|
--db "$rir_root/full/db" \
|
||||||
|
--tal-path "$FIRST_TAL" \
|
||||||
|
--disable-rrdp \
|
||||||
|
--rsync-command "$WRAPPER" \
|
||||||
|
--validation-time "2026-04-09T00:00:00Z" \
|
||||||
|
--ccr-out "$rir_root/full/result.ccr" \
|
||||||
|
--report-json "$rir_root/full/report.json" >/dev/null 2>&1
|
||||||
|
for idx in $(seq 1 "$DELTA_COUNT"); do
|
||||||
|
step="$(printf 'delta-%03d' "$idx")"
|
||||||
|
mkdir -p "$rir_root/$step"
|
||||||
|
cp "$rir_root/full/input.cir" "$rir_root/$step/input.cir"
|
||||||
|
cp "$rir_root/full/result.ccr" "$rir_root/$step/result.ccr"
|
||||||
|
cp "$rir_root/full/report.json" "$rir_root/$step/report.json"
|
||||||
|
done
|
||||||
|
python3 - <<'PY' "$rir_root" "$DELTA_COUNT"
|
||||||
|
import json, sys
|
||||||
|
from pathlib import Path
|
||||||
|
root = Path(sys.argv[1]); delta_count = int(sys.argv[2])
|
||||||
|
steps = [{"stepId":"full","kind":"full","validationTime":"2026-04-09T00:00:00Z","cirPath":"full/input.cir","ccrPath":"full/result.ccr","reportPath":"full/report.json","previousStepId":None}]
|
||||||
|
prev = "full"
|
||||||
|
for i in range(1, delta_count + 1):
|
||||||
|
step = f"delta-{i:03d}"
|
||||||
|
steps.append({"stepId":step,"kind":"delta","validationTime":"2026-04-09T00:00:00Z","cirPath":f"{step}/input.cir","ccrPath":f"{step}/result.ccr","reportPath":f"{step}/report.json","previousStepId":prev})
|
||||||
|
prev = step
|
||||||
|
(root/"sequence.json").write_text(json.dumps({"version":1,"repoBytesDbPath":"repo-bytes.db","steps":steps}, indent=2), encoding="utf-8")
|
||||||
|
(root/"summary.json").write_text(json.dumps({"version":1,"stepCount":len(steps)}, indent=2), encoding="utf-8")
|
||||||
|
PY
|
||||||
|
done
|
||||||
|
|
||||||
|
python3 - <<'PY' "$OUT_ROOT" "$RIRS"
|
||||||
|
import json, sys
|
||||||
|
from pathlib import Path
|
||||||
|
root = Path(sys.argv[1]); rirs = [x for x in sys.argv[2].split(',') if x]
|
||||||
|
items=[]
|
||||||
|
for rir in rirs:
|
||||||
|
seq=json.loads((root/rir/'sequence.json').read_text())
|
||||||
|
items.append({"rir":rir,"stepCount":len(seq['steps'])})
|
||||||
|
(root/'summary.json').write_text(json.dumps({"version":1,"rirs":items}, indent=2), encoding='utf-8')
|
||||||
|
PY
|
||||||
|
|
||||||
|
echo "done: $OUT_ROOT"
|
||||||
49
scripts/cir/run_cir_record_sequence_ta_only_remote_multi_rir.sh
Executable file
49
scripts/cir/run_cir_record_sequence_ta_only_remote_multi_rir.sh
Executable file
@@ -0,0 +1,49 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage:
|
||||||
|
./scripts/cir/run_cir_record_sequence_ta_only_remote_multi_rir.sh \
|
||||||
|
--remote-root <path> \
|
||||||
|
[--ssh-target <user@host>] \
|
||||||
|
[--rir <afrinic,apnic,arin,lacnic,ripe>] \
|
||||||
|
[--delta-count <n>]
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
|
SSH_TARGET="${SSH_TARGET:-root@47.77.183.68}"
|
||||||
|
REMOTE_ROOT=""
|
||||||
|
RIRS="afrinic,apnic,arin,lacnic,ripe"
|
||||||
|
DELTA_COUNT=2
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--remote-root) REMOTE_ROOT="$2"; shift 2 ;;
|
||||||
|
--ssh-target) SSH_TARGET="$2"; shift 2 ;;
|
||||||
|
--rir) RIRS="$2"; shift 2 ;;
|
||||||
|
--delta-count) DELTA_COUNT="$2"; shift 2 ;;
|
||||||
|
-h|--help) usage; exit 0 ;;
|
||||||
|
*) echo "unknown argument: $1" >&2; usage; exit 2 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
[[ -n "$REMOTE_ROOT" ]] || { usage >&2; exit 2; }
|
||||||
|
|
||||||
|
rsync -a --delete \
|
||||||
|
--exclude target \
|
||||||
|
--exclude .git \
|
||||||
|
"$ROOT_DIR/" "$SSH_TARGET:$REMOTE_ROOT/"
|
||||||
|
ssh "$SSH_TARGET" "mkdir -p '$REMOTE_ROOT/target/release'"
|
||||||
|
for bin in rpki cir_ta_only_fixture cir_materialize cir_extract_inputs; do
|
||||||
|
rsync -a "$ROOT_DIR/target/release/$bin" "$SSH_TARGET:$REMOTE_ROOT/target/release/"
|
||||||
|
done
|
||||||
|
|
||||||
|
ssh "$SSH_TARGET" "bash -lc '
|
||||||
|
set -euo pipefail
|
||||||
|
cd $REMOTE_ROOT
|
||||||
|
OUT=target/replay/cir_sequence_remote_ta_only_\$(date -u +%Y%m%dT%H%M%SZ)
|
||||||
|
./scripts/cir/run_cir_record_sequence_ta_only_multi_rir.sh --rir $RIRS --delta-count $DELTA_COUNT --out-root \"\$OUT\"
|
||||||
|
echo \"\$OUT\"
|
||||||
|
'"
|
||||||
293
scripts/cir/run_cir_replay_matrix.sh
Executable file
293
scripts/cir/run_cir_replay_matrix.sh
Executable file
@@ -0,0 +1,293 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage:
|
||||||
|
./scripts/cir/run_cir_replay_matrix.sh \
|
||||||
|
--cir <path> \
|
||||||
|
--repo-bytes-db <path> \
|
||||||
|
--out-dir <path> \
|
||||||
|
--reference-ccr <path> \
|
||||||
|
--rpki-client-build-dir <path> \
|
||||||
|
[--keep-db] \
|
||||||
|
[--rpki-bin <path>] \
|
||||||
|
[--routinator-root <path>] \
|
||||||
|
[--routinator-bin <path>] \
|
||||||
|
[--real-rsync-bin <path>]
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
|
|
||||||
|
CIR=""
|
||||||
|
REPO_BYTES_DB=""
|
||||||
|
OUT_DIR=""
|
||||||
|
REFERENCE_CCR=""
|
||||||
|
RPKI_CLIENT_BUILD_DIR=""
|
||||||
|
KEEP_DB=0
|
||||||
|
RPKI_BIN="${RPKI_BIN:-$ROOT_DIR/target/release/rpki}"
|
||||||
|
ROUTINATOR_ROOT="${ROUTINATOR_ROOT:-/home/yuyr/dev/rust_playground/routinator}"
|
||||||
|
ROUTINATOR_BIN="${ROUTINATOR_BIN:-$ROUTINATOR_ROOT/target/debug/routinator}"
|
||||||
|
REAL_RSYNC_BIN="${REAL_RSYNC_BIN:-/usr/bin/rsync}"
|
||||||
|
OURS_SCRIPT="$ROOT_DIR/scripts/cir/run_cir_replay_ours.sh"
|
||||||
|
ROUTINATOR_SCRIPT="$ROOT_DIR/scripts/cir/run_cir_replay_routinator.sh"
|
||||||
|
RPKI_CLIENT_SCRIPT="$ROOT_DIR/scripts/cir/run_cir_replay_rpki_client.sh"
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--cir) CIR="$2"; shift 2 ;;
|
||||||
|
--repo-bytes-db) REPO_BYTES_DB="$2"; shift 2 ;;
|
||||||
|
--out-dir) OUT_DIR="$2"; shift 2 ;;
|
||||||
|
--reference-ccr) REFERENCE_CCR="$2"; shift 2 ;;
|
||||||
|
--rpki-client-build-dir) RPKI_CLIENT_BUILD_DIR="$2"; shift 2 ;;
|
||||||
|
--keep-db) KEEP_DB=1; shift ;;
|
||||||
|
--rpki-bin) RPKI_BIN="$2"; shift 2 ;;
|
||||||
|
--routinator-root) ROUTINATOR_ROOT="$2"; shift 2 ;;
|
||||||
|
--routinator-bin) ROUTINATOR_BIN="$2"; shift 2 ;;
|
||||||
|
--real-rsync-bin) REAL_RSYNC_BIN="$2"; shift 2 ;;
|
||||||
|
-h|--help) usage; exit 0 ;;
|
||||||
|
*) echo "unknown argument: $1" >&2; usage; exit 2 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
[[ -n "$CIR" && -n "$REPO_BYTES_DB" && -n "$OUT_DIR" && -n "$REFERENCE_CCR" && -n "$RPKI_CLIENT_BUILD_DIR" ]] || {
|
||||||
|
usage >&2
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
|
||||||
|
mkdir -p "$OUT_DIR"
|
||||||
|
|
||||||
|
run_with_timing() {
|
||||||
|
local summary_path="$1"
|
||||||
|
local timing_path="$2"
|
||||||
|
shift 2
|
||||||
|
local start end status
|
||||||
|
start="$(python3 - <<'PY'
|
||||||
|
import time
|
||||||
|
print(time.perf_counter_ns())
|
||||||
|
PY
|
||||||
|
)"
|
||||||
|
if "$@"; then
|
||||||
|
status=0
|
||||||
|
else
|
||||||
|
status=$?
|
||||||
|
fi
|
||||||
|
end="$(python3 - <<'PY'
|
||||||
|
import time
|
||||||
|
print(time.perf_counter_ns())
|
||||||
|
PY
|
||||||
|
)"
|
||||||
|
python3 - <<'PY' "$summary_path" "$timing_path" "$status" "$start" "$end"
|
||||||
|
import json, sys
|
||||||
|
summary_path, timing_path, status, start, end = sys.argv[1:]
|
||||||
|
duration_ms = max(0, (int(end) - int(start)) // 1_000_000)
|
||||||
|
data = {"exitCode": int(status), "durationMs": duration_ms}
|
||||||
|
try:
|
||||||
|
with open(summary_path, "r", encoding="utf-8") as f:
|
||||||
|
data["compare"] = json.load(f)
|
||||||
|
except FileNotFoundError:
|
||||||
|
data["compare"] = None
|
||||||
|
with open(timing_path, "w", encoding="utf-8") as f:
|
||||||
|
json.dump(data, f, indent=2)
|
||||||
|
PY
|
||||||
|
return "$status"
|
||||||
|
}
|
||||||
|
|
||||||
|
OURS_OUT="$OUT_DIR/ours"
|
||||||
|
ROUTINATOR_OUT="$OUT_DIR/routinator"
|
||||||
|
RPKI_CLIENT_OUT="$OUT_DIR/rpki-client"
|
||||||
|
mkdir -p "$OURS_OUT" "$ROUTINATOR_OUT" "$RPKI_CLIENT_OUT"
|
||||||
|
|
||||||
|
ours_cmd=(
|
||||||
|
"$OURS_SCRIPT"
|
||||||
|
--cir "$CIR"
|
||||||
|
--repo-bytes-db "$REPO_BYTES_DB"
|
||||||
|
--out-dir "$OURS_OUT"
|
||||||
|
--reference-ccr "$REFERENCE_CCR"
|
||||||
|
--rpki-bin "$RPKI_BIN"
|
||||||
|
--real-rsync-bin "$REAL_RSYNC_BIN"
|
||||||
|
)
|
||||||
|
routinator_cmd=(
|
||||||
|
"$ROUTINATOR_SCRIPT"
|
||||||
|
--cir "$CIR"
|
||||||
|
--repo-bytes-db "$REPO_BYTES_DB"
|
||||||
|
--out-dir "$ROUTINATOR_OUT"
|
||||||
|
--reference-ccr "$REFERENCE_CCR"
|
||||||
|
--routinator-root "$ROUTINATOR_ROOT"
|
||||||
|
--routinator-bin "$ROUTINATOR_BIN"
|
||||||
|
--real-rsync-bin "$REAL_RSYNC_BIN"
|
||||||
|
)
|
||||||
|
rpki_client_cmd=(
|
||||||
|
"$RPKI_CLIENT_SCRIPT"
|
||||||
|
--cir "$CIR"
|
||||||
|
--repo-bytes-db "$REPO_BYTES_DB"
|
||||||
|
--out-dir "$RPKI_CLIENT_OUT"
|
||||||
|
--reference-ccr "$REFERENCE_CCR"
|
||||||
|
--build-dir "$RPKI_CLIENT_BUILD_DIR"
|
||||||
|
--real-rsync-bin "$REAL_RSYNC_BIN"
|
||||||
|
)
|
||||||
|
|
||||||
|
if [[ "$KEEP_DB" -eq 1 ]]; then
|
||||||
|
ours_cmd+=(--keep-db)
|
||||||
|
routinator_cmd+=(--keep-db)
|
||||||
|
rpki_client_cmd+=(--keep-db)
|
||||||
|
fi
|
||||||
|
|
||||||
|
ours_status=0
|
||||||
|
routinator_status=0
|
||||||
|
rpki_client_status=0
|
||||||
|
if run_with_timing "$OURS_OUT/compare-summary.json" "$OURS_OUT/timing.json" "${ours_cmd[@]}"; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
ours_status=$?
|
||||||
|
fi
|
||||||
|
if run_with_timing "$ROUTINATOR_OUT/compare-summary.json" "$ROUTINATOR_OUT/timing.json" "${routinator_cmd[@]}"; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
routinator_status=$?
|
||||||
|
fi
|
||||||
|
if run_with_timing "$RPKI_CLIENT_OUT/compare-summary.json" "$RPKI_CLIENT_OUT/timing.json" "${rpki_client_cmd[@]}"; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
rpki_client_status=$?
|
||||||
|
fi
|
||||||
|
|
||||||
|
SUMMARY_JSON="$OUT_DIR/summary.json"
|
||||||
|
SUMMARY_MD="$OUT_DIR/summary.md"
|
||||||
|
DETAIL_MD="$OUT_DIR/detail.md"
|
||||||
|
|
||||||
|
python3 - <<'PY' \
|
||||||
|
"$CIR" \
|
||||||
|
"$REPO_BYTES_DB" \
|
||||||
|
"$REFERENCE_CCR" \
|
||||||
|
"$OURS_OUT" \
|
||||||
|
"$ROUTINATOR_OUT" \
|
||||||
|
"$RPKI_CLIENT_OUT" \
|
||||||
|
"$SUMMARY_JSON" \
|
||||||
|
"$SUMMARY_MD" \
|
||||||
|
"$DETAIL_MD"
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
cir_path, repo_bytes_db, reference_ccr, ours_out, routinator_out, rpki_client_out, summary_json, summary_md, detail_md = sys.argv[1:]
|
||||||
|
|
||||||
|
participants = []
|
||||||
|
all_match = True
|
||||||
|
for name, out_dir in [
|
||||||
|
("ours", ours_out),
|
||||||
|
("routinator", routinator_out),
|
||||||
|
("rpki-client", rpki_client_out),
|
||||||
|
]:
|
||||||
|
out = Path(out_dir)
|
||||||
|
timing = json.loads((out / "timing.json").read_text(encoding="utf-8"))
|
||||||
|
compare = timing.get("compare") or {}
|
||||||
|
vrps = compare.get("vrps") or {}
|
||||||
|
vaps = compare.get("vaps") or {}
|
||||||
|
participant = {
|
||||||
|
"name": name,
|
||||||
|
"outDir": str(out),
|
||||||
|
"tmpRoot": str(out / ".tmp"),
|
||||||
|
"mirrorPath": str(out / ".tmp" / "mirror"),
|
||||||
|
"timingPath": str(out / "timing.json"),
|
||||||
|
"summaryPath": str(out / "compare-summary.json"),
|
||||||
|
"exitCode": timing["exitCode"],
|
||||||
|
"durationMs": timing["durationMs"],
|
||||||
|
"compareMode": compare.get("compareMode"),
|
||||||
|
"talCount": compare.get("talCount"),
|
||||||
|
"talPaths": compare.get("talPaths", []),
|
||||||
|
"vrps": vrps,
|
||||||
|
"vaps": vaps,
|
||||||
|
"match": bool(vrps.get("match")) and bool(vaps.get("match")) and timing["exitCode"] == 0,
|
||||||
|
"logPaths": [str(path) for path in sorted(out.glob("*.log"))],
|
||||||
|
}
|
||||||
|
participants.append(participant)
|
||||||
|
all_match = all_match and participant["match"]
|
||||||
|
|
||||||
|
summary = {
|
||||||
|
"cirPath": cir_path,
|
||||||
|
"repoBytesDb": repo_bytes_db,
|
||||||
|
"referenceCcr": reference_ccr,
|
||||||
|
"participants": participants,
|
||||||
|
"allMatch": all_match,
|
||||||
|
}
|
||||||
|
Path(summary_json).write_text(json.dumps(summary, indent=2), encoding="utf-8")
|
||||||
|
|
||||||
|
lines = [
|
||||||
|
"# CIR Replay Matrix Summary",
|
||||||
|
"",
|
||||||
|
f"- `cir`: `{cir_path}`",
|
||||||
|
f"- `repo_bytes_db`: `{repo_bytes_db}`",
|
||||||
|
f"- `reference_ccr`: `{reference_ccr}`",
|
||||||
|
f"- `all_match`: `{all_match}`",
|
||||||
|
"",
|
||||||
|
"| Participant | Exit | Duration (ms) | TALs | Compare mode | VRP actual/ref | VRP match | VAP actual/ref | VAP match | Log |",
|
||||||
|
"| --- | ---: | ---: | ---: | --- | --- | --- | --- | --- | --- |",
|
||||||
|
]
|
||||||
|
for participant in participants:
|
||||||
|
vrps = participant["vrps"] or {}
|
||||||
|
vaps = participant["vaps"] or {}
|
||||||
|
log_path = participant["logPaths"][0] if participant["logPaths"] else ""
|
||||||
|
lines.append(
|
||||||
|
"| {name} | {exit_code} | {duration_ms} | {tal_count} | {compare_mode} | {vrp_actual}/{vrp_ref} | {vrp_match} | {vap_actual}/{vap_ref} | {vap_match} | `{log_path}` |".format(
|
||||||
|
name=participant["name"],
|
||||||
|
exit_code=participant["exitCode"],
|
||||||
|
duration_ms=participant["durationMs"],
|
||||||
|
tal_count=participant.get("talCount") if participant.get("talCount") is not None else "-",
|
||||||
|
compare_mode=participant.get("compareMode") or "-",
|
||||||
|
vrp_actual=vrps.get("actual", "-"),
|
||||||
|
vrp_ref=vrps.get("reference", "-"),
|
||||||
|
vrp_match=vrps.get("match", False),
|
||||||
|
vap_actual=vaps.get("actual", "-"),
|
||||||
|
vap_ref=vaps.get("reference", "-"),
|
||||||
|
vap_match=vaps.get("match", False),
|
||||||
|
log_path=log_path,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
Path(summary_md).write_text("\n".join(lines) + "\n", encoding="utf-8")
|
||||||
|
|
||||||
|
detail_lines = [
|
||||||
|
"# CIR Replay Matrix Detail",
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
for participant in participants:
|
||||||
|
vrps = participant["vrps"] or {}
|
||||||
|
vaps = participant["vaps"] or {}
|
||||||
|
detail_lines.extend([
|
||||||
|
f"## {participant['name']}",
|
||||||
|
f"- `exit_code`: `{participant['exitCode']}`",
|
||||||
|
f"- `duration_ms`: `{participant['durationMs']}`",
|
||||||
|
f"- `out_dir`: `{participant['outDir']}`",
|
||||||
|
f"- `tmp_root`: `{participant['tmpRoot']}`",
|
||||||
|
f"- `mirror_path`: `{participant['mirrorPath']}`",
|
||||||
|
f"- `summary_path`: `{participant['summaryPath']}`",
|
||||||
|
f"- `timing_path`: `{participant['timingPath']}`",
|
||||||
|
f"- `compare_mode`: `{participant.get('compareMode')}`",
|
||||||
|
f"- `tal_count`: `{participant.get('talCount')}`",
|
||||||
|
f"- `log_paths`: `{', '.join(participant['logPaths'])}`",
|
||||||
|
f"- `vrps`: `actual={vrps.get('actual', '-')}` `reference={vrps.get('reference', '-')}` `match={vrps.get('match', False)}`",
|
||||||
|
f"- `vaps`: `actual={vaps.get('actual', '-')}` `reference={vaps.get('reference', '-')}` `match={vaps.get('match', False)}`",
|
||||||
|
f"- `vrps.only_in_actual`: `{vrps.get('only_in_actual', [])}`",
|
||||||
|
f"- `vrps.only_in_reference`: `{vrps.get('only_in_reference', [])}`",
|
||||||
|
f"- `vaps.only_in_actual`: `{vaps.get('only_in_actual', [])}`",
|
||||||
|
f"- `vaps.only_in_reference`: `{vaps.get('only_in_reference', [])}`",
|
||||||
|
"",
|
||||||
|
])
|
||||||
|
Path(detail_md).write_text("\n".join(detail_lines), encoding="utf-8")
|
||||||
|
PY
|
||||||
|
|
||||||
|
if [[ "$ours_status" -ne 0 || "$routinator_status" -ne 0 || "$rpki_client_status" -ne 0 ]]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
all_match="$(python3 - <<'PY' "$SUMMARY_JSON"
|
||||||
|
import json,sys
|
||||||
|
print("true" if json.load(open(sys.argv[1]))["allMatch"] else "false")
|
||||||
|
PY
|
||||||
|
)"
|
||||||
|
if [[ "$all_match" != "true" ]]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "done: $OUT_DIR"
|
||||||
252
scripts/cir/run_cir_replay_ours.sh
Executable file
252
scripts/cir/run_cir_replay_ours.sh
Executable file
@@ -0,0 +1,252 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage:
|
||||||
|
./scripts/cir/run_cir_replay_ours.sh \
|
||||||
|
--cir <path> \
|
||||||
|
--repo-bytes-db <path> \
|
||||||
|
--out-dir <path> \
|
||||||
|
--reference-ccr <path> \
|
||||||
|
[--keep-db] \
|
||||||
|
[--write-actual-ccr] \
|
||||||
|
[--write-report-json] \
|
||||||
|
[--report-json-compact] \
|
||||||
|
[--phase2-object-workers <n>] \
|
||||||
|
[--phase2-worker-queue-capacity <n>] \
|
||||||
|
[--rpki-bin <path>] \
|
||||||
|
[--real-rsync-bin <path>]
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
|
|
||||||
|
CIR=""
|
||||||
|
REPO_BYTES_DB=""
|
||||||
|
OUT_DIR=""
|
||||||
|
REFERENCE_CCR=""
|
||||||
|
KEEP_DB=0
|
||||||
|
WRITE_ACTUAL_CCR=0
|
||||||
|
WRITE_REPORT_JSON=0
|
||||||
|
REPORT_JSON_COMPACT=0
|
||||||
|
PHASE2_OBJECT_WORKERS="${CIR_REPLAY_PHASE2_OBJECT_WORKERS:-4}"
|
||||||
|
PHASE2_WORKER_QUEUE_CAPACITY="${CIR_REPLAY_PHASE2_WORKER_QUEUE_CAPACITY:-64}"
|
||||||
|
RPKI_BIN="${RPKI_BIN:-$ROOT_DIR/target/release/rpki}"
|
||||||
|
CIR_MATERIALIZE_BIN="${CIR_MATERIALIZE_BIN:-$ROOT_DIR/target/release/cir_materialize}"
|
||||||
|
CIR_EXTRACT_INPUTS_BIN="${CIR_EXTRACT_INPUTS_BIN:-$ROOT_DIR/target/release/cir_extract_inputs}"
|
||||||
|
CCR_TO_COMPARE_VIEWS_BIN="${CCR_TO_COMPARE_VIEWS_BIN:-$ROOT_DIR/target/release/ccr_to_compare_views}"
|
||||||
|
REAL_RSYNC_BIN="${REAL_RSYNC_BIN:-/usr/bin/rsync}"
|
||||||
|
WRAPPER="$ROOT_DIR/scripts/cir/cir-rsync-wrapper"
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--cir) CIR="$2"; shift 2 ;;
|
||||||
|
--repo-bytes-db) REPO_BYTES_DB="$2"; shift 2 ;;
|
||||||
|
--out-dir) OUT_DIR="$2"; shift 2 ;;
|
||||||
|
--reference-ccr) REFERENCE_CCR="$2"; shift 2 ;;
|
||||||
|
--keep-db) KEEP_DB=1; shift ;;
|
||||||
|
--write-actual-ccr) WRITE_ACTUAL_CCR=1; shift ;;
|
||||||
|
--write-report-json) WRITE_REPORT_JSON=1; shift ;;
|
||||||
|
--report-json-compact) WRITE_REPORT_JSON=1; REPORT_JSON_COMPACT=1; shift ;;
|
||||||
|
--phase2-object-workers) PHASE2_OBJECT_WORKERS="$2"; shift 2 ;;
|
||||||
|
--phase2-worker-queue-capacity) PHASE2_WORKER_QUEUE_CAPACITY="$2"; shift 2 ;;
|
||||||
|
--rpki-bin) RPKI_BIN="$2"; shift 2 ;;
|
||||||
|
--real-rsync-bin) REAL_RSYNC_BIN="$2"; shift 2 ;;
|
||||||
|
-h|--help) usage; exit 0 ;;
|
||||||
|
*) echo "unknown argument: $1" >&2; usage; exit 2 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
[[ -n "$CIR" && -n "$REPO_BYTES_DB" && -n "$OUT_DIR" && -n "$REFERENCE_CCR" ]] || {
|
||||||
|
usage >&2
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
|
||||||
|
mkdir -p "$OUT_DIR"
|
||||||
|
|
||||||
|
needs_build=0
|
||||||
|
if [[ ! -x "$RPKI_BIN" || ! -x "$CIR_MATERIALIZE_BIN" || ! -x "$CIR_EXTRACT_INPUTS_BIN" || ! -x "$CCR_TO_COMPARE_VIEWS_BIN" ]]; then
|
||||||
|
needs_build=1
|
||||||
|
elif [[ "$RPKI_BIN" == "$ROOT_DIR/target/release/rpki" ]] && find "$ROOT_DIR/src" "$ROOT_DIR/Cargo.toml" -newer "$RPKI_BIN" -print -quit | grep -q .; then
|
||||||
|
needs_build=1
|
||||||
|
fi
|
||||||
|
if [[ "$needs_build" -eq 1 ]]; then
|
||||||
|
(
|
||||||
|
cd "$ROOT_DIR"
|
||||||
|
cargo build --release --bin rpki --bin cir_materialize --bin cir_extract_inputs --bin ccr_to_compare_views
|
||||||
|
)
|
||||||
|
fi
|
||||||
|
|
||||||
|
TMP_ROOT="$OUT_DIR/.tmp"
|
||||||
|
TALS_DIR="$TMP_ROOT/tals"
|
||||||
|
META_JSON="$TMP_ROOT/meta.json"
|
||||||
|
MIRROR_ROOT="$TMP_ROOT/mirror"
|
||||||
|
DB_DIR="$TMP_ROOT/work-db"
|
||||||
|
REPLAY_RAW_STORE_DB="$TMP_ROOT/replay-raw-store.db"
|
||||||
|
REPLAY_REPO_BYTES_DB="$TMP_ROOT/replay-repo-bytes.db"
|
||||||
|
ACTUAL_CCR="$OUT_DIR/actual.ccr"
|
||||||
|
ACTUAL_REPORT="$OUT_DIR/report.json"
|
||||||
|
ACTUAL_VRPS="$OUT_DIR/actual-vrps.csv"
|
||||||
|
ACTUAL_VAPS="$OUT_DIR/actual-vaps.csv"
|
||||||
|
REF_VRPS="$OUT_DIR/reference-vrps.csv"
|
||||||
|
REF_VAPS="$OUT_DIR/reference-vaps.csv"
|
||||||
|
COMPARE_JSON="$OUT_DIR/compare-summary.json"
|
||||||
|
RUN_LOG="$OUT_DIR/run.log"
|
||||||
|
|
||||||
|
rm -rf "$TMP_ROOT"
|
||||||
|
mkdir -p "$TMP_ROOT"
|
||||||
|
|
||||||
|
"$CIR_EXTRACT_INPUTS_BIN" --cir "$CIR" --tals-dir "$TALS_DIR" --meta-json "$META_JSON"
|
||||||
|
materialize_cmd=("$CIR_MATERIALIZE_BIN" --cir "$CIR" --repo-bytes-db "$REPO_BYTES_DB" --mirror-root "$MIRROR_ROOT")
|
||||||
|
if [[ "$KEEP_DB" -eq 1 ]]; then
|
||||||
|
materialize_cmd+=(--keep-db)
|
||||||
|
fi
|
||||||
|
"${materialize_cmd[@]}"
|
||||||
|
|
||||||
|
VALIDATION_TIME="$(python3 - <<'PY' "$META_JSON"
|
||||||
|
import json,sys
|
||||||
|
print(json.load(open(sys.argv[1]))["validationTime"])
|
||||||
|
PY
|
||||||
|
)"
|
||||||
|
mapfile -t TAL_PATHS < <(python3 - <<'PY' "$META_JSON"
|
||||||
|
import json, sys
|
||||||
|
for item in json.load(open(sys.argv[1], encoding="utf-8"))["talFiles"]:
|
||||||
|
print(item["path"])
|
||||||
|
PY
|
||||||
|
)
|
||||||
|
TAL_ARGS=()
|
||||||
|
for tal_path in "${TAL_PATHS[@]}"; do
|
||||||
|
TAL_ARGS+=(--tal-path "$tal_path")
|
||||||
|
done
|
||||||
|
|
||||||
|
export CIR_MIRROR_ROOT="$(python3 - <<'PY' "$MIRROR_ROOT"
|
||||||
|
from pathlib import Path
|
||||||
|
import sys
|
||||||
|
print(Path(sys.argv[1]).resolve())
|
||||||
|
PY
|
||||||
|
)"
|
||||||
|
export REAL_RSYNC_BIN="$REAL_RSYNC_BIN"
|
||||||
|
export CIR_LOCAL_LINK_MODE=1
|
||||||
|
|
||||||
|
REPORT_JSON_ARGS=(--skip-report-build)
|
||||||
|
VCIR_ARGS=(--skip-vcir-persist)
|
||||||
|
if [[ "$WRITE_REPORT_JSON" -eq 1 ]]; then
|
||||||
|
REPORT_JSON_ARGS=(--report-json "$ACTUAL_REPORT")
|
||||||
|
if [[ "$REPORT_JSON_COMPACT" -eq 1 ]]; then
|
||||||
|
REPORT_JSON_ARGS+=(--report-json-compact)
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
CCR_ARGS=()
|
||||||
|
if [[ "$WRITE_ACTUAL_CCR" -eq 1 ]]; then
|
||||||
|
CCR_ARGS=(--ccr-out "$ACTUAL_CCR")
|
||||||
|
fi
|
||||||
|
|
||||||
|
"$RPKI_BIN" \
|
||||||
|
--db "$DB_DIR" \
|
||||||
|
--raw-store-db "$REPLAY_RAW_STORE_DB" \
|
||||||
|
--repo-bytes-db "$REPLAY_REPO_BYTES_DB" \
|
||||||
|
"${TAL_ARGS[@]}" \
|
||||||
|
--parallel-phase2-object-workers "$PHASE2_OBJECT_WORKERS" \
|
||||||
|
--parallel-phase2-worker-queue-capacity "$PHASE2_WORKER_QUEUE_CAPACITY" \
|
||||||
|
--disable-rrdp \
|
||||||
|
--rsync-command "$WRAPPER" \
|
||||||
|
--validation-time "$VALIDATION_TIME" \
|
||||||
|
"${CCR_ARGS[@]}" \
|
||||||
|
--vrps-csv-out "$ACTUAL_VRPS" \
|
||||||
|
--vaps-csv-out "$ACTUAL_VAPS" \
|
||||||
|
--compare-view-trust-anchor unknown \
|
||||||
|
"${VCIR_ARGS[@]}" \
|
||||||
|
"${REPORT_JSON_ARGS[@]}" \
|
||||||
|
>"$RUN_LOG" 2>&1
|
||||||
|
|
||||||
|
sort_compare_csv() {
|
||||||
|
local path="$1"
|
||||||
|
local tmp="${path}.sorted.tmp"
|
||||||
|
{
|
||||||
|
head -n 1 "$path"
|
||||||
|
tail -n +2 "$path" | LC_ALL=C sort -u
|
||||||
|
} >"$tmp"
|
||||||
|
mv "$tmp" "$path"
|
||||||
|
}
|
||||||
|
|
||||||
|
sort_compare_csv "$ACTUAL_VRPS"
|
||||||
|
sort_compare_csv "$ACTUAL_VAPS"
|
||||||
|
"$CCR_TO_COMPARE_VIEWS_BIN" --ccr "$REFERENCE_CCR" --vrps-out "$REF_VRPS" --vaps-out "$REF_VAPS" --trust-anchor unknown
|
||||||
|
|
||||||
|
python3 - <<'PY' "$ACTUAL_VRPS" "$REF_VRPS" "$ACTUAL_VAPS" "$REF_VAPS" "$COMPARE_JSON" "$META_JSON" "$WRITE_REPORT_JSON" "$WRITE_ACTUAL_CCR" "$PHASE2_OBJECT_WORKERS" "$PHASE2_WORKER_QUEUE_CAPACITY"
|
||||||
|
import csv, json, sys
|
||||||
|
|
||||||
|
def next_row(reader):
|
||||||
|
try:
|
||||||
|
return tuple(next(reader))
|
||||||
|
except StopIteration:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def compare_sorted_csv(actual_path, ref_path):
|
||||||
|
actual_count = 0
|
||||||
|
ref_count = 0
|
||||||
|
only_actual_count = 0
|
||||||
|
only_ref_count = 0
|
||||||
|
only_actual_sample = []
|
||||||
|
only_ref_sample = []
|
||||||
|
with open(actual_path, newline="") as actual_file, open(ref_path, newline="") as ref_file:
|
||||||
|
actual_reader = csv.reader(actual_file)
|
||||||
|
ref_reader = csv.reader(ref_file)
|
||||||
|
next(actual_reader, None)
|
||||||
|
next(ref_reader, None)
|
||||||
|
actual = next_row(actual_reader)
|
||||||
|
ref = next_row(ref_reader)
|
||||||
|
while actual is not None or ref is not None:
|
||||||
|
if ref is None or (actual is not None and actual < ref):
|
||||||
|
actual_count += 1
|
||||||
|
only_actual_count += 1
|
||||||
|
if len(only_actual_sample) < 20:
|
||||||
|
only_actual_sample.append(list(actual))
|
||||||
|
actual = next_row(actual_reader)
|
||||||
|
elif actual is None or ref < actual:
|
||||||
|
ref_count += 1
|
||||||
|
only_ref_count += 1
|
||||||
|
if len(only_ref_sample) < 20:
|
||||||
|
only_ref_sample.append(list(ref))
|
||||||
|
ref = next_row(ref_reader)
|
||||||
|
else:
|
||||||
|
actual_count += 1
|
||||||
|
ref_count += 1
|
||||||
|
actual = next_row(actual_reader)
|
||||||
|
ref = next_row(ref_reader)
|
||||||
|
return {
|
||||||
|
"actual": actual_count,
|
||||||
|
"reference": ref_count,
|
||||||
|
"only_in_actual": only_actual_sample,
|
||||||
|
"only_in_reference": only_ref_sample,
|
||||||
|
"only_in_actual_count": only_actual_count,
|
||||||
|
"only_in_reference_count": only_ref_count,
|
||||||
|
"match": only_actual_count == 0 and only_ref_count == 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
vrps = compare_sorted_csv(sys.argv[1], sys.argv[2])
|
||||||
|
vaps = compare_sorted_csv(sys.argv[3], sys.argv[4])
|
||||||
|
meta = json.load(open(sys.argv[6], encoding="utf-8"))
|
||||||
|
summary = {
|
||||||
|
"compareMode": "trust-anchor-agnostic",
|
||||||
|
"talCount": len(meta["talFiles"]),
|
||||||
|
"talPaths": [item["path"] for item in meta["talFiles"]],
|
||||||
|
"actualCcrWritten": sys.argv[8] == "1",
|
||||||
|
"reportJsonWritten": sys.argv[7] == "1",
|
||||||
|
"replayParallelism": {
|
||||||
|
"phase2ObjectWorkers": int(sys.argv[9]),
|
||||||
|
"phase2WorkerQueueCapacity": int(sys.argv[10]),
|
||||||
|
},
|
||||||
|
"vrps": vrps,
|
||||||
|
"vaps": vaps,
|
||||||
|
}
|
||||||
|
with open(sys.argv[5], "w") as f:
|
||||||
|
json.dump(summary, f, indent=2)
|
||||||
|
PY
|
||||||
|
|
||||||
|
if [[ "$KEEP_DB" -ne 1 ]]; then
|
||||||
|
rm -rf "$TMP_ROOT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "done: $OUT_DIR"
|
||||||
239
scripts/cir/run_cir_replay_routinator.sh
Executable file
239
scripts/cir/run_cir_replay_routinator.sh
Executable file
@@ -0,0 +1,239 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage:
|
||||||
|
./scripts/cir/run_cir_replay_routinator.sh \
|
||||||
|
--cir <path> \
|
||||||
|
--repo-bytes-db <path> \
|
||||||
|
--out-dir <path> \
|
||||||
|
--reference-ccr <path> \
|
||||||
|
[--keep-db] \
|
||||||
|
[--routinator-root <path>] \
|
||||||
|
[--routinator-bin <path>] \
|
||||||
|
[--real-rsync-bin <path>]
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
|
RPKI_DEV_ROOT="${RPKI_DEV_ROOT:-$ROOT_DIR}"
|
||||||
|
|
||||||
|
CIR=""
|
||||||
|
REPO_BYTES_DB=""
|
||||||
|
OUT_DIR=""
|
||||||
|
REFERENCE_CCR=""
|
||||||
|
KEEP_DB=0
|
||||||
|
ROUTINATOR_ROOT="${ROUTINATOR_ROOT:-/home/yuyr/dev/rust_playground/routinator}"
|
||||||
|
ROUTINATOR_BIN="${ROUTINATOR_BIN:-$ROUTINATOR_ROOT/target/debug/routinator}"
|
||||||
|
REAL_RSYNC_BIN="${REAL_RSYNC_BIN:-/usr/bin/rsync}"
|
||||||
|
CIR_MATERIALIZE_BIN="${CIR_MATERIALIZE_BIN:-$ROOT_DIR/target/release/cir_materialize}"
|
||||||
|
CIR_EXTRACT_INPUTS_BIN="${CIR_EXTRACT_INPUTS_BIN:-$ROOT_DIR/target/release/cir_extract_inputs}"
|
||||||
|
CCR_TO_COMPARE_VIEWS_BIN="${CCR_TO_COMPARE_VIEWS_BIN:-$ROOT_DIR/target/release/ccr_to_compare_views}"
|
||||||
|
WRAPPER="$ROOT_DIR/scripts/cir/cir-rsync-wrapper"
|
||||||
|
JSON_TO_VAPS="$ROOT_DIR/scripts/cir/json_to_vaps_csv.py"
|
||||||
|
FAKETIME_LIB="${FAKETIME_LIB:-$ROOT_DIR/target/tools/faketime_pkg/extracted/libfaketime/usr/lib/x86_64-linux-gnu/faketime/libfaketime.so.1}"
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--cir) CIR="$2"; shift 2 ;;
|
||||||
|
--repo-bytes-db) REPO_BYTES_DB="$2"; shift 2 ;;
|
||||||
|
--out-dir) OUT_DIR="$2"; shift 2 ;;
|
||||||
|
--reference-ccr) REFERENCE_CCR="$2"; shift 2 ;;
|
||||||
|
--keep-db) KEEP_DB=1; shift ;;
|
||||||
|
--routinator-root) ROUTINATOR_ROOT="$2"; shift 2 ;;
|
||||||
|
--routinator-bin) ROUTINATOR_BIN="$2"; shift 2 ;;
|
||||||
|
--real-rsync-bin) REAL_RSYNC_BIN="$2"; shift 2 ;;
|
||||||
|
-h|--help) usage; exit 0 ;;
|
||||||
|
*) echo "unknown argument: $1" >&2; usage; exit 2 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
[[ -n "$CIR" && -n "$REPO_BYTES_DB" && -n "$OUT_DIR" && -n "$REFERENCE_CCR" ]] || {
|
||||||
|
usage >&2
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
if [[ ! -x "$ROUTINATOR_BIN" ]]; then
|
||||||
|
echo "routinator binary not executable: $ROUTINATOR_BIN" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$OUT_DIR"
|
||||||
|
if [[ ! -x "$CIR_MATERIALIZE_BIN" || ! -x "$CIR_EXTRACT_INPUTS_BIN" || ! -x "$CCR_TO_COMPARE_VIEWS_BIN" ]]; then
|
||||||
|
(
|
||||||
|
cd "$ROOT_DIR"
|
||||||
|
cargo build --release --bin cir_materialize --bin cir_extract_inputs --bin ccr_to_compare_views
|
||||||
|
)
|
||||||
|
fi
|
||||||
|
|
||||||
|
TMP_ROOT="$OUT_DIR/.tmp"
|
||||||
|
TALS_DIR="$TMP_ROOT/tals"
|
||||||
|
META_JSON="$TMP_ROOT/meta.json"
|
||||||
|
MIRROR_ROOT="$TMP_ROOT/mirror"
|
||||||
|
WORK_REPO="$TMP_ROOT/repository"
|
||||||
|
RUN_LOG="$OUT_DIR/routinator.log"
|
||||||
|
ACTUAL_VRPS="$OUT_DIR/actual-vrps.csv"
|
||||||
|
ACTUAL_VAPS_JSON="$OUT_DIR/actual-vaps.json"
|
||||||
|
ACTUAL_VAPS="$OUT_DIR/actual-vaps.csv"
|
||||||
|
REF_VRPS="$OUT_DIR/reference-vrps.csv"
|
||||||
|
REF_VAPS="$OUT_DIR/reference-vaps.csv"
|
||||||
|
SUMMARY_JSON="$OUT_DIR/compare-summary.json"
|
||||||
|
|
||||||
|
rm -rf "$TMP_ROOT"
|
||||||
|
mkdir -p "$TMP_ROOT"
|
||||||
|
|
||||||
|
"$CIR_EXTRACT_INPUTS_BIN" --cir "$CIR" --tals-dir "$TALS_DIR" --meta-json "$META_JSON"
|
||||||
|
python3 - <<'PY' "$TALS_DIR"
|
||||||
|
from pathlib import Path
|
||||||
|
import sys
|
||||||
|
for tal in Path(sys.argv[1]).glob("*.tal"):
|
||||||
|
lines = tal.read_text(encoding="utf-8").splitlines()
|
||||||
|
rsync_uris = [line for line in lines if line.startswith("rsync://")]
|
||||||
|
base64_lines = []
|
||||||
|
seen_sep = False
|
||||||
|
for line in lines:
|
||||||
|
if seen_sep:
|
||||||
|
if line.strip():
|
||||||
|
base64_lines.append(line)
|
||||||
|
elif line.strip() == "":
|
||||||
|
seen_sep = True
|
||||||
|
tal.write_text("\n".join(rsync_uris) + "\n\n" + "\n".join(base64_lines) + "\n", encoding="utf-8")
|
||||||
|
PY
|
||||||
|
materialize_cmd=("$CIR_MATERIALIZE_BIN" --cir "$CIR" --repo-bytes-db "$REPO_BYTES_DB" --mirror-root "$MIRROR_ROOT")
|
||||||
|
if [[ "$KEEP_DB" -eq 1 ]]; then
|
||||||
|
materialize_cmd+=(--keep-db)
|
||||||
|
fi
|
||||||
|
"${materialize_cmd[@]}"
|
||||||
|
|
||||||
|
VALIDATION_TIME="$(python3 - <<'PY' "$META_JSON"
|
||||||
|
import json,sys
|
||||||
|
print(json.load(open(sys.argv[1]))["validationTime"])
|
||||||
|
PY
|
||||||
|
)"
|
||||||
|
mapfile -t TAL_PATHS < <(python3 - <<'PY' "$META_JSON"
|
||||||
|
import json, sys
|
||||||
|
for item in json.load(open(sys.argv[1], encoding="utf-8"))["talFiles"]:
|
||||||
|
print(item["path"])
|
||||||
|
PY
|
||||||
|
)
|
||||||
|
COMPARE_TRUST_ANCHOR="unknown"
|
||||||
|
FAKE_EPOCH="$(python3 - <<'PY' "$VALIDATION_TIME"
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
import sys
|
||||||
|
dt = datetime.fromisoformat(sys.argv[1].replace("Z", "+00:00")).astimezone(timezone.utc)
|
||||||
|
print(int(dt.timestamp()))
|
||||||
|
PY
|
||||||
|
)"
|
||||||
|
|
||||||
|
export CIR_MIRROR_ROOT="$(python3 - <<'PY' "$MIRROR_ROOT"
|
||||||
|
from pathlib import Path
|
||||||
|
import sys
|
||||||
|
print(Path(sys.argv[1]).resolve())
|
||||||
|
PY
|
||||||
|
)"
|
||||||
|
export REAL_RSYNC_BIN="$REAL_RSYNC_BIN"
|
||||||
|
export CIR_LOCAL_LINK_MODE=1
|
||||||
|
env \
|
||||||
|
LD_PRELOAD="$FAKETIME_LIB" \
|
||||||
|
FAKETIME_FMT=%s \
|
||||||
|
FAKETIME="$FAKE_EPOCH" \
|
||||||
|
FAKETIME_DONT_FAKE_MONOTONIC=1 \
|
||||||
|
"$ROUTINATOR_BIN" \
|
||||||
|
--repository-dir "$WORK_REPO" \
|
||||||
|
--disable-rrdp \
|
||||||
|
--rsync-command "$WRAPPER" \
|
||||||
|
--no-rir-tals \
|
||||||
|
--extra-tals-dir "$TALS_DIR" \
|
||||||
|
--enable-aspa \
|
||||||
|
update --complete >"$RUN_LOG" 2>&1 || true
|
||||||
|
|
||||||
|
env \
|
||||||
|
LD_PRELOAD="$FAKETIME_LIB" \
|
||||||
|
FAKETIME_FMT=%s \
|
||||||
|
FAKETIME="$FAKE_EPOCH" \
|
||||||
|
FAKETIME_DONT_FAKE_MONOTONIC=1 \
|
||||||
|
"$ROUTINATOR_BIN" \
|
||||||
|
--repository-dir "$WORK_REPO" \
|
||||||
|
--disable-rrdp \
|
||||||
|
--rsync-command "$WRAPPER" \
|
||||||
|
--no-rir-tals \
|
||||||
|
--extra-tals-dir "$TALS_DIR" \
|
||||||
|
--enable-aspa \
|
||||||
|
vrps --noupdate -o "$ACTUAL_VRPS" >>"$RUN_LOG" 2>&1
|
||||||
|
|
||||||
|
env \
|
||||||
|
LD_PRELOAD="$FAKETIME_LIB" \
|
||||||
|
FAKETIME_FMT=%s \
|
||||||
|
FAKETIME="$FAKE_EPOCH" \
|
||||||
|
FAKETIME_DONT_FAKE_MONOTONIC=1 \
|
||||||
|
"$ROUTINATOR_BIN" \
|
||||||
|
--repository-dir "$WORK_REPO" \
|
||||||
|
--disable-rrdp \
|
||||||
|
--rsync-command "$WRAPPER" \
|
||||||
|
--no-rir-tals \
|
||||||
|
--extra-tals-dir "$TALS_DIR" \
|
||||||
|
--enable-aspa \
|
||||||
|
vrps --noupdate --format json -o "$ACTUAL_VAPS_JSON" >>"$RUN_LOG" 2>&1
|
||||||
|
|
||||||
|
python3 "$JSON_TO_VAPS" --input "$ACTUAL_VAPS_JSON" --csv-out "$ACTUAL_VAPS"
|
||||||
|
|
||||||
|
normalize_trust_anchor_csv() {
|
||||||
|
python3 - <<'PY' "$1" "$2"
|
||||||
|
import csv
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
path = Path(sys.argv[1])
|
||||||
|
trust_anchor = sys.argv[2]
|
||||||
|
rows = list(csv.reader(path.open(newline="", encoding="utf-8")))
|
||||||
|
if rows:
|
||||||
|
for row in rows[1:]:
|
||||||
|
if row:
|
||||||
|
row[-1] = trust_anchor
|
||||||
|
with path.open("w", newline="", encoding="utf-8") as fh:
|
||||||
|
csv.writer(fh).writerows(rows)
|
||||||
|
PY
|
||||||
|
}
|
||||||
|
|
||||||
|
normalize_trust_anchor_csv "$ACTUAL_VRPS" "$COMPARE_TRUST_ANCHOR"
|
||||||
|
normalize_trust_anchor_csv "$ACTUAL_VAPS" "$COMPARE_TRUST_ANCHOR"
|
||||||
|
"$CCR_TO_COMPARE_VIEWS_BIN" --ccr "$REFERENCE_CCR" --vrps-out "$REF_VRPS" --vaps-out "$REF_VAPS" --trust-anchor "$COMPARE_TRUST_ANCHOR"
|
||||||
|
|
||||||
|
python3 - <<'PY' "$ACTUAL_VRPS" "$REF_VRPS" "$ACTUAL_VAPS" "$REF_VAPS" "$SUMMARY_JSON" "$META_JSON"
|
||||||
|
import csv, json, sys
|
||||||
|
def rows(path):
|
||||||
|
with open(path, newline="") as f:
|
||||||
|
return list(csv.reader(f))[1:]
|
||||||
|
actual_vrps = {tuple(r) for r in rows(sys.argv[1])}
|
||||||
|
ref_vrps = {tuple(r) for r in rows(sys.argv[2])}
|
||||||
|
actual_vaps = {tuple(r) for r in rows(sys.argv[3])}
|
||||||
|
ref_vaps = {tuple(r) for r in rows(sys.argv[4])}
|
||||||
|
meta = json.load(open(sys.argv[6], encoding="utf-8"))
|
||||||
|
summary = {
|
||||||
|
"compareMode": "trust-anchor-agnostic",
|
||||||
|
"talCount": len(meta["talFiles"]),
|
||||||
|
"talPaths": [item["path"] for item in meta["talFiles"]],
|
||||||
|
"vrps": {
|
||||||
|
"actual": len(actual_vrps),
|
||||||
|
"reference": len(ref_vrps),
|
||||||
|
"match": actual_vrps == ref_vrps,
|
||||||
|
"only_in_actual": sorted(actual_vrps - ref_vrps)[:20],
|
||||||
|
"only_in_reference": sorted(ref_vrps - actual_vrps)[:20],
|
||||||
|
},
|
||||||
|
"vaps": {
|
||||||
|
"actual": len(actual_vaps),
|
||||||
|
"reference": len(ref_vaps),
|
||||||
|
"match": actual_vaps == ref_vaps,
|
||||||
|
"only_in_actual": sorted(actual_vaps - ref_vaps)[:20],
|
||||||
|
"only_in_reference": sorted(ref_vaps - actual_vaps)[:20],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
with open(sys.argv[5], "w") as f:
|
||||||
|
json.dump(summary, f, indent=2)
|
||||||
|
PY
|
||||||
|
|
||||||
|
if [[ "$KEEP_DB" -ne 1 ]]; then
|
||||||
|
rm -rf "$TMP_ROOT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "done: $OUT_DIR"
|
||||||
248
scripts/cir/run_cir_replay_rpki_client.sh
Executable file
248
scripts/cir/run_cir_replay_rpki_client.sh
Executable file
@@ -0,0 +1,248 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage:
|
||||||
|
./scripts/cir/run_cir_replay_rpki_client.sh \
|
||||||
|
--cir <path> \
|
||||||
|
--repo-bytes-db <path> \
|
||||||
|
--out-dir <path> \
|
||||||
|
--reference-ccr <path> \
|
||||||
|
[--build-dir <path> | --rpki-client-bin <path>] \
|
||||||
|
[--keep-db] \
|
||||||
|
[--real-rsync-bin <path>]
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
|
CIR=""
|
||||||
|
REPO_BYTES_DB=""
|
||||||
|
OUT_DIR=""
|
||||||
|
REFERENCE_CCR=""
|
||||||
|
BUILD_DIR=""
|
||||||
|
RPKI_CLIENT_BIN=""
|
||||||
|
KEEP_DB=0
|
||||||
|
REAL_RSYNC_BIN="${REAL_RSYNC_BIN:-/usr/bin/rsync}"
|
||||||
|
CIR_MATERIALIZE_BIN="${CIR_MATERIALIZE_BIN:-$ROOT_DIR/target/release/cir_materialize}"
|
||||||
|
CIR_EXTRACT_INPUTS_BIN="${CIR_EXTRACT_INPUTS_BIN:-$ROOT_DIR/target/release/cir_extract_inputs}"
|
||||||
|
CCR_TO_COMPARE_VIEWS_BIN="${CCR_TO_COMPARE_VIEWS_BIN:-$ROOT_DIR/target/release/ccr_to_compare_views}"
|
||||||
|
WRAPPER="$ROOT_DIR/scripts/cir/cir-rsync-wrapper"
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--cir) CIR="$2"; shift 2 ;;
|
||||||
|
--repo-bytes-db) REPO_BYTES_DB="$2"; shift 2 ;;
|
||||||
|
--out-dir) OUT_DIR="$2"; shift 2 ;;
|
||||||
|
--reference-ccr) REFERENCE_CCR="$2"; shift 2 ;;
|
||||||
|
--build-dir) BUILD_DIR="$2"; shift 2 ;;
|
||||||
|
--rpki-client-bin) RPKI_CLIENT_BIN="$2"; shift 2 ;;
|
||||||
|
--keep-db) KEEP_DB=1; shift ;;
|
||||||
|
--real-rsync-bin) REAL_RSYNC_BIN="$2"; shift 2 ;;
|
||||||
|
-h|--help) usage; exit 0 ;;
|
||||||
|
*) echo "unknown argument: $1" >&2; usage; exit 2 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
[[ -n "$CIR" && -n "$REPO_BYTES_DB" && -n "$OUT_DIR" && -n "$REFERENCE_CCR" ]] || {
|
||||||
|
usage >&2
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
if [[ -z "$BUILD_DIR" && -z "$RPKI_CLIENT_BIN" ]]; then
|
||||||
|
usage >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
if [[ -z "$RPKI_CLIENT_BIN" ]]; then
|
||||||
|
RPKI_CLIENT_BIN="$BUILD_DIR/src/rpki-client"
|
||||||
|
fi
|
||||||
|
if [[ ! -x "$RPKI_CLIENT_BIN" ]]; then
|
||||||
|
echo "rpki-client binary not executable: $RPKI_CLIENT_BIN" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$OUT_DIR"
|
||||||
|
if [[ ! -x "$CIR_MATERIALIZE_BIN" || ! -x "$CIR_EXTRACT_INPUTS_BIN" || ! -x "$CCR_TO_COMPARE_VIEWS_BIN" ]]; then
|
||||||
|
(
|
||||||
|
cd "$ROOT_DIR"
|
||||||
|
cargo build --release --bin cir_materialize --bin cir_extract_inputs --bin ccr_to_compare_views
|
||||||
|
)
|
||||||
|
fi
|
||||||
|
|
||||||
|
TMP_ROOT="$OUT_DIR/.tmp"
|
||||||
|
TALS_DIR="$TMP_ROOT/tals"
|
||||||
|
META_JSON="$TMP_ROOT/meta.json"
|
||||||
|
MIRROR_ROOT="$TMP_ROOT/mirror"
|
||||||
|
CACHE_DIR="$TMP_ROOT/cache"
|
||||||
|
OUT_CCR_DIR="$TMP_ROOT/out"
|
||||||
|
RUN_LOG="$OUT_DIR/rpki-client.log"
|
||||||
|
ACTUAL_VRPS="$OUT_DIR/actual-vrps.csv"
|
||||||
|
ACTUAL_VAPS="$OUT_DIR/actual-vaps.csv"
|
||||||
|
ACTUAL_VAPS_META="$OUT_DIR/actual-vaps-meta.json"
|
||||||
|
ACTUAL_VRPS_META="$OUT_DIR/actual-vrps-meta.json"
|
||||||
|
REF_VRPS="$OUT_DIR/reference-vrps.csv"
|
||||||
|
REF_VAPS="$OUT_DIR/reference-vaps.csv"
|
||||||
|
SUMMARY_JSON="$OUT_DIR/compare-summary.json"
|
||||||
|
|
||||||
|
rm -rf "$TMP_ROOT"
|
||||||
|
mkdir -p "$TMP_ROOT"
|
||||||
|
|
||||||
|
"$CIR_EXTRACT_INPUTS_BIN" --cir "$CIR" --tals-dir "$TALS_DIR" --meta-json "$META_JSON"
|
||||||
|
python3 - <<'PY' "$TALS_DIR"
|
||||||
|
from pathlib import Path
|
||||||
|
import sys
|
||||||
|
for tal in Path(sys.argv[1]).glob("*.tal"):
|
||||||
|
lines = tal.read_text(encoding="utf-8").splitlines()
|
||||||
|
rsync_uris = [line for line in lines if line.startswith("rsync://")]
|
||||||
|
base64_lines = []
|
||||||
|
seen_sep = False
|
||||||
|
for line in lines:
|
||||||
|
if seen_sep:
|
||||||
|
if line.strip():
|
||||||
|
base64_lines.append(line)
|
||||||
|
elif line.strip() == "":
|
||||||
|
seen_sep = True
|
||||||
|
tal.write_text("\n".join(rsync_uris) + "\n\n" + "\n".join(base64_lines) + "\n", encoding="utf-8")
|
||||||
|
PY
|
||||||
|
materialize_cmd=("$CIR_MATERIALIZE_BIN" --cir "$CIR" --repo-bytes-db "$REPO_BYTES_DB" --mirror-root "$MIRROR_ROOT")
|
||||||
|
if [[ "$KEEP_DB" -eq 1 ]]; then
|
||||||
|
materialize_cmd+=(--keep-db)
|
||||||
|
fi
|
||||||
|
"${materialize_cmd[@]}"
|
||||||
|
|
||||||
|
VALIDATION_EPOCH="$(python3 - <<'PY' "$META_JSON"
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
import json, sys
|
||||||
|
vt = json.load(open(sys.argv[1]))["validationTime"]
|
||||||
|
dt = datetime.fromisoformat(vt.replace("Z", "+00:00")).astimezone(timezone.utc)
|
||||||
|
print(int(dt.timestamp()))
|
||||||
|
PY
|
||||||
|
)"
|
||||||
|
mapfile -t TAL_PATHS < <(python3 - <<'PY' "$META_JSON"
|
||||||
|
import json, sys
|
||||||
|
for item in json.load(open(sys.argv[1], encoding="utf-8"))["talFiles"]:
|
||||||
|
print(item["path"])
|
||||||
|
PY
|
||||||
|
)
|
||||||
|
CLIENT_TAL_ARGS=()
|
||||||
|
for tal_path in "${TAL_PATHS[@]}"; do
|
||||||
|
CLIENT_TAL_ARGS+=(-t "$tal_path")
|
||||||
|
done
|
||||||
|
COMPARE_TRUST_ANCHOR="unknown"
|
||||||
|
|
||||||
|
export CIR_MIRROR_ROOT="$(python3 - <<'PY' "$MIRROR_ROOT"
|
||||||
|
from pathlib import Path
|
||||||
|
import sys
|
||||||
|
print(Path(sys.argv[1]).resolve())
|
||||||
|
PY
|
||||||
|
)"
|
||||||
|
export REAL_RSYNC_BIN="$REAL_RSYNC_BIN"
|
||||||
|
export CIR_LOCAL_LINK_MODE=1
|
||||||
|
|
||||||
|
mkdir -p "$CACHE_DIR" "$OUT_CCR_DIR"
|
||||||
|
chmod -R 0777 "$TMP_ROOT"
|
||||||
|
"$RPKI_CLIENT_BIN" \
|
||||||
|
-R \
|
||||||
|
-e "$WRAPPER" \
|
||||||
|
-P "$VALIDATION_EPOCH" \
|
||||||
|
"${CLIENT_TAL_ARGS[@]}" \
|
||||||
|
-d "$CACHE_DIR" \
|
||||||
|
"$OUT_CCR_DIR" >"$RUN_LOG" 2>&1
|
||||||
|
|
||||||
|
if [[ -f "$OUT_CCR_DIR/rpki.ccr" ]]; then
|
||||||
|
"$CCR_TO_COMPARE_VIEWS_BIN" \
|
||||||
|
--ccr "$OUT_CCR_DIR/rpki.ccr" \
|
||||||
|
--vrps-out "$ACTUAL_VRPS" \
|
||||||
|
--vaps-out "$ACTUAL_VAPS" \
|
||||||
|
--trust-anchor "$COMPARE_TRUST_ANCHOR"
|
||||||
|
else
|
||||||
|
python3 - <<'PY' "$OUT_CCR_DIR/json" "$ACTUAL_VRPS" "$ACTUAL_VAPS" "$COMPARE_TRUST_ANCHOR"
|
||||||
|
import csv
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
json_path = Path(sys.argv[1])
|
||||||
|
vrps_out = Path(sys.argv[2])
|
||||||
|
vaps_out = Path(sys.argv[3])
|
||||||
|
compare_ta = sys.argv[4]
|
||||||
|
|
||||||
|
if not json_path.is_file():
|
||||||
|
raise SystemExit(f"rpki-client output has neither rpki.ccr nor json: {json_path}")
|
||||||
|
|
||||||
|
data = json.loads(json_path.read_text(encoding="utf-8"))
|
||||||
|
|
||||||
|
vrps_out.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
with vrps_out.open("w", newline="", encoding="utf-8") as fh:
|
||||||
|
writer = csv.writer(fh)
|
||||||
|
writer.writerow(["ASN", "IP Prefix", "Max Length", "Trust Anchor"])
|
||||||
|
for roa in data.get("roas", []):
|
||||||
|
writer.writerow([
|
||||||
|
f"AS{roa['asn']}",
|
||||||
|
roa["prefix"],
|
||||||
|
str(roa["maxLength"]),
|
||||||
|
compare_ta,
|
||||||
|
])
|
||||||
|
|
||||||
|
with vaps_out.open("w", newline="", encoding="utf-8") as fh:
|
||||||
|
writer = csv.writer(fh)
|
||||||
|
writer.writerow(["Customer ASN", "Providers", "Trust Anchor"])
|
||||||
|
for aspa in data.get("aspas", []):
|
||||||
|
providers = ";".join(f"AS{item}" for item in sorted(aspa.get("providers", [])))
|
||||||
|
writer.writerow([
|
||||||
|
f"AS{aspa['customer_asid']}",
|
||||||
|
providers,
|
||||||
|
compare_ta,
|
||||||
|
])
|
||||||
|
PY
|
||||||
|
fi
|
||||||
|
|
||||||
|
python3 - <<'PY' "$ACTUAL_VRPS" "$ACTUAL_VAPS" "$ACTUAL_VRPS_META" "$ACTUAL_VAPS_META"
|
||||||
|
import csv, json, sys
|
||||||
|
def count_rows(path):
|
||||||
|
with open(path, newline="") as f:
|
||||||
|
rows = list(csv.reader(f))
|
||||||
|
return max(len(rows) - 1, 0)
|
||||||
|
json.dump({"count": count_rows(sys.argv[1])}, open(sys.argv[3], "w"), indent=2)
|
||||||
|
json.dump({"count": count_rows(sys.argv[2])}, open(sys.argv[4], "w"), indent=2)
|
||||||
|
PY
|
||||||
|
|
||||||
|
"$CCR_TO_COMPARE_VIEWS_BIN" --ccr "$REFERENCE_CCR" --vrps-out "$REF_VRPS" --vaps-out "$REF_VAPS" --trust-anchor "$COMPARE_TRUST_ANCHOR"
|
||||||
|
|
||||||
|
python3 - <<'PY' "$ACTUAL_VRPS" "$REF_VRPS" "$ACTUAL_VAPS" "$REF_VAPS" "$SUMMARY_JSON" "$META_JSON"
|
||||||
|
import csv, json, sys
|
||||||
|
def rows(path):
|
||||||
|
with open(path, newline="") as f:
|
||||||
|
return list(csv.reader(f))[1:]
|
||||||
|
actual_vrps = {tuple(r) for r in rows(sys.argv[1])}
|
||||||
|
ref_vrps = {tuple(r) for r in rows(sys.argv[2])}
|
||||||
|
actual_vaps = {tuple(r) for r in rows(sys.argv[3])}
|
||||||
|
ref_vaps = {tuple(r) for r in rows(sys.argv[4])}
|
||||||
|
meta = json.load(open(sys.argv[6], encoding="utf-8"))
|
||||||
|
summary = {
|
||||||
|
"compareMode": "trust-anchor-agnostic",
|
||||||
|
"talCount": len(meta["talFiles"]),
|
||||||
|
"talPaths": [item["path"] for item in meta["talFiles"]],
|
||||||
|
"vrps": {
|
||||||
|
"actual": len(actual_vrps),
|
||||||
|
"reference": len(ref_vrps),
|
||||||
|
"match": actual_vrps == ref_vrps,
|
||||||
|
"only_in_actual": sorted(actual_vrps - ref_vrps)[:20],
|
||||||
|
"only_in_reference": sorted(ref_vrps - actual_vrps)[:20],
|
||||||
|
},
|
||||||
|
"vaps": {
|
||||||
|
"actual": len(actual_vaps),
|
||||||
|
"reference": len(ref_vaps),
|
||||||
|
"match": actual_vaps == ref_vaps,
|
||||||
|
"only_in_actual": sorted(actual_vaps - ref_vaps)[:20],
|
||||||
|
"only_in_reference": sorted(ref_vaps - actual_vaps)[:20],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
with open(sys.argv[5], "w") as f:
|
||||||
|
json.dump(summary, f, indent=2)
|
||||||
|
PY
|
||||||
|
|
||||||
|
if [[ "$KEEP_DB" -ne 1 ]]; then
|
||||||
|
rm -rf "$TMP_ROOT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "done: $OUT_DIR"
|
||||||
147
scripts/cir/run_cir_replay_sequence_ours.sh
Executable file
147
scripts/cir/run_cir_replay_sequence_ours.sh
Executable file
@@ -0,0 +1,147 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage:
|
||||||
|
./scripts/cir/run_cir_replay_sequence_ours.sh \
|
||||||
|
--sequence-root <path> \
|
||||||
|
[--rpki-bin <path>] \
|
||||||
|
[--real-rsync-bin <path>]
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
|
SEQUENCE_ROOT=""
|
||||||
|
RPKI_BIN="${RPKI_BIN:-$ROOT_DIR/target/release/rpki}"
|
||||||
|
REAL_RSYNC_BIN="${REAL_RSYNC_BIN:-/usr/bin/rsync}"
|
||||||
|
STEP_SCRIPT="$ROOT_DIR/scripts/cir/run_cir_replay_ours.sh"
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--sequence-root) SEQUENCE_ROOT="$2"; shift 2 ;;
|
||||||
|
--rpki-bin) RPKI_BIN="$2"; shift 2 ;;
|
||||||
|
--real-rsync-bin) REAL_RSYNC_BIN="$2"; shift 2 ;;
|
||||||
|
-h|--help) usage; exit 0 ;;
|
||||||
|
*) echo "unknown argument: $1" >&2; usage; exit 2 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
[[ -n "$SEQUENCE_ROOT" ]] || { usage >&2; exit 2; }
|
||||||
|
|
||||||
|
SEQUENCE_ROOT="$(python3 - <<'PY' "$SEQUENCE_ROOT"
|
||||||
|
from pathlib import Path
|
||||||
|
import sys
|
||||||
|
print(Path(sys.argv[1]).resolve())
|
||||||
|
PY
|
||||||
|
)"
|
||||||
|
|
||||||
|
SUMMARY_JSON="$SEQUENCE_ROOT/sequence-summary.json"
|
||||||
|
SUMMARY_MD="$SEQUENCE_ROOT/sequence-summary.md"
|
||||||
|
DETAIL_JSON="$SEQUENCE_ROOT/sequence-detail.json"
|
||||||
|
|
||||||
|
python3 - <<'PY' "$SEQUENCE_ROOT" "$SUMMARY_JSON" "$SUMMARY_MD" "$DETAIL_JSON" "$STEP_SCRIPT" "$RPKI_BIN" "$REAL_RSYNC_BIN"
|
||||||
|
import json
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
sequence_root = Path(sys.argv[1])
|
||||||
|
summary_json = Path(sys.argv[2])
|
||||||
|
summary_md = Path(sys.argv[3])
|
||||||
|
detail_json = Path(sys.argv[4])
|
||||||
|
step_script = Path(sys.argv[5])
|
||||||
|
rpki_bin = sys.argv[6]
|
||||||
|
real_rsync_bin = sys.argv[7]
|
||||||
|
|
||||||
|
sequence = json.loads((sequence_root / "sequence.json").read_text(encoding="utf-8"))
|
||||||
|
repo_bytes_db = sequence_root / sequence["repoBytesDbPath"]
|
||||||
|
steps = sequence["steps"]
|
||||||
|
|
||||||
|
results = []
|
||||||
|
all_match = True
|
||||||
|
for step in steps:
|
||||||
|
step_id = step["stepId"]
|
||||||
|
out_dir = sequence_root / "replay-ours" / step_id
|
||||||
|
out_dir.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
cmd = [
|
||||||
|
str(step_script),
|
||||||
|
"--cir",
|
||||||
|
str(sequence_root / step["cirPath"]),
|
||||||
|
"--out-dir",
|
||||||
|
str(out_dir),
|
||||||
|
"--reference-ccr",
|
||||||
|
str(sequence_root / step["ccrPath"]),
|
||||||
|
"--rpki-bin",
|
||||||
|
rpki_bin,
|
||||||
|
"--real-rsync-bin",
|
||||||
|
real_rsync_bin,
|
||||||
|
]
|
||||||
|
cmd.extend(["--repo-bytes-db", str(repo_bytes_db)])
|
||||||
|
proc = subprocess.run(cmd, capture_output=True, text=True)
|
||||||
|
if proc.returncode != 0:
|
||||||
|
raise SystemExit(
|
||||||
|
f"ours sequence replay failed for {step_id}: stdout={proc.stdout} stderr={proc.stderr}"
|
||||||
|
)
|
||||||
|
compare = json.loads((out_dir / "compare-summary.json").read_text(encoding="utf-8"))
|
||||||
|
timing = json.loads((out_dir / "timing.json").read_text(encoding="utf-8")) if (out_dir / "timing.json").exists() else {}
|
||||||
|
record = {
|
||||||
|
"stepId": step_id,
|
||||||
|
"kind": step["kind"],
|
||||||
|
"validationTime": step["validationTime"],
|
||||||
|
"outDir": str(out_dir),
|
||||||
|
"comparePath": str(out_dir / "compare-summary.json"),
|
||||||
|
"timingPath": str(out_dir / "timing.json"),
|
||||||
|
"compareMode": compare.get("compareMode"),
|
||||||
|
"talCount": compare.get("talCount"),
|
||||||
|
"talPaths": compare.get("talPaths", []),
|
||||||
|
"compare": compare,
|
||||||
|
"timing": timing,
|
||||||
|
"match": bool(compare["vrps"]["match"]) and bool(compare["vaps"]["match"]),
|
||||||
|
}
|
||||||
|
all_match = all_match and record["match"]
|
||||||
|
results.append(record)
|
||||||
|
|
||||||
|
summary = {
|
||||||
|
"version": 1,
|
||||||
|
"participant": "ours",
|
||||||
|
"sequenceRoot": str(sequence_root),
|
||||||
|
"stepCount": len(results),
|
||||||
|
"allMatch": all_match,
|
||||||
|
"steps": results,
|
||||||
|
}
|
||||||
|
summary_json.write_text(json.dumps(summary, indent=2), encoding="utf-8")
|
||||||
|
detail_json.write_text(json.dumps(results, indent=2), encoding="utf-8")
|
||||||
|
|
||||||
|
lines = [
|
||||||
|
"# Ours CIR Sequence Replay Summary",
|
||||||
|
"",
|
||||||
|
f"- `sequence_root`: `{sequence_root}`",
|
||||||
|
f"- `step_count`: `{len(results)}`",
|
||||||
|
f"- `all_match`: `{all_match}`",
|
||||||
|
"",
|
||||||
|
"| Step | Kind | TALs | Compare mode | VRP actual/ref | VRP match | VAP actual/ref | VAP match | Duration (ms) |",
|
||||||
|
"| --- | --- | ---: | --- | --- | --- | --- | --- | ---: |",
|
||||||
|
]
|
||||||
|
for item in results:
|
||||||
|
compare = item["compare"]
|
||||||
|
timing = item.get("timing") or {}
|
||||||
|
lines.append(
|
||||||
|
"| {step} | {kind} | {tal_count} | {compare_mode} | {va}/{vr} | {vm} | {aa}/{ar} | {am} | {dur} |".format(
|
||||||
|
step=item["stepId"],
|
||||||
|
kind=item["kind"],
|
||||||
|
tal_count=item.get("talCount") if item.get("talCount") is not None else "-",
|
||||||
|
compare_mode=item.get("compareMode") or "-",
|
||||||
|
va=compare["vrps"]["actual"],
|
||||||
|
vr=compare["vrps"]["reference"],
|
||||||
|
vm=compare["vrps"]["match"],
|
||||||
|
aa=compare["vaps"]["actual"],
|
||||||
|
ar=compare["vaps"]["reference"],
|
||||||
|
am=compare["vaps"]["match"],
|
||||||
|
dur=timing.get("durationMs", "-"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
summary_md.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
||||||
|
PY
|
||||||
|
|
||||||
|
echo "done: $SEQUENCE_ROOT"
|
||||||
146
scripts/cir/run_cir_replay_sequence_routinator.sh
Executable file
146
scripts/cir/run_cir_replay_sequence_routinator.sh
Executable file
@@ -0,0 +1,146 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage:
|
||||||
|
./scripts/cir/run_cir_replay_sequence_routinator.sh \
|
||||||
|
--sequence-root <path> \
|
||||||
|
[--routinator-root <path>] \
|
||||||
|
[--routinator-bin <path>] \
|
||||||
|
[--real-rsync-bin <path>]
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
|
SEQUENCE_ROOT=""
|
||||||
|
ROUTINATOR_ROOT="${ROUTINATOR_ROOT:-/home/yuyr/dev/rust_playground/routinator}"
|
||||||
|
ROUTINATOR_BIN="${ROUTINATOR_BIN:-$ROUTINATOR_ROOT/target/debug/routinator}"
|
||||||
|
REAL_RSYNC_BIN="${REAL_RSYNC_BIN:-/usr/bin/rsync}"
|
||||||
|
STEP_SCRIPT="$ROOT_DIR/scripts/cir/run_cir_replay_routinator.sh"
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--sequence-root) SEQUENCE_ROOT="$2"; shift 2 ;;
|
||||||
|
--routinator-root) ROUTINATOR_ROOT="$2"; shift 2 ;;
|
||||||
|
--routinator-bin) ROUTINATOR_BIN="$2"; shift 2 ;;
|
||||||
|
--real-rsync-bin) REAL_RSYNC_BIN="$2"; shift 2 ;;
|
||||||
|
-h|--help) usage; exit 0 ;;
|
||||||
|
*) echo "unknown argument: $1" >&2; usage; exit 2 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
[[ -n "$SEQUENCE_ROOT" ]] || { usage >&2; exit 2; }
|
||||||
|
|
||||||
|
SEQUENCE_ROOT="$(python3 - <<'PY' "$SEQUENCE_ROOT"
|
||||||
|
from pathlib import Path
|
||||||
|
import sys
|
||||||
|
print(Path(sys.argv[1]).resolve())
|
||||||
|
PY
|
||||||
|
)"
|
||||||
|
|
||||||
|
SUMMARY_JSON="$SEQUENCE_ROOT/sequence-summary-routinator.json"
|
||||||
|
SUMMARY_MD="$SEQUENCE_ROOT/sequence-summary-routinator.md"
|
||||||
|
|
||||||
|
python3 - <<'PY' "$SEQUENCE_ROOT" "$SUMMARY_JSON" "$SUMMARY_MD" "$STEP_SCRIPT" "$ROUTINATOR_ROOT" "$ROUTINATOR_BIN" "$REAL_RSYNC_BIN"
|
||||||
|
import json
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
sequence_root = Path(sys.argv[1])
|
||||||
|
summary_json = Path(sys.argv[2])
|
||||||
|
summary_md = Path(sys.argv[3])
|
||||||
|
step_script = Path(sys.argv[4])
|
||||||
|
routinator_root = sys.argv[5]
|
||||||
|
routinator_bin = sys.argv[6]
|
||||||
|
real_rsync_bin = sys.argv[7]
|
||||||
|
|
||||||
|
sequence = json.loads((sequence_root / "sequence.json").read_text(encoding="utf-8"))
|
||||||
|
repo_bytes_db = sequence_root / sequence["repoBytesDbPath"]
|
||||||
|
steps = sequence["steps"]
|
||||||
|
results = []
|
||||||
|
all_match = True
|
||||||
|
|
||||||
|
for step in steps:
|
||||||
|
step_id = step["stepId"]
|
||||||
|
out_dir = sequence_root / "replay-routinator" / step_id
|
||||||
|
out_dir.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
cmd = [
|
||||||
|
str(step_script),
|
||||||
|
"--cir",
|
||||||
|
str(sequence_root / step["cirPath"]),
|
||||||
|
"--out-dir",
|
||||||
|
str(out_dir),
|
||||||
|
"--reference-ccr",
|
||||||
|
str(sequence_root / step["ccrPath"]),
|
||||||
|
"--routinator-root",
|
||||||
|
routinator_root,
|
||||||
|
"--routinator-bin",
|
||||||
|
routinator_bin,
|
||||||
|
"--real-rsync-bin",
|
||||||
|
real_rsync_bin,
|
||||||
|
]
|
||||||
|
cmd.extend(["--repo-bytes-db", str(repo_bytes_db)])
|
||||||
|
proc = subprocess.run(cmd, capture_output=True, text=True)
|
||||||
|
if proc.returncode != 0:
|
||||||
|
raise SystemExit(
|
||||||
|
f"routinator sequence replay failed for {step_id}: stdout={proc.stdout} stderr={proc.stderr}"
|
||||||
|
)
|
||||||
|
compare = json.loads((out_dir / "compare-summary.json").read_text(encoding="utf-8"))
|
||||||
|
match = bool(compare["vrps"]["match"]) and bool(compare["vaps"]["match"])
|
||||||
|
all_match = all_match and match
|
||||||
|
results.append(
|
||||||
|
{
|
||||||
|
"stepId": step_id,
|
||||||
|
"kind": step["kind"],
|
||||||
|
"validationTime": step["validationTime"],
|
||||||
|
"outDir": str(out_dir),
|
||||||
|
"comparePath": str(out_dir / "compare-summary.json"),
|
||||||
|
"compareMode": compare.get("compareMode"),
|
||||||
|
"talCount": compare.get("talCount"),
|
||||||
|
"talPaths": compare.get("talPaths", []),
|
||||||
|
"match": match,
|
||||||
|
"compare": compare,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
summary = {
|
||||||
|
"version": 1,
|
||||||
|
"participant": "routinator",
|
||||||
|
"sequenceRoot": str(sequence_root),
|
||||||
|
"stepCount": len(results),
|
||||||
|
"allMatch": all_match,
|
||||||
|
"steps": results,
|
||||||
|
}
|
||||||
|
summary_json.write_text(json.dumps(summary, indent=2), encoding="utf-8")
|
||||||
|
lines = [
|
||||||
|
"# Routinator CIR Sequence Replay Summary",
|
||||||
|
"",
|
||||||
|
f"- `sequence_root`: `{sequence_root}`",
|
||||||
|
f"- `step_count`: `{len(results)}`",
|
||||||
|
f"- `all_match`: `{all_match}`",
|
||||||
|
"",
|
||||||
|
"| Step | Kind | TALs | Compare mode | VRP actual/ref | VRP match | VAP actual/ref | VAP match |",
|
||||||
|
"| --- | --- | ---: | --- | --- | --- | --- | --- |",
|
||||||
|
]
|
||||||
|
for item in results:
|
||||||
|
compare = item["compare"]
|
||||||
|
lines.append(
|
||||||
|
"| {step} | {kind} | {tal_count} | {compare_mode} | {va}/{vr} | {vm} | {aa}/{ar} | {am} |".format(
|
||||||
|
step=item["stepId"],
|
||||||
|
kind=item["kind"],
|
||||||
|
tal_count=item.get("talCount") if item.get("talCount") is not None else "-",
|
||||||
|
compare_mode=item.get("compareMode") or "-",
|
||||||
|
va=compare["vrps"]["actual"],
|
||||||
|
vr=compare["vrps"]["reference"],
|
||||||
|
vm=compare["vrps"]["match"],
|
||||||
|
aa=compare["vaps"]["actual"],
|
||||||
|
ar=compare["vaps"]["reference"],
|
||||||
|
am=compare["vaps"]["match"],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
summary_md.write_text("\n".join(lines), encoding="utf-8")
|
||||||
|
PY
|
||||||
|
|
||||||
|
echo "done: $SEQUENCE_ROOT"
|
||||||
145
scripts/cir/run_cir_replay_sequence_rpki_client.sh
Executable file
145
scripts/cir/run_cir_replay_sequence_rpki_client.sh
Executable file
@@ -0,0 +1,145 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage:
|
||||||
|
./scripts/cir/run_cir_replay_sequence_rpki_client.sh \
|
||||||
|
--sequence-root <path> \
|
||||||
|
[--build-dir <path> | --rpki-client-bin <path>] \
|
||||||
|
[--real-rsync-bin <path>]
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
|
SEQUENCE_ROOT=""
|
||||||
|
BUILD_DIR=""
|
||||||
|
RPKI_CLIENT_BIN=""
|
||||||
|
REAL_RSYNC_BIN="${REAL_RSYNC_BIN:-/usr/bin/rsync}"
|
||||||
|
STEP_SCRIPT="$ROOT_DIR/scripts/cir/run_cir_replay_rpki_client.sh"
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--sequence-root) SEQUENCE_ROOT="$2"; shift 2 ;;
|
||||||
|
--build-dir) BUILD_DIR="$2"; shift 2 ;;
|
||||||
|
--rpki-client-bin) RPKI_CLIENT_BIN="$2"; shift 2 ;;
|
||||||
|
--real-rsync-bin) REAL_RSYNC_BIN="$2"; shift 2 ;;
|
||||||
|
-h|--help) usage; exit 0 ;;
|
||||||
|
*) echo "unknown argument: $1" >&2; usage; exit 2 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
[[ -n "$SEQUENCE_ROOT" && ( -n "$BUILD_DIR" || -n "$RPKI_CLIENT_BIN" ) ]] || { usage >&2; exit 2; }
|
||||||
|
|
||||||
|
SEQUENCE_ROOT="$(python3 - <<'PY' "$SEQUENCE_ROOT"
|
||||||
|
from pathlib import Path
|
||||||
|
import sys
|
||||||
|
print(Path(sys.argv[1]).resolve())
|
||||||
|
PY
|
||||||
|
)"
|
||||||
|
|
||||||
|
SUMMARY_JSON="$SEQUENCE_ROOT/sequence-summary-rpki-client.json"
|
||||||
|
SUMMARY_MD="$SEQUENCE_ROOT/sequence-summary-rpki-client.md"
|
||||||
|
|
||||||
|
python3 - <<'PY' "$SEQUENCE_ROOT" "$SUMMARY_JSON" "$SUMMARY_MD" "$STEP_SCRIPT" "$BUILD_DIR" "$RPKI_CLIENT_BIN" "$REAL_RSYNC_BIN"
|
||||||
|
import json
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
sequence_root = Path(sys.argv[1])
|
||||||
|
summary_json = Path(sys.argv[2])
|
||||||
|
summary_md = Path(sys.argv[3])
|
||||||
|
step_script = Path(sys.argv[4])
|
||||||
|
build_dir = sys.argv[5]
|
||||||
|
rpki_client_bin = sys.argv[6]
|
||||||
|
real_rsync_bin = sys.argv[7]
|
||||||
|
|
||||||
|
sequence = json.loads((sequence_root / "sequence.json").read_text(encoding="utf-8"))
|
||||||
|
repo_bytes_db = sequence_root / sequence["repoBytesDbPath"]
|
||||||
|
steps = sequence["steps"]
|
||||||
|
results = []
|
||||||
|
all_match = True
|
||||||
|
|
||||||
|
for step in steps:
|
||||||
|
step_id = step["stepId"]
|
||||||
|
out_dir = sequence_root / "replay-rpki-client" / step_id
|
||||||
|
out_dir.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
cmd = [
|
||||||
|
str(step_script),
|
||||||
|
"--cir",
|
||||||
|
str(sequence_root / step["cirPath"]),
|
||||||
|
"--out-dir",
|
||||||
|
str(out_dir),
|
||||||
|
"--reference-ccr",
|
||||||
|
str(sequence_root / step["ccrPath"]),
|
||||||
|
"--real-rsync-bin",
|
||||||
|
real_rsync_bin,
|
||||||
|
]
|
||||||
|
if rpki_client_bin:
|
||||||
|
cmd.extend(["--rpki-client-bin", rpki_client_bin])
|
||||||
|
else:
|
||||||
|
cmd.extend(["--build-dir", build_dir])
|
||||||
|
cmd.extend(["--repo-bytes-db", str(repo_bytes_db)])
|
||||||
|
proc = subprocess.run(cmd, capture_output=True, text=True)
|
||||||
|
if proc.returncode != 0:
|
||||||
|
raise SystemExit(
|
||||||
|
f"rpki-client sequence replay failed for {step_id}: stdout={proc.stdout} stderr={proc.stderr}"
|
||||||
|
)
|
||||||
|
compare = json.loads((out_dir / "compare-summary.json").read_text(encoding="utf-8"))
|
||||||
|
match = bool(compare["vrps"]["match"]) and bool(compare["vaps"]["match"])
|
||||||
|
all_match = all_match and match
|
||||||
|
results.append(
|
||||||
|
{
|
||||||
|
"stepId": step_id,
|
||||||
|
"kind": step["kind"],
|
||||||
|
"validationTime": step["validationTime"],
|
||||||
|
"outDir": str(out_dir),
|
||||||
|
"comparePath": str(out_dir / "compare-summary.json"),
|
||||||
|
"compareMode": compare.get("compareMode"),
|
||||||
|
"talCount": compare.get("talCount"),
|
||||||
|
"talPaths": compare.get("talPaths", []),
|
||||||
|
"match": match,
|
||||||
|
"compare": compare,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
summary = {
|
||||||
|
"version": 1,
|
||||||
|
"participant": "rpki-client",
|
||||||
|
"sequenceRoot": str(sequence_root),
|
||||||
|
"stepCount": len(results),
|
||||||
|
"allMatch": all_match,
|
||||||
|
"steps": results,
|
||||||
|
}
|
||||||
|
summary_json.write_text(json.dumps(summary, indent=2), encoding="utf-8")
|
||||||
|
lines = [
|
||||||
|
"# rpki-client CIR Sequence Replay Summary",
|
||||||
|
"",
|
||||||
|
f"- `sequence_root`: `{sequence_root}`",
|
||||||
|
f"- `step_count`: `{len(results)}`",
|
||||||
|
f"- `all_match`: `{all_match}`",
|
||||||
|
"",
|
||||||
|
"| Step | Kind | TALs | Compare mode | VRP actual/ref | VRP match | VAP actual/ref | VAP match |",
|
||||||
|
"| --- | --- | ---: | --- | --- | --- | --- | --- |",
|
||||||
|
]
|
||||||
|
for item in results:
|
||||||
|
compare = item["compare"]
|
||||||
|
lines.append(
|
||||||
|
"| {step} | {kind} | {tal_count} | {compare_mode} | {va}/{vr} | {vm} | {aa}/{ar} | {am} |".format(
|
||||||
|
step=item["stepId"],
|
||||||
|
kind=item["kind"],
|
||||||
|
tal_count=item.get("talCount") if item.get("talCount") is not None else "-",
|
||||||
|
compare_mode=item.get("compareMode") or "-",
|
||||||
|
va=compare["vrps"]["actual"],
|
||||||
|
vr=compare["vrps"]["reference"],
|
||||||
|
vm=compare["vrps"]["match"],
|
||||||
|
aa=compare["vaps"]["actual"],
|
||||||
|
ar=compare["vaps"]["reference"],
|
||||||
|
am=compare["vaps"]["match"],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
summary_md.write_text("\n".join(lines), encoding="utf-8")
|
||||||
|
PY
|
||||||
|
|
||||||
|
echo "done: $SEQUENCE_ROOT"
|
||||||
132
scripts/cir/run_cir_sequence_matrix_multi_rir.sh
Executable file
132
scripts/cir/run_cir_sequence_matrix_multi_rir.sh
Executable file
@@ -0,0 +1,132 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage:
|
||||||
|
./scripts/cir/run_cir_sequence_matrix_multi_rir.sh \
|
||||||
|
--root <path> \
|
||||||
|
[--rir <afrinic,apnic,arin,lacnic,ripe>] \
|
||||||
|
[--rpki-bin <path>] \
|
||||||
|
[--routinator-root <path>] \
|
||||||
|
[--routinator-bin <path>] \
|
||||||
|
[--rpki-client-build-dir <path>] \
|
||||||
|
[--drop-bin <path>]
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
|
ROOT=""
|
||||||
|
RIRS="afrinic,apnic,arin,lacnic,ripe"
|
||||||
|
RPKI_BIN="${RPKI_BIN:-$ROOT_DIR/target/release/rpki}"
|
||||||
|
ROUTINATOR_ROOT="${ROUTINATOR_ROOT:-/home/yuyr/dev/rust_playground/routinator}"
|
||||||
|
ROUTINATOR_BIN="${ROUTINATOR_BIN:-$ROUTINATOR_ROOT/target/debug/routinator}"
|
||||||
|
RPKI_CLIENT_BUILD_DIR="${RPKI_CLIENT_BUILD_DIR:-/home/yuyr/dev/rpki-client-9.7/build-m5}"
|
||||||
|
DROP_BIN="${DROP_BIN:-$ROOT_DIR/target/release/cir_drop_report}"
|
||||||
|
|
||||||
|
OURS_SCRIPT="$ROOT_DIR/scripts/cir/run_cir_replay_sequence_ours.sh"
|
||||||
|
ROUTINATOR_SCRIPT="$ROOT_DIR/scripts/cir/run_cir_replay_sequence_routinator.sh"
|
||||||
|
RPKIC_SCRIPT="$ROOT_DIR/scripts/cir/run_cir_replay_sequence_rpki_client.sh"
|
||||||
|
DROP_SCRIPT="$ROOT_DIR/scripts/cir/run_cir_drop_sequence.sh"
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--root) ROOT="$2"; shift 2 ;;
|
||||||
|
--rir) RIRS="$2"; shift 2 ;;
|
||||||
|
--rpki-bin) RPKI_BIN="$2"; shift 2 ;;
|
||||||
|
--routinator-root) ROUTINATOR_ROOT="$2"; shift 2 ;;
|
||||||
|
--routinator-bin) ROUTINATOR_BIN="$2"; shift 2 ;;
|
||||||
|
--rpki-client-build-dir) RPKI_CLIENT_BUILD_DIR="$2"; shift 2 ;;
|
||||||
|
--drop-bin) DROP_BIN="$2"; shift 2 ;;
|
||||||
|
-h|--help) usage; exit 0 ;;
|
||||||
|
*) echo "unknown argument: $1" >&2; usage; exit 2 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
[[ -n "$ROOT" ]] || { usage >&2; exit 2; }
|
||||||
|
|
||||||
|
SUMMARY_JSON="$ROOT/final-summary.json"
|
||||||
|
SUMMARY_MD="$ROOT/final-summary.md"
|
||||||
|
IFS=',' read -r -a ITEMS <<< "$RIRS"
|
||||||
|
|
||||||
|
results=()
|
||||||
|
for rir in "${ITEMS[@]}"; do
|
||||||
|
seq_root="$ROOT/$rir"
|
||||||
|
"$OURS_SCRIPT" --sequence-root "$seq_root" --rpki-bin "$RPKI_BIN"
|
||||||
|
"$ROUTINATOR_SCRIPT" --sequence-root "$seq_root" --routinator-root "$ROUTINATOR_ROOT" --routinator-bin "$ROUTINATOR_BIN"
|
||||||
|
"$RPKIC_SCRIPT" --sequence-root "$seq_root" --build-dir "$RPKI_CLIENT_BUILD_DIR"
|
||||||
|
"$DROP_SCRIPT" --sequence-root "$seq_root" --drop-bin "$DROP_BIN"
|
||||||
|
done
|
||||||
|
|
||||||
|
python3 - <<'PY' "$ROOT" "$RIRS" "$SUMMARY_JSON" "$SUMMARY_MD"
|
||||||
|
import json, sys
|
||||||
|
from pathlib import Path
|
||||||
|
from collections import Counter
|
||||||
|
|
||||||
|
root = Path(sys.argv[1]).resolve()
|
||||||
|
rirs = [item for item in sys.argv[2].split(',') if item]
|
||||||
|
summary_json = Path(sys.argv[3])
|
||||||
|
summary_md = Path(sys.argv[4])
|
||||||
|
items = []
|
||||||
|
total_steps = 0
|
||||||
|
total_dropped_vrps = 0
|
||||||
|
total_dropped_objects = 0
|
||||||
|
reason_counter = Counter()
|
||||||
|
for rir in rirs:
|
||||||
|
seq_root = root / rir
|
||||||
|
ours = json.loads((seq_root / "sequence-summary.json").read_text(encoding="utf-8"))
|
||||||
|
routinator = json.loads((seq_root / "sequence-summary-routinator.json").read_text(encoding="utf-8"))
|
||||||
|
rpki_client = json.loads((seq_root / "sequence-summary-rpki-client.json").read_text(encoding="utf-8"))
|
||||||
|
drop = json.loads((seq_root / "drop-summary.json").read_text(encoding="utf-8"))
|
||||||
|
step_count = len(ours["steps"])
|
||||||
|
total_steps += step_count
|
||||||
|
rir_dropped_vrps = 0
|
||||||
|
rir_dropped_objects = 0
|
||||||
|
for step in drop["steps"]:
|
||||||
|
drop_path = Path(step["reportPath"])
|
||||||
|
detail = json.loads(drop_path.read_text(encoding="utf-8"))
|
||||||
|
summary = detail.get("summary", {})
|
||||||
|
rir_dropped_vrps += int(summary.get("droppedVrpCount", 0))
|
||||||
|
rir_dropped_objects += int(summary.get("droppedObjectCount", 0))
|
||||||
|
total_dropped_vrps += int(summary.get("droppedVrpCount", 0))
|
||||||
|
total_dropped_objects += int(summary.get("droppedObjectCount", 0))
|
||||||
|
for reason, count in summary.get("droppedByReason", {}).items():
|
||||||
|
reason_counter[reason] += int(count)
|
||||||
|
items.append({
|
||||||
|
"rir": rir,
|
||||||
|
"stepCount": step_count,
|
||||||
|
"oursAllMatch": ours["allMatch"],
|
||||||
|
"routinatorAllMatch": routinator["allMatch"],
|
||||||
|
"rpkiClientAllMatch": rpki_client["allMatch"],
|
||||||
|
"dropSummary": drop["steps"],
|
||||||
|
"droppedVrpCount": rir_dropped_vrps,
|
||||||
|
"droppedObjectCount": rir_dropped_objects,
|
||||||
|
})
|
||||||
|
summary = {
|
||||||
|
"version": 1,
|
||||||
|
"totalStepCount": total_steps,
|
||||||
|
"totalDroppedVrpCount": total_dropped_vrps,
|
||||||
|
"totalDroppedObjectCount": total_dropped_objects,
|
||||||
|
"topReasons": [{"reason": reason, "count": count} for reason, count in reason_counter.most_common(10)],
|
||||||
|
"rirs": items,
|
||||||
|
}
|
||||||
|
summary_json.write_text(json.dumps(summary, indent=2), encoding="utf-8")
|
||||||
|
lines = ["# Multi-RIR CIR Sequence Matrix Summary", ""]
|
||||||
|
lines.append(f"- `total_step_count`: `{total_steps}`")
|
||||||
|
lines.append(f"- `total_dropped_vrps`: `{total_dropped_vrps}`")
|
||||||
|
lines.append(f"- `total_dropped_objects`: `{total_dropped_objects}`")
|
||||||
|
lines.append("")
|
||||||
|
if reason_counter:
|
||||||
|
lines.append("## Top Drop Reasons")
|
||||||
|
lines.append("")
|
||||||
|
for reason, count in reason_counter.most_common(10):
|
||||||
|
lines.append(f"- `{reason}`: `{count}`")
|
||||||
|
lines.append("")
|
||||||
|
for item in items:
|
||||||
|
lines.append(
|
||||||
|
f"- `{item['rir']}`: `steps={item['stepCount']}` `ours={item['oursAllMatch']}` `routinator={item['routinatorAllMatch']}` `rpki-client={item['rpkiClientAllMatch']}` `drop_vrps={item['droppedVrpCount']}` `drop_objects={item['droppedObjectCount']}`"
|
||||||
|
)
|
||||||
|
summary_md.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
||||||
|
PY
|
||||||
|
|
||||||
|
echo "done: $ROOT"
|
||||||
502
scripts/compare/run_perf_compare_quick_remote.sh
Executable file
502
scripts/compare/run_perf_compare_quick_remote.sh
Executable file
@@ -0,0 +1,502 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage:
|
||||||
|
./scripts/compare/run_perf_compare_quick_remote.sh \
|
||||||
|
--run-root <path> \
|
||||||
|
--remote-root <path> \
|
||||||
|
[--rir-set <mixed2|all5>] \
|
||||||
|
[--ssh-target <user@host>] \
|
||||||
|
[--rpki-client-bin <path>] \
|
||||||
|
[--libtls-path <path>] \
|
||||||
|
[--rp-run-mode <serial|parallel>] \
|
||||||
|
[--copy-rpki-client-cache] \
|
||||||
|
[--probe-rpki-client-cache] \
|
||||||
|
[--ours-extra-args '<args>'] \
|
||||||
|
[--dry-run]
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
|
|
||||||
|
first_existing_executable() {
|
||||||
|
local fallback="$1"
|
||||||
|
shift
|
||||||
|
local candidate
|
||||||
|
for candidate in "$@"; do
|
||||||
|
if [[ -x "$candidate" ]]; then
|
||||||
|
printf '%s' "$candidate"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
printf '%s' "$fallback"
|
||||||
|
}
|
||||||
|
|
||||||
|
first_existing_file() {
|
||||||
|
local fallback="$1"
|
||||||
|
shift
|
||||||
|
local candidate
|
||||||
|
for candidate in "$@"; do
|
||||||
|
if [[ -f "$candidate" ]]; then
|
||||||
|
printf '%s' "$candidate"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
printf '%s' "$fallback"
|
||||||
|
}
|
||||||
|
|
||||||
|
RUN_ROOT=""
|
||||||
|
REMOTE_ROOT=""
|
||||||
|
SSH_TARGET="${SSH_TARGET:-root@47.251.56.108}"
|
||||||
|
RPKI_CLIENT_BIN="${RPKI_CLIENT_BIN:-$(first_existing_executable \
|
||||||
|
"/home/yuyr/dev/rpki-client-9.7/build-m5/src/rpki-client" \
|
||||||
|
"$ROOT_DIR/../../.cache/rpki-client-9.7-build/bin/rpki-client" \
|
||||||
|
"$ROOT_DIR/../../.cache/rpki-client-9.7-build/src/rpki-client-9.7/src/rpki-client" \
|
||||||
|
"$ROOT_DIR/../../.cache/rpki-client-remote9.0/rpki-client" \
|
||||||
|
"/home/yuyr/dev/rpki-client-9.7/build-m5/src/rpki-client")}"
|
||||||
|
LIBTLS_PATH="${LIBTLS_PATH:-$(first_existing_file \
|
||||||
|
"/home/yuyr/dev/rpki-client-9.7/.deps/libtls/root/usr/lib/x86_64-linux-gnu/libtls.so.28.0.0" \
|
||||||
|
"$ROOT_DIR/../../.cache/rpki-client-9.7-build/runlib/libtls.so.28" \
|
||||||
|
"$ROOT_DIR/../../.cache/rpki-client-9.7-build/sysroot/usr/lib/x86_64-linux-gnu/libtls.so.28.0.0" \
|
||||||
|
"$ROOT_DIR/../../.cache/rpki-client-remote9.0/libtls.so.28" \
|
||||||
|
"/home/yuyr/dev/rpki-client-9.7/.deps/libtls/root/usr/lib/x86_64-linux-gnu/libtls.so.28.0.0")}"
|
||||||
|
RP_RUN_MODE="${RP_RUN_MODE:-serial}"
|
||||||
|
RIR_SET="${RIR_SET:-mixed2}"
|
||||||
|
OURS_EXTRA_ARGS="${OURS_EXTRA_ARGS:-}"
|
||||||
|
COPY_RPKI_CLIENT_CACHE="${COPY_RPKI_CLIENT_CACHE:-0}"
|
||||||
|
PROBE_RPKI_CLIENT_CACHE="${PROBE_RPKI_CLIENT_CACHE:-0}"
|
||||||
|
DRY_RUN=0
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--run-root) RUN_ROOT="$2"; shift 2 ;;
|
||||||
|
--remote-root) REMOTE_ROOT="$2"; shift 2 ;;
|
||||||
|
--rir-set) RIR_SET="$2"; shift 2 ;;
|
||||||
|
--ssh-target) SSH_TARGET="$2"; shift 2 ;;
|
||||||
|
--rpki-client-bin) RPKI_CLIENT_BIN="$2"; shift 2 ;;
|
||||||
|
--libtls-path) LIBTLS_PATH="$2"; shift 2 ;;
|
||||||
|
--rp-run-mode) RP_RUN_MODE="$2"; shift 2 ;;
|
||||||
|
--copy-rpki-client-cache) COPY_RPKI_CLIENT_CACHE=1; shift ;;
|
||||||
|
--probe-rpki-client-cache) PROBE_RPKI_CLIENT_CACHE=1; shift ;;
|
||||||
|
--ours-extra-args) OURS_EXTRA_ARGS="$2"; shift 2 ;;
|
||||||
|
--dry-run) DRY_RUN=1; shift ;;
|
||||||
|
-h|--help) usage; exit 0 ;;
|
||||||
|
*) echo "unknown argument: $1" >&2; usage; exit 2 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
[[ -n "$RUN_ROOT" && -n "$REMOTE_ROOT" ]] || { usage >&2; exit 2; }
|
||||||
|
[[ "$RP_RUN_MODE" == "serial" || "$RP_RUN_MODE" == "parallel" ]] || { echo "invalid --rp-run-mode: $RP_RUN_MODE" >&2; usage; exit 2; }
|
||||||
|
[[ "$RIR_SET" == "mixed2" || "$RIR_SET" == "all5" ]] || { echo "invalid --rir-set: $RIR_SET" >&2; usage; exit 2; }
|
||||||
|
[[ "$DRY_RUN" -eq 1 || -x "$RPKI_CLIENT_BIN" ]] || { echo "rpki-client binary not executable: $RPKI_CLIENT_BIN" >&2; exit 2; }
|
||||||
|
[[ "$DRY_RUN" -eq 1 || -f "$LIBTLS_PATH" ]] || { echo "libtls not found: $LIBTLS_PATH" >&2; exit 2; }
|
||||||
|
|
||||||
|
RUN_ROOT="$(python3 - <<'PY' "$RUN_ROOT"
|
||||||
|
from pathlib import Path
|
||||||
|
import sys
|
||||||
|
print(Path(sys.argv[1]).resolve())
|
||||||
|
PY
|
||||||
|
)"
|
||||||
|
|
||||||
|
mkdir -p "$RUN_ROOT/steps/step-001/ours" "$RUN_ROOT/steps/step-001/rpki-client" "$RUN_ROOT/steps/step-001/compare"
|
||||||
|
mkdir -p "$RUN_ROOT/steps/step-002/ours" "$RUN_ROOT/steps/step-002/rpki-client" "$RUN_ROOT/steps/step-002/compare"
|
||||||
|
|
||||||
|
tal_path_for_rir() {
|
||||||
|
case "$1" in
|
||||||
|
afrinic) printf '%s' "$ROOT_DIR/tests/fixtures/tal/afrinic.tal" ;;
|
||||||
|
apnic) printf '%s' "$ROOT_DIR/tests/fixtures/tal/apnic-rfc7730-https.tal" ;;
|
||||||
|
arin) printf '%s' "$ROOT_DIR/tests/fixtures/tal/arin.tal" ;;
|
||||||
|
lacnic) printf '%s' "$ROOT_DIR/tests/fixtures/tal/lacnic.tal" ;;
|
||||||
|
ripe) printf '%s' "$ROOT_DIR/tests/fixtures/tal/ripe-ncc.tal" ;;
|
||||||
|
*) echo "unknown rir: $1" >&2; exit 2 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
ta_path_for_rir() {
|
||||||
|
case "$1" in
|
||||||
|
afrinic) printf '%s' "$ROOT_DIR/tests/fixtures/ta/afrinic-ta.cer" ;;
|
||||||
|
apnic) printf '%s' "$ROOT_DIR/tests/fixtures/ta/apnic-ta.cer" ;;
|
||||||
|
arin) printf '%s' "$ROOT_DIR/tests/fixtures/ta/arin-ta.cer" ;;
|
||||||
|
lacnic) printf '%s' "$ROOT_DIR/tests/fixtures/ta/lacnic-ta.cer" ;;
|
||||||
|
ripe) printf '%s' "$ROOT_DIR/tests/fixtures/ta/ripe-ncc-ta.cer" ;;
|
||||||
|
*) echo "unknown rir: $1" >&2; exit 2 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
case "$RIR_SET" in
|
||||||
|
mixed2)
|
||||||
|
RIRS=(apnic arin)
|
||||||
|
SCOPE_LABEL="APNIC+ARIN mixed release two-step synchronized compare"
|
||||||
|
;;
|
||||||
|
all5)
|
||||||
|
RIRS=(afrinic apnic arin lacnic ripe)
|
||||||
|
SCOPE_LABEL="all-five-RIR mixed release two-step synchronized compare"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
COPY_FILES=()
|
||||||
|
for rir in "${RIRS[@]}"; do
|
||||||
|
COPY_FILES+=("$(tal_path_for_rir "$rir")" "$(ta_path_for_rir "$rir")")
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ "$DRY_RUN" -eq 1 ]]; then
|
||||||
|
cat <<EOF2
|
||||||
|
workflow_name=性能对比测试快速版
|
||||||
|
scope=$SCOPE_LABEL
|
||||||
|
rir_set=$RIR_SET
|
||||||
|
rirs=${RIRS[*]}
|
||||||
|
run_root=$RUN_ROOT
|
||||||
|
remote_root=$REMOTE_ROOT
|
||||||
|
ssh_target=$SSH_TARGET
|
||||||
|
rp_run_mode=$RP_RUN_MODE
|
||||||
|
ours_extra_args=$OURS_EXTRA_ARGS
|
||||||
|
EOF2
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
cleanup_remote() {
|
||||||
|
if [[ "${KEEP_REMOTE:-0}" != "1" ]]; then
|
||||||
|
ssh "$SSH_TARGET" "rm -rf '$REMOTE_ROOT'" >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
trap cleanup_remote EXIT
|
||||||
|
|
||||||
|
(
|
||||||
|
cd "$ROOT_DIR"
|
||||||
|
cargo build --release --bin rpki --bin ccr_to_compare_views --bin ccr_state_compare --bin cir_state_compare --bin cir_probe_rpki_client_cache
|
||||||
|
)
|
||||||
|
|
||||||
|
ssh "$SSH_TARGET" "set -e; systemctl disable --now rpki-client.timer >/dev/null 2>&1 || true; systemctl stop rpki-client.service >/dev/null 2>&1 || true; pkill -f '[/]rpki-client([[:space:]]|$)' >/dev/null 2>&1 || true; pkill -f '[/]routinator([[:space:]]|$)' >/dev/null 2>&1 || true; id -u _rpki-client >/dev/null 2>&1 || useradd -r -M -s /usr/sbin/nologin _rpki-client || true; rm -rf '$REMOTE_ROOT'; mkdir -p '$REMOTE_ROOT/bin' '$REMOTE_ROOT/lib' '$REMOTE_ROOT/state/ours' '$REMOTE_ROOT/state/rpki-client' '$REMOTE_ROOT/steps/step-001/ours' '$REMOTE_ROOT/steps/step-001/rpki-client' '$REMOTE_ROOT/steps/step-002/ours' '$REMOTE_ROOT/steps/step-002/rpki-client'"
|
||||||
|
scp "$ROOT_DIR/target/release/rpki" "${COPY_FILES[@]}" "$SSH_TARGET:$REMOTE_ROOT/"
|
||||||
|
if [[ "$PROBE_RPKI_CLIENT_CACHE" == "1" ]]; then
|
||||||
|
scp "$ROOT_DIR/target/release/cir_probe_rpki_client_cache" "$SSH_TARGET:$REMOTE_ROOT/bin/"
|
||||||
|
fi
|
||||||
|
scp "$RPKI_CLIENT_BIN" "$SSH_TARGET:$REMOTE_ROOT/bin/rpki-client"
|
||||||
|
scp "$LIBTLS_PATH" "$SSH_TARGET:$REMOTE_ROOT/lib/libtls.so.28"
|
||||||
|
printf '%s' "$OURS_EXTRA_ARGS" | ssh "$SSH_TARGET" "cat > '$REMOTE_ROOT/ours-extra-args.txt'"
|
||||||
|
printf '%s' "$RP_RUN_MODE" | ssh "$SSH_TARGET" "cat > '$REMOTE_ROOT/rp-run-mode.txt'"
|
||||||
|
printf '%s' "$RIR_SET" | ssh "$SSH_TARGET" "cat > '$REMOTE_ROOT/rir-set.txt'"
|
||||||
|
|
||||||
|
run_step() {
|
||||||
|
local step_id="$1"
|
||||||
|
local kind="$2"
|
||||||
|
local local_step="$RUN_ROOT/steps/$step_id"
|
||||||
|
|
||||||
|
ssh "$SSH_TARGET" bash -s -- "$REMOTE_ROOT" "$step_id" "$kind" <<'EOS'
|
||||||
|
set -euo pipefail
|
||||||
|
REMOTE_ROOT="$1"
|
||||||
|
STEP_ID="$2"
|
||||||
|
KIND="$3"
|
||||||
|
|
||||||
|
cd "$REMOTE_ROOT"
|
||||||
|
mkdir -p "steps/$STEP_ID/ours" "steps/$STEP_ID/rpki-client"
|
||||||
|
touch rpki-client-skiplist
|
||||||
|
chmod 0644 rpki-client-skiplist
|
||||||
|
OURS_EXTRA_ARGS="$(cat ours-extra-args.txt)"
|
||||||
|
RP_RUN_MODE="$(cat rp-run-mode.txt)"
|
||||||
|
RIR_SET="$(cat rir-set.txt)"
|
||||||
|
OURS_EXTRA_ARGV=()
|
||||||
|
if [[ -n "$OURS_EXTRA_ARGS" ]]; then
|
||||||
|
# shellcheck disable=SC2206
|
||||||
|
OURS_EXTRA_ARGV=($OURS_EXTRA_ARGS)
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$RIR_SET" in
|
||||||
|
mixed2) RIRS=(apnic arin) ;;
|
||||||
|
all5) RIRS=(afrinic apnic arin lacnic ripe) ;;
|
||||||
|
*) echo "invalid rir set: $RIR_SET" >&2; exit 2 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
tal_file_for_rir() {
|
||||||
|
case "$1" in
|
||||||
|
afrinic) printf '%s' "afrinic.tal" ;;
|
||||||
|
apnic) printf '%s' "apnic-rfc7730-https.tal" ;;
|
||||||
|
arin) printf '%s' "arin.tal" ;;
|
||||||
|
lacnic) printf '%s' "lacnic.tal" ;;
|
||||||
|
ripe) printf '%s' "ripe-ncc.tal" ;;
|
||||||
|
*) echo "unknown rir: $1" >&2; exit 2 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
tal_uri_for_rir() {
|
||||||
|
case "$1" in
|
||||||
|
afrinic) printf '%s' "https://rpki.afrinic.net/repository/AfriNIC.cer" ;;
|
||||||
|
apnic) printf '%s' "https://rpki.apnic.net/repository/apnic-rpki-root-iana-origin.cer" ;;
|
||||||
|
arin) printf '%s' "https://rrdp.arin.net/arin-rpki-ta.cer" ;;
|
||||||
|
lacnic) printf '%s' "https://rrdp.lacnic.net/ta/rta-lacnic-rpki.cer" ;;
|
||||||
|
ripe) printf '%s' "https://rpki.ripe.net/ta/ripe-ncc-ta.cer" ;;
|
||||||
|
*) echo "unknown rir: $1" >&2; exit 2 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
ta_file_for_rir() {
|
||||||
|
case "$1" in
|
||||||
|
afrinic) printf '%s' "afrinic-ta.cer" ;;
|
||||||
|
apnic) printf '%s' "apnic-ta.cer" ;;
|
||||||
|
arin) printf '%s' "arin-ta.cer" ;;
|
||||||
|
lacnic) printf '%s' "lacnic-ta.cer" ;;
|
||||||
|
ripe) printf '%s' "ripe-ncc-ta.cer" ;;
|
||||||
|
*) echo "unknown rir: $1" >&2; exit 2 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh_ta_file_for_rir() {
|
||||||
|
local rir="$1"
|
||||||
|
local uri
|
||||||
|
local file
|
||||||
|
uri="$(tal_uri_for_rir "$rir")"
|
||||||
|
file="$(ta_file_for_rir "$rir")"
|
||||||
|
python3 - <<'PY' "$uri" "$file"
|
||||||
|
import sys
|
||||||
|
import urllib.request
|
||||||
|
uri, path = sys.argv[1:]
|
||||||
|
request = urllib.request.Request(uri, headers={"User-Agent": "rpki-dev/compare-fast-path"})
|
||||||
|
with urllib.request.urlopen(request, timeout=30) as response:
|
||||||
|
data = response.read()
|
||||||
|
if not data:
|
||||||
|
raise SystemExit(f"empty TA certificate response: {uri}")
|
||||||
|
with open(path, "wb") as output:
|
||||||
|
output.write(data)
|
||||||
|
PY
|
||||||
|
}
|
||||||
|
|
||||||
|
for rir in "${RIRS[@]}"; do
|
||||||
|
refresh_ta_file_for_rir "$rir"
|
||||||
|
done
|
||||||
|
|
||||||
|
OURS_TAL_ARGS=()
|
||||||
|
CLIENT_TAL_ARGS=()
|
||||||
|
OURS_CIR_TAL_ARGS=()
|
||||||
|
for rir in "${RIRS[@]}"; do
|
||||||
|
tal_file="$(tal_file_for_rir "$rir")"
|
||||||
|
ta_file="$(ta_file_for_rir "$rir")"
|
||||||
|
tal_uri="$(tal_uri_for_rir "$rir")"
|
||||||
|
OURS_TAL_ARGS+=(--tal-path "$tal_file" --ta-path "$ta_file")
|
||||||
|
OURS_CIR_TAL_ARGS+=(--cir-tal-uri "$tal_uri")
|
||||||
|
CLIENT_TAL_ARGS+=(-t "../../$tal_file")
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ "$KIND" == "snapshot" ]]; then
|
||||||
|
rm -rf state/ours/work-db state/ours/raw-store.db state/ours/repo-bytes.db state/rpki-client/cache state/rpki-client/out state/rpki-client/ta state/rpki-client/.ta
|
||||||
|
fi
|
||||||
|
mkdir -p state/ours/work-db state/ours/raw-store.db state/ours/repo-bytes.db state/rpki-client/cache state/rpki-client/out state/rpki-client/ta state/rpki-client/.ta
|
||||||
|
chmod 0777 state/ours/work-db state/ours/raw-store.db state/ours/repo-bytes.db
|
||||||
|
chmod -R 0777 state/rpki-client
|
||||||
|
touch state/rpki-client/rpki-client-skiplist
|
||||||
|
chmod 0644 state/rpki-client/rpki-client-skiplist
|
||||||
|
|
||||||
|
START_EPOCH="$(python3 - <<'PY'
|
||||||
|
import time
|
||||||
|
print(time.time() + 3.0)
|
||||||
|
PY
|
||||||
|
)"
|
||||||
|
|
||||||
|
run_ours() {
|
||||||
|
python3 - <<'PY' "$START_EPOCH"
|
||||||
|
import sys, time
|
||||||
|
x = float(sys.argv[1])
|
||||||
|
d = x - time.time()
|
||||||
|
if d > 0:
|
||||||
|
time.sleep(d)
|
||||||
|
PY
|
||||||
|
started_ms="$(python3 - <<'PY'
|
||||||
|
import time
|
||||||
|
print(int(time.time() * 1000))
|
||||||
|
PY
|
||||||
|
)"
|
||||||
|
set +e
|
||||||
|
env RPKI_PROGRESS_LOG=1 RPKI_PROGRESS_SLOW_SECS=0 ./rpki \
|
||||||
|
--db state/ours/work-db \
|
||||||
|
--raw-store-db state/ours/raw-store.db \
|
||||||
|
--repo-bytes-db state/ours/repo-bytes.db \
|
||||||
|
"${OURS_TAL_ARGS[@]}" \
|
||||||
|
"${OURS_EXTRA_ARGV[@]}" \
|
||||||
|
--ccr-out "steps/$STEP_ID/ours/result.ccr" \
|
||||||
|
--cir-enable \
|
||||||
|
--cir-out "steps/$STEP_ID/ours/result.cir" \
|
||||||
|
"${OURS_CIR_TAL_ARGS[@]}" \
|
||||||
|
--report-json "steps/$STEP_ID/ours/report.json" \
|
||||||
|
> "steps/$STEP_ID/ours/run.log" 2>&1
|
||||||
|
exit_code=$?
|
||||||
|
set -e
|
||||||
|
finished_ms="$(python3 - <<'PY'
|
||||||
|
import time
|
||||||
|
print(int(time.time() * 1000))
|
||||||
|
PY
|
||||||
|
)"
|
||||||
|
python3 - <<'PY' "steps/$STEP_ID/ours/round-result.json" "$STEP_ID" "$KIND" "$started_ms" "$finished_ms" "$exit_code"
|
||||||
|
import json, sys
|
||||||
|
path, step_id, kind, started_ms, finished_ms, exit_code = sys.argv[1:]
|
||||||
|
json.dump(
|
||||||
|
{
|
||||||
|
"stepId": step_id,
|
||||||
|
"kind": kind,
|
||||||
|
"durationMs": int(finished_ms) - int(started_ms),
|
||||||
|
"exitCode": int(exit_code),
|
||||||
|
},
|
||||||
|
open(path, "w"),
|
||||||
|
indent=2,
|
||||||
|
)
|
||||||
|
PY
|
||||||
|
}
|
||||||
|
|
||||||
|
run_client() {
|
||||||
|
cd state/rpki-client
|
||||||
|
python3 - <<'PY' "$START_EPOCH"
|
||||||
|
import sys, time
|
||||||
|
x = float(sys.argv[1])
|
||||||
|
d = x - time.time()
|
||||||
|
if d > 0:
|
||||||
|
time.sleep(d)
|
||||||
|
PY
|
||||||
|
started_ms="$(python3 - <<'PY'
|
||||||
|
import time
|
||||||
|
print(int(time.time() * 1000))
|
||||||
|
PY
|
||||||
|
)"
|
||||||
|
set +e
|
||||||
|
LD_LIBRARY_PATH="$REMOTE_ROOT/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" "$REMOTE_ROOT/bin/rpki-client" \
|
||||||
|
-vv \
|
||||||
|
-S rpki-client-skiplist \
|
||||||
|
"${CLIENT_TAL_ARGS[@]}" \
|
||||||
|
-d cache out \
|
||||||
|
> "$REMOTE_ROOT/steps/$STEP_ID/rpki-client/run.log" 2>&1
|
||||||
|
exit_code=$?
|
||||||
|
set -e
|
||||||
|
cp out/rpki.ccr "$REMOTE_ROOT/steps/$STEP_ID/rpki-client/result.ccr" 2>/dev/null || true
|
||||||
|
cp out/rpki.cir "$REMOTE_ROOT/steps/$STEP_ID/rpki-client/result.cir" 2>/dev/null || true
|
||||||
|
cp out/openbgpd "$REMOTE_ROOT/steps/$STEP_ID/rpki-client/openbgpd" 2>/dev/null || true
|
||||||
|
finished_ms="$(python3 - <<'PY'
|
||||||
|
import time
|
||||||
|
print(int(time.time() * 1000))
|
||||||
|
PY
|
||||||
|
)"
|
||||||
|
python3 - <<'PY' "$REMOTE_ROOT/steps/$STEP_ID/rpki-client/round-result.json" "$STEP_ID" "$KIND" "$started_ms" "$finished_ms" "$exit_code"
|
||||||
|
import json, sys
|
||||||
|
path, step_id, kind, started_ms, finished_ms, exit_code = sys.argv[1:]
|
||||||
|
json.dump(
|
||||||
|
{
|
||||||
|
"stepId": step_id,
|
||||||
|
"kind": kind,
|
||||||
|
"durationMs": int(finished_ms) - int(started_ms),
|
||||||
|
"exitCode": int(exit_code),
|
||||||
|
},
|
||||||
|
open(path, "w"),
|
||||||
|
indent=2,
|
||||||
|
)
|
||||||
|
PY
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ "$RP_RUN_MODE" == "parallel" ]]; then
|
||||||
|
run_ours &
|
||||||
|
OURS_PID=$!
|
||||||
|
run_client &
|
||||||
|
CLIENT_PID=$!
|
||||||
|
wait "$OURS_PID"
|
||||||
|
wait "$CLIENT_PID"
|
||||||
|
else
|
||||||
|
run_ours
|
||||||
|
run_client
|
||||||
|
fi
|
||||||
|
EOS
|
||||||
|
|
||||||
|
for rel in result.ccr result.cir round-result.json run.log stage-timing.json; do
|
||||||
|
scp -C "$SSH_TARGET:$REMOTE_ROOT/steps/$step_id/ours/$rel" "$local_step/ours/"
|
||||||
|
done
|
||||||
|
for rel in result.ccr result.cir round-result.json run.log openbgpd; do
|
||||||
|
scp -C "$SSH_TARGET:$REMOTE_ROOT/steps/$step_id/rpki-client/$rel" "$local_step/rpki-client/" || true
|
||||||
|
done
|
||||||
|
if [[ "$COPY_RPKI_CLIENT_CACHE" == "1" ]]; then
|
||||||
|
mkdir -p "$local_step/rpki-client/cache"
|
||||||
|
rsync -a --delete "$SSH_TARGET:$REMOTE_ROOT/state/rpki-client/cache/" "$local_step/rpki-client/cache/"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -f "$local_step/ours/result.cir" && -f "$local_step/rpki-client/result.cir" ]]; then
|
||||||
|
"$ROOT_DIR/scripts/periodic/compare_ccr_cir_round.sh" \
|
||||||
|
--ours-ccr "$local_step/ours/result.ccr" \
|
||||||
|
--rpki-client-ccr "$local_step/rpki-client/result.ccr" \
|
||||||
|
--ours-cir "$local_step/ours/result.cir" \
|
||||||
|
--rpki-client-cir "$local_step/rpki-client/result.cir" \
|
||||||
|
--out-dir "$local_step/compare" \
|
||||||
|
--trust-anchor unknown >/dev/null
|
||||||
|
if [[ "$PROBE_RPKI_CLIENT_CACHE" == "1" ]]; then
|
||||||
|
ssh "$SSH_TARGET" "set -e; mkdir -p '$REMOTE_ROOT/steps/$step_id/compare/cir'; '$REMOTE_ROOT/bin/cir_probe_rpki_client_cache' --ours-cir '$REMOTE_ROOT/steps/$step_id/ours/result.cir' --rpki-client-cir '$REMOTE_ROOT/steps/$step_id/rpki-client/result.cir' --cache-root '$REMOTE_ROOT/state/rpki-client/cache' --rpki-client-log '$REMOTE_ROOT/steps/$step_id/rpki-client/run.log' --out-json '$REMOTE_ROOT/steps/$step_id/compare/cir/rpki-client-cache-probe.json' --sample-limit 50 >/dev/null"
|
||||||
|
scp -C "$SSH_TARGET:$REMOTE_ROOT/steps/$step_id/compare/cir/rpki-client-cache-probe.json" "$local_step/compare/cir/"
|
||||||
|
fi
|
||||||
|
if [[ "$COPY_RPKI_CLIENT_CACHE" == "1" ]]; then
|
||||||
|
"$ROOT_DIR/target/release/cir_probe_rpki_client_cache" \
|
||||||
|
--ours-cir "$local_step/ours/result.cir" \
|
||||||
|
--rpki-client-cir "$local_step/rpki-client/result.cir" \
|
||||||
|
--cache-root "$local_step/rpki-client/cache" \
|
||||||
|
--rpki-client-log "$local_step/rpki-client/run.log" \
|
||||||
|
--out-json "$local_step/compare/cir/rpki-client-cache-probe.json" \
|
||||||
|
--sample-limit 50 >/dev/null
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
"$ROOT_DIR/scripts/periodic/compare_ccr_round.sh" \
|
||||||
|
--ours-ccr "$local_step/ours/result.ccr" \
|
||||||
|
--rpki-client-ccr "$local_step/rpki-client/result.ccr" \
|
||||||
|
--out-dir "$local_step/compare" \
|
||||||
|
--trust-anchor unknown >/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
python3 - <<'PY' "$local_step/ours/round-result.json" "$local_step/rpki-client/round-result.json" "$local_step/ours/stage-timing.json" "$local_step/compare/summary.json" "$local_step/compare/compare-summary.json" "$local_step/step-summary.json" "$OURS_EXTRA_ARGS"
|
||||||
|
import json, sys
|
||||||
|
ours = json.load(open(sys.argv[1]))
|
||||||
|
client = json.load(open(sys.argv[2]))
|
||||||
|
stage = json.load(open(sys.argv[3]))
|
||||||
|
compare_path = sys.argv[4] if __import__('pathlib').Path(sys.argv[4]).exists() else sys.argv[5]
|
||||||
|
compare = json.load(open(compare_path))
|
||||||
|
ours_extra_args = sys.argv[7]
|
||||||
|
json.dump(
|
||||||
|
{
|
||||||
|
"stepId": ours["stepId"],
|
||||||
|
"kind": ours["kind"],
|
||||||
|
"oursExtraArgs": ours_extra_args,
|
||||||
|
"oursDurationMs": ours["durationMs"],
|
||||||
|
"rpkiClientDurationMs": client["durationMs"],
|
||||||
|
"oursExitCode": ours["exitCode"],
|
||||||
|
"rpkiClientExitCode": client["exitCode"],
|
||||||
|
"oursTotalMs": stage["total_ms"],
|
||||||
|
"oursRepoSyncMsTotal": stage["repo_sync_ms_total"],
|
||||||
|
"oursPublicationPointRepoSyncMsTotal": stage.get("publication_point_repo_sync_ms_total"),
|
||||||
|
"oursDownloadEventCount": stage.get("download_event_count"),
|
||||||
|
"oursRrdpDownloadMsTotal": stage.get("rrdp_download_ms_total"),
|
||||||
|
"oursRsyncDownloadMsTotal": stage.get("rsync_download_ms_total"),
|
||||||
|
"oursDownloadBytesTotal": stage.get("download_bytes_total"),
|
||||||
|
"oursVrps": compare["vrps"]["ours"],
|
||||||
|
"rpkiClientVrps": compare["vrps"]["rpkiClient"],
|
||||||
|
"oursVaps": compare["vaps"]["ours"],
|
||||||
|
"rpkiClientVaps": compare["vaps"]["rpkiClient"],
|
||||||
|
"vrpMatch": compare["vrps"]["match"],
|
||||||
|
"vapMatch": compare["vaps"]["match"],
|
||||||
|
"allMatch": compare["allMatch"],
|
||||||
|
"onlyInOurs": len(compare["vrps"]["onlyInOurs"]),
|
||||||
|
"onlyInRpkiClient": len(compare["vrps"]["onlyInRpkiClient"]),
|
||||||
|
},
|
||||||
|
open(sys.argv[6], "w"),
|
||||||
|
indent=2,
|
||||||
|
)
|
||||||
|
PY
|
||||||
|
}
|
||||||
|
|
||||||
|
run_step step-001 snapshot
|
||||||
|
run_step step-002 delta
|
||||||
|
|
||||||
|
python3 - <<'PY' "$RUN_ROOT/steps/step-001/step-summary.json" "$RUN_ROOT/steps/step-002/step-summary.json" "$RUN_ROOT/summary.json" "$RP_RUN_MODE" "$OURS_EXTRA_ARGS" "$RIR_SET" "$SCOPE_LABEL" "${RIRS[@]}"
|
||||||
|
import json, sys
|
||||||
|
steps = [json.load(open(p)) for p in sys.argv[1:3]]
|
||||||
|
summary = {
|
||||||
|
"workflowName": "性能对比测试快速版",
|
||||||
|
"scope": sys.argv[7],
|
||||||
|
"rpRunMode": sys.argv[4],
|
||||||
|
"oursExtraArgs": sys.argv[5],
|
||||||
|
"rirSet": sys.argv[6],
|
||||||
|
"rirs": sys.argv[8:],
|
||||||
|
"steps": steps,
|
||||||
|
}
|
||||||
|
json.dump(summary, open(sys.argv[3], "w"), indent=2, ensure_ascii=False)
|
||||||
|
print(json.dumps(summary, indent=2, ensure_ascii=False))
|
||||||
|
PY
|
||||||
1706
scripts/compare/run_three_rp_10run_benchmark.py
Executable file
1706
scripts/compare/run_three_rp_10run_benchmark.py
Executable file
File diff suppressed because it is too large
Load Diff
104
scripts/coverage.sh
Executable file
104
scripts/coverage.sh
Executable file
@@ -0,0 +1,104 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Requires:
|
||||||
|
# rustup component add llvm-tools-preview
|
||||||
|
# cargo install cargo-llvm-cov --locked
|
||||||
|
|
||||||
|
# Optional:
|
||||||
|
# COVERAGE_FORCE_CLEAN=1 Force `cargo llvm-cov clean --workspace` before the run.
|
||||||
|
# Default behavior is to reuse existing llvm-cov build artifacts.
|
||||||
|
# RPKI_SKIP_HEAVY_SCRIPT_REPLAY_TESTS=1 Skip replay/matrix integration tests that
|
||||||
|
# spawn shell pipelines and can trigger separate release builds.
|
||||||
|
# coverage.sh enables this by default.
|
||||||
|
# RPKI_SKIP_HEAVY_BLACKBOX_TESTS=1 Skip slower blackbox CLI/script integration tests
|
||||||
|
# that provide low incremental coverage per wall-clock second.
|
||||||
|
# coverage.sh enables this by default.
|
||||||
|
# RPKI_SKIP_HEAVY_CRYPTO_TESTS=1 Skip slower OpenSSL-heavy certificate generation tests
|
||||||
|
# that provide low incremental coverage per wall-clock second.
|
||||||
|
# coverage.sh enables this by default.
|
||||||
|
|
||||||
|
run_out="$(mktemp)"
|
||||||
|
text_out="$(mktemp)"
|
||||||
|
html_out="$(mktemp)"
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
rm -f "$run_out" "$text_out" "$html_out"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
IGNORE_REGEX='repository_view_stats\.rs|db_stats\.rs|rrdp_state_dump\.rs|ccr_dump\.rs|ccr_verify\.rs|ccr_to_routinator_csv\.rs|ccr_to_compare_views\.rs|cir_materialize\.rs|cir_extract_inputs\.rs|cir_drop_report\.rs|cir_ta_only_fixture\.rs|cir_dump_reject_list\.rs|rpki_object_parse\.rs|rpki_query_indexer\.rs|rpki_query_service\.rs|triage_ccr_cir_pair\.rs|rpki_artifact_metrics|rpki_inter_rp_metrics|rpki_daemon\.rs|sequence_triage_ccr_cir|ccr_state_compare\.rs|cir_state_compare\.rs|cir_probe_rpki_client_cache\.rs|ccr/compare_view\.rs|progress_log\.rs|cli\.rs|validation/run_tree_from_tal\.rs|validation/tree_parallel\.rs|validation/tree_runner|validation/from_tal\.rs|sync/store_projection\.rs|sync/repo\.rs|sync/rrdp|(^|/)storage(/|\.rs$)|cir/materialize\.rs'
|
||||||
|
|
||||||
|
# Preserve colored output even though we post-process output by running under a pseudo-TTY.
|
||||||
|
# We run tests only once, then generate both CLI text + HTML reports without rerunning tests.
|
||||||
|
set +e
|
||||||
|
|
||||||
|
if [ "${COVERAGE_FORCE_CLEAN:-0}" = "1" ]; then
|
||||||
|
cargo llvm-cov clean --workspace >/dev/null 2>&1
|
||||||
|
echo "coverage mode: clean build (COVERAGE_FORCE_CLEAN=1)"
|
||||||
|
else
|
||||||
|
echo "coverage mode: reuse existing llvm-cov artifacts (default)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
export RPKI_SKIP_HEAVY_SCRIPT_REPLAY_TESTS="${RPKI_SKIP_HEAVY_SCRIPT_REPLAY_TESTS:-1}"
|
||||||
|
export RPKI_SKIP_HEAVY_BLACKBOX_TESTS="${RPKI_SKIP_HEAVY_BLACKBOX_TESTS:-1}"
|
||||||
|
export RPKI_SKIP_HEAVY_CRYPTO_TESTS="${RPKI_SKIP_HEAVY_CRYPTO_TESTS:-1}"
|
||||||
|
|
||||||
|
# 1) Run tests once to collect coverage data (no report).
|
||||||
|
script -q -e -c "CARGO_TERM_COLOR=always cargo llvm-cov --no-report" "$run_out" >/dev/null 2>&1
|
||||||
|
run_status="$?"
|
||||||
|
|
||||||
|
# 2) CLI summary report + fail-under gate (no test rerun).
|
||||||
|
script -q -e -c "CARGO_TERM_COLOR=always cargo llvm-cov report --fail-under-lines 90 --ignore-filename-regex '$IGNORE_REGEX'" "$text_out" >/dev/null 2>&1
|
||||||
|
text_status="$?"
|
||||||
|
|
||||||
|
# 3) HTML report (no test rerun).
|
||||||
|
script -q -e -c "CARGO_TERM_COLOR=always cargo llvm-cov report --html --ignore-filename-regex '$IGNORE_REGEX'" "$html_out" >/dev/null 2>&1
|
||||||
|
html_status="$?"
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
strip_script_noise() {
|
||||||
|
tr -d '\r' | sed '/^Script \(started\|done\) on /d'
|
||||||
|
}
|
||||||
|
|
||||||
|
strip_ansi_for_parse() {
|
||||||
|
awk '
|
||||||
|
{
|
||||||
|
line = $0
|
||||||
|
gsub(/\033\[[0-9;]*[A-Za-z]/, "", line) # CSI escapes
|
||||||
|
gsub(/\033\([A-Za-z]/, "", line) # charset escapes (e.g., ESC(B)
|
||||||
|
gsub(/\r/, "", line)
|
||||||
|
print line
|
||||||
|
}
|
||||||
|
'
|
||||||
|
}
|
||||||
|
|
||||||
|
cat "$run_out" | strip_script_noise
|
||||||
|
cat "$text_out" | strip_script_noise
|
||||||
|
cat "$html_out" | strip_script_noise
|
||||||
|
|
||||||
|
cat "$run_out" | strip_ansi_for_parse | awk '
|
||||||
|
BEGIN {
|
||||||
|
passed=0; failed=0; ignored=0; measured=0; filtered=0;
|
||||||
|
}
|
||||||
|
/^test result: / {
|
||||||
|
if (match($0, /([0-9]+) passed; ([0-9]+) failed; ([0-9]+) ignored; ([0-9]+) measured; ([0-9]+) filtered out;/, m)) {
|
||||||
|
passed += m[1]; failed += m[2]; ignored += m[3]; measured += m[4]; filtered += m[5];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
END {
|
||||||
|
executed = passed + failed;
|
||||||
|
total = passed + failed + ignored + measured;
|
||||||
|
printf("\nTEST SUMMARY (all suites): passed=%d failed=%d ignored=%d measured=%d filtered_out=%d executed=%d total=%d\n",
|
||||||
|
passed, failed, ignored, measured, filtered, executed, total);
|
||||||
|
}
|
||||||
|
'
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "HTML report: target/llvm-cov/html/index.html"
|
||||||
|
|
||||||
|
status="$text_status"
|
||||||
|
if [ "$run_status" -ne 0 ]; then status="$run_status"; fi
|
||||||
|
if [ "$html_status" -ne 0 ]; then status="$html_status"; fi
|
||||||
|
exit "$status"
|
||||||
5
scripts/docker/build_arm64_installer_package.sh
Executable file
5
scripts/docker/build_arm64_installer_package.sh
Executable file
@@ -0,0 +1,5 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
exec "$SCRIPT_DIR/build_docker_installer_package.sh" --arch arm64 "$@"
|
||||||
5
scripts/docker/build_arm64_metrics_image.sh
Executable file
5
scripts/docker/build_arm64_metrics_image.sh
Executable file
@@ -0,0 +1,5 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
exec "$SCRIPT_DIR/build_docker_metrics_image.sh" --arch arm64 "$@"
|
||||||
5
scripts/docker/build_arm64_runtime_image.sh
Executable file
5
scripts/docker/build_arm64_runtime_image.sh
Executable file
@@ -0,0 +1,5 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
exec "$SCRIPT_DIR/build_docker_runtime_image.sh" --arch arm64 "$@"
|
||||||
438
scripts/docker/build_docker_installer_package.sh
Executable file
438
scripts/docker/build_docker_installer_package.sh
Executable file
@@ -0,0 +1,438 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||||
|
|
||||||
|
TARGET_ARCH="${TARGET_ARCH:-}"
|
||||||
|
IMAGE_TAG="${IMAGE_TAG:-}"
|
||||||
|
IMAGE_TAR="${IMAGE_TAR:-}"
|
||||||
|
METRICS_IMAGE="${METRICS_IMAGE:-}"
|
||||||
|
METRICS_IMAGE_TAR="${METRICS_IMAGE_TAR:-}"
|
||||||
|
PROMETHEUS_IMAGE="${PROMETHEUS_IMAGE:-prom/prometheus:v2.55.1}"
|
||||||
|
PROMETHEUS_IMAGE_TAR="${PROMETHEUS_IMAGE_TAR:-}"
|
||||||
|
GRAFANA_IMAGE="${GRAFANA_IMAGE:-grafana/grafana:11.3.1}"
|
||||||
|
GRAFANA_IMAGE_TAR="${GRAFANA_IMAGE_TAR:-}"
|
||||||
|
OUT_DIR="${OUT_DIR:-}"
|
||||||
|
PACKAGE_PREFIX="${PACKAGE_PREFIX:-}"
|
||||||
|
TEMPLATE_DIR="${TEMPLATE_DIR:-$REPO_ROOT/deploy/docker-installer}"
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'USAGE'
|
||||||
|
Usage:
|
||||||
|
scripts/docker/build_docker_installer_package.sh --arch amd64|arm64 [options]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--arch <arch> Target architecture: amd64|arm64
|
||||||
|
--image <tag> Runtime image tag recorded in package manifest.
|
||||||
|
--image-tar <path> Existing docker save tar/tar.gz to include.
|
||||||
|
--metrics-image <tag>
|
||||||
|
Metrics image tag recorded in package manifest.
|
||||||
|
--metrics-image-tar <path>
|
||||||
|
Existing metrics docker save tar/tar.gz to include.
|
||||||
|
--prometheus-image <tag>
|
||||||
|
Prometheus image tag to record and package.
|
||||||
|
--prometheus-image-tar <path>
|
||||||
|
Existing Prometheus docker save tar/tar.gz to include.
|
||||||
|
--grafana-image <tag>
|
||||||
|
Grafana image tag to record and package.
|
||||||
|
--grafana-image-tar <path>
|
||||||
|
Existing Grafana docker save tar/tar.gz to include.
|
||||||
|
--out-dir <path> Output directory.
|
||||||
|
--prefix <name> Package directory/tar prefix.
|
||||||
|
--template-dir <path>
|
||||||
|
Package template directory.
|
||||||
|
-h, --help Show help.
|
||||||
|
|
||||||
|
If --image-tar or --metrics-image-tar is omitted, the script uses the
|
||||||
|
matching docker-save archive under target/<arch>-docker/.
|
||||||
|
USAGE
|
||||||
|
}
|
||||||
|
|
||||||
|
normalize_arch() {
|
||||||
|
case "$1" in
|
||||||
|
amd64|x86_64)
|
||||||
|
printf 'amd64\n'
|
||||||
|
;;
|
||||||
|
arm64|aarch64)
|
||||||
|
printf 'arm64\n'
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
platform_for_arch() {
|
||||||
|
printf 'linux/%s\n' "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
safe_tag_name() {
|
||||||
|
printf '%s' "$1" | tr '/:' '--'
|
||||||
|
}
|
||||||
|
|
||||||
|
default_runtime_tag() {
|
||||||
|
printf 'ours-rp-runtime-%s:dev\n' "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
default_metrics_tag() {
|
||||||
|
printf 'ours-rp-metrics-%s:dev\n' "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
default_package_prefix() {
|
||||||
|
printf 'ours-rp-docker-installer-%s\n' "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
default_host_data_dir() {
|
||||||
|
printf '/var/lib/ours-rp-%s-installer\n' "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
default_compose_project() {
|
||||||
|
printf 'ours-rp-%s-installer\n' "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
default_metrics_instance() {
|
||||||
|
printf '%s-installer\n' "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
replace_or_append_env() {
|
||||||
|
local env_path="$1"
|
||||||
|
local key="$2"
|
||||||
|
local value="$3"
|
||||||
|
local tmp_env
|
||||||
|
tmp_env="${env_path}.tmp"
|
||||||
|
awk -v key="$key" -v value="$value" '
|
||||||
|
BEGIN { done=0 }
|
||||||
|
$0 ~ "^" key "=" { print key "=" value; done=1; next }
|
||||||
|
{ print }
|
||||||
|
END { if (!done) print key "=" value }
|
||||||
|
' "$env_path" > "$tmp_env"
|
||||||
|
mv "$tmp_env" "$env_path"
|
||||||
|
}
|
||||||
|
|
||||||
|
replace_text_in_file() {
|
||||||
|
local file_path="$1"
|
||||||
|
local old_text="$2"
|
||||||
|
local new_text="$3"
|
||||||
|
OLD_TEXT="$old_text" NEW_TEXT="$new_text" perl -0pi -e 's/\Q$ENV{OLD_TEXT}\E/$ENV{NEW_TEXT}/g' "$file_path"
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_image_for_platform() {
|
||||||
|
local image="$1"
|
||||||
|
local expected_platform="$2"
|
||||||
|
local role="$3"
|
||||||
|
local actual_platform
|
||||||
|
if ! docker image inspect "$image" >/dev/null 2>&1; then
|
||||||
|
echo "pulling $role image for $expected_platform: $image" >&2
|
||||||
|
docker pull --platform "$expected_platform" "$image" >&2
|
||||||
|
fi
|
||||||
|
actual_platform="$(docker image inspect --format '{{.Os}}/{{.Architecture}}' "$image" 2>/dev/null || echo unknown)"
|
||||||
|
if [[ "$actual_platform" != "$expected_platform" ]]; then
|
||||||
|
echo "re-pulling $role image for $expected_platform: $image (current=$actual_platform)" >&2
|
||||||
|
docker pull --platform "$expected_platform" "$image" >&2
|
||||||
|
actual_platform="$(docker image inspect --format '{{.Os}}/{{.Architecture}}' "$image" 2>/dev/null || echo unknown)"
|
||||||
|
fi
|
||||||
|
[[ "$actual_platform" == "$expected_platform" ]] || {
|
||||||
|
cat >&2 <<EOF
|
||||||
|
wrong platform for $role image: $image
|
||||||
|
expected: $expected_platform
|
||||||
|
actual: $actual_platform
|
||||||
|
EOF
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
save_image_if_needed() {
|
||||||
|
local image="$1"
|
||||||
|
local existing_tar="$2"
|
||||||
|
local out_dir="$3"
|
||||||
|
local role="$4"
|
||||||
|
local expected_platform="$5"
|
||||||
|
if [[ -n "$existing_tar" ]]; then
|
||||||
|
[[ -f "$existing_tar" ]] || {
|
||||||
|
echo "missing $role image tar: $existing_tar" >&2
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
printf '%s\n' "$existing_tar"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
ensure_image_for_platform "$image" "$expected_platform" "$role"
|
||||||
|
local tar_path="$out_dir/$(safe_tag_name "$image")-${TARGET_ARCH}.tar.gz"
|
||||||
|
echo "saving $role image to $tar_path" >&2
|
||||||
|
docker save "$image" | gzip -c > "$tar_path"
|
||||||
|
printf '%s\n' "$tar_path"
|
||||||
|
}
|
||||||
|
|
||||||
|
default_saved_image_tar() {
|
||||||
|
local image="$1"
|
||||||
|
local out_dir="$REPO_ROOT/target/${TARGET_ARCH}-docker"
|
||||||
|
local safe_name
|
||||||
|
safe_name="$(safe_tag_name "$image")"
|
||||||
|
if [[ -f "$out_dir/${safe_name}.tar.gz" ]]; then
|
||||||
|
printf '%s\n' "$out_dir/${safe_name}.tar.gz"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [[ -f "$out_dir/${safe_name}.tar" ]]; then
|
||||||
|
printf '%s\n' "$out_dir/${safe_name}.tar"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
printf '\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--arch)
|
||||||
|
TARGET_ARCH="$(normalize_arch "$2")" || {
|
||||||
|
echo "unsupported target architecture: $2" >&2
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--image)
|
||||||
|
IMAGE_TAG="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--image-tar)
|
||||||
|
IMAGE_TAR="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--metrics-image)
|
||||||
|
METRICS_IMAGE="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--metrics-image-tar)
|
||||||
|
METRICS_IMAGE_TAR="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--prometheus-image)
|
||||||
|
PROMETHEUS_IMAGE="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--prometheus-image-tar)
|
||||||
|
PROMETHEUS_IMAGE_TAR="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--grafana-image)
|
||||||
|
GRAFANA_IMAGE="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--grafana-image-tar)
|
||||||
|
GRAFANA_IMAGE_TAR="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--out-dir)
|
||||||
|
OUT_DIR="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--prefix)
|
||||||
|
PACKAGE_PREFIX="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--template-dir)
|
||||||
|
TEMPLATE_DIR="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "unknown option: $1" >&2
|
||||||
|
usage >&2
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
[[ -n "$TARGET_ARCH" ]] || {
|
||||||
|
echo "--arch is required" >&2
|
||||||
|
usage >&2
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
|
||||||
|
[[ -d "$TEMPLATE_DIR" ]] || {
|
||||||
|
echo "missing template dir: $TEMPLATE_DIR" >&2
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ -z "$IMAGE_TAG" ]]; then
|
||||||
|
IMAGE_TAG="$(default_runtime_tag "$TARGET_ARCH")"
|
||||||
|
fi
|
||||||
|
if [[ -z "$METRICS_IMAGE" ]]; then
|
||||||
|
METRICS_IMAGE="$(default_metrics_tag "$TARGET_ARCH")"
|
||||||
|
fi
|
||||||
|
if [[ -z "$OUT_DIR" ]]; then
|
||||||
|
OUT_DIR="$REPO_ROOT/target/${TARGET_ARCH}-installer"
|
||||||
|
fi
|
||||||
|
if [[ -z "$PACKAGE_PREFIX" ]]; then
|
||||||
|
PACKAGE_PREFIX="$(default_package_prefix "$TARGET_ARCH")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
TARGET_PLATFORM="$(platform_for_arch "$TARGET_ARCH")"
|
||||||
|
HOST_DATA_DIR_DEFAULT="$(default_host_data_dir "$TARGET_ARCH")"
|
||||||
|
COMPOSE_PROJECT_DEFAULT="$(default_compose_project "$TARGET_ARCH")"
|
||||||
|
METRICS_INSTANCE_DEFAULT="$(default_metrics_instance "$TARGET_ARCH")"
|
||||||
|
|
||||||
|
if [[ -z "$IMAGE_TAR" ]]; then
|
||||||
|
IMAGE_TAR="$(default_saved_image_tar "$IMAGE_TAG")"
|
||||||
|
fi
|
||||||
|
if [[ -z "$METRICS_IMAGE_TAR" ]]; then
|
||||||
|
METRICS_IMAGE_TAR="$(default_saved_image_tar "$METRICS_IMAGE")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ -n "$IMAGE_TAR" && -f "$IMAGE_TAR" ]] || {
|
||||||
|
cat >&2 <<EOF
|
||||||
|
missing runtime image tar for $TARGET_ARCH.
|
||||||
|
|
||||||
|
Build one first, for example:
|
||||||
|
scripts/docker/build_docker_runtime_image.sh --arch $TARGET_ARCH --image $IMAGE_TAG
|
||||||
|
EOF
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
[[ -n "$METRICS_IMAGE_TAR" && -f "$METRICS_IMAGE_TAR" ]] || {
|
||||||
|
cat >&2 <<EOF
|
||||||
|
missing metrics image tar for $TARGET_ARCH.
|
||||||
|
|
||||||
|
Build one first, for example:
|
||||||
|
scripts/docker/build_docker_metrics_image.sh --arch $TARGET_ARCH --image $METRICS_IMAGE
|
||||||
|
EOF
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
|
||||||
|
mkdir -p "$OUT_DIR"
|
||||||
|
commit="$(git -C "$REPO_ROOT" rev-parse --short HEAD 2>/dev/null || echo unknown)"
|
||||||
|
timestamp="$(date -u +%Y%m%dT%H%M%SZ)"
|
||||||
|
package_name="${PACKAGE_PREFIX}-${timestamp}-${commit}"
|
||||||
|
stage="$OUT_DIR/$package_name"
|
||||||
|
tar_path="$OUT_DIR/$package_name.tar.gz"
|
||||||
|
|
||||||
|
rm -rf "$stage"
|
||||||
|
rsync -a --delete "$TEMPLATE_DIR"/ "$stage"/
|
||||||
|
mkdir -p "$stage/images"
|
||||||
|
cp "$IMAGE_TAR" "$stage/images/"
|
||||||
|
cp "$METRICS_IMAGE_TAR" "$stage/images/"
|
||||||
|
|
||||||
|
monitor_image_stage="$OUT_DIR/.monitor-images-$TARGET_ARCH-$timestamp"
|
||||||
|
rm -rf "$monitor_image_stage"
|
||||||
|
mkdir -p "$monitor_image_stage"
|
||||||
|
prometheus_tar="$(save_image_if_needed "$PROMETHEUS_IMAGE" "$PROMETHEUS_IMAGE_TAR" "$monitor_image_stage" "prometheus" "$TARGET_PLATFORM")"
|
||||||
|
grafana_tar="$(save_image_if_needed "$GRAFANA_IMAGE" "$GRAFANA_IMAGE_TAR" "$monitor_image_stage" "grafana" "$TARGET_PLATFORM")"
|
||||||
|
cp "$prometheus_tar" "$stage/images/"
|
||||||
|
cp "$grafana_tar" "$stage/images/"
|
||||||
|
|
||||||
|
if [[ -f "$stage/.env.example" ]]; then
|
||||||
|
replace_or_append_env "$stage/.env.example" "PACKAGE_ARCH" "$TARGET_ARCH"
|
||||||
|
replace_or_append_env "$stage/.env.example" "PACKAGE_PLATFORM" "$TARGET_PLATFORM"
|
||||||
|
replace_or_append_env "$stage/.env.example" "COMPOSE_PROJECT_NAME" "$COMPOSE_PROJECT_DEFAULT"
|
||||||
|
replace_or_append_env "$stage/.env.example" "RPKI_IMAGE" "$IMAGE_TAG"
|
||||||
|
replace_or_append_env "$stage/.env.example" "RPKI_PLATFORM" "$TARGET_PLATFORM"
|
||||||
|
replace_or_append_env "$stage/.env.example" "METRICS_IMAGE" "$METRICS_IMAGE"
|
||||||
|
replace_or_append_env "$stage/.env.example" "METRICS_PLATFORM" "$TARGET_PLATFORM"
|
||||||
|
replace_or_append_env "$stage/.env.example" "HOST_DATA_DIR" "$HOST_DATA_DIR_DEFAULT"
|
||||||
|
replace_or_append_env "$stage/.env.example" "RTR_REPORT_DIR" "$HOST_DATA_DIR_DEFAULT/empty-rtr-report"
|
||||||
|
replace_or_append_env "$stage/.env.example" "RTR_REPORT_CONTAINER_DIR" "/var/lib/ours-rp/rtr-report"
|
||||||
|
replace_or_append_env "$stage/.env.example" "ALLOW_CROSS_ARCH" "0"
|
||||||
|
replace_or_append_env "$stage/.env.example" "METRICS_INSTANCE" "$METRICS_INSTANCE_DEFAULT"
|
||||||
|
replace_or_append_env "$stage/.env.example" "MONITOR_PLATFORM" "$TARGET_PLATFORM"
|
||||||
|
replace_or_append_env "$stage/.env.example" "PROMETHEUS_IMAGE" "$PROMETHEUS_IMAGE"
|
||||||
|
replace_or_append_env "$stage/.env.example" "GRAFANA_IMAGE" "$GRAFANA_IMAGE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
replace_text_in_file "$stage/docs/README.zh-CN.md" "__PACKAGE_ARCH__" "$TARGET_ARCH"
|
||||||
|
replace_text_in_file "$stage/docs/README.en.md" "__PACKAGE_ARCH__" "$TARGET_ARCH"
|
||||||
|
replace_text_in_file "$stage/docs/operations.zh-CN.md" "__PACKAGE_ARCH__" "$TARGET_ARCH"
|
||||||
|
replace_text_in_file "$stage/docs/operations.en.md" "__PACKAGE_ARCH__" "$TARGET_ARCH"
|
||||||
|
replace_text_in_file "$stage/docs/troubleshooting.zh-CN.md" "__PACKAGE_ARCH__" "$TARGET_ARCH"
|
||||||
|
replace_text_in_file "$stage/docs/troubleshooting.en.md" "__PACKAGE_ARCH__" "$TARGET_ARCH"
|
||||||
|
replace_text_in_file "$stage/docs/README.zh-CN.md" "__PACKAGE_PLATFORM__" "$TARGET_PLATFORM"
|
||||||
|
replace_text_in_file "$stage/docs/README.en.md" "__PACKAGE_PLATFORM__" "$TARGET_PLATFORM"
|
||||||
|
replace_text_in_file "$stage/docs/troubleshooting.zh-CN.md" "__PACKAGE_PLATFORM__" "$TARGET_PLATFORM"
|
||||||
|
replace_text_in_file "$stage/docs/troubleshooting.en.md" "__PACKAGE_PLATFORM__" "$TARGET_PLATFORM"
|
||||||
|
replace_text_in_file "$stage/docs/README.zh-CN.md" "__HOST_DATA_DIR__" "$HOST_DATA_DIR_DEFAULT"
|
||||||
|
replace_text_in_file "$stage/docs/README.en.md" "__HOST_DATA_DIR__" "$HOST_DATA_DIR_DEFAULT"
|
||||||
|
replace_text_in_file "$stage/docs/operations.zh-CN.md" "__HOST_DATA_DIR__" "$HOST_DATA_DIR_DEFAULT"
|
||||||
|
replace_text_in_file "$stage/docs/operations.en.md" "__HOST_DATA_DIR__" "$HOST_DATA_DIR_DEFAULT"
|
||||||
|
replace_text_in_file "$stage/docs/troubleshooting.zh-CN.md" "__HOST_DATA_DIR__" "$HOST_DATA_DIR_DEFAULT"
|
||||||
|
replace_text_in_file "$stage/docs/troubleshooting.en.md" "__HOST_DATA_DIR__" "$HOST_DATA_DIR_DEFAULT"
|
||||||
|
|
||||||
|
runtime_tar_sha256="$(sha256sum "$IMAGE_TAR" | awk '{print $1}')"
|
||||||
|
metrics_tar_sha256="$(sha256sum "$METRICS_IMAGE_TAR" | awk '{print $1}')"
|
||||||
|
prometheus_tar_sha256="$(sha256sum "$prometheus_tar" | awk '{print $1}')"
|
||||||
|
grafana_tar_sha256="$(sha256sum "$grafana_tar" | awk '{print $1}')"
|
||||||
|
|
||||||
|
cat > "$stage/PACKAGE-MANIFEST.env" <<EOF
|
||||||
|
package_name=$package_name
|
||||||
|
created_at_utc=$timestamp
|
||||||
|
git_commit=$commit
|
||||||
|
git_status_count=$(git -C "$REPO_ROOT" status --short 2>/dev/null | wc -l | tr -d ' ')
|
||||||
|
PACKAGE_ARCH=$TARGET_ARCH
|
||||||
|
PACKAGE_PLATFORM=$TARGET_PLATFORM
|
||||||
|
package_arch=$TARGET_ARCH
|
||||||
|
package_platform=$TARGET_PLATFORM
|
||||||
|
cross_arch_default=deny
|
||||||
|
cross_arch_enable_var=ALLOW_CROSS_ARCH
|
||||||
|
RPKI_IMAGE=$IMAGE_TAG
|
||||||
|
RPKI_PLATFORM=$TARGET_PLATFORM
|
||||||
|
image_tag=$IMAGE_TAG
|
||||||
|
image_tar=$(basename "$IMAGE_TAR")
|
||||||
|
image_tar_size_bytes=$(wc -c < "$IMAGE_TAR")
|
||||||
|
image_tar_sha256=$runtime_tar_sha256
|
||||||
|
rpki_image=$IMAGE_TAG
|
||||||
|
rpki_platform=$TARGET_PLATFORM
|
||||||
|
METRICS_IMAGE=$METRICS_IMAGE
|
||||||
|
METRICS_PLATFORM=$TARGET_PLATFORM
|
||||||
|
metrics_image=$METRICS_IMAGE
|
||||||
|
metrics_image_tar=$(basename "$METRICS_IMAGE_TAR")
|
||||||
|
metrics_image_tar_size_bytes=$(wc -c < "$METRICS_IMAGE_TAR")
|
||||||
|
metrics_image_tar_sha256=$metrics_tar_sha256
|
||||||
|
PROMETHEUS_IMAGE=$PROMETHEUS_IMAGE
|
||||||
|
prometheus_image=$PROMETHEUS_IMAGE
|
||||||
|
prometheus_image_tar=$(basename "$prometheus_tar")
|
||||||
|
prometheus_image_tar_size_bytes=$(wc -c < "$prometheus_tar")
|
||||||
|
prometheus_image_tar_sha256=$prometheus_tar_sha256
|
||||||
|
GRAFANA_IMAGE=$GRAFANA_IMAGE
|
||||||
|
grafana_image=$GRAFANA_IMAGE
|
||||||
|
grafana_image_tar=$(basename "$grafana_tar")
|
||||||
|
grafana_image_tar_size_bytes=$(wc -c < "$grafana_tar")
|
||||||
|
grafana_image_tar_sha256=$grafana_tar_sha256
|
||||||
|
target_platform=$TARGET_PLATFORM
|
||||||
|
MONITOR_PLATFORM=$TARGET_PLATFORM
|
||||||
|
rpki_image_tar=$(basename "$IMAGE_TAR")
|
||||||
|
monitor_platform=$TARGET_PLATFORM
|
||||||
|
metrics_platform=$TARGET_PLATFORM
|
||||||
|
default_host_data_dir=$HOST_DATA_DIR_DEFAULT
|
||||||
|
default_compose_project_name=$COMPOSE_PROJECT_DEFAULT
|
||||||
|
default_metrics_instance=$METRICS_INSTANCE_DEFAULT
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat > "$stage/PACKAGE-SUMMARY.txt" <<EOF
|
||||||
|
package_name: $package_name
|
||||||
|
package_arch: $TARGET_ARCH
|
||||||
|
package_platform: $TARGET_PLATFORM
|
||||||
|
runtime_image: $IMAGE_TAG
|
||||||
|
metrics_image: $METRICS_IMAGE
|
||||||
|
prometheus_image: $PROMETHEUS_IMAGE
|
||||||
|
grafana_image: $GRAFANA_IMAGE
|
||||||
|
default_host_data_dir: $HOST_DATA_DIR_DEFAULT
|
||||||
|
default_compose_project_name: $COMPOSE_PROJECT_DEFAULT
|
||||||
|
cross_arch_default: deny
|
||||||
|
cross_arch_enable_var: ALLOW_CROSS_ARCH
|
||||||
|
EOF
|
||||||
|
|
||||||
|
chmod +x "$stage"/*.sh "$stage/scripts"/*.sh
|
||||||
|
tar -C "$OUT_DIR" -czf "$tar_path" "$package_name"
|
||||||
|
package_sha256="$(sha256sum "$tar_path" | awk '{print $1}')"
|
||||||
|
rm -rf "$monitor_image_stage"
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "package=$tar_path"
|
||||||
|
echo "package_dir=$stage"
|
||||||
|
echo "package_size_bytes=$(wc -c < "$tar_path")"
|
||||||
|
echo "package_sha256=$package_sha256"
|
||||||
|
echo "package_arch=$TARGET_ARCH"
|
||||||
|
echo "package_platform=$TARGET_PLATFORM"
|
||||||
|
echo "manifest=$stage/PACKAGE-MANIFEST.env"
|
||||||
|
echo "summary=$stage/PACKAGE-SUMMARY.txt"
|
||||||
|
} > "$OUT_DIR/$package_name.summary.env"
|
||||||
|
|
||||||
|
echo "package built: $tar_path"
|
||||||
78
scripts/docker/build_docker_metrics_image.sh
Executable file
78
scripts/docker/build_docker_metrics_image.sh
Executable file
@@ -0,0 +1,78 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||||
|
|
||||||
|
normalize_arch() {
|
||||||
|
case "$1" in
|
||||||
|
amd64|x86_64)
|
||||||
|
printf 'amd64\n'
|
||||||
|
;;
|
||||||
|
arm64|aarch64)
|
||||||
|
printf 'arm64\n'
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
TARGET_ARCH="${TARGET_ARCH:-}"
|
||||||
|
IMAGE_TAG="${IMAGE_TAG:-}"
|
||||||
|
DOCKERFILE="${DOCKERFILE:-$REPO_ROOT/docker/ours-rp-metrics.Dockerfile}"
|
||||||
|
args=()
|
||||||
|
image_explicit=0
|
||||||
|
dockerfile_explicit=0
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--arch)
|
||||||
|
TARGET_ARCH="$(normalize_arch "$2")" || {
|
||||||
|
echo "unsupported target architecture: $2" >&2
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
args+=("$1" "$TARGET_ARCH")
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--image)
|
||||||
|
IMAGE_TAG="$2"
|
||||||
|
image_explicit=1
|
||||||
|
args+=("$1" "$2")
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--dockerfile)
|
||||||
|
DOCKERFILE="$2"
|
||||||
|
dockerfile_explicit=1
|
||||||
|
args+=("$1" "$2")
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
args+=("$1")
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -z "$TARGET_ARCH" ]]; then
|
||||||
|
cat >&2 <<'EOF'
|
||||||
|
missing target architecture.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
scripts/docker/build_docker_metrics_image.sh --arch amd64|arm64 [options]
|
||||||
|
EOF
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "$IMAGE_TAG" ]]; then
|
||||||
|
IMAGE_TAG="ours-rp-metrics-${TARGET_ARCH}:dev"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$dockerfile_explicit" != "1" ]]; then
|
||||||
|
args=(--dockerfile "$DOCKERFILE" "${args[@]}")
|
||||||
|
fi
|
||||||
|
if [[ "$image_explicit" != "1" ]]; then
|
||||||
|
args=(--image "$IMAGE_TAG" "${args[@]}")
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec "$SCRIPT_DIR/build_docker_runtime_image.sh" "${args[@]}"
|
||||||
238
scripts/docker/build_docker_runtime_image.sh
Executable file
238
scripts/docker/build_docker_runtime_image.sh
Executable file
@@ -0,0 +1,238 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||||
|
|
||||||
|
TARGET_ARCH="${TARGET_ARCH:-}"
|
||||||
|
IMAGE_TAG="${IMAGE_TAG:-}"
|
||||||
|
BUILDER_IMAGE="${BUILDER_IMAGE:-rust:1-bookworm}"
|
||||||
|
RUNTIME_IMAGE="${RUNTIME_IMAGE:-debian:bookworm-slim}"
|
||||||
|
OUT_DIR="${OUT_DIR:-}"
|
||||||
|
DOCKERFILE="${DOCKERFILE:-$REPO_ROOT/docker/ours-rp-runtime.Dockerfile}"
|
||||||
|
BUILDER_NAME="${BUILDER_NAME:-default}"
|
||||||
|
INSTALL_BINFMT="${INSTALL_BINFMT:-1}"
|
||||||
|
SAVE_IMAGE="${SAVE_IMAGE:-1}"
|
||||||
|
LOAD_IMAGE="${LOAD_IMAGE:-1}"
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'USAGE'
|
||||||
|
Usage:
|
||||||
|
scripts/docker/build_docker_runtime_image.sh --arch amd64|arm64 [options]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--arch <arch> Target architecture: amd64|arm64
|
||||||
|
--image <tag> Docker image tag (default: ours-rp-runtime-<arch>:dev)
|
||||||
|
--out-dir <path> Directory for docker save tar.gz (default: target/<arch>-docker)
|
||||||
|
--dockerfile <path> Dockerfile path
|
||||||
|
--builder <name> buildx builder name
|
||||||
|
--builder-image <tag>
|
||||||
|
Builder base image (default: rust:1-bookworm)
|
||||||
|
--runtime-image <tag>
|
||||||
|
Runtime base image (default: debian:bookworm-slim)
|
||||||
|
--no-binfmt Do not install binfmt/qemu for cross-architecture builds
|
||||||
|
--no-save Build image but do not docker save it
|
||||||
|
--no-load Use buildx output tar instead of --load
|
||||||
|
-h, --help Show this help
|
||||||
|
USAGE
|
||||||
|
}
|
||||||
|
|
||||||
|
normalize_arch() {
|
||||||
|
case "$1" in
|
||||||
|
amd64|x86_64)
|
||||||
|
printf 'amd64\n'
|
||||||
|
;;
|
||||||
|
arm64|aarch64)
|
||||||
|
printf 'arm64\n'
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
safe_tag_name() {
|
||||||
|
printf '%s' "$1" | tr '/:' '--'
|
||||||
|
}
|
||||||
|
|
||||||
|
platform_for_arch() {
|
||||||
|
printf 'linux/%s\n' "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
default_image_tag() {
|
||||||
|
printf 'ours-rp-runtime-%s:dev\n' "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
require_command() {
|
||||||
|
command -v "$1" >/dev/null 2>&1 || {
|
||||||
|
echo "missing required command: $1" >&2
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
host_arch() {
|
||||||
|
local raw_arch
|
||||||
|
raw_arch="$(uname -m)"
|
||||||
|
normalize_arch "$raw_arch" || {
|
||||||
|
echo "unsupported host architecture: $raw_arch" >&2
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--arch)
|
||||||
|
TARGET_ARCH="$(normalize_arch "$2")" || {
|
||||||
|
echo "unsupported target architecture: $2" >&2
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--image)
|
||||||
|
IMAGE_TAG="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--out-dir)
|
||||||
|
OUT_DIR="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--dockerfile)
|
||||||
|
DOCKERFILE="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--builder)
|
||||||
|
BUILDER_NAME="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--builder-image)
|
||||||
|
BUILDER_IMAGE="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--runtime-image)
|
||||||
|
RUNTIME_IMAGE="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--no-binfmt)
|
||||||
|
INSTALL_BINFMT=0
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--no-save)
|
||||||
|
SAVE_IMAGE=0
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--no-load)
|
||||||
|
LOAD_IMAGE=0
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "unknown option: $1" >&2
|
||||||
|
usage >&2
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
[[ -n "$TARGET_ARCH" ]] || {
|
||||||
|
echo "--arch is required" >&2
|
||||||
|
usage >&2
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ -z "$IMAGE_TAG" ]]; then
|
||||||
|
IMAGE_TAG="$(default_image_tag "$TARGET_ARCH")"
|
||||||
|
fi
|
||||||
|
if [[ -z "$OUT_DIR" ]]; then
|
||||||
|
OUT_DIR="$REPO_ROOT/target/${TARGET_ARCH}-docker"
|
||||||
|
fi
|
||||||
|
|
||||||
|
require_command docker
|
||||||
|
mkdir -p "$OUT_DIR"
|
||||||
|
|
||||||
|
target_platform="$(platform_for_arch "$TARGET_ARCH")"
|
||||||
|
local_host_arch="$(host_arch)"
|
||||||
|
|
||||||
|
if [[ "$INSTALL_BINFMT" == "1" && "$local_host_arch" != "$TARGET_ARCH" ]]; then
|
||||||
|
echo "installing binfmt/qemu for $TARGET_ARCH"
|
||||||
|
docker run --rm --privileged tonistiigi/binfmt --install "$TARGET_ARCH"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$BUILDER_NAME" != "default" ]] && ! docker buildx inspect "$BUILDER_NAME" >/dev/null 2>&1; then
|
||||||
|
docker buildx create --name "$BUILDER_NAME" --driver docker-container --use >/dev/null
|
||||||
|
else
|
||||||
|
docker buildx use "$BUILDER_NAME" >/dev/null
|
||||||
|
fi
|
||||||
|
docker buildx inspect --bootstrap >/dev/null
|
||||||
|
|
||||||
|
metadata_path="$OUT_DIR/$(safe_tag_name "$IMAGE_TAG").build-metadata.json"
|
||||||
|
tar_path="$OUT_DIR/$(safe_tag_name "$IMAGE_TAG").tar.gz"
|
||||||
|
build_log="$OUT_DIR/$(safe_tag_name "$IMAGE_TAG").build.log"
|
||||||
|
|
||||||
|
echo "building $target_platform image: $IMAGE_TAG"
|
||||||
|
echo "repo: $REPO_ROOT"
|
||||||
|
echo "dockerfile: $DOCKERFILE"
|
||||||
|
echo "builder_image: $BUILDER_IMAGE"
|
||||||
|
echo "runtime_image: $RUNTIME_IMAGE"
|
||||||
|
start_epoch="$(date +%s)"
|
||||||
|
|
||||||
|
if [[ "$LOAD_IMAGE" == "1" ]]; then
|
||||||
|
docker buildx build \
|
||||||
|
--platform "$target_platform" \
|
||||||
|
--builder "$BUILDER_NAME" \
|
||||||
|
--load \
|
||||||
|
--build-arg BUILDKIT_INLINE_CACHE=1 \
|
||||||
|
--build-arg "BUILDER_IMAGE=$BUILDER_IMAGE" \
|
||||||
|
--build-arg "RUNTIME_IMAGE=$RUNTIME_IMAGE" \
|
||||||
|
--metadata-file "$metadata_path" \
|
||||||
|
-t "$IMAGE_TAG" \
|
||||||
|
-f "$DOCKERFILE" \
|
||||||
|
"$REPO_ROOT" 2>&1 | tee "$build_log"
|
||||||
|
else
|
||||||
|
raw_tar_path="$OUT_DIR/$(safe_tag_name "$IMAGE_TAG").tar"
|
||||||
|
docker buildx build \
|
||||||
|
--platform "$target_platform" \
|
||||||
|
--builder "$BUILDER_NAME" \
|
||||||
|
--output "type=docker,dest=$raw_tar_path" \
|
||||||
|
--build-arg BUILDKIT_INLINE_CACHE=1 \
|
||||||
|
--build-arg "BUILDER_IMAGE=$BUILDER_IMAGE" \
|
||||||
|
--build-arg "RUNTIME_IMAGE=$RUNTIME_IMAGE" \
|
||||||
|
--metadata-file "$metadata_path" \
|
||||||
|
-t "$IMAGE_TAG" \
|
||||||
|
-f "$DOCKERFILE" \
|
||||||
|
"$REPO_ROOT" 2>&1 | tee "$build_log"
|
||||||
|
gzip -f "$raw_tar_path"
|
||||||
|
tar_path="${raw_tar_path}.gz"
|
||||||
|
fi
|
||||||
|
|
||||||
|
elapsed_secs=$(( $(date +%s) - start_epoch ))
|
||||||
|
|
||||||
|
if [[ "$SAVE_IMAGE" == "1" && "$LOAD_IMAGE" == "1" ]]; then
|
||||||
|
echo "saving image to $tar_path"
|
||||||
|
docker save "$IMAGE_TAG" | gzip -c > "$tar_path"
|
||||||
|
fi
|
||||||
|
|
||||||
|
tar_sha256=""
|
||||||
|
if [[ -f "$tar_path" ]]; then
|
||||||
|
tar_sha256="$(sha256sum "$tar_path" | awk '{print $1}')"
|
||||||
|
fi
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "image=$IMAGE_TAG"
|
||||||
|
echo "platform=$target_platform"
|
||||||
|
echo "arch=$TARGET_ARCH"
|
||||||
|
echo "builder_image=$BUILDER_IMAGE"
|
||||||
|
echo "runtime_image=$RUNTIME_IMAGE"
|
||||||
|
echo "elapsed_secs=$elapsed_secs"
|
||||||
|
echo "metadata=$metadata_path"
|
||||||
|
echo "tar=$tar_path"
|
||||||
|
echo "tar_size_bytes=$(wc -c < "$tar_path" 2>/dev/null || echo 0)"
|
||||||
|
echo "tar_sha256=$tar_sha256"
|
||||||
|
echo "git_commit=$(git -C "$REPO_ROOT" rev-parse --short HEAD 2>/dev/null || echo unknown)"
|
||||||
|
echo "git_status_count=$(git -C "$REPO_ROOT" status --short 2>/dev/null | wc -l | tr -d ' ')"
|
||||||
|
echo "built_at_utc=$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||||
|
} > "$OUT_DIR/$(safe_tag_name "$IMAGE_TAG").build-summary.env"
|
||||||
|
|
||||||
|
echo "build complete: elapsed=${elapsed_secs}s tar=$tar_path"
|
||||||
41
scripts/experiments/feature035/experiments.json
Normal file
41
scripts/experiments/feature035/experiments.json
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"defaultRirs": ["afrinic", "apnic", "arin", "lacnic", "ripe"],
|
||||||
|
"experiments": [
|
||||||
|
{
|
||||||
|
"id": "sync-ours-rsync-only",
|
||||||
|
"left": { "rpKind": "ours", "mode": "standard", "protocol": "rrdp+rsync", "rsyncScope": "module-root" },
|
||||||
|
"right": { "rpKind": "ours", "mode": "standard", "protocol": "rsync-only", "rsyncScope": "module-root" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sync-rpki-client-rsync-only",
|
||||||
|
"left": { "rpKind": "rpki-client", "mode": "standard", "protocol": "rrdp+rsync" },
|
||||||
|
"right": { "rpKind": "rpki-client", "mode": "standard", "protocol": "rsync-only" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "strict-name",
|
||||||
|
"left": { "rpKind": "ours", "mode": "standard", "protocol": "rrdp+rsync" },
|
||||||
|
"right": { "rpKind": "ours", "mode": "strict-name", "protocol": "rrdp+rsync" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "strict-cms-der",
|
||||||
|
"left": { "rpKind": "ours", "mode": "standard", "protocol": "rrdp+rsync" },
|
||||||
|
"right": { "rpKind": "ours", "mode": "strict-cms-der", "protocol": "rrdp+rsync" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "strict-signed-attrs",
|
||||||
|
"left": { "rpKind": "ours", "mode": "standard", "protocol": "rrdp+rsync" },
|
||||||
|
"right": { "rpKind": "ours", "mode": "strict-signed-attrs", "protocol": "rrdp+rsync" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "strict-all",
|
||||||
|
"left": { "rpKind": "ours", "mode": "standard", "protocol": "rrdp+rsync" },
|
||||||
|
"right": { "rpKind": "ours", "mode": "strict-all", "protocol": "rrdp+rsync" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "rp-implementation-standard",
|
||||||
|
"left": { "rpKind": "ours", "mode": "standard", "protocol": "rrdp+rsync" },
|
||||||
|
"right": { "rpKind": "rpki-client", "mode": "standard", "protocol": "rrdp+rsync" }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
431
scripts/experiments/feature035/feature035_bundle.py
Executable file
431
scripts/experiments/feature035/feature035_bundle.py
Executable file
@@ -0,0 +1,431 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import argparse
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import platform
|
||||||
|
import subprocess
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
|
RIR_FIXTURES = {
|
||||||
|
"afrinic": {
|
||||||
|
"tal": "tal/afrinic.tal",
|
||||||
|
"ta": "ta/afrinic-ta.cer",
|
||||||
|
},
|
||||||
|
"apnic": {
|
||||||
|
"tal": "tal/apnic-rfc7730-https.tal",
|
||||||
|
"ta": "ta/apnic-ta.cer",
|
||||||
|
},
|
||||||
|
"arin": {
|
||||||
|
"tal": "tal/arin.tal",
|
||||||
|
"ta": "ta/arin-ta.cer",
|
||||||
|
},
|
||||||
|
"lacnic": {
|
||||||
|
"tal": "tal/lacnic.tal",
|
||||||
|
"ta": "ta/lacnic-ta.cer",
|
||||||
|
},
|
||||||
|
"ripe": {
|
||||||
|
"tal": "tal/ripe-ncc.tal",
|
||||||
|
"ta": "ta/ripe-ncc-ta.cer",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def utc_now() -> str:
|
||||||
|
return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
|
||||||
|
|
||||||
|
|
||||||
|
def sha256_file(path: Path) -> str:
|
||||||
|
hasher = hashlib.sha256()
|
||||||
|
with path.open("rb") as file:
|
||||||
|
for chunk in iter(lambda: file.read(1024 * 1024), b""):
|
||||||
|
hasher.update(chunk)
|
||||||
|
return hasher.hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
def read_tal_uris(path: Path) -> list[str]:
|
||||||
|
uris: list[str] = []
|
||||||
|
with path.open("r", encoding="utf-8") as file:
|
||||||
|
for line in file:
|
||||||
|
item = line.strip()
|
||||||
|
if not item or item.startswith("#"):
|
||||||
|
if uris:
|
||||||
|
break
|
||||||
|
continue
|
||||||
|
if item.startswith(("rsync://", "https://", "http://")):
|
||||||
|
uris.append(item)
|
||||||
|
continue
|
||||||
|
if uris:
|
||||||
|
break
|
||||||
|
return uris
|
||||||
|
|
||||||
|
|
||||||
|
def first_uri(uris: list[str], prefixes: tuple[str, ...]) -> str | None:
|
||||||
|
for uri in uris:
|
||||||
|
if uri.startswith(prefixes):
|
||||||
|
return uri
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def parse_rirs(raw: str) -> list[str]:
|
||||||
|
rirs = [item.strip().lower() for item in raw.split(",") if item.strip()]
|
||||||
|
if not rirs:
|
||||||
|
raise SystemExit("RIR list must not be empty")
|
||||||
|
invalid = [item for item in rirs if item not in RIR_FIXTURES]
|
||||||
|
if invalid:
|
||||||
|
raise SystemExit(
|
||||||
|
f"invalid RIR(s): {','.join(invalid)}; allowed: {','.join(RIR_FIXTURES)}"
|
||||||
|
)
|
||||||
|
return rirs
|
||||||
|
|
||||||
|
|
||||||
|
def rel_or_abs(path: Path, root: Path | None) -> str:
|
||||||
|
path = path.resolve()
|
||||||
|
if root is not None:
|
||||||
|
try:
|
||||||
|
return path.relative_to(root.resolve()).as_posix()
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
return path.as_posix()
|
||||||
|
|
||||||
|
|
||||||
|
def git_commit(repo_root: Path) -> str | None:
|
||||||
|
try:
|
||||||
|
return subprocess.check_output(
|
||||||
|
["git", "-C", str(repo_root), "rev-parse", "--short", "HEAD"],
|
||||||
|
text=True,
|
||||||
|
stderr=subprocess.DEVNULL,
|
||||||
|
).strip()
|
||||||
|
except (subprocess.CalledProcessError, FileNotFoundError):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def write_json(path: Path, value: dict[str, Any]) -> None:
|
||||||
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
path.write_text(json.dumps(value, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def build_fixture_proof(
|
||||||
|
fixture_dir: Path,
|
||||||
|
rirs: list[str],
|
||||||
|
repo_root: Path | None,
|
||||||
|
ta_online_fetch_observed: bool,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
trust_anchors = []
|
||||||
|
for rir in rirs:
|
||||||
|
mapping = RIR_FIXTURES[rir]
|
||||||
|
tal_path = fixture_dir / mapping["tal"]
|
||||||
|
ta_path = fixture_dir / mapping["ta"]
|
||||||
|
if not tal_path.is_file():
|
||||||
|
raise SystemExit(f"missing TAL fixture for {rir}: {tal_path}")
|
||||||
|
if not ta_path.is_file():
|
||||||
|
raise SystemExit(f"missing TA fixture for {rir}: {ta_path}")
|
||||||
|
tal_uris = read_tal_uris(tal_path)
|
||||||
|
trust_anchors.append(
|
||||||
|
{
|
||||||
|
"rir": rir,
|
||||||
|
"talPath": rel_or_abs(tal_path, repo_root),
|
||||||
|
"taPath": rel_or_abs(ta_path, repo_root),
|
||||||
|
"talUri": first_uri(tal_uris, ("https://", "http://")),
|
||||||
|
"taRsyncUri": first_uri(tal_uris, ("rsync://",)),
|
||||||
|
"talSha256": sha256_file(tal_path),
|
||||||
|
"taCertificateSha256": sha256_file(ta_path),
|
||||||
|
"talBytes": tal_path.stat().st_size,
|
||||||
|
"taCertificateBytes": ta_path.stat().st_size,
|
||||||
|
"taFixturePinned": not ta_online_fetch_observed,
|
||||||
|
"taOnlineFetchObserved": ta_online_fetch_observed,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"generatedBy": "feature035-experiment-driver",
|
||||||
|
"generatedAtUtc": utc_now(),
|
||||||
|
"fixtureDir": rel_or_abs(fixture_dir, repo_root),
|
||||||
|
"all5": set(rirs) == set(RIR_FIXTURES),
|
||||||
|
"rirs": rirs,
|
||||||
|
"trustAnchors": trust_anchors,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def parse_csv(raw: str) -> list[str]:
|
||||||
|
if not raw:
|
||||||
|
return []
|
||||||
|
return [item.strip() for item in raw.split(",") if item.strip()]
|
||||||
|
|
||||||
|
|
||||||
|
def optional_path(raw: str | None, repo_root: Path | None) -> str | None:
|
||||||
|
if raw is None:
|
||||||
|
return None
|
||||||
|
return rel_or_abs(Path(raw), repo_root)
|
||||||
|
|
||||||
|
|
||||||
|
def build_run_meta(args: argparse.Namespace) -> dict[str, Any]:
|
||||||
|
rirs = parse_rirs(args.rirs)
|
||||||
|
repo_root = Path(args.repo_root).resolve() if args.repo_root else None
|
||||||
|
argv = json.loads(args.argv_json) if args.argv_json else []
|
||||||
|
env_whitelist = json.loads(args.env_json) if args.env_json else {}
|
||||||
|
fixture_proof_summary = (
|
||||||
|
json.loads(args.fixture_proof_summary_json)
|
||||||
|
if args.fixture_proof_summary_json
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
fixture_proof = None
|
||||||
|
if args.fixture_proof:
|
||||||
|
fixture_proof_path = Path(args.fixture_proof)
|
||||||
|
if fixture_proof_path.is_file():
|
||||||
|
fixture_proof = json.loads(fixture_proof_path.read_text(encoding="utf-8"))
|
||||||
|
if not isinstance(argv, list):
|
||||||
|
raise SystemExit("--argv-json must decode to a JSON array")
|
||||||
|
if not isinstance(env_whitelist, dict):
|
||||||
|
raise SystemExit("--env-json must decode to a JSON object")
|
||||||
|
if fixture_proof_summary is not None and not isinstance(fixture_proof_summary, dict):
|
||||||
|
raise SystemExit("--fixture-proof-summary-json must decode to a JSON object")
|
||||||
|
|
||||||
|
return {
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"generatedBy": "feature035-experiment-driver",
|
||||||
|
"generatedAtUtc": utc_now(),
|
||||||
|
"experimentId": args.experiment_id,
|
||||||
|
"side": args.side,
|
||||||
|
"sideLabel": args.side_label,
|
||||||
|
"step": args.step,
|
||||||
|
"runId": args.run_id,
|
||||||
|
"liveRun": not args.replay_used,
|
||||||
|
"replayUsed": args.replay_used,
|
||||||
|
"rp": {
|
||||||
|
"kind": args.rp_kind,
|
||||||
|
"binary": args.rp_binary,
|
||||||
|
"version": args.rp_version,
|
||||||
|
"gitCommit": args.rp_git_commit,
|
||||||
|
"mode": args.rp_mode,
|
||||||
|
"protocolMode": args.protocol_mode,
|
||||||
|
"strictPolicies": parse_csv(args.strict_policies),
|
||||||
|
},
|
||||||
|
"scope": {
|
||||||
|
"rirs": rirs,
|
||||||
|
"all5": set(rirs) == set(RIR_FIXTURES),
|
||||||
|
},
|
||||||
|
"command": {
|
||||||
|
"argv": argv,
|
||||||
|
"cwd": args.cwd,
|
||||||
|
"envWhitelist": env_whitelist,
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"resetBeforeRun": args.reset_before_run,
|
||||||
|
"stateRoot": args.state_root,
|
||||||
|
"db": args.db,
|
||||||
|
"repoBytesDb": args.repo_bytes_db,
|
||||||
|
"rawStoreDb": args.raw_store_db,
|
||||||
|
"rsyncMirrorRoot": args.rsync_mirror_root,
|
||||||
|
"cacheRoot": args.cache_root,
|
||||||
|
},
|
||||||
|
"artifacts": {
|
||||||
|
"ccr": optional_path(args.ccr, repo_root),
|
||||||
|
"cir": optional_path(args.cir, repo_root),
|
||||||
|
"runMeta": optional_path(str(args.out), repo_root),
|
||||||
|
"fixtureProof": optional_path(args.fixture_proof, repo_root),
|
||||||
|
"reportJson": optional_path(args.report_json, repo_root),
|
||||||
|
"stageTimingJson": optional_path(args.stage_timing_json, repo_root),
|
||||||
|
"stdoutLog": optional_path(args.stdout_log, repo_root),
|
||||||
|
"stderrLog": optional_path(args.stderr_log, repo_root),
|
||||||
|
"processTime": optional_path(args.process_time, repo_root),
|
||||||
|
"vrpsCsv": optional_path(args.vrps_csv, repo_root),
|
||||||
|
"vapsCsv": optional_path(args.vaps_csv, repo_root),
|
||||||
|
},
|
||||||
|
"fixtureProof": fixture_proof,
|
||||||
|
"fixtureProofSummary": fixture_proof_summary,
|
||||||
|
"metrics": {
|
||||||
|
"exitCode": args.exit_code,
|
||||||
|
"wallMs": args.wall_ms,
|
||||||
|
"maxRssKb": args.max_rss_kb,
|
||||||
|
"vrps": args.vrps,
|
||||||
|
"vaps": args.vaps,
|
||||||
|
"publicationPoints": args.publication_points,
|
||||||
|
"warnings": args.warnings,
|
||||||
|
"cirObjectCount": args.cir_object_count,
|
||||||
|
"cirRejectCount": args.cir_reject_count,
|
||||||
|
"cirTrustAnchorCount": args.cir_trust_anchor_count,
|
||||||
|
"ccrStateDigest": args.ccr_state_digest,
|
||||||
|
},
|
||||||
|
"environment": {
|
||||||
|
"host": args.host or platform.node(),
|
||||||
|
"platform": args.platform or platform.platform(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def command_fixture_proof(args: argparse.Namespace) -> None:
|
||||||
|
repo_root = Path(args.repo_root).resolve() if args.repo_root else None
|
||||||
|
proof = build_fixture_proof(
|
||||||
|
fixture_dir=Path(args.fixture_dir),
|
||||||
|
rirs=parse_rirs(args.rirs),
|
||||||
|
repo_root=repo_root,
|
||||||
|
ta_online_fetch_observed=args.ta_online_fetch_observed,
|
||||||
|
)
|
||||||
|
write_json(Path(args.out), proof)
|
||||||
|
|
||||||
|
|
||||||
|
def command_run_meta(args: argparse.Namespace) -> None:
|
||||||
|
write_json(Path(args.out), build_run_meta(args))
|
||||||
|
|
||||||
|
|
||||||
|
def command_dry_run_bundle(args: argparse.Namespace) -> None:
|
||||||
|
out_dir = Path(args.out_dir)
|
||||||
|
repo_root = Path(args.repo_root).resolve() if args.repo_root else Path.cwd().resolve()
|
||||||
|
fixture_proof = out_dir / "fixture-proof.json"
|
||||||
|
command_fixture_proof(
|
||||||
|
argparse.Namespace(
|
||||||
|
fixture_dir=args.fixture_dir,
|
||||||
|
rirs=args.rirs,
|
||||||
|
repo_root=str(repo_root),
|
||||||
|
out=str(fixture_proof),
|
||||||
|
ta_online_fetch_observed=False,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
for side, side_label in (("left", "A"), ("right", "B")):
|
||||||
|
run_dir = out_dir / side_label / "snapshot"
|
||||||
|
meta_args = argparse.Namespace(
|
||||||
|
out=run_dir / "run-meta.json",
|
||||||
|
repo_root=str(repo_root),
|
||||||
|
experiment_id=args.experiment_id,
|
||||||
|
side=side,
|
||||||
|
side_label=side_label,
|
||||||
|
step="snapshot",
|
||||||
|
run_id=f"{side_label}-snapshot-dry-run",
|
||||||
|
replay_used=False,
|
||||||
|
rp_kind="ours" if side_label == "A" else "rpki-client",
|
||||||
|
rp_binary=f"bin/{'rpki' if side_label == 'A' else 'rpki-client'}",
|
||||||
|
rp_version="dry-run",
|
||||||
|
rp_git_commit=git_commit(repo_root),
|
||||||
|
rp_mode="standard",
|
||||||
|
protocol_mode="rrdp+rsync",
|
||||||
|
strict_policies="",
|
||||||
|
rirs=args.rirs,
|
||||||
|
argv_json=json.dumps(["dry-run"]),
|
||||||
|
env_json=json.dumps({"RPKI_PROGRESS_LOG": "1"}),
|
||||||
|
cwd=str(out_dir),
|
||||||
|
reset_before_run=True,
|
||||||
|
state_root=str(out_dir / side_label / "state"),
|
||||||
|
db=str(out_dir / side_label / "state" / "work-db"),
|
||||||
|
repo_bytes_db=str(out_dir / side_label / "state" / "repo-bytes.db"),
|
||||||
|
raw_store_db=str(out_dir / side_label / "state" / "raw-store.db"),
|
||||||
|
rsync_mirror_root=str(out_dir / side_label / "state" / "rsync-mirror"),
|
||||||
|
cache_root=str(out_dir / side_label / "state" / "cache"),
|
||||||
|
ccr=str(run_dir / "result.ccr"),
|
||||||
|
cir=str(run_dir / "result.cir"),
|
||||||
|
fixture_proof=str(fixture_proof),
|
||||||
|
report_json=str(run_dir / "report.json"),
|
||||||
|
stage_timing_json=str(run_dir / "stage-timing.json"),
|
||||||
|
stdout_log=str(run_dir / "stdout.log"),
|
||||||
|
stderr_log=str(run_dir / "stderr.log"),
|
||||||
|
process_time=str(run_dir / "process-time.txt"),
|
||||||
|
vrps_csv=str(run_dir / "vrps.csv"),
|
||||||
|
vaps_csv=str(run_dir / "vaps.csv"),
|
||||||
|
exit_code=0,
|
||||||
|
wall_ms=0,
|
||||||
|
max_rss_kb=0,
|
||||||
|
vrps=0,
|
||||||
|
vaps=0,
|
||||||
|
publication_points=0,
|
||||||
|
warnings=0,
|
||||||
|
cir_object_count=0,
|
||||||
|
cir_reject_count=0,
|
||||||
|
cir_trust_anchor_count=len(parse_rirs(args.rirs)),
|
||||||
|
ccr_state_digest=None,
|
||||||
|
fixture_proof_summary_json=json.dumps(
|
||||||
|
{
|
||||||
|
"taFixturePinned": True,
|
||||||
|
"taOnlineFetchObserved": False,
|
||||||
|
"trustAnchorCount": len(parse_rirs(args.rirs)),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
)
|
||||||
|
command_run_meta(meta_args)
|
||||||
|
|
||||||
|
|
||||||
|
def add_run_meta_args(parser: argparse.ArgumentParser) -> None:
|
||||||
|
parser.add_argument("--out", required=True)
|
||||||
|
parser.add_argument("--repo-root")
|
||||||
|
parser.add_argument("--experiment-id", required=True)
|
||||||
|
parser.add_argument("--side", choices=["left", "right"], required=True)
|
||||||
|
parser.add_argument("--side-label", choices=["A", "B"], required=True)
|
||||||
|
parser.add_argument("--step", choices=["snapshot", "delta"], required=True)
|
||||||
|
parser.add_argument("--run-id", required=True)
|
||||||
|
parser.add_argument("--replay-used", action="store_true")
|
||||||
|
parser.add_argument("--rp-kind", required=True)
|
||||||
|
parser.add_argument("--rp-binary", required=True)
|
||||||
|
parser.add_argument("--rp-version")
|
||||||
|
parser.add_argument("--rp-git-commit")
|
||||||
|
parser.add_argument("--rp-mode", default="standard")
|
||||||
|
parser.add_argument("--protocol-mode", default="rrdp+rsync")
|
||||||
|
parser.add_argument("--strict-policies", default="")
|
||||||
|
parser.add_argument("--rirs", default="afrinic,apnic,arin,lacnic,ripe")
|
||||||
|
parser.add_argument("--argv-json")
|
||||||
|
parser.add_argument("--env-json")
|
||||||
|
parser.add_argument("--cwd", default=os.getcwd())
|
||||||
|
parser.add_argument("--reset-before-run", action="store_true")
|
||||||
|
parser.add_argument("--state-root")
|
||||||
|
parser.add_argument("--db")
|
||||||
|
parser.add_argument("--repo-bytes-db")
|
||||||
|
parser.add_argument("--raw-store-db")
|
||||||
|
parser.add_argument("--rsync-mirror-root")
|
||||||
|
parser.add_argument("--cache-root")
|
||||||
|
parser.add_argument("--ccr")
|
||||||
|
parser.add_argument("--cir")
|
||||||
|
parser.add_argument("--fixture-proof")
|
||||||
|
parser.add_argument("--fixture-proof-summary-json")
|
||||||
|
parser.add_argument("--report-json")
|
||||||
|
parser.add_argument("--stage-timing-json")
|
||||||
|
parser.add_argument("--stdout-log")
|
||||||
|
parser.add_argument("--stderr-log")
|
||||||
|
parser.add_argument("--process-time")
|
||||||
|
parser.add_argument("--vrps-csv")
|
||||||
|
parser.add_argument("--vaps-csv")
|
||||||
|
parser.add_argument("--exit-code", type=int)
|
||||||
|
parser.add_argument("--wall-ms", type=int)
|
||||||
|
parser.add_argument("--max-rss-kb", type=int)
|
||||||
|
parser.add_argument("--vrps", type=int)
|
||||||
|
parser.add_argument("--vaps", type=int)
|
||||||
|
parser.add_argument("--publication-points", type=int)
|
||||||
|
parser.add_argument("--warnings", type=int)
|
||||||
|
parser.add_argument("--cir-object-count", type=int)
|
||||||
|
parser.add_argument("--cir-reject-count", type=int)
|
||||||
|
parser.add_argument("--cir-trust-anchor-count", type=int)
|
||||||
|
parser.add_argument("--ccr-state-digest")
|
||||||
|
parser.add_argument("--host")
|
||||||
|
parser.add_argument("--platform")
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
parser = argparse.ArgumentParser(description="Feature #035 CCR/CIR experiment bundle helpers")
|
||||||
|
subparsers = parser.add_subparsers(dest="command", required=True)
|
||||||
|
|
||||||
|
fixture = subparsers.add_parser("fixture-proof")
|
||||||
|
fixture.add_argument("--fixture-dir", default="tests/fixtures")
|
||||||
|
fixture.add_argument("--rirs", default="afrinic,apnic,arin,lacnic,ripe")
|
||||||
|
fixture.add_argument("--repo-root")
|
||||||
|
fixture.add_argument("--out", required=True)
|
||||||
|
fixture.add_argument("--ta-online-fetch-observed", action="store_true")
|
||||||
|
fixture.set_defaults(func=command_fixture_proof)
|
||||||
|
|
||||||
|
run_meta = subparsers.add_parser("run-meta")
|
||||||
|
add_run_meta_args(run_meta)
|
||||||
|
run_meta.set_defaults(func=command_run_meta)
|
||||||
|
|
||||||
|
dry_run = subparsers.add_parser("dry-run-bundle")
|
||||||
|
dry_run.add_argument("--out-dir", required=True)
|
||||||
|
dry_run.add_argument("--repo-root", default=".")
|
||||||
|
dry_run.add_argument("--fixture-dir", default="tests/fixtures")
|
||||||
|
dry_run.add_argument("--rirs", default="afrinic,apnic,arin,lacnic,ripe")
|
||||||
|
dry_run.add_argument("--experiment-id", default="m2-dry-run")
|
||||||
|
dry_run.set_defaults(func=command_dry_run_bundle)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
args.func(args)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
10
scripts/experiments/feature035/fixture-manifest.json
Normal file
10
scripts/experiments/feature035/fixture-manifest.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"rirs": {
|
||||||
|
"afrinic": { "tal": "tal/afrinic.tal", "ta": "ta/afrinic-ta.cer" },
|
||||||
|
"apnic": { "tal": "tal/apnic-rfc7730-https.tal", "ta": "ta/apnic-ta.cer" },
|
||||||
|
"arin": { "tal": "tal/arin.tal", "ta": "ta/arin-ta.cer" },
|
||||||
|
"lacnic": { "tal": "tal/lacnic.tal", "ta": "ta/lacnic-ta.cer" },
|
||||||
|
"ripe": { "tal": "tal/ripe-ncc.tal", "ta": "ta/ripe-ncc-ta.cer" }
|
||||||
|
}
|
||||||
|
}
|
||||||
1036
scripts/experiments/feature035/run_feature035_experiment.py
Executable file
1036
scripts/experiments/feature035/run_feature035_experiment.py
Executable file
File diff suppressed because it is too large
Load Diff
939
scripts/experiments/feature043/run_sequence_triage_experiment.py
Executable file
939
scripts/experiments/feature043/run_sequence_triage_experiment.py
Executable file
@@ -0,0 +1,939 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import shlex
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
SCRIPT_DIR = Path(__file__).resolve().parent
|
||||||
|
REPO_ROOT = SCRIPT_DIR.parents[2]
|
||||||
|
DEV_ROOT = REPO_ROOT.parents[1]
|
||||||
|
FEATURE035_DIR = REPO_ROOT / "scripts" / "experiments" / "feature035"
|
||||||
|
FIXTURE_MANIFEST_PATH = FEATURE035_DIR / "fixture-manifest.json"
|
||||||
|
PORTABLE_ROOT = DEV_ROOT / "rpki-client-portable"
|
||||||
|
CACHED_CIR_RPKI_CLIENT = DEV_ROOT / ".cache" / "rpki-client-9.8-cir" / "rpki-client"
|
||||||
|
CACHED_CIR_LIBTLS = DEV_ROOT / ".cache" / "rpki-client-9.8-cir" / "libtls.so.28"
|
||||||
|
|
||||||
|
DEFAULT_RIRS = ["afrinic", "apnic", "arin", "lacnic", "ripe"]
|
||||||
|
|
||||||
|
|
||||||
|
def run_local(argv: list[str], *, cwd: Path | None = None, capture: bool = False, check: bool = True) -> subprocess.CompletedProcess[str]:
|
||||||
|
result = subprocess.run(argv, cwd=str(cwd) if cwd else None, text=True, capture_output=capture, check=False)
|
||||||
|
if check and result.returncode != 0:
|
||||||
|
raise SystemExit(
|
||||||
|
f"command failed ({result.returncode}): {' '.join(shlex.quote(x) for x in argv)}\n"
|
||||||
|
f"stdout:\n{result.stdout or ''}\nstderr:\n{result.stderr or ''}"
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def ssh_script(target: str, script: str, *, capture: bool = False, check: bool = True) -> subprocess.CompletedProcess[str]:
|
||||||
|
result = subprocess.run(["ssh", target, "bash", "-s"], input=script, text=True, capture_output=capture, check=False)
|
||||||
|
if check and result.returncode != 0:
|
||||||
|
raise SystemExit(f"remote script failed ({result.returncode}) on {target}\n{result.stdout}\n{result.stderr}")
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def rsync_to_remote(target: str, source: Path, destination: str | Path) -> None:
|
||||||
|
run_local(["rsync", "-a", str(source), f"{target}:{destination}"])
|
||||||
|
|
||||||
|
|
||||||
|
def rsync_dir_to_remote(target: str, source: Path, destination: str | Path) -> None:
|
||||||
|
run_local(["rsync", "-a", f"{source}/", f"{target}:{destination}/"])
|
||||||
|
|
||||||
|
|
||||||
|
def rsync_from_remote(target: str, source: str | Path, destination: Path) -> None:
|
||||||
|
destination.mkdir(parents=True, exist_ok=True)
|
||||||
|
run_local(["rsync", "-a", f"{target}:{source}/", f"{destination}/"])
|
||||||
|
|
||||||
|
|
||||||
|
def rsync_run_artifacts_from_remote(target: str, source: str | Path, destination: Path) -> None:
|
||||||
|
destination.mkdir(parents=True, exist_ok=True)
|
||||||
|
rsync_base = ["rsync", "-az", "--ignore-missing-args", "--partial", "--partial-dir=.rsync-partial"]
|
||||||
|
for name in [
|
||||||
|
"result.ccr",
|
||||||
|
"result.cir",
|
||||||
|
"report.json",
|
||||||
|
"vrps.csv",
|
||||||
|
"vaps.csv",
|
||||||
|
"process-time.txt",
|
||||||
|
"remote-run-meta.json",
|
||||||
|
"exit-code.txt",
|
||||||
|
"started-at.txt",
|
||||||
|
"finished-at.txt",
|
||||||
|
"stdout.log",
|
||||||
|
"stderr.log",
|
||||||
|
]:
|
||||||
|
run_local([*rsync_base, f"{target}:{source}/{name}", f"{destination}/"])
|
||||||
|
|
||||||
|
|
||||||
|
def rsync_remote_analysis_from_remote(target: str, remote_exp_root: str | Path, local_exp_root: Path) -> None:
|
||||||
|
local_exp_root.mkdir(parents=True, exist_ok=True)
|
||||||
|
rsync_base = ["rsync", "-az", "--ignore-missing-args", "--partial", "--partial-dir=.rsync-partial"]
|
||||||
|
for name in [
|
||||||
|
"left-sequence.jsonl",
|
||||||
|
"right-sequence.jsonl",
|
||||||
|
"run-progress.json",
|
||||||
|
"sequence-triage-time.txt",
|
||||||
|
"sequence-triage/sequence-triage.json",
|
||||||
|
]:
|
||||||
|
destination = local_exp_root / Path(name).parent
|
||||||
|
destination.mkdir(parents=True, exist_ok=True)
|
||||||
|
run_local([*rsync_base, f"{target}:{remote_exp_root}/{name}", f"{destination}/"])
|
||||||
|
|
||||||
|
|
||||||
|
def same_remote_location(
|
||||||
|
left_target: str,
|
||||||
|
left_root: str | Path,
|
||||||
|
right_target: str,
|
||||||
|
right_root: str | Path,
|
||||||
|
) -> bool:
|
||||||
|
return left_target == right_target and str(left_root) == str(right_root)
|
||||||
|
|
||||||
|
|
||||||
|
def load_json(path: Path) -> Any:
|
||||||
|
with path.open("r", encoding="utf-8") as handle:
|
||||||
|
return json.load(handle)
|
||||||
|
|
||||||
|
|
||||||
|
def write_json(path: Path, value: Any) -> None:
|
||||||
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
with path.open("w", encoding="utf-8") as handle:
|
||||||
|
json.dump(value, handle, indent=2, sort_keys=True, ensure_ascii=False)
|
||||||
|
handle.write("\n")
|
||||||
|
|
||||||
|
|
||||||
|
def append_jsonl(path: Path, value: Any) -> None:
|
||||||
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
with path.open("a", encoding="utf-8") as handle:
|
||||||
|
handle.write(json.dumps(value, sort_keys=True, ensure_ascii=False))
|
||||||
|
handle.write("\n")
|
||||||
|
|
||||||
|
|
||||||
|
def utc_stamp() -> str:
|
||||||
|
return time.strftime("%Y%m%dT%H%M%SZ", time.gmtime())
|
||||||
|
|
||||||
|
|
||||||
|
def rfc3339_now() -> str:
|
||||||
|
return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
||||||
|
|
||||||
|
|
||||||
|
def fixture_manifest() -> dict[str, Any]:
|
||||||
|
return load_json(FIXTURE_MANIFEST_PATH)
|
||||||
|
|
||||||
|
|
||||||
|
def fixture_name(rir: str, kind: str) -> str:
|
||||||
|
return Path(fixture_manifest()["rirs"][rir][kind]).name
|
||||||
|
|
||||||
|
|
||||||
|
def fixture_desc(rir: str) -> str:
|
||||||
|
return {
|
||||||
|
"afrinic": "afrinic",
|
||||||
|
"apnic": "apnic-rfc7730-https",
|
||||||
|
"arin": "arin",
|
||||||
|
"lacnic": "lacnic",
|
||||||
|
"ripe": "ripe-ncc",
|
||||||
|
}[rir]
|
||||||
|
|
||||||
|
|
||||||
|
def cir_tal_uri_for_rir(rir: str) -> str:
|
||||||
|
return {
|
||||||
|
"afrinic": "https://rpki.afrinic.net/tal/afrinic.tal",
|
||||||
|
"apnic": "https://rpki.apnic.net/tal/apnic-rfc7730-https.tal",
|
||||||
|
"arin": "https://www.arin.net/resources/manage/rpki/arin.tal",
|
||||||
|
"lacnic": "https://www.lacnic.net/innovaportal/file/4983/1/lacnic.tal",
|
||||||
|
"ripe": "https://tal.rpki.ripe.net/ripe-ncc.tal",
|
||||||
|
}[rir]
|
||||||
|
|
||||||
|
|
||||||
|
def sha256_file(path: Path) -> str:
|
||||||
|
digest = hashlib.sha256()
|
||||||
|
with path.open("rb") as handle:
|
||||||
|
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
|
||||||
|
digest.update(chunk)
|
||||||
|
return digest.hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
def parse_elapsed_to_ms(raw: str) -> int:
|
||||||
|
raw = raw.strip()
|
||||||
|
if not raw:
|
||||||
|
return 0
|
||||||
|
if "-" in raw:
|
||||||
|
days, raw = raw.split("-", 1)
|
||||||
|
else:
|
||||||
|
days = "0"
|
||||||
|
parts = raw.split(":")
|
||||||
|
if len(parts) == 3:
|
||||||
|
hours, minutes, seconds = parts
|
||||||
|
elif len(parts) == 2:
|
||||||
|
hours = "0"
|
||||||
|
minutes, seconds = parts
|
||||||
|
else:
|
||||||
|
hours = "0"
|
||||||
|
minutes = "0"
|
||||||
|
seconds = parts[0]
|
||||||
|
return int(round((int(days) * 86400 + int(hours) * 3600 + int(minutes) * 60 + float(seconds)) * 1000))
|
||||||
|
|
||||||
|
|
||||||
|
def parse_time_file(path: Path) -> dict[str, Any]:
|
||||||
|
data: dict[str, Any] = {}
|
||||||
|
if not path.is_file():
|
||||||
|
return data
|
||||||
|
for line in path.read_text(encoding="utf-8", errors="replace").splitlines():
|
||||||
|
if "Elapsed (wall clock) time" in line:
|
||||||
|
elapsed = line.rsplit(":", 1)[1] if "):" not in line else line.rsplit("):", 1)[1]
|
||||||
|
data["wallMs"] = parse_elapsed_to_ms(elapsed)
|
||||||
|
elif "Maximum resident set size" in line:
|
||||||
|
try:
|
||||||
|
data["maxRssKb"] = int(line.rsplit(":", 1)[1].strip())
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def rpki_client_bin_path() -> Path:
|
||||||
|
primary = PORTABLE_ROOT / "src" / "rpki-client"
|
||||||
|
for candidate in (primary, CACHED_CIR_RPKI_CLIENT):
|
||||||
|
if not candidate.is_file():
|
||||||
|
continue
|
||||||
|
smoke = run_local([str(candidate), "-T", "invalid"], capture=True, check=False)
|
||||||
|
if "--ta-fixture requires <tal>:<path>" in (smoke.stderr + smoke.stdout):
|
||||||
|
return candidate
|
||||||
|
raise SystemExit("rpki-client binary lacks CIR/TA fixture support; checkout feature/cir-output-for-rp-compare or restore .cache/rpki-client-9.8-cir/rpki-client")
|
||||||
|
|
||||||
|
|
||||||
|
def detect_libtls_path(rpki_client_bin: Path) -> Path:
|
||||||
|
if CACHED_CIR_LIBTLS.is_file():
|
||||||
|
return CACHED_CIR_LIBTLS
|
||||||
|
ldd = run_local(["ldd", str(rpki_client_bin)], capture=True)
|
||||||
|
for line in ldd.stdout.splitlines():
|
||||||
|
if "libtls.so.28" not in line or "=>" not in line:
|
||||||
|
continue
|
||||||
|
candidate = Path(line.split("=>", 1)[1].strip().split(" ", 1)[0])
|
||||||
|
if candidate.is_file():
|
||||||
|
return candidate
|
||||||
|
fallback = DEV_ROOT / ".cache" / "rpki-client-9.8-cir" / "libtls.so.28"
|
||||||
|
if fallback.is_file():
|
||||||
|
return fallback
|
||||||
|
raise SystemExit("unable to locate libtls.so.28 for rpki-client")
|
||||||
|
|
||||||
|
|
||||||
|
def build_tool_binaries() -> None:
|
||||||
|
run_local([
|
||||||
|
"cargo", "build", "--release",
|
||||||
|
"--bin", "rpki",
|
||||||
|
"--bin", "sequence_triage_ccr_cir",
|
||||||
|
"--bin", "cir_dump_reject_list",
|
||||||
|
], cwd=REPO_ROOT)
|
||||||
|
_ = rpki_client_bin_path()
|
||||||
|
|
||||||
|
|
||||||
|
def validate_remote_disk(ssh_target: str) -> None:
|
||||||
|
script = r'''
|
||||||
|
set -euo pipefail
|
||||||
|
df -h /data / || true
|
||||||
|
python3 - <<'PY'
|
||||||
|
import shutil
|
||||||
|
for path in ['/data', '/']:
|
||||||
|
try:
|
||||||
|
usage = shutil.disk_usage(path)
|
||||||
|
except FileNotFoundError:
|
||||||
|
continue
|
||||||
|
used = usage.used / usage.total if usage.total else 0
|
||||||
|
print(f'{path} used={used:.2%}')
|
||||||
|
if used >= 0.90:
|
||||||
|
raise SystemExit(f'{path} disk usage >= 90%; cleanup required before all5 sequence experiment')
|
||||||
|
PY
|
||||||
|
'''
|
||||||
|
ssh_script(ssh_target, script)
|
||||||
|
|
||||||
|
|
||||||
|
def prepare_remote(ssh_target: str, remote_root: Path, needs_rpki_client: bool) -> None:
|
||||||
|
validate_remote_disk(ssh_target)
|
||||||
|
preflight = (
|
||||||
|
"set -euo pipefail; "
|
||||||
|
"systemctl disable --now rpki-client.timer >/dev/null 2>&1 || true; "
|
||||||
|
"systemctl stop rpki-client.service >/dev/null 2>&1 || true; "
|
||||||
|
"pkill -x rpki-client >/dev/null 2>&1 || true; "
|
||||||
|
"pkill -x routinator >/dev/null 2>&1 || true; "
|
||||||
|
f"mkdir -p {shlex.quote(str(remote_root / 'bin'))} {shlex.quote(str(remote_root / 'lib'))} "
|
||||||
|
f"{shlex.quote(str(remote_root / 'fixtures' / 'tal'))} {shlex.quote(str(remote_root / 'fixtures' / 'ta'))} "
|
||||||
|
f"{shlex.quote(str(remote_root / 'experiments'))}; "
|
||||||
|
f"df -h /data / > {shlex.quote(str(remote_root / 'df-before.txt'))} 2>&1 || true; "
|
||||||
|
f"free -h > {shlex.quote(str(remote_root / 'free-before.txt'))} 2>&1 || true"
|
||||||
|
)
|
||||||
|
ssh_script(ssh_target, preflight)
|
||||||
|
rsync_dir_to_remote(ssh_target, REPO_ROOT / "tests" / "fixtures" / "tal", remote_root / "fixtures" / "tal")
|
||||||
|
rsync_dir_to_remote(ssh_target, REPO_ROOT / "tests" / "fixtures" / "ta", remote_root / "fixtures" / "ta")
|
||||||
|
rsync_to_remote(ssh_target, REPO_ROOT / "target" / "release" / "rpki", remote_root / "bin" / "rpki")
|
||||||
|
rsync_to_remote(ssh_target, REPO_ROOT / "target" / "release" / "sequence_triage_ccr_cir", remote_root / "bin" / "sequence_triage_ccr_cir")
|
||||||
|
rsync_to_remote(ssh_target, REPO_ROOT / "target" / "release" / "cir_dump_reject_list", remote_root / "bin" / "cir_dump_reject_list")
|
||||||
|
if needs_rpki_client:
|
||||||
|
rpki_client_bin = rpki_client_bin_path()
|
||||||
|
rsync_to_remote(ssh_target, rpki_client_bin, remote_root / "bin" / "rpki-client")
|
||||||
|
rsync_to_remote(ssh_target, detect_libtls_path(rpki_client_bin), remote_root / "lib" / "libtls.so.28")
|
||||||
|
|
||||||
|
|
||||||
|
def prepare_remote_once(
|
||||||
|
prepared: dict[tuple[str, str], bool],
|
||||||
|
ssh_target: str,
|
||||||
|
remote_root: Path,
|
||||||
|
needs_rpki_client: bool,
|
||||||
|
) -> None:
|
||||||
|
key = (ssh_target, str(remote_root))
|
||||||
|
already_has_rpki_client = prepared.get(key)
|
||||||
|
if already_has_rpki_client is not None and (already_has_rpki_client or not needs_rpki_client):
|
||||||
|
return
|
||||||
|
prepare_remote(ssh_target, remote_root, needs_rpki_client)
|
||||||
|
prepared[key] = bool(already_has_rpki_client or needs_rpki_client)
|
||||||
|
|
||||||
|
|
||||||
|
def side_config(name: str) -> dict[str, Any]:
|
||||||
|
if name == "ours-standard":
|
||||||
|
return {"rpKind": "ours", "mode": "standard", "protocol": "rrdp+rsync", "rsyncScope": "module-root"}
|
||||||
|
if name == "ours-strict-all":
|
||||||
|
return {
|
||||||
|
"rpKind": "ours",
|
||||||
|
"mode": "strict-all",
|
||||||
|
"protocol": "rrdp+rsync",
|
||||||
|
"rsyncScope": "module-root",
|
||||||
|
"strictPolicies": "name,cms-der,signed-attrs",
|
||||||
|
}
|
||||||
|
if name == "rpki-client-standard":
|
||||||
|
return {"rpKind": "rpki-client", "mode": "standard", "protocol": "rrdp+rsync"}
|
||||||
|
raise SystemExit(f"unknown side config: {name}")
|
||||||
|
|
||||||
|
|
||||||
|
def parse_rirs(raw: str) -> list[str]:
|
||||||
|
rirs = [item.strip() for item in raw.split(",") if item.strip()]
|
||||||
|
if not rirs:
|
||||||
|
raise SystemExit("--rirs must contain at least one RIR")
|
||||||
|
seen: set[str] = set()
|
||||||
|
invalid: list[str] = []
|
||||||
|
duplicate: list[str] = []
|
||||||
|
for rir in rirs:
|
||||||
|
if rir not in DEFAULT_RIRS:
|
||||||
|
invalid.append(rir)
|
||||||
|
if rir in seen:
|
||||||
|
duplicate.append(rir)
|
||||||
|
seen.add(rir)
|
||||||
|
if invalid:
|
||||||
|
raise SystemExit(f"unsupported RIR(s): {','.join(invalid)}; valid values: {','.join(DEFAULT_RIRS)}")
|
||||||
|
if duplicate:
|
||||||
|
raise SystemExit(f"duplicate RIR(s): {','.join(duplicate)}")
|
||||||
|
return rirs
|
||||||
|
|
||||||
|
|
||||||
|
def build_remote_command(remote_root: Path, side_name: str, side: dict[str, Any], side_label: str, seq: int, rirs: list[str]) -> tuple[Path, str]:
|
||||||
|
run_dir = remote_root / "experiments" / "sequence" / side_label / f"run_{seq:04d}"
|
||||||
|
state_dir = remote_root / "experiments" / "sequence" / side_label / "state" / side["rpKind"]
|
||||||
|
sync_mode = "snapshot" if seq == 1 else "delta"
|
||||||
|
ensure = [f"mkdir -p {shlex.quote(str(run_dir))}", f"chmod 0777 {shlex.quote(str(run_dir))}"]
|
||||||
|
if side["rpKind"] == "ours":
|
||||||
|
if seq == 1:
|
||||||
|
ensure.append(f"rm -rf {shlex.quote(str(state_dir))}")
|
||||||
|
ensure.extend([
|
||||||
|
f"mkdir -p {shlex.quote(str(state_dir / 'work-db'))} {shlex.quote(str(state_dir / 'rsync-mirror'))}",
|
||||||
|
f"chmod -R 0777 {shlex.quote(str(state_dir.parent))}",
|
||||||
|
])
|
||||||
|
argv = [
|
||||||
|
str(remote_root / "bin" / "rpki"),
|
||||||
|
"--db", str(state_dir / "work-db"),
|
||||||
|
"--raw-store-db", str(state_dir / "raw-store.db"),
|
||||||
|
"--repo-bytes-db", str(state_dir / "repo-bytes.db"),
|
||||||
|
"--rsync-scope", side.get("rsyncScope", "module-root"),
|
||||||
|
]
|
||||||
|
if side.get("strictPolicies"):
|
||||||
|
argv.extend(["--strict", str(side["strictPolicies"])])
|
||||||
|
for rir in rirs:
|
||||||
|
argv.extend(["--tal-path", str(remote_root / "fixtures" / "tal" / fixture_name(rir, "tal"))])
|
||||||
|
argv.extend(["--ta-path", str(remote_root / "fixtures" / "ta" / fixture_name(rir, "ta"))])
|
||||||
|
argv.extend(["--report-json", str(run_dir / "report.json"), "--report-json-compact"])
|
||||||
|
argv.extend(["--ccr-out", str(run_dir / "result.ccr"), "--cir-enable", "--cir-out", str(run_dir / "result.cir")])
|
||||||
|
for rir in rirs:
|
||||||
|
argv.extend(["--cir-tal-uri", cir_tal_uri_for_rir(rir)])
|
||||||
|
argv.extend(["--vrps-csv-out", str(run_dir / "vrps.csv"), "--vaps-csv-out", str(run_dir / "vaps.csv")])
|
||||||
|
prefix = "env RPKI_PROGRESS_LOG=1 RPKI_PROGRESS_SLOW_SECS=10 /usr/bin/time"
|
||||||
|
else:
|
||||||
|
if seq == 1:
|
||||||
|
ensure.append(f"rm -rf {shlex.quote(str(state_dir))}")
|
||||||
|
ensure.extend([
|
||||||
|
f"mkdir -p {shlex.quote(str(state_dir / 'cache' / 'fixtures'))}",
|
||||||
|
f"touch {shlex.quote(str(state_dir / 'rpki-client-skiplist'))}",
|
||||||
|
f"chmod -R 0777 {shlex.quote(str(state_dir.parent))}",
|
||||||
|
])
|
||||||
|
for rir in rirs:
|
||||||
|
ensure.append(
|
||||||
|
f"cp -f {shlex.quote(str(remote_root / 'fixtures' / 'ta' / fixture_name(rir, 'ta')))} "
|
||||||
|
f"{shlex.quote(str(state_dir / 'cache' / 'fixtures' / fixture_name(rir, 'ta')))}"
|
||||||
|
)
|
||||||
|
argv = [str(remote_root / "bin" / "rpki-client"), "-vv", "-S", str(state_dir / "rpki-client-skiplist")]
|
||||||
|
for rir in rirs:
|
||||||
|
argv.extend(["-t", str(remote_root / "fixtures" / "tal" / fixture_name(rir, "tal"))])
|
||||||
|
argv.extend(["-T", f"{fixture_desc(rir)}:{state_dir / 'cache' / 'fixtures' / fixture_name(rir, 'ta')}"])
|
||||||
|
argv.extend(["-d", str(state_dir / "cache"), str(run_dir)])
|
||||||
|
prefix = f"env LD_LIBRARY_PATH={shlex.quote(str(remote_root / 'lib'))} /usr/bin/time"
|
||||||
|
command = (
|
||||||
|
"set -euo pipefail; "
|
||||||
|
+ "; ".join(ensure)
|
||||||
|
+ "; date -u +%Y-%m-%dT%H:%M:%SZ > " + shlex.quote(str(run_dir / "started-at.txt"))
|
||||||
|
+ "; set +e; "
|
||||||
|
+ prefix + " -v -o " + shlex.quote(str(run_dir / "process-time.txt"))
|
||||||
|
+ " -- " + shlex.join(argv)
|
||||||
|
+ " > " + shlex.quote(str(run_dir / "stdout.log"))
|
||||||
|
+ " 2> " + shlex.quote(str(run_dir / "stderr.log"))
|
||||||
|
+ "; ec=$?; set -e; printf '%s\n' \"$ec\" > " + shlex.quote(str(run_dir / "exit-code.txt"))
|
||||||
|
+ "; date -u +%Y-%m-%dT%H:%M:%SZ > " + shlex.quote(str(run_dir / "finished-at.txt"))
|
||||||
|
+ "; true"
|
||||||
|
)
|
||||||
|
if side["rpKind"] == "rpki-client":
|
||||||
|
command += (
|
||||||
|
f"; [ -f {shlex.quote(str(run_dir / 'json'))} ] && cp -f {shlex.quote(str(run_dir / 'json'))} {shlex.quote(str(run_dir / 'report.json'))} || true"
|
||||||
|
f"; [ -f {shlex.quote(str(run_dir / 'rpki.ccr'))} ] && cp -f {shlex.quote(str(run_dir / 'rpki.ccr'))} {shlex.quote(str(run_dir / 'result.ccr'))} || true"
|
||||||
|
f"; [ -f {shlex.quote(str(run_dir / 'rpki.cir'))} ] && cp -f {shlex.quote(str(run_dir / 'rpki.cir'))} {shlex.quote(str(run_dir / 'result.cir'))} || true"
|
||||||
|
)
|
||||||
|
command += (
|
||||||
|
f"; python3 - <<'REMOTE_META' {shlex.quote(str(run_dir))} {shlex.quote(side_name)} {shlex.quote(side_label)} {seq} {shlex.quote(sync_mode)}\n"
|
||||||
|
"import json, pathlib, sys\n"
|
||||||
|
"run_dir=pathlib.Path(sys.argv[1]); side_name=sys.argv[2]; side_label=sys.argv[3]; seq=int(sys.argv[4]); sync_mode=sys.argv[5]\n"
|
||||||
|
"def read(p):\n return p.read_text().strip() if p.exists() else None\n"
|
||||||
|
"def counts_from_report():\n"
|
||||||
|
" p=run_dir/'report.json'\n"
|
||||||
|
" if not p.exists():\n"
|
||||||
|
" return {}\n"
|
||||||
|
" try:\n"
|
||||||
|
" report=json.load(open(p))\n"
|
||||||
|
" except Exception:\n"
|
||||||
|
" return {}\n"
|
||||||
|
" meta=report.get('metadata') if isinstance(report, dict) else None\n"
|
||||||
|
" if isinstance(meta, dict):\n"
|
||||||
|
" return {'vrps': int(meta.get('vrps') or 0), 'vaps': int(meta.get('vaps') or meta.get('aspas') or 0), 'publicationPoints': int(meta.get('repositories') or 0), 'warnings': 0}\n"
|
||||||
|
" pps=report.get('publication_points', []) if isinstance(report, dict) else []\n"
|
||||||
|
" tree=report.get('tree', {}) if isinstance(report, dict) else {}\n"
|
||||||
|
" pp_warnings=sum(len(pp.get('warnings', [])) for pp in pps if isinstance(pp, dict)) if isinstance(pps, list) else 0\n"
|
||||||
|
" return {'vrps': len(report.get('vrps', [])), 'vaps': len(report.get('aspas', [])), 'publicationPoints': len(pps) if isinstance(pps, list) else 0, 'warnings': len(tree.get('warnings', [])) + pp_warnings if isinstance(tree, dict) else pp_warnings}\n"
|
||||||
|
"meta={'sideName':side_name,'sideLabel':side_label,'seq':seq,'syncMode':sync_mode,'startedAt':read(run_dir/'started-at.txt'),'finishedAt':read(run_dir/'finished-at.txt'),'exitCode':int(read(run_dir/'exit-code.txt') or '1'),'counts':counts_from_report()}\n"
|
||||||
|
"json.dump(meta, open(run_dir/'remote-run-meta.json','w'), indent=2, sort_keys=True); print()\n"
|
||||||
|
"REMOTE_META"
|
||||||
|
)
|
||||||
|
return run_dir, command
|
||||||
|
|
||||||
|
|
||||||
|
def run_remote_sample(ssh_target: str, remote_root: Path, side_name: str, side: dict[str, Any], side_label: str, seq: int, rirs: list[str]) -> Path:
|
||||||
|
run_dir, command = build_remote_command(remote_root, side_name, side, side_label, seq, rirs)
|
||||||
|
ssh_script(ssh_target, command)
|
||||||
|
return run_dir
|
||||||
|
|
||||||
|
|
||||||
|
def append_remote_sequence_item(
|
||||||
|
ssh_target: str,
|
||||||
|
remote_root: Path,
|
||||||
|
side_name: str,
|
||||||
|
side_label: str,
|
||||||
|
seq: int,
|
||||||
|
run_dir: Path,
|
||||||
|
schedule_mode: str,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
remote_exp_root = remote_root / "experiments" / "sequence"
|
||||||
|
seq_path = remote_exp_root / ("left-sequence.jsonl" if side_label == "A" else "right-sequence.jsonl")
|
||||||
|
side_value = "left" if side_label == "A" else "right"
|
||||||
|
script = f"""
|
||||||
|
set -euo pipefail
|
||||||
|
python3 - <<'REMOTE_SEQUENCE_ITEM' {shlex.quote(str(remote_exp_root))} {shlex.quote(str(seq_path))} {shlex.quote(str(run_dir))} {shlex.quote(str(remote_root / 'bin' / 'cir_dump_reject_list'))} {shlex.quote(side_name)} {shlex.quote(side_label)} {seq} {shlex.quote(side_value)} {shlex.quote(schedule_mode)}
|
||||||
|
import hashlib, json, os, pathlib, subprocess, sys
|
||||||
|
exp_root=pathlib.Path(sys.argv[1])
|
||||||
|
seq_path=pathlib.Path(sys.argv[2])
|
||||||
|
run_dir=pathlib.Path(sys.argv[3])
|
||||||
|
cir_dump=pathlib.Path(sys.argv[4])
|
||||||
|
side_name=sys.argv[5]
|
||||||
|
side_label=sys.argv[6]
|
||||||
|
seq=int(sys.argv[7])
|
||||||
|
side_value=sys.argv[8]
|
||||||
|
schedule_mode=sys.argv[9]
|
||||||
|
def read(p):
|
||||||
|
return p.read_text().strip() if p.exists() else None
|
||||||
|
def sha256_file(p):
|
||||||
|
h=hashlib.sha256()
|
||||||
|
with open(p, 'rb') as f:
|
||||||
|
for chunk in iter(lambda: f.read(1024 * 1024), b''):
|
||||||
|
h.update(chunk)
|
||||||
|
return h.hexdigest()
|
||||||
|
def parse_elapsed_to_ms(raw):
|
||||||
|
raw=raw.strip()
|
||||||
|
if not raw:
|
||||||
|
return 0
|
||||||
|
if '-' in raw:
|
||||||
|
days, raw=raw.split('-', 1)
|
||||||
|
else:
|
||||||
|
days='0'
|
||||||
|
parts=raw.split(':')
|
||||||
|
if len(parts) == 3:
|
||||||
|
hours, minutes, seconds=parts
|
||||||
|
elif len(parts) == 2:
|
||||||
|
hours='0'; minutes, seconds=parts
|
||||||
|
else:
|
||||||
|
hours='0'; minutes='0'; seconds=parts[0]
|
||||||
|
return int(round((int(days)*86400 + int(hours)*3600 + int(minutes)*60 + float(seconds))*1000))
|
||||||
|
def parse_time_file(p):
|
||||||
|
data={{}}
|
||||||
|
if not p.exists():
|
||||||
|
return data
|
||||||
|
for line in p.read_text(errors='replace').splitlines():
|
||||||
|
if 'Elapsed (wall clock) time' in line:
|
||||||
|
elapsed=line.rsplit('):', 1)[1] if '):' in line else line.rsplit(':', 1)[1]
|
||||||
|
data['wallMs']=parse_elapsed_to_ms(elapsed)
|
||||||
|
elif 'Maximum resident set size' in line:
|
||||||
|
try:
|
||||||
|
data['maxRssKb']=int(line.rsplit(':', 1)[1].strip())
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
return data
|
||||||
|
def cir_counts(cir):
|
||||||
|
values={{}}
|
||||||
|
if not cir.exists():
|
||||||
|
return {{}}
|
||||||
|
result=subprocess.run([str(cir_dump), '--cir', str(cir), '--limit', '0'], text=True, capture_output=True, check=True)
|
||||||
|
for line in result.stdout.splitlines():
|
||||||
|
if '=' not in line:
|
||||||
|
continue
|
||||||
|
key, value=line.split('=', 1)
|
||||||
|
if key in ('object_count', 'trust_anchor_count', 'reject_count'):
|
||||||
|
values[key]=int(value)
|
||||||
|
return {{'cirObjectCount': values.get('object_count', 0), 'cirTrustAnchorCount': values.get('trust_anchor_count', 0), 'cirRejectCount': values.get('reject_count', 0)}}
|
||||||
|
meta=json.load(open(run_dir/'remote-run-meta.json'))
|
||||||
|
time_info=parse_time_file(run_dir/'process-time.txt')
|
||||||
|
counts=dict(meta.get('counts') or {{}})
|
||||||
|
ccr=run_dir/'result.ccr'
|
||||||
|
cir=run_dir/'result.cir'
|
||||||
|
if meta.get('exitCode') != 0:
|
||||||
|
raise SystemExit(f"remote run failed: exitCode={{meta.get('exitCode')}} runDir={{run_dir}}")
|
||||||
|
missing=[str(path) for path in (ccr, cir) if not path.exists()]
|
||||||
|
if missing:
|
||||||
|
raise SystemExit(f"remote run missing required artifact(s): {{missing}}")
|
||||||
|
counts.update(cir_counts(cir))
|
||||||
|
item={{
|
||||||
|
'schemaVersion': 1,
|
||||||
|
'rpId': side_name,
|
||||||
|
'side': side_value,
|
||||||
|
'seq': seq,
|
||||||
|
'runId': f'{{side_label}}-{{seq:04d}}',
|
||||||
|
'syncMode': 'snapshot' if seq == 1 else 'delta',
|
||||||
|
'status': 'success' if meta.get('exitCode') == 0 else 'failed',
|
||||||
|
'startTime': meta.get('startedAt'),
|
||||||
|
'finishTime': meta.get('finishedAt'),
|
||||||
|
'validationTime': None,
|
||||||
|
'ccrPath': os.path.relpath(ccr, exp_root),
|
||||||
|
'cirPath': os.path.relpath(cir, exp_root),
|
||||||
|
'ccrSha256': sha256_file(ccr) if ccr.exists() else None,
|
||||||
|
'cirSha256': sha256_file(cir) if cir.exists() else None,
|
||||||
|
'wallMs': time_info.get('wallMs'),
|
||||||
|
'maxRssKb': time_info.get('maxRssKb'),
|
||||||
|
'vrps': counts.get('vrps'),
|
||||||
|
'vaps': counts.get('vaps'),
|
||||||
|
'publicationPoints': counts.get('publicationPoints'),
|
||||||
|
'cirObjectCount': counts.get('cirObjectCount'),
|
||||||
|
'cirRejectCount': counts.get('cirRejectCount'),
|
||||||
|
'cirTrustAnchorCount': counts.get('cirTrustAnchorCount'),
|
||||||
|
'scheduleMode': schedule_mode,
|
||||||
|
}}
|
||||||
|
seq_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
with open(seq_path, 'a', encoding='utf-8') as handle:
|
||||||
|
handle.write(json.dumps(item, sort_keys=True, ensure_ascii=False) + '\\n')
|
||||||
|
print(json.dumps(item, sort_keys=True, ensure_ascii=False))
|
||||||
|
REMOTE_SEQUENCE_ITEM
|
||||||
|
"""
|
||||||
|
result = ssh_script(ssh_target, script, capture=True)
|
||||||
|
lines = [line for line in result.stdout.splitlines() if line.strip()]
|
||||||
|
if not lines:
|
||||||
|
raise SystemExit("remote sequence item append produced no output")
|
||||||
|
return json.loads(lines[-1])
|
||||||
|
|
||||||
|
|
||||||
|
def cleanup_remote_run_nonessential(ssh_target: str, run_dir: Path) -> None:
|
||||||
|
keep = {
|
||||||
|
"result.ccr",
|
||||||
|
"result.cir",
|
||||||
|
"process-time.txt",
|
||||||
|
"remote-run-meta.json",
|
||||||
|
"exit-code.txt",
|
||||||
|
"started-at.txt",
|
||||||
|
"finished-at.txt",
|
||||||
|
"stdout.log",
|
||||||
|
"stderr.log",
|
||||||
|
}
|
||||||
|
keep_json = json.dumps(sorted(keep), ensure_ascii=False)
|
||||||
|
script = f"""
|
||||||
|
set -euo pipefail
|
||||||
|
python3 - <<'REMOTE_CLEAN' {shlex.quote(str(run_dir))} {shlex.quote(keep_json)}
|
||||||
|
import json, pathlib, sys
|
||||||
|
run_dir=pathlib.Path(sys.argv[1])
|
||||||
|
keep=set(json.loads(sys.argv[2]))
|
||||||
|
removed=0
|
||||||
|
if run_dir.exists():
|
||||||
|
for path in run_dir.iterdir():
|
||||||
|
if path.name in keep or path.is_dir():
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
removed += path.stat().st_size
|
||||||
|
path.unlink()
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
print(f'cleaned_nonessential_bytes={{removed}}')
|
||||||
|
REMOTE_CLEAN
|
||||||
|
"""
|
||||||
|
ssh_script(ssh_target, script)
|
||||||
|
|
||||||
|
|
||||||
|
def cir_counts(cir_path: Path) -> dict[str, int]:
|
||||||
|
result = run_local([str(REPO_ROOT / "target" / "release" / "cir_dump_reject_list"), "--cir", str(cir_path), "--limit", "0"], capture=True)
|
||||||
|
values: dict[str, int] = {}
|
||||||
|
for line in result.stdout.splitlines():
|
||||||
|
if "=" not in line:
|
||||||
|
continue
|
||||||
|
key, value = line.split("=", 1)
|
||||||
|
if key in {"object_count", "trust_anchor_count", "reject_count"}:
|
||||||
|
values[key] = int(value)
|
||||||
|
return {
|
||||||
|
"cirObjectCount": values.get("object_count", 0),
|
||||||
|
"cirTrustAnchorCount": values.get("trust_anchor_count", 0),
|
||||||
|
"cirRejectCount": values.get("reject_count", 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def report_counts(path: Path, rp_kind: str) -> dict[str, int]:
|
||||||
|
if not path.is_file():
|
||||||
|
return {}
|
||||||
|
report = load_json(path)
|
||||||
|
if rp_kind == "rpki-client":
|
||||||
|
meta = report.get("metadata", {})
|
||||||
|
return {
|
||||||
|
"vrps": int(meta.get("vrps", 0)),
|
||||||
|
"vaps": int(meta.get("vaps", 0) or meta.get("aspas", 0) or 0),
|
||||||
|
"publicationPoints": int(meta.get("repositories", 0)),
|
||||||
|
"warnings": 0,
|
||||||
|
}
|
||||||
|
pps = report.get("publication_points", [])
|
||||||
|
tree = report.get("tree", {})
|
||||||
|
return {
|
||||||
|
"vrps": len(report.get("vrps", [])),
|
||||||
|
"vaps": len(report.get("aspas", [])),
|
||||||
|
"publicationPoints": len(pps),
|
||||||
|
"warnings": len(tree.get("warnings", [])) + sum(len(pp.get("warnings", [])) for pp in pps if isinstance(pp, dict)),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def build_sequence_item(local_root: Path, side_name: str, side_label: str, side: dict[str, Any], seq: int, run_dir: Path) -> dict[str, Any]:
|
||||||
|
ccr = run_dir / "result.ccr"
|
||||||
|
cir = run_dir / "result.cir"
|
||||||
|
meta = load_json(run_dir / "remote-run-meta.json")
|
||||||
|
time_info = parse_time_file(run_dir / "process-time.txt")
|
||||||
|
counts = dict(meta.get("counts") or {})
|
||||||
|
if not counts:
|
||||||
|
counts = report_counts(run_dir / "report.json", side["rpKind"])
|
||||||
|
counts.update(cir_counts(cir))
|
||||||
|
return {
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"rpId": side_name,
|
||||||
|
"side": "left" if side_label == "A" else "right",
|
||||||
|
"seq": seq,
|
||||||
|
"runId": f"{side_label}-{seq:04d}",
|
||||||
|
"syncMode": "snapshot" if seq == 1 else "delta",
|
||||||
|
"status": "success" if meta.get("exitCode") == 0 else "failed",
|
||||||
|
"startTime": meta.get("startedAt"),
|
||||||
|
"finishTime": meta.get("finishedAt"),
|
||||||
|
"validationTime": None,
|
||||||
|
"ccrPath": ccr.relative_to(local_root).as_posix(),
|
||||||
|
"cirPath": cir.relative_to(local_root).as_posix(),
|
||||||
|
"ccrSha256": sha256_file(ccr),
|
||||||
|
"cirSha256": sha256_file(cir),
|
||||||
|
"wallMs": time_info.get("wallMs"),
|
||||||
|
"maxRssKb": time_info.get("maxRssKb"),
|
||||||
|
"vrps": counts.get("vrps"),
|
||||||
|
"vaps": counts.get("vaps"),
|
||||||
|
"publicationPoints": counts.get("publicationPoints"),
|
||||||
|
"cirObjectCount": counts.get("cirObjectCount"),
|
||||||
|
"cirRejectCount": counts.get("cirRejectCount"),
|
||||||
|
"cirTrustAnchorCount": counts.get("cirTrustAnchorCount"),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def run_sequence_triage(local_exp_root: Path, args: argparse.Namespace) -> None:
|
||||||
|
compare_dir = local_exp_root / "sequence-triage"
|
||||||
|
run_local([
|
||||||
|
str(REPO_ROOT / "target" / "release" / "sequence_triage_ccr_cir"),
|
||||||
|
"--left-sequence", str(local_exp_root / "left-sequence.jsonl"),
|
||||||
|
"--right-sequence", str(local_exp_root / "right-sequence.jsonl"),
|
||||||
|
"--out-dir", str(compare_dir),
|
||||||
|
"--align-window-runs", str(args.align_window_runs),
|
||||||
|
"--align-window-secs", str(args.align_window_secs),
|
||||||
|
"--sample-limit", str(args.sample_limit),
|
||||||
|
"--timeline-sample-limit", str(args.timeline_sample_limit),
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
def run_sequence_triage_remote(ssh_target: str, remote_root: Path, args: argparse.Namespace) -> None:
|
||||||
|
remote_exp_root = remote_root / "experiments" / "sequence"
|
||||||
|
compare_dir = remote_exp_root / "sequence-triage"
|
||||||
|
time_path = remote_exp_root / "sequence-triage-time.txt"
|
||||||
|
command = " ".join(
|
||||||
|
shlex.quote(item)
|
||||||
|
for item in [
|
||||||
|
str(remote_root / "bin" / "sequence_triage_ccr_cir"),
|
||||||
|
"--left-sequence", str(remote_exp_root / "left-sequence.jsonl"),
|
||||||
|
"--right-sequence", str(remote_exp_root / "right-sequence.jsonl"),
|
||||||
|
"--out-dir", str(compare_dir),
|
||||||
|
"--align-window-runs", str(args.align_window_runs),
|
||||||
|
"--align-window-secs", str(args.align_window_secs),
|
||||||
|
"--sample-limit", str(args.sample_limit),
|
||||||
|
"--timeline-sample-limit", str(args.timeline_sample_limit),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
ssh_script(
|
||||||
|
ssh_target,
|
||||||
|
"set -euo pipefail; "
|
||||||
|
f"rm -rf {shlex.quote(str(compare_dir))} {shlex.quote(str(time_path))}; "
|
||||||
|
f"/usr/bin/time -v -o {shlex.quote(str(time_path))} -- {command}",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def sync_side_to_analysis_remote(
|
||||||
|
source_ssh_target: str,
|
||||||
|
source_remote_root: Path,
|
||||||
|
analysis_ssh_target: str,
|
||||||
|
analysis_remote_root: Path,
|
||||||
|
side_label: str,
|
||||||
|
) -> None:
|
||||||
|
source_exp_root = source_remote_root / "experiments" / "sequence"
|
||||||
|
analysis_exp_root = analysis_remote_root / "experiments" / "sequence"
|
||||||
|
sequence_name = "left-sequence.jsonl" if side_label == "A" else "right-sequence.jsonl"
|
||||||
|
if same_remote_location(source_ssh_target, source_exp_root, analysis_ssh_target, analysis_exp_root):
|
||||||
|
return
|
||||||
|
side_dir = source_exp_root / side_label
|
||||||
|
script = (
|
||||||
|
"set -euo pipefail; "
|
||||||
|
f"ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new {shlex.quote(analysis_ssh_target)} "
|
||||||
|
f"{shlex.quote('mkdir -p ' + shlex.quote(str(analysis_exp_root / side_label)))}; "
|
||||||
|
f"rsync -az --delete {shlex.quote(str(side_dir))}/ {shlex.quote(analysis_ssh_target)}:{shlex.quote(str(analysis_exp_root / side_label))}/; "
|
||||||
|
f"rsync -az {shlex.quote(str(source_exp_root / sequence_name))} "
|
||||||
|
f"{shlex.quote(analysis_ssh_target)}:{shlex.quote(str(analysis_exp_root / sequence_name))}"
|
||||||
|
)
|
||||||
|
ssh_script(source_ssh_target, script)
|
||||||
|
|
||||||
|
|
||||||
|
def run_side_sequence(
|
||||||
|
args: argparse.Namespace,
|
||||||
|
ssh_target: str,
|
||||||
|
remote_root: Path,
|
||||||
|
local_exp_root: Path,
|
||||||
|
side_label: str,
|
||||||
|
side_name: str,
|
||||||
|
side: dict[str, Any],
|
||||||
|
seq_path: Path,
|
||||||
|
rirs: list[str],
|
||||||
|
) -> list[dict[str, Any]]:
|
||||||
|
side_progress: list[dict[str, Any]] = []
|
||||||
|
for seq in range(1, args.samples_per_side + 1):
|
||||||
|
side_progress.append(
|
||||||
|
run_one_side_sample(args, ssh_target, remote_root, local_exp_root, side_label, side_name, side, seq_path, seq, rirs)
|
||||||
|
)
|
||||||
|
return side_progress
|
||||||
|
|
||||||
|
|
||||||
|
def run_one_side_sample(
|
||||||
|
args: argparse.Namespace,
|
||||||
|
ssh_target: str,
|
||||||
|
remote_root: Path,
|
||||||
|
local_exp_root: Path,
|
||||||
|
side_label: str,
|
||||||
|
side_name: str,
|
||||||
|
side: dict[str, Any],
|
||||||
|
seq_path: Path,
|
||||||
|
seq: int,
|
||||||
|
rirs: list[str],
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
rir_label = ",".join(rirs)
|
||||||
|
print(
|
||||||
|
f"[run] {side_label} {side_name} seq={seq} rirs={rir_label} schedule={args.schedule_mode}",
|
||||||
|
flush=True,
|
||||||
|
)
|
||||||
|
remote_run_dir = run_remote_sample(ssh_target, remote_root, side_name, side, side_label, seq, rirs)
|
||||||
|
if args.remote_triage:
|
||||||
|
item = append_remote_sequence_item(
|
||||||
|
ssh_target,
|
||||||
|
remote_root,
|
||||||
|
side_name,
|
||||||
|
side_label,
|
||||||
|
seq,
|
||||||
|
remote_run_dir,
|
||||||
|
args.schedule_mode,
|
||||||
|
)
|
||||||
|
if args.cleanup_run_nonessential:
|
||||||
|
cleanup_remote_run_nonessential(ssh_target, remote_run_dir)
|
||||||
|
else:
|
||||||
|
local_run_dir = local_exp_root / side_label / f"run_{seq:04d}"
|
||||||
|
rsync_run_artifacts_from_remote(ssh_target, remote_run_dir, local_run_dir)
|
||||||
|
item = build_sequence_item(local_exp_root, side_name, side_label, side, seq, local_run_dir)
|
||||||
|
item["scheduleMode"] = args.schedule_mode
|
||||||
|
append_jsonl(seq_path, item)
|
||||||
|
print(
|
||||||
|
f"[done] {side_label} seq={seq} wallMs={item.get('wallMs')} vrps={item.get('vrps')} vaps={item.get('vaps')} objects={item.get('cirObjectCount')} rejects={item.get('cirRejectCount')}",
|
||||||
|
flush=True,
|
||||||
|
)
|
||||||
|
return item
|
||||||
|
|
||||||
|
|
||||||
|
def run_experiment(args: argparse.Namespace) -> None:
|
||||||
|
if not args.skip_build:
|
||||||
|
build_tool_binaries()
|
||||||
|
rirs = parse_rirs(args.rirs)
|
||||||
|
left = side_config(args.left)
|
||||||
|
right = side_config(args.right)
|
||||||
|
run_root = Path(args.run_root).resolve()
|
||||||
|
remote_root = Path(args.remote_root)
|
||||||
|
left_ssh_target = args.left_ssh_target or args.ssh_target
|
||||||
|
right_ssh_target = args.right_ssh_target or args.ssh_target
|
||||||
|
analysis_ssh_target = args.analysis_ssh_target or left_ssh_target
|
||||||
|
left_remote_root = Path(args.left_remote_root or args.remote_root)
|
||||||
|
right_remote_root = Path(args.right_remote_root or args.remote_root)
|
||||||
|
analysis_remote_root = Path(args.analysis_remote_root or args.remote_root)
|
||||||
|
run_root.mkdir(parents=True, exist_ok=True)
|
||||||
|
write_json(run_root / "experiment-config.json", {
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"generatedAtUtc": utc_stamp(),
|
||||||
|
"left": args.left,
|
||||||
|
"right": args.right,
|
||||||
|
"samplesPerSide": args.samples_per_side,
|
||||||
|
"rirs": rirs,
|
||||||
|
"scheduleMode": args.schedule_mode,
|
||||||
|
"remoteRoot": str(remote_root),
|
||||||
|
"leftSshTarget": left_ssh_target,
|
||||||
|
"rightSshTarget": right_ssh_target,
|
||||||
|
"analysisSshTarget": analysis_ssh_target,
|
||||||
|
"leftRemoteRoot": str(left_remote_root),
|
||||||
|
"rightRemoteRoot": str(right_remote_root),
|
||||||
|
"analysisRemoteRoot": str(analysis_remote_root),
|
||||||
|
"sshTarget": args.ssh_target,
|
||||||
|
})
|
||||||
|
if args.dry_run:
|
||||||
|
print(json.dumps(load_json(run_root / "experiment-config.json"), indent=2, ensure_ascii=False))
|
||||||
|
return
|
||||||
|
if args.triage_only:
|
||||||
|
run_sequence_triage(run_root / "experiments" / "sequence", args)
|
||||||
|
print(json.dumps({
|
||||||
|
"runRoot": str(run_root),
|
||||||
|
"triage": str(run_root / "experiments" / "sequence" / "sequence-triage" / "sequence-triage.json"),
|
||||||
|
}, indent=2))
|
||||||
|
return
|
||||||
|
prepared_remotes: dict[tuple[str, str], bool] = {}
|
||||||
|
prepare_remote_once(prepared_remotes, left_ssh_target, left_remote_root, needs_rpki_client=(left["rpKind"] == "rpki-client"))
|
||||||
|
prepare_remote_once(prepared_remotes, right_ssh_target, right_remote_root, needs_rpki_client=(right["rpKind"] == "rpki-client"))
|
||||||
|
prepare_remote_once(prepared_remotes, analysis_ssh_target, analysis_remote_root, needs_rpki_client=False)
|
||||||
|
local_exp_root = run_root / "experiments" / "sequence"
|
||||||
|
left_seq_path = local_exp_root / "left-sequence.jsonl"
|
||||||
|
right_seq_path = local_exp_root / "right-sequence.jsonl"
|
||||||
|
left_seq_path.unlink(missing_ok=True)
|
||||||
|
right_seq_path.unlink(missing_ok=True)
|
||||||
|
progress: list[dict[str, Any]] = []
|
||||||
|
if args.schedule_mode == "interleaved":
|
||||||
|
for seq in range(1, args.samples_per_side + 1):
|
||||||
|
for side_label, ssh_target, side_remote_root, side_name, side, seq_path in [
|
||||||
|
("A", left_ssh_target, left_remote_root, args.left, left, left_seq_path),
|
||||||
|
("B", right_ssh_target, right_remote_root, args.right, right, right_seq_path),
|
||||||
|
]:
|
||||||
|
progress.append(
|
||||||
|
run_one_side_sample(
|
||||||
|
args,
|
||||||
|
ssh_target,
|
||||||
|
side_remote_root,
|
||||||
|
local_exp_root,
|
||||||
|
side_label,
|
||||||
|
side_name,
|
||||||
|
side,
|
||||||
|
seq_path,
|
||||||
|
seq,
|
||||||
|
rirs,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
with ThreadPoolExecutor(max_workers=2) as executor:
|
||||||
|
futures = [
|
||||||
|
executor.submit(run_side_sequence, args, left_ssh_target, left_remote_root, local_exp_root, "A", args.left, left, left_seq_path, rirs),
|
||||||
|
executor.submit(run_side_sequence, args, right_ssh_target, right_remote_root, local_exp_root, "B", args.right, right, right_seq_path, rirs),
|
||||||
|
]
|
||||||
|
for future in as_completed(futures):
|
||||||
|
progress.extend(future.result())
|
||||||
|
progress.sort(key=lambda item: (str(item.get("side")), int(item.get("seq") or 0)))
|
||||||
|
write_json(local_exp_root / "run-progress.json", progress)
|
||||||
|
if args.remote_triage:
|
||||||
|
sync_side_to_analysis_remote(left_ssh_target, left_remote_root, analysis_ssh_target, analysis_remote_root, "A")
|
||||||
|
sync_side_to_analysis_remote(right_ssh_target, right_remote_root, analysis_ssh_target, analysis_remote_root, "B")
|
||||||
|
remote_exp_root = analysis_remote_root / "experiments" / "sequence"
|
||||||
|
remote_progress = json.dumps(progress, sort_keys=True, ensure_ascii=False)
|
||||||
|
ssh_script(
|
||||||
|
analysis_ssh_target,
|
||||||
|
"set -euo pipefail; "
|
||||||
|
f"cat > {shlex.quote(str(remote_exp_root / 'run-progress.json'))} <<'REMOTE_PROGRESS_JSON'\n"
|
||||||
|
f"{remote_progress}\n"
|
||||||
|
"REMOTE_PROGRESS_JSON\n",
|
||||||
|
)
|
||||||
|
run_sequence_triage_remote(analysis_ssh_target, analysis_remote_root, args)
|
||||||
|
if args.fetch_remote_analysis:
|
||||||
|
rsync_remote_analysis_from_remote(analysis_ssh_target, remote_exp_root, local_exp_root)
|
||||||
|
else:
|
||||||
|
run_sequence_triage(local_exp_root, args)
|
||||||
|
ssh_script(analysis_ssh_target, f"df -h /data / > {shlex.quote(str(analysis_remote_root / 'df-after.txt'))} 2>&1 || true; free -h > {shlex.quote(str(analysis_remote_root / 'free-after.txt'))} 2>&1 || true")
|
||||||
|
compare_dir = local_exp_root / "sequence-triage"
|
||||||
|
remote_compare_dir = analysis_remote_root / "experiments" / "sequence" / "sequence-triage"
|
||||||
|
print(json.dumps({
|
||||||
|
"runRoot": str(run_root),
|
||||||
|
"remoteRoot": str(remote_root),
|
||||||
|
"leftRemoteRoot": str(left_remote_root),
|
||||||
|
"rightRemoteRoot": str(right_remote_root),
|
||||||
|
"analysisRemoteRoot": str(analysis_remote_root),
|
||||||
|
"triage": str(compare_dir / "sequence-triage.json") if not args.remote_triage or args.fetch_remote_analysis else None,
|
||||||
|
"remoteTriage": str(remote_compare_dir / "sequence-triage.json") if args.remote_triage else None,
|
||||||
|
}, indent=2))
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
parser = argparse.ArgumentParser(description="Feature #043 all5 sequence triage experiment driver")
|
||||||
|
parser.add_argument("--run-root", required=True)
|
||||||
|
parser.add_argument("--remote-root", required=True)
|
||||||
|
parser.add_argument("--ssh-target", default=os.environ.get("SSH_TARGET", "root@47.251.56.108"))
|
||||||
|
parser.add_argument("--left-ssh-target")
|
||||||
|
parser.add_argument("--right-ssh-target")
|
||||||
|
parser.add_argument("--analysis-ssh-target")
|
||||||
|
parser.add_argument("--left-remote-root")
|
||||||
|
parser.add_argument("--right-remote-root")
|
||||||
|
parser.add_argument("--analysis-remote-root")
|
||||||
|
parser.add_argument("--left", default="ours-standard")
|
||||||
|
parser.add_argument("--right", default="rpki-client-standard")
|
||||||
|
parser.add_argument("--samples-per-side", type=int, default=3)
|
||||||
|
parser.add_argument("--rirs", default=",".join(DEFAULT_RIRS))
|
||||||
|
parser.add_argument("--schedule-mode", choices=["interleaved", "parallel"], default="interleaved")
|
||||||
|
parser.add_argument("--align-window-runs", type=int, default=2)
|
||||||
|
parser.add_argument("--align-window-secs", type=int, default=1800)
|
||||||
|
parser.add_argument("--sample-limit", type=int, default=200)
|
||||||
|
parser.add_argument("--timeline-sample-limit", type=int, default=0)
|
||||||
|
parser.add_argument("--dry-run", action="store_true")
|
||||||
|
parser.add_argument("--skip-build", action="store_true", help="reuse existing release binaries")
|
||||||
|
parser.add_argument("--triage-only", action="store_true", help="only rerun local sequence triage for an existing run root")
|
||||||
|
parser.add_argument("--remote-triage", action="store_true", help="keep CIR/CCR on remote, write sequence JSONL remotely, and run triage on remote")
|
||||||
|
parser.add_argument("--fetch-remote-analysis", action="store_true", help="when --remote-triage is set, fetch only small sequence/triage JSON outputs; never fetch CIR/CCR")
|
||||||
|
parser.add_argument("--cleanup-run-nonessential", action="store_true", help="after each successful remote sequence item, remove report/log/CSV files and keep only CIR/CCR/timing/meta")
|
||||||
|
args = parser.parse_args()
|
||||||
|
if args.samples_per_side < 2:
|
||||||
|
raise SystemExit("--samples-per-side must be >= 2")
|
||||||
|
run_experiment(args)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
265
scripts/inter_rp/control_remote200_rp.sh
Executable file
265
scripts/inter_rp/control_remote200_rp.sh
Executable file
@@ -0,0 +1,265 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
REMOTE_HOST="${REMOTE_HOST:-root@43.110.128.200}"
|
||||||
|
REMOTE_CONFIG="${REMOTE_CONFIG:-/root/inter-rp-runners/inter-rp.env}"
|
||||||
|
REMOTE_SCRIPTS_DIR="${REMOTE_SCRIPTS_DIR:-/root/inter-rp-runners/scripts}"
|
||||||
|
DEFAULT_RETAIN_RUNS="${RETAIN_RUNS:-20}"
|
||||||
|
DEFAULT_RSS_SAMPLE_MS="${RSS_SAMPLE_MS:-500}"
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'USAGE'
|
||||||
|
Usage:
|
||||||
|
control_remote200_rp.sh status [rpki-client|routinator|all]
|
||||||
|
control_remote200_rp.sh start <rpki-client|routinator>
|
||||||
|
control_remote200_rp.sh stop <rpki-client|routinator|all>
|
||||||
|
control_remote200_rp.sh restart <rpki-client|routinator>
|
||||||
|
|
||||||
|
Environment overrides:
|
||||||
|
REMOTE_HOST=root@43.110.128.200
|
||||||
|
REMOTE_CONFIG=/root/inter-rp-runners/inter-rp.env
|
||||||
|
REMOTE_SCRIPTS_DIR=/root/inter-rp-runners/scripts
|
||||||
|
RETAIN_RUNS=20
|
||||||
|
RSS_SAMPLE_MS=500
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
- start uses the remote run_single_rp_with_rss.sh loop and runs until stopped.
|
||||||
|
- stop only kills the selected RP loop branch and its current child processes.
|
||||||
|
- rpki-client and routinator are managed independently.
|
||||||
|
USAGE
|
||||||
|
}
|
||||||
|
|
||||||
|
ACTION="${1:-status}"
|
||||||
|
RP="${2:-all}"
|
||||||
|
|
||||||
|
case "$ACTION" in
|
||||||
|
status|start|stop|restart) ;;
|
||||||
|
-h|--help|help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "unknown action: $ACTION" >&2
|
||||||
|
usage >&2
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
case "$RP" in
|
||||||
|
rpki-client|routinator|all) ;;
|
||||||
|
*)
|
||||||
|
echo "unknown RP: $RP" >&2
|
||||||
|
usage >&2
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [[ "$ACTION" == "start" || "$ACTION" == "restart" ]] && [[ "$RP" == "all" ]]; then
|
||||||
|
echo "start/restart requires one RP: rpki-client or routinator" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
ssh "$REMOTE_HOST" \
|
||||||
|
"REMOTE_CONFIG='$REMOTE_CONFIG' REMOTE_SCRIPTS_DIR='$REMOTE_SCRIPTS_DIR' RETAIN_RUNS='$DEFAULT_RETAIN_RUNS' RSS_SAMPLE_MS='$DEFAULT_RSS_SAMPLE_MS' ACTION='$ACTION' RP='$RP' bash -s" <<'REMOTE'
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
load_config() {
|
||||||
|
if [[ -f "$REMOTE_CONFIG" ]]; then
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "$REMOTE_CONFIG"
|
||||||
|
fi
|
||||||
|
INTER_RP_ROOT="${INTER_RP_ROOT:-/var/lib/inter-rp-runners}"
|
||||||
|
RETAIN_RUNS="${RETAIN_RUNS:-20}"
|
||||||
|
RSS_SAMPLE_MS="${RSS_SAMPLE_MS:-500}"
|
||||||
|
ROUTINATOR_RUN_COMMAND="${ROUTINATOR_RUN_COMMAND:-/root/inter-rp-runners/scripts/run_routinator_once.sh}"
|
||||||
|
RPKI_CLIENT_RUN_COMMAND="${RPKI_CLIENT_RUN_COMMAND:-/root/inter-rp-runners/scripts/run_rpki_client_official_98_once.sh}"
|
||||||
|
}
|
||||||
|
|
||||||
|
rp_root_name() {
|
||||||
|
case "$1" in
|
||||||
|
routinator) echo "routinator" ;;
|
||||||
|
rpki-client) echo "rpki-client" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
rp_pid_file() {
|
||||||
|
echo "$INTER_RP_ROOT/$(rp_root_name "$1").loop.pid"
|
||||||
|
}
|
||||||
|
|
||||||
|
rp_log_file() {
|
||||||
|
echo "$INTER_RP_ROOT/$(rp_root_name "$1").loop.log"
|
||||||
|
}
|
||||||
|
|
||||||
|
rp_command() {
|
||||||
|
case "$1" in
|
||||||
|
routinator) echo "$ROUTINATOR_RUN_COMMAND" ;;
|
||||||
|
rpki-client) echo "$RPKI_CLIENT_RUN_COMMAND" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
rp_binary_pattern() {
|
||||||
|
case "$1" in
|
||||||
|
routinator) echo "/root/inter-rp-runners/bin/routinator|[[:space:]]routinator[[:space:]]" ;;
|
||||||
|
rpki-client) echo "rpki-client-official-9\\.8|[[:space:]]rpki-client[[:space:]]" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
loop_pattern() {
|
||||||
|
local rp="$1"
|
||||||
|
echo "run_single_rp_with_rss\\.sh --rp $rp "
|
||||||
|
}
|
||||||
|
|
||||||
|
is_running_pid() {
|
||||||
|
local pid="${1:-}"
|
||||||
|
[[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
current_loop_pids() {
|
||||||
|
local rp="$1"
|
||||||
|
pgrep -af "$(loop_pattern "$rp")" 2>/dev/null | awk '{print $1}' || true
|
||||||
|
}
|
||||||
|
|
||||||
|
status_one() {
|
||||||
|
local rp="$1"
|
||||||
|
local pid_file
|
||||||
|
pid_file="$(rp_pid_file "$rp")"
|
||||||
|
local pid=""
|
||||||
|
[[ -f "$pid_file" ]] && pid="$(cat "$pid_file" 2>/dev/null || true)"
|
||||||
|
echo "== $rp =="
|
||||||
|
if is_running_pid "$pid"; then
|
||||||
|
echo "loop: running pid=$pid"
|
||||||
|
else
|
||||||
|
local detected
|
||||||
|
detected="$(current_loop_pids "$rp" | paste -sd, -)"
|
||||||
|
if [[ -n "$detected" ]]; then
|
||||||
|
echo "loop: running detected_pids=$detected"
|
||||||
|
else
|
||||||
|
echo "loop: stopped"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
ps -eo pid,ppid,stat,etime,%cpu,%mem,rss,cmd --sort=-%cpu \
|
||||||
|
| grep -E "$(loop_pattern "$rp")|$(rp_binary_pattern "$rp")" \
|
||||||
|
| grep -v grep \
|
||||||
|
| head -20 || true
|
||||||
|
local latest="$INTER_RP_ROOT/$(rp_root_name "$rp")/latest/run-meta.json"
|
||||||
|
if [[ -f "$latest" ]]; then
|
||||||
|
python3 - "$latest" <<'PY'
|
||||||
|
import json, sys
|
||||||
|
path = sys.argv[1]
|
||||||
|
with open(path, "r", encoding="utf-8") as handle:
|
||||||
|
meta = json.load(handle)
|
||||||
|
print("latest:", "runSeq=", meta.get("runSeq"), "success=", meta.get("success"), "wallMs=", meta.get("wallMs"), "vrps=", meta.get("counts", {}).get("vrps"), "vaps=", meta.get("counts", {}).get("vaps"), "rssKb=", meta.get("maxRssKb", {}).get("aggregatePeak"))
|
||||||
|
PY
|
||||||
|
else
|
||||||
|
echo "latest: none"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
start_one() {
|
||||||
|
local rp="$1"
|
||||||
|
local pid_file log_file root_name run_command
|
||||||
|
pid_file="$(rp_pid_file "$rp")"
|
||||||
|
log_file="$(rp_log_file "$rp")"
|
||||||
|
root_name="$(rp_root_name "$rp")"
|
||||||
|
run_command="$(rp_command "$rp")"
|
||||||
|
mkdir -p "$INTER_RP_ROOT/$root_name" "$INTER_RP_ROOT"
|
||||||
|
local existing=""
|
||||||
|
if [[ -f "$pid_file" ]]; then
|
||||||
|
existing="$(cat "$pid_file" 2>/dev/null || true)"
|
||||||
|
fi
|
||||||
|
if is_running_pid "$existing"; then
|
||||||
|
echo "$rp already running pid=$existing"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
local detected
|
||||||
|
detected="$(current_loop_pids "$rp" | head -1)"
|
||||||
|
if [[ -n "$detected" ]] && is_running_pid "$detected"; then
|
||||||
|
echo "$detected" >"$pid_file"
|
||||||
|
echo "$rp already running detected pid=$detected"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
nohup bash -lc '
|
||||||
|
set -euo pipefail
|
||||||
|
while true; do
|
||||||
|
"'"$REMOTE_SCRIPTS_DIR"'/run_single_rp_with_rss.sh" \
|
||||||
|
--rp "'"$rp"'" \
|
||||||
|
--root "'"$INTER_RP_ROOT/$root_name"'" \
|
||||||
|
--command "'"$run_command"'" \
|
||||||
|
--retain-runs "'"$RETAIN_RUNS"'" \
|
||||||
|
--sample-ms "'"$RSS_SAMPLE_MS"'" || true
|
||||||
|
done
|
||||||
|
' >"$log_file" 2>&1 &
|
||||||
|
local pid="$!"
|
||||||
|
echo "$pid" >"$pid_file"
|
||||||
|
echo "$rp started pid=$pid log=$log_file"
|
||||||
|
}
|
||||||
|
|
||||||
|
stop_one() {
|
||||||
|
local rp="$1"
|
||||||
|
local candidates=()
|
||||||
|
local pid_file pid
|
||||||
|
pid_file="$(rp_pid_file "$rp")"
|
||||||
|
if [[ -f "$pid_file" ]]; then
|
||||||
|
pid="$(cat "$pid_file" 2>/dev/null || true)"
|
||||||
|
[[ -n "$pid" ]] && candidates+=("$pid")
|
||||||
|
fi
|
||||||
|
while read -r pid; do
|
||||||
|
[[ -n "$pid" ]] && candidates+=("$pid")
|
||||||
|
done < <(current_loop_pids "$rp")
|
||||||
|
while read -r pid; do
|
||||||
|
[[ -n "$pid" ]] && candidates+=("$pid")
|
||||||
|
done < <(pgrep -af "$(rp_binary_pattern "$rp")" 2>/dev/null | awk '{print $1}' || true)
|
||||||
|
|
||||||
|
if ((${#candidates[@]} == 0)); then
|
||||||
|
echo "$rp already stopped"
|
||||||
|
rm -f "$pid_file"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local unique_pids
|
||||||
|
unique_pids="$(printf '%s\n' "${candidates[@]}" | awk '!seen[$0]++' | tr '\n' ' ')"
|
||||||
|
echo "stopping $rp pids=$unique_pids"
|
||||||
|
for pid in $unique_pids; do
|
||||||
|
kill -TERM "$pid" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
sleep 3
|
||||||
|
for pid in $unique_pids; do
|
||||||
|
kill -KILL "$pid" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
rm -f "$pid_file"
|
||||||
|
echo "$rp stopped"
|
||||||
|
}
|
||||||
|
|
||||||
|
load_config
|
||||||
|
|
||||||
|
case "$ACTION:$RP" in
|
||||||
|
status:all)
|
||||||
|
date -Is
|
||||||
|
uptime
|
||||||
|
status_one routinator
|
||||||
|
status_one rpki-client
|
||||||
|
;;
|
||||||
|
status:*)
|
||||||
|
date -Is
|
||||||
|
uptime
|
||||||
|
status_one "$RP"
|
||||||
|
;;
|
||||||
|
start:*)
|
||||||
|
start_one "$RP"
|
||||||
|
status_one "$RP"
|
||||||
|
;;
|
||||||
|
stop:all)
|
||||||
|
stop_one routinator
|
||||||
|
stop_one rpki-client
|
||||||
|
;;
|
||||||
|
stop:*)
|
||||||
|
stop_one "$RP"
|
||||||
|
status_one "$RP"
|
||||||
|
;;
|
||||||
|
restart:*)
|
||||||
|
stop_one "$RP"
|
||||||
|
start_one "$RP"
|
||||||
|
status_one "$RP"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
REMOTE
|
||||||
26
scripts/inter_rp/inter-rp.env.example
Normal file
26
scripts/inter_rp/inter-rp.env.example
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# 远端200 runner 配置示例。
|
||||||
|
# 该脚本只负责任务调度、RSS采样、run目录和run-meta.json;具体RP命令由下面两个变量提供。
|
||||||
|
|
||||||
|
# 远端200上的运行根目录。
|
||||||
|
# 建议把运行态数据放到 /var/lib,避免 rpki-client 降权后无法读写 /root。
|
||||||
|
INTER_RP_ROOT=/var/lib/inter-rp-runners
|
||||||
|
|
||||||
|
# -1 表示持续运行;正整数表示每个RP运行多少轮。
|
||||||
|
MAX_RUNS=-1
|
||||||
|
|
||||||
|
# 每轮结束后的等待秒数。
|
||||||
|
RUN_INTERVAL_SECS=0
|
||||||
|
|
||||||
|
# 每个RP保留最近多少轮。
|
||||||
|
RETAIN_RUNS=20
|
||||||
|
|
||||||
|
# RSS采样间隔,毫秒。
|
||||||
|
RSS_SAMPLE_MS=500
|
||||||
|
|
||||||
|
# Routinator 命令模板。命令运行时会自动导出 RUN_DIR/RP_ROOT/RUN_SEQ/RUN_ID/RP_NAME。
|
||||||
|
# 命令必须把 vrps.csv/vaps.csv 写入 $RUN_DIR;Routinator 使用 --enable-aspa + JSON 输出转换出 VAP CSV。
|
||||||
|
ROUTINATOR_RUN_COMMAND=/root/inter-rp-runners/scripts/run_routinator_once.sh
|
||||||
|
|
||||||
|
# 官方 rpki-client 9.8 命令模板。
|
||||||
|
# rpki-client 会降权运行,cache 和 output 必须预先放在可写目录;命令需复制 output/csv、output/rpki.ccr,并从 output/json 转换出 vaps.csv。
|
||||||
|
RPKI_CLIENT_RUN_COMMAND=/root/inter-rp-runners/scripts/run_rpki_client_official_98_once.sh
|
||||||
472
scripts/inter_rp/inter_rp_ours_routinator_exporter.py
Executable file
472
scripts/inter_rp/inter_rp_ours_routinator_exporter.py
Executable file
@@ -0,0 +1,472 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import csv
|
||||||
|
import datetime as dt
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import socket
|
||||||
|
import sys
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
RUN_ROOT = Path(os.environ.get("OURS_RUN_ROOT", "/root/ours-rp-continuous/portable-soak"))
|
||||||
|
PEER_ROOT = Path(os.environ.get("PEER_ROOT", "/root/ours-rp-continuous/portable-soak/inter-rp-peers"))
|
||||||
|
INSTANCE = os.environ.get("INTER_RP_INSTANCE", "remote200-inter-rp")
|
||||||
|
LISTEN = os.environ.get("INTER_RP_LISTEN", "0.0.0.0:9557")
|
||||||
|
SCAN_TTL = float(os.environ.get("INTER_RP_SCAN_TTL_SECONDS", "10"))
|
||||||
|
|
||||||
|
_cache_lock = threading.Lock()
|
||||||
|
_cache = {"deadline": 0.0, "metrics": "", "status": {}}
|
||||||
|
_count_cache = {}
|
||||||
|
_set_cache = {}
|
||||||
|
|
||||||
|
def unix_now():
|
||||||
|
return time.time()
|
||||||
|
|
||||||
|
def parse_rfc3339(value):
|
||||||
|
if not value:
|
||||||
|
return None
|
||||||
|
text = str(value).replace("Z", "+00:00")
|
||||||
|
try:
|
||||||
|
return dt.datetime.fromisoformat(text).timestamp()
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def read_json(path):
|
||||||
|
with open(path, "r", encoding="utf-8") as handle:
|
||||||
|
return json.load(handle)
|
||||||
|
|
||||||
|
def latest_ours_run():
|
||||||
|
runs = RUN_ROOT / "runs"
|
||||||
|
candidates = sorted(p for p in runs.glob("run_*") if (p / "run-summary.json").exists())
|
||||||
|
return candidates[-1] if candidates else None
|
||||||
|
|
||||||
|
def unique_csv_set(path, cols):
|
||||||
|
if not path.exists():
|
||||||
|
return None
|
||||||
|
stat = path.stat()
|
||||||
|
key = (str(path), stat.st_mtime_ns, stat.st_size, cols)
|
||||||
|
if key in _set_cache:
|
||||||
|
return _set_cache[key]
|
||||||
|
seen = set()
|
||||||
|
with open(path, "r", encoding="utf-8", newline="") as handle:
|
||||||
|
reader = csv.reader(handle)
|
||||||
|
first = True
|
||||||
|
for row in reader:
|
||||||
|
if first:
|
||||||
|
first = False
|
||||||
|
continue
|
||||||
|
if not row:
|
||||||
|
continue
|
||||||
|
if len(row) < cols:
|
||||||
|
continue
|
||||||
|
seen.add(tuple(cell.strip() for cell in row[:cols]))
|
||||||
|
_set_cache.clear()
|
||||||
|
_set_cache[key] = seen
|
||||||
|
return seen
|
||||||
|
|
||||||
|
def count_unique_csv(path, cols):
|
||||||
|
values = unique_csv_set(path, cols)
|
||||||
|
if values is None:
|
||||||
|
return None
|
||||||
|
return len(values)
|
||||||
|
|
||||||
|
def metric_line(name, labels, value):
|
||||||
|
label_text = ",".join(f'{k}="{str(v).replace(chr(92), chr(92)+chr(92)).replace(chr(34), chr(92)+chr(34))}"' for k, v in labels.items())
|
||||||
|
return f"{name}{{{label_text}}} {value}\n"
|
||||||
|
|
||||||
|
def bool_num(value):
|
||||||
|
return 1 if value else 0
|
||||||
|
|
||||||
|
def sample_ours(now, errors):
|
||||||
|
sample = {"rp": "ours-rp", "present": False, "success": False, "max": {}, "errors": 0}
|
||||||
|
run_dir = latest_ours_run()
|
||||||
|
if run_dir is None:
|
||||||
|
sample["errors"] += 1
|
||||||
|
errors.append("ours-rp: no run-summary.json")
|
||||||
|
return sample
|
||||||
|
sample["present"] = True
|
||||||
|
summary_path = run_dir / "run-summary.json"
|
||||||
|
try:
|
||||||
|
summary = read_json(summary_path)
|
||||||
|
except Exception as exc:
|
||||||
|
sample["errors"] += 1
|
||||||
|
errors.append(f"ours-rp: read {summary_path}: {exc}")
|
||||||
|
return sample
|
||||||
|
sample["run_id"] = summary.get("runId") or run_dir.name
|
||||||
|
sample["run_seq"] = summary.get("runSeq") or int(run_dir.name.split("_")[-1])
|
||||||
|
sample["success"] = summary.get("status") == "success" and int(summary.get("exitCode", 0)) == 0
|
||||||
|
if summary.get("wallMs") is not None:
|
||||||
|
sample["wall"] = float(summary["wallMs"]) / 1000.0
|
||||||
|
finished = parse_rfc3339(summary.get("finishedAtRfc3339Utc"))
|
||||||
|
if finished is not None:
|
||||||
|
sample["finished"] = finished
|
||||||
|
sample["age"] = max(0.0, now - finished)
|
||||||
|
rss = summary.get("processMetrics", {}).get("maxRssKb")
|
||||||
|
if rss is not None:
|
||||||
|
sample["max"]["parent"] = int(rss) * 1024
|
||||||
|
sample["max"]["aggregate_peak"] = int(rss) * 1024
|
||||||
|
sample["vrp_set"] = unique_csv_set(run_dir / "vrps.csv", 3)
|
||||||
|
sample["vap_set"] = unique_csv_set(run_dir / "vaps.csv", 2)
|
||||||
|
sample["vrps"] = len(sample["vrp_set"]) if sample["vrp_set"] is not None else None
|
||||||
|
sample["vaps"] = len(sample["vap_set"]) if sample["vap_set"] is not None else None
|
||||||
|
return sample
|
||||||
|
|
||||||
|
def sample_routinator(now, errors):
|
||||||
|
rp = "routinator"
|
||||||
|
latest = PEER_ROOT / rp / "latest"
|
||||||
|
sample = {"rp": rp, "present": False, "success": False, "max": {}, "errors": 0}
|
||||||
|
if not latest.exists():
|
||||||
|
sample["errors"] += 1
|
||||||
|
errors.append(f"routinator: missing latest directory: {latest}")
|
||||||
|
return sample
|
||||||
|
sample["present"] = True
|
||||||
|
meta_path = latest / "run-meta.json"
|
||||||
|
try:
|
||||||
|
meta = read_json(meta_path)
|
||||||
|
except Exception as exc:
|
||||||
|
sample["errors"] += 1
|
||||||
|
errors.append(f"routinator: read {meta_path}: {exc}")
|
||||||
|
return sample
|
||||||
|
sample["run_id"] = meta.get("runId")
|
||||||
|
sample["run_seq"] = meta.get("runSeq")
|
||||||
|
sample["success"] = bool(meta.get("success"))
|
||||||
|
if meta.get("wallMs") is not None:
|
||||||
|
sample["wall"] = float(meta["wallMs"]) / 1000.0
|
||||||
|
finished = parse_rfc3339(meta.get("finishedAtRfc3339Utc"))
|
||||||
|
if finished is not None:
|
||||||
|
sample["finished"] = finished
|
||||||
|
sample["age"] = max(0.0, now - finished)
|
||||||
|
max_rss = meta.get("maxRssKb", {})
|
||||||
|
for label, key in [("parent", "parent"), ("child_max", "childMax"), ("aggregate_peak", "aggregatePeak")]:
|
||||||
|
if max_rss.get(key) is not None:
|
||||||
|
sample["max"][label] = int(max_rss[key]) * 1024
|
||||||
|
sample["vrp_set"] = unique_csv_set(latest / "vrps.csv", 3)
|
||||||
|
sample["vap_set"] = unique_csv_set(latest / "vaps.csv", 2)
|
||||||
|
sample["vrps"] = len(sample["vrp_set"]) if sample["vrp_set"] is not None else None
|
||||||
|
sample["vaps"] = len(sample["vap_set"]) if sample["vap_set"] is not None else None
|
||||||
|
return sample
|
||||||
|
|
||||||
|
|
||||||
|
def parse_prom_labels(label_text):
|
||||||
|
labels = {}
|
||||||
|
for key, value in re.findall(r'(\w+)="((?:[^"\\]|\\.)*)"', label_text):
|
||||||
|
labels[key] = value.replace(r'\"', '"').replace(r'\\', '\\')
|
||||||
|
return labels
|
||||||
|
|
||||||
|
def parse_prometheus_samples(text, metric_name):
|
||||||
|
pattern = re.compile(r'^' + re.escape(metric_name) + r'\{([^}]*)\}\s+([-+0-9.eE]+)\s*$', re.MULTILINE)
|
||||||
|
for match in pattern.finditer(text):
|
||||||
|
try:
|
||||||
|
value = float(match.group(2))
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
yield parse_prom_labels(match.group(1)), value
|
||||||
|
|
||||||
|
def canonical_object_type(value):
|
||||||
|
value = (value or "").strip().lower()
|
||||||
|
if value in ("manifest", "mft"):
|
||||||
|
return "mft"
|
||||||
|
if value == "crl":
|
||||||
|
return "crl"
|
||||||
|
if value in ("certificate", "cert", "ca_cert", "router_cert", "ee_cert", "crt"):
|
||||||
|
return "crt"
|
||||||
|
if value == "roa":
|
||||||
|
return "roa"
|
||||||
|
if value == "aspa":
|
||||||
|
return "aspa"
|
||||||
|
return None
|
||||||
|
|
||||||
|
def empty_object_counts():
|
||||||
|
return {"mft": 0, "crl": 0, "crt": 0, "roa": 0, "aspa": 0}
|
||||||
|
|
||||||
|
def add_object_count(counts_by_uri, uri, object_type, value):
|
||||||
|
canonical = canonical_object_type(object_type)
|
||||||
|
if not uri or canonical is None:
|
||||||
|
return
|
||||||
|
counts = counts_by_uri.setdefault(uri, empty_object_counts())
|
||||||
|
counts[canonical] = counts.get(canonical, 0) + int(value)
|
||||||
|
|
||||||
|
def object_count_labels(counts_by_uri, uri):
|
||||||
|
counts = counts_by_uri.get(uri, empty_object_counts())
|
||||||
|
return {key: str(int(counts.get(key, 0))) for key in ["mft", "crl", "crt", "roa", "aspa"]}
|
||||||
|
|
||||||
|
def load_ours_repo_sets(errors):
|
||||||
|
try:
|
||||||
|
import urllib.request
|
||||||
|
text = urllib.request.urlopen("http://127.0.0.1:9556/metrics", timeout=10).read().decode("utf-8", "replace")
|
||||||
|
except Exception as exc:
|
||||||
|
errors.append(f"ours-rp repo metrics: {exc}")
|
||||||
|
return {"total": set(), "available": set(), "failed": set(), "info": {}, "object_counts": {}}
|
||||||
|
info = {}
|
||||||
|
states = {}
|
||||||
|
object_counts = {}
|
||||||
|
for labels, value in parse_prometheus_samples(text, "ours_rp_repository_info"):
|
||||||
|
uri = labels.get("uri")
|
||||||
|
if uri:
|
||||||
|
info[uri] = labels
|
||||||
|
for labels, value in parse_prometheus_samples(text, "ours_rp_repository_terminal_state_publication_points"):
|
||||||
|
uri = None
|
||||||
|
repo_id = labels.get("repo_id")
|
||||||
|
for candidate_uri, candidate in info.items():
|
||||||
|
if candidate.get("repo_id") == repo_id:
|
||||||
|
uri = candidate_uri
|
||||||
|
break
|
||||||
|
if not uri:
|
||||||
|
continue
|
||||||
|
states.setdefault(uri, {})[labels.get("terminal_state", "unknown")] = value
|
||||||
|
for labels, value in parse_prometheus_samples(text, "ours_rp_repository_objects_by_type"):
|
||||||
|
uri = labels.get("uri")
|
||||||
|
if not uri:
|
||||||
|
repo_id = labels.get("repo_id")
|
||||||
|
for candidate_uri, candidate in info.items():
|
||||||
|
if candidate.get("repo_id") == repo_id:
|
||||||
|
uri = candidate_uri
|
||||||
|
break
|
||||||
|
add_object_count(object_counts, uri, labels.get("object_type"), value)
|
||||||
|
total = set(info)
|
||||||
|
available = set()
|
||||||
|
failed = set()
|
||||||
|
for uri in total:
|
||||||
|
repo_states = states.get(uri, {})
|
||||||
|
non_failed = sum(value for state, value in repo_states.items() if state != "failed_no_cache")
|
||||||
|
if non_failed > 0:
|
||||||
|
available.add(uri)
|
||||||
|
else:
|
||||||
|
failed.add(uri)
|
||||||
|
return {"total": total, "available": available, "failed": failed, "info": info, "object_counts": object_counts}
|
||||||
|
|
||||||
|
def load_routinator_repo_sets(errors):
|
||||||
|
path = PEER_ROOT / "routinator" / "routinator-metrics.prom"
|
||||||
|
if not path.exists():
|
||||||
|
errors.append(f"routinator repo metrics: missing {path}")
|
||||||
|
return {"total": set(), "success": set(), "failed": set(), "duration": {}, "object_counts": {}}
|
||||||
|
try:
|
||||||
|
text = path.read_text(encoding="utf-8", errors="replace")
|
||||||
|
except Exception as exc:
|
||||||
|
errors.append(f"routinator repo metrics: read {path}: {exc}")
|
||||||
|
return {"total": set(), "success": set(), "failed": set(), "duration": {}, "object_counts": {}}
|
||||||
|
total = set()
|
||||||
|
success = set()
|
||||||
|
failed = set()
|
||||||
|
duration = {}
|
||||||
|
object_counts = {}
|
||||||
|
publication_point_states = {}
|
||||||
|
for metric in ["routinator_rrdp_status", "routinator_rsync_status"]:
|
||||||
|
for labels, value in parse_prometheus_samples(text, metric):
|
||||||
|
uri = labels.get("uri")
|
||||||
|
if not uri:
|
||||||
|
continue
|
||||||
|
total.add(uri)
|
||||||
|
ok = value in (0.0, 200.0, 304.0)
|
||||||
|
if ok:
|
||||||
|
success.add(uri)
|
||||||
|
else:
|
||||||
|
failed.add(uri)
|
||||||
|
for labels, value in parse_prometheus_samples(text, "routinator_repository_publication_points_total"):
|
||||||
|
uri = labels.get("uri")
|
||||||
|
state = labels.get("state", "unknown")
|
||||||
|
if not uri:
|
||||||
|
continue
|
||||||
|
total.add(uri)
|
||||||
|
publication_point_states.setdefault(uri, {})[state] = publication_point_states.setdefault(uri, {}).get(state, 0.0) + value
|
||||||
|
for uri, states in publication_point_states.items():
|
||||||
|
valid_count = states.get("valid", 0.0)
|
||||||
|
non_valid_count = sum(value for state, value in states.items() if state != "valid")
|
||||||
|
if valid_count > 0:
|
||||||
|
success.add(uri)
|
||||||
|
elif non_valid_count > 0:
|
||||||
|
failed.add(uri)
|
||||||
|
for metric in ["routinator_rrdp_duration", "routinator_rsync_duration"]:
|
||||||
|
for labels, value in parse_prometheus_samples(text, metric):
|
||||||
|
uri = labels.get("uri")
|
||||||
|
if uri:
|
||||||
|
duration[uri] = max(duration.get(uri, 0.0), value)
|
||||||
|
for labels, value in parse_prometheus_samples(text, "routinator_repository_objects_total"):
|
||||||
|
add_object_count(object_counts, labels.get("uri"), labels.get("type"), value)
|
||||||
|
failed = failed - success
|
||||||
|
return {"total": total, "success": success, "failed": failed, "duration": duration, "object_counts": object_counts}
|
||||||
|
|
||||||
|
def emit_repo_diff_metrics(out, errors):
|
||||||
|
ours = load_ours_repo_sets(errors)
|
||||||
|
routinator = load_routinator_repo_sets(errors)
|
||||||
|
ours_ok = ours["available"]
|
||||||
|
rout_ok = routinator["success"]
|
||||||
|
only_ours = sorted(ours_ok - rout_ok)
|
||||||
|
only_routinator = sorted(rout_ok - ours_ok)
|
||||||
|
both = ours_ok & rout_ok
|
||||||
|
neither = (ours["total"] | routinator["total"]) - (ours_ok | rout_ok)
|
||||||
|
base = {"instance": INSTANCE, "left": "ours-rp", "right": "routinator"}
|
||||||
|
out.append(metric_line("inter_rp_repo_sync_total", {"instance": INSTANCE, "rp": "ours-rp", "state": "total"}, len(ours["total"])))
|
||||||
|
out.append(metric_line("inter_rp_repo_sync_total", {"instance": INSTANCE, "rp": "ours-rp", "state": "available"}, len(ours_ok)))
|
||||||
|
out.append(metric_line("inter_rp_repo_sync_total", {"instance": INSTANCE, "rp": "ours-rp", "state": "failed"}, len(ours["failed"])))
|
||||||
|
out.append(metric_line("inter_rp_repo_sync_total", {"instance": INSTANCE, "rp": "routinator", "state": "total"}, len(routinator["total"])))
|
||||||
|
out.append(metric_line("inter_rp_repo_sync_total", {"instance": INSTANCE, "rp": "routinator", "state": "available"}, len(rout_ok)))
|
||||||
|
out.append(metric_line("inter_rp_repo_sync_total", {"instance": INSTANCE, "rp": "routinator", "state": "failed"}, len(routinator["failed"])))
|
||||||
|
out.append(metric_line("inter_rp_repo_sync_overlap_total", {**base, "class": "both_available"}, len(both)))
|
||||||
|
out.append(metric_line("inter_rp_repo_sync_overlap_total", {**base, "class": "only_ours"}, len(only_ours)))
|
||||||
|
out.append(metric_line("inter_rp_repo_sync_overlap_total", {**base, "class": "only_routinator"}, len(only_routinator)))
|
||||||
|
out.append(metric_line("inter_rp_repo_sync_overlap_total", {**base, "class": "neither_available"}, len(neither)))
|
||||||
|
for diff_class, uris, counts_by_uri in [
|
||||||
|
("only_ours", only_ours, ours["object_counts"]),
|
||||||
|
("only_routinator", only_routinator, routinator["object_counts"]),
|
||||||
|
]:
|
||||||
|
for rank, uri in enumerate(uris[:50], start=1):
|
||||||
|
labels = {
|
||||||
|
**base,
|
||||||
|
"class": diff_class,
|
||||||
|
"rank": rank,
|
||||||
|
"uri": uri,
|
||||||
|
**object_count_labels(counts_by_uri, uri),
|
||||||
|
}
|
||||||
|
if uri in routinator["duration"]:
|
||||||
|
labels["routinator_duration"] = f"{routinator['duration'][uri]:.3f}"
|
||||||
|
out.append(metric_line("inter_rp_repo_sync_diff_info", labels, 1))
|
||||||
|
return {
|
||||||
|
"oursTotal": len(ours["total"]),
|
||||||
|
"oursAvailable": len(ours_ok),
|
||||||
|
"routinatorTotal": len(routinator["total"]),
|
||||||
|
"routinatorAvailable": len(rout_ok),
|
||||||
|
"onlyOurs": only_ours[:50],
|
||||||
|
"onlyRoutinator": only_routinator[:50],
|
||||||
|
"bothAvailable": len(both),
|
||||||
|
"neitherAvailable": len(neither),
|
||||||
|
}
|
||||||
|
|
||||||
|
def sync_metrics(now):
|
||||||
|
path = PEER_ROOT / "sync-status.json"
|
||||||
|
if not path.exists():
|
||||||
|
return {"present": False, "success": False, "message": f"missing {path}"}
|
||||||
|
try:
|
||||||
|
value = read_json(path)
|
||||||
|
except Exception as exc:
|
||||||
|
return {"present": True, "success": False, "message": str(exc)}
|
||||||
|
ts = parse_rfc3339(value.get("lastSyncAtRfc3339Utc"))
|
||||||
|
return {
|
||||||
|
"present": True,
|
||||||
|
"success": bool(value.get("success")),
|
||||||
|
"timestamp": ts,
|
||||||
|
"age": max(0.0, now - ts) if ts is not None else None,
|
||||||
|
"remote": value.get("remoteHost", ""),
|
||||||
|
"message": value.get("message", ""),
|
||||||
|
}
|
||||||
|
|
||||||
|
def build_metrics():
|
||||||
|
now = unix_now()
|
||||||
|
errors = []
|
||||||
|
start = time.time()
|
||||||
|
samples = [sample_ours(now, errors), sample_routinator(now, errors)]
|
||||||
|
sync = sync_metrics(now)
|
||||||
|
if not sync.get("success"):
|
||||||
|
errors.append("sync: " + str(sync.get("message", "failed")))
|
||||||
|
out = []
|
||||||
|
out.append(metric_line("inter_rp_service_up", {"instance": INSTANCE}, 1))
|
||||||
|
out.append(metric_line("inter_rp_service_last_scan_timestamp_seconds", {"instance": INSTANCE}, now))
|
||||||
|
out.append(metric_line("inter_rp_service_last_scan_duration_seconds", {"instance": INSTANCE}, time.time() - start))
|
||||||
|
out.append(metric_line("inter_rp_service_last_reload_success", {"instance": INSTANCE}, bool_num(len(errors) == 0)))
|
||||||
|
out.append(metric_line("inter_rp_parse_errors", {"instance": INSTANCE}, len(errors)))
|
||||||
|
out.append(metric_line("inter_rp_sync_present", {"instance": INSTANCE}, bool_num(sync.get("present"))))
|
||||||
|
out.append(metric_line("inter_rp_sync_last_success", {"instance": INSTANCE}, bool_num(sync.get("success"))))
|
||||||
|
if sync.get("age") is not None:
|
||||||
|
out.append(metric_line("inter_rp_sync_age_seconds", {"instance": INSTANCE}, sync["age"]))
|
||||||
|
if sync.get("timestamp") is not None:
|
||||||
|
out.append(metric_line("inter_rp_sync_last_timestamp_seconds", {"instance": INSTANCE}, sync["timestamp"]))
|
||||||
|
by_rp = {s["rp"]: s for s in samples}
|
||||||
|
for s in samples:
|
||||||
|
labels = {"instance": INSTANCE, "rp": s["rp"]}
|
||||||
|
out.append(metric_line("inter_rp_run_present", labels, bool_num(s.get("present"))))
|
||||||
|
out.append(metric_line("inter_rp_run_success", labels, bool_num(s.get("success"))))
|
||||||
|
out.append(metric_line("inter_rp_sample_parse_errors", labels, s.get("errors", 0)))
|
||||||
|
if s.get("run_seq") is not None:
|
||||||
|
out.append(metric_line("inter_rp_run_seq", labels, s["run_seq"]))
|
||||||
|
if s.get("wall") is not None:
|
||||||
|
out.append(metric_line("inter_rp_run_wall_seconds", labels, s["wall"]))
|
||||||
|
if s.get("age") is not None:
|
||||||
|
out.append(metric_line("inter_rp_artifact_age_seconds", labels, s["age"]))
|
||||||
|
if s.get("vrps") is not None:
|
||||||
|
out.append(metric_line("inter_rp_vrps", labels, s["vrps"]))
|
||||||
|
if s.get("vaps") is not None:
|
||||||
|
out.append(metric_line("inter_rp_vaps", labels, s["vaps"]))
|
||||||
|
for kind, value in s.get("max", {}).items():
|
||||||
|
labels2 = dict(labels)
|
||||||
|
labels2["kind"] = kind
|
||||||
|
out.append(metric_line("inter_rp_run_max_rss_bytes", labels2, value))
|
||||||
|
ours, rout = by_rp.get("ours-rp", {}), by_rp.get("routinator", {})
|
||||||
|
if ours.get("vrp_set") is not None and rout.get("vrp_set") is not None:
|
||||||
|
only_ours = ours["vrp_set"] - rout["vrp_set"]
|
||||||
|
only_rout = rout["vrp_set"] - ours["vrp_set"]
|
||||||
|
total_diff = len(only_ours) + len(only_rout)
|
||||||
|
labels = {"instance": INSTANCE, "left": "ours-rp", "right": "routinator"}
|
||||||
|
out.append(metric_line("inter_rp_vrps_diff", labels, total_diff))
|
||||||
|
out.append(metric_line("inter_rp_vrps_diff_by_class", {**labels, "class": "total"}, total_diff))
|
||||||
|
out.append(metric_line("inter_rp_vrps_diff_by_class", {**labels, "class": "only_ours"}, len(only_ours)))
|
||||||
|
out.append(metric_line("inter_rp_vrps_diff_by_class", {**labels, "class": "only_routinator"}, len(only_rout)))
|
||||||
|
elif ours.get("vrps") is not None and rout.get("vrps") is not None:
|
||||||
|
labels = {"instance": INSTANCE, "left": "ours-rp", "right": "routinator"}
|
||||||
|
total_diff = abs(int(ours["vrps"]) - int(rout["vrps"]))
|
||||||
|
out.append(metric_line("inter_rp_vrps_diff", labels, total_diff))
|
||||||
|
out.append(metric_line("inter_rp_vrps_diff_by_class", {**labels, "class": "total"}, total_diff))
|
||||||
|
if ours.get("vap_set") is not None and rout.get("vap_set") is not None:
|
||||||
|
only_ours = ours["vap_set"] - rout["vap_set"]
|
||||||
|
only_rout = rout["vap_set"] - ours["vap_set"]
|
||||||
|
total_diff = len(only_ours) + len(only_rout)
|
||||||
|
labels = {"instance": INSTANCE, "left": "ours-rp", "right": "routinator"}
|
||||||
|
out.append(metric_line("inter_rp_vaps_diff", labels, total_diff))
|
||||||
|
out.append(metric_line("inter_rp_vaps_diff_by_class", {**labels, "class": "total"}, total_diff))
|
||||||
|
out.append(metric_line("inter_rp_vaps_diff_by_class", {**labels, "class": "only_ours"}, len(only_ours)))
|
||||||
|
out.append(metric_line("inter_rp_vaps_diff_by_class", {**labels, "class": "only_routinator"}, len(only_rout)))
|
||||||
|
elif ours.get("vaps") is not None and rout.get("vaps") is not None:
|
||||||
|
labels = {"instance": INSTANCE, "left": "ours-rp", "right": "routinator"}
|
||||||
|
total_diff = abs(int(ours["vaps"]) - int(rout["vaps"]))
|
||||||
|
out.append(metric_line("inter_rp_vaps_diff", labels, total_diff))
|
||||||
|
out.append(metric_line("inter_rp_vaps_diff_by_class", {**labels, "class": "total"}, total_diff))
|
||||||
|
repo_diff = emit_repo_diff_metrics(out, errors)
|
||||||
|
return "".join(out), {"errors": errors, "samples": samples, "sync": sync, "repoDiff": repo_diff}
|
||||||
|
|
||||||
|
def get_metrics():
|
||||||
|
now = time.time()
|
||||||
|
with _cache_lock:
|
||||||
|
if _cache["metrics"] and _cache["deadline"] > now:
|
||||||
|
return _cache["metrics"]
|
||||||
|
metrics, status = build_metrics()
|
||||||
|
_cache["metrics"] = metrics
|
||||||
|
_cache["status"] = status
|
||||||
|
_cache["deadline"] = now + SCAN_TTL
|
||||||
|
return metrics
|
||||||
|
|
||||||
|
class Handler(BaseHTTPRequestHandler):
|
||||||
|
def do_GET(self):
|
||||||
|
if self.path == "/metrics":
|
||||||
|
body = get_metrics().encode("utf-8")
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header("Content-Type", "text/plain; version=0.0.4; charset=utf-8")
|
||||||
|
self.send_header("Content-Length", str(len(body)))
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(body)
|
||||||
|
return
|
||||||
|
if self.path == "/status":
|
||||||
|
with _cache_lock:
|
||||||
|
if not _cache["status"] or _cache["deadline"] <= time.time():
|
||||||
|
build_metrics()
|
||||||
|
body = json.dumps(_cache["status"], indent=2).encode("utf-8")
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header("Content-Type", "application/json")
|
||||||
|
self.send_header("Content-Length", str(len(body)))
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(body)
|
||||||
|
return
|
||||||
|
self.send_response(404)
|
||||||
|
self.end_headers()
|
||||||
|
def log_message(self, fmt, *args):
|
||||||
|
return
|
||||||
|
|
||||||
|
def main():
|
||||||
|
host, port = LISTEN.rsplit(":", 1)
|
||||||
|
server = ThreadingHTTPServer((host, int(port)), Handler)
|
||||||
|
print(f"inter-rp ours+routinator exporter listen={LISTEN} instance={INSTANCE}", flush=True)
|
||||||
|
server.serve_forever()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
47
scripts/inter_rp/run_inter_rp_metrics_sidecar.sh
Executable file
47
scripts/inter_rp/run_inter_rp_metrics_sidecar.sh
Executable file
@@ -0,0 +1,47 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
CONFIG_FILE="${INTER_RP_METRICS_CONFIG:-./inter-rp-metrics.env}"
|
||||||
|
if [[ -f "$CONFIG_FILE" ]]; then
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "$CONFIG_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
INTER_RP_METRICS_EXPORTER="${INTER_RP_METRICS_EXPORTER:-ours-routinator}"
|
||||||
|
RPKI_INTER_RP_METRICS_BIN="${RPKI_INTER_RP_METRICS_BIN:-./bin/rpki_inter_rp_metrics}"
|
||||||
|
OURS_ROUTINATOR_EXPORTER="${OURS_ROUTINATOR_EXPORTER:-$SCRIPT_DIR/inter_rp_ours_routinator_exporter.py}"
|
||||||
|
OURS_RUN_ROOT="${OURS_RUN_ROOT:?OURS_RUN_ROOT is required}"
|
||||||
|
PEER_ROOT="${PEER_ROOT:-/root/inter-rp-aggregator/synced-from-200}"
|
||||||
|
LISTEN="${INTER_RP_METRICS_LISTEN:-0.0.0.0:9557}"
|
||||||
|
POLL_SECS="${INTER_RP_METRICS_POLL_SECS:-30}"
|
||||||
|
SCAN_TTL_SECONDS="${INTER_RP_SCAN_TTL_SECONDS:-20}"
|
||||||
|
INSTANCE="${INTER_RP_METRICS_INSTANCE:-remote231-inter-rp}"
|
||||||
|
LOG_DIR="${INTER_RP_METRICS_LOG_DIR:-./logs}"
|
||||||
|
|
||||||
|
mkdir -p "$LOG_DIR"
|
||||||
|
case "$INTER_RP_METRICS_EXPORTER" in
|
||||||
|
ours-routinator)
|
||||||
|
exec env \
|
||||||
|
OURS_RUN_ROOT="$OURS_RUN_ROOT" \
|
||||||
|
PEER_ROOT="$PEER_ROOT" \
|
||||||
|
INTER_RP_INSTANCE="$INSTANCE" \
|
||||||
|
INTER_RP_LISTEN="$LISTEN" \
|
||||||
|
INTER_RP_SCAN_TTL_SECONDS="$SCAN_TTL_SECONDS" \
|
||||||
|
"$OURS_ROUTINATOR_EXPORTER" \
|
||||||
|
>>"$LOG_DIR/inter-rp-metrics.log" 2>&1
|
||||||
|
;;
|
||||||
|
rust-generic)
|
||||||
|
exec "$RPKI_INTER_RP_METRICS_BIN" \
|
||||||
|
--ours-run-root "$OURS_RUN_ROOT" \
|
||||||
|
--peer-root "$PEER_ROOT" \
|
||||||
|
--listen "$LISTEN" \
|
||||||
|
--poll-secs "$POLL_SECS" \
|
||||||
|
--instance "$INSTANCE" \
|
||||||
|
>>"$LOG_DIR/inter-rp-metrics.log" 2>&1
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "unknown INTER_RP_METRICS_EXPORTER: $INTER_RP_METRICS_EXPORTER" >&2
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
62
scripts/inter_rp/run_remote200_rp_loops.sh
Executable file
62
scripts/inter_rp/run_remote200_rp_loops.sh
Executable file
@@ -0,0 +1,62 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
CONFIG_FILE="${INTER_RP_CONFIG:-./inter-rp.env}"
|
||||||
|
if [[ -f "$CONFIG_FILE" ]]; then
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "$CONFIG_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
INTER_RP_ROOT="${INTER_RP_ROOT:-/root/inter-rp-runners}"
|
||||||
|
MAX_RUNS="${MAX_RUNS:--1}"
|
||||||
|
RUN_INTERVAL_SECS="${RUN_INTERVAL_SECS:-0}"
|
||||||
|
RETAIN_RUNS="${RETAIN_RUNS:-20}"
|
||||||
|
RSS_SAMPLE_MS="${RSS_SAMPLE_MS:-500}"
|
||||||
|
ROUTINATOR_RUN_COMMAND="${ROUTINATOR_RUN_COMMAND:-}"
|
||||||
|
RPKI_CLIENT_RUN_COMMAND="${RPKI_CLIENT_RUN_COMMAND:-}"
|
||||||
|
|
||||||
|
if [[ -z "$ROUTINATOR_RUN_COMMAND" || -z "$RPKI_CLIENT_RUN_COMMAND" ]]; then
|
||||||
|
cat >&2 <<'MSG'
|
||||||
|
ROUTINATOR_RUN_COMMAND and RPKI_CLIENT_RUN_COMMAND are required.
|
||||||
|
Each command runs with RUN_DIR, RP_ROOT, RUN_SEQ, RUN_ID and RP_NAME exported.
|
||||||
|
MSG
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$INTER_RP_ROOT"
|
||||||
|
|
||||||
|
run_loop() {
|
||||||
|
local rp_name="$1"
|
||||||
|
local run_command="$2"
|
||||||
|
local rp_root="$INTER_RP_ROOT/$rp_name"
|
||||||
|
local completed="0"
|
||||||
|
mkdir -p "$rp_root"
|
||||||
|
while true; do
|
||||||
|
if [[ "$MAX_RUNS" =~ ^[0-9]+$ ]] && (( completed >= MAX_RUNS )); then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
"$SCRIPT_DIR/run_single_rp_with_rss.sh" \
|
||||||
|
--rp "$rp_name" \
|
||||||
|
--root "$rp_root" \
|
||||||
|
--command "$run_command" \
|
||||||
|
--retain-runs "$RETAIN_RUNS" \
|
||||||
|
--sample-ms "$RSS_SAMPLE_MS" || true
|
||||||
|
completed=$((completed + 1))
|
||||||
|
if (( RUN_INTERVAL_SECS > 0 )); then
|
||||||
|
sleep "$RUN_INTERVAL_SECS"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
run_loop routinator "$ROUTINATOR_RUN_COMMAND" >"$INTER_RP_ROOT/routinator.loop.log" 2>&1 &
|
||||||
|
ROUTINATOR_LOOP_PID=$!
|
||||||
|
run_loop rpki-client "$RPKI_CLIENT_RUN_COMMAND" >"$INTER_RP_ROOT/rpki-client.loop.log" 2>&1 &
|
||||||
|
RPKI_CLIENT_LOOP_PID=$!
|
||||||
|
|
||||||
|
cat >"$INTER_RP_ROOT/runner-pids.env" <<PIDS
|
||||||
|
ROUTINATOR_LOOP_PID=$ROUTINATOR_LOOP_PID
|
||||||
|
RPKI_CLIENT_LOOP_PID=$RPKI_CLIENT_LOOP_PID
|
||||||
|
PIDS
|
||||||
|
|
||||||
|
wait "$ROUTINATOR_LOOP_PID" "$RPKI_CLIENT_LOOP_PID"
|
||||||
62
scripts/inter_rp/run_routinator_once.sh
Executable file
62
scripts/inter_rp/run_routinator_once.sh
Executable file
@@ -0,0 +1,62 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
: "${RUN_DIR:?RUN_DIR is required}"
|
||||||
|
: "${RP_ROOT:?RP_ROOT is required}"
|
||||||
|
|
||||||
|
timeout 1800 /root/inter-rp-runners/bin/routinator \
|
||||||
|
--repository-dir "$RP_ROOT/repository" \
|
||||||
|
--no-rir-tals \
|
||||||
|
--extra-tals-dir /var/lib/inter-rp-runners/fixtures/tal \
|
||||||
|
--enable-aspa \
|
||||||
|
vrps --format json --output "$RUN_DIR/routinator-output.json"
|
||||||
|
|
||||||
|
python3 - "$RUN_DIR/routinator-output.json" "$RUN_DIR/vrps.csv" "$RUN_DIR/vaps.csv" <<'PY'
|
||||||
|
import csv
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
|
||||||
|
json_path, vrps_path, vaps_path = sys.argv[1:4]
|
||||||
|
|
||||||
|
with open(json_path, "r", encoding="utf-8") as handle:
|
||||||
|
payload = json.load(handle)
|
||||||
|
|
||||||
|
def asn_text(value):
|
||||||
|
if isinstance(value, int):
|
||||||
|
return f"AS{value}"
|
||||||
|
text = str(value)
|
||||||
|
return text if text.startswith("AS") else f"AS{text}"
|
||||||
|
|
||||||
|
def asn_sort_key(value):
|
||||||
|
text = asn_text(value)
|
||||||
|
try:
|
||||||
|
return int(text[2:])
|
||||||
|
except ValueError:
|
||||||
|
return text
|
||||||
|
|
||||||
|
with open(vrps_path, "w", encoding="utf-8", newline="") as handle:
|
||||||
|
writer = csv.writer(handle)
|
||||||
|
writer.writerow(["ASN", "IP Prefix", "Max Length", "Trust Anchor"])
|
||||||
|
for row in payload.get("roas", []):
|
||||||
|
writer.writerow([
|
||||||
|
asn_text(row.get("asn", "")),
|
||||||
|
row.get("prefix", ""),
|
||||||
|
row.get("maxLength", row.get("max_length", "")),
|
||||||
|
row.get("ta", row.get("trust_anchor", "")),
|
||||||
|
])
|
||||||
|
|
||||||
|
with open(vaps_path, "w", encoding="utf-8", newline="") as handle:
|
||||||
|
writer = csv.writer(handle)
|
||||||
|
writer.writerow(["Customer ASN", "Providers", "Trust Anchor"])
|
||||||
|
for row in payload.get("aspas", payload.get("aspa", [])):
|
||||||
|
providers = sorted({asn_text(provider) for provider in row.get("providers", [])}, key=asn_sort_key)
|
||||||
|
writer.writerow([
|
||||||
|
asn_text(row.get("customer", row.get("customer_asid", ""))),
|
||||||
|
";".join(providers),
|
||||||
|
row.get("ta", row.get("trust_anchor", "unknown")),
|
||||||
|
])
|
||||||
|
PY
|
||||||
|
|
||||||
|
if [[ "${KEEP_INTER_RP_RAW_JSON:-0}" != "1" ]]; then
|
||||||
|
rm -f "$RUN_DIR/routinator-output.json"
|
||||||
|
fi
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user