diff --git a/abs.sh b/abs.sh index db330c0..76eb6fb 100755 --- a/abs.sh +++ b/abs.sh @@ -201,6 +201,8 @@ case "$PROFILE" in esac is_pos_int() { [[ "${1:-}" =~ ^[1-9][0-9]*$ ]]; } + +is_num() { [[ "${1:-}" =~ ^[0-9]+(\.[0-9]+)?$ ]]; } valid_port_range() { local range="${1:-}" low high [[ "$range" =~ ^([0-9]{1,5})(-([0-9]{1,5}))?$ ]] || return 1 @@ -1628,25 +1630,45 @@ fi if have sysbench; then if run_log cpu-1t sysbench cpu --threads=1 --time="$D" --events=0 --percentile=95 --cpu-max-prime="$CPU_PRIME" run; then - add "CPU single thread" "$(eps "$LAST_LOG") events/s, p95 $(p95 "$LAST_LOG") ms" + cpu_1t_eps="$(eps "$LAST_LOG")" + if is_num "$cpu_1t_eps"; then + add "CPU single thread" "$cpu_1t_eps events/s, p95 $(p95 "$LAST_LOG") ms" + else + add "CPU single thread" "FAILED; no events-per-second field; see $LAST_LOG" + fi else add "CPU single thread" "FAILED; see $LAST_LOG" fi if run_log cpu-all sysbench cpu --threads="$T" --time="$D" --events=0 --percentile=95 --cpu-max-prime="$CPU_PRIME" run; then - add "CPU all threads ($T)" "$(eps "$LAST_LOG") events/s, p95 $(p95 "$LAST_LOG") ms" + cpu_all_eps="$(eps "$LAST_LOG")" + if is_num "$cpu_all_eps"; then + add "CPU all threads ($T)" "$cpu_all_eps events/s, p95 $(p95 "$LAST_LOG") ms" + else + add "CPU all threads ($T)" "FAILED; no events-per-second field; see $LAST_LOG" + fi else add "CPU all threads ($T)" "FAILED; see $LAST_LOG" fi if run_log mem-read sysbench memory --threads="$T" --time="$D" --events=0 --percentile=95 --memory-block-size=1M --memory-total-size=100T --memory-oper=read --memory-access-mode=seq run; then - add "Memory read ($T threads)" "$(memspeed "$LAST_LOG")" + mem_read="$(memspeed "$LAST_LOG")" + if [ -n "$mem_read" ]; then + add "Memory read ($T threads)" "$mem_read" + else + add "Memory read" "FAILED; no throughput field; see $LAST_LOG" + fi else add "Memory read" "FAILED; see $LAST_LOG" fi if run_log mem-write sysbench memory --threads="$T" --time="$D" --events=0 --percentile=95 --memory-block-size=1M --memory-total-size=100T --memory-oper=write --memory-access-mode=seq run; then - add "Memory write ($T threads)" "$(memspeed "$LAST_LOG")" + mem_write="$(memspeed "$LAST_LOG")" + if [ -n "$mem_write" ]; then + add "Memory write ($T threads)" "$mem_write" + else + add "Memory write" "FAILED; no throughput field; see $LAST_LOG" + fi else add "Memory write" "FAILED; see $LAST_LOG" fi diff --git a/tests/test_abs.py b/tests/test_abs.py index c87adb3..b346de6 100644 --- a/tests/test_abs.py +++ b/tests/test_abs.py @@ -591,6 +591,54 @@ def test_buffered_fio_cannot_produce_comparable_score(self) -> None: self.assertEqual(result["score"]["missing_components"], ["disk-buffered", "fsync-buffered"]) self.assertEqual(result["verdict"]["code"], "INCOMPLETE") + def test_missing_cpu_eps_cannot_score_from_p95_label(self) -> None: + with tempfile.TemporaryDirectory() as td: + root = Path(td) + bin_dir = root / "bin" + data_dir = root / "data" + log_dir = root / "logs" + bin_dir.mkdir() + data_dir.mkdir() + write_fake_tools(bin_dir) + # sysbench exits 0 but emits only a percentile line — no EPS field. + sysbench = bin_dir / "sysbench" + sysbench.write_text( + "#!/usr/bin/env bash\n" + "if [ \"${1:-}\" = \"--version\" ]; then echo 'sysbench 1.0.20'; exit 0; fi\n" + "case \" ${*} \" in\n" + " *\" memory \"*) echo '102400.00 MiB transferred (30000.00 MiB/sec)' ;;\n" + " *) echo '95th percentile: 1.00' ;;\n" + "esac\n" + ) + sysbench.chmod(0o755) + result_json = root / "result.json" + env = os.environ.copy() + env.update( + { + "PATH": f"{bin_dir}:{env['PATH']}", + "DIR": str(data_dir), + "LOGDIR": str(log_dir), + "INSTALL": "0", + "ABS_LANG": "en", + } + ) + proc = subprocess.run( + [str(SCRIPT), "--quick", "-n", "--no-network", "--json-file", str(result_json)], + env=env, + text=True, + capture_output=True, + timeout=60, + ) + self.assertEqual(proc.returncode, 0, proc.stdout + proc.stderr) + result = json.loads(result_json.read_text(encoding="utf-8")) + self.assertEqual(result["score"]["status"], "partial") + self.assertFalse(result["score"]["comparable"]) + self.assertIn("cpu", result["score"]["missing_components"]) + metrics = {row["metric"]: row["result"] for row in result["results"]} + self.assertTrue(metrics["CPU single thread"].startswith("FAILED"), metrics["CPU single thread"]) + all_threads = next(v for k, v in metrics.items() if k.startswith("CPU all threads")) + self.assertTrue(all_threads.startswith("FAILED"), all_threads) + def test_json_copy_failure_returns_nonzero_without_false_success(self) -> None: with tempfile.TemporaryDirectory() as td: root = Path(td)