-
Notifications
You must be signed in to change notification settings - Fork 37
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add ASPNET SnapStart example * Update package versions
- Loading branch information
1 parent
3ef3e14
commit 23a3a92
Showing
16 changed files
with
678 additions
and
0 deletions.
There are no files selected for viewing
35 changes: 35 additions & 0 deletions
35
src/NET8MinimalAPISnapSnart/ApiBootstrap/ApiBootstrap.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<OutputType>exe</OutputType> | ||
<TargetFramework>net8.0</TargetFramework> | ||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> | ||
<AWSProjectType>Lambda</AWSProjectType> | ||
<PublishReadyToRunComposite>true</PublishReadyToRunComposite> | ||
<EventSourceSupport>false</EventSourceSupport> | ||
<UseSystemResourceKeys>true</UseSystemResourceKeys> | ||
<InvariantGlobalization>true</InvariantGlobalization> | ||
<SelfContained>true</SelfContained> | ||
<RuntimeIdentifier>linux-x64</RuntimeIdentifier> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Amazon.Lambda.AspNetCoreServer.Hosting" Version="1.7.2" /> | ||
<PackageReference Include="Amazon.Lambda.Core" Version="$(AmazonLambdaCoreVersion)" /> | ||
<PackageReference Include="Amazon.Lambda.APIGatewayEvents" Version="$(ApiGatewayEventsVersion)" /> | ||
<PackageReference Include="Amazon.Lambda.Serialization.SystemTextJson" Version="$(AmazonLambdaSerializationVersion)" /> | ||
<PackageReference Include="AWSSDK.CloudWatchLogs" Version="$(CloudWatchLogsSdkVersion)" /> | ||
<PackageReference Include="AWSXRayRecorder.Core" Version="$(XRayRecorderVersion)" /> | ||
<PackageReference Include="AWSXRayRecorder.Handlers.AwsSdk" Version="$(XRaySdkHandlerVersion)" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\Shared\Shared.csproj" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<Content Include="..\template.yaml"> | ||
<Link>template.yaml</Link> | ||
</Content> | ||
</ItemGroup> | ||
</Project> |
51 changes: 51 additions & 0 deletions
51
src/NET8MinimalAPISnapSnart/ApiBootstrap/CloudWatchQueryExecution.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
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<List<List<ResultField>>> 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<List<ResultField>> finalResults = new List<List<ResultField>>(); | ||
|
||
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Net; | ||
using System.Text.Json;using System.Threading.Tasks; | ||
using Amazon.CloudWatchLogs; | ||
using Amazon.CloudWatchLogs.Model; | ||
using ApiBootstrap; | ||
using GetProducts; | ||
using Microsoft.AspNetCore.Builder; | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Microsoft.Extensions.Logging; | ||
using Shared; | ||
using Shared.DataAccess; | ||
using Shared.Models; | ||
|
||
var builder = WebApplication.CreateSlimBuilder(args); | ||
builder.Services.AddServices(builder.Configuration); | ||
builder.Services.AddSingleton<Handlers>(); | ||
builder.Logging.ClearProviders(); | ||
builder.Logging.AddJsonConsole(options => | ||
{ | ||
options.IncludeScopes = true; | ||
options.UseUtcTimestamp = true; | ||
options.TimestampFormat = "hh:mm:ss "; | ||
}); | ||
|
||
var app = builder.Build(); | ||
|
||
var dataAccess = app.Services.GetRequiredService<ProductsDAO>(); | ||
var handlers = app.Services.GetRequiredService<Handlers>(); | ||
|
||
Amazon.Lambda.Core.SnapshotRestore.RegisterBeforeSnapshot(async () => await BeforeCheckpoint(app.Logger, handlers)); | ||
Amazon.Lambda.Core.SnapshotRestore.RegisterBeforeSnapshot(AfterRestore); | ||
|
||
var cloudWatchClient = new AmazonCloudWatchLogsClient(); | ||
|
||
app.MapGet("/", async () => await handlers.ListProducts()); | ||
|
||
app.MapDelete("/{id}", async (HttpContext context) => | ||
{ | ||
try | ||
{ | ||
var id = context.Request.RouteValues["id"].ToString(); | ||
|
||
app.Logger.LogInformation($"Received request to delete {id}"); | ||
|
||
var product = await dataAccess.GetProduct(id); | ||
|
||
if (product == null) | ||
{ | ||
app.Logger.LogWarning($"Id {id} not found."); | ||
|
||
context.Response.StatusCode = (int) HttpStatusCode.NotFound; | ||
Results.NotFound(); | ||
return; | ||
} | ||
|
||
app.Logger.LogInformation($"Deleting {product.Name}"); | ||
|
||
await dataAccess.DeleteProduct(product.Id); | ||
|
||
app.Logger.LogInformation("Delete complete"); | ||
|
||
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"); | ||
|
||
context.Response.StatusCode = (int) HttpStatusCode.NotFound; | ||
} | ||
}); | ||
|
||
app.MapPut("/{id}", async (HttpContext context) => | ||
{ | ||
try | ||
{ | ||
var id = context.Request.RouteValues["id"].ToString(); | ||
|
||
app.Logger.LogInformation($"Received request to put {id}"); | ||
|
||
var product = await JsonSerializer.DeserializeAsync<Product>(context.Request.Body); | ||
|
||
if (product == null || id != product.Id) | ||
{ | ||
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; | ||
} | ||
|
||
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}"); | ||
} | ||
catch (Exception e) | ||
{ | ||
app.Logger.LogError(e, "Failure deleting product"); | ||
|
||
context.Response.StatusCode = (int) HttpStatusCode.BadRequest; | ||
} | ||
}); | ||
|
||
app.MapGet("/{id}", async (HttpContext context) => | ||
{ | ||
var id = context.Request.RouteValues["id"].ToString(); | ||
|
||
return await handlers.GetProduct(id); | ||
}); | ||
|
||
app.MapGet("/test-results", async (HttpContext context) => | ||
{ | ||
var resultRows = 0; | ||
var queryCount = 0; | ||
|
||
List<List<ResultField>> finalResults = new List<List<ResultField>>(); | ||
|
||
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(); | ||
|
||
static async ValueTask BeforeCheckpoint(ILogger logger, Handlers handlers) | ||
{ | ||
logger.LogInformation("Before checkpoint"); | ||
|
||
for (int i = 0; i < 20; i++) | ||
{ | ||
await handlers.ListProducts(); | ||
await handlers.GetProduct("test-product-id"); | ||
} | ||
|
||
logger.LogInformation("Before checkpoint"); | ||
} | ||
|
||
static ValueTask AfterRestore() | ||
{ | ||
Console.WriteLine("After restore"); | ||
|
||
return ValueTask.CompletedTask; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
using System.Collections.Generic; | ||
using System.Threading.Tasks; | ||
using Microsoft.AspNetCore.Mvc; | ||
using Microsoft.Extensions.Logging; | ||
using Shared.DataAccess; | ||
using Shared.Models; | ||
|
||
namespace ApiBootstrap; | ||
|
||
public class Handlers(ILogger<Handlers> logger, ProductsDAO products) | ||
{ | ||
public async Task<ActionResult<List<Product>>> ListProducts() | ||
{ | ||
logger.LogInformation("Received request to list all products"); | ||
|
||
var productList = await products.GetAllProducts(); | ||
|
||
logger.LogInformation($"Found {productList.Products.Count} products(s)"); | ||
|
||
return new OkObjectResult(products); | ||
} | ||
|
||
public async Task<ActionResult<Product>> GetProduct(string productId) | ||
{ | ||
logger.LogInformation("Received request to list all products"); | ||
|
||
var product = await products.GetProduct(productId); | ||
|
||
if (product == null) | ||
{ | ||
logger.LogWarning($"{productId} not found"); | ||
return new NotFoundResult(); | ||
} | ||
|
||
return new OkObjectResult(product); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
namespace GetProducts; | ||
|
||
public record QueryResultWrapper | ||
{ | ||
public string LoadTestType { get; set; } | ||
|
||
public QueryResult ColdStart { get; set; } | ||
|
||
public QueryResult WarmStart { get; set; } | ||
|
||
public string AsMarkdownTableRow() => $"<table class=\"table-bordered\"><tr><th colspan=\"1\" style=\"horizontal-align : middle;text-align:center;\"></th><th colspan=\"4\" style=\"horizontal-align : middle;text-align:center;\">Cold Start (ms)</th><th colspan=\"4\" style=\"horizontal-align : middle;text-align:center;\">Warm Start (ms)</th></tr> <tr><th></th><th scope=\"col\">p50</th><th scope=\"col\">p90</th><th scope=\"col\">p99</th><th scope=\"col\">max</th><th scope=\"col\">p50</th><th scope=\"col\">p90</th><th scope=\"col\">p99</th><th scope=\"col\">max</th> </tr><tr><th>{LoadTestType}</th><td>{ColdStart.P50}</td><td>{ColdStart.P90}</td><td>{ColdStart.P99}</td><td>{ColdStart.Max}</td><td><b style=\"color: green\">{WarmStart.P50}</b></td><td><b style=\"color: green\">{WarmStart.P90}</b></td><td><b style=\"color: green\">{WarmStart.P99}</b></td><td>{WarmStart.Max}</td></tr></table>"; | ||
} | ||
|
||
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; } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
|
||
Microsoft Visual Studio Solution File, Format Version 12.00 | ||
# Visual Studio Version 17 | ||
VisualStudioVersion = 17.0.31903.59 | ||
MinimumVisualStudioVersion = 10.0.40219.1 | ||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ApiBootstrap", "ApiBootstrap\ApiBootstrap.csproj", "{1CB65CF9-FD9D-46A0-B6FB-523E74F360F9}" | ||
EndProject | ||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shared", "Shared\Shared.csproj", "{6FD38FCF-DB5C-45B6-8DA5-50696891ED94}" | ||
EndProject | ||
Global | ||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||
Debug|Any CPU = Debug|Any CPU | ||
Release|Any CPU = Release|Any CPU | ||
EndGlobalSection | ||
GlobalSection(SolutionProperties) = preSolution | ||
HideSolutionNode = FALSE | ||
EndGlobalSection | ||
GlobalSection(ProjectConfigurationPlatforms) = postSolution | ||
{1CB65CF9-FD9D-46A0-B6FB-523E74F360F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
{1CB65CF9-FD9D-46A0-B6FB-523E74F360F9}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
{1CB65CF9-FD9D-46A0-B6FB-523E74F360F9}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
{1CB65CF9-FD9D-46A0-B6FB-523E74F360F9}.Release|Any CPU.Build.0 = Release|Any CPU | ||
{6FD38FCF-DB5C-45B6-8DA5-50696891ED94}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
{6FD38FCF-DB5C-45B6-8DA5-50696891ED94}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
{6FD38FCF-DB5C-45B6-8DA5-50696891ED94}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
{6FD38FCF-DB5C-45B6-8DA5-50696891ED94}.Release|Any CPU.Build.0 = Release|Any CPU | ||
EndGlobalSection | ||
EndGlobal |
Oops, something went wrong.