com.facebook.stats.MultiWindowRate.java Source code

Java tutorial

Introduction

Here is the source code for com.facebook.stats.MultiWindowRate.java

Source

/*
 * Copyright (C) 2012 Facebook, 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.facebook.stats;

import org.joda.time.DateTime;
import org.joda.time.DateTimeUtils;
import org.joda.time.Duration;
import org.joda.time.ReadableDateTime;

public class MultiWindowRate implements ReadableMultiWindowRate, WritableMultiWindowStat {
    private static final int DEFAULT_TIME_BUCKET_SIZE_MILLIS = 6000; // 6 seconds
    // all-time counter is a windowed counter that is effectively unbounded
    private final CompositeSum allTimeCounter;
    private final CompositeSum hourCounter;
    private final CompositeSum tenMinuteCounter;
    private final CompositeSum minuteCounter;
    private final EventRate hourRate;
    private final EventRate tenMinuteRate;
    private final EventRate minuteRate;
    private final ReadableDateTime start;
    private final Object rollLock = new Object();
    private final int timeBucketSizeMillis;

    private volatile EventCounterIf<EventCounter> currentCounter;

    MultiWindowRate(int timeBucketSizeMillis) {
        this(newCompositeEventCounter(Integer.MAX_VALUE), newCompositeEventCounter(60),
                newCompositeEventCounter(10), newCompositeEventCounter(1), new DateTime(), timeBucketSizeMillis);
    }

    MultiWindowRate(CompositeSum allTimeCounter, CompositeSum hourCounter, CompositeSum tenMinuteCounter,
            CompositeSum minuteCounter, ReadableDateTime start, int timeBucketSizeMillis) {
        this.allTimeCounter = allTimeCounter;
        this.hourCounter = hourCounter;
        this.tenMinuteCounter = tenMinuteCounter;
        this.minuteCounter = minuteCounter;
        this.start = start;
        this.timeBucketSizeMillis = timeBucketSizeMillis;
        hourRate = newEventRate(hourCounter, Duration.standardMinutes(60), start);
        tenMinuteRate = newEventRate(tenMinuteCounter, Duration.standardMinutes(10), start);
        minuteRate = newEventRate(minuteCounter, Duration.standardMinutes(1), start);
        currentCounter = nextCurrentCounter(start.toDateTime());
    }

    public MultiWindowRate() {
        this(DEFAULT_TIME_BUCKET_SIZE_MILLIS);
    }

    private static CompositeSum newCompositeEventCounter(int minutes) {
        return new CompositeSum(Duration.standardMinutes(minutes));
    }

    private EventRate newEventRate(EventCounterIf<EventCounter> counter, Duration windowSize,
            ReadableDateTime start) {
        return new EventRateImpl(counter, windowSize, start);
    }

    @Override
    public void add(long delta) {
        rollCurrentIfNeeded();
        currentCounter.add(delta);
    }

    private void rollCurrentIfNeeded() {
        //do outside the synchronized block
        long now = DateTimeUtils.currentTimeMillis();
        // this is false for the majority of calls, so skip lock acquisition
        if (currentCounter.getEnd().getMillis() <= now) {
            synchronized (rollLock) {
                // lock and re-check
                if (currentCounter.getEnd().getMillis() <= now) {
                    currentCounter = nextCurrentCounter(new DateTime(now));
                }
            }
        }
    }

    @Override
    public long getMinuteSum() {
        rollCurrentIfNeeded();

        return minuteCounter.getValue();
    }

    @Override
    public long getMinuteRate() {
        rollCurrentIfNeeded();

        return minuteRate.getValue();
    }

    @Override
    public long getTenMinuteSum() {
        rollCurrentIfNeeded();

        return tenMinuteCounter.getValue();
    }

    @Override
    public long getTenMinuteRate() {
        rollCurrentIfNeeded();

        return tenMinuteRate.getValue();
    }

    @Override
    public long getHourSum() {
        rollCurrentIfNeeded();

        return hourCounter.getValue();
    }

    @Override
    public long getHourRate() {
        rollCurrentIfNeeded();

        return hourRate.getValue();
    }

    @Override
    public long getAllTimeSum() {
        return allTimeCounter.getValue();
    }

    @Override
    public long getAllTimeRate() {
        Duration sinceStart = new Duration(start, getNow());

        if (sinceStart.getStandardSeconds() == 0) {
            return 0;
        }

        return allTimeCounter.getValue() / sinceStart.getStandardSeconds();
    }

    protected ReadableDateTime getNow() {
        return new DateTime();
    }

    // current
    private EventCounterIf<EventCounter> nextCurrentCounter(ReadableDateTime now) {
        EventCounter eventCounter = new EventCounterImpl(now, now.toDateTime().plusMillis(timeBucketSizeMillis));

        allTimeCounter.addEventCounter(eventCounter);
        hourCounter.addEventCounter(eventCounter);
        tenMinuteCounter.addEventCounter(eventCounter);
        minuteCounter.addEventCounter(eventCounter);

        return eventCounter;
    }

    public MultiWindowRate merge(MultiWindowRate rate) {
        return new MultiWindowRate((CompositeSum) allTimeCounter.merge(rate.allTimeCounter),
                (CompositeSum) hourCounter.merge(rate.hourCounter),
                (CompositeSum) tenMinuteCounter.merge(rate.tenMinuteCounter),
                (CompositeSum) minuteCounter.merge(rate.minuteCounter),
                start.isBefore(rate.start) ? start : rate.start, timeBucketSizeMillis);
    }
}