package org.rrd4j.core;
import org.rrd4j.ConsolFun;
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 Rrd4j 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 these parts.
*
* @author Sasa Markovic
*/
public class Archive implements RrdUpdater {
private final RrdDb parentDb;
// definition
protected final RrdString consolFun;
protected final RrdDouble xff;
protected final RrdInt steps;
protected final RrdInt rows;
// state
private final Robin[] robins;
private final ArcState[] states;
Archive(RrdDb parentDb, ArcDef arcDef) throws IOException {
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
boolean shouldInitialize = arcDef != null;
if (shouldInitialize) {
consolFun.set(arcDef.getConsolFun().name());
xff.set(arcDef.getXff());
steps.set(arcDef.getSteps());
rows.set(arcDef.getRows());
}
int n = parentDb.getHeader().getDsCount();
int numRows = rows.get();
states = new ArcState[n];
int version = parentDb.getHeader().getVersion();
if (version == 1) {
robins = new RobinArray[n];
for (int i = 0; i < n; i++) {
states[i] = new ArcState(this, shouldInitialize);
robins[i] = new RobinArray(this, numRows, shouldInitialize);
}
} else {
RrdInt[] pointers = new RrdInt[n];
robins = new RobinMatrix[n];
for (int i = 0; i < n; i++) {
pointers[i] = new RrdInt(this);
//Purge old pointers content, avoid problems with file reuse
if(shouldInitialize) {
pointers[i].set(0);
}
states[i] = new ArcState(this, shouldInitialize);
}
RrdDoubleMatrix values = new RrdDoubleMatrix(this, numRows, n, shouldInitialize);
for (int i = 0; i < n; i++) {
robins[i] = new RobinMatrix(this, values, pointers[i], i);
}
}
}
// read from XML
Archive(RrdDb parentDb, DataImporter reader, int arcIndex) throws IOException {
this(parentDb, new ArcDef(
reader.getConsolFun(arcIndex), reader.getXff(arcIndex),
reader.getSteps(arcIndex), reader.getRows(arcIndex)));
int n = parentDb.getHeader().getDsCount();
for (int i = 0; i < n; 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 java.io.IOException Thrown in case of I/O error.
*/
public long getArcStep() throws IOException {
return parentDb.getHeader().getStep() * steps.get();
}
String dump() throws IOException {
StringBuilder sb = new StringBuilder("== ARCHIVE ==\n");
sb.append("RRA:")
.append(consolFun.get())
.append(":")
.append(xff.get())
.append(":")
.append(steps.get())
.append(":")
.append(rows.get())
.append("\n")
.append("interval [")
.append(getStartTime())
.append(", ")
.append(getEndTime())
.append("]" + "\n");
for (int i = 0; i < robins.length; i++) {
sb.append(states[i].dump());
sb.append(robins[i].dump());
}
return sb.toString();
}
RrdDb getParentDb() {
return parentDb;
}
void archive(int dsIndex, double value, long numUpdates) throws IOException {
Robin robin = robins[dsIndex];
ArcState state = states[dsIndex];
long step = parentDb.getHeader().getStep();
long lastUpdateTime = parentDb.getHeader().getLastUpdateTime();
long updateTime = Util.normalize(lastUpdateTime, step) + step;
long arcStep = getArcStep();
// finish current step
while (numUpdates > 0) {
accumulate(state, value);
numUpdates--;
if (updateTime % arcStep == 0) {
finalizeStep(state, robin);
break;
} else {
updateTime += step;
}
}
// update robin in bulk
int bulkUpdateCount = (int) Math.min(numUpdates / steps.get(), (long) rows.get());
robin.bulkStore(value, bulkUpdateCount);
// update remaining steps
long remainingUpdates = numUpdates % steps.get();
for (long i = 0; i < remainingUpdates; i++) {
accumulate(state, value);
}
}
private void accumulate(ArcState state, double value) throws IOException {
if (Double.isNaN(value)) {
state.setNanSteps(state.getNanSteps() + 1);
} else {
switch (ConsolFun.valueOf(consolFun.get())) {
case MIN:
state.setAccumValue(Util.min(state.getAccumValue(), value));
break;
case MAX:
state.setAccumValue(Util.max(state.getAccumValue(), value));
break;
case FIRST:
if (Double.isNaN(state.getAccumValue())) {
state.setAccumValue(value);
}
break;
case LAST:
state.setAccumValue(value);
break;
case AVERAGE:
case TOTAL:
state.setAccumValue(Util.sum(state.getAccumValue(), value));
break;
}
}
}
private void finalizeStep(ArcState state, Robin robin) throws IOException {
// should store
long arcSteps = steps.get();
double arcXff = xff.get();
long nanSteps = state.getNanSteps();
//double nanPct = (double) nanSteps / (double) arcSteps;
double accumValue = state.getAccumValue();
if (nanSteps <= arcXff * arcSteps && !Double.isNaN(accumValue)) {
if (getConsolFun() == ConsolFun.AVERAGE) {
accumValue /= (arcSteps - nanSteps);
}
robin.store(accumValue);
} else {
robin.store(Double.NaN);
}
state.setAccumValue(Double.NaN);
state.setNanSteps(0);
}
/**
* Returns archive consolidation function ("AVERAGE", "MIN", "MAX", "FIRST", "LAST" or "TOTAL").
*
* @return Archive consolidation function.
* @throws java.io.IOException Thrown in case of I/O error.
*/
public ConsolFun getConsolFun() throws IOException {
return ConsolFun.valueOf(consolFun.get());
}
/**
* Returns archive X-files factor.
*
* @return Archive X-files factor (between 0 and 1).
* @throws java.io.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 java.io.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 java.io.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 java.io.IOException Thrown in case of I/O error.
*/
public long getStartTime() throws IOException {
long endTime = getEndTime();
long arcStep = getArcStep();
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 java.io.IOException Thrown in case of I/O error.
*/
public long getEndTime() throws IOException {
long arcStep = getArcStep();
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(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(int dsIndex) {
return robins[dsIndex];
}
FetchData fetchData(FetchRequest request) throws IOException {
long arcStep = getArcStep();
long fetchStart = Util.normalize(request.getFetchStart(), arcStep);
long fetchEnd = Util.normalize(request.getFetchEnd(), arcStep);
if (fetchEnd < request.getFetchEnd()) {
fetchEnd += arcStep;
}
long startTime = getStartTime();
long endTime = getEndTime();
String[] dsToFetch = request.getFilter();
if (dsToFetch == null) {
dsToFetch = parentDb.getDsNames();
}
int dsCount = dsToFetch.length;
int ptsCount = (int) ((fetchEnd - fetchStart) / arcStep + 1);
long[] timestamps = new long[ptsCount];
double[][] values = new double[dsCount][ptsCount];
long matchStartTime = Math.max(fetchStart, startTime);
long matchEndTime = Math.min(fetchEnd, endTime);
double[][] robinValues = null;
if (matchStartTime <= matchEndTime) {
// preload robin values
int matchCount = (int) ((matchEndTime - matchStartTime) / arcStep + 1);
int matchStartIndex = (int) ((matchStartTime - startTime) / arcStep);
robinValues = new double[dsCount][];
for (int i = 0; i < dsCount; i++) {
int dsIndex = parentDb.getDsIndex(dsToFetch[i]);
robinValues[i] = robins[dsIndex].getValues(matchStartIndex, matchCount);
}
}
for (int ptIndex = 0; ptIndex < ptsCount; ptIndex++) {
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
int robinValueIndex = (int) ((time - matchStartTime) / arcStep);
assert robinValues != null;
value = robinValues[i][robinValueIndex];
}
values[i][ptIndex] = value;
}
}
FetchData fetchData = new FetchData(this, request);
fetchData.setTimestamps(timestamps);
fetchData.setValues(values);
return fetchData;
}
void appendXml(XmlWriter writer) throws IOException {
writer.startTag("rra");
writer.writeTag("cf", consolFun.get());
writer.writeComment(getArcStep() + " seconds");
writer.writeTag("pdp_per_row", steps.get());
writer.startTag("params");
writer.writeTag("xff", xff.get());
writer.closeTag(); // params
writer.startTag("cdp_prep");
for (ArcState state : states) {
state.appendXml(writer);
}
writer.closeTag(); // cdp_prep
writer.startTag("database");
long startTime = getStartTime();
for (int i = 0; i < rows.get(); i++) {
long time = startTime + i * getArcStep();
writer.writeComment(Util.getDate(time) + " / " + time);
writer.startTag("row");
for (Robin robin : robins) {
writer.writeTag("v", robin.getValue(i));
}
writer.closeTag(); // row
}
writer.closeTag(); // database
writer.closeTag(); // rra
}
/**
* {@inheritDoc}
*
* Copies object's internal state to another Archive object.
*/
public void copyStateTo(RrdUpdater other) throws IOException {
if (!(other instanceof Archive)) {
throw new IllegalArgumentException(
"Cannot copy Archive object to " + other.getClass().getName());
}
Archive arc = (Archive) other;
if (!arc.consolFun.get().equals(consolFun.get())) {
throw new IllegalArgumentException("Incompatible consolidation functions");
}
if (arc.steps.get() != steps.get()) {
throw new IllegalArgumentException("Incompatible number of steps");
}
int count = parentDb.getHeader().getDsCount();
for (int i = 0; i < count; i++) {
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 java.io.IOException Thrown in case of I/O error
*/
public void setXff(double xff) throws IOException {
if (xff < 0D || xff >= 1D) {
throw new IllegalArgumentException("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();
}
}