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

io.pcp.parfait.TimeWindowCounter Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2009-2017 Aconex
 *
 * 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 io.pcp.parfait;

import java.util.Arrays;

import net.jcip.annotations.GuardedBy;
import net.jcip.annotations.ThreadSafe;

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

/**
 * 

* Counter which keeps track of the increments only over a particular * {@link TimeWindow}, expiring old increments after the window has elapsed. For * example, a period of Uses the resolution of the supplied TimeWindow to group * events together for efficiency. For example, when supplied with a TimeWindow * with a period of 60 seconds and a resolution of 5 seconds, 12 'buckets' will * be created to keep count of events. This means that as each bucket is * overwritten after 60 seconds, the total count may be understated by as much * as (5/60) ≅ 8%. This, however, means that the memory footprint is * approximately that of just the 12 counters, rather than having to track the * time of each individual event. *

*

* Currently uses very coarse-grained locking (each {@link #get()} or * {@link #inc(long)} takes and holds a shared lock for the duration); this may * prove too contentious and require change later. *

*/ @ThreadSafe public class TimeWindowCounter implements Counter { @GuardedBy("lock") private long overallValue; @GuardedBy("lock") private final long[] interimValues; @GuardedBy("lock") private int headIndex = 0; @GuardedBy("lock") private long headTime; private final Object lock = new Object(); private final Supplier timeSource; private final TimeWindow window; public TimeWindowCounter(TimeWindow window) { this(window, new SystemTimePoller()); } @VisibleForTesting TimeWindowCounter(TimeWindow window, Supplier timeSource) { this.window = window; this.interimValues = new long[(int) (window.getBuckets())]; this.timeSource = timeSource; this.headTime = timeSource.get(); } @Override public void inc(long increment) { synchronized (lock) { cleanState(); overallValue += increment; interimValues[headIndex] += increment; } } /** * Clean out old data from the buckets, getting us ready to enter a new * bucket. interimValues, headTime, and headIndex comprise a circular buffer * of the last n sub-values, and the start time of the head bucket. On each * write or get, we progressively clear out entries in the circular buffer * until headTime is within one 'tick' of the current time; we have then * found the correct bucket. */ @GuardedBy("lock") private void cleanState() { long eventTime = timeSource.get(); long bucketsToSkip = (eventTime - headTime) / window.getResolution(); while (bucketsToSkip > 0) { headIndex = (headIndex + 1) % interimValues.length; bucketsToSkip--; overallValue -= interimValues[headIndex]; interimValues[headIndex] = 0L; headTime += window.getResolution(); } } public Long get() { synchronized (lock) { cleanState(); return overallValue; } } @Override public void inc() { inc(1L); } @Override public String toString() { return String.format("last %s=%s", window.getName(), overallValue); } @VisibleForTesting String counterState() { synchronized (lock) { return Arrays.toString(interimValues); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy