/******************************************************************************* * 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; /** * Class to represent single datasource within RRD. Each datasource object holds the * following information: datasource definition (once set, never changed) and * datasource state variables (changed whenever RRD gets updated). * <p> * Normally, you don't need to manipluate Datasource objects directly, it's up to * JRobin framework to do it for you. * * @author <a href="mailto:saxon@jrobin.org">Sasa Markovic</a> */ public class Datasource implements RrdUpdater, DsTypes { private static final double MAX_32_BIT = Math.pow(2, 32); private static final double MAX_64_BIT = Math.pow(2, 64); private RrdDb parentDb; // definition private RrdString dsName, dsType; private RrdLong heartbeat; private RrdDouble minValue, maxValue; // cache private String m_primitiveDsName = null; private String m_primitiveDsType = null; // state variables private RrdDouble lastValue; private RrdLong nanSeconds; private RrdDouble accumValue; Datasource(final RrdDb parentDb, final DsDef dsDef) throws IOException { boolean shouldInitialize = dsDef != null; this.parentDb = parentDb; dsName = new RrdString(this); dsType = new RrdString(this); heartbeat = new RrdLong(this); minValue = new RrdDouble(this); maxValue = new RrdDouble(this); lastValue = new RrdDouble(this); accumValue = new RrdDouble(this); nanSeconds = new RrdLong(this); if (shouldInitialize) { dsName.set(dsDef.getDsName()); m_primitiveDsName = null; dsType.set(dsDef.getDsType()); m_primitiveDsType = null; heartbeat.set(dsDef.getHeartbeat()); minValue.set(dsDef.getMinValue()); maxValue.set(dsDef.getMaxValue()); lastValue.set(Double.NaN); accumValue.set(0.0); final Header header = parentDb.getHeader(); nanSeconds.set(header.getLastUpdateTime() % header.getStep()); } } Datasource(final RrdDb parentDb, final DataImporter reader, final int dsIndex) throws IOException, RrdException { this(parentDb, null); dsName.set(reader.getDsName(dsIndex)); m_primitiveDsName = null; dsType.set(reader.getDsType(dsIndex)); m_primitiveDsType = null; heartbeat.set(reader.getHeartbeat(dsIndex)); minValue.set(reader.getMinValue(dsIndex)); maxValue.set(reader.getMaxValue(dsIndex)); lastValue.set(reader.getLastValue(dsIndex)); accumValue.set(reader.getAccumValue(dsIndex)); nanSeconds.set(reader.getNanSeconds(dsIndex)); } String dump() throws IOException { return "== DATASOURCE ==\n" + "DS:" + dsName.get() + ":" + dsType.get() + ":" + heartbeat.get() + ":" + minValue.get() + ":" + maxValue.get() + "\nlastValue:" + lastValue.get() + " nanSeconds:" + nanSeconds.get() + " accumValue:" + accumValue.get() + "\n"; } /** * Returns datasource name. * * @return Datasource name * @throws IOException Thrown in case of I/O error */ public String getDsName() throws IOException { if (m_primitiveDsName == null) { m_primitiveDsName = dsName.get(); } return m_primitiveDsName; } /** * Returns datasource type (GAUGE, COUNTER, DERIVE, ABSOLUTE). * * @return Datasource type. * @throws IOException Thrown in case of I/O error */ public String getDsType() throws IOException { if (m_primitiveDsType == null) { m_primitiveDsType = dsType.get(); } return m_primitiveDsType; } /** * Returns datasource heartbeat * * @return Datasource heartbeat * @throws IOException Thrown in case of I/O error */ public long getHeartbeat() throws IOException { return heartbeat.get(); } /** * Returns mimimal allowed value for this datasource. * * @return Minimal value allowed. * @throws IOException Thrown in case of I/O error */ public double getMinValue() throws IOException { return minValue.get(); } /** * Returns maximal allowed value for this datasource. * * @return Maximal value allowed. * @throws IOException Thrown in case of I/O error */ public double getMaxValue() throws IOException { return maxValue.get(); } /** * Returns last known value of the datasource. * * @return Last datasource value. * @throws IOException Thrown in case of I/O error */ public double getLastValue() throws IOException { return lastValue.get(); } /** * Returns value this datasource accumulated so far. * * @return Accumulated datasource value. * @throws IOException Thrown in case of I/O error */ public double getAccumValue() throws IOException { return accumValue.get(); } /** * Returns the number of accumulated NaN seconds. * * @return Accumulated NaN seconds. * @throws IOException Thrown in case of I/O error */ public long getNanSeconds() throws IOException { return nanSeconds.get(); } void process(final long newTime, final double newValue) throws IOException, RrdException { final Header header = parentDb.getHeader(); final long step = header.getStep(); final long oldTime = header.getLastUpdateTime(); final long startTime = Util.normalize(oldTime, step); final long endTime = startTime + step; final double oldValue = lastValue.get(); final double updateValue = calculateUpdateValue(oldTime, oldValue, newTime, newValue); if (newTime < endTime) { accumulate(oldTime, newTime, updateValue); } else { // should store something final long boundaryTime = Util.normalize(newTime, step); accumulate(oldTime, boundaryTime, updateValue); final double value = calculateTotal(startTime, boundaryTime); // how many updates? final long numSteps = (boundaryTime - endTime) / step + 1L; // ACTION! parentDb.archive(this, value, numSteps); // cleanup nanSeconds.set(0); accumValue.set(0.0); accumulate(boundaryTime, newTime, updateValue); } } private double calculateUpdateValue(final long oldTime, final double oldValue, final long newTime, final double newValue) throws IOException { double updateValue = Double.NaN; if (newTime - oldTime <= heartbeat.get()) { final String type = dsType.get(); if (type.equals(DT_GAUGE)) { updateValue = newValue; } else if (type.equals(DT_ABSOLUTE)) { if (!Double.isNaN(newValue)) { updateValue = newValue / (newTime - oldTime); } } else if (type.equals(DT_DERIVE)) { if (!Double.isNaN(newValue) && !Double.isNaN(oldValue)) { updateValue = (newValue - oldValue) / (newTime - oldTime); } } else if (type.equals(DT_COUNTER)) { if (!Double.isNaN(newValue) && !Double.isNaN(oldValue)) { double diff = newValue - oldValue; if (diff < 0) { diff += MAX_32_BIT; } if (diff < 0) { diff += MAX_64_BIT - MAX_32_BIT; } if (diff >= 0) { updateValue = diff / (newTime - oldTime); } } } if (!Double.isNaN(updateValue)) { final double minVal = minValue.get(); final double maxVal = maxValue.get(); if (!Double.isNaN(minVal) && updateValue < minVal) { updateValue = Double.NaN; } if (!Double.isNaN(maxVal) && updateValue > maxVal) { updateValue = Double.NaN; } } } lastValue.set(newValue); return updateValue; } private void accumulate(final long oldTime, final long newTime, final double updateValue) throws IOException { if (Double.isNaN(updateValue)) { nanSeconds.set(nanSeconds.get() + (newTime - oldTime)); } else { accumValue.set(accumValue.get() + updateValue * (newTime - oldTime)); } } private double calculateTotal(final long startTime, final long boundaryTime) throws IOException { double totalValue = Double.NaN; final long validSeconds = boundaryTime - startTime - nanSeconds.get(); if (nanSeconds.get() <= heartbeat.get() && validSeconds > 0) { totalValue = accumValue.get() / validSeconds; } // IMPORTANT: // if datasource name ends with "!", we'll send zeros instead of NaNs // this might be handy from time to time if (Double.isNaN(totalValue) && dsName.get().endsWith(DsDef.FORCE_ZEROS_FOR_NANS_SUFFIX)) { totalValue = 0D; } return totalValue; } void appendXml(final XmlWriter writer) throws IOException { writer.startTag("ds"); writer.writeTag("name", dsName.get()); writer.writeTag("type", dsType.get()); writer.writeTag("minimal_heartbeat", heartbeat.get()); writer.writeTag("min", minValue.get()); writer.writeTag("max", maxValue.get()); writer.writeComment("PDP Status"); writer.writeTag("last_ds", lastValue.get(), "UNKN"); writer.writeTag("value", accumValue.get()); writer.writeTag("unknown_sec", nanSeconds.get()); writer.closeTag(); // ds } /** * Copies object's internal state to another Datasource object. * * @param other New Datasource object to copy state to * @throws IOException Thrown in case of I/O error * @throws RrdException Thrown if supplied argument is not a Datasource object */ public void copyStateTo(final RrdUpdater other) throws IOException, RrdException { if (!(other instanceof Datasource)) { throw new RrdException("Cannot copy Datasource object to " + other.getClass().getName()); } final Datasource datasource = (Datasource) other; if (!datasource.dsName.get().equals(dsName.get())) { throw new RrdException("Incomaptible datasource names"); } if (!datasource.dsType.get().equals(dsType.get())) { throw new RrdException("Incomaptible datasource types"); } datasource.lastValue.set(lastValue.get()); datasource.nanSeconds.set(nanSeconds.get()); datasource.accumValue.set(accumValue.get()); } /** * Returns index of this Datasource object in the RRD. * * @return Datasource index in the RRD. * @throws IOException Thrown in case of I/O error */ public int getDsIndex() throws IOException { try { return parentDb.getDsIndex(dsName.get()); } catch (final RrdException e) { return -1; } } /** * Sets datasource heartbeat to a new value. * * @param heartbeat New heartbeat value * @throws IOException Thrown in case of I/O error * @throws RrdException Thrown if invalid (non-positive) heartbeat value is specified. */ public void setHeartbeat(final long heartbeat) throws RrdException, IOException { if (heartbeat < 1L) { throw new RrdException("Invalid heartbeat specified: " + heartbeat); } this.heartbeat.set(heartbeat); } /** * Sets datasource name to a new value * * @param newDsName New datasource name * @throws RrdException Thrown if invalid data source name is specified (name too long, or * name already defined in the RRD * @throws IOException Thrown in case of I/O error */ public void setDsName(final String newDsName) throws RrdException, IOException { if (newDsName.length() > RrdString.STRING_LENGTH) { throw new RrdException("Invalid datasource name specified: " + newDsName); } if (parentDb.containsDs(newDsName)) { throw new RrdException("Datasource already defined in this RRD: " + newDsName); } dsName.set(newDsName); m_primitiveDsName = null; } public void setDsType(final String newDsType) throws RrdException, IOException { if (!DsDef.isValidDsType(newDsType)) { throw new RrdException("Invalid datasource type: " + newDsType); } // set datasource type this.dsType.set(newDsType); m_primitiveDsType = null; // reset datasource status lastValue.set(Double.NaN); accumValue.set(0.0); // reset archive status final int dsIndex = parentDb.getDsIndex(dsName.get()); final Archive[] archives = parentDb.getArchives(); for (final Archive archive : archives) { archive.getArcState(dsIndex).setAccumValue(Double.NaN); } } /** * Sets minimum allowed value for this datasource. If <code>filterArchivedValues</code> * argment is set to true, all archived values less then <code>minValue</code> will * be fixed to NaN. * * @param minValue New minimal value. Specify <code>Double.NaN</code> if no minimal * value should be set * @param filterArchivedValues true, if archived datasource values should be fixed; * false, otherwise. * @throws IOException Thrown in case of I/O error * @throws RrdException Thrown if invalid minValue was supplied (not less then maxValue) */ public void setMinValue(final double minValue, final boolean filterArchivedValues) throws IOException, RrdException { final double maxValue = this.maxValue.get(); if (!Double.isNaN(minValue) && !Double.isNaN(maxValue) && minValue >= maxValue) { throw new RrdException("Invalid min/max values: " + minValue + "/" + maxValue); } this.minValue.set(minValue); if (!Double.isNaN(minValue) && filterArchivedValues) { final int dsIndex = getDsIndex(); final Archive[] archives = parentDb.getArchives(); for (final Archive archive : archives) { archive.getRobin(dsIndex).filterValues(minValue, Double.NaN); } } } /** * Sets maximum allowed value for this datasource. If <code>filterArchivedValues</code> * argment is set to true, all archived values greater then <code>maxValue</code> will * be fixed to NaN. * * @param maxValue New maximal value. Specify <code>Double.NaN</code> if no max * value should be set. * @param filterArchivedValues true, if archived datasource values should be fixed; * false, otherwise. * @throws IOException Thrown in case of I/O error * @throws RrdException Thrown if invalid maxValue was supplied (not greater then minValue) */ public void setMaxValue(final double maxValue, final boolean filterArchivedValues) throws IOException, RrdException { final double minValue = this.minValue.get(); if (!Double.isNaN(minValue) && !Double.isNaN(maxValue) && minValue >= maxValue) { throw new RrdException("Invalid min/max values: " + minValue + "/" + maxValue); } this.maxValue.set(maxValue); if (!Double.isNaN(maxValue) && filterArchivedValues) { final int dsIndex = getDsIndex(); final Archive[] archives = parentDb.getArchives(); for (final Archive archive : archives) { archive.getRobin(dsIndex).filterValues(Double.NaN, maxValue); } } } /** * Sets min/max values allowed for this datasource. If <code>filterArchivedValues</code> * argment is set to true, all archived values less then <code>minValue</code> or * greater then <code>maxValue</code> will be fixed to NaN. * * @param minValue New minimal value. Specify <code>Double.NaN</code> if no min * value should be set. * @param maxValue New maximal value. Specify <code>Double.NaN</code> if no max * value should be set. * @param filterArchivedValues true, if archived datasource values should be fixed; * false, otherwise. * @throws IOException Thrown in case of I/O error * @throws RrdException Thrown if invalid min/max values were supplied */ public void setMinMaxValue(final double minValue, final double maxValue, final boolean filterArchivedValues) throws IOException, RrdException { if (!Double.isNaN(minValue) && !Double.isNaN(maxValue) && minValue >= maxValue) { throw new RrdException("Invalid min/max values: " + minValue + "/" + maxValue); } this.minValue.set(minValue); this.maxValue.set(maxValue); if (!(Double.isNaN(minValue) && Double.isNaN(maxValue)) && filterArchivedValues) { final int dsIndex = getDsIndex(); final Archive[] archives = parentDb.getArchives(); for (final Archive archive : archives) { archive.getRobin(dsIndex).filterValues(minValue, maxValue); } } } /** * Returns the underlying storage (backend) object which actually performs all * I/O operations. * * @return I/O backend object */ public RrdBackend getRrdBackend() { return parentDb.getRrdBackend(); } /** * Required to implement RrdUpdater interface. You should never call this method directly. * * @return Allocator object */ public RrdAllocator getRrdAllocator() { return parentDb.getRrdAllocator(); } public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()) + "[parentDb=" + parentDb + ",dsName=" + dsName + ",dsType=" + dsType + ",heartbeat=" + heartbeat + ",minValue=" + minValue + ",maxValue=" + maxValue + "]"; } }