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

com.twitter.hbc.BasicRateTracker Maven / Gradle / Ivy

/**
 * Copyright 2013 Twitter, Inc.
 * 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 com.twitter.hbc;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;

import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

/**
 * Tracks the rate of a recurring event with a sliding window.
 * Threadsafe
 */
public class BasicRateTracker implements RateTracker {

    private final int granularityMillis;
    private final int numBuckets;

    private final RateUpdater rateUpdater;

    private ScheduledFuture future;
    private final ScheduledExecutorService executor;

    public BasicRateTracker(int granularityMillis, int numBuckets, boolean startPaused, ScheduledExecutorService executor) {
        Preconditions.checkArgument(numBuckets > 0);
        Preconditions.checkArgument(granularityMillis > 0);
        Preconditions.checkArgument(granularityMillis / numBuckets > 0);

        this.granularityMillis = granularityMillis;
        this.numBuckets = numBuckets;
        this.executor = Preconditions.checkNotNull(executor);

        this.rateUpdater = new RateUpdater(startPaused);
    }

    @Override
    public void eventObserved() {
        rateUpdater.eventObserved();
    }

    /**
     * Pauses the rate tracker: the rate will be frozen.
     */
    @Override
    public void pause() {
        rateUpdater.pause();
    }

    @Override
    public void resume() {
        rateUpdater.resume();
    }

    @Override
    public void start() {
        this.future = executor.scheduleAtFixedRate(rateUpdater, granularityMillis / numBuckets, granularityMillis / numBuckets, TimeUnit.MILLISECONDS);
    }

    /**
     * Stops tracking the rate
     */
    @Override
    public void stop() {
        if (future != null) {
            future.cancel(false);
        }
    }

    /**
     * Stops and shuts down the underlying executor
     */
    @Override
    public void shutdown() {
        stop();
        executor.shutdown();
    }

    /**
     * Only used for testing
     */
    @VisibleForTesting
    void recalculate() {
        rateUpdater.run();
    }

    /**
     * @return the current rate if it is available, NaN if not.
     * The rate is unavailable if granularityMillis hasn't elapsed
     */
    public double getCurrentRateSeconds() {
        return rateUpdater.getCurrentRateSeconds();
    }

    class RateUpdater implements Runnable {

        private final int[] buckets;
        private final Object lock;

        private boolean paused;
        private double rate;

        private boolean rateValid;
        private int total;
        private int currentBucket;
        private boolean previouslyPaused;
        private int currentBucketCount;

        RateUpdater() {
            this(false);
        }

        RateUpdater(boolean paused) {
            this.rateValid = false;
            this.buckets = new int[numBuckets];
            this.paused = paused;
            this.rate = Double.NaN;
            this.lock = new Object();
        }

        @Override
        public void run() {
            synchronized (lock) {
                int currentCount = currentBucketCount;
                currentBucketCount = 0;

                if (paused) {
                    previouslyPaused = true;
                    return;
                }

                // skip the first estimation after a pause, since it could be in the middle of an estimation
                if (previouslyPaused) {
                    previouslyPaused = false;
                    return;
                }

                int prevBucket = currentBucket;
                currentBucket = (currentBucket + 1) % numBuckets;

                if (currentBucket == 0) {
                    // we've wrapped around again. this rate is now valid
                    rateValid = true;
                }

                int prevBucketCount = buckets[prevBucket];
                buckets[prevBucket] = currentCount;

                total += currentCount - prevBucketCount;
                if (rateValid) {
                    rate = total * 1000d / granularityMillis;
                }
            }
        }

        public void eventObserved() {
            synchronized (lock) {
                currentBucketCount++;
            }
        }

        public void pause() {
            synchronized (lock) {
                paused = true;
            }
        }

        public void resume() {
            synchronized (lock) {
                paused = false;
            }
        }

        public double getCurrentRateSeconds() {
            synchronized (lock) {
                return rate;
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy