示例#1
0
        internal static async Task <HttpResponseMessage> CreateGoodHitCountResponse(HttpRequestMessage request,
                                                                                    DateTime startTime,
                                                                                    int numSampleBuckets)
        {
            var requestMessage = await UnpackRequest <TieredRequest>(request);

            var responseContent = new CounterQueryResponse
            {
                Samples        = new List <DataSample>(),
                RequestDetails = requestMessage.Sources.Select(source =>
                                                               new RequestDetails
                {
                    Server = source,
                    Status = RequestStatus.Success
                }).ToList()
            };

            // add a response for the leader
            responseContent.RequestDetails.Add(new RequestDetails
            {
                Server           = ExtractServerInfo(request.RequestUri),
                Status           = RequestStatus.Success,
                HttpResponseCode = 200
            });

            AddHitCountSamples(responseContent.Samples, startTime, numSampleBuckets,
                               (uint)requestMessage.Sources.Count + 1);

            return(CreateResponse(responseContent));
        }
        private CounterQueryResponse CreateResponse(DateTime startTime, int bucketTimeInMinutes, int deltaBetweenBucketStarts, int numBuckets, int uniqueDimensions = 1, string dimensionValue = null)
        {
            var response = new CounterQueryResponse {
                Samples = new List <DataSample>()
            };

            var bucketStart = startTime;

            for (int dim = 0; dim < uniqueDimensions; dim++)
            {
                for (int i = 0; i < numBuckets; i++)
                {
                    response.Samples.Add(
                        new DataSample
                    {
                        HitCount   = 1,
                        Dimensions = new Dictionary <string, string> {
                            { AnyDimensionName, dimensionValue ?? dim.ToString() }
                        },
                        SampleType = DataSampleType.HitCount,
                        StartTime  = bucketStart.ToMillisecondTimestamp(),
                        EndTime    =
                            bucketStart.AddMinutes(bucketTimeInMinutes)
                            .ToMillisecondTimestamp()
                    });

                    bucketStart = bucketStart.AddMinutes(deltaBetweenBucketStarts);
                }
            }
            return(response);
        }
        public void CounterAggregatorRejectsPerMachinePercentiles()
        {
            var aggregator = new CounterAggregator(DefaultDimensions);

            var response = new CounterQueryResponse
            {
                HttpResponseCode = 200,
                Samples          = new List <DataSample>
                {
                    new DataSample
                    {
                        Name       = "bob",
                        StartTime  = DateTime.Now.ToMillisecondTimestamp(),
                        EndTime    = DateTime.Now.ToMillisecondTimestamp(),
                        Dimensions =
                            new Dictionary <string, string> {
                            { AnyDimensionName, "tacos" }
                        },
                        SampleType      = DataSampleType.Percentile,
                        Percentile      = 40,
                        PercentileValue = 11,
                    }
                }
            };

            Assert.Throws <ArgumentException>(() => aggregator.AddMachineResponse(response));
        }
示例#4
0
        internal static HttpResponseMessage CreateGoodCounterResponse(DateTime startTime, int numBuckets)
        {
            var CounterQueryResponse = new CounterQueryResponse {
                Samples = new List <DataSample>()
            };

            AddHitCountSamples(CounterQueryResponse.Samples, startTime, numBuckets, 1);

            return(CreateResponse(CounterQueryResponse));
        }
