mirror of
https://github.com/3proxy/3proxy.git
synced 2026-10-16 00:25:48 +08:00
bench: TLS streams, envoy, a table for each machine
tls-term-stream* and tls-init-stream* run iperf3 streams through the TLS relays. iperf3 has no TLS of its own, so bench/mix gets a TLS wrapper mode, run on the client to begin TLS towards a proxy ending it and at the origin to end the TLS a proxy begins. --proxy envoy measures envoy where nginx and haproxy are measured: as an HTTP proxy for the origin, as a TCP relay and in the TLS scenarios, its circuit breakers lifted for the scenarios opening more than 1024 connections. report.py --machine with a single machine gives its medians alone, with the client- or origin-limited ones marked; --overall marks only the best of each row. The mix client's -errlog prints every failed exchange. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
aec167f8b3
commit
97b8b9d7fb
@ -89,6 +89,7 @@ host) falls on all of them alike; the summary gives medians.
|
|||||||
| `mix-*-3k` | the same, exchanges started at 3000 a second whether the proxy keeps up or not: latencies compared at equal load |
|
| `mix-*-3k` | the same, exchanges started at 3000 a second whether the proxy keeps up or not: latencies compared at equal load |
|
||||||
| `tls-term-ka-1k`, `tls-term-ka-1m`, `tls-term-close-1k` | TLS ended by the proxy: wrk speaks HTTPS to a relay of the proxy, which passes plain TCP to the origin; kept-alive connections with 1 KB and 1 MB answers, and a new connection, so a full handshake, for every request |
|
| `tls-term-ka-1k`, `tls-term-ka-1m`, `tls-term-close-1k` | TLS ended by the proxy: wrk speaks HTTPS to a relay of the proxy, which passes plain TCP to the origin; kept-alive connections with 1 KB and 1 MB answers, and a new connection, so a full handshake, for every request |
|
||||||
| `tls-init-*` | TLS begun by the proxy: wrk speaks plain HTTP to a relay, which connects to the origin's HTTPS listener |
|
| `tls-init-*` | TLS begun by the proxy: wrk speaks plain HTTP to a relay, which connects to the origin's HTTPS listener |
|
||||||
|
| `tls-term-stream1-down`, `tls-term-stream16`, `tls-init-stream1-down`, `tls-init-stream16` | the same with iperf3 streams, one downloading or sixteen uploading |
|
||||||
|
|
||||||
### The mix scenarios
|
### The mix scenarios
|
||||||
|
|
||||||
@ -146,10 +147,11 @@ answer, and `peak_mb` divided by the pool what each open connection costs the
|
|||||||
proxy. The binaries are built with `bench/mix/build.sh`, on any system with
|
proxy. The binaries are built with `bench/mix/build.sh`, on any system with
|
||||||
Go, for both Linux architectures.
|
Go, for both Linux architectures.
|
||||||
|
|
||||||
nginx is not a forward proxy: it answers absolute URIs by passing them to its
|
nginx and envoy are not forward proxies: they answer absolute URIs by passing
|
||||||
upstream, which is the origin here. Neither nginx nor haproxy has CONNECT or
|
them to their upstream, which is the origin here. nginx and haproxy have
|
||||||
SOCKS, so they take part in the HTTP and `relay-*` scenarios but not in the
|
neither CONNECT nor SOCKS, and envoy is measured as they are, so they take
|
||||||
`connect-*` and `socks-*` ones. tinyproxy
|
part in the HTTP and `relay-*` scenarios but not in the `connect-*` and
|
||||||
|
`socks-*` ones. tinyproxy
|
||||||
and squid are HTTP proxies, measured in the HTTP and `connect-*` scenarios;
|
and squid are HTTP proxies, measured in the HTTP and `connect-*` scenarios;
|
||||||
dante is a SOCKS server, measured in the `socks-*` ones.
|
dante is a SOCKS server, measured in the `socks-*` ones.
|
||||||
|
|
||||||
@ -160,10 +162,18 @@ The `tls-*` scenarios compare TLS done in the proxy: 3proxy (`tcppm` under
|
|||||||
`proxy_ssl on`), haproxy (`mode tcp` with `bind ... ssl crt` and `server ...
|
`proxy_ssl on`), haproxy (`mode tcp` with `bind ... ssl crt` and `server ...
|
||||||
ssl`) and stunnel, which takes part in these scenarios only. Every one relays
|
ssl`) and stunnel, which takes part in these scenarios only. Every one relays
|
||||||
TCP: no HTTP is read, so what differs is the TLS and how data is moved around
|
TCP: no HTTP is read, so what differs is the TLS and how data is moved around
|
||||||
it. The certificate is made for every run: an ECDSA P-256 key signed by a CA
|
it. envoy takes part too, with BoringSSL where the others use OpenSSL. The certificate is made for every run: an ECDSA P-256 key signed by a CA
|
||||||
of its own, with the proxy's and the origin's addresses in it; nobody
|
of its own, with the proxy's and the origin's addresses in it; nobody
|
||||||
verifies it. `none` has wrk speak HTTPS to the origin itself.
|
verifies it. `none` has wrk speak HTTPS to the origin itself.
|
||||||
|
|
||||||
|
iperf3 has no TLS of its own, so for the TLS streams `bench/mix tls` wraps
|
||||||
|
its connections: on the client it begins TLS towards the proxy ending it (or
|
||||||
|
towards the origin with no proxy), at the origin it ends the TLS a proxy
|
||||||
|
begins and passes the stream to iperf3. The wrapper is in Go, a goroutine
|
||||||
|
for each direction of each connection: on one stream it, rather than the
|
||||||
|
proxy, can be the limit (about 10 Gbit/s a core), which the run's `bound`
|
||||||
|
shows.
|
||||||
|
|
||||||
wrk does not resume TLS sessions, so in `tls-term-close-1k` every connection
|
wrk does not resume TLS sessions, so in `tls-term-close-1k` every connection
|
||||||
is a full handshake. Towards the origin a proxy may resume them: nginx
|
is a full handshake. Towards the origin a proxy may resume them: nginx
|
||||||
(`proxy_ssl_session_reuse`) and haproxy do by default, so `tls-init-close-1k`
|
(`proxy_ssl_session_reuse`) and haproxy do by default, so `tls-init-close-1k`
|
||||||
@ -194,7 +204,9 @@ sudo bench/bench.py \
|
|||||||
`bench/report.py <csv>` turns a run into Markdown tables: throughput, the
|
`bench/report.py <csv>` turns a run into Markdown tables: throughput, the
|
||||||
proxy's CPU per gigabyte or per request, and its peak memory. With
|
proxy's CPU per gigabyte or per request, and its peak memory. With
|
||||||
`--machine NAME=a.csv,b.csv` once per machine it makes a single throughput
|
`--machine NAME=a.csv,b.csv` once per machine it makes a single throughput
|
||||||
table of all of them, each cell their medians and their average.
|
table of all of them, each cell their medians and their average; `--overall`
|
||||||
|
then marks only the best of each row, for tables where one group has a
|
||||||
|
single proxy.
|
||||||
|
|
||||||
After the binary, a 3proxy entry can carry options for both its services:
|
After the binary, a 3proxy entry can carry options for both its services:
|
||||||
`-s1`, `-s2` and `-s3` splice downloads, uploads or both (`-s0` keeps 0.9.0 to
|
`-s1`, `-s2` and `-s3` splice downloads, uploads or both (`-s0` keeps 0.9.0 to
|
||||||
@ -212,7 +224,12 @@ variant; `nginx::halfclose` turns on `proxy_half_close` in its relays, which
|
|||||||
nginx leaves off: then a client shutting down its sending side still gets the
|
nginx leaves off: then a client shutting down its sending side still gets the
|
||||||
answer, as through 3proxy and haproxy.
|
answer, as through 3proxy and haproxy.
|
||||||
|
|
||||||
`--proxy stunnel` measures stunnel in the `tls-*` scenarios.
|
`--proxy stunnel` measures stunnel in the `tls-*` scenarios. `--proxy envoy`
|
||||||
|
measures envoy where nginx and haproxy are measured: as an HTTP proxy for the
|
||||||
|
origin (its connections there pooled), as a TCP relay (`tcp_proxy`) and in
|
||||||
|
the TLS scenarios; its circuit breakers, which allow 1024 connections to a
|
||||||
|
cluster by default, are lifted for the scenarios that open more. The
|
||||||
|
official release binaries run as they are (`envoy` on the PATH).
|
||||||
|
|
||||||
`--scenario` picks scenarios, `--reps` and `--duration` set the repetitions
|
`--scenario` picks scenarios, `--reps` and `--duration` set the repetitions
|
||||||
and their length, `--netem "delay 1ms rate 10gbit"` puts a slower, longer link
|
and their length, `--netem "delay 1ms rate 10gbit"` puts a slower, longer link
|
||||||
|
|||||||
165
bench/bench.py
165
bench/bench.py
@ -51,6 +51,10 @@ MIX_PORTS = 16
|
|||||||
# (TLS from the client, plain to ORIGIN_HTTP) and initiate it (plain from the
|
# (TLS from the client, plain to ORIGIN_HTTP) and initiate it (plain from the
|
||||||
# client, TLS to ORIGIN_TLS). TLS holds the certificate files once made.
|
# client, TLS to ORIGIN_TLS). TLS holds the certificate files once made.
|
||||||
ORIGIN_TLS, TLS_TERM, TLS_INIT = 8443, 5443, 5444
|
ORIGIN_TLS, TLS_TERM, TLS_INIT = 8443, 5443, 5444
|
||||||
|
# the same for TLS streams: iperf3 has no TLS, so bench/mix wraps its
|
||||||
|
# connections, on the client (CLI_TLS, for TLS ended by the proxy) and at the
|
||||||
|
# origin (ORIGIN_TLS_TCP, taking the TLS begun by the proxy)
|
||||||
|
ORIGIN_TLS_TCP, TLS_TERM_TCP, TLS_INIT_TCP, CLI_TLS = 8444, 5445, 5446, 5202
|
||||||
TLS = None
|
TLS = None
|
||||||
# Each measurement takes the next of SLOTS sets of addresses: the proxy's to
|
# Each measurement takes the next of SLOTS sets of addresses: the proxy's to
|
||||||
# connect from, and the mix client's to connect from. What a run leaves in
|
# connect from, and the mix client's to connect from. What a run leaves in
|
||||||
@ -107,6 +111,10 @@ SCENARIOS = {
|
|||||||
"tls-init-ka-1k": ("http", {"file": "1k", "conns": 64, "via": "tls-init"}),
|
"tls-init-ka-1k": ("http", {"file": "1k", "conns": 64, "via": "tls-init"}),
|
||||||
"tls-init-ka-1m": ("http", {"file": "1m", "conns": 16, "via": "tls-init"}),
|
"tls-init-ka-1m": ("http", {"file": "1m", "conns": 16, "via": "tls-init"}),
|
||||||
"tls-init-close-1k": ("http", {"file": "1k", "conns": 64, "close": True, "via": "tls-init"}),
|
"tls-init-close-1k": ("http", {"file": "1k", "conns": 64, "close": True, "via": "tls-init"}),
|
||||||
|
"tls-term-stream1-down": ("stream", {"streams": 1, "reverse": True, "via": "tls-term"}),
|
||||||
|
"tls-term-stream16": ("stream", {"streams": 16, "via": "tls-term"}),
|
||||||
|
"tls-init-stream1-down": ("stream", {"streams": 1, "reverse": True, "via": "tls-init"}),
|
||||||
|
"tls-init-stream16": ("stream", {"streams": 16, "via": "tls-init"}),
|
||||||
# a pool of open connections and exchanges on random ones of them, sizes
|
# a pool of open connections and exchanges on random ones of them, sizes
|
||||||
# and pieces from a web-like profile (bench/mix): -active of them going
|
# and pieces from a web-like profile (bench/mix): -active of them going
|
||||||
# at once all the time, or started at a fixed rate whatever the proxy
|
# at once all the time, or started at a fixed rate whatever the proxy
|
||||||
@ -445,6 +453,11 @@ http {{
|
|||||||
procs = [spawn("bsrv", "srv", [tool("nginx"), "-c", conf, "-p", work], log),
|
procs = [spawn("bsrv", "srv", [tool("nginx"), "-c", conf, "-p", work], log),
|
||||||
spawn("bsrv", "srv", [tool("iperf3"), "-s", "-B", SRV_ADDR, "-p", str(ORIGIN_TCP)], log)]
|
spawn("bsrv", "srv", [tool("iperf3"), "-s", "-B", SRV_ADDR, "-p", str(ORIGIN_TCP)], log)]
|
||||||
ports = [ORIGIN_HTTP, ORIGIN_TCP] + ([ORIGIN_TLS] if TLS else [])
|
ports = [ORIGIN_HTTP, ORIGIN_TCP] + ([ORIGIN_TLS] if TLS else [])
|
||||||
|
if args.wrap:
|
||||||
|
procs.append(spawn("bsrv", "srv", [args.wrap, "tls", "-listen", f"{SRV_ADDR}:{ORIGIN_TLS_TCP}",
|
||||||
|
"-connect", f"{SRV_ADDR}:{ORIGIN_TCP}",
|
||||||
|
"-cert", TLS["cert"], "-key", TLS["key"]], log))
|
||||||
|
ports.append(ORIGIN_TLS_TCP)
|
||||||
if args.mix:
|
if args.mix:
|
||||||
procs.append(spawn("bsrv", "srv", [args.mix, "origin", "-listen", f"{SRV_ADDR}:{ORIGIN_MIX}",
|
procs.append(spawn("bsrv", "srv", [args.mix, "origin", "-listen", f"{SRV_ADDR}:{ORIGIN_MIX}",
|
||||||
"-ports", str(MIX_PORTS)], log))
|
"-ports", str(MIX_PORTS)], log))
|
||||||
@ -458,6 +471,64 @@ http {{
|
|||||||
|
|
||||||
# --- proxies -----------------------------------------------------------------
|
# --- proxies -----------------------------------------------------------------
|
||||||
|
|
||||||
|
def envoy_conf(work, label, out):
|
||||||
|
"""envoy as nginx and haproxy are measured: an HTTP proxy for the origin
|
||||||
|
(absolute URIs taken, the origin's connections pooled), TCP relays, and
|
||||||
|
the TLS ones. Its default circuit breakers allow 1024 connections to a
|
||||||
|
cluster, far fewer than the scenarios open: they are lifted."""
|
||||||
|
def addr(host, port):
|
||||||
|
return {"socket_address": {"address": host, "port_value": port}}
|
||||||
|
|
||||||
|
def cluster(name, port, tls=False):
|
||||||
|
c = {"name": name, "type": "STATIC", "connect_timeout": "5s",
|
||||||
|
"upstream_bind_config": {"source_address": {"address": out, "port_value": 0}},
|
||||||
|
"circuit_breakers": {"thresholds": [{"max_connections": 1000000,
|
||||||
|
"max_pending_requests": 1000000,
|
||||||
|
"max_requests": 1000000}]},
|
||||||
|
"load_assignment": {"cluster_name": name, "endpoints": [{"lb_endpoints": [
|
||||||
|
{"endpoint": {"address": addr(SRV_ADDR, port)}}]}]}}
|
||||||
|
if tls:
|
||||||
|
c["transport_socket"] = {"name": "envoy.transport_sockets.tls", "typed_config": {
|
||||||
|
"@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext"}}
|
||||||
|
return c
|
||||||
|
|
||||||
|
def http(name, port, dest):
|
||||||
|
return {"name": name, "address": addr(PRX_IN, port), "filter_chains": [{"filters": [{
|
||||||
|
"name": "envoy.filters.network.http_connection_manager", "typed_config": {
|
||||||
|
"@type": "type.googleapis.com/envoy.extensions.filters.network."
|
||||||
|
"http_connection_manager.v3.HttpConnectionManager",
|
||||||
|
"stat_prefix": name, "codec_type": "HTTP1",
|
||||||
|
"route_config": {"virtual_hosts": [{"name": "all", "domains": ["*"], "routes": [
|
||||||
|
{"match": {"prefix": "/"}, "route": {"cluster": dest, "timeout": "0s"}}]}]},
|
||||||
|
"http_filters": [{"name": "envoy.filters.http.router", "typed_config": {
|
||||||
|
"@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router"}}]}}]}]}
|
||||||
|
|
||||||
|
def tcp(name, port, dest, tls=False):
|
||||||
|
chain = {"filters": [{"name": "envoy.filters.network.tcp_proxy", "typed_config": {
|
||||||
|
"@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy",
|
||||||
|
"stat_prefix": name, "cluster": dest}}]}
|
||||||
|
if tls:
|
||||||
|
chain["transport_socket"] = {"name": "envoy.transport_sockets.tls", "typed_config": {
|
||||||
|
"@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext",
|
||||||
|
"common_tls_context": {"tls_certificates": [{
|
||||||
|
"certificate_chain": {"filename": TLS["cert"]},
|
||||||
|
"private_key": {"filename": TLS["key"]}}]}}}
|
||||||
|
return {"name": name, "address": addr(PRX_IN, port), "filter_chains": [chain]}
|
||||||
|
|
||||||
|
listeners = [http("http", HTTP_PORT, "origin"), http("httpmix", HTTP_MIX, "mix"),
|
||||||
|
tcp("tcp", TCP_PORT, "tcp"), tcp("tcphttp", RELAY_HTTP, "origin"),
|
||||||
|
tcp("tcpmix", RELAY_MIX, "mix")]
|
||||||
|
clusters = [cluster("origin", ORIGIN_HTTP), cluster("mix", ORIGIN_MIX), cluster("tcp", ORIGIN_TCP)]
|
||||||
|
if TLS:
|
||||||
|
listeners += [tcp("tlsterm", TLS_TERM, "origin", True), tcp("tlstermtcp", TLS_TERM_TCP, "tcp", True),
|
||||||
|
tcp("tlsinit", TLS_INIT, "origintls"), tcp("tlsinittcp", TLS_INIT_TCP, "tcptls")]
|
||||||
|
clusters += [cluster("origintls", ORIGIN_TLS, True), cluster("tcptls", ORIGIN_TLS_TCP, True)]
|
||||||
|
conf = os.path.join(work, f"{label}.json")
|
||||||
|
with open(conf, "w") as f:
|
||||||
|
json.dump({"static_resources": {"listeners": listeners, "clusters": clusters}}, f, indent=1)
|
||||||
|
return conf
|
||||||
|
|
||||||
|
|
||||||
def proxy_conf(kind, work, ncpu, label, opts="", out=PRX_OUT):
|
def proxy_conf(kind, work, ncpu, label, opts="", out=PRX_OUT):
|
||||||
"""Configuration for one proxy: an HTTP proxy on HTTP_PORT and a TCP relay
|
"""Configuration for one proxy: an HTTP proxy on HTTP_PORT and a TCP relay
|
||||||
on TCP_PORT, both towards the origin. Returns the argv. opts are added to
|
on TCP_PORT, both towards the origin. Returns the argv. opts are added to
|
||||||
@ -479,12 +550,17 @@ socks {opts} -i{PRX_IN} -e{out} -p{SOCKS_PORT}
|
|||||||
ssl_server_key {TLS["key"]}
|
ssl_server_key {TLS["key"]}
|
||||||
ssl_serv
|
ssl_serv
|
||||||
tcppm {opts} -i{PRX_IN} -e{out} {TLS_TERM} {SRV_ADDR} {ORIGIN_HTTP}
|
tcppm {opts} -i{PRX_IN} -e{out} {TLS_TERM} {SRV_ADDR} {ORIGIN_HTTP}
|
||||||
|
tcppm {opts} -i{PRX_IN} -e{out} {TLS_TERM_TCP} {SRV_ADDR} {ORIGIN_TCP}
|
||||||
ssl_noserv
|
ssl_noserv
|
||||||
ssl_cli
|
ssl_cli
|
||||||
tcppm {opts} -i{PRX_IN} -e{out} {TLS_INIT} {SRV_ADDR} {ORIGIN_TLS}
|
tcppm {opts} -i{PRX_IN} -e{out} {TLS_INIT} {SRV_ADDR} {ORIGIN_TLS}
|
||||||
|
tcppm {opts} -i{PRX_IN} -e{out} {TLS_INIT_TCP} {SRV_ADDR} {ORIGIN_TLS_TCP}
|
||||||
ssl_nocli
|
ssl_nocli
|
||||||
""")
|
""")
|
||||||
return [conf]
|
return [conf]
|
||||||
|
if kind == "envoy":
|
||||||
|
return ["-c", envoy_conf(work, label, out), "--concurrency", str(ncpu),
|
||||||
|
"--log-level", "error", "--disable-hot-restart"]
|
||||||
if kind == "stunnel":
|
if kind == "stunnel":
|
||||||
conf = os.path.join(work, f"{label}.conf")
|
conf = os.path.join(work, f"{label}.conf")
|
||||||
with open(conf, "w") as f:
|
with open(conf, "w") as f:
|
||||||
@ -503,6 +579,17 @@ client = yes
|
|||||||
accept = {PRX_IN}:{TLS_INIT}
|
accept = {PRX_IN}:{TLS_INIT}
|
||||||
connect = {SRV_ADDR}:{ORIGIN_TLS}
|
connect = {SRV_ADDR}:{ORIGIN_TLS}
|
||||||
local = {out}
|
local = {out}
|
||||||
|
[termtcp]
|
||||||
|
accept = {PRX_IN}:{TLS_TERM_TCP}
|
||||||
|
connect = {SRV_ADDR}:{ORIGIN_TCP}
|
||||||
|
local = {out}
|
||||||
|
cert = {TLS["cert"]}
|
||||||
|
key = {TLS["key"]}
|
||||||
|
[inittcp]
|
||||||
|
client = yes
|
||||||
|
accept = {PRX_IN}:{TLS_INIT_TCP}
|
||||||
|
connect = {SRV_ADDR}:{ORIGIN_TLS_TCP}
|
||||||
|
local = {out}
|
||||||
""")
|
""")
|
||||||
return [conf]
|
return [conf]
|
||||||
if kind == "tinyproxy":
|
if kind == "tinyproxy":
|
||||||
@ -565,12 +652,15 @@ socks pass {{ from: 0.0.0.0/0 to: 0.0.0.0/0 }}
|
|||||||
halfclose = " proxy_half_close on;\n" if opts == "halfclose" else ""
|
halfclose = " proxy_half_close on;\n" if opts == "halfclose" else ""
|
||||||
nginx_tls = ""
|
nginx_tls = ""
|
||||||
if TLS:
|
if TLS:
|
||||||
nginx_tls = (f" server {{\n listen {PRX_IN}:{TLS_TERM} ssl backlog=65535 reuseport;\n"
|
nginx_tls = ""
|
||||||
f" ssl_certificate {TLS['cert']};\n ssl_certificate_key {TLS['key']};\n"
|
for port, dest in ((TLS_TERM, ORIGIN_HTTP), (TLS_TERM_TCP, ORIGIN_TCP)):
|
||||||
f" proxy_pass {SRV_ADDR}:{ORIGIN_HTTP};\n proxy_bind {out};\n }}\n"
|
nginx_tls += (f" server {{\n listen {PRX_IN}:{port} ssl backlog=65535 reuseport;\n"
|
||||||
f" server {{\n listen {PRX_IN}:{TLS_INIT} backlog=65535 reuseport;\n"
|
f" ssl_certificate {TLS['cert']};\n ssl_certificate_key {TLS['key']};\n"
|
||||||
f" proxy_pass {SRV_ADDR}:{ORIGIN_TLS};\n proxy_ssl on;\n"
|
f" proxy_pass {SRV_ADDR}:{dest};\n proxy_bind {out};\n }}\n")
|
||||||
f" proxy_bind {out};\n }}\n")
|
for port, dest in ((TLS_INIT, ORIGIN_TLS), (TLS_INIT_TCP, ORIGIN_TLS_TCP)):
|
||||||
|
nginx_tls += (f" server {{\n listen {PRX_IN}:{port} backlog=65535 reuseport;\n"
|
||||||
|
f" proxy_pass {SRV_ADDR}:{dest};\n proxy_ssl on;\n"
|
||||||
|
f" proxy_bind {out};\n }}\n")
|
||||||
f.write(f"""{load}daemon off;
|
f.write(f"""{load}daemon off;
|
||||||
worker_processes {ncpu};
|
worker_processes {ncpu};
|
||||||
pid {work}/{label}.pid;
|
pid {work}/{label}.pid;
|
||||||
@ -691,6 +781,16 @@ listen tlsinit
|
|||||||
bind {PRX_IN}:{TLS_INIT}
|
bind {PRX_IN}:{TLS_INIT}
|
||||||
source {out}
|
source {out}
|
||||||
server origin {SRV_ADDR}:{ORIGIN_TLS} ssl verify none
|
server origin {SRV_ADDR}:{ORIGIN_TLS} ssl verify none
|
||||||
|
listen tlstermtcp
|
||||||
|
mode tcp
|
||||||
|
bind {PRX_IN}:{TLS_TERM_TCP} ssl crt {TLS["pem"]}
|
||||||
|
source {out}
|
||||||
|
server origin {SRV_ADDR}:{ORIGIN_TCP}
|
||||||
|
listen tlsinittcp
|
||||||
|
mode tcp
|
||||||
|
bind {PRX_IN}:{TLS_INIT_TCP}
|
||||||
|
source {out}
|
||||||
|
server origin {SRV_ADDR}:{ORIGIN_TLS_TCP} ssl verify none
|
||||||
""")
|
""")
|
||||||
return ["-f", conf]
|
return ["-f", conf]
|
||||||
raise ValueError(kind)
|
raise ValueError(kind)
|
||||||
@ -715,6 +815,9 @@ class Proxy:
|
|||||||
sys.exit("nginx options: halfclose")
|
sys.exit("nginx options: halfclose")
|
||||||
self.binary = path or tool(kind)
|
self.binary = path or tool(kind)
|
||||||
self.label = kind + ("-" + self.opts if self.opts else "")
|
self.label = kind + ("-" + self.opts if self.opts else "")
|
||||||
|
elif kind == "envoy":
|
||||||
|
self.binary = path or tool("envoy")
|
||||||
|
self.label = kind
|
||||||
elif kind in ("tinyproxy", "squid", "dante", "stunnel"):
|
elif kind in ("tinyproxy", "squid", "dante", "stunnel"):
|
||||||
# dante's server is sockd, danted on Debian; stunnel stunnel4 there
|
# dante's server is sockd, danted on Debian; stunnel stunnel4 there
|
||||||
self.binary = path or (tool("sockd", "danted") if kind == "dante" else
|
self.binary = path or (tool("sockd", "danted") if kind == "dante" else
|
||||||
@ -731,12 +834,14 @@ class Proxy:
|
|||||||
PORTS = {"3proxy": (HTTP_PORT, TCP_PORT, SOCKS_PORT, RELAY_HTTP, RELAY_MIX),
|
PORTS = {"3proxy": (HTTP_PORT, TCP_PORT, SOCKS_PORT, RELAY_HTTP, RELAY_MIX),
|
||||||
"nginx": (HTTP_PORT, TCP_PORT, RELAY_HTTP, HTTP_MIX, RELAY_MIX),
|
"nginx": (HTTP_PORT, TCP_PORT, RELAY_HTTP, HTTP_MIX, RELAY_MIX),
|
||||||
"haproxy": (HTTP_PORT, TCP_PORT, RELAY_HTTP, HTTP_MIX, RELAY_MIX),
|
"haproxy": (HTTP_PORT, TCP_PORT, RELAY_HTTP, HTTP_MIX, RELAY_MIX),
|
||||||
|
"envoy": (HTTP_PORT, TCP_PORT, RELAY_HTTP, HTTP_MIX, RELAY_MIX),
|
||||||
"tinyproxy": (HTTP_PORT,), "squid": (HTTP_PORT,), "dante": (SOCKS_PORT,),
|
"tinyproxy": (HTTP_PORT,), "squid": (HTTP_PORT,), "dante": (SOCKS_PORT,),
|
||||||
"stunnel": (TLS_TERM, TLS_INIT)}
|
"stunnel": (TLS_TERM, TLS_INIT, TLS_TERM_TCP, TLS_INIT_TCP)}
|
||||||
|
|
||||||
def ports(self):
|
def ports(self):
|
||||||
"""What to wait for: with TLS scenarios the relays for them too."""
|
"""What to wait for: with TLS scenarios the relays for them too."""
|
||||||
extra = (TLS_TERM, TLS_INIT) if TLS and self.kind in ("3proxy", "nginx", "haproxy") else ()
|
extra = (TLS_TERM, TLS_INIT, TLS_TERM_TCP, TLS_INIT_TCP) \
|
||||||
|
if TLS and self.kind in ("3proxy", "nginx", "haproxy", "envoy") else ()
|
||||||
return self.PORTS[self.kind] + extra
|
return self.PORTS[self.kind] + extra
|
||||||
|
|
||||||
def serves(self, scenario):
|
def serves(self, scenario):
|
||||||
@ -745,14 +850,14 @@ class Proxy:
|
|||||||
# TLS ended or begun on a relay: what 3proxy, nginx, haproxy and
|
# TLS ended or begun on a relay: what 3proxy, nginx, haproxy and
|
||||||
# stunnel do, and only stunnel
|
# stunnel do, and only stunnel
|
||||||
if via in ("tls-term", "tls-init"):
|
if via in ("tls-term", "tls-init"):
|
||||||
return self.kind in ("3proxy", "nginx", "haproxy", "stunnel", "none")
|
return self.kind in ("3proxy", "nginx", "haproxy", "envoy", "stunnel", "none")
|
||||||
if self.kind == "stunnel":
|
if self.kind == "stunnel":
|
||||||
return False
|
return False
|
||||||
if self.kind == "dante":
|
if self.kind == "dante":
|
||||||
return via == "socks"
|
return via == "socks"
|
||||||
if self.kind in ("tinyproxy", "squid"):
|
if self.kind in ("tinyproxy", "squid"):
|
||||||
return via == "connect" or (kind in ("http", "mix") and not via)
|
return via == "connect" or (kind in ("http", "mix") and not via)
|
||||||
if self.kind in ("nginx", "haproxy"):
|
if self.kind in ("nginx", "haproxy", "envoy"):
|
||||||
# no CONNECT, no SOCKS
|
# no CONNECT, no SOCKS
|
||||||
return via in (None, "relay")
|
return via in (None, "relay")
|
||||||
return True
|
return True
|
||||||
@ -876,12 +981,38 @@ def run_http(proxy, params, args, work, mark):
|
|||||||
|
|
||||||
|
|
||||||
def run_stream(proxy, params, args, work, mark):
|
def run_stream(proxy, params, args, work, mark):
|
||||||
host, port, prefix = target(proxy, params, work, ORIGIN_TCP, TCP_PORT, TCP_PORT)
|
via = params.get("via")
|
||||||
|
wrapper = None
|
||||||
|
if via in ("tls-term", "tls-init"):
|
||||||
|
# iperf3 talks to a wrapper on the client; with TLS ended by the
|
||||||
|
# proxy, or with no proxy, it begins TLS there, and the origin's
|
||||||
|
# wrapper ends it; with TLS begun by the proxy it passes plain TCP
|
||||||
|
if proxy.kind == "none":
|
||||||
|
dest, tls = f"{SRV_ADDR}:{ORIGIN_TLS_TCP}", True
|
||||||
|
elif via == "tls-term":
|
||||||
|
dest, tls = f"{PRX_IN}:{TLS_TERM_TCP}", True
|
||||||
|
else:
|
||||||
|
dest, tls = f"{PRX_IN}:{TLS_INIT_TCP}", False
|
||||||
|
if tls:
|
||||||
|
log = open(os.path.join(work, "wrap.out"), "a")
|
||||||
|
wrapper = spawn("bcli", "cli", [args.wrap, "tls", "-listen", f"{CLI_ADDR}:{CLI_TLS}",
|
||||||
|
"-connect", dest], log)
|
||||||
|
wait_port("bcli", CLI_ADDR, CLI_TLS, 10)
|
||||||
|
host, port = CLI_ADDR, CLI_TLS
|
||||||
|
else:
|
||||||
|
host, port = PRX_IN, TLS_INIT_TCP
|
||||||
|
prefix = []
|
||||||
|
else:
|
||||||
|
host, port, prefix = target(proxy, params, work, ORIGIN_TCP, TCP_PORT, TCP_PORT)
|
||||||
argv = prefix + [tool("iperf3"), "-c", host, "-p", str(port), "-t", str(args.duration),
|
argv = prefix + [tool("iperf3"), "-c", host, "-p", str(port), "-t", str(args.duration),
|
||||||
"-P", str(params["streams"]), "-J"]
|
"-P", str(params["streams"]), "-J"]
|
||||||
if params.get("reverse"):
|
if params.get("reverse"):
|
||||||
argv.append("-R")
|
argv.append("-R")
|
||||||
out = run_in("bcli", "cli", argv, args.duration + 30)
|
try:
|
||||||
|
out = run_in("bcli", "cli", argv, args.duration + 30)
|
||||||
|
finally:
|
||||||
|
if wrapper:
|
||||||
|
stop(wrapper)
|
||||||
res = {"raw": out, "rate": float("nan"), "p50_ms": float("nan"),
|
res = {"raw": out, "rate": float("nan"), "p50_ms": float("nan"),
|
||||||
"p99_ms": float("nan"), "requests": 0, "errors": 0, "retrans": 0, "timeouts": 0}
|
"p99_ms": float("nan"), "requests": 0, "errors": 0, "retrans": 0, "timeouts": 0}
|
||||||
try:
|
try:
|
||||||
@ -908,7 +1039,7 @@ def run_mix(proxy, params, args, work, mark):
|
|||||||
addr = f"{PRX_IN}:{RELAY_MIX}"
|
addr = f"{PRX_IN}:{RELAY_MIX}"
|
||||||
elif via == "socks":
|
elif via == "socks":
|
||||||
addr = f"{PRX_IN}:{SOCKS_PORT}"
|
addr = f"{PRX_IN}:{SOCKS_PORT}"
|
||||||
elif via == "http" and proxy.kind in ("nginx", "haproxy"):
|
elif via == "http" and proxy.kind in ("nginx", "haproxy", "envoy"):
|
||||||
addr = f"{PRX_IN}:{HTTP_MIX}"
|
addr = f"{PRX_IN}:{HTTP_MIX}"
|
||||||
else:
|
else:
|
||||||
addr = f"{PRX_IN}:{HTTP_PORT}"
|
addr = f"{PRX_IN}:{HTTP_PORT}"
|
||||||
@ -1088,6 +1219,14 @@ def main():
|
|||||||
sys.exit(f"{args.mix} is missing: build it with bench/mix/build.sh")
|
sys.exit(f"{args.mix} is missing: build it with bench/mix/build.sh")
|
||||||
else:
|
else:
|
||||||
args.mix = None
|
args.mix = None
|
||||||
|
# the TLS streams need its TLS wrapper
|
||||||
|
args.wrap = None
|
||||||
|
if any(SCENARIOS[s][0] == "stream" and SCENARIOS[s][1].get("via") in ("tls-term", "tls-init")
|
||||||
|
for s in scenarios):
|
||||||
|
arch = {"x86_64": "amd64", "aarch64": "arm64"}.get(os.uname().machine, os.uname().machine)
|
||||||
|
args.wrap = os.path.abspath(os.path.join(os.path.dirname(__file__), "mix", f"mix-linux-{arch}"))
|
||||||
|
if not os.access(args.wrap, os.X_OK):
|
||||||
|
sys.exit(f"{args.wrap} is missing: build it with bench/mix/build.sh")
|
||||||
|
|
||||||
stamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
|
stamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
|
||||||
global TLS
|
global TLS
|
||||||
|
|||||||
@ -26,6 +26,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/tls"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
@ -45,6 +46,62 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// --- a TLS wrapper ---------------------------------------------------------------
|
||||||
|
|
||||||
|
// wrap relays connections to listen to target, with TLS on one side: given a
|
||||||
|
// certificate it takes TLS and passes plain TCP on (the end of a TLS stream
|
||||||
|
// at the origin), without one it takes plain TCP and begins TLS (for a client
|
||||||
|
// with no TLS of its own, iperf3). A side shut down for writing is shut down
|
||||||
|
// on the other side too, so half-closed connections work.
|
||||||
|
func wrap(listen, target, cert, key string) error {
|
||||||
|
var server *tls.Config
|
||||||
|
if cert != "" {
|
||||||
|
pair, err := tls.LoadX509KeyPair(cert, key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
server = &tls.Config{Certificates: []tls.Certificate{pair}}
|
||||||
|
}
|
||||||
|
ln, err := net.Listen("tcp", listen)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
c, err := ln.Accept()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
var in, out net.Conn = c, nil
|
||||||
|
var err error
|
||||||
|
if server != nil {
|
||||||
|
in = tls.Server(c, server)
|
||||||
|
out, err = net.Dial("tcp", target)
|
||||||
|
} else {
|
||||||
|
out, err = tls.Dial("tcp", target, &tls.Config{InsecureSkipVerify: true})
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
c.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
done := make(chan struct{}, 2)
|
||||||
|
pass := func(dst, src net.Conn) {
|
||||||
|
io.Copy(dst, src)
|
||||||
|
if cw, ok := dst.(interface{ CloseWrite() error }); ok {
|
||||||
|
cw.CloseWrite()
|
||||||
|
}
|
||||||
|
done <- struct{}{}
|
||||||
|
}
|
||||||
|
go pass(out, in)
|
||||||
|
go pass(in, out)
|
||||||
|
<-done
|
||||||
|
<-done
|
||||||
|
in.Close()
|
||||||
|
out.Close()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- the profile -------------------------------------------------------------
|
// --- the profile -------------------------------------------------------------
|
||||||
|
|
||||||
// lognormal draws from a lognormal distribution by its median and sigma,
|
// lognormal draws from a lognormal distribution by its median and sigma,
|
||||||
@ -332,6 +389,7 @@ type client struct {
|
|||||||
aborts, resets, halfcloses atomic.Uint64
|
aborts, resets, halfcloses atomic.Uint64
|
||||||
complete, first histogram
|
complete, first histogram
|
||||||
dialErr, exchErr atomic.Value // the first of each, as text
|
dialErr, exchErr atomic.Value // the first of each, as text
|
||||||
|
errlog bool // every failed exchange to stderr
|
||||||
}
|
}
|
||||||
|
|
||||||
// shard is a share of the pool with a dispatcher of its own: one dispatcher
|
// shard is a share of the pool with a dispatcher of its own: one dispatcher
|
||||||
@ -470,8 +528,10 @@ func (cl *client) release(c int) {
|
|||||||
func (cl *client) worker(id int, c net.Conn) {
|
func (cl *client) worker(id int, c net.Conn) {
|
||||||
scratch := make([]byte, 64<<10)
|
scratch := make([]byte, 64<<10)
|
||||||
head := make([]byte, 0, 4096)
|
head := make([]byte, 0, 4096)
|
||||||
|
nth := 0 // exchanges on this connection so far
|
||||||
for j := range cl.jobs[id] {
|
for j := range cl.jobs[id] {
|
||||||
if c == nil {
|
if c == nil {
|
||||||
|
nth = 0
|
||||||
var err error
|
var err error
|
||||||
if c, err = cl.dial(id); err != nil {
|
if c, err = cl.dial(id); err != nil {
|
||||||
cl.dialErr.CompareAndSwap(nil, err.Error())
|
cl.dialErr.CompareAndSwap(nil, err.Error())
|
||||||
@ -488,6 +548,7 @@ func (cl *client) worker(id int, c net.Conn) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
n, err := cl.exchange(c, id, j, head, scratch)
|
n, err := cl.exchange(c, id, j, head, scratch)
|
||||||
|
nth++
|
||||||
j.slot <- struct{}{}
|
j.slot <- struct{}{}
|
||||||
if err == errGone {
|
if err == errGone {
|
||||||
// closed as drawn, with a reset or not
|
// closed as drawn, with a reset or not
|
||||||
@ -509,6 +570,9 @@ func (cl *client) worker(id int, c net.Conn) {
|
|||||||
if err != nil || j.e.close || halfclosed {
|
if err != nil || j.e.close || halfclosed {
|
||||||
c.Close()
|
c.Close()
|
||||||
c = nil
|
c = nil
|
||||||
|
if err != nil && cl.errlog {
|
||||||
|
fmt.Fprintf(os.Stderr, "error: connection %d, exchange %d on it: %v\n", id, nth, err)
|
||||||
|
}
|
||||||
if err != nil && cl.measuring.Load() {
|
if err != nil && cl.measuring.Load() {
|
||||||
cl.exchErr.CompareAndSwap(nil, err.Error())
|
cl.exchErr.CompareAndSwap(nil, err.Error())
|
||||||
var ne net.Error
|
var ne net.Error
|
||||||
@ -567,7 +631,7 @@ func (cl *client) exchange(c net.Conn, id int, j job, head, scratch []byte) (int
|
|||||||
}
|
}
|
||||||
head = append(head, "\r\n"...)
|
head = append(head, "\r\n"...)
|
||||||
if err := send(c, r, clientWrites, head, e.upload); err != nil {
|
if err := send(c, r, clientWrites, head, e.upload); err != nil {
|
||||||
return 0, err
|
return 0, fmt.Errorf("sending (end %d, upload %d): %w", e.end, e.upload, err)
|
||||||
}
|
}
|
||||||
if halfclose {
|
if halfclose {
|
||||||
if cw, ok := c.(interface{ CloseWrite() error }); ok {
|
if cw, ok := c.(interface{ CloseWrite() error }); ok {
|
||||||
@ -624,7 +688,8 @@ func (cl *client) exchange(c net.Conn, id int, j job, head, scratch []byte) (int
|
|||||||
if want >= 0 && body >= want {
|
if want >= 0 && body >= want {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
return 0, err
|
return 0, fmt.Errorf("reading (end %d, close %v, upload %d, answer %d, got %d, body %d of %d): %w",
|
||||||
|
e.end, e.close, e.upload, e.answer, got, body, want, err)
|
||||||
}
|
}
|
||||||
if d := readPause(r); d > 0 {
|
if d := readPause(r); d > 0 {
|
||||||
time.Sleep(d)
|
time.Sleep(d)
|
||||||
@ -794,7 +859,7 @@ func runClient(cl *client, active int, rate float64, warmup, duration time.Durat
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if len(os.Args) < 2 {
|
if len(os.Args) < 2 {
|
||||||
fmt.Fprintln(os.Stderr, "usage: mix origin|client [flags]")
|
fmt.Fprintln(os.Stderr, "usage: mix origin|client|tls [flags]")
|
||||||
os.Exit(2)
|
os.Exit(2)
|
||||||
}
|
}
|
||||||
fs := flag.NewFlagSet(os.Args[1], flag.ExitOnError)
|
fs := flag.NewFlagSet(os.Args[1], flag.ExitOnError)
|
||||||
@ -807,6 +872,16 @@ func main() {
|
|||||||
fmt.Fprintln(os.Stderr, err)
|
fmt.Fprintln(os.Stderr, err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
case "tls":
|
||||||
|
listen := fs.String("listen", "127.0.0.1:5443", "address to take connections on")
|
||||||
|
target := fs.String("connect", "127.0.0.1:5201", "address to pass them to")
|
||||||
|
cert := fs.String("cert", "", "certificate: take TLS and pass plain TCP on; none: the other way round")
|
||||||
|
key := fs.String("key", "", "the certificate's key")
|
||||||
|
fs.Parse(os.Args[2:])
|
||||||
|
if err := wrap(*listen, *target, *cert, *key); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
case "client":
|
case "client":
|
||||||
cl := &client{}
|
cl := &client{}
|
||||||
fs.StringVar(&cl.via, "via", "http", "http (an HTTP proxy), connect, socks, relay or direct")
|
fs.StringVar(&cl.via, "via", "http", "http (an HTTP proxy), connect, socks, relay or direct")
|
||||||
@ -815,6 +890,7 @@ func main() {
|
|||||||
fs.IntVar(&cl.origins, "origins", 1, "the origin's ports to spread over (origin -ports)")
|
fs.IntVar(&cl.origins, "origins", 1, "the origin's ports to spread over (origin -ports)")
|
||||||
sources := fs.String("sources", "", "client addresses to open connections from, comma separated")
|
sources := fs.String("sources", "", "client addresses to open connections from, comma separated")
|
||||||
fs.IntVar(&cl.pool, "pool", 20000, "connections kept open")
|
fs.IntVar(&cl.pool, "pool", 20000, "connections kept open")
|
||||||
|
fs.BoolVar(&cl.errlog, "errlog", false, "print every failed exchange to stderr")
|
||||||
active := fs.Int("active", 2000, "exchanges in progress at most")
|
active := fs.Int("active", 2000, "exchanges in progress at most")
|
||||||
rate := fs.Float64("rate", 0, "exchanges started per second, 0 to keep -active going all the time")
|
rate := fs.Float64("rate", 0, "exchanges started per second, 0 to keep -active going all the time")
|
||||||
duration := fs.Duration("duration", 10*time.Second, "how long to measure")
|
duration := fs.Duration("duration", 10*time.Second, "how long to measure")
|
||||||
@ -848,7 +924,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
runClient(cl, *active, *rate, *warmup, *duration, *dialers, *shards)
|
runClient(cl, *active, *rate, *warmup, *duration, *dialers, *shards)
|
||||||
default:
|
default:
|
||||||
fmt.Fprintln(os.Stderr, "usage: mix origin|client [flags]")
|
fmt.Fprintln(os.Stderr, "usage: mix origin|client|tls [flags]")
|
||||||
os.Exit(2)
|
os.Exit(2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,7 +27,8 @@ of the group whose range (the average of its slowest runs to that of its
|
|||||||
fastest) reaches the best one's; * marks the best of the row, both groups
|
fastest) reaches the best one's; * marks the best of the row, both groups
|
||||||
together, in the same way. --split puts the splice group in a table of its own
|
together, in the same way. --split puts the splice group in a table of its own
|
||||||
(* still compares across both), --prefix connect- takes only the scenarios so
|
(* still compares across both), --prefix connect- takes only the scenarios so
|
||||||
named.
|
named. --overall marks the best of the row only, in bold and with *: for
|
||||||
|
tables where one group has a single proxy, its group's best says nothing.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@ -119,7 +120,7 @@ def load(paths):
|
|||||||
BULK = STREAM + ("http-ka-1m",)
|
BULK = STREAM + ("http-ka-1m",)
|
||||||
|
|
||||||
|
|
||||||
def combined(machines, labels, splice, prefix=None, split=False):
|
def combined(machines, labels, splice, prefix=None, split=False, overall_only=False):
|
||||||
"""One throughput table for several machines: each cell their medians
|
"""One throughput table for several machines: each cell their medians
|
||||||
and their average. Bold: the best of its group; *: the best of the row.
|
and their average. Bold: the best of its group; *: the best of the row.
|
||||||
split puts the proxies using splice() in a table of their own, * still
|
split puts the proxies using splice() in a table of their own, * still
|
||||||
@ -175,7 +176,17 @@ def combined(machines, labels, splice, prefix=None, split=False):
|
|||||||
plain = [p for p in proxies if p != "direct" and p not in splice]
|
plain = [p for p in proxies if p != "direct" and p not in splice]
|
||||||
spliced = [p for p in proxies if p in splice]
|
spliced = [p for p in proxies if p in splice]
|
||||||
direct = [p for p in proxies if p == "direct"]
|
direct = [p for p in proxies if p == "direct"]
|
||||||
names = " / ".join(n for n, _ in machines) + " / average"
|
# one machine: its medians alone, with ‡ where most runs were limited by
|
||||||
|
# the client or the origin; several: their medians and their average
|
||||||
|
single = len(machines) == 1
|
||||||
|
names = machines[0][0] if single else " / ".join(n for n, _ in machines) + " / average"
|
||||||
|
|
||||||
|
def limited(s, p):
|
||||||
|
if not single:
|
||||||
|
return ""
|
||||||
|
bounds = [r.get("bound", "") for r in data[machines[0][0]].get((s, p), [])]
|
||||||
|
other = sum(b in ("client", "origin") for b in bounds)
|
||||||
|
return " ‡" if bounds and other * 2 > len(bounds) else ""
|
||||||
|
|
||||||
def render(tables):
|
def render(tables):
|
||||||
"""tables: lists of groups, each table printed on its own; bold for
|
"""tables: lists of groups, each table printed on its own; bold for
|
||||||
@ -198,13 +209,15 @@ def combined(machines, labels, splice, prefix=None, split=False):
|
|||||||
for i, g in enumerate(t):
|
for i, g in enumerate(t):
|
||||||
if i:
|
if i:
|
||||||
cells.append("‖")
|
cells.append("‖")
|
||||||
best = leaders(s, g)
|
best = overall if overall_only else leaders(s, g)
|
||||||
for p in g:
|
for p in g:
|
||||||
vals = [value(n, s, p) for n, _ in machines] + [average(s, p)]
|
vals = [value(n, s, p) for n, _ in machines]
|
||||||
|
if not single:
|
||||||
|
vals.append(average(s, p))
|
||||||
if all(v is None for v in vals):
|
if all(v is None for v in vals):
|
||||||
cells.append("–")
|
cells.append("–")
|
||||||
continue
|
continue
|
||||||
text = " / ".join(fmt(v, 1) for v in vals)
|
text = " / ".join(fmt(v, 1) for v in vals) + limited(s, p)
|
||||||
if p in best:
|
if p in best:
|
||||||
text = f"**{text}**"
|
text = f"**{text}**"
|
||||||
if p in overall:
|
if p in overall:
|
||||||
@ -231,6 +244,8 @@ def main():
|
|||||||
ap.add_argument("--machine", action="append", default=[],
|
ap.add_argument("--machine", action="append", default=[],
|
||||||
help="NAME=a.csv[,b.csv]: one throughput table for all the "
|
help="NAME=a.csv[,b.csv]: one throughput table for all the "
|
||||||
"machines given, instead of the tables for one")
|
"machines given, instead of the tables for one")
|
||||||
|
ap.add_argument("--overall", action="store_true",
|
||||||
|
help="with --machine: only the best of the row, bold and *")
|
||||||
ap.add_argument("--label", action="append", default=[], help="old=new")
|
ap.add_argument("--label", action="append", default=[], help="old=new")
|
||||||
ap.add_argument("--title", default="")
|
ap.add_argument("--title", default="")
|
||||||
ap.add_argument("--splice", action="append", default=[],
|
ap.add_argument("--splice", action="append", default=[],
|
||||||
@ -240,7 +255,8 @@ def main():
|
|||||||
|
|
||||||
if args.machine:
|
if args.machine:
|
||||||
combined([(m.split("=", 1)[0], m.split("=", 1)[1].split(","))
|
combined([(m.split("=", 1)[0], m.split("=", 1)[1].split(","))
|
||||||
for m in args.machine], labels, args.splice, args.prefix, args.split)
|
for m in args.machine], labels, args.splice, args.prefix, args.split,
|
||||||
|
args.overall)
|
||||||
return
|
return
|
||||||
rows = load(args.csv)
|
rows = load(args.csv)
|
||||||
proxies, scenarios = [], []
|
proxies, scenarios = [], []
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user