Skip to content

Commit

Permalink
Fix slowTickIfNecessary with infrequently used EWMA (#3929)
Browse files Browse the repository at this point in the history
  • Loading branch information
aweisberg authored Oct 3, 2024
1 parent f0bb90d commit 6794019
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 4 deletions.
8 changes: 8 additions & 0 deletions metrics-core/src/main/java/com/codahale/metrics/EWMA.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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();
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}

0 comments on commit 6794019

Please sign in to comment.