1 /****************************************************************************
2 * RRDtool 1.2.14 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 ; /* ft_step will record what we got from fetch */
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;
731 if (ft_step < im->gdes[i].step) {
732 reduce_data(im->gdes[i].cf_reduce,
740 im->gdes[i].step = ft_step;
744 /* lets see if the required data source is really there */
745 for(ii=0;ii<(int)im->gdes[i].ds_cnt;ii++){
746 if(strcmp(im->gdes[i].ds_namv[ii],im->gdes[i].ds_nam) == 0){
749 if (im->gdes[i].ds== -1){
750 rrd_set_error("No DS called '%s' in '%s'",
751 im->gdes[i].ds_nam,im->gdes[i].rrd);
759 /* evaluate the expressions in the CDEF functions */
761 /*************************************************************
763 *************************************************************/
766 find_var_wrapper(void *arg1, char *key)
768 return find_var((image_desc_t *) arg1, key);
771 /* find gdes containing var*/
773 find_var(image_desc_t *im, char *key){
775 for(ii=0;ii<im->gdes_c-1;ii++){
776 if((im->gdes[ii].gf == GF_DEF
777 || im->gdes[ii].gf == GF_VDEF
778 || im->gdes[ii].gf == GF_CDEF)
779 && (strcmp(im->gdes[ii].vname,key) == 0)){
786 /* find the largest common denominator for all the numbers
787 in the 0 terminated num array */
792 for (i=0;num[i+1]!=0;i++){
794 rest=num[i] % num[i+1];
795 num[i]=num[i+1]; num[i+1]=rest;
799 /* return i==0?num[i]:num[i-1]; */
803 /* run the rpn calculator on all the VDEF and CDEF arguments */
805 data_calc( image_desc_t *im){
809 long *steparray, rpi;
814 rpnstack_init(&rpnstack);
816 for (gdi=0;gdi<im->gdes_c;gdi++){
817 /* Look for GF_VDEF and GF_CDEF in the same loop,
818 * so CDEFs can use VDEFs and vice versa
820 switch (im->gdes[gdi].gf) {
824 graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx];
826 /* remove current shift */
827 vdp->start -= vdp->shift;
828 vdp->end -= vdp->shift;
831 if (im->gdes[gdi].shidx >= 0)
832 vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
835 vdp->shift = im->gdes[gdi].shval;
837 /* normalize shift to multiple of consolidated step */
838 vdp->shift = (vdp->shift / (long)vdp->step) * (long)vdp->step;
841 vdp->start += vdp->shift;
842 vdp->end += vdp->shift;
846 /* A VDEF has no DS. This also signals other parts
847 * of rrdtool that this is a VDEF value, not a CDEF.
849 im->gdes[gdi].ds_cnt = 0;
850 if (vdef_calc(im,gdi)) {
851 rrd_set_error("Error processing VDEF '%s'"
854 rpnstack_free(&rpnstack);
859 im->gdes[gdi].ds_cnt = 1;
860 im->gdes[gdi].ds = 0;
861 im->gdes[gdi].data_first = 1;
862 im->gdes[gdi].start = 0;
863 im->gdes[gdi].end = 0;
868 /* Find the variables in the expression.
869 * - VDEF variables are substituted by their values
870 * and the opcode is changed into OP_NUMBER.
871 * - CDEF variables are analized for their step size,
872 * the lowest common denominator of all the step
873 * sizes of the data sources involved is calculated
874 * and the resulting number is the step size for the
875 * resulting data source.
877 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
878 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
879 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER){
880 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
881 if (im->gdes[ptr].ds_cnt == 0) { /* this is a VDEF data source */
883 printf("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
885 im->gdes[ptr].vname);
886 printf("DEBUG: value from vdef is %f\n",im->gdes[ptr].vf.val);
888 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
889 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
890 } else { /* normal variables and PREF(variables) */
892 /* add one entry to the array that keeps track of the step sizes of the
893 * data sources going into the CDEF. */
895 rrd_realloc(steparray,
896 (++stepcnt+1)*sizeof(*steparray)))==NULL){
897 rrd_set_error("realloc steparray");
898 rpnstack_free(&rpnstack);
902 steparray[stepcnt-1] = im->gdes[ptr].step;
904 /* adjust start and end of cdef (gdi) so
905 * that it runs from the latest start point
906 * to the earliest endpoint of any of the
907 * rras involved (ptr)
910 if(im->gdes[gdi].start < im->gdes[ptr].start)
911 im->gdes[gdi].start = im->gdes[ptr].start;
913 if(im->gdes[gdi].end == 0 ||
914 im->gdes[gdi].end > im->gdes[ptr].end)
915 im->gdes[gdi].end = im->gdes[ptr].end;
917 /* store pointer to the first element of
918 * the rra providing data for variable,
919 * further save step size and data source
922 im->gdes[gdi].rpnp[rpi].data = im->gdes[ptr].data + im->gdes[ptr].ds;
923 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
924 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
926 /* backoff the *.data ptr; this is done so
927 * rpncalc() function doesn't have to treat
928 * the first case differently
930 } /* if ds_cnt != 0 */
931 } /* if OP_VARIABLE */
932 } /* loop through all rpi */
934 /* move the data pointers to the correct period */
935 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
936 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
937 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER){
938 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
939 long diff = im->gdes[gdi].start - im->gdes[ptr].start;
942 im->gdes[gdi].rpnp[rpi].data += (diff / im->gdes[ptr].step) * im->gdes[ptr].ds_cnt;
946 if(steparray == NULL){
947 rrd_set_error("rpn expressions without DEF"
948 " or CDEF variables are not supported");
949 rpnstack_free(&rpnstack);
952 steparray[stepcnt]=0;
953 /* Now find the resulting step. All steps in all
954 * used RRAs have to be visited
956 im->gdes[gdi].step = lcd(steparray);
958 if((im->gdes[gdi].data = malloc((
959 (im->gdes[gdi].end-im->gdes[gdi].start)
960 / im->gdes[gdi].step)
961 * sizeof(double)))==NULL){
962 rrd_set_error("malloc im->gdes[gdi].data");
963 rpnstack_free(&rpnstack);
967 /* Step through the new cdef results array and
968 * calculate the values
970 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
971 now<=im->gdes[gdi].end;
972 now += im->gdes[gdi].step)
974 rpnp_t *rpnp = im -> gdes[gdi].rpnp;
976 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
977 * in this case we are advancing by timesteps;
978 * we use the fact that time_t is a synonym for long
980 if (rpn_calc(rpnp,&rpnstack,(long) now,
981 im->gdes[gdi].data,++dataidx) == -1) {
982 /* rpn_calc sets the error string */
983 rpnstack_free(&rpnstack);
986 } /* enumerate over time steps within a CDEF */
991 } /* enumerate over CDEFs */
992 rpnstack_free(&rpnstack);
996 /* massage data so, that we get one value for each x coordinate in the graph */
998 data_proc( image_desc_t *im ){
1000 double pixstep = (double)(im->end-im->start)
1001 /(double)im->xsize; /* how much time
1002 passes in one pixel */
1004 double minval=DNAN,maxval=DNAN;
1006 unsigned long gr_time;
1008 /* memory for the processed data */
1009 for(i=0;i<im->gdes_c;i++) {
1010 if((im->gdes[i].gf==GF_LINE) ||
1011 (im->gdes[i].gf==GF_AREA) ||
1012 (im->gdes[i].gf==GF_TICK)) {
1013 if((im->gdes[i].p_data = malloc((im->xsize +1)
1014 * sizeof(rrd_value_t)))==NULL){
1015 rrd_set_error("malloc data_proc");
1021 for (i=0;i<im->xsize;i++) { /* for each pixel */
1023 gr_time = im->start+pixstep*i; /* time of the current step */
1026 for (ii=0;ii<im->gdes_c;ii++) {
1028 switch (im->gdes[ii].gf) {
1032 if (!im->gdes[ii].stack)
1034 value = im->gdes[ii].yrule;
1035 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1036 /* The time of the data doesn't necessarily match
1037 ** the time of the graph. Beware.
1039 vidx = im->gdes[ii].vidx;
1040 if (im->gdes[vidx].gf == GF_VDEF) {
1041 value = im->gdes[vidx].vf.val;
1042 } else if (((long int)gr_time >= (long int)im->gdes[vidx].start) &&
1043 ((long int)gr_time <= (long int)im->gdes[vidx].end) ) {
1044 value = im->gdes[vidx].data[
1045 (unsigned long) floor(
1046 (double)(gr_time - im->gdes[vidx].start)
1047 / im->gdes[vidx].step)
1048 * im->gdes[vidx].ds_cnt
1056 if (! isnan(value)) {
1058 im->gdes[ii].p_data[i] = paintval;
1059 /* GF_TICK: the data values are not
1060 ** relevant for min and max
1062 if (finite(paintval) && im->gdes[ii].gf != GF_TICK ) {
1063 if (isnan(minval) || paintval < minval)
1065 if (isnan(maxval) || paintval > maxval)
1069 im->gdes[ii].p_data[i] = DNAN;
1073 rrd_set_error("STACK should already be turned into LINE or AREA here");
1082 /* if min or max have not been asigned a value this is because
1083 there was no data in the graph ... this is not good ...
1084 lets set these to dummy values then ... */
1086 if (im->logarithmic) {
1087 if (isnan(minval)) minval = 0.2;
1088 if (isnan(maxval)) maxval = 5.1;
1091 if (isnan(minval)) minval = 0.0;
1092 if (isnan(maxval)) maxval = 1.0;
1095 /* adjust min and max values */
1096 if (isnan(im->minval)
1097 /* don't adjust low-end with log scale */ /* why not? */
1098 || ((!im->rigid) && im->minval > minval)
1100 if (im->logarithmic)
1101 im->minval = minval * 0.5;
1103 im->minval = minval;
1105 if (isnan(im->maxval)
1106 || (!im->rigid && im->maxval < maxval)
1108 if (im->logarithmic)
1109 im->maxval = maxval * 2.0;
1111 im->maxval = maxval;
1113 /* make sure min is smaller than max */
1114 if (im->minval > im->maxval) {
1115 im->minval = 0.99 * im->maxval;
1118 /* make sure min and max are not equal */
1119 if (im->minval == im->maxval) {
1121 if (! im->logarithmic) {
1124 /* make sure min and max are not both zero */
1125 if (im->maxval == 0.0) {
1134 /* identify the point where the first gridline, label ... gets placed */
1138 time_t start, /* what is the initial time */
1139 enum tmt_en baseint, /* what is the basic interval */
1140 long basestep /* how many if these do we jump a time */
1144 localtime_r(&start, &tm);
1147 tm.tm_sec -= tm.tm_sec % basestep; break;
1150 tm.tm_min -= tm.tm_min % basestep;
1155 tm.tm_hour -= tm.tm_hour % basestep; break;
1157 /* we do NOT look at the basestep for this ... */
1160 tm.tm_hour = 0; break;
1162 /* we do NOT look at the basestep for this ... */
1166 tm.tm_mday -= tm.tm_wday -1; /* -1 because we want the monday */
1167 if (tm.tm_wday==0) tm.tm_mday -= 7; /* we want the *previous* monday */
1174 tm.tm_mon -= tm.tm_mon % basestep; break;
1182 tm.tm_year -= (tm.tm_year+1900) % basestep;
1187 /* identify the point where the next gridline, label ... gets placed */
1190 time_t current, /* what is the initial time */
1191 enum tmt_en baseint, /* what is the basic interval */
1192 long basestep /* how many if these do we jump a time */
1197 localtime_r(¤t, &tm);
1201 tm.tm_sec += basestep; break;
1203 tm.tm_min += basestep; break;
1205 tm.tm_hour += basestep; break;
1207 tm.tm_mday += basestep; break;
1209 tm.tm_mday += 7*basestep; break;
1211 tm.tm_mon += basestep; break;
1213 tm.tm_year += basestep;
1215 madetime = mktime(&tm);
1216 } while (madetime == -1); /* this is necessary to skip impssible times
1217 like the daylight saving time skips */
1223 /* calculate values required for PRINT and GPRINT functions */
1226 print_calc(image_desc_t *im, char ***prdata)
1228 long i,ii,validsteps;
1231 int graphelement = 0;
1234 double magfact = -1;
1238 /* wow initializing tmvdef is quite a task :-) */
1239 time_t now = time(NULL);
1240 localtime_r(&now,&tmvdef);
1241 if (im->imginfo) prlines++;
1242 for(i=0;i<im->gdes_c;i++){
1243 switch(im->gdes[i].gf){
1246 if(((*prdata) = rrd_realloc((*prdata),prlines*sizeof(char *)))==NULL){
1247 rrd_set_error("realloc prdata");
1251 /* PRINT and GPRINT can now print VDEF generated values.
1252 * There's no need to do any calculations on them as these
1253 * calculations were already made.
1255 vidx = im->gdes[i].vidx;
1256 if (im->gdes[vidx].gf==GF_VDEF) { /* simply use vals */
1257 printval = im->gdes[vidx].vf.val;
1258 localtime_r(&im->gdes[vidx].vf.when,&tmvdef);
1259 } else { /* need to calculate max,min,avg etcetera */
1260 max_ii =((im->gdes[vidx].end
1261 - im->gdes[vidx].start)
1262 / im->gdes[vidx].step
1263 * im->gdes[vidx].ds_cnt);
1266 for( ii=im->gdes[vidx].ds;
1268 ii+=im->gdes[vidx].ds_cnt){
1269 if (! finite(im->gdes[vidx].data[ii]))
1271 if (isnan(printval)){
1272 printval = im->gdes[vidx].data[ii];
1277 switch (im->gdes[i].cf){
1280 case CF_DEVSEASONAL:
1284 printval += im->gdes[vidx].data[ii];
1287 printval = min( printval, im->gdes[vidx].data[ii]);
1291 printval = max( printval, im->gdes[vidx].data[ii]);
1294 printval = im->gdes[vidx].data[ii];
1297 if (im->gdes[i].cf==CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1298 if (validsteps > 1) {
1299 printval = (printval / validsteps);
1302 } /* prepare printval */
1304 if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) {
1305 /* Magfact is set to -1 upon entry to print_calc. If it
1306 * is still less than 0, then we need to run auto_scale.
1307 * Otherwise, put the value into the correct units. If
1308 * the value is 0, then do not set the symbol or magnification
1309 * so next the calculation will be performed again. */
1310 if (magfact < 0.0) {
1311 auto_scale(im,&printval,&si_symb,&magfact);
1312 if (printval == 0.0)
1315 printval /= magfact;
1317 *(++percent_s) = 's';
1318 } else if (strstr(im->gdes[i].format,"%s") != NULL) {
1319 auto_scale(im,&printval,&si_symb,&magfact);
1322 if (im->gdes[i].gf == GF_PRINT){
1323 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1324 (*prdata)[prlines-1] = NULL;
1325 if (im->gdes[i].strftm){
1326 strftime((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,&tmvdef);
1328 if (bad_format(im->gdes[i].format)) {
1329 rrd_set_error("bad format for PRINT in '%s'", im->gdes[i].format);
1333 #ifdef HAVE_SNPRINTF
1334 snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
1336 sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
1342 if (im->gdes[i].strftm){
1343 strftime(im->gdes[i].legend,FMT_LEG_LEN,im->gdes[i].format,&tmvdef);
1345 if (bad_format(im->gdes[i].format)) {
1346 rrd_set_error("bad format for GPRINT in '%s'", im->gdes[i].format);
1349 #ifdef HAVE_SNPRINTF
1350 snprintf(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
1352 sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
1364 if(isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1365 im->gdes[i].yrule=im->gdes[im->gdes[i].vidx].vf.val;
1370 if(im->gdes[i].xrule == 0) { /* again ... the legend printer needs it*/
1371 im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
1379 #ifdef WITH_PIECHART
1386 rrd_set_error("STACK should already be turned into LINE or AREA here");
1391 return graphelement;
1395 /* place legends with color spots */
1397 leg_place(image_desc_t *im)
1400 int interleg = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1401 int border = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1402 int fill=0, fill_last;
1404 int leg_x = border, leg_y = im->yimg;
1405 int leg_y_prev = im->yimg;
1409 char prt_fctn; /*special printfunctions */
1412 if( !(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH) ) {
1413 if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1414 rrd_set_error("malloc for legspace");
1418 for(i=0;i<im->gdes_c;i++){
1421 /* hid legends for rules which are not displayed */
1423 if(!(im->extra_flags & FORCE_RULES_LEGEND)) {
1424 if (im->gdes[i].gf == GF_HRULE &&
1425 (im->gdes[i].yrule < im->minval || im->gdes[i].yrule > im->maxval))
1426 im->gdes[i].legend[0] = '\0';
1428 if (im->gdes[i].gf == GF_VRULE &&
1429 (im->gdes[i].xrule < im->start || im->gdes[i].xrule > im->end))
1430 im->gdes[i].legend[0] = '\0';
1433 leg_cc = strlen(im->gdes[i].legend);
1435 /* is there a controle code ant the end of the legend string ? */
1436 /* and it is not a tab \\t */
1437 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\' && im->gdes[i].legend[leg_cc-1] != 't') {
1438 prt_fctn = im->gdes[i].legend[leg_cc-1];
1440 im->gdes[i].legend[leg_cc] = '\0';
1444 /* remove exess space */
1445 while (prt_fctn=='g' &&
1447 im->gdes[i].legend[leg_cc-1]==' '){
1449 im->gdes[i].legend[leg_cc]='\0';
1452 legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1455 /* no interleg space if string ends in \g */
1456 fill += legspace[i];
1458 fill += gfx_get_text_width(im->canvas, fill+border,
1459 im->text_prop[TEXT_PROP_LEGEND].font,
1460 im->text_prop[TEXT_PROP_LEGEND].size,
1462 im->gdes[i].legend, 0);
1467 /* who said there was a special tag ... ?*/
1468 if (prt_fctn=='g') {
1471 if (prt_fctn == '\0') {
1472 if (i == im->gdes_c -1 ) prt_fctn ='l';
1474 /* is it time to place the legends ? */
1475 if (fill > im->ximg - 2*border){
1490 if (prt_fctn != '\0'){
1492 if (leg_c >= 2 && prt_fctn == 'j') {
1493 glue = (im->ximg - fill - 2* border) / (leg_c-1);
1497 if (prt_fctn =='c') leg_x = (im->ximg - fill) / 2.0;
1498 if (prt_fctn =='r') leg_x = im->ximg - fill - border;
1500 for(ii=mark;ii<=i;ii++){
1501 if(im->gdes[ii].legend[0]=='\0')
1502 continue; /* skip empty legends */
1503 im->gdes[ii].leg_x = leg_x;
1504 im->gdes[ii].leg_y = leg_y;
1506 gfx_get_text_width(im->canvas, leg_x,
1507 im->text_prop[TEXT_PROP_LEGEND].font,
1508 im->text_prop[TEXT_PROP_LEGEND].size,
1510 im->gdes[ii].legend, 0)
1515 /* only add y space if there was text on the line */
1516 if (leg_x > border || prt_fctn == 's')
1517 leg_y += im->text_prop[TEXT_PROP_LEGEND].size*1.8;
1518 if (prt_fctn == 's')
1519 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1525 im->yimg = leg_y_prev;
1526 /* if we did place some legends we have to add vertical space */
1527 if (leg_y != im->yimg){
1528 im->yimg += im->text_prop[TEXT_PROP_LEGEND].size*1.8;
1535 /* create a grid on the graph. it determines what to do
1536 from the values of xsize, start and end */
1538 /* the xaxis labels are determined from the number of seconds per pixel
1539 in the requested graph */
1544 calc_horizontal_grid(image_desc_t *im)
1550 int decimals, fractionals;
1552 im->ygrid_scale.labfact=2;
1553 range = im->maxval - im->minval;
1554 scaledrange = range / im->magfact;
1556 /* does the scale of this graph make it impossible to put lines
1557 on it? If so, give up. */
1558 if (isnan(scaledrange)) {
1562 /* find grid spaceing */
1564 if(isnan(im->ygridstep)){
1565 if(im->extra_flags & ALTYGRID) {
1566 /* find the value with max number of digits. Get number of digits */
1567 decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))*im->viewfactor/im->magfact));
1568 if(decimals <= 0) /* everything is small. make place for zero */
1571 im->ygrid_scale.gridstep = pow((double)10, floor(log10(range*im->viewfactor/im->magfact)))/im->viewfactor*im->magfact;
1573 if(im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1574 im->ygrid_scale.gridstep = 0.1;
1575 /* should have at least 5 lines but no more then 15 */
1576 if(range/im->ygrid_scale.gridstep < 5)
1577 im->ygrid_scale.gridstep /= 10;
1578 if(range/im->ygrid_scale.gridstep > 15)
1579 im->ygrid_scale.gridstep *= 10;
1580 if(range/im->ygrid_scale.gridstep > 5) {
1581 im->ygrid_scale.labfact = 1;
1582 if(range/im->ygrid_scale.gridstep > 8)
1583 im->ygrid_scale.labfact = 2;
1586 im->ygrid_scale.gridstep /= 5;
1587 im->ygrid_scale.labfact = 5;
1589 fractionals = floor(log10(im->ygrid_scale.gridstep*(double)im->ygrid_scale.labfact*im->viewfactor/im->magfact));
1590 if(fractionals < 0) { /* small amplitude. */
1591 int len = decimals - fractionals + 1;
1592 if (im->unitslength < len+2) im->unitslength = len+2;
1593 sprintf(im->ygrid_scale.labfmt, "%%%d.%df%s", len, -fractionals,(im->symbol != ' ' ? " %c" : ""));
1595 int len = decimals + 1;
1596 if (im->unitslength < len+2) im->unitslength = len+2;
1597 sprintf(im->ygrid_scale.labfmt, "%%%d.0f%s", len, ( im->symbol != ' ' ? " %c" : "" ));
1601 for(i=0;ylab[i].grid > 0;i++){
1602 pixel = im->ysize / (scaledrange / ylab[i].grid);
1609 if (pixel * ylab[gridind].lfac[i] >= 2.5 * im->text_prop[TEXT_PROP_AXIS].size) {
1610 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1615 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1618 im->ygrid_scale.gridstep = im->ygridstep;
1619 im->ygrid_scale.labfact = im->ylabfact;
1624 int draw_horizontal_grid(image_desc_t *im)
1628 char graph_label[100];
1630 double X0=im->xorigin;
1631 double X1=im->xorigin+im->xsize;
1633 int sgrid = (int)( im->minval / im->ygrid_scale.gridstep - 1);
1634 int egrid = (int)( im->maxval / im->ygrid_scale.gridstep + 1);
1636 scaledstep = im->ygrid_scale.gridstep/(double)im->magfact*(double)im->viewfactor;
1637 MaxY = scaledstep*(double)egrid;
1638 for (i = sgrid; i <= egrid; i++){
1639 double Y0=ytr(im,im->ygrid_scale.gridstep*i);
1640 double YN=ytr(im,im->ygrid_scale.gridstep*(i+1));
1641 if ( Y0 >= im->yorigin-im->ysize
1642 && Y0 <= im->yorigin){
1643 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
1644 with the chosen settings. Add a label if required by settings, or if
1645 there is only one label so far and the next grid line is out of bounds. */
1646 if(i % im->ygrid_scale.labfact == 0 || ( nlabels==1 && (YN < im->yorigin-im->ysize || YN > im->yorigin) )){
1647 if (im->symbol == ' ') {
1648 if(im->extra_flags & ALTYGRID) {
1649 sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*(double)i);
1652 sprintf(graph_label,"%4.1f",scaledstep*(double)i);
1654 sprintf(graph_label,"%4.0f",scaledstep*(double)i);
1658 char sisym = ( i == 0 ? ' ' : im->symbol);
1659 if(im->extra_flags & ALTYGRID) {
1660 sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*(double)i,sisym);
1663 sprintf(graph_label,"%4.1f %c",scaledstep*(double)i, sisym);
1665 sprintf(graph_label,"%4.0f %c",scaledstep*(double)i, sisym);
1671 gfx_new_text ( im->canvas,
1672 X0-im->text_prop[TEXT_PROP_AXIS].size, Y0,
1673 im->graph_col[GRC_FONT],
1674 im->text_prop[TEXT_PROP_AXIS].font,
1675 im->text_prop[TEXT_PROP_AXIS].size,
1676 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1678 gfx_new_dashed_line ( im->canvas,
1681 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1682 im->grid_dash_on, im->grid_dash_off);
1684 } else if (!(im->extra_flags & NOMINOR)) {
1685 gfx_new_dashed_line ( im->canvas,
1688 GRIDWIDTH, im->graph_col[GRC_GRID],
1689 im->grid_dash_on, im->grid_dash_off);
1697 /* this is frexp for base 10 */
1698 double frexp10(double, double *);
1699 double frexp10(double x, double *e) {
1703 iexp = floor(log(fabs(x)) / log(10));
1704 mnt = x / pow(10.0, iexp);
1707 mnt = x / pow(10.0, iexp);
1713 /* logaritmic horizontal grid */
1715 horizontal_log_grid(image_desc_t *im)
1717 double yloglab[][10] = {
1718 {1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
1719 {1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
1720 {1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0},
1721 {1.0, 2.0, 4.0, 6.0, 8.0, 10., 0.0, 0.0, 0.0, 0.0},
1722 {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.}};
1724 int i, j, val_exp, min_exp;
1725 double nex; /* number of decades in data */
1726 double logscale; /* scale in logarithmic space */
1727 int exfrac = 1; /* decade spacing */
1728 int mid = -1; /* row in yloglab for major grid */
1729 double mspac; /* smallest major grid spacing (pixels) */
1730 int flab; /* first value in yloglab to use */
1733 char graph_label[100];
1735 nex = log10(im->maxval / im->minval);
1736 logscale = im->ysize / nex;
1738 /* major spacing for data with high dynamic range */
1739 while(logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
1740 if(exfrac == 1) exfrac = 3;
1744 /* major spacing for less dynamic data */
1746 /* search best row in yloglab */
1748 for(i = 0; yloglab[mid][i + 1] < 10.0; i++);
1749 mspac = logscale * log10(10.0 / yloglab[mid][i]);
1750 } while(mspac > 2 * im->text_prop[TEXT_PROP_LEGEND].size && mid < 5);
1753 /* find first value in yloglab */
1754 for(flab = 0; frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
1755 if(yloglab[mid][flab] == 10.0) {
1760 if(val_exp % exfrac) val_exp += abs(-val_exp % exfrac);
1763 X1=im->xorigin+im->xsize;
1767 value = yloglab[mid][flab] * pow(10.0, val_exp);
1769 Y0 = ytr(im, value);
1770 if(Y0 <= im->yorigin - im->ysize) break;
1772 /* major grid line */
1773 gfx_new_dashed_line ( im->canvas,
1776 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1777 im->grid_dash_on, im->grid_dash_off);
1780 if (im->extra_flags & FORCE_UNITS_SI) {
1785 scale = floor(val_exp / 3.0);
1786 if( value >= 1.0 ) pvalue = pow(10.0, val_exp % 3);
1787 else pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
1788 pvalue *= yloglab[mid][flab];
1790 if ( ((scale+si_symbcenter) < (int)sizeof(si_symbol)) &&
1791 ((scale+si_symbcenter) >= 0) )
1792 symbol = si_symbol[scale+si_symbcenter];
1796 sprintf(graph_label,"%3.0f %c", pvalue, symbol);
1798 sprintf(graph_label,"%3.0e", value);
1799 gfx_new_text ( im->canvas,
1800 X0-im->text_prop[TEXT_PROP_AXIS].size, Y0,
1801 im->graph_col[GRC_FONT],
1802 im->text_prop[TEXT_PROP_AXIS].font,
1803 im->text_prop[TEXT_PROP_AXIS].size,
1804 im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1808 if(mid < 4 && exfrac == 1) {
1809 /* find first and last minor line behind current major line
1810 * i is the first line and j tha last */
1812 min_exp = val_exp - 1;
1813 for(i = 1; yloglab[mid][i] < 10.0; i++);
1814 i = yloglab[mid][i - 1] + 1;
1819 i = yloglab[mid][flab - 1] + 1;
1820 j = yloglab[mid][flab];
1823 /* draw minor lines below current major line */
1826 value = i * pow(10.0, min_exp);
1827 if(value < im->minval) continue;
1829 Y0 = ytr(im, value);
1830 if(Y0 <= im->yorigin - im->ysize) break;
1833 gfx_new_dashed_line ( im->canvas,
1836 GRIDWIDTH, im->graph_col[GRC_GRID],
1837 im->grid_dash_on, im->grid_dash_off);
1840 else if(exfrac > 1) {
1841 for(i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
1842 value = pow(10.0, i);
1843 if(value < im->minval) continue;
1845 Y0 = ytr(im, value);
1846 if(Y0 <= im->yorigin - im->ysize) break;
1849 gfx_new_dashed_line ( im->canvas,
1852 GRIDWIDTH, im->graph_col[GRC_GRID],
1853 im->grid_dash_on, im->grid_dash_off);
1858 if(yloglab[mid][++flab] == 10.0) {
1864 /* draw minor lines after highest major line */
1865 if(mid < 4 && exfrac == 1) {
1866 /* find first and last minor line below current major line
1867 * i is the first line and j tha last */
1869 min_exp = val_exp - 1;
1870 for(i = 1; yloglab[mid][i] < 10.0; i++);
1871 i = yloglab[mid][i - 1] + 1;
1876 i = yloglab[mid][flab - 1] + 1;
1877 j = yloglab[mid][flab];
1880 /* draw minor lines below current major line */
1883 value = i * pow(10.0, min_exp);
1884 if(value < im->minval) continue;
1886 Y0 = ytr(im, value);
1887 if(Y0 <= im->yorigin - im->ysize) break;
1890 gfx_new_dashed_line ( im->canvas,
1893 GRIDWIDTH, im->graph_col[GRC_GRID],
1894 im->grid_dash_on, im->grid_dash_off);
1897 /* fancy minor gridlines */
1898 else if(exfrac > 1) {
1899 for(i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
1900 value = pow(10.0, i);
1901 if(value < im->minval) continue;
1903 Y0 = ytr(im, value);
1904 if(Y0 <= im->yorigin - im->ysize) break;
1907 gfx_new_dashed_line ( im->canvas,
1910 GRIDWIDTH, im->graph_col[GRC_GRID],
1911 im->grid_dash_on, im->grid_dash_off);
1923 int xlab_sel; /* which sort of label and grid ? */
1924 time_t ti, tilab, timajor;
1926 char graph_label[100];
1927 double X0,Y0,Y1; /* points for filled graph and more*/
1930 /* the type of time grid is determined by finding
1931 the number of seconds per pixel in the graph */
1934 if(im->xlab_user.minsec == -1){
1935 factor=(im->end - im->start)/im->xsize;
1937 while ( xlab[xlab_sel+1].minsec != -1
1938 && xlab[xlab_sel+1].minsec <= factor) { xlab_sel++; } /* pick the last one */
1939 while ( xlab[xlab_sel-1].minsec == xlab[xlab_sel].minsec
1940 && xlab[xlab_sel].length > (im->end - im->start)) { xlab_sel--; } /* go back to the smallest size */
1941 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
1942 im->xlab_user.gridst = xlab[xlab_sel].gridst;
1943 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
1944 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
1945 im->xlab_user.labtm = xlab[xlab_sel].labtm;
1946 im->xlab_user.labst = xlab[xlab_sel].labst;
1947 im->xlab_user.precis = xlab[xlab_sel].precis;
1948 im->xlab_user.stst = xlab[xlab_sel].stst;
1951 /* y coords are the same for every line ... */
1953 Y1 = im->yorigin-im->ysize;
1956 /* paint the minor grid */
1957 if (!(im->extra_flags & NOMINOR))
1959 for(ti = find_first_time(im->start,
1960 im->xlab_user.gridtm,
1961 im->xlab_user.gridst),
1962 timajor = find_first_time(im->start,
1963 im->xlab_user.mgridtm,
1964 im->xlab_user.mgridst);
1966 ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
1968 /* are we inside the graph ? */
1969 if (ti < im->start || ti > im->end) continue;
1970 while (timajor < ti) {
1971 timajor = find_next_time(timajor,
1972 im->xlab_user.mgridtm, im->xlab_user.mgridst);
1974 if (ti == timajor) continue; /* skip as falls on major grid line */
1976 gfx_new_dashed_line(im->canvas,X0,Y0+1, X0,Y1-1,GRIDWIDTH,
1977 im->graph_col[GRC_GRID],
1978 im->grid_dash_on, im->grid_dash_off);
1983 /* paint the major grid */
1984 for(ti = find_first_time(im->start,
1985 im->xlab_user.mgridtm,
1986 im->xlab_user.mgridst);
1988 ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
1990 /* are we inside the graph ? */
1991 if (ti < im->start || ti > im->end) continue;
1993 gfx_new_dashed_line(im->canvas,X0,Y0+3, X0,Y1-2,MGRIDWIDTH,
1994 im->graph_col[GRC_MGRID],
1995 im->grid_dash_on, im->grid_dash_off);
1998 /* paint the labels below the graph */
1999 for(ti = find_first_time(im->start - im->xlab_user.precis/2,
2000 im->xlab_user.labtm,
2001 im->xlab_user.labst);
2002 ti <= im->end - im->xlab_user.precis/2;
2003 ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
2005 tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
2006 /* are we inside the graph ? */
2007 if (tilab < im->start || tilab > im->end) continue;
2010 localtime_r(&tilab, &tm);
2011 strftime(graph_label,99,im->xlab_user.stst, &tm);
2013 # error "your libc has no strftime I guess we'll abort the exercise here."
2015 gfx_new_text ( im->canvas,
2016 xtr(im,tilab), Y0+im->text_prop[TEXT_PROP_AXIS].size*1.4+5,
2017 im->graph_col[GRC_FONT],
2018 im->text_prop[TEXT_PROP_AXIS].font,
2019 im->text_prop[TEXT_PROP_AXIS].size,
2020 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_BOTTOM,
2033 /* draw x and y axis */
2034 /* gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2035 im->xorigin+im->xsize,im->yorigin-im->ysize,
2036 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2038 gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2039 im->xorigin+im->xsize,im->yorigin-im->ysize,
2040 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2042 gfx_new_line ( im->canvas, im->xorigin-4,im->yorigin,
2043 im->xorigin+im->xsize+4,im->yorigin,
2044 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2046 gfx_new_line ( im->canvas, im->xorigin,im->yorigin+4,
2047 im->xorigin,im->yorigin-im->ysize-4,
2048 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2051 /* arrow for X and Y axis direction */
2052 gfx_new_area ( im->canvas,
2053 im->xorigin+im->xsize+2, im->yorigin-2,
2054 im->xorigin+im->xsize+2, im->yorigin+3,
2055 im->xorigin+im->xsize+7, im->yorigin+0.5, /* LINEOFFSET */
2056 im->graph_col[GRC_ARROW]);
2058 gfx_new_area ( im->canvas,
2059 im->xorigin-2, im->yorigin-im->ysize-2,
2060 im->xorigin+3, im->yorigin-im->ysize-2,
2061 im->xorigin+0.5, im->yorigin-im->ysize-7, /* LINEOFFSET */
2062 im->graph_col[GRC_ARROW]);
2067 grid_paint(image_desc_t *im)
2071 double X0,Y0; /* points for filled graph and more*/
2074 /* draw 3d border */
2075 node = gfx_new_area (im->canvas, 0,im->yimg,
2077 2,2,im->graph_col[GRC_SHADEA]);
2078 gfx_add_point( node , im->ximg - 2, 2 );
2079 gfx_add_point( node , im->ximg, 0 );
2080 gfx_add_point( node , 0,0 );
2081 /* gfx_add_point( node , 0,im->yimg ); */
2083 node = gfx_new_area (im->canvas, 2,im->yimg-2,
2084 im->ximg-2,im->yimg-2,
2086 im->graph_col[GRC_SHADEB]);
2087 gfx_add_point( node , im->ximg,0);
2088 gfx_add_point( node , im->ximg,im->yimg);
2089 gfx_add_point( node , 0,im->yimg);
2090 /* gfx_add_point( node , 0,im->yimg ); */
2093 if (im->draw_x_grid == 1 )
2096 if (im->draw_y_grid == 1){
2097 if(im->logarithmic){
2098 res = horizontal_log_grid(im);
2100 res = draw_horizontal_grid(im);
2103 /* dont draw horizontal grid if there is no min and max val */
2105 char *nodata = "No Data found";
2106 gfx_new_text(im->canvas,im->ximg/2, (2*im->yorigin-im->ysize) / 2,
2107 im->graph_col[GRC_FONT],
2108 im->text_prop[TEXT_PROP_AXIS].font,
2109 im->text_prop[TEXT_PROP_AXIS].size,
2110 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
2115 /* yaxis unit description */
2116 gfx_new_text( im->canvas,
2117 10, (im->yorigin - im->ysize/2),
2118 im->graph_col[GRC_FONT],
2119 im->text_prop[TEXT_PROP_UNIT].font,
2120 im->text_prop[TEXT_PROP_UNIT].size, im->tabwidth,
2121 RRDGRAPH_YLEGEND_ANGLE,
2122 GFX_H_LEFT, GFX_V_CENTER,
2126 gfx_new_text( im->canvas,
2127 im->ximg/2, im->text_prop[TEXT_PROP_TITLE].size*1.3+4,
2128 im->graph_col[GRC_FONT],
2129 im->text_prop[TEXT_PROP_TITLE].font,
2130 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
2131 GFX_H_CENTER, GFX_V_CENTER,
2133 /* rrdtool 'logo' */
2134 gfx_new_text( im->canvas,
2136 ( im->graph_col[GRC_FONT] & 0xffffff00 ) | 0x00000044,
2137 im->text_prop[TEXT_PROP_AXIS].font,
2138 5.5, im->tabwidth, 270,
2139 GFX_H_RIGHT, GFX_V_TOP,
2140 "RRDTOOL / TOBI OETIKER");
2142 /* graph watermark */
2143 if(im->watermark[0] != '\0') {
2144 gfx_new_text( im->canvas,
2145 im->ximg/2, im->yimg-6,
2146 ( im->graph_col[GRC_FONT] & 0xffffff00 ) | 0x00000044,
2147 im->text_prop[TEXT_PROP_AXIS].font,
2148 5.5, im->tabwidth, 0,
2149 GFX_H_CENTER, GFX_V_BOTTOM,
2154 if( !(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH) ) {
2155 for(i=0;i<im->gdes_c;i++){
2156 if(im->gdes[i].legend[0] =='\0')
2159 /* im->gdes[i].leg_y is the bottom of the legend */
2160 X0 = im->gdes[i].leg_x;
2161 Y0 = im->gdes[i].leg_y;
2162 gfx_new_text ( im->canvas, X0, Y0,
2163 im->graph_col[GRC_FONT],
2164 im->text_prop[TEXT_PROP_LEGEND].font,
2165 im->text_prop[TEXT_PROP_LEGEND].size,
2166 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
2167 im->gdes[i].legend );
2168 /* The legend for GRAPH items starts with "M " to have
2169 enough space for the box */
2170 if ( im->gdes[i].gf != GF_PRINT &&
2171 im->gdes[i].gf != GF_GPRINT &&
2172 im->gdes[i].gf != GF_COMMENT) {
2175 boxH = gfx_get_text_width(im->canvas, 0,
2176 im->text_prop[TEXT_PROP_LEGEND].font,
2177 im->text_prop[TEXT_PROP_LEGEND].size,
2178 im->tabwidth,"o", 0) * 1.2;
2181 /* make sure transparent colors show up the same way as in the graph */
2182 node = gfx_new_area(im->canvas,
2186 im->graph_col[GRC_BACK]);
2187 gfx_add_point ( node, X0+boxH, Y0-boxV );
2189 node = gfx_new_area(im->canvas,
2194 gfx_add_point ( node, X0+boxH, Y0-boxV );
2195 node = gfx_new_line(im->canvas,
2198 1.0,im->graph_col[GRC_FRAME]);
2199 gfx_add_point(node,X0+boxH,Y0);
2200 gfx_add_point(node,X0+boxH,Y0-boxV);
2201 gfx_close_path(node);
2208 /*****************************************************
2209 * lazy check make sure we rely need to create this graph
2210 *****************************************************/
2212 int lazy_check(image_desc_t *im){
2215 struct stat imgstat;
2217 if (im->lazy == 0) return 0; /* no lazy option */
2218 if (stat(im->graphfile,&imgstat) != 0)
2219 return 0; /* can't stat */
2220 /* one pixel in the existing graph is more then what we would
2222 if (time(NULL) - imgstat.st_mtime >
2223 (im->end - im->start) / im->xsize)
2225 if ((fd = fopen(im->graphfile,"rb")) == NULL)
2226 return 0; /* the file does not exist */
2227 switch (im->canvas->imgformat) {
2229 size = PngSize(fd,&(im->ximg),&(im->yimg));
2238 #ifdef WITH_PIECHART
2240 pie_part(image_desc_t *im, gfx_color_t color,
2241 double PieCenterX, double PieCenterY, double Radius,
2242 double startangle, double endangle)
2246 double step=M_PI/50; /* Number of iterations for the circle;
2247 ** 10 is definitely too low, more than
2248 ** 50 seems to be overkill
2251 /* Strange but true: we have to work clockwise or else
2252 ** anti aliasing nor transparency don't work.
2254 ** This test is here to make sure we do it right, also
2255 ** this makes the for...next loop more easy to implement.
2256 ** The return will occur if the user enters a negative number
2257 ** (which shouldn't be done according to the specs) or if the
2258 ** programmers do something wrong (which, as we all know, never
2259 ** happens anyway :)
2261 if (endangle<startangle) return;
2263 /* Hidden feature: Radius decreases each full circle */
2265 while (angle>=2*M_PI) {
2270 node=gfx_new_area(im->canvas,
2271 PieCenterX+sin(startangle)*Radius,
2272 PieCenterY-cos(startangle)*Radius,
2275 PieCenterX+sin(endangle)*Radius,
2276 PieCenterY-cos(endangle)*Radius,
2278 for (angle=endangle;angle-startangle>=step;angle-=step) {
2280 PieCenterX+sin(angle)*Radius,
2281 PieCenterY-cos(angle)*Radius );
2288 graph_size_location(image_desc_t *im, int elements
2290 #ifdef WITH_PIECHART
2296 /* The actual size of the image to draw is determined from
2297 ** several sources. The size given on the command line is
2298 ** the graph area but we need more as we have to draw labels
2299 ** and other things outside the graph area
2302 /* +-+-------------------------------------------+
2303 ** |l|.................title.....................|
2304 ** |e+--+-------------------------------+--------+
2307 ** |l| l| main graph area | chart |
2310 ** |r+--+-------------------------------+--------+
2311 ** |e| | x-axis labels | |
2312 ** |v+--+-------------------------------+--------+
2313 ** | |..............legends......................|
2314 ** +-+-------------------------------------------+
2316 ** +---------------------------------------------+
2322 #ifdef WITH_PIECHART
2327 Xlegend =0, Ylegend =0,
2329 Xspacing =15, Yspacing =15,
2333 if (im->extra_flags & ONLY_GRAPH) {
2335 im->ximg = im->xsize;
2336 im->yimg = im->ysize;
2337 im->yorigin = im->ysize;
2342 if (im->ylegend[0] != '\0' ) {
2343 Xvertical = im->text_prop[TEXT_PROP_UNIT].size *2;
2347 if (im->title[0] != '\0') {
2348 /* The title is placed "inbetween" two text lines so it
2349 ** automatically has some vertical spacing. The horizontal
2350 ** spacing is added here, on each side.
2352 /* don't care for the with of the title
2353 Xtitle = gfx_get_text_width(im->canvas, 0,
2354 im->text_prop[TEXT_PROP_TITLE].font,
2355 im->text_prop[TEXT_PROP_TITLE].size,
2357 im->title, 0) + 2*Xspacing; */
2358 Ytitle = im->text_prop[TEXT_PROP_TITLE].size*2.6+10;
2364 if (im->draw_x_grid) {
2365 Yxlabel=im->text_prop[TEXT_PROP_AXIS].size *2.5;
2367 if (im->draw_y_grid) {
2368 Xylabel=gfx_get_text_width(im->canvas, 0,
2369 im->text_prop[TEXT_PROP_AXIS].font,
2370 im->text_prop[TEXT_PROP_AXIS].size,
2372 "0", 0) * im->unitslength;
2376 #ifdef WITH_PIECHART
2378 im->piesize=im->xsize<im->ysize?im->xsize:im->ysize;
2384 /* Now calculate the total size. Insert some spacing where
2385 desired. im->xorigin and im->yorigin need to correspond
2386 with the lower left corner of the main graph area or, if
2387 this one is not set, the imaginary box surrounding the
2390 /* The legend width cannot yet be determined, as a result we
2391 ** have problems adjusting the image to it. For now, we just
2392 ** forget about it at all; the legend will have to fit in the
2393 ** size already allocated.
2395 im->ximg = Xylabel + Xmain + 2 * Xspacing;
2397 #ifdef WITH_PIECHART
2401 if (Xmain) im->ximg += Xspacing;
2402 #ifdef WITH_PIECHART
2403 if (Xpie) im->ximg += Xspacing;
2406 im->xorigin = Xspacing + Xylabel;
2408 /* the length of the title should not influence with width of the graph
2409 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2411 if (Xvertical) { /* unit description */
2412 im->ximg += Xvertical;
2413 im->xorigin += Xvertical;
2417 /* The vertical size is interesting... we need to compare
2418 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with
2419 ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2420 ** in order to start even thinking about Ylegend or Ywatermark.
2422 ** Do it in three portions: First calculate the inner part,
2423 ** then do the legend, then adjust the total height of the img,
2424 ** adding space for a watermark if one exists;
2427 /* reserve space for main and/or pie */
2429 im->yimg = Ymain + Yxlabel;
2431 #ifdef WITH_PIECHART
2432 if (im->yimg < Ypie) im->yimg = Ypie;
2435 im->yorigin = im->yimg - Yxlabel;
2437 /* reserve space for the title *or* some padding above the graph */
2440 im->yorigin += Ytitle;
2442 im->yimg += 1.5*Yspacing;
2443 im->yorigin += 1.5*Yspacing;
2445 /* reserve space for padding below the graph */
2446 im->yimg += Yspacing;
2448 /* Determine where to place the legends onto the image.
2449 ** Adjust im->yimg to match the space requirements.
2451 if(leg_place(im)==-1)
2454 if (im->watermark[0] != '\0') {
2455 im->yimg += Ywatermark;
2459 if (Xlegend > im->ximg) {
2461 /* reposition Pie */
2465 #ifdef WITH_PIECHART
2466 /* The pie is placed in the upper right hand corner,
2467 ** just below the title (if any) and with sufficient
2471 im->pie_x = im->ximg - Xspacing - Xpie/2;
2472 im->pie_y = im->yorigin-Ymain+Ypie/2;
2474 im->pie_x = im->ximg/2;
2475 im->pie_y = im->yorigin-Ypie/2;
2483 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
2484 /* yes we are loosing precision by doing tos with floats instead of doubles
2485 but it seems more stable this way. */
2487 static int AlmostEqual2sComplement (float A, float B, int maxUlps)
2490 int aInt = *(int*)&A;
2491 int bInt = *(int*)&B;
2493 /* Make sure maxUlps is non-negative and small enough that the
2494 default NAN won't compare as equal to anything. */
2496 /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
2498 /* Make aInt lexicographically ordered as a twos-complement int */
2501 aInt = 0x80000000l - aInt;
2503 /* Make bInt lexicographically ordered as a twos-complement int */
2506 bInt = 0x80000000l - bInt;
2508 intDiff = abs(aInt - bInt);
2510 if (intDiff <= maxUlps)
2516 /* draw that picture thing ... */
2518 graph_paint(image_desc_t *im, char ***calcpr)
2521 int lazy = lazy_check(im);
2522 #ifdef WITH_PIECHART
2524 double PieStart=0.0;
2529 double areazero = 0.0;
2530 graph_desc_t *lastgdes = NULL;
2532 /* if we are lazy and there is nothing to PRINT ... quit now */
2533 if (lazy && im->prt_c==0) return 0;
2535 /* pull the data from the rrd files ... */
2537 if(data_fetch(im)==-1)
2540 /* evaluate VDEF and CDEF operations ... */
2541 if(data_calc(im)==-1)
2544 #ifdef WITH_PIECHART
2545 /* check if we need to draw a piechart */
2546 for(i=0;i<im->gdes_c;i++){
2547 if (im->gdes[i].gf == GF_PART) {
2554 /* calculate and PRINT and GPRINT definitions. We have to do it at
2555 * this point because it will affect the length of the legends
2556 * if there are no graph elements we stop here ...
2557 * if we are lazy, try to quit ...
2559 i=print_calc(im,calcpr);
2562 #ifdef WITH_PIECHART
2565 ) || lazy) return 0;
2567 #ifdef WITH_PIECHART
2568 /* If there's only the pie chart to draw, signal this */
2569 if (i==0) piechart=2;
2572 /* get actual drawing data and find min and max values*/
2573 if(data_proc(im)==-1)
2576 if(!im->logarithmic){si_unit(im);} /* identify si magnitude Kilo, Mega Giga ? */
2578 if(!im->rigid && ! im->logarithmic)
2579 expand_range(im); /* make sure the upper and lower limit are
2582 if (!calc_horizontal_grid(im))
2589 /**************************************************************
2590 *** Calculating sizes and locations became a bit confusing ***
2591 *** so I moved this into a separate function. ***
2592 **************************************************************/
2593 if(graph_size_location(im,i
2594 #ifdef WITH_PIECHART
2600 /* the actual graph is created by going through the individual
2601 graph elements and then drawing them */
2603 node=gfx_new_area ( im->canvas,
2607 im->graph_col[GRC_BACK]);
2609 gfx_add_point(node,im->ximg, 0);
2611 #ifdef WITH_PIECHART
2612 if (piechart != 2) {
2614 node=gfx_new_area ( im->canvas,
2615 im->xorigin, im->yorigin,
2616 im->xorigin + im->xsize, im->yorigin,
2617 im->xorigin + im->xsize, im->yorigin-im->ysize,
2618 im->graph_col[GRC_CANVAS]);
2620 gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
2622 if (im->minval > 0.0)
2623 areazero = im->minval;
2624 if (im->maxval < 0.0)
2625 areazero = im->maxval;
2626 #ifdef WITH_PIECHART
2630 #ifdef WITH_PIECHART
2632 pie_part(im,im->graph_col[GRC_CANVAS],im->pie_x,im->pie_y,im->piesize*0.5,0,2*M_PI);
2636 for(i=0;i<im->gdes_c;i++){
2637 switch(im->gdes[i].gf){
2650 for (ii = 0; ii < im->xsize; ii++)
2652 if (!isnan(im->gdes[i].p_data[ii]) &&
2653 im->gdes[i].p_data[ii] != 0.0)
2655 if (im -> gdes[i].yrule > 0 ) {
2656 gfx_new_line(im->canvas,
2657 im -> xorigin + ii, im->yorigin,
2658 im -> xorigin + ii, im->yorigin - im -> gdes[i].yrule * im -> ysize,
2660 im -> gdes[i].col );
2661 } else if ( im -> gdes[i].yrule < 0 ) {
2662 gfx_new_line(im->canvas,
2663 im -> xorigin + ii, im->yorigin - im -> ysize,
2664 im -> xorigin + ii, im->yorigin - ( 1 - im -> gdes[i].yrule ) * im -> ysize,
2666 im -> gdes[i].col );
2674 /* fix data points at oo and -oo */
2675 for(ii=0;ii<im->xsize;ii++){
2676 if (isinf(im->gdes[i].p_data[ii])){
2677 if (im->gdes[i].p_data[ii] > 0) {
2678 im->gdes[i].p_data[ii] = im->maxval ;
2680 im->gdes[i].p_data[ii] = im->minval ;
2686 /* *******************************************************
2691 -------|--t-1--t--------------------------------
2693 if we know the value at time t was a then
2694 we draw a square from t-1 to t with the value a.
2696 ********************************************************* */
2697 if (im->gdes[i].col != 0x0){
2698 /* GF_LINE and friend */
2699 if(im->gdes[i].gf == GF_LINE ){
2702 for(ii=1;ii<im->xsize;ii++){
2703 if (isnan(im->gdes[i].p_data[ii]) || (im->slopemode==1 && isnan(im->gdes[i].p_data[ii-1]))){
2707 if ( node == NULL ) {
2708 last_y = ytr(im,im->gdes[i].p_data[ii]);
2709 if ( im->slopemode == 0 ){
2710 node = gfx_new_line(im->canvas,
2711 ii-1+im->xorigin,last_y,
2712 ii+im->xorigin,last_y,
2713 im->gdes[i].linewidth,
2716 node = gfx_new_line(im->canvas,
2717 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2718 ii+im->xorigin,last_y,
2719 im->gdes[i].linewidth,
2723 double new_y = ytr(im,im->gdes[i].p_data[ii]);
2724 if ( im->slopemode==0 && ! AlmostEqual2sComplement(new_y,last_y,4)){
2725 gfx_add_point(node,ii-1+im->xorigin,new_y);
2728 gfx_add_point(node,ii+im->xorigin,new_y);
2734 double *foreY=malloc(sizeof(double)*im->xsize*2);
2735 double *foreX=malloc(sizeof(double)*im->xsize*2);
2736 double *backY=malloc(sizeof(double)*im->xsize*2);
2737 double *backX=malloc(sizeof(double)*im->xsize*2);
2739 for(ii=0;ii<=im->xsize;ii++){
2741 if ( idxI > 0 && ( drawem != 0 || ii==im->xsize)){
2744 while (cntI < idxI && AlmostEqual2sComplement(foreY[lastI],foreY[cntI],4) && AlmostEqual2sComplement(foreY[lastI],foreY[cntI+1],4)){cntI++;}
2745 node = gfx_new_area(im->canvas,
2748 foreX[cntI],foreY[cntI], im->gdes[i].col);
2749 while (cntI < idxI) {
2752 while ( cntI < idxI && AlmostEqual2sComplement(foreY[lastI],foreY[cntI],4) && AlmostEqual2sComplement(foreY[lastI],foreY[cntI+1],4)){cntI++;}
2753 gfx_add_point(node,foreX[cntI],foreY[cntI]);
2755 gfx_add_point(node,backX[idxI],backY[idxI]);
2759 while ( idxI > 1 && AlmostEqual2sComplement(backY[lastI], backY[idxI],4) && AlmostEqual2sComplement(backY[lastI],backY[idxI-1],4)){idxI--;}
2760 gfx_add_point(node,backX[idxI],backY[idxI]);
2769 if (ii == im->xsize) break;
2771 /* keep things simple for now, just draw these bars
2772 do not try to build a big and complex area */
2775 if ( im->slopemode == 0 && ii==0){
2778 if ( isnan(im->gdes[i].p_data[ii]) ) {
2782 ytop = ytr(im,im->gdes[i].p_data[ii]);
2783 if ( lastgdes && im->gdes[i].stack ) {
2784 ybase = ytr(im,lastgdes->p_data[ii]);
2786 ybase = ytr(im,areazero);
2788 if ( ybase == ytop ){
2792 /* every area has to be wound clock-wise,
2793 so we have to make sur base remains base */
2795 double extra = ytop;
2799 if ( im->slopemode == 0 ){
2800 backY[++idxI] = ybase-0.2;
2801 backX[idxI] = ii+im->xorigin-1;
2802 foreY[idxI] = ytop+0.2;
2803 foreX[idxI] = ii+im->xorigin-1;
2805 backY[++idxI] = ybase-0.2;
2806 backX[idxI] = ii+im->xorigin;
2807 foreY[idxI] = ytop+0.2;
2808 foreX[idxI] = ii+im->xorigin;
2810 /* close up any remaining area */
2815 } /* else GF_LINE */
2816 } /* if color != 0x0 */
2817 /* make sure we do not run into trouble when stacking on NaN */
2818 for(ii=0;ii<im->xsize;ii++){
2819 if (isnan(im->gdes[i].p_data[ii])) {
2820 if (lastgdes && (im->gdes[i].stack)) {
2821 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
2823 im->gdes[i].p_data[ii] = areazero;
2827 lastgdes = &(im->gdes[i]);
2829 #ifdef WITH_PIECHART
2831 if(isnan(im->gdes[i].yrule)) /* fetch variable */
2832 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2834 if (finite(im->gdes[i].yrule)) { /* even the fetched var can be NaN */
2835 pie_part(im,im->gdes[i].col,
2836 im->pie_x,im->pie_y,im->piesize*0.4,
2837 M_PI*2.0*PieStart/100.0,
2838 M_PI*2.0*(PieStart+im->gdes[i].yrule)/100.0);
2839 PieStart += im->gdes[i].yrule;
2844 rrd_set_error("STACK should already be turned into LINE or AREA here");
2850 #ifdef WITH_PIECHART
2858 /* grid_paint also does the text */
2859 if( !(im->extra_flags & ONLY_GRAPH) )
2863 if( !(im->extra_flags & ONLY_GRAPH) )
2866 /* the RULES are the last thing to paint ... */
2867 for(i=0;i<im->gdes_c;i++){
2869 switch(im->gdes[i].gf){
2871 if(im->gdes[i].yrule >= im->minval
2872 && im->gdes[i].yrule <= im->maxval)
2873 gfx_new_line(im->canvas,
2874 im->xorigin,ytr(im,im->gdes[i].yrule),
2875 im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2876 1.0,im->gdes[i].col);
2879 if(im->gdes[i].xrule >= im->start
2880 && im->gdes[i].xrule <= im->end)
2881 gfx_new_line(im->canvas,
2882 xtr(im,im->gdes[i].xrule),im->yorigin,
2883 xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2884 1.0,im->gdes[i].col);
2892 if (strcmp(im->graphfile,"-")==0) {
2893 fo = im->graphhandle ? im->graphhandle : stdout;
2894 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
2895 /* Change translation mode for stdout to BINARY */
2896 _setmode( _fileno( fo ), O_BINARY );
2899 if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2900 rrd_set_error("Opening '%s' for write: %s",im->graphfile,
2901 rrd_strerror(errno));
2905 gfx_render (im->canvas,im->ximg,im->yimg,0x00000000,fo);
2906 if (strcmp(im->graphfile,"-") != 0)
2912 /*****************************************************
2914 *****************************************************/
2917 gdes_alloc(image_desc_t *im){
2920 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2921 * sizeof(graph_desc_t)))==NULL){
2922 rrd_set_error("realloc graph_descs");
2927 im->gdes[im->gdes_c-1].step=im->step;
2928 im->gdes[im->gdes_c-1].step_orig=im->step;
2929 im->gdes[im->gdes_c-1].stack=0;
2930 im->gdes[im->gdes_c-1].linewidth=0;
2931 im->gdes[im->gdes_c-1].debug=0;
2932 im->gdes[im->gdes_c-1].start=im->start;
2933 im->gdes[im->gdes_c-1].start_orig=im->start;
2934 im->gdes[im->gdes_c-1].end=im->end;
2935 im->gdes[im->gdes_c-1].end_orig=im->end;
2936 im->gdes[im->gdes_c-1].vname[0]='\0';
2937 im->gdes[im->gdes_c-1].data=NULL;
2938 im->gdes[im->gdes_c-1].ds_namv=NULL;
2939 im->gdes[im->gdes_c-1].data_first=0;
2940 im->gdes[im->gdes_c-1].p_data=NULL;
2941 im->gdes[im->gdes_c-1].rpnp=NULL;
2942 im->gdes[im->gdes_c-1].shift=0;
2943 im->gdes[im->gdes_c-1].col = 0x0;
2944 im->gdes[im->gdes_c-1].legend[0]='\0';
2945 im->gdes[im->gdes_c-1].format[0]='\0';
2946 im->gdes[im->gdes_c-1].strftm=0;
2947 im->gdes[im->gdes_c-1].rrd[0]='\0';
2948 im->gdes[im->gdes_c-1].ds=-1;
2949 im->gdes[im->gdes_c-1].cf_reduce=CF_AVERAGE;
2950 im->gdes[im->gdes_c-1].cf=CF_AVERAGE;
2951 im->gdes[im->gdes_c-1].p_data=NULL;
2952 im->gdes[im->gdes_c-1].yrule=DNAN;
2953 im->gdes[im->gdes_c-1].xrule=0;
2957 /* copies input untill the first unescaped colon is found
2958 or until input ends. backslashes have to be escaped as well */
2960 scan_for_col(const char *const input, int len, char *const output)
2965 input[inp] != ':' &&
2968 if (input[inp] == '\\' &&
2969 input[inp+1] != '\0' &&
2970 (input[inp+1] == '\\' ||
2971 input[inp+1] == ':')){
2972 output[outp++] = input[++inp];
2975 output[outp++] = input[inp];
2978 output[outp] = '\0';
2981 /* Some surgery done on this function, it became ridiculously big.
2983 ** - initializing now in rrd_graph_init()
2984 ** - options parsing now in rrd_graph_options()
2985 ** - script parsing now in rrd_graph_script()
2988 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize, FILE *stream, double *ymin, double *ymax)
2991 rrd_graph_init(&im);
2992 im.graphhandle = stream;
2994 rrd_graph_options(argc,argv,&im);
2995 if (rrd_test_error()) {
3000 if (strlen(argv[optind])>=MAXPATH) {
3001 rrd_set_error("filename (including path) too long");
3005 strncpy(im.graphfile,argv[optind],MAXPATH-1);
3006 im.graphfile[MAXPATH-1]='\0';
3008 rrd_graph_script(argc,argv,&im,1);
3009 if (rrd_test_error()) {
3014 /* Everything is now read and the actual work can start */
3017 if (graph_paint(&im,prdata)==-1){
3022 /* The image is generated and needs to be output.
3023 ** Also, if needed, print a line with information about the image.
3033 /* maybe prdata is not allocated yet ... lets do it now */
3034 if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
3035 rrd_set_error("malloc imginfo");
3039 if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
3041 rrd_set_error("malloc imginfo");
3044 filename=im.graphfile+strlen(im.graphfile);
3045 while(filename > im.graphfile) {
3046 if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
3050 sprintf((*prdata)[0],im.imginfo,filename,(long)(im.canvas->zoom*im.ximg),(long)(im.canvas->zoom*im.yimg));
3057 rrd_graph_init(image_desc_t *im)
3064 #ifdef HAVE_SETLOCALE
3065 setlocale(LC_TIME,"");
3066 #ifdef HAVE_MBSTOWCS
3067 setlocale(LC_CTYPE,"");
3073 im->xlab_user.minsec = -1;
3079 im->ylegend[0] = '\0';
3080 im->title[0] = '\0';
3081 im->watermark[0] = '\0';
3084 im->unitsexponent= 9999;
3087 im->viewfactor = 1.0;
3094 im->logarithmic = 0;
3095 im->ygridstep = DNAN;
3096 im->draw_x_grid = 1;
3097 im->draw_y_grid = 1;
3102 im->canvas = gfx_new_canvas();
3103 im->grid_dash_on = 1;
3104 im->grid_dash_off = 1;
3105 im->tabwidth = 40.0;
3107 for(i=0;i<DIM(graph_col);i++)
3108 im->graph_col[i]=graph_col[i];
3110 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3113 char rrd_win_default_font[1000];
3114 windir = getenv("windir");
3115 /* %windir% is something like D:\windows or C:\winnt */
3116 if (windir != NULL) {
3117 strncpy(rrd_win_default_font,windir,500);
3118 rrd_win_default_font[500] = '\0';
3119 strcat(rrd_win_default_font,"\\fonts\\");
3120 strcat(rrd_win_default_font,RRD_DEFAULT_FONT);
3121 for(i=0;i<DIM(text_prop);i++){
3122 strncpy(text_prop[i].font,rrd_win_default_font,sizeof(text_prop[i].font)-1);
3123 text_prop[i].font[sizeof(text_prop[i].font)-1] = '\0';
3130 deffont = getenv("RRD_DEFAULT_FONT");
3131 if (deffont != NULL) {
3132 for(i=0;i<DIM(text_prop);i++){
3133 strncpy(text_prop[i].font,deffont,sizeof(text_prop[i].font)-1);
3134 text_prop[i].font[sizeof(text_prop[i].font)-1] = '\0';
3138 for(i=0;i<DIM(text_prop);i++){
3139 im->text_prop[i].size = text_prop[i].size;
3140 strcpy(im->text_prop[i].font,text_prop[i].font);
3145 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
3148 char *parsetime_error = NULL;
3149 char scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
3150 time_t start_tmp=0,end_tmp=0;
3152 struct rrd_time_value start_tv, end_tv;
3154 optind = 0; opterr = 0; /* initialize getopt */
3156 parsetime("end-24h", &start_tv);
3157 parsetime("now", &end_tv);
3159 /* defines for long options without a short equivalent. should be bytes,
3160 and may not collide with (the ASCII value of) short options */
3161 #define LONGOPT_UNITS_SI 255
3164 static struct option long_options[] =
3166 {"start", required_argument, 0, 's'},
3167 {"end", required_argument, 0, 'e'},
3168 {"x-grid", required_argument, 0, 'x'},
3169 {"y-grid", required_argument, 0, 'y'},
3170 {"vertical-label",required_argument,0,'v'},
3171 {"width", required_argument, 0, 'w'},
3172 {"height", required_argument, 0, 'h'},
3173 {"interlaced", no_argument, 0, 'i'},
3174 {"upper-limit",required_argument, 0, 'u'},
3175 {"lower-limit",required_argument, 0, 'l'},
3176 {"rigid", no_argument, 0, 'r'},
3177 {"base", required_argument, 0, 'b'},
3178 {"logarithmic",no_argument, 0, 'o'},
3179 {"color", required_argument, 0, 'c'},
3180 {"font", required_argument, 0, 'n'},
3181 {"title", required_argument, 0, 't'},
3182 {"imginfo", required_argument, 0, 'f'},
3183 {"imgformat", required_argument, 0, 'a'},
3184 {"lazy", no_argument, 0, 'z'},
3185 {"zoom", required_argument, 0, 'm'},
3186 {"no-legend", no_argument, 0, 'g'},
3187 {"force-rules-legend",no_argument,0, 'F'},
3188 {"only-graph", no_argument, 0, 'j'},
3189 {"alt-y-grid", no_argument, 0, 'Y'},
3190 {"no-minor", no_argument, 0, 'I'},
3191 {"slope-mode", no_argument, 0, 'E'},
3192 {"alt-autoscale", no_argument, 0, 'A'},
3193 {"alt-autoscale-max", no_argument, 0, 'M'},
3194 {"no-gridfit", no_argument, 0, 'N'},
3195 {"units-exponent",required_argument, 0, 'X'},
3196 {"units-length",required_argument, 0, 'L'},
3197 {"units", required_argument, 0, LONGOPT_UNITS_SI },
3198 {"step", required_argument, 0, 'S'},
3199 {"tabwidth", required_argument, 0, 'T'},
3200 {"font-render-mode", required_argument, 0, 'R'},
3201 {"font-smoothing-threshold", required_argument, 0, 'B'},
3202 {"watermark", required_argument, 0, 'W'},
3203 {"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 */
3205 int option_index = 0;
3207 int col_start,col_end;
3209 opt = getopt_long(argc, argv,
3210 "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:I:zgjFYAMEX:L:S:T:NR:B:W:",
3211 long_options, &option_index);
3218 im->extra_flags |= NOMINOR;
3221 im->extra_flags |= ALTYGRID;
3224 im->extra_flags |= ALTAUTOSCALE;
3227 im->extra_flags |= ALTAUTOSCALE_MAX;
3230 im->extra_flags |= ONLY_GRAPH;
3233 im->extra_flags |= NOLEGEND;
3236 im->extra_flags |= FORCE_RULES_LEGEND;
3238 case LONGOPT_UNITS_SI:
3239 if(im->extra_flags & FORCE_UNITS) {
3240 rrd_set_error("--units can only be used once!");
3243 if(strcmp(optarg,"si")==0)
3244 im->extra_flags |= FORCE_UNITS_SI;
3246 rrd_set_error("invalid argument for --units: %s", optarg );
3251 im->unitsexponent = atoi(optarg);
3254 im->unitslength = atoi(optarg);
3257 im->tabwidth = atof(optarg);
3260 im->step = atoi(optarg);
3266 if ((parsetime_error = parsetime(optarg, &start_tv))) {
3267 rrd_set_error( "start time: %s", parsetime_error );
3272 if ((parsetime_error = parsetime(optarg, &end_tv))) {
3273 rrd_set_error( "end time: %s", parsetime_error );
3278 if(strcmp(optarg,"none") == 0){
3284 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3286 &im->xlab_user.gridst,
3288 &im->xlab_user.mgridst,
3290 &im->xlab_user.labst,
3291 &im->xlab_user.precis,
3292 &stroff) == 7 && stroff != 0){
3293 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
3294 im->xlab_form[sizeof(im->xlab_form)-1] = '\0';
3295 if((int)(im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
3296 rrd_set_error("unknown keyword %s",scan_gtm);
3298 } else if ((int)(im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
3299 rrd_set_error("unknown keyword %s",scan_mtm);
3301 } else if ((int)(im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
3302 rrd_set_error("unknown keyword %s",scan_ltm);
3305 im->xlab_user.minsec = 1;
3306 im->xlab_user.stst = im->xlab_form;
3308 rrd_set_error("invalid x-grid format");
3314 if(strcmp(optarg,"none") == 0){
3322 &im->ylabfact) == 2) {
3323 if(im->ygridstep<=0){
3324 rrd_set_error("grid step must be > 0");
3326 } else if (im->ylabfact < 1){
3327 rrd_set_error("label factor must be > 0");
3331 rrd_set_error("invalid y-grid format");
3336 strncpy(im->ylegend,optarg,150);
3337 im->ylegend[150]='\0';
3340 im->maxval = atof(optarg);
3343 im->minval = atof(optarg);
3346 im->base = atol(optarg);
3347 if(im->base != 1024 && im->base != 1000 ){
3348 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
3353 long_tmp = atol(optarg);
3354 if (long_tmp < 10) {
3355 rrd_set_error("width below 10 pixels");
3358 im->xsize = long_tmp;
3361 long_tmp = atol(optarg);
3362 if (long_tmp < 10) {
3363 rrd_set_error("height below 10 pixels");
3366 im->ysize = long_tmp;
3369 im->canvas->interlaced = 1;
3375 im->imginfo = optarg;
3378 if((int)(im->canvas->imgformat = if_conv(optarg)) == -1) {
3379 rrd_set_error("unsupported graphics format '%s'",optarg);
3391 im->logarithmic = 1;
3395 "%10[A-Z]#%n%8lx%n",
3396 col_nam,&col_start,&color,&col_end) == 2){
3398 int col_len = col_end - col_start;
3402 ((color & 0xF00) * 0x110000) |
3403 ((color & 0x0F0) * 0x011000) |
3404 ((color & 0x00F) * 0x001100) |
3410 ((color & 0xF000) * 0x11000) |
3411 ((color & 0x0F00) * 0x01100) |
3412 ((color & 0x00F0) * 0x00110) |
3413 ((color & 0x000F) * 0x00011)
3417 color = (color << 8) + 0xff /* shift left by 8 */;
3422 rrd_set_error("the color format is #RRGGBB[AA]");
3425 if((ci=grc_conv(col_nam)) != -1){
3426 im->graph_col[ci]=color;
3428 rrd_set_error("invalid color name '%s'",col_nam);
3432 rrd_set_error("invalid color def format");
3439 char font[1024] = "";
3442 "%10[A-Z]:%lf:%1000s",
3443 prop,&size,font) >= 2){
3445 if((sindex=text_prop_conv(prop)) != -1){
3446 for (propidx=sindex;propidx<TEXT_PROP_LAST;propidx++){
3448 im->text_prop[propidx].size=size;
3450 if (strlen(font) > 0){
3451 strcpy(im->text_prop[propidx].font,font);
3453 if (propidx==sindex && sindex != 0) break;
3456 rrd_set_error("invalid fonttag '%s'",prop);
3460 rrd_set_error("invalid text property format");
3466 im->canvas->zoom = atof(optarg);
3467 if (im->canvas->zoom <= 0.0) {
3468 rrd_set_error("zoom factor must be > 0");
3473 strncpy(im->title,optarg,150);
3474 im->title[150]='\0';
3478 if ( strcmp( optarg, "normal" ) == 0 )
3479 im->canvas->aa_type = AA_NORMAL;
3480 else if ( strcmp( optarg, "light" ) == 0 )
3481 im->canvas->aa_type = AA_LIGHT;
3482 else if ( strcmp( optarg, "mono" ) == 0 )
3483 im->canvas->aa_type = AA_NONE;
3486 rrd_set_error("unknown font-render-mode '%s'", optarg );
3492 im->canvas->font_aa_threshold = atof(optarg);
3496 strncpy(im->watermark,optarg,100);
3497 im->watermark[99]='\0';
3502 rrd_set_error("unknown option '%c'", optopt);
3504 rrd_set_error("unknown option '%s'",argv[optind-1]);
3509 if (optind >= argc) {
3510 rrd_set_error("missing filename");
3514 if (im->logarithmic == 1 && im->minval <= 0){
3515 rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");
3519 if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
3520 /* error string is set in parsetime.c */
3524 if (start_tmp < 3600*24*365*10){
3525 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
3529 if (end_tmp < start_tmp) {
3530 rrd_set_error("start (%ld) should be less than end (%ld)",
3531 start_tmp, end_tmp);
3535 im->start = start_tmp;
3537 im->step = max((long)im->step, (im->end-im->start)/im->xsize);
3541 rrd_graph_check_vname(image_desc_t *im, char *varname, char *err)
3543 if ((im->gdes[im->gdes_c-1].vidx=find_var(im,varname))==-1) {
3544 rrd_set_error("Unknown variable '%s' in %s",varname,err);
3550 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
3553 graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
3555 color=strstr(var,"#");
3558 rrd_set_error("Found no color in %s",err);
3567 rest=strstr(color,":");
3575 sscanf(color,"#%6lx%n",&col,&n);
3576 col = (col << 8) + 0xff /* shift left by 8 */;
3577 if (n!=7) rrd_set_error("Color problem in %s",err);
3580 sscanf(color,"#%8lx%n",&col,&n);
3583 rrd_set_error("Color problem in %s",err);
3585 if (rrd_test_error()) return 0;
3592 int bad_format(char *fmt) {
3596 while (*ptr != '\0')
3597 if (*ptr++ == '%') {
3599 /* line cannot end with percent char */
3600 if (*ptr == '\0') return 1;
3602 /* '%s', '%S' and '%%' are allowed */
3603 if (*ptr == 's' || *ptr == 'S' || *ptr == '%') ptr++;
3605 /* %c is allowed (but use only with vdef!) */
3606 else if (*ptr == 'c') {
3611 /* or else '% 6.2lf' and such are allowed */
3613 /* optional padding character */
3614 if (*ptr == ' ' || *ptr == '+' || *ptr == '-') ptr++;
3616 /* This should take care of 'm.n' with all three optional */
3617 while (*ptr >= '0' && *ptr <= '9') ptr++;
3618 if (*ptr == '.') ptr++;
3619 while (*ptr >= '0' && *ptr <= '9') ptr++;
3621 /* Either 'le', 'lf' or 'lg' must follow here */
3622 if (*ptr++ != 'l') return 1;
3623 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g') ptr++;
3634 vdef_parse(gdes,str)
3635 struct graph_desc_t *gdes;
3636 const char *const str;
3638 /* A VDEF currently is either "func" or "param,func"
3639 * so the parsing is rather simple. Change if needed.
3646 sscanf(str,"%le,%29[A-Z]%n",¶m,func,&n);
3647 if (n== (int)strlen(str)) { /* matched */
3651 sscanf(str,"%29[A-Z]%n",func,&n);
3652 if (n== (int)strlen(str)) { /* matched */
3655 rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3662 if (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3663 else if (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3664 else if (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3665 else if (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3666 else if (!strcmp("TOTAL", func)) gdes->vf.op = VDEF_TOTAL;
3667 else if (!strcmp("FIRST", func)) gdes->vf.op = VDEF_FIRST;
3668 else if (!strcmp("LAST", func)) gdes->vf.op = VDEF_LAST;
3669 else if (!strcmp("LSLSLOPE", func)) gdes->vf.op = VDEF_LSLSLOPE;
3670 else if (!strcmp("LSLINT", func)) gdes->vf.op = VDEF_LSLINT;
3671 else if (!strcmp("LSLCORREL",func)) gdes->vf.op = VDEF_LSLCORREL;
3673 rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3680 switch (gdes->vf.op) {
3682 if (isnan(param)) { /* no parameter given */
3683 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3689 if (param>=0.0 && param<=100.0) {
3690 gdes->vf.param = param;
3691 gdes->vf.val = DNAN; /* undefined */
3692 gdes->vf.when = 0; /* undefined */
3694 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3709 case VDEF_LSLCORREL:
3711 gdes->vf.param = DNAN;
3712 gdes->vf.val = DNAN;
3715 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3732 graph_desc_t *src,*dst;
3736 dst = &im->gdes[gdi];
3737 src = &im->gdes[dst->vidx];
3738 data = src->data + src->ds;
3739 steps = (src->end - src->start) / src->step;
3742 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3749 switch (dst->vf.op) {
3750 case VDEF_PERCENT: {
3751 rrd_value_t * array;
3755 if ((array = malloc(steps*sizeof(double)))==NULL) {
3756 rrd_set_error("malloc VDEV_PERCENT");
3759 for (step=0;step < steps; step++) {
3760 array[step]=data[step*src->ds_cnt];
3762 qsort(array,step,sizeof(double),vdef_percent_compar);
3764 field = (steps-1)*dst->vf.param/100;
3765 dst->vf.val = array[field];
3766 dst->vf.when = 0; /* no time component */
3769 for(step=0;step<steps;step++)
3770 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3776 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3777 if (step == steps) {
3781 dst->vf.val = data[step*src->ds_cnt];
3782 dst->vf.when = src->start + (step+1)*src->step;
3784 while (step != steps) {
3785 if (finite(data[step*src->ds_cnt])) {
3786 if (data[step*src->ds_cnt] > dst->vf.val) {
3787 dst->vf.val = data[step*src->ds_cnt];
3788 dst->vf.when = src->start + (step+1)*src->step;
3795 case VDEF_AVERAGE: {
3798 for (step=0;step<steps;step++) {
3799 if (finite(data[step*src->ds_cnt])) {
3800 sum += data[step*src->ds_cnt];
3805 if (dst->vf.op == VDEF_TOTAL) {
3806 dst->vf.val = sum*src->step;
3807 dst->vf.when = 0; /* no time component */
3809 dst->vf.val = sum/cnt;
3810 dst->vf.when = 0; /* no time component */
3820 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3821 if (step == steps) {
3825 dst->vf.val = data[step*src->ds_cnt];
3826 dst->vf.when = src->start + (step+1)*src->step;
3828 while (step != steps) {
3829 if (finite(data[step*src->ds_cnt])) {
3830 if (data[step*src->ds_cnt] < dst->vf.val) {
3831 dst->vf.val = data[step*src->ds_cnt];
3832 dst->vf.when = src->start + (step+1)*src->step;
3839 /* The time value returned here is one step before the
3840 * actual time value. This is the start of the first
3844 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3845 if (step == steps) { /* all entries were NaN */
3849 dst->vf.val = data[step*src->ds_cnt];
3850 dst->vf.when = src->start + step*src->step;
3854 /* The time value returned here is the
3855 * actual time value. This is the end of the last
3859 while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3860 if (step < 0) { /* all entries were NaN */
3864 dst->vf.val = data[step*src->ds_cnt];
3865 dst->vf.when = src->start + (step+1)*src->step;
3870 case VDEF_LSLCORREL:{
3871 /* Bestfit line by linear least squares method */
3874 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl ;
3875 SUMx = 0; SUMy = 0; SUMxy = 0; SUMxx = 0; SUMyy = 0;
3877 for (step=0;step<steps;step++) {
3878 if (finite(data[step*src->ds_cnt])) {
3881 SUMxx += step * step;
3882 SUMxy += step * data[step*src->ds_cnt];
3883 SUMy += data[step*src->ds_cnt];
3884 SUMyy += data[step*src->ds_cnt]*data[step*src->ds_cnt];
3888 slope = ( SUMx*SUMy - cnt*SUMxy ) / ( SUMx*SUMx - cnt*SUMxx );
3889 y_intercept = ( SUMy - slope*SUMx ) / cnt;
3890 correl = (SUMxy - (SUMx*SUMy)/cnt) / sqrt((SUMxx - (SUMx*SUMx)/cnt)*(SUMyy - (SUMy*SUMy)/cnt));
3893 if (dst->vf.op == VDEF_LSLSLOPE) {
3894 dst->vf.val = slope;
3896 } else if (dst->vf.op == VDEF_LSLINT) {
3897 dst->vf.val = y_intercept;
3899 } else if (dst->vf.op == VDEF_LSLCORREL) {
3900 dst->vf.val = correl;
3914 /* NaN < -INF < finite_values < INF */
3916 vdef_percent_compar(a,b)
3919 /* Equality is not returned; this doesn't hurt except
3920 * (maybe) for a little performance.
3923 /* First catch NaN values. They are smallest */
3924 if (isnan( *(double *)a )) return -1;
3925 if (isnan( *(double *)b )) return 1;
3927 /* NaN doesn't reach this part so INF and -INF are extremes.
3928 * The sign from isinf() is compatible with the sign we return
3930 if (isinf( *(double *)a )) return isinf( *(double *)a );
3931 if (isinf( *(double *)b )) return isinf( *(double *)b );
3933 /* If we reach this, both values must be finite */
3934 if ( *(double *)a < *(double *)b ) return -1; else return 1;