/// <summary>
 /// Initializes a new instance of the <see cref="ReplayFilerRunnerResult"/> class.
 /// </summary>
 /// <param name="description">The description, from the replay file.</param>
 /// <param name="baseUri">The base URI.</param>
 /// <param name="uri">The relative URI.</param>
 /// <param name="iterations">The number of iterations performed.</param>
 /// <param name="concurrentRequests">The number of concurrent requests performed.</param>
 /// <param name="lastStatusCode">The last status code observed.</param>
 /// <param name="lastResponseBodySize">The last size of the response body observed.</param>
 /// <param name="lastResponseBodyChecksum">The last response body checksum observed.</param>
 /// <param name="responseTimeStats">The response time statistics</param>
 /// <param name="requestsPerSec">The number of requests per second.</param>
 /// <param name="testStartTime">The approximate timestamp when the first iteration started.</param>
 /// <param name="testEndTime">The approximate timestamp when all iterations finished.</param>
 public ReplayFilerRunnerResult(
     string description,
     string baseUri,
     string uri,
     int iterations,
     int concurrentRequests,
     int lastStatusCode,
     long lastResponseBodySize,
     string lastResponseBodyChecksum,
     PercentileStats<TimeSpan> responseTimeStats,
     double requestsPerSec,
     DateTimeOffset testStartTime,
     DateTimeOffset testEndTime)
 {
     this.responseTimeStats = responseTimeStats;
     this.Description = description;
     this.BaseUri = baseUri;
     this.Uri = uri;
     this.Iterations = iterations;
     this.ConcurrentRequests = concurrentRequests;
     this.LastStatusCode = lastStatusCode;
     this.LastResponseBodySize = lastResponseBodySize;
     this.LastResponseBodyChecksum = lastResponseBodyChecksum;
     this.RequestsPerSec = requestsPerSec;
     this.TestStartTime = testStartTime;
     this.TestEndTime = testEndTime;
 }
        /// <summary>
        /// Asynchronously runs the specified replay file.
        /// </summary>
        /// <param name="replayFile">The replay file.</param>
        /// <param name="callback">The callback to call at the completion of each of the request in the replay file..</param>
        /// <param name="uriRunnerOptions">The options for the uri runner.</param>
        /// <param name="iterations">The iterations.</param>
        /// <param name="concurrentRequests">The number of concurrent requests to execute.</param>
        /// <returns>The list of the results.</returns>
        public static async Task RunReplayFileAsync(
            ReplayFile replayFile, 
            Action<ReplayFilerRunnerResult> callback, 
            UriRunnerOptions uriRunnerOptions, 
            int iterations = 1, 
            int concurrentRequests = 1)
        {
            var consoleWriteLine = StringWriterFuns.ConsoleWriter();

            using (var handler = new HttpClientHandler())
            using (var client = new HttpClient(handler))
            {
                handler.AllowAutoRedirect = true;
                handler.AutomaticDecompression = DecompressionMethods.None;
                handler.UseProxy = true;

                // Need to set this to false to make sure the http client does not
                // mess around with our own cookies.
                handler.UseCookies = false;

                client.BaseAddress = new Uri(replayFile.BaseUri);
                foreach (var header in replayFile.Headers)
                {
                    if (!client.DefaultRequestHeaders.TryAddWithoutValidation(header.Key, header.Value))
                    {
                        consoleWriteLine("Failed to add header: {0} = {1}", header.Key, header.Value);
                    }
                }

                foreach (var uri in replayFile.Uris)
                {
                    var sw = new Stopwatch();

                    var numberOfRequests = iterations * concurrentRequests;
                    var allResults = new List<UriRunnerResult>(capacity: numberOfRequests);
                    var startTimeUtc = DateTimeOffset.UtcNow;

                    // Exec same URI with the specified concurrency level
                    var uris = Enumerable.Repeat(uri, concurrentRequests).ToArray();
                    for (var i = 0; i < iterations; i++)
                    {
                        sw.Start();
                        var runUrisResults = await UriRunner.RunUrisConcurrentlyAsync(
                            client: client, 
                            uris: uris,
                            uriRunnerOptions: uriRunnerOptions);
                        sw.Stop();
                        allResults.AddRange(runUrisResults);
                    }

                    var endTimeUtc = DateTimeOffset.UtcNow;
                    var requestsPerSec = (double)numberOfRequests / sw.Elapsed.TotalSeconds;
                    var responseTimes = allResults.Select(r => r.ResponseTime);
                    var percentiles = new PercentileStats<TimeSpan>(responseTimes);

                    var lastResult = allResults.Last();

                    var reportLine = new ReplayFilerRunnerResult(
                        iterations: iterations, 
                        concurrentRequests: concurrentRequests, 
                        lastStatusCode: (int)lastResult.StatusCode, 
                        lastResponseBodySize: lastResult.BodySize, 
                        lastResponseBodyChecksum: lastResult.BodyChecksum, 
                        responseTimeStats: percentiles, 
                        description: replayFile.Description, 
                        baseUri: replayFile.BaseUri, 
                        uri: uri,
                        requestsPerSec: requestsPerSec,
                        testStartTime: startTimeUtc,
                        testEndTime: endTimeUtc);

                    if (callback != null)
                    {
                        callback(reportLine);
                    }
                }
            }
        }