Commits

mathias.bogaert@gmail.com  committed 4c103f5

Forgot these files.

  • Participants
  • Parent commits 8746a68
  • Branches atlassian

Comments (0)

Files changed (16)

File src/main/java/org/rrd4j/core/jrrd/Archive.java

+/*
+ * Copyright (C) 2001 Ciaran Treanor <ciaran@codeloop.com>
+ *
+ * Distributable under GPL license.
+ * See terms of license at gnu.org.
+ *
+ * $Id: Archive.java,v 1.1 2006/02/03 08:27:16 sasam Exp $
+ */
+package org.rrd4j.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;
+
+/**
+ * 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 {
+
+    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 {
+
+        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 {
+
+        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.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 {
+
+        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 {
+
+        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) {
+
+        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.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 {
+        // 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() {
+
+        StringBuilder sb = new StringBuilder("[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();
+    }
+}

File src/main/java/org/rrd4j/core/jrrd/CDPStatusBlock.java

+/*
+ * Copyright (C) 2001 Ciaran Treanor <ciaran@codeloop.com>
+ *
+ * Distributable under GPL license.
+ * See terms of license at gnu.org.
+ *
+ * $Id: CDPStatusBlock.java,v 1.1 2006/02/03 08:27:16 sasam Exp $
+ */
+package org.rrd4j.core.jrrd;
+
+import java.io.IOException;
+import java.io.PrintStream;
+
+/**
+ * Instances of this class model the consolidation data point status from an RRD file.
+ *
+ * @author <a href="mailto:ciaran@codeloop.com">Ciaran Treanor</a>
+ * @version $Revision: 1.1 $
+ */
+public class CDPStatusBlock {
+
+    long offset;
+    long size;
+    int unknownDatapoints;
+    double value;
+
+    CDPStatusBlock(RRDFile file) throws IOException {
+
+        offset = file.getFilePointer();
+        value = file.readDouble();
+        unknownDatapoints = file.readInt();
+
+        // Skip rest of cdp_prep_t.scratch
+        file.skipBytes(68);
+
+        size = file.getFilePointer() - offset;
+    }
+
+    /**
+     * Returns the number of unknown primary data points that were integrated.
+     *
+     * @return the number of unknown primary data points that were integrated.
+     */
+    public int getUnknownDatapoints() {
+        return unknownDatapoints;
+    }
+
+    /**
+     * Returns the value of this consolidated data point.
+     *
+     * @return the value of this consolidated data point.
+     */
+    public double getValue() {
+        return value;
+    }
+
+    void toXml(PrintStream s) {
+
+        s.print("\t\t\t<ds><value> ");
+        s.print(value);
+        s.print(" </value>  <unknown_datapoints> ");
+        s.print(unknownDatapoints);
+        s.println(" </unknown_datapoints></ds>");
+    }
+
+    /**
+     * Returns a summary the contents of this CDP status block.
+     *
+     * @return a summary of the information contained in the CDP status block.
+     */
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder("[CDPStatusBlock: OFFSET=0x");
+
+        sb.append(Long.toHexString(offset));
+        sb.append(", SIZE=0x");
+        sb.append(Long.toHexString(size));
+        sb.append(", unknownDatapoints=");
+        sb.append(unknownDatapoints);
+        sb.append(", value=");
+        sb.append(value);
+        sb.append("]");
+
+        return sb.toString();
+    }
+}

File src/main/java/org/rrd4j/core/jrrd/ConsolidationFunctionType.java

+/*
+ * Copyright (C) 2001 Ciaran Treanor <ciaran@codeloop.com>
+ *
+ * Distributable under GPL license.
+ * See terms of license at gnu.org.
+ *
+ * $Id: ConsolidationFunctionType.java,v 1.1 2006/02/03 08:27:16 sasam Exp $
+ */
+package org.rrd4j.core.jrrd;
+
+/**
+ * Class ConsolidationFunctionType
+ *
+ * @author <a href="mailto:ciaran@codeloop.com">Ciaran Treanor</a>
+ * @version $Revision: 1.1 $
+ */
+public class ConsolidationFunctionType {
+
+    private static final int _AVERAGE = 0;
+    private static final String STR_AVERAGE = "AVERAGE";
+
+    /**
+     * Field AVERAGE
+     */
+    public static final ConsolidationFunctionType AVERAGE =
+            new ConsolidationFunctionType(_AVERAGE);
+    private static final int _MIN = 1;
+    private static final String STR_MIN = "MIN";
+
+    /**
+     * Field MIN
+     */
+    public static final ConsolidationFunctionType MIN =
+            new ConsolidationFunctionType(_MIN);
+    private static final int _MAX = 2;
+    private static final String STR_MAX = "MAX";
+
+    /**
+     * Field MAX
+     */
+    public static final ConsolidationFunctionType MAX =
+            new ConsolidationFunctionType(_MAX);
+    private static final int _LAST = 3;
+    private static final String STR_LAST = "LAST";
+
+    /**
+     * Field LAST
+     */
+    public static final ConsolidationFunctionType LAST =
+            new ConsolidationFunctionType(_LAST);
+    private int type;
+
+    private ConsolidationFunctionType(int type) {
+        this.type = type;
+    }
+
+    /**
+     * Returns a <code>ConsolidationFunctionType</code> with the given name.
+     *
+     * @param s name of the <code>ConsolidationFunctionType</code> required.
+     * @return a <code>ConsolidationFunctionType</code> with the given name.
+     */
+    public static ConsolidationFunctionType get(String s) {
+
+        if (s.equalsIgnoreCase(STR_AVERAGE)) {
+            return AVERAGE;
+        }
+        else if (s.equalsIgnoreCase(STR_MIN)) {
+            return MIN;
+        }
+        else if (s.equalsIgnoreCase(STR_MAX)) {
+            return MAX;
+        }
+        else if (s.equalsIgnoreCase(STR_LAST)) {
+            return LAST;
+        }
+        else {
+            throw new IllegalArgumentException("Invalid ConsolidationFunctionType");
+        }
+    }
+
+    /**
+     * Compares this object against the specified object.
+     *
+     * @return <code>true</code> if the objects are the same,
+     *         <code>false</code> otherwise.
+     */
+    public boolean equals(Object o) {
+
+        if (!(o instanceof ConsolidationFunctionType)) {
+            throw new IllegalArgumentException("Not a ConsolidationFunctionType");
+        }
+
+        return (((ConsolidationFunctionType) o).type == type)
+                ? true
+                : false;
+    }
+
+    /**
+     * Returns a string representation of this object.
+     *
+     * @return a string representation of this object.
+     */
+    public String toString() {
+
+        String strType;
+
+        switch (type) {
+
+            case _AVERAGE:
+                strType = STR_AVERAGE;
+                break;
+
+            case _MIN:
+                strType = STR_MIN;
+                break;
+
+            case _MAX:
+                strType = STR_MAX;
+                break;
+
+            case _LAST:
+                strType = STR_LAST;
+                break;
+
+            default:
+                throw new RuntimeException("This should never happen");
+        }
+
+        return strType;
+    }
+}

