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-developers mailing list
[email protected]
https://lists.oetiker.ch/cgi-bin/listinfo/rrd-developers