/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ /* * Forked from https://github.com/codahale/metrics */ package org.apache.solr.util.stats; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import static java.lang.Math.sqrt; /** * A metric which calculates the distribution of a value. * * @see <a href="http://www.johndcook.com/standard_deviation.html">Accurately computing running * variance</a> */ public class Histogram { private static final int DEFAULT_SAMPLE_SIZE = 1028; private static final double DEFAULT_ALPHA = 0.015; /** * The type of sampling the histogram should be performing. */ enum SampleType { /** * Uses a uniform sample of 1028 elements, which offers a 99.9% confidence level with a 5% * margin of error assuming a normal distribution. */ UNIFORM { @Override public Sample newSample() { return new UniformSample(DEFAULT_SAMPLE_SIZE); } }, /** * Uses an exponentially decaying sample of 1028 elements, which offers a 99.9% confidence * level with a 5% margin of error assuming a normal distribution, and an alpha factor of * 0.015, which heavily biases the sample to the past 5 minutes of measurements. */ BIASED { @Override public Sample newSample() { return new ExponentiallyDecayingSample(DEFAULT_SAMPLE_SIZE, DEFAULT_ALPHA); } }; public abstract Sample newSample(); } private final Sample sample; private final AtomicLong min = new AtomicLong(); private final AtomicLong max = new AtomicLong(); private final AtomicLong sum = new AtomicLong(); // These are for the Welford algorithm for calculating running variance // without floating-point doom. private final AtomicReference<double[]> variance = new AtomicReference<>(new double[]{-1, 0}); // M, S private final AtomicLong count = new AtomicLong(); /** * Creates a new {@link Histogram} with the given sample type. * * @param type the type of sample to use */ Histogram(SampleType type) { this(type.newSample()); } /** * Creates a new {@link Histogram} with the given sample. * * @param sample the sample to create a histogram from */ Histogram(Sample sample) { this.sample = sample; clear(); } /** * Clears all recorded values. */ public void clear() { sample.clear(); count.set(0); max.set(Long.MIN_VALUE); min.set(Long.MAX_VALUE); sum.set(0); variance.set(new double[]{ -1, 0 }); } /** * Adds a recorded value. * * @param value the length of the value */ public void update(int value) { update((long) value); } /** * Adds a recorded value. * * @param value the length of the value */ public void update(long value) { count.incrementAndGet(); sample.update(value); setMax(value); setMin(value); sum.getAndAdd(value); updateVariance(value); } /** * Returns the number of values recorded. * * @return the number of values recorded */ public long getCount() { return count.get(); } /* (non-Javadoc) * @see com.yammer.metrics.core.Summarizable#max() */ public double getMax() { if (getCount() > 0) { return max.get(); } return 0.0; } /* (non-Javadoc) * @see com.yammer.metrics.core.Summarizable#min() */ public double getMin() { if (getCount() > 0) { return min.get(); } return 0.0; } /* (non-Javadoc) * @see com.yammer.metrics.core.Summarizable#mean() */ public double getMean() { if (getCount() > 0) { return sum.get() / (double) getCount(); } return 0.0; } /* (non-Javadoc) * @see com.yammer.metrics.core.Summarizable#stdDev() */ public double getStdDev() { if (getCount() > 0) { return sqrt(getVariance()); } return 0.0; } /* (non-Javadoc) * @see com.yammer.metrics.core.Summarizable#sum() */ public double getSum() { return (double) sum.get(); } public Snapshot getSnapshot() { return sample.getSnapshot(); } private double getVariance() { if (getCount() <= 1) { return 0.0; } return variance.get()[1] / (getCount() - 1); } private void setMax(long potentialMax) { boolean done = false; while (!done) { final long currentMax = max.get(); done = currentMax >= potentialMax || max.compareAndSet(currentMax, potentialMax); } } private void setMin(long potentialMin) { boolean done = false; while (!done) { final long currentMin = min.get(); done = currentMin <= potentialMin || min.compareAndSet(currentMin, potentialMin); } } private void updateVariance(long value) { while (true) { final double[] oldValues = variance.get(); final double[] newValues = new double[2]; if (oldValues[0] == -1) { newValues[0] = value; newValues[1] = 0; } else { final double oldM = oldValues[0]; final double oldS = oldValues[1]; final double newM = oldM + ((value - oldM) / getCount()); final double newS = oldS + ((value - oldM) * (value - newM)); newValues[0] = newM; newValues[1] = newS; } if (variance.compareAndSet(oldValues, newValues)) { return; } } } }