package org.infinispan.stats.container;
import static org.infinispan.stats.container.ExtendedStatistic.NO_INDEX;
import static org.infinispan.stats.container.ExtendedStatistic.getLocalStatsSize;
import static org.infinispan.stats.container.ExtendedStatistic.getRemoteStatsSize;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import org.infinispan.util.TimeService;
/**
* Thread safe cache statistics that allows multiple writers and reader at the same time.
*
* @author Pedro Ruivo
* @since 5.2
*/
public final class ConcurrentGlobalContainer {
private static final int LOCAL_STATS_OFFSET = 1;
private static final int REMOTE_STATS_OFFSET = LOCAL_STATS_OFFSET + getLocalStatsSize();
private static final int LOCAL_SIZE = getLocalStatsSize();
private static final int REMOTE_SIZE = getRemoteStatsSize();
private static final int TOTAL_SIZE = 1 + LOCAL_SIZE + REMOTE_SIZE;
private final AtomicBoolean flushing;
private final BlockingQueue<Mergeable> queue;
private final TimeService timeService;
private volatile double[] values;
private volatile boolean reset;
public ConcurrentGlobalContainer(TimeService timeService) {
this.timeService = timeService;
flushing = new AtomicBoolean(false);
queue = new LinkedBlockingQueue<Mergeable>();
values = create();
values[0] = timeService.time();
}
public final void add(ExtendedStatistic stat, double value, boolean local) {
queue.add(new SingleOperation(local ? getLocalIndex(stat) : getRemoteIndex(stat), value));
tryFlush();
}
public final void merge(double[] toMerge, boolean local) {
final int expectedSize = local ? LOCAL_SIZE : REMOTE_SIZE;
final int offset = local ? LOCAL_STATS_OFFSET : REMOTE_STATS_OFFSET;
if (toMerge.length != expectedSize) {
throw new IllegalArgumentException("Size mismatch to merge transaction statistic");
}
queue.add(new Transaction(toMerge, offset));
tryFlush();
}
public final StatisticsSnapshot getSnapshot() {
tryFlush();
return new StatisticsSnapshot(values);
}
public final void reset() {
reset = true;
tryFlush();
}
public static int getLocalIndex(ExtendedStatistic stat) {
final int index = stat.getLocalIndex();
if (index == NO_INDEX) {
throw new IllegalArgumentException("This should never happen. Statistic " + stat + " is not local");
}
return LOCAL_STATS_OFFSET + index;
}
public static int getRemoteIndex(ExtendedStatistic stat) {
final int index = stat.getRemoteIndex();
if (index == NO_INDEX) {
throw new IllegalArgumentException("This should never happen. Statistic " + stat + " is not remote");
}
return REMOTE_STATS_OFFSET + index;
}
/**
* @return TEST ONLY!!
*/
public final BlockingQueue<?> queue() {
return queue;
}
/**
* @return TEST ONLY!!
*/
public final AtomicBoolean flushing() {
return flushing;
}
/**
* @return TEST ONLY!!
*/
public final boolean isReset() {
return reset;
}
public final void dumpTo(PrintWriter writer) {
final double[] snapshot = values;
writer.println("Global Statistics:");
for (ExtendedStatistic statistic : ExtendedStatistic.values()) {
if (statistic.isLocal()) {
writer.print(statistic.name());
writer.print(" [local]=");
writer.println(snapshot[getLocalIndex(statistic)]);
}
if (statistic.isRemote()) {
writer.print(statistic.name());
writer.print(" [remote]=");
writer.println(snapshot[getLocalIndex(statistic)]);
}
}
writer.print("LAST_RESET=");
writer.println(snapshot[0]);
writer.flush();
}
private void tryFlush() {
if (flushing.compareAndSet(false, true)) {
flush();
}
}
private void flush() {
if (reset) {
values = create();
queue.clear();
reset = false;
values[0] = timeService.time();
flushing.set(false);
return;
}
final double[] copy = create();
System.arraycopy(values, 0, copy, 0, copy.length);
List<Mergeable> drain = new ArrayList<Mergeable>();
queue.drainTo(drain);
for (Mergeable mergeable : drain) {
try {
mergeable.mergeTo(copy);
} catch (Throwable throwable) {
//ignore
}
}
values = copy;
flushing.set(false);
}
private double[] create() {
return new double[TOTAL_SIZE];
}
private interface Mergeable {
void mergeTo(double[] values);
}
private class Transaction implements Mergeable {
private final double[] toMerge;
private final int offset;
private Transaction(double[] toMerge, int offset) {
this.toMerge = toMerge;
this.offset = offset;
}
@Override
public void mergeTo(double[] values) {
for (int i = 0; i < values.length; ++i) {
values[offset + i] += toMerge[i];
}
}
}
private class SingleOperation implements Mergeable {
private final int index;
private final double value;
private SingleOperation(int index, double value) {
this.value = value;
this.index = index;
}
@Override
public void mergeTo(double[] values) {
values[index] += value;
}
}
}