Skip to content

Commit 6794019

Browse files
authored
Fix slowTickIfNecessary with infrequently used EWMA (#3929)
1 parent f0bb90d commit 6794019

File tree

3 files changed

+67
-4
lines changed

3 files changed

+67
-4
lines changed

metrics-core/src/main/java/com/codahale/metrics/EWMA.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,14 @@ public void update(long n) {
8181
uncounted.add(n);
8282
}
8383

84+
/**
85+
* Set the rate to the smallest possible positive value. Used to avoid calling tick a large number of times.
86+
*/
87+
public void reset() {
88+
uncounted.reset();
89+
rate = Double.MIN_NORMAL;
90+
}
91+
8492
/**
8593
* Mark the passage of time and decay the current rate accordingly.
8694
*/

metrics-core/src/main/java/com/codahale/metrics/ExponentialMovingAverages.java

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,28 @@
1111
*/
1212
public class ExponentialMovingAverages implements MovingAverages {
1313

14+
/**
15+
* If ticking would reduce even Long.MAX_VALUE in the 15 minute EWMA below this target then don't bother
16+
* ticking in a loop and instead reset all the EWMAs.
17+
*/
18+
private static final double maxTickZeroTarget = 0.0001;
19+
private static final int maxTicks;
1420
private static final long TICK_INTERVAL = TimeUnit.SECONDS.toNanos(5);
1521

22+
static
23+
{
24+
int m3Ticks = 1;
25+
final EWMA m3 = EWMA.fifteenMinuteEWMA();
26+
m3.update(Long.MAX_VALUE);
27+
do
28+
{
29+
m3.tick();
30+
m3Ticks++;
31+
}
32+
while (m3.getRate(TimeUnit.SECONDS) > maxTickZeroTarget);
33+
maxTicks = m3Ticks;
34+
}
35+
1636
private final EWMA m1Rate = EWMA.oneMinuteEWMA();
1737
private final EWMA m5Rate = EWMA.fiveMinuteEWMA();
1838
private final EWMA m15Rate = EWMA.fifteenMinuteEWMA();
@@ -51,10 +71,19 @@ public void tickIfNecessary() {
5171
final long newIntervalStartTick = newTick - age % TICK_INTERVAL;
5272
if (lastTick.compareAndSet(oldTick, newIntervalStartTick)) {
5373
final long requiredTicks = age / TICK_INTERVAL;
54-
for (long i = 0; i < requiredTicks; i++) {
55-
m1Rate.tick();
56-
m5Rate.tick();
57-
m15Rate.tick();
74+
if (requiredTicks >= maxTicks) {
75+
m1Rate.reset();
76+
m5Rate.reset();
77+
m15Rate.reset();
78+
}
79+
else
80+
{
81+
for (long i = 0; i < requiredTicks; i++)
82+
{
83+
m1Rate.tick();
84+
m5Rate.tick();
85+
m15Rate.tick();
86+
}
5887
}
5988
}
6089
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package com.codahale.metrics;
2+
3+
import java.util.concurrent.TimeUnit;
4+
5+
import org.junit.Test;
6+
7+
import static org.junit.Assert.assertEquals;
8+
import static org.mockito.Mockito.mock;
9+
import static org.mockito.Mockito.when;
10+
11+
public class ExponentialMovingAveragesTest
12+
{
13+
@Test
14+
public void testMaxTicks()
15+
{
16+
final Clock clock = mock(Clock.class);
17+
when(clock.getTick()).thenReturn(0L, Long.MAX_VALUE);
18+
final ExponentialMovingAverages ema = new ExponentialMovingAverages(clock);
19+
ema.update(Long.MAX_VALUE);
20+
ema.tickIfNecessary();
21+
final long secondNanos = TimeUnit.SECONDS.toNanos(1);
22+
assertEquals(ema.getM1Rate(), Double.MIN_NORMAL * secondNanos, 0.0);
23+
assertEquals(ema.getM5Rate(), Double.MIN_NORMAL * secondNanos, 0.0);
24+
assertEquals(ema.getM15Rate(), Double.MIN_NORMAL * secondNanos, 0.0);
25+
}
26+
}

0 commit comments

Comments
 (0)