public ServerInstance( CreateWebHostBuilder createWebHostBuilder, int coldStartDelay) { _coldStartDelay = coldStartDelay; _testServer = new TestServer(createWebHostBuilder()); ActivationTime = DateTime.UtcNow; }
/// <summary> /// Initialized a new instance of <see cref="LambdaSimulator"/> /// </summary> /// <param name="createWebHostBuilder"> /// A delegate to create the web host builder of the hosted /// AspNetCore application. /// </param> /// <param name="maxConcurrency"> /// The maximum number of concurrent in-flight requests being /// handled. Each in-flight request will have be handled by an /// independent AspNetCore application (aka ServerInstance). /// </param> /// <param name="maxInstanceLifetime"> /// The maximum liftime of a Server Instance. Generally AWS will /// keep used lambda function instances 'warm', however their /// lifecycle is completely at the behest of AWS. Set this to a low /// value simulate the instance being shut down and a new one /// starting. /// </param> /// <param name="instanceColdStartDelay"> /// The delay experiences when starting an AspNetCore lambda /// function from cold. Typically, this takes 4-8 seconds. /// </param> public LambdaSimulator( CreateWebHostBuilder createWebHostBuilder, int maxConcurrency, TimeSpan maxInstanceLifetime, int instanceColdStartDelay, int requestExecutionDuration) { _createWebHostBuilder = createWebHostBuilder ?? throw new ArgumentNullException(nameof(createWebHostBuilder)); if (maxConcurrency <= 0) { throw new ArgumentOutOfRangeException(nameof(maxConcurrency)); } _maxConcurrency = maxConcurrency; _maxInstanceLifetime = maxInstanceLifetime; _instanceColdStartDelay = instanceColdStartDelay; _requestExecutionDuration = requestExecutionDuration; }
public async Task LoadTest() { /* The very nature of this test is non-deterministic. AWS controls * the lifecycle of a function instance that is * entirely at the behest of AWS. Given a set of values around * maximum permitted concurrency, when the requests are made within * a window, some of the responses will be 200 OK, others will be * 429 Too Many Requests. Due to the parallel operations and varying * executions and the core count of of your machine, the exact * numbers are not deterministic and will vary. */ CreateWebHostBuilder createWebHostBuilder = () => WebHost.CreateDefaultBuilder() .UseStartup <Startup>(); // Tweaking these values will result is differing distributions of // 200 and 429 responses. // The delay experienced when cold starting a lambda function. Realy // world typically 3-8 seconds depending on cold start Olonger) or // from hibernation (shorter). var coldStartDelay = 300; // Increase to reduce the count of 429s. No 429s when >= numberOfRequests var maxConcurrency = 10; // Should be longer than coldStartDelay otherwise no instances are // re-used and less than maxDelayBeforeRequest to simulate instance // disposing and new instances statting. Typically an active instance // will be kept warm for much longer than this. var instanceLifespan = TimeSpan.FromMilliseconds(750); // Total number of requests issued. var numberOfRequests = 250; // The maxium delay before a request is issued. var maxDelayBeforeRequest = 1000; // The time taken to execute a request. Typically 30-150ms. var requestExecutionDuration = 9; using (var simulator = new LambdaSimulator( createWebHostBuilder, maxConcurrency, instanceLifespan, coldStartDelay, requestExecutionDuration)) { var httpClient = simulator.CreateHttpClient(); var requestDelay = new Random(); // Fire off a number of concurrent requests, distributed over a window (maxDelayBeforeRequest) var tasks = Enumerable.Range(0, numberOfRequests) .Select(async _ => { await Task.Delay(requestDelay.Next(0, maxDelayBeforeRequest)); return(await httpClient.GetAsync("https://example.com/")); }); var responses = await Task.WhenAll(tasks); var tooManyRequestsCount = responses.Count(r => r.StatusCode == (HttpStatusCode)429); var okCount = responses.Count(r => r.StatusCode == HttpStatusCode.OK); var uniqueServers = responses .Where(r => r.StatusCode == HttpStatusCode.OK) .Select(r => r.Headers.GetValues("ServerInstanceId").Single()).Distinct().Count(); tooManyRequestsCount.ShouldBeGreaterThan(0); okCount.ShouldBeGreaterThanOrEqualTo(maxConcurrency); uniqueServers.ShouldBeGreaterThanOrEqualTo(maxConcurrency); _testOutputHelper.WriteLine($"Status code 429: {tooManyRequestsCount}"); _testOutputHelper.WriteLine($"Status code 200: {okCount}"); _testOutputHelper.WriteLine($"Unique servers spawned: {uniqueServers}"); } }