Example #1
0
 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;
 }
Example #3
0
        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}");
            }
        }