/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package org.dcache.commons.stats.rrd;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.security.AccessControlException;
import java.util.HashMap;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Random;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import org.dcache.commons.stats.RequestCounter;
import org.dcache.commons.stats.RequestCounters;
/**
* a utility class that creates and manages rrd databases (using RRDRequestCounter)
* and plots graphs for RequestCounters collection of request counters
* @param <T> a type of key inherited from RequestCounters
* @author timur
*/
public class RrdRequestCounters<T> {
private static final Logger logger = LoggerFactory.getLogger(RrdRequestCounters.class);
private final Map<T,RRDRequestCounter> rrdcounters =
new HashMap<>();
private final RRDRequestCounter totalRequestCounter;
private final RequestCounters<T> requestCounters;
private static final Timer rrdTimer = new Timer("RrdRequestCounters",true);
private final String rrdDir;
private TimerTask updateRrd ;
private TimerTask updateRrdGraphs;
private long updatePeriodSecs = 60L; //one minute by default
private long graphPeriodSecs = 5*60L; //five minutes by default
/**
* creates new instance of RrdRequestCounters
* you will need to call startRrdUpdates to begin collecting data
* and startRrdGraphPlots to being automatically updating images
* intead it is possible to plot on demand by calling
* plotGraphs
* @param requestCounters from which counters will be logged and plotted
* @param rrdDir place where to put rrd database files and images
*/
public RrdRequestCounters(RequestCounters<T> requestCounters,
String rrdDir) throws IOException {
this(requestCounters,rrdDir,60L,5*60L);
}
/**
* creates new instance of RrdRequestCounters
* you will need to call startRrdUpdates to begin collecting data
* and startRrdGraphPlots to being automatically updating images
* intead it is possible to plot on demand by calling
* plotGraphs
* @param requestCounters from which counters will be logged and plotted
* @param rrdDir place where to put rrd database files and images
* @param updatePeriodSecs specifies how often to read counters
* @param graphPeriodSecs specifies how often to plot images
*/
public RrdRequestCounters(RequestCounters<T> requestCounters,
String rrdDir,long updatePeriodSecs,
long graphPeriodSecs) throws IOException {
logger.debug("RrdRequestCounters("+requestCounters+", "+rrdDir);
this.requestCounters = requestCounters;
this.rrdDir = rrdDir;
File dir = new File(rrdDir);
if(!dir.exists()) {
dir.mkdir() ;
}
if(!dir.exists() || !dir.canRead() ||
!dir.canWrite() || !dir.canExecute()) {
throw new AccessControlException("directory "+
dir + " does not exists or is not accessable");
}
this.updatePeriodSecs = updatePeriodSecs;
this.graphPeriodSecs = graphPeriodSecs;
totalRequestCounter =
new RRDRequestCounter(rrdDir,
requestCounters.getTotalRequestCounter(),
updatePeriodSecs);
updateIndex();
}
/**
* starts data collection
*/
public synchronized void startRrdUpdates() {
if(updateRrd != null) {
throw new IllegalStateException("RRD Updates are started");
}
updateRrd = new TimerTask() {
@Override
public void run() {
logger.debug("RrdRequestCounters updateRrd running updateRrds()");
try {
updateRrds() ;
logger.debug("RrdRequestCounters updateRrd updateRrds() is done");
} catch (Throwable t) {
logger.error("updateRrds",t);
}
}
};
rrdTimer.schedule(updateRrd, updatePeriodSecs*1000L,updatePeriodSecs*1000L);
}
private void updateRrds() throws IOException {
boolean countersAdded = false;
logger.debug("updateRrds() for "+requestCounters);
synchronized (requestCounters) {
for(T key:requestCounters.keySet()) {
logger.debug("updatePrds(): key is "+key);
if(!rrdcounters.containsKey(key)) {
RequestCounter requestCounter = requestCounters.getCounter(key);
logger.debug("updatePrds(): creating RRDRequestCounter for "+requestCounter);
RRDRequestCounter rrdRequestCounter =
new RRDRequestCounter(rrdDir,requestCounter,updatePeriodSecs);
rrdcounters.put(key, rrdRequestCounter);
countersAdded = true;
}
}
}
for(RRDRequestCounter rrdRequestCounter:rrdcounters.values() ) {
logger.debug("updateRrds(): calling rrdRequestCounter.update()");
rrdRequestCounter.update();
}
logger.debug("updateRrds(): calling totalRequestCounter.update()");
totalRequestCounter.update();
if(countersAdded) {
updateIndex();
}
}
/**
*
* @return keyset
*/
public Set<T> keySet() {
return rrdcounters.keySet();
}
/**
*
* @param counterKey a key corresponding to a counter
* @return a RequestCounter associated with counterKey
* @throws NoSuchElementException if counter for counterKey is not defined
*/
public RRDRequestCounter getRrdCounter(T key) {
synchronized(this) {
if(rrdcounters.containsKey(key)) {
return rrdcounters.get(key);
} else {
throw new NoSuchElementException("counter with name "+
key+" is not defined in rrdcounters" );
}
}
}
/**
* plots graphs
* @throws IOException
*/
public void plotGraphs() throws IOException {
for(RRDRequestCounter counter : rrdcounters.values()) {
logger.debug("plotGraphs(): calling counter.graph()");
counter.graph();
logger.debug("plotGraphs(): counter.graph() returned");
}
logger.debug("plotGraphs(): calling totalRequestCounter.graph()");
totalRequestCounter.graph();
logger.debug("plotGraphs(): totalRequestCounter.graph() returned");
}
/**
* starts automatic graphs updates
*/
public synchronized void startRrdGraphPlots() {
if(updateRrdGraphs != null) {
throw new IllegalStateException("RRD Graph Updates are started");
}
updateRrdGraphs = new TimerTask() {
@Override
public void run() {
try {
logger.debug("RrdRequestCounters updateRrd running plotGraphs()");
plotGraphs() ;
} catch(Throwable t) {
logger.error("plotGraphs", t);
}
}
};
rrdTimer.schedule(updateRrdGraphs, updatePeriodSecs*1000L,graphPeriodSecs*1000L);
}
/**
* stops data collection
*/
public synchronized void stopRrdUpdates() {
if(updateRrd != null) {
updateRrd.cancel();
updateRrd = null;
}
}
/**
* stop automatic graphs updates
*/
public synchronized void stopRrdGraphPlots() {
if(updateRrdGraphs != null) {
updateRrdGraphs.cancel();
updateRrdGraphs = null;
}
}
private void updateIndex() throws IOException {
File index = new File(rrdDir,"index.html");
String indexHtml = getIndexHtml();
FileWriter fw = new FileWriter(index);
fw.write(indexHtml);
fw.close();
}
private String getIndexHtml() {
StringBuilder sb = new StringBuilder();
sb.append("<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n");
sb.append("<html>\n");
sb.append("<head>\n");
sb.append(" <meta content=\"text/html; charset=ISO-8859-1\"\n");
sb.append(" http-equiv=\"content-type\">\n");
sb.append(" <title>Request Rate Graphs Index for ");
sb.append(requestCounters.getName());
sb.append(" </title>\n");
sb.append("</head>\n");
sb.append("<body>\n");
sb.append(" <h1>Request Rate Graphs Index for ");
sb.append(requestCounters.getName());
sb.append(" </h1>\n");
sb.append(" <table> \n");
sb.append(" <tr> <td> \n");
for(RRDRequestCounter counter : rrdcounters.values()) {
sb.append(" <tr> <td> \n");
String nextHtml = counter.getRrdGraphicsHtmlFileName();
sb.append("<a href=\"");
sb.append(nextHtml);
sb.append("\">");
sb.append(nextHtml);
sb.append("</a>\n");
sb.append(" </td> </tr> \n");
}
sb.append(" <tr> <td> \n");
String nextHtml = totalRequestCounter.getRrdGraphicsHtmlFileName();
sb.append("<a href=\"");
sb.append(nextHtml);
sb.append("\">");
sb.append(nextHtml);
sb.append("</a>\n");
sb.append(" </td> </tr> \n");
sb.append("</body>");
sb.append("</html>");
return sb.toString();
}
/**
* runs tests of the rrdRequestCouters
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception{
String dir = args[0];
RequestCounters<String> rcs = new RequestCounters<>(dir);
Random r = new Random();
rcs.incrementRequests("counter1",r.nextInt(100));
rcs.incrementRequests("counter2",r.nextInt(100));
RrdRequestCounters<String> rrdrcs =
new RrdRequestCounters<>(rcs,dir,10,20);
rrdrcs.startRrdUpdates();
rrdrcs.startRrdGraphPlots();
while(true) {
Thread.sleep(10000);
rcs.incrementRequests("counter1",r.nextInt(300));
rcs.incrementFailed("counter1",r.nextInt(200));
rcs.incrementRequests("counter2",r.nextInt(300));
rcs.incrementFailed("counter2",r.nextInt(200));
System.out.println("updated counters:\n"+rcs);
}
}
public RRDRequestCounter getTotalRequestCounter() {
return totalRequestCounter;
}
}