/// <summary>
        /// Constructor that validates the query request and initializes internal state and fixes/cleans up the individual queries
        /// </summary>
        public BatchResponseAggregator(BatchQueryRequest request)
        {
            if (request == null)
            {
                throw new ArgumentNullException("request");
            }

            if (request.Queries == null || request.Queries.Count == 0)
            {
                throw new ArgumentException("No queries provided", "request");
            }

            foreach (var counterRequest in request.Queries)
            {
                if (string.IsNullOrEmpty(counterRequest.UserContext))
                {
                    counterRequest.UserContext = Guid.NewGuid().ToString("D");
                }
                else if (this.requestMapping.ContainsKey(counterRequest.UserContext))
                {
                    throw new ArgumentException("Duplicate user context in request");
                }

                var counterAggregator = new CounterAggregator(counterRequest.QueryParameters);
                var shouldMergeSamples = DistributedQueryClient.ShouldMergeTimeBuckets(counterRequest.QueryParameters);

                counterRequest.QueryParameters = counterAggregator.ApplyPercentileCalculationAggregation(counterRequest.QueryParameters);
                this.requestMapping.Add(counterRequest.UserContext, new Tuple<bool, CounterAggregator>(shouldMergeSamples, counterAggregator));
            }
        }
        /// <summary>
        /// Run a distributed (tiered) query based on the parameters in the original request. Split the sources into N blocks (N is determined
        /// by the MaxFanout setting). Client will select a (random) leader for each block and send the request downstream. Client will merge all responses
        /// and in the case of failure will include diagnostics for each server in the chunk
        /// </summary>
        /// <param name="counterName">Name of the counter to query.</param>
        /// <param name="request">Base request to be distributed into blocks.</param>
        /// <param name="queryParameters">Query parameters for the counter.</param>
        /// <param name="dimensionSet">DimensionSet used to quickly merge per-sample query dimensions.</param>
        /// <returns>Aggregated response optionally returning per-source diagnostics. </returns>
        public async Task <CounterQueryResponse> CounterQuery(string counterName, TieredRequest request,
                                                              IDictionary <string, string> queryParameters,
                                                              DimensionSet dimensionSet)
        {
            if (string.IsNullOrEmpty(counterName))
            {
                throw new ArgumentException("No counter name specified", "counterName");
            }

            if (request == null)
            {
                throw new ArgumentNullException("request");
            }

            if (dimensionSet == null)
            {
                throw new ArgumentNullException("dimensionSet");
            }

            var counterAggregator = new CounterAggregator(dimensionSet);

            // if the client requested a percentile and there are multiple sources we let the aggregator apply that filtering after data collection
            if (HasSources(request))
            {
                queryParameters = counterAggregator.ApplyPercentileCalculationAggregation(queryParameters);
            }

            var command = Protocol.BuildCounterRequestCommand(RestCommands.CounterQueryCommand, counterName, queryParameters);

            return(await this.Execute(request, command, counterAggregator.AddMachineResponse,
                                      () => counterAggregator.GetResponse(ShouldMergeTimeBuckets(queryParameters))));
        }
        /// <summary>
        /// Constructor that validates the query request and initializes internal state and fixes/cleans up the individual queries
        /// </summary>
        public BatchResponseAggregator(BatchQueryRequest request)
        {
            if (request == null)
            {
                throw new ArgumentNullException("request");
            }

            if (request.Queries == null || request.Queries.Count == 0)
            {
                throw new ArgumentException("No queries provided", "request");
            }

            foreach (var counterRequest in request.Queries)
            {
                if (string.IsNullOrEmpty(counterRequest.UserContext))
                {
                    counterRequest.UserContext = Guid.NewGuid().ToString("D");
                }
                else if (this.requestMapping.ContainsKey(counterRequest.UserContext))
                {
                    throw new ArgumentException("Duplicate user context in request");
                }

                var counterAggregator  = new CounterAggregator(counterRequest.QueryParameters);
                var shouldMergeSamples = DistributedQueryClient.ShouldMergeTimeBuckets(counterRequest.QueryParameters);

                counterRequest.QueryParameters = counterAggregator.ApplyPercentileCalculationAggregation(counterRequest.QueryParameters);
                this.requestMapping.Add(counterRequest.UserContext, new Tuple <bool, CounterAggregator>(shouldMergeSamples, counterAggregator));
            }
        }