1 /****************************************************************************
2 * RRDtool 1.2.19 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 im->maxval = im->minval + new_range;
501 ytr(im,DNAN); /* reset precalc */
502 /* make sure first minor gridline is on integer pixel y coord */
503 minor_y = gridstep * floor(im->minval / gridstep);
504 while (minor_y < im->minval)
506 minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
507 minor_y_px_frac = minor_y_px - floor(minor_y_px);
508 if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
509 double yfrac = minor_y_px_frac / im->ysize;
510 double range = im->maxval - im->minval;
511 im->minval = im->minval - yfrac * range;
512 im->maxval = im->maxval - yfrac * range;
513 ytr(im,DNAN); /* reset precalc */
515 calc_horizontal_grid(im); /* recalc with changed im->maxval */
519 /* reduce data reimplementation by Alex */
523 enum cf_en cf, /* which consolidation function ?*/
524 unsigned long cur_step, /* step the data currently is in */
525 time_t *start, /* start, end and step as requested ... */
526 time_t *end, /* ... by the application will be ... */
527 unsigned long *step, /* ... adjusted to represent reality */
528 unsigned long *ds_cnt, /* number of data sources in file */
529 rrd_value_t **data) /* two dimensional array containing the data */
531 int i,reduce_factor = ceil((double)(*step) / (double)cur_step);
532 unsigned long col,dst_row,row_cnt,start_offset,end_offset,skiprows=0;
533 rrd_value_t *srcptr,*dstptr;
535 (*step) = cur_step*reduce_factor; /* set new step size for reduced data */
538 row_cnt = ((*end)-(*start))/cur_step;
544 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
545 row_cnt,reduce_factor,*start,*end,cur_step);
546 for (col=0;col<row_cnt;col++) {
547 printf("time %10lu: ",*start+(col+1)*cur_step);
548 for (i=0;i<*ds_cnt;i++)
549 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
554 /* We have to combine [reduce_factor] rows of the source
555 ** into one row for the destination. Doing this we also
556 ** need to take care to combine the correct rows. First
557 ** alter the start and end time so that they are multiples
558 ** of the new step time. We cannot reduce the amount of
559 ** time so we have to move the end towards the future and
560 ** the start towards the past.
562 end_offset = (*end) % (*step);
563 start_offset = (*start) % (*step);
565 /* If there is a start offset (which cannot be more than
566 ** one destination row), skip the appropriate number of
567 ** source rows and one destination row. The appropriate
568 ** number is what we do know (start_offset/cur_step) of
569 ** the new interval (*step/cur_step aka reduce_factor).
572 printf("start_offset: %lu end_offset: %lu\n",start_offset,end_offset);
573 printf("row_cnt before: %lu\n",row_cnt);
576 (*start) = (*start)-start_offset;
577 skiprows=reduce_factor-start_offset/cur_step;
578 srcptr+=skiprows* *ds_cnt;
579 for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
583 printf("row_cnt between: %lu\n",row_cnt);
586 /* At the end we have some rows that are not going to be
587 ** used, the amount is end_offset/cur_step
590 (*end) = (*end)-end_offset+(*step);
591 skiprows = end_offset/cur_step;
595 printf("row_cnt after: %lu\n",row_cnt);
598 /* Sanity check: row_cnt should be multiple of reduce_factor */
599 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
601 if (row_cnt%reduce_factor) {
602 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
603 row_cnt,reduce_factor);
604 printf("BUG in reduce_data()\n");
608 /* Now combine reduce_factor intervals at a time
609 ** into one interval for the destination.
612 for (dst_row=0;(long int)row_cnt>=reduce_factor;dst_row++) {
613 for (col=0;col<(*ds_cnt);col++) {
614 rrd_value_t newval=DNAN;
615 unsigned long validval=0;
617 for (i=0;i<reduce_factor;i++) {
618 if (isnan(srcptr[i*(*ds_cnt)+col])) {
622 if (isnan(newval)) newval = srcptr[i*(*ds_cnt)+col];
630 newval += srcptr[i*(*ds_cnt)+col];
633 newval = min (newval,srcptr[i*(*ds_cnt)+col]);
636 /* an interval contains a failure if any subintervals contained a failure */
638 newval = max (newval,srcptr[i*(*ds_cnt)+col]);
641 newval = srcptr[i*(*ds_cnt)+col];
646 if (validval == 0){newval = DNAN;} else{
664 srcptr+=(*ds_cnt)*reduce_factor;
665 row_cnt-=reduce_factor;
667 /* If we had to alter the endtime, we didn't have enough
668 ** source rows to fill the last row. Fill it with NaN.
670 if (end_offset) for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
672 row_cnt = ((*end)-(*start))/ *step;
674 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
675 row_cnt,*start,*end,*step);
676 for (col=0;col<row_cnt;col++) {
677 printf("time %10lu: ",*start+(col+1)*(*step));
678 for (i=0;i<*ds_cnt;i++)
679 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
686 /* get the data required for the graphs from the
690 data_fetch(image_desc_t *im )
695 /* pull the data from the rrd files ... */
696 for (i=0;i< (int)im->gdes_c;i++){
697 /* only GF_DEF elements fetch data */
698 if (im->gdes[i].gf != GF_DEF)
702 /* do we have it already ?*/
703 for (ii=0;ii<i;ii++) {
704 if (im->gdes[ii].gf != GF_DEF)
706 if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
707 && (im->gdes[i].cf == im->gdes[ii].cf)
708 && (im->gdes[i].cf_reduce == im->gdes[ii].cf_reduce)
709 && (im->gdes[i].start_orig == im->gdes[ii].start_orig)
710 && (im->gdes[i].end_orig == im->gdes[ii].end_orig)
711 && (im->gdes[i].step_orig == im->gdes[ii].step_orig)) {
712 /* OK, the data is already there.
713 ** Just copy the header portion
715 im->gdes[i].start = im->gdes[ii].start;
716 im->gdes[i].end = im->gdes[ii].end;
717 im->gdes[i].step = im->gdes[ii].step;
718 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
719 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
720 im->gdes[i].data = im->gdes[ii].data;
721 im->gdes[i].data_first = 0;
728 unsigned long ft_step = im->gdes[i].step ; /* ft_step will record what we got from fetch */
730 if((rrd_fetch_fn(im->gdes[i].rrd,
736 &im->gdes[i].ds_namv,
737 &im->gdes[i].data)) == -1){
740 im->gdes[i].data_first = 1;
742 if (ft_step < im->gdes[i].step) {
743 reduce_data(im->gdes[i].cf_reduce,
751 im->gdes[i].step = ft_step;
755 /* lets see if the required data source is really there */
756 for(ii=0;ii<(int)im->gdes[i].ds_cnt;ii++){
757 if(strcmp(im->gdes[i].ds_namv[ii],im->gdes[i].ds_nam) == 0){
760 if (im->gdes[i].ds== -1){
761 rrd_set_error("No DS called '%s' in '%s'",
762 im->gdes[i].ds_nam,im->gdes[i].rrd);
770 /* evaluate the expressions in the CDEF functions */
772 /*************************************************************
774 *************************************************************/
777 find_var_wrapper(void *arg1, char *key)
779 return find_var((image_desc_t *) arg1, key);
782 /* find gdes containing var*/
784 find_var(image_desc_t *im, char *key){
786 for(ii=0;ii<im->gdes_c-1;ii++){
787 if((im->gdes[ii].gf == GF_DEF
788 || im->gdes[ii].gf == GF_VDEF
789 || im->gdes[ii].gf == GF_CDEF)
790 && (strcmp(im->gdes[ii].vname,key) == 0)){
797 /* find the largest common denominator for all the numbers
798 in the 0 terminated num array */
803 for (i=0;num[i+1]!=0;i++){
805 rest=num[i] % num[i+1];
806 num[i]=num[i+1]; num[i+1]=rest;
810 /* return i==0?num[i]:num[i-1]; */
814 /* run the rpn calculator on all the VDEF and CDEF arguments */
816 data_calc( image_desc_t *im){
820 long *steparray, rpi;
825 rpnstack_init(&rpnstack);
827 for (gdi=0;gdi<im->gdes_c;gdi++){
828 /* Look for GF_VDEF and GF_CDEF in the same loop,
829 * so CDEFs can use VDEFs and vice versa
831 switch (im->gdes[gdi].gf) {
835 graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx];
837 /* remove current shift */
838 vdp->start -= vdp->shift;
839 vdp->end -= vdp->shift;
842 if (im->gdes[gdi].shidx >= 0)
843 vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
846 vdp->shift = im->gdes[gdi].shval;
848 /* normalize shift to multiple of consolidated step */
849 vdp->shift = (vdp->shift / (long)vdp->step) * (long)vdp->step;
852 vdp->start += vdp->shift;
853 vdp->end += vdp->shift;
857 /* A VDEF has no DS. This also signals other parts
858 * of rrdtool that this is a VDEF value, not a CDEF.
860 im->gdes[gdi].ds_cnt = 0;
861 if (vdef_calc(im,gdi)) {
862 rrd_set_error("Error processing VDEF '%s'"
865 rpnstack_free(&rpnstack);
870 im->gdes[gdi].ds_cnt = 1;
871 im->gdes[gdi].ds = 0;
872 im->gdes[gdi].data_first = 1;
873 im->gdes[gdi].start = 0;
874 im->gdes[gdi].end = 0;
879 /* Find the variables in the expression.
880 * - VDEF variables are substituted by their values
881 * and the opcode is changed into OP_NUMBER.
882 * - CDEF variables are analized for their step size,
883 * the lowest common denominator of all the step
884 * sizes of the data sources involved is calculated
885 * and the resulting number is the step size for the
886 * resulting data source.
888 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
889 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
890 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER){
891 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
892 if (im->gdes[ptr].ds_cnt == 0) { /* this is a VDEF data source */
894 printf("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
896 im->gdes[ptr].vname);
897 printf("DEBUG: value from vdef is %f\n",im->gdes[ptr].vf.val);
899 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
900 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
901 } else { /* normal variables and PREF(variables) */
903 /* add one entry to the array that keeps track of the step sizes of the
904 * data sources going into the CDEF. */
906 rrd_realloc(steparray,
907 (++stepcnt+1)*sizeof(*steparray)))==NULL){
908 rrd_set_error("realloc steparray");
909 rpnstack_free(&rpnstack);
913 steparray[stepcnt-1] = im->gdes[ptr].step;
915 /* adjust start and end of cdef (gdi) so
916 * that it runs from the latest start point
917 * to the earliest endpoint of any of the
918 * rras involved (ptr)
921 if(im->gdes[gdi].start < im->gdes[ptr].start)
922 im->gdes[gdi].start = im->gdes[ptr].start;
924 if(im->gdes[gdi].end == 0 ||
925 im->gdes[gdi].end > im->gdes[ptr].end)
926 im->gdes[gdi].end = im->gdes[ptr].end;
928 /* store pointer to the first element of
929 * the rra providing data for variable,
930 * further save step size and data source
933 im->gdes[gdi].rpnp[rpi].data = im->gdes[ptr].data + im->gdes[ptr].ds;
934 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
935 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
937 /* backoff the *.data ptr; this is done so
938 * rpncalc() function doesn't have to treat
939 * the first case differently
941 } /* if ds_cnt != 0 */
942 } /* if OP_VARIABLE */
943 } /* loop through all rpi */
945 /* move the data pointers to the correct period */
946 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
947 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
948 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER){
949 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
950 long diff = im->gdes[gdi].start - im->gdes[ptr].start;
953 im->gdes[gdi].rpnp[rpi].data += (diff / im->gdes[ptr].step) * im->gdes[ptr].ds_cnt;
957 if(steparray == NULL){
958 rrd_set_error("rpn expressions without DEF"
959 " or CDEF variables are not supported");
960 rpnstack_free(&rpnstack);
963 steparray[stepcnt]=0;
964 /* Now find the resulting step. All steps in all
965 * used RRAs have to be visited
967 im->gdes[gdi].step = lcd(steparray);
969 if((im->gdes[gdi].data = malloc((
970 (im->gdes[gdi].end-im->gdes[gdi].start)
971 / im->gdes[gdi].step)
972 * sizeof(double)))==NULL){
973 rrd_set_error("malloc im->gdes[gdi].data");
974 rpnstack_free(&rpnstack);
978 /* Step through the new cdef results array and
979 * calculate the values
981 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
982 now<=im->gdes[gdi].end;
983 now += im->gdes[gdi].step)
985 rpnp_t *rpnp = im -> gdes[gdi].rpnp;
987 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
988 * in this case we are advancing by timesteps;
989 * we use the fact that time_t is a synonym for long
991 if (rpn_calc(rpnp,&rpnstack,(long) now,
992 im->gdes[gdi].data,++dataidx) == -1) {
993 /* rpn_calc sets the error string */
994 rpnstack_free(&rpnstack);
997 } /* enumerate over time steps within a CDEF */
1002 } /* enumerate over CDEFs */
1003 rpnstack_free(&rpnstack);
1007 /* massage data so, that we get one value for each x coordinate in the graph */
1009 data_proc( image_desc_t *im ){
1011 double pixstep = (double)(im->end-im->start)
1012 /(double)im->xsize; /* how much time
1013 passes in one pixel */
1015 double minval=DNAN,maxval=DNAN;
1017 unsigned long gr_time;
1019 /* memory for the processed data */
1020 for(i=0;i<im->gdes_c;i++) {
1021 if((im->gdes[i].gf==GF_LINE) ||
1022 (im->gdes[i].gf==GF_AREA) ||
1023 (im->gdes[i].gf==GF_TICK)) {
1024 if((im->gdes[i].p_data = malloc((im->xsize +1)
1025 * sizeof(rrd_value_t)))==NULL){
1026 rrd_set_error("malloc data_proc");
1032 for (i=0;i<im->xsize;i++) { /* for each pixel */
1034 gr_time = im->start+pixstep*i; /* time of the current step */
1037 for (ii=0;ii<im->gdes_c;ii++) {
1039 switch (im->gdes[ii].gf) {
1043 if (!im->gdes[ii].stack)
1045 value = im->gdes[ii].yrule;
1046 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1047 /* The time of the data doesn't necessarily match
1048 ** the time of the graph. Beware.
1050 vidx = im->gdes[ii].vidx;
1051 if (im->gdes[vidx].gf == GF_VDEF) {
1052 value = im->gdes[vidx].vf.val;
1053 } else if (((long int)gr_time >= (long int)im->gdes[vidx].start) &&
1054 ((long int)gr_time <= (long int)im->gdes[vidx].end) ) {
1055 value = im->gdes[vidx].data[
1056 (unsigned long) floor(
1057 (double)(gr_time - im->gdes[vidx].start)
1058 / im->gdes[vidx].step)
1059 * im->gdes[vidx].ds_cnt
1067 if (! isnan(value)) {
1069 im->gdes[ii].p_data[i] = paintval;
1070 /* GF_TICK: the data values are not
1071 ** relevant for min and max
1073 if (finite(paintval) && im->gdes[ii].gf != GF_TICK ) {
1074 if ((isnan(minval) || paintval < minval ) &&
1075 ! (im->logarithmic && paintval <= 0.0))
1077 if (isnan(maxval) || paintval > maxval)
1081 im->gdes[ii].p_data[i] = DNAN;
1085 rrd_set_error("STACK should already be turned into LINE or AREA here");
1094 /* if min or max have not been asigned a value this is because
1095 there was no data in the graph ... this is not good ...
1096 lets set these to dummy values then ... */
1098 if (im->logarithmic) {
1099 if (isnan(minval)) minval = 0.2;
1100 if (isnan(maxval)) maxval = 5.1;
1103 if (isnan(minval)) minval = 0.0;
1104 if (isnan(maxval)) maxval = 1.0;
1107 /* adjust min and max values */
1108 if (isnan(im->minval)
1109 /* don't adjust low-end with log scale */ /* why not? */
1110 || ((!im->rigid) && im->minval > minval)
1112 if (im->logarithmic)
1113 im->minval = minval * 0.5;
1115 im->minval = minval;
1117 if (isnan(im->maxval)
1118 || (!im->rigid && im->maxval < maxval)
1120 if (im->logarithmic)
1121 im->maxval = maxval * 2.0;
1123 im->maxval = maxval;
1125 /* make sure min is smaller than max */
1126 if (im->minval > im->maxval) {
1127 im->minval = 0.99 * im->maxval;
1130 /* make sure min and max are not equal */
1131 if (im->minval == im->maxval) {
1133 if (! im->logarithmic) {
1136 /* make sure min and max are not both zero */
1137 if (im->maxval == 0.0) {
1146 /* identify the point where the first gridline, label ... gets placed */
1150 time_t start, /* what is the initial time */
1151 enum tmt_en baseint, /* what is the basic interval */
1152 long basestep /* how many if these do we jump a time */
1156 localtime_r(&start, &tm);
1159 tm.tm_sec -= tm.tm_sec % basestep; break;
1162 tm.tm_min -= tm.tm_min % basestep;
1167 tm.tm_hour -= tm.tm_hour % basestep; break;
1169 /* we do NOT look at the basestep for this ... */
1172 tm.tm_hour = 0; break;
1174 /* we do NOT look at the basestep for this ... */
1178 tm.tm_mday -= tm.tm_wday -1; /* -1 because we want the monday */
1179 if (tm.tm_wday==0) tm.tm_mday -= 7; /* we want the *previous* monday */
1186 tm.tm_mon -= tm.tm_mon % basestep; break;
1194 tm.tm_year -= (tm.tm_year+1900) % basestep;
1199 /* identify the point where the next gridline, label ... gets placed */
1202 time_t current, /* what is the initial time */
1203 enum tmt_en baseint, /* what is the basic interval */
1204 long basestep /* how many if these do we jump a time */
1209 localtime_r(¤t, &tm);
1213 tm.tm_sec += basestep; break;
1215 tm.tm_min += basestep; break;
1217 tm.tm_hour += basestep; break;
1219 tm.tm_mday += basestep; break;
1221 tm.tm_mday += 7*basestep; break;
1223 tm.tm_mon += basestep; break;
1225 tm.tm_year += basestep;
1227 madetime = mktime(&tm);
1228 } while (madetime == -1); /* this is necessary to skip impssible times
1229 like the daylight saving time skips */
1235 /* calculate values required for PRINT and GPRINT functions */
1238 print_calc(image_desc_t *im, char ***prdata)
1240 long i,ii,validsteps;
1243 int graphelement = 0;
1246 double magfact = -1;
1250 /* wow initializing tmvdef is quite a task :-) */
1251 time_t now = time(NULL);
1252 localtime_r(&now,&tmvdef);
1253 if (im->imginfo) prlines++;
1254 for(i=0;i<im->gdes_c;i++){
1255 vidx = im->gdes[i].vidx;
1256 switch(im->gdes[i].gf){
1259 if(((*prdata) = rrd_realloc((*prdata),prlines*sizeof(char *)))==NULL){
1260 rrd_set_error("realloc prdata");
1264 /* PRINT and GPRINT can now print VDEF generated values.
1265 * There's no need to do any calculations on them as these
1266 * calculations were already made.
1268 if (im->gdes[vidx].gf==GF_VDEF) { /* simply use vals */
1269 printval = im->gdes[vidx].vf.val;
1270 localtime_r(&im->gdes[vidx].vf.when,&tmvdef);
1271 } else { /* need to calculate max,min,avg etcetera */
1272 max_ii =((im->gdes[vidx].end
1273 - im->gdes[vidx].start)
1274 / im->gdes[vidx].step
1275 * im->gdes[vidx].ds_cnt);
1278 for( ii=im->gdes[vidx].ds;
1280 ii+=im->gdes[vidx].ds_cnt){
1281 if (! finite(im->gdes[vidx].data[ii]))
1283 if (isnan(printval)){
1284 printval = im->gdes[vidx].data[ii];
1289 switch (im->gdes[i].cf){
1292 case CF_DEVSEASONAL:
1296 printval += im->gdes[vidx].data[ii];
1299 printval = min( printval, im->gdes[vidx].data[ii]);
1303 printval = max( printval, im->gdes[vidx].data[ii]);
1306 printval = im->gdes[vidx].data[ii];
1309 if (im->gdes[i].cf==CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1310 if (validsteps > 1) {
1311 printval = (printval / validsteps);
1314 } /* prepare printval */
1316 if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) {
1317 /* Magfact is set to -1 upon entry to print_calc. If it
1318 * is still less than 0, then we need to run auto_scale.
1319 * Otherwise, put the value into the correct units. If
1320 * the value is 0, then do not set the symbol or magnification
1321 * so next the calculation will be performed again. */
1322 if (magfact < 0.0) {
1323 auto_scale(im,&printval,&si_symb,&magfact);
1324 if (printval == 0.0)
1327 printval /= magfact;
1329 *(++percent_s) = 's';
1330 } else if (strstr(im->gdes[i].format,"%s") != NULL) {
1331 auto_scale(im,&printval,&si_symb,&magfact);
1334 if (im->gdes[i].gf == GF_PRINT){
1335 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1336 (*prdata)[prlines-1] = NULL;
1337 if (im->gdes[i].strftm){
1338 strftime((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,&tmvdef);
1340 if (bad_format(im->gdes[i].format)) {
1341 rrd_set_error("bad format for PRINT in '%s'", im->gdes[i].format);
1345 #ifdef HAVE_SNPRINTF
1346 snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
1348 sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
1354 if (im->gdes[i].strftm){
1355 strftime(im->gdes[i].legend,FMT_LEG_LEN,im->gdes[i].format,&tmvdef);
1357 if (bad_format(im->gdes[i].format)) {
1358 rrd_set_error("bad format for GPRINT in '%s'", im->gdes[i].format);
1361 #ifdef HAVE_SNPRINTF
1362 snprintf(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
1364 sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
1376 if(isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1377 im->gdes[i].yrule=im->gdes[vidx].vf.val;
1382 if(im->gdes[i].xrule == 0) { /* again ... the legend printer needs it*/
1383 im->gdes[i].xrule = im->gdes[vidx].vf.when;
1391 #ifdef WITH_PIECHART
1398 rrd_set_error("STACK should already be turned into LINE or AREA here");
1403 return graphelement;
1407 /* place legends with color spots */
1409 leg_place(image_desc_t *im)
1412 int interleg = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1413 int border = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1414 int fill=0, fill_last;
1416 int leg_x = border, leg_y = im->yimg;
1417 int leg_y_prev = im->yimg;
1421 char prt_fctn; /*special printfunctions */
1424 if( !(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH) ) {
1425 if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1426 rrd_set_error("malloc for legspace");
1430 for(i=0;i<im->gdes_c;i++){
1433 /* hid legends for rules which are not displayed */
1435 if(!(im->extra_flags & FORCE_RULES_LEGEND)) {
1436 if (im->gdes[i].gf == GF_HRULE &&
1437 (im->gdes[i].yrule < im->minval || im->gdes[i].yrule > im->maxval))
1438 im->gdes[i].legend[0] = '\0';
1440 if (im->gdes[i].gf == GF_VRULE &&
1441 (im->gdes[i].xrule < im->start || im->gdes[i].xrule > im->end))
1442 im->gdes[i].legend[0] = '\0';
1445 leg_cc = strlen(im->gdes[i].legend);
1447 /* is there a controle code ant the end of the legend string ? */
1448 /* and it is not a tab \\t */
1449 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\' && im->gdes[i].legend[leg_cc-1] != 't') {
1450 prt_fctn = im->gdes[i].legend[leg_cc-1];
1452 im->gdes[i].legend[leg_cc] = '\0';
1456 /* only valid control codes */
1457 if (prt_fctn != 'l' &&
1458 prt_fctn != 'n' && /* a synonym for l */
1467 rrd_set_error("Unknown control code at the end of '%s\\%c'",im->gdes[i].legend,prt_fctn);
1472 /* remove exess space */
1473 if ( prt_fctn == 'n' ){
1477 while (prt_fctn=='g' &&
1479 im->gdes[i].legend[leg_cc-1]==' '){
1481 im->gdes[i].legend[leg_cc]='\0';
1484 legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1487 /* no interleg space if string ends in \g */
1488 fill += legspace[i];
1490 fill += gfx_get_text_width(im->canvas, fill+border,
1491 im->text_prop[TEXT_PROP_LEGEND].font,
1492 im->text_prop[TEXT_PROP_LEGEND].size,
1494 im->gdes[i].legend, 0);
1499 /* who said there was a special tag ... ?*/
1500 if (prt_fctn=='g') {
1503 if (prt_fctn == '\0') {
1504 if (i == im->gdes_c -1 ) prt_fctn ='l';
1506 /* is it time to place the legends ? */
1507 if (fill > im->ximg - 2*border){
1522 if (prt_fctn != '\0'){
1524 if (leg_c >= 2 && prt_fctn == 'j') {
1525 glue = (im->ximg - fill - 2* border) / (leg_c-1);
1529 if (prt_fctn =='c') leg_x = (im->ximg - fill) / 2.0;
1530 if (prt_fctn =='r') leg_x = im->ximg - fill - border;
1532 for(ii=mark;ii<=i;ii++){
1533 if(im->gdes[ii].legend[0]=='\0')
1534 continue; /* skip empty legends */
1535 im->gdes[ii].leg_x = leg_x;
1536 im->gdes[ii].leg_y = leg_y;
1538 gfx_get_text_width(im->canvas, leg_x,
1539 im->text_prop[TEXT_PROP_LEGEND].font,
1540 im->text_prop[TEXT_PROP_LEGEND].size,
1542 im->gdes[ii].legend, 0)
1547 /* only add y space if there was text on the line */
1548 if (leg_x > border || prt_fctn == 's')
1549 leg_y += im->text_prop[TEXT_PROP_LEGEND].size*1.8;
1550 if (prt_fctn == 's')
1551 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1557 im->yimg = leg_y_prev;
1558 /* if we did place some legends we have to add vertical space */
1559 if (leg_y != im->yimg){
1560 im->yimg += im->text_prop[TEXT_PROP_LEGEND].size*1.8;
1567 /* create a grid on the graph. it determines what to do
1568 from the values of xsize, start and end */
1570 /* the xaxis labels are determined from the number of seconds per pixel
1571 in the requested graph */
1576 calc_horizontal_grid(image_desc_t *im)
1582 int decimals, fractionals;
1584 im->ygrid_scale.labfact=2;
1585 range = im->maxval - im->minval;
1586 scaledrange = range / im->magfact;
1588 /* does the scale of this graph make it impossible to put lines
1589 on it? If so, give up. */
1590 if (isnan(scaledrange)) {
1594 /* find grid spaceing */
1596 if(isnan(im->ygridstep)){
1597 if(im->extra_flags & ALTYGRID) {
1598 /* find the value with max number of digits. Get number of digits */
1599 decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))*im->viewfactor/im->magfact));
1600 if(decimals <= 0) /* everything is small. make place for zero */
1603 im->ygrid_scale.gridstep = pow((double)10, floor(log10(range*im->viewfactor/im->magfact)))/im->viewfactor*im->magfact;
1605 if(im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1606 im->ygrid_scale.gridstep = 0.1;
1607 /* should have at least 5 lines but no more then 15 */
1608 if(range/im->ygrid_scale.gridstep < 5)
1609 im->ygrid_scale.gridstep /= 10;
1610 if(range/im->ygrid_scale.gridstep > 15)
1611 im->ygrid_scale.gridstep *= 10;
1612 if(range/im->ygrid_scale.gridstep > 5) {
1613 im->ygrid_scale.labfact = 1;
1614 if(range/im->ygrid_scale.gridstep > 8)
1615 im->ygrid_scale.labfact = 2;
1618 im->ygrid_scale.gridstep /= 5;
1619 im->ygrid_scale.labfact = 5;
1621 fractionals = floor(log10(im->ygrid_scale.gridstep*(double)im->ygrid_scale.labfact*im->viewfactor/im->magfact));
1622 if(fractionals < 0) { /* small amplitude. */
1623 int len = decimals - fractionals + 1;
1624 if (im->unitslength < len+2) im->unitslength = len+2;
1625 sprintf(im->ygrid_scale.labfmt, "%%%d.%df%s", len, -fractionals,(im->symbol != ' ' ? " %c" : ""));
1627 int len = decimals + 1;
1628 if (im->unitslength < len+2) im->unitslength = len+2;
1629 sprintf(im->ygrid_scale.labfmt, "%%%d.0f%s", len, ( im->symbol != ' ' ? " %c" : "" ));
1633 for(i=0;ylab[i].grid > 0;i++){
1634 pixel = im->ysize / (scaledrange / ylab[i].grid);
1641 if (pixel * ylab[gridind].lfac[i] >= 2.5 * im->text_prop[TEXT_PROP_AXIS].size) {
1642 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1647 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1650 im->ygrid_scale.gridstep = im->ygridstep;
1651 im->ygrid_scale.labfact = im->ylabfact;
1656 int draw_horizontal_grid(image_desc_t *im)
1660 char graph_label[100];
1662 double X0=im->xorigin;
1663 double X1=im->xorigin+im->xsize;
1665 int sgrid = (int)( im->minval / im->ygrid_scale.gridstep - 1);
1666 int egrid = (int)( im->maxval / im->ygrid_scale.gridstep + 1);
1668 scaledstep = im->ygrid_scale.gridstep/(double)im->magfact*(double)im->viewfactor;
1669 MaxY = scaledstep*(double)egrid;
1670 for (i = sgrid; i <= egrid; i++){
1671 double Y0=ytr(im,im->ygrid_scale.gridstep*i);
1672 double YN=ytr(im,im->ygrid_scale.gridstep*(i+1));
1673 if ( Y0 >= im->yorigin-im->ysize
1674 && Y0 <= im->yorigin){
1675 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
1676 with the chosen settings. Add a label if required by settings, or if
1677 there is only one label so far and the next grid line is out of bounds. */
1678 if(i % im->ygrid_scale.labfact == 0 || ( nlabels==1 && (YN < im->yorigin-im->ysize || YN > im->yorigin) )){
1679 if (im->symbol == ' ') {
1680 if(im->extra_flags & ALTYGRID) {
1681 sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*(double)i);
1684 sprintf(graph_label,"%4.1f",scaledstep*(double)i);
1686 sprintf(graph_label,"%4.0f",scaledstep*(double)i);
1690 char sisym = ( i == 0 ? ' ' : im->symbol);
1691 if(im->extra_flags & ALTYGRID) {
1692 sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*(double)i,sisym);
1695 sprintf(graph_label,"%4.1f %c",scaledstep*(double)i, sisym);
1697 sprintf(graph_label,"%4.0f %c",scaledstep*(double)i, sisym);
1703 gfx_new_text ( im->canvas,
1704 X0-im->text_prop[TEXT_PROP_AXIS].size, Y0,
1705 im->graph_col[GRC_FONT],
1706 im->text_prop[TEXT_PROP_AXIS].font,
1707 im->text_prop[TEXT_PROP_AXIS].size,
1708 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1710 gfx_new_dashed_line ( im->canvas,
1713 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1714 im->grid_dash_on, im->grid_dash_off);
1716 } else if (!(im->extra_flags & NOMINOR)) {
1717 gfx_new_dashed_line ( im->canvas,
1720 GRIDWIDTH, im->graph_col[GRC_GRID],
1721 im->grid_dash_on, im->grid_dash_off);
1729 /* this is frexp for base 10 */
1730 double frexp10(double, double *);
1731 double frexp10(double x, double *e) {
1735 iexp = floor(log(fabs(x)) / log(10));
1736 mnt = x / pow(10.0, iexp);
1739 mnt = x / pow(10.0, iexp);
1745 static int AlmostEqual2sComplement (float A, float B, int maxUlps)
1748 int aInt = *(int*)&A;
1749 int bInt = *(int*)&B;
1751 /* Make sure maxUlps is non-negative and small enough that the
1752 default NAN won't compare as equal to anything. */
1754 /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
1756 /* Make aInt lexicographically ordered as a twos-complement int */
1759 aInt = 0x80000000l - aInt;
1761 /* Make bInt lexicographically ordered as a twos-complement int */
1764 bInt = 0x80000000l - bInt;
1766 intDiff = abs(aInt - bInt);
1768 if (intDiff <= maxUlps)
1774 /* logaritmic horizontal grid */
1776 horizontal_log_grid(image_desc_t *im)
1778 double yloglab[][10] = {
1779 {1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
1780 {1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
1781 {1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0},
1782 {1.0, 2.0, 4.0, 6.0, 8.0, 10., 0.0, 0.0, 0.0, 0.0},
1783 {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.},
1784 {0,0,0,0,0, 0,0,0,0,0} /* last line */ };
1786 int i, j, val_exp, min_exp;
1787 double nex; /* number of decades in data */
1788 double logscale; /* scale in logarithmic space */
1789 int exfrac = 1; /* decade spacing */
1790 int mid = -1; /* row in yloglab for major grid */
1791 double mspac; /* smallest major grid spacing (pixels) */
1792 int flab; /* first value in yloglab to use */
1793 double value, tmp, pre_value;
1795 char graph_label[100];
1797 nex = log10(im->maxval / im->minval);
1798 logscale = im->ysize / nex;
1800 /* major spacing for data with high dynamic range */
1801 while(logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
1802 if(exfrac == 1) exfrac = 3;
1806 /* major spacing for less dynamic data */
1808 /* search best row in yloglab */
1810 for(i = 0; yloglab[mid][i + 1] < 10.0; i++);
1811 mspac = logscale * log10(10.0 / yloglab[mid][i]);
1812 } while(mspac > 2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0);
1815 /* find first value in yloglab */
1816 for(flab = 0; yloglab[mid][flab] < 10 && frexp10(im->minval, &tmp) > yloglab[mid][flab] ; flab++);
1817 if(yloglab[mid][flab] == 10.0) {
1822 if(val_exp % exfrac) val_exp += abs(-val_exp % exfrac);
1825 X1=im->xorigin+im->xsize;
1831 value = yloglab[mid][flab] * pow(10.0, val_exp);
1832 if ( AlmostEqual2sComplement(value,pre_value,4) ) break; /* it seems we are not converging */
1836 Y0 = ytr(im, value);
1837 if(Y0 <= im->yorigin - im->ysize) break;
1839 /* major grid line */
1840 gfx_new_dashed_line ( im->canvas,
1843 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1844 im->grid_dash_on, im->grid_dash_off);
1847 if (im->extra_flags & FORCE_UNITS_SI) {
1852 scale = floor(val_exp / 3.0);
1853 if( value >= 1.0 ) pvalue = pow(10.0, val_exp % 3);
1854 else pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
1855 pvalue *= yloglab[mid][flab];
1857 if ( ((scale+si_symbcenter) < (int)sizeof(si_symbol)) &&
1858 ((scale+si_symbcenter) >= 0) )
1859 symbol = si_symbol[scale+si_symbcenter];
1863 sprintf(graph_label,"%3.0f %c", pvalue, symbol);
1865 sprintf(graph_label,"%3.0e", value);
1866 gfx_new_text ( im->canvas,
1867 X0-im->text_prop[TEXT_PROP_AXIS].size, Y0,
1868 im->graph_col[GRC_FONT],
1869 im->text_prop[TEXT_PROP_AXIS].font,
1870 im->text_prop[TEXT_PROP_AXIS].size,
1871 im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1875 if(mid < 4 && exfrac == 1) {
1876 /* find first and last minor line behind current major line
1877 * i is the first line and j tha last */
1879 min_exp = val_exp - 1;
1880 for(i = 1; yloglab[mid][i] < 10.0; i++);
1881 i = yloglab[mid][i - 1] + 1;
1886 i = yloglab[mid][flab - 1] + 1;
1887 j = yloglab[mid][flab];
1890 /* draw minor lines below current major line */
1893 value = i * pow(10.0, min_exp);
1894 if(value < im->minval) continue;
1896 Y0 = ytr(im, value);
1897 if(Y0 <= im->yorigin - im->ysize) break;
1900 gfx_new_dashed_line ( im->canvas,
1903 GRIDWIDTH, im->graph_col[GRC_GRID],
1904 im->grid_dash_on, im->grid_dash_off);
1907 else if(exfrac > 1) {
1908 for(i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
1909 value = pow(10.0, i);
1910 if(value < im->minval) continue;
1912 Y0 = ytr(im, value);
1913 if(Y0 <= im->yorigin - im->ysize) break;
1916 gfx_new_dashed_line ( im->canvas,
1919 GRIDWIDTH, im->graph_col[GRC_GRID],
1920 im->grid_dash_on, im->grid_dash_off);
1925 if(yloglab[mid][++flab] == 10.0) {
1931 /* draw minor lines after highest major line */
1932 if(mid < 4 && exfrac == 1) {
1933 /* find first and last minor line below current major line
1934 * i is the first line and j tha last */
1936 min_exp = val_exp - 1;
1937 for(i = 1; yloglab[mid][i] < 10.0; i++);
1938 i = yloglab[mid][i - 1] + 1;
1943 i = yloglab[mid][flab - 1] + 1;
1944 j = yloglab[mid][flab];
1947 /* draw minor lines below current major line */
1950 value = i * pow(10.0, min_exp);
1951 if(value < im->minval) continue;
1953 Y0 = ytr(im, value);
1954 if(Y0 <= im->yorigin - im->ysize) break;
1957 gfx_new_dashed_line ( im->canvas,
1960 GRIDWIDTH, im->graph_col[GRC_GRID],
1961 im->grid_dash_on, im->grid_dash_off);
1964 /* fancy minor gridlines */
1965 else if(exfrac > 1) {
1966 for(i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
1967 value = pow(10.0, i);
1968 if(value < im->minval) continue;
1970 Y0 = ytr(im, value);
1971 if(Y0 <= im->yorigin - im->ysize) break;
1974 gfx_new_dashed_line ( im->canvas,
1977 GRIDWIDTH, im->graph_col[GRC_GRID],
1978 im->grid_dash_on, im->grid_dash_off);
1990 int xlab_sel; /* which sort of label and grid ? */
1991 time_t ti, tilab, timajor;
1993 char graph_label[100];
1994 double X0,Y0,Y1; /* points for filled graph and more*/
1997 /* the type of time grid is determined by finding
1998 the number of seconds per pixel in the graph */
2001 if(im->xlab_user.minsec == -1){
2002 factor=(im->end - im->start)/im->xsize;
2004 while ( xlab[xlab_sel+1].minsec != -1
2005 && xlab[xlab_sel+1].minsec <= factor) { xlab_sel++; } /* pick the last one */
2006 while ( xlab[xlab_sel-1].minsec == xlab[xlab_sel].minsec
2007 && xlab[xlab_sel].length > (im->end - im->start)) { xlab_sel--; } /* go back to the smallest size */
2008 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2009 im->xlab_user.gridst = xlab[xlab_sel].gridst;
2010 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2011 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2012 im->xlab_user.labtm = xlab[xlab_sel].labtm;
2013 im->xlab_user.labst = xlab[xlab_sel].labst;
2014 im->xlab_user.precis = xlab[xlab_sel].precis;
2015 im->xlab_user.stst = xlab[xlab_sel].stst;
2018 /* y coords are the same for every line ... */
2020 Y1 = im->yorigin-im->ysize;
2023 /* paint the minor grid */
2024 if (!(im->extra_flags & NOMINOR))
2026 for(ti = find_first_time(im->start,
2027 im->xlab_user.gridtm,
2028 im->xlab_user.gridst),
2029 timajor = find_first_time(im->start,
2030 im->xlab_user.mgridtm,
2031 im->xlab_user.mgridst);
2033 ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
2035 /* are we inside the graph ? */
2036 if (ti < im->start || ti > im->end) continue;
2037 while (timajor < ti) {
2038 timajor = find_next_time(timajor,
2039 im->xlab_user.mgridtm, im->xlab_user.mgridst);
2041 if (ti == timajor) continue; /* skip as falls on major grid line */
2043 gfx_new_dashed_line(im->canvas,X0,Y0+1, X0,Y1-1,GRIDWIDTH,
2044 im->graph_col[GRC_GRID],
2045 im->grid_dash_on, im->grid_dash_off);
2050 /* paint the major grid */
2051 for(ti = find_first_time(im->start,
2052 im->xlab_user.mgridtm,
2053 im->xlab_user.mgridst);
2055 ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
2057 /* are we inside the graph ? */
2058 if (ti < im->start || ti > im->end) continue;
2060 gfx_new_dashed_line(im->canvas,X0,Y0+3, X0,Y1-2,MGRIDWIDTH,
2061 im->graph_col[GRC_MGRID],
2062 im->grid_dash_on, im->grid_dash_off);
2065 /* paint the labels below the graph */
2066 for(ti = find_first_time(im->start - im->xlab_user.precis/2,
2067 im->xlab_user.labtm,
2068 im->xlab_user.labst);
2069 ti <= im->end - im->xlab_user.precis/2;
2070 ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
2072 tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
2073 /* are we inside the graph ? */
2074 if (tilab < im->start || tilab > im->end) continue;
2077 localtime_r(&tilab, &tm);
2078 strftime(graph_label,99,im->xlab_user.stst, &tm);
2080 # error "your libc has no strftime I guess we'll abort the exercise here."
2082 gfx_new_text ( im->canvas,
2083 xtr(im,tilab), Y0+im->text_prop[TEXT_PROP_AXIS].size*1.4+5,
2084 im->graph_col[GRC_FONT],
2085 im->text_prop[TEXT_PROP_AXIS].font,
2086 im->text_prop[TEXT_PROP_AXIS].size,
2087 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_BOTTOM,
2100 /* draw x and y axis */
2101 /* gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2102 im->xorigin+im->xsize,im->yorigin-im->ysize,
2103 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2105 gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2106 im->xorigin+im->xsize,im->yorigin-im->ysize,
2107 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2109 gfx_new_line ( im->canvas, im->xorigin-4,im->yorigin,
2110 im->xorigin+im->xsize+4,im->yorigin,
2111 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2113 gfx_new_line ( im->canvas, im->xorigin,im->yorigin+4,
2114 im->xorigin,im->yorigin-im->ysize-4,
2115 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2118 /* arrow for X and Y axis direction */
2119 gfx_new_area ( im->canvas,
2120 im->xorigin+im->xsize+2, im->yorigin-2,
2121 im->xorigin+im->xsize+2, im->yorigin+3,
2122 im->xorigin+im->xsize+7, im->yorigin+0.5, /* LINEOFFSET */
2123 im->graph_col[GRC_ARROW]);
2125 gfx_new_area ( im->canvas,
2126 im->xorigin-2, im->yorigin-im->ysize-2,
2127 im->xorigin+3, im->yorigin-im->ysize-2,
2128 im->xorigin+0.5, im->yorigin-im->ysize-7, /* LINEOFFSET */
2129 im->graph_col[GRC_ARROW]);
2134 grid_paint(image_desc_t *im)
2138 double X0,Y0; /* points for filled graph and more*/
2141 /* draw 3d border */
2142 node = gfx_new_area (im->canvas, 0,im->yimg,
2144 2,2,im->graph_col[GRC_SHADEA]);
2145 gfx_add_point( node , im->ximg - 2, 2 );
2146 gfx_add_point( node , im->ximg, 0 );
2147 gfx_add_point( node , 0,0 );
2148 /* gfx_add_point( node , 0,im->yimg ); */
2150 node = gfx_new_area (im->canvas, 2,im->yimg-2,
2151 im->ximg-2,im->yimg-2,
2153 im->graph_col[GRC_SHADEB]);
2154 gfx_add_point( node , im->ximg,0);
2155 gfx_add_point( node , im->ximg,im->yimg);
2156 gfx_add_point( node , 0,im->yimg);
2157 /* gfx_add_point( node , 0,im->yimg ); */
2160 if (im->draw_x_grid == 1 )
2163 if (im->draw_y_grid == 1){
2164 if(im->logarithmic){
2165 res = horizontal_log_grid(im);
2167 res = draw_horizontal_grid(im);
2170 /* dont draw horizontal grid if there is no min and max val */
2172 char *nodata = "No Data found";
2173 gfx_new_text(im->canvas,im->ximg/2, (2*im->yorigin-im->ysize) / 2,
2174 im->graph_col[GRC_FONT],
2175 im->text_prop[TEXT_PROP_AXIS].font,
2176 im->text_prop[TEXT_PROP_AXIS].size,
2177 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
2182 /* yaxis unit description */
2183 gfx_new_text( im->canvas,
2184 10, (im->yorigin - im->ysize/2),
2185 im->graph_col[GRC_FONT],
2186 im->text_prop[TEXT_PROP_UNIT].font,
2187 im->text_prop[TEXT_PROP_UNIT].size, im->tabwidth,
2188 RRDGRAPH_YLEGEND_ANGLE,
2189 GFX_H_LEFT, GFX_V_CENTER,
2193 gfx_new_text( im->canvas,
2194 im->ximg/2, im->text_prop[TEXT_PROP_TITLE].size*1.3+4,
2195 im->graph_col[GRC_FONT],
2196 im->text_prop[TEXT_PROP_TITLE].font,
2197 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
2198 GFX_H_CENTER, GFX_V_CENTER,
2200 /* rrdtool 'logo' */
2201 gfx_new_text( im->canvas,
2203 ( im->graph_col[GRC_FONT] & 0xffffff00 ) | 0x00000044,
2204 im->text_prop[TEXT_PROP_AXIS].font,
2205 5.5, im->tabwidth, 270,
2206 GFX_H_RIGHT, GFX_V_TOP,
2207 "RRDTOOL / TOBI OETIKER");
2209 /* graph watermark */
2210 if(im->watermark[0] != '\0') {
2211 gfx_new_text( im->canvas,
2212 im->ximg/2, im->yimg-6,
2213 ( im->graph_col[GRC_FONT] & 0xffffff00 ) | 0x00000044,
2214 im->text_prop[TEXT_PROP_AXIS].font,
2215 5.5, im->tabwidth, 0,
2216 GFX_H_CENTER, GFX_V_BOTTOM,
2221 if( !(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH) ) {
2222 for(i=0;i<im->gdes_c;i++){
2223 if(im->gdes[i].legend[0] =='\0')
2226 /* im->gdes[i].leg_y is the bottom of the legend */
2227 X0 = im->gdes[i].leg_x;
2228 Y0 = im->gdes[i].leg_y;
2229 gfx_new_text ( im->canvas, X0, Y0,
2230 im->graph_col[GRC_FONT],
2231 im->text_prop[TEXT_PROP_LEGEND].font,
2232 im->text_prop[TEXT_PROP_LEGEND].size,
2233 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
2234 im->gdes[i].legend );
2235 /* The legend for GRAPH items starts with "M " to have
2236 enough space for the box */
2237 if ( im->gdes[i].gf != GF_PRINT &&
2238 im->gdes[i].gf != GF_GPRINT &&
2239 im->gdes[i].gf != GF_COMMENT) {
2242 boxH = gfx_get_text_width(im->canvas, 0,
2243 im->text_prop[TEXT_PROP_LEGEND].font,
2244 im->text_prop[TEXT_PROP_LEGEND].size,
2245 im->tabwidth,"o", 0) * 1.2;
2248 /* make sure transparent colors show up the same way as in the graph */
2249 node = gfx_new_area(im->canvas,
2253 im->graph_col[GRC_BACK]);
2254 gfx_add_point ( node, X0+boxH, Y0-boxV );
2256 node = gfx_new_area(im->canvas,
2261 gfx_add_point ( node, X0+boxH, Y0-boxV );
2262 node = gfx_new_line(im->canvas,
2265 1.0,im->graph_col[GRC_FRAME]);
2266 gfx_add_point(node,X0+boxH,Y0);
2267 gfx_add_point(node,X0+boxH,Y0-boxV);
2268 gfx_close_path(node);
2275 /*****************************************************
2276 * lazy check make sure we rely need to create this graph
2277 *****************************************************/
2279 int lazy_check(image_desc_t *im){
2282 struct stat imgstat;
2284 if (im->lazy == 0) return 0; /* no lazy option */
2285 if (stat(im->graphfile,&imgstat) != 0)
2286 return 0; /* can't stat */
2287 /* one pixel in the existing graph is more then what we would
2289 if (time(NULL) - imgstat.st_mtime >
2290 (im->end - im->start) / im->xsize)
2292 if ((fd = fopen(im->graphfile,"rb")) == NULL)
2293 return 0; /* the file does not exist */
2294 switch (im->canvas->imgformat) {
2296 size = PngSize(fd,&(im->ximg),&(im->yimg));
2305 #ifdef WITH_PIECHART
2307 pie_part(image_desc_t *im, gfx_color_t color,
2308 double PieCenterX, double PieCenterY, double Radius,
2309 double startangle, double endangle)
2313 double step=M_PI/50; /* Number of iterations for the circle;
2314 ** 10 is definitely too low, more than
2315 ** 50 seems to be overkill
2318 /* Strange but true: we have to work clockwise or else
2319 ** anti aliasing nor transparency don't work.
2321 ** This test is here to make sure we do it right, also
2322 ** this makes the for...next loop more easy to implement.
2323 ** The return will occur if the user enters a negative number
2324 ** (which shouldn't be done according to the specs) or if the
2325 ** programmers do something wrong (which, as we all know, never
2326 ** happens anyway :)
2328 if (endangle<startangle) return;
2330 /* Hidden feature: Radius decreases each full circle */
2332 while (angle>=2*M_PI) {
2337 node=gfx_new_area(im->canvas,
2338 PieCenterX+sin(startangle)*Radius,
2339 PieCenterY-cos(startangle)*Radius,
2342 PieCenterX+sin(endangle)*Radius,
2343 PieCenterY-cos(endangle)*Radius,
2345 for (angle=endangle;angle-startangle>=step;angle-=step) {
2347 PieCenterX+sin(angle)*Radius,
2348 PieCenterY-cos(angle)*Radius );
2355 graph_size_location(image_desc_t *im, int elements
2357 #ifdef WITH_PIECHART
2363 /* The actual size of the image to draw is determined from
2364 ** several sources. The size given on the command line is
2365 ** the graph area but we need more as we have to draw labels
2366 ** and other things outside the graph area
2369 /* +-+-------------------------------------------+
2370 ** |l|.................title.....................|
2371 ** |e+--+-------------------------------+--------+
2374 ** |l| l| main graph area | chart |
2377 ** |r+--+-------------------------------+--------+
2378 ** |e| | x-axis labels | |
2379 ** |v+--+-------------------------------+--------+
2380 ** | |..............legends......................|
2381 ** +-+-------------------------------------------+
2383 ** +---------------------------------------------+
2389 #ifdef WITH_PIECHART
2394 Xlegend =0, Ylegend =0,
2396 Xspacing =15, Yspacing =15,
2400 if (im->extra_flags & ONLY_GRAPH) {
2402 im->ximg = im->xsize;
2403 im->yimg = im->ysize;
2404 im->yorigin = im->ysize;
2409 if (im->ylegend[0] != '\0' ) {
2410 Xvertical = im->text_prop[TEXT_PROP_UNIT].size *2;
2414 if (im->title[0] != '\0') {
2415 /* The title is placed "inbetween" two text lines so it
2416 ** automatically has some vertical spacing. The horizontal
2417 ** spacing is added here, on each side.
2419 /* don't care for the with of the title
2420 Xtitle = gfx_get_text_width(im->canvas, 0,
2421 im->text_prop[TEXT_PROP_TITLE].font,
2422 im->text_prop[TEXT_PROP_TITLE].size,
2424 im->title, 0) + 2*Xspacing; */
2425 Ytitle = im->text_prop[TEXT_PROP_TITLE].size*2.6+10;
2431 if (im->draw_x_grid) {
2432 Yxlabel=im->text_prop[TEXT_PROP_AXIS].size *2.5;
2434 if (im->draw_y_grid || im->forceleftspace ) {
2435 Xylabel=gfx_get_text_width(im->canvas, 0,
2436 im->text_prop[TEXT_PROP_AXIS].font,
2437 im->text_prop[TEXT_PROP_AXIS].size,
2439 "0", 0) * im->unitslength;
2443 #ifdef WITH_PIECHART
2445 im->piesize=im->xsize<im->ysize?im->xsize:im->ysize;
2451 /* Now calculate the total size. Insert some spacing where
2452 desired. im->xorigin and im->yorigin need to correspond
2453 with the lower left corner of the main graph area or, if
2454 this one is not set, the imaginary box surrounding the
2457 /* The legend width cannot yet be determined, as a result we
2458 ** have problems adjusting the image to it. For now, we just
2459 ** forget about it at all; the legend will have to fit in the
2460 ** size already allocated.
2462 im->ximg = Xylabel + Xmain + 2 * Xspacing;
2464 #ifdef WITH_PIECHART
2468 if (Xmain) im->ximg += Xspacing;
2469 #ifdef WITH_PIECHART
2470 if (Xpie) im->ximg += Xspacing;
2473 im->xorigin = Xspacing + Xylabel;
2475 /* the length of the title should not influence with width of the graph
2476 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2478 if (Xvertical) { /* unit description */
2479 im->ximg += Xvertical;
2480 im->xorigin += Xvertical;
2484 /* The vertical size is interesting... we need to compare
2485 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with
2486 ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2487 ** in order to start even thinking about Ylegend or Ywatermark.
2489 ** Do it in three portions: First calculate the inner part,
2490 ** then do the legend, then adjust the total height of the img,
2491 ** adding space for a watermark if one exists;
2494 /* reserve space for main and/or pie */
2496 im->yimg = Ymain + Yxlabel;
2498 #ifdef WITH_PIECHART
2499 if (im->yimg < Ypie) im->yimg = Ypie;
2502 im->yorigin = im->yimg - Yxlabel;
2504 /* reserve space for the title *or* some padding above the graph */
2507 im->yorigin += Ytitle;
2509 im->yimg += 1.5*Yspacing;
2510 im->yorigin += 1.5*Yspacing;
2512 /* reserve space for padding below the graph */
2513 im->yimg += Yspacing;
2515 /* Determine where to place the legends onto the image.
2516 ** Adjust im->yimg to match the space requirements.
2518 if(leg_place(im)==-1)
2521 if (im->watermark[0] != '\0') {
2522 im->yimg += Ywatermark;
2526 if (Xlegend > im->ximg) {
2528 /* reposition Pie */
2532 #ifdef WITH_PIECHART
2533 /* The pie is placed in the upper right hand corner,
2534 ** just below the title (if any) and with sufficient
2538 im->pie_x = im->ximg - Xspacing - Xpie/2;
2539 im->pie_y = im->yorigin-Ymain+Ypie/2;
2541 im->pie_x = im->ximg/2;
2542 im->pie_y = im->yorigin-Ypie/2;
2550 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
2551 /* yes we are loosing precision by doing tos with floats instead of doubles
2552 but it seems more stable this way. */
2555 /* draw that picture thing ... */
2557 graph_paint(image_desc_t *im, char ***calcpr)
2560 int lazy = lazy_check(im);
2561 #ifdef WITH_PIECHART
2563 double PieStart=0.0;
2568 double areazero = 0.0;
2569 graph_desc_t *lastgdes = NULL;
2571 /* if we are lazy and there is nothing to PRINT ... quit now */
2572 if (lazy && im->prt_c==0) return 0;
2574 /* pull the data from the rrd files ... */
2576 if(data_fetch(im)==-1)
2579 /* evaluate VDEF and CDEF operations ... */
2580 if(data_calc(im)==-1)
2583 #ifdef WITH_PIECHART
2584 /* check if we need to draw a piechart */
2585 for(i=0;i<im->gdes_c;i++){
2586 if (im->gdes[i].gf == GF_PART) {
2593 /* calculate and PRINT and GPRINT definitions. We have to do it at
2594 * this point because it will affect the length of the legends
2595 * if there are no graph elements we stop here ...
2596 * if we are lazy, try to quit ...
2598 i=print_calc(im,calcpr);
2601 #ifdef WITH_PIECHART
2604 ) || lazy) return 0;
2606 #ifdef WITH_PIECHART
2607 /* If there's only the pie chart to draw, signal this */
2608 if (i==0) piechart=2;
2611 /* get actual drawing data and find min and max values*/
2612 if(data_proc(im)==-1)
2615 if(!im->logarithmic){si_unit(im);} /* identify si magnitude Kilo, Mega Giga ? */
2617 if(!im->rigid && ! im->logarithmic)
2618 expand_range(im); /* make sure the upper and lower limit are
2621 if (!calc_horizontal_grid(im))
2628 /**************************************************************
2629 *** Calculating sizes and locations became a bit confusing ***
2630 *** so I moved this into a separate function. ***
2631 **************************************************************/
2632 if(graph_size_location(im,i
2633 #ifdef WITH_PIECHART
2639 /* the actual graph is created by going through the individual
2640 graph elements and then drawing them */
2642 node=gfx_new_area ( im->canvas,
2646 im->graph_col[GRC_BACK]);
2648 gfx_add_point(node,im->ximg, 0);
2650 #ifdef WITH_PIECHART
2651 if (piechart != 2) {
2653 node=gfx_new_area ( im->canvas,
2654 im->xorigin, im->yorigin,
2655 im->xorigin + im->xsize, im->yorigin,
2656 im->xorigin + im->xsize, im->yorigin-im->ysize,
2657 im->graph_col[GRC_CANVAS]);
2659 gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
2661 if (im->minval > 0.0)
2662 areazero = im->minval;
2663 if (im->maxval < 0.0)
2664 areazero = im->maxval;
2665 #ifdef WITH_PIECHART
2669 #ifdef WITH_PIECHART
2671 pie_part(im,im->graph_col[GRC_CANVAS],im->pie_x,im->pie_y,im->piesize*0.5,0,2*M_PI);
2675 for(i=0;i<im->gdes_c;i++){
2676 switch(im->gdes[i].gf){
2689 for (ii = 0; ii < im->xsize; ii++)
2691 if (!isnan(im->gdes[i].p_data[ii]) &&
2692 im->gdes[i].p_data[ii] != 0.0)
2694 if (im -> gdes[i].yrule > 0 ) {
2695 gfx_new_line(im->canvas,
2696 im -> xorigin + ii, im->yorigin,
2697 im -> xorigin + ii, im->yorigin - im -> gdes[i].yrule * im -> ysize,
2699 im -> gdes[i].col );
2700 } else if ( im -> gdes[i].yrule < 0 ) {
2701 gfx_new_line(im->canvas,
2702 im -> xorigin + ii, im->yorigin - im -> ysize,
2703 im -> xorigin + ii, im->yorigin - ( 1 - im -> gdes[i].yrule ) * im -> ysize,
2705 im -> gdes[i].col );
2713 /* fix data points at oo and -oo */
2714 for(ii=0;ii<im->xsize;ii++){
2715 if (isinf(im->gdes[i].p_data[ii])){
2716 if (im->gdes[i].p_data[ii] > 0) {
2717 im->gdes[i].p_data[ii] = im->maxval ;
2719 im->gdes[i].p_data[ii] = im->minval ;
2725 /* *******************************************************
2730 -------|--t-1--t--------------------------------
2732 if we know the value at time t was a then
2733 we draw a square from t-1 to t with the value a.
2735 ********************************************************* */
2736 if (im->gdes[i].col != 0x0){
2737 /* GF_LINE and friend */
2738 if(im->gdes[i].gf == GF_LINE ){
2741 for(ii=1;ii<im->xsize;ii++){
2742 if (isnan(im->gdes[i].p_data[ii]) || (im->slopemode==1 && isnan(im->gdes[i].p_data[ii-1]))){
2746 if ( node == NULL ) {
2747 last_y = ytr(im,im->gdes[i].p_data[ii]);
2748 if ( im->slopemode == 0 ){
2749 node = gfx_new_line(im->canvas,
2750 ii-1+im->xorigin,last_y,
2751 ii+im->xorigin,last_y,
2752 im->gdes[i].linewidth,
2755 node = gfx_new_line(im->canvas,
2756 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2757 ii+im->xorigin,last_y,
2758 im->gdes[i].linewidth,
2762 double new_y = ytr(im,im->gdes[i].p_data[ii]);
2763 if ( im->slopemode==0 && ! AlmostEqual2sComplement(new_y,last_y,4)){
2764 gfx_add_point(node,ii-1+im->xorigin,new_y);
2767 gfx_add_point(node,ii+im->xorigin,new_y);
2773 double *foreY=malloc(sizeof(double)*im->xsize*2);
2774 double *foreX=malloc(sizeof(double)*im->xsize*2);
2775 double *backY=malloc(sizeof(double)*im->xsize*2);
2776 double *backX=malloc(sizeof(double)*im->xsize*2);
2778 for(ii=0;ii<=im->xsize;ii++){
2780 if ( idxI > 0 && ( drawem != 0 || ii==im->xsize)){
2783 while (cntI < idxI && AlmostEqual2sComplement(foreY[lastI],foreY[cntI],4) && AlmostEqual2sComplement(foreY[lastI],foreY[cntI+1],4)){cntI++;}
2784 node = gfx_new_area(im->canvas,
2787 foreX[cntI],foreY[cntI], im->gdes[i].col);
2788 while (cntI < idxI) {
2791 while ( cntI < idxI && AlmostEqual2sComplement(foreY[lastI],foreY[cntI],4) && AlmostEqual2sComplement(foreY[lastI],foreY[cntI+1],4)){cntI++;}
2792 gfx_add_point(node,foreX[cntI],foreY[cntI]);
2794 gfx_add_point(node,backX[idxI],backY[idxI]);
2798 while ( idxI > 1 && AlmostEqual2sComplement(backY[lastI], backY[idxI],4) && AlmostEqual2sComplement(backY[lastI],backY[idxI-1],4)){idxI--;}
2799 gfx_add_point(node,backX[idxI],backY[idxI]);
2808 if (ii == im->xsize) break;
2810 /* keep things simple for now, just draw these bars
2811 do not try to build a big and complex area */
2814 if ( im->slopemode == 0 && ii==0){
2817 if ( isnan(im->gdes[i].p_data[ii]) ) {
2821 ytop = ytr(im,im->gdes[i].p_data[ii]);
2822 if ( lastgdes && im->gdes[i].stack ) {
2823 ybase = ytr(im,lastgdes->p_data[ii]);
2825 ybase = ytr(im,areazero);
2827 if ( ybase == ytop ){
2831 /* every area has to be wound clock-wise,
2832 so we have to make sur base remains base */
2834 double extra = ytop;
2838 if ( im->slopemode == 0 ){
2839 backY[++idxI] = ybase-0.2;
2840 backX[idxI] = ii+im->xorigin-1;
2841 foreY[idxI] = ytop+0.2;
2842 foreX[idxI] = ii+im->xorigin-1;
2844 backY[++idxI] = ybase-0.2;
2845 backX[idxI] = ii+im->xorigin;
2846 foreY[idxI] = ytop+0.2;
2847 foreX[idxI] = ii+im->xorigin;
2849 /* close up any remaining area */
2854 } /* else GF_LINE */
2855 } /* if color != 0x0 */
2856 /* make sure we do not run into trouble when stacking on NaN */
2857 for(ii=0;ii<im->xsize;ii++){
2858 if (isnan(im->gdes[i].p_data[ii])) {
2859 if (lastgdes && (im->gdes[i].stack)) {
2860 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
2862 im->gdes[i].p_data[ii] = areazero;
2866 lastgdes = &(im->gdes[i]);
2868 #ifdef WITH_PIECHART
2870 if(isnan(im->gdes[i].yrule)) /* fetch variable */
2871 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2873 if (finite(im->gdes[i].yrule)) { /* even the fetched var can be NaN */
2874 pie_part(im,im->gdes[i].col,
2875 im->pie_x,im->pie_y,im->piesize*0.4,
2876 M_PI*2.0*PieStart/100.0,
2877 M_PI*2.0*(PieStart+im->gdes[i].yrule)/100.0);
2878 PieStart += im->gdes[i].yrule;
2883 rrd_set_error("STACK should already be turned into LINE or AREA here");
2889 #ifdef WITH_PIECHART
2897 /* grid_paint also does the text */
2898 if( !(im->extra_flags & ONLY_GRAPH) )
2902 if( !(im->extra_flags & ONLY_GRAPH) )
2905 /* the RULES are the last thing to paint ... */
2906 for(i=0;i<im->gdes_c;i++){
2908 switch(im->gdes[i].gf){
2910 if(im->gdes[i].yrule >= im->minval
2911 && im->gdes[i].yrule <= im->maxval)
2912 gfx_new_line(im->canvas,
2913 im->xorigin,ytr(im,im->gdes[i].yrule),
2914 im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2915 1.0,im->gdes[i].col);
2918 if(im->gdes[i].xrule >= im->start
2919 && im->gdes[i].xrule <= im->end)
2920 gfx_new_line(im->canvas,
2921 xtr(im,im->gdes[i].xrule),im->yorigin,
2922 xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2923 1.0,im->gdes[i].col);
2931 if (strcmp(im->graphfile,"-")==0) {
2932 fo = im->graphhandle ? im->graphhandle : stdout;
2933 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
2934 /* Change translation mode for stdout to BINARY */
2935 _setmode( _fileno( fo ), O_BINARY );
2938 if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2939 rrd_set_error("Opening '%s' for write: %s",im->graphfile,
2940 rrd_strerror(errno));
2944 gfx_render (im->canvas,im->ximg,im->yimg,0x00000000,fo);
2945 if (strcmp(im->graphfile,"-") != 0)
2951 /*****************************************************
2953 *****************************************************/
2956 gdes_alloc(image_desc_t *im){
2959 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2960 * sizeof(graph_desc_t)))==NULL){
2961 rrd_set_error("realloc graph_descs");
2966 im->gdes[im->gdes_c-1].step=im->step;
2967 im->gdes[im->gdes_c-1].step_orig=im->step;
2968 im->gdes[im->gdes_c-1].stack=0;
2969 im->gdes[im->gdes_c-1].linewidth=0;
2970 im->gdes[im->gdes_c-1].debug=0;
2971 im->gdes[im->gdes_c-1].start=im->start;
2972 im->gdes[im->gdes_c-1].start_orig=im->start;
2973 im->gdes[im->gdes_c-1].end=im->end;
2974 im->gdes[im->gdes_c-1].end_orig=im->end;
2975 im->gdes[im->gdes_c-1].vname[0]='\0';
2976 im->gdes[im->gdes_c-1].data=NULL;
2977 im->gdes[im->gdes_c-1].ds_namv=NULL;
2978 im->gdes[im->gdes_c-1].data_first=0;
2979 im->gdes[im->gdes_c-1].p_data=NULL;
2980 im->gdes[im->gdes_c-1].rpnp=NULL;
2981 im->gdes[im->gdes_c-1].shift=0;
2982 im->gdes[im->gdes_c-1].col = 0x0;
2983 im->gdes[im->gdes_c-1].legend[0]='\0';
2984 im->gdes[im->gdes_c-1].format[0]='\0';
2985 im->gdes[im->gdes_c-1].strftm=0;
2986 im->gdes[im->gdes_c-1].rrd[0]='\0';
2987 im->gdes[im->gdes_c-1].ds=-1;
2988 im->gdes[im->gdes_c-1].cf_reduce=CF_AVERAGE;
2989 im->gdes[im->gdes_c-1].cf=CF_AVERAGE;
2990 im->gdes[im->gdes_c-1].p_data=NULL;
2991 im->gdes[im->gdes_c-1].yrule=DNAN;
2992 im->gdes[im->gdes_c-1].xrule=0;
2996 /* copies input untill the first unescaped colon is found
2997 or until input ends. backslashes have to be escaped as well */
2999 scan_for_col(const char *const input, int len, char *const output)
3004 input[inp] != ':' &&
3007 if (input[inp] == '\\' &&
3008 input[inp+1] != '\0' &&
3009 (input[inp+1] == '\\' ||
3010 input[inp+1] == ':')){
3011 output[outp++] = input[++inp];
3014 output[outp++] = input[inp];
3017 output[outp] = '\0';
3020 /* Some surgery done on this function, it became ridiculously big.
3022 ** - initializing now in rrd_graph_init()
3023 ** - options parsing now in rrd_graph_options()
3024 ** - script parsing now in rrd_graph_script()
3027 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize, FILE *stream, double *ymin, double *ymax)
3030 rrd_graph_init(&im);
3031 im.graphhandle = stream;
3033 rrd_graph_options(argc,argv,&im);
3034 if (rrd_test_error()) {
3039 if (strlen(argv[optind])>=MAXPATH) {
3040 rrd_set_error("filename (including path) too long");
3044 strncpy(im.graphfile,argv[optind],MAXPATH-1);
3045 im.graphfile[MAXPATH-1]='\0';
3047 rrd_graph_script(argc,argv,&im,1);
3048 if (rrd_test_error()) {
3053 /* Everything is now read and the actual work can start */
3056 if (graph_paint(&im,prdata)==-1){
3061 /* The image is generated and needs to be output.
3062 ** Also, if needed, print a line with information about the image.
3072 /* maybe prdata is not allocated yet ... lets do it now */
3073 if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
3074 rrd_set_error("malloc imginfo");
3078 if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
3080 rrd_set_error("malloc imginfo");
3083 filename=im.graphfile+strlen(im.graphfile);
3084 while(filename > im.graphfile) {
3085 if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
3089 sprintf((*prdata)[0],im.imginfo,filename,(long)(im.canvas->zoom*im.ximg),(long)(im.canvas->zoom*im.yimg));
3096 rrd_graph_init(image_desc_t *im)
3103 #ifdef HAVE_SETLOCALE
3104 setlocale(LC_TIME,"");
3105 #ifdef HAVE_MBSTOWCS
3106 setlocale(LC_CTYPE,"");
3112 im->xlab_user.minsec = -1;
3118 im->ylegend[0] = '\0';
3119 im->title[0] = '\0';
3120 im->watermark[0] = '\0';
3123 im->unitsexponent= 9999;
3125 im->forceleftspace = 0;
3127 im->viewfactor = 1.0;
3134 im->logarithmic = 0;
3135 im->ygridstep = DNAN;
3136 im->draw_x_grid = 1;
3137 im->draw_y_grid = 1;
3142 im->canvas = gfx_new_canvas();
3143 im->grid_dash_on = 1;
3144 im->grid_dash_off = 1;
3145 im->tabwidth = 40.0;
3147 for(i=0;i<DIM(graph_col);i++)
3148 im->graph_col[i]=graph_col[i];
3150 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3153 char rrd_win_default_font[1000];
3154 windir = getenv("windir");
3155 /* %windir% is something like D:\windows or C:\winnt */
3156 if (windir != NULL) {
3157 strncpy(rrd_win_default_font,windir,500);
3158 rrd_win_default_font[500] = '\0';
3159 strcat(rrd_win_default_font,"\\fonts\\");
3160 strcat(rrd_win_default_font,RRD_DEFAULT_FONT);
3161 for(i=0;i<DIM(text_prop);i++){
3162 strncpy(text_prop[i].font,rrd_win_default_font,sizeof(text_prop[i].font)-1);
3163 text_prop[i].font[sizeof(text_prop[i].font)-1] = '\0';
3170 deffont = getenv("RRD_DEFAULT_FONT");
3171 if (deffont != NULL) {
3172 for(i=0;i<DIM(text_prop);i++){
3173 strncpy(text_prop[i].font,deffont,sizeof(text_prop[i].font)-1);
3174 text_prop[i].font[sizeof(text_prop[i].font)-1] = '\0';
3178 for(i=0;i<DIM(text_prop);i++){
3179 im->text_prop[i].size = text_prop[i].size;
3180 strcpy(im->text_prop[i].font,text_prop[i].font);
3185 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
3188 char *parsetime_error = NULL;
3189 char scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
3190 time_t start_tmp=0,end_tmp=0;
3192 struct rrd_time_value start_tv, end_tv;
3194 optind = 0; opterr = 0; /* initialize getopt */
3196 parsetime("end-24h", &start_tv);
3197 parsetime("now", &end_tv);
3199 /* defines for long options without a short equivalent. should be bytes,
3200 and may not collide with (the ASCII value of) short options */
3201 #define LONGOPT_UNITS_SI 255
3204 static struct option long_options[] =
3206 {"start", required_argument, 0, 's'},
3207 {"end", required_argument, 0, 'e'},
3208 {"x-grid", required_argument, 0, 'x'},
3209 {"y-grid", required_argument, 0, 'y'},
3210 {"vertical-label",required_argument,0,'v'},
3211 {"width", required_argument, 0, 'w'},
3212 {"height", required_argument, 0, 'h'},
3213 {"interlaced", no_argument, 0, 'i'},
3214 {"upper-limit",required_argument, 0, 'u'},
3215 {"lower-limit",required_argument, 0, 'l'},
3216 {"rigid", no_argument, 0, 'r'},
3217 {"base", required_argument, 0, 'b'},
3218 {"logarithmic",no_argument, 0, 'o'},
3219 {"color", required_argument, 0, 'c'},
3220 {"font", required_argument, 0, 'n'},
3221 {"title", required_argument, 0, 't'},
3222 {"imginfo", required_argument, 0, 'f'},
3223 {"imgformat", required_argument, 0, 'a'},
3224 {"lazy", no_argument, 0, 'z'},
3225 {"zoom", required_argument, 0, 'm'},
3226 {"no-legend", no_argument, 0, 'g'},
3227 {"force-rules-legend",no_argument,0, 'F'},
3228 {"only-graph", no_argument, 0, 'j'},
3229 {"alt-y-grid", no_argument, 0, 'Y'},
3230 {"no-minor", no_argument, 0, 'I'},
3231 {"slope-mode", no_argument, 0, 'E'},
3232 {"alt-autoscale", no_argument, 0, 'A'},
3233 {"alt-autoscale-min", no_argument, 0, 'J'},
3234 {"alt-autoscale-max", no_argument, 0, 'M'},
3235 {"no-gridfit", no_argument, 0, 'N'},
3236 {"units-exponent",required_argument, 0, 'X'},
3237 {"units-length",required_argument, 0, 'L'},
3238 {"units", required_argument, 0, LONGOPT_UNITS_SI },
3239 {"step", required_argument, 0, 'S'},
3240 {"tabwidth", required_argument, 0, 'T'},
3241 {"font-render-mode", required_argument, 0, 'R'},
3242 {"font-smoothing-threshold", required_argument, 0, 'B'},
3243 {"watermark", required_argument, 0, 'W'},
3244 {"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 */
3246 int option_index = 0;
3248 int col_start,col_end;
3250 opt = getopt_long(argc, argv,
3251 "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:I:zgjFYAMEX:L:S:T:NR:B:W:",
3252 long_options, &option_index);
3259 im->extra_flags |= NOMINOR;
3262 im->extra_flags |= ALTYGRID;
3265 im->extra_flags |= ALTAUTOSCALE;
3268 im->extra_flags |= ALTAUTOSCALE_MIN;
3271 im->extra_flags |= ALTAUTOSCALE_MAX;
3274 im->extra_flags |= ONLY_GRAPH;
3277 im->extra_flags |= NOLEGEND;
3280 im->extra_flags |= FORCE_RULES_LEGEND;
3282 case LONGOPT_UNITS_SI:
3283 if(im->extra_flags & FORCE_UNITS) {
3284 rrd_set_error("--units can only be used once!");
3287 if(strcmp(optarg,"si")==0)
3288 im->extra_flags |= FORCE_UNITS_SI;
3290 rrd_set_error("invalid argument for --units: %s", optarg );
3295 im->unitsexponent = atoi(optarg);
3298 im->unitslength = atoi(optarg);
3299 im->forceleftspace = 1;
3302 im->tabwidth = atof(optarg);
3305 im->step = atoi(optarg);
3311 if ((parsetime_error = parsetime(optarg, &start_tv))) {
3312 rrd_set_error( "start time: %s", parsetime_error );
3317 if ((parsetime_error = parsetime(optarg, &end_tv))) {
3318 rrd_set_error( "end time: %s", parsetime_error );
3323 if(strcmp(optarg,"none") == 0){
3329 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3331 &im->xlab_user.gridst,
3333 &im->xlab_user.mgridst,
3335 &im->xlab_user.labst,
3336 &im->xlab_user.precis,
3337 &stroff) == 7 && stroff != 0){
3338 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
3339 im->xlab_form[sizeof(im->xlab_form)-1] = '\0';
3340 if((int)(im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
3341 rrd_set_error("unknown keyword %s",scan_gtm);
3343 } else if ((int)(im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
3344 rrd_set_error("unknown keyword %s",scan_mtm);
3346 } else if ((int)(im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
3347 rrd_set_error("unknown keyword %s",scan_ltm);
3350 im->xlab_user.minsec = 1;
3351 im->xlab_user.stst = im->xlab_form;
3353 rrd_set_error("invalid x-grid format");
3359 if(strcmp(optarg,"none") == 0){
3367 &im->ylabfact) == 2) {
3368 if(im->ygridstep<=0){
3369 rrd_set_error("grid step must be > 0");
3371 } else if (im->ylabfact < 1){
3372 rrd_set_error("label factor must be > 0");
3376 rrd_set_error("invalid y-grid format");
3381 strncpy(im->ylegend,optarg,150);
3382 im->ylegend[150]='\0';
3385 im->maxval = atof(optarg);
3388 im->minval = atof(optarg);
3391 im->base = atol(optarg);
3392 if(im->base != 1024 && im->base != 1000 ){
3393 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
3398 long_tmp = atol(optarg);
3399 if (long_tmp < 10) {
3400 rrd_set_error("width below 10 pixels");
3403 im->xsize = long_tmp;
3406 long_tmp = atol(optarg);
3407 if (long_tmp < 10) {
3408 rrd_set_error("height below 10 pixels");
3411 im->ysize = long_tmp;
3414 im->canvas->interlaced = 1;
3420 im->imginfo = optarg;
3423 if((int)(im->canvas->imgformat = if_conv(optarg)) == -1) {
3424 rrd_set_error("unsupported graphics format '%s'",optarg);
3436 im->logarithmic = 1;
3440 "%10[A-Z]#%n%8lx%n",
3441 col_nam,&col_start,&color,&col_end) == 2){
3443 int col_len = col_end - col_start;
3447 ((color & 0xF00) * 0x110000) |
3448 ((color & 0x0F0) * 0x011000) |
3449 ((color & 0x00F) * 0x001100) |
3455 ((color & 0xF000) * 0x11000) |
3456 ((color & 0x0F00) * 0x01100) |
3457 ((color & 0x00F0) * 0x00110) |
3458 ((color & 0x000F) * 0x00011)
3462 color = (color << 8) + 0xff /* shift left by 8 */;
3467 rrd_set_error("the color format is #RRGGBB[AA]");
3470 if((ci=grc_conv(col_nam)) != -1){
3471 im->graph_col[ci]=color;
3473 rrd_set_error("invalid color name '%s'",col_nam);
3477 rrd_set_error("invalid color def format");
3484 char font[1024] = "";
3487 "%10[A-Z]:%lf:%1000s",
3488 prop,&size,font) >= 2){
3490 if((sindex=text_prop_conv(prop)) != -1){
3491 for (propidx=sindex;propidx<TEXT_PROP_LAST;propidx++){
3493 im->text_prop[propidx].size=size;
3495 if (strlen(font) > 0){
3496 strcpy(im->text_prop[propidx].font,font);
3498 if (propidx==sindex && sindex != 0) break;
3501 rrd_set_error("invalid fonttag '%s'",prop);
3505 rrd_set_error("invalid text property format");
3511 im->canvas->zoom = atof(optarg);
3512 if (im->canvas->zoom <= 0.0) {
3513 rrd_set_error("zoom factor must be > 0");
3518 strncpy(im->title,optarg,150);
3519 im->title[150]='\0';
3523 if ( strcmp( optarg, "normal" ) == 0 )
3524 im->canvas->aa_type = AA_NORMAL;
3525 else if ( strcmp( optarg, "light" ) == 0 )
3526 im->canvas->aa_type = AA_LIGHT;
3527 else if ( strcmp( optarg, "mono" ) == 0 )
3528 im->canvas->aa_type = AA_NONE;
3531 rrd_set_error("unknown font-render-mode '%s'", optarg );
3537 im->canvas->font_aa_threshold = atof(optarg);
3541 strncpy(im->watermark,optarg,100);
3542 im->watermark[99]='\0';
3547 rrd_set_error("unknown option '%c'", optopt);
3549 rrd_set_error("unknown option '%s'",argv[optind-1]);
3554 if (optind >= argc) {
3555 rrd_set_error("missing filename");
3559 if (im->logarithmic == 1 && im->minval <= 0){
3560 rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");
3564 if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
3565 /* error string is set in parsetime.c */
3569 if (start_tmp < 3600*24*365*10){
3570 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
3574 if (end_tmp < start_tmp) {
3575 rrd_set_error("start (%ld) should be less than end (%ld)",
3576 start_tmp, end_tmp);
3580 im->start = start_tmp;
3582 im->step = max((long)im->step, (im->end-im->start)/im->xsize);
3586 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
3589 graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
3591 color=strstr(var,"#");
3594 rrd_set_error("Found no color in %s",err);
3603 rest=strstr(color,":");
3611 sscanf(color,"#%6lx%n",&col,&n);
3612 col = (col << 8) + 0xff /* shift left by 8 */;
3613 if (n!=7) rrd_set_error("Color problem in %s",err);
3616 sscanf(color,"#%8lx%n",&col,&n);
3619 rrd_set_error("Color problem in %s",err);
3621 if (rrd_test_error()) return 0;
3628 int bad_format(char *fmt) {
3632 while (*ptr != '\0')
3633 if (*ptr++ == '%') {
3635 /* line cannot end with percent char */
3636 if (*ptr == '\0') return 1;
3638 /* '%s', '%S' and '%%' are allowed */
3639 if (*ptr == 's' || *ptr == 'S' || *ptr == '%') ptr++;
3641 /* %c is allowed (but use only with vdef!) */
3642 else if (*ptr == 'c') {
3647 /* or else '% 6.2lf' and such are allowed */
3649 /* optional padding character */
3650 if (*ptr == ' ' || *ptr == '+' || *ptr == '-') ptr++;
3652 /* This should take care of 'm.n' with all three optional */
3653 while (*ptr >= '0' && *ptr <= '9') ptr++;
3654 if (*ptr == '.') ptr++;
3655 while (*ptr >= '0' && *ptr <= '9') ptr++;
3657 /* Either 'le', 'lf' or 'lg' must follow here */
3658 if (*ptr++ != 'l') return 1;
3659 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g') ptr++;
3670 vdef_parse(gdes,str)
3671 struct graph_desc_t *gdes;
3672 const char *const str;
3674 /* A VDEF currently is either "func" or "param,func"
3675 * so the parsing is rather simple. Change if needed.
3682 sscanf(str,"%le,%29[A-Z]%n",¶m,func,&n);
3683 if (n== (int)strlen(str)) { /* matched */
3687 sscanf(str,"%29[A-Z]%n",func,&n);
3688 if (n== (int)strlen(str)) { /* matched */
3691 rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3698 if (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3699 else if (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3700 else if (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3701 else if (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3702 else if (!strcmp("TOTAL", func)) gdes->vf.op = VDEF_TOTAL;
3703 else if (!strcmp("FIRST", func)) gdes->vf.op = VDEF_FIRST;
3704 else if (!strcmp("LAST", func)) gdes->vf.op = VDEF_LAST;
3705 else if (!strcmp("LSLSLOPE", func)) gdes->vf.op = VDEF_LSLSLOPE;
3706 else if (!strcmp("LSLINT", func)) gdes->vf.op = VDEF_LSLINT;
3707 else if (!strcmp("LSLCORREL",func)) gdes->vf.op = VDEF_LSLCORREL;
3709 rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3716 switch (gdes->vf.op) {
3718 if (isnan(param)) { /* no parameter given */
3719 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3725 if (param>=0.0 && param<=100.0) {
3726 gdes->vf.param = param;
3727 gdes->vf.val = DNAN; /* undefined */
3728 gdes->vf.when = 0; /* undefined */
3730 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3745 case VDEF_LSLCORREL:
3747 gdes->vf.param = DNAN;
3748 gdes->vf.val = DNAN;
3751 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3768 graph_desc_t *src,*dst;
3772 dst = &im->gdes[gdi];
3773 src = &im->gdes[dst->vidx];
3774 data = src->data + src->ds;
3775 steps = (src->end - src->start) / src->step;
3778 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3785 switch (dst->vf.op) {
3786 case VDEF_PERCENT: {
3787 rrd_value_t * array;
3791 if ((array = malloc(steps*sizeof(double)))==NULL) {
3792 rrd_set_error("malloc VDEV_PERCENT");
3795 for (step=0;step < steps; step++) {
3796 array[step]=data[step*src->ds_cnt];
3798 qsort(array,step,sizeof(double),vdef_percent_compar);
3800 field = (steps-1)*dst->vf.param/100;
3801 dst->vf.val = array[field];
3802 dst->vf.when = 0; /* no time component */
3805 for(step=0;step<steps;step++)
3806 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3812 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3813 if (step == steps) {
3817 dst->vf.val = data[step*src->ds_cnt];
3818 dst->vf.when = src->start + (step+1)*src->step;
3820 while (step != steps) {
3821 if (finite(data[step*src->ds_cnt])) {
3822 if (data[step*src->ds_cnt] > dst->vf.val) {
3823 dst->vf.val = data[step*src->ds_cnt];
3824 dst->vf.when = src->start + (step+1)*src->step;
3831 case VDEF_AVERAGE: {
3834 for (step=0;step<steps;step++) {
3835 if (finite(data[step*src->ds_cnt])) {
3836 sum += data[step*src->ds_cnt];
3841 if (dst->vf.op == VDEF_TOTAL) {
3842 dst->vf.val = sum*src->step;
3843 dst->vf.when = 0; /* no time component */
3845 dst->vf.val = sum/cnt;
3846 dst->vf.when = 0; /* no time component */
3856 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3857 if (step == steps) {
3861 dst->vf.val = data[step*src->ds_cnt];
3862 dst->vf.when = src->start + (step+1)*src->step;
3864 while (step != steps) {
3865 if (finite(data[step*src->ds_cnt])) {
3866 if (data[step*src->ds_cnt] < dst->vf.val) {
3867 dst->vf.val = data[step*src->ds_cnt];
3868 dst->vf.when = src->start + (step+1)*src->step;
3875 /* The time value returned here is one step before the
3876 * actual time value. This is the start of the first
3880 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3881 if (step == steps) { /* all entries were NaN */
3885 dst->vf.val = data[step*src->ds_cnt];
3886 dst->vf.when = src->start + step*src->step;
3890 /* The time value returned here is the
3891 * actual time value. This is the end of the last
3895 while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3896 if (step < 0) { /* all entries were NaN */
3900 dst->vf.val = data[step*src->ds_cnt];
3901 dst->vf.when = src->start + (step+1)*src->step;
3906 case VDEF_LSLCORREL:{
3907 /* Bestfit line by linear least squares method */
3910 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl ;
3911 SUMx = 0; SUMy = 0; SUMxy = 0; SUMxx = 0; SUMyy = 0;
3913 for (step=0;step<steps;step++) {
3914 if (finite(data[step*src->ds_cnt])) {
3917 SUMxx += step * step;
3918 SUMxy += step * data[step*src->ds_cnt];
3919 SUMy += data[step*src->ds_cnt];
3920 SUMyy += data[step*src->ds_cnt]*data[step*src->ds_cnt];
3924 slope = ( SUMx*SUMy - cnt*SUMxy ) / ( SUMx*SUMx - cnt*SUMxx );
3925 y_intercept = ( SUMy - slope*SUMx ) / cnt;
3926 correl = (SUMxy - (SUMx*SUMy)/cnt) / sqrt((SUMxx - (SUMx*SUMx)/cnt)*(SUMyy - (SUMy*SUMy)/cnt));
3929 if (dst->vf.op == VDEF_LSLSLOPE) {
3930 dst->vf.val = slope;
3932 } else if (dst->vf.op == VDEF_LSLINT) {
3933 dst->vf.val = y_intercept;
3935 } else if (dst->vf.op == VDEF_LSLCORREL) {
3936 dst->vf.val = correl;
3950 /* NaN < -INF < finite_values < INF */
3952 vdef_percent_compar(a,b)
3955 /* Equality is not returned; this doesn't hurt except
3956 * (maybe) for a little performance.
3959 /* First catch NaN values. They are smallest */
3960 if (isnan( *(double *)a )) return -1;
3961 if (isnan( *(double *)b )) return 1;
3963 /* NaN doesn't reach this part so INF and -INF are extremes.
3964 * The sign from isinf() is compatible with the sign we return
3966 if (isinf( *(double *)a )) return isinf( *(double *)a );
3967 if (isinf( *(double *)b )) return isinf( *(double *)b );
3969 /* If we reach this, both values must be finite */
3970 if ( *(double *)a < *(double *)b ) return -1; else return 1;