benchmarking/locust: capture cluster hardware facts and density frontiers. - #1603
Nishanth Kotla (Nishanth29) wants to merge 4 commits into
Conversation
Max Smythe (maxsmythe)
left a comment
There was a problem hiding this comment.
Thanks for this! Left a few initial comments.
| if with_boomer: | ||
| boomer_cmd = [BOOMER_BINARY, "--user-class", Path(test_file(args.file)).stem] | ||
| boomer_cmd = [BOOMER_BINARY] | ||
| if "boomer-glutton" not in os.path.basename(BOOMER_BINARY): |
There was a problem hiding this comment.
Is all this switching to avoid breaking changes? Shouldn't the binary be bundled in the container? May be able to just straight rename the binary.
There was a problem hiding this comment.
yeah, my benchmark cluster was still running an older image from before the #1295 rename, so I needed
these to get my trials running against it. None of it applies to a normal run since the Dockerfile
bundles /app/boomer-worker. Reverting to that and dropping the env knobs.
| return int(float(mem_str)) | ||
|
|
||
|
|
||
| def get_cluster_hardware_facts(logs: TextIO | None = None) -> dict[str, any]: |
There was a problem hiding this comment.
I think this batch of code is large enough and focused enough to warrant its own file.
There was a problem hiding this comment.
will move it into its own file
| "allocatable_ram_gb": None, | ||
| "worker_pod_count": None, | ||
| } | ||
| # 1. Environment variables override |
There was a problem hiding this comment.
Why do we need overrides?
There was a problem hiding this comment.
I added them as a fallback in case the runner couldn't read the cluster, but this PR adds the RBAC for
that anyway so they aren't really needed. Removing them.
| ): | ||
| return facts | ||
|
|
||
| # 2. In-cluster HTTP API or local kubectl discovery |
There was a problem hiding this comment.
I think K8s has a Python client we can use for this?
There was a problem hiding this comment.
oh yeah... I went with urllib because the locust image is distroless and I was trying to avoid
adding a dependency, but we already install pip packages in there anyway, so that isn't really a
concern. will be switching to the client.
| except Exception: | ||
| pass | ||
| elif shutil.which("kubectl"): | ||
| # Local workstation fallback via kubectl |
There was a problem hiding this comment.
Why do we need this? Also I think we may get standard auth for free w/a client
There was a problem hiding this comment.
that was just so I could run runner.py from my workstation, but you're right that the client handles
that for us. Dropping the kubectl path.
| else: | ||
| stats_generated = jsonl_path.exists() | ||
| if stats_generated: | ||
| facts = get_cluster_hardware_facts(logs) |
There was a problem hiding this comment.
for truly large clusters, we may want the ability to disable this (hitting every node may take a long time)
There was a problem hiding this comment.
good call, will be adding a --cluster-facts / --no-cluster-facts flag (default on) so it can be skipped
entirely.
|
Thanks Max, good catches. All six are fixed: reverted the boomer switching, Also fixed some telemetry bugs, rewrote the tests and documented the flags and output fields in the README. |
Fixes #1590
What this PR does
In alignment with the Actor Density Benchmark Specs, this PR teaches the Locust runner to discover cluster capacity, record actor density frontiers, and harvest server-side Prometheus metrics into
stats.jsonlandserver_summary.json. Discovery lives in its own module,benchmarking/locust/cluster_facts.py, rather than insiderunner.py.Proposed Changes
Cluster hardware discovery (
cluster_facts.py,locust.yaml)kubectlsubprocess.liston nodes and pods.--no-cluster-factsskips discovery, since listing is expensive on a large cluster.Density frontiers (
trial_summaryinstats.jsonl)actors_per_node,actors_per_vcpu,actors_per_gb_ram.ap_ratio_p50/p90/p99over the steady-state samples rather than one average, so it is visible whether the system actually pegs at 1.raw_configuration, so ratios can be re-derived after the fact.Server ground truth (
server_telemetry.py,server_summary.json)ate_workerpool_workers, as packing percentiles plus the underlying timeseries.server_telemetry.pyuses only the standard library, every call with a timeout. An unreachable Prometheus leaves nulls rather than failing the run, and--prometheus-urlretargets it.status.jsonkeeps its existing minimal schema.container="node"relabel and the snapshot queries need themetrics/rawpipeline. Merged ahead of those, the PSI and snapshot fields come backnull.Docs
benchmarking/README.mdcovers the new flags and every field in the output files.How this was tested
test_cluster_facts.pyandtest_server_telemetry.py, covering percentile edges, steady-state detection, counter resets across an atelet restart, and the null-versus-zero rules.stats.jsonl,server_summary.jsonis populated, andstatus.jsonis unchanged.References
Agent Substrate: Actor Density Benchmark Specs