/*******************************************************************************
* 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.jrrd;
import java.io.IOException;
import java.io.PrintStream;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.Iterator;
import org.jrobin.core.RrdException;
/**
* Instances of this class model an archive section of an RRD file.
*
* @author <a href="mailto:ciaran@codeloop.com">Ciaran Treanor</a>
* @version $Revision$
*/
public class Archive {
RRDatabase db;
long offset;
long dataOffset;
long size;
ConsolidationFunctionType type;
int rowCount;
int pdpCount;
double xff;
ArrayList<CDPStatusBlock> cdpStatusBlocks;
int currentRow;
private double[][] values;
Archive(RRDatabase db) throws IOException,RrdException {
this.db = db;
RRDFile file = db.rrdFile;
offset = file.getFilePointer();
type =
ConsolidationFunctionType.get(file.readString(Constants.CF_NAM_SIZE));
rowCount = file.readInt();
pdpCount = file.readInt();
file.align();
xff = file.readDouble();
// Skip rest of rra_def_t.par[]
file.align();
file.skipBytes(72);
size = file.getFilePointer() - offset;
}
/**
* Returns the type of function used to calculate the consolidated data point.
*
* @return the type of function used to calculate the consolidated data point.
*/
public ConsolidationFunctionType getType() {
return type;
}
void loadCDPStatusBlocks(RRDFile file, int numBlocks) throws IOException, RrdException {
cdpStatusBlocks = new ArrayList<CDPStatusBlock>();
for (int i = 0; i < numBlocks; i++) {
cdpStatusBlocks.add(new CDPStatusBlock(file));
}
}
/**
* Returns the <code>CDPStatusBlock</code> at the specified position in this archive.
*
* @param index index of <code>CDPStatusBlock</code> to return.
* @return the <code>CDPStatusBlock</code> at the specified position in this archive.
*/
public CDPStatusBlock getCDPStatusBlock(int index) {
return cdpStatusBlocks.get(index);
}
/**
* Returns an iterator over the CDP status blocks in this archive in proper sequence.
*
* @return an iterator over the CDP status blocks in this archive in proper sequence.
* @see CDPStatusBlock
*/
public Iterator<CDPStatusBlock> getCDPStatusBlocks() {
return cdpStatusBlocks.iterator();
}
void loadCurrentRow(RRDFile file) throws IOException,RrdException {
currentRow = file.readInt();
}
void loadData(RRDFile file, int dsCount) throws IOException {
dataOffset = file.getFilePointer();
// Skip over the data to position ourselves at the start of the next archive
file.skipBytes(8 * rowCount * dsCount);
}
DataChunk loadData(DataChunk chunk) throws IOException,RrdException {
Calendar end = Calendar.getInstance();
Calendar start = (Calendar) end.clone();
start.add(Calendar.DATE, -1);
loadData(chunk, start.getTime().getTime() / 1000,
end.getTime().getTime() / 1000);
return chunk;
}
void loadData(DataChunk chunk, long startTime, long endTime)
throws IOException,RrdException {
long pointer;
if (chunk.start < 0) {
pointer = currentRow + 1;
}
else {
pointer = currentRow + chunk.start + 1;
}
db.rrdFile.ras.seek(dataOffset + (pointer * 8));
//cat.debug("Archive Base: " + dataOffset + " Archive Pointer: " + pointer);
//cat.debug("Start Offset: " + chunk.start + " End Offset: "
// + (rowCount - chunk.end));
double[][] data = chunk.data;
/*
* This is also terrible - cleanup - CT
*/
int row = 0;
for (int i = chunk.start; i < rowCount - chunk.end; i++, row++) {
if (i < 0) { // no valid data yet
for (int ii = 0; ii < chunk.dsCount; ii++) {
data[row][ii] = Double.NaN;
}
}
else if (i >= rowCount) { // past valid data area
for (int ii = 0; ii < chunk.dsCount; ii++) {
data[row][ii] = Double.NaN;
}
}
else { // inside the valid are but the pointer has to be wrapped
if (pointer >= rowCount) {
pointer -= rowCount;
db.rrdFile.ras.seek(dataOffset + (pointer * 8));
}
for (int ii = 0; ii < chunk.dsCount; ii++) {
data[row][ii] = db.rrdFile.readDouble();
}
pointer++;
}
}
}
void printInfo(PrintStream s, NumberFormat numberFormat, int index) {
StringBuffer sb = new StringBuffer("rra[");
sb.append(index);
s.print(sb);
s.print("].cf = \"");
s.print(type);
s.println("\"");
s.print(sb);
s.print("].rows = ");
s.println(rowCount);
s.print(sb);
s.print("].pdp_per_row = ");
s.println(pdpCount);
s.print(sb);
s.print("].xff = ");
s.println(xff);
sb.append("].cdp_prep[");
int cdpIndex = 0;
for (Iterator<CDPStatusBlock> i = cdpStatusBlocks.iterator(); i.hasNext();) {
CDPStatusBlock cdp = i.next();
s.print(sb);
s.print(cdpIndex);
s.print("].value = ");
double value = cdp.value;
s.println(Double.isNaN(value)
? "NaN"
: numberFormat.format(value));
s.print(sb);
s.print(cdpIndex++);
s.print("].unknown_datapoints = ");
s.println(cdp.unknownDatapoints);
}
}
void toXml(PrintStream s) throws RrdException {
try {
s.println("\t<rra>");
s.print("\t\t<cf> ");
s.print(type);
s.println(" </cf>");
s.print("\t\t<pdp_per_row> ");
s.print(pdpCount);
s.print(" </pdp_per_row> <!-- ");
s.print(db.header.pdpStep * pdpCount);
s.println(" seconds -->");
s.print("\t\t<xff> ");
s.print(xff);
s.println(" </xff>");
s.println();
s.println("\t\t<cdp_prep>");
for (int i = 0; i < cdpStatusBlocks.size(); i++) {
cdpStatusBlocks.get(i).toXml(s);
}
s.println("\t\t</cdp_prep>");
s.println("\t\t<database>");
long timer = -(rowCount - 1);
int counter = 0;
int row = currentRow;
db.rrdFile.ras.seek(dataOffset + (row + 1) * 16);
long lastUpdate = db.lastUpdate.getTime() / 1000;
int pdpStep = db.header.pdpStep;
NumberFormat numberFormat = new DecimalFormat("0.0000000000E0");
SimpleDateFormat dateFormat =
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z");
while (counter++ < rowCount) {
row++;
if (row == rowCount) {
row = 0;
db.rrdFile.ras.seek(dataOffset);
}
long now = (lastUpdate - lastUpdate % (pdpCount * pdpStep))
+ (timer * pdpCount * pdpStep);
timer++;
s.print("\t\t\t<!-- ");
s.print(dateFormat.format(new Date(now * 1000)));
s.print(" / ");
s.print(now);
s.print(" --> ");
for (int col = 0; col < db.header.dsCount; col++) {
s.print("<v> ");
double value = db.rrdFile.readDouble();
// NumberFormat doesn't know how to handle NaN
if (Double.isNaN(value)) {
s.print("NaN");
}
else {
s.print(numberFormat.format(value));
}
s.print(" </v>");
}
s.println("</row>");
}
s.println("\t\t</database>");
s.println("\t</rra>");
}
catch (IOException e) { // Is the best thing to do here?
throw new RuntimeException(e.getMessage());
}
}
/*
// THIS IS THE ORIGINAL CODE: BUGGY! Replaced by Sasa Markovic with a new method
// Funny: the bug will appear only if dsCount != 2 :)
public double[][] getValuesOriginal() throws IOException {
if (values != null) {
return values;
}
values = new double[db.header.dsCount][rowCount];
int row = currentRow;
db.rrdFile.ras.seek(dataOffset + (row + 1) * 16); // <----- BUG (resolved below)
for (int counter = 0; counter < rowCount; counter++) {
row++;
if (row == rowCount) {
row = 0;
db.rrdFile.ras.seek(dataOffset);
}
for (int col = 0; col < db.header.dsCount; col++) {
double value = db.rrdFile.readDouble();
values[col][counter] = value;
}
}
return values;
}
*/
// Resolved bug from the original method (see above)
public double[][] getValues() throws IOException,RrdException {
// OK PART
if (values != null) {
return values;
}
values = new double[db.header.dsCount][rowCount];
int row = currentRow;
// HERE ARE THE DRAGONS!
db.rrdFile.ras.seek(dataOffset + (row + 1) * db.header.dsCount * 8);
// OK, TOO!
for (int counter = 0; counter < rowCount; counter++) {
row++;
if (row == rowCount) {
row = 0;
db.rrdFile.ras.seek(dataOffset);
}
for (int col = 0; col < db.header.dsCount; col++) {
double value = db.rrdFile.readDouble();
values[col][counter] = value;
}
}
return values;
}
/**
* Returns the number of primary data points required for a consolidated
* data point in this archive.
*
* @return the number of primary data points required for a consolidated
* data point in this archive.
*/
public int getPdpCount() {
return pdpCount;
}
/**
* Returns the number of entries in this archive.
*
* @return the number of entries in this archive.
*/
public int getRowCount() {
return rowCount;
}
/**
* Returns the X-Files Factor for this archive.
*
* @return the X-Files Factor for this archive.
*/
public double getXff() {
return xff;
}
/**
* Returns a summary the contents of this archive.
*
* @return a summary of the information contained in this archive.
*/
public String toString() {
StringBuffer sb = new StringBuffer("[Archive: OFFSET=0x");
sb.append(Long.toHexString(offset));
sb.append(", SIZE=0x");
sb.append(Long.toHexString(size));
sb.append(", type=");
sb.append(type);
sb.append(", rowCount=");
sb.append(rowCount);
sb.append(", pdpCount=");
sb.append(pdpCount);
sb.append(", xff=");
sb.append(xff);
sb.append(", currentRow=");
sb.append(currentRow);
sb.append("]");
for (Iterator<CDPStatusBlock> i = cdpStatusBlocks.iterator(); i.hasNext();) {
CDPStatusBlock cdp = i.next();
sb.append("\n\t\t");
sb.append(cdp.toString());
}
return sb.toString();
}
}