/******************************************************************************* * Copyright (c) 2001-2005 Sasa Markovic and Ciaran Treanor. * Copyright (c) 2011 The OpenNMS Group, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *******************************************************************************/ package org.jrobin.core; import java.io.IOException; import java.util.Date; import java.util.StringTokenizer; /** * Class to represent data source values for the given timestamp. Objects of this * class are never created directly (no public constructor is provided). To learn more how * to update RRDs, see RRDTool's * <a href="../../../../man/rrdupdate.html" target="man">rrdupdate man page</a>. * <p> * To update a RRD with JRobin use the following procedure: * <p> * <ol> * <li>Obtain empty Sample object by calling method {@link RrdDb#createSample(long) * createSample()} on respective {@link RrdDb RrdDb} object. * <li>Adjust Sample timestamp if necessary (see {@link #setTime(long) setTime()} method). * <li>Supply data source values (see {@link #setValue(String, double) setValue()}). * <li>Call Sample's {@link #update() update()} method. * </ol> * <p> * Newly created Sample object contains all data source values set to 'unknown'. * You should specifify only 'known' data source values. However, if you want to specify * 'unknown' values too, use <code>Double.NaN</code>. * * @author <a href="mailto:saxon@jrobin.org">Sasa Markovic</a> */ public class Sample { private RrdDb parentDb; private long time; private String[] dsNames; private double[] values; Sample(final RrdDb parentDb, final long time) throws IOException { this.parentDb = parentDb; this.time = time; this.dsNames = parentDb.getDsNames(); values = new double[dsNames.length]; clearCurrentValues(); } private Sample clearCurrentValues() { for (int i = 0; i < values.length; i++) { values[i] = Double.NaN; } return this; } /** * Sets single data source value in the sample. * * @param dsName Data source name. * @param value Data source value. * @return This <code>Sample</code> object * @throws RrdException Thrown if invalid data source name is supplied. */ public Sample setValue(final String dsName, final double value) throws RrdException { for (int i = 0; i < values.length; i++) { if (dsNames[i].equals(dsName)) { values[i] = value; return this; } } throw new RrdException("Datasource " + dsName + " not found"); } /** * Sets single datasource value using data source index. Data sources are indexed by * the order specified during RRD creation (zero-based). * * @param i Data source index * @param value Data source values * @return This <code>Sample</code> object * @throws RrdException Thrown if data source index is invalid. */ public Sample setValue(final int i, final double value) throws RrdException { if (i < values.length) { values[i] = value; return this; } else { throw new RrdException("Sample datasource index " + i + " out of bounds"); } } /** * Sets some (possibly all) data source values in bulk. Data source values are * assigned in the order of their definition inside the RRD. * * @param values Data source values. * @return This <code>Sample</code> object * @throws RrdException Thrown if the number of supplied values is zero or greater * than the number of data sources defined in the RRD. */ public Sample setValues(final double[] values) throws RrdException { if (values.length <= this.values.length) { System.arraycopy(values, 0, this.values, 0, values.length); return this; } else { throw new RrdException("Invalid number of values specified (found " + values.length + ", only " + dsNames.length + " allowed)"); } } /** * Returns all current data source values in the sample. * * @return Data source values. */ public double[] getValues() { return values; } /** * Returns sample timestamp (in seconds, without milliseconds). * * @return Sample timestamp. */ public long getTime() { return time; } /** * Sets sample timestamp. Timestamp should be defined in seconds (without milliseconds). * * @param time New sample timestamp. * @return This <code>Sample</code> object */ public Sample setTime(final long time) { this.time = time; return this; } /** * Returns an array of all data source names. If you try to set value for the data source * name not in this array, an exception is thrown. * * @return Acceptable data source names. */ public String[] getDsNames() { return dsNames; } /** * Sets sample timestamp and data source values in a fashion similar to RRDTool. * Argument string should be composed in the following way: * <code>timestamp:value1:value2:...:valueN</code>. * <p> * You don't have to supply all datasource values. Unspecified values will be treated * as unknowns. To specify unknown value in the argument string, use letter 'U'. * * @param timeAndValues String made by concatenating sample timestamp with corresponding * data source values delmited with colons. For example:<p> * <pre> * 1005234132:12.2:35.6:U:24.5 * NOW:12.2:35.6:U:24.5 * </pre> * 'N' stands for the current timestamp (can be replaced with 'NOW')<p> * Method will throw an exception if timestamp is invalid (cannot be parsed as Long, and is not 'N' * or 'NOW'). Datasource value which cannot be parsed as 'double' will be silently set to NaN.<p> * @return This <code>Sample</code> object * @throws RrdException Thrown if too many datasource values are supplied */ public Sample set(final String timeAndValues) throws RrdException { final StringTokenizer tokenizer = new StringTokenizer(timeAndValues, ":", false); final int tokenCount = tokenizer.countTokens(); if (tokenCount > values.length + 1) { throw new RrdException("Invalid number of values specified (found " + values.length + ", " + dsNames.length + " allowed)"); } final String timeToken = tokenizer.nextToken(); try { time = Long.parseLong(timeToken); } catch (final NumberFormatException nfe) { if (timeToken.equalsIgnoreCase("N") || timeToken.equalsIgnoreCase("NOW")) { time = Util.getTime(); } else { throw new RrdException("Invalid sample timestamp: " + timeToken); } } for (int i = 0; tokenizer.hasMoreTokens(); i++) { try { values[i] = Double.parseDouble(tokenizer.nextToken()); } catch (final NumberFormatException nfe) { // NOP, value is already set to NaN } } return this; } /** * Stores sample in the corresponding RRD. If the update operation succeedes, * all datasource values in the sample will be set to Double.NaN (unknown) values. * * @throws IOException Thrown in case of I/O error. * @throws RrdException Thrown in case of JRobin related error. */ public void update() throws IOException, RrdException { parentDb.store(this); clearCurrentValues(); } /** * <p>Creates sample with the timestamp and data source values supplied * in the argument string and stores sample in the corresponding RRD. * This method is just a shortcut for:</p> * <pre> * set(timeAndValues); * update(); * </pre> * * @param timeAndValues String made by concatenating sample timestamp with corresponding * data source values delmited with colons. For example:<br> * <code>1005234132:12.2:35.6:U:24.5</code><br> * <code>NOW:12.2:35.6:U:24.5</code> * @throws IOException Thrown in case of I/O error. * @throws RrdException Thrown in case of JRobin related error. */ public void setAndUpdate(final String timeAndValues) throws IOException, RrdException { set(timeAndValues); update(); } /** * Dumps sample content using the syntax of RRDTool's update command. * * @return Sample dump. */ public String dump() { final StringBuffer buffer = new StringBuffer("update \""); buffer.append(parentDb.getRrdBackend().getPath()).append("\" ").append(time); for (final double value : values) { buffer.append(":"); buffer.append(Util.formatDouble(value, "U", false)); } return buffer.toString(); } String getRrdToolCommand() { return dump(); } public String toString() { return getClass().getSimpleName() + "@" + "[parentDb=" + parentDb + ",time=" + new Date(time * 1000L) + ",dsNames=[" + printList(dsNames) + "],values=[" + printList(values) + "]]"; } private String printList(final Object[] dsNames) { if (dsNames == null) return "null"; final StringBuffer sb = new StringBuffer(); for (int i = 0; i < dsNames.length; i++) { if (i == dsNames.length - 1) { sb.append(dsNames[i]); } else { sb.append(dsNames[i]).append(", "); } } return sb.toString(); } private String printList(final double[] values) { if (values == null) return "null"; final StringBuffer sb = new StringBuffer(); for (int i = 0; i < values.length; i++) { if (i == values.length - 1) { sb.append(values[i]); } else { sb.append(values[i]).append(", "); } } return sb.toString(); } }