Skip to content

Commit 9c3f699

Browse files
committed
feat(evnode-txsim): keep-alive conn pool + pprof endpoint
Two diagnostic improvements for the load generator: 1. http.Transport.MaxIdleConnsPerHost defaults to 2 in stdlib. With --concurrency=8 (or higher), 6+ goroutines per cycle had to open fresh TCP+TLS sockets per request because the pool couldn't hold their idle conns between requests. Bump MaxIdleConns / MaxIdleConnsPerHost / MaxConnsPerHost to 2*concurrency so every active sender has a reusable keep-alive socket, eliminating handshake churn from the hot path. 2. Always-on net/http/pprof on 127.0.0.1:6060. evnode-txsim is a load tester, not a production daemon, so cost of always serving profiling is acceptable; the payoff is being able to grab CPU profiles under live load without re-deploying the binary — `ssh -L 6060:127.0.0.1:6060 root@loadgen \ go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30`. A profile captured this way under c=8 traced the per-request hot path: 25.5% in kernel write(2), 25% in net/http body marshaling. That diagnostic surfaced that the c6in.2xlarge loadgen was the binding constraint for the experiment at ~22 MB/s, not evnode or DA — a finding we'd have spent another debug round chasing without the in-process profiler.
1 parent ae00ab8 commit 9c3f699

1 file changed

Lines changed: 22 additions & 1 deletion

File tree

  • tools/talis/cmd/evnode-txsim

tools/talis/cmd/evnode-txsim/main.go

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"fmt"
2727
"io"
2828
"net/http"
29+
_ "net/http/pprof"
2930
"os"
3031
"os/signal"
3132
"sort"
@@ -113,7 +114,27 @@ func run(cli cliFlags) error {
113114
return fmt.Errorf("seed random pool: %w", err)
114115
}
115116

116-
httpClient := &http.Client{Timeout: cli.timeout}
117+
// Bump per-host idle connections so concurrent goroutines reuse
118+
// keep-alive sockets instead of churning TCP+TLS handshakes —
119+
// stdlib default MaxIdleConnsPerHost=2 caps in-flight requests
120+
// to 2 keep-alive sockets per target, which serializes any
121+
// concurrency>2 onto fresh connections each request.
122+
transport := http.DefaultTransport.(*http.Transport).Clone()
123+
transport.MaxIdleConns = 2 * cli.concurrency
124+
transport.MaxIdleConnsPerHost = 2 * cli.concurrency
125+
transport.MaxConnsPerHost = 2 * cli.concurrency
126+
httpClient := &http.Client{Timeout: cli.timeout, Transport: transport}
127+
128+
// pprof on a dedicated listener — `_ "net/http/pprof"` registers
129+
// handlers on http.DefaultServeMux. Always-on at 127.0.0.1:6060
130+
// since this is a load-tester binary, not a production daemon;
131+
// SSH port-forward to grab profiles under load:
132+
//
133+
// ssh -L 6060:127.0.0.1:6060 root@loadgen \
134+
// go tool pprof http://localhost:6060/debug/pprof/profile?seconds=10
135+
go func() {
136+
_ = http.ListenAndServe("127.0.0.1:6060", nil)
137+
}()
117138

118139
ctx, cancel := context.WithCancel(context.Background())
119140
if cli.duration > 0 {

0 commit comments

Comments
 (0)