Skip to content

Commit c7cdbc9

Browse files
garyrussellartembilan
authored andcommitted
INT-4250: Fix Statistics Mean Decay with Time
JIRA: https://jira.spring.io/browse/INT-4250 When retrieving the mean from `ExponentialMovingAverageRate` or `ExponentialMovingAverageRatio` via `getStatistics()` the mean did not decay over time. The mean did decay when using `getMean()`. This was caused by the statistics performance refactoring. (cherry picked from commit fac04ae)
1 parent cb76cfa commit c7cdbc9

File tree

5 files changed

+72
-32
lines changed

5 files changed

+72
-32
lines changed

spring-integration-core/src/main/java/org/springframework/integration/support/management/ExponentialMovingAverageRate.java

Lines changed: 37 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
/*
2-
* Copyright 2009-2016 the original author or authors.
2+
* Copyright 2009-2017 the original author or authors.
33
*
4-
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
5-
* the License. You may obtain a copy of the License at
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
67
*
7-
* http://www.apache.org/licenses/LICENSE-2.0
8+
* http://www.apache.org/licenses/LICENSE-2.0
89
*
9-
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10-
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
11-
* specific language governing permissions and limitations under the License.
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
1215
*/
1316

1417
package org.springframework.integration.support.management;
@@ -19,7 +22,6 @@
1922
import java.util.List;
2023

2124