示例#5
0
        internal static HttpResponseMessage CreateFailedTieredResponse(string hostname, HttpStatusCode status)
        {
            var response = new CounterQueryResponse();

            response.RequestDetails.Add(new RequestDetails
            {
                Server = new ServerInfo
                {
                    Hostname = hostname,
                    Port     = 42,
                },
                HttpResponseCode  = (short)status,
                Status            = RequestStatus.ServerFailureResponse,
                IsAggregator      = false,
                StatusDescription = "Not found"
            });

            return(CreateResponse(response, HttpStatusCode.NotFound));
        }
        public void CounterAggregatorCalculatesPercentileAfterAggregation()
        {
            var aggregator = new CounterAggregator(DefaultDimensions);

            var response = new CounterQueryResponse
            {
                HttpResponseCode = 200,
                Samples          = new List <DataSample>
                {
                    new DataSample
                    {
                        Name       = "bob",
                        StartTime  = DateTime.Now.ToMillisecondTimestamp(),
                        EndTime    = DateTime.Now.ToMillisecondTimestamp(),
                        Dimensions =
                            new Dictionary <string, string> {
                            { AnyDimensionName, "tacos" }
                        },
                        SampleType = DataSampleType.Histogram,
                        Histogram  = new Dictionary <long, uint> {
                            { 1, 1 }, { 2, 1 }, { 3, 1 }, { 4, 1 }, { 5, 1 }, { 6, 1 }, { 7, 1 }, { 8, 1 }, { 9, 1 }, { 10, 1 }
                        }
                    }
                }
            };

            // by default, we do not apply percentile filtering
            aggregator.AddMachineResponse(response);
            var defaultValue = aggregator.Samples.First();

            Assert.AreEqual(DataSampleType.Histogram, defaultValue.SampleType);

            // now that the client asked for filtering, we calculate the 99.999% percentile (should be the max value from earlier)
            aggregator.ApplyPercentileCalculationAggregation(new Dictionary <string, string> {
                { "Percentile", "99.999" }
            });
            var aggregatedValue = aggregator.Samples.First();

            Assert.AreEqual(DataSampleType.Percentile, aggregatedValue.SampleType);
            Assert.AreEqual(10, aggregatedValue.PercentileValue);
        }
示例#7
0
        /// <summary>
        /// Merge the local Query request and the distributed tiered response into a single response
        /// </summary>
        private static CounterQueryResponse MergeResponses(IDictionary <string, string> queryParameters,
                                                           CounterQueryResponse localResponse, bool mergeTimeBuckets,
                                                           CounterQueryResponse distributedResponse)
        {
            // no distributed response, just use the local one
            if (distributedResponse == null)
            {
                return(localResponse);
            }

            var sampleMerger = new CounterAggregator(queryParameters ?? new Dictionary <string, string>(0));

            sampleMerger.AddMachineResponse(localResponse);
            sampleMerger.AddMachineResponse(distributedResponse);

            // response code from this server is:
            //  200: *Anyone* has data
            //  40X: *Everyone failed to get data, but everyone has the same response code
            //  409: Mixed error codes ("Conflict" used liberally)
            HttpStatusCode responseCode;

            if ((localResponse.Samples != null && localResponse.Samples.Count > 0) ||
                (distributedResponse.Samples != null && distributedResponse.Samples.Count > 0))
            {
                responseCode = HttpStatusCode.OK;
            }
            else
            {
                responseCode = distributedResponse.RequestDetails != null &&
                               distributedResponse.RequestDetails.Count > 0 &&
                               distributedResponse.RequestDetails.All(d => d.HttpResponseCode == (int)localResponse.HttpResponseCode)
                                   ? (HttpStatusCode)localResponse.HttpResponseCode
                                   : HttpStatusCode.Conflict;
            }

            var mergedResponse = sampleMerger.GetResponse(mergeTimeBuckets);

            mergedResponse.HttpResponseCode = (short)responseCode;
            return(mergedResponse);
        }
        private static async Task <HttpResponseMessage> GenerateFailureResponse(HttpRequestMessage request,
                                                                                HttpStatusCode responseCode,
                                                                                RequestStatus statusForDownstreamSources)
        {
            var requestMessage = await MockDataFactory.UnpackRequest <TieredRequest>(request);

            var responseContent = new CounterQueryResponse();

            responseContent.RequestDetails = requestMessage.Sources.Select(source =>
                                                                           new RequestDetails
            {
                Server = source,
                Status = statusForDownstreamSources
            }).ToList();

            // add one for the leader
            responseContent.RequestDetails.Add(new RequestDetails
            {
                Server           = MockDataFactory.ExtractServerInfo(request.RequestUri),
                Status           = RequestStatus.ServerFailureResponse,
                HttpResponseCode = (short)responseCode
            });
            return(MockDataFactory.CreateResponse(responseContent, responseCode));
        }
        public async void BatchQueryAggregatesFanoutCorrectly()
        {
            const string tacoTruck = "/Tacos";
            const string competingBurritoTruck = "/Burritos";

            var dimensionSet = new DimensionSet(new HashSet<Dimension>());
            var counter = await this.dataManager.CreateHitCounter(tacoTruck, dimensionSet);

            // locally store dim1
            counter.Increment(100, new DimensionSpecification());
            this.dataManager.Flush();

            var data = new BatchQueryRequest();
            data.Queries.Add(new BatchCounterQuery { CounterName = tacoTruck, UserContext = tacoTruck});
            data.Queries.Add(new BatchCounterQuery
                             {
                                 CounterName = competingBurritoTruck,
                                 UserContext = competingBurritoTruck
                             });
            data.Sources.Add(new ServerInfo {Hostname = "a", Port = 42});
            data.Sources.Add(new ServerInfo {Hostname = "b", Port = 42});

            var sampleStart = DateTime.Now;
            var sampleEnd = sampleStart.AddHours(1);

            // remotely return 100 for dim2 only
            DistributedQueryClient.RequesterFactory = new MockHttpRequesterFactory(message =>
                    {
                        var batchResponse = new BatchQueryResponse();
                        batchResponse.RequestDetails.Add(new RequestDetails
                                                         {
                                                             Server =
                                                                 new ServerInfo
                                                                 {
                                                                     Hostname = "bob",
                                                                     Port = 42
                                                                 },
                                                             HttpResponseCode = 200
                                                         });
                        var counterResponse = new CounterQueryResponse
                                              {
                                                  HttpResponseCode = 200, 
                                                  UserContext = competingBurritoTruck,
                                                  Samples = new List<DataSample> {
                                                    new DataSample { HitCount = 100, Dimensions = new Dictionary<string, string>(), SampleType = DataSampleType.HitCount, StartTime = sampleStart.ToMillisecondTimestamp(), EndTime = sampleEnd.ToMillisecondTimestamp() }
                                                    }
                                               };
                        batchResponse.Responses.Add(counterResponse);
                        return MockDataFactory.CreateResponse(batchResponse);
                    });
            
            var response = await
                           this.httpClient.PostAsync(TestUtils.GetUri(this.server, RestCommands.BatchQueryCommand,
                                                                      string.Empty),
                                                     GetRequestPayload(data));
            Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
            var responseData = await MockDataFactory.ReadResponseData<BatchQueryResponse>(response);
            Assert.IsNotNull(responseData);
            Assert.AreEqual(3, responseData.RequestDetails.Count);
            Assert.IsTrue(responseData.RequestDetails.All(x => x.HttpResponseCode == 200));
            Assert.AreEqual(2, responseData.Responses.Count);
            Assert.AreEqual(1, responseData.Responses.Count(x => x.UserContext.Equals(tacoTruck) && x.Samples[0].HitCount == 100));
            Assert.AreEqual(1, responseData.Responses.Count(x => x.UserContext.Equals(competingBurritoTruck) && x.Samples[0].HitCount == 200));
        }
        private static async Task<HttpResponseMessage> GenerateFailureResponse(HttpRequestMessage request,
                                                                               HttpStatusCode responseCode,
                                                                               RequestStatus statusForDownstreamSources)
        {
            var requestMessage = await MockDataFactory.UnpackRequest<TieredRequest>(request);
            var responseContent = new CounterQueryResponse();
            responseContent.RequestDetails = requestMessage.Sources.Select(source =>
                                                                           new RequestDetails
                                                                           {
                                                                               Server = source,
                                                                               Status = statusForDownstreamSources
                                                                           }).ToList();

            // add one for the leader
            responseContent.RequestDetails.Add(new RequestDetails
            {
                Server = MockDataFactory.ExtractServerInfo(request.RequestUri),
                Status = RequestStatus.ServerFailureResponse,
                HttpResponseCode = (short)responseCode
            });
            return MockDataFactory.CreateResponse(responseContent, responseCode);
        }
