Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ jobs:
linux_nightly_6_0_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"
linux_nightly_main_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"

benchmarks:
name: Benchmarks
uses: apple/swift-nio/.github/workflows/benchmarks.yml@main
with:
benchmark_package_path: "Benchmarks"

cxx-interop:
name: Cxx interop
uses: apple/swift-nio/.github/workflows/cxx_interop.yml@main
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/scheduled.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,9 @@ jobs:
linux_6_0_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"
linux_nightly_6_0_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"
linux_nightly_main_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"

benchmarks:
name: Benchmarks
uses: apple/swift-nio/.github/workflows/benchmarks.yml@main
with:
benchmark_package_path: "Benchmarks"
66 changes: 66 additions & 0 deletions Benchmarks/Benchmarks/NIOSSHBenchmarks/Benchmarks.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2024 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import Benchmark
import NIOCore
import NIOEmbedded
import NIOSSH

let benchmarks = {
let defaultMetrics: [BenchmarkMetric] = [
.mallocCountTotal
]

Benchmark(
"OneCommandPerConnection",
configuration: .init(
metrics: defaultMetrics,
scalingFactor: .kilo,
maxDuration: .seconds(10_000_000),
maxIterations: 10
)
) { benchmark in
try runOneCommandPerConnection(
numberOfConnections: benchmark.scaledIterations.upperBound
)
}

Benchmark(
"StreamingLargeMessageInSmallChunks",
configuration: .init(
metrics: defaultMetrics,
scalingFactor: .kilo,
maxDuration: .seconds(10_000_000),
maxIterations: 10
)
) { benchmark in
try runStreamingLargeMessageInSmallChunks(
numberOfChunks: benchmark.scaledIterations.upperBound
)
}

Benchmark(
"ManySmallCommandsPerConnection",
configuration: .init(
metrics: defaultMetrics,
scalingFactor: .kilo,
maxDuration: .seconds(10_000_000),
maxIterations: 10
)
) { benchmark in
try runManySmallCommandsPerConnection(
numberOfWrites: benchmark.scaledIterations.upperBound
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2019-2024 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import NIOCore
import NIOEmbedded
import NIOSSH

func runManySmallCommandsPerConnection(numberOfWrites: Int) throws {
final class ServerHandler: ChannelInboundHandler {
typealias InboundIn = SSHChannelData
typealias OutboundOut = SSHChannelData

func channelRead(context: ChannelHandlerContext, data: NIOAny) {
context.write(data, promise: nil)
}

func channelReadComplete(context: ChannelHandlerContext) {
context.flush()
}
}

final class ClientHandler: ChannelInboundHandler {
typealias InboundIn = SSHChannelData
typealias OutboundOut = SSHChannelData

private var didSend: Bool = false
private let message: ByteBuffer = ByteBuffer(string: "hello")
var readBytes: Int = 0

func handlerAdded(context: ChannelHandlerContext) {
if context.channel.isActive {
self.sendInitialMessage(context: context)
}
}

func channelActive(context: ChannelHandlerContext) {
self.sendInitialMessage(context: context)
}

private func sendInitialMessage(context: ChannelHandlerContext) {
if self.didSend { return }

self.didSend = true
let data = SSHChannelData(type: .channel, data: .byteBuffer(message))
context.writeAndFlush(self.wrapOutboundOut(data), promise: nil)
}

func channelRead(context: ChannelHandlerContext, data: NIOAny) {
let data = self.unwrapInboundIn(data)
guard case .byteBuffer(let buffer) = data.data else {
fatalError()
}
self.readBytes += buffer.readableBytes

if self.readBytes == self.message.readableBytes {
context.close(promise: nil)
}
}
}

let loop = EmbeddedEventLoop()
let hostKey = NIOSSHPrivateKey(ed25519Key: .init())

let clientChannel = EmbeddedChannel(loop: loop)
let serverChannel = EmbeddedChannel(loop: loop)

try clientChannel.pipeline.addHandler(
NIOSSHHandler(
role: .client(
.init(
userAuthDelegate: HardcodedClientPasswordDelegate(),
serverAuthDelegate: AcceptAllHostKeysDelegate()
)
),
allocator: clientChannel.allocator,
inboundChildChannelInitializer: nil
)
).wait()
try serverChannel.pipeline.addHandler(
NIOSSHHandler(
role: .server(
.init(
hostKeys: [hostKey],
userAuthDelegate: HardcodedServerPasswordDelegate()
)
),
allocator: serverChannel.allocator,
inboundChildChannelInitializer: { channel, _ in
channel.pipeline.addHandler(ServerHandler())
}
)
).wait()

try clientChannel.connect(to: SocketAddress(ipAddress: "1.2.3.4", port: 5678)).wait()
try serverChannel.connect(to: SocketAddress(ipAddress: "1.2.3.4", port: 5678)).wait()

for _ in 0..<numberOfWrites {
let clientHandler = ClientHandler()

let childChannelFuture: EventLoopFuture<Channel> = clientChannel.pipeline.handler(type: NIOSSHHandler.self)
.flatMap { sshHandler in
let promise = clientChannel.eventLoop.makePromise(of: Channel.self)
sshHandler.createChannel(promise) { childChannel, _ in
childChannel.pipeline.addHandlers([clientHandler])
}
return promise.futureResult
}
clientChannel.embeddedEventLoop.run()
try interactInMemory(clientChannel, serverChannel)

let childChannel = try childChannelFuture.wait()

try childChannel.closeFuture.wait()
}

try clientChannel.close().wait()
try serverChannel.close().wait()

}
130 changes: 130 additions & 0 deletions Benchmarks/Benchmarks/NIOSSHBenchmarks/OneCommandPerConnection.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2019-2024 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import NIOCore
import NIOEmbedded
import NIOSSH

func runOneCommandPerConnection(numberOfConnections: Int) throws {
final class ServerHandler: ChannelInboundHandler {
typealias InboundIn = SSHChannelData
typealias OutboundOut = SSHChannelData

func channelRead(context: ChannelHandlerContext, data: NIOAny) {
context.write(data, promise: nil)
}

func channelReadComplete(context: ChannelHandlerContext) {
context.flush()
}
}

final class ClientHandler: ChannelInboundHandler {
typealias InboundIn = SSHChannelData
typealias OutboundOut = SSHChannelData

private var didSend: Bool = false
private let message: ByteBuffer = ByteBuffer(string: "hello")
var readBytes: Int = 0

func handlerAdded(context: ChannelHandlerContext) {
if context.channel.isActive {
self.sendInitialMessage(context: context)
}
}

func channelActive(context: ChannelHandlerContext) {
self.sendInitialMessage(context: context)
}

private func sendInitialMessage(context: ChannelHandlerContext) {
if self.didSend { return }

self.didSend = true
let data = SSHChannelData(type: .channel, data: .byteBuffer(message))
context.writeAndFlush(self.wrapOutboundOut(data), promise: nil)
}

func channelRead(context: ChannelHandlerContext, data: NIOAny) {
let data = self.unwrapInboundIn(data)
guard case .byteBuffer(let buffer) = data.data else {
fatalError()
}
self.readBytes += buffer.readableBytes

if self.readBytes == self.message.readableBytes {
context.close(promise: nil)
}
}
}

let loop = EmbeddedEventLoop()
let hostKey = NIOSSHPrivateKey(ed25519Key: .init())

for _ in 0..<numberOfConnections {
let clientChannel = EmbeddedChannel(loop: loop)
let serverChannel = EmbeddedChannel(loop: loop)

try clientChannel.pipeline.addHandler(
NIOSSHHandler(
role: .client(
.init(
userAuthDelegate: HardcodedClientPasswordDelegate(),
serverAuthDelegate: AcceptAllHostKeysDelegate()
)
),
allocator: clientChannel.allocator,
inboundChildChannelInitializer: nil
)
).wait()
try serverChannel.pipeline.addHandler(
NIOSSHHandler(
role: .server(
.init(
hostKeys: [hostKey],
userAuthDelegate: HardcodedServerPasswordDelegate()
)
),
allocator: serverChannel.allocator,
inboundChildChannelInitializer: { channel, _ in
channel.pipeline.addHandler(ServerHandler())
}
)
).wait()

try clientChannel.connect(to: SocketAddress(ipAddress: "1.2.3.4", port: 5678)).wait()
try serverChannel.connect(to: SocketAddress(ipAddress: "1.2.3.4", port: 5678)).wait()

let clientHandler = ClientHandler()

let childChannelFuture: EventLoopFuture<Channel> = clientChannel.pipeline.handler(type: NIOSSHHandler.self)
.flatMap { sshHandler in
let promise = clientChannel.eventLoop.makePromise(of: Channel.self)
sshHandler.createChannel(promise) { childChannel, _ in
childChannel.pipeline.addHandlers([clientHandler])
}
return promise.futureResult
}
clientChannel.embeddedEventLoop.run()
try interactInMemory(clientChannel, serverChannel)

let childChannel = try childChannelFuture.wait()

try childChannel.closeFuture.wait()

try clientChannel.close().wait()
try serverChannel.close().wait()
}

}
Loading