/******************************************************************************* * 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 RRD archive in a RRD with its internal state. * Normally, you don't need methods to manipulate archive objects directly * because JRobin framework does it automatically for you. * <p> * Each archive object consists of three parts: archive definition, archive state objects * (one state object for each datasource) and round robin archives (one round robin for * each datasource). API (read-only) is provided to access each of theese parts. * * @author <a href="mailto:saxon@jrobin.org">Sasa Markovic</a> */ public class Archive implements RrdUpdater, ConsolFuns { private RrdDb parentDb; // definition private RrdString consolFun; private RrdDouble xff; private RrdInt steps, rows; // state private Robin[] robins; private ArcState[] states; Archive(final RrdDb parentDb, final ArcDef arcDef) throws IOException { final boolean shouldInitialize = arcDef != null; this.parentDb = parentDb; consolFun = new RrdString(this, true); // constant, may be cached xff = new RrdDouble(this); steps = new RrdInt(this, true); // constant, may be cached rows = new RrdInt(this, true); // constant, may be cached if (shouldInitialize) { consolFun.set(arcDef.getConsolFun()); xff.set(arcDef.getXff()); steps.set(arcDef.getSteps()); rows.set(arcDef.getRows()); } final int dsCount = parentDb.getHeader().getDsCount(); states = new ArcState[dsCount]; robins = new Robin[dsCount]; final int numRows = rows.get(); for (int i = 0; i < dsCount; i++) { states[i] = new ArcState(this, shouldInitialize); robins[i] = new Robin(this, numRows, shouldInitialize); } } // read from XML Archive(final RrdDb parentDb, final DataImporter reader, final int arcIndex) throws IOException, RrdException,RrdException { this(parentDb, new ArcDef( reader.getConsolFun(arcIndex), reader.getXff(arcIndex), reader.getSteps(arcIndex), reader.getRows(arcIndex))); final int dsCount = parentDb.getHeader().getDsCount(); for (int i = 0; i < dsCount; i++) { // restore state states[i].setAccumValue(reader.getStateAccumValue(arcIndex, i)); states[i].setNanSteps(reader.getStateNanSteps(arcIndex, i)); // restore robins double[] values = reader.getValues(arcIndex, i); robins[i].update(values); } } /** * Returns archive time step in seconds. Archive step is equal to RRD step * multiplied with the number of archive steps. * * @return Archive time step in seconds * @throws IOException Thrown in case of I/O error. */ public long getArcStep() throws IOException { final long step = parentDb.getHeader().getStep(); return step * steps.get(); } String dump() throws IOException { final StringBuffer buffer = new StringBuffer("== ARCHIVE ==\n"); buffer.append("RRA:").append(consolFun.get()).append(":").append(xff.get()).append(":").append(steps.get()). append(":").append(rows.get()).append("\n"); buffer.append("interval [").append(getStartTime()).append(", ").append(getEndTime()).append("]" + "\n"); for (int i = 0; i < robins.length; i++) { buffer.append(states[i].dump()); buffer.append(robins[i].dump()); } return buffer.toString(); } RrdDb getParentDb() { return parentDb; } public void archive(final int dsIndex, final double value, final long numStepUpdates) throws IOException { final Robin robin = robins[dsIndex]; final ArcState state = states[dsIndex]; final long step = parentDb.getHeader().getStep(); final long lastUpdateTime = parentDb.getHeader().getLastUpdateTime(); long updateTime = Util.normalize(lastUpdateTime, step) + step; final long arcStep = getArcStep(); final String consolFunString = consolFun.get(); final int numSteps = steps.get(); final int numRows = rows.get(); final double xffValue = xff.get(); // finish current step long numUpdates = numStepUpdates; while (numUpdates > 0) { accumulate(state, value, consolFunString); numUpdates--; if (updateTime % arcStep == 0) { finalizeStep(state, robin, consolFunString, numSteps, xffValue); break; } else { updateTime += step; } } // update robin in bulk final int bulkUpdateCount = (int) Math.min(numUpdates / numSteps, (long) numRows); robin.bulkStore(value, bulkUpdateCount); // update remaining steps final long remainingUpdates = numUpdates % numSteps; for (long i = 0; i < remainingUpdates; i++) { accumulate(state, value, consolFunString); } } private void accumulate(final ArcState state, final double value, String consolFunString) throws IOException { if (Double.isNaN(value)) { state.setNanSteps(state.getNanSteps() + 1); } else { final double accumValue = state.getAccumValue(); if (consolFunString.equals(CF_MIN)) { final double minValue = Util.min(accumValue, value); if (minValue != accumValue) { state.setAccumValue(minValue); } } else if (consolFunString.equals(CF_MAX)) { final double maxValue = Util.max(accumValue, value); if (maxValue != accumValue) { state.setAccumValue(maxValue); } } else if (consolFunString.equals(CF_LAST)) { state.setAccumValue(value); } else if (consolFunString.equals(CF_AVERAGE)) { state.setAccumValue(Util.sum(accumValue, value)); } } } private void finalizeStep(final ArcState state, final Robin robin, final String consolFunString, final long numSteps, final double xffValue) throws IOException { final long nanSteps = state.getNanSteps(); //double nanPct = (double) nanSteps / (double) arcSteps; double accumValue = state.getAccumValue(); if (nanSteps <= xffValue * numSteps && !Double.isNaN(accumValue)) { if (consolFunString.equals(CF_AVERAGE)) { accumValue /= (numSteps - nanSteps); } robin.store(accumValue); } else { robin.store(Double.NaN); } state.setAccumValue(Double.NaN); state.setNanSteps(0); } /** * Returns archive consolidation function ("AVERAGE", "MIN", "MAX" or "LAST"). * * @return Archive consolidation function. * @throws IOException Thrown in case of I/O error. */ public String getConsolFun() throws IOException { return consolFun.get(); } /** * Returns archive X-files factor. * * @return Archive X-files factor (between 0 and 1). * @throws IOException Thrown in case of I/O error. */ public double getXff() throws IOException { return xff.get(); } /** * Returns the number of archive steps. * * @return Number of archive steps. * @throws IOException Thrown in case of I/O error. */ public int getSteps() throws IOException { return steps.get(); } /** * Returns the number of archive rows. * * @return Number of archive rows. * @throws IOException Thrown in case of I/O error. */ public int getRows() throws IOException { return rows.get(); } /** * Returns current starting timestamp. This value is not constant. * * @return Timestamp corresponding to the first archive row * @throws IOException Thrown in case of I/O error. */ public long getStartTime() throws IOException { final long endTime = getEndTime(); final long arcStep = getArcStep(); final long numRows = rows.get(); return endTime - (numRows - 1) * arcStep; } /** * Returns current ending timestamp. This value is not constant. * * @return Timestamp corresponding to the last archive row * @throws IOException Thrown in case of I/O error. */ public long getEndTime() throws IOException { final long arcStep = getArcStep(); final long lastUpdateTime = parentDb.getHeader().getLastUpdateTime(); return Util.normalize(lastUpdateTime, arcStep); } /** * Returns the underlying archive state object. Each datasource has its * corresponding ArcState object (archive states are managed independently * for each RRD datasource). * * @param dsIndex Datasource index * @return Underlying archive state object */ public ArcState getArcState(final int dsIndex) { return states[dsIndex]; } /** * Returns the underlying round robin archive. Robins are used to store actual * archive values on a per-datasource basis. * * @param dsIndex Index of the datasource in the RRD. * @return Underlying round robin archive for the given datasource. */ public Robin getRobin(final int dsIndex) { return robins[dsIndex]; } FetchData fetchData(final FetchRequest request) throws IOException, RrdException { final long arcStep = getArcStep(); final long fetchStart = Util.normalize(request.getFetchStart(), arcStep); long fetchEnd = Util.normalize(request.getFetchEnd(), arcStep); if (fetchEnd < request.getFetchEnd()) { fetchEnd += arcStep; } final long startTime = getStartTime(); final long endTime = getEndTime(); String[] dsToFetch = request.getFilter(); if (dsToFetch == null) { dsToFetch = parentDb.getDsNames(); } final int dsCount = dsToFetch.length; final int ptsCount = (int) ((fetchEnd - fetchStart) / arcStep + 1); final long[] timestamps = new long[ptsCount]; final double[][] values = new double[dsCount][ptsCount]; final long matchStartTime = Math.max(fetchStart, startTime); final long matchEndTime = Math.min(fetchEnd, endTime); double[][] robinValues = null; if (matchStartTime <= matchEndTime) { // preload robin values final int matchCount = (int) ((matchEndTime - matchStartTime) / arcStep + 1); final int matchStartIndex = (int) ((matchStartTime - startTime) / arcStep); robinValues = new double[dsCount][]; for (int i = 0; i < dsCount; i++) { final int dsIndex = parentDb.getDsIndex(dsToFetch[i]); robinValues[i] = robins[dsIndex].getValues(matchStartIndex, matchCount); } } for (int ptIndex = 0; ptIndex < ptsCount; ptIndex++) { final long time = fetchStart + ptIndex * arcStep; timestamps[ptIndex] = time; for (int i = 0; i < dsCount; i++) { double value = Double.NaN; if (time >= matchStartTime && time <= matchEndTime) { // inbound time final int robinValueIndex = (int) ((time - matchStartTime) / arcStep); assert robinValues != null; value = robinValues[i][robinValueIndex]; } values[i][ptIndex] = value; } } final FetchData fetchData = new FetchData(this, request); fetchData.setTimestamps(timestamps); fetchData.setValues(values); return fetchData; } void appendXml(final XmlWriter writer) throws IOException { writer.startTag("rra"); writer.writeTag("cf", consolFun.get()); writer.writeComment(getArcStep() + " seconds"); writer.writeTag("pdp_per_row", steps.get()); writer.writeTag("xff", xff.get()); writer.startTag("cdp_prep"); for (final ArcState state : states) { state.appendXml(writer); } writer.closeTag(); // cdp_prep writer.startTag("database"); final long startTime = getStartTime(); for (int i = 0; i < rows.get(); i++) { final long time = startTime + i * getArcStep(); writer.writeComment(Util.getDate(time) + " / " + time); writer.startTag("row"); for (final Robin robin : robins) { writer.writeTag("v", robin.getValue(i)); } writer.closeTag(); // row } writer.closeTag(); // database writer.closeTag(); // rra } /** * Copies object's internal state to another Archive object. * * @param other New Archive object to copy state to * @throws IOException Thrown in case of I/O error * @throws RrdException Thrown if supplied argument is not an Archive object */ public void copyStateTo(final RrdUpdater other) throws IOException, RrdException { if (!(other instanceof Archive)) { throw new RrdException("Cannot copy Archive object to " + other.getClass().getName()); } final Archive arc = (Archive) other; if (!arc.consolFun.get().equals(consolFun.get())) { throw new RrdException("Incompatible consolidation functions"); } if (arc.steps.get() != steps.get()) { throw new RrdException("Incompatible number of steps"); } final int count = parentDb.getHeader().getDsCount(); for (int i = 0; i < count; i++) { final int j = Util.getMatchingDatasourceIndex(parentDb, i, arc.parentDb); if (j >= 0) { states[i].copyStateTo(arc.states[j]); robins[i].copyStateTo(arc.robins[j]); } } } /** * Sets X-files factor to a new value. * * @param xff New X-files factor value. Must be >= 0 and < 1. * @throws RrdException Thrown if invalid value is supplied * @throws IOException Thrown in case of I/O error */ public void setXff(final double xff) throws RrdException, IOException { if (xff < 0D || xff >= 1D) { throw new RrdException("Invalid xff supplied (" + xff + "), must be >= 0 and < 1"); } this.xff.set(xff); } /** * 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 "Archive@" + Integer.toHexString(hashCode()) + "[parentDb=" + parentDb + ",consolFun=" + consolFun + ",xff=" + xff + ",steps=" + steps + ",rows=" + rows + ",robins=" + robins + ",states=" + states + "]"; } }