From ef9234d5c96c30462b308583a05088623d260832 Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Wed, 2 Oct 2024 16:47:49 +0200 Subject: [PATCH 1/3] Added initial benchmarks --- .gitignore | 1 + Benchmarks/.gitignore | 8 +++ Benchmarks/Package.swift | 30 +++++++++ Benchmarks/README.md | 23 +++++++ Benchmarks/Sources/Benchmarks.swift | 63 +++++++++++++++++++ .../AsyncHTTPClientTransport.swift | 27 ++++++++ 6 files changed, 152 insertions(+) create mode 100644 Benchmarks/.gitignore create mode 100644 Benchmarks/Package.swift create mode 100644 Benchmarks/README.md create mode 100644 Benchmarks/Sources/Benchmarks.swift diff --git a/.gitignore b/.gitignore index f6f5465..ff9a6c0 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ DerivedData/ /Package.resolved .ci/ .docc-build/ +Package.resolved diff --git a/Benchmarks/.gitignore b/Benchmarks/.gitignore new file mode 100644 index 0000000..fd3b359 --- /dev/null +++ b/Benchmarks/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc +.benchmarkBaselines/ diff --git a/Benchmarks/Package.swift b/Benchmarks/Package.swift new file mode 100644 index 0000000..4cf2b0e --- /dev/null +++ b/Benchmarks/Package.swift @@ -0,0 +1,30 @@ +// swift-tools-version: 5.9 + +import PackageDescription + +let package = Package( + name: "benchmarks", + platforms: [ + .macOS(.v14), + ], + products: [ + .executable(name: "Benchmarks", targets: ["Benchmarks"]), + ], + dependencies: [ + .package(name: "swift-openapi-async-http-client", path: "../"), + .package(url: "https://github.com/ordo-one/package-benchmark.git", from: "1.26.0"), + ], + targets: [ + .executableTarget( + name: "Benchmarks", + dependencies: [ + .product(name: "Benchmark", package: "package-benchmark"), + .product(name: "OpenAPIAsyncHTTPClient", package: "swift-openapi-async-http-client") + ], + path: "Sources", + plugins: [ + .plugin(name: "BenchmarkPlugin", package: "package-benchmark") + ] + ), + ] +) diff --git a/Benchmarks/README.md b/Benchmarks/README.md new file mode 100644 index 0000000..18cfca4 --- /dev/null +++ b/Benchmarks/README.md @@ -0,0 +1,23 @@ +# Benchmarks + +## Running + +Run and check results. + +```zsh +swift package benchmark +``` + +## Checking against baselines + +Run for a specific Swift version, for example: +```zsh +swift package benchmark baseline check --check-absolute-path Thresholds/5.10/ +``` + +## Updating baselines + +Update for a specific Swift version, for example: +```zsh +swift package --allow-writing-to-package-directory benchmark --format metricP90AbsoluteThresholds --path Thresholds/5.10/ +``` diff --git a/Benchmarks/Sources/Benchmarks.swift b/Benchmarks/Sources/Benchmarks.swift new file mode 100644 index 0000000..5af0f0f --- /dev/null +++ b/Benchmarks/Sources/Benchmarks.swift @@ -0,0 +1,63 @@ +import Benchmark +@_spi(Benchmarks) import OpenAPIAsyncHTTPClient +import AsyncHTTPClient +import NIOCore +import HTTPTypes +import OpenAPIRuntime +import Foundation + +let benchmarks = { + let defaultMetrics: [BenchmarkMetric] = [ + .mallocCountTotal, + .cpuTotal + ] + let config: Benchmark.Configuration = .init( + metrics: defaultMetrics, + scalingFactor: .kilo, + maxDuration: .seconds(10) + ) + + Benchmark("Creation.Default", configuration: config) { benchmark in + for _ in benchmark.scaledIterations { + blackHole({ + AsyncHTTPClientTransport() + }()) + } + } + + Benchmark("Conversion", configuration: config) { benchmark in + let request = HTTPRequest( + method: .post, + scheme: nil, + authority: nil, + path: "/stuff", + headerFields: [ + .init("x-stuff")!: "things" + ] + ) + let requestBody = HTTPBody("Hello world") + let baseURL = URL(string: "https://example.com")! + let response = HTTPClientResponse( + status: .ok, + headers: [ + "x-stuff": "things" + ], + body: .bytes(ByteBuffer(string: "Hello world")) + ) + let transport = AsyncHTTPClientTransport( + configuration: .init(), + requestSenderClosure: { _, _, _ in + response + } + ) + for _ in benchmark.scaledIterations { + let (response, responseBody) = try await transport.send( + request, + body: requestBody, + baseURL: baseURL, + operationID: "postThings" + ) + blackHole((response, responseBody)) + } + } +} diff --git a/Sources/OpenAPIAsyncHTTPClient/AsyncHTTPClientTransport.swift b/Sources/OpenAPIAsyncHTTPClient/AsyncHTTPClientTransport.swift index d16220e..fb06efd 100644 --- a/Sources/OpenAPIAsyncHTTPClient/AsyncHTTPClientTransport.swift +++ b/Sources/OpenAPIAsyncHTTPClient/AsyncHTTPClientTransport.swift @@ -126,6 +126,33 @@ public struct AsyncHTTPClientTransport: ClientTransport { self.requestSender = requestSender } + /// Creates a new transport. + /// - Parameters: + /// - configuration: A set of configuration values used by the transport. + /// - requestSender: The underlying request sender closure. + @_spi(Benchmarks) + public init( + configuration: Configuration, + requestSenderClosure: @Sendable @escaping (HTTPClientRequest, HTTPClient, TimeAmount) async throws -> HTTPClientResponse + ) { + struct ClosureRequestSender: HTTPRequestSending { + var sendClosure: @Sendable ( + AsyncHTTPClientTransport.Request, + HTTPClient, + TimeAmount + ) async throws -> AsyncHTTPClientTransport.Response + func send( + request: AsyncHTTPClientTransport.Request, + with client: HTTPClient, + timeout: TimeAmount + ) async throws -> AsyncHTTPClientTransport.Response { + try await sendClosure(request, client, timeout) + } + } + self.configuration = configuration + self.requestSender = ClosureRequestSender(sendClosure: requestSenderClosure) + } + /// Creates a new transport. /// - Parameter configuration: A set of configuration values used by the transport. public init(configuration: Configuration = .init()) { From 37e8fae647425803a5fd6b95739e091a430c7bd9 Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Wed, 2 Oct 2024 17:01:32 +0200 Subject: [PATCH 2/3] Fixups --- Benchmarks/Sources/Benchmarks.swift | 14 ++++++++++ Package.swift | 2 +- .../AsyncHTTPClientTransport.swift | 27 +++++++------------ scripts/check-license-headers.sh | 3 +++ 4 files changed, 28 insertions(+), 18 deletions(-) diff --git a/Benchmarks/Sources/Benchmarks.swift b/Benchmarks/Sources/Benchmarks.swift index 5af0f0f..2ac9f99 100644 --- a/Benchmarks/Sources/Benchmarks.swift +++ b/Benchmarks/Sources/Benchmarks.swift @@ -1,3 +1,17 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftOpenAPIGenerator open source project +// +// Copyright (c) YEARS Apple Inc. and the SwiftOpenAPIGenerator project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + import Benchmark @_spi(Benchmarks) import OpenAPIAsyncHTTPClient import AsyncHTTPClient diff --git a/Package.swift b/Package.swift index c1d69ba..21c12ed 100644 --- a/Package.swift +++ b/Package.swift @@ -35,7 +35,7 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/apple/swift-nio", from: "2.58.0"), - .package(url: "https://github.com/swift-server/async-http-client.git", from: "1.19.0"), + .package(url: "https://github.com/swift-server/async-http-client.git", from: "1.23.0"), .package(url: "https://github.com/apple/swift-openapi-runtime", from: "1.0.0"), .package(url: "https://github.com/apple/swift-http-types", from: "1.0.0"), .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"), diff --git a/Sources/OpenAPIAsyncHTTPClient/AsyncHTTPClientTransport.swift b/Sources/OpenAPIAsyncHTTPClient/AsyncHTTPClientTransport.swift index fb06efd..9877149 100644 --- a/Sources/OpenAPIAsyncHTTPClient/AsyncHTTPClientTransport.swift +++ b/Sources/OpenAPIAsyncHTTPClient/AsyncHTTPClientTransport.swift @@ -130,24 +130,18 @@ public struct AsyncHTTPClientTransport: ClientTransport { /// - Parameters: /// - configuration: A set of configuration values used by the transport. /// - requestSender: The underlying request sender closure. - @_spi(Benchmarks) - public init( + @_spi(Benchmarks) public init( configuration: Configuration, - requestSenderClosure: @Sendable @escaping (HTTPClientRequest, HTTPClient, TimeAmount) async throws -> HTTPClientResponse + requestSenderClosure: @Sendable @escaping (HTTPClientRequest, HTTPClient, TimeAmount) async throws -> + HTTPClientResponse ) { struct ClosureRequestSender: HTTPRequestSending { - var sendClosure: @Sendable ( - AsyncHTTPClientTransport.Request, - HTTPClient, - TimeAmount - ) async throws -> AsyncHTTPClientTransport.Response - func send( - request: AsyncHTTPClientTransport.Request, - with client: HTTPClient, - timeout: TimeAmount - ) async throws -> AsyncHTTPClientTransport.Response { - try await sendClosure(request, client, timeout) - } + var sendClosure: + @Sendable (AsyncHTTPClientTransport.Request, HTTPClient, TimeAmount) async throws -> + AsyncHTTPClientTransport.Response + func send(request: AsyncHTTPClientTransport.Request, with client: HTTPClient, timeout: TimeAmount) + async throws -> AsyncHTTPClientTransport.Response + { try await sendClosure(request, client, timeout) } } self.configuration = configuration self.requestSender = ClosureRequestSender(sendClosure: requestSenderClosure) @@ -201,8 +195,7 @@ public struct AsyncHTTPClientTransport: ClientTransport { let length: HTTPClientRequest.Body.Length switch body.length { case .unknown: length = .unknown - case .known(let count): - if let intValue = Int(exactly: count) { length = .known(intValue) } else { length = .unknown } + case .known(let count): length = .known(count) } clientRequest.body = .stream(body.map { .init(bytes: $0) }, length: length) } diff --git a/scripts/check-license-headers.sh b/scripts/check-license-headers.sh index 9a52856..0dff43f 100644 --- a/scripts/check-license-headers.sh +++ b/scripts/check-license-headers.sh @@ -43,6 +43,9 @@ read -ra PATHS_TO_CHECK_FOR_LICENSE <<< "$( \ ":(exclude).gitignore" \ ":(exclude).spi.yml" \ ":(exclude).swift-format" \ + ":(exclude)Benchmarks/.gitignore" \ + ":(exclude)Benchmarks/Package.swift" \ + ":(exclude)Benchmarks/README.md" \ ":(exclude)CODE_OF_CONDUCT.md" \ ":(exclude)CONTRIBUTING.md" \ ":(exclude)CONTRIBUTORS.txt" \ From 3762e1f32228dad848d6322e9783d4243917202d Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Wed, 2 Oct 2024 17:08:42 +0200 Subject: [PATCH 3/3] Fix up a comment --- Sources/OpenAPIAsyncHTTPClient/AsyncHTTPClientTransport.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/OpenAPIAsyncHTTPClient/AsyncHTTPClientTransport.swift b/Sources/OpenAPIAsyncHTTPClient/AsyncHTTPClientTransport.swift index 9877149..b0fbb02 100644 --- a/Sources/OpenAPIAsyncHTTPClient/AsyncHTTPClientTransport.swift +++ b/Sources/OpenAPIAsyncHTTPClient/AsyncHTTPClientTransport.swift @@ -129,7 +129,7 @@ public struct AsyncHTTPClientTransport: ClientTransport { /// Creates a new transport. /// - Parameters: /// - configuration: A set of configuration values used by the transport. - /// - requestSender: The underlying request sender closure. + /// - requestSenderClosure: The underlying request sender closure. @_spi(Benchmarks) public init( configuration: Configuration, requestSenderClosure: @Sendable @escaping (HTTPClientRequest, HTTPClient, TimeAmount) async throws ->