From 679401991c0f2cb7b5725a19c4527253027803dd Mon Sep 17 00:00:00 2001 From: aweisberg Date: Thu, 3 Oct 2024 12:59:41 -0400 Subject: [PATCH] Fix slowTickIfNecessary with infrequently used EWMA (#3929) --- .../main/java/com/codahale/metrics/EWMA.java | 8 ++++ .../metrics/ExponentialMovingAverages.java | 37 +++++++++++++++++-- .../ExponentialMovingAveragesTest.java | 26 +++++++++++++ 3 files changed, 67 insertions(+), 4 deletions(-) create mode 100644 metrics-core/src/test/java/com/codahale/metrics/ExponentialMovingAveragesTest.java diff --git a/metrics-core/src/main/java/com/codahale/metrics/EWMA.java b/metrics-core/src/main/java/com/codahale/metrics/EWMA.java index 2d2e658093..641bb728c1 100644 --- a/metrics-core/src/main/java/com/codahale/metrics/EWMA.java +++ b/metrics-core/src/main/java/com/codahale/metrics/EWMA.java @@ -81,6 +81,14 @@ public void update(long n) { uncounted.add(n); } + /** + * Set the rate to the smallest possible positive value. Used to avoid calling tick a large number of times. + */ + public void reset() { + uncounted.reset(); + rate = Double.MIN_NORMAL; + } + /** * Mark the passage of time and decay the current rate accordingly. */ diff --git a/metrics-core/src/main/java/com/codahale/metrics/ExponentialMovingAverages.java b/metrics-core/src/main/java/com/codahale/metrics/ExponentialMovingAverages.java index 9879d280ee..0a12129c7d 100644 --- a/metrics-core/src/main/java/com/codahale/metrics/ExponentialMovingAverages.java +++ b/metrics-core/src/main/java/com/codahale/metrics/ExponentialMovingAverages.java @@ -11,8 +11,28 @@ */ public class ExponentialMovingAverages implements MovingAverages { + /** + * If ticking would reduce even Long.MAX_VALUE in the 15 minute EWMA below this target then don't bother + * ticking in a loop and instead reset all the EWMAs. + */ + private static final double maxTickZeroTarget = 0.0001; + private static final int maxTicks; private static final long TICK_INTERVAL = TimeUnit.SECONDS.toNanos(5); + static + { + int m3Ticks = 1; + final EWMA m3 = EWMA.fifteenMinuteEWMA(); + m3.update(Long.MAX_VALUE); + do + { + m3.tick(); + m3Ticks++; + } + while (m3.getRate(TimeUnit.SECONDS) > maxTickZeroTarget); + maxTicks = m3Ticks; + } + private final EWMA m1Rate = EWMA.oneMinuteEWMA(); private final EWMA m5Rate = EWMA.fiveMinuteEWMA(); private final EWMA m15Rate = EWMA.fifteenMinuteEWMA(); @@ -51,10 +71,19 @@ public void tickIfNecessary() { final long newIntervalStartTick = newTick - age % TICK_INTERVAL; if (lastTick.compareAndSet(oldTick, newIntervalStartTick)) { final long requiredTicks = age / TICK_INTERVAL; - for (long i = 0; i < requiredTicks; i++) { - m1Rate.tick(); - m5Rate.tick(); - m15Rate.tick(); + if (requiredTicks >= maxTicks) { + m1Rate.reset(); + m5Rate.reset(); + m15Rate.reset(); + } + else + { + for (long i = 0; i < requiredTicks; i++) + { + m1Rate.tick(); + m5Rate.tick(); + m15Rate.tick(); + } } } } diff --git a/metrics-core/src/test/java/com/codahale/metrics/ExponentialMovingAveragesTest.java b/metrics-core/src/test/java/com/codahale/metrics/ExponentialMovingAveragesTest.java new file mode 100644 index 0000000000..2a84940065 --- /dev/null +++ b/metrics-core/src/test/java/com/codahale/metrics/ExponentialMovingAveragesTest.java @@ -0,0 +1,26 @@ +package com.codahale.metrics; + +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class ExponentialMovingAveragesTest +{ + @Test + public void testMaxTicks() + { + final Clock clock = mock(Clock.class); + when(clock.getTick()).thenReturn(0L, Long.MAX_VALUE); + final ExponentialMovingAverages ema = new ExponentialMovingAverages(clock); + ema.update(Long.MAX_VALUE); + ema.tickIfNecessary(); + final long secondNanos = TimeUnit.SECONDS.toNanos(1); + assertEquals(ema.getM1Rate(), Double.MIN_NORMAL * secondNanos, 0.0); + assertEquals(ema.getM5Rate(), Double.MIN_NORMAL * secondNanos, 0.0); + assertEquals(ema.getM15Rate(), Double.MIN_NORMAL * secondNanos, 0.0); + } +}