Skip to content

Commit 8e1bf6a

Browse files
NIOPerformanceTester: Add benchmarks for UDP flows
Signed-off-by: Si Beaumont <[email protected]>
1 parent 4588691 commit 8e1bf6a

File tree

2 files changed

+272
-0
lines changed

2 files changed

+272
-0
lines changed
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftNIO open source project
4+
//
5+
// Copyright (c) 2022 Apple Inc. and the SwiftNIO project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
import NIOCore
15+
import NIOPosix
16+
17+
final class DoNothingHandler: ChannelInboundHandler {
18+
public typealias InboundIn = ByteBuffer
19+
public typealias OutboundOut = ByteBuffer
20+
}
21+
22+
final class CountReadsHandler: ChannelInboundHandler {
23+
public typealias InboundIn = NIOAny
24+
public typealias OutboundOut = NIOAny
25+
26+
private var numReads: Int = 0
27+
private let receivedAtLeastOneReadPromise: EventLoopPromise<Void>
28+
29+
var receivedAtLeastOneRead: EventLoopFuture<Void> {
30+
self.receivedAtLeastOneReadPromise.futureResult
31+
}
32+
33+
init(receivedAtLeastOneReadPromise: EventLoopPromise<Void>) {
34+
self.receivedAtLeastOneReadPromise = receivedAtLeastOneReadPromise
35+
}
36+
37+
func channelRead(context: ChannelHandlerContext, data: NIOAny) {
38+
self.numReads += 1
39+
self.receivedAtLeastOneReadPromise.succeed(())
40+
}
41+
}
42+
43+
44+
private final class EchoHandler: ChannelInboundHandler {
45+
public typealias InboundIn = AddressedEnvelope<ByteBuffer>
46+
public typealias OutboundOut = AddressedEnvelope<ByteBuffer>
47+
48+
public func channelRead(context: ChannelHandlerContext, data: NIOAny) {
49+
context.write(data, promise: nil)
50+
}
51+
52+
public func channelReadComplete(context: ChannelHandlerContext) {
53+
context.flush()
54+
}
55+
56+
public func errorCaught(context: ChannelHandlerContext, error: Error) {
57+
preconditionFailure("EchoHandler received errorCaught")
58+
}
59+
}
60+
61+
class DatagramClientBenchmark {
62+
final private let group: MultiThreadedEventLoopGroup
63+
final let serverChannel: Channel
64+
final let localhostPickPort: SocketAddress
65+
final let clientBootstrap: DatagramBootstrap
66+
final let clientHandler: CountReadsHandler
67+
final let clientChannel: Channel
68+
final let payload: NIOAny
69+
70+
final let iterations: Int
71+
72+
init(iterations: Int, connect: Bool, envelope: Bool, metadata: Bool) {
73+
self.iterations = iterations
74+
75+
self.group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)
76+
77+
// server setup
78+
self.localhostPickPort = try! SocketAddress.makeAddressResolvingHost("127.0.0.1", port: 0)
79+
self.serverChannel = try! DatagramBootstrap(group: self.group)
80+
.channelInitializer { $0.pipeline.addHandler(EchoHandler()) }
81+
.bind(to: localhostPickPort)
82+
.wait()
83+
84+
// client handler setup
85+
let clientHandler = CountReadsHandler(
86+
receivedAtLeastOneReadPromise: self.group.next().makePromise()
87+
)
88+
self.clientHandler = clientHandler
89+
90+
// client bootstrap setup
91+
self.clientBootstrap = DatagramBootstrap(group: self.group).channelInitializer { channel in
92+
channel.pipeline.addHandler(clientHandler)
93+
}
94+
95+
// client channel bind
96+
self.clientChannel = try! self.clientBootstrap.bind(to: self.localhostPickPort).wait()
97+
98+
// channel connect
99+
if connect {
100+
try! self.clientChannel.connect(to: self.serverChannel.localAddress!).wait()
101+
}
102+
103+
// payload setup
104+
let buffer = self.clientChannel.allocator.buffer(integer: 1, as: UInt8.self)
105+
switch (envelope, metadata) {
106+
case (true, true):
107+
self.payload = NIOAny(AddressedEnvelope<ByteBuffer>(
108+
remoteAddress: self.serverChannel.localAddress!,
109+
data: buffer,
110+
metadata: .init(ecnState: .transportCapableFlag1)
111+
))
112+
case (true, false):
113+
self.payload = NIOAny(AddressedEnvelope<ByteBuffer>(
114+
remoteAddress: self.serverChannel.localAddress!,
115+
data: buffer
116+
))
117+
case (false, false):
118+
self.payload = NIOAny(buffer)
119+
case (false, true):
120+
preconditionFailure("No API for this")
121+
}
122+
}
123+
124+
func setUp() throws {
125+
}
126+
127+
func tearDown() {
128+
try! self.group.syncShutdownGracefully()
129+
}
130+
}
131+
132+
final class DatagramBootstrapCreateBenchmark: DatagramClientBenchmark, Benchmark {
133+
init(iterations: Int) {
134+
super.init(iterations: iterations, connect: false, envelope: false, metadata: false)
135+
}
136+
137+
func run() throws -> Int {
138+
for _ in 1...self.iterations {
139+
_ = DatagramBootstrap(group: group)
140+
.channelInitializer { channel in
141+
channel.pipeline.addHandler(DoNothingHandler())
142+
}
143+
}
144+
return 0
145+
}
146+
}
147+
148+
final class DatagramChannelBindBenchmark: DatagramClientBenchmark, Benchmark {
149+
init(iterations: Int) {
150+
super.init(iterations: iterations, connect: false, envelope: false, metadata: false)
151+
}
152+
153+
func run() throws -> Int {
154+
for _ in 1...self.iterations {
155+
try! self.clientBootstrap.bind(to: self.localhostPickPort).flatMap { $0.close() }.wait()
156+
}
157+
return 0
158+
}
159+
}
160+
161+
final class DatagramChannelConnectBenchmark: DatagramClientBenchmark, Benchmark {
162+
init(iterations: Int) {
163+
super.init(iterations: iterations, connect: false, envelope: false, metadata: false)
164+
}
165+
166+
func run() throws -> Int {
167+
for _ in 1...self.iterations {
168+
try! self.clientBootstrap.connect(to: self.serverChannel.localAddress!).flatMap { $0.close() }.wait()
169+
}
170+
return 0
171+
}
172+
}
173+
174+
final class DatagramChannelWriteBenchmark: DatagramClientBenchmark, Benchmark {
175+
func run() throws -> Int {
176+
for _ in 1...self.iterations {
177+
_ = self.clientChannel.writeAndFlush(self.payload, promise: nil)
178+
}
179+
return 0
180+
}
181+
182+
override func tearDown() {
183+
try! self.clientHandler.receivedAtLeastOneRead.wait()
184+
super.tearDown()
185+
}
186+
}
187+
188+
189+
final class DatagramChannelReadBenchmark: DatagramClientBenchmark, Benchmark {
190+
func run() throws -> Int {
191+
for _ in 1...self.iterations {
192+
_ = self.clientChannel.writeAndFlush(self.payload, promise: nil)
193+
}
194+
return 0
195+
}
196+
197+
override func tearDown() {
198+
try! self.clientHandler.receivedAtLeastOneRead.wait()
199+
super.tearDown()
200+
}
201+
}

Sources/NIOPerformanceTester/main.swift

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1053,3 +1053,74 @@ try measureAndPrint(
10531053
iterations: 1_000_000
10541054
)
10551055
)
1056+
1057+
try measureAndPrint(
1058+
desc: "datagram_channel_bootstrap_create",
1059+
benchmark: DatagramBootstrapCreateBenchmark(
1060+
iterations: 100_000
1061+
)
1062+
)
1063+
1064+
try measureAndPrint(
1065+
desc: "datagram_channel_bind",
1066+
benchmark: DatagramChannelBindBenchmark(
1067+
iterations: 1_000
1068+
)
1069+
)
1070+
1071+
try measureAndPrint(
1072+
desc: "datagram_channel_connect",
1073+
benchmark: DatagramChannelConnectBenchmark(
1074+
iterations: 1_000
1075+
)
1076+
)
1077+
1078+
try measureAndPrint(
1079+
desc: "datagram_channel_write_unconnected_addressed",
1080+
benchmark: DatagramChannelWriteBenchmark(
1081+
iterations: 100_000,
1082+
connect: false,
1083+
envelope: true,
1084+
metadata: false
1085+
)
1086+
)
1087+
1088+
try measureAndPrint(
1089+
desc: "datagram_channel_write_connected_addressed",
1090+
benchmark: DatagramChannelWriteBenchmark(
1091+
iterations: 100_000,
1092+
connect: true,
1093+
envelope: true,
1094+
metadata: false
1095+
)
1096+
)
1097+
1098+
try measureAndPrint(
1099+
desc: "datagram_channel_write_connected_unaddressed",
1100+
benchmark: DatagramChannelWriteBenchmark(
1101+
iterations: 100_000,
1102+
connect: true,
1103+
envelope: false,
1104+
metadata: false
1105+
)
1106+
)
1107+
1108+
try measureAndPrint(
1109+
desc: "datagram_channel_write_unconnected_addressed_metadata",
1110+
benchmark: DatagramChannelWriteBenchmark(
1111+
iterations: 100_000,
1112+
connect: false,
1113+
envelope: true,
1114+
metadata: true
1115+
)
1116+
)
1117+
1118+
try measureAndPrint(
1119+
desc: "datagram_channel_write_connected_addressed_metadata",
1120+
benchmark: DatagramChannelWriteBenchmark(
1121+
iterations: 100_000,
1122+
connect: true,
1123+
envelope: true,
1124+
metadata: true
1125+
)
1126+
)

0 commit comments

Comments
 (0)