/**
*
*/
package com.trendrr.oss.executionreport;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.trendrr.oss.IncrementMap;
import com.trendrr.oss.TimeAmount;
import com.trendrr.oss.Timeframe;
/**
*
* Increments keys for display in an execution report.
*
* inc empty string for top level
* inc with . for nested keys.
*
* @author Dustin Norlander
* @created Sep 20, 2011
*
*/
public class ExecutionReport extends TimerTask implements ExecutionReportIncrementor {
protected static Log log = LogFactory.getLog(ExecutionReport.class);
protected String name;
protected AtomicReference<ExecutionReportConfig> config = new AtomicReference<ExecutionReportConfig>();
protected AtomicReference<Date> lastSerialization = new AtomicReference<Date>(new Date());
private class Vals {
IncrementMap vals = new IncrementMap();
IncrementMap millis = new IncrementMap();
}
protected AtomicReference<Vals> vals = new AtomicReference<Vals>(new Vals());
protected static AtomicReference<Timer> timer = new AtomicReference<Timer>();
protected static AtomicReference<ExecutionReportConfig> defaultConfig = new AtomicReference<ExecutionReportConfig>(new ExecutionReportConfig());
public static void setDefaultConfig(ExecutionReportConfig config) {
defaultConfig.set(config);
}
public static ExecutionReportConfig getDefaultConfig() {
return defaultConfig.get();
}
protected static AtomicReference<String> jvmInstanceId = new AtomicReference<String>();
public static String getJvmInstanceId() {
return jvmInstanceId.get();
}
/**
* This will add an additional level that allows you to view executions for specific jvms
*
* This is null by default, but we suggest setting it to the IP address via
*
* ExecutionReport.setJvmInstanceId(WhatsMyIp.getIP());
*
*
* @param jvmInstanceId
*/
public static void setJvmInstanceId(String jvmInstanceId) {
ExecutionReport.jvmInstanceId.set(cleanup(jvmInstanceId));
}
protected static ConcurrentHashMap<String, ExecutionReport> reports = new ConcurrentHashMap<String, ExecutionReport>();
public static ExecutionReport instance(String name) {
return instance(name, null);
}
public static ExecutionReport instance(String name, ExecutionReportConfig config) {
ExecutionReport report = reports.putIfAbsent(name, new ExecutionReport(name, config));
if (report == null) {
report = reports.get(name);
report.start();
}
return report;
}
/**
* Create a new execution report
* @param name name of this top level report.
*/
protected ExecutionReport(String name, ExecutionReportConfig config) {
this.name = cleanup(name);
if (config != null) {
this.config.set(config);
} else {
this.config.set(getDefaultConfig());
}
}
/**
* cleans up a key.
*
* will replace . with | and trim whitespace
* @param name
* @return
*/
public static String cleanup(String name) {
return name.replace('.', '|').trim();
}
/**
* Increments the amount. uses start to calculate the number of millis this execution took
* @param key
* @param amount
* @param start
*/
public void inc(String key, long amount, Date start) {
if (start != null) {
this.inc(key, amount, new Date().getTime() - start.getTime());
} else {
this.inc(key, amount, 0l);
}
}
/**
* increments the val and millis
* @param key
* @param amount
* @param millis
*/
public void inc(String key, long amount, long millis) {
Vals v = vals.get();
v.vals.inc(key, amount);
v.millis.inc(key, millis);
}
/**
* increments the value and uses 0 for the millis
* @param key
* @param amount
*/
public void inc(String key, long amount) {
inc(key, amount, 0l);
}
/**
* increments the value by 1 and uses start to calculation the millis
* @param key
* @param start
*/
public void inc(String key, Date start) {
inc(key, 1l, start);
}
public void inc(String key) {
inc(key, 1l, 0l);
}
/* (non-Javadoc)
* @see com.trendrr.oss.executionreport.ExecutionReportIncrementor#inc(long, java.util.Date)
*/
@Override
public void inc(long amount, Date start) {
this.inc("", amount, start);
}
/* (non-Javadoc)
* @see com.trendrr.oss.executionreport.ExecutionReportIncrementor#inc(long, long)
*/
@Override
public void inc(long amount, long millis) {
this.inc("", amount, millis);
}
/* (non-Javadoc)
* @see com.trendrr.oss.executionreport.ExecutionReportIncrementor#inc(long)
*/
@Override
public void inc(long amount) {
this.inc("", amount);
}
/* (non-Javadoc)
* @see com.trendrr.oss.executionreport.ExecutionReportIncrementor#inc(java.util.Date)
*/
@Override
public void inc(Date start) {
this.inc("", start);
}
/* (non-Javadoc)
* @see com.trendrr.oss.executionreport.ExecutionReportIncrementor#inc()
*/
@Override
public void inc() {
this.inc("");
}
/**
* clears any values
*/
public void clear() {
vals.set(new Vals());
}
/**
* this runs on the timer as long as flushMillis is set.
*/
public synchronized void flush() {
try {
Vals v = vals.getAndSet(new Vals());
Date end = this.lastSerialization.getAndSet(new Date());
ExecutionReportNodeTree tree = new ExecutionReportNodeTree(this.getName());
List<ExecutionReportPoint> points = new ArrayList<ExecutionReportPoint>();
for (String k : v.vals.keySet()) {
Long val = v.vals.get(k);
if (val == null)
continue;
Long millis = v.millis.get(k);
if (millis == null)
millis = 0l;
if (k.isEmpty()) {
//increment self
ExecutionReportPoint point = new ExecutionReportPoint();
point.setFullname(this.getName());
point.setTimestamp(end);
point.setMillis(millis);
point.setVal(val);
points.add(point);
} else {
tree.addNode(k, val, millis);
}
}
HashMap<String, Set<String>> children = new HashMap<String, Set<String>>();
for (ExecutionReportNode node: tree.getNodes()) {
//handle the children
String parentFullname = node.getParent().getFullname();
if (!children.containsKey(parentFullname)) {
children.put(parentFullname, new TreeSet<String>());
}
children.get(parentFullname).add(node.getFullname());
if (node.getMillis() == 0 && node.getVal() == 0) {
continue ; //no data
}
{
ExecutionReportPoint point = new ExecutionReportPoint();
point.setTimestamp(end);
point.setMillis(node.getMillis());
point.setVal(node.getVal());
point.setFullname(node.getFullname());
points.add(point);
}
String instanceId = getJvmInstanceId();
if (instanceId != null) {
ExecutionReportPoint point = new ExecutionReportPoint();
point.setTimestamp(end);
point.setMillis(node.getMillis());
point.setVal(node.getVal());
point.setFullname(node.getFullname() + "." + instanceId);
points.add(point);
//need to add as a child.
if (!children.containsKey(node.getFullname())) {
children.put(node.getFullname(), new TreeSet<String>());
}
children.get(node.getFullname()).add(node.getFullname() + "." + instanceId);
}
}
children.put("execution_reports", new TreeSet<String>());
children.get("execution_reports").add(this.name);
for (String parent : children.keySet()) {
//need to get the date and timeframe of the parent.
for (TimeAmount amount: this.getConfig().getTimeAmounts()) {
this.getConfig().getSerializer().saveChildren(parent, children.get(parent), end, amount);
}
}
this.getConfig().getSerializer().save(this, points);
} catch (Exception x) {
log.error("CAught", x);
}
}
public String getName() {
return name;
}
/* (non-Javadoc)
* @see java.lang.Runnable#run()
*/
@Override
public void run() {
this.flush();
}
public void setConfig(ExecutionReportConfig config) {
this.config.set(config);
}
/**
* gets the current config object, or the default if a custom one is not set.
* @return
*/
public ExecutionReportConfig getConfig() {
ExecutionReportConfig config = this.config.get();
if (config == null)
return getDefaultConfig();
return config;
}
/**
* starts the timer. this is called automatically the first time the report is accessed.
*
* If the report is already scheduled this will cancel it and restart. You should call this method if you wish to update the
* flush millis in the config
*
*/
public void start() {
if (timer.get() == null) {
timer.compareAndSet(null, new Timer(true));
}
// this.cancel(); //cancel in case this has been previously scheduled
long millis = this.getConfig().getFlushMillis();
if (millis < 1)
return;
timer.get().schedule(this, millis, millis);
}
/* (non-Javadoc)
* @see com.trendrr.oss.executionreport.ExecutionReportIncrementor#getParent()
*/
@Override
public ExecutionReportIncrementor getParent() {
//should this always be null? I think so..
return null;
}
/* (non-Javadoc)
* @see com.trendrr.oss.executionreport.ExecutionReportIncrementor#getChild(java.lang.String)
*/
@Override
public ExecutionReportIncrementor getChild(String key) {
return new ExecutionSubReport(key, this);
}
}