@marcel91200 / caddy Mirror · read-only
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 11, 01:00 UTC

master
go 169 lines 4.49 KB
Raw
1 package encode_test
2
3 import (
4 "bytes"
5 "fmt"
6 "net/http"
7 "net/http/httptest"
8 "testing"
9
10 "github.com/caddyserver/caddy/v2/modules/caddyhttp"
11 "github.com/caddyserver/caddy/v2/modules/caddyhttp/encode"
12 )
13
14 const (
15 benchmarkParallelism = 4
16 handlerBenchWarmupIterations = 5
17 )
18
19 // BenchmarkStandardEncodingPayloads measures raw encoder throughput (NewEncoder → Write → Close)
20 // across the standard HTML/JSON/JS/CSS payloads and gzip/zstd compression levels.
21 // Each subtest runs with 4 parallel workers (SetParallelism).
22 func BenchmarkStandardEncodingPayloads(b *testing.B) {
23 forEachBenchmarkCase(b, func(b *testing.B, corpus benchmarkCorpus, encCase encoderCase) {
24 benchmarkEncode(b, corpus.data, encCase.encoding)
25 })
26 }
27
28 // BenchmarkEncodeHandlerCorpus measures the full encode middleware path (ServeHTTP,
29 // responseWriter, writer pools) using the same payload and level grid.
30 func BenchmarkEncodeHandlerCorpus(b *testing.B) {
31 forEachBenchmarkCase(b, func(b *testing.B, corpus benchmarkCorpus, encCase encoderCase) {
32 enc := newEncodeHandler(b, encCase, 1)
33 benchmarkEncodeHandler(b, enc, encCase, corpus)
34 })
35 }
36
37 func forEachBenchmarkCase(b *testing.B, fn func(b *testing.B, corpus benchmarkCorpus, encCase encoderCase)) {
38 for _, corpus := range benchmarkCorpora(b) {
39 for _, encCase := range benchmarkEncoderCases(b) {
40 b.Run(benchmarkSubtestName(corpus.name, encCase), func(b *testing.B) {
41 fn(b, corpus, encCase)
42 })
43 }
44 }
45 }
46
47 func benchmarkSubtestName(corpus string, encCase encoderCase) string {
48 return fmt.Sprintf("payload-%s/encoder-%s/compress-level-%s",
49 corpus, encCase.encoder, encCase.level)
50 }
51
52 func benchmarkEncode(b *testing.B, payload []byte, encoding encode.Encoding) {
53 b.Helper()
54 b.ReportAllocs()
55 b.SetBytes(int64(len(payload)))
56 b.SetParallelism(benchmarkParallelism)
57
58 b.ResetTimer()
59 b.RunParallel(func(pb *testing.PB) {
60 encoder := encoding.NewEncoder()
61 var dst bytes.Buffer
62 for pb.Next() {
63 dst.Reset()
64 encoder.Reset(&dst)
65 if _, err := encoder.Write(payload); err != nil {
66 b.Fatalf("Write() error = %v", err)
67 }
68 if err := encoder.Close(); err != nil {
69 b.Fatalf("Close() error = %v", err)
70 }
71 }
72 })
73 }
74
75 func benchmarkEncodeHandler(b *testing.B, enc *encode.Encode, encCase encoderCase, corpus benchmarkCorpus) {
76 b.Helper()
77 b.ReportAllocs()
78 b.SetBytes(int64(len(corpus.data)))
79 b.SetParallelism(benchmarkParallelism)
80
81 next := corpusHandler(corpus)
82 w := newBenchmarkResponseWriter()
83 r := newHandlerBenchRequest(encCase)
84 warmupEncodeHandler(enc, w, r, next)
85
86 b.ResetTimer()
87 b.RunParallel(func(pb *testing.PB) {
88 w := newBenchmarkResponseWriter()
89 r := newHandlerBenchRequest(encCase)
90 for pb.Next() {
91 w.reset()
92 if err := enc.ServeHTTP(w, r, next); err != nil {
93 b.Fatalf("ServeHTTP() error = %v", err)
94 }
95 }
96 })
97 }
98
99 func newHandlerBenchRequest(encCase encoderCase) *http.Request {
100 r := httptest.NewRequest(http.MethodGet, "/", nil)
101 r.Header.Set("Accept-Encoding", encCase.encoding.AcceptEncoding())
102 return r
103 }
104
105 func warmupEncodeHandler(enc *encode.Encode, w *benchmarkResponseWriter, r *http.Request, next caddyhttp.Handler) {
106 for range handlerBenchWarmupIterations {
107 w.reset()
108 if err := enc.ServeHTTP(w, r, next); err != nil {
109 panic("warmup ServeHTTP: " + err.Error())
110 }
111 }
112 }
113
114 // benchmarkResponseWriter is a resettable http.ResponseWriter for handler benchmarks.
115 // httptest.ResponseRecorder cannot be safely reused because it keeps unexported state.
116 type benchmarkResponseWriter struct {
117 header http.Header
118 code int
119 body bytes.Buffer
120 wroteHeader bool
121 }
122
123 func newBenchmarkResponseWriter() *benchmarkResponseWriter {
124 return &benchmarkResponseWriter{
125 header: make(http.Header),
126 }
127 }
128
129 func (w *benchmarkResponseWriter) reset() {
130 w.code = 0
131 w.wroteHeader = false
132 w.body.Reset()
133 for k := range w.header {
134 delete(w.header, k)
135 }
136 }
137
138 func (w *benchmarkResponseWriter) Header() http.Header {
139 return w.header
140 }
141
142 func (w *benchmarkResponseWriter) Write(p []byte) (int, error) {
143 if !w.wroteHeader {
144 w.WriteHeader(http.StatusOK)
145 }
146 return w.body.Write(p)
147 }
148
149 func (w *benchmarkResponseWriter) WriteHeader(statusCode int) {
150 if w.wroteHeader {
151 return
152 }
153 w.code = statusCode
154 w.wroteHeader = true
155 }
156
157 func (w *benchmarkResponseWriter) Flush() {
158 if !w.wroteHeader {
159 w.WriteHeader(http.StatusOK)
160 }
161 }
162
163 func corpusHandler(corpus benchmarkCorpus) caddyhttp.Handler {
164 return caddyhttp.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
165 w.Header().Set("Content-Type", corpus.contentType)
166 _, err := w.Write(corpus.data)
167 return err
168 })
169 }