示例#11
0
        public async void BatchQueryAggregatesFanoutCorrectly()
        {
            const string tacoTruck             = "/Tacos";
            const string competingBurritoTruck = "/Burritos";

            var dimensionSet = new DimensionSet(new HashSet <Dimension>());
            var counter      = await this.dataManager.CreateHitCounter(tacoTruck, dimensionSet);

            // locally store dim1
            counter.Increment(100, new DimensionSpecification());
            this.dataManager.Flush();

            var data = new BatchQueryRequest();

            data.Queries.Add(new BatchCounterQuery {
                CounterName = tacoTruck, UserContext = tacoTruck
            });
            data.Queries.Add(new BatchCounterQuery
            {
                CounterName = competingBurritoTruck,
                UserContext = competingBurritoTruck
            });
            data.Sources.Add(new ServerInfo {
                Hostname = "a", Port = 42
            });
            data.Sources.Add(new ServerInfo {
                Hostname = "b", Port = 42
            });

            var sampleStart = DateTime.Now;
            var sampleEnd   = sampleStart.AddHours(1);

            // remotely return 100 for dim2 only
            DistributedQueryClient.RequesterFactory = new MockHttpRequesterFactory(message =>
            {
                var batchResponse = new BatchQueryResponse();
                batchResponse.RequestDetails.Add(new RequestDetails
                {
                    Server =
                        new ServerInfo
                    {
                        Hostname = "bob",
                        Port     = 42
                    },
                    HttpResponseCode = 200
                });
                var counterResponse = new CounterQueryResponse
                {
                    HttpResponseCode = 200,
                    UserContext      = competingBurritoTruck,
                    Samples          = new List <DataSample> {
                        new DataSample {
                            HitCount = 100, Dimensions = new Dictionary <string, string>(), SampleType = DataSampleType.HitCount, StartTime = sampleStart.ToMillisecondTimestamp(), EndTime = sampleEnd.ToMillisecondTimestamp()
                        }
                    }
                };
                batchResponse.Responses.Add(counterResponse);
                return(MockDataFactory.CreateResponse(batchResponse));
            });

            var response = await
                           this.httpClient.PostAsync(TestUtils.GetUri(this.server, RestCommands.BatchQueryCommand,
                                                                      string.Empty),
                                                     GetRequestPayload(data));

            Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
            var responseData = await MockDataFactory.ReadResponseData <BatchQueryResponse>(response);

            Assert.IsNotNull(responseData);
            Assert.AreEqual(3, responseData.RequestDetails.Count);
            Assert.IsTrue(responseData.RequestDetails.All(x => x.HttpResponseCode == 200));
            Assert.AreEqual(2, responseData.Responses.Count);
            Assert.AreEqual(1, responseData.Responses.Count(x => x.UserContext.Equals(tacoTruck) && x.Samples[0].HitCount == 100));
            Assert.AreEqual(1, responseData.Responses.Count(x => x.UserContext.Equals(competingBurritoTruck) && x.Samples[0].HitCount == 200));
        }
示例#12
0
        /// <summary>
        /// Merge the local Query request and the distributed tiered response into a single response
        /// </summary>
        private static CounterQueryResponse MergeResponses(IDictionary<string, string> queryParameters,
                                                           CounterQueryResponse localResponse, bool mergeTimeBuckets,
                                                           CounterQueryResponse distributedResponse)
        {
            // no distributed response, just use the local one
            if (distributedResponse == null)
            {
                return localResponse;
            }

            var sampleMerger = new CounterAggregator(queryParameters ?? new Dictionary<string, string>(0));
            sampleMerger.AddMachineResponse(localResponse);
            sampleMerger.AddMachineResponse(distributedResponse);

            // response code from this server is:
            //  200: *Anyone* has data
            //  40X: *Everyone failed to get data, but everyone has the same response code
            //  409: Mixed error codes ("Conflict" used liberally)
            HttpStatusCode responseCode;
            if ((localResponse.Samples != null && localResponse.Samples.Count > 0) ||
                (distributedResponse.Samples != null && distributedResponse.Samples.Count > 0))
            {
                responseCode = HttpStatusCode.OK;
            }
            else
            {
                responseCode = distributedResponse.RequestDetails != null &&
                               distributedResponse.RequestDetails.Count > 0 &&
                               distributedResponse.RequestDetails.All(d => d.HttpResponseCode == (int)localResponse.HttpResponseCode)
                                   ? (HttpStatusCode)localResponse.HttpResponseCode
                                   : HttpStatusCode.Conflict;
            }

            var mergedResponse = sampleMerger.GetResponse(mergeTimeBuckets);
            mergedResponse.HttpResponseCode = (short)responseCode;
            return mergedResponse;
        }
