/
RouterBenchmarks.swift
150 lines (139 loc) · 5.48 KB
/
RouterBenchmarks.swift
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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
//===----------------------------------------------------------------------===//
//
// This source file is part of the Hummingbird server framework project
//
// Copyright (c) 2021-2021 the Hummingbird authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See hummingbird/CONTRIBUTORS.txt for the list of Hummingbird authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
import Benchmark
import HTTPTypes
import Hummingbird
import NIOHTTPTypes
@_spi(Internal) import HummingbirdCore
import Logging
import NIOCore
import NIOPosix
/// Implementation of a basic request context that supports everything the Hummingbird library needs
struct BasicBenchmarkContext: RequestContext {
var coreContext: CoreRequestContext
public init(channel: Channel, logger: Logger) {
self.coreContext = .init(allocator: channel.allocator, logger: logger)
}
}
/// Writes ByteBuffers to AsyncChannel outbound writer
struct BenchmarkBodyWriter: Sendable, ResponseBodyWriter {
func write(_: ByteBuffer) async throws {}
}
extension Benchmark {
@discardableResult
convenience init?<Context: RequestContext>(
name: String,
context: Context.Type = BasicBenchmarkContext.self,
configuration: Benchmark.Configuration = Benchmark.defaultConfiguration,
request: HTTPRequest,
writeBody: @escaping @Sendable (StreamedRequestBody.InboundStream.TestSource) async throws -> Void = { _ in },
setupRouter: @escaping @Sendable (Router<Context>) async throws -> Void
) {
let router = Router(context: Context.self)
self.init(name, configuration: configuration) { benchmark in
let responder = router.buildResponder()
benchmark.startMeasurement()
for _ in benchmark.scaledIterations {
for _ in 0..<50 {
try await withThrowingTaskGroup(of: Void.self) { group in
let context = Context(
allocator: ByteBufferAllocator(),
logger: Logger(label: "Benchmark")
)
let (inbound, source) = NIOAsyncChannelInboundStream<HTTPRequestPart>.makeTestingStream()
let streamer = StreamedRequestBody(iterator: inbound.makeAsyncIterator())
let requestBody = RequestBody.stream(streamer)
let Request = Request(head: request, body: requestBody)
group.addTask {
let response = try await responder.respond(to: Request, context: context)
_ = try await response.body.write(BenchmarkBodyWriter())
}
try await writeBody(source)
source.yield(.end(nil))
source.finish()
}
}
}
} setup: {
try await setupRouter(router)
Self.blackHole(MultiThreadedEventLoopGroup.singleton.any())
}
}
}
extension HTTPField.Name {
static let test = Self("Test")!
}
func routerBenchmarks() {
let buffer = ByteBufferAllocator().buffer(repeating: 0xFF, count: 10000)
Benchmark(
name: "Router:GET",
configuration: .init(warmupIterations: 10),
request: .init(method: .get, scheme: "http", authority: "localhost", path: "/")
) { router in
router.get { _, _ in
buffer
}
}
Benchmark(
name: "Router:PUT",
configuration: .init(warmupIterations: 10),
request: .init(method: .put, scheme: "http", authority: "localhost", path: "/")
) { bodyStream in
bodyStream.yield(.body(buffer))
bodyStream.yield(.body(buffer))
bodyStream.yield(.body(buffer))
bodyStream.yield(.body(buffer))
} setupRouter: { router in
router.put { request, _ in
let body = try await request.body.collate(maxSize: .max)
return body.readableBytes.description
}
}
Benchmark(
name: "Router:Echo",
configuration: .init(warmupIterations: 10),
request: .init(method: .post, scheme: "http", authority: "localhost", path: "/")
) { bodyStream in
bodyStream.yield(.body(buffer))
bodyStream.yield(.body(buffer))
bodyStream.yield(.body(buffer))
bodyStream.yield(.body(buffer))
} setupRouter: { router in
router.post { request, _ in
Response(status: .ok, headers: [:], body: .init { writer in
for try await buffer in request.body {
try await writer.write(buffer)
}
})
}
}
Benchmark(
name: "Middleware Chain",
configuration: .init(warmupIterations: 10),
request: .init(method: .get, scheme: "http", authority: "localhost", path: "/")
) { router in
struct EmptyMiddleware<Context>: MiddlewareProtocol {
func handle(_ request: Request, context: Context, next: (Request, Context) async throws -> Response) async throws -> Response {
return try await next(request, context)
}
}
router.middlewares.add(EmptyMiddleware())
router.middlewares.add(EmptyMiddleware())
router.middlewares.add(EmptyMiddleware())
router.middlewares.add(EmptyMiddleware())
router.get { _, _ in
HTTPResponse.Status.ok
}
}
}