1 /* ============================================================
2  * JRobin : Pure java implementation of RRDTool's functionality
3  * ============================================================
4  *
5  * Project Info:  http://www.jrobin.org
6  * Project Lead:  Sasa Markovic (saxon@jrobin.org);
7  *
8  * (C) Copyright 2003-2005, by Sasa Markovic.
9  *
10  * Developers:    Sasa Markovic (saxon@jrobin.org)
11  *
12  *
13  * This library is free software; you can redistribute it and/or modify it under the terms
14  * of the GNU Lesser General Public License as published by the Free Software Foundation;
15  * either version 2.1 of the License, or (at your option) any later version.
16  *
17  * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
18  * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
19  * See the GNU Lesser General Public License for more details.
20  *
21  * You should have received a copy of the GNU Lesser General Public License along with this
22  * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
23  * Boston, MA 02111-1307, USA.
24  */

25
26 package org.jrobin.core;
27
28 import java.io.*;
29 import java.util.Date;
30
31 /**
32  * <p>Main class used to create and manipulate round robin databases (RRDs). Use this class to perform
33  * update and fetch operations on exisiting RRDs, to create new RRD from
34  * the definition (object of class {@link org.jrobin.core.RrdDef RrdDef}) or
35  * from XML file (dumped content of RRDTool's or JRobin's RRD file).</p>
36  * <p/>
37  * <p>Each RRD is backed with some kind of storage. For example, RRDTool supports only one kind of
38  * storage (disk file). On the contrary, JRobin gives you freedom to use other storage (backend) types
39  * even to create your own backend types for some special purposes. JRobin by default stores
40  * RRD data in files (as RRDTool), but you might choose to store RRD data in memory (this is
41  * supported in JRobin), to use java.nio.* instead of java.io.* package for file manipulation
42  * (also supported) or to store whole RRDs in the SQL database
43  * (you'll have to extend some classes to do this).</p>
44  * <p/>
45  * <p>Note that JRobin uses binary format different from RRDTool's format. You cannot
46  * use this class to manipulate RRD files created with RRDTool. <b>However, if you perform
47  * the same sequence of create, update and fetch operations, you will get exactly the same
48  * results from JRobin and RRDTool.</b><p>
49  * <p/>
50  * <p/>
51  * You will not be able to use JRobin API if you are not familiar with
52  * basic RRDTool concepts. Good place to start is the
53  * <a href="http://people.ee.ethz.ch/~oetiker/webtools/rrdtool/tutorial/rrdtutorial.html">official RRD tutorial</a>
54  * and relevant RRDTool man pages: <a href="../../../../man/rrdcreate.html" target="man">rrdcreate</a>,
55  * <a href="../../../../man/rrdupdate.html" target="man">rrdupdate</a>,
56  * <a href="../../../../man/rrdfetch.html" target="man">rrdfetch</a> and
57  * <a href="../../../../man/rrdgraph.html" target="man">rrdgraph</a>.
58  * For RRDTool's advanced graphing capabilities (RPN extensions), also supported in JRobin,
59  * there is an excellent
60  * <a href="http://people.ee.ethz.ch/~oetiker/webtools/rrdtool/tutorial/cdeftutorial.html" target="man">CDEF tutorial</a>.
61  * </p>
62  *
63  * @see RrdBackend
64  * @see RrdBackendFactory
65  */

66 public class RrdDb implements RrdUpdater {
67     /**
68      * prefix to identify external XML file source used in various RrdDb constructors
69      */

70     public static final String PREFIX_XML = "xml:/";
71     /**
72      * prefix to identify external RRDTool file source used in various RrdDb constructors
73      */

74     public static final String PREFIX_RRDTool = "rrdtool:/";
75
76     // static final String RRDTOOL = "rrdtool";
77     static final int XML_INITIAL_BUFFER_CAPACITY = 100000; // bytes
78
79     private RrdBackend backend;
80     private RrdAllocator allocator = new RrdAllocator();
81
82     private Header header;
83     private Datasource[] datasources;
84     private Archive[] archives;
85
86     private boolean closed = false;
87
88     /**
89      * <p>Constructor used to create new RRD object from the definition. This RRD object will be backed
90      * with a storage (backend) of the default type. Initially, storage type defaults to "NIO"
91      * (RRD bytes will be put in a file on the disk). Default storage type can be changed with a static
92      * {@link RrdBackendFactory#setDefaultFactory(String)} method call.</p>
93      * <p/>
94      * <p>New RRD file structure is specified with an object of class
95      * {@link org.jrobin.core.RrdDef <b>RrdDef</b>}. The underlying RRD storage is created as soon
96      * as the constructor returns.</p>
97      * <p/>
98      * <p>Typical scenario:</p>
99      * <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(thisnull);
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(thisnull);
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 }
1154