/*
* 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;
import org.joda.time.ReadableDuration;
public class MultiWindowMin implements ReadableMultiWindowCounter, WritableMultiWindowStat {
private static final ReadableDuration COUNTER_GRANULARITY =
Duration.standardSeconds(6);
private final Object rollLock = new Object();
private final CompositeMin allTimeCounter;
private final CompositeMin hourCounter;
private final CompositeMin tenMinuteCounter;
private final CompositeMin minuteCounter;
private volatile EventCounterIf<EventCounter> currentCounter;
MultiWindowMin(
CompositeMin allTimeCounter,
CompositeMin hourCounter,
CompositeMin tenMinuteCounter,
CompositeMin minuteCounter
) {
this.allTimeCounter = allTimeCounter;
this.hourCounter = hourCounter;
this.tenMinuteCounter = tenMinuteCounter;
this.minuteCounter = minuteCounter;
currentCounter = addNewCurrentCounter();
}
public MultiWindowMin() {
this(
new CompositeMin(Duration.standardMinutes(Integer.MAX_VALUE)),
new CompositeMin(Duration.standardMinutes(60)),
new CompositeMin(Duration.standardMinutes(10)),
new CompositeMin(Duration.standardMinutes(1))
);
}
@Override
public void add(long value) {
rollCurrentIfNeeded();
currentCounter.add(value);
}
@Override
public long getMinuteValue() {
rollCurrentIfNeeded();
return minuteCounter.getValue();
}
@Override
public long getTenMinuteValue() {
rollCurrentIfNeeded();
return tenMinuteCounter.getValue();
}
@Override
public long getHourValue() {
rollCurrentIfNeeded();
return hourCounter.getValue();
}
@Override
public long getAllTimeValue() {
rollCurrentIfNeeded();
return allTimeCounter.getValue();
}
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 = addNewCurrentCounter();
}
}
}
}
private MinEventCounter addNewCurrentCounter() {
ReadableDateTime now = new DateTime();
MinEventCounter minEventCounter = new MinEventCounter(
now,
now.toDateTime().plus(COUNTER_GRANULARITY)
);
allTimeCounter.addEventCounter(minEventCounter);
hourCounter.addEventCounter(minEventCounter);
tenMinuteCounter.addEventCounter(minEventCounter);
minuteCounter.addEventCounter(minEventCounter);
return minEventCounter;
}
public MultiWindowMin merge(MultiWindowMin other) {
return new MultiWindowMin(
(CompositeMin) allTimeCounter.merge(other.allTimeCounter),
(CompositeMin) hourCounter.merge(other.hourCounter),
(CompositeMin) tenMinuteCounter.merge(other.tenMinuteCounter),
(CompositeMin) minuteCounter.merge(other.minuteCounter)
);
}
}