org.springframework.integration.support.management.ExponentialMovingAverageRatio.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.integration.support.management.ExponentialMovingAverageRatio.java

Source

/*
 * Copyright 2009-2019 the original author or 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
 *
 *      https://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.springframework.integration.support.management;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.Iterator;
import java.util.List;

/**
 * Cumulative statistics for success ratio with higher weight given to recent data.
 * Clients call {@link #success()} or {@link #failure()} when an event occurs, and the ratio of success to total events
 * is accumulated. Older values are given exponentially smaller weight, with a decay factor determined by a duration
 * chosen by the client. The rate measurement weights decay in two dimensions:
 * <ul>
 * <li>in time according to the lapse period supplied: <code>weight = exp((t0-t)/T)</code> where <code>t0</code> is the
 * last measurement time, <code>t</code> is the current time and <code>T</code> is the lapse period)</li>
 * <li>per measurement according to the lapse window supplied: <code>weight = exp(-i/L)</code> where <code>L</code> is
 * the lapse window and <code>i</code> is the sequence number of the measurement.</li>
 * </ul>
 * For performance reasons, the calculation is performed on retrieval,
 * {@code window * 5} samples are retained meaning that the earliest retained value contributes just 0.5% to the
 * sum.
 * @deprecated in favor of dimensional metrics via
 * {@link org.springframework.integration.support.management.metrics.MeterFacade}.
 * Built-in metrics will be removed in a future release.
 * @author Dave Syer
 * @author Gary Russell
 * @author Steven Swor
 * @since 2.0
 */
@Deprecated
public class ExponentialMovingAverageRatio {

    private volatile double t0;

    private volatile long count;

    private volatile double min = Double.MAX_VALUE;

    private volatile double max;

    private final double lapse;

    private final Deque<Long> times = new ArrayDeque<>();

    private final Deque<Integer> values = new ArrayDeque<>();

    private final int retention;

    private final int window;

    private final double factor;

    /**
     * @param lapsePeriod the exponential lapse rate for the rate average (in seconds)
     * @param window the exponential lapse window (number of measurements)
     */
    public ExponentialMovingAverageRatio(double lapsePeriod, int window) {
        this(lapsePeriod, window, false);
    }

    /**
     * @param lapsePeriod the exponential lapse rate for the rate average (in seconds)
     * @param window the exponential lapse window (number of measurements)
     * @param millis when true, analyze the data as milliseconds instead of the native nanoseconds
     * @since 4.2
     */
    public ExponentialMovingAverageRatio(double lapsePeriod, int window, boolean millis) {
        this.lapse = lapsePeriod > 0 ? 0.001 / lapsePeriod : 0; // convert to milliseconds
        this.window = window;
        this.retention = window * 5;
        this.factor = millis ? 1000000 : 1;
        this.t0 = System.nanoTime() / this.factor;
    }

    /**
     * Add a new event with successful outcome.
     */
    public void success() {
        append(1, System.nanoTime());
    }

    /**
     * Add a new event with successful outcome at time t.
     * @param t the System.nanoTime().
     */
    public void success(long t) {
        append(1, t);
    }

    /**
     * Add a new event with failed outcome.
     */
    public void failure() {
        append(0, System.nanoTime());
    }

    /**
     * Add a new event with failed outcome at time t.
     * @param t a new event with failed outcome in milliseconds.
     */
    public void failure(long t) {
        append(0, t);
    }

    public synchronized void reset() {
        this.t0 = System.nanoTime() / this.factor;
        this.times.clear();
        this.values.clear();
        this.count = 0;
        this.max = 0;
        this.min = Double.MAX_VALUE;
    }

    private synchronized void append(int value, long t) {
        if (this.times.size() == this.retention) {
            this.times.poll();
            this.values.poll();
        }
        this.times.add(t);
        this.values.add(value);
        this.count++; //NOSONAR - false positive, we're synchronized
    }

    private Statistics calcStatic() {
        List<Long> copyTimes;
        List<Integer> copyValues;
        long currentCount;
        synchronized (this) {
            copyTimes = new ArrayList<Long>(this.times);
            copyValues = new ArrayList<Integer>(this.values);
            currentCount = this.count;
        }
        ExponentialMovingAverage cumulative = new ExponentialMovingAverage(this.window);
        double currentT0 = 0;
        double sum = 0;
        double weight = 0;
        double currentMin = this.min;
        double currentMax = this.max;
        int size = copyTimes.size();
        Iterator<Integer> valuesIterator = copyValues.iterator();
        for (Long time : copyTimes) {
            double t = time / this.factor;
            if (size == 1) {
                currentT0 = this.t0;
            } else if (currentT0 == 0) {
                currentT0 = t;
                valuesIterator.next();
                continue;
            }
            double alpha = Math.exp((currentT0 - t) * this.lapse);
            currentT0 = t;
            sum = alpha * sum + valuesIterator.next();
            weight = alpha * weight + 1;
            double value = sum / weight;
            if (value > currentMax) {
                currentMax = value;
            }
            if (value < currentMin) {
                currentMin = value;
            }
            cumulative.append(value);
        }
        synchronized (this) {
            if (currentMax > this.max) {
                this.max = currentMax;
            }
            if (currentMin < this.min) {
                this.min = currentMin;
            }
        }
        return new Statistics(currentCount, currentMin < Double.MAX_VALUE ? currentMin : 0, currentMax,
                cumulative.getMean(), cumulative.getStandardDeviation());
    }

    /**
     * @return the number of measurements recorded
     */
    public int getCount() {
        return (int) this.count;
    }

    /**
     * @return the number of measurements recorded
     */
    public long getCountLong() {
        return this.count;
    }

    /**
     * @return the time in seconds since the last measurement
     */
    public double getTimeSinceLastMeasurement() {
        double delta = System.nanoTime() - lastTime();
        return delta / 1000. / this.factor;
    }

    /**
     * @return the mean success rate
     */
    public double getMean() {
        if (this.count == 0) {
            // Optimistic to start: success rate is 100%
            return 1;
        }
        return decayMean(calcStatic());
    }

    /**
     * Decay the mean using the current time.
     * @param staticStats the static statistics.
     * @return the new mean.
     */
    private double decayMean(Statistics statistics) {
        double t = System.nanoTime() / this.factor;
        double mean = statistics.getMean();
        double alpha = Math.exp((lastTime() / this.factor - t) * this.lapse);
        return alpha * mean + 1 - alpha;
    }

    private synchronized double lastTime() {
        if (this.times.size() > 0) {
            return this.times.peekLast();
        } else {
            return this.t0 * this.factor;
        }
    }

    /**
     * @return the approximate standard deviation of the success rate measurements
     */
    public double getStandardDeviation() {
        return calcStatic().getStandardDeviation();
    }

    /**
     * @return the maximum value recorded of the exponential weighted average (per measurement) success rate
     */
    public double getMax() {
        return calcStatic().getMax();
    }

    /**
     * @return the minimum value recorded of the exponential weighted average (per measurement) success rate
     */
    public double getMin() {
        return calcStatic().getMin();
    }

    /**
     * @return summary statistics (count, mean, standard deviation etc.)
     */
    public Statistics getStatistics() {
        Statistics staticStats = calcStatic();
        staticStats.setMean(decayMean(staticStats));
        return staticStats;
    }

    @Override
    public String toString() {
        return String.format("[%s, timeSinceLast=%f]", getStatistics(), getTimeSinceLastMeasurement());
    }

}