001 /*
002 * Copyright (C) 2001 Ciaran Treanor <ciaran@codeloop.com>
003 *
004 * Distributable under GPL license.
005 * See terms of license at gnu.org.
006 *
007 * $Id: RRDatabase.java,v 1.3 2006/12/21 18:02:42 tarus Exp $
008 */
009 package org.jrobin.core.jrrd;
010
011 import java.io.File;
012 import java.io.IOException;
013 import java.io.PrintStream;
014 import java.text.DecimalFormat;
015 import java.text.NumberFormat;
016 import java.util.ArrayList;
017 import java.util.Calendar;
018 import java.util.Date;
019 import java.util.Iterator;
020
021 /**
022 * Instances of this class model
023 * <a href="http://people.ee.ethz.ch/~oetiker/webtools/rrdtool/">Round Robin Database</a>
024 * (RRD) files.
025 *
026 * @author <a href="mailto:ciaran@codeloop.com">Ciaran Treanor</a>
027 * @version $Revision: 1.3 $
028 */
029 public class RRDatabase {
030
031 RRDFile rrdFile;
032
033 // RRD file name
034 private String name;
035 Header header;
036 ArrayList<DataSource> dataSources;
037 ArrayList<Archive> archives;
038 Date lastUpdate;
039
040 /**
041 * Creates a database to read from.
042 *
043 * @param name the filename of the file to read from.
044 * @throws IOException if an I/O error occurs.
045 */
046 public RRDatabase(String name) throws IOException {
047 this(new File(name));
048 }
049
050 /**
051 * Creates a database to read from.
052 *
053 * @param file the file to read from.
054 * @throws IOException if an I/O error occurs.
055 */
056 public RRDatabase(File file) throws IOException {
057
058 name = file.getName();
059 rrdFile = new RRDFile(file);
060 header = new Header(rrdFile);
061
062 // Load the data sources
063 dataSources = new ArrayList<DataSource>();
064
065 for (int i = 0; i < header.dsCount; i++) {
066 DataSource ds = new DataSource(rrdFile);
067
068 dataSources.add(ds);
069 }
070
071 // Load the archives
072 archives = new ArrayList<Archive>();
073
074 for (int i = 0; i < header.rraCount; i++) {
075 Archive archive = new Archive(this);
076
077 archives.add(archive);
078 }
079
080 rrdFile.align();
081
082 lastUpdate = new Date((long) (rrdFile.readInt()) * 1000);
083
084 // Load PDPStatus(s)
085 for (int i = 0; i < header.dsCount; i++) {
086 DataSource ds = dataSources.get(i);
087
088 ds.loadPDPStatusBlock(rrdFile);
089 }
090
091 // Load CDPStatus(s)
092 for (int i = 0; i < header.rraCount; i++) {
093 Archive archive = archives.get(i);
094
095 archive.loadCDPStatusBlocks(rrdFile, header.dsCount);
096 }
097
098 // Load current row information for each archive
099 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 }