com.datastax.driver.core.policies.RollingCount Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of cassandra-driver Show documentation
Show all versions of cassandra-driver Show documentation
Shaded version of DataStax Java Driver for Apache Cassandra
/*
* Copyright (C) 2012-2015 DataStax Inc.
*
* 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 com.datastax.driver.core.policies;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicLongArray;
import java.util.concurrent.atomic.AtomicReference;
/**
* A "rolling" count over a 1-minute sliding window.
*/
class RollingCount {
// Divide the minute into 5-second intervals
private static final long INTERVAL_SIZE = TimeUnit.SECONDS.toNanos(5);
// A circular buffer containing the counts over the 12 previous intervals. Their sum is the count we're looking for.
// If we're at t = 61s this would span [0,60[
private final AtomicLongArray previousIntervals = new AtomicLongArray(12);
// The interval we're currently recording events for. It hasn't completed yet, so it's not included in the count.
// If we're at t = 61s this would span [60,65[
// Note that we don't expect very high concurrency on RollingCount (it's used to count errors), so AtomicLong is
// good enough here.
private final AtomicLong currentInterval = new AtomicLong();
// Other mutable state, grouped in an object for atomic updates
private final AtomicReference state;
private final Clock clock;
RollingCount(Clock clock) {
this.state = new AtomicReference(new State(clock.nanoTime()));
this.clock = clock;
}
void increment() {
add(1);
}
void add(long amount) {
tickIfNecessary();
currentInterval.addAndGet(amount);
}
long get() {
tickIfNecessary();
return state.get().totalCount;
}
private void tickIfNecessary() {
State oldState = state.get();
long newTick = clock.nanoTime();
long age = newTick - oldState.lastTick;
if (age >= INTERVAL_SIZE) {
long currentCount = currentInterval.get();
long newIntervalStartTick = newTick - age % INTERVAL_SIZE;
long elapsedIntervals = Math.min(age / INTERVAL_SIZE, 12);
int newOffset = (int) ((oldState.offset + elapsedIntervals) % 12);
long newTotal;
if (elapsedIntervals == 12) {
// We wrapped around the circular buffer, all our values are stale
// Don't mutate previousIntervals yet because this part of the code is still multi-threaded.
newTotal = 0;
} else {
// Add the current interval that just completed
newTotal = oldState.totalCount + currentCount;
// Subtract all elapsed intervals: they're either idle ones, or the one at the old offset that we're
// about to replace
for (int i = 1; i <= elapsedIntervals; i++) {
newTotal -= previousIntervals.get((newOffset + 12 - i) % 12);
}
}
State newState = new State(newIntervalStartTick, newOffset, newTotal);
if (state.compareAndSet(oldState, newState)) {
// Only one thread gets here, so we can now:
// - reset the current count (don't use reset because other threads might already have started updating
// it)
currentInterval.addAndGet(-currentCount);
// - store the interval that just completed (or clear it if we wrapped)
previousIntervals.set(oldState.offset, elapsedIntervals < 12 ? currentCount : 0);
// - clear any idle interval
for (int i = 1; i < elapsedIntervals; i++) {
previousIntervals.set((newOffset + 12 - i) % 12, 0);
}
}
}
}
static class State {
final long lastTick; // last time the state was modified
final int offset; // the offset that the current interval will replace once it's complete
final long totalCount; // cache of sum(previousIntervals)
State(long lastTick) {
this(lastTick, 0, 0);
}
State(long lastTick, int offset, long totalCount) {
this.lastTick = lastTick;
this.offset = offset;
this.totalCount = totalCount;
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy