-
Notifications
You must be signed in to change notification settings - Fork 292
/
TimedCircuitStats.java
241 lines (208 loc) · 6.62 KB
/
TimedCircuitStats.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
/*
* Copyright 2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package dev.failsafe.internal;
import java.time.Duration;
import java.util.Arrays;
/**
* A CircuitStats implementation that counts execution results within a time period, and buckets results to
* minimize overhead.
*/
class TimedCircuitStats implements CircuitStats {
static final int DEFAULT_BUCKET_COUNT = 10;
private final Clock clock;
private final long bucketSizeMillis;
private final long windowSizeMillis;
// Mutable state
final Bucket[] buckets;
private final Stat summary;
volatile int currentIndex;
public TimedCircuitStats(int bucketCount, Duration thresholdingPeriod, Clock clock, CircuitStats oldStats) {
this.clock = clock;
this.buckets = new Bucket[bucketCount];
windowSizeMillis = thresholdingPeriod.toMillis();
bucketSizeMillis = thresholdingPeriod.toMillis() / buckets.length;
for (int i = 0; i < buckets.length; i++)
buckets[i] = new Bucket();
this.summary = new Stat();
if (oldStats == null) {
buckets[0].startTimeMillis = clock.currentTimeMillis();
} else {
synchronized (oldStats) {
copyStats(oldStats);
}
}
}
static class Clock {
long currentTimeMillis() {
return System.currentTimeMillis();
}
}
static class Stat {
int successes;
int failures;
void reset() {
successes = 0;
failures = 0;
}
void add(Bucket bucket) {
successes += bucket.successes;
failures += bucket.failures;
}
void remove(Bucket bucket) {
successes -= bucket.successes;
failures -= bucket.failures;
}
@Override
public String toString() {
return "[s=" + successes + ", f=" + failures + ']';
}
}
static class Bucket extends Stat {
long startTimeMillis = -1;
void reset(long startTimeMillis) {
this.startTimeMillis = startTimeMillis;
reset();
}
void copyFrom(Bucket other) {
startTimeMillis = other.startTimeMillis;
successes = other.successes;
failures = other.failures;
}
@Override
public String toString() {
return "[startTime=" + startTimeMillis + ", s=" + successes + ", f=" + failures + ']';
}
}
/**
* Copies the most recent stats from the {@code oldStats} into this in order from oldest to newest and orders buckets
* from oldest to newest, with uninitialized buckets counting as oldest.
*/
void copyStats(CircuitStats oldStats) {
if (oldStats instanceof TimedCircuitStats) {
TimedCircuitStats old = (TimedCircuitStats) oldStats;
int bucketsToCopy = Math.min(old.buckets.length, buckets.length);
// Get oldest index to start copying from
int oldIndex = old.indexAfter(old.currentIndex);
for (int i = 0; i < bucketsToCopy; i++)
oldIndex = old.indexBefore(oldIndex);
for (int i = 0; i < bucketsToCopy; i++) {
if (i != 0) {
oldIndex = old.indexAfter(oldIndex);
currentIndex = nextIndex();
}
buckets[currentIndex].copyFrom(old.buckets[oldIndex]);
summary.add(buckets[currentIndex]);
}
} else {
buckets[0].startTimeMillis = clock.currentTimeMillis();
copyExecutions(oldStats);
}
}
@Override
public synchronized void recordSuccess() {
getCurrentBucket().successes++;
summary.successes++;
}
@Override
public synchronized void recordFailure() {
getCurrentBucket().failures++;
summary.failures++;
}
@Override
public int getExecutionCount() {
return summary.successes + summary.failures;
}
@Override
public int getFailureCount() {
return summary.failures;
}
@Override
public synchronized int getFailureRate() {
int executions = getExecutionCount();
return (int) Math.round(executions == 0 ? 0 : (double) summary.failures / (double) executions * 100.0);
}
@Override
public int getSuccessCount() {
return summary.successes;
}
@Override
public synchronized int getSuccessRate() {
int executions = getExecutionCount();
return (int) Math.round(executions == 0 ? 0 : (double) summary.successes / (double) executions * 100.0);
}
@Override
public synchronized void reset() {
long startTimeMillis = clock.currentTimeMillis();
for (Bucket bucket : buckets) {
bucket.reset(startTimeMillis);
startTimeMillis += bucketSizeMillis;
}
summary.reset();
currentIndex = 0;
}
/**
* Returns the current bucket based on the current time, moving the internal storage to the current bucket if
* necessary, resetting bucket stats along the way.
*/
synchronized Bucket getCurrentBucket() {
Bucket previousBucket, currentBucket = buckets[currentIndex];
long currentTime = clock.currentTimeMillis();
long timeDiff = currentTime - currentBucket.startTimeMillis;
if (timeDiff >= bucketSizeMillis) {
int bucketsToMove = (int) (timeDiff / bucketSizeMillis);
if (bucketsToMove <= buckets.length) {
// Reset some buckets
do {
currentIndex = nextIndex();
previousBucket = currentBucket;
currentBucket = buckets[currentIndex];
long bucketStartTime = currentBucket.startTimeMillis == -1 ?
previousBucket.startTimeMillis + bucketSizeMillis :
currentBucket.startTimeMillis + windowSizeMillis;
summary.remove(currentBucket);
currentBucket.reset(bucketStartTime);
bucketsToMove--;
} while (bucketsToMove > 0);
} else {
// Reset all buckets
reset();
}
}
return currentBucket;
}
/**
* Returns the next index.
*/
private int nextIndex() {
return (currentIndex + 1) % buckets.length;
}
/**
* Returns the index after the {@code index}.
*/
private int indexAfter(int index) {
return index == buckets.length - 1 ? 0 : index + 1;
}
/**
* Returns the index before the {@code index}.
*/
private int indexBefore(int index) {
return index == 0 ? buckets.length - 1 : index - 1;
}
@Override
public String toString() {
return "TimedCircuitStats[summary=" + summary + ", buckets=" + Arrays.toString(buckets) + ']';
}
}