From 21c487cc1f131d504664ef12bee81c57590b8aab Mon Sep 17 00:00:00 2001 From: Riccardo Venturi Date: Sun, 5 Nov 2023 00:15:47 +0100 Subject: [PATCH 01/32] fixing .net 8 minimal api --- loadtest/codebuild/load-test-buildspec.yml | 1 + loadtest/codebuild/run-all-load-tests.sh | 11 ++ .../ApiBootstrap/ApiBootstrap.csproj | 18 +-- .../ApiBootstrap/CloudWatchQueryExecution.cs | 51 -------- src/NET8MinimalAPI/ApiBootstrap/DateUtils.cs | 13 -- src/NET8MinimalAPI/ApiBootstrap/Function.cs | 53 ++------ .../ApiBootstrap/QueryResult.cs | 25 ---- src/NET8MinimalAPI/Makefile | 5 +- .../Report/load-test-report-arm64.json | 62 +++++++++ .../Report/load-test-report-arm64.txt | 18 +++ .../Report/load-test-report-x86.json | 62 +++++++++ .../Report/load-test-report-x86.txt | 18 +++ src/NET8MinimalAPI/Shared/Shared.csproj | 6 +- src/NET8MinimalAPI/deploy.sh | 2 + src/NET8MinimalAPI/omnisharp.json | 11 -- src/NET8MinimalAPI/run-loadtest.sh | 121 ++++++++++++++++++ src/NET8MinimalAPI/template.yaml | 66 +++++++--- 17 files changed, 369 insertions(+), 174 deletions(-) delete mode 100644 src/NET8MinimalAPI/ApiBootstrap/CloudWatchQueryExecution.cs delete mode 100644 src/NET8MinimalAPI/ApiBootstrap/DateUtils.cs delete mode 100644 src/NET8MinimalAPI/ApiBootstrap/QueryResult.cs create mode 100644 src/NET8MinimalAPI/Report/load-test-report-arm64.json create mode 100644 src/NET8MinimalAPI/Report/load-test-report-arm64.txt create mode 100644 src/NET8MinimalAPI/Report/load-test-report-x86.json create mode 100644 src/NET8MinimalAPI/Report/load-test-report-x86.txt create mode 100755 src/NET8MinimalAPI/deploy.sh delete mode 100644 src/NET8MinimalAPI/omnisharp.json create mode 100755 src/NET8MinimalAPI/run-loadtest.sh diff --git a/loadtest/codebuild/load-test-buildspec.yml b/loadtest/codebuild/load-test-buildspec.yml index d2c1e0f..af4fbcb 100644 --- a/loadtest/codebuild/load-test-buildspec.yml +++ b/loadtest/codebuild/load-test-buildspec.yml @@ -13,4 +13,5 @@ artifacts: files: - src/NET6MinimalAPI/Report/* - src/NET6MinimalAPIWebAdapter/Report/* + - src/NET8MinimalAPI/Report/* name: loadtest-$(date +%Y-%m-%H-%M) \ No newline at end of file diff --git a/loadtest/codebuild/run-all-load-tests.sh b/loadtest/codebuild/run-all-load-tests.sh index 1076019..94cfc22 100755 --- a/loadtest/codebuild/run-all-load-tests.sh +++ b/loadtest/codebuild/run-all-load-tests.sh @@ -37,3 +37,14 @@ else source ./deploy.sh source ./run-loadtest.sh $TEST_DURATIOMN_SEC $LOG_INTERVALL_MIN $LOG_DELETE fi + +#export LT_NET6_MINIMAL_API_WEB_ADAPTER=1 +if [ x"${LT_NET8_MINIMAL_API}" == "x" ]; +then + echo SKIPPING net8 minimal api :$LT_NET8_MINIMAL_API +else + echo "RUNNING load test for net8 minimal api" + cd ../../src/NET8MinimalAPI/ + source ./deploy.sh + source ./run-loadtest.sh $TEST_DURATIOMN_SEC $LOG_INTERVALL_MIN $LOG_DELETE +fi diff --git a/src/NET8MinimalAPI/ApiBootstrap/ApiBootstrap.csproj b/src/NET8MinimalAPI/ApiBootstrap/ApiBootstrap.csproj index cf7e8a5..c3879a4 100644 --- a/src/NET8MinimalAPI/ApiBootstrap/ApiBootstrap.csproj +++ b/src/NET8MinimalAPI/ApiBootstrap/ApiBootstrap.csproj @@ -5,7 +5,7 @@ net8.0 true Lambda - bootstrap + bootstrap true false true @@ -15,14 +15,14 @@ - - - - - - - - + + + + + + + + diff --git a/src/NET8MinimalAPI/ApiBootstrap/CloudWatchQueryExecution.cs b/src/NET8MinimalAPI/ApiBootstrap/CloudWatchQueryExecution.cs deleted file mode 100644 index 23b847f..0000000 --- a/src/NET8MinimalAPI/ApiBootstrap/CloudWatchQueryExecution.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Amazon.CloudWatchLogs; -using Amazon.CloudWatchLogs.Model; -using Amazon.Lambda.Core; - -namespace GetProducts; - -public static class CloudWatchQueryExecution -{ - public static async Task>> RunQuery(AmazonCloudWatchLogsClient cloudWatchLogsClient) - { - var logGroupNamePrefix = - $"{Environment.GetEnvironmentVariable("LOG_GROUP_PREFIX")}{Environment.GetEnvironmentVariable("LAMBDA_ARCHITECTURE")}" - .Replace("_", "-"); - - var logGroupList = await cloudWatchLogsClient.DescribeLogGroupsAsync(new DescribeLogGroupsRequest() - { - LogGroupNamePrefix = logGroupNamePrefix, - }); - - var queryRes = await cloudWatchLogsClient.StartQueryAsync(new StartQueryRequest() - { - LogGroupNames = logGroupList.LogGroups.Select(p => p.LogGroupName).ToList(), - QueryString = - "filter @type=\"REPORT\" | fields greatest(@initDuration, 0) + @duration as duration, ispresent(@initDuration) as coldstart | stats count(*) as count, pct(duration, 50) as p50, pct(duration, 90) as p90, pct(duration, 99) as p99, max(duration) as max by coldstart", - StartTime = DateTime.Now.AddMinutes(-20).AsUnixTimestamp(), - EndTime = DateTime.Now.AsUnixTimestamp(), - }); - - QueryStatus currentQueryStatus = QueryStatus.Running; - List> finalResults = new List>(); - - while (currentQueryStatus == QueryStatus.Running || currentQueryStatus == QueryStatus.Scheduled) - { - var queryResults = await cloudWatchLogsClient.GetQueryResultsAsync(new GetQueryResultsRequest() - { - QueryId = queryRes.QueryId - }); - - currentQueryStatus = queryResults.Status; - finalResults = queryResults.Results; - - await Task.Delay(TimeSpan.FromSeconds(5)); - } - - return finalResults; - } -} \ No newline at end of file diff --git a/src/NET8MinimalAPI/ApiBootstrap/DateUtils.cs b/src/NET8MinimalAPI/ApiBootstrap/DateUtils.cs deleted file mode 100644 index 8a572e0..0000000 --- a/src/NET8MinimalAPI/ApiBootstrap/DateUtils.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; - -namespace GetProducts; - -public static class DateUtils -{ - public static long AsUnixTimestamp(this DateTime date) - { - DateTime origin = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); - TimeSpan diff = date.ToUniversalTime() - origin; - return (long)Math.Floor(diff.TotalSeconds); - } -} \ No newline at end of file diff --git a/src/NET8MinimalAPI/ApiBootstrap/Function.cs b/src/NET8MinimalAPI/ApiBootstrap/Function.cs index a4cd497..10a82a7 100644 --- a/src/NET8MinimalAPI/ApiBootstrap/Function.cs +++ b/src/NET8MinimalAPI/ApiBootstrap/Function.cs @@ -4,7 +4,6 @@ using System.Text.Json; using Amazon.CloudWatchLogs; using Amazon.CloudWatchLogs.Model; -using GetProducts; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; @@ -14,6 +13,15 @@ using Shared.Models; var app = Startup.Build(args); +var basePath=Environment.GetEnvironmentVariable("DEMO_BASE_PATH"); +if(String.IsNullOrEmpty(basePath)) + app.Logger.LogInformation($"No BASE PATH specified"); +else +{ + app.Logger.LogInformation($"Using BASE PATH:{basePath}"); + app.UsePathBase(basePath); + app.UseRouting(); +} var dataAccess = app.Services.GetRequiredService(); @@ -37,7 +45,7 @@ { var id = context.Request.RouteValues["id"].ToString(); - app.Logger.LogInformation($"Received request to delete {id}"); + app.Logger.LogInformation($"Received request to delete {id} from the database"); var product = await dataAccess.GetProduct(id); @@ -123,45 +131,4 @@ await context.Response.WriteAsJsonAsync(product); }); -app.MapGet("/test-results", async (HttpContext context) => -{ - var resultRows = 0; - var queryCount = 0; - - List> finalResults = new List>(); - - while (resultRows < 2 || queryCount >= 3) - { - finalResults = await CloudWatchQueryExecution.RunQuery(cloudWatchClient); - - resultRows = finalResults.Count; - queryCount++; - } - - var wrapper = new QueryResultWrapper() - { - LoadTestType = - $"{Environment.GetEnvironmentVariable("LOAD_TEST_TYPE")} ({Environment.GetEnvironmentVariable("LAMBDA_ARCHITECTURE")})", - WarmStart = new QueryResult() - { - Count = finalResults[0][1].Value, - P50 = finalResults[0][2].Value, - P90 = finalResults[0][3].Value, - P99 = finalResults[0][4].Value, - Max = finalResults[0][5].Value, - }, - ColdStart = new QueryResult() - { - Count = finalResults[1][1].Value, - P50 = finalResults[1][2].Value, - P90 = finalResults[1][3].Value, - P99 = finalResults[1][4].Value, - Max = finalResults[1][5].Value, - } - }; - - context.Response.StatusCode = (int) HttpStatusCode.OK; - await context.Response.WriteAsync(wrapper.AsMarkdownTableRow()); -}); - app.Run(); \ No newline at end of file diff --git a/src/NET8MinimalAPI/ApiBootstrap/QueryResult.cs b/src/NET8MinimalAPI/ApiBootstrap/QueryResult.cs deleted file mode 100644 index ff692d2..0000000 --- a/src/NET8MinimalAPI/ApiBootstrap/QueryResult.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace GetProducts; - -public record QueryResultWrapper -{ - public string LoadTestType { get; set; } - - public QueryResult ColdStart { get; set; } - - public QueryResult WarmStart { get; set; } - - public string AsMarkdownTableRow() => $"
Cold Start (ms)Warm Start (ms)
p50p90p99maxp50p90p99max
{LoadTestType}{ColdStart.P50}{ColdStart.P90}{ColdStart.P99}{ColdStart.Max}{WarmStart.P50}{WarmStart.P90}{WarmStart.P99}{WarmStart.Max}
"; -} - -public record QueryResult -{ - public string Count { get; set; } - - public string P50 { get; set; } - - public string P90 { get; set; } - - public string P99 { get; set; } - - public string Max { get; set; } -} \ No newline at end of file diff --git a/src/NET8MinimalAPI/Makefile b/src/NET8MinimalAPI/Makefile index 3f5ee87..4ec8c4a 100644 --- a/src/NET8MinimalAPI/Makefile +++ b/src/NET8MinimalAPI/Makefile @@ -1,3 +1,6 @@ -build-ApiFunction: +build-MinimalApiX86: dotnet clean dotnet publish ApiBootstrap/ApiBootstrap.csproj -c Release -r linux-x64 --self-contained -o $(ARTIFACTS_DIR) +build-MinimalApiArm64: + dotnet clean + dotnet publish ApiBootstrap/ApiBootstrap.csproj -c Release -r linux-arm64 --self-contained -o $(ARTIFACTS_DIR) \ No newline at end of file diff --git a/src/NET8MinimalAPI/Report/load-test-report-arm64.json b/src/NET8MinimalAPI/Report/load-test-report-arm64.json new file mode 100644 index 0000000..93cb117 --- /dev/null +++ b/src/NET8MinimalAPI/Report/load-test-report-arm64.json @@ -0,0 +1,62 @@ +{ + "results": [ + [ + { + "field": "coldstart", + "value": "0" + }, + { + "field": "count", + "value": "2379" + }, + { + "field": "p50", + "value": "9.5259" + }, + { + "field": "p90", + "value": "23.9189" + }, + { + "field": "p99", + "value": "47.3338" + }, + { + "field": "max", + "value": "92.05" + } + ], + [ + { + "field": "coldstart", + "value": "1" + }, + { + "field": "count", + "value": "58" + }, + { + "field": "p50", + "value": "1918.93" + }, + { + "field": "p90", + "value": "1984.77" + }, + { + "field": "p99", + "value": "2030.69" + }, + { + "field": "max", + "value": "2030.69" + } + ] + ], + "statistics": { + "recordsMatched": 2437.0, + "recordsScanned": 22033.0, + "bytesScanned": 16894616.0 + }, + "status": "Complete" +} diff --git a/src/NET8MinimalAPI/Report/load-test-report-arm64.txt b/src/NET8MinimalAPI/Report/load-test-report-arm64.txt new file mode 100644 index 0000000..b87ccb6 --- /dev/null +++ b/src/NET8MinimalAPI/Report/load-test-report-arm64.txt @@ -0,0 +1,18 @@ +Sun Nov 5 00:12:47 CET 2023 +arm64 RESULTS lambda: Net8-MinimalApi-Arm64 +Test duration sec: 60 +Log interval min: 20 +Complete +RESULTS coldstart 0 +RESULTS count 2379 +RESULTS p50 9.5259 +RESULTS p90 23.9189 +RESULTS p99 47.3338 +RESULTS max 92.05 +RESULTS coldstart 1 +RESULTS count 58 +RESULTS p50 1918.93 +RESULTS p90 1984.77 +RESULTS p99 2030.69 +RESULTS max 2030.69 +STATISTICS 16894616.0 2437.0 22033.0 diff --git a/src/NET8MinimalAPI/Report/load-test-report-x86.json b/src/NET8MinimalAPI/Report/load-test-report-x86.json new file mode 100644 index 0000000..e3ec86e --- /dev/null +++ b/src/NET8MinimalAPI/Report/load-test-report-x86.json @@ -0,0 +1,62 @@ +{ + "results": [ + [ + { + "field": "coldstart", + "value": "0" + }, + { + "field": "count", + "value": "1526" + }, + { + "field": "p50", + "value": "8.1277" + }, + { + "field": "p90", + "value": "18.8511" + }, + { + "field": "p99", + "value": "37.9017" + }, + { + "field": "max", + "value": "78.73" + } + ], + [ + { + "field": "coldstart", + "value": "1" + }, + { + "field": "count", + "value": "38" + }, + { + "field": "p50", + "value": "1649.85" + }, + { + "field": "p90", + "value": "1712.45" + }, + { + "field": "p99", + "value": "1760.44" + }, + { + "field": "max", + "value": "1760.44" + } + ] + ], + "statistics": { + "recordsMatched": 1564.0, + "recordsScanned": 14095.0, + "bytesScanned": 10756719.0 + }, + "status": "Complete" +} diff --git a/src/NET8MinimalAPI/Report/load-test-report-x86.txt b/src/NET8MinimalAPI/Report/load-test-report-x86.txt new file mode 100644 index 0000000..20762d7 --- /dev/null +++ b/src/NET8MinimalAPI/Report/load-test-report-x86.txt @@ -0,0 +1,18 @@ +Sun Nov 5 00:10:57 CET 2023 +x86 RESULTS lambda: Net8-MinimalApi-X86 +Test duration sec: 60 +Log interval min: 20 +Complete +RESULTS coldstart 0 +RESULTS count 1526 +RESULTS p50 8.1277 +RESULTS p90 18.8511 +RESULTS p99 37.9017 +RESULTS max 78.73 +RESULTS coldstart 1 +RESULTS count 38 +RESULTS p50 1649.85 +RESULTS p90 1712.45 +RESULTS p99 1760.44 +RESULTS max 1760.44 +STATISTICS 10756719.0 1564.0 14095.0 diff --git a/src/NET8MinimalAPI/Shared/Shared.csproj b/src/NET8MinimalAPI/Shared/Shared.csproj index 34cd820..50accf7 100644 --- a/src/NET8MinimalAPI/Shared/Shared.csproj +++ b/src/NET8MinimalAPI/Shared/Shared.csproj @@ -1,14 +1,14 @@ - net8.0 + net8.0 enable enable - - + + diff --git a/src/NET8MinimalAPI/deploy.sh b/src/NET8MinimalAPI/deploy.sh new file mode 100755 index 0000000..c3c63fa --- /dev/null +++ b/src/NET8MinimalAPI/deploy.sh @@ -0,0 +1,2 @@ +sam build +sam deploy --stack-name dotnet8-minimal-api --resolve-s3 --s3-prefix dotnet8-minimal-api --no-confirm-changeset --no-fail-on-empty-changeset --capabilities CAPABILITY_IAM \ No newline at end of file diff --git a/src/NET8MinimalAPI/omnisharp.json b/src/NET8MinimalAPI/omnisharp.json deleted file mode 100644 index c42f8db..0000000 --- a/src/NET8MinimalAPI/omnisharp.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "fileOptions": { - "excludeSearchPatterns": [ - "**/bin/**/*", - "**/obj/**/*" - ] - }, - "msbuild": { - "Platform": "rhel.7.2-x64" - } -} \ No newline at end of file diff --git a/src/NET8MinimalAPI/run-loadtest.sh b/src/NET8MinimalAPI/run-loadtest.sh new file mode 100755 index 0000000..1a8729d --- /dev/null +++ b/src/NET8MinimalAPI/run-loadtest.sh @@ -0,0 +1,121 @@ +#Arguments: +#$1 - load test duration in seconds +#$2 - log interval to be used in the cloudwatch query in minutes +#$3 - when equal to 1 cloudwatch log group will be deleted to ensure that only logs of the load test will be evaluated for stat + +STACK_NAME=dotnet8-minimal-api +TEST_DURATIOMN_SEC=60 +LOG_INTERVAL_MIN=20 +LOG_DELETE=1 + +COLOR='\033[0;33m' +NO_COLOR='\033[0m' # No Color + +if [ x$1 != "x" ]; +then + TEST_DURATIOMN_SEC=$1 +fi + +if [ x$2 != "x" ]; +then + LOG_INTERVAL_MIN=$2 +fi + +if [ x$3 != "x" ]; +then + LOG_DELETE=$3 +fi + +echo "${COLOR}" +echo -------------------------------------------- +echo DURATION:$TEST_DURATIOMN_SEC +echo LOG INTERVAL:$LOG_INTERVAL_MIN +echo -------------------------------------------- +echo "${NO_COLOR}" + +function RunLoadTest() +{ + #Params: + #$1 - Architecture (x86 or arm64).Used for logging and naming report file + #$2 - Stack output name to get API Url + #$3 - Stack output name to get lambda name + + #get test params from cloud formation output + echo "${COLOR}" + API_URL=$(aws cloudformation describe-stacks --stack-name $STACK_NAME \ + --query "Stacks[0].Outputs[?OutputKey=='$2'].OutputValue" \ + --output text) + echo API URL: $API_URL + + LAMBDA=$(aws cloudformation describe-stacks --stack-name $STACK_NAME \ + --query "Stacks[0].Outputs[?OutputKey=='$3'].OutputValue" \ + --output text) + echo LAMBDA: $LAMBDA + + if [ $LOG_DELETE == "1" ]; + then + echo -------------------------------------------- + echo DELETING CLOUDWATCH LOG GROUP /aws/lambda/$LAMBDA + echo -------------------------------------------- + aws logs delete-log-group --log-group-name /aws/lambda/$LAMBDA + echo --------------------------------------------- + echo Waiting 10 sec. for deletion to complete + echo -------------------------------------------- + sleep 10 + fi + + #run load test with artillery + echo -------------------------------------------- + echo $1 RUNNING LOAD TEST $TEST_DURATIOMN_SEC sec $LAMBDA: $API_URL + echo -------------------------------------------- + echo "${NO_COLOR}" + artillery run \ + --target "$API_URL" \ + --overrides '{"config": { "phases": [{ "duration": '$TEST_DURATIOMN_SEC', "arrivalRate": 100 }] } }' \ + --quiet \ + ../../loadtest/load-test.yml + + echo "${COLOR}" + echo -------------------------------------------- + echo Waiting 10 sec. for logs to consolidate + echo -------------------------------------------- + sleep 10 + + #get stats from cloudwatch + enddate=$(date "+%s") + startdate=$(($enddate-($LOG_INTERVAL_MIN*60))) + echo -------------------------------------------- + echo Log start:$startdate end:$enddate + echo -------------------------------------------- + + QUERY_ID=$(aws logs start-query \ + --log-group-name /aws/lambda/$LAMBDA \ + --start-time $startdate \ + --end-time $enddate \ + --query-string 'filter @type="REPORT" | fields greatest(@initDuration, 0) + @duration as duration, ispresent(@initDuration) as coldstart | stats count(*) as count, pct(duration, 50) as p50, pct(duration, 90) as p90, pct(duration, 99) as p99, max(duration) as max by coldstart' \ + | jq -r '.queryId') + + echo -------------------------------------------- + echo Query started, id: $QUERY_ID + echo -------------------------------------------- + + echo --------------------------------------------- + echo Waiting 10 sec. for cloudwatch query to complete + echo -------------------------------------------- + sleep 10 + + echo -------------------------------------------- + echo RESULTS $LAMBDA + echo -------------------------------------------- + echo "${NO_COLOR}" + date > ./Report/load-test-report-$1.txt + echo $1 RESULTS lambda: $LAMBDA >> ./Report/load-test-report-$1.txt + echo Test duration sec: $TEST_DURATIOMN_SEC >> ./Report/load-test-report-$1.txt + echo Log interval min: $LOG_INTERVAL_MIN >> ./Report/load-test-report-$1.txt + aws logs get-query-results --query-id $QUERY_ID --output text >> ./Report/load-test-report-$1.txt + cat ./Report/load-test-report-$1.txt + aws logs get-query-results --query-id $QUERY_ID --output json >> ./Report/load-test-report-$1.json +} + +RunLoadTest x86 ApiUrlX86 LambdaX86Name +RunLoadTest arm64 ApiUrlArm64 LambdaArm64Name \ No newline at end of file diff --git a/src/NET8MinimalAPI/template.yaml b/src/NET8MinimalAPI/template.yaml index aeb8b06..e5501b8 100644 --- a/src/NET8MinimalAPI/template.yaml +++ b/src/NET8MinimalAPI/template.yaml @@ -1,47 +1,65 @@ AWSTemplateFormatVersion: "2010-09-09" Transform: AWS::Serverless-2016-10-31 +Parameters: + x86FunctionName: + Type: String + Default: Net8-MinimalApi-X86 + arm64FunctionName: + Type: String + Default: Net8-MinimalApi-Arm64 + Globals: Function: MemorySize: 1024 - Architectures: ["x86_64"] Runtime: provided.al2 Timeout: 30 Tracing: Active Environment: Variables: PRODUCT_TABLE_NAME: !Ref Table - LOG_GROUP_PREFIX: !Sub "/aws/lambda/net-8-minimal-" - LOAD_TEST_TYPE: "NET 8 Minimal API" Resources: - ApiFunction: + MinimalApiX86: + Type: AWS::Serverless::Function + Properties: + FunctionName: !Ref x86FunctionName + Architectures: [x86_64] + CodeUri: ./ + Handler: ApiBootstrap + Environment: + Variables: + DEMO_BASE_PATH: /x86 + Events: + Api: + Type: HttpApi + Properties: + Path: /x86/{proxy+} + Method: ANY + Policies: + - DynamoDBCrudPolicy: + TableName: + !Ref Table + MinimalApiArm64: Type: AWS::Serverless::Function Properties: + FunctionName: !Ref arm64FunctionName + Architectures: [arm64] CodeUri: ./ Handler: ApiBootstrap + Environment: + Variables: + DEMO_BASE_PATH: /arm64 Events: Api: Type: HttpApi Properties: - Path: /{proxy+} + Path: /arm64/{proxy+} Method: ANY Policies: - DynamoDBCrudPolicy: TableName: !Ref Table - - Version: "2012-10-17" - Statement: - - Sid: AllowStartQueries - Effect: Allow - Action: - - logs:DescribeLogGroups - - logs:StartQuery - Resource: "*" - - Sid: AllowGetQueryResults - Effect: Allow - Action: logs:GetQueryResults - Resource: "*" Table: Type: AWS::DynamoDB::Table @@ -59,4 +77,16 @@ Resources: Outputs: ApiUrl: Description: "API Gateway endpoint URL" - Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/" \ No newline at end of file + Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/" + LambdaX86Name: + Description: "Lambda X86 Name" + Value: !Ref x86FunctionName + ApiUrlX86: + Description: "X86 API endpoint URL" + Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/x86/" + LambdaArm64Name: + Description: "Lambda Arm64 Name" + Value: !Ref arm64FunctionName + ApiUrlArm64: + Description: "Arm64 GateAPI endpoint URL" + Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/arm64/" \ No newline at end of file From bab15e6be091cdd811e0dea794595d8da0cb92b8 Mon Sep 17 00:00:00 2001 From: Riccardo Venturi Date: Sun, 5 Nov 2023 09:55:35 +0100 Subject: [PATCH 02/32] . --- src/NET8MinimalAPI/run-loadtest.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/NET8MinimalAPI/run-loadtest.sh b/src/NET8MinimalAPI/run-loadtest.sh index 1a8729d..4acbab4 100755 --- a/src/NET8MinimalAPI/run-loadtest.sh +++ b/src/NET8MinimalAPI/run-loadtest.sh @@ -26,6 +26,8 @@ then LOG_DELETE=$3 fi +mkdir Report -p + echo "${COLOR}" echo -------------------------------------------- echo DURATION:$TEST_DURATIOMN_SEC From 57e29764ea99dd2700f06a99d963486c9fb6b8da Mon Sep 17 00:00:00 2001 From: Riccardo Venturi Date: Sun, 5 Nov 2023 10:10:34 +0100 Subject: [PATCH 03/32] fixing load test with codebuild --- loadtest/codebuild/run-all-load-tests.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/loadtest/codebuild/run-all-load-tests.sh b/loadtest/codebuild/run-all-load-tests.sh index 4f30087..91a8975 100755 --- a/loadtest/codebuild/run-all-load-tests.sh +++ b/loadtest/codebuild/run-all-load-tests.sh @@ -38,8 +38,8 @@ else source ./run-loadtest.sh $TEST_DURATIOMN_SEC $LOG_INTERVAL_MIN $LOG_DELETE fi -#export LT_NET6_MINIMAL_API_WEB_ADAPTER=1 -if [ x"${LT_NET8_MINIMAL_API}" == "x" ]; +#export LT_NET8_MINIMAL_API=1 +if [ $LT_NET8_MINIMAL_API != "yes" ]; then echo SKIPPING net8 minimal api :$LT_NET8_MINIMAL_API else From 994cd7f5d3ac89445b4a9614bc7da1dc0753c108 Mon Sep 17 00:00:00 2001 From: Riccardo Venturi Date: Sun, 5 Nov 2023 10:34:22 +0100 Subject: [PATCH 04/32] adding sdk 8.0 for code build compilation --- loadtest/codebuild/load-test-buildspec.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/loadtest/codebuild/load-test-buildspec.yml b/loadtest/codebuild/load-test-buildspec.yml index af4fbcb..1feec27 100644 --- a/loadtest/codebuild/load-test-buildspec.yml +++ b/loadtest/codebuild/load-test-buildspec.yml @@ -3,6 +3,7 @@ version: 0.2 phases: install: commands: + - curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin --channel 8.0 - npm i artillery -g build: commands: From a97f739dac2b42cdf409dd57dbc51c43a854e39b Mon Sep 17 00:00:00 2001 From: Riccardo Venturi Date: Sun, 5 Nov 2023 11:04:41 +0100 Subject: [PATCH 05/32] . --- loadtest/codebuild/run-all-load-tests.sh | 2 +- src/NET8MinimalAPI/run-loadtest.sh | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/loadtest/codebuild/run-all-load-tests.sh b/loadtest/codebuild/run-all-load-tests.sh index 3164952..525d3d6 100755 --- a/loadtest/codebuild/run-all-load-tests.sh +++ b/loadtest/codebuild/run-all-load-tests.sh @@ -44,7 +44,7 @@ else fi #export LT_NET8_MINIMAL_API=1 -if [ $LT_NET8_MINIMAL_API != "yes" ]; +if [ "$LT_NET8_MINIMAL_API" != yes ]; then echo SKIPPING net8 minimal api :$LT_NET8_MINIMAL_API else diff --git a/src/NET8MinimalAPI/run-loadtest.sh b/src/NET8MinimalAPI/run-loadtest.sh index 4acbab4..322e04b 100755 --- a/src/NET8MinimalAPI/run-loadtest.sh +++ b/src/NET8MinimalAPI/run-loadtest.sh @@ -11,30 +11,31 @@ LOG_DELETE=1 COLOR='\033[0;33m' NO_COLOR='\033[0m' # No Color -if [ x$1 != "x" ]; +if [ "x$1" != x ]; then TEST_DURATIOMN_SEC=$1 fi -if [ x$2 != "x" ]; +if [ "x$2" != x ]; then LOG_INTERVAL_MIN=$2 fi -if [ x$3 != "x" ]; +if [ "x$3" != x ]; then LOG_DELETE=$3 fi -mkdir Report -p - echo "${COLOR}" echo -------------------------------------------- echo DURATION:$TEST_DURATIOMN_SEC echo LOG INTERVAL:$LOG_INTERVAL_MIN +echo LOG_DELETE: $LOG_DELETE echo -------------------------------------------- echo "${NO_COLOR}" +mkdir Report -p + function RunLoadTest() { #Params: From 74b5450afd287a97f6d7831a1ba4c37b69fddd3b Mon Sep 17 00:00:00 2001 From: Riccardo Venturi Date: Sun, 5 Nov 2023 16:26:33 +0100 Subject: [PATCH 06/32] fixing typo --- loadtest/codebuild/run-all-load-tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/loadtest/codebuild/run-all-load-tests.sh b/loadtest/codebuild/run-all-load-tests.sh index 525d3d6..f49caa4 100755 --- a/loadtest/codebuild/run-all-load-tests.sh +++ b/loadtest/codebuild/run-all-load-tests.sh @@ -51,5 +51,5 @@ else echo "RUNNING load test for net8 minimal api" cd ../../src/NET8MinimalAPI/ source ./deploy.sh - source ./run-loadtest.sh $TEST_DURATIOMN_SEC $LOG_INTERVALL_MIN $LOG_DELETE + source ./run-loadtest.sh $TEST_DURATIOMN_SEC $LOG_INTERVAL_MIN $LOG_DELETE fi From a8bd2d6208e422d3f606d855c95f0745b9db98d4 Mon Sep 17 00:00:00 2001 From: Riccardo Venturi Date: Sun, 5 Nov 2023 17:03:57 +0100 Subject: [PATCH 07/32] . --- loadtest/codebuild/run-all-load-tests.sh | 2 +- src/NET8MinimalAPI/deploy.sh | 41 ++++++++++++++++++++++++ src/NET8MinimalAPI/run-loadtest.sh | 4 +-- 3 files changed, 44 insertions(+), 3 deletions(-) diff --git a/loadtest/codebuild/run-all-load-tests.sh b/loadtest/codebuild/run-all-load-tests.sh index 6717046..f64b9cf 100755 --- a/loadtest/codebuild/run-all-load-tests.sh +++ b/loadtest/codebuild/run-all-load-tests.sh @@ -57,6 +57,6 @@ then else echo "RUNNING load test for net8 minimal api" cd ../../src/NET8MinimalAPI/ - source ./deploy.sh + source ./deploy.sh $DELETE_STACK source ./run-loadtest.sh $TEST_DURATIOMN_SEC $LOG_INTERVAL_MIN $LOG_DELETE fi diff --git a/src/NET8MinimalAPI/deploy.sh b/src/NET8MinimalAPI/deploy.sh index c3c63fa..80e0ccd 100755 --- a/src/NET8MinimalAPI/deploy.sh +++ b/src/NET8MinimalAPI/deploy.sh @@ -1,2 +1,43 @@ +#Arguments: +#$1 - delete stack formation to ensure that all test have same conditon and favour similar ammount of cold start events + +STACK_NAME=dotnet8-minimal-api +DELETE_STACK=yes + +COLOR='\033[0;33m' +NO_COLOR='\033[0m' # No Color + +if [ "x$4" != x ]; +then + DELETE_STACK=$1 +fi + +echo "${COLOR}" +echo -------------------------------------------- +echo DELETE_STACK: $DELETE_STACK +echo -------------------------------------------- +echo "${NO_COLOR}" + +if [ $DELETE_STACK == "yes" ]; +then + echo "${COLOR}" + echo -------------------------------------------- + echo DELETING STACK $STACK_NAME + echo -------------------------------------------- + echo "${NO_COLOR}" + aws cloudformation delete-stack --stack-name $STACK_NAME + echo "${COLOR}" + echo --------------------------------------------- + echo Waiting stack to be deleted + echo -------------------------------------------- + echo "${NO_COLOR}" + aws cloudformation wait stack-delete-complete --stack-name $STACK_NAME + echo "${COLOR}" + echo --------------------------------------------- + echo Stack deleted + echo -------------------------------------------- + echo "${NO_COLOR}" +fi + sam build sam deploy --stack-name dotnet8-minimal-api --resolve-s3 --s3-prefix dotnet8-minimal-api --no-confirm-changeset --no-fail-on-empty-changeset --capabilities CAPABILITY_IAM \ No newline at end of file diff --git a/src/NET8MinimalAPI/run-loadtest.sh b/src/NET8MinimalAPI/run-loadtest.sh index 322e04b..d413e3b 100755 --- a/src/NET8MinimalAPI/run-loadtest.sh +++ b/src/NET8MinimalAPI/run-loadtest.sh @@ -6,7 +6,7 @@ STACK_NAME=dotnet8-minimal-api TEST_DURATIOMN_SEC=60 LOG_INTERVAL_MIN=20 -LOG_DELETE=1 +LOG_DELETE=yes COLOR='\033[0;33m' NO_COLOR='\033[0m' # No Color @@ -55,7 +55,7 @@ function RunLoadTest() --output text) echo LAMBDA: $LAMBDA - if [ $LOG_DELETE == "1" ]; + if [ $LOG_DELETE == "yes" ]; then echo -------------------------------------------- echo DELETING CLOUDWATCH LOG GROUP /aws/lambda/$LAMBDA From 87cd54e9763a3dcd09abf23e8356c04c64c349c4 Mon Sep 17 00:00:00 2001 From: Riccardo Venturi Date: Sun, 5 Nov 2023 19:22:13 +0100 Subject: [PATCH 08/32] . --- .../Report/load-test-report-arm64.json | 62 ------------------- .../Report/load-test-report-arm64.txt | 18 ------ .../Report/load-test-report-x86.json | 62 ------------------- .../Report/load-test-report-x86.txt | 18 ------ src/NET8MinimalAPI/run-loadtest.sh | 2 +- 5 files changed, 1 insertion(+), 161 deletions(-) delete mode 100644 src/NET8MinimalAPI/Report/load-test-report-arm64.json delete mode 100644 src/NET8MinimalAPI/Report/load-test-report-arm64.txt delete mode 100644 src/NET8MinimalAPI/Report/load-test-report-x86.json delete mode 100644 src/NET8MinimalAPI/Report/load-test-report-x86.txt diff --git a/src/NET8MinimalAPI/Report/load-test-report-arm64.json b/src/NET8MinimalAPI/Report/load-test-report-arm64.json deleted file mode 100644 index 93cb117..0000000 --- a/src/NET8MinimalAPI/Report/load-test-report-arm64.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "results": [ - [ - { - "field": "coldstart", - "value": "0" - }, - { - "field": "count", - "value": "2379" - }, - { - "field": "p50", - "value": "9.5259" - }, - { - "field": "p90", - "value": "23.9189" - }, - { - "field": "p99", - "value": "47.3338" - }, - { - "field": "max", - "value": "92.05" - } - ], - [ - { - "field": "coldstart", - "value": "1" - }, - { - "field": "count", - "value": "58" - }, - { - "field": "p50", - "value": "1918.93" - }, - { - "field": "p90", - "value": "1984.77" - }, - { - "field": "p99", - "value": "2030.69" - }, - { - "field": "max", - "value": "2030.69" - } - ] - ], - "statistics": { - "recordsMatched": 2437.0, - "recordsScanned": 22033.0, - "bytesScanned": 16894616.0 - }, - "status": "Complete" -} diff --git a/src/NET8MinimalAPI/Report/load-test-report-arm64.txt b/src/NET8MinimalAPI/Report/load-test-report-arm64.txt deleted file mode 100644 index b87ccb6..0000000 --- a/src/NET8MinimalAPI/Report/load-test-report-arm64.txt +++ /dev/null @@ -1,18 +0,0 @@ -Sun Nov 5 00:12:47 CET 2023 -arm64 RESULTS lambda: Net8-MinimalApi-Arm64 -Test duration sec: 60 -Log interval min: 20 -Complete -RESULTS coldstart 0 -RESULTS count 2379 -RESULTS p50 9.5259 -RESULTS p90 23.9189 -RESULTS p99 47.3338 -RESULTS max 92.05 -RESULTS coldstart 1 -RESULTS count 58 -RESULTS p50 1918.93 -RESULTS p90 1984.77 -RESULTS p99 2030.69 -RESULTS max 2030.69 -STATISTICS 16894616.0 2437.0 22033.0 diff --git a/src/NET8MinimalAPI/Report/load-test-report-x86.json b/src/NET8MinimalAPI/Report/load-test-report-x86.json deleted file mode 100644 index e3ec86e..0000000 --- a/src/NET8MinimalAPI/Report/load-test-report-x86.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "results": [ - [ - { - "field": "coldstart", - "value": "0" - }, - { - "field": "count", - "value": "1526" - }, - { - "field": "p50", - "value": "8.1277" - }, - { - "field": "p90", - "value": "18.8511" - }, - { - "field": "p99", - "value": "37.9017" - }, - { - "field": "max", - "value": "78.73" - } - ], - [ - { - "field": "coldstart", - "value": "1" - }, - { - "field": "count", - "value": "38" - }, - { - "field": "p50", - "value": "1649.85" - }, - { - "field": "p90", - "value": "1712.45" - }, - { - "field": "p99", - "value": "1760.44" - }, - { - "field": "max", - "value": "1760.44" - } - ] - ], - "statistics": { - "recordsMatched": 1564.0, - "recordsScanned": 14095.0, - "bytesScanned": 10756719.0 - }, - "status": "Complete" -} diff --git a/src/NET8MinimalAPI/Report/load-test-report-x86.txt b/src/NET8MinimalAPI/Report/load-test-report-x86.txt deleted file mode 100644 index 20762d7..0000000 --- a/src/NET8MinimalAPI/Report/load-test-report-x86.txt +++ /dev/null @@ -1,18 +0,0 @@ -Sun Nov 5 00:10:57 CET 2023 -x86 RESULTS lambda: Net8-MinimalApi-X86 -Test duration sec: 60 -Log interval min: 20 -Complete -RESULTS coldstart 0 -RESULTS count 1526 -RESULTS p50 8.1277 -RESULTS p90 18.8511 -RESULTS p99 37.9017 -RESULTS max 78.73 -RESULTS coldstart 1 -RESULTS count 38 -RESULTS p50 1649.85 -RESULTS p90 1712.45 -RESULTS p99 1760.44 -RESULTS max 1760.44 -STATISTICS 10756719.0 1564.0 14095.0 diff --git a/src/NET8MinimalAPI/run-loadtest.sh b/src/NET8MinimalAPI/run-loadtest.sh index d413e3b..41344f0 100755 --- a/src/NET8MinimalAPI/run-loadtest.sh +++ b/src/NET8MinimalAPI/run-loadtest.sh @@ -34,7 +34,7 @@ echo LOG_DELETE: $LOG_DELETE echo -------------------------------------------- echo "${NO_COLOR}" -mkdir Report -p +mkdir -p Report function RunLoadTest() { From 2d9db902e2f3dd52e24d92fca8cd66a9dfef381b Mon Sep 17 00:00:00 2001 From: Riccardo Venturi Date: Sun, 5 Nov 2023 21:45:21 +0100 Subject: [PATCH 09/32] adding net8 minimila api stats after 10 min loadtest --- README.md | 26 ++++++++ .../Report/load-test-report-arm64.json | 62 +++++++++++++++++++ .../Report/load-test-report-arm64.txt | 18 ++++++ .../Report/load-test-report-x86.json | 62 +++++++++++++++++++ .../Report/load-test-report-x86.txt | 18 ++++++ 5 files changed, 186 insertions(+) create mode 100644 src/NET8MinimalAPI/Report/load-test-report-arm64.json create mode 100644 src/NET8MinimalAPI/Report/load-test-report-arm64.txt create mode 100644 src/NET8MinimalAPI/Report/load-test-report-x86.json create mode 100644 src/NET8MinimalAPI/Report/load-test-report-x86.txt diff --git a/README.md b/README.md index b3b43dd..ac3bbbe 100755 --- a/README.md +++ b/README.md @@ -557,6 +557,32 @@ The .NET 8 benchmarks include the number of cold and warm starts, alongside the 23.84 120.45 + + Minimal API on X86 + 195 + 1672.85 + 1737.61 + 1833.97 + 1889.15 + 136,006 + 6.30 + 10.98 + 26.72 + 124.67 + + + Minimal API on ARM64 + 242 + 1972.79 + 2049.16 + 2107.32 + 2124.55 + 136,816 + 6.01 + 9.37 + 24.69 + 331.6 + *The .NET 8 native AOT examples need to be compiled on Amazon Linux 2, this is a temporary solution as a pre-cursor to a SAM build image being available for .NET 8. diff --git a/src/NET8MinimalAPI/Report/load-test-report-arm64.json b/src/NET8MinimalAPI/Report/load-test-report-arm64.json new file mode 100644 index 0000000..8a37bf2 --- /dev/null +++ b/src/NET8MinimalAPI/Report/load-test-report-arm64.json @@ -0,0 +1,62 @@ +{ + "results": [ + [ + { + "field": "coldstart", + "value": "0" + }, + { + "field": "count", + "value": "136816" + }, + { + "field": "p50", + "value": "6.0116" + }, + { + "field": "p90", + "value": "9.3759" + }, + { + "field": "p99", + "value": "24.6905" + }, + { + "field": "max", + "value": "331.6" + } + ], + [ + { + "field": "coldstart", + "value": "1" + }, + { + "field": "count", + "value": "242" + }, + { + "field": "p50", + "value": "1972.7958" + }, + { + "field": "p90", + "value": "2049.1658" + }, + { + "field": "p99", + "value": "2107.3237" + }, + { + "field": "max", + "value": "2124.55" + } + ] + ], + "statistics": { + "recordsMatched": 137058.0, + "recordsScanned": 1233898.0, + "bytesScanned": 945917431.0 + }, + "status": "Complete" +} diff --git a/src/NET8MinimalAPI/Report/load-test-report-arm64.txt b/src/NET8MinimalAPI/Report/load-test-report-arm64.txt new file mode 100644 index 0000000..e98f8f2 --- /dev/null +++ b/src/NET8MinimalAPI/Report/load-test-report-arm64.txt @@ -0,0 +1,18 @@ +Sun Nov 5 19:38:43 UTC 2023 +arm64 RESULTS lambda: Net8-MinimalApi-Arm64 +Test duration sec: 600 +Log interval min: 20 +Complete +RESULTS coldstart 0 +RESULTS count 136816 +RESULTS p50 6.0116 +RESULTS p90 9.3759 +RESULTS p99 24.6905 +RESULTS max 331.6 +RESULTS coldstart 1 +RESULTS count 242 +RESULTS p50 1972.7958 +RESULTS p90 2049.1658 +RESULTS p99 2107.3237 +RESULTS max 2124.55 +STATISTICS 945917431.0 137058.0 1233898.0 diff --git a/src/NET8MinimalAPI/Report/load-test-report-x86.json b/src/NET8MinimalAPI/Report/load-test-report-x86.json new file mode 100644 index 0000000..6b47edc --- /dev/null +++ b/src/NET8MinimalAPI/Report/load-test-report-x86.json @@ -0,0 +1,62 @@ +{ + "results": [ + [ + { + "field": "coldstart", + "value": "0" + }, + { + "field": "count", + "value": "136006" + }, + { + "field": "p50", + "value": "6.3048" + }, + { + "field": "p90", + "value": "10.9888" + }, + { + "field": "p99", + "value": "26.7299" + }, + { + "field": "max", + "value": "124.67" + } + ], + [ + { + "field": "coldstart", + "value": "1" + }, + { + "field": "count", + "value": "195" + }, + { + "field": "p50", + "value": "1672.8591" + }, + { + "field": "p90", + "value": "1737.618" + }, + { + "field": "p99", + "value": "1833.9796" + }, + { + "field": "max", + "value": "1889.15" + } + ] + ], + "statistics": { + "recordsMatched": 136201.0, + "recordsScanned": 1226041.0, + "bytesScanned": 935106143.0 + }, + "status": "Complete" +} diff --git a/src/NET8MinimalAPI/Report/load-test-report-x86.txt b/src/NET8MinimalAPI/Report/load-test-report-x86.txt new file mode 100644 index 0000000..f57a52f --- /dev/null +++ b/src/NET8MinimalAPI/Report/load-test-report-x86.txt @@ -0,0 +1,18 @@ +Sun Nov 5 19:28:01 UTC 2023 +x86 RESULTS lambda: Net8-MinimalApi-X86 +Test duration sec: 600 +Log interval min: 20 +Complete +RESULTS coldstart 0 +RESULTS count 136006 +RESULTS p50 6.3048 +RESULTS p90 10.9888 +RESULTS p99 26.7299 +RESULTS max 124.67 +RESULTS coldstart 1 +RESULTS count 195 +RESULTS p50 1672.8591 +RESULTS p90 1737.618 +RESULTS p99 1833.9796 +RESULTS max 1889.15 +STATISTICS 935106143.0 136201.0 1226041.0 From 130cdb5409c760b3da7f932be825532fbc3e5c7d Mon Sep 17 00:00:00 2001 From: Riccardo Venturi Date: Fri, 10 Nov 2023 21:34:05 +0100 Subject: [PATCH 10/32] updating NET8 project with load test script --- loadtest/codebuild/load-test.yml | 32 +++ src/NET8/DeleteProduct/DeleteProduct.csproj | 12 +- src/NET8/GenerateLoadTestResults/DateUtils.cs | 13 - src/NET8/GenerateLoadTestResults/Function.cs | 146 ---------- .../GenerateLoadTestResults.csproj | 17 -- .../GenerateLoadTestResults/QueryResult.cs | 25 -- src/NET8/GetProduct/GetProduct.csproj | 12 +- src/NET8/GetProducts/GetProducts.csproj | 12 +- src/NET8/Makefile | 26 +- src/NET8/PutProduct/PutProduct.csproj | 12 +- src/NET8/Report/load-test-report-.json | 0 src/NET8/Report/load-test-report-.txt | 2 + src/NET8/Report/load-test-report-arm64.json | 186 +++++++++++++ src/NET8/Report/load-test-report-arm64.txt | 18 ++ src/NET8/Report/load-test-report-x86.json | 260 ++++++++++++++++++ src/NET8/Report/load-test-report-x86.txt | 18 ++ src/NET8/Shared/Shared.csproj | 8 +- src/NET8/deploy.sh | 43 +++ src/NET8/run-loadtest.sh | 153 +++++++++++ src/NET8/template.yaml | 165 +++++++++-- 20 files changed, 893 insertions(+), 267 deletions(-) create mode 100755 loadtest/codebuild/load-test.yml delete mode 100644 src/NET8/GenerateLoadTestResults/DateUtils.cs delete mode 100644 src/NET8/GenerateLoadTestResults/Function.cs delete mode 100644 src/NET8/GenerateLoadTestResults/GenerateLoadTestResults.csproj delete mode 100644 src/NET8/GenerateLoadTestResults/QueryResult.cs create mode 100644 src/NET8/Report/load-test-report-.json create mode 100644 src/NET8/Report/load-test-report-.txt create mode 100644 src/NET8/Report/load-test-report-arm64.json create mode 100644 src/NET8/Report/load-test-report-arm64.txt create mode 100644 src/NET8/Report/load-test-report-x86.json create mode 100644 src/NET8/Report/load-test-report-x86.txt create mode 100755 src/NET8/deploy.sh create mode 100755 src/NET8/run-loadtest.sh diff --git a/loadtest/codebuild/load-test.yml b/loadtest/codebuild/load-test.yml new file mode 100755 index 0000000..75c7beb --- /dev/null +++ b/loadtest/codebuild/load-test.yml @@ -0,0 +1,32 @@ +config: + target: "{{ $processEnvironment.API_URL }}" + http: + timeout : 60 + processor: "../generator.js" + phases: + - duration: 60 + arrivalRate: 100 + +scenarios: + - name: "Generate products" + weight: 8 + flow: + - function: "generateProduct" + - put: + url: "/{{ Id }}" + headers: + Content-Type: "application/json" + json: + Id: "{{ Id }}" + Name: "{{ Name }}" + Price: "{{ Price }}" + - get: + url: "/{{ Id }}" + - think: 3 + - delete: + url: "/{{ Id }}" + - name: "Get products" + weight: 2 + flow: + - get: + url: "{{ $processEnvironment.API_URL }}" \ No newline at end of file diff --git a/src/NET8/DeleteProduct/DeleteProduct.csproj b/src/NET8/DeleteProduct/DeleteProduct.csproj index 825e086..a386b03 100644 --- a/src/NET8/DeleteProduct/DeleteProduct.csproj +++ b/src/NET8/DeleteProduct/DeleteProduct.csproj @@ -15,12 +15,12 @@ - - - - - - + + + + + + diff --git a/src/NET8/GenerateLoadTestResults/DateUtils.cs b/src/NET8/GenerateLoadTestResults/DateUtils.cs deleted file mode 100644 index c37c43d..0000000 --- a/src/NET8/GenerateLoadTestResults/DateUtils.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; - -namespace GenerateLoadTestResults; - -public static class DateUtils -{ - public static long AsUnixTimestamp(this DateTime date) - { - DateTime origin = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); - TimeSpan diff = date.ToUniversalTime() - origin; - return (long)Math.Floor(diff.TotalSeconds); - } -} \ No newline at end of file diff --git a/src/NET8/GenerateLoadTestResults/Function.cs b/src/NET8/GenerateLoadTestResults/Function.cs deleted file mode 100644 index 21a57a8..0000000 --- a/src/NET8/GenerateLoadTestResults/Function.cs +++ /dev/null @@ -1,146 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Text.Json; -using System.Threading.Tasks; -using Amazon.CloudWatchLogs; -using Amazon.CloudWatchLogs.Model; -using Amazon.Lambda.APIGatewayEvents; -using Amazon.Lambda.Core; - -[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] - -namespace GenerateLoadTestResults -{ - public class Function - { - private AmazonCloudWatchLogsClient _cloudWatchLogsClient; - - public Function() - { - this._cloudWatchLogsClient = new AmazonCloudWatchLogsClient(); - } - - public async Task FunctionHandler( - APIGatewayHttpApiV2ProxyRequest apigProxyEvent, - ILambdaContext context) - { - if (!apigProxyEvent.RequestContext.Http.Method.Equals(HttpMethod.Get.Method)) - { - return new APIGatewayHttpApiV2ProxyResponse - { - Body = "Only GET allowed", - StatusCode = (int) HttpStatusCode.MethodNotAllowed, - }; - } - - try - { - var resultRows = 0; - var queryCount = 0; - - List> finalResults = new List>(); - - while (resultRows < 2 || queryCount >= 3) - { - finalResults = await runQuery(context); - - resultRows = finalResults.Count; - queryCount++; - } - - var wrapper = new QueryResultWrapper() - { - LoadTestType = - $"{Environment.GetEnvironmentVariable("LOAD_TEST_TYPE")} ({Environment.GetEnvironmentVariable("LAMBDA_ARCHITECTURE")})", - WarmStart = new QueryResult() - { - Count = finalResults[0][1].Value, - P50 = finalResults[0][2].Value, - P90 = finalResults[0][3].Value, - P99 = finalResults[0][4].Value, - Max = finalResults[0][5].Value, - }, - ColdStart = new QueryResult() - { - Count = finalResults[1][1].Value, - P50 = finalResults[1][2].Value, - P90 = finalResults[1][3].Value, - P99 = finalResults[1][4].Value, - Max = finalResults[1][5].Value, - } - }; - - return new APIGatewayHttpApiV2ProxyResponse - { - StatusCode = (int) HttpStatusCode.OK, - Body = wrapper.AsMarkdownTableRow(), - Headers = new Dictionary {{"Content-Type", "text/html"}} - }; - } - catch (Exception e) - { - context.Logger.LogLine($"Error retrieving results {e.Message} {e.StackTrace}"); - - return new APIGatewayHttpApiV2ProxyResponse - { - Body = "Not Found", - StatusCode = (int) HttpStatusCode.InternalServerError, - }; - } - } - - private async Task>> runQuery(ILambdaContext context) - { - var logGroupNamePrefix = - $"{Environment.GetEnvironmentVariable("LOG_GROUP_PREFIX")}{Environment.GetEnvironmentVariable("LAMBDA_ARCHITECTURE")}" - .Replace("_", "-"); - - context.Logger.LogLine($"Retrieving log groups with prefix {logGroupNamePrefix}"); - - var logGroupList = await _cloudWatchLogsClient.DescribeLogGroupsAsync(new DescribeLogGroupsRequest() - { - LogGroupNamePrefix = logGroupNamePrefix, - }); - - context.Logger.LogLine($"Found {logGroupList.LogGroups.Count} log group(s)"); - - var queryRes = await _cloudWatchLogsClient.StartQueryAsync(new StartQueryRequest() - { - LogGroupNames = logGroupList.LogGroups.Select(p => p.LogGroupName).ToList(), - QueryString = - "filter @type=\"REPORT\" | fields greatest(@initDuration, 0) + @duration as duration, ispresent(@initDuration) as coldstart | stats count(*) as count, pct(duration, 50) as p50, pct(duration, 90) as p90, pct(duration, 99) as p99, max(duration) as max by coldstart", - StartTime = DateTime.Now.AddMinutes(-20).AsUnixTimestamp(), - EndTime = DateTime.Now.AsUnixTimestamp(), - }); - - context.Logger.LogLine($"Running query, query id is {queryRes.QueryId}"); - - QueryStatus currentQueryStatus = QueryStatus.Running; - List> finalResults = new List>(); - - while (currentQueryStatus == QueryStatus.Running || currentQueryStatus == QueryStatus.Scheduled) - { - context.Logger.LogLine("Retrieving query results"); - - var queryResults = await _cloudWatchLogsClient.GetQueryResultsAsync(new GetQueryResultsRequest() - { - QueryId = queryRes.QueryId - }); - - context.Logger.LogLine($"Query result status is {queryResults.Status}"); - - currentQueryStatus = queryResults.Status; - finalResults = queryResults.Results; - - await Task.Delay(TimeSpan.FromSeconds(5)); - } - - context.Logger.LogLine($"Final results: {finalResults.Count} row(s)"); - - return finalResults; - } - } -} \ No newline at end of file diff --git a/src/NET8/GenerateLoadTestResults/GenerateLoadTestResults.csproj b/src/NET8/GenerateLoadTestResults/GenerateLoadTestResults.csproj deleted file mode 100644 index 4a8d708..0000000 --- a/src/NET8/GenerateLoadTestResults/GenerateLoadTestResults.csproj +++ /dev/null @@ -1,17 +0,0 @@ - - - - net6.0 - true - true - - - - - - - - - - - diff --git a/src/NET8/GenerateLoadTestResults/QueryResult.cs b/src/NET8/GenerateLoadTestResults/QueryResult.cs deleted file mode 100644 index 69b5a31..0000000 --- a/src/NET8/GenerateLoadTestResults/QueryResult.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace GenerateLoadTestResults; - -public record QueryResultWrapper -{ - public string LoadTestType { get; set; } - - public QueryResult ColdStart { get; set; } - - public QueryResult WarmStart { get; set; } - - public string AsMarkdownTableRow() => $"
Cold Start (ms)Warm Start (ms)
p50p90p99maxp50p90p99max
{LoadTestType}{ColdStart.P50}{ColdStart.P90}{ColdStart.P99}{ColdStart.Max}{WarmStart.P50}{WarmStart.P90}{WarmStart.P99}{WarmStart.Max}
"; -} - -public record QueryResult -{ - public string Count { get; set; } - - public string P50 { get; set; } - - public string P90 { get; set; } - - public string P99 { get; set; } - - public string Max { get; set; } -} \ No newline at end of file diff --git a/src/NET8/GetProduct/GetProduct.csproj b/src/NET8/GetProduct/GetProduct.csproj index 9757d2d..8253d9e 100644 --- a/src/NET8/GetProduct/GetProduct.csproj +++ b/src/NET8/GetProduct/GetProduct.csproj @@ -13,12 +13,12 @@ linux-x64 - - - - - - + + + + + + diff --git a/src/NET8/GetProducts/GetProducts.csproj b/src/NET8/GetProducts/GetProducts.csproj index 55460a1..982b1ac 100644 --- a/src/NET8/GetProducts/GetProducts.csproj +++ b/src/NET8/GetProducts/GetProducts.csproj @@ -14,12 +14,12 @@ linux-x64 - - - - - - + + + + + + diff --git a/src/NET8/Makefile b/src/NET8/Makefile index 2aadd31..4850513 100644 --- a/src/NET8/Makefile +++ b/src/NET8/Makefile @@ -1,15 +1,23 @@ -build-GetProductFunction: - dotnet clean +build-GetProductFunctionX86: dotnet publish GetProduct/GetProduct.csproj -c Release -r linux-x64 --self-contained -o $(ARTIFACTS_DIR) -build-GetProductsFunction: - dotnet clean +build-GetProductsFunctionX86: dotnet publish GetProducts/GetProducts.csproj -c Release -r linux-x64 --self-contained -o $(ARTIFACTS_DIR) -build-PutProductFunction: - dotnet clean +build-PutProductFunctionX86: dotnet publish PutProduct/PutProduct.csproj -c Release -r linux-x64 --self-contained -o $(ARTIFACTS_DIR) -build-DeleteProductFunction: - dotnet clean - dotnet publish DeleteProduct/DeleteProduct.csproj -c Release -r linux-x64 --self-contained -o $(ARTIFACTS_DIR) \ No newline at end of file +build-DeleteProductFunctionX86: + dotnet publish DeleteProduct/DeleteProduct.csproj -c Release -r linux-x64 --self-contained -o $(ARTIFACTS_DIR) + +build-GetProductFunctionArm64: + dotnet publish GetProduct/GetProduct.csproj -c Release -r linux-arm64 --self-contained -o $(ARTIFACTS_DIR) + +build-GetProductsFunctionArm64: + dotnet publish GetProducts/GetProducts.csproj -c Release -r linux-arm64 --self-contained -o $(ARTIFACTS_DIR) + +build-PutProductFunctionArm64: + dotnet publish PutProduct/PutProduct.csproj -c Release -r linux-arm64 --self-contained -o $(ARTIFACTS_DIR) + +build-DeleteProductFunctionArm64: + dotnet publish DeleteProduct/DeleteProduct.csproj -c Release -r linux-arm64 --self-contained -o $(ARTIFACTS_DIR) \ No newline at end of file diff --git a/src/NET8/PutProduct/PutProduct.csproj b/src/NET8/PutProduct/PutProduct.csproj index 9fae9e3..3f79dae 100644 --- a/src/NET8/PutProduct/PutProduct.csproj +++ b/src/NET8/PutProduct/PutProduct.csproj @@ -15,12 +15,12 @@ - - - - - - + + + + + + diff --git a/src/NET8/Report/load-test-report-.json b/src/NET8/Report/load-test-report-.json new file mode 100644 index 0000000..e69de29 diff --git a/src/NET8/Report/load-test-report-.txt b/src/NET8/Report/load-test-report-.txt new file mode 100644 index 0000000..183df41 --- /dev/null +++ b/src/NET8/Report/load-test-report-.txt @@ -0,0 +1,2 @@ +Test duration sec: 60 +Log interval min: 20 diff --git a/src/NET8/Report/load-test-report-arm64.json b/src/NET8/Report/load-test-report-arm64.json new file mode 100644 index 0000000..a6c8a0a --- /dev/null +++ b/src/NET8/Report/load-test-report-arm64.json @@ -0,0 +1,186 @@ +{ + "results": [ + [ + { + "field": "coldstart", + "value": "0" + }, + { + "field": "count", + "value": "806" + }, + { + "field": "p50", + "value": "9.5341" + }, + { + "field": "p90", + "value": "22.7017" + }, + { + "field": "p99", + "value": "42.4839" + }, + { + "field": "max", + "value": "107.71" + } + ], + [ + { + "field": "coldstart", + "value": "1" + }, + { + "field": "count", + "value": "146" + }, + { + "field": "p50", + "value": "1725.5032" + }, + { + "field": "p90", + "value": "1801.2795" + }, + { + "field": "p99", + "value": "1865.4079" + }, + { + "field": "max", + "value": "1961.07" + } + ] + ], + "statistics": { + "recordsMatched": 952.0, + "recordsScanned": 4680.0, + "bytesScanned": 1408784.0 + }, + "status": "Complete" +} +{ + "results": [ + [ + { + "field": "coldstart", + "value": "0" + }, + { + "field": "count", + "value": "1968" + }, + { + "field": "p50", + "value": "9.7659" + }, + { + "field": "p90", + "value": "22.5459" + }, + { + "field": "p99", + "value": "38.4524" + }, + { + "field": "max", + "value": "113.03" + } + ], + [ + { + "field": "coldstart", + "value": "1" + }, + { + "field": "count", + "value": "334" + }, + { + "field": "p50", + "value": "1725.5032" + }, + { + "field": "p90", + "value": "1792.3" + }, + { + "field": "p99", + "value": "1835.8135" + }, + { + "field": "max", + "value": "1915.31" + } + ] + ], + "statistics": { + "recordsMatched": 2302.0, + "recordsScanned": 9347.0, + "bytesScanned": 3833050.0 + }, + "status": "Complete" +} +{ + "results": [ + [ + { + "field": "coldstart", + "value": "0" + }, + { + "field": "count", + "value": "1736" + }, + { + "field": "p50", + "value": "9.0179" + }, + { + "field": "p90", + "value": "21.3228" + }, + { + "field": "p99", + "value": "36.6573" + }, + { + "field": "max", + "value": "57.18" + } + ], + [ + { + "field": "coldstart", + "value": "1" + }, + { + "field": "count", + "value": "123" + }, + { + "field": "p50", + "value": "1703.2279" + }, + { + "field": "p90", + "value": "1756.8276" + }, + { + "field": "p99", + "value": "1812.1142" + }, + { + "field": "max", + "value": "1845.97" + } + ] + ], + "statistics": { + "recordsMatched": 1859.0, + "recordsScanned": 7270.0, + "bytesScanned": 2523694.0 + }, + "status": "Complete" +} diff --git a/src/NET8/Report/load-test-report-arm64.txt b/src/NET8/Report/load-test-report-arm64.txt new file mode 100644 index 0000000..6cc0f30 --- /dev/null +++ b/src/NET8/Report/load-test-report-arm64.txt @@ -0,0 +1,18 @@ +Fri Nov 10 21:22:49 CET 2023 +arm64 RESULTS +Test duration sec: 60 +Log interval min: 20 +Complete +RESULTS coldstart 0 +RESULTS count 1736 +RESULTS p50 9.0179 +RESULTS p90 21.3228 +RESULTS p99 36.6573 +RESULTS max 57.18 +RESULTS coldstart 1 +RESULTS count 123 +RESULTS p50 1703.2279 +RESULTS p90 1756.8276 +RESULTS p99 1812.1142 +RESULTS max 1845.97 +STATISTICS 2523694.0 1859.0 7270.0 diff --git a/src/NET8/Report/load-test-report-x86.json b/src/NET8/Report/load-test-report-x86.json new file mode 100644 index 0000000..317de0a --- /dev/null +++ b/src/NET8/Report/load-test-report-x86.json @@ -0,0 +1,260 @@ +{ + "results": [ + [ + { + "field": "coldstart", + "value": "0" + }, + { + "field": "count", + "value": "3387" + }, + { + "field": "p50", + "value": "5.6355" + }, + { + "field": "p90", + "value": "11.0938" + }, + { + "field": "p99", + "value": "28.4069" + }, + { + "field": "max", + "value": "65.62" + } + ] + ], + "statistics": { + "recordsMatched": 3387.0, + "recordsScanned": 27649.0, + "bytesScanned": 6866946.0 + }, + "status": "Complete" +} + +{ + "results": [ + [ + { + "field": "coldstart", + "value": "0" + }, + { + "field": "count", + "value": "2742" + }, + { + "field": "p50", + "value": "6.9328" + }, + { + "field": "p90", + "value": "14.4304" + }, + { + "field": "p99", + "value": "34.3935" + }, + { + "field": "max", + "value": "54.72" + } + ], + [ + { + "field": "coldstart", + "value": "1" + }, + { + "field": "count", + "value": "445" + }, + { + "field": "p50", + "value": "1452.9626" + }, + { + "field": "p90", + "value": "1509.209" + }, + { + "field": "p99", + "value": "1608.9048" + }, + { + "field": "max", + "value": "1730.88" + } + ] + ], + "statistics": { + "recordsMatched": 3187.0, + "recordsScanned": 12122.0, + "bytesScanned": 4277937.0 + }, + "status": "Complete" +} +{ + "results": [ + [ + { + "field": "coldstart", + "value": "0" + }, + { + "field": "count", + "value": "1010" + }, + { + "field": "p50", + "value": "9.6115" + }, + { + "field": "p90", + "value": "19.072" + }, + { + "field": "p99", + "value": "38.1472" + }, + { + "field": "max", + "value": "56.86" + } + ], + [ + { + "field": "coldstart", + "value": "1" + }, + { + "field": "count", + "value": "141" + }, + { + "field": "p50", + "value": "1457.3259" + }, + { + "field": "p90", + "value": "1503.1872" + }, + { + "field": "p99", + "value": "1556.7031" + }, + { + "field": "max", + "value": "1609.72" + } + ] + ], + "statistics": { + "recordsMatched": 1151.0, + "recordsScanned": 4761.0, + "bytesScanned": 2021156.0 + }, + "status": "Complete" +} + +{ + "results": [ + [ + { + "field": "coldstart", + "value": "0" + }, + { + "field": "count", + "value": "2137" + }, + { + "field": "p50", + "value": "7.3743" + }, + { + "field": "p90", + "value": "9.0232" + }, + { + "field": "p99", + "value": "15.8828" + }, + { + "field": "max", + "value": "30.14" + } + ] + ], + "statistics": { + "recordsMatched": 2137.0, + "recordsScanned": 18984.0, + "bytesScanned": 8138385.0 + }, + "status": "Complete" +} +{ + "results": [ + [ + { + "field": "coldstart", + "value": "0" + }, + { + "field": "count", + "value": "2538" + }, + { + "field": "p50", + "value": "6.9328" + }, + { + "field": "p90", + "value": "14.3159" + }, + { + "field": "p99", + "value": "30.2766" + }, + { + "field": "max", + "value": "66.97" + } + ], + [ + { + "field": "coldstart", + "value": "1" + }, + { + "field": "count", + "value": "441" + }, + { + "field": "p50", + "value": "1471.9649" + }, + { + "field": "p90", + "value": "1527.4194" + }, + { + "field": "p99", + "value": "1701.5263" + }, + { + "field": "max", + "value": "1958.74" + } + ] + ], + "statistics": { + "recordsMatched": 2979.0, + "recordsScanned": 11840.0, + "bytesScanned": 3773751.0 + }, + "status": "Complete" +} diff --git a/src/NET8/Report/load-test-report-x86.txt b/src/NET8/Report/load-test-report-x86.txt new file mode 100644 index 0000000..a772c6d --- /dev/null +++ b/src/NET8/Report/load-test-report-x86.txt @@ -0,0 +1,18 @@ +Fri Nov 10 21:20:45 CET 2023 +x86 RESULTS +Test duration sec: 60 +Log interval min: 20 +Complete +RESULTS coldstart 0 +RESULTS count 2538 +RESULTS p50 6.9328 +RESULTS p90 14.3159 +RESULTS p99 30.2766 +RESULTS max 66.97 +RESULTS coldstart 1 +RESULTS count 441 +RESULTS p50 1471.9649 +RESULTS p90 1527.4194 +RESULTS p99 1701.5263 +RESULTS max 1958.74 +STATISTICS 3773751.0 2979.0 11840.0 diff --git a/src/NET8/Shared/Shared.csproj b/src/NET8/Shared/Shared.csproj index 31e5c9f..2797db9 100644 --- a/src/NET8/Shared/Shared.csproj +++ b/src/NET8/Shared/Shared.csproj @@ -13,10 +13,10 @@ linux-x64 - - - - + + + + diff --git a/src/NET8/deploy.sh b/src/NET8/deploy.sh new file mode 100755 index 0000000..9aae7f5 --- /dev/null +++ b/src/NET8/deploy.sh @@ -0,0 +1,43 @@ +#Arguments: +#$1 - delete stack formation to ensure that all test have same conditon and favour similar ammount of cold start events + +STACK_NAME=dotnet8 +DELETE_STACK=yes + +COLOR='\033[0;33m' +NO_COLOR='\033[0m' # No Color + +if [ "x$4" != x ]; +then + DELETE_STACK=$1 +fi + +echo "${COLOR}" +echo -------------------------------------------- +echo DELETE_STACK: $DELETE_STACK +echo -------------------------------------------- +echo "${NO_COLOR}" + +if [ $DELETE_STACK == "yes" ]; +then + echo "${COLOR}" + echo -------------------------------------------- + echo DELETING STACK $STACK_NAME + echo -------------------------------------------- + echo "${NO_COLOR}" + aws cloudformation delete-stack --stack-name $STACK_NAME + echo "${COLOR}" + echo --------------------------------------------- + echo Waiting stack to be deleted + echo -------------------------------------------- + echo "${NO_COLOR}" + aws cloudformation wait stack-delete-complete --stack-name $STACK_NAME + echo "${COLOR}" + echo --------------------------------------------- + echo Stack deleted + echo -------------------------------------------- + echo "${NO_COLOR}" +fi + +sam build +sam deploy --stack-name $STACK_NAME --resolve-s3 --s3-prefix $STACK_NAME --no-confirm-changeset --no-fail-on-empty-changeset --capabilities CAPABILITY_IAM \ No newline at end of file diff --git a/src/NET8/run-loadtest.sh b/src/NET8/run-loadtest.sh new file mode 100755 index 0000000..2391e84 --- /dev/null +++ b/src/NET8/run-loadtest.sh @@ -0,0 +1,153 @@ +#Arguments: +#$1 - load test duration in seconds +#$2 - log interval to be used in the cloudwatch query in minutes +#$3 - when equal to 1 cloudwatch log group will be deleted to ensure that only logs of the load test will be evaluated for stat + +STACK_NAME=dotnet8 +TEST_DURATIOMN_SEC=60 +LOG_INTERVAL_MIN=20 +LOG_DELETE=yes + +COLOR='\033[0;33m' +NO_COLOR='\033[0m' # No Color + +if [ "x$1" != x ]; +then + TEST_DURATIOMN_SEC=$1 +fi + +if [ "x$2" != x ]; +then + LOG_INTERVAL_MIN=$2 +fi + +if [ "x$3" != x ]; +then + LOG_DELETE=$3 +fi + +echo "${COLOR}" +echo -------------------------------------------- +echo DURATION:$TEST_DURATIOMN_SEC +echo LOG INTERVAL:$LOG_INTERVAL_MIN +echo LOG_DELETE: $LOG_DELETE +echo -------------------------------------------- +echo "${NO_COLOR}" + +mkdir -p Report + +function RunLoadTest() +{ + #Params: + #$1 - Architecture (x86 or arm64).Used for logging and naming report file + #$2 - Stack output name to get API Url + #$3 - Stack output name to get lambda name GetProducts + #$4 - Stack output name to get lambda name GetProduct + #$5 - Stack output name to get lambda name DeleteProduct + #$6 - Stack output name to get lambda name PutProduct + + #get test params from cloud formation output + echo "${COLOR}" + export API_URL=$(aws cloudformation describe-stacks --stack-name $STACK_NAME \ + --query "Stacks[0].Outputs[?OutputKey=='$2'].OutputValue" \ + --output text) + echo API URL: $API_URL + + LAMBDA_GETPRODUCTS=$(aws cloudformation describe-stacks --stack-name $STACK_NAME \ + --query "Stacks[0].Outputs[?OutputKey=='$3'].OutputValue" \ + --output text) + echo LAMBDA: $LAMBDA_GETPRODUCTS + + LAMBDA_GETPRODUCT=$(aws cloudformation describe-stacks --stack-name $STACK_NAME \ + --query "Stacks[0].Outputs[?OutputKey=='$4'].OutputValue" \ + --output text) + echo LAMBDA: $LAMBDA_GETPRODUCT + + LAMBDA_DELETEPRODUCT=$(aws cloudformation describe-stacks --stack-name $STACK_NAME \ + --query "Stacks[0].Outputs[?OutputKey=='$5'].OutputValue" \ + --output text) + echo LAMBDA: $LAMBDA_DELETEPRODUCT + + LAMBDA_PUTPRODUCT=$(aws cloudformation describe-stacks --stack-name $STACK_NAME \ + --query "Stacks[0].Outputs[?OutputKey=='$6'].OutputValue" \ + --output text) + echo LAMBDA: $LAMBDA_PUTPRODUCT + + if [ $LOG_DELETE == "yes" ]; + then + echo -------------------------------------------- + echo DELETING CLOUDWATCH LOG GROUP /aws/lambda/$LAMBDA_GETPRODUCTS + echo -------------------------------------------- + aws logs delete-log-group --log-group-name /aws/lambda/$LAMBDA_GETPRODUCTS + echo -------------------------------------------- + echo DELETING CLOUDWATCH LOG GROUP /aws/lambda/$LAMBDA_GETPRODUCT + echo -------------------------------------------- + aws logs delete-log-group --log-group-name /aws/lambda/$LAMBDA_GETPRODUCT + echo -------------------------------------------- + echo DELETING CLOUDWATCH LOG GROUP /aws/lambda/$LAMBDA_DELETEPRODUCT + echo -------------------------------------------- + aws logs delete-log-group --log-group-name /aws/lambda/$LAMBDA_DELETEPRODUCT + echo -------------------------------------------- + echo DELETING CLOUDWATCH LOG GROUP /aws/lambda/$LAMBDA_PUTPRODUCT + echo -------------------------------------------- + aws logs delete-log-group --log-group-name /aws/lambda/$LAMBDA_PUTPRODUCT + echo --------------------------------------------- + echo Waiting 10 sec. for deletion to complete + echo -------------------------------------------- + sleep 10 + fi + + #run load test with artillery + echo -------------------------------------------- + echo $1 RUNNING LOAD TEST $TEST_DURATIOMN_SEC sec $API_URL + echo -------------------------------------------- + echo "${NO_COLOR}" + artillery run \ + --overrides '{"config": { "phases": [{ "duration": '$TEST_DURATIOMN_SEC', "arrivalRate": 100 }] } }' \ + --quiet \ + ../../loadtest/codebuild/load-test.yml + + echo "${COLOR}" + echo -------------------------------------------- + echo Waiting 10 sec. for logs to consolidate + echo -------------------------------------------- + sleep 10 + + #get stats from cloudwatch + enddate=$(date "+%s") + startdate=$(($enddate-($LOG_INTERVAL_MIN*60))) + echo -------------------------------------------- + echo Log start:$startdate end:$enddate + echo -------------------------------------------- + + QUERY_ID=$(aws logs start-query \ + --log-group-names "/aws/lambda/$LAMBDA_GETPRODUCTS" "/aws/lambda/$LAMBDA_GETPRODUCT" "/aws/lambda/$LAMBDA_DELETEPRODUCT" "/aws/lambda/$LAMBDA_PUTPRODUCT" \ + --start-time $startdate \ + --end-time $enddate \ + --query-string 'filter @type="REPORT" | fields greatest(@initDuration, 0) + @duration as duration, ispresent(@initDuration) as coldstart | stats count(*) as count, pct(duration, 50) as p50, pct(duration, 90) as p90, pct(duration, 99) as p99, max(duration) as max by coldstart' \ + | jq -r '.queryId') + + echo -------------------------------------------- + echo Query started, id: $QUERY_ID + echo -------------------------------------------- + + echo --------------------------------------------- + echo Waiting 10 sec. for cloudwatch query to complete + echo -------------------------------------------- + sleep 10 + + echo -------------------------------------------- + echo RESULTS $1 + echo -------------------------------------------- + echo "${NO_COLOR}" + date > ./Report/load-test-report-$1.txt + echo $1 RESULTS >> ./Report/load-test-report-$1.txt + echo Test duration sec: $TEST_DURATIOMN_SEC >> ./Report/load-test-report-$1.txt + echo Log interval min: $LOG_INTERVAL_MIN >> ./Report/load-test-report-$1.txt + aws logs get-query-results --query-id $QUERY_ID --output text >> ./Report/load-test-report-$1.txt + cat ./Report/load-test-report-$1.txt + aws logs get-query-results --query-id $QUERY_ID --output json >> ./Report/load-test-report-$1.json +} + +RunLoadTest x86 ApiUrlX86 LambdaX86NameGetProducts LambdaX86NameGetProduct LambdaX86NameDeleteProduct LambdaX86NamePutProduct +RunLoadTest arm64 ApiUrlArm64 LambdaArm64NameGetProducts LambdaArm64NameGetProduct LambdaArm64NameDeleteProduct LambdaArm64NamePutProduct \ No newline at end of file diff --git a/src/NET8/template.yaml b/src/NET8/template.yaml index c6c34f7..0e49fe0 100644 --- a/src/NET8/template.yaml +++ b/src/NET8/template.yaml @@ -1,10 +1,17 @@ AWSTemplateFormatVersion: "2010-09-09" Transform: AWS::Serverless-2016-10-31 +Parameters: + x86FunctionNamePrefix: + Type: String + Default: Net8-X86 + arm64FunctionNamePrefix: + Type: String + Default: Net8-Arm64 + Globals: Function: MemorySize: 1024 - Architectures: ["x86_64"] Runtime: provided.al2 Timeout: 30 Tracing: Active @@ -13,16 +20,19 @@ Globals: PRODUCT_TABLE_NAME: !Ref Table Resources: - GetProductsFunction: + #X86 + GetProductsFunctionX86: Type: AWS::Serverless::Function Properties: + FunctionName: !Join [ -,[!Ref x86FunctionNamePrefix,GetProducts]] + Architectures: [x86_64] CodeUri: ./ Handler: bootstrap Events: Api: Type: HttpApi Properties: - Path: / + Path: /x86 Method: GET Policies: - DynamoDBReadPolicy: @@ -31,16 +41,18 @@ Resources: Metadata: BuildMethod: makefile - GetProductFunction: + GetProductFunctionX86: Type: AWS::Serverless::Function Properties: + FunctionName: !Join [ -,[!Ref x86FunctionNamePrefix,GetProduct]] + Architectures: [x86_64] CodeUri: ./ Handler: GetProduct Events: Api: Type: HttpApi Properties: - Path: /{id} + Path: /x86/{id} Method: GET Policies: - Version: "2012-10-17" @@ -51,16 +63,18 @@ Resources: Metadata: BuildMethod: makefile - DeleteProductFunction: + DeleteProductFunctionX86: Type: AWS::Serverless::Function Properties: + FunctionName: !Join [ -,[!Ref x86FunctionNamePrefix,DeleteProduct]] + Architectures: [x86_64] CodeUri: ./ Handler: DeleteProduct Events: Api: Type: HttpApi Properties: - Path: /{id} + Path: /x86/{id} Method: DELETE Policies: - Version: "2012-10-17" @@ -73,16 +87,18 @@ Resources: Metadata: BuildMethod: makefile - PutProductFunction: + PutProductFunctionX86: Type: AWS::Serverless::Function Properties: + FunctionName: !Join [ -,[!Ref x86FunctionNamePrefix,PutProduct]] + Architectures: [x86_64] CodeUri: ./ Handler: PutProduct Events: Api: Type: HttpApi Properties: - Path: /{id} + Path: /x86/{id} Method: PUT Policies: - Version: "2012-10-17" @@ -92,37 +108,94 @@ Resources: Resource: !GetAtt Table.Arn Metadata: BuildMethod: makefile + #ARM64 + GetProductsFunctionArm64: + Type: AWS::Serverless::Function + Properties: + FunctionName: !Join [ -,[!Ref arm64FunctionNamePrefix,GetProducts]] + Architectures: [arm64] + CodeUri: ./ + Handler: bootstrap + Events: + Api: + Type: HttpApi + Properties: + Path: /arm64 + Method: GET + Policies: + - DynamoDBReadPolicy: + TableName: + !Ref Table + Metadata: + BuildMethod: makefile - GenerateLoadTestResults: + GetProductFunctionArm64: Type: AWS::Serverless::Function Properties: - CodeUri: ./GenerateLoadTestResults/ - Handler: GenerateLoadTestResults::GenerateLoadTestResults.Function::FunctionHandler - Runtime: dotnet6 + FunctionName: !Join [ -,[!Ref arm64FunctionNamePrefix,GetProduct]] + Architectures: [arm64] + CodeUri: ./ + Handler: GetProduct Events: Api: Type: HttpApi Properties: - Path: /test-results + Path: /arm64/{id} Method: GET - Environment: - Variables: - LOG_GROUP_PREFIX: !Sub "/aws/lambda/net-8-base-" - LOAD_TEST_TYPE: "NET 8" - LAMBDA_ARCHITECTURE: "x86_64" Policies: - Version: "2012-10-17" Statement: - - Sid: AllowStartQueries - Effect: Allow + - Effect: Allow + Action: dynamodb:GetItem + Resource: !GetAtt Table.Arn + Metadata: + BuildMethod: makefile + + DeleteProductFunctionArm64: + Type: AWS::Serverless::Function + Properties: + FunctionName: !Join [ -,[!Ref arm64FunctionNamePrefix,DeleteProduct]] + Architectures: [arm64] + CodeUri: ./ + Handler: DeleteProduct + Events: + Api: + Type: HttpApi + Properties: + Path: /arm64/{id} + Method: DELETE + Policies: + - Version: "2012-10-17" + Statement: + - Effect: Allow Action: - - logs:DescribeLogGroups - - logs:StartQuery - Resource: "*" - - Sid: AllowGetQueryResults - Effect: Allow - Action: logs:GetQueryResults - Resource: "*" + - dynamodb:DeleteItem + - dynamodb:GetItem + Resource: !GetAtt Table.Arn + Metadata: + BuildMethod: makefile + + PutProductFunctionArm64: + Type: AWS::Serverless::Function + Properties: + FunctionName: !Join [ -,[!Ref arm64FunctionNamePrefix,PutProduct]] + Architectures: [arm64] + CodeUri: ./ + Handler: PutProduct + Events: + Api: + Type: HttpApi + Properties: + Path: /arm64/{id} + Method: PUT + Policies: + - Version: "2012-10-17" + Statement: + - Effect: Allow + Action: dynamodb:PutItem + Resource: !GetAtt Table.Arn + Metadata: + BuildMethod: makefile Table: Type: AWS::DynamoDB::Table @@ -138,4 +211,38 @@ Resources: Outputs: ApiUrl: Description: "API Gateway endpoint URL" - Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/" \ No newline at end of file + Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com" + ApiUrlX86: + Description: "X86 API endpoint URL" + Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/x86" + ApiUrlArm64: + Description: "Arm64 GateAPI endpoint URL" + Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/arm64" + + #x86 + LambdaX86NameGetProducts: + Description: "Lambda X86 GetProducts" + Value: !Join [ -,[!Ref x86FunctionNamePrefix,GetProducts]] + LambdaX86NameGetProduct: + Description: "Lambda X86 GetProduct" + Value: !Join [ -,[!Ref x86FunctionNamePrefix,GetProduct]] + LambdaX86NameDeleteProduct: + Description: "Lambda X86 DeleteProduct" + Value: !Join [ -,[!Ref x86FunctionNamePrefix,DeleteProduct]] + LambdaX86NamePutProduct: + Description: "Lambda X86 PutProduct" + Value: !Join [ -,[!Ref x86FunctionNamePrefix,PutProduct]] + + #arm64 + LambdaArm64NameGetProducts: + Description: "Lambda X86 GetProducts" + Value: !Join [ -,[!Ref arm64FunctionNamePrefix,GetProducts]] + LambdaArm64NameGetProduct: + Description: "Lambda X86 GetProduct" + Value: !Join [ -,[!Ref arm64FunctionNamePrefix,GetProduct]] + LambdaArm64NameDeleteProduct: + Description: "Lambda X86 DeleteProduct" + Value: !Join [ -,[!Ref arm64FunctionNamePrefix,DeleteProduct]] + LambdaArm64NamePutProduct: + Description: "Lambda X86 PutProduct" + Value: !Join [ -,[!Ref arm64FunctionNamePrefix,PutProduct]] \ No newline at end of file From e340cec061256763897d855c1d75ec4a41aaf685 Mon Sep 17 00:00:00 2001 From: Riccardo Venturi Date: Fri, 10 Nov 2023 21:48:56 +0100 Subject: [PATCH 11/32] adding net8 load test in global script --- loadtest/codebuild/run-all-load-tests.sh | 11 +++++++++++ src/NET8MinimalAPI/run-loadtest.sh | 3 +-- src/NET8MinimalAPI/template.yaml | 10 +++++++--- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/loadtest/codebuild/run-all-load-tests.sh b/loadtest/codebuild/run-all-load-tests.sh index f64b9cf..0156363 100755 --- a/loadtest/codebuild/run-all-load-tests.sh +++ b/loadtest/codebuild/run-all-load-tests.sh @@ -50,6 +50,17 @@ else source ./run-loadtest.sh $TEST_DURATIOMN_SEC $LOG_INTERVAL_MIN $LOG_DELETE fi +#export LT_NET8_MINIMAL_API=1 +if [ "$LT_NET8" != yes ]; +then + echo SKIPPING net8 :$LT_NET8 +else + echo "RUNNING load test for net8" + cd ../../src/NET8/ + source ./deploy.sh $DELETE_STACK + source ./run-loadtest.sh $TEST_DURATIOMN_SEC $LOG_INTERVAL_MIN $LOG_DELETE +fi + #export LT_NET8_MINIMAL_API=1 if [ "$LT_NET8_MINIMAL_API" != yes ]; then diff --git a/src/NET8MinimalAPI/run-loadtest.sh b/src/NET8MinimalAPI/run-loadtest.sh index 41344f0..4c69913 100755 --- a/src/NET8MinimalAPI/run-loadtest.sh +++ b/src/NET8MinimalAPI/run-loadtest.sh @@ -45,7 +45,7 @@ function RunLoadTest() #get test params from cloud formation output echo "${COLOR}" - API_URL=$(aws cloudformation describe-stacks --stack-name $STACK_NAME \ + export API_URL=$(aws cloudformation describe-stacks --stack-name $STACK_NAME \ --query "Stacks[0].Outputs[?OutputKey=='$2'].OutputValue" \ --output text) echo API URL: $API_URL @@ -73,7 +73,6 @@ function RunLoadTest() echo -------------------------------------------- echo "${NO_COLOR}" artillery run \ - --target "$API_URL" \ --overrides '{"config": { "phases": [{ "duration": '$TEST_DURATIOMN_SEC', "arrivalRate": 100 }] } }' \ --quiet \ ../../loadtest/load-test.yml diff --git a/src/NET8MinimalAPI/template.yaml b/src/NET8MinimalAPI/template.yaml index e5501b8..abb40f5 100644 --- a/src/NET8MinimalAPI/template.yaml +++ b/src/NET8MinimalAPI/template.yaml @@ -40,6 +40,8 @@ Resources: - DynamoDBCrudPolicy: TableName: !Ref Table + Metadata: + BuildMethod: makefile MinimalApiArm64: Type: AWS::Serverless::Function Properties: @@ -60,6 +62,8 @@ Resources: - DynamoDBCrudPolicy: TableName: !Ref Table + Metadata: + BuildMethod: makefile Table: Type: AWS::DynamoDB::Table @@ -77,16 +81,16 @@ Resources: Outputs: ApiUrl: Description: "API Gateway endpoint URL" - Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/" + Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com" LambdaX86Name: Description: "Lambda X86 Name" Value: !Ref x86FunctionName ApiUrlX86: Description: "X86 API endpoint URL" - Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/x86/" + Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/x86" LambdaArm64Name: Description: "Lambda Arm64 Name" Value: !Ref arm64FunctionName ApiUrlArm64: Description: "Arm64 GateAPI endpoint URL" - Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/arm64/" \ No newline at end of file + Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/arm64" \ No newline at end of file From d0a285ae30989522154d5b901a2e0a8eea0ef892 Mon Sep 17 00:00:00 2001 From: Riccardo Venturi Date: Fri, 10 Nov 2023 22:34:11 +0100 Subject: [PATCH 12/32] saving NET8 load test report in artifacts --- loadtest/codebuild/load-test-buildspec.yml | 1 + .../DeleteProduct/DeleteProduct.csproj | 12 +- .../GenerateLoadTestResults/DateUtils.cs | 13 -- .../GenerateLoadTestResults/Function.cs | 146 --------------- .../GenerateLoadTestResults.csproj | 17 -- .../GenerateLoadTestResults/QueryResult.cs | 25 --- src/NET8Native/GetProduct/GetProduct.csproj | 12 +- src/NET8Native/GetProducts/GetProducts.csproj | 12 +- src/NET8Native/Makefile | 28 ++- src/NET8Native/PutProduct/PutProduct.csproj | 12 +- src/NET8Native/Shared/Shared.csproj | 8 +- src/NET8Native/deploy.sh | 43 +++++ src/NET8Native/run-loadtest.sh | 153 ++++++++++++++++ src/NET8Native/template.yaml | 166 ++++++++++++++++-- .../ApiBootstrap/ApiBootstrap.csproj | 8 +- 15 files changed, 401 insertions(+), 255 deletions(-) delete mode 100644 src/NET8Native/GenerateLoadTestResults/DateUtils.cs delete mode 100644 src/NET8Native/GenerateLoadTestResults/Function.cs delete mode 100644 src/NET8Native/GenerateLoadTestResults/GenerateLoadTestResults.csproj delete mode 100644 src/NET8Native/GenerateLoadTestResults/QueryResult.cs create mode 100755 src/NET8Native/deploy.sh create mode 100755 src/NET8Native/run-loadtest.sh diff --git a/loadtest/codebuild/load-test-buildspec.yml b/loadtest/codebuild/load-test-buildspec.yml index 1feec27..8e5a880 100644 --- a/loadtest/codebuild/load-test-buildspec.yml +++ b/loadtest/codebuild/load-test-buildspec.yml @@ -14,5 +14,6 @@ artifacts: files: - src/NET6MinimalAPI/Report/* - src/NET6MinimalAPIWebAdapter/Report/* + - src/NET8/Report/* - src/NET8MinimalAPI/Report/* name: loadtest-$(date +%Y-%m-%H-%M) \ No newline at end of file diff --git a/src/NET8Native/DeleteProduct/DeleteProduct.csproj b/src/NET8Native/DeleteProduct/DeleteProduct.csproj index 1be8350..f64c696 100644 --- a/src/NET8Native/DeleteProduct/DeleteProduct.csproj +++ b/src/NET8Native/DeleteProduct/DeleteProduct.csproj @@ -16,12 +16,12 @@ - - - - - - + + + + + + diff --git a/src/NET8Native/GenerateLoadTestResults/DateUtils.cs b/src/NET8Native/GenerateLoadTestResults/DateUtils.cs deleted file mode 100644 index c37c43d..0000000 --- a/src/NET8Native/GenerateLoadTestResults/DateUtils.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; - -namespace GenerateLoadTestResults; - -public static class DateUtils -{ - public static long AsUnixTimestamp(this DateTime date) - { - DateTime origin = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); - TimeSpan diff = date.ToUniversalTime() - origin; - return (long)Math.Floor(diff.TotalSeconds); - } -} \ No newline at end of file diff --git a/src/NET8Native/GenerateLoadTestResults/Function.cs b/src/NET8Native/GenerateLoadTestResults/Function.cs deleted file mode 100644 index 21a57a8..0000000 --- a/src/NET8Native/GenerateLoadTestResults/Function.cs +++ /dev/null @@ -1,146 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Text.Json; -using System.Threading.Tasks; -using Amazon.CloudWatchLogs; -using Amazon.CloudWatchLogs.Model; -using Amazon.Lambda.APIGatewayEvents; -using Amazon.Lambda.Core; - -[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] - -namespace GenerateLoadTestResults -{ - public class Function - { - private AmazonCloudWatchLogsClient _cloudWatchLogsClient; - - public Function() - { - this._cloudWatchLogsClient = new AmazonCloudWatchLogsClient(); - } - - public async Task FunctionHandler( - APIGatewayHttpApiV2ProxyRequest apigProxyEvent, - ILambdaContext context) - { - if (!apigProxyEvent.RequestContext.Http.Method.Equals(HttpMethod.Get.Method)) - { - return new APIGatewayHttpApiV2ProxyResponse - { - Body = "Only GET allowed", - StatusCode = (int) HttpStatusCode.MethodNotAllowed, - }; - } - - try - { - var resultRows = 0; - var queryCount = 0; - - List> finalResults = new List>(); - - while (resultRows < 2 || queryCount >= 3) - { - finalResults = await runQuery(context); - - resultRows = finalResults.Count; - queryCount++; - } - - var wrapper = new QueryResultWrapper() - { - LoadTestType = - $"{Environment.GetEnvironmentVariable("LOAD_TEST_TYPE")} ({Environment.GetEnvironmentVariable("LAMBDA_ARCHITECTURE")})", - WarmStart = new QueryResult() - { - Count = finalResults[0][1].Value, - P50 = finalResults[0][2].Value, - P90 = finalResults[0][3].Value, - P99 = finalResults[0][4].Value, - Max = finalResults[0][5].Value, - }, - ColdStart = new QueryResult() - { - Count = finalResults[1][1].Value, - P50 = finalResults[1][2].Value, - P90 = finalResults[1][3].Value, - P99 = finalResults[1][4].Value, - Max = finalResults[1][5].Value, - } - }; - - return new APIGatewayHttpApiV2ProxyResponse - { - StatusCode = (int) HttpStatusCode.OK, - Body = wrapper.AsMarkdownTableRow(), - Headers = new Dictionary {{"Content-Type", "text/html"}} - }; - } - catch (Exception e) - { - context.Logger.LogLine($"Error retrieving results {e.Message} {e.StackTrace}"); - - return new APIGatewayHttpApiV2ProxyResponse - { - Body = "Not Found", - StatusCode = (int) HttpStatusCode.InternalServerError, - }; - } - } - - private async Task>> runQuery(ILambdaContext context) - { - var logGroupNamePrefix = - $"{Environment.GetEnvironmentVariable("LOG_GROUP_PREFIX")}{Environment.GetEnvironmentVariable("LAMBDA_ARCHITECTURE")}" - .Replace("_", "-"); - - context.Logger.LogLine($"Retrieving log groups with prefix {logGroupNamePrefix}"); - - var logGroupList = await _cloudWatchLogsClient.DescribeLogGroupsAsync(new DescribeLogGroupsRequest() - { - LogGroupNamePrefix = logGroupNamePrefix, - }); - - context.Logger.LogLine($"Found {logGroupList.LogGroups.Count} log group(s)"); - - var queryRes = await _cloudWatchLogsClient.StartQueryAsync(new StartQueryRequest() - { - LogGroupNames = logGroupList.LogGroups.Select(p => p.LogGroupName).ToList(), - QueryString = - "filter @type=\"REPORT\" | fields greatest(@initDuration, 0) + @duration as duration, ispresent(@initDuration) as coldstart | stats count(*) as count, pct(duration, 50) as p50, pct(duration, 90) as p90, pct(duration, 99) as p99, max(duration) as max by coldstart", - StartTime = DateTime.Now.AddMinutes(-20).AsUnixTimestamp(), - EndTime = DateTime.Now.AsUnixTimestamp(), - }); - - context.Logger.LogLine($"Running query, query id is {queryRes.QueryId}"); - - QueryStatus currentQueryStatus = QueryStatus.Running; - List> finalResults = new List>(); - - while (currentQueryStatus == QueryStatus.Running || currentQueryStatus == QueryStatus.Scheduled) - { - context.Logger.LogLine("Retrieving query results"); - - var queryResults = await _cloudWatchLogsClient.GetQueryResultsAsync(new GetQueryResultsRequest() - { - QueryId = queryRes.QueryId - }); - - context.Logger.LogLine($"Query result status is {queryResults.Status}"); - - currentQueryStatus = queryResults.Status; - finalResults = queryResults.Results; - - await Task.Delay(TimeSpan.FromSeconds(5)); - } - - context.Logger.LogLine($"Final results: {finalResults.Count} row(s)"); - - return finalResults; - } - } -} \ No newline at end of file diff --git a/src/NET8Native/GenerateLoadTestResults/GenerateLoadTestResults.csproj b/src/NET8Native/GenerateLoadTestResults/GenerateLoadTestResults.csproj deleted file mode 100644 index 4a8d708..0000000 --- a/src/NET8Native/GenerateLoadTestResults/GenerateLoadTestResults.csproj +++ /dev/null @@ -1,17 +0,0 @@ - - - - net6.0 - true - true - - - - - - - - - - - diff --git a/src/NET8Native/GenerateLoadTestResults/QueryResult.cs b/src/NET8Native/GenerateLoadTestResults/QueryResult.cs deleted file mode 100644 index 69b5a31..0000000 --- a/src/NET8Native/GenerateLoadTestResults/QueryResult.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace GenerateLoadTestResults; - -public record QueryResultWrapper -{ - public string LoadTestType { get; set; } - - public QueryResult ColdStart { get; set; } - - public QueryResult WarmStart { get; set; } - - public string AsMarkdownTableRow() => $"
Cold Start (ms)Warm Start (ms)
p50p90p99maxp50p90p99max
{LoadTestType}{ColdStart.P50}{ColdStart.P90}{ColdStart.P99}{ColdStart.Max}{WarmStart.P50}{WarmStart.P90}{WarmStart.P99}{WarmStart.Max}
"; -} - -public record QueryResult -{ - public string Count { get; set; } - - public string P50 { get; set; } - - public string P90 { get; set; } - - public string P99 { get; set; } - - public string Max { get; set; } -} \ No newline at end of file diff --git a/src/NET8Native/GetProduct/GetProduct.csproj b/src/NET8Native/GetProduct/GetProduct.csproj index 1354b30..e070d2e 100644 --- a/src/NET8Native/GetProduct/GetProduct.csproj +++ b/src/NET8Native/GetProduct/GetProduct.csproj @@ -18,12 +18,12 @@ Speed - - - - - - + + + + + + diff --git a/src/NET8Native/GetProducts/GetProducts.csproj b/src/NET8Native/GetProducts/GetProducts.csproj index 578c023..7f65871 100644 --- a/src/NET8Native/GetProducts/GetProducts.csproj +++ b/src/NET8Native/GetProducts/GetProducts.csproj @@ -15,12 +15,12 @@ Speed - - - - - - + + + + + + diff --git a/src/NET8Native/Makefile b/src/NET8Native/Makefile index 9cc4200..4850513 100644 --- a/src/NET8Native/Makefile +++ b/src/NET8Native/Makefile @@ -1,11 +1,23 @@ -build-GetProductsFunction: - dotnet publish -c Release -r linux-x64 ./GetProducts/GetProducts.csproj -o $(ARTIFACTS_DIR) +build-GetProductFunctionX86: + dotnet publish GetProduct/GetProduct.csproj -c Release -r linux-x64 --self-contained -o $(ARTIFACTS_DIR) -build-GetProductFunction: - dotnet publish -c Release -r linux-x64 ./GetProduct/GetProduct.csproj -o $(ARTIFACTS_DIR) +build-GetProductsFunctionX86: + dotnet publish GetProducts/GetProducts.csproj -c Release -r linux-x64 --self-contained -o $(ARTIFACTS_DIR) -build-DeleteProductFunction: - dotnet publish -c Release -r linux-x64 ./DeleteProduct/DeleteProduct.csproj -o $(ARTIFACTS_DIR) +build-PutProductFunctionX86: + dotnet publish PutProduct/PutProduct.csproj -c Release -r linux-x64 --self-contained -o $(ARTIFACTS_DIR) -build-PutProductFunction: - dotnet publish -c Release -r linux-x64 ./PutProduct/PutProduct.csproj -o $(ARTIFACTS_DIR) \ No newline at end of file +build-DeleteProductFunctionX86: + dotnet publish DeleteProduct/DeleteProduct.csproj -c Release -r linux-x64 --self-contained -o $(ARTIFACTS_DIR) + +build-GetProductFunctionArm64: + dotnet publish GetProduct/GetProduct.csproj -c Release -r linux-arm64 --self-contained -o $(ARTIFACTS_DIR) + +build-GetProductsFunctionArm64: + dotnet publish GetProducts/GetProducts.csproj -c Release -r linux-arm64 --self-contained -o $(ARTIFACTS_DIR) + +build-PutProductFunctionArm64: + dotnet publish PutProduct/PutProduct.csproj -c Release -r linux-arm64 --self-contained -o $(ARTIFACTS_DIR) + +build-DeleteProductFunctionArm64: + dotnet publish DeleteProduct/DeleteProduct.csproj -c Release -r linux-arm64 --self-contained -o $(ARTIFACTS_DIR) \ No newline at end of file diff --git a/src/NET8Native/PutProduct/PutProduct.csproj b/src/NET8Native/PutProduct/PutProduct.csproj index 1be8350..f64c696 100644 --- a/src/NET8Native/PutProduct/PutProduct.csproj +++ b/src/NET8Native/PutProduct/PutProduct.csproj @@ -16,12 +16,12 @@ - - - - - - + + + + + + diff --git a/src/NET8Native/Shared/Shared.csproj b/src/NET8Native/Shared/Shared.csproj index a2b16db..cf01798 100644 --- a/src/NET8Native/Shared/Shared.csproj +++ b/src/NET8Native/Shared/Shared.csproj @@ -18,10 +18,10 @@ Speed - - - - + + + + diff --git a/src/NET8Native/deploy.sh b/src/NET8Native/deploy.sh new file mode 100755 index 0000000..9aae7f5 --- /dev/null +++ b/src/NET8Native/deploy.sh @@ -0,0 +1,43 @@ +#Arguments: +#$1 - delete stack formation to ensure that all test have same conditon and favour similar ammount of cold start events + +STACK_NAME=dotnet8 +DELETE_STACK=yes + +COLOR='\033[0;33m' +NO_COLOR='\033[0m' # No Color + +if [ "x$4" != x ]; +then + DELETE_STACK=$1 +fi + +echo "${COLOR}" +echo -------------------------------------------- +echo DELETE_STACK: $DELETE_STACK +echo -------------------------------------------- +echo "${NO_COLOR}" + +if [ $DELETE_STACK == "yes" ]; +then + echo "${COLOR}" + echo -------------------------------------------- + echo DELETING STACK $STACK_NAME + echo -------------------------------------------- + echo "${NO_COLOR}" + aws cloudformation delete-stack --stack-name $STACK_NAME + echo "${COLOR}" + echo --------------------------------------------- + echo Waiting stack to be deleted + echo -------------------------------------------- + echo "${NO_COLOR}" + aws cloudformation wait stack-delete-complete --stack-name $STACK_NAME + echo "${COLOR}" + echo --------------------------------------------- + echo Stack deleted + echo -------------------------------------------- + echo "${NO_COLOR}" +fi + +sam build +sam deploy --stack-name $STACK_NAME --resolve-s3 --s3-prefix $STACK_NAME --no-confirm-changeset --no-fail-on-empty-changeset --capabilities CAPABILITY_IAM \ No newline at end of file diff --git a/src/NET8Native/run-loadtest.sh b/src/NET8Native/run-loadtest.sh new file mode 100755 index 0000000..2391e84 --- /dev/null +++ b/src/NET8Native/run-loadtest.sh @@ -0,0 +1,153 @@ +#Arguments: +#$1 - load test duration in seconds +#$2 - log interval to be used in the cloudwatch query in minutes +#$3 - when equal to 1 cloudwatch log group will be deleted to ensure that only logs of the load test will be evaluated for stat + +STACK_NAME=dotnet8 +TEST_DURATIOMN_SEC=60 +LOG_INTERVAL_MIN=20 +LOG_DELETE=yes + +COLOR='\033[0;33m' +NO_COLOR='\033[0m' # No Color + +if [ "x$1" != x ]; +then + TEST_DURATIOMN_SEC=$1 +fi + +if [ "x$2" != x ]; +then + LOG_INTERVAL_MIN=$2 +fi + +if [ "x$3" != x ]; +then + LOG_DELETE=$3 +fi + +echo "${COLOR}" +echo -------------------------------------------- +echo DURATION:$TEST_DURATIOMN_SEC +echo LOG INTERVAL:$LOG_INTERVAL_MIN +echo LOG_DELETE: $LOG_DELETE +echo -------------------------------------------- +echo "${NO_COLOR}" + +mkdir -p Report + +function RunLoadTest() +{ + #Params: + #$1 - Architecture (x86 or arm64).Used for logging and naming report file + #$2 - Stack output name to get API Url + #$3 - Stack output name to get lambda name GetProducts + #$4 - Stack output name to get lambda name GetProduct + #$5 - Stack output name to get lambda name DeleteProduct + #$6 - Stack output name to get lambda name PutProduct + + #get test params from cloud formation output + echo "${COLOR}" + export API_URL=$(aws cloudformation describe-stacks --stack-name $STACK_NAME \ + --query "Stacks[0].Outputs[?OutputKey=='$2'].OutputValue" \ + --output text) + echo API URL: $API_URL + + LAMBDA_GETPRODUCTS=$(aws cloudformation describe-stacks --stack-name $STACK_NAME \ + --query "Stacks[0].Outputs[?OutputKey=='$3'].OutputValue" \ + --output text) + echo LAMBDA: $LAMBDA_GETPRODUCTS + + LAMBDA_GETPRODUCT=$(aws cloudformation describe-stacks --stack-name $STACK_NAME \ + --query "Stacks[0].Outputs[?OutputKey=='$4'].OutputValue" \ + --output text) + echo LAMBDA: $LAMBDA_GETPRODUCT + + LAMBDA_DELETEPRODUCT=$(aws cloudformation describe-stacks --stack-name $STACK_NAME \ + --query "Stacks[0].Outputs[?OutputKey=='$5'].OutputValue" \ + --output text) + echo LAMBDA: $LAMBDA_DELETEPRODUCT + + LAMBDA_PUTPRODUCT=$(aws cloudformation describe-stacks --stack-name $STACK_NAME \ + --query "Stacks[0].Outputs[?OutputKey=='$6'].OutputValue" \ + --output text) + echo LAMBDA: $LAMBDA_PUTPRODUCT + + if [ $LOG_DELETE == "yes" ]; + then + echo -------------------------------------------- + echo DELETING CLOUDWATCH LOG GROUP /aws/lambda/$LAMBDA_GETPRODUCTS + echo -------------------------------------------- + aws logs delete-log-group --log-group-name /aws/lambda/$LAMBDA_GETPRODUCTS + echo -------------------------------------------- + echo DELETING CLOUDWATCH LOG GROUP /aws/lambda/$LAMBDA_GETPRODUCT + echo -------------------------------------------- + aws logs delete-log-group --log-group-name /aws/lambda/$LAMBDA_GETPRODUCT + echo -------------------------------------------- + echo DELETING CLOUDWATCH LOG GROUP /aws/lambda/$LAMBDA_DELETEPRODUCT + echo -------------------------------------------- + aws logs delete-log-group --log-group-name /aws/lambda/$LAMBDA_DELETEPRODUCT + echo -------------------------------------------- + echo DELETING CLOUDWATCH LOG GROUP /aws/lambda/$LAMBDA_PUTPRODUCT + echo -------------------------------------------- + aws logs delete-log-group --log-group-name /aws/lambda/$LAMBDA_PUTPRODUCT + echo --------------------------------------------- + echo Waiting 10 sec. for deletion to complete + echo -------------------------------------------- + sleep 10 + fi + + #run load test with artillery + echo -------------------------------------------- + echo $1 RUNNING LOAD TEST $TEST_DURATIOMN_SEC sec $API_URL + echo -------------------------------------------- + echo "${NO_COLOR}" + artillery run \ + --overrides '{"config": { "phases": [{ "duration": '$TEST_DURATIOMN_SEC', "arrivalRate": 100 }] } }' \ + --quiet \ + ../../loadtest/codebuild/load-test.yml + + echo "${COLOR}" + echo -------------------------------------------- + echo Waiting 10 sec. for logs to consolidate + echo -------------------------------------------- + sleep 10 + + #get stats from cloudwatch + enddate=$(date "+%s") + startdate=$(($enddate-($LOG_INTERVAL_MIN*60))) + echo -------------------------------------------- + echo Log start:$startdate end:$enddate + echo -------------------------------------------- + + QUERY_ID=$(aws logs start-query \ + --log-group-names "/aws/lambda/$LAMBDA_GETPRODUCTS" "/aws/lambda/$LAMBDA_GETPRODUCT" "/aws/lambda/$LAMBDA_DELETEPRODUCT" "/aws/lambda/$LAMBDA_PUTPRODUCT" \ + --start-time $startdate \ + --end-time $enddate \ + --query-string 'filter @type="REPORT" | fields greatest(@initDuration, 0) + @duration as duration, ispresent(@initDuration) as coldstart | stats count(*) as count, pct(duration, 50) as p50, pct(duration, 90) as p90, pct(duration, 99) as p99, max(duration) as max by coldstart' \ + | jq -r '.queryId') + + echo -------------------------------------------- + echo Query started, id: $QUERY_ID + echo -------------------------------------------- + + echo --------------------------------------------- + echo Waiting 10 sec. for cloudwatch query to complete + echo -------------------------------------------- + sleep 10 + + echo -------------------------------------------- + echo RESULTS $1 + echo -------------------------------------------- + echo "${NO_COLOR}" + date > ./Report/load-test-report-$1.txt + echo $1 RESULTS >> ./Report/load-test-report-$1.txt + echo Test duration sec: $TEST_DURATIOMN_SEC >> ./Report/load-test-report-$1.txt + echo Log interval min: $LOG_INTERVAL_MIN >> ./Report/load-test-report-$1.txt + aws logs get-query-results --query-id $QUERY_ID --output text >> ./Report/load-test-report-$1.txt + cat ./Report/load-test-report-$1.txt + aws logs get-query-results --query-id $QUERY_ID --output json >> ./Report/load-test-report-$1.json +} + +RunLoadTest x86 ApiUrlX86 LambdaX86NameGetProducts LambdaX86NameGetProduct LambdaX86NameDeleteProduct LambdaX86NamePutProduct +RunLoadTest arm64 ApiUrlArm64 LambdaArm64NameGetProducts LambdaArm64NameGetProduct LambdaArm64NameDeleteProduct LambdaArm64NamePutProduct \ No newline at end of file diff --git a/src/NET8Native/template.yaml b/src/NET8Native/template.yaml index 22dc554..0e49fe0 100644 --- a/src/NET8Native/template.yaml +++ b/src/NET8Native/template.yaml @@ -1,10 +1,17 @@ AWSTemplateFormatVersion: "2010-09-09" Transform: AWS::Serverless-2016-10-31 +Parameters: + x86FunctionNamePrefix: + Type: String + Default: Net8-X86 + arm64FunctionNamePrefix: + Type: String + Default: Net8-Arm64 + Globals: Function: MemorySize: 1024 - Architectures: ["x86_64"] Runtime: provided.al2 Timeout: 30 Tracing: Active @@ -13,16 +20,107 @@ Globals: PRODUCT_TABLE_NAME: !Ref Table Resources: - GetProductsFunction: + #X86 + GetProductsFunctionX86: + Type: AWS::Serverless::Function + Properties: + FunctionName: !Join [ -,[!Ref x86FunctionNamePrefix,GetProducts]] + Architectures: [x86_64] + CodeUri: ./ + Handler: bootstrap + Events: + Api: + Type: HttpApi + Properties: + Path: /x86 + Method: GET + Policies: + - DynamoDBReadPolicy: + TableName: + !Ref Table + Metadata: + BuildMethod: makefile + + GetProductFunctionX86: + Type: AWS::Serverless::Function + Properties: + FunctionName: !Join [ -,[!Ref x86FunctionNamePrefix,GetProduct]] + Architectures: [x86_64] + CodeUri: ./ + Handler: GetProduct + Events: + Api: + Type: HttpApi + Properties: + Path: /x86/{id} + Method: GET + Policies: + - Version: "2012-10-17" + Statement: + - Effect: Allow + Action: dynamodb:GetItem + Resource: !GetAtt Table.Arn + Metadata: + BuildMethod: makefile + + DeleteProductFunctionX86: + Type: AWS::Serverless::Function + Properties: + FunctionName: !Join [ -,[!Ref x86FunctionNamePrefix,DeleteProduct]] + Architectures: [x86_64] + CodeUri: ./ + Handler: DeleteProduct + Events: + Api: + Type: HttpApi + Properties: + Path: /x86/{id} + Method: DELETE + Policies: + - Version: "2012-10-17" + Statement: + - Effect: Allow + Action: + - dynamodb:DeleteItem + - dynamodb:GetItem + Resource: !GetAtt Table.Arn + Metadata: + BuildMethod: makefile + + PutProductFunctionX86: + Type: AWS::Serverless::Function + Properties: + FunctionName: !Join [ -,[!Ref x86FunctionNamePrefix,PutProduct]] + Architectures: [x86_64] + CodeUri: ./ + Handler: PutProduct + Events: + Api: + Type: HttpApi + Properties: + Path: /x86/{id} + Method: PUT + Policies: + - Version: "2012-10-17" + Statement: + - Effect: Allow + Action: dynamodb:PutItem + Resource: !GetAtt Table.Arn + Metadata: + BuildMethod: makefile + #ARM64 + GetProductsFunctionArm64: Type: AWS::Serverless::Function Properties: - CodeUri: . + FunctionName: !Join [ -,[!Ref arm64FunctionNamePrefix,GetProducts]] + Architectures: [arm64] + CodeUri: ./ Handler: bootstrap Events: Api: Type: HttpApi Properties: - Path: / + Path: /arm64 Method: GET Policies: - DynamoDBReadPolicy: @@ -31,16 +129,18 @@ Resources: Metadata: BuildMethod: makefile - GetProductFunction: + GetProductFunctionArm64: Type: AWS::Serverless::Function Properties: - CodeUri: . + FunctionName: !Join [ -,[!Ref arm64FunctionNamePrefix,GetProduct]] + Architectures: [arm64] + CodeUri: ./ Handler: GetProduct Events: Api: Type: HttpApi Properties: - Path: /{id} + Path: /arm64/{id} Method: GET Policies: - Version: "2012-10-17" @@ -51,16 +151,18 @@ Resources: Metadata: BuildMethod: makefile - DeleteProductFunction: + DeleteProductFunctionArm64: Type: AWS::Serverless::Function Properties: - CodeUri: . + FunctionName: !Join [ -,[!Ref arm64FunctionNamePrefix,DeleteProduct]] + Architectures: [arm64] + CodeUri: ./ Handler: DeleteProduct Events: Api: Type: HttpApi Properties: - Path: /{id} + Path: /arm64/{id} Method: DELETE Policies: - Version: "2012-10-17" @@ -73,16 +175,18 @@ Resources: Metadata: BuildMethod: makefile - PutProductFunction: + PutProductFunctionArm64: Type: AWS::Serverless::Function Properties: - CodeUri: . + FunctionName: !Join [ -,[!Ref arm64FunctionNamePrefix,PutProduct]] + Architectures: [arm64] + CodeUri: ./ Handler: PutProduct Events: Api: Type: HttpApi Properties: - Path: /{id} + Path: /arm64/{id} Method: PUT Policies: - Version: "2012-10-17" @@ -107,4 +211,38 @@ Resources: Outputs: ApiUrl: Description: "API Gateway endpoint URL" - Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/" \ No newline at end of file + Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com" + ApiUrlX86: + Description: "X86 API endpoint URL" + Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/x86" + ApiUrlArm64: + Description: "Arm64 GateAPI endpoint URL" + Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/arm64" + + #x86 + LambdaX86NameGetProducts: + Description: "Lambda X86 GetProducts" + Value: !Join [ -,[!Ref x86FunctionNamePrefix,GetProducts]] + LambdaX86NameGetProduct: + Description: "Lambda X86 GetProduct" + Value: !Join [ -,[!Ref x86FunctionNamePrefix,GetProduct]] + LambdaX86NameDeleteProduct: + Description: "Lambda X86 DeleteProduct" + Value: !Join [ -,[!Ref x86FunctionNamePrefix,DeleteProduct]] + LambdaX86NamePutProduct: + Description: "Lambda X86 PutProduct" + Value: !Join [ -,[!Ref x86FunctionNamePrefix,PutProduct]] + + #arm64 + LambdaArm64NameGetProducts: + Description: "Lambda X86 GetProducts" + Value: !Join [ -,[!Ref arm64FunctionNamePrefix,GetProducts]] + LambdaArm64NameGetProduct: + Description: "Lambda X86 GetProduct" + Value: !Join [ -,[!Ref arm64FunctionNamePrefix,GetProduct]] + LambdaArm64NameDeleteProduct: + Description: "Lambda X86 DeleteProduct" + Value: !Join [ -,[!Ref arm64FunctionNamePrefix,DeleteProduct]] + LambdaArm64NamePutProduct: + Description: "Lambda X86 PutProduct" + Value: !Join [ -,[!Ref arm64FunctionNamePrefix,PutProduct]] \ No newline at end of file diff --git a/src/NET8NativeMinimalAPI/ApiBootstrap/ApiBootstrap.csproj b/src/NET8NativeMinimalAPI/ApiBootstrap/ApiBootstrap.csproj index 48bb4ef..7e0bdad 100644 --- a/src/NET8NativeMinimalAPI/ApiBootstrap/ApiBootstrap.csproj +++ b/src/NET8NativeMinimalAPI/ApiBootstrap/ApiBootstrap.csproj @@ -15,10 +15,10 @@ - - - - + + + + From d9a422eb71029e95efc2b9e60a6f0fc1702aa5ba Mon Sep 17 00:00:00 2001 From: Riccardo Venturi Date: Fri, 10 Nov 2023 23:06:51 +0100 Subject: [PATCH 13/32] updatin net 8 projects --- src/NET8/Report/load-test-report-.json | 0 src/NET8/Report/load-test-report-.txt | 2 - src/NET8/Report/load-test-report-arm64.json | 186 ------------- src/NET8/Report/load-test-report-arm64.txt | 18 -- src/NET8/Report/load-test-report-x86.json | 260 ------------------ src/NET8/Report/load-test-report-x86.txt | 18 -- src/NET8MinimalAPI/Makefile | 2 - src/NET8MinimalAPI/Shared/Shared.csproj | 2 +- src/NET8Native/Makefile | 16 +- .../ApiBootstrap/ApiBootstrap.csproj | 7 +- .../ApiBootstrap/Function.cs | 163 ++++++----- src/NET8NativeMinimalAPI/Makefile | 6 +- .../Shared/DataAccess/DynamoDbProducts.cs | 5 +- .../Shared/DataAccess/ProductMapper.cs | 4 +- .../Shared/DataAccess/ProductsDAO.cs | 3 +- .../Shared/Models/Product.cs | 11 +- .../Shared/Models/ProductWrapper.cs | 4 +- src/NET8NativeMinimalAPI/Shared/Shared.csproj | 4 +- src/NET8NativeMinimalAPI/Shared/Startup.cs | 7 +- src/NET8NativeMinimalAPI/deploy.sh | 43 +++ src/NET8NativeMinimalAPI/run-loadtest.sh | 123 +++++++++ src/NET8NativeMinimalAPI/template.yaml | 72 +++-- 22 files changed, 348 insertions(+), 608 deletions(-) delete mode 100644 src/NET8/Report/load-test-report-.json delete mode 100644 src/NET8/Report/load-test-report-.txt delete mode 100644 src/NET8/Report/load-test-report-arm64.json delete mode 100644 src/NET8/Report/load-test-report-arm64.txt delete mode 100644 src/NET8/Report/load-test-report-x86.json delete mode 100644 src/NET8/Report/load-test-report-x86.txt create mode 100755 src/NET8NativeMinimalAPI/deploy.sh create mode 100755 src/NET8NativeMinimalAPI/run-loadtest.sh diff --git a/src/NET8/Report/load-test-report-.json b/src/NET8/Report/load-test-report-.json deleted file mode 100644 index e69de29..0000000 diff --git a/src/NET8/Report/load-test-report-.txt b/src/NET8/Report/load-test-report-.txt deleted file mode 100644 index 183df41..0000000 --- a/src/NET8/Report/load-test-report-.txt +++ /dev/null @@ -1,2 +0,0 @@ -Test duration sec: 60 -Log interval min: 20 diff --git a/src/NET8/Report/load-test-report-arm64.json b/src/NET8/Report/load-test-report-arm64.json deleted file mode 100644 index a6c8a0a..0000000 --- a/src/NET8/Report/load-test-report-arm64.json +++ /dev/null @@ -1,186 +0,0 @@ -{ - "results": [ - [ - { - "field": "coldstart", - "value": "0" - }, - { - "field": "count", - "value": "806" - }, - { - "field": "p50", - "value": "9.5341" - }, - { - "field": "p90", - "value": "22.7017" - }, - { - "field": "p99", - "value": "42.4839" - }, - { - "field": "max", - "value": "107.71" - } - ], - [ - { - "field": "coldstart", - "value": "1" - }, - { - "field": "count", - "value": "146" - }, - { - "field": "p50", - "value": "1725.5032" - }, - { - "field": "p90", - "value": "1801.2795" - }, - { - "field": "p99", - "value": "1865.4079" - }, - { - "field": "max", - "value": "1961.07" - } - ] - ], - "statistics": { - "recordsMatched": 952.0, - "recordsScanned": 4680.0, - "bytesScanned": 1408784.0 - }, - "status": "Complete" -} -{ - "results": [ - [ - { - "field": "coldstart", - "value": "0" - }, - { - "field": "count", - "value": "1968" - }, - { - "field": "p50", - "value": "9.7659" - }, - { - "field": "p90", - "value": "22.5459" - }, - { - "field": "p99", - "value": "38.4524" - }, - { - "field": "max", - "value": "113.03" - } - ], - [ - { - "field": "coldstart", - "value": "1" - }, - { - "field": "count", - "value": "334" - }, - { - "field": "p50", - "value": "1725.5032" - }, - { - "field": "p90", - "value": "1792.3" - }, - { - "field": "p99", - "value": "1835.8135" - }, - { - "field": "max", - "value": "1915.31" - } - ] - ], - "statistics": { - "recordsMatched": 2302.0, - "recordsScanned": 9347.0, - "bytesScanned": 3833050.0 - }, - "status": "Complete" -} -{ - "results": [ - [ - { - "field": "coldstart", - "value": "0" - }, - { - "field": "count", - "value": "1736" - }, - { - "field": "p50", - "value": "9.0179" - }, - { - "field": "p90", - "value": "21.3228" - }, - { - "field": "p99", - "value": "36.6573" - }, - { - "field": "max", - "value": "57.18" - } - ], - [ - { - "field": "coldstart", - "value": "1" - }, - { - "field": "count", - "value": "123" - }, - { - "field": "p50", - "value": "1703.2279" - }, - { - "field": "p90", - "value": "1756.8276" - }, - { - "field": "p99", - "value": "1812.1142" - }, - { - "field": "max", - "value": "1845.97" - } - ] - ], - "statistics": { - "recordsMatched": 1859.0, - "recordsScanned": 7270.0, - "bytesScanned": 2523694.0 - }, - "status": "Complete" -} diff --git a/src/NET8/Report/load-test-report-arm64.txt b/src/NET8/Report/load-test-report-arm64.txt deleted file mode 100644 index 6cc0f30..0000000 --- a/src/NET8/Report/load-test-report-arm64.txt +++ /dev/null @@ -1,18 +0,0 @@ -Fri Nov 10 21:22:49 CET 2023 -arm64 RESULTS -Test duration sec: 60 -Log interval min: 20 -Complete -RESULTS coldstart 0 -RESULTS count 1736 -RESULTS p50 9.0179 -RESULTS p90 21.3228 -RESULTS p99 36.6573 -RESULTS max 57.18 -RESULTS coldstart 1 -RESULTS count 123 -RESULTS p50 1703.2279 -RESULTS p90 1756.8276 -RESULTS p99 1812.1142 -RESULTS max 1845.97 -STATISTICS 2523694.0 1859.0 7270.0 diff --git a/src/NET8/Report/load-test-report-x86.json b/src/NET8/Report/load-test-report-x86.json deleted file mode 100644 index 317de0a..0000000 --- a/src/NET8/Report/load-test-report-x86.json +++ /dev/null @@ -1,260 +0,0 @@ -{ - "results": [ - [ - { - "field": "coldstart", - "value": "0" - }, - { - "field": "count", - "value": "3387" - }, - { - "field": "p50", - "value": "5.6355" - }, - { - "field": "p90", - "value": "11.0938" - }, - { - "field": "p99", - "value": "28.4069" - }, - { - "field": "max", - "value": "65.62" - } - ] - ], - "statistics": { - "recordsMatched": 3387.0, - "recordsScanned": 27649.0, - "bytesScanned": 6866946.0 - }, - "status": "Complete" -} - -{ - "results": [ - [ - { - "field": "coldstart", - "value": "0" - }, - { - "field": "count", - "value": "2742" - }, - { - "field": "p50", - "value": "6.9328" - }, - { - "field": "p90", - "value": "14.4304" - }, - { - "field": "p99", - "value": "34.3935" - }, - { - "field": "max", - "value": "54.72" - } - ], - [ - { - "field": "coldstart", - "value": "1" - }, - { - "field": "count", - "value": "445" - }, - { - "field": "p50", - "value": "1452.9626" - }, - { - "field": "p90", - "value": "1509.209" - }, - { - "field": "p99", - "value": "1608.9048" - }, - { - "field": "max", - "value": "1730.88" - } - ] - ], - "statistics": { - "recordsMatched": 3187.0, - "recordsScanned": 12122.0, - "bytesScanned": 4277937.0 - }, - "status": "Complete" -} -{ - "results": [ - [ - { - "field": "coldstart", - "value": "0" - }, - { - "field": "count", - "value": "1010" - }, - { - "field": "p50", - "value": "9.6115" - }, - { - "field": "p90", - "value": "19.072" - }, - { - "field": "p99", - "value": "38.1472" - }, - { - "field": "max", - "value": "56.86" - } - ], - [ - { - "field": "coldstart", - "value": "1" - }, - { - "field": "count", - "value": "141" - }, - { - "field": "p50", - "value": "1457.3259" - }, - { - "field": "p90", - "value": "1503.1872" - }, - { - "field": "p99", - "value": "1556.7031" - }, - { - "field": "max", - "value": "1609.72" - } - ] - ], - "statistics": { - "recordsMatched": 1151.0, - "recordsScanned": 4761.0, - "bytesScanned": 2021156.0 - }, - "status": "Complete" -} - -{ - "results": [ - [ - { - "field": "coldstart", - "value": "0" - }, - { - "field": "count", - "value": "2137" - }, - { - "field": "p50", - "value": "7.3743" - }, - { - "field": "p90", - "value": "9.0232" - }, - { - "field": "p99", - "value": "15.8828" - }, - { - "field": "max", - "value": "30.14" - } - ] - ], - "statistics": { - "recordsMatched": 2137.0, - "recordsScanned": 18984.0, - "bytesScanned": 8138385.0 - }, - "status": "Complete" -} -{ - "results": [ - [ - { - "field": "coldstart", - "value": "0" - }, - { - "field": "count", - "value": "2538" - }, - { - "field": "p50", - "value": "6.9328" - }, - { - "field": "p90", - "value": "14.3159" - }, - { - "field": "p99", - "value": "30.2766" - }, - { - "field": "max", - "value": "66.97" - } - ], - [ - { - "field": "coldstart", - "value": "1" - }, - { - "field": "count", - "value": "441" - }, - { - "field": "p50", - "value": "1471.9649" - }, - { - "field": "p90", - "value": "1527.4194" - }, - { - "field": "p99", - "value": "1701.5263" - }, - { - "field": "max", - "value": "1958.74" - } - ] - ], - "statistics": { - "recordsMatched": 2979.0, - "recordsScanned": 11840.0, - "bytesScanned": 3773751.0 - }, - "status": "Complete" -} diff --git a/src/NET8/Report/load-test-report-x86.txt b/src/NET8/Report/load-test-report-x86.txt deleted file mode 100644 index a772c6d..0000000 --- a/src/NET8/Report/load-test-report-x86.txt +++ /dev/null @@ -1,18 +0,0 @@ -Fri Nov 10 21:20:45 CET 2023 -x86 RESULTS -Test duration sec: 60 -Log interval min: 20 -Complete -RESULTS coldstart 0 -RESULTS count 2538 -RESULTS p50 6.9328 -RESULTS p90 14.3159 -RESULTS p99 30.2766 -RESULTS max 66.97 -RESULTS coldstart 1 -RESULTS count 441 -RESULTS p50 1471.9649 -RESULTS p90 1527.4194 -RESULTS p99 1701.5263 -RESULTS max 1958.74 -STATISTICS 3773751.0 2979.0 11840.0 diff --git a/src/NET8MinimalAPI/Makefile b/src/NET8MinimalAPI/Makefile index 4ec8c4a..ae88de0 100644 --- a/src/NET8MinimalAPI/Makefile +++ b/src/NET8MinimalAPI/Makefile @@ -1,6 +1,4 @@ build-MinimalApiX86: - dotnet clean dotnet publish ApiBootstrap/ApiBootstrap.csproj -c Release -r linux-x64 --self-contained -o $(ARTIFACTS_DIR) build-MinimalApiArm64: - dotnet clean dotnet publish ApiBootstrap/ApiBootstrap.csproj -c Release -r linux-arm64 --self-contained -o $(ARTIFACTS_DIR) \ No newline at end of file diff --git a/src/NET8MinimalAPI/Shared/Shared.csproj b/src/NET8MinimalAPI/Shared/Shared.csproj index 50accf7..7d6a8a2 100644 --- a/src/NET8MinimalAPI/Shared/Shared.csproj +++ b/src/NET8MinimalAPI/Shared/Shared.csproj @@ -1,7 +1,7 @@ - net8.0 + net8.0 enable enable diff --git a/src/NET8Native/Makefile b/src/NET8Native/Makefile index 4850513..1a83e83 100644 --- a/src/NET8Native/Makefile +++ b/src/NET8Native/Makefile @@ -1,23 +1,23 @@ build-GetProductFunctionX86: - dotnet publish GetProduct/GetProduct.csproj -c Release -r linux-x64 --self-contained -o $(ARTIFACTS_DIR) + dotnet publish GetProduct/GetProduct.csproj -c Release -r linux-x64 -o $(ARTIFACTS_DIR) build-GetProductsFunctionX86: - dotnet publish GetProducts/GetProducts.csproj -c Release -r linux-x64 --self-contained -o $(ARTIFACTS_DIR) + dotnet publish GetProducts/GetProducts.csproj -c Release -r linux-x64 -o $(ARTIFACTS_DIR) build-PutProductFunctionX86: - dotnet publish PutProduct/PutProduct.csproj -c Release -r linux-x64 --self-contained -o $(ARTIFACTS_DIR) + dotnet publish PutProduct/PutProduct.csproj -c Release -r linux-x64 -o $(ARTIFACTS_DIR) build-DeleteProductFunctionX86: - dotnet publish DeleteProduct/DeleteProduct.csproj -c Release -r linux-x64 --self-contained -o $(ARTIFACTS_DIR) + dotnet publish DeleteProduct/DeleteProduct.csproj -c Release -r linux-x64 -o $(ARTIFACTS_DIR) build-GetProductFunctionArm64: - dotnet publish GetProduct/GetProduct.csproj -c Release -r linux-arm64 --self-contained -o $(ARTIFACTS_DIR) + dotnet publish GetProduct/GetProduct.csproj -c Release -r linux-arm64 -o $(ARTIFACTS_DIR) build-GetProductsFunctionArm64: - dotnet publish GetProducts/GetProducts.csproj -c Release -r linux-arm64 --self-contained -o $(ARTIFACTS_DIR) + dotnet publish GetProducts/GetProducts.csproj -c Release -r linux-arm64 -o $(ARTIFACTS_DIR) build-PutProductFunctionArm64: - dotnet publish PutProduct/PutProduct.csproj -c Release -r linux-arm64 --self-contained -o $(ARTIFACTS_DIR) + dotnet publish PutProduct/PutProduct.csproj -c Release -r linux-arm64 -o $(ARTIFACTS_DIR) build-DeleteProductFunctionArm64: - dotnet publish DeleteProduct/DeleteProduct.csproj -c Release -r linux-arm64 --self-contained -o $(ARTIFACTS_DIR) \ No newline at end of file + dotnet publish DeleteProduct/DeleteProduct.csproj -c Release -r linux-arm64 -o $(ARTIFACTS_DIR) \ No newline at end of file diff --git a/src/NET8NativeMinimalAPI/ApiBootstrap/ApiBootstrap.csproj b/src/NET8NativeMinimalAPI/ApiBootstrap/ApiBootstrap.csproj index 7e0bdad..facf661 100644 --- a/src/NET8NativeMinimalAPI/ApiBootstrap/ApiBootstrap.csproj +++ b/src/NET8NativeMinimalAPI/ApiBootstrap/ApiBootstrap.csproj @@ -14,13 +14,14 @@ - + - - + + + diff --git a/src/NET8NativeMinimalAPI/ApiBootstrap/Function.cs b/src/NET8NativeMinimalAPI/ApiBootstrap/Function.cs index 0462ff5..10a82a7 100644 --- a/src/NET8NativeMinimalAPI/ApiBootstrap/Function.cs +++ b/src/NET8NativeMinimalAPI/ApiBootstrap/Function.cs @@ -1,5 +1,9 @@ +using System; +using System.Collections.Generic; using System.Net; using System.Text.Json; +using Amazon.CloudWatchLogs; +using Amazon.CloudWatchLogs.Model; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; @@ -9,115 +13,122 @@ using Shared.Models; var app = Startup.Build(args); +var basePath=Environment.GetEnvironmentVariable("DEMO_BASE_PATH"); +if(String.IsNullOrEmpty(basePath)) + app.Logger.LogInformation($"No BASE PATH specified"); +else +{ + app.Logger.LogInformation($"Using BASE PATH:{basePath}"); + app.UsePathBase(basePath); + app.UseRouting(); +} -Handlers.DataAccess = app.Services.GetRequiredService(); -Handlers.Logger = app.Logger; +var dataAccess = app.Services.GetRequiredService(); -app.MapGet("/", Handlers.GetAllProducts); +var cloudWatchClient = new AmazonCloudWatchLogsClient(); -app.MapDelete("/{id}", Handlers.DeleteProduct); +app.MapGet("/", async (HttpContext context) => +{ + app.Logger.LogInformation("Received request to list all products"); -app.MapPut("/{id}", Handlers.PutProduct); + var products = await dataAccess.GetAllProducts(); -app.MapGet("/{id}", Handlers.GetProduct); + app.Logger.LogInformation($"Found {products.Products.Count} products(s)"); -app.Run(); + context.Response.StatusCode = (int) HttpStatusCode.OK; + await context.Response.WriteAsJsonAsync(products); +}); -static class Handlers +app.MapDelete("/{id}", async (HttpContext context) => { - internal static ProductsDAO DataAccess; - internal static ILogger Logger; - - public static async Task GetAllProducts(HttpContext context) + try { - Logger.LogInformation("Received request to list all products"); + var id = context.Request.RouteValues["id"].ToString(); - var products = await DataAccess.GetAllProducts(); + app.Logger.LogInformation($"Received request to delete {id} from the database"); - Logger.LogInformation($"Found {products.Products.Count} products(s)"); + var product = await dataAccess.GetProduct(id); - await context.WriteResponse(HttpStatusCode.OK, products); - } - - public static async Task DeleteProduct(HttpContext context) - { - try + if (product == null) { - var id = context.Request.RouteValues["id"].ToString(); - - Logger.LogInformation($"Received request to delete {id}"); - - var product = await DataAccess.GetProduct(id); + app.Logger.LogWarning($"Id {id} not found."); - if (product == null) - { - Logger.LogWarning($"Id {id} not found."); + context.Response.StatusCode = (int) HttpStatusCode.NotFound; + Results.NotFound(); + return; + } - await context.WriteResponse(HttpStatusCode.NotFound); - - return; - } + app.Logger.LogInformation($"Deleting {product.Name}"); - Logger.LogInformation($"Deleting {product.Name}"); + await dataAccess.DeleteProduct(product.Id); - await DataAccess.DeleteProduct(product.Id); + app.Logger.LogInformation("Delete complete"); - Logger.LogInformation("Delete complete"); - - await context.WriteResponse(HttpStatusCode.OK, $"Product with id {id} deleted"); - } - catch (Exception e) - { - Logger.LogError(e, "Failure deleting product"); + context.Response.StatusCode = (int) HttpStatusCode.OK; + await context.Response.WriteAsJsonAsync($"Product with id {id} deleted"); + } + catch (Exception e) + { + app.Logger.LogError(e, "Failure deleting product"); - await context.WriteResponse(HttpStatusCode.BadRequest); - } + context.Response.StatusCode = (int) HttpStatusCode.NotFound; } +}); - public static async Task GetProduct(HttpContext context) +app.MapPut("/{id}", async (HttpContext context) => +{ + try { var id = context.Request.RouteValues["id"].ToString(); - - Logger.LogInformation($"Received request to get {id}"); - var product = await DataAccess.GetProduct(id); + app.Logger.LogInformation($"Received request to put {id}"); - if (product == null) - { - Logger.LogWarning($"{id} not found"); - await context.WriteResponse(HttpStatusCode.NotFound, $"{id} not found"); - } + var product = await JsonSerializer.DeserializeAsync(context.Request.Body); - await context.WriteResponse(HttpStatusCode.OK, product); - } - - public static async Task PutProduct(HttpContext context) - { - var id = context.Request.RouteValues["id"].ToString(); - var product = await JsonSerializer.DeserializeAsync(context.Request.Body, ApiSerializerContext.Default.Product); - if (product == null || id != product.Id) { - await context.WriteResponse(HttpStatusCode.BadRequest, "Product ID in the body does not match path parameter"); + app.Logger.LogWarning("Product ID in the body does not match path parameter"); + + context.Response.StatusCode = (int) HttpStatusCode.BadRequest; + await context.Response.WriteAsJsonAsync("Product ID in the body does not match path parameter"); + return; } - await DataAccess.PutProduct(product); + app.Logger.LogInformation("Putting product"); - await context.WriteResponse(HttpStatusCode.OK, $"Created product with id {id}"); - } -} + await dataAccess.PutProduct(product); -static class ResponseWriter -{ - public static async Task WriteResponse(this HttpContext context, HttpStatusCode statusCode) + app.Logger.LogTrace("Done"); + + context.Response.StatusCode = (int) HttpStatusCode.OK; + await context.Response.WriteAsJsonAsync($"Created product with id {id}"); + } + catch (Exception e) { - await context.WriteResponse(statusCode, ""); + app.Logger.LogError(e, "Failure deleting product"); + + context.Response.StatusCode = (int) HttpStatusCode.BadRequest; } - - public static async Task WriteResponse(this HttpContext context, HttpStatusCode statusCode, TResponseType body) where TResponseType : class +}); + +app.MapGet("/{id}", async (HttpContext context) => +{ + var id = context.Request.RouteValues["id"].ToString(); + + app.Logger.LogInformation($"Received request to get {id}"); + + var product = await dataAccess.GetProduct(id); + + if (product == null) { - context.Response.StatusCode = (int)statusCode; - context.Response.ContentType = "application/json"; - await context.Response.WriteAsync(JsonSerializer.Serialize(body, typeof(TResponseType), ApiSerializerContext.Default)); + app.Logger.LogWarning($"{id} not found"); + context.Response.StatusCode = (int) HttpStatusCode.NotFound; + Results.NotFound(); + return; } -} \ No newline at end of file + + context.Response.StatusCode = (int) HttpStatusCode.OK; + await context.Response.WriteAsJsonAsync(product); +}); + +app.Run(); \ No newline at end of file diff --git a/src/NET8NativeMinimalAPI/Makefile b/src/NET8NativeMinimalAPI/Makefile index fc76f1e..79df09e 100644 --- a/src/NET8NativeMinimalAPI/Makefile +++ b/src/NET8NativeMinimalAPI/Makefile @@ -1,2 +1,4 @@ -build-ApiFunction: - dotnet publish -c Release -r linux-x64 ./ApiBootstrap/ApiBootstrap.csproj -o $(ARTIFACTS_DIR) \ No newline at end of file +build-MinimalApiX86: + dotnet publish ApiBootstrap/ApiBootstrap.csproj -c Release -r linux-x64 -o $(ARTIFACTS_DIR) +build-MinimalApiArm64: + dotnet publish ApiBootstrap/ApiBootstrap.csproj -c Release -r linux-arm64 -o $(ARTIFACTS_DIR) \ No newline at end of file diff --git a/src/NET8NativeMinimalAPI/Shared/DataAccess/DynamoDbProducts.cs b/src/NET8NativeMinimalAPI/Shared/DataAccess/DynamoDbProducts.cs index e41da85..5f85635 100644 --- a/src/NET8NativeMinimalAPI/Shared/DataAccess/DynamoDbProducts.cs +++ b/src/NET8NativeMinimalAPI/Shared/DataAccess/DynamoDbProducts.cs @@ -1,4 +1,7 @@ -using Amazon.DynamoDBv2; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Amazon.DynamoDBv2; using Amazon.DynamoDBv2.Model; using Shared.Models; diff --git a/src/NET8NativeMinimalAPI/Shared/DataAccess/ProductMapper.cs b/src/NET8NativeMinimalAPI/Shared/DataAccess/ProductMapper.cs index 7abc616..8e073ed 100644 --- a/src/NET8NativeMinimalAPI/Shared/DataAccess/ProductMapper.cs +++ b/src/NET8NativeMinimalAPI/Shared/DataAccess/ProductMapper.cs @@ -1,4 +1,6 @@ -using System.Globalization; +using System; +using System.Collections.Generic; +using System.Globalization; using Amazon.DynamoDBv2.Model; using Shared.Models; diff --git a/src/NET8NativeMinimalAPI/Shared/DataAccess/ProductsDAO.cs b/src/NET8NativeMinimalAPI/Shared/DataAccess/ProductsDAO.cs index c1e13a6..d09decd 100644 --- a/src/NET8NativeMinimalAPI/Shared/DataAccess/ProductsDAO.cs +++ b/src/NET8NativeMinimalAPI/Shared/DataAccess/ProductsDAO.cs @@ -1,4 +1,5 @@ -using Shared.Models; +using System.Threading.Tasks; +using Shared.Models; namespace Shared.DataAccess { diff --git a/src/NET8NativeMinimalAPI/Shared/Models/Product.cs b/src/NET8NativeMinimalAPI/Shared/Models/Product.cs index 88a62ca..93f9896 100644 --- a/src/NET8NativeMinimalAPI/Shared/Models/Product.cs +++ b/src/NET8NativeMinimalAPI/Shared/Models/Product.cs @@ -1,4 +1,6 @@ -namespace Shared.Models +using System; + +namespace Shared.Models { public class Product { @@ -17,7 +19,12 @@ public Product(string id, string name, decimal price) public string Name { get; set; } - public decimal Price { get; set; } + public decimal Price { get; private set; } + + public void SetPrice(decimal newPrice) + { + this.Price = Math.Round(newPrice, 2); + } public override string ToString() { diff --git a/src/NET8NativeMinimalAPI/Shared/Models/ProductWrapper.cs b/src/NET8NativeMinimalAPI/Shared/Models/ProductWrapper.cs index 47cc485..c89af93 100644 --- a/src/NET8NativeMinimalAPI/Shared/Models/ProductWrapper.cs +++ b/src/NET8NativeMinimalAPI/Shared/Models/ProductWrapper.cs @@ -1,4 +1,6 @@ -namespace Shared.Models +using System.Collections.Generic; + +namespace Shared.Models { public class ProductWrapper { diff --git a/src/NET8NativeMinimalAPI/Shared/Shared.csproj b/src/NET8NativeMinimalAPI/Shared/Shared.csproj index 8361c85..7d6a8a2 100644 --- a/src/NET8NativeMinimalAPI/Shared/Shared.csproj +++ b/src/NET8NativeMinimalAPI/Shared/Shared.csproj @@ -7,8 +7,8 @@ - - + + diff --git a/src/NET8NativeMinimalAPI/Shared/Startup.cs b/src/NET8NativeMinimalAPI/Shared/Startup.cs index 3926dc3..ef2b0db 100644 --- a/src/NET8NativeMinimalAPI/Shared/Startup.cs +++ b/src/NET8NativeMinimalAPI/Shared/Startup.cs @@ -1,6 +1,7 @@ using System.Text.Json; using Amazon.Lambda.Serialization.SystemTextJson; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Shared.DataAccess; @@ -33,11 +34,7 @@ public static WebApplication Build(string[] args) options.TimestampFormat = "hh:mm:ss "; }); - var app = builder.Build(); - - // Add generic app configuration here. - - return app; + return builder.Build(); } } } \ No newline at end of file diff --git a/src/NET8NativeMinimalAPI/deploy.sh b/src/NET8NativeMinimalAPI/deploy.sh new file mode 100755 index 0000000..7da02c0 --- /dev/null +++ b/src/NET8NativeMinimalAPI/deploy.sh @@ -0,0 +1,43 @@ +#Arguments: +#$1 - delete stack formation to ensure that all test have same conditon and favour similar ammount of cold start events + +STACK_NAME=dotnet8-native-minimal-api +DELETE_STACK=yes + +COLOR='\033[0;33m' +NO_COLOR='\033[0m' # No Color + +if [ "x$4" != x ]; +then + DELETE_STACK=$1 +fi + +echo "${COLOR}" +echo -------------------------------------------- +echo DELETE_STACK: $DELETE_STACK +echo -------------------------------------------- +echo "${NO_COLOR}" + +if [ $DELETE_STACK == "yes" ]; +then + echo "${COLOR}" + echo -------------------------------------------- + echo DELETING STACK $STACK_NAME + echo -------------------------------------------- + echo "${NO_COLOR}" + aws cloudformation delete-stack --stack-name $STACK_NAME + echo "${COLOR}" + echo --------------------------------------------- + echo Waiting stack to be deleted + echo -------------------------------------------- + echo "${NO_COLOR}" + aws cloudformation wait stack-delete-complete --stack-name $STACK_NAME + echo "${COLOR}" + echo --------------------------------------------- + echo Stack deleted + echo -------------------------------------------- + echo "${NO_COLOR}" +fi + +sam build +sam deploy --stack-name dotnet8-minimal-api --resolve-s3 --s3-prefix dotnet8-minimal-api --no-confirm-changeset --no-fail-on-empty-changeset --capabilities CAPABILITY_IAM \ No newline at end of file diff --git a/src/NET8NativeMinimalAPI/run-loadtest.sh b/src/NET8NativeMinimalAPI/run-loadtest.sh new file mode 100755 index 0000000..8e7408e --- /dev/null +++ b/src/NET8NativeMinimalAPI/run-loadtest.sh @@ -0,0 +1,123 @@ +#Arguments: +#$1 - load test duration in seconds +#$2 - log interval to be used in the cloudwatch query in minutes +#$3 - when equal to 1 cloudwatch log group will be deleted to ensure that only logs of the load test will be evaluated for stat + +STACK_NAME=dotnet8-native-minimal-api +TEST_DURATIOMN_SEC=60 +LOG_INTERVAL_MIN=20 +LOG_DELETE=yes + +COLOR='\033[0;33m' +NO_COLOR='\033[0m' # No Color + +if [ "x$1" != x ]; +then + TEST_DURATIOMN_SEC=$1 +fi + +if [ "x$2" != x ]; +then + LOG_INTERVAL_MIN=$2 +fi + +if [ "x$3" != x ]; +then + LOG_DELETE=$3 +fi + +echo "${COLOR}" +echo -------------------------------------------- +echo DURATION:$TEST_DURATIOMN_SEC +echo LOG INTERVAL:$LOG_INTERVAL_MIN +echo LOG_DELETE: $LOG_DELETE +echo -------------------------------------------- +echo "${NO_COLOR}" + +mkdir -p Report + +function RunLoadTest() +{ + #Params: + #$1 - Architecture (x86 or arm64).Used for logging and naming report file + #$2 - Stack output name to get API Url + #$3 - Stack output name to get lambda name + + #get test params from cloud formation output + echo "${COLOR}" + export API_URL=$(aws cloudformation describe-stacks --stack-name $STACK_NAME \ + --query "Stacks[0].Outputs[?OutputKey=='$2'].OutputValue" \ + --output text) + echo API URL: $API_URL + + LAMBDA=$(aws cloudformation describe-stacks --stack-name $STACK_NAME \ + --query "Stacks[0].Outputs[?OutputKey=='$3'].OutputValue" \ + --output text) + echo LAMBDA: $LAMBDA + + if [ $LOG_DELETE == "yes" ]; + then + echo -------------------------------------------- + echo DELETING CLOUDWATCH LOG GROUP /aws/lambda/$LAMBDA + echo -------------------------------------------- + aws logs delete-log-group --log-group-name /aws/lambda/$LAMBDA + echo --------------------------------------------- + echo Waiting 10 sec. for deletion to complete + echo -------------------------------------------- + sleep 10 + fi + + #run load test with artillery + echo -------------------------------------------- + echo $1 RUNNING LOAD TEST $TEST_DURATIOMN_SEC sec $LAMBDA: $API_URL + echo -------------------------------------------- + echo "${NO_COLOR}" + artillery run \ + --overrides '{"config": { "phases": [{ "duration": '$TEST_DURATIOMN_SEC', "arrivalRate": 100 }] } }' \ + --quiet \ + ../../loadtest/load-test.yml + + echo "${COLOR}" + echo -------------------------------------------- + echo Waiting 10 sec. for logs to consolidate + echo -------------------------------------------- + sleep 10 + + #get stats from cloudwatch + enddate=$(date "+%s") + startdate=$(($enddate-($LOG_INTERVAL_MIN*60))) + echo -------------------------------------------- + echo Log start:$startdate end:$enddate + echo -------------------------------------------- + + QUERY_ID=$(aws logs start-query \ + --log-group-name /aws/lambda/$LAMBDA \ + --start-time $startdate \ + --end-time $enddate \ + --query-string 'filter @type="REPORT" | fields greatest(@initDuration, 0) + @duration as duration, ispresent(@initDuration) as coldstart | stats count(*) as count, pct(duration, 50) as p50, pct(duration, 90) as p90, pct(duration, 99) as p99, max(duration) as max by coldstart' \ + | jq -r '.queryId') + + echo -------------------------------------------- + echo Query started, id: $QUERY_ID + echo -------------------------------------------- + + echo --------------------------------------------- + echo Waiting 10 sec. for cloudwatch query to complete + echo -------------------------------------------- + sleep 10 + + echo -------------------------------------------- + echo RESULTS $LAMBDA + echo -------------------------------------------- + echo "${NO_COLOR}" + date > ./Report/load-test-report-$1.txt + echo $1 RESULTS lambda: $LAMBDA >> ./Report/load-test-report-$1.txt + echo Test duration sec: $TEST_DURATIOMN_SEC >> ./Report/load-test-report-$1.txt + echo Log interval min: $LOG_INTERVAL_MIN >> ./Report/load-test-report-$1.txt + aws logs get-query-results --query-id $QUERY_ID --output text >> ./Report/load-test-report-$1.txt + cat ./Report/load-test-report-$1.txt + aws logs get-query-results --query-id $QUERY_ID --output json >> ./Report/load-test-report-$1.json +} + +RunLoadTest x86 ApiUrlX86 LambdaX86Name +RunLoadTest arm64 ApiUrlArm64 LambdaArm64Name \ No newline at end of file diff --git a/src/NET8NativeMinimalAPI/template.yaml b/src/NET8NativeMinimalAPI/template.yaml index db2eb8e..abb40f5 100644 --- a/src/NET8NativeMinimalAPI/template.yaml +++ b/src/NET8NativeMinimalAPI/template.yaml @@ -1,47 +1,67 @@ AWSTemplateFormatVersion: "2010-09-09" Transform: AWS::Serverless-2016-10-31 +Parameters: + x86FunctionName: + Type: String + Default: Net8-MinimalApi-X86 + arm64FunctionName: + Type: String + Default: Net8-MinimalApi-Arm64 + Globals: Function: MemorySize: 1024 - Architectures: ["x86_64"] Runtime: provided.al2 Timeout: 30 Tracing: Active Environment: Variables: PRODUCT_TABLE_NAME: !Ref Table - LOG_GROUP_PREFIX: !Sub "/aws/lambda/net-8-aot-minimal-" - LOAD_TEST_TYPE: "NET 8 native AOT Minimal API" Resources: - ApiFunction: + MinimalApiX86: + Type: AWS::Serverless::Function + Properties: + FunctionName: !Ref x86FunctionName + Architectures: [x86_64] + CodeUri: ./ + Handler: ApiBootstrap + Environment: + Variables: + DEMO_BASE_PATH: /x86 + Events: + Api: + Type: HttpApi + Properties: + Path: /x86/{proxy+} + Method: ANY + Policies: + - DynamoDBCrudPolicy: + TableName: + !Ref Table + Metadata: + BuildMethod: makefile + MinimalApiArm64: Type: AWS::Serverless::Function Properties: - CodeUri: . + FunctionName: !Ref arm64FunctionName + Architectures: [arm64] + CodeUri: ./ Handler: ApiBootstrap + Environment: + Variables: + DEMO_BASE_PATH: /arm64 Events: Api: Type: HttpApi Properties: - Path: /{proxy+} + Path: /arm64/{proxy+} Method: ANY Policies: - DynamoDBCrudPolicy: TableName: !Ref Table - - Version: "2012-10-17" - Statement: - - Sid: AllowStartQueries - Effect: Allow - Action: - - logs:DescribeLogGroups - - logs:StartQuery - Resource: "*" - - Sid: AllowGetQueryResults - Effect: Allow - Action: logs:GetQueryResults - Resource: "*" Metadata: BuildMethod: makefile @@ -55,8 +75,22 @@ Resources: KeySchema: - AttributeName: id KeyType: HASH + StreamSpecification: + StreamViewType: NEW_AND_OLD_IMAGES Outputs: ApiUrl: Description: "API Gateway endpoint URL" - Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/" \ No newline at end of file + Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com" + LambdaX86Name: + Description: "Lambda X86 Name" + Value: !Ref x86FunctionName + ApiUrlX86: + Description: "X86 API endpoint URL" + Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/x86" + LambdaArm64Name: + Description: "Lambda Arm64 Name" + Value: !Ref arm64FunctionName + ApiUrlArm64: + Description: "Arm64 GateAPI endpoint URL" + Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/arm64" \ No newline at end of file From 34efac06240ce0a679e36ddc3af3cd18162d013c Mon Sep 17 00:00:00 2001 From: Riccardo Venturi Date: Sat, 11 Nov 2023 09:10:44 +0100 Subject: [PATCH 14/32] testing net8native --- loadtest/codebuild/run-all-load-tests.sh | 11 +++++++++++ src/NET8Native/deploy.sh | 2 +- src/NET8Native/run-loadtest.sh | 2 +- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/loadtest/codebuild/run-all-load-tests.sh b/loadtest/codebuild/run-all-load-tests.sh index 0156363..ac3efce 100755 --- a/loadtest/codebuild/run-all-load-tests.sh +++ b/loadtest/codebuild/run-all-load-tests.sh @@ -71,3 +71,14 @@ else source ./deploy.sh $DELETE_STACK source ./run-loadtest.sh $TEST_DURATIOMN_SEC $LOG_INTERVAL_MIN $LOG_DELETE fi + +#export LT_NET8_NATIVE=1 +if [ "$LT_NET8_NATIVE" != yes ]; +then + echo SKIPPING net8 native :$LT_NET8_NATIVE +else + echo "RUNNING load test for net8 native" + cd ../../src/NET8Native/ + source ./deploy.sh $DELETE_STACK + source ./run-loadtest.sh $TEST_DURATIOMN_SEC $LOG_INTERVAL_MIN $LOG_DELETE +fi diff --git a/src/NET8Native/deploy.sh b/src/NET8Native/deploy.sh index 9aae7f5..924d77c 100755 --- a/src/NET8Native/deploy.sh +++ b/src/NET8Native/deploy.sh @@ -1,7 +1,7 @@ #Arguments: #$1 - delete stack formation to ensure that all test have same conditon and favour similar ammount of cold start events -STACK_NAME=dotnet8 +STACK_NAME=dotnet8-native DELETE_STACK=yes COLOR='\033[0;33m' diff --git a/src/NET8Native/run-loadtest.sh b/src/NET8Native/run-loadtest.sh index 2391e84..ab8f5a8 100755 --- a/src/NET8Native/run-loadtest.sh +++ b/src/NET8Native/run-loadtest.sh @@ -3,7 +3,7 @@ #$2 - log interval to be used in the cloudwatch query in minutes #$3 - when equal to 1 cloudwatch log group will be deleted to ensure that only logs of the load test will be evaluated for stat -STACK_NAME=dotnet8 +STACK_NAME=dotnet8-native TEST_DURATIOMN_SEC=60 LOG_INTERVAL_MIN=20 LOG_DELETE=yes From 197715b8ac6f9577ca4fb79f577125dbc4786bf2 Mon Sep 17 00:00:00 2001 From: Riccardo Venturi Date: Sat, 11 Nov 2023 09:19:08 +0100 Subject: [PATCH 15/32] . --- src/NET8Native/Makefile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/NET8Native/Makefile b/src/NET8Native/Makefile index 1a83e83..655de9c 100644 --- a/src/NET8Native/Makefile +++ b/src/NET8Native/Makefile @@ -11,13 +11,13 @@ build-DeleteProductFunctionX86: dotnet publish DeleteProduct/DeleteProduct.csproj -c Release -r linux-x64 -o $(ARTIFACTS_DIR) build-GetProductFunctionArm64: - dotnet publish GetProduct/GetProduct.csproj -c Release -r linux-arm64 -o $(ARTIFACTS_DIR) + dotnet publish GetProduct/GetProduct.csproj -c Release -r linux-x64 -o $(ARTIFACTS_DIR) build-GetProductsFunctionArm64: - dotnet publish GetProducts/GetProducts.csproj -c Release -r linux-arm64 -o $(ARTIFACTS_DIR) + dotnet publish GetProducts/GetProducts.csproj -c Release -r linux-x64 -o $(ARTIFACTS_DIR) build-PutProductFunctionArm64: - dotnet publish PutProduct/PutProduct.csproj -c Release -r linux-arm64 -o $(ARTIFACTS_DIR) + dotnet publish PutProduct/PutProduct.csproj -c Release -r linux-x64 -o $(ARTIFACTS_DIR) build-DeleteProductFunctionArm64: - dotnet publish DeleteProduct/DeleteProduct.csproj -c Release -r linux-arm64 -o $(ARTIFACTS_DIR) \ No newline at end of file + dotnet publish DeleteProduct/DeleteProduct.csproj -c Release -r linux-x64 -o $(ARTIFACTS_DIR) \ No newline at end of file From ee76535409491a47c891604ce649a15c045e53c5 Mon Sep 17 00:00:00 2001 From: Riccardo Venturi Date: Sat, 11 Nov 2023 09:28:06 +0100 Subject: [PATCH 16/32] . --- loadtest/codebuild/run-all-load-tests.sh | 23 +++++++++++++++-------- src/NET8NativeMinimalAPI/Makefile | 2 +- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/loadtest/codebuild/run-all-load-tests.sh b/loadtest/codebuild/run-all-load-tests.sh index ac3efce..931176b 100755 --- a/loadtest/codebuild/run-all-load-tests.sh +++ b/loadtest/codebuild/run-all-load-tests.sh @@ -32,7 +32,7 @@ echo -------------------------------------------- if [ "$LT_NET6_MINIMAL_API" != yes ]; then - echo SKIPPING net6 minimal api :$LT_NET6_MINIMAL_API + echo SKIPPING net6 minimal api - LT_NET6_MINIMAL_API=$LT_NET6_MINIMAL_API else echo "RUNNING load test for net6 minimal api" cd ../../src/NET6MinimalAPI/ @@ -42,7 +42,7 @@ fi if [ "$LT_NET6_MINIMAL_API_WEB_ADAPTER" != yes ]; then - echo SKIPPING net6 minimal api web adapter :$LT_NET6_MINIMAL_API_WEB_ADAPTER + echo SKIPPING net6 minimal api web adapter - LT_NET6_MINIMAL_API_WEB_ADAPTER = $LT_NET6_MINIMAL_API_WEB_ADAPTER else echo "RUNNING load test for net6 minimal api web adapter" cd ../../src/NET6MinimalAPIWebAdapter/ @@ -50,10 +50,9 @@ else source ./run-loadtest.sh $TEST_DURATIOMN_SEC $LOG_INTERVAL_MIN $LOG_DELETE fi -#export LT_NET8_MINIMAL_API=1 if [ "$LT_NET8" != yes ]; then - echo SKIPPING net8 :$LT_NET8 + echo SKIPPING net8 - LT_NET8=$LT_NET8 else echo "RUNNING load test for net8" cd ../../src/NET8/ @@ -61,10 +60,9 @@ else source ./run-loadtest.sh $TEST_DURATIOMN_SEC $LOG_INTERVAL_MIN $LOG_DELETE fi -#export LT_NET8_MINIMAL_API=1 if [ "$LT_NET8_MINIMAL_API" != yes ]; then - echo SKIPPING net8 minimal api :$LT_NET8_MINIMAL_API + echo SKIPPING net8 minimal api - LT_NET8_MINIMAL_API=$LT_NET8_MINIMAL_API else echo "RUNNING load test for net8 minimal api" cd ../../src/NET8MinimalAPI/ @@ -72,13 +70,22 @@ else source ./run-loadtest.sh $TEST_DURATIOMN_SEC $LOG_INTERVAL_MIN $LOG_DELETE fi -#export LT_NET8_NATIVE=1 if [ "$LT_NET8_NATIVE" != yes ]; then - echo SKIPPING net8 native :$LT_NET8_NATIVE + echo SKIPPING net8 native - LT_NET8_NATIVE=$LT_NET8_NATIVE else echo "RUNNING load test for net8 native" cd ../../src/NET8Native/ source ./deploy.sh $DELETE_STACK source ./run-loadtest.sh $TEST_DURATIOMN_SEC $LOG_INTERVAL_MIN $LOG_DELETE fi + +if [ "$LT_NET8_NATIVE_MINIMAL_API" != yes ]; +then + echo SKIPPING net8 native minimal api LT_NET8_NATIVE_MINIMAL_API=$LT_NET8_NATIVE_MINIMAL_API +else + echo "RUNNING load test for net8 native minimal api" + cd ../../src/NET8NativeMinimalApi/ + source ./deploy.sh $DELETE_STACK + source ./run-loadtest.sh $TEST_DURATIOMN_SEC $LOG_INTERVAL_MIN $LOG_DELETE +fi \ No newline at end of file diff --git a/src/NET8NativeMinimalAPI/Makefile b/src/NET8NativeMinimalAPI/Makefile index 79df09e..5b5c4f6 100644 --- a/src/NET8NativeMinimalAPI/Makefile +++ b/src/NET8NativeMinimalAPI/Makefile @@ -1,4 +1,4 @@ build-MinimalApiX86: dotnet publish ApiBootstrap/ApiBootstrap.csproj -c Release -r linux-x64 -o $(ARTIFACTS_DIR) build-MinimalApiArm64: - dotnet publish ApiBootstrap/ApiBootstrap.csproj -c Release -r linux-arm64 -o $(ARTIFACTS_DIR) \ No newline at end of file + dotnet publish ApiBootstrap/ApiBootstrap.csproj -c Release -r linux-x64 -o $(ARTIFACTS_DIR) \ No newline at end of file From d7e6d1ad914738d99d40e6020af66f362e2cbc94 Mon Sep 17 00:00:00 2001 From: Riccardo Venturi Date: Sat, 11 Nov 2023 09:43:12 +0100 Subject: [PATCH 17/32] . --- src/NET8Native/template.yaml | 4 ++-- src/NET8NativeMinimalAPI/template.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/NET8Native/template.yaml b/src/NET8Native/template.yaml index 0e49fe0..b439ecc 100644 --- a/src/NET8Native/template.yaml +++ b/src/NET8Native/template.yaml @@ -4,10 +4,10 @@ Transform: AWS::Serverless-2016-10-31 Parameters: x86FunctionNamePrefix: Type: String - Default: Net8-X86 + Default: Net8-Native-X86 arm64FunctionNamePrefix: Type: String - Default: Net8-Arm64 + Default: Net8-Native-Arm64 Globals: Function: diff --git a/src/NET8NativeMinimalAPI/template.yaml b/src/NET8NativeMinimalAPI/template.yaml index abb40f5..3dbb403 100644 --- a/src/NET8NativeMinimalAPI/template.yaml +++ b/src/NET8NativeMinimalAPI/template.yaml @@ -4,10 +4,10 @@ Transform: AWS::Serverless-2016-10-31 Parameters: x86FunctionName: Type: String - Default: Net8-MinimalApi-X86 + Default: Net8-Native-MinimalApi-X86 arm64FunctionName: Type: String - Default: Net8-MinimalApi-Arm64 + Default: Net8-Native-MinimalApi-Arm64 Globals: Function: From bb4c77072b57cbcc7609fd1c5f5dcad24b0c0756 Mon Sep 17 00:00:00 2001 From: Riccardo Venturi Date: Sun, 12 Nov 2023 21:06:23 +0100 Subject: [PATCH 18/32] fixing NET8 Native --- .../aws-lambda-tools-defaults.json | 18 --- .../GetProduct/aws-lambda-tools-defaults.json | 18 --- .../aws-lambda-tools-defaults.json | 18 --- .../PutProduct/aws-lambda-tools-defaults.json | 15 --- .../Report/load-test-report-arm64.json | 62 ---------- .../Report/load-test-report-arm64.txt | 18 --- .../Report/load-test-report-x86.json | 62 ---------- .../Report/load-test-report-x86.txt | 18 --- .../Report/load-test-report-arm64.json | 62 ---------- .../Report/load-test-report-arm64.txt | 18 --- .../Report/load-test-report-x86.json | 62 ---------- .../Report/load-test-report-x86.txt | 18 --- .../aws-lambda-tools-defaults.json | 18 --- .../GetProduct/aws-lambda-tools-defaults.json | 18 --- .../aws-lambda-tools-defaults.json | 18 --- .../PutProduct/aws-lambda-tools-defaults.json | 18 --- .../aws-lambda-tools-defaults.json | 18 --- .../GetProduct/aws-lambda-tools-defaults.json | 18 --- .../aws-lambda-tools-defaults.json | 18 --- .../PutProduct/aws-lambda-tools-defaults.json | 15 --- .../Report/load-test-report-arm64.json | 62 ---------- .../Report/load-test-report-arm64.txt | 18 --- .../Report/load-test-report-x86.json | 62 ---------- .../Report/load-test-report-x86.txt | 18 --- .../DeleteProduct/DeleteProduct.csproj | 14 ++- .../aws-lambda-tools-defaults.json | 18 --- src/NET8Native/GetProduct/GetProduct.csproj | 16 +-- .../GetProduct/aws-lambda-tools-defaults.json | 18 --- src/NET8Native/GetProducts/GetProducts.csproj | 15 ++- .../aws-lambda-tools-defaults.json | 18 --- src/NET8Native/Makefile | 20 +--- src/NET8Native/PutProduct/PutProduct.csproj | 14 ++- .../PutProduct/aws-lambda-tools-defaults.json | 15 --- .../Shared/JsonSerializerContext.cs | 3 +- src/NET8Native/Shared/Shared.csproj | 17 ++- src/NET8Native/deploy.sh | 2 +- src/NET8Native/run-loadtest.sh | 3 +- src/NET8Native/template.yaml | 112 +----------------- 38 files changed, 53 insertions(+), 922 deletions(-) delete mode 100644 src/NET6CustomRuntime/DeleteProduct/aws-lambda-tools-defaults.json delete mode 100644 src/NET6CustomRuntime/GetProduct/aws-lambda-tools-defaults.json delete mode 100644 src/NET6CustomRuntime/GetProducts/aws-lambda-tools-defaults.json delete mode 100644 src/NET6CustomRuntime/PutProduct/aws-lambda-tools-defaults.json delete mode 100644 src/NET6MinimalAPI/Report/load-test-report-arm64.json delete mode 100644 src/NET6MinimalAPI/Report/load-test-report-arm64.txt delete mode 100644 src/NET6MinimalAPI/Report/load-test-report-x86.json delete mode 100644 src/NET6MinimalAPI/Report/load-test-report-x86.txt delete mode 100644 src/NET6MinimalAPIWebAdapter/Report/load-test-report-arm64.json delete mode 100644 src/NET6MinimalAPIWebAdapter/Report/load-test-report-arm64.txt delete mode 100644 src/NET6MinimalAPIWebAdapter/Report/load-test-report-x86.json delete mode 100644 src/NET6MinimalAPIWebAdapter/Report/load-test-report-x86.txt delete mode 100755 src/NET6Native/DeleteProduct/aws-lambda-tools-defaults.json delete mode 100755 src/NET6Native/GetProduct/aws-lambda-tools-defaults.json delete mode 100755 src/NET6Native/GetProducts/aws-lambda-tools-defaults.json delete mode 100755 src/NET6Native/PutProduct/aws-lambda-tools-defaults.json delete mode 100644 src/NET8/DeleteProduct/aws-lambda-tools-defaults.json delete mode 100644 src/NET8/GetProduct/aws-lambda-tools-defaults.json delete mode 100644 src/NET8/GetProducts/aws-lambda-tools-defaults.json delete mode 100644 src/NET8/PutProduct/aws-lambda-tools-defaults.json delete mode 100644 src/NET8MinimalAPI/Report/load-test-report-arm64.json delete mode 100644 src/NET8MinimalAPI/Report/load-test-report-arm64.txt delete mode 100644 src/NET8MinimalAPI/Report/load-test-report-x86.json delete mode 100644 src/NET8MinimalAPI/Report/load-test-report-x86.txt delete mode 100644 src/NET8Native/DeleteProduct/aws-lambda-tools-defaults.json delete mode 100644 src/NET8Native/GetProduct/aws-lambda-tools-defaults.json delete mode 100644 src/NET8Native/GetProducts/aws-lambda-tools-defaults.json delete mode 100644 src/NET8Native/PutProduct/aws-lambda-tools-defaults.json diff --git a/src/NET6CustomRuntime/DeleteProduct/aws-lambda-tools-defaults.json b/src/NET6CustomRuntime/DeleteProduct/aws-lambda-tools-defaults.json deleted file mode 100644 index 1d112e0..0000000 --- a/src/NET6CustomRuntime/DeleteProduct/aws-lambda-tools-defaults.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "Information" : [ - "This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.", - "To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.", - - "dotnet lambda help", - - "All the command line options for the Lambda command can be specified in this file." - ], - - "profile":"", - "region" : "", - "configuration": "Release", - "function-runtime":"provided.al2", - "function-memory-size" : 256, - "function-timeout" : 30, - "function-handler" : "CancelBooking" -} diff --git a/src/NET6CustomRuntime/GetProduct/aws-lambda-tools-defaults.json b/src/NET6CustomRuntime/GetProduct/aws-lambda-tools-defaults.json deleted file mode 100644 index 1d112e0..0000000 --- a/src/NET6CustomRuntime/GetProduct/aws-lambda-tools-defaults.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "Information" : [ - "This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.", - "To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.", - - "dotnet lambda help", - - "All the command line options for the Lambda command can be specified in this file." - ], - - "profile":"", - "region" : "", - "configuration": "Release", - "function-runtime":"provided.al2", - "function-memory-size" : 256, - "function-timeout" : 30, - "function-handler" : "CancelBooking" -} diff --git a/src/NET6CustomRuntime/GetProducts/aws-lambda-tools-defaults.json b/src/NET6CustomRuntime/GetProducts/aws-lambda-tools-defaults.json deleted file mode 100644 index 1d112e0..0000000 --- a/src/NET6CustomRuntime/GetProducts/aws-lambda-tools-defaults.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "Information" : [ - "This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.", - "To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.", - - "dotnet lambda help", - - "All the command line options for the Lambda command can be specified in this file." - ], - - "profile":"", - "region" : "", - "configuration": "Release", - "function-runtime":"provided.al2", - "function-memory-size" : 256, - "function-timeout" : 30, - "function-handler" : "CancelBooking" -} diff --git a/src/NET6CustomRuntime/PutProduct/aws-lambda-tools-defaults.json b/src/NET6CustomRuntime/PutProduct/aws-lambda-tools-defaults.json deleted file mode 100644 index 7ed1688..0000000 --- a/src/NET6CustomRuntime/PutProduct/aws-lambda-tools-defaults.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "Information": [ - "This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.", - "To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.", - "dotnet lambda help", - "All the command line options for the Lambda command can be specified in this file." - ], - "profile": "", - "region": "", - "configuration": "Release", - "function-runtime": "provided.al2", - "function-memory-size": 256, - "function-timeout": 30, - "function-handler": "bootstrap" -} \ No newline at end of file diff --git a/src/NET6MinimalAPI/Report/load-test-report-arm64.json b/src/NET6MinimalAPI/Report/load-test-report-arm64.json deleted file mode 100644 index 005bf4d..0000000 --- a/src/NET6MinimalAPI/Report/load-test-report-arm64.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "results": [ - [ - { - "field": "coldstart", - "value": "0" - }, - { - "field": "count", - "value": "135963" - }, - { - "field": "p50", - "value": "6.2055" - }, - { - "field": "p90", - "value": "9.6783" - }, - { - "field": "p99", - "value": "20.0868" - }, - { - "field": "max", - "value": "528.13" - } - ], - [ - { - "field": "coldstart", - "value": "1" - }, - { - "field": "count", - "value": "246" - }, - { - "field": "p50", - "value": "2105.2185" - }, - { - "field": "p90", - "value": "2164.9673" - }, - { - "field": "p99", - "value": "2215.3132" - }, - { - "field": "max", - "value": "2228.18" - } - ] - ], - "statistics": { - "recordsMatched": 136209.0, - "recordsScanned": 1226110.0, - "bytesScanned": 900312482.0 - }, - "status": "Complete" -} diff --git a/src/NET6MinimalAPI/Report/load-test-report-arm64.txt b/src/NET6MinimalAPI/Report/load-test-report-arm64.txt deleted file mode 100644 index 4ae278b..0000000 --- a/src/NET6MinimalAPI/Report/load-test-report-arm64.txt +++ /dev/null @@ -1,18 +0,0 @@ -Sun Nov 5 18:51:08 UTC 2023 -arm64 RESULTS lambda: Net6-MinimalApi-Arm64 -Test duration sec: 600 -Log interval min: 20 -Complete -RESULTS coldstart 0 -RESULTS count 135963 -RESULTS p50 6.2055 -RESULTS p90 9.6783 -RESULTS p99 20.0868 -RESULTS max 528.13 -RESULTS coldstart 1 -RESULTS count 246 -RESULTS p50 2105.2185 -RESULTS p90 2164.9673 -RESULTS p99 2215.3132 -RESULTS max 2228.18 -STATISTICS 900312482.0 136209.0 1226110.0 diff --git a/src/NET6MinimalAPI/Report/load-test-report-x86.json b/src/NET6MinimalAPI/Report/load-test-report-x86.json deleted file mode 100644 index d2d2920..0000000 --- a/src/NET6MinimalAPI/Report/load-test-report-x86.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "results": [ - [ - { - "field": "coldstart", - "value": "0" - }, - { - "field": "count", - "value": "135861" - }, - { - "field": "p50", - "value": "5.9169" - }, - { - "field": "p90", - "value": "9.9905" - }, - { - "field": "p99", - "value": "21.746" - }, - { - "field": "max", - "value": "108.6" - } - ], - [ - { - "field": "coldstart", - "value": "1" - }, - { - "field": "count", - "value": "197" - }, - { - "field": "p50", - "value": "1742.8361" - }, - { - "field": "p90", - "value": "1966.8893" - }, - { - "field": "p99", - "value": "2411.7468" - }, - { - "field": "max", - "value": "2503.31" - } - ] - ], - "statistics": { - "recordsMatched": 136058.0, - "recordsScanned": 1224739.0, - "bytesScanned": 894477163.0 - }, - "status": "Complete" -} diff --git a/src/NET6MinimalAPI/Report/load-test-report-x86.txt b/src/NET6MinimalAPI/Report/load-test-report-x86.txt deleted file mode 100644 index 21b5798..0000000 --- a/src/NET6MinimalAPI/Report/load-test-report-x86.txt +++ /dev/null @@ -1,18 +0,0 @@ -Sun Nov 5 18:40:27 UTC 2023 -x86 RESULTS lambda: Net6-MinimalApi-X86 -Test duration sec: 600 -Log interval min: 20 -Complete -RESULTS coldstart 0 -RESULTS count 135861 -RESULTS p50 5.9169 -RESULTS p90 9.9905 -RESULTS p99 21.746 -RESULTS max 108.6 -RESULTS coldstart 1 -RESULTS count 197 -RESULTS p50 1742.8361 -RESULTS p90 1966.8893 -RESULTS p99 2411.7468 -RESULTS max 2503.31 -STATISTICS 894477163.0 136058.0 1224739.0 diff --git a/src/NET6MinimalAPIWebAdapter/Report/load-test-report-arm64.json b/src/NET6MinimalAPIWebAdapter/Report/load-test-report-arm64.json deleted file mode 100644 index be65931..0000000 --- a/src/NET6MinimalAPIWebAdapter/Report/load-test-report-arm64.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "results": [ - [ - { - "field": "coldstart", - "value": "0" - }, - { - "field": "count", - "value": "138527" - }, - { - "field": "p50", - "value": "6.1078" - }, - { - "field": "p90", - "value": "9.3759" - }, - { - "field": "p99", - "value": "17.9744" - }, - { - "field": "max", - "value": "838.78" - } - ], - [ - { - "field": "coldstart", - "value": "1" - }, - { - "field": "count", - "value": "139" - }, - { - "field": "p50", - "value": "1277.1986" - }, - { - "field": "p90", - "value": "1326.6409" - }, - { - "field": "p99", - "value": "1358.8491" - }, - { - "field": "max", - "value": "1367.49" - } - ] - ], - "statistics": { - "recordsMatched": 138666.0, - "recordsScanned": 694392.0, - "bytesScanned": 268615241.0 - }, - "status": "Complete" -} diff --git a/src/NET6MinimalAPIWebAdapter/Report/load-test-report-arm64.txt b/src/NET6MinimalAPIWebAdapter/Report/load-test-report-arm64.txt deleted file mode 100644 index 6df5dbc..0000000 --- a/src/NET6MinimalAPIWebAdapter/Report/load-test-report-arm64.txt +++ /dev/null @@ -1,18 +0,0 @@ -Sun Nov 5 19:14:52 UTC 2023 -arm64 RESULTS lambda: Net6-MinimalApi-WebadApter-Arm64 -Test duration sec: 600 -Log interval min: 20 -Complete -RESULTS coldstart 0 -RESULTS count 138527 -RESULTS p50 6.1078 -RESULTS p90 9.3759 -RESULTS p99 17.9744 -RESULTS max 838.78 -RESULTS coldstart 1 -RESULTS count 139 -RESULTS p50 1277.1986 -RESULTS p90 1326.6409 -RESULTS p99 1358.8491 -RESULTS max 1367.49 -STATISTICS 268615241.0 138666.0 694392.0 diff --git a/src/NET6MinimalAPIWebAdapter/Report/load-test-report-x86.json b/src/NET6MinimalAPIWebAdapter/Report/load-test-report-x86.json deleted file mode 100644 index 60522ca..0000000 --- a/src/NET6MinimalAPIWebAdapter/Report/load-test-report-x86.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "results": [ - [ - { - "field": "coldstart", - "value": "0" - }, - { - "field": "count", - "value": "140872" - }, - { - "field": "p50", - "value": "6.2055" - }, - { - "field": "p90", - "value": "10.3128" - }, - { - "field": "p99", - "value": "21.746" - }, - { - "field": "max", - "value": "154.62" - } - ], - [ - { - "field": "coldstart", - "value": "1" - }, - { - "field": "count", - "value": "112" - }, - { - "field": "p50", - "value": "1013.88" - }, - { - "field": "p90", - "value": "1102.6789" - }, - { - "field": "p99", - "value": "1330.6248" - }, - { - "field": "max", - "value": "1392.85" - } - ] - ], - "statistics": { - "recordsMatched": 140984.0, - "recordsScanned": 705687.0, - "bytesScanned": 272986184.0 - }, - "status": "Complete" -} diff --git a/src/NET6MinimalAPIWebAdapter/Report/load-test-report-x86.txt b/src/NET6MinimalAPIWebAdapter/Report/load-test-report-x86.txt deleted file mode 100644 index 1f5f629..0000000 --- a/src/NET6MinimalAPIWebAdapter/Report/load-test-report-x86.txt +++ /dev/null @@ -1,18 +0,0 @@ -Sun Nov 5 19:04:10 UTC 2023 -x86 RESULTS lambda: Net6-MinimalApi-WebadApter-X86 -Test duration sec: 600 -Log interval min: 20 -Complete -RESULTS coldstart 0 -RESULTS count 140872 -RESULTS p50 6.2055 -RESULTS p90 10.3128 -RESULTS p99 21.746 -RESULTS max 154.62 -RESULTS coldstart 1 -RESULTS count 112 -RESULTS p50 1013.88 -RESULTS p90 1102.6789 -RESULTS p99 1330.6248 -RESULTS max 1392.85 -STATISTICS 272986184.0 140984.0 705687.0 diff --git a/src/NET6Native/DeleteProduct/aws-lambda-tools-defaults.json b/src/NET6Native/DeleteProduct/aws-lambda-tools-defaults.json deleted file mode 100755 index 1d112e0..0000000 --- a/src/NET6Native/DeleteProduct/aws-lambda-tools-defaults.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "Information" : [ - "This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.", - "To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.", - - "dotnet lambda help", - - "All the command line options for the Lambda command can be specified in this file." - ], - - "profile":"", - "region" : "", - "configuration": "Release", - "function-runtime":"provided.al2", - "function-memory-size" : 256, - "function-timeout" : 30, - "function-handler" : "CancelBooking" -} diff --git a/src/NET6Native/GetProduct/aws-lambda-tools-defaults.json b/src/NET6Native/GetProduct/aws-lambda-tools-defaults.json deleted file mode 100755 index 1d112e0..0000000 --- a/src/NET6Native/GetProduct/aws-lambda-tools-defaults.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "Information" : [ - "This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.", - "To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.", - - "dotnet lambda help", - - "All the command line options for the Lambda command can be specified in this file." - ], - - "profile":"", - "region" : "", - "configuration": "Release", - "function-runtime":"provided.al2", - "function-memory-size" : 256, - "function-timeout" : 30, - "function-handler" : "CancelBooking" -} diff --git a/src/NET6Native/GetProducts/aws-lambda-tools-defaults.json b/src/NET6Native/GetProducts/aws-lambda-tools-defaults.json deleted file mode 100755 index 1d112e0..0000000 --- a/src/NET6Native/GetProducts/aws-lambda-tools-defaults.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "Information" : [ - "This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.", - "To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.", - - "dotnet lambda help", - - "All the command line options for the Lambda command can be specified in this file." - ], - - "profile":"", - "region" : "", - "configuration": "Release", - "function-runtime":"provided.al2", - "function-memory-size" : 256, - "function-timeout" : 30, - "function-handler" : "CancelBooking" -} diff --git a/src/NET6Native/PutProduct/aws-lambda-tools-defaults.json b/src/NET6Native/PutProduct/aws-lambda-tools-defaults.json deleted file mode 100755 index 1d112e0..0000000 --- a/src/NET6Native/PutProduct/aws-lambda-tools-defaults.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "Information" : [ - "This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.", - "To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.", - - "dotnet lambda help", - - "All the command line options for the Lambda command can be specified in this file." - ], - - "profile":"", - "region" : "", - "configuration": "Release", - "function-runtime":"provided.al2", - "function-memory-size" : 256, - "function-timeout" : 30, - "function-handler" : "CancelBooking" -} diff --git a/src/NET8/DeleteProduct/aws-lambda-tools-defaults.json b/src/NET8/DeleteProduct/aws-lambda-tools-defaults.json deleted file mode 100644 index 1d112e0..0000000 --- a/src/NET8/DeleteProduct/aws-lambda-tools-defaults.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "Information" : [ - "This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.", - "To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.", - - "dotnet lambda help", - - "All the command line options for the Lambda command can be specified in this file." - ], - - "profile":"", - "region" : "", - "configuration": "Release", - "function-runtime":"provided.al2", - "function-memory-size" : 256, - "function-timeout" : 30, - "function-handler" : "CancelBooking" -} diff --git a/src/NET8/GetProduct/aws-lambda-tools-defaults.json b/src/NET8/GetProduct/aws-lambda-tools-defaults.json deleted file mode 100644 index 1d112e0..0000000 --- a/src/NET8/GetProduct/aws-lambda-tools-defaults.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "Information" : [ - "This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.", - "To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.", - - "dotnet lambda help", - - "All the command line options for the Lambda command can be specified in this file." - ], - - "profile":"", - "region" : "", - "configuration": "Release", - "function-runtime":"provided.al2", - "function-memory-size" : 256, - "function-timeout" : 30, - "function-handler" : "CancelBooking" -} diff --git a/src/NET8/GetProducts/aws-lambda-tools-defaults.json b/src/NET8/GetProducts/aws-lambda-tools-defaults.json deleted file mode 100644 index 1d112e0..0000000 --- a/src/NET8/GetProducts/aws-lambda-tools-defaults.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "Information" : [ - "This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.", - "To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.", - - "dotnet lambda help", - - "All the command line options for the Lambda command can be specified in this file." - ], - - "profile":"", - "region" : "", - "configuration": "Release", - "function-runtime":"provided.al2", - "function-memory-size" : 256, - "function-timeout" : 30, - "function-handler" : "CancelBooking" -} diff --git a/src/NET8/PutProduct/aws-lambda-tools-defaults.json b/src/NET8/PutProduct/aws-lambda-tools-defaults.json deleted file mode 100644 index 7ed1688..0000000 --- a/src/NET8/PutProduct/aws-lambda-tools-defaults.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "Information": [ - "This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.", - "To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.", - "dotnet lambda help", - "All the command line options for the Lambda command can be specified in this file." - ], - "profile": "", - "region": "", - "configuration": "Release", - "function-runtime": "provided.al2", - "function-memory-size": 256, - "function-timeout": 30, - "function-handler": "bootstrap" -} \ No newline at end of file diff --git a/src/NET8MinimalAPI/Report/load-test-report-arm64.json b/src/NET8MinimalAPI/Report/load-test-report-arm64.json deleted file mode 100644 index 8a37bf2..0000000 --- a/src/NET8MinimalAPI/Report/load-test-report-arm64.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "results": [ - [ - { - "field": "coldstart", - "value": "0" - }, - { - "field": "count", - "value": "136816" - }, - { - "field": "p50", - "value": "6.0116" - }, - { - "field": "p90", - "value": "9.3759" - }, - { - "field": "p99", - "value": "24.6905" - }, - { - "field": "max", - "value": "331.6" - } - ], - [ - { - "field": "coldstart", - "value": "1" - }, - { - "field": "count", - "value": "242" - }, - { - "field": "p50", - "value": "1972.7958" - }, - { - "field": "p90", - "value": "2049.1658" - }, - { - "field": "p99", - "value": "2107.3237" - }, - { - "field": "max", - "value": "2124.55" - } - ] - ], - "statistics": { - "recordsMatched": 137058.0, - "recordsScanned": 1233898.0, - "bytesScanned": 945917431.0 - }, - "status": "Complete" -} diff --git a/src/NET8MinimalAPI/Report/load-test-report-arm64.txt b/src/NET8MinimalAPI/Report/load-test-report-arm64.txt deleted file mode 100644 index e98f8f2..0000000 --- a/src/NET8MinimalAPI/Report/load-test-report-arm64.txt +++ /dev/null @@ -1,18 +0,0 @@ -Sun Nov 5 19:38:43 UTC 2023 -arm64 RESULTS lambda: Net8-MinimalApi-Arm64 -Test duration sec: 600 -Log interval min: 20 -Complete -RESULTS coldstart 0 -RESULTS count 136816 -RESULTS p50 6.0116 -RESULTS p90 9.3759 -RESULTS p99 24.6905 -RESULTS max 331.6 -RESULTS coldstart 1 -RESULTS count 242 -RESULTS p50 1972.7958 -RESULTS p90 2049.1658 -RESULTS p99 2107.3237 -RESULTS max 2124.55 -STATISTICS 945917431.0 137058.0 1233898.0 diff --git a/src/NET8MinimalAPI/Report/load-test-report-x86.json b/src/NET8MinimalAPI/Report/load-test-report-x86.json deleted file mode 100644 index 6b47edc..0000000 --- a/src/NET8MinimalAPI/Report/load-test-report-x86.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "results": [ - [ - { - "field": "coldstart", - "value": "0" - }, - { - "field": "count", - "value": "136006" - }, - { - "field": "p50", - "value": "6.3048" - }, - { - "field": "p90", - "value": "10.9888" - }, - { - "field": "p99", - "value": "26.7299" - }, - { - "field": "max", - "value": "124.67" - } - ], - [ - { - "field": "coldstart", - "value": "1" - }, - { - "field": "count", - "value": "195" - }, - { - "field": "p50", - "value": "1672.8591" - }, - { - "field": "p90", - "value": "1737.618" - }, - { - "field": "p99", - "value": "1833.9796" - }, - { - "field": "max", - "value": "1889.15" - } - ] - ], - "statistics": { - "recordsMatched": 136201.0, - "recordsScanned": 1226041.0, - "bytesScanned": 935106143.0 - }, - "status": "Complete" -} diff --git a/src/NET8MinimalAPI/Report/load-test-report-x86.txt b/src/NET8MinimalAPI/Report/load-test-report-x86.txt deleted file mode 100644 index f57a52f..0000000 --- a/src/NET8MinimalAPI/Report/load-test-report-x86.txt +++ /dev/null @@ -1,18 +0,0 @@ -Sun Nov 5 19:28:01 UTC 2023 -x86 RESULTS lambda: Net8-MinimalApi-X86 -Test duration sec: 600 -Log interval min: 20 -Complete -RESULTS coldstart 0 -RESULTS count 136006 -RESULTS p50 6.3048 -RESULTS p90 10.9888 -RESULTS p99 26.7299 -RESULTS max 124.67 -RESULTS coldstart 1 -RESULTS count 195 -RESULTS p50 1672.8591 -RESULTS p90 1737.618 -RESULTS p99 1833.9796 -RESULTS max 1889.15 -STATISTICS 935106143.0 136201.0 1226041.0 diff --git a/src/NET8Native/DeleteProduct/DeleteProduct.csproj b/src/NET8Native/DeleteProduct/DeleteProduct.csproj index f64c696..9f5a81b 100644 --- a/src/NET8Native/DeleteProduct/DeleteProduct.csproj +++ b/src/NET8Native/DeleteProduct/DeleteProduct.csproj @@ -3,16 +3,20 @@ exe net8.0 - true - Lambda bootstrap + true - false - true - true + true + Lambda + true + skylake Speed + + false + true + true diff --git a/src/NET8Native/DeleteProduct/aws-lambda-tools-defaults.json b/src/NET8Native/DeleteProduct/aws-lambda-tools-defaults.json deleted file mode 100644 index 1d112e0..0000000 --- a/src/NET8Native/DeleteProduct/aws-lambda-tools-defaults.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "Information" : [ - "This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.", - "To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.", - - "dotnet lambda help", - - "All the command line options for the Lambda command can be specified in this file." - ], - - "profile":"", - "region" : "", - "configuration": "Release", - "function-runtime":"provided.al2", - "function-memory-size" : 256, - "function-timeout" : 30, - "function-handler" : "CancelBooking" -} diff --git a/src/NET8Native/GetProduct/GetProduct.csproj b/src/NET8Native/GetProduct/GetProduct.csproj index e070d2e..364daed 100644 --- a/src/NET8Native/GetProduct/GetProduct.csproj +++ b/src/NET8Native/GetProduct/GetProduct.csproj @@ -1,21 +1,21 @@  - exe net8.0 - true - Lambda bootstrap true - false - true - true - + true + Lambda + true + skylake Speed + + false + true + true diff --git a/src/NET8Native/GetProduct/aws-lambda-tools-defaults.json b/src/NET8Native/GetProduct/aws-lambda-tools-defaults.json deleted file mode 100644 index 1d112e0..0000000 --- a/src/NET8Native/GetProduct/aws-lambda-tools-defaults.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "Information" : [ - "This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.", - "To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.", - - "dotnet lambda help", - - "All the command line options for the Lambda command can be specified in this file." - ], - - "profile":"", - "region" : "", - "configuration": "Release", - "function-runtime":"provided.al2", - "function-memory-size" : 256, - "function-timeout" : 30, - "function-handler" : "CancelBooking" -} diff --git a/src/NET8Native/GetProducts/GetProducts.csproj b/src/NET8Native/GetProducts/GetProducts.csproj index 7f65871..364daed 100644 --- a/src/NET8Native/GetProducts/GetProducts.csproj +++ b/src/NET8Native/GetProducts/GetProducts.csproj @@ -1,18 +1,21 @@  - exe net8.0 - true - Lambda bootstrap + true - false - true - true + true + Lambda + true + skylake Speed + + false + true + true diff --git a/src/NET8Native/GetProducts/aws-lambda-tools-defaults.json b/src/NET8Native/GetProducts/aws-lambda-tools-defaults.json deleted file mode 100644 index 1d112e0..0000000 --- a/src/NET8Native/GetProducts/aws-lambda-tools-defaults.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "Information" : [ - "This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.", - "To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.", - - "dotnet lambda help", - - "All the command line options for the Lambda command can be specified in this file." - ], - - "profile":"", - "region" : "", - "configuration": "Release", - "function-runtime":"provided.al2", - "function-memory-size" : 256, - "function-timeout" : 30, - "function-handler" : "CancelBooking" -} diff --git a/src/NET8Native/Makefile b/src/NET8Native/Makefile index 655de9c..9993dc3 100644 --- a/src/NET8Native/Makefile +++ b/src/NET8Native/Makefile @@ -1,23 +1,11 @@ build-GetProductFunctionX86: - dotnet publish GetProduct/GetProduct.csproj -c Release -r linux-x64 -o $(ARTIFACTS_DIR) + dotnet publish GetProduct/GetProduct.csproj -c Release -r linux-x64 --self-contained -o $(ARTIFACTS_DIR) build-GetProductsFunctionX86: - dotnet publish GetProducts/GetProducts.csproj -c Release -r linux-x64 -o $(ARTIFACTS_DIR) + dotnet publish GetProducts/GetProducts.csproj -c Release -r linux-x64 --self-contained -o $(ARTIFACTS_DIR) build-PutProductFunctionX86: - dotnet publish PutProduct/PutProduct.csproj -c Release -r linux-x64 -o $(ARTIFACTS_DIR) + dotnet publish PutProduct/PutProduct.csproj -c Release -r linux-x64 --self-contained -o $(ARTIFACTS_DIR) build-DeleteProductFunctionX86: - dotnet publish DeleteProduct/DeleteProduct.csproj -c Release -r linux-x64 -o $(ARTIFACTS_DIR) - -build-GetProductFunctionArm64: - dotnet publish GetProduct/GetProduct.csproj -c Release -r linux-x64 -o $(ARTIFACTS_DIR) - -build-GetProductsFunctionArm64: - dotnet publish GetProducts/GetProducts.csproj -c Release -r linux-x64 -o $(ARTIFACTS_DIR) - -build-PutProductFunctionArm64: - dotnet publish PutProduct/PutProduct.csproj -c Release -r linux-x64 -o $(ARTIFACTS_DIR) - -build-DeleteProductFunctionArm64: - dotnet publish DeleteProduct/DeleteProduct.csproj -c Release -r linux-x64 -o $(ARTIFACTS_DIR) \ No newline at end of file + dotnet publish DeleteProduct/DeleteProduct.csproj -c Release -r linux-x64 --self-contained -o $(ARTIFACTS_DIR) \ No newline at end of file diff --git a/src/NET8Native/PutProduct/PutProduct.csproj b/src/NET8Native/PutProduct/PutProduct.csproj index f64c696..9f5a81b 100644 --- a/src/NET8Native/PutProduct/PutProduct.csproj +++ b/src/NET8Native/PutProduct/PutProduct.csproj @@ -3,16 +3,20 @@ exe net8.0 - true - Lambda bootstrap + true - false - true - true + true + Lambda + true + skylake Speed + + false + true + true diff --git a/src/NET8Native/PutProduct/aws-lambda-tools-defaults.json b/src/NET8Native/PutProduct/aws-lambda-tools-defaults.json deleted file mode 100644 index 7ed1688..0000000 --- a/src/NET8Native/PutProduct/aws-lambda-tools-defaults.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "Information": [ - "This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.", - "To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.", - "dotnet lambda help", - "All the command line options for the Lambda command can be specified in this file." - ], - "profile": "", - "region": "", - "configuration": "Release", - "function-runtime": "provided.al2", - "function-memory-size": 256, - "function-timeout": 30, - "function-handler": "bootstrap" -} \ No newline at end of file diff --git a/src/NET8Native/Shared/JsonSerializerContext.cs b/src/NET8Native/Shared/JsonSerializerContext.cs index 99f168f..f71528f 100644 --- a/src/NET8Native/Shared/JsonSerializerContext.cs +++ b/src/NET8Native/Shared/JsonSerializerContext.cs @@ -1,4 +1,5 @@ -using System.Text.Json.Serialization; +using System.Collections.Generic; +using System.Text.Json.Serialization; using Amazon.Lambda.APIGatewayEvents; using Shared.Models; diff --git a/src/NET8Native/Shared/Shared.csproj b/src/NET8Native/Shared/Shared.csproj index cf01798..84e7fb3 100644 --- a/src/NET8Native/Shared/Shared.csproj +++ b/src/NET8Native/Shared/Shared.csproj @@ -2,20 +2,19 @@ net8.0 - enable - enable - true - true - false - true - true - + true + Lambda + true + skylake Speed + + false + true + true diff --git a/src/NET8Native/deploy.sh b/src/NET8Native/deploy.sh index 924d77c..dda9b4d 100755 --- a/src/NET8Native/deploy.sh +++ b/src/NET8Native/deploy.sh @@ -39,5 +39,5 @@ then echo "${NO_COLOR}" fi -sam build +sam build --use-container --build-image plantpowerjames/dotnet-8-lambda-build:rc2 sam deploy --stack-name $STACK_NAME --resolve-s3 --s3-prefix $STACK_NAME --no-confirm-changeset --no-fail-on-empty-changeset --capabilities CAPABILITY_IAM \ No newline at end of file diff --git a/src/NET8Native/run-loadtest.sh b/src/NET8Native/run-loadtest.sh index ab8f5a8..dd87955 100755 --- a/src/NET8Native/run-loadtest.sh +++ b/src/NET8Native/run-loadtest.sh @@ -149,5 +149,4 @@ function RunLoadTest() aws logs get-query-results --query-id $QUERY_ID --output json >> ./Report/load-test-report-$1.json } -RunLoadTest x86 ApiUrlX86 LambdaX86NameGetProducts LambdaX86NameGetProduct LambdaX86NameDeleteProduct LambdaX86NamePutProduct -RunLoadTest arm64 ApiUrlArm64 LambdaArm64NameGetProducts LambdaArm64NameGetProduct LambdaArm64NameDeleteProduct LambdaArm64NamePutProduct \ No newline at end of file +RunLoadTest x86 ApiUrlX86 LambdaX86NameGetProducts LambdaX86NameGetProduct LambdaX86NameDeleteProduct LambdaX86NamePutProduct \ No newline at end of file diff --git a/src/NET8Native/template.yaml b/src/NET8Native/template.yaml index b439ecc..88b4d70 100644 --- a/src/NET8Native/template.yaml +++ b/src/NET8Native/template.yaml @@ -5,9 +5,6 @@ Parameters: x86FunctionNamePrefix: Type: String Default: Net8-Native-X86 - arm64FunctionNamePrefix: - Type: String - Default: Net8-Native-Arm64 Globals: Function: @@ -108,94 +105,6 @@ Resources: Resource: !GetAtt Table.Arn Metadata: BuildMethod: makefile - #ARM64 - GetProductsFunctionArm64: - Type: AWS::Serverless::Function - Properties: - FunctionName: !Join [ -,[!Ref arm64FunctionNamePrefix,GetProducts]] - Architectures: [arm64] - CodeUri: ./ - Handler: bootstrap - Events: - Api: - Type: HttpApi - Properties: - Path: /arm64 - Method: GET - Policies: - - DynamoDBReadPolicy: - TableName: - !Ref Table - Metadata: - BuildMethod: makefile - - GetProductFunctionArm64: - Type: AWS::Serverless::Function - Properties: - FunctionName: !Join [ -,[!Ref arm64FunctionNamePrefix,GetProduct]] - Architectures: [arm64] - CodeUri: ./ - Handler: GetProduct - Events: - Api: - Type: HttpApi - Properties: - Path: /arm64/{id} - Method: GET - Policies: - - Version: "2012-10-17" - Statement: - - Effect: Allow - Action: dynamodb:GetItem - Resource: !GetAtt Table.Arn - Metadata: - BuildMethod: makefile - - DeleteProductFunctionArm64: - Type: AWS::Serverless::Function - Properties: - FunctionName: !Join [ -,[!Ref arm64FunctionNamePrefix,DeleteProduct]] - Architectures: [arm64] - CodeUri: ./ - Handler: DeleteProduct - Events: - Api: - Type: HttpApi - Properties: - Path: /arm64/{id} - Method: DELETE - Policies: - - Version: "2012-10-17" - Statement: - - Effect: Allow - Action: - - dynamodb:DeleteItem - - dynamodb:GetItem - Resource: !GetAtt Table.Arn - Metadata: - BuildMethod: makefile - - PutProductFunctionArm64: - Type: AWS::Serverless::Function - Properties: - FunctionName: !Join [ -,[!Ref arm64FunctionNamePrefix,PutProduct]] - Architectures: [arm64] - CodeUri: ./ - Handler: PutProduct - Events: - Api: - Type: HttpApi - Properties: - Path: /arm64/{id} - Method: PUT - Policies: - - Version: "2012-10-17" - Statement: - - Effect: Allow - Action: dynamodb:PutItem - Resource: !GetAtt Table.Arn - Metadata: - BuildMethod: makefile Table: Type: AWS::DynamoDB::Table @@ -215,10 +124,7 @@ Outputs: ApiUrlX86: Description: "X86 API endpoint URL" Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/x86" - ApiUrlArm64: - Description: "Arm64 GateAPI endpoint URL" - Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/arm64" - + #x86 LambdaX86NameGetProducts: Description: "Lambda X86 GetProducts" @@ -231,18 +137,4 @@ Outputs: Value: !Join [ -,[!Ref x86FunctionNamePrefix,DeleteProduct]] LambdaX86NamePutProduct: Description: "Lambda X86 PutProduct" - Value: !Join [ -,[!Ref x86FunctionNamePrefix,PutProduct]] - - #arm64 - LambdaArm64NameGetProducts: - Description: "Lambda X86 GetProducts" - Value: !Join [ -,[!Ref arm64FunctionNamePrefix,GetProducts]] - LambdaArm64NameGetProduct: - Description: "Lambda X86 GetProduct" - Value: !Join [ -,[!Ref arm64FunctionNamePrefix,GetProduct]] - LambdaArm64NameDeleteProduct: - Description: "Lambda X86 DeleteProduct" - Value: !Join [ -,[!Ref arm64FunctionNamePrefix,DeleteProduct]] - LambdaArm64NamePutProduct: - Description: "Lambda X86 PutProduct" - Value: !Join [ -,[!Ref arm64FunctionNamePrefix,PutProduct]] \ No newline at end of file + Value: !Join [ -,[!Ref x86FunctionNamePrefix,PutProduct]] \ No newline at end of file From 7784f4efa12f95c6a43891ddda4a7581a157a41c Mon Sep 17 00:00:00 2001 From: Riccardo Venturi Date: Sun, 12 Nov 2023 21:28:06 +0100 Subject: [PATCH 19/32] fixing net8 native minimal api --- .../ApiBootstrap/ApiBootstrap.csproj | 11 +- .../ApiBootstrap/Function.cs | 163 ++++++++---------- src/NET8NativeMinimalAPI/Makefile | 4 +- .../Shared/DataAccess/DynamoDbProducts.cs | 5 +- .../Shared/DataAccess/ProductMapper.cs | 4 +- .../Shared/DataAccess/ProductsDAO.cs | 3 +- .../Shared/Models/Product.cs | 11 +- .../Shared/Models/ProductWrapper.cs | 4 +- src/NET8NativeMinimalAPI/Shared/Shared.csproj | 4 + src/NET8NativeMinimalAPI/Shared/Startup.cs | 7 +- src/NET8NativeMinimalAPI/deploy.sh | 4 +- src/NET8NativeMinimalAPI/run-loadtest.sh | 3 +- src/NET8NativeMinimalAPI/template.yaml | 38 +--- 13 files changed, 105 insertions(+), 156 deletions(-) diff --git a/src/NET8NativeMinimalAPI/ApiBootstrap/ApiBootstrap.csproj b/src/NET8NativeMinimalAPI/ApiBootstrap/ApiBootstrap.csproj index facf661..e2c8e51 100644 --- a/src/NET8NativeMinimalAPI/ApiBootstrap/ApiBootstrap.csproj +++ b/src/NET8NativeMinimalAPI/ApiBootstrap/ApiBootstrap.csproj @@ -3,14 +3,17 @@ Exe net8.0 + bootstrap + + true + + true true enable enable GetProducts - true - bootstrap + full - true @@ -29,6 +32,8 @@ + + diff --git a/src/NET8NativeMinimalAPI/ApiBootstrap/Function.cs b/src/NET8NativeMinimalAPI/ApiBootstrap/Function.cs index 10a82a7..0462ff5 100644 --- a/src/NET8NativeMinimalAPI/ApiBootstrap/Function.cs +++ b/src/NET8NativeMinimalAPI/ApiBootstrap/Function.cs @@ -1,9 +1,5 @@ -using System; -using System.Collections.Generic; using System.Net; using System.Text.Json; -using Amazon.CloudWatchLogs; -using Amazon.CloudWatchLogs.Model; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; @@ -13,122 +9,115 @@ using Shared.Models; var app = Startup.Build(args); -var basePath=Environment.GetEnvironmentVariable("DEMO_BASE_PATH"); -if(String.IsNullOrEmpty(basePath)) - app.Logger.LogInformation($"No BASE PATH specified"); -else -{ - app.Logger.LogInformation($"Using BASE PATH:{basePath}"); - app.UsePathBase(basePath); - app.UseRouting(); -} -var dataAccess = app.Services.GetRequiredService(); +Handlers.DataAccess = app.Services.GetRequiredService(); +Handlers.Logger = app.Logger; -var cloudWatchClient = new AmazonCloudWatchLogsClient(); +app.MapGet("/", Handlers.GetAllProducts); -app.MapGet("/", async (HttpContext context) => -{ - app.Logger.LogInformation("Received request to list all products"); +app.MapDelete("/{id}", Handlers.DeleteProduct); - var products = await dataAccess.GetAllProducts(); +app.MapPut("/{id}", Handlers.PutProduct); - app.Logger.LogInformation($"Found {products.Products.Count} products(s)"); +app.MapGet("/{id}", Handlers.GetProduct); - context.Response.StatusCode = (int) HttpStatusCode.OK; - await context.Response.WriteAsJsonAsync(products); -}); +app.Run(); -app.MapDelete("/{id}", async (HttpContext context) => +static class Handlers { - try + internal static ProductsDAO DataAccess; + internal static ILogger Logger; + + public static async Task GetAllProducts(HttpContext context) { - var id = context.Request.RouteValues["id"].ToString(); + Logger.LogInformation("Received request to list all products"); - app.Logger.LogInformation($"Received request to delete {id} from the database"); + var products = await DataAccess.GetAllProducts(); - var product = await dataAccess.GetProduct(id); + Logger.LogInformation($"Found {products.Products.Count} products(s)"); - if (product == null) + await context.WriteResponse(HttpStatusCode.OK, products); + } + + public static async Task DeleteProduct(HttpContext context) + { + try { - app.Logger.LogWarning($"Id {id} not found."); + var id = context.Request.RouteValues["id"].ToString(); + + Logger.LogInformation($"Received request to delete {id}"); - context.Response.StatusCode = (int) HttpStatusCode.NotFound; - Results.NotFound(); - return; - } + var product = await DataAccess.GetProduct(id); - app.Logger.LogInformation($"Deleting {product.Name}"); + if (product == null) + { + Logger.LogWarning($"Id {id} not found."); - await dataAccess.DeleteProduct(product.Id); + await context.WriteResponse(HttpStatusCode.NotFound); + + return; + } - app.Logger.LogInformation("Delete complete"); + Logger.LogInformation($"Deleting {product.Name}"); - context.Response.StatusCode = (int) HttpStatusCode.OK; - await context.Response.WriteAsJsonAsync($"Product with id {id} deleted"); - } - catch (Exception e) - { - app.Logger.LogError(e, "Failure deleting product"); + await DataAccess.DeleteProduct(product.Id); + + Logger.LogInformation("Delete complete"); + + await context.WriteResponse(HttpStatusCode.OK, $"Product with id {id} deleted"); + } + catch (Exception e) + { + Logger.LogError(e, "Failure deleting product"); - context.Response.StatusCode = (int) HttpStatusCode.NotFound; + await context.WriteResponse(HttpStatusCode.BadRequest); + } } -}); -app.MapPut("/{id}", async (HttpContext context) => -{ - try + public static async Task GetProduct(HttpContext context) { var id = context.Request.RouteValues["id"].ToString(); + + Logger.LogInformation($"Received request to get {id}"); - app.Logger.LogInformation($"Received request to put {id}"); + var product = await DataAccess.GetProduct(id); - var product = await JsonSerializer.DeserializeAsync(context.Request.Body); - - if (product == null || id != product.Id) + if (product == null) { - app.Logger.LogWarning("Product ID in the body does not match path parameter"); - - context.Response.StatusCode = (int) HttpStatusCode.BadRequest; - await context.Response.WriteAsJsonAsync("Product ID in the body does not match path parameter"); - return; + Logger.LogWarning($"{id} not found"); + await context.WriteResponse(HttpStatusCode.NotFound, $"{id} not found"); } - app.Logger.LogInformation("Putting product"); - - await dataAccess.PutProduct(product); - - app.Logger.LogTrace("Done"); - - context.Response.StatusCode = (int) HttpStatusCode.OK; - await context.Response.WriteAsJsonAsync($"Created product with id {id}"); + await context.WriteResponse(HttpStatusCode.OK, product); } - catch (Exception e) + + public static async Task PutProduct(HttpContext context) { - app.Logger.LogError(e, "Failure deleting product"); + var id = context.Request.RouteValues["id"].ToString(); + var product = await JsonSerializer.DeserializeAsync(context.Request.Body, ApiSerializerContext.Default.Product); + + if (product == null || id != product.Id) + { + await context.WriteResponse(HttpStatusCode.BadRequest, "Product ID in the body does not match path parameter"); + } - context.Response.StatusCode = (int) HttpStatusCode.BadRequest; + await DataAccess.PutProduct(product); + + await context.WriteResponse(HttpStatusCode.OK, $"Created product with id {id}"); } -}); +} -app.MapGet("/{id}", async (HttpContext context) => +static class ResponseWriter { - var id = context.Request.RouteValues["id"].ToString(); - - app.Logger.LogInformation($"Received request to get {id}"); - - var product = await dataAccess.GetProduct(id); - - if (product == null) + public static async Task WriteResponse(this HttpContext context, HttpStatusCode statusCode) { - app.Logger.LogWarning($"{id} not found"); - context.Response.StatusCode = (int) HttpStatusCode.NotFound; - Results.NotFound(); - return; + await context.WriteResponse(statusCode, ""); } - - context.Response.StatusCode = (int) HttpStatusCode.OK; - await context.Response.WriteAsJsonAsync(product); -}); - -app.Run(); \ No newline at end of file + + public static async Task WriteResponse(this HttpContext context, HttpStatusCode statusCode, TResponseType body) where TResponseType : class + { + context.Response.StatusCode = (int)statusCode; + context.Response.ContentType = "application/json"; + await context.Response.WriteAsync(JsonSerializer.Serialize(body, typeof(TResponseType), ApiSerializerContext.Default)); + } +} \ No newline at end of file diff --git a/src/NET8NativeMinimalAPI/Makefile b/src/NET8NativeMinimalAPI/Makefile index 5b5c4f6..0e8e9c9 100644 --- a/src/NET8NativeMinimalAPI/Makefile +++ b/src/NET8NativeMinimalAPI/Makefile @@ -1,4 +1,2 @@ build-MinimalApiX86: - dotnet publish ApiBootstrap/ApiBootstrap.csproj -c Release -r linux-x64 -o $(ARTIFACTS_DIR) -build-MinimalApiArm64: - dotnet publish ApiBootstrap/ApiBootstrap.csproj -c Release -r linux-x64 -o $(ARTIFACTS_DIR) \ No newline at end of file + dotnet publish ApiBootstrap/ApiBootstrap.csproj -c Release -r linux-x64 --self-contained -o $(ARTIFACTS_DIR) \ No newline at end of file diff --git a/src/NET8NativeMinimalAPI/Shared/DataAccess/DynamoDbProducts.cs b/src/NET8NativeMinimalAPI/Shared/DataAccess/DynamoDbProducts.cs index 5f85635..e41da85 100644 --- a/src/NET8NativeMinimalAPI/Shared/DataAccess/DynamoDbProducts.cs +++ b/src/NET8NativeMinimalAPI/Shared/DataAccess/DynamoDbProducts.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Amazon.DynamoDBv2; +using Amazon.DynamoDBv2; using Amazon.DynamoDBv2.Model; using Shared.Models; diff --git a/src/NET8NativeMinimalAPI/Shared/DataAccess/ProductMapper.cs b/src/NET8NativeMinimalAPI/Shared/DataAccess/ProductMapper.cs index 8e073ed..7abc616 100644 --- a/src/NET8NativeMinimalAPI/Shared/DataAccess/ProductMapper.cs +++ b/src/NET8NativeMinimalAPI/Shared/DataAccess/ProductMapper.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Globalization; +using System.Globalization; using Amazon.DynamoDBv2.Model; using Shared.Models; diff --git a/src/NET8NativeMinimalAPI/Shared/DataAccess/ProductsDAO.cs b/src/NET8NativeMinimalAPI/Shared/DataAccess/ProductsDAO.cs index d09decd..c1e13a6 100644 --- a/src/NET8NativeMinimalAPI/Shared/DataAccess/ProductsDAO.cs +++ b/src/NET8NativeMinimalAPI/Shared/DataAccess/ProductsDAO.cs @@ -1,5 +1,4 @@ -using System.Threading.Tasks; -using Shared.Models; +using Shared.Models; namespace Shared.DataAccess { diff --git a/src/NET8NativeMinimalAPI/Shared/Models/Product.cs b/src/NET8NativeMinimalAPI/Shared/Models/Product.cs index 93f9896..88a62ca 100644 --- a/src/NET8NativeMinimalAPI/Shared/Models/Product.cs +++ b/src/NET8NativeMinimalAPI/Shared/Models/Product.cs @@ -1,6 +1,4 @@ -using System; - -namespace Shared.Models +namespace Shared.Models { public class Product { @@ -19,12 +17,7 @@ public Product(string id, string name, decimal price) public string Name { get; set; } - public decimal Price { get; private set; } - - public void SetPrice(decimal newPrice) - { - this.Price = Math.Round(newPrice, 2); - } + public decimal Price { get; set; } public override string ToString() { diff --git a/src/NET8NativeMinimalAPI/Shared/Models/ProductWrapper.cs b/src/NET8NativeMinimalAPI/Shared/Models/ProductWrapper.cs index c89af93..47cc485 100644 --- a/src/NET8NativeMinimalAPI/Shared/Models/ProductWrapper.cs +++ b/src/NET8NativeMinimalAPI/Shared/Models/ProductWrapper.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; - -namespace Shared.Models +namespace Shared.Models { public class ProductWrapper { diff --git a/src/NET8NativeMinimalAPI/Shared/Shared.csproj b/src/NET8NativeMinimalAPI/Shared/Shared.csproj index 7d6a8a2..466b824 100644 --- a/src/NET8NativeMinimalAPI/Shared/Shared.csproj +++ b/src/NET8NativeMinimalAPI/Shared/Shared.csproj @@ -2,6 +2,10 @@ net8.0 + + true + + true enable enable diff --git a/src/NET8NativeMinimalAPI/Shared/Startup.cs b/src/NET8NativeMinimalAPI/Shared/Startup.cs index ef2b0db..3926dc3 100644 --- a/src/NET8NativeMinimalAPI/Shared/Startup.cs +++ b/src/NET8NativeMinimalAPI/Shared/Startup.cs @@ -1,7 +1,6 @@ using System.Text.Json; using Amazon.Lambda.Serialization.SystemTextJson; using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Shared.DataAccess; @@ -34,7 +33,11 @@ public static WebApplication Build(string[] args) options.TimestampFormat = "hh:mm:ss "; }); - return builder.Build(); + var app = builder.Build(); + + // Add generic app configuration here. + + return app; } } } \ No newline at end of file diff --git a/src/NET8NativeMinimalAPI/deploy.sh b/src/NET8NativeMinimalAPI/deploy.sh index 7da02c0..71a251b 100755 --- a/src/NET8NativeMinimalAPI/deploy.sh +++ b/src/NET8NativeMinimalAPI/deploy.sh @@ -39,5 +39,5 @@ then echo "${NO_COLOR}" fi -sam build -sam deploy --stack-name dotnet8-minimal-api --resolve-s3 --s3-prefix dotnet8-minimal-api --no-confirm-changeset --no-fail-on-empty-changeset --capabilities CAPABILITY_IAM \ No newline at end of file +sam build --use-container --build-image plantpowerjames/dotnet-8-lambda-build:rc2 +sam deploy --stack-name $STACK_NAME --resolve-s3 --s3-prefix $STACK_NAME --no-confirm-changeset --no-fail-on-empty-changeset --capabilities CAPABILITY_IAM \ No newline at end of file diff --git a/src/NET8NativeMinimalAPI/run-loadtest.sh b/src/NET8NativeMinimalAPI/run-loadtest.sh index 8e7408e..351288a 100755 --- a/src/NET8NativeMinimalAPI/run-loadtest.sh +++ b/src/NET8NativeMinimalAPI/run-loadtest.sh @@ -119,5 +119,4 @@ function RunLoadTest() aws logs get-query-results --query-id $QUERY_ID --output json >> ./Report/load-test-report-$1.json } -RunLoadTest x86 ApiUrlX86 LambdaX86Name -RunLoadTest arm64 ApiUrlArm64 LambdaArm64Name \ No newline at end of file +RunLoadTest x86 ApiUrlX86 LambdaX86Name \ No newline at end of file diff --git a/src/NET8NativeMinimalAPI/template.yaml b/src/NET8NativeMinimalAPI/template.yaml index 3dbb403..181d59b 100644 --- a/src/NET8NativeMinimalAPI/template.yaml +++ b/src/NET8NativeMinimalAPI/template.yaml @@ -5,9 +5,6 @@ Parameters: x86FunctionName: Type: String Default: Net8-Native-MinimalApi-X86 - arm64FunctionName: - Type: String - Default: Net8-Native-MinimalApi-Arm64 Globals: Function: @@ -27,36 +24,11 @@ Resources: Architectures: [x86_64] CodeUri: ./ Handler: ApiBootstrap - Environment: - Variables: - DEMO_BASE_PATH: /x86 - Events: - Api: - Type: HttpApi - Properties: - Path: /x86/{proxy+} - Method: ANY - Policies: - - DynamoDBCrudPolicy: - TableName: - !Ref Table - Metadata: - BuildMethod: makefile - MinimalApiArm64: - Type: AWS::Serverless::Function - Properties: - FunctionName: !Ref arm64FunctionName - Architectures: [arm64] - CodeUri: ./ - Handler: ApiBootstrap - Environment: - Variables: - DEMO_BASE_PATH: /arm64 Events: Api: Type: HttpApi Properties: - Path: /arm64/{proxy+} + Path: /{proxy+} Method: ANY Policies: - DynamoDBCrudPolicy: @@ -87,10 +59,4 @@ Outputs: Value: !Ref x86FunctionName ApiUrlX86: Description: "X86 API endpoint URL" - Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/x86" - LambdaArm64Name: - Description: "Lambda Arm64 Name" - Value: !Ref arm64FunctionName - ApiUrlArm64: - Description: "Arm64 GateAPI endpoint URL" - Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/arm64" \ No newline at end of file + Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com" \ No newline at end of file From c664217fc0557c036a7571426f2ae0bc9cfcecf9 Mon Sep 17 00:00:00 2001 From: Riccardo Venturi Date: Sun, 12 Nov 2023 21:34:40 +0100 Subject: [PATCH 20/32] adding ability to choose .netsdk to be used in codebuild --- loadtest/codebuild/load-test-buildspec.yml | 14 ++++++++++---- loadtest/codebuild/setup.sh | 16 ++++++++++++++++ 2 files changed, 26 insertions(+), 4 deletions(-) create mode 100755 loadtest/codebuild/setup.sh diff --git a/loadtest/codebuild/load-test-buildspec.yml b/loadtest/codebuild/load-test-buildspec.yml index 8e5a880..b9f2a0b 100644 --- a/loadtest/codebuild/load-test-buildspec.yml +++ b/loadtest/codebuild/load-test-buildspec.yml @@ -3,17 +3,23 @@ version: 0.2 phases: install: commands: - - curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin --channel 8.0 - - npm i artillery -g + - cd loadtest/codebuild + - ./setup.sh build: commands: - - export temp1=$(pwd);echo $temp1 - - cd loadtest/codebuild - ./run-all-load-tests.sh artifacts: files: + - src/NET6/Report/* + - src/NET6Containers/Report/* + - src/NET6CustomRuntime/Report/* - src/NET6MinimalAPI/Report/* - src/NET6MinimalAPIWebAdapter/Report/* + - src/NET6Native/Report/* + - src/NET6TopLevelStatements/Report/* + - src/NET6WithPowerTools/Report/* - src/NET8/Report/* - src/NET8MinimalAPI/Report/* + - src/NET8Native/Report/* + - src/NET8NativeMinimalApi/Report/* name: loadtest-$(date +%Y-%m-%H-%M) \ No newline at end of file diff --git a/loadtest/codebuild/setup.sh b/loadtest/codebuild/setup.sh new file mode 100755 index 0000000..ffa1a0b --- /dev/null +++ b/loadtest/codebuild/setup.sh @@ -0,0 +1,16 @@ +#LT_NET_SDK_CHANNEL +#Specifies the source channel for the installation. The possible values are: +# STS: The most recent Standard Term Support release. +# LTS: The most recent Long Term Support release. +# Two-part version in A.B format, representing a specific release (for example, 3.1 or 6.0). +# Three-part version in A.B.Cxx format, representing a specific SDK release (for example, 6.0.1xx or 6.0.2xx). Available since the 5.0 release. +echo -------------------------------------------- +echo Installing dotnet runtime version: $LT_NET_SDK_CHANNEL +echo -------------------------------------------- + +curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin --channel $LT_NET_SDK_CHANNEL + +echo -------------------------------------------- +echo Installing artillery +echo -------------------------------------------- +npm i artillery -g \ No newline at end of file From 13103c69707bb548fe65f8f4b1f0463c7dd7a2a6 Mon Sep 17 00:00:00 2001 From: Riccardo Venturi Date: Sun, 12 Nov 2023 22:24:49 +0100 Subject: [PATCH 21/32] . --- loadtest/codebuild/run-all-load-tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/loadtest/codebuild/run-all-load-tests.sh b/loadtest/codebuild/run-all-load-tests.sh index 931176b..2febd69 100755 --- a/loadtest/codebuild/run-all-load-tests.sh +++ b/loadtest/codebuild/run-all-load-tests.sh @@ -85,7 +85,7 @@ then echo SKIPPING net8 native minimal api LT_NET8_NATIVE_MINIMAL_API=$LT_NET8_NATIVE_MINIMAL_API else echo "RUNNING load test for net8 native minimal api" - cd ../../src/NET8NativeMinimalApi/ + cd ../../src/NET8NativeMinimalAPI/ source ./deploy.sh $DELETE_STACK source ./run-loadtest.sh $TEST_DURATIOMN_SEC $LOG_INTERVAL_MIN $LOG_DELETE fi \ No newline at end of file From befff93308cf3cbee260cea033a0f698383d6ab6 Mon Sep 17 00:00:00 2001 From: Riccardo Venturi Date: Sun, 12 Nov 2023 22:36:04 +0100 Subject: [PATCH 22/32] . --- loadtest/codebuild/load-test-buildspec.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/loadtest/codebuild/load-test-buildspec.yml b/loadtest/codebuild/load-test-buildspec.yml index b9f2a0b..84bccfb 100644 --- a/loadtest/codebuild/load-test-buildspec.yml +++ b/loadtest/codebuild/load-test-buildspec.yml @@ -21,5 +21,5 @@ artifacts: - src/NET8/Report/* - src/NET8MinimalAPI/Report/* - src/NET8Native/Report/* - - src/NET8NativeMinimalApi/Report/* + - src/NET8NativeMinimalAPI/Report/* name: loadtest-$(date +%Y-%m-%H-%M) \ No newline at end of file From 2ad45f16ac88bb6a3b214044d58bd578b481df3d Mon Sep 17 00:00:00 2001 From: Riccardo Venturi Date: Mon, 13 Nov 2023 08:07:25 +0100 Subject: [PATCH 23/32] refactoring NET6 projects adding load testing script --- loadtest/codebuild/run-all-load-tests.sh | 67 +++++++ src/NET6/DeleteProduct/DeleteProduct.csproj | 14 +- src/NET6/GenerateLoadTestResults/DateUtils.cs | 13 -- src/NET6/GenerateLoadTestResults/Function.cs | 146 -------------- .../GenerateLoadTestResults.csproj | 17 -- .../GenerateLoadTestResults/QueryResult.cs | 25 --- src/NET6/GetProduct/GetProduct.csproj | 14 +- src/NET6/GetProducts/GetProducts.csproj | 14 +- src/NET6/PutProduct/PutProduct.csproj | 14 +- src/NET6/Shared/Shared.csproj | 6 +- src/NET6/deploy.sh | 43 ++++ src/NET6/run-loadtest.sh | 153 ++++++++++++++ src/NET6/template.yaml | 187 ++++++++++++------ .../DeleteProduct/DeleteProduct.csproj | 14 +- .../GenerateLoadTestResults/DateUtils.cs | 13 -- .../GenerateLoadTestResults/Function.cs | 146 -------------- .../GenerateLoadTestResults.csproj | 17 -- .../GenerateLoadTestResults/QueryResult.cs | 25 --- .../GetProduct/GetProduct.csproj | 14 +- .../GetProducts/GetProducts.csproj | 14 +- .../PutProduct/PutProduct.csproj | 14 +- src/NET6Containers/Shared/Shared.csproj | 6 +- src/NET6Containers/deploy.sh | 51 +++++ src/NET6Containers/run-loadtest.sh | 152 ++++++++++++++ src/NET6Containers/template.yaml | 98 ++++----- .../DeleteProduct/DeleteProduct.csproj | 12 +- .../GenerateLoadTestResults/DateUtils.cs | 13 -- .../GenerateLoadTestResults/Function.cs | 146 -------------- .../GenerateLoadTestResults.csproj | 17 -- .../GenerateLoadTestResults/QueryResult.cs | 25 --- .../GetProduct/GetProduct.csproj | 12 +- .../GetProducts/GetProducts.csproj | 12 +- src/NET6CustomRuntime/Makefile | 26 ++- .../PutProduct/PutProduct.csproj | 12 +- src/NET6CustomRuntime/Shared/Shared.csproj | 8 +- src/NET6CustomRuntime/deploy.sh | 43 ++++ src/NET6CustomRuntime/run-loadtest.sh | 153 ++++++++++++++ src/NET6CustomRuntime/template.yaml | 165 +++++++++++++--- src/NET6MinimalAPI/deploy.sh | 2 +- src/NET6MinimalAPI/run-loadtest.sh | 5 +- src/NET6MinimalAPIWebAdapter/deploy.sh | 2 +- src/NET6MinimalAPIWebAdapter/run-loadtest.sh | 5 +- .../DeleteProduct/DeleteProduct.csproj | 18 +- src/NET6Native/GetProduct/GetProduct.csproj | 18 +- src/NET6Native/GetProducts/GetProducts.csproj | 18 +- src/NET6Native/Makefile | 23 +++ src/NET6Native/PutProduct/PutProduct.csproj | 18 +- src/NET6Native/Shared/Shared.csproj | 10 +- src/NET6Native/deploy.sh | 43 ++++ src/NET6Native/run-loadtest.sh | 153 ++++++++++++++ src/NET6Native/template.yaml | 178 +++++++++++++++-- .../DeleteProduct/DeleteProduct.csproj | 16 +- .../GenerateLoadTestResults/DateUtils.cs | 13 -- .../GenerateLoadTestResults/Function.cs | 125 ------------ .../GenerateLoadTestResults.csproj | 17 -- .../GenerateLoadTestResults/QueryResult.cs | 25 --- .../GetProduct/GetProduct.csproj | 17 +- .../GetProducts/GetProducts.csproj | 16 +- .../PutProduct/PutProduct.csproj | 16 +- .../Shared/Shared.csproj | 10 +- src/NET6TopLevelStatements/deploy.sh | 43 ++++ src/NET6TopLevelStatements/run-loadtest.sh | 153 ++++++++++++++ src/NET6TopLevelStatements/template.yaml | 173 ++++++++++++---- .../DeleteProduct/DeleteProduct.csproj | 10 +- .../GenerateLoadTestResults/DateUtils.cs | 13 -- .../GenerateLoadTestResults/Function.cs | 146 -------------- .../GenerateLoadTestResults.csproj | 17 -- .../GenerateLoadTestResults/QueryResult.cs | 25 --- .../GetProduct/GetProduct.csproj | 10 +- .../GetProducts/GetProducts.csproj | 10 +- .../PutProduct/PutProduct.csproj | 10 +- src/NET6WithPowerTools/Shared/Shared.csproj | 6 +- src/NET6WithPowerTools/deploy.sh | 43 ++++ src/NET6WithPowerTools/run-loadtest.sh | 153 ++++++++++++++ src/NET6WithPowerTools/template.yaml | 173 ++++++++++++---- 75 files changed, 2216 insertions(+), 1438 deletions(-) delete mode 100644 src/NET6/GenerateLoadTestResults/DateUtils.cs delete mode 100644 src/NET6/GenerateLoadTestResults/Function.cs delete mode 100644 src/NET6/GenerateLoadTestResults/GenerateLoadTestResults.csproj delete mode 100644 src/NET6/GenerateLoadTestResults/QueryResult.cs create mode 100755 src/NET6/deploy.sh create mode 100755 src/NET6/run-loadtest.sh delete mode 100644 src/NET6Containers/GenerateLoadTestResults/DateUtils.cs delete mode 100644 src/NET6Containers/GenerateLoadTestResults/Function.cs delete mode 100644 src/NET6Containers/GenerateLoadTestResults/GenerateLoadTestResults.csproj delete mode 100644 src/NET6Containers/GenerateLoadTestResults/QueryResult.cs create mode 100755 src/NET6Containers/deploy.sh create mode 100755 src/NET6Containers/run-loadtest.sh delete mode 100644 src/NET6CustomRuntime/GenerateLoadTestResults/DateUtils.cs delete mode 100644 src/NET6CustomRuntime/GenerateLoadTestResults/Function.cs delete mode 100644 src/NET6CustomRuntime/GenerateLoadTestResults/GenerateLoadTestResults.csproj delete mode 100644 src/NET6CustomRuntime/GenerateLoadTestResults/QueryResult.cs create mode 100755 src/NET6CustomRuntime/deploy.sh create mode 100755 src/NET6CustomRuntime/run-loadtest.sh create mode 100644 src/NET6Native/Makefile create mode 100755 src/NET6Native/deploy.sh create mode 100755 src/NET6Native/run-loadtest.sh delete mode 100644 src/NET6TopLevelStatements/GenerateLoadTestResults/DateUtils.cs delete mode 100644 src/NET6TopLevelStatements/GenerateLoadTestResults/Function.cs delete mode 100644 src/NET6TopLevelStatements/GenerateLoadTestResults/GenerateLoadTestResults.csproj delete mode 100644 src/NET6TopLevelStatements/GenerateLoadTestResults/QueryResult.cs create mode 100755 src/NET6TopLevelStatements/deploy.sh create mode 100755 src/NET6TopLevelStatements/run-loadtest.sh delete mode 100644 src/NET6WithPowerTools/GenerateLoadTestResults/DateUtils.cs delete mode 100644 src/NET6WithPowerTools/GenerateLoadTestResults/Function.cs delete mode 100644 src/NET6WithPowerTools/GenerateLoadTestResults/GenerateLoadTestResults.csproj delete mode 100644 src/NET6WithPowerTools/GenerateLoadTestResults/QueryResult.cs create mode 100755 src/NET6WithPowerTools/deploy.sh create mode 100755 src/NET6WithPowerTools/run-loadtest.sh diff --git a/loadtest/codebuild/run-all-load-tests.sh b/loadtest/codebuild/run-all-load-tests.sh index 2febd69..9dd3d9a 100755 --- a/loadtest/codebuild/run-all-load-tests.sh +++ b/loadtest/codebuild/run-all-load-tests.sh @@ -2,6 +2,7 @@ TEST_DURATIOMN_SEC=60 LOG_INTERVAL_MIN=20 LOG_DELETE=yes DELETE_STACK=yes +ECR_URI=NotSet if [ "x${LT_TEST_DURATIOMN_SEC}" != x ]; then @@ -23,13 +24,49 @@ then DELETE_STACK=$LT_DELETE_STACK fi +if [ "x${LT_ECRURI}" != x ]; +then + ECR_URI=$LT_ECRURI +fi + echo -------------------------------------------- echo TEST_DURATIOMN_SEC: $TEST_DURATIOMN_SEC echo LOG_INTERVAL_MIN: $LOG_INTERVAL_MIN echo LOG_DELETE: $LOG_DELETE echo DELETE_STACK: $DELETE_STACK +echo ECR_URI: $ECR_URI echo -------------------------------------------- +if [ "$LT_NET6" != yes ]; +then + echo SKIPPING net6 - LT_NET6=$LT_NET6 +else + echo "RUNNING load test for net6" + cd ../../src/NET6/ + source ./deploy.sh $DELETE_STACK + source ./run-loadtest.sh $TEST_DURATIOMN_SEC $LOG_INTERVAL_MIN $LOG_DELETE +fi + +if [ "$LT_NET6_CONTAINERS" != yes ]; +then + echo SKIPPING net6 containers - LT_NET6_CONTAINERS=$LT_NET6_CONTAINERS +else + echo "RUNNING load test for net6 containers" + cd ../../src/NET6Containers/ + source ./deploy.sh $ECR_URI $DELETE_STACK + source ./run-loadtest.sh $TEST_DURATIOMN_SEC $LOG_INTERVAL_MIN $LOG_DELETE +fi + +if [ "$LT_NET6_CUSTOM" != yes ]; +then + echo SKIPPING net6 custom runtime - LT_NET6_CUSTOM=$LT_NET6_CUSTOM +else + echo "RUNNING load test for net6 custom runtime" + cd ../../src/NET6CustomRuntime/ + source ./deploy.sh $DELETE_STACK + source ./run-loadtest.sh $TEST_DURATIOMN_SEC $LOG_INTERVAL_MIN $LOG_DELETE +fi + if [ "$LT_NET6_MINIMAL_API" != yes ]; then echo SKIPPING net6 minimal api - LT_NET6_MINIMAL_API=$LT_NET6_MINIMAL_API @@ -50,6 +87,36 @@ else source ./run-loadtest.sh $TEST_DURATIOMN_SEC $LOG_INTERVAL_MIN $LOG_DELETE fi +if [ "$LT_NET6_NATIVE" != yes ]; +then + echo SKIPPING net6 native - LT_NET6_NATIVE = $LT_NET6_NATIVE +else + echo "RUNNING load test for net6 native" + cd ../../src/NET6Native/ + source ./deploy.sh $DELETE_STACK + source ./run-loadtest.sh $TEST_DURATIOMN_SEC $LOG_INTERVAL_MIN $LOG_DELETE +fi + +if [ "$LT_NET6_TOPLEVEL" != yes ]; +then + echo SKIPPING net6 top level - LT_NET6_TOPLEVEL = $LT_NET6_TOPLEVEL +else + echo "RUNNING load test for net6 top level" + cd ../../src/NET6TopLevelStatements/ + source ./deploy.sh $DELETE_STACK + source ./run-loadtest.sh $TEST_DURATIOMN_SEC $LOG_INTERVAL_MIN $LOG_DELETE +fi + +if [ "$LT_NET6_POWERTOOLS" != yes ]; +then + echo SKIPPING net6 power tools - LT_NET6_POWERTOOLS = $LT_NET6_POWERTOOLS +else + echo "RUNNING load test for net6 power tools" + cd ../../src/NET6WithPowerTools/ + source ./deploy.sh $DELETE_STACK + source ./run-loadtest.sh $TEST_DURATIOMN_SEC $LOG_INTERVAL_MIN $LOG_DELETE +fi + if [ "$LT_NET8" != yes ]; then echo SKIPPING net8 - LT_NET8=$LT_NET8 diff --git a/src/NET6/DeleteProduct/DeleteProduct.csproj b/src/NET6/DeleteProduct/DeleteProduct.csproj index c22ca94..3fe0a1c 100755 --- a/src/NET6/DeleteProduct/DeleteProduct.csproj +++ b/src/NET6/DeleteProduct/DeleteProduct.csproj @@ -6,13 +6,13 @@ true - - - - - - - + + + + + + + diff --git a/src/NET6/GenerateLoadTestResults/DateUtils.cs b/src/NET6/GenerateLoadTestResults/DateUtils.cs deleted file mode 100644 index c37c43d..0000000 --- a/src/NET6/GenerateLoadTestResults/DateUtils.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; - -namespace GenerateLoadTestResults; - -public static class DateUtils -{ - public static long AsUnixTimestamp(this DateTime date) - { - DateTime origin = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); - TimeSpan diff = date.ToUniversalTime() - origin; - return (long)Math.Floor(diff.TotalSeconds); - } -} \ No newline at end of file diff --git a/src/NET6/GenerateLoadTestResults/Function.cs b/src/NET6/GenerateLoadTestResults/Function.cs deleted file mode 100644 index 21a57a8..0000000 --- a/src/NET6/GenerateLoadTestResults/Function.cs +++ /dev/null @@ -1,146 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Text.Json; -using System.Threading.Tasks; -using Amazon.CloudWatchLogs; -using Amazon.CloudWatchLogs.Model; -using Amazon.Lambda.APIGatewayEvents; -using Amazon.Lambda.Core; - -[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] - -namespace GenerateLoadTestResults -{ - public class Function - { - private AmazonCloudWatchLogsClient _cloudWatchLogsClient; - - public Function() - { - this._cloudWatchLogsClient = new AmazonCloudWatchLogsClient(); - } - - public async Task FunctionHandler( - APIGatewayHttpApiV2ProxyRequest apigProxyEvent, - ILambdaContext context) - { - if (!apigProxyEvent.RequestContext.Http.Method.Equals(HttpMethod.Get.Method)) - { - return new APIGatewayHttpApiV2ProxyResponse - { - Body = "Only GET allowed", - StatusCode = (int) HttpStatusCode.MethodNotAllowed, - }; - } - - try - { - var resultRows = 0; - var queryCount = 0; - - List> finalResults = new List>(); - - while (resultRows < 2 || queryCount >= 3) - { - finalResults = await runQuery(context); - - resultRows = finalResults.Count; - queryCount++; - } - - var wrapper = new QueryResultWrapper() - { - LoadTestType = - $"{Environment.GetEnvironmentVariable("LOAD_TEST_TYPE")} ({Environment.GetEnvironmentVariable("LAMBDA_ARCHITECTURE")})", - WarmStart = new QueryResult() - { - Count = finalResults[0][1].Value, - P50 = finalResults[0][2].Value, - P90 = finalResults[0][3].Value, - P99 = finalResults[0][4].Value, - Max = finalResults[0][5].Value, - }, - ColdStart = new QueryResult() - { - Count = finalResults[1][1].Value, - P50 = finalResults[1][2].Value, - P90 = finalResults[1][3].Value, - P99 = finalResults[1][4].Value, - Max = finalResults[1][5].Value, - } - }; - - return new APIGatewayHttpApiV2ProxyResponse - { - StatusCode = (int) HttpStatusCode.OK, - Body = wrapper.AsMarkdownTableRow(), - Headers = new Dictionary {{"Content-Type", "text/html"}} - }; - } - catch (Exception e) - { - context.Logger.LogLine($"Error retrieving results {e.Message} {e.StackTrace}"); - - return new APIGatewayHttpApiV2ProxyResponse - { - Body = "Not Found", - StatusCode = (int) HttpStatusCode.InternalServerError, - }; - } - } - - private async Task>> runQuery(ILambdaContext context) - { - var logGroupNamePrefix = - $"{Environment.GetEnvironmentVariable("LOG_GROUP_PREFIX")}{Environment.GetEnvironmentVariable("LAMBDA_ARCHITECTURE")}" - .Replace("_", "-"); - - context.Logger.LogLine($"Retrieving log groups with prefix {logGroupNamePrefix}"); - - var logGroupList = await _cloudWatchLogsClient.DescribeLogGroupsAsync(new DescribeLogGroupsRequest() - { - LogGroupNamePrefix = logGroupNamePrefix, - }); - - context.Logger.LogLine($"Found {logGroupList.LogGroups.Count} log group(s)"); - - var queryRes = await _cloudWatchLogsClient.StartQueryAsync(new StartQueryRequest() - { - LogGroupNames = logGroupList.LogGroups.Select(p => p.LogGroupName).ToList(), - QueryString = - "filter @type=\"REPORT\" | fields greatest(@initDuration, 0) + @duration as duration, ispresent(@initDuration) as coldstart | stats count(*) as count, pct(duration, 50) as p50, pct(duration, 90) as p90, pct(duration, 99) as p99, max(duration) as max by coldstart", - StartTime = DateTime.Now.AddMinutes(-20).AsUnixTimestamp(), - EndTime = DateTime.Now.AsUnixTimestamp(), - }); - - context.Logger.LogLine($"Running query, query id is {queryRes.QueryId}"); - - QueryStatus currentQueryStatus = QueryStatus.Running; - List> finalResults = new List>(); - - while (currentQueryStatus == QueryStatus.Running || currentQueryStatus == QueryStatus.Scheduled) - { - context.Logger.LogLine("Retrieving query results"); - - var queryResults = await _cloudWatchLogsClient.GetQueryResultsAsync(new GetQueryResultsRequest() - { - QueryId = queryRes.QueryId - }); - - context.Logger.LogLine($"Query result status is {queryResults.Status}"); - - currentQueryStatus = queryResults.Status; - finalResults = queryResults.Results; - - await Task.Delay(TimeSpan.FromSeconds(5)); - } - - context.Logger.LogLine($"Final results: {finalResults.Count} row(s)"); - - return finalResults; - } - } -} \ No newline at end of file diff --git a/src/NET6/GenerateLoadTestResults/GenerateLoadTestResults.csproj b/src/NET6/GenerateLoadTestResults/GenerateLoadTestResults.csproj deleted file mode 100644 index 4a8d708..0000000 --- a/src/NET6/GenerateLoadTestResults/GenerateLoadTestResults.csproj +++ /dev/null @@ -1,17 +0,0 @@ - - - - net6.0 - true - true - - - - - - - - - - - diff --git a/src/NET6/GenerateLoadTestResults/QueryResult.cs b/src/NET6/GenerateLoadTestResults/QueryResult.cs deleted file mode 100644 index 69b5a31..0000000 --- a/src/NET6/GenerateLoadTestResults/QueryResult.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace GenerateLoadTestResults; - -public record QueryResultWrapper -{ - public string LoadTestType { get; set; } - - public QueryResult ColdStart { get; set; } - - public QueryResult WarmStart { get; set; } - - public string AsMarkdownTableRow() => $"
Cold Start (ms)Warm Start (ms)
p50p90p99maxp50p90p99max
{LoadTestType}{ColdStart.P50}{ColdStart.P90}{ColdStart.P99}{ColdStart.Max}{WarmStart.P50}{WarmStart.P90}{WarmStart.P99}{WarmStart.Max}
"; -} - -public record QueryResult -{ - public string Count { get; set; } - - public string P50 { get; set; } - - public string P90 { get; set; } - - public string P99 { get; set; } - - public string Max { get; set; } -} \ No newline at end of file diff --git a/src/NET6/GetProduct/GetProduct.csproj b/src/NET6/GetProduct/GetProduct.csproj index c22ca94..3fe0a1c 100755 --- a/src/NET6/GetProduct/GetProduct.csproj +++ b/src/NET6/GetProduct/GetProduct.csproj @@ -6,13 +6,13 @@ true - - - - - - - + + + + + + + diff --git a/src/NET6/GetProducts/GetProducts.csproj b/src/NET6/GetProducts/GetProducts.csproj index c22ca94..3fe0a1c 100755 --- a/src/NET6/GetProducts/GetProducts.csproj +++ b/src/NET6/GetProducts/GetProducts.csproj @@ -6,13 +6,13 @@ true - - - - - - - + + + + + + + diff --git a/src/NET6/PutProduct/PutProduct.csproj b/src/NET6/PutProduct/PutProduct.csproj index c22ca94..3fe0a1c 100755 --- a/src/NET6/PutProduct/PutProduct.csproj +++ b/src/NET6/PutProduct/PutProduct.csproj @@ -6,13 +6,13 @@ true - - - - - - - + + + + + + + diff --git a/src/NET6/Shared/Shared.csproj b/src/NET6/Shared/Shared.csproj index e1fbf59..e3a7858 100755 --- a/src/NET6/Shared/Shared.csproj +++ b/src/NET6/Shared/Shared.csproj @@ -4,8 +4,8 @@ netstandard2.1 - - - + + +
diff --git a/src/NET6/deploy.sh b/src/NET6/deploy.sh new file mode 100755 index 0000000..0a05a23 --- /dev/null +++ b/src/NET6/deploy.sh @@ -0,0 +1,43 @@ +#Arguments: +#$1 - delete stack formation to ensure that all test have same conditon and favour similar ammount of cold start events + +STACK_NAME=dotnet6 +DELETE_STACK=yes + +COLOR='\033[0;33m' +NO_COLOR='\033[0m' # No Color + +if [ "x$1" != x ]; +then + DELETE_STACK=$1 +fi + +echo "${COLOR}" +echo -------------------------------------------- +echo DELETE_STACK: $DELETE_STACK +echo -------------------------------------------- +echo "${NO_COLOR}" + +if [ $DELETE_STACK == "yes" ]; +then + echo "${COLOR}" + echo -------------------------------------------- + echo DELETING STACK $STACK_NAME + echo -------------------------------------------- + echo "${NO_COLOR}" + aws cloudformation delete-stack --stack-name $STACK_NAME + echo "${COLOR}" + echo --------------------------------------------- + echo Waiting stack to be deleted + echo -------------------------------------------- + echo "${NO_COLOR}" + aws cloudformation wait stack-delete-complete --stack-name $STACK_NAME + echo "${COLOR}" + echo --------------------------------------------- + echo Stack deleted + echo -------------------------------------------- + echo "${NO_COLOR}" +fi + +sam build +sam deploy --stack-name $STACK_NAME --resolve-s3 --s3-prefix $STACK_NAME --no-confirm-changeset --no-fail-on-empty-changeset --capabilities CAPABILITY_IAM \ No newline at end of file diff --git a/src/NET6/run-loadtest.sh b/src/NET6/run-loadtest.sh new file mode 100755 index 0000000..981b6cd --- /dev/null +++ b/src/NET6/run-loadtest.sh @@ -0,0 +1,153 @@ +#Arguments: +#$1 - load test duration in seconds +#$2 - log interval to be used in the cloudwatch query in minutes +#$3 - when equal to 1 cloudwatch log group will be deleted to ensure that only logs of the load test will be evaluated for stat + +STACK_NAME=dotnet6 +TEST_DURATIOMN_SEC=60 +LOG_INTERVAL_MIN=20 +LOG_DELETE=yes + +COLOR='\033[0;33m' +NO_COLOR='\033[0m' # No Color + +if [ "x$1" != x ]; +then + TEST_DURATIOMN_SEC=$1 +fi + +if [ "x$2" != x ]; +then + LOG_INTERVAL_MIN=$2 +fi + +if [ "x$3" != x ]; +then + LOG_DELETE=$3 +fi + +echo "${COLOR}" +echo -------------------------------------------- +echo DURATION:$TEST_DURATIOMN_SEC +echo LOG INTERVAL:$LOG_INTERVAL_MIN +echo LOG_DELETE: $LOG_DELETE +echo -------------------------------------------- +echo "${NO_COLOR}" + +mkdir -p Report + +function RunLoadTest() +{ + #Params: + #$1 - Architecture (x86 or arm64).Used for logging and naming report file + #$2 - Stack output name to get API Url + #$3 - Stack output name to get lambda name GetProducts + #$4 - Stack output name to get lambda name GetProduct + #$5 - Stack output name to get lambda name DeleteProduct + #$6 - Stack output name to get lambda name PutProduct + + #get test params from cloud formation output + echo "${COLOR}" + export API_URL=$(aws cloudformation describe-stacks --stack-name $STACK_NAME \ + --query "Stacks[0].Outputs[?OutputKey=='$2'].OutputValue" \ + --output text) + echo API URL: $API_URL + + LAMBDA_GETPRODUCTS=$(aws cloudformation describe-stacks --stack-name $STACK_NAME \ + --query "Stacks[0].Outputs[?OutputKey=='$3'].OutputValue" \ + --output text) + echo LAMBDA: $LAMBDA_GETPRODUCTS + + LAMBDA_GETPRODUCT=$(aws cloudformation describe-stacks --stack-name $STACK_NAME \ + --query "Stacks[0].Outputs[?OutputKey=='$4'].OutputValue" \ + --output text) + echo LAMBDA: $LAMBDA_GETPRODUCT + + LAMBDA_DELETEPRODUCT=$(aws cloudformation describe-stacks --stack-name $STACK_NAME \ + --query "Stacks[0].Outputs[?OutputKey=='$5'].OutputValue" \ + --output text) + echo LAMBDA: $LAMBDA_DELETEPRODUCT + + LAMBDA_PUTPRODUCT=$(aws cloudformation describe-stacks --stack-name $STACK_NAME \ + --query "Stacks[0].Outputs[?OutputKey=='$6'].OutputValue" \ + --output text) + echo LAMBDA: $LAMBDA_PUTPRODUCT + + if [ $LOG_DELETE == "yes" ]; + then + echo -------------------------------------------- + echo DELETING CLOUDWATCH LOG GROUP /aws/lambda/$LAMBDA_GETPRODUCTS + echo -------------------------------------------- + aws logs delete-log-group --log-group-name /aws/lambda/$LAMBDA_GETPRODUCTS + echo -------------------------------------------- + echo DELETING CLOUDWATCH LOG GROUP /aws/lambda/$LAMBDA_GETPRODUCT + echo -------------------------------------------- + aws logs delete-log-group --log-group-name /aws/lambda/$LAMBDA_GETPRODUCT + echo -------------------------------------------- + echo DELETING CLOUDWATCH LOG GROUP /aws/lambda/$LAMBDA_DELETEPRODUCT + echo -------------------------------------------- + aws logs delete-log-group --log-group-name /aws/lambda/$LAMBDA_DELETEPRODUCT + echo -------------------------------------------- + echo DELETING CLOUDWATCH LOG GROUP /aws/lambda/$LAMBDA_PUTPRODUCT + echo -------------------------------------------- + aws logs delete-log-group --log-group-name /aws/lambda/$LAMBDA_PUTPRODUCT + echo --------------------------------------------- + echo Waiting 10 sec. for deletion to complete + echo -------------------------------------------- + sleep 10 + fi + + #run load test with artillery + echo -------------------------------------------- + echo $1 RUNNING LOAD TEST $TEST_DURATIOMN_SEC sec $API_URL + echo -------------------------------------------- + echo "${NO_COLOR}" + artillery run \ + --overrides '{"config": { "phases": [{ "duration": '$TEST_DURATIOMN_SEC', "arrivalRate": 100 }] } }' \ + --quiet \ + ../../loadtest/codebuild/load-test.yml + + echo "${COLOR}" + echo -------------------------------------------- + echo Waiting 10 sec. for logs to consolidate + echo -------------------------------------------- + sleep 10 + + #get stats from cloudwatch + enddate=$(date "+%s") + startdate=$(($enddate-($LOG_INTERVAL_MIN*60))) + echo -------------------------------------------- + echo Log start:$startdate end:$enddate + echo -------------------------------------------- + + QUERY_ID=$(aws logs start-query \ + --log-group-names "/aws/lambda/$LAMBDA_GETPRODUCTS" "/aws/lambda/$LAMBDA_GETPRODUCT" "/aws/lambda/$LAMBDA_DELETEPRODUCT" "/aws/lambda/$LAMBDA_PUTPRODUCT" \ + --start-time $startdate \ + --end-time $enddate \ + --query-string 'filter @type="REPORT" | fields greatest(@initDuration, 0) + @duration as duration, ispresent(@initDuration) as coldstart | stats count(*) as count, pct(duration, 50) as p50, pct(duration, 90) as p90, pct(duration, 99) as p99, max(duration) as max by coldstart' \ + | jq -r '.queryId') + + echo -------------------------------------------- + echo Query started, id: $QUERY_ID + echo -------------------------------------------- + + echo --------------------------------------------- + echo Waiting 10 sec. for cloudwatch query to complete + echo -------------------------------------------- + sleep 10 + + echo -------------------------------------------- + echo RESULTS $1 + echo -------------------------------------------- + echo "${NO_COLOR}" + date > ./Report/load-test-report-$1.txt + echo $1 RESULTS >> ./Report/load-test-report-$1.txt + echo Test duration sec: $TEST_DURATIOMN_SEC >> ./Report/load-test-report-$1.txt + echo Log interval min: $LOG_INTERVAL_MIN >> ./Report/load-test-report-$1.txt + aws logs get-query-results --query-id $QUERY_ID --output text >> ./Report/load-test-report-$1.txt + cat ./Report/load-test-report-$1.txt + aws logs get-query-results --query-id $QUERY_ID --output json >> ./Report/load-test-report-$1.json +} + +RunLoadTest x86 ApiUrlX86 LambdaX86NameGetProducts LambdaX86NameGetProduct LambdaX86NameDeleteProduct LambdaX86NamePutProduct +RunLoadTest arm64 ApiUrlArm64 LambdaArm64NameGetProducts LambdaArm64NameGetProduct LambdaArm64NameDeleteProduct LambdaArm64NamePutProduct \ No newline at end of file diff --git a/src/NET6/template.yaml b/src/NET6/template.yaml index 02504fb..848cf70 100755 --- a/src/NET6/template.yaml +++ b/src/NET6/template.yaml @@ -1,13 +1,16 @@ AWSTemplateFormatVersion: "2010-09-09" Transform: AWS::Serverless-2016-10-31 +Parameters: + x86FunctionNamePrefix: + Type: String + Default: Net6-X86 + arm64FunctionNamePrefix: + Type: String + Default: Net6-Arm64 Globals: - HttpApi: - Auth: - EnableIamAuthorizer: true Function: MemorySize: 1024 - Architectures: [!Ref LambdaArchitecture] Runtime: dotnet6 Timeout: 30 Tracing: Active @@ -15,49 +18,39 @@ Globals: Variables: PRODUCT_TABLE_NAME: !Ref Table -Parameters: - LambdaArchitecture: - Type: String - AllowedValues: - - arm64 - - x86_64 - Description: Enter arm64 or x86_64 - - Resources: - GetProductsFunction: + #X86 + GetProductsFunctionX86: Type: AWS::Serverless::Function Properties: + FunctionName: !Join [ -,[!Ref x86FunctionNamePrefix,GetProducts]] + Architectures: [x86_64] CodeUri: ./GetProducts/ Handler: GetProducts::GetProducts.Function::FunctionHandler Events: Api: Type: HttpApi Properties: - Path: / + Path: /x86 Method: GET - Auth: - Authorizer: AWS_IAM Policies: - - Version: "2012-10-17" - Statement: - - Effect: Allow - Action: dynamodb:Scan - Resource: !GetAtt Table.Arn + - DynamoDBReadPolicy: + TableName: + !Ref Table - GetProductFunction: + GetProductFunctionX86: Type: AWS::Serverless::Function Properties: + FunctionName: !Join [ -,[!Ref x86FunctionNamePrefix,GetProduct]] + Architectures: [x86_64] CodeUri: ./GetProduct/ Handler: GetProduct::GetProduct.Function::FunctionHandler Events: Api: Type: HttpApi Properties: - Path: /{id} + Path: /x86/{id} Method: GET - Auth: - Authorizer: AWS_IAM Policies: - Version: "2012-10-17" Statement: @@ -65,19 +58,19 @@ Resources: Action: dynamodb:GetItem Resource: !GetAtt Table.Arn - DeleteProductFunction: + DeleteProductFunctionX86: Type: AWS::Serverless::Function Properties: + FunctionName: !Join [ -,[!Ref x86FunctionNamePrefix,DeleteProduct]] + Architectures: [x86_64] CodeUri: ./DeleteProduct/ Handler: DeleteProduct::DeleteProduct.Function::FunctionHandler Events: Api: Type: HttpApi Properties: - Path: /{id} + Path: /x86/{id} Method: DELETE - Auth: - Authorizer: AWS_IAM Policies: - Version: "2012-10-17" Statement: @@ -87,57 +80,105 @@ Resources: - dynamodb:GetItem Resource: !GetAtt Table.Arn - PutProductFunction: + PutProductFunctionX86: Type: AWS::Serverless::Function Properties: + FunctionName: !Join [ -,[!Ref x86FunctionNamePrefix,PutProduct]] + Architectures: [x86_64] CodeUri: ./PutProduct/ Handler: PutProduct::PutProduct.Function::FunctionHandler Events: Api: Type: HttpApi Properties: - Path: /{id} + Path: /x86/{id} Method: PUT - Auth: - Authorizer: AWS_IAM Policies: - Version: "2012-10-17" Statement: - Effect: Allow Action: dynamodb:PutItem Resource: !GetAtt Table.Arn +#ARM64 + GetProductsFunctionArm64: + Type: AWS::Serverless::Function + Properties: + FunctionName: !Join [ -,[!Ref arm64FunctionNamePrefix,GetProducts]] + Architectures: [arm64] + CodeUri: ./GetProducts/ + Handler: GetProducts::GetProducts.Function::FunctionHandler + Events: + Api: + Type: HttpApi + Properties: + Path: /arm64 + Method: GET + Policies: + - DynamoDBReadPolicy: + TableName: + !Ref Table - GenerateLoadTestResults: + GetProductFunctionArm64: Type: AWS::Serverless::Function Properties: - CodeUri: ./GenerateLoadTestResults/ - Handler: GenerateLoadTestResults::GenerateLoadTestResults.Function::FunctionHandler + FunctionName: !Join [ -,[!Ref arm64FunctionNamePrefix,GetProduct]] + Architectures: [arm64] + CodeUri: ./GetProduct/ + Handler: GetProduct::GetProduct.Function::FunctionHandler Events: Api: Type: HttpApi Properties: - Path: /test-results + Path: /arm64/{id} Method: GET - Auth: - Authorizer: AWS_IAM - Environment: - Variables: - LOG_GROUP_PREFIX: !Sub "/aws/lambda/net-6-base-" - LOAD_TEST_TYPE: "NET 6" - LAMBDA_ARCHITECTURE: !Ref LambdaArchitecture Policies: - Version: "2012-10-17" Statement: - - Sid: AllowStartQueries - Effect: Allow + - Effect: Allow + Action: dynamodb:GetItem + Resource: !GetAtt Table.Arn + + DeleteProductFunctionArm64: + Type: AWS::Serverless::Function + Properties: + FunctionName: !Join [ -,[!Ref arm64FunctionNamePrefix,DeleteProduct]] + Architectures: [arm64] + CodeUri: ./DeleteProduct/ + Handler: DeleteProduct::DeleteProduct.Function::FunctionHandler + Events: + Api: + Type: HttpApi + Properties: + Path: /arm64/{id} + Method: DELETE + Policies: + - Version: "2012-10-17" + Statement: + - Effect: Allow Action: - - logs:DescribeLogGroups - - logs:StartQuery - Resource: "*" - - Sid: AllowGetQueryResults - Effect: Allow - Action: logs:GetQueryResults - Resource: "*" + - dynamodb:DeleteItem + - dynamodb:GetItem + Resource: !GetAtt Table.Arn + + PutProductFunctionArm64: + Type: AWS::Serverless::Function + Properties: + FunctionName: !Join [ -,[!Ref arm64FunctionNamePrefix,PutProduct]] + Architectures: [arm64] + CodeUri: ./PutProduct/ + Handler: PutProduct::PutProduct.Function::FunctionHandler + Events: + Api: + Type: HttpApi + Properties: + Path: /arm64/{id} + Method: PUT + Policies: + - Version: "2012-10-17" + Statement: + - Effect: Allow + Action: dynamodb:PutItem + Resource: !GetAtt Table.Arn Table: Type: AWS::DynamoDB::Table @@ -149,10 +190,42 @@ Resources: KeySchema: - AttributeName: id KeyType: HASH - StreamSpecification: - StreamViewType: NEW_AND_OLD_IMAGES Outputs: ApiUrl: Description: "API Gateway endpoint URL" - Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/" + Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com" + ApiUrlX86: + Description: "X86 API endpoint URL" + Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/x86" + ApiUrlArm64: + Description: "Arm64 GateAPI endpoint URL" + Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/arm64" + + #x86 + LambdaX86NameGetProducts: + Description: "Lambda X86 GetProducts" + Value: !Join [ -,[!Ref x86FunctionNamePrefix,GetProducts]] + LambdaX86NameGetProduct: + Description: "Lambda X86 GetProduct" + Value: !Join [ -,[!Ref x86FunctionNamePrefix,GetProduct]] + LambdaX86NameDeleteProduct: + Description: "Lambda X86 DeleteProduct" + Value: !Join [ -,[!Ref x86FunctionNamePrefix,DeleteProduct]] + LambdaX86NamePutProduct: + Description: "Lambda X86 PutProduct" + Value: !Join [ -,[!Ref x86FunctionNamePrefix,PutProduct]] + + #arm64 + LambdaArm64NameGetProducts: + Description: "Lambda X86 GetProducts" + Value: !Join [ -,[!Ref arm64FunctionNamePrefix,GetProducts]] + LambdaArm64NameGetProduct: + Description: "Lambda X86 GetProduct" + Value: !Join [ -,[!Ref arm64FunctionNamePrefix,GetProduct]] + LambdaArm64NameDeleteProduct: + Description: "Lambda X86 DeleteProduct" + Value: !Join [ -,[!Ref arm64FunctionNamePrefix,DeleteProduct]] + LambdaArm64NamePutProduct: + Description: "Lambda X86 PutProduct" + Value: !Join [ -,[!Ref arm64FunctionNamePrefix,PutProduct]] \ No newline at end of file diff --git a/src/NET6Containers/DeleteProduct/DeleteProduct.csproj b/src/NET6Containers/DeleteProduct/DeleteProduct.csproj index c22ca94..3fe0a1c 100644 --- a/src/NET6Containers/DeleteProduct/DeleteProduct.csproj +++ b/src/NET6Containers/DeleteProduct/DeleteProduct.csproj @@ -6,13 +6,13 @@ true - - - - - - - + + + + + + + diff --git a/src/NET6Containers/GenerateLoadTestResults/DateUtils.cs b/src/NET6Containers/GenerateLoadTestResults/DateUtils.cs deleted file mode 100644 index c37c43d..0000000 --- a/src/NET6Containers/GenerateLoadTestResults/DateUtils.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; - -namespace GenerateLoadTestResults; - -public static class DateUtils -{ - public static long AsUnixTimestamp(this DateTime date) - { - DateTime origin = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); - TimeSpan diff = date.ToUniversalTime() - origin; - return (long)Math.Floor(diff.TotalSeconds); - } -} \ No newline at end of file diff --git a/src/NET6Containers/GenerateLoadTestResults/Function.cs b/src/NET6Containers/GenerateLoadTestResults/Function.cs deleted file mode 100644 index 21a57a8..0000000 --- a/src/NET6Containers/GenerateLoadTestResults/Function.cs +++ /dev/null @@ -1,146 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Text.Json; -using System.Threading.Tasks; -using Amazon.CloudWatchLogs; -using Amazon.CloudWatchLogs.Model; -using Amazon.Lambda.APIGatewayEvents; -using Amazon.Lambda.Core; - -[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] - -namespace GenerateLoadTestResults -{ - public class Function - { - private AmazonCloudWatchLogsClient _cloudWatchLogsClient; - - public Function() - { - this._cloudWatchLogsClient = new AmazonCloudWatchLogsClient(); - } - - public async Task FunctionHandler( - APIGatewayHttpApiV2ProxyRequest apigProxyEvent, - ILambdaContext context) - { - if (!apigProxyEvent.RequestContext.Http.Method.Equals(HttpMethod.Get.Method)) - { - return new APIGatewayHttpApiV2ProxyResponse - { - Body = "Only GET allowed", - StatusCode = (int) HttpStatusCode.MethodNotAllowed, - }; - } - - try - { - var resultRows = 0; - var queryCount = 0; - - List> finalResults = new List>(); - - while (resultRows < 2 || queryCount >= 3) - { - finalResults = await runQuery(context); - - resultRows = finalResults.Count; - queryCount++; - } - - var wrapper = new QueryResultWrapper() - { - LoadTestType = - $"{Environment.GetEnvironmentVariable("LOAD_TEST_TYPE")} ({Environment.GetEnvironmentVariable("LAMBDA_ARCHITECTURE")})", - WarmStart = new QueryResult() - { - Count = finalResults[0][1].Value, - P50 = finalResults[0][2].Value, - P90 = finalResults[0][3].Value, - P99 = finalResults[0][4].Value, - Max = finalResults[0][5].Value, - }, - ColdStart = new QueryResult() - { - Count = finalResults[1][1].Value, - P50 = finalResults[1][2].Value, - P90 = finalResults[1][3].Value, - P99 = finalResults[1][4].Value, - Max = finalResults[1][5].Value, - } - }; - - return new APIGatewayHttpApiV2ProxyResponse - { - StatusCode = (int) HttpStatusCode.OK, - Body = wrapper.AsMarkdownTableRow(), - Headers = new Dictionary {{"Content-Type", "text/html"}} - }; - } - catch (Exception e) - { - context.Logger.LogLine($"Error retrieving results {e.Message} {e.StackTrace}"); - - return new APIGatewayHttpApiV2ProxyResponse - { - Body = "Not Found", - StatusCode = (int) HttpStatusCode.InternalServerError, - }; - } - } - - private async Task>> runQuery(ILambdaContext context) - { - var logGroupNamePrefix = - $"{Environment.GetEnvironmentVariable("LOG_GROUP_PREFIX")}{Environment.GetEnvironmentVariable("LAMBDA_ARCHITECTURE")}" - .Replace("_", "-"); - - context.Logger.LogLine($"Retrieving log groups with prefix {logGroupNamePrefix}"); - - var logGroupList = await _cloudWatchLogsClient.DescribeLogGroupsAsync(new DescribeLogGroupsRequest() - { - LogGroupNamePrefix = logGroupNamePrefix, - }); - - context.Logger.LogLine($"Found {logGroupList.LogGroups.Count} log group(s)"); - - var queryRes = await _cloudWatchLogsClient.StartQueryAsync(new StartQueryRequest() - { - LogGroupNames = logGroupList.LogGroups.Select(p => p.LogGroupName).ToList(), - QueryString = - "filter @type=\"REPORT\" | fields greatest(@initDuration, 0) + @duration as duration, ispresent(@initDuration) as coldstart | stats count(*) as count, pct(duration, 50) as p50, pct(duration, 90) as p90, pct(duration, 99) as p99, max(duration) as max by coldstart", - StartTime = DateTime.Now.AddMinutes(-20).AsUnixTimestamp(), - EndTime = DateTime.Now.AsUnixTimestamp(), - }); - - context.Logger.LogLine($"Running query, query id is {queryRes.QueryId}"); - - QueryStatus currentQueryStatus = QueryStatus.Running; - List> finalResults = new List>(); - - while (currentQueryStatus == QueryStatus.Running || currentQueryStatus == QueryStatus.Scheduled) - { - context.Logger.LogLine("Retrieving query results"); - - var queryResults = await _cloudWatchLogsClient.GetQueryResultsAsync(new GetQueryResultsRequest() - { - QueryId = queryRes.QueryId - }); - - context.Logger.LogLine($"Query result status is {queryResults.Status}"); - - currentQueryStatus = queryResults.Status; - finalResults = queryResults.Results; - - await Task.Delay(TimeSpan.FromSeconds(5)); - } - - context.Logger.LogLine($"Final results: {finalResults.Count} row(s)"); - - return finalResults; - } - } -} \ No newline at end of file diff --git a/src/NET6Containers/GenerateLoadTestResults/GenerateLoadTestResults.csproj b/src/NET6Containers/GenerateLoadTestResults/GenerateLoadTestResults.csproj deleted file mode 100644 index 4a8d708..0000000 --- a/src/NET6Containers/GenerateLoadTestResults/GenerateLoadTestResults.csproj +++ /dev/null @@ -1,17 +0,0 @@ - - - - net6.0 - true - true - - - - - - - - - - - diff --git a/src/NET6Containers/GenerateLoadTestResults/QueryResult.cs b/src/NET6Containers/GenerateLoadTestResults/QueryResult.cs deleted file mode 100644 index 69b5a31..0000000 --- a/src/NET6Containers/GenerateLoadTestResults/QueryResult.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace GenerateLoadTestResults; - -public record QueryResultWrapper -{ - public string LoadTestType { get; set; } - - public QueryResult ColdStart { get; set; } - - public QueryResult WarmStart { get; set; } - - public string AsMarkdownTableRow() => $"
Cold Start (ms)Warm Start (ms)
p50p90p99maxp50p90p99max
{LoadTestType}{ColdStart.P50}{ColdStart.P90}{ColdStart.P99}{ColdStart.Max}{WarmStart.P50}{WarmStart.P90}{WarmStart.P99}{WarmStart.Max}
"; -} - -public record QueryResult -{ - public string Count { get; set; } - - public string P50 { get; set; } - - public string P90 { get; set; } - - public string P99 { get; set; } - - public string Max { get; set; } -} \ No newline at end of file diff --git a/src/NET6Containers/GetProduct/GetProduct.csproj b/src/NET6Containers/GetProduct/GetProduct.csproj index c22ca94..3fe0a1c 100644 --- a/src/NET6Containers/GetProduct/GetProduct.csproj +++ b/src/NET6Containers/GetProduct/GetProduct.csproj @@ -6,13 +6,13 @@ true - - - - - - - + + + + + + + diff --git a/src/NET6Containers/GetProducts/GetProducts.csproj b/src/NET6Containers/GetProducts/GetProducts.csproj index c22ca94..3fe0a1c 100644 --- a/src/NET6Containers/GetProducts/GetProducts.csproj +++ b/src/NET6Containers/GetProducts/GetProducts.csproj @@ -6,13 +6,13 @@ true - - - - - - - + + + + + + + diff --git a/src/NET6Containers/PutProduct/PutProduct.csproj b/src/NET6Containers/PutProduct/PutProduct.csproj index c22ca94..3fe0a1c 100644 --- a/src/NET6Containers/PutProduct/PutProduct.csproj +++ b/src/NET6Containers/PutProduct/PutProduct.csproj @@ -6,13 +6,13 @@ true - - - - - - - + + + + + + + diff --git a/src/NET6Containers/Shared/Shared.csproj b/src/NET6Containers/Shared/Shared.csproj index e1fbf59..e3a7858 100644 --- a/src/NET6Containers/Shared/Shared.csproj +++ b/src/NET6Containers/Shared/Shared.csproj @@ -4,8 +4,8 @@ netstandard2.1 - - - + + +
diff --git a/src/NET6Containers/deploy.sh b/src/NET6Containers/deploy.sh new file mode 100755 index 0000000..614f82b --- /dev/null +++ b/src/NET6Containers/deploy.sh @@ -0,0 +1,51 @@ +#Arguments: +#$1 - ECR URI +#$2 - delete stack formation to ensure that all test have same conditon and favour similar ammount of cold start events + +STACK_NAME=dotnet6-container +ECR_URI=NotSet +DELETE_STACK=yes + +COLOR='\033[0;33m' +NO_COLOR='\033[0m' # No Color + +if [ "x$1" != x ]; +else + ECR_URI=$1 +fi + +if [ "x$2" != x ]; +then + DELETE_STACK=$2 +fi + +echo "${COLOR}" +echo -------------------------------------------- +echo ECR URI: ECR_URI +echo DELETE_STACK: $DELETE_STACK +echo -------------------------------------------- +echo "${NO_COLOR}" + +if [ $DELETE_STACK == "yes" ]; +then + echo "${COLOR}" + echo -------------------------------------------- + echo DELETING STACK $STACK_NAME + echo -------------------------------------------- + echo "${NO_COLOR}" + aws cloudformation delete-stack --stack-name $STACK_NAME + echo "${COLOR}" + echo --------------------------------------------- + echo Waiting stack to be deleted + echo -------------------------------------------- + echo "${NO_COLOR}" + aws cloudformation wait stack-delete-complete --stack-name $STACK_NAME + echo "${COLOR}" + echo --------------------------------------------- + echo Stack deleted + echo -------------------------------------------- + echo "${NO_COLOR}" +fi + +sam build +sam deploy --stack-name $STACK_NAME --resolve-s3 --s3-prefix $STACK_NAME --image-repository $ECR_URI --no-confirm-changeset --no-fail-on-empty-changeset --capabilities CAPABILITY_IAM \ No newline at end of file diff --git a/src/NET6Containers/run-loadtest.sh b/src/NET6Containers/run-loadtest.sh new file mode 100755 index 0000000..ab537dc --- /dev/null +++ b/src/NET6Containers/run-loadtest.sh @@ -0,0 +1,152 @@ +#Arguments: +#$1 - load test duration in seconds +#$2 - log interval to be used in the cloudwatch query in minutes +#$3 - when equal to 1 cloudwatch log group will be deleted to ensure that only logs of the load test will be evaluated for stat + +STACK_NAME=dotnet6-container +TEST_DURATIOMN_SEC=60 +LOG_INTERVAL_MIN=20 +LOG_DELETE=yes + +COLOR='\033[0;33m' +NO_COLOR='\033[0m' # No Color + +if [ "x$1" != x ]; +then + TEST_DURATIOMN_SEC=$1 +fi + +if [ "x$2" != x ]; +then + LOG_INTERVAL_MIN=$2 +fi + +if [ "x$3" != x ]; +then + LOG_DELETE=$3 +fi + +echo "${COLOR}" +echo -------------------------------------------- +echo DURATION:$TEST_DURATIOMN_SEC +echo LOG INTERVAL:$LOG_INTERVAL_MIN +echo LOG_DELETE: $LOG_DELETE +echo -------------------------------------------- +echo "${NO_COLOR}" + +mkdir -p Report + +function RunLoadTest() +{ + #Params: + #$1 - Architecture (x86 or arm64).Used for logging and naming report file + #$2 - Stack output name to get API Url + #$3 - Stack output name to get lambda name GetProducts + #$4 - Stack output name to get lambda name GetProduct + #$5 - Stack output name to get lambda name DeleteProduct + #$6 - Stack output name to get lambda name PutProduct + + #get test params from cloud formation output + echo "${COLOR}" + export API_URL=$(aws cloudformation describe-stacks --stack-name $STACK_NAME \ + --query "Stacks[0].Outputs[?OutputKey=='$2'].OutputValue" \ + --output text) + echo API URL: $API_URL + + LAMBDA_GETPRODUCTS=$(aws cloudformation describe-stacks --stack-name $STACK_NAME \ + --query "Stacks[0].Outputs[?OutputKey=='$3'].OutputValue" \ + --output text) + echo LAMBDA: $LAMBDA_GETPRODUCTS + + LAMBDA_GETPRODUCT=$(aws cloudformation describe-stacks --stack-name $STACK_NAME \ + --query "Stacks[0].Outputs[?OutputKey=='$4'].OutputValue" \ + --output text) + echo LAMBDA: $LAMBDA_GETPRODUCT + + LAMBDA_DELETEPRODUCT=$(aws cloudformation describe-stacks --stack-name $STACK_NAME \ + --query "Stacks[0].Outputs[?OutputKey=='$5'].OutputValue" \ + --output text) + echo LAMBDA: $LAMBDA_DELETEPRODUCT + + LAMBDA_PUTPRODUCT=$(aws cloudformation describe-stacks --stack-name $STACK_NAME \ + --query "Stacks[0].Outputs[?OutputKey=='$6'].OutputValue" \ + --output text) + echo LAMBDA: $LAMBDA_PUTPRODUCT + + if [ $LOG_DELETE == "yes" ]; + then + echo -------------------------------------------- + echo DELETING CLOUDWATCH LOG GROUP /aws/lambda/$LAMBDA_GETPRODUCTS + echo -------------------------------------------- + aws logs delete-log-group --log-group-name /aws/lambda/$LAMBDA_GETPRODUCTS + echo -------------------------------------------- + echo DELETING CLOUDWATCH LOG GROUP /aws/lambda/$LAMBDA_GETPRODUCT + echo -------------------------------------------- + aws logs delete-log-group --log-group-name /aws/lambda/$LAMBDA_GETPRODUCT + echo -------------------------------------------- + echo DELETING CLOUDWATCH LOG GROUP /aws/lambda/$LAMBDA_DELETEPRODUCT + echo -------------------------------------------- + aws logs delete-log-group --log-group-name /aws/lambda/$LAMBDA_DELETEPRODUCT + echo -------------------------------------------- + echo DELETING CLOUDWATCH LOG GROUP /aws/lambda/$LAMBDA_PUTPRODUCT + echo -------------------------------------------- + aws logs delete-log-group --log-group-name /aws/lambda/$LAMBDA_PUTPRODUCT + echo --------------------------------------------- + echo Waiting 10 sec. for deletion to complete + echo -------------------------------------------- + sleep 10 + fi + + #run load test with artillery + echo -------------------------------------------- + echo $1 RUNNING LOAD TEST $TEST_DURATIOMN_SEC sec $API_URL + echo -------------------------------------------- + echo "${NO_COLOR}" + artillery run \ + --overrides '{"config": { "phases": [{ "duration": '$TEST_DURATIOMN_SEC', "arrivalRate": 100 }] } }' \ + --quiet \ + ../../loadtest/codebuild/load-test.yml + + echo "${COLOR}" + echo -------------------------------------------- + echo Waiting 10 sec. for logs to consolidate + echo -------------------------------------------- + sleep 10 + + #get stats from cloudwatch + enddate=$(date "+%s") + startdate=$(($enddate-($LOG_INTERVAL_MIN*60))) + echo -------------------------------------------- + echo Log start:$startdate end:$enddate + echo -------------------------------------------- + + QUERY_ID=$(aws logs start-query \ + --log-group-names "/aws/lambda/$LAMBDA_GETPRODUCTS" "/aws/lambda/$LAMBDA_GETPRODUCT" "/aws/lambda/$LAMBDA_DELETEPRODUCT" "/aws/lambda/$LAMBDA_PUTPRODUCT" \ + --start-time $startdate \ + --end-time $enddate \ + --query-string 'filter @type="REPORT" | fields greatest(@initDuration, 0) + @duration as duration, ispresent(@initDuration) as coldstart | stats count(*) as count, pct(duration, 50) as p50, pct(duration, 90) as p90, pct(duration, 99) as p99, max(duration) as max by coldstart' \ + | jq -r '.queryId') + + echo -------------------------------------------- + echo Query started, id: $QUERY_ID + echo -------------------------------------------- + + echo --------------------------------------------- + echo Waiting 10 sec. for cloudwatch query to complete + echo -------------------------------------------- + sleep 10 + + echo -------------------------------------------- + echo RESULTS $1 + echo -------------------------------------------- + echo "${NO_COLOR}" + date > ./Report/load-test-report-$1.txt + echo $1 RESULTS >> ./Report/load-test-report-$1.txt + echo Test duration sec: $TEST_DURATIOMN_SEC >> ./Report/load-test-report-$1.txt + echo Log interval min: $LOG_INTERVAL_MIN >> ./Report/load-test-report-$1.txt + aws logs get-query-results --query-id $QUERY_ID --output text >> ./Report/load-test-report-$1.txt + cat ./Report/load-test-report-$1.txt + aws logs get-query-results --query-id $QUERY_ID --output json >> ./Report/load-test-report-$1.json +} + +RunLoadTest x86 ApiUrlX86 LambdaX86NameGetProducts LambdaX86NameGetProduct LambdaX86NameDeleteProduct LambdaX86NamePutProduct \ No newline at end of file diff --git a/src/NET6Containers/template.yaml b/src/NET6Containers/template.yaml index f8c0a29..b9c6ff1 100644 --- a/src/NET6Containers/template.yaml +++ b/src/NET6Containers/template.yaml @@ -1,42 +1,37 @@ AWSTemplateFormatVersion: "2010-09-09" Transform: AWS::Serverless-2016-10-31 +Parameters: + x86FunctionNamePrefix: + Type: String + Default: Net6-Container-X86 Globals: Function: MemorySize: 1024 - Architectures: [!Ref LambdaArchitecture] - Runtime: dotnet6 Timeout: 30 Tracing: Active Environment: Variables: PRODUCT_TABLE_NAME: !Ref Table -Parameters: - LambdaArchitecture: - Type: String - AllowedValues: - - arm64 - - x86_64 - Description: Enter arm64 or x86_64 - Resources: - GetProductsFunction: + #X86 + GetProductsFunctionX86: Type: AWS::Serverless::Function Properties: + FunctionName: !Join [ -,[!Ref x86FunctionNamePrefix,GetProducts]] + Architectures: [x86_64] PackageType: Image Events: Api: Type: HttpApi Properties: - Path: / + Path: /x86 Method: GET Policies: - - Version: "2012-10-17" - Statement: - - Effect: Allow - Action: dynamodb:Scan - Resource: !GetAtt Table.Arn + - DynamoDBReadPolicy: + TableName: + !Ref Table Metadata: DockerTag: dotnet6-v1 DockerContext: ../../ @@ -44,15 +39,17 @@ Resources: DockerBuildArgs: SAM_BUILD_MODE: run - GetProductFunction: + GetProductFunctionX86: Type: AWS::Serverless::Function Properties: PackageType: Image + FunctionName: !Join [ -,[!Ref x86FunctionNamePrefix,GetProduct]] + Architectures: [x86_64] Events: Api: Type: HttpApi Properties: - Path: /{id} + Path: /x86/{id} Method: GET Policies: - Version: "2012-10-17" @@ -67,15 +64,17 @@ Resources: DockerBuildArgs: SAM_BUILD_MODE: run - DeleteProductFunction: + DeleteProductFunctionX86: Type: AWS::Serverless::Function Properties: + FunctionName: !Join [ -,[!Ref x86FunctionNamePrefix,DeleteProduct]] + Architectures: [x86_64] PackageType: Image Events: Api: Type: HttpApi Properties: - Path: /{id} + Path: /x86/{id} Method: DELETE Policies: - Version: "2012-10-17" @@ -92,15 +91,17 @@ Resources: DockerBuildArgs: SAM_BUILD_MODE: run - PutProductFunction: + PutProductFunctionX86: Type: AWS::Serverless::Function Properties: + FunctionName: !Join [ -,[!Ref x86FunctionNamePrefix,PutProduct]] + Architectures: [x86_64] PackageType: Image Events: Api: Type: HttpApi Properties: - Path: /{id} + Path: /x86/{id} Method: PUT Policies: - Version: "2012-10-17" @@ -115,36 +116,6 @@ Resources: DockerBuildArgs: SAM_BUILD_MODE: run - GenerateLoadTestResults: - Type: AWS::Serverless::Function - Properties: - CodeUri: ./GenerateLoadTestResults/ - Handler: GenerateLoadTestResults::GenerateLoadTestResults.Function::FunctionHandler - Events: - Api: - Type: HttpApi - Properties: - Path: /test-results - Method: GET - Environment: - Variables: - LOG_GROUP_PREFIX: !Sub "/aws/lambda/net-6-containers-" - LOAD_TEST_TYPE: "NET 6 Containers" - LAMBDA_ARCHITECTURE: !Ref LambdaArchitecture - Policies: - - Version: "2012-10-17" - Statement: - - Sid: AllowStartQueries - Effect: Allow - Action: - - logs:DescribeLogGroups - - logs:StartQuery - Resource: "*" - - Sid: AllowGetQueryResults - Effect: Allow - Action: logs:GetQueryResults - Resource: "*" - Table: Type: AWS::DynamoDB::Table Properties: @@ -155,10 +126,25 @@ Resources: KeySchema: - AttributeName: id KeyType: HASH - StreamSpecification: - StreamViewType: NEW_AND_OLD_IMAGES Outputs: ApiUrl: Description: "API Gateway endpoint URL" - Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/" \ No newline at end of file + Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com" + ApiUrlX86: + Description: "X86 API endpoint URL" + Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/x86" + + #x86 + LambdaX86NameGetProducts: + Description: "Lambda X86 GetProducts" + Value: !Join [ -,[!Ref x86FunctionNamePrefix,GetProducts]] + LambdaX86NameGetProduct: + Description: "Lambda X86 GetProduct" + Value: !Join [ -,[!Ref x86FunctionNamePrefix,GetProduct]] + LambdaX86NameDeleteProduct: + Description: "Lambda X86 DeleteProduct" + Value: !Join [ -,[!Ref x86FunctionNamePrefix,DeleteProduct]] + LambdaX86NamePutProduct: + Description: "Lambda X86 PutProduct" + Value: !Join [ -,[!Ref x86FunctionNamePrefix,PutProduct]] \ No newline at end of file diff --git a/src/NET6CustomRuntime/DeleteProduct/DeleteProduct.csproj b/src/NET6CustomRuntime/DeleteProduct/DeleteProduct.csproj index 7f2069d..f733a6a 100644 --- a/src/NET6CustomRuntime/DeleteProduct/DeleteProduct.csproj +++ b/src/NET6CustomRuntime/DeleteProduct/DeleteProduct.csproj @@ -15,12 +15,12 @@ - - - - - - + + + + + + diff --git a/src/NET6CustomRuntime/GenerateLoadTestResults/DateUtils.cs b/src/NET6CustomRuntime/GenerateLoadTestResults/DateUtils.cs deleted file mode 100644 index c37c43d..0000000 --- a/src/NET6CustomRuntime/GenerateLoadTestResults/DateUtils.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; - -namespace GenerateLoadTestResults; - -public static class DateUtils -{ - public static long AsUnixTimestamp(this DateTime date) - { - DateTime origin = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); - TimeSpan diff = date.ToUniversalTime() - origin; - return (long)Math.Floor(diff.TotalSeconds); - } -} \ No newline at end of file diff --git a/src/NET6CustomRuntime/GenerateLoadTestResults/Function.cs b/src/NET6CustomRuntime/GenerateLoadTestResults/Function.cs deleted file mode 100644 index 21a57a8..0000000 --- a/src/NET6CustomRuntime/GenerateLoadTestResults/Function.cs +++ /dev/null @@ -1,146 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Text.Json; -using System.Threading.Tasks; -using Amazon.CloudWatchLogs; -using Amazon.CloudWatchLogs.Model; -using Amazon.Lambda.APIGatewayEvents; -using Amazon.Lambda.Core; - -[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] - -namespace GenerateLoadTestResults -{ - public class Function - { - private AmazonCloudWatchLogsClient _cloudWatchLogsClient; - - public Function() - { - this._cloudWatchLogsClient = new AmazonCloudWatchLogsClient(); - } - - public async Task FunctionHandler( - APIGatewayHttpApiV2ProxyRequest apigProxyEvent, - ILambdaContext context) - { - if (!apigProxyEvent.RequestContext.Http.Method.Equals(HttpMethod.Get.Method)) - { - return new APIGatewayHttpApiV2ProxyResponse - { - Body = "Only GET allowed", - StatusCode = (int) HttpStatusCode.MethodNotAllowed, - }; - } - - try - { - var resultRows = 0; - var queryCount = 0; - - List> finalResults = new List>(); - - while (resultRows < 2 || queryCount >= 3) - { - finalResults = await runQuery(context); - - resultRows = finalResults.Count; - queryCount++; - } - - var wrapper = new QueryResultWrapper() - { - LoadTestType = - $"{Environment.GetEnvironmentVariable("LOAD_TEST_TYPE")} ({Environment.GetEnvironmentVariable("LAMBDA_ARCHITECTURE")})", - WarmStart = new QueryResult() - { - Count = finalResults[0][1].Value, - P50 = finalResults[0][2].Value, - P90 = finalResults[0][3].Value, - P99 = finalResults[0][4].Value, - Max = finalResults[0][5].Value, - }, - ColdStart = new QueryResult() - { - Count = finalResults[1][1].Value, - P50 = finalResults[1][2].Value, - P90 = finalResults[1][3].Value, - P99 = finalResults[1][4].Value, - Max = finalResults[1][5].Value, - } - }; - - return new APIGatewayHttpApiV2ProxyResponse - { - StatusCode = (int) HttpStatusCode.OK, - Body = wrapper.AsMarkdownTableRow(), - Headers = new Dictionary {{"Content-Type", "text/html"}} - }; - } - catch (Exception e) - { - context.Logger.LogLine($"Error retrieving results {e.Message} {e.StackTrace}"); - - return new APIGatewayHttpApiV2ProxyResponse - { - Body = "Not Found", - StatusCode = (int) HttpStatusCode.InternalServerError, - }; - } - } - - private async Task>> runQuery(ILambdaContext context) - { - var logGroupNamePrefix = - $"{Environment.GetEnvironmentVariable("LOG_GROUP_PREFIX")}{Environment.GetEnvironmentVariable("LAMBDA_ARCHITECTURE")}" - .Replace("_", "-"); - - context.Logger.LogLine($"Retrieving log groups with prefix {logGroupNamePrefix}"); - - var logGroupList = await _cloudWatchLogsClient.DescribeLogGroupsAsync(new DescribeLogGroupsRequest() - { - LogGroupNamePrefix = logGroupNamePrefix, - }); - - context.Logger.LogLine($"Found {logGroupList.LogGroups.Count} log group(s)"); - - var queryRes = await _cloudWatchLogsClient.StartQueryAsync(new StartQueryRequest() - { - LogGroupNames = logGroupList.LogGroups.Select(p => p.LogGroupName).ToList(), - QueryString = - "filter @type=\"REPORT\" | fields greatest(@initDuration, 0) + @duration as duration, ispresent(@initDuration) as coldstart | stats count(*) as count, pct(duration, 50) as p50, pct(duration, 90) as p90, pct(duration, 99) as p99, max(duration) as max by coldstart", - StartTime = DateTime.Now.AddMinutes(-20).AsUnixTimestamp(), - EndTime = DateTime.Now.AsUnixTimestamp(), - }); - - context.Logger.LogLine($"Running query, query id is {queryRes.QueryId}"); - - QueryStatus currentQueryStatus = QueryStatus.Running; - List> finalResults = new List>(); - - while (currentQueryStatus == QueryStatus.Running || currentQueryStatus == QueryStatus.Scheduled) - { - context.Logger.LogLine("Retrieving query results"); - - var queryResults = await _cloudWatchLogsClient.GetQueryResultsAsync(new GetQueryResultsRequest() - { - QueryId = queryRes.QueryId - }); - - context.Logger.LogLine($"Query result status is {queryResults.Status}"); - - currentQueryStatus = queryResults.Status; - finalResults = queryResults.Results; - - await Task.Delay(TimeSpan.FromSeconds(5)); - } - - context.Logger.LogLine($"Final results: {finalResults.Count} row(s)"); - - return finalResults; - } - } -} \ No newline at end of file diff --git a/src/NET6CustomRuntime/GenerateLoadTestResults/GenerateLoadTestResults.csproj b/src/NET6CustomRuntime/GenerateLoadTestResults/GenerateLoadTestResults.csproj deleted file mode 100644 index 4a8d708..0000000 --- a/src/NET6CustomRuntime/GenerateLoadTestResults/GenerateLoadTestResults.csproj +++ /dev/null @@ -1,17 +0,0 @@ - - - - net6.0 - true - true - - - - - - - - - - - diff --git a/src/NET6CustomRuntime/GenerateLoadTestResults/QueryResult.cs b/src/NET6CustomRuntime/GenerateLoadTestResults/QueryResult.cs deleted file mode 100644 index 69b5a31..0000000 --- a/src/NET6CustomRuntime/GenerateLoadTestResults/QueryResult.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace GenerateLoadTestResults; - -public record QueryResultWrapper -{ - public string LoadTestType { get; set; } - - public QueryResult ColdStart { get; set; } - - public QueryResult WarmStart { get; set; } - - public string AsMarkdownTableRow() => $"
Cold Start (ms)Warm Start (ms)
p50p90p99maxp50p90p99max
{LoadTestType}{ColdStart.P50}{ColdStart.P90}{ColdStart.P99}{ColdStart.Max}{WarmStart.P50}{WarmStart.P90}{WarmStart.P99}{WarmStart.Max}
"; -} - -public record QueryResult -{ - public string Count { get; set; } - - public string P50 { get; set; } - - public string P90 { get; set; } - - public string P99 { get; set; } - - public string Max { get; set; } -} \ No newline at end of file diff --git a/src/NET6CustomRuntime/GetProduct/GetProduct.csproj b/src/NET6CustomRuntime/GetProduct/GetProduct.csproj index e94ebb7..355ae20 100644 --- a/src/NET6CustomRuntime/GetProduct/GetProduct.csproj +++ b/src/NET6CustomRuntime/GetProduct/GetProduct.csproj @@ -13,12 +13,12 @@ linux-x64 - - - - - - + + + + + + diff --git a/src/NET6CustomRuntime/GetProducts/GetProducts.csproj b/src/NET6CustomRuntime/GetProducts/GetProducts.csproj index 11ab6ab..ef13d3b 100644 --- a/src/NET6CustomRuntime/GetProducts/GetProducts.csproj +++ b/src/NET6CustomRuntime/GetProducts/GetProducts.csproj @@ -14,12 +14,12 @@ linux-x64 - - - - - - + + + + + + diff --git a/src/NET6CustomRuntime/Makefile b/src/NET6CustomRuntime/Makefile index 2aadd31..4850513 100644 --- a/src/NET6CustomRuntime/Makefile +++ b/src/NET6CustomRuntime/Makefile @@ -1,15 +1,23 @@ -build-GetProductFunction: - dotnet clean +build-GetProductFunctionX86: dotnet publish GetProduct/GetProduct.csproj -c Release -r linux-x64 --self-contained -o $(ARTIFACTS_DIR) -build-GetProductsFunction: - dotnet clean +build-GetProductsFunctionX86: dotnet publish GetProducts/GetProducts.csproj -c Release -r linux-x64 --self-contained -o $(ARTIFACTS_DIR) -build-PutProductFunction: - dotnet clean +build-PutProductFunctionX86: dotnet publish PutProduct/PutProduct.csproj -c Release -r linux-x64 --self-contained -o $(ARTIFACTS_DIR) -build-DeleteProductFunction: - dotnet clean - dotnet publish DeleteProduct/DeleteProduct.csproj -c Release -r linux-x64 --self-contained -o $(ARTIFACTS_DIR) \ No newline at end of file +build-DeleteProductFunctionX86: + dotnet publish DeleteProduct/DeleteProduct.csproj -c Release -r linux-x64 --self-contained -o $(ARTIFACTS_DIR) + +build-GetProductFunctionArm64: + dotnet publish GetProduct/GetProduct.csproj -c Release -r linux-arm64 --self-contained -o $(ARTIFACTS_DIR) + +build-GetProductsFunctionArm64: + dotnet publish GetProducts/GetProducts.csproj -c Release -r linux-arm64 --self-contained -o $(ARTIFACTS_DIR) + +build-PutProductFunctionArm64: + dotnet publish PutProduct/PutProduct.csproj -c Release -r linux-arm64 --self-contained -o $(ARTIFACTS_DIR) + +build-DeleteProductFunctionArm64: + dotnet publish DeleteProduct/DeleteProduct.csproj -c Release -r linux-arm64 --self-contained -o $(ARTIFACTS_DIR) \ No newline at end of file diff --git a/src/NET6CustomRuntime/PutProduct/PutProduct.csproj b/src/NET6CustomRuntime/PutProduct/PutProduct.csproj index fcfcccc..f3ddff1 100644 --- a/src/NET6CustomRuntime/PutProduct/PutProduct.csproj +++ b/src/NET6CustomRuntime/PutProduct/PutProduct.csproj @@ -15,12 +15,12 @@ - - - - - - + + + + + + diff --git a/src/NET6CustomRuntime/Shared/Shared.csproj b/src/NET6CustomRuntime/Shared/Shared.csproj index 3416950..b8db67f 100644 --- a/src/NET6CustomRuntime/Shared/Shared.csproj +++ b/src/NET6CustomRuntime/Shared/Shared.csproj @@ -13,10 +13,10 @@ linux-x64 - - - - + + + + diff --git a/src/NET6CustomRuntime/deploy.sh b/src/NET6CustomRuntime/deploy.sh new file mode 100755 index 0000000..a87f3e6 --- /dev/null +++ b/src/NET6CustomRuntime/deploy.sh @@ -0,0 +1,43 @@ +#Arguments: +#$1 - delete stack formation to ensure that all test have same conditon and favour similar ammount of cold start events + +STACK_NAME=dotnet6-custom +DELETE_STACK=yes + +COLOR='\033[0;33m' +NO_COLOR='\033[0m' # No Color + +if [ "x$1" != x ]; +then + DELETE_STACK=$1 +fi + +echo "${COLOR}" +echo -------------------------------------------- +echo DELETE_STACK: $DELETE_STACK +echo -------------------------------------------- +echo "${NO_COLOR}" + +if [ $DELETE_STACK == "yes" ]; +then + echo "${COLOR}" + echo -------------------------------------------- + echo DELETING STACK $STACK_NAME + echo -------------------------------------------- + echo "${NO_COLOR}" + aws cloudformation delete-stack --stack-name $STACK_NAME + echo "${COLOR}" + echo --------------------------------------------- + echo Waiting stack to be deleted + echo -------------------------------------------- + echo "${NO_COLOR}" + aws cloudformation wait stack-delete-complete --stack-name $STACK_NAME + echo "${COLOR}" + echo --------------------------------------------- + echo Stack deleted + echo -------------------------------------------- + echo "${NO_COLOR}" +fi + +sam build +sam deploy --stack-name $STACK_NAME --resolve-s3 --s3-prefix $STACK_NAME --no-confirm-changeset --no-fail-on-empty-changeset --capabilities CAPABILITY_IAM \ No newline at end of file diff --git a/src/NET6CustomRuntime/run-loadtest.sh b/src/NET6CustomRuntime/run-loadtest.sh new file mode 100755 index 0000000..8c5fb05 --- /dev/null +++ b/src/NET6CustomRuntime/run-loadtest.sh @@ -0,0 +1,153 @@ +#Arguments: +#$1 - load test duration in seconds +#$2 - log interval to be used in the cloudwatch query in minutes +#$3 - when equal to 1 cloudwatch log group will be deleted to ensure that only logs of the load test will be evaluated for stat + +STACK_NAME=dotnet6-custom +TEST_DURATIOMN_SEC=60 +LOG_INTERVAL_MIN=20 +LOG_DELETE=yes + +COLOR='\033[0;33m' +NO_COLOR='\033[0m' # No Color + +if [ "x$1" != x ]; +then + TEST_DURATIOMN_SEC=$1 +fi + +if [ "x$2" != x ]; +then + LOG_INTERVAL_MIN=$2 +fi + +if [ "x$3" != x ]; +then + LOG_DELETE=$3 +fi + +echo "${COLOR}" +echo -------------------------------------------- +echo DURATION:$TEST_DURATIOMN_SEC +echo LOG INTERVAL:$LOG_INTERVAL_MIN +echo LOG_DELETE: $LOG_DELETE +echo -------------------------------------------- +echo "${NO_COLOR}" + +mkdir -p Report + +function RunLoadTest() +{ + #Params: + #$1 - Architecture (x86 or arm64).Used for logging and naming report file + #$2 - Stack output name to get API Url + #$3 - Stack output name to get lambda name GetProducts + #$4 - Stack output name to get lambda name GetProduct + #$5 - Stack output name to get lambda name DeleteProduct + #$6 - Stack output name to get lambda name PutProduct + + #get test params from cloud formation output + echo "${COLOR}" + export API_URL=$(aws cloudformation describe-stacks --stack-name $STACK_NAME \ + --query "Stacks[0].Outputs[?OutputKey=='$2'].OutputValue" \ + --output text) + echo API URL: $API_URL + + LAMBDA_GETPRODUCTS=$(aws cloudformation describe-stacks --stack-name $STACK_NAME \ + --query "Stacks[0].Outputs[?OutputKey=='$3'].OutputValue" \ + --output text) + echo LAMBDA: $LAMBDA_GETPRODUCTS + + LAMBDA_GETPRODUCT=$(aws cloudformation describe-stacks --stack-name $STACK_NAME \ + --query "Stacks[0].Outputs[?OutputKey=='$4'].OutputValue" \ + --output text) + echo LAMBDA: $LAMBDA_GETPRODUCT + + LAMBDA_DELETEPRODUCT=$(aws cloudformation describe-stacks --stack-name $STACK_NAME \ + --query "Stacks[0].Outputs[?OutputKey=='$5'].OutputValue" \ + --output text) + echo LAMBDA: $LAMBDA_DELETEPRODUCT + + LAMBDA_PUTPRODUCT=$(aws cloudformation describe-stacks --stack-name $STACK_NAME \ + --query "Stacks[0].Outputs[?OutputKey=='$6'].OutputValue" \ + --output text) + echo LAMBDA: $LAMBDA_PUTPRODUCT + + if [ $LOG_DELETE == "yes" ]; + then + echo -------------------------------------------- + echo DELETING CLOUDWATCH LOG GROUP /aws/lambda/$LAMBDA_GETPRODUCTS + echo -------------------------------------------- + aws logs delete-log-group --log-group-name /aws/lambda/$LAMBDA_GETPRODUCTS + echo -------------------------------------------- + echo DELETING CLOUDWATCH LOG GROUP /aws/lambda/$LAMBDA_GETPRODUCT + echo -------------------------------------------- + aws logs delete-log-group --log-group-name /aws/lambda/$LAMBDA_GETPRODUCT + echo -------------------------------------------- + echo DELETING CLOUDWATCH LOG GROUP /aws/lambda/$LAMBDA_DELETEPRODUCT + echo -------------------------------------------- + aws logs delete-log-group --log-group-name /aws/lambda/$LAMBDA_DELETEPRODUCT + echo -------------------------------------------- + echo DELETING CLOUDWATCH LOG GROUP /aws/lambda/$LAMBDA_PUTPRODUCT + echo -------------------------------------------- + aws logs delete-log-group --log-group-name /aws/lambda/$LAMBDA_PUTPRODUCT + echo --------------------------------------------- + echo Waiting 10 sec. for deletion to complete + echo -------------------------------------------- + sleep 10 + fi + + #run load test with artillery + echo -------------------------------------------- + echo $1 RUNNING LOAD TEST $TEST_DURATIOMN_SEC sec $API_URL + echo -------------------------------------------- + echo "${NO_COLOR}" + artillery run \ + --overrides '{"config": { "phases": [{ "duration": '$TEST_DURATIOMN_SEC', "arrivalRate": 100 }] } }' \ + --quiet \ + ../../loadtest/codebuild/load-test.yml + + echo "${COLOR}" + echo -------------------------------------------- + echo Waiting 10 sec. for logs to consolidate + echo -------------------------------------------- + sleep 10 + + #get stats from cloudwatch + enddate=$(date "+%s") + startdate=$(($enddate-($LOG_INTERVAL_MIN*60))) + echo -------------------------------------------- + echo Log start:$startdate end:$enddate + echo -------------------------------------------- + + QUERY_ID=$(aws logs start-query \ + --log-group-names "/aws/lambda/$LAMBDA_GETPRODUCTS" "/aws/lambda/$LAMBDA_GETPRODUCT" "/aws/lambda/$LAMBDA_DELETEPRODUCT" "/aws/lambda/$LAMBDA_PUTPRODUCT" \ + --start-time $startdate \ + --end-time $enddate \ + --query-string 'filter @type="REPORT" | fields greatest(@initDuration, 0) + @duration as duration, ispresent(@initDuration) as coldstart | stats count(*) as count, pct(duration, 50) as p50, pct(duration, 90) as p90, pct(duration, 99) as p99, max(duration) as max by coldstart' \ + | jq -r '.queryId') + + echo -------------------------------------------- + echo Query started, id: $QUERY_ID + echo -------------------------------------------- + + echo --------------------------------------------- + echo Waiting 10 sec. for cloudwatch query to complete + echo -------------------------------------------- + sleep 10 + + echo -------------------------------------------- + echo RESULTS $1 + echo -------------------------------------------- + echo "${NO_COLOR}" + date > ./Report/load-test-report-$1.txt + echo $1 RESULTS >> ./Report/load-test-report-$1.txt + echo Test duration sec: $TEST_DURATIOMN_SEC >> ./Report/load-test-report-$1.txt + echo Log interval min: $LOG_INTERVAL_MIN >> ./Report/load-test-report-$1.txt + aws logs get-query-results --query-id $QUERY_ID --output text >> ./Report/load-test-report-$1.txt + cat ./Report/load-test-report-$1.txt + aws logs get-query-results --query-id $QUERY_ID --output json >> ./Report/load-test-report-$1.json +} + +RunLoadTest x86 ApiUrlX86 LambdaX86NameGetProducts LambdaX86NameGetProduct LambdaX86NameDeleteProduct LambdaX86NamePutProduct +RunLoadTest arm64 ApiUrlArm64 LambdaArm64NameGetProducts LambdaArm64NameGetProduct LambdaArm64NameDeleteProduct LambdaArm64NamePutProduct \ No newline at end of file diff --git a/src/NET6CustomRuntime/template.yaml b/src/NET6CustomRuntime/template.yaml index c6c34f7..477c3ad 100644 --- a/src/NET6CustomRuntime/template.yaml +++ b/src/NET6CustomRuntime/template.yaml @@ -1,10 +1,17 @@ AWSTemplateFormatVersion: "2010-09-09" Transform: AWS::Serverless-2016-10-31 +Parameters: + x86FunctionNamePrefix: + Type: String + Default: Net6-X86-Custom + arm64FunctionNamePrefix: + Type: String + Default: Net6-Arm64-Custom + Globals: Function: MemorySize: 1024 - Architectures: ["x86_64"] Runtime: provided.al2 Timeout: 30 Tracing: Active @@ -13,16 +20,19 @@ Globals: PRODUCT_TABLE_NAME: !Ref Table Resources: - GetProductsFunction: + #X86 + GetProductsFunctionX86: Type: AWS::Serverless::Function Properties: + FunctionName: !Join [ -,[!Ref x86FunctionNamePrefix,GetProducts]] + Architectures: [x86_64] CodeUri: ./ Handler: bootstrap Events: Api: Type: HttpApi Properties: - Path: / + Path: /x86 Method: GET Policies: - DynamoDBReadPolicy: @@ -31,16 +41,18 @@ Resources: Metadata: BuildMethod: makefile - GetProductFunction: + GetProductFunctionX86: Type: AWS::Serverless::Function Properties: + FunctionName: !Join [ -,[!Ref x86FunctionNamePrefix,GetProduct]] + Architectures: [x86_64] CodeUri: ./ Handler: GetProduct Events: Api: Type: HttpApi Properties: - Path: /{id} + Path: /x86/{id} Method: GET Policies: - Version: "2012-10-17" @@ -51,16 +63,18 @@ Resources: Metadata: BuildMethod: makefile - DeleteProductFunction: + DeleteProductFunctionX86: Type: AWS::Serverless::Function Properties: + FunctionName: !Join [ -,[!Ref x86FunctionNamePrefix,DeleteProduct]] + Architectures: [x86_64] CodeUri: ./ Handler: DeleteProduct Events: Api: Type: HttpApi Properties: - Path: /{id} + Path: /x86/{id} Method: DELETE Policies: - Version: "2012-10-17" @@ -73,16 +87,18 @@ Resources: Metadata: BuildMethod: makefile - PutProductFunction: + PutProductFunctionX86: Type: AWS::Serverless::Function Properties: + FunctionName: !Join [ -,[!Ref x86FunctionNamePrefix,PutProduct]] + Architectures: [x86_64] CodeUri: ./ Handler: PutProduct Events: Api: Type: HttpApi Properties: - Path: /{id} + Path: /x86/{id} Method: PUT Policies: - Version: "2012-10-17" @@ -92,37 +108,94 @@ Resources: Resource: !GetAtt Table.Arn Metadata: BuildMethod: makefile + #ARM64 + GetProductsFunctionArm64: + Type: AWS::Serverless::Function + Properties: + FunctionName: !Join [ -,[!Ref arm64FunctionNamePrefix,GetProducts]] + Architectures: [arm64] + CodeUri: ./ + Handler: bootstrap + Events: + Api: + Type: HttpApi + Properties: + Path: /arm64 + Method: GET + Policies: + - DynamoDBReadPolicy: + TableName: + !Ref Table + Metadata: + BuildMethod: makefile - GenerateLoadTestResults: + GetProductFunctionArm64: Type: AWS::Serverless::Function Properties: - CodeUri: ./GenerateLoadTestResults/ - Handler: GenerateLoadTestResults::GenerateLoadTestResults.Function::FunctionHandler - Runtime: dotnet6 + FunctionName: !Join [ -,[!Ref arm64FunctionNamePrefix,GetProduct]] + Architectures: [arm64] + CodeUri: ./ + Handler: GetProduct Events: Api: Type: HttpApi Properties: - Path: /test-results + Path: /arm64/{id} Method: GET - Environment: - Variables: - LOG_GROUP_PREFIX: !Sub "/aws/lambda/net-8-base-" - LOAD_TEST_TYPE: "NET 8" - LAMBDA_ARCHITECTURE: "x86_64" Policies: - Version: "2012-10-17" Statement: - - Sid: AllowStartQueries - Effect: Allow + - Effect: Allow + Action: dynamodb:GetItem + Resource: !GetAtt Table.Arn + Metadata: + BuildMethod: makefile + + DeleteProductFunctionArm64: + Type: AWS::Serverless::Function + Properties: + FunctionName: !Join [ -,[!Ref arm64FunctionNamePrefix,DeleteProduct]] + Architectures: [arm64] + CodeUri: ./ + Handler: DeleteProduct + Events: + Api: + Type: HttpApi + Properties: + Path: /arm64/{id} + Method: DELETE + Policies: + - Version: "2012-10-17" + Statement: + - Effect: Allow Action: - - logs:DescribeLogGroups - - logs:StartQuery - Resource: "*" - - Sid: AllowGetQueryResults - Effect: Allow - Action: logs:GetQueryResults - Resource: "*" + - dynamodb:DeleteItem + - dynamodb:GetItem + Resource: !GetAtt Table.Arn + Metadata: + BuildMethod: makefile + + PutProductFunctionArm64: + Type: AWS::Serverless::Function + Properties: + FunctionName: !Join [ -,[!Ref arm64FunctionNamePrefix,PutProduct]] + Architectures: [arm64] + CodeUri: ./ + Handler: PutProduct + Events: + Api: + Type: HttpApi + Properties: + Path: /arm64/{id} + Method: PUT + Policies: + - Version: "2012-10-17" + Statement: + - Effect: Allow + Action: dynamodb:PutItem + Resource: !GetAtt Table.Arn + Metadata: + BuildMethod: makefile Table: Type: AWS::DynamoDB::Table @@ -138,4 +211,38 @@ Resources: Outputs: ApiUrl: Description: "API Gateway endpoint URL" - Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/" \ No newline at end of file + Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com" + ApiUrlX86: + Description: "X86 API endpoint URL" + Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/x86" + ApiUrlArm64: + Description: "Arm64 GateAPI endpoint URL" + Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/arm64" + + #x86 + LambdaX86NameGetProducts: + Description: "Lambda X86 GetProducts" + Value: !Join [ -,[!Ref x86FunctionNamePrefix,GetProducts]] + LambdaX86NameGetProduct: + Description: "Lambda X86 GetProduct" + Value: !Join [ -,[!Ref x86FunctionNamePrefix,GetProduct]] + LambdaX86NameDeleteProduct: + Description: "Lambda X86 DeleteProduct" + Value: !Join [ -,[!Ref x86FunctionNamePrefix,DeleteProduct]] + LambdaX86NamePutProduct: + Description: "Lambda X86 PutProduct" + Value: !Join [ -,[!Ref x86FunctionNamePrefix,PutProduct]] + + #arm64 + LambdaArm64NameGetProducts: + Description: "Lambda X86 GetProducts" + Value: !Join [ -,[!Ref arm64FunctionNamePrefix,GetProducts]] + LambdaArm64NameGetProduct: + Description: "Lambda X86 GetProduct" + Value: !Join [ -,[!Ref arm64FunctionNamePrefix,GetProduct]] + LambdaArm64NameDeleteProduct: + Description: "Lambda X86 DeleteProduct" + Value: !Join [ -,[!Ref arm64FunctionNamePrefix,DeleteProduct]] + LambdaArm64NamePutProduct: + Description: "Lambda X86 PutProduct" + Value: !Join [ -,[!Ref arm64FunctionNamePrefix,PutProduct]] \ No newline at end of file diff --git a/src/NET6MinimalAPI/deploy.sh b/src/NET6MinimalAPI/deploy.sh index 6876c29..7897959 100755 --- a/src/NET6MinimalAPI/deploy.sh +++ b/src/NET6MinimalAPI/deploy.sh @@ -7,7 +7,7 @@ DELETE_STACK=yes COLOR='\033[0;33m' NO_COLOR='\033[0m' # No Color -if [ "x$4" != x ]; +if [ "x$1" != x ]; then DELETE_STACK=$1 fi diff --git a/src/NET6MinimalAPI/run-loadtest.sh b/src/NET6MinimalAPI/run-loadtest.sh index 0832846..6b8ed6d 100755 --- a/src/NET6MinimalAPI/run-loadtest.sh +++ b/src/NET6MinimalAPI/run-loadtest.sh @@ -45,7 +45,7 @@ function RunLoadTest() #get test params from cloud formation output echo "${COLOR}" - API_URL=$(aws cloudformation describe-stacks --stack-name $STACK_NAME \ + export API_URL=$(aws cloudformation describe-stacks --stack-name $STACK_NAME \ --query "Stacks[0].Outputs[?OutputKey=='$2'].OutputValue" \ --output text) echo API URL: $API_URL @@ -73,10 +73,9 @@ function RunLoadTest() echo -------------------------------------------- echo "${NO_COLOR}" artillery run \ - --target "$API_URL" \ --overrides '{"config": { "phases": [{ "duration": '$TEST_DURATIOMN_SEC', "arrivalRate": 100 }] } }' \ --quiet \ - ../../loadtest/load-test.yml + ../../loadtest/codebuild/load-test.yml echo "${COLOR}" echo -------------------------------------------- diff --git a/src/NET6MinimalAPIWebAdapter/deploy.sh b/src/NET6MinimalAPIWebAdapter/deploy.sh index 1cff98f..5c4dade 100755 --- a/src/NET6MinimalAPIWebAdapter/deploy.sh +++ b/src/NET6MinimalAPIWebAdapter/deploy.sh @@ -7,7 +7,7 @@ DELETE_STACK=yes COLOR='\033[0;33m' NO_COLOR='\033[0m' # No Color -if [ "x$4" != x ]; +if [ "x$1" != x ]; then DELETE_STACK=$1 fi diff --git a/src/NET6MinimalAPIWebAdapter/run-loadtest.sh b/src/NET6MinimalAPIWebAdapter/run-loadtest.sh index 003446e..328efae 100755 --- a/src/NET6MinimalAPIWebAdapter/run-loadtest.sh +++ b/src/NET6MinimalAPIWebAdapter/run-loadtest.sh @@ -44,7 +44,7 @@ function RunLoadTest() #get test params from cloud formation output echo "${COLOR}" - API_URL=$(aws cloudformation describe-stacks --stack-name $STACK_NAME \ + export API_URL=$(aws cloudformation describe-stacks --stack-name $STACK_NAME \ --query "Stacks[0].Outputs[?OutputKey=='$2'].OutputValue" \ --output text) echo API URL: $API_URL @@ -72,10 +72,9 @@ function RunLoadTest() echo -------------------------------------------- echo "${NO_COLOR}" artillery run \ - --target "$API_URL" \ --overrides '{"config": { "phases": [{ "duration": '$TEST_DURATIOMN_SEC', "arrivalRate": 100 }] } }' \ --quiet \ - ../../loadtest/load-test.yml + ../../loadtest/codebuild/load-test.yml echo "${COLOR}" echo -------------------------------------------- diff --git a/src/NET6Native/DeleteProduct/DeleteProduct.csproj b/src/NET6Native/DeleteProduct/DeleteProduct.csproj index db7b4ad..8ef9370 100755 --- a/src/NET6Native/DeleteProduct/DeleteProduct.csproj +++ b/src/NET6Native/DeleteProduct/DeleteProduct.csproj @@ -1,20 +1,20 @@  - exe + Exe net6.0 + bootstrap true - true - - - - - - - + + + + + + + diff --git a/src/NET6Native/GetProduct/GetProduct.csproj b/src/NET6Native/GetProduct/GetProduct.csproj index db7b4ad..8ef9370 100755 --- a/src/NET6Native/GetProduct/GetProduct.csproj +++ b/src/NET6Native/GetProduct/GetProduct.csproj @@ -1,20 +1,20 @@  - exe + Exe net6.0 + bootstrap true - true - - - - - - - + + + + + + + diff --git a/src/NET6Native/GetProducts/GetProducts.csproj b/src/NET6Native/GetProducts/GetProducts.csproj index db7b4ad..8ef9370 100755 --- a/src/NET6Native/GetProducts/GetProducts.csproj +++ b/src/NET6Native/GetProducts/GetProducts.csproj @@ -1,20 +1,20 @@  - exe + Exe net6.0 + bootstrap true - true - - - - - - - + + + + + + + diff --git a/src/NET6Native/Makefile b/src/NET6Native/Makefile new file mode 100644 index 0000000..556eded --- /dev/null +++ b/src/NET6Native/Makefile @@ -0,0 +1,23 @@ +build-GetProductFunctionX86: + dotnet publish GetProduct/GetProduct.csproj -c Release -r linux-x64 --self-contained -o $(ARTIFACTS_DIR) + +build-GetProductsFunctionX86: + dotnet publish GetProducts/GetProducts.csproj -c Release -r linux-x64 --self-contained -o $(ARTIFACTS_DIR) + +build-PutProductFunctionX86: + dotnet publish PutProduct/PutProduct.csproj -c Release -r linux-x64 --self-contained -o $(ARTIFACTS_DIR) + +build-DeleteProductFunctionX86: + dotnet publish DeleteProduct/DeleteProduct.csproj -c Release -r linux-x64 --self-contained -o $(ARTIFACTS_DIR) + +build-GetProductFunctionArm64: + dotnet publish GetProduct/GetProduct.csproj -c Release -r linux-x64 --self-contained -o $(ARTIFACTS_DIR) + +build-GetProductsFunctionArm64: + dotnet publish GetProducts/GetProducts.csproj -c Release -r linux-x64 --self-contained -o $(ARTIFACTS_DIR) + +build-PutProductFunctionArm64: + dotnet publish PutProduct/PutProduct.csproj -c Release -r linux-x64 --self-contained -o $(ARTIFACTS_DIR) + +build-DeleteProductFunctionArm64: + dotnet publish DeleteProduct/DeleteProduct.csproj -c Release -r linux-x64 --self-contained -o $(ARTIFACTS_DIR) \ No newline at end of file diff --git a/src/NET6Native/PutProduct/PutProduct.csproj b/src/NET6Native/PutProduct/PutProduct.csproj index db7b4ad..8ef9370 100755 --- a/src/NET6Native/PutProduct/PutProduct.csproj +++ b/src/NET6Native/PutProduct/PutProduct.csproj @@ -1,20 +1,20 @@  - exe + Exe net6.0 + bootstrap true - true - - - - - - - + + + + + + + diff --git a/src/NET6Native/Shared/Shared.csproj b/src/NET6Native/Shared/Shared.csproj index 9fce83c..fcdc7e6 100755 --- a/src/NET6Native/Shared/Shared.csproj +++ b/src/NET6Native/Shared/Shared.csproj @@ -6,10 +6,10 @@ enable - - - - - + + + + + diff --git a/src/NET6Native/deploy.sh b/src/NET6Native/deploy.sh new file mode 100755 index 0000000..8f7cd88 --- /dev/null +++ b/src/NET6Native/deploy.sh @@ -0,0 +1,43 @@ +#Arguments: +#$1 - delete stack formation to ensure that all test have same conditon and favour similar ammount of cold start events + +STACK_NAME=dotnet6-native +DELETE_STACK=yes + +COLOR='\033[0;33m' +NO_COLOR='\033[0m' # No Color + +if [ "x$1" != x ]; +then + DELETE_STACK=$1 +fi + +echo "${COLOR}" +echo -------------------------------------------- +echo DELETE_STACK: $DELETE_STACK +echo -------------------------------------------- +echo "${NO_COLOR}" + +if [ $DELETE_STACK == "yes" ]; +then + echo "${COLOR}" + echo -------------------------------------------- + echo DELETING STACK $STACK_NAME + echo -------------------------------------------- + echo "${NO_COLOR}" + aws cloudformation delete-stack --stack-name $STACK_NAME + echo "${COLOR}" + echo --------------------------------------------- + echo Waiting stack to be deleted + echo -------------------------------------------- + echo "${NO_COLOR}" + aws cloudformation wait stack-delete-complete --stack-name $STACK_NAME + echo "${COLOR}" + echo --------------------------------------------- + echo Stack deleted + echo -------------------------------------------- + echo "${NO_COLOR}" +fi + +sam build +sam deploy --stack-name $STACK_NAME --resolve-s3 --s3-prefix $STACK_NAME --no-confirm-changeset --no-fail-on-empty-changeset --capabilities CAPABILITY_IAM \ No newline at end of file diff --git a/src/NET6Native/run-loadtest.sh b/src/NET6Native/run-loadtest.sh new file mode 100755 index 0000000..03b81e3 --- /dev/null +++ b/src/NET6Native/run-loadtest.sh @@ -0,0 +1,153 @@ +#Arguments: +#$1 - load test duration in seconds +#$2 - log interval to be used in the cloudwatch query in minutes +#$3 - when equal to 1 cloudwatch log group will be deleted to ensure that only logs of the load test will be evaluated for stat + +STACK_NAME=dotnet6-native +TEST_DURATIOMN_SEC=60 +LOG_INTERVAL_MIN=20 +LOG_DELETE=yes + +COLOR='\033[0;33m' +NO_COLOR='\033[0m' # No Color + +if [ "x$1" != x ]; +then + TEST_DURATIOMN_SEC=$1 +fi + +if [ "x$2" != x ]; +then + LOG_INTERVAL_MIN=$2 +fi + +if [ "x$3" != x ]; +then + LOG_DELETE=$3 +fi + +echo "${COLOR}" +echo -------------------------------------------- +echo DURATION:$TEST_DURATIOMN_SEC +echo LOG INTERVAL:$LOG_INTERVAL_MIN +echo LOG_DELETE: $LOG_DELETE +echo -------------------------------------------- +echo "${NO_COLOR}" + +mkdir -p Report + +function RunLoadTest() +{ + #Params: + #$1 - Architecture (x86 or arm64).Used for logging and naming report file + #$2 - Stack output name to get API Url + #$3 - Stack output name to get lambda name GetProducts + #$4 - Stack output name to get lambda name GetProduct + #$5 - Stack output name to get lambda name DeleteProduct + #$6 - Stack output name to get lambda name PutProduct + + #get test params from cloud formation output + echo "${COLOR}" + export API_URL=$(aws cloudformation describe-stacks --stack-name $STACK_NAME \ + --query "Stacks[0].Outputs[?OutputKey=='$2'].OutputValue" \ + --output text) + echo API URL: $API_URL + + LAMBDA_GETPRODUCTS=$(aws cloudformation describe-stacks --stack-name $STACK_NAME \ + --query "Stacks[0].Outputs[?OutputKey=='$3'].OutputValue" \ + --output text) + echo LAMBDA: $LAMBDA_GETPRODUCTS + + LAMBDA_GETPRODUCT=$(aws cloudformation describe-stacks --stack-name $STACK_NAME \ + --query "Stacks[0].Outputs[?OutputKey=='$4'].OutputValue" \ + --output text) + echo LAMBDA: $LAMBDA_GETPRODUCT + + LAMBDA_DELETEPRODUCT=$(aws cloudformation describe-stacks --stack-name $STACK_NAME \ + --query "Stacks[0].Outputs[?OutputKey=='$5'].OutputValue" \ + --output text) + echo LAMBDA: $LAMBDA_DELETEPRODUCT + + LAMBDA_PUTPRODUCT=$(aws cloudformation describe-stacks --stack-name $STACK_NAME \ + --query "Stacks[0].Outputs[?OutputKey=='$6'].OutputValue" \ + --output text) + echo LAMBDA: $LAMBDA_PUTPRODUCT + + if [ $LOG_DELETE == "yes" ]; + then + echo -------------------------------------------- + echo DELETING CLOUDWATCH LOG GROUP /aws/lambda/$LAMBDA_GETPRODUCTS + echo -------------------------------------------- + aws logs delete-log-group --log-group-name /aws/lambda/$LAMBDA_GETPRODUCTS + echo -------------------------------------------- + echo DELETING CLOUDWATCH LOG GROUP /aws/lambda/$LAMBDA_GETPRODUCT + echo -------------------------------------------- + aws logs delete-log-group --log-group-name /aws/lambda/$LAMBDA_GETPRODUCT + echo -------------------------------------------- + echo DELETING CLOUDWATCH LOG GROUP /aws/lambda/$LAMBDA_DELETEPRODUCT + echo -------------------------------------------- + aws logs delete-log-group --log-group-name /aws/lambda/$LAMBDA_DELETEPRODUCT + echo -------------------------------------------- + echo DELETING CLOUDWATCH LOG GROUP /aws/lambda/$LAMBDA_PUTPRODUCT + echo -------------------------------------------- + aws logs delete-log-group --log-group-name /aws/lambda/$LAMBDA_PUTPRODUCT + echo --------------------------------------------- + echo Waiting 10 sec. for deletion to complete + echo -------------------------------------------- + sleep 10 + fi + + #run load test with artillery + echo -------------------------------------------- + echo $1 RUNNING LOAD TEST $TEST_DURATIOMN_SEC sec $API_URL + echo -------------------------------------------- + echo "${NO_COLOR}" + artillery run \ + --overrides '{"config": { "phases": [{ "duration": '$TEST_DURATIOMN_SEC', "arrivalRate": 100 }] } }' \ + --quiet \ + ../../loadtest/codebuild/load-test.yml + + echo "${COLOR}" + echo -------------------------------------------- + echo Waiting 10 sec. for logs to consolidate + echo -------------------------------------------- + sleep 10 + + #get stats from cloudwatch + enddate=$(date "+%s") + startdate=$(($enddate-($LOG_INTERVAL_MIN*60))) + echo -------------------------------------------- + echo Log start:$startdate end:$enddate + echo -------------------------------------------- + + QUERY_ID=$(aws logs start-query \ + --log-group-names "/aws/lambda/$LAMBDA_GETPRODUCTS" "/aws/lambda/$LAMBDA_GETPRODUCT" "/aws/lambda/$LAMBDA_DELETEPRODUCT" "/aws/lambda/$LAMBDA_PUTPRODUCT" \ + --start-time $startdate \ + --end-time $enddate \ + --query-string 'filter @type="REPORT" | fields greatest(@initDuration, 0) + @duration as duration, ispresent(@initDuration) as coldstart | stats count(*) as count, pct(duration, 50) as p50, pct(duration, 90) as p90, pct(duration, 99) as p99, max(duration) as max by coldstart' \ + | jq -r '.queryId') + + echo -------------------------------------------- + echo Query started, id: $QUERY_ID + echo -------------------------------------------- + + echo --------------------------------------------- + echo Waiting 10 sec. for cloudwatch query to complete + echo -------------------------------------------- + sleep 10 + + echo -------------------------------------------- + echo RESULTS $1 + echo -------------------------------------------- + echo "${NO_COLOR}" + date > ./Report/load-test-report-$1.txt + echo $1 RESULTS >> ./Report/load-test-report-$1.txt + echo Test duration sec: $TEST_DURATIOMN_SEC >> ./Report/load-test-report-$1.txt + echo Log interval min: $LOG_INTERVAL_MIN >> ./Report/load-test-report-$1.txt + aws logs get-query-results --query-id $QUERY_ID --output text >> ./Report/load-test-report-$1.txt + cat ./Report/load-test-report-$1.txt + aws logs get-query-results --query-id $QUERY_ID --output json >> ./Report/load-test-report-$1.json +} + +RunLoadTest x86 ApiUrlX86 LambdaX86NameGetProducts LambdaX86NameGetProduct LambdaX86NameDeleteProduct LambdaX86NamePutProduct +RunLoadTest arm64 ApiUrlArm64 LambdaArm64NameGetProducts LambdaArm64NameGetProduct LambdaArm64NameDeleteProduct LambdaArm64NamePutProduct \ No newline at end of file diff --git a/src/NET6Native/template.yaml b/src/NET6Native/template.yaml index dffeb21..cea0c20 100755 --- a/src/NET6Native/template.yaml +++ b/src/NET6Native/template.yaml @@ -1,46 +1,146 @@ AWSTemplateFormatVersion: "2010-09-09" Transform: AWS::Serverless-2016-10-31 +Parameters: + x86FunctionNamePrefix: + Type: String + Default: Net6-X86-Native + arm64FunctionNamePrefix: + Type: String + Default: Net6-Arm64-Native + Globals: Function: MemorySize: 1024 - Architectures: ["arm64"] Runtime: provided.al2 - Timeout: 5 + Timeout: 30 Tracing: Active Environment: Variables: PRODUCT_TABLE_NAME: !Ref Table Resources: - GetProductsFunction: + #X86 + GetProductsFunctionX86: Type: AWS::Serverless::Function Properties: - CodeUri: ./GetProducts/ + FunctionName: !Join [ -,[!Ref x86FunctionNamePrefix,GetProducts]] + Architectures: [x86_64] + CodeUri: ./ Handler: GetProducts Events: Api: Type: HttpApi Properties: - Path: / + Path: /x86 + Method: GET + Policies: + - DynamoDBReadPolicy: + TableName: + !Ref Table + Metadata: + BuildMethod: makefile + + GetProductFunctionX86: + Type: AWS::Serverless::Function + Properties: + FunctionName: !Join [ -,[!Ref x86FunctionNamePrefix,GetProduct]] + Architectures: [x86_64] + CodeUri: ./ + Handler: GetProduct + Events: + Api: + Type: HttpApi + Properties: + Path: /x86/{id} Method: GET Policies: - Version: "2012-10-17" Statement: - Effect: Allow - Action: dynamodb:Scan + Action: dynamodb:GetItem Resource: !GetAtt Table.Arn + Metadata: + BuildMethod: makefile + + DeleteProductFunctionX86: + Type: AWS::Serverless::Function + Properties: + FunctionName: !Join [ -,[!Ref x86FunctionNamePrefix,DeleteProduct]] + Architectures: [x86_64] + CodeUri: ./ + Handler: DeleteProduct + Events: + Api: + Type: HttpApi + Properties: + Path: /x86/{id} + Method: DELETE + Policies: + - Version: "2012-10-17" + Statement: + - Effect: Allow + Action: + - dynamodb:DeleteItem + - dynamodb:GetItem + Resource: !GetAtt Table.Arn + Metadata: + BuildMethod: makefile + + PutProductFunctionX86: + Type: AWS::Serverless::Function + Properties: + FunctionName: !Join [ -,[!Ref x86FunctionNamePrefix,PutProduct]] + Architectures: [x86_64] + CodeUri: ./ + Handler: PutProduct + Events: + Api: + Type: HttpApi + Properties: + Path: /x86/{id} + Method: PUT + Policies: + - Version: "2012-10-17" + Statement: + - Effect: Allow + Action: dynamodb:PutItem + Resource: !GetAtt Table.Arn + Metadata: + BuildMethod: makefile + #ARM64 + GetProductsFunctionArm64: + Type: AWS::Serverless::Function + Properties: + FunctionName: !Join [ -,[!Ref arm64FunctionNamePrefix,GetProducts]] + Architectures: [arm64] + CodeUri: ./ + Handler: GetProducts + Events: + Api: + Type: HttpApi + Properties: + Path: /arm64 + Method: GET + Policies: + - DynamoDBReadPolicy: + TableName: + !Ref Table + Metadata: + BuildMethod: makefile - GetProductFunction: + GetProductFunctionArm64: Type: AWS::Serverless::Function Properties: - CodeUri: ./GetProduct/ + FunctionName: !Join [ -,[!Ref arm64FunctionNamePrefix,GetProduct]] + Architectures: [arm64] + CodeUri: ./ Handler: GetProduct Events: Api: Type: HttpApi Properties: - Path: /{id} + Path: /arm64/{id} Method: GET Policies: - Version: "2012-10-17" @@ -48,17 +148,21 @@ Resources: - Effect: Allow Action: dynamodb:GetItem Resource: !GetAtt Table.Arn + Metadata: + BuildMethod: makefile - DeleteProductFunction: + DeleteProductFunctionArm64: Type: AWS::Serverless::Function Properties: - CodeUri: ./DeleteProduct/ + FunctionName: !Join [ -,[!Ref arm64FunctionNamePrefix,DeleteProduct]] + Architectures: [arm64] + CodeUri: ./ Handler: DeleteProduct Events: Api: Type: HttpApi Properties: - Path: /{id} + Path: /arm64/{id} Method: DELETE Policies: - Version: "2012-10-17" @@ -68,17 +172,21 @@ Resources: - dynamodb:DeleteItem - dynamodb:GetItem Resource: !GetAtt Table.Arn + Metadata: + BuildMethod: makefile - PutProductFunction: + PutProductFunctionArm64: Type: AWS::Serverless::Function Properties: - CodeUri: ./PutProduct/ + FunctionName: !Join [ -,[!Ref arm64FunctionNamePrefix,PutProduct]] + Architectures: [arm64] + CodeUri: ./ Handler: PutProduct Events: Api: Type: HttpApi Properties: - Path: /{id} + Path: /arm64/{id} Method: PUT Policies: - Version: "2012-10-17" @@ -86,6 +194,8 @@ Resources: - Effect: Allow Action: dynamodb:PutItem Resource: !GetAtt Table.Arn + Metadata: + BuildMethod: makefile Table: Type: AWS::DynamoDB::Table @@ -97,10 +207,42 @@ Resources: KeySchema: - AttributeName: id KeyType: HASH - StreamSpecification: - StreamViewType: NEW_AND_OLD_IMAGES Outputs: ApiUrl: Description: "API Gateway endpoint URL" - Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/" \ No newline at end of file + Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com" + ApiUrlX86: + Description: "X86 API endpoint URL" + Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/x86" + ApiUrlArm64: + Description: "Arm64 GateAPI endpoint URL" + Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/arm64" + + #x86 + LambdaX86NameGetProducts: + Description: "Lambda X86 GetProducts" + Value: !Join [ -,[!Ref x86FunctionNamePrefix,GetProducts]] + LambdaX86NameGetProduct: + Description: "Lambda X86 GetProduct" + Value: !Join [ -,[!Ref x86FunctionNamePrefix,GetProduct]] + LambdaX86NameDeleteProduct: + Description: "Lambda X86 DeleteProduct" + Value: !Join [ -,[!Ref x86FunctionNamePrefix,DeleteProduct]] + LambdaX86NamePutProduct: + Description: "Lambda X86 PutProduct" + Value: !Join [ -,[!Ref x86FunctionNamePrefix,PutProduct]] + + #arm64 + LambdaArm64NameGetProducts: + Description: "Lambda X86 GetProducts" + Value: !Join [ -,[!Ref arm64FunctionNamePrefix,GetProducts]] + LambdaArm64NameGetProduct: + Description: "Lambda X86 GetProduct" + Value: !Join [ -,[!Ref arm64FunctionNamePrefix,GetProduct]] + LambdaArm64NameDeleteProduct: + Description: "Lambda X86 DeleteProduct" + Value: !Join [ -,[!Ref arm64FunctionNamePrefix,DeleteProduct]] + LambdaArm64NamePutProduct: + Description: "Lambda X86 PutProduct" + Value: !Join [ -,[!Ref arm64FunctionNamePrefix,PutProduct]] \ No newline at end of file diff --git a/src/NET6TopLevelStatements/DeleteProduct/DeleteProduct.csproj b/src/NET6TopLevelStatements/DeleteProduct/DeleteProduct.csproj index ba213d4..8b25d8b 100755 --- a/src/NET6TopLevelStatements/DeleteProduct/DeleteProduct.csproj +++ b/src/NET6TopLevelStatements/DeleteProduct/DeleteProduct.csproj @@ -7,14 +7,14 @@ true - - - - - - - - + + + + + + + + diff --git a/src/NET6TopLevelStatements/GenerateLoadTestResults/DateUtils.cs b/src/NET6TopLevelStatements/GenerateLoadTestResults/DateUtils.cs deleted file mode 100644 index c37c43d..0000000 --- a/src/NET6TopLevelStatements/GenerateLoadTestResults/DateUtils.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; - -namespace GenerateLoadTestResults; - -public static class DateUtils -{ - public static long AsUnixTimestamp(this DateTime date) - { - DateTime origin = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); - TimeSpan diff = date.ToUniversalTime() - origin; - return (long)Math.Floor(diff.TotalSeconds); - } -} \ No newline at end of file diff --git a/src/NET6TopLevelStatements/GenerateLoadTestResults/Function.cs b/src/NET6TopLevelStatements/GenerateLoadTestResults/Function.cs deleted file mode 100644 index 0b701ce..0000000 --- a/src/NET6TopLevelStatements/GenerateLoadTestResults/Function.cs +++ /dev/null @@ -1,125 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Text.Json; -using System.Threading.Tasks; -using Amazon.CloudWatchLogs; -using Amazon.CloudWatchLogs.Model; -using Amazon.Lambda.APIGatewayEvents; -using Amazon.Lambda.Core; - -[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] - -namespace GenerateLoadTestResults -{ - public class Function - { - private AmazonCloudWatchLogsClient _cloudWatchLogsClient; - - public Function() - { - this._cloudWatchLogsClient = new AmazonCloudWatchLogsClient(); - } - - public async Task FunctionHandler(APIGatewayHttpApiV2ProxyRequest apigProxyEvent, - ILambdaContext context) - { - if (!apigProxyEvent.RequestContext.Http.Method.Equals(HttpMethod.Get.Method)) - { - return new APIGatewayHttpApiV2ProxyResponse - { - Body = "Only GET allowed", - StatusCode = (int)HttpStatusCode.MethodNotAllowed, - }; - } - - try - { - var logGroupNamePrefix = - $"{Environment.GetEnvironmentVariable("LOG_GROUP_PREFIX")}{Environment.GetEnvironmentVariable("LAMBDA_ARCHITECTURE")}".Replace("_", "-"); - - context.Logger.LogLine($"Retrieving log groups with prefix {logGroupNamePrefix}"); - - var logGroupList = await _cloudWatchLogsClient.DescribeLogGroupsAsync(new DescribeLogGroupsRequest() - { - LogGroupNamePrefix = logGroupNamePrefix, - }); - - context.Logger.LogLine($"Found {logGroupList.LogGroups.Count} log group(s)"); - - var queryRes = await _cloudWatchLogsClient.StartQueryAsync(new StartQueryRequest() - { - LogGroupNames = logGroupList.LogGroups.Select(p => p.LogGroupName).ToList(), - QueryString = - "filter @type=\"REPORT\" | fields greatest(@initDuration, 0) + @duration as duration, ispresent(@initDuration) as coldstart | stats count(*) as count, pct(duration, 50) as p50, pct(duration, 90) as p90, pct(duration, 99) as p99, max(duration) as max by coldstart", - StartTime = DateTime.Now.AddMinutes(-20).AsUnixTimestamp(), - EndTime = DateTime.Now.AsUnixTimestamp(), - }); - - context.Logger.LogLine($"Running query, query id is {queryRes.QueryId}"); - - QueryStatus currentQueryStatus = QueryStatus.Running; - List> finalResults = new List>(); - - while (currentQueryStatus == QueryStatus.Running || currentQueryStatus == QueryStatus.Scheduled) - { - context.Logger.LogLine("Retrieving query results"); - - var queryResults = await _cloudWatchLogsClient.GetQueryResultsAsync(new GetQueryResultsRequest() - { - QueryId = queryRes.QueryId - }); - - context.Logger.LogLine($"Query result status is {queryResults.Status}"); - - currentQueryStatus = queryResults.Status; - finalResults = queryResults.Results; - - await Task.Delay(TimeSpan.FromSeconds(5)); - } - - context.Logger.LogLine($"Final results: {finalResults.Count} row(s)"); - - var wrapper = new QueryResultWrapper() - { - LoadTestType = $"{Environment.GetEnvironmentVariable("LOAD_TEST_TYPE")} ({Environment.GetEnvironmentVariable("LAMBDA_ARCHITECTURE")})", - WarmStart = new QueryResult() - { - Count = finalResults[0][1].Value, - P50 = finalResults[0][2].Value, - P90 = finalResults[0][3].Value, - P99 = finalResults[0][4].Value, - Max = finalResults[0][5].Value, - }, - ColdStart = new QueryResult() - { - Count = finalResults[1][1].Value, - P50 = finalResults[1][2].Value, - P90 = finalResults[1][3].Value, - P99 = finalResults[1][4].Value, - Max = finalResults[1][5].Value, - } - }; - - return new APIGatewayHttpApiV2ProxyResponse - { - StatusCode = (int)HttpStatusCode.OK, - Body = wrapper.AsMarkdownTableRow(), - Headers = new Dictionary {{"Content-Type", "text/html"}} - }; - } - catch (Exception e) - { - context.Logger.LogLine($"Error retrieving results {e.Message} {e.StackTrace}"); - - return new APIGatewayHttpApiV2ProxyResponse - { - Body = "Not Found", - StatusCode = (int)HttpStatusCode.InternalServerError, - }; - } - } - } -} diff --git a/src/NET6TopLevelStatements/GenerateLoadTestResults/GenerateLoadTestResults.csproj b/src/NET6TopLevelStatements/GenerateLoadTestResults/GenerateLoadTestResults.csproj deleted file mode 100644 index 4a8d708..0000000 --- a/src/NET6TopLevelStatements/GenerateLoadTestResults/GenerateLoadTestResults.csproj +++ /dev/null @@ -1,17 +0,0 @@ - - - - net6.0 - true - true - - - - - - - - - - - diff --git a/src/NET6TopLevelStatements/GenerateLoadTestResults/QueryResult.cs b/src/NET6TopLevelStatements/GenerateLoadTestResults/QueryResult.cs deleted file mode 100644 index 69b5a31..0000000 --- a/src/NET6TopLevelStatements/GenerateLoadTestResults/QueryResult.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace GenerateLoadTestResults; - -public record QueryResultWrapper -{ - public string LoadTestType { get; set; } - - public QueryResult ColdStart { get; set; } - - public QueryResult WarmStart { get; set; } - - public string AsMarkdownTableRow() => $"
Cold Start (ms)Warm Start (ms)
p50p90p99maxp50p90p99max
{LoadTestType}{ColdStart.P50}{ColdStart.P90}{ColdStart.P99}{ColdStart.Max}{WarmStart.P50}{WarmStart.P90}{WarmStart.P99}{WarmStart.Max}
"; -} - -public record QueryResult -{ - public string Count { get; set; } - - public string P50 { get; set; } - - public string P90 { get; set; } - - public string P99 { get; set; } - - public string Max { get; set; } -} \ No newline at end of file diff --git a/src/NET6TopLevelStatements/GetProduct/GetProduct.csproj b/src/NET6TopLevelStatements/GetProduct/GetProduct.csproj index ba213d4..441cb1e 100755 --- a/src/NET6TopLevelStatements/GetProduct/GetProduct.csproj +++ b/src/NET6TopLevelStatements/GetProduct/GetProduct.csproj @@ -7,14 +7,15 @@ true - - - - - - - - + + + + + + + + + diff --git a/src/NET6TopLevelStatements/GetProducts/GetProducts.csproj b/src/NET6TopLevelStatements/GetProducts/GetProducts.csproj index ba213d4..8b25d8b 100755 --- a/src/NET6TopLevelStatements/GetProducts/GetProducts.csproj +++ b/src/NET6TopLevelStatements/GetProducts/GetProducts.csproj @@ -7,14 +7,14 @@ true - - - - - - - - + + + + + + + + diff --git a/src/NET6TopLevelStatements/PutProduct/PutProduct.csproj b/src/NET6TopLevelStatements/PutProduct/PutProduct.csproj index ba213d4..8b25d8b 100755 --- a/src/NET6TopLevelStatements/PutProduct/PutProduct.csproj +++ b/src/NET6TopLevelStatements/PutProduct/PutProduct.csproj @@ -7,14 +7,14 @@ true - - - - - - - - + + + + + + + + diff --git a/src/NET6TopLevelStatements/Shared/Shared.csproj b/src/NET6TopLevelStatements/Shared/Shared.csproj index 9fce83c..fcdc7e6 100755 --- a/src/NET6TopLevelStatements/Shared/Shared.csproj +++ b/src/NET6TopLevelStatements/Shared/Shared.csproj @@ -6,10 +6,10 @@ enable - - - - - + + + + +
diff --git a/src/NET6TopLevelStatements/deploy.sh b/src/NET6TopLevelStatements/deploy.sh new file mode 100755 index 0000000..56d3457 --- /dev/null +++ b/src/NET6TopLevelStatements/deploy.sh @@ -0,0 +1,43 @@ +#Arguments: +#$1 - delete stack formation to ensure that all test have same conditon and favour similar ammount of cold start events + +STACK_NAME=dotnet6-toplevel +DELETE_STACK=yes + +COLOR='\033[0;33m' +NO_COLOR='\033[0m' # No Color + +if [ "x$1" != x ]; +then + DELETE_STACK=$1 +fi + +echo "${COLOR}" +echo -------------------------------------------- +echo DELETE_STACK: $DELETE_STACK +echo -------------------------------------------- +echo "${NO_COLOR}" + +if [ $DELETE_STACK == "yes" ]; +then + echo "${COLOR}" + echo -------------------------------------------- + echo DELETING STACK $STACK_NAME + echo -------------------------------------------- + echo "${NO_COLOR}" + aws cloudformation delete-stack --stack-name $STACK_NAME + echo "${COLOR}" + echo --------------------------------------------- + echo Waiting stack to be deleted + echo -------------------------------------------- + echo "${NO_COLOR}" + aws cloudformation wait stack-delete-complete --stack-name $STACK_NAME + echo "${COLOR}" + echo --------------------------------------------- + echo Stack deleted + echo -------------------------------------------- + echo "${NO_COLOR}" +fi + +sam build +sam deploy --stack-name $STACK_NAME --resolve-s3 --s3-prefix $STACK_NAME --no-confirm-changeset --no-fail-on-empty-changeset --capabilities CAPABILITY_IAM \ No newline at end of file diff --git a/src/NET6TopLevelStatements/run-loadtest.sh b/src/NET6TopLevelStatements/run-loadtest.sh new file mode 100755 index 0000000..49e1f4c --- /dev/null +++ b/src/NET6TopLevelStatements/run-loadtest.sh @@ -0,0 +1,153 @@ +#Arguments: +#$1 - load test duration in seconds +#$2 - log interval to be used in the cloudwatch query in minutes +#$3 - when equal to 1 cloudwatch log group will be deleted to ensure that only logs of the load test will be evaluated for stat + +STACK_NAME=dotnet6-toplevel +TEST_DURATIOMN_SEC=60 +LOG_INTERVAL_MIN=20 +LOG_DELETE=yes + +COLOR='\033[0;33m' +NO_COLOR='\033[0m' # No Color + +if [ "x$1" != x ]; +then + TEST_DURATIOMN_SEC=$1 +fi + +if [ "x$2" != x ]; +then + LOG_INTERVAL_MIN=$2 +fi + +if [ "x$3" != x ]; +then + LOG_DELETE=$3 +fi + +echo "${COLOR}" +echo -------------------------------------------- +echo DURATION:$TEST_DURATIOMN_SEC +echo LOG INTERVAL:$LOG_INTERVAL_MIN +echo LOG_DELETE: $LOG_DELETE +echo -------------------------------------------- +echo "${NO_COLOR}" + +mkdir -p Report + +function RunLoadTest() +{ + #Params: + #$1 - Architecture (x86 or arm64).Used for logging and naming report file + #$2 - Stack output name to get API Url + #$3 - Stack output name to get lambda name GetProducts + #$4 - Stack output name to get lambda name GetProduct + #$5 - Stack output name to get lambda name DeleteProduct + #$6 - Stack output name to get lambda name PutProduct + + #get test params from cloud formation output + echo "${COLOR}" + export API_URL=$(aws cloudformation describe-stacks --stack-name $STACK_NAME \ + --query "Stacks[0].Outputs[?OutputKey=='$2'].OutputValue" \ + --output text) + echo API URL: $API_URL + + LAMBDA_GETPRODUCTS=$(aws cloudformation describe-stacks --stack-name $STACK_NAME \ + --query "Stacks[0].Outputs[?OutputKey=='$3'].OutputValue" \ + --output text) + echo LAMBDA: $LAMBDA_GETPRODUCTS + + LAMBDA_GETPRODUCT=$(aws cloudformation describe-stacks --stack-name $STACK_NAME \ + --query "Stacks[0].Outputs[?OutputKey=='$4'].OutputValue" \ + --output text) + echo LAMBDA: $LAMBDA_GETPRODUCT + + LAMBDA_DELETEPRODUCT=$(aws cloudformation describe-stacks --stack-name $STACK_NAME \ + --query "Stacks[0].Outputs[?OutputKey=='$5'].OutputValue" \ + --output text) + echo LAMBDA: $LAMBDA_DELETEPRODUCT + + LAMBDA_PUTPRODUCT=$(aws cloudformation describe-stacks --stack-name $STACK_NAME \ + --query "Stacks[0].Outputs[?OutputKey=='$6'].OutputValue" \ + --output text) + echo LAMBDA: $LAMBDA_PUTPRODUCT + + if [ $LOG_DELETE == "yes" ]; + then + echo -------------------------------------------- + echo DELETING CLOUDWATCH LOG GROUP /aws/lambda/$LAMBDA_GETPRODUCTS + echo -------------------------------------------- + aws logs delete-log-group --log-group-name /aws/lambda/$LAMBDA_GETPRODUCTS + echo -------------------------------------------- + echo DELETING CLOUDWATCH LOG GROUP /aws/lambda/$LAMBDA_GETPRODUCT + echo -------------------------------------------- + aws logs delete-log-group --log-group-name /aws/lambda/$LAMBDA_GETPRODUCT + echo -------------------------------------------- + echo DELETING CLOUDWATCH LOG GROUP /aws/lambda/$LAMBDA_DELETEPRODUCT + echo -------------------------------------------- + aws logs delete-log-group --log-group-name /aws/lambda/$LAMBDA_DELETEPRODUCT + echo -------------------------------------------- + echo DELETING CLOUDWATCH LOG GROUP /aws/lambda/$LAMBDA_PUTPRODUCT + echo -------------------------------------------- + aws logs delete-log-group --log-group-name /aws/lambda/$LAMBDA_PUTPRODUCT + echo --------------------------------------------- + echo Waiting 10 sec. for deletion to complete + echo -------------------------------------------- + sleep 10 + fi + + #run load test with artillery + echo -------------------------------------------- + echo $1 RUNNING LOAD TEST $TEST_DURATIOMN_SEC sec $API_URL + echo -------------------------------------------- + echo "${NO_COLOR}" + artillery run \ + --overrides '{"config": { "phases": [{ "duration": '$TEST_DURATIOMN_SEC', "arrivalRate": 100 }] } }' \ + --quiet \ + ../../loadtest/codebuild/load-test.yml + + echo "${COLOR}" + echo -------------------------------------------- + echo Waiting 10 sec. for logs to consolidate + echo -------------------------------------------- + sleep 10 + + #get stats from cloudwatch + enddate=$(date "+%s") + startdate=$(($enddate-($LOG_INTERVAL_MIN*60))) + echo -------------------------------------------- + echo Log start:$startdate end:$enddate + echo -------------------------------------------- + + QUERY_ID=$(aws logs start-query \ + --log-group-names "/aws/lambda/$LAMBDA_GETPRODUCTS" "/aws/lambda/$LAMBDA_GETPRODUCT" "/aws/lambda/$LAMBDA_DELETEPRODUCT" "/aws/lambda/$LAMBDA_PUTPRODUCT" \ + --start-time $startdate \ + --end-time $enddate \ + --query-string 'filter @type="REPORT" | fields greatest(@initDuration, 0) + @duration as duration, ispresent(@initDuration) as coldstart | stats count(*) as count, pct(duration, 50) as p50, pct(duration, 90) as p90, pct(duration, 99) as p99, max(duration) as max by coldstart' \ + | jq -r '.queryId') + + echo -------------------------------------------- + echo Query started, id: $QUERY_ID + echo -------------------------------------------- + + echo --------------------------------------------- + echo Waiting 10 sec. for cloudwatch query to complete + echo -------------------------------------------- + sleep 10 + + echo -------------------------------------------- + echo RESULTS $1 + echo -------------------------------------------- + echo "${NO_COLOR}" + date > ./Report/load-test-report-$1.txt + echo $1 RESULTS >> ./Report/load-test-report-$1.txt + echo Test duration sec: $TEST_DURATIOMN_SEC >> ./Report/load-test-report-$1.txt + echo Log interval min: $LOG_INTERVAL_MIN >> ./Report/load-test-report-$1.txt + aws logs get-query-results --query-id $QUERY_ID --output text >> ./Report/load-test-report-$1.txt + cat ./Report/load-test-report-$1.txt + aws logs get-query-results --query-id $QUERY_ID --output json >> ./Report/load-test-report-$1.json +} + +RunLoadTest x86 ApiUrlX86 LambdaX86NameGetProducts LambdaX86NameGetProduct LambdaX86NameDeleteProduct LambdaX86NamePutProduct +RunLoadTest arm64 ApiUrlArm64 LambdaArm64NameGetProducts LambdaArm64NameGetProduct LambdaArm64NameDeleteProduct LambdaArm64NamePutProduct \ No newline at end of file diff --git a/src/NET6TopLevelStatements/template.yaml b/src/NET6TopLevelStatements/template.yaml index c2888aa..2d428cf 100755 --- a/src/NET6TopLevelStatements/template.yaml +++ b/src/NET6TopLevelStatements/template.yaml @@ -1,10 +1,16 @@ AWSTemplateFormatVersion: "2010-09-09" Transform: AWS::Serverless-2016-10-31 +Parameters: + x86FunctionNamePrefix: + Type: String + Default: Net6-X86-toplevel + arm64FunctionNamePrefix: + Type: String + Default: Net6-Arm64-toplevel Globals: Function: MemorySize: 1024 - Architectures: [!Ref LambdaArchitecture] Runtime: dotnet6 Timeout: 30 Tracing: Active @@ -12,43 +18,38 @@ Globals: Variables: PRODUCT_TABLE_NAME: !Ref Table -Parameters: - LambdaArchitecture: - Type: String - AllowedValues: - - arm64 - - x86_64 - Description: Enter arm64 or x86_64 - Resources: - GetProductsFunction: + #X86 + GetProductsFunctionX86: Type: AWS::Serverless::Function Properties: + FunctionName: !Join [ -,[!Ref x86FunctionNamePrefix,GetProducts]] + Architectures: [x86_64] CodeUri: ./GetProducts/ Handler: GetProducts Events: Api: Type: HttpApi Properties: - Path: / + Path: /x86 Method: GET Policies: - - Version: "2012-10-17" - Statement: - - Effect: Allow - Action: dynamodb:Scan - Resource: !GetAtt Table.Arn + - DynamoDBReadPolicy: + TableName: + !Ref Table - GetProductFunction: + GetProductFunctionX86: Type: AWS::Serverless::Function Properties: + FunctionName: !Join [ -,[!Ref x86FunctionNamePrefix,GetProduct]] + Architectures: [x86_64] CodeUri: ./GetProduct/ Handler: GetProduct Events: Api: Type: HttpApi Properties: - Path: /{id} + Path: /x86/{id} Method: GET Policies: - Version: "2012-10-17" @@ -57,16 +58,18 @@ Resources: Action: dynamodb:GetItem Resource: !GetAtt Table.Arn - DeleteProductFunction: + DeleteProductFunctionX86: Type: AWS::Serverless::Function Properties: + FunctionName: !Join [ -,[!Ref x86FunctionNamePrefix,DeleteProduct]] + Architectures: [x86_64] CodeUri: ./DeleteProduct/ Handler: DeleteProduct Events: Api: Type: HttpApi Properties: - Path: /{id} + Path: /x86/{id} Method: DELETE Policies: - Version: "2012-10-17" @@ -77,16 +80,18 @@ Resources: - dynamodb:GetItem Resource: !GetAtt Table.Arn - PutProductFunction: + PutProductFunctionX86: Type: AWS::Serverless::Function Properties: + FunctionName: !Join [ -,[!Ref x86FunctionNamePrefix,PutProduct]] + Architectures: [x86_64] CodeUri: ./PutProduct/ Handler: PutProduct Events: Api: Type: HttpApi Properties: - Path: /{id} + Path: /x86/{id} Method: PUT Policies: - Version: "2012-10-17" @@ -94,36 +99,86 @@ Resources: - Effect: Allow Action: dynamodb:PutItem Resource: !GetAtt Table.Arn +#ARM64 + GetProductsFunctionArm64: + Type: AWS::Serverless::Function + Properties: + FunctionName: !Join [ -,[!Ref arm64FunctionNamePrefix,GetProducts]] + Architectures: [arm64] + CodeUri: ./GetProducts/ + Handler: GetProducts + Events: + Api: + Type: HttpApi + Properties: + Path: /arm64 + Method: GET + Policies: + - DynamoDBReadPolicy: + TableName: + !Ref Table - GenerateLoadTestResults: + GetProductFunctionArm64: Type: AWS::Serverless::Function Properties: - CodeUri: ./GenerateLoadTestResults/ - Handler: GenerateLoadTestResults::GenerateLoadTestResults.Function::FunctionHandler + FunctionName: !Join [ -,[!Ref arm64FunctionNamePrefix,GetProduct]] + Architectures: [arm64] + CodeUri: ./GetProduct/ + Handler: GetProduct Events: Api: Type: HttpApi Properties: - Path: /test-results + Path: /arm64/{id} Method: GET - Environment: - Variables: - LOG_GROUP_PREFIX: !Sub "/aws/lambda/net-6-top-level-" - LOAD_TEST_TYPE: "NET 6 Top Level Statements" - LAMBDA_ARCHITECTURE: !Ref LambdaArchitecture Policies: - Version: "2012-10-17" Statement: - - Sid: AllowStartQueries - Effect: Allow + - Effect: Allow + Action: dynamodb:GetItem + Resource: !GetAtt Table.Arn + + DeleteProductFunctionArm64: + Type: AWS::Serverless::Function + Properties: + FunctionName: !Join [ -,[!Ref arm64FunctionNamePrefix,DeleteProduct]] + Architectures: [arm64] + CodeUri: ./DeleteProduct/ + Handler: DeleteProduct + Events: + Api: + Type: HttpApi + Properties: + Path: /arm64/{id} + Method: DELETE + Policies: + - Version: "2012-10-17" + Statement: + - Effect: Allow Action: - - logs:DescribeLogGroups - - logs:StartQuery - Resource: "*" - - Sid: AllowGetQueryResults - Effect: Allow - Action: logs:GetQueryResults - Resource: "*" + - dynamodb:DeleteItem + - dynamodb:GetItem + Resource: !GetAtt Table.Arn + + PutProductFunctionArm64: + Type: AWS::Serverless::Function + Properties: + FunctionName: !Join [ -,[!Ref arm64FunctionNamePrefix,PutProduct]] + Architectures: [arm64] + CodeUri: ./PutProduct/ + Handler: PutProduct + Events: + Api: + Type: HttpApi + Properties: + Path: /arm64/{id} + Method: PUT + Policies: + - Version: "2012-10-17" + Statement: + - Effect: Allow + Action: dynamodb:PutItem + Resource: !GetAtt Table.Arn Table: Type: AWS::DynamoDB::Table @@ -135,10 +190,42 @@ Resources: KeySchema: - AttributeName: id KeyType: HASH - StreamSpecification: - StreamViewType: NEW_AND_OLD_IMAGES Outputs: ApiUrl: Description: "API Gateway endpoint URL" - Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/" \ No newline at end of file + Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com" + ApiUrlX86: + Description: "X86 API endpoint URL" + Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/x86" + ApiUrlArm64: + Description: "Arm64 GateAPI endpoint URL" + Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/arm64" + + #x86 + LambdaX86NameGetProducts: + Description: "Lambda X86 GetProducts" + Value: !Join [ -,[!Ref x86FunctionNamePrefix,GetProducts]] + LambdaX86NameGetProduct: + Description: "Lambda X86 GetProduct" + Value: !Join [ -,[!Ref x86FunctionNamePrefix,GetProduct]] + LambdaX86NameDeleteProduct: + Description: "Lambda X86 DeleteProduct" + Value: !Join [ -,[!Ref x86FunctionNamePrefix,DeleteProduct]] + LambdaX86NamePutProduct: + Description: "Lambda X86 PutProduct" + Value: !Join [ -,[!Ref x86FunctionNamePrefix,PutProduct]] + + #arm64 + LambdaArm64NameGetProducts: + Description: "Lambda X86 GetProducts" + Value: !Join [ -,[!Ref arm64FunctionNamePrefix,GetProducts]] + LambdaArm64NameGetProduct: + Description: "Lambda X86 GetProduct" + Value: !Join [ -,[!Ref arm64FunctionNamePrefix,GetProduct]] + LambdaArm64NameDeleteProduct: + Description: "Lambda X86 DeleteProduct" + Value: !Join [ -,[!Ref arm64FunctionNamePrefix,DeleteProduct]] + LambdaArm64NamePutProduct: + Description: "Lambda X86 PutProduct" + Value: !Join [ -,[!Ref arm64FunctionNamePrefix,PutProduct]] \ No newline at end of file diff --git a/src/NET6WithPowerTools/DeleteProduct/DeleteProduct.csproj b/src/NET6WithPowerTools/DeleteProduct/DeleteProduct.csproj index 9e91e7c..d81c654 100755 --- a/src/NET6WithPowerTools/DeleteProduct/DeleteProduct.csproj +++ b/src/NET6WithPowerTools/DeleteProduct/DeleteProduct.csproj @@ -7,11 +7,11 @@ - - - - - + + + + + diff --git a/src/NET6WithPowerTools/GenerateLoadTestResults/DateUtils.cs b/src/NET6WithPowerTools/GenerateLoadTestResults/DateUtils.cs deleted file mode 100644 index c37c43d..0000000 --- a/src/NET6WithPowerTools/GenerateLoadTestResults/DateUtils.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; - -namespace GenerateLoadTestResults; - -public static class DateUtils -{ - public static long AsUnixTimestamp(this DateTime date) - { - DateTime origin = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); - TimeSpan diff = date.ToUniversalTime() - origin; - return (long)Math.Floor(diff.TotalSeconds); - } -} \ No newline at end of file diff --git a/src/NET6WithPowerTools/GenerateLoadTestResults/Function.cs b/src/NET6WithPowerTools/GenerateLoadTestResults/Function.cs deleted file mode 100644 index 21a57a8..0000000 --- a/src/NET6WithPowerTools/GenerateLoadTestResults/Function.cs +++ /dev/null @@ -1,146 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Text.Json; -using System.Threading.Tasks; -using Amazon.CloudWatchLogs; -using Amazon.CloudWatchLogs.Model; -using Amazon.Lambda.APIGatewayEvents; -using Amazon.Lambda.Core; - -[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] - -namespace GenerateLoadTestResults -{ - public class Function - { - private AmazonCloudWatchLogsClient _cloudWatchLogsClient; - - public Function() - { - this._cloudWatchLogsClient = new AmazonCloudWatchLogsClient(); - } - - public async Task FunctionHandler( - APIGatewayHttpApiV2ProxyRequest apigProxyEvent, - ILambdaContext context) - { - if (!apigProxyEvent.RequestContext.Http.Method.Equals(HttpMethod.Get.Method)) - { - return new APIGatewayHttpApiV2ProxyResponse - { - Body = "Only GET allowed", - StatusCode = (int) HttpStatusCode.MethodNotAllowed, - }; - } - - try - { - var resultRows = 0; - var queryCount = 0; - - List> finalResults = new List>(); - - while (resultRows < 2 || queryCount >= 3) - { - finalResults = await runQuery(context); - - resultRows = finalResults.Count; - queryCount++; - } - - var wrapper = new QueryResultWrapper() - { - LoadTestType = - $"{Environment.GetEnvironmentVariable("LOAD_TEST_TYPE")} ({Environment.GetEnvironmentVariable("LAMBDA_ARCHITECTURE")})", - WarmStart = new QueryResult() - { - Count = finalResults[0][1].Value, - P50 = finalResults[0][2].Value, - P90 = finalResults[0][3].Value, - P99 = finalResults[0][4].Value, - Max = finalResults[0][5].Value, - }, - ColdStart = new QueryResult() - { - Count = finalResults[1][1].Value, - P50 = finalResults[1][2].Value, - P90 = finalResults[1][3].Value, - P99 = finalResults[1][4].Value, - Max = finalResults[1][5].Value, - } - }; - - return new APIGatewayHttpApiV2ProxyResponse - { - StatusCode = (int) HttpStatusCode.OK, - Body = wrapper.AsMarkdownTableRow(), - Headers = new Dictionary {{"Content-Type", "text/html"}} - }; - } - catch (Exception e) - { - context.Logger.LogLine($"Error retrieving results {e.Message} {e.StackTrace}"); - - return new APIGatewayHttpApiV2ProxyResponse - { - Body = "Not Found", - StatusCode = (int) HttpStatusCode.InternalServerError, - }; - } - } - - private async Task>> runQuery(ILambdaContext context) - { - var logGroupNamePrefix = - $"{Environment.GetEnvironmentVariable("LOG_GROUP_PREFIX")}{Environment.GetEnvironmentVariable("LAMBDA_ARCHITECTURE")}" - .Replace("_", "-"); - - context.Logger.LogLine($"Retrieving log groups with prefix {logGroupNamePrefix}"); - - var logGroupList = await _cloudWatchLogsClient.DescribeLogGroupsAsync(new DescribeLogGroupsRequest() - { - LogGroupNamePrefix = logGroupNamePrefix, - }); - - context.Logger.LogLine($"Found {logGroupList.LogGroups.Count} log group(s)"); - - var queryRes = await _cloudWatchLogsClient.StartQueryAsync(new StartQueryRequest() - { - LogGroupNames = logGroupList.LogGroups.Select(p => p.LogGroupName).ToList(), - QueryString = - "filter @type=\"REPORT\" | fields greatest(@initDuration, 0) + @duration as duration, ispresent(@initDuration) as coldstart | stats count(*) as count, pct(duration, 50) as p50, pct(duration, 90) as p90, pct(duration, 99) as p99, max(duration) as max by coldstart", - StartTime = DateTime.Now.AddMinutes(-20).AsUnixTimestamp(), - EndTime = DateTime.Now.AsUnixTimestamp(), - }); - - context.Logger.LogLine($"Running query, query id is {queryRes.QueryId}"); - - QueryStatus currentQueryStatus = QueryStatus.Running; - List> finalResults = new List>(); - - while (currentQueryStatus == QueryStatus.Running || currentQueryStatus == QueryStatus.Scheduled) - { - context.Logger.LogLine("Retrieving query results"); - - var queryResults = await _cloudWatchLogsClient.GetQueryResultsAsync(new GetQueryResultsRequest() - { - QueryId = queryRes.QueryId - }); - - context.Logger.LogLine($"Query result status is {queryResults.Status}"); - - currentQueryStatus = queryResults.Status; - finalResults = queryResults.Results; - - await Task.Delay(TimeSpan.FromSeconds(5)); - } - - context.Logger.LogLine($"Final results: {finalResults.Count} row(s)"); - - return finalResults; - } - } -} \ No newline at end of file diff --git a/src/NET6WithPowerTools/GenerateLoadTestResults/GenerateLoadTestResults.csproj b/src/NET6WithPowerTools/GenerateLoadTestResults/GenerateLoadTestResults.csproj deleted file mode 100644 index 4a8d708..0000000 --- a/src/NET6WithPowerTools/GenerateLoadTestResults/GenerateLoadTestResults.csproj +++ /dev/null @@ -1,17 +0,0 @@ - - - - net6.0 - true - true - - - - - - - - - - - diff --git a/src/NET6WithPowerTools/GenerateLoadTestResults/QueryResult.cs b/src/NET6WithPowerTools/GenerateLoadTestResults/QueryResult.cs deleted file mode 100644 index 69b5a31..0000000 --- a/src/NET6WithPowerTools/GenerateLoadTestResults/QueryResult.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace GenerateLoadTestResults; - -public record QueryResultWrapper -{ - public string LoadTestType { get; set; } - - public QueryResult ColdStart { get; set; } - - public QueryResult WarmStart { get; set; } - - public string AsMarkdownTableRow() => $"
Cold Start (ms)Warm Start (ms)
p50p90p99maxp50p90p99max
{LoadTestType}{ColdStart.P50}{ColdStart.P90}{ColdStart.P99}{ColdStart.Max}{WarmStart.P50}{WarmStart.P90}{WarmStart.P99}{WarmStart.Max}
"; -} - -public record QueryResult -{ - public string Count { get; set; } - - public string P50 { get; set; } - - public string P90 { get; set; } - - public string P99 { get; set; } - - public string Max { get; set; } -} \ No newline at end of file diff --git a/src/NET6WithPowerTools/GetProduct/GetProduct.csproj b/src/NET6WithPowerTools/GetProduct/GetProduct.csproj index 9e91e7c..d81c654 100755 --- a/src/NET6WithPowerTools/GetProduct/GetProduct.csproj +++ b/src/NET6WithPowerTools/GetProduct/GetProduct.csproj @@ -7,11 +7,11 @@ - - - - - + + + + + diff --git a/src/NET6WithPowerTools/GetProducts/GetProducts.csproj b/src/NET6WithPowerTools/GetProducts/GetProducts.csproj index 9e91e7c..d81c654 100755 --- a/src/NET6WithPowerTools/GetProducts/GetProducts.csproj +++ b/src/NET6WithPowerTools/GetProducts/GetProducts.csproj @@ -7,11 +7,11 @@ - - - - - + + + + + diff --git a/src/NET6WithPowerTools/PutProduct/PutProduct.csproj b/src/NET6WithPowerTools/PutProduct/PutProduct.csproj index 9e91e7c..d81c654 100755 --- a/src/NET6WithPowerTools/PutProduct/PutProduct.csproj +++ b/src/NET6WithPowerTools/PutProduct/PutProduct.csproj @@ -7,11 +7,11 @@ - - - - - + + + + + diff --git a/src/NET6WithPowerTools/Shared/Shared.csproj b/src/NET6WithPowerTools/Shared/Shared.csproj index e1fbf59..e3a7858 100755 --- a/src/NET6WithPowerTools/Shared/Shared.csproj +++ b/src/NET6WithPowerTools/Shared/Shared.csproj @@ -4,8 +4,8 @@ netstandard2.1 - - - + + +
diff --git a/src/NET6WithPowerTools/deploy.sh b/src/NET6WithPowerTools/deploy.sh new file mode 100755 index 0000000..c0d14b6 --- /dev/null +++ b/src/NET6WithPowerTools/deploy.sh @@ -0,0 +1,43 @@ +#Arguments: +#$1 - delete stack formation to ensure that all test have same conditon and favour similar ammount of cold start events + +STACK_NAME=dotnet6-powertools +DELETE_STACK=yes + +COLOR='\033[0;33m' +NO_COLOR='\033[0m' # No Color + +if [ "x$1" != x ]; +then + DELETE_STACK=$1 +fi + +echo "${COLOR}" +echo -------------------------------------------- +echo DELETE_STACK: $DELETE_STACK +echo -------------------------------------------- +echo "${NO_COLOR}" + +if [ $DELETE_STACK == "yes" ]; +then + echo "${COLOR}" + echo -------------------------------------------- + echo DELETING STACK $STACK_NAME + echo -------------------------------------------- + echo "${NO_COLOR}" + aws cloudformation delete-stack --stack-name $STACK_NAME + echo "${COLOR}" + echo --------------------------------------------- + echo Waiting stack to be deleted + echo -------------------------------------------- + echo "${NO_COLOR}" + aws cloudformation wait stack-delete-complete --stack-name $STACK_NAME + echo "${COLOR}" + echo --------------------------------------------- + echo Stack deleted + echo -------------------------------------------- + echo "${NO_COLOR}" +fi + +sam build +sam deploy --stack-name $STACK_NAME --resolve-s3 --s3-prefix $STACK_NAME --no-confirm-changeset --no-fail-on-empty-changeset --capabilities CAPABILITY_IAM \ No newline at end of file diff --git a/src/NET6WithPowerTools/run-loadtest.sh b/src/NET6WithPowerTools/run-loadtest.sh new file mode 100755 index 0000000..5474c33 --- /dev/null +++ b/src/NET6WithPowerTools/run-loadtest.sh @@ -0,0 +1,153 @@ +#Arguments: +#$1 - load test duration in seconds +#$2 - log interval to be used in the cloudwatch query in minutes +#$3 - when equal to 1 cloudwatch log group will be deleted to ensure that only logs of the load test will be evaluated for stat + +STACK_NAME=dotnet6-powertools +TEST_DURATIOMN_SEC=60 +LOG_INTERVAL_MIN=20 +LOG_DELETE=yes + +COLOR='\033[0;33m' +NO_COLOR='\033[0m' # No Color + +if [ "x$1" != x ]; +then + TEST_DURATIOMN_SEC=$1 +fi + +if [ "x$2" != x ]; +then + LOG_INTERVAL_MIN=$2 +fi + +if [ "x$3" != x ]; +then + LOG_DELETE=$3 +fi + +echo "${COLOR}" +echo -------------------------------------------- +echo DURATION:$TEST_DURATIOMN_SEC +echo LOG INTERVAL:$LOG_INTERVAL_MIN +echo LOG_DELETE: $LOG_DELETE +echo -------------------------------------------- +echo "${NO_COLOR}" + +mkdir -p Report + +function RunLoadTest() +{ + #Params: + #$1 - Architecture (x86 or arm64).Used for logging and naming report file + #$2 - Stack output name to get API Url + #$3 - Stack output name to get lambda name GetProducts + #$4 - Stack output name to get lambda name GetProduct + #$5 - Stack output name to get lambda name DeleteProduct + #$6 - Stack output name to get lambda name PutProduct + + #get test params from cloud formation output + echo "${COLOR}" + export API_URL=$(aws cloudformation describe-stacks --stack-name $STACK_NAME \ + --query "Stacks[0].Outputs[?OutputKey=='$2'].OutputValue" \ + --output text) + echo API URL: $API_URL + + LAMBDA_GETPRODUCTS=$(aws cloudformation describe-stacks --stack-name $STACK_NAME \ + --query "Stacks[0].Outputs[?OutputKey=='$3'].OutputValue" \ + --output text) + echo LAMBDA: $LAMBDA_GETPRODUCTS + + LAMBDA_GETPRODUCT=$(aws cloudformation describe-stacks --stack-name $STACK_NAME \ + --query "Stacks[0].Outputs[?OutputKey=='$4'].OutputValue" \ + --output text) + echo LAMBDA: $LAMBDA_GETPRODUCT + + LAMBDA_DELETEPRODUCT=$(aws cloudformation describe-stacks --stack-name $STACK_NAME \ + --query "Stacks[0].Outputs[?OutputKey=='$5'].OutputValue" \ + --output text) + echo LAMBDA: $LAMBDA_DELETEPRODUCT + + LAMBDA_PUTPRODUCT=$(aws cloudformation describe-stacks --stack-name $STACK_NAME \ + --query "Stacks[0].Outputs[?OutputKey=='$6'].OutputValue" \ + --output text) + echo LAMBDA: $LAMBDA_PUTPRODUCT + + if [ $LOG_DELETE == "yes" ]; + then + echo -------------------------------------------- + echo DELETING CLOUDWATCH LOG GROUP /aws/lambda/$LAMBDA_GETPRODUCTS + echo -------------------------------------------- + aws logs delete-log-group --log-group-name /aws/lambda/$LAMBDA_GETPRODUCTS + echo -------------------------------------------- + echo DELETING CLOUDWATCH LOG GROUP /aws/lambda/$LAMBDA_GETPRODUCT + echo -------------------------------------------- + aws logs delete-log-group --log-group-name /aws/lambda/$LAMBDA_GETPRODUCT + echo -------------------------------------------- + echo DELETING CLOUDWATCH LOG GROUP /aws/lambda/$LAMBDA_DELETEPRODUCT + echo -------------------------------------------- + aws logs delete-log-group --log-group-name /aws/lambda/$LAMBDA_DELETEPRODUCT + echo -------------------------------------------- + echo DELETING CLOUDWATCH LOG GROUP /aws/lambda/$LAMBDA_PUTPRODUCT + echo -------------------------------------------- + aws logs delete-log-group --log-group-name /aws/lambda/$LAMBDA_PUTPRODUCT + echo --------------------------------------------- + echo Waiting 10 sec. for deletion to complete + echo -------------------------------------------- + sleep 10 + fi + + #run load test with artillery + echo -------------------------------------------- + echo $1 RUNNING LOAD TEST $TEST_DURATIOMN_SEC sec $API_URL + echo -------------------------------------------- + echo "${NO_COLOR}" + artillery run \ + --overrides '{"config": { "phases": [{ "duration": '$TEST_DURATIOMN_SEC', "arrivalRate": 100 }] } }' \ + --quiet \ + ../../loadtest/codebuild/load-test.yml + + echo "${COLOR}" + echo -------------------------------------------- + echo Waiting 10 sec. for logs to consolidate + echo -------------------------------------------- + sleep 10 + + #get stats from cloudwatch + enddate=$(date "+%s") + startdate=$(($enddate-($LOG_INTERVAL_MIN*60))) + echo -------------------------------------------- + echo Log start:$startdate end:$enddate + echo -------------------------------------------- + + QUERY_ID=$(aws logs start-query \ + --log-group-names "/aws/lambda/$LAMBDA_GETPRODUCTS" "/aws/lambda/$LAMBDA_GETPRODUCT" "/aws/lambda/$LAMBDA_DELETEPRODUCT" "/aws/lambda/$LAMBDA_PUTPRODUCT" \ + --start-time $startdate \ + --end-time $enddate \ + --query-string 'filter @type="REPORT" | fields greatest(@initDuration, 0) + @duration as duration, ispresent(@initDuration) as coldstart | stats count(*) as count, pct(duration, 50) as p50, pct(duration, 90) as p90, pct(duration, 99) as p99, max(duration) as max by coldstart' \ + | jq -r '.queryId') + + echo -------------------------------------------- + echo Query started, id: $QUERY_ID + echo -------------------------------------------- + + echo --------------------------------------------- + echo Waiting 10 sec. for cloudwatch query to complete + echo -------------------------------------------- + sleep 10 + + echo -------------------------------------------- + echo RESULTS $1 + echo -------------------------------------------- + echo "${NO_COLOR}" + date > ./Report/load-test-report-$1.txt + echo $1 RESULTS >> ./Report/load-test-report-$1.txt + echo Test duration sec: $TEST_DURATIOMN_SEC >> ./Report/load-test-report-$1.txt + echo Log interval min: $LOG_INTERVAL_MIN >> ./Report/load-test-report-$1.txt + aws logs get-query-results --query-id $QUERY_ID --output text >> ./Report/load-test-report-$1.txt + cat ./Report/load-test-report-$1.txt + aws logs get-query-results --query-id $QUERY_ID --output json >> ./Report/load-test-report-$1.json +} + +RunLoadTest x86 ApiUrlX86 LambdaX86NameGetProducts LambdaX86NameGetProduct LambdaX86NameDeleteProduct LambdaX86NamePutProduct +RunLoadTest arm64 ApiUrlArm64 LambdaArm64NameGetProducts LambdaArm64NameGetProduct LambdaArm64NameDeleteProduct LambdaArm64NamePutProduct \ No newline at end of file diff --git a/src/NET6WithPowerTools/template.yaml b/src/NET6WithPowerTools/template.yaml index 0b46b51..00902b6 100755 --- a/src/NET6WithPowerTools/template.yaml +++ b/src/NET6WithPowerTools/template.yaml @@ -1,10 +1,16 @@ AWSTemplateFormatVersion: "2010-09-09" Transform: AWS::Serverless-2016-10-31 +Parameters: + x86FunctionNamePrefix: + Type: String + Default: Net6-X86-powertools + arm64FunctionNamePrefix: + Type: String + Default: Net6-Arm64-powertools Globals: Function: MemorySize: 1024 - Architectures: [!Ref LambdaArchitecture] Runtime: dotnet6 Timeout: 30 Tracing: Active @@ -17,43 +23,38 @@ Globals: POWERTOOLS_LOGGER_LOG_EVENT: true POWERTOOLS_LOGGER_CASE: SnakeCase -Parameters: - LambdaArchitecture: - Type: String - AllowedValues: - - arm64 - - x86_64 - Description: Enter arm64 or x86_64 - Resources: - GetProductsFunction: + #X86 + GetProductsFunctionX86: Type: AWS::Serverless::Function Properties: + FunctionName: !Join [ -,[!Ref x86FunctionNamePrefix,GetProducts]] + Architectures: [x86_64] CodeUri: ./GetProducts/ Handler: GetProducts::GetProducts.Function::FunctionHandler Events: Api: Type: HttpApi Properties: - Path: / + Path: /x86 Method: GET Policies: - - Version: "2012-10-17" - Statement: - - Effect: Allow - Action: dynamodb:Scan - Resource: !GetAtt Table.Arn + - DynamoDBReadPolicy: + TableName: + !Ref Table - GetProductFunction: + GetProductFunctionX86: Type: AWS::Serverless::Function Properties: + FunctionName: !Join [ -,[!Ref x86FunctionNamePrefix,GetProduct]] + Architectures: [x86_64] CodeUri: ./GetProduct/ Handler: GetProduct::GetProduct.Function::FunctionHandler Events: Api: Type: HttpApi Properties: - Path: /{id} + Path: /x86/{id} Method: GET Policies: - Version: "2012-10-17" @@ -62,16 +63,18 @@ Resources: Action: dynamodb:GetItem Resource: !GetAtt Table.Arn - DeleteProductFunction: + DeleteProductFunctionX86: Type: AWS::Serverless::Function Properties: + FunctionName: !Join [ -,[!Ref x86FunctionNamePrefix,DeleteProduct]] + Architectures: [x86_64] CodeUri: ./DeleteProduct/ Handler: DeleteProduct::DeleteProduct.Function::FunctionHandler Events: Api: Type: HttpApi Properties: - Path: /{id} + Path: /x86/{id} Method: DELETE Policies: - Version: "2012-10-17" @@ -82,16 +85,18 @@ Resources: - dynamodb:GetItem Resource: !GetAtt Table.Arn - PutProductFunction: + PutProductFunctionX86: Type: AWS::Serverless::Function Properties: + FunctionName: !Join [ -,[!Ref x86FunctionNamePrefix,PutProduct]] + Architectures: [x86_64] CodeUri: ./PutProduct/ Handler: PutProduct::PutProduct.Function::FunctionHandler Events: Api: Type: HttpApi Properties: - Path: /{id} + Path: /x86/{id} Method: PUT Policies: - Version: "2012-10-17" @@ -99,36 +104,86 @@ Resources: - Effect: Allow Action: dynamodb:PutItem Resource: !GetAtt Table.Arn +#ARM64 + GetProductsFunctionArm64: + Type: AWS::Serverless::Function + Properties: + FunctionName: !Join [ -,[!Ref arm64FunctionNamePrefix,GetProducts]] + Architectures: [arm64] + CodeUri: ./GetProducts/ + Handler: GetProducts::GetProducts.Function::FunctionHandler + Events: + Api: + Type: HttpApi + Properties: + Path: /arm64 + Method: GET + Policies: + - DynamoDBReadPolicy: + TableName: + !Ref Table - GenerateLoadTestResults: + GetProductFunctionArm64: Type: AWS::Serverless::Function Properties: - CodeUri: ./GenerateLoadTestResults/ - Handler: GenerateLoadTestResults::GenerateLoadTestResults.Function::FunctionHandler + FunctionName: !Join [ -,[!Ref arm64FunctionNamePrefix,GetProduct]] + Architectures: [arm64] + CodeUri: ./GetProduct/ + Handler: GetProduct::GetProduct.Function::FunctionHandler Events: Api: Type: HttpApi Properties: - Path: /test-results + Path: /arm64/{id} Method: GET - Environment: - Variables: - LOG_GROUP_PREFIX: !Sub "/aws/lambda/net-6-power-tools-" - LOAD_TEST_TYPE: "NET 6 w/Power Tools" - LAMBDA_ARCHITECTURE: !Ref LambdaArchitecture Policies: - Version: "2012-10-17" Statement: - - Sid: AllowStartQueries - Effect: Allow + - Effect: Allow + Action: dynamodb:GetItem + Resource: !GetAtt Table.Arn + + DeleteProductFunctionArm64: + Type: AWS::Serverless::Function + Properties: + FunctionName: !Join [ -,[!Ref arm64FunctionNamePrefix,DeleteProduct]] + Architectures: [arm64] + CodeUri: ./DeleteProduct/ + Handler: DeleteProduct::DeleteProduct.Function::FunctionHandler + Events: + Api: + Type: HttpApi + Properties: + Path: /arm64/{id} + Method: DELETE + Policies: + - Version: "2012-10-17" + Statement: + - Effect: Allow Action: - - logs:DescribeLogGroups - - logs:StartQuery - Resource: "*" - - Sid: AllowGetQueryResults - Effect: Allow - Action: logs:GetQueryResults - Resource: "*" + - dynamodb:DeleteItem + - dynamodb:GetItem + Resource: !GetAtt Table.Arn + + PutProductFunctionArm64: + Type: AWS::Serverless::Function + Properties: + FunctionName: !Join [ -,[!Ref arm64FunctionNamePrefix,PutProduct]] + Architectures: [arm64] + CodeUri: ./PutProduct/ + Handler: PutProduct::PutProduct.Function::FunctionHandler + Events: + Api: + Type: HttpApi + Properties: + Path: /arm64/{id} + Method: PUT + Policies: + - Version: "2012-10-17" + Statement: + - Effect: Allow + Action: dynamodb:PutItem + Resource: !GetAtt Table.Arn Table: Type: AWS::DynamoDB::Table @@ -140,10 +195,42 @@ Resources: KeySchema: - AttributeName: id KeyType: HASH - StreamSpecification: - StreamViewType: NEW_AND_OLD_IMAGES Outputs: ApiUrl: Description: "API Gateway endpoint URL" - Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/" \ No newline at end of file + Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com" + ApiUrlX86: + Description: "X86 API endpoint URL" + Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/x86" + ApiUrlArm64: + Description: "Arm64 GateAPI endpoint URL" + Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/arm64" + + #x86 + LambdaX86NameGetProducts: + Description: "Lambda X86 GetProducts" + Value: !Join [ -,[!Ref x86FunctionNamePrefix,GetProducts]] + LambdaX86NameGetProduct: + Description: "Lambda X86 GetProduct" + Value: !Join [ -,[!Ref x86FunctionNamePrefix,GetProduct]] + LambdaX86NameDeleteProduct: + Description: "Lambda X86 DeleteProduct" + Value: !Join [ -,[!Ref x86FunctionNamePrefix,DeleteProduct]] + LambdaX86NamePutProduct: + Description: "Lambda X86 PutProduct" + Value: !Join [ -,[!Ref x86FunctionNamePrefix,PutProduct]] + + #arm64 + LambdaArm64NameGetProducts: + Description: "Lambda X86 GetProducts" + Value: !Join [ -,[!Ref arm64FunctionNamePrefix,GetProducts]] + LambdaArm64NameGetProduct: + Description: "Lambda X86 GetProduct" + Value: !Join [ -,[!Ref arm64FunctionNamePrefix,GetProduct]] + LambdaArm64NameDeleteProduct: + Description: "Lambda X86 DeleteProduct" + Value: !Join [ -,[!Ref arm64FunctionNamePrefix,DeleteProduct]] + LambdaArm64NamePutProduct: + Description: "Lambda X86 PutProduct" + Value: !Join [ -,[!Ref arm64FunctionNamePrefix,PutProduct]] \ No newline at end of file From 893391b7f67d2f220cbdc53cb15d202dd2560708 Mon Sep 17 00:00:00 2001 From: Riccardo Venturi Date: Mon, 13 Nov 2023 08:09:03 +0100 Subject: [PATCH 24/32] . --- src/NET8/deploy.sh | 2 +- src/NET8MinimalAPI/deploy.sh | 2 +- src/NET8Native/deploy.sh | 2 +- src/NET8NativeMinimalAPI/deploy.sh | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/NET8/deploy.sh b/src/NET8/deploy.sh index 9aae7f5..814ecb5 100755 --- a/src/NET8/deploy.sh +++ b/src/NET8/deploy.sh @@ -7,7 +7,7 @@ DELETE_STACK=yes COLOR='\033[0;33m' NO_COLOR='\033[0m' # No Color -if [ "x$4" != x ]; +if [ "x$1" != x ]; then DELETE_STACK=$1 fi diff --git a/src/NET8MinimalAPI/deploy.sh b/src/NET8MinimalAPI/deploy.sh index 80e0ccd..1caf08f 100755 --- a/src/NET8MinimalAPI/deploy.sh +++ b/src/NET8MinimalAPI/deploy.sh @@ -7,7 +7,7 @@ DELETE_STACK=yes COLOR='\033[0;33m' NO_COLOR='\033[0m' # No Color -if [ "x$4" != x ]; +if [ "x$1" != x ]; then DELETE_STACK=$1 fi diff --git a/src/NET8Native/deploy.sh b/src/NET8Native/deploy.sh index dda9b4d..a6d0c85 100755 --- a/src/NET8Native/deploy.sh +++ b/src/NET8Native/deploy.sh @@ -7,7 +7,7 @@ DELETE_STACK=yes COLOR='\033[0;33m' NO_COLOR='\033[0m' # No Color -if [ "x$4" != x ]; +if [ "x$1" != x ]; then DELETE_STACK=$1 fi diff --git a/src/NET8NativeMinimalAPI/deploy.sh b/src/NET8NativeMinimalAPI/deploy.sh index 71a251b..d6da858 100755 --- a/src/NET8NativeMinimalAPI/deploy.sh +++ b/src/NET8NativeMinimalAPI/deploy.sh @@ -7,7 +7,7 @@ DELETE_STACK=yes COLOR='\033[0;33m' NO_COLOR='\033[0m' # No Color -if [ "x$4" != x ]; +if [ "x$1" != x ]; then DELETE_STACK=$1 fi From 7b7e56430e94072c2cfc42501aabfe752afc4c84 Mon Sep 17 00:00:00 2001 From: Riccardo Venturi Date: Mon, 13 Nov 2023 08:35:47 +0100 Subject: [PATCH 25/32] . --- src/NET6Containers/deploy.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NET6Containers/deploy.sh b/src/NET6Containers/deploy.sh index 614f82b..7c260a3 100755 --- a/src/NET6Containers/deploy.sh +++ b/src/NET6Containers/deploy.sh @@ -10,7 +10,7 @@ COLOR='\033[0;33m' NO_COLOR='\033[0m' # No Color if [ "x$1" != x ]; -else +then ECR_URI=$1 fi From 174bc3d3b39143198a6d61a79688bfbd190a7eb3 Mon Sep 17 00:00:00 2001 From: Riccardo Venturi Date: Mon, 13 Nov 2023 08:41:30 +0100 Subject: [PATCH 26/32] . --- loadtest/codebuild/run-all-load-tests.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/loadtest/codebuild/run-all-load-tests.sh b/loadtest/codebuild/run-all-load-tests.sh index 9dd3d9a..2e38fa7 100755 --- a/loadtest/codebuild/run-all-load-tests.sh +++ b/loadtest/codebuild/run-all-load-tests.sh @@ -24,9 +24,9 @@ then DELETE_STACK=$LT_DELETE_STACK fi -if [ "x${LT_ECRURI}" != x ]; +if [ "x${LT_ECR_URI}" != x ]; then - ECR_URI=$LT_ECRURI + ECR_URI=$LT_ECR_URI fi echo -------------------------------------------- From f07d3015b2ee2eb5f2b14f1b3321b9b686cb33a6 Mon Sep 17 00:00:00 2001 From: Riccardo Venturi Date: Mon, 13 Nov 2023 14:38:43 +0100 Subject: [PATCH 27/32] adding loadtestcli to generate global html with all results to be exported in readme.md --- loadtest/codebuild/load-test-buildspec.yml | 3 + loadtest/codebuild/run-all-load-tests.sh | 4 +- loadtestcli/LoadtestCli.csproj | 10 + loadtestcli/Program.cs | 278 ++++++++++++++++++ loadtestcli/Samples/EMPTY/Report/tmp.txt | 1 + .../FAKE/Report/load-test-report-Empty.json | 2 + .../FAKE/Report/load-test-report-No-Cold.json | 36 +++ .../FAKE/Report/load-test-report-No-Warm.json | 36 +++ .../NET8/Report/load-test-report-arm64.json | 62 ++++ .../NET8/Report/load-test-report-arm64.txt | 18 ++ .../NET8/Report/load-test-report-x86.json | 62 ++++ .../NET8/Report/load-test-report-x86.txt | 18 ++ .../Report/load-test-report-arm64.json | 62 ++++ .../Report/load-test-report-arm64.txt | 18 ++ .../Report/load-test-report-x86.json | 62 ++++ .../Report/load-test-report-x86.txt | 18 ++ .../Report/load-test-report-x86.json | 62 ++++ .../Report/load-test-report-x86.txt | 18 ++ .../Report/load-test-report-x86.json | 62 ++++ .../Report/load-test-report-x86.txt | 18 ++ 20 files changed, 848 insertions(+), 2 deletions(-) create mode 100644 loadtestcli/LoadtestCli.csproj create mode 100644 loadtestcli/Program.cs create mode 100644 loadtestcli/Samples/EMPTY/Report/tmp.txt create mode 100644 loadtestcli/Samples/FAKE/Report/load-test-report-Empty.json create mode 100644 loadtestcli/Samples/FAKE/Report/load-test-report-No-Cold.json create mode 100644 loadtestcli/Samples/FAKE/Report/load-test-report-No-Warm.json create mode 100644 loadtestcli/Samples/NET8/Report/load-test-report-arm64.json create mode 100644 loadtestcli/Samples/NET8/Report/load-test-report-arm64.txt create mode 100644 loadtestcli/Samples/NET8/Report/load-test-report-x86.json create mode 100644 loadtestcli/Samples/NET8/Report/load-test-report-x86.txt create mode 100644 loadtestcli/Samples/NET8MinimalAPI/Report/load-test-report-arm64.json create mode 100644 loadtestcli/Samples/NET8MinimalAPI/Report/load-test-report-arm64.txt create mode 100644 loadtestcli/Samples/NET8MinimalAPI/Report/load-test-report-x86.json create mode 100644 loadtestcli/Samples/NET8MinimalAPI/Report/load-test-report-x86.txt create mode 100644 loadtestcli/Samples/NET8Native/Report/load-test-report-x86.json create mode 100644 loadtestcli/Samples/NET8Native/Report/load-test-report-x86.txt create mode 100644 loadtestcli/Samples/NET8NativeMinimalAPI/Report/load-test-report-x86.json create mode 100644 loadtestcli/Samples/NET8NativeMinimalAPI/Report/load-test-report-x86.txt diff --git a/loadtest/codebuild/load-test-buildspec.yml b/loadtest/codebuild/load-test-buildspec.yml index 84bccfb..c3ecc19 100644 --- a/loadtest/codebuild/load-test-buildspec.yml +++ b/loadtest/codebuild/load-test-buildspec.yml @@ -8,6 +8,8 @@ phases: build: commands: - ./run-all-load-tests.sh + - cd ../loadtestcli + - dotnet run ../src artifacts: files: - src/NET6/Report/* @@ -22,4 +24,5 @@ artifacts: - src/NET8MinimalAPI/Report/* - src/NET8Native/Report/* - src/NET8NativeMinimalAPI/Report/* + - loadtestcli/Report/* name: loadtest-$(date +%Y-%m-%H-%M) \ No newline at end of file diff --git a/loadtest/codebuild/run-all-load-tests.sh b/loadtest/codebuild/run-all-load-tests.sh index 2e38fa7..9dd3d9a 100755 --- a/loadtest/codebuild/run-all-load-tests.sh +++ b/loadtest/codebuild/run-all-load-tests.sh @@ -24,9 +24,9 @@ then DELETE_STACK=$LT_DELETE_STACK fi -if [ "x${LT_ECR_URI}" != x ]; +if [ "x${LT_ECRURI}" != x ]; then - ECR_URI=$LT_ECR_URI + ECR_URI=$LT_ECRURI fi echo -------------------------------------------- diff --git a/loadtestcli/LoadtestCli.csproj b/loadtestcli/LoadtestCli.csproj new file mode 100644 index 0000000..d439800 --- /dev/null +++ b/loadtestcli/LoadtestCli.csproj @@ -0,0 +1,10 @@ + + + + Exe + net7.0 + enable + enable + + + diff --git a/loadtestcli/Program.cs b/loadtestcli/Program.cs new file mode 100644 index 0000000..f27e44c --- /dev/null +++ b/loadtestcli/Program.cs @@ -0,0 +1,278 @@ +using System.Text; +using System.Text.Json; + +public record QueryResultWrapper +{ + public string? LoadTestType { get; set; } + + public QueryResult? ColdStart { get; set; } + + public QueryResult? WarmStart { get; set; } +} + +public record QueryResult +{ + public string? Count { get; set; } + + public string? P50 { get; set; } + + public string? P90 { get; set; } + + public string? P99 { get; set; } + + public string? Max { get; set; } +} + +public class LoadTestResults +{ + public List>? results { get; set; } +} + +public class LoadTestSubResult +{ + public string? field { get; set; } + public string? value { get; set; } +} + +internal class Program +{ + private static async Task Main(string[] args) + { + var folderToAnalize = ""; + var reportFileOutputPath = ""; + var reportResultList = new List(); + + DisplayStartMsg(); + + if(args.Count()<1) + { + DisplayHelp(); + return -1; + } + folderToAnalize=args[0]; + + if(args.Count()==2) + { + reportFileOutputPath=args[1]; + } + else + { + reportFileOutputPath=$"./Report/{DateTime.UtcNow:yyyy-MM-dd-hh-mm}-loadtest-report.html"; + } + + //folderToAnalize = "./Samples"; + + try + { + System.Console.WriteLine($"STARTING using folder: {folderToAnalize}"); + System.Console.WriteLine($" - using folder: {folderToAnalize}"); + System.Console.WriteLine($" - destination path: {reportFileOutputPath}"); + + if(!Directory.Exists(folderToAnalize)) + { + System.Console.WriteLine($"ERROR: folder {folderToAnalize} does not exist"); + return -2; + } + foreach (var folderPath in Directory.EnumerateDirectories(folderToAnalize)) + { + System.Console.WriteLine($"Processing folder: {folderPath}"); + var reportFolderPath = Path.Combine(folderPath, "Report"); + System.Console.WriteLine($" - Searching sub folder {reportFolderPath}"); + if (Directory.Exists(reportFolderPath)) + { + System.Console.WriteLine($" - Processing sub folder: {reportFolderPath}"); + var reportFolderInfo = new DirectoryInfo(reportFolderPath); + foreach (var reportFileInfo in reportFolderInfo.GetFiles("load-test-report-*.json")) + { + System.Console.WriteLine($" - Processing sub folder file: {reportFileInfo.Name}"); + var ltName = reportFileInfo.Name.Replace("load-test-report-", "").Replace(reportFileInfo.Extension,""); + var title = $"{reportFolderInfo.Parent?.Name} - {ltName}"; + System.Console.WriteLine($" - Report: {title}"); + var ltResult = await DeserializeLoadTestResult(reportFileInfo.FullName, title); + reportResultList.Add(ltResult); + } + } + } + System.Console.WriteLine("Generating HTML Report!"); + GenerateHtmlGlobalReport(reportResultList,reportFileOutputPath); + System.Console.WriteLine("FINISHED!"); + } + catch(Exception ex) + { + System.Console.WriteLine($"ERROR!:{ex.Message}"); + System.Console.WriteLine($"ERROR!:{ex.ToString()}"); + } + return 0; + } + + private static void DisplayStartMsg() + { + System.Console.WriteLine("LOAD-TEST CLI"); + System.Console.WriteLine("--------------------------------------"); + } + + private static void DisplayHelp() + { + System.Console.WriteLine("ERROR - Missing required parameters:"); + System.Console.WriteLine("- param1: folder to process"); + System.Console.WriteLine("- param2: output file path"); + } + private static void GenerateHtmlGlobalReport(List reportResultList,string reportFileOutputPath) + { + /* + we are producing an html similar to this: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Cold Start (ms)Warm Start (ms)
Invoke Countp50p90p99maxInvoke Countp50p90p99max
Minimal API on ARM642421972.792049.162107.322124.55136,8166.019.3724.69331.6
+ */ + var sb = new StringBuilder(); + + sb.Append(@" + + + + + + + + + + + + + + + + + + + + "); + foreach (var ltResult in reportResultList) + { + sb.Append($@" + + + + + + + + + + + + + + "); + } + sb.Append($@" +
Cold Start (ms)Warm Start (ms)
Invoke Countp50p90p99maxInvoke Countp50p90p99max
{ltResult.LoadTestType}{FormatCount(ltResult.ColdStart?.Count??"-")}{FormatMilli(ltResult.ColdStart?.P50??"-")}{FormatMilli(ltResult.ColdStart?.P90??"-")}{FormatMilli(ltResult.ColdStart?.P99??"-")}{FormatMilli(ltResult.ColdStart?.Max??"-")}{FormatCount(ltResult.WarmStart?.Count??"-")}{FormatMilli(ltResult.WarmStart?.P50??"-")}{FormatMilli(ltResult.WarmStart?.P90??"-")}{FormatMilli(ltResult.WarmStart?.P99??"-")}{FormatMilli(ltResult.WarmStart?.Max??"-")}
+ "); + + var outputDir=Path.GetDirectoryName(reportFileOutputPath); + if(!String.IsNullOrEmpty(outputDir)) + { + System.Console.WriteLine($"CREATING OUTPUT FOLDER:{outputDir}"); + Directory.CreateDirectory(outputDir); + } + File.WriteAllText(reportFileOutputPath, sb.ToString()); + System.Console.WriteLine($"OUTPUT REPORT CREATED: {reportFileOutputPath}"); + } + + private static async Task DeserializeLoadTestResult(string filePath,string title) + { + var json = await File.ReadAllTextAsync(filePath); + var lt = JsonSerializer.Deserialize(json); + + var ret = new QueryResultWrapper + { + LoadTestType = title, + WarmStart=null, + ColdStart=null + }; + + if(lt?.results==null) + return ret; + + foreach (var ltItem in lt.results) + { + var isWarm = ltItem.Where(r => r.field == "coldstart" && r.value == "0").Count() > 0; + System.Console.WriteLine($" . isWarm: {isWarm}"); + + var tmp = new QueryResult + { + Count = ltItem?.Where(r => r.field == "count").FirstOrDefault()?.value, + P50 = ltItem?.Where(r => r.field == "p50").FirstOrDefault()?.value, + P90 = ltItem?.Where(r => r.field == "p90").FirstOrDefault()?.value, + P99 = ltItem?.Where(r => r.field == "p99").FirstOrDefault()?.value, + Max = ltItem?.Where(r => r.field == "max").FirstOrDefault()?.value + }; + + if (isWarm) + ret.WarmStart = tmp; + else + ret.ColdStart = tmp; + } + + if(ret.WarmStart==null) + { + System.Console.WriteLine(" !!!! ATTENTION MISING WARM STATS !!!!"); + } + + if(ret.ColdStart==null) + { + System.Console.WriteLine(" !!!! ATTENTION MISING COLD STATS !!!!"); + } + + return ret; + } + + private static string FormatMilli(string value) + { + Double valueNr=0; + if(!Double.TryParse(value,out valueNr)) + return value; + return ($"{valueNr:#,##0.00}"); + } + + private static string FormatCount(string value) + { + Double valueNr=0; + if(!Double.TryParse(value,out valueNr)) + return value; + return ($"{valueNr:#,##0}"); + } +} \ No newline at end of file diff --git a/loadtestcli/Samples/EMPTY/Report/tmp.txt b/loadtestcli/Samples/EMPTY/Report/tmp.txt new file mode 100644 index 0000000..945c9b4 --- /dev/null +++ b/loadtestcli/Samples/EMPTY/Report/tmp.txt @@ -0,0 +1 @@ +. \ No newline at end of file diff --git a/loadtestcli/Samples/FAKE/Report/load-test-report-Empty.json b/loadtestcli/Samples/FAKE/Report/load-test-report-Empty.json new file mode 100644 index 0000000..2c63c08 --- /dev/null +++ b/loadtestcli/Samples/FAKE/Report/load-test-report-Empty.json @@ -0,0 +1,2 @@ +{ +} diff --git a/loadtestcli/Samples/FAKE/Report/load-test-report-No-Cold.json b/loadtestcli/Samples/FAKE/Report/load-test-report-No-Cold.json new file mode 100644 index 0000000..0d462b1 --- /dev/null +++ b/loadtestcli/Samples/FAKE/Report/load-test-report-No-Cold.json @@ -0,0 +1,36 @@ +{ + "results": [ + [ + { + "field": "coldstart", + "value": "0" + }, + { + "field": "count", + "value": "139116" + }, + { + "field": "p50", + "value": "6.1078" + }, + { + "field": "p90", + "value": "9.6783" + }, + { + "field": "p99", + "value": "23.9189" + }, + { + "field": "max", + "value": "135.26" + } + ] + ], + "statistics": { + "recordsMatched": 140114.0, + "recordsScanned": 529742.0, + "bytesScanned": 176419355.0 + }, + "status": "Complete" +} diff --git a/loadtestcli/Samples/FAKE/Report/load-test-report-No-Warm.json b/loadtestcli/Samples/FAKE/Report/load-test-report-No-Warm.json new file mode 100644 index 0000000..1c47e7d --- /dev/null +++ b/loadtestcli/Samples/FAKE/Report/load-test-report-No-Warm.json @@ -0,0 +1,36 @@ +{ + "results": [ + [ + { + "field": "coldstart", + "value": "1" + }, + { + "field": "count", + "value": "802" + }, + { + "field": "p50", + "value": "1477.8616" + }, + { + "field": "p90", + "value": "1533.5383" + }, + { + "field": "p99", + "value": "1666.1844" + }, + { + "field": "max", + "value": "1836.55" + } + ] + ], + "statistics": { + "recordsMatched": 137744.0, + "recordsScanned": 520790.0, + "bytesScanned": 173060538.0 + }, + "status": "Complete" +} diff --git a/loadtestcli/Samples/NET8/Report/load-test-report-arm64.json b/loadtestcli/Samples/NET8/Report/load-test-report-arm64.json new file mode 100644 index 0000000..68a0455 --- /dev/null +++ b/loadtestcli/Samples/NET8/Report/load-test-report-arm64.json @@ -0,0 +1,62 @@ +{ + "results": [ + [ + { + "field": "coldstart", + "value": "0" + }, + { + "field": "count", + "value": "139116" + }, + { + "field": "p50", + "value": "6.1078" + }, + { + "field": "p90", + "value": "9.6783" + }, + { + "field": "p99", + "value": "23.9189" + }, + { + "field": "max", + "value": "135.26" + } + ], + [ + { + "field": "coldstart", + "value": "1" + }, + { + "field": "count", + "value": "998" + }, + { + "field": "p50", + "value": "1716.9015" + }, + { + "field": "p90", + "value": "1779.8039" + }, + { + "field": "p99", + "value": "1839.487" + }, + { + "field": "max", + "value": "1856.62" + } + ] + ], + "statistics": { + "recordsMatched": 140114.0, + "recordsScanned": 529742.0, + "bytesScanned": 176419355.0 + }, + "status": "Complete" +} diff --git a/loadtestcli/Samples/NET8/Report/load-test-report-arm64.txt b/loadtestcli/Samples/NET8/Report/load-test-report-arm64.txt new file mode 100644 index 0000000..b95d153 --- /dev/null +++ b/loadtestcli/Samples/NET8/Report/load-test-report-arm64.txt @@ -0,0 +1,18 @@ +Mon Nov 13 07:09:45 UTC 2023 +arm64 RESULTS +Test duration sec: 600 +Log interval min: 20 +Complete +RESULTS coldstart 0 +RESULTS count 139116 +RESULTS p50 6.1078 +RESULTS p90 9.6783 +RESULTS p99 23.9189 +RESULTS max 135.26 +RESULTS coldstart 1 +RESULTS count 998 +RESULTS p50 1716.9015 +RESULTS p90 1779.8039 +RESULTS p99 1839.487 +RESULTS max 1856.62 +STATISTICS 176419355.0 140114.0 529742.0 diff --git a/loadtestcli/Samples/NET8/Report/load-test-report-x86.json b/loadtestcli/Samples/NET8/Report/load-test-report-x86.json new file mode 100644 index 0000000..c1e6979 --- /dev/null +++ b/loadtestcli/Samples/NET8/Report/load-test-report-x86.json @@ -0,0 +1,62 @@ +{ + "results": [ + [ + { + "field": "coldstart", + "value": "0" + }, + { + "field": "count", + "value": "136942" + }, + { + "field": "p50", + "value": "6.2055" + }, + { + "field": "p90", + "value": "10.3128" + }, + { + "field": "p99", + "value": "23.9189" + }, + { + "field": "max", + "value": "143.08" + } + ], + [ + { + "field": "coldstart", + "value": "1" + }, + { + "field": "count", + "value": "802" + }, + { + "field": "p50", + "value": "1477.8616" + }, + { + "field": "p90", + "value": "1533.5383" + }, + { + "field": "p99", + "value": "1666.1844" + }, + { + "field": "max", + "value": "1836.55" + } + ] + ], + "statistics": { + "recordsMatched": 137744.0, + "recordsScanned": 520790.0, + "bytesScanned": 173060538.0 + }, + "status": "Complete" +} diff --git a/loadtestcli/Samples/NET8/Report/load-test-report-x86.txt b/loadtestcli/Samples/NET8/Report/load-test-report-x86.txt new file mode 100644 index 0000000..b86b219 --- /dev/null +++ b/loadtestcli/Samples/NET8/Report/load-test-report-x86.txt @@ -0,0 +1,18 @@ +Mon Nov 13 06:58:59 UTC 2023 +x86 RESULTS +Test duration sec: 600 +Log interval min: 20 +Complete +RESULTS coldstart 0 +RESULTS count 136942 +RESULTS p50 6.2055 +RESULTS p90 10.3128 +RESULTS p99 23.9189 +RESULTS max 143.08 +RESULTS coldstart 1 +RESULTS count 802 +RESULTS p50 1477.8616 +RESULTS p90 1533.5383 +RESULTS p99 1666.1844 +RESULTS max 1836.55 +STATISTICS 173060538.0 137744.0 520790.0 diff --git a/loadtestcli/Samples/NET8MinimalAPI/Report/load-test-report-arm64.json b/loadtestcli/Samples/NET8MinimalAPI/Report/load-test-report-arm64.json new file mode 100644 index 0000000..a38559c --- /dev/null +++ b/loadtestcli/Samples/NET8MinimalAPI/Report/load-test-report-arm64.json @@ -0,0 +1,62 @@ +{ + "results": [ + [ + { + "field": "coldstart", + "value": "0" + }, + { + "field": "count", + "value": "136380" + }, + { + "field": "p50", + "value": "6.3048" + }, + { + "field": "p90", + "value": "10.6454" + }, + { + "field": "p99", + "value": "30.8348" + }, + { + "field": "max", + "value": "156.29" + } + ], + [ + { + "field": "coldstart", + "value": "1" + }, + { + "field": "count", + "value": "262" + }, + { + "field": "p50", + "value": "1990.6222" + }, + { + "field": "p90", + "value": "2088.4523" + }, + { + "field": "p99", + "value": "2156.3291" + }, + { + "field": "max", + "value": "2159.3" + } + ] + ], + "statistics": { + "recordsMatched": 136642.0, + "recordsScanned": 1230214.0, + "bytesScanned": 943182997.0 + }, + "status": "Complete" +} diff --git a/loadtestcli/Samples/NET8MinimalAPI/Report/load-test-report-arm64.txt b/loadtestcli/Samples/NET8MinimalAPI/Report/load-test-report-arm64.txt new file mode 100644 index 0000000..3a4b8c8 --- /dev/null +++ b/loadtestcli/Samples/NET8MinimalAPI/Report/load-test-report-arm64.txt @@ -0,0 +1,18 @@ +Mon Nov 13 07:33:34 UTC 2023 +arm64 RESULTS lambda: Net8-MinimalApi-Arm64 +Test duration sec: 600 +Log interval min: 20 +Complete +RESULTS coldstart 0 +RESULTS count 136380 +RESULTS p50 6.3048 +RESULTS p90 10.6454 +RESULTS p99 30.8348 +RESULTS max 156.29 +RESULTS coldstart 1 +RESULTS count 262 +RESULTS p50 1990.6222 +RESULTS p90 2088.4523 +RESULTS p99 2156.3291 +RESULTS max 2159.3 +STATISTICS 943182997.0 136642.0 1230214.0 diff --git a/loadtestcli/Samples/NET8MinimalAPI/Report/load-test-report-x86.json b/loadtestcli/Samples/NET8MinimalAPI/Report/load-test-report-x86.json new file mode 100644 index 0000000..b834f98 --- /dev/null +++ b/loadtestcli/Samples/NET8MinimalAPI/Report/load-test-report-x86.json @@ -0,0 +1,62 @@ +{ + "results": [ + [ + { + "field": "coldstart", + "value": "0" + }, + { + "field": "count", + "value": "141497" + }, + { + "field": "p50", + "value": "5.8237" + }, + { + "field": "p90", + "value": "9.5259" + }, + { + "field": "p99", + "value": "22.8066" + }, + { + "field": "max", + "value": "145.7" + } + ], + [ + { + "field": "coldstart", + "value": "1" + }, + { + "field": "count", + "value": "199" + }, + { + "field": "p50", + "value": "1701.5263" + }, + { + "field": "p90", + "value": "1803.0807" + }, + { + "field": "p99", + "value": "1966.8893" + }, + { + "field": "max", + "value": "2001.37" + } + ] + ], + "statistics": { + "recordsMatched": 141696.0, + "recordsScanned": 1275490.0, + "bytesScanned": 973056498.0 + }, + "status": "Complete" +} diff --git a/loadtestcli/Samples/NET8MinimalAPI/Report/load-test-report-x86.txt b/loadtestcli/Samples/NET8MinimalAPI/Report/load-test-report-x86.txt new file mode 100644 index 0000000..e09e9bc --- /dev/null +++ b/loadtestcli/Samples/NET8MinimalAPI/Report/load-test-report-x86.txt @@ -0,0 +1,18 @@ +Mon Nov 13 07:22:52 UTC 2023 +x86 RESULTS lambda: Net8-MinimalApi-X86 +Test duration sec: 600 +Log interval min: 20 +Complete +RESULTS coldstart 0 +RESULTS count 141497 +RESULTS p50 5.8237 +RESULTS p90 9.5259 +RESULTS p99 22.8066 +RESULTS max 145.7 +RESULTS coldstart 1 +RESULTS count 199 +RESULTS p50 1701.5263 +RESULTS p90 1803.0807 +RESULTS p99 1966.8893 +RESULTS max 2001.37 +STATISTICS 973056498.0 141696.0 1275490.0 diff --git a/loadtestcli/Samples/NET8Native/Report/load-test-report-x86.json b/loadtestcli/Samples/NET8Native/Report/load-test-report-x86.json new file mode 100644 index 0000000..52f202f --- /dev/null +++ b/loadtestcli/Samples/NET8Native/Report/load-test-report-x86.json @@ -0,0 +1,62 @@ +{ + "results": [ + [ + { + "field": "coldstart", + "value": "0" + }, + { + "field": "count", + "value": "136757" + }, + { + "field": "p50", + "value": "5.7719" + }, + { + "field": "p90", + "value": "8.735" + }, + { + "field": "p99", + "value": "19.5334" + }, + { + "field": "max", + "value": "130.47" + } + ], + [ + { + "field": "coldstart", + "value": "1" + }, + { + "field": "count", + "value": "256" + }, + { + "field": "p50", + "value": "365.4209" + }, + { + "field": "p90", + "value": "409.523" + }, + { + "field": "p99", + "value": "463.0947" + }, + { + "field": "max", + "value": "515.67" + } + ] + ], + "statistics": { + "recordsMatched": 137013.0, + "recordsScanned": 516656.0, + "bytesScanned": 171255480.0 + }, + "status": "Complete" +} diff --git a/loadtestcli/Samples/NET8Native/Report/load-test-report-x86.txt b/loadtestcli/Samples/NET8Native/Report/load-test-report-x86.txt new file mode 100644 index 0000000..c08cce0 --- /dev/null +++ b/loadtestcli/Samples/NET8Native/Report/load-test-report-x86.txt @@ -0,0 +1,18 @@ +Mon Nov 13 07:49:43 UTC 2023 +x86 RESULTS +Test duration sec: 600 +Log interval min: 20 +Complete +RESULTS coldstart 0 +RESULTS count 136757 +RESULTS p50 5.7719 +RESULTS p90 8.735 +RESULTS p99 19.5334 +RESULTS max 130.47 +RESULTS coldstart 1 +RESULTS count 256 +RESULTS p50 365.4209 +RESULTS p90 409.523 +RESULTS p99 463.0947 +RESULTS max 515.67 +STATISTICS 171255480.0 137013.0 516656.0 diff --git a/loadtestcli/Samples/NET8NativeMinimalAPI/Report/load-test-report-x86.json b/loadtestcli/Samples/NET8NativeMinimalAPI/Report/load-test-report-x86.json new file mode 100644 index 0000000..b991edf --- /dev/null +++ b/loadtestcli/Samples/NET8NativeMinimalAPI/Report/load-test-report-x86.json @@ -0,0 +1,62 @@ +{ + "results": [ + [ + { + "field": "coldstart", + "value": "0" + }, + { + "field": "count", + "value": "141726" + }, + { + "field": "p50", + "value": "5.9169" + }, + { + "field": "p90", + "value": "10.1504" + }, + { + "field": "p99", + "value": "21.746" + }, + { + "field": "max", + "value": "209.82" + } + ], + [ + { + "field": "coldstart", + "value": "1" + }, + { + "field": "count", + "value": "65" + }, + { + "field": "p50", + "value": "543.61" + }, + { + "field": "p90", + "value": "592.98" + }, + { + "field": "p99", + "value": "740.61" + }, + { + "field": "max", + "value": "740.61" + } + ] + ], + "statistics": { + "recordsMatched": 141791.0, + "recordsScanned": 1188681.0, + "bytesScanned": 880231703.0 + }, + "status": "Complete" +} diff --git a/loadtestcli/Samples/NET8NativeMinimalAPI/Report/load-test-report-x86.txt b/loadtestcli/Samples/NET8NativeMinimalAPI/Report/load-test-report-x86.txt new file mode 100644 index 0000000..4613c0d --- /dev/null +++ b/loadtestcli/Samples/NET8NativeMinimalAPI/Report/load-test-report-x86.txt @@ -0,0 +1,18 @@ +Mon Nov 13 08:03:28 UTC 2023 +x86 RESULTS lambda: Net8-Native-MinimalApi-X86 +Test duration sec: 600 +Log interval min: 20 +Complete +RESULTS coldstart 0 +RESULTS count 141726 +RESULTS p50 5.9169 +RESULTS p90 10.1504 +RESULTS p99 21.746 +RESULTS max 209.82 +RESULTS coldstart 1 +RESULTS count 65 +RESULTS p50 543.61 +RESULTS p90 592.98 +RESULTS p99 740.61 +RESULTS max 740.61 +STATISTICS 880231703.0 141791.0 1188681.0 From b50f8b9928520d6e0b59307d52a98bdaeaba7039 Mon Sep 17 00:00:00 2001 From: Riccardo Venturi Date: Mon, 13 Nov 2023 14:58:39 +0100 Subject: [PATCH 28/32] . --- loadtest/codebuild/load-test-buildspec.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/loadtest/codebuild/load-test-buildspec.yml b/loadtest/codebuild/load-test-buildspec.yml index c3ecc19..6e65a49 100644 --- a/loadtest/codebuild/load-test-buildspec.yml +++ b/loadtest/codebuild/load-test-buildspec.yml @@ -8,7 +8,7 @@ phases: build: commands: - ./run-all-load-tests.sh - - cd ../loadtestcli + - cd ../../loadtestcli - dotnet run ../src artifacts: files: From 7eccd1af7f0cc7fd0902e4d529cd8ecea77340cc Mon Sep 17 00:00:00 2001 From: Riccardo Venturi Date: Mon, 13 Nov 2023 15:19:47 +0100 Subject: [PATCH 29/32] moving loadtestcli to net6 --- loadtestcli/LoadtestCli.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/loadtestcli/LoadtestCli.csproj b/loadtestcli/LoadtestCli.csproj index d439800..40c60dd 100644 --- a/loadtestcli/LoadtestCli.csproj +++ b/loadtestcli/LoadtestCli.csproj @@ -2,7 +2,7 @@ Exe - net7.0 + net6.0 enable enable From d9aaed6c2f833818e985fe78c43e14c9c35c93e0 Mon Sep 17 00:00:00 2001 From: Riccardo Venturi Date: Mon, 13 Nov 2023 23:22:46 +0100 Subject: [PATCH 30/32] fixing net6 container load test --- loadtest/codebuild/run-all-load-tests.sh | 4 +- loadtestcli/Program.cs | 31 +++++++--- .../FAKE/Report/load-test-report-Empty.json | 2 - .../FAKE/Report/load-test-report-Null.json | 2 + .../NET6/Report/load-test-report-arm64.json | 62 +++++++++++++++++++ .../NET6/Report/load-test-report-arm64.txt | 18 ++++++ .../NET6/Report/load-test-report-x86.json | 62 +++++++++++++++++++ .../NET6/Report/load-test-report-x86.txt | 18 ++++++ .../Report/load-test-report-arm64.json | 62 +++++++++++++++++++ .../Report/load-test-report-arm64.txt | 18 ++++++ .../Report/load-test-report-x86.json | 62 +++++++++++++++++++ .../Report/load-test-report-x86.txt | 18 ++++++ .../Report/load-test-report-arm64.json | 62 +++++++++++++++++++ .../Report/load-test-report-arm64.txt | 18 ++++++ .../Report/load-test-report-x86.json | 62 +++++++++++++++++++ .../Report/load-test-report-x86.txt | 18 ++++++ .../Report/load-test-report-arm64.json | 62 +++++++++++++++++++ .../Report/load-test-report-arm64.txt | 18 ++++++ .../Report/load-test-report-x86.json | 62 +++++++++++++++++++ .../Report/load-test-report-x86.txt | 18 ++++++ .../Report/load-test-report-arm64.json | 36 +++++++++++ .../Report/load-test-report-arm64.txt | 12 ++++ .../Report/load-test-report-x86.json | 62 +++++++++++++++++++ .../Report/load-test-report-x86.txt | 18 ++++++ .../Report/load-test-report-arm64.json | 62 +++++++++++++++++++ .../Report/load-test-report-arm64.txt | 18 ++++++ .../Report/load-test-report-x86.json | 62 +++++++++++++++++++ .../Report/load-test-report-x86.txt | 18 ++++++ .../Report/load-test-report-arm64.json | 62 +++++++++++++++++++ .../Report/load-test-report-arm64.txt | 18 ++++++ .../Report/load-test-report-x86.json | 62 +++++++++++++++++++ .../Report/load-test-report-x86.txt | 18 ++++++ src/NET6Containers/deploy.sh | 2 +- 33 files changed, 1116 insertions(+), 13 deletions(-) create mode 100644 loadtestcli/Samples/FAKE/Report/load-test-report-Null.json create mode 100644 loadtestcli/Samples/NET6/Report/load-test-report-arm64.json create mode 100644 loadtestcli/Samples/NET6/Report/load-test-report-arm64.txt create mode 100644 loadtestcli/Samples/NET6/Report/load-test-report-x86.json create mode 100644 loadtestcli/Samples/NET6/Report/load-test-report-x86.txt create mode 100644 loadtestcli/Samples/NET6CustomRuntime/Report/load-test-report-arm64.json create mode 100644 loadtestcli/Samples/NET6CustomRuntime/Report/load-test-report-arm64.txt create mode 100644 loadtestcli/Samples/NET6CustomRuntime/Report/load-test-report-x86.json create mode 100644 loadtestcli/Samples/NET6CustomRuntime/Report/load-test-report-x86.txt create mode 100644 loadtestcli/Samples/NET6MinimalAPI/Report/load-test-report-arm64.json create mode 100644 loadtestcli/Samples/NET6MinimalAPI/Report/load-test-report-arm64.txt create mode 100644 loadtestcli/Samples/NET6MinimalAPI/Report/load-test-report-x86.json create mode 100644 loadtestcli/Samples/NET6MinimalAPI/Report/load-test-report-x86.txt create mode 100644 loadtestcli/Samples/NET6MinimalAPIWebAdapter/Report/load-test-report-arm64.json create mode 100644 loadtestcli/Samples/NET6MinimalAPIWebAdapter/Report/load-test-report-arm64.txt create mode 100644 loadtestcli/Samples/NET6MinimalAPIWebAdapter/Report/load-test-report-x86.json create mode 100644 loadtestcli/Samples/NET6MinimalAPIWebAdapter/Report/load-test-report-x86.txt create mode 100644 loadtestcli/Samples/NET6Native/Report/load-test-report-arm64.json create mode 100644 loadtestcli/Samples/NET6Native/Report/load-test-report-arm64.txt create mode 100644 loadtestcli/Samples/NET6Native/Report/load-test-report-x86.json create mode 100644 loadtestcli/Samples/NET6Native/Report/load-test-report-x86.txt create mode 100644 loadtestcli/Samples/NET6TopLevelStatements/Report/load-test-report-arm64.json create mode 100644 loadtestcli/Samples/NET6TopLevelStatements/Report/load-test-report-arm64.txt create mode 100644 loadtestcli/Samples/NET6TopLevelStatements/Report/load-test-report-x86.json create mode 100644 loadtestcli/Samples/NET6TopLevelStatements/Report/load-test-report-x86.txt create mode 100644 loadtestcli/Samples/NET6WithPowerTools/Report/load-test-report-arm64.json create mode 100644 loadtestcli/Samples/NET6WithPowerTools/Report/load-test-report-arm64.txt create mode 100644 loadtestcli/Samples/NET6WithPowerTools/Report/load-test-report-x86.json create mode 100644 loadtestcli/Samples/NET6WithPowerTools/Report/load-test-report-x86.txt diff --git a/loadtest/codebuild/run-all-load-tests.sh b/loadtest/codebuild/run-all-load-tests.sh index 9dd3d9a..2e38fa7 100755 --- a/loadtest/codebuild/run-all-load-tests.sh +++ b/loadtest/codebuild/run-all-load-tests.sh @@ -24,9 +24,9 @@ then DELETE_STACK=$LT_DELETE_STACK fi -if [ "x${LT_ECRURI}" != x ]; +if [ "x${LT_ECR_URI}" != x ]; then - ECR_URI=$LT_ECRURI + ECR_URI=$LT_ECR_URI fi echo -------------------------------------------- diff --git a/loadtestcli/Program.cs b/loadtestcli/Program.cs index f27e44c..800c987 100644 --- a/loadtestcli/Program.cs +++ b/loadtestcli/Program.cs @@ -84,11 +84,25 @@ private static async Task Main(string[] args) var reportFolderInfo = new DirectoryInfo(reportFolderPath); foreach (var reportFileInfo in reportFolderInfo.GetFiles("load-test-report-*.json")) { - System.Console.WriteLine($" - Processing sub folder file: {reportFileInfo.Name}"); - var ltName = reportFileInfo.Name.Replace("load-test-report-", "").Replace(reportFileInfo.Extension,""); - var title = $"{reportFolderInfo.Parent?.Name} - {ltName}"; - System.Console.WriteLine($" - Report: {title}"); - var ltResult = await DeserializeLoadTestResult(reportFileInfo.FullName, title); + QueryResultWrapper ltResult; + try + { + System.Console.WriteLine($" - Processing sub folder file: {reportFileInfo.Name}"); + var ltName = reportFileInfo.Name.Replace("load-test-report-", "").Replace(reportFileInfo.Extension,""); + var title = $"{reportFolderInfo.Parent?.Name} - {ltName}"; + System.Console.WriteLine($" - Report: {title}"); + ltResult = await DeserializeLoadTestResult(reportFileInfo.FullName, title); + + } + catch(Exception ex) + { + ltResult=new QueryResultWrapper() + { + LoadTestType= $"error processing file {reportFileInfo.FullName}: {ex.Message}" + }; + System.Console.WriteLine($" !!!! ERROR PROCESSING FILE: {reportFileInfo.FullName}: {ex.Message} !!!!"); + System.Console.WriteLine(ex.ToString()); + } reportResultList.Add(ltResult); } } @@ -215,15 +229,16 @@ private static void GenerateHtmlGlobalReport(List reportResu private static async Task DeserializeLoadTestResult(string filePath,string title) { var json = await File.ReadAllTextAsync(filePath); - var lt = JsonSerializer.Deserialize(json); - + var ret = new QueryResultWrapper { LoadTestType = title, WarmStart=null, ColdStart=null }; - + + var lt=JsonSerializer.Deserialize(json); + if(lt?.results==null) return ret; diff --git a/loadtestcli/Samples/FAKE/Report/load-test-report-Empty.json b/loadtestcli/Samples/FAKE/Report/load-test-report-Empty.json index 2c63c08..e69de29 100644 --- a/loadtestcli/Samples/FAKE/Report/load-test-report-Empty.json +++ b/loadtestcli/Samples/FAKE/Report/load-test-report-Empty.json @@ -1,2 +0,0 @@ -{ -} diff --git a/loadtestcli/Samples/FAKE/Report/load-test-report-Null.json b/loadtestcli/Samples/FAKE/Report/load-test-report-Null.json new file mode 100644 index 0000000..2c63c08 --- /dev/null +++ b/loadtestcli/Samples/FAKE/Report/load-test-report-Null.json @@ -0,0 +1,2 @@ +{ +} diff --git a/loadtestcli/Samples/NET6/Report/load-test-report-arm64.json b/loadtestcli/Samples/NET6/Report/load-test-report-arm64.json new file mode 100644 index 0000000..ac4021b --- /dev/null +++ b/loadtestcli/Samples/NET6/Report/load-test-report-arm64.json @@ -0,0 +1,62 @@ +{ + "results": [ + [ + { + "field": "coldstart", + "value": "0" + }, + { + "field": "count", + "value": "137147" + }, + { + "field": "p50", + "value": "6.0116" + }, + { + "field": "p90", + "value": "8.7991" + }, + { + "field": "p99", + "value": "19.4591" + }, + { + "field": "max", + "value": "218.59" + } + ], + [ + { + "field": "coldstart", + "value": "1" + }, + { + "field": "count", + "value": "567" + }, + { + "field": "p50", + "value": "1121.5751" + }, + { + "field": "p90", + "value": "1174.3456" + }, + { + "field": "p99", + "value": "1236.9951" + }, + { + "field": "max", + "value": "1303.02" + } + ] + ], + "statistics": { + "recordsMatched": 137714.0, + "recordsScanned": 434509.0, + "bytesScanned": 59860688.0 + }, + "status": "Complete" +} diff --git a/loadtestcli/Samples/NET6/Report/load-test-report-arm64.txt b/loadtestcli/Samples/NET6/Report/load-test-report-arm64.txt new file mode 100644 index 0000000..0aa2af5 --- /dev/null +++ b/loadtestcli/Samples/NET6/Report/load-test-report-arm64.txt @@ -0,0 +1,18 @@ +Mon Nov 13 18:58:34 UTC 2023 +arm64 RESULTS +Test duration sec: 600 +Log interval min: 20 +Complete +RESULTS coldstart 0 +RESULTS count 137147 +RESULTS p50 6.0116 +RESULTS p90 8.7991 +RESULTS p99 19.4591 +RESULTS max 218.59 +RESULTS coldstart 1 +RESULTS count 567 +RESULTS p50 1121.5751 +RESULTS p90 1174.3456 +RESULTS p99 1236.9951 +RESULTS max 1303.02 +STATISTICS 59860688.0 137714.0 434509.0 diff --git a/loadtestcli/Samples/NET6/Report/load-test-report-x86.json b/loadtestcli/Samples/NET6/Report/load-test-report-x86.json new file mode 100644 index 0000000..649da94 --- /dev/null +++ b/loadtestcli/Samples/NET6/Report/load-test-report-x86.json @@ -0,0 +1,62 @@ +{ + "results": [ + [ + { + "field": "coldstart", + "value": "0" + }, + { + "field": "count", + "value": "135097" + }, + { + "field": "p50", + "value": "5.3794" + }, + { + "field": "p90", + "value": "9.3759" + }, + { + "field": "p99", + "value": "21.746" + }, + { + "field": "max", + "value": "134.11" + } + ], + [ + { + "field": "coldstart", + "value": "1" + }, + { + "field": "count", + "value": "468" + }, + { + "field": "p50", + "value": "865.7695" + }, + { + "field": "p90", + "value": "928.5124" + }, + { + "field": "p99", + "value": "1041.6136" + }, + { + "field": "max", + "value": "1125.25" + } + ] + ], + "statistics": { + "recordsMatched": 135565.0, + "recordsScanned": 428084.0, + "bytesScanned": 59006872.0 + }, + "status": "Complete" +} diff --git a/loadtestcli/Samples/NET6/Report/load-test-report-x86.txt b/loadtestcli/Samples/NET6/Report/load-test-report-x86.txt new file mode 100644 index 0000000..3124fce --- /dev/null +++ b/loadtestcli/Samples/NET6/Report/load-test-report-x86.txt @@ -0,0 +1,18 @@ +Mon Nov 13 18:47:49 UTC 2023 +x86 RESULTS +Test duration sec: 600 +Log interval min: 20 +Complete +RESULTS coldstart 0 +RESULTS count 135097 +RESULTS p50 5.3794 +RESULTS p90 9.3759 +RESULTS p99 21.746 +RESULTS max 134.11 +RESULTS coldstart 1 +RESULTS count 468 +RESULTS p50 865.7695 +RESULTS p90 928.5124 +RESULTS p99 1041.6136 +RESULTS max 1125.25 +STATISTICS 59006872.0 135565.0 428084.0 diff --git a/loadtestcli/Samples/NET6CustomRuntime/Report/load-test-report-arm64.json b/loadtestcli/Samples/NET6CustomRuntime/Report/load-test-report-arm64.json new file mode 100644 index 0000000..03b0870 --- /dev/null +++ b/loadtestcli/Samples/NET6CustomRuntime/Report/load-test-report-arm64.json @@ -0,0 +1,62 @@ +{ + "results": [ + [ + { + "field": "coldstart", + "value": "0" + }, + { + "field": "count", + "value": "135961" + }, + { + "field": "p50", + "value": "6.2055" + }, + { + "field": "p90", + "value": "9.6783" + }, + { + "field": "p99", + "value": "21.0665" + }, + { + "field": "max", + "value": "182.08" + } + ], + [ + { + "field": "coldstart", + "value": "1" + }, + { + "field": "count", + "value": "1151" + }, + { + "field": "p50", + "value": "1951.2247" + }, + { + "field": "p90", + "value": "2028.7864" + }, + { + "field": "p99", + "value": "2105.2185" + }, + { + "field": "max", + "value": "2189.86" + } + ] + ], + "statistics": { + "recordsMatched": 137112.0, + "recordsScanned": 519167.0, + "bytesScanned": 173196929.0 + }, + "status": "Complete" +} diff --git a/loadtestcli/Samples/NET6CustomRuntime/Report/load-test-report-arm64.txt b/loadtestcli/Samples/NET6CustomRuntime/Report/load-test-report-arm64.txt new file mode 100644 index 0000000..c5b1950 --- /dev/null +++ b/loadtestcli/Samples/NET6CustomRuntime/Report/load-test-report-arm64.txt @@ -0,0 +1,18 @@ +Mon Nov 13 19:25:15 UTC 2023 +arm64 RESULTS +Test duration sec: 600 +Log interval min: 20 +Complete +RESULTS coldstart 0 +RESULTS count 135961 +RESULTS p50 6.2055 +RESULTS p90 9.6783 +RESULTS p99 21.0665 +RESULTS max 182.08 +RESULTS coldstart 1 +RESULTS count 1151 +RESULTS p50 1951.2247 +RESULTS p90 2028.7864 +RESULTS p99 2105.2185 +RESULTS max 2189.86 +STATISTICS 173196929.0 137112.0 519167.0 diff --git a/loadtestcli/Samples/NET6CustomRuntime/Report/load-test-report-x86.json b/loadtestcli/Samples/NET6CustomRuntime/Report/load-test-report-x86.json new file mode 100644 index 0000000..51b88b3 --- /dev/null +++ b/loadtestcli/Samples/NET6CustomRuntime/Report/load-test-report-x86.json @@ -0,0 +1,62 @@ +{ + "results": [ + [ + { + "field": "coldstart", + "value": "0" + }, + { + "field": "count", + "value": "134829" + }, + { + "field": "p50", + "value": "5.9169" + }, + { + "field": "p90", + "value": "10.6454" + }, + { + "field": "p99", + "value": "23.9189" + }, + { + "field": "max", + "value": "186.97" + } + ], + [ + { + "field": "coldstart", + "value": "1" + }, + { + "field": "count", + "value": "858" + }, + { + "field": "p50", + "value": "1553.5943" + }, + { + "field": "p90", + "value": "1666.1844" + }, + { + "field": "p99", + "value": "1837.6493" + }, + { + "field": "max", + "value": "2335.72" + } + ] + ], + "statistics": { + "recordsMatched": 135687.0, + "recordsScanned": 512095.0, + "bytesScanned": 169019404.0 + }, + "status": "Complete" +} diff --git a/loadtestcli/Samples/NET6CustomRuntime/Report/load-test-report-x86.txt b/loadtestcli/Samples/NET6CustomRuntime/Report/load-test-report-x86.txt new file mode 100644 index 0000000..ace00da --- /dev/null +++ b/loadtestcli/Samples/NET6CustomRuntime/Report/load-test-report-x86.txt @@ -0,0 +1,18 @@ +Mon Nov 13 19:14:30 UTC 2023 +x86 RESULTS +Test duration sec: 600 +Log interval min: 20 +Complete +RESULTS coldstart 0 +RESULTS count 134829 +RESULTS p50 5.9169 +RESULTS p90 10.6454 +RESULTS p99 23.9189 +RESULTS max 186.97 +RESULTS coldstart 1 +RESULTS count 858 +RESULTS p50 1553.5943 +RESULTS p90 1666.1844 +RESULTS p99 1837.6493 +RESULTS max 2335.72 +STATISTICS 169019404.0 135687.0 512095.0 diff --git a/loadtestcli/Samples/NET6MinimalAPI/Report/load-test-report-arm64.json b/loadtestcli/Samples/NET6MinimalAPI/Report/load-test-report-arm64.json new file mode 100644 index 0000000..5024ee6 --- /dev/null +++ b/loadtestcli/Samples/NET6MinimalAPI/Report/load-test-report-arm64.json @@ -0,0 +1,62 @@ +{ + "results": [ + [ + { + "field": "coldstart", + "value": "0" + }, + { + "field": "count", + "value": "139157" + }, + { + "field": "p50", + "value": "6.2055" + }, + { + "field": "p90", + "value": "9.5259" + }, + { + "field": "p99", + "value": "20.7347" + }, + { + "field": "max", + "value": "186.95" + } + ], + [ + { + "field": "coldstart", + "value": "1" + }, + { + "field": "count", + "value": "243" + }, + { + "field": "p50", + "value": "2107.3237" + }, + { + "field": "p90", + "value": "2195.4747" + }, + { + "field": "p99", + "value": "2289.6004" + }, + { + "field": "max", + "value": "2309.17" + } + ] + ], + "statistics": { + "recordsMatched": 139400.0, + "recordsScanned": 1254913.0, + "bytesScanned": 921494250.0 + }, + "status": "Complete" +} diff --git a/loadtestcli/Samples/NET6MinimalAPI/Report/load-test-report-arm64.txt b/loadtestcli/Samples/NET6MinimalAPI/Report/load-test-report-arm64.txt new file mode 100644 index 0000000..15d2922 --- /dev/null +++ b/loadtestcli/Samples/NET6MinimalAPI/Report/load-test-report-arm64.txt @@ -0,0 +1,18 @@ +Mon Nov 13 19:48:11 UTC 2023 +arm64 RESULTS lambda: Net6-MinimalApi-Arm64 +Test duration sec: 600 +Log interval min: 20 +Complete +RESULTS coldstart 0 +RESULTS count 139157 +RESULTS p50 6.2055 +RESULTS p90 9.5259 +RESULTS p99 20.7347 +RESULTS max 186.95 +RESULTS coldstart 1 +RESULTS count 243 +RESULTS p50 2107.3237 +RESULTS p90 2195.4747 +RESULTS p99 2289.6004 +RESULTS max 2309.17 +STATISTICS 921494250.0 139400.0 1254913.0 diff --git a/loadtestcli/Samples/NET6MinimalAPI/Report/load-test-report-x86.json b/loadtestcli/Samples/NET6MinimalAPI/Report/load-test-report-x86.json new file mode 100644 index 0000000..abb6b80 --- /dev/null +++ b/loadtestcli/Samples/NET6MinimalAPI/Report/load-test-report-x86.json @@ -0,0 +1,62 @@ +{ + "results": [ + [ + { + "field": "coldstart", + "value": "0" + }, + { + "field": "count", + "value": "139442" + }, + { + "field": "p50", + "value": "6.3048" + }, + { + "field": "p90", + "value": "10.8157" + }, + { + "field": "p99", + "value": "24.6905" + }, + { + "field": "max", + "value": "168.18" + } + ], + [ + { + "field": "coldstart", + "value": "1" + }, + { + "field": "count", + "value": "201" + }, + { + "field": "p50", + "value": "1756.8276" + }, + { + "field": "p90", + "value": "1884.1461" + }, + { + "field": "p99", + "value": "2084.2817" + }, + { + "field": "max", + "value": "2446.27" + } + ] + ], + "statistics": { + "recordsMatched": 139643.0, + "recordsScanned": 1257061.0, + "bytesScanned": 918027433.0 + }, + "status": "Complete" +} diff --git a/loadtestcli/Samples/NET6MinimalAPI/Report/load-test-report-x86.txt b/loadtestcli/Samples/NET6MinimalAPI/Report/load-test-report-x86.txt new file mode 100644 index 0000000..077753c --- /dev/null +++ b/loadtestcli/Samples/NET6MinimalAPI/Report/load-test-report-x86.txt @@ -0,0 +1,18 @@ +Mon Nov 13 19:37:29 UTC 2023 +x86 RESULTS lambda: Net6-MinimalApi-X86 +Test duration sec: 600 +Log interval min: 20 +Complete +RESULTS coldstart 0 +RESULTS count 139442 +RESULTS p50 6.3048 +RESULTS p90 10.8157 +RESULTS p99 24.6905 +RESULTS max 168.18 +RESULTS coldstart 1 +RESULTS count 201 +RESULTS p50 1756.8276 +RESULTS p90 1884.1461 +RESULTS p99 2084.2817 +RESULTS max 2446.27 +STATISTICS 918027433.0 139643.0 1257061.0 diff --git a/loadtestcli/Samples/NET6MinimalAPIWebAdapter/Report/load-test-report-arm64.json b/loadtestcli/Samples/NET6MinimalAPIWebAdapter/Report/load-test-report-arm64.json new file mode 100644 index 0000000..f741b2e --- /dev/null +++ b/loadtestcli/Samples/NET6MinimalAPIWebAdapter/Report/load-test-report-arm64.json @@ -0,0 +1,62 @@ +{ + "results": [ + [ + { + "field": "coldstart", + "value": "0" + }, + { + "field": "count", + "value": "138066" + }, + { + "field": "p50", + "value": "6.1078" + }, + { + "field": "p90", + "value": "9.3759" + }, + { + "field": "p99", + "value": "20.0868" + }, + { + "field": "max", + "value": "188.1" + } + ], + [ + { + "field": "coldstart", + "value": "1" + }, + { + "field": "count", + "value": "149" + }, + { + "field": "p50", + "value": "1288.7395" + }, + { + "field": "p90", + "value": "1335.9553" + }, + { + "field": "p99", + "value": "1428.4831" + }, + { + "field": "max", + "value": "1464.5" + } + ] + ], + "statistics": { + "recordsMatched": 138215.0, + "recordsScanned": 692169.0, + "bytesScanned": 267841605.0 + }, + "status": "Complete" +} diff --git a/loadtestcli/Samples/NET6MinimalAPIWebAdapter/Report/load-test-report-arm64.txt b/loadtestcli/Samples/NET6MinimalAPIWebAdapter/Report/load-test-report-arm64.txt new file mode 100644 index 0000000..8860889 --- /dev/null +++ b/loadtestcli/Samples/NET6MinimalAPIWebAdapter/Report/load-test-report-arm64.txt @@ -0,0 +1,18 @@ +Mon Nov 13 20:10:50 UTC 2023 +arm64 RESULTS lambda: Net6-MinimalApi-WebadApter-Arm64 +Test duration sec: 600 +Log interval min: 20 +Complete +RESULTS coldstart 0 +RESULTS count 138066 +RESULTS p50 6.1078 +RESULTS p90 9.3759 +RESULTS p99 20.0868 +RESULTS max 188.1 +RESULTS coldstart 1 +RESULTS count 149 +RESULTS p50 1288.7395 +RESULTS p90 1335.9553 +RESULTS p99 1428.4831 +RESULTS max 1464.5 +STATISTICS 267841605.0 138215.0 692169.0 diff --git a/loadtestcli/Samples/NET6MinimalAPIWebAdapter/Report/load-test-report-x86.json b/loadtestcli/Samples/NET6MinimalAPIWebAdapter/Report/load-test-report-x86.json new file mode 100644 index 0000000..06b9194 --- /dev/null +++ b/loadtestcli/Samples/NET6MinimalAPIWebAdapter/Report/load-test-report-x86.json @@ -0,0 +1,62 @@ +{ + "results": [ + [ + { + "field": "coldstart", + "value": "0" + }, + { + "field": "count", + "value": "134610" + }, + { + "field": "p50", + "value": "6.1078" + }, + { + "field": "p90", + "value": "9.9905" + }, + { + "field": "p99", + "value": "21.0665" + }, + { + "field": "max", + "value": "123.79" + } + ], + [ + { + "field": "coldstart", + "value": "1" + }, + { + "field": "count", + "value": "112" + }, + { + "field": "p50", + "value": "1028.1669" + }, + { + "field": "p90", + "value": "1109.3115" + }, + { + "field": "p99", + "value": "1210.0916" + }, + { + "field": "max", + "value": "1237.92" + } + ] + ], + "statistics": { + "recordsMatched": 134722.0, + "recordsScanned": 674331.0, + "bytesScanned": 260957154.0 + }, + "status": "Complete" +} diff --git a/loadtestcli/Samples/NET6MinimalAPIWebAdapter/Report/load-test-report-x86.txt b/loadtestcli/Samples/NET6MinimalAPIWebAdapter/Report/load-test-report-x86.txt new file mode 100644 index 0000000..0f24e84 --- /dev/null +++ b/loadtestcli/Samples/NET6MinimalAPIWebAdapter/Report/load-test-report-x86.txt @@ -0,0 +1,18 @@ +Mon Nov 13 20:00:08 UTC 2023 +x86 RESULTS lambda: Net6-MinimalApi-WebadApter-X86 +Test duration sec: 600 +Log interval min: 20 +Complete +RESULTS coldstart 0 +RESULTS count 134610 +RESULTS p50 6.1078 +RESULTS p90 9.9905 +RESULTS p99 21.0665 +RESULTS max 123.79 +RESULTS coldstart 1 +RESULTS count 112 +RESULTS p50 1028.1669 +RESULTS p90 1109.3115 +RESULTS p99 1210.0916 +RESULTS max 1237.92 +STATISTICS 260957154.0 134722.0 674331.0 diff --git a/loadtestcli/Samples/NET6Native/Report/load-test-report-arm64.json b/loadtestcli/Samples/NET6Native/Report/load-test-report-arm64.json new file mode 100644 index 0000000..600f742 --- /dev/null +++ b/loadtestcli/Samples/NET6Native/Report/load-test-report-arm64.json @@ -0,0 +1,36 @@ +{ + "results": [ + [ + { + "field": "coldstart", + "value": "0" + }, + { + "field": "count", + "value": "137737" + }, + { + "field": "p50", + "value": "3.4491" + }, + { + "field": "p90", + "value": "5.8237" + }, + { + "field": "p99", + "value": "14.3927" + }, + { + "field": "max", + "value": "64.44" + } + ] + ], + "statistics": { + "recordsMatched": 137737.0, + "recordsScanned": 690625.0, + "bytesScanned": 97122238.0 + }, + "status": "Complete" +} diff --git a/loadtestcli/Samples/NET6Native/Report/load-test-report-arm64.txt b/loadtestcli/Samples/NET6Native/Report/load-test-report-arm64.txt new file mode 100644 index 0000000..9b689a2 --- /dev/null +++ b/loadtestcli/Samples/NET6Native/Report/load-test-report-arm64.txt @@ -0,0 +1,12 @@ +Mon Nov 13 20:37:16 UTC 2023 +arm64 RESULTS +Test duration sec: 600 +Log interval min: 20 +Complete +RESULTS coldstart 0 +RESULTS count 137737 +RESULTS p50 3.4491 +RESULTS p90 5.8237 +RESULTS p99 14.3927 +RESULTS max 64.44 +STATISTICS 97122238.0 137737.0 690625.0 diff --git a/loadtestcli/Samples/NET6Native/Report/load-test-report-x86.json b/loadtestcli/Samples/NET6Native/Report/load-test-report-x86.json new file mode 100644 index 0000000..b0a54b9 --- /dev/null +++ b/loadtestcli/Samples/NET6Native/Report/load-test-report-x86.json @@ -0,0 +1,62 @@ +{ + "results": [ + [ + { + "field": "coldstart", + "value": "0" + }, + { + "field": "count", + "value": "136565" + }, + { + "field": "p50", + "value": "5.9169" + }, + { + "field": "p90", + "value": "9.8332" + }, + { + "field": "p99", + "value": "22.0939" + }, + { + "field": "max", + "value": "443.61" + } + ], + [ + { + "field": "coldstart", + "value": "1" + }, + { + "field": "count", + "value": "886" + }, + { + "field": "p50", + "value": "1610.5137" + }, + { + "field": "p90", + "value": "1739.3556" + }, + { + "field": "p99", + "value": "1918.3506" + }, + { + "field": "max", + "value": "2234.13" + } + ] + ], + "statistics": { + "recordsMatched": 137451.0, + "recordsScanned": 434767.0, + "bytesScanned": 59976637.0 + }, + "status": "Complete" +} diff --git a/loadtestcli/Samples/NET6Native/Report/load-test-report-x86.txt b/loadtestcli/Samples/NET6Native/Report/load-test-report-x86.txt new file mode 100644 index 0000000..63d1de9 --- /dev/null +++ b/loadtestcli/Samples/NET6Native/Report/load-test-report-x86.txt @@ -0,0 +1,18 @@ +Mon Nov 13 20:26:31 UTC 2023 +x86 RESULTS +Test duration sec: 600 +Log interval min: 20 +Complete +RESULTS coldstart 0 +RESULTS count 136565 +RESULTS p50 5.9169 +RESULTS p90 9.8332 +RESULTS p99 22.0939 +RESULTS max 443.61 +RESULTS coldstart 1 +RESULTS count 886 +RESULTS p50 1610.5137 +RESULTS p90 1739.3556 +RESULTS p99 1918.3506 +RESULTS max 2234.13 +STATISTICS 59976637.0 137451.0 434767.0 diff --git a/loadtestcli/Samples/NET6TopLevelStatements/Report/load-test-report-arm64.json b/loadtestcli/Samples/NET6TopLevelStatements/Report/load-test-report-arm64.json new file mode 100644 index 0000000..cd10bfa --- /dev/null +++ b/loadtestcli/Samples/NET6TopLevelStatements/Report/load-test-report-arm64.json @@ -0,0 +1,62 @@ +{ + "results": [ + [ + { + "field": "coldstart", + "value": "0" + }, + { + "field": "count", + "value": "138920" + }, + { + "field": "p50", + "value": "5.9115" + }, + { + "field": "p90", + "value": "9.4595" + }, + { + "field": "p99", + "value": "20.006" + }, + { + "field": "max", + "value": "148.55" + } + ], + [ + { + "field": "coldstart", + "value": "1" + }, + { + "field": "count", + "value": "614" + }, + { + "field": "p50", + "value": "1154.5603" + }, + { + "field": "p90", + "value": "1199.2551" + }, + { + "field": "p99", + "value": "1256.9361" + }, + { + "field": "max", + "value": "1452.91" + } + ] + ], + "statistics": { + "recordsMatched": 139534.0, + "recordsScanned": 440180.0, + "bytesScanned": 60681734.0 + }, + "status": "Complete" +} diff --git a/loadtestcli/Samples/NET6TopLevelStatements/Report/load-test-report-arm64.txt b/loadtestcli/Samples/NET6TopLevelStatements/Report/load-test-report-arm64.txt new file mode 100644 index 0000000..1b5eb8f --- /dev/null +++ b/loadtestcli/Samples/NET6TopLevelStatements/Report/load-test-report-arm64.txt @@ -0,0 +1,18 @@ +Mon Nov 13 21:01:12 UTC 2023 +arm64 RESULTS +Test duration sec: 600 +Log interval min: 20 +Complete +RESULTS coldstart 0 +RESULTS count 138920 +RESULTS p50 5.9115 +RESULTS p90 9.4595 +RESULTS p99 20.006 +RESULTS max 148.55 +RESULTS coldstart 1 +RESULTS count 614 +RESULTS p50 1154.5603 +RESULTS p90 1199.2551 +RESULTS p99 1256.9361 +RESULTS max 1452.91 +STATISTICS 60681734.0 139534.0 440180.0 diff --git a/loadtestcli/Samples/NET6TopLevelStatements/Report/load-test-report-x86.json b/loadtestcli/Samples/NET6TopLevelStatements/Report/load-test-report-x86.json new file mode 100644 index 0000000..6c61d15 --- /dev/null +++ b/loadtestcli/Samples/NET6TopLevelStatements/Report/load-test-report-x86.json @@ -0,0 +1,62 @@ +{ + "results": [ + [ + { + "field": "coldstart", + "value": "0" + }, + { + "field": "count", + "value": "138755" + }, + { + "field": "p50", + "value": "5.5529" + }, + { + "field": "p90", + "value": "10.1504" + }, + { + "field": "p99", + "value": "22.4474" + }, + { + "field": "max", + "value": "171.14" + } + ], + [ + { + "field": "coldstart", + "value": "1" + }, + { + "field": "count", + "value": "485" + }, + { + "field": "p50", + "value": "924.8077" + }, + { + "field": "p90", + "value": "997.795" + }, + { + "field": "p99", + "value": "1118.2171" + }, + { + "field": "max", + "value": "1252.6" + } + ] + ], + "statistics": { + "recordsMatched": 139240.0, + "recordsScanned": 440977.0, + "bytesScanned": 60856076.0 + }, + "status": "Complete" +} diff --git a/loadtestcli/Samples/NET6TopLevelStatements/Report/load-test-report-x86.txt b/loadtestcli/Samples/NET6TopLevelStatements/Report/load-test-report-x86.txt new file mode 100644 index 0000000..817111b --- /dev/null +++ b/loadtestcli/Samples/NET6TopLevelStatements/Report/load-test-report-x86.txt @@ -0,0 +1,18 @@ +Mon Nov 13 20:50:27 UTC 2023 +x86 RESULTS +Test duration sec: 600 +Log interval min: 20 +Complete +RESULTS coldstart 0 +RESULTS count 138755 +RESULTS p50 5.5529 +RESULTS p90 10.1504 +RESULTS p99 22.4474 +RESULTS max 171.14 +RESULTS coldstart 1 +RESULTS count 485 +RESULTS p50 924.8077 +RESULTS p90 997.795 +RESULTS p99 1118.2171 +RESULTS max 1252.6 +STATISTICS 60856076.0 139240.0 440977.0 diff --git a/loadtestcli/Samples/NET6WithPowerTools/Report/load-test-report-arm64.json b/loadtestcli/Samples/NET6WithPowerTools/Report/load-test-report-arm64.json new file mode 100644 index 0000000..b068b00 --- /dev/null +++ b/loadtestcli/Samples/NET6WithPowerTools/Report/load-test-report-arm64.json @@ -0,0 +1,62 @@ +{ + "results": [ + [ + { + "field": "coldstart", + "value": "0" + }, + { + "field": "count", + "value": "132463" + }, + { + "field": "p50", + "value": "6.2506" + }, + { + "field": "p90", + "value": "9.4595" + }, + { + "field": "p99", + "value": "19.8472" + }, + { + "field": "max", + "value": "205.08" + } + ], + [ + { + "field": "coldstart", + "value": "1" + }, + { + "field": "count", + "value": "618" + }, + { + "field": "p50", + "value": "1154.5603" + }, + { + "field": "p90", + "value": "1195.6645" + }, + { + "field": "p99", + "value": "1240.7098" + }, + { + "field": "max", + "value": "1309.71" + } + ] + ], + "statistics": { + "recordsMatched": 133081.0, + "recordsScanned": 543411.0, + "bytesScanned": 97004522.0 + }, + "status": "Complete" +} diff --git a/loadtestcli/Samples/NET6WithPowerTools/Report/load-test-report-arm64.txt b/loadtestcli/Samples/NET6WithPowerTools/Report/load-test-report-arm64.txt new file mode 100644 index 0000000..2248cdd --- /dev/null +++ b/loadtestcli/Samples/NET6WithPowerTools/Report/load-test-report-arm64.txt @@ -0,0 +1,18 @@ +Mon Nov 13 21:25:42 UTC 2023 +arm64 RESULTS +Test duration sec: 600 +Log interval min: 20 +Complete +RESULTS coldstart 0 +RESULTS count 132463 +RESULTS p50 6.2506 +RESULTS p90 9.4595 +RESULTS p99 19.8472 +RESULTS max 205.08 +RESULTS coldstart 1 +RESULTS count 618 +RESULTS p50 1154.5603 +RESULTS p90 1195.6645 +RESULTS p99 1240.7098 +RESULTS max 1309.71 +STATISTICS 97004522.0 133081.0 543411.0 diff --git a/loadtestcli/Samples/NET6WithPowerTools/Report/load-test-report-x86.json b/loadtestcli/Samples/NET6WithPowerTools/Report/load-test-report-x86.json new file mode 100644 index 0000000..1488688 --- /dev/null +++ b/loadtestcli/Samples/NET6WithPowerTools/Report/load-test-report-x86.json @@ -0,0 +1,62 @@ +{ + "results": [ + [ + { + "field": "coldstart", + "value": "0" + }, + { + "field": "count", + "value": "138929" + }, + { + "field": "p50", + "value": "6.0116" + }, + { + "field": "p90", + "value": "9.5259" + }, + { + "field": "p99", + "value": "20.7347" + }, + { + "field": "max", + "value": "170.67" + } + ], + [ + { + "field": "coldstart", + "value": "1" + }, + { + "field": "count", + "value": "493" + }, + { + "field": "p50", + "value": "885.9028" + }, + { + "field": "p90", + "value": "935.9666" + }, + { + "field": "p99", + "value": "1010.8444" + }, + { + "field": "max", + "value": "1110.18" + } + ] + ], + "statistics": { + "recordsMatched": 139422.0, + "recordsScanned": 568395.0, + "bytesScanned": 101301803.0 + }, + "status": "Complete" +} diff --git a/loadtestcli/Samples/NET6WithPowerTools/Report/load-test-report-x86.txt b/loadtestcli/Samples/NET6WithPowerTools/Report/load-test-report-x86.txt new file mode 100644 index 0000000..5c44d25 --- /dev/null +++ b/loadtestcli/Samples/NET6WithPowerTools/Report/load-test-report-x86.txt @@ -0,0 +1,18 @@ +Mon Nov 13 21:14:57 UTC 2023 +x86 RESULTS +Test duration sec: 600 +Log interval min: 20 +Complete +RESULTS coldstart 0 +RESULTS count 138929 +RESULTS p50 6.0116 +RESULTS p90 9.5259 +RESULTS p99 20.7347 +RESULTS max 170.67 +RESULTS coldstart 1 +RESULTS count 493 +RESULTS p50 885.9028 +RESULTS p90 935.9666 +RESULTS p99 1010.8444 +RESULTS max 1110.18 +STATISTICS 101301803.0 139422.0 568395.0 diff --git a/src/NET6Containers/deploy.sh b/src/NET6Containers/deploy.sh index 7c260a3..d7e806b 100755 --- a/src/NET6Containers/deploy.sh +++ b/src/NET6Containers/deploy.sh @@ -21,7 +21,7 @@ fi echo "${COLOR}" echo -------------------------------------------- -echo ECR URI: ECR_URI +echo ECR URI: $ECR_URI echo DELETE_STACK: $DELETE_STACK echo -------------------------------------------- echo "${NO_COLOR}" From f0adb2522a588ff1ad40d8f4723ee9271ca89b6f Mon Sep 17 00:00:00 2001 From: Riccardo Venturi Date: Wed, 15 Nov 2023 21:04:27 +0100 Subject: [PATCH 31/32] refactoring load test automation for .net6 projects and removing net6 native --- README.md | 30 +-- loadtest/codebuild/load-test-buildspec.yml | 3 - loadtest/codebuild/run-all-load-tests.sh | 44 ++-- loadtestcli/LoadtestCli.csproj | 5 + loadtestcli/Program.cs | 129 +++++++-- .../NET6/Report/load-test-errors-arm64.json | 10 + .../NET6/Report/load-test-errors-x86.json | 10 + .../Report/load-test-errors-arm64.json | 10 + .../Report/load-test-errors-x86.json | 15 ++ .../Report/load-test-errors-arm64.json | 2 + .../Report/load-test-errors-x86.json | 2 + .../Report/load-test-errors-arm64.json | 0 .../Report/load-test-errors-x86.json | 0 .../Report/load-test-errors-arm64.json | 1 + .../Report/load-test-errors-x86.json | 1 + .../Report/load-test-errors-arm64.json | 10 + .../Report/load-test-errors-x86.json | 15 ++ src/NET6/run-loadtest.sh | 31 ++- src/NET6Containers/run-loadtest.sh | 30 ++- src/NET6CustomRuntime/run-loadtest.sh | 30 ++- src/NET6MinimalAPI/run-loadtest.sh | 31 ++- src/NET6MinimalAPIWebAdapter/run-loadtest.sh | 30 ++- .../DeleteProduct/DeleteProduct.csproj | 23 -- src/NET6Native/DeleteProduct/Function.cs | 73 ------ src/NET6Native/GetProduct/Function.cs | 73 ------ src/NET6Native/GetProduct/GetProduct.csproj | 23 -- src/NET6Native/GetProducts/Function.cs | 53 ---- src/NET6Native/GetProducts/GetProducts.csproj | 23 -- src/NET6Native/Makefile | 23 -- src/NET6Native/PutProduct/Function.cs | 75 ------ src/NET6Native/PutProduct/PutProduct.csproj | 23 -- .../Shared/DataAccess/DynamoDbProducts.cs | 62 ----- .../Shared/DataAccess/ProductMapper.cs | 33 --- .../Shared/DataAccess/ProductsDAO.cs | 16 -- src/NET6Native/Shared/Models/Product.cs | 40 --- .../Shared/Models/ProductWrapper.cs | 19 -- src/NET6Native/Shared/Shared.csproj | 15 -- src/NET6Native/deploy.sh | 43 --- src/NET6Native/run-loadtest.sh | 153 ----------- src/NET6Native/template.yaml | 248 ------------------ src/NET6TopLevelStatements/run-loadtest.sh | 31 ++- src/NET6WithPowerTools/run-loadtest.sh | 31 ++- 42 files changed, 423 insertions(+), 1096 deletions(-) create mode 100644 loadtestcli/Samples/NET6/Report/load-test-errors-arm64.json create mode 100644 loadtestcli/Samples/NET6/Report/load-test-errors-x86.json create mode 100644 loadtestcli/Samples/NET6CustomRuntime/Report/load-test-errors-arm64.json create mode 100644 loadtestcli/Samples/NET6CustomRuntime/Report/load-test-errors-x86.json create mode 100644 loadtestcli/Samples/NET6MinimalAPI/Report/load-test-errors-arm64.json create mode 100644 loadtestcli/Samples/NET6MinimalAPI/Report/load-test-errors-x86.json create mode 100644 loadtestcli/Samples/NET6MinimalAPIWebAdapter/Report/load-test-errors-arm64.json create mode 100644 loadtestcli/Samples/NET6MinimalAPIWebAdapter/Report/load-test-errors-x86.json create mode 100644 loadtestcli/Samples/NET6Native/Report/load-test-errors-arm64.json create mode 100644 loadtestcli/Samples/NET6Native/Report/load-test-errors-x86.json create mode 100644 loadtestcli/Samples/NET6TopLevelStatements/Report/load-test-errors-arm64.json create mode 100644 loadtestcli/Samples/NET6TopLevelStatements/Report/load-test-errors-x86.json delete mode 100755 src/NET6Native/DeleteProduct/DeleteProduct.csproj delete mode 100755 src/NET6Native/DeleteProduct/Function.cs delete mode 100755 src/NET6Native/GetProduct/Function.cs delete mode 100755 src/NET6Native/GetProduct/GetProduct.csproj delete mode 100755 src/NET6Native/GetProducts/Function.cs delete mode 100755 src/NET6Native/GetProducts/GetProducts.csproj delete mode 100644 src/NET6Native/Makefile delete mode 100755 src/NET6Native/PutProduct/Function.cs delete mode 100755 src/NET6Native/PutProduct/PutProduct.csproj delete mode 100755 src/NET6Native/Shared/DataAccess/DynamoDbProducts.cs delete mode 100755 src/NET6Native/Shared/DataAccess/ProductMapper.cs delete mode 100755 src/NET6Native/Shared/DataAccess/ProductsDAO.cs delete mode 100755 src/NET6Native/Shared/Models/Product.cs delete mode 100755 src/NET6Native/Shared/Models/ProductWrapper.cs delete mode 100755 src/NET6Native/Shared/Shared.csproj delete mode 100755 src/NET6Native/deploy.sh delete mode 100755 src/NET6Native/run-loadtest.sh delete mode 100755 src/NET6Native/template.yaml diff --git a/README.md b/README.md index ac3bbbe..feddb31 100755 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ This implementation uses the new features detailed in [this link](https://aws.am - Source generation - Executable assemblies -### Minimal API +### .NET6 Minimal API There is a single project named ApiBootstrap that contains all the start-up code and API endpoint mapping. The SAM template still deploys a separate function per API endpoint to negate concurrency issues. It uses the new minimal API hosting model as detailed [here](https://aws.amazon.com/blogs/compute/introducing-the-net-6-runtime-for-aws-lambda/). @@ -59,12 +59,6 @@ It uses the new minimal API hosting model as detailed [here](https://aws.amazon. Same as minimal API but instead of using Amazon.Lambda.AspNetCoreServer.Hosting/Amazon.Lambda.AspNetCoreServer it is based on [Aws Lambda Web Adapter](https://github.com/awslabs/aws-lambda-web-adapter) -### .NET 6 native AOT - -The code is compiled natively for either Linux-x86_64 or Linux-ARM64 and then deployed manually to Lambda as a zip file. The SAM deploy can still be used to stand up the API Gateway endpoints and DynamoDb table, but won't be able to deploy native AOT .NET Lambda functions yet. Packages need to be published from Linux, since cross-OS native compilation is not supported yet. - -Details for compiling .NET 6 native AOT can be found [here](https://github.com/dotnet/runtimelab/blob/feature/NativeAOT/docs/using-nativeaot/compiling.md) - ### .NET 7 Custom Runtime The code is compiled on a custom runtime and deployed to the provided.al2 Lambda runtime because Lambda doesn't have a .NET 7 runtime. The code is compiled as ReadyToRun and Self-Contained because there is not .NET runtime on provided.al2 to depend on. This type of deployment is expected to be slower than a fully supported Lambda runtime like .NET 6. This sample should be able to be tested with `sam build` and then `sam deploy --guided`. @@ -295,28 +289,6 @@ filter @type="REPORT" 36.71 111.28 - - Native AOT on ARM64 - 1277.19 - 1326.64 - 1358.84 - 1367.49 - 6.10 - 9.37 - 17.97 - 838.78 - - - Native AOT on X86 - 466.81 - 542.86 - 700.45 - 730.51 - 6.21 - 11.34 - 24.69 - 371.16 - ### .NET 7 diff --git a/loadtest/codebuild/load-test-buildspec.yml b/loadtest/codebuild/load-test-buildspec.yml index 6e65a49..173cd0e 100644 --- a/loadtest/codebuild/load-test-buildspec.yml +++ b/loadtest/codebuild/load-test-buildspec.yml @@ -8,8 +8,6 @@ phases: build: commands: - ./run-all-load-tests.sh - - cd ../../loadtestcli - - dotnet run ../src artifacts: files: - src/NET6/Report/* @@ -17,7 +15,6 @@ artifacts: - src/NET6CustomRuntime/Report/* - src/NET6MinimalAPI/Report/* - src/NET6MinimalAPIWebAdapter/Report/* - - src/NET6Native/Report/* - src/NET6TopLevelStatements/Report/* - src/NET6WithPowerTools/Report/* - src/NET8/Report/* diff --git a/loadtest/codebuild/run-all-load-tests.sh b/loadtest/codebuild/run-all-load-tests.sh index 2e38fa7..97e8813 100755 --- a/loadtest/codebuild/run-all-load-tests.sh +++ b/loadtest/codebuild/run-all-load-tests.sh @@ -29,12 +29,14 @@ then ECR_URI=$LT_ECR_URI fi + echo -------------------------------------------- echo TEST_DURATIOMN_SEC: $TEST_DURATIOMN_SEC echo LOG_INTERVAL_MIN: $LOG_INTERVAL_MIN echo LOG_DELETE: $LOG_DELETE echo DELETE_STACK: $DELETE_STACK echo ECR_URI: $ECR_URI +echo LT_SNS_TOPIC_ARN: $LT_SNS_TOPIC_ARN echo -------------------------------------------- if [ "$LT_NET6" != yes ]; @@ -44,7 +46,7 @@ else echo "RUNNING load test for net6" cd ../../src/NET6/ source ./deploy.sh $DELETE_STACK - source ./run-loadtest.sh $TEST_DURATIOMN_SEC $LOG_INTERVAL_MIN $LOG_DELETE + source ./run-loadtest.sh $TEST_DURATIOMN_SEC $LOG_INTERVAL_MIN $LOG_DELETE $LT_SNS_TOPIC_ARN fi if [ "$LT_NET6_CONTAINERS" != yes ]; @@ -54,7 +56,7 @@ else echo "RUNNING load test for net6 containers" cd ../../src/NET6Containers/ source ./deploy.sh $ECR_URI $DELETE_STACK - source ./run-loadtest.sh $TEST_DURATIOMN_SEC $LOG_INTERVAL_MIN $LOG_DELETE + source ./run-loadtest.sh $TEST_DURATIOMN_SEC $LOG_INTERVAL_MIN $LOG_DELETE $LT_SNS_TOPIC_ARN fi if [ "$LT_NET6_CUSTOM" != yes ]; @@ -64,7 +66,7 @@ else echo "RUNNING load test for net6 custom runtime" cd ../../src/NET6CustomRuntime/ source ./deploy.sh $DELETE_STACK - source ./run-loadtest.sh $TEST_DURATIOMN_SEC $LOG_INTERVAL_MIN $LOG_DELETE + source ./run-loadtest.sh $TEST_DURATIOMN_SEC $LOG_INTERVAL_MIN $LOG_DELETE $LT_SNS_TOPIC_ARN fi if [ "$LT_NET6_MINIMAL_API" != yes ]; @@ -74,7 +76,7 @@ else echo "RUNNING load test for net6 minimal api" cd ../../src/NET6MinimalAPI/ source ./deploy.sh $DELETE_STACK - source ./run-loadtest.sh $TEST_DURATIOMN_SEC $LOG_INTERVAL_MIN $LOG_DELETE + source ./run-loadtest.sh $TEST_DURATIOMN_SEC $LOG_INTERVAL_MIN $LOG_DELETE $LT_SNS_TOPIC_ARN fi if [ "$LT_NET6_MINIMAL_API_WEB_ADAPTER" != yes ]; @@ -84,17 +86,7 @@ else echo "RUNNING load test for net6 minimal api web adapter" cd ../../src/NET6MinimalAPIWebAdapter/ source ./deploy.sh $DELETE_STACK - source ./run-loadtest.sh $TEST_DURATIOMN_SEC $LOG_INTERVAL_MIN $LOG_DELETE -fi - -if [ "$LT_NET6_NATIVE" != yes ]; -then - echo SKIPPING net6 native - LT_NET6_NATIVE = $LT_NET6_NATIVE -else - echo "RUNNING load test for net6 native" - cd ../../src/NET6Native/ - source ./deploy.sh $DELETE_STACK - source ./run-loadtest.sh $TEST_DURATIOMN_SEC $LOG_INTERVAL_MIN $LOG_DELETE + source ./run-loadtest.sh $TEST_DURATIOMN_SEC $LOG_INTERVAL_MIN $LOG_DELETE $LT_SNS_TOPIC_ARN fi if [ "$LT_NET6_TOPLEVEL" != yes ]; @@ -104,7 +96,7 @@ else echo "RUNNING load test for net6 top level" cd ../../src/NET6TopLevelStatements/ source ./deploy.sh $DELETE_STACK - source ./run-loadtest.sh $TEST_DURATIOMN_SEC $LOG_INTERVAL_MIN $LOG_DELETE + source ./run-loadtest.sh $TEST_DURATIOMN_SEC $LOG_INTERVAL_MIN $LOG_DELETE $LT_SNS_TOPIC_ARN fi if [ "$LT_NET6_POWERTOOLS" != yes ]; @@ -114,7 +106,7 @@ else echo "RUNNING load test for net6 power tools" cd ../../src/NET6WithPowerTools/ source ./deploy.sh $DELETE_STACK - source ./run-loadtest.sh $TEST_DURATIOMN_SEC $LOG_INTERVAL_MIN $LOG_DELETE + source ./run-loadtest.sh $TEST_DURATIOMN_SEC $LOG_INTERVAL_MIN $LOG_DELETE $LT_SNS_TOPIC_ARN fi if [ "$LT_NET8" != yes ]; @@ -124,7 +116,7 @@ else echo "RUNNING load test for net8" cd ../../src/NET8/ source ./deploy.sh $DELETE_STACK - source ./run-loadtest.sh $TEST_DURATIOMN_SEC $LOG_INTERVAL_MIN $LOG_DELETE + source ./run-loadtest.sh $TEST_DURATIOMN_SEC $LOG_INTERVAL_MIN $LOG_DELETE $LT_SNS_TOPIC_ARN fi if [ "$LT_NET8_MINIMAL_API" != yes ]; @@ -134,7 +126,7 @@ else echo "RUNNING load test for net8 minimal api" cd ../../src/NET8MinimalAPI/ source ./deploy.sh $DELETE_STACK - source ./run-loadtest.sh $TEST_DURATIOMN_SEC $LOG_INTERVAL_MIN $LOG_DELETE + source ./run-loadtest.sh $TEST_DURATIOMN_SEC $LOG_INTERVAL_MIN $LOG_DELETE $LT_SNS_TOPIC_ARN fi if [ "$LT_NET8_NATIVE" != yes ]; @@ -144,7 +136,7 @@ else echo "RUNNING load test for net8 native" cd ../../src/NET8Native/ source ./deploy.sh $DELETE_STACK - source ./run-loadtest.sh $TEST_DURATIOMN_SEC $LOG_INTERVAL_MIN $LOG_DELETE + source ./run-loadtest.sh $TEST_DURATIOMN_SEC $LOG_INTERVAL_MIN $LOG_DELETE $LT_SNS_TOPIC_ARN fi if [ "$LT_NET8_NATIVE_MINIMAL_API" != yes ]; @@ -154,5 +146,13 @@ else echo "RUNNING load test for net8 native minimal api" cd ../../src/NET8NativeMinimalAPI/ source ./deploy.sh $DELETE_STACK - source ./run-loadtest.sh $TEST_DURATIOMN_SEC $LOG_INTERVAL_MIN $LOG_DELETE -fi \ No newline at end of file + source ./run-loadtest.sh $TEST_DURATIOMN_SEC $LOG_INTERVAL_MIN $LOG_DELETE $LT_SNS_TOPIC_ARN +fi + +echo -------------------------------------------- +echo GENERATING FULL REPORT +echo -------------------------------------------- +cd ../../loadtestcli +dotnet run ../src $LT_SNS_TOPIC_ARN + + diff --git a/loadtestcli/LoadtestCli.csproj b/loadtestcli/LoadtestCli.csproj index 40c60dd..0f937ed 100644 --- a/loadtestcli/LoadtestCli.csproj +++ b/loadtestcli/LoadtestCli.csproj @@ -1,5 +1,10 @@ + + + + + Exe net6.0 diff --git a/loadtestcli/Program.cs b/loadtestcli/Program.cs index 800c987..b3a38a4 100644 --- a/loadtestcli/Program.cs +++ b/loadtestcli/Program.cs @@ -1,5 +1,9 @@ -using System.Text; +using System; +using System.Text; using System.Text.Json; +using System.Threading.Tasks; +using Amazon.SimpleNotificationService; +using Amazon.SimpleNotificationService.Model; public record QueryResultWrapper { @@ -8,6 +12,8 @@ public record QueryResultWrapper public QueryResult? ColdStart { get; set; } public QueryResult? WarmStart { get; set; } + + public string? ErrorCount { get; set; } } public record QueryResult @@ -23,6 +29,20 @@ public record QueryResult public string? Max { get; set; } } +public class CloudWatchMetricResultWrapper +{ + public string? Label { get; set; } + public List? Datapoints { get; set; } +} +public class CloudWatchMetricDatapoint +{ + public DateTime? Timestamp { get; set; } + public double? Sum { get; set; } + public string? Unit { get; set; } +} + + + public class LoadTestResults { public List>? results { get; set; } @@ -40,6 +60,8 @@ private static async Task Main(string[] args) { var folderToAnalize = ""; var reportFileOutputPath = ""; + var snsTopicArn=""; + var reportResultList = new List(); DisplayStartMsg(); @@ -51,17 +73,20 @@ private static async Task Main(string[] args) } folderToAnalize=args[0]; - if(args.Count()==2) + if(args.Count()>=2) + { + snsTopicArn=args[1]; + } + + if(args.Count()>=3) { - reportFileOutputPath=args[1]; + reportFileOutputPath=args[2]; } else { reportFileOutputPath=$"./Report/{DateTime.UtcNow:yyyy-MM-dd-hh-mm}-loadtest-report.html"; } - - //folderToAnalize = "./Samples"; - + try { System.Console.WriteLine($"STARTING using folder: {folderToAnalize}"); @@ -91,8 +116,8 @@ private static async Task Main(string[] args) var ltName = reportFileInfo.Name.Replace("load-test-report-", "").Replace(reportFileInfo.Extension,""); var title = $"{reportFolderInfo.Parent?.Name} - {ltName}"; System.Console.WriteLine($" - Report: {title}"); - ltResult = await DeserializeLoadTestResult(reportFileInfo.FullName, title); - + ltResult = await DeserializeLoadTestResultAsync(reportFileInfo.FullName, title); + ltResult.ErrorCount=await GetLoadtestErrorCountAsync(reportFileInfo.FullName.Replace("load-test-report-","load-test-errors-")); } catch(Exception ex) { @@ -108,7 +133,7 @@ private static async Task Main(string[] args) } } System.Console.WriteLine("Generating HTML Report!"); - GenerateHtmlGlobalReport(reportResultList,reportFileOutputPath); + GenerateHtmlGlobalReport(reportResultList,reportFileOutputPath,snsTopicArn); System.Console.WriteLine("FINISHED!"); } catch(Exception ex) @@ -128,10 +153,11 @@ private static void DisplayStartMsg() private static void DisplayHelp() { System.Console.WriteLine("ERROR - Missing required parameters:"); - System.Console.WriteLine("- param1: folder to process"); - System.Console.WriteLine("- param2: output file path"); + System.Console.WriteLine("- param1: mandatory folder to process"); + System.Console.WriteLine("- param2: optional sns arn"); + System.Console.WriteLine("- param3: optional output file path"); } - private static void GenerateHtmlGlobalReport(List reportResultList,string reportFileOutputPath) + private static void GenerateHtmlGlobalReport(List reportResultList,string reportFileOutputPath,string snsTopicArn) { /* we are producing an html similar to this: @@ -198,7 +224,7 @@ private static void GenerateHtmlGlobalReport(List reportResu { sb.Append($@" - {ltResult.LoadTestType} + {ltResult.LoadTestType} errors:{ltResult.ErrorCount} {FormatCount(ltResult.ColdStart?.Count??"-")} {FormatMilli(ltResult.ColdStart?.P50??"-")} {FormatMilli(ltResult.ColdStart?.P90??"-")} @@ -223,10 +249,53 @@ private static void GenerateHtmlGlobalReport(List reportResu Directory.CreateDirectory(outputDir); } File.WriteAllText(reportFileOutputPath, sb.ToString()); + + if(!string.IsNullOrEmpty(snsTopicArn)) + { + SendSNSMsg(snsTopicArn,"servereless dotnet demo - LOAD TEST FINAL REPORT",sb.ToString()); + } + else + { + Console.WriteLine("SKIPPING SENSID SNS MSG"); + } System.Console.WriteLine($"OUTPUT REPORT CREATED: {reportFileOutputPath}"); } - private static async Task DeserializeLoadTestResult(string filePath,string title) + private static async Task GetLoadtestErrorCountAsync(string filePath) + { + int ret=-1; + System.Console.WriteLine($" - PROCESSING ERROR ERROR COUNT FILE:{filePath}"); + try + { + if(!File.Exists(filePath)) + return "missing"; + + var json = await File.ReadAllTextAsync(filePath); + var tmp=JsonSerializer.Deserialize(json); + if(tmp!=null) + { + if(tmp.Datapoints!=null) + { + ret=0; + foreach(var i in tmp.Datapoints) + { + ret+=(int)(i.Sum??0); + } + } + } + else + return "invalid"; + } + catch(Exception ex) + { + System.Console.WriteLine($"FAILED TO PROCESS ERROR COUNT FILE!:{ex.Message}"); + System.Console.WriteLine($"{ex.ToString()}"); + return $"failed: {ex.Message}"; + } + return ret.ToString(); + } + + private static async Task DeserializeLoadTestResultAsync(string filePath,string title) { var json = await File.ReadAllTextAsync(filePath); @@ -264,12 +333,12 @@ private static async Task DeserializeLoadTestResult(string f if(ret.WarmStart==null) { - System.Console.WriteLine(" !!!! ATTENTION MISING WARM STATS !!!!"); + System.Console.WriteLine(" !!!! ATTENTION MISING WARM STATS !!!!"); } if(ret.ColdStart==null) { - System.Console.WriteLine(" !!!! ATTENTION MISING COLD STATS !!!!"); + System.Console.WriteLine(" !!!! ATTENTION MISING COLD STATS !!!!"); } return ret; @@ -290,4 +359,32 @@ private static string FormatCount(string value) return value; return ($"{valueNr:#,##0}"); } + + private static void SendSNSMsg(string snsTopicArn,string subject,string msg) + { + Console.WriteLine($"SENDING SNS MSG: {snsTopicArn}"); + try + { + using(var snsClient = new AmazonSimpleNotificationServiceClient()) + { + var request = new PublishRequest + { + TopicArn = snsTopicArn, + Subject=subject + Message = msg, + }; + + var t = snsClient.PublishAsync(request); + var result=t.Result; + + Console.WriteLine($"SNS Message Published ID:{result.MessageId}"); + + } + } + catch(Exception ex) + { + System.Console.WriteLine($"FAILED TO SEND SNS MSG!:{ex.Message}"); + System.Console.WriteLine($"{ex.ToString()}"); + } + } } \ No newline at end of file diff --git a/loadtestcli/Samples/NET6/Report/load-test-errors-arm64.json b/loadtestcli/Samples/NET6/Report/load-test-errors-arm64.json new file mode 100644 index 0000000..4ea0b92 --- /dev/null +++ b/loadtestcli/Samples/NET6/Report/load-test-errors-arm64.json @@ -0,0 +1,10 @@ +{ + "Label": "Errors", + "Datapoints": [ + { + "Timestamp": "2023-11-15T12:29:00+00:00", + "Sum": 0.0, + "Unit": "Count" + } + ] +} diff --git a/loadtestcli/Samples/NET6/Report/load-test-errors-x86.json b/loadtestcli/Samples/NET6/Report/load-test-errors-x86.json new file mode 100644 index 0000000..15a60ff --- /dev/null +++ b/loadtestcli/Samples/NET6/Report/load-test-errors-x86.json @@ -0,0 +1,10 @@ +{ + "Label": "Errors", + "Datapoints": [ + { + "Timestamp": "2023-11-15T12:27:00+00:00", + "Sum": 0.0, + "Unit": "Count" + } + ] +} diff --git a/loadtestcli/Samples/NET6CustomRuntime/Report/load-test-errors-arm64.json b/loadtestcli/Samples/NET6CustomRuntime/Report/load-test-errors-arm64.json new file mode 100644 index 0000000..4cecab3 --- /dev/null +++ b/loadtestcli/Samples/NET6CustomRuntime/Report/load-test-errors-arm64.json @@ -0,0 +1,10 @@ +{ + "Label": "Errors", + "Datapoints": [ + { + "Timestamp": "2023-11-15T12:29:00+00:00", + "Sum": 100.0, + "Unit": "Count" + } + ] +} diff --git a/loadtestcli/Samples/NET6CustomRuntime/Report/load-test-errors-x86.json b/loadtestcli/Samples/NET6CustomRuntime/Report/load-test-errors-x86.json new file mode 100644 index 0000000..c746834 --- /dev/null +++ b/loadtestcli/Samples/NET6CustomRuntime/Report/load-test-errors-x86.json @@ -0,0 +1,15 @@ +{ + "Label": "Errors", + "Datapoints": [ + { + "Timestamp": "2023-11-15T12:27:00+00:00", + "Sum": 0.0, + "Unit": "Count" + }, + { + "Timestamp": "2023-11-15T12:27:00+00:00", + "Sum": 20.0, + "Unit": "Count" + } + ] +} diff --git a/loadtestcli/Samples/NET6MinimalAPI/Report/load-test-errors-arm64.json b/loadtestcli/Samples/NET6MinimalAPI/Report/load-test-errors-arm64.json new file mode 100644 index 0000000..2c63c08 --- /dev/null +++ b/loadtestcli/Samples/NET6MinimalAPI/Report/load-test-errors-arm64.json @@ -0,0 +1,2 @@ +{ +} diff --git a/loadtestcli/Samples/NET6MinimalAPI/Report/load-test-errors-x86.json b/loadtestcli/Samples/NET6MinimalAPI/Report/load-test-errors-x86.json new file mode 100644 index 0000000..2c63c08 --- /dev/null +++ b/loadtestcli/Samples/NET6MinimalAPI/Report/load-test-errors-x86.json @@ -0,0 +1,2 @@ +{ +} diff --git a/loadtestcli/Samples/NET6MinimalAPIWebAdapter/Report/load-test-errors-arm64.json b/loadtestcli/Samples/NET6MinimalAPIWebAdapter/Report/load-test-errors-arm64.json new file mode 100644 index 0000000..e69de29 diff --git a/loadtestcli/Samples/NET6MinimalAPIWebAdapter/Report/load-test-errors-x86.json b/loadtestcli/Samples/NET6MinimalAPIWebAdapter/Report/load-test-errors-x86.json new file mode 100644 index 0000000..e69de29 diff --git a/loadtestcli/Samples/NET6Native/Report/load-test-errors-arm64.json b/loadtestcli/Samples/NET6Native/Report/load-test-errors-arm64.json new file mode 100644 index 0000000..e466dcb --- /dev/null +++ b/loadtestcli/Samples/NET6Native/Report/load-test-errors-arm64.json @@ -0,0 +1 @@ +invalid \ No newline at end of file diff --git a/loadtestcli/Samples/NET6Native/Report/load-test-errors-x86.json b/loadtestcli/Samples/NET6Native/Report/load-test-errors-x86.json new file mode 100644 index 0000000..e466dcb --- /dev/null +++ b/loadtestcli/Samples/NET6Native/Report/load-test-errors-x86.json @@ -0,0 +1 @@ +invalid \ No newline at end of file diff --git a/loadtestcli/Samples/NET6TopLevelStatements/Report/load-test-errors-arm64.json b/loadtestcli/Samples/NET6TopLevelStatements/Report/load-test-errors-arm64.json new file mode 100644 index 0000000..4ea0b92 --- /dev/null +++ b/loadtestcli/Samples/NET6TopLevelStatements/Report/load-test-errors-arm64.json @@ -0,0 +1,10 @@ +{ + "Label": "Errors", + "Datapoints": [ + { + "Timestamp": "2023-11-15T12:29:00+00:00", + "Sum": 0.0, + "Unit": "Count" + } + ] +} diff --git a/loadtestcli/Samples/NET6TopLevelStatements/Report/load-test-errors-x86.json b/loadtestcli/Samples/NET6TopLevelStatements/Report/load-test-errors-x86.json new file mode 100644 index 0000000..54147f5 --- /dev/null +++ b/loadtestcli/Samples/NET6TopLevelStatements/Report/load-test-errors-x86.json @@ -0,0 +1,15 @@ +{ + "Label": "Errors", + "Datapoints": [ + { + "Timestamp": "2023-11-15T12:27:00+00:00", + "Sum": 0.0, + "Unit": "Count" + }, + { + "Timestamp": "2023-11-15T12:27:00+00:00", + "Sum": 0.0, + "Unit": "Count" + } + ] +} diff --git a/src/NET6/run-loadtest.sh b/src/NET6/run-loadtest.sh index 981b6cd..054f7ac 100755 --- a/src/NET6/run-loadtest.sh +++ b/src/NET6/run-loadtest.sh @@ -1,7 +1,8 @@ #Arguments: #$1 - load test duration in seconds #$2 - log interval to be used in the cloudwatch query in minutes -#$3 - when equal to 1 cloudwatch log group will be deleted to ensure that only logs of the load test will be evaluated for stat +#$3 - when equal to yes cloudwatch log group will be deleted to ensure that only logs of the load test will be evaluated for stat +#$4 - ARN of sns topic to notify test results STACK_NAME=dotnet6 TEST_DURATIOMN_SEC=60 @@ -26,11 +27,17 @@ then LOG_DELETE=$3 fi +if [ "x$4" != x ]; +then + SNS_TOPIC_ARN=$4 +fi + echo "${COLOR}" echo -------------------------------------------- echo DURATION:$TEST_DURATIOMN_SEC echo LOG INTERVAL:$LOG_INTERVAL_MIN echo LOG_DELETE: $LOG_DELETE +echo SNS_TOPIC_ARN: $SNS_TOPIC_ARN echo -------------------------------------------- echo "${NO_COLOR}" @@ -120,6 +127,18 @@ function RunLoadTest() echo Log start:$startdate end:$enddate echo -------------------------------------------- + echo -------------------------------------------- + echo GET ERROR METRICS $1 + echo -------------------------------------------- + aws cloudwatch get-metric-statistics \ + --namespace AWS/Lambda \ + --metric-name Errors \ + --dimensions Name=FunctionName,Value=$LAMBDA_GETPRODUCTS Name=FunctionName,Value=$LAMBDA_GETPRODUCT Name=FunctionName,Value=$LAMBDA_DELETEPRODUCT Name=FunctionName,Value=$LAMBDA_PUTPRODUCT \ + --statistics Sum --period 43200 \ + --start-time $startdate --end-time $enddate > ./Report/load-test-errors-$1.json + + cat ./Report/load-test-errors-$1.json + QUERY_ID=$(aws logs start-query \ --log-group-names "/aws/lambda/$LAMBDA_GETPRODUCTS" "/aws/lambda/$LAMBDA_GETPRODUCT" "/aws/lambda/$LAMBDA_DELETEPRODUCT" "/aws/lambda/$LAMBDA_PUTPRODUCT" \ --start-time $startdate \ @@ -147,6 +166,16 @@ function RunLoadTest() aws logs get-query-results --query-id $QUERY_ID --output text >> ./Report/load-test-report-$1.txt cat ./Report/load-test-report-$1.txt aws logs get-query-results --query-id $QUERY_ID --output json >> ./Report/load-test-report-$1.json + + if [ "x$SNS_TOPIC_ARN" != x ]; + then + echo -------------------------------------------- + echo Sending message to sns topic: $SNS_TOPIC_ARN + echo -------------------------------------------- + msg=$(<./Report/load-test-report-$1.txt)\n\n$(<./Report/load-test-errors-$1.json) + subject="serverless dotnet demo load test result for $LAMBDA_GETPRODUCTS" + aws sns publish --topic-arn $SNS_TOPIC_ARN --subject "$subject" --message "$msg" + fi } RunLoadTest x86 ApiUrlX86 LambdaX86NameGetProducts LambdaX86NameGetProduct LambdaX86NameDeleteProduct LambdaX86NamePutProduct diff --git a/src/NET6Containers/run-loadtest.sh b/src/NET6Containers/run-loadtest.sh index ab537dc..3fd4dbd 100755 --- a/src/NET6Containers/run-loadtest.sh +++ b/src/NET6Containers/run-loadtest.sh @@ -1,7 +1,8 @@ #Arguments: #$1 - load test duration in seconds #$2 - log interval to be used in the cloudwatch query in minutes -#$3 - when equal to 1 cloudwatch log group will be deleted to ensure that only logs of the load test will be evaluated for stat +#$3 - when equal to yes cloudwatch log group will be deleted to ensure that only logs of the load test will be evaluated for stat +#$4 - ARN of sns topic to notify test results STACK_NAME=dotnet6-container TEST_DURATIOMN_SEC=60 @@ -26,11 +27,17 @@ then LOG_DELETE=$3 fi +if [ "x$4" != x ]; +then + SNS_TOPIC_ARN=$4 +fi + echo "${COLOR}" echo -------------------------------------------- echo DURATION:$TEST_DURATIOMN_SEC echo LOG INTERVAL:$LOG_INTERVAL_MIN echo LOG_DELETE: $LOG_DELETE +echo SNS_TOPIC_ARN: $SNS_TOPIC_ARN echo -------------------------------------------- echo "${NO_COLOR}" @@ -120,6 +127,17 @@ function RunLoadTest() echo Log start:$startdate end:$enddate echo -------------------------------------------- + echo -------------------------------------------- + echo GET ERROR METRICS $1 + echo -------------------------------------------- + aws cloudwatch get-metric-statistics \ + --namespace AWS/Lambda \ + --metric-name Errors \ + --dimensions Name=FunctionName,Value=$LAMBDA_GETPRODUCTS Name=FunctionName,Value=$LAMBDA_GETPRODUCT Name=FunctionName,Value=$LAMBDA_DELETEPRODUCT Name=FunctionName,Value=$LAMBDA_PUTPRODUCT \ + --statistics Sum --period 43200 \ + --start-time $startdate --end-time $enddate > ./Report/load-test-errors-$1.json + + cat ./Report/load-test-errors-$1.json QUERY_ID=$(aws logs start-query \ --log-group-names "/aws/lambda/$LAMBDA_GETPRODUCTS" "/aws/lambda/$LAMBDA_GETPRODUCT" "/aws/lambda/$LAMBDA_DELETEPRODUCT" "/aws/lambda/$LAMBDA_PUTPRODUCT" \ --start-time $startdate \ @@ -147,6 +165,16 @@ function RunLoadTest() aws logs get-query-results --query-id $QUERY_ID --output text >> ./Report/load-test-report-$1.txt cat ./Report/load-test-report-$1.txt aws logs get-query-results --query-id $QUERY_ID --output json >> ./Report/load-test-report-$1.json + + if [ "x$SNS_TOPIC_ARN" != x ]; + then + echo -------------------------------------------- + echo Sending message to sns topic: $SNS_TOPIC_ARN + echo -------------------------------------------- + msg=$(<./Report/load-test-report-$1.txt)\n\n$(<./Report/load-test-errors-$1.json) + subject="serverless dotnet demo load test result for $LAMBDA_GETPRODUCTS" + aws sns publish --topic-arn $SNS_TOPIC_ARN --subject "$subject" --message "$msg" + fi } RunLoadTest x86 ApiUrlX86 LambdaX86NameGetProducts LambdaX86NameGetProduct LambdaX86NameDeleteProduct LambdaX86NamePutProduct \ No newline at end of file diff --git a/src/NET6CustomRuntime/run-loadtest.sh b/src/NET6CustomRuntime/run-loadtest.sh index 8c5fb05..35d335d 100755 --- a/src/NET6CustomRuntime/run-loadtest.sh +++ b/src/NET6CustomRuntime/run-loadtest.sh @@ -1,7 +1,8 @@ #Arguments: #$1 - load test duration in seconds #$2 - log interval to be used in the cloudwatch query in minutes -#$3 - when equal to 1 cloudwatch log group will be deleted to ensure that only logs of the load test will be evaluated for stat +#$3 - when equal to yes cloudwatch log group will be deleted to ensure that only logs of the load test will be evaluated for stat +#$4 - ARN of sns topic to notify test results STACK_NAME=dotnet6-custom TEST_DURATIOMN_SEC=60 @@ -26,11 +27,17 @@ then LOG_DELETE=$3 fi +if [ "x$4" != x ]; +then + SNS_TOPIC_ARN=$4 +fi + echo "${COLOR}" echo -------------------------------------------- echo DURATION:$TEST_DURATIOMN_SEC echo LOG INTERVAL:$LOG_INTERVAL_MIN echo LOG_DELETE: $LOG_DELETE +echo SNS_TOPIC_ARN: $SNS_TOPIC_ARN echo -------------------------------------------- echo "${NO_COLOR}" @@ -120,6 +127,17 @@ function RunLoadTest() echo Log start:$startdate end:$enddate echo -------------------------------------------- + echo -------------------------------------------- + echo GET ERROR METRICS $1 + echo -------------------------------------------- + aws cloudwatch get-metric-statistics \ + --namespace AWS/Lambda \ + --metric-name Errors \ + --dimensions Name=FunctionName,Value=$LAMBDA_GETPRODUCTS Name=FunctionName,Value=$LAMBDA_GETPRODUCT Name=FunctionName,Value=$LAMBDA_DELETEPRODUCT Name=FunctionName,Value=$LAMBDA_PUTPRODUCT \ + --statistics Sum --period 43200 \ + --start-time $startdate --end-time $enddate > ./Report/load-test-errors-$1.json + + cat ./Report/load-test-errors-$1.json QUERY_ID=$(aws logs start-query \ --log-group-names "/aws/lambda/$LAMBDA_GETPRODUCTS" "/aws/lambda/$LAMBDA_GETPRODUCT" "/aws/lambda/$LAMBDA_DELETEPRODUCT" "/aws/lambda/$LAMBDA_PUTPRODUCT" \ --start-time $startdate \ @@ -147,6 +165,16 @@ function RunLoadTest() aws logs get-query-results --query-id $QUERY_ID --output text >> ./Report/load-test-report-$1.txt cat ./Report/load-test-report-$1.txt aws logs get-query-results --query-id $QUERY_ID --output json >> ./Report/load-test-report-$1.json + + if [ "x$SNS_TOPIC_ARN" != x ]; + then + echo -------------------------------------------- + echo Sending message to sns topic: $SNS_TOPIC_ARN + echo -------------------------------------------- + msg=$(<./Report/load-test-report-$1.txt)\n\n$(<./Report/load-test-errors-$1.json) + subject="serverless dotnet demo load test result for $LAMBDA_GETPRODUCTS" + aws sns publish --topic-arn $SNS_TOPIC_ARN --subject "$subject" --message "$msg" + fi } RunLoadTest x86 ApiUrlX86 LambdaX86NameGetProducts LambdaX86NameGetProduct LambdaX86NameDeleteProduct LambdaX86NamePutProduct diff --git a/src/NET6MinimalAPI/run-loadtest.sh b/src/NET6MinimalAPI/run-loadtest.sh index 6b8ed6d..ca2a1c3 100755 --- a/src/NET6MinimalAPI/run-loadtest.sh +++ b/src/NET6MinimalAPI/run-loadtest.sh @@ -1,7 +1,8 @@ #Arguments: #$1 - load test duration in seconds #$2 - log interval to be used in the cloudwatch query in minutes -#$3 - when equal to 1 cloudwatch log group will be deleted to ensure that only logs of the load test will be evaluated for stat +#$3 - when equal to yes cloudwatch log group will be deleted to ensure that only logs of the load test will be evaluated for stat +#$4 - ARN of sns topic to notify test results STACK_NAME=dotnet6-minimal-api TEST_DURATIOMN_SEC=60 @@ -26,15 +27,21 @@ then LOG_DELETE=$3 fi +if [ "x$4" != x ]; +then + SNS_TOPIC_ARN=$4 +fi + echo "${COLOR}" echo -------------------------------------------- echo DURATION:$TEST_DURATIOMN_SEC echo LOG INTERVAL:$LOG_INTERVAL_MIN echo LOG_DELETE: $LOG_DELETE +echo SNS_TOPIC_ARN: $SNS_TOPIC_ARN echo -------------------------------------------- echo "${NO_COLOR}" -mkdir -p Report +mkdir -p Report function RunLoadTest() { @@ -90,6 +97,16 @@ function RunLoadTest() echo Log start:$startdate end:$enddate echo -------------------------------------------- + echo -------------------------------------------- + echo GET ERROR METRICS $1 + echo -------------------------------------------- + aws cloudwatch get-metric-statistics \ + --namespace AWS/Lambda \ + --metric-name Errors \ + --dimensions Name=FunctionName,Value=$LAMBDA \ + --statistics Sum --period 43200 \ + --start-time $startdate --end-time $enddate > ./Report/load-test-errors-$1.json + QUERY_ID=$(aws logs start-query \ --log-group-name /aws/lambda/$LAMBDA \ --start-time $startdate \ @@ -117,6 +134,16 @@ function RunLoadTest() aws logs get-query-results --query-id $QUERY_ID --output text >> ./Report/load-test-report-$1.txt cat ./Report/load-test-report-$1.txt aws logs get-query-results --query-id $QUERY_ID --output json >> ./Report/load-test-report-$1.json + + if [ "x$SNS_TOPIC_ARN" != x ]; + then + echo -------------------------------------------- + echo Sending message to sns topic: $SNS_TOPIC_ARN + echo -------------------------------------------- + msg=$(<./Report/load-test-report-$1.txt)\n\n$(<./Report/load-test-errors-$1.json) + subject="serverless dotnet demo load test result for $LAMBDA" + aws sns publish --topic-arn $SNS_TOPIC_ARN --subject "$subject" --message "$msg" + fi } RunLoadTest x86 ApiUrlX86 LambdaX86Name diff --git a/src/NET6MinimalAPIWebAdapter/run-loadtest.sh b/src/NET6MinimalAPIWebAdapter/run-loadtest.sh index 328efae..2242a7f 100755 --- a/src/NET6MinimalAPIWebAdapter/run-loadtest.sh +++ b/src/NET6MinimalAPIWebAdapter/run-loadtest.sh @@ -1,7 +1,9 @@ #Arguments: #$1 - load test duration in seconds #$2 - log interval to be used in the cloudwatch query in minutes -#$3 - when equal to 1 cloudwatch log group will be deleted to ensure that only logs of the load test will be evaluated for stat +#$3 - when equal to yes cloudwatch log group will be deleted to ensure that only logs of the load test will be evaluated for stat +#$4 - ARN of sns topic to notify test results + STACK_NAME=dotnet6-minimal-api-web-adapter TEST_DURATIOMN_SEC=60 LOG_INTERVAL_MIN=20 @@ -25,11 +27,17 @@ then LOG_DELETE=$3 fi +if [ "x$4" != x ]; +then + SNS_TOPIC_ARN=$4 +fi + echo "${COLOR}" echo -------------------------------------------- echo DURATION:$TEST_DURATIOMN_SEC echo LOG INTERVAL:$LOG_INTERVAL_MIN echo LOG_DELETE: $LOG_DELETE +echo SNS_TOPIC_ARN: $SNS_TOPIC_ARN echo -------------------------------------------- echo "${NO_COLOR}" @@ -89,6 +97,16 @@ function RunLoadTest() echo Log start:$startdate end:$enddate echo -------------------------------------------- + echo -------------------------------------------- + echo GET ERROR METRICS $1 + echo -------------------------------------------- + aws cloudwatch get-metric-statistics \ + --namespace AWS/Lambda \ + --metric-name Errors \ + --dimensions Name=FunctionName,Value=$LAMBDA \ + --statistics Sum --period 43200 \ + --start-time $startdate --end-time $enddate > ./Report/load-test-errors-$1.json + QUERY_ID=$(aws logs start-query \ --log-group-name /aws/lambda/$LAMBDA \ --start-time $startdate \ @@ -116,6 +134,16 @@ function RunLoadTest() aws logs get-query-results --query-id $QUERY_ID --output text >> ./Report/load-test-report-$1.txt cat ./Report/load-test-report-$1.txt aws logs get-query-results --query-id $QUERY_ID --output json >> ./Report/load-test-report-$1.json + + if [ "x$SNS_TOPIC_ARN" != x ]; + then + echo -------------------------------------------- + echo Sending message to sns topic: $SNS_TOPIC_ARN + echo -------------------------------------------- + msg=$(<./Report/load-test-report-$1.txt)\n\n$(<./Report/load-test-errors-$1.json) + subject="serverless dotnet demo load test result for $LAMBDA" + aws sns publish --topic-arn $SNS_TOPIC_ARN --subject "$subject" --message "$msg" + fi } RunLoadTest x86 ApiUrlX86 LambdaX86Name diff --git a/src/NET6Native/DeleteProduct/DeleteProduct.csproj b/src/NET6Native/DeleteProduct/DeleteProduct.csproj deleted file mode 100755 index 8ef9370..0000000 --- a/src/NET6Native/DeleteProduct/DeleteProduct.csproj +++ /dev/null @@ -1,23 +0,0 @@ - - - - Exe - net6.0 - bootstrap - true - - - - - - - - - - - - - - - - diff --git a/src/NET6Native/DeleteProduct/Function.cs b/src/NET6Native/DeleteProduct/Function.cs deleted file mode 100755 index 92c0989..0000000 --- a/src/NET6Native/DeleteProduct/Function.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net; -using System.Net.Http; -using System.Text.Json.Serialization; -using Amazon.Lambda.APIGatewayEvents; -using Amazon.Lambda.Core; -using Amazon.Lambda.RuntimeSupport; -using Amazon.Lambda.Serialization.SystemTextJson; -using Amazon.XRay.Recorder.Handlers.AwsSdk; -using Shared.DataAccess; - -[assembly: LambdaSerializer(typeof(SourceGeneratorLambdaJsonSerializer))] - -AWSSDKHandler.RegisterXRayForAllServices(); -ProductsDAO dataAccess = new DynamoDbProducts(); - -var handler = async (APIGatewayHttpApiV2ProxyRequest apigProxyEvent, ILambdaContext context) => -{ - if (!apigProxyEvent.RequestContext.Http.Method.Equals(HttpMethod.Delete.Method)) - { - return new APIGatewayHttpApiV2ProxyResponse - { - Body = "Only DELETE allowed", - StatusCode = (int)HttpStatusCode.MethodNotAllowed, - }; - } - - try - { - var id = apigProxyEvent.PathParameters["id"]; - - var product = await dataAccess.GetProduct(id); - - if (product == null) - { - return new APIGatewayHttpApiV2ProxyResponse - { - Body = "Not Found", - StatusCode = (int)HttpStatusCode.NotFound, - }; - } - - await dataAccess.DeleteProduct(product.Id); - - return new APIGatewayHttpApiV2ProxyResponse - { - StatusCode = (int)HttpStatusCode.OK, - Body = $"Product with id {id} deleted" - }; - } - catch (Exception e) - { - context.Logger.LogError($"Error deleting product {e.Message} {e.StackTrace}"); - - return new APIGatewayHttpApiV2ProxyResponse - { - Body = "Not Found", - StatusCode = (int)HttpStatusCode.InternalServerError, - }; - } -}; - -await LambdaBootstrapBuilder.Create(handler, new DefaultLambdaJsonSerializer()) - .Build() - .RunAsync(); - -[JsonSerializable(typeof(APIGatewayHttpApiV2ProxyRequest))] -[JsonSerializable(typeof(APIGatewayHttpApiV2ProxyResponse))] -[JsonSerializable(typeof(Dictionary))] -public partial class ApiGatewayProxyJsonSerializerContext : JsonSerializerContext -{ -} \ No newline at end of file diff --git a/src/NET6Native/GetProduct/Function.cs b/src/NET6Native/GetProduct/Function.cs deleted file mode 100755 index 7d5e3e1..0000000 --- a/src/NET6Native/GetProduct/Function.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net; -using System.Net.Http; -using System.Text.Json; -using System.Text.Json.Serialization; -using Amazon.Lambda.APIGatewayEvents; -using Amazon.Lambda.Core; -using Amazon.Lambda.RuntimeSupport; -using Amazon.Lambda.Serialization.SystemTextJson; -using Amazon.XRay.Recorder.Handlers.AwsSdk; -using Shared.DataAccess; - -[assembly: LambdaSerializer(typeof(SourceGeneratorLambdaJsonSerializer))] - -AWSSDKHandler.RegisterXRayForAllServices(); -ProductsDAO dataAccess = new DynamoDbProducts(); - -var handler = async (APIGatewayHttpApiV2ProxyRequest apigProxyEvent, ILambdaContext context) => -{ - if (!apigProxyEvent.RequestContext.Http.Method.Equals(HttpMethod.Get.Method)) - { - return new APIGatewayHttpApiV2ProxyResponse - { - Body = "Only GET allowed", - StatusCode = (int)HttpStatusCode.MethodNotAllowed, - }; - } - - try - { - var id = apigProxyEvent.PathParameters["id"]; - - var product = await dataAccess.GetProduct(id); - - if (product == null) - { - return new APIGatewayHttpApiV2ProxyResponse - { - Body = "Not Found", - StatusCode = (int)HttpStatusCode.NotFound, - }; - } - - return new APIGatewayHttpApiV2ProxyResponse - { - StatusCode = (int)HttpStatusCode.OK, - Body = JsonSerializer.Serialize(product), - Headers = new Dictionary {{"Content-Type", "application/json"}} - }; - } - catch (Exception e) - { - context.Logger.LogError($"Error getting product {e.Message} {e.StackTrace}"); - - return new APIGatewayHttpApiV2ProxyResponse - { - Body = "Not Found", - StatusCode = (int)HttpStatusCode.InternalServerError, - }; - } -}; - -await LambdaBootstrapBuilder.Create(handler, new DefaultLambdaJsonSerializer()) - .Build() - .RunAsync(); - -[JsonSerializable(typeof(APIGatewayHttpApiV2ProxyRequest))] -[JsonSerializable(typeof(APIGatewayHttpApiV2ProxyResponse))] -[JsonSerializable(typeof(Dictionary))] -public partial class ApiGatewayProxyJsonSerializerContext : JsonSerializerContext -{ -} diff --git a/src/NET6Native/GetProduct/GetProduct.csproj b/src/NET6Native/GetProduct/GetProduct.csproj deleted file mode 100755 index 8ef9370..0000000 --- a/src/NET6Native/GetProduct/GetProduct.csproj +++ /dev/null @@ -1,23 +0,0 @@ - - - - Exe - net6.0 - bootstrap - true - - - - - - - - - - - - - - - - diff --git a/src/NET6Native/GetProducts/Function.cs b/src/NET6Native/GetProducts/Function.cs deleted file mode 100755 index f19d6f1..0000000 --- a/src/NET6Native/GetProducts/Function.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System.Collections.Generic; -using System.Net; -using System.Net.Http; -using System.Text.Json; -using System.Text.Json.Serialization; -using Amazon.Lambda.APIGatewayEvents; -using Amazon.Lambda.Core; -using Amazon.Lambda.RuntimeSupport; -using Amazon.Lambda.Serialization.SystemTextJson; -using Amazon.XRay.Recorder.Handlers.AwsSdk; -using Shared.DataAccess; - -[assembly: LambdaSerializer(typeof(SourceGeneratorLambdaJsonSerializer))] - -AWSSDKHandler.RegisterXRayForAllServices(); -ProductsDAO dataAccess = new DynamoDbProducts(); - -var handler = async (APIGatewayHttpApiV2ProxyRequest apigProxyEvent, ILambdaContext context) => -{ - if (!apigProxyEvent.RequestContext.Http.Method.Equals(HttpMethod.Get.Method)) - { - return new APIGatewayHttpApiV2ProxyResponse - { - Body = "Only GET allowed", - StatusCode = (int)HttpStatusCode.MethodNotAllowed, - }; - } - - context.Logger.LogInformation($"Received {apigProxyEvent}"); - - var products = await dataAccess.GetAllProducts(); - - context.Logger.LogInformation($"Found {products.Products.Count} product(s)"); - - return new APIGatewayHttpApiV2ProxyResponse - { - Body = JsonSerializer.Serialize(products), - StatusCode = 200, - Headers = new Dictionary {{"Content-Type", "application/json"}} - }; -}; - -await LambdaBootstrapBuilder.Create(handler, new DefaultLambdaJsonSerializer()) - .Build() - .RunAsync(); - -[JsonSerializable(typeof(APIGatewayHttpApiV2ProxyRequest))] -[JsonSerializable(typeof(APIGatewayHttpApiV2ProxyResponse))] -[JsonSerializable(typeof(List))] -[JsonSerializable(typeof(Dictionary))] -public partial class ApiGatewayProxyJsonSerializerContext : JsonSerializerContext -{ -} diff --git a/src/NET6Native/GetProducts/GetProducts.csproj b/src/NET6Native/GetProducts/GetProducts.csproj deleted file mode 100755 index 8ef9370..0000000 --- a/src/NET6Native/GetProducts/GetProducts.csproj +++ /dev/null @@ -1,23 +0,0 @@ - - - - Exe - net6.0 - bootstrap - true - - - - - - - - - - - - - - - - diff --git a/src/NET6Native/Makefile b/src/NET6Native/Makefile deleted file mode 100644 index 556eded..0000000 --- a/src/NET6Native/Makefile +++ /dev/null @@ -1,23 +0,0 @@ -build-GetProductFunctionX86: - dotnet publish GetProduct/GetProduct.csproj -c Release -r linux-x64 --self-contained -o $(ARTIFACTS_DIR) - -build-GetProductsFunctionX86: - dotnet publish GetProducts/GetProducts.csproj -c Release -r linux-x64 --self-contained -o $(ARTIFACTS_DIR) - -build-PutProductFunctionX86: - dotnet publish PutProduct/PutProduct.csproj -c Release -r linux-x64 --self-contained -o $(ARTIFACTS_DIR) - -build-DeleteProductFunctionX86: - dotnet publish DeleteProduct/DeleteProduct.csproj -c Release -r linux-x64 --self-contained -o $(ARTIFACTS_DIR) - -build-GetProductFunctionArm64: - dotnet publish GetProduct/GetProduct.csproj -c Release -r linux-x64 --self-contained -o $(ARTIFACTS_DIR) - -build-GetProductsFunctionArm64: - dotnet publish GetProducts/GetProducts.csproj -c Release -r linux-x64 --self-contained -o $(ARTIFACTS_DIR) - -build-PutProductFunctionArm64: - dotnet publish PutProduct/PutProduct.csproj -c Release -r linux-x64 --self-contained -o $(ARTIFACTS_DIR) - -build-DeleteProductFunctionArm64: - dotnet publish DeleteProduct/DeleteProduct.csproj -c Release -r linux-x64 --self-contained -o $(ARTIFACTS_DIR) \ No newline at end of file diff --git a/src/NET6Native/PutProduct/Function.cs b/src/NET6Native/PutProduct/Function.cs deleted file mode 100755 index 85b2341..0000000 --- a/src/NET6Native/PutProduct/Function.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net; -using System.Net.Http; -using System.Text.Json; -using System.Text.Json.Serialization; -using Amazon.Lambda.APIGatewayEvents; -using Amazon.Lambda.Core; -using Amazon.Lambda.RuntimeSupport; -using Amazon.Lambda.Serialization.SystemTextJson; -using Amazon.XRay.Recorder.Handlers.AwsSdk; -using Shared.DataAccess; -using Shared.Models; - -[assembly: LambdaSerializer(typeof(SourceGeneratorLambdaJsonSerializer))] - -AWSSDKHandler.RegisterXRayForAllServices(); -ProductsDAO dataAccess = new DynamoDbProducts(); - -var handler = async (APIGatewayHttpApiV2ProxyRequest apigProxyEvent, ILambdaContext context) => -{ - if (!apigProxyEvent.RequestContext.Http.Method.Equals(HttpMethod.Put.Method)) - { - return new APIGatewayHttpApiV2ProxyResponse - { - Body = "Only PUT allowed", - StatusCode = (int)HttpStatusCode.MethodNotAllowed, - }; - } - - try - { - var id = apigProxyEvent.PathParameters["id"]; - - var product = JsonSerializer.Deserialize(apigProxyEvent.Body); - - if (product == null || id != product.Id) - { - return new APIGatewayHttpApiV2ProxyResponse - { - Body = "Product ID in the body does not match path parameter", - StatusCode = (int)HttpStatusCode.BadRequest, - }; - } - - await dataAccess.PutProduct(product); - - return new APIGatewayHttpApiV2ProxyResponse - { - StatusCode = (int)HttpStatusCode.Created, - Body = $"Created product with id {id}" - }; - } - catch (Exception e) - { - context.Logger.LogError($"Error creating product {e.Message} {e.StackTrace}"); - - return new APIGatewayHttpApiV2ProxyResponse - { - Body = "Not Found", - StatusCode = (int)HttpStatusCode.InternalServerError, - }; - } -}; - -await LambdaBootstrapBuilder.Create(handler, new DefaultLambdaJsonSerializer()) - .Build() - .RunAsync(); - -[JsonSerializable(typeof(APIGatewayHttpApiV2ProxyRequest))] -[JsonSerializable(typeof(APIGatewayHttpApiV2ProxyResponse))] -[JsonSerializable(typeof(Dictionary))] -public partial class ApiGatewayProxyJsonSerializerContext : JsonSerializerContext -{ -} diff --git a/src/NET6Native/PutProduct/PutProduct.csproj b/src/NET6Native/PutProduct/PutProduct.csproj deleted file mode 100755 index 8ef9370..0000000 --- a/src/NET6Native/PutProduct/PutProduct.csproj +++ /dev/null @@ -1,23 +0,0 @@ - - - - Exe - net6.0 - bootstrap - true - - - - - - - - - - - - - - - - diff --git a/src/NET6Native/Shared/DataAccess/DynamoDbProducts.cs b/src/NET6Native/Shared/DataAccess/DynamoDbProducts.cs deleted file mode 100755 index 9feb6e2..0000000 --- a/src/NET6Native/Shared/DataAccess/DynamoDbProducts.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Amazon.DynamoDBv2; -using Amazon.DynamoDBv2.Model; -using Shared.Models; - -namespace Shared.DataAccess -{ - public class DynamoDbProducts : ProductsDAO - { - private static readonly string PRODUCT_TABLE_NAME = Environment.GetEnvironmentVariable("PRODUCT_TABLE_NAME") ?? string.Empty; - private readonly AmazonDynamoDBClient _dynamoDbClient; - - public DynamoDbProducts() - { - this._dynamoDbClient = new AmazonDynamoDBClient(); - } - - public async Task GetProduct(string id) - { - var getItemResponse = await this._dynamoDbClient.GetItemAsync(new GetItemRequest(PRODUCT_TABLE_NAME, - new Dictionary(1) - { - {ProductMapper.PK, new AttributeValue(id)} - })); - - return getItemResponse.IsItemSet ? ProductMapper.ProductFromDynamoDB(getItemResponse.Item) : null; - } - - public async Task PutProduct(Product product) - { - await this._dynamoDbClient.PutItemAsync(PRODUCT_TABLE_NAME, ProductMapper.ProductToDynamoDb(product)); - } - - public async Task DeleteProduct(string id) - { - await this._dynamoDbClient.DeleteItemAsync(PRODUCT_TABLE_NAME, new Dictionary(1) - { - {ProductMapper.PK, new AttributeValue(id)} - }); - } - - public async Task GetAllProducts() - { - var data = await this._dynamoDbClient.ScanAsync(new ScanRequest() - { - TableName = PRODUCT_TABLE_NAME, - Limit = 20 - }); - - var products = new List(); - - foreach (var item in data.Items) - { - products.Add(ProductMapper.ProductFromDynamoDB(item)); - } - - return new ProductWrapper(products); - } - } -} \ No newline at end of file diff --git a/src/NET6Native/Shared/DataAccess/ProductMapper.cs b/src/NET6Native/Shared/DataAccess/ProductMapper.cs deleted file mode 100755 index 8e073ed..0000000 --- a/src/NET6Native/Shared/DataAccess/ProductMapper.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using Amazon.DynamoDBv2.Model; -using Shared.Models; - -namespace Shared.DataAccess -{ - public class ProductMapper - { - public static string PK = "id"; - public static string NAME = "name"; - public static string PRICE = "price"; - - public static Product ProductFromDynamoDB(Dictionary items) { - var product = new Product(items[PK].S, items[NAME].S, decimal.Parse(items[PRICE].N)); - - return product; - } - - public static Dictionary ProductToDynamoDb(Product product) { - Dictionary item = new Dictionary(3); - item.Add(PK, new AttributeValue(product.Id)); - item.Add(NAME, new AttributeValue(product.Name)); - item.Add(PRICE, new AttributeValue() - { - N = product.Price.ToString(CultureInfo.InvariantCulture) - }); - - return item; - } - } -} \ No newline at end of file diff --git a/src/NET6Native/Shared/DataAccess/ProductsDAO.cs b/src/NET6Native/Shared/DataAccess/ProductsDAO.cs deleted file mode 100755 index d09decd..0000000 --- a/src/NET6Native/Shared/DataAccess/ProductsDAO.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Threading.Tasks; -using Shared.Models; - -namespace Shared.DataAccess -{ - public interface ProductsDAO - { - Task GetProduct(string id); - - Task PutProduct(Product product); - - Task DeleteProduct(string id); - - Task GetAllProducts(); - } -} \ No newline at end of file diff --git a/src/NET6Native/Shared/Models/Product.cs b/src/NET6Native/Shared/Models/Product.cs deleted file mode 100755 index 8264cb7..0000000 --- a/src/NET6Native/Shared/Models/Product.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; - -namespace Shared.Models -{ - public class Product - { - public Product() - { - this.Id = string.Empty; - this.Name = string.Empty; - } - - public Product(string id, string name, decimal price) - { - this.Id = id; - this.Name = name; - this.Price = price; - } - - public string Id { get; set; } - - public string Name { get; set; } - - public decimal Price { get; private set; } - - public void SetPrice(decimal newPrice) - { - this.Price = Math.Round(newPrice, 2); - } - - public override string ToString() - { - return "Product{" + - "id='" + this.Id + '\'' + - ", name='" + this.Name + '\'' + - ", price=" + this.Price + - '}'; - } - } -} \ No newline at end of file diff --git a/src/NET6Native/Shared/Models/ProductWrapper.cs b/src/NET6Native/Shared/Models/ProductWrapper.cs deleted file mode 100755 index dab4592..0000000 --- a/src/NET6Native/Shared/Models/ProductWrapper.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Collections.Generic; - -namespace Shared.Models -{ - public class ProductWrapper - { - public ProductWrapper() - { - this.Products = new List(); - } - - public ProductWrapper(List products) - { - this.Products = products; - } - - public List Products { get; set; } - } -} \ No newline at end of file diff --git a/src/NET6Native/Shared/Shared.csproj b/src/NET6Native/Shared/Shared.csproj deleted file mode 100755 index fcdc7e6..0000000 --- a/src/NET6Native/Shared/Shared.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - - net6.0 - enable - enable - - - - - - - - - diff --git a/src/NET6Native/deploy.sh b/src/NET6Native/deploy.sh deleted file mode 100755 index 8f7cd88..0000000 --- a/src/NET6Native/deploy.sh +++ /dev/null @@ -1,43 +0,0 @@ -#Arguments: -#$1 - delete stack formation to ensure that all test have same conditon and favour similar ammount of cold start events - -STACK_NAME=dotnet6-native -DELETE_STACK=yes - -COLOR='\033[0;33m' -NO_COLOR='\033[0m' # No Color - -if [ "x$1" != x ]; -then - DELETE_STACK=$1 -fi - -echo "${COLOR}" -echo -------------------------------------------- -echo DELETE_STACK: $DELETE_STACK -echo -------------------------------------------- -echo "${NO_COLOR}" - -if [ $DELETE_STACK == "yes" ]; -then - echo "${COLOR}" - echo -------------------------------------------- - echo DELETING STACK $STACK_NAME - echo -------------------------------------------- - echo "${NO_COLOR}" - aws cloudformation delete-stack --stack-name $STACK_NAME - echo "${COLOR}" - echo --------------------------------------------- - echo Waiting stack to be deleted - echo -------------------------------------------- - echo "${NO_COLOR}" - aws cloudformation wait stack-delete-complete --stack-name $STACK_NAME - echo "${COLOR}" - echo --------------------------------------------- - echo Stack deleted - echo -------------------------------------------- - echo "${NO_COLOR}" -fi - -sam build -sam deploy --stack-name $STACK_NAME --resolve-s3 --s3-prefix $STACK_NAME --no-confirm-changeset --no-fail-on-empty-changeset --capabilities CAPABILITY_IAM \ No newline at end of file diff --git a/src/NET6Native/run-loadtest.sh b/src/NET6Native/run-loadtest.sh deleted file mode 100755 index 03b81e3..0000000 --- a/src/NET6Native/run-loadtest.sh +++ /dev/null @@ -1,153 +0,0 @@ -#Arguments: -#$1 - load test duration in seconds -#$2 - log interval to be used in the cloudwatch query in minutes -#$3 - when equal to 1 cloudwatch log group will be deleted to ensure that only logs of the load test will be evaluated for stat - -STACK_NAME=dotnet6-native -TEST_DURATIOMN_SEC=60 -LOG_INTERVAL_MIN=20 -LOG_DELETE=yes - -COLOR='\033[0;33m' -NO_COLOR='\033[0m' # No Color - -if [ "x$1" != x ]; -then - TEST_DURATIOMN_SEC=$1 -fi - -if [ "x$2" != x ]; -then - LOG_INTERVAL_MIN=$2 -fi - -if [ "x$3" != x ]; -then - LOG_DELETE=$3 -fi - -echo "${COLOR}" -echo -------------------------------------------- -echo DURATION:$TEST_DURATIOMN_SEC -echo LOG INTERVAL:$LOG_INTERVAL_MIN -echo LOG_DELETE: $LOG_DELETE -echo -------------------------------------------- -echo "${NO_COLOR}" - -mkdir -p Report - -function RunLoadTest() -{ - #Params: - #$1 - Architecture (x86 or arm64).Used for logging and naming report file - #$2 - Stack output name to get API Url - #$3 - Stack output name to get lambda name GetProducts - #$4 - Stack output name to get lambda name GetProduct - #$5 - Stack output name to get lambda name DeleteProduct - #$6 - Stack output name to get lambda name PutProduct - - #get test params from cloud formation output - echo "${COLOR}" - export API_URL=$(aws cloudformation describe-stacks --stack-name $STACK_NAME \ - --query "Stacks[0].Outputs[?OutputKey=='$2'].OutputValue" \ - --output text) - echo API URL: $API_URL - - LAMBDA_GETPRODUCTS=$(aws cloudformation describe-stacks --stack-name $STACK_NAME \ - --query "Stacks[0].Outputs[?OutputKey=='$3'].OutputValue" \ - --output text) - echo LAMBDA: $LAMBDA_GETPRODUCTS - - LAMBDA_GETPRODUCT=$(aws cloudformation describe-stacks --stack-name $STACK_NAME \ - --query "Stacks[0].Outputs[?OutputKey=='$4'].OutputValue" \ - --output text) - echo LAMBDA: $LAMBDA_GETPRODUCT - - LAMBDA_DELETEPRODUCT=$(aws cloudformation describe-stacks --stack-name $STACK_NAME \ - --query "Stacks[0].Outputs[?OutputKey=='$5'].OutputValue" \ - --output text) - echo LAMBDA: $LAMBDA_DELETEPRODUCT - - LAMBDA_PUTPRODUCT=$(aws cloudformation describe-stacks --stack-name $STACK_NAME \ - --query "Stacks[0].Outputs[?OutputKey=='$6'].OutputValue" \ - --output text) - echo LAMBDA: $LAMBDA_PUTPRODUCT - - if [ $LOG_DELETE == "yes" ]; - then - echo -------------------------------------------- - echo DELETING CLOUDWATCH LOG GROUP /aws/lambda/$LAMBDA_GETPRODUCTS - echo -------------------------------------------- - aws logs delete-log-group --log-group-name /aws/lambda/$LAMBDA_GETPRODUCTS - echo -------------------------------------------- - echo DELETING CLOUDWATCH LOG GROUP /aws/lambda/$LAMBDA_GETPRODUCT - echo -------------------------------------------- - aws logs delete-log-group --log-group-name /aws/lambda/$LAMBDA_GETPRODUCT - echo -------------------------------------------- - echo DELETING CLOUDWATCH LOG GROUP /aws/lambda/$LAMBDA_DELETEPRODUCT - echo -------------------------------------------- - aws logs delete-log-group --log-group-name /aws/lambda/$LAMBDA_DELETEPRODUCT - echo -------------------------------------------- - echo DELETING CLOUDWATCH LOG GROUP /aws/lambda/$LAMBDA_PUTPRODUCT - echo -------------------------------------------- - aws logs delete-log-group --log-group-name /aws/lambda/$LAMBDA_PUTPRODUCT - echo --------------------------------------------- - echo Waiting 10 sec. for deletion to complete - echo -------------------------------------------- - sleep 10 - fi - - #run load test with artillery - echo -------------------------------------------- - echo $1 RUNNING LOAD TEST $TEST_DURATIOMN_SEC sec $API_URL - echo -------------------------------------------- - echo "${NO_COLOR}" - artillery run \ - --overrides '{"config": { "phases": [{ "duration": '$TEST_DURATIOMN_SEC', "arrivalRate": 100 }] } }' \ - --quiet \ - ../../loadtest/codebuild/load-test.yml - - echo "${COLOR}" - echo -------------------------------------------- - echo Waiting 10 sec. for logs to consolidate - echo -------------------------------------------- - sleep 10 - - #get stats from cloudwatch - enddate=$(date "+%s") - startdate=$(($enddate-($LOG_INTERVAL_MIN*60))) - echo -------------------------------------------- - echo Log start:$startdate end:$enddate - echo -------------------------------------------- - - QUERY_ID=$(aws logs start-query \ - --log-group-names "/aws/lambda/$LAMBDA_GETPRODUCTS" "/aws/lambda/$LAMBDA_GETPRODUCT" "/aws/lambda/$LAMBDA_DELETEPRODUCT" "/aws/lambda/$LAMBDA_PUTPRODUCT" \ - --start-time $startdate \ - --end-time $enddate \ - --query-string 'filter @type="REPORT" | fields greatest(@initDuration, 0) + @duration as duration, ispresent(@initDuration) as coldstart | stats count(*) as count, pct(duration, 50) as p50, pct(duration, 90) as p90, pct(duration, 99) as p99, max(duration) as max by coldstart' \ - | jq -r '.queryId') - - echo -------------------------------------------- - echo Query started, id: $QUERY_ID - echo -------------------------------------------- - - echo --------------------------------------------- - echo Waiting 10 sec. for cloudwatch query to complete - echo -------------------------------------------- - sleep 10 - - echo -------------------------------------------- - echo RESULTS $1 - echo -------------------------------------------- - echo "${NO_COLOR}" - date > ./Report/load-test-report-$1.txt - echo $1 RESULTS >> ./Report/load-test-report-$1.txt - echo Test duration sec: $TEST_DURATIOMN_SEC >> ./Report/load-test-report-$1.txt - echo Log interval min: $LOG_INTERVAL_MIN >> ./Report/load-test-report-$1.txt - aws logs get-query-results --query-id $QUERY_ID --output text >> ./Report/load-test-report-$1.txt - cat ./Report/load-test-report-$1.txt - aws logs get-query-results --query-id $QUERY_ID --output json >> ./Report/load-test-report-$1.json -} - -RunLoadTest x86 ApiUrlX86 LambdaX86NameGetProducts LambdaX86NameGetProduct LambdaX86NameDeleteProduct LambdaX86NamePutProduct -RunLoadTest arm64 ApiUrlArm64 LambdaArm64NameGetProducts LambdaArm64NameGetProduct LambdaArm64NameDeleteProduct LambdaArm64NamePutProduct \ No newline at end of file diff --git a/src/NET6Native/template.yaml b/src/NET6Native/template.yaml deleted file mode 100755 index cea0c20..0000000 --- a/src/NET6Native/template.yaml +++ /dev/null @@ -1,248 +0,0 @@ -AWSTemplateFormatVersion: "2010-09-09" -Transform: AWS::Serverless-2016-10-31 - -Parameters: - x86FunctionNamePrefix: - Type: String - Default: Net6-X86-Native - arm64FunctionNamePrefix: - Type: String - Default: Net6-Arm64-Native - -Globals: - Function: - MemorySize: 1024 - Runtime: provided.al2 - Timeout: 30 - Tracing: Active - Environment: - Variables: - PRODUCT_TABLE_NAME: !Ref Table - -Resources: - #X86 - GetProductsFunctionX86: - Type: AWS::Serverless::Function - Properties: - FunctionName: !Join [ -,[!Ref x86FunctionNamePrefix,GetProducts]] - Architectures: [x86_64] - CodeUri: ./ - Handler: GetProducts - Events: - Api: - Type: HttpApi - Properties: - Path: /x86 - Method: GET - Policies: - - DynamoDBReadPolicy: - TableName: - !Ref Table - Metadata: - BuildMethod: makefile - - GetProductFunctionX86: - Type: AWS::Serverless::Function - Properties: - FunctionName: !Join [ -,[!Ref x86FunctionNamePrefix,GetProduct]] - Architectures: [x86_64] - CodeUri: ./ - Handler: GetProduct - Events: - Api: - Type: HttpApi - Properties: - Path: /x86/{id} - Method: GET - Policies: - - Version: "2012-10-17" - Statement: - - Effect: Allow - Action: dynamodb:GetItem - Resource: !GetAtt Table.Arn - Metadata: - BuildMethod: makefile - - DeleteProductFunctionX86: - Type: AWS::Serverless::Function - Properties: - FunctionName: !Join [ -,[!Ref x86FunctionNamePrefix,DeleteProduct]] - Architectures: [x86_64] - CodeUri: ./ - Handler: DeleteProduct - Events: - Api: - Type: HttpApi - Properties: - Path: /x86/{id} - Method: DELETE - Policies: - - Version: "2012-10-17" - Statement: - - Effect: Allow - Action: - - dynamodb:DeleteItem - - dynamodb:GetItem - Resource: !GetAtt Table.Arn - Metadata: - BuildMethod: makefile - - PutProductFunctionX86: - Type: AWS::Serverless::Function - Properties: - FunctionName: !Join [ -,[!Ref x86FunctionNamePrefix,PutProduct]] - Architectures: [x86_64] - CodeUri: ./ - Handler: PutProduct - Events: - Api: - Type: HttpApi - Properties: - Path: /x86/{id} - Method: PUT - Policies: - - Version: "2012-10-17" - Statement: - - Effect: Allow - Action: dynamodb:PutItem - Resource: !GetAtt Table.Arn - Metadata: - BuildMethod: makefile - #ARM64 - GetProductsFunctionArm64: - Type: AWS::Serverless::Function - Properties: - FunctionName: !Join [ -,[!Ref arm64FunctionNamePrefix,GetProducts]] - Architectures: [arm64] - CodeUri: ./ - Handler: GetProducts - Events: - Api: - Type: HttpApi - Properties: - Path: /arm64 - Method: GET - Policies: - - DynamoDBReadPolicy: - TableName: - !Ref Table - Metadata: - BuildMethod: makefile - - GetProductFunctionArm64: - Type: AWS::Serverless::Function - Properties: - FunctionName: !Join [ -,[!Ref arm64FunctionNamePrefix,GetProduct]] - Architectures: [arm64] - CodeUri: ./ - Handler: GetProduct - Events: - Api: - Type: HttpApi - Properties: - Path: /arm64/{id} - Method: GET - Policies: - - Version: "2012-10-17" - Statement: - - Effect: Allow - Action: dynamodb:GetItem - Resource: !GetAtt Table.Arn - Metadata: - BuildMethod: makefile - - DeleteProductFunctionArm64: - Type: AWS::Serverless::Function - Properties: - FunctionName: !Join [ -,[!Ref arm64FunctionNamePrefix,DeleteProduct]] - Architectures: [arm64] - CodeUri: ./ - Handler: DeleteProduct - Events: - Api: - Type: HttpApi - Properties: - Path: /arm64/{id} - Method: DELETE - Policies: - - Version: "2012-10-17" - Statement: - - Effect: Allow - Action: - - dynamodb:DeleteItem - - dynamodb:GetItem - Resource: !GetAtt Table.Arn - Metadata: - BuildMethod: makefile - - PutProductFunctionArm64: - Type: AWS::Serverless::Function - Properties: - FunctionName: !Join [ -,[!Ref arm64FunctionNamePrefix,PutProduct]] - Architectures: [arm64] - CodeUri: ./ - Handler: PutProduct - Events: - Api: - Type: HttpApi - Properties: - Path: /arm64/{id} - Method: PUT - Policies: - - Version: "2012-10-17" - Statement: - - Effect: Allow - Action: dynamodb:PutItem - Resource: !GetAtt Table.Arn - Metadata: - BuildMethod: makefile - - Table: - Type: AWS::DynamoDB::Table - Properties: - AttributeDefinitions: - - AttributeName: id - AttributeType: S - BillingMode: PAY_PER_REQUEST - KeySchema: - - AttributeName: id - KeyType: HASH - -Outputs: - ApiUrl: - Description: "API Gateway endpoint URL" - Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com" - ApiUrlX86: - Description: "X86 API endpoint URL" - Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/x86" - ApiUrlArm64: - Description: "Arm64 GateAPI endpoint URL" - Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/arm64" - - #x86 - LambdaX86NameGetProducts: - Description: "Lambda X86 GetProducts" - Value: !Join [ -,[!Ref x86FunctionNamePrefix,GetProducts]] - LambdaX86NameGetProduct: - Description: "Lambda X86 GetProduct" - Value: !Join [ -,[!Ref x86FunctionNamePrefix,GetProduct]] - LambdaX86NameDeleteProduct: - Description: "Lambda X86 DeleteProduct" - Value: !Join [ -,[!Ref x86FunctionNamePrefix,DeleteProduct]] - LambdaX86NamePutProduct: - Description: "Lambda X86 PutProduct" - Value: !Join [ -,[!Ref x86FunctionNamePrefix,PutProduct]] - - #arm64 - LambdaArm64NameGetProducts: - Description: "Lambda X86 GetProducts" - Value: !Join [ -,[!Ref arm64FunctionNamePrefix,GetProducts]] - LambdaArm64NameGetProduct: - Description: "Lambda X86 GetProduct" - Value: !Join [ -,[!Ref arm64FunctionNamePrefix,GetProduct]] - LambdaArm64NameDeleteProduct: - Description: "Lambda X86 DeleteProduct" - Value: !Join [ -,[!Ref arm64FunctionNamePrefix,DeleteProduct]] - LambdaArm64NamePutProduct: - Description: "Lambda X86 PutProduct" - Value: !Join [ -,[!Ref arm64FunctionNamePrefix,PutProduct]] \ No newline at end of file diff --git a/src/NET6TopLevelStatements/run-loadtest.sh b/src/NET6TopLevelStatements/run-loadtest.sh index 49e1f4c..d6f645b 100755 --- a/src/NET6TopLevelStatements/run-loadtest.sh +++ b/src/NET6TopLevelStatements/run-loadtest.sh @@ -1,7 +1,8 @@ #Arguments: #$1 - load test duration in seconds #$2 - log interval to be used in the cloudwatch query in minutes -#$3 - when equal to 1 cloudwatch log group will be deleted to ensure that only logs of the load test will be evaluated for stat +#$3 - when equal to yes cloudwatch log group will be deleted to ensure that only logs of the load test will be evaluated for stat +#$4 - ARN of sns topic to notify test results STACK_NAME=dotnet6-toplevel TEST_DURATIOMN_SEC=60 @@ -26,11 +27,17 @@ then LOG_DELETE=$3 fi +if [ "x$4" != x ]; +then + SNS_TOPIC_ARN=$4 +fi + echo "${COLOR}" echo -------------------------------------------- echo DURATION:$TEST_DURATIOMN_SEC echo LOG INTERVAL:$LOG_INTERVAL_MIN echo LOG_DELETE: $LOG_DELETE +echo SNS_TOPIC_ARN: $SNS_TOPIC_ARN echo -------------------------------------------- echo "${NO_COLOR}" @@ -120,6 +127,18 @@ function RunLoadTest() echo Log start:$startdate end:$enddate echo -------------------------------------------- + echo -------------------------------------------- + echo GET ERROR METRICS $1 + echo -------------------------------------------- + aws cloudwatch get-metric-statistics \ + --namespace AWS/Lambda \ + --metric-name Errors \ + --dimensions Name=FunctionName,Value=$LAMBDA_GETPRODUCTS Name=FunctionName,Value=$LAMBDA_GETPRODUCT Name=FunctionName,Value=$LAMBDA_DELETEPRODUCT Name=FunctionName,Value=$LAMBDA_PUTPRODUCT \ + --statistics Sum --period 43200 \ + --start-time $startdate --end-time $enddate > ./Report/load-test-errors-$1.json + + cat ./Report/load-test-errors-$1.json + QUERY_ID=$(aws logs start-query \ --log-group-names "/aws/lambda/$LAMBDA_GETPRODUCTS" "/aws/lambda/$LAMBDA_GETPRODUCT" "/aws/lambda/$LAMBDA_DELETEPRODUCT" "/aws/lambda/$LAMBDA_PUTPRODUCT" \ --start-time $startdate \ @@ -147,6 +166,16 @@ function RunLoadTest() aws logs get-query-results --query-id $QUERY_ID --output text >> ./Report/load-test-report-$1.txt cat ./Report/load-test-report-$1.txt aws logs get-query-results --query-id $QUERY_ID --output json >> ./Report/load-test-report-$1.json + + if [ "x$SNS_TOPIC_ARN" != x ]; + then + echo -------------------------------------------- + echo Sending message to sns topic: $SNS_TOPIC_ARN + echo -------------------------------------------- + msg=$(<./Report/load-test-report-$1.txt)\n\n$(<./Report/load-test-errors-$1.json) + subject="serverless dotnet demo load test result for $LAMBDA_GETPRODUCTS" + aws sns publish --topic-arn $SNS_TOPIC_ARN --subject "$subject" --message "$msg" + fi } RunLoadTest x86 ApiUrlX86 LambdaX86NameGetProducts LambdaX86NameGetProduct LambdaX86NameDeleteProduct LambdaX86NamePutProduct diff --git a/src/NET6WithPowerTools/run-loadtest.sh b/src/NET6WithPowerTools/run-loadtest.sh index 5474c33..0e7e6b9 100755 --- a/src/NET6WithPowerTools/run-loadtest.sh +++ b/src/NET6WithPowerTools/run-loadtest.sh @@ -1,7 +1,8 @@ #Arguments: #$1 - load test duration in seconds #$2 - log interval to be used in the cloudwatch query in minutes -#$3 - when equal to 1 cloudwatch log group will be deleted to ensure that only logs of the load test will be evaluated for stat +#$3 - when equal to yes cloudwatch log group will be deleted to ensure that only logs of the load test will be evaluated for stat +#$4 - ARN of sns topic to notify test results STACK_NAME=dotnet6-powertools TEST_DURATIOMN_SEC=60 @@ -26,11 +27,17 @@ then LOG_DELETE=$3 fi +if [ "x$4" != x ]; +then + SNS_TOPIC_ARN=$4 +fi + echo "${COLOR}" echo -------------------------------------------- echo DURATION:$TEST_DURATIOMN_SEC echo LOG INTERVAL:$LOG_INTERVAL_MIN echo LOG_DELETE: $LOG_DELETE +echo SNS_TOPIC_ARN: $SNS_TOPIC_ARN echo -------------------------------------------- echo "${NO_COLOR}" @@ -120,6 +127,18 @@ function RunLoadTest() echo Log start:$startdate end:$enddate echo -------------------------------------------- + echo -------------------------------------------- + echo GET ERROR METRICS $1 + echo -------------------------------------------- + aws cloudwatch get-metric-statistics \ + --namespace AWS/Lambda \ + --metric-name Errors \ + --dimensions Name=FunctionName,Value=$LAMBDA_GETPRODUCTS Name=FunctionName,Value=$LAMBDA_GETPRODUCT Name=FunctionName,Value=$LAMBDA_DELETEPRODUCT Name=FunctionName,Value=$LAMBDA_PUTPRODUCT \ + --statistics Sum --period 43200 \ + --start-time $startdate --end-time $enddate > ./Report/load-test-errors-$1.json + + cat ./Report/load-test-errors-$1.json + QUERY_ID=$(aws logs start-query \ --log-group-names "/aws/lambda/$LAMBDA_GETPRODUCTS" "/aws/lambda/$LAMBDA_GETPRODUCT" "/aws/lambda/$LAMBDA_DELETEPRODUCT" "/aws/lambda/$LAMBDA_PUTPRODUCT" \ --start-time $startdate \ @@ -147,6 +166,16 @@ function RunLoadTest() aws logs get-query-results --query-id $QUERY_ID --output text >> ./Report/load-test-report-$1.txt cat ./Report/load-test-report-$1.txt aws logs get-query-results --query-id $QUERY_ID --output json >> ./Report/load-test-report-$1.json + + if [ "x$SNS_TOPIC_ARN" != x ]; + then + echo -------------------------------------------- + echo Sending message to sns topic: $SNS_TOPIC_ARN + echo -------------------------------------------- + msg=$(<./Report/load-test-report-$1.txt)\n\n$(<./Report/load-test-errors-$1.json) + subject="serverless dotnet demo load test result for $LAMBDA_GETPRODUCTS" + aws sns publish --topic-arn $SNS_TOPIC_ARN --subject "$subject" --message "$msg" + fi } RunLoadTest x86 ApiUrlX86 LambdaX86NameGetProducts LambdaX86NameGetProduct LambdaX86NameDeleteProduct LambdaX86NamePutProduct From 90650b59dbfd5b8f87ce8f0074330c0e5f9ead8e Mon Sep 17 00:00:00 2001 From: Riccardo Venturi Date: Thu, 16 Nov 2023 08:01:35 +0100 Subject: [PATCH 32/32] . --- loadtestcli/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/loadtestcli/Program.cs b/loadtestcli/Program.cs index b3a38a4..0a71e71 100644 --- a/loadtestcli/Program.cs +++ b/loadtestcli/Program.cs @@ -370,7 +370,7 @@ private static void SendSNSMsg(string snsTopicArn,string subject,string msg) var request = new PublishRequest { TopicArn = snsTopicArn, - Subject=subject + Subject=subject, Message = msg, };