diff --git a/devenv.nix b/devenv.nix index ed979af..d8b3984 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; + # 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 = '' + set -e + gum format "## BenchmarkProbeCiphersConcurrently baseline (-count=6)" + + go test ./internal/certinfo/ -run '^$' \ + -bench BenchmarkProbeCiphersConcurrently -benchmem -count=6 + ''; + + 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 '^$' \ + -bench BenchmarkProbeCiphersConcurrently -benchtime 2s -benchmem \ + -cpuprofile BenchmarkProbeCiphersConcurrently.cpu.out \ + -outputdir /tmp + go tool pprof -http=:3111 /tmp/BenchmarkProbeCiphersConcurrently.cpu.out + ''; + + 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. + 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 + ''; + + 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 '^$' \ + -bench BenchmarkProbeCiphersConcurrently -benchtime 2s \ + -blockprofile BenchmarkProbeCiphersConcurrently.block.out \ + -outputdir /tmp + go tool pprof -http=:3111 /tmp/BenchmarkProbeCiphersConcurrently.block.out + ''; + + 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. + go test ./internal/certinfo/ -run '^$' \ + -bench BenchmarkProbeCiphersConcurrently -benchtime 2s \ + -mutexprofile BenchmarkProbeCiphersConcurrently.mutex.out \ + -outputdir /tmp + go tool pprof -http=:3111 /tmp/BenchmarkProbeCiphersConcurrently.mutex.out + ''; + + 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 '^$' \ + -bench BenchmarkProbeCiphersConcurrently -benchtime 1s \ + -trace /tmp/BenchmarkProbeCiphersConcurrently.trace.out \ + -outputdir /tmp + go tool trace -http=:3111 /tmp/BenchmarkProbeCiphersConcurrently.trace.out + ''; + 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) + } +}