mariadb-haskell-0.1.0.0: validation/supplementary.py
#!/usr/bin/env python3
"""Separate Stage 1 experiments; never change frozen workload/recorder semantics.
Retention experiments collect current live heap after GC. Ordinary timing
profiles retain the original supplementary timing regions and high-water metric.
"""
import argparse
import json
import os
from pathlib import Path
import statistics
import publish_results as live
import release_recorder as run
PROFILES = {
"connect.tcp": 30,
"parameters.select": 500,
"parameters.mixed": 500,
"transaction.empty": 300,
"batch.1000": 5,
"stream.retention": 100000,
}
RETENTION = {
"retention.10000": 10000,
"retention.1000000": 1000000,
"retention.tiny-slice": 16777216,
}
def read_sample(output, *, retention):
"""Parse the harness protocol, keeping live heap distinct from high-water."""
marker = "RETENTION" if retention else "STAGE1"
line = next(line for line in output.splitlines() if line.startswith(marker + "\t"))
_, _, *values = line.split("\t")
fields = (["units", "elapsed_ns", "current_live_bytes", "checksum"] if retention
else ["units", "elapsed_ns", "allocated_bytes", "max_live_bytes", "checksum"])
if len(fields) != len(values):
raise ValueError("unexpected supplementary sample shape")
sample = dict(zip(fields, map(int, values)))
if retention:
sample["live_heap_samples_bytes"] = [
int(line.split("\t")[2]) for line in output.splitlines()
if line.startswith("LIVE_HEAP\t")
]
return sample
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--output", type=Path, required=True)
parser.add_argument("--repetitions", type=int, default=7)
args = parser.parse_args()
if args.repetitions < 1:
parser.error("repetitions must be positive")
if args.output.exists():
parser.error("output exists; preserve previous experiment")
env = os.environ.copy()
env.update(json.loads((run.HERE / ".local/environment.json").read_text()))
env["TZ"] = "UTC"
print("MariaDB:", run.sql_client(env, "SELECT VERSION()"), flush=True)
inputs = live.input_snapshot()
# A failed fresh build raises here: an older executable is never measured.
run.command(["cabal", "build", "exe:mariadb-validation", "-O2", "--enable-tests"], timeout=300)
if live.input_snapshot()["files"] != inputs["files"]:
raise RuntimeError("inputs changed during supplementary build")
binary = run.executable("exe:mariadb-validation")
data = {"schema": 1, "recorded_at": live.now(), "inputs": inputs,
"server": run.server_metadata(env), "profiles": {}}
for ident, count in {**PROFILES, **RETENTION}.items():
retention = ident in RETENTION
samples = []
for repetition in range(args.repetitions):
output = run.command(
[binary, "stage1-bench", ident, str(count), "+RTS", "-N4", "-T", "-RTS"],
env=env, timeout=180,
).stdout
samples.append(read_sample(output, retention=retention))
if retention:
# GC checkpoints belong only to these memory experiments. Their
# elapsed time is not comparable with frozen throughput profiles.
live_values = [sample["current_live_bytes"] for sample in samples]
checkpoints = [value for sample in samples for value in sample["live_heap_samples_bytes"]]
profile = {
"samples": samples,
"kind": "retention experiment; GC checkpoints are inside this experiment only",
"median_current_live_bytes": statistics.median(live_values),
"maximum_checkpoint_live_bytes": max(live_values + checkpoints),
}
else:
profile = {
"samples": samples,
"median_ns_per_unit": statistics.median(
sample["elapsed_ns"] / sample["units"] for sample in samples
),
"median_allocated_bytes_per_unit": statistics.median(
sample["allocated_bytes"] / sample["units"] for sample in samples
),
}
data["profiles"][ident] = profile
print(ident, {key: value for key, value in profile.items() if key != "samples"}, flush=True)
if live.input_snapshot()["files"] != inputs["files"]:
raise RuntimeError("inputs changed during supplementary measurement")
args.output.parent.mkdir(parents=True, exist_ok=True)
args.output.write_text(json.dumps(data, indent=2) + "\n")
if __name__ == "__main__":
main()