001    /*
002     * Copyright (C) 2001 Ciaran Treanor <ciaran@codeloop.com>
003     *
004     * Distributable under GPL license.
005     * See terms of license at gnu.org.
006     *
007     * $Id: RRDatabase.java,v 1.3 2006/12/21 18:02:42 tarus Exp $
008     */
009    package org.jrobin.core.jrrd;
010    
011    import java.io.File;
012    import java.io.IOException;
013    import java.io.PrintStream;
014    import java.text.DecimalFormat;
015    import java.text.NumberFormat;
016    import java.util.ArrayList;
017    import java.util.Calendar;
018    import java.util.Date;
019    import java.util.Iterator;
020    
021    /**
022     * Instances of this class model
023     * <a href="http://people.ee.ethz.ch/~oetiker/webtools/rrdtool/">Round Robin Database</a>
024     * (RRD) files.
025     *
026     * @author <a href="mailto:ciaran@codeloop.com">Ciaran Treanor</a>
027     * @version $Revision: 1.3 $
028     */
029    public class RRDatabase {
030    
031            RRDFile rrdFile;
032    
033            // RRD file name
034            private String name;
035            Header header;
036            ArrayList<DataSource> dataSources;
037            ArrayList<Archive> archives;
038            Date lastUpdate;
039    
040            /**
041             * Creates a database to read from.
042             *
043             * @param name the filename of the file to read from.
044             * @throws IOException if an I/O error occurs.
045             */
046            public RRDatabase(String name) throws IOException {
047                    this(new File(name));
048            }
049    
050            /**
051             * Creates a database to read from.
052             *
053             * @param file the file to read from.
054             * @throws IOException if an I/O error occurs.
055             */
056            public RRDatabase(File file) throws IOException {
057    
058                    name = file.getName();
059                    rrdFile = new RRDFile(file);
060                    header = new Header(rrdFile);
061    
062                    // Load the data sources
063                    dataSources = new ArrayList<DataSource>();
064    
065                    for (int i = 0; i < header.dsCount; i++) {
066                            DataSource ds = new DataSource(rrdFile);
067    
068                            dataSources.add(ds);
069                    }
070    
071                    // Load the archives
072                    archives = new ArrayList<Archive>();
073    
074                    for (int i = 0; i < header.rraCount; i++) {
075                            Archive archive = new Archive(this);
076    
077                            archives.add(archive);
078                    }
079    
080                    rrdFile.align();
081    
082                    lastUpdate = new Date((long) (rrdFile.readInt()) * 1000);
083    
084                    // Load PDPStatus(s)
085                    for (int i = 0; i < header.dsCount; i++) {
086                            DataSource ds = dataSources.get(i);
087    
088                            ds.loadPDPStatusBlock(rrdFile);
089                    }
090    
091                    // Load CDPStatus(s)
092                    for (int i = 0; i < header.rraCount; i++) {
093                            Archive archive = archives.get(i);
094    
095                            archive.loadCDPStatusBlocks(rrdFile, header.dsCount);
096                    }
097    
098                    // Load current row information for each archive
099                    for (int i = 0; i < header.rraCount; i++) {
100                            Archive archive = archives.get(i);
101    
102                            archive.loadCurrentRow(rrdFile);
103                    }
104    
105                    // Now load the data
106                    for (int i = 0; i < header.rraCount; i++) {
107                            Archive archive = archives.get(i);
108    
109                            archive.loadData(rrdFile, header.dsCount);
110                    }
111            }
112    
113            /**
114             * Returns the <code>Header</code> for this database.
115             *
116             * @return the <code>Header</code> for this database.
117             */
118            public Header getHeader() {
119                    return header;
120            }
121    
122            /**
123             * Returns the date this database was last updated. To convert this date to
124             * the form returned by <code>rrdtool last</code> call Date.getTime() and
125             * divide the result by 1000.
126             *
127             * @return the date this database was last updated.
128             */
129            public Date getLastUpdate() {
130                    return lastUpdate;
131            }
132    
133            /**
134             * Returns the <code>DataSource</code> at the specified position in this database.
135             *
136             * @param index index of <code>DataSource</code> to return.
137             * @return the <code>DataSource</code> at the specified position in this database
138             */
139            public DataSource getDataSource(int index) {
140                    return dataSources.get(index);
141            }
142    
143            /**
144             * Returns an iterator over the data sources in this database in proper sequence.
145             *
146             * @return an iterator over the data sources in this database in proper sequence.
147             */
148            public Iterator<DataSource> getDataSources() {
149                    return dataSources.iterator();
150            }
151    
152            /**
153             * Returns the <code>Archive</code> at the specified position in this database.
154             *
155             * @param index index of <code>Archive</code> to return.
156             * @return the <code>Archive</code> at the specified position in this database.
157             */
158            public Archive getArchive(int index) {
159                    return archives.get(index);
160            }
161    
162            /**
163             * Returns an iterator over the archives in this database in proper sequence.
164             *
165             * @return an iterator over the archives in this database in proper sequence.
166             */
167            public Iterator<Archive> getArchives() {
168                    return archives.iterator();
169            }
170    
171            /**
172             * Returns the number of archives in this database.
173             *
174             * @return the number of archives in this database.
175             */
176            public int getNumArchives() {
177                    return header.rraCount;
178            }
179    
180            /**
181             * Returns an iterator over the archives in this database of the given type
182             * in proper sequence.
183             *
184             * @param type the consolidation function that should have been applied to
185             *             the data.
186             * @return an iterator over the archives in this database of the given type
187             *         in proper sequence.
188             */
189            public Iterator<Archive> getArchives(ConsolidationFunctionType type) {
190                    return getArchiveList(type).iterator();
191            }
192    
193            ArrayList<Archive> getArchiveList(ConsolidationFunctionType type) {
194    
195                    ArrayList<Archive> subset = new ArrayList<Archive>();
196    
197                    for (int i = 0; i < archives.size(); i++) {
198                            Archive archive = archives.get(i);
199    
200                            if (archive.getType().equals(type)) {
201                                    subset.add(archive);
202                            }
203                    }
204    
205                    return subset;
206            }
207    
208            /**
209             * Closes this database stream and releases any associated system resources.
210             *
211             * @throws IOException if an I/O error occurs.
212             */
213            public void close() throws IOException {
214                    rrdFile.close();
215            }
216    
217            /**
218             * Outputs the header information of the database to the given print stream
219             * using the default number format. The default format for <code>double</code>
220             * is 0.0000000000E0.
221             *
222             * @param s the PrintStream to print the header information to.
223             */
224            public void printInfo(PrintStream s) {
225    
226                    NumberFormat numberFormat = new DecimalFormat("0.0000000000E0");
227    
228                    printInfo(s, numberFormat);
229            }
230    
231            /**
232             * Returns data from the database corresponding to the given consolidation
233             * function and a step size of 1.
234             *
235             * @param type the consolidation function that should have been applied to
236             *             the data.
237             * @return the raw data.
238             * @throws RRDException if there was a problem locating a data archive with
239             *                      the requested consolidation function.
240             * @throws IOException  if there was a problem reading data from the database.
241             */
242            public DataChunk getData(ConsolidationFunctionType type)
243                            throws RRDException, IOException {
244                    return getData(type, 1L);
245            }
246    
247            /**
248             * Returns data from the database corresponding to the given consolidation
249             * function.
250             *
251             * @param type the consolidation function that should have been applied to
252             *             the data.
253             * @param step the step size to use.
254             * @return the raw data.
255             * @throws RRDException if there was a problem locating a data archive with
256             *                      the requested consolidation function.
257             * @throws IOException  if there was a problem reading data from the database.
258             */
259            public DataChunk getData(ConsolidationFunctionType type, long step)
260                            throws RRDException, IOException {
261    
262                    ArrayList<Archive> possibleArchives = getArchiveList(type);
263    
264                    if (possibleArchives.size() == 0) {
265                            throw new RRDException("Database does not contain an Archive of consolidation function type "
266                                            + type);
267                    }
268    
269                    Calendar endCal = Calendar.getInstance();
270    
271                    endCal.set(Calendar.MILLISECOND, 0);
272    
273                    Calendar startCal = (Calendar) endCal.clone();
274    
275                    startCal.add(Calendar.DATE, -1);
276    
277                    long end = endCal.getTime().getTime() / 1000;
278                    long start = startCal.getTime().getTime() / 1000;
279                    Archive archive = findBestArchive(start, end, step, possibleArchives);
280    
281                    // Tune the parameters
282                    step = header.pdpStep * archive.pdpCount;
283                    start -= start % step;
284    
285                    if (end % step != 0) {
286                            end += step - end % step;
287                    }
288    
289                    int rows = (int) ((end - start) / step + 1);
290    
291                    //cat.debug("start " + start + " end " + end + " step " + step + " rows "
292                    //          + rows);
293    
294                    // Find start and end offsets
295                    // This is terrible - some of this should be encapsulated in Archive - CT.
296                    long lastUpdateLong = lastUpdate.getTime() / 1000;
297                    long archiveEndTime = lastUpdateLong - (lastUpdateLong % step);
298                    long archiveStartTime = archiveEndTime - (step * (archive.rowCount - 1));
299                    int startOffset = (int) ((start - archiveStartTime) / step);
300                    int endOffset = (int) ((archiveEndTime - end) / step);
301    
302                    //cat.debug("start " + archiveStartTime + " end " + archiveEndTime
303                    //          + " startOffset " + startOffset + " endOffset "
304                    //          + (archive.rowCount - endOffset));
305    
306                    DataChunk chunk = new DataChunk(start, startOffset, endOffset, step,
307                                    header.dsCount, rows);
308    
309                    archive.loadData(chunk);
310    
311                    return chunk;
312            }
313    
314            /*
315             * This is almost a verbatim copy of the original C code by Tobias Oetiker.
316             * I need to put more of a Java style on it - CT
317             */
318            private Archive findBestArchive(long start, long end, long step,
319                                                                            ArrayList<Archive> archives) {
320    
321                    Archive archive = null;
322                    Archive bestFullArchive = null;
323                    Archive bestPartialArchive = null;
324                    long lastUpdateLong = lastUpdate.getTime() / 1000;
325                    int firstPart = 1;
326                    int firstFull = 1;
327                    long bestMatch = 0;
328                    //long bestPartRRA = 0;
329                    long bestStepDiff = 0;
330                    long tmpStepDiff = 0;
331    
332                    for (int i = 0; i < archives.size(); i++) {
333                            archive = archives.get(i);
334    
335                            long calEnd = lastUpdateLong
336                                            - (lastUpdateLong
337                                            % (archive.pdpCount * header.pdpStep));
338                            long calStart = calEnd
339                                            - (archive.pdpCount * archive.rowCount
340                                            * header.pdpStep);
341                            long fullMatch = end - start;
342    
343                            if ((calEnd >= end) && (calStart < start)) {      // Best full match
344                                    tmpStepDiff = Math.abs(step - (header.pdpStep * archive.pdpCount));
345    
346                                    if ((firstFull != 0) || (tmpStepDiff < bestStepDiff)) {
347                                            firstFull = 0;
348                                            bestStepDiff = tmpStepDiff;
349                                            bestFullArchive = archive;
350                                    }
351                            }
352                            else {                                                                          // Best partial match
353                                    long tmpMatch = fullMatch;
354    
355                                    if (calStart > start) {
356                                            tmpMatch -= calStart - start;
357                                    }
358    
359                                    if (calEnd < end) {
360                                            tmpMatch -= end - calEnd;
361                                    }
362    
363                                    if ((firstPart != 0) || (bestMatch < tmpMatch)) {
364                                            firstPart = 0;
365                                            bestMatch = tmpMatch;
366                                            bestPartialArchive = archive;
367                                    }
368                            }
369                    }
370    
371                    // See how the matching went
372                    // optimise this
373                    if (firstFull == 0) {
374                            archive = bestFullArchive;
375                    }
376                    else if (firstPart == 0) {
377                            archive = bestPartialArchive;
378                    }
379    
380                    return archive;
381            }
382    
383            /**
384             * Outputs the header information of the database to the given print stream
385             * using the given number format. The format is almost identical to that
386             * produced by
387             * <a href="http://people.ee.ethz.ch/~oetiker/webtools/rrdtool/manual/rrdinfo.html">rrdtool info</a>
388             *
389             * @param s                     the PrintStream to print the header information to.
390             * @param numberFormat the format to print <code>double</code>s as.
391             */
392            public void printInfo(PrintStream s, NumberFormat numberFormat) {
393    
394                    s.print("filename = \"");
395                    s.print(name);
396                    s.println("\"");
397                    s.print("rrd_version = \"");
398                    s.print(header.version);
399                    s.println("\"");
400                    s.print("step = ");
401                    s.println(header.pdpStep);
402                    s.print("last_update = ");
403                    s.println(lastUpdate.getTime() / 1000);
404    
405                    for (Iterator<DataSource> i = dataSources.iterator(); i.hasNext();) {
406                            DataSource ds = i.next();
407    
408                            ds.printInfo(s, numberFormat);
409                    }
410    
411                    int index = 0;
412    
413                    for (Iterator<Archive> i = archives.iterator(); i.hasNext();) {
414                            Archive archive = i.next();
415    
416                            archive.printInfo(s, numberFormat, index++);
417                    }
418            }
419    
420            /**
421             * Outputs the content of the database to the given print stream
422             * as a stream of XML. The XML format is almost identical to that produced by
423             * <a href="http://people.ee.ethz.ch/~oetiker/webtools/rrdtool/manual/rrddump.html">rrdtool dump</a>
424             *
425             * @param s the PrintStream to send the XML to.
426             */
427            public void toXml(PrintStream s) {
428    
429                    s.println("<!--");
430                    s.println("  -- Round Robin RRDatabase Dump ");
431                    s.println("  -- Generated by jRRD <ciaran@codeloop.com>");
432                    s.println("  -->");
433                    s.println("<rrd>");
434                    s.print("\t<version> ");
435                    s.print(header.version);
436                    s.println(" </version>");
437                    s.print("\t<step> ");
438                    s.print(header.pdpStep);
439                    s.println(" </step> <!-- Seconds -->");
440                    s.print("\t<lastupdate> ");
441                    s.print(lastUpdate.getTime() / 1000);
442                    s.print(" </lastupdate> <!-- ");
443                    s.print(lastUpdate.toString());
444                    s.println(" -->");
445                    s.println();
446    
447                    for (int i = 0; i < header.dsCount; i++) {
448                            DataSource ds = dataSources.get(i);
449    
450                            ds.toXml(s);
451                    }
452    
453                    s.println("<!-- Round Robin Archives -->");
454    
455                    for (int i = 0; i < header.rraCount; i++) {
456                            Archive archive = archives.get(i);
457    
458                            archive.toXml(s);
459                    }
460    
461                    s.println("</rrd>");
462            }
463    
464            /**
465             * Returns a summary the contents of this database.
466             *
467             * @return a summary of the information contained in this database.
468             */
469            public String toString() {
470    
471                    StringBuffer sb = new StringBuffer("\n");
472    
473                    sb.append(header.toString());
474    
475                    for (Iterator<DataSource> i = dataSources.iterator(); i.hasNext();) {
476                            DataSource ds = i.next();
477    
478                            sb.append("\n\t");
479                            sb.append(ds.toString());
480                    }
481    
482                    for (Iterator<Archive> i = archives.iterator(); i.hasNext();) {
483                            Archive archive = i.next();
484    
485                            sb.append("\n\t");
486                            sb.append(archive.toString());
487                    }
488    
489                    return sb.toString();
490            }
491    }