1 /****************************************************************************
2 * RRDtool 1.1.x Copyright Tobias Oetiker, 1997 - 2002
3 ****************************************************************************
4 * rrd__graph.c make creates ne rrds
5 ****************************************************************************/
25 #include "rrd_graph.h"
27 /* some constant definitions */
31 char rrd_win_default_font[80];
34 #ifndef RRD_DEFAULT_FONT
36 #define RRD_DEFAULT_FONT "VeraMono.ttf"
37 /* #define RRD_DEFAULT_FONT "/usr/share/fonts/truetype/openoffice/ariosor.ttf" */
38 /* #define RRD_DEFAULT_FONT "/usr/share/fonts/truetype/Arial.ttf" */
42 text_prop_t text_prop[] = {
43 { 10.0, RRD_DEFAULT_FONT }, /* default */
44 { 10.0, RRD_DEFAULT_FONT }, /* title */
45 { 8.0, RRD_DEFAULT_FONT }, /* axis */
46 { 10.0, RRD_DEFAULT_FONT }, /* unit */
47 { 10.0, RRD_DEFAULT_FONT } /* legend */
51 {0, TMT_SECOND,30, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
52 {2, TMT_MINUTE,1, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
53 {5, TMT_MINUTE,2, TMT_MINUTE,10, TMT_MINUTE,10, 0,"%H:%M"},
54 {10, TMT_MINUTE,5, TMT_MINUTE,20, TMT_MINUTE,20, 0,"%H:%M"},
55 {30, TMT_MINUTE,10, TMT_HOUR,1, TMT_HOUR,1, 0,"%H:%M"},
56 {60, TMT_MINUTE,30, TMT_HOUR,2, TMT_HOUR,2, 0,"%H:%M"},
57 {180, TMT_HOUR,1, TMT_HOUR,6, TMT_HOUR,6, 0,"%H:%M"},
58 /*{300, TMT_HOUR,3, TMT_HOUR,12, TMT_HOUR,12, 12*3600,"%a %p"}, this looks silly*/
59 {600, TMT_HOUR,6, TMT_DAY,1, TMT_DAY,1, 24*3600,"%a"},
60 {1800, TMT_HOUR,12, TMT_DAY,1, TMT_DAY,2, 24*3600,"%a"},
61 {3600, TMT_DAY,1, TMT_WEEK,1, TMT_WEEK,1, 7*24*3600,"Week %V"},
62 {3*3600, TMT_WEEK,1, TMT_MONTH,1, TMT_WEEK,2, 7*24*3600,"Week %V"},
63 {6*3600, TMT_MONTH,1, TMT_MONTH,1, TMT_MONTH,1, 30*24*3600,"%b"},
64 {48*3600, TMT_MONTH,1, TMT_MONTH,3, TMT_MONTH,3, 30*24*3600,"%b"},
65 {10*24*3600, TMT_YEAR,1, TMT_YEAR,1, TMT_YEAR,1, 365*24*3600,"%y"},
66 {-1,TMT_MONTH,0,TMT_MONTH,0,TMT_MONTH,0,0,""}
69 /* sensible logarithmic y label intervals ...
70 the first element of each row defines the possible starting points on the
71 y axis ... the other specify the */
73 double yloglab[][12]= {{ 1e9, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
74 { 1e3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
75 { 1e1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
76 /* { 1e1, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, */
77 { 1e1, 1, 2.5, 5, 7.5, 0, 0, 0, 0, 0, 0, 0 },
78 { 1e1, 1, 2, 4, 6, 8, 0, 0, 0, 0, 0, 0 },
79 { 1e1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0 },
80 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }};
82 /* sensible y label intervals ...*/
100 gfx_color_t graph_col[] = /* default colors */
101 { 0xFFFFFFFF, /* canvas */
102 0xF0F0F0FF, /* background */
103 0xD0D0D0FF, /* shade A */
104 0xA0A0A0FF, /* shade B */
105 0x909090FF, /* grid */
106 0xE05050FF, /* major grid */
107 0x000000FF, /* font */
108 0x000000FF, /* frame */
109 0xFF0000FF /* arrow */
116 # define DPRINT(x) (void)(printf x, printf("\n"))
122 /* initialize with xtr(im,0); */
124 xtr(image_desc_t *im,time_t mytime){
127 pixie = (double) im->xsize / (double)(im->end - im->start);
130 return (int)((double)im->xorigin
131 + pixie * ( mytime - im->start ) );
134 /* translate data values into y coordinates */
136 ytr(image_desc_t *im, double value){
141 pixie = (double) im->ysize / (im->maxval - im->minval);
143 pixie = (double) im->ysize / (log10(im->maxval) - log10(im->minval));
145 } else if(!im->logarithmic) {
146 yval = im->yorigin - pixie * (value - im->minval);
148 if (value < im->minval) {
151 yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
154 /* make sure we don't return anything too unreasonable. GD lib can
155 get terribly slow when drawing lines outside its scope. This is
156 especially problematic in connection with the rigid option */
158 /* keep yval as-is */
159 } else if (yval > im->yorigin) {
160 yval = im->yorigin+2;
161 } else if (yval < im->yorigin - im->ysize){
162 yval = im->yorigin - im->ysize - 2;
169 /* conversion function for symbolic entry names */
172 #define conv_if(VV,VVV) \
173 if (strcmp(#VV, string) == 0) return VVV ;
175 enum gf_en gf_conv(char *string){
177 conv_if(PRINT,GF_PRINT)
178 conv_if(GPRINT,GF_GPRINT)
179 conv_if(COMMENT,GF_COMMENT)
180 conv_if(HRULE,GF_HRULE)
181 conv_if(VRULE,GF_VRULE)
182 conv_if(LINE,GF_LINE)
183 conv_if(AREA,GF_AREA)
184 conv_if(STACK,GF_STACK)
185 conv_if(TICK,GF_TICK)
187 conv_if(CDEF,GF_CDEF)
188 conv_if(VDEF,GF_VDEF)
189 conv_if(PART,GF_PART)
190 conv_if(XPORT,GF_XPORT)
191 conv_if(SHIFT,GF_SHIFT)
196 enum gfx_if_en if_conv(char *string){
206 enum tmt_en tmt_conv(char *string){
208 conv_if(SECOND,TMT_SECOND)
209 conv_if(MINUTE,TMT_MINUTE)
210 conv_if(HOUR,TMT_HOUR)
212 conv_if(WEEK,TMT_WEEK)
213 conv_if(MONTH,TMT_MONTH)
214 conv_if(YEAR,TMT_YEAR)
218 enum grc_en grc_conv(char *string){
220 conv_if(BACK,GRC_BACK)
221 conv_if(CANVAS,GRC_CANVAS)
222 conv_if(SHADEA,GRC_SHADEA)
223 conv_if(SHADEB,GRC_SHADEB)
224 conv_if(GRID,GRC_GRID)
225 conv_if(MGRID,GRC_MGRID)
226 conv_if(FONT,GRC_FONT)
227 conv_if(FRAME,GRC_FRAME)
228 conv_if(ARROW,GRC_ARROW)
233 enum text_prop_en text_prop_conv(char *string){
235 conv_if(DEFAULT,TEXT_PROP_DEFAULT)
236 conv_if(TITLE,TEXT_PROP_TITLE)
237 conv_if(AXIS,TEXT_PROP_AXIS)
238 conv_if(UNIT,TEXT_PROP_UNIT)
239 conv_if(LEGEND,TEXT_PROP_LEGEND)
247 im_free(image_desc_t *im)
251 if (im == NULL) return 0;
252 for(i=0;i<(unsigned)im->gdes_c;i++){
253 if (im->gdes[i].data_first){
254 /* careful here, because a single pointer can occur several times */
255 free (im->gdes[i].data);
256 if (im->gdes[i].ds_namv){
257 for (ii=0;ii<im->gdes[i].ds_cnt;ii++)
258 free(im->gdes[i].ds_namv[ii]);
259 free(im->gdes[i].ds_namv);
262 free (im->gdes[i].p_data);
263 free (im->gdes[i].rpnp);
266 gfx_destroy(im->canvas);
270 /* find SI magnitude symbol for the given number*/
273 image_desc_t *im, /* image description */
280 char *symbol[] = {"a", /* 10e-18 Atto */
281 "f", /* 10e-15 Femto */
282 "p", /* 10e-12 Pico */
283 "n", /* 10e-9 Nano */
284 "u", /* 10e-6 Micro */
285 "m", /* 10e-3 Milli */
290 "T", /* 10e12 Tera */
291 "P", /* 10e15 Peta */
297 if (*value == 0.0 || isnan(*value) ) {
301 sindex = floor(log(fabs(*value))/log((double)im->base));
302 *magfact = pow((double)im->base, (double)sindex);
303 (*value) /= (*magfact);
305 if ( sindex <= symbcenter && sindex >= -symbcenter) {
306 (*symb_ptr) = symbol[sindex+symbcenter];
314 /* find SI magnitude symbol for the numbers on the y-axis*/
317 image_desc_t *im /* image description */
321 char symbol[] = {'a', /* 10e-18 Atto */
322 'f', /* 10e-15 Femto */
323 'p', /* 10e-12 Pico */
324 'n', /* 10e-9 Nano */
325 'u', /* 10e-6 Micro */
326 'm', /* 10e-3 Milli */
331 'T', /* 10e12 Tera */
332 'P', /* 10e15 Peta */
338 if (im->unitsexponent != 9999) {
339 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
340 digits = floor(im->unitsexponent / 3);
342 digits = floor( log( max( fabs(im->minval),fabs(im->maxval)))/log((double)im->base));
344 im->magfact = pow((double)im->base , digits);
347 printf("digits %6.3f im->magfact %6.3f\n",digits,im->magfact);
350 if ( ((digits+symbcenter) < sizeof(symbol)) &&
351 ((digits+symbcenter) >= 0) )
352 im->symbol = symbol[(int)digits+symbcenter];
357 /* move min and max values around to become sensible */
360 expand_range(image_desc_t *im)
362 double sensiblevalues[] ={1000.0,900.0,800.0,750.0,700.0,
363 600.0,500.0,400.0,300.0,250.0,
364 200.0,125.0,100.0,90.0,80.0,
365 75.0,70.0,60.0,50.0,40.0,30.0,
366 25.0,20.0,10.0,9.0,8.0,
367 7.0,6.0,5.0,4.0,3.5,3.0,
368 2.5,2.0,1.8,1.5,1.2,1.0,
369 0.8,0.7,0.6,0.5,0.4,0.3,0.2,0.1,0.0,-1};
371 double scaled_min,scaled_max;
378 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
379 im->minval,im->maxval,im->magfact);
382 if (isnan(im->ygridstep)){
383 if(im->extra_flags & ALTAUTOSCALE) {
384 /* measure the amplitude of the function. Make sure that
385 graph boundaries are slightly higher then max/min vals
386 so we can see amplitude on the graph */
389 delt = im->maxval - im->minval;
391 fact = 2.0 * pow(10.0,
392 floor(log10(max(fabs(im->minval), fabs(im->maxval)))) - 2);
394 adj = (fact - delt) * 0.55;
396 printf("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n", im->minval, im->maxval, delt, fact, adj);
402 else if(im->extra_flags & ALTAUTOSCALE_MAX) {
403 /* measure the amplitude of the function. Make sure that
404 graph boundaries are slightly higher than max vals
405 so we can see amplitude on the graph */
406 adj = (im->maxval - im->minval) * 0.1;
410 scaled_min = im->minval / im->magfact;
411 scaled_max = im->maxval / im->magfact;
413 for (i=1; sensiblevalues[i] > 0; i++){
414 if (sensiblevalues[i-1]>=scaled_min &&
415 sensiblevalues[i]<=scaled_min)
416 im->minval = sensiblevalues[i]*(im->magfact);
418 if (-sensiblevalues[i-1]<=scaled_min &&
419 -sensiblevalues[i]>=scaled_min)
420 im->minval = -sensiblevalues[i-1]*(im->magfact);
422 if (sensiblevalues[i-1] >= scaled_max &&
423 sensiblevalues[i] <= scaled_max)
424 im->maxval = sensiblevalues[i-1]*(im->magfact);
426 if (-sensiblevalues[i-1]<=scaled_max &&
427 -sensiblevalues[i] >=scaled_max)
428 im->maxval = -sensiblevalues[i]*(im->magfact);
432 /* adjust min and max to the grid definition if there is one */
433 im->minval = (double)im->ylabfact * im->ygridstep *
434 floor(im->minval / ((double)im->ylabfact * im->ygridstep));
435 im->maxval = (double)im->ylabfact * im->ygridstep *
436 ceil(im->maxval /( (double)im->ylabfact * im->ygridstep));
440 fprintf(stderr,"SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
441 im->minval,im->maxval,im->magfact);
446 apply_gridfit(image_desc_t *im)
448 if (isnan(im->minval) || isnan(im->maxval))
451 if (im->logarithmic) {
452 double ya, yb, ypix, ypixfrac;
453 double log10_range = log10(im->maxval) - log10(im->minval);
454 ya = pow((double)10, floor(log10(im->minval)));
455 while (ya < im->minval)
458 return; /* don't have y=10^x gridline */
460 if (yb <= im->maxval) {
461 /* we have at least 2 y=10^x gridlines.
462 Make sure distance between them in pixels
463 are an integer by expanding im->maxval */
464 double y_pixel_delta = ytr(im, ya) - ytr(im, yb);
465 double factor = y_pixel_delta / floor(y_pixel_delta);
466 double new_log10_range = factor * log10_range;
467 double new_ymax_log10 = log10(im->minval) + new_log10_range;
468 im->maxval = pow(10, new_ymax_log10);
469 ytr(im, DNAN); /* reset precalc */
470 log10_range = log10(im->maxval) - log10(im->minval);
472 /* make sure first y=10^x gridline is located on
473 integer pixel position by moving scale slightly
474 downwards (sub-pixel movement) */
475 ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
476 ypixfrac = ypix - floor(ypix);
477 if (ypixfrac > 0 && ypixfrac < 1) {
478 double yfrac = ypixfrac / im->ysize;
479 im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
480 im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
481 ytr(im, DNAN); /* reset precalc */
484 /* Make sure we have an integer pixel distance between
485 each minor gridline */
486 double ypos1 = ytr(im, im->minval);
487 double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
488 double y_pixel_delta = ypos1 - ypos2;
489 double factor = y_pixel_delta / floor(y_pixel_delta);
490 double new_range = factor * (im->maxval - im->minval);
491 double gridstep = im->ygrid_scale.gridstep;
492 double minor_y, minor_y_px, minor_y_px_frac;
493 im->maxval = im->minval + new_range;
494 ytr(im, DNAN); /* reset precalc */
495 /* make sure first minor gridline is on integer pixel y coord */
496 minor_y = gridstep * floor(im->minval / gridstep);
497 while (minor_y < im->minval)
499 minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
500 minor_y_px_frac = minor_y_px - floor(minor_y_px);
501 if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
502 double yfrac = minor_y_px_frac / im->ysize;
503 double range = im->maxval - im->minval;
504 im->minval = im->minval - yfrac * range;
505 im->maxval = im->maxval - yfrac * range;
506 ytr(im, DNAN); /* reset precalc */
508 calc_horizontal_grid(im); /* recalc with changed im->maxval */
512 /* reduce data reimplementation by Alex */
516 enum cf_en cf, /* which consolidation function ?*/
517 unsigned long cur_step, /* step the data currently is in */
518 time_t *start, /* start, end and step as requested ... */
519 time_t *end, /* ... by the application will be ... */
520 unsigned long *step, /* ... adjusted to represent reality */
521 unsigned long *ds_cnt, /* number of data sources in file */
522 rrd_value_t **data) /* two dimensional array containing the data */
524 int i,reduce_factor = ceil((double)(*step) / (double)cur_step);
525 unsigned long col,dst_row,row_cnt,start_offset,end_offset,skiprows=0;
526 rrd_value_t *srcptr,*dstptr;
528 (*step) = cur_step*reduce_factor; /* set new step size for reduced data */
531 row_cnt = ((*end)-(*start))/cur_step;
537 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
538 row_cnt,reduce_factor,*start,*end,cur_step);
539 for (col=0;col<row_cnt;col++) {
540 printf("time %10lu: ",*start+(col+1)*cur_step);
541 for (i=0;i<*ds_cnt;i++)
542 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
547 /* We have to combine [reduce_factor] rows of the source
548 ** into one row for the destination. Doing this we also
549 ** need to take care to combine the correct rows. First
550 ** alter the start and end time so that they are multiples
551 ** of the new step time. We cannot reduce the amount of
552 ** time so we have to move the end towards the future and
553 ** the start towards the past.
555 end_offset = (*end) % (*step);
556 start_offset = (*start) % (*step);
558 /* If there is a start offset (which cannot be more than
559 ** one destination row), skip the appropriate number of
560 ** source rows and one destination row. The appropriate
561 ** number is what we do know (start_offset/cur_step) of
562 ** the new interval (*step/cur_step aka reduce_factor).
565 printf("start_offset: %lu end_offset: %lu\n",start_offset,end_offset);
566 printf("row_cnt before: %lu\n",row_cnt);
569 (*start) = (*start)-start_offset;
570 skiprows=reduce_factor-start_offset/cur_step;
571 srcptr+=skiprows* *ds_cnt;
572 for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
576 printf("row_cnt between: %lu\n",row_cnt);
579 /* At the end we have some rows that are not going to be
580 ** used, the amount is end_offset/cur_step
583 (*end) = (*end)-end_offset+(*step);
584 skiprows = end_offset/cur_step;
588 printf("row_cnt after: %lu\n",row_cnt);
591 /* Sanity check: row_cnt should be multiple of reduce_factor */
592 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
594 if (row_cnt%reduce_factor) {
595 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
596 row_cnt,reduce_factor);
597 printf("BUG in reduce_data()\n");
601 /* Now combine reduce_factor intervals at a time
602 ** into one interval for the destination.
605 for (dst_row=0;(long int)row_cnt>=reduce_factor;dst_row++) {
606 for (col=0;col<(*ds_cnt);col++) {
607 rrd_value_t newval=DNAN;
608 unsigned long validval=0;
610 for (i=0;i<reduce_factor;i++) {
611 if (isnan(srcptr[i*(*ds_cnt)+col])) {
615 if (isnan(newval)) newval = srcptr[i*(*ds_cnt)+col];
623 newval += srcptr[i*(*ds_cnt)+col];
626 newval = min (newval,srcptr[i*(*ds_cnt)+col]);
629 /* an interval contains a failure if any subintervals contained a failure */
631 newval = max (newval,srcptr[i*(*ds_cnt)+col]);
634 newval = srcptr[i*(*ds_cnt)+col];
639 if (validval == 0){newval = DNAN;} else{
657 srcptr+=(*ds_cnt)*reduce_factor;
658 row_cnt-=reduce_factor;
660 /* If we had to alter the endtime, we didn't have enough
661 ** source rows to fill the last row. Fill it with NaN.
663 if (end_offset) for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
665 row_cnt = ((*end)-(*start))/ *step;
667 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
668 row_cnt,*start,*end,*step);
669 for (col=0;col<row_cnt;col++) {
670 printf("time %10lu: ",*start+(col+1)*(*step));
671 for (i=0;i<*ds_cnt;i++)
672 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
679 /* get the data required for the graphs from the
683 data_fetch(image_desc_t *im )
688 /* pull the data from the log files ... */
689 for (i=0;i< (int)im->gdes_c;i++){
690 /* only GF_DEF elements fetch data */
691 if (im->gdes[i].gf != GF_DEF)
695 /* do we have it already ?*/
696 for (ii=0;ii<i;ii++) {
697 if (im->gdes[ii].gf != GF_DEF)
699 if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
700 && (im->gdes[i].cf == im->gdes[ii].cf)
701 && (im->gdes[i].start == im->gdes[ii].start)
702 && (im->gdes[i].end == im->gdes[ii].end)
703 && (im->gdes[i].step == im->gdes[ii].step)) {
704 /* OK, the data is already there.
705 ** Just copy the header portion
707 im->gdes[i].start = im->gdes[ii].start;
708 im->gdes[i].end = im->gdes[ii].end;
709 im->gdes[i].step = im->gdes[ii].step;
710 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
711 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
712 im->gdes[i].data = im->gdes[ii].data;
713 im->gdes[i].data_first = 0;
720 unsigned long ft_step = im->gdes[i].step ;
722 if((rrd_fetch_fn(im->gdes[i].rrd,
728 &im->gdes[i].ds_namv,
729 &im->gdes[i].data)) == -1){
732 im->gdes[i].data_first = 1;
734 if (ft_step < im->gdes[i].step) {
735 reduce_data(im->gdes[i].cf,
743 im->gdes[i].step = ft_step;
747 /* lets see if the required data source is really there */
748 for(ii=0;ii<(int)im->gdes[i].ds_cnt;ii++){
749 if(strcmp(im->gdes[i].ds_namv[ii],im->gdes[i].ds_nam) == 0){
752 if (im->gdes[i].ds== -1){
753 rrd_set_error("No DS called '%s' in '%s'",
754 im->gdes[i].ds_nam,im->gdes[i].rrd);
762 /* evaluate the expressions in the CDEF functions */
764 /*************************************************************
766 *************************************************************/
769 find_var_wrapper(void *arg1, char *key)
771 return find_var((image_desc_t *) arg1, key);
774 /* find gdes containing var*/
776 find_var(image_desc_t *im, char *key){
778 for(ii=0;ii<im->gdes_c-1;ii++){
779 if((im->gdes[ii].gf == GF_DEF
780 || im->gdes[ii].gf == GF_VDEF
781 || im->gdes[ii].gf == GF_CDEF)
782 && (strcmp(im->gdes[ii].vname,key) == 0)){
789 /* find the largest common denominator for all the numbers
790 in the 0 terminated num array */
795 for (i=0;num[i+1]!=0;i++){
797 rest=num[i] % num[i+1];
798 num[i]=num[i+1]; num[i+1]=rest;
802 /* return i==0?num[i]:num[i-1]; */
806 /* run the rpn calculator on all the VDEF and CDEF arguments */
808 data_calc( image_desc_t *im){
812 long *steparray, rpi;
817 rpnstack_init(&rpnstack);
819 for (gdi=0;gdi<im->gdes_c;gdi++){
820 /* Look for GF_VDEF and GF_CDEF in the same loop,
821 * so CDEFs can use VDEFs and vice versa
823 switch (im->gdes[gdi].gf) {
827 graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx];
829 /* remove current shift */
830 vdp->start -= vdp->shift;
831 vdp->end -= vdp->shift;
834 if (im->gdes[gdi].shidx >= 0)
835 vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
838 vdp->shift = im->gdes[gdi].shval;
840 /* normalize shift to multiple of consolidated step */
841 vdp->shift = (vdp->shift / vdp->step) * vdp->step;
844 vdp->start += vdp->shift;
845 vdp->end += vdp->shift;
849 /* A VDEF has no DS. This also signals other parts
850 * of rrdtool that this is a VDEF value, not a CDEF.
852 im->gdes[gdi].ds_cnt = 0;
853 if (vdef_calc(im,gdi)) {
854 rrd_set_error("Error processing VDEF '%s'"
857 rpnstack_free(&rpnstack);
862 im->gdes[gdi].ds_cnt = 1;
863 im->gdes[gdi].ds = 0;
864 im->gdes[gdi].data_first = 1;
865 im->gdes[gdi].start = 0;
866 im->gdes[gdi].end = 0;
871 /* Find the variables in the expression.
872 * - VDEF variables are substituted by their values
873 * and the opcode is changed into OP_NUMBER.
874 * - CDEF variables are analized for their step size,
875 * the lowest common denominator of all the step
876 * sizes of the data sources involved is calculated
877 * and the resulting number is the step size for the
878 * resulting data source.
880 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
881 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
882 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER){
883 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
884 if (im->gdes[ptr].ds_cnt == 0) {
886 printf("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
888 im->gdes[ptr].vname);
889 printf("DEBUG: value from vdef is %f\n",im->gdes[ptr].vf.val);
891 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
892 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
895 rrd_realloc(steparray,
896 (++stepcnt+1)*sizeof(*steparray)))==NULL){
897 rrd_set_error("realloc steparray");
898 rpnstack_free(&rpnstack);
902 steparray[stepcnt-1] = im->gdes[ptr].step;
904 /* adjust start and end of cdef (gdi) so
905 * that it runs from the latest start point
906 * to the earliest endpoint of any of the
907 * rras involved (ptr)
909 if(im->gdes[gdi].start < im->gdes[ptr].start)
910 im->gdes[gdi].start = im->gdes[ptr].start;
912 if(im->gdes[gdi].end == 0 ||
913 im->gdes[gdi].end > im->gdes[ptr].end)
914 im->gdes[gdi].end = im->gdes[ptr].end;
916 /* store pointer to the first element of
917 * the rra providing data for variable,
918 * further save step size and data source
921 im->gdes[gdi].rpnp[rpi].data = im->gdes[ptr].data + im->gdes[ptr].ds;
922 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
923 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
925 /* backoff the *.data ptr; this is done so
926 * rpncalc() function doesn't have to treat
927 * the first case differently
929 } /* if ds_cnt != 0 */
930 } /* if OP_VARIABLE */
931 } /* loop through all rpi */
933 /* move the data pointers to the correct period */
934 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
935 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
936 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER){
937 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
938 long diff = im->gdes[gdi].start - im->gdes[ptr].start;
941 im->gdes[gdi].rpnp[rpi].data += (diff / im->gdes[ptr].step) * im->gdes[ptr].ds_cnt;
945 if(steparray == NULL){
946 rrd_set_error("rpn expressions without DEF"
947 " or CDEF variables are not supported");
948 rpnstack_free(&rpnstack);
951 steparray[stepcnt]=0;
952 /* Now find the resulting step. All steps in all
953 * used RRAs have to be visited
955 im->gdes[gdi].step = lcd(steparray);
957 if((im->gdes[gdi].data = malloc((
958 (im->gdes[gdi].end-im->gdes[gdi].start)
959 / im->gdes[gdi].step)
960 * sizeof(double)))==NULL){
961 rrd_set_error("malloc im->gdes[gdi].data");
962 rpnstack_free(&rpnstack);
966 /* Step through the new cdef results array and
967 * calculate the values
969 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
970 now<=im->gdes[gdi].end;
971 now += im->gdes[gdi].step)
973 rpnp_t *rpnp = im -> gdes[gdi].rpnp;
975 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
976 * in this case we are advancing by timesteps;
977 * we use the fact that time_t is a synonym for long
979 if (rpn_calc(rpnp,&rpnstack,(long) now,
980 im->gdes[gdi].data,++dataidx) == -1) {
981 /* rpn_calc sets the error string */
982 rpnstack_free(&rpnstack);
985 } /* enumerate over time steps within a CDEF */
990 } /* enumerate over CDEFs */
991 rpnstack_free(&rpnstack);
995 /* massage data so, that we get one value for each x coordinate in the graph */
997 data_proc( image_desc_t *im ){
999 double pixstep = (double)(im->end-im->start)
1000 /(double)im->xsize; /* how much time
1001 passes in one pixel */
1003 double minval=DNAN,maxval=DNAN;
1005 unsigned long gr_time;
1007 /* memory for the processed data */
1008 for(i=0;i<im->gdes_c;i++) {
1009 if((im->gdes[i].gf==GF_LINE) ||
1010 (im->gdes[i].gf==GF_AREA) ||
1011 (im->gdes[i].gf==GF_TICK) ||
1012 (im->gdes[i].gf==GF_STACK)) {
1013 if((im->gdes[i].p_data = malloc((im->xsize +1)
1014 * sizeof(rrd_value_t)))==NULL){
1015 rrd_set_error("malloc data_proc");
1021 for (i=0;i<im->xsize;i++) { /* for each pixel */
1023 gr_time = im->start+pixstep*i; /* time of the current step */
1026 for (ii=0;ii<im->gdes_c;ii++) {
1028 switch (im->gdes[ii].gf) {
1032 if (!im->gdes[ii].stack)
1035 value = im->gdes[ii].yrule;
1036 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1037 /* The time of the data doesn't necessarily match
1038 ** the time of the graph. Beware.
1040 vidx = im->gdes[ii].vidx;
1041 if (im->gdes[vidx].gf == GF_VDEF) {
1042 value = im->gdes[vidx].vf.val;
1043 } else if (((long int)gr_time >= (long int)im->gdes[vidx].start) &&
1044 ((long int)gr_time <= (long int)im->gdes[vidx].end) ) {
1045 value = im->gdes[vidx].data[
1046 (unsigned long) floor(
1047 (double)(gr_time - im->gdes[vidx].start)
1048 / im->gdes[vidx].step)
1049 * im->gdes[vidx].ds_cnt
1057 if (! isnan(value)) {
1059 im->gdes[ii].p_data[i] = paintval;
1060 /* GF_TICK: the data values are not
1061 ** relevant for min and max
1063 if (finite(paintval) && im->gdes[ii].gf != GF_TICK ) {
1064 if (isnan(minval) || paintval < minval)
1066 if (isnan(maxval) || paintval > maxval)
1070 im->gdes[ii].p_data[i] = DNAN;
1079 /* if min or max have not been asigned a value this is because
1080 there was no data in the graph ... this is not good ...
1081 lets set these to dummy values then ... */
1083 if (isnan(minval)) minval = 0.0;
1084 if (isnan(maxval)) maxval = 1.0;
1086 /* adjust min and max values */
1087 if (isnan(im->minval)
1088 /* don't adjust low-end with log scale */
1089 || ((!im->logarithmic && !im->rigid) && im->minval > minval)
1091 im->minval = minval;
1092 if (isnan(im->maxval)
1093 || (!im->rigid && im->maxval < maxval)
1095 if (im->logarithmic)
1096 im->maxval = maxval * 1.1;
1098 im->maxval = maxval;
1100 /* make sure min is smaller than max */
1101 if (im->minval > im->maxval) {
1102 im->minval = 0.99 * im->maxval;
1105 /* make sure min and max are not equal */
1106 if (im->minval == im->maxval) {
1108 if (! im->logarithmic) {
1111 /* make sure min and max are not both zero */
1112 if (im->maxval == 0.0) {
1121 /* identify the point where the first gridline, label ... gets placed */
1125 time_t start, /* what is the initial time */
1126 enum tmt_en baseint, /* what is the basic interval */
1127 long basestep /* how many if these do we jump a time */
1131 localtime_r(&start, &tm);
1134 tm.tm_sec -= tm.tm_sec % basestep; break;
1137 tm.tm_min -= tm.tm_min % basestep;
1142 tm.tm_hour -= tm.tm_hour % basestep; break;
1144 /* we do NOT look at the basestep for this ... */
1147 tm.tm_hour = 0; break;
1149 /* we do NOT look at the basestep for this ... */
1153 tm.tm_mday -= tm.tm_wday -1; /* -1 because we want the monday */
1154 if (tm.tm_wday==0) tm.tm_mday -= 7; /* we want the *previous* monday */
1161 tm.tm_mon -= tm.tm_mon % basestep; break;
1169 tm.tm_year -= (tm.tm_year+1900) % basestep;
1174 /* identify the point where the next gridline, label ... gets placed */
1177 time_t current, /* what is the initial time */
1178 enum tmt_en baseint, /* what is the basic interval */
1179 long basestep /* how many if these do we jump a time */
1184 localtime_r(¤t, &tm);
1188 tm.tm_sec += basestep; break;
1190 tm.tm_min += basestep; break;
1192 tm.tm_hour += basestep; break;
1194 tm.tm_mday += basestep; break;
1196 tm.tm_mday += 7*basestep; break;
1198 tm.tm_mon += basestep; break;
1200 tm.tm_year += basestep;
1202 madetime = mktime(&tm);
1203 } while (madetime == -1); /* this is necessary to skip impssible times
1204 like the daylight saving time skips */
1210 /* calculate values required for PRINT and GPRINT functions */
1213 print_calc(image_desc_t *im, char ***prdata)
1215 long i,ii,validsteps;
1218 int graphelement = 0;
1221 double magfact = -1;
1225 if (im->imginfo) prlines++;
1226 for(i=0;i<im->gdes_c;i++){
1227 switch(im->gdes[i].gf){
1230 if(((*prdata) = rrd_realloc((*prdata),prlines*sizeof(char *)))==NULL){
1231 rrd_set_error("realloc prdata");
1235 /* PRINT and GPRINT can now print VDEF generated values.
1236 * There's no need to do any calculations on them as these
1237 * calculations were already made.
1239 vidx = im->gdes[i].vidx;
1240 if (im->gdes[vidx].gf==GF_VDEF) { /* simply use vals */
1241 printval = im->gdes[vidx].vf.val;
1242 printtime = im->gdes[vidx].vf.when;
1243 } else { /* need to calculate max,min,avg etcetera */
1244 max_ii =((im->gdes[vidx].end
1245 - im->gdes[vidx].start)
1246 / im->gdes[vidx].step
1247 * im->gdes[vidx].ds_cnt);
1250 for( ii=im->gdes[vidx].ds;
1252 ii+=im->gdes[vidx].ds_cnt){
1253 if (! finite(im->gdes[vidx].data[ii]))
1255 if (isnan(printval)){
1256 printval = im->gdes[vidx].data[ii];
1261 switch (im->gdes[i].cf){
1264 case CF_DEVSEASONAL:
1268 printval += im->gdes[vidx].data[ii];
1271 printval = min( printval, im->gdes[vidx].data[ii]);
1275 printval = max( printval, im->gdes[vidx].data[ii]);
1278 printval = im->gdes[vidx].data[ii];
1281 if (im->gdes[i].cf==CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1282 if (validsteps > 1) {
1283 printval = (printval / validsteps);
1286 } /* prepare printval */
1288 if (!strcmp(im->gdes[i].format,"%c")) { /* VDEF time print */
1289 char ctime_buf[128]; /* PS: for ctime_r, must be >= 26 chars */
1290 if (im->gdes[i].gf == GF_PRINT){
1291 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1292 sprintf((*prdata)[prlines-2],"%s (%lu)",
1293 ctime_r(&printtime,ctime_buf),printtime);
1294 (*prdata)[prlines-1] = NULL;
1296 sprintf(im->gdes[i].legend,"%s (%lu)",
1297 ctime_r(&printtime,ctime_buf),printtime);
1301 if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) {
1302 /* Magfact is set to -1 upon entry to print_calc. If it
1303 * is still less than 0, then we need to run auto_scale.
1304 * Otherwise, put the value into the correct units. If
1305 * the value is 0, then do not set the symbol or magnification
1306 * so next the calculation will be performed again. */
1307 if (magfact < 0.0) {
1308 auto_scale(im,&printval,&si_symb,&magfact);
1309 if (printval == 0.0)
1312 printval /= magfact;
1314 *(++percent_s) = 's';
1315 } else if (strstr(im->gdes[i].format,"%s") != NULL) {
1316 auto_scale(im,&printval,&si_symb,&magfact);
1319 if (im->gdes[i].gf == GF_PRINT){
1320 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1321 (*prdata)[prlines-1] = NULL;
1322 if (bad_format(im->gdes[i].format)) {
1323 rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1326 #ifdef HAVE_SNPRINTF
1327 snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
1329 sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
1334 if (bad_format(im->gdes[i].format)) {
1335 rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1338 #ifdef HAVE_SNPRINTF
1339 snprintf(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
1341 sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
1365 return graphelement;
1369 /* place legends with color spots */
1371 leg_place(image_desc_t *im)
1374 int interleg = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1375 int box =im->text_prop[TEXT_PROP_LEGEND].size*1.5;
1376 int border = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1377 int fill=0, fill_last;
1379 int leg_x = border, leg_y = im->yimg;
1383 char prt_fctn; /*special printfunctions */
1386 if( !(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH) ) {
1387 if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1388 rrd_set_error("malloc for legspace");
1392 for(i=0;i<im->gdes_c;i++){
1395 /* hid legends for rules which are not displayed */
1397 if (im->gdes[i].gf == GF_HRULE &&
1398 (im->gdes[i].yrule < im->minval || im->gdes[i].yrule > im->maxval))
1399 im->gdes[i].legend[0] = '\0';
1401 if (im->gdes[i].gf == GF_VRULE &&
1402 (im->gdes[i].xrule < im->start || im->gdes[i].xrule > im->end))
1403 im->gdes[i].legend[0] = '\0';
1405 leg_cc = strlen(im->gdes[i].legend);
1407 /* is there a controle code ant the end of the legend string ? */
1408 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\') {
1409 prt_fctn = im->gdes[i].legend[leg_cc-1];
1411 im->gdes[i].legend[leg_cc] = '\0';
1415 /* remove exess space */
1416 while (prt_fctn=='g' &&
1418 im->gdes[i].legend[leg_cc-1]==' '){
1420 im->gdes[i].legend[leg_cc]='\0';
1423 legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1426 /* no interleg space if string ends in \g */
1427 fill += legspace[i];
1429 if (im->gdes[i].gf != GF_GPRINT &&
1430 im->gdes[i].gf != GF_COMMENT) {
1433 fill += gfx_get_text_width(im->canvas, fill+border,
1434 im->text_prop[TEXT_PROP_LEGEND].font,
1435 im->text_prop[TEXT_PROP_LEGEND].size,
1437 im->gdes[i].legend, 0);
1442 /* who said there was a special tag ... ?*/
1443 if (prt_fctn=='g') {
1446 if (prt_fctn == '\0') {
1447 if (i == im->gdes_c -1 ) prt_fctn ='l';
1449 /* is it time to place the legends ? */
1450 if (fill > im->ximg - 2*border){
1465 if (prt_fctn != '\0'){
1467 if (leg_c >= 2 && prt_fctn == 'j') {
1468 glue = (im->ximg - fill - 2* border) / (leg_c-1);
1472 if (prt_fctn =='c') leg_x = (im->ximg - fill) / 2.0;
1473 if (prt_fctn =='r') leg_x = im->ximg - fill - border;
1475 for(ii=mark;ii<=i;ii++){
1476 if(im->gdes[ii].legend[0]=='\0')
1478 im->gdes[ii].leg_x = leg_x;
1479 im->gdes[ii].leg_y = leg_y;
1481 gfx_get_text_width(im->canvas, leg_x,
1482 im->text_prop[TEXT_PROP_LEGEND].font,
1483 im->text_prop[TEXT_PROP_LEGEND].size,
1485 im->gdes[ii].legend, 0)
1488 if (im->gdes[ii].gf != GF_GPRINT &&
1489 im->gdes[ii].gf != GF_COMMENT)
1492 leg_y = leg_y + im->text_prop[TEXT_PROP_LEGEND].size*1.2;
1493 if (prt_fctn == 's') leg_y -= im->text_prop[TEXT_PROP_LEGEND].size*1.2;
1505 /* create a grid on the graph. it determines what to do
1506 from the values of xsize, start and end */
1508 /* the xaxis labels are determined from the number of seconds per pixel
1509 in the requested graph */
1514 calc_horizontal_grid(image_desc_t *im)
1520 int decimals, fractionals;
1522 im->ygrid_scale.labfact=2;
1524 range = im->maxval - im->minval;
1525 scaledrange = range / im->magfact;
1527 /* does the scale of this graph make it impossible to put lines
1528 on it? If so, give up. */
1529 if (isnan(scaledrange)) {
1533 /* find grid spaceing */
1535 if(isnan(im->ygridstep)){
1536 if(im->extra_flags & ALTYGRID) {
1537 /* find the value with max number of digits. Get number of digits */
1538 decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))));
1539 if(decimals <= 0) /* everything is small. make place for zero */
1542 fractionals = floor(log10(range));
1543 if(fractionals < 0) /* small amplitude. */
1544 sprintf(im->ygrid_scale.labfmt, "%%%d.%df", decimals - fractionals + 1, -fractionals + 1);
1546 sprintf(im->ygrid_scale.labfmt, "%%%d.1f", decimals + 1);
1547 im->ygrid_scale.gridstep = pow((double)10, (double)fractionals);
1548 if(im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1549 im->ygrid_scale.gridstep = 0.1;
1550 /* should have at least 5 lines but no more then 15 */
1551 if(range/im->ygrid_scale.gridstep < 5)
1552 im->ygrid_scale.gridstep /= 10;
1553 if(range/im->ygrid_scale.gridstep > 15)
1554 im->ygrid_scale.gridstep *= 10;
1555 if(range/im->ygrid_scale.gridstep > 5) {
1556 im->ygrid_scale.labfact = 1;
1557 if(range/im->ygrid_scale.gridstep > 8)
1558 im->ygrid_scale.labfact = 2;
1561 im->ygrid_scale.gridstep /= 5;
1562 im->ygrid_scale.labfact = 5;
1566 for(i=0;ylab[i].grid > 0;i++){
1567 pixel = im->ysize / (scaledrange / ylab[i].grid);
1568 if (gridind == -1 && pixel > 5) {
1575 if (pixel * ylab[gridind].lfac[i] >= 2 * im->text_prop[TEXT_PROP_AXIS].size) {
1576 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1581 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1584 im->ygrid_scale.gridstep = im->ygridstep;
1585 im->ygrid_scale.labfact = im->ylabfact;
1590 int draw_horizontal_grid(image_desc_t *im)
1594 char graph_label[100];
1595 double X0=im->xorigin;
1596 double X1=im->xorigin+im->xsize;
1598 int sgrid = (int)( im->minval / im->ygrid_scale.gridstep - 1);
1599 int egrid = (int)( im->maxval / im->ygrid_scale.gridstep + 1);
1600 scaledstep = im->ygrid_scale.gridstep/im->magfact;
1601 for (i = sgrid; i <= egrid; i++){
1602 double Y0=ytr(im,im->ygrid_scale.gridstep*i);
1603 if ( Y0 >= im->yorigin-im->ysize
1604 && Y0 <= im->yorigin){
1605 if(i % im->ygrid_scale.labfact == 0){
1606 if (i==0 || im->symbol == ' ') {
1608 if(im->extra_flags & ALTYGRID) {
1609 sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*i);
1612 sprintf(graph_label,"%4.1f",scaledstep*i);
1615 sprintf(graph_label,"%4.0f",scaledstep*i);
1619 sprintf(graph_label,"%4.1f %c",scaledstep*i, im->symbol);
1621 sprintf(graph_label,"%4.0f %c",scaledstep*i, im->symbol);
1625 gfx_new_text ( im->canvas,
1626 X0-im->text_prop[TEXT_PROP_AXIS].size/1.5, Y0,
1627 im->graph_col[GRC_FONT],
1628 im->text_prop[TEXT_PROP_AXIS].font,
1629 im->text_prop[TEXT_PROP_AXIS].size,
1630 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1632 gfx_new_dashed_line ( im->canvas,
1635 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1636 im->grid_dash_on, im->grid_dash_off);
1638 } else if (!(im->extra_flags & NOMINOR)) {
1639 gfx_new_dashed_line ( im->canvas,
1642 GRIDWIDTH, im->graph_col[GRC_GRID],
1643 im->grid_dash_on, im->grid_dash_off);
1651 /* logaritmic horizontal grid */
1653 horizontal_log_grid(image_desc_t *im)
1657 int minoridx=0, majoridx=0;
1658 char graph_label[100];
1660 double value, pixperstep, minstep;
1662 /* find grid spaceing */
1663 pixpex= (double)im->ysize / (log10(im->maxval) - log10(im->minval));
1665 if (isnan(pixpex)) {
1669 for(i=0;yloglab[i][0] > 0;i++){
1670 minstep = log10(yloglab[i][0]);
1671 for(ii=1;yloglab[i][ii+1] > 0;ii++){
1672 if(yloglab[i][ii+2]==0){
1673 minstep = log10(yloglab[i][ii+1])-log10(yloglab[i][ii]);
1677 pixperstep = pixpex * minstep;
1678 if(pixperstep > 5){minoridx = i;}
1679 if(pixperstep > 2 * im->text_prop[TEXT_PROP_LEGEND].size){majoridx = i;}
1683 X1=im->xorigin+im->xsize;
1684 /* paint minor grid */
1685 for (value = pow((double)10, log10(im->minval)
1686 - fmod(log10(im->minval),log10(yloglab[minoridx][0])));
1687 value <= im->maxval;
1688 value *= yloglab[minoridx][0]){
1689 if (value < im->minval) continue;
1691 while(yloglab[minoridx][++i] > 0){
1692 Y0 = ytr(im,value * yloglab[minoridx][i]);
1693 if (Y0 <= im->yorigin - im->ysize) break;
1694 gfx_new_dashed_line ( im->canvas,
1697 GRIDWIDTH, im->graph_col[GRC_GRID],
1698 im->grid_dash_on, im->grid_dash_off);
1702 /* paint major grid and labels*/
1703 for (value = pow((double)10, log10(im->minval)
1704 - fmod(log10(im->minval),log10(yloglab[majoridx][0])));
1705 value <= im->maxval;
1706 value *= yloglab[majoridx][0]){
1707 if (value < im->minval) continue;
1709 while(yloglab[majoridx][++i] > 0){
1710 Y0 = ytr(im,value * yloglab[majoridx][i]);
1711 if (Y0 <= im->yorigin - im->ysize) break;
1712 gfx_new_dashed_line ( im->canvas,
1715 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1716 im->grid_dash_on, im->grid_dash_off);
1718 sprintf(graph_label,"%3.0e",value * yloglab[majoridx][i]);
1719 gfx_new_text ( im->canvas,
1720 X0-im->text_prop[TEXT_PROP_AXIS].size/1.5, Y0,
1721 im->graph_col[GRC_FONT],
1722 im->text_prop[TEXT_PROP_AXIS].font,
1723 im->text_prop[TEXT_PROP_AXIS].size,
1724 im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1736 int xlab_sel; /* which sort of label and grid ? */
1737 time_t ti, tilab, timajor;
1739 char graph_label[100];
1740 double X0,Y0,Y1; /* points for filled graph and more*/
1743 /* the type of time grid is determined by finding
1744 the number of seconds per pixel in the graph */
1747 if(im->xlab_user.minsec == -1){
1748 factor=(im->end - im->start)/im->xsize;
1750 while ( xlab[xlab_sel+1].minsec != -1
1751 && xlab[xlab_sel+1].minsec <= factor){ xlab_sel++; }
1752 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
1753 im->xlab_user.gridst = xlab[xlab_sel].gridst;
1754 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
1755 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
1756 im->xlab_user.labtm = xlab[xlab_sel].labtm;
1757 im->xlab_user.labst = xlab[xlab_sel].labst;
1758 im->xlab_user.precis = xlab[xlab_sel].precis;
1759 im->xlab_user.stst = xlab[xlab_sel].stst;
1762 /* y coords are the same for every line ... */
1764 Y1 = im->yorigin-im->ysize;
1767 /* paint the minor grid */
1768 if (!(im->extra_flags & NOMINOR))
1770 for(ti = find_first_time(im->start,
1771 im->xlab_user.gridtm,
1772 im->xlab_user.gridst),
1773 timajor = find_first_time(im->start,
1774 im->xlab_user.mgridtm,
1775 im->xlab_user.mgridst);
1777 ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
1779 /* are we inside the graph ? */
1780 if (ti < im->start || ti > im->end) continue;
1781 while (timajor < ti) {
1782 timajor = find_next_time(timajor,
1783 im->xlab_user.mgridtm, im->xlab_user.mgridst);
1785 if (ti == timajor) continue; /* skip as falls on major grid line */
1787 gfx_new_dashed_line(im->canvas,X0,Y0+1, X0,Y1-1,GRIDWIDTH,
1788 im->graph_col[GRC_GRID],
1789 im->grid_dash_on, im->grid_dash_off);
1794 /* paint the major grid */
1795 for(ti = find_first_time(im->start,
1796 im->xlab_user.mgridtm,
1797 im->xlab_user.mgridst);
1799 ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
1801 /* are we inside the graph ? */
1802 if (ti < im->start || ti > im->end) continue;
1804 gfx_new_dashed_line(im->canvas,X0,Y0+3, X0,Y1-2,MGRIDWIDTH,
1805 im->graph_col[GRC_MGRID],
1806 im->grid_dash_on, im->grid_dash_off);
1809 /* paint the labels below the graph */
1810 for(ti = find_first_time(im->start,
1811 im->xlab_user.labtm,
1812 im->xlab_user.labst);
1814 ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
1816 tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
1817 /* are we inside the graph ? */
1818 if (ti < im->start || ti > im->end) continue;
1821 localtime_r(&tilab, &tm);
1822 strftime(graph_label,99,im->xlab_user.stst, &tm);
1824 # error "your libc has no strftime I guess we'll abort the exercise here."
1826 gfx_new_text ( im->canvas,
1827 xtr(im,tilab), Y0+im->text_prop[TEXT_PROP_AXIS].size/1.5,
1828 im->graph_col[GRC_FONT],
1829 im->text_prop[TEXT_PROP_AXIS].font,
1830 im->text_prop[TEXT_PROP_AXIS].size,
1831 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP,
1844 /* draw x and y axis */
1845 gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
1846 im->xorigin+im->xsize,im->yorigin-im->ysize,
1847 GRIDWIDTH, im->graph_col[GRC_GRID]);
1849 gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
1850 im->xorigin+im->xsize,im->yorigin-im->ysize,
1851 GRIDWIDTH, im->graph_col[GRC_GRID]);
1853 gfx_new_line ( im->canvas, im->xorigin-4,im->yorigin,
1854 im->xorigin+im->xsize+4,im->yorigin,
1855 MGRIDWIDTH, im->graph_col[GRC_GRID]);
1857 gfx_new_line ( im->canvas, im->xorigin,im->yorigin+4,
1858 im->xorigin,im->yorigin-im->ysize-4,
1859 MGRIDWIDTH, im->graph_col[GRC_GRID]);
1862 /* arrow for X axis direction */
1863 gfx_new_area ( im->canvas,
1864 im->xorigin+im->xsize+3, im->yorigin-3,
1865 im->xorigin+im->xsize+3, im->yorigin+4,
1866 im->xorigin+im->xsize+8, im->yorigin+0.5, /* LINEOFFSET */
1867 im->graph_col[GRC_ARROW]);
1874 grid_paint(image_desc_t *im)
1878 double X0,Y0; /* points for filled graph and more*/
1881 /* draw 3d border */
1882 node = gfx_new_area (im->canvas, 0,im->yimg,
1884 2,2,im->graph_col[GRC_SHADEA]);
1885 gfx_add_point( node , im->ximg - 2, 2 );
1886 gfx_add_point( node , im->ximg, 0 );
1887 gfx_add_point( node , 0,0 );
1888 /* gfx_add_point( node , 0,im->yimg ); */
1890 node = gfx_new_area (im->canvas, 2,im->yimg-2,
1891 im->ximg-2,im->yimg-2,
1893 im->graph_col[GRC_SHADEB]);
1894 gfx_add_point( node , im->ximg,0);
1895 gfx_add_point( node , im->ximg,im->yimg);
1896 gfx_add_point( node , 0,im->yimg);
1897 /* gfx_add_point( node , 0,im->yimg ); */
1900 if (im->draw_x_grid == 1 )
1903 if (im->draw_y_grid == 1){
1904 if(im->logarithmic){
1905 res = horizontal_log_grid(im);
1907 res = draw_horizontal_grid(im);
1910 /* dont draw horizontal grid if there is no min and max val */
1912 char *nodata = "No Data found";
1913 gfx_new_text(im->canvas,im->ximg/2, (2*im->yorigin-im->ysize) / 2,
1914 im->graph_col[GRC_FONT],
1915 im->text_prop[TEXT_PROP_AXIS].font,
1916 im->text_prop[TEXT_PROP_AXIS].size,
1917 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
1922 /* yaxis description */
1923 /* if (im->canvas->imgformat != IF_PNG) {*/
1925 gfx_new_text( im->canvas,
1926 7, (im->yorigin - im->ysize/2),
1927 im->graph_col[GRC_FONT],
1928 im->text_prop[TEXT_PROP_AXIS].font,
1929 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth,
1930 RRDGRAPH_YLEGEND_ANGLE,
1931 GFX_H_LEFT, GFX_V_CENTER,
1934 /* horrible hack until we can actually print vertically */
1938 for (n=0;n< (int)strlen(im->ylegend);n++) {
1939 s[0]=im->ylegend[n];
1941 gfx_new_text(im->canvas,7,im->text_prop[TEXT_PROP_AXIS].size*(n+1),
1942 im->graph_col[GRC_FONT],
1943 im->text_prop[TEXT_PROP_AXIS].font,
1944 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 270.0,
1945 GFX_H_CENTER, GFX_V_CENTER,
1952 gfx_new_text( im->canvas,
1953 im->ximg/2, im->text_prop[TEXT_PROP_TITLE].size,
1954 im->graph_col[GRC_FONT],
1955 im->text_prop[TEXT_PROP_TITLE].font,
1956 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
1957 GFX_H_CENTER, GFX_V_CENTER,
1961 if( !(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH) ) {
1962 for(i=0;i<im->gdes_c;i++){
1963 if(im->gdes[i].legend[0] =='\0')
1966 /* im->gdes[i].leg_y is the bottom of the legend */
1967 X0 = im->gdes[i].leg_x;
1968 Y0 = im->gdes[i].leg_y;
1970 if ( im->gdes[i].gf != GF_GPRINT
1971 && im->gdes[i].gf != GF_COMMENT) {
1974 boxH = gfx_get_text_width(im->canvas, 0,
1975 im->text_prop[TEXT_PROP_AXIS].font,
1976 im->text_prop[TEXT_PROP_AXIS].size,
1977 im->tabwidth,"M", 0) * 1.25;
1980 node = gfx_new_area(im->canvas,
1985 gfx_add_point ( node, X0+boxH, Y0-boxV );
1986 node = gfx_new_line(im->canvas,
1989 gfx_add_point(node,X0+boxH,Y0);
1990 gfx_add_point(node,X0+boxH,Y0-boxV);
1991 gfx_close_path(node);
1992 X0 += boxH / 1.25 * 2;
1994 gfx_new_text ( im->canvas, X0, Y0,
1995 im->graph_col[GRC_FONT],
1996 im->text_prop[TEXT_PROP_AXIS].font,
1997 im->text_prop[TEXT_PROP_AXIS].size,
1998 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
1999 im->gdes[i].legend );
2005 /*****************************************************
2006 * lazy check make sure we rely need to create this graph
2007 *****************************************************/
2009 int lazy_check(image_desc_t *im){
2012 struct stat imgstat;
2014 if (im->lazy == 0) return 0; /* no lazy option */
2015 if (stat(im->graphfile,&imgstat) != 0)
2016 return 0; /* can't stat */
2017 /* one pixel in the existing graph is more then what we would
2019 if (time(NULL) - imgstat.st_mtime >
2020 (im->end - im->start) / im->xsize)
2022 if ((fd = fopen(im->graphfile,"rb")) == NULL)
2023 return 0; /* the file does not exist */
2024 switch (im->canvas->imgformat) {
2026 size = PngSize(fd,&(im->ximg),&(im->yimg));
2036 pie_part(image_desc_t *im, gfx_color_t color,
2037 double PieCenterX, double PieCenterY, double Radius,
2038 double startangle, double endangle)
2042 double step=M_PI/50; /* Number of iterations for the circle;
2043 ** 10 is definitely too low, more than
2044 ** 50 seems to be overkill
2047 /* Strange but true: we have to work clockwise or else
2048 ** anti aliasing nor transparency don't work.
2050 ** This test is here to make sure we do it right, also
2051 ** this makes the for...next loop more easy to implement.
2052 ** The return will occur if the user enters a negative number
2053 ** (which shouldn't be done according to the specs) or if the
2054 ** programmers do something wrong (which, as we all know, never
2055 ** happens anyway :)
2057 if (endangle<startangle) return;
2059 /* Hidden feature: Radius decreases each full circle */
2061 while (angle>=2*M_PI) {
2066 node=gfx_new_area(im->canvas,
2067 PieCenterX+sin(startangle)*Radius,
2068 PieCenterY-cos(startangle)*Radius,
2071 PieCenterX+sin(endangle)*Radius,
2072 PieCenterY-cos(endangle)*Radius,
2074 for (angle=endangle;angle-startangle>=step;angle-=step) {
2076 PieCenterX+sin(angle)*Radius,
2077 PieCenterY-cos(angle)*Radius );
2082 graph_size_location(image_desc_t *im, int elements, int piechart )
2084 /* The actual size of the image to draw is determined from
2085 ** several sources. The size given on the command line is
2086 ** the graph area but we need more as we have to draw labels
2087 ** and other things outside the graph area
2090 /* +-+-------------------------------------------+
2091 ** |l|.................title.....................|
2092 ** |e+--+-------------------------------+--------+
2095 ** |l| l| main graph area | chart |
2098 ** |r+--+-------------------------------+--------+
2099 ** |e| | x-axis labels | |
2100 ** |v+--+-------------------------------+--------+
2101 ** | |..............legends......................|
2102 ** +-+-------------------------------------------+
2104 int Xvertical=0, Yvertical=0,
2105 Xtitle =0, Ytitle =0,
2106 Xylabel =0, Yylabel =0,
2109 Xxlabel =0, Yxlabel =0,
2111 Xlegend =0, Ylegend =0,
2113 Xspacing =10, Yspacing =10;
2115 if (im->extra_flags & ONLY_GRAPH) {
2116 if ( im->ysize > 32 ) {
2117 rrd_set_error("height > 32 is not possible with --only-graph option");
2123 if (im->ylegend[0] != '\0') {
2124 Xvertical = im->text_prop[TEXT_PROP_LEGEND].size *2;
2125 Yvertical = im->text_prop[TEXT_PROP_LEGEND].size * (strlen(im->ylegend)+1);
2129 if (im->title[0] != '\0') {
2130 /* The title is placed "inbetween" two text lines so it
2131 ** automatically has some vertical spacing. The horizontal
2132 ** spacing is added here, on each side.
2134 Xtitle = gfx_get_text_width(im->canvas, 0,
2135 im->text_prop[TEXT_PROP_TITLE].font,
2136 im->text_prop[TEXT_PROP_TITLE].size,
2138 im->title, 0) + 2*Xspacing;
2139 Ytitle = im->text_prop[TEXT_PROP_TITLE].size*2;
2145 if (im->draw_x_grid) {
2147 Yxlabel=im->text_prop[TEXT_PROP_LEGEND].size *2;
2149 if (im->draw_y_grid) {
2150 Xylabel=im->text_prop[TEXT_PROP_LEGEND].size *6;
2156 im->piesize=im->xsize<im->ysize?im->xsize:im->ysize;
2161 /* Now calculate the total size. Insert some spacing where
2162 desired. im->xorigin and im->yorigin need to correspond
2163 with the lower left corner of the main graph area or, if
2164 this one is not set, the imaginary box surrounding the
2167 /* The legend width cannot yet be determined, as a result we
2168 ** have problems adjusting the image to it. For now, we just
2169 ** forget about it at all; the legend will have to fit in the
2170 ** size already allocated.
2174 if ( !(im->extra_flags & ONLY_GRAPH) ) {
2175 im->ximg = Xylabel + Xmain + Xpie + Xspacing;
2178 if (Xmain) im->ximg += Xspacing;
2179 if (Xpie) im->ximg += Xspacing;
2181 if (im->extra_flags & ONLY_GRAPH) {
2184 im->xorigin = Xspacing + Xylabel;
2187 if (Xtitle > im->ximg) im->ximg = Xtitle;
2189 im->ximg += Xvertical;
2190 im->xorigin += Xvertical;
2194 /* The vertical size is interesting... we need to compare
2195 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend} with Yvertical
2196 ** however we need to know {Ytitle+Ymain+Yxlabel} in order to
2197 ** start even thinking about Ylegend.
2199 ** Do it in three portions: First calculate the inner part,
2200 ** then do the legend, then adjust the total height of the img.
2203 /* reserve space for main and/or pie */
2205 if (im->extra_flags & ONLY_GRAPH) {
2208 im->yimg = Ymain + Yxlabel;
2211 if (im->yimg < Ypie) im->yimg = Ypie;
2213 if (im->extra_flags & ONLY_GRAPH) {
2214 im->yorigin = im->yimg;
2216 im->yorigin = im->yimg - Yxlabel;
2219 /* reserve space for the title *or* some padding above the graph */
2222 im->yorigin += Ytitle;
2224 im->yimg += Yspacing;
2225 im->yorigin += Yspacing;
2227 /* reserve space for padding below the graph */
2228 im->yimg += Yspacing;
2231 /* Determine where to place the legends onto the image.
2232 ** Adjust im->yimg to match the space requirements.
2234 if(leg_place(im)==-1)
2237 /* last of three steps: check total height of image */
2238 if (im->yimg < Yvertical) im->yimg = Yvertical;
2241 if (Xlegend > im->ximg) {
2243 /* reposition Pie */
2247 /* The pie is placed in the upper right hand corner,
2248 ** just below the title (if any) and with sufficient
2252 im->pie_x = im->ximg - Xspacing - Xpie/2;
2253 im->pie_y = im->yorigin-Ymain+Ypie/2;
2255 im->pie_x = im->ximg/2;
2256 im->pie_y = im->yorigin-Ypie/2;
2262 /* draw that picture thing ... */
2264 graph_paint(image_desc_t *im, char ***calcpr)
2267 int lazy = lazy_check(im);
2269 double PieStart=0.0;
2273 double areazero = 0.0;
2274 enum gf_en stack_gf = GF_PRINT;
2275 graph_desc_t *lastgdes = NULL;
2277 /* if we are lazy and there is nothing to PRINT ... quit now */
2278 if (lazy && im->prt_c==0) return 0;
2280 /* pull the data from the rrd files ... */
2282 if(data_fetch(im)==-1)
2285 /* evaluate VDEF and CDEF operations ... */
2286 if(data_calc(im)==-1)
2289 /* check if we need to draw a piechart */
2290 for(i=0;i<im->gdes_c;i++){
2291 if (im->gdes[i].gf == GF_PART) {
2297 /* calculate and PRINT and GPRINT definitions. We have to do it at
2298 * this point because it will affect the length of the legends
2299 * if there are no graph elements we stop here ...
2300 * if we are lazy, try to quit ...
2302 i=print_calc(im,calcpr);
2304 if(((i==0)&&(piechart==0)) || lazy) return 0;
2306 /* If there's only the pie chart to draw, signal this */
2307 if (i==0) piechart=2;
2309 /* get actual drawing data and find min and max values*/
2310 if(data_proc(im)==-1)
2313 if(!im->logarithmic){si_unit(im);} /* identify si magnitude Kilo, Mega Giga ? */
2315 if(!im->rigid && ! im->logarithmic)
2316 expand_range(im); /* make sure the upper and lower limit are
2319 if (!calc_horizontal_grid(im))
2326 /**************************************************************
2327 *** Calculating sizes and locations became a bit confusing ***
2328 *** so I moved this into a separate function. ***
2329 **************************************************************/
2330 if(graph_size_location(im,i,piechart)==-1)
2333 /* the actual graph is created by going through the individual
2334 graph elements and then drawing them */
2336 node=gfx_new_area ( im->canvas,
2340 im->graph_col[GRC_BACK]);
2342 gfx_add_point(node,0, im->yimg);
2344 if (piechart != 2) {
2345 node=gfx_new_area ( im->canvas,
2346 im->xorigin, im->yorigin,
2347 im->xorigin + im->xsize, im->yorigin,
2348 im->xorigin + im->xsize, im->yorigin-im->ysize,
2349 im->graph_col[GRC_CANVAS]);
2351 gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
2353 if (im->minval > 0.0)
2354 areazero = im->minval;
2355 if (im->maxval < 0.0)
2356 areazero = im->maxval;
2357 if( !(im->extra_flags & ONLY_GRAPH) )
2362 pie_part(im,im->graph_col[GRC_CANVAS],im->pie_x,im->pie_y,im->piesize*0.5,0,2*M_PI);
2365 for(i=0;i<im->gdes_c;i++){
2366 switch(im->gdes[i].gf){
2379 for (ii = 0; ii < im->xsize; ii++)
2381 if (!isnan(im->gdes[i].p_data[ii]) &&
2382 im->gdes[i].p_data[ii] > 0.0)
2384 /* generate a tick */
2385 gfx_new_line(im->canvas, im -> xorigin + ii,
2386 im -> yorigin - (im -> gdes[i].yrule * im -> ysize),
2390 im -> gdes[i].col );
2396 stack_gf = im->gdes[i].gf;
2398 /* fix data points at oo and -oo */
2399 for(ii=0;ii<im->xsize;ii++){
2400 if (isinf(im->gdes[i].p_data[ii])){
2401 if (im->gdes[i].p_data[ii] > 0) {
2402 im->gdes[i].p_data[ii] = im->maxval ;
2404 im->gdes[i].p_data[ii] = im->minval ;
2410 if (im->gdes[i].col != 0x0){
2411 /* GF_LINE and friend */
2412 if(stack_gf == GF_LINE ){
2414 for(ii=1;ii<im->xsize;ii++){
2415 if ( ! isnan(im->gdes[i].p_data[ii-1])
2416 && ! isnan(im->gdes[i].p_data[ii])){
2418 node = gfx_new_line(im->canvas,
2419 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2420 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2421 im->gdes[i].linewidth,
2424 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2433 for(ii=1;ii<im->xsize;ii++){
2435 if ( ! isnan(im->gdes[i].p_data[ii-1])
2436 && ! isnan(im->gdes[i].p_data[ii])){
2440 if (im->gdes[i].gf == GF_STACK) {
2442 if ( (im->gdes[i].gf == GF_STACK)
2443 || (im->gdes[i].stack) ) {
2445 ybase = ytr(im,lastgdes->p_data[ii-1]);
2447 ybase = ytr(im,areazero);
2450 node = gfx_new_area(im->canvas,
2451 ii-1+im->xorigin,ybase,
2452 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2453 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2457 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2461 if ( node != NULL && (ii+1==im->xsize || isnan(im->gdes[i].p_data[ii]) )){
2462 /* GF_AREA STACK type*/
2464 if (im->gdes[i].gf == GF_STACK ) {
2466 if ( (im->gdes[i].gf == GF_STACK)
2467 || (im->gdes[i].stack) ) {
2469 for (iii=ii-1;iii>area_start;iii--){
2470 gfx_add_point(node,iii+im->xorigin,ytr(im,lastgdes->p_data[iii]));
2473 gfx_add_point(node,ii+im->xorigin,ytr(im,areazero));
2478 } /* else GF_LINE */
2479 } /* if color != 0x0 */
2480 /* make sure we do not run into trouble when stacking on NaN */
2481 for(ii=0;ii<im->xsize;ii++){
2482 if (isnan(im->gdes[i].p_data[ii])) {
2483 if (lastgdes && (im->gdes[i].gf == GF_STACK)) {
2484 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
2486 im->gdes[i].p_data[ii] = ytr(im,areazero);
2490 lastgdes = &(im->gdes[i]);
2493 if(isnan(im->gdes[i].yrule)) /* fetch variable */
2494 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2496 if (finite(im->gdes[i].yrule)) { /* even the fetched var can be NaN */
2497 pie_part(im,im->gdes[i].col,
2498 im->pie_x,im->pie_y,im->piesize*0.4,
2499 M_PI*2.0*PieStart/100.0,
2500 M_PI*2.0*(PieStart+im->gdes[i].yrule)/100.0);
2501 PieStart += im->gdes[i].yrule;
2510 /* grid_paint also does the text */
2511 if( !(im->extra_flags & ONLY_GRAPH) )
2514 /* the RULES are the last thing to paint ... */
2515 for(i=0;i<im->gdes_c;i++){
2517 switch(im->gdes[i].gf){
2519 if(isnan(im->gdes[i].yrule)) { /* fetch variable */
2520 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2522 if(im->gdes[i].yrule >= im->minval
2523 && im->gdes[i].yrule <= im->maxval)
2524 gfx_new_line(im->canvas,
2525 im->xorigin,ytr(im,im->gdes[i].yrule),
2526 im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2527 1.0,im->gdes[i].col);
2530 if(im->gdes[i].xrule == 0) { /* fetch variable */
2531 im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
2533 if(im->gdes[i].xrule >= im->start
2534 && im->gdes[i].xrule <= im->end)
2535 gfx_new_line(im->canvas,
2536 xtr(im,im->gdes[i].xrule),im->yorigin,
2537 xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2538 1.0,im->gdes[i].col);
2546 if (strcmp(im->graphfile,"-")==0) {
2547 fo = im->graphhandle ? im->graphhandle : stdout;
2549 /* Change translation mode for stdout to BINARY */
2550 _setmode( _fileno( fo ), O_BINARY );
2553 if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2554 rrd_set_error("Opening '%s' for write: %s",im->graphfile,
2555 rrd_strerror(errno));
2559 gfx_render (im->canvas,im->ximg,im->yimg,0x0,fo);
2560 if (strcmp(im->graphfile,"-") != 0)
2566 /*****************************************************
2568 *****************************************************/
2571 gdes_alloc(image_desc_t *im){
2573 unsigned long def_step = (im->end-im->start)/im->xsize;
2575 if (im->step > def_step) /* step can be increassed ... no decreassed */
2576 def_step = im->step;
2580 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2581 * sizeof(graph_desc_t)))==NULL){
2582 rrd_set_error("realloc graph_descs");
2587 im->gdes[im->gdes_c-1].step=def_step;
2588 im->gdes[im->gdes_c-1].stack=0;
2589 im->gdes[im->gdes_c-1].debug=0;
2590 im->gdes[im->gdes_c-1].start=im->start;
2591 im->gdes[im->gdes_c-1].end=im->end;
2592 im->gdes[im->gdes_c-1].vname[0]='\0';
2593 im->gdes[im->gdes_c-1].data=NULL;
2594 im->gdes[im->gdes_c-1].ds_namv=NULL;
2595 im->gdes[im->gdes_c-1].data_first=0;
2596 im->gdes[im->gdes_c-1].p_data=NULL;
2597 im->gdes[im->gdes_c-1].rpnp=NULL;
2598 im->gdes[im->gdes_c-1].shift=0;
2599 im->gdes[im->gdes_c-1].col = 0x0;
2600 im->gdes[im->gdes_c-1].legend[0]='\0';
2601 im->gdes[im->gdes_c-1].rrd[0]='\0';
2602 im->gdes[im->gdes_c-1].ds=-1;
2603 im->gdes[im->gdes_c-1].p_data=NULL;
2604 im->gdes[im->gdes_c-1].yrule=DNAN;
2605 im->gdes[im->gdes_c-1].xrule=0;
2609 /* copies input untill the first unescaped colon is found
2610 or until input ends. backslashes have to be escaped as well */
2612 scan_for_col(char *input, int len, char *output)
2617 input[inp] != ':' &&
2620 if (input[inp] == '\\' &&
2621 input[inp+1] != '\0' &&
2622 (input[inp+1] == '\\' ||
2623 input[inp+1] == ':')){
2624 output[outp++] = input[++inp];
2627 output[outp++] = input[inp];
2630 output[outp] = '\0';
2633 /* Some surgery done on this function, it became ridiculously big.
2635 ** - initializing now in rrd_graph_init()
2636 ** - options parsing now in rrd_graph_options()
2637 ** - script parsing now in rrd_graph_script()
2640 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize, FILE *stream)
2644 rrd_graph_init(&im);
2645 im.graphhandle = stream;
2647 rrd_graph_options(argc,argv,&im);
2648 if (rrd_test_error()) {
2653 if (strlen(argv[optind])>=MAXPATH) {
2654 rrd_set_error("filename (including path) too long");
2658 strncpy(im.graphfile,argv[optind],MAXPATH-1);
2659 im.graphfile[MAXPATH-1]='\0';
2661 rrd_graph_script(argc,argv,&im,1);
2662 if (rrd_test_error()) {
2667 /* Everything is now read and the actual work can start */
2670 if (graph_paint(&im,prdata)==-1){
2675 /* The image is generated and needs to be output.
2676 ** Also, if needed, print a line with information about the image.
2684 /* maybe prdata is not allocated yet ... lets do it now */
2685 if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
2686 rrd_set_error("malloc imginfo");
2690 if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
2692 rrd_set_error("malloc imginfo");
2695 filename=im.graphfile+strlen(im.graphfile);
2696 while(filename > im.graphfile) {
2697 if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
2701 sprintf((*prdata)[0],im.imginfo,filename,(long)(im.canvas->zoom*im.ximg),(long)(im.canvas->zoom*im.yimg));
2708 rrd_graph_init(image_desc_t *im)
2715 #ifdef HAVE_SETLOCALE
2716 setlocale(LC_TIME,"");
2719 im->xlab_user.minsec = -1;
2725 im->ylegend[0] = '\0';
2726 im->title[0] = '\0';
2729 im->unitsexponent= 9999;
2735 im->logarithmic = 0;
2736 im->ygridstep = DNAN;
2737 im->draw_x_grid = 1;
2738 im->draw_y_grid = 1;
2743 im->canvas = gfx_new_canvas();
2744 im->grid_dash_on = 1;
2745 im->grid_dash_off = 1;
2747 for(i=0;i<DIM(graph_col);i++)
2748 im->graph_col[i]=graph_col[i];
2752 windir = getenv("windir");
2753 /* %windir% is something like D:\windows or C:\winnt */
2754 if (windir != NULL) {
2755 strcpy(rrd_win_default_font,windir);
2756 strcat(rrd_win_default_font,"\\fonts\\cour.ttf");
2757 for(i=0;i<DIM(text_prop);i++)
2758 text_prop[i].font = rrd_win_default_font;
2762 for(i=0;i<DIM(text_prop);i++){
2763 im->text_prop[i].size = text_prop[i].size;
2764 im->text_prop[i].font = text_prop[i].font;
2769 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
2772 char *parsetime_error = NULL;
2773 char scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
2774 time_t start_tmp=0,end_tmp=0;
2776 struct rrd_time_value start_tv, end_tv;
2779 parsetime("end-24h", &start_tv);
2780 parsetime("now", &end_tv);
2783 static struct option long_options[] =
2785 {"start", required_argument, 0, 's'},
2786 {"end", required_argument, 0, 'e'},
2787 {"x-grid", required_argument, 0, 'x'},
2788 {"y-grid", required_argument, 0, 'y'},
2789 {"vertical-label",required_argument,0,'v'},
2790 {"width", required_argument, 0, 'w'},
2791 {"height", required_argument, 0, 'h'},
2792 {"interlaced", no_argument, 0, 'i'},
2793 {"upper-limit",required_argument, 0, 'u'},
2794 {"lower-limit",required_argument, 0, 'l'},
2795 {"rigid", no_argument, 0, 'r'},
2796 {"base", required_argument, 0, 'b'},
2797 {"logarithmic",no_argument, 0, 'o'},
2798 {"color", required_argument, 0, 'c'},
2799 {"font", required_argument, 0, 'n'},
2800 {"title", required_argument, 0, 't'},
2801 {"imginfo", required_argument, 0, 'f'},
2802 {"imgformat", required_argument, 0, 'a'},
2803 {"lazy", no_argument, 0, 'z'},
2804 {"zoom", required_argument, 0, 'm'},
2805 {"no-legend", no_argument, 0, 'g'},
2806 {"only-graph", no_argument, 0, 'j'},
2807 {"alt-y-grid", no_argument, 0, 'Y'},
2808 {"no-minor", no_argument, 0, 'I'},
2809 {"alt-autoscale", no_argument, 0, 'A'},
2810 {"alt-autoscale-max", no_argument, 0, 'M'},
2811 {"units-exponent",required_argument, 0, 'X'},
2812 {"step", required_argument, 0, 'S'},
2813 {"no-gridfit", no_argument, 0, 'N'},
2815 int option_index = 0;
2819 opt = getopt_long(argc, argv,
2820 "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:I:zgjYAMX:S:N",
2821 long_options, &option_index);
2828 im->extra_flags |= NOMINOR;
2831 im->extra_flags |= ALTYGRID;
2834 im->extra_flags |= ALTAUTOSCALE;
2837 im->extra_flags |= ALTAUTOSCALE_MAX;
2840 im->extra_flags |= ONLY_GRAPH;
2843 im->extra_flags |= NOLEGEND;
2846 im->unitsexponent = atoi(optarg);
2849 im->step = atoi(optarg);
2855 if ((parsetime_error = parsetime(optarg, &start_tv))) {
2856 rrd_set_error( "start time: %s", parsetime_error );
2861 if ((parsetime_error = parsetime(optarg, &end_tv))) {
2862 rrd_set_error( "end time: %s", parsetime_error );
2867 if(strcmp(optarg,"none") == 0){
2873 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
2875 &im->xlab_user.gridst,
2877 &im->xlab_user.mgridst,
2879 &im->xlab_user.labst,
2880 &im->xlab_user.precis,
2881 &stroff) == 7 && stroff != 0){
2882 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
2883 if((int)(im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
2884 rrd_set_error("unknown keyword %s",scan_gtm);
2886 } else if ((int)(im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
2887 rrd_set_error("unknown keyword %s",scan_mtm);
2889 } else if ((int)(im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
2890 rrd_set_error("unknown keyword %s",scan_ltm);
2893 im->xlab_user.minsec = 1;
2894 im->xlab_user.stst = im->xlab_form;
2896 rrd_set_error("invalid x-grid format");
2902 if(strcmp(optarg,"none") == 0){
2910 &im->ylabfact) == 2) {
2911 if(im->ygridstep<=0){
2912 rrd_set_error("grid step must be > 0");
2914 } else if (im->ylabfact < 1){
2915 rrd_set_error("label factor must be > 0");
2919 rrd_set_error("invalid y-grid format");
2924 strncpy(im->ylegend,optarg,150);
2925 im->ylegend[150]='\0';
2928 im->maxval = atof(optarg);
2931 im->minval = atof(optarg);
2934 im->base = atol(optarg);
2935 if(im->base != 1024 && im->base != 1000 ){
2936 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
2941 long_tmp = atol(optarg);
2942 if (long_tmp < 10) {
2943 rrd_set_error("width below 10 pixels");
2946 im->xsize = long_tmp;
2949 long_tmp = atol(optarg);
2950 if (long_tmp < 10) {
2951 rrd_set_error("height below 10 pixels");
2954 im->ysize = long_tmp;
2957 im->canvas->interlaced = 1;
2963 im->imginfo = optarg;
2966 if((int)(im->canvas->imgformat = if_conv(optarg)) == -1) {
2967 rrd_set_error("unsupported graphics format '%s'",optarg);
2975 im->logarithmic = 1;
2976 if (isnan(im->minval))
2982 col_nam,&color) == 2){
2984 if((ci=grc_conv(col_nam)) != -1){
2985 im->graph_col[ci]=color;
2987 rrd_set_error("invalid color name '%s'",col_nam);
2990 rrd_set_error("invalid color def format");
2995 /* originally this used char *prop = "" and
2996 ** char *font = "dummy" however this results
2997 ** in a SEG fault, at least on RH7.1
2999 ** The current implementation isn't proper
3000 ** either, font is never freed and prop uses
3001 ** a fixed width string
3010 prop,&size,font) == 3){
3012 if((sindex=text_prop_conv(prop)) != -1){
3013 im->text_prop[sindex].size=size;
3014 im->text_prop[sindex].font=font;
3015 if (sindex==0) { /* the default */
3016 im->text_prop[TEXT_PROP_TITLE].size=size;
3017 im->text_prop[TEXT_PROP_TITLE].font=font;
3018 im->text_prop[TEXT_PROP_AXIS].size=size;
3019 im->text_prop[TEXT_PROP_AXIS].font=font;
3020 im->text_prop[TEXT_PROP_UNIT].size=size;
3021 im->text_prop[TEXT_PROP_UNIT].font=font;
3022 im->text_prop[TEXT_PROP_LEGEND].size=size;
3023 im->text_prop[TEXT_PROP_LEGEND].font=font;
3026 rrd_set_error("invalid fonttag '%s'",prop);
3030 rrd_set_error("invalid text property format");
3036 im->canvas->zoom = atof(optarg);
3037 if (im->canvas->zoom <= 0.0) {
3038 rrd_set_error("zoom factor must be > 0");
3043 strncpy(im->title,optarg,150);
3044 im->title[150]='\0';
3049 rrd_set_error("unknown option '%c'", optopt);
3051 rrd_set_error("unknown option '%s'",argv[optind-1]);
3056 if (optind >= argc) {
3057 rrd_set_error("missing filename");
3061 if (im->logarithmic == 1 && (im->minval <= 0 || isnan(im->minval))){
3062 rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");
3066 if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
3067 /* error string is set in parsetime.c */
3071 if (start_tmp < 3600*24*365*10){
3072 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
3076 if (end_tmp < start_tmp) {
3077 rrd_set_error("start (%ld) should be less than end (%ld)",
3078 start_tmp, end_tmp);
3082 im->start = start_tmp;
3087 rrd_graph_check_vname(image_desc_t *im, char *varname, char *err)
3089 if ((im->gdes[im->gdes_c-1].vidx=find_var(im,varname))==-1) {
3090 rrd_set_error("Unknown variable '%s' in %s",varname,err);
3096 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
3099 graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
3101 color=strstr(var,"#");
3104 rrd_set_error("Found no color in %s",err);
3113 rest=strstr(color,":");
3121 sscanf(color,"#%6lx%n",&col,&n);
3122 col = (col << 8) + 0xff /* shift left by 8 */;
3123 if (n!=7) rrd_set_error("Color problem in %s",err);
3126 sscanf(color,"#%8lx%n",&col,&n);
3129 rrd_set_error("Color problem in %s",err);
3131 if (rrd_test_error()) return 0;
3137 rrd_graph_legend(graph_desc_t *gdp, char *line)
3141 i=scan_for_col(line,FMT_LEG_LEN,gdp->legend);
3143 return (strlen(&line[i])==0);
3147 int bad_format(char *fmt) {
3151 while (*ptr != '\0')
3152 if (*ptr++ == '%') {
3154 /* line cannot end with percent char */
3155 if (*ptr == '\0') return 1;
3157 /* '%s', '%S' and '%%' are allowed */
3158 if (*ptr == 's' || *ptr == 'S' || *ptr == '%') ptr++;
3160 /* or else '% 6.2lf' and such are allowed */
3163 /* optional padding character */
3164 if (*ptr == ' ' || *ptr == '+' || *ptr == '-') ptr++;
3166 /* This should take care of 'm.n' with all three optional */
3167 while (*ptr >= '0' && *ptr <= '9') ptr++;
3168 if (*ptr == '.') ptr++;
3169 while (*ptr >= '0' && *ptr <= '9') ptr++;
3171 /* Either 'le', 'lf' or 'lg' must follow here */
3172 if (*ptr++ != 'l') return 1;
3173 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g') ptr++;
3184 vdef_parse(gdes,str)
3185 struct graph_desc_t *gdes;
3188 /* A VDEF currently is either "func" or "param,func"
3189 * so the parsing is rather simple. Change if needed.
3196 sscanf(str,"%le,%29[A-Z]%n",¶m,func,&n);
3197 if (n== (int)strlen(str)) { /* matched */
3201 sscanf(str,"%29[A-Z]%n",func,&n);
3202 if (n== (int)strlen(str)) { /* matched */
3205 rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3212 if (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3213 else if (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3214 else if (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3215 else if (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3216 else if (!strcmp("TOTAL", func)) gdes->vf.op = VDEF_TOTAL;
3217 else if (!strcmp("FIRST", func)) gdes->vf.op = VDEF_FIRST;
3218 else if (!strcmp("LAST", func)) gdes->vf.op = VDEF_LAST;
3220 rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3227 switch (gdes->vf.op) {
3229 if (isnan(param)) { /* no parameter given */
3230 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3236 if (param>=0.0 && param<=100.0) {
3237 gdes->vf.param = param;
3238 gdes->vf.val = DNAN; /* undefined */
3239 gdes->vf.when = 0; /* undefined */
3241 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3255 gdes->vf.param = DNAN;
3256 gdes->vf.val = DNAN;
3259 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3276 graph_desc_t *src,*dst;
3280 dst = &im->gdes[gdi];
3281 src = &im->gdes[dst->vidx];
3282 data = src->data + src->ds;
3283 steps = (src->end - src->start) / src->step;
3286 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3293 switch (dst->vf.op) {
3294 case VDEF_PERCENT: {
3295 rrd_value_t * array;
3299 if ((array = malloc(steps*sizeof(double)))==NULL) {
3300 rrd_set_error("malloc VDEV_PERCENT");
3303 for (step=0;step < steps; step++) {
3304 array[step]=data[step*src->ds_cnt];
3306 qsort(array,step,sizeof(double),vdef_percent_compar);
3308 field = (steps-1)*dst->vf.param/100;
3309 dst->vf.val = array[field];
3310 dst->vf.when = 0; /* no time component */
3313 for(step=0;step<steps;step++)
3314 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3320 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3321 if (step == steps) {
3325 dst->vf.val = data[step*src->ds_cnt];
3326 dst->vf.when = src->start + (step+1)*src->step;
3328 while (step != steps) {
3329 if (finite(data[step*src->ds_cnt])) {
3330 if (data[step*src->ds_cnt] > dst->vf.val) {
3331 dst->vf.val = data[step*src->ds_cnt];
3332 dst->vf.when = src->start + (step+1)*src->step;
3339 case VDEF_AVERAGE: {
3342 for (step=0;step<steps;step++) {
3343 if (finite(data[step*src->ds_cnt])) {
3344 sum += data[step*src->ds_cnt];
3349 if (dst->vf.op == VDEF_TOTAL) {
3350 dst->vf.val = sum*src->step;
3351 dst->vf.when = cnt*src->step; /* not really "when" */
3353 dst->vf.val = sum/cnt;
3354 dst->vf.when = 0; /* no time component */
3364 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3365 if (step == steps) {
3369 dst->vf.val = data[step*src->ds_cnt];
3370 dst->vf.when = src->start + (step+1)*src->step;
3372 while (step != steps) {
3373 if (finite(data[step*src->ds_cnt])) {
3374 if (data[step*src->ds_cnt] < dst->vf.val) {
3375 dst->vf.val = data[step*src->ds_cnt];
3376 dst->vf.when = src->start + (step+1)*src->step;
3383 /* The time value returned here is one step before the
3384 * actual time value. This is the start of the first
3388 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3389 if (step == steps) { /* all entries were NaN */
3393 dst->vf.val = data[step*src->ds_cnt];
3394 dst->vf.when = src->start + step*src->step;
3398 /* The time value returned here is the
3399 * actual time value. This is the end of the last
3403 while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3404 if (step < 0) { /* all entries were NaN */
3408 dst->vf.val = data[step*src->ds_cnt];
3409 dst->vf.when = src->start + (step+1)*src->step;
3416 /* NaN < -INF < finite_values < INF */
3418 vdef_percent_compar(a,b)
3421 /* Equality is not returned; this doesn't hurt except
3422 * (maybe) for a little performance.
3425 /* First catch NaN values. They are smallest */
3426 if (isnan( *(double *)a )) return -1;
3427 if (isnan( *(double *)b )) return 1;
3429 /* NaN doesn't reach this part so INF and -INF are extremes.
3430 * The sign from isinf() is compatible with the sign we return
3432 if (isinf( *(double *)a )) return isinf( *(double *)a );
3433 if (isinf( *(double *)b )) return isinf( *(double *)b );
3435 /* If we reach this, both values must be finite */
3436 if ( *(double *)a < *(double *)b ) return -1; else return 1;