/******************************************************************************* * This file is part of OpenNMS(R). * * Copyright (C) 2006-2011 The OpenNMS Group, Inc. * OpenNMS(R) is Copyright (C) 1999-2011 The OpenNMS Group, Inc. * * OpenNMS(R) is a registered trademark of The OpenNMS Group, Inc. * * OpenNMS(R) is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * * OpenNMS(R) is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OpenNMS(R). If not, see: * http://www.gnu.org/licenses/ * * For more information contact: * OpenNMS(R) Licensing <license@opennms.org> * http://www.opennms.org/ * http://www.opennms.com/ *******************************************************************************/ package org.opennms.netmgt.rrd.jrobin; import java.awt.Color; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Properties; import org.jrobin.core.FetchData; import org.jrobin.core.RrdDb; import org.jrobin.core.RrdDef; import org.jrobin.core.RrdException; import org.jrobin.core.Sample; import org.jrobin.data.DataProcessor; import org.jrobin.data.Plottable; import org.jrobin.graph.RrdGraph; import org.jrobin.graph.RrdGraphDef; import org.opennms.core.utils.StringUtils; import org.opennms.core.utils.ThreadCategory; import org.opennms.netmgt.rrd.RrdDataSource; import org.opennms.netmgt.rrd.RrdGraphDetails; import org.opennms.netmgt.rrd.RrdStrategy; import org.opennms.netmgt.rrd.RrdUtils; /** * Provides a JRobin based implementation of RrdStrategy. It uses JRobin 1.4 in * FILE mode (NIO is too memory consuming for the large number of files that we * open) * * @author ranger * @version $Id: $ */ public class JRobinRrdStrategy implements RrdStrategy<RrdDef,RrdDb> { private static final String BACKEND_FACTORY_PROPERTY = "org.jrobin.core.RrdBackendFactory"; private static final String DEFAULT_BACKEND_FACTORY = "FILE"; /* * Ensure that we only initialize certain things *once* per * Java VM, not once per instantiation of this class. */ private static boolean s_initialized = false; private Properties m_configurationProperties; /** * An extremely simple Plottable for holding static datasources that * can't be represented with an SDEF -- currently used only for PERCENT * pseudo-VDEFs * * @author jeffg * */ class ConstantStaticDef extends Plottable { private double m_startTime = Double.NEGATIVE_INFINITY; private double m_endTime = Double.POSITIVE_INFINITY; private double m_value = Double.NaN; ConstantStaticDef(long startTime, long endTime, double value) { m_startTime = startTime; m_endTime = endTime; m_value = value; } public double getValue(long timestamp) { if (m_startTime <= timestamp && m_endTime >= timestamp) { return m_value; } else { return Double.NaN; } } } /** * <p>getConfigurationProperties</p> * * @return a {@link java.util.Properties} object. */ public Properties getConfigurationProperties() { return m_configurationProperties; } /** {@inheritDoc} */ public void setConfigurationProperties(final Properties configurationParameters) { m_configurationProperties = configurationParameters; if(!s_initialized) { String factory = null; if (m_configurationProperties == null) { factory = DEFAULT_BACKEND_FACTORY; } else { factory = (String)m_configurationProperties.get(BACKEND_FACTORY_PROPERTY); } try { RrdDb.setDefaultFactory(factory); s_initialized=true; } catch (RrdException e) { log().error("Could not set default JRobin RRD factory: " + e.getMessage(), e); } } } /** * Closes the JRobin RrdDb. * * @param rrdFile a {@link org.jrobin.core.RrdDb} object. * @throws java.lang.Exception if any. */ public void closeFile(final RrdDb rrdFile) throws Exception { rrdFile.close(); } /** {@inheritDoc} */ public RrdDef createDefinition(final String creator, final String directory, final String rrdName, int step, final List<RrdDataSource> dataSources, final List<String> rraList) throws Exception { File f = new File(directory); f.mkdirs(); String fileName = directory + File.separator + rrdName + RrdUtils.getExtension(); if (new File(fileName).exists()) { return null; } RrdDef def = new RrdDef(fileName); // def.setStartTime(System.currentTimeMillis()/1000L - 2592000L); def.setStartTime(1000); def.setStep(step); for (RrdDataSource dataSource : dataSources) { String dsMin = dataSource.getMin(); String dsMax = dataSource.getMax(); double min = (dsMin == null || "U".equals(dsMin) ? Double.NaN : Double.parseDouble(dsMin)); double max = (dsMax == null || "U".equals(dsMax) ? Double.NaN : Double.parseDouble(dsMax)); def.addDatasource(dataSource.getName(), dataSource.getType(), dataSource.getHeartBeat(), min, max); } for (String rra : rraList) { def.addArchive(rra); } return def; } /** * Creates the JRobin RrdDb from the def by opening the file and then * closing. TODO: Change the interface here to create the file and return it * opened. * * @param rrdDef a {@link org.jrobin.core.RrdDef} object. * @throws java.lang.Exception if any. */ public void createFile(final RrdDef rrdDef) throws Exception { if (rrdDef == null) { return; } RrdDb rrd = new RrdDb(rrdDef); rrd.close(); } /** * {@inheritDoc} * * Opens the JRobin RrdDb by name and returns it. */ public RrdDb openFile(final String fileName) throws Exception { RrdDb rrd = new RrdDb(fileName); return rrd; } /** * {@inheritDoc} * * Creates a sample from the JRobin RrdDb and passes in the data provided. */ public void updateFile(final RrdDb rrdFile, final String owner, final String data) throws Exception { Sample sample = rrdFile.createSample(); sample.setAndUpdate(data); } /** * Initialized the RrdDb to use the FILE factory because the NIO factory * uses too much memory for our implementation. * * @throws java.lang.Exception if any. */ public JRobinRrdStrategy() throws Exception { String home = System.getProperty("opennms.home"); System.setProperty("jrobin.fontdir", home + File.separator + "etc"); } /** * {@inheritDoc} * * Fetch the last value from the JRobin RrdDb file. */ public Double fetchLastValue(final String fileName, final String ds, final int interval) throws NumberFormatException, org.opennms.netmgt.rrd.RrdException { return fetchLastValue(fileName, ds, "AVERAGE", interval); } /** {@inheritDoc} */ public Double fetchLastValue(final String fileName, final String ds, final String consolidationFunction, final int interval) throws org.opennms.netmgt.rrd.RrdException { RrdDb rrd = null; try { long now = System.currentTimeMillis(); long collectTime = (now - (now % interval)) / 1000L; rrd = new RrdDb(fileName); FetchData data = rrd.createFetchRequest(consolidationFunction, collectTime, collectTime).fetchData(); if(log().isDebugEnabled()) { //The "toString" method of FetchData is quite computationally expensive; log().debug(data.toString()); } double[] vals = data.getValues(ds); if (vals.length > 0) { return new Double(vals[vals.length - 1]); } return null; } catch (IOException e) { throw new org.opennms.netmgt.rrd.RrdException("Exception occurred fetching data from " + fileName, e); } catch (RrdException e) { throw new org.opennms.netmgt.rrd.RrdException("Exception occurred fetching data from " + fileName, e); } finally { if (rrd != null) { try { rrd.close(); } catch (IOException e) { log().error("Failed to close rrd file: " + fileName, e); } } } } /** {@inheritDoc} */ public Double fetchLastValueInRange(final String fileName, final String ds, final int interval, final int range) throws NumberFormatException, org.opennms.netmgt.rrd.RrdException { RrdDb rrd = null; try { rrd = new RrdDb(fileName); long now = System.currentTimeMillis(); long latestUpdateTime = (now - (now % interval)) / 1000L; long earliestUpdateTime = ((now - (now % interval)) - range) / 1000L; if (log().isDebugEnabled()) { log().debug("fetchInRange: fetching data from " + earliestUpdateTime + " to " + latestUpdateTime); } FetchData data = rrd.createFetchRequest("AVERAGE", earliestUpdateTime, latestUpdateTime).fetchData(); double[] vals = data.getValues(ds); long[] times = data.getTimestamps(); // step backwards through the array of values until we get something that's a number for(int i = vals.length - 1; i >= 0; i--) { if ( Double.isNaN(vals[i]) ) { if (log().isDebugEnabled()) { log().debug("fetchInRange: Got a NaN value at interval: " + times[i] + " continuing back in time"); } } else { if (log().isDebugEnabled()) { log().debug("Got a non NaN value at interval: " + times[i] + " : " + vals[i] ); } return new Double(vals[i]); } } return null; } catch (IOException e) { throw new org.opennms.netmgt.rrd.RrdException("Exception occurred fetching data from " + fileName, e); } catch (RrdException e) { throw new org.opennms.netmgt.rrd.RrdException("Exception occurred fetching data from " + fileName, e); } finally { if (rrd != null) { try { rrd.close(); } catch (IOException e) { log().error("Failed to close rrd file: " + fileName, e); } } } } private Color getColor(final String colorValue) { int colorVal = Integer.parseInt(colorValue, 16); return new Color(colorVal); } // For compatibility with RRDtool defs, the colour value for // LINE and AREA is optional. If it's missing, the line is rendered // invisibly. private Color getColorOrInvisible(final String[] array, final int index) { if (array.length > index) { return getColor(array[index]); } return new Color(1.0f, 1.0f, 1.0f, 0.0f); } /** {@inheritDoc} */ public InputStream createGraph(final String command, final File workDir) throws IOException, org.opennms.netmgt.rrd.RrdException { return createGraphReturnDetails(command, workDir).getInputStream(); } /** * {@inheritDoc} * * This constructs a graphDef by parsing the rrdtool style command and using * the values to create the JRobin graphDef. It does not understand the 'AT * style' time arguments however. Also there may be some rrdtool parameters * that it does not understand. These will be ignored. The graphDef will be * used to construct an RrdGraph and a PNG image will be created. An input * stream returning the bytes of the PNG image is returned. */ public RrdGraphDetails createGraphReturnDetails(final String command, final File workDir) throws IOException, org.opennms.netmgt.rrd.RrdException { try { String[] commandArray = tokenize(command, " \t", false); RrdGraphDef graphDef = createGraphDef(workDir, commandArray); graphDef.setSignature("OpenNMS/JRobin"); RrdGraph graph = new RrdGraph(graphDef); /* * We use a custom RrdGraphDetails object here instead of the * DefaultRrdGraphDetails because we won't have an InputStream * available if no graphing commands were used, e.g.: if we only * use PRINT or if the user goofs up a graph definition. * * We want to throw an RrdException if the caller calls * RrdGraphDetails.getInputStream and no graphing commands were * used. If they just call RrdGraphDetails.getPrintLines, though, * we don't want to throw an exception. */ return new JRobinRrdGraphDetails(graph, command); } catch (Throwable e) { log().error("JRobin: exception occurred creating graph: " + e.getMessage(), e); throw new org.opennms.netmgt.rrd.RrdException("An exception occurred creating the graph: " + e.getMessage(), e); } } /** {@inheritDoc} */ public void promoteEnqueuedFiles(Collection<String> rrdFiles) { // no need to do anything since this strategy doesn't queue } /** * <p>createGraphDef</p> * * @param workDir a {@link java.io.File} object. * @param commandArray an array of {@link java.lang.String} objects. * @return a {@link org.jrobin.graph.RrdGraphDef} object. * @throws org.jrobin.core.RrdException if any. */ protected RrdGraphDef createGraphDef(final File workDir, final String[] inputArray) throws RrdException { RrdGraphDef graphDef = new RrdGraphDef(); graphDef.setImageFormat("PNG"); long start = 0; long end = 0; int height = 100; int width = 400; double lowerLimit = Double.NaN; double upperLimit = Double.NaN; boolean rigid = false; Map<String,List<String>> defs = new LinkedHashMap<String,List<String>>(); // Map<String,List<String>> cdefs = new HashMap<String,List<String>>(); final String[] commandArray; if (inputArray[0].contains("rrdtool") && inputArray[1].equals("graph") && inputArray[2].equals("-")) { commandArray = Arrays.copyOfRange(inputArray, 3, inputArray.length); } else { commandArray = inputArray; } log().debug("command array = " + Arrays.toString(commandArray)); for (int i = 0; i < commandArray.length; i++) { String arg = commandArray[i]; if (arg.startsWith("--start=")) { start = Long.parseLong(arg.substring("--start=".length())); log().debug("JRobin start time: " + start); } else if (arg.equals("--start")) { if (i + 1 < commandArray.length) { start = Long.parseLong(commandArray[++i]); log().debug("JRobin start time: " + start); } else { throw new IllegalArgumentException("--start must be followed by a start time"); } } else if (arg.startsWith("--end=")) { end = Long.parseLong(arg.substring("--end=".length())); log().debug("JRobin end time: " + end); } else if (arg.equals("--end")) { if (i + 1 < commandArray.length) { end = Long.parseLong(commandArray[++i]); log().debug("JRobin end time: " + end); } else { throw new IllegalArgumentException("--end must be followed by an end time"); } } else if (arg.startsWith("--title=")) { String[] title = tokenize(arg, "=", true); graphDef.setTitle(title[1]); } else if (arg.equals("--title")) { if (i + 1 < commandArray.length) { graphDef.setTitle(commandArray[++i]); } else { throw new IllegalArgumentException("--title must be followed by a title"); } } else if (arg.startsWith("--color=")) { String[] color = tokenize(arg, "=", true); parseGraphColor(graphDef, color[1]); } else if (arg.equals("--color") || arg.equals("-c")) { if (i + 1 < commandArray.length) { parseGraphColor(graphDef, commandArray[++i]); } else { throw new IllegalArgumentException("--color must be followed by a color"); } } else if (arg.startsWith("--vertical-label=")) { String[] label = tokenize(arg, "=", true); graphDef.setVerticalLabel(label[1]); } else if (arg.equals("--vertical-label")) { if (i + 1 < commandArray.length) { graphDef.setVerticalLabel(commandArray[++i]); } else { throw new IllegalArgumentException("--vertical-label must be followed by a label"); } } else if (arg.startsWith("--height=")) { String[] argParm = tokenize(arg, "=", true); height = Integer.parseInt(argParm[1]); log().debug("JRobin height: "+height); } else if (arg.equals("--height")) { if (i + 1 < commandArray.length) { height = Integer.parseInt(commandArray[++i]); log().debug("JRobin height: "+height); } else { throw new IllegalArgumentException("--height must be followed by a number"); } } else if (arg.startsWith("--width=")) { String[] argParm = tokenize(arg, "=", true); width = Integer.parseInt(argParm[1]); log().debug("JRobin width: "+width); } else if (arg.equals("--width")) { if (i + 1 < commandArray.length) { width = Integer.parseInt(commandArray[++i]); log().debug("JRobin width: "+width); } else { throw new IllegalArgumentException("--width must be followed by a number"); } } else if (arg.startsWith("--units-exponent=")) { String[] argParm = tokenize(arg, "=", true); int exponent = Integer.parseInt(argParm[1]); log().debug("JRobin units exponent: "+exponent); graphDef.setUnitsExponent(exponent); } else if (arg.equals("--units-exponent")) { if (i + 1 < commandArray.length) { int exponent = Integer.parseInt(commandArray[++i]); log().debug("JRobin units exponent: "+exponent); graphDef.setUnitsExponent(exponent); } else { throw new IllegalArgumentException("--units-exponent must be followed by a number"); } } else if (arg.startsWith("--lower-limit=")) { String[] argParm = tokenize(arg, "=", true); lowerLimit = Double.parseDouble(argParm[1]); log().debug("JRobin lower limit: "+lowerLimit); } else if (arg.equals("--lower-limit")) { if (i + 1 < commandArray.length) { lowerLimit = Double.parseDouble(commandArray[++i]); log().debug("JRobin lower limit: "+lowerLimit); } else { throw new IllegalArgumentException("--lower-limit must be followed by a number"); } } else if (arg.startsWith("--upper-limit=")) { String[] argParm = tokenize(arg, "=", true); upperLimit = Double.parseDouble(argParm[1]); log().debug("JRobin upp limit: "+upperLimit); } else if (arg.equals("--upper-limit")) { if (i + 1 < commandArray.length) { upperLimit = Double.parseDouble(commandArray[++i]); log().debug("JRobin upper limit: "+upperLimit); } else { throw new IllegalArgumentException("--upper-limit must be followed by a number"); } } else if (arg.startsWith("--base=")) { String[] argParm = tokenize(arg, "=", true); graphDef.setBase(Double.parseDouble(argParm[1])); } else if (arg.equals("--base")) { if (i + 1 < commandArray.length) { graphDef.setBase(Double.parseDouble(commandArray[++i])); } else { throw new IllegalArgumentException("--base must be followed by a number"); } } else if (arg.startsWith("--font=")) { String[] argParm = tokenize(arg, "=", true); processRrdFontArgument(graphDef, argParm[1]); } else if (arg.equals("--font")) { if (i + 1 < commandArray.length) { processRrdFontArgument(graphDef, commandArray[++i]); } else { throw new IllegalArgumentException("--font must be followed by an argument"); } } else if (arg.startsWith("--imgformat=")) { String[] argParm = tokenize(arg, "=", true); graphDef.setImageFormat(argParm[1]); } else if (arg.equals("--imgformat")) { if (i + 1 < commandArray.length) { graphDef.setImageFormat(commandArray[++i]); } else { throw new IllegalArgumentException("--imgformat must be followed by an argument"); } } else if (arg.equals("--rigid")) { rigid = true; } else if (arg.startsWith("DEF:")) { String definition = arg.substring("DEF:".length()); String[] def = splitDef(definition); String[] ds = def[0].split("="); // log().debug("ds = " + Arrays.toString(ds)); final String replaced = ds[1].replaceAll("\\\\(.)", "$1"); // log().debug("replaced = " + replaced); final File dsFile; File rawPathFile = new File(replaced); if (rawPathFile.isAbsolute()) { dsFile = rawPathFile; } else { dsFile = new File(workDir, replaced); } // log().debug("dsFile = " + dsFile + ", ds[1] = " + ds[1]); final String absolutePath = (File.separatorChar == '\\')? dsFile.getAbsolutePath().replace("\\", "\\\\") : dsFile.getAbsolutePath(); // log().debug("absolutePath = " + absolutePath); graphDef.datasource(ds[0], absolutePath, def[1], def[2]); List<String> defBits = new ArrayList<String>(); defBits.add(absolutePath); defBits.add(def[1]); defBits.add(def[2]); defs.put(ds[0], defBits); } else if (arg.startsWith("CDEF:")) { String definition = arg.substring("CDEF:".length()); String[] cdef = tokenize(definition, "=", true); graphDef.datasource(cdef[0], cdef[1]); List<String> cdefBits = new ArrayList<String>(); cdefBits.add(cdef[1]); defs.put(cdef[0], cdefBits); } else if (arg.startsWith("VDEF:")) { String definition = arg.substring("VDEF:".length()); String[] vdef = tokenize(definition, "=", true); String[] expressionTokens = tokenize(vdef[1], ",", false); addVdefDs(graphDef, vdef[0], expressionTokens, start, end, defs); } else if (arg.startsWith("LINE1:")) { String definition = arg.substring("LINE1:".length()); String[] line1 = tokenize(definition, ":", true); String[] color = tokenize(line1[0], "#", true); graphDef.line(color[0], getColorOrInvisible(color, 1), (line1.length > 1 ? line1[1] : "")); } else if (arg.startsWith("LINE2:")) { String definition = arg.substring("LINE2:".length()); String[] line2 = tokenize(definition, ":", true); String[] color = tokenize(line2[0], "#", true); graphDef.line(color[0], getColorOrInvisible(color, 1), (line2.length > 1 ? line2[1] : ""), 2); } else if (arg.startsWith("LINE3:")) { String definition = arg.substring("LINE3:".length()); String[] line3 = tokenize(definition, ":", true); String[] color = tokenize(line3[0], "#", true); graphDef.line(color[0], getColorOrInvisible(color, 1), (line3.length > 1 ? line3[1] : ""), 3); } else if (arg.startsWith("GPRINT:")) { String definition = arg.substring("GPRINT:".length()); String gprint[] = tokenize(definition, ":", true); String format = gprint[2]; //format = format.replaceAll("%(\\d*\\.\\d*)lf", "@$1"); //format = format.replaceAll("%s", "@s"); //format = format.replaceAll("%%", "%"); //log.debug("gprint: oldformat = " + gprint[2] + " newformat = " + format); format = format.replaceAll("\\n", "\\\\l"); graphDef.gprint(gprint[0], gprint[1], format); } else if (arg.startsWith("PRINT:")) { String definition = arg.substring("PRINT:".length()); String print[] = tokenize(definition, ":", true); String format = print[2]; //format = format.replaceAll("%(\\d*\\.\\d*)lf", "@$1"); //format = format.replaceAll("%s", "@s"); //format = format.replaceAll("%%", "%"); //log.debug("gprint: oldformat = " + print[2] + " newformat = " + format); format = format.replaceAll("\\n", "\\\\l"); graphDef.print(print[0], print[1], format); } else if (arg.startsWith("COMMENT:")) { String comments[] = tokenize(arg, ":", true); String format = comments[1].replaceAll("\\n", "\\\\l"); graphDef.comment(format); } else if (arg.startsWith("AREA:")) { String definition = arg.substring("AREA:".length()); String area[] = tokenize(definition, ":", true); String[] color = tokenize(area[0], "#", true); if (area.length > 1) { graphDef.area(color[0], getColorOrInvisible(color, 1), area[1]); } else { graphDef.area(color[0], getColorOrInvisible(color, 1)); } } else if (arg.startsWith("STACK:")) { String definition = arg.substring("STACK:".length()); String stack[] = tokenize(definition, ":", true); String[] color = tokenize(stack[0], "#", true); graphDef.stack(color[0], getColor(color[1]), (stack.length > 1 ? stack[1] : "")); } else if (arg.endsWith("/rrdtool") || arg.equals("graph") || arg.equals("-")) { // ignore, this is just a leftover from the rrdtool-specific options } else { log().warn("JRobin: Unrecognized graph argument: " + arg); } } graphDef.setTimeSpan(start, end); graphDef.setMinValue(lowerLimit); graphDef.setMaxValue(upperLimit); graphDef.setRigid(rigid); graphDef.setHeight(height); graphDef.setWidth(width); // graphDef.setSmallFont(new Font("Monospaced", Font.PLAIN, 10)); // graphDef.setLargeFont(new Font("Monospaced", Font.PLAIN, 12)); log().debug("JRobin Finished tokenizing checking: start time: " + start + "; end time: " + end); log().debug("large font = " + graphDef.getLargeFont() + ", small font = " + graphDef.getSmallFont()); return graphDef; } private String[] splitDef(final String definition) { // log().debug("splitDef(" + definition + ")"); final String[] def; if (File.separatorChar == '\\') { // log().debug("windows"); // Windows, make sure the beginning isn't eg: C:\\foo\\bar if (definition.matches("[^=]*=[a-zA-Z]:.*")) { final String[] tempDef = definition.split("(?<!\\\\):"); def = new String[tempDef.length - 1]; def[0] = tempDef[0] + ':' + tempDef[1]; if (tempDef.length > 2) { for (int i = 2; i < tempDef.length; i++) { def[i-1] = tempDef[i]; } } } else { // log().debug("no match"); def = definition.split("(?<!\\\\):"); } } else { def = definition.split("(?<!\\\\):"); } // log().debug("returning: " + Arrays.toString(def)); return def; } private void processRrdFontArgument(RrdGraphDef graphDef, String argParm) { /* String[] argValue = tokenize(argParm, ":", true); if (argValue[0].equals("DEFAULT")) { int newPointSize = Integer.parseInt(argValue[1]); graphDef.setSmallFont(graphDef.getSmallFont().deriveFont(newPointSize)); } else if (argValue[0].equals("TITLE")) { int newPointSize = Integer.parseInt(argValue[1]); graphDef.setLargeFont(graphDef.getLargeFont().deriveFont(newPointSize)); } else { try { Font font = Font.createFont(Font.TRUETYPE_FONT, new File(argValue[0])); } catch (Throwable e) { // oh well, fall back to existing font stuff log().warn("unable to create font from font argument " + argParm, e); } } */ } private String[] tokenize(final String line, final String delimiters, final boolean processQuotes) { String passthroughTokens = "lcrjgsJ"; /* see org.jrobin.graph.RrdGraphConstants.MARKERS */ return tokenizeWithQuotingAndEscapes(line, delimiters, processQuotes, passthroughTokens); } /** * @param colorArg Should have the form COLORTAG#RRGGBB * @see http://www.jrobin.org/support/man/rrdgraph.html */ private void parseGraphColor(final RrdGraphDef graphDef, final String colorArg) throws IllegalArgumentException { // Parse for format COLORTAG#RRGGBB String[] colorArgParts = tokenize(colorArg, "#", false); if (colorArgParts.length != 2) { throw new IllegalArgumentException("--color must be followed by value with format COLORTAG#RRGGBB"); } String colorTag = colorArgParts[0].toUpperCase(); String colorHex = colorArgParts[1].toUpperCase(); // validate hex color input is actually an RGB hex color value if (colorHex.length() != 6) { throw new IllegalArgumentException("--color must be followed by value with format COLORTAG#RRGGBB"); } // this might throw NumberFormatException, but whoever wrote // createGraph didn't seem to care, so I guess I don't care either. // It'll get wrapped in an RrdException anyway. Color color = getColor(colorHex); // These are the documented RRD color tags try { if (colorTag.equals("BACK")) { graphDef.setColor("BACK", color); } else if (colorTag.equals("CANVAS")) { graphDef.setColor("CANVAS", color); } else if (colorTag.equals("SHADEA")) { graphDef.setColor("SHADEA", color); } else if (colorTag.equals("SHADEB")) { graphDef.setColor("SHADEB", color); } else if (colorTag.equals("GRID")) { graphDef.setColor("GRID", color); } else if (colorTag.equals("MGRID")) { graphDef.setColor("MGRID", color); } else if (colorTag.equals("FONT")) { graphDef.setColor("FONT", color); } else if (colorTag.equals("FRAME")) { graphDef.setColor("FRAME", color); } else if (colorTag.equals("ARROW")) { graphDef.setColor("ARROW", color); } else { throw new org.jrobin.core.RrdException("Unknown color tag " + colorTag); } } catch (Throwable e) { log().error("JRobin: exception occurred creating graph: " + e, e); } } /** * This implementation does not track any stats. * * @return a {@link java.lang.String} object. */ public String getStats() { return ""; } /* * These offsets work for ranger@ with Safari and JRobin 1.5.8. */ /** * <p>getGraphLeftOffset</p> * * @return a int. */ public int getGraphLeftOffset() { return 74; } /** * <p>getGraphRightOffset</p> * * @return a int. */ public int getGraphRightOffset() { return -15; } /** * <p>getGraphTopOffsetWithText</p> * * @return a int. */ public int getGraphTopOffsetWithText() { return -61; } /** * <p>getDefaultFileExtension</p> * * @return a {@link java.lang.String} object. */ public String getDefaultFileExtension() { return ".jrb"; } private final ThreadCategory log() { return ThreadCategory.getInstance(getClass()); } /** * <p>tokenizeWithQuotingAndEscapes</p> * * @param line a {@link java.lang.String} object. * @param delims a {@link java.lang.String} object. * @param processQuoted a boolean. * @return an array of {@link java.lang.String} objects. */ public static String[] tokenizeWithQuotingAndEscapes(String line, String delims, boolean processQuoted) { return tokenizeWithQuotingAndEscapes(line, delims, processQuoted, ""); } /** * Tokenize a {@link String} into an array of {@link String}s. * * @param line * the string to tokenize * @param delims * a string containing zero or more characters to treat as a delimiter * @param processQuoted * whether or not to process escaped values inside quotes * @param tokens * custom escaped tokens to pass through, escaped. For example, if tokens contains "lsg", then \l, \s, and \g * will be passed through unescaped. * @return an array of {@link java.lang.String} objects. */ public static String[] tokenizeWithQuotingAndEscapes(final String line, final String delims, final boolean processQuoted, final String tokens) { ThreadCategory log = ThreadCategory.getInstance(StringUtils.class); List<String> tokenList = new LinkedList<String>(); StringBuffer currToken = new StringBuffer(); boolean quoting = false; boolean escaping = false; boolean debugTokens = Boolean.getBoolean("org.opennms.netmgt.rrd.debugTokens"); if (!log.isDebugEnabled()) debugTokens = false; if (debugTokens) log.debug("tokenize: line=" + line + " delims=" + delims); for (int i = 0; i < line.length(); i++) { char ch = line.charAt(i); if (debugTokens) log.debug("tokenize: checking char: " + ch); if (escaping) { if (ch == 'n') { currToken.append(escapeIfNotPathSepInDEF(ch, '\n', currToken)); } else if (ch == 'r') { currToken.append(escapeIfNotPathSepInDEF(ch, '\r', currToken)); } else if (ch == 't') { currToken.append(escapeIfNotPathSepInDEF(ch, '\t', currToken)); } else { if (tokens.indexOf(ch) >= 0) { currToken.append('\\').append(ch); } else if (currToken.toString().startsWith("DEF:")) { currToken.append('\\').append(ch); } else { // silently pass through the character *without* the \ in front of it currToken.append(ch); } } escaping = false; if (debugTokens) log.debug("tokenize: escaped. appended to " + currToken); } else if (ch == '\\') { if (debugTokens) log.debug("tokenize: found a backslash... escaping currToken = " + currToken); if (quoting && !processQuoted) currToken.append(ch); else escaping = true; } else if (ch == '\"') { if (!processQuoted) currToken.append(ch); if (quoting) { if (debugTokens) log.debug("tokenize: found a quote ending quotation currToken = " + currToken); quoting = false; } else { if (debugTokens) log.debug("tokenize: found a quote beginning quotation currToken =" + currToken); quoting = true; } } else if (!quoting && delims.indexOf(ch) >= 0) { if (debugTokens) log.debug("tokenize: found a token: " + ch + " ending token [" + currToken + "] and starting a new one"); tokenList.add(currToken.toString()); currToken = new StringBuffer(); } else { if (debugTokens) log.debug("tokenize: appending " + ch + " to token: " + currToken); currToken.append(ch); } } if (escaping || quoting) { if (debugTokens) log.debug("tokenize: ended string but escaping = " + escaping + " and quoting = " + quoting); throw new IllegalArgumentException("unable to tokenize string " + line + " with token chars " + delims); } if (debugTokens) log.debug("tokenize: reached end of string. completing token " + currToken); tokenList.add(currToken.toString()); return (String[]) tokenList.toArray(new String[tokenList.size()]); } /** * <p>escapeIfNotPathSepInDEF</p> * * @param encountered a char. * @param escaped a char. * @param currToken a {@link java.lang.StringBuffer} object. * @return an array of char. */ public static char[] escapeIfNotPathSepInDEF(final char encountered, final char escaped, final StringBuffer currToken) { if ( ('\\' != File.separatorChar) || (! currToken.toString().startsWith("DEF:")) ) { return new char[] { escaped }; } else { return new char[] { '\\', encountered }; } } protected void addVdefDs(RrdGraphDef graphDef, String sourceName, String[] rhs, double start, double end, Map<String,List<String>> defs) throws RrdException { if (rhs.length == 2) { graphDef.datasource(sourceName, rhs[0], rhs[1]); } else if (rhs.length == 3 && "PERCENT".equals(rhs[2])) { // Is there a better way to do this than with a separate DataProcessor? double pctRank = Double.valueOf(rhs[1]); DataProcessor dataProcessor = new DataProcessor((int)start, (int)end); for (String dsName : defs.keySet()) { List<String> thisDef = defs.get(dsName); if (thisDef.size() == 3) { dataProcessor.addDatasource(dsName, thisDef.get(0), thisDef.get(1), thisDef.get(2)); } else if (thisDef.size() == 1) { dataProcessor.addDatasource(dsName, thisDef.get(0)); } } try { dataProcessor.processData(); } catch (IOException e) { throw new RrdException("Caught IOException: " + e.getMessage()); } double result = dataProcessor.getPercentile(rhs[0], pctRank); ConstantStaticDef csDef = new ConstantStaticDef((long)start, (long)end, result); graphDef.datasource(sourceName, csDef); } } }