Skip to content

Commit

Permalink
#187 backport calculateFullRefillingTime to the Verbose API
Browse files Browse the repository at this point in the history
  • Loading branch information
vladimir-bukhtoyarov committed Nov 14, 2021
1 parent 2f12aa3 commit 099b1ac
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 3 deletions.
26 changes: 24 additions & 2 deletions bucket4j-core/src/main/java/io/github/bucket4j/BucketState.java
Expand Up @@ -478,7 +478,7 @@ private void setLastRefillTimeNanos(int bandwidth, long nanos) {
stateData[bandwidth * BANDWIDTH_SIZE] = nanos;
}

long getCurrentSize(int bandwidth) {
public long getCurrentSize(int bandwidth) {
return stateData[bandwidth * BANDWIDTH_SIZE + 1];
}

Expand All @@ -490,14 +490,36 @@ private void consume(int bandwidth, long tokens) {
stateData[bandwidth * BANDWIDTH_SIZE + 1] -= tokens;
}

long getRoundingError(int bandwidth) {
public long getRoundingError(int bandwidth) {
return stateData[bandwidth * BANDWIDTH_SIZE + 2];
}

private void setRoundingError(int bandwidth, long roundingError) {
stateData[bandwidth * BANDWIDTH_SIZE + 2] = roundingError;
}

public long calculateFullRefillingTime(Bandwidth[] bandwidths, long currentTimeNanos) {
long maxTimeToFullRefillNanos = calculateFullRefillingTime(0, bandwidths[0], currentTimeNanos);
for (int i = 1; i < bandwidths.length; i++) {
maxTimeToFullRefillNanos = Math.max(maxTimeToFullRefillNanos, calculateFullRefillingTime(i, bandwidths[i], currentTimeNanos));
}
return maxTimeToFullRefillNanos;
}

private long calculateFullRefillingTime(int bandwidthIndex, Bandwidth bandwidth, long currentTimeNanos) {
long availableTokens = getCurrentSize(bandwidthIndex);
if (availableTokens >= bandwidth.capacity) {
return 0L;
}
long deficit = bandwidth.capacity - availableTokens;

if (bandwidth.isRefillIntervally()) {
return calculateDelayNanosAfterWillBePossibleToConsumeForIntervalBandwidth(bandwidthIndex, bandwidth, deficit, currentTimeNanos);
} else {
return calculateDelayNanosAfterWillBePossibleToConsumeForGreedyBandwidth(bandwidthIndex, bandwidth, deficit);
}
}

public static final SerializationHandle<BucketState> SERIALIZATION_HANDLE = new SerializationHandle<BucketState>() {
@Override
public <S> BucketState deserialize(DeserializationAdapter<S> adapter, S input) throws IOException {
Expand Down
Expand Up @@ -38,6 +38,12 @@ public class VerboseResult<T extends Serializable> implements Serializable {
private final T value;
private final BucketConfiguration configuration;
private final BucketState state;
private final Diagnostics diagnostics = new Diagnostics() {
@Override
public long calculateFullRefillingTime() {
return state.calculateFullRefillingTime(configuration.getBandwidths(), operationTimeNanos);
}
};

public VerboseResult(long operationTimeNanos, T value, BucketConfiguration configuration, BucketState state) {
this.operationTimeNanos = operationTimeNanos;
Expand All @@ -53,7 +59,6 @@ public T getValue() {
return value;
}


/**
* @return snapshot of configuration which was actual at operation time
*/
Expand All @@ -75,6 +80,27 @@ public long getOperationTimeNanos() {
return operationTimeNanos;
}

/**
* @return internal state describer
*/
public Diagnostics getDiagnostics() {
return diagnostics;
}

/**
* Describer of internal bucket state
*/
interface Diagnostics {

/**
* Returns time in nanoseconds that need to wait until bucket will be fully refilled to its maximum
*
* @return time in nanoseconds that need to wait until bucket will be fully refilled to its maximum
*/
long calculateFullRefillingTime();

}

public <R extends Serializable> VerboseResult<R> map(Function<T, R> mapper) {
return new VerboseResult<>(operationTimeNanos, mapper.apply(value), configuration, state);
}
Expand Down
@@ -0,0 +1,97 @@
package io.github.bucket4j

import spock.lang.Specification
import spock.lang.Unroll

import java.time.Duration

class VerboseApiTest extends Specification {

@Unroll
def "calculateFullRefillingTime specification #testNumber"(String testNumber, long requiredTime,
long timeShiftBeforeAsk, long tokensConsumeBeforeAsk, BucketConfiguration configuration) {
setup:
long currentTimeNanos = 0L
BucketState state = BucketState.createInitialState(configuration, currentTimeNanos)
state.refillAllBandwidth(configuration.bandwidths, timeShiftBeforeAsk)
state.consume(configuration.bandwidths, tokensConsumeBeforeAsk)
VerboseResult verboseResult = new VerboseResult(timeShiftBeforeAsk, 42, configuration, state)

when:
long actualTime = verboseResult.getDiagnostics().calculateFullRefillingTime()
then:
actualTime == requiredTime
where:
[testNumber, requiredTime, timeShiftBeforeAsk, tokensConsumeBeforeAsk, configuration] << [
[
"#1",
90,
0,
0,
Bucket4j.configurationBuilder()
.addLimit(Bandwidth.simple(10, Duration.ofNanos(100)).withInitialTokens(1))
.build()
], [
"#2",
100,
0,
0,
Bucket4j.configurationBuilder()
.addLimit(Bandwidth.classic(10, Refill.intervally(10, Duration.ofNanos(100))).withInitialTokens(1))
.build()
], [
"#3",
1650,
0,
23,
Bucket4j.configurationBuilder()
.addLimit(Bandwidth.classic(10, Refill.greedy(2, Duration.ofNanos(100))).withInitialTokens(0))
.build()
], [
"#4",
1700,
0,
23,
Bucket4j.configurationBuilder()
.addLimit(Bandwidth.classic(10, Refill.intervally(2, Duration.ofNanos(100))).withInitialTokens(0))
.build()
], [
"#5",
60,
0,
0,
Bucket4j.configurationBuilder()
.addLimit(Bandwidth.simple(10, Duration.ofNanos(100)).withInitialTokens(4))
.build()
], [
"#6",
90,
0,
0,
Bucket4j.configurationBuilder()
.addLimit(Bandwidth.simple(10, Duration.ofNanos(100)).withInitialTokens(1))
.addLimit(Bandwidth.simple(5, Duration.ofNanos(10)).withInitialTokens(2))
.build()
], [
"#7",
90,
0,
0,
Bucket4j.configurationBuilder()
.addLimit(Bandwidth.simple(5, Duration.ofNanos(10)).withInitialTokens(2))
.addLimit(Bandwidth.simple(10, Duration.ofNanos(100)).withInitialTokens(1))
.build()
], [
"#8",
70,
0,
0,
Bucket4j.configurationBuilder()
.addLimit(Bandwidth.simple(5, Duration.ofNanos(10)).withInitialTokens(5))
.addLimit(Bandwidth.simple(10, Duration.ofNanos(100)).withInitialTokens(3))
.build()
]
]
}

}

0 comments on commit 099b1ac

Please sign in to comment.