Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix slowTickIfNecessary with infrequently used EWMA #3929

Merged
merged 1 commit into from
Oct 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
}
}