All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.meeuw.math.windowed.WindowedEventRate Maven / Gradle / Ivy

/*
 *  Copyright 2022 Michiel Meeuwissen
 *
 *    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.meeuw.math.windowed;

import jakarta.annotation.PreDestroy;

import java.time.Clock;
import java.time.Duration;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.*;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.meeuw.configuration.ConfigurationService;
import org.meeuw.math.WithUnits;
import org.meeuw.math.statistics.StatisticalLong;
import org.meeuw.math.uncertainnumbers.CompareConfiguration;
import org.meeuw.math.uncertainnumbers.UncertainDouble;
import org.meeuw.math.uncertainnumbers.field.UncertainDoubleElement;
import org.meeuw.math.uncertainnumbers.field.UncertainReal;


/**
 * An implementation of {@link Windowed} with {@link AtomicLong} values.
 * 

* Keeps track of an event rate in a current window of a given duration * E.g. If you want to report a certain event rate average for the last 5 minutes. *

* Every 'bucket' of the window is just counter, and the associated {@link #getWindowValue()} is just the sum. *

* Logically this class also provides {@link #getRate(TimeUnit)}. * * @author Michiel Meeuwissen * @since 0.38 */ public class WindowedEventRate extends Windowed implements AutoCloseable, IntConsumer, UncertainDouble, WithUnits { private static final ThreadGroup THREAD_GROUP = new ThreadGroup("mihxil-statistics"); private static ScheduledExecutorService backgroundExecutor; private static synchronized ScheduledExecutorService getBackgroundExecutor() { if (backgroundExecutor == null) { backgroundExecutor = Executors .newScheduledThreadPool(0, new ThreadFactory() { int counter = 0; @Override public Thread newThread(Runnable r) { Thread t = new Thread(THREAD_GROUP, r, "WindowedEventRate-" + (counter++)); t.setDaemon(true); return t; } }) ; } return backgroundExecutor; } private final ScheduledFuture scheduledReporter; /** * @param window The total time window for which events are going to be measured (or null if bucketDuration specified) * @param bucketDuration The duration of one bucket (or null if window specified). * @param bucketCount The number of buckets the total window time is to be divided in. * @param reporter A consumer that will be called every {@code bucketDuration} * @param executor The executor to use for the reporter (or null if the default one should be used) */ @lombok.Builder private WindowedEventRate( @Nullable Duration window, @Nullable Duration bucketDuration, @Nullable Integer bucketCount, @Nullable Consumer reporter, @Nullable ScheduledExecutorService executor, @NonNull BiConsumer>@Nullable[] eventListenersArray, @Nullable Clock clock ) { super(AtomicLong.class, window, bucketDuration, bucketCount, eventListenersArray, clock); if (reporter != null) { scheduledReporter = (executor == null ? getBackgroundExecutor() : executor).scheduleAtFixedRate( () -> { try { reporter.accept(WindowedEventRate.this); } catch (Throwable t) { Logger.getLogger(WindowedEventRate.class.getName()).log(Level.WARNING, t.getMessage(), t); } }, 0, this.bucketDuration, TimeUnit.MILLISECONDS); } else { scheduledReporter = null; } } /** * @return rate in /s (See {@link #getRate()} */ @Override public double doubleValue() { return getRate(); } /** * @return the uncertainty in the rate in /s (See {@link #getRate()} */ @Override public double doubleUncertainty() { shiftBuckets(); StatisticalLong statisticalLong = new StatisticalLong(); for (AtomicLong bucket : getRelevantBuckets()) { statisticalLong.enter(bucket.get()); } return 1000 * statisticalLong.doubleUncertainty() * getRelevantBuckets().length / getTotalDuration().toMillis(); } public UncertainReal getUncertainRate() { return immutableInstanceOfPrimitives(getRate(), doubleUncertainty()); } @Override public String getUnitsAsString() { return "/s"; } @Override public UncertainDoubleElement immutableInstanceOfPrimitives(double value, double uncertainty) { return new UncertainDoubleElement(value, uncertainty); } /** * If using a reporter, cancels the associated {@link ScheduledFuture}. */ @Override @PreDestroy public void close() { if (scheduledReporter != null) { scheduledReporter.cancel(true); } } @Override protected AtomicLong initialValue() { return new AtomicLong(0L); } @Override protected boolean resetValue(AtomicLong value) { value.set(0); return true; } public WindowedEventRate(int unit, TimeUnit timeUnit, int bucketCount) { this(Duration.ofMillis( TimeUnit.MILLISECONDS.convert(unit, timeUnit) * bucketCount), null, bucketCount, null, null, null, null); } public WindowedEventRate(int unit, TimeUnit timeUnit) { this(unit, timeUnit, 100); } public WindowedEventRate(TimeUnit timeUnit) { this(1, timeUnit); } public void newEvent() { currentBucket().getAndIncrement(); } /** * Registers a number of events at once. * @param count The number of events to register */ public void newEvents(int count) { currentBucket().getAndAdd(count); } /** * Accepting an integer is equivalent to {@link #newEvents(int)} */ @Override public void accept(int value) { newEvents(value); } public long getTotalCount() { shiftBuckets(); long totalCount = 0; for (AtomicLong bucket : buckets) { totalCount += bucket.get(); } return totalCount; } @Override public AtomicLong getWindowValue() { return new AtomicLong(getTotalCount()); } /** * The current rate as a number of events per given unit of time. * See also {@link #getRate(Duration)} and {@link #getRate()} * @param unit The unit of time to express the rate in. * @return the rate as a double */ public double getRate(TimeUnit unit) { long totalCount = getTotalCount(); Duration relevantDuration = getRelevantDuration(); return ((double) totalCount * TimeUnit.NANOSECONDS.convert(1, unit)) / relevantDuration.toNanos(); } /** * See also {@link #getRate(TimeUnit)} and {@link #getRate()} * @return the rate as a double * @param perInterval The reciprocal unit of the rate to report e.g. 1 second or 1 minute. */ public double getRate(final Duration perInterval) { long totalCount = getTotalCount(); Duration relevantDuration = getRelevantDuration(); return ((double) totalCount * perInterval.toNanos()) / relevantDuration.toNanos(); } /** * @return the current rate as a number of events per SI unit of time (the second) * @see #getRate(TimeUnit) */ public double getRate() { return getRate(TimeUnit.SECONDS); } @Override public boolean strictlyEquals(Object o) { if (!(o instanceof WindowedEventRate)) { return false; } WindowedEventRate other = (WindowedEventRate) o; return getRate() == other.getRate(); } @Override public boolean equals(Object o) { if ( ConfigurationService.getConfigurationAspect(CompareConfiguration.class).isEqualsIsStrict()) { return strictlyEquals(o); } else { return eq((WindowedEventRate) o); } } public boolean eq(WindowedEventRate o) { return eq(o, 1); } @Override public String toString() { return "" + getUncertainRate() + " " + getUnitsAsString() + (isWarmingUp() ? " (warming up)" : ""); } public static class Builder { @SafeVarargs public final Builder eventListeners(BiConsumer>... eventListeners) { return eventListenersArray(eventListeners); } } @PreDestroy public static void shutdown() { backgroundExecutor.shutdown(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy