From: DJ G. <djg...@us...> - 2005-09-06 21:27:16
|
Update of /cvsroot/jrobin/src/org/jrobin/core/jrrd In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv15741/src/org/jrobin/core/jrrd Added Files: Tag: release-1-5-2 Archive.java CDPStatusBlock.java ConsolidationFunctionType.java Constants.java DataChunk.java DataSource.java DataSourceType.java Header.java PDPStatusBlock.java RRDException.java RRDFile.java RRDatabase.java Log Message: fix import conflicts, take two; anyone want to take bets on whether or not I'm done? --- NEW FILE: DataSourceType.java --- /* * Copyright (C) 2001 Ciaran Treanor <ci...@co...> * * Distributable under GPL license. * See terms of license at gnu.org. * * $Id: DataSourceType.java,v 1.1.4.2 2005/09/06 21:27:04 djgregor Exp $ */ package org.jrobin.core.jrrd; /** * Class DataSourceType * * @author <a href="mailto:ci...@co...">Ciaran Treanor</a> * @version $Revision: 1.1.4.2 $ */ 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; } } --- NEW FILE: ConsolidationFunctionType.java --- /* * Copyright (C) 2001 Ciaran Treanor <ci...@co...> * * Distributable under GPL license. * See terms of license at gnu.org. * * $Id: ConsolidationFunctionType.java,v 1.1.4.2 2005/09/06 21:27:04 djgregor Exp $ */ package org.jrobin.core.jrrd; /** * Class ConsolidationFunctionType * * @author <a href="mailto:ci...@co...">Ciaran Treanor</a> * @version $Revision: 1.1.4.2 $ */ 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; } } --- NEW FILE: RRDException.java --- /* * Copyright (C) 2001 Ciaran Treanor <ci...@co...> * * Distributable under GPL license. * See terms of license at gnu.org. * * $Id: RRDException.java,v 1.1.4.2 2005/09/06 21:27:04 djgregor Exp $ */ package org.jrobin.core.jrrd; /** * This exception may be throw if an error occurs while operating * on an RRD object. * * @author <a href="mailto:ci...@co...">Ciaran Treanor</a> * @version $Revision: 1.1.4.2 $ */ public class RRDException extends Exception { /** * Constructs an RRDException with no detail message. */ public RRDException() { super(); } /** * Constructs an RRDException with the specified detail message. */ public RRDException(String message) { super(message); } } --- NEW FILE: Header.java --- /* * Copyright (C) 2001 Ciaran Treanor <ci...@co...> * * Distributable under GPL license. * See terms of license at gnu.org. * * $Id: Header.java,v 1.1.4.2 2005/09/06 21:27:04 djgregor Exp $ */ package org.jrobin.core.jrrd; import java.io.IOException; /** * Instances of this class model the header section of an RRD file. * * @author <a href="mailto:ci...@co...">Ciaran Treanor</a> * @version $Revision: 1.1.4.2 $ */ 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() { StringBuffer sb = new StringBuffer("[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(); } } --- NEW FILE: Archive.java --- /* * Copyright (C) 2001 Ciaran Treanor <ci...@co...> * * Distributable under GPL license. * See terms of license at gnu.org. * * $Id: Archive.java,v 1.2.2.2 2005/09/06 21:27:04 djgregor Exp $ */ 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; /** * Instances of this class model an archive section of an RRD file. * * @author <a href="mailto:ci...@co...">Ciaran Treanor</a> * @version $Revision: 1.2.2.2 $ */ 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) { 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) { 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() { 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(); } } --- NEW FILE: RRDFile.java --- /* * Copyright (C) 2001 Ciaran Treanor <ci...@co...> * * Distributable under GPL license. * See terms of license at gnu.org. * * $Id: RRDFile.java,v 1.1.4.2 2005/09/06 21:27:04 djgregor Exp $ */ package org.jrobin.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:ci...@co...">Ciaran Treanor</a> * @version $Revision: 1.1.4.2 $ */ 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(); } } --- NEW FILE: Constants.java --- /* * Copyright (C) 2001 Ciaran Treanor <ci...@co...> * * Distributable under GPL license. * See terms of license at gnu.org. * * $Id: Constants.java,v 1.1.4.2 2005/09/06 21:27:04 djgregor Exp $ */ package org.jrobin.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}; } --- NEW FILE: CDPStatusBlock.java --- /* * Copyright (C) 2001 Ciaran Treanor <ci...@co...> * * Distributable under GPL license. * See terms of license at gnu.org. * * $Id: CDPStatusBlock.java,v 1.1.4.2 2005/09/06 21:27:04 djgregor Exp $ */ package org.jrobin.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:ci...@co...">Ciaran Treanor</a> * @version $Revision: 1.1.4.2 $ */ 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() { StringBuffer sb = new StringBuffer("[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(); } } --- NEW FILE: RRDatabase.java --- /* * Copyright (C) 2001 Ciaran Treanor <ci...@co...> * * Distributable under GPL license. * See terms of license at gnu.org. * * $Id: RRDatabase.java,v 1.2.4.2 2005/09/06 21:27:04 djgregor Exp $ */ package org.jrobin.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:ci...@co...">Ciaran Treanor</a> * @version $Revision: 1.2.4.2 $ */ 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 (int i = 0; i < archives.size(); i++) { Archive archive = archives.get(i); 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 RRDException 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 RRDException, 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 RRDException 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 RRDException, IOException { ArrayList<Archive> possibleArchives = getArchiveList(type); if (possibleArchives.size() == 0) { throw new RRDException("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 (int i = 0; i < archives.size(); i++) { archive = archives.get(i); 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 (Iterator<DataSource> i = dataSources.iterator(); i.hasNext();) { DataSource ds = i.next(); ds.printInfo(s, numberFormat); } int index = 0; for (Iterator<Archive> i = archives.iterator(); i.hasNext();) { Archive archive = i.next(); 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 <ci...@co...>"); 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> <!-- "); s.print(lastUpdate.toString()); s.println(" -->"); s.println(); for (int i = 0; i < header.dsCount; i++) { DataSource ds = dataSources.get(i); ds.toXml(s); } s.println("<!-- Round Robin Archives -->"); for (int i = 0; i < header.rraCount; i++) { Archive archive = archives.get(i); archive.toXml(s); } s.println("</rrd>"); } /** * Returns a summary the contents of this database. * * @return a summary of the information contained in this database. */ public String toString() { StringBuffer sb = new StringBuffer("\n"); sb.append(header.toString()); for (Iterator<DataSource> i = dataSources.iterator(); i.hasNext();) { DataSource ds = i.next(); sb.append("\n\t"); sb.append(ds.toString()); } for (Iterator<Archive> i = archives.iterator(); i.hasNext();) { Archive archive = i.next(); sb.append("\n\t"); sb.append(archive.toString()); } return sb.toString(); } } --- NEW FILE: DataSource.java --- /* * Copyright (C) 2001 Ciaran Treanor <ci...@co...> * * Distributable under GPL license. * See terms of license at gnu.org. * * $Id: DataSource.java,v 1.1.4.2 2005/09/06 21:27:04 djgregor Exp $ */ package org.jrobin.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:ci...@co...">Ciaran Treanor</a> * @version $Revision: 1.1.4.2 $ */ 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) { StringBuffer sb = new StringBuffer("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() { StringBuffer sb = new StringBuffer("[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(); } } --- NEW FILE: PDPStatusBlock.java --- /* * Copyright (C) 2001 Ciaran Treanor <ci...@co...> * * Distributable under GPL license. * See terms of license at gnu.org. * * $Id: PDPStatusBlock.java,v 1.1.4.2 2005/09/06 21:27:04 djgregor Exp $ */ package org.jrobin.core.jrrd; import java.io.IOException; /** * Instances of this class model the primary data point status from an RRD file. * * @author <a href="mailto:ci...@co...">Ciaran Treanor</a> * @version $Revision: 1.1.4.2 $ */ 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() { StringBuffer sb = new StringBuffer("[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(); } } --- NEW FILE: DataChunk.java --- /* * Copyright (C) 2001 Ciaran Treanor <ci...@co...> * * Distributable under GPL license. * See terms of license at gnu.org. * * $Id: DataChunk.java,v 1.2.2.2 2005/09/06 21:27:04 djgregor Exp $ */ package org.jrobin.core.jrrd; /** * Models a chunk of result data from an RRDatabase. * * @author <a href="mailto:ci...@co...">Ciaran Treanor</a> * @version $Revision: 1.2.2.2 $ */ 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() { StringBuffer sb = new StringBuffer(); 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(); } } |