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

Reply via email to