/// <summary>
        /// Initializes a new instance of the <see cref="SamplingPercentageEstimatorTelemetryProcessor"/> class.
        /// <param name="settings">Dynamic sampling estimator settings.</param>
        /// <param name="callback">Callback to invoke every time sampling percentage is evaluated.</param>
        /// <param name="next">Next TelemetryProcessor in call chain.</param>
        /// </summary>
        public SamplingPercentageEstimatorTelemetryProcessor(
            SamplingPercentageEstimatorSettings settings,
            AdaptiveSamplingPercentageEvaluatedCallback callback,
            ITelemetryProcessor next)
        {
            if (settings == null)
            {
                throw new ArgumentNullException("settings");
            }

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

            this.evaluationCallback = callback;
            this.settings           = settings;
            this.next = next;

            this.currenSamplingRate = settings.EffectiveInitialSamplingRate;

            this.itemCount = new ExponentialMovingAverageCounter(settings.EffectiveMovingAverageRatio);

            this.samplingPercentageLastChangeDateTime = DateTimeOffset.UtcNow;

            // set evaluation interval to default value if it is negative or zero
            this.evaluationInterval = this.settings.EffectiveEvaluationInterval;

            // set up timer to run math to estimate sampling percentage
            this.evaluationTimer = new Timer(
                this.EstimateSamplingPercentage,
                null,
                this.evaluationInterval,
                this.evaluationInterval);
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="SamplingPercentageEstimatorTelemetryProcessor"/> class.
        /// <param name="settings">Dynamic sampling estimator settings.</param>
        /// <param name="callback">Callback to invoke every time sampling percentage is evaluated.</param>
        /// <param name="next">Next TelemetryProcessor in call chain.</param>
        /// </summary>
        public SamplingPercentageEstimatorTelemetryProcessor(
            Channel.Implementation.SamplingPercentageEstimatorSettings settings,
            Channel.Implementation.AdaptiveSamplingPercentageEvaluatedCallback callback,
            ITelemetryProcessor next)
        {
            this.evaluationCallback = callback;
            this.settings           = settings ?? throw new ArgumentNullException(nameof(settings));
            this.next = next ?? throw new ArgumentNullException(nameof(next));

            this.CurrentSamplingRate          = settings.EffectiveInitialSamplingRate;
            this.CurrentProactiveSamplingRate = settings.EffectiveInitialSamplingRate;

            this.itemCount = new ExponentialMovingAverageCounter(settings.EffectiveMovingAverageRatio);
            this.proactivelySampledInCount = new ExponentialMovingAverageCounter(settings.EffectiveMovingAverageRatio);

            this.samplingPercentageLastChangeDateTime = PreciseTimestamp.GetUtcNow();

            // set evaluation interval to default value if it is negative or zero
            this.evaluationInterval = this.settings.EffectiveEvaluationInterval;

            // set up timer to run math to estimate sampling percentage
            this.evaluationTimer = new Timer(
                this.EstimateSamplingPercentage,
                null,
                this.evaluationInterval,
                this.evaluationInterval);
        }
        /// <summary>
        /// Callback for sampling percentage evaluation timer.
        /// </summary>
        /// <param name="state">Timer state.</param>
        private void EstimateSamplingPercentage(object state)
        {
            // get observed after-sampling eps
            double observedEps = this.itemCount.StartNewInterval() / this.evaluationInterval.TotalSeconds;

            // we see events post sampling, so get pre-sampling eps
            double beforeSamplingEps = observedEps * this.currenSamplingRate;

            // calculate suggested sampling rate
            int suggestedSamplingRate = (int)Math.Ceiling(beforeSamplingEps / this.settings.EffectiveMaxTelemetryItemsPerSecond);

            // adjust suggested rate so that it fits between min and max configured
            if (suggestedSamplingRate > this.settings.EffectiveMaxSamplingRate)
            {
                suggestedSamplingRate = this.settings.EffectiveMaxSamplingRate;
            }

            if (suggestedSamplingRate < this.settings.EffectiveMinSamplingRate)
            {
                suggestedSamplingRate = this.settings.EffectiveMinSamplingRate;
            }

            // see if evaluation interval was changed and apply change
            if (this.evaluationInterval != this.settings.EffectiveEvaluationInterval)
            {
                this.evaluationInterval = this.settings.EffectiveEvaluationInterval;
                this.evaluationTimer.Change(this.evaluationInterval, this.evaluationInterval);
            }

            // check to see if sampling rate needs changes
            bool samplingPercentageChangeNeeded = suggestedSamplingRate != this.currenSamplingRate;

            if (samplingPercentageChangeNeeded)
            {
                // check to see if enough time passed since last sampling % change
                if ((DateTimeOffset.UtcNow - this.samplingPercentageLastChangeDateTime) <
                    (suggestedSamplingRate > this.currenSamplingRate
                        ? this.settings.EffectiveSamplingPercentageDecreaseTimeout
                        : this.settings.EffectiveSamplingPercentageIncreaseTimeout))
                {
                    samplingPercentageChangeNeeded = false;
                }
            }

            // call evaluation callback if provided
            if (this.evaluationCallback != null)
            {
                // we do not want to crash timer thread knocking out the process
                // in case customer-provided callback failed
                try
                {
                    this.evaluationCallback(
                        observedEps,
                        100.0 / this.currenSamplingRate,
                        100.0 / suggestedSamplingRate,
                        samplingPercentageChangeNeeded,
                        this.settings);
                }
                catch (Exception exp)
                {
                    TelemetryChannelEventSource.Log.SamplingCallbackError(exp.ToString());
                }
            }

            if (samplingPercentageChangeNeeded)
            {
                // apply sampling percentage change
                this.samplingPercentageLastChangeDateTime = DateTimeOffset.UtcNow;
                this.currenSamplingRate = suggestedSamplingRate;
            }

            if (samplingPercentageChangeNeeded ||
                MovingAverageCoefficientChanged(this.itemCount.Coefficient, this.settings.EffectiveMovingAverageRatio))
            {
                // since we're observing event count post sampling and we're about
                // to change sampling rate or change coefficient, reset counter
                this.itemCount = new ExponentialMovingAverageCounter(this.settings.EffectiveMovingAverageRatio);
            }
        }