File src/main/java/org/rrd4j/core/jrrd/Constants.java

+/*
+ * Copyright (C) 2001 Ciaran Treanor <ciaran@codeloop.com>
+ *
+ * Distributable under GPL license.
+ * See terms of license at gnu.org.
+ *
+ * $Id: Constants.java,v 1.1 2006/02/03 08:27:16 sasam Exp $
+ */
+package org.rrd4j.core.jrrd;
+
+interface Constants {
+
+    int DS_NAM_SIZE = 20;
+    int DST_SIZE = 20;
+    int CF_NAM_SIZE = 20;
+    int LAST_DS_LEN = 30;
+    static String COOKIE = "RRD";
+    static String VERSION = "0001";
+    double FLOAT_COOKIE = 8.642135E130;
+    static byte[] FLOAT_COOKIE_BIG_ENDIAN = {0x5B, 0x1F, 0x2B, 0x43,
+            (byte) 0xC7, (byte) 0xC0, 0x25,
+            0x2F};
+    static byte[] FLOAT_COOKIE_LITTLE_ENDIAN = {0x2F, 0x25, (byte) 0xC0,
+            (byte) 0xC7, 0x43, 0x2B, 0x1F,
+            0x5B};
+}

File src/main/java/org/rrd4j/core/jrrd/DataChunk.java

+/*
+ * Copyright (C) 2001 Ciaran Treanor <ciaran@codeloop.com>
+ *
+ * Distributable under GPL license.
+ * See terms of license at gnu.org.
+ *
+ * $Id: DataChunk.java,v 1.1 2006/02/03 08:27:16 sasam Exp $
+ */
+package org.rrd4j.core.jrrd;
+
+/**
+ * Models a chunk of result data from an RRDatabase.
+ *
+ * @author <a href="mailto:ciaran@codeloop.com">Ciaran Treanor</a>
+ * @version $Revision: 1.1 $
+ */
+public class DataChunk {
+
+    private static final String NEWLINE = System.getProperty("line.separator");
+    long startTime;
+    int start;
+    int end;
+    long step;
+    int dsCount;
+    double[][] data;
+    int rows;
+
+    DataChunk(long startTime, int start, int end, long step, int dsCount, int rows) {
+        this.startTime = startTime;
+        this.start = start;
+        this.end = end;
+        this.step = step;
+        this.dsCount = dsCount;
+        this.rows = rows;
+        data = new double[rows][dsCount];
+    }
+
+    /**
+     * Returns a summary of the contents of this data chunk. The first column is
+     * the time (RRD format) and the following columns are the data source
+     * values.
+     *
+     * @return a summary of the contents of this data chunk.
+     */
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder();
+        long time = startTime;
+
+        for (int row = 0; row < rows; row++, time += step) {
+            sb.append(time);
+            sb.append(": ");
+
+            for (int ds = 0; ds < dsCount; ds++) {
+                sb.append(data[row][ds]);
+                sb.append(" ");
+            }
+
+            sb.append(NEWLINE);
+        }
+
+        return sb.toString();
+    }
+}

File src/main/java/org/rrd4j/core/jrrd/DataSource.java

