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 }