package ddth.dasp.framework.stats; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLongArray; /** * This class encapsulates a rate (number of things/amount of time) counter. * * @author NBThanh <btnguyen2k@gmail.com> */ public class RateCounter { private final static int NUM_SLOTS_BUFFER = 2; /* * Number of slots should be power of 2 (e.g. 2, 4, 8, 16, etc) */ private int numSlots; private int numSlotsReturns; private int slotMask; private Timer timer; private TimerTask task; /* * Resolution is best to be power of 2 (e.g. 2, 4, 8, 16, etc) */ private int slotResolution; private long slotResolutionNanoseconds; private int resolutionShift; private int numSlotsLast10secs = -1; private int numSlotsLast20secs = -1; private int numSlotsLast30secs = -1; private int numSlotsLast40secs = -1; private int numSlotsLast50secs = -1; private int numSlotsLast60secs = -1; private String name; private AtomicLongArray slots; private AtomicInteger slotNumber = new AtomicInteger(0); private volatile long lastAccessTimestamp = System.nanoTime(); /** * Constructs a new {@link RateCounter} instance. */ public RateCounter() { } /** * Constructs a new {@link RateCounter} instance. */ public RateCounter(Timer timer) { setTimer(timer); } /** * Setter for {@link #timer}. * * @param timer * Timer */ public void setTimer(Timer timer) { this.timer = timer; } /** * Getter for {@link #timer}. * * @return Timer */ protected Timer getTimer() { return timer; } public void destroy() { if (task != null) { task.cancel(); task = null; } } /** * Initialzing method. */ public void init() { int numSlots = 4; // min number of slots is 4 while (numSlots < this.numSlots) { numSlots <<= 1; } this.numSlots = numSlots; this.slots = new AtomicLongArray(this.numSlots); this.numSlotsReturns = this.numSlots - NUM_SLOTS_BUFFER; this.slotMask = this.numSlots - 1; if (this.slotResolution < 1) { this.slotResolution = 1; } this.resolutionShift = 20; this.slotResolutionNanoseconds = 1 << this.resolutionShift; long temp = this.slotResolution * 1000000L; while (this.slotResolutionNanoseconds < temp) { this.resolutionShift++; this.slotResolutionNanoseconds <<= 1; } long nanoLast10secs = 10 * 1000000000L; long nanoLast20secs = 20 * 1000000000L; long nanoLast30secs = 30 * 1000000000L; long nanoLast40secs = 40 * 1000000000L; long nanoLast50secs = 50 * 1000000000L; long nanoLast60secs = 60 * 1000000000L; numSlotsLast10secs = -1; numSlotsLast20secs = -1; numSlotsLast30secs = -1; numSlotsLast40secs = -1; numSlotsLast50secs = -1; numSlotsLast60secs = -1; long nanoCounter = this.slotResolutionNanoseconds; int slotCounter = 1; while (slotCounter <= this.numSlotsReturns) { if (numSlotsLast10secs == -1 && nanoCounter >= nanoLast10secs) { numSlotsLast10secs = slotCounter; } if (numSlotsLast20secs == -1 && nanoCounter >= nanoLast20secs) { numSlotsLast20secs = slotCounter; } if (numSlotsLast30secs == -1 && nanoCounter >= nanoLast30secs) { numSlotsLast30secs = slotCounter; } if (numSlotsLast40secs == -1 && nanoCounter >= nanoLast40secs) { numSlotsLast40secs = slotCounter; } if (numSlotsLast50secs == -1 && nanoCounter >= nanoLast50secs) { numSlotsLast50secs = slotCounter; } if (numSlotsLast60secs == -1 && nanoCounter >= nanoLast60secs) { numSlotsLast60secs = slotCounter; } slotCounter++; nanoCounter += this.slotResolutionNanoseconds; } slotNumber.set(calcSlotNumber(System.nanoTime())); if (timer != null) { long delay = (numSlots / 4) * slotResolution; task = new IdleUpdateTask(this); timer.scheduleAtFixedRate(task, delay, delay); } } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getNumSlots() { return numSlots; } public void setNumSlots(int numSlots) { this.numSlots = numSlots; } /** * Gets number of slots that covers the last 10 seconds. * * @param numSlots * int */ public int getNumSlotsLast10secs() { return numSlotsLast10secs; } /** * Gets number of slots that covers the last 20 seconds. * * @param numSlots * int */ public int getNumSlotsLast20secs() { return numSlotsLast20secs; } /** * Gets number of slots that covers the last 30 seconds. * * @param numSlots * int */ public int getNumSlotsLast30secs() { return numSlotsLast30secs; } /** * Gets number of slots that covers the last 40 seconds. * * @param numSlots * int */ public int getNumSlotsLast40secs() { return numSlotsLast40secs; } /** * Gets number of slots that covers the last 50 seconds. * * @param numSlots * int */ public int getNumSlotsLast50secs() { return numSlotsLast50secs; } /** * Gets number of slots that covers the last 60 seconds. * * @param numSlots * int */ public int getNumSlotsLast60secs() { return numSlotsLast60secs; } /** * Gets slot's resolution in nanoseconds. * * @return long the resolution in nanoseconds */ public long getSlotResolutionNano() { return this.slotResolutionNanoseconds; } /** * Gets slot's resolution. * * @return long the resolution in milliseconds */ public int getSlotResolution() { return this.slotResolution; } /** * Sets slot's resolution. * * @param resolution * int slot's resolution in milliseconds */ public void setSlotResolution(int resolution) { this.slotResolution = resolution; } /** * Gets the counter's last access timestamp. * * @return long last access timestamp in nanoseconds */ public long getLastAccessTimestamp() { return this.lastAccessTimestamp; } /** * The "hash" function to calculate the slot number from the timestamp. * * @param timestamp * timestamp in nanoseconds * @return int the calculated slot number */ private int calcSlotNumber(long timestamp) { return (int) ((timestamp >> resolutionShift) & slotMask); } /** * Calculates the next slot number from the current one. * * @param currentSlotNumber * int * @return int */ private int nextSlotNumber(int currentSlotNumber) { return (currentSlotNumber + 1) & slotMask; } /** * Calculates the previous slot number from the current one. * * @param currentSlotNumber * int * @return int */ private int prevSlotNumber(int currentSlotNumber) { return (currentSlotNumber - 1) & slotMask; } /** * Gets the counter value of the current slot. * * @return long */ public long getCounter() { return slots.get(slotNumber.get()); } /** * Gets the last 'n' counter values from the the current slot (inclusive). * * @param numCounters * int number of slots to retrieve (current slot inclusive) * @return long */ public long[] getCounters(int numCounters) { if (numCounters < 1) { numCounters = 1; } if (numCounters > numSlotsReturns) { numCounters = numSlotsReturns; } long[] result = new long[numCounters]; int currentSlotNumber = slotNumber.get(); for (int temp = numCounters - 1; temp >= 0; temp--) { result[temp] = slots.get(currentSlotNumber); currentSlotNumber = prevSlotNumber(currentSlotNumber); } return result; } /** * Gets counter values that cover last 10 seconds. * * @return long[] */ public long[] getCountersLast10secs() { return numSlotsLast10secs > 0 ? getCounters(numSlotsLast10secs) : null; } /** * Gets counter values that cover last 20 seconds. * * @return long[] */ public long[] getCountersLast20secs() { return numSlotsLast20secs > 0 ? getCounters(numSlotsLast20secs) : null; } /** * Gets counter values that cover last 30 seconds. * * @return long[] */ public long[] getCountersLast30secs() { return numSlotsLast30secs > 0 ? getCounters(numSlotsLast30secs) : null; } /** * Gets counter values that cover last 40 seconds. * * @return long[] */ public long[] getCountersLast40secs() { return numSlotsLast40secs > 0 ? getCounters(numSlotsLast40secs) : null; } /** * Gets counter values that cover last 50 seconds. * * @return long[] */ public long[] getCountersLast50secs() { return numSlotsLast50secs > 0 ? getCounters(numSlotsLast50secs) : null; } /** * Gets counter values that cover last 60 seconds. * * @return long[] */ public long[] getCountersLast60secs() { return numSlotsLast60secs > 0 ? getCounters(numSlotsLast60secs) : null; } /** * Increases counter by 1 and returns the post-inc value. * * @return */ public long incCounter() { return incCounter(1); } /** * Increases counter by a specific value and returns the post-inc value. * * @param value * long * @return */ synchronized public long incCounter(long value) { long timestamp = System.nanoTime(); int currentSlotNumber = slotNumber.get(); int newSlotNumber = calcSlotNumber(timestamp); slotNumber.set(newSlotNumber); long result = value; if (currentSlotNumber != newSlotNumber) { // reset idle slots while (currentSlotNumber != newSlotNumber) { currentSlotNumber = nextSlotNumber(currentSlotNumber); // if (currentSlotNumber != newSlotNumber) { slots.set(currentSlotNumber, 0); // } } // set value to slot number slots.set(newSlotNumber, value); // long oldValue = slots.get(newSlotNumber); // if (!slots.compareAndSet(newSlotNumber, oldValue, value)) { // System.out.println("Conflict"); // result = slots.addAndGet(newSlotNumber, value); // } } else { // add value to slot number result = slots.addAndGet(newSlotNumber, value); } // update last access timestamp lastAccessTimestamp = timestamp; return result; } static class IdleUpdateTask extends TimerTask { private RateCounter rateCounter; public IdleUpdateTask(RateCounter rateCounter) { this.rateCounter = rateCounter; } /** * {@inheritDoc} */ @Override public void run() { rateCounter.incCounter(0); } } public static void main(String[] atgv) throws InterruptedException { Timer timer = new Timer(true); final RateCounter counter = new RateCounter(); counter.setTimer(timer); counter.setNumSlots(128); counter.setSlotResolution(100); counter.init(); int concurrent = 4; final CountDownLatch latch = new CountDownLatch(concurrent); for (int i = 0; i < concurrent; i++) { new Thread() { @Override public void run() { for (int i = 0; i < 1000; i++) { // System.out.println(i); counter.incCounter(1); try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } latch.countDown(); } }.start(); } latch.await(); long resolutionNano = counter.getSlotResolutionNano(); double resolutionMillis = resolutionNano / 1e6; System.out.println("Resolution Nano :" + resolutionNano); System.out.println("Resolution Millis:" + resolutionMillis); System.out.println("Rate: " + counter.slots); long slotCounts = 0; for (int i = 0; i < counter.slots.length(); i++) { slotCounts += counter.slots.get(i); } System.out.println(slotCounts); } }