1 /****************************************************************************
2 * RRDtool 1.2.23 Copyright by Tobi Oetiker, 1997-2007
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_MIN) {
403 /* measure the amplitude of the function. Make sure that
404 graph boundaries are slightly lower than min vals
405 so we can see amplitude on the graph */
406 adj = (im->maxval - im->minval) * 0.1;
409 else if(im->extra_flags & ALTAUTOSCALE_MAX) {
410 /* measure the amplitude of the function. Make sure that
411 graph boundaries are slightly higher than max vals
412 so we can see amplitude on the graph */
413 adj = (im->maxval - im->minval) * 0.1;
417 scaled_min = im->minval / im->magfact;
418 scaled_max = im->maxval / im->magfact;
420 for (i=1; sensiblevalues[i] > 0; i++){
421 if (sensiblevalues[i-1]>=scaled_min &&
422 sensiblevalues[i]<=scaled_min)
423 im->minval = sensiblevalues[i]*(im->magfact);
425 if (-sensiblevalues[i-1]<=scaled_min &&
426 -sensiblevalues[i]>=scaled_min)
427 im->minval = -sensiblevalues[i-1]*(im->magfact);
429 if (sensiblevalues[i-1] >= scaled_max &&
430 sensiblevalues[i] <= scaled_max)
431 im->maxval = sensiblevalues[i-1]*(im->magfact);
433 if (-sensiblevalues[i-1]<=scaled_max &&
434 -sensiblevalues[i] >=scaled_max)
435 im->maxval = -sensiblevalues[i]*(im->magfact);
439 /* adjust min and max to the grid definition if there is one */
440 im->minval = (double)im->ylabfact * im->ygridstep *
441 floor(im->minval / ((double)im->ylabfact * im->ygridstep));
442 im->maxval = (double)im->ylabfact * im->ygridstep *
443 ceil(im->maxval /( (double)im->ylabfact * im->ygridstep));
447 fprintf(stderr,"SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
448 im->minval,im->maxval,im->magfact);
453 apply_gridfit(image_desc_t *im)
455 if (isnan(im->minval) || isnan(im->maxval))
458 if (im->logarithmic) {
459 double ya, yb, ypix, ypixfrac;
460 double log10_range = log10(im->maxval) - log10(im->minval);
461 ya = pow((double)10, floor(log10(im->minval)));
462 while (ya < im->minval)
465 return; /* don't have y=10^x gridline */
467 if (yb <= im->maxval) {
468 /* we have at least 2 y=10^x gridlines.
469 Make sure distance between them in pixels
470 are an integer by expanding im->maxval */
471 double y_pixel_delta = ytr(im, ya) - ytr(im, yb);
472 double factor = y_pixel_delta / floor(y_pixel_delta);
473 double new_log10_range = factor * log10_range;
474 double new_ymax_log10 = log10(im->minval) + new_log10_range;
475 im->maxval = pow(10, new_ymax_log10);
476 ytr(im,DNAN); /* reset precalc */
477 log10_range = log10(im->maxval) - log10(im->minval);
479 /* make sure first y=10^x gridline is located on
480 integer pixel position by moving scale slightly
481 downwards (sub-pixel movement) */
482 ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
483 ypixfrac = ypix - floor(ypix);
484 if (ypixfrac > 0 && ypixfrac < 1) {
485 double yfrac = ypixfrac / im->ysize;
486 im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
487 im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
488 ytr(im,DNAN); /* reset precalc */
491 /* Make sure we have an integer pixel distance between
492 each minor gridline */
493 double ypos1 = ytr(im, im->minval);
494 double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
495 double y_pixel_delta = ypos1 - ypos2;
496 double factor = y_pixel_delta / floor(y_pixel_delta);
497 double new_range = factor * (im->maxval - im->minval);
498 double gridstep = im->ygrid_scale.gridstep;
499 double minor_y, minor_y_px, minor_y_px_frac;
500 if (im->maxval > 0.0)
501 im->maxval = im->minval + new_range;
503 im->minval = im->maxval - new_range;
504 ytr(im,DNAN); /* reset precalc */
505 /* make sure first minor gridline is on integer pixel y coord */
506 minor_y = gridstep * floor(im->minval / gridstep);
507 while (minor_y < im->minval)
509 minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
510 minor_y_px_frac = minor_y_px - floor(minor_y_px);
511 if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
512 double yfrac = minor_y_px_frac / im->ysize;
513 double range = im->maxval - im->minval;
514 im->minval = im->minval - yfrac * range;
515 im->maxval = im->maxval - yfrac * range;
516 ytr(im,DNAN); /* reset precalc */
518 calc_horizontal_grid(im); /* recalc with changed im->maxval */
522 /* reduce data reimplementation by Alex */
526 enum cf_en cf, /* which consolidation function ?*/
527 unsigned long cur_step, /* step the data currently is in */
528 time_t *start, /* start, end and step as requested ... */
529 time_t *end, /* ... by the application will be ... */
530 unsigned long *step, /* ... adjusted to represent reality */
531 unsigned long *ds_cnt, /* number of data sources in file */
532 rrd_value_t **data) /* two dimensional array containing the data */
534 int i,reduce_factor = ceil((double)(*step) / (double)cur_step);
535 unsigned long col,dst_row,row_cnt,start_offset,end_offset,skiprows=0;
536 rrd_value_t *srcptr,*dstptr;
538 (*step) = cur_step*reduce_factor; /* set new step size for reduced data */
541 row_cnt = ((*end)-(*start))/cur_step;
547 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
548 row_cnt,reduce_factor,*start,*end,cur_step);
549 for (col=0;col<row_cnt;col++) {
550 printf("time %10lu: ",*start+(col+1)*cur_step);
551 for (i=0;i<*ds_cnt;i++)
552 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
557 /* We have to combine [reduce_factor] rows of the source
558 ** into one row for the destination. Doing this we also
559 ** need to take care to combine the correct rows. First
560 ** alter the start and end time so that they are multiples
561 ** of the new step time. We cannot reduce the amount of
562 ** time so we have to move the end towards the future and
563 ** the start towards the past.
565 end_offset = (*end) % (*step);
566 start_offset = (*start) % (*step);
568 /* If there is a start offset (which cannot be more than
569 ** one destination row), skip the appropriate number of
570 ** source rows and one destination row. The appropriate
571 ** number is what we do know (start_offset/cur_step) of
572 ** the new interval (*step/cur_step aka reduce_factor).
575 printf("start_offset: %lu end_offset: %lu\n",start_offset,end_offset);
576 printf("row_cnt before: %lu\n",row_cnt);
579 (*start) = (*start)-start_offset;
580 skiprows=reduce_factor-start_offset/cur_step;
581 srcptr+=skiprows* *ds_cnt;
582 for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
586 printf("row_cnt between: %lu\n",row_cnt);
589 /* At the end we have some rows that are not going to be
590 ** used, the amount is end_offset/cur_step
593 (*end) = (*end)-end_offset+(*step);
594 skiprows = end_offset/cur_step;
598 printf("row_cnt after: %lu\n",row_cnt);
601 /* Sanity check: row_cnt should be multiple of reduce_factor */
602 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
604 if (row_cnt%reduce_factor) {
605 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
606 row_cnt,reduce_factor);
607 printf("BUG in reduce_data()\n");
611 /* Now combine reduce_factor intervals at a time
612 ** into one interval for the destination.
615 for (dst_row=0;(long int)row_cnt>=reduce_factor;dst_row++) {
616 for (col=0;col<(*ds_cnt);col++) {
617 rrd_value_t newval=DNAN;
618 unsigned long validval=0;
620 for (i=0;i<reduce_factor;i++) {
621 if (isnan(srcptr[i*(*ds_cnt)+col])) {
625 if (isnan(newval)) newval = srcptr[i*(*ds_cnt)+col];
633 newval += srcptr[i*(*ds_cnt)+col];
636 newval = min (newval,srcptr[i*(*ds_cnt)+col]);
639 /* an interval contains a failure if any subintervals contained a failure */
641 newval = max (newval,srcptr[i*(*ds_cnt)+col]);
644 newval = srcptr[i*(*ds_cnt)+col];
649 if (validval == 0){newval = DNAN;} else{
667 srcptr+=(*ds_cnt)*reduce_factor;
668 row_cnt-=reduce_factor;
670 /* If we had to alter the endtime, we didn't have enough
671 ** source rows to fill the last row. Fill it with NaN.
673 if (end_offset) for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
675 row_cnt = ((*end)-(*start))/ *step;
677 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
678 row_cnt,*start,*end,*step);
679 for (col=0;col<row_cnt;col++) {
680 printf("time %10lu: ",*start+(col+1)*(*step));
681 for (i=0;i<*ds_cnt;i++)
682 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
689 /* get the data required for the graphs from the
693 data_fetch(image_desc_t *im )
698 /* pull the data from the rrd files ... */
699 for (i=0;i< (int)im->gdes_c;i++){
700 /* only GF_DEF elements fetch data */
701 if (im->gdes[i].gf != GF_DEF)
705 /* do we have it already ?*/
706 for (ii=0;ii<i;ii++) {
707 if (im->gdes[ii].gf != GF_DEF)
709 if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
710 && (im->gdes[i].cf == im->gdes[ii].cf)
711 && (im->gdes[i].cf_reduce == im->gdes[ii].cf_reduce)
712 && (im->gdes[i].start_orig == im->gdes[ii].start_orig)
713 && (im->gdes[i].end_orig == im->gdes[ii].end_orig)
714 && (im->gdes[i].step_orig == im->gdes[ii].step_orig)) {
715 /* OK, the data is already there.
716 ** Just copy the header portion
718 im->gdes[i].start = im->gdes[ii].start;
719 im->gdes[i].end = im->gdes[ii].end;
720 im->gdes[i].step = im->gdes[ii].step;
721 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
722 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
723 im->gdes[i].data = im->gdes[ii].data;
724 im->gdes[i].data_first = 0;
731 unsigned long ft_step = im->gdes[i].step ; /* ft_step will record what we got from fetch */
733 if((rrd_fetch_fn(im->gdes[i].rrd,
739 &im->gdes[i].ds_namv,
740 &im->gdes[i].data)) == -1){
743 im->gdes[i].data_first = 1;
745 if (ft_step < im->gdes[i].step) {
746 reduce_data(im->gdes[i].cf_reduce,
754 im->gdes[i].step = ft_step;
758 /* lets see if the required data source is really there */
759 for(ii=0;ii<(int)im->gdes[i].ds_cnt;ii++){
760 if(strcmp(im->gdes[i].ds_namv[ii],im->gdes[i].ds_nam) == 0){
763 if (im->gdes[i].ds== -1){
764 rrd_set_error("No DS called '%s' in '%s'",
765 im->gdes[i].ds_nam,im->gdes[i].rrd);
773 /* evaluate the expressions in the CDEF functions */
775 /*************************************************************
777 *************************************************************/
780 find_var_wrapper(void *arg1, char *key)
782 return find_var((image_desc_t *) arg1, key);
785 /* find gdes containing var*/
787 find_var(image_desc_t *im, char *key){
789 for(ii=0;ii<im->gdes_c-1;ii++){
790 if((im->gdes[ii].gf == GF_DEF
791 || im->gdes[ii].gf == GF_VDEF
792 || im->gdes[ii].gf == GF_CDEF)
793 && (strcmp(im->gdes[ii].vname,key) == 0)){
800 /* find the largest common denominator for all the numbers
801 in the 0 terminated num array */
806 for (i=0;num[i+1]!=0;i++){
808 rest=num[i] % num[i+1];
809 num[i]=num[i+1]; num[i+1]=rest;
813 /* return i==0?num[i]:num[i-1]; */
817 /* run the rpn calculator on all the VDEF and CDEF arguments */
819 data_calc( image_desc_t *im){
823 long *steparray, rpi;
828 rpnstack_init(&rpnstack);
830 for (gdi=0;gdi<im->gdes_c;gdi++){
831 /* Look for GF_VDEF and GF_CDEF in the same loop,
832 * so CDEFs can use VDEFs and vice versa
834 switch (im->gdes[gdi].gf) {
838 graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx];
840 /* remove current shift */
841 vdp->start -= vdp->shift;
842 vdp->end -= vdp->shift;
845 if (im->gdes[gdi].shidx >= 0)
846 vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
849 vdp->shift = im->gdes[gdi].shval;
851 /* normalize shift to multiple of consolidated step */
852 vdp->shift = (vdp->shift / (long)vdp->step) * (long)vdp->step;
855 vdp->start += vdp->shift;
856 vdp->end += vdp->shift;
860 /* A VDEF has no DS. This also signals other parts
861 * of rrdtool that this is a VDEF value, not a CDEF.
863 im->gdes[gdi].ds_cnt = 0;
864 if (vdef_calc(im,gdi)) {
865 rrd_set_error("Error processing VDEF '%s'"
868 rpnstack_free(&rpnstack);
873 im->gdes[gdi].ds_cnt = 1;
874 im->gdes[gdi].ds = 0;
875 im->gdes[gdi].data_first = 1;
876 im->gdes[gdi].start = 0;
877 im->gdes[gdi].end = 0;
882 /* Find the variables in the expression.
883 * - VDEF variables are substituted by their values
884 * and the opcode is changed into OP_NUMBER.
885 * - CDEF variables are analized for their step size,
886 * the lowest common denominator of all the step
887 * sizes of the data sources involved is calculated
888 * and the resulting number is the step size for the
889 * resulting data source.
891 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
892 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
893 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER){
894 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
895 if (im->gdes[ptr].ds_cnt == 0) { /* this is a VDEF data source */
897 printf("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
899 im->gdes[ptr].vname);
900 printf("DEBUG: value from vdef is %f\n",im->gdes[ptr].vf.val);
902 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
903 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
904 } else { /* normal variables and PREF(variables) */
906 /* add one entry to the array that keeps track of the step sizes of the
907 * data sources going into the CDEF. */
909 rrd_realloc(steparray,
910 (++stepcnt+1)*sizeof(*steparray)))==NULL){
911 rrd_set_error("realloc steparray");
912 rpnstack_free(&rpnstack);
916 steparray[stepcnt-1] = im->gdes[ptr].step;
918 /* adjust start and end of cdef (gdi) so
919 * that it runs from the latest start point
920 * to the earliest endpoint of any of the
921 * rras involved (ptr)
924 if(im->gdes[gdi].start < im->gdes[ptr].start)
925 im->gdes[gdi].start = im->gdes[ptr].start;
927 if(im->gdes[gdi].end == 0 ||
928 im->gdes[gdi].end > im->gdes[ptr].end)
929 im->gdes[gdi].end = im->gdes[ptr].end;
931 /* store pointer to the first element of
932 * the rra providing data for variable,
933 * further save step size and data source
936 im->gdes[gdi].rpnp[rpi].data = im->gdes[ptr].data + im->gdes[ptr].ds;
937 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
938 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
940 /* backoff the *.data ptr; this is done so
941 * rpncalc() function doesn't have to treat
942 * the first case differently
944 } /* if ds_cnt != 0 */
945 } /* if OP_VARIABLE */
946 } /* loop through all rpi */
948 /* move the data pointers to the correct period */
949 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
950 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
951 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER){
952 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
953 long diff = im->gdes[gdi].start - im->gdes[ptr].start;
956 im->gdes[gdi].rpnp[rpi].data += (diff / im->gdes[ptr].step) * im->gdes[ptr].ds_cnt;
960 if(steparray == NULL){
961 rrd_set_error("rpn expressions without DEF"
962 " or CDEF variables are not supported");
963 rpnstack_free(&rpnstack);
966 steparray[stepcnt]=0;
967 /* Now find the resulting step. All steps in all
968 * used RRAs have to be visited
970 im->gdes[gdi].step = lcd(steparray);
972 if((im->gdes[gdi].data = malloc((
973 (im->gdes[gdi].end-im->gdes[gdi].start)
974 / im->gdes[gdi].step)
975 * sizeof(double)))==NULL){
976 rrd_set_error("malloc im->gdes[gdi].data");
977 rpnstack_free(&rpnstack);
981 /* Step through the new cdef results array and
982 * calculate the values
984 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
985 now<=im->gdes[gdi].end;
986 now += im->gdes[gdi].step)
988 rpnp_t *rpnp = im -> gdes[gdi].rpnp;
990 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
991 * in this case we are advancing by timesteps;
992 * we use the fact that time_t is a synonym for long
994 if (rpn_calc(rpnp,&rpnstack,(long) now,
995 im->gdes[gdi].data,++dataidx) == -1) {
996 /* rpn_calc sets the error string */
997 rpnstack_free(&rpnstack);
1000 } /* enumerate over time steps within a CDEF */
1005 } /* enumerate over CDEFs */
1006 rpnstack_free(&rpnstack);
1010 /* massage data so, that we get one value for each x coordinate in the graph */
1012 data_proc( image_desc_t *im ){
1014 double pixstep = (double)(im->end-im->start)
1015 /(double)im->xsize; /* how much time
1016 passes in one pixel */
1018 double minval=DNAN,maxval=DNAN;
1020 unsigned long gr_time;
1022 /* memory for the processed data */
1023 for(i=0;i<im->gdes_c;i++) {
1024 if((im->gdes[i].gf==GF_LINE) ||
1025 (im->gdes[i].gf==GF_AREA) ||
1026 (im->gdes[i].gf==GF_TICK)) {
1027 if((im->gdes[i].p_data = malloc((im->xsize +1)
1028 * sizeof(rrd_value_t)))==NULL){
1029 rrd_set_error("malloc data_proc");
1035 for (i=0;i<im->xsize;i++) { /* for each pixel */
1037 gr_time = im->start+pixstep*i; /* time of the current step */
1040 for (ii=0;ii<im->gdes_c;ii++) {
1042 switch (im->gdes[ii].gf) {
1046 if (!im->gdes[ii].stack)
1048 value = im->gdes[ii].yrule;
1049 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1050 /* The time of the data doesn't necessarily match
1051 ** the time of the graph. Beware.
1053 vidx = im->gdes[ii].vidx;
1054 if (im->gdes[vidx].gf == GF_VDEF) {
1055 value = im->gdes[vidx].vf.val;
1056 } else if (((long int)gr_time >= (long int)im->gdes[vidx].start) &&
1057 ((long int)gr_time <= (long int)im->gdes[vidx].end) ) {
1058 value = im->gdes[vidx].data[
1059 (unsigned long) floor(
1060 (double)(gr_time - im->gdes[vidx].start)
1061 / im->gdes[vidx].step)
1062 * im->gdes[vidx].ds_cnt
1070 if (! isnan(value)) {
1072 im->gdes[ii].p_data[i] = paintval;
1073 /* GF_TICK: the data values are not
1074 ** relevant for min and max
1076 if (finite(paintval) && im->gdes[ii].gf != GF_TICK ) {
1077 if ((isnan(minval) || paintval < minval ) &&
1078 ! (im->logarithmic && paintval <= 0.0))
1080 if (isnan(maxval) || paintval > maxval)
1084 im->gdes[ii].p_data[i] = DNAN;
1088 rrd_set_error("STACK should already be turned into LINE or AREA here");
1097 /* if min or max have not been asigned a value this is because
1098 there was no data in the graph ... this is not good ...
1099 lets set these to dummy values then ... */
1101 if (im->logarithmic) {
1102 if (isnan(minval)) minval = 0.2;
1103 if (isnan(maxval)) maxval = 5.1;
1106 if (isnan(minval)) minval = 0.0;
1107 if (isnan(maxval)) maxval = 1.0;
1110 /* adjust min and max values */
1111 if (isnan(im->minval)
1112 /* don't adjust low-end with log scale */ /* why not? */
1113 || ((!im->rigid) && im->minval > minval)
1115 if (im->logarithmic)
1116 im->minval = minval * 0.5;
1118 im->minval = minval;
1120 if (isnan(im->maxval)
1121 || (!im->rigid && im->maxval < maxval)
1123 if (im->logarithmic)
1124 im->maxval = maxval * 2.0;
1126 im->maxval = maxval;
1128 /* make sure min is smaller than max */
1129 if (im->minval > im->maxval) {
1130 im->minval = 0.99 * im->maxval;
1133 /* make sure min and max are not equal */
1134 if (im->minval == im->maxval) {
1136 if (! im->logarithmic) {
1139 /* make sure min and max are not both zero */
1140 if (im->maxval == 0.0) {
1149 /* identify the point where the first gridline, label ... gets placed */
1153 time_t start, /* what is the initial time */
1154 enum tmt_en baseint, /* what is the basic interval */
1155 long basestep /* how many if these do we jump a time */
1159 localtime_r(&start, &tm);
1162 tm.tm_sec -= tm.tm_sec % basestep; break;
1165 tm.tm_min -= tm.tm_min % basestep;
1170 tm.tm_hour -= tm.tm_hour % basestep; break;
1172 /* we do NOT look at the basestep for this ... */
1175 tm.tm_hour = 0; break;
1177 /* we do NOT look at the basestep for this ... */
1181 tm.tm_mday -= tm.tm_wday -1; /* -1 because we want the monday */
1182 if (tm.tm_wday==0) tm.tm_mday -= 7; /* we want the *previous* monday */
1189 tm.tm_mon -= tm.tm_mon % basestep; break;
1197 tm.tm_year -= (tm.tm_year+1900) % basestep;
1202 /* identify the point where the next gridline, label ... gets placed */
1205 time_t current, /* what is the initial time */
1206 enum tmt_en baseint, /* what is the basic interval */
1207 long basestep /* how many if these do we jump a time */
1212 localtime_r(¤t, &tm);
1216 tm.tm_sec += basestep; break;
1218 tm.tm_min += basestep; break;
1220 tm.tm_hour += basestep; break;
1222 tm.tm_mday += basestep; break;
1224 tm.tm_mday += 7*basestep; break;
1226 tm.tm_mon += basestep; break;
1228 tm.tm_year += basestep;
1230 madetime = mktime(&tm);
1231 } while (madetime == -1); /* this is necessary to skip impssible times
1232 like the daylight saving time skips */
1238 /* calculate values required for PRINT and GPRINT functions */
1241 print_calc(image_desc_t *im, char ***prdata)
1243 long i,ii,validsteps;
1246 int graphelement = 0;
1249 double magfact = -1;
1253 /* wow initializing tmvdef is quite a task :-) */
1254 time_t now = time(NULL);
1255 localtime_r(&now,&tmvdef);
1256 if (im->imginfo) prlines++;
1257 for(i=0;i<im->gdes_c;i++){
1258 vidx = im->gdes[i].vidx;
1259 switch(im->gdes[i].gf){
1262 if(((*prdata) = rrd_realloc((*prdata),prlines*sizeof(char *)))==NULL){
1263 rrd_set_error("realloc prdata");
1267 /* PRINT and GPRINT can now print VDEF generated values.
1268 * There's no need to do any calculations on them as these
1269 * calculations were already made.
1271 if (im->gdes[vidx].gf==GF_VDEF) { /* simply use vals */
1272 printval = im->gdes[vidx].vf.val;
1273 localtime_r(&im->gdes[vidx].vf.when,&tmvdef);
1274 } else { /* need to calculate max,min,avg etcetera */
1275 max_ii =((im->gdes[vidx].end
1276 - im->gdes[vidx].start)
1277 / im->gdes[vidx].step
1278 * im->gdes[vidx].ds_cnt);
1281 for( ii=im->gdes[vidx].ds;
1283 ii+=im->gdes[vidx].ds_cnt){
1284 if (! finite(im->gdes[vidx].data[ii]))
1286 if (isnan(printval)){
1287 printval = im->gdes[vidx].data[ii];
1292 switch (im->gdes[i].cf){
1295 case CF_DEVSEASONAL:
1299 printval += im->gdes[vidx].data[ii];
1302 printval = min( printval, im->gdes[vidx].data[ii]);
1306 printval = max( printval, im->gdes[vidx].data[ii]);
1309 printval = im->gdes[vidx].data[ii];
1312 if (im->gdes[i].cf==CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1313 if (validsteps > 1) {
1314 printval = (printval / validsteps);
1317 } /* prepare printval */
1319 if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) {
1320 /* Magfact is set to -1 upon entry to print_calc. If it
1321 * is still less than 0, then we need to run auto_scale.
1322 * Otherwise, put the value into the correct units. If
1323 * the value is 0, then do not set the symbol or magnification
1324 * so next the calculation will be performed again. */
1325 if (magfact < 0.0) {
1326 auto_scale(im,&printval,&si_symb,&magfact);
1327 if (printval == 0.0)
1330 printval /= magfact;
1332 *(++percent_s) = 's';
1333 } else if (strstr(im->gdes[i].format,"%s") != NULL) {
1334 auto_scale(im,&printval,&si_symb,&magfact);
1337 if (im->gdes[i].gf == GF_PRINT){
1338 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1339 (*prdata)[prlines-1] = NULL;
1340 if (im->gdes[i].strftm){
1341 strftime((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,&tmvdef);
1343 if (bad_format(im->gdes[i].format)) {
1344 rrd_set_error("bad format for PRINT in '%s'", im->gdes[i].format);
1348 #ifdef HAVE_SNPRINTF
1349 snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
1351 sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
1357 if (im->gdes[i].strftm){
1358 strftime(im->gdes[i].legend,FMT_LEG_LEN,im->gdes[i].format,&tmvdef);
1360 if (bad_format(im->gdes[i].format)) {
1361 rrd_set_error("bad format for GPRINT in '%s'", im->gdes[i].format);
1364 #ifdef HAVE_SNPRINTF
1365 snprintf(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
1367 sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
1379 if(isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1380 im->gdes[i].yrule=im->gdes[vidx].vf.val;
1385 if(im->gdes[i].xrule == 0) { /* again ... the legend printer needs it*/
1386 im->gdes[i].xrule = im->gdes[vidx].vf.when;
1394 #ifdef WITH_PIECHART
1401 rrd_set_error("STACK should already be turned into LINE or AREA here");
1406 return graphelement;
1410 /* place legends with color spots */
1412 leg_place(image_desc_t *im)
1415 int interleg = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1416 int border = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1417 int fill=0, fill_last;
1419 int leg_x = border, leg_y = im->yimg;
1420 int leg_y_prev = im->yimg;
1424 char prt_fctn; /*special printfunctions */
1427 if( !(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH) ) {
1428 if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1429 rrd_set_error("malloc for legspace");
1433 for(i=0;i<im->gdes_c;i++){
1436 /* hid legends for rules which are not displayed */
1438 if(!(im->extra_flags & FORCE_RULES_LEGEND)) {
1439 if (im->gdes[i].gf == GF_HRULE &&
1440 (im->gdes[i].yrule < im->minval || im->gdes[i].yrule > im->maxval))
1441 im->gdes[i].legend[0] = '\0';
1443 if (im->gdes[i].gf == GF_VRULE &&
1444 (im->gdes[i].xrule < im->start || im->gdes[i].xrule > im->end))
1445 im->gdes[i].legend[0] = '\0';
1448 leg_cc = strlen(im->gdes[i].legend);
1450 /* is there a controle code ant the end of the legend string ? */
1451 /* and it is not a tab \\t */
1452 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\' && im->gdes[i].legend[leg_cc-1] != 't') {
1453 prt_fctn = im->gdes[i].legend[leg_cc-1];
1455 im->gdes[i].legend[leg_cc] = '\0';
1459 /* only valid control codes */
1460 if (prt_fctn != 'l' &&
1461 prt_fctn != 'n' && /* a synonym for l */
1470 rrd_set_error("Unknown control code at the end of '%s\\%c'",im->gdes[i].legend,prt_fctn);
1475 /* remove exess space */
1476 if ( prt_fctn == 'n' ){
1480 while (prt_fctn=='g' &&
1482 im->gdes[i].legend[leg_cc-1]==' '){
1484 im->gdes[i].legend[leg_cc]='\0';
1487 legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1490 /* no interleg space if string ends in \g */
1491 fill += legspace[i];
1493 fill += gfx_get_text_width(im->canvas, fill+border,
1494 im->text_prop[TEXT_PROP_LEGEND].font,
1495 im->text_prop[TEXT_PROP_LEGEND].size,
1497 im->gdes[i].legend, 0);
1502 /* who said there was a special tag ... ?*/
1503 if (prt_fctn=='g') {
1506 if (prt_fctn == '\0') {
1507 if (i == im->gdes_c -1 ) prt_fctn ='l';
1509 /* is it time to place the legends ? */
1510 if (fill > im->ximg - 2*border){
1525 if (prt_fctn != '\0'){
1527 if (leg_c >= 2 && prt_fctn == 'j') {
1528 glue = (im->ximg - fill - 2* border) / (leg_c-1);
1532 if (prt_fctn =='c') leg_x = (im->ximg - fill) / 2.0;
1533 if (prt_fctn =='r') leg_x = im->ximg - fill - border;
1535 for(ii=mark;ii<=i;ii++){
1536 if(im->gdes[ii].legend[0]=='\0')
1537 continue; /* skip empty legends */
1538 im->gdes[ii].leg_x = leg_x;
1539 im->gdes[ii].leg_y = leg_y;
1541 gfx_get_text_width(im->canvas, leg_x,
1542 im->text_prop[TEXT_PROP_LEGEND].font,
1543 im->text_prop[TEXT_PROP_LEGEND].size,
1545 im->gdes[ii].legend, 0)
1550 /* only add y space if there was text on the line */
1551 if (leg_x > border || prt_fctn == 's')
1552 leg_y += im->text_prop[TEXT_PROP_LEGEND].size*1.8;
1553 if (prt_fctn == 's')
1554 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1560 im->yimg = leg_y_prev;
1561 /* if we did place some legends we have to add vertical space */
1562 if (leg_y != im->yimg){
1563 im->yimg += im->text_prop[TEXT_PROP_LEGEND].size*1.8;
1570 /* create a grid on the graph. it determines what to do
1571 from the values of xsize, start and end */
1573 /* the xaxis labels are determined from the number of seconds per pixel
1574 in the requested graph */
1579 calc_horizontal_grid(image_desc_t *im)
1585 int decimals, fractionals;
1587 im->ygrid_scale.labfact=2;
1588 range = im->maxval - im->minval;
1589 scaledrange = range / im->magfact;
1591 /* does the scale of this graph make it impossible to put lines
1592 on it? If so, give up. */
1593 if (isnan(scaledrange)) {
1597 /* find grid spaceing */
1599 if(isnan(im->ygridstep)){
1600 if(im->extra_flags & ALTYGRID) {
1601 /* find the value with max number of digits. Get number of digits */
1602 decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))*im->viewfactor/im->magfact));
1603 if(decimals <= 0) /* everything is small. make place for zero */
1606 im->ygrid_scale.gridstep = pow((double)10, floor(log10(range*im->viewfactor/im->magfact)))/im->viewfactor*im->magfact;
1608 if(im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1609 im->ygrid_scale.gridstep = 0.1;
1610 /* should have at least 5 lines but no more then 15 */
1611 if(range/im->ygrid_scale.gridstep < 5)
1612 im->ygrid_scale.gridstep /= 10;
1613 if(range/im->ygrid_scale.gridstep > 15)
1614 im->ygrid_scale.gridstep *= 10;
1615 if(range/im->ygrid_scale.gridstep > 5) {
1616 im->ygrid_scale.labfact = 1;
1617 if(range/im->ygrid_scale.gridstep > 8)
1618 im->ygrid_scale.labfact = 2;
1621 im->ygrid_scale.gridstep /= 5;
1622 im->ygrid_scale.labfact = 5;
1624 fractionals = floor(log10(im->ygrid_scale.gridstep*(double)im->ygrid_scale.labfact*im->viewfactor/im->magfact));
1625 if(fractionals < 0) { /* small amplitude. */
1626 int len = decimals - fractionals + 1;
1627 if (im->unitslength < len+2) im->unitslength = len+2;
1628 sprintf(im->ygrid_scale.labfmt, "%%%d.%df%s", len, -fractionals,(im->symbol != ' ' ? " %c" : ""));
1630 int len = decimals + 1;
1631 if (im->unitslength < len+2) im->unitslength = len+2;
1632 sprintf(im->ygrid_scale.labfmt, "%%%d.0f%s", len, ( im->symbol != ' ' ? " %c" : "" ));
1636 for(i=0;ylab[i].grid > 0;i++){
1637 pixel = im->ysize / (scaledrange / ylab[i].grid);
1644 if (pixel * ylab[gridind].lfac[i] >= 2.5 * im->text_prop[TEXT_PROP_AXIS].size) {
1645 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1650 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1653 im->ygrid_scale.gridstep = im->ygridstep;
1654 im->ygrid_scale.labfact = im->ylabfact;
1659 int draw_horizontal_grid(image_desc_t *im)
1663 char graph_label[100];
1665 double X0=im->xorigin;
1666 double X1=im->xorigin+im->xsize;
1668 int sgrid = (int)( im->minval / im->ygrid_scale.gridstep - 1);
1669 int egrid = (int)( im->maxval / im->ygrid_scale.gridstep + 1);
1671 scaledstep = im->ygrid_scale.gridstep/(double)im->magfact*(double)im->viewfactor;
1672 MaxY = scaledstep*(double)egrid;
1673 for (i = sgrid; i <= egrid; i++){
1674 double Y0=ytr(im,im->ygrid_scale.gridstep*i);
1675 double YN=ytr(im,im->ygrid_scale.gridstep*(i+1));
1676 if ( floor(Y0+0.5) >= im->yorigin-im->ysize
1677 && floor(Y0+0.5) <= im->yorigin){
1678 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
1679 with the chosen settings. Add a label if required by settings, or if
1680 there is only one label so far and the next grid line is out of bounds. */
1681 if(i % im->ygrid_scale.labfact == 0 || ( nlabels==1 && (YN < im->yorigin-im->ysize || YN > im->yorigin) )){
1682 if (im->symbol == ' ') {
1683 if(im->extra_flags & ALTYGRID) {
1684 sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*(double)i);
1687 sprintf(graph_label,"%4.1f",scaledstep*(double)i);
1689 sprintf(graph_label,"%4.0f",scaledstep*(double)i);
1693 char sisym = ( i == 0 ? ' ' : im->symbol);
1694 if(im->extra_flags & ALTYGRID) {
1695 sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*(double)i,sisym);
1698 sprintf(graph_label,"%4.1f %c",scaledstep*(double)i, sisym);
1700 sprintf(graph_label,"%4.0f %c",scaledstep*(double)i, sisym);
1706 gfx_new_text ( im->canvas,
1707 X0-im->text_prop[TEXT_PROP_AXIS].size, Y0,
1708 im->graph_col[GRC_FONT],
1709 im->text_prop[TEXT_PROP_AXIS].font,
1710 im->text_prop[TEXT_PROP_AXIS].size,
1711 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1713 gfx_new_dashed_line ( im->canvas,
1716 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1717 im->grid_dash_on, im->grid_dash_off);
1719 } else if (!(im->extra_flags & NOMINOR)) {
1720 gfx_new_dashed_line ( im->canvas,
1723 GRIDWIDTH, im->graph_col[GRC_GRID],
1724 im->grid_dash_on, im->grid_dash_off);
1732 /* this is frexp for base 10 */
1733 double frexp10(double, double *);
1734 double frexp10(double x, double *e) {
1738 iexp = floor(log(fabs(x)) / log(10));
1739 mnt = x / pow(10.0, iexp);
1742 mnt = x / pow(10.0, iexp);
1748 static int AlmostEqual2sComplement (float A, float B, int maxUlps)
1751 int aInt = *(int*)&A;
1752 int bInt = *(int*)&B;
1754 /* Make sure maxUlps is non-negative and small enough that the
1755 default NAN won't compare as equal to anything. */
1757 /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
1759 /* Make aInt lexicographically ordered as a twos-complement int */
1762 aInt = 0x80000000l - aInt;
1764 /* Make bInt lexicographically ordered as a twos-complement int */
1767 bInt = 0x80000000l - bInt;
1769 intDiff = abs(aInt - bInt);
1771 if (intDiff <= maxUlps)
1777 /* logaritmic horizontal grid */
1779 horizontal_log_grid(image_desc_t *im)
1781 double yloglab[][10] = {
1782 {1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
1783 {1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
1784 {1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0},
1785 {1.0, 2.0, 4.0, 6.0, 8.0, 10., 0.0, 0.0, 0.0, 0.0},
1786 {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.},
1787 {0,0,0,0,0, 0,0,0,0,0} /* last line */ };
1789 int i, j, val_exp, min_exp;
1790 double nex; /* number of decades in data */
1791 double logscale; /* scale in logarithmic space */
1792 int exfrac = 1; /* decade spacing */
1793 int mid = -1; /* row in yloglab for major grid */
1794 double mspac; /* smallest major grid spacing (pixels) */
1795 int flab; /* first value in yloglab to use */
1796 double value, tmp, pre_value;
1798 char graph_label[100];
1800 nex = log10(im->maxval / im->minval);
1801 logscale = im->ysize / nex;
1803 /* major spacing for data with high dynamic range */
1804 while(logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
1805 if(exfrac == 1) exfrac = 3;
1809 /* major spacing for less dynamic data */
1811 /* search best row in yloglab */
1813 for(i = 0; yloglab[mid][i + 1] < 10.0; i++);
1814 mspac = logscale * log10(10.0 / yloglab[mid][i]);
1815 } while(mspac > 2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0);
1818 /* find first value in yloglab */
1819 for(flab = 0; yloglab[mid][flab] < 10 && frexp10(im->minval, &tmp) > yloglab[mid][flab] ; flab++);
1820 if(yloglab[mid][flab] == 10.0) {
1825 if(val_exp % exfrac) val_exp += abs(-val_exp % exfrac);
1828 X1=im->xorigin+im->xsize;
1834 value = yloglab[mid][flab] * pow(10.0, val_exp);
1835 if ( AlmostEqual2sComplement(value,pre_value,4) ) break; /* it seems we are not converging */
1839 Y0 = ytr(im, value);
1840 if(floor(Y0+0.5) <= im->yorigin - im->ysize) break;
1842 /* major grid line */
1843 gfx_new_dashed_line ( im->canvas,
1846 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1847 im->grid_dash_on, im->grid_dash_off);
1850 if (im->extra_flags & FORCE_UNITS_SI) {
1855 scale = floor(val_exp / 3.0);
1856 if( value >= 1.0 ) pvalue = pow(10.0, val_exp % 3);
1857 else pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
1858 pvalue *= yloglab[mid][flab];
1860 if ( ((scale+si_symbcenter) < (int)sizeof(si_symbol)) &&
1861 ((scale+si_symbcenter) >= 0) )
1862 symbol = si_symbol[scale+si_symbcenter];
1866 sprintf(graph_label,"%3.0f %c", pvalue, symbol);
1868 sprintf(graph_label,"%3.0e", value);
1869 gfx_new_text ( im->canvas,
1870 X0-im->text_prop[TEXT_PROP_AXIS].size, Y0,
1871 im->graph_col[GRC_FONT],
1872 im->text_prop[TEXT_PROP_AXIS].font,
1873 im->text_prop[TEXT_PROP_AXIS].size,
1874 im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1878 if(mid < 4 && exfrac == 1) {
1879 /* find first and last minor line behind current major line
1880 * i is the first line and j tha last */
1882 min_exp = val_exp - 1;
1883 for(i = 1; yloglab[mid][i] < 10.0; i++);
1884 i = yloglab[mid][i - 1] + 1;
1889 i = yloglab[mid][flab - 1] + 1;
1890 j = yloglab[mid][flab];
1893 /* draw minor lines below current major line */
1896 value = i * pow(10.0, min_exp);
1897 if(value < im->minval) continue;
1899 Y0 = ytr(im, value);
1900 if(floor(Y0+0.5) <= im->yorigin - im->ysize) break;
1903 gfx_new_dashed_line ( im->canvas,
1906 GRIDWIDTH, im->graph_col[GRC_GRID],
1907 im->grid_dash_on, im->grid_dash_off);
1910 else if(exfrac > 1) {
1911 for(i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
1912 value = pow(10.0, i);
1913 if(value < im->minval) continue;
1915 Y0 = ytr(im, value);
1916 if(floor(Y0+0.5) <= im->yorigin - im->ysize) break;
1919 gfx_new_dashed_line ( im->canvas,
1922 GRIDWIDTH, im->graph_col[GRC_GRID],
1923 im->grid_dash_on, im->grid_dash_off);
1928 if(yloglab[mid][++flab] == 10.0) {
1934 /* draw minor lines after highest major line */
1935 if(mid < 4 && exfrac == 1) {
1936 /* find first and last minor line below current major line
1937 * i is the first line and j tha last */
1939 min_exp = val_exp - 1;
1940 for(i = 1; yloglab[mid][i] < 10.0; i++);
1941 i = yloglab[mid][i - 1] + 1;
1946 i = yloglab[mid][flab - 1] + 1;
1947 j = yloglab[mid][flab];
1950 /* draw minor lines below current major line */
1953 value = i * pow(10.0, min_exp);
1954 if(value < im->minval) continue;
1956 Y0 = ytr(im, value);
1957 if(floor(Y0+0.5) <= im->yorigin - im->ysize) break;
1960 gfx_new_dashed_line ( im->canvas,
1963 GRIDWIDTH, im->graph_col[GRC_GRID],
1964 im->grid_dash_on, im->grid_dash_off);
1967 /* fancy minor gridlines */
1968 else if(exfrac > 1) {
1969 for(i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
1970 value = pow(10.0, i);
1971 if(value < im->minval) continue;
1973 Y0 = ytr(im, value);
1974 if(floor(Y0+0.5) <= im->yorigin - im->ysize) break;
1977 gfx_new_dashed_line ( im->canvas,
1980 GRIDWIDTH, im->graph_col[GRC_GRID],
1981 im->grid_dash_on, im->grid_dash_off);
1993 int xlab_sel; /* which sort of label and grid ? */
1994 time_t ti, tilab, timajor;
1996 char graph_label[100];
1997 double X0,Y0,Y1; /* points for filled graph and more*/
2000 /* the type of time grid is determined by finding
2001 the number of seconds per pixel in the graph */
2004 if(im->xlab_user.minsec == -1){
2005 factor=(im->end - im->start)/im->xsize;
2007 while ( xlab[xlab_sel+1].minsec != -1
2008 && xlab[xlab_sel+1].minsec <= factor) { xlab_sel++; } /* pick the last one */
2009 while ( xlab[xlab_sel-1].minsec == xlab[xlab_sel].minsec
2010 && xlab[xlab_sel].length > (im->end - im->start)) { xlab_sel--; } /* go back to the smallest size */
2011 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2012 im->xlab_user.gridst = xlab[xlab_sel].gridst;
2013 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2014 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2015 im->xlab_user.labtm = xlab[xlab_sel].labtm;
2016 im->xlab_user.labst = xlab[xlab_sel].labst;
2017 im->xlab_user.precis = xlab[xlab_sel].precis;
2018 im->xlab_user.stst = xlab[xlab_sel].stst;
2021 /* y coords are the same for every line ... */
2023 Y1 = im->yorigin-im->ysize;
2026 /* paint the minor grid */
2027 if (!(im->extra_flags & NOMINOR))
2029 for(ti = find_first_time(im->start,
2030 im->xlab_user.gridtm,
2031 im->xlab_user.gridst),
2032 timajor = find_first_time(im->start,
2033 im->xlab_user.mgridtm,
2034 im->xlab_user.mgridst);
2036 ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
2038 /* are we inside the graph ? */
2039 if (ti < im->start || ti > im->end) continue;
2040 while (timajor < ti) {
2041 timajor = find_next_time(timajor,
2042 im->xlab_user.mgridtm, im->xlab_user.mgridst);
2044 if (ti == timajor) continue; /* skip as falls on major grid line */
2046 gfx_new_dashed_line(im->canvas,X0,Y0+1, X0,Y1-1,GRIDWIDTH,
2047 im->graph_col[GRC_GRID],
2048 im->grid_dash_on, im->grid_dash_off);
2053 /* paint the major grid */
2054 for(ti = find_first_time(im->start,
2055 im->xlab_user.mgridtm,
2056 im->xlab_user.mgridst);
2058 ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
2060 /* are we inside the graph ? */
2061 if (ti < im->start || ti > im->end) continue;
2063 gfx_new_dashed_line(im->canvas,X0,Y0+3, X0,Y1-2,MGRIDWIDTH,
2064 im->graph_col[GRC_MGRID],
2065 im->grid_dash_on, im->grid_dash_off);
2068 /* paint the labels below the graph */
2069 for(ti = find_first_time(im->start - im->xlab_user.precis/2,
2070 im->xlab_user.labtm,
2071 im->xlab_user.labst);
2072 ti <= im->end - im->xlab_user.precis/2;
2073 ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
2075 tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
2076 /* are we inside the graph ? */
2077 if (tilab < im->start || tilab > im->end) continue;
2080 localtime_r(&tilab, &tm);
2081 strftime(graph_label,99,im->xlab_user.stst, &tm);
2083 # error "your libc has no strftime I guess we'll abort the exercise here."
2085 gfx_new_text ( im->canvas,
2086 xtr(im,tilab), Y0+im->text_prop[TEXT_PROP_AXIS].size*1.4+5,
2087 im->graph_col[GRC_FONT],
2088 im->text_prop[TEXT_PROP_AXIS].font,
2089 im->text_prop[TEXT_PROP_AXIS].size,
2090 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_BOTTOM,
2103 /* draw x and y axis */
2104 /* gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2105 im->xorigin+im->xsize,im->yorigin-im->ysize,
2106 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2108 gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2109 im->xorigin+im->xsize,im->yorigin-im->ysize,
2110 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2112 gfx_new_line ( im->canvas, im->xorigin-4,im->yorigin,
2113 im->xorigin+im->xsize+4,im->yorigin,
2114 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2116 gfx_new_line ( im->canvas, im->xorigin,im->yorigin+4,
2117 im->xorigin,im->yorigin-im->ysize-4,
2118 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2121 /* arrow for X and Y axis direction */
2122 gfx_new_area ( im->canvas,
2123 im->xorigin+im->xsize+2, im->yorigin-2,
2124 im->xorigin+im->xsize+2, im->yorigin+3,
2125 im->xorigin+im->xsize+7, im->yorigin+0.5, /* LINEOFFSET */
2126 im->graph_col[GRC_ARROW]);
2128 gfx_new_area ( im->canvas,
2129 im->xorigin-2, im->yorigin-im->ysize-2,
2130 im->xorigin+3, im->yorigin-im->ysize-2,
2131 im->xorigin+0.5, im->yorigin-im->ysize-7, /* LINEOFFSET */
2132 im->graph_col[GRC_ARROW]);
2137 grid_paint(image_desc_t *im)
2141 double X0,Y0; /* points for filled graph and more*/
2144 /* draw 3d border */
2145 node = gfx_new_area (im->canvas, 0,im->yimg,
2147 2,2,im->graph_col[GRC_SHADEA]);
2148 gfx_add_point( node , im->ximg - 2, 2 );
2149 gfx_add_point( node , im->ximg, 0 );
2150 gfx_add_point( node , 0,0 );
2151 /* gfx_add_point( node , 0,im->yimg ); */
2153 node = gfx_new_area (im->canvas, 2,im->yimg-2,
2154 im->ximg-2,im->yimg-2,
2156 im->graph_col[GRC_SHADEB]);
2157 gfx_add_point( node , im->ximg,0);
2158 gfx_add_point( node , im->ximg,im->yimg);
2159 gfx_add_point( node , 0,im->yimg);
2160 /* gfx_add_point( node , 0,im->yimg ); */
2163 if (im->draw_x_grid == 1 )
2166 if (im->draw_y_grid == 1){
2167 if(im->logarithmic){
2168 res = horizontal_log_grid(im);
2170 res = draw_horizontal_grid(im);
2173 /* dont draw horizontal grid if there is no min and max val */
2175 char *nodata = "No Data found";
2176 gfx_new_text(im->canvas,im->ximg/2, (2*im->yorigin-im->ysize) / 2,
2177 im->graph_col[GRC_FONT],
2178 im->text_prop[TEXT_PROP_AXIS].font,
2179 im->text_prop[TEXT_PROP_AXIS].size,
2180 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
2185 /* yaxis unit description */
2186 gfx_new_text( im->canvas,
2187 10, (im->yorigin - im->ysize/2),
2188 im->graph_col[GRC_FONT],
2189 im->text_prop[TEXT_PROP_UNIT].font,
2190 im->text_prop[TEXT_PROP_UNIT].size, im->tabwidth,
2191 RRDGRAPH_YLEGEND_ANGLE,
2192 GFX_H_LEFT, GFX_V_CENTER,
2196 gfx_new_text( im->canvas,
2197 im->ximg/2, im->text_prop[TEXT_PROP_TITLE].size*1.3+4,
2198 im->graph_col[GRC_FONT],
2199 im->text_prop[TEXT_PROP_TITLE].font,
2200 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
2201 GFX_H_CENTER, GFX_V_CENTER,
2203 /* rrdtool 'logo' */
2204 gfx_new_text( im->canvas,
2206 ( im->graph_col[GRC_FONT] & 0xffffff00 ) | 0x00000044,
2207 im->text_prop[TEXT_PROP_AXIS].font,
2208 5.5, im->tabwidth, 270,
2209 GFX_H_RIGHT, GFX_V_TOP,
2210 "RRDTOOL / TOBI OETIKER");
2212 /* graph watermark */
2213 if(im->watermark[0] != '\0') {
2214 gfx_new_text( im->canvas,
2215 im->ximg/2, im->yimg-6,
2216 ( im->graph_col[GRC_FONT] & 0xffffff00 ) | 0x00000044,
2217 im->text_prop[TEXT_PROP_AXIS].font,
2218 5.5, im->tabwidth, 0,
2219 GFX_H_CENTER, GFX_V_BOTTOM,
2224 if( !(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH) ) {
2225 for(i=0;i<im->gdes_c;i++){
2226 if(im->gdes[i].legend[0] =='\0')
2229 /* im->gdes[i].leg_y is the bottom of the legend */
2230 X0 = im->gdes[i].leg_x;
2231 Y0 = im->gdes[i].leg_y;
2232 gfx_new_text ( im->canvas, X0, Y0,
2233 im->graph_col[GRC_FONT],
2234 im->text_prop[TEXT_PROP_LEGEND].font,
2235 im->text_prop[TEXT_PROP_LEGEND].size,
2236 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
2237 im->gdes[i].legend );
2238 /* The legend for GRAPH items starts with "M " to have
2239 enough space for the box */
2240 if ( im->gdes[i].gf != GF_PRINT &&
2241 im->gdes[i].gf != GF_GPRINT &&
2242 im->gdes[i].gf != GF_COMMENT) {
2245 boxH = gfx_get_text_width(im->canvas, 0,
2246 im->text_prop[TEXT_PROP_LEGEND].font,
2247 im->text_prop[TEXT_PROP_LEGEND].size,
2248 im->tabwidth,"o", 0) * 1.2;
2251 /* make sure transparent colors show up the same way as in the graph */
2252 node = gfx_new_area(im->canvas,
2256 im->graph_col[GRC_BACK]);
2257 gfx_add_point ( node, X0+boxH, Y0-boxV );
2259 node = gfx_new_area(im->canvas,
2264 gfx_add_point ( node, X0+boxH, Y0-boxV );
2265 node = gfx_new_line(im->canvas,
2268 1.0,im->graph_col[GRC_FRAME]);
2269 gfx_add_point(node,X0+boxH,Y0);
2270 gfx_add_point(node,X0+boxH,Y0-boxV);
2271 gfx_close_path(node);
2278 /*****************************************************
2279 * lazy check make sure we rely need to create this graph
2280 *****************************************************/
2282 int lazy_check(image_desc_t *im){
2285 struct stat imgstat;
2287 if (im->lazy == 0) return 0; /* no lazy option */
2288 if (stat(im->graphfile,&imgstat) != 0)
2289 return 0; /* can't stat */
2290 /* one pixel in the existing graph is more then what we would
2292 if (time(NULL) - imgstat.st_mtime >
2293 (im->end - im->start) / im->xsize)
2295 if ((fd = fopen(im->graphfile,"rb")) == NULL)
2296 return 0; /* the file does not exist */
2297 switch (im->canvas->imgformat) {
2299 size = PngSize(fd,&(im->ximg),&(im->yimg));
2308 #ifdef WITH_PIECHART
2310 pie_part(image_desc_t *im, gfx_color_t color,
2311 double PieCenterX, double PieCenterY, double Radius,
2312 double startangle, double endangle)
2316 double step=M_PI/50; /* Number of iterations for the circle;
2317 ** 10 is definitely too low, more than
2318 ** 50 seems to be overkill
2321 /* Strange but true: we have to work clockwise or else
2322 ** anti aliasing nor transparency don't work.
2324 ** This test is here to make sure we do it right, also
2325 ** this makes the for...next loop more easy to implement.
2326 ** The return will occur if the user enters a negative number
2327 ** (which shouldn't be done according to the specs) or if the
2328 ** programmers do something wrong (which, as we all know, never
2329 ** happens anyway :)
2331 if (endangle<startangle) return;
2333 /* Hidden feature: Radius decreases each full circle */
2335 while (angle>=2*M_PI) {
2340 node=gfx_new_area(im->canvas,
2341 PieCenterX+sin(startangle)*Radius,
2342 PieCenterY-cos(startangle)*Radius,
2345 PieCenterX+sin(endangle)*Radius,
2346 PieCenterY-cos(endangle)*Radius,
2348 for (angle=endangle;angle-startangle>=step;angle-=step) {
2350 PieCenterX+sin(angle)*Radius,
2351 PieCenterY-cos(angle)*Radius );
2358 graph_size_location(image_desc_t *im, int elements
2360 #ifdef WITH_PIECHART
2366 /* The actual size of the image to draw is determined from
2367 ** several sources. The size given on the command line is
2368 ** the graph area but we need more as we have to draw labels
2369 ** and other things outside the graph area
2372 /* +-+-------------------------------------------+
2373 ** |l|.................title.....................|
2374 ** |e+--+-------------------------------+--------+
2377 ** |l| l| main graph area | chart |
2380 ** |r+--+-------------------------------+--------+
2381 ** |e| | x-axis labels | |
2382 ** |v+--+-------------------------------+--------+
2383 ** | |..............legends......................|
2384 ** +-+-------------------------------------------+
2386 ** +---------------------------------------------+
2392 #ifdef WITH_PIECHART
2397 Xlegend =0, Ylegend =0,
2399 Xspacing =15, Yspacing =15,
2403 if (im->extra_flags & ONLY_GRAPH) {
2405 im->ximg = im->xsize;
2406 im->yimg = im->ysize;
2407 im->yorigin = im->ysize;
2412 if (im->ylegend[0] != '\0' ) {
2413 Xvertical = im->text_prop[TEXT_PROP_UNIT].size *2;
2417 if (im->title[0] != '\0') {
2418 /* The title is placed "inbetween" two text lines so it
2419 ** automatically has some vertical spacing. The horizontal
2420 ** spacing is added here, on each side.
2422 /* don't care for the with of the title
2423 Xtitle = gfx_get_text_width(im->canvas, 0,
2424 im->text_prop[TEXT_PROP_TITLE].font,
2425 im->text_prop[TEXT_PROP_TITLE].size,
2427 im->title, 0) + 2*Xspacing; */
2428 Ytitle = im->text_prop[TEXT_PROP_TITLE].size*2.6+10;
2434 if (im->draw_x_grid) {
2435 Yxlabel=im->text_prop[TEXT_PROP_AXIS].size *2.5;
2437 if (im->draw_y_grid || im->forceleftspace ) {
2438 Xylabel=gfx_get_text_width(im->canvas, 0,
2439 im->text_prop[TEXT_PROP_AXIS].font,
2440 im->text_prop[TEXT_PROP_AXIS].size,
2442 "0", 0) * im->unitslength;
2446 #ifdef WITH_PIECHART
2448 im->piesize=im->xsize<im->ysize?im->xsize:im->ysize;
2454 /* Now calculate the total size. Insert some spacing where
2455 desired. im->xorigin and im->yorigin need to correspond
2456 with the lower left corner of the main graph area or, if
2457 this one is not set, the imaginary box surrounding the
2460 /* The legend width cannot yet be determined, as a result we
2461 ** have problems adjusting the image to it. For now, we just
2462 ** forget about it at all; the legend will have to fit in the
2463 ** size already allocated.
2465 im->ximg = Xylabel + Xmain + 2 * Xspacing;
2467 #ifdef WITH_PIECHART
2471 if (Xmain) im->ximg += Xspacing;
2472 #ifdef WITH_PIECHART
2473 if (Xpie) im->ximg += Xspacing;
2476 im->xorigin = Xspacing + Xylabel;
2478 /* the length of the title should not influence with width of the graph
2479 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2481 if (Xvertical) { /* unit description */
2482 im->ximg += Xvertical;
2483 im->xorigin += Xvertical;
2487 /* The vertical size is interesting... we need to compare
2488 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with
2489 ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2490 ** in order to start even thinking about Ylegend or Ywatermark.
2492 ** Do it in three portions: First calculate the inner part,
2493 ** then do the legend, then adjust the total height of the img,
2494 ** adding space for a watermark if one exists;
2497 /* reserve space for main and/or pie */
2499 im->yimg = Ymain + Yxlabel;
2501 #ifdef WITH_PIECHART
2502 if (im->yimg < Ypie) im->yimg = Ypie;
2505 im->yorigin = im->yimg - Yxlabel;
2507 /* reserve space for the title *or* some padding above the graph */
2510 im->yorigin += Ytitle;
2512 im->yimg += 1.5*Yspacing;
2513 im->yorigin += 1.5*Yspacing;
2515 /* reserve space for padding below the graph */
2516 im->yimg += Yspacing;
2518 /* Determine where to place the legends onto the image.
2519 ** Adjust im->yimg to match the space requirements.
2521 if(leg_place(im)==-1)
2524 if (im->watermark[0] != '\0') {
2525 im->yimg += Ywatermark;
2529 if (Xlegend > im->ximg) {
2531 /* reposition Pie */
2535 #ifdef WITH_PIECHART
2536 /* The pie is placed in the upper right hand corner,
2537 ** just below the title (if any) and with sufficient
2541 im->pie_x = im->ximg - Xspacing - Xpie/2;
2542 im->pie_y = im->yorigin-Ymain+Ypie/2;
2544 im->pie_x = im->ximg/2;
2545 im->pie_y = im->yorigin-Ypie/2;
2553 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
2554 /* yes we are loosing precision by doing tos with floats instead of doubles
2555 but it seems more stable this way. */
2558 /* draw that picture thing ... */
2560 graph_paint(image_desc_t *im, char ***calcpr)
2563 int lazy = lazy_check(im);
2564 #ifdef WITH_PIECHART
2566 double PieStart=0.0;
2571 double areazero = 0.0;
2572 graph_desc_t *lastgdes = NULL;
2574 /* if we are lazy and there is nothing to PRINT ... quit now */
2575 if (lazy && im->prt_c==0) return 0;
2577 /* pull the data from the rrd files ... */
2579 if(data_fetch(im)==-1)
2582 /* evaluate VDEF and CDEF operations ... */
2583 if(data_calc(im)==-1)
2586 #ifdef WITH_PIECHART
2587 /* check if we need to draw a piechart */
2588 for(i=0;i<im->gdes_c;i++){
2589 if (im->gdes[i].gf == GF_PART) {
2596 /* calculate and PRINT and GPRINT definitions. We have to do it at
2597 * this point because it will affect the length of the legends
2598 * if there are no graph elements we stop here ...
2599 * if we are lazy, try to quit ...
2601 i=print_calc(im,calcpr);
2604 #ifdef WITH_PIECHART
2607 ) || lazy) return 0;
2609 #ifdef WITH_PIECHART
2610 /* If there's only the pie chart to draw, signal this */
2611 if (i==0) piechart=2;
2614 /* get actual drawing data and find min and max values*/
2615 if(data_proc(im)==-1)
2618 if(!im->logarithmic){si_unit(im);} /* identify si magnitude Kilo, Mega Giga ? */
2620 if(!im->rigid && ! im->logarithmic)
2621 expand_range(im); /* make sure the upper and lower limit are
2624 if (!calc_horizontal_grid(im))
2631 /**************************************************************
2632 *** Calculating sizes and locations became a bit confusing ***
2633 *** so I moved this into a separate function. ***
2634 **************************************************************/
2635 if(graph_size_location(im,i
2636 #ifdef WITH_PIECHART
2642 /* the actual graph is created by going through the individual
2643 graph elements and then drawing them */
2645 node=gfx_new_area ( im->canvas,
2649 im->graph_col[GRC_BACK]);
2651 gfx_add_point(node,im->ximg, 0);
2653 #ifdef WITH_PIECHART
2654 if (piechart != 2) {
2656 node=gfx_new_area ( im->canvas,
2657 im->xorigin, im->yorigin,
2658 im->xorigin + im->xsize, im->yorigin,
2659 im->xorigin + im->xsize, im->yorigin-im->ysize,
2660 im->graph_col[GRC_CANVAS]);
2662 gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
2664 if (im->minval > 0.0)
2665 areazero = im->minval;
2666 if (im->maxval < 0.0)
2667 areazero = im->maxval;
2668 #ifdef WITH_PIECHART
2672 #ifdef WITH_PIECHART
2674 pie_part(im,im->graph_col[GRC_CANVAS],im->pie_x,im->pie_y,im->piesize*0.5,0,2*M_PI);
2678 for(i=0;i<im->gdes_c;i++){
2679 switch(im->gdes[i].gf){
2692 for (ii = 0; ii < im->xsize; ii++)
2694 if (!isnan(im->gdes[i].p_data[ii]) &&
2695 im->gdes[i].p_data[ii] != 0.0)
2697 if (im -> gdes[i].yrule > 0 ) {
2698 gfx_new_line(im->canvas,
2699 im -> xorigin + ii, im->yorigin,
2700 im -> xorigin + ii, im->yorigin - im -> gdes[i].yrule * im -> ysize,
2702 im -> gdes[i].col );
2703 } else if ( im -> gdes[i].yrule < 0 ) {
2704 gfx_new_line(im->canvas,
2705 im -> xorigin + ii, im->yorigin - im -> ysize,
2706 im -> xorigin + ii, im->yorigin - ( 1 - im -> gdes[i].yrule ) * im -> ysize,
2708 im -> gdes[i].col );
2716 /* fix data points at oo and -oo */
2717 for(ii=0;ii<im->xsize;ii++){
2718 if (isinf(im->gdes[i].p_data[ii])){
2719 if (im->gdes[i].p_data[ii] > 0) {
2720 im->gdes[i].p_data[ii] = im->maxval ;
2722 im->gdes[i].p_data[ii] = im->minval ;
2728 /* *******************************************************
2733 -------|--t-1--t--------------------------------
2735 if we know the value at time t was a then
2736 we draw a square from t-1 to t with the value a.
2738 ********************************************************* */
2739 if (im->gdes[i].col != 0x0){
2740 /* GF_LINE and friend */
2741 if(im->gdes[i].gf == GF_LINE ){
2744 for(ii=1;ii<im->xsize;ii++){
2745 if (isnan(im->gdes[i].p_data[ii]) || (im->slopemode==1 && isnan(im->gdes[i].p_data[ii-1]))){
2749 if ( node == NULL ) {
2750 last_y = ytr(im,im->gdes[i].p_data[ii]);
2751 if ( im->slopemode == 0 ){
2752 node = gfx_new_line(im->canvas,
2753 ii-1+im->xorigin,last_y,
2754 ii+im->xorigin,last_y,
2755 im->gdes[i].linewidth,
2758 node = gfx_new_line(im->canvas,
2759 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2760 ii+im->xorigin,last_y,
2761 im->gdes[i].linewidth,
2765 double new_y = ytr(im,im->gdes[i].p_data[ii]);
2766 if ( im->slopemode==0 && ! AlmostEqual2sComplement(new_y,last_y,4)){
2767 gfx_add_point(node,ii-1+im->xorigin,new_y);
2770 gfx_add_point(node,ii+im->xorigin,new_y);
2776 double *foreY=malloc(sizeof(double)*im->xsize*2);
2777 double *foreX=malloc(sizeof(double)*im->xsize*2);
2778 double *backY=malloc(sizeof(double)*im->xsize*2);
2779 double *backX=malloc(sizeof(double)*im->xsize*2);
2781 for(ii=0;ii<=im->xsize;ii++){
2783 if ( idxI > 0 && ( drawem != 0 || ii==im->xsize)){
2786 while (cntI < idxI && AlmostEqual2sComplement(foreY[lastI],foreY[cntI],4) && AlmostEqual2sComplement(foreY[lastI],foreY[cntI+1],4)){cntI++;}
2787 node = gfx_new_area(im->canvas,
2790 foreX[cntI],foreY[cntI], im->gdes[i].col);
2791 while (cntI < idxI) {
2794 while ( cntI < idxI && AlmostEqual2sComplement(foreY[lastI],foreY[cntI],4) && AlmostEqual2sComplement(foreY[lastI],foreY[cntI+1],4)){cntI++;}
2795 gfx_add_point(node,foreX[cntI],foreY[cntI]);
2797 gfx_add_point(node,backX[idxI],backY[idxI]);
2801 while ( idxI > 1 && AlmostEqual2sComplement(backY[lastI], backY[idxI],4) && AlmostEqual2sComplement(backY[lastI],backY[idxI-1],4)){idxI--;}
2802 gfx_add_point(node,backX[idxI],backY[idxI]);
2811 if (ii == im->xsize) break;
2813 /* keep things simple for now, just draw these bars
2814 do not try to build a big and complex area */
2817 if ( im->slopemode == 0 && ii==0){
2820 if ( isnan(im->gdes[i].p_data[ii]) ) {
2824 ytop = ytr(im,im->gdes[i].p_data[ii]);
2825 if ( lastgdes && im->gdes[i].stack ) {
2826 ybase = ytr(im,lastgdes->p_data[ii]);
2828 ybase = ytr(im,areazero);
2830 if ( ybase == ytop ){
2834 /* every area has to be wound clock-wise,
2835 so we have to make sur base remains base */
2837 double extra = ytop;
2841 if ( im->slopemode == 0 ){
2842 backY[++idxI] = ybase-0.2;
2843 backX[idxI] = ii+im->xorigin-1;
2844 foreY[idxI] = ytop+0.2;
2845 foreX[idxI] = ii+im->xorigin-1;
2847 backY[++idxI] = ybase-0.2;
2848 backX[idxI] = ii+im->xorigin;
2849 foreY[idxI] = ytop+0.2;
2850 foreX[idxI] = ii+im->xorigin;
2852 /* close up any remaining area */
2857 } /* else GF_LINE */
2858 } /* if color != 0x0 */
2859 /* make sure we do not run into trouble when stacking on NaN */
2860 for(ii=0;ii<im->xsize;ii++){
2861 if (isnan(im->gdes[i].p_data[ii])) {
2862 if (lastgdes && (im->gdes[i].stack)) {
2863 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
2865 im->gdes[i].p_data[ii] = areazero;
2869 lastgdes = &(im->gdes[i]);
2871 #ifdef WITH_PIECHART
2873 if(isnan(im->gdes[i].yrule)) /* fetch variable */
2874 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2876 if (finite(im->gdes[i].yrule)) { /* even the fetched var can be NaN */
2877 pie_part(im,im->gdes[i].col,
2878 im->pie_x,im->pie_y,im->piesize*0.4,
2879 M_PI*2.0*PieStart/100.0,
2880 M_PI*2.0*(PieStart+im->gdes[i].yrule)/100.0);
2881 PieStart += im->gdes[i].yrule;
2886 rrd_set_error("STACK should already be turned into LINE or AREA here");
2892 #ifdef WITH_PIECHART
2900 /* grid_paint also does the text */
2901 if( !(im->extra_flags & ONLY_GRAPH) )
2905 if( !(im->extra_flags & ONLY_GRAPH) )
2908 /* the RULES are the last thing to paint ... */
2909 for(i=0;i<im->gdes_c;i++){
2911 switch(im->gdes[i].gf){
2913 if(im->gdes[i].yrule >= im->minval
2914 && im->gdes[i].yrule <= im->maxval)
2915 gfx_new_line(im->canvas,
2916 im->xorigin,ytr(im,im->gdes[i].yrule),
2917 im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2918 1.0,im->gdes[i].col);
2921 if(im->gdes[i].xrule >= im->start
2922 && im->gdes[i].xrule <= im->end)
2923 gfx_new_line(im->canvas,
2924 xtr(im,im->gdes[i].xrule),im->yorigin,
2925 xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2926 1.0,im->gdes[i].col);
2934 if (strcmp(im->graphfile,"-")==0) {
2935 fo = im->graphhandle ? im->graphhandle : stdout;
2936 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
2937 /* Change translation mode for stdout to BINARY */
2938 _setmode( _fileno( fo ), O_BINARY );
2941 if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2942 rrd_set_error("Opening '%s' for write: %s",im->graphfile,
2943 rrd_strerror(errno));
2947 gfx_render (im->canvas,im->ximg,im->yimg,0x00000000,fo);
2948 if (strcmp(im->graphfile,"-") != 0)
2954 /*****************************************************
2956 *****************************************************/
2959 gdes_alloc(image_desc_t *im){
2962 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2963 * sizeof(graph_desc_t)))==NULL){
2964 rrd_set_error("realloc graph_descs");
2969 im->gdes[im->gdes_c-1].step=im->step;
2970 im->gdes[im->gdes_c-1].step_orig=im->step;
2971 im->gdes[im->gdes_c-1].stack=0;
2972 im->gdes[im->gdes_c-1].linewidth=0;
2973 im->gdes[im->gdes_c-1].debug=0;
2974 im->gdes[im->gdes_c-1].start=im->start;
2975 im->gdes[im->gdes_c-1].start_orig=im->start;
2976 im->gdes[im->gdes_c-1].end=im->end;
2977 im->gdes[im->gdes_c-1].end_orig=im->end;
2978 im->gdes[im->gdes_c-1].vname[0]='\0';
2979 im->gdes[im->gdes_c-1].data=NULL;
2980 im->gdes[im->gdes_c-1].ds_namv=NULL;
2981 im->gdes[im->gdes_c-1].data_first=0;
2982 im->gdes[im->gdes_c-1].p_data=NULL;
2983 im->gdes[im->gdes_c-1].rpnp=NULL;
2984 im->gdes[im->gdes_c-1].shift=0;
2985 im->gdes[im->gdes_c-1].col = 0x0;
2986 im->gdes[im->gdes_c-1].legend[0]='\0';
2987 im->gdes[im->gdes_c-1].format[0]='\0';
2988 im->gdes[im->gdes_c-1].strftm=0;
2989 im->gdes[im->gdes_c-1].rrd[0]='\0';
2990 im->gdes[im->gdes_c-1].ds=-1;
2991 im->gdes[im->gdes_c-1].cf_reduce=CF_AVERAGE;
2992 im->gdes[im->gdes_c-1].cf=CF_AVERAGE;
2993 im->gdes[im->gdes_c-1].p_data=NULL;
2994 im->gdes[im->gdes_c-1].yrule=DNAN;
2995 im->gdes[im->gdes_c-1].xrule=0;
2999 /* copies input untill the first unescaped colon is found
3000 or until input ends. backslashes have to be escaped as well */
3002 scan_for_col(const char *const input, int len, char *const output)
3007 input[inp] != ':' &&
3010 if (input[inp] == '\\' &&
3011 input[inp+1] != '\0' &&
3012 (input[inp+1] == '\\' ||
3013 input[inp+1] == ':')){
3014 output[outp++] = input[++inp];
3017 output[outp++] = input[inp];
3020 output[outp] = '\0';
3023 /* Some surgery done on this function, it became ridiculously big.
3025 ** - initializing now in rrd_graph_init()
3026 ** - options parsing now in rrd_graph_options()
3027 ** - script parsing now in rrd_graph_script()
3030 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize, FILE *stream, double *ymin, double *ymax)
3033 rrd_graph_init(&im);
3034 im.graphhandle = stream;
3036 rrd_graph_options(argc,argv,&im);
3037 if (rrd_test_error()) {
3042 if (strlen(argv[optind])>=MAXPATH) {
3043 rrd_set_error("filename (including path) too long");
3047 strncpy(im.graphfile,argv[optind],MAXPATH-1);
3048 im.graphfile[MAXPATH-1]='\0';
3050 rrd_graph_script(argc,argv,&im,1);
3051 if (rrd_test_error()) {
3056 /* Everything is now read and the actual work can start */
3059 if (graph_paint(&im,prdata)==-1){
3064 /* The image is generated and needs to be output.
3065 ** Also, if needed, print a line with information about the image.
3075 /* maybe prdata is not allocated yet ... lets do it now */
3076 if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
3077 rrd_set_error("malloc imginfo");
3081 if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
3083 rrd_set_error("malloc imginfo");
3086 filename=im.graphfile+strlen(im.graphfile);
3087 while(filename > im.graphfile) {
3088 if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
3092 sprintf((*prdata)[0],im.imginfo,filename,(long)(im.canvas->zoom*im.ximg),(long)(im.canvas->zoom*im.yimg));
3099 rrd_graph_init(image_desc_t *im)
3106 #ifdef HAVE_SETLOCALE
3107 setlocale(LC_TIME,"");
3108 #ifdef HAVE_MBSTOWCS
3109 setlocale(LC_CTYPE,"");
3115 im->xlab_user.minsec = -1;
3121 im->ylegend[0] = '\0';
3122 im->title[0] = '\0';
3123 im->watermark[0] = '\0';
3126 im->unitsexponent= 9999;
3128 im->forceleftspace = 0;
3130 im->viewfactor = 1.0;
3137 im->logarithmic = 0;
3138 im->ygridstep = DNAN;
3139 im->draw_x_grid = 1;
3140 im->draw_y_grid = 1;
3145 im->canvas = gfx_new_canvas();
3146 im->grid_dash_on = 1;
3147 im->grid_dash_off = 1;
3148 im->tabwidth = 40.0;
3150 for(i=0;i<DIM(graph_col);i++)
3151 im->graph_col[i]=graph_col[i];
3153 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3156 char rrd_win_default_font[1000];
3157 windir = getenv("windir");
3158 /* %windir% is something like D:\windows or C:\winnt */
3159 if (windir != NULL) {
3160 strncpy(rrd_win_default_font,windir,500);
3161 rrd_win_default_font[500] = '\0';
3162 strcat(rrd_win_default_font,"\\fonts\\");
3163 strcat(rrd_win_default_font,RRD_DEFAULT_FONT);
3164 for(i=0;i<DIM(text_prop);i++){
3165 strncpy(text_prop[i].font,rrd_win_default_font,sizeof(text_prop[i].font)-1);
3166 text_prop[i].font[sizeof(text_prop[i].font)-1] = '\0';
3173 deffont = getenv("RRD_DEFAULT_FONT");
3174 if (deffont != NULL) {
3175 for(i=0;i<DIM(text_prop);i++){
3176 strncpy(text_prop[i].font,deffont,sizeof(text_prop[i].font)-1);
3177 text_prop[i].font[sizeof(text_prop[i].font)-1] = '\0';
3181 for(i=0;i<DIM(text_prop);i++){
3182 im->text_prop[i].size = text_prop[i].size;
3183 strcpy(im->text_prop[i].font,text_prop[i].font);
3188 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
3191 char *parsetime_error = NULL;
3192 char scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
3193 time_t start_tmp=0,end_tmp=0;
3195 struct rrd_time_value start_tv, end_tv;
3197 optind = 0; opterr = 0; /* initialize getopt */
3199 parsetime("end-24h", &start_tv);
3200 parsetime("now", &end_tv);
3202 /* defines for long options without a short equivalent. should be bytes,
3203 and may not collide with (the ASCII value of) short options */
3204 #define LONGOPT_UNITS_SI 255
3207 static struct option long_options[] =
3209 {"start", required_argument, 0, 's'},
3210 {"end", required_argument, 0, 'e'},
3211 {"x-grid", required_argument, 0, 'x'},
3212 {"y-grid", required_argument, 0, 'y'},
3213 {"vertical-label",required_argument,0,'v'},
3214 {"width", required_argument, 0, 'w'},
3215 {"height", required_argument, 0, 'h'},
3216 {"interlaced", no_argument, 0, 'i'},
3217 {"upper-limit",required_argument, 0, 'u'},
3218 {"lower-limit",required_argument, 0, 'l'},
3219 {"rigid", no_argument, 0, 'r'},
3220 {"base", required_argument, 0, 'b'},
3221 {"logarithmic",no_argument, 0, 'o'},
3222 {"color", required_argument, 0, 'c'},
3223 {"font", required_argument, 0, 'n'},
3224 {"title", required_argument, 0, 't'},
3225 {"imginfo", required_argument, 0, 'f'},
3226 {"imgformat", required_argument, 0, 'a'},
3227 {"lazy", no_argument, 0, 'z'},
3228 {"zoom", required_argument, 0, 'm'},
3229 {"no-legend", no_argument, 0, 'g'},
3230 {"force-rules-legend",no_argument,0, 'F'},
3231 {"only-graph", no_argument, 0, 'j'},
3232 {"alt-y-grid", no_argument, 0, 'Y'},
3233 {"no-minor", no_argument, 0, 'I'},
3234 {"slope-mode", no_argument, 0, 'E'},
3235 {"alt-autoscale", no_argument, 0, 'A'},
3236 {"alt-autoscale-min", no_argument, 0, 'J'},
3237 {"alt-autoscale-max", no_argument, 0, 'M'},
3238 {"no-gridfit", no_argument, 0, 'N'},
3239 {"units-exponent",required_argument, 0, 'X'},
3240 {"units-length",required_argument, 0, 'L'},
3241 {"units", required_argument, 0, LONGOPT_UNITS_SI },
3242 {"step", required_argument, 0, 'S'},
3243 {"tabwidth", required_argument, 0, 'T'},
3244 {"font-render-mode", required_argument, 0, 'R'},
3245 {"font-smoothing-threshold", required_argument, 0, 'B'},
3246 {"watermark", required_argument, 0, 'W'},
3247 {"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 */
3249 int option_index = 0;
3251 int col_start,col_end;
3253 opt = getopt_long(argc, argv,
3254 "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:I:zgjFYAMEX:L:S:T:NR:B:W:",
3255 long_options, &option_index);
3262 im->extra_flags |= NOMINOR;
3265 im->extra_flags |= ALTYGRID;
3268 im->extra_flags |= ALTAUTOSCALE;
3271 im->extra_flags |= ALTAUTOSCALE_MIN;
3274 im->extra_flags |= ALTAUTOSCALE_MAX;
3277 im->extra_flags |= ONLY_GRAPH;
3280 im->extra_flags |= NOLEGEND;
3283 im->extra_flags |= FORCE_RULES_LEGEND;
3285 case LONGOPT_UNITS_SI:
3286 if(im->extra_flags & FORCE_UNITS) {
3287 rrd_set_error("--units can only be used once!");
3290 if(strcmp(optarg,"si")==0)
3291 im->extra_flags |= FORCE_UNITS_SI;
3293 rrd_set_error("invalid argument for --units: %s", optarg );
3298 im->unitsexponent = atoi(optarg);
3301 im->unitslength = atoi(optarg);
3302 im->forceleftspace = 1;
3305 im->tabwidth = atof(optarg);
3308 im->step = atoi(optarg);
3314 if ((parsetime_error = parsetime(optarg, &start_tv))) {
3315 rrd_set_error( "start time: %s", parsetime_error );
3320 if ((parsetime_error = parsetime(optarg, &end_tv))) {
3321 rrd_set_error( "end time: %s", parsetime_error );
3326 if(strcmp(optarg,"none") == 0){
3332 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3334 &im->xlab_user.gridst,
3336 &im->xlab_user.mgridst,
3338 &im->xlab_user.labst,
3339 &im->xlab_user.precis,
3340 &stroff) == 7 && stroff != 0){
3341 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
3342 im->xlab_form[sizeof(im->xlab_form)-1] = '\0';
3343 if((int)(im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
3344 rrd_set_error("unknown keyword %s",scan_gtm);
3346 } else if ((int)(im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
3347 rrd_set_error("unknown keyword %s",scan_mtm);
3349 } else if ((int)(im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
3350 rrd_set_error("unknown keyword %s",scan_ltm);
3353 im->xlab_user.minsec = 1;
3354 im->xlab_user.stst = im->xlab_form;
3356 rrd_set_error("invalid x-grid format");
3362 if(strcmp(optarg,"none") == 0){
3370 &im->ylabfact) == 2) {
3371 if(im->ygridstep<=0){
3372 rrd_set_error("grid step must be > 0");
3374 } else if (im->ylabfact < 1){
3375 rrd_set_error("label factor must be > 0");
3379 rrd_set_error("invalid y-grid format");
3384 strncpy(im->ylegend,optarg,150);
3385 im->ylegend[150]='\0';
3388 im->maxval = atof(optarg);
3391 im->minval = atof(optarg);
3394 im->base = atol(optarg);
3395 if(im->base != 1024 && im->base != 1000 ){
3396 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
3401 long_tmp = atol(optarg);
3402 if (long_tmp < 10) {
3403 rrd_set_error("width below 10 pixels");
3406 im->xsize = long_tmp;
3409 long_tmp = atol(optarg);
3410 if (long_tmp < 10) {
3411 rrd_set_error("height below 10 pixels");
3414 im->ysize = long_tmp;
3417 im->canvas->interlaced = 1;
3423 im->imginfo = optarg;
3426 if((int)(im->canvas->imgformat = if_conv(optarg)) == -1) {
3427 rrd_set_error("unsupported graphics format '%s'",optarg);
3439 im->logarithmic = 1;
3443 "%10[A-Z]#%n%8lx%n",
3444 col_nam,&col_start,&color,&col_end) == 2){
3446 int col_len = col_end - col_start;
3450 ((color & 0xF00) * 0x110000) |
3451 ((color & 0x0F0) * 0x011000) |
3452 ((color & 0x00F) * 0x001100) |
3458 ((color & 0xF000) * 0x11000) |
3459 ((color & 0x0F00) * 0x01100) |
3460 ((color & 0x00F0) * 0x00110) |
3461 ((color & 0x000F) * 0x00011)
3465 color = (color << 8) + 0xff /* shift left by 8 */;
3470 rrd_set_error("the color format is #RRGGBB[AA]");
3473 if((ci=grc_conv(col_nam)) != -1){
3474 im->graph_col[ci]=color;
3476 rrd_set_error("invalid color name '%s'",col_nam);
3480 rrd_set_error("invalid color def format");
3487 char font[1024] = "";
3490 "%10[A-Z]:%lf:%1000s",
3491 prop,&size,font) >= 2){
3493 if((sindex=text_prop_conv(prop)) != -1){
3494 for (propidx=sindex;propidx<TEXT_PROP_LAST;propidx++){
3496 im->text_prop[propidx].size=size;
3498 if (strlen(font) > 0){
3499 strcpy(im->text_prop[propidx].font,font);
3501 if (propidx==sindex && sindex != 0) break;
3504 rrd_set_error("invalid fonttag '%s'",prop);
3508 rrd_set_error("invalid text property format");
3514 im->canvas->zoom = atof(optarg);
3515 if (im->canvas->zoom <= 0.0) {
3516 rrd_set_error("zoom factor must be > 0");
3521 strncpy(im->title,optarg,150);
3522 im->title[150]='\0';
3526 if ( strcmp( optarg, "normal" ) == 0 )
3527 im->canvas->aa_type = AA_NORMAL;
3528 else if ( strcmp( optarg, "light" ) == 0 )
3529 im->canvas->aa_type = AA_LIGHT;
3530 else if ( strcmp( optarg, "mono" ) == 0 )
3531 im->canvas->aa_type = AA_NONE;
3534 rrd_set_error("unknown font-render-mode '%s'", optarg );
3540 im->canvas->font_aa_threshold = atof(optarg);
3544 strncpy(im->watermark,optarg,100);
3545 im->watermark[99]='\0';
3550 rrd_set_error("unknown option '%c'", optopt);
3552 rrd_set_error("unknown option '%s'",argv[optind-1]);
3557 if (optind >= argc) {
3558 rrd_set_error("missing filename");
3562 if (im->logarithmic == 1 && im->minval <= 0){
3563 rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");
3567 if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
3568 /* error string is set in parsetime.c */
3572 if (start_tmp < 3600*24*365*10){
3573 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
3577 if (end_tmp < start_tmp) {
3578 rrd_set_error("start (%ld) should be less than end (%ld)",
3579 start_tmp, end_tmp);
3583 im->start = start_tmp;
3585 im->step = max((long)im->step, (im->end-im->start)/im->xsize);
3589 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
3592 graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
3594 color=strstr(var,"#");
3597 rrd_set_error("Found no color in %s",err);
3606 rest=strstr(color,":");
3614 sscanf(color,"#%6lx%n",&col,&n);
3615 col = (col << 8) + 0xff /* shift left by 8 */;
3616 if (n!=7) rrd_set_error("Color problem in %s",err);
3619 sscanf(color,"#%8lx%n",&col,&n);
3622 rrd_set_error("Color problem in %s",err);
3624 if (rrd_test_error()) return 0;
3631 int bad_format(char *fmt) {
3635 while (*ptr != '\0')
3636 if (*ptr++ == '%') {
3638 /* line cannot end with percent char */
3639 if (*ptr == '\0') return 1;
3641 /* '%s', '%S' and '%%' are allowed */
3642 if (*ptr == 's' || *ptr == 'S' || *ptr == '%') ptr++;
3644 /* %c is allowed (but use only with vdef!) */
3645 else if (*ptr == 'c') {
3650 /* or else '% 6.2lf' and such are allowed */
3652 /* optional padding character */
3653 if (*ptr == ' ' || *ptr == '+' || *ptr == '-') ptr++;
3655 /* This should take care of 'm.n' with all three optional */
3656 while (*ptr >= '0' && *ptr <= '9') ptr++;
3657 if (*ptr == '.') ptr++;
3658 while (*ptr >= '0' && *ptr <= '9') ptr++;
3660 /* Either 'le', 'lf' or 'lg' must follow here */
3661 if (*ptr++ != 'l') return 1;
3662 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g') ptr++;
3673 vdef_parse(gdes,str)
3674 struct graph_desc_t *gdes;
3675 const char *const str;
3677 /* A VDEF currently is either "func" or "param,func"
3678 * so the parsing is rather simple. Change if needed.
3685 sscanf(str,"%le,%29[A-Z]%n",¶m,func,&n);
3686 if (n== (int)strlen(str)) { /* matched */
3690 sscanf(str,"%29[A-Z]%n",func,&n);
3691 if (n== (int)strlen(str)) { /* matched */
3694 rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3701 if (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3702 else if (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3703 else if (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3704 else if (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3705 else if (!strcmp("TOTAL", func)) gdes->vf.op = VDEF_TOTAL;
3706 else if (!strcmp("FIRST", func)) gdes->vf.op = VDEF_FIRST;
3707 else if (!strcmp("LAST", func)) gdes->vf.op = VDEF_LAST;
3708 else if (!strcmp("LSLSLOPE", func)) gdes->vf.op = VDEF_LSLSLOPE;
3709 else if (!strcmp("LSLINT", func)) gdes->vf.op = VDEF_LSLINT;
3710 else if (!strcmp("LSLCORREL",func)) gdes->vf.op = VDEF_LSLCORREL;
3712 rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3719 switch (gdes->vf.op) {
3721 if (isnan(param)) { /* no parameter given */
3722 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3728 if (param>=0.0 && param<=100.0) {
3729 gdes->vf.param = param;
3730 gdes->vf.val = DNAN; /* undefined */
3731 gdes->vf.when = 0; /* undefined */
3733 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3748 case VDEF_LSLCORREL:
3750 gdes->vf.param = DNAN;
3751 gdes->vf.val = DNAN;
3754 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3771 graph_desc_t *src,*dst;
3775 dst = &im->gdes[gdi];
3776 src = &im->gdes[dst->vidx];
3777 data = src->data + src->ds;
3778 steps = (src->end - src->start) / src->step;
3781 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3788 switch (dst->vf.op) {
3789 case VDEF_PERCENT: {
3790 rrd_value_t * array;
3794 if ((array = malloc(steps*sizeof(double)))==NULL) {
3795 rrd_set_error("malloc VDEV_PERCENT");
3798 for (step=0;step < steps; step++) {
3799 array[step]=data[step*src->ds_cnt];
3801 qsort(array,step,sizeof(double),vdef_percent_compar);
3803 field = (steps-1)*dst->vf.param/100;
3804 dst->vf.val = array[field];
3805 dst->vf.when = 0; /* no time component */
3808 for(step=0;step<steps;step++)
3809 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3815 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3816 if (step == steps) {
3820 dst->vf.val = data[step*src->ds_cnt];
3821 dst->vf.when = src->start + (step+1)*src->step;
3823 while (step != steps) {
3824 if (finite(data[step*src->ds_cnt])) {
3825 if (data[step*src->ds_cnt] > dst->vf.val) {
3826 dst->vf.val = data[step*src->ds_cnt];
3827 dst->vf.when = src->start + (step+1)*src->step;
3834 case VDEF_AVERAGE: {
3837 for (step=0;step<steps;step++) {
3838 if (finite(data[step*src->ds_cnt])) {
3839 sum += data[step*src->ds_cnt];
3844 if (dst->vf.op == VDEF_TOTAL) {
3845 dst->vf.val = sum*src->step;
3846 dst->vf.when = 0; /* no time component */
3848 dst->vf.val = sum/cnt;
3849 dst->vf.when = 0; /* no time component */
3859 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3860 if (step == steps) {
3864 dst->vf.val = data[step*src->ds_cnt];
3865 dst->vf.when = src->start + (step+1)*src->step;
3867 while (step != steps) {
3868 if (finite(data[step*src->ds_cnt])) {
3869 if (data[step*src->ds_cnt] < dst->vf.val) {
3870 dst->vf.val = data[step*src->ds_cnt];
3871 dst->vf.when = src->start + (step+1)*src->step;
3878 /* The time value returned here is one step before the
3879 * actual time value. This is the start of the first
3883 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3884 if (step == steps) { /* all entries were NaN */
3888 dst->vf.val = data[step*src->ds_cnt];
3889 dst->vf.when = src->start + step*src->step;
3893 /* The time value returned here is the
3894 * actual time value. This is the end of the last
3898 while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3899 if (step < 0) { /* all entries were NaN */
3903 dst->vf.val = data[step*src->ds_cnt];
3904 dst->vf.when = src->start + (step+1)*src->step;
3909 case VDEF_LSLCORREL:{
3910 /* Bestfit line by linear least squares method */
3913 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl ;
3914 SUMx = 0; SUMy = 0; SUMxy = 0; SUMxx = 0; SUMyy = 0;
3916 for (step=0;step<steps;step++) {
3917 if (finite(data[step*src->ds_cnt])) {
3920 SUMxx += step * step;
3921 SUMxy += step * data[step*src->ds_cnt];
3922 SUMy += data[step*src->ds_cnt];
3923 SUMyy += data[step*src->ds_cnt]*data[step*src->ds_cnt];
3927 slope = ( SUMx*SUMy - cnt*SUMxy ) / ( SUMx*SUMx - cnt*SUMxx );
3928 y_intercept = ( SUMy - slope*SUMx ) / cnt;
3929 correl = (SUMxy - (SUMx*SUMy)/cnt) / sqrt((SUMxx - (SUMx*SUMx)/cnt)*(SUMyy - (SUMy*SUMy)/cnt));
3932 if (dst->vf.op == VDEF_LSLSLOPE) {
3933 dst->vf.val = slope;
3935 } else if (dst->vf.op == VDEF_LSLINT) {
3936 dst->vf.val = y_intercept;
3938 } else if (dst->vf.op == VDEF_LSLCORREL) {
3939 dst->vf.val = correl;
3953 /* NaN < -INF < finite_values < INF */
3955 vdef_percent_compar(a,b)
3958 /* Equality is not returned; this doesn't hurt except
3959 * (maybe) for a little performance.
3962 /* First catch NaN values. They are smallest */
3963 if (isnan( *(double *)a )) return -1;
3964 if (isnan( *(double *)b )) return 1;
3966 /* NaN doesn't reach this part so INF and -INF are extremes.
3967 * The sign from isinf() is compatible with the sign we return
3969 if (isinf( *(double *)a )) return isinf( *(double *)a );
3970 if (isinf( *(double *)b )) return isinf( *(double *)b );
3972 /* If we reach this, both values must be finite */
3973 if ( *(double *)a < *(double *)b ) return -1; else return 1;