/* * Copyright 2015-present Open Networking Laboratory * * 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 org.onlab.util; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; import static com.google.common.base.Preconditions.checkArgument; import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor; import static org.onlab.util.Tools.groupedThreads; /** * Maintains a sliding window of value counts. The sliding window counter is * initialized with a number of window slots. Calls to #incrementCount() will * increment the value in the current window slot. Periodically the window * slides and the oldest value count is dropped. Calls to #get() will get the * total count for the last N window slots. */ public final class SlidingWindowCounter { private volatile int headSlot; private final int windowSlots; private final List<AtomicLong> counters; private final ScheduledExecutorService background; private static final int SLIDE_WINDOW_PERIOD_SECONDS = 1; /** * Creates a new sliding window counter with the given total number of * window slots. * * @param windowSlots total number of window slots */ public SlidingWindowCounter(int windowSlots) { checkArgument(windowSlots > 0, "Window size must be a positive integer"); this.windowSlots = windowSlots; this.headSlot = 0; // Initialize each item in the list to an AtomicLong of 0 this.counters = Collections.nCopies(windowSlots, 0) .stream() .map(AtomicLong::new) .collect(Collectors.toCollection(ArrayList::new)); background = newSingleThreadScheduledExecutor(groupedThreads("SlidingWindowCounter", "bg-%d")); background.scheduleWithFixedDelay(this::advanceHead, 0, SLIDE_WINDOW_PERIOD_SECONDS, TimeUnit.SECONDS); } /** * Releases resources used by the SlidingWindowCounter. */ public void destroy() { background.shutdownNow(); } /** * Increments the count of the current window slot by 1. */ public void incrementCount() { incrementCount(headSlot, 1); } /** * Increments the count of the current window slot by the given value. * * @param value value to increment by */ public void incrementCount(long value) { incrementCount(headSlot, value); } private void incrementCount(int slot, long value) { counters.get(slot).addAndGet(value); } /** * Gets the total count for the last N window slots. * * @param slots number of slots to include in the count * @return total count for last N slots */ public long get(int slots) { checkArgument(slots <= windowSlots, "Requested window must be less than the total window slots"); long sum = 0; for (int i = 0; i < slots; i++) { int currentIndex = headSlot - i; if (currentIndex < 0) { currentIndex = counters.size() + currentIndex; } sum += counters.get(currentIndex).get(); } return sum; } void advanceHead() { counters.get(slotAfter(headSlot)).set(0); headSlot = slotAfter(headSlot); } private int slotAfter(int slot) { return (slot + 1) % windowSlots; } }