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