/**
* Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.util;
import java.util.Arrays;
import com.opengamma.OpenGammaRuntimeException;
/**
* A counter that keeps track of how many times something has happened
* in the recent past.
* <p>
* The implementation uses {@link System#currentTimeMillis()} so
* the figures will only be approximate.
* <p>
* The implementation is thread-safe.
*/
public class PerformanceCounter {
/**
* The length of history to keep.
*/
private final int _secondsOfHistoryToKeep;
/**
* The number of hits.
*/
private long _hits;
/**
* The history of hits.
*/
private final long[] _hitsHistory;
/**
* The timestamp representing zero.
*/
private long _zeroTimestamp;
/**
* The timestamp that the last hit occurred.
*/
private long _lastHitTimestamp;
/**
* Creates the counter with a number of seconds to keep history for.
* @param secondsOfHistoryToKeep the seconds to keep history for
*/
public PerformanceCounter(int secondsOfHistoryToKeep) {
this(secondsOfHistoryToKeep, System.currentTimeMillis());
}
/**
* Creates the counter with a number of seconds to keep history for and a timestamp.
* @param secondsOfHistoryToKeep the seconds to keep history for
* @param zeroTimestamp the zero epoch millisecond timestamp
*/
PerformanceCounter(int secondsOfHistoryToKeep, long zeroTimestamp) {
if (secondsOfHistoryToKeep <= 0) {
throw new IllegalArgumentException("secondsOfHistoryToKeep must be positive");
}
if (zeroTimestamp < 0) {
throw new IllegalArgumentException("zeroTimestamp must be non-negative");
}
_secondsOfHistoryToKeep = secondsOfHistoryToKeep;
_hitsHistory = new long[_secondsOfHistoryToKeep];
reset(zeroTimestamp);
}
//-------------------------------------------------------------------------
/**
* Gets the number of seconds to keep history for,
* @return the history storage length in seconds
*/
public int getSecondsOfHistoryToKeep() {
return _secondsOfHistoryToKeep;
}
//-------------------------------------------------------------------------
/**
* Resets the counter.
*/
public final void reset() {
// final, as this is called in the constructor
long timestamp = System.currentTimeMillis();
reset(timestamp);
}
/**
* Resets the counter.
* @param zeroTimestamp the zero epoch millisecond timestamp
*/
synchronized void reset(long zeroTimestamp) {
_hits = 0;
Arrays.fill(_hitsHistory, 0);
_zeroTimestamp = zeroTimestamp;
_lastHitTimestamp = _zeroTimestamp;
}
private long getSecondsSinceInception(long timestamp) {
return (timestamp - _zeroTimestamp) / 1000;
}
private long getEarliestAvailableTime() {
long lastHitTimestampRoundedDownToSeconds = _lastHitTimestamp / 1000 * 1000;
return lastHitTimestampRoundedDownToSeconds - 1000 * _secondsOfHistoryToKeep;
}
private int getIndex(long timestamp) {
if (timestamp < getEarliestAvailableTime()) {
throw new IllegalArgumentException("Earliest available is " + getEarliestAvailableTime() + ", tried to request " + timestamp);
}
return (int) (getSecondsSinceInception(timestamp) % _secondsOfHistoryToKeep);
}
private synchronized void hitMultiple(long timestamp, long count) {
if (timestamp < _lastHitTimestamp) { // could happen if the system clock is played with
reset(timestamp);
}
long secondsSinceLastHit = getSecondsSinceInception(timestamp) - getSecondsSinceInception(_lastHitTimestamp);
if (secondsSinceLastHit < 0) {
throw new OpenGammaRuntimeException("Seconds since last hit should never be negative" + secondsSinceLastHit);
}
if (secondsSinceLastHit >= _secondsOfHistoryToKeep) {
Arrays.fill(_hitsHistory, _hits);
} else {
int lastIndex = getIndex(_lastHitTimestamp);
int index = getIndex(timestamp);
if (index > lastIndex) {
Arrays.fill(_hitsHistory, lastIndex, index, _hits);
} else if (index < lastIndex) {
Arrays.fill(_hitsHistory, lastIndex, _hitsHistory.length, _hits);
Arrays.fill(_hitsHistory, 0, index, _hits);
}
}
_hits += count;
_lastHitTimestamp = timestamp;
}
void hit(long timestamp) {
hitMultiple(timestamp, 1);
}
/**
* Stores a performance counter hit.
*/
public void hit() {
hitMultiple(1);
}
/**
* Stores multiple performance counter hits.
* @param count The number of hits to register
*/
public synchronized void hitMultiple(long count) {
long timestamp = System.currentTimeMillis();
hitMultiple(timestamp, count);
}
/**
* Gets the count of hits per second.
* @return the hit-rate
*/
public double getHitsPerSecond() {
return getHitsPerSecond(_secondsOfHistoryToKeep);
}
/**
* Gets the count of hits per second.
* @param secsOfHistory the history to calculate over
* @return the hit-rate
*/
public double getHitsPerSecond(int secsOfHistory) {
long timestamp = System.currentTimeMillis();
return getHitsPerSecond(secsOfHistory, timestamp);
}
double getHitsPerSecondAsOfLastHit(int secsOfHistory) {
long timestamp = _lastHitTimestamp;
return getHitsPerSecond(secsOfHistory, timestamp);
}
synchronized double getHitsPerSecond(int secsOfHistory, long timestamp) {
if (secsOfHistory <= 0) {
throw new IllegalArgumentException("Please give positive secs of history: " + secsOfHistory);
}
if (secsOfHistory > _secondsOfHistoryToKeep) {
throw new IllegalArgumentException("Max secs of history is " + _secondsOfHistoryToKeep + ", was given " + secsOfHistory);
}
long historicalTime = timestamp - 1000 * secsOfHistory;
if (historicalTime < getEarliestAvailableTime()) { // could happen if the system clock is played with
reset(timestamp);
}
long currentHitCount;
if (timestamp < _zeroTimestamp) {
currentHitCount = 0;
} else if (timestamp >= _lastHitTimestamp) {
currentHitCount = _hits;
} else {
int currentIndex = getIndex(timestamp);
currentHitCount = _hitsHistory[currentIndex];
}
long historicalHitCount;
if (historicalTime < _zeroTimestamp) {
historicalHitCount = 0;
} else if (historicalTime >= _lastHitTimestamp) {
historicalHitCount = _hits;
} else {
int historicalIndex = getIndex(historicalTime);
historicalHitCount = _hitsHistory[historicalIndex];
}
long hits = currentHitCount - historicalHitCount;
double hitsPerSecond = ((double) hits) / secsOfHistory;
return hitsPerSecond;
}
}