Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
4a456ca
Serde benchmark changes
sichanyoo Mar 2, 2026
949b62b
Trim newline character from body of CBOR response protocol tests.
sichanyoo Mar 3, 2026
0943eb0
Merge branch 'main' into serde-benchmark
jbelkins May 21, 2026
b7a5593
Catch up with main, move all to smithy-swift
jbelkins May 22, 2026
eac6696
Use local smithy-swift
jbelkins May 22, 2026
176eed6
Move generated test code to SmithyTestUtil
jbelkins May 22, 2026
05f8150
Fix nsec conversion, make interceptors public
jbelkins May 22, 2026
edb528d
Run all serde benchmarks
jbelkins May 22, 2026
ce6a92e
Fix performance tests
jbelkins May 22, 2026
6c1a34f
Fix metadata
jbelkins May 22, 2026
09c06d4
Merge branch 'main' into jbe/serde_benchmark
jbelkins May 23, 2026
bfda239
Fix lint
jbelkins May 23, 2026
e4abdff
Use custom telemetry to collect serde times
jbelkins May 25, 2026
42c88dc
Logic for min & max iterations added
jbelkins May 25, 2026
df6f7d6
Fix tests, lint
jbelkins May 25, 2026
0f83e07
Refactor iteration out to runtime
jbelkins May 26, 2026
0de481d
Revert async config
jbelkins May 26, 2026
5c676ab
Fix lint
jbelkins May 26, 2026
e1147bb
Merge branch 'main' into jbe/serde_benchmark
jbelkins May 26, 2026
be19269
Merge branch 'main' into jbe/serde_benchmark
jbelkins May 28, 2026
dc60103
Merge branch 'main' into jbe/serde_benchmark
jbelkins Jun 1, 2026
4f43e5d
Refactor, simplify
jbelkins Jun 1, 2026
2c11eae
Refactor & clean up + test fixes
jbelkins Jun 2, 2026
d5b1724
post-review cleanup
jbelkins Jun 2, 2026
d173a11
Fix swiftlint
jbelkins Jun 2, 2026
73a598e
Codegen cleanup
jbelkins Jun 2, 2026
de5c38d
Merge branch 'main' into jbe/serde_benchmark
jbelkins Jun 2, 2026
65ab7ea
Fix codegen tests
jbelkins Jun 2, 2026
64431c0
Improve auth schemes, partition for smithy-based SDKs
jbelkins Jun 5, 2026
c4dd6ad
Merge branch 'main' into jbe/serde_benchmark
jbelkins Jun 7, 2026
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,6 @@ smithy-build.json
# swiftlint analyzer build logs
xcodebuild.log
xcodebuild-filtered.log

