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 }
529