aboutsummaryrefslogtreecommitdiff
path: root/benchmark
diff options
context:
space:
mode:
authorChris Ball <chris@printf.net>2023-09-01 02:26:58 -0700
committerChris Ball <chris@printf.net>2023-09-05 01:37:13 -0700
commit0091afc7618f68a04d89ea163a40ec64793f6d50 (patch)
treeb7dd39c298d53ea97cca90a7c0201ab475c2cc84 /benchmark
parentbcaa3cb5914098455d70a6a02e898b45fbab510c (diff)
downloadafl++-0091afc7618f68a04d89ea163a40ec64793f6d50.tar.gz
Add support for multi-core benchmarking
Diffstat (limited to 'benchmark')
-rw-r--r--benchmark/benchmark.py240
1 files changed, 117 insertions, 123 deletions
diff --git a/benchmark/benchmark.py b/benchmark/benchmark.py
index bbc166ea..16057bfc 100644
--- a/benchmark/benchmark.py
+++ b/benchmark/benchmark.py
@@ -2,13 +2,89 @@
# Requires Python 3.6+.
# Author: Chris Ball <chris@printf.net>
# Ported from Marc "van Hauser" Heuse's "benchmark.sh".
+import asyncio
+import glob
+import json
+import multiprocessing
import os
-import re
import shutil
-import subprocess
import sys
+from decimal import Decimal
-def colon_value_or_none(filename: str, searchKey: str) -> str | None:
+debug = False
+
+targets = [
+ {"source": "../test-instr.c", "binary": "test-instr"},
+ {"source": "../utils/persistent_mode/test-instr.c", "binary": "test-instr-persistent-shmem"},
+]
+modes = ["single-core", "multi-core"]
+results = {}
+
+colors = {
+ "blue": "\033[1;94m",
+ "gray": "\033[1;90m",
+ "green": "\033[0;32m",
+ "red": "\033[0;31m",
+ "reset": "\033[0m",
+}
+
+async def clean_up() -> None:
+ """Remove temporary files."""
+ shutil.rmtree("in")
+ for target in targets:
+ # os.remove(target["binary"])
+ for mode in modes:
+ for outdir in glob.glob(f"/tmp/out-{mode}-{target['binary']}*"):
+ shutil.rmtree(outdir)
+
+async def check_deps() -> None:
+ """Check if the necessary files exist and are executable."""
+ if not (os.access("../afl-fuzz", os.X_OK) and os.access("../afl-cc", os.X_OK) and os.path.exists("../SanitizerCoveragePCGUARD.so")):
+ sys.exit(f"{colors['red']}Error: you need to compile AFL++ first, we need afl-fuzz, afl-clang-fast and SanitizerCoveragePCGUARD.so built.{colors['reset']}")
+
+async def prep_env() -> dict:
+ # Unset AFL_* environment variables
+ for e in list(os.environ.keys()):
+ if e.startswith("AFL_"):
+ os.environ.pop(e)
+ # Create input directory and file
+ os.makedirs("in", exist_ok=True)
+ with open("in/in.txt", "wb") as f:
+ f.write(b"\x00" * 10240)
+ # Rest of env
+ AFL_PATH = os.path.abspath("../")
+ os.environ["PATH"] = AFL_PATH + ":" + os.environ["PATH"]
+ return {
+ "AFL_BENCH_JUST_ONE": "1",
+ "AFL_DISABLE_TRIM": "1",
+ "AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES": "1",
+ "AFL_NO_UI": "1",
+ "AFL_TRY_AFFINITY": "1",
+ "PATH": f"{AFL_PATH}:{os.environ['PATH']}",
+ }
+
+async def compile_target(source: str, binary: str) -> None:
+ (returncode, stdout, stderr) = await run_command(
+ ["afl-cc", "-o", binary, source],
+ env={"AFL_INSTRUMENT": "PCGUARD", "PATH": os.environ["PATH"]},
+ )
+ if returncode != 0:
+ sys.exit(f"{colors['red']} [*] Error: afl-cc is unable to compile: {stderr} {stdout}{colors['reset']}")
+
+async def cool_down() -> None:
+ """Avoid the next test run's results being contaminated by e.g. thermal limits hit on this one."""
+ print(f"{colors['blue']}Taking a five second break to stay cool.{colors['reset']}")
+ await asyncio.sleep(10)
+
+async def run_command(args, env) -> (int | None, bytes, bytes):
+ if debug:
+ print(f"\n{colors['blue']}Launching command: {args} with env {env}{colors['reset']}")
+ p = await asyncio.create_subprocess_exec(*args, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, env=env)
+ stdout, stderr = await p.communicate()
+ return (p.returncode, stdout, stderr)
+
+async def colon_value_or_none(filename: str, searchKey: str) -> str | None:
+ """Read a value (e.g. 'cpu MHz : 4976.109') given its filename and key."""
with open(filename, "r") as fh:
for line in fh:
kv = line.split(": ", 1)
@@ -20,123 +96,41 @@ def colon_value_or_none(filename: str, searchKey: str) -> str | None:
return value
return None
-def compile_target(source: str, binary: str) -> None:
- with open("afl.log", "w") as f:
- process = subprocess.run(
- ["afl-cc", "-o", binary, source],
- stdout=f,
- stderr=subprocess.STDOUT,
- env={"AFL_INSTRUMENT": "PCGUARD", "PATH": os.environ["PATH"]}
- )
- if process.returncode != 0:
- sys.exit("Error: afl-cc is unable to compile")
-
-# Check if the necessary files exist and are executable
-if not (
- os.access("../afl-fuzz", os.X_OK)
- and os.access("../afl-cc", os.X_OK)
- and os.path.exists("../SanitizerCoveragePCGUARD.so")
-):
- sys.exit("Error: you need to compile AFL++ first, we need afl-fuzz, afl-clang-fast and SanitizerCoveragePCGUARD.so built.")
-
-print("Preparing environment")
-
-targets = [
- {"source": "../test-instr.c", "binary": "test-instr"},
- {"source": "../utils/persistent_mode/test-instr.c", "binary": "test-instr-persistent"}
-]
-
-# Unset AFL_* environment variables
-for e in list(os.environ.keys()):
- if e.startswith("AFL_"):
- os.environ.pop(e)
-
-AFL_PATH = os.path.abspath("../")
-os.environ["PATH"] = AFL_PATH + ":" + os.environ["PATH"]
-
-for target in targets:
- compile_target(target["source"], target["binary"])
-
-# Create input directory and file
-os.makedirs("in", exist_ok=True)
-with open("in/in.txt", "wb") as f:
- f.write(b"\x00" * 10240)
-
-print("Ready, starting benchmark - this will take approx 20-30 seconds ...")
-
-# Run afl-fuzz
-env_vars = {
- "AFL_DISABLE_TRIM": "1",
- "AFL_NO_UI": "1",
- "AFL_TRY_AFFINITY": "1",
- "AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES": "1",
- "AFL_BENCH_JUST_ONE": "1",
-}
-
-for target in targets:
- with open(f"afl-{target['binary']}.log", "a") as f:
- process = subprocess.run(
- [
- "afl-fuzz",
- "-i",
- "in",
- "-o",
- f"out-{target['binary']}",
- "-s",
- "123",
- "-D",
- f"./{target['binary']}",
- ],
- stdout=f,
- stderr=subprocess.STDOUT,
- env={**os.environ, **env_vars},
- )
-
-print("Analysis:")
-
-# Extract CPUID from afl.log
-with open(f"afl-test-instr.log", "r") as f:
- match = re.search(r".*try binding to.*#(\d+)", f.read())
- if not match:
- sys.exit("Couldn't see which CPU# was used in afl.log", 1)
- cpuid = match.group(1)
-
-# Print CPU model
-model = colon_value_or_none("/proc/cpuinfo", "model name")
-if model:
- print(" CPU:", model)
-
-# Print CPU frequency
-cpu_speed = None
-with open("/proc/cpuinfo", "r") as fh:
- current_cpu = None
- for line in fh:
- kv = line.split(": ", 1)
- if kv and len(kv) == 2:
- (key, value) = kv
- key = key.strip()
- value = value.strip()
- if key == "processor":
- current_cpu = value
- elif key == "cpu MHz" and current_cpu == cpuid:
- cpu_speed = value
-if cpu_speed:
- print(" Mhz:", cpu_speed)
-
-# Print execs_per_sec from fuzzer_stats
-for target in targets:
- execs = colon_value_or_none(f"out-{target['binary']}/default/fuzzer_stats", "execs_per_sec")
- if execs:
- print(f" {target['binary']} single-core execs/s:", execs)
-
-print("\nComparison: (note that values can change by 10-15% per run)")
-with open("COMPARISON", "r") as f:
- print(f.read())
-
-# Clean up
-os.remove("afl.log")
-shutil.rmtree("in")
-for target in targets:
- shutil.rmtree(f"out-{target['binary']}")
- os.remove(target["binary"])
-
+async def main() -> None:
+ # Remove stale files, if necessary.
+ try:
+ await clean_up()
+ except FileNotFoundError:
+ pass
+
+ await check_deps()
+ env_vars = await prep_env()
+ cpu_count = multiprocessing.cpu_count()
+ print(f"{colors['gray']} [*] Preparing environment{colors['reset']}")
+ print(f"{colors['gray']} [*] Ready, starting benchmark - this will take approx 1-2 minutes...{colors['reset']}")
+ for target in targets:
+ await compile_target(target["source"], target["binary"])
+ for mode in modes:
+ await cool_down()
+ print(f" [*] {mode} {target['binary']} benchmark starting, execs/s: ", end="", flush=True)
+ if mode == "single-core":
+ cpus = [0]
+ elif mode == "multi-core":
+ cpus = range(0, cpu_count)
+ basedir = f"/tmp/out-{mode}-{target['binary']}-"
+ args = [["afl-fuzz", "-i", "in", "-o", f"{basedir}{cpu}", "-M", f"{cpu}", "-s", "123", "-D", f"./{target['binary']}"] for cpu in cpus]
+ tasks = [run_command(args[cpu], env_vars) for cpu in cpus]
+ output = await asyncio.gather(*tasks)
+ if debug:
+ for _, (_, stdout, stderr) in enumerate(output):
+ print(f"{colors['blue']}Output: {stdout} {stderr}{colors['reset']}")
+ execs = sum([Decimal(await colon_value_or_none(f"{basedir}{cpu}/{cpu}/fuzzer_stats", "execs_per_sec")) for cpu in cpus])
+ print(f"{colors['green']}{execs}{colors['reset']}")
+
+ print("\nComparison: (note that values can change by 10-20% per run)")
+ with open("COMPARISON", "r") as f:
+ print(f.read())
+ await clean_up()
+
+if __name__ == "__main__":
+ asyncio.run(main()) \ No newline at end of file