/* * 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.Duration; import org.joda.time.ReadableDateTime; import java.util.concurrent.atomic.AtomicLong; /** * special purpose counter that decays exponentially fashion. * <p/> * Note: all value is decayed as though it was added at the 'start' value. * This is best used to decay the counter value once it has been fixed */ public class DecayCounter implements EventCounter { private final AtomicLong count = new AtomicLong(0); private final ReadableDateTime start; private final ReadableDateTime decayStart; private final ReadableDateTime end; private final float decayRatePerSecond; /** * @param start start of the counter range * @param decayStart time from which to compute decay * @param end end of the counter range * @param decayRatePerSecond ex, 0.05 => 5% decay per second */ public DecayCounter( ReadableDateTime start, ReadableDateTime decayStart, ReadableDateTime end, float decayRatePerSecond ) { this.start = start; this.decayStart = decayStart; this.end = end; this.decayRatePerSecond = decayRatePerSecond; } public DecayCounter( ReadableDateTime start, ReadableDateTime end, float decayRatePerSecond ) { this(start, start, end, decayRatePerSecond); } @Override public void add(long delta) { count.addAndGet(delta); } /** * @return counter value after computing exponential decay of the counter * value */ @Override public long getValue() { DateTime now = getNow(); // don't start decay unless it's at least 1s after the decayStart if (now.isAfter(decayStart.toDateTime().plusSeconds(1))) { Duration elapsed = new Duration(decayStart, now); long millis = elapsed.getMillis(); // compute total decay for millis / 1000 seconds double thisDecay = Math.pow(1.0 - decayRatePerSecond, (double) (millis / (double) 1000)); return (long) (count.get() * thisDecay); } else { return count.get(); } } @Override public ReadableDateTime getStart() { return start; } @Override public ReadableDateTime getEnd() { return end; } @Override public Duration getLength() { return new Duration(start, end); } /** * creates a merged counter with our decayed value + the other counter's * value. This will be a DecayCounter with a decayStart of getNow() * and a range that spans the extend of both * * @param counter : any EventCounter * @return */ @Override public EventCounter merge(EventCounter counter) { ReadableDateTime mergedStart = start.isBefore(counter.getStart()) ? start : counter.getStart(); ReadableDateTime mergedEnd = end.isAfter(counter.getEnd()) ? end : counter.getEnd(); DateTime now = getNow(); DecayCounter mergedCounter = new DecayCounter( mergedStart, now.isAfter(decayStart) ? now : decayStart, mergedEnd, decayRatePerSecond ); mergedCounter.add(getValue()); mergedCounter.add(counter.getValue()); return mergedCounter; } DateTime getNow() { return new DateTime(); } }