View Javadoc

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 }