All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.phantomthief.stats.n.impl.SimpleDurationStats Maven / Gradle / Ivy

There is a newer version: 0.2.2
Show newest version
/**
 * 
 */
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