Skip to content

Commit

Permalink
Fix slowTickIfNecessary with infrequently used EWMA
Browse files Browse the repository at this point in the history
EWMA.tickIfNecessary does an amount of work that is linear to the amount of time that has passed since the last time the EWMA was ticked. For infrequently used EWMA this can lead to pauses observed in the 700-800 millisecond range after a few hundred days.

It's not really necessary to perform every tick as all that is doing is slowly approaching the smallest representable positive number in a double. Instead pick a number close to zero and then bound the number of ticks to allow that to be reachable from the largest value representable by the EWMA. Actually approaching the smallest representable number is still measurably slow and not particularly useful.
  • Loading branch information
aweisberg committed Jan 30, 2024
1 parent 2ada84c commit bfbf60e
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,24 @@
*/
public class ExponentialMovingAverages implements MovingAverages {

static final double maxTickZeroTarget = 0.0001;
static final int maxTicks;
private static final long TICK_INTERVAL = TimeUnit.SECONDS.toNanos(5);

static
{
int m3Ticks = 1;
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 @@ -50,7 +66,7 @@ public void tickIfNecessary() {
if (age > TICK_INTERVAL) {
final long newIntervalStartTick = newTick - age % TICK_INTERVAL;
if (lastTick.compareAndSet(oldTick, newIntervalStartTick)) {
final long requiredTicks = age / TICK_INTERVAL;
final long requiredTicks = Math.min(maxTicks, age / TICK_INTERVAL);
for (long i = 0; i < requiredTicks; i++) {
m1Rate.tick();
m5Rate.tick();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.codahale.metrics;

import org.junit.Test;

import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class ExponentialMovingAveragesTest
{
@Test
public void testMaxTicks()
{
Clock clock = mock(Clock.class);
when(clock.getTick()).thenReturn(0L, Long.MAX_VALUE);
ExponentialMovingAverages ema = new ExponentialMovingAverages(clock);
ema.update(Long.MAX_VALUE);
ema.tickIfNecessary();
assertTrue (ema.getM1Rate() < ExponentialMovingAverages.maxTickZeroTarget);
assertTrue (ema.getM5Rate() < ExponentialMovingAverages.maxTickZeroTarget);
assertTrue (ema.getM15Rate() < ExponentialMovingAverages.maxTickZeroTarget);
}
}

0 comments on commit bfbf60e

Please sign in to comment.