#!/bin/bash
# Runs the RI benchmark against two builds (base and patched), alternating the
# order every round so that drift or thermal throttling doesn't favor one, and
# prints the median per scenario and the difference.
#
# Usage: run.sh <base-bindir> <patched-bindir> [rounds] [rows] [selfref-roots]
#               [selfref-inval-roots]
#   e.g. run.sh ~/dev/pg-bp/bench/inst-master-base/bin \
#               ~/dev/pg-bp/bench/inst-master-patched/bin 10 1000000 200000 20000
#
# Passing the same bindir twice gives an A/A run, which measures the noise of
# the machine.  selfref_nested_inval only runs on the patched build (without
# the fix it crashes); set INVAL=off to skip it, e.g. for an A/A run.
#
# Both builds should be release builds without assertions.  The server is
# pinned to one CPU and the client to another, one client only: this measures
# per-call cost, not concurrency, and keeps the machine's load low.
set -euo pipefail

BASE_BIN=$1; PATCHED_BIN=$2
ROUNDS=${3:-10}; N=${4:-1000000}; N_SELF=${5:-200000}; N_INVAL=${6:-20000}
INVAL=${INVAL:-on}
SERVER_CPU=${SERVER_CPU:-2}; CLIENT_CPU=${CLIENT_CPU:-3}
HERE=$(cd "$(dirname "$0")" && pwd)
OUT=$HERE/out-$(date +%Y%m%d-%H%M%S)
mkdir -p "$OUT"
PORT=55440

declare -A BIN=([base]=$BASE_BIN [patched]=$PATCHED_BIN)
declare -A DO_INVAL=([base]=off [patched]=$INVAL)

pgstart() {		# label
	taskset -c "$SERVER_CPU" "${BIN[$1]}/pg_ctl" -D "$OUT/data-$1" \
		-l "$OUT/server-$1.log" -w start \
		-o "-p $PORT -c listen_addresses=localhost -c unix_socket_directories=''" >/dev/null
}
pgstop() {		# label
	"${BIN[$1]}/pg_ctl" -D "$OUT/data-$1" -m fast -w stop >/dev/null
}
psqlb() {		# label, psql args...
	local l=$1; shift
	taskset -c "$CLIENT_CPU" "${BIN[$l]}/psql" -X -q -v ON_ERROR_STOP=1 \
		-h localhost -p $PORT -d postgres "$@"
}

{
	echo "base:    $BASE_BIN"
	echo "patched: $PATCHED_BIN"
	echo "rounds=$ROUNDS n=$N n_self=$N_SELF n_inval=$N_INVAL inval=$INVAL"
	echo "server cpu=$SERVER_CPU client cpu=$CLIENT_CPU"
} > "$OUT/params.txt"

for l in base patched; do
	"${BIN[$l]}/initdb" -D "$OUT/data-$l" -A trust >/dev/null
	cat >> "$OUT/data-$l/postgresql.conf" <<-EOF
	shared_buffers = 1GB
	fsync = off
	synchronous_commit = off
	full_page_writes = off
	autovacuum = off
	max_wal_size = 20GB
	checkpoint_timeout = 1h
	EOF
	pgstart $l
	psqlb $l -t -c "SELECT version()" | head -1 > "$OUT/version-$l.txt"
	psqlb $l -f "$HERE/00_setup.sql"
	pgstop $l
done

for r in $(seq 1 "$ROUNDS"); do
	if (( r % 2 )); then order="base patched"; else order="patched base"; fi
	for l in $order; do
		echo "round $r/$ROUNDS: $l"
		pgstart $l
		psqlb $l -v build=$l -v n="$N" -v n_self="$N_SELF" \
			-v n_inval="$N_INVAL" -v inval="${DO_INVAL[$l]}" \
			-f "$HERE/20_run_once.sql" >/dev/null
		pgstop $l
	done
done

# Gather both builds' results into the base server and summarize.
for l in base patched; do
	pgstart $l
	psqlb $l -c "\\copy bench_results TO '$OUT/results-$l.csv' CSV HEADER"
	pgstop $l
done
pgstart base
psqlb base -c "TRUNCATE bench_results"
for l in base patched; do
	psqlb base -c "\\copy bench_results FROM '$OUT/results-$l.csv' CSV HEADER"
done
psqlb base -c "\\copy bench_results TO '$OUT/results.csv' CSV HEADER"
psqlb base -v base=base -v patched=patched -f "$HERE/90_summary.sql" | tee "$OUT/summary.txt"
pgstop base
echo "results in $OUT"
