/* * Copyright 2016 KairosDB 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 org.kairosdb.core.aggregator; import com.google.inject.Inject; import org.kairosdb.core.DataPoint; import org.kairosdb.core.aggregator.annotation.AggregatorName; import org.kairosdb.core.aggregator.annotation.AggregatorProperty; import org.kairosdb.core.datapoints.DoubleDataPointFactory; import org.kairosdb.core.http.rest.validation.NonZero; import org.kairosdb.util.Reservoir; import org.kairosdb.util.UniformReservoir; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import static java.lang.Math.floor; @AggregatorName( name = "percentile", description = "Finds the percentile of the data range.",properties = { @AggregatorProperty(name = "percentile", type = "double") } ) public class PercentileAggregator extends RangeAggregator { public static final Logger logger = LoggerFactory.getLogger(PercentileAggregator.class); private DoubleDataPointFactory m_dataPointFactory; @Inject public PercentileAggregator(DoubleDataPointFactory dataPointFactory) { m_dataPointFactory = dataPointFactory; } @Override public boolean canAggregate(String groupType) { return DataPoint.GROUP_NUMBER.equals(groupType); } @Override public String getAggregatedGroupType(String groupType) { return m_dataPointFactory.getGroupType(); } @NonZero private double percentile; public void setPercentile(double percentile) { this.percentile = percentile; } @Override protected RangeSubAggregator getSubAggregator() { return (new PercentileDataPointAggregator()); } private class PercentileDataPointAggregator implements RangeSubAggregator { private double[] values; private Reservoir reservoir; private double percentileValue; @Override public Iterable<DataPoint> getNextDataPoints(long returnTime, Iterator<DataPoint> dataPointRange) { reservoir = new UniformReservoir(); while (dataPointRange.hasNext()) { reservoir.update(dataPointRange.next().getDoubleValue()); } getAndSortValues(reservoir.getValues()); percentileValue = getValue(percentile); if (logger.isDebugEnabled()) { logger.debug("Aggregating the " + percentile + " percentile"); } return Collections.singletonList(m_dataPointFactory.createDataPoint(returnTime, percentileValue)); } private void getAndSortValues(double[] values){ this.values = values; Arrays.sort(this.values); } /** * Returns the value at the given quantile. * * @param quantile a given quantile, in {@code [0..1]} * @return the value in the distribution at {@code quantile} */ private double getValue(double quantile) { if (quantile < 0.0 || quantile > 1.0) { throw new IllegalArgumentException(quantile + " is not in [0..1]"); } if (values.length == 0) { return 0.0; } final double pos = quantile * (values.length + 1); if (pos < 1) { return values[0]; } if (pos >= values.length) { return values[values.length - 1]; } final double lower = values[(int) pos - 1]; final double upper = values[(int) pos]; return lower + (pos - floor(pos)) * (upper - lower); } } }