/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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.apache.nifi.util.timebuffer; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; public class TimedBuffer<T> { private final int numBins; private final EntitySum<T>[] bins; private final EntityAccess<T> entityAccess; private final TimeUnit binPrecision; @SuppressWarnings("unchecked") public TimedBuffer(final TimeUnit binPrecision, final int numBins, final EntityAccess<T> accessor) { this.binPrecision = binPrecision; this.numBins = numBins + 1; this.bins = new EntitySum[this.numBins]; for (int i = 0; i < this.numBins; i++) { this.bins[i] = new EntitySum<>(binPrecision, numBins, accessor); } this.entityAccess = accessor; } public T add(final T entity) { final int binIdx = (int) (binPrecision.convert(System.currentTimeMillis(), TimeUnit.MILLISECONDS) % numBins); final EntitySum<T> sum = bins[binIdx]; return sum.addOrReset(entity); } public T getAggregateValue(final long sinceEpochMillis) { final int startBinIdx = (int) (binPrecision.convert(sinceEpochMillis, TimeUnit.MILLISECONDS) % numBins); T total = null; for (int i = 0; i < numBins; i++) { int binIdx = (startBinIdx + i) % numBins; final EntitySum<T> bin = bins[binIdx]; if (!bin.isExpired()) { total = entityAccess.aggregate(total, bin.getValue()); } } return total; } private static class EntitySum<S> { private final EntityAccess<S> entityAccess; private final AtomicReference<S> ref = new AtomicReference<>(); private final TimeUnit binPrecision; private final int numConfiguredBins; public EntitySum(final TimeUnit binPrecision, final int numConfiguredBins, final EntityAccess<S> aggregator) { this.binPrecision = binPrecision; this.entityAccess = aggregator; this.numConfiguredBins = numConfiguredBins; } private S add(final S event) { S newValue; S value; do { value = ref.get(); newValue = entityAccess.aggregate(value, event); } while (!ref.compareAndSet(value, newValue)); return newValue; } public S getValue() { return ref.get(); } public boolean isExpired() { // entityAccess.getTimestamp(curValue) represents the time at which the current value // was last updated. If the last value is less than current time - 1 binPrecision, then it // means that we've rolled over and need to reset the value. final long maxExpectedTimePeriod = System.currentTimeMillis() - TimeUnit.MILLISECONDS.convert(numConfiguredBins, binPrecision); final S curValue = ref.get(); return (entityAccess.getTimestamp(curValue) < maxExpectedTimePeriod); } public S addOrReset(final S event) { // entityAccess.getTimestamp(curValue) represents the time at which the current value // was last updated. If the last value is less than current time - 1 binPrecision, then it // means that we've rolled over and need to reset the value. final long maxExpectedTimePeriod = System.currentTimeMillis() - TimeUnit.MILLISECONDS.convert(1, binPrecision); final S curValue = ref.get(); if (entityAccess.getTimestamp(curValue) < maxExpectedTimePeriod) { ref.compareAndSet(curValue, entityAccess.createNew()); } return add(event); } } }