示例#13
0
        internal async Task<CounterQueryResponse> Query(string counterName, TieredRequest fanoutRequest,
                                                        DimensionSpecification queryParameters)
        {
            Task<CounterQueryResponse> distributedRequest;

            // if fanout is required, start it here (don't block)
            if (fanoutRequest != null && fanoutRequest.Sources != null && fanoutRequest.Sources.Count > 0)
            {
                distributedRequest = this.server.CreateQueryClient(fanoutRequest)
                                         .CounterQuery(counterName, fanoutRequest, queryParameters,
                                                       DimensionSet.FromQueryParameters(queryParameters));
            }
            else
            {
                distributedRequest = Task.FromResult<CounterQueryResponse>(null);
            }

            // get the local data (in parallel to the async distributed request running, if necessary)
            var localResponse = new CounterQueryResponse();
            try
            {
                var counter = this.server.DataManager.GetCounter<Counter>(counterName);
                if (counter == null)
                {
                    localResponse.HttpResponseCode = (int)HttpStatusCode.NotFound;
                    localResponse.ErrorMessage = "Counter not found.";
                }
                else
                {
                    Events.Write.BeginProcessingQuery(RestCommands.CounterQueryCommand, counterName);
                    localResponse.Samples = counter.Query(queryParameters).ToList();

                    if (localResponse.Samples.Count > 0)
                    {
                        Events.Write.EndProcessingQuery("CounterQuery", counter.Name, 200);
                        localResponse.HttpResponseCode = (int)HttpStatusCode.OK;
                    }
                    else
                    {
                        Events.Write.EndProcessingQuery("CounterQuery", counter.Name, 404);
                        localResponse.HttpResponseCode = (int)HttpStatusCode.NotFound;
                        localResponse.ErrorMessage = "No data matched query.";
                    }
                }
            }
            catch (Exception ex)
            {
                if (ex is FormatException || ex is ArgumentOutOfRangeException || ex is ArgumentException
                    || ex is KeyNotFoundException || ex is NotSupportedException)
                {
                     localResponse.HttpResponseCode = (int)HttpStatusCode.BadRequest;
                     localResponse.ErrorMessage = ex.Message;
                }
                else
                {
                    throw;
                }
            }

            // wait for the distributed request to finish
            var distributedResponse = await distributedRequest;

            if (fanoutRequest != null && fanoutRequest.IncludeRequestDiagnostics && localResponse.RequestDetails != null)
            {
                localResponse.RequestDetails.Add(new RequestDetails
                {
                    Server = new MetricSystem.ServerInfo
                             {
                                 Hostname = this.server.Hostname,
                                 Port = this.server.Port,
                                 Datacenter = this.server.Datacenter,
                                 MachineFunction = this.server.MachineFunction,
                             },
                    HttpResponseCode = localResponse.HttpResponseCode,
                    StatusDescription = localResponse.ErrorMessage ?? string.Empty,
                    Status = localResponse.HttpResponseCode == (short)HttpStatusCode.OK ? RequestStatus.Success : RequestStatus.ServerFailureResponse,
                    IsAggregator = distributedResponse != null
                });
            }

            var response = MergeResponses(queryParameters, localResponse,
                                          DistributedQueryClient.ShouldMergeTimeBuckets(queryParameters),
                                          distributedResponse);

            return response;
        }
