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 }