20260601 sequence triage支持独立时间序列
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
@@ -56,15 +57,20 @@ def rsync_from_remote(target: str, source: str | Path, destination: Path) -> Non
|
||||
|
||||
def rsync_run_artifacts_from_remote(target: str, source: str | Path, destination: Path) -> None:
|
||||
destination.mkdir(parents=True, exist_ok=True)
|
||||
rsync_base = ["rsync", "-az", "--partial", "--partial-dir=.rsync-partial"]
|
||||
rsync_base = ["rsync", "-az", "--ignore-missing-args", "--partial", "--partial-dir=.rsync-partial"]
|
||||
for name in [
|
||||
"result.ccr",
|
||||
"result.cir",
|
||||
"report.json",
|
||||
"vrps.csv",
|
||||
"vaps.csv",
|
||||
"process-time.txt",
|
||||
"remote-run-meta.json",
|
||||
"exit-code.txt",
|
||||
"started-at.txt",
|
||||
"finished-at.txt",
|
||||
"stdout.log",
|
||||
"stderr.log",
|
||||
]:
|
||||
run_local([*rsync_base, f"{target}:{source}/{name}", f"{destination}/"])
|
||||
|
||||
@@ -260,6 +266,26 @@ def side_config(name: str) -> dict[str, Any]:
|
||||
raise SystemExit(f"unknown side config: {name}")
|
||||
|
||||
|
||||
def parse_rirs(raw: str) -> list[str]:
|
||||
rirs = [item.strip() for item in raw.split(",") if item.strip()]
|
||||
if not rirs:
|
||||
raise SystemExit("--rirs must contain at least one RIR")
|
||||
seen: set[str] = set()
|
||||
invalid: list[str] = []
|
||||
duplicate: list[str] = []
|
||||
for rir in rirs:
|
||||
if rir not in DEFAULT_RIRS:
|
||||
invalid.append(rir)
|
||||
if rir in seen:
|
||||
duplicate.append(rir)
|
||||
seen.add(rir)
|
||||
if invalid:
|
||||
raise SystemExit(f"unsupported RIR(s): {','.join(invalid)}; valid values: {','.join(DEFAULT_RIRS)}")
|
||||
if duplicate:
|
||||
raise SystemExit(f"duplicate RIR(s): {','.join(duplicate)}")
|
||||
return rirs
|
||||
|
||||
|
||||
def build_remote_command(remote_root: Path, side_name: str, side: dict[str, Any], side_label: str, seq: int, rirs: list[str]) -> tuple[Path, str]:
|
||||
run_dir = remote_root / "experiments" / "sequence" / side_label / f"run_{seq:04d}"
|
||||
state_dir = remote_root / "experiments" / "sequence" / side_label / "state" / side["rpKind"]
|
||||
@@ -429,12 +455,57 @@ def run_sequence_triage(local_exp_root: Path, args: argparse.Namespace) -> None:
|
||||
])
|
||||
|
||||
|
||||
def run_side_sequence(
|
||||
args: argparse.Namespace,
|
||||
remote_root: Path,
|
||||
local_exp_root: Path,
|
||||
side_label: str,
|
||||
side_name: str,
|
||||
side: dict[str, Any],
|
||||
seq_path: Path,
|
||||
rirs: list[str],
|
||||
) -> list[dict[str, Any]]:
|
||||
side_progress: list[dict[str, Any]] = []
|
||||
for seq in range(1, args.samples_per_side + 1):
|
||||
side_progress.append(
|
||||
run_one_side_sample(args, remote_root, local_exp_root, side_label, side_name, side, seq_path, seq, rirs)
|
||||
)
|
||||
return side_progress
|
||||
|
||||
|
||||
def run_one_side_sample(
|
||||
args: argparse.Namespace,
|
||||
remote_root: Path,
|
||||
local_exp_root: Path,
|
||||
side_label: str,
|
||||
side_name: str,
|
||||
side: dict[str, Any],
|
||||
seq_path: Path,
|
||||
seq: int,
|
||||
rirs: list[str],
|
||||
) -> dict[str, Any]:
|
||||
rir_label = ",".join(rirs)
|
||||
print(
|
||||
f"[run] {side_label} {side_name} seq={seq} rirs={rir_label} schedule={args.schedule_mode}",
|
||||
flush=True,
|
||||
)
|
||||
remote_run_dir = run_remote_sample(args.ssh_target, remote_root, side_name, side, side_label, seq, rirs)
|
||||
local_run_dir = local_exp_root / side_label / f"run_{seq:04d}"
|
||||
rsync_run_artifacts_from_remote(args.ssh_target, remote_run_dir, local_run_dir)
|
||||
item = build_sequence_item(local_exp_root, side_name, side_label, side, seq, local_run_dir)
|
||||
item["scheduleMode"] = args.schedule_mode
|
||||
append_jsonl(seq_path, item)
|
||||
print(
|
||||
f"[done] {side_label} seq={seq} wallMs={item.get('wallMs')} vrps={item.get('vrps')} vaps={item.get('vaps')} objects={item.get('cirObjectCount')} rejects={item.get('cirRejectCount')}",
|
||||
flush=True,
|
||||
)
|
||||
return item
|
||||
|
||||
|
||||
def run_experiment(args: argparse.Namespace) -> None:
|
||||
if not args.skip_build:
|
||||
build_tool_binaries()
|
||||
rirs = [item.strip() for item in args.rirs.split(",") if item.strip()]
|
||||
if set(rirs) != set(DEFAULT_RIRS) or len(rirs) != len(DEFAULT_RIRS):
|
||||
raise SystemExit("#043 remote acceptance requires all5 RIRs: afrinic,apnic,arin,lacnic,ripe")
|
||||
rirs = parse_rirs(args.rirs)
|
||||
left = side_config(args.left)
|
||||
right = side_config(args.right)
|
||||
run_root = Path(args.run_root).resolve()
|
||||
@@ -447,6 +518,7 @@ def run_experiment(args: argparse.Namespace) -> None:
|
||||
"right": args.right,
|
||||
"samplesPerSide": args.samples_per_side,
|
||||
"rirs": rirs,
|
||||
"scheduleMode": args.schedule_mode,
|
||||
"remoteRoot": str(remote_root),
|
||||
"sshTarget": args.ssh_target,
|
||||
})
|
||||
@@ -467,19 +539,24 @@ def run_experiment(args: argparse.Namespace) -> None:
|
||||
left_seq_path.unlink(missing_ok=True)
|
||||
right_seq_path.unlink(missing_ok=True)
|
||||
progress: list[dict[str, Any]] = []
|
||||
for seq in range(1, args.samples_per_side + 1):
|
||||
for side_label, side_name, side, seq_path in [("A", args.left, left, left_seq_path), ("B", args.right, right, right_seq_path)]:
|
||||
print(f"[run] {side_label} {side_name} seq={seq} all5", flush=True)
|
||||
remote_run_dir = run_remote_sample(args.ssh_target, remote_root, side_name, side, side_label, seq, rirs)
|
||||
local_run_dir = local_exp_root / side_label / f"run_{seq:04d}"
|
||||
rsync_run_artifacts_from_remote(args.ssh_target, remote_run_dir, local_run_dir)
|
||||
item = build_sequence_item(local_exp_root, side_name, side_label, side, seq, local_run_dir)
|
||||
append_jsonl(seq_path, item)
|
||||
progress.append(item)
|
||||
print(
|
||||
f"[done] {side_label} seq={seq} wallMs={item.get('wallMs')} vrps={item.get('vrps')} vaps={item.get('vaps')} objects={item.get('cirObjectCount')} rejects={item.get('cirRejectCount')}",
|
||||
flush=True,
|
||||
)
|
||||
if args.schedule_mode == "interleaved":
|
||||
for seq in range(1, args.samples_per_side + 1):
|
||||
for side_label, side_name, side, seq_path in [
|
||||
("A", args.left, left, left_seq_path),
|
||||
("B", args.right, right, right_seq_path),
|
||||
]:
|
||||
progress.append(
|
||||
run_one_side_sample(args, remote_root, local_exp_root, side_label, side_name, side, seq_path, seq, rirs)
|
||||
)
|
||||
else:
|
||||
with ThreadPoolExecutor(max_workers=2) as executor:
|
||||
futures = [
|
||||
executor.submit(run_side_sequence, args, remote_root, local_exp_root, "A", args.left, left, left_seq_path, rirs),
|
||||
executor.submit(run_side_sequence, args, remote_root, local_exp_root, "B", args.right, right, right_seq_path, rirs),
|
||||
]
|
||||
for future in as_completed(futures):
|
||||
progress.extend(future.result())
|
||||
progress.sort(key=lambda item: (str(item.get("side")), int(item.get("seq") or 0)))
|
||||
write_json(local_exp_root / "run-progress.json", progress)
|
||||
run_sequence_triage(local_exp_root, args)
|
||||
ssh_script(args.ssh_target, f"df -h /data / > {shlex.quote(str(remote_root / 'df-after.txt'))} 2>&1 || true; free -h > {shlex.quote(str(remote_root / 'free-after.txt'))} 2>&1 || true")
|
||||
@@ -496,6 +573,7 @@ def main() -> None:
|
||||
parser.add_argument("--right", default="rpki-client-standard")
|
||||
parser.add_argument("--samples-per-side", type=int, default=3)
|
||||
parser.add_argument("--rirs", default=",".join(DEFAULT_RIRS))
|
||||
parser.add_argument("--schedule-mode", choices=["interleaved", "parallel"], default="interleaved")
|
||||
parser.add_argument("--align-window-runs", type=int, default=2)
|
||||
parser.add_argument("--align-window-secs", type=int, default=1800)
|
||||
parser.add_argument("--sample-limit", type=int, default=200)
|
||||
|
||||
Reference in New Issue
Block a user