1 /****************************************************************************
2 * RRDtool 1.1.x Copyright Tobias Oetiker, 1997 - 2002
3 ****************************************************************************
4 * rrd__graph.c make creates ne rrds
5 ****************************************************************************/
23 #include "rrd_graph.h"
24 #include "rrd_graph_helper.h"
26 /* some constant definitions */
29 #ifndef RRD_DEFAULT_FONT
31 #define RRD_DEFAULT_FONT "c:/winnt/fonts/COUR.TTF"
33 #define RRD_DEFAULT_FONT "/usr/share/fonts/truetype/openoffice/ariosor.ttf"
34 /* #define RRD_DEFAULT_FONT "/usr/share/fonts/truetype/Arial.ttf" */
39 text_prop_t text_prop[] = {
40 { 10.0, RRD_DEFAULT_FONT }, /* default */
41 { 12.0, RRD_DEFAULT_FONT }, /* title */
42 { 8.0, RRD_DEFAULT_FONT }, /* axis */
43 { 10.0, RRD_DEFAULT_FONT }, /* unit */
44 { 10.0, RRD_DEFAULT_FONT } /* legend */
48 {0, TMT_SECOND,30, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
49 {2, TMT_MINUTE,1, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
50 {5, TMT_MINUTE,2, TMT_MINUTE,10, TMT_MINUTE,10, 0,"%H:%M"},
51 {10, TMT_MINUTE,5, TMT_MINUTE,20, TMT_MINUTE,20, 0,"%H:%M"},
52 {30, TMT_MINUTE,10, TMT_HOUR,1, TMT_HOUR,1, 0,"%H:%M"},
53 {60, TMT_MINUTE,30, TMT_HOUR,2, TMT_HOUR,2, 0,"%H:%M"},
54 {180, TMT_HOUR,1, TMT_HOUR,6, TMT_HOUR,6, 0,"%H:%M"},
55 /*{300, TMT_HOUR,3, TMT_HOUR,12, TMT_HOUR,12, 12*3600,"%a %p"}, this looks silly*/
56 {600, TMT_HOUR,6, TMT_DAY,1, TMT_DAY,1, 24*3600,"%a"},
57 {1800, TMT_HOUR,12, TMT_DAY,1, TMT_DAY,2, 24*3600,"%a"},
58 {3600, TMT_DAY,1, TMT_WEEK,1, TMT_WEEK,1, 7*24*3600,"Week %V"},
59 {3*3600, TMT_WEEK,1, TMT_MONTH,1, TMT_WEEK,2, 7*24*3600,"Week %V"},
60 {6*3600, TMT_MONTH,1, TMT_MONTH,1, TMT_MONTH,1, 30*24*3600,"%b"},
61 {48*3600, TMT_MONTH,1, TMT_MONTH,3, TMT_MONTH,3, 30*24*3600,"%b"},
62 {10*24*3600, TMT_YEAR,1, TMT_YEAR,1, TMT_YEAR,1, 365*24*3600,"%y"},
63 {-1,TMT_MONTH,0,TMT_MONTH,0,TMT_MONTH,0,0,""}
66 /* sensible logarithmic y label intervals ...
67 the first element of each row defines the possible starting points on the
68 y axis ... the other specify the */
70 double yloglab[][12]= {{ 1e9, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
71 { 1e3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
72 { 1e1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
73 /* { 1e1, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, */
74 { 1e1, 1, 2.5, 5, 7.5, 0, 0, 0, 0, 0, 0, 0 },
75 { 1e1, 1, 2, 4, 6, 8, 0, 0, 0, 0, 0, 0 },
76 { 1e1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0 },
77 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }};
79 /* sensible y label intervals ...*/
97 gfx_color_t graph_col[] = /* default colors */
98 { 0xFFFFFFFF, /* canvas */
99 0xF0F0F0FF, /* background */
100 0xD0D0D0FF, /* shade A */
101 0xA0A0A0FF, /* shade B */
102 0x909090FF, /* grid */
103 0xE05050FF, /* major grid */
104 0x000000FF, /* font */
105 0x000000FF, /* frame */
106 0xFF0000FF /* arrow */
113 # define DPRINT(x) (void)(printf x, printf("\n"))
119 /* initialize with xtr(im,0); */
121 xtr(image_desc_t *im,time_t mytime){
124 pixie = (double) im->xsize / (double)(im->end - im->start);
127 return (int)((double)im->xorigin
128 + pixie * ( mytime - im->start ) );
131 /* translate data values into y coordinates */
133 ytr(image_desc_t *im, double value){
138 pixie = (double) im->ysize / (im->maxval - im->minval);
140 pixie = (double) im->ysize / (log10(im->maxval) - log10(im->minval));
142 } else if(!im->logarithmic) {
143 yval = im->yorigin - pixie * (value - im->minval);
145 if (value < im->minval) {
148 yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
151 /* make sure we don't return anything too unreasonable. GD lib can
152 get terribly slow when drawing lines outside its scope. This is
153 especially problematic in connection with the rigid option */
155 /* keep yval as-is */
156 } else if (yval > im->yorigin) {
157 yval = im->yorigin+2;
158 } else if (yval < im->yorigin - im->ysize){
159 yval = im->yorigin - im->ysize - 2;
166 /* conversion function for symbolic entry names */
169 #define conv_if(VV,VVV) \
170 if (strcmp(#VV, string) == 0) return VVV ;
172 enum gf_en gf_conv(char *string){
174 conv_if(PRINT,GF_PRINT)
175 conv_if(GPRINT,GF_GPRINT)
176 conv_if(COMMENT,GF_COMMENT)
177 conv_if(HRULE,GF_HRULE)
178 conv_if(VRULE,GF_VRULE)
179 conv_if(LINE,GF_LINE)
180 conv_if(AREA,GF_AREA)
181 conv_if(STACK,GF_STACK)
182 conv_if(TICK,GF_TICK)
184 conv_if(CDEF,GF_CDEF)
185 conv_if(VDEF,GF_VDEF)
186 conv_if(PART,GF_PART)
191 enum gfx_if_en if_conv(char *string){
200 enum tmt_en tmt_conv(char *string){
202 conv_if(SECOND,TMT_SECOND)
203 conv_if(MINUTE,TMT_MINUTE)
204 conv_if(HOUR,TMT_HOUR)
206 conv_if(WEEK,TMT_WEEK)
207 conv_if(MONTH,TMT_MONTH)
208 conv_if(YEAR,TMT_YEAR)
212 enum grc_en grc_conv(char *string){
214 conv_if(BACK,GRC_BACK)
215 conv_if(CANVAS,GRC_CANVAS)
216 conv_if(SHADEA,GRC_SHADEA)
217 conv_if(SHADEB,GRC_SHADEB)
218 conv_if(GRID,GRC_GRID)
219 conv_if(MGRID,GRC_MGRID)
220 conv_if(FONT,GRC_FONT)
221 conv_if(FRAME,GRC_FRAME)
222 conv_if(ARROW,GRC_ARROW)
227 enum text_prop_en text_prop_conv(char *string){
229 conv_if(DEFAULT,TEXT_PROP_DEFAULT)
230 conv_if(TITLE,TEXT_PROP_TITLE)
231 conv_if(AXIS,TEXT_PROP_AXIS)
232 conv_if(UNIT,TEXT_PROP_UNIT)
233 conv_if(LEGEND,TEXT_PROP_LEGEND)
243 im_free(image_desc_t *im)
246 if (im == NULL) return 0;
247 for(i=0;i<im->gdes_c;i++){
248 if (im->gdes[i].data_first){
249 /* careful here, because a single pointer can occur several times */
250 free (im->gdes[i].data);
251 if (im->gdes[i].ds_namv){
252 for (ii=0;ii<im->gdes[i].ds_cnt;ii++)
253 free(im->gdes[i].ds_namv[ii]);
254 free(im->gdes[i].ds_namv);
257 free (im->gdes[i].p_data);
258 free (im->gdes[i].rpnp);
261 gfx_destroy(im->canvas);
265 /* find SI magnitude symbol for the given number*/
268 image_desc_t *im, /* image description */
275 char *symbol[] = {"a", /* 10e-18 Atto */
276 "f", /* 10e-15 Femto */
277 "p", /* 10e-12 Pico */
278 "n", /* 10e-9 Nano */
279 "u", /* 10e-6 Micro */
280 "m", /* 10e-3 Milli */
285 "T", /* 10e12 Tera */
286 "P", /* 10e15 Peta */
292 if (*value == 0.0 || isnan(*value) ) {
296 sindex = floor(log(fabs(*value))/log((double)im->base));
297 *magfact = pow((double)im->base, (double)sindex);
298 (*value) /= (*magfact);
300 if ( sindex <= symbcenter && sindex >= -symbcenter) {
301 (*symb_ptr) = symbol[sindex+symbcenter];
309 /* find SI magnitude symbol for the numbers on the y-axis*/
312 image_desc_t *im /* image description */
316 char symbol[] = {'a', /* 10e-18 Atto */
317 'f', /* 10e-15 Femto */
318 'p', /* 10e-12 Pico */
319 'n', /* 10e-9 Nano */
320 'u', /* 10e-6 Micro */
321 'm', /* 10e-3 Milli */
326 'T', /* 10e12 Tera */
327 'P', /* 10e15 Peta */
333 if (im->unitsexponent != 9999) {
334 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
335 digits = floor(im->unitsexponent / 3);
337 digits = floor( log( max( fabs(im->minval),fabs(im->maxval)))/log((double)im->base));
339 im->magfact = pow((double)im->base , digits);
342 printf("digits %6.3f im->magfact %6.3f\n",digits,im->magfact);
345 if ( ((digits+symbcenter) < sizeof(symbol)) &&
346 ((digits+symbcenter) >= 0) )
347 im->symbol = symbol[(int)digits+symbcenter];
352 /* move min and max values around to become sensible */
355 expand_range(image_desc_t *im)
357 double sensiblevalues[] ={1000.0,900.0,800.0,750.0,700.0,
358 600.0,500.0,400.0,300.0,250.0,
359 200.0,125.0,100.0,90.0,80.0,
360 75.0,70.0,60.0,50.0,40.0,30.0,
361 25.0,20.0,10.0,9.0,8.0,
362 7.0,6.0,5.0,4.0,3.5,3.0,
363 2.5,2.0,1.8,1.5,1.2,1.0,
364 0.8,0.7,0.6,0.5,0.4,0.3,0.2,0.1,0.0,-1};
366 double scaled_min,scaled_max;
373 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
374 im->minval,im->maxval,im->magfact);
377 if (isnan(im->ygridstep)){
378 if(im->extra_flags & ALTAUTOSCALE) {
379 /* measure the amplitude of the function. Make sure that
380 graph boundaries are slightly higher then max/min vals
381 so we can see amplitude on the graph */
384 delt = im->maxval - im->minval;
386 fact = 2.0 * pow(10.0,
387 floor(log10(max(fabs(im->minval), fabs(im->maxval)))) - 2);
389 adj = (fact - delt) * 0.55;
391 printf("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n", im->minval, im->maxval, delt, fact, adj);
397 else if(im->extra_flags & ALTAUTOSCALE_MAX) {
398 /* measure the amplitude of the function. Make sure that
399 graph boundaries are slightly higher than max vals
400 so we can see amplitude on the graph */
401 adj = (im->maxval - im->minval) * 0.1;
405 scaled_min = im->minval / im->magfact;
406 scaled_max = im->maxval / im->magfact;
408 for (i=1; sensiblevalues[i] > 0; i++){
409 if (sensiblevalues[i-1]>=scaled_min &&
410 sensiblevalues[i]<=scaled_min)
411 im->minval = sensiblevalues[i]*(im->magfact);
413 if (-sensiblevalues[i-1]<=scaled_min &&
414 -sensiblevalues[i]>=scaled_min)
415 im->minval = -sensiblevalues[i-1]*(im->magfact);
417 if (sensiblevalues[i-1] >= scaled_max &&
418 sensiblevalues[i] <= scaled_max)
419 im->maxval = sensiblevalues[i-1]*(im->magfact);
421 if (-sensiblevalues[i-1]<=scaled_max &&
422 -sensiblevalues[i] >=scaled_max)
423 im->maxval = -sensiblevalues[i]*(im->magfact);
427 /* adjust min and max to the grid definition if there is one */
428 im->minval = (double)im->ylabfact * im->ygridstep *
429 floor(im->minval / ((double)im->ylabfact * im->ygridstep));
430 im->maxval = (double)im->ylabfact * im->ygridstep *
431 ceil(im->maxval /( (double)im->ylabfact * im->ygridstep));
435 fprintf(stderr,"SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
436 im->minval,im->maxval,im->magfact);
441 apply_gridfit(image_desc_t *im)
443 if (isnan(im->minval) || isnan(im->maxval))
446 if (im->logarithmic) {
447 double ya, yb, ypix, ypixfrac;
448 double log10_range = log10(im->maxval) - log10(im->minval);
449 ya = pow((double)10, floor(log10(im->minval)));
450 while (ya < im->minval)
453 return; /* don't have y=10^x gridline */
455 if (yb <= im->maxval) {
456 /* we have at least 2 y=10^x gridlines.
457 Make sure distance between them in pixels
458 are an integer by expanding im->maxval */
459 double y_pixel_delta = ytr(im, ya) - ytr(im, yb);
460 double factor = y_pixel_delta / floor(y_pixel_delta);
461 double new_log10_range = factor * log10_range;
462 double new_ymax_log10 = log10(im->minval) + new_log10_range;
463 im->maxval = pow(10, new_ymax_log10);
464 ytr(im, DNAN); /* reset precalc */
465 log10_range = log10(im->maxval) - log10(im->minval);
467 /* make sure first y=10^x gridline is located on
468 integer pixel position by moving scale slightly
469 downwards (sub-pixel movement) */
470 ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
471 ypixfrac = ypix - floor(ypix);
472 if (ypixfrac > 0 && ypixfrac < 1) {
473 double yfrac = ypixfrac / im->ysize;
474 im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
475 im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
476 ytr(im, DNAN); /* reset precalc */
479 /* Make sure we have an integer pixel distance between
480 each minor gridline */
481 double ypos1 = ytr(im, im->minval);
482 double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
483 double y_pixel_delta = ypos1 - ypos2;
484 double factor = y_pixel_delta / floor(y_pixel_delta);
485 double new_range = factor * (im->maxval - im->minval);
486 double gridstep = im->ygrid_scale.gridstep;
487 double minor_y, minor_y_px, minor_y_px_frac;
488 im->maxval = im->minval + new_range;
489 ytr(im, DNAN); /* reset precalc */
490 /* make sure first minor gridline is on integer pixel y coord */
491 minor_y = gridstep * floor(im->minval / gridstep);
492 while (minor_y < im->minval)
494 minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
495 minor_y_px_frac = minor_y_px - floor(minor_y_px);
496 if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
497 double yfrac = minor_y_px_frac / im->ysize;
498 double range = im->maxval - im->minval;
499 im->minval = im->minval - yfrac * range;
500 im->maxval = im->maxval - yfrac * range;
501 ytr(im, DNAN); /* reset precalc */
503 calc_horizontal_grid(im); /* recalc with changed im->maxval */
507 /* reduce data reimplementation by Alex */
511 enum cf_en cf, /* which consolidation function ?*/
512 unsigned long cur_step, /* step the data currently is in */
513 time_t *start, /* start, end and step as requested ... */
514 time_t *end, /* ... by the application will be ... */
515 unsigned long *step, /* ... adjusted to represent reality */
516 unsigned long *ds_cnt, /* number of data sources in file */
517 rrd_value_t **data) /* two dimensional array containing the data */
519 int i,reduce_factor = ceil((double)(*step) / (double)cur_step);
520 unsigned long col,dst_row,row_cnt,start_offset,end_offset,skiprows=0;
521 rrd_value_t *srcptr,*dstptr;
523 (*step) = cur_step*reduce_factor; /* set new step size for reduced data */
526 row_cnt = ((*end)-(*start))/cur_step;
532 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
533 row_cnt,reduce_factor,*start,*end,cur_step);
534 for (col=0;col<row_cnt;col++) {
535 printf("time %10lu: ",*start+(col+1)*cur_step);
536 for (i=0;i<*ds_cnt;i++)
537 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
542 /* We have to combine [reduce_factor] rows of the source
543 ** into one row for the destination. Doing this we also
544 ** need to take care to combine the correct rows. First
545 ** alter the start and end time so that they are multiples
546 ** of the new step time. We cannot reduce the amount of
547 ** time so we have to move the end towards the future and
548 ** the start towards the past.
550 end_offset = (*end) % (*step);
551 start_offset = (*start) % (*step);
553 /* If there is a start offset (which cannot be more than
554 ** one destination row), skip the appropriate number of
555 ** source rows and one destination row. The appropriate
556 ** number is what we do know (start_offset/cur_step) of
557 ** the new interval (*step/cur_step aka reduce_factor).
560 printf("start_offset: %lu end_offset: %lu\n",start_offset,end_offset);
561 printf("row_cnt before: %lu\n",row_cnt);
564 (*start) = (*start)-start_offset;
565 skiprows=reduce_factor-start_offset/cur_step;
566 srcptr+=skiprows* *ds_cnt;
567 for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
571 printf("row_cnt between: %lu\n",row_cnt);
574 /* At the end we have some rows that are not going to be
575 ** used, the amount is end_offset/cur_step
578 (*end) = (*end)-end_offset+(*step);
579 skiprows = end_offset/cur_step;
583 printf("row_cnt after: %lu\n",row_cnt);
586 /* Sanity check: row_cnt should be multiple of reduce_factor */
587 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
589 if (row_cnt%reduce_factor) {
590 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
591 row_cnt,reduce_factor);
592 printf("BUG in reduce_data()\n");
596 /* Now combine reduce_factor intervals at a time
597 ** into one interval for the destination.
600 for (dst_row=0;row_cnt>=reduce_factor;dst_row++) {
601 for (col=0;col<(*ds_cnt);col++) {
602 rrd_value_t newval=DNAN;
603 unsigned long validval=0;
605 for (i=0;i<reduce_factor;i++) {
606 if (isnan(srcptr[i*(*ds_cnt)+col])) {
610 if (isnan(newval)) newval = srcptr[i*(*ds_cnt)+col];
618 newval += srcptr[i*(*ds_cnt)+col];
621 newval = min (newval,srcptr[i*(*ds_cnt)+col]);
624 /* an interval contains a failure if any subintervals contained a failure */
626 newval = max (newval,srcptr[i*(*ds_cnt)+col]);
629 newval = srcptr[i*(*ds_cnt)+col];
634 if (validval == 0){newval = DNAN;} else{
652 srcptr+=(*ds_cnt)*reduce_factor;
653 row_cnt-=reduce_factor;
655 /* If we had to alter the endtime, we didn't have enough
656 ** source rows to fill the last row. Fill it with NaN.
658 if (end_offset) for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
660 row_cnt = ((*end)-(*start))/ *step;
662 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
663 row_cnt,*start,*end,*step);
664 for (col=0;col<row_cnt;col++) {
665 printf("time %10lu: ",*start+(col+1)*(*step));
666 for (i=0;i<*ds_cnt;i++)
667 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
674 /* get the data required for the graphs from the
678 data_fetch( image_desc_t *im )
682 /* pull the data from the log files ... */
683 for (i=0;i<im->gdes_c;i++){
684 /* only GF_DEF elements fetch data */
685 if (im->gdes[i].gf != GF_DEF)
689 /* do we have it already ?*/
690 for (ii=0;ii<i;ii++){
691 if (im->gdes[ii].gf != GF_DEF)
693 if((strcmp(im->gdes[i].rrd,im->gdes[ii].rrd) == 0)
694 && (im->gdes[i].cf == im->gdes[ii].cf)){
695 /* OK the data it is here already ...
696 * we just copy the header portion */
697 im->gdes[i].start = im->gdes[ii].start;
698 im->gdes[i].end = im->gdes[ii].end;
699 im->gdes[i].step = im->gdes[ii].step;
700 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
701 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
702 im->gdes[i].data = im->gdes[ii].data;
703 im->gdes[i].data_first = 0;
710 unsigned long ft_step = im->gdes[i].step ;
712 if((rrd_fetch_fn(im->gdes[i].rrd,
718 &im->gdes[i].ds_namv,
719 &im->gdes[i].data)) == -1){
722 im->gdes[i].data_first = 1;
724 if (ft_step < im->gdes[i].step) {
725 reduce_data(im->gdes[i].cf,
733 im->gdes[i].step = ft_step;
737 /* lets see if the required data source is realy there */
738 for(ii=0;ii<im->gdes[i].ds_cnt;ii++){
739 if(strcmp(im->gdes[i].ds_namv[ii],im->gdes[i].ds_nam) == 0){
742 if (im->gdes[i].ds== -1){
743 rrd_set_error("No DS called '%s' in '%s'",
744 im->gdes[i].ds_nam,im->gdes[i].rrd);
752 /* evaluate the expressions in the CDEF functions */
754 /*************************************************************
756 *************************************************************/
759 find_var_wrapper(void *arg1, char *key)
761 return find_var((image_desc_t *) arg1, key);
764 /* find gdes containing var*/
766 find_var(image_desc_t *im, char *key){
768 for(ii=0;ii<im->gdes_c-1;ii++){
769 if((im->gdes[ii].gf == GF_DEF
770 || im->gdes[ii].gf == GF_VDEF
771 || im->gdes[ii].gf == GF_CDEF)
772 && (strcmp(im->gdes[ii].vname,key) == 0)){
779 /* find the largest common denominator for all the numbers
780 in the 0 terminated num array */
785 for (i=0;num[i+1]!=0;i++){
787 rest=num[i] % num[i+1];
788 num[i]=num[i+1]; num[i+1]=rest;
792 /* return i==0?num[i]:num[i-1]; */
796 /* run the rpn calculator on all the VDEF and CDEF arguments */
798 data_calc( image_desc_t *im){
802 long *steparray, rpi;
807 rpnstack_init(&rpnstack);
809 for (gdi=0;gdi<im->gdes_c;gdi++){
810 /* Look for GF_VDEF and GF_CDEF in the same loop,
811 * so CDEFs can use VDEFs and vice versa
813 switch (im->gdes[gdi].gf) {
815 /* A VDEF has no DS. This also signals other parts
816 * of rrdtool that this is a VDEF value, not a CDEF.
818 im->gdes[gdi].ds_cnt = 0;
819 if (vdef_calc(im,gdi)) {
820 rrd_set_error("Error processing VDEF '%s'"
823 rpnstack_free(&rpnstack);
828 im->gdes[gdi].ds_cnt = 1;
829 im->gdes[gdi].ds = 0;
830 im->gdes[gdi].data_first = 1;
831 im->gdes[gdi].start = 0;
832 im->gdes[gdi].end = 0;
837 /* Find the variables in the expression.
838 * - VDEF variables are substituted by their values
839 * and the opcode is changed into OP_NUMBER.
840 * - CDEF variables are analized for their step size,
841 * the lowest common denominator of all the step
842 * sizes of the data sources involved is calculated
843 * and the resulting number is the step size for the
844 * resulting data source.
846 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
847 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE){
848 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
849 if (im->gdes[ptr].ds_cnt == 0) {
851 printf("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
853 im->gdes[ptr].vname);
854 printf("DEBUG: value from vdef is %f\n",im->gdes[ptr].vf.val);
856 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
857 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
859 if ((steparray = rrd_realloc(steparray, (++stepcnt+1)*sizeof(*steparray)))==NULL){
860 rrd_set_error("realloc steparray");
861 rpnstack_free(&rpnstack);
865 steparray[stepcnt-1] = im->gdes[ptr].step;
867 /* adjust start and end of cdef (gdi) so
868 * that it runs from the latest start point
869 * to the earliest endpoint of any of the
870 * rras involved (ptr)
872 if(im->gdes[gdi].start < im->gdes[ptr].start)
873 im->gdes[gdi].start = im->gdes[ptr].start;
875 if(im->gdes[gdi].end == 0 ||
876 im->gdes[gdi].end > im->gdes[ptr].end)
877 im->gdes[gdi].end = im->gdes[ptr].end;
879 /* store pointer to the first element of
880 * the rra providing data for variable,
881 * further save step size and data source
884 im->gdes[gdi].rpnp[rpi].data = im->gdes[ptr].data + im->gdes[ptr].ds;
885 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
886 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
888 /* backoff the *.data ptr; this is done so
889 * rpncalc() function doesn't have to treat
890 * the first case differently
892 } /* if ds_cnt != 0 */
893 } /* if OP_VARIABLE */
894 } /* loop through all rpi */
896 /* move the data pointers to the correct period */
897 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
898 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE){
899 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
900 if(im->gdes[gdi].start > im->gdes[ptr].start) {
901 im->gdes[gdi].rpnp[rpi].data += im->gdes[gdi].rpnp[rpi].ds_cnt;
907 if(steparray == NULL){
908 rrd_set_error("rpn expressions without DEF"
909 " or CDEF variables are not supported");
910 rpnstack_free(&rpnstack);
913 steparray[stepcnt]=0;
914 /* Now find the resulting step. All steps in all
915 * used RRAs have to be visited
917 im->gdes[gdi].step = lcd(steparray);
919 if((im->gdes[gdi].data = malloc((
920 (im->gdes[gdi].end-im->gdes[gdi].start)
921 / im->gdes[gdi].step)
922 * sizeof(double)))==NULL){
923 rrd_set_error("malloc im->gdes[gdi].data");
924 rpnstack_free(&rpnstack);
928 /* Step through the new cdef results array and
929 * calculate the values
931 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
932 now<=im->gdes[gdi].end;
933 now += im->gdes[gdi].step)
935 rpnp_t *rpnp = im -> gdes[gdi].rpnp;
937 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
938 * in this case we are advancing by timesteps;
939 * we use the fact that time_t is a synonym for long
941 if (rpn_calc(rpnp,&rpnstack,(long) now,
942 im->gdes[gdi].data,++dataidx) == -1) {
943 /* rpn_calc sets the error string */
944 rpnstack_free(&rpnstack);
947 } /* enumerate over time steps within a CDEF */
952 } /* enumerate over CDEFs */
953 rpnstack_free(&rpnstack);
957 /* massage data so, that we get one value for each x coordinate in the graph */
959 data_proc( image_desc_t *im ){
961 double pixstep = (double)(im->end-im->start)
962 /(double)im->xsize; /* how much time
963 passes in one pixel */
965 double minval=DNAN,maxval=DNAN;
967 unsigned long gr_time;
969 /* memory for the processed data */
970 for(i=0;i<im->gdes_c;i++){
971 if((im->gdes[i].gf==GF_LINE) ||
972 (im->gdes[i].gf==GF_AREA) ||
973 (im->gdes[i].gf==GF_TICK) ||
974 (im->gdes[i].gf==GF_STACK)){
975 if((im->gdes[i].p_data = malloc((im->xsize +1)
976 * sizeof(rrd_value_t)))==NULL){
977 rrd_set_error("malloc data_proc");
983 for(i=0;i<im->xsize;i++){
985 gr_time = im->start+pixstep*i; /* time of the
989 for(ii=0;ii<im->gdes_c;ii++){
991 switch(im->gdes[ii].gf){
997 vidx = im->gdes[ii].vidx;
1000 im->gdes[vidx].data[
1001 ((unsigned long)floor(
1002 (double)(gr_time-im->gdes[vidx].start) / im->gdes[vidx].step
1004 ) *im->gdes[vidx].ds_cnt
1005 +im->gdes[vidx].ds];
1007 if (! isnan(value)) {
1009 im->gdes[ii].p_data[i] = paintval;
1010 /* GF_TICK: the data values are not relevant for min and max */
1011 if (finite(paintval) && im->gdes[ii].gf != GF_TICK ){
1012 if (isnan(minval) || paintval < minval)
1014 if (isnan(maxval) || paintval > maxval)
1018 im->gdes[ii].p_data[i] = DNAN;
1035 /* if min or max have not been asigned a value this is because
1036 there was no data in the graph ... this is not good ...
1037 lets set these to dummy values then ... */
1039 if (isnan(minval)) minval = 0.0;
1040 if (isnan(maxval)) maxval = 1.0;
1042 /* adjust min and max values */
1043 if (isnan(im->minval)
1044 || ((!im->logarithmic && !im->rigid) /* don't adjust low-end with log scale */
1045 && im->minval > minval))
1046 im->minval = minval;
1047 if (isnan(im->maxval)
1049 && im->maxval < maxval)){
1050 if (im->logarithmic)
1051 im->maxval = maxval * 1.1;
1053 im->maxval = maxval;
1055 /* make sure min and max are not equal */
1056 if (im->minval == im->maxval) {
1058 if (! im->logarithmic) {
1062 /* make sure min and max are not both zero */
1063 if (im->maxval == 0.0) {
1073 /* identify the point where the first gridline, label ... gets placed */
1077 time_t start, /* what is the initial time */
1078 enum tmt_en baseint, /* what is the basic interval */
1079 long basestep /* how many if these do we jump a time */
1083 tm = *localtime(&start);
1086 tm.tm_sec -= tm.tm_sec % basestep; break;
1089 tm.tm_min -= tm.tm_min % basestep;
1094 tm.tm_hour -= tm.tm_hour % basestep; break;
1096 /* we do NOT look at the basestep for this ... */
1099 tm.tm_hour = 0; break;
1101 /* we do NOT look at the basestep for this ... */
1105 tm.tm_mday -= tm.tm_wday -1; /* -1 because we want the monday */
1106 if (tm.tm_wday==0) tm.tm_mday -= 7; /* we want the *previous* monday */
1113 tm.tm_mon -= tm.tm_mon % basestep; break;
1121 tm.tm_year -= (tm.tm_year+1900) % basestep;
1126 /* identify the point where the next gridline, label ... gets placed */
1129 time_t current, /* what is the initial time */
1130 enum tmt_en baseint, /* what is the basic interval */
1131 long basestep /* how many if these do we jump a time */
1136 tm = *localtime(¤t);
1140 tm.tm_sec += basestep; break;
1142 tm.tm_min += basestep; break;
1144 tm.tm_hour += basestep; break;
1146 tm.tm_mday += basestep; break;
1148 tm.tm_mday += 7*basestep; break;
1150 tm.tm_mon += basestep; break;
1152 tm.tm_year += basestep;
1154 madetime = mktime(&tm);
1155 } while (madetime == -1); /* this is necessary to skip impssible times
1156 like the daylight saving time skips */
1162 /* calculate values required for PRINT and GPRINT functions */
1165 print_calc(image_desc_t *im, char ***prdata)
1167 long i,ii,validsteps;
1170 int graphelement = 0;
1173 double magfact = -1;
1177 if (im->imginfo) prlines++;
1178 for(i=0;i<im->gdes_c;i++){
1179 switch(im->gdes[i].gf){
1182 if(((*prdata) = rrd_realloc((*prdata),prlines*sizeof(char *)))==NULL){
1183 rrd_set_error("realloc prdata");
1187 /* PRINT and GPRINT can now print VDEF generated values.
1188 * There's no need to do any calculations on them as these
1189 * calculations were already made.
1191 vidx = im->gdes[i].vidx;
1192 if (im->gdes[vidx].gf==GF_VDEF) { /* simply use vals */
1193 printval = im->gdes[vidx].vf.val;
1194 printtime = im->gdes[vidx].vf.when;
1195 } else { /* need to calculate max,min,avg etcetera */
1196 max_ii =((im->gdes[vidx].end
1197 - im->gdes[vidx].start)
1198 / im->gdes[vidx].step
1199 * im->gdes[vidx].ds_cnt);
1202 for( ii=im->gdes[vidx].ds;
1204 ii+=im->gdes[vidx].ds_cnt){
1205 if (! finite(im->gdes[vidx].data[ii]))
1207 if (isnan(printval)){
1208 printval = im->gdes[vidx].data[ii];
1213 switch (im->gdes[i].cf){
1216 case CF_DEVSEASONAL:
1220 printval += im->gdes[vidx].data[ii];
1223 printval = min( printval, im->gdes[vidx].data[ii]);
1227 printval = max( printval, im->gdes[vidx].data[ii]);
1230 printval = im->gdes[vidx].data[ii];
1233 if (im->gdes[i].cf==CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1234 if (validsteps > 1) {
1235 printval = (printval / validsteps);
1238 } /* prepare printval */
1240 if (!strcmp(im->gdes[i].format,"%c")) { /* VDEF time print */
1241 if (im->gdes[i].gf == GF_PRINT){
1242 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1243 sprintf((*prdata)[prlines-2],"%s (%lu)",
1244 ctime(&printtime),printtime);
1245 (*prdata)[prlines-1] = NULL;
1247 sprintf(im->gdes[i].legend,"%s (%lu)",
1248 ctime(&printtime),printtime);
1252 if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) {
1253 /* Magfact is set to -1 upon entry to print_calc. If it
1254 * is still less than 0, then we need to run auto_scale.
1255 * Otherwise, put the value into the correct units. If
1256 * the value is 0, then do not set the symbol or magnification
1257 * so next the calculation will be performed again. */
1258 if (magfact < 0.0) {
1259 auto_scale(im,&printval,&si_symb,&magfact);
1260 if (printval == 0.0)
1263 printval /= magfact;
1265 *(++percent_s) = 's';
1266 } else if (strstr(im->gdes[i].format,"%s") != NULL) {
1267 auto_scale(im,&printval,&si_symb,&magfact);
1270 if (im->gdes[i].gf == GF_PRINT){
1271 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1272 if (bad_format(im->gdes[i].format)) {
1273 rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1276 #ifdef HAVE_SNPRINTF
1277 snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
1279 sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
1281 (*prdata)[prlines-1] = NULL;
1285 if (bad_format(im->gdes[i].format)) {
1286 rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1289 #ifdef HAVE_SNPRINTF
1290 snprintf(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
1292 sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
1314 return graphelement;
1318 /* place legends with color spots */
1320 leg_place(image_desc_t *im)
1323 int interleg = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1324 int box =im->text_prop[TEXT_PROP_LEGEND].size*1.5;
1325 int border = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1326 int fill=0, fill_last;
1328 int leg_x = border, leg_y = im->yimg;
1332 char prt_fctn; /*special printfunctions */
1335 if( !(im->extra_flags & NOLEGEND) ) {
1336 if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1337 rrd_set_error("malloc for legspace");
1341 for(i=0;i<im->gdes_c;i++){
1344 leg_cc = strlen(im->gdes[i].legend);
1346 /* is there a controle code ant the end of the legend string ? */
1347 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\') {
1348 prt_fctn = im->gdes[i].legend[leg_cc-1];
1350 im->gdes[i].legend[leg_cc] = '\0';
1354 /* remove exess space */
1355 while (prt_fctn=='g' &&
1357 im->gdes[i].legend[leg_cc-1]==' '){
1359 im->gdes[i].legend[leg_cc]='\0';
1362 legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1365 /* no interleg space if string ends in \g */
1366 fill += legspace[i];
1368 if (im->gdes[i].gf != GF_GPRINT &&
1369 im->gdes[i].gf != GF_COMMENT) {
1372 fill += gfx_get_text_width(im->canvas, fill+border,
1373 im->text_prop[TEXT_PROP_LEGEND].font,
1374 im->text_prop[TEXT_PROP_LEGEND].size,
1376 im->gdes[i].legend);
1381 /* who said there was a special tag ... ?*/
1382 if (prt_fctn=='g') {
1385 if (prt_fctn == '\0') {
1386 if (i == im->gdes_c -1 ) prt_fctn ='l';
1388 /* is it time to place the legends ? */
1389 if (fill > im->ximg - 2*border){
1404 if (prt_fctn != '\0'){
1406 if (leg_c >= 2 && prt_fctn == 'j') {
1407 glue = (im->ximg - fill - 2* border) / (leg_c-1);
1411 if (prt_fctn =='c') leg_x = (im->ximg - fill) / 2.0;
1412 if (prt_fctn =='r') leg_x = im->ximg - fill - border;
1414 for(ii=mark;ii<=i;ii++){
1415 if(im->gdes[ii].legend[0]=='\0')
1417 im->gdes[ii].leg_x = leg_x;
1418 im->gdes[ii].leg_y = leg_y;
1420 gfx_get_text_width(im->canvas, leg_x,
1421 im->text_prop[TEXT_PROP_LEGEND].font,
1422 im->text_prop[TEXT_PROP_LEGEND].size,
1424 im->gdes[ii].legend)
1427 if (im->gdes[ii].gf != GF_GPRINT &&
1428 im->gdes[ii].gf != GF_COMMENT)
1431 leg_y = leg_y + im->text_prop[TEXT_PROP_LEGEND].size*1.2;
1432 if (prt_fctn == 's') leg_y -= im->text_prop[TEXT_PROP_LEGEND].size*1.2;
1444 /* create a grid on the graph. it determines what to do
1445 from the values of xsize, start and end */
1447 /* the xaxis labels are determined from the number of seconds per pixel
1448 in the requested graph */
1453 calc_horizontal_grid(image_desc_t *im)
1459 int decimals, fractionals;
1461 im->ygrid_scale.labfact=2;
1463 range = im->maxval - im->minval;
1464 scaledrange = range / im->magfact;
1466 /* does the scale of this graph make it impossible to put lines
1467 on it? If so, give up. */
1468 if (isnan(scaledrange)) {
1472 /* find grid spaceing */
1474 if(isnan(im->ygridstep)){
1475 if(im->extra_flags & ALTYGRID) {
1476 /* find the value with max number of digits. Get number of digits */
1477 decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))));
1478 if(decimals <= 0) /* everything is small. make place for zero */
1481 fractionals = floor(log10(range));
1482 if(fractionals < 0) /* small amplitude. */
1483 sprintf(im->ygrid_scale.labfmt, "%%%d.%df", decimals - fractionals + 1, -fractionals + 1);
1485 sprintf(im->ygrid_scale.labfmt, "%%%d.1f", decimals + 1);
1486 im->ygrid_scale.gridstep = pow((double)10, (double)fractionals);
1487 if(im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1488 im->ygrid_scale.gridstep = 0.1;
1489 /* should have at least 5 lines but no more then 15 */
1490 if(range/im->ygrid_scale.gridstep < 5)
1491 im->ygrid_scale.gridstep /= 10;
1492 if(range/im->ygrid_scale.gridstep > 15)
1493 im->ygrid_scale.gridstep *= 10;
1494 if(range/im->ygrid_scale.gridstep > 5) {
1495 im->ygrid_scale.labfact = 1;
1496 if(range/im->ygrid_scale.gridstep > 8)
1497 im->ygrid_scale.labfact = 2;
1500 im->ygrid_scale.gridstep /= 5;
1501 im->ygrid_scale.labfact = 5;
1505 for(i=0;ylab[i].grid > 0;i++){
1506 pixel = im->ysize / (scaledrange / ylab[i].grid);
1507 if (gridind == -1 && pixel > 5) {
1514 if (pixel * ylab[gridind].lfac[i] >= 2 * im->text_prop[TEXT_PROP_AXIS].size) {
1515 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1520 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1523 im->ygrid_scale.gridstep = im->ygridstep;
1524 im->ygrid_scale.labfact = im->ylabfact;
1529 int draw_horizontal_grid(image_desc_t *im)
1533 char graph_label[100];
1534 double X0=im->xorigin;
1535 double X1=im->xorigin+im->xsize;
1537 int sgrid = (int)( im->minval / im->ygrid_scale.gridstep - 1);
1538 int egrid = (int)( im->maxval / im->ygrid_scale.gridstep + 1);
1539 scaledstep = im->ygrid_scale.gridstep/im->magfact;
1540 for (i = sgrid; i <= egrid; i++){
1541 double Y0=ytr(im,im->ygrid_scale.gridstep*i);
1542 if ( Y0 >= im->yorigin-im->ysize
1543 && Y0 <= im->yorigin){
1544 if(i % im->ygrid_scale.labfact == 0){
1545 if (i==0 || im->symbol == ' ') {
1547 if(im->extra_flags & ALTYGRID) {
1548 sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*i);
1551 sprintf(graph_label,"%4.1f",scaledstep*i);
1554 sprintf(graph_label,"%4.0f",scaledstep*i);
1558 sprintf(graph_label,"%4.1f %c",scaledstep*i, im->symbol);
1560 sprintf(graph_label,"%4.0f %c",scaledstep*i, im->symbol);
1564 gfx_new_text ( im->canvas,
1565 X0-im->text_prop[TEXT_PROP_AXIS].size/1.5, Y0,
1566 im->graph_col[GRC_FONT],
1567 im->text_prop[TEXT_PROP_AXIS].font,
1568 im->text_prop[TEXT_PROP_AXIS].size,
1569 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1571 gfx_new_line ( im->canvas,
1574 MGRIDWIDTH, im->graph_col[GRC_MGRID] );
1577 gfx_new_line ( im->canvas,
1580 GRIDWIDTH, im->graph_col[GRC_GRID] );
1588 /* logaritmic horizontal grid */
1590 horizontal_log_grid(image_desc_t *im)
1594 int minoridx=0, majoridx=0;
1595 char graph_label[100];
1597 double value, pixperstep, minstep;
1599 /* find grid spaceing */
1600 pixpex= (double)im->ysize / (log10(im->maxval) - log10(im->minval));
1602 if (isnan(pixpex)) {
1606 for(i=0;yloglab[i][0] > 0;i++){
1607 minstep = log10(yloglab[i][0]);
1608 for(ii=1;yloglab[i][ii+1] > 0;ii++){
1609 if(yloglab[i][ii+2]==0){
1610 minstep = log10(yloglab[i][ii+1])-log10(yloglab[i][ii]);
1614 pixperstep = pixpex * minstep;
1615 if(pixperstep > 5){minoridx = i;}
1616 if(pixperstep > 2 * im->text_prop[TEXT_PROP_LEGEND].size){majoridx = i;}
1620 X1=im->xorigin+im->xsize;
1621 /* paint minor grid */
1622 for (value = pow((double)10, log10(im->minval)
1623 - fmod(log10(im->minval),log10(yloglab[minoridx][0])));
1624 value <= im->maxval;
1625 value *= yloglab[minoridx][0]){
1626 if (value < im->minval) continue;
1628 while(yloglab[minoridx][++i] > 0){
1629 Y0 = ytr(im,value * yloglab[minoridx][i]);
1630 if (Y0 <= im->yorigin - im->ysize) break;
1631 gfx_new_line ( im->canvas,
1634 GRIDWIDTH, im->graph_col[GRC_GRID] );
1638 /* paint major grid and labels*/
1639 for (value = pow((double)10, log10(im->minval)
1640 - fmod(log10(im->minval),log10(yloglab[majoridx][0])));
1641 value <= im->maxval;
1642 value *= yloglab[majoridx][0]){
1643 if (value < im->minval) continue;
1645 while(yloglab[majoridx][++i] > 0){
1646 Y0 = ytr(im,value * yloglab[majoridx][i]);
1647 if (Y0 <= im->yorigin - im->ysize) break;
1648 gfx_new_line ( im->canvas,
1651 MGRIDWIDTH, im->graph_col[GRC_MGRID] );
1653 sprintf(graph_label,"%3.0e",value * yloglab[majoridx][i]);
1654 gfx_new_text ( im->canvas,
1655 X0-im->text_prop[TEXT_PROP_AXIS].size/1.5, Y0,
1656 im->graph_col[GRC_FONT],
1657 im->text_prop[TEXT_PROP_AXIS].font,
1658 im->text_prop[TEXT_PROP_AXIS].size,
1659 im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1671 int xlab_sel; /* which sort of label and grid ? */
1674 char graph_label[100];
1675 double X0,Y0,Y1; /* points for filled graph and more*/
1678 /* the type of time grid is determined by finding
1679 the number of seconds per pixel in the graph */
1682 if(im->xlab_user.minsec == -1){
1683 factor=(im->end - im->start)/im->xsize;
1685 while ( xlab[xlab_sel+1].minsec != -1
1686 && xlab[xlab_sel+1].minsec <= factor){ xlab_sel++; }
1687 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
1688 im->xlab_user.gridst = xlab[xlab_sel].gridst;
1689 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
1690 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
1691 im->xlab_user.labtm = xlab[xlab_sel].labtm;
1692 im->xlab_user.labst = xlab[xlab_sel].labst;
1693 im->xlab_user.precis = xlab[xlab_sel].precis;
1694 im->xlab_user.stst = xlab[xlab_sel].stst;
1697 /* y coords are the same for every line ... */
1699 Y1 = im->yorigin-im->ysize;
1702 /* paint the minor grid */
1703 for(ti = find_first_time(im->start,
1704 im->xlab_user.gridtm,
1705 im->xlab_user.gridst);
1707 ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
1709 /* are we inside the graph ? */
1710 if (ti < im->start || ti > im->end) continue;
1712 gfx_new_line(im->canvas,X0,Y0+1, X0,Y1-1,GRIDWIDTH, im->graph_col[GRC_GRID]);
1716 /* paint the major grid */
1717 for(ti = find_first_time(im->start,
1718 im->xlab_user.mgridtm,
1719 im->xlab_user.mgridst);
1721 ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
1723 /* are we inside the graph ? */
1724 if (ti < im->start || ti > im->end) continue;
1726 gfx_new_line(im->canvas,X0,Y0+2, X0,Y1-2,MGRIDWIDTH, im->graph_col[GRC_MGRID]);
1729 /* paint the labels below the graph */
1730 for(ti = find_first_time(im->start,
1731 im->xlab_user.labtm,
1732 im->xlab_user.labst);
1734 ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
1736 tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
1737 /* are we inside the graph ? */
1738 if (ti < im->start || ti > im->end) continue;
1741 strftime(graph_label,99,im->xlab_user.stst,localtime(&tilab));
1743 # error "your libc has no strftime I guess we'll abort the exercise here."
1745 gfx_new_text ( im->canvas,
1746 xtr(im,tilab), Y0+im->text_prop[TEXT_PROP_AXIS].size/1.5,
1747 im->graph_col[GRC_FONT],
1748 im->text_prop[TEXT_PROP_AXIS].font,
1749 im->text_prop[TEXT_PROP_AXIS].size,
1750 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP,
1763 /* draw x and y axis */
1764 gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
1765 im->xorigin+im->xsize,im->yorigin-im->ysize,
1766 GRIDWIDTH, im->graph_col[GRC_GRID]);
1768 gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
1769 im->xorigin+im->xsize,im->yorigin-im->ysize,
1770 GRIDWIDTH, im->graph_col[GRC_GRID]);
1772 gfx_new_line ( im->canvas, im->xorigin-4,im->yorigin,
1773 im->xorigin+im->xsize+4,im->yorigin,
1774 MGRIDWIDTH, im->graph_col[GRC_GRID]);
1776 gfx_new_line ( im->canvas, im->xorigin,im->yorigin+4,
1777 im->xorigin,im->yorigin-im->ysize-4,
1778 MGRIDWIDTH, im->graph_col[GRC_GRID]);
1781 /* arrow for X axis direction */
1782 gfx_new_area ( im->canvas,
1783 im->xorigin+im->xsize+3, im->yorigin-3,
1784 im->xorigin+im->xsize+3, im->yorigin+4,
1785 im->xorigin+im->xsize+8, im->yorigin+0.5, /* LINEOFFSET */
1786 im->graph_col[GRC_ARROW]);
1793 grid_paint(image_desc_t *im)
1797 double X0,Y0; /* points for filled graph and more*/
1800 /* draw 3d border */
1801 node = gfx_new_area (im->canvas, 0,im->yimg,
1803 2,2,im->graph_col[GRC_SHADEA]);
1804 gfx_add_point( node , im->ximg - 2, 2 );
1805 gfx_add_point( node , im->ximg, 0 );
1806 gfx_add_point( node , 0,0 );
1807 /* gfx_add_point( node , 0,im->yimg ); */
1809 node = gfx_new_area (im->canvas, 2,im->yimg-2,
1810 im->ximg-2,im->yimg-2,
1812 im->graph_col[GRC_SHADEB]);
1813 gfx_add_point( node , im->ximg,0);
1814 gfx_add_point( node , im->ximg,im->yimg);
1815 gfx_add_point( node , 0,im->yimg);
1816 /* gfx_add_point( node , 0,im->yimg ); */
1819 if (im->draw_x_grid == 1 )
1822 if (im->draw_y_grid == 1){
1823 if(im->logarithmic){
1824 res = horizontal_log_grid(im);
1826 res = draw_horizontal_grid(im);
1829 /* dont draw horizontal grid if there is no min and max val */
1831 char *nodata = "No Data found";
1832 gfx_new_text(im->canvas,im->ximg/2, (2*im->yorigin-im->ysize) / 2,
1833 im->graph_col[GRC_FONT],
1834 im->text_prop[TEXT_PROP_AXIS].font,
1835 im->text_prop[TEXT_PROP_AXIS].size,
1836 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
1841 /* yaxis description */
1842 if (im->canvas->imgformat != IF_PNG) {
1843 gfx_new_text( im->canvas,
1844 7, (im->yorigin - im->ysize/2),
1845 im->graph_col[GRC_FONT],
1846 im->text_prop[TEXT_PROP_AXIS].font,
1847 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 270.0,
1848 GFX_H_CENTER, GFX_V_CENTER,
1851 /* horrible hack until we can actually print vertically */
1854 int l=strlen(im->ylegend);
1856 for (n=0;n<strlen(im->ylegend);n++) {
1857 s[0]=im->ylegend[n];
1859 gfx_new_text(im->canvas,7,im->text_prop[TEXT_PROP_AXIS].size*(l-n),
1860 im->graph_col[GRC_FONT],
1861 im->text_prop[TEXT_PROP_AXIS].font,
1862 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 270.0,
1863 GFX_H_CENTER, GFX_V_CENTER,
1870 gfx_new_text( im->canvas,
1871 im->ximg/2, im->text_prop[TEXT_PROP_TITLE].size,
1872 im->graph_col[GRC_FONT],
1873 im->text_prop[TEXT_PROP_TITLE].font,
1874 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
1875 GFX_H_CENTER, GFX_V_CENTER,
1879 if( !(im->extra_flags & NOLEGEND) ) {
1880 for(i=0;i<im->gdes_c;i++){
1881 if(im->gdes[i].legend[0] =='\0')
1884 /* im->gdes[i].leg_y is the bottom of the legend */
1885 X0 = im->gdes[i].leg_x;
1886 Y0 = im->gdes[i].leg_y;
1888 if ( im->gdes[i].gf != GF_GPRINT
1889 && im->gdes[i].gf != GF_COMMENT) {
1892 boxH = gfx_get_text_width(im->canvas, 0,
1893 im->text_prop[TEXT_PROP_AXIS].font,
1894 im->text_prop[TEXT_PROP_AXIS].size,
1895 im->tabwidth,"M") * 1.25;
1898 node = gfx_new_area(im->canvas,
1903 gfx_add_point ( node, X0+boxH, Y0-boxV );
1904 node = gfx_new_line(im->canvas,
1907 gfx_add_point(node,X0+boxH,Y0);
1908 gfx_add_point(node,X0+boxH,Y0-boxV);
1909 gfx_close_path(node);
1910 X0 += boxH / 1.25 * 2;
1912 gfx_new_text ( im->canvas, X0, Y0,
1913 im->graph_col[GRC_FONT],
1914 im->text_prop[TEXT_PROP_AXIS].font,
1915 im->text_prop[TEXT_PROP_AXIS].size,
1916 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
1917 im->gdes[i].legend );
1923 /*****************************************************
1924 * lazy check make sure we rely need to create this graph
1925 *****************************************************/
1927 int lazy_check(image_desc_t *im){
1930 struct stat imgstat;
1932 if (im->lazy == 0) return 0; /* no lazy option */
1933 if (stat(im->graphfile,&imgstat) != 0)
1934 return 0; /* can't stat */
1935 /* one pixel in the existing graph is more then what we would
1937 if (time(NULL) - imgstat.st_mtime >
1938 (im->end - im->start) / im->xsize)
1940 if ((fd = fopen(im->graphfile,"rb")) == NULL)
1941 return 0; /* the file does not exist */
1942 switch (im->canvas->imgformat) {
1944 size = PngSize(fd,&(im->ximg),&(im->yimg));
1954 pie_part(image_desc_t *im, gfx_color_t color,
1955 double PieCenterX, double PieCenterY, double Radius,
1956 double startangle, double endangle)
1960 double step=M_PI/50; /* Number of iterations for the circle;
1961 ** 10 is definitely too low, more than
1962 ** 50 seems to be overkill
1965 /* Strange but true: we have to work clockwise or else
1966 ** anti aliasing nor transparency don't work.
1968 ** This test is here to make sure we do it right, also
1969 ** this makes the for...next loop more easy to implement.
1970 ** The return will occur if the user enters a negative number
1971 ** (which shouldn't be done according to the specs) or if the
1972 ** programmers do something wrong (which, as we all know, never
1973 ** happens anyway :)
1975 if (endangle<startangle) return;
1977 /* Hidden feature: Radius decreases each full circle */
1979 while (angle>=2*M_PI) {
1984 node=gfx_new_area(im->canvas,
1985 PieCenterX+sin(startangle)*Radius,
1986 PieCenterY-cos(startangle)*Radius,
1989 PieCenterX+sin(endangle)*Radius,
1990 PieCenterY-cos(endangle)*Radius,
1992 for (angle=endangle;angle-startangle>=step;angle-=step) {
1994 PieCenterX+sin(angle)*Radius,
1995 PieCenterY-cos(angle)*Radius );
2000 graph_size_location(image_desc_t *im, int elements, int piechart )
2002 /* The actual size of the image to draw is determined from
2003 ** several sources. The size given on the command line is
2004 ** the graph area but we need more as we have to draw labels
2005 ** and other things outside the graph area
2008 /* +-+-------------------------------------------+
2009 ** |l|.................title.....................|
2010 ** |e+--+-------------------------------+--------+
2013 ** |l| l| main graph area | chart |
2016 ** |r+--+-------------------------------+--------+
2017 ** |e| | x-axis labels | |
2018 ** |v+--+-------------------------------+--------+
2019 ** | |..............legends......................|
2020 ** +-+-------------------------------------------+
2022 int Xvertical=0, Yvertical=0,
2023 Xtitle =0, Ytitle =0,
2024 Xylabel =0, Yylabel =0,
2027 Xxlabel =0, Yxlabel =0,
2029 Xlegend =0, Ylegend =0,
2031 Xspacing =10, Yspacing =10;
2033 if (im->ylegend[0] != '\0') {
2034 Xvertical = im->text_prop[TEXT_PROP_LEGEND].size *2;
2035 Yvertical = im->text_prop[TEXT_PROP_LEGEND].size * (strlen(im->ylegend)+1);
2038 if (im->title[0] != '\0') {
2039 /* The title is placed "inbetween" two text lines so it
2040 ** automatically has some vertical spacing. The horizontal
2041 ** spacing is added here, on each side.
2043 Xtitle = gfx_get_text_width(im->canvas, 0,
2044 im->text_prop[TEXT_PROP_TITLE].font,
2045 im->text_prop[TEXT_PROP_TITLE].size,
2047 im->title) + 2*Xspacing;
2048 Ytitle = im->text_prop[TEXT_PROP_TITLE].size*2;
2054 if (im->draw_x_grid) {
2056 Yxlabel=im->text_prop[TEXT_PROP_LEGEND].size *2;
2058 if (im->draw_y_grid) {
2059 Xylabel=im->text_prop[TEXT_PROP_LEGEND].size *6;
2065 im->piesize=im->xsize<im->ysize?im->xsize:im->ysize;
2070 /* Now calculate the total size. Insert some spacing where
2071 desired. im->xorigin and im->yorigin need to correspond
2072 with the lower left corner of the main graph area or, if
2073 this one is not set, the imaginary box surrounding the
2076 /* The legend width cannot yet be determined, as a result we
2077 ** have problems adjusting the image to it. For now, we just
2078 ** forget about it at all; the legend will have to fit in the
2079 ** size already allocated.
2081 im->ximg = Xylabel + Xmain + Xpie + Xspacing;
2082 if (Xmain) im->ximg += Xspacing;
2083 if (Xpie) im->ximg += Xspacing;
2084 im->xorigin = Xspacing + Xylabel;
2085 if (Xtitle > im->ximg) im->ximg = Xtitle;
2087 im->ximg += Xvertical;
2088 im->xorigin += Xvertical;
2092 /* The vertical size is interesting... we need to compare
2093 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend} with Yvertical
2094 ** however we need to know {Ytitle+Ymain+Yxlabel} in order to
2095 ** start even thinking about Ylegend.
2097 ** Do it in three portions: First calculate the inner part,
2098 ** then do the legend, then adjust the total height of the img.
2101 /* reserve space for main and/or pie */
2102 im->yimg = Ymain + Yxlabel;
2103 if (im->yimg < Ypie) im->yimg = Ypie;
2104 im->yorigin = im->yimg - Yxlabel;
2105 /* reserve space for the title *or* some padding above the graph */
2108 im->yorigin += Ytitle;
2110 im->yimg += Yspacing;
2111 im->yorigin += Yspacing;
2113 /* reserve space for padding below the graph */
2114 im->yimg += Yspacing;
2117 /* Determine where to place the legends onto the image.
2118 ** Adjust im->yimg to match the space requirements.
2120 if(leg_place(im)==-1)
2123 /* last of three steps: check total height of image */
2124 if (im->yimg < Yvertical) im->yimg = Yvertical;
2127 if (Xlegend > im->ximg) {
2129 /* reposition Pie */
2132 /* The pie is placed in the upper right hand corner,
2133 ** just below the title (if any) and with sufficient
2137 im->pie_x = im->ximg - Xspacing - Xpie/2;
2138 im->pie_y = im->yorigin-Ymain+Ypie/2;
2140 im->pie_x = im->ximg/2;
2141 im->pie_y = im->yorigin-Ypie/2;
2147 /* draw that picture thing ... */
2149 graph_paint(image_desc_t *im, char ***calcpr)
2152 int lazy = lazy_check(im);
2154 double PieStart=0.0;
2158 double areazero = 0.0;
2159 enum gf_en stack_gf = GF_PRINT;
2160 graph_desc_t *lastgdes = NULL;
2162 /* if we are lazy and there is nothing to PRINT ... quit now */
2163 if (lazy && im->prt_c==0) return 0;
2165 /* pull the data from the rrd files ... */
2167 if(data_fetch(im)==-1)
2170 /* evaluate VDEF and CDEF operations ... */
2171 if(data_calc(im)==-1)
2174 /* check if we need to draw a piechart */
2175 for(i=0;i<im->gdes_c;i++){
2176 if (im->gdes[i].gf == GF_PART) {
2182 /* calculate and PRINT and GPRINT definitions. We have to do it at
2183 * this point because it will affect the length of the legends
2184 * if there are no graph elements we stop here ...
2185 * if we are lazy, try to quit ...
2187 i=print_calc(im,calcpr);
2189 if(((i==0)&&(piechart==0)) || lazy) return 0;
2191 /* If there's only the pie chart to draw, signal this */
2192 if (i==0) piechart=2;
2194 /* get actual drawing data and find min and max values*/
2195 if(data_proc(im)==-1)
2198 if(!im->logarithmic){si_unit(im);} /* identify si magnitude Kilo, Mega Giga ? */
2200 if(!im->rigid && ! im->logarithmic)
2201 expand_range(im); /* make sure the upper and lower limit are
2204 if (!calc_horizontal_grid(im))
2209 /**************************************************************
2210 *** Calculating sizes and locations became a bit confusing ***
2211 *** so I moved this into a separate function. ***
2212 **************************************************************/
2213 if(graph_size_location(im,i,piechart)==-1)
2216 /* the actual graph is created by going through the individual
2217 graph elements and then drawing them */
2219 node=gfx_new_area ( im->canvas,
2223 im->graph_col[GRC_BACK]);
2225 gfx_add_point(node,0, im->yimg);
2227 if (piechart != 2) {
2228 node=gfx_new_area ( im->canvas,
2229 im->xorigin, im->yorigin,
2230 im->xorigin + im->xsize, im->yorigin,
2231 im->xorigin + im->xsize, im->yorigin-im->ysize,
2232 im->graph_col[GRC_CANVAS]);
2234 gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
2236 if (im->minval > 0.0)
2237 areazero = im->minval;
2238 if (im->maxval < 0.0)
2239 areazero = im->maxval;
2245 pie_part(im,im->graph_col[GRC_CANVAS],im->pie_x,im->pie_y,im->piesize*0.5,0,2*M_PI);
2248 for(i=0;i<im->gdes_c;i++){
2249 switch(im->gdes[i].gf){
2260 for (ii = 0; ii < im->xsize; ii++)
2262 if (!isnan(im->gdes[i].p_data[ii]) &&
2263 im->gdes[i].p_data[ii] > 0.0)
2265 /* generate a tick */
2266 gfx_new_line(im->canvas, im -> xorigin + ii,
2267 im -> yorigin - (im -> gdes[i].yrule * im -> ysize),
2271 im -> gdes[i].col );
2277 stack_gf = im->gdes[i].gf;
2279 /* fix data points at oo and -oo */
2280 for(ii=0;ii<im->xsize;ii++){
2281 if (isinf(im->gdes[i].p_data[ii])){
2282 if (im->gdes[i].p_data[ii] > 0) {
2283 im->gdes[i].p_data[ii] = im->maxval ;
2285 im->gdes[i].p_data[ii] = im->minval ;
2291 if (im->gdes[i].col != 0x0){
2292 /* GF_LINE and friend */
2293 if(stack_gf == GF_LINE ){
2295 for(ii=1;ii<im->xsize;ii++){
2296 if ( ! isnan(im->gdes[i].p_data[ii-1])
2297 && ! isnan(im->gdes[i].p_data[ii])){
2299 node = gfx_new_line(im->canvas,
2300 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2301 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2302 im->gdes[i].linewidth,
2305 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2314 for(ii=1;ii<im->xsize;ii++){
2316 if ( ! isnan(im->gdes[i].p_data[ii-1])
2317 && ! isnan(im->gdes[i].p_data[ii])){
2320 if (im->gdes[i].gf == GF_STACK) {
2321 ybase = ytr(im,lastgdes->p_data[ii-1]);
2323 ybase = ytr(im,areazero);
2326 node = gfx_new_area(im->canvas,
2327 ii-1+im->xorigin,ybase,
2328 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2329 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2333 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2337 if ( node != NULL && (ii+1==im->xsize || isnan(im->gdes[i].p_data[ii]) )){
2338 /* GF_AREA STACK type*/
2339 if (im->gdes[i].gf == GF_STACK ) {
2341 for (iii=ii-1;iii>area_start;iii--){
2342 gfx_add_point(node,iii+im->xorigin,ytr(im,lastgdes->p_data[iii]));
2345 gfx_add_point(node,ii+im->xorigin,ytr(im,areazero));
2350 } /* else GF_LINE */
2351 } /* if color != 0x0 */
2352 /* make sure we do not run into trouble when stacking on NaN */
2353 for(ii=0;ii<im->xsize;ii++){
2354 if (isnan(im->gdes[i].p_data[ii])) {
2357 ybase = ytr(im,lastgdes->p_data[ii-1]);
2359 if (isnan(ybase) || !lastgdes ){
2360 ybase = ytr(im,areazero);
2362 im->gdes[i].p_data[ii] = ybase;
2365 lastgdes = &(im->gdes[i]);
2368 if(isnan(im->gdes[i].yrule)) /* fetch variable */
2369 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2371 if (finite(im->gdes[i].yrule)) { /* even the fetched var can be NaN */
2372 pie_part(im,im->gdes[i].col,
2373 im->pie_x,im->pie_y,im->piesize*0.4,
2374 M_PI*2.0*PieStart/100.0,
2375 M_PI*2.0*(PieStart+im->gdes[i].yrule)/100.0);
2376 PieStart += im->gdes[i].yrule;
2385 /* grid_paint also does the text */
2388 /* the RULES are the last thing to paint ... */
2389 for(i=0;i<im->gdes_c;i++){
2391 switch(im->gdes[i].gf){
2393 if(isnan(im->gdes[i].yrule)) { /* fetch variable */
2394 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2396 if(im->gdes[i].yrule >= im->minval
2397 && im->gdes[i].yrule <= im->maxval)
2398 gfx_new_line(im->canvas,
2399 im->xorigin,ytr(im,im->gdes[i].yrule),
2400 im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2401 1.0,im->gdes[i].col);
2404 if(im->gdes[i].xrule == 0) { /* fetch variable */
2405 im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
2407 if(im->gdes[i].xrule >= im->start
2408 && im->gdes[i].xrule <= im->end)
2409 gfx_new_line(im->canvas,
2410 xtr(im,im->gdes[i].xrule),im->yorigin,
2411 xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2412 1.0,im->gdes[i].col);
2420 if (strcmp(im->graphfile,"-")==0) {
2422 /* Change translation mode for stdout to BINARY */
2423 _setmode( _fileno( stdout ), O_BINARY );
2427 if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2428 rrd_set_error("Opening '%s' for write: %s",im->graphfile,
2433 gfx_render (im->canvas,im->ximg,im->yimg,0x0,fo);
2434 if (strcmp(im->graphfile,"-") != 0)
2440 /*****************************************************
2442 *****************************************************/
2445 gdes_alloc(image_desc_t *im){
2447 long def_step = (im->end-im->start)/im->xsize;
2449 if (im->step > def_step) /* step can be increassed ... no decreassed */
2450 def_step = im->step;
2454 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2455 * sizeof(graph_desc_t)))==NULL){
2456 rrd_set_error("realloc graph_descs");
2461 im->gdes[im->gdes_c-1].step=def_step;
2462 im->gdes[im->gdes_c-1].start=im->start;
2463 im->gdes[im->gdes_c-1].end=im->end;
2464 im->gdes[im->gdes_c-1].vname[0]='\0';
2465 im->gdes[im->gdes_c-1].data=NULL;
2466 im->gdes[im->gdes_c-1].ds_namv=NULL;
2467 im->gdes[im->gdes_c-1].data_first=0;
2468 im->gdes[im->gdes_c-1].p_data=NULL;
2469 im->gdes[im->gdes_c-1].rpnp=NULL;
2470 im->gdes[im->gdes_c-1].col = 0x0;
2471 im->gdes[im->gdes_c-1].legend[0]='\0';
2472 im->gdes[im->gdes_c-1].rrd[0]='\0';
2473 im->gdes[im->gdes_c-1].ds=-1;
2474 im->gdes[im->gdes_c-1].p_data=NULL;
2478 /* copies input untill the first unescaped colon is found
2479 or until input ends. backslashes have to be escaped as well */
2481 scan_for_col(char *input, int len, char *output)
2486 input[inp] != ':' &&
2489 if (input[inp] == '\\' &&
2490 input[inp+1] != '\0' &&
2491 (input[inp+1] == '\\' ||
2492 input[inp+1] == ':')){
2493 output[outp++] = input[++inp];
2496 output[outp++] = input[inp];
2499 output[outp] = '\0';
2503 /* Some surgery done on this function, it became ridiculously big.
2505 ** - initializing now in rrd_graph_init()
2506 ** - options parsing now in rrd_graph_options()
2507 ** - script parsing now in rrd_graph_script()
2510 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize)
2517 #ifdef HAVE_SETLOCALE
2518 setlocale(LC_ALL,"");
2522 rrd_graph_init(&im);
2524 rrd_graph_options(argc,argv,&im);
2525 if (rrd_test_error()) return -1;
2527 if (strlen(argv[optind])>=MAXPATH) {
2528 rrd_set_error("filename (including path) too long");
2531 strncpy(im.graphfile,argv[optind],MAXPATH-1);
2532 im.graphfile[MAXPATH-1]='\0';
2534 rrd_graph_script(argc,argv,&im);
2535 if (rrd_test_error()) return -1;
2537 /* Everything is now read and the actual work can start */
2540 if (graph_paint(&im,prdata)==-1){
2545 /* The image is generated and needs to be output.
2546 ** Also, if needed, print a line with information about the image.
2554 /* maybe prdata is not allocated yet ... lets do it now */
2555 if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
2556 rrd_set_error("malloc imginfo");
2560 if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
2562 rrd_set_error("malloc imginfo");
2565 filename=im.graphfile+strlen(im.graphfile);
2566 while(filename > im.graphfile) {
2567 if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
2571 sprintf((*prdata)[0],im.imginfo,filename,(long)(im.canvas->zoom*im.ximg),(long)(im.canvas->zoom*im.yimg));
2578 rrd_graph_init(image_desc_t *im)
2582 im->xlab_user.minsec = -1;
2588 im->ylegend[0] = '\0';
2589 im->title[0] = '\0';
2592 im->unitsexponent= 9999;
2598 im->logarithmic = 0;
2599 im->ygridstep = DNAN;
2600 im->draw_x_grid = 1;
2601 im->draw_y_grid = 1;
2606 im->canvas = gfx_new_canvas();
2608 for(i=0;i<DIM(graph_col);i++)
2609 im->graph_col[i]=graph_col[i];
2611 for(i=0;i<DIM(text_prop);i++){
2612 im->text_prop[i].size = text_prop[i].size;
2613 im->text_prop[i].font = text_prop[i].font;
2618 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
2621 char *parsetime_error = NULL;
2622 char scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
2623 time_t start_tmp=0,end_tmp=0;
2625 struct time_value start_tv, end_tv;
2628 parsetime("end-24h", &start_tv);
2629 parsetime("now", &end_tv);
2632 static struct option long_options[] =
2634 {"start", required_argument, 0, 's'},
2635 {"end", required_argument, 0, 'e'},
2636 {"x-grid", required_argument, 0, 'x'},
2637 {"y-grid", required_argument, 0, 'y'},
2638 {"vertical-label",required_argument,0,'v'},
2639 {"width", required_argument, 0, 'w'},
2640 {"height", required_argument, 0, 'h'},
2641 {"interlaced", no_argument, 0, 'i'},
2642 {"upper-limit",required_argument, 0, 'u'},
2643 {"lower-limit",required_argument, 0, 'l'},
2644 {"rigid", no_argument, 0, 'r'},
2645 {"base", required_argument, 0, 'b'},
2646 {"logarithmic",no_argument, 0, 'o'},
2647 {"color", required_argument, 0, 'c'},
2648 {"font", required_argument, 0, 'n'},
2649 {"title", required_argument, 0, 't'},
2650 {"imginfo", required_argument, 0, 'f'},
2651 {"imgformat", required_argument, 0, 'a'},
2652 {"lazy", no_argument, 0, 'z'},
2653 {"zoom", required_argument, 0, 'm'},
2654 {"no-legend", no_argument, 0, 'g'},
2655 {"alt-y-grid", no_argument, 0, 257 },
2656 {"alt-autoscale", no_argument, 0, 258 },
2657 {"alt-autoscale-max", no_argument, 0, 259 },
2658 {"units-exponent",required_argument, 0, 260},
2659 {"step", required_argument, 0, 261},
2660 {"no-gridfit", no_argument, 0, 262},
2662 int option_index = 0;
2666 opt = getopt_long(argc, argv,
2667 "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:z:g",
2668 long_options, &option_index);
2675 im->extra_flags |= ALTYGRID;
2678 im->extra_flags |= ALTAUTOSCALE;
2681 im->extra_flags |= ALTAUTOSCALE_MAX;
2684 im->extra_flags |= NOLEGEND;
2687 im->unitsexponent = atoi(optarg);
2690 im->step = atoi(optarg);
2696 if ((parsetime_error = parsetime(optarg, &start_tv))) {
2697 rrd_set_error( "start time: %s", parsetime_error );
2702 if ((parsetime_error = parsetime(optarg, &end_tv))) {
2703 rrd_set_error( "end time: %s", parsetime_error );
2708 if(strcmp(optarg,"none") == 0){
2714 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
2716 &im->xlab_user.gridst,
2718 &im->xlab_user.mgridst,
2720 &im->xlab_user.labst,
2721 &im->xlab_user.precis,
2722 &stroff) == 7 && stroff != 0){
2723 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
2724 if((im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
2725 rrd_set_error("unknown keyword %s",scan_gtm);
2727 } else if ((im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
2728 rrd_set_error("unknown keyword %s",scan_mtm);
2730 } else if ((im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
2731 rrd_set_error("unknown keyword %s",scan_ltm);
2734 im->xlab_user.minsec = 1;
2735 im->xlab_user.stst = im->xlab_form;
2737 rrd_set_error("invalid x-grid format");
2743 if(strcmp(optarg,"none") == 0){
2751 &im->ylabfact) == 2) {
2752 if(im->ygridstep<=0){
2753 rrd_set_error("grid step must be > 0");
2755 } else if (im->ylabfact < 1){
2756 rrd_set_error("label factor must be > 0");
2760 rrd_set_error("invalid y-grid format");
2765 strncpy(im->ylegend,optarg,150);
2766 im->ylegend[150]='\0';
2769 im->maxval = atof(optarg);
2772 im->minval = atof(optarg);
2775 im->base = atol(optarg);
2776 if(im->base != 1024 && im->base != 1000 ){
2777 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
2782 long_tmp = atol(optarg);
2783 if (long_tmp < 10) {
2784 rrd_set_error("width below 10 pixels");
2787 im->xsize = long_tmp;
2790 long_tmp = atol(optarg);
2791 if (long_tmp < 10) {
2792 rrd_set_error("height below 10 pixels");
2795 im->ysize = long_tmp;
2798 im->canvas->interlaced = 1;
2804 im->imginfo = optarg;
2807 if((im->canvas->imgformat = if_conv(optarg)) == -1) {
2808 rrd_set_error("unsupported graphics format '%s'",optarg);
2816 im->logarithmic = 1;
2817 if (isnan(im->minval))
2823 col_nam,&color) == 2){
2825 if((ci=grc_conv(col_nam)) != -1){
2826 im->graph_col[ci]=color;
2828 rrd_set_error("invalid color name '%s'",col_nam);
2831 rrd_set_error("invalid color def format");
2836 /* originally this used char *prop = "" and
2837 ** char *font = "dummy" however this results
2838 ** in a SEG fault, at least on RH7.1
2840 ** The current implementation isn't proper
2841 ** either, font is never freed and prop uses
2842 ** a fixed width string
2851 prop,&size,font) == 3){
2853 if((sindex=text_prop_conv(prop)) != -1){
2854 im->text_prop[sindex].size=size;
2855 im->text_prop[sindex].font=font;
2856 if (sindex==0) { /* the default */
2857 im->text_prop[TEXT_PROP_TITLE].size=size;
2858 im->text_prop[TEXT_PROP_TITLE].font=font;
2859 im->text_prop[TEXT_PROP_AXIS].size=size;
2860 im->text_prop[TEXT_PROP_AXIS].font=font;
2861 im->text_prop[TEXT_PROP_UNIT].size=size;
2862 im->text_prop[TEXT_PROP_UNIT].font=font;
2863 im->text_prop[TEXT_PROP_LEGEND].size=size;
2864 im->text_prop[TEXT_PROP_LEGEND].font=font;
2867 rrd_set_error("invalid fonttag '%s'",prop);
2871 rrd_set_error("invalid text property format");
2877 im->canvas->zoom = atof(optarg);
2878 if (im->canvas->zoom <= 0.0) {
2879 rrd_set_error("zoom factor must be > 0");
2884 strncpy(im->title,optarg,150);
2885 im->title[150]='\0';
2890 rrd_set_error("unknown option '%c'", optopt);
2892 rrd_set_error("unknown option '%s'",argv[optind-1]);
2897 if (optind >= argc) {
2898 rrd_set_error("missing filename");
2902 if (im->logarithmic == 1 && (im->minval <= 0 || isnan(im->minval))){
2903 rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");
2907 if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
2908 /* error string is set in parsetime.c */
2912 if (start_tmp < 3600*24*365*10){
2913 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
2917 if (end_tmp < start_tmp) {
2918 rrd_set_error("start (%ld) should be less than end (%ld)",
2919 start_tmp, end_tmp);
2923 im->start = start_tmp;
2928 rrd_graph_script(int argc, char *argv[], image_desc_t *im)
2932 int linepass = 0; /* stack must follow LINE*, AREA or STACK */
2934 for (i=optind+1;i<argc;i++) {
2939 char funcname[10],vname[MAX_VNAME_LEN+1],sep[1];
2944 /* Each command is one element from *argv[], we call this "line".
2946 ** Each command defines the most current gdes inside struct im.
2947 ** In stead of typing "im->gdes[im->gdes_c-1]" we use "gdp".
2950 gdp=&im->gdes[im->gdes_c-1];
2953 /* function:newvname=string[:ds-name:CF] for xDEF
2954 ** function:vname[#color[:string]] for LINEx,AREA,STACK
2955 ** function:vname#color[:num[:string]] for TICK
2956 ** function:vname-or-num#color[:string] for xRULE,PART
2957 ** function:vname:CF:string for xPRINT
2958 ** function:string for COMMENT
2962 sscanf(line, "%10[A-Z0-9]:%n", funcname,&argstart);
2964 rrd_set_error("Cannot parse function in line: %s",line);
2968 if(sscanf(funcname,"LINE%lf",&linewidth)){
2969 im->gdes[im->gdes_c-1].gf = GF_LINE;
2970 im->gdes[im->gdes_c-1].linewidth = linewidth;
2972 if ((gdp->gf=gf_conv(funcname))==-1) {
2973 rrd_set_error("'%s' is not a valid function name",funcname);
2979 /* If the error string is set, we exit at the end of the switch */
2982 if (rrd_graph_legend(gdp,&line[argstart])==0)
2983 rrd_set_error("Cannot parse comment in line: %s",line);
2989 sscanf(&line[argstart], "%lf%n#%n", &d, &j, &k);
2990 sscanf(&line[argstart], DEF_NAM_FMT "%n#%n", vname, &l, &m);
2992 rrd_set_error("Cannot parse name or num in line: %s",line);
2999 } else if (!rrd_graph_check_vname(im,vname,line)) {
3003 } else break; /* exit due to wrong vname */
3004 if ((j=rrd_graph_color(im,&line[argstart],line,0))==0) break;
3006 if (strlen(&line[argstart])!=0) {
3007 if (rrd_graph_legend(gdp,&line[++argstart])==0)
3008 rrd_set_error("Cannot parse comment in line: %s",line);
3013 rrd_set_error("STACK must follow another graphing element");
3021 sscanf(&line[argstart],DEF_NAM_FMT"%n%1[#:]%n",vname,&j,sep,&k);
3023 rrd_set_error("Cannot parse vname in line: %s",line);
3024 else if (rrd_graph_check_vname(im,vname,line))
3025 rrd_set_error("Undefined vname '%s' in line: %s",line);
3027 k=rrd_graph_color(im,&line[argstart],line,1);
3028 if (rrd_test_error()) break;
3029 argstart=argstart+j+k;
3030 if ((strlen(&line[argstart])!=0)&&(gdp->gf==GF_TICK)) {
3032 sscanf(&line[argstart], ":%lf%n", &gdp->yrule,&j);
3035 if (strlen(&line[argstart])!=0)
3036 if (rrd_graph_legend(gdp,&line[++argstart])==0)
3037 rrd_set_error("Cannot parse legend in line: %s",line);
3043 sscanf(&line[argstart], DEF_NAM_FMT ":%n",gdp->vname,&j);
3045 rrd_set_error("Cannot parse vname in line: '%s'",line);
3049 if (rrd_graph_check_vname(im,gdp->vname,line)) return;
3051 sscanf(&line[argstart], CF_NAM_FMT ":%n",symname,&j);
3053 k=(j!=0)?rrd_graph_check_CF(im,symname,line):1;
3054 #define VIDX im->gdes[gdp->vidx]
3056 case -1: /* looks CF but is not really CF */
3057 if (VIDX.gf == GF_VDEF) rrd_clear_error();
3059 case 0: /* CF present and correct */
3060 if (VIDX.gf == GF_VDEF)
3061 rrd_set_error("Don't use CF when printing VDEF");
3064 case 1: /* CF not present */
3065 if (VIDX.gf == GF_VDEF) rrd_clear_error();
3066 else rrd_set_error("Printing DEF or CDEF needs CF");
3069 rrd_set_error("Oops, bug in GPRINT scanning");
3072 if (rrd_test_error()) break;
3074 if (strlen(&line[argstart])!=0) {
3075 if (rrd_graph_legend(gdp,&line[argstart])==0)
3076 rrd_set_error("Cannot parse legend in line: %s",line);
3077 } else rrd_set_error("No legend in (G)PRINT line: %s",line);
3078 strcpy(gdp->format, gdp->legend);
3084 sscanf(&line[argstart], DEF_NAM_FMT "=%n",gdp->vname,&j);
3086 rrd_set_error("Could not parse line: %s",line);
3089 if (find_var(im,gdp->vname)!=-1) {
3090 rrd_set_error("Variable '%s' in line '%s' already in use\n",
3097 argstart+=scan_for_col(&line[argstart],MAXPATH,gdp->rrd);
3099 sscanf(&line[argstart],
3100 ":" DS_NAM_FMT ":" CF_NAM_FMT "%n%*s%n",
3101 gdp->ds_nam, symname, &j, &k);
3102 if ((j==0)||(k!=0)) {
3103 rrd_set_error("Cannot parse DS or CF in '%s'",line);
3106 rrd_graph_check_CF(im,symname,line);
3110 sscanf(&line[argstart],DEF_NAM_FMT ",%n",vname,&j);
3112 rrd_set_error("Cannot parse vname in line '%s'",line);
3116 if (rrd_graph_check_vname(im,vname,line)) return;
3117 if ( im->gdes[gdp->vidx].gf != GF_DEF
3118 && im->gdes[gdp->vidx].gf != GF_CDEF) {
3119 rrd_set_error("variable '%s' not DEF nor "
3120 "CDEF in VDEF '%s'", vname,gdp->vname);
3123 vdef_parse(gdp,&line[argstart+strstart]);
3126 if (strstr(&line[argstart],":")!=NULL) {
3127 rrd_set_error("Error in RPN, line: %s",line);
3130 if ((gdp->rpnp = rpn_parse(
3135 rrd_set_error("invalid rpn expression in: %s",line);
3140 default: rrd_set_error("Big oops");
3142 if (rrd_test_error()) {
3149 rrd_set_error("can't make a graph without contents");
3150 im_free(im); /* ??? is this set ??? */
3155 rrd_graph_check_vname(image_desc_t *im, char *varname, char *err)
3157 if ((im->gdes[im->gdes_c-1].vidx=find_var(im,varname))==-1) {
3158 rrd_set_error("Unknown variable '%s' in %s",varname,err);
3164 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
3167 graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
3169 color=strstr(var,"#");
3172 rrd_set_error("Found no color in %s",err);
3181 rest=strstr(color,":");
3189 sscanf(color,"#%6lx%n",&col,&n);
3190 col = (col << 8) + 0xff /* shift left by 8 */;
3191 if (n!=7) rrd_set_error("Color problem in %s",err);
3194 sscanf(color,"#%8lx%n",&col,&n);
3197 rrd_set_error("Color problem in %s",err);
3199 if (rrd_test_error()) return 0;
3205 rrd_graph_check_CF(image_desc_t *im, char *symname, char *err)
3207 if ((im->gdes[im->gdes_c-1].cf=cf_conv(symname))==-1) {
3208 rrd_set_error("Unknown CF '%s' in %s",symname,err);
3214 rrd_graph_legend(graph_desc_t *gdp, char *line)
3218 i=scan_for_col(line,FMT_LEG_LEN,gdp->legend);
3220 return (strlen(&line[i])==0);
3224 int bad_format(char *fmt) {
3229 while (*ptr != '\0') {
3230 if (*ptr == '%') {ptr++;
3231 if (*ptr == '\0') return 1;
3232 while ((*ptr >= '0' && *ptr <= '9') || *ptr == '.') {
3235 if (*ptr == '\0') return 1;
3239 if (*ptr == '\0') return 1;
3240 if (*ptr == 'e' || *ptr == 'f') {
3242 } else { return 1; }
3244 else if (*ptr == 's' || *ptr == 'S' || *ptr == '%') { ++ptr; }
3253 vdef_parse(gdes,str)
3254 struct graph_desc_t *gdes;
3257 /* A VDEF currently is either "func" or "param,func"
3258 * so the parsing is rather simple. Change if needed.
3265 sscanf(str,"%le,%29[A-Z]%n",¶m,func,&n);
3266 if (n==strlen(str)) { /* matched */
3270 sscanf(str,"%29[A-Z]%n",func,&n);
3271 if (n==strlen(str)) { /* matched */
3274 rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3281 if (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3282 else if (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3283 else if (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3284 else if (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3285 else if (!strcmp("TOTAL", func)) gdes->vf.op = VDEF_TOTAL;
3286 else if (!strcmp("FIRST", func)) gdes->vf.op = VDEF_FIRST;
3287 else if (!strcmp("LAST", func)) gdes->vf.op = VDEF_LAST;
3289 rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3296 switch (gdes->vf.op) {
3298 if (isnan(param)) { /* no parameter given */
3299 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3305 if (param>=0.0 && param<=100.0) {
3306 gdes->vf.param = param;
3307 gdes->vf.val = DNAN; /* undefined */
3308 gdes->vf.when = 0; /* undefined */
3310 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3324 gdes->vf.param = DNAN;
3325 gdes->vf.val = DNAN;
3328 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3343 graph_desc_t *src,*dst;
3347 dst = &im->gdes[gdi];
3348 src = &im->gdes[dst->vidx];
3349 data = src->data + src->ds;
3350 steps = (src->end - src->start) / src->step;
3353 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3360 switch (dst->vf.op) {
3361 case VDEF_PERCENT: {
3362 rrd_value_t * array;
3366 if ((array = malloc(steps*sizeof(double)))==NULL) {
3367 rrd_set_error("malloc VDEV_PERCENT");
3370 for (step=0;step < steps; step++) {
3371 array[step]=data[step*src->ds_cnt];
3373 qsort(array,step,sizeof(double),vdef_percent_compar);
3375 field = (steps-1)*dst->vf.param/100;
3376 dst->vf.val = array[field];
3377 dst->vf.when = 0; /* no time component */
3379 for(step=0;step<steps;step++)
3380 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3386 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3387 if (step == steps) {
3391 dst->vf.val = data[step*src->ds_cnt];
3392 dst->vf.when = src->start + (step+1)*src->step;
3394 while (step != steps) {
3395 if (finite(data[step*src->ds_cnt])) {
3396 if (data[step*src->ds_cnt] > dst->vf.val) {
3397 dst->vf.val = data[step*src->ds_cnt];
3398 dst->vf.when = src->start + (step+1)*src->step;
3405 case VDEF_AVERAGE: {
3408 for (step=0;step<steps;step++) {
3409 if (finite(data[step*src->ds_cnt])) {
3410 sum += data[step*src->ds_cnt];
3415 if (dst->vf.op == VDEF_TOTAL) {
3416 dst->vf.val = sum*src->step;
3417 dst->vf.when = cnt*src->step; /* not really "when" */
3419 dst->vf.val = sum/cnt;
3420 dst->vf.when = 0; /* no time component */
3430 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3431 if (step == steps) {
3435 dst->vf.val = data[step*src->ds_cnt];
3436 dst->vf.when = src->start + (step+1)*src->step;
3438 while (step != steps) {
3439 if (finite(data[step*src->ds_cnt])) {
3440 if (data[step*src->ds_cnt] < dst->vf.val) {
3441 dst->vf.val = data[step*src->ds_cnt];
3442 dst->vf.when = src->start + (step+1)*src->step;
3449 /* The time value returned here is one step before the
3450 * actual time value. This is the start of the first
3454 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3455 if (step == steps) { /* all entries were NaN */
3459 dst->vf.val = data[step*src->ds_cnt];
3460 dst->vf.when = src->start + step*src->step;
3464 /* The time value returned here is the
3465 * actual time value. This is the end of the last
3469 while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3470 if (step < 0) { /* all entries were NaN */
3474 dst->vf.val = data[step*src->ds_cnt];
3475 dst->vf.when = src->start + (step+1)*src->step;
3482 /* NaN < -INF < finite_values < INF */
3484 vdef_percent_compar(a,b)
3487 /* Equality is not returned; this doesn't hurt except
3488 * (maybe) for a little performance.
3491 /* First catch NaN values. They are smallest */
3492 if (isnan( *(double *)a )) return -1;
3493 if (isnan( *(double *)b )) return 1;
3495 /* NaN doesn't reach this part so INF and -INF are extremes.
3496 * The sign from isinf() is compatible with the sign we return
3498 if (isinf( *(double *)a )) return isinf( *(double *)a );
3499 if (isinf( *(double *)b )) return isinf( *(double *)b );
3501 /* If we reach this, both values must be finite */
3502 if ( *(double *)a < *(double *)b ) return -1; else return 1;