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 org.jrobin.data.Aggregates;
29  import org.jrobin.data.DataProcessor;
30  
31  import java.io.ByteArrayOutputStream;
32  import java.io.FileOutputStream;
33  import java.io.IOException;
34  import java.io.OutputStream;
35  
36  /**
37   * Class used to represent data fetched from the RRD.
38   * Object of this class is created when the method
39   * {@link FetchRequest#fetchData() fetchData()} is
40   * called on a {@link FetchRequest FetchRequest} object.<p>
41   * <p/>
42   * Data returned from the RRD is, simply, just one big table filled with
43   * timestamps and corresponding datasource values.
44   * Use {@link #getRowCount() getRowCount()} method to count the number
45   * of returned timestamps (table rows).<p>
46   * <p/>
47   * The first table column is filled with timestamps. Time intervals
48   * between consecutive timestamps are guaranteed to be equal. Use
49   * {@link #getTimestamps() getTimestamps()} method to get an array of
50   * timestamps returned.<p>
51   * <p/>
52   * Remaining columns are filled with datasource values for the whole timestamp range,
53   * on a column-per-datasource basis. Use {@link #getColumnCount() getColumnCount()} to find
54   * the number of datasources and {@link #getValues(int) getValues(i)} method to obtain
55   * all values for the i-th datasource. Returned datasource values correspond to
56   * the values returned with {@link #getTimestamps() getTimestamps()} method.<p>
57   */
58  public class FetchData implements ConsolFuns {
59  	// anything fuuny will do
60  	private static final String RPN_SOURCE_NAME = "WHERE THE SPEECHLES UNITE IN A SILENT ACCORD";
61  
62  	private FetchRequest request;
63  	private String[] dsNames;
64  	private long[] timestamps;
65  	private double[][] values;
66  
67  	private Archive matchingArchive;
68  	private long arcStep;
69  	private long arcEndTime;
70  
71  	FetchData(Archive matchingArchive, FetchRequest request) throws IOException {
72  		this.matchingArchive = matchingArchive;
73  		this.arcStep = matchingArchive.getArcStep();
74  		this.arcEndTime = matchingArchive.getEndTime();
75  		this.dsNames = request.getFilter();
76  		if (this.dsNames == null) {
77  			this.dsNames = matchingArchive.getParentDb().getDsNames();
78  		}
79  		this.request = request;
80  	}
81  
82  	void setTimestamps(long[] timestamps) {
83  		this.timestamps = timestamps;
84  	}
85  
86  	void setValues(double[][] values) {
87  		this.values = values;
88  	}
89  
90  	/**
91  	 * Returns the number of rows fetched from the corresponding RRD.
92  	 * Each row represents datasource values for the specific timestamp.
93  	 *
94  	 * @return Number of rows.
95  	 */
96  	public int getRowCount() {
97  		return timestamps.length;
98  	}
99  
100 	/**
101 	 * Returns the number of columns fetched from the corresponding RRD.
102 	 * This number is always equal to the number of datasources defined
103 	 * in the RRD. Each column represents values of a single datasource.
104 	 *
105 	 * @return Number of columns (datasources).
106 	 */
107 	public int getColumnCount() {
108 		return dsNames.length;
109 	}
110 
111 	/**
112 	 * Returns an array of timestamps covering the whole range specified in the
113 	 * {@link FetchRequest FetchReguest} object.
114 	 *
115 	 * @return Array of equidistant timestamps.
116 	 */
117 	public long[] getTimestamps() {
118 		return timestamps;
119 	}
120 
121 	/**
122 	 * Returns the step with which this data was fetched.
123 	 *
124 	 * @return Step as long.
125 	 */
126 	public long getStep() {
127 		return timestamps[1] - timestamps[0];
128 	}
129 
130 	/**
131 	 * Returns all archived values for a single datasource.
132 	 * Returned values correspond to timestamps
133 	 * returned with {@link #getTimestamps() getTimestamps()} method.
134 	 *
135 	 * @param dsIndex Datasource index.
136 	 * @return Array of single datasource values.
137 	 */
138 	public double[] getValues(int dsIndex) {
139 		return values[dsIndex];
140 	}
141 
142 	/**
143 	 * Returns all archived values for all datasources.
144 	 * Returned values correspond to timestamps
145 	 * returned with {@link #getTimestamps() getTimestamps()} method.
146 	 *
147 	 * @return Two-dimensional aray of all datasource values.
148 	 */
149 	public double[][] getValues() {
150 		return values;
151 	}
152 
153 	/**
154 	 * Returns all archived values for a single datasource.
155 	 * Returned values correspond to timestamps
156 	 * returned with {@link #getTimestamps() getTimestamps()} method.
157 	 *
158 	 * @param dsName Datasource name.
159 	 * @return Array of single datasource values.
160 	 * @throws RrdException Thrown if no matching datasource name is found.
161 	 */
162 	public double[] getValues(String dsName) throws RrdException {
163 		for (int dsIndex = 0; dsIndex < getColumnCount(); dsIndex++) {
164 			if (dsName.equals(dsNames[dsIndex])) {
165 				return getValues(dsIndex);
166 			}
167 		}
168 		throw new RrdException("Datasource [" + dsName + "] not found");
169 	}
170 
171 	/**
172 	 * Returns a set of values created by applying RPN expression to the fetched data.
173 	 * For example, if you have two datasources named <code>x</code> and <code>y</code>
174 	 * in this FetchData and you want to calculate values for <code>(x+y)/2<code> use something like: <p>
175 	 * <code>getRpnValues("x,y,+,2,/");</code><p>
176 	 *
177 	 * @param rpnExpression RRDTool-like RPN expression
178 	 * @return Calculated values
179 	 * @throws RrdException Thrown if invalid RPN expression is supplied
180 	 */
181 	public double[] getRpnValues(String rpnExpression) throws RrdException {
182 		DataProcessor dataProcessor = createDataProcessor(rpnExpression);
183 		return dataProcessor.getValues(RPN_SOURCE_NAME);
184 	}
185 
186 	/**
187 	 * Returns {@link FetchRequest FetchRequest} object used to create this FetchData object.
188 	 *
189 	 * @return Fetch request object.
190 	 */
191 	public FetchRequest getRequest() {
192 		return request;
193 	}
194 
195 	/**
196 	 * Returns the first timestamp in this FetchData object.
197 	 *
198 	 * @return The smallest timestamp.
199 	 */
200 	public long getFirstTimestamp() {
201 		return timestamps[0];
202 	}
203 
204 	/**
205 	 * Returns the last timestamp in this FecthData object.
206 	 *
207 	 * @return The biggest timestamp.
208 	 */
209 	public long getLastTimestamp() {
210 		return timestamps[timestamps.length - 1];
211 	}
212 
213 	/**
214 	 * Returns Archive object which is determined to be the best match for the
215 	 * timestamps specified in the fetch request. All datasource values are obtained
216 	 * from round robin archives belonging to this archive.
217 	 *
218 	 * @return Matching archive.
219 	 */
220 	public Archive getMatchingArchive() {
221 		return matchingArchive;
222 	}
223 
224 	/**
225 	 * Returns array of datasource names found in the corresponding RRD. If the request
226 	 * was filtered (data was fetched only for selected datasources), only datasources selected
227 	 * for fetching are returned.
228 	 *
229 	 * @return Array of datasource names.
230 	 */
231 	public String[] getDsNames() {
232 		return dsNames;
233 	}
234 
235 	/**
236 	 * Retrieve the table index number of a datasource by name.  Names are case sensitive.
237 	 *
238 	 * @param dsName Name of the datasource for which to find the index.
239 	 * @return Index number of the datasources in the value table.
240 	 */
241 	public int getDsIndex(String dsName) {
242 		// Let's assume the table of dsNames is always small, so it is not necessary to use a hashmap for lookups
243 		for (int i = 0; i < dsNames.length; i++) {
244 			if (dsNames[i].equals(dsName)) {
245 				return i;
246 			}
247 		}
248 		return -1;		// Datasource not found !
249 	}
250 
251 	/**
252 	 * Dumps the content of the whole FetchData object. Useful for debugging.
253 	 */
254 	public String dump() {
255 		StringBuffer buffer = new StringBuffer("");
256 		for (int row = 0; row < getRowCount(); row++) {
257 			buffer.append(timestamps[row]);
258 			buffer.append(":  ");
259 			for (int dsIndex = 0; dsIndex < getColumnCount(); dsIndex++) {
260 				buffer.append(Util.formatDouble(values[dsIndex][row], true));
261 				buffer.append("  ");
262 			}
263 			buffer.append("\n");
264 		}
265 		return buffer.toString();
266 	}
267 
268 	/**
269 	 * Returns string representing fetched data in a RRDTool-like form.
270 	 *
271 	 * @return Fetched data as a string in a rrdfetch-like output form.
272 	 */
273 	public String toString() {
274 		// print header row
275 		StringBuffer buff = new StringBuffer();
276 		buff.append(padWithBlanks("", 10));
277 		buff.append(" ");
278 		for (String dsName : dsNames) {
279 			buff.append(padWithBlanks(dsName, 18));
280 		}
281 		buff.append("\n \n");
282 		for (int i = 0; i < timestamps.length; i++) {
283 			buff.append(padWithBlanks("" + timestamps[i], 10));
284 			buff.append(":");
285 			for (int j = 0; j < dsNames.length; j++) {
286 				double value = values[j][i];
287 				String valueStr = Double.isNaN(value) ? "nan" : Util.formatDouble(value);
288 				buff.append(padWithBlanks(valueStr, 18));
289 			}
290 			buff.append("\n");
291 		}
292 		return buff.toString();
293 	}
294 
295 	private static String padWithBlanks(String input, int width) {
296 		StringBuffer buff = new StringBuffer("");
297 		int diff = width - input.length();
298 		while (diff-- > 0) {
299 			buff.append(' ');
300 		}
301 		buff.append(input);
302 		return buff.toString();
303 	}
304 
305 	/**
306 	 * Returns single aggregated value from the fetched data for a single datasource.
307 	 *
308 	 * @param dsName	Datasource name
309 	 * @param consolFun Consolidation function to be applied to fetched datasource values.
310 	 *                  Valid consolidation functions are "MIN", "MAX", "LAST", "FIRST", "AVERAGE" and "TOTAL"
311 	 *                  (these string constants are conveniently defined in the {@link ConsolFuns} class)
312 	 * @return MIN, MAX, LAST, FIRST, AVERAGE or TOTAL value calculated from the fetched data
313 	 *         for the given datasource name
314 	 * @throws RrdException Thrown if the given datasource name cannot be found in fetched data.
315 	 */
316 	public double getAggregate(String dsName, String consolFun) throws RrdException {
317 		DataProcessor dp = createDataProcessor(null);
318 		return dp.getAggregate(dsName, consolFun);
319 	}
320 
321 	/**
322 	 * Returns aggregated value from the fetched data for a single datasource.
323 	 * Before applying aggregation functions, specified RPN expression is applied to fetched data.
324 	 * For example, if you have a gauge datasource named 'foots' but you want to find the maximum
325 	 * fetched value in meters use something like: <p>
326 	 * <code>getAggregate("foots", "MAX", "foots,0.3048,*");</code><p>
327 	 *
328 	 * @param dsName		Datasource name
329 	 * @param consolFun	 Consolidation function (MIN, MAX, LAST, FIRST, AVERAGE or TOTAL)
330 	 * @param rpnExpression RRDTool-like RPN expression
331 	 * @return Aggregated value
332 	 * @throws RrdException Thrown if the given datasource name cannot be found in fetched data, or if
333 	 *                      invalid RPN expression is supplied
334 	 * @throws IOException  Thrown in case of I/O error (unlikely to happen)
335 	 * @deprecated This method is preserved just for backward compatibility.
336 	 */
337 	public double getAggregate(String dsName, String consolFun, String rpnExpression)
338 			throws RrdException, IOException {
339 		// for backward compatibility
340 		rpnExpression = rpnExpression.replaceAll("value", dsName);
341 		return getRpnAggregate(rpnExpression, consolFun);
342 	}
343 
344 	/**
345 	 * Returns aggregated value for a set of values calculated by applying an RPN expression to the
346 	 * fetched data. For example, if you have two datasources named <code>x</code> and <code>y</code>
347 	 * in this FetchData and you want to calculate MAX value of <code>(x+y)/2<code> use something like: <p>
348 	 * <code>getRpnAggregate("x,y,+,2,/", "MAX");</code><p>
349 	 *
350 	 * @param rpnExpression RRDTool-like RPN expression
351 	 * @param consolFun	 Consolidation function (MIN, MAX, LAST, FIRST, AVERAGE or TOTAL)
352 	 * @return Aggregated value
353 	 * @throws RrdException Thrown if invalid RPN expression is supplied
354 	 */
355 	public double getRpnAggregate(String rpnExpression, String consolFun) throws RrdException {
356 		DataProcessor dataProcessor = createDataProcessor(rpnExpression);
357 		return dataProcessor.getAggregate(RPN_SOURCE_NAME, consolFun);
358 	}
359 
360 	/**
361 	 * Returns all aggregated values (MIN, MAX, LAST, FIRST, AVERAGE or TOTAL) calculated from the fetched data
362 	 * for a single datasource.
363 	 *
364 	 * @param dsName Datasource name.
365 	 * @return Simple object containing all aggregated values.
366 	 * @throws RrdException Thrown if the given datasource name cannot be found in the fetched data.
367 	 */
368 	public Aggregates getAggregates(String dsName) throws RrdException {
369 		DataProcessor dataProcessor = createDataProcessor(null);
370 		return dataProcessor.getAggregates(dsName);
371 	}
372 
373 	/**
374 	 * Returns all aggregated values for a set of values calculated by applying an RPN expression to the
375 	 * fetched data. For example, if you have two datasources named <code>x</code> and <code>y</code>
376 	 * in this FetchData and you want to calculate MIN, MAX, LAST, FIRST, AVERAGE and TOTAL value
377 	 * of <code>(x+y)/2<code> use something like: <p>
378 	 * <code>getRpnAggregates("x,y,+,2,/");</code><p>
379 	 *
380 	 * @param rpnExpression RRDTool-like RPN expression
381 	 * @return Object containing all aggregated values
382 	 * @throws RrdException Thrown if invalid RPN expression is supplied
383 	 */
384 	public Aggregates getRpnAggregates(String rpnExpression) throws RrdException, IOException {
385 		DataProcessor dataProcessor = createDataProcessor(rpnExpression);
386 		return dataProcessor.getAggregates(RPN_SOURCE_NAME);
387 	}
388 
389 	/**
390 	 * Used by ISPs which charge for bandwidth utilization on a "95th percentile" basis.<p>
391 	 * <p/>
392 	 * The 95th percentile is the highest source value left when the top 5% of a numerically sorted set
393 	 * of source data is discarded. It is used as a measure of the peak value used when one discounts
394 	 * a fair amount for transitory spikes. This makes it markedly different from the average.<p>
395 	 * <p/>
396 	 * Read more about this topic at:<p>
397 	 * <a href="http://www.red.net/support/resourcecentre/leasedline/percentile.php">Rednet</a> or<br>
398 	 * <a href="http://www.bytemark.co.uk/support/tech/95thpercentile.html">Bytemark</a>.
399 	 *
400 	 * @param dsName Datasource name
401 	 * @return 95th percentile of fetched source values
402 	 * @throws RrdException Thrown if invalid source name is supplied
403 	 */
404 	public double get95Percentile(String dsName) throws RrdException {
405 		DataProcessor dataProcessor = createDataProcessor(null);
406 		return dataProcessor.get95Percentile(dsName);
407 	}
408 
409 	/**
410 	 * Same as {@link #get95Percentile(String)}, but for a set of values calculated with the given
411 	 * RPN expression.
412 	 *
413 	 * @param rpnExpression RRDTool-like RPN expression
414 	 * @return 95-percentile
415 	 * @throws RrdException Thrown if invalid RPN expression is supplied
416 	 */
417 	public double getRpn95Percentile(String rpnExpression) throws RrdException {
418 		DataProcessor dataProcessor = createDataProcessor(rpnExpression);
419 		return dataProcessor.get95Percentile(RPN_SOURCE_NAME);
420 	}
421 
422 	/**
423 	 * Dumps fetch data to output stream in XML format.
424 	 *
425 	 * @param outputStream Output stream to dump fetch data to
426 	 * @throws IOException Thrown in case of I/O error
427 	 */
428 	public void exportXml(OutputStream outputStream) throws IOException {
429 		XmlWriter writer = new XmlWriter(outputStream);
430 		writer.startTag("fetch_data");
431 		writer.startTag("request");
432 		writer.writeTag("file", request.getParentDb().getPath());
433 		writer.writeComment(Util.getDate(request.getFetchStart()));
434 		writer.writeTag("start", request.getFetchStart());
435 		writer.writeComment(Util.getDate(request.getFetchEnd()));
436 		writer.writeTag("end", request.getFetchEnd());
437 		writer.writeTag("resolution", request.getResolution());
438 		writer.writeTag("cf", request.getConsolFun());
439 		writer.closeTag(); // request
440 		writer.startTag("datasources");
441 		for (String dsName : dsNames) {
442 			writer.writeTag("name", dsName);
443 		}
444 		writer.closeTag(); // datasources
445 		writer.startTag("data");
446 		for (int i = 0; i < timestamps.length; i++) {
447 			writer.startTag("row");
448 			writer.writeComment(Util.getDate(timestamps[i]));
449 			writer.writeTag("timestamp", timestamps[i]);
450 			writer.startTag("values");
451 			for (int j = 0; j < dsNames.length; j++) {
452 				writer.writeTag("v", values[j][i]);
453 			}
454 			writer.closeTag(); // values
455 			writer.closeTag(); // row
456 		}
457 		writer.closeTag(); // data
458 		writer.closeTag(); // fetch_data
459 		writer.flush();
460 	}
461 
462 	/**
463 	 * Dumps fetch data to file in XML format.
464 	 *
465 	 * @param filepath Path to destination file
466 	 * @throws IOException Thrown in case of I/O error
467 	 */
468 	public void exportXml(String filepath) throws IOException {
469 		OutputStream outputStream = null;
470 		try {
471 			outputStream = new FileOutputStream(filepath);
472 			exportXml(outputStream);
473 		}
474 		finally {
475 			if (outputStream != null) {
476 				outputStream.close();
477 			}
478 		}
479 	}
480 
481 	/**
482 	 * Dumps fetch data in XML format.
483 	 *
484 	 * @return String containing XML formatted fetch data
485 	 * @throws IOException Thrown in case of I/O error
486 	 */
487 	public String exportXml() throws IOException {
488 		ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
489 		exportXml(outputStream);
490 		return outputStream.toString();
491 	}
492 
493 	/**
494 	 * Returns the step of the corresponding RRA archive
495 	 *
496 	 * @return Archive step in seconds
497 	 */
498 	public long getArcStep() {
499 		return arcStep;
500 	}
501 
502 	/**
503 	 * Returns the timestamp of the last populated slot in the corresponding RRA archive
504 	 *
505 	 * @return Timestamp in seconds
506 	 */
507 	public long getArcEndTime() {
508 		return arcEndTime;
509 	}
510 
511 	private DataProcessor createDataProcessor(String rpnExpression) throws RrdException {
512 		DataProcessor dataProcessor = new DataProcessor(request.getFetchStart(), request.getFetchEnd());
513 		for (String dsName : dsNames) {
514 			dataProcessor.addDatasource(dsName, this);
515 		}
516 		if (rpnExpression != null) {
517 			dataProcessor.addDatasource(RPN_SOURCE_NAME, rpnExpression);
518 			try {
519 				dataProcessor.processData();
520 			}
521 			catch (IOException ioe) {
522 				// highly unlikely, since all datasources have already calculated values
523 				throw new RuntimeException("Impossible error: " + ioe);
524 			}
525 		}
526 		return dataProcessor;
527 	}
528 }