1import { Effect } from "effect"
2import * as Headers from "effect/http/Headers"
3import * as HttpMiddleware from "effect/http/HttpMiddleware"
4import * as HttpServerRequest from "effect/http/HttpServerRequest"
5import * as HttpServerResponse from "effect/http/HttpServerResponse"
6import * as HttpTraceContext from "effect/http/HttpTraceContext"
7import * as Tracer from "effect/Tracer"
8import { Bench } from "tinybench"
9
10const bench = new Bench()
11
12const noopTracer = Tracer.make({
13 span: (options) =>
14 ({
15 _tag: "Span",
16 spanId: "spanid0123456789",
17 traceId: "traceid0123456789traceid01234567",
18 sampled: true,
19 name: options.name,
20 parent: options.parent,
21 annotations: options.annotations,
22 links: options.links,
23 kind: options.kind,
24 status: { _tag: "Started", startTime: options.startTime },
25 attributes: new Map(),
26 attribute() {},
27 event() {},
28 end() {},
29 addLinks() {}
30 }) as Tracer.Span
31})
32
33const request = HttpServerRequest.fromWeb(
34 new Request("http://localhost:3000/some/path?foo=bar", {
35 headers: {
36 "user-agent": "bench/1.0",
37 "accept": "application/json",
38 "accept-encoding": "gzip, br",
39 "authorization": "Bearer secret",
40 "cookie": "session=abc",
41 "x-request-id": "12345678",
42 "host": "localhost:3000"
43 }
44 })
45)
46
47const requestWithTraceparent = HttpServerRequest.fromWeb(
48 new Request("http://localhost:3000/some/path?foo=bar", {
49 headers: {
50 "traceparent": "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01",
51 "host": "localhost:3000"
52 }
53 })
54)
55
56const app = Effect.succeed(HttpServerResponse.text("ok"))
57
58const tracedApp = HttpMiddleware.tracer(app).pipe(
59 Effect.provideService(HttpServerRequest.HttpServerRequest, request),
60 Effect.provideService(Tracer.Tracer, noopTracer)
61)
62
63const tracedAppNative = HttpMiddleware.tracer(app).pipe(
64 Effect.provideService(HttpServerRequest.HttpServerRequest, request)
65)
66
67const tracedAppDisabled = HttpMiddleware.tracer(app).pipe(
68 Effect.provideService(HttpServerRequest.HttpServerRequest, request),
69 Effect.withTracerEnabled(false)
70)
71
72const bareApp = app.pipe(
73 Effect.provideService(HttpServerRequest.HttpServerRequest, request)
74)
75
76const withSpanApp = Effect.void.pipe(
77 Effect.withSpan("bench"),
78 Effect.provideService(Tracer.Tracer, noopTracer)
79)
80
81const withSpanAppNoStack = Effect.void.pipe(
82 Effect.withSpan("bench", undefined, { captureStackTrace: false }),
83 Effect.provideService(Tracer.Tracer, noopTracer)
84)
85
86const withSpanDisabled = Effect.void.pipe(
87 Effect.withSpan("bench"),
88 Effect.withTracerEnabled(false)
89)
90
91bench
92 .add("bare app (no middleware)", async () => {
93 await Effect.runPromise(bareApp)
94 })
95 .add("tracer middleware (noop tracer)", async () => {
96 await Effect.runPromise(tracedApp)
97 })
98 .add("tracer middleware (NativeSpan)", async () => {
99 await Effect.runPromise(tracedAppNative)
100 })
101 .add("tracer middleware (tracing disabled)", async () => {
102 await Effect.runPromise(tracedAppDisabled)
103 })
104 .add("withSpan reused (noop tracer)", async () => {
105 await Effect.runPromise(withSpanApp)
106 })
107 .add("withSpan constructed per run (noop tracer)", async () => {
108 await Effect.runPromise(
109 Effect.void.pipe(
110 Effect.withSpan("bench"),
111 Effect.provideService(Tracer.Tracer, noopTracer)
112 )
113 )
114 })
115 .add("withSpan constructed per run, no stack capture", async () => {
116 await Effect.runPromise(
117 Effect.void.pipe(
118 Effect.withSpan("bench", undefined, { captureStackTrace: false }),
119 Effect.provideService(Tracer.Tracer, noopTracer)
120 )
121 )
122 })
123 .add("withSpan reused, no stack capture (noop tracer)", async () => {
124 await Effect.runPromise(withSpanAppNoStack)
125 })
126 .add("withSpan reused (tracing disabled)", async () => {
127 await Effect.runPromise(withSpanDisabled)
128 })
129 .add("fromHeaders: no trace headers", () => {
130 HttpTraceContext.fromHeaders(request.headers)
131 })
132 .add("fromHeaders: traceparent", () => {
133 HttpTraceContext.fromHeaders(requestWithTraceparent.headers)
134 })
135 .add("Headers.redact (7 headers, default names)", () => {
136 Headers.redact(request.headers, ["authorization", "cookie", "set-cookie", "x-api-key"])
137 })
138 .add("Request.toURL", () => {
139 HttpServerRequest.toURL(request)
140 })
141
142await bench.run()
143console.table(bench.table())