+/*
+ * Copyright (C) 2001 Ciaran Treanor <ciaran@codeloop.com>
+ *
+ * Distributable under GPL license.
+ * See terms of license at gnu.org.
+ *
+ * $Id: DataSource.java,v 1.1 2006/02/03 08:27:16 sasam Exp $
+ */
+package org.rrd4j.core.jrrd;
+
+import java.io.IOException;
+import java.io.PrintStream;
+import java.text.NumberFormat;
+
+/**
+ * Instances of this class model a data source in an RRD file.
+ *
+ * @author <a href="mailto:ciaran@codeloop.com">Ciaran Treanor</a>
+ * @version $Revision: 1.1 $
+ */
+public class DataSource {
+
+    long offset;
+    long size;
+    String name;
+    DataSourceType type;
+    int minimumHeartbeat;
+    double minimum;
+    double maximum;
+    PDPStatusBlock pdpStatusBlock;
+
+    DataSource(RRDFile file) throws IOException {
+
+        offset = file.getFilePointer();
+        name = file.readString(Constants.DS_NAM_SIZE);
+        type = DataSourceType.get(file.readString(Constants.DST_SIZE));
+
+        file.align(8);
+
+        minimumHeartbeat = file.readInt(true);
+
+        file.align(8);
+
+        minimum = file.readDouble();
+        maximum = file.readDouble();
+
+        // Skip rest of ds_def_t.par[]
+        file.align();
+        file.skipBytes(56);
+
+        size = file.getFilePointer() - offset;
+    }
+
+    void loadPDPStatusBlock(RRDFile file) throws IOException {
+        pdpStatusBlock = new PDPStatusBlock(file);
+    }
+
+    /**
+     * Returns the primary data point status block for this data source.
+     *
+     * @return the primary data point status block for this data source.
+     */
+    public PDPStatusBlock getPDPStatusBlock() {
+        return pdpStatusBlock;
+    }
+
+    /**
+     * Returns the minimum required heartbeat for this data source.
+     *
+     * @return the minimum required heartbeat for this data source.
+     */
+    public int getMinimumHeartbeat() {
+        return minimumHeartbeat;
+    }
+
+    /**
+     * Returns the minimum value input to this data source can have.
+     *
+     * @return the minimum value input to this data source can have.
+     */
+    public double getMinimum() {
+        return minimum;
+    }
+
+    /**
+     * Returns the type this data source is.
+     *
+     * @return the type this data source is.
+     * @see DataSourceType
+     */
+    public DataSourceType getType() {
+        return type;
+    }
+
+    /**
+     * Returns the maximum value input to this data source can have.
+     *
+     * @return the maximum value input to this data source can have.
+     */
+    public double getMaximum() {
+        return maximum;
+    }
+
+    /**
+     * Returns the name of this data source.
+     *
+     * @return the name of this data source.
+     */
+    public String getName() {
+        return name;
+    }
+
+    void printInfo(PrintStream s, NumberFormat numberFormat) {
+
+        StringBuilder sb = new StringBuilder("ds[");
+
+        sb.append(name);
+        s.print(sb);
+        s.print("].type = \"");
+        s.print(type);
+        s.println("\"");
+        s.print(sb);
+        s.print("].minimal_heartbeat = ");
+        s.println(minimumHeartbeat);
+        s.print(sb);
+        s.print("].min = ");
+        s.println(Double.isNaN(minimum)
+                ? "NaN"
+                : numberFormat.format(minimum));
+        s.print(sb);
+        s.print("].max = ");
+        s.println(Double.isNaN(maximum)
+                ? "NaN"
+                : numberFormat.format(maximum));
+        s.print(sb);
+        s.print("].last_ds = ");
+        s.println(pdpStatusBlock.lastReading);
+        s.print(sb);
+        s.print("].value = ");
+
+        double value = pdpStatusBlock.value;
+
+        s.println(Double.isNaN(value)
+                ? "NaN"
+                : numberFormat.format(value));
+        s.print(sb);
+        s.print("].unknown_sec = ");
+        s.println(pdpStatusBlock.unknownSeconds);
+    }
+
+    void toXml(PrintStream s) {
+
+        s.println("\t<ds>");
+        s.print("\t\t<name> ");
+        s.print(name);
+        s.println(" </name>");
+        s.print("\t\t<type> ");
+        s.print(type);
+        s.println(" </type>");
+        s.print("\t\t<minimal_heartbeat> ");
+        s.print(minimumHeartbeat);
+        s.println(" </minimal_heartbeat>");
+        s.print("\t\t<min> ");
+        s.print(minimum);
+        s.println(" </min>");
+        s.print("\t\t<max> ");
+        s.print(maximum);
+        s.println(" </max>");
+        s.println();
+        s.println("\t\t<!-- PDP Status -->");
+        s.print("\t\t<last_ds> ");
+        s.print(pdpStatusBlock.lastReading);
+        s.println(" </last_ds>");
+        s.print("\t\t<value> ");
+        s.print(pdpStatusBlock.value);
+        s.println(" </value>");
+        s.print("\t\t<unknown_sec> ");
+        s.print(pdpStatusBlock.unknownSeconds);
+        s.println(" </unknown_sec>");
+        s.println("\t</ds>");
+        s.println();
+    }
+
+    /**
+     * Returns a summary the contents of this data source.
+     *
+     * @return a summary of the information contained in this data source.
+     */
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder("[DataSource: OFFSET=0x");
+
+        sb.append(Long.toHexString(offset));
+        sb.append(", SIZE=0x");
+        sb.append(Long.toHexString(size));
+        sb.append(", name=");
+        sb.append(name);
+        sb.append(", type=");
+        sb.append(type.toString());
+        sb.append(", minHeartbeat=");
+        sb.append(minimumHeartbeat);
+        sb.append(", min=");
+        sb.append(minimum);
+        sb.append(", max=");
+        sb.append(maximum);
+        sb.append("]");
+        sb.append("\n\t\t");
+        sb.append(pdpStatusBlock.toString());
+
+        return sb.toString();
+    }
+}

File src/main/java/org/rrd4j/core/jrrd/DataSourceType.java

