org.opendaylight.yangtools.util.ConcurrentDurationStatisticsTracker Maven / Gradle / Ivy
/*
* Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v10.html
*/
package org.opendaylight.yangtools.util;
import com.google.common.primitives.UnsignedLong;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
/**
* Concurrent version of {@link DurationStatisticsTracker}.
*/
// TODO: once DurationStatsTracker is gone make this class final
class ConcurrentDurationStatisticsTracker extends DurationStatisticsTracker {
private static final VarHandle SUM;
private static final VarHandle COUNT;
private static final VarHandle LONGEST;
private static final VarHandle SHORTEST;
static {
final var lookup = MethodHandles.lookup();
try {
SUM = lookup.findVarHandle(ConcurrentDurationStatisticsTracker.class, "sum", long.class);
COUNT = lookup.findVarHandle(ConcurrentDurationStatisticsTracker.class, "count", long.class);
LONGEST = lookup.findVarHandle(
ConcurrentDurationStatisticsTracker.class, "longest", DurationWithTime.class);
SHORTEST = lookup.findVarHandle(
ConcurrentDurationStatisticsTracker.class, "shortest", DurationWithTime.class);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new ExceptionInInitializerError(e);
}
}
private volatile long sum;
private volatile long count;
private volatile DurationWithTime longest;
private volatile DurationWithTime shortest;
ConcurrentDurationStatisticsTracker() {
// Hidden on purpose
}
@Override
public final void addDuration(final long duration) {
// First update the quick stats
SUM.getAndAdd(this, duration);
COUNT.getAndAdd(this, 1L);
/*
* Now the hairy 'min/max' things. The notion of "now" we cache, so the first time we use it, we do not call it
* twice. We populate it lazily, though.
*
* The longest/shortest stats both are encapsulated in an object, so we update them atomically and we minimize
* the number of volatile operations.
*/
final var currentShortest = (DurationWithTime) SHORTEST.getAcquire(this);
if (currentShortest == null || duration < currentShortest.duration()) {
updateShortest(currentShortest, duration);
}
final var currentLongest = (DurationWithTime) LONGEST.getAcquire(this);
if (currentLongest == null || duration > currentLongest.duration()) {
updateLongest(currentLongest, duration);
}
}
private void updateShortest(final DurationWithTime prev, final long duration) {
final var newObj = new DurationWithTime(duration, System.currentTimeMillis());
var expected = prev;
while (true) {
final var witness = (DurationWithTime) SHORTEST.compareAndExchangeRelease(this, expected, newObj);
if (witness == expected || duration >= witness.duration()) {
break;
}
expected = witness;
}
}
private void updateLongest(final DurationWithTime prev, final long duration) {
final var newObj = new DurationWithTime(duration, System.currentTimeMillis());
var expected = prev;
while (true) {
final var witness = (DurationWithTime) LONGEST.compareAndExchangeRelease(this, expected, newObj);
if (witness == expected || duration <= witness.duration()) {
break;
}
expected = witness;
}
}
@Override
public final long getTotalDurations() {
return count;
}
@Override
public final double getAverageDuration() {
final long myCount = count;
return myCount == 0 ? 0 : UnsignedLong.fromLongBits(sum).doubleValue() / myCount;
}
@Override
public final synchronized void reset() {
// Synchronized is just to make sure we do not have concurrent resets :)
longest = null;
shortest = null;
count = 0;
sum = 0;
}
@Override
protected final DurationWithTime getLongest() {
return longest;
}
@Override
protected final DurationWithTime getShortest() {
return shortest;
}
}