# serde benchmark results
/instance-results.json
1 change: 1 addition & 0 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ excluded:
- Sources/SmithyTestUtil/*
- Tests/*
- test-sdks/*
- serde-benchmark/*

analyzer_rules:
- unused_import
Expand Down
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ var runtimeTargets: [PackageDescription.Target] {
),
.target(
name: "SmithyTestUtil",
dependencies: ["ClientRuntime", "SmithyHTTPAPI", "SmithyIdentity", "SmithyCBOR"]
dependencies: ["ClientRuntime", "SmithyHTTPAPI", "SmithyIdentity", "SmithyCBOR", "SmithyTelemetryAPI"]
),
.target(
name: "SmithyIdentity",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//

import class Smithy.ContextBuilder
import struct SmithyHTTPAuth.SigV4AuthScheme

extension ContextBuilder {

Expand All @@ -22,5 +23,7 @@ extension ContextBuilder {
.withSocketTimeout(value: config.httpClientConfiguration.socketTimeout)
.withIdentityResolver(value: config.bearerTokenIdentityResolver, schemeID: "smithy.api#httpBearerAuth")
.withIdentityResolver(value: config.awsCredentialIdentityResolver, schemeID: "aws.auth#sigv4")
.withRegion(value: config.region)
.withSigningRegion(value: config.signingRegion)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,16 @@ public protocol DefaultHttpClientConfiguration: ClientConfiguration {
/// The AWS credential identity resolver to be used for AWS credentials.
var awsCredentialIdentityResolver: any AWSCredentialIdentityResolver { get set }

/// The region identifier to be used for sigv4 signing,
///
/// May be left nil if sigv4 is not used or if region will be resolved some other way.
var region: String? { get }

/// The signing region identifier to be used for sigv4 signing,
///
/// May be left nil if sigv4 is not used or if signing region will be resolved some other way.
var signingRegion: String? { get }

/// Adds a `HttpInterceptorProvider` that will be used to provide interceptors for all HTTP operations.
///
/// - Parameter provider: The `HttpInterceptorProvider` to add.
Expand Down
1 change: 1 addition & 0 deletions Sources/SmithyTestUtil/CBORComparator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
//
// SPDX-License-Identifier: Apache-2.0
//

import AwsCommonRuntimeKit
import Foundation

Expand Down
84 changes: 84 additions & 0 deletions Sources/SmithyTestUtil/SerdeBenchmarks/SerdeBenchmark.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

import Foundation

public struct SerdeBenchmark: Codable {
public let id: String
public let n: Int
public let mean: Int
public let p50: Int
public let p90: Int
public let p95: Int
public let p99: Int
public let std_dev: Int

public init(id: String, n: Int, mean: Int, p50: Int, p90: Int, p95: Int, p99: Int, std_dev: Int) {
self.id = id
self.n = n
self.mean = mean
self.p50 = p50
self.p90 = p90
self.p95 = p95
self.p99 = p99
self.std_dev = std_dev
}

public init(id: String, measurements: [Double]) {
let runCount = measurements.count

// Calcluate mean
var sum = 0.0
for num in measurements {
sum += num
}
let mean = sum / Double(runCount)

// Calculate standard deviation
var diffSquaredSum = 0.0
for num in measurements {
diffSquaredSum += (num - mean) * (num - mean)
}
let sd = sqrt(diffSquaredSum / Double(runCount - 1))
let percentiles = Self.calculatePercentiles(measurements)
self.id = id
self.n = runCount
self.mean = Int(mean * nsecPerSec)
self.p50 = percentiles.p50
self.p90 = percentiles.p90
self.p95 = percentiles.p95
self.p99 = percentiles.p99
self.std_dev = Int(sd * nsecPerSec)
}

private static func calculatePercentiles(
_ measurements: [Double]
) -> (p50: Int, p90: Int, p95: Int, p99: Int) {
let sorted = measurements.sorted()
let count = sorted.count

func percentile(_ p: Double) -> Int {
let index = p / 100.0 * Double(count - 1)
let lower = Int(floor(index))
let upper = Int(ceil(index))
if lower == upper {
return Int(sorted[lower] * nsecPerSec)
}
let fraction = index - Double(lower)
return Int((sorted[lower] * (1 - fraction) + sorted[upper] * fraction) * nsecPerSec)
}

return (
p50: percentile(50),
p90: percentile(90),
p95: percentile(95),
p99: percentile(99)
)
}
}

private let nsecPerSec = 1_000_000_000.0
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

public struct SerdeBenchmarkMetadata: Codable {
public let lang: String
public let software: [[String]]
public let os: String
public let instance: String
public let precision: String
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

import Foundation

public struct SerdeBenchmarkReport: Codable {
public let metadata: SerdeBenchmarkMetadata
public let serdeBenchmarks: [SerdeBenchmark]

public enum CodingKeys: String, CodingKey {
case metadata
case serdeBenchmarks = "serde_benchmarks"
}

public static func update(at path: String, with serdeBenchmark: SerdeBenchmark) throws {
let fileURL = URL(fileURLWithPath: path)
let newSerdeBenchmarkReport: SerdeBenchmarkReport
if FileManager.default.fileExists(atPath: path) {
let data = try Data(contentsOf: fileURL)
let serdeBenchmarkReport = try JSONDecoder().decode(Self.self, from: data)
newSerdeBenchmarkReport = serdeBenchmarkReport.adding(serdeBenchmark: serdeBenchmark)
} else {
newSerdeBenchmarkReport = Self(serdeBenchmarks: [serdeBenchmark])
}
let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
let data = try encoder.encode(newSerdeBenchmarkReport)
try data.write(to: fileURL)
}

public init(
lang: String = "swift",
software: [[String]] = [
["swift", "6.3.2"],
["smithy-swift", "0.206.0"],
],
os: String = "Ubuntu 24.04 LTS (x86_64)",
instance: String = "m7i.xlarge",
precision: String = "-9",
serdeBenchmarks: [SerdeBenchmark]
) {
self.metadata = SerdeBenchmarkMetadata(
lang: lang,
software: software,
os: os,
instance: instance,
precision: precision
)
self.serdeBenchmarks = serdeBenchmarks
}

public func adding(serdeBenchmark: SerdeBenchmark) -> Self {
let newSerdeBenchmarks = self.serdeBenchmarks.filter { $0.id != serdeBenchmark.id } + [serdeBenchmark]
return SerdeBenchmarkReport(
lang: self.metadata.lang,
software: self.metadata.software,
os: self.metadata.os,
instance: self.metadata.instance,
precision: self.metadata.precision,
serdeBenchmarks: newSerdeBenchmarks.sorted { $0.id < $1.id }
)
}
}
151 changes: 151 additions & 0 deletions Sources/SmithyTestUtil/SerdeBenchmarks/SerdeBenchmarkTelemetry.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

import Smithy
import SmithyTelemetryAPI

public final class SerdeBenchmarkTelemetryProvider: TelemetryProvider {
public let requestHistogram: SerdeBenchmarkHistogram
public let responseHistogram: SerdeBenchmarkHistogram
public let contextManager: any SmithyTelemetryAPI.TelemetryContextManager
public let loggerProvider: any SmithyTelemetryAPI.LoggerProvider
public let meterProvider: any SmithyTelemetryAPI.MeterProvider
public let tracerProvider: any SmithyTelemetryAPI.TracerProvider

public init() {
self.requestHistogram = SerdeBenchmarkHistogram()
self.responseHistogram = SerdeBenchmarkHistogram()
self.contextManager = NoOpTelemetryContextManager()
self.loggerProvider = DefaultLoggerProvider()
self.meterProvider = SerdeBenchmarkMeterProvider(
requestHistogram: requestHistogram,
responseHistogram: responseHistogram
)
self.tracerProvider = NoOpTracerProvider()
}
}

final class SerdeBenchmarkMeterProvider: MeterProvider {
let requestHistogram: any Histogram
let responseHistogram: any Histogram

init(requestHistogram: any Histogram, responseHistogram: any Histogram) {
self.requestHistogram = requestHistogram
self.responseHistogram = responseHistogram
}

func getMeter(scope: String, attributes: Smithy.Attributes?) -> any SmithyTelemetryAPI.Meter {
return SerdeBenchmarkMeter(requestHistogram: requestHistogram, responseHistogram: responseHistogram)
}
}

final class SerdeBenchmarkMeter: Meter {
let requestHistogram: any Histogram
let responseHistogram: any Histogram

init(requestHistogram: any Histogram, responseHistogram: any Histogram) {
self.requestHistogram = requestHistogram
self.responseHistogram = responseHistogram
}

func createGauge(name: String, callback: @escaping (any SmithyTelemetryAPI.DoubleAsyncMeasurement) -> Void, units: String?, description: String?) -> any SmithyTelemetryAPI.AsyncMeasurementHandle {
NoOpAsyncMeasurementHandle()
}

func createUpDownCounter(name: String, units: String?, description: String?) -> any SmithyTelemetryAPI.UpDownCounter {
NoOpUpDownCounter()
}

func createAsyncUpDownCounter(name: String, callback: @escaping (any SmithyTelemetryAPI.LongAsyncMeasurement) -> Void, units: String?, description: String?) -> any SmithyTelemetryAPI.AsyncMeasurementHandle {
NoOpAsyncMeasurementHandle()
}

func createCounter(name: String, units: String?, description: String?) -> any SmithyTelemetryAPI.MonotonicCounter {
NoOpMonotonicCounter()
}

func createAsyncMonotonicCounter(name: String, callback: @escaping (any SmithyTelemetryAPI.LongAsyncMeasurement) -> Void, units: String?, description: String?) -> any SmithyTelemetryAPI.AsyncMeasurementHandle {
NoOpAsyncMeasurementHandle()
}

func createHistogram(name: String, units: String?, description: String?) -> any SmithyTelemetryAPI.Histogram {
if name == "smithy.client.serialization_duration" {
requestHistogram
} else if name == "smithy.client.deserialization_duration" {
responseHistogram
} else {
NoOpHistogram()
}
}
}

public final class SerdeBenchmarkHistogram: @unchecked Sendable, Histogram {
public var value = -1.0
public var attributes = Attributes()

public init() {}

public func record(value: Double, attributes: Smithy.Attributes?, context: (any SmithyTelemetryAPI.TelemetryContext)?) {
self.value = value
self.attributes = attributes ?? Attributes()
}
}

fileprivate final class NoOpTelemetryContextManager: TelemetryContextManager {
func current() -> TelemetryContext { NoOpTelemetryContext() }
}

fileprivate final class NoOpTelemetryContext: TelemetryContext {
func makeCurrent() -> TelemetryScope { NoOpTelemetryScope() }
}

fileprivate final class NoOpTelemetryScope: TelemetryScope {
func end() {}
}

fileprivate final class DefaultLoggerProvider: LoggerProvider {
func getLogger(name: String) -> LogAgent { SwiftLogger(label: name) }
}

fileprivate final class NoOpTracerProvider: TracerProvider {
func getTracer(scope: String) -> Tracer { NoOpTracer() }
}

fileprivate final class NoOpAsyncMeasurementHandle: AsyncMeasurementHandle {
func stop() {}
}

fileprivate final class NoOpUpDownCounter: UpDownCounter {
func add(value: Int, attributes: Attributes?, context: TelemetryContext?) {}
}

fileprivate final class NoOpMonotonicCounter: MonotonicCounter {
func add(value: Int, attributes: Attributes?, context: TelemetryContext?) {}
}

fileprivate final class NoOpHistogram: Histogram {
func record(value: Double, attributes: Attributes?, context: TelemetryContext?) {}
}

fileprivate final class NoOpTracer: Tracer {
func createSpan(
name: String,
initialAttributes: Attributes?,
spanKind: SpanKind,
parentContext: TelemetryContext?
) -> TraceSpan {
NoOpTraceSpan()
}
}

fileprivate final class NoOpTraceSpan: TraceSpan {
let name: String = ""
func emitEvent(name: String, attributes: Attributes?) {}
func setAttribute<T>(key: AttributeKey<T>, value: T) {}
func setStatus(status: TraceSpanStatus) {}
func end() {}
}
Loading
Loading