
com.github.phantomthief.stats.n.impl.SimpleDurationStats Maven / Gradle / Ivy
/**
*
*/
package com.github.phantomthief.stats.n.impl;
import static java.util.stream.Collectors.toMap;
import static org.slf4j.LoggerFactory.getLogger;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.BinaryOperator;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import com.github.phantomthief.stats.n.DurationStats;
import com.github.phantomthief.stats.n.MultiDurationStats;
import com.github.phantomthief.stats.n.counter.Duration;
import com.github.phantomthief.stats.n.counter.SimpleCounter;
import com.github.phantomthief.stats.n.util.DurationStatsUtils;
import com.github.phantomthief.stats.n.util.SharedStatsScheduledExecutorHolder;
import com.github.phantomthief.stats.n.util.SimpleDurationFormatter;
import com.google.common.base.Preconditions;
/**
* @author w.vela
*/
public class SimpleDurationStats implements DurationStats, AutoCloseable {
private static org.slf4j.Logger logger = getLogger(SimpleDurationStats.class);
private static final long SECOND = TimeUnit.SECONDS.toMillis(1);
private static final long MINUTE = TimeUnit.MINUTES.toMillis(1);
private static final long MERGE_THRESHOLD = TimeUnit.MINUTES.toMillis(2);
private final Map counters = new ConcurrentHashMap<>();
private final Set statsDurations;
private final Function counterFactory;
private final BinaryOperator counterMerger;
private final ScheduledFuture> cleanupScheduledFuture;
private SimpleDurationStats(Set statsDurations, Function counterFactory,
BinaryOperator counterMerger) {
long maxTimePeriod = statsDurations.stream().max(Comparator.naturalOrder()).get();
this.statsDurations = statsDurations;
this.counterFactory = counterFactory;
this.counterMerger = counterMerger;
this.cleanupScheduledFuture = SharedStatsScheduledExecutorHolder.getInstance()
.scheduleWithFixedDelay(() -> {
long now = System.currentTimeMillis();
Iterator> iterator = counters.entrySet().iterator();
while (iterator.hasNext()) {
Entry entry = iterator.next();
if (now - entry.getKey() > maxTimePeriod) {
if (logger.isDebugEnabled()) {
logger.debug("remove expired counter:{}", entry);
}
iterator.remove();
}
if (entry.getValue().duration() == SECOND
&& now - entry.getKey() > MERGE_THRESHOLD) {
long mergedKey = entry.getKey() / MINUTE * MINUTE;
counters.merge(mergedKey, entry.getValue(), counterMerger);
iterator.remove();
if (logger.isDebugEnabled()) {
logger.debug("merge counter:{}, merge to:{}->{}", entry, mergedKey,
counters.get(mergedKey));
}
}
}
} , 1, 1, TimeUnit.MINUTES);
}
/* (non-Javadoc)
* @see com.github.phantomthief.stats.n.DurationStats#stat(java.util.function.Consumer)
*/
@Override
public void stat(Consumer statsFunction) {
try {
long timePoint = System.currentTimeMillis() / SECOND * SECOND;
V counter = counters.computeIfAbsent(timePoint, t -> counterFactory.apply(SECOND));
statsFunction.accept(counter);
} catch (Throwable e) {
logger.error("Ops.", e);
}
}
/* (non-Javadoc)
* @see com.github.phantomthief.stats.n.DurationStats#getStats()
*/
@Override
public Map getStats() {
Map result = new HashMap<>();
long now = System.currentTimeMillis();
counters.forEach((d, counter) -> {
statsDurations.forEach(s -> {
if (now - d <= s) {
result.merge(s, counter, counterMerger);
}
});
});
return result;
}
/* (non-Javadoc)
* @see java.lang.AutoCloseable#close()
*/
@Override
public void close() {
cleanupScheduledFuture.cancel(false);
}
public static final class SimpleMultiDurationStats
implements MultiDurationStats {
private final ConcurrentMap> map = new ConcurrentHashMap<>();
private final Supplier> statsFactory;
/**
* @param statsFactory
*/
private SimpleMultiDurationStats(Supplier> statsFactory) {
this.statsFactory = statsFactory;
}
/* (non-Javadoc)
* @see com.github.phantomthief.stats.n.MultiDurationStats#stat(java.lang.Object, java.util.function.Consumer)
*/
@Override
public void stat(K key, Consumer statsFunction) {
map.computeIfAbsent(key, k -> statsFactory.get()).stat(statsFunction);
}
/* (non-Javadoc)
* @see com.github.phantomthief.stats.n.MultiDurationStats#getStats()
*/
@Override
public Map> getStats() {
return map.entrySet().stream()
.collect(toMap(Entry::getKey, e -> e.getValue().getStats()));
}
}
public static final class Builder {
private final Set statsDurations = new HashSet<>();
public Builder addDuration(long time, TimeUnit unit) {
statsDurations.add(unit.toMillis(time));
return this;
}
public SimpleDurationStats build() {
return build(SimpleCounter::new);
}
public SimpleDurationStats build(Function counterFactory) {
return build(counterFactory, DurationStatsUtils::merge);
}
public SimpleDurationStats build(Function counterFactory,
BinaryOperator counterMerger) {
Preconditions.checkNotNull(counterFactory);
Preconditions.checkNotNull(counterMerger);
ensure();
return new SimpleDurationStats<>(statsDurations, counterFactory, counterMerger);
}
public SimpleMultiDurationStats buildMulti() {
return buildMulti(SimpleCounter::new);
}
public SimpleMultiDurationStats
buildMulti(Function counterFactory) {
return buildMulti(counterFactory, DurationStatsUtils::merge);
}
public SimpleMultiDurationStats
buildMulti(Function counterFactory, BinaryOperator counterMerger) {
return new SimpleMultiDurationStats<>(() -> build(counterFactory, counterMerger));
}
private void ensure() {
if (statsDurations.isEmpty()) {
statsDurations.add(SimpleDurationFormatter.HOUR);
statsDurations.add(SimpleDurationFormatter.MINUTE);
statsDurations.add(SimpleDurationFormatter.TEN_SECOND);
}
}
}
public static final Builder newBuilder() {
return new Builder();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy