/* * Copyright (C) 2015 SoftIndex LLC. * * 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 io.datakernel.jmx; import static java.lang.Math.exp; import static java.lang.Math.log; /** * Computes total amount of events and dynamic rate using exponential smoothing algorithm * <p/> * Class is supposed to work in single thread */ public final class EventStats implements JmxRefreshableStats<EventStats> { private static final long TOO_LONG_TIME_PERIOD_BETWEEN_REFRESHES = 60 * 60 * 1000; // 1 hour private static final double LN_2 = log(2); private long lastTimestampMillis; private int lastCount; private long totalCount; private double smoothedCount; private double smoothedTimeSeconds; private double smoothedRate; private double smoothingWindow; private double smoothingWindowCoef; // fields for aggregation private int addedStats; // region builders private EventStats(double smoothingWindow) { this.smoothingWindow = smoothingWindow; this.smoothingWindowCoef = calculateSmoothingWindowCoef(smoothingWindow); } private EventStats() { // create accumulator instance, smoothing window will be taken from actual stats this.smoothingWindow = -1; this.smoothingWindowCoef = -1; } public static EventStats createAccumulator() { return new EventStats(); } /** * Creates new EventStats with specified smoothing window * * @param smoothingWindow in seconds */ public static EventStats create(double smoothingWindow) { return new EventStats(smoothingWindow); } // endregion private static double calculateSmoothingWindowCoef(double smoothingWindow) { return -(LN_2 / smoothingWindow); } /** * Resets rate to zero */ public void resetStats() { lastCount = 0; totalCount = 0; lastTimestampMillis = 0; smoothedCount = 0; smoothedRate = 0; smoothedTimeSeconds = 0; } /** * Records event and updates rate */ public void recordEvent() { lastCount++; } /** * Records events and updates rate * * @param events number of events */ public void recordEvents(int events) { lastCount += events; } @Override public void refresh(long timestamp) { long timeElapsedMillis = timestamp - lastTimestampMillis; if (isTimePeriodValid(timeElapsedMillis)) { double timeElapsedSeconds = timeElapsedMillis * 0.001; double smoothingFactor = exp(timeElapsedSeconds * smoothingWindowCoef); smoothedCount = lastCount + smoothedCount * smoothingFactor; smoothedTimeSeconds = timeElapsedSeconds + smoothedTimeSeconds * smoothingFactor; smoothedRate = smoothedCount / smoothedTimeSeconds; } else { // skip stats of last time period } totalCount += lastCount; lastCount = 0; lastTimestampMillis = timestamp; } private static boolean isTimePeriodValid(long timePeriod) { return timePeriod < TOO_LONG_TIME_PERIOD_BETWEEN_REFRESHES && timePeriod > 0; } @Override public void add(EventStats anotherStats) { totalCount += anotherStats.totalCount; smoothedCount += anotherStats.smoothedCount; smoothedRate += anotherStats.smoothedRate; if (addedStats == 0) { smoothingWindow = anotherStats.smoothingWindow; smoothingWindowCoef = anotherStats.smoothingWindowCoef; } else { // all stats should have same smoothing window, -1 means smoothing windows differ in stats, which is error if (smoothingWindow != anotherStats.smoothingWindow) { smoothingWindow = -1; smoothingWindowCoef = calculateSmoothingWindowCoef(smoothingWindow); } } addedStats++; } /** * Returns smoothed value of rate in events per second. * <p/> * Value may be delayed. Last update was performed during {@code recordEvent()} method invocation * * @return smoothed value of rate in events per second */ @JmxAttribute(optional = true) public double getSmoothedRate() { return smoothedRate; } /** * Returns total amount of recorded events * * @return total amount of recorded events */ @JmxAttribute(optional = true) public long getTotalCount() { return totalCount; } @JmxAttribute(optional = true) public double getSmoothingWindow() { return smoothingWindow; } @JmxAttribute(optional = true) public void setSmoothingWindow(double smoothingWindow) { this.smoothingWindow = smoothingWindow; this.smoothingWindowCoef = calculateSmoothingWindowCoef(smoothingWindow); } @JmxAttribute public String get() { return toString(); } @Override public String toString() { return String.format("%d @ %.3f/s", getTotalCount(), getSmoothedRate()); } }