On Mon, Jun 09, 2008 at 10:46:40AM +0200, Sebastian Harl wrote: >Hi, > >On Sun, Jun 08, 2008 at 11:15:16PM +0200, Tobias Oetiker wrote: >> Today Bernhard Fischer wrote: >> > > * I did not include rrd_open(), rrd_close(), rrd_write() and similar >> > > functions in the public interface so far as they look like pretty >> > > low-level functions which usually are not needed outside of rrdtool. >> > >> > These functions should be exported since any librrdtool is pretty >> > useless without them. >> > >> > Suppose i want to write a program that opens an rrd and reads one or >> > more DS and a varying number of entries from some CF, calculates >> > something and returns a result. Currently i would have to do alot of >> > what fetch does to achieve this, which doesn't make sense in the light >> > of a librrdtool. >> > See >> > http://www.mail-archive.com/[EMAIL PROTECTED]/msg01866.html >> >> so would >> >> rrd_open >> rrd_read >> rrd_close >> rrd_tell >> rrd_write >> >> be enough, or should there be more ? > >Imho, rrd_flush, rrd_seek and rrd_lock would make sense as well. Also, >as those functions use parameters of type rrd_t, I would add rrd_init >and rrd_free as well. I'm not quite sure about the purpose of >rrd_dontneed but I'd tend to rather include all of those functions or >none at all. I think, the interfaces should be fairly stable so we >should not run into much problems caused by API/ABI changes.
fetch_fn() too, but it needs fixes. See below. I'm attaching a small application that is based off rrdtool that i use to provide nagios-checks. E.g.: ----- 8< ----- # 'check_rrd_value' checks data from rrd file # .../rrd_reader /a/file.rrd discards_out LAST -n3 -w0.2 -c1.0 -ge -o'mystring' define command{ command_name check_rrd_traffic_discards command_line $USER1$/rrd_reader $ARG1$ $ARG2$ $ARG3$ -n$ARG4$ -w$ARG5$ -c$ARG6$ $ARG7$ -o'$ARG8$' } # Check for e.g. CPU utilization define command{ command_name check_rrd_cpu_util command_line $USER1$/rrd_reader $ARG1$ $ARG2$ $ARG3$ -n$ARG4$ -w$ARG5$ -c$ARG6$ $ARG7$ } ----- 8< ----- The applet itself has to do something like: ----- 8< ----- # /opt/nagios_snmp/bin/rrd_reader rrd_reader 0.7.5 20080527 13:23:22 +0200 Usage: rrd_reader file.rrd rrd file to read DS ds_def[].ds_nam, name of the DataSource CF rra[].cf, e.g. "LAST" or "AVERAGE" -n<count> number of data-points (default 1, i.e. most recent) -w<val,percent> Warning threshold -c<val,percent> Critical threshold -z<arith> [min|avg|max] applied to counter -M<multiplier> multiply value from rrd by this (default 1.0) -o<string> append string to output [-le|-ge] Warning/Critical if less or greater equal, respectively If no arith was given, all <num> recent values are used. If arith was given, the result of <arith> applied to num is used. Arguments have to be specified without an intermittent space. The -M<multiplier> is useful of you recorded data with one metric but want to use warning/critical thresholds in a different metric (e.g. bytes vs. bits). ----- 8< ----- You can see how i had to: a) duplicate basically all of rrd_format.h b) had to duplicate those parts of fetching values that deal with the calculation of the locus of the data we need. The applet should basically do this instead: 0) [APP] option handling 1) call rrd_open() 2) [APP] eventually inspect the header. 3) [APP] setup desired start, end, step. 4) call rrd_find_rra() or rrd_find_best_rra() /* or however it would be called */ 5) [APP] adjust start, end, step to legit values. 6) call rrd_fetch_fn() 7) [APP] rrd_close(); rrd_free() 8) [APP] do something with the results and exit. Where stuff marked with [APP] is ok to be done by the application, but the rest (points 1, 4, 6) should be just simple calls into librrd.
// Copyright (c) 2007 Bernhard Fischer // Licensed under GPL // See the file COPYING distributed with this package. // $Id: rrd_reader.h 77 2007-06-11 12:39:55Z bernhard $ // // Taken from rrdtool (oss.oetiker.ch) #define RRD_READONLY (1<<0) #define RRD_READWRITE (1<<1) #define RRD_CREAT (1<<2) #define RRD_READAHEAD (1<<3) #define RRD_COPY (1<<4) #define DNAN set_to_DNAN() #define DINF set_to_DINF() double set_to_DNAN (void); double set_to_DINF (void); typedef union unival { unsigned long u_cnt; rrd_value_t u_val; } unival; typedef struct stat_head_t { /* Data Base Identification Section ** */ char cookie[4]; /* RRD */ char version[5]; /* version of the format */ double float_cookie; /* is it the correct double * representation ? */ /* Data Base Structure Definition **** */ unsigned long ds_cnt; /* how many different ds provide * input to the rrd */ unsigned long rra_cnt; /* how many rras will be maintained * in the rrd */ unsigned long pdp_step; /* pdp interval in seconds */ unival par[10]; /* global parameters ... unused at the moment */ } stat_head_t; enum dst_en { DST_COUNTER = 0, /* data source types available */ DST_ABSOLUTE, DST_GAUGE, DST_DERIVE, DST_CDEF }; enum dst_en dst_conv (char *string); /* The magic number here is one less than DS_NAM_SIZE */ #define DS_NAM_FMT "%19[a-zA-Z0-9_-]" #define DS_NAM_SIZE 20 #define DST_FMT "%19[A-Z]" #define DST_SIZE 20 typedef struct ds_def_t { char ds_nam[DS_NAM_SIZE]; /* Name of the data source (null terminated) */ char dst[DST_SIZE]; /* Type of data source (null terminated) */ unival par[10]; /* index of this array see ds_param_en */ } ds_def_t; #define MAX_RRA_PAR_EN 10 enum cf_en { CF_AVERAGE = 0, /* data consolidation functions */ CF_MINIMUM, CF_MAXIMUM, CF_LAST, CF_HWPREDICT, /* An array of predictions using the seasonal * Holt-Winters algorithm. Requires an RRA of type * CF_SEASONAL for this data source. */ CF_SEASONAL, /* An array of seasonal effects. Requires an RRA of * type CF_HWPREDICT for this data source. */ CF_DEVPREDICT, /* An array of deviation predictions based upon * smoothed seasonal deviations. Requires an RRA of * type CF_DEVSEASONAL for this data source. */ CF_DEVSEASONAL, /* An array of smoothed seasonal deviations. Requires * an RRA of type CF_HWPREDICT for this data source. */ CF_FAILURES }; enum cf_en cf_conv (char *string); #define CF_NAM_FMT "%19[A-Z]" #define CF_NAM_SIZE 20 typedef struct rra_def_t { char cf_nam[CF_NAM_SIZE]; /* consolidation function (null term) */ unsigned long row_cnt; /* number of entries in the store */ unsigned long pdp_cnt; /* how many primary data points are * required for a consolidated data * point?*/ unival par[MAX_RRA_PAR_EN]; /* index see rra_param_en */ } rra_def_t; typedef struct live_head_t { time_t last_up; /* when was rrd last updated */ long last_up_usec; /* micro seconds part of the update timestamp. Always >= 0 */ } live_head_t; #define LAST_DS_LEN 30 /* DO NOT CHANGE THIS ... */ typedef struct pdp_prep_t { char last_ds[LAST_DS_LEN]; /* the last reading from the data * source. this is stored in ASCII * to cater for very large counters * we might encounter in connection * with SNMP. */ unival scratch[10]; /* contents according to pdp_par_en */ } pdp_prep_t; #define MAX_CDP_PAR_EN 10 typedef struct cdp_prep_t { unival scratch[MAX_CDP_PAR_EN]; /* contents according to cdp_par_en * * init state should be NAN */ } cdp_prep_t; typedef struct rra_ptr_t { unsigned long cur_row; /* current row in the rra */ } rra_ptr_t; typedef struct rrd_t { stat_head_t *stat_head; /* the static header */ ds_def_t *ds_def; /* list of data source definitions */ rra_def_t *rra_def; /* list of round robin archive def */ live_head_t *live_head; pdp_prep_t *pdp_prep; /* pdp data prep area */ cdp_prep_t *cdp_prep; /* cdp prep area */ rra_ptr_t *rra_ptr; /* list of rra pointers */ rrd_value_t *rrd_value; /* list of rrd values */ } rrd_t; rrd_file_t *rrd_open (const char *file_name, rrd_t * rrd, int rdwr); int rrd_close (rrd_file_t * rrd_file); void rrd_free (rrd_t * rrd); ssize_t rrd_read (rrd_file_t * rrd_file, void *buf, size_t count); ssize_t rrd_write (rrd_file_t * rrd_file, const void *buf, size_t count); void rrd_flush (rrd_file_t * rrd_file); off_t rrd_seek (rrd_file_t * rrd_file, off_t off, int whence); off_t rrd_tell (rrd_file_t * rrd_file);
// Copyright (c) 2007-2008 Bernhard Fischer // Licensed under GPL // See the file COPYING distributed with this package. // $Id: rrd_reader.c 143 2008-03-03 14:24:52Z bernhard $ // // Parts taken from rrdtool (oss.oetiker.ch) #ifdef HAVE_CONFIG_H #include "config.h" #endif #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <string.h> #include <stdbool.h> #include <ctype.h> #include <math.h> #include <rrd.h> #include "rrd_reader.h" /* indent -ts4 -br -ce */ #ifndef ATTRIBUTE_UNUSED #define ATTRIBUTE_UNUSED __attribute__ ((__unused__)) #endif #ifndef ATTRIBUTE_NORETURN #define ATTRIBUTE_NORETURN __attribute__ (( __noreturn__ )) #endif #undef DEBUG #if defined DEBUG && DEBUG > 0 #include <stdarg.h> unsigned debug = 0; static void dbg (const char *fmt, ...) { if (debug) { va_list ap; char str[BUFSIZ]; va_start (ap, fmt); vsnprintf (str, BUFSIZ - 1, fmt, ap); va_end (ap); fprintf (stderr, "%s\n", str); } } #else static void dbg (const char *fmt ATTRIBUTE_UNUSED, ...) { } #endif #define RET_OK (0) #define RET_WARN (1) #define RET_CRIT (2) #define RET_ERR (3) static void * xrealloc (void *ptr, size_t size) { char *tmp = realloc (ptr, size); if (tmp == NULL) { write (STDERR_FILENO, "Out of memory\n", 14); exit (RET_ERR); } return tmp; } typedef struct llist_t { char *data; struct llist_t *link; } llist_t; #if 0 /* Add data to the end of the linked list. */ static void llist_add_to_end (llist_t ** list_head, void *data) { llist_t *new_item = xrealloc (NULL, sizeof (llist_t)); new_item->data = data; new_item->link = NULL; if (!*list_head) *list_head = new_item; else { llist_t *tail = *list_head; while (tail->link) tail = tail->link; tail->link = new_item; } } #endif #if 0 /* Remove first element from the list and return it */ static void * llist_pop (llist_t ** head) { void *data, *next; if (!*head) return NULL; data = (*head)->data; next = (*head)->link; free (*head); *head = next; return data; } static int xdup (int oldfd) { int newfd = dup (oldfd); if (newfd < 0) { perror ("dup()"); exit (RET_ERR); } return newfd; } static void xclose (int fd) { if (close (fd) < 0) { perror ("close()"); exit (RET_ERR); } } #endif typedef struct list_double_t { long double data; struct list_double_t *link; } list_double_t; static void list_double_add_to_end (list_double_t ** list_head, long double data) { list_double_t *new_item = xrealloc (NULL, sizeof (list_double_t)); new_item->data = data; new_item->link = NULL; if (!*list_head) *list_head = new_item; else { list_double_t *tail = *list_head; while (tail->link) tail = tail->link; tail->link = new_item; } } typedef enum METRIC { METRIC_BYTE = 0, //.< value is in bytes METRIC_KIBIBYTE, //.< value is in kibibyte METRIC_MIBIBYTE, //.< value is in mibibyte METRIC_GIBIBYTE, //.< value is in gibibyte METRIC_TIBIBYTE, //.< value is in tibibyte METRIC_PIBIBYTE, //.< value is in pibibyte METRIC_PERCENT = 2048, //.< value is percent METRIC_LAST = -1 //.< internal marker for invalid metric //.< We better do not rely on INT_MAX //.< being the biggest val for enum.. } metric_t; typedef struct metric_tuple_t { long double lo; /* low watermark */ long double hi; /* high watermark */ metric_t met_lo; metric_t met_hi; } metric_tuple_t; static metric_tuple_t * do_metric_stuff (char *const inp) { char *val = inp, *tmp; size_t inp_len = strlen (val), l = inp_len - 1; bool done = false; metric_tuple_t *out = (metric_tuple_t *) malloc (sizeof (metric_tuple_t)); if (out == NULL) exit (3); memset (out, 0, sizeof (metric_tuple_t)); // char blah[12] = "bBkKmMgGtTpP"; do { tmp = memchr (val, ',', l + 1); /* have an ',' ? */ if (tmp == NULL) { done = true; } else { *tmp = '\0'; l = strlen (val); } if (done) out->hi = strtod (val, NULL); else out->lo = strtod (val, NULL);; tmp = strpbrk (val + l - 1, "bBkKmMgGtTpP"); dbg ("tmp = '%s'", tmp); if (!tmp) { if (done) out->met_hi = METRIC_BYTE; else out->met_lo = METRIC_BYTE; } else switch (*tmp) { case 'p': case 'P': if (done) out->met_hi = METRIC_PIBIBYTE; else out->met_lo = METRIC_PIBIBYTE; break; case 't': case 'T': if (done) out->met_hi = METRIC_TIBIBYTE; else out->met_lo = METRIC_TIBIBYTE; break; case 'g': case 'G': if (done) out->met_hi = METRIC_GIBIBYTE; else out->met_lo = METRIC_GIBIBYTE; break; case 'm': case 'M': if (done) out->met_hi = METRIC_MIBIBYTE; else out->met_lo = METRIC_MIBIBYTE; break; case 'k': case 'K': if (done) out->met_hi = METRIC_KIBIBYTE; else out->met_lo = METRIC_KIBIBYTE; break; case 'b': case 'B': default: if (done) out->met_hi = METRIC_BYTE; else out->met_lo = METRIC_BYTE; break; } if (!done) { val += ++l; l = inp_len - l; } dbg ("metric = {%Le, %i\t%Le, %i}", out->lo, out->met_lo, out->hi, out->met_hi); } while (!done); return out; } static unsigned long long get_metric (metric_t met) { if (met == METRIC_BYTE) return 1; else return 1024 << (10 * (met - 1)); } static long double arith_min (list_double_t * vals, unsigned num ATTRIBUTE_UNUSED) { long double val = vals->data; while (vals) { val = fminl (val, vals->data); vals = vals->link; } return val; } static long double arith_avg (list_double_t * vals, unsigned num) { long double val = 0; unsigned i = 0; /*XXX: FIXME: This will overflow way too early! */ while (vals && ++i <= num + 1) { val += vals->data; vals = vals->link; } return val / i; } static long double arith_max (list_double_t * vals, unsigned num ATTRIBUTE_UNUSED) { long double val = vals->data; while (vals) { val = fmaxl (vals->data, val); vals = vals->link; } return val; } static void ATTRIBUTE_NORETURN usage (void) { fprintf (stderr, TOOL_NAME " " PACKAGE_VERSION " " PACKAGE_DATE "\n" "Usage: " TOOL_NAME "\n" "\tfile.rrd\trrd file to read\n" "\tDS\t\tds_def[].ds_nam, name of the DataSource\n" "\tCF\t\trra[].cf, e.g. \"LAST\" or \"AVERAGE\"\n" "\t-n<count>\tnumber of data-points (default 1, i.e. most recent)\n" "\t-w<val,percent>\tWarning threshold\n" "\t-c<val,percent>\tCritical threshold\n" "\t-z<arith>\t[min|avg|max] applied to counter\n" "\t-M<multiplier>\tmultiply value from rrd by this (default 1.0)\n" "\t-o<string>\tappend string to output\n" "\t[-le|-ge]\tWarning/Critical if less or greater equal, respectively\n" #if defined DEBUG && DEBUG > 0 "\t-v\tverbose\n" #endif "\nIf no arith was given, all <num> recent values are used.\n" "If arith was given, the result of <arith> applied to num is used.\n" "Arguments have to be specified without an intermittent space.\n" "\nThe -M<multiplier> is useful of you recorded data with one\n" "metric but want to use warning/critical thresholds in a different\n" "metric (e.g. bytes vs. bits).\n" "\n"); exit (RET_ERR); } int main (int argc, char **argv) { char *filename, *_ds, *_cf; bool done = false; int ret = RET_OK; enum { BINOP_NONE = 1, BINOP_LE = 2, BINOP_GE = 4 }; int binop = BINOP_NONE; unsigned ds_idx, num = 0; enum cf_en cf_idx; /*int pip[2]; */ rrd_t rrd; rrd_file_t *rrd_file; time_t last_update; unsigned long ds_cnt, rra_cnt, step = 1; metric_tuple_t *warn = NULL, *crit = NULL; long pdp_step, i, ii; // llist_t *hdr = NULL; list_double_t *vals = NULL, *free_vals = NULL; #if defined DEBUG && DEBUG > 1 int fd = open ("./mylog", O_CREAT | O_TRUNC | O_RDWR, 0440); #endif // long rrd_head_size; time_t cal_start, cal_end, rra_start_time, rra_end_time, start, end; long best_full_rra = 0, best_part_rra = 0, chosen_rra = 0, rra_pointer = 0; long best_full_step_diff = 0, best_part_step_diff = 0, best_match = 0; long tmp_step_diff = 0, tmp_match = 0; long full_match, rra_base; long start_offset, end_offset; int first_full = 1; int first_part = 1; rrd_value_t *data_ptr, *data; unsigned long rows; /* -M<multiplier> */ float multiplier = 1.0; /* for -z<arith> handling */ long double (*arith_fn) (list_double_t * vals, unsigned num) = NULL; char *append_str = NULL; if (argc < 4) usage (); #if (defined __GNUC__ && __GNUC__ < 5) _ds = _cf = filename = NULL; _cf = NULL; #endif while (--argc) { dbg ("argc==%i =%s", argc, argv[argc]); if (argv[argc][0] == '-') { switch (argv[argc][1]) { case 'n': /* -n<num> given */ num = strtoull (argv[argc] + 2, NULL, 10) - 1; if (num > 99) { fprintf (stderr, "num %d too large!\n", num); exit (RET_ERR); } break; case 'M': /* -M<multiplier> given */ multiplier = strtof (argv[argc] + 2, NULL); if (isnan (multiplier) || isinf (multiplier)) { fprintf (stderr, "multiplier %f is not a number!\n", multiplier); exit (RET_ERR); } break; case 'w': /* -w[num , percent] warning */ warn = do_metric_stuff (argv[argc] + 2); break; case 'c': /* -c[num , percent] critical */ crit = do_metric_stuff (argv[argc] + 2); break; #ifdef DEBUG case 'v': debug++; break; #endif case 'z': if (strlen (argv[argc]) == 5) switch (argv[argc][3]) { case 'i': /* min */ arith_fn = arith_min; break; case 'v': /* avg */ arith_fn = arith_avg; break; case 'a': /* max */ arith_fn = arith_max; break; } if (arith_fn == NULL) usage (); break; case 'l': if (argv[argc][2] == 'e') binop = BINOP_LE; break; case 'g': if (argv[argc][2] == 'e') binop = BINOP_GE; break; case 'o': append_str = argv[argc] + 2; break; default: usage (); break; } } else { if (argc == 1) filename = argv[1]; else if (argc == 2) _ds = argv[2]; else if (argc == 3) _cf = argv[3]; } } if (warn == NULL) { warn = malloc (sizeof (metric_tuple_t)); if (warn == NULL) exit (3); warn->lo = 0; warn->met_lo = METRIC_BYTE; warn->hi = 1; warn->met_hi = METRIC_BYTE; } if (crit == NULL) { crit = malloc (sizeof (metric_tuple_t)); if (crit == NULL) exit (3); crit->lo = 0; crit->met_lo = METRIC_BYTE; crit->hi = 1; crit->met_hi = METRIC_BYTE; } if (append_str == NULL) append_str = ""; dbg ("filename = '%s'", filename); dbg ("DataSource = '%s'", _ds); dbg ("ConsolidationFunc = '%s'", _cf); dbg ("warn = {%Le, %i\t%Le, %i}", warn->lo, warn->met_lo, warn->hi, warn->met_hi); dbg ("crit = {%Le, %i\t%Le, %i}", crit->lo, crit->met_lo, crit->hi, crit->met_hi); dbg ("multiplier = '%f'", multiplier); dbg ("append_str = '%s'", append_str); // filename = argv[1]; // _ds = argv[2]; // _cf = argv[3]; if ((rrd_file = rrd_open (filename, &rrd, RRD_READONLY)) == NULL) { printf("CRITICAL: failed to open rrd. %s", append_str); exit (RET_ERR); } // rrd_head_size = rrd_file->header_len; ds_cnt = rrd.stat_head->ds_cnt; rra_cnt = rrd.stat_head->rra_cnt; for (ds_idx = 0; ds_idx <= ds_cnt; ds_idx++) if (strncmp (_ds, rrd.ds_def[ds_idx].ds_nam, strlen (rrd.ds_def[ds_idx].ds_nam)) == 0) { done = true; break; } if (!done) { fprintf (stderr, "No DS '%s' found. %s", _ds, append_str); exit (RET_ERR); } done = false; if ((int) (cf_idx = cf_conv (_cf)) < 0) { fprintf (stderr, "Unknown CF '%s' requested. %s", _cf, append_str); exit (RET_ERR); } last_update = rrd.live_head->last_up; pdp_step = rrd.stat_head->pdp_step; // if (argc >= 5) { } { unsigned long _step = 0; // if (dst_conv (rrd.ds_def[ds_idx].dst) == DST_COUNTER) _step = pdp_step; // * rrd.rra_def[cf_idx].pdp_cnt; start = last_update - (num * _step) - _step; end = last_update /*- _step*/ ; start -= (start % _step); end -= (end % _step); // startt; /* default: -s lastup-6min-pdp_step */ // endt; /* default: -e lastup-pdp_step */ } dbg ("We want: start %10lu end %10lu step %5lu", start, end, step); /* llist_add_to_end (&hdr, rrd.ds_def[ds_idx].ds_nam); */ /* find the rra which best matches the requirements */ for (i = 0; (unsigned) i < rrd.stat_head->rra_cnt; i++) { if (cf_conv (rrd.rra_def[i].cf_nam) == cf_idx) { cal_end = (rrd.live_head->last_up - (rrd.live_head->last_up % (rrd.rra_def[i].pdp_cnt * rrd.stat_head->pdp_step))); cal_start = (cal_end - (rrd.rra_def[i].pdp_cnt * rrd.rra_def[i].row_cnt * rrd.stat_head->pdp_step)); full_match = end - start; #ifdef DEBUG dbg ("Considering: start %10lu end %10lu step %5lu ", cal_start, cal_end, rrd.stat_head->pdp_step * rrd.rra_def[i].pdp_cnt); #endif /* we need step difference in either full or partial case */ tmp_step_diff = labs (step - (rrd.stat_head->pdp_step * rrd.rra_def[i].pdp_cnt)); /* best full match */ if (cal_end >= end && cal_start <= start) { if (first_full || (tmp_step_diff < best_full_step_diff)) { first_full = 0; best_full_step_diff = tmp_step_diff; best_full_rra = i; #ifdef DEBUG fprintf (stderr, "best full match so far\n"); #endif } else { #ifdef DEBUG fprintf (stderr, "full match, not best\n"); #endif } } else { /* best partial match */ tmp_match = full_match; if (cal_start > start) tmp_match -= (cal_start - start); if (cal_end < end) tmp_match -= (end - cal_end); if (first_part || (best_match < tmp_match) || (best_match == tmp_match && tmp_step_diff < best_part_step_diff)) { #ifdef DEBUG fprintf (stderr, "best partial so far\n"); #endif first_part = 0; best_match = tmp_match; best_part_step_diff = tmp_step_diff; best_part_rra = i; } else { #ifdef DEBUG fprintf (stderr, "partial match, not best\n"); #endif } } } } /* lets see how the matching went. */ if (first_full == 0) chosen_rra = best_full_rra; else if (first_part == 0) chosen_rra = best_part_rra; else { rrd_set_error ("the RRD does not contain an RRA matching the chosen CF"); rrd_close (rrd_file); rrd_free (&rrd); exit (RET_ERR); } /* set the wish parameters to their real values */ step = rrd.stat_head->pdp_step * rrd.rra_def[chosen_rra].pdp_cnt; start -= (start % step); end += (step - end % step); rows = (end - start) / step + 1; #ifdef DEBUG fprintf (stderr, "We found: start %10lu end %10lu step %5lu rows %lu\n", start, end, step, rows); #endif /* Start and end are now multiples of the step size. The amount of ** steps we want is (end-start)/step and *not* an extra one. ** Reasoning: if step is s and we want to graph from t to t+s, ** we need exactly ((t+s)-t)/s rows. The row to collect from the ** database is the one with time stamp (t+s) which means t to t+s. */ ds_cnt = rrd.stat_head->ds_cnt; if ((data = malloc (ds_cnt * rows * sizeof (rrd_value_t))) == NULL) { rrd_set_error ("malloc fetch data area"); rrd_close (rrd_file); rrd_free (&rrd); exit (RET_ERR); } data_ptr = data; /* find base address of rra */ rra_base = rrd_file->header_len; for (i = 0; i < chosen_rra; i++) rra_base += (ds_cnt * rrd.rra_def[i].row_cnt * sizeof (rrd_value_t)); /* find start and end offset */ rra_end_time = (rrd.live_head->last_up - (rrd.live_head->last_up % step)); rra_start_time = (rra_end_time - (step * (rrd.rra_def[chosen_rra].row_cnt - 1))); /* here's an error by one if we don't be careful */ start_offset = (long) (start + step - rra_start_time) / (long) step; end_offset = (long) (rra_end_time - end) / (long) step; #ifdef DEBUG fprintf (stderr, "rra_start %lu, rra_end %lu, start_off %li, end_off %li\n", rra_start_time, rra_end_time, start_offset, end_offset); #endif /* fill the gap at the start if needs be */ if (start_offset <= 0) rra_pointer = rrd.rra_ptr[chosen_rra].cur_row + 1; else rra_pointer = rrd.rra_ptr[chosen_rra].cur_row + 1 + start_offset; if (rrd_seek (rrd_file, (rra_base + (rra_pointer * ds_cnt * sizeof (rrd_value_t))), SEEK_SET) != 0) { rrd_set_error ("seek error in RRA"); rrd_close (rrd_file); rrd_free (&rrd); free (data); data = NULL; exit (RET_ERR); } #ifdef DEBUG fprintf (stderr, "First Seek: rra_base %lu rra_pointer %lu\n", rra_base, rra_pointer); #endif /* step trough the array */ for (i = start_offset; i < (signed) rrd.rra_def[chosen_rra].row_cnt - end_offset; i++) { /* no valid data yet */ if (i < 0) { #ifdef DEBUG fprintf (stderr, "pre fetch %li -- ", i); #endif for (ii = 0; (unsigned) ii < ds_cnt; ii++) { *(data_ptr++) = DNAN; #ifdef DEBUG fprintf (stderr, "%10.2f ", *(data_ptr - 1)); #endif } } /* past the valid data area */ else if (i >= (signed) rrd.rra_def[chosen_rra].row_cnt) { #ifdef DEBUG fprintf (stderr, "post fetch %li -- ", i); #endif for (ii = 0; (unsigned) ii < ds_cnt; ii++) { *(data_ptr++) = DNAN; #ifdef DEBUG fprintf (stderr, "%10.2f ", *(data_ptr - 1)); #endif } } else { /* OK we are inside the valid area but the pointer has to * be wrapped*/ if (rra_pointer >= (signed) rrd.rra_def[chosen_rra].row_cnt) { rra_pointer -= rrd.rra_def[chosen_rra].row_cnt; if (rrd_seek (rrd_file, (rra_base + rra_pointer * ds_cnt * sizeof (rrd_value_t)), SEEK_SET) != 0) { rrd_set_error ("wrap seek in RRA did fail"); rrd_close (rrd_file); rrd_free (&rrd); free (data); data = NULL; exit (RET_ERR); } #ifdef DEBUG fprintf (stderr, "wrap seek ...\n"); #endif } if (rrd_read (rrd_file, data_ptr, sizeof (rrd_value_t) * ds_cnt) != (ssize_t) (sizeof (rrd_value_t) * rrd.stat_head->ds_cnt)) { rrd_set_error ("fetching cdp from rra"); rrd_close (rrd_file); rrd_free (&rrd); free (data); data = NULL; exit (RET_ERR); } #ifdef HAVE_POSIX_FADVISE /* don't pollute the buffer cache with data read from the file. We do this while reading to * keep damage minimal */ if (0 != posix_fadvise (fileno (in_file), rrd_head_size, 0, POSIX_FADV_DONTNEED)) { rrd_set_error ("setting POSIX_FADV_DONTNEED on '%s': %s", filename, rrd_strerror (errno)); fclose (in_file); exit (RET_ERR); } #endif #ifdef DEBUG fprintf (stderr, "post fetch %li -- ", i); for (ii = 0; (unsigned long) ii < ds_cnt; ii++) fprintf (stderr, "%10.2f ", *(data_ptr + ii)); #endif data_ptr += ds_cnt; rra_pointer++; } #ifdef DEBUG fprintf (stderr, "\n"); #endif } rrd_close (rrd_file); rrd_free (&rrd); #ifdef HAVE_POSIX_FADVISE /* and just to be sure we drop everything except the header at the end */ if (0 != posix_fadvise (fileno (in_file), rrd_head_size, 0, POSIX_FADV_DONTNEED)) { rrd_set_error ("setting POSIX_FADV_DONTNEED on '%s': %s", filename, rrd_strerror (errno)); fclose (in_file); exit (RET_ERR); } #endif for (i = 0; (unsigned) i <= num; i++) { /* printf (" %0.10e\n", *(data + ds_idx + i*(ds_idx+1)));*/ list_double_add_to_end (&vals, *(data + ds_idx + i * (ds_idx + 1)) * multiplier); } free (data); data = NULL; //printf(" %0.10e\n", *(data_ptr+ds_idx)); #if 0 if (pipe (pip) < 0) { perror ("pipe()"); exit (RET_ERR); } switch (fork ()) { case 0: /* child */ xclose (STDOUT_FILENO); xdup (pip[1]); xclose (pip[0]); execvp ("rrdtool", fetch_cmd); perror ("execvp()"); exit (RET_ERR); break; case -1: perror ("fork()"); exit (RET_ERR); break; default: /* parent */ { char *buf = xrealloc (NULL, BUFSIZ); char *ptr_start, *ptr; unsigned lines = 0, records = 0; xclose (STDIN_FILENO); xdup (pip[0]); xclose (pip[1]); /* now read the childs output */ do { ssize_t sz; /*unsigned long stamp; */ memset (buf, 0, BUFSIZ); sz = read (pip[0], buf, BUFSIZ); if (sz <= 0) break; write (fd, buf, sz); ptr_start = ptr = buf; while (lines < 2) { /* skip "header\n\n" */ ptr = memchr (ptr_start, '\n', BUFSIZ); if (ptr == NULL) break; sz -= ++ptr - ptr_start; ptr_start += ptr - ptr_start; ++lines; } do { /* read fields */ unsigned field = 0; long double val1; int match; ptr = memchr (ptr_start, ':', BUFSIZ); /* skip stamp */ if (ptr == NULL) break; sz -= ++ptr - ptr_start; ptr_start += ptr - ptr_start; while (isspace (*ptr_start)) { ++ptr_start; --sz; } while (field < ds_cnt) { ptr = memchr (ptr_start, ' ', BUFSIZ); if (ptr != NULL) { ++field; sz -= ptr - ptr_start; ptr_start += ptr - ptr_start; } else break; } if (ptr == NULL) break; while (isspace (*ptr_start)) { ptr = ++ptr_start; --sz; } if (strncmp (ptr, "nan", 3) == 0) continue; match = sscanf (ptr, "%Le", &val1); // fprintf (stderr, "Saw ->%Le<-\n", val1); if (match == EOF || match <= 0) continue; list_double_add_to_end (&vals, val1); // fprintf (stderr, "Added ->%Le<-\n", val1); ++records; } while (sz); } while (1); // if (records != num + 1) // exit (RET_ERR); } break; } #endif #if defined DEBUG && DEBUG > 1 xclose (fd); #endif // dbg ("ds_cnt=%lu\n", ds_cnt); /* while (hdr) fprintf (stdout, "%-12s", (char *) llist_pop (&hdr)); putc ('\n', stdout); */ free_vals = vals; if (vals) { char met = '\0'; long double val = NAN; unsigned long long modif = 1; bool got_value = false; if (arith_fn != NULL) { vals->data = arith_fn (vals, num); vals->link = NULL; /* ahem. Gross shortcut.. */ goto do_compare; } else { while (vals) { do_compare: dbg ("data == %Le ", vals->data); if (ret != RET_CRIT) { if (!(binop & BINOP_GE) && vals->data <= crit->lo * get_metric (crit->met_lo)) { val = vals->data; modif = get_metric (crit->met_lo); met = '<'; ret = RET_CRIT; } else { if (!(binop & BINOP_LE) && vals->data >= crit->hi * get_metric (crit->met_hi)) { val = vals->data; modif = get_metric (crit->met_hi); met = '>'; ret = RET_CRIT; } } } if (ret != RET_CRIT) { if (!(binop & BINOP_GE) && vals->data <= warn->lo * get_metric (warn->met_lo)) { val = vals->data; modif = get_metric (warn->met_lo); met = '<'; ret = RET_WARN; } else { if (!(binop & BINOP_LE) && vals->data >= warn->hi * get_metric (warn->met_hi)) { val = vals->data; modif = get_metric (warn->met_hi); met = '>'; ret = RET_WARN; } } } if (ret == RET_OK && !got_value) { val = vals->data; /* remember the first of <num> values */ got_value = true; } vals = vals->link; } } if (met == '<') { printf ("%s: %s %Le %c= %Le /%lus %s", (ret == RET_CRIT) ? "CRITICAL" : "WARNING", _ds, val, met, (ret == RET_CRIT) ? crit->lo * modif : warn->lo * modif, step, append_str); } else if (met == '>') { printf ("%s: %s %Le %c= %Le /%lus %s", (ret == RET_CRIT) ? "CRITICAL" : "WARNING", _ds, val, met, (ret == RET_CRIT) ? crit->hi * modif : warn->hi * modif, step, append_str); } else printf ("%s: %s %Lg /%lus %s", (ret == RET_OK) ? "OK" : (ret == RET_ERR ? "ERROR" : "???"), _ds, val, step, append_str); fflush (stdout); } while (free_vals) { list_double_t *tmp = free_vals->link; free (free_vals); free_vals = tmp; } free (warn); free (crit); return ret; } /* vi: set ts=4 sw=4: */
_______________________________________________ rrd-users mailing list rrd-users@lists.oetiker.ch https://lists.oetiker.ch/cgi-bin/listinfo/rrd-users