From 5df07d8ef8e74800e9e03e9922ad825544da7d11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Stormacq?= Date: Fri, 1 Aug 2025 18:36:15 +0400 Subject: [PATCH 1/6] fix performance test script --- Sources/MockServer/MockHTTPServer.swift | 36 ++++---- scripts/performance_test.sh | 105 ++++++++++++++---------- 2 files changed, 74 insertions(+), 67 deletions(-) diff --git a/Sources/MockServer/MockHTTPServer.swift b/Sources/MockServer/MockHTTPServer.swift index 78685c52..3fc63189 100644 --- a/Sources/MockServer/MockHTTPServer.swift +++ b/Sources/MockServer/MockHTTPServer.swift @@ -35,7 +35,7 @@ struct HttpServer { private let eventLoopGroup: MultiThreadedEventLoopGroup /// the mode. Are we mocking a server for a Lambda function that expects a String or a JSON document? (default: string) private let mode: Mode - /// the number of connections this server must accept before shutting down (default: 1) + /// the number of invocations this server must accept before shutting down (default: 1) private let maxInvocations: Int /// the logger (control verbosity with LOG_LEVEL environment variable) private let logger: Logger @@ -91,10 +91,6 @@ struct HttpServer { ] ) - // This counter is used to track the number of incoming connections. - // This mock servers accepts n TCP connection then shutdowns - let connectionCounter = SharedCounter(maxValue: self.maxInvocations) - // We are handling each incoming connection in a separate child task. It is important // to use a discarding task group here which automatically discards finished child tasks. // A normal task group retains all child tasks and their outputs in memory until they are @@ -105,21 +101,15 @@ struct HttpServer { try await channel.executeThenClose { inbound in for try await connectionChannel in inbound { - let counter = connectionCounter.current() - logger.trace("Handling new connection", metadata: ["connectionNumber": "\(counter)"]) - group.addTask { - await self.handleConnection(channel: connectionChannel) - logger.trace("Done handling connection", metadata: ["connectionNumber": "\(counter)"]) + await self.handleConnection(channel: connectionChannel, maxInvocations: self.maxInvocations) + logger.trace("Done handling connection") //, metadata: ["connectionNumber": "\(counter)"]) } - if connectionCounter.increment() { - logger.info( - "Maximum number of connections reached, shutting down after current connection", - metadata: ["maxConnections": "\(self.maxInvocations)"] - ) - break // this causes the server to shutdown after handling the connection - } + // This mock server only accepts one connection + // the Lambda Function Runtime will send multiple requests on that single connection + // This Mock Server closes the connection when MAX_INVOCATION is reached + break } } } @@ -131,17 +121,19 @@ struct HttpServer { /// It handles two requests: one for the next invocation and one for the response. /// when the maximum number of requests is reached, it closes the connection. private func handleConnection( - channel: NIOAsyncChannel + channel: NIOAsyncChannel, + maxInvocations: Int ) async { var requestHead: HTTPRequestHead! var requestBody: ByteBuffer? - // each Lambda invocation results in TWO HTTP requests (next and response) - let requestCount = SharedCounter(maxValue: 2) + // each Lambda invocation results in TWO HTTP requests (GET /next and POST /response) + let maxRequests = maxInvocations * 2 + let requestCount = SharedCounter(maxValue: maxRequests) // Note that this method is non-throwing and we are catching any error. - // We do this since we don't want to tear down the whole server when a single connection + // We do this since we don't want to tear down the whole server when a single request // encounters an error. do { try await channel.executeThenClose { inbound, outbound in @@ -178,7 +170,7 @@ struct HttpServer { if requestCount.increment() { logger.info( "Maximum number of requests reached, closing this connection", - metadata: ["maxRequest": "2"] + metadata: ["maxRequest": "\(maxRequests)"] ) break // this finishes handiling request on this connection } diff --git a/scripts/performance_test.sh b/scripts/performance_test.sh index e4157cb0..8c92b83f 100755 --- a/scripts/performance_test.sh +++ b/scripts/performance_test.sh @@ -3,7 +3,7 @@ ## ## This source file is part of the SwiftAWSLambdaRuntime open source project ## -## Copyright (c) 2017-2022 Apple Inc. and the SwiftAWSLambdaRuntime project authors +## Copyright (c) 2017-2025 Apple Inc. and the SwiftAWSLambdaRuntime project authors ## Licensed under Apache License v2.0 ## ## See LICENSE.txt for license information @@ -13,67 +13,86 @@ ## ##===----------------------------------------------------------------------===## -set -eu +# set -eu +# set -euo pipefail + +log() { printf -- "** %s\n" "$*" >&2; } +error() { printf -- "** ERROR: %s\n" "$*" >&2; } +fatal() { error "$@"; exit 1; } export HOST=127.0.0.1 -export PORT=3000 +export PORT=7000 export AWS_LAMBDA_RUNTIME_API="$HOST:$PORT" -export LOG_LEVEL=warning # important, otherwise log becomes a bottleneck +export LOG_LEVEL=error # important, otherwise log becomes a bottleneck + +DATE_CMD="date" +# using gdate on darwin for nanoseconds +if [[ $(uname -s) == "Darwin" ]]; then + # DATE_CMD="gdate" + DATE_CMD="date" #temp for testing +fi +echo "⏱️ using $DATE_CMD to count time" -# using gdate on mdarwin for nanoseconds -if [[ $(uname -s) == "Linux" ]]; then - shopt -s expand_aliases - alias gdate="date" +if ! command -v "$DATE_CMD" &> /dev/null; then + fatal "$DATE_CMD could not be found. Please install $DATE_CMD to proceed." fi +echo "🏗️ Building library and test functions" swift build -c release -Xswiftc -g LAMBDA_USE_LOCAL_DEPS=../.. swift build --package-path Examples/HelloWorld -c release -Xswiftc -g LAMBDA_USE_LOCAL_DEPS=../.. swift build --package-path Examples/HelloJSON -c release -Xswiftc -g cleanup() { - kill -9 $server_pid # ignore-unacceptable-language + pkill -9 MockServer && echo "killed previous mock server" # ignore-unacceptable-language } -trap "cleanup" ERR +# start a mock server +start_mockserver() { + # TODO: check if we have two parameters + MODE=$1 + INVOCATIONS=$2 + pkill -9 MockServer && echo "killed previous mock server" && sleep 1 # ignore-unacceptable-language + echo "👨‍🔧 starting server in $MODE mode for $INVOCATIONS invocations" + (MAX_INVOCATIONS="$INVOCATIONS" MODE="$MODE" ./.build/release/MockServer) & + server_pid=$! + sleep 1 + kill -0 $server_pid # check server is alive # ignore-unacceptable-language +} -cold_iterations=1000 -warm_iterations=10000 +cold_iterations=100 +warm_iterations=1000 results=() #------------------ # string #------------------ -export MODE=string +MODE=string -# start (fork) mock server -pkill -9 MockServer && echo "killed previous servers" && sleep 1 # ignore-unacceptable-language -echo "starting server in $MODE mode" -(./.build/release/MockServer) & -server_pid=$! -sleep 1 -kill -0 $server_pid # check server is alive # ignore-unacceptable-language +# Start mock server +start_mockserver $MODE $cold_iterations # cold start -echo "running $MODE mode cold test" +echo "🚀❄️ running $MODE mode $cold_iterations cold test" cold=() -export MAX_REQUESTS=1 for (( i=0; i Date: Fri, 1 Aug 2025 21:25:17 +0200 Subject: [PATCH 2/6] swift format --- Sources/MockServer/MockHTTPServer.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/MockServer/MockHTTPServer.swift b/Sources/MockServer/MockHTTPServer.swift index 3fc63189..ada1d765 100644 --- a/Sources/MockServer/MockHTTPServer.swift +++ b/Sources/MockServer/MockHTTPServer.swift @@ -103,7 +103,7 @@ struct HttpServer { group.addTask { await self.handleConnection(channel: connectionChannel, maxInvocations: self.maxInvocations) - logger.trace("Done handling connection") //, metadata: ["connectionNumber": "\(counter)"]) + logger.trace("Done handling connection") } // This mock server only accepts one connection From 55badb2f5bc2a50f7cd9338f26a77039aa7bdf7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Stormacq?= Date: Fri, 1 Aug 2025 21:26:10 +0200 Subject: [PATCH 3/6] shell check --- scripts/performance_test.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/performance_test.sh b/scripts/performance_test.sh index 8c92b83f..a67cb484 100755 --- a/scripts/performance_test.sh +++ b/scripts/performance_test.sh @@ -70,7 +70,7 @@ results=() MODE=string # Start mock server -start_mockserver $MODE $cold_iterations +start_mockserver "$MODE" "$cold_iterations" # cold start echo "🚀❄️ running $MODE mode $cold_iterations cold test" @@ -86,7 +86,7 @@ avg_cold=$((sum_cold/cold_iterations)) results+=( "$MODE, cold: $avg_cold (ns)" ) # reset mock server -start_mockserver $MODE $warm_iterations +start_mockserver "$MODE" "$warm_iterations" # normal calls echo "🚀🌤️ running $MODE mode warm test" From 5cd8a1c1d8822eb07926dd1653407d8719206a57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Stormacq?= Date: Fri, 1 Aug 2025 21:30:19 +0200 Subject: [PATCH 4/6] shell check 2 --- scripts/performance_test.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/performance_test.sh b/scripts/performance_test.sh index a67cb484..f0555c44 100755 --- a/scripts/performance_test.sh +++ b/scripts/performance_test.sh @@ -104,7 +104,7 @@ results+=( "$MODE, warm: $avg_warm (ns)" ) export MODE=json # Start mock server -start_mockserver $MODE $cold_iterations +start_mockserver "$MODE" "$cold_iterations" # cold start echo "🚀❄️ running $MODE mode cold test" @@ -120,7 +120,7 @@ avg_cold=$((sum_cold/cold_iterations)) results+=( "$MODE, cold: $avg_cold (ns)" ) # reset mock server -start_mockserver $MODE $warm_iterations +start_mockserver "$MODE" "$warm_iterations" # normal calls echo "🚀🌤️ running $MODE mode warm test" From 95066824c16ca85453277cc455872addb7dbb361 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Stormacq?= Date: Fri, 1 Aug 2025 21:37:23 +0200 Subject: [PATCH 5/6] check if gdate is installed + cleanup --- scripts/performance_test.sh | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/scripts/performance_test.sh b/scripts/performance_test.sh index f0555c44..24ff5e50 100755 --- a/scripts/performance_test.sh +++ b/scripts/performance_test.sh @@ -13,9 +13,6 @@ ## ##===----------------------------------------------------------------------===## -# set -eu -# set -euo pipefail - log() { printf -- "** %s\n" "$*" >&2; } error() { printf -- "** ERROR: %s\n" "$*" >&2; } fatal() { error "$@"; exit 1; } @@ -27,9 +24,12 @@ export LOG_LEVEL=error # important, otherwise log becomes a bottleneck DATE_CMD="date" # using gdate on darwin for nanoseconds +# gdate is installed by coreutils on macOS if [[ $(uname -s) == "Darwin" ]]; then - # DATE_CMD="gdate" - DATE_CMD="date" #temp for testing + if ! command -v gdate &> /dev/null; then + fatal "gdate could not be found. Please `brew install coreutils` to proceed." + fi + DATE_CMD="gdate" fi echo "⏱️ using $DATE_CMD to count time" @@ -48,7 +48,9 @@ cleanup() { # start a mock server start_mockserver() { - # TODO: check if we have two parameters + if [ $# -ne 2 ]; then + fatal "Usage: $0 " + fi MODE=$1 INVOCATIONS=$2 pkill -9 MockServer && echo "killed previous mock server" && sleep 1 # ignore-unacceptable-language From 47f159b6468565b1a804bcdc9d0a6db015e6f815 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Stormacq?= Date: Fri, 1 Aug 2025 21:41:28 +0200 Subject: [PATCH 6/6] shell check 3 --- scripts/performance_test.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/performance_test.sh b/scripts/performance_test.sh index 24ff5e50..700b6810 100755 --- a/scripts/performance_test.sh +++ b/scripts/performance_test.sh @@ -27,7 +27,8 @@ DATE_CMD="date" # gdate is installed by coreutils on macOS if [[ $(uname -s) == "Darwin" ]]; then if ! command -v gdate &> /dev/null; then - fatal "gdate could not be found. Please `brew install coreutils` to proceed." + # shellcheck disable=SC2006 # we explicitly want to use backticks here + fatal "gdate could not be found. Please \`brew install coreutils\` to proceed." fi DATE_CMD="gdate" fi