+/*
+ * Copyright (C) 2001 Ciaran Treanor <ciaran@codeloop.com>
+ *
+ * Distributable under GPL license.
+ * See terms of license at gnu.org.
+ *
+ * $Id: DataSourceType.java,v 1.1 2006/02/03 08:27:16 sasam Exp $
+ */
+package org.rrd4j.core.jrrd;
+
+/**
+ * Class DataSourceType
+ *
+ * @author <a href="mailto:ciaran@codeloop.com">Ciaran Treanor</a>
+ * @version $Revision: 1.1 $
+ */
+public class DataSourceType {
+
+    private static final int _COUNTER = 0;
+    private static final String STR_COUNTER = "COUNTER";
+
+    /**
+     * Field COUNTER
+     */
+    public static final DataSourceType COUNTER =
+            new DataSourceType(_COUNTER);
+    private static final int _ABSOLUTE = 1;
+    private static final String STR_ABSOLUTE = "ABSOLUTE";
+
+    /**
+     * Field ABSOLUTE
+     */
+    public static final DataSourceType ABSOLUTE =
+            new DataSourceType(_ABSOLUTE);
+    private static final int _GAUGE = 2;
+    private static final String STR_GAUGE = "GAUGE";
+
+    /**
+     * Field GAUGE
+     */
+    public static final DataSourceType GAUGE = new DataSourceType(_GAUGE);
+    private static final int _DERIVE = 3;
+    private static final String STR_DERIVE = "DERIVE";
+
+    /**
+     * Field DERIVE
+     */
+    public static final DataSourceType DERIVE = new DataSourceType(_DERIVE);
+    private int type;
+
+    private DataSourceType(int type) {
+        this.type = type;
+    }
+
+    /**
+     * Returns a <code>DataSourceType</code> with the given name.
+     *
+     * @param s name of the <code>DataSourceType</code> required.
+     * @return a <code>DataSourceType</code> with the given name.
+     */
+    public static DataSourceType get(String s) {
+
+        if (s.equalsIgnoreCase(STR_COUNTER)) {
+            return COUNTER;
+        }
+        else if (s.equalsIgnoreCase(STR_ABSOLUTE)) {
+            return ABSOLUTE;
+        }
+        else if (s.equalsIgnoreCase(STR_GAUGE)) {
+            return GAUGE;
+        }
+        else if (s.equalsIgnoreCase(STR_DERIVE)) {
+            return DERIVE;
+        }
+        else {
+            throw new IllegalArgumentException("Invalid DataSourceType");
+        }
+    }
+
+    /**
+     * Compares this object against the specified object.
+     *
+     * @return <code>true</code> if the objects are the same,
+     *         <code>false</code> otherwise.
+     */
+    public boolean equals(Object obj) {
+
+        if (!(obj instanceof DataSourceType)) {
+            throw new IllegalArgumentException("Not a DataSourceType");
+        }
+
+        return (((DataSourceType) obj).type == type)
+                ? true
+                : false;
+    }
+
+    /**
+     * Returns a string representation of this object.
+     *
+     * @return a string representation of this object.
+     */
+    public String toString() {
+
+        String strType;
+
+        switch (type) {
+
+            case _COUNTER:
+                strType = STR_COUNTER;
+                break;
+
+            case _ABSOLUTE:
+                strType = STR_ABSOLUTE;
+                break;
+
+            case _GAUGE:
+                strType = STR_GAUGE;
+                break;
+
+            case _DERIVE:
+                strType = STR_DERIVE;
+                break;
+
+            default:
+                // Don't you just hate it when you see a line like this?
+                throw new RuntimeException("This should never happen");
+        }
+
+        return strType;
+    }
+}

File src/main/java/org/rrd4j/core/jrrd/Header.java

+/*
+ * Copyright (C) 2001 Ciaran Treanor <ciaran@codeloop.com>
+ *
+ * Distributable under GPL license.
+ * See terms of license at gnu.org.
+ *
+ * $Id: Header.java,v 1.1 2006/02/03 08:27:16 sasam Exp $
+ */
+package org.rrd4j.core.jrrd;
+
+import java.io.IOException;
+
+/**
+ * Instances of this class model the header section of an RRD file.
+ *
+ * @author <a href="mailto:ciaran@codeloop.com">Ciaran Treanor</a>
+ * @version $Revision: 1.1 $
+ */
+public class Header implements Constants {
+
+    static final long offset = 0;
+    long size;
+    String version;
+    int dsCount;
+    int rraCount;
+    int pdpStep;
+
+    Header(RRDFile file) throws IOException {
+
+        if (!file.readString(4).equals(COOKIE)) {
+            throw new IOException("Invalid COOKIE");
+        }
+
+        if (!(version = file.readString(5)).equals(VERSION)) {
+            throw new IOException("Unsupported RRD version (" + version + ")");
+        }
+
+        file.align();
+
+        // Consume the FLOAT_COOKIE
+        file.readDouble();
+
+        dsCount = file.readInt();
+        rraCount = file.readInt();
+        pdpStep = file.readInt();
+
+        // Skip rest of stat_head_t.par
+        file.align();
+        file.skipBytes(80);
+
+        size = file.getFilePointer() - offset;
+    }
+
+    /**
+     * Returns the version of the database.
+     *
+     * @return the version of the database.
+     */
+    public String getVersion() {
+        return version;
+    }
+
+    /**
+     * Returns the number of <code>DataSource</code>s in the database.
+     *
+     * @return the number of <code>DataSource</code>s in the database.
+     */
+    public int getDSCount() {
+        return dsCount;
+    }
+
+    /**
+     * Returns the number of <code>Archive</code>s in the database.
+     *
+     * @return the number of <code>Archive</code>s in the database.
+     */
+    public int getRRACount() {
+        return rraCount;
+    }
+
+    /**
+     * Returns the primary data point interval in seconds.
+     *
+     * @return the primary data point interval in seconds.
+     */
+    public int getPDPStep() {
+        return pdpStep;
+    }
+
+    /**
+     * Returns a summary the contents of this header.
+     *
+     * @return a summary of the information contained in this header.
+     */
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder("[Header: OFFSET=0x00, SIZE=0x");
+
+        sb.append(Long.toHexString(size));
+        sb.append(", version=");
+        sb.append(version);
+        sb.append(", dsCount=");
+        sb.append(dsCount);
+        sb.append(", rraCount=");
+        sb.append(rraCount);
+        sb.append(", pdpStep=");
+        sb.append(pdpStep);
+        sb.append("]");
+
+        return sb.toString();
+    }
+}

File src/main/java/org/rrd4j/core/jrrd/PDPStatusBlock.java

+/*
+ * Copyright (C) 2001 Ciaran Treanor <ciaran@codeloop.com>
+ *
+ * Distributable under GPL license.
+ * See terms of license at gnu.org.
+ *
+ * $Id: PDPStatusBlock.java,v 1.1 2006/02/03 08:27:16 sasam Exp $
+ */
+package org.rrd4j.core.jrrd;
+
+import java.io.IOException;
+
+/**
+ * Instances of this class model the primary data point status from an RRD file.
+ *
+ * @author <a href="mailto:ciaran@codeloop.com">Ciaran Treanor</a>
+ * @version $Revision: 1.1 $
+ */
+public class PDPStatusBlock {
+
+    long offset;
+    long size;
+    String lastReading;
+    int unknownSeconds;
+    double value;
+
+    PDPStatusBlock(RRDFile file) throws IOException {
+
+        offset = file.getFilePointer();
+        lastReading = file.readString(Constants.LAST_DS_LEN);
+
+        file.align(4);
+
+        unknownSeconds = file.readInt();
+
+        file.skipBytes(4);
+
+        value = file.readDouble();
+
+        // Skip rest of pdp_prep_t.par[]
+        file.skipBytes(64);
+
+        size = file.getFilePointer() - offset;
+    }
+
+    /**
+     * Returns the last reading from the data source.
+     *
+     * @return the last reading from the data source.
+     */
+    public String getLastReading() {
+        return lastReading;
+    }
+
+    /**
+     * Returns the current value of the primary data point.
+     *
+     * @return the current value of the primary data point.
+     */
+    public double getValue() {
+        return value;
+    }
+
+    /**
+     * Returns the number of seconds of the current primary data point is
+     * unknown data.
+     *
+     * @return the number of seconds of the current primary data point is unknown data.
+     */
+    public int getUnknownSeconds() {
+        return unknownSeconds;
+    }
+
+    /**
+     * Returns a summary the contents of this PDP status block.
+     *
+     * @return a summary of the information contained in this PDP status block.
+     */
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder("[PDPStatus: OFFSET=0x");
+
+        sb.append(Long.toHexString(offset));
+        sb.append(", SIZE=0x");
+        sb.append(Long.toHexString(size));
+        sb.append(", lastReading=");
+        sb.append(lastReading);
+        sb.append(", unknownSeconds=");
+        sb.append(unknownSeconds);
+        sb.append(", value=");
+        sb.append(value);
+        sb.append("]");
+
+        return sb.toString();
+    }
+}

File src/main/java/org/rrd4j/core/jrrd/RRDFile.java

