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(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 }
1154