示例#14
0
        internal async Task <CounterQueryResponse> Query(string counterName, TieredRequest fanoutRequest,
                                                         DimensionSpecification queryParameters)
        {
            Task <CounterQueryResponse> distributedRequest;

            // if fanout is required, start it here (don't block)
            if (fanoutRequest != null && fanoutRequest.Sources != null && fanoutRequest.Sources.Count > 0)
            {
                distributedRequest = this.server.CreateQueryClient(fanoutRequest)
                                     .CounterQuery(counterName, fanoutRequest, queryParameters,
                                                   DimensionSet.FromQueryParameters(queryParameters));
            }
            else
            {
                distributedRequest = Task.FromResult <CounterQueryResponse>(null);
            }

            // get the local data (in parallel to the async distributed request running, if necessary)
            var localResponse = new CounterQueryResponse();

            try
            {
                var counter = this.server.DataManager.GetCounter <Counter>(counterName);
                if (counter == null)
                {
                    localResponse.HttpResponseCode = (int)HttpStatusCode.NotFound;
                    localResponse.ErrorMessage     = "Counter not found.";
                }
                else
                {
                    Events.Write.BeginProcessingQuery(RestCommands.CounterQueryCommand, counterName);
                    localResponse.Samples = counter.Query(queryParameters).ToList();

                    if (localResponse.Samples.Count > 0)
                    {
                        Events.Write.EndProcessingQuery("CounterQuery", counter.Name, 200);
                        localResponse.HttpResponseCode = (int)HttpStatusCode.OK;
                    }
                    else
                    {
                        Events.Write.EndProcessingQuery("CounterQuery", counter.Name, 404);
                        localResponse.HttpResponseCode = (int)HttpStatusCode.NotFound;
                        localResponse.ErrorMessage     = "No data matched query.";
                    }
                }
            }
            catch (Exception ex)
            {
                if (ex is FormatException || ex is ArgumentOutOfRangeException || ex is ArgumentException ||
                    ex is KeyNotFoundException || ex is NotSupportedException)
                {
                    localResponse.HttpResponseCode = (int)HttpStatusCode.BadRequest;
                    localResponse.ErrorMessage     = ex.Message;
                }
                else
                {
                    throw;
                }
            }

            // wait for the distributed request to finish
            var distributedResponse = await distributedRequest;

            if (fanoutRequest != null && fanoutRequest.IncludeRequestDiagnostics && localResponse.RequestDetails != null)
            {
                localResponse.RequestDetails.Add(new RequestDetails
                {
                    Server = new MetricSystem.ServerInfo
                    {
                        Hostname        = this.server.Hostname,
                        Port            = this.server.Port,
                        Datacenter      = this.server.Datacenter,
                        MachineFunction = this.server.MachineFunction,
                    },
                    HttpResponseCode  = localResponse.HttpResponseCode,
                    StatusDescription = localResponse.ErrorMessage ?? string.Empty,
                    Status            = localResponse.HttpResponseCode == (short)HttpStatusCode.OK ? RequestStatus.Success : RequestStatus.ServerFailureResponse,
                    IsAggregator      = distributedResponse != null
                });
            }

            var response = MergeResponses(queryParameters, localResponse,
                                          DistributedQueryClient.ShouldMergeTimeBuckets(queryParameters),
                                          distributedResponse);

            return(response);
        }
        /// <summary>
        /// Add all samples from this query response. Combine overlapping time buckets as they are encountered.
        /// This method IS threadsafe against itself.
        /// </summary>
        /// <param name="response"></param>
        public void AddMachineResponse(CounterQueryResponse response)
        {
            if (response == null)
            {
                throw new ArgumentNullException("response");
            }

            if (response.RequestDetails != null)
            {
                lock (this.requestDetails)
                {
                    this.requestDetails.AddRange(response.RequestDetails);
                }
            }

            if (response.Samples == null)
            {
                return;
            }

            foreach (var sample in response.Samples)
            {
                var baseSample = sample;

                var            hashKey         = this.dimensionSet.CreateKey(sample.Dimensions);
                var            sampleTimeRange = CreateTimeRange(baseSample);
                var            rangesToRemove  = new List <TimeRange>();
                SampleCombiner combiner        = null;
                SortedList <TimeRange, SampleCombiner> aggregatedBuckets;

                // grab the appropriate bucket list
                lock (this.dataDictionary)
                {
                    if (!this.dataDictionary.TryGetValue(hashKey, out aggregatedBuckets))
                    {
                        aggregatedBuckets = new SortedList <TimeRange, SampleCombiner>();
                        this.dataDictionary.Add(hashKey, aggregatedBuckets);
                    }
                }

                lock (aggregatedBuckets)
                {
                    // The buckets are ordered by start time - thus it is safe to merge and continue to
                    // walk forward as we cannot ever merge which requires a backwards reprocess
                    foreach (var bucket in aggregatedBuckets)
                    {
                        var existingRange = bucket.Key;

                        // did we get past the end of the range we are interested in?
                        if (existingRange.Start > sampleTimeRange.End)
                        {
                            break;
                        }

                        if (existingRange.IntersectsWith(sampleTimeRange))
                        {
                            sampleTimeRange = TimeRange.Merge(sampleTimeRange, existingRange);
                            rangesToRemove.Add(bucket.Key);

                            // if this is the first merge, just add this sample
                            if (combiner == null)
                            {
                                combiner = bucket.Value;
                                combiner.AddSample(sample);
                                combiner.MachineCount += SampleCombiner.ExtractMachineCount(sample);
                            }
                            else
                            {
                                // this is a N-merge (N > 1), thus sample is already accounted for in the combiner. Merge the values
                                combiner.Merge(bucket.Value);
                            }
                        }
                    }

                    // if there was no merge, then create a new bucket with this sample
                    if (combiner == null)
                    {
                        combiner = new SampleCombiner(sample)
                        {
                            MachineCount = SampleCombiner.ExtractMachineCount(sample)
                        };
                    }

                    // remove the merged items and add the new item
                    foreach (var range in rangesToRemove)
                    {
                        aggregatedBuckets.Remove(range);
                    }

                    aggregatedBuckets.Add(sampleTimeRange, combiner);
                }
            }
        }