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
1 change: 1 addition & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ let package = Package(
.target(name: "NIOSSH", dependencies: ["NIO", "NIOFoundationCompat", "Crypto"]),
.target(name: "NIOSSHClient", dependencies: ["NIO", "NIOSSH", "NIOConcurrencyHelpers"]),
.target(name: "NIOSSHServer", dependencies: ["NIO", "NIOSSH", "NIOFoundationCompat", "Crypto"]),
.target(name: "NIOSSHPerformanceTester", dependencies: ["NIO", "NIOSSH", "Crypto"]),
.testTarget(name: "NIOSSHTests", dependencies: ["NIOSSH", "NIO", "NIOFoundationCompat"]),
]
)
29 changes: 29 additions & 0 deletions Sources/NIOSSHPerformanceTester/Benchmark.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2020 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
//
//===----------------------------------------------------------------------===//

protocol Benchmark: AnyObject {
func setUp() throws
func tearDown()
func run() throws -> Int
}

func measureAndPrint<B: Benchmark>(desc: String, benchmark bench: B) throws {
try bench.setUp()
defer {
bench.tearDown()
}
try measureAndPrint(desc: desc) {
try bench.run()
}
}
44 changes: 44 additions & 0 deletions Sources/NIOSSHPerformanceTester/BenchmarkHandshake.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2020 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 Crypto
import NIO
import NIOSSH

final class BenchmarkHandshake: Benchmark {
let serverRole = SSHConnectionRole.server(.init(hostKeys: [.init(ed25519Key: .init())], userAuthDelegate: ExpectPasswordDelegate("password")))
let clientRole = SSHConnectionRole.client(.init(userAuthDelegate: RepeatingPasswordDelegate("password")))
let loopCount: Int

init(loopCount: Int) {
self.loopCount = loopCount
}

func setUp() throws {}

func tearDown() {}

func run() throws -> Int {
for _ in 0 ..< self.loopCount {
let b2b = BackToBackEmbeddedChannel()
b2b.client.connect(to: try .init(unixDomainSocketPath: "/foo"), promise: nil)
b2b.server.connect(to: try .init(unixDomainSocketPath: "/foo"), promise: nil)

try b2b.client.pipeline.addHandler(NIOSSHHandler(role: self.clientRole, allocator: b2b.client.allocator, inboundChildChannelInitializer: nil)).wait()
try b2b.server.pipeline.addHandler(NIOSSHHandler(role: self.serverRole, allocator: b2b.server.allocator, inboundChildChannelInitializer: nil)).wait()
try b2b.interactInMemory()
}

return self.loopCount
}
}
63 changes: 63 additions & 0 deletions Sources/NIOSSHPerformanceTester/main.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2020 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 Crypto
import Dispatch
import Foundation
import NIO
import NIOSSH

// MARK: Test Harness

var warning: String = ""
assert({
print("======================================================")
print("= YOU ARE RUNNING NIOPerformanceTester IN DEBUG MODE =")
print("======================================================")
warning = " <<< DEBUG MODE >>>"
return true
}())

public func measure(_ fn: () throws -> Int) rethrows -> [TimeInterval] {
func measureOne(_ fn: () throws -> Int) rethrows -> TimeInterval {
let start = Date()
_ = try fn()
let end = Date()
return end.timeIntervalSince(start)
}

_ = try measureOne(fn) /* pre-heat and throw away */
var measurements = Array(repeating: 0.0, count: 10)
for i in 0 ..< 10 {
measurements[i] = try measureOne(fn)
}

return measurements
}

let limitSet = CommandLine.arguments.dropFirst()

public func measureAndPrint(desc: String, fn: () throws -> Int) rethrows {
if limitSet.count == 0 || limitSet.contains(desc) {
print("measuring\(warning): \(desc): ", terminator: "")
let measurements = try measure(fn)
print(measurements.reduce("") { $0 + "\($1), " })
} else {
print("skipping '\(desc)', limit set = \(limitSet)")
}
}

// MARK: Utilities

try measureAndPrint(desc: "10000_handshakes", benchmark: BenchmarkHandshake(loopCount: 10000))
88 changes: 88 additions & 0 deletions Sources/NIOSSHPerformanceTester/shared.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2020 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 Crypto
import NIO
import NIOSSH

class BackToBackEmbeddedChannel {
private(set) var client: EmbeddedChannel
private(set) var server: EmbeddedChannel
private var loop: EmbeddedEventLoop

init() {
self.loop = EmbeddedEventLoop()
self.client = EmbeddedChannel(loop: self.loop)
self.server = EmbeddedChannel(loop: self.loop)
}

func run() {
self.loop.run()
}

func interactInMemory() throws {
var workToDo = true

while workToDo {
workToDo = false

self.loop.run()
let clientDatum = try self.client.readOutbound(as: IOData.self)
let serverDatum = try self.server.readOutbound(as: IOData.self)

if let clientMsg = clientDatum {
try self.server.writeInbound(clientMsg)
workToDo = true
}

if let serverMsg = serverDatum {
try self.client.writeInbound(serverMsg)
workToDo = true
}
}
}
}

final class ExpectPasswordDelegate: NIOSSHServerUserAuthenticationDelegate {
let supportedAuthenticationMethods: NIOSSHAvailableUserAuthenticationMethods = .password

let expectedPassword: String

init(_ expectedPassword: String) {
self.expectedPassword = expectedPassword
}

func requestReceived(request: NIOSSHUserAuthenticationRequest, responsePromise: EventLoopPromise<NIOSSHUserAuthenticationOutcome>) {
guard case .password(let password) = request.request, password.password == self.expectedPassword else {
responsePromise.succeed(.failure)
return
}
responsePromise.succeed(.success)
}
}

final class RepeatingPasswordDelegate: NIOSSHClientUserAuthenticationDelegate {
let password: String

init(_ password: String) {
self.password = password
}

func nextAuthenticationType(availableMethods: NIOSSHAvailableUserAuthenticationMethods, nextChallengePromise: EventLoopPromise<NIOSSHUserAuthenticationOffer?>) {
if availableMethods.contains(.password) {
nextChallengePromise.succeed(.init(username: "foo", serviceName: "ssh-connection", offer: .password(.init(password: self.password))))
} else {
nextChallengePromise.succeed(nil)
}
}
}