/// <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); } }