package com.arondor.common.management.statistics;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import org.apache.log4j.Logger;
import com.arondor.common.management.mbean.MBeanObject;
/**
* Statistics class aggregator
*
* @author francois
*
*/
public class Statistics extends MBeanObject implements Serializable
{
/**
*
*/
private static final long serialVersionUID = -6655570213872337647L;
/**
* Log4J logger
*/
private static final Logger LOG = Logger.getLogger(Statistics.class);
/**
*
*/
private static Statistics SINGLETON = null;
/**
* Statistics : time when Statistics started
*/
private long startTime = System.currentTimeMillis();
/**
* Set the number of values used for instantSpeed duration
*/
private int instantSpeedNumber = 32;
/**
*
*/
public static synchronized Statistics getInstance()
{
if (SINGLETON == null)
SINGLETON = new Statistics();
return SINGLETON;
}
private Statistics()
{
super("Statistics");
}
public static String prettyPrint(long numberMS)
{
if (numberMS < 1000)
{
return String.valueOf(numberMS + "ms");
}
if (numberMS < 60 * 1000)
{
return String.valueOf(((float) numberMS) / 1000 + "s");
}
long s = numberMS / 1000;
long m = (s / 60) % 60;
long h = (s / 3600) % 24;
long d = (s / 86400);
s = s % 60;
return (d > 0 ? (d + "d") : "") + ((h > 0) ? (h + "h") : "") + m + "m" + s + "s";
}
/**
* Simple class to store stat information
*
* @author Francois Barre
*
*/
public class StatInfo extends MBeanObject
{
/**
* Class of the StatInfo
*/
private final Class<?> clazz;
/**
*
*/
private String name;
/**
*
*/
private long number = 0;
/**
*
*/
private long total = 0;
/**
*
*/
private long min = Long.MAX_VALUE;
/**
*
*/
private long max = Long.MIN_VALUE;
/**
*
*/
private Queue<Long> instantValues = new LinkedList<Long>();
/**
*
*/
private Queue<Long> instantStartPoints = new LinkedList<Long>();
/**
* Simple Constructor
*
* @param name
* the name for this stat info
*/
public StatInfo(Class<?> clazz, String name)
{
super(clazz, name);
this.clazz = clazz;
this.name = name;
}
/**
* Update stat
*
* @param statPoint
* time spent, in milliseconds
*/
public synchronized void update(StatPoint statPoint)
{
number += statPoint.getNumber();
long duration = statPoint.getDuration();
total += duration;
min = Math.min(min, duration);
max = Math.max(max, duration);
while (instantValues.size() >= getInstantSpeedNumber())
{
instantValues.remove();
instantStartPoints.remove();
}
instantValues.add(duration);
instantStartPoints.add(System.currentTimeMillis());
}
public float getTheoreticalRate()
{
return (total > 0 ? ((float) number * 1000 / (float) total) : 0);
}
public float getEffectiveRate()
{
long runtime = (System.currentTimeMillis() - startTime);
return (runtime > 0 ? ((float) number * 1000 / (float) runtime) : 0);
}
public synchronized float getInstantDuration()
{
int number = instantValues.size();
if (number == 0)
return 0;
long total = 0;
for (Long value : instantValues)
{
total += value;
}
return ((float) total) / (float) number;
}
public float getInstantTheoreticalRate()
{
return ((float) 1000) / getInstantDuration();
}
public synchronized float getInstantEffectiveRate()
{
if (instantStartPoints.isEmpty())
return 0f;
long globalInstantDuration = System.currentTimeMillis() - instantStartPoints.peek();
if (globalInstantDuration == 0)
return 0f;
int instantNumbers = instantStartPoints.size();
return ((float) (1000 * instantNumbers)) / (float) globalInstantDuration;
}
public synchronized int getInstantValueNumber()
{
return instantValues.size();
}
public long getTotalCalls()
{
return number;
}
public long getMinDuration()
{
return min;
}
public long getMaxDuration()
{
return max;
}
public float getAverageDuration()
{
return (number > 0 ? ((float) total / (float) number) : 0f);
}
public String getTotalSpent()
{
return prettyPrint(total);
}
public long getTotal()
{
return total;
}
public Class<?> getClazz()
{
return clazz;
}
/**
* To String
*/
public String toString()
{
return clazz.getName() + "." + name + "[total=" + prettyPrint(total) + ",calls=" + number + ",min="
+ prettyPrint(min) + ",max=" + prettyPrint(max) + ",avg="
+ prettyPrint((number > 0 ? (total / number) : 0)) + ",theo=" + getTheoreticalRate() + " c/s"
+ ",rate=" + getEffectiveRate() + " c/s" + "]";
}
public String getName()
{
return name;
}
}
/**
* Statistics info map
*/
private Map<String, StatInfo> statInfoMap = new HashMap<String, StatInfo>();
/**
*
*/
private Thread periodicThread;
/**
*
* @param statsInterval
* At which interval shall we dump statistics
*/
public synchronized void startStatThread(final int statsInterval)
{
long newStartTime = System.currentTimeMillis();
LOG.info("Setting Processing Statistics to start Now ! (gap=" + (newStartTime - startTime)
+ "ms), statsInterval=" + statsInterval + "s");
startTime = newStartTime;
if (statsInterval > 0)
{
periodicThread = new Thread("Statistics")
{
@Override
public void run()
{
LOG.info("Starting Statistics PeriodicThread");
while (true)
{
try
{
Thread.sleep(statsInterval * 1000);
}
catch (InterruptedException e)
{
LOG.debug("Interrupted StatInfo Thread.");
break;
}
showStatistics();
}
}
};
periodicThread.start();
}
}
public synchronized void stopStatThread()
{
if (periodicThread != null)
{
periodicThread.interrupt();
}
}
public static void stopStatistics()
{
getInstance().stopStatThread();
getInstance().showStatistics();
}
public void updateStat(StatPoint statPoint, String name)
{
String fullname = statPoint.getFullName();
if (name != null)
fullname += "." + name;
StatInfo statInfo;
synchronized (statInfoMap)
{
statInfo = statInfoMap.get(fullname);
if (statInfo == null)
{
statInfo = new StatInfo(statPoint.getClazz(), statPoint.getName() + (name != null ? name : ""));
statInfoMap.put(fullname, statInfo);
}
}
statInfo.update(statPoint);
}
public void updateStat(StatPoint statPoint)
{
updateStat(statPoint, null);
}
/**
*
*/
public synchronized void showStatistics()
{
String msg = "Stats : (running for " + getRunningTime() + "ms)";
List<String> keys = new ArrayList<String>(statInfoMap.keySet());
Collections.sort(keys);
for (String key : keys)
{
msg += "\n" + statInfoMap.get(key);
}
LOG.info(msg);
}
public synchronized void writeStatistics2CSV(BufferedWriter writer) throws IOException
{
writer.write("class;name;total;calls;minimum;maximum;average;theoreticalRate;effectiveRate\n");
for (StatInfo statInfo : statInfoMap.values())
{
writer.write(statInfo.getClazz().getName() + ";" + statInfo.getName() + ";" + statInfo.getTotal() + ";"
+ statInfo.getTotalCalls() + ";" + statInfo.getMinDuration() + ";" + statInfo.getMaxDuration()
+ ";" + statInfo.getAverageDuration() + ";" + statInfo.getTheoreticalRate() + ";"
+ statInfo.getEffectiveRate() + "\n");
}
}
public Long getRunningTime()
{
return System.currentTimeMillis() - startTime;
}
public String getRunningTimeStr()
{
return prettyPrint(System.currentTimeMillis() - startTime);
}
public void setInstantSpeedNumber(int instantSpeedNumber)
{
this.instantSpeedNumber = instantSpeedNumber;
}
public int getInstantSpeedNumber()
{
return instantSpeedNumber;
}
}