X-Git-Url: https://git.octo.it/?a=blobdiff_plain;f=src%2Frrd_graph.c;h=18f7d2382cc9c25650439c147059dec5da5925b3;hb=9460822de0482683f76ebcaf17c0e1ed5cee0000;hp=f42acaf397b1980f5c966795f92b2a76bdc16140;hpb=7c016dfa001ae254bf4e18126f814ee8f0abd821;p=rrdtool.git diff --git a/src/rrd_graph.c b/src/rrd_graph.c index f42acaf..18f7d23 100644 --- a/src/rrd_graph.c +++ b/src/rrd_graph.c @@ -13,6 +13,7 @@ #include #include #endif +#include "rrd_rpncalc.h" #define SmallFont gdLucidaNormal10 #define LargeFont gdLucidaBold12 @@ -35,27 +36,25 @@ enum grc_en {GRC_CANVAS=0,GRC_BACK,GRC_SHADEA,GRC_SHADEB, 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_TICK,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 */ @@ -166,8 +165,9 @@ typedef struct graph_desc_t { 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 */ @@ -247,13 +247,13 @@ void expand_range(image_desc_t *); 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 *); @@ -268,7 +268,9 @@ int gdes_alloc(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 \ @@ -349,6 +351,7 @@ enum gf_en gf_conv(char *string){ conv_if(TICK,GF_TICK) conv_if(DEF,GF_DEF) conv_if(CDEF,GF_CDEF) + conv_if(VDEF,GF_VDEF) return (-1); } @@ -688,18 +691,18 @@ reduce_data( 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_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_FAILURES: + /* an interval contains a failure if any subintervals contained a failure */ case CF_MAXIMUM: newval = max (newval,srcptr[i*(*ds_cnt)+col]); break; @@ -711,15 +714,15 @@ reduce_data( } if (validval == 0){newval = DNAN;} else{ switch (cf) { - case CF_HWPREDICT: - case CF_DEVSEASONAL: - case CF_DEVPREDICT: - case CF_SEASONAL: - 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_FAILURES: + case CF_FAILURES: case CF_MAXIMUM: case CF_LAST: break; @@ -822,12 +825,19 @@ data_fetch( image_desc_t *im ) * 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;iigdes_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; @@ -853,536 +863,163 @@ lcd(long *num){ 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; + rpnstack_t rpnstack; - for (gdi=0;gdigdes_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; - }; - + rpnstack_init(&rpnstack); - 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; + for (gdi=0;gdigdes_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 ){ @@ -1462,6 +1099,7 @@ data_proc( image_desc_t *im ){ case GF_VRULE: case GF_DEF: case GF_CDEF: + case GF_VDEF: break; } } @@ -1766,6 +1404,7 @@ print_calc(image_desc_t *im, char ***prdata) break; case GF_DEF: case GF_CDEF: + case GF_VDEF: break; } } @@ -2493,7 +2132,7 @@ graph_paint(image_desc_t *im, char ***calcpr) if(data_fetch(im)==-1) return -1; - /* evaluate CDEF operations ... */ + /* evaluate VDEF and CDEF operations ... */ if(data_calc(im)==-1) return -1; @@ -2607,6 +2246,7 @@ graph_paint(image_desc_t *im, char ***calcpr) switch(im->gdes[i].gf){ case GF_CDEF: + case GF_VDEF: case GF_DEF: case GF_PRINT: case GF_GPRINT: @@ -2706,6 +2346,10 @@ graph_paint(image_desc_t *im, char ***calcpr) 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, @@ -2714,13 +2358,16 @@ graph_paint(image_desc_t *im, char ***calcpr) 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; } @@ -3174,47 +2821,129 @@ rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize) 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( - &argv[i][argstart], - "%lu#%2x%2x%2x:%n", - &im.gdes[im.gdes_c-1].xrule, - &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 "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: @@ -3315,20 +3044,79 @@ rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize) 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], @@ -3435,4 +3223,248 @@ int bad_format(char *fmt) { } 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;stepds_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;stepds_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; +}