1 /****************************************************************************
2 * RRDtool 1.2.15 Copyright by Tobi Oetiker, 1997-2006
3 ****************************************************************************
4 * rrd__graph.c produce graphs from data in rrdfiles
5 ****************************************************************************/
15 #if defined(WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
28 #include "rrd_graph.h"
30 /* some constant definitions */
34 #ifndef RRD_DEFAULT_FONT
35 /* there is special code later to pick Cour.ttf when running on windows */
36 #define RRD_DEFAULT_FONT "DejaVuSansMono-Roman.ttf"
39 text_prop_t text_prop[] = {
40 { 8.0, RRD_DEFAULT_FONT }, /* default */
41 { 9.0, RRD_DEFAULT_FONT }, /* title */
42 { 7.0, RRD_DEFAULT_FONT }, /* axis */
43 { 8.0, RRD_DEFAULT_FONT }, /* unit */
44 { 8.0, RRD_DEFAULT_FONT } /* legend */
48 {0, 0, TMT_SECOND,30, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
49 {2, 0, TMT_MINUTE,1, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
50 {5, 0, TMT_MINUTE,2, TMT_MINUTE,10, TMT_MINUTE,10, 0,"%H:%M"},
51 {10, 0, TMT_MINUTE,5, TMT_MINUTE,20, TMT_MINUTE,20, 0,"%H:%M"},
52 {30, 0, TMT_MINUTE,10, TMT_HOUR,1, TMT_HOUR,1, 0,"%H:%M"},
53 {60, 0, TMT_MINUTE,30, TMT_HOUR,2, TMT_HOUR,2, 0,"%H:%M"},
54 {60, 24*3600, TMT_MINUTE,30, TMT_HOUR,2, TMT_HOUR,4, 0,"%a %H:%M"},
55 {180, 0, TMT_HOUR,1, TMT_HOUR,6, TMT_HOUR,6, 0,"%H:%M"},
56 {180, 24*3600, TMT_HOUR,1, TMT_HOUR,6, TMT_HOUR,12, 0,"%a %H:%M"},
57 /*{300, 0, TMT_HOUR,3, TMT_HOUR,12, TMT_HOUR,12, 12*3600,"%a %p"}, this looks silly*/
58 {600, 0, TMT_HOUR,6, TMT_DAY,1, TMT_DAY,1, 24*3600,"%a"},
59 {1200, 0, TMT_HOUR,6, TMT_DAY,1, TMT_DAY,1, 24*3600,"%d"},
60 {1800, 0, TMT_HOUR,12, TMT_DAY,1, TMT_DAY,2, 24*3600,"%a %d"},
61 {2400, 0, TMT_HOUR,12, TMT_DAY,1, TMT_DAY,2, 24*3600,"%a"},
62 {3600, 0, TMT_DAY,1, TMT_WEEK,1, TMT_WEEK,1, 7*24*3600,"Week %V"},
63 {3*3600, 0, TMT_WEEK,1, TMT_MONTH,1, TMT_WEEK,2, 7*24*3600,"Week %V"},
64 {6*3600, 0, TMT_MONTH,1, TMT_MONTH,1, TMT_MONTH,1, 30*24*3600,"%b"},
65 {48*3600, 0, TMT_MONTH,1, TMT_MONTH,3, TMT_MONTH,3, 30*24*3600,"%b"},
66 {315360, 0, TMT_MONTH,3, TMT_YEAR,1, TMT_YEAR,1, 365*24*3600,"%Y"},
67 {10*24*3600, 0, TMT_YEAR,1, TMT_YEAR,1, TMT_YEAR,1, 365*24*3600,"%y"},
68 {-1,0,TMT_MONTH,0,TMT_MONTH,0,TMT_MONTH,0,0,""}
71 /* sensible y label intervals ...*/
89 gfx_color_t graph_col[] = /* default colors */
90 { 0xFFFFFFFF, /* canvas */
91 0xF0F0F0FF, /* background */
92 0xD0D0D0FF, /* shade A */
93 0xA0A0A0FF, /* shade B */
94 0x90909080, /* grid */
95 0xE0505080, /* major grid */
96 0x000000FF, /* font */
97 0x802020FF, /* arrow */
98 0x202020FF, /* axis */
99 0x000000FF /* frame */
106 # define DPRINT(x) (void)(printf x, printf("\n"))
112 /* initialize with xtr(im,0); */
114 xtr(image_desc_t *im,time_t mytime){
117 pixie = (double) im->xsize / (double)(im->end - im->start);
120 return (int)((double)im->xorigin
121 + pixie * ( mytime - im->start ) );
124 /* translate data values into y coordinates */
126 ytr(image_desc_t *im, double value){
131 pixie = (double) im->ysize / (im->maxval - im->minval);
133 pixie = (double) im->ysize / (log10(im->maxval) - log10(im->minval));
135 } else if(!im->logarithmic) {
136 yval = im->yorigin - pixie * (value - im->minval);
138 if (value < im->minval) {
141 yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
144 /* make sure we don't return anything too unreasonable. GD lib can
145 get terribly slow when drawing lines outside its scope. This is
146 especially problematic in connection with the rigid option */
148 /* keep yval as-is */
149 } else if (yval > im->yorigin) {
150 yval = im->yorigin +0.00001;
151 } else if (yval < im->yorigin - im->ysize){
152 yval = im->yorigin - im->ysize - 0.00001;
159 /* conversion function for symbolic entry names */
162 #define conv_if(VV,VVV) \
163 if (strcmp(#VV, string) == 0) return VVV ;
165 enum gf_en gf_conv(char *string){
167 conv_if(PRINT,GF_PRINT)
168 conv_if(GPRINT,GF_GPRINT)
169 conv_if(COMMENT,GF_COMMENT)
170 conv_if(HRULE,GF_HRULE)
171 conv_if(VRULE,GF_VRULE)
172 conv_if(LINE,GF_LINE)
173 conv_if(AREA,GF_AREA)
174 conv_if(STACK,GF_STACK)
175 conv_if(TICK,GF_TICK)
177 conv_if(CDEF,GF_CDEF)
178 conv_if(VDEF,GF_VDEF)
180 conv_if(PART,GF_PART)
182 conv_if(XPORT,GF_XPORT)
183 conv_if(SHIFT,GF_SHIFT)
188 enum gfx_if_en if_conv(char *string){
198 enum tmt_en tmt_conv(char *string){
200 conv_if(SECOND,TMT_SECOND)
201 conv_if(MINUTE,TMT_MINUTE)
202 conv_if(HOUR,TMT_HOUR)
204 conv_if(WEEK,TMT_WEEK)
205 conv_if(MONTH,TMT_MONTH)
206 conv_if(YEAR,TMT_YEAR)
210 enum grc_en grc_conv(char *string){
212 conv_if(BACK,GRC_BACK)
213 conv_if(CANVAS,GRC_CANVAS)
214 conv_if(SHADEA,GRC_SHADEA)
215 conv_if(SHADEB,GRC_SHADEB)
216 conv_if(GRID,GRC_GRID)
217 conv_if(MGRID,GRC_MGRID)
218 conv_if(FONT,GRC_FONT)
219 conv_if(ARROW,GRC_ARROW)
220 conv_if(AXIS,GRC_AXIS)
221 conv_if(FRAME,GRC_FRAME)
226 enum text_prop_en text_prop_conv(char *string){
228 conv_if(DEFAULT,TEXT_PROP_DEFAULT)
229 conv_if(TITLE,TEXT_PROP_TITLE)
230 conv_if(AXIS,TEXT_PROP_AXIS)
231 conv_if(UNIT,TEXT_PROP_UNIT)
232 conv_if(LEGEND,TEXT_PROP_LEGEND)
240 im_free(image_desc_t *im)
244 if (im == NULL) return 0;
245 for(i=0;i<(unsigned)im->gdes_c;i++){
246 if (im->gdes[i].data_first){
247 /* careful here, because a single pointer can occur several times */
248 free (im->gdes[i].data);
249 if (im->gdes[i].ds_namv){
250 for (ii=0;ii<im->gdes[i].ds_cnt;ii++)
251 free(im->gdes[i].ds_namv[ii]);
252 free(im->gdes[i].ds_namv);
255 free (im->gdes[i].p_data);
256 free (im->gdes[i].rpnp);
259 gfx_destroy(im->canvas);
263 /* find SI magnitude symbol for the given number*/
266 image_desc_t *im, /* image description */
273 char *symbol[] = {"a", /* 10e-18 Atto */
274 "f", /* 10e-15 Femto */
275 "p", /* 10e-12 Pico */
276 "n", /* 10e-9 Nano */
277 "u", /* 10e-6 Micro */
278 "m", /* 10e-3 Milli */
283 "T", /* 10e12 Tera */
284 "P", /* 10e15 Peta */
290 if (*value == 0.0 || isnan(*value) ) {
294 sindex = floor(log(fabs(*value))/log((double)im->base));
295 *magfact = pow((double)im->base, (double)sindex);
296 (*value) /= (*magfact);
298 if ( sindex <= symbcenter && sindex >= -symbcenter) {
299 (*symb_ptr) = symbol[sindex+symbcenter];
307 static char si_symbol[] = {
308 'a', /* 10e-18 Atto */
309 'f', /* 10e-15 Femto */
310 'p', /* 10e-12 Pico */
311 'n', /* 10e-9 Nano */
312 'u', /* 10e-6 Micro */
313 'm', /* 10e-3 Milli */
318 'T', /* 10e12 Tera */
319 'P', /* 10e15 Peta */
322 static const int si_symbcenter = 6;
324 /* find SI magnitude symbol for the numbers on the y-axis*/
327 image_desc_t *im /* image description */
331 double digits,viewdigits=0;
333 digits = floor( log( max( fabs(im->minval),fabs(im->maxval)))/log((double)im->base));
335 if (im->unitsexponent != 9999) {
336 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
337 viewdigits = floor(im->unitsexponent / 3);
342 im->magfact = pow((double)im->base , digits);
345 printf("digits %6.3f im->magfact %6.3f\n",digits,im->magfact);
348 im->viewfactor = im->magfact / pow((double)im->base , viewdigits);
350 if ( ((viewdigits+si_symbcenter) < sizeof(si_symbol)) &&
351 ((viewdigits+si_symbcenter) >= 0) )
352 im->symbol = si_symbol[(int)viewdigits+si_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))/im->magfact)) - 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 rrd 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].cf_reduce == im->gdes[ii].cf_reduce)
702 && (im->gdes[i].start_orig == im->gdes[ii].start_orig)
703 && (im->gdes[i].end_orig == im->gdes[ii].end_orig)
704 && (im->gdes[i].step_orig == im->gdes[ii].step_orig)) {
705 /* OK, the data is already there.
706 ** Just copy the header portion
708 im->gdes[i].start = im->gdes[ii].start;
709 im->gdes[i].end = im->gdes[ii].end;
710 im->gdes[i].step = im->gdes[ii].step;
711 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
712 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
713 im->gdes[i].data = im->gdes[ii].data;
714 im->gdes[i].data_first = 0;
721 unsigned long ft_step = im->gdes[i].step ; /* ft_step will record what we got from fetch */
723 if((rrd_fetch_fn(im->gdes[i].rrd,
729 &im->gdes[i].ds_namv,
730 &im->gdes[i].data)) == -1){
733 im->gdes[i].data_first = 1;
735 if (ft_step < im->gdes[i].step) {
736 reduce_data(im->gdes[i].cf_reduce,
744 im->gdes[i].step = ft_step;
748 /* lets see if the required data source is really there */
749 for(ii=0;ii<(int)im->gdes[i].ds_cnt;ii++){
750 if(strcmp(im->gdes[i].ds_namv[ii],im->gdes[i].ds_nam) == 0){
753 if (im->gdes[i].ds== -1){
754 rrd_set_error("No DS called '%s' in '%s'",
755 im->gdes[i].ds_nam,im->gdes[i].rrd);
763 /* evaluate the expressions in the CDEF functions */
765 /*************************************************************
767 *************************************************************/
770 find_var_wrapper(void *arg1, char *key)
772 return find_var((image_desc_t *) arg1, key);
775 /* find gdes containing var*/
777 find_var(image_desc_t *im, char *key){
779 for(ii=0;ii<im->gdes_c-1;ii++){
780 if((im->gdes[ii].gf == GF_DEF
781 || im->gdes[ii].gf == GF_VDEF
782 || im->gdes[ii].gf == GF_CDEF)
783 && (strcmp(im->gdes[ii].vname,key) == 0)){
790 /* find the largest common denominator for all the numbers
791 in the 0 terminated num array */
796 for (i=0;num[i+1]!=0;i++){
798 rest=num[i] % num[i+1];
799 num[i]=num[i+1]; num[i+1]=rest;
803 /* return i==0?num[i]:num[i-1]; */
807 /* run the rpn calculator on all the VDEF and CDEF arguments */
809 data_calc( image_desc_t *im){
813 long *steparray, rpi;
818 rpnstack_init(&rpnstack);
820 for (gdi=0;gdi<im->gdes_c;gdi++){
821 /* Look for GF_VDEF and GF_CDEF in the same loop,
822 * so CDEFs can use VDEFs and vice versa
824 switch (im->gdes[gdi].gf) {
828 graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx];
830 /* remove current shift */
831 vdp->start -= vdp->shift;
832 vdp->end -= vdp->shift;
835 if (im->gdes[gdi].shidx >= 0)
836 vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
839 vdp->shift = im->gdes[gdi].shval;
841 /* normalize shift to multiple of consolidated step */
842 vdp->shift = (vdp->shift / (long)vdp->step) * (long)vdp->step;
845 vdp->start += vdp->shift;
846 vdp->end += vdp->shift;
850 /* A VDEF has no DS. This also signals other parts
851 * of rrdtool that this is a VDEF value, not a CDEF.
853 im->gdes[gdi].ds_cnt = 0;
854 if (vdef_calc(im,gdi)) {
855 rrd_set_error("Error processing VDEF '%s'"
858 rpnstack_free(&rpnstack);
863 im->gdes[gdi].ds_cnt = 1;
864 im->gdes[gdi].ds = 0;
865 im->gdes[gdi].data_first = 1;
866 im->gdes[gdi].start = 0;
867 im->gdes[gdi].end = 0;
872 /* Find the variables in the expression.
873 * - VDEF variables are substituted by their values
874 * and the opcode is changed into OP_NUMBER.
875 * - CDEF variables are analized for their step size,
876 * the lowest common denominator of all the step
877 * sizes of the data sources involved is calculated
878 * and the resulting number is the step size for the
879 * resulting data source.
881 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
882 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
883 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER){
884 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
885 if (im->gdes[ptr].ds_cnt == 0) { /* this is a VDEF data source */
887 printf("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
889 im->gdes[ptr].vname);
890 printf("DEBUG: value from vdef is %f\n",im->gdes[ptr].vf.val);
892 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
893 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
894 } else { /* normal variables and PREF(variables) */
896 /* add one entry to the array that keeps track of the step sizes of the
897 * data sources going into the CDEF. */
899 rrd_realloc(steparray,
900 (++stepcnt+1)*sizeof(*steparray)))==NULL){
901 rrd_set_error("realloc steparray");
902 rpnstack_free(&rpnstack);
906 steparray[stepcnt-1] = im->gdes[ptr].step;
908 /* adjust start and end of cdef (gdi) so
909 * that it runs from the latest start point
910 * to the earliest endpoint of any of the
911 * rras involved (ptr)
914 if(im->gdes[gdi].start < im->gdes[ptr].start)
915 im->gdes[gdi].start = im->gdes[ptr].start;
917 if(im->gdes[gdi].end == 0 ||
918 im->gdes[gdi].end > im->gdes[ptr].end)
919 im->gdes[gdi].end = im->gdes[ptr].end;
921 /* store pointer to the first element of
922 * the rra providing data for variable,
923 * further save step size and data source
926 im->gdes[gdi].rpnp[rpi].data = im->gdes[ptr].data + im->gdes[ptr].ds;
927 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
928 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
930 /* backoff the *.data ptr; this is done so
931 * rpncalc() function doesn't have to treat
932 * the first case differently
934 } /* if ds_cnt != 0 */
935 } /* if OP_VARIABLE */
936 } /* loop through all rpi */
938 /* move the data pointers to the correct period */
939 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
940 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
941 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER){
942 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
943 long diff = im->gdes[gdi].start - im->gdes[ptr].start;
946 im->gdes[gdi].rpnp[rpi].data += (diff / im->gdes[ptr].step) * im->gdes[ptr].ds_cnt;
950 if(steparray == NULL){
951 rrd_set_error("rpn expressions without DEF"
952 " or CDEF variables are not supported");
953 rpnstack_free(&rpnstack);
956 steparray[stepcnt]=0;
957 /* Now find the resulting step. All steps in all
958 * used RRAs have to be visited
960 im->gdes[gdi].step = lcd(steparray);
962 if((im->gdes[gdi].data = malloc((
963 (im->gdes[gdi].end-im->gdes[gdi].start)
964 / im->gdes[gdi].step)
965 * sizeof(double)))==NULL){
966 rrd_set_error("malloc im->gdes[gdi].data");
967 rpnstack_free(&rpnstack);
971 /* Step through the new cdef results array and
972 * calculate the values
974 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
975 now<=im->gdes[gdi].end;
976 now += im->gdes[gdi].step)
978 rpnp_t *rpnp = im -> gdes[gdi].rpnp;
980 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
981 * in this case we are advancing by timesteps;
982 * we use the fact that time_t is a synonym for long
984 if (rpn_calc(rpnp,&rpnstack,(long) now,
985 im->gdes[gdi].data,++dataidx) == -1) {
986 /* rpn_calc sets the error string */
987 rpnstack_free(&rpnstack);
990 } /* enumerate over time steps within a CDEF */
995 } /* enumerate over CDEFs */
996 rpnstack_free(&rpnstack);
1000 /* massage data so, that we get one value for each x coordinate in the graph */
1002 data_proc( image_desc_t *im ){
1004 double pixstep = (double)(im->end-im->start)
1005 /(double)im->xsize; /* how much time
1006 passes in one pixel */
1008 double minval=DNAN,maxval=DNAN;
1010 unsigned long gr_time;
1012 /* memory for the processed data */
1013 for(i=0;i<im->gdes_c;i++) {
1014 if((im->gdes[i].gf==GF_LINE) ||
1015 (im->gdes[i].gf==GF_AREA) ||
1016 (im->gdes[i].gf==GF_TICK)) {
1017 if((im->gdes[i].p_data = malloc((im->xsize +1)
1018 * sizeof(rrd_value_t)))==NULL){
1019 rrd_set_error("malloc data_proc");
1025 for (i=0;i<im->xsize;i++) { /* for each pixel */
1027 gr_time = im->start+pixstep*i; /* time of the current step */
1030 for (ii=0;ii<im->gdes_c;ii++) {
1032 switch (im->gdes[ii].gf) {
1036 if (!im->gdes[ii].stack)
1038 value = im->gdes[ii].yrule;
1039 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1040 /* The time of the data doesn't necessarily match
1041 ** the time of the graph. Beware.
1043 vidx = im->gdes[ii].vidx;
1044 if (im->gdes[vidx].gf == GF_VDEF) {
1045 value = im->gdes[vidx].vf.val;
1046 } else if (((long int)gr_time >= (long int)im->gdes[vidx].start) &&
1047 ((long int)gr_time <= (long int)im->gdes[vidx].end) ) {
1048 value = im->gdes[vidx].data[
1049 (unsigned long) floor(
1050 (double)(gr_time - im->gdes[vidx].start)
1051 / im->gdes[vidx].step)
1052 * im->gdes[vidx].ds_cnt
1060 if (! isnan(value)) {
1062 im->gdes[ii].p_data[i] = paintval;
1063 /* GF_TICK: the data values are not
1064 ** relevant for min and max
1066 if (finite(paintval) && im->gdes[ii].gf != GF_TICK ) {
1067 if ((isnan(minval) || paintval < minval ) &&
1068 ! (im->logarithmic && paintval <= 0.0))
1070 if (isnan(maxval) || paintval > maxval)
1074 im->gdes[ii].p_data[i] = DNAN;
1078 rrd_set_error("STACK should already be turned into LINE or AREA here");
1087 /* if min or max have not been asigned a value this is because
1088 there was no data in the graph ... this is not good ...
1089 lets set these to dummy values then ... */
1091 if (im->logarithmic) {
1092 if (isnan(minval)) minval = 0.2;
1093 if (isnan(maxval)) maxval = 5.1;
1096 if (isnan(minval)) minval = 0.0;
1097 if (isnan(maxval)) maxval = 1.0;
1100 /* adjust min and max values */
1101 if (isnan(im->minval)
1102 /* don't adjust low-end with log scale */ /* why not? */
1103 || ((!im->rigid) && im->minval > minval)
1105 if (im->logarithmic)
1106 im->minval = minval * 0.5;
1108 im->minval = minval;
1110 if (isnan(im->maxval)
1111 || (!im->rigid && im->maxval < maxval)
1113 if (im->logarithmic)
1114 im->maxval = maxval * 2.0;
1116 im->maxval = maxval;
1118 /* make sure min is smaller than max */
1119 if (im->minval > im->maxval) {
1120 im->minval = 0.99 * im->maxval;
1123 /* make sure min and max are not equal */
1124 if (im->minval == im->maxval) {
1126 if (! im->logarithmic) {
1129 /* make sure min and max are not both zero */
1130 if (im->maxval == 0.0) {
1139 /* identify the point where the first gridline, label ... gets placed */
1143 time_t start, /* what is the initial time */
1144 enum tmt_en baseint, /* what is the basic interval */
1145 long basestep /* how many if these do we jump a time */
1149 localtime_r(&start, &tm);
1152 tm.tm_sec -= tm.tm_sec % basestep; break;
1155 tm.tm_min -= tm.tm_min % basestep;
1160 tm.tm_hour -= tm.tm_hour % basestep; break;
1162 /* we do NOT look at the basestep for this ... */
1165 tm.tm_hour = 0; break;
1167 /* we do NOT look at the basestep for this ... */
1171 tm.tm_mday -= tm.tm_wday -1; /* -1 because we want the monday */
1172 if (tm.tm_wday==0) tm.tm_mday -= 7; /* we want the *previous* monday */
1179 tm.tm_mon -= tm.tm_mon % basestep; break;
1187 tm.tm_year -= (tm.tm_year+1900) % basestep;
1192 /* identify the point where the next gridline, label ... gets placed */
1195 time_t current, /* what is the initial time */
1196 enum tmt_en baseint, /* what is the basic interval */
1197 long basestep /* how many if these do we jump a time */
1202 localtime_r(¤t, &tm);
1206 tm.tm_sec += basestep; break;
1208 tm.tm_min += basestep; break;
1210 tm.tm_hour += basestep; break;
1212 tm.tm_mday += basestep; break;
1214 tm.tm_mday += 7*basestep; break;
1216 tm.tm_mon += basestep; break;
1218 tm.tm_year += basestep;
1220 madetime = mktime(&tm);
1221 } while (madetime == -1); /* this is necessary to skip impssible times
1222 like the daylight saving time skips */
1228 /* calculate values required for PRINT and GPRINT functions */
1231 print_calc(image_desc_t *im, char ***prdata)
1233 long i,ii,validsteps;
1236 int graphelement = 0;
1239 double magfact = -1;
1243 /* wow initializing tmvdef is quite a task :-) */
1244 time_t now = time(NULL);
1245 localtime_r(&now,&tmvdef);
1246 if (im->imginfo) prlines++;
1247 for(i=0;i<im->gdes_c;i++){
1248 switch(im->gdes[i].gf){
1251 if(((*prdata) = rrd_realloc((*prdata),prlines*sizeof(char *)))==NULL){
1252 rrd_set_error("realloc prdata");
1256 /* PRINT and GPRINT can now print VDEF generated values.
1257 * There's no need to do any calculations on them as these
1258 * calculations were already made.
1260 vidx = im->gdes[i].vidx;
1261 if (im->gdes[vidx].gf==GF_VDEF) { /* simply use vals */
1262 printval = im->gdes[vidx].vf.val;
1263 localtime_r(&im->gdes[vidx].vf.when,&tmvdef);
1264 } else { /* need to calculate max,min,avg etcetera */
1265 max_ii =((im->gdes[vidx].end
1266 - im->gdes[vidx].start)
1267 / im->gdes[vidx].step
1268 * im->gdes[vidx].ds_cnt);
1271 for( ii=im->gdes[vidx].ds;
1273 ii+=im->gdes[vidx].ds_cnt){
1274 if (! finite(im->gdes[vidx].data[ii]))
1276 if (isnan(printval)){
1277 printval = im->gdes[vidx].data[ii];
1282 switch (im->gdes[i].cf){
1285 case CF_DEVSEASONAL:
1289 printval += im->gdes[vidx].data[ii];
1292 printval = min( printval, im->gdes[vidx].data[ii]);
1296 printval = max( printval, im->gdes[vidx].data[ii]);
1299 printval = im->gdes[vidx].data[ii];
1302 if (im->gdes[i].cf==CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1303 if (validsteps > 1) {
1304 printval = (printval / validsteps);
1307 } /* prepare printval */
1309 if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) {
1310 /* Magfact is set to -1 upon entry to print_calc. If it
1311 * is still less than 0, then we need to run auto_scale.
1312 * Otherwise, put the value into the correct units. If
1313 * the value is 0, then do not set the symbol or magnification
1314 * so next the calculation will be performed again. */
1315 if (magfact < 0.0) {
1316 auto_scale(im,&printval,&si_symb,&magfact);
1317 if (printval == 0.0)
1320 printval /= magfact;
1322 *(++percent_s) = 's';
1323 } else if (strstr(im->gdes[i].format,"%s") != NULL) {
1324 auto_scale(im,&printval,&si_symb,&magfact);
1327 if (im->gdes[i].gf == GF_PRINT){
1328 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1329 (*prdata)[prlines-1] = NULL;
1330 if (im->gdes[i].strftm){
1331 strftime((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,&tmvdef);
1333 if (bad_format(im->gdes[i].format)) {
1334 rrd_set_error("bad format for PRINT in '%s'", im->gdes[i].format);
1338 #ifdef HAVE_SNPRINTF
1339 snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
1341 sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
1347 if (im->gdes[i].strftm){
1348 strftime(im->gdes[i].legend,FMT_LEG_LEN,im->gdes[i].format,&tmvdef);
1350 if (bad_format(im->gdes[i].format)) {
1351 rrd_set_error("bad format for GPRINT in '%s'", im->gdes[i].format);
1354 #ifdef HAVE_SNPRINTF
1355 snprintf(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
1357 sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
1369 if(isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1370 im->gdes[i].yrule=im->gdes[im->gdes[i].vidx].vf.val;
1375 if(im->gdes[i].xrule == 0) { /* again ... the legend printer needs it*/
1376 im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
1384 #ifdef WITH_PIECHART
1391 rrd_set_error("STACK should already be turned into LINE or AREA here");
1396 return graphelement;
1400 /* place legends with color spots */
1402 leg_place(image_desc_t *im)
1405 int interleg = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1406 int border = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1407 int fill=0, fill_last;
1409 int leg_x = border, leg_y = im->yimg;
1410 int leg_y_prev = im->yimg;
1414 char prt_fctn; /*special printfunctions */
1417 if( !(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH) ) {
1418 if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1419 rrd_set_error("malloc for legspace");
1423 for(i=0;i<im->gdes_c;i++){
1426 /* hid legends for rules which are not displayed */
1428 if(!(im->extra_flags & FORCE_RULES_LEGEND)) {
1429 if (im->gdes[i].gf == GF_HRULE &&
1430 (im->gdes[i].yrule < im->minval || im->gdes[i].yrule > im->maxval))
1431 im->gdes[i].legend[0] = '\0';
1433 if (im->gdes[i].gf == GF_VRULE &&
1434 (im->gdes[i].xrule < im->start || im->gdes[i].xrule > im->end))
1435 im->gdes[i].legend[0] = '\0';
1438 leg_cc = strlen(im->gdes[i].legend);
1440 /* is there a controle code ant the end of the legend string ? */
1441 /* and it is not a tab \\t */
1442 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\' && im->gdes[i].legend[leg_cc-1] != 't') {
1443 prt_fctn = im->gdes[i].legend[leg_cc-1];
1445 im->gdes[i].legend[leg_cc] = '\0';
1449 /* remove exess space */
1450 while (prt_fctn=='g' &&
1452 im->gdes[i].legend[leg_cc-1]==' '){
1454 im->gdes[i].legend[leg_cc]='\0';
1457 legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1460 /* no interleg space if string ends in \g */
1461 fill += legspace[i];
1463 fill += gfx_get_text_width(im->canvas, fill+border,
1464 im->text_prop[TEXT_PROP_LEGEND].font,
1465 im->text_prop[TEXT_PROP_LEGEND].size,
1467 im->gdes[i].legend, 0);
1472 /* who said there was a special tag ... ?*/
1473 if (prt_fctn=='g') {
1476 if (prt_fctn == '\0') {
1477 if (i == im->gdes_c -1 ) prt_fctn ='l';
1479 /* is it time to place the legends ? */
1480 if (fill > im->ximg - 2*border){
1495 if (prt_fctn != '\0'){
1497 if (leg_c >= 2 && prt_fctn == 'j') {
1498 glue = (im->ximg - fill - 2* border) / (leg_c-1);
1502 if (prt_fctn =='c') leg_x = (im->ximg - fill) / 2.0;
1503 if (prt_fctn =='r') leg_x = im->ximg - fill - border;
1505 for(ii=mark;ii<=i;ii++){
1506 if(im->gdes[ii].legend[0]=='\0')
1507 continue; /* skip empty legends */
1508 im->gdes[ii].leg_x = leg_x;
1509 im->gdes[ii].leg_y = leg_y;
1511 gfx_get_text_width(im->canvas, leg_x,
1512 im->text_prop[TEXT_PROP_LEGEND].font,
1513 im->text_prop[TEXT_PROP_LEGEND].size,
1515 im->gdes[ii].legend, 0)
1520 /* only add y space if there was text on the line */
1521 if (leg_x > border || prt_fctn == 's')
1522 leg_y += im->text_prop[TEXT_PROP_LEGEND].size*1.8;
1523 if (prt_fctn == 's')
1524 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1530 im->yimg = leg_y_prev;
1531 /* if we did place some legends we have to add vertical space */
1532 if (leg_y != im->yimg){
1533 im->yimg += im->text_prop[TEXT_PROP_LEGEND].size*1.8;
1540 /* create a grid on the graph. it determines what to do
1541 from the values of xsize, start and end */
1543 /* the xaxis labels are determined from the number of seconds per pixel
1544 in the requested graph */
1549 calc_horizontal_grid(image_desc_t *im)
1555 int decimals, fractionals;
1557 im->ygrid_scale.labfact=2;
1558 range = im->maxval - im->minval;
1559 scaledrange = range / im->magfact;
1561 /* does the scale of this graph make it impossible to put lines
1562 on it? If so, give up. */
1563 if (isnan(scaledrange)) {
1567 /* find grid spaceing */
1569 if(isnan(im->ygridstep)){
1570 if(im->extra_flags & ALTYGRID) {
1571 /* find the value with max number of digits. Get number of digits */
1572 decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))*im->viewfactor/im->magfact));
1573 if(decimals <= 0) /* everything is small. make place for zero */
1576 im->ygrid_scale.gridstep = pow((double)10, floor(log10(range*im->viewfactor/im->magfact)))/im->viewfactor*im->magfact;
1578 if(im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1579 im->ygrid_scale.gridstep = 0.1;
1580 /* should have at least 5 lines but no more then 15 */
1581 if(range/im->ygrid_scale.gridstep < 5)
1582 im->ygrid_scale.gridstep /= 10;
1583 if(range/im->ygrid_scale.gridstep > 15)
1584 im->ygrid_scale.gridstep *= 10;
1585 if(range/im->ygrid_scale.gridstep > 5) {
1586 im->ygrid_scale.labfact = 1;
1587 if(range/im->ygrid_scale.gridstep > 8)
1588 im->ygrid_scale.labfact = 2;
1591 im->ygrid_scale.gridstep /= 5;
1592 im->ygrid_scale.labfact = 5;
1594 fractionals = floor(log10(im->ygrid_scale.gridstep*(double)im->ygrid_scale.labfact*im->viewfactor/im->magfact));
1595 if(fractionals < 0) { /* small amplitude. */
1596 int len = decimals - fractionals + 1;
1597 if (im->unitslength < len+2) im->unitslength = len+2;
1598 sprintf(im->ygrid_scale.labfmt, "%%%d.%df%s", len, -fractionals,(im->symbol != ' ' ? " %c" : ""));
1600 int len = decimals + 1;
1601 if (im->unitslength < len+2) im->unitslength = len+2;
1602 sprintf(im->ygrid_scale.labfmt, "%%%d.0f%s", len, ( im->symbol != ' ' ? " %c" : "" ));
1606 for(i=0;ylab[i].grid > 0;i++){
1607 pixel = im->ysize / (scaledrange / ylab[i].grid);
1614 if (pixel * ylab[gridind].lfac[i] >= 2.5 * im->text_prop[TEXT_PROP_AXIS].size) {
1615 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1620 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1623 im->ygrid_scale.gridstep = im->ygridstep;
1624 im->ygrid_scale.labfact = im->ylabfact;
1629 int draw_horizontal_grid(image_desc_t *im)
1633 char graph_label[100];
1635 double X0=im->xorigin;
1636 double X1=im->xorigin+im->xsize;
1638 int sgrid = (int)( im->minval / im->ygrid_scale.gridstep - 1);
1639 int egrid = (int)( im->maxval / im->ygrid_scale.gridstep + 1);
1641 scaledstep = im->ygrid_scale.gridstep/(double)im->magfact*(double)im->viewfactor;
1642 MaxY = scaledstep*(double)egrid;
1643 for (i = sgrid; i <= egrid; i++){
1644 double Y0=ytr(im,im->ygrid_scale.gridstep*i);
1645 double YN=ytr(im,im->ygrid_scale.gridstep*(i+1));
1646 if ( Y0 >= im->yorigin-im->ysize
1647 && Y0 <= im->yorigin){
1648 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
1649 with the chosen settings. Add a label if required by settings, or if
1650 there is only one label so far and the next grid line is out of bounds. */
1651 if(i % im->ygrid_scale.labfact == 0 || ( nlabels==1 && (YN < im->yorigin-im->ysize || YN > im->yorigin) )){
1652 if (im->symbol == ' ') {
1653 if(im->extra_flags & ALTYGRID) {
1654 sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*(double)i);
1657 sprintf(graph_label,"%4.1f",scaledstep*(double)i);
1659 sprintf(graph_label,"%4.0f",scaledstep*(double)i);
1663 char sisym = ( i == 0 ? ' ' : im->symbol);
1664 if(im->extra_flags & ALTYGRID) {
1665 sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*(double)i,sisym);
1668 sprintf(graph_label,"%4.1f %c",scaledstep*(double)i, sisym);
1670 sprintf(graph_label,"%4.0f %c",scaledstep*(double)i, sisym);
1676 gfx_new_text ( im->canvas,
1677 X0-im->text_prop[TEXT_PROP_AXIS].size, Y0,
1678 im->graph_col[GRC_FONT],
1679 im->text_prop[TEXT_PROP_AXIS].font,
1680 im->text_prop[TEXT_PROP_AXIS].size,
1681 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1683 gfx_new_dashed_line ( im->canvas,
1686 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1687 im->grid_dash_on, im->grid_dash_off);
1689 } else if (!(im->extra_flags & NOMINOR)) {
1690 gfx_new_dashed_line ( im->canvas,
1693 GRIDWIDTH, im->graph_col[GRC_GRID],
1694 im->grid_dash_on, im->grid_dash_off);
1702 /* this is frexp for base 10 */
1703 double frexp10(double, double *);
1704 double frexp10(double x, double *e) {
1708 iexp = floor(log(fabs(x)) / log(10));
1709 mnt = x / pow(10.0, iexp);
1712 mnt = x / pow(10.0, iexp);
1718 static int AlmostEqual2sComplement (float A, float B, int maxUlps)
1721 int aInt = *(int*)&A;
1722 int bInt = *(int*)&B;
1724 /* Make sure maxUlps is non-negative and small enough that the
1725 default NAN won't compare as equal to anything. */
1727 /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
1729 /* Make aInt lexicographically ordered as a twos-complement int */
1732 aInt = 0x80000000l - aInt;
1734 /* Make bInt lexicographically ordered as a twos-complement int */
1737 bInt = 0x80000000l - bInt;
1739 intDiff = abs(aInt - bInt);
1741 if (intDiff <= maxUlps)
1747 /* logaritmic horizontal grid */
1749 horizontal_log_grid(image_desc_t *im)
1751 double yloglab[][10] = {
1752 {1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
1753 {1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
1754 {1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0},
1755 {1.0, 2.0, 4.0, 6.0, 8.0, 10., 0.0, 0.0, 0.0, 0.0},
1756 {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.},
1757 {0,0,0,0,0, 0,0,0,0,0} /* last line */ };
1759 int i, j, val_exp, min_exp;
1760 double nex; /* number of decades in data */
1761 double logscale; /* scale in logarithmic space */
1762 int exfrac = 1; /* decade spacing */
1763 int mid = -1; /* row in yloglab for major grid */
1764 double mspac; /* smallest major grid spacing (pixels) */
1765 int flab; /* first value in yloglab to use */
1766 double value, tmp, pre_value;
1768 char graph_label[100];
1770 nex = log10(im->maxval / im->minval);
1771 logscale = im->ysize / nex;
1773 /* major spacing for data with high dynamic range */
1774 while(logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
1775 if(exfrac == 1) exfrac = 3;
1779 /* major spacing for less dynamic data */
1781 /* search best row in yloglab */
1783 for(i = 0; yloglab[mid][i + 1] < 10.0; i++);
1784 mspac = logscale * log10(10.0 / yloglab[mid][i]);
1785 } while(mspac > 2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0);
1788 /* find first value in yloglab */
1789 for(flab = 0; yloglab[mid][flab] < 10 && frexp10(im->minval, &tmp) > yloglab[mid][flab] ; flab++);
1790 if(yloglab[mid][flab] == 10.0) {
1795 if(val_exp % exfrac) val_exp += abs(-val_exp % exfrac);
1798 X1=im->xorigin+im->xsize;
1804 value = yloglab[mid][flab] * pow(10.0, val_exp);
1805 if ( AlmostEqual2sComplement(value,pre_value,4) ) break; /* it seems we are not converging */
1809 Y0 = ytr(im, value);
1810 if(Y0 <= im->yorigin - im->ysize) break;
1812 /* major grid line */
1813 gfx_new_dashed_line ( im->canvas,
1816 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1817 im->grid_dash_on, im->grid_dash_off);
1820 if (im->extra_flags & FORCE_UNITS_SI) {
1825 scale = floor(val_exp / 3.0);
1826 if( value >= 1.0 ) pvalue = pow(10.0, val_exp % 3);
1827 else pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
1828 pvalue *= yloglab[mid][flab];
1830 if ( ((scale+si_symbcenter) < (int)sizeof(si_symbol)) &&
1831 ((scale+si_symbcenter) >= 0) )
1832 symbol = si_symbol[scale+si_symbcenter];
1836 sprintf(graph_label,"%3.0f %c", pvalue, symbol);
1838 sprintf(graph_label,"%3.0e", value);
1839 gfx_new_text ( im->canvas,
1840 X0-im->text_prop[TEXT_PROP_AXIS].size, Y0,
1841 im->graph_col[GRC_FONT],
1842 im->text_prop[TEXT_PROP_AXIS].font,
1843 im->text_prop[TEXT_PROP_AXIS].size,
1844 im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1848 if(mid < 4 && exfrac == 1) {
1849 /* find first and last minor line behind current major line
1850 * i is the first line and j tha last */
1852 min_exp = val_exp - 1;
1853 for(i = 1; yloglab[mid][i] < 10.0; i++);
1854 i = yloglab[mid][i - 1] + 1;
1859 i = yloglab[mid][flab - 1] + 1;
1860 j = yloglab[mid][flab];
1863 /* draw minor lines below current major line */
1866 value = i * pow(10.0, min_exp);
1867 if(value < im->minval) continue;
1869 Y0 = ytr(im, value);
1870 if(Y0 <= im->yorigin - im->ysize) break;
1873 gfx_new_dashed_line ( im->canvas,
1876 GRIDWIDTH, im->graph_col[GRC_GRID],
1877 im->grid_dash_on, im->grid_dash_off);
1880 else if(exfrac > 1) {
1881 for(i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
1882 value = pow(10.0, i);
1883 if(value < im->minval) continue;
1885 Y0 = ytr(im, value);
1886 if(Y0 <= im->yorigin - im->ysize) break;
1889 gfx_new_dashed_line ( im->canvas,
1892 GRIDWIDTH, im->graph_col[GRC_GRID],
1893 im->grid_dash_on, im->grid_dash_off);
1898 if(yloglab[mid][++flab] == 10.0) {
1904 /* draw minor lines after highest major line */
1905 if(mid < 4 && exfrac == 1) {
1906 /* find first and last minor line below current major line
1907 * i is the first line and j tha last */
1909 min_exp = val_exp - 1;
1910 for(i = 1; yloglab[mid][i] < 10.0; i++);
1911 i = yloglab[mid][i - 1] + 1;
1916 i = yloglab[mid][flab - 1] + 1;
1917 j = yloglab[mid][flab];
1920 /* draw minor lines below current major line */
1923 value = i * pow(10.0, min_exp);
1924 if(value < im->minval) continue;
1926 Y0 = ytr(im, value);
1927 if(Y0 <= im->yorigin - im->ysize) break;
1930 gfx_new_dashed_line ( im->canvas,
1933 GRIDWIDTH, im->graph_col[GRC_GRID],
1934 im->grid_dash_on, im->grid_dash_off);
1937 /* fancy minor gridlines */
1938 else if(exfrac > 1) {
1939 for(i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
1940 value = pow(10.0, i);
1941 if(value < im->minval) continue;
1943 Y0 = ytr(im, value);
1944 if(Y0 <= im->yorigin - im->ysize) break;
1947 gfx_new_dashed_line ( im->canvas,
1950 GRIDWIDTH, im->graph_col[GRC_GRID],
1951 im->grid_dash_on, im->grid_dash_off);
1963 int xlab_sel; /* which sort of label and grid ? */
1964 time_t ti, tilab, timajor;
1966 char graph_label[100];
1967 double X0,Y0,Y1; /* points for filled graph and more*/
1970 /* the type of time grid is determined by finding
1971 the number of seconds per pixel in the graph */
1974 if(im->xlab_user.minsec == -1){
1975 factor=(im->end - im->start)/im->xsize;
1977 while ( xlab[xlab_sel+1].minsec != -1
1978 && xlab[xlab_sel+1].minsec <= factor) { xlab_sel++; } /* pick the last one */
1979 while ( xlab[xlab_sel-1].minsec == xlab[xlab_sel].minsec
1980 && xlab[xlab_sel].length > (im->end - im->start)) { xlab_sel--; } /* go back to the smallest size */
1981 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
1982 im->xlab_user.gridst = xlab[xlab_sel].gridst;
1983 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
1984 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
1985 im->xlab_user.labtm = xlab[xlab_sel].labtm;
1986 im->xlab_user.labst = xlab[xlab_sel].labst;
1987 im->xlab_user.precis = xlab[xlab_sel].precis;
1988 im->xlab_user.stst = xlab[xlab_sel].stst;
1991 /* y coords are the same for every line ... */
1993 Y1 = im->yorigin-im->ysize;
1996 /* paint the minor grid */
1997 if (!(im->extra_flags & NOMINOR))
1999 for(ti = find_first_time(im->start,
2000 im->xlab_user.gridtm,
2001 im->xlab_user.gridst),
2002 timajor = find_first_time(im->start,
2003 im->xlab_user.mgridtm,
2004 im->xlab_user.mgridst);
2006 ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
2008 /* are we inside the graph ? */
2009 if (ti < im->start || ti > im->end) continue;
2010 while (timajor < ti) {
2011 timajor = find_next_time(timajor,
2012 im->xlab_user.mgridtm, im->xlab_user.mgridst);
2014 if (ti == timajor) continue; /* skip as falls on major grid line */
2016 gfx_new_dashed_line(im->canvas,X0,Y0+1, X0,Y1-1,GRIDWIDTH,
2017 im->graph_col[GRC_GRID],
2018 im->grid_dash_on, im->grid_dash_off);
2023 /* paint the major grid */
2024 for(ti = find_first_time(im->start,
2025 im->xlab_user.mgridtm,
2026 im->xlab_user.mgridst);
2028 ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
2030 /* are we inside the graph ? */
2031 if (ti < im->start || ti > im->end) continue;
2033 gfx_new_dashed_line(im->canvas,X0,Y0+3, X0,Y1-2,MGRIDWIDTH,
2034 im->graph_col[GRC_MGRID],
2035 im->grid_dash_on, im->grid_dash_off);
2038 /* paint the labels below the graph */
2039 for(ti = find_first_time(im->start - im->xlab_user.precis/2,
2040 im->xlab_user.labtm,
2041 im->xlab_user.labst);
2042 ti <= im->end - im->xlab_user.precis/2;
2043 ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
2045 tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
2046 /* are we inside the graph ? */
2047 if (tilab < im->start || tilab > im->end) continue;
2050 localtime_r(&tilab, &tm);
2051 strftime(graph_label,99,im->xlab_user.stst, &tm);
2053 # error "your libc has no strftime I guess we'll abort the exercise here."
2055 gfx_new_text ( im->canvas,
2056 xtr(im,tilab), Y0+im->text_prop[TEXT_PROP_AXIS].size*1.4+5,
2057 im->graph_col[GRC_FONT],
2058 im->text_prop[TEXT_PROP_AXIS].font,
2059 im->text_prop[TEXT_PROP_AXIS].size,
2060 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_BOTTOM,
2073 /* draw x and y axis */
2074 /* gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2075 im->xorigin+im->xsize,im->yorigin-im->ysize,
2076 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2078 gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2079 im->xorigin+im->xsize,im->yorigin-im->ysize,
2080 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2082 gfx_new_line ( im->canvas, im->xorigin-4,im->yorigin,
2083 im->xorigin+im->xsize+4,im->yorigin,
2084 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2086 gfx_new_line ( im->canvas, im->xorigin,im->yorigin+4,
2087 im->xorigin,im->yorigin-im->ysize-4,
2088 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2091 /* arrow for X and Y axis direction */
2092 gfx_new_area ( im->canvas,
2093 im->xorigin+im->xsize+2, im->yorigin-2,
2094 im->xorigin+im->xsize+2, im->yorigin+3,
2095 im->xorigin+im->xsize+7, im->yorigin+0.5, /* LINEOFFSET */
2096 im->graph_col[GRC_ARROW]);
2098 gfx_new_area ( im->canvas,
2099 im->xorigin-2, im->yorigin-im->ysize-2,
2100 im->xorigin+3, im->yorigin-im->ysize-2,
2101 im->xorigin+0.5, im->yorigin-im->ysize-7, /* LINEOFFSET */
2102 im->graph_col[GRC_ARROW]);
2107 grid_paint(image_desc_t *im)
2111 double X0,Y0; /* points for filled graph and more*/
2114 /* draw 3d border */
2115 node = gfx_new_area (im->canvas, 0,im->yimg,
2117 2,2,im->graph_col[GRC_SHADEA]);
2118 gfx_add_point( node , im->ximg - 2, 2 );
2119 gfx_add_point( node , im->ximg, 0 );
2120 gfx_add_point( node , 0,0 );
2121 /* gfx_add_point( node , 0,im->yimg ); */
2123 node = gfx_new_area (im->canvas, 2,im->yimg-2,
2124 im->ximg-2,im->yimg-2,
2126 im->graph_col[GRC_SHADEB]);
2127 gfx_add_point( node , im->ximg,0);
2128 gfx_add_point( node , im->ximg,im->yimg);
2129 gfx_add_point( node , 0,im->yimg);
2130 /* gfx_add_point( node , 0,im->yimg ); */
2133 if (im->draw_x_grid == 1 )
2136 if (im->draw_y_grid == 1){
2137 if(im->logarithmic){
2138 res = horizontal_log_grid(im);
2140 res = draw_horizontal_grid(im);
2143 /* dont draw horizontal grid if there is no min and max val */
2145 char *nodata = "No Data found";
2146 gfx_new_text(im->canvas,im->ximg/2, (2*im->yorigin-im->ysize) / 2,
2147 im->graph_col[GRC_FONT],
2148 im->text_prop[TEXT_PROP_AXIS].font,
2149 im->text_prop[TEXT_PROP_AXIS].size,
2150 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
2155 /* yaxis unit description */
2156 gfx_new_text( im->canvas,
2157 10, (im->yorigin - im->ysize/2),
2158 im->graph_col[GRC_FONT],
2159 im->text_prop[TEXT_PROP_UNIT].font,
2160 im->text_prop[TEXT_PROP_UNIT].size, im->tabwidth,
2161 RRDGRAPH_YLEGEND_ANGLE,
2162 GFX_H_LEFT, GFX_V_CENTER,
2166 gfx_new_text( im->canvas,
2167 im->ximg/2, im->text_prop[TEXT_PROP_TITLE].size*1.3+4,
2168 im->graph_col[GRC_FONT],
2169 im->text_prop[TEXT_PROP_TITLE].font,
2170 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
2171 GFX_H_CENTER, GFX_V_CENTER,
2173 /* rrdtool 'logo' */
2174 gfx_new_text( im->canvas,
2176 ( im->graph_col[GRC_FONT] & 0xffffff00 ) | 0x00000044,
2177 im->text_prop[TEXT_PROP_AXIS].font,
2178 5.5, im->tabwidth, 270,
2179 GFX_H_RIGHT, GFX_V_TOP,
2180 "RRDTOOL / TOBI OETIKER");
2182 /* graph watermark */
2183 if(im->watermark[0] != '\0') {
2184 gfx_new_text( im->canvas,
2185 im->ximg/2, im->yimg-6,
2186 ( im->graph_col[GRC_FONT] & 0xffffff00 ) | 0x00000044,
2187 im->text_prop[TEXT_PROP_AXIS].font,
2188 5.5, im->tabwidth, 0,
2189 GFX_H_CENTER, GFX_V_BOTTOM,
2194 if( !(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH) ) {
2195 for(i=0;i<im->gdes_c;i++){
2196 if(im->gdes[i].legend[0] =='\0')
2199 /* im->gdes[i].leg_y is the bottom of the legend */
2200 X0 = im->gdes[i].leg_x;
2201 Y0 = im->gdes[i].leg_y;
2202 gfx_new_text ( im->canvas, X0, Y0,
2203 im->graph_col[GRC_FONT],
2204 im->text_prop[TEXT_PROP_LEGEND].font,
2205 im->text_prop[TEXT_PROP_LEGEND].size,
2206 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
2207 im->gdes[i].legend );
2208 /* The legend for GRAPH items starts with "M " to have
2209 enough space for the box */
2210 if ( im->gdes[i].gf != GF_PRINT &&
2211 im->gdes[i].gf != GF_GPRINT &&
2212 im->gdes[i].gf != GF_COMMENT) {
2215 boxH = gfx_get_text_width(im->canvas, 0,
2216 im->text_prop[TEXT_PROP_LEGEND].font,
2217 im->text_prop[TEXT_PROP_LEGEND].size,
2218 im->tabwidth,"o", 0) * 1.2;
2221 /* make sure transparent colors show up the same way as in the graph */
2222 node = gfx_new_area(im->canvas,
2226 im->graph_col[GRC_BACK]);
2227 gfx_add_point ( node, X0+boxH, Y0-boxV );
2229 node = gfx_new_area(im->canvas,
2234 gfx_add_point ( node, X0+boxH, Y0-boxV );
2235 node = gfx_new_line(im->canvas,
2238 1.0,im->graph_col[GRC_FRAME]);
2239 gfx_add_point(node,X0+boxH,Y0);
2240 gfx_add_point(node,X0+boxH,Y0-boxV);
2241 gfx_close_path(node);
2248 /*****************************************************
2249 * lazy check make sure we rely need to create this graph
2250 *****************************************************/
2252 int lazy_check(image_desc_t *im){
2255 struct stat imgstat;
2257 if (im->lazy == 0) return 0; /* no lazy option */
2258 if (stat(im->graphfile,&imgstat) != 0)
2259 return 0; /* can't stat */
2260 /* one pixel in the existing graph is more then what we would
2262 if (time(NULL) - imgstat.st_mtime >
2263 (im->end - im->start) / im->xsize)
2265 if ((fd = fopen(im->graphfile,"rb")) == NULL)
2266 return 0; /* the file does not exist */
2267 switch (im->canvas->imgformat) {
2269 size = PngSize(fd,&(im->ximg),&(im->yimg));
2278 #ifdef WITH_PIECHART
2280 pie_part(image_desc_t *im, gfx_color_t color,
2281 double PieCenterX, double PieCenterY, double Radius,
2282 double startangle, double endangle)
2286 double step=M_PI/50; /* Number of iterations for the circle;
2287 ** 10 is definitely too low, more than
2288 ** 50 seems to be overkill
2291 /* Strange but true: we have to work clockwise or else
2292 ** anti aliasing nor transparency don't work.
2294 ** This test is here to make sure we do it right, also
2295 ** this makes the for...next loop more easy to implement.
2296 ** The return will occur if the user enters a negative number
2297 ** (which shouldn't be done according to the specs) or if the
2298 ** programmers do something wrong (which, as we all know, never
2299 ** happens anyway :)
2301 if (endangle<startangle) return;
2303 /* Hidden feature: Radius decreases each full circle */
2305 while (angle>=2*M_PI) {
2310 node=gfx_new_area(im->canvas,
2311 PieCenterX+sin(startangle)*Radius,
2312 PieCenterY-cos(startangle)*Radius,
2315 PieCenterX+sin(endangle)*Radius,
2316 PieCenterY-cos(endangle)*Radius,
2318 for (angle=endangle;angle-startangle>=step;angle-=step) {
2320 PieCenterX+sin(angle)*Radius,
2321 PieCenterY-cos(angle)*Radius );
2328 graph_size_location(image_desc_t *im, int elements
2330 #ifdef WITH_PIECHART
2336 /* The actual size of the image to draw is determined from
2337 ** several sources. The size given on the command line is
2338 ** the graph area but we need more as we have to draw labels
2339 ** and other things outside the graph area
2342 /* +-+-------------------------------------------+
2343 ** |l|.................title.....................|
2344 ** |e+--+-------------------------------+--------+
2347 ** |l| l| main graph area | chart |
2350 ** |r+--+-------------------------------+--------+
2351 ** |e| | x-axis labels | |
2352 ** |v+--+-------------------------------+--------+
2353 ** | |..............legends......................|
2354 ** +-+-------------------------------------------+
2356 ** +---------------------------------------------+
2362 #ifdef WITH_PIECHART
2367 Xlegend =0, Ylegend =0,
2369 Xspacing =15, Yspacing =15,
2373 if (im->extra_flags & ONLY_GRAPH) {
2375 im->ximg = im->xsize;
2376 im->yimg = im->ysize;
2377 im->yorigin = im->ysize;
2382 if (im->ylegend[0] != '\0' ) {
2383 Xvertical = im->text_prop[TEXT_PROP_UNIT].size *2;
2387 if (im->title[0] != '\0') {
2388 /* The title is placed "inbetween" two text lines so it
2389 ** automatically has some vertical spacing. The horizontal
2390 ** spacing is added here, on each side.
2392 /* don't care for the with of the title
2393 Xtitle = gfx_get_text_width(im->canvas, 0,
2394 im->text_prop[TEXT_PROP_TITLE].font,
2395 im->text_prop[TEXT_PROP_TITLE].size,
2397 im->title, 0) + 2*Xspacing; */
2398 Ytitle = im->text_prop[TEXT_PROP_TITLE].size*2.6+10;
2404 if (im->draw_x_grid) {
2405 Yxlabel=im->text_prop[TEXT_PROP_AXIS].size *2.5;
2407 if (im->draw_y_grid) {
2408 Xylabel=gfx_get_text_width(im->canvas, 0,
2409 im->text_prop[TEXT_PROP_AXIS].font,
2410 im->text_prop[TEXT_PROP_AXIS].size,
2412 "0", 0) * im->unitslength;
2416 #ifdef WITH_PIECHART
2418 im->piesize=im->xsize<im->ysize?im->xsize:im->ysize;
2424 /* Now calculate the total size. Insert some spacing where
2425 desired. im->xorigin and im->yorigin need to correspond
2426 with the lower left corner of the main graph area or, if
2427 this one is not set, the imaginary box surrounding the
2430 /* The legend width cannot yet be determined, as a result we
2431 ** have problems adjusting the image to it. For now, we just
2432 ** forget about it at all; the legend will have to fit in the
2433 ** size already allocated.
2435 im->ximg = Xylabel + Xmain + 2 * Xspacing;
2437 #ifdef WITH_PIECHART
2441 if (Xmain) im->ximg += Xspacing;
2442 #ifdef WITH_PIECHART
2443 if (Xpie) im->ximg += Xspacing;
2446 im->xorigin = Xspacing + Xylabel;
2448 /* the length of the title should not influence with width of the graph
2449 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2451 if (Xvertical) { /* unit description */
2452 im->ximg += Xvertical;
2453 im->xorigin += Xvertical;
2457 /* The vertical size is interesting... we need to compare
2458 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with
2459 ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2460 ** in order to start even thinking about Ylegend or Ywatermark.
2462 ** Do it in three portions: First calculate the inner part,
2463 ** then do the legend, then adjust the total height of the img,
2464 ** adding space for a watermark if one exists;
2467 /* reserve space for main and/or pie */
2469 im->yimg = Ymain + Yxlabel;
2471 #ifdef WITH_PIECHART
2472 if (im->yimg < Ypie) im->yimg = Ypie;
2475 im->yorigin = im->yimg - Yxlabel;
2477 /* reserve space for the title *or* some padding above the graph */
2480 im->yorigin += Ytitle;
2482 im->yimg += 1.5*Yspacing;
2483 im->yorigin += 1.5*Yspacing;
2485 /* reserve space for padding below the graph */
2486 im->yimg += Yspacing;
2488 /* Determine where to place the legends onto the image.
2489 ** Adjust im->yimg to match the space requirements.
2491 if(leg_place(im)==-1)
2494 if (im->watermark[0] != '\0') {
2495 im->yimg += Ywatermark;
2499 if (Xlegend > im->ximg) {
2501 /* reposition Pie */
2505 #ifdef WITH_PIECHART
2506 /* The pie is placed in the upper right hand corner,
2507 ** just below the title (if any) and with sufficient
2511 im->pie_x = im->ximg - Xspacing - Xpie/2;
2512 im->pie_y = im->yorigin-Ymain+Ypie/2;
2514 im->pie_x = im->ximg/2;
2515 im->pie_y = im->yorigin-Ypie/2;
2523 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
2524 /* yes we are loosing precision by doing tos with floats instead of doubles
2525 but it seems more stable this way. */
2528 /* draw that picture thing ... */
2530 graph_paint(image_desc_t *im, char ***calcpr)
2533 int lazy = lazy_check(im);
2534 #ifdef WITH_PIECHART
2536 double PieStart=0.0;
2541 double areazero = 0.0;
2542 graph_desc_t *lastgdes = NULL;
2544 /* if we are lazy and there is nothing to PRINT ... quit now */
2545 if (lazy && im->prt_c==0) return 0;
2547 /* pull the data from the rrd files ... */
2549 if(data_fetch(im)==-1)
2552 /* evaluate VDEF and CDEF operations ... */
2553 if(data_calc(im)==-1)
2556 #ifdef WITH_PIECHART
2557 /* check if we need to draw a piechart */
2558 for(i=0;i<im->gdes_c;i++){
2559 if (im->gdes[i].gf == GF_PART) {
2566 /* calculate and PRINT and GPRINT definitions. We have to do it at
2567 * this point because it will affect the length of the legends
2568 * if there are no graph elements we stop here ...
2569 * if we are lazy, try to quit ...
2571 i=print_calc(im,calcpr);
2574 #ifdef WITH_PIECHART
2577 ) || lazy) return 0;
2579 #ifdef WITH_PIECHART
2580 /* If there's only the pie chart to draw, signal this */
2581 if (i==0) piechart=2;
2584 /* get actual drawing data and find min and max values*/
2585 if(data_proc(im)==-1)
2588 if(!im->logarithmic){si_unit(im);} /* identify si magnitude Kilo, Mega Giga ? */
2590 if(!im->rigid && ! im->logarithmic)
2591 expand_range(im); /* make sure the upper and lower limit are
2594 if (!calc_horizontal_grid(im))
2601 /**************************************************************
2602 *** Calculating sizes and locations became a bit confusing ***
2603 *** so I moved this into a separate function. ***
2604 **************************************************************/
2605 if(graph_size_location(im,i
2606 #ifdef WITH_PIECHART
2612 /* the actual graph is created by going through the individual
2613 graph elements and then drawing them */
2615 node=gfx_new_area ( im->canvas,
2619 im->graph_col[GRC_BACK]);
2621 gfx_add_point(node,im->ximg, 0);
2623 #ifdef WITH_PIECHART
2624 if (piechart != 2) {
2626 node=gfx_new_area ( im->canvas,
2627 im->xorigin, im->yorigin,
2628 im->xorigin + im->xsize, im->yorigin,
2629 im->xorigin + im->xsize, im->yorigin-im->ysize,
2630 im->graph_col[GRC_CANVAS]);
2632 gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
2634 if (im->minval > 0.0)
2635 areazero = im->minval;
2636 if (im->maxval < 0.0)
2637 areazero = im->maxval;
2638 #ifdef WITH_PIECHART
2642 #ifdef WITH_PIECHART
2644 pie_part(im,im->graph_col[GRC_CANVAS],im->pie_x,im->pie_y,im->piesize*0.5,0,2*M_PI);
2648 for(i=0;i<im->gdes_c;i++){
2649 switch(im->gdes[i].gf){
2662 for (ii = 0; ii < im->xsize; ii++)
2664 if (!isnan(im->gdes[i].p_data[ii]) &&
2665 im->gdes[i].p_data[ii] != 0.0)
2667 if (im -> gdes[i].yrule > 0 ) {
2668 gfx_new_line(im->canvas,
2669 im -> xorigin + ii, im->yorigin,
2670 im -> xorigin + ii, im->yorigin - im -> gdes[i].yrule * im -> ysize,
2672 im -> gdes[i].col );
2673 } else if ( im -> gdes[i].yrule < 0 ) {
2674 gfx_new_line(im->canvas,
2675 im -> xorigin + ii, im->yorigin - im -> ysize,
2676 im -> xorigin + ii, im->yorigin - ( 1 - im -> gdes[i].yrule ) * im -> ysize,
2678 im -> gdes[i].col );
2686 /* fix data points at oo and -oo */
2687 for(ii=0;ii<im->xsize;ii++){
2688 if (isinf(im->gdes[i].p_data[ii])){
2689 if (im->gdes[i].p_data[ii] > 0) {
2690 im->gdes[i].p_data[ii] = im->maxval ;
2692 im->gdes[i].p_data[ii] = im->minval ;
2698 /* *******************************************************
2703 -------|--t-1--t--------------------------------
2705 if we know the value at time t was a then
2706 we draw a square from t-1 to t with the value a.
2708 ********************************************************* */
2709 if (im->gdes[i].col != 0x0){
2710 /* GF_LINE and friend */
2711 if(im->gdes[i].gf == GF_LINE ){
2714 for(ii=1;ii<im->xsize;ii++){
2715 if (isnan(im->gdes[i].p_data[ii]) || (im->slopemode==1 && isnan(im->gdes[i].p_data[ii-1]))){
2719 if ( node == NULL ) {
2720 last_y = ytr(im,im->gdes[i].p_data[ii]);
2721 if ( im->slopemode == 0 ){
2722 node = gfx_new_line(im->canvas,
2723 ii-1+im->xorigin,last_y,
2724 ii+im->xorigin,last_y,
2725 im->gdes[i].linewidth,
2728 node = gfx_new_line(im->canvas,
2729 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2730 ii+im->xorigin,last_y,
2731 im->gdes[i].linewidth,
2735 double new_y = ytr(im,im->gdes[i].p_data[ii]);
2736 if ( im->slopemode==0 && ! AlmostEqual2sComplement(new_y,last_y,4)){
2737 gfx_add_point(node,ii-1+im->xorigin,new_y);
2740 gfx_add_point(node,ii+im->xorigin,new_y);
2746 double *foreY=malloc(sizeof(double)*im->xsize*2);
2747 double *foreX=malloc(sizeof(double)*im->xsize*2);
2748 double *backY=malloc(sizeof(double)*im->xsize*2);
2749 double *backX=malloc(sizeof(double)*im->xsize*2);
2751 for(ii=0;ii<=im->xsize;ii++){
2753 if ( idxI > 0 && ( drawem != 0 || ii==im->xsize)){
2756 while (cntI < idxI && AlmostEqual2sComplement(foreY[lastI],foreY[cntI],4) && AlmostEqual2sComplement(foreY[lastI],foreY[cntI+1],4)){cntI++;}
2757 node = gfx_new_area(im->canvas,
2760 foreX[cntI],foreY[cntI], im->gdes[i].col);
2761 while (cntI < idxI) {
2764 while ( cntI < idxI && AlmostEqual2sComplement(foreY[lastI],foreY[cntI],4) && AlmostEqual2sComplement(foreY[lastI],foreY[cntI+1],4)){cntI++;}
2765 gfx_add_point(node,foreX[cntI],foreY[cntI]);
2767 gfx_add_point(node,backX[idxI],backY[idxI]);
2771 while ( idxI > 1 && AlmostEqual2sComplement(backY[lastI], backY[idxI],4) && AlmostEqual2sComplement(backY[lastI],backY[idxI-1],4)){idxI--;}
2772 gfx_add_point(node,backX[idxI],backY[idxI]);
2781 if (ii == im->xsize) break;
2783 /* keep things simple for now, just draw these bars
2784 do not try to build a big and complex area */
2787 if ( im->slopemode == 0 && ii==0){
2790 if ( isnan(im->gdes[i].p_data[ii]) ) {
2794 ytop = ytr(im,im->gdes[i].p_data[ii]);
2795 if ( lastgdes && im->gdes[i].stack ) {
2796 ybase = ytr(im,lastgdes->p_data[ii]);
2798 ybase = ytr(im,areazero);
2800 if ( ybase == ytop ){
2804 /* every area has to be wound clock-wise,
2805 so we have to make sur base remains base */
2807 double extra = ytop;
2811 if ( im->slopemode == 0 ){
2812 backY[++idxI] = ybase-0.2;
2813 backX[idxI] = ii+im->xorigin-1;
2814 foreY[idxI] = ytop+0.2;
2815 foreX[idxI] = ii+im->xorigin-1;
2817 backY[++idxI] = ybase-0.2;
2818 backX[idxI] = ii+im->xorigin;
2819 foreY[idxI] = ytop+0.2;
2820 foreX[idxI] = ii+im->xorigin;
2822 /* close up any remaining area */
2827 } /* else GF_LINE */
2828 } /* if color != 0x0 */
2829 /* make sure we do not run into trouble when stacking on NaN */
2830 for(ii=0;ii<im->xsize;ii++){
2831 if (isnan(im->gdes[i].p_data[ii])) {
2832 if (lastgdes && (im->gdes[i].stack)) {
2833 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
2835 im->gdes[i].p_data[ii] = areazero;
2839 lastgdes = &(im->gdes[i]);
2841 #ifdef WITH_PIECHART
2843 if(isnan(im->gdes[i].yrule)) /* fetch variable */
2844 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2846 if (finite(im->gdes[i].yrule)) { /* even the fetched var can be NaN */
2847 pie_part(im,im->gdes[i].col,
2848 im->pie_x,im->pie_y,im->piesize*0.4,
2849 M_PI*2.0*PieStart/100.0,
2850 M_PI*2.0*(PieStart+im->gdes[i].yrule)/100.0);
2851 PieStart += im->gdes[i].yrule;
2856 rrd_set_error("STACK should already be turned into LINE or AREA here");
2862 #ifdef WITH_PIECHART
2870 /* grid_paint also does the text */
2871 if( !(im->extra_flags & ONLY_GRAPH) )
2875 if( !(im->extra_flags & ONLY_GRAPH) )
2878 /* the RULES are the last thing to paint ... */
2879 for(i=0;i<im->gdes_c;i++){
2881 switch(im->gdes[i].gf){
2883 if(im->gdes[i].yrule >= im->minval
2884 && im->gdes[i].yrule <= im->maxval)
2885 gfx_new_line(im->canvas,
2886 im->xorigin,ytr(im,im->gdes[i].yrule),
2887 im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2888 1.0,im->gdes[i].col);
2891 if(im->gdes[i].xrule >= im->start
2892 && im->gdes[i].xrule <= im->end)
2893 gfx_new_line(im->canvas,
2894 xtr(im,im->gdes[i].xrule),im->yorigin,
2895 xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2896 1.0,im->gdes[i].col);
2904 if (strcmp(im->graphfile,"-")==0) {
2905 fo = im->graphhandle ? im->graphhandle : stdout;
2906 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
2907 /* Change translation mode for stdout to BINARY */
2908 _setmode( _fileno( fo ), O_BINARY );
2911 if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2912 rrd_set_error("Opening '%s' for write: %s",im->graphfile,
2913 rrd_strerror(errno));
2917 gfx_render (im->canvas,im->ximg,im->yimg,0x00000000,fo);
2918 if (strcmp(im->graphfile,"-") != 0)
2924 /*****************************************************
2926 *****************************************************/
2929 gdes_alloc(image_desc_t *im){
2932 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2933 * sizeof(graph_desc_t)))==NULL){
2934 rrd_set_error("realloc graph_descs");
2939 im->gdes[im->gdes_c-1].step=im->step;
2940 im->gdes[im->gdes_c-1].step_orig=im->step;
2941 im->gdes[im->gdes_c-1].stack=0;
2942 im->gdes[im->gdes_c-1].linewidth=0;
2943 im->gdes[im->gdes_c-1].debug=0;
2944 im->gdes[im->gdes_c-1].start=im->start;
2945 im->gdes[im->gdes_c-1].start_orig=im->start;
2946 im->gdes[im->gdes_c-1].end=im->end;
2947 im->gdes[im->gdes_c-1].end_orig=im->end;
2948 im->gdes[im->gdes_c-1].vname[0]='\0';
2949 im->gdes[im->gdes_c-1].data=NULL;
2950 im->gdes[im->gdes_c-1].ds_namv=NULL;
2951 im->gdes[im->gdes_c-1].data_first=0;
2952 im->gdes[im->gdes_c-1].p_data=NULL;
2953 im->gdes[im->gdes_c-1].rpnp=NULL;
2954 im->gdes[im->gdes_c-1].shift=0;
2955 im->gdes[im->gdes_c-1].col = 0x0;
2956 im->gdes[im->gdes_c-1].legend[0]='\0';
2957 im->gdes[im->gdes_c-1].format[0]='\0';
2958 im->gdes[im->gdes_c-1].strftm=0;
2959 im->gdes[im->gdes_c-1].rrd[0]='\0';
2960 im->gdes[im->gdes_c-1].ds=-1;
2961 im->gdes[im->gdes_c-1].cf_reduce=CF_AVERAGE;
2962 im->gdes[im->gdes_c-1].cf=CF_AVERAGE;
2963 im->gdes[im->gdes_c-1].p_data=NULL;
2964 im->gdes[im->gdes_c-1].yrule=DNAN;
2965 im->gdes[im->gdes_c-1].xrule=0;
2969 /* copies input untill the first unescaped colon is found
2970 or until input ends. backslashes have to be escaped as well */
2972 scan_for_col(const char *const input, int len, char *const output)
2977 input[inp] != ':' &&
2980 if (input[inp] == '\\' &&
2981 input[inp+1] != '\0' &&
2982 (input[inp+1] == '\\' ||
2983 input[inp+1] == ':')){
2984 output[outp++] = input[++inp];
2987 output[outp++] = input[inp];
2990 output[outp] = '\0';
2993 /* Some surgery done on this function, it became ridiculously big.
2995 ** - initializing now in rrd_graph_init()
2996 ** - options parsing now in rrd_graph_options()
2997 ** - script parsing now in rrd_graph_script()
3000 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize, FILE *stream, double *ymin, double *ymax)
3003 rrd_graph_init(&im);
3004 im.graphhandle = stream;
3006 rrd_graph_options(argc,argv,&im);
3007 if (rrd_test_error()) {
3012 if (strlen(argv[optind])>=MAXPATH) {
3013 rrd_set_error("filename (including path) too long");
3017 strncpy(im.graphfile,argv[optind],MAXPATH-1);
3018 im.graphfile[MAXPATH-1]='\0';
3020 rrd_graph_script(argc,argv,&im,1);
3021 if (rrd_test_error()) {
3026 /* Everything is now read and the actual work can start */
3029 if (graph_paint(&im,prdata)==-1){
3034 /* The image is generated and needs to be output.
3035 ** Also, if needed, print a line with information about the image.
3045 /* maybe prdata is not allocated yet ... lets do it now */
3046 if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
3047 rrd_set_error("malloc imginfo");
3051 if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
3053 rrd_set_error("malloc imginfo");
3056 filename=im.graphfile+strlen(im.graphfile);
3057 while(filename > im.graphfile) {
3058 if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
3062 sprintf((*prdata)[0],im.imginfo,filename,(long)(im.canvas->zoom*im.ximg),(long)(im.canvas->zoom*im.yimg));
3069 rrd_graph_init(image_desc_t *im)
3076 #ifdef HAVE_SETLOCALE
3077 setlocale(LC_TIME,"");
3078 #ifdef HAVE_MBSTOWCS
3079 setlocale(LC_CTYPE,"");
3085 im->xlab_user.minsec = -1;
3091 im->ylegend[0] = '\0';
3092 im->title[0] = '\0';
3093 im->watermark[0] = '\0';
3096 im->unitsexponent= 9999;
3099 im->viewfactor = 1.0;
3106 im->logarithmic = 0;
3107 im->ygridstep = DNAN;
3108 im->draw_x_grid = 1;
3109 im->draw_y_grid = 1;
3114 im->canvas = gfx_new_canvas();
3115 im->grid_dash_on = 1;
3116 im->grid_dash_off = 1;
3117 im->tabwidth = 40.0;
3119 for(i=0;i<DIM(graph_col);i++)
3120 im->graph_col[i]=graph_col[i];
3122 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3125 char rrd_win_default_font[1000];
3126 windir = getenv("windir");
3127 /* %windir% is something like D:\windows or C:\winnt */
3128 if (windir != NULL) {
3129 strncpy(rrd_win_default_font,windir,500);
3130 rrd_win_default_font[500] = '\0';
3131 strcat(rrd_win_default_font,"\\fonts\\");
3132 strcat(rrd_win_default_font,RRD_DEFAULT_FONT);
3133 for(i=0;i<DIM(text_prop);i++){
3134 strncpy(text_prop[i].font,rrd_win_default_font,sizeof(text_prop[i].font)-1);
3135 text_prop[i].font[sizeof(text_prop[i].font)-1] = '\0';
3142 deffont = getenv("RRD_DEFAULT_FONT");
3143 if (deffont != NULL) {
3144 for(i=0;i<DIM(text_prop);i++){
3145 strncpy(text_prop[i].font,deffont,sizeof(text_prop[i].font)-1);
3146 text_prop[i].font[sizeof(text_prop[i].font)-1] = '\0';
3150 for(i=0;i<DIM(text_prop);i++){
3151 im->text_prop[i].size = text_prop[i].size;
3152 strcpy(im->text_prop[i].font,text_prop[i].font);
3157 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
3160 char *parsetime_error = NULL;
3161 char scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
3162 time_t start_tmp=0,end_tmp=0;
3164 struct rrd_time_value start_tv, end_tv;
3166 optind = 0; opterr = 0; /* initialize getopt */
3168 parsetime("end-24h", &start_tv);
3169 parsetime("now", &end_tv);
3171 /* defines for long options without a short equivalent. should be bytes,
3172 and may not collide with (the ASCII value of) short options */
3173 #define LONGOPT_UNITS_SI 255
3176 static struct option long_options[] =
3178 {"start", required_argument, 0, 's'},
3179 {"end", required_argument, 0, 'e'},
3180 {"x-grid", required_argument, 0, 'x'},
3181 {"y-grid", required_argument, 0, 'y'},
3182 {"vertical-label",required_argument,0,'v'},
3183 {"width", required_argument, 0, 'w'},
3184 {"height", required_argument, 0, 'h'},
3185 {"interlaced", no_argument, 0, 'i'},
3186 {"upper-limit",required_argument, 0, 'u'},
3187 {"lower-limit",required_argument, 0, 'l'},
3188 {"rigid", no_argument, 0, 'r'},
3189 {"base", required_argument, 0, 'b'},
3190 {"logarithmic",no_argument, 0, 'o'},
3191 {"color", required_argument, 0, 'c'},
3192 {"font", required_argument, 0, 'n'},
3193 {"title", required_argument, 0, 't'},
3194 {"imginfo", required_argument, 0, 'f'},
3195 {"imgformat", required_argument, 0, 'a'},
3196 {"lazy", no_argument, 0, 'z'},
3197 {"zoom", required_argument, 0, 'm'},
3198 {"no-legend", no_argument, 0, 'g'},
3199 {"force-rules-legend",no_argument,0, 'F'},
3200 {"only-graph", no_argument, 0, 'j'},
3201 {"alt-y-grid", no_argument, 0, 'Y'},
3202 {"no-minor", no_argument, 0, 'I'},
3203 {"slope-mode", no_argument, 0, 'E'},
3204 {"alt-autoscale", no_argument, 0, 'A'},
3205 {"alt-autoscale-max", no_argument, 0, 'M'},
3206 {"no-gridfit", no_argument, 0, 'N'},
3207 {"units-exponent",required_argument, 0, 'X'},
3208 {"units-length",required_argument, 0, 'L'},
3209 {"units", required_argument, 0, LONGOPT_UNITS_SI },
3210 {"step", required_argument, 0, 'S'},
3211 {"tabwidth", required_argument, 0, 'T'},
3212 {"font-render-mode", required_argument, 0, 'R'},
3213 {"font-smoothing-threshold", required_argument, 0, 'B'},
3214 {"watermark", required_argument, 0, 'W'},
3215 {"alt-y-mrtg", no_argument, 0, 1000}, /* this has no effect it is just here to save old apps from crashing when they use it */
3217 int option_index = 0;
3219 int col_start,col_end;
3221 opt = getopt_long(argc, argv,
3222 "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:I:zgjFYAMEX:L:S:T:NR:B:W:",
3223 long_options, &option_index);
3230 im->extra_flags |= NOMINOR;
3233 im->extra_flags |= ALTYGRID;
3236 im->extra_flags |= ALTAUTOSCALE;
3239 im->extra_flags |= ALTAUTOSCALE_MAX;
3242 im->extra_flags |= ONLY_GRAPH;
3245 im->extra_flags |= NOLEGEND;
3248 im->extra_flags |= FORCE_RULES_LEGEND;
3250 case LONGOPT_UNITS_SI:
3251 if(im->extra_flags & FORCE_UNITS) {
3252 rrd_set_error("--units can only be used once!");
3255 if(strcmp(optarg,"si")==0)
3256 im->extra_flags |= FORCE_UNITS_SI;
3258 rrd_set_error("invalid argument for --units: %s", optarg );
3263 im->unitsexponent = atoi(optarg);
3266 im->unitslength = atoi(optarg);
3269 im->tabwidth = atof(optarg);
3272 im->step = atoi(optarg);
3278 if ((parsetime_error = parsetime(optarg, &start_tv))) {
3279 rrd_set_error( "start time: %s", parsetime_error );
3284 if ((parsetime_error = parsetime(optarg, &end_tv))) {
3285 rrd_set_error( "end time: %s", parsetime_error );
3290 if(strcmp(optarg,"none") == 0){
3296 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3298 &im->xlab_user.gridst,
3300 &im->xlab_user.mgridst,
3302 &im->xlab_user.labst,
3303 &im->xlab_user.precis,
3304 &stroff) == 7 && stroff != 0){
3305 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
3306 im->xlab_form[sizeof(im->xlab_form)-1] = '\0';
3307 if((int)(im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
3308 rrd_set_error("unknown keyword %s",scan_gtm);
3310 } else if ((int)(im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
3311 rrd_set_error("unknown keyword %s",scan_mtm);
3313 } else if ((int)(im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
3314 rrd_set_error("unknown keyword %s",scan_ltm);
3317 im->xlab_user.minsec = 1;
3318 im->xlab_user.stst = im->xlab_form;
3320 rrd_set_error("invalid x-grid format");
3326 if(strcmp(optarg,"none") == 0){
3334 &im->ylabfact) == 2) {
3335 if(im->ygridstep<=0){
3336 rrd_set_error("grid step must be > 0");
3338 } else if (im->ylabfact < 1){
3339 rrd_set_error("label factor must be > 0");
3343 rrd_set_error("invalid y-grid format");
3348 strncpy(im->ylegend,optarg,150);
3349 im->ylegend[150]='\0';
3352 im->maxval = atof(optarg);
3355 im->minval = atof(optarg);
3358 im->base = atol(optarg);
3359 if(im->base != 1024 && im->base != 1000 ){
3360 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
3365 long_tmp = atol(optarg);
3366 if (long_tmp < 10) {
3367 rrd_set_error("width below 10 pixels");
3370 im->xsize = long_tmp;
3373 long_tmp = atol(optarg);
3374 if (long_tmp < 10) {
3375 rrd_set_error("height below 10 pixels");
3378 im->ysize = long_tmp;
3381 im->canvas->interlaced = 1;
3387 im->imginfo = optarg;
3390 if((int)(im->canvas->imgformat = if_conv(optarg)) == -1) {
3391 rrd_set_error("unsupported graphics format '%s'",optarg);
3403 im->logarithmic = 1;
3407 "%10[A-Z]#%n%8lx%n",
3408 col_nam,&col_start,&color,&col_end) == 2){
3410 int col_len = col_end - col_start;
3414 ((color & 0xF00) * 0x110000) |
3415 ((color & 0x0F0) * 0x011000) |
3416 ((color & 0x00F) * 0x001100) |
3422 ((color & 0xF000) * 0x11000) |
3423 ((color & 0x0F00) * 0x01100) |
3424 ((color & 0x00F0) * 0x00110) |
3425 ((color & 0x000F) * 0x00011)
3429 color = (color << 8) + 0xff /* shift left by 8 */;
3434 rrd_set_error("the color format is #RRGGBB[AA]");
3437 if((ci=grc_conv(col_nam)) != -1){
3438 im->graph_col[ci]=color;
3440 rrd_set_error("invalid color name '%s'",col_nam);
3444 rrd_set_error("invalid color def format");
3451 char font[1024] = "";
3454 "%10[A-Z]:%lf:%1000s",
3455 prop,&size,font) >= 2){
3457 if((sindex=text_prop_conv(prop)) != -1){
3458 for (propidx=sindex;propidx<TEXT_PROP_LAST;propidx++){
3460 im->text_prop[propidx].size=size;
3462 if (strlen(font) > 0){
3463 strcpy(im->text_prop[propidx].font,font);
3465 if (propidx==sindex && sindex != 0) break;
3468 rrd_set_error("invalid fonttag '%s'",prop);
3472 rrd_set_error("invalid text property format");
3478 im->canvas->zoom = atof(optarg);
3479 if (im->canvas->zoom <= 0.0) {
3480 rrd_set_error("zoom factor must be > 0");
3485 strncpy(im->title,optarg,150);
3486 im->title[150]='\0';
3490 if ( strcmp( optarg, "normal" ) == 0 )
3491 im->canvas->aa_type = AA_NORMAL;
3492 else if ( strcmp( optarg, "light" ) == 0 )
3493 im->canvas->aa_type = AA_LIGHT;
3494 else if ( strcmp( optarg, "mono" ) == 0 )
3495 im->canvas->aa_type = AA_NONE;
3498 rrd_set_error("unknown font-render-mode '%s'", optarg );
3504 im->canvas->font_aa_threshold = atof(optarg);
3508 strncpy(im->watermark,optarg,100);
3509 im->watermark[99]='\0';
3514 rrd_set_error("unknown option '%c'", optopt);
3516 rrd_set_error("unknown option '%s'",argv[optind-1]);
3521 if (optind >= argc) {
3522 rrd_set_error("missing filename");
3526 if (im->logarithmic == 1 && im->minval <= 0){
3527 rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");
3531 if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
3532 /* error string is set in parsetime.c */
3536 if (start_tmp < 3600*24*365*10){
3537 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
3541 if (end_tmp < start_tmp) {
3542 rrd_set_error("start (%ld) should be less than end (%ld)",
3543 start_tmp, end_tmp);
3547 im->start = start_tmp;
3549 im->step = max((long)im->step, (im->end-im->start)/im->xsize);
3553 rrd_graph_check_vname(image_desc_t *im, char *varname, char *err)
3555 if ((im->gdes[im->gdes_c-1].vidx=find_var(im,varname))==-1) {
3556 rrd_set_error("Unknown variable '%s' in %s",varname,err);
3562 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
3565 graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
3567 color=strstr(var,"#");
3570 rrd_set_error("Found no color in %s",err);
3579 rest=strstr(color,":");
3587 sscanf(color,"#%6lx%n",&col,&n);
3588 col = (col << 8) + 0xff /* shift left by 8 */;
3589 if (n!=7) rrd_set_error("Color problem in %s",err);
3592 sscanf(color,"#%8lx%n",&col,&n);
3595 rrd_set_error("Color problem in %s",err);
3597 if (rrd_test_error()) return 0;
3604 int bad_format(char *fmt) {
3608 while (*ptr != '\0')
3609 if (*ptr++ == '%') {
3611 /* line cannot end with percent char */
3612 if (*ptr == '\0') return 1;
3614 /* '%s', '%S' and '%%' are allowed */
3615 if (*ptr == 's' || *ptr == 'S' || *ptr == '%') ptr++;
3617 /* %c is allowed (but use only with vdef!) */
3618 else if (*ptr == 'c') {
3623 /* or else '% 6.2lf' and such are allowed */
3625 /* optional padding character */
3626 if (*ptr == ' ' || *ptr == '+' || *ptr == '-') ptr++;
3628 /* This should take care of 'm.n' with all three optional */
3629 while (*ptr >= '0' && *ptr <= '9') ptr++;
3630 if (*ptr == '.') ptr++;
3631 while (*ptr >= '0' && *ptr <= '9') ptr++;
3633 /* Either 'le', 'lf' or 'lg' must follow here */
3634 if (*ptr++ != 'l') return 1;
3635 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g') ptr++;
3646 vdef_parse(gdes,str)
3647 struct graph_desc_t *gdes;
3648 const char *const str;
3650 /* A VDEF currently is either "func" or "param,func"
3651 * so the parsing is rather simple. Change if needed.
3658 sscanf(str,"%le,%29[A-Z]%n",¶m,func,&n);
3659 if (n== (int)strlen(str)) { /* matched */
3663 sscanf(str,"%29[A-Z]%n",func,&n);
3664 if (n== (int)strlen(str)) { /* matched */
3667 rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3674 if (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3675 else if (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3676 else if (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3677 else if (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3678 else if (!strcmp("TOTAL", func)) gdes->vf.op = VDEF_TOTAL;
3679 else if (!strcmp("FIRST", func)) gdes->vf.op = VDEF_FIRST;
3680 else if (!strcmp("LAST", func)) gdes->vf.op = VDEF_LAST;
3681 else if (!strcmp("LSLSLOPE", func)) gdes->vf.op = VDEF_LSLSLOPE;
3682 else if (!strcmp("LSLINT", func)) gdes->vf.op = VDEF_LSLINT;
3683 else if (!strcmp("LSLCORREL",func)) gdes->vf.op = VDEF_LSLCORREL;
3685 rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3692 switch (gdes->vf.op) {
3694 if (isnan(param)) { /* no parameter given */
3695 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3701 if (param>=0.0 && param<=100.0) {
3702 gdes->vf.param = param;
3703 gdes->vf.val = DNAN; /* undefined */
3704 gdes->vf.when = 0; /* undefined */
3706 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3721 case VDEF_LSLCORREL:
3723 gdes->vf.param = DNAN;
3724 gdes->vf.val = DNAN;
3727 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3744 graph_desc_t *src,*dst;
3748 dst = &im->gdes[gdi];
3749 src = &im->gdes[dst->vidx];
3750 data = src->data + src->ds;
3751 steps = (src->end - src->start) / src->step;
3754 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3761 switch (dst->vf.op) {
3762 case VDEF_PERCENT: {
3763 rrd_value_t * array;
3767 if ((array = malloc(steps*sizeof(double)))==NULL) {
3768 rrd_set_error("malloc VDEV_PERCENT");
3771 for (step=0;step < steps; step++) {
3772 array[step]=data[step*src->ds_cnt];
3774 qsort(array,step,sizeof(double),vdef_percent_compar);
3776 field = (steps-1)*dst->vf.param/100;
3777 dst->vf.val = array[field];
3778 dst->vf.when = 0; /* no time component */
3781 for(step=0;step<steps;step++)
3782 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3788 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3789 if (step == steps) {
3793 dst->vf.val = data[step*src->ds_cnt];
3794 dst->vf.when = src->start + (step+1)*src->step;
3796 while (step != steps) {
3797 if (finite(data[step*src->ds_cnt])) {
3798 if (data[step*src->ds_cnt] > dst->vf.val) {
3799 dst->vf.val = data[step*src->ds_cnt];
3800 dst->vf.when = src->start + (step+1)*src->step;
3807 case VDEF_AVERAGE: {
3810 for (step=0;step<steps;step++) {
3811 if (finite(data[step*src->ds_cnt])) {
3812 sum += data[step*src->ds_cnt];
3817 if (dst->vf.op == VDEF_TOTAL) {
3818 dst->vf.val = sum*src->step;
3819 dst->vf.when = 0; /* no time component */
3821 dst->vf.val = sum/cnt;
3822 dst->vf.when = 0; /* no time component */
3832 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3833 if (step == steps) {
3837 dst->vf.val = data[step*src->ds_cnt];
3838 dst->vf.when = src->start + (step+1)*src->step;
3840 while (step != steps) {
3841 if (finite(data[step*src->ds_cnt])) {
3842 if (data[step*src->ds_cnt] < dst->vf.val) {
3843 dst->vf.val = data[step*src->ds_cnt];
3844 dst->vf.when = src->start + (step+1)*src->step;
3851 /* The time value returned here is one step before the
3852 * actual time value. This is the start of the first
3856 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3857 if (step == steps) { /* all entries were NaN */
3861 dst->vf.val = data[step*src->ds_cnt];
3862 dst->vf.when = src->start + step*src->step;
3866 /* The time value returned here is the
3867 * actual time value. This is the end of the last
3871 while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3872 if (step < 0) { /* all entries were NaN */
3876 dst->vf.val = data[step*src->ds_cnt];
3877 dst->vf.when = src->start + (step+1)*src->step;
3882 case VDEF_LSLCORREL:{
3883 /* Bestfit line by linear least squares method */
3886 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl ;
3887 SUMx = 0; SUMy = 0; SUMxy = 0; SUMxx = 0; SUMyy = 0;
3889 for (step=0;step<steps;step++) {
3890 if (finite(data[step*src->ds_cnt])) {
3893 SUMxx += step * step;
3894 SUMxy += step * data[step*src->ds_cnt];
3895 SUMy += data[step*src->ds_cnt];
3896 SUMyy += data[step*src->ds_cnt]*data[step*src->ds_cnt];
3900 slope = ( SUMx*SUMy - cnt*SUMxy ) / ( SUMx*SUMx - cnt*SUMxx );
3901 y_intercept = ( SUMy - slope*SUMx ) / cnt;
3902 correl = (SUMxy - (SUMx*SUMy)/cnt) / sqrt((SUMxx - (SUMx*SUMx)/cnt)*(SUMyy - (SUMy*SUMy)/cnt));
3905 if (dst->vf.op == VDEF_LSLSLOPE) {
3906 dst->vf.val = slope;
3908 } else if (dst->vf.op == VDEF_LSLINT) {
3909 dst->vf.val = y_intercept;
3911 } else if (dst->vf.op == VDEF_LSLCORREL) {
3912 dst->vf.val = correl;
3926 /* NaN < -INF < finite_values < INF */
3928 vdef_percent_compar(a,b)
3931 /* Equality is not returned; this doesn't hurt except
3932 * (maybe) for a little performance.
3935 /* First catch NaN values. They are smallest */
3936 if (isnan( *(double *)a )) return -1;
3937 if (isnan( *(double *)b )) return 1;
3939 /* NaN doesn't reach this part so INF and -INF are extremes.
3940 * The sign from isinf() is compatible with the sign we return
3942 if (isinf( *(double *)a )) return isinf( *(double *)a );
3943 if (isinf( *(double *)b )) return isinf( *(double *)b );
3945 /* If we reach this, both values must be finite */
3946 if ( *(double *)a < *(double *)b ) return -1; else return 1;