22-
2325
/**
2426
* Cumulative statistics for an event rate with higher weight given to recent data.
2527
* Clients call {@link #increment()} when a new event occurs, and then use convenience methods (e.g. {@link #getMean()})
@@ -63,7 +65,6 @@ public class ExponentialMovingAverageRate {
6365
private final double factor;
6466

6567

66-
6768
/**
6869
* @param period the period to base the rate measurement (in seconds)
6970
* @param lapsePeriod the exponential lapse rate for the rate average (in seconds)
@@ -95,7 +96,7 @@ public synchronized void reset() {
9596
this.max = 0;
9697
this.count = 0;
9798
this.times.clear();
98-
t0 = System.nanoTime() / this.factor;
99+
this.t0 = System.nanoTime() / this.factor;
99100
}
100101

101102
/**
@@ -114,17 +115,17 @@ public synchronized void increment(long t) {
114115
this.times.poll();
115116
}
116117
this.times.add(t);
117-
this.count++;//NOSONAR - false positive, we're synchronized
118+
this.count++; //NOSONAR - false positive, we're synchronized
118119
}
119120

120-
private Statistics calc() {
121+
private Statistics calcStatic() {
121122
List<Long> copy;
122123
long count;
123124
synchronized (this) {
124125
copy = new ArrayList<Long>(this.times);
125126
count = this.count;
126127
}
127-
ExponentialMovingAverage rates = new ExponentialMovingAverage(window);
128+
ExponentialMovingAverage rates = new ExponentialMovingAverage(this.window);
128129
double t0 = 0;
129130
double sum = 0;
130131
double weight = 0;
@@ -141,14 +142,14 @@ else if (t0 == 0) {
141142
continue;
142143
}
143144
double delta = t - t0;
144-
double value = delta > 0 ? delta / period : 0;
145+
double value = delta > 0 ? delta / this.period : 0;
145146
if (value > max) {
146147
max = value;
147148
}
148149
if (value < min) {
149150
min = value;
150151
}
151-
double alpha = Math.exp(-delta * lapse);
152+
double alpha = Math.exp(-delta * this.lapse);
152153
t0 = t;
153154
sum = alpha * sum + value;
154155
weight = alpha * weight + 1;
@@ -185,6 +186,9 @@ public long getCountLong() {
185186
* @return the time in milliseconds since the last measurement
186187
*/
187188
public double getTimeSinceLastMeasurement() {
189+
if (this.count == 0) {
190+
return 0;
191+
}
188192
double t0 = lastTime();
189193
return (System.nanoTime() / this.factor - t0);
190194
}
@@ -193,54 +197,65 @@ public double getTimeSinceLastMeasurement() {
193197
* @return the mean value
194198
*/
195199
public double getMean() {
200+
return recalcMean(calcStatic());
201+
}
202+
203+
/**
204+
* Decay the mean using the current time.
205+
* @param staticStats the static statistics.
206+
* @return the new mean.
207+
*/
208+
private double recalcMean(Statistics staticStats) {
196209
long count = this.count;
197210
count = count > this.retention ? this.retention : count;
198211
if (count == 0) {
199212
return 0;
200213
}
201214
double t0 = lastTime();
202215
double t = System.nanoTime() / this.factor;
203-
double value = t > t0 ? (t - t0) / period : 0;
204-
return count / (count / calc().getMean() + value);
216+
double value = t > t0 ? (t - t0) / this.period : 0;
217+
return count / (count / staticStats.getMean() + value);
205218
}
206219

207220
private synchronized double lastTime() {
208221
if (this.times.size() > 0) {
209222
return this.times.peekLast() / this.factor;
210223
}
211224
else {
212-
return this.t0;
225+
return this.t0;
213226
}
214227
}
215228

216229
/**
217230
* @return the approximate standard deviation
218231
*/
219232
public double getStandardDeviation() {
220-
return calc().getStandardDeviation();
233+
return calcStatic().getStandardDeviation();
221234
}
222235

223236
/**
224237
* @return the maximum value recorded (not weighted)
225238
*/
226239
public double getMax() {
227-
double min = calc().getMin();
240+
double min = calcStatic().getMin();
228241
return min > 0 ? 1 / min : 0;
229242
}
230243

231244
/**
232245
* @return the minimum value recorded (not weighted)
233246
*/
234247
public double getMin() {
235-
double max = calc().getMax();
248+
double max = calcStatic().getMax();
236249
return max > 0 ? 1 / max : 0;
237250
}
238251

239252
/**
240253
* @return summary statistics (count, mean, standard deviation etc.)
241254
*/
242255
public Statistics getStatistics() {
243-
return calc();
256+
Statistics staticStats = calcStatic();
257+
staticStats.setMean(recalcMean(staticStats));
258+
return staticStats;
244259
}
245260

246261
@Override

spring-integration-core/src/main/java/org/springframework/integration/support/management/ExponentialMovingAverageRatio.java

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2009-2016 the original author or authors.
2+
* Copyright 2009-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
55
* the License. You may obtain a copy of the License at
@@ -134,7 +134,7 @@ private synchronized void append(int value, long t) {
134134
this.count++;//NOSONAR - false positive, we're synchronized
135135
}
136136

137-
private Statistics calc() {
137+
private Statistics calcStatic() {
138138
List<Long> copyTimes;
139139
List<Integer> copyValues;
140140
long count;
@@ -216,7 +216,15 @@ public double getMean() {
216216
// Optimistic to start: success rate is 100%
217217
return 1;
218218
}
219-
Statistics statistics = calc();
219+
return decayMean(calcStatic());
220+
}
221+
222+
/**
223+
* Decay the mean using the current time.
224+
* @param staticStats the static statistics.
225+
* @return the new mean.
226+
*/
227+
private double decayMean(Statistics statistics) {
220228
double t = System.nanoTime() / this.factor;
221229
double mean = statistics.getMean();
222230
double alpha = Math.exp((lastTime() / this.factor - t) * this.lapse);
@@ -236,28 +244,30 @@ private synchronized double lastTime() {
236244
* @return the approximate standard deviation of the success rate measurements
237245
*/
238246
public double getStandardDeviation() {
239-
return calc().getStandardDeviation();
247+
return calcStatic().getStandardDeviation();
240248
}
241249

242250
/**
243251
* @return the maximum value recorded of the exponential weighted average (per measurement) success rate
244252
*/
245253
public double getMax() {
246-
return calc().getMax();
254+
return calcStatic().getMax();
247255
}
248256

249257
/**
250258
* @return the minimum value recorded of the exponential weighted average (per measurement) success rate
251259
*/
252260
public double getMin() {
253-
return calc().getMin();
261+
return calcStatic().getMin();
254262
}
255263

256264
/**
257265
* @return summary statistics (count, mean, standard deviation etc.)
258266
*/
259267
public Statistics getStatistics() {
260-
return calc();
268+
Statistics staticStats = calcStatic();
269+
staticStats.setMean(decayMean(staticStats));
270+
return staticStats;
261271
}
262272

263273
@Override

spring-integration-core/src/main/java/org/springframework/integration/support/management/Statistics.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 the original author or authors.
2+
* Copyright 2002-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@
1818

1919
/**
2020
* @author Dave Syer
21+
* @author Gary Russell
2122
* @since 2.0
2223
*/
2324
public class Statistics {
@@ -28,7 +29,7 @@ public class Statistics {
2829

2930
private final double max;
3031

31-
private final double mean;
32+
private double mean;
3233

3334
private final double standardDeviation;
3435

@@ -62,6 +63,10 @@ public double getMean() {
6263
return this.mean;
6364
}
6465

66+
public void setMean(double mean) {
67+
this.mean = mean;
68+
}
69+
6570
public double getStandardDeviation() {
6671
return this.standardDeviation;
6772
}

spring-integration-core/src/test/java/org/springframework/integration/support/management/ExponentialMovingAverageRateTests.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2016 the original author or authors.
2+
* Copyright 2002-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
55
* the License. You may obtain a copy of the License at
@@ -102,13 +102,15 @@ public void testGetMean() throws Exception {
102102
Thread.sleep(20L);
103103
history.increment();
104104
double before = history.getMean();
105+
Statistics statisticsBefore = history.getStatistics();
105106
long elapsed = System.currentTimeMillis() - t0;
106107
if (elapsed < 50L) {
107108
assertTrue(before > 10);
108109
Thread.sleep(20L);
109110
elapsed = System.currentTimeMillis() - t0;
110111
if (elapsed < 80L) {
111112
assertThat(history.getMean(), lessThan(before));
113+
assertThat(history.getStatistics().getMean(), lessThan(statisticsBefore.getMean()));
112114
}
113115
else {
114116
logger.warn("Test took too long to verify mean");

spring-integration-core/src/test/java/org/springframework/integration/support/management/ExponentialMovingAverageRatioTests.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package org.springframework.integration.support.management;
1717

1818
import static org.hamcrest.Matchers.equalTo;
19+
import static org.hamcrest.Matchers.greaterThan;
1920
import static org.hamcrest.Matchers.not;
2021
import static org.junit.Assert.assertEquals;
2122
import static org.junit.Assert.assertNotEquals;
@@ -94,6 +95,13 @@ public void testGetEarlyFailure() throws Exception {
9495
public void testDecayedMean() throws Exception {
9596
history.failure(System.nanoTime() - 200000000);
9697
assertEquals(average(0, Math.exp(-0.4)), history.getMean(), 0.01);
98+
history.success();
99+
history.failure();
100+
double mean = history.getMean();
101+
Statistics statistics = history.getStatistics();
102+
Thread.sleep(50);
103+
assertThat(history.getMean(), greaterThan(mean));
104+
assertThat(history.getStatistics().getMean(), greaterThan(statistics.getMean()));
97105
}
98106

99107
@Test

0 commit comments

Comments
 (0)