org.terracotta.statistics.derived.EventParameterSimpleMovingAverage Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ehcache Show documentation
Show all versions of ehcache Show documentation
Ehcache is an open source, standards-based cache used to boost performance,
offload the database and simplify scalability. Ehcache is robust, proven and full-featured and
this has made it the most widely-used Java-based cache.
/*
* All content copyright Terracotta, Inc., unless otherwise indicated.
*
* 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 org.terracotta.statistics.derived;
import static org.terracotta.statistics.Time.time;
import java.util.Iterator;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import org.terracotta.statistics.ValueStatistic;
import org.terracotta.statistics.jsr166e.LongAdder;
import org.terracotta.statistics.jsr166e.LongMaxUpdater;
import org.terracotta.statistics.observer.ChainedEventObserver;
/**
*
* @author cdennis
*/
public class EventParameterSimpleMovingAverage implements ChainedEventObserver {
private static final int PARTITION_COUNT = 10;
private final Queue archive = new ConcurrentLinkedQueue();
private final AtomicReference activePartition;
private volatile long windowSize;
private volatile long partitionSize;
public EventParameterSimpleMovingAverage(long time, TimeUnit unit) {
this.windowSize = unit.toNanos(time);
this.partitionSize = windowSize / PARTITION_COUNT;
this.activePartition = new AtomicReference(new AveragePartition(Long.MIN_VALUE, partitionSize));
}
public void setWindow(long time, TimeUnit unit) {
this.windowSize = unit.toNanos(time);
this.partitionSize = windowSize / PARTITION_COUNT;
}
public Double value() {
return average();
}
public ValueStatistic averageStatistic() {
return new ValueStatistic() {
@Override
public Double value() {
return average();
}
};
}
public ValueStatistic minimumStatistic() {
return new ValueStatistic() {
@Override
public Long value() {
return minimum();
}
};
}
public ValueStatistic maximumStatistic() {
return new ValueStatistic() {
@Override
public Long value() {
return maximum();
}
};
}
public final double average() {
long startTime = time() - windowSize;
AveragePartition current = activePartition.get();
if (current.isBefore(startTime)) {
return Double.NaN;
} else {
Average average = new Average();
current.aggregate(average);
for (Iterator it = archive.iterator(); it.hasNext(); ) {
AveragePartition partition = it.next();
if (partition == current) {
break;
} else if (partition.isBefore(startTime)) {
it.remove();
} else {
partition.aggregate(average);
}
}
return ((double) average.total) / average.count;
}
}
public final Long maximum() {
long startTime = time() - windowSize;
AveragePartition current = activePartition.get();
if (current.isBefore(startTime)) {
return null;
} else {
long maximum = current.maximum();
for (Iterator it = archive.iterator(); it.hasNext(); ) {
AveragePartition partition = it.next();
if (partition == current) {
break;
} else if (partition.isBefore(startTime)) {
it.remove();
} else {
maximum = Math.max(maximum, partition.maximum());
}
}
return maximum;
}
}
public final Long minimum() {
long startTime = time() - windowSize;
AveragePartition current = activePartition.get();
if (current.isBefore(startTime)) {
return null;
} else {
long minimum = current.minimum();
for (Iterator it = archive.iterator(); it.hasNext(); ) {
AveragePartition partition = it.next();
if (partition == current) {
break;
} else if (partition.isBefore(startTime)) {
it.remove();
} else {
minimum = Math.min(minimum, partition.minimum());
}
}
return minimum;
}
}
@Override
public void event(long time, long ... parameters) {
while (true) {
AveragePartition partition = activePartition.get();
if (partition.targetFor(time)) {
partition.event(parameters[0]);
return;
} else {
AveragePartition newPartition = new AveragePartition(time, partitionSize);
if (activePartition.compareAndSet(partition, newPartition)) {
archive(partition);
newPartition.event(parameters[0]);
return;
}
}
}
}
private void archive(AveragePartition partition) {
archive.add(partition);
long startTime = partition.end() - windowSize;
for (AveragePartition earliest = archive.peek(); earliest!=null && earliest.isBefore(startTime); earliest = archive.peek()) {
if (archive.remove(earliest)) {
break;
}
}
}
static class AveragePartition {
private final LongAdder total = new LongAdder();
private final LongAdder count = new LongAdder();
private final LongMaxUpdater maximum = new LongMaxUpdater();
private final LongMaxUpdater minimum = new LongMaxUpdater();
private final long start;
private final long end;
public AveragePartition(long start, long length) {
this.start = start;
this.end = start + length;
}
public boolean targetFor(long time) {
return end > time;
}
public boolean isBefore(long time) {
return end < time;
}
public long start() {
return start;
}
public long end() {
return end;
}
public void event(long parameter) {
total.add(parameter);
count.increment();
maximum.update(parameter);
minimum.update(-parameter);
}
public void aggregate(Average average) {
average.total += total.sum();
average.count += count.sum();
}
public long maximum() {
return maximum.max();
}
public long minimum() {
return -minimum.max();
}
}
static class Average {
long total;
long count;
}
}