~bigbes/sr-ht-ecore

ref: b36a927213562090e100c8b43ab27ef9094b1de9 sr-ht-ecore/chimw/chimw_bench_test.go -rw-r--r-- 3.8 KiB
b36a9272 — Eugene Blikh beads: ignore the JSONL exports a day ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
package chimw

import (
	"io"
	"log/slog"
	"net/http"
	"testing"

	"github.com/go-chi/chi/v5"
	chimiddleware "github.com/go-chi/chi/v5/middleware"
)

// nullWriter is a ResponseWriter that keeps nothing; see the same type in
// middleware for why a recorder is the wrong instrument here.
type nullWriter struct{ header http.Header }

func (w *nullWriter) Header() http.Header         { return w.header }
func (w *nullWriter) Write(b []byte) (int, error) { return len(b), nil }
func (w *nullWriter) WriteHeader(int)             {}

// discardLogger is a real handler doing real encoding work, writing nowhere.
// The formatter's cost is the attributes it builds and the record the handler
// encodes; sending that to a buffer would grow one by b.N records and measure
// the allocator instead.
func discardLogger() *slog.Logger {
	return slog.New(slog.NewJSONHandler(io.Discard, &slog.HandlerOptions{Level: slog.LevelDebug}))
}

// benchRouter mounts one handler at /page behind the middlewares the case
// wants, which is how a service installs them.
func benchRouter(f SlogFormatter, withRequestID bool) http.Handler {
	r := chi.NewRouter()
	if withRequestID {
		r.Use(chimiddleware.RequestID)
	}
	r.Use(RequestLogger(f))
	r.Get("/page", func(w http.ResponseWriter, _ *http.Request) {
		_, _ = w.Write([]byte("ok"))
	})
	return r
}

// benchRequest is reused across iterations: chi routes on a copy carrying its
// own route context and leaves this value alone.
func benchRequest(b *testing.B, path string) *http.Request {
	b.Helper()

	r, err := http.NewRequest(http.MethodGet, "http://bench.example.org"+path, nil)
	if err != nil {
		b.Fatalf("building the request: %v", err)
	}
	return r
}

// BenchmarkRequestLogger is the outermost middleware of every service on the
// instance: one record per request, built and encoded on the request's own
// goroutine before it returns.
//
// The three cases are the three shapes that exist in production. "logged" is
// the ordinary request. "with_request_id" adds chi's RequestID above, which is
// how these routers are actually wired and which costs a context lookup plus a
// sixth attribute. "skipped" is a probe hitting /healthz every second — it must
// stay far cheaper than the other two, because that is the entire reason Skip
// exists.
func BenchmarkRequestLogger(b *testing.B) {
	logger := discardLogger()

	b.Run("logged", func(b *testing.B) {
		h := benchRouter(SlogFormatter{Logger: logger}, false)
		r := benchRequest(b, "/page")

		b.ReportAllocs()
		for b.Loop() {
			h.ServeHTTP(&nullWriter{header: make(http.Header, 4)}, r)
		}
	})

	b.Run("with_request_id", func(b *testing.B) {
		h := benchRouter(SlogFormatter{Logger: logger}, true)
		r := benchRequest(b, "/page")

		b.ReportAllocs()
		for b.Loop() {
			h.ServeHTTP(&nullWriter{header: make(http.Header, 4)}, r)
		}
	})

	b.Run("skipped", func(b *testing.B) {
		h := benchRouter(SlogFormatter{
			Logger: logger,
			Skip:   SkipPaths("/page"),
		}, false)
		r := benchRequest(b, "/page")

		b.ReportAllocs()
		for b.Loop() {
			h.ServeHTTP(&nullWriter{header: make(http.Header, 4)}, r)
		}
	})
}

// BenchmarkGetHead measures the pair a page route registers: the GET a viewer
// makes and the HEAD a monitor or a proxy makes through the same handler. Both
// go through the routing tree this package writes into, so this is the cost of
// the convenience rather than of chi.
func BenchmarkGetHead(b *testing.B) {
	r := chi.NewRouter()
	GetHead(r, "/page", func(w http.ResponseWriter, _ *http.Request) {
		_, _ = w.Write([]byte("ok"))
	})

	for _, method := range []string{http.MethodGet, http.MethodHead} {
		b.Run(method, func(b *testing.B) {
			req, err := http.NewRequest(method, "http://bench.example.org/page", nil)
			if err != nil {
				b.Fatalf("building the request: %v", err)
			}

			b.ReportAllocs()
			for b.Loop() {
				r.ServeHTTP(&nullWriter{header: make(http.Header, 4)}, req)
			}
		})
	}
}