#include <io.h>
#include <fcntl.h>
#endif
+#include "rrd_rpncalc.h"
#define SmallFont gdLucidaNormal10
#define LargeFont gdLucidaBold12
enum gf_en {GF_PRINT=0,GF_GPRINT,GF_COMMENT,GF_HRULE,GF_VRULE,GF_LINE1,
- GF_LINE2,GF_LINE3,GF_AREA,GF_STACK, GF_DEF, GF_CDEF };
-
-enum op_en {OP_NUMBER=0,OP_VARIABLE,OP_INF,OP_PREV,OP_NEGINF,
- OP_UNKN,OP_NOW,OP_TIME,OP_LTIME,OP_ADD,OP_MOD,
- OP_SUB,OP_MUL,
- OP_DIV,OP_SIN, OP_DUP, OP_EXC, OP_POP,
- OP_COS,OP_LOG,OP_EXP,OP_LT,OP_LE,OP_GT,OP_GE,OP_EQ,OP_IF,
- OP_MIN,OP_MAX,OP_LIMIT, OP_FLOOR, OP_CEIL,
- OP_UN,OP_END};
+ GF_LINE2,GF_LINE3,GF_AREA,GF_STACK,GF_TICK,
+ GF_DEF, GF_CDEF, GF_VDEF};
enum if_en {IF_GIF=0,IF_PNG=1};
-typedef struct rpnp_t {
- enum op_en op;
- double val; /* value for a OP_NUMBER */
- long ptr; /* pointer into the gdes array for OP_VAR */
- double *data; /* pointer to the current value from OP_VAR DAS*/
- long ds_cnt; /* data source count for data pointer */
- long step; /* time step for OP_VAR das */
-} rpnp_t;
-
+enum vdef_op_en {
+ VDEF_MAXIMUM /* like the MAX in (G)PRINT */
+ ,VDEF_MINIMUM /* like the MIN in (G)PRINT */
+ ,VDEF_AVERAGE /* like the AVERAGE in (G)PRINT */
+ ,VDEF_PERCENT /* Nth percentile */
+ ,VDEF_FIRST /* first non-unknown value and time */
+ ,VDEF_LAST /* last non-unknown value and time */
+ };
+typedef struct vdef_t {
+ enum vdef_op_en op;
+ double param; /* parameter for function, if applicable */
+ double val; /* resulting value */
+ time_t when; /* timestamp, if applicable */
+} vdef_t;
typedef struct col_trip_t {
int red; /* red = -1 is no color */
char format[FMT_LEG_LEN+5]; /* format for PRINT AND GPRINT */
char legend[FMT_LEG_LEN+5]; /* legend*/
gdPoint legloc; /* location of legend */
- double yrule; /* value for y rule line */
- time_t xrule; /* value for x rule line */
+ double yrule; /* value for y rule line and for VDEF */
+ time_t xrule; /* time for x rule line and for VDEF */
+ vdef_t vf; /* instruction for VDEF function */
rpnp_t *rpnp; /* instructions for CDEF function */
/* description of data fetched for the graph element */
void reduce_data( enum cf_en, unsigned long, time_t *, time_t *, unsigned long *, unsigned long *, rrd_value_t **);
int data_fetch( image_desc_t *);
long find_var(image_desc_t *, char *);
+long find_var_wrapper(void *arg1, char *key);
long lcd(long *);
int data_calc( image_desc_t *);
int data_proc( image_desc_t *);
time_t find_first_time( time_t, enum tmt_en, long);
time_t find_next_time( time_t, enum tmt_en, long);
void gator( gdImagePtr, int, int);
-int tzoffset(time_t);
int print_calc(image_desc_t *, char ***);
int leg_place(image_desc_t *);
int horizontal_grid(gdImagePtr, image_desc_t *);
int scan_for_col(char *, int, char *);
int rrd_graph(int, char **, char ***, int *, int *);
int bad_format(char *);
-rpnp_t * str2rpn(image_desc_t *,char *);
+int vdef_parse(struct graph_desc_t *,char *);
+int vdef_calc(image_desc_t *, int);
+int vdef_percent_compar(const void *,const void *);
/* translate time values into x coordinates */
/*#define xtr(x) (int)((double)im->xorigin \
conv_if(LINE3,GF_LINE3)
conv_if(AREA,GF_AREA)
conv_if(STACK,GF_STACK)
+ conv_if(TICK,GF_TICK)
conv_if(DEF,GF_DEF)
conv_if(CDEF,GF_CDEF)
+ conv_if(VDEF,GF_VDEF)
return (-1);
}
if (isnan(newval)) newval = srcptr[i*(*ds_cnt)+col];
else {
switch (cf) {
+ case CF_HWPREDICT:
+ case CF_DEVSEASONAL:
+ case CF_DEVPREDICT:
+ case CF_SEASONAL:
case CF_AVERAGE:
newval += srcptr[i*(*ds_cnt)+col];
break;
case CF_MINIMUM:
newval = min (newval,srcptr[i*(*ds_cnt)+col]);
break;
+ case CF_FAILURES:
+ /* an interval contains a failure if any subintervals contained a failure */
case CF_MAXIMUM:
newval = max (newval,srcptr[i*(*ds_cnt)+col]);
break;
}
if (validval == 0){newval = DNAN;} else{
switch (cf) {
- case CF_AVERAGE:
- newval /= validval;
+ case CF_HWPREDICT:
+ case CF_DEVSEASONAL:
+ case CF_DEVPREDICT:
+ case CF_SEASONAL:
+ case CF_AVERAGE:
+ newval /= validval;
break;
case CF_MINIMUM:
- case CF_MAXIMUM:
+ case CF_FAILURES:
+ case CF_MAXIMUM:
case CF_LAST:
break;
}
* CDEF stuff
*************************************************************/
+long
+find_var_wrapper(void *arg1, char *key)
+{
+ return find_var((image_desc_t *) arg1, key);
+}
+
/* find gdes containing var*/
long
find_var(image_desc_t *im, char *key){
long ii;
for(ii=0;ii<im->gdes_c-1;ii++){
if((im->gdes[ii].gf == GF_DEF
+ || im->gdes[ii].gf == GF_VDEF
|| im->gdes[ii].gf == GF_CDEF)
&& (strcmp(im->gdes[ii].vname,key) == 0)){
return ii;
return num[i];
}
-
-/* convert string to rpnp */
-rpnp_t *
-str2rpn(image_desc_t *im,char *expr){
- int pos=0;
- long steps=-1;
- rpnp_t *rpnp;
- char vname[30];
-
- rpnp=NULL;
-
- while(*expr){
- if ((rpnp = (rpnp_t *) rrd_realloc(rpnp, (++steps + 2)*
- sizeof(rpnp_t)))==NULL){
- return NULL;
- }
-
- else if((sscanf(expr,"%lf%n",&rpnp[steps].val,&pos) == 1)
- && (expr[pos] == ',')){
- rpnp[steps].op = OP_NUMBER;
- expr+=pos;
- }
-
-#define match_op(VV,VVV) \
- else if (strncmp(expr, #VVV, strlen(#VVV))==0 && \
- (expr[strlen(#VVV)] == ',' || expr[strlen(#VVV)] == '\0') ){ \
- rpnp[steps].op = VV; \
- expr+=strlen(#VVV); \
- }
-
- match_op(OP_ADD,+)
- match_op(OP_SUB,-)
- match_op(OP_MUL,*)
- match_op(OP_DIV,/)
- match_op(OP_MOD,%)
- match_op(OP_SIN,SIN)
- match_op(OP_COS,COS)
- match_op(OP_LOG,LOG)
- match_op(OP_FLOOR,FLOOR)
- match_op(OP_CEIL,CEIL)
- match_op(OP_EXP,EXP)
- match_op(OP_DUP,DUP)
- match_op(OP_EXC,EXC)
- match_op(OP_POP,POP)
- match_op(OP_LT,LT)
- match_op(OP_LE,LE)
- match_op(OP_GT,GT)
- match_op(OP_GE,GE)
- match_op(OP_EQ,EQ)
- match_op(OP_IF,IF)
- match_op(OP_MIN,MIN)
- match_op(OP_MAX,MAX)
- match_op(OP_LIMIT,LIMIT)
- /* order is important here ! .. match longest first */
- match_op(OP_UNKN,UNKN)
- match_op(OP_UN,UN)
- match_op(OP_NEGINF,NEGINF)
- match_op(OP_PREV,PREV)
- match_op(OP_INF,INF)
- match_op(OP_NOW,NOW)
- match_op(OP_LTIME,LTIME)
- match_op(OP_TIME,TIME)
-
-
-#undef match_op
-
-
- else if ((sscanf(expr,DEF_NAM_FMT "%n",
- vname,&pos) == 1)
- && ((rpnp[steps].ptr = find_var(im,vname)) != -1)){
- rpnp[steps].op = OP_VARIABLE;
- expr+=pos;
- }
-
- else {
- free(rpnp);
- return NULL;
- }
- if (*expr == 0)
- break;
- if (*expr == ',')
- expr++;
- else {
- free(rpnp);
- return NULL;
- }
- }
- rpnp[steps+1].op = OP_END;
- return rpnp;
-}
-
-/* figure out what the local timezone offset for any point in
- time was. Return it in seconds */
-
-int
-tzoffset( time_t now ){
- int gm_sec, gm_min, gm_hour, gm_yday, gm_year,
- l_sec, l_min, l_hour, l_yday, l_year;
- struct tm *t;
- int off;
- t = gmtime(&now);
- gm_sec = t->tm_sec;
- gm_min = t->tm_min;
- gm_hour = t->tm_hour;
- gm_yday = t->tm_yday;
- gm_year = t->tm_year;
- t = localtime(&now);
- l_sec = t->tm_sec;
- l_min = t->tm_min;
- l_hour = t->tm_hour;
- l_yday = t->tm_yday;
- l_year = t->tm_year;
- off = (l_sec-gm_sec)+(l_min-gm_min)*60+(l_hour-gm_hour)*3600;
- if ( l_yday > gm_yday || l_year > gm_year){
- off += 24*3600;
- } else if ( l_yday < gm_yday || l_year < gm_year){
- off -= 24*3600;
- }
-
- return off;
-}
-
-
-
-#define dc_stackblock 100
-
-/* run the rpn calculator on all the CDEF arguments */
-
+/* run the rpn calculator on all the VDEF and CDEF arguments */
int
data_calc( image_desc_t *im){
- int gdi,rpi;
+ int gdi;
int dataidx;
- long *steparray;
+ long *steparray, rpi;
int stepcnt;
time_t now;
- double *stack = NULL;
- long dc_stacksize = 0;
-
- for (gdi=0;gdi<im->gdes_c;gdi++){
- /* only GF_CDEF elements are of interest */
- if (im->gdes[gdi].gf != GF_CDEF)
- continue;
- im->gdes[gdi].ds_cnt = 1;
- im->gdes[gdi].ds = 0;
- im->gdes[gdi].data_first = 1;
- im->gdes[gdi].start = 0;
- im->gdes[gdi].end = 0;
- steparray=NULL;
- stepcnt = 0;
- dataidx=-1;
-
- /* find the variables in the expression. And calc the lowest
- common denominator of all step sizes of the data sources involved.
- this will be the step size for the cdef created data source*/
-
- for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
- if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE){
- long ptr = im->gdes[gdi].rpnp[rpi].ptr;
- if ((steparray = rrd_realloc(steparray, (++stepcnt+1)*sizeof(*steparray)))==NULL){
- rrd_set_error("realloc steparray");
- free(stack);
- return -1;
- };
-
-
- steparray[stepcnt-1] = im->gdes[ptr].step;
-
- /* adjust start and end of cdef (gdi) so that it runs from
- the latest start point to the earliest endpoint of any of the
- rras involved (ptr) */
+ rpnstack_t rpnstack;
- if(im->gdes[gdi].start < im->gdes[ptr].start)
- im->gdes[gdi].start = im->gdes[ptr].start;
+ rpnstack_init(&rpnstack);
- if(im->gdes[gdi].end == 0
- || im->gdes[gdi].end > im->gdes[ptr].end)
- im->gdes[gdi].end = im->gdes[ptr].end;
+ for (gdi=0;gdi<im->gdes_c;gdi++){
+ /* Look for GF_VDEF and GF_CDEF in the same loop,
+ * so CDEFs can use VDEFs and vice versa
+ */
+ switch (im->gdes[gdi].gf) {
+ case GF_VDEF:
+ /* A VDEF has no DS. This also signals other parts
+ * of rrdtool that this is a VDEF value, not a CDEF.
+ */
+ im->gdes[gdi].ds_cnt = 0;
+ if (vdef_calc(im,gdi)) {
+ rrd_set_error("Error processing VDEF '%s'"
+ ,im->gdes[gdi].vname
+ );
+ rpnstack_free(&rpnstack);
+ return -1;
+ }
+ break;
+ case GF_CDEF:
+ im->gdes[gdi].ds_cnt = 1;
+ im->gdes[gdi].ds = 0;
+ im->gdes[gdi].data_first = 1;
+ im->gdes[gdi].start = 0;
+ im->gdes[gdi].end = 0;
+ steparray=NULL;
+ stepcnt = 0;
+ dataidx=-1;
+
+ /* Find the variables in the expression.
+ * - VDEF variables are substituted by their values
+ * and the opcode is changed into OP_NUMBER.
+******************
+* Note to Jake: I cannot oversee the implications for your
+* COMPUTE DS stuff. Please check if VDEF and COMPUTE are
+* compatible (or can be made so).
+******************
+ * - CDEF variables are analized for their step size,
+ * the lowest common denominator of all the step
+ * sizes of the data sources involved is calculated
+ * and the resulting number is the step size for the
+ * resulting data source.
+ */
+ for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
+ if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE){
+ long ptr = im->gdes[gdi].rpnp[rpi].ptr;
+ if (im->gdes[ptr].ds_cnt == 0) {
+#if 0
+printf("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
+ im->gdes[gdi].vname,
+ im->gdes[ptr].vname);
+printf("DEBUG: value from vdef is %f\n",im->gdes[ptr].vf.val);
+#endif
+ im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
+ im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
+ } else {
+ if ((steparray = rrd_realloc(steparray, (++stepcnt+1)*sizeof(*steparray)))==NULL){
+ rrd_set_error("realloc steparray");
+ rpnstack_free(&rpnstack);
+ return -1;
+ };
+
+ steparray[stepcnt-1] = im->gdes[ptr].step;
+
+ /* adjust start and end of cdef (gdi) so
+ * that it runs from the latest start point
+ * to the earliest endpoint of any of the
+ * rras involved (ptr)
+ */
+ if(im->gdes[gdi].start < im->gdes[ptr].start)
+ im->gdes[gdi].start = im->gdes[ptr].start;
+
+ if(im->gdes[gdi].end == 0 ||
+ im->gdes[gdi].end > im->gdes[ptr].end)
+ im->gdes[gdi].end = im->gdes[ptr].end;
- /* store pointer to the first element of the rra providing
- data for variable, further save step size and data source count
- of this rra*/
- im->gdes[gdi].rpnp[rpi].data =
- im->gdes[ptr].data + im->gdes[ptr].ds;
- im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
- im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
- }
-
- }
- if(steparray == NULL){
- rrd_set_error("rpn expressions without variables are not supported");
- free(stack);
- return -1;
- }
- steparray[stepcnt]=0;
- /* now find the step for the result of the cdef. so that we land on
- each step in all of the variables rras */
-
- im->gdes[gdi].step = lcd(steparray);
-
-
- free(steparray);
-
- if((im->gdes[gdi].data = malloc(((im->gdes[gdi].end
- -im->gdes[gdi].start)
+ /* store pointer to the first element of
+ * the rra providing data for variable,
+ * further save step size and data source
+ * count of this rra
+ */
+ im->gdes[gdi].rpnp[rpi].data =
+ im->gdes[ptr].data + im->gdes[ptr].ds;
+ im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
+ im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
+
+ /* backoff the *.data ptr; this is done so
+ * rpncalc() function doesn't have to treat
+ * the first case differently
+ */
+ im->gdes[gdi].rpnp[rpi].data-=im->gdes[ptr].ds_cnt;
+ } /* if ds_cnt != 0 */
+ } /* if OP_VARIABLE */
+ } /* loop through all rpi */
+
+ if(steparray == NULL){
+ rrd_set_error("rpn expressions without DEF"
+ " or CDEF variables are not supported");
+ rpnstack_free(&rpnstack);
+ return -1;
+ }
+ steparray[stepcnt]=0;
+ /* Now find the resulting step. All steps in all
+ * used RRAs have to be visited
+ */
+ im->gdes[gdi].step = lcd(steparray);
+ free(steparray);
+ if((im->gdes[gdi].data = malloc((
+ (im->gdes[gdi].end-im->gdes[gdi].start)
/ im->gdes[gdi].step +1)
* sizeof(double)))==NULL){
- rrd_set_error("malloc im->gdes[gdi].data");
- free(stack);
- return -1;
- }
-
- /* step through the new cdef results array and calculate the values */
- for (now = im->gdes[gdi].start;
- now<=im->gdes[gdi].end;
- now += im->gdes[gdi].step){
- long stptr=-1;
- /* process each op from the rpn in turn */
- for (rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
- if (stptr +5 > dc_stacksize){
- dc_stacksize += dc_stackblock;
- stack = rrd_realloc(stack,dc_stacksize*sizeof(*stack));
- if (stack==NULL){
- rrd_set_error("RPN stack overflow");
- return -1;
- }
+ rrd_set_error("malloc im->gdes[gdi].data");
+ rpnstack_free(&rpnstack);
+ return -1;
}
- switch (im->gdes[gdi].rpnp[rpi].op){
- case OP_NUMBER:
- stack[++stptr] = im->gdes[gdi].rpnp[rpi].val;
- break;
- case OP_VARIABLE:
- /* make sure we pull the correct value from the *.data array */
- /* adjust the pointer into the array acordingly. */
- if(now > im->gdes[gdi].start &&
- now % im->gdes[gdi].rpnp[rpi].step == 0){
- im->gdes[gdi].rpnp[rpi].data +=
- im->gdes[gdi].rpnp[rpi].ds_cnt;
- }
- stack[++stptr] = *im->gdes[gdi].rpnp[rpi].data;
- break;
- case OP_PREV:
- if (dataidx <= 0) {
- stack[++stptr] = DNAN;
- } else {
- stack[++stptr] = im->gdes[gdi].data[dataidx];
- }
- break;
- case OP_UNKN:
- stack[++stptr] = DNAN;
- break;
- case OP_INF:
- stack[++stptr] = DINF;
- break;
- case OP_NEGINF:
- stack[++stptr] = -DINF;
- break;
- case OP_NOW:
- stack[++stptr] = (double)time(NULL);
- break;
- case OP_TIME:
- stack[++stptr] = (double)now;
- break;
- case OP_LTIME:
- stack[++stptr] = (double)tzoffset(now)+(double)now;
- break;
- case OP_ADD:
- if(stptr<1){
- rrd_set_error("RPN stack underflow");
- free(stack);
- return -1;
- }
- stack[stptr-1] = stack[stptr-1] + stack[stptr];
- stptr--;
- break;
- case OP_SUB:
- if(stptr<1){
- rrd_set_error("RPN stack underflow");
- free(stack);
- return -1;
- }
- stack[stptr-1] = stack[stptr-1] - stack[stptr];
- stptr--;
- break;
- case OP_MUL:
- if(stptr<1){
- rrd_set_error("RPN stack underflow");
- free(stack);
- return -1;
- }
- stack[stptr-1] = stack[stptr-1] * stack[stptr];
- stptr--;
- break;
- case OP_DIV:
- if(stptr<1){
- rrd_set_error("RPN stack underflow");
- free(stack);
- return -1;
- }
- stack[stptr-1] = stack[stptr-1] / stack[stptr];
- stptr--;
- break;
- case OP_MOD:
- if(stptr<1){
- rrd_set_error("RPN stack underflow");
- free(stack);
- return -1;
- }
- stack[stptr-1] = fmod(stack[stptr-1],stack[stptr]);
- stptr--;
- break;
- case OP_SIN:
- if(stptr<0){
- rrd_set_error("RPN stack underflow");
- free(stack);
- return -1;
- }
- stack[stptr] = sin(stack[stptr]);
- break;
- case OP_COS:
- if(stptr<0){
- rrd_set_error("RPN stack underflow");
- free(stack);
- return -1;
- }
- stack[stptr] = cos(stack[stptr]);
- break;
- case OP_CEIL:
- if(stptr<0){
- rrd_set_error("RPN stack underflow");
- free(stack);
- return -1;
- }
- stack[stptr] = ceil(stack[stptr]);
- break;
- case OP_FLOOR:
- if(stptr<0){
- rrd_set_error("RPN stack underflow");
- free(stack);
- return -1;
- }
- stack[stptr] = floor(stack[stptr]);
- break;
- case OP_LOG:
- if(stptr<0){
- rrd_set_error("RPN stack underflow");
- free(stack);
- return -1;
- }
- stack[stptr] = log(stack[stptr]);
- break;
- case OP_DUP:
- if(stptr<0){
- rrd_set_error("RPN stack underflow");
- free(stack);
- return -1;
- }
- stack[stptr+1] = stack[stptr];
- stptr++;
- break;
- case OP_POP:
- if(stptr<0){
- rrd_set_error("RPN stack underflow");
- free(stack);
- return -1;
- }
- stptr--;
- break;
- case OP_EXC:
- if(stptr<1){
- rrd_set_error("RPN stack underflow");
- free(stack);
- return -1;
- } else {
- double dummy;
- dummy = stack[stptr] ;
- stack[stptr] = stack[stptr-1];
- stack[stptr-1] = dummy;
- }
- break;
- case OP_EXP:
- if(stptr<0){
- rrd_set_error("RPN stack underflow");
- free(stack);
- return -1;
- }
- stack[stptr] = exp(stack[stptr]);
- break;
- case OP_LT:
- if(stptr<1){
- rrd_set_error("RPN stack underflow");
- free(stack);
- return -1;
- }
- if (isnan(stack[stptr-1]) || isnan(stack[stptr]))
- stack[stptr-1] = 0.0;
- else
- stack[stptr-1] = stack[stptr-1] < stack[stptr] ? 1.0 : 0.0;
- stptr--;
- break;
- case OP_LE:
- if(stptr<1){
- rrd_set_error("RPN stack underflow");
- free(stack);
- return -1;
- }
- if (isnan(stack[stptr-1]) || isnan(stack[stptr]))
- stack[stptr-1] = 0.0;
- else
- stack[stptr-1] = stack[stptr-1] <= stack[stptr] ? 1.0 : 0.0;
- stptr--;
- break;
- case OP_GT:
- if(stptr<1){
- rrd_set_error("RPN stack underflow");
- free(stack);
- return -1;
- }
- if (isnan(stack[stptr-1]) || isnan(stack[stptr]))
- stack[stptr-1] = 0.0;
- else
- stack[stptr-1] = stack[stptr-1] > stack[stptr] ? 1.0 : 0.0;
- stptr--;
- break;
- case OP_GE:
- if(stptr<1){
- rrd_set_error("RPN stack underflow");
- free(stack);
- return -1;
- }
- if (isnan(stack[stptr-1]) || isnan(stack[stptr]))
- stack[stptr-1] = 0.0;
- else
- stack[stptr-1] = stack[stptr-1] >= stack[stptr] ? 1.0 : 0.0;
- stptr--;
- break;
- case OP_EQ:
- if(stptr<1){
- rrd_set_error("RPN stack underflow");
- free(stack);
- return -1;
- }
- if (isnan(stack[stptr-1]) || isnan(stack[stptr]))
- stack[stptr-1] = 0.0;
- else
- stack[stptr-1] = stack[stptr-1] == stack[stptr] ? 1.0 : 0.0;
- stptr--;
- break;
- case OP_IF:
- if(stptr<2){
- rrd_set_error("RPN stack underflow");
- free(stack);
- return -1;
- }
- stack[stptr-2] = stack[stptr-2] != 0.0 ? stack[stptr-1] : stack[stptr];
- stptr--;
- stptr--;
- break;
- case OP_MIN:
- if(stptr<1){
- rrd_set_error("RPN stack underflow");
- free(stack);
- return -1;
- }
- if (isnan(stack[stptr-1]))
- ;
- else if (isnan(stack[stptr]))
- stack[stptr-1] = stack[stptr];
- else if (stack[stptr-1] > stack[stptr])
- stack[stptr-1] = stack[stptr];
- stptr--;
- break;
- case OP_MAX:
- if(stptr<1){
- rrd_set_error("RPN stack underflow");
- free(stack);
- return -1;
- }
- if (isnan(stack[stptr-1]))
- ;
- else if (isnan(stack[stptr]))
- stack[stptr-1] = stack[stptr];
- else if (stack[stptr-1] < stack[stptr])
- stack[stptr-1] = stack[stptr];
- stptr--;
- break;
- case OP_LIMIT:
- if(stptr<2){
- rrd_set_error("RPN stack underflow");
- free(stack);
- return -1;
- }
- if (isnan(stack[stptr-2]))
- ;
- else if (isnan(stack[stptr-1]))
- stack[stptr-2] = stack[stptr-1];
- else if (isnan(stack[stptr]))
- stack[stptr-2] = stack[stptr];
- else if (stack[stptr-2] < stack[stptr-1])
- stack[stptr-2] = DNAN;
- else if (stack[stptr-2] > stack[stptr])
- stack[stptr-2] = DNAN;
- stptr-=2;
- break;
- case OP_UN:
- if(stptr<0){
- rrd_set_error("RPN stack underflow");
- free(stack);
+
+ /* Step through the new cdef results array and
+ * calculate the values
+ */
+ for (now = im->gdes[gdi].start;
+ now<=im->gdes[gdi].end;
+ now += im->gdes[gdi].step)
+ {
+ rpnp_t *rpnp = im -> gdes[gdi].rpnp;
+
+ /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
+ * in this case we are advancing by timesteps;
+ * we use the fact that time_t is a synonym for long
+ */
+ if (rpn_calc(rpnp,&rpnstack,(long) now,
+ im->gdes[gdi].data,++dataidx) == -1) {
+ /* rpn_calc sets the error string */
+ rpnstack_free(&rpnstack);
return -1;
- }
- stack[stptr] = isnan(stack[stptr]) ? 1.0 : 0.0;
- break;
- case OP_END:
- break;
- }
- }
- if(stptr!=0){
- rrd_set_error("RPN final stack size != 1");
- free(stack);
- return -1;
- }
- im->gdes[gdi].data[++dataidx] = stack[0];
+ }
+ } /* enumerate over time steps within a CDEF */
+ break;
+ default:
+ continue;
}
- }
- free(stack);
+ } /* enumerate over CDEFs */
+ rpnstack_free(&rpnstack);
return 0;
}
-#undef dc_stacksize
-
/* massage data so, that we get one value for each x coordinate in the graph */
int
data_proc( image_desc_t *im ){
(im->gdes[i].gf==GF_LINE2) ||
(im->gdes[i].gf==GF_LINE3) ||
(im->gdes[i].gf==GF_AREA) ||
+ (im->gdes[i].gf==GF_TICK) ||
(im->gdes[i].gf==GF_STACK)){
if((im->gdes[i].p_data = malloc((im->xsize +1)
* sizeof(rrd_value_t)))==NULL){
case GF_LINE2:
case GF_LINE3:
case GF_AREA:
+ case GF_TICK:
paintval = 0.0;
case GF_STACK:
vidx = im->gdes[ii].vidx;
if (! isnan(value)) {
paintval += value;
im->gdes[ii].p_data[i] = paintval;
- if (finite(paintval)){
+ /* GF_TICK: the data values are not relevant for min and max */
+ if (finite(paintval) && im->gdes[ii].gf != GF_TICK ){
if (isnan(minval) || paintval < minval)
minval = paintval;
if (isnan(maxval) || paintval > maxval)
case GF_VRULE:
case GF_DEF:
case GF_CDEF:
+ case GF_VDEF:
break;
}
}
}
switch (im->gdes[i].cf){
+ case CF_HWPREDICT:
+ case CF_DEVPREDICT:
+ case CF_DEVSEASONAL:
+ case CF_SEASONAL:
case CF_AVERAGE:
validsteps++;
printval += im->gdes[vidx].data[ii];
case CF_MINIMUM:
printval = min( printval, im->gdes[vidx].data[ii]);
break;
+ case CF_FAILURES:
case CF_MAXIMUM:
printval = max( printval, im->gdes[vidx].data[ii]);
break;
printval = im->gdes[vidx].data[ii];
}
}
- if (im->gdes[i].cf == CF_AVERAGE) {
+ if (im->gdes[i].cf == CF_AVERAGE || im -> gdes[i].cf > CF_LAST) {
if (validsteps > 1) {
printval = (printval / validsteps);
}
case GF_LINE2:
case GF_LINE3:
case GF_AREA:
+ case GF_TICK:
case GF_STACK:
case GF_HRULE:
case GF_VRULE:
break;
case GF_DEF:
case GF_CDEF:
+ case GF_VDEF:
break;
}
}
int lazy_check(image_desc_t *im){
FILE *fd = NULL;
- int size = 1;
+ int size = 1;
struct stat gifstat;
if (im->lazy == 0) return 0; /* no lazy option */
return 0; /* the file does not exist */
switch (im->imgformat) {
case IF_GIF:
- size = GifSize(fd,&(im->xgif),&(im->ygif));
- break;
+ size = GifSize(fd,&(im->xgif),&(im->ygif));
+ break;
case IF_PNG:
- size = PngSize(fd,&(im->xgif),&(im->ygif));
- break;
+ size = PngSize(fd,&(im->xgif),&(im->ygif));
+ break;
}
fclose(fd);
return size;
if(data_fetch(im)==-1)
return -1;
- /* evaluate CDEF operations ... */
+ /* evaluate VDEF and CDEF operations ... */
if(data_calc(im)==-1)
return -1;
switch(im->gdes[i].gf){
case GF_CDEF:
+ case GF_VDEF:
case GF_DEF:
case GF_PRINT:
case GF_GPRINT:
case GF_COMMENT:
case GF_HRULE:
case GF_VRULE:
- break;
+ break;
+ case GF_TICK:
+ for (ii = 0; ii < im->xsize; ii++)
+ {
+ if (!isnan(im->gdes[i].p_data[ii]) &&
+ im->gdes[i].p_data[ii] > 0.0)
+ {
+ /* generate a tick */
+ gdImageLine(gif, im -> xorigin + ii,
+ im -> yorigin - (im -> gdes[i].yrule * im -> ysize),
+ im -> xorigin + ii,
+ im -> yorigin,
+ im -> gdes[i].col.i);
+ }
+ }
+ break;
case GF_LINE1:
case GF_LINE2:
case GF_LINE3:
switch(im->gdes[i].gf){
case GF_HRULE:
+printf("DEBUG: HRULE at %f\n",im->gdes[i].yrule);
+ if(isnan(im->gdes[i].yrule)) { /* fetch variable */
+ im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
+ };
if(im->gdes[i].yrule >= im->minval
&& im->gdes[i].yrule <= im->maxval)
gdImageLine(gif,
im->gdes[i].col.i);
break;
case GF_VRULE:
- if(im->gdes[i].xrule >= im->start
- && im->gdes[i].xrule <= im->end)
- gdImageLine(gif,
+ if(im->gdes[i].xrule == 0) { /* fetch variable */
+ im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
+ };
+ if(im->gdes[i].xrule >= im->start
+ && im->gdes[i].xrule <= im->end)
+ gdImageLine(gif,
xtr(im,im->gdes[i].xrule),im->yorigin,
xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
im->gdes[i].col.i);
- break;
+ break;
default:
break;
}
strcpy(im.gdes[im.gdes_c-1].legend, &argv[i][argstart]);
break;
case GF_HRULE:
- if(sscanf(
- &argv[i][argstart],
- "%lf#%2x%2x%2x:%n",
- &im.gdes[im.gdes_c-1].yrule,
- &col_red,&col_green,&col_blue,
- &strstart) >= 4){
- im.gdes[im.gdes_c-1].col.red = col_red;
- im.gdes[im.gdes_c-1].col.green = col_green;
- im.gdes[im.gdes_c-1].col.blue = col_blue;
- if(strstart <= 0){
- im.gdes[im.gdes_c-1].legend[0] = '\0';
- } else {
- scan_for_col(&argv[i][argstart+strstart],FMT_LEG_LEN,im.gdes[im.gdes_c-1].legend);
+ /* scan for either "HRULE:vname#..." or "HRULE:num#..."
+ *
+ * If a vname is used, the value NaN is set; this is catched
+ * when graphing. Setting value NaN from the script is not
+ * permitted
+ */
+ strstart=0;
+ sscanf(&argv[i][argstart], "%lf#%n"
+ ,&im.gdes[im.gdes_c-1].yrule
+ ,&strstart
+ );
+ if (strstart==0) { /* no number, should be vname */
+ sscanf(&argv[i][argstart], DEF_NAM_FMT "#%n"
+ ,varname
+ ,&strstart
+ );
+ if (strstart) {
+ im.gdes[im.gdes_c-1].yrule = DNAN;/* signal use of vname */
+ if((im.gdes[im.gdes_c-1].vidx=find_var(&im,varname))==-1){
+ im_free(&im);
+ rrd_set_error("unknown variable '%s' in HRULE",varname);
+ return -1;
+ }
+ if(im.gdes[im.gdes[im.gdes_c-1].vidx].gf != GF_VDEF) {
+ im_free(&im);
+ rrd_set_error("Only VDEF is allowed in HRULE",varname);
+ return -1;
+ }
}
} else {
+printf("DEBUG: matched HRULE:num\n");
+printf("DEBUG: strstart==%i\n",strstart);
+ };
+ if (strstart==0) {
im_free(&im);
rrd_set_error("can't parse '%s'",&argv[i][argstart]);
return -1;
- }
+ } else {
+ int n=0;
+ if(sscanf(
+ &argv[i][argstart+strstart],
+ "%2x%2x%2x:%n",
+ &col_red,
+ &col_green,
+ &col_blue,
+ &n)>=3) {
+ im.gdes[im.gdes_c-1].col.red = col_red;
+ im.gdes[im.gdes_c-1].col.green = col_green;
+ im.gdes[im.gdes_c-1].col.blue = col_blue;
+ if (n==0) {
+ im.gdes[im.gdes_c-1].legend[0] = '\0';
+ } else {
+ scan_for_col(&argv[i][argstart+strstart+n],FMT_LEG_LEN,im.gdes[im.gdes_c-1].legend);
+ }
+ } else {
+ im_free(&im);
+ rrd_set_error("can't parse '%s'",&argv[i][argstart]);
+ return -1;
+ }
+ }
+
break;
case GF_VRULE:
- if(sscanf(
+ /* scan for either "VRULE:vname#..." or "VRULE:num#..."
+ *
+ * If a vname is used, the value 0 is set; this is catched
+ * when graphing. Setting value 0 from the script is not
+ * permitted
+ */
+ strstart=0;
+ sscanf(&argv[i][argstart], "%lu#%n"
+ ,(long unsigned int *)&im.gdes[im.gdes_c-1].xrule
+ ,&strstart
+ );
+ if (strstart==0) { /* no number, should be vname */
+ sscanf(&argv[i][argstart], DEF_NAM_FMT "#%n"
+ ,varname
+ ,&strstart
+ );
+ if (strstart!=0) { /* vname matched */
+ im.gdes[im.gdes_c-1].xrule = 0;/* signal use of vname */
+ if((im.gdes[im.gdes_c-1].vidx=find_var(&im,varname))==-1){
+ im_free(&im);
+ rrd_set_error("unknown variable '%s' in VRULE",varname);
+ return -1;
+ }
+ if(im.gdes[im.gdes[im.gdes_c-1].vidx].gf != GF_VDEF) {
+ im_free(&im);
+ rrd_set_error("Only VDEF is allowed in VRULE",varname);
+ return -1;
+ }
+ }
+ } else {
+ if (im.gdes[im.gdes_c-1].xrule==0)
+ strstart=0;
+ }
+
+ if (strstart==0) {
+ im_free(&im);
+ rrd_set_error("can't parse '%s'",&argv[i][argstart]);
+ return -1;
+ } else {
+ int n=0;
+ if(sscanf(
+ &argv[i][argstart+strstart],
+ "%2x%2x%2x:%n",
+ &col_red,
+ &col_green,
+ &col_blue,
+ &n)>=3) {
+ im.gdes[im.gdes_c-1].col.red = col_red;
+ im.gdes[im.gdes_c-1].col.green = col_green;
+ im.gdes[im.gdes_c-1].col.blue = col_blue;
+ if (n==0) {
+ im.gdes[im.gdes_c-1].legend[0] = '\0';
+ } else {
+ scan_for_col(&argv[i][argstart+strstart+n],FMT_LEG_LEN,im.gdes[im.gdes_c-1].legend);
+ }
+ } else {
+ im_free(&im);
+ rrd_set_error("can't parse '%s'",&argv[i][argstart]);
+ return -1;
+ }
+ }
+ break;
+ case GF_TICK:
+ if((scancount=sscanf(
&argv[i][argstart],
- "%lu#%2x%2x%2x:%n",
- &im.gdes[im.gdes_c-1].xrule,
+ "%29[^:#]#%2x%2x%2x:%lf:%n",
+ varname,
&col_red,
&col_green,
&col_blue,
- &strstart) >= 4){
+ &(im.gdes[im.gdes_c-1].yrule),
+ &strstart))>=1)
+ {
im.gdes[im.gdes_c-1].col.red = col_red;
im.gdes[im.gdes_c-1].col.green = col_green;
im.gdes[im.gdes_c-1].col.blue = col_blue;
- if(strstart <= 0){
+ if(strstart <= 0){
im.gdes[im.gdes_c-1].legend[0] = '\0';
} else {
scan_for_col(&argv[i][argstart+strstart],FMT_LEG_LEN,im.gdes[im.gdes_c-1].legend);
}
- } else {
- im_free(&im);
- rrd_set_error("can't parse '%s'",&argv[i][argstart]);
- return -1;
- }
- break;
+ if((im.gdes[im.gdes_c-1].vidx=find_var(&im,varname))==-1){
+ im_free(&im);
+ rrd_set_error("unknown variable '%s'",varname);
+ return -1;
+ }
+ if (im.gdes[im.gdes_c-1].yrule <= 0.0 || im.gdes[im.gdes_c-1].yrule > 1.0)
+ {
+ im_free(&im);
+ rrd_set_error("Tick mark scaling factor out of range");
+ return -1;
+ }
+ if (scancount < 4)
+ im.gdes[im.gdes_c-1].col.red = -1;
+ if (scancount < 5)
+ /* default tick marks: 10% of the y-axis */
+ im.gdes[im.gdes_c-1].yrule = 0.1;
+
+ } else {
+ im_free(&im);
+ rrd_set_error("can't parse '%s'",&argv[i][argstart]);
+ return -1;
+ } /* endif sscanf */
+ break;
case GF_STACK:
if(linepass == 0){
im_free(&im);
rrd_set_error("can't parse CDEF '%s'",&argv[i][argstart]);
return -1;
}
- /* checking for duplicate DEF CDEFS */
+ /* checking for duplicate variable names */
if(find_var(&im,im.gdes[im.gdes_c-1].vname) != -1){
im_free(&im);
rrd_set_error("duplicate variable '%s'",
im.gdes[im.gdes_c-1].vname);
return -1;
}
- if((im.gdes[im.gdes_c-1].rpnp = str2rpn(&im,rpnex))== NULL){
+ if((im.gdes[im.gdes_c-1].rpnp =
+ rpn_parse((void*)&im,rpnex,&find_var_wrapper))== NULL){
rrd_set_error("invalid rpn expression '%s'", rpnex);
im_free(&im);
return -1;
}
free(rpnex);
break;
+ case GF_VDEF:
+ /*
+ * strstart is set to zero and will NOT be changed
+ * if the comma is not matched. This means that it
+ * remains zero. Although strstart is initialized to
+ * zero at the beginning of this loop, we do it again
+ * here just in case someone changes the code...
+ *
+ * According to the docs we cannot rely on the
+ * returned value from sscanf; it can be 2 or 3,
+ * depending on %n incrementing it or not.
+ */
+ strstart=0;
+ sscanf(
+ &argv[i][argstart],
+ DEF_NAM_FMT "=" DEF_NAM_FMT ",%n",
+ im.gdes[im.gdes_c-1].vname,
+ varname,
+ &strstart);
+ if (strstart){
+ /* checking both variable names */
+ if (find_var(&im,im.gdes[im.gdes_c-1].vname) != -1){
+ im_free(&im);
+ rrd_set_error("duplicate variable '%s'",
+ im.gdes[im.gdes_c-1].vname);
+ return -1;
+ } else {
+ if ((im.gdes[im.gdes_c-1].vidx=find_var(&im,varname)) == -1){
+ im_free(&im);
+ rrd_set_error("variable '%s' not known in VDEF '%s'",
+ varname,
+ im.gdes[im.gdes_c-1].vname);
+ return -1;
+ } else {
+ if(im.gdes[im.gdes[im.gdes_c-1].vidx].gf != GF_DEF
+ && im.gdes[im.gdes[im.gdes_c-1].vidx].gf != GF_CDEF){
+ rrd_set_error("variable '%s' not DEF nor CDEF in VDEF '%s'",
+ varname,
+ im.gdes[im.gdes_c-1].vname);
+ im_free(&im);
+ return -1;
+ }
+ }
+ /* parsed upto and including the first comma. Now
+ * see what function is requested. This function
+ * sets the error string.
+ */
+ if (vdef_parse(&im.gdes[im.gdes_c-1],&argv[i][argstart+strstart])<0) {
+ im_free(&im);
+ return -1;
+ };
+ }
+ } else {
+ im_free(&im);
+ rrd_set_error("can't parse VDEF '%s'",&argv[i][argstart]);
+ return -1;
+ }
+ break;
case GF_DEF:
if (sscanf(
&argv[i][argstart],
}
return 0;
}
+int
+vdef_parse(gdes,str)
+struct graph_desc_t *gdes;
+char *str;
+{
+ /* A VDEF currently is either "func" or "param,func"
+ * so the parsing is rather simple. Change if needed.
+ */
+ double param;
+ char func[30];
+ int n;
+
+ n=0;
+ sscanf(str,"%le,%29[A-Z]%n",¶m,func,&n);
+ if (n==strlen(str)) { /* matched */
+ ;
+ } else {
+ n=0;
+ sscanf(str,"%29[A-Z]%n",func,&n);
+ if (n==strlen(str)) { /* matched */
+ param=DNAN;
+ } else {
+ rrd_set_error("Unknown function string '%s' in VDEF '%s'"
+ ,str
+ ,gdes->vname
+ );
+ return -1;
+ }
+ }
+ if (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
+ else if (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
+ else if (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
+ else if (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
+ else if (!strcmp("FIRST", func)) gdes->vf.op = VDEF_FIRST;
+ else if (!strcmp("LAST", func)) gdes->vf.op = VDEF_LAST;
+ else {
+ rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
+ ,func
+ ,gdes->vname
+ );
+ return -1;
+ };
+
+ switch (gdes->vf.op) {
+ case VDEF_PERCENT:
+ if (isnan(param)) { /* no parameter given */
+ rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
+ ,func
+ ,gdes->vname
+ );
+ return -1;
+ };
+ if (param>=0.0 && param<=100.0) {
+ gdes->vf.param = param;
+ gdes->vf.val = DNAN; /* undefined */
+ gdes->vf.when = 0; /* undefined */
+ } else {
+ rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
+ ,param
+ ,gdes->vname
+ );
+ return -1;
+ };
+ break;
+ case VDEF_MAXIMUM:
+ case VDEF_AVERAGE:
+ case VDEF_MINIMUM:
+ case VDEF_FIRST:
+ case VDEF_LAST:
+ if (isnan(param)) {
+ gdes->vf.param = DNAN;
+ gdes->vf.val = DNAN;
+ gdes->vf.when = 0;
+ } else {
+ rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
+ ,func
+ ,gdes->vname
+ );
+ return -1;
+ };
+ break;
+ };
+ return 0;
+}
+int
+vdef_calc(im,gdi)
+image_desc_t *im;
+int gdi;
+{
+ graph_desc_t *src,*dst;
+ rrd_value_t *data;
+ long step,steps;
+
+ dst = &im->gdes[gdi];
+ src = &im->gdes[dst->vidx];
+ data = src->data + src->ds + src->ds_cnt; /* skip first value! */
+ steps = (src->end - src->start) / src->step;
+
+#if 0
+printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
+ ,src->start
+ ,src->end
+ ,steps
+ );
+#endif
+
+ switch (im->gdes[gdi].vf.op) {
+ case VDEF_PERCENT: {
+ rrd_value_t * array;
+ int field;
+
+
+ if ((array = malloc(steps*sizeof(double)))==NULL) {
+ rrd_set_error("malloc VDEV_PERCENT");
+ return -1;
+ }
+ for (step=0;step < steps; step++) {
+ array[step]=data[step*src->ds_cnt];
+ }
+ qsort(array,step,sizeof(double),vdef_percent_compar);
+
+ field = (steps-1)*dst->vf.param/100;
+ dst->vf.val = array[field];
+ dst->vf.when = 0; /* no time component */
+#if 1
+for(step=0;step<steps;step++)
+printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
+#endif
+ }
+ break;
+ case VDEF_MAXIMUM:
+ step=0;
+ while (step != steps && isnan(data[step*src->ds_cnt])) step++;
+ if (step == steps) {
+ dst->vf.val = DNAN;
+ dst->vf.when = 0;
+ } else {
+ dst->vf.val = data[steps*src->ds_cnt];
+ dst->vf.when = src->start + (step+1)*src->step;
+ }
+ while (step != steps) {
+ if (finite(data[step*src->ds_cnt])) {
+ if (data[step*src->ds_cnt] > dst->vf.val) {
+ dst->vf.val = data[steps*src->ds_cnt];
+ dst->vf.when = src->start + (step+1)*src->step;
+ }
+ }
+ step++;
+ }
+ break;
+ case VDEF_AVERAGE: {
+ int cnt=0;
+ double sum=0.0;
+ for (step=0;step<steps;step++) {
+ if (finite(data[step*src->ds_cnt])) {
+ sum += data[step*src->ds_cnt];
+ cnt ++;
+ }
+ step++;
+ }
+ if (cnt) {
+ dst->vf.val = sum/cnt;
+ dst->vf.when = 0; /* no time component */
+ } else {
+ dst->vf.val = DNAN;
+ dst->vf.when = 0;
+ }
+ }
+ break;
+ case VDEF_MINIMUM:
+ step=0;
+ while (step != steps && isnan(data[step*src->ds_cnt])) step++;
+ if (step == steps) {
+ dst->vf.val = DNAN;
+ dst->vf.when = 0;
+ } else {
+ dst->vf.val = data[steps*src->ds_cnt];
+ dst->vf.when = src->start + (step+1)*src->step;
+ }
+ while (step != steps) {
+ if (finite(data[step*src->ds_cnt])) {
+ if (data[step*src->ds_cnt] < dst->vf.val) {
+ dst->vf.val = data[steps*src->ds_cnt];
+ dst->vf.when = src->start + (step+1)*src->step;
+ }
+ }
+ step++;
+ }
+ break;
+ case VDEF_FIRST:
+ /* The time value returned here is one step before the
+ * actual time value. This is the start of the first
+ * non-NaN interval.
+ */
+ step=0;
+ while (step != steps && isnan(data[step*src->ds_cnt])) step++;
+ if (step == steps) { /* all entries were NaN */
+ dst->vf.val = DNAN;
+ dst->vf.when = 0;
+ } else {
+ dst->vf.val = data[step*src->ds_cnt];
+ dst->vf.when = src->start + step*src->step;
+ }
+ break;
+ case VDEF_LAST:
+ /* The time value returned here is the
+ * actual time value. This is the end of the last
+ * non-NaN interval.
+ */
+ step=steps-1;
+ while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
+ if (step < 0) { /* all entries were NaN */
+ dst->vf.val = DNAN;
+ dst->vf.when = 0;
+ } else {
+ dst->vf.val = data[step*src->ds_cnt];
+ dst->vf.when = src->start + (step+1)*src->step;
+ }
+ break;
+ }
+ return 0;
+}
+
+/* NaN <= -INF <= finite_values <= INF */
+int
+vdef_percent_compar(a,b)
+const void *a,*b;
+{
+ /* Equality is not returned; this doesn't hurt except
+ * (maybe) for a little performance.
+ */
+ /* First catch NaN values. They are smallest */
+ if (isnan( *(double *)a )) return -1;
+ if (isnan( *(double *)b )) return 1;
+
+ /* NaN doestn't reach this part so INF and -INF are extremes.
+ * The sign from isinf() is compatible with the sign we return
+ */
+ if (isinf( *(double *)a )) return isinf( *(double *)a );
+ if (isinf( *(double *)b )) return isinf( *(double *)b );
+
+ /* If we reach this, both values must be finite */
+ if ( *(double *)a < *(double *)b ) return -1; else return 1;
+}