View Javadoc

1   /*
2    * Copyright (C) 2001 Ciaran Treanor <ciaran@codeloop.com>
3    *
4    * Distributable under GPL license.
5    * See terms of license at gnu.org.
6    *
7    * $Id: RRDatabase.java,v 1.3 2006/12/21 18:02:42 tarus Exp $
8    */
9   package org.jrobin.core.jrrd;
10  
11  import java.io.File;
12  import java.io.IOException;
13  import java.io.PrintStream;
14  import java.text.DecimalFormat;
15  import java.text.NumberFormat;
16  import java.util.ArrayList;
17  import java.util.Calendar;
18  import java.util.Date;
19  import java.util.Iterator;
20  
21  /**
22   * Instances of this class model
23   * <a href="http://people.ee.ethz.ch/~oetiker/webtools/rrdtool/">Round Robin Database</a>
24   * (RRD) files.
25   *
26   * @author <a href="mailto:ciaran@codeloop.com">Ciaran Treanor</a>
27   * @version $Revision: 1.3 $
28   */
29  public class RRDatabase {
30  
31  	RRDFile rrdFile;
32  
33  	// RRD file name
34  	private String name;
35  	Header header;
36  	ArrayList<DataSource> dataSources;
37  	ArrayList<Archive> archives;
38  	Date lastUpdate;
39  
40  	/**
41  	 * Creates a database to read from.
42  	 *
43  	 * @param name the filename of the file to read from.
44  	 * @throws IOException if an I/O error occurs.
45  	 */
46  	public RRDatabase(String name) throws IOException {
47  		this(new File(name));
48  	}
49  
50  	/**
51  	 * Creates a database to read from.
52  	 *
53  	 * @param file the file to read from.
54  	 * @throws IOException if an I/O error occurs.
55  	 */
56  	public RRDatabase(File file) throws IOException {
57  
58  		name = file.getName();
59  		rrdFile = new RRDFile(file);
60  		header = new Header(rrdFile);
61  
62  		// Load the data sources
63  		dataSources = new ArrayList<DataSource>();
64  
65  		for (int i = 0; i < header.dsCount; i++) {
66  			DataSource ds = new DataSource(rrdFile);
67  
68  			dataSources.add(ds);
69  		}
70  
71  		// Load the archives
72  		archives = new ArrayList<Archive>();
73  
74  		for (int i = 0; i < header.rraCount; i++) {
75  			Archive archive = new Archive(this);
76  
77  			archives.add(archive);
78  		}
79  
80  		rrdFile.align();
81  
82  		lastUpdate = new Date((long) (rrdFile.readInt()) * 1000);
83  
84  		// Load PDPStatus(s)
85  		for (int i = 0; i < header.dsCount; i++) {
86  			DataSource ds = dataSources.get(i);
87  
88  			ds.loadPDPStatusBlock(rrdFile);
89  		}
90  
91  		// Load CDPStatus(s)
92  		for (int i = 0; i < header.rraCount; i++) {
93  			Archive archive = archives.get(i);
94  
95  			archive.loadCDPStatusBlocks(rrdFile, header.dsCount);
96  		}
97  
98  		// Load current row information for each archive
99  		for (int i = 0; i < header.rraCount; i++) {
100 			Archive archive = archives.get(i);
101 
102 			archive.loadCurrentRow(rrdFile);
103 		}
104 
105 		// Now load the data
106 		for (int i = 0; i < header.rraCount; i++) {
107 			Archive archive = archives.get(i);
108 
109 			archive.loadData(rrdFile, header.dsCount);
110 		}
111 	}
112 
113 	/**
114 	 * Returns the <code>Header</code> for this database.
115 	 *
116 	 * @return the <code>Header</code> for this database.
117 	 */
118 	public Header getHeader() {
119 		return header;
120 	}
121 
122 	/**
123 	 * Returns the date this database was last updated. To convert this date to
124 	 * the form returned by <code>rrdtool last</code> call Date.getTime() and
125 	 * divide the result by 1000.
126 	 *
127 	 * @return the date this database was last updated.
128 	 */
129 	public Date getLastUpdate() {
130 		return lastUpdate;
131 	}
132 
133 	/**
134 	 * Returns the <code>DataSource</code> at the specified position in this database.
135 	 *
136 	 * @param index index of <code>DataSource</code> to return.
137 	 * @return the <code>DataSource</code> at the specified position in this database
138 	 */
139 	public DataSource getDataSource(int index) {
140 		return dataSources.get(index);
141 	}
142 
143 	/**
144 	 * Returns an iterator over the data sources in this database in proper sequence.
145 	 *
146 	 * @return an iterator over the data sources in this database in proper sequence.
147 	 */
148 	public Iterator<DataSource> getDataSources() {
149 		return dataSources.iterator();
150 	}
151 
152 	/**
153 	 * Returns the <code>Archive</code> at the specified position in this database.
154 	 *
155 	 * @param index index of <code>Archive</code> to return.
156 	 * @return the <code>Archive</code> at the specified position in this database.
157 	 */
158 	public Archive getArchive(int index) {
159 		return archives.get(index);
160 	}
161 
162 	/**
163 	 * Returns an iterator over the archives in this database in proper sequence.
164 	 *
165 	 * @return an iterator over the archives in this database in proper sequence.
166 	 */
167 	public Iterator<Archive> getArchives() {
168 		return archives.iterator();
169 	}
170 
171 	/**
172 	 * Returns the number of archives in this database.
173 	 *
174 	 * @return the number of archives in this database.
175 	 */
176 	public int getNumArchives() {
177 		return header.rraCount;
178 	}
179 
180 	/**
181 	 * Returns an iterator over the archives in this database of the given type
182 	 * in proper sequence.
183 	 *
184 	 * @param type the consolidation function that should have been applied to
185 	 *             the data.
186 	 * @return an iterator over the archives in this database of the given type
187 	 *         in proper sequence.
188 	 */
189 	public Iterator<Archive> getArchives(ConsolidationFunctionType type) {
190 		return getArchiveList(type).iterator();
191 	}
192 
193 	ArrayList<Archive> getArchiveList(ConsolidationFunctionType type) {
194 
195 		ArrayList<Archive> subset = new ArrayList<Archive>();
196 
197 		for (int i = 0; i < archives.size(); i++) {
198 			Archive archive = archives.get(i);
199 
200 			if (archive.getType().equals(type)) {
201 				subset.add(archive);
202 			}
203 		}
204 
205 		return subset;
206 	}
207 
208 	/**
209 	 * Closes this database stream and releases any associated system resources.
210 	 *
211 	 * @throws IOException if an I/O error occurs.
212 	 */
213 	public void close() throws IOException {
214 		rrdFile.close();
215 	}
216 
217 	/**
218 	 * Outputs the header information of the database to the given print stream
219 	 * using the default number format. The default format for <code>double</code>
220 	 * is 0.0000000000E0.
221 	 *
222 	 * @param s the PrintStream to print the header information to.
223 	 */
224 	public void printInfo(PrintStream s) {
225 
226 		NumberFormat numberFormat = new DecimalFormat("0.0000000000E0");
227 
228 		printInfo(s, numberFormat);
229 	}
230 
231 	/**
232 	 * Returns data from the database corresponding to the given consolidation
233 	 * function and a step size of 1.
234 	 *
235 	 * @param type the consolidation function that should have been applied to
236 	 *             the data.
237 	 * @return the raw data.
238 	 * @throws RRDException if there was a problem locating a data archive with
239 	 *                      the requested consolidation function.
240 	 * @throws IOException  if there was a problem reading data from the database.
241 	 */
242 	public DataChunk getData(ConsolidationFunctionType type)
243 			throws RRDException, IOException {
244 		return getData(type, 1L);
245 	}
246 
247 	/**
248 	 * Returns data from the database corresponding to the given consolidation
249 	 * function.
250 	 *
251 	 * @param type the consolidation function that should have been applied to
252 	 *             the data.
253 	 * @param step the step size to use.
254 	 * @return the raw data.
255 	 * @throws RRDException if there was a problem locating a data archive with
256 	 *                      the requested consolidation function.
257 	 * @throws IOException  if there was a problem reading data from the database.
258 	 */
259 	public DataChunk getData(ConsolidationFunctionType type, long step)
260 			throws RRDException, IOException {
261 
262 		ArrayList<Archive> possibleArchives = getArchiveList(type);
263 
264 		if (possibleArchives.size() == 0) {
265 			throw new RRDException("Database does not contain an Archive of consolidation function type "
266 					+ type);
267 		}
268 
269 		Calendar endCal = Calendar.getInstance();
270 
271 		endCal.set(Calendar.MILLISECOND, 0);
272 
273 		Calendar startCal = (Calendar) endCal.clone();
274 
275 		startCal.add(Calendar.DATE, -1);
276 
277 		long end = endCal.getTime().getTime() / 1000;
278 		long start = startCal.getTime().getTime() / 1000;
279 		Archive archive = findBestArchive(start, end, step, possibleArchives);
280 
281 		// Tune the parameters
282 		step = header.pdpStep * archive.pdpCount;
283 		start -= start % step;
284 
285 		if (end % step != 0) {
286 			end += step - end % step;
287 		}
288 
289 		int rows = (int) ((end - start) / step + 1);
290 
291 		//cat.debug("start " + start + " end " + end + " step " + step + " rows "
292 		//          + rows);
293 
294 		// Find start and end offsets
295 		// This is terrible - some of this should be encapsulated in Archive - CT.
296 		long lastUpdateLong = lastUpdate.getTime() / 1000;
297 		long archiveEndTime = lastUpdateLong - (lastUpdateLong % step);
298 		long archiveStartTime = archiveEndTime - (step * (archive.rowCount - 1));
299 		int startOffset = (int) ((start - archiveStartTime) / step);
300 		int endOffset = (int) ((archiveEndTime - end) / step);
301 
302 		//cat.debug("start " + archiveStartTime + " end " + archiveEndTime
303 		//          + " startOffset " + startOffset + " endOffset "
304 		//          + (archive.rowCount - endOffset));
305 
306 		DataChunk chunk = new DataChunk(start, startOffset, endOffset, step,
307 				header.dsCount, rows);
308 
309 		archive.loadData(chunk);
310 
311 		return chunk;
312 	}
313 
314 	/*
315 	 * This is almost a verbatim copy of the original C code by Tobias Oetiker.
316 	 * I need to put more of a Java style on it - CT
317 	 */
318 	private Archive findBestArchive(long start, long end, long step,
319 									ArrayList<Archive> archives) {
320 
321 		Archive archive = null;
322 		Archive bestFullArchive = null;
323 		Archive bestPartialArchive = null;
324 		long lastUpdateLong = lastUpdate.getTime() / 1000;
325 		int firstPart = 1;
326 		int firstFull = 1;
327 		long bestMatch = 0;
328 		//long bestPartRRA = 0;
329 		long bestStepDiff = 0;
330 		long tmpStepDiff = 0;
331 
332 		for (int i = 0; i < archives.size(); i++) {
333 			archive = archives.get(i);
334 
335 			long calEnd = lastUpdateLong
336 					- (lastUpdateLong
337 					% (archive.pdpCount * header.pdpStep));
338 			long calStart = calEnd
339 					- (archive.pdpCount * archive.rowCount
340 					* header.pdpStep);
341 			long fullMatch = end - start;
342 
343 			if ((calEnd >= end) && (calStart < start)) {	// Best full match
344 				tmpStepDiff = Math.abs(step - (header.pdpStep * archive.pdpCount));
345 
346 				if ((firstFull != 0) || (tmpStepDiff < bestStepDiff)) {
347 					firstFull = 0;
348 					bestStepDiff = tmpStepDiff;
349 					bestFullArchive = archive;
350 				}
351 			}
352 			else {										// Best partial match
353 				long tmpMatch = fullMatch;
354 
355 				if (calStart > start) {
356 					tmpMatch -= calStart - start;
357 				}
358 
359 				if (calEnd < end) {
360 					tmpMatch -= end - calEnd;
361 				}
362 
363 				if ((firstPart != 0) || (bestMatch < tmpMatch)) {
364 					firstPart = 0;
365 					bestMatch = tmpMatch;
366 					bestPartialArchive = archive;
367 				}
368 			}
369 		}
370 
371 		// See how the matching went
372 		// optimise this
373 		if (firstFull == 0) {
374 			archive = bestFullArchive;
375 		}
376 		else if (firstPart == 0) {
377 			archive = bestPartialArchive;
378 		}
379 
380 		return archive;
381 	}
382 
383 	/**
384 	 * Outputs the header information of the database to the given print stream
385 	 * using the given number format. The format is almost identical to that
386 	 * produced by
387 	 * <a href="http://people.ee.ethz.ch/~oetiker/webtools/rrdtool/manual/rrdinfo.html">rrdtool info</a>
388 	 *
389 	 * @param s			the PrintStream to print the header information to.
390 	 * @param numberFormat the format to print <code>double</code>s as.
391 	 */
392 	public void printInfo(PrintStream s, NumberFormat numberFormat) {
393 
394 		s.print("filename = \"");
395 		s.print(name);
396 		s.println("\"");
397 		s.print("rrd_version = \"");
398 		s.print(header.version);
399 		s.println("\"");
400 		s.print("step = ");
401 		s.println(header.pdpStep);
402 		s.print("last_update = ");
403 		s.println(lastUpdate.getTime() / 1000);
404 
405 		for (Iterator<DataSource> i = dataSources.iterator(); i.hasNext();) {
406 			DataSource ds = i.next();
407 
408 			ds.printInfo(s, numberFormat);
409 		}
410 
411 		int index = 0;
412 
413 		for (Iterator<Archive> i = archives.iterator(); i.hasNext();) {
414 			Archive archive = i.next();
415 
416 			archive.printInfo(s, numberFormat, index++);
417 		}
418 	}
419 
420 	/**
421 	 * Outputs the content of the database to the given print stream
422 	 * as a stream of XML. The XML format is almost identical to that produced by
423 	 * <a href="http://people.ee.ethz.ch/~oetiker/webtools/rrdtool/manual/rrddump.html">rrdtool dump</a>
424 	 *
425 	 * @param s the PrintStream to send the XML to.
426 	 */
427 	public void toXml(PrintStream s) {
428 
429 		s.println("<!--");
430 		s.println("  -- Round Robin RRDatabase Dump ");
431 		s.println("  -- Generated by jRRD <ciaran@codeloop.com>");
432 		s.println("  -->");
433 		s.println("<rrd>");
434 		s.print("\t<version> ");
435 		s.print(header.version);
436 		s.println(" </version>");
437 		s.print("\t<step> ");
438 		s.print(header.pdpStep);
439 		s.println(" </step> <!-- Seconds -->");
440 		s.print("\t<lastupdate> ");
441 		s.print(lastUpdate.getTime() / 1000);
442 		s.print(" </lastupdate> <!-- ");
443 		s.print(lastUpdate.toString());
444 		s.println(" -->");
445 		s.println();
446 
447 		for (int i = 0; i < header.dsCount; i++) {
448 			DataSource ds = dataSources.get(i);
449 
450 			ds.toXml(s);
451 		}
452 
453 		s.println("<!-- Round Robin Archives -->");
454 
455 		for (int i = 0; i < header.rraCount; i++) {
456 			Archive archive = archives.get(i);
457 
458 			archive.toXml(s);
459 		}
460 
461 		s.println("</rrd>");
462 	}
463 
464 	/**
465 	 * Returns a summary the contents of this database.
466 	 *
467 	 * @return a summary of the information contained in this database.
468 	 */
469 	public String toString() {
470 
471 		StringBuffer sb = new StringBuffer("\n");
472 
473 		sb.append(header.toString());
474 
475 		for (Iterator<DataSource> i = dataSources.iterator(); i.hasNext();) {
476 			DataSource ds = i.next();
477 
478 			sb.append("\n\t");
479 			sb.append(ds.toString());
480 		}
481 
482 		for (Iterator<Archive> i = archives.iterator(); i.hasNext();) {
483 			Archive archive = i.next();
484 
485 			sb.append("\n\t");
486 			sb.append(archive.toString());
487 		}
488 
489 		return sb.toString();
490 	}
491 }