package org.rrd4j.core.jrrd;
import java.io.IOException;
import java.io.PrintStream;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
/**
* Instances of this class model an archive section of an RRD file.
*
* @author <a href="mailto:ciaran@codeloop.com">Ciaran Treanor</a>
* @version $Revision: 1.1 $
*/
public class Archive {
private static enum rra_par_en {RRA_cdp_xff_val, RRA_hw_alpha};
final RRDatabase db;
/** Header offset within file in bytes */
final long headerOffset;
/** Header size in bytes */
private final long headerSize;
/** Data offset within file in bytes */
long dataOffset;
private final ConsolidationFunctionType type;
/** Data row count */
final int rowCount;
final int pdpCount;
final double xff;
/// Following fields are initialized during RRDatabase construction
/// and in fact immutable
/** Consolitation data points */
List<CDPStatusBlock> cdpStatusBlocks;
/** Row for last modification time of database */
int currentRow;
/** Cached content */
private double[][] values;
Archive(RRDatabase db) throws IOException {
this.db = db;
RRDFile file = db.rrdFile;
headerOffset = file.getFilePointer();
type = ConsolidationFunctionType.valueOf(file.readString(Constants.CF_NAM_SIZE).toUpperCase());
file.align(file.getBits() / 8);
rowCount = file.readLong();
pdpCount = file.readLong();
UnivalArray par = file.getUnivalArray(10);
xff = par.getDouble(rra_par_en.RRA_cdp_xff_val);
headerSize = file.getFilePointer() - headerOffset;
}
/**
* 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 {
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 {
currentRow = file.readLong();
}
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(Constants.SIZE_OF_DOUBLE * rowCount * dsCount);
}
void loadData(DataChunk chunk)
throws IOException {
long rowIndexPointer;
if (chunk.startOffset < 0) {
rowIndexPointer = currentRow + 1;
}
else {
rowIndexPointer = currentRow + chunk.startOffset + 1;
}
if (rowIndexPointer < rowCount) {
db.rrdFile.seek((dataOffset + (chunk.dsCount * rowIndexPointer * Constants.SIZE_OF_DOUBLE)));
} else {
// Safety net: prevent from reading random portions of file
// if something went wrong
db.rrdFile.seekToEndOfFile();
}
double[][] data = chunk.data;
/*
* This is also terrible - cleanup - CT
*/
int row = 0;
for (int i = chunk.startOffset; i < rowCount - chunk.endOffset; i++, row++) {
if (i < 0) { // no valid data yet
Arrays.fill(data[row], Double.NaN);
}
else if (i >= rowCount) { // past valid data area
Arrays.fill(data[row], Double.NaN);
}
else { // inside the valid are but the pointer has to be wrapped
if (rowIndexPointer >= rowCount) {
rowIndexPointer -= rowCount;
db.rrdFile.seek(dataOffset + (chunk.dsCount * rowIndexPointer * Constants.SIZE_OF_DOUBLE));
}
for (int ii = 0; ii < chunk.dsCount; ii++) {
data[row][ii] = db.rrdFile.readDouble();
}
rowIndexPointer++;
}
}
}
void printInfo(PrintStream s, NumberFormat numberFormat, int index) {
StringBuilder sb = new StringBuilder("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) {
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.seek(dataOffset + (row + 1) * db.header.dsCount * Constants.SIZE_OF_DOUBLE);
long lastUpdate = db.lastUpdate.getTime() / 1000;
int pdpStep = db.header.pdpStep;
NumberFormat numberFormat = new DecimalFormat("0.0000000000E0", DecimalFormatSymbols.getInstance(Locale.US));
SimpleDateFormat dateFormat =
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z");
while (counter++ < rowCount) {
row++;
if (row == rowCount) {
row = 0;
db.rrdFile.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(" --> ");
s.println("<row>");
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());
}
}
/**
* <p>Getter for the field <code>values</code>.</p>
*
* @return an array of double.
* @throws java.io.IOException if any.
*/
public double[][] getValues() throws IOException {
if (values != null) {
return values;
}
values = new double[db.header.dsCount][rowCount];
int row = currentRow;
db.rrdFile.seek(dataOffset + (row + 1) * db.header.dsCount * Constants.SIZE_OF_DOUBLE);
for (int counter = 0; counter < rowCount; counter++) {
row++;
if (row == rowCount) {
row = 0;
db.rrdFile.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() {
StringBuilder sb = new StringBuilder("[Archive: OFFSET=0x");
sb.append(Long.toHexString(headerOffset))
.append(", SIZE=0x")
.append(Long.toHexString(headerSize))
.append(", type=")
.append(type)
.append(", rowCount=")
.append(rowCount)
.append(", pdpCount=")
.append(pdpCount)
.append(", xff=")
.append(xff)
.append(", currentRow=")
.append(currentRow)
.append("]");
for(CDPStatusBlock cdp: cdpStatusBlocks) {
sb.append("\n\t\t");
sb.append(cdp.toString());
}
return sb.toString();
}
}