/*
* 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.DateTimeUtils;
import java.util.concurrent.atomic.AtomicLong;
public class ShardedConcurrentCounter {
// this seemed to help maybe 10-15% by
private static int MEMORY_WORD_MULTIPLIER = 1;
private volatile long value = 0;
private final long maxStaleMillis;
private volatile long globalLastDrainMillis =
DateTimeUtils.currentTimeMillis();
private final CounterShard[] counterShards;
public ShardedConcurrentCounter(int numShards, long maxStaleMillis) {
this.maxStaleMillis = maxStaleMillis;
counterShards = new CounterShard[MEMORY_WORD_MULTIPLIER*numShards];
long now = DateTimeUtils.currentTimeMillis();
long staggerMillis = maxStaleMillis / numShards;
for (int i = 0; i < MEMORY_WORD_MULTIPLIER*numShards; i++) {
long firstDrainMillis = now + (i * staggerMillis);
// TODO: figure out if 1.5 make sense here?
counterShards[i] =
new CounterShard(firstDrainMillis, (long)(1.5 * maxStaleMillis));
}
}
public ShardedConcurrentCounter() {
this(16, 500);
}
public void add(long delta) {
counterShards[getShard()].add(delta);
}
private int getShard() {
return MEMORY_WORD_MULTIPLIER*(int)Thread.currentThread().getId() %
counterShards.length;
}
public long get() {
drainThreadToShared();
return value;
}
public long getStale() {
return value;
}
// TODO: possibly expose this publicly?
private void updateIfStale() {
if (DateTimeUtils.currentTimeMillis() - globalLastDrainMillis >= maxStaleMillis) {
synchronized (counterShards) {
if (DateTimeUtils.currentTimeMillis() - globalLastDrainMillis >=
maxStaleMillis
) {
drainThreadToShared();
globalLastDrainMillis = DateTimeUtils.currentTimeMillis();
}
}
}
}
private void drainThreadToShared() {
synchronized (counterShards) {
for (CounterShard counterShard : counterShards) {
value += counterShard.drain();
}
}
}
private class CounterShard {
private final long frequencyMillis;
private final AtomicLong counter = new AtomicLong(0);
private volatile long lastDrainMillis;
private CounterShard(long firstDrainMillis, long frequencyMillis) {
lastDrainMillis = firstDrainMillis;
this.frequencyMillis = frequencyMillis;
}
private void add(long delta) {
if (DateTimeUtils.currentTimeMillis() - lastDrainMillis >= frequencyMillis) {
drainThreadToShared();
lastDrainMillis = DateTimeUtils.currentTimeMillis();
}
counter.addAndGet(delta);
}
private long drain() {
return counter.getAndSet(0);
}
}
}