20260526 增加持续soak监控与本地回放工具
This commit is contained in:
parent
cda7fdb135
commit
7e1c24fcc3
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
|||||||
target/
|
target/
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
perf.*
|
perf.*
|
||||||
|
specs/* copy.excalidraw
|
||||||
|
|||||||
131
monitor/README.md
Normal file
131
monitor/README.md
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
# 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
|
||||||
|
```
|
||||||
|
|
||||||
|
## 停止
|
||||||
|
|
||||||
|
```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`
|
||||||
|
- `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`
|
||||||
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:
|
||||||
632
monitor/grafana/dashboards/ours-rp-repo-sync.json
Normal file
632
monitor/grafana/dashboards/ours-rp-repo-sync.json
Normal file
@ -0,0 +1,632 @@
|
|||||||
|
{
|
||||||
|
"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": "ours_rp_repo_sync_phase_count{phase=\"rrdp_failed_rsync_ok\"}",
|
||||||
|
"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": 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"
|
||||||
|
},
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"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": ""
|
||||||
|
}
|
||||||
582
monitor/grafana/dashboards/ours-rp-soak-overview.json
Normal file
582
monitor/grafana/dashboards/ours-rp-soak-overview.json
Normal file
@ -0,0 +1,582 @@
|
|||||||
|
{
|
||||||
|
"annotations": {
|
||||||
|
"list": []
|
||||||
|
},
|
||||||
|
"editable": true,
|
||||||
|
"fiscalYearStartMonth": 0,
|
||||||
|
"graphTooltip": 0,
|
||||||
|
"id": null,
|
||||||
|
"links": [],
|
||||||
|
"panels": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "short"
|
||||||
|
},
|
||||||
|
"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": {
|
||||||
|
"unit": "short"
|
||||||
|
},
|
||||||
|
"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": {
|
||||||
|
"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": {
|
||||||
|
"unit": "short"
|
||||||
|
},
|
||||||
|
"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",
|
||||||
|
"legendFormat": "VRPs",
|
||||||
|
"refId": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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": {
|
||||||
|
"unit": "short"
|
||||||
|
},
|
||||||
|
"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": "short"
|
||||||
|
},
|
||||||
|
"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": {
|
||||||
|
"unit": "short"
|
||||||
|
},
|
||||||
|
"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": "ours_rp_run_success",
|
||||||
|
"legendFormat": "success",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Latest Run Success",
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "short"
|
||||||
|
},
|
||||||
|
"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",
|
||||||
|
"legendFormat": "VRPs",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "VRPs",
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "Prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "short"
|
||||||
|
},
|
||||||
|
"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": 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"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"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
|
||||||
13
monitor/prometheus/prometheus.yml
Normal file
13
monitor/prometheus/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:
|
||||||
|
- host.docker.internal:9556
|
||||||
|
labels:
|
||||||
|
rp: ours-rp
|
||||||
|
source: artifact-sidecar
|
||||||
79
scripts/local_repo_replay/build_local_repo_replay_package.sh
Executable file
79
scripts/local_repo_replay/build_local_repo_replay_package.sh
Executable file
@ -0,0 +1,79 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage:
|
||||||
|
build_local_repo_replay_package.sh --out <path> [--tar-gz]
|
||||||
|
|
||||||
|
Build a standalone local repository tree replay package for Routinator and
|
||||||
|
rpki-client. The package does not include repository data and does not include
|
||||||
|
materialize tooling.
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
|
SRC_DIR="$ROOT_DIR/scripts/local_repo_replay"
|
||||||
|
OUT=""
|
||||||
|
TAR_GZ=0
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--out) OUT="$2"; shift 2 ;;
|
||||||
|
--tar-gz) TAR_GZ=1; shift ;;
|
||||||
|
-h|--help) usage; exit 0 ;;
|
||||||
|
*) echo "unknown argument: $1" >&2; usage >&2; exit 2 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
[[ -n "$OUT" ]] || { usage >&2; exit 2; }
|
||||||
|
|
||||||
|
if [[ "$TAR_GZ" -eq 1 ]]; then
|
||||||
|
PACKAGE_DIR="$(mktemp -d)"
|
||||||
|
TARGET_DIR="$PACKAGE_DIR/local-repo-replay-package"
|
||||||
|
else
|
||||||
|
TARGET_DIR="$OUT"
|
||||||
|
rm -rf "$TARGET_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$TARGET_DIR/scripts" "$TARGET_DIR/docs" "$TARGET_DIR/examples"
|
||||||
|
|
||||||
|
install -m 0755 "$SRC_DIR/run_routinator_from_local_tree.sh" "$TARGET_DIR/scripts/"
|
||||||
|
install -m 0755 "$SRC_DIR/run_rpki_client_from_local_tree.sh" "$TARGET_DIR/scripts/"
|
||||||
|
install -m 0755 "$SRC_DIR/run_dual_local_tree_replay.sh" "$TARGET_DIR/scripts/"
|
||||||
|
install -m 0755 "$SRC_DIR/prepare_tals.py" "$TARGET_DIR/scripts/"
|
||||||
|
install -m 0755 "$SRC_DIR/normalize_rp_outputs.py" "$TARGET_DIR/scripts/"
|
||||||
|
install -m 0755 "$SRC_DIR/compare_normalized_sets.py" "$TARGET_DIR/scripts/"
|
||||||
|
install -m 0755 "$SRC_DIR/summarize_replay.py" "$TARGET_DIR/scripts/"
|
||||||
|
install -m 0755 "$ROOT_DIR/scripts/cir/cir-rsync-wrapper" "$TARGET_DIR/scripts/"
|
||||||
|
install -m 0755 "$ROOT_DIR/scripts/cir/cir-local-link-sync.py" "$TARGET_DIR/scripts/"
|
||||||
|
install -m 0644 "$SRC_DIR/templates/README.md" "$TARGET_DIR/README.md"
|
||||||
|
install -m 0644 "$SRC_DIR/templates/docs/input_tree_requirements.md" "$TARGET_DIR/docs/"
|
||||||
|
install -m 0644 "$SRC_DIR/templates/docs/offline_replay_limits.md" "$TARGET_DIR/docs/"
|
||||||
|
install -m 0644 "$SRC_DIR/templates/docs/output_files.md" "$TARGET_DIR/docs/"
|
||||||
|
install -m 0755 "$SRC_DIR/templates/examples/routinator_example.sh" "$TARGET_DIR/examples/"
|
||||||
|
install -m 0755 "$SRC_DIR/templates/examples/rpki_client_example.sh" "$TARGET_DIR/examples/"
|
||||||
|
install -m 0755 "$SRC_DIR/templates/examples/dual_compare_example.sh" "$TARGET_DIR/examples/"
|
||||||
|
cat > "$TARGET_DIR/env.example" <<'EOF'
|
||||||
|
# 本地目录树 replay 示例配置。目录树由使用者提前准备,不包含在 package 中。
|
||||||
|
TAL_DIR=/data/replay/tals
|
||||||
|
MIRROR_ROOT=/data/replay/mirror
|
||||||
|
ROUTINATOR_BIN=/opt/routinator/target/release/routinator
|
||||||
|
RPKI_CLIENT_BIN=/opt/rpki-client/src/rpki-client
|
||||||
|
RPKI_CLIENT_CACHE_DIR=/data/replay/work/rpki-client-cache
|
||||||
|
VALIDATION_TIME=2026-05-23T00:00:00Z
|
||||||
|
EOF
|
||||||
|
|
||||||
|
if grep -R -E 'cir_materialize|repo-bytes\\.db|\\.cir' "$TARGET_DIR/scripts" >/dev/null; then
|
||||||
|
echo "package contains forbidden materialize/repo-bytes implementation references" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$TAR_GZ" -eq 1 ]]; then
|
||||||
|
mkdir -p "$(dirname "$OUT")"
|
||||||
|
tar -C "$PACKAGE_DIR" -czf "$OUT" local-repo-replay-package
|
||||||
|
rm -rf "$PACKAGE_DIR"
|
||||||
|
echo "$OUT"
|
||||||
|
else
|
||||||
|
echo "$TARGET_DIR"
|
||||||
|
fi
|
||||||
51
scripts/local_repo_replay/compare_normalized_sets.py
Executable file
51
scripts/local_repo_replay/compare_normalized_sets.py
Executable file
@ -0,0 +1,51 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def read_set(path: Path) -> set[str]:
|
||||||
|
if not path.is_file():
|
||||||
|
return set()
|
||||||
|
return {line.strip() for line in path.read_text(encoding="utf-8", errors="replace").splitlines() if line.strip()}
|
||||||
|
|
||||||
|
|
||||||
|
def compare(left: set[str], right: set[str]) -> dict[str, object]:
|
||||||
|
union = left | right
|
||||||
|
inter = left & right
|
||||||
|
return {
|
||||||
|
"left": len(left),
|
||||||
|
"right": len(right),
|
||||||
|
"intersection": len(inter),
|
||||||
|
"onlyLeft": len(left - right),
|
||||||
|
"onlyRight": len(right - left),
|
||||||
|
"jaccard": round(len(inter) / len(union), 8) if union else 1.0,
|
||||||
|
"onlyLeftSamples": sorted(left - right)[:20],
|
||||||
|
"onlyRightSamples": sorted(right - left)[:20],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("--left", type=Path, required=True)
|
||||||
|
parser.add_argument("--right", type=Path, required=True)
|
||||||
|
parser.add_argument("--left-name", default="left")
|
||||||
|
parser.add_argument("--right-name", default="right")
|
||||||
|
parser.add_argument("--out", type=Path, required=True)
|
||||||
|
args = parser.parse_args()
|
||||||
|
result = {
|
||||||
|
"leftName": args.left_name,
|
||||||
|
"rightName": args.right_name,
|
||||||
|
"vrps": compare(read_set(args.left / "vrps.normalized.txt"), read_set(args.right / "vrps.normalized.txt")),
|
||||||
|
"vaps": compare(read_set(args.left / "vaps.normalized.txt"), read_set(args.right / "vaps.normalized.txt")),
|
||||||
|
}
|
||||||
|
args.out.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
args.out.write_text(json.dumps(result, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
||||||
|
print(args.out)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
123
scripts/local_repo_replay/normalize_rp_outputs.py
Executable file
123
scripts/local_repo_replay/normalize_rp_outputs.py
Executable file
@ -0,0 +1,123 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import csv
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_asn(value: Any) -> str:
|
||||||
|
text = str(value).strip().upper()
|
||||||
|
if text.startswith("AS"):
|
||||||
|
text = text[2:]
|
||||||
|
return f"AS{int(text)}"
|
||||||
|
|
||||||
|
|
||||||
|
def write_set(path: Path, rows: set[str]) -> None:
|
||||||
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
path.write_text("\n".join(sorted(rows)) + ("\n" if rows else ""), encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def load_json(path: Path) -> Any:
|
||||||
|
return json.loads(path.read_text(encoding="utf-8"))
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_vrps_from_json(path: Path) -> set[str]:
|
||||||
|
data = load_json(path)
|
||||||
|
rows: set[str] = set()
|
||||||
|
objects: list[dict[str, Any]] = []
|
||||||
|
if isinstance(data, dict):
|
||||||
|
for key in ("roas", "routeOrigins", "valid_roas"):
|
||||||
|
value = data.get(key)
|
||||||
|
if isinstance(value, list):
|
||||||
|
objects.extend(item for item in value if isinstance(item, dict))
|
||||||
|
elif isinstance(data, list):
|
||||||
|
objects = [item for item in data if isinstance(item, dict)]
|
||||||
|
for item in objects:
|
||||||
|
asn = item.get("asn") or item.get("asID") or item.get("origin")
|
||||||
|
prefix = item.get("prefix")
|
||||||
|
max_length = item.get("maxLength") or item.get("max_length") or item.get("maxlen") or item.get("maxLengthPrefix")
|
||||||
|
if asn is None or prefix is None or max_length is None:
|
||||||
|
continue
|
||||||
|
rows.add(f"{normalize_asn(asn)}|{prefix}|{int(max_length)}")
|
||||||
|
return rows
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_vaps_from_json(path: Path) -> set[str]:
|
||||||
|
data = load_json(path)
|
||||||
|
rows: set[str] = set()
|
||||||
|
objects: list[dict[str, Any]] = []
|
||||||
|
if isinstance(data, dict):
|
||||||
|
for key in ("aspas", "aspaAssertions", "vaps"):
|
||||||
|
value = data.get(key)
|
||||||
|
if isinstance(value, list):
|
||||||
|
objects.extend(item for item in value if isinstance(item, dict))
|
||||||
|
elif isinstance(data, list):
|
||||||
|
objects = [item for item in data if isinstance(item, dict)]
|
||||||
|
for item in objects:
|
||||||
|
customer = (
|
||||||
|
item.get("customer")
|
||||||
|
or item.get("customer_asid")
|
||||||
|
or item.get("customerASID")
|
||||||
|
or item.get("customerAsid")
|
||||||
|
or item.get("customerAsn")
|
||||||
|
)
|
||||||
|
providers = item.get("providers") or item.get("provider_asns") or item.get("providerASNs") or []
|
||||||
|
if customer is None:
|
||||||
|
continue
|
||||||
|
provider_asns = [normalize_asn(provider) for provider in providers]
|
||||||
|
rows.add(f"{normalize_asn(customer)}|{','.join(sorted(set(provider_asns), key=lambda value: int(value[2:])))}")
|
||||||
|
return rows
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_vrps_from_csv(path: Path) -> set[str]:
|
||||||
|
rows: set[str] = set()
|
||||||
|
with path.open(newline="", encoding="utf-8", errors="replace") as handle:
|
||||||
|
for row in csv.DictReader(handle):
|
||||||
|
asn = row.get("ASN") or row.get("asn") or row.get("AS")
|
||||||
|
prefix = row.get("IP Prefix") or row.get("prefix") or row.get("Prefix")
|
||||||
|
max_length = row.get("Max Length") or row.get("maxLength") or row.get("max_length")
|
||||||
|
if asn and prefix and max_length:
|
||||||
|
rows.add(f"{normalize_asn(asn)}|{prefix}|{int(max_length)}")
|
||||||
|
return rows
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_routinator(input_path: Path) -> tuple[set[str], set[str]]:
|
||||||
|
return normalize_vrps_from_json(input_path), normalize_vaps_from_json(input_path)
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_rpki_client(input_path: Path) -> tuple[set[str], set[str]]:
|
||||||
|
json_path = input_path / "json" if input_path.is_dir() else input_path
|
||||||
|
csv_path = input_path / "csv" if input_path.is_dir() else input_path
|
||||||
|
vrps: set[str] = set()
|
||||||
|
vaps: set[str] = set()
|
||||||
|
if json_path.is_file():
|
||||||
|
vrps |= normalize_vrps_from_json(json_path)
|
||||||
|
vaps |= normalize_vaps_from_json(json_path)
|
||||||
|
if csv_path.is_file():
|
||||||
|
vrps |= normalize_vrps_from_csv(csv_path)
|
||||||
|
return vrps, vaps
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("--kind", choices=["routinator", "rpki-client"], required=True)
|
||||||
|
parser.add_argument("--input", type=Path, required=True)
|
||||||
|
parser.add_argument("--vrps-out", type=Path, required=True)
|
||||||
|
parser.add_argument("--vaps-out", type=Path, required=True)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.kind == "routinator":
|
||||||
|
vrps, vaps = normalize_routinator(args.input)
|
||||||
|
else:
|
||||||
|
vrps, vaps = normalize_rpki_client(args.input)
|
||||||
|
write_set(args.vrps_out, vrps)
|
||||||
|
write_set(args.vaps_out, vaps)
|
||||||
|
print(json.dumps({"vrps": len(vrps), "vaps": len(vaps)}, sort_keys=True))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
84
scripts/local_repo_replay/prepare_tals.py
Executable file
84
scripts/local_repo_replay/prepare_tals.py
Executable file
@ -0,0 +1,84 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def split_tal(text: str) -> tuple[list[str], list[str]]:
|
||||||
|
lines = text.splitlines()
|
||||||
|
for index, line in enumerate(lines):
|
||||||
|
if line.strip() == "":
|
||||||
|
return lines[:index], lines[index + 1 :]
|
||||||
|
return lines, []
|
||||||
|
|
||||||
|
|
||||||
|
def rsync_only_tal(source: Path) -> tuple[str, dict[str, object]]:
|
||||||
|
text = source.read_text(encoding="utf-8", errors="replace")
|
||||||
|
uri_lines, key_lines = split_tal(text)
|
||||||
|
uris = [
|
||||||
|
line.strip()
|
||||||
|
for line in uri_lines
|
||||||
|
if line.strip() and not line.lstrip().startswith("#")
|
||||||
|
]
|
||||||
|
rsync_uris = [
|
||||||
|
uri for uri in uris
|
||||||
|
if uri.lower().startswith("rsync://")
|
||||||
|
]
|
||||||
|
if not rsync_uris:
|
||||||
|
return text if text.endswith("\n") else text + "\n", {
|
||||||
|
"file": source.name,
|
||||||
|
"mode": "unchanged_no_rsync_uri",
|
||||||
|
"uris": len(uris),
|
||||||
|
"rsyncUris": 0,
|
||||||
|
}
|
||||||
|
out = "\n".join(rsync_uris) + "\n\n" + "\n".join(key_lines).strip() + "\n"
|
||||||
|
return out, {
|
||||||
|
"file": source.name,
|
||||||
|
"mode": "rsync_only",
|
||||||
|
"uris": len(uris),
|
||||||
|
"rsyncUris": len(rsync_uris),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def collect_tals(tal_dir: Path | None, tals: list[Path]) -> list[Path]:
|
||||||
|
paths: list[Path] = []
|
||||||
|
if tal_dir is not None:
|
||||||
|
paths.extend(sorted(tal_dir.glob("*.tal")))
|
||||||
|
paths.extend(tals)
|
||||||
|
unique: dict[str, Path] = {}
|
||||||
|
for path in paths:
|
||||||
|
unique[path.name] = path
|
||||||
|
return [unique[name] for name in sorted(unique)]
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser(description="Prepare TALs for local rsync-tree replay.")
|
||||||
|
parser.add_argument("--tal-dir", type=Path)
|
||||||
|
parser.add_argument("--tal", type=Path, action="append", default=[])
|
||||||
|
parser.add_argument("--out-dir", type=Path, required=True)
|
||||||
|
parser.add_argument("--summary", type=Path)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
sources = collect_tals(args.tal_dir, args.tal)
|
||||||
|
if not sources:
|
||||||
|
raise SystemExit("no TAL files provided")
|
||||||
|
|
||||||
|
args.out_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
rows = []
|
||||||
|
for source in sources:
|
||||||
|
if not source.is_file():
|
||||||
|
raise SystemExit(f"TAL file not found: {source}")
|
||||||
|
content, row = rsync_only_tal(source)
|
||||||
|
(args.out_dir / source.name).write_text(content, encoding="utf-8")
|
||||||
|
rows.append(row)
|
||||||
|
|
||||||
|
if args.summary is not None:
|
||||||
|
args.summary.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
args.summary.write_text(json.dumps(rows, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
76
scripts/local_repo_replay/run_dual_local_tree_replay.sh
Executable file
76
scripts/local_repo_replay/run_dual_local_tree_replay.sh
Executable file
@ -0,0 +1,76 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage:
|
||||||
|
run_dual_local_tree_replay.sh \
|
||||||
|
--routinator-bin <path> --routinator-mirror-root <dir> \
|
||||||
|
--rpki-client-bin <path> --rpki-client-mirror-root <dir> \
|
||||||
|
--tal-dir <dir> --out-dir <dir> [--validation-time <RFC3339>]
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
ROUTINATOR_BIN=""
|
||||||
|
ROUTINATOR_MIRROR_ROOT=""
|
||||||
|
RPKI_CLIENT_BIN=""
|
||||||
|
RPKI_CLIENT_MIRROR_ROOT=""
|
||||||
|
RPKI_CLIENT_CACHE_DIR=""
|
||||||
|
TAL_DIR=""
|
||||||
|
OUT_DIR=""
|
||||||
|
VALIDATION_TIME=""
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--routinator-bin) ROUTINATOR_BIN="$2"; shift 2 ;;
|
||||||
|
--routinator-mirror-root) ROUTINATOR_MIRROR_ROOT="$2"; shift 2 ;;
|
||||||
|
--rpki-client-bin) RPKI_CLIENT_BIN="$2"; shift 2 ;;
|
||||||
|
--rpki-client-mirror-root) RPKI_CLIENT_MIRROR_ROOT="$2"; shift 2 ;;
|
||||||
|
--rpki-client-cache-dir) RPKI_CLIENT_CACHE_DIR="$2"; shift 2 ;;
|
||||||
|
--tal-dir) TAL_DIR="$2"; shift 2 ;;
|
||||||
|
--out-dir) OUT_DIR="$2"; shift 2 ;;
|
||||||
|
--validation-time) VALIDATION_TIME="$2"; shift 2 ;;
|
||||||
|
-h|--help) usage; exit 0 ;;
|
||||||
|
*) echo "unknown argument: $1" >&2; usage >&2; exit 2 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -z "$RPKI_CLIENT_MIRROR_ROOT" ]]; then
|
||||||
|
RPKI_CLIENT_MIRROR_ROOT="$ROUTINATOR_MIRROR_ROOT"
|
||||||
|
fi
|
||||||
|
[[ -n "$ROUTINATOR_BIN" && -n "$ROUTINATOR_MIRROR_ROOT" && -n "$RPKI_CLIENT_BIN" && -n "$RPKI_CLIENT_MIRROR_ROOT" && -n "$TAL_DIR" && -n "$OUT_DIR" ]] || { usage >&2; exit 2; }
|
||||||
|
|
||||||
|
mkdir -p "$OUT_DIR"
|
||||||
|
TIME_ARGS=()
|
||||||
|
if [[ -n "$VALIDATION_TIME" ]]; then
|
||||||
|
TIME_ARGS=(--validation-time "$VALIDATION_TIME")
|
||||||
|
fi
|
||||||
|
CACHE_ARGS=()
|
||||||
|
if [[ -n "$RPKI_CLIENT_CACHE_DIR" ]]; then
|
||||||
|
CACHE_ARGS=(--cache-dir "$RPKI_CLIENT_CACHE_DIR")
|
||||||
|
fi
|
||||||
|
|
||||||
|
"$SCRIPT_DIR/run_routinator_from_local_tree.sh" \
|
||||||
|
--routinator-bin "$ROUTINATOR_BIN" \
|
||||||
|
--mirror-root "$ROUTINATOR_MIRROR_ROOT" \
|
||||||
|
--tal-dir "$TAL_DIR" \
|
||||||
|
--out-dir "$OUT_DIR/routinator" \
|
||||||
|
--enable-aspa \
|
||||||
|
"${TIME_ARGS[@]}"
|
||||||
|
|
||||||
|
"$SCRIPT_DIR/run_rpki_client_from_local_tree.sh" \
|
||||||
|
--rpki-client-bin "$RPKI_CLIENT_BIN" \
|
||||||
|
--mirror-root "$RPKI_CLIENT_MIRROR_ROOT" \
|
||||||
|
--tal-dir "$TAL_DIR" \
|
||||||
|
--out-dir "$OUT_DIR/rpki-client" \
|
||||||
|
"${CACHE_ARGS[@]}" \
|
||||||
|
"${TIME_ARGS[@]}"
|
||||||
|
|
||||||
|
python3 "$SCRIPT_DIR/compare_normalized_sets.py" \
|
||||||
|
--left "$OUT_DIR/routinator" \
|
||||||
|
--right "$OUT_DIR/rpki-client" \
|
||||||
|
--left-name routinator \
|
||||||
|
--right-name rpki-client \
|
||||||
|
--out "$OUT_DIR/compare-summary.json"
|
||||||
|
echo "done: $OUT_DIR"
|
||||||
131
scripts/local_repo_replay/run_routinator_from_local_tree.sh
Executable file
131
scripts/local_repo_replay/run_routinator_from_local_tree.sh
Executable file
@ -0,0 +1,131 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage:
|
||||||
|
run_routinator_from_local_tree.sh \
|
||||||
|
--routinator-bin <path> \
|
||||||
|
--mirror-root <local-rsync-mirror-root> \
|
||||||
|
--tal-dir <dir> | --tal <file>... \
|
||||||
|
--out-dir <path> \
|
||||||
|
[--validation-time <RFC3339>] \
|
||||||
|
[--real-rsync-bin <path>] \
|
||||||
|
[--enable-aspa]
|
||||||
|
|
||||||
|
The input mirror is prepared by the caller. This script does not generate it.
|
||||||
|
Pass --validation-time only if FAKETIME_LIB points to a working libfaketime
|
||||||
|
library; otherwise Routinator will validate at wall-clock time.
|
||||||
|
Example:
|
||||||
|
export FAKETIME_LIB=/usr/lib/x86_64-linux-gnu/faketime/libfaketime.so.1
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
ROUTINATOR_BIN=""
|
||||||
|
MIRROR_ROOT=""
|
||||||
|
OUT_DIR=""
|
||||||
|
VALIDATION_TIME=""
|
||||||
|
REAL_RSYNC_BIN="${REAL_RSYNC_BIN:-/usr/bin/rsync}"
|
||||||
|
ENABLE_ASPA=0
|
||||||
|
TAL_DIR=""
|
||||||
|
TALS=()
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--routinator-bin) ROUTINATOR_BIN="$2"; shift 2 ;;
|
||||||
|
--mirror-root) MIRROR_ROOT="$2"; shift 2 ;;
|
||||||
|
--tal-dir) TAL_DIR="$2"; shift 2 ;;
|
||||||
|
--tal) TALS+=("$2"); shift 2 ;;
|
||||||
|
--out-dir) OUT_DIR="$2"; shift 2 ;;
|
||||||
|
--validation-time) VALIDATION_TIME="$2"; shift 2 ;;
|
||||||
|
--real-rsync-bin) REAL_RSYNC_BIN="$2"; shift 2 ;;
|
||||||
|
--enable-aspa) ENABLE_ASPA=1; shift ;;
|
||||||
|
-h|--help) usage; exit 0 ;;
|
||||||
|
*) echo "unknown argument: $1" >&2; usage >&2; exit 2 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
[[ -n "$ROUTINATOR_BIN" && -n "$MIRROR_ROOT" && -n "$OUT_DIR" ]] || { usage >&2; exit 2; }
|
||||||
|
[[ -x "$ROUTINATOR_BIN" ]] || { echo "routinator binary not executable: $ROUTINATOR_BIN" >&2; exit 2; }
|
||||||
|
[[ -d "$MIRROR_ROOT" ]] || { echo "mirror root not found: $MIRROR_ROOT" >&2; exit 2; }
|
||||||
|
if [[ -z "$TAL_DIR" && "${#TALS[@]}" -eq 0 ]]; then
|
||||||
|
echo "provide --tal-dir or at least one --tal" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$OUT_DIR"
|
||||||
|
WORK_DIR="$OUT_DIR/work"
|
||||||
|
REPO_DIR="$WORK_DIR/repository"
|
||||||
|
EXTRA_TALS="$WORK_DIR/tals"
|
||||||
|
CONFIG_FILE="$WORK_DIR/routinator.conf"
|
||||||
|
rm -rf "$WORK_DIR"
|
||||||
|
mkdir -p "$REPO_DIR" "$EXTRA_TALS"
|
||||||
|
|
||||||
|
PREPARE_TAL_ARGS=(--out-dir "$EXTRA_TALS" --summary "$OUT_DIR/prepared-tals.json")
|
||||||
|
if [[ -n "$TAL_DIR" ]]; then
|
||||||
|
PREPARE_TAL_ARGS+=(--tal-dir "$TAL_DIR")
|
||||||
|
fi
|
||||||
|
for tal in "${TALS[@]}"; do
|
||||||
|
PREPARE_TAL_ARGS+=(--tal "$tal")
|
||||||
|
done
|
||||||
|
python3 "$SCRIPT_DIR/prepare_tals.py" "${PREPARE_TAL_ARGS[@]}"
|
||||||
|
|
||||||
|
cat > "$CONFIG_FILE" <<EOF
|
||||||
|
repository-dir = "$REPO_DIR"
|
||||||
|
no-rir-tals = true
|
||||||
|
extra-tals-dir = "$EXTRA_TALS"
|
||||||
|
disable-rrdp = true
|
||||||
|
rrdp-fallback = "never"
|
||||||
|
rsync-command = "$SCRIPT_DIR/cir-rsync-wrapper"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
export CIR_MIRROR_ROOT="$(cd "$MIRROR_ROOT" && pwd)"
|
||||||
|
export REAL_RSYNC_BIN="$REAL_RSYNC_BIN"
|
||||||
|
export CIR_LOCAL_LINK_MODE=1
|
||||||
|
export LOCAL_REPO_REPLAY_VALIDATION_TIME="$VALIDATION_TIME"
|
||||||
|
export LOCAL_REPO_REPLAY_OUT_DIR="$OUT_DIR"
|
||||||
|
|
||||||
|
ARGS=(
|
||||||
|
"$ROUTINATOR_BIN"
|
||||||
|
--config "$CONFIG_FILE"
|
||||||
|
--repository-dir "$REPO_DIR"
|
||||||
|
--disable-rrdp
|
||||||
|
--rrdp-fallback never
|
||||||
|
--rsync-command "$SCRIPT_DIR/cir-rsync-wrapper"
|
||||||
|
--no-rir-tals
|
||||||
|
--extra-tals-dir "$EXTRA_TALS"
|
||||||
|
)
|
||||||
|
if [[ "$ENABLE_ASPA" -eq 1 ]]; then
|
||||||
|
echo 'enable-aspa = true' >> "$CONFIG_FILE"
|
||||||
|
ARGS+=(--enable-aspa)
|
||||||
|
fi
|
||||||
|
if [[ -n "$VALIDATION_TIME" && -z "${FAKETIME_LIB:-}" ]]; then
|
||||||
|
echo "warning: --validation-time is ignored for Routinator because FAKETIME_LIB is not set" >&2
|
||||||
|
fi
|
||||||
|
VRP_ARGS=(vrps -o "$OUT_DIR/vrps.csv")
|
||||||
|
JSON_ARGS=(vrps -f jsonext -o "$OUT_DIR/routinator.json")
|
||||||
|
|
||||||
|
/usr/bin/time -v -o "$OUT_DIR/process-time.txt" bash -c '
|
||||||
|
set -euo pipefail
|
||||||
|
if [[ -n "$LOCAL_REPO_REPLAY_VALIDATION_TIME" && -n "${FAKETIME_LIB:-}" ]]; then
|
||||||
|
faketime_value="$(python3 - <<'"'"'PY'"'"' "$LOCAL_REPO_REPLAY_VALIDATION_TIME"
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
import sys
|
||||||
|
dt = datetime.fromisoformat(sys.argv[1].replace("Z", "+00:00")).astimezone(timezone.utc)
|
||||||
|
print("@" + dt.strftime("%Y-%m-%d %H:%M:%S"))
|
||||||
|
PY
|
||||||
|
)"
|
||||||
|
export LD_PRELOAD="$FAKETIME_LIB"
|
||||||
|
export TZ=UTC
|
||||||
|
unset FAKETIME_FMT
|
||||||
|
export FAKETIME="$faketime_value"
|
||||||
|
export FAKETIME_DONT_FAKE_MONOTONIC=1
|
||||||
|
fi
|
||||||
|
"$@" update --complete >"$LOCAL_REPO_REPLAY_OUT_DIR/update.log" 2>&1 || true
|
||||||
|
"$@" vrps -o "$LOCAL_REPO_REPLAY_OUT_DIR/vrps.csv" >"$LOCAL_REPO_REPLAY_OUT_DIR/vrps.log" 2>&1
|
||||||
|
"$@" vrps -f jsonext -o "$LOCAL_REPO_REPLAY_OUT_DIR/routinator.json" >"$LOCAL_REPO_REPLAY_OUT_DIR/json.log" 2>&1
|
||||||
|
' local_repo_replay "${ARGS[@]}"
|
||||||
|
python3 "$SCRIPT_DIR/normalize_rp_outputs.py" --kind routinator --input "$OUT_DIR/routinator.json" --vrps-out "$OUT_DIR/vrps.normalized.txt" --vaps-out "$OUT_DIR/vaps.normalized.txt"
|
||||||
|
python3 "$SCRIPT_DIR/summarize_replay.py" --rp routinator --out-dir "$OUT_DIR" --summary "$OUT_DIR/summary.json"
|
||||||
|
echo "done: $OUT_DIR"
|
||||||
107
scripts/local_repo_replay/run_rpki_client_from_local_tree.sh
Executable file
107
scripts/local_repo_replay/run_rpki_client_from_local_tree.sh
Executable file
@ -0,0 +1,107 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage:
|
||||||
|
run_rpki_client_from_local_tree.sh \
|
||||||
|
--rpki-client-bin <path> \
|
||||||
|
--mirror-root <local-rsync-mirror-root> \
|
||||||
|
--tal-dir <dir> | --tal <file>... \
|
||||||
|
--out-dir <path> \
|
||||||
|
[--cache-dir <work-cache-dir>] \
|
||||||
|
[--validation-time <RFC3339>] \
|
||||||
|
[--real-rsync-bin <path>] \
|
||||||
|
[--parser-workers <n>]
|
||||||
|
|
||||||
|
The input mirror is prepared by the caller. This script disables RRDP, points
|
||||||
|
rpki-client's rsync command at a local URI mapper, and fetches only from the
|
||||||
|
local filesystem tree.
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
RPKI_CLIENT_BIN=""
|
||||||
|
CACHE_DIR=""
|
||||||
|
MIRROR_ROOT=""
|
||||||
|
OUT_DIR=""
|
||||||
|
TAL_DIR=""
|
||||||
|
VALIDATION_TIME=""
|
||||||
|
PARSER_WORKERS="${PARSER_WORKERS:-4}"
|
||||||
|
REAL_RSYNC_BIN="${REAL_RSYNC_BIN:-/usr/bin/rsync}"
|
||||||
|
TALS=()
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--rpki-client-bin) RPKI_CLIENT_BIN="$2"; shift 2 ;;
|
||||||
|
--mirror-root) MIRROR_ROOT="$2"; shift 2 ;;
|
||||||
|
--cache-dir) CACHE_DIR="$2"; shift 2 ;;
|
||||||
|
--tal-dir) TAL_DIR="$2"; shift 2 ;;
|
||||||
|
--tal) TALS+=("$2"); shift 2 ;;
|
||||||
|
--out-dir) OUT_DIR="$2"; shift 2 ;;
|
||||||
|
--validation-time) VALIDATION_TIME="$2"; shift 2 ;;
|
||||||
|
--real-rsync-bin) REAL_RSYNC_BIN="$2"; shift 2 ;;
|
||||||
|
--parser-workers) PARSER_WORKERS="$2"; shift 2 ;;
|
||||||
|
-h|--help) usage; exit 0 ;;
|
||||||
|
*) echo "unknown argument: $1" >&2; usage >&2; exit 2 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
[[ -n "$RPKI_CLIENT_BIN" && -n "$MIRROR_ROOT" && -n "$OUT_DIR" ]] || { usage >&2; exit 2; }
|
||||||
|
[[ -x "$RPKI_CLIENT_BIN" ]] || { echo "rpki-client binary not executable: $RPKI_CLIENT_BIN" >&2; exit 2; }
|
||||||
|
[[ -d "$MIRROR_ROOT" ]] || { echo "mirror root not found: $MIRROR_ROOT" >&2; exit 2; }
|
||||||
|
if [[ -z "$TAL_DIR" && "${#TALS[@]}" -eq 0 ]]; then
|
||||||
|
echo "provide --tal-dir or at least one --tal" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$OUT_DIR"
|
||||||
|
if [[ -z "$CACHE_DIR" ]]; then
|
||||||
|
CACHE_DIR="$OUT_DIR/cache"
|
||||||
|
fi
|
||||||
|
mkdir -p "$CACHE_DIR" "$OUT_DIR/out"
|
||||||
|
chmod a+rwx "$OUT_DIR" "$CACHE_DIR" "$OUT_DIR/out"
|
||||||
|
WORK_DIR="$OUT_DIR/work"
|
||||||
|
PREPARED_TALS="$WORK_DIR/tals"
|
||||||
|
rm -rf "$WORK_DIR"
|
||||||
|
mkdir -p "$PREPARED_TALS"
|
||||||
|
PREPARE_TAL_ARGS=(--out-dir "$PREPARED_TALS" --summary "$OUT_DIR/prepared-tals.json")
|
||||||
|
if [[ -n "$TAL_DIR" ]]; then
|
||||||
|
PREPARE_TAL_ARGS+=(--tal-dir "$TAL_DIR")
|
||||||
|
fi
|
||||||
|
for tal in "${TALS[@]}"; do
|
||||||
|
PREPARE_TAL_ARGS+=(--tal "$tal")
|
||||||
|
done
|
||||||
|
python3 "$SCRIPT_DIR/prepare_tals.py" "${PREPARE_TAL_ARGS[@]}"
|
||||||
|
|
||||||
|
CLIENT_TAL_ARGS=()
|
||||||
|
while IFS= read -r tal; do
|
||||||
|
CLIENT_TAL_ARGS+=(-t "$tal")
|
||||||
|
done < <(find "$PREPARED_TALS" -maxdepth 1 -type f -name '*.tal' | sort)
|
||||||
|
|
||||||
|
TIME_ARGS=()
|
||||||
|
if [[ -n "$VALIDATION_TIME" ]]; then
|
||||||
|
epoch="$(python3 - <<'PY' "$VALIDATION_TIME"
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
import sys
|
||||||
|
print(int(datetime.fromisoformat(sys.argv[1].replace("Z", "+00:00")).astimezone(timezone.utc).timestamp()))
|
||||||
|
PY
|
||||||
|
)"
|
||||||
|
TIME_ARGS=(-P "$epoch")
|
||||||
|
fi
|
||||||
|
|
||||||
|
export CIR_MIRROR_ROOT="$(cd "$MIRROR_ROOT" && pwd)"
|
||||||
|
export REAL_RSYNC_BIN="$REAL_RSYNC_BIN"
|
||||||
|
export CIR_LOCAL_LINK_MODE=1
|
||||||
|
|
||||||
|
/usr/bin/time -v -o "$OUT_DIR/process-time.txt" \
|
||||||
|
"$RPKI_CLIENT_BIN" \
|
||||||
|
-R -e "$SCRIPT_DIR/cir-rsync-wrapper" -j -c -p "$PARSER_WORKERS" \
|
||||||
|
"${TIME_ARGS[@]}" \
|
||||||
|
"${CLIENT_TAL_ARGS[@]}" \
|
||||||
|
-d "$CACHE_DIR" \
|
||||||
|
"$OUT_DIR/out" >"$OUT_DIR/stdout.log" 2>"$OUT_DIR/stderr.log"
|
||||||
|
|
||||||
|
python3 "$SCRIPT_DIR/normalize_rp_outputs.py" --kind rpki-client --input "$OUT_DIR/out" --vrps-out "$OUT_DIR/vrps.normalized.txt" --vaps-out "$OUT_DIR/vaps.normalized.txt"
|
||||||
|
python3 "$SCRIPT_DIR/summarize_replay.py" --rp rpki-client --out-dir "$OUT_DIR" --summary "$OUT_DIR/summary.json"
|
||||||
|
echo "done: $OUT_DIR"
|
||||||
47
scripts/local_repo_replay/summarize_replay.py
Executable file
47
scripts/local_repo_replay/summarize_replay.py
Executable file
@ -0,0 +1,47 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def count_lines(path: Path) -> int:
|
||||||
|
if not path.is_file():
|
||||||
|
return 0
|
||||||
|
return sum(1 for line in path.read_text(encoding="utf-8", errors="replace").splitlines() if line.strip())
|
||||||
|
|
||||||
|
|
||||||
|
def parse_time(path: Path) -> dict[str, object]:
|
||||||
|
if not path.is_file():
|
||||||
|
return {}
|
||||||
|
text = path.read_text(encoding="utf-8", errors="replace")
|
||||||
|
elapsed = re.search(r"Elapsed \(wall clock\) time .*: (.+)", text)
|
||||||
|
rss = re.search(r"Maximum resident set size \(kbytes\): (\d+)", text)
|
||||||
|
return {
|
||||||
|
"elapsed": elapsed.group(1).strip() if elapsed else "",
|
||||||
|
"maxRssKb": int(rss.group(1)) if rss else 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("--rp", required=True)
|
||||||
|
parser.add_argument("--out-dir", type=Path, required=True)
|
||||||
|
parser.add_argument("--summary", type=Path, required=True)
|
||||||
|
args = parser.parse_args()
|
||||||
|
summary = {
|
||||||
|
"rp": args.rp,
|
||||||
|
"vrps": count_lines(args.out_dir / "vrps.normalized.txt"),
|
||||||
|
"vaps": count_lines(args.out_dir / "vaps.normalized.txt"),
|
||||||
|
"time": parse_time(args.out_dir / "process-time.txt"),
|
||||||
|
"artifacts": sorted(path.name for path in args.out_dir.iterdir() if path.is_file()),
|
||||||
|
}
|
||||||
|
args.summary.write_text(json.dumps(summary, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
||||||
|
print(args.summary)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
118
scripts/local_repo_replay/templates/README.md
Normal file
118
scripts/local_repo_replay/templates/README.md
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
# Local Repository Tree Replay Package
|
||||||
|
|
||||||
|
This package replays already prepared local RPKI repository trees with
|
||||||
|
Routinator and rpki-client.
|
||||||
|
|
||||||
|
It is intentionally independent from CIR:
|
||||||
|
|
||||||
|
- it does not read `.cir`;
|
||||||
|
- it does not read `repo-bytes.db`;
|
||||||
|
- it does not call `cir_materialize`;
|
||||||
|
- it does not generate a local repository tree.
|
||||||
|
|
||||||
|
The caller must prepare the local repository/cache tree before running these
|
||||||
|
scripts.
|
||||||
|
|
||||||
|
## Contents
|
||||||
|
|
||||||
|
```text
|
||||||
|
local-repo-replay-package/
|
||||||
|
scripts/
|
||||||
|
run_routinator_from_local_tree.sh
|
||||||
|
run_rpki_client_from_local_tree.sh
|
||||||
|
run_dual_local_tree_replay.sh
|
||||||
|
prepare_tals.py
|
||||||
|
cir-rsync-wrapper
|
||||||
|
cir-local-link-sync.py
|
||||||
|
normalize_rp_outputs.py
|
||||||
|
compare_normalized_sets.py
|
||||||
|
summarize_replay.py
|
||||||
|
docs/
|
||||||
|
input_tree_requirements.md
|
||||||
|
offline_replay_limits.md
|
||||||
|
output_files.md
|
||||||
|
examples/
|
||||||
|
routinator_example.sh
|
||||||
|
rpki_client_example.sh
|
||||||
|
dual_compare_example.sh
|
||||||
|
env.example
|
||||||
|
```
|
||||||
|
|
||||||
|
## Routinator replay
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/run_routinator_from_local_tree.sh \
|
||||||
|
--routinator-bin /opt/routinator/target/release/routinator \
|
||||||
|
--mirror-root /data/replay/mirror \
|
||||||
|
--tal-dir /data/replay/tals \
|
||||||
|
--out-dir /data/replay/out/routinator \
|
||||||
|
--enable-aspa
|
||||||
|
```
|
||||||
|
|
||||||
|
The script uses `--disable-rrdp`, `--rsync-command ./scripts/cir-rsync-wrapper`,
|
||||||
|
and the local mirror root to satisfy rsync fetches from the local filesystem.
|
||||||
|
The wrapper name is historical; in this package it is only a generic
|
||||||
|
`rsync://` to local-path mapper.
|
||||||
|
|
||||||
|
If `--validation-time` is needed for Routinator, set `FAKETIME_LIB` to a working
|
||||||
|
libfaketime shared library. Otherwise Routinator validates at wall-clock time.
|
||||||
|
|
||||||
|
On Ubuntu, install and use faketime like this:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt-get install -y libfaketime
|
||||||
|
export FAKETIME_LIB=/usr/lib/x86_64-linux-gnu/faketime/libfaketime.so.1
|
||||||
|
./scripts/run_routinator_from_local_tree.sh \
|
||||||
|
--routinator-bin /opt/routinator/target/release/routinator \
|
||||||
|
--mirror-root /data/replay/mirror \
|
||||||
|
--tal-dir /data/replay/tals \
|
||||||
|
--out-dir /data/replay/out/routinator \
|
||||||
|
--validation-time 2026-05-14T06:48:00Z \
|
||||||
|
--enable-aspa
|
||||||
|
```
|
||||||
|
|
||||||
|
Without `FAKETIME_LIB`, old local trees can produce empty or smaller output
|
||||||
|
because Routinator validates manifests and CRLs against current wall-clock time.
|
||||||
|
|
||||||
|
## rpki-client replay
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/run_rpki_client_from_local_tree.sh \
|
||||||
|
--rpki-client-bin /opt/rpki-client/src/rpki-client \
|
||||||
|
--mirror-root /data/replay/mirror \
|
||||||
|
--tal-dir /data/replay/tals \
|
||||||
|
--out-dir /data/replay/out/rpki-client \
|
||||||
|
--parser-workers 4
|
||||||
|
```
|
||||||
|
|
||||||
|
The script uses `rpki-client -R -e ./scripts/cir-rsync-wrapper` so RRDP is
|
||||||
|
disabled and rsync fetches are served from the local mirror. `--cache-dir` is an
|
||||||
|
optional working cache directory used by rpki-client during this local replay.
|
||||||
|
|
||||||
|
## Dual replay
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/run_dual_local_tree_replay.sh \
|
||||||
|
--routinator-bin /opt/routinator/target/release/routinator \
|
||||||
|
--routinator-mirror-root /data/replay/mirror \
|
||||||
|
--rpki-client-bin /opt/rpki-client/src/rpki-client \
|
||||||
|
--rpki-client-mirror-root /data/replay/mirror \
|
||||||
|
--tal-dir /data/replay/tals \
|
||||||
|
--out-dir /data/replay/out/dual
|
||||||
|
```
|
||||||
|
|
||||||
|
If `--validation-time` is passed to dual replay, remember to export
|
||||||
|
`FAKETIME_LIB` first so Routinator and rpki-client use the same logical
|
||||||
|
validation time.
|
||||||
|
|
||||||
|
## Outputs
|
||||||
|
|
||||||
|
Each run writes normalized output:
|
||||||
|
|
||||||
|
- `vrps.normalized.txt`
|
||||||
|
- `vaps.normalized.txt`
|
||||||
|
- `summary.json`
|
||||||
|
- raw RP output and logs
|
||||||
|
- `process-time.txt`
|
||||||
|
|
||||||
|
See `docs/output_files.md`.
|
||||||
@ -0,0 +1,39 @@
|
|||||||
|
# Input Tree Requirements
|
||||||
|
|
||||||
|
The input tree is not part of this package. The caller must prepare it before
|
||||||
|
running replay.
|
||||||
|
|
||||||
|
## Mirror root
|
||||||
|
|
||||||
|
The mirror root must map rsync URIs to local paths:
|
||||||
|
|
||||||
|
```text
|
||||||
|
rsync://rpki.example.net/repo/a/b/c.roa
|
||||||
|
=> <mirror-root>/rpki.example.net/repo/a/b/c.roa
|
||||||
|
```
|
||||||
|
|
||||||
|
The tree must contain all objects needed by the selected TALs: TA certificates,
|
||||||
|
manifests, CRLs, ROAs, ASPAs, router certs, and child CA certificates.
|
||||||
|
|
||||||
|
Both Routinator and rpki-client scripts consume this same mirror root through a
|
||||||
|
local rsync command wrapper.
|
||||||
|
|
||||||
|
## rpki-client working cache
|
||||||
|
|
||||||
|
For rpki-client replay, `--cache-dir` is only rpki-client's working cache
|
||||||
|
directory for this local run. It is not the input dataset. The authoritative
|
||||||
|
input is `--mirror-root`.
|
||||||
|
|
||||||
|
## TALs
|
||||||
|
|
||||||
|
Provide either `--tal-dir <dir>` or repeated `--tal <file>`.
|
||||||
|
|
||||||
|
The scripts prepare a replay-local TAL copy that prefers `rsync://` TA
|
||||||
|
certificate URIs. This prevents a TAL with an HTTPS URI listed first from
|
||||||
|
escaping to the network during local replay. The TAL set should match the local
|
||||||
|
tree. Mixing a tree from one run with different TALs may produce meaningless
|
||||||
|
differences.
|
||||||
|
|
||||||
|
## No generation
|
||||||
|
|
||||||
|
This package does not generate the tree and does not repair missing objects.
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
# Offline Replay Limits
|
||||||
|
|
||||||
|
## Routinator
|
||||||
|
|
||||||
|
The Routinator script disables RRDP and uses an rsync command wrapper to map
|
||||||
|
rsync URLs to local paths. It still runs Routinator's normal validation logic.
|
||||||
|
|
||||||
|
If the local mirror does not contain required objects, validation can fail or
|
||||||
|
produce fewer outputs.
|
||||||
|
|
||||||
|
## rpki-client
|
||||||
|
|
||||||
|
The rpki-client script uses `-R` to disable RRDP and `-e` to point rsync at the
|
||||||
|
local mapper. rpki-client still builds its normal working cache, but every
|
||||||
|
rsync source is rewritten to the local mirror.
|
||||||
|
|
||||||
|
If the mirror was incomplete or produced by a different TAL set, replay results
|
||||||
|
may differ from the original run.
|
||||||
|
|
||||||
|
## Validation time
|
||||||
|
|
||||||
|
rpki-client supports `-P <posix-seconds>`. Routinator does not expose the same
|
||||||
|
simple command-line evaluation-time option in the tested version; if `FAKETIME_LIB`
|
||||||
|
is configured, the script can run Routinator under faketime. Without
|
||||||
|
`FAKETIME_LIB`, `--validation-time` is intentionally ignored for Routinator and
|
||||||
|
current wall-clock validation can reject stale manifests or CRLs.
|
||||||
|
|
||||||
|
Ubuntu example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt-get install -y libfaketime
|
||||||
|
export FAKETIME_LIB=/usr/lib/x86_64-linux-gnu/faketime/libfaketime.so.1
|
||||||
|
```
|
||||||
|
|
||||||
|
The script sets `TZ=UTC` and converts RFC3339 validation time to libfaketime
|
||||||
|
absolute UTC format, for example `2026-05-14T06:48:00Z` becomes
|
||||||
|
`@2026-05-14 06:48:00`. Setting `TZ=UTC` is required because libfaketime parses
|
||||||
|
absolute timestamps in the process timezone.
|
||||||
16
scripts/local_repo_replay/templates/docs/output_files.md
Normal file
16
scripts/local_repo_replay/templates/docs/output_files.md
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# Output Files
|
||||||
|
|
||||||
|
Each replay output directory can contain:
|
||||||
|
|
||||||
|
- `vrps.normalized.txt`: one normalized VRP per line.
|
||||||
|
- `vaps.normalized.txt`: one normalized VAP/ASPA per line.
|
||||||
|
- `summary.json`: counts and resource summary.
|
||||||
|
- `process-time.txt`: `/usr/bin/time -v` output.
|
||||||
|
- RP-specific raw output:
|
||||||
|
- Routinator: `routinator.json`, `vrps.csv`.
|
||||||
|
- rpki-client: `out/json`, `out/csv`, and other native files.
|
||||||
|
- logs:
|
||||||
|
- `stdout.log` / `stderr.log` for rpki-client.
|
||||||
|
- `update.log`, `vrps.log`, `json.log` for Routinator.
|
||||||
|
|
||||||
|
Dual replay additionally writes `compare-summary.json`.
|
||||||
11
scripts/local_repo_replay/templates/examples/dual_compare_example.sh
Executable file
11
scripts/local_repo_replay/templates/examples/dual_compare_example.sh
Executable file
@ -0,0 +1,11 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
./scripts/run_dual_local_tree_replay.sh \
|
||||||
|
--routinator-bin "${ROUTINATOR_BIN:-/opt/routinator/target/release/routinator}" \
|
||||||
|
--routinator-mirror-root "${ROUTINATOR_MIRROR_ROOT:-/data/replay/mirror}" \
|
||||||
|
--rpki-client-bin "${RPKI_CLIENT_BIN:-/opt/rpki-client/src/rpki-client}" \
|
||||||
|
--rpki-client-mirror-root "${RPKI_CLIENT_MIRROR_ROOT:-/data/replay/mirror}" \
|
||||||
|
--rpki-client-cache-dir "${RPKI_CLIENT_CACHE_DIR:-/data/replay/work/rpki-client-cache}" \
|
||||||
|
--tal-dir "${TAL_DIR:-/data/replay/tals}" \
|
||||||
|
--out-dir "${OUT_DIR:-/data/replay/out/dual}"
|
||||||
9
scripts/local_repo_replay/templates/examples/routinator_example.sh
Executable file
9
scripts/local_repo_replay/templates/examples/routinator_example.sh
Executable file
@ -0,0 +1,9 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
./scripts/run_routinator_from_local_tree.sh \
|
||||||
|
--routinator-bin "${ROUTINATOR_BIN:-/opt/routinator/target/release/routinator}" \
|
||||||
|
--mirror-root "${MIRROR_ROOT:-/data/replay/mirror}" \
|
||||||
|
--tal-dir "${TAL_DIR:-/data/replay/tals}" \
|
||||||
|
--out-dir "${OUT_DIR:-/data/replay/out/routinator}" \
|
||||||
|
--enable-aspa
|
||||||
10
scripts/local_repo_replay/templates/examples/rpki_client_example.sh
Executable file
10
scripts/local_repo_replay/templates/examples/rpki_client_example.sh
Executable file
@ -0,0 +1,10 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
./scripts/run_rpki_client_from_local_tree.sh \
|
||||||
|
--rpki-client-bin "${RPKI_CLIENT_BIN:-/opt/rpki-client/src/rpki-client}" \
|
||||||
|
--mirror-root "${MIRROR_ROOT:-/data/replay/mirror}" \
|
||||||
|
--cache-dir "${RPKI_CLIENT_CACHE_DIR:-/data/replay/work/rpki-client-cache}" \
|
||||||
|
--tal-dir "${TAL_DIR:-/data/replay/tals}" \
|
||||||
|
--out-dir "${OUT_DIR:-/data/replay/out/rpki-client}" \
|
||||||
|
--parser-workers "${PARSER_WORKERS:-4}"
|
||||||
@ -2,6 +2,7 @@
|
|||||||
# 复制为 .env 后可以在远端直接调整;所有路径默认相对 package 根目录。
|
# 复制为 .env 后可以在远端直接调整;所有路径默认相对 package 根目录。
|
||||||
|
|
||||||
# 最大运行轮次。重复执行 run_soak.sh 时会从已有最后一轮之后继续编号。
|
# 最大运行轮次。重复执行 run_soak.sh 时会从已有最后一轮之后继续编号。
|
||||||
|
# 正整数表示固定运行轮次;负数(例如 -1)表示持续运行不自动停止;0 非法。
|
||||||
MAX_RUNS=3
|
MAX_RUNS=3
|
||||||
|
|
||||||
# 两轮之间等待秒数。做连续无等待验收时设置为 0。
|
# 两轮之间等待秒数。做连续无等待验收时设置为 0。
|
||||||
@ -15,7 +16,7 @@ RIRS=afrinic,apnic,arin,lacnic,ripe
|
|||||||
# 运行根目录。默认使用 package 根目录;如需把产物写到独立数据盘,可改成绝对路径。
|
# 运行根目录。默认使用 package 根目录;如需把产物写到独立数据盘,可改成绝对路径。
|
||||||
RUN_ROOT="${PACKAGE_ROOT}"
|
RUN_ROOT="${PACKAGE_ROOT}"
|
||||||
|
|
||||||
# 保留最近多少轮 run 目录。旧 run 会由 rpki_daemon 自身或后续脚本策略清理。
|
# 保留最近多少轮 run 目录。持续运行模式建议设置为 5 或按磁盘容量评估。
|
||||||
RETAIN_RUNS=10
|
RETAIN_RUNS=10
|
||||||
|
|
||||||
# 是否输出 compact report JSON。1 表示启用,0 表示关闭。
|
# 是否输出 compact report JSON。1 表示启用,0 表示关闭。
|
||||||
|
|||||||
@ -77,6 +77,11 @@ validate_non_negative_int() {
|
|||||||
[[ "$value" =~ ^[0-9]+$ ]] || die "$name must be an integer: $value"
|
[[ "$value" =~ ^[0-9]+$ ]] || die "$name must be an integer: $value"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
validate_max_runs() {
|
||||||
|
[[ "$MAX_RUNS" =~ ^-?[0-9]+$ ]] || die "MAX_RUNS must be an integer: $MAX_RUNS"
|
||||||
|
[[ "$MAX_RUNS" != "0" ]] || die "MAX_RUNS must be non-zero; use a positive value for fixed runs or -1 for continuous mode"
|
||||||
|
}
|
||||||
|
|
||||||
validate_rsync_scope() {
|
validate_rsync_scope() {
|
||||||
case "$RSYNC_SCOPE" in
|
case "$RSYNC_SCOPE" in
|
||||||
publication-point|module-root)
|
publication-point|module-root)
|
||||||
@ -486,20 +491,30 @@ PY
|
|||||||
|
|
||||||
apply_outer_retention() {
|
apply_outer_retention() {
|
||||||
local dirs=()
|
local dirs=()
|
||||||
|
local retain_limit="$RETAIN_RUNS"
|
||||||
|
local keep_run="${1:-}"
|
||||||
local run_dir
|
local run_dir
|
||||||
shopt -s nullglob
|
shopt -s nullglob
|
||||||
for run_dir in "$RUNS_ROOT"/run_[0-9][0-9][0-9][0-9]; do
|
for run_dir in "$RUNS_ROOT"/run_[0-9][0-9][0-9][0-9]; do
|
||||||
[[ -d "$run_dir" ]] && dirs+=("$run_dir")
|
[[ -d "$run_dir" ]] && dirs+=("$run_dir")
|
||||||
done
|
done
|
||||||
shopt -u nullglob
|
shopt -u nullglob
|
||||||
if (( ${#dirs[@]} <= RETAIN_RUNS )); then
|
if (( ${#dirs[@]} <= retain_limit )); then
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
mapfile -t dirs < <(printf '%s\n' "${dirs[@]}" | sort)
|
mapfile -t dirs < <(printf '%s\n' "${dirs[@]}" | sort)
|
||||||
local remove_count=$(( ${#dirs[@]} - RETAIN_RUNS ))
|
local remove_count=$(( ${#dirs[@]} - retain_limit ))
|
||||||
local index
|
local removed_count=0
|
||||||
for (( index = 0; index < remove_count; index++ )); do
|
local candidate
|
||||||
rm -rf "${dirs[$index]}"
|
for candidate in "${dirs[@]}"; do
|
||||||
|
if [[ -n "$keep_run" && "$(basename "$candidate")" == "$keep_run" ]]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
rm -rf "$candidate"
|
||||||
|
removed_count=$((removed_count + 1))
|
||||||
|
if (( removed_count >= remove_count )); then
|
||||||
|
break
|
||||||
|
fi
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -519,6 +534,7 @@ run_one_round() {
|
|||||||
local summary_state
|
local summary_state
|
||||||
|
|
||||||
mkdir -p "$run_dir" "$daemon_state_root"
|
mkdir -p "$run_dir" "$daemon_state_root"
|
||||||
|
apply_outer_retention "$run_id"
|
||||||
started_at="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
started_at="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||||
write_run_meta "$run_dir/run-meta.json" "running" "$run_index" "$run_id" "$sync_mode" \
|
write_run_meta "$run_dir/run-meta.json" "running" "$run_index" "$run_id" "$sync_mode" \
|
||||||
"$snapshot_reason" "$previous_run_id" "$previous_success_value" "$started_at" "" \
|
"$snapshot_reason" "$previous_run_id" "$previous_success_value" "$started_at" "" \
|
||||||
@ -573,7 +589,7 @@ main() {
|
|||||||
require_command python3
|
require_command python3
|
||||||
require_command date
|
require_command date
|
||||||
require_command find
|
require_command find
|
||||||
validate_positive_int "MAX_RUNS" "$MAX_RUNS"
|
validate_max_runs
|
||||||
validate_non_negative_int "INTERVAL_SECS" "$INTERVAL_SECS"
|
validate_non_negative_int "INTERVAL_SECS" "$INTERVAL_SECS"
|
||||||
validate_positive_int "RETAIN_RUNS" "$RETAIN_RUNS"
|
validate_positive_int "RETAIN_RUNS" "$RETAIN_RUNS"
|
||||||
validate_rsync_scope
|
validate_rsync_scope
|
||||||
@ -599,12 +615,20 @@ main() {
|
|||||||
|
|
||||||
local max_index
|
local max_index
|
||||||
local next_index
|
local next_index
|
||||||
|
local run_forever=0
|
||||||
|
local stop_index=0
|
||||||
max_index="$(max_existing_run_index)"
|
max_index="$(max_existing_run_index)"
|
||||||
next_index=$((max_index + 1))
|
next_index=$((max_index + 1))
|
||||||
local stop_index=$((max_index + MAX_RUNS))
|
if (( MAX_RUNS < 0 )); then
|
||||||
|
run_forever=1
|
||||||
|
echo "run_soak mode=continuous max_existing_run_index=$max_index next_run=$(printf 'run_%04d' "$next_index")"
|
||||||
|
else
|
||||||
|
stop_index=$((max_index + MAX_RUNS))
|
||||||
|
echo "run_soak mode=fixed max_existing_run_index=$max_index next_run=$(printf 'run_%04d' "$next_index") stop_run=$(printf 'run_%04d' "$stop_index")"
|
||||||
|
fi
|
||||||
local any_failed=0
|
local any_failed=0
|
||||||
|
|
||||||
while (( next_index <= stop_index )); do
|
while (( run_forever == 1 || next_index <= stop_index )); do
|
||||||
INVALID_DB_PATH=""
|
INVALID_DB_PATH=""
|
||||||
INVALID_STATE_PATH=""
|
INVALID_STATE_PATH=""
|
||||||
INVALID_TMP_PATH=""
|
INVALID_TMP_PATH=""
|
||||||
@ -649,7 +673,7 @@ main() {
|
|||||||
echo "completed run $(printf 'run_%04d' "$next_index") status=failed" >&2
|
echo "completed run $(printf 'run_%04d' "$next_index") status=failed" >&2
|
||||||
any_failed=1
|
any_failed=1
|
||||||
fi
|
fi
|
||||||
if (( next_index < stop_index && INTERVAL_SECS > 0 )); then
|
if (( (run_forever == 1 || next_index < stop_index) && INTERVAL_SECS > 0 )); then
|
||||||
sleep "$INTERVAL_SECS"
|
sleep "$INTERVAL_SECS"
|
||||||
fi
|
fi
|
||||||
next_index=$((next_index + 1))
|
next_index=$((next_index + 1))
|
||||||
|
|||||||
2037
src/bin/rpki_artifact_metrics.rs
Normal file
2037
src/bin/rpki_artifact_metrics.rs
Normal file
File diff suppressed because it is too large
Load Diff
@ -2,6 +2,7 @@ pub mod decode;
|
|||||||
pub mod encode;
|
pub mod encode;
|
||||||
#[cfg(feature = "full")]
|
#[cfg(feature = "full")]
|
||||||
pub mod export;
|
pub mod export;
|
||||||
|
#[cfg(feature = "full")]
|
||||||
pub mod materialize;
|
pub mod materialize;
|
||||||
pub mod model;
|
pub mod model;
|
||||||
pub mod sequence;
|
pub mod sequence;
|
||||||
@ -15,6 +16,7 @@ pub use export::{
|
|||||||
CirExportError, CirExportSummary, CirTrustAnchorBinding, build_cir_from_run,
|
CirExportError, CirExportSummary, CirTrustAnchorBinding, build_cir_from_run,
|
||||||
build_cir_from_run_multi, export_cir_from_run, export_cir_from_run_multi, write_cir_file,
|
build_cir_from_run_multi, export_cir_from_run, export_cir_from_run_multi, write_cir_file,
|
||||||
};
|
};
|
||||||
|
#[cfg(feature = "full")]
|
||||||
pub use materialize::{
|
pub use materialize::{
|
||||||
CirMaterializeError, CirMaterializeSummary, materialize_cir, materialize_cir_from_raw_store,
|
CirMaterializeError, CirMaterializeSummary, materialize_cir, materialize_cir_from_raw_store,
|
||||||
materialize_cir_from_repo_bytes, mirror_relative_path_for_rsync_uri, resolve_static_pool_file,
|
materialize_cir_from_repo_bytes, mirror_relative_path_for_rsync_uri, resolve_static_pool_file,
|
||||||
|
|||||||
@ -12,6 +12,7 @@ pub mod audit_downloads;
|
|||||||
pub mod audit_trace;
|
pub mod audit_trace;
|
||||||
#[cfg(feature = "full")]
|
#[cfg(feature = "full")]
|
||||||
pub mod blob_store;
|
pub mod blob_store;
|
||||||
|
#[cfg(feature = "full")]
|
||||||
pub mod cli;
|
pub mod cli;
|
||||||
#[cfg(feature = "full")]
|
#[cfg(feature = "full")]
|
||||||
pub mod current_repo_index;
|
pub mod current_repo_index;
|
||||||
|
|||||||
@ -589,6 +589,48 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct FailRrdpThenFailRsyncExecutor {
|
||||||
|
rrdp_count: Arc<AtomicUsize>,
|
||||||
|
rsync_count: Arc<AtomicUsize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RepoTransportExecutor for FailRrdpThenFailRsyncExecutor {
|
||||||
|
fn execute_transport(&self, task: RepoTransportTask) -> RepoTransportResultEnvelope {
|
||||||
|
match task.mode {
|
||||||
|
RepoTransportMode::Rrdp => {
|
||||||
|
self.rrdp_count.fetch_add(1, Ordering::SeqCst);
|
||||||
|
RepoTransportResultEnvelope {
|
||||||
|
dedup_key: task.dedup_key,
|
||||||
|
repo_identity: task.repo_identity,
|
||||||
|
mode: RepoTransportMode::Rrdp,
|
||||||
|
tal_id: task.tal_id,
|
||||||
|
rir_id: task.rir_id,
|
||||||
|
timing_ms: 10,
|
||||||
|
result: RepoTransportResultKind::Failed {
|
||||||
|
detail: "rrdp failed".to_string(),
|
||||||
|
warnings: vec![Warning::new("rrdp failed")],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RepoTransportMode::Rsync => {
|
||||||
|
self.rsync_count.fetch_add(1, Ordering::SeqCst);
|
||||||
|
RepoTransportResultEnvelope {
|
||||||
|
dedup_key: task.dedup_key,
|
||||||
|
repo_identity: task.repo_identity,
|
||||||
|
mode: RepoTransportMode::Rsync,
|
||||||
|
tal_id: task.tal_id,
|
||||||
|
rir_id: task.rir_id,
|
||||||
|
timing_ms: 12,
|
||||||
|
result: RepoTransportResultKind::Failed {
|
||||||
|
detail: "rsync failed".to_string(),
|
||||||
|
warnings: vec![Warning::new("rsync failed")],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn phase1_runtime_waits_for_rrdp_transport_and_returns_rrdp_outcome() {
|
fn phase1_runtime_waits_for_rrdp_transport_and_returns_rrdp_outcome() {
|
||||||
let coordinator = GlobalRunCoordinator::new(
|
let coordinator = GlobalRunCoordinator::new(
|
||||||
@ -852,6 +894,42 @@ mod tests {
|
|||||||
assert_eq!(rsync_count.load(Ordering::SeqCst), 1);
|
assert_eq!(rsync_count.load(Ordering::SeqCst), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn phase1_runtime_terminal_failure_keeps_rsync_failure_duration() {
|
||||||
|
let rrdp_count = Arc::new(AtomicUsize::new(0));
|
||||||
|
let rsync_count = Arc::new(AtomicUsize::new(0));
|
||||||
|
let coordinator = GlobalRunCoordinator::new(
|
||||||
|
ParallelPhase1Config::default(),
|
||||||
|
vec![TalInputSpec::from_url("https://example.test/arin.tal")],
|
||||||
|
);
|
||||||
|
let pool = RepoTransportWorkerPool::new(
|
||||||
|
RepoWorkerPoolConfig { max_workers: 1 },
|
||||||
|
FailRrdpThenFailRsyncExecutor {
|
||||||
|
rrdp_count: Arc::clone(&rrdp_count),
|
||||||
|
rsync_count: Arc::clone(&rsync_count),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.expect("pool");
|
||||||
|
let runtime = Phase1RepoSyncRuntime::new(
|
||||||
|
coordinator,
|
||||||
|
pool,
|
||||||
|
Arc::new(|_base: &str| "rsync://example.test/module/".to_string()),
|
||||||
|
SyncPreference::RrdpThenRsync,
|
||||||
|
);
|
||||||
|
|
||||||
|
let outcome = runtime
|
||||||
|
.sync_publication_point_repo(&sample_ca("rsync://example.test/repo/root.mft"))
|
||||||
|
.expect("sync repo");
|
||||||
|
assert!(!outcome.repo_sync_ok);
|
||||||
|
assert_eq!(
|
||||||
|
outcome.repo_sync_phase.as_deref(),
|
||||||
|
Some("rrdp_failed_rsync_failed")
|
||||||
|
);
|
||||||
|
assert_eq!(outcome.repo_sync_duration_ms, 12);
|
||||||
|
assert_eq!(rrdp_count.load(Ordering::SeqCst), 1);
|
||||||
|
assert_eq!(rsync_count.load(Ordering::SeqCst), 1);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn phase1_runtime_prefetch_submits_transport_task_before_consumption() {
|
fn phase1_runtime_prefetch_submits_transport_task_before_consumption() {
|
||||||
let rrdp_count = Arc::new(AtomicUsize::new(0));
|
let rrdp_count = Arc::new(AtomicUsize::new(0));
|
||||||
|
|||||||
@ -433,6 +433,7 @@ impl<'a> PublicationPointRunner for Rpkiv1PublicationPointRunner<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let repo_sync_started = std::time::Instant::now();
|
let repo_sync_started = std::time::Instant::now();
|
||||||
|
let mut runtime_repo_sync_duration_ms = None;
|
||||||
let (repo_sync_ok, repo_sync_err, repo_sync_source, repo_sync_phase): (
|
let (repo_sync_ok, repo_sync_err, repo_sync_source, repo_sync_phase): (
|
||||||
bool,
|
bool,
|
||||||
Option<String>,
|
Option<String>,
|
||||||
@ -444,9 +445,10 @@ impl<'a> PublicationPointRunner for Rpkiv1PublicationPointRunner<'a> {
|
|||||||
repo_sync_err,
|
repo_sync_err,
|
||||||
repo_sync_source,
|
repo_sync_source,
|
||||||
repo_sync_phase,
|
repo_sync_phase,
|
||||||
repo_sync_duration_ms: _,
|
repo_sync_duration_ms,
|
||||||
warnings: repo_warnings,
|
warnings: repo_warnings,
|
||||||
} = runtime.sync_publication_point_repo(ca)?;
|
} = runtime.sync_publication_point_repo(ca)?;
|
||||||
|
runtime_repo_sync_duration_ms = Some(repo_sync_duration_ms);
|
||||||
warnings.extend(repo_warnings);
|
warnings.extend(repo_warnings);
|
||||||
(
|
(
|
||||||
repo_sync_ok,
|
repo_sync_ok,
|
||||||
@ -575,7 +577,11 @@ impl<'a> PublicationPointRunner for Rpkiv1PublicationPointRunner<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let repo_sync_duration_ms = repo_sync_started.elapsed().as_millis() as u64;
|
let repo_sync_duration_ms = effective_repo_sync_duration_ms(
|
||||||
|
repo_sync_started.elapsed().as_millis() as u64,
|
||||||
|
runtime_repo_sync_duration_ms,
|
||||||
|
repo_sync_ok,
|
||||||
|
);
|
||||||
crate::progress_log::emit(
|
crate::progress_log::emit(
|
||||||
"publication_point_repo_sync_done",
|
"publication_point_repo_sync_done",
|
||||||
serde_json::json!({
|
serde_json::json!({
|
||||||
@ -1640,6 +1646,19 @@ fn repo_sync_source_label(source: crate::sync::repo::RepoSyncSource) -> &'static
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn effective_repo_sync_duration_ms(
|
||||||
|
elapsed_ms: u64,
|
||||||
|
runtime_reported_duration_ms: Option<u64>,
|
||||||
|
repo_sync_ok: bool,
|
||||||
|
) -> u64 {
|
||||||
|
if repo_sync_ok {
|
||||||
|
return elapsed_ms;
|
||||||
|
}
|
||||||
|
runtime_reported_duration_ms
|
||||||
|
.map(|runtime_ms| elapsed_ms.max(runtime_ms))
|
||||||
|
.unwrap_or(elapsed_ms)
|
||||||
|
}
|
||||||
|
|
||||||
fn kind_from_vcir_artifact_kind(kind: VcirArtifactKind) -> AuditObjectKind {
|
fn kind_from_vcir_artifact_kind(kind: VcirArtifactKind) -> AuditObjectKind {
|
||||||
match kind {
|
match kind {
|
||||||
VcirArtifactKind::Mft => AuditObjectKind::Manifest,
|
VcirArtifactKind::Mft => AuditObjectKind::Manifest,
|
||||||
@ -6463,6 +6482,15 @@ authorityKeyIdentifier = keyid:always
|
|||||||
assert!(audit.objects.is_empty());
|
assert!(audit.objects.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn effective_repo_sync_duration_uses_runtime_duration_for_failures() {
|
||||||
|
assert_eq!(effective_repo_sync_duration_ms(0, Some(12), false), 12);
|
||||||
|
assert_eq!(effective_repo_sync_duration_ms(5, Some(12), false), 12);
|
||||||
|
assert_eq!(effective_repo_sync_duration_ms(20, Some(12), false), 20);
|
||||||
|
assert_eq!(effective_repo_sync_duration_ms(5, None, false), 5);
|
||||||
|
assert_eq!(effective_repo_sync_duration_ms(5, Some(12), true), 5);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn reconstruct_snapshot_from_vcir_reports_missing_manifest_and_related_raw_bytes() {
|
fn reconstruct_snapshot_from_vcir_reports_missing_manifest_and_related_raw_bytes() {
|
||||||
let now = time::OffsetDateTime::now_utc();
|
let now = time::OffsetDateTime::now_utc();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user