+/*
+ * Copyright (C) 2001 Ciaran Treanor <ciaran@codeloop.com>
+ *
+ * Distributable under GPL license.
+ * See terms of license at gnu.org.
+ *
+ * $Id: RRDFile.java,v 1.1 2006/02/03 08:27:16 sasam Exp $
+ */
+package org.rrd4j.core.jrrd;
+
+import java.io.*;
+
+/**
+ * This class is a quick hack to read information from an RRD file. Writing
+ * to RRD files is not currently supported. As I said, this is a quick hack.
+ * Some thought should be put into the overall design of the file IO.
+ * <p/>
+ * Currently this can read RRD files that were generated on Solaris (Sparc)
+ * and Linux (x86).
+ *
+ * @author <a href="mailto:ciaran@codeloop.com">Ciaran Treanor</a>
+ * @version $Revision: 1.1 $
+ */
+public class RRDFile implements Constants {
+
+    boolean bigEndian;
+    int alignment;
+    RandomAccessFile ras;
+    byte[] buffer;
+
+    RRDFile(String name) throws IOException {
+        this(new File(name));
+    }
+
+    RRDFile(File file) throws IOException {
+
+        ras = new RandomAccessFile(file, "r");
+        buffer = new byte[128];
+
+        initDataLayout(file);
+    }
+
+    private void initDataLayout(File file) throws IOException {
+
+        if (file.exists()) {    // Load the data formats from the file
+            ras.read(buffer, 0, 24);
+
+            int index;
+
+            if ((index = indexOf(FLOAT_COOKIE_BIG_ENDIAN, buffer)) != -1) {
+                bigEndian = true;
+            }
+            else if ((index = indexOf(FLOAT_COOKIE_LITTLE_ENDIAN, buffer))
+                    != -1) {
+                bigEndian = false;
+            }
+            else {
+                throw new IOException("Invalid RRD file");
+            }
+
+            switch (index) {
+
+                case 12:
+                    alignment = 4;
+                    break;
+
+                case 16:
+                    alignment = 8;
+                    break;
+
+                default:
+                    throw new RuntimeException("Unsupported architecture");
+            }
+        }
+        else {                // Default to data formats for this hardware architecture
+        }
+
+        ras.seek(0);    // Reset file pointer to start of file
+    }
+
+    private int indexOf(byte[] pattern, byte[] array) {
+        return (new String(array)).indexOf(new String(pattern));
+    }
+
+    boolean isBigEndian() {
+        return bigEndian;
+    }
+
+    int getAlignment() {
+        return alignment;
+    }
+
+    double readDouble() throws IOException {
+
+        //double value;
+        byte[] tx = new byte[8];
+
+        ras.read(buffer, 0, 8);
+
+        if (bigEndian) {
+            tx = buffer;
+        }
+        else {
+            for (int i = 0; i < 8; i++) {
+                tx[7 - i] = buffer[i];
+            }
+        }
+
+        DataInputStream reverseDis =
+                new DataInputStream(new ByteArrayInputStream(tx));
+
+        return reverseDis.readDouble();
+    }
+
+    int readInt() throws IOException {
+        return readInt(false);
+    }
+
+    int readInt(boolean dump) throws IOException {
+
+        ras.read(buffer, 0, 4);
+
+        int value;
+
+        if (bigEndian) {
+            value = (0xFF & buffer[3]) | ((0xFF & buffer[2]) << 8)
+                    | ((0xFF & buffer[1]) << 16) | ((0xFF & buffer[0]) << 24);
+        }
+        else {
+            value = (0xFF & buffer[0]) | ((0xFF & buffer[1]) << 8)
+                    | ((0xFF & buffer[2]) << 16) | ((0xFF & buffer[3]) << 24);
+        }
+
+        return value;
+    }
+
+    String readString(int maxLength) throws IOException {
+
+        ras.read(buffer, 0, maxLength);
+
+        return new String(buffer, 0, maxLength).trim();
+    }
+
+    void skipBytes(int n) throws IOException {
+        ras.skipBytes(n);
+    }
+
+    int align(int boundary) throws IOException {
+
+        int skip = (int) (boundary - (ras.getFilePointer() % boundary)) % boundary;
+
+        if (skip != 0) {
+            ras.skipBytes(skip);
+        }
+
+        return skip;
+    }
+
+    int align() throws IOException {
+        return align(alignment);
+    }
+
+    long info() throws IOException {
+        return ras.getFilePointer();
+    }
+
+    long getFilePointer() throws IOException {
+        return ras.getFilePointer();
+    }
+
+    void close() throws IOException {
+        ras.close();
+    }
+}

File src/main/java/org/rrd4j/core/jrrd/RRDatabase.java

