io.trino.jdbc.$internal.dev.failsafe.internal.TimedCircuitStats Maven / Gradle / Ivy
/*
* 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 io.trino.jdbc.\$internal.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) + ']';
}
}