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.File;
29  import java.io.IOException;
30  import java.util.List;
31  import java.util.LinkedList;
32  import java.util.Arrays;
33  
34  /**
35   * <p>Class used to perform various complex operations on RRD files. Use an instance of the
36   * RrdToolkit class to:</p>
37   * <ul>
38   * <li>add datasource to a RRD file.
39   * <li>add archive to a RRD file.
40   * <li>remove datasource from a RRD file.
41   * <li>remove archive from a RRD file.
42   * </ul>
43   * <p>All these operations can be performed on the copy of the original RRD file, or on the
44   * original file itself (with possible backup file creation)</p>
45   * <p/>
46   * <p><b><u>IMPORTANT</u></b>: NEVER use methods found in this class on 'live' RRD files
47   * (files which are currently in use).</p>
48   */
49  public class RrdToolkit {
50  	/**
51  	 * Creates a new RRD file with one more datasource in it. RRD file is created based on the
52  	 * existing one (the original RRD file is not modified at all). All data from
53  	 * the original RRD file is copied to the new one.
54  	 *
55  	 * @param sourcePath	path to a RRD file to import data from (will not be modified)
56  	 * @param destPath	  path to a new RRD file (will be created)
57  	 * @param newDatasource Datasource definition to be added to the new RRD file
58  	 * @throws IOException  Thrown in case of I/O error
59  	 * @throws RrdException Thrown in case of JRobin specific error
60  	 */
61  	public static void addDatasource(String sourcePath, String destPath, DsDef newDatasource)
62  			throws IOException, RrdException {
63  		if (Util.sameFilePath(sourcePath, destPath)) {
64  			throw new RrdException("Source and destination paths are the same");
65  		}
66  		RrdDb rrdSource = new RrdDb(sourcePath);
67  		try {
68  			RrdDef rrdDef = rrdSource.getRrdDef();
69  			rrdDef.setPath(destPath);
70  			rrdDef.addDatasource(newDatasource);
71  			RrdDb rrdDest = new RrdDb(rrdDef);
72  			try {
73  				rrdSource.copyStateTo(rrdDest);
74  			}
75  			finally {
76  				rrdDest.close();
77  			}
78  		}
79  		finally {
80  			rrdSource.close();
81  		}
82  	}
83  
84  	/**
85  	 * <p>Adds one more datasource to a RRD file.</p>
86  	 * <p>WARNING: This method is potentialy dangerous! It will modify your RRD file.
87  	 * It is highly recommended to preserve the original RRD file (<i>saveBackup</i>
88  	 * should be set to <code>true</code>). The backup file will be created in the same
89  	 * directory as the original one with <code>.bak</code> extension added to the
90  	 * original name.</p>
91  	 * <p>Before applying this method, be sure that the specified RRD file is not in use
92  	 * (not open)</p>
93  	 *
94  	 * @param sourcePath	path to a RRD file to add datasource to.
95  	 * @param newDatasource Datasource definition to be added to the RRD file
96  	 * @param saveBackup	true, if backup of the original file should be created;
97  	 *                      false, otherwise
98  	 * @throws IOException  Thrown in case of I/O error
99  	 * @throws RrdException Thrown in case of JRobin specific error
100 	 */
101 	public static void addDatasource(String sourcePath, DsDef newDatasource, boolean saveBackup)
102 			throws IOException, RrdException {
103 		String destPath = Util.getTmpFilename();
104 		addDatasource(sourcePath, destPath, newDatasource);
105 		copyFile(destPath, sourcePath, saveBackup);
106 	}
107 
108 	/**
109 	 * Creates a new RRD file with one datasource removed. RRD file is created based on the
110 	 * existing one (the original RRD file is not modified at all). All remaining data from
111 	 * the original RRD file is copied to the new one.
112 	 *
113 	 * @param sourcePath path to a RRD file to import data from (will not be modified)
114 	 * @param destPath   path to a new RRD file (will be created)
115 	 * @param dsName	 Name of the Datasource to be removed from the new RRD file
116 	 * @throws IOException  Thrown in case of I/O error
117 	 * @throws RrdException Thrown in case of JRobin specific error
118 	 */
119 	public static void removeDatasource(String sourcePath, String destPath, String dsName)
120 			throws IOException, RrdException {
121 		if (Util.sameFilePath(sourcePath, destPath)) {
122 			throw new RrdException("Source and destination paths are the same");
123 		}
124 		RrdDb rrdSource = new RrdDb(sourcePath);
125 		try {
126 			RrdDef rrdDef = rrdSource.getRrdDef();
127 			rrdDef.setPath(destPath);
128 			rrdDef.removeDatasource(dsName);
129 			RrdDb rrdDest = new RrdDb(rrdDef);
130 			try {
131 				rrdSource.copyStateTo(rrdDest);
132 			}
133 			finally {
134 				rrdDest.close();
135 			}
136 		}
137 		finally {
138 			rrdSource.close();
139 		}
140 	}
141 
142 	/**
143 	 * <p>Removes single datasource from a RRD file.</p>
144 	 * <p>WARNING: This method is potentialy dangerous! It will modify your RRD file.
145 	 * It is highly recommended to preserve the original RRD file (<i>saveBackup</i>
146 	 * should be set to <code>true</code>). The backup file will be created in the same
147 	 * directory as the original one with <code>.bak</code> extension added to the
148 	 * original name.</p>
149 	 * <p>Before applying this method, be sure that the specified RRD file is not in use
150 	 * (not open)</p>
151 	 *
152 	 * @param sourcePath path to a RRD file to remove datasource from.
153 	 * @param dsName	 Name of the Datasource to be removed from the RRD file
154 	 * @param saveBackup true, if backup of the original file should be created;
155 	 *                   false, otherwise
156 	 * @throws IOException  Thrown in case of I/O error
157 	 * @throws RrdException Thrown in case of JRobin specific error
158 	 */
159 	public static void removeDatasource(String sourcePath, String dsName, boolean saveBackup)
160 			throws IOException, RrdException {
161 		String destPath = Util.getTmpFilename();
162 		removeDatasource(sourcePath, destPath, dsName);
163 		copyFile(destPath, sourcePath, saveBackup);
164 	}
165 
166 	/**
167 	 * Renames single datasource in the given RRD file.
168 	 *
169 	 * @param sourcePath Path to a RRD file
170 	 * @param oldDsName  Old datasource name
171 	 * @param newDsName  New datasource name
172 	 * @throws IOException  Thrown in case of I/O error
173 	 * @throws RrdException Thrown in case of JRobin specific error (invalid path or datasource names,
174 	 *                      for example)
175 	 */
176 	public static void renameDatasource(String sourcePath, String oldDsName, String newDsName)
177 			throws IOException, RrdException {
178 		RrdDb rrd = new RrdDb(sourcePath);
179 		try {
180 			if (rrd.containsDs(oldDsName)) {
181 				Datasource datasource = rrd.getDatasource(oldDsName);
182 				datasource.setDsName(newDsName);
183 			}
184 			else {
185 				throw new RrdException("Could not find datasource [" + oldDsName + "] in file " + sourcePath);
186 			}
187 		}
188 		finally {
189 			rrd.close();
190 		}
191 	}
192 
193 	/**
194 	 * Updates single or all datasource names in the specified RRD file
195 	 * by appending '!' (if not already present). Datasources with names ending with '!'
196 	 * will never store NaNs in RRA archives (zero value will be used instead). Might be useful
197 	 * from time to time
198 	 *
199 	 * @param sourcePath Path to a RRD file
200 	 * @param dsName	 Datasource name or null if you want to rename all datasources
201 	 * @return Number of datasources successfully renamed
202 	 * @throws IOException  Thrown in case of I/O error
203 	 * @throws RrdException Thrown in case of JRobin specific error (invalid path or datasource name,
204 	 *                      for example)
205 	 */
206 	public static int forceZerosForNans(String sourcePath, String dsName) throws IOException, RrdException {
207 		RrdDb rrd = new RrdDb(sourcePath);
208 		try {
209 			Datasource[] datasources;
210 			if (dsName == null) {
211 				datasources = rrd.getDatasources();
212 			}
213 			else {
214 				if (rrd.containsDs(dsName)) {
215 					datasources = new Datasource[] {rrd.getDatasource(dsName)};
216 				}
217 				else {
218 					throw new RrdException("Could not find datasource [" + dsName + "] in file " + sourcePath);
219 				}
220 			}
221 			int count = 0;
222 			for (Datasource datasource : datasources) {
223 				String currentDsName = datasource.getDsName();
224 				if (!currentDsName.endsWith(DsDef.FORCE_ZEROS_FOR_NANS_SUFFIX)) {
225 					datasource.setDsName(currentDsName + DsDef.FORCE_ZEROS_FOR_NANS_SUFFIX);
226 					count++;
227 				}
228 			}
229 			return count;
230 		}
231 		finally {
232 			rrd.close();
233 		}
234 	}
235 
236 	/**
237 	 * Creates a new RRD file with one more archive in it. RRD file is created based on the
238 	 * existing one (the original RRD file is not modified at all). All data from
239 	 * the original RRD file is copied to the new one.
240 	 *
241 	 * @param sourcePath path to a RRD file to import data from (will not be modified)
242 	 * @param destPath   path to a new RRD file (will be created)
243 	 * @param newArchive Archive definition to be added to the new RRD file
244 	 * @throws IOException  Thrown in case of I/O error
245 	 * @throws RrdException Thrown in case of JRobin specific error
246 	 */
247 	public static void addArchive(String sourcePath, String destPath, ArcDef newArchive)
248 			throws IOException, RrdException {
249 		if (Util.sameFilePath(sourcePath, destPath)) {
250 			throw new RrdException("Source and destination paths are the same");
251 		}
252 		RrdDb rrdSource = new RrdDb(sourcePath);
253 		try {
254 			RrdDef rrdDef = rrdSource.getRrdDef();
255 			rrdDef.setPath(destPath);
256 			rrdDef.addArchive(newArchive);
257 			RrdDb rrdDest = new RrdDb(rrdDef);
258 			try {
259 				rrdSource.copyStateTo(rrdDest);
260 			}
261 			finally {
262 				rrdDest.close();
263 			}
264 		}
265 		finally {
266 			rrdSource.close();
267 		}
268 	}
269 
270 	/**
271 	 * <p>Adds one more archive to a RRD file.</p>
272 	 * <p>WARNING: This method is potentialy dangerous! It will modify your RRD file.
273 	 * It is highly recommended to preserve the original RRD file (<i>saveBackup</i>
274 	 * should be set to <code>true</code>). The backup file will be created in the same
275 	 * directory as the original one with <code>.bak</code> extension added to the
276 	 * original name.</p>
277 	 * <p>Before applying this method, be sure that the specified RRD file is not in use
278 	 * (not open)</p>
279 	 *
280 	 * @param sourcePath path to a RRD file to add datasource to.
281 	 * @param newArchive Archive definition to be added to the RRD file
282 	 * @param saveBackup true, if backup of the original file should be created;
283 	 *                   false, otherwise
284 	 * @throws IOException  Thrown in case of I/O error
285 	 * @throws RrdException Thrown in case of JRobin specific error
286 	 */
287 	public static void addArchive(String sourcePath, ArcDef newArchive, boolean saveBackup)
288 			throws IOException, RrdException {
289 		String destPath = Util.getTmpFilename();
290 		addArchive(sourcePath, destPath, newArchive);
291 		copyFile(destPath, sourcePath, saveBackup);
292 	}
293 
294 	/**
295 	 * Creates a new RRD file with one archive removed. RRD file is created based on the
296 	 * existing one (the original RRD file is not modified at all). All relevant data from
297 	 * the original RRD file is copied to the new one.
298 	 *
299 	 * @param sourcePath path to a RRD file to import data from (will not be modified)
300 	 * @param destPath   path to a new RRD file (will be created)
301 	 * @param consolFun  Consolidation function of Archive which should be removed
302 	 * @param steps	  Number of steps for Archive which should be removed
303 	 * @throws IOException  Thrown in case of I/O error
304 	 * @throws RrdException Thrown in case of JRobin specific error
305 	 */
306 	public static void removeArchive(String sourcePath, String destPath, String consolFun, int steps)
307 			throws IOException, RrdException {
308 		if (Util.sameFilePath(sourcePath, destPath)) {
309 			throw new RrdException("Source and destination paths are the same");
310 		}
311 		RrdDb rrdSource = new RrdDb(sourcePath);
312 		try {
313 			RrdDef rrdDef = rrdSource.getRrdDef();
314 			rrdDef.setPath(destPath);
315 			rrdDef.removeArchive(consolFun, steps);
316 			RrdDb rrdDest = new RrdDb(rrdDef);
317 			try {
318 				rrdSource.copyStateTo(rrdDest);
319 			}
320 			finally {
321 				rrdDest.close();
322 			}
323 		}
324 		finally {
325 			rrdSource.close();
326 		}
327 	}
328 
329 	/**
330 	 * <p>Removes one archive from a RRD file.</p>
331 	 * <p>WARNING: This method is potentialy dangerous! It will modify your RRD file.
332 	 * It is highly recommended to preserve the original RRD file (<i>saveBackup</i>
333 	 * should be set to <code>true</code>). The backup file will be created in the same
334 	 * directory as the original one with <code>.bak</code> extension added to the
335 	 * original name.</p>
336 	 * <p>Before applying this method, be sure that the specified RRD file is not in use
337 	 * (not open)</p>
338 	 *
339 	 * @param sourcePath path to a RRD file to add datasource to.
340 	 * @param consolFun  Consolidation function of Archive which should be removed
341 	 * @param steps	  Number of steps for Archive which should be removed
342 	 * @param saveBackup true, if backup of the original file should be created;
343 	 *                   false, otherwise
344 	 * @throws IOException  Thrown in case of I/O error
345 	 * @throws RrdException Thrown in case of JRobin specific error
346 	 */
347 	public static void removeArchive(String sourcePath, String consolFun, int steps,
348 									 boolean saveBackup) throws IOException, RrdException {
349 		String destPath = Util.getTmpFilename();
350 		removeArchive(sourcePath, destPath, consolFun, steps);
351 		copyFile(destPath, sourcePath, saveBackup);
352 	}
353 
354 	private static void copyFile(String sourcePath, String destPath, boolean saveBackup)
355 			throws IOException {
356 		File source = new File(sourcePath);
357 		File dest = new File(destPath);
358 		if (saveBackup) {
359 			String backupPath = getBackupPath(destPath);
360 			File backup = new File(backupPath);
361 			deleteFile(backup);
362 			if (!dest.renameTo(backup)) {
363 				throw new IOException("Could not create backup file " + backupPath);
364 			}
365 		}
366 		deleteFile(dest);
367 		if (!source.renameTo(dest)) {
368 			throw new IOException("Could not create file " + destPath + " from " + sourcePath);
369 		}
370 	}
371 
372 	private static String getBackupPath(String destPath) {
373 		String backupPath = destPath;
374 		do {
375 			backupPath += ".bak";
376 		} while (Util.fileExists(backupPath));
377 		return backupPath;
378 	}
379 
380 	/**
381 	 * Sets datasource heartbeat to a new value.
382 	 *
383 	 * @param sourcePath	 Path to exisiting RRD file (will be updated)
384 	 * @param datasourceName Name of the datasource in the specified RRD file
385 	 * @param newHeartbeat   New datasource heartbeat
386 	 * @throws RrdException Thrown in case of JRobin specific error
387 	 * @throws IOException  Thrown in case of I/O error
388 	 */
389 	public static void setDsHeartbeat(String sourcePath, String datasourceName,
390 									  long newHeartbeat) throws RrdException, IOException {
391 		RrdDb rrd = new RrdDb(sourcePath);
392 		try {
393 			Datasource ds = rrd.getDatasource(datasourceName);
394 			ds.setHeartbeat(newHeartbeat);
395 		}
396 		finally {
397 			rrd.close();
398 		}
399 	}
400 
401 	/**
402 	 * Sets datasource heartbeat to a new value.
403 	 *
404 	 * @param sourcePath   Path to exisiting RRD file (will be updated)
405 	 * @param dsIndex	  Index of the datasource in the specified RRD file
406 	 * @param newHeartbeat New datasource heartbeat
407 	 * @throws RrdException Thrown in case of JRobin specific error
408 	 * @throws IOException  Thrown in case of I/O error
409 	 */
410 	public static void setDsHeartbeat(String sourcePath, int dsIndex, long newHeartbeat)
411 			throws RrdException, IOException {
412 		RrdDb rrd = new RrdDb(sourcePath);
413 		try {
414 			Datasource ds = rrd.getDatasource(dsIndex);
415 			ds.setHeartbeat(newHeartbeat);
416 		}
417 		finally {
418 			rrd.close();
419 		}
420 	}
421 
422 	/**
423 	 * Sets datasource min value to a new value
424 	 *
425 	 * @param sourcePath		   Path to exisiting RRD file (will be updated)
426 	 * @param datasourceName	   Name of the datasource in the specified RRD file
427 	 * @param newMinValue		  New min value for the datasource
428 	 * @param filterArchivedValues set to <code>true</code> if archived values less than
429 	 *                             <code>newMinValue</code> should be set to NaN; set to false, otherwise.
430 	 * @throws RrdException Thrown in case of JRobin specific error
431 	 * @throws IOException  Thrown in case of I/O error
432 	 */
433 	public static void setDsMinValue(String sourcePath, String datasourceName,
434 									 double newMinValue, boolean filterArchivedValues) throws RrdException, IOException {
435 		RrdDb rrd = new RrdDb(sourcePath);
436 		try {
437 			Datasource ds = rrd.getDatasource(datasourceName);
438 			ds.setMinValue(newMinValue, filterArchivedValues);
439 		}
440 		finally {
441 			rrd.close();
442 		}
443 	}
444 
445 	/**
446 	 * Sets datasource max value to a new value.
447 	 *
448 	 * @param sourcePath		   Path to exisiting RRD file (will be updated)
449 	 * @param datasourceName	   Name of the datasource in the specified RRD file
450 	 * @param newMaxValue		  New max value for the datasource
451 	 * @param filterArchivedValues set to <code>true</code> if archived values greater than
452 	 *                             <code>newMaxValue</code> should be set to NaN; set to false, otherwise.
453 	 * @throws RrdException Thrown in case of JRobin specific error
454 	 * @throws IOException  Thrown in case of I/O error
455 	 */
456 	public static void setDsMaxValue(String sourcePath, String datasourceName,
457 									 double newMaxValue, boolean filterArchivedValues) throws RrdException, IOException {
458 		RrdDb rrd = new RrdDb(sourcePath);
459 		try {
460 			Datasource ds = rrd.getDatasource(datasourceName);
461 			ds.setMaxValue(newMaxValue, filterArchivedValues);
462 		}
463 		finally {
464 			rrd.close();
465 		}
466 	}
467 
468 	/**
469 	 * Updates valid value range for the given datasource.
470 	 *
471 	 * @param sourcePath		   Path to exisiting RRD file (will be updated)
472 	 * @param datasourceName	   Name of the datasource in the specified RRD file
473 	 * @param newMinValue		  New min value for the datasource
474 	 * @param newMaxValue		  New max value for the datasource
475 	 * @param filterArchivedValues set to <code>true</code> if archived values outside
476 	 *                             of the specified min/max range should be replaced with NaNs.
477 	 * @throws RrdException Thrown in case of JRobin specific error
478 	 * @throws IOException  Thrown in case of I/O error
479 	 */
480 	public static void setDsMinMaxValue(String sourcePath, String datasourceName,
481 										double newMinValue, double newMaxValue, boolean filterArchivedValues)
482 			throws RrdException, IOException {
483 		RrdDb rrd = new RrdDb(sourcePath);
484 		try {
485 			Datasource ds = rrd.getDatasource(datasourceName);
486 			ds.setMinMaxValue(newMinValue, newMaxValue, filterArchivedValues);
487 		}
488 		finally {
489 			rrd.close();
490 		}
491 	}
492 
493 	/**
494 	 * Sets single archive's X-files factor to a new value.
495 	 *
496 	 * @param sourcePath Path to existing RRD file (will be updated)
497 	 * @param consolFun  Consolidation function of the target archive
498 	 * @param steps	  Number of sptes of the target archive
499 	 * @param newXff	 New X-files factor for the target archive
500 	 * @throws RrdException Thrown in case of JRobin specific error
501 	 * @throws IOException  Thrown in case of I/O error
502 	 */
503 	public static void setArcXff(String sourcePath, String consolFun, int steps,
504 								 double newXff) throws RrdException, IOException {
505 		RrdDb rrd = new RrdDb(sourcePath);
506 		try {
507 			Archive arc = rrd.getArchive(consolFun, steps);
508 			arc.setXff(newXff);
509 		}
510 		finally {
511 			rrd.close();
512 		}
513 	}
514 
515 	/**
516 	 * Creates new RRD file based on the existing one, but with a different
517 	 * size (number of rows) for a single archive. The archive to be resized
518 	 * is identified by its consolidation function and the number of steps.
519 	 *
520 	 * @param sourcePath Path to the source RRD file (will not be modified)
521 	 * @param destPath   Path to the new RRD file (will be created)
522 	 * @param consolFun  Consolidation function of the archive to be resized
523 	 * @param numSteps   Number of steps of the archive to be resized
524 	 * @param newRows	New archive size (number of archive rows)
525 	 * @throws IOException  Thrown in case of I/O error
526 	 * @throws RrdException Thrown in case of JRobin specific error
527 	 */
528 	public static void resizeArchive(String sourcePath, String destPath, String consolFun,
529 									 int numSteps, int newRows)
530 			throws IOException, RrdException {
531 		if (Util.sameFilePath(sourcePath, destPath)) {
532 			throw new RrdException("Source and destination paths are the same");
533 		}
534 		if (newRows < 2) {
535 			throw new RrdException("New arcihve size must be at least 2");
536 		}
537 		RrdDb rrdSource = new RrdDb(sourcePath);
538 		try {
539 			RrdDef rrdDef = rrdSource.getRrdDef();
540 			ArcDef arcDef = rrdDef.findArchive(consolFun, numSteps);
541 			if (arcDef.getRows() != newRows) {
542 				arcDef.setRows(newRows);
543 				rrdDef.setPath(destPath);
544 				RrdDb rrdDest = new RrdDb(rrdDef);
545 				try {
546 					rrdSource.copyStateTo(rrdDest);
547 				}
548 				finally {
549 					rrdDest.close();
550 				}
551 			}
552 		}
553 		finally {
554 			rrdSource.close();
555 		}
556 	}
557 
558 	/**
559 	 * Modifies existing RRD file, by resizing its chosen archive. The archive to be resized
560 	 * is identified by its consolidation function and the number of steps.
561 	 *
562 	 * @param sourcePath Path to the RRD file (will be modified)
563 	 * @param consolFun  Consolidation function of the archive to be resized
564 	 * @param numSteps   Number of steps of the archive to be resized
565 	 * @param newRows	New archive size (number of archive rows)
566 	 * @param saveBackup true, if backup of the original file should be created;
567 	 *                   false, otherwise
568 	 * @throws IOException  Thrown in case of I/O error
569 	 * @throws RrdException Thrown in case of JRobin specific error
570 	 */
571 	public static void resizeArchive(String sourcePath, String consolFun,
572 									 int numSteps, int newRows, boolean saveBackup)
573 			throws IOException, RrdException {
574 		String destPath = Util.getTmpFilename();
575 		resizeArchive(sourcePath, destPath, consolFun, numSteps, newRows);
576 		copyFile(destPath, sourcePath, saveBackup);
577 	}
578 
579 	private static void deleteFile(File file) throws IOException {
580 		if (file.exists() && !file.delete()) {
581 			throw new IOException("Could not delete file: " + file.getCanonicalPath());
582 		}
583 	}
584 
585 	/**
586 	 * Splits single RRD file with several datasources into a number of smaller RRD files
587 	 * with a single datasource in it. All archived values are preserved. If
588 	 * you have a RRD file named 'traffic.rrd' with two datasources, 'in' and 'out', this
589 	 * method will create two files (with a single datasource, in the same directory)
590 	 * named 'in-traffic.rrd' and 'out-traffic.rrd'.
591 	 *
592 	 * @param sourcePath Path to a RRD file with multiple datasources defined
593 	 * @throws IOException  Thrown in case of I/O error
594 	 * @throws RrdException Thrown in case of JRobin specific error
595 	 */
596 	public static void split(String sourcePath) throws IOException, RrdException {
597 		RrdDb rrdSource = new RrdDb(sourcePath);
598 		try {
599 			String[] dsNames = rrdSource.getDsNames();
600 			for (String dsName : dsNames) {
601 				RrdDef rrdDef = rrdSource.getRrdDef();
602 				rrdDef.setPath(createSplitPath(dsName, sourcePath));
603 				rrdDef.saveSingleDatasource(dsName);
604 				RrdDb rrdDest = new RrdDb(rrdDef);
605 				try {
606 					rrdSource.copyStateTo(rrdDest);
607 				}
608 				finally {
609 					rrdDest.close();
610 				}
611 			}
612 		}
613 		finally {
614 			rrdSource.close();
615 		}
616 	}
617 
618 	/**
619 	 * Returns list of canonical file names with the specified extension in the given directory. This
620 	 * method is not RRD related, but might come handy to create a quick list of all RRD files
621 	 * in the given directory.
622 	 *
623 	 * @param directory Source directory
624 	 * @param extension File extension (like ".rrd", ".jrb", ".rrd.jrb")
625 	 * @param resursive true if all subdirectories should be traversed for the same extension, false otherwise
626 	 * @return Array of sorted canonical file names with the given extension
627 	 * @throws IOException Thrown in case of I/O error
628 	 */
629 	public static String[] getCanonicalPaths(String directory, final String extension, boolean resursive)
630 			throws IOException {
631 		File baseDir = new File(directory);
632 		if (!baseDir.isDirectory()) {
633 			throw new IOException("Not a directory: " + directory);
634 		}
635 		List<String> fileList = new LinkedList<String>();
636 		traverseDirectory(new File(directory), extension, resursive, fileList);
637 		String[] result = fileList.toArray(new String[fileList.size()]);
638 		Arrays.sort(result);
639 		return result;
640 	}
641 
642 	private static void traverseDirectory(File directory, String extension, boolean recursive, List<String> list)
643 			throws IOException {
644 		File[] files = directory.listFiles();
645 		for (File file : files) {
646 			if (file.isDirectory() && recursive) {
647 				// traverse subdirectories only if recursive flag is specified
648 				traverseDirectory(file, extension, recursive, list);
649 			}
650 			else if (file.isFile() && file.getName().endsWith(extension)) {
651 				list.add(file.getCanonicalPath());
652 			}
653 		}
654 	}
655 
656 	private static String createSplitPath(String dsName, String sourcePath) {
657 		File file = new File(sourcePath);
658 		String newName = dsName + "-" + file.getName();
659 		String path = file.getAbsolutePath();
660 		String parentDir = path.substring(0, 1 + path.lastIndexOf(Util.getFileSeparator()));
661 		return parentDir + newName;
662 	}
663 }
664