BINARY := target/release/runbound
PGO_DIR := /tmp/runbound-pgo
MERGED := $(PGO_DIR)/merged.profdata
QUERIES := bench/queries.txt
BENCH_DUR := 30
BENCH_QPS := 150000
.PHONY: all build release pgo pgo-instrument pgo-merge pgo-optimise \
bench install clean \
audit deny sbom audit-full
all: build
build:
cargo build
release:
cargo build --release
pgo: pgo-instrument pgo-profile pgo-merge pgo-optimise
@echo ""
@echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
@echo " PGO build complete: $(BINARY)"
@echo " Run 'make bench' to measure the improvement."
@echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
pgo-instrument:
@echo "▶ Step 1/3 — Instrumented build (profiling counters injected)..."
@mkdir -p $(PGO_DIR)
RUSTFLAGS="-Cprofile-generate=$(PGO_DIR)" \
cargo build --release
@echo " Done: $(BINARY) (instrumented — ~20% slower, for profiling only)"
pgo-profile: $(BINARY)
@echo ""
@echo "▶ Step 2/3 — Collecting profile data..."
@echo " Starting Runbound in background (instrumented build)..."
@$(BINARY) /etc/runbound/unbound.conf &
@sleep 1
@if ! command -v dnsperf > /dev/null 2>&1; then \
echo " [WARN] dnsperf not found — generating synthetic queries instead"; \
$(MAKE) _pgo-synthetic-profile; \
else \
echo " Running dnsperf for $(BENCH_DUR)s at $(BENCH_QPS) QPS..."; \
dnsperf -s 127.0.0.1 -d $(QUERIES) -Q $(BENCH_QPS) -l $(BENCH_DUR) || true; \
fi
@echo " Stopping instrumented server..."
@pkill -f "runbound.*unbound.conf" 2>/dev/null || true
@sleep 1
@echo " Profile data written to $(PGO_DIR)/"
_pgo-synthetic-profile:
@echo " Synthetic profiling: 50 000 queries via dig..."
@for i in $$(seq 1 10000); do \
dig +short @127.0.0.1 google.com A > /dev/null 2>&1; \
dig +short @127.0.0.1 cloudflare.com AAAA > /dev/null 2>&1; \
dig +short @127.0.0.1 github.com A > /dev/null 2>&1; \
dig +short @127.0.0.1 example.com MX > /dev/null 2>&1; \
dig +short @127.0.0.1 rust-lang.org A > /dev/null 2>&1; \
done
pgo-merge:
@echo ""
@echo "▶ Step 3a/3 — Merging profile data..."
@ @LLVM_PROFDATA=$$(find $$(rustup toolchain list -v | grep default | awk '{print $$2}') \
-name "llvm-profdata" 2>/dev/null | head -1); \
if [ -z "$$LLVM_PROFDATA" ]; then \
LLVM_PROFDATA=$$(command -v llvm-profdata 2>/dev/null); \
fi; \
if [ -z "$$LLVM_PROFDATA" ]; then \
echo " [ERROR] llvm-profdata not found."; \
echo " Install with: rustup component add llvm-tools-preview"; \
exit 1; \
fi; \
echo " Using: $$LLVM_PROFDATA"; \
$$LLVM_PROFDATA merge -output=$(MERGED) $(PGO_DIR)/*.profraw
@echo " Merged: $(MERGED)"
pgo-optimise:
@echo ""
@echo "▶ Step 3b/3 — Final optimised build with profile data..."
RUSTFLAGS="-Cprofile-use=$(MERGED) -Cllvm-args=-pgo-warn-missing-function" \
cargo build --release
@echo " Done: $(BINARY) (PGO-optimised)"
bench:
@if ! command -v dnsperf > /dev/null 2>&1; then \
echo "[ERROR] dnsperf not installed. On Debian/Ubuntu:"; \
echo " apt install dnsperf"; \
exit 1; \
fi
@if [ ! -f $(QUERIES) ]; then \
echo "[WARN] No query file at $(QUERIES) — using built-in queries"; \
mkdir -p bench; \
printf "google.com A\ncloudflare.com AAAA\ngithub.com A\nexample.com MX\nrust-lang.org A\n" | \
awk 'BEGIN{for(i=0;i<2000;i++) {getline line < "/dev/stdin"; print line}}' \
> $(QUERIES) 2>/dev/null || \
for i in $$(seq 10000); do \
echo "google.com A"; echo "cloudflare.com A"; \
done > $(QUERIES); \
fi
@echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
@echo " Benchmarking Runbound at 127.0.0.1:53 for $(BENCH_DUR)s"
@echo " Target: $(BENCH_QPS) QPS"
@echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
dnsperf -s 127.0.0.1 -d $(QUERIES) -Q $(BENCH_QPS) -l $(BENCH_DUR)
install: release
sudo ./install.sh
audit:
@command -v cargo-audit >/dev/null 2>&1 || \
{ echo "[ERROR] cargo-audit not installed. Run: cargo install cargo-audit"; exit 1; }
cargo audit --deny warnings
deny:
@command -v cargo-deny >/dev/null 2>&1 || \
{ echo "[ERROR] cargo-deny not installed. Run: cargo install cargo-deny"; exit 1; }
cargo deny check
sbom:
@command -v cargo-cyclonedx >/dev/null 2>&1 || \
{ echo "[ERROR] cargo-cyclonedx not installed. Run: cargo install cargo-cyclonedx"; exit 1; }
cargo cyclonedx --format json --output sbom.cdx.json
@echo "SBOM written to sbom.cdx.json"
audit-full: audit deny sbom
@command -v cargo-outdated >/dev/null 2>&1 || \
{ echo "[WARN] cargo-outdated not installed. Run: cargo install cargo-outdated"; }
@command -v cargo-outdated >/dev/null 2>&1 && cargo outdated || true
@echo ""
@echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
@echo " Full audit complete. Attach sbom.cdx.json to the GitHub release."
@echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
clean:
cargo clean
rm -rf $(PGO_DIR)