001    /* ============================================================
002     * JRobin : Pure java implementation of RRDTool's functionality
003     * ============================================================
004     *
005     * Project Info:  http://www.jrobin.org
006     * Project Lead:  Sasa Markovic (saxon@jrobin.org);
007     *
008     * (C) Copyright 2003-2005, by Sasa Markovic.
009     *
010     * Developers:    Sasa Markovic (saxon@jrobin.org)
011     *
012     *
013     * This library is free software; you can redistribute it and/or modify it under the terms
014     * of the GNU Lesser General Public License as published by the Free Software Foundation;
015     * either version 2.1 of the License, or (at your option) any later version.
016     *
017     * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
018     * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
019     * See the GNU Lesser General Public License for more details.
020     *
021     * You should have received a copy of the GNU Lesser General Public License along with this
022     * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
023     * Boston, MA 02111-1307, USA.
024     */
025    
026    package org.jrobin.core;
027    
028    import java.io.*;
029    import java.util.Date;
030    
031    /**
032     * <p>Main class used to create and manipulate round robin databases (RRDs). Use this class to perform
033     * update and fetch operations on exisiting RRDs, to create new RRD from
034     * the definition (object of class {@link org.jrobin.core.RrdDef RrdDef}) or
035     * from XML file (dumped content of RRDTool's or JRobin's RRD file).</p>
036     * <p/>
037     * <p>Each RRD is backed with some kind of storage. For example, RRDTool supports only one kind of
038     * storage (disk file). On the contrary, JRobin gives you freedom to use other storage (backend) types
039     * even to create your own backend types for some special purposes. JRobin by default stores
040     * RRD data in files (as RRDTool), but you might choose to store RRD data in memory (this is
041     * supported in JRobin), to use java.nio.* instead of java.io.* package for file manipulation
042     * (also supported) or to store whole RRDs in the SQL database
043     * (you'll have to extend some classes to do this).</p>
044     * <p/>
045     * <p>Note that JRobin uses binary format different from RRDTool's format. You cannot
046     * use this class to manipulate RRD files created with RRDTool. <b>However, if you perform
047     * the same sequence of create, update and fetch operations, you will get exactly the same
048     * results from JRobin and RRDTool.</b><p>
049     * <p/>
050     * <p/>
051     * You will not be able to use JRobin API if you are not familiar with
052     * basic RRDTool concepts. Good place to start is the
053     * <a href="http://people.ee.ethz.ch/~oetiker/webtools/rrdtool/tutorial/rrdtutorial.html">official RRD tutorial</a>
054     * and relevant RRDTool man pages: <a href="../../../../man/rrdcreate.html" target="man">rrdcreate</a>,
055     * <a href="../../../../man/rrdupdate.html" target="man">rrdupdate</a>,
056     * <a href="../../../../man/rrdfetch.html" target="man">rrdfetch</a> and
057     * <a href="../../../../man/rrdgraph.html" target="man">rrdgraph</a>.
058     * For RRDTool's advanced graphing capabilities (RPN extensions), also supported in JRobin,
059     * there is an excellent
060     * <a href="http://people.ee.ethz.ch/~oetiker/webtools/rrdtool/tutorial/cdeftutorial.html" target="man">CDEF tutorial</a>.
061     * </p>
062     *
063     * @see RrdBackend
064     * @see RrdBackendFactory
065     */
066    public class RrdDb implements RrdUpdater {
067            /**
068             * prefix to identify external XML file source used in various RrdDb constructors
069             */
070            public static final String PREFIX_XML = "xml:/";
071            /**
072             * prefix to identify external RRDTool file source used in various RrdDb constructors
073             */
074            public static final String PREFIX_RRDTool = "rrdtool:/";
075    
076            // static final String RRDTOOL = "rrdtool";
077            static final int XML_INITIAL_BUFFER_CAPACITY = 100000; // bytes
078    
079            private RrdBackend backend;
080            private RrdAllocator allocator = new RrdAllocator();
081    
082            private Header header;
083            private Datasource[] datasources;
084            private Archive[] archives;
085    
086            private boolean closed = false;
087    
088            /**
089             * <p>Constructor used to create new RRD object from the definition. This RRD object will be backed
090             * with a storage (backend) of the default type. Initially, storage type defaults to "NIO"
091             * (RRD bytes will be put in a file on the disk). Default storage type can be changed with a static
092             * {@link RrdBackendFactory#setDefaultFactory(String)} method call.</p>
093             * <p/>
094             * <p>New RRD file structure is specified with an object of class
095             * {@link org.jrobin.core.RrdDef <b>RrdDef</b>}. The underlying RRD storage is created as soon
096             * as the constructor returns.</p>
097             * <p/>
098             * <p>Typical scenario:</p>
099             * <p/>
100             * <pre>
101             * // create new RRD definition
102             * RrdDef def = new RrdDef("test.rrd", 300);
103             * def.addDatasource("input", DsTypes.DT_COUNTER, 600, 0, Double.NaN);
104             * def.addDatasource("output", DsTypes.DT_COUNTER, 600, 0, Double.NaN);
105             * def.addArchive(ConsolFuns.CF_AVERAGE, 0.5, 1, 600);
106             * def.addArchive(ConsolFuns.CF_AVERAGE, 0.5, 6, 700);
107             * def.addArchive(ConsolFuns.CF_AVERAGE, 0.5, 24, 797);
108             * def.addArchive(ConsolFuns.CF_AVERAGE, 0.5, 288, 775);
109             * def.addArchive(ConsolFuns.CF_MAX, 0.5, 1, 600);
110             * def.addArchive(ConsolFuns.CF_MAX, 0.5, 6, 700);
111             * def.addArchive(ConsolFuns.CF_MAX, 0.5, 24, 797);
112             * def.addArchive(ConsolFuns.CF_MAX, 0.5, 288, 775);
113             * <p/>
114             * // RRD definition is now completed, create the database!
115             * RrdDb rrd = new RrdDb(def);
116             * // new RRD file has been created on your disk
117             * </pre>
118             *
119             * @param rrdDef Object describing the structure of the new RRD file.
120             * @throws IOException  Thrown in case of I/O error.
121             * @throws RrdException Thrown if invalid RrdDef object is supplied.
122             */
123            public RrdDb(RrdDef rrdDef) throws RrdException, IOException {
124                    this(rrdDef, RrdFileBackendFactory.getDefaultFactory());
125            }
126    
127            /**
128             * <p>Constructor used to create new RRD object from the definition object but with a storage
129             * (backend) different from default.</p>
130             * <p/>
131             * <p>JRobin uses <i>factories</i> to create RRD backend objecs. There are three different
132             * backend factories supplied with JRobin, and each factory has its unique name:</p>
133             * <p/>
134             * <ul>
135             * <li><b>FILE</b>: backends created from this factory will store RRD data to files by using
136             * java.io.* classes and methods
137             * <li><b>NIO</b>: backends created from this factory will store RRD data to files by using
138             * java.nio.* classes and methods
139             * <li><b>MEMORY</b>: backends created from this factory will store RRD data in memory. This might
140             * be useful in runtime environments which prohibit disk utilization, or for storing temporary,
141             * non-critical data (it gets lost as soon as JVM exits).
142             * </ul>
143             * <p/>
144             * <p>For example, to create RRD in memory, use the following code</p>
145             * <pre>
146             * RrdBackendFactory factory = RrdBackendFactory.getFactory("MEMORY");
147             * RrdDb rrdDb = new RrdDb(rrdDef, factory);
148             * rrdDb.close();
149             * </pre>
150             * <p/>
151             * <p>New RRD file structure is specified with an object of class
152             * {@link org.jrobin.core.RrdDef <b>RrdDef</b>}. The underlying RRD storage is created as soon
153             * as the constructor returns.</p>
154             *
155             * @param rrdDef  RRD definition object
156             * @param factory The factory which will be used to create storage for this RRD
157             * @throws RrdException Thrown if invalid factory or definition is supplied
158             * @throws IOException  Thrown in case of I/O error
159             * @see RrdBackendFactory
160             */
161            public RrdDb(RrdDef rrdDef, RrdBackendFactory factory) throws RrdException, IOException {
162                    rrdDef.validate();
163                    String path = rrdDef.getPath();
164                    backend = factory.open(path, false);
165                    try {
166                            backend.setLength(rrdDef.getEstimatedSize());
167                            // create header
168                            header = new Header(this, rrdDef);
169                            // create datasources
170                            DsDef[] dsDefs = rrdDef.getDsDefs();
171                            datasources = new Datasource[dsDefs.length];
172                            for (int i = 0; i < dsDefs.length; i++) {
173                                    datasources[i] = new Datasource(this, dsDefs[i]);
174                            }
175                            // create archives
176                            ArcDef[] arcDefs = rrdDef.getArcDefs();
177                            archives = new Archive[arcDefs.length];
178                            for (int i = 0; i < arcDefs.length; i++) {
179                                    archives[i] = new Archive(this, arcDefs[i]);
180                            }
181                    }
182                    catch (IOException e) {
183                            backend.close();
184                            throw e;
185                    }
186            }
187    
188            /**
189             * <p>Constructor used to open already existing RRD. This RRD object will be backed
190             * with a storage (backend) of the default type (file on the disk). Constructor
191             * obtains read or read/write access to this RRD.</p>
192             *
193             * @param path   Path to existing RRD.
194             * @param readOnly Should be set to <code>false</code> if you want to update
195             *                 the underlying RRD. If you want just to fetch data from the RRD file
196             *                 (read-only access), specify <code>true</code>. If you try to update RRD file
197             *                 open in read-only mode (<code>readOnly</code> set to <code>true</code>),
198             *                 <code>IOException</code> will be thrown.
199             * @throws IOException  Thrown in case of I/O error.
200             * @throws RrdException Thrown in case of JRobin specific error.
201             */
202            public RrdDb(String path, boolean readOnly) throws IOException, RrdException {
203                    this(path, readOnly, RrdBackendFactory.getDefaultFactory());
204            }
205    
206            /**
207             * <p>Constructor used to open already existing RRD backed
208             * with a storage (backend) different from default. Constructor
209             * obtains read or read/write access to this RRD.</p>
210             *
211             * @param path   Path to existing RRD.
212             * @param readOnly Should be set to <code>false</code> if you want to update
213             *                 the underlying RRD. If you want just to fetch data from the RRD file
214             *                 (read-only access), specify <code>true</code>. If you try to update RRD file
215             *                 open in read-only mode (<code>readOnly</code> set to <code>true</code>),
216             *                 <code>IOException</code> will be thrown.
217             * @param factory  Backend factory which will be used for this RRD.
218             * @throws FileNotFoundException Thrown if the requested file does not exist.
219             * @throws IOException             Thrown in case of general I/O error (bad RRD file, for example).
220             * @throws RrdException           Thrown in case of JRobin specific error.
221             * @see RrdBackendFactory
222             */
223            public RrdDb(String path, boolean readOnly, RrdBackendFactory factory)
224                            throws FileNotFoundException, IOException, RrdException {
225                    // opens existing RRD file - throw exception if the file does not exist...
226                    if (!factory.exists(path)) {
227                            throw new FileNotFoundException("Could not open " + path + " [non existent]");
228                    }
229                    backend = factory.open(path, readOnly);
230                    try {
231                            // restore header
232                            header = new Header(this, (RrdDef) null);
233                            header.validateHeader();
234                            // restore datasources
235                            int dsCount = header.getDsCount();
236                            datasources = new Datasource[dsCount];
237                            for (int i = 0; i < dsCount; i++) {
238                                    datasources[i] = new Datasource(this, null);
239                            }
240                            // restore archives
241                            int arcCount = header.getArcCount();
242                            archives = new Archive[arcCount];
243                            for (int i = 0; i < arcCount; i++) {
244                                    archives[i] = new Archive(this, null);
245                            }
246                    }
247                    catch (RrdException e) {
248                            backend.close();
249                            throw e;
250                    }
251                    catch (IOException e) {
252                            backend.close();
253                            throw e;
254                    }
255            }
256    
257            /**
258             * <p>Constructor used to open already existing RRD in R/W mode, with a default storage
259             * (backend) type (file on the disk).
260             *
261             * @param path Path to existing RRD.
262             * @throws IOException  Thrown in case of I/O error.
263             * @throws RrdException Thrown in case of JRobin specific error.
264             */
265            public RrdDb(String path) throws IOException, RrdException {
266                    this(path, false);
267            }
268    
269            /**
270             * <p>Constructor used to open already existing RRD in R/W mode with a storage (backend) type
271             * different from default.</p>
272             *
273             * @param path  Path to existing RRD.
274             * @param factory Backend factory used to create this RRD.
275             * @throws IOException  Thrown in case of I/O error.
276             * @throws RrdException Thrown in case of JRobin specific error.
277             * @see RrdBackendFactory
278             */
279            public RrdDb(String path, RrdBackendFactory factory) throws IOException, RrdException {
280                    this(path, false, factory);
281            }
282    
283            /**
284             * <p>Constructor used to create RRD files from external file sources.
285             * Supported external file sources are:</p>
286             * <p/>
287             * <ul>
288             * <li>RRDTool/JRobin XML file dumps (i.e files created with <code>rrdtool dump</code> command).
289             * <li>RRDTool binary files.
290             * </ul>
291             * <p/>
292             * <p>Newly created RRD will be backed with a default storage (backend) type
293             * (file on the disk).</p>
294             * <p/>
295             * <p>JRobin and RRDTool use the same format for XML dump and this constructor should be used to
296             * (re)create JRobin RRD files from XML dumps. First, dump the content of a RRDTool
297             * RRD file (use command line):</p>
298             * <p/>
299             * <pre>
300             * rrdtool dump original.rrd > original.xml
301             * </pre>
302             * <p/>
303             * <p>Than, use the file <code>original.xml</code> to create JRobin RRD file named
304             * <code>copy.rrd</code>:</p>
305             * <p/>
306             * <pre>
307             * RrdDb rrd = new RrdDb("copy.rrd", "original.xml");
308             * </pre>
309             * <p/>
310             * <p>or:</p>
311             * <p/>
312             * <pre>
313             * RrdDb rrd = new RrdDb("copy.rrd", "xml:/original.xml");
314             * </pre>
315             * <p/>
316             * <p>See documentation for {@link #dumpXml(java.lang.String) dumpXml()} method
317             * to see how to convert JRobin files to RRDTool's format.</p>
318             * <p/>
319             * <p>To read RRDTool files directly, specify <code>rrdtool:/</code> prefix in the
320             * <code>externalPath</code> argument. For example, to create JRobin compatible file named
321             * <code>copy.rrd</code> from the file <code>original.rrd</code> created with RRDTool, use
322             * the following code:</p>
323             * <p/>
324             * <pre>
325             * RrdDb rrd = new RrdDb("copy.rrd", "rrdtool:/original.rrd");
326             * </pre>
327             * <p/>
328             * <p>Note that the prefix <code>xml:/</code> or <code>rrdtool:/</code> is necessary to distinguish
329             * between XML and RRDTool's binary sources. If no prefix is supplied, XML format is assumed</p>
330             *
331             * @param rrdPath         Path to a RRD file which will be created
332             * @param externalPath Path to an external file which should be imported, with an optional
333             *                     <code>xml:/</code> or <code>rrdtool:/</code> prefix.
334             * @throws IOException  Thrown in case of I/O error
335             * @throws RrdException Thrown in case of JRobin specific error
336             */
337            public RrdDb(String rrdPath, String externalPath) throws IOException, RrdException {
338                    this(rrdPath, externalPath, RrdBackendFactory.getDefaultFactory());
339            }
340    
341            /**
342             * <p>Constructor used to create RRD files from external file sources with a backend type
343             * different from default. Supported external file sources are:</p>
344             * <p/>
345             * <ul>
346             * <li>RRDTool/JRobin XML file dumps (i.e files created with <code>rrdtool dump</code> command).
347             * <li>RRDTool binary files.
348             * </ul>
349             * <p/>
350             * <p>JRobin and RRDTool use the same format for XML dump and this constructor should be used to
351             * (re)create JRobin RRD files from XML dumps. First, dump the content of a RRDTool
352             * RRD file (use command line):</p>
353             * <p/>
354             * <pre>
355             * rrdtool dump original.rrd > original.xml
356             * </pre>
357             * <p/>
358             * <p>Than, use the file <code>original.xml</code> to create JRobin RRD file named
359             * <code>copy.rrd</code>:</p>
360             * <p/>
361             * <pre>
362             * RrdDb rrd = new RrdDb("copy.rrd", "original.xml");
363             * </pre>
364             * <p/>
365             * <p>or:</p>
366             * <p/>
367             * <pre>
368             * RrdDb rrd = new RrdDb("copy.rrd", "xml:/original.xml");
369             * </pre>
370             * <p/>
371             * <p>See documentation for {@link #dumpXml(java.lang.String) dumpXml()} method
372             * to see how to convert JRobin files to RRDTool's format.</p>
373             * <p/>
374             * <p>To read RRDTool files directly, specify <code>rrdtool:/</code> prefix in the
375             * <code>externalPath</code> argument. For example, to create JRobin compatible file named
376             * <code>copy.rrd</code> from the file <code>original.rrd</code> created with RRDTool, use
377             * the following code:</p>
378             * <p/>
379             * <pre>
380             * RrdDb rrd = new RrdDb("copy.rrd", "rrdtool:/original.rrd");
381             * </pre>
382             * <p/>
383             * <p>Note that the prefix <code>xml:/</code> or <code>rrdtool:/</code> is necessary to distinguish
384             * between XML and RRDTool's binary sources. If no prefix is supplied, XML format is assumed</p>
385             *
386             * @param rrdPath         Path to RRD which will be created
387             * @param externalPath Path to an external file which should be imported, with an optional
388             *                     <code>xml:/</code> or <code>rrdtool:/</code> prefix.
389             * @param factory         Backend factory which will be used to create storage (backend) for this RRD.
390             * @throws IOException  Thrown in case of I/O error
391             * @throws RrdException Thrown in case of JRobin specific error
392             * @see RrdBackendFactory
393             */
394            public RrdDb(String rrdPath, String externalPath, RrdBackendFactory factory)
395                            throws IOException, RrdException {
396                    DataImporter reader;
397                    if (externalPath.startsWith(PREFIX_RRDTool)) {
398                            String rrdToolPath = externalPath.substring(PREFIX_RRDTool.length());
399                            reader = new RrdToolReader(rrdToolPath);
400                    }
401                    else if (externalPath.startsWith(PREFIX_XML)) {
402                            externalPath = externalPath.substring(PREFIX_XML.length());
403                            reader = new XmlReader(externalPath);
404                    }
405                    else {
406                            reader = new XmlReader(externalPath);
407                    }
408                    backend = factory.open(rrdPath, false);
409                    try {
410                            backend.setLength(reader.getEstimatedSize());
411                            // create header
412                            header = new Header(this, reader);
413                            // create datasources
414                            datasources = new Datasource[reader.getDsCount()];
415                            for (int i = 0; i < datasources.length; i++) {
416                                    datasources[i] = new Datasource(this, reader, i);
417                            }
418                            // create archives
419                            archives = new Archive[reader.getArcCount()];
420                            for (int i = 0; i < archives.length; i++) {
421                                    archives[i] = new Archive(this, reader, i);
422                            }
423                            reader.release();
424                    }
425                    catch (RrdException e) {
426                            backend.close();
427                            throw e;
428                    }
429                    catch (IOException e) {
430                            backend.close();
431                            throw e;
432                    }
433            }
434    
435            /**
436             * Closes RRD. No further operations are allowed on this RrdDb object.
437             *
438             * @throws IOException Thrown in case of I/O related error.
439             */
440            public synchronized void close() throws IOException {
441                    if (!closed) {
442                            closed = true;
443                            backend.close();
444                    }
445            }
446    
447            /**
448             * Returns true if the RRD is closed.
449             *
450             * @return true if closed, false otherwise
451             */
452            public boolean isClosed() {
453                    return closed;
454            }
455    
456            /**
457             * Returns RRD header.
458             *
459             * @return Header object
460             */
461            public Header getHeader() {
462                    return header;
463            }
464    
465            /**
466             * Returns Datasource object for the given datasource index.
467             *
468             * @param dsIndex Datasource index (zero based)
469             * @return Datasource object
470             */
471            public Datasource getDatasource(int dsIndex) {
472                    return datasources[dsIndex];
473            }
474    
475            /**
476             * Returns Archive object for the given archive index.
477             *
478             * @param arcIndex Archive index (zero based)
479             * @return Archive object
480             */
481            public Archive getArchive(int arcIndex) {
482                    return archives[arcIndex];
483            }
484    
485            /**
486             * <p>Returns an array of datasource names defined in RRD.</p>
487             *
488             * @return Array of datasource names.
489             * @throws IOException Thrown in case of I/O error.
490             */
491            public String[] getDsNames() throws IOException {
492                    int n = datasources.length;
493                    String[] dsNames = new String[n];
494                    for (int i = 0; i < n; i++) {
495                            dsNames[i] = datasources[i].getDsName();
496                    }
497                    return dsNames;
498            }
499    
500            /**
501             * <p>Creates new sample with the given timestamp and all datasource values set to
502             * 'unknown'. Use returned <code>Sample</code> object to specify
503             * datasource values for the given timestamp. See documentation for
504             * {@link org.jrobin.core.Sample Sample} for an explanation how to do this.</p>
505             * <p/>
506             * <p>Once populated with data source values, call Sample's
507             * {@link org.jrobin.core.Sample#update() update()} method to actually
508             * store sample in the RRD associated with it.</p>
509             *
510             * @param time Sample timestamp rounded to the nearest second (without milliseconds).
511             * @return Fresh sample with the given timestamp and all data source values set to 'unknown'.
512             * @throws IOException Thrown in case of I/O error.
513             */
514            public Sample createSample(long time) throws IOException {
515                    return new Sample(this, time);
516            }
517    
518            /**
519             * <p>Creates new sample with the current timestamp and all data source values set to
520             * 'unknown'. Use returned <code>Sample</code> object to specify
521             * datasource values for the current timestamp. See documentation for
522             * {@link org.jrobin.core.Sample Sample} for an explanation how to do this.</p>
523             * <p/>
524             * <p>Once populated with data source values, call Sample's
525             * {@link org.jrobin.core.Sample#update() update()} method to actually
526             * store sample in the RRD associated with it.</p>
527             *
528             * @return Fresh sample with the current timestamp and all
529             *         data source values set to 'unknown'.
530             * @throws IOException Thrown in case of I/O error.
531             */
532            public Sample createSample() throws IOException {
533                    return createSample(Util.getTime());
534            }
535    
536            /**
537             * <p>Prepares fetch request to be executed on this RRD. Use returned
538             * <code>FetchRequest</code> object and its {@link org.jrobin.core.FetchRequest#fetchData() fetchData()}
539             * method to actually fetch data from the RRD file.</p>
540             *
541             * @param consolFun  Consolidation function to be used in fetch request. Allowed values are
542             *                   "AVERAGE", "MIN", "MAX" and "LAST" (these constants are conveniently defined in the
543             *                   {@link ConsolFuns} class).
544             * @param fetchStart Starting timestamp for fetch request.
545             * @param fetchEnd   Ending timestamp for fetch request.
546             * @param resolution Fetch resolution (see RRDTool's
547             *                   <a href="../../../../man/rrdfetch.html" target="man">rrdfetch man page</a> for an
548             *                   explanation of this parameter.
549             * @return Request object that should be used to actually fetch data from RRD.
550             * @throws RrdException In case of JRobin related error (invalid consolidation function or
551             *                      invalid time span).
552             */
553            public FetchRequest createFetchRequest(String consolFun, long fetchStart, long fetchEnd,
554                                                                                       long resolution) throws RrdException {
555                    return new FetchRequest(this, consolFun, fetchStart, fetchEnd, resolution);
556            }
557    
558            /**
559             * <p>Prepares fetch request to be executed on this RRD. Use returned
560             * <code>FetchRequest</code> object and its {@link org.jrobin.core.FetchRequest#fetchData() fetchData()}
561             * method to actually fetch data from this RRD. Data will be fetched with the smallest
562             * possible resolution (see RRDTool's
563             * <a href="../../../../man/rrdfetch.html" target="man">rrdfetch man page</a>
564             * for the explanation of the resolution parameter).</p>
565             *
566             * @param consolFun  Consolidation function to be used in fetch request. Allowed values are
567             *                   "AVERAGE", "MIN", "MAX" and "LAST" (these constants are conveniently defined in the
568             *                   {@link ConsolFuns} class).
569             * @param fetchStart Starting timestamp for fetch request.
570             * @param fetchEnd   Ending timestamp for fetch request.
571             * @return Request object that should be used to actually fetch data from RRD.
572             * @throws RrdException In case of JRobin related error (invalid consolidation function or
573             *                      invalid time span).
574             */
575            public FetchRequest createFetchRequest(String consolFun, long fetchStart, long fetchEnd)
576                            throws RrdException {
577                    return createFetchRequest(consolFun, fetchStart, fetchEnd, 1);
578            }
579    
580            synchronized void store(Sample sample) throws IOException, RrdException {
581                    if (closed) {
582                            throw new RrdException("RRD already closed, cannot store this  sample");
583                    }
584                    long newTime = sample.getTime();
585                    long lastTime = header.getLastUpdateTime();
586                    if (lastTime >= newTime) {
587                            throw new RrdException("Bad sample timestamp " + newTime +
588                                            ". Last update time was " + lastTime + ", at least one second step is required");
589                    }
590                    double[] newValues = sample.getValues();
591                    for (int i = 0; i < datasources.length; i++) {
592                            double newValue = newValues[i];
593                            datasources[i].process(newTime, newValue);
594                    }
595                    header.setLastUpdateTime(newTime);
596            }
597    
598            synchronized FetchData fetchData(FetchRequest request) throws IOException, RrdException {
599                    if (closed) {
600                            throw new RrdException("RRD already closed, cannot fetch data");
601                    }
602                    Archive archive = findMatchingArchive(request);
603                    return archive.fetchData(request);
604            }
605    
606            public Archive findMatchingArchive(FetchRequest request) throws RrdException, IOException {
607                    String consolFun = request.getConsolFun();
608                    long fetchStart = request.getFetchStart();
609                    long fetchEnd = request.getFetchEnd();
610                    long resolution = request.getResolution();
611                    Archive bestFullMatch = null, bestPartialMatch = null;
612                    long bestStepDiff = 0, bestMatch = 0;
613                    for (Archive archive : archives) {
614                            if (archive.getConsolFun().equals(consolFun)) {
615                                    long arcStep = archive.getArcStep();
616                                    long arcStart = archive.getStartTime() - arcStep;
617                                    long arcEnd = archive.getEndTime();
618                                    long fullMatch = fetchEnd - fetchStart;
619                                    if (arcEnd >= fetchEnd && arcStart <= fetchStart) {
620                                            long tmpStepDiff = Math.abs(archive.getArcStep() - resolution);
621    
622                                            if (tmpStepDiff < bestStepDiff || bestFullMatch == null) {
623                                                    bestStepDiff = tmpStepDiff;
624                                                    bestFullMatch = archive;
625                                            }
626                                    }
627                                    else {
628                                            long tmpMatch = fullMatch;
629    
630                                            if (arcStart > fetchStart) {
631                                                    tmpMatch -= (arcStart - fetchStart);
632                                            }
633                                            if (arcEnd < fetchEnd) {
634                                                    tmpMatch -= (fetchEnd - arcEnd);
635                                            }
636                                            if (bestPartialMatch == null || bestMatch < tmpMatch) {
637                                                    bestPartialMatch = archive;
638                                                    bestMatch = tmpMatch;
639                                            }
640                                    }
641                            }
642                    }
643                    if (bestFullMatch != null) {
644                            return bestFullMatch;
645                    }
646                    else if (bestPartialMatch != null) {
647                            return bestPartialMatch;
648                    }
649                    else {
650                            throw new RrdException("RRD file does not contain RRA:" + consolFun + " archive");
651                    }
652            }
653    
654            /**
655             * Finds the archive that best matches to the start time (time period being start-time until now)
656             * and requested resolution.
657             *
658             * @param consolFun  Consolidation function of the datasource.
659             * @param startTime  Start time of the time period in seconds.
660             * @param resolution Requested fetch resolution.
661             * @return Reference to the best matching archive.
662             * @throws IOException Thrown in case of I/O related error.
663             */
664            public Archive findStartMatchArchive(String consolFun, long startTime, long resolution) throws IOException {
665                    long arcStep, diff;
666                    int fallBackIndex = 0;
667                    int arcIndex = -1;
668                    long minDiff = Long.MAX_VALUE;
669                    long fallBackDiff = Long.MAX_VALUE;
670    
671                    for (int i = 0; i < archives.length; i++) {
672                            if (archives[i].getConsolFun().equals(consolFun)) {
673                                    arcStep = archives[i].getArcStep();
674                                    diff = Math.abs(resolution - arcStep);
675    
676                                    // Now compare start time, see if this archive encompasses the requested interval
677                                    if (startTime >= archives[i].getStartTime()) {
678                                            if (diff == 0)                          // Best possible match either way
679                                            {
680                                                    return archives[i];
681                                            }
682                                            else if (diff < minDiff) {
683                                                    minDiff = diff;
684                                                    arcIndex = i;
685                                            }
686                                    }
687                                    else if (diff < fallBackDiff) {
688                                            fallBackDiff = diff;
689                                            fallBackIndex = i;
690                                    }
691                            }
692                    }
693    
694                    return (arcIndex >= 0 ? archives[arcIndex] : archives[fallBackIndex]);
695            }
696    
697            /**
698             * <p>Returns string representing complete internal RRD state. The returned
699             * string can be printed to <code>stdout</code> and/or used for debugging purposes.</p>
700             *
701             * @return String representing internal RRD state.
702             * @throws IOException Thrown in case of I/O related error.
703             */
704            public synchronized String dump() throws IOException {
705                    StringBuffer buffer = new StringBuffer();
706                    buffer.append(header.dump());
707                    for (Datasource datasource : datasources) {
708                            buffer.append(datasource.dump());
709                    }
710                    for (Archive archive : archives) {
711                            buffer.append(archive.dump());
712                    }
713                    return buffer.toString();
714            }
715    
716            void archive(Datasource datasource, double value, long numUpdates)
717                            throws IOException, RrdException {
718                    int dsIndex = getDsIndex(datasource.getDsName());
719                    for (Archive archive : archives) {
720                            archive.archive(dsIndex, value, numUpdates);
721                    }
722            }
723    
724            /**
725             * <p>Returns internal index number for the given datasource name. This index is heavily
726             * used by jrobin.graph package and has no value outside of it.</p>
727             *
728             * @param dsName Data source name.
729             * @return Internal index of the given data source name in this RRD.
730             * @throws RrdException Thrown in case of JRobin related error (invalid data source name,
731             *                      for example)
732             * @throws IOException  Thrown in case of I/O error.
733             */
734            public int getDsIndex(String dsName) throws RrdException, IOException {
735                    for (int i = 0; i < datasources.length; i++) {
736                            if (datasources[i].getDsName().equals(dsName)) {
737                                    return i;
738                            }
739                    }
740                    throw new RrdException("Unknown datasource name: " + dsName);
741            }
742    
743            /**
744             * Checks presence of a specific datasource.
745             *
746             * @param dsName Datasource name to check
747             * @return <code>true</code> if datasource is present in this RRD, <code>false</code> otherwise
748             * @throws IOException Thrown in case of I/O error.
749             */
750            public boolean containsDs(String dsName) throws IOException {
751                    for (Datasource datasource : datasources) {
752                            if (datasource.getDsName().equals(dsName)) {
753                                    return true;
754                            }
755                    }
756                    return false;
757            }
758    
759            Datasource[] getDatasources() {
760                    return datasources;
761            }
762    
763            Archive[] getArchives() {
764                    return archives;
765            }
766    
767            /**
768             * <p>Writes the RRD content to OutputStream using XML format. This format
769             * is fully compatible with RRDTool's XML dump format and can be used for conversion
770             * purposes or debugging.</p>
771             *
772             * @param destination Output stream to receive XML data
773             * @throws IOException Thrown in case of I/O related error
774             */
775            public synchronized void dumpXml(OutputStream destination) throws IOException {
776                    XmlWriter writer = new XmlWriter(destination);
777                    writer.startTag("rrd");
778                    // dump header
779                    header.appendXml(writer);
780                    // dump datasources
781                    for (Datasource datasource : datasources) {
782                            datasource.appendXml(writer);
783                    }
784                    // dump archives
785                    for (Archive archive : archives) {
786                            archive.appendXml(writer);
787                    }
788                    writer.closeTag();
789                    writer.flush();
790            }
791    
792            /**
793             * This method is just an alias for {@link #dumpXml(OutputStream) dumpXml} method.
794             *
795             * @throws IOException Thrown in case of I/O related error
796             */
797            public synchronized void exportXml(OutputStream destination) throws IOException {
798                    dumpXml(destination);
799            }
800    
801            /**
802             * <p>Returns string representing internal RRD state in XML format. This format
803             * is fully compatible with RRDTool's XML dump format and can be used for conversion
804             * purposes or debugging.</p>
805             *
806             * @return Internal RRD state in XML format.
807             * @throws IOException  Thrown in case of I/O related error
808             * @throws RrdException Thrown in case of JRobin specific error
809             */
810            public synchronized String getXml() throws IOException, RrdException {
811                    ByteArrayOutputStream destination = new ByteArrayOutputStream(XML_INITIAL_BUFFER_CAPACITY);
812                    dumpXml(destination);
813                    return destination.toString();
814            }
815    
816            /**
817             * This method is just an alias for {@link #getXml() getXml} method.
818             *
819             * @return Internal RRD state in XML format.
820             * @throws IOException  Thrown in case of I/O related error
821             * @throws RrdException Thrown in case of JRobin specific error
822             */
823            public synchronized String exportXml() throws IOException, RrdException {
824                    return getXml();
825            }
826    
827            /**
828             * <p>Dumps internal RRD state to XML file.
829             * Use this XML file to convert your JRobin RRD to RRDTool format.</p>
830             * <p/>
831             * <p>Suppose that you have a JRobin RRD file <code>original.rrd</code> and you want
832             * to convert it to RRDTool format. First, execute the following java code:</p>
833             * <p/>
834             * <code>RrdDb rrd = new RrdDb("original.rrd");
835             * rrd.dumpXml("original.xml");</code>
836             * <p/>
837             * <p>Use <code>original.xml</code> file to create the corresponding RRDTool file
838             * (from your command line):
839             * <p/>
840             * <code>rrdtool restore copy.rrd original.xml</code>
841             *
842             * @param filename Path to XML file which will be created.
843             * @throws IOException  Thrown in case of I/O related error.
844             * @throws RrdException Thrown in case of JRobin related error.
845             */
846            public synchronized void dumpXml(String filename) throws IOException, RrdException {
847                    OutputStream outputStream = null;
848                    try {
849                            outputStream = new FileOutputStream(filename, false);
850                            dumpXml(outputStream);
851                    }
852                    finally {
853                            if (outputStream != null) {
854                                    outputStream.close();
855                            }
856                    }
857            }
858    
859            /**
860             * This method is just an alias for {@link #dumpXml(String) dumpXml(String)} method.
861             *
862             * @throws IOException  Thrown in case of I/O related error
863             * @throws RrdException Thrown in case of JRobin specific error
864             */
865            public synchronized void exportXml(String filename) throws IOException, RrdException {
866                    dumpXml(filename);
867            }
868    
869            /**
870             * Returns time of last update operation as timestamp (in seconds).
871             *
872             * @return Last update time (in seconds).
873             */
874            public synchronized long getLastUpdateTime() throws IOException {
875                    return header.getLastUpdateTime();
876            }
877    
878            /**
879             * <p>Returns RRD definition object which can be used to create new RRD
880             * with the same creation parameters but with no data in it.</p>
881             * <p/>
882             * <p>Example:</p>
883             * <p/>
884             * <pre>
885             * RrdDb rrd1 = new RrdDb("original.rrd");
886             * RrdDef def = rrd1.getRrdDef();
887             * // fix path
888             * def.setPath("empty_copy.rrd");
889             * // create new RRD file
890             * RrdDb rrd2 = new RrdDb(def);
891             * </pre>
892             *
893             * @return RRD definition.
894             * @throws RrdException Thrown in case of JRobin specific error.
895             */
896            public synchronized RrdDef getRrdDef() throws RrdException, IOException {
897                    // set header
898                    long startTime = header.getLastUpdateTime();
899                    long step = header.getStep();
900                    String path = backend.getPath();
901                    RrdDef rrdDef = new RrdDef(path, startTime, step);
902                    // add datasources
903                    for (Datasource datasource : datasources) {
904                            DsDef dsDef = new DsDef(datasource.getDsName(),
905                                            datasource.getDsType(), datasource.getHeartbeat(),
906                                            datasource.getMinValue(), datasource.getMaxValue());
907                            rrdDef.addDatasource(dsDef);
908                    }
909                    // add archives
910                    for (Archive archive : archives) {
911                            ArcDef arcDef = new ArcDef(archive.getConsolFun(),
912                                            archive.getXff(), archive.getSteps(), archive.getRows());
913                            rrdDef.addArchive(arcDef);
914                    }
915                    return rrdDef;
916            }
917    
918            protected void finalize() throws Throwable {
919                    super.finalize();
920                    close();
921            }
922    
923            /**
924             * Copies object's internal state to another RrdDb object.
925             *
926             * @param other New RrdDb object to copy state to
927             * @throws IOException  Thrown in case of I/O error
928             * @throws RrdException Thrown if supplied argument is not a compatible RrdDb object
929             */
930            public synchronized void copyStateTo(RrdUpdater other) throws IOException, RrdException {
931                    if (!(other instanceof RrdDb)) {
932                            throw new RrdException("Cannot copy RrdDb object to " + other.getClass().getName());
933                    }
934                    RrdDb otherRrd = (RrdDb) other;
935                    header.copyStateTo(otherRrd.header);
936                    for (int i = 0; i < datasources.length; i++) {
937                            int j = Util.getMatchingDatasourceIndex(this, i, otherRrd);
938                            if (j >= 0) {
939                                    datasources[i].copyStateTo(otherRrd.datasources[j]);
940                            }
941                    }
942                    for (int i = 0; i < archives.length; i++) {
943                            int j = Util.getMatchingArchiveIndex(this, i, otherRrd);
944                            if (j >= 0) {
945                                    archives[i].copyStateTo(otherRrd.archives[j]);
946                            }
947                    }
948            }
949    
950            /**
951             * Returns Datasource object corresponding to the given datasource name.
952             *
953             * @param dsName Datasource name
954             * @return Datasource object corresponding to the give datasource name or null
955             *         if not found.
956             * @throws IOException Thrown in case of I/O error
957             */
958            public Datasource getDatasource(String dsName) throws IOException {
959                    try {
960                            return getDatasource(getDsIndex(dsName));
961                    }
962                    catch (RrdException e) {
963                            return null;
964                    }
965            }
966    
967            /**
968             * Returns index of Archive object with the given consolidation function and the number
969             * of steps. Exception is thrown if such archive could not be found.
970             *
971             * @param consolFun Consolidation function
972             * @param steps  Number of archive steps
973             * @return Requested Archive object
974             * @throws IOException  Thrown in case of I/O error
975             * @throws RrdException Thrown if no such archive could be found
976             */
977            public int getArcIndex(String consolFun, int steps) throws RrdException, IOException {
978                    for (int i = 0; i < archives.length; i++) {
979                            if (archives[i].getConsolFun().equals(consolFun) &&
980                                            archives[i].getSteps() == steps) {
981                                    return i;
982                            }
983                    }
984                    throw new RrdException("Could not find archive " + consolFun + "/" + steps);
985            }
986    
987            /**
988             * Returns Archive object with the given consolidation function and the number
989             * of steps.
990             *
991             * @param consolFun Consolidation function
992             * @param steps  Number of archive steps
993             * @return Requested Archive object or null if no such archive could be found
994             * @throws IOException Thrown in case of I/O error
995             */
996            public Archive getArchive(String consolFun, int steps) throws IOException {
997                    try {
998                            return getArchive(getArcIndex(consolFun, steps));
999                    }
1000                    catch (RrdException e) {
1001                            return null;
1002                    }
1003            }
1004    
1005            /**
1006             * Returns canonical path to the underlying RRD file. Note that this method makes sense just for
1007             * ordinary RRD files created on the disk - an exception will be thrown for RRD objects created in
1008             * memory or with custom backends.
1009             *
1010             * @return Canonical path to RRD file;
1011             * @throws IOException Thrown in case of I/O error or if the underlying backend is
1012             *                     not derived from RrdFileBackend.
1013             */
1014            public String getCanonicalPath() throws IOException {
1015                    if (backend instanceof RrdFileBackend) {
1016                            return ((RrdFileBackend) backend).getCanonicalPath();
1017                    }
1018                    else {
1019                            throw new IOException("The underlying backend has no canonical path");
1020                    }
1021            }
1022    
1023            /**
1024             * Returns path to this RRD.
1025             *
1026             * @return Path to this RRD.
1027             */
1028            public String getPath() {
1029                    return backend.getPath();
1030            }
1031    
1032            /**
1033             * Returns backend object for this RRD which performs actual I/O operations.
1034             *
1035             * @return RRD backend for this RRD.
1036             */
1037            public RrdBackend getRrdBackend() {
1038                    return backend;
1039            }
1040    
1041            /**
1042             * Required to implement RrdUpdater interface. You should never call this method directly.
1043             *
1044             * @return Allocator object
1045             */
1046            public RrdAllocator getRrdAllocator() {
1047                    return allocator;
1048            }
1049    
1050            /**
1051             * Returns an array of bytes representing the whole RRD.
1052             *
1053             * @return All RRD bytes
1054             * @throws IOException Thrown in case of I/O related error.
1055             */
1056            public synchronized byte[] getBytes() throws IOException {
1057                    return backend.readAll();
1058            }
1059    
1060            /**
1061             * Sets default backend factory to be used. This method is just an alias for
1062             * {@link RrdBackendFactory#setDefaultFactory(java.lang.String)}.<p>
1063             *
1064             * @param factoryName Name of the backend factory to be set as default.
1065             * @throws RrdException Thrown if invalid factory name is supplied, or not called
1066             *                      before the first backend object (before the first RrdDb object) is created.
1067             */
1068            public static void setDefaultFactory(String factoryName) throws RrdException {
1069                    RrdBackendFactory.setDefaultFactory(factoryName);
1070            }
1071    
1072            /**
1073             * Returns an array of last datasource values. The first value in the array corresponds
1074             * to the first datasource defined in the RrdDb and so on.
1075             *
1076             * @return Array of last datasource values
1077             * @throws IOException Thrown in case of I/O error
1078             */
1079            public synchronized double[] getLastDatasourceValues() throws IOException {
1080                    double[] values = new double[datasources.length];
1081                    for (int i = 0; i < values.length; i++) {
1082                            values[i] = datasources[i].getLastValue();
1083                    }
1084                    return values;
1085            }
1086    
1087            /**
1088             * Returns the last stored value for the given datasource.
1089             *
1090             * @param dsName Datasource name
1091             * @return Last stored value for the given datasource
1092             * @throws IOException  Thrown in case of I/O error
1093             * @throws RrdException Thrown if no datasource in this RrdDb matches the given datasource name
1094             */
1095            public synchronized double getLastDatasourceValue(String dsName) throws IOException, RrdException {
1096                    int dsIndex = getDsIndex(dsName);
1097                    return datasources[dsIndex].getLastValue();
1098            }
1099    
1100            /**
1101             * Returns the number of datasources defined in the file
1102             *
1103             * @return The number of datasources defined in the file
1104             */
1105            public int getDsCount() {
1106                    return datasources.length;
1107            }
1108    
1109            /**
1110             * Returns the number of RRA arcihves defined in the file
1111             *
1112             * @return The number of RRA arcihves defined in the file
1113             */
1114            public int getArcCount() {
1115                    return archives.length;
1116            }
1117    
1118            /**
1119             * Returns the last time when some of the archives in this RRD was updated. This time is not the
1120             * same as the {@link #getLastUpdateTime()} since RRD file can be updated without updating any of
1121             * the archives.
1122             *
1123             * @return last time when some of the archives in this RRD was updated
1124             * @throws IOException Thrown in case of I/O error
1125             */
1126            public long getLastArchiveUpdateTime() throws IOException {
1127                    long last = 0;
1128                    for (Archive archive : archives) {
1129                            last = Math.max(last, archive.getEndTime());
1130                    }
1131                    return last;
1132            }
1133    
1134            public synchronized String getInfo() throws IOException {
1135                    return header.getInfo();
1136            }
1137    
1138            public synchronized void setInfo(String info) throws IOException {
1139                    header.setInfo(info);
1140            }
1141    
1142            public static void main(String[] args) {
1143                    System.out.println("JRobin Java Library :: RRDTool choice for the Java world");
1144                    System.out.println("==================================================================");
1145                    System.out.println("JRobin base directory: " + Util.getJRobinHomeDirectory());
1146                    long time = Util.getTime();
1147                    System.out.println("Current timestamp: " + time + ": " + new Date(time * 1000L));
1148                    System.out.println("------------------------------------------------------------------");
1149                    System.out.println("For the latest information visit: http://www.jrobin.org");
1150                    System.out.println("(C) 2003-2005 Sasa Markovic. All rights reserved.");
1151            }
1152    
1153    }