1 /****************************************************************************
2 * RRDtool 1.1.x Copyright Tobias Oetiker, 1997 - 2002
3 ****************************************************************************
4 * rrd__graph.c make creates ne rrds
5 ****************************************************************************/
17 #include "rrd_graph.h"
18 #include "rrd_graph_helper.h"
20 /* some constant definitions */
23 #ifndef RRD_DEFAULT_FONT
24 #define RRD_DEFAULT_FONT "/usr/share/fonts/truetype/openoffice/ariosor.ttf"
25 /* #define RRD_DEFAULT_FONT "/usr/share/fonts/truetype/Arial.ttf" */
29 text_prop_t text_prop[] = {
30 { 10.0, RRD_DEFAULT_FONT }, /* default */
31 { 12.0, RRD_DEFAULT_FONT }, /* title */
32 { 8.0, RRD_DEFAULT_FONT }, /* axis */
33 { 10.0, RRD_DEFAULT_FONT }, /* unit */
34 { 10.0, RRD_DEFAULT_FONT } /* legend */
38 {0, TMT_SECOND,30, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
39 {2, TMT_MINUTE,1, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
40 {5, TMT_MINUTE,2, TMT_MINUTE,10, TMT_MINUTE,10, 0,"%H:%M"},
41 {10, TMT_MINUTE,5, TMT_MINUTE,20, TMT_MINUTE,20, 0,"%H:%M"},
42 {30, TMT_MINUTE,10, TMT_HOUR,1, TMT_HOUR,1, 0,"%H:%M"},
43 {60, TMT_MINUTE,30, TMT_HOUR,2, TMT_HOUR,2, 0,"%H:%M"},
44 {180, TMT_HOUR,1, TMT_HOUR,6, TMT_HOUR,6, 0,"%H:%M"},
45 /*{300, TMT_HOUR,3, TMT_HOUR,12, TMT_HOUR,12, 12*3600,"%a %p"}, this looks silly*/
46 {600, TMT_HOUR,6, TMT_DAY,1, TMT_DAY,1, 24*3600,"%a"},
47 {1800, TMT_HOUR,12, TMT_DAY,1, TMT_DAY,2, 24*3600,"%a"},
48 {3600, TMT_DAY,1, TMT_WEEK,1, TMT_WEEK,1, 7*24*3600,"Week %V"},
49 {3*3600, TMT_WEEK,1, TMT_MONTH,1, TMT_WEEK,2, 7*24*3600,"Week %V"},
50 {6*3600, TMT_MONTH,1, TMT_MONTH,1, TMT_MONTH,1, 30*24*3600,"%b"},
51 {48*3600, TMT_MONTH,1, TMT_MONTH,3, TMT_MONTH,3, 30*24*3600,"%b"},
52 {10*24*3600, TMT_YEAR,1, TMT_YEAR,1, TMT_YEAR,1, 365*24*3600,"%y"},
53 {-1,TMT_MONTH,0,TMT_MONTH,0,TMT_MONTH,0,0,""}
56 /* sensible logarithmic y label intervals ...
57 the first element of each row defines the possible starting points on the
58 y axis ... the other specify the */
60 double yloglab[][12]= {{ 1e9, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
61 { 1e3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
62 { 1e1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
63 /* { 1e1, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, */
64 { 1e1, 1, 2.5, 5, 7.5, 0, 0, 0, 0, 0, 0, 0 },
65 { 1e1, 1, 2, 4, 6, 8, 0, 0, 0, 0, 0, 0 },
66 { 1e1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0 },
67 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }};
69 /* sensible y label intervals ...*/
87 gfx_color_t graph_col[] = /* default colors */
88 { 0xFFFFFFFF, /* canvas */
89 0xF0F0F0FF, /* background */
90 0xD0D0D0FF, /* shade A */
91 0xA0A0A0FF, /* shade B */
92 0x909090FF, /* grid */
93 0xE05050FF, /* major grid */
94 0x000000FF, /* font */
95 0x000000FF, /* frame */
96 0xFF0000FF /* arrow */
103 # define DPRINT(x) (void)(printf x, printf("\n"))
109 /* initialize with xtr(im,0); */
111 xtr(image_desc_t *im,time_t mytime){
114 pixie = (double) im->xsize / (double)(im->end - im->start);
117 return (int)((double)im->xorigin
118 + pixie * ( mytime - im->start ) );
121 /* translate data values into y coordinates */
123 ytr(image_desc_t *im, double value){
128 pixie = (double) im->ysize / (im->maxval - im->minval);
130 pixie = (double) im->ysize / (log10(im->maxval) - log10(im->minval));
132 } else if(!im->logarithmic) {
133 yval = im->yorigin - pixie * (value - im->minval) + 0.5;
135 if (value < im->minval) {
138 yval = im->yorigin - pixie * (log10(value) - log10(im->minval)) + 0.5;
141 /* make sure we don't return anything too unreasonable. GD lib can
142 get terribly slow when drawing lines outside its scope. This is
143 especially problematic in connection with the rigid option */
146 } else if ((int)yval > im->yorigin) {
147 return im->yorigin+2;
148 } else if ((int) yval < im->yorigin - im->ysize){
149 return im->yorigin - im->ysize - 2;
157 /* conversion function for symbolic entry names */
160 #define conv_if(VV,VVV) \
161 if (strcmp(#VV, string) == 0) return VVV ;
163 enum gf_en gf_conv(char *string){
165 conv_if(PRINT,GF_PRINT)
166 conv_if(GPRINT,GF_GPRINT)
167 conv_if(COMMENT,GF_COMMENT)
168 conv_if(HRULE,GF_HRULE)
169 conv_if(VRULE,GF_VRULE)
170 conv_if(LINE,GF_LINE)
171 conv_if(AREA,GF_AREA)
172 conv_if(STACK,GF_STACK)
173 conv_if(TICK,GF_TICK)
175 conv_if(CDEF,GF_CDEF)
176 conv_if(VDEF,GF_VDEF)
181 enum if_en if_conv(char *string){
189 enum tmt_en tmt_conv(char *string){
191 conv_if(SECOND,TMT_SECOND)
192 conv_if(MINUTE,TMT_MINUTE)
193 conv_if(HOUR,TMT_HOUR)
195 conv_if(WEEK,TMT_WEEK)
196 conv_if(MONTH,TMT_MONTH)
197 conv_if(YEAR,TMT_YEAR)
201 enum grc_en grc_conv(char *string){
203 conv_if(BACK,GRC_BACK)
204 conv_if(CANVAS,GRC_CANVAS)
205 conv_if(SHADEA,GRC_SHADEA)
206 conv_if(SHADEB,GRC_SHADEB)
207 conv_if(GRID,GRC_GRID)
208 conv_if(MGRID,GRC_MGRID)
209 conv_if(FONT,GRC_FONT)
210 conv_if(FRAME,GRC_FRAME)
211 conv_if(ARROW,GRC_ARROW)
216 enum text_prop_en text_prop_conv(char *string){
218 conv_if(DEFAULT,TEXT_PROP_DEFAULT)
219 conv_if(TITLE,TEXT_PROP_TITLE)
220 conv_if(AXIS,TEXT_PROP_AXIS)
221 conv_if(UNIT,TEXT_PROP_UNIT)
222 conv_if(LEGEND,TEXT_PROP_LEGEND)
232 im_free(image_desc_t *im)
235 if (im == NULL) return 0;
236 for(i=0;i<im->gdes_c;i++){
237 if (im->gdes[i].data_first){
238 /* careful here, because a single pointer can occur several times */
239 free (im->gdes[i].data);
240 if (im->gdes[i].ds_namv){
241 for (ii=0;ii<im->gdes[i].ds_cnt;ii++)
242 free(im->gdes[i].ds_namv[ii]);
243 free(im->gdes[i].ds_namv);
246 free (im->gdes[i].p_data);
247 free (im->gdes[i].rpnp);
253 /* find SI magnitude symbol for the given number*/
256 image_desc_t *im, /* image description */
263 char *symbol[] = {"a", /* 10e-18 Atto */
264 "f", /* 10e-15 Femto */
265 "p", /* 10e-12 Pico */
266 "n", /* 10e-9 Nano */
267 "u", /* 10e-6 Micro */
268 "m", /* 10e-3 Milli */
273 "T", /* 10e12 Tera */
274 "P", /* 10e15 Peta */
280 if (*value == 0.0 || isnan(*value) ) {
284 sindex = floor(log(fabs(*value))/log((double)im->base));
285 *magfact = pow((double)im->base, (double)sindex);
286 (*value) /= (*magfact);
288 if ( sindex <= symbcenter && sindex >= -symbcenter) {
289 (*symb_ptr) = symbol[sindex+symbcenter];
297 /* find SI magnitude symbol for the numbers on the y-axis*/
300 image_desc_t *im /* image description */
304 char symbol[] = {'a', /* 10e-18 Atto */
305 'f', /* 10e-15 Femto */
306 'p', /* 10e-12 Pico */
307 'n', /* 10e-9 Nano */
308 'u', /* 10e-6 Micro */
309 'm', /* 10e-3 Milli */
314 'T', /* 10e12 Tera */
315 'P', /* 10e15 Peta */
321 if (im->unitsexponent != 9999) {
322 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
323 digits = floor(im->unitsexponent / 3);
325 digits = floor( log( max( fabs(im->minval),fabs(im->maxval)))/log((double)im->base));
327 im->magfact = pow((double)im->base , digits);
330 printf("digits %6.3f im->magfact %6.3f\n",digits,im->magfact);
333 if ( ((digits+symbcenter) < sizeof(symbol)) &&
334 ((digits+symbcenter) >= 0) )
335 im->symbol = symbol[(int)digits+symbcenter];
340 /* move min and max values around to become sensible */
343 expand_range(image_desc_t *im)
345 double sensiblevalues[] ={1000.0,900.0,800.0,750.0,700.0,
346 600.0,500.0,400.0,300.0,250.0,
347 200.0,125.0,100.0,90.0,80.0,
348 75.0,70.0,60.0,50.0,40.0,30.0,
349 25.0,20.0,10.0,9.0,8.0,
350 7.0,6.0,5.0,4.0,3.5,3.0,
351 2.5,2.0,1.8,1.5,1.2,1.0,
352 0.8,0.7,0.6,0.5,0.4,0.3,0.2,0.1,0.0,-1};
354 double scaled_min,scaled_max;
361 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
362 im->minval,im->maxval,im->magfact);
365 if (isnan(im->ygridstep)){
366 if(im->extra_flags & ALTAUTOSCALE) {
367 /* measure the amplitude of the function. Make sure that
368 graph boundaries are slightly higher then max/min vals
369 so we can see amplitude on the graph */
372 delt = im->maxval - im->minval;
374 fact = 2.0 * pow(10.0,
375 floor(log10(max(fabs(im->minval), fabs(im->maxval)))) - 2);
377 adj = (fact - delt) * 0.55;
379 printf("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n", im->minval, im->maxval, delt, fact, adj);
385 else if(im->extra_flags & ALTAUTOSCALE_MAX) {
386 /* measure the amplitude of the function. Make sure that
387 graph boundaries are slightly higher than max vals
388 so we can see amplitude on the graph */
389 adj = (im->maxval - im->minval) * 0.1;
393 scaled_min = im->minval / im->magfact;
394 scaled_max = im->maxval / im->magfact;
396 for (i=1; sensiblevalues[i] > 0; i++){
397 if (sensiblevalues[i-1]>=scaled_min &&
398 sensiblevalues[i]<=scaled_min)
399 im->minval = sensiblevalues[i]*(im->magfact);
401 if (-sensiblevalues[i-1]<=scaled_min &&
402 -sensiblevalues[i]>=scaled_min)
403 im->minval = -sensiblevalues[i-1]*(im->magfact);
405 if (sensiblevalues[i-1] >= scaled_max &&
406 sensiblevalues[i] <= scaled_max)
407 im->maxval = sensiblevalues[i-1]*(im->magfact);
409 if (-sensiblevalues[i-1]<=scaled_max &&
410 -sensiblevalues[i] >=scaled_max)
411 im->maxval = -sensiblevalues[i]*(im->magfact);
415 /* adjust min and max to the grid definition if there is one */
416 im->minval = (double)im->ylabfact * im->ygridstep *
417 floor(im->minval / ((double)im->ylabfact * im->ygridstep));
418 im->maxval = (double)im->ylabfact * im->ygridstep *
419 ceil(im->maxval /( (double)im->ylabfact * im->ygridstep));
423 fprintf(stderr,"SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
424 im->minval,im->maxval,im->magfact);
429 /* reduce data reimplementation by Alex */
433 enum cf_en cf, /* which consolidation function ?*/
434 unsigned long cur_step, /* step the data currently is in */
435 time_t *start, /* start, end and step as requested ... */
436 time_t *end, /* ... by the application will be ... */
437 unsigned long *step, /* ... adjusted to represent reality */
438 unsigned long *ds_cnt, /* number of data sources in file */
439 rrd_value_t **data) /* two dimensional array containing the data */
441 int i,reduce_factor = ceil((double)(*step) / (double)cur_step);
442 unsigned long col,dst_row,row_cnt,start_offset,end_offset,skiprows=0;
443 rrd_value_t *srcptr,*dstptr;
445 (*step) = cur_step*reduce_factor; /* set new step size for reduced data */
448 row_cnt = ((*end)-(*start))/cur_step;
454 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
455 row_cnt,reduce_factor,*start,*end,cur_step);
456 for (col=0;col<row_cnt;col++) {
457 printf("time %10lu: ",*start+(col+1)*cur_step);
458 for (i=0;i<*ds_cnt;i++)
459 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
464 /* We have to combine [reduce_factor] rows of the source
465 ** into one row for the destination. Doing this we also
466 ** need to take care to combine the correct rows. First
467 ** alter the start and end time so that they are multiples
468 ** of the new step time. We cannot reduce the amount of
469 ** time so we have to move the end towards the future and
470 ** the start towards the past.
472 end_offset = (*end) % (*step);
473 start_offset = (*start) % (*step);
475 /* If there is a start offset (which cannot be more than
476 ** one destination row), skip the appropriate number of
477 ** source rows and one destination row. The appropriate
478 ** number is what we do know (start_offset/cur_step) of
479 ** the new interval (*step/cur_step aka reduce_factor).
482 printf("start_offset: %lu end_offset: %lu\n",start_offset,end_offset);
483 printf("row_cnt before: %lu\n",row_cnt);
486 (*start) = (*start)-start_offset;
487 skiprows=reduce_factor-start_offset/cur_step;
488 srcptr+=skiprows* *ds_cnt;
489 for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
493 printf("row_cnt between: %lu\n",row_cnt);
496 /* At the end we have some rows that are not going to be
497 ** used, the amount is end_offset/cur_step
500 (*end) = (*end)-end_offset+(*step);
501 skiprows = end_offset/cur_step;
505 printf("row_cnt after: %lu\n",row_cnt);
508 /* Sanity check: row_cnt should be multiple of reduce_factor */
509 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
511 if (row_cnt%reduce_factor) {
512 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
513 row_cnt,reduce_factor);
514 printf("BUG in reduce_data()\n");
518 /* Now combine reduce_factor intervals at a time
519 ** into one interval for the destination.
522 for (dst_row=0;row_cnt>=reduce_factor;dst_row++) {
523 for (col=0;col<(*ds_cnt);col++) {
524 rrd_value_t newval=DNAN;
525 unsigned long validval=0;
527 for (i=0;i<reduce_factor;i++) {
528 if (isnan(srcptr[i*(*ds_cnt)+col])) {
532 if (isnan(newval)) newval = srcptr[i*(*ds_cnt)+col];
540 newval += srcptr[i*(*ds_cnt)+col];
543 newval = min (newval,srcptr[i*(*ds_cnt)+col]);
546 /* an interval contains a failure if any subintervals contained a failure */
548 newval = max (newval,srcptr[i*(*ds_cnt)+col]);
551 newval = srcptr[i*(*ds_cnt)+col];
556 if (validval == 0){newval = DNAN;} else{
574 srcptr+=(*ds_cnt)*reduce_factor;
575 row_cnt-=reduce_factor;
577 /* If we had to alter the endtime, we didn't have enough
578 ** source rows to fill the last row. Fill it with NaN.
580 if (end_offset) for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
582 row_cnt = ((*end)-(*start))/ *step;
584 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
585 row_cnt,*start,*end,*step);
586 for (col=0;col<row_cnt;col++) {
587 printf("time %10lu: ",*start+(col+1)*(*step));
588 for (i=0;i<*ds_cnt;i++)
589 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
596 /* get the data required for the graphs from the
600 data_fetch( image_desc_t *im )
604 /* pull the data from the log files ... */
605 for (i=0;i<im->gdes_c;i++){
606 /* only GF_DEF elements fetch data */
607 if (im->gdes[i].gf != GF_DEF)
611 /* do we have it already ?*/
612 for (ii=0;ii<i;ii++){
613 if (im->gdes[ii].gf != GF_DEF)
615 if((strcmp(im->gdes[i].rrd,im->gdes[ii].rrd) == 0)
616 && (im->gdes[i].cf == im->gdes[ii].cf)){
617 /* OK the data it is here already ...
618 * we just copy the header portion */
619 im->gdes[i].start = im->gdes[ii].start;
620 im->gdes[i].end = im->gdes[ii].end;
621 im->gdes[i].step = im->gdes[ii].step;
622 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
623 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
624 im->gdes[i].data = im->gdes[ii].data;
625 im->gdes[i].data_first = 0;
632 unsigned long ft_step = im->gdes[i].step ;
634 if((rrd_fetch_fn(im->gdes[i].rrd,
640 &im->gdes[i].ds_namv,
641 &im->gdes[i].data)) == -1){
644 im->gdes[i].data_first = 1;
646 if (ft_step < im->gdes[i].step) {
647 reduce_data(im->gdes[i].cf,
655 im->gdes[i].step = ft_step;
659 /* lets see if the required data source is realy there */
660 for(ii=0;ii<im->gdes[i].ds_cnt;ii++){
661 if(strcmp(im->gdes[i].ds_namv[ii],im->gdes[i].ds_nam) == 0){
664 if (im->gdes[i].ds== -1){
665 rrd_set_error("No DS called '%s' in '%s'",
666 im->gdes[i].ds_nam,im->gdes[i].rrd);
674 /* evaluate the expressions in the CDEF functions */
676 /*************************************************************
678 *************************************************************/
681 find_var_wrapper(void *arg1, char *key)
683 return find_var((image_desc_t *) arg1, key);
686 /* find gdes containing var*/
688 find_var(image_desc_t *im, char *key){
690 for(ii=0;ii<im->gdes_c-1;ii++){
691 if((im->gdes[ii].gf == GF_DEF
692 || im->gdes[ii].gf == GF_VDEF
693 || im->gdes[ii].gf == GF_CDEF)
694 && (strcmp(im->gdes[ii].vname,key) == 0)){
701 /* find the largest common denominator for all the numbers
702 in the 0 terminated num array */
707 for (i=0;num[i+1]!=0;i++){
709 rest=num[i] % num[i+1];
710 num[i]=num[i+1]; num[i+1]=rest;
714 /* return i==0?num[i]:num[i-1]; */
718 /* run the rpn calculator on all the VDEF and CDEF arguments */
720 data_calc( image_desc_t *im){
724 long *steparray, rpi;
729 rpnstack_init(&rpnstack);
731 for (gdi=0;gdi<im->gdes_c;gdi++){
732 /* Look for GF_VDEF and GF_CDEF in the same loop,
733 * so CDEFs can use VDEFs and vice versa
735 switch (im->gdes[gdi].gf) {
737 /* A VDEF has no DS. This also signals other parts
738 * of rrdtool that this is a VDEF value, not a CDEF.
740 im->gdes[gdi].ds_cnt = 0;
741 if (vdef_calc(im,gdi)) {
742 rrd_set_error("Error processing VDEF '%s'"
745 rpnstack_free(&rpnstack);
750 im->gdes[gdi].ds_cnt = 1;
751 im->gdes[gdi].ds = 0;
752 im->gdes[gdi].data_first = 1;
753 im->gdes[gdi].start = 0;
754 im->gdes[gdi].end = 0;
759 /* Find the variables in the expression.
760 * - VDEF variables are substituted by their values
761 * and the opcode is changed into OP_NUMBER.
762 * - CDEF variables are analized for their step size,
763 * the lowest common denominator of all the step
764 * sizes of the data sources involved is calculated
765 * and the resulting number is the step size for the
766 * resulting data source.
768 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
769 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE){
770 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
771 if (im->gdes[ptr].ds_cnt == 0) {
773 printf("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
775 im->gdes[ptr].vname);
776 printf("DEBUG: value from vdef is %f\n",im->gdes[ptr].vf.val);
778 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
779 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
781 if ((steparray = rrd_realloc(steparray, (++stepcnt+1)*sizeof(*steparray)))==NULL){
782 rrd_set_error("realloc steparray");
783 rpnstack_free(&rpnstack);
787 steparray[stepcnt-1] = im->gdes[ptr].step;
789 /* adjust start and end of cdef (gdi) so
790 * that it runs from the latest start point
791 * to the earliest endpoint of any of the
792 * rras involved (ptr)
794 if(im->gdes[gdi].start < im->gdes[ptr].start)
795 im->gdes[gdi].start = im->gdes[ptr].start;
797 if(im->gdes[gdi].end == 0 ||
798 im->gdes[gdi].end > im->gdes[ptr].end)
799 im->gdes[gdi].end = im->gdes[ptr].end;
801 /* store pointer to the first element of
802 * the rra providing data for variable,
803 * further save step size and data source
806 im->gdes[gdi].rpnp[rpi].data = im->gdes[ptr].data;
807 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
808 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
810 /* backoff the *.data ptr; this is done so
811 * rpncalc() function doesn't have to treat
812 * the first case differently
814 } /* if ds_cnt != 0 */
815 } /* if OP_VARIABLE */
816 } /* loop through all rpi */
818 if(steparray == NULL){
819 rrd_set_error("rpn expressions without DEF"
820 " or CDEF variables are not supported");
821 rpnstack_free(&rpnstack);
824 steparray[stepcnt]=0;
825 /* Now find the resulting step. All steps in all
826 * used RRAs have to be visited
828 im->gdes[gdi].step = lcd(steparray);
830 if((im->gdes[gdi].data = malloc((
831 (im->gdes[gdi].end-im->gdes[gdi].start)
832 / im->gdes[gdi].step)
833 * sizeof(double)))==NULL){
834 rrd_set_error("malloc im->gdes[gdi].data");
835 rpnstack_free(&rpnstack);
839 /* Step through the new cdef results array and
840 * calculate the values
842 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
843 now<=im->gdes[gdi].end;
844 now += im->gdes[gdi].step)
846 rpnp_t *rpnp = im -> gdes[gdi].rpnp;
848 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
849 * in this case we are advancing by timesteps;
850 * we use the fact that time_t is a synonym for long
852 if (rpn_calc(rpnp,&rpnstack,(long) now,
853 im->gdes[gdi].data,++dataidx) == -1) {
854 /* rpn_calc sets the error string */
855 rpnstack_free(&rpnstack);
858 } /* enumerate over time steps within a CDEF */
863 } /* enumerate over CDEFs */
864 rpnstack_free(&rpnstack);
868 /* massage data so, that we get one value for each x coordinate in the graph */
870 data_proc( image_desc_t *im ){
872 double pixstep = (double)(im->end-im->start)
873 /(double)im->xsize; /* how much time
874 passes in one pixel */
876 double minval=DNAN,maxval=DNAN;
878 unsigned long gr_time;
880 /* memory for the processed data */
881 for(i=0;i<im->gdes_c;i++){
882 if((im->gdes[i].gf==GF_LINE) ||
883 (im->gdes[i].gf==GF_AREA) ||
884 (im->gdes[i].gf==GF_TICK) ||
885 (im->gdes[i].gf==GF_STACK)){
886 if((im->gdes[i].p_data = malloc((im->xsize +1)
887 * sizeof(rrd_value_t)))==NULL){
888 rrd_set_error("malloc data_proc");
894 for(i=0;i<im->xsize;i++){
896 gr_time = im->start+pixstep*i; /* time of the
900 for(ii=0;ii<im->gdes_c;ii++){
902 switch(im->gdes[ii].gf){
908 vidx = im->gdes[ii].vidx;
912 ((unsigned long)floor(
913 (double)(gr_time-im->gdes[vidx].start) / im->gdes[vidx].step
915 ) *im->gdes[vidx].ds_cnt
918 if (! isnan(value)) {
920 im->gdes[ii].p_data[i] = paintval;
921 /* GF_TICK: the data values are not relevant for min and max */
922 if (finite(paintval) && im->gdes[ii].gf != GF_TICK ){
923 if (isnan(minval) || paintval < minval)
925 if (isnan(maxval) || paintval > maxval)
929 im->gdes[ii].p_data[i] = DNAN;
945 /* if min or max have not been asigned a value this is because
946 there was no data in the graph ... this is not good ...
947 lets set these to dummy values then ... */
949 if (isnan(minval)) minval = 0.0;
950 if (isnan(maxval)) maxval = 1.0;
952 /* adjust min and max values */
953 if (isnan(im->minval)
954 || ((!im->logarithmic && !im->rigid) /* don't adjust low-end with log scale */
955 && im->minval > minval))
957 if (isnan(im->maxval)
959 && im->maxval < maxval)){
961 im->maxval = maxval * 1.1;
965 /* make sure min and max are not equal */
966 if (im->minval == im->maxval) {
968 if (! im->logarithmic) {
972 /* make sure min and max are not both zero */
973 if (im->maxval == 0.0) {
983 /* identify the point where the first gridline, label ... gets placed */
987 time_t start, /* what is the initial time */
988 enum tmt_en baseint, /* what is the basic interval */
989 long basestep /* how many if these do we jump a time */
993 tm = *localtime(&start);
996 tm.tm_sec -= tm.tm_sec % basestep; break;
999 tm.tm_min -= tm.tm_min % basestep;
1004 tm.tm_hour -= tm.tm_hour % basestep; break;
1006 /* we do NOT look at the basestep for this ... */
1009 tm.tm_hour = 0; break;
1011 /* we do NOT look at the basestep for this ... */
1015 tm.tm_mday -= tm.tm_wday -1; /* -1 because we want the monday */
1016 if (tm.tm_wday==0) tm.tm_mday -= 7; /* we want the *previous* monday */
1023 tm.tm_mon -= tm.tm_mon % basestep; break;
1031 tm.tm_year -= (tm.tm_year+1900) % basestep;
1036 /* identify the point where the next gridline, label ... gets placed */
1039 time_t current, /* what is the initial time */
1040 enum tmt_en baseint, /* what is the basic interval */
1041 long basestep /* how many if these do we jump a time */
1046 tm = *localtime(¤t);
1050 tm.tm_sec += basestep; break;
1052 tm.tm_min += basestep; break;
1054 tm.tm_hour += basestep; break;
1056 tm.tm_mday += basestep; break;
1058 tm.tm_mday += 7*basestep; break;
1060 tm.tm_mon += basestep; break;
1062 tm.tm_year += basestep;
1064 madetime = mktime(&tm);
1065 } while (madetime == -1); /* this is necessary to skip impssible times
1066 like the daylight saving time skips */
1072 /* calculate values required for PRINT and GPRINT functions */
1075 print_calc(image_desc_t *im, char ***prdata)
1077 long i,ii,validsteps;
1080 int graphelement = 0;
1083 double magfact = -1;
1087 if (im->imginfo) prlines++;
1088 for(i=0;i<im->gdes_c;i++){
1089 switch(im->gdes[i].gf){
1092 if(((*prdata) = rrd_realloc((*prdata),prlines*sizeof(char *)))==NULL){
1093 rrd_set_error("realloc prdata");
1097 /* PRINT and GPRINT can now print VDEF generated values.
1098 * There's no need to do any calculations on them as these
1099 * calculations were already made.
1101 vidx = im->gdes[i].vidx;
1102 if (im->gdes[vidx].gf==GF_VDEF) { /* simply use vals */
1103 printval = im->gdes[vidx].vf.val;
1104 printtime = im->gdes[vidx].vf.when;
1105 } else { /* need to calculate max,min,avg etcetera */
1106 max_ii =((im->gdes[vidx].end
1107 - im->gdes[vidx].start)
1108 / im->gdes[vidx].step
1109 * im->gdes[vidx].ds_cnt);
1112 for( ii=im->gdes[vidx].ds;
1114 ii+=im->gdes[vidx].ds_cnt){
1115 if (! finite(im->gdes[vidx].data[ii]))
1117 if (isnan(printval)){
1118 printval = im->gdes[vidx].data[ii];
1123 switch (im->gdes[i].cf){
1126 case CF_DEVSEASONAL:
1130 printval += im->gdes[vidx].data[ii];
1133 printval = min( printval, im->gdes[vidx].data[ii]);
1137 printval = max( printval, im->gdes[vidx].data[ii]);
1140 printval = im->gdes[vidx].data[ii];
1143 if (im->gdes[i].cf==CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1144 if (validsteps > 1) {
1145 printval = (printval / validsteps);
1148 } /* prepare printval */
1150 if (!strcmp(im->gdes[i].format,"%c")) { /* VDEF time print */
1151 if (im->gdes[i].gf == GF_PRINT){
1152 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1153 sprintf((*prdata)[prlines-2],"%s (%lu)",
1154 ctime(&printtime),printtime);
1155 (*prdata)[prlines-1] = NULL;
1157 sprintf(im->gdes[i].legend,"%s (%lu)",
1158 ctime(&printtime),printtime);
1162 if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) {
1163 /* Magfact is set to -1 upon entry to print_calc. If it
1164 * is still less than 0, then we need to run auto_scale.
1165 * Otherwise, put the value into the correct units. If
1166 * the value is 0, then do not set the symbol or magnification
1167 * so next the calculation will be performed again. */
1168 if (magfact < 0.0) {
1169 auto_scale(im,&printval,&si_symb,&magfact);
1170 if (printval == 0.0)
1173 printval /= magfact;
1175 *(++percent_s) = 's';
1176 } else if (strstr(im->gdes[i].format,"%s") != NULL) {
1177 auto_scale(im,&printval,&si_symb,&magfact);
1180 if (im->gdes[i].gf == GF_PRINT){
1181 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1182 if (bad_format(im->gdes[i].format)) {
1183 rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1186 #ifdef HAVE_SNPRINTF
1187 snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
1189 sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
1191 (*prdata)[prlines-1] = NULL;
1195 if (bad_format(im->gdes[i].format)) {
1196 rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1199 #ifdef HAVE_SNPRINTF
1200 snprintf(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
1202 sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
1223 return graphelement;
1227 /* place legends with color spots */
1229 leg_place(image_desc_t *im)
1232 int interleg = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1233 int box =im->text_prop[TEXT_PROP_LEGEND].size*1.5;
1234 int border = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1235 int fill=0, fill_last;
1237 int leg_x = border, leg_y = im->ygif;
1241 char prt_fctn; /*special printfunctions */
1244 if( !(im->extra_flags & NOLEGEND) ) {
1245 if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1246 rrd_set_error("malloc for legspace");
1250 for(i=0;i<im->gdes_c;i++){
1253 leg_cc = strlen(im->gdes[i].legend);
1255 /* is there a controle code ant the end of the legend string ? */
1256 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\') {
1257 prt_fctn = im->gdes[i].legend[leg_cc-1];
1259 im->gdes[i].legend[leg_cc] = '\0';
1263 /* remove exess space */
1264 while (prt_fctn=='g' &&
1266 im->gdes[i].legend[leg_cc-1]==' '){
1268 im->gdes[i].legend[leg_cc]='\0';
1271 legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1274 /* no interleg space if string ends in \g */
1275 fill += legspace[i];
1277 if (im->gdes[i].gf != GF_GPRINT &&
1278 im->gdes[i].gf != GF_COMMENT) {
1281 fill += gfx_get_text_width(fill+border,im->text_prop[TEXT_PROP_LEGEND].font,
1282 im->text_prop[TEXT_PROP_LEGEND].size,
1284 im->gdes[i].legend);
1289 /* who said there was a special tag ... ?*/
1290 if (prt_fctn=='g') {
1293 if (prt_fctn == '\0') {
1294 if (i == im->gdes_c -1 ) prt_fctn ='l';
1296 /* is it time to place the legends ? */
1297 if (fill > im->xgif - 2*border){
1312 if (prt_fctn != '\0'){
1314 if (leg_c >= 2 && prt_fctn == 'j') {
1315 glue = (im->xgif - fill - 2* border) / (leg_c-1);
1319 if (prt_fctn =='c') leg_x = (im->xgif - fill) / 2.0;
1320 if (prt_fctn =='r') leg_x = im->xgif - fill - border;
1322 for(ii=mark;ii<=i;ii++){
1323 if(im->gdes[ii].legend[0]=='\0')
1325 im->gdes[ii].leg_x = leg_x;
1326 im->gdes[ii].leg_y = leg_y;
1328 gfx_get_text_width(leg_x,im->text_prop[TEXT_PROP_LEGEND].font,
1329 im->text_prop[TEXT_PROP_LEGEND].size,
1331 im->gdes[ii].legend)
1334 if (im->gdes[ii].gf != GF_GPRINT &&
1335 im->gdes[ii].gf != GF_COMMENT)
1338 leg_y = leg_y + im->text_prop[TEXT_PROP_LEGEND].size*1.2;
1339 if (prt_fctn == 's') leg_y -= im->text_prop[TEXT_PROP_LEGEND].size*1.2;
1351 /* create a grid on the graph. it determines what to do
1352 from the values of xsize, start and end */
1354 /* the xaxis labels are determined from the number of seconds per pixel
1355 in the requested graph */
1360 horizontal_grid(gfx_canvas_t *canvas, image_desc_t *im)
1368 char graph_label[100];
1370 int labfact,gridind;
1371 int decimals, fractionals;
1376 range = im->maxval - im->minval;
1377 scaledrange = range / im->magfact;
1379 /* does the scale of this graph make it impossible to put lines
1380 on it? If so, give up. */
1381 if (isnan(scaledrange)) {
1385 /* find grid spaceing */
1387 if(isnan(im->ygridstep)){
1388 if(im->extra_flags & ALTYGRID) {
1389 /* find the value with max number of digits. Get number of digits */
1390 decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))));
1391 if(decimals <= 0) /* everything is small. make place for zero */
1394 fractionals = floor(log10(range));
1395 if(fractionals < 0) /* small amplitude. */
1396 sprintf(labfmt, "%%%d.%df", decimals - fractionals + 1, -fractionals + 1);
1398 sprintf(labfmt, "%%%d.1f", decimals + 1);
1399 gridstep = pow((double)10, (double)fractionals);
1400 if(gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1402 /* should have at least 5 lines but no more then 15 */
1403 if(range/gridstep < 5)
1405 if(range/gridstep > 15)
1407 if(range/gridstep > 5) {
1409 if(range/gridstep > 8)
1418 for(i=0;ylab[i].grid > 0;i++){
1419 pixel = im->ysize / (scaledrange / ylab[i].grid);
1420 if (gridind == -1 && pixel > 5) {
1427 if (pixel * ylab[gridind].lfac[i] >= 2 * im->text_prop[TEXT_PROP_AXIS].size) {
1428 labfact = ylab[gridind].lfac[i];
1433 gridstep = ylab[gridind].grid * im->magfact;
1436 gridstep = im->ygridstep;
1437 labfact = im->ylabfact;
1441 x1=im->xorigin+im->xsize;
1443 sgrid = (int)( im->minval / gridstep - 1);
1444 egrid = (int)( im->maxval / gridstep + 1);
1445 scaledstep = gridstep/im->magfact;
1446 for (i = sgrid; i <= egrid; i++){
1447 y0=ytr(im,gridstep*i);
1448 if ( y0 >= im->yorigin-im->ysize
1449 && y0 <= im->yorigin){
1450 if(i % labfact == 0){
1451 if (i==0 || im->symbol == ' ') {
1453 if(im->extra_flags & ALTYGRID) {
1454 sprintf(graph_label,labfmt,scaledstep*i);
1457 sprintf(graph_label,"%4.1f",scaledstep*i);
1460 sprintf(graph_label,"%4.0f",scaledstep*i);
1464 sprintf(graph_label,"%4.1f %c",scaledstep*i, im->symbol);
1466 sprintf(graph_label,"%4.0f %c",scaledstep*i, im->symbol);
1470 gfx_new_text ( canvas,
1471 x0-im->text_prop[TEXT_PROP_AXIS].size/1.5, y0,
1472 im->graph_col[GRC_FONT],
1473 im->text_prop[TEXT_PROP_AXIS].font,
1474 im->text_prop[TEXT_PROP_AXIS].size,
1475 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1477 gfx_new_line ( canvas,
1480 MGRIDWIDTH, im->graph_col[GRC_MGRID] );
1483 gfx_new_line ( canvas,
1486 GRIDWIDTH, im->graph_col[GRC_GRID] );
1494 /* logaritmic horizontal grid */
1496 horizontal_log_grid(gfx_canvas_t *canvas, image_desc_t *im)
1500 int minoridx=0, majoridx=0;
1501 char graph_label[100];
1503 double value, pixperstep, minstep;
1505 /* find grid spaceing */
1506 pixpex= (double)im->ysize / (log10(im->maxval) - log10(im->minval));
1508 if (isnan(pixpex)) {
1512 for(i=0;yloglab[i][0] > 0;i++){
1513 minstep = log10(yloglab[i][0]);
1514 for(ii=1;yloglab[i][ii+1] > 0;ii++){
1515 if(yloglab[i][ii+2]==0){
1516 minstep = log10(yloglab[i][ii+1])-log10(yloglab[i][ii]);
1520 pixperstep = pixpex * minstep;
1521 if(pixperstep > 5){minoridx = i;}
1522 if(pixperstep > 2 * im->text_prop[TEXT_PROP_LEGEND].size){majoridx = i;}
1526 x1=im->xorigin+im->xsize;
1527 /* paint minor grid */
1528 for (value = pow((double)10, log10(im->minval)
1529 - fmod(log10(im->minval),log10(yloglab[minoridx][0])));
1530 value <= im->maxval;
1531 value *= yloglab[minoridx][0]){
1532 if (value < im->minval) continue;
1534 while(yloglab[minoridx][++i] > 0){
1535 y0 = ytr(im,value * yloglab[minoridx][i]);
1536 if (y0 <= im->yorigin - im->ysize) break;
1537 gfx_new_line ( canvas,
1540 GRIDWIDTH, im->graph_col[GRC_GRID] );
1544 /* paint major grid and labels*/
1545 for (value = pow((double)10, log10(im->minval)
1546 - fmod(log10(im->minval),log10(yloglab[majoridx][0])));
1547 value <= im->maxval;
1548 value *= yloglab[majoridx][0]){
1549 if (value < im->minval) continue;
1551 while(yloglab[majoridx][++i] > 0){
1552 y0 = ytr(im,value * yloglab[majoridx][i]);
1553 if (y0 <= im->yorigin - im->ysize) break;
1554 gfx_new_line ( canvas,
1557 MGRIDWIDTH, im->graph_col[GRC_MGRID] );
1559 sprintf(graph_label,"%3.0e",value * yloglab[majoridx][i]);
1560 gfx_new_text ( canvas,
1561 x0-im->text_prop[TEXT_PROP_AXIS].size/1.5, y0,
1562 im->graph_col[GRC_FONT],
1563 im->text_prop[TEXT_PROP_AXIS].font,
1564 im->text_prop[TEXT_PROP_AXIS].size,
1565 im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1575 gfx_canvas_t *canvas,
1578 int xlab_sel; /* which sort of label and grid ? */
1581 char graph_label[100];
1582 double x0,y0,y1; /* points for filled graph and more*/
1585 /* the type of time grid is determined by finding
1586 the number of seconds per pixel in the graph */
1589 if(im->xlab_user.minsec == -1){
1590 factor=(im->end - im->start)/im->xsize;
1592 while ( xlab[xlab_sel+1].minsec != -1
1593 && xlab[xlab_sel+1].minsec <= factor){ xlab_sel++; }
1594 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
1595 im->xlab_user.gridst = xlab[xlab_sel].gridst;
1596 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
1597 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
1598 im->xlab_user.labtm = xlab[xlab_sel].labtm;
1599 im->xlab_user.labst = xlab[xlab_sel].labst;
1600 im->xlab_user.precis = xlab[xlab_sel].precis;
1601 im->xlab_user.stst = xlab[xlab_sel].stst;
1604 /* y coords are the same for every line ... */
1606 y1 = im->yorigin-im->ysize;
1609 /* paint the minor grid */
1610 for(ti = find_first_time(im->start,
1611 im->xlab_user.gridtm,
1612 im->xlab_user.gridst);
1614 ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
1616 /* are we inside the graph ? */
1617 if (ti < im->start || ti > im->end) continue;
1619 gfx_new_line(canvas,x0,y0+1, x0,y1-1,GRIDWIDTH, im->graph_col[GRC_GRID]);
1623 /* paint the major grid */
1624 for(ti = find_first_time(im->start,
1625 im->xlab_user.mgridtm,
1626 im->xlab_user.mgridst);
1628 ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
1630 /* are we inside the graph ? */
1631 if (ti < im->start || ti > im->end) continue;
1633 gfx_new_line(canvas,x0,y0+2, x0,y1-2,MGRIDWIDTH, im->graph_col[GRC_MGRID]);
1636 /* paint the labels below the graph */
1637 for(ti = find_first_time(im->start,
1638 im->xlab_user.labtm,
1639 im->xlab_user.labst);
1641 ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
1643 tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
1646 strftime(graph_label,99,im->xlab_user.stst,localtime(&tilab));
1648 # error "your libc has no strftime I guess we'll abort the exercise here."
1650 gfx_new_text ( canvas,
1651 xtr(im,tilab), y0+im->text_prop[TEXT_PROP_AXIS].size/1.5,
1652 im->graph_col[GRC_FONT],
1653 im->text_prop[TEXT_PROP_AXIS].font,
1654 im->text_prop[TEXT_PROP_AXIS].size,
1655 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP,
1666 gfx_canvas_t *canvas
1669 /* draw x and y axis */
1670 gfx_new_line ( canvas, im->xorigin+im->xsize,im->yorigin,
1671 im->xorigin+im->xsize,im->yorigin-im->ysize,
1672 GRIDWIDTH, im->graph_col[GRC_GRID]);
1674 gfx_new_line ( canvas, im->xorigin,im->yorigin-im->ysize,
1675 im->xorigin+im->xsize,im->yorigin-im->ysize,
1676 GRIDWIDTH, im->graph_col[GRC_GRID]);
1678 gfx_new_line ( canvas, im->xorigin-4,im->yorigin,
1679 im->xorigin+im->xsize+4,im->yorigin,
1680 MGRIDWIDTH, im->graph_col[GRC_GRID]);
1682 gfx_new_line ( canvas, im->xorigin,im->yorigin+4,
1683 im->xorigin,im->yorigin-im->ysize-4,
1684 MGRIDWIDTH, im->graph_col[GRC_GRID]);
1687 /* arrow for X axis direction */
1688 gfx_new_area ( canvas,
1689 im->xorigin+im->xsize+4, im->yorigin-3,
1690 im->xorigin+im->xsize+4, im->yorigin+3,
1691 im->xorigin+im->xsize+9, im->yorigin,
1692 im->graph_col[GRC_ARROW]);
1701 gfx_canvas_t *canvas
1708 double x0,x1,x2,x3,y0,y1,y2,y3; /* points for filled graph and more*/
1712 /* draw 3d border */
1713 node = gfx_new_area (canvas, 0,im->ygif, 0,0, im->xgif, 0,im->graph_col[GRC_SHADEA]);
1714 gfx_add_point( node , im->xgif - 2, 2 );
1715 gfx_add_point( node , 2,2 );
1716 gfx_add_point( node , 2,im->ygif-2 );
1717 gfx_add_point( node , 0,im->ygif );
1719 node = gfx_new_area (canvas, 0,im->ygif, im->xgif,im->ygif, im->xgif,0,im->graph_col[GRC_SHADEB]);
1720 gfx_add_point( node , im->xgif - 2, 2 );
1721 gfx_add_point( node , im->xgif-2,im->ygif-2 );
1722 gfx_add_point( node , 2,im->ygif-2 );
1723 gfx_add_point( node , 0,im->ygif );
1726 if (im->draw_x_grid == 1 )
1727 vertical_grid(canvas, im);
1729 if (im->draw_y_grid == 1){
1730 if(im->logarithmic){
1731 res = horizontal_log_grid(canvas,im);
1733 res = horizontal_grid(canvas,im);
1736 /* dont draw horizontal grid if there is no min and max val */
1738 char *nodata = "No Data found";
1739 gfx_new_text(canvas,im->xgif/2, (2*im->yorigin-im->ysize) / 2,
1740 im->graph_col[GRC_FONT],
1741 im->text_prop[TEXT_PROP_AXIS].font,
1742 im->text_prop[TEXT_PROP_AXIS].size,
1743 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
1748 /* yaxis description */
1749 gfx_new_text( canvas,
1750 7, (im->yorigin - im->ysize/2),
1751 im->graph_col[GRC_FONT],
1752 im->text_prop[TEXT_PROP_AXIS].font,
1753 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 270.0,
1754 GFX_H_CENTER, GFX_V_CENTER,
1758 gfx_new_text( canvas,
1759 im->xgif/2, im->text_prop[TEXT_PROP_TITLE].size*1.5,
1760 im->graph_col[GRC_FONT],
1761 im->text_prop[TEXT_PROP_TITLE].font,
1762 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
1763 GFX_H_CENTER, GFX_V_CENTER,
1767 if( !(im->extra_flags & NOLEGEND) ) {
1768 for(i=0;i<im->gdes_c;i++){
1769 if(im->gdes[i].legend[0] =='\0')
1772 if(im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT){
1773 x0 = im->gdes[i].leg_x;
1774 y0 = im->gdes[i].leg_y+1.0;
1781 node = gfx_new_area(canvas, x0,y0,x1,y1,x2,y2 ,im->gdes[i].col);
1782 gfx_add_point ( node, x3, y3 );
1783 gfx_add_point ( node, x0, y0 );
1784 node = gfx_new_line(canvas, x0,y0,x1,y1 ,GRIDWIDTH, im->graph_col[GRC_FRAME]);
1785 gfx_add_point ( node, x2, y2 );
1786 gfx_add_point ( node, x3, y3 );
1787 gfx_add_point ( node, x0, y0 );
1789 gfx_new_text ( canvas, x0+boxH+6, (y0+y2) / 2.0,
1790 im->graph_col[GRC_FONT],
1791 im->text_prop[TEXT_PROP_AXIS].font,
1792 im->text_prop[TEXT_PROP_AXIS].size,
1793 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
1794 im->gdes[i].legend );
1797 x0 = im->gdes[i].leg_x;
1798 y0 = im->gdes[i].leg_y;
1800 gfx_new_text ( canvas, x0, (y0+y2) / 2.0,
1801 im->graph_col[GRC_FONT],
1802 im->text_prop[TEXT_PROP_AXIS].font,
1803 im->text_prop[TEXT_PROP_AXIS].size,
1804 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
1805 im->gdes[i].legend );
1813 /*****************************************************
1814 * lazy check make sure we rely need to create this graph
1815 *****************************************************/
1817 int lazy_check(image_desc_t *im){
1820 struct stat gifstat;
1822 if (im->lazy == 0) return 0; /* no lazy option */
1823 if (stat(im->graphfile,&gifstat) != 0)
1824 return 0; /* can't stat */
1825 /* one pixel in the existing graph is more then what we would
1827 if (time(NULL) - gifstat.st_mtime >
1828 (im->end - im->start) / im->xsize)
1830 if ((fd = fopen(im->graphfile,"rb")) == NULL)
1831 return 0; /* the file does not exist */
1832 switch (im->imgformat) {
1834 size = GifSize(fd,&(im->xgif),&(im->ygif));
1837 size = PngSize(fd,&(im->xgif),&(im->ygif));
1845 /* draw that picture thing ... */
1847 graph_paint(image_desc_t *im, char ***calcpr)
1850 int lazy = lazy_check(im);
1852 gfx_canvas_t *canvas;
1855 double areazero = 0.0;
1856 enum gf_en stack_gf = GF_PRINT;
1857 graph_desc_t *lastgdes = NULL;
1859 /* if we are lazy and there is nothing to PRINT ... quit now */
1860 if (lazy && im->prt_c==0) return 0;
1862 /* pull the data from the rrd files ... */
1864 if(data_fetch(im)==-1)
1867 /* evaluate VDEF and CDEF operations ... */
1868 if(data_calc(im)==-1)
1871 /* calculate and PRINT and GPRINT definitions. We have to do it at
1872 * this point because it will affect the length of the legends
1873 * if there are no graph elements we stop here ...
1874 * if we are lazy, try to quit ...
1876 i=print_calc(im,calcpr);
1878 if(i==0 || lazy) return 0;
1880 /* get actual drawing data and find min and max values*/
1881 if(data_proc(im)==-1)
1884 if(!im->logarithmic){si_unit(im);} /* identify si magnitude Kilo, Mega Giga ? */
1886 if(!im->rigid && ! im->logarithmic)
1887 expand_range(im); /* make sure the upper and lower limit are
1890 /* init xtr and ytr */
1891 /* determine the actual size of the gif to draw. The size given
1892 on the cmdline is the graph area. But we need more as we have
1893 draw labels and other things outside the graph area */
1896 im->xorigin = 10 + 9 * im->text_prop[TEXT_PROP_LEGEND].size;
1900 im->yorigin = 10 + im->ysize;
1904 if(im->title[0] != '\0')
1905 im->yorigin += im->text_prop[TEXT_PROP_TITLE].size*3+4;
1907 im->xgif=20+im->xsize + im->xorigin;
1908 im->ygif= im->yorigin+2* im->text_prop[TEXT_PROP_LEGEND].size;
1910 /* determine where to place the legends onto the graphics.
1911 and set im->ygif to match space requirements for text */
1912 if(leg_place(im)==-1)
1915 canvas=gfx_new_canvas();
1917 /* the actual graph is created by going through the individual
1918 graph elements and then drawing them */
1920 node=gfx_new_area ( canvas,
1924 im->graph_col[GRC_BACK]);
1926 gfx_add_point(node,0, im->ygif);
1928 node=gfx_new_area ( canvas,
1929 im->xorigin, im->yorigin,
1930 im->xorigin + im->xsize, im->yorigin,
1931 im->xorigin + im->xsize, im->yorigin-im->ysize,
1932 im->graph_col[GRC_CANVAS]);
1934 gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
1937 if (im->minval > 0.0)
1938 areazero = im->minval;
1939 if (im->maxval < 0.0)
1940 areazero = im->maxval;
1942 axis_paint(im,canvas);
1945 for(i=0;i<im->gdes_c;i++){
1946 switch(im->gdes[i].gf){
1957 for (ii = 0; ii < im->xsize; ii++)
1959 if (!isnan(im->gdes[i].p_data[ii]) &&
1960 im->gdes[i].p_data[ii] > 0.0)
1962 /* generate a tick */
1963 gfx_new_line(canvas, im -> xorigin + ii,
1964 im -> yorigin - (im -> gdes[i].yrule * im -> ysize),
1968 im -> gdes[i].col );
1974 stack_gf = im->gdes[i].gf;
1976 /* fix data points at oo and -oo */
1977 for(ii=0;ii<im->xsize;ii++){
1978 if (isinf(im->gdes[i].p_data[ii])){
1979 if (im->gdes[i].p_data[ii] > 0) {
1980 im->gdes[i].p_data[ii] = im->maxval ;
1982 im->gdes[i].p_data[ii] = im->minval ;
1988 if (im->gdes[i].col != 0x0){
1989 /* GF_LINE and friend */
1990 if(stack_gf == GF_LINE ){
1992 for(ii=1;ii<im->xsize;ii++){
1993 if ( ! isnan(im->gdes[i].p_data[ii-1])
1994 && ! isnan(im->gdes[i].p_data[ii])){
1996 node = gfx_new_line(canvas,
1997 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
1998 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
1999 im->gdes[i].linewidth,
2002 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2011 for(ii=1;ii<im->xsize;ii++){
2013 if ( ! isnan(im->gdes[i].p_data[ii-1])
2014 && ! isnan(im->gdes[i].p_data[ii])){
2017 if (im->gdes[i].gf == GF_STACK) {
2018 ybase = ytr(im,lastgdes->p_data[ii-1]);
2020 ybase = ytr(im,areazero);
2023 node = gfx_new_area(canvas,
2024 ii-1+im->xorigin,ybase,
2025 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2026 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2030 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2034 if ( node != NULL && (ii+1==im->xsize || isnan(im->gdes[i].p_data[ii]) )){
2035 /* GF_AREA STACK type*/
2036 if (im->gdes[i].gf == GF_STACK ) {
2038 for (iii=ii-1;iii>area_start;iii--){
2039 gfx_add_point(node,iii+im->xorigin,ytr(im,lastgdes->p_data[iii]));
2042 gfx_add_point(node,ii+im->xorigin,ytr(im,areazero));
2047 } /* else GF_LINE */
2048 } /* if color != 0x0 */
2049 /* make sure we do not run into trouble when stacking on NaN */
2050 for(ii=0;ii<im->xsize;ii++){
2051 if (isnan(im->gdes[i].p_data[ii])) {
2054 ybase = ytr(im,lastgdes->p_data[ii-1]);
2056 if (isnan(ybase) || !lastgdes ){
2057 ybase = ytr(im,areazero);
2059 im->gdes[i].p_data[ii] = ybase;
2062 lastgdes = &(im->gdes[i]);
2066 grid_paint(im,canvas);
2068 /* the RULES are the last thing to paint ... */
2069 for(i=0;i<im->gdes_c;i++){
2071 switch(im->gdes[i].gf){
2073 printf("DEBUG: HRULE at %f\n",im->gdes[i].yrule);
2074 if(isnan(im->gdes[i].yrule)) { /* fetch variable */
2075 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2077 if(im->gdes[i].yrule >= im->minval
2078 && im->gdes[i].yrule <= im->maxval)
2079 gfx_new_line(canvas,
2080 im->xorigin,ytr(im,im->gdes[i].yrule),
2081 im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2082 1.0,im->gdes[i].col);
2085 if(im->gdes[i].xrule == 0) { /* fetch variable */
2086 im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
2088 if(im->gdes[i].xrule >= im->start
2089 && im->gdes[i].xrule <= im->end)
2090 gfx_new_line(canvas,
2091 xtr(im,im->gdes[i].xrule),im->yorigin,
2092 xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2093 1.0,im->gdes[i].col);
2101 if (strcmp(im->graphfile,"-")==0) {
2103 /* Change translation mode for stdout to BINARY */
2104 _setmode( _fileno( stdout ), O_BINARY );
2108 if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2109 rrd_set_error("Opening '%s' for write: %s",im->graphfile,
2114 switch (im->imgformat) {
2118 gfx_render_png (canvas,im->xgif,im->ygif,im->zoom,0x0,fo);
2121 if (strcmp(im->graphfile,"-") != 0)
2124 gfx_destroy(canvas);
2129 /*****************************************************
2131 *****************************************************/
2134 gdes_alloc(image_desc_t *im){
2136 long def_step = (im->end-im->start)/im->xsize;
2138 if (im->step > def_step) /* step can be increassed ... no decreassed */
2139 def_step = im->step;
2143 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2144 * sizeof(graph_desc_t)))==NULL){
2145 rrd_set_error("realloc graph_descs");
2150 im->gdes[im->gdes_c-1].step=def_step;
2151 im->gdes[im->gdes_c-1].start=im->start;
2152 im->gdes[im->gdes_c-1].end=im->end;
2153 im->gdes[im->gdes_c-1].vname[0]='\0';
2154 im->gdes[im->gdes_c-1].data=NULL;
2155 im->gdes[im->gdes_c-1].ds_namv=NULL;
2156 im->gdes[im->gdes_c-1].data_first=0;
2157 im->gdes[im->gdes_c-1].p_data=NULL;
2158 im->gdes[im->gdes_c-1].rpnp=NULL;
2159 im->gdes[im->gdes_c-1].col = 0x0;
2160 im->gdes[im->gdes_c-1].legend[0]='\0';
2161 im->gdes[im->gdes_c-1].rrd[0]='\0';
2162 im->gdes[im->gdes_c-1].ds=-1;
2163 im->gdes[im->gdes_c-1].p_data=NULL;
2167 /* copies input untill the first unescaped colon is found
2168 or until input ends. backslashes have to be escaped as well */
2170 scan_for_col(char *input, int len, char *output)
2175 input[inp] != ':' &&
2178 if (input[inp] == '\\' &&
2179 input[inp+1] != '\0' &&
2180 (input[inp+1] == '\\' ||
2181 input[inp+1] == ':')){
2182 output[outp++] = input[++inp];
2185 output[outp++] = input[inp];
2188 output[outp] = '\0';
2192 /* Some surgery done on this function, it became ridiculously big.
2194 ** - initializing now in rrd_graph_init()
2195 ** - options parsing now in rrd_graph_options()
2196 ** - script parsing now in rrd_graph_script()
2199 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize)
2203 rrd_graph_init(&im);
2205 rrd_graph_options(argc,argv,&im);
2206 if (rrd_test_error()) return -1;
2208 if (strlen(argv[optind])>=MAXPATH) {
2209 rrd_set_error("filename (including path) too long");
2212 strncpy(im.graphfile,argv[optind],MAXPATH-1);
2213 im.graphfile[MAXPATH-1]='\0';
2215 rrd_graph_script(argc,argv,&im);
2216 if (rrd_test_error()) return -1;
2218 /* Everything is now read and the actual work can start */
2221 if (graph_paint(&im,prdata)==-1){
2226 /* The image is generated and needs to be output.
2227 ** Also, if needed, print a line with information about the image.
2235 /* maybe prdata is not allocated yet ... lets do it now */
2236 if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
2237 rrd_set_error("malloc imginfo");
2241 if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
2243 rrd_set_error("malloc imginfo");
2246 filename=im.graphfile+strlen(im.graphfile);
2247 while(filename > im.graphfile) {
2248 if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
2252 sprintf((*prdata)[0],im.imginfo,filename,(long)(im.zoom*im.xgif),(long)(im.zoom*im.ygif));
2259 rrd_graph_init(image_desc_t *im)
2263 im->xlab_user.minsec = -1;
2269 im->ylegend[0] = '\0';
2270 im->title[0] = '\0';
2274 im->unitsexponent= 9999;
2279 im->logarithmic = 0;
2280 im->ygridstep = DNAN;
2281 im->draw_x_grid = 1;
2282 im->draw_y_grid = 1;
2288 im->imgformat = IF_GIF; /* we default to GIF output */
2290 for(i=0;i<DIM(graph_col);i++)
2291 im->graph_col[i]=graph_col[i];
2293 for(i=0;i<DIM(text_prop);i++){
2294 im->text_prop[i].size = text_prop[i].size;
2295 im->text_prop[i].font = text_prop[i].font;
2300 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
2303 char *parsetime_error = NULL;
2304 char scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
2305 time_t start_tmp=0,end_tmp=0;
2307 struct time_value start_tv, end_tv;
2310 parsetime("end-24h", &start_tv);
2311 parsetime("now", &end_tv);
2314 static struct option long_options[] =
2316 {"start", required_argument, 0, 's'},
2317 {"end", required_argument, 0, 'e'},
2318 {"x-grid", required_argument, 0, 'x'},
2319 {"y-grid", required_argument, 0, 'y'},
2320 {"vertical-label",required_argument,0,'v'},
2321 {"width", required_argument, 0, 'w'},
2322 {"height", required_argument, 0, 'h'},
2323 {"interlaced", no_argument, 0, 'i'},
2324 {"upper-limit",required_argument, 0, 'u'},
2325 {"lower-limit",required_argument, 0, 'l'},
2326 {"rigid", no_argument, 0, 'r'},
2327 {"base", required_argument, 0, 'b'},
2328 {"logarithmic",no_argument, 0, 'o'},
2329 {"color", required_argument, 0, 'c'},
2330 {"font", required_argument, 0, 'n'},
2331 {"title", required_argument, 0, 't'},
2332 {"imginfo", required_argument, 0, 'f'},
2333 {"imgformat", required_argument, 0, 'a'},
2334 {"lazy", no_argument, 0, 'z'},
2335 {"zoom", required_argument, 0, 'm'},
2336 {"no-legend", no_argument, 0, 'g'},
2337 {"alt-y-grid", no_argument, 0, 257 },
2338 {"alt-autoscale", no_argument, 0, 258 },
2339 {"alt-autoscale-max", no_argument, 0, 259 },
2340 {"units-exponent",required_argument, 0, 260},
2341 {"step", required_argument, 0, 261},
2343 int option_index = 0;
2347 opt = getopt_long(argc, argv,
2348 "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:z:g",
2349 long_options, &option_index);
2356 im->extra_flags |= ALTYGRID;
2359 im->extra_flags |= ALTAUTOSCALE;
2362 im->extra_flags |= ALTAUTOSCALE_MAX;
2365 im->extra_flags |= NOLEGEND;
2368 im->unitsexponent = atoi(optarg);
2371 im->step = atoi(optarg);
2374 if ((parsetime_error = parsetime(optarg, &start_tv))) {
2375 rrd_set_error( "start time: %s", parsetime_error );
2380 if ((parsetime_error = parsetime(optarg, &end_tv))) {
2381 rrd_set_error( "end time: %s", parsetime_error );
2386 if(strcmp(optarg,"none") == 0){
2392 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
2394 &im->xlab_user.gridst,
2396 &im->xlab_user.mgridst,
2398 &im->xlab_user.labst,
2399 &im->xlab_user.precis,
2400 &stroff) == 7 && stroff != 0){
2401 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
2402 if((im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
2403 rrd_set_error("unknown keyword %s",scan_gtm);
2405 } else if ((im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
2406 rrd_set_error("unknown keyword %s",scan_mtm);
2408 } else if ((im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
2409 rrd_set_error("unknown keyword %s",scan_ltm);
2412 im->xlab_user.minsec = 1;
2413 im->xlab_user.stst = im->xlab_form;
2415 rrd_set_error("invalid x-grid format");
2421 if(strcmp(optarg,"none") == 0){
2429 &im->ylabfact) == 2) {
2430 if(im->ygridstep<=0){
2431 rrd_set_error("grid step must be > 0");
2433 } else if (im->ylabfact < 1){
2434 rrd_set_error("label factor must be > 0");
2438 rrd_set_error("invalid y-grid format");
2443 strncpy(im->ylegend,optarg,150);
2444 im->ylegend[150]='\0';
2447 im->maxval = atof(optarg);
2450 im->minval = atof(optarg);
2453 im->base = atol(optarg);
2454 if(im->base != 1024 && im->base != 1000 ){
2455 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
2460 long_tmp = atol(optarg);
2461 if (long_tmp < 10) {
2462 rrd_set_error("width below 10 pixels");
2465 im->xsize = long_tmp;
2468 long_tmp = atol(optarg);
2469 if (long_tmp < 10) {
2470 rrd_set_error("height below 10 pixels");
2473 im->ysize = long_tmp;
2482 im->imginfo = optarg;
2485 if((im->imgformat = if_conv(optarg)) == -1) {
2486 rrd_set_error("unsupported graphics format '%s'",optarg);
2494 im->logarithmic = 1;
2495 if (isnan(im->minval))
2501 col_nam,&color) == 2){
2503 if((ci=grc_conv(col_nam)) != -1){
2504 im->graph_col[ci]=color;
2506 rrd_set_error("invalid color name '%s'",col_nam);
2509 rrd_set_error("invalid color def format");
2516 char *font = "dummy";
2520 prop,&size,font) == 3){
2522 if((sindex=text_prop_conv(prop)) != -1){
2523 im->text_prop[sindex].size=size;
2524 im->text_prop[sindex].font=font;
2527 rrd_set_error("invalid color name '%s'",col_nam);
2530 rrd_set_error("invalid text property format");
2536 im->zoom= atof(optarg);
2537 if (im->zoom <= 0.0) {
2538 rrd_set_error("zoom factor must be > 0");
2543 strncpy(im->title,optarg,150);
2544 im->title[150]='\0';
2549 rrd_set_error("unknown option '%c'", optopt);
2551 rrd_set_error("unknown option '%s'",argv[optind-1]);
2556 if (optind >= argc) {
2557 rrd_set_error("missing filename");
2561 if (im->logarithmic == 1 && (im->minval <= 0 || isnan(im->minval))){
2562 rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");
2566 if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
2567 /* error string is set in parsetime.c */
2571 if (start_tmp < 3600*24*365*10){
2572 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
2576 if (end_tmp < start_tmp) {
2577 rrd_set_error("start (%ld) should be less than end (%ld)",
2578 start_tmp, end_tmp);
2582 im->start = start_tmp;
2587 rrd_graph_script(int argc, char *argv[], image_desc_t *im)
2591 int linepass = 0; /* stack must follow LINE*, AREA or STACK */
2593 for (i=optind+1;i<argc;i++) {
2598 char funcname[10],vname[MAX_VNAME_LEN+1],sep[1];
2603 /* Each command is one element from *argv[], we call this "line".
2605 ** Each command defines the most current gdes inside struct im.
2606 ** In stead of typing "im->gdes[im->gdes_c-1]" we use "gdp".
2609 gdp=&im->gdes[im->gdes_c-1];
2612 /* function:newvname=string[:ds-name:CF] for xDEF
2613 ** function:vname[#color[:string]] for LINEx,AREA,STACK
2614 ** function:vname#color[:num[:string]] for TICK
2615 ** function:vname-or-num#color[:string] for xRULE
2616 ** function:vname:CF:string for xPRINT
2617 ** function:string for COMMENT
2621 sscanf(line, "%10[A-Z0-9]:%n", funcname,&argstart);
2623 rrd_set_error("Cannot parse function in line: %s",line);
2627 if(sscanf(funcname,"LINE%lf",&linewidth)){
2628 im->gdes[im->gdes_c-1].gf = GF_LINE;
2629 im->gdes[im->gdes_c-1].linewidth = linewidth;
2631 if ((gdp->gf=gf_conv(funcname))==-1) {
2632 rrd_set_error("'%s' is not a valid function name",funcname);
2638 /* If the error string is set, we exit at the end of the switch */
2641 if (rrd_graph_legend(gdp,&line[argstart])==0)
2642 rrd_set_error("Cannot parse comment in line: %s",line);
2647 sscanf(&line[argstart], "%lf%n#%n", &d, &j, &k);
2648 sscanf(&line[argstart], DEF_NAM_FMT "%n#%n", vname, &l, &m);
2650 rrd_set_error("Cannot parse name or num in line: %s",line);
2657 } else if (!rrd_graph_check_vname(im,vname,line)) {
2661 } else break; /* exit due to wrong vname */
2662 if ((j=rrd_graph_color(im,&line[argstart],line,0))==0) break;
2664 if (strlen(&line[argstart])!=0) {
2665 if (rrd_graph_legend(gdp,&line[++argstart])==0)
2666 rrd_set_error("Cannot parse comment in line: %s",line);
2671 rrd_set_error("STACK must follow another graphing element");
2679 sscanf(&line[argstart],DEF_NAM_FMT"%n%1[#:]%n",vname,&j,sep,&k);
2681 rrd_set_error("Cannot parse vname in line: %s",line);
2682 else if (rrd_graph_check_vname(im,vname,line))
2683 rrd_set_error("Undefined vname '%s' in line: %s",line);
2685 k=rrd_graph_color(im,&line[argstart],line,1);
2686 if (rrd_test_error()) break;
2687 argstart=argstart+j+k;
2688 if ((strlen(&line[argstart])!=0)&&(gdp->gf==GF_TICK)) {
2690 sscanf(&line[argstart], ":%lf%n", &gdp->yrule,&j);
2693 if (strlen(&line[argstart])!=0)
2694 if (rrd_graph_legend(gdp,&line[++argstart])==0)
2695 rrd_set_error("Cannot parse legend in line: %s",line);
2701 sscanf(&line[argstart], DEF_NAM_FMT ":%n",gdp->vname,&j);
2703 rrd_set_error("Cannot parse vname in line: '%s'",line);
2707 if (rrd_graph_check_vname(im,gdp->vname,line)) return;
2709 sscanf(&line[argstart], CF_NAM_FMT ":%n",symname,&j);
2711 k=(j!=0)?rrd_graph_check_CF(im,symname,line):1;
2712 #define VIDX im->gdes[gdp->vidx]
2714 case -1: /* looks CF but is not really CF */
2715 if (VIDX.gf == GF_VDEF) rrd_clear_error();
2717 case 0: /* CF present and correct */
2718 if (VIDX.gf == GF_VDEF)
2719 rrd_set_error("Don't use CF when printing VDEF");
2722 case 1: /* CF not present */
2723 if (VIDX.gf == GF_VDEF) rrd_clear_error();
2724 else rrd_set_error("Printing DEF or CDEF needs CF");
2727 rrd_set_error("Oops, bug in GPRINT scanning");
2730 if (rrd_test_error()) break;
2732 if (strlen(&line[argstart])!=0) {
2733 if (rrd_graph_legend(gdp,&line[argstart])==0)
2734 rrd_set_error("Cannot parse legend in line: %s",line);
2735 } else rrd_set_error("No legend in (G)PRINT line: %s",line);
2736 strcpy(gdp->format, gdp->legend);
2742 sscanf(&line[argstart], DEF_NAM_FMT "=%n",gdp->vname,&j);
2744 rrd_set_error("Could not parse line: %s",line);
2747 if (find_var(im,gdp->vname)!=-1) {
2748 rrd_set_error("Variable '%s' in line '%s' already in use\n",
2755 argstart+=scan_for_col(&line[argstart],MAXPATH,gdp->rrd);
2757 sscanf(&line[argstart],
2758 ":" DS_NAM_FMT ":" CF_NAM_FMT "%n%*s%n",
2759 gdp->ds_nam, symname, &j, &k);
2760 if ((j==0)||(k!=0)) {
2761 rrd_set_error("Cannot parse DS or CF in '%s'",line);
2764 rrd_graph_check_CF(im,symname,line);
2768 sscanf(&line[argstart],DEF_NAM_FMT ",%n",vname,&j);
2770 rrd_set_error("Cannot parse vname in line '%s'",line);
2774 if (rrd_graph_check_vname(im,vname,line)) return;
2775 if ( im->gdes[gdp->vidx].gf != GF_DEF
2776 && im->gdes[gdp->vidx].gf != GF_CDEF) {
2777 rrd_set_error("variable '%s' not DEF nor "
2778 "CDEF in VDEF '%s'", vname,gdp->vname);
2781 vdef_parse(gdp,&line[argstart+strstart]);
2784 if (strstr(&line[argstart],":")!=NULL) {
2785 rrd_set_error("Error in RPN, line: %s",line);
2788 if ((gdp->rpnp = rpn_parse(
2793 rrd_set_error("invalid rpn expression in: %s",line);
2798 default: rrd_set_error("Big oops");
2800 if (rrd_test_error()) {
2807 rrd_set_error("can't make a graph without contents");
2808 im_free(im); /* ??? is this set ??? */
2813 rrd_graph_check_vname(image_desc_t *im, char *varname, char *err)
2815 if ((im->gdes[im->gdes_c-1].vidx=find_var(im,varname))==-1) {
2816 rrd_set_error("Unknown variable '%s' in %s",varname,err);
2822 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
2825 graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
2827 color=strstr(var,"#");
2830 rrd_set_error("Found no color in %s",err);
2839 rest=strstr(color,":");
2847 sscanf(color,"#%6x%n",&col,&n);
2848 col = (col << 8) + 0xff /* shift left by 8 */;
2849 if (n!=7) rrd_set_error("Color problem in %s",err);
2852 sscanf(color,"#%8x%n",&col,&n);
2855 rrd_set_error("Color problem in %s",err);
2857 if (rrd_test_error()) return 0;
2863 rrd_graph_check_CF(image_desc_t *im, char *symname, char *err)
2865 if ((im->gdes[im->gdes_c-1].cf=cf_conv(symname))==-1) {
2866 rrd_set_error("Unknown CF '%s' in %s",symname,err);
2872 rrd_graph_legend(graph_desc_t *gdp, char *line)
2876 i=scan_for_col(line,FMT_LEG_LEN,gdp->legend);
2878 return (strlen(&line[i])==0);
2882 int bad_format(char *fmt) {
2887 while (*ptr != '\0') {
2888 if (*ptr == '%') {ptr++;
2889 if (*ptr == '\0') return 1;
2890 while ((*ptr >= '0' && *ptr <= '9') || *ptr == '.') {
2893 if (*ptr == '\0') return 1;
2897 if (*ptr == '\0') return 1;
2898 if (*ptr == 'e' || *ptr == 'f') {
2900 } else { return 1; }
2902 else if (*ptr == 's' || *ptr == 'S' || *ptr == '%') { ++ptr; }
2911 vdef_parse(gdes,str)
2912 struct graph_desc_t *gdes;
2915 /* A VDEF currently is either "func" or "param,func"
2916 * so the parsing is rather simple. Change if needed.
2923 sscanf(str,"%le,%29[A-Z]%n",¶m,func,&n);
2924 if (n==strlen(str)) { /* matched */
2928 sscanf(str,"%29[A-Z]%n",func,&n);
2929 if (n==strlen(str)) { /* matched */
2932 rrd_set_error("Unknown function string '%s' in VDEF '%s'"
2939 if (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
2940 else if (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
2941 else if (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
2942 else if (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
2943 else if (!strcmp("TOTAL", func)) gdes->vf.op = VDEF_TOTAL;
2944 else if (!strcmp("FIRST", func)) gdes->vf.op = VDEF_FIRST;
2945 else if (!strcmp("LAST", func)) gdes->vf.op = VDEF_LAST;
2947 rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
2954 switch (gdes->vf.op) {
2956 if (isnan(param)) { /* no parameter given */
2957 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
2963 if (param>=0.0 && param<=100.0) {
2964 gdes->vf.param = param;
2965 gdes->vf.val = DNAN; /* undefined */
2966 gdes->vf.when = 0; /* undefined */
2968 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
2982 gdes->vf.param = DNAN;
2983 gdes->vf.val = DNAN;
2986 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3001 graph_desc_t *src,*dst;
3005 dst = &im->gdes[gdi];
3006 src = &im->gdes[dst->vidx];
3007 data = src->data + src->ds;
3008 steps = (src->end - src->start) / src->step;
3011 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3018 switch (dst->vf.op) {
3019 case VDEF_PERCENT: {
3020 rrd_value_t * array;
3024 if ((array = malloc(steps*sizeof(double)))==NULL) {
3025 rrd_set_error("malloc VDEV_PERCENT");
3028 for (step=0;step < steps; step++) {
3029 array[step]=data[step*src->ds_cnt];
3031 qsort(array,step,sizeof(double),vdef_percent_compar);
3033 field = (steps-1)*dst->vf.param/100;
3034 dst->vf.val = array[field];
3035 dst->vf.when = 0; /* no time component */
3037 for(step=0;step<steps;step++)
3038 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3044 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3045 if (step == steps) {
3049 dst->vf.val = data[step*src->ds_cnt];
3050 dst->vf.when = src->start + (step+1)*src->step;
3052 while (step != steps) {
3053 if (finite(data[step*src->ds_cnt])) {
3054 if (data[step*src->ds_cnt] > dst->vf.val) {
3055 dst->vf.val = data[step*src->ds_cnt];
3056 dst->vf.when = src->start + (step+1)*src->step;
3063 case VDEF_AVERAGE: {
3066 for (step=0;step<steps;step++) {
3067 if (finite(data[step*src->ds_cnt])) {
3068 sum += data[step*src->ds_cnt];
3073 if (dst->vf.op == VDEF_TOTAL) {
3074 dst->vf.val = sum*src->step;
3075 dst->vf.when = cnt*src->step; /* not really "when" */
3077 dst->vf.val = sum/cnt;
3078 dst->vf.when = 0; /* no time component */
3088 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3089 if (step == steps) {
3093 dst->vf.val = data[step*src->ds_cnt];
3094 dst->vf.when = src->start + (step+1)*src->step;
3096 while (step != steps) {
3097 if (finite(data[step*src->ds_cnt])) {
3098 if (data[step*src->ds_cnt] < dst->vf.val) {
3099 dst->vf.val = data[step*src->ds_cnt];
3100 dst->vf.when = src->start + (step+1)*src->step;
3107 /* The time value returned here is one step before the
3108 * actual time value. This is the start of the first
3112 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3113 if (step == steps) { /* all entries were NaN */
3117 dst->vf.val = data[step*src->ds_cnt];
3118 dst->vf.when = src->start + step*src->step;
3122 /* The time value returned here is the
3123 * actual time value. This is the end of the last
3127 while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3128 if (step < 0) { /* all entries were NaN */
3132 dst->vf.val = data[step*src->ds_cnt];
3133 dst->vf.when = src->start + (step+1)*src->step;
3140 /* NaN < -INF < finite_values < INF */
3142 vdef_percent_compar(a,b)
3145 /* Equality is not returned; this doesn't hurt except
3146 * (maybe) for a little performance.
3149 /* First catch NaN values. They are smallest */
3150 if (isnan( *(double *)a )) return -1;
3151 if (isnan( *(double *)b )) return 1;
3153 /* NaN doesn't reach this part so INF and -INF are extremes.
3154 * The sign from isinf() is compatible with the sign we return
3156 if (isinf( *(double *)a )) return isinf( *(double *)a );
3157 if (isinf( *(double *)b )) return isinf( *(double *)b );
3159 /* If we reach this, both values must be finite */
3160 if ( *(double *)a < *(double *)b ) return -1; else return 1;