1 /****************************************************************************
2 * RRDtool 1.2.13 Copyright by Tobi Oetiker, 1997-2006
3 ****************************************************************************
4 * rrd__graph.c produce graphs from data in rrdfiles
5 ****************************************************************************/
12 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
25 #include "rrd_graph.h"
27 /* some constant definitions */
31 #ifndef RRD_DEFAULT_FONT
32 /* there is special code later to pick Cour.ttf when running on windows */
33 #define RRD_DEFAULT_FONT "DejaVuSansMono-Roman.ttf"
36 text_prop_t text_prop[] = {
37 { 8.0, RRD_DEFAULT_FONT }, /* default */
38 { 9.0, RRD_DEFAULT_FONT }, /* title */
39 { 7.0, RRD_DEFAULT_FONT }, /* axis */
40 { 8.0, RRD_DEFAULT_FONT }, /* unit */
41 { 8.0, RRD_DEFAULT_FONT } /* legend */
45 {0, 0, TMT_SECOND,30, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
46 {2, 0, TMT_MINUTE,1, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
47 {5, 0, TMT_MINUTE,2, TMT_MINUTE,10, TMT_MINUTE,10, 0,"%H:%M"},
48 {10, 0, TMT_MINUTE,5, TMT_MINUTE,20, TMT_MINUTE,20, 0,"%H:%M"},
49 {30, 0, TMT_MINUTE,10, TMT_HOUR,1, TMT_HOUR,1, 0,"%H:%M"},
50 {60, 0, TMT_MINUTE,30, TMT_HOUR,2, TMT_HOUR,2, 0,"%H:%M"},
51 {60, 24*3600, TMT_MINUTE,30, TMT_HOUR,2, TMT_HOUR,4, 0,"%a %H:%M"},
52 {180, 0, TMT_HOUR,1, TMT_HOUR,6, TMT_HOUR,6, 0,"%H:%M"},
53 {180, 24*3600, TMT_HOUR,1, TMT_HOUR,6, TMT_HOUR,12, 0,"%a %H:%M"},
54 /*{300, 0, TMT_HOUR,3, TMT_HOUR,12, TMT_HOUR,12, 12*3600,"%a %p"}, this looks silly*/
55 {600, 0, TMT_HOUR,6, TMT_DAY,1, TMT_DAY,1, 24*3600,"%a"},
56 {1200, 0, TMT_HOUR,6, TMT_DAY,1, TMT_DAY,1, 24*3600,"%d"},
57 {1800, 0, TMT_HOUR,12, TMT_DAY,1, TMT_DAY,2, 24*3600,"%a %d"},
58 {2400, 0, TMT_HOUR,12, TMT_DAY,1, TMT_DAY,2, 24*3600,"%a"},
59 {3600, 0, TMT_DAY,1, TMT_WEEK,1, TMT_WEEK,1, 7*24*3600,"Week %V"},
60 {3*3600, 0, TMT_WEEK,1, TMT_MONTH,1, TMT_WEEK,2, 7*24*3600,"Week %V"},
61 {6*3600, 0, TMT_MONTH,1, TMT_MONTH,1, TMT_MONTH,1, 30*24*3600,"%b"},
62 {48*3600, 0, TMT_MONTH,1, TMT_MONTH,3, TMT_MONTH,3, 30*24*3600,"%b"},
63 {10*24*3600, 0, TMT_YEAR,1, TMT_YEAR,1, TMT_YEAR,1, 365*24*3600,"%y"},
64 {-1,0,TMT_MONTH,0,TMT_MONTH,0,TMT_MONTH,0,0,""}
67 /* sensible y label intervals ...*/
85 gfx_color_t graph_col[] = /* default colors */
86 { 0xFFFFFFFF, /* canvas */
87 0xF0F0F0FF, /* background */
88 0xD0D0D0FF, /* shade A */
89 0xA0A0A0FF, /* shade B */
90 0x90909080, /* grid */
91 0xE0505080, /* major grid */
92 0x000000FF, /* font */
93 0x802020FF, /* arrow */
94 0x202020FF, /* axis */
95 0x000000FF /* frame */
102 # define DPRINT(x) (void)(printf x, printf("\n"))
108 /* initialize with xtr(im,0); */
110 xtr(image_desc_t *im,time_t mytime){
113 pixie = (double) im->xsize / (double)(im->end - im->start);
116 return (int)((double)im->xorigin
117 + pixie * ( mytime - im->start ) );
120 /* translate data values into y coordinates */
122 ytr(image_desc_t *im, double value){
127 pixie = (double) im->ysize / (im->maxval - im->minval);
129 pixie = (double) im->ysize / (log10(im->maxval) - log10(im->minval));
131 } else if(!im->logarithmic) {
132 yval = im->yorigin - pixie * (value - im->minval);
134 if (value < im->minval) {
137 yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
140 /* make sure we don't return anything too unreasonable. GD lib can
141 get terribly slow when drawing lines outside its scope. This is
142 especially problematic in connection with the rigid option */
144 /* keep yval as-is */
145 } else if (yval > im->yorigin) {
146 yval = im->yorigin +0.00001;
147 } else if (yval < im->yorigin - im->ysize){
148 yval = im->yorigin - im->ysize - 0.00001;
155 /* conversion function for symbolic entry names */
158 #define conv_if(VV,VVV) \
159 if (strcmp(#VV, string) == 0) return VVV ;
161 enum gf_en gf_conv(char *string){
163 conv_if(PRINT,GF_PRINT)
164 conv_if(GPRINT,GF_GPRINT)
165 conv_if(COMMENT,GF_COMMENT)
166 conv_if(HRULE,GF_HRULE)
167 conv_if(VRULE,GF_VRULE)
168 conv_if(LINE,GF_LINE)
169 conv_if(AREA,GF_AREA)
170 conv_if(STACK,GF_STACK)
171 conv_if(TICK,GF_TICK)
173 conv_if(CDEF,GF_CDEF)
174 conv_if(VDEF,GF_VDEF)
176 conv_if(PART,GF_PART)
178 conv_if(XPORT,GF_XPORT)
179 conv_if(SHIFT,GF_SHIFT)
184 enum gfx_if_en if_conv(char *string){
194 enum tmt_en tmt_conv(char *string){
196 conv_if(SECOND,TMT_SECOND)
197 conv_if(MINUTE,TMT_MINUTE)
198 conv_if(HOUR,TMT_HOUR)
200 conv_if(WEEK,TMT_WEEK)
201 conv_if(MONTH,TMT_MONTH)
202 conv_if(YEAR,TMT_YEAR)
206 enum grc_en grc_conv(char *string){
208 conv_if(BACK,GRC_BACK)
209 conv_if(CANVAS,GRC_CANVAS)
210 conv_if(SHADEA,GRC_SHADEA)
211 conv_if(SHADEB,GRC_SHADEB)
212 conv_if(GRID,GRC_GRID)
213 conv_if(MGRID,GRC_MGRID)
214 conv_if(FONT,GRC_FONT)
215 conv_if(ARROW,GRC_ARROW)
216 conv_if(AXIS,GRC_AXIS)
217 conv_if(FRAME,GRC_FRAME)
222 enum text_prop_en text_prop_conv(char *string){
224 conv_if(DEFAULT,TEXT_PROP_DEFAULT)
225 conv_if(TITLE,TEXT_PROP_TITLE)
226 conv_if(AXIS,TEXT_PROP_AXIS)
227 conv_if(UNIT,TEXT_PROP_UNIT)
228 conv_if(LEGEND,TEXT_PROP_LEGEND)
236 im_free(image_desc_t *im)
240 if (im == NULL) return 0;
241 for(i=0;i<(unsigned)im->gdes_c;i++){
242 if (im->gdes[i].data_first){
243 /* careful here, because a single pointer can occur several times */
244 free (im->gdes[i].data);
245 if (im->gdes[i].ds_namv){
246 for (ii=0;ii<im->gdes[i].ds_cnt;ii++)
247 free(im->gdes[i].ds_namv[ii]);
248 free(im->gdes[i].ds_namv);
251 free (im->gdes[i].p_data);
252 free (im->gdes[i].rpnp);
255 gfx_destroy(im->canvas);
259 /* find SI magnitude symbol for the given number*/
262 image_desc_t *im, /* image description */
269 char *symbol[] = {"a", /* 10e-18 Atto */
270 "f", /* 10e-15 Femto */
271 "p", /* 10e-12 Pico */
272 "n", /* 10e-9 Nano */
273 "u", /* 10e-6 Micro */
274 "m", /* 10e-3 Milli */
279 "T", /* 10e12 Tera */
280 "P", /* 10e15 Peta */
286 if (*value == 0.0 || isnan(*value) ) {
290 sindex = floor(log(fabs(*value))/log((double)im->base));
291 *magfact = pow((double)im->base, (double)sindex);
292 (*value) /= (*magfact);
294 if ( sindex <= symbcenter && sindex >= -symbcenter) {
295 (*symb_ptr) = symbol[sindex+symbcenter];
303 static char si_symbol[] = {
304 'a', /* 10e-18 Atto */
305 'f', /* 10e-15 Femto */
306 'p', /* 10e-12 Pico */
307 'n', /* 10e-9 Nano */
308 'u', /* 10e-6 Micro */
309 'm', /* 10e-3 Milli */
314 'T', /* 10e12 Tera */
315 'P', /* 10e15 Peta */
318 static const int si_symbcenter = 6;
320 /* find SI magnitude symbol for the numbers on the y-axis*/
323 image_desc_t *im /* image description */
327 double digits,viewdigits=0;
329 digits = floor( log( max( fabs(im->minval),fabs(im->maxval)))/log((double)im->base));
331 if (im->unitsexponent != 9999) {
332 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
333 viewdigits = floor(im->unitsexponent / 3);
338 im->magfact = pow((double)im->base , digits);
341 printf("digits %6.3f im->magfact %6.3f\n",digits,im->magfact);
344 im->viewfactor = im->magfact / pow((double)im->base , viewdigits);
346 if ( ((viewdigits+si_symbcenter) < sizeof(si_symbol)) &&
347 ((viewdigits+si_symbcenter) >= 0) )
348 im->symbol = si_symbol[(int)viewdigits+si_symbcenter];
353 /* move min and max values around to become sensible */
356 expand_range(image_desc_t *im)
358 double sensiblevalues[] ={1000.0,900.0,800.0,750.0,700.0,
359 600.0,500.0,400.0,300.0,250.0,
360 200.0,125.0,100.0,90.0,80.0,
361 75.0,70.0,60.0,50.0,40.0,30.0,
362 25.0,20.0,10.0,9.0,8.0,
363 7.0,6.0,5.0,4.0,3.5,3.0,
364 2.5,2.0,1.8,1.5,1.2,1.0,
365 0.8,0.7,0.6,0.5,0.4,0.3,0.2,0.1,0.0,-1};
367 double scaled_min,scaled_max;
374 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
375 im->minval,im->maxval,im->magfact);
378 if (isnan(im->ygridstep)){
379 if(im->extra_flags & ALTAUTOSCALE) {
380 /* measure the amplitude of the function. Make sure that
381 graph boundaries are slightly higher then max/min vals
382 so we can see amplitude on the graph */
385 delt = im->maxval - im->minval;
387 fact = 2.0 * pow(10.0,
388 floor(log10(max(fabs(im->minval), fabs(im->maxval))/im->magfact)) - 2);
390 adj = (fact - delt) * 0.55;
392 printf("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n", im->minval, im->maxval, delt, fact, adj);
398 else if(im->extra_flags & ALTAUTOSCALE_MAX) {
399 /* measure the amplitude of the function. Make sure that
400 graph boundaries are slightly higher than max vals
401 so we can see amplitude on the graph */
402 adj = (im->maxval - im->minval) * 0.1;
406 scaled_min = im->minval / im->magfact;
407 scaled_max = im->maxval / im->magfact;
409 for (i=1; sensiblevalues[i] > 0; i++){
410 if (sensiblevalues[i-1]>=scaled_min &&
411 sensiblevalues[i]<=scaled_min)
412 im->minval = sensiblevalues[i]*(im->magfact);
414 if (-sensiblevalues[i-1]<=scaled_min &&
415 -sensiblevalues[i]>=scaled_min)
416 im->minval = -sensiblevalues[i-1]*(im->magfact);
418 if (sensiblevalues[i-1] >= scaled_max &&
419 sensiblevalues[i] <= scaled_max)
420 im->maxval = sensiblevalues[i-1]*(im->magfact);
422 if (-sensiblevalues[i-1]<=scaled_max &&
423 -sensiblevalues[i] >=scaled_max)
424 im->maxval = -sensiblevalues[i]*(im->magfact);
428 /* adjust min and max to the grid definition if there is one */
429 im->minval = (double)im->ylabfact * im->ygridstep *
430 floor(im->minval / ((double)im->ylabfact * im->ygridstep));
431 im->maxval = (double)im->ylabfact * im->ygridstep *
432 ceil(im->maxval /( (double)im->ylabfact * im->ygridstep));
436 fprintf(stderr,"SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
437 im->minval,im->maxval,im->magfact);
442 apply_gridfit(image_desc_t *im)
444 if (isnan(im->minval) || isnan(im->maxval))
447 if (im->logarithmic) {
448 double ya, yb, ypix, ypixfrac;
449 double log10_range = log10(im->maxval) - log10(im->minval);
450 ya = pow((double)10, floor(log10(im->minval)));
451 while (ya < im->minval)
454 return; /* don't have y=10^x gridline */
456 if (yb <= im->maxval) {
457 /* we have at least 2 y=10^x gridlines.
458 Make sure distance between them in pixels
459 are an integer by expanding im->maxval */
460 double y_pixel_delta = ytr(im, ya) - ytr(im, yb);
461 double factor = y_pixel_delta / floor(y_pixel_delta);
462 double new_log10_range = factor * log10_range;
463 double new_ymax_log10 = log10(im->minval) + new_log10_range;
464 im->maxval = pow(10, new_ymax_log10);
465 ytr(im,DNAN); /* reset precalc */
466 log10_range = log10(im->maxval) - log10(im->minval);
468 /* make sure first y=10^x gridline is located on
469 integer pixel position by moving scale slightly
470 downwards (sub-pixel movement) */
471 ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
472 ypixfrac = ypix - floor(ypix);
473 if (ypixfrac > 0 && ypixfrac < 1) {
474 double yfrac = ypixfrac / im->ysize;
475 im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
476 im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
477 ytr(im,DNAN); /* reset precalc */
480 /* Make sure we have an integer pixel distance between
481 each minor gridline */
482 double ypos1 = ytr(im, im->minval);
483 double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
484 double y_pixel_delta = ypos1 - ypos2;
485 double factor = y_pixel_delta / floor(y_pixel_delta);
486 double new_range = factor * (im->maxval - im->minval);
487 double gridstep = im->ygrid_scale.gridstep;
488 double minor_y, minor_y_px, minor_y_px_frac;
489 im->maxval = im->minval + new_range;
490 ytr(im,DNAN); /* reset precalc */
491 /* make sure first minor gridline is on integer pixel y coord */
492 minor_y = gridstep * floor(im->minval / gridstep);
493 while (minor_y < im->minval)
495 minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
496 minor_y_px_frac = minor_y_px - floor(minor_y_px);
497 if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
498 double yfrac = minor_y_px_frac / im->ysize;
499 double range = im->maxval - im->minval;
500 im->minval = im->minval - yfrac * range;
501 im->maxval = im->maxval - yfrac * range;
502 ytr(im,DNAN); /* reset precalc */
504 calc_horizontal_grid(im); /* recalc with changed im->maxval */
508 /* reduce data reimplementation by Alex */
512 enum cf_en cf, /* which consolidation function ?*/
513 unsigned long cur_step, /* step the data currently is in */
514 time_t *start, /* start, end and step as requested ... */
515 time_t *end, /* ... by the application will be ... */
516 unsigned long *step, /* ... adjusted to represent reality */
517 unsigned long *ds_cnt, /* number of data sources in file */
518 rrd_value_t **data) /* two dimensional array containing the data */
520 int i,reduce_factor = ceil((double)(*step) / (double)cur_step);
521 unsigned long col,dst_row,row_cnt,start_offset,end_offset,skiprows=0;
522 rrd_value_t *srcptr,*dstptr;
524 (*step) = cur_step*reduce_factor; /* set new step size for reduced data */
527 row_cnt = ((*end)-(*start))/cur_step;
533 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
534 row_cnt,reduce_factor,*start,*end,cur_step);
535 for (col=0;col<row_cnt;col++) {
536 printf("time %10lu: ",*start+(col+1)*cur_step);
537 for (i=0;i<*ds_cnt;i++)
538 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
543 /* We have to combine [reduce_factor] rows of the source
544 ** into one row for the destination. Doing this we also
545 ** need to take care to combine the correct rows. First
546 ** alter the start and end time so that they are multiples
547 ** of the new step time. We cannot reduce the amount of
548 ** time so we have to move the end towards the future and
549 ** the start towards the past.
551 end_offset = (*end) % (*step);
552 start_offset = (*start) % (*step);
554 /* If there is a start offset (which cannot be more than
555 ** one destination row), skip the appropriate number of
556 ** source rows and one destination row. The appropriate
557 ** number is what we do know (start_offset/cur_step) of
558 ** the new interval (*step/cur_step aka reduce_factor).
561 printf("start_offset: %lu end_offset: %lu\n",start_offset,end_offset);
562 printf("row_cnt before: %lu\n",row_cnt);
565 (*start) = (*start)-start_offset;
566 skiprows=reduce_factor-start_offset/cur_step;
567 srcptr+=skiprows* *ds_cnt;
568 for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
572 printf("row_cnt between: %lu\n",row_cnt);
575 /* At the end we have some rows that are not going to be
576 ** used, the amount is end_offset/cur_step
579 (*end) = (*end)-end_offset+(*step);
580 skiprows = end_offset/cur_step;
584 printf("row_cnt after: %lu\n",row_cnt);
587 /* Sanity check: row_cnt should be multiple of reduce_factor */
588 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
590 if (row_cnt%reduce_factor) {
591 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
592 row_cnt,reduce_factor);
593 printf("BUG in reduce_data()\n");
597 /* Now combine reduce_factor intervals at a time
598 ** into one interval for the destination.
601 for (dst_row=0;(long int)row_cnt>=reduce_factor;dst_row++) {
602 for (col=0;col<(*ds_cnt);col++) {
603 rrd_value_t newval=DNAN;
604 unsigned long validval=0;
606 for (i=0;i<reduce_factor;i++) {
607 if (isnan(srcptr[i*(*ds_cnt)+col])) {
611 if (isnan(newval)) newval = srcptr[i*(*ds_cnt)+col];
619 newval += srcptr[i*(*ds_cnt)+col];
622 newval = min (newval,srcptr[i*(*ds_cnt)+col]);
625 /* an interval contains a failure if any subintervals contained a failure */
627 newval = max (newval,srcptr[i*(*ds_cnt)+col]);
630 newval = srcptr[i*(*ds_cnt)+col];
635 if (validval == 0){newval = DNAN;} else{
653 srcptr+=(*ds_cnt)*reduce_factor;
654 row_cnt-=reduce_factor;
656 /* If we had to alter the endtime, we didn't have enough
657 ** source rows to fill the last row. Fill it with NaN.
659 if (end_offset) for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
661 row_cnt = ((*end)-(*start))/ *step;
663 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
664 row_cnt,*start,*end,*step);
665 for (col=0;col<row_cnt;col++) {
666 printf("time %10lu: ",*start+(col+1)*(*step));
667 for (i=0;i<*ds_cnt;i++)
668 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
675 /* get the data required for the graphs from the
679 data_fetch(image_desc_t *im )
684 /* pull the data from the rrd files ... */
685 for (i=0;i< (int)im->gdes_c;i++){
686 /* only GF_DEF elements fetch data */
687 if (im->gdes[i].gf != GF_DEF)
691 /* do we have it already ?*/
692 for (ii=0;ii<i;ii++) {
693 if (im->gdes[ii].gf != GF_DEF)
695 if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
696 && (im->gdes[i].cf == im->gdes[ii].cf)
697 && (im->gdes[i].cf_reduce == im->gdes[ii].cf_reduce)
698 && (im->gdes[i].start_orig == im->gdes[ii].start_orig)
699 && (im->gdes[i].end_orig == im->gdes[ii].end_orig)
700 && (im->gdes[i].step_orig == im->gdes[ii].step_orig)) {
701 /* OK, the data is already there.
702 ** Just copy the header portion
704 im->gdes[i].start = im->gdes[ii].start;
705 im->gdes[i].end = im->gdes[ii].end;
706 im->gdes[i].step = im->gdes[ii].step;
707 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
708 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
709 im->gdes[i].data = im->gdes[ii].data;
710 im->gdes[i].data_first = 0;
717 unsigned long ft_step = im->gdes[i].step ;
719 if((rrd_fetch_fn(im->gdes[i].rrd,
725 &im->gdes[i].ds_namv,
726 &im->gdes[i].data)) == -1){
729 im->gdes[i].data_first = 1;
730 im->gdes[i].step = im->step;
732 if (ft_step < im->gdes[i].step) {
733 reduce_data(im->gdes[i].cf_reduce,
741 im->gdes[i].step = ft_step;
745 /* lets see if the required data source is really there */
746 for(ii=0;ii<(int)im->gdes[i].ds_cnt;ii++){
747 if(strcmp(im->gdes[i].ds_namv[ii],im->gdes[i].ds_nam) == 0){
750 if (im->gdes[i].ds== -1){
751 rrd_set_error("No DS called '%s' in '%s'",
752 im->gdes[i].ds_nam,im->gdes[i].rrd);
760 /* evaluate the expressions in the CDEF functions */
762 /*************************************************************
764 *************************************************************/
767 find_var_wrapper(void *arg1, char *key)
769 return find_var((image_desc_t *) arg1, key);
772 /* find gdes containing var*/
774 find_var(image_desc_t *im, char *key){
776 for(ii=0;ii<im->gdes_c-1;ii++){
777 if((im->gdes[ii].gf == GF_DEF
778 || im->gdes[ii].gf == GF_VDEF
779 || im->gdes[ii].gf == GF_CDEF)
780 && (strcmp(im->gdes[ii].vname,key) == 0)){
787 /* find the largest common denominator for all the numbers
788 in the 0 terminated num array */
793 for (i=0;num[i+1]!=0;i++){
795 rest=num[i] % num[i+1];
796 num[i]=num[i+1]; num[i+1]=rest;
800 /* return i==0?num[i]:num[i-1]; */
804 /* run the rpn calculator on all the VDEF and CDEF arguments */
806 data_calc( image_desc_t *im){
810 long *steparray, rpi;
815 rpnstack_init(&rpnstack);
817 for (gdi=0;gdi<im->gdes_c;gdi++){
818 /* Look for GF_VDEF and GF_CDEF in the same loop,
819 * so CDEFs can use VDEFs and vice versa
821 switch (im->gdes[gdi].gf) {
825 graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx];
827 /* remove current shift */
828 vdp->start -= vdp->shift;
829 vdp->end -= vdp->shift;
832 if (im->gdes[gdi].shidx >= 0)
833 vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
836 vdp->shift = im->gdes[gdi].shval;
838 /* normalize shift to multiple of consolidated step */
839 vdp->shift = (vdp->shift / (long)vdp->step) * (long)vdp->step;
842 vdp->start += vdp->shift;
843 vdp->end += vdp->shift;
847 /* A VDEF has no DS. This also signals other parts
848 * of rrdtool that this is a VDEF value, not a CDEF.
850 im->gdes[gdi].ds_cnt = 0;
851 if (vdef_calc(im,gdi)) {
852 rrd_set_error("Error processing VDEF '%s'"
855 rpnstack_free(&rpnstack);
860 im->gdes[gdi].ds_cnt = 1;
861 im->gdes[gdi].ds = 0;
862 im->gdes[gdi].data_first = 1;
863 im->gdes[gdi].start = 0;
864 im->gdes[gdi].end = 0;
869 /* Find the variables in the expression.
870 * - VDEF variables are substituted by their values
871 * and the opcode is changed into OP_NUMBER.
872 * - CDEF variables are analized for their step size,
873 * the lowest common denominator of all the step
874 * sizes of the data sources involved is calculated
875 * and the resulting number is the step size for the
876 * resulting data source.
878 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
879 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
880 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER){
881 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
882 if (im->gdes[ptr].ds_cnt == 0) { /* this is a VDEF data source */
884 printf("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
886 im->gdes[ptr].vname);
887 printf("DEBUG: value from vdef is %f\n",im->gdes[ptr].vf.val);
889 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
890 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
891 } else { /* normal variables and PREF(variables) */
893 /* add one entry to the array that keeps track of the step sizes of the
894 * data sources going into the CDEF. */
896 rrd_realloc(steparray,
897 (++stepcnt+1)*sizeof(*steparray)))==NULL){
898 rrd_set_error("realloc steparray");
899 rpnstack_free(&rpnstack);
903 steparray[stepcnt-1] = im->gdes[ptr].step;
905 /* adjust start and end of cdef (gdi) so
906 * that it runs from the latest start point
907 * to the earliest endpoint of any of the
908 * rras involved (ptr)
911 if(im->gdes[gdi].start < im->gdes[ptr].start)
912 im->gdes[gdi].start = im->gdes[ptr].start;
914 if(im->gdes[gdi].end == 0 ||
915 im->gdes[gdi].end > im->gdes[ptr].end)
916 im->gdes[gdi].end = im->gdes[ptr].end;
918 /* store pointer to the first element of
919 * the rra providing data for variable,
920 * further save step size and data source
923 im->gdes[gdi].rpnp[rpi].data = im->gdes[ptr].data + im->gdes[ptr].ds;
924 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
925 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
927 /* backoff the *.data ptr; this is done so
928 * rpncalc() function doesn't have to treat
929 * the first case differently
931 } /* if ds_cnt != 0 */
932 } /* if OP_VARIABLE */
933 } /* loop through all rpi */
935 /* move the data pointers to the correct period */
936 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
937 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
938 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER){
939 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
940 long diff = im->gdes[gdi].start - im->gdes[ptr].start;
943 im->gdes[gdi].rpnp[rpi].data += (diff / im->gdes[ptr].step) * im->gdes[ptr].ds_cnt;
947 if(steparray == NULL){
948 rrd_set_error("rpn expressions without DEF"
949 " or CDEF variables are not supported");
950 rpnstack_free(&rpnstack);
953 steparray[stepcnt]=0;
954 /* Now find the resulting step. All steps in all
955 * used RRAs have to be visited
957 im->gdes[gdi].step = lcd(steparray);
959 if((im->gdes[gdi].data = malloc((
960 (im->gdes[gdi].end-im->gdes[gdi].start)
961 / im->gdes[gdi].step)
962 * sizeof(double)))==NULL){
963 rrd_set_error("malloc im->gdes[gdi].data");
964 rpnstack_free(&rpnstack);
968 /* Step through the new cdef results array and
969 * calculate the values
971 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
972 now<=im->gdes[gdi].end;
973 now += im->gdes[gdi].step)
975 rpnp_t *rpnp = im -> gdes[gdi].rpnp;
977 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
978 * in this case we are advancing by timesteps;
979 * we use the fact that time_t is a synonym for long
981 if (rpn_calc(rpnp,&rpnstack,(long) now,
982 im->gdes[gdi].data,++dataidx) == -1) {
983 /* rpn_calc sets the error string */
984 rpnstack_free(&rpnstack);
987 } /* enumerate over time steps within a CDEF */
992 } /* enumerate over CDEFs */
993 rpnstack_free(&rpnstack);
997 /* massage data so, that we get one value for each x coordinate in the graph */
999 data_proc( image_desc_t *im ){
1001 double pixstep = (double)(im->end-im->start)
1002 /(double)im->xsize; /* how much time
1003 passes in one pixel */
1005 double minval=DNAN,maxval=DNAN;
1007 unsigned long gr_time;
1009 /* memory for the processed data */
1010 for(i=0;i<im->gdes_c;i++) {
1011 if((im->gdes[i].gf==GF_LINE) ||
1012 (im->gdes[i].gf==GF_AREA) ||
1013 (im->gdes[i].gf==GF_TICK)) {
1014 if((im->gdes[i].p_data = malloc((im->xsize +1)
1015 * sizeof(rrd_value_t)))==NULL){
1016 rrd_set_error("malloc data_proc");
1022 for (i=0;i<im->xsize;i++) { /* for each pixel */
1024 gr_time = im->start+pixstep*i; /* time of the current step */
1027 for (ii=0;ii<im->gdes_c;ii++) {
1029 switch (im->gdes[ii].gf) {
1033 if (!im->gdes[ii].stack)
1035 value = im->gdes[ii].yrule;
1036 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1037 /* The time of the data doesn't necessarily match
1038 ** the time of the graph. Beware.
1040 vidx = im->gdes[ii].vidx;
1041 if (im->gdes[vidx].gf == GF_VDEF) {
1042 value = im->gdes[vidx].vf.val;
1043 } else if (((long int)gr_time >= (long int)im->gdes[vidx].start) &&
1044 ((long int)gr_time <= (long int)im->gdes[vidx].end) ) {
1045 value = im->gdes[vidx].data[
1046 (unsigned long) floor(
1047 (double)(gr_time - im->gdes[vidx].start)
1048 / im->gdes[vidx].step)
1049 * im->gdes[vidx].ds_cnt
1057 if (! isnan(value)) {
1059 im->gdes[ii].p_data[i] = paintval;
1060 /* GF_TICK: the data values are not
1061 ** relevant for min and max
1063 if (finite(paintval) && im->gdes[ii].gf != GF_TICK ) {
1064 if (isnan(minval) || paintval < minval)
1066 if (isnan(maxval) || paintval > maxval)
1070 im->gdes[ii].p_data[i] = DNAN;
1074 rrd_set_error("STACK should already be turned into LINE or AREA here");
1083 /* if min or max have not been asigned a value this is because
1084 there was no data in the graph ... this is not good ...
1085 lets set these to dummy values then ... */
1087 if (im->logarithmic) {
1088 if (isnan(minval)) minval = 0.2;
1089 if (isnan(maxval)) maxval = 5.1;
1092 if (isnan(minval)) minval = 0.0;
1093 if (isnan(maxval)) maxval = 1.0;
1096 /* adjust min and max values */
1097 if (isnan(im->minval)
1098 /* don't adjust low-end with log scale */ /* why not? */
1099 || ((!im->rigid) && im->minval > minval)
1101 if (im->logarithmic)
1102 im->minval = minval * 0.5;
1104 im->minval = minval;
1106 if (isnan(im->maxval)
1107 || (!im->rigid && im->maxval < maxval)
1109 if (im->logarithmic)
1110 im->maxval = maxval * 2.0;
1112 im->maxval = maxval;
1114 /* make sure min is smaller than max */
1115 if (im->minval > im->maxval) {
1116 im->minval = 0.99 * im->maxval;
1119 /* make sure min and max are not equal */
1120 if (im->minval == im->maxval) {
1122 if (! im->logarithmic) {
1125 /* make sure min and max are not both zero */
1126 if (im->maxval == 0.0) {
1135 /* identify the point where the first gridline, label ... gets placed */
1139 time_t start, /* what is the initial time */
1140 enum tmt_en baseint, /* what is the basic interval */
1141 long basestep /* how many if these do we jump a time */
1145 localtime_r(&start, &tm);
1148 tm.tm_sec -= tm.tm_sec % basestep; break;
1151 tm.tm_min -= tm.tm_min % basestep;
1156 tm.tm_hour -= tm.tm_hour % basestep; break;
1158 /* we do NOT look at the basestep for this ... */
1161 tm.tm_hour = 0; break;
1163 /* we do NOT look at the basestep for this ... */
1167 tm.tm_mday -= tm.tm_wday -1; /* -1 because we want the monday */
1168 if (tm.tm_wday==0) tm.tm_mday -= 7; /* we want the *previous* monday */
1175 tm.tm_mon -= tm.tm_mon % basestep; break;
1183 tm.tm_year -= (tm.tm_year+1900) % basestep;
1188 /* identify the point where the next gridline, label ... gets placed */
1191 time_t current, /* what is the initial time */
1192 enum tmt_en baseint, /* what is the basic interval */
1193 long basestep /* how many if these do we jump a time */
1198 localtime_r(¤t, &tm);
1202 tm.tm_sec += basestep; break;
1204 tm.tm_min += basestep; break;
1206 tm.tm_hour += basestep; break;
1208 tm.tm_mday += basestep; break;
1210 tm.tm_mday += 7*basestep; break;
1212 tm.tm_mon += basestep; break;
1214 tm.tm_year += basestep;
1216 madetime = mktime(&tm);
1217 } while (madetime == -1); /* this is necessary to skip impssible times
1218 like the daylight saving time skips */
1224 /* calculate values required for PRINT and GPRINT functions */
1227 print_calc(image_desc_t *im, char ***prdata)
1229 long i,ii,validsteps;
1232 int graphelement = 0;
1235 double magfact = -1;
1239 /* wow initializing tmvdef is quite a task :-) */
1240 time_t now = time(NULL);
1241 localtime_r(&now,&tmvdef);
1242 if (im->imginfo) prlines++;
1243 for(i=0;i<im->gdes_c;i++){
1244 switch(im->gdes[i].gf){
1247 if(((*prdata) = rrd_realloc((*prdata),prlines*sizeof(char *)))==NULL){
1248 rrd_set_error("realloc prdata");
1252 /* PRINT and GPRINT can now print VDEF generated values.
1253 * There's no need to do any calculations on them as these
1254 * calculations were already made.
1256 vidx = im->gdes[i].vidx;
1257 if (im->gdes[vidx].gf==GF_VDEF) { /* simply use vals */
1258 printval = im->gdes[vidx].vf.val;
1259 localtime_r(&im->gdes[vidx].vf.when,&tmvdef);
1260 } else { /* need to calculate max,min,avg etcetera */
1261 max_ii =((im->gdes[vidx].end
1262 - im->gdes[vidx].start)
1263 / im->gdes[vidx].step
1264 * im->gdes[vidx].ds_cnt);
1267 for( ii=im->gdes[vidx].ds;
1269 ii+=im->gdes[vidx].ds_cnt){
1270 if (! finite(im->gdes[vidx].data[ii]))
1272 if (isnan(printval)){
1273 printval = im->gdes[vidx].data[ii];
1278 switch (im->gdes[i].cf){
1281 case CF_DEVSEASONAL:
1285 printval += im->gdes[vidx].data[ii];
1288 printval = min( printval, im->gdes[vidx].data[ii]);
1292 printval = max( printval, im->gdes[vidx].data[ii]);
1295 printval = im->gdes[vidx].data[ii];
1298 if (im->gdes[i].cf==CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1299 if (validsteps > 1) {
1300 printval = (printval / validsteps);
1303 } /* prepare printval */
1305 if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) {
1306 /* Magfact is set to -1 upon entry to print_calc. If it
1307 * is still less than 0, then we need to run auto_scale.
1308 * Otherwise, put the value into the correct units. If
1309 * the value is 0, then do not set the symbol or magnification
1310 * so next the calculation will be performed again. */
1311 if (magfact < 0.0) {
1312 auto_scale(im,&printval,&si_symb,&magfact);
1313 if (printval == 0.0)
1316 printval /= magfact;
1318 *(++percent_s) = 's';
1319 } else if (strstr(im->gdes[i].format,"%s") != NULL) {
1320 auto_scale(im,&printval,&si_symb,&magfact);
1323 if (im->gdes[i].gf == GF_PRINT){
1324 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1325 (*prdata)[prlines-1] = NULL;
1326 if (im->gdes[i].strftm){
1327 strftime((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,&tmvdef);
1329 if (bad_format(im->gdes[i].format)) {
1330 rrd_set_error("bad format for PRINT in '%s'", im->gdes[i].format);
1334 #ifdef HAVE_SNPRINTF
1335 snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
1337 sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
1343 if (im->gdes[i].strftm){
1344 strftime(im->gdes[i].legend,FMT_LEG_LEN,im->gdes[i].format,&tmvdef);
1346 if (bad_format(im->gdes[i].format)) {
1347 rrd_set_error("bad format for GPRINT in '%s'", im->gdes[i].format);
1350 #ifdef HAVE_SNPRINTF
1351 snprintf(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
1353 sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
1365 if(isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1366 im->gdes[i].yrule=im->gdes[im->gdes[i].vidx].vf.val;
1371 if(im->gdes[i].xrule == 0) { /* again ... the legend printer needs it*/
1372 im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
1380 #ifdef WITH_PIECHART
1387 rrd_set_error("STACK should already be turned into LINE or AREA here");
1392 return graphelement;
1396 /* place legends with color spots */
1398 leg_place(image_desc_t *im)
1401 int interleg = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1402 int border = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1403 int fill=0, fill_last;
1405 int leg_x = border, leg_y = im->yimg;
1406 int leg_y_prev = im->yimg;
1410 char prt_fctn; /*special printfunctions */
1413 if( !(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH) ) {
1414 if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1415 rrd_set_error("malloc for legspace");
1419 for(i=0;i<im->gdes_c;i++){
1422 /* hid legends for rules which are not displayed */
1424 if(!(im->extra_flags & FORCE_RULES_LEGEND)) {
1425 if (im->gdes[i].gf == GF_HRULE &&
1426 (im->gdes[i].yrule < im->minval || im->gdes[i].yrule > im->maxval))
1427 im->gdes[i].legend[0] = '\0';
1429 if (im->gdes[i].gf == GF_VRULE &&
1430 (im->gdes[i].xrule < im->start || im->gdes[i].xrule > im->end))
1431 im->gdes[i].legend[0] = '\0';
1434 leg_cc = strlen(im->gdes[i].legend);
1436 /* is there a controle code ant the end of the legend string ? */
1437 /* and it is not a tab \\t */
1438 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\' && im->gdes[i].legend[leg_cc-1] != 't') {
1439 prt_fctn = im->gdes[i].legend[leg_cc-1];
1441 im->gdes[i].legend[leg_cc] = '\0';
1445 /* remove exess space */
1446 while (prt_fctn=='g' &&
1448 im->gdes[i].legend[leg_cc-1]==' '){
1450 im->gdes[i].legend[leg_cc]='\0';
1453 legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1456 /* no interleg space if string ends in \g */
1457 fill += legspace[i];
1459 fill += gfx_get_text_width(im->canvas, fill+border,
1460 im->text_prop[TEXT_PROP_LEGEND].font,
1461 im->text_prop[TEXT_PROP_LEGEND].size,
1463 im->gdes[i].legend, 0);
1468 /* who said there was a special tag ... ?*/
1469 if (prt_fctn=='g') {
1472 if (prt_fctn == '\0') {
1473 if (i == im->gdes_c -1 ) prt_fctn ='l';
1475 /* is it time to place the legends ? */
1476 if (fill > im->ximg - 2*border){
1491 if (prt_fctn != '\0'){
1493 if (leg_c >= 2 && prt_fctn == 'j') {
1494 glue = (im->ximg - fill - 2* border) / (leg_c-1);
1498 if (prt_fctn =='c') leg_x = (im->ximg - fill) / 2.0;
1499 if (prt_fctn =='r') leg_x = im->ximg - fill - border;
1501 for(ii=mark;ii<=i;ii++){
1502 if(im->gdes[ii].legend[0]=='\0')
1503 continue; /* skip empty legends */
1504 im->gdes[ii].leg_x = leg_x;
1505 im->gdes[ii].leg_y = leg_y;
1507 gfx_get_text_width(im->canvas, leg_x,
1508 im->text_prop[TEXT_PROP_LEGEND].font,
1509 im->text_prop[TEXT_PROP_LEGEND].size,
1511 im->gdes[ii].legend, 0)
1516 /* only add y space if there was text on the line */
1517 if (leg_x > border || prt_fctn == 's')
1518 leg_y += im->text_prop[TEXT_PROP_LEGEND].size*1.8;
1519 if (prt_fctn == 's')
1520 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1526 im->yimg = leg_y_prev;
1527 /* if we did place some legends we have to add vertical space */
1528 if (leg_y != im->yimg){
1529 im->yimg += im->text_prop[TEXT_PROP_LEGEND].size*1.8;
1536 /* create a grid on the graph. it determines what to do
1537 from the values of xsize, start and end */
1539 /* the xaxis labels are determined from the number of seconds per pixel
1540 in the requested graph */
1545 calc_horizontal_grid(image_desc_t *im)
1551 int decimals, fractionals;
1553 im->ygrid_scale.labfact=2;
1554 range = im->maxval - im->minval;
1555 scaledrange = range / im->magfact;
1557 /* does the scale of this graph make it impossible to put lines
1558 on it? If so, give up. */
1559 if (isnan(scaledrange)) {
1563 /* find grid spaceing */
1565 if(isnan(im->ygridstep)){
1566 if(im->extra_flags & ALTYGRID) {
1567 /* find the value with max number of digits. Get number of digits */
1568 decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))*im->viewfactor/im->magfact));
1569 if(decimals <= 0) /* everything is small. make place for zero */
1572 im->ygrid_scale.gridstep = pow((double)10, floor(log10(range*im->viewfactor/im->magfact)))/im->viewfactor*im->magfact;
1574 if(im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1575 im->ygrid_scale.gridstep = 0.1;
1576 /* should have at least 5 lines but no more then 15 */
1577 if(range/im->ygrid_scale.gridstep < 5)
1578 im->ygrid_scale.gridstep /= 10;
1579 if(range/im->ygrid_scale.gridstep > 15)
1580 im->ygrid_scale.gridstep *= 10;
1581 if(range/im->ygrid_scale.gridstep > 5) {
1582 im->ygrid_scale.labfact = 1;
1583 if(range/im->ygrid_scale.gridstep > 8)
1584 im->ygrid_scale.labfact = 2;
1587 im->ygrid_scale.gridstep /= 5;
1588 im->ygrid_scale.labfact = 5;
1590 fractionals = floor(log10(im->ygrid_scale.gridstep*(double)im->ygrid_scale.labfact*im->viewfactor/im->magfact));
1591 if(fractionals < 0) { /* small amplitude. */
1592 int len = decimals - fractionals + 1;
1593 if (im->unitslength < len+2) im->unitslength = len+2;
1594 sprintf(im->ygrid_scale.labfmt, "%%%d.%df%s", len, -fractionals,(im->symbol != ' ' ? " %c" : ""));
1596 int len = decimals + 1;
1597 if (im->unitslength < len+2) im->unitslength = len+2;
1598 sprintf(im->ygrid_scale.labfmt, "%%%d.0f%s", len, ( im->symbol != ' ' ? " %c" : "" ));
1602 for(i=0;ylab[i].grid > 0;i++){
1603 pixel = im->ysize / (scaledrange / ylab[i].grid);
1610 if (pixel * ylab[gridind].lfac[i] >= 2.5 * im->text_prop[TEXT_PROP_AXIS].size) {
1611 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1616 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1619 im->ygrid_scale.gridstep = im->ygridstep;
1620 im->ygrid_scale.labfact = im->ylabfact;
1625 int draw_horizontal_grid(image_desc_t *im)
1629 char graph_label[100];
1631 double X0=im->xorigin;
1632 double X1=im->xorigin+im->xsize;
1634 int sgrid = (int)( im->minval / im->ygrid_scale.gridstep - 1);
1635 int egrid = (int)( im->maxval / im->ygrid_scale.gridstep + 1);
1637 scaledstep = im->ygrid_scale.gridstep/(double)im->magfact*(double)im->viewfactor;
1638 MaxY = scaledstep*(double)egrid;
1639 for (i = sgrid; i <= egrid; i++){
1640 double Y0=ytr(im,im->ygrid_scale.gridstep*i);
1641 double YN=ytr(im,im->ygrid_scale.gridstep*(i+1));
1642 if ( Y0 >= im->yorigin-im->ysize
1643 && Y0 <= im->yorigin){
1644 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
1645 with the chosen settings. Add a label if required by settings, or if
1646 there is only one label so far and the next grid line is out of bounds. */
1647 if(i % im->ygrid_scale.labfact == 0 || ( nlabels==1 && (YN < im->yorigin-im->ysize || YN > im->yorigin) )){
1648 if (im->symbol == ' ') {
1649 if(im->extra_flags & ALTYGRID) {
1650 sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*(double)i);
1653 sprintf(graph_label,"%4.1f",scaledstep*(double)i);
1655 sprintf(graph_label,"%4.0f",scaledstep*(double)i);
1659 char sisym = ( i == 0 ? ' ' : im->symbol);
1660 if(im->extra_flags & ALTYGRID) {
1661 sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*(double)i,sisym);
1664 sprintf(graph_label,"%4.1f %c",scaledstep*(double)i, sisym);
1666 sprintf(graph_label,"%4.0f %c",scaledstep*(double)i, sisym);
1672 gfx_new_text ( im->canvas,
1673 X0-im->text_prop[TEXT_PROP_AXIS].size, Y0,
1674 im->graph_col[GRC_FONT],
1675 im->text_prop[TEXT_PROP_AXIS].font,
1676 im->text_prop[TEXT_PROP_AXIS].size,
1677 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1679 gfx_new_dashed_line ( im->canvas,
1682 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1683 im->grid_dash_on, im->grid_dash_off);
1685 } else if (!(im->extra_flags & NOMINOR)) {
1686 gfx_new_dashed_line ( im->canvas,
1689 GRIDWIDTH, im->graph_col[GRC_GRID],
1690 im->grid_dash_on, im->grid_dash_off);
1698 /* this is frexp for base 10 */
1699 double frexp10(double, double *);
1700 double frexp10(double x, double *e) {
1704 iexp = floor(log(fabs(x)) / log(10));
1705 mnt = x / pow(10.0, iexp);
1708 mnt = x / pow(10.0, iexp);
1714 /* logaritmic horizontal grid */
1716 horizontal_log_grid(image_desc_t *im)
1718 double yloglab[][10] = {
1719 {1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
1720 {1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
1721 {1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0},
1722 {1.0, 2.0, 4.0, 6.0, 8.0, 10., 0.0, 0.0, 0.0, 0.0},
1723 {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.}};
1725 int i, j, val_exp, min_exp;
1726 double nex; /* number of decades in data */
1727 double logscale; /* scale in logarithmic space */
1728 int exfrac = 1; /* decade spacing */
1729 int mid = -1; /* row in yloglab for major grid */
1730 double mspac; /* smallest major grid spacing (pixels) */
1731 int flab; /* first value in yloglab to use */
1734 char graph_label[100];
1736 nex = log10(im->maxval / im->minval);
1737 logscale = im->ysize / nex;
1739 /* major spacing for data with high dynamic range */
1740 while(logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
1741 if(exfrac == 1) exfrac = 3;
1745 /* major spacing for less dynamic data */
1747 /* search best row in yloglab */
1749 for(i = 0; yloglab[mid][i + 1] < 10.0; i++);
1750 mspac = logscale * log10(10.0 / yloglab[mid][i]);
1751 } while(mspac > 2 * im->text_prop[TEXT_PROP_LEGEND].size && mid < 5);
1754 /* find first value in yloglab */
1755 for(flab = 0; frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
1756 if(yloglab[mid][flab] == 10.0) {
1761 if(val_exp % exfrac) val_exp += abs(-val_exp % exfrac);
1764 X1=im->xorigin+im->xsize;
1768 value = yloglab[mid][flab] * pow(10.0, val_exp);
1770 Y0 = ytr(im, value);
1771 if(Y0 <= im->yorigin - im->ysize) break;
1773 /* major grid line */
1774 gfx_new_dashed_line ( im->canvas,
1777 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1778 im->grid_dash_on, im->grid_dash_off);
1781 if (im->extra_flags & FORCE_UNITS_SI) {
1786 scale = floor(val_exp / 3.0);
1787 if( value >= 1.0 ) pvalue = pow(10.0, val_exp % 3);
1788 else pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
1789 pvalue *= yloglab[mid][flab];
1791 if ( ((scale+si_symbcenter) < (int)sizeof(si_symbol)) &&
1792 ((scale+si_symbcenter) >= 0) )
1793 symbol = si_symbol[scale+si_symbcenter];
1797 sprintf(graph_label,"%3.0f %c", pvalue, symbol);
1799 sprintf(graph_label,"%3.0e", value);
1800 gfx_new_text ( im->canvas,
1801 X0-im->text_prop[TEXT_PROP_AXIS].size, Y0,
1802 im->graph_col[GRC_FONT],
1803 im->text_prop[TEXT_PROP_AXIS].font,
1804 im->text_prop[TEXT_PROP_AXIS].size,
1805 im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1809 if(mid < 4 && exfrac == 1) {
1810 /* find first and last minor line behind current major line
1811 * i is the first line and j tha last */
1813 min_exp = val_exp - 1;
1814 for(i = 1; yloglab[mid][i] < 10.0; i++);
1815 i = yloglab[mid][i - 1] + 1;
1820 i = yloglab[mid][flab - 1] + 1;
1821 j = yloglab[mid][flab];
1824 /* draw minor lines below current major line */
1827 value = i * pow(10.0, min_exp);
1828 if(value < im->minval) continue;
1830 Y0 = ytr(im, value);
1831 if(Y0 <= im->yorigin - im->ysize) break;
1834 gfx_new_dashed_line ( im->canvas,
1837 GRIDWIDTH, im->graph_col[GRC_GRID],
1838 im->grid_dash_on, im->grid_dash_off);
1841 else if(exfrac > 1) {
1842 for(i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
1843 value = pow(10.0, i);
1844 if(value < im->minval) continue;
1846 Y0 = ytr(im, value);
1847 if(Y0 <= im->yorigin - im->ysize) break;
1850 gfx_new_dashed_line ( im->canvas,
1853 GRIDWIDTH, im->graph_col[GRC_GRID],
1854 im->grid_dash_on, im->grid_dash_off);
1859 if(yloglab[mid][++flab] == 10.0) {
1865 /* draw minor lines after highest major line */
1866 if(mid < 4 && exfrac == 1) {
1867 /* find first and last minor line below current major line
1868 * i is the first line and j tha last */
1870 min_exp = val_exp - 1;
1871 for(i = 1; yloglab[mid][i] < 10.0; i++);
1872 i = yloglab[mid][i - 1] + 1;
1877 i = yloglab[mid][flab - 1] + 1;
1878 j = yloglab[mid][flab];
1881 /* draw minor lines below current major line */
1884 value = i * pow(10.0, min_exp);
1885 if(value < im->minval) continue;
1887 Y0 = ytr(im, value);
1888 if(Y0 <= im->yorigin - im->ysize) break;
1891 gfx_new_dashed_line ( im->canvas,
1894 GRIDWIDTH, im->graph_col[GRC_GRID],
1895 im->grid_dash_on, im->grid_dash_off);
1898 /* fancy minor gridlines */
1899 else if(exfrac > 1) {
1900 for(i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
1901 value = pow(10.0, i);
1902 if(value < im->minval) continue;
1904 Y0 = ytr(im, value);
1905 if(Y0 <= im->yorigin - im->ysize) break;
1908 gfx_new_dashed_line ( im->canvas,
1911 GRIDWIDTH, im->graph_col[GRC_GRID],
1912 im->grid_dash_on, im->grid_dash_off);
1924 int xlab_sel; /* which sort of label and grid ? */
1925 time_t ti, tilab, timajor;
1927 char graph_label[100];
1928 double X0,Y0,Y1; /* points for filled graph and more*/
1931 /* the type of time grid is determined by finding
1932 the number of seconds per pixel in the graph */
1935 if(im->xlab_user.minsec == -1){
1936 factor=(im->end - im->start)/im->xsize;
1938 while ( xlab[xlab_sel+1].minsec != -1
1939 && xlab[xlab_sel+1].minsec <= factor) { xlab_sel++; } /* pick the last one */
1940 while ( xlab[xlab_sel-1].minsec == xlab[xlab_sel].minsec
1941 && xlab[xlab_sel].length > (im->end - im->start)) { xlab_sel--; } /* go back to the smallest size */
1942 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
1943 im->xlab_user.gridst = xlab[xlab_sel].gridst;
1944 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
1945 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
1946 im->xlab_user.labtm = xlab[xlab_sel].labtm;
1947 im->xlab_user.labst = xlab[xlab_sel].labst;
1948 im->xlab_user.precis = xlab[xlab_sel].precis;
1949 im->xlab_user.stst = xlab[xlab_sel].stst;
1952 /* y coords are the same for every line ... */
1954 Y1 = im->yorigin-im->ysize;
1957 /* paint the minor grid */
1958 if (!(im->extra_flags & NOMINOR))
1960 for(ti = find_first_time(im->start,
1961 im->xlab_user.gridtm,
1962 im->xlab_user.gridst),
1963 timajor = find_first_time(im->start,
1964 im->xlab_user.mgridtm,
1965 im->xlab_user.mgridst);
1967 ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
1969 /* are we inside the graph ? */
1970 if (ti < im->start || ti > im->end) continue;
1971 while (timajor < ti) {
1972 timajor = find_next_time(timajor,
1973 im->xlab_user.mgridtm, im->xlab_user.mgridst);
1975 if (ti == timajor) continue; /* skip as falls on major grid line */
1977 gfx_new_dashed_line(im->canvas,X0,Y0+1, X0,Y1-1,GRIDWIDTH,
1978 im->graph_col[GRC_GRID],
1979 im->grid_dash_on, im->grid_dash_off);
1984 /* paint the major grid */
1985 for(ti = find_first_time(im->start,
1986 im->xlab_user.mgridtm,
1987 im->xlab_user.mgridst);
1989 ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
1991 /* are we inside the graph ? */
1992 if (ti < im->start || ti > im->end) continue;
1994 gfx_new_dashed_line(im->canvas,X0,Y0+3, X0,Y1-2,MGRIDWIDTH,
1995 im->graph_col[GRC_MGRID],
1996 im->grid_dash_on, im->grid_dash_off);
1999 /* paint the labels below the graph */
2000 for(ti = find_first_time(im->start - im->xlab_user.precis/2,
2001 im->xlab_user.labtm,
2002 im->xlab_user.labst);
2003 ti <= im->end - im->xlab_user.precis/2;
2004 ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
2006 tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
2007 /* are we inside the graph ? */
2008 if (tilab < im->start || tilab > im->end) continue;
2011 localtime_r(&tilab, &tm);
2012 strftime(graph_label,99,im->xlab_user.stst, &tm);
2014 # error "your libc has no strftime I guess we'll abort the exercise here."
2016 gfx_new_text ( im->canvas,
2017 xtr(im,tilab), Y0+im->text_prop[TEXT_PROP_AXIS].size*1.4+5,
2018 im->graph_col[GRC_FONT],
2019 im->text_prop[TEXT_PROP_AXIS].font,
2020 im->text_prop[TEXT_PROP_AXIS].size,
2021 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_BOTTOM,
2034 /* draw x and y axis */
2035 /* gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2036 im->xorigin+im->xsize,im->yorigin-im->ysize,
2037 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2039 gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2040 im->xorigin+im->xsize,im->yorigin-im->ysize,
2041 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2043 gfx_new_line ( im->canvas, im->xorigin-4,im->yorigin,
2044 im->xorigin+im->xsize+4,im->yorigin,
2045 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2047 gfx_new_line ( im->canvas, im->xorigin,im->yorigin+4,
2048 im->xorigin,im->yorigin-im->ysize-4,
2049 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2052 /* arrow for X and Y axis direction */
2053 gfx_new_area ( im->canvas,
2054 im->xorigin+im->xsize+2, im->yorigin-2,
2055 im->xorigin+im->xsize+2, im->yorigin+3,
2056 im->xorigin+im->xsize+7, im->yorigin+0.5, /* LINEOFFSET */
2057 im->graph_col[GRC_ARROW]);
2059 gfx_new_area ( im->canvas,
2060 im->xorigin-2, im->yorigin-im->ysize-2,
2061 im->xorigin+3, im->yorigin-im->ysize-2,
2062 im->xorigin+0.5, im->yorigin-im->ysize-7, /* LINEOFFSET */
2063 im->graph_col[GRC_ARROW]);
2068 grid_paint(image_desc_t *im)
2072 double X0,Y0; /* points for filled graph and more*/
2075 /* draw 3d border */
2076 node = gfx_new_area (im->canvas, 0,im->yimg,
2078 2,2,im->graph_col[GRC_SHADEA]);
2079 gfx_add_point( node , im->ximg - 2, 2 );
2080 gfx_add_point( node , im->ximg, 0 );
2081 gfx_add_point( node , 0,0 );
2082 /* gfx_add_point( node , 0,im->yimg ); */
2084 node = gfx_new_area (im->canvas, 2,im->yimg-2,
2085 im->ximg-2,im->yimg-2,
2087 im->graph_col[GRC_SHADEB]);
2088 gfx_add_point( node , im->ximg,0);
2089 gfx_add_point( node , im->ximg,im->yimg);
2090 gfx_add_point( node , 0,im->yimg);
2091 /* gfx_add_point( node , 0,im->yimg ); */
2094 if (im->draw_x_grid == 1 )
2097 if (im->draw_y_grid == 1){
2098 if(im->logarithmic){
2099 res = horizontal_log_grid(im);
2101 res = draw_horizontal_grid(im);
2104 /* dont draw horizontal grid if there is no min and max val */
2106 char *nodata = "No Data found";
2107 gfx_new_text(im->canvas,im->ximg/2, (2*im->yorigin-im->ysize) / 2,
2108 im->graph_col[GRC_FONT],
2109 im->text_prop[TEXT_PROP_AXIS].font,
2110 im->text_prop[TEXT_PROP_AXIS].size,
2111 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
2116 /* yaxis unit description */
2117 gfx_new_text( im->canvas,
2118 10, (im->yorigin - im->ysize/2),
2119 im->graph_col[GRC_FONT],
2120 im->text_prop[TEXT_PROP_UNIT].font,
2121 im->text_prop[TEXT_PROP_UNIT].size, im->tabwidth,
2122 RRDGRAPH_YLEGEND_ANGLE,
2123 GFX_H_LEFT, GFX_V_CENTER,
2127 gfx_new_text( im->canvas,
2128 im->ximg/2, im->text_prop[TEXT_PROP_TITLE].size*1.3+4,
2129 im->graph_col[GRC_FONT],
2130 im->text_prop[TEXT_PROP_TITLE].font,
2131 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
2132 GFX_H_CENTER, GFX_V_CENTER,
2134 /* rrdtool 'logo' */
2135 gfx_new_text( im->canvas,
2137 ( im->graph_col[GRC_FONT] & 0xffffff00 ) | 0x00000044,
2138 im->text_prop[TEXT_PROP_AXIS].font,
2139 5.5, im->tabwidth, 270,
2140 GFX_H_RIGHT, GFX_V_TOP,
2141 "RRDTOOL / TOBI OETIKER");
2143 /* graph watermark */
2144 if(im->watermark[0] != '\0') {
2145 gfx_new_text( im->canvas,
2146 im->ximg/2, im->yimg-6,
2147 ( im->graph_col[GRC_FONT] & 0xffffff00 ) | 0x00000044,
2148 im->text_prop[TEXT_PROP_AXIS].font,
2149 5.5, im->tabwidth, 0,
2150 GFX_H_CENTER, GFX_V_BOTTOM,
2155 if( !(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH) ) {
2156 for(i=0;i<im->gdes_c;i++){
2157 if(im->gdes[i].legend[0] =='\0')
2160 /* im->gdes[i].leg_y is the bottom of the legend */
2161 X0 = im->gdes[i].leg_x;
2162 Y0 = im->gdes[i].leg_y;
2163 gfx_new_text ( im->canvas, X0, Y0,
2164 im->graph_col[GRC_FONT],
2165 im->text_prop[TEXT_PROP_LEGEND].font,
2166 im->text_prop[TEXT_PROP_LEGEND].size,
2167 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
2168 im->gdes[i].legend );
2169 /* The legend for GRAPH items starts with "M " to have
2170 enough space for the box */
2171 if ( im->gdes[i].gf != GF_PRINT &&
2172 im->gdes[i].gf != GF_GPRINT &&
2173 im->gdes[i].gf != GF_COMMENT) {
2176 boxH = gfx_get_text_width(im->canvas, 0,
2177 im->text_prop[TEXT_PROP_LEGEND].font,
2178 im->text_prop[TEXT_PROP_LEGEND].size,
2179 im->tabwidth,"o", 0) * 1.2;
2182 /* make sure transparent colors show up the same way as in the graph */
2183 node = gfx_new_area(im->canvas,
2187 im->graph_col[GRC_BACK]);
2188 gfx_add_point ( node, X0+boxH, Y0-boxV );
2190 node = gfx_new_area(im->canvas,
2195 gfx_add_point ( node, X0+boxH, Y0-boxV );
2196 node = gfx_new_line(im->canvas,
2199 1.0,im->graph_col[GRC_FRAME]);
2200 gfx_add_point(node,X0+boxH,Y0);
2201 gfx_add_point(node,X0+boxH,Y0-boxV);
2202 gfx_close_path(node);
2209 /*****************************************************
2210 * lazy check make sure we rely need to create this graph
2211 *****************************************************/
2213 int lazy_check(image_desc_t *im){
2216 struct stat imgstat;
2218 if (im->lazy == 0) return 0; /* no lazy option */
2219 if (stat(im->graphfile,&imgstat) != 0)
2220 return 0; /* can't stat */
2221 /* one pixel in the existing graph is more then what we would
2223 if (time(NULL) - imgstat.st_mtime >
2224 (im->end - im->start) / im->xsize)
2226 if ((fd = fopen(im->graphfile,"rb")) == NULL)
2227 return 0; /* the file does not exist */
2228 switch (im->canvas->imgformat) {
2230 size = PngSize(fd,&(im->ximg),&(im->yimg));
2239 #ifdef WITH_PIECHART
2241 pie_part(image_desc_t *im, gfx_color_t color,
2242 double PieCenterX, double PieCenterY, double Radius,
2243 double startangle, double endangle)
2247 double step=M_PI/50; /* Number of iterations for the circle;
2248 ** 10 is definitely too low, more than
2249 ** 50 seems to be overkill
2252 /* Strange but true: we have to work clockwise or else
2253 ** anti aliasing nor transparency don't work.
2255 ** This test is here to make sure we do it right, also
2256 ** this makes the for...next loop more easy to implement.
2257 ** The return will occur if the user enters a negative number
2258 ** (which shouldn't be done according to the specs) or if the
2259 ** programmers do something wrong (which, as we all know, never
2260 ** happens anyway :)
2262 if (endangle<startangle) return;
2264 /* Hidden feature: Radius decreases each full circle */
2266 while (angle>=2*M_PI) {
2271 node=gfx_new_area(im->canvas,
2272 PieCenterX+sin(startangle)*Radius,
2273 PieCenterY-cos(startangle)*Radius,
2276 PieCenterX+sin(endangle)*Radius,
2277 PieCenterY-cos(endangle)*Radius,
2279 for (angle=endangle;angle-startangle>=step;angle-=step) {
2281 PieCenterX+sin(angle)*Radius,
2282 PieCenterY-cos(angle)*Radius );
2289 graph_size_location(image_desc_t *im, int elements
2291 #ifdef WITH_PIECHART
2297 /* The actual size of the image to draw is determined from
2298 ** several sources. The size given on the command line is
2299 ** the graph area but we need more as we have to draw labels
2300 ** and other things outside the graph area
2303 /* +-+-------------------------------------------+
2304 ** |l|.................title.....................|
2305 ** |e+--+-------------------------------+--------+
2308 ** |l| l| main graph area | chart |
2311 ** |r+--+-------------------------------+--------+
2312 ** |e| | x-axis labels | |
2313 ** |v+--+-------------------------------+--------+
2314 ** | |..............legends......................|
2315 ** +-+-------------------------------------------+
2317 ** +---------------------------------------------+
2323 #ifdef WITH_PIECHART
2328 Xlegend =0, Ylegend =0,
2330 Xspacing =15, Yspacing =15,
2334 if (im->extra_flags & ONLY_GRAPH) {
2336 im->ximg = im->xsize;
2337 im->yimg = im->ysize;
2338 im->yorigin = im->ysize;
2343 if (im->ylegend[0] != '\0' ) {
2344 Xvertical = im->text_prop[TEXT_PROP_UNIT].size *2;
2348 if (im->title[0] != '\0') {
2349 /* The title is placed "inbetween" two text lines so it
2350 ** automatically has some vertical spacing. The horizontal
2351 ** spacing is added here, on each side.
2353 /* don't care for the with of the title
2354 Xtitle = gfx_get_text_width(im->canvas, 0,
2355 im->text_prop[TEXT_PROP_TITLE].font,
2356 im->text_prop[TEXT_PROP_TITLE].size,
2358 im->title, 0) + 2*Xspacing; */
2359 Ytitle = im->text_prop[TEXT_PROP_TITLE].size*2.6+10;
2365 if (im->draw_x_grid) {
2366 Yxlabel=im->text_prop[TEXT_PROP_AXIS].size *2.5;
2368 if (im->draw_y_grid) {
2369 Xylabel=gfx_get_text_width(im->canvas, 0,
2370 im->text_prop[TEXT_PROP_AXIS].font,
2371 im->text_prop[TEXT_PROP_AXIS].size,
2373 "0", 0) * im->unitslength;
2377 #ifdef WITH_PIECHART
2379 im->piesize=im->xsize<im->ysize?im->xsize:im->ysize;
2385 /* Now calculate the total size. Insert some spacing where
2386 desired. im->xorigin and im->yorigin need to correspond
2387 with the lower left corner of the main graph area or, if
2388 this one is not set, the imaginary box surrounding the
2391 /* The legend width cannot yet be determined, as a result we
2392 ** have problems adjusting the image to it. For now, we just
2393 ** forget about it at all; the legend will have to fit in the
2394 ** size already allocated.
2396 im->ximg = Xylabel + Xmain + 2 * Xspacing;
2398 #ifdef WITH_PIECHART
2402 if (Xmain) im->ximg += Xspacing;
2403 #ifdef WITH_PIECHART
2404 if (Xpie) im->ximg += Xspacing;
2407 im->xorigin = Xspacing + Xylabel;
2409 /* the length of the title should not influence with width of the graph
2410 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2412 if (Xvertical) { /* unit description */
2413 im->ximg += Xvertical;
2414 im->xorigin += Xvertical;
2418 /* The vertical size is interesting... we need to compare
2419 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with
2420 ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2421 ** in order to start even thinking about Ylegend or Ywatermark.
2423 ** Do it in three portions: First calculate the inner part,
2424 ** then do the legend, then adjust the total height of the img,
2425 ** adding space for a watermark if one exists;
2428 /* reserve space for main and/or pie */
2430 im->yimg = Ymain + Yxlabel;
2432 #ifdef WITH_PIECHART
2433 if (im->yimg < Ypie) im->yimg = Ypie;
2436 im->yorigin = im->yimg - Yxlabel;
2438 /* reserve space for the title *or* some padding above the graph */
2441 im->yorigin += Ytitle;
2443 im->yimg += 1.5*Yspacing;
2444 im->yorigin += 1.5*Yspacing;
2446 /* reserve space for padding below the graph */
2447 im->yimg += Yspacing;
2449 /* Determine where to place the legends onto the image.
2450 ** Adjust im->yimg to match the space requirements.
2452 if(leg_place(im)==-1)
2455 if (im->watermark[0] != '\0') {
2456 im->yimg += Ywatermark;
2460 if (Xlegend > im->ximg) {
2462 /* reposition Pie */
2466 #ifdef WITH_PIECHART
2467 /* The pie is placed in the upper right hand corner,
2468 ** just below the title (if any) and with sufficient
2472 im->pie_x = im->ximg - Xspacing - Xpie/2;
2473 im->pie_y = im->yorigin-Ymain+Ypie/2;
2475 im->pie_x = im->ximg/2;
2476 im->pie_y = im->yorigin-Ypie/2;
2484 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
2485 /* yes we are loosing precision by doing tos with floats instead of doubles
2486 but it seems more stable this way. */
2488 static int AlmostEqual2sComplement (float A, float B, int maxUlps)
2491 int aInt = *(int*)&A;
2492 int bInt = *(int*)&B;
2494 /* Make sure maxUlps is non-negative and small enough that the
2495 default NAN won't compare as equal to anything. */
2497 /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
2499 /* Make aInt lexicographically ordered as a twos-complement int */
2502 aInt = 0x80000000l - aInt;
2504 /* Make bInt lexicographically ordered as a twos-complement int */
2507 bInt = 0x80000000l - bInt;
2509 intDiff = abs(aInt - bInt);
2511 if (intDiff <= maxUlps)
2517 /* draw that picture thing ... */
2519 graph_paint(image_desc_t *im, char ***calcpr)
2522 int lazy = lazy_check(im);
2523 #ifdef WITH_PIECHART
2525 double PieStart=0.0;
2530 double areazero = 0.0;
2531 graph_desc_t *lastgdes = NULL;
2533 /* if we are lazy and there is nothing to PRINT ... quit now */
2534 if (lazy && im->prt_c==0) return 0;
2536 /* pull the data from the rrd files ... */
2538 if(data_fetch(im)==-1)
2541 /* evaluate VDEF and CDEF operations ... */
2542 if(data_calc(im)==-1)
2545 #ifdef WITH_PIECHART
2546 /* check if we need to draw a piechart */
2547 for(i=0;i<im->gdes_c;i++){
2548 if (im->gdes[i].gf == GF_PART) {
2555 /* calculate and PRINT and GPRINT definitions. We have to do it at
2556 * this point because it will affect the length of the legends
2557 * if there are no graph elements we stop here ...
2558 * if we are lazy, try to quit ...
2560 i=print_calc(im,calcpr);
2563 #ifdef WITH_PIECHART
2566 ) || lazy) return 0;
2568 #ifdef WITH_PIECHART
2569 /* If there's only the pie chart to draw, signal this */
2570 if (i==0) piechart=2;
2573 /* get actual drawing data and find min and max values*/
2574 if(data_proc(im)==-1)
2577 if(!im->logarithmic){si_unit(im);} /* identify si magnitude Kilo, Mega Giga ? */
2579 if(!im->rigid && ! im->logarithmic)
2580 expand_range(im); /* make sure the upper and lower limit are
2583 if (!calc_horizontal_grid(im))
2590 /**************************************************************
2591 *** Calculating sizes and locations became a bit confusing ***
2592 *** so I moved this into a separate function. ***
2593 **************************************************************/
2594 if(graph_size_location(im,i
2595 #ifdef WITH_PIECHART
2601 /* the actual graph is created by going through the individual
2602 graph elements and then drawing them */
2604 node=gfx_new_area ( im->canvas,
2608 im->graph_col[GRC_BACK]);
2610 gfx_add_point(node,im->ximg, 0);
2612 #ifdef WITH_PIECHART
2613 if (piechart != 2) {
2615 node=gfx_new_area ( im->canvas,
2616 im->xorigin, im->yorigin,
2617 im->xorigin + im->xsize, im->yorigin,
2618 im->xorigin + im->xsize, im->yorigin-im->ysize,
2619 im->graph_col[GRC_CANVAS]);
2621 gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
2623 if (im->minval > 0.0)
2624 areazero = im->minval;
2625 if (im->maxval < 0.0)
2626 areazero = im->maxval;
2627 #ifdef WITH_PIECHART
2631 #ifdef WITH_PIECHART
2633 pie_part(im,im->graph_col[GRC_CANVAS],im->pie_x,im->pie_y,im->piesize*0.5,0,2*M_PI);
2637 for(i=0;i<im->gdes_c;i++){
2638 switch(im->gdes[i].gf){
2651 for (ii = 0; ii < im->xsize; ii++)
2653 if (!isnan(im->gdes[i].p_data[ii]) &&
2654 im->gdes[i].p_data[ii] != 0.0)
2656 if (im -> gdes[i].yrule > 0 ) {
2657 gfx_new_line(im->canvas,
2658 im -> xorigin + ii, im->yorigin,
2659 im -> xorigin + ii, im->yorigin - im -> gdes[i].yrule * im -> ysize,
2661 im -> gdes[i].col );
2662 } else if ( im -> gdes[i].yrule < 0 ) {
2663 gfx_new_line(im->canvas,
2664 im -> xorigin + ii, im->yorigin - im -> ysize,
2665 im -> xorigin + ii, im->yorigin - ( 1 - im -> gdes[i].yrule ) * im -> ysize,
2667 im -> gdes[i].col );
2675 /* fix data points at oo and -oo */
2676 for(ii=0;ii<im->xsize;ii++){
2677 if (isinf(im->gdes[i].p_data[ii])){
2678 if (im->gdes[i].p_data[ii] > 0) {
2679 im->gdes[i].p_data[ii] = im->maxval ;
2681 im->gdes[i].p_data[ii] = im->minval ;
2687 /* *******************************************************
2692 -------|--t-1--t--------------------------------
2694 if we know the value at time t was a then
2695 we draw a square from t-1 to t with the value a.
2697 ********************************************************* */
2698 if (im->gdes[i].col != 0x0){
2699 /* GF_LINE and friend */
2700 if(im->gdes[i].gf == GF_LINE ){
2703 for(ii=1;ii<im->xsize;ii++){
2704 if (isnan(im->gdes[i].p_data[ii]) || (im->slopemode==1 && isnan(im->gdes[i].p_data[ii-1]))){
2708 if ( node == NULL ) {
2709 last_y = ytr(im,im->gdes[i].p_data[ii]);
2710 if ( im->slopemode == 0 ){
2711 node = gfx_new_line(im->canvas,
2712 ii-1+im->xorigin,last_y,
2713 ii+im->xorigin,last_y,
2714 im->gdes[i].linewidth,
2717 node = gfx_new_line(im->canvas,
2718 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2719 ii+im->xorigin,last_y,
2720 im->gdes[i].linewidth,
2724 double new_y = ytr(im,im->gdes[i].p_data[ii]);
2725 if ( im->slopemode==0 && ! AlmostEqual2sComplement(new_y,last_y,4)){
2726 gfx_add_point(node,ii-1+im->xorigin,new_y);
2729 gfx_add_point(node,ii+im->xorigin,new_y);
2735 double *foreY=malloc(sizeof(double)*im->xsize*2);
2736 double *foreX=malloc(sizeof(double)*im->xsize*2);
2737 double *backY=malloc(sizeof(double)*im->xsize*2);
2738 double *backX=malloc(sizeof(double)*im->xsize*2);
2740 for(ii=0;ii<=im->xsize;ii++){
2742 if ( idxI > 0 && ( drawem != 0 || ii==im->xsize)){
2745 while (cntI < idxI && AlmostEqual2sComplement(foreY[lastI],foreY[cntI],4) && AlmostEqual2sComplement(foreY[lastI],foreY[cntI+1],4)){cntI++;}
2746 node = gfx_new_area(im->canvas,
2749 foreX[cntI],foreY[cntI], im->gdes[i].col);
2750 while (cntI < idxI) {
2753 while ( cntI < idxI && AlmostEqual2sComplement(foreY[lastI],foreY[cntI],4) && AlmostEqual2sComplement(foreY[lastI],foreY[cntI+1],4)){cntI++;}
2754 gfx_add_point(node,foreX[cntI],foreY[cntI]);
2756 gfx_add_point(node,backX[idxI],backY[idxI]);
2760 while ( idxI > 1 && AlmostEqual2sComplement(backY[lastI], backY[idxI],4) && AlmostEqual2sComplement(backY[lastI],backY[idxI-1],4)){idxI--;}
2761 gfx_add_point(node,backX[idxI],backY[idxI]);
2770 if (ii == im->xsize) break;
2772 /* keep things simple for now, just draw these bars
2773 do not try to build a big and complex area */
2776 if ( im->slopemode == 0 && ii==0){
2779 if ( isnan(im->gdes[i].p_data[ii]) ) {
2783 ytop = ytr(im,im->gdes[i].p_data[ii]);
2784 if ( lastgdes && im->gdes[i].stack ) {
2785 ybase = ytr(im,lastgdes->p_data[ii]);
2787 ybase = ytr(im,areazero);
2789 if ( ybase == ytop ){
2793 /* every area has to be wound clock-wise,
2794 so we have to make sur base remains base */
2796 double extra = ytop;
2800 if ( im->slopemode == 0 ){
2801 backY[++idxI] = ybase-0.2;
2802 backX[idxI] = ii+im->xorigin-1;
2803 foreY[idxI] = ytop+0.2;
2804 foreX[idxI] = ii+im->xorigin-1;
2806 backY[++idxI] = ybase-0.2;
2807 backX[idxI] = ii+im->xorigin;
2808 foreY[idxI] = ytop+0.2;
2809 foreX[idxI] = ii+im->xorigin;
2811 /* close up any remaining area */
2816 } /* else GF_LINE */
2817 } /* if color != 0x0 */
2818 /* make sure we do not run into trouble when stacking on NaN */
2819 for(ii=0;ii<im->xsize;ii++){
2820 if (isnan(im->gdes[i].p_data[ii])) {
2821 if (lastgdes && (im->gdes[i].stack)) {
2822 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
2824 im->gdes[i].p_data[ii] = areazero;
2828 lastgdes = &(im->gdes[i]);
2830 #ifdef WITH_PIECHART
2832 if(isnan(im->gdes[i].yrule)) /* fetch variable */
2833 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2835 if (finite(im->gdes[i].yrule)) { /* even the fetched var can be NaN */
2836 pie_part(im,im->gdes[i].col,
2837 im->pie_x,im->pie_y,im->piesize*0.4,
2838 M_PI*2.0*PieStart/100.0,
2839 M_PI*2.0*(PieStart+im->gdes[i].yrule)/100.0);
2840 PieStart += im->gdes[i].yrule;
2845 rrd_set_error("STACK should already be turned into LINE or AREA here");
2851 #ifdef WITH_PIECHART
2859 /* grid_paint also does the text */
2860 if( !(im->extra_flags & ONLY_GRAPH) )
2864 if( !(im->extra_flags & ONLY_GRAPH) )
2867 /* the RULES are the last thing to paint ... */
2868 for(i=0;i<im->gdes_c;i++){
2870 switch(im->gdes[i].gf){
2872 if(im->gdes[i].yrule >= im->minval
2873 && im->gdes[i].yrule <= im->maxval)
2874 gfx_new_line(im->canvas,
2875 im->xorigin,ytr(im,im->gdes[i].yrule),
2876 im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2877 1.0,im->gdes[i].col);
2880 if(im->gdes[i].xrule >= im->start
2881 && im->gdes[i].xrule <= im->end)
2882 gfx_new_line(im->canvas,
2883 xtr(im,im->gdes[i].xrule),im->yorigin,
2884 xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2885 1.0,im->gdes[i].col);
2893 if (strcmp(im->graphfile,"-")==0) {
2894 fo = im->graphhandle ? im->graphhandle : stdout;
2895 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
2896 /* Change translation mode for stdout to BINARY */
2897 _setmode( _fileno( fo ), O_BINARY );
2900 if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2901 rrd_set_error("Opening '%s' for write: %s",im->graphfile,
2902 rrd_strerror(errno));
2906 gfx_render (im->canvas,im->ximg,im->yimg,0x00000000,fo);
2907 if (strcmp(im->graphfile,"-") != 0)
2913 /*****************************************************
2915 *****************************************************/
2918 gdes_alloc(image_desc_t *im){
2921 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2922 * sizeof(graph_desc_t)))==NULL){
2923 rrd_set_error("realloc graph_descs");
2928 im->gdes[im->gdes_c-1].step=im->step;
2929 im->gdes[im->gdes_c-1].step_orig=im->step;
2930 im->gdes[im->gdes_c-1].stack=0;
2931 im->gdes[im->gdes_c-1].linewidth=0;
2932 im->gdes[im->gdes_c-1].debug=0;
2933 im->gdes[im->gdes_c-1].start=im->start;
2934 im->gdes[im->gdes_c-1].start_orig=im->start;
2935 im->gdes[im->gdes_c-1].end=im->end;
2936 im->gdes[im->gdes_c-1].end_orig=im->end;
2937 im->gdes[im->gdes_c-1].vname[0]='\0';
2938 im->gdes[im->gdes_c-1].data=NULL;
2939 im->gdes[im->gdes_c-1].ds_namv=NULL;
2940 im->gdes[im->gdes_c-1].data_first=0;
2941 im->gdes[im->gdes_c-1].p_data=NULL;
2942 im->gdes[im->gdes_c-1].rpnp=NULL;
2943 im->gdes[im->gdes_c-1].shift=0;
2944 im->gdes[im->gdes_c-1].col = 0x0;
2945 im->gdes[im->gdes_c-1].legend[0]='\0';
2946 im->gdes[im->gdes_c-1].format[0]='\0';
2947 im->gdes[im->gdes_c-1].strftm=0;
2948 im->gdes[im->gdes_c-1].rrd[0]='\0';
2949 im->gdes[im->gdes_c-1].ds=-1;
2950 im->gdes[im->gdes_c-1].cf_reduce=CF_AVERAGE;
2951 im->gdes[im->gdes_c-1].cf=CF_AVERAGE;
2952 im->gdes[im->gdes_c-1].p_data=NULL;
2953 im->gdes[im->gdes_c-1].yrule=DNAN;
2954 im->gdes[im->gdes_c-1].xrule=0;
2958 /* copies input untill the first unescaped colon is found
2959 or until input ends. backslashes have to be escaped as well */
2961 scan_for_col(const char *const input, int len, char *const output)
2966 input[inp] != ':' &&
2969 if (input[inp] == '\\' &&
2970 input[inp+1] != '\0' &&
2971 (input[inp+1] == '\\' ||
2972 input[inp+1] == ':')){
2973 output[outp++] = input[++inp];
2976 output[outp++] = input[inp];
2979 output[outp] = '\0';
2982 /* Some surgery done on this function, it became ridiculously big.
2984 ** - initializing now in rrd_graph_init()
2985 ** - options parsing now in rrd_graph_options()
2986 ** - script parsing now in rrd_graph_script()
2989 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize, FILE *stream, double *ymin, double *ymax)
2992 rrd_graph_init(&im);
2993 im.graphhandle = stream;
2995 rrd_graph_options(argc,argv,&im);
2996 if (rrd_test_error()) {
3001 if (strlen(argv[optind])>=MAXPATH) {
3002 rrd_set_error("filename (including path) too long");
3006 strncpy(im.graphfile,argv[optind],MAXPATH-1);
3007 im.graphfile[MAXPATH-1]='\0';
3009 rrd_graph_script(argc,argv,&im,1);
3010 if (rrd_test_error()) {
3015 /* Everything is now read and the actual work can start */
3018 if (graph_paint(&im,prdata)==-1){
3023 /* The image is generated and needs to be output.
3024 ** Also, if needed, print a line with information about the image.
3034 /* maybe prdata is not allocated yet ... lets do it now */
3035 if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
3036 rrd_set_error("malloc imginfo");
3040 if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
3042 rrd_set_error("malloc imginfo");
3045 filename=im.graphfile+strlen(im.graphfile);
3046 while(filename > im.graphfile) {
3047 if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
3051 sprintf((*prdata)[0],im.imginfo,filename,(long)(im.canvas->zoom*im.ximg),(long)(im.canvas->zoom*im.yimg));
3058 rrd_graph_init(image_desc_t *im)
3065 #ifdef HAVE_SETLOCALE
3066 setlocale(LC_TIME,"");
3067 #ifdef HAVE_MBSTOWCS
3068 setlocale(LC_CTYPE,"");
3074 im->xlab_user.minsec = -1;
3080 im->ylegend[0] = '\0';
3081 im->title[0] = '\0';
3082 im->watermark[0] = '\0';
3085 im->unitsexponent= 9999;
3088 im->viewfactor = 1.0;
3095 im->logarithmic = 0;
3096 im->ygridstep = DNAN;
3097 im->draw_x_grid = 1;
3098 im->draw_y_grid = 1;
3103 im->canvas = gfx_new_canvas();
3104 im->grid_dash_on = 1;
3105 im->grid_dash_off = 1;
3106 im->tabwidth = 40.0;
3108 for(i=0;i<DIM(graph_col);i++)
3109 im->graph_col[i]=graph_col[i];
3111 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3114 char rrd_win_default_font[1000];
3115 windir = getenv("windir");
3116 /* %windir% is something like D:\windows or C:\winnt */
3117 if (windir != NULL) {
3118 strncpy(rrd_win_default_font,windir,500);
3119 rrd_win_default_font[500] = '\0';
3120 strcat(rrd_win_default_font,"\\fonts\\");
3121 strcat(rrd_win_default_font,RRD_DEFAULT_FONT);
3122 for(i=0;i<DIM(text_prop);i++){
3123 strncpy(text_prop[i].font,rrd_win_default_font,sizeof(text_prop[i].font)-1);
3124 text_prop[i].font[sizeof(text_prop[i].font)-1] = '\0';
3131 deffont = getenv("RRD_DEFAULT_FONT");
3132 if (deffont != NULL) {
3133 for(i=0;i<DIM(text_prop);i++){
3134 strncpy(text_prop[i].font,deffont,sizeof(text_prop[i].font)-1);
3135 text_prop[i].font[sizeof(text_prop[i].font)-1] = '\0';
3139 for(i=0;i<DIM(text_prop);i++){
3140 im->text_prop[i].size = text_prop[i].size;
3141 strcpy(im->text_prop[i].font,text_prop[i].font);
3146 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
3149 char *parsetime_error = NULL;
3150 char scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
3151 time_t start_tmp=0,end_tmp=0;
3153 struct rrd_time_value start_tv, end_tv;
3155 optind = 0; opterr = 0; /* initialize getopt */
3157 parsetime("end-24h", &start_tv);
3158 parsetime("now", &end_tv);
3160 /* defines for long options without a short equivalent. should be bytes,
3161 and may not collide with (the ASCII value of) short options */
3162 #define LONGOPT_UNITS_SI 255
3165 static struct option long_options[] =
3167 {"start", required_argument, 0, 's'},
3168 {"end", required_argument, 0, 'e'},
3169 {"x-grid", required_argument, 0, 'x'},
3170 {"y-grid", required_argument, 0, 'y'},
3171 {"vertical-label",required_argument,0,'v'},
3172 {"width", required_argument, 0, 'w'},
3173 {"height", required_argument, 0, 'h'},
3174 {"interlaced", no_argument, 0, 'i'},
3175 {"upper-limit",required_argument, 0, 'u'},
3176 {"lower-limit",required_argument, 0, 'l'},
3177 {"rigid", no_argument, 0, 'r'},
3178 {"base", required_argument, 0, 'b'},
3179 {"logarithmic",no_argument, 0, 'o'},
3180 {"color", required_argument, 0, 'c'},
3181 {"font", required_argument, 0, 'n'},
3182 {"title", required_argument, 0, 't'},
3183 {"imginfo", required_argument, 0, 'f'},
3184 {"imgformat", required_argument, 0, 'a'},
3185 {"lazy", no_argument, 0, 'z'},
3186 {"zoom", required_argument, 0, 'm'},
3187 {"no-legend", no_argument, 0, 'g'},
3188 {"force-rules-legend",no_argument,0, 'F'},
3189 {"only-graph", no_argument, 0, 'j'},
3190 {"alt-y-grid", no_argument, 0, 'Y'},
3191 {"no-minor", no_argument, 0, 'I'},
3192 {"slope-mode", no_argument, 0, 'E'},
3193 {"alt-autoscale", no_argument, 0, 'A'},
3194 {"alt-autoscale-max", no_argument, 0, 'M'},
3195 {"no-gridfit", no_argument, 0, 'N'},
3196 {"units-exponent",required_argument, 0, 'X'},
3197 {"units-length",required_argument, 0, 'L'},
3198 {"units", required_argument, 0, LONGOPT_UNITS_SI },
3199 {"step", required_argument, 0, 'S'},
3200 {"tabwidth", required_argument, 0, 'T'},
3201 {"font-render-mode", required_argument, 0, 'R'},
3202 {"font-smoothing-threshold", required_argument, 0, 'B'},
3203 {"watermark", required_argument, 0, 'W'},
3204 {"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 */
3206 int option_index = 0;
3208 int col_start,col_end;
3210 opt = getopt_long(argc, argv,
3211 "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:I:zgjFYAMEX:L:S:T:NR:B:W:",
3212 long_options, &option_index);
3219 im->extra_flags |= NOMINOR;
3222 im->extra_flags |= ALTYGRID;
3225 im->extra_flags |= ALTAUTOSCALE;
3228 im->extra_flags |= ALTAUTOSCALE_MAX;
3231 im->extra_flags |= ONLY_GRAPH;
3234 im->extra_flags |= NOLEGEND;
3237 im->extra_flags |= FORCE_RULES_LEGEND;
3239 case LONGOPT_UNITS_SI:
3240 if(im->extra_flags & FORCE_UNITS) {
3241 rrd_set_error("--units can only be used once!");
3244 if(strcmp(optarg,"si")==0)
3245 im->extra_flags |= FORCE_UNITS_SI;
3247 rrd_set_error("invalid argument for --units: %s", optarg );
3252 im->unitsexponent = atoi(optarg);
3255 im->unitslength = atoi(optarg);
3258 im->tabwidth = atof(optarg);
3261 im->step = atoi(optarg);
3267 if ((parsetime_error = parsetime(optarg, &start_tv))) {
3268 rrd_set_error( "start time: %s", parsetime_error );
3273 if ((parsetime_error = parsetime(optarg, &end_tv))) {
3274 rrd_set_error( "end time: %s", parsetime_error );
3279 if(strcmp(optarg,"none") == 0){
3285 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3287 &im->xlab_user.gridst,
3289 &im->xlab_user.mgridst,
3291 &im->xlab_user.labst,
3292 &im->xlab_user.precis,
3293 &stroff) == 7 && stroff != 0){
3294 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
3295 im->xlab_form[sizeof(im->xlab_form)-1] = '\0';
3296 if((int)(im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
3297 rrd_set_error("unknown keyword %s",scan_gtm);
3299 } else if ((int)(im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
3300 rrd_set_error("unknown keyword %s",scan_mtm);
3302 } else if ((int)(im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
3303 rrd_set_error("unknown keyword %s",scan_ltm);
3306 im->xlab_user.minsec = 1;
3307 im->xlab_user.stst = im->xlab_form;
3309 rrd_set_error("invalid x-grid format");
3315 if(strcmp(optarg,"none") == 0){
3323 &im->ylabfact) == 2) {
3324 if(im->ygridstep<=0){
3325 rrd_set_error("grid step must be > 0");
3327 } else if (im->ylabfact < 1){
3328 rrd_set_error("label factor must be > 0");
3332 rrd_set_error("invalid y-grid format");
3337 strncpy(im->ylegend,optarg,150);
3338 im->ylegend[150]='\0';
3341 im->maxval = atof(optarg);
3344 im->minval = atof(optarg);
3347 im->base = atol(optarg);
3348 if(im->base != 1024 && im->base != 1000 ){
3349 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
3354 long_tmp = atol(optarg);
3355 if (long_tmp < 10) {
3356 rrd_set_error("width below 10 pixels");
3359 im->xsize = long_tmp;
3362 long_tmp = atol(optarg);
3363 if (long_tmp < 10) {
3364 rrd_set_error("height below 10 pixels");
3367 im->ysize = long_tmp;
3370 im->canvas->interlaced = 1;
3376 im->imginfo = optarg;
3379 if((int)(im->canvas->imgformat = if_conv(optarg)) == -1) {
3380 rrd_set_error("unsupported graphics format '%s'",optarg);
3392 im->logarithmic = 1;
3396 "%10[A-Z]#%n%8lx%n",
3397 col_nam,&col_start,&color,&col_end) == 2){
3399 int col_len = col_end - col_start;
3403 ((color & 0xF00) * 0x110000) |
3404 ((color & 0x0F0) * 0x011000) |
3405 ((color & 0x00F) * 0x001100) |
3411 ((color & 0xF000) * 0x11000) |
3412 ((color & 0x0F00) * 0x01100) |
3413 ((color & 0x00F0) * 0x00110) |
3414 ((color & 0x000F) * 0x00011)
3418 color = (color << 8) + 0xff /* shift left by 8 */;
3423 rrd_set_error("the color format is #RRGGBB[AA]");
3426 if((ci=grc_conv(col_nam)) != -1){
3427 im->graph_col[ci]=color;
3429 rrd_set_error("invalid color name '%s'",col_nam);
3433 rrd_set_error("invalid color def format");
3440 char font[1024] = "";
3443 "%10[A-Z]:%lf:%1000s",
3444 prop,&size,font) >= 2){
3446 if((sindex=text_prop_conv(prop)) != -1){
3447 for (propidx=sindex;propidx<TEXT_PROP_LAST;propidx++){
3449 im->text_prop[propidx].size=size;
3451 if (strlen(font) > 0){
3452 strcpy(im->text_prop[propidx].font,font);
3454 if (propidx==sindex && sindex != 0) break;
3457 rrd_set_error("invalid fonttag '%s'",prop);
3461 rrd_set_error("invalid text property format");
3467 im->canvas->zoom = atof(optarg);
3468 if (im->canvas->zoom <= 0.0) {
3469 rrd_set_error("zoom factor must be > 0");
3474 strncpy(im->title,optarg,150);
3475 im->title[150]='\0';
3479 if ( strcmp( optarg, "normal" ) == 0 )
3480 im->canvas->aa_type = AA_NORMAL;
3481 else if ( strcmp( optarg, "light" ) == 0 )
3482 im->canvas->aa_type = AA_LIGHT;
3483 else if ( strcmp( optarg, "mono" ) == 0 )
3484 im->canvas->aa_type = AA_NONE;
3487 rrd_set_error("unknown font-render-mode '%s'", optarg );
3493 im->canvas->font_aa_threshold = atof(optarg);
3497 strncpy(im->watermark,optarg,100);
3498 im->watermark[99]='\0';
3503 rrd_set_error("unknown option '%c'", optopt);
3505 rrd_set_error("unknown option '%s'",argv[optind-1]);
3510 if (optind >= argc) {
3511 rrd_set_error("missing filename");
3515 if (im->logarithmic == 1 && im->minval <= 0){
3516 rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");
3520 if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
3521 /* error string is set in parsetime.c */
3525 if (start_tmp < 3600*24*365*10){
3526 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
3530 if (end_tmp < start_tmp) {
3531 rrd_set_error("start (%ld) should be less than end (%ld)",
3532 start_tmp, end_tmp);
3536 im->start = start_tmp;
3538 im->step = max((long)im->step, (im->end-im->start)/im->xsize);
3542 rrd_graph_check_vname(image_desc_t *im, char *varname, char *err)
3544 if ((im->gdes[im->gdes_c-1].vidx=find_var(im,varname))==-1) {
3545 rrd_set_error("Unknown variable '%s' in %s",varname,err);
3551 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
3554 graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
3556 color=strstr(var,"#");
3559 rrd_set_error("Found no color in %s",err);
3568 rest=strstr(color,":");
3576 sscanf(color,"#%6lx%n",&col,&n);
3577 col = (col << 8) + 0xff /* shift left by 8 */;
3578 if (n!=7) rrd_set_error("Color problem in %s",err);
3581 sscanf(color,"#%8lx%n",&col,&n);
3584 rrd_set_error("Color problem in %s",err);
3586 if (rrd_test_error()) return 0;
3593 int bad_format(char *fmt) {
3597 while (*ptr != '\0')
3598 if (*ptr++ == '%') {
3600 /* line cannot end with percent char */
3601 if (*ptr == '\0') return 1;
3603 /* '%s', '%S' and '%%' are allowed */
3604 if (*ptr == 's' || *ptr == 'S' || *ptr == '%') ptr++;
3606 /* %c is allowed (but use only with vdef!) */
3607 else if (*ptr == 'c') {
3612 /* or else '% 6.2lf' and such are allowed */
3614 /* optional padding character */
3615 if (*ptr == ' ' || *ptr == '+' || *ptr == '-') ptr++;
3617 /* This should take care of 'm.n' with all three optional */
3618 while (*ptr >= '0' && *ptr <= '9') ptr++;
3619 if (*ptr == '.') ptr++;
3620 while (*ptr >= '0' && *ptr <= '9') ptr++;
3622 /* Either 'le', 'lf' or 'lg' must follow here */
3623 if (*ptr++ != 'l') return 1;
3624 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g') ptr++;
3635 vdef_parse(gdes,str)
3636 struct graph_desc_t *gdes;
3637 const char *const str;
3639 /* A VDEF currently is either "func" or "param,func"
3640 * so the parsing is rather simple. Change if needed.
3647 sscanf(str,"%le,%29[A-Z]%n",¶m,func,&n);
3648 if (n== (int)strlen(str)) { /* matched */
3652 sscanf(str,"%29[A-Z]%n",func,&n);
3653 if (n== (int)strlen(str)) { /* matched */
3656 rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3663 if (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3664 else if (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3665 else if (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3666 else if (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3667 else if (!strcmp("TOTAL", func)) gdes->vf.op = VDEF_TOTAL;
3668 else if (!strcmp("FIRST", func)) gdes->vf.op = VDEF_FIRST;
3669 else if (!strcmp("LAST", func)) gdes->vf.op = VDEF_LAST;
3670 else if (!strcmp("LSLSLOPE", func)) gdes->vf.op = VDEF_LSLSLOPE;
3671 else if (!strcmp("LSLINT", func)) gdes->vf.op = VDEF_LSLINT;
3672 else if (!strcmp("LSLCORREL",func)) gdes->vf.op = VDEF_LSLCORREL;
3674 rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3681 switch (gdes->vf.op) {
3683 if (isnan(param)) { /* no parameter given */
3684 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3690 if (param>=0.0 && param<=100.0) {
3691 gdes->vf.param = param;
3692 gdes->vf.val = DNAN; /* undefined */
3693 gdes->vf.when = 0; /* undefined */
3695 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3710 case VDEF_LSLCORREL:
3712 gdes->vf.param = DNAN;
3713 gdes->vf.val = DNAN;
3716 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3733 graph_desc_t *src,*dst;
3737 dst = &im->gdes[gdi];
3738 src = &im->gdes[dst->vidx];
3739 data = src->data + src->ds;
3740 steps = (src->end - src->start) / src->step;
3743 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3750 switch (dst->vf.op) {
3751 case VDEF_PERCENT: {
3752 rrd_value_t * array;
3756 if ((array = malloc(steps*sizeof(double)))==NULL) {
3757 rrd_set_error("malloc VDEV_PERCENT");
3760 for (step=0;step < steps; step++) {
3761 array[step]=data[step*src->ds_cnt];
3763 qsort(array,step,sizeof(double),vdef_percent_compar);
3765 field = (steps-1)*dst->vf.param/100;
3766 dst->vf.val = array[field];
3767 dst->vf.when = 0; /* no time component */
3770 for(step=0;step<steps;step++)
3771 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3777 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3778 if (step == steps) {
3782 dst->vf.val = data[step*src->ds_cnt];
3783 dst->vf.when = src->start + (step+1)*src->step;
3785 while (step != steps) {
3786 if (finite(data[step*src->ds_cnt])) {
3787 if (data[step*src->ds_cnt] > dst->vf.val) {
3788 dst->vf.val = data[step*src->ds_cnt];
3789 dst->vf.when = src->start + (step+1)*src->step;
3796 case VDEF_AVERAGE: {
3799 for (step=0;step<steps;step++) {
3800 if (finite(data[step*src->ds_cnt])) {
3801 sum += data[step*src->ds_cnt];
3806 if (dst->vf.op == VDEF_TOTAL) {
3807 dst->vf.val = sum*src->step;
3808 dst->vf.when = 0; /* no time component */
3810 dst->vf.val = sum/cnt;
3811 dst->vf.when = 0; /* no time component */
3821 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3822 if (step == steps) {
3826 dst->vf.val = data[step*src->ds_cnt];
3827 dst->vf.when = src->start + (step+1)*src->step;
3829 while (step != steps) {
3830 if (finite(data[step*src->ds_cnt])) {
3831 if (data[step*src->ds_cnt] < dst->vf.val) {
3832 dst->vf.val = data[step*src->ds_cnt];
3833 dst->vf.when = src->start + (step+1)*src->step;
3840 /* The time value returned here is one step before the
3841 * actual time value. This is the start of the first
3845 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3846 if (step == steps) { /* all entries were NaN */
3850 dst->vf.val = data[step*src->ds_cnt];
3851 dst->vf.when = src->start + step*src->step;
3855 /* The time value returned here is the
3856 * actual time value. This is the end of the last
3860 while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3861 if (step < 0) { /* all entries were NaN */
3865 dst->vf.val = data[step*src->ds_cnt];
3866 dst->vf.when = src->start + (step+1)*src->step;
3871 case VDEF_LSLCORREL:{
3872 /* Bestfit line by linear least squares method */
3875 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl ;
3876 SUMx = 0; SUMy = 0; SUMxy = 0; SUMxx = 0; SUMyy = 0;
3878 for (step=0;step<steps;step++) {
3879 if (finite(data[step*src->ds_cnt])) {
3882 SUMxx += step * step;
3883 SUMxy += step * data[step*src->ds_cnt];
3884 SUMy += data[step*src->ds_cnt];
3885 SUMyy += data[step*src->ds_cnt]*data[step*src->ds_cnt];
3889 slope = ( SUMx*SUMy - cnt*SUMxy ) / ( SUMx*SUMx - cnt*SUMxx );
3890 y_intercept = ( SUMy - slope*SUMx ) / cnt;
3891 correl = (SUMxy - (SUMx*SUMy)/cnt) / sqrt((SUMxx - (SUMx*SUMx)/cnt)*(SUMyy - (SUMy*SUMy)/cnt));
3894 if (dst->vf.op == VDEF_LSLSLOPE) {
3895 dst->vf.val = slope;
3897 } else if (dst->vf.op == VDEF_LSLINT) {
3898 dst->vf.val = y_intercept;
3900 } else if (dst->vf.op == VDEF_LSLCORREL) {
3901 dst->vf.val = correl;
3915 /* NaN < -INF < finite_values < INF */
3917 vdef_percent_compar(a,b)
3920 /* Equality is not returned; this doesn't hurt except
3921 * (maybe) for a little performance.
3924 /* First catch NaN values. They are smallest */
3925 if (isnan( *(double *)a )) return -1;
3926 if (isnan( *(double *)b )) return 1;
3928 /* NaN doesn't reach this part so INF and -INF are extremes.
3929 * The sign from isinf() is compatible with the sign we return
3931 if (isinf( *(double *)a )) return isinf( *(double *)a );
3932 if (isinf( *(double *)b )) return isinf( *(double *)b );
3934 /* If we reach this, both values must be finite */
3935 if ( *(double *)a < *(double *)b ) return -1; else return 1;