Repository navigation
benchmarking: add the egress-tunnels workload - #2716
Benjamin Elder (BenTheElder) merged 6 commits into
Conversation
c2054a9 to
9cc5d9f
Compare
9cc5d9f to
629c673
Compare
Lior Lieberman (LiorLieberman)
left a comment
There was a problem hiding this comment.
Thx Yuan, defering to Bowei Du (@bowei) on this one. My 2c is why do we need to add more actors, cant we reuse images of existing ones?
e9156ff to
8c66e38
Compare
f7d637b to
f27c177
Compare
| // Package egresstunnels is shared by the egress-tunnels driver, actor and | ||
| // target: the routes the actor serves, the request that starts its loop, the | ||
| // stats it reports, the endpoint names, and the target's certificate. | ||
| package egresstunnels |
There was a problem hiding this comment.
We should keep all of the benchmarking code into the cmd/benchmarking/... package instead of at the top level
There was a problem hiding this comment.
Done. The package now lives at cmd/benchmarking/internal/egresstunnels. PTAL
f27c177 to
3452267
Compare
|
Consolidate this into one executable to reduce the number of executables we need to build. Use Cobra to create subcommands. Use this package structure: cmd/benchmarking/egress/main.go Use Cobra flags to create two subcommands for the executable:
Not clear why we need to split off into a separate package. Can we keep everything in cmd/benchmarking/egress/xxx.go? Make sure you unexport all of the symbols. |
| // Histogram counts durations in fixed log-spaced buckets. Every Histogram | ||
| // shares the same buckets, so histograms from many actors merge exactly. | ||
| // The zero value is empty and ready to use. | ||
| type Histogram struct { |
There was a problem hiding this comment.
Can we reuse the otel.metric data structures vs creating our own?
There was a problem hiding this comment.
Done. Each measurement is an OTel instrument. The OTel Prometheus exporter serves them on GET /metrics. The driver sums the scrapes per le label and reads the quantiles from the sum.
One part stays in histogram.go: the bucket bounds. All actors must use the same bounds, so the scrapes add bucket by bucket. The SDK does not give fixed log-spaced bounds, and math.Pow differs across architectures. The code rounds each bound once to four significant digits. README §4 lists the families.
| @@ -0,0 +1,67 @@ | |||
| # egresstunnels | |||
There was a problem hiding this comment.
Can we work on this README?
Rewrite this readme with the following sections.
- The goal of what the egress benchmark tests
- General topology of the setup, seems like you run
benchmark-egress actorinside the Actor andbenchmark-egress targetfor the traffic sink. - Workflow that the Actor runs.
- Metrics that you are collecting from the benchmark Actor.
- What metrics are aggregated and will be summarized by running the experiment.
After rewriting the readme, you should structure your code etc to match the above description.
There was a problem hiding this comment.
Done. The README has these five sections:
- What the benchmark measures: the goal, the terms tunnel, blended and settled, and the costs a cell reports.
- Topology: driver,
benchmark-egress actorin the Actor, gateway, endpoint Services, andbenchmark-egress targetas the backend. - What the actor does: the routes, the
/startbody, and the per-endpoint loop. - What the actor reports: each family on
/metrics, what it counts, and what it is used for. - What a run aggregates: the merge rules, the two windows, and how each result is computed.
bbcbdbe to
fd05f73
Compare
a76ddf9 to
f166d9e
Compare
f9ce87a to
5af6b6a
Compare
5af6b6a to
37f5768
Compare
Bowei Du (bowei)
left a comment
There was a problem hiding this comment.
Looks ok.
This is benchmarking code.
The egress scale test holds many CONNECT tunnels open through the egress gateway at a fixed pace and measures what they cost. Its programs are subcommands of one binary, so there is one image to build and ship. This is the Cobra root; the subcommands follow.
The actor's contract is what the actor, the target and the driver agree on: the routes and the /start body, the endpoint names the loop calls and the egress policy allows, and the Stats the actor answers with. It opens package actor; the loop follows in its own commit. The Stats is JSON: plain counters, cumulative since /start and cheap enough to poll, and on a cut the four latency distributions, opening, latency, dns and tls, each as a Point, the fields of an OTel histogram data point over one window. The bounds, 20 per decade from 1 us to 63.1 s out of a mantissa table, travel in every Point, so a merge across actors refuses a point from an image that buckets differently; Merge and Quantile give a driver the fleet's quantiles to one bucket step and an exact maximum. The contract is plain JSON, so a driver decodes a report with the standard library alone.
targetcert issues what an HTTPS run trusts: a throwaway CA that is name-constrained to the endpoint domain, and the certificate for the endpoint Services that the target serves. The constraint keeps a certificate the driver hands out from vouching for anything but the test's own Services. The actor's HTTPS tests verify against it.
The actor is the workload inside the sandbox. On POST /start it runs one goroutine per endpoint: endpoint i opens its tunnel i intervals after the start, then requests the endpoint once per period of C intervals, so the actor sends one request per interval and a request that outlasts the period delays only its own tunnel. Each request updates the counters under one lock and records its latencies into four OTel histograms on a private MeterProvider behind a manual reader in delta temporality. GET /stats serves the counters. POST /collect?window=N cuts a window, the counters and the distributions since the previous cut, under the same lock, and answers the last cut again for the same id, so a lost answer costs the driver a retry rather than a window. POST /stop ends the loop and serves the tail window with the final counters. The loop starts on request rather than at boot because every actor restores from a golden snapshot taken before any egress policy exists. Every answer carries Benchmark-Egress-Protocol: 2, so a driver can refuse an actor whose image speaks another contract instead of misreading its report. With it come classify's low-cardinality error classes.
The target is the endpoint every tunnel leads to: a 200 with a small fixed body over plain HTTP and, given a certificate, HTTPS. One Deployment sits behind all of the test's Services, each on its own backend port, so two Services on distinct ClusterIPs cannot hand it two connections with the same 4-tuple.
The README is the design the driver and the result reader hold the workload to: what the benchmark measures, the topology, what the actor does on each route and in its loop, the fleet's traffic shape over a run, what a cut holds, how a run merges the cuts into results, and the decisions behind each piece with the alternatives they displaced. Facts only a maintainer of the code needs stay as comments in the source.
0c19343 to
5540993
Compare
Bowei Du (bowei)
left a comment
There was a problem hiding this comment.
Approved.
Perf testing related code.
Benjamin Elder (@BenTheElder) Julian Gutierrez Oschmann (@juli4n) PTAL and add it to the merge queue if you see fit. |
|
all benchmarking is fine, added to queue. |
Part of #2715
First of the PRs that measure how many CONNECT tunnels the egress gateway can hold and what each costs. This PR adds
benchmark-egress, one binary with two subcommands:actor, which runs inside each Actor and holds one tunnel per endpoint through the gateway, andtarget, the Deployment behind the endpoint Services. The driver, the deploy script and the orchestrator entry follow in later PRs.Design and details: https://github.com/ygao-g/substrate/tree/egress-tunnels-workload/cmd/benchmarking/egress. The README there is the design doc: what the benchmark measures (tunnel, blended, settled), the topology, the traffic shape over a run, what the actor does and reports, what a run computes, and the decisions with the alternatives they displaced. More specifically,
/start, then each is requested once per period of C ×intervalMs.POST /collect?window=Nanswers the counters and the distributions since the previous cut, each distribution as the fields of an OTel histogram data point with the bounds in the payload, so the driver merges across actors withPoint.Mergeand refuses an image that buckets differently. The actor caches the last cut by id: a lost answer is asked for again and costs a retry, not a window; a skipped or older id is 409.GET /statsserves the cumulative counters for a driver's gates.POST /stopis the last cut.Overview of Commits.
/startfields, theStatscounters,PointwithMergeandQuantile, the shared bounds, the collect route and window id, protocol version 2/stats,/collectwith window ids,/stop, the protocol header,classifyTesting
Unit tests cover the window ids (first cut is 1, the last id answers again byte for byte, the next id cuts, anything else is 409 with the next id named), that a repeated
/collectconsumes nothing and the sample between two answers lands in the next window, the cut invariant under concurrent reads (each window's samples equal the growth of successes since the previous cut), that a/collectracing a/stopis never a 500,Mergerefusing differing bounds by index, and the payload budget at C = 1024 (/statsunder 1 KiB, a cut under 12 KiB).Ran on GKE at 250 × 1000 and 1000 × 1000 HTTPS endpoints through one gateway replica, with the actor serving cuts: 0 errors and 0 redials in the settled window, every actor answering every round, 6.0 M settled requests at 10,070 req/s on 8.13 gateway cores at 1000 × 1000, and p99 within one bucket step of the earlier exposition-based run. The target served 1000 Services on the port range from one image.
Prepared with help from an AI coding agent; reviewed and verified by the author.