001 /* ============================================================
002 * JRobin : Pure java implementation of RRDTool's functionality
003 * ============================================================
004 *
005 * Project Info: http://www.jrobin.org
006 * Project Lead: Sasa Markovic (saxon@jrobin.org);
007 *
008 * (C) Copyright 2003-2005, by Sasa Markovic.
009 *
010 * Developers: Sasa Markovic (saxon@jrobin.org)
011 *
012 *
013 * This library is free software; you can redistribute it and/or modify it under the terms
014 * of the GNU Lesser General Public License as published by the Free Software Foundation;
015 * either version 2.1 of the License, or (at your option) any later version.
016 *
017 * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
018 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
019 * See the GNU Lesser General Public License for more details.
020 *
021 * You should have received a copy of the GNU Lesser General Public License along with this
022 * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
023 * Boston, MA 02111-1307, USA.
024 */
025
026 package org.jrobin.core;
027
028 import java.io.ByteArrayOutputStream;
029 import java.io.FileOutputStream;
030 import java.io.IOException;
031 import java.io.OutputStream;
032 import java.util.*;
033
034 /**
035 * <p>Class to represent definition of new Round Robin Database (RRD).
036 * Object of this class is used to create
037 * new RRD from scratch - pass its reference as a <code>RrdDb</code> constructor
038 * argument (see documentation for {@link RrdDb RrdDb} class). <code>RrdDef</code>
039 * object <b>does not</b> actually create new RRD. It just holds all necessary
040 * information which will be used during the actual creation process</p>
041 * <p/>
042 * <p>RRD definition (RrdDef object) consists of the following elements:</p>
043 * <p/>
044 * <ul>
045 * <li> path to RRD that will be created
046 * <li> starting timestamp
047 * <li> step
048 * <li> one or more datasource definitions
049 * <li> one or more archive definitions
050 * </ul>
051 * <p>RrdDef provides API to set all these elements. For the complete explanation of all
052 * RRD definition parameters, see RRDTool's
053 * <a href="../../../../man/rrdcreate.html" target="man">rrdcreate man page</a>.</p>
054 *
055 * @author <a href="mailto:saxon@jrobin.org">Sasa Markovic</a>
056 */
057 public class RrdDef {
058 /**
059 * default RRD step to be used if not specified in constructor (300 seconds)
060 */
061 public static final long DEFAULT_STEP = 300L;
062 /**
063 * if not specified in constructor, starting timestamp will be set to the
064 * current timestamp plus DEFAULT_INITIAL_SHIFT seconds (-10)
065 */
066 public static final long DEFAULT_INITIAL_SHIFT = -10L;
067
068 private String path;
069 private long startTime = Util.getTime() + DEFAULT_INITIAL_SHIFT;
070 private long step = DEFAULT_STEP;
071 private ArrayList<DsDef> dsDefs = new ArrayList<DsDef>();
072 private ArrayList<ArcDef> arcDefs = new ArrayList<ArcDef>();
073
074 /**
075 * <p>Creates new RRD definition object with the given path.
076 * When this object is passed to
077 * <code>RrdDb</code> constructor, new RRD will be created using the
078 * specified path. </p>
079 *
080 * @param path Path to new RRD.
081 * @throws RrdException Thrown if name is invalid (null or empty).
082 */
083 public RrdDef(String path) throws RrdException {
084 if (path == null || path.length() == 0) {
085 throw new RrdException("No path specified");
086 }
087 this.path = path;
088 }
089
090 /**
091 * <p>Creates new RRD definition object with the given path and step.</p>
092 *
093 * @param path Path to new RRD.
094 * @param step RRD step.
095 * @throws RrdException Thrown if supplied parameters are invalid.
096 */
097 public RrdDef(String path, long step) throws RrdException {
098 this(path);
099 if (step <= 0) {
100 throw new RrdException("Invalid RRD step specified: " + step);
101 }
102 this.step = step;
103 }
104
105 /**
106 * <p>Creates new RRD definition object with the given path, starting timestamp
107 * and step.</p>
108 *
109 * @param path Path to new RRD.
110 * @param startTime RRD starting timestamp.
111 * @param step RRD step.
112 * @throws RrdException Thrown if supplied parameters are invalid.
113 */
114 public RrdDef(String path, long startTime, long step) throws RrdException {
115 this(path, step);
116 if (startTime < 0) {
117 throw new RrdException("Invalid RRD start time specified: " + startTime);
118 }
119 this.startTime = startTime;
120 }
121
122 /**
123 * Returns path for the new RRD
124 *
125 * @return path to the new RRD which should be created
126 */
127 public String getPath() {
128 return path;
129 }
130
131 /**
132 * Returns starting timestamp for the RRD that should be created.
133 *
134 * @return RRD starting timestamp
135 */
136 public long getStartTime() {
137 return startTime;
138 }
139
140 /**
141 * Returns time step for the RRD that will be created.
142 *
143 * @return RRD step
144 */
145 public long getStep() {
146 return step;
147 }
148
149 /**
150 * Sets path to RRD.
151 *
152 * @param path to new RRD.
153 */
154 public void setPath(String path) {
155 this.path = path;
156 }
157
158 /**
159 * Sets RRD's starting timestamp.
160 *
161 * @param startTime starting timestamp.
162 */
163 public void setStartTime(long startTime) {
164 this.startTime = startTime;
165 }
166
167 /**
168 * Sets RRD's starting timestamp.
169 *
170 * @param date starting date
171 */
172 public void setStartTime(Date date) {
173 this.startTime = Util.getTimestamp(date);
174 }
175
176 /**
177 * Sets RRD's starting timestamp.
178 *
179 * @param gc starting date
180 */
181 public void setStartTime(Calendar gc) {
182 this.startTime = Util.getTimestamp(gc);
183 }
184
185 /**
186 * Sets RRD's time step.
187 *
188 * @param step RRD time step.
189 */
190 public void setStep(long step) {
191 this.step = step;
192 }
193
194 /**
195 * Adds single datasource definition represented with object of class <code>DsDef</code>.
196 *
197 * @param dsDef Datasource definition.
198 * @throws RrdException Thrown if new datasource definition uses already used data
199 * source name.
200 */
201 public void addDatasource(DsDef dsDef) throws RrdException {
202 if (dsDefs.contains(dsDef)) {
203 throw new RrdException("Datasource already defined: " + dsDef.dump());
204 }
205 dsDefs.add(dsDef);
206 }
207
208 /**
209 * <p>Adds single datasource to RRD definition by specifying its data source name, source type,
210 * heartbeat, minimal and maximal value. For the complete explanation of all data
211 * source definition parameters see RRDTool's
212 * <a href="../../../../man/rrdcreate.html" target="man">rrdcreate man page</a>.</p>
213 * <p/>
214 * <p><b>IMPORTANT NOTE:</b> If datasource name ends with '!', corresponding archives will never
215 * store NaNs as datasource values. In that case, NaN datasource values will be silently
216 * replaced with zeros by the framework.</p>
217 *
218 * @param dsName Data source name.
219 * @param dsType Data source type. Valid types are "COUNTER",
220 * "GAUGE", "DERIVE" and "ABSOLUTE" (these string constants are conveniently defined in
221 * the {@link DsTypes} class).
222 * @param heartbeat Data source heartbeat.
223 * @param minValue Minimal acceptable value. Use <code>Double.NaN</code> if unknown.
224 * @param maxValue Maximal acceptable value. Use <code>Double.NaN</code> if unknown.
225 * @throws RrdException Thrown if new datasource definition uses already used data
226 * source name.
227 */
228 public void addDatasource(String dsName, String dsType, long heartbeat,
229 double minValue, double maxValue) throws RrdException {
230 addDatasource(new DsDef(dsName, dsType, heartbeat, minValue, maxValue));
231 }
232
233 /**
234 * Adds single datasource to RRD definition from a RRDTool-like
235 * datasource definition string. The string must have six elements separated with colons
236 * (:) in the following order:<p>
237 * <pre>
238 * DS:name:type:heartbeat:minValue:maxValue
239 * </pre>
240 * For example:</p>
241 * <pre>
242 * DS:input:COUNTER:600:0:U
243 * </pre>
244 * For more information on datasource definition parameters see <code>rrdcreate</code>
245 * man page.<p>
246 *
247 * @param rrdToolDsDef Datasource definition string with the syntax borrowed from RRDTool.
248 * @throws RrdException Thrown if invalid string is supplied.
249 */
250 public void addDatasource(String rrdToolDsDef) throws RrdException {
251 RrdException rrdException = new RrdException(
252 "Wrong rrdtool-like datasource definition: " + rrdToolDsDef);
253 StringTokenizer tokenizer = new StringTokenizer(rrdToolDsDef, ":");
254 if (tokenizer.countTokens() != 6) {
255 throw rrdException;
256 }
257 String[] tokens = new String[6];
258 for (int curTok = 0; tokenizer.hasMoreTokens(); curTok++) {
259 tokens[curTok] = tokenizer.nextToken();
260 }
261 if (!tokens[0].equalsIgnoreCase("DS")) {
262 throw rrdException;
263 }
264 String dsName = tokens[1];
265 String dsType = tokens[2];
266 long dsHeartbeat;
267 try {
268 dsHeartbeat = Long.parseLong(tokens[3]);
269 }
270 catch (NumberFormatException nfe) {
271 throw rrdException;
272 }
273 double minValue = Double.NaN;
274 if (!tokens[4].equalsIgnoreCase("U")) {
275 try {
276 minValue = Double.parseDouble(tokens[4]);
277 }
278 catch (NumberFormatException nfe) {
279 throw rrdException;
280 }
281 }
282 double maxValue = Double.NaN;
283 if (!tokens[5].equalsIgnoreCase("U")) {
284 try {
285 maxValue = Double.parseDouble(tokens[5]);
286 }
287 catch (NumberFormatException nfe) {
288 throw rrdException;
289 }
290 }
291 addDatasource(new DsDef(dsName, dsType, dsHeartbeat, minValue, maxValue));
292 }
293
294 /**
295 * Adds data source definitions to RRD definition in bulk.
296 *
297 * @param dsDefs Array of data source definition objects.
298 * @throws RrdException Thrown if duplicate data source name is used.
299 */
300 public void addDatasource(DsDef[] dsDefs) throws RrdException {
301 for (DsDef dsDef : dsDefs) {
302 addDatasource(dsDef);
303 }
304 }
305
306 /**
307 * Adds single archive definition represented with object of class <code>ArcDef</code>.
308 *
309 * @param arcDef Archive definition.
310 * @throws RrdException Thrown if archive with the same consolidation function
311 * and the same number of steps is already added.
312 */
313 public void addArchive(ArcDef arcDef) throws RrdException {
314 if (arcDefs.contains(arcDef)) {
315 throw new RrdException("Archive already defined: " + arcDef.dump());
316 }
317 arcDefs.add(arcDef);
318 }
319
320 /**
321 * Adds archive definitions to RRD definition in bulk.
322 *
323 * @param arcDefs Array of archive definition objects
324 * @throws RrdException Thrown if RRD definition already contains archive with
325 * the same consolidation function and the same number of steps.
326 */
327 public void addArchive(ArcDef[] arcDefs) throws RrdException {
328 for (ArcDef arcDef : arcDefs) {
329 addArchive(arcDef);
330 }
331 }
332
333 /**
334 * Adds single archive definition by specifying its consolidation function, X-files factor,
335 * number of steps and rows. For the complete explanation of all archive
336 * definition parameters see RRDTool's
337 * <a href="../../../../man/rrdcreate.html" target="man">rrdcreate man page</a>.</p>
338 *
339 * @param consolFun Consolidation function. Valid values are "AVERAGE",
340 * "MIN", "MAX" and "LAST" (these constants are conveniently defined in the
341 * {@link ConsolFuns} class)
342 * @param xff X-files factor. Valid values are between 0 and 1.
343 * @param steps Number of archive steps
344 * @param rows Number of archive rows
345 * @throws RrdException Thrown if archive with the same consolidation function
346 * and the same number of steps is already added.
347 */
348 public void addArchive(String consolFun, double xff, int steps, int rows)
349 throws RrdException {
350 addArchive(new ArcDef(consolFun, xff, steps, rows));
351 }
352
353 /**
354 * Adds single archive to RRD definition from a RRDTool-like
355 * archive definition string. The string must have five elements separated with colons
356 * (:) in the following order:<p>
357 * <pre>
358 * RRA:consolidationFunction:XFilesFactor:steps:rows
359 * </pre>
360 * For example:</p>
361 * <pre>
362 * RRA:AVERAGE:0.5:10:1000
363 * </pre>
364 * For more information on archive definition parameters see <code>rrdcreate</code>
365 * man page.<p>
366 *
367 * @param rrdToolArcDef Archive definition string with the syntax borrowed from RRDTool.
368 * @throws RrdException Thrown if invalid string is supplied.
369 */
370 public void addArchive(String rrdToolArcDef) throws RrdException {
371 RrdException rrdException = new RrdException(
372 "Wrong rrdtool-like archive definition: " + rrdToolArcDef);
373 StringTokenizer tokenizer = new StringTokenizer(rrdToolArcDef, ":");
374 if (tokenizer.countTokens() != 5) {
375 throw rrdException;
376 }
377 String[] tokens = new String[5];
378 for (int curTok = 0; tokenizer.hasMoreTokens(); curTok++) {
379 tokens[curTok] = tokenizer.nextToken();
380 }
381 if (!tokens[0].equalsIgnoreCase("RRA")) {
382 throw rrdException;
383 }
384 String consolFun = tokens[1];
385 double xff;
386 try {
387 xff = Double.parseDouble(tokens[2]);
388 }
389 catch (NumberFormatException nfe) {
390 throw rrdException;
391 }
392 int steps;
393 try {
394 steps = Integer.parseInt(tokens[3]);
395 }
396 catch (NumberFormatException nfe) {
397 throw rrdException;
398 }
399 int rows;
400 try {
401 rows = Integer.parseInt(tokens[4]);
402 }
403 catch (NumberFormatException nfe) {
404 throw rrdException;
405 }
406 addArchive(new ArcDef(consolFun, xff, steps, rows));
407 }
408
409 void validate() throws RrdException {
410 if (dsDefs.size() == 0) {
411 throw new RrdException("No RRD datasource specified. At least one is needed.");
412 }
413 if (arcDefs.size() == 0) {
414 throw new RrdException("No RRD archive specified. At least one is needed.");
415 }
416 }
417
418 /**
419 * Returns all data source definition objects specified so far.
420 *
421 * @return Array of data source definition objects
422 */
423 public DsDef[] getDsDefs() {
424 return dsDefs.toArray(new DsDef[0]);
425 }
426
427 /**
428 * Returns all archive definition objects specified so far.
429 *
430 * @return Array of archive definition objects.
431 */
432 public ArcDef[] getArcDefs() {
433 return arcDefs.toArray(new ArcDef[0]);
434 }
435
436 /**
437 * Returns number of defined datasources.
438 *
439 * @return Number of defined datasources.
440 */
441 public int getDsCount() {
442 return dsDefs.size();
443 }
444
445 /**
446 * Returns number of defined archives.
447 *
448 * @return Number of defined archives.
449 */
450 public int getArcCount() {
451 return arcDefs.size();
452 }
453
454 /**
455 * Returns string that represents all specified RRD creation parameters. Returned string
456 * has the syntax of RRDTool's <code>create</code> command.
457 *
458 * @return Dumped content of <code>RrdDb</code> object.
459 */
460 public String dump() {
461 StringBuffer buffer = new StringBuffer("create \"");
462 buffer.append(path).append("\"");
463 buffer.append(" --start ").append(getStartTime());
464 buffer.append(" --step ").append(getStep()).append(" ");
465 for (DsDef dsDef : dsDefs) {
466 buffer.append(dsDef.dump()).append(" ");
467 }
468 for (ArcDef arcDef : arcDefs) {
469 buffer.append(arcDef.dump()).append(" ");
470 }
471 return buffer.toString().trim();
472 }
473
474 String getRrdToolCommand() {
475 return dump();
476 }
477
478 void removeDatasource(String dsName) throws RrdException {
479 for (int i = 0; i < dsDefs.size(); i++) {
480 DsDef dsDef = dsDefs.get(i);
481 if (dsDef.getDsName().equals(dsName)) {
482 dsDefs.remove(i);
483 return;
484 }
485 }
486 throw new RrdException("Could not find datasource named '" + dsName + "'");
487 }
488
489 void saveSingleDatasource(String dsName) {
490 Iterator<DsDef> it = dsDefs.iterator();
491 while (it.hasNext()) {
492 DsDef dsDef = it.next();
493 if (!dsDef.getDsName().equals(dsName)) {
494 it.remove();
495 }
496 }
497 }
498
499 void removeArchive(String consolFun, int steps) throws RrdException {
500 ArcDef arcDef = findArchive(consolFun, steps);
501 if (!arcDefs.remove(arcDef)) {
502 throw new RrdException("Could not remove archive " + consolFun + "/" + steps);
503 }
504 }
505
506 ArcDef findArchive(String consolFun, int steps) throws RrdException {
507 for (ArcDef arcDef : arcDefs) {
508 if (arcDef.getConsolFun().equals(consolFun) && arcDef.getSteps() == steps) {
509 return arcDef;
510 }
511 }
512 throw new RrdException("Could not find archive " + consolFun + "/" + steps);
513 }
514
515 /**
516 * Exports RrdDef object to output stream in XML format. Generated XML code can be parsed
517 * with {@link RrdDefTemplate} class.
518 *
519 * @param out Output stream
520 */
521 public void exportXmlTemplate(OutputStream out) {
522 XmlWriter xml = new XmlWriter(out);
523 xml.startTag("rrd_def");
524 xml.writeTag("path", getPath());
525 xml.writeTag("step", getStep());
526 xml.writeTag("start", getStartTime());
527 // datasources
528 DsDef[] dsDefs = getDsDefs();
529 for (DsDef dsDef : dsDefs) {
530 xml.startTag("datasource");
531 xml.writeTag("name", dsDef.getDsName());
532 xml.writeTag("type", dsDef.getDsType());
533 xml.writeTag("heartbeat", dsDef.getHeartbeat());
534 xml.writeTag("min", dsDef.getMinValue(), "U");
535 xml.writeTag("max", dsDef.getMaxValue(), "U");
536 xml.closeTag(); // datasource
537 }
538 ArcDef[] arcDefs = getArcDefs();
539 for (ArcDef arcDef : arcDefs) {
540 xml.startTag("archive");
541 xml.writeTag("cf", arcDef.getConsolFun());
542 xml.writeTag("xff", arcDef.getXff());
543 xml.writeTag("steps", arcDef.getSteps());
544 xml.writeTag("rows", arcDef.getRows());
545 xml.closeTag(); // archive
546 }
547 xml.closeTag(); // rrd_def
548 xml.flush();
549 }
550
551 /**
552 * Exports RrdDef object to string in XML format. Generated XML string can be parsed
553 * with {@link RrdDefTemplate} class.
554 *
555 * @return XML formatted string representing this RrdDef object
556 */
557 public String exportXmlTemplate() {
558 ByteArrayOutputStream out = new ByteArrayOutputStream();
559 exportXmlTemplate(out);
560 return out.toString();
561 }
562
563 /**
564 * Exports RrdDef object to a file in XML format. Generated XML code can be parsed
565 * with {@link RrdDefTemplate} class.
566 *
567 * @param filePath Path to the file
568 */
569 public void exportXmlTemplate(String filePath) throws IOException {
570 FileOutputStream out = new FileOutputStream(filePath, false);
571 exportXmlTemplate(out);
572 out.close();
573 }
574
575 /**
576 * Returns the number of storage bytes required to create RRD from this
577 * RrdDef object.
578 *
579 * @return Estimated byte count of the underlying RRD storage.
580 */
581 public long getEstimatedSize() {
582 int dsCount = dsDefs.size();
583 int arcCount = arcDefs.size();
584 int rowsCount = 0;
585 for (ArcDef arcDef : arcDefs) {
586 rowsCount += arcDef.getRows();
587 }
588 return calculateSize(dsCount, arcCount, rowsCount);
589 }
590
591 static long calculateSize(int dsCount, int arcCount, int rowsCount) {
592 // return 64L + 128L * dsCount + 56L * arcCount +
593 // 20L * dsCount * arcCount + 8L * dsCount * rowsCount;
594 return (24L + 48L * dsCount + 16L * arcCount +
595 20L * dsCount * arcCount + 8L * dsCount * rowsCount) +
596 (1L + 2L * dsCount + arcCount) * 2L * RrdPrimitive.STRING_LENGTH;
597 }
598
599 /**
600 * Compares the current RrdDef with another. RrdDefs are considered equal if:<p>
601 * <ul>
602 * <li>RRD steps match
603 * <li>all datasources have exactly the same definition in both RrdDef objects (datasource names,
604 * types, heartbeat, min and max values must match)
605 * <li>all archives have exactly the same definition in both RrdDef objects (archive consolidation
606 * functions, X-file factors, step and row counts must match)
607 * </ul>
608 *
609 * @param obj The second RrdDef object
610 * @return true if RrdDefs match exactly, false otherwise
611 */
612 public boolean equals(Object obj) {
613 if (obj == null || !(obj instanceof RrdDef)) {
614 return false;
615 }
616 RrdDef rrdDef2 = (RrdDef) obj;
617 // check primary RRD step
618 if (step != rrdDef2.step) {
619 return false;
620 }
621 // check datasources
622 DsDef[] dsDefs = getDsDefs(), dsDefs2 = rrdDef2.getDsDefs();
623 if (dsDefs.length != dsDefs2.length) {
624 return false;
625 }
626 for (DsDef dsDef : dsDefs) {
627 boolean matched = false;
628 for (DsDef dsDef2 : dsDefs2) {
629 if (dsDef.exactlyEqual(dsDef2)) {
630 matched = true;
631 break;
632 }
633 }
634 // this datasource could not be matched
635 if (!matched) {
636 return false;
637 }
638 }
639 // check archives
640 ArcDef[] arcDefs = getArcDefs(), arcDefs2 = rrdDef2.getArcDefs();
641 if (arcDefs.length != arcDefs2.length) {
642 return false;
643 }
644 for (ArcDef arcDef : arcDefs) {
645 boolean matched = false;
646 for (ArcDef arcDef2 : arcDefs2) {
647 if (arcDef.exactlyEqual(arcDef2)) {
648 matched = true;
649 break;
650 }
651 }
652 // this archive could not be matched
653 if (!matched) {
654 return false;
655 }
656 }
657 // everything matches
658 return true;
659 }
660
661 /**
662 * Removes all datasource definitions.
663 */
664 public void removeDatasources() {
665 dsDefs.clear();
666 }
667
668 /**
669 * Removes all RRA archive definitions.
670 */
671 public void removeArchives() {
672 arcDefs.clear();
673 }
674 }