/**
* 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.ArrayDeque;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Takes measurements by name and returns a summarized list, or empty. Empty is returned until
* measurements are notable enough to warrant recalculating a sample rate.
*/
final class SampleRateCalculatorInput
implements Function<Map<String, Integer>, Optional<List<Integer>>> {
final Logger log = LoggerFactory.getLogger(SampleRateCalculatorInput.class);
final AtomicInteger target; // visible for testing
private final int measurementsInWindow;
private final int sufficientThreshold;
private final int requiredOutliers;
private final ArrayDeque<Map<String, Integer>> buffer;
SampleRateCalculatorInput(ZooKeeperCollectorSampler.Builder builder, AtomicInteger target) {
this.target = target;
this.measurementsInWindow = builder.windowSize / builder.updateFrequency;
this.sufficientThreshold = builder.sufficientWindowSize / builder.updateFrequency;
this.requiredOutliers = builder.outlierThreshold / builder.updateFrequency;
this.buffer = new ArrayDeque<>(measurementsInWindow);
}
@Override public Optional<List<Integer>> apply(Map<String, Integer> nextMeasurements) {
List<Integer> measurements = addMeasurementsAndSumValues(nextMeasurements);
boolean targetSet = target.get() > 0;
boolean sufficientMeasurementsInWindow = measurements.size() >= sufficientThreshold;
boolean allMeasurementsInWindowArePositive = measurements.stream().allMatch(j -> j > 0);
boolean lastMeasurementsWereOutliers = lastMeasurementsWereOutliers(measurements);
if (targetSet
&& allMeasurementsInWindowArePositive
&& sufficientMeasurementsInWindow
&& lastMeasurementsWereOutliers) {
log.debug("summarized measurements in window warrant a new sample rate: {}", measurements);
return Optional.of(measurements);
}
return Optional.empty();
}
/**
* Returns a list of aggregate rate measurements, in insertion order
*/
private List<Integer> addMeasurementsAndSumValues(Map<String, Integer> nextMeasurements) {
synchronized (buffer) {
if (buffer.size() == measurementsInWindow) {
buffer.remove();
}
buffer.add(nextMeasurements);
log.debug("summarizing measurements in window: {}", buffer);
return buffer.stream()
.map(m -> m.values().stream().mapToInt(Integer::intValue).sum())
.collect(Collectors.toList());
}
}
/** The most recent data has to include only outliers to warrant a sample rate change. */
private boolean lastMeasurementsWereOutliers(List<Integer> measurements) {
// outliers have to be the most recent measurements
int i = Math.max(measurements.size() - requiredOutliers, 0);
int outliers = 0;
while (i < measurements.size() && isOutlier(measurements.get(i++))) {
outliers++;
}
return outliers == requiredOutliers;
}
/** An outlier is a measurement that is outside 15% of the target */
private boolean isOutlier(int measurement) {
int target = this.target.get();
return Math.abs(measurement - target) > target * 0.15;
}
}