/**
* Copyright 2015-2016 The OpenZipkin Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package zipkin.collector.zookeeper;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This calculates a new sample rate given the input measurements or returns empty if the current
* rate is fine.
*
* <p>The sample rate is calculated using a discounted average. The discount rate is low (0.09),
* which prefers larger numbers. This is used to ensure large measurements (like a traffic spike)
* trip a lower sample rate, while small measurements (like a new collector instance) don't.
*/
final class SampleRateCalculator implements Function<Optional<List<Integer>>, Optional<Float>> {
final Logger log = LoggerFactory.getLogger(SampleRateCalculator.class);
private final AtomicInteger target;
private final AtomicReference<Float> sampleRate;
private final float discountRate = 0.09f;
private final float threshold = 0.05f;
SampleRateCalculator(AtomicInteger target, AtomicReference<Float> sampleRate) {
this.target = target;
this.sampleRate = sampleRate;
}
@Override public Optional<Float> apply(Optional<List<Integer>> input) {
if (!input.isPresent()) return Optional.empty();
List<Integer> measurements = input.get();
int discountedAverage = discountedAverage(measurements, discountRate);
log.debug("{} discounted average measurement: {}", discountRate, discountedAverage);
if (discountedAverage <= 0) return Optional.empty();
float oldSampleRate = sampleRate.get();
float newSampleRate = Math.min(1.0f, oldSampleRate * target.get() / discountedAverage);
float change = Math.abs(oldSampleRate - newSampleRate) / oldSampleRate;
if (change > 0.0f) {
log.debug("Sample rate changed {} from {} to {}", change, oldSampleRate, sampleRate);
if (change < threshold) {
log.debug("Sample rate change was less than {} threshold. Ignoring", threshold);
}
}
return change >= threshold ? Optional.of(newSampleRate) : Optional.empty();
}
static int discountedAverage(List<Integer> measurements, float discountRate) {
double discountTotal = 0;
double discountedVals = 0;
for (int i = 0; i < measurements.size(); i++) {
double discount = Math.pow(discountRate, i);
discountTotal += discount;
discountedVals += discount * measurements.get(i);
}
return (int) (discountedVals / discountTotal);
}
}