From 940d00078209f36f60ff7bdac4a4b2db3b5baec3 Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Thu, 3 Sep 2026 15:20:31 +0200 Subject: [PATCH 1/2] ci: add benchmark tests for ProbeCiphersConcurrently --- devenv.nix | 82 +++++++++++++++++++ .../certinfo/certinfo_handlers_bench_test.go | 48 +++++++++++ 2 files changed, 130 insertions(+) create mode 100644 internal/certinfo/certinfo_handlers_bench_test.go diff --git a/devenv.nix b/devenv.nix index ed979af..6f3c792 100644 --- a/devenv.nix +++ b/devenv.nix @@ -746,6 +746,88 @@ in golangci-lint run --fix ''; + # BenchmarkProbeCiphersConcurrently — pprof / trace helpers + # + # Wraps the hermetic cipher-scan bench (I/O-bound localhost TLS, 10-worker pool). + # One script per profile kind: combining -cpuprofile + -memprofile + -blockprofile + # in a single go test run skews the results. + # + # Shared flags: -run '^$' (skip unit tests), -bench BenchmarkProbeCiphersConcurrently. + # pprof / trace HTTP UI: 127.0.0.1:3111 (not 3000). Go 1.27 -http=:port is localhost-only. + # Artifacts live under /tmp (*.out). Profiling can also leave internal/certinfo/certinfo.test; + # both are removed after the UI exits (Ctrl+C). Do not commit them. + # Mutex is last and likely quiet (the pool itself has no mutexes). + + scripts.BenchmarkProbeCiphersConcurrently_bench.exec = '' + set -e + gum format "## BenchmarkProbeCiphersConcurrently baseline (-count=6)" + + go test ./internal/certinfo/ -run '^$' \ + -bench BenchmarkProbeCiphersConcurrently -benchmem -count=6 + ''; + + scripts.BenchmarkProbeCiphersConcurrently_cpu.exec = '' + set -e + gum format "## BenchmarkProbeCiphersConcurrently CPU profile (pprof :3111)" + + go test ./internal/certinfo/ -run '^$' \ + -bench BenchmarkProbeCiphersConcurrently -benchtime 2s -benchmem \ + -cpuprofile BenchmarkProbeCiphersConcurrently.cpu.out \ + -outputdir /tmp + go tool pprof -http=:3111 /tmp/BenchmarkProbeCiphersConcurrently.cpu.out + rm -f /tmp/BenchmarkProbeCiphersConcurrently.cpu.out internal/certinfo/certinfo.test + ''; + + scripts.BenchmarkProbeCiphersConcurrently_mem.exec = '' + set -e + gum format "## BenchmarkProbeCiphersConcurrently heap profile (pprof :3111, -alloc_objects)" + + # -alloc_objects = allocation count (GC pressure). Swap to -alloc_space for bytes. + go test ./internal/certinfo/ -run '^$' \ + -bench BenchmarkProbeCiphersConcurrently -benchtime 2s -benchmem \ + -memprofile BenchmarkProbeCiphersConcurrently.mem.out \ + -outputdir /tmp + go tool pprof -http=:3111 -alloc_objects /tmp/BenchmarkProbeCiphersConcurrently.mem.out + rm -f /tmp/BenchmarkProbeCiphersConcurrently.mem.out internal/certinfo/certinfo.test + ''; + + scripts.BenchmarkProbeCiphersConcurrently_block.exec = '' + set -e + gum format "## BenchmarkProbeCiphersConcurrently block profile (pprof :3111)" + + go test ./internal/certinfo/ -run '^$' \ + -bench BenchmarkProbeCiphersConcurrently -benchtime 2s \ + -blockprofile BenchmarkProbeCiphersConcurrently.block.out \ + -outputdir /tmp + go tool pprof -http=:3111 /tmp/BenchmarkProbeCiphersConcurrently.block.out + rm -f /tmp/BenchmarkProbeCiphersConcurrently.block.out internal/certinfo/certinfo.test + ''; + + scripts.BenchmarkProbeCiphersConcurrently_mutex.exec = '' + set -e + gum format "## BenchmarkProbeCiphersConcurrently mutex profile (pprof :3111)" + + # Likely quiet: probeCiphersConcurrently uses WaitGroup/channels, not mutexes. + go test ./internal/certinfo/ -run '^$' \ + -bench BenchmarkProbeCiphersConcurrently -benchtime 2s \ + -mutexprofile BenchmarkProbeCiphersConcurrently.mutex.out \ + -outputdir /tmp + go tool pprof -http=:3111 /tmp/BenchmarkProbeCiphersConcurrently.mutex.out + rm -f /tmp/BenchmarkProbeCiphersConcurrently.mutex.out internal/certinfo/certinfo.test + ''; + + scripts.BenchmarkProbeCiphersConcurrently_trace.exec = '' + set -e + gum format "## BenchmarkProbeCiphersConcurrently execution trace" + + go test ./internal/certinfo/ -run '^$' \ + -bench BenchmarkProbeCiphersConcurrently -benchtime 1s \ + -trace /tmp/BenchmarkProbeCiphersConcurrently.trace.out \ + -outputdir /tmp + go tool trace -http=:3111 /tmp/BenchmarkProbeCiphersConcurrently.trace.out + rm -f /tmp/BenchmarkProbeCiphersConcurrently.trace.out internal/certinfo/certinfo.test + ''; + enterShell = '' echo "https-wrench devenv ready" go version diff --git a/internal/certinfo/certinfo_handlers_bench_test.go b/internal/certinfo/certinfo_handlers_bench_test.go new file mode 100644 index 0000000..70ae8d5 --- /dev/null +++ b/internal/certinfo/certinfo_handlers_bench_test.go @@ -0,0 +1,48 @@ +package certinfo + +import ( + "crypto/tls" + "io" + "log" + "net/http" + "net/http/httptest" + "net/url" + "testing" + + "github.com/stretchr/testify/require" +) + +func BenchmarkProbeCiphersConcurrently(b *testing.B) { + server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + })) + server.Config.ErrorLog = log.New(io.Discard, "", 0) + b.Cleanup(server.Close) + + u, err := url.Parse(server.URL) + require.NoError(b, err) + + cc, err := New() + require.NoError(b, err) + + cc.SetTLSInsecure(true) + cc.SetTLSServerName("example.com") + + err = cc.SetTLSEndpoint(b.Context(), u.Host) + require.NoError(b, err) + + cc.ProbedProtocols = map[string]bool{ + "TLS 1.3": true, + "TLS 1.2": true, + "TLS 1.1": false, + "TLS 1.0": false, + } + + suites := append(tls.CipherSuites(), tls.InsecureCipherSuites()...) + + b.ReportAllocs() + + for b.Loop() { + _ = cc.probeCiphersConcurrently(b.Context(), suites) + } +} From 79b8b6ab0da01ec9e1515011c17fa6d0fd2483c0 Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Thu, 3 Sep 2026 16:19:05 +0200 Subject: [PATCH 2/2] fix(devenv): use EXIT traps for pprof script cleanup --- devenv.nix | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/devenv.nix b/devenv.nix index 6f3c792..d8b3984 100644 --- a/devenv.nix +++ b/devenv.nix @@ -755,7 +755,7 @@ in # Shared flags: -run '^$' (skip unit tests), -bench BenchmarkProbeCiphersConcurrently. # pprof / trace HTTP UI: 127.0.0.1:3111 (not 3000). Go 1.27 -http=:port is localhost-only. # Artifacts live under /tmp (*.out). Profiling can also leave internal/certinfo/certinfo.test; - # both are removed after the UI exits (Ctrl+C). Do not commit them. + # each script registers an EXIT trap to remove both on any exit (test failure, Ctrl+C, normal). Do not commit them. # Mutex is last and likely quiet (the pool itself has no mutexes). scripts.BenchmarkProbeCiphersConcurrently_bench.exec = '' @@ -768,6 +768,7 @@ in scripts.BenchmarkProbeCiphersConcurrently_cpu.exec = '' set -e + trap 'rm -f /tmp/BenchmarkProbeCiphersConcurrently.cpu.out internal/certinfo/certinfo.test' EXIT gum format "## BenchmarkProbeCiphersConcurrently CPU profile (pprof :3111)" go test ./internal/certinfo/ -run '^$' \ @@ -775,11 +776,11 @@ in -cpuprofile BenchmarkProbeCiphersConcurrently.cpu.out \ -outputdir /tmp go tool pprof -http=:3111 /tmp/BenchmarkProbeCiphersConcurrently.cpu.out - rm -f /tmp/BenchmarkProbeCiphersConcurrently.cpu.out internal/certinfo/certinfo.test ''; scripts.BenchmarkProbeCiphersConcurrently_mem.exec = '' set -e + trap 'rm -f /tmp/BenchmarkProbeCiphersConcurrently.mem.out internal/certinfo/certinfo.test' EXIT gum format "## BenchmarkProbeCiphersConcurrently heap profile (pprof :3111, -alloc_objects)" # -alloc_objects = allocation count (GC pressure). Swap to -alloc_space for bytes. @@ -788,11 +789,11 @@ in -memprofile BenchmarkProbeCiphersConcurrently.mem.out \ -outputdir /tmp go tool pprof -http=:3111 -alloc_objects /tmp/BenchmarkProbeCiphersConcurrently.mem.out - rm -f /tmp/BenchmarkProbeCiphersConcurrently.mem.out internal/certinfo/certinfo.test ''; scripts.BenchmarkProbeCiphersConcurrently_block.exec = '' set -e + trap 'rm -f /tmp/BenchmarkProbeCiphersConcurrently.block.out internal/certinfo/certinfo.test' EXIT gum format "## BenchmarkProbeCiphersConcurrently block profile (pprof :3111)" go test ./internal/certinfo/ -run '^$' \ @@ -800,11 +801,11 @@ in -blockprofile BenchmarkProbeCiphersConcurrently.block.out \ -outputdir /tmp go tool pprof -http=:3111 /tmp/BenchmarkProbeCiphersConcurrently.block.out - rm -f /tmp/BenchmarkProbeCiphersConcurrently.block.out internal/certinfo/certinfo.test ''; scripts.BenchmarkProbeCiphersConcurrently_mutex.exec = '' set -e + trap 'rm -f /tmp/BenchmarkProbeCiphersConcurrently.mutex.out internal/certinfo/certinfo.test' EXIT gum format "## BenchmarkProbeCiphersConcurrently mutex profile (pprof :3111)" # Likely quiet: probeCiphersConcurrently uses WaitGroup/channels, not mutexes. @@ -813,11 +814,11 @@ in -mutexprofile BenchmarkProbeCiphersConcurrently.mutex.out \ -outputdir /tmp go tool pprof -http=:3111 /tmp/BenchmarkProbeCiphersConcurrently.mutex.out - rm -f /tmp/BenchmarkProbeCiphersConcurrently.mutex.out internal/certinfo/certinfo.test ''; scripts.BenchmarkProbeCiphersConcurrently_trace.exec = '' set -e + trap 'rm -f /tmp/BenchmarkProbeCiphersConcurrently.trace.out internal/certinfo/certinfo.test' EXIT gum format "## BenchmarkProbeCiphersConcurrently execution trace" go test ./internal/certinfo/ -run '^$' \ @@ -825,7 +826,6 @@ in -trace /tmp/BenchmarkProbeCiphersConcurrently.trace.out \ -outputdir /tmp go tool trace -http=:3111 /tmp/BenchmarkProbeCiphersConcurrently.trace.out - rm -f /tmp/BenchmarkProbeCiphersConcurrently.trace.out internal/certinfo/certinfo.test ''; enterShell = ''