+/*
+ * Copyright (C) 2001 Ciaran Treanor <ciaran@codeloop.com>
+ *
+ * Distributable under GPL license.
+ * See terms of license at gnu.org.
+ *
+ * $Id: RRDatabase.java,v 1.1 2006/02/03 08:27:16 sasam Exp $
+ */
+package org.rrd4j.core.jrrd;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Iterator;
+
+/**
+ * Instances of this class model
+ * <a href="http://people.ee.ethz.ch/~oetiker/webtools/rrdtool/">Round Robin Database</a>
+ * (RRD) files.
+ *
+ * @author <a href="mailto:ciaran@codeloop.com">Ciaran Treanor</a>
+ * @version $Revision: 1.1 $
+ */
+public class RRDatabase {
+
+    RRDFile rrdFile;
+
+    // RRD file name
+    private String name;
+    Header header;
+    ArrayList<DataSource> dataSources;
+    ArrayList<Archive> archives;
+    Date lastUpdate;
+
+    /**
+     * Creates a database to read from.
+     *
+     * @param name the filename of the file to read from.
+     * @throws IOException if an I/O error occurs.
+     */
+    public RRDatabase(String name) throws IOException {
+        this(new File(name));
+    }
+
+    /**
+     * Creates a database to read from.
+     *
+     * @param file the file to read from.
+     * @throws IOException if an I/O error occurs.
+     */
+    public RRDatabase(File file) throws IOException {
+
+        name = file.getName();
+        rrdFile = new RRDFile(file);
+        header = new Header(rrdFile);
+
+        // Load the data sources
+        dataSources = new ArrayList<DataSource>();
+
+        for (int i = 0; i < header.dsCount; i++) {
+            DataSource ds = new DataSource(rrdFile);
+
+            dataSources.add(ds);
+        }
+
+        // Load the archives
+        archives = new ArrayList<Archive>();
+
+        for (int i = 0; i < header.rraCount; i++) {
+            Archive archive = new Archive(this);
+
+            archives.add(archive);
+        }
+
+        rrdFile.align();
+
+        lastUpdate = new Date((long) (rrdFile.readInt()) * 1000);
+
+        // Load PDPStatus(s)
+        for (int i = 0; i < header.dsCount; i++) {
+            DataSource ds = dataSources.get(i);
+
+            ds.loadPDPStatusBlock(rrdFile);
+        }
+
+        // Load CDPStatus(s)
+        for (int i = 0; i < header.rraCount; i++) {
+            Archive archive = archives.get(i);
+
+            archive.loadCDPStatusBlocks(rrdFile, header.dsCount);
+        }
+
+        // Load current row information for each archive
+        for (int i = 0; i < header.rraCount; i++) {
+            Archive archive = archives.get(i);
+
+            archive.loadCurrentRow(rrdFile);
+        }
+
+        // Now load the data
+        for (int i = 0; i < header.rraCount; i++) {
+            Archive archive = archives.get(i);
+
+            archive.loadData(rrdFile, header.dsCount);
+        }
+    }
+
+    /**
+     * Returns the <code>Header</code> for this database.
+     *
+     * @return the <code>Header</code> for this database.
+     */
+    public Header getHeader() {
+        return header;
+    }
+
+    /**
+     * Returns the date this database was last updated. To convert this date to
+     * the form returned by <code>rrdtool last</code> call Date.getTime() and
+     * divide the result by 1000.
+     *
+     * @return the date this database was last updated.
+     */
+    public Date getLastUpdate() {
+        return lastUpdate;
+    }
+
+    /**
+     * Returns the <code>DataSource</code> at the specified position in this database.
+     *
+     * @param index index of <code>DataSource</code> to return.
+     * @return the <code>DataSource</code> at the specified position in this database
+     */
+    public DataSource getDataSource(int index) {
+        return dataSources.get(index);
+    }
+
+    /**
+     * Returns an iterator over the data sources in this database in proper sequence.
+     *
+     * @return an iterator over the data sources in this database in proper sequence.
+     */
+    public Iterator<DataSource> getDataSources() {
+        return dataSources.iterator();
+    }
+
+    /**
+     * Returns the <code>Archive</code> at the specified position in this database.
+     *
+     * @param index index of <code>Archive</code> to return.
+     * @return the <code>Archive</code> at the specified position in this database.
+     */
+    public Archive getArchive(int index) {
+        return archives.get(index);
+    }
+
+    /**
+     * Returns an iterator over the archives in this database in proper sequence.
+     *
+     * @return an iterator over the archives in this database in proper sequence.
+     */
+    public Iterator<Archive> getArchives() {
+        return archives.iterator();
+    }
+
+    /**
+     * Returns the number of archives in this database.
+     *
+     * @return the number of archives in this database.
+     */
+    public int getNumArchives() {
+        return header.rraCount;
+    }
+
+    /**
+     * Returns an iterator over the archives in this database of the given type
+     * in proper sequence.
+     *
+     * @param type the consolidation function that should have been applied to
+     *             the data.
+     * @return an iterator over the archives in this database of the given type
+     *         in proper sequence.
+     */
+    public Iterator<Archive> getArchives(ConsolidationFunctionType type) {
+        return getArchiveList(type).iterator();
+    }
+
+    ArrayList<Archive> getArchiveList(ConsolidationFunctionType type) {
+
+        ArrayList<Archive> subset = new ArrayList<Archive>();
+
+        for (Archive archive : archives) {
+            if (archive.getType().equals(type)) {
+                subset.add(archive);
+            }
+        }
+
+        return subset;
+    }
+
+    /**
+     * Closes this database stream and releases any associated system resources.
+     *
+     * @throws IOException if an I/O error occurs.
+     */
+    public void close() throws IOException {
+        rrdFile.close();
+    }
+
+    /**
+     * Outputs the header information of the database to the given print stream
+     * using the default number format. The default format for <code>double</code>
+     * is 0.0000000000E0.
+     *
+     * @param s the PrintStream to print the header information to.
+     */
+    public void printInfo(PrintStream s) {
+
+        NumberFormat numberFormat = new DecimalFormat("0.0000000000E0");
+
+        printInfo(s, numberFormat);
+    }
+
+    /**
+     * Returns data from the database corresponding to the given consolidation
+     * function and a step size of 1.
+     *
+     * @param type the consolidation function that should have been applied to
+     *             the data.
+     * @return the raw data.
+     * @throws IllegalArgumentException if there was a problem locating a data archive with
+     *                                  the requested consolidation function.
+     * @throws IOException              if there was a problem reading data from the database.
+     */
+    public DataChunk getData(ConsolidationFunctionType type) throws IOException {
+        return getData(type, 1L);
+    }
+
+    /**
+     * Returns data from the database corresponding to the given consolidation
+     * function.
+     *
+     * @param type the consolidation function that should have been applied to
+     *             the data.
+     * @param step the step size to use.
+     * @return the raw data.
+     * @throws IllegalArgumentException if there was a problem locating a data archive with
+     *                                  the requested consolidation function.
+     * @throws IOException              if there was a problem reading data from the database.
+     */
+    public DataChunk getData(ConsolidationFunctionType type, long step)
+            throws IOException {
+
+        ArrayList<Archive> possibleArchives = getArchiveList(type);
+
+        if (possibleArchives.size() == 0) {
+            throw new IllegalArgumentException("Database does not contain an Archive of consolidation function type "
+                    + type);
+        }
+
+        Calendar endCal = Calendar.getInstance();
+
+        endCal.set(Calendar.MILLISECOND, 0);
+
+        Calendar startCal = (Calendar) endCal.clone();
+
+        startCal.add(Calendar.DATE, -1);
+
+        long end = endCal.getTime().getTime() / 1000;
+        long start = startCal.getTime().getTime() / 1000;
+        Archive archive = findBestArchive(start, end, step, possibleArchives);
+
+        // Tune the parameters
+        step = header.pdpStep * archive.pdpCount;
+        start -= start % step;
+
+        if (end % step != 0) {
+            end += step - end % step;
+        }
+
+        int rows = (int) ((end - start) / step + 1);
+
+        //cat.debug("start " + start + " end " + end + " step " + step + " rows "
+        //          + rows);
+
+        // Find start and end offsets
+        // This is terrible - some of this should be encapsulated in Archive - CT.
+        long lastUpdateLong = lastUpdate.getTime() / 1000;
+        long archiveEndTime = lastUpdateLong - (lastUpdateLong % step);
+        long archiveStartTime = archiveEndTime - (step * (archive.rowCount - 1));
+        int startOffset = (int) ((start - archiveStartTime) / step);
+        int endOffset = (int) ((archiveEndTime - end) / step);
+
+        //cat.debug("start " + archiveStartTime + " end " + archiveEndTime
+        //          + " startOffset " + startOffset + " endOffset "
+        //          + (archive.rowCount - endOffset));
+
+        DataChunk chunk = new DataChunk(start, startOffset, endOffset, step,
+                header.dsCount, rows);
+
+        archive.loadData(chunk);
+
+        return chunk;
+    }
+
+    /*
+      * This is almost a verbatim copy of the original C code by Tobias Oetiker.
+      * I need to put more of a Java style on it - CT
+      */
+    private Archive findBestArchive(long start, long end, long step,
+                                    ArrayList<Archive> archives) {
+
+        Archive archive = null;
+        Archive bestFullArchive = null;
+        Archive bestPartialArchive = null;
+        long lastUpdateLong = lastUpdate.getTime() / 1000;
+        int firstPart = 1;
+        int firstFull = 1;
+        long bestMatch = 0;
+        //long bestPartRRA = 0;
+        long bestStepDiff = 0;
+        long tmpStepDiff = 0;
+
+        for (Archive archive1 : archives) {
+            archive = archive1;
+
+            long calEnd = lastUpdateLong
+                    - (lastUpdateLong
+                    % (archive.pdpCount * header.pdpStep));
+            long calStart = calEnd
+                    - (archive.pdpCount * archive.rowCount
+                    * header.pdpStep);
+            long fullMatch = end - start;
+
+            if ((calEnd >= end) && (calStart < start)) {    // Best full match
+                tmpStepDiff = Math.abs(step - (header.pdpStep * archive.pdpCount));
+
+                if ((firstFull != 0) || (tmpStepDiff < bestStepDiff)) {
+                    firstFull = 0;
+                    bestStepDiff = tmpStepDiff;
+                    bestFullArchive = archive;
+                }
+            }
+            else {                                        // Best partial match
+                long tmpMatch = fullMatch;
+
+                if (calStart > start) {
+                    tmpMatch -= calStart - start;
+                }
+
+                if (calEnd < end) {
+                    tmpMatch -= end - calEnd;
+                }
+
+                if ((firstPart != 0) || (bestMatch < tmpMatch)) {
+                    firstPart = 0;
+                    bestMatch = tmpMatch;
+                    bestPartialArchive = archive;
+                }
+            }
+        }
+
+        // See how the matching went
+        // optimise this
+        if (firstFull == 0) {
+            archive = bestFullArchive;
+        }
+        else if (firstPart == 0) {
+            archive = bestPartialArchive;
+        }
+
+        return archive;
+    }
+
+    /**
+     * Outputs the header information of the database to the given print stream
+     * using the given number format. The format is almost identical to that
+     * produced by
+     * <a href="http://people.ee.ethz.ch/~oetiker/webtools/rrdtool/manual/rrdinfo.html">rrdtool info</a>
+     *
+     * @param s            the PrintStream to print the header information to.
+     * @param numberFormat the format to print <code>double</code>s as.
+     */
+    public void printInfo(PrintStream s, NumberFormat numberFormat) {
+
+        s.print("filename = \"");
+        s.print(name);
+        s.println("\"");
+        s.print("rrd_version = \"");
+        s.print(header.version);
+        s.println("\"");
+        s.print("step = ");
+        s.println(header.pdpStep);
+        s.print("last_update = ");
+        s.println(lastUpdate.getTime() / 1000);
+
+        for (DataSource ds : dataSources) {
+            ds.printInfo(s, numberFormat);
+        }
+
+        int index = 0;
+
+        for (Archive archive : archives) {
+            archive.printInfo(s, numberFormat, index++);
+        }
+    }
+
+    /**
+     * Outputs the content of the database to the given print stream
+     * as a stream of XML. The XML format is almost identical to that produced by
+     * <a href="http://people.ee.ethz.ch/~oetiker/webtools/rrdtool/manual/rrddump.html">rrdtool dump</a>
+     *
+     * @param s the PrintStream to send the XML to.
+     */
+    public void toXml(PrintStream s) {
+
+        s.println("<!--");
+        s.println("  -- Round Robin RRDatabase Dump ");
+        s.println("  -- Generated by jRRD <ciaran@codeloop.com>");
+        s.println("  -->");
+        s.println("<rrd>");
+        s.print("\t<version> ");
+        s.print(header.version);
+        s.println(" </version>");
+        s.print("\t<step> ");
+        s.print(header.pdpStep);
+        s.println(" </step> <!-- Seconds -->");
+        s.print("\t<lastupdate> ");
+        s.print(lastUpdate.getTime() / 1000);
+        s.print(" </lastupdate> <!-- ");