/* * 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.Random; import java.util.Timer; import java.util.TimerTask; import org.dcache.commons.stats.RequestExecutionTimeGauge; import org.dcache.commons.stats.RequestExecutionTimeGauges; /** * a utility class that creates and manages rrd databases (using RRDRequestExecutionTimeGauge) * and plots graphs for RequestExecutionTimeGauges collection of request counters * @param <T> a type of key inherited from RequestExecutionTimeGauges * @author timur */ public class RrdRequestExecutionTimeGauges<T> { private static final Logger logger = LoggerFactory.getLogger(RrdRequestExecutionTimeGauges.class); private final Map<T,RRDRequestExecutionTimeGauge> rrdgauges = new HashMap<>(); private final RequestExecutionTimeGauges<T> gauges; private static final Timer rrdTimer = new Timer("RrdRequestGauges",true); private final File 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 RrdRequestGauges * 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 RrdRequestExecutionTimeGauges(RequestExecutionTimeGauges<T> requestGauges, File rrdDir) throws IOException { this(requestGauges,rrdDir,60L,5*60L); } /** * creates new instance of RrdRequestGauges * 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 RrdRequestExecutionTimeGauges(RequestExecutionTimeGauges<T> requestGauges, File rrdDir,long updatePeriodSecs, long graphPeriodSecs) throws IOException { logger.debug("RrdRequestGauges("+requestGauges+", "+rrdDir); this.gauges = requestGauges; this.rrdDir = rrdDir; if(!rrdDir.exists()) { rrdDir.mkdir() ; } if(!rrdDir.exists() || !rrdDir.canRead() || !rrdDir.canWrite() || !rrdDir.canExecute()) { throw new AccessControlException("directory "+ rrdDir + " does not exists or is not accessable"); } this.updatePeriodSecs = updatePeriodSecs; this.graphPeriodSecs = graphPeriodSecs; 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("RrdRequestGauges updateRrd running updateRrds()"); try { updateRrds() ; logger.debug("RrdRequestGauges updateRrd updateRrds() is done"); } catch (IOException ioe) { logger.error("updateRrds io exception : ",ioe); } } }; rrdTimer.schedule(updateRrd, updatePeriodSecs*1000L,updatePeriodSecs*1000L); } private void updateRrds() throws IOException { boolean gaugesAdded = false; logger.debug("updateRrds() for "+gauges); synchronized (gauges) { for(T key:gauges.keySet()) { logger.debug("updatePrds(): key is "+key); if(!rrdgauges.containsKey(key)) { RequestExecutionTimeGauge requestGauges = gauges.getGauge(key); logger.debug("updatePrds(): creating RRDRequestExecutionTimeGauge for "+requestGauges); RRDRequestExecutionTimeGauge rrdRequestGauges = new RRDRequestExecutionTimeGauge(rrdDir,requestGauges,updatePeriodSecs); rrdgauges.put(key, rrdRequestGauges); gaugesAdded = true; } } } for(RRDRequestExecutionTimeGauge gauge : rrdgauges.values() ) { logger.debug("updateRrds(): calling gauge.update()"); gauge.update(); } logger.debug("updateRrds(): calling totalRequestCounter.update()"); if(gaugesAdded) { updateIndex(); } } /** * plots graphs * @throws IOException */ public void plotGraphs() throws IOException { for(RRDRequestExecutionTimeGauge gauge : rrdgauges.values()) { gauge.graph(); } } /** * 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("RrdRequestGauges updateRrd running plotGraphs()"); plotGraphs() ; } catch(IOException ioe) { logger.error("plotGraphs io exception : ", ioe); } } }; 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(); try (FileWriter fw = new FileWriter(index)) { fw.write(indexHtml); } } 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 Execution Time Graphs Index for "); sb.append(gauges.getName()); sb.append(" </title>\n"); sb.append("</head>\n"); sb.append("<body>\n"); sb.append(" <h1>Request Execution Time Graphs Index for "); sb.append(gauges.getName()); sb.append(" </h1>\n"); sb.append(" <table> \n"); sb.append(" <tr> <td> \n"); for(RRDRequestExecutionTimeGauge gauge : rrdgauges.values()) { sb.append(" <tr> <td> \n"); String nextHtml = gauge.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 dirname = args[0]; RequestExecutionTimeGauges<String> retg = new RequestExecutionTimeGauges<>(dirname); Random r = new Random(); retg.update("gauge1",r.nextInt(100)); retg.update("gauge2",r.nextInt(100)); File dir = new File(dirname); RrdRequestExecutionTimeGauges<String> rrdrcs = new RrdRequestExecutionTimeGauges<>(retg,dir,10,20); rrdrcs.startRrdUpdates(); rrdrcs.startRrdGraphPlots(); while(true) { Thread.sleep(10000); retg.update("gauge1",r.nextInt(300)); retg.update("gauge2",r.nextInt(300)); System.out.println("updated gauges:\n"+retg); } } }