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 {180, 0, TMT_HOUR,1, TMT_HOUR,6, TMT_HOUR,6, 0,"%H:%M"},
52 {180, 1*24*3600, TMT_HOUR,1, TMT_HOUR,6, TMT_HOUR,6, 0,"%a %H:%M"},
53 /*{300, 0, TMT_HOUR,3, TMT_HOUR,12, TMT_HOUR,12, 12*3600,"%a %p"}, this looks silly*/
54 {600, 0, TMT_HOUR,6, TMT_DAY,1, TMT_DAY,1, 24*3600,"%a"},
55 {600, 1*24*3600, TMT_HOUR,6, TMT_DAY,1, TMT_DAY,1, 24*3600,"%a %d"},
56 {1800, 0, TMT_HOUR,12, TMT_DAY,1, TMT_DAY,2, 24*3600,"%a"},
57 {1800, 1*24*3600, TMT_HOUR,12, TMT_DAY,1, TMT_DAY,2, 24*3600,"%a %d"},
58 {3600, 0, TMT_DAY,1, TMT_WEEK,1, TMT_WEEK,1, 7*24*3600,"Week %V"},
59 {3*3600, 0, TMT_WEEK,1, TMT_MONTH,1, TMT_WEEK,2, 7*24*3600,"Week %V"},
60 {6*3600, 0, TMT_MONTH,1, TMT_MONTH,1, TMT_MONTH,1, 30*24*3600,"%b"},
61 {48*3600, 0, TMT_MONTH,1, TMT_MONTH,3, TMT_MONTH,3, 30*24*3600,"%b"},
62 {10*24*3600, 0, TMT_YEAR,1, TMT_YEAR,1, TMT_YEAR,1, 365*24*3600,"%y"},
63 {-1,0,TMT_MONTH,0,TMT_MONTH,0,TMT_MONTH,0,0,""}
66 /* sensible logarithmic y label intervals ...
67 the first element of each row defines the possible starting points on the
68 y axis ... the other specify the */
70 double yloglab[][12]= {{ 1e9, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
71 { 1e3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
72 { 1e1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
73 /* { 1e1, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, */
74 { 1e1, 1, 2.5, 5, 7.5, 0, 0, 0, 0, 0, 0, 0 },
75 { 1e1, 1, 2, 4, 6, 8, 0, 0, 0, 0, 0, 0 },
76 { 1e1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0 },
77 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }};
79 /* sensible y label intervals ...*/
97 gfx_color_t graph_col[] = /* default colors */
98 { 0xFFFFFFFF, /* canvas */
99 0xF0F0F0FF, /* background */
100 0xD0D0D0FF, /* shade A */
101 0xA0A0A0FF, /* shade B */
102 0x90909080, /* grid */
103 0xE0505080, /* major grid */
104 0x000000FF, /* font */
105 0x802020FF, /* arrow */
106 0x202020FF, /* axis */
107 0x000000FF /* frame */
114 # define DPRINT(x) (void)(printf x, printf("\n"))
120 /* initialize with xtr(im,0); */
122 xtr(image_desc_t *im,time_t mytime){
125 pixie = (double) im->xsize / (double)(im->end - im->start);
128 return (int)((double)im->xorigin
129 + pixie * ( mytime - im->start ) );
132 /* translate data values into y coordinates */
134 ytr(image_desc_t *im, double value){
139 pixie = (double) im->ysize / (im->maxval - im->minval);
141 pixie = (double) im->ysize / (log10(im->maxval) - log10(im->minval));
143 } else if(!im->logarithmic) {
144 yval = im->yorigin - pixie * (value - im->minval);
146 if (value < im->minval) {
149 yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
152 /* make sure we don't return anything too unreasonable. GD lib can
153 get terribly slow when drawing lines outside its scope. This is
154 especially problematic in connection with the rigid option */
156 /* keep yval as-is */
157 } else if (yval > im->yorigin) {
158 yval = im->yorigin +0.00001;
159 } else if (yval < im->yorigin - im->ysize){
160 yval = im->yorigin - im->ysize - 0.00001;
167 /* conversion function for symbolic entry names */
170 #define conv_if(VV,VVV) \
171 if (strcmp(#VV, string) == 0) return VVV ;
173 enum gf_en gf_conv(char *string){
175 conv_if(PRINT,GF_PRINT)
176 conv_if(GPRINT,GF_GPRINT)
177 conv_if(COMMENT,GF_COMMENT)
178 conv_if(HRULE,GF_HRULE)
179 conv_if(VRULE,GF_VRULE)
180 conv_if(LINE,GF_LINE)
181 conv_if(AREA,GF_AREA)
182 conv_if(STACK,GF_STACK)
183 conv_if(TICK,GF_TICK)
185 conv_if(CDEF,GF_CDEF)
186 conv_if(VDEF,GF_VDEF)
188 conv_if(PART,GF_PART)
190 conv_if(XPORT,GF_XPORT)
191 conv_if(SHIFT,GF_SHIFT)
196 enum gfx_if_en if_conv(char *string){
206 enum tmt_en tmt_conv(char *string){
208 conv_if(SECOND,TMT_SECOND)
209 conv_if(MINUTE,TMT_MINUTE)
210 conv_if(HOUR,TMT_HOUR)
212 conv_if(WEEK,TMT_WEEK)
213 conv_if(MONTH,TMT_MONTH)
214 conv_if(YEAR,TMT_YEAR)
218 enum grc_en grc_conv(char *string){
220 conv_if(BACK,GRC_BACK)
221 conv_if(CANVAS,GRC_CANVAS)
222 conv_if(SHADEA,GRC_SHADEA)
223 conv_if(SHADEB,GRC_SHADEB)
224 conv_if(GRID,GRC_GRID)
225 conv_if(MGRID,GRC_MGRID)
226 conv_if(FONT,GRC_FONT)
227 conv_if(ARROW,GRC_ARROW)
228 conv_if(AXIS,GRC_AXIS)
229 conv_if(FRAME,GRC_FRAME)
234 enum text_prop_en text_prop_conv(char *string){
236 conv_if(DEFAULT,TEXT_PROP_DEFAULT)
237 conv_if(TITLE,TEXT_PROP_TITLE)
238 conv_if(AXIS,TEXT_PROP_AXIS)
239 conv_if(UNIT,TEXT_PROP_UNIT)
240 conv_if(LEGEND,TEXT_PROP_LEGEND)
248 im_free(image_desc_t *im)
252 if (im == NULL) return 0;
253 for(i=0;i<(unsigned)im->gdes_c;i++){
254 if (im->gdes[i].data_first){
255 /* careful here, because a single pointer can occur several times */
256 free (im->gdes[i].data);
257 if (im->gdes[i].ds_namv){
258 for (ii=0;ii<im->gdes[i].ds_cnt;ii++)
259 free(im->gdes[i].ds_namv[ii]);
260 free(im->gdes[i].ds_namv);
263 free (im->gdes[i].p_data);
264 free (im->gdes[i].rpnp);
267 gfx_destroy(im->canvas);
271 /* find SI magnitude symbol for the given number*/
274 image_desc_t *im, /* image description */
281 char *symbol[] = {"a", /* 10e-18 Atto */
282 "f", /* 10e-15 Femto */
283 "p", /* 10e-12 Pico */
284 "n", /* 10e-9 Nano */
285 "u", /* 10e-6 Micro */
286 "m", /* 10e-3 Milli */
291 "T", /* 10e12 Tera */
292 "P", /* 10e15 Peta */
298 if (*value == 0.0 || isnan(*value) ) {
302 sindex = floor(log(fabs(*value))/log((double)im->base));
303 *magfact = pow((double)im->base, (double)sindex);
304 (*value) /= (*magfact);
306 if ( sindex <= symbcenter && sindex >= -symbcenter) {
307 (*symb_ptr) = symbol[sindex+symbcenter];
315 static char si_symbol[] = {
316 'a', /* 10e-18 Atto */
317 'f', /* 10e-15 Femto */
318 'p', /* 10e-12 Pico */
319 'n', /* 10e-9 Nano */
320 'u', /* 10e-6 Micro */
321 'm', /* 10e-3 Milli */
326 'T', /* 10e12 Tera */
327 'P', /* 10e15 Peta */
330 const static int si_symbcenter = 6;
332 /* find SI magnitude symbol for the numbers on the y-axis*/
335 image_desc_t *im /* image description */
339 double digits,viewdigits=0;
341 digits = floor( log( max( fabs(im->minval),fabs(im->maxval)))/log((double)im->base));
343 if (im->unitsexponent != 9999) {
344 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
345 viewdigits = floor(im->unitsexponent / 3);
350 im->magfact = pow((double)im->base , digits);
353 printf("digits %6.3f im->magfact %6.3f\n",digits,im->magfact);
356 im->viewfactor = im->magfact / pow((double)im->base , viewdigits);
358 if ( ((viewdigits+si_symbcenter) < sizeof(si_symbol)) &&
359 ((viewdigits+si_symbcenter) >= 0) )
360 im->symbol = si_symbol[(int)viewdigits+si_symbcenter];
365 /* move min and max values around to become sensible */
368 expand_range(image_desc_t *im)
370 double sensiblevalues[] ={1000.0,900.0,800.0,750.0,700.0,
371 600.0,500.0,400.0,300.0,250.0,
372 200.0,125.0,100.0,90.0,80.0,
373 75.0,70.0,60.0,50.0,40.0,30.0,
374 25.0,20.0,10.0,9.0,8.0,
375 7.0,6.0,5.0,4.0,3.5,3.0,
376 2.5,2.0,1.8,1.5,1.2,1.0,
377 0.8,0.7,0.6,0.5,0.4,0.3,0.2,0.1,0.0,-1};
379 double scaled_min,scaled_max;
386 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
387 im->minval,im->maxval,im->magfact);
390 if (isnan(im->ygridstep)){
391 if(im->extra_flags & ALTAUTOSCALE) {
392 /* measure the amplitude of the function. Make sure that
393 graph boundaries are slightly higher then max/min vals
394 so we can see amplitude on the graph */
397 delt = im->maxval - im->minval;
399 fact = 2.0 * pow(10.0,
400 floor(log10(max(fabs(im->minval), fabs(im->maxval))/im->magfact)) - 2);
402 adj = (fact - delt) * 0.55;
404 printf("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n", im->minval, im->maxval, delt, fact, adj);
410 else if(im->extra_flags & ALTAUTOSCALE_MAX) {
411 /* measure the amplitude of the function. Make sure that
412 graph boundaries are slightly higher than max vals
413 so we can see amplitude on the graph */
414 adj = (im->maxval - im->minval) * 0.1;
418 scaled_min = im->minval / im->magfact;
419 scaled_max = im->maxval / im->magfact;
421 for (i=1; sensiblevalues[i] > 0; i++){
422 if (sensiblevalues[i-1]>=scaled_min &&
423 sensiblevalues[i]<=scaled_min)
424 im->minval = sensiblevalues[i]*(im->magfact);
426 if (-sensiblevalues[i-1]<=scaled_min &&
427 -sensiblevalues[i]>=scaled_min)
428 im->minval = -sensiblevalues[i-1]*(im->magfact);
430 if (sensiblevalues[i-1] >= scaled_max &&
431 sensiblevalues[i] <= scaled_max)
432 im->maxval = sensiblevalues[i-1]*(im->magfact);
434 if (-sensiblevalues[i-1]<=scaled_max &&
435 -sensiblevalues[i] >=scaled_max)
436 im->maxval = -sensiblevalues[i]*(im->magfact);
440 /* adjust min and max to the grid definition if there is one */
441 im->minval = (double)im->ylabfact * im->ygridstep *
442 floor(im->minval / ((double)im->ylabfact * im->ygridstep));
443 im->maxval = (double)im->ylabfact * im->ygridstep *
444 ceil(im->maxval /( (double)im->ylabfact * im->ygridstep));
448 fprintf(stderr,"SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
449 im->minval,im->maxval,im->magfact);
454 apply_gridfit(image_desc_t *im)
456 if (isnan(im->minval) || isnan(im->maxval))
459 if (im->logarithmic) {
460 double ya, yb, ypix, ypixfrac;
461 double log10_range = log10(im->maxval) - log10(im->minval);
462 ya = pow((double)10, floor(log10(im->minval)));
463 while (ya < im->minval)
466 return; /* don't have y=10^x gridline */
468 if (yb <= im->maxval) {
469 /* we have at least 2 y=10^x gridlines.
470 Make sure distance between them in pixels
471 are an integer by expanding im->maxval */
472 double y_pixel_delta = ytr(im, ya) - ytr(im, yb);
473 double factor = y_pixel_delta / floor(y_pixel_delta);
474 double new_log10_range = factor * log10_range;
475 double new_ymax_log10 = log10(im->minval) + new_log10_range;
476 im->maxval = pow(10, new_ymax_log10);
477 ytr(im,DNAN); /* reset precalc */
478 log10_range = log10(im->maxval) - log10(im->minval);
480 /* make sure first y=10^x gridline is located on
481 integer pixel position by moving scale slightly
482 downwards (sub-pixel movement) */
483 ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
484 ypixfrac = ypix - floor(ypix);
485 if (ypixfrac > 0 && ypixfrac < 1) {
486 double yfrac = ypixfrac / im->ysize;
487 im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
488 im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
489 ytr(im,DNAN); /* reset precalc */
492 /* Make sure we have an integer pixel distance between
493 each minor gridline */
494 double ypos1 = ytr(im, im->minval);
495 double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
496 double y_pixel_delta = ypos1 - ypos2;
497 double factor = y_pixel_delta / floor(y_pixel_delta);
498 double new_range = factor * (im->maxval - im->minval);
499 double gridstep = im->ygrid_scale.gridstep;
500 double minor_y, minor_y_px, minor_y_px_frac;
501 im->maxval = im->minval + new_range;
502 ytr(im,DNAN); /* reset precalc */
503 /* make sure first minor gridline is on integer pixel y coord */
504 minor_y = gridstep * floor(im->minval / gridstep);
505 while (minor_y < im->minval)
507 minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
508 minor_y_px_frac = minor_y_px - floor(minor_y_px);
509 if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
510 double yfrac = minor_y_px_frac / im->ysize;
511 double range = im->maxval - im->minval;
512 im->minval = im->minval - yfrac * range;
513 im->maxval = im->maxval - yfrac * range;
514 ytr(im,DNAN); /* reset precalc */
516 calc_horizontal_grid(im); /* recalc with changed im->maxval */
520 /* reduce data reimplementation by Alex */
524 enum cf_en cf, /* which consolidation function ?*/
525 unsigned long cur_step, /* step the data currently is in */
526 time_t *start, /* start, end and step as requested ... */
527 time_t *end, /* ... by the application will be ... */
528 unsigned long *step, /* ... adjusted to represent reality */
529 unsigned long *ds_cnt, /* number of data sources in file */
530 rrd_value_t **data) /* two dimensional array containing the data */
532 int i,reduce_factor = ceil((double)(*step) / (double)cur_step);
533 unsigned long col,dst_row,row_cnt,start_offset,end_offset,skiprows=0;
534 rrd_value_t *srcptr,*dstptr;
536 (*step) = cur_step*reduce_factor; /* set new step size for reduced data */
539 row_cnt = ((*end)-(*start))/cur_step;
545 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
546 row_cnt,reduce_factor,*start,*end,cur_step);
547 for (col=0;col<row_cnt;col++) {
548 printf("time %10lu: ",*start+(col+1)*cur_step);
549 for (i=0;i<*ds_cnt;i++)
550 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
555 /* We have to combine [reduce_factor] rows of the source
556 ** into one row for the destination. Doing this we also
557 ** need to take care to combine the correct rows. First
558 ** alter the start and end time so that they are multiples
559 ** of the new step time. We cannot reduce the amount of
560 ** time so we have to move the end towards the future and
561 ** the start towards the past.
563 end_offset = (*end) % (*step);
564 start_offset = (*start) % (*step);
566 /* If there is a start offset (which cannot be more than
567 ** one destination row), skip the appropriate number of
568 ** source rows and one destination row. The appropriate
569 ** number is what we do know (start_offset/cur_step) of
570 ** the new interval (*step/cur_step aka reduce_factor).
573 printf("start_offset: %lu end_offset: %lu\n",start_offset,end_offset);
574 printf("row_cnt before: %lu\n",row_cnt);
577 (*start) = (*start)-start_offset;
578 skiprows=reduce_factor-start_offset/cur_step;
579 srcptr+=skiprows* *ds_cnt;
580 for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
584 printf("row_cnt between: %lu\n",row_cnt);
587 /* At the end we have some rows that are not going to be
588 ** used, the amount is end_offset/cur_step
591 (*end) = (*end)-end_offset+(*step);
592 skiprows = end_offset/cur_step;
596 printf("row_cnt after: %lu\n",row_cnt);
599 /* Sanity check: row_cnt should be multiple of reduce_factor */
600 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
602 if (row_cnt%reduce_factor) {
603 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
604 row_cnt,reduce_factor);
605 printf("BUG in reduce_data()\n");
609 /* Now combine reduce_factor intervals at a time
610 ** into one interval for the destination.
613 for (dst_row=0;(long int)row_cnt>=reduce_factor;dst_row++) {
614 for (col=0;col<(*ds_cnt);col++) {
615 rrd_value_t newval=DNAN;
616 unsigned long validval=0;
618 for (i=0;i<reduce_factor;i++) {
619 if (isnan(srcptr[i*(*ds_cnt)+col])) {
623 if (isnan(newval)) newval = srcptr[i*(*ds_cnt)+col];
631 newval += srcptr[i*(*ds_cnt)+col];
634 newval = min (newval,srcptr[i*(*ds_cnt)+col]);
637 /* an interval contains a failure if any subintervals contained a failure */
639 newval = max (newval,srcptr[i*(*ds_cnt)+col]);
642 newval = srcptr[i*(*ds_cnt)+col];
647 if (validval == 0){newval = DNAN;} else{
665 srcptr+=(*ds_cnt)*reduce_factor;
666 row_cnt-=reduce_factor;
668 /* If we had to alter the endtime, we didn't have enough
669 ** source rows to fill the last row. Fill it with NaN.
671 if (end_offset) for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
673 row_cnt = ((*end)-(*start))/ *step;
675 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
676 row_cnt,*start,*end,*step);
677 for (col=0;col<row_cnt;col++) {
678 printf("time %10lu: ",*start+(col+1)*(*step));
679 for (i=0;i<*ds_cnt;i++)
680 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
687 /* get the data required for the graphs from the
691 data_fetch(image_desc_t *im )
696 /* pull the data from the rrd files ... */
697 for (i=0;i< (int)im->gdes_c;i++){
698 /* only GF_DEF elements fetch data */
699 if (im->gdes[i].gf != GF_DEF)
703 /* do we have it already ?*/
704 for (ii=0;ii<i;ii++) {
705 if (im->gdes[ii].gf != GF_DEF)
707 if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
708 && (im->gdes[i].cf == im->gdes[ii].cf)
709 && (im->gdes[i].cf_reduce == im->gdes[ii].cf_reduce)
710 && (im->gdes[i].start_orig == im->gdes[ii].start_orig)
711 && (im->gdes[i].end_orig == im->gdes[ii].end_orig)
712 && (im->gdes[i].step_orig == im->gdes[ii].step_orig)) {
713 /* OK, the data is already there.
714 ** Just copy the header portion
716 im->gdes[i].start = im->gdes[ii].start;
717 im->gdes[i].end = im->gdes[ii].end;
718 im->gdes[i].step = im->gdes[ii].step;
719 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
720 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
721 im->gdes[i].data = im->gdes[ii].data;
722 im->gdes[i].data_first = 0;
729 unsigned long ft_step = im->gdes[i].step ;
731 if((rrd_fetch_fn(im->gdes[i].rrd,
737 &im->gdes[i].ds_namv,
738 &im->gdes[i].data)) == -1){
741 im->gdes[i].data_first = 1;
742 im->gdes[i].step = im->step;
744 if (ft_step < im->gdes[i].step) {
745 reduce_data(im->gdes[i].cf_reduce,
753 im->gdes[i].step = ft_step;
757 /* lets see if the required data source is really there */
758 for(ii=0;ii<(int)im->gdes[i].ds_cnt;ii++){
759 if(strcmp(im->gdes[i].ds_namv[ii],im->gdes[i].ds_nam) == 0){
762 if (im->gdes[i].ds== -1){
763 rrd_set_error("No DS called '%s' in '%s'",
764 im->gdes[i].ds_nam,im->gdes[i].rrd);
772 /* evaluate the expressions in the CDEF functions */
774 /*************************************************************
776 *************************************************************/
779 find_var_wrapper(void *arg1, char *key)
781 return find_var((image_desc_t *) arg1, key);
784 /* find gdes containing var*/
786 find_var(image_desc_t *im, char *key){
788 for(ii=0;ii<im->gdes_c-1;ii++){
789 if((im->gdes[ii].gf == GF_DEF
790 || im->gdes[ii].gf == GF_VDEF
791 || im->gdes[ii].gf == GF_CDEF)
792 && (strcmp(im->gdes[ii].vname,key) == 0)){
799 /* find the largest common denominator for all the numbers
800 in the 0 terminated num array */
805 for (i=0;num[i+1]!=0;i++){
807 rest=num[i] % num[i+1];
808 num[i]=num[i+1]; num[i+1]=rest;
812 /* return i==0?num[i]:num[i-1]; */
816 /* run the rpn calculator on all the VDEF and CDEF arguments */
818 data_calc( image_desc_t *im){
822 long *steparray, rpi;
827 rpnstack_init(&rpnstack);
829 for (gdi=0;gdi<im->gdes_c;gdi++){
830 /* Look for GF_VDEF and GF_CDEF in the same loop,
831 * so CDEFs can use VDEFs and vice versa
833 switch (im->gdes[gdi].gf) {
837 graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx];
839 /* remove current shift */
840 vdp->start -= vdp->shift;
841 vdp->end -= vdp->shift;
844 if (im->gdes[gdi].shidx >= 0)
845 vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
848 vdp->shift = im->gdes[gdi].shval;
850 /* normalize shift to multiple of consolidated step */
851 vdp->shift = (vdp->shift / (long)vdp->step) * (long)vdp->step;
854 vdp->start += vdp->shift;
855 vdp->end += vdp->shift;
859 /* A VDEF has no DS. This also signals other parts
860 * of rrdtool that this is a VDEF value, not a CDEF.
862 im->gdes[gdi].ds_cnt = 0;
863 if (vdef_calc(im,gdi)) {
864 rrd_set_error("Error processing VDEF '%s'"
867 rpnstack_free(&rpnstack);
872 im->gdes[gdi].ds_cnt = 1;
873 im->gdes[gdi].ds = 0;
874 im->gdes[gdi].data_first = 1;
875 im->gdes[gdi].start = 0;
876 im->gdes[gdi].end = 0;
881 /* Find the variables in the expression.
882 * - VDEF variables are substituted by their values
883 * and the opcode is changed into OP_NUMBER.
884 * - CDEF variables are analized for their step size,
885 * the lowest common denominator of all the step
886 * sizes of the data sources involved is calculated
887 * and the resulting number is the step size for the
888 * resulting data source.
890 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
891 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
892 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER){
893 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
894 if (im->gdes[ptr].ds_cnt == 0) { /* this is a VDEF data source */
896 printf("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
898 im->gdes[ptr].vname);
899 printf("DEBUG: value from vdef is %f\n",im->gdes[ptr].vf.val);
901 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
902 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
903 } else { /* normal variables and PREF(variables) */
905 /* add one entry to the array that keeps track of the step sizes of the
906 * data sources going into the CDEF. */
908 rrd_realloc(steparray,
909 (++stepcnt+1)*sizeof(*steparray)))==NULL){
910 rrd_set_error("realloc steparray");
911 rpnstack_free(&rpnstack);
915 steparray[stepcnt-1] = im->gdes[ptr].step;
917 /* adjust start and end of cdef (gdi) so
918 * that it runs from the latest start point
919 * to the earliest endpoint of any of the
920 * rras involved (ptr)
923 if(im->gdes[gdi].start < im->gdes[ptr].start)
924 im->gdes[gdi].start = im->gdes[ptr].start;
926 if(im->gdes[gdi].end == 0 ||
927 im->gdes[gdi].end > im->gdes[ptr].end)
928 im->gdes[gdi].end = im->gdes[ptr].end;
930 /* store pointer to the first element of
931 * the rra providing data for variable,
932 * further save step size and data source
935 im->gdes[gdi].rpnp[rpi].data = im->gdes[ptr].data + im->gdes[ptr].ds;
936 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
937 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
939 /* backoff the *.data ptr; this is done so
940 * rpncalc() function doesn't have to treat
941 * the first case differently
943 } /* if ds_cnt != 0 */
944 } /* if OP_VARIABLE */
945 } /* loop through all rpi */
947 /* move the data pointers to the correct period */
948 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
949 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
950 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER){
951 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
952 long diff = im->gdes[gdi].start - im->gdes[ptr].start;
955 im->gdes[gdi].rpnp[rpi].data += (diff / im->gdes[ptr].step) * im->gdes[ptr].ds_cnt;
959 if(steparray == NULL){
960 rrd_set_error("rpn expressions without DEF"
961 " or CDEF variables are not supported");
962 rpnstack_free(&rpnstack);
965 steparray[stepcnt]=0;
966 /* Now find the resulting step. All steps in all
967 * used RRAs have to be visited
969 im->gdes[gdi].step = lcd(steparray);
971 if((im->gdes[gdi].data = malloc((
972 (im->gdes[gdi].end-im->gdes[gdi].start)
973 / im->gdes[gdi].step)
974 * sizeof(double)))==NULL){
975 rrd_set_error("malloc im->gdes[gdi].data");
976 rpnstack_free(&rpnstack);
980 /* Step through the new cdef results array and
981 * calculate the values
983 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
984 now<=im->gdes[gdi].end;
985 now += im->gdes[gdi].step)
987 rpnp_t *rpnp = im -> gdes[gdi].rpnp;
989 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
990 * in this case we are advancing by timesteps;
991 * we use the fact that time_t is a synonym for long
993 if (rpn_calc(rpnp,&rpnstack,(long) now,
994 im->gdes[gdi].data,++dataidx) == -1) {
995 /* rpn_calc sets the error string */
996 rpnstack_free(&rpnstack);
999 } /* enumerate over time steps within a CDEF */
1004 } /* enumerate over CDEFs */
1005 rpnstack_free(&rpnstack);
1009 /* massage data so, that we get one value for each x coordinate in the graph */
1011 data_proc( image_desc_t *im ){
1013 double pixstep = (double)(im->end-im->start)
1014 /(double)im->xsize; /* how much time
1015 passes in one pixel */
1017 double minval=DNAN,maxval=DNAN;
1019 unsigned long gr_time;
1021 /* memory for the processed data */
1022 for(i=0;i<im->gdes_c;i++) {
1023 if((im->gdes[i].gf==GF_LINE) ||
1024 (im->gdes[i].gf==GF_AREA) ||
1025 (im->gdes[i].gf==GF_TICK)) {
1026 if((im->gdes[i].p_data = malloc((im->xsize +1)
1027 * sizeof(rrd_value_t)))==NULL){
1028 rrd_set_error("malloc data_proc");
1034 for (i=0;i<im->xsize;i++) { /* for each pixel */
1036 gr_time = im->start+pixstep*i; /* time of the current step */
1039 for (ii=0;ii<im->gdes_c;ii++) {
1041 switch (im->gdes[ii].gf) {
1045 if (!im->gdes[ii].stack)
1047 value = im->gdes[ii].yrule;
1048 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1049 /* The time of the data doesn't necessarily match
1050 ** the time of the graph. Beware.
1052 vidx = im->gdes[ii].vidx;
1053 if (im->gdes[vidx].gf == GF_VDEF) {
1054 value = im->gdes[vidx].vf.val;
1055 } else if (((long int)gr_time >= (long int)im->gdes[vidx].start) &&
1056 ((long int)gr_time <= (long int)im->gdes[vidx].end) ) {
1057 value = im->gdes[vidx].data[
1058 (unsigned long) floor(
1059 (double)(gr_time - im->gdes[vidx].start)
1060 / im->gdes[vidx].step)
1061 * im->gdes[vidx].ds_cnt
1069 if (! isnan(value)) {
1071 im->gdes[ii].p_data[i] = paintval;
1072 /* GF_TICK: the data values are not
1073 ** relevant for min and max
1075 if (finite(paintval) && im->gdes[ii].gf != GF_TICK ) {
1076 if (isnan(minval) || paintval < minval)
1078 if (isnan(maxval) || paintval > maxval)
1082 im->gdes[ii].p_data[i] = DNAN;
1086 rrd_set_error("STACK should already be turned into LINE or AREA here");
1095 /* if min or max have not been asigned a value this is because
1096 there was no data in the graph ... this is not good ...
1097 lets set these to dummy values then ... */
1099 if (isnan(minval)) minval = 0.0;
1100 if (isnan(maxval)) maxval = 1.0;
1102 /* adjust min and max values */
1103 if (isnan(im->minval)
1104 /* don't adjust low-end with log scale */
1105 || ((!im->logarithmic && !im->rigid) && im->minval > minval)
1107 im->minval = minval;
1108 if (isnan(im->maxval)
1109 || (!im->rigid && im->maxval < maxval)
1111 if (im->logarithmic)
1112 im->maxval = maxval * 1.1;
1114 im->maxval = maxval;
1116 /* make sure min is smaller than max */
1117 if (im->minval > im->maxval) {
1118 im->minval = 0.99 * im->maxval;
1121 /* make sure min and max are not equal */
1122 if (im->minval == im->maxval) {
1124 if (! im->logarithmic) {
1127 /* make sure min and max are not both zero */
1128 if (im->maxval == 0.0) {
1137 /* identify the point where the first gridline, label ... gets placed */
1141 time_t start, /* what is the initial time */
1142 enum tmt_en baseint, /* what is the basic interval */
1143 long basestep /* how many if these do we jump a time */
1147 localtime_r(&start, &tm);
1150 tm.tm_sec -= tm.tm_sec % basestep; break;
1153 tm.tm_min -= tm.tm_min % basestep;
1158 tm.tm_hour -= tm.tm_hour % basestep; break;
1160 /* we do NOT look at the basestep for this ... */
1163 tm.tm_hour = 0; break;
1165 /* we do NOT look at the basestep for this ... */
1169 tm.tm_mday -= tm.tm_wday -1; /* -1 because we want the monday */
1170 if (tm.tm_wday==0) tm.tm_mday -= 7; /* we want the *previous* monday */
1177 tm.tm_mon -= tm.tm_mon % basestep; break;
1185 tm.tm_year -= (tm.tm_year+1900) % basestep;
1190 /* identify the point where the next gridline, label ... gets placed */
1193 time_t current, /* what is the initial time */
1194 enum tmt_en baseint, /* what is the basic interval */
1195 long basestep /* how many if these do we jump a time */
1200 localtime_r(¤t, &tm);
1204 tm.tm_sec += basestep; break;
1206 tm.tm_min += basestep; break;
1208 tm.tm_hour += basestep; break;
1210 tm.tm_mday += basestep; break;
1212 tm.tm_mday += 7*basestep; break;
1214 tm.tm_mon += basestep; break;
1216 tm.tm_year += basestep;
1218 madetime = mktime(&tm);
1219 } while (madetime == -1); /* this is necessary to skip impssible times
1220 like the daylight saving time skips */
1226 /* calculate values required for PRINT and GPRINT functions */
1229 print_calc(image_desc_t *im, char ***prdata)
1231 long i,ii,validsteps;
1234 int graphelement = 0;
1237 double magfact = -1;
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 printtime = im->gdes[vidx].vf.when;
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 (!strcmp(im->gdes[i].format,"%c")) { /* VDEF time print */
1305 char ctime_buf[128]; /* PS: for ctime_r, must be >= 26 chars */
1307 ctime_r(&printtime,ctime_buf);
1308 while(isprint(ctime_buf[iii])){iii++;}
1309 ctime_buf[iii]='\0';
1310 if (im->gdes[i].gf == GF_PRINT){
1311 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1312 sprintf((*prdata)[prlines-2],"%s (%lu)",ctime_buf,printtime);
1313 (*prdata)[prlines-1] = NULL;
1315 sprintf(im->gdes[i].legend,"%s (%lu)",ctime_buf,printtime);
1319 if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) {
1320 /* Magfact is set to -1 upon entry to print_calc. If it
1321 * is still less than 0, then we need to run auto_scale.
1322 * Otherwise, put the value into the correct units. If
1323 * the value is 0, then do not set the symbol or magnification
1324 * so next the calculation will be performed again. */
1325 if (magfact < 0.0) {
1326 auto_scale(im,&printval,&si_symb,&magfact);
1327 if (printval == 0.0)
1330 printval /= magfact;
1332 *(++percent_s) = 's';
1333 } else if (strstr(im->gdes[i].format,"%s") != NULL) {
1334 auto_scale(im,&printval,&si_symb,&magfact);
1337 if (im->gdes[i].gf == GF_PRINT){
1338 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1339 (*prdata)[prlines-1] = NULL;
1340 if (bad_format(im->gdes[i].format)) {
1341 rrd_set_error("bad format for PRINT in '%s'", im->gdes[i].format);
1344 #ifdef HAVE_SNPRINTF
1345 snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
1347 sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
1352 if (bad_format(im->gdes[i].format)) {
1353 rrd_set_error("bad format for GPRINT in '%s'", im->gdes[i].format);
1356 #ifdef HAVE_SNPRINTF
1357 snprintf(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
1359 sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
1376 #ifdef WITH_PIECHART
1383 rrd_set_error("STACK should already be turned into LINE or AREA here");
1388 return graphelement;
1392 /* place legends with color spots */
1394 leg_place(image_desc_t *im)
1397 int interleg = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1398 int border = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1399 int fill=0, fill_last;
1401 int leg_x = border, leg_y = im->yimg;
1405 char prt_fctn; /*special printfunctions */
1408 if( !(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH) ) {
1409 if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1410 rrd_set_error("malloc for legspace");
1414 for(i=0;i<im->gdes_c;i++){
1417 /* hid legends for rules which are not displayed */
1419 if(!(im->extra_flags & FORCE_RULES_LEGEND)) {
1420 if (im->gdes[i].gf == GF_HRULE &&
1421 (im->gdes[i].yrule < im->minval || im->gdes[i].yrule > im->maxval))
1422 im->gdes[i].legend[0] = '\0';
1424 if (im->gdes[i].gf == GF_VRULE &&
1425 (im->gdes[i].xrule < im->start || im->gdes[i].xrule > im->end))
1426 im->gdes[i].legend[0] = '\0';
1429 leg_cc = strlen(im->gdes[i].legend);
1431 /* is there a controle code ant the end of the legend string ? */
1432 /* and it is not a tab \\t */
1433 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\' && im->gdes[i].legend[leg_cc-1] != 't') {
1434 prt_fctn = im->gdes[i].legend[leg_cc-1];
1436 im->gdes[i].legend[leg_cc] = '\0';
1440 /* remove exess space */
1441 while (prt_fctn=='g' &&
1443 im->gdes[i].legend[leg_cc-1]==' '){
1445 im->gdes[i].legend[leg_cc]='\0';
1448 legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1451 /* no interleg space if string ends in \g */
1452 fill += legspace[i];
1454 fill += gfx_get_text_width(im->canvas, fill+border,
1455 im->text_prop[TEXT_PROP_LEGEND].font,
1456 im->text_prop[TEXT_PROP_LEGEND].size,
1458 im->gdes[i].legend, 0);
1463 /* who said there was a special tag ... ?*/
1464 if (prt_fctn=='g') {
1467 if (prt_fctn == '\0') {
1468 if (i == im->gdes_c -1 ) prt_fctn ='l';
1470 /* is it time to place the legends ? */
1471 if (fill > im->ximg - 2*border){
1486 if (prt_fctn != '\0'){
1488 if (leg_c >= 2 && prt_fctn == 'j') {
1489 glue = (im->ximg - fill - 2* border) / (leg_c-1);
1493 if (prt_fctn =='c') leg_x = (im->ximg - fill) / 2.0;
1494 if (prt_fctn =='r') leg_x = im->ximg - fill - border;
1496 for(ii=mark;ii<=i;ii++){
1497 if(im->gdes[ii].legend[0]=='\0')
1498 continue; /* skip empty legends */
1499 im->gdes[ii].leg_x = leg_x;
1500 im->gdes[ii].leg_y = leg_y;
1502 gfx_get_text_width(im->canvas, leg_x,
1503 im->text_prop[TEXT_PROP_LEGEND].font,
1504 im->text_prop[TEXT_PROP_LEGEND].size,
1506 im->gdes[ii].legend, 0)
1510 leg_y += im->text_prop[TEXT_PROP_LEGEND].size*1.8;
1511 if (prt_fctn == 's') leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1523 /* create a grid on the graph. it determines what to do
1524 from the values of xsize, start and end */
1526 /* the xaxis labels are determined from the number of seconds per pixel
1527 in the requested graph */
1532 calc_horizontal_grid(image_desc_t *im)
1538 int decimals, fractionals;
1540 im->ygrid_scale.labfact=2;
1541 range = im->maxval - im->minval;
1542 scaledrange = range / im->magfact;
1544 /* does the scale of this graph make it impossible to put lines
1545 on it? If so, give up. */
1546 if (isnan(scaledrange)) {
1550 /* find grid spaceing */
1552 if(isnan(im->ygridstep)){
1553 if(im->extra_flags & ALTYGRID) {
1554 /* find the value with max number of digits. Get number of digits */
1555 decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))*im->viewfactor/im->magfact));
1556 if(decimals <= 0) /* everything is small. make place for zero */
1559 im->ygrid_scale.gridstep = pow((double)10, floor(log10(range*im->viewfactor/im->magfact)))/im->viewfactor*im->magfact;
1561 if(im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1562 im->ygrid_scale.gridstep = 0.1;
1563 /* should have at least 5 lines but no more then 15 */
1564 if(range/im->ygrid_scale.gridstep < 5)
1565 im->ygrid_scale.gridstep /= 10;
1566 if(range/im->ygrid_scale.gridstep > 15)
1567 im->ygrid_scale.gridstep *= 10;
1568 if(range/im->ygrid_scale.gridstep > 5) {
1569 im->ygrid_scale.labfact = 1;
1570 if(range/im->ygrid_scale.gridstep > 8)
1571 im->ygrid_scale.labfact = 2;
1574 im->ygrid_scale.gridstep /= 5;
1575 im->ygrid_scale.labfact = 5;
1577 fractionals = floor(log10(im->ygrid_scale.gridstep*(double)im->ygrid_scale.labfact*im->viewfactor/im->magfact));
1578 if(fractionals < 0) { /* small amplitude. */
1579 int len = decimals - fractionals + 1;
1580 if (im->unitslength < len+2) im->unitslength = len+2;
1581 sprintf(im->ygrid_scale.labfmt, "%%%d.%df%s", len, -fractionals,(im->symbol != ' ' ? " %c" : ""));
1583 int len = decimals + 1;
1584 if (im->unitslength < len+2) im->unitslength = len+2;
1585 sprintf(im->ygrid_scale.labfmt, "%%%d.0f%s", len, ( im->symbol != ' ' ? " %c" : "" ));
1589 for(i=0;ylab[i].grid > 0;i++){
1590 pixel = im->ysize / (scaledrange / ylab[i].grid);
1597 if (pixel * ylab[gridind].lfac[i] >= 2.5 * im->text_prop[TEXT_PROP_AXIS].size) {
1598 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1603 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1606 im->ygrid_scale.gridstep = im->ygridstep;
1607 im->ygrid_scale.labfact = im->ylabfact;
1612 int draw_horizontal_grid(image_desc_t *im)
1616 char graph_label[100];
1618 double X0=im->xorigin;
1619 double X1=im->xorigin+im->xsize;
1621 int sgrid = (int)( im->minval / im->ygrid_scale.gridstep - 1);
1622 int egrid = (int)( im->maxval / im->ygrid_scale.gridstep + 1);
1624 scaledstep = im->ygrid_scale.gridstep/(double)im->magfact*(double)im->viewfactor;
1625 MaxY = scaledstep*(double)egrid;
1626 for (i = sgrid; i <= egrid; i++){
1627 double Y0=ytr(im,im->ygrid_scale.gridstep*i);
1628 double YN=ytr(im,im->ygrid_scale.gridstep*(i+1));
1629 if ( Y0 >= im->yorigin-im->ysize
1630 && Y0 <= im->yorigin){
1631 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
1632 with the chosen settings. Add a label if required by settings, or if
1633 there is only one label so far and the next grid line is out of bounds. */
1634 if(i % im->ygrid_scale.labfact == 0 || ( nlabels==1 && (YN < im->yorigin-im->ysize || YN > im->yorigin) )){
1635 if (im->symbol == ' ') {
1636 if(im->extra_flags & ALTYGRID) {
1637 sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*(double)i);
1640 sprintf(graph_label,"%4.1f",scaledstep*(double)i);
1642 sprintf(graph_label,"%4.0f",scaledstep*(double)i);
1646 char sisym = ( i == 0 ? ' ' : im->symbol);
1647 if(im->extra_flags & ALTYGRID) {
1648 sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*(double)i,sisym);
1651 sprintf(graph_label,"%4.1f %c",scaledstep*(double)i, sisym);
1653 sprintf(graph_label,"%4.0f %c",scaledstep*(double)i, sisym);
1659 gfx_new_text ( im->canvas,
1660 X0-im->text_prop[TEXT_PROP_AXIS].size, Y0,
1661 im->graph_col[GRC_FONT],
1662 im->text_prop[TEXT_PROP_AXIS].font,
1663 im->text_prop[TEXT_PROP_AXIS].size,
1664 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1666 gfx_new_dashed_line ( im->canvas,
1669 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1670 im->grid_dash_on, im->grid_dash_off);
1672 } else if (!(im->extra_flags & NOMINOR)) {
1673 gfx_new_dashed_line ( im->canvas,
1676 GRIDWIDTH, im->graph_col[GRC_GRID],
1677 im->grid_dash_on, im->grid_dash_off);
1685 /* logaritmic horizontal grid */
1687 horizontal_log_grid(image_desc_t *im)
1691 int minoridx=0, majoridx=0;
1692 char graph_label[100];
1694 double value, pixperstep, minstep;
1696 /* find grid spaceing */
1697 pixpex= (double)im->ysize / (log10(im->maxval) - log10(im->minval));
1699 if (isnan(pixpex)) {
1703 for(i=0;yloglab[i][0] > 0;i++){
1704 minstep = log10(yloglab[i][0]);
1705 for(ii=1;yloglab[i][ii+1] > 0;ii++){
1706 if(yloglab[i][ii+2]==0){
1707 minstep = log10(yloglab[i][ii+1])-log10(yloglab[i][ii]);
1711 pixperstep = pixpex * minstep;
1712 if(pixperstep > 5){minoridx = i;}
1713 if(pixperstep > 2 * im->text_prop[TEXT_PROP_LEGEND].size){majoridx = i;}
1717 X1=im->xorigin+im->xsize;
1718 /* paint minor grid */
1719 for (value = pow((double)10, log10(im->minval)
1720 - fmod(log10(im->minval),log10(yloglab[minoridx][0])));
1721 value <= im->maxval;
1722 value *= yloglab[minoridx][0]){
1723 if (value < im->minval) continue;
1725 while(yloglab[minoridx][++i] > 0){
1726 Y0 = ytr(im,value * yloglab[minoridx][i]);
1727 if (Y0 <= im->yorigin - im->ysize) break;
1728 gfx_new_dashed_line ( im->canvas,
1731 GRIDWIDTH, im->graph_col[GRC_GRID],
1732 im->grid_dash_on, im->grid_dash_off);
1736 /* paint major grid and labels*/
1737 for (value = pow((double)10, log10(im->minval)
1738 - fmod(log10(im->minval),log10(yloglab[majoridx][0])));
1739 value <= im->maxval;
1740 value *= yloglab[majoridx][0]){
1741 if (value < im->minval) continue;
1743 while(yloglab[majoridx][++i] > 0){
1744 Y0 = ytr(im,value * yloglab[majoridx][i]);
1745 if (Y0 <= im->yorigin - im->ysize) break;
1746 gfx_new_dashed_line ( im->canvas,
1749 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1750 im->grid_dash_on, im->grid_dash_off);
1752 if (im->extra_flags & FORCE_UNITS_SI) {
1753 double pvalue = value * yloglab[majoridx][i];
1754 double scale = floor( log10( fabs(pvalue)) / 3);
1757 pvalue /= pow(10, 3*scale);
1759 if ( ((scale+si_symbcenter) < sizeof(si_symbol)) &&
1760 ((scale+si_symbcenter) >= 0) )
1761 symbol = si_symbol[(int)scale+si_symbcenter];
1765 sprintf(graph_label,"%3.0f %c", pvalue, symbol);
1767 sprintf(graph_label,"%3.0e",value * yloglab[majoridx][i]);
1769 gfx_new_text ( im->canvas,
1770 X0-im->text_prop[TEXT_PROP_AXIS].size, Y0,
1771 im->graph_col[GRC_FONT],
1772 im->text_prop[TEXT_PROP_AXIS].font,
1773 im->text_prop[TEXT_PROP_AXIS].size,
1774 im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1786 int xlab_sel; /* which sort of label and grid ? */
1787 time_t ti, tilab, timajor;
1789 char graph_label[100];
1790 double X0,Y0,Y1; /* points for filled graph and more*/
1793 /* the type of time grid is determined by finding
1794 the number of seconds per pixel in the graph */
1797 if(im->xlab_user.minsec == -1){
1798 factor=(im->end - im->start)/im->xsize;
1800 while ( xlab[xlab_sel+1].minsec != -1
1801 && xlab[xlab_sel+1].minsec <= factor) { xlab_sel++; } /* pick the last one */
1802 while ( xlab[xlab_sel-1].minsec == xlab[xlab_sel].minsec
1803 && xlab[xlab_sel].length > (im->end - im->start)) { xlab_sel--; } /* go back to the smallest size */
1804 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
1805 im->xlab_user.gridst = xlab[xlab_sel].gridst;
1806 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
1807 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
1808 im->xlab_user.labtm = xlab[xlab_sel].labtm;
1809 im->xlab_user.labst = xlab[xlab_sel].labst;
1810 im->xlab_user.precis = xlab[xlab_sel].precis;
1811 im->xlab_user.stst = xlab[xlab_sel].stst;
1814 /* y coords are the same for every line ... */
1816 Y1 = im->yorigin-im->ysize;
1819 /* paint the minor grid */
1820 if (!(im->extra_flags & NOMINOR))
1822 for(ti = find_first_time(im->start,
1823 im->xlab_user.gridtm,
1824 im->xlab_user.gridst),
1825 timajor = find_first_time(im->start,
1826 im->xlab_user.mgridtm,
1827 im->xlab_user.mgridst);
1829 ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
1831 /* are we inside the graph ? */
1832 if (ti < im->start || ti > im->end) continue;
1833 while (timajor < ti) {
1834 timajor = find_next_time(timajor,
1835 im->xlab_user.mgridtm, im->xlab_user.mgridst);
1837 if (ti == timajor) continue; /* skip as falls on major grid line */
1839 gfx_new_dashed_line(im->canvas,X0,Y0+1, X0,Y1-1,GRIDWIDTH,
1840 im->graph_col[GRC_GRID],
1841 im->grid_dash_on, im->grid_dash_off);
1846 /* paint the major grid */
1847 for(ti = find_first_time(im->start,
1848 im->xlab_user.mgridtm,
1849 im->xlab_user.mgridst);
1851 ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
1853 /* are we inside the graph ? */
1854 if (ti < im->start || ti > im->end) continue;
1856 gfx_new_dashed_line(im->canvas,X0,Y0+3, X0,Y1-2,MGRIDWIDTH,
1857 im->graph_col[GRC_MGRID],
1858 im->grid_dash_on, im->grid_dash_off);
1861 /* paint the labels below the graph */
1862 for(ti = find_first_time(im->start - im->xlab_user.precis/2,
1863 im->xlab_user.labtm,
1864 im->xlab_user.labst);
1865 ti <= im->end - im->xlab_user.precis/2;
1866 ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
1868 tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
1869 /* are we inside the graph ? */
1870 if (tilab < im->start || tilab > im->end) continue;
1873 localtime_r(&tilab, &tm);
1874 strftime(graph_label,99,im->xlab_user.stst, &tm);
1876 # error "your libc has no strftime I guess we'll abort the exercise here."
1878 gfx_new_text ( im->canvas,
1879 xtr(im,tilab), Y0+im->text_prop[TEXT_PROP_AXIS].size*1.4+5,
1880 im->graph_col[GRC_FONT],
1881 im->text_prop[TEXT_PROP_AXIS].font,
1882 im->text_prop[TEXT_PROP_AXIS].size,
1883 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_BOTTOM,
1896 /* draw x and y axis */
1897 /* gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
1898 im->xorigin+im->xsize,im->yorigin-im->ysize,
1899 GRIDWIDTH, im->graph_col[GRC_AXIS]);
1901 gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
1902 im->xorigin+im->xsize,im->yorigin-im->ysize,
1903 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
1905 gfx_new_line ( im->canvas, im->xorigin-4,im->yorigin,
1906 im->xorigin+im->xsize+4,im->yorigin,
1907 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
1909 gfx_new_line ( im->canvas, im->xorigin,im->yorigin+4,
1910 im->xorigin,im->yorigin-im->ysize-4,
1911 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
1914 /* arrow for X and Y axis direction */
1915 gfx_new_area ( im->canvas,
1916 im->xorigin+im->xsize+2, im->yorigin-2,
1917 im->xorigin+im->xsize+2, im->yorigin+3,
1918 im->xorigin+im->xsize+7, im->yorigin+0.5, /* LINEOFFSET */
1919 im->graph_col[GRC_ARROW]);
1921 gfx_new_area ( im->canvas,
1922 im->xorigin-2, im->yorigin-im->ysize-2,
1923 im->xorigin+3, im->yorigin-im->ysize-2,
1924 im->xorigin+0.5, im->yorigin-im->ysize-7, /* LINEOFFSET */
1925 im->graph_col[GRC_ARROW]);
1930 grid_paint(image_desc_t *im)
1934 double X0,Y0; /* points for filled graph and more*/
1937 /* draw 3d border */
1938 node = gfx_new_area (im->canvas, 0,im->yimg,
1940 2,2,im->graph_col[GRC_SHADEA]);
1941 gfx_add_point( node , im->ximg - 2, 2 );
1942 gfx_add_point( node , im->ximg, 0 );
1943 gfx_add_point( node , 0,0 );
1944 /* gfx_add_point( node , 0,im->yimg ); */
1946 node = gfx_new_area (im->canvas, 2,im->yimg-2,
1947 im->ximg-2,im->yimg-2,
1949 im->graph_col[GRC_SHADEB]);
1950 gfx_add_point( node , im->ximg,0);
1951 gfx_add_point( node , im->ximg,im->yimg);
1952 gfx_add_point( node , 0,im->yimg);
1953 /* gfx_add_point( node , 0,im->yimg ); */
1956 if (im->draw_x_grid == 1 )
1959 if (im->draw_y_grid == 1){
1960 if(im->logarithmic){
1961 res = horizontal_log_grid(im);
1963 res = draw_horizontal_grid(im);
1966 /* dont draw horizontal grid if there is no min and max val */
1968 char *nodata = "No Data found";
1969 gfx_new_text(im->canvas,im->ximg/2, (2*im->yorigin-im->ysize) / 2,
1970 im->graph_col[GRC_FONT],
1971 im->text_prop[TEXT_PROP_AXIS].font,
1972 im->text_prop[TEXT_PROP_AXIS].size,
1973 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
1978 /* yaxis unit description */
1979 gfx_new_text( im->canvas,
1980 10, (im->yorigin - im->ysize/2),
1981 im->graph_col[GRC_FONT],
1982 im->text_prop[TEXT_PROP_UNIT].font,
1983 im->text_prop[TEXT_PROP_UNIT].size, im->tabwidth,
1984 RRDGRAPH_YLEGEND_ANGLE,
1985 GFX_H_LEFT, GFX_V_CENTER,
1989 gfx_new_text( im->canvas,
1990 im->ximg/2, im->text_prop[TEXT_PROP_TITLE].size*1.3+4,
1991 im->graph_col[GRC_FONT],
1992 im->text_prop[TEXT_PROP_TITLE].font,
1993 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
1994 GFX_H_CENTER, GFX_V_CENTER,
1996 /* rrdtool 'logo' */
1997 gfx_new_text( im->canvas,
1999 ( im->graph_col[GRC_FONT] & 0xffffff00 ) | 0x00000044,
2000 im->text_prop[TEXT_PROP_AXIS].font,
2001 5.5, im->tabwidth, 270,
2002 GFX_H_RIGHT, GFX_V_TOP,
2003 "RRDTOOL / TOBI OETIKER");
2005 /* graph watermark */
2006 if(im->watermark[0] != '\0') {
2007 gfx_new_text( im->canvas,
2008 im->ximg/2, im->yimg-6,
2009 ( im->graph_col[GRC_FONT] & 0xffffff00 ) | 0x00000044,
2010 im->text_prop[TEXT_PROP_AXIS].font,
2011 5.5, im->tabwidth, 0,
2012 GFX_H_CENTER, GFX_V_BOTTOM,
2017 if( !(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH) ) {
2018 for(i=0;i<im->gdes_c;i++){
2019 if(im->gdes[i].legend[0] =='\0')
2022 /* im->gdes[i].leg_y is the bottom of the legend */
2023 X0 = im->gdes[i].leg_x;
2024 Y0 = im->gdes[i].leg_y;
2025 gfx_new_text ( im->canvas, X0, Y0,
2026 im->graph_col[GRC_FONT],
2027 im->text_prop[TEXT_PROP_LEGEND].font,
2028 im->text_prop[TEXT_PROP_LEGEND].size,
2029 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
2030 im->gdes[i].legend );
2031 /* The legend for GRAPH items starts with "M " to have
2032 enough space for the box */
2033 if ( im->gdes[i].gf != GF_PRINT &&
2034 im->gdes[i].gf != GF_GPRINT &&
2035 im->gdes[i].gf != GF_COMMENT) {
2038 boxH = gfx_get_text_width(im->canvas, 0,
2039 im->text_prop[TEXT_PROP_LEGEND].font,
2040 im->text_prop[TEXT_PROP_LEGEND].size,
2041 im->tabwidth,"o", 0) * 1.2;
2044 /* make sure transparent colors show up the same way as in the graph */
2045 node = gfx_new_area(im->canvas,
2049 im->graph_col[GRC_BACK]);
2050 gfx_add_point ( node, X0+boxH, Y0-boxV );
2052 node = gfx_new_area(im->canvas,
2057 gfx_add_point ( node, X0+boxH, Y0-boxV );
2058 node = gfx_new_line(im->canvas,
2061 1.0,im->graph_col[GRC_FRAME]);
2062 gfx_add_point(node,X0+boxH,Y0);
2063 gfx_add_point(node,X0+boxH,Y0-boxV);
2064 gfx_close_path(node);
2071 /*****************************************************
2072 * lazy check make sure we rely need to create this graph
2073 *****************************************************/
2075 int lazy_check(image_desc_t *im){
2078 struct stat imgstat;
2080 if (im->lazy == 0) return 0; /* no lazy option */
2081 if (stat(im->graphfile,&imgstat) != 0)
2082 return 0; /* can't stat */
2083 /* one pixel in the existing graph is more then what we would
2085 if (time(NULL) - imgstat.st_mtime >
2086 (im->end - im->start) / im->xsize)
2088 if ((fd = fopen(im->graphfile,"rb")) == NULL)
2089 return 0; /* the file does not exist */
2090 switch (im->canvas->imgformat) {
2092 size = PngSize(fd,&(im->ximg),&(im->yimg));
2101 #ifdef WITH_PIECHART
2103 pie_part(image_desc_t *im, gfx_color_t color,
2104 double PieCenterX, double PieCenterY, double Radius,
2105 double startangle, double endangle)
2109 double step=M_PI/50; /* Number of iterations for the circle;
2110 ** 10 is definitely too low, more than
2111 ** 50 seems to be overkill
2114 /* Strange but true: we have to work clockwise or else
2115 ** anti aliasing nor transparency don't work.
2117 ** This test is here to make sure we do it right, also
2118 ** this makes the for...next loop more easy to implement.
2119 ** The return will occur if the user enters a negative number
2120 ** (which shouldn't be done according to the specs) or if the
2121 ** programmers do something wrong (which, as we all know, never
2122 ** happens anyway :)
2124 if (endangle<startangle) return;
2126 /* Hidden feature: Radius decreases each full circle */
2128 while (angle>=2*M_PI) {
2133 node=gfx_new_area(im->canvas,
2134 PieCenterX+sin(startangle)*Radius,
2135 PieCenterY-cos(startangle)*Radius,
2138 PieCenterX+sin(endangle)*Radius,
2139 PieCenterY-cos(endangle)*Radius,
2141 for (angle=endangle;angle-startangle>=step;angle-=step) {
2143 PieCenterX+sin(angle)*Radius,
2144 PieCenterY-cos(angle)*Radius );
2151 graph_size_location(image_desc_t *im, int elements
2153 #ifdef WITH_PIECHART
2159 /* The actual size of the image to draw is determined from
2160 ** several sources. The size given on the command line is
2161 ** the graph area but we need more as we have to draw labels
2162 ** and other things outside the graph area
2165 /* +-+-------------------------------------------+
2166 ** |l|.................title.....................|
2167 ** |e+--+-------------------------------+--------+
2170 ** |l| l| main graph area | chart |
2173 ** |r+--+-------------------------------+--------+
2174 ** |e| | x-axis labels | |
2175 ** |v+--+-------------------------------+--------+
2176 ** | |..............legends......................|
2177 ** +-+-------------------------------------------+
2179 ** +---------------------------------------------+
2185 #ifdef WITH_PIECHART
2190 Xlegend =0, Ylegend =0,
2192 Xspacing =15, Yspacing =15,
2196 if (im->extra_flags & ONLY_GRAPH) {
2198 im->ximg = im->xsize;
2199 im->yimg = im->ysize;
2200 im->yorigin = im->ysize;
2205 if (im->ylegend[0] != '\0' ) {
2206 Xvertical = im->text_prop[TEXT_PROP_UNIT].size *2;
2210 if (im->title[0] != '\0') {
2211 /* The title is placed "inbetween" two text lines so it
2212 ** automatically has some vertical spacing. The horizontal
2213 ** spacing is added here, on each side.
2215 /* don't care for the with of the title
2216 Xtitle = gfx_get_text_width(im->canvas, 0,
2217 im->text_prop[TEXT_PROP_TITLE].font,
2218 im->text_prop[TEXT_PROP_TITLE].size,
2220 im->title, 0) + 2*Xspacing; */
2221 Ytitle = im->text_prop[TEXT_PROP_TITLE].size*2.6+10;
2227 if (im->draw_x_grid) {
2228 Yxlabel=im->text_prop[TEXT_PROP_AXIS].size *2.5;
2230 if (im->draw_y_grid) {
2231 Xylabel=gfx_get_text_width(im->canvas, 0,
2232 im->text_prop[TEXT_PROP_AXIS].font,
2233 im->text_prop[TEXT_PROP_AXIS].size,
2235 "0", 0) * im->unitslength;
2239 #ifdef WITH_PIECHART
2241 im->piesize=im->xsize<im->ysize?im->xsize:im->ysize;
2247 /* Now calculate the total size. Insert some spacing where
2248 desired. im->xorigin and im->yorigin need to correspond
2249 with the lower left corner of the main graph area or, if
2250 this one is not set, the imaginary box surrounding the
2253 /* The legend width cannot yet be determined, as a result we
2254 ** have problems adjusting the image to it. For now, we just
2255 ** forget about it at all; the legend will have to fit in the
2256 ** size already allocated.
2258 im->ximg = Xylabel + Xmain + 2 * Xspacing;
2260 #ifdef WITH_PIECHART
2264 if (Xmain) im->ximg += Xspacing;
2265 #ifdef WITH_PIECHART
2266 if (Xpie) im->ximg += Xspacing;
2269 im->xorigin = Xspacing + Xylabel;
2271 /* the length of the title should not influence with width of the graph
2272 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2274 if (Xvertical) { /* unit description */
2275 im->ximg += Xvertical;
2276 im->xorigin += Xvertical;
2280 /* The vertical size is interesting... we need to compare
2281 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with
2282 ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2283 ** in order to start even thinking about Ylegend or Ywatermark.
2285 ** Do it in three portions: First calculate the inner part,
2286 ** then do the legend, then adjust the total height of the img,
2287 ** adding space for a watermark if one exists;
2290 /* reserve space for main and/or pie */
2292 im->yimg = Ymain + Yxlabel;
2294 #ifdef WITH_PIECHART
2295 if (im->yimg < Ypie) im->yimg = Ypie;
2298 im->yorigin = im->yimg - Yxlabel;
2300 /* reserve space for the title *or* some padding above the graph */
2303 im->yorigin += Ytitle;
2305 im->yimg += 1.5*Yspacing;
2306 im->yorigin += 1.5*Yspacing;
2308 /* reserve space for padding below the graph */
2309 im->yimg += Yspacing;
2311 /* Determine where to place the legends onto the image.
2312 ** Adjust im->yimg to match the space requirements.
2314 if(leg_place(im)==-1)
2317 if (im->watermark[0] != '\0') {
2318 im->yimg += Ywatermark;
2322 if (Xlegend > im->ximg) {
2324 /* reposition Pie */
2328 #ifdef WITH_PIECHART
2329 /* The pie is placed in the upper right hand corner,
2330 ** just below the title (if any) and with sufficient
2334 im->pie_x = im->ximg - Xspacing - Xpie/2;
2335 im->pie_y = im->yorigin-Ymain+Ypie/2;
2337 im->pie_x = im->ximg/2;
2338 im->pie_y = im->yorigin-Ypie/2;
2346 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
2347 /* yes we are loosing precision by doing tos with floats instead of doubles
2348 but it seems more stable this way. */
2350 static int AlmostEqual2sComplement (float A, float B, int maxUlps)
2353 int aInt = *(int*)&A;
2354 int bInt = *(int*)&B;
2356 /* Make sure maxUlps is non-negative and small enough that the
2357 default NAN won't compare as equal to anything. */
2359 /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
2361 /* Make aInt lexicographically ordered as a twos-complement int */
2364 aInt = 0x80000000l - aInt;
2366 /* Make bInt lexicographically ordered as a twos-complement int */
2369 bInt = 0x80000000l - bInt;
2371 intDiff = abs(aInt - bInt);
2373 if (intDiff <= maxUlps)
2379 /* draw that picture thing ... */
2381 graph_paint(image_desc_t *im, char ***calcpr)
2384 int lazy = lazy_check(im);
2385 #ifdef WITH_PIECHART
2387 double PieStart=0.0;
2392 double areazero = 0.0;
2393 graph_desc_t *lastgdes = NULL;
2395 /* if we are lazy and there is nothing to PRINT ... quit now */
2396 if (lazy && im->prt_c==0) return 0;
2398 /* pull the data from the rrd files ... */
2400 if(data_fetch(im)==-1)
2403 /* evaluate VDEF and CDEF operations ... */
2404 if(data_calc(im)==-1)
2407 #ifdef WITH_PIECHART
2408 /* check if we need to draw a piechart */
2409 for(i=0;i<im->gdes_c;i++){
2410 if (im->gdes[i].gf == GF_PART) {
2417 /* calculate and PRINT and GPRINT definitions. We have to do it at
2418 * this point because it will affect the length of the legends
2419 * if there are no graph elements we stop here ...
2420 * if we are lazy, try to quit ...
2422 i=print_calc(im,calcpr);
2425 #ifdef WITH_PIECHART
2428 ) || lazy) return 0;
2430 #ifdef WITH_PIECHART
2431 /* If there's only the pie chart to draw, signal this */
2432 if (i==0) piechart=2;
2435 /* get actual drawing data and find min and max values*/
2436 if(data_proc(im)==-1)
2439 if(!im->logarithmic){si_unit(im);} /* identify si magnitude Kilo, Mega Giga ? */
2441 if(!im->rigid && ! im->logarithmic)
2442 expand_range(im); /* make sure the upper and lower limit are
2445 if (!calc_horizontal_grid(im))
2452 /**************************************************************
2453 *** Calculating sizes and locations became a bit confusing ***
2454 *** so I moved this into a separate function. ***
2455 **************************************************************/
2456 if(graph_size_location(im,i
2457 #ifdef WITH_PIECHART
2463 /* the actual graph is created by going through the individual
2464 graph elements and then drawing them */
2466 node=gfx_new_area ( im->canvas,
2470 im->graph_col[GRC_BACK]);
2472 gfx_add_point(node,im->ximg, 0);
2474 #ifdef WITH_PIECHART
2475 if (piechart != 2) {
2477 node=gfx_new_area ( im->canvas,
2478 im->xorigin, im->yorigin,
2479 im->xorigin + im->xsize, im->yorigin,
2480 im->xorigin + im->xsize, im->yorigin-im->ysize,
2481 im->graph_col[GRC_CANVAS]);
2483 gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
2485 if (im->minval > 0.0)
2486 areazero = im->minval;
2487 if (im->maxval < 0.0)
2488 areazero = im->maxval;
2489 #ifdef WITH_PIECHART
2493 #ifdef WITH_PIECHART
2495 pie_part(im,im->graph_col[GRC_CANVAS],im->pie_x,im->pie_y,im->piesize*0.5,0,2*M_PI);
2499 for(i=0;i<im->gdes_c;i++){
2500 switch(im->gdes[i].gf){
2513 for (ii = 0; ii < im->xsize; ii++)
2515 if (!isnan(im->gdes[i].p_data[ii]) &&
2516 im->gdes[i].p_data[ii] != 0.0)
2518 if (im -> gdes[i].yrule > 0 ) {
2519 gfx_new_line(im->canvas,
2520 im -> xorigin + ii, im->yorigin,
2521 im -> xorigin + ii, im->yorigin - im -> gdes[i].yrule * im -> ysize,
2523 im -> gdes[i].col );
2524 } else if ( im -> gdes[i].yrule < 0 ) {
2525 gfx_new_line(im->canvas,
2526 im -> xorigin + ii, im->yorigin - im -> ysize,
2527 im -> xorigin + ii, im->yorigin - ( 1 - im -> gdes[i].yrule ) * im -> ysize,
2529 im -> gdes[i].col );
2537 /* fix data points at oo and -oo */
2538 for(ii=0;ii<im->xsize;ii++){
2539 if (isinf(im->gdes[i].p_data[ii])){
2540 if (im->gdes[i].p_data[ii] > 0) {
2541 im->gdes[i].p_data[ii] = im->maxval ;
2543 im->gdes[i].p_data[ii] = im->minval ;
2549 /* *******************************************************
2554 -------|--t-1--t--------------------------------
2556 if we know the value at time t was a then
2557 we draw a square from t-1 to t with the value a.
2559 ********************************************************* */
2560 if (im->gdes[i].col != 0x0){
2561 /* GF_LINE and friend */
2562 if(im->gdes[i].gf == GF_LINE ){
2565 for(ii=1;ii<im->xsize;ii++){
2566 if (isnan(im->gdes[i].p_data[ii]) || (im->slopemode==1 && isnan(im->gdes[i].p_data[ii-1]))){
2570 if ( node == NULL ) {
2571 last_y = ytr(im,im->gdes[i].p_data[ii]);
2572 if ( im->slopemode == 0 ){
2573 node = gfx_new_line(im->canvas,
2574 ii-1+im->xorigin,last_y,
2575 ii+im->xorigin,last_y,
2576 im->gdes[i].linewidth,
2579 node = gfx_new_line(im->canvas,
2580 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2581 ii+im->xorigin,last_y,
2582 im->gdes[i].linewidth,
2586 double new_y = ytr(im,im->gdes[i].p_data[ii]);
2587 if ( im->slopemode==0 && ! AlmostEqual2sComplement(new_y,last_y,4)){
2588 gfx_add_point(node,ii-1+im->xorigin,new_y);
2591 gfx_add_point(node,ii+im->xorigin,new_y);
2597 double *foreY=malloc(sizeof(double)*im->xsize*2);
2598 double *foreX=malloc(sizeof(double)*im->xsize*2);
2599 double *backY=malloc(sizeof(double)*im->xsize*2);
2600 double *backX=malloc(sizeof(double)*im->xsize*2);
2602 for(ii=0;ii<=im->xsize;ii++){
2604 if ( idxI > 0 && ( drawem != 0 || ii==im->xsize)){
2607 while (cntI < idxI && AlmostEqual2sComplement(foreY[lastI],foreY[cntI],4) && AlmostEqual2sComplement(foreY[lastI],foreY[cntI+1],4)){cntI++;}
2608 node = gfx_new_area(im->canvas,
2611 foreX[cntI],foreY[cntI], im->gdes[i].col);
2612 while (cntI < idxI) {
2615 while ( cntI < idxI && AlmostEqual2sComplement(foreY[lastI],foreY[cntI],4) && AlmostEqual2sComplement(foreY[lastI],foreY[cntI+1],4)){cntI++;}
2616 gfx_add_point(node,foreX[cntI],foreY[cntI]);
2618 gfx_add_point(node,backX[idxI],backY[idxI]);
2622 while ( idxI > 1 && AlmostEqual2sComplement(backY[lastI], backY[idxI],4) && AlmostEqual2sComplement(backY[lastI],backY[idxI-1],4)){idxI--;}
2623 gfx_add_point(node,backX[idxI],backY[idxI]);
2632 if (ii == im->xsize) break;
2634 /* keep things simple for now, just draw these bars
2635 do not try to build a big and complex area */
2638 if ( im->slopemode == 0 && ii==0){
2641 if ( isnan(im->gdes[i].p_data[ii]) ) {
2645 ytop = ytr(im,im->gdes[i].p_data[ii]);
2646 if ( lastgdes && im->gdes[i].stack ) {
2647 ybase = ytr(im,lastgdes->p_data[ii]);
2649 ybase = ytr(im,areazero);
2651 if ( ybase == ytop ){
2655 /* every area has to be wound clock-wise,
2656 so we have to make sur base remains base */
2658 double extra = ytop;
2662 if ( im->slopemode == 0 ){
2663 backY[++idxI] = ybase-0.2;
2664 backX[idxI] = ii+im->xorigin-1;
2665 foreY[idxI] = ytop+0.2;
2666 foreX[idxI] = ii+im->xorigin-1;
2668 backY[++idxI] = ybase-0.2;
2669 backX[idxI] = ii+im->xorigin;
2670 foreY[idxI] = ytop+0.2;
2671 foreX[idxI] = ii+im->xorigin;
2673 /* close up any remaining area */
2678 } /* else GF_LINE */
2679 } /* if color != 0x0 */
2680 /* make sure we do not run into trouble when stacking on NaN */
2681 for(ii=0;ii<im->xsize;ii++){
2682 if (isnan(im->gdes[i].p_data[ii])) {
2683 if (lastgdes && (im->gdes[i].stack)) {
2684 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
2686 im->gdes[i].p_data[ii] = areazero;
2690 lastgdes = &(im->gdes[i]);
2692 #ifdef WITH_PIECHART
2694 if(isnan(im->gdes[i].yrule)) /* fetch variable */
2695 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2697 if (finite(im->gdes[i].yrule)) { /* even the fetched var can be NaN */
2698 pie_part(im,im->gdes[i].col,
2699 im->pie_x,im->pie_y,im->piesize*0.4,
2700 M_PI*2.0*PieStart/100.0,
2701 M_PI*2.0*(PieStart+im->gdes[i].yrule)/100.0);
2702 PieStart += im->gdes[i].yrule;
2707 rrd_set_error("STACK should already be turned into LINE or AREA here");
2713 #ifdef WITH_PIECHART
2721 /* grid_paint also does the text */
2722 if( !(im->extra_flags & ONLY_GRAPH) )
2726 if( !(im->extra_flags & ONLY_GRAPH) )
2729 /* the RULES are the last thing to paint ... */
2730 for(i=0;i<im->gdes_c;i++){
2732 switch(im->gdes[i].gf){
2734 if(isnan(im->gdes[i].yrule)) { /* fetch variable */
2735 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2737 if(im->gdes[i].yrule >= im->minval
2738 && im->gdes[i].yrule <= im->maxval)
2739 gfx_new_line(im->canvas,
2740 im->xorigin,ytr(im,im->gdes[i].yrule),
2741 im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2742 1.0,im->gdes[i].col);
2745 if(im->gdes[i].xrule == 0) { /* fetch variable */
2746 im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
2748 if(im->gdes[i].xrule >= im->start
2749 && im->gdes[i].xrule <= im->end)
2750 gfx_new_line(im->canvas,
2751 xtr(im,im->gdes[i].xrule),im->yorigin,
2752 xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2753 1.0,im->gdes[i].col);
2761 if (strcmp(im->graphfile,"-")==0) {
2762 fo = im->graphhandle ? im->graphhandle : stdout;
2763 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
2764 /* Change translation mode for stdout to BINARY */
2765 _setmode( _fileno( fo ), O_BINARY );
2768 if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2769 rrd_set_error("Opening '%s' for write: %s",im->graphfile,
2770 rrd_strerror(errno));
2774 gfx_render (im->canvas,im->ximg,im->yimg,0x00000000,fo);
2775 if (strcmp(im->graphfile,"-") != 0)
2781 /*****************************************************
2783 *****************************************************/
2786 gdes_alloc(image_desc_t *im){
2789 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2790 * sizeof(graph_desc_t)))==NULL){
2791 rrd_set_error("realloc graph_descs");
2796 im->gdes[im->gdes_c-1].step=im->step;
2797 im->gdes[im->gdes_c-1].step_orig=im->step;
2798 im->gdes[im->gdes_c-1].stack=0;
2799 im->gdes[im->gdes_c-1].linewidth=0;
2800 im->gdes[im->gdes_c-1].debug=0;
2801 im->gdes[im->gdes_c-1].start=im->start;
2802 im->gdes[im->gdes_c-1].start_orig=im->start;
2803 im->gdes[im->gdes_c-1].end=im->end;
2804 im->gdes[im->gdes_c-1].end_orig=im->end;
2805 im->gdes[im->gdes_c-1].vname[0]='\0';
2806 im->gdes[im->gdes_c-1].data=NULL;
2807 im->gdes[im->gdes_c-1].ds_namv=NULL;
2808 im->gdes[im->gdes_c-1].data_first=0;
2809 im->gdes[im->gdes_c-1].p_data=NULL;
2810 im->gdes[im->gdes_c-1].rpnp=NULL;
2811 im->gdes[im->gdes_c-1].shift=0;
2812 im->gdes[im->gdes_c-1].col = 0x0;
2813 im->gdes[im->gdes_c-1].legend[0]='\0';
2814 im->gdes[im->gdes_c-1].format[0]='\0';
2815 im->gdes[im->gdes_c-1].rrd[0]='\0';
2816 im->gdes[im->gdes_c-1].ds=-1;
2817 im->gdes[im->gdes_c-1].cf_reduce=CF_AVERAGE;
2818 im->gdes[im->gdes_c-1].cf=CF_AVERAGE;
2819 im->gdes[im->gdes_c-1].p_data=NULL;
2820 im->gdes[im->gdes_c-1].yrule=DNAN;
2821 im->gdes[im->gdes_c-1].xrule=0;
2825 /* copies input untill the first unescaped colon is found
2826 or until input ends. backslashes have to be escaped as well */
2828 scan_for_col(const char *const input, int len, char *const output)
2833 input[inp] != ':' &&
2836 if (input[inp] == '\\' &&
2837 input[inp+1] != '\0' &&
2838 (input[inp+1] == '\\' ||
2839 input[inp+1] == ':')){
2840 output[outp++] = input[++inp];
2843 output[outp++] = input[inp];
2846 output[outp] = '\0';
2849 /* Some surgery done on this function, it became ridiculously big.
2851 ** - initializing now in rrd_graph_init()
2852 ** - options parsing now in rrd_graph_options()
2853 ** - script parsing now in rrd_graph_script()
2856 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize, FILE *stream, double *ymin, double *ymax)
2859 rrd_graph_init(&im);
2860 im.graphhandle = stream;
2862 rrd_graph_options(argc,argv,&im);
2863 if (rrd_test_error()) {
2868 if (strlen(argv[optind])>=MAXPATH) {
2869 rrd_set_error("filename (including path) too long");
2873 strncpy(im.graphfile,argv[optind],MAXPATH-1);
2874 im.graphfile[MAXPATH-1]='\0';
2876 rrd_graph_script(argc,argv,&im,1);
2877 if (rrd_test_error()) {
2882 /* Everything is now read and the actual work can start */
2885 if (graph_paint(&im,prdata)==-1){
2890 /* The image is generated and needs to be output.
2891 ** Also, if needed, print a line with information about the image.
2901 /* maybe prdata is not allocated yet ... lets do it now */
2902 if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
2903 rrd_set_error("malloc imginfo");
2907 if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
2909 rrd_set_error("malloc imginfo");
2912 filename=im.graphfile+strlen(im.graphfile);
2913 while(filename > im.graphfile) {
2914 if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
2918 sprintf((*prdata)[0],im.imginfo,filename,(long)(im.canvas->zoom*im.ximg),(long)(im.canvas->zoom*im.yimg));
2925 rrd_graph_init(image_desc_t *im)
2932 #ifdef HAVE_SETLOCALE
2933 setlocale(LC_TIME,"");
2934 #ifdef HAVE_MBSTOWCS
2935 setlocale(LC_CTYPE,"");
2941 im->xlab_user.minsec = -1;
2947 im->ylegend[0] = '\0';
2948 im->title[0] = '\0';
2949 im->watermark[0] = '\0';
2952 im->unitsexponent= 9999;
2955 im->viewfactor = 1.0;
2962 im->logarithmic = 0;
2963 im->ygridstep = DNAN;
2964 im->draw_x_grid = 1;
2965 im->draw_y_grid = 1;
2970 im->canvas = gfx_new_canvas();
2971 im->grid_dash_on = 1;
2972 im->grid_dash_off = 1;
2973 im->tabwidth = 40.0;
2975 for(i=0;i<DIM(graph_col);i++)
2976 im->graph_col[i]=graph_col[i];
2978 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
2981 char rrd_win_default_font[1000];
2982 windir = getenv("windir");
2983 /* %windir% is something like D:\windows or C:\winnt */
2984 if (windir != NULL) {
2985 strncpy(rrd_win_default_font,windir,500);
2986 rrd_win_default_font[500] = '\0';
2987 strcat(rrd_win_default_font,"\\fonts\\");
2988 strcat(rrd_win_default_font,RRD_DEFAULT_FONT);
2989 for(i=0;i<DIM(text_prop);i++){
2990 strncpy(text_prop[i].font,rrd_win_default_font,sizeof(text_prop[i].font)-1);
2991 text_prop[i].font[sizeof(text_prop[i].font)-1] = '\0';
2998 deffont = getenv("RRD_DEFAULT_FONT");
2999 if (deffont != NULL) {
3000 for(i=0;i<DIM(text_prop);i++){
3001 strncpy(text_prop[i].font,deffont,sizeof(text_prop[i].font)-1);
3002 text_prop[i].font[sizeof(text_prop[i].font)-1] = '\0';
3006 for(i=0;i<DIM(text_prop);i++){
3007 im->text_prop[i].size = text_prop[i].size;
3008 strcpy(im->text_prop[i].font,text_prop[i].font);
3013 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
3016 char *parsetime_error = NULL;
3017 char scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
3018 time_t start_tmp=0,end_tmp=0;
3020 struct rrd_time_value start_tv, end_tv;
3022 optind = 0; opterr = 0; /* initialize getopt */
3024 parsetime("end-24h", &start_tv);
3025 parsetime("now", &end_tv);
3027 /* defines for long options without a short equivalent. should be bytes,
3028 and may not collide with (the ASCII value of) short options */
3029 #define LONGOPT_UNITS_SI 255
3032 static struct option long_options[] =
3034 {"start", required_argument, 0, 's'},
3035 {"end", required_argument, 0, 'e'},
3036 {"x-grid", required_argument, 0, 'x'},
3037 {"y-grid", required_argument, 0, 'y'},
3038 {"vertical-label",required_argument,0,'v'},
3039 {"width", required_argument, 0, 'w'},
3040 {"height", required_argument, 0, 'h'},
3041 {"interlaced", no_argument, 0, 'i'},
3042 {"upper-limit",required_argument, 0, 'u'},
3043 {"lower-limit",required_argument, 0, 'l'},
3044 {"rigid", no_argument, 0, 'r'},
3045 {"base", required_argument, 0, 'b'},
3046 {"logarithmic",no_argument, 0, 'o'},
3047 {"color", required_argument, 0, 'c'},
3048 {"font", required_argument, 0, 'n'},
3049 {"title", required_argument, 0, 't'},
3050 {"imginfo", required_argument, 0, 'f'},
3051 {"imgformat", required_argument, 0, 'a'},
3052 {"lazy", no_argument, 0, 'z'},
3053 {"zoom", required_argument, 0, 'm'},
3054 {"no-legend", no_argument, 0, 'g'},
3055 {"force-rules-legend",no_argument,0, 'F'},
3056 {"only-graph", no_argument, 0, 'j'},
3057 {"alt-y-grid", no_argument, 0, 'Y'},
3058 {"no-minor", no_argument, 0, 'I'},
3059 {"slope-mode", no_argument, 0, 'E'},
3060 {"alt-autoscale", no_argument, 0, 'A'},
3061 {"alt-autoscale-max", no_argument, 0, 'M'},
3062 {"no-gridfit", no_argument, 0, 'N'},
3063 {"units-exponent",required_argument, 0, 'X'},
3064 {"units-length",required_argument, 0, 'L'},
3065 {"units", required_argument, 0, LONGOPT_UNITS_SI },
3066 {"step", required_argument, 0, 'S'},
3067 {"tabwidth", required_argument, 0, 'T'},
3068 {"font-render-mode", required_argument, 0, 'R'},
3069 {"font-smoothing-threshold", required_argument, 0, 'B'},
3070 {"watermark", required_argument, 0, 'W'},
3071 {"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 */
3073 int option_index = 0;
3075 int col_start,col_end;
3077 opt = getopt_long(argc, argv,
3078 "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:I:zgjFYAMEX:L:S:T:NR:B:W:",
3079 long_options, &option_index);
3086 im->extra_flags |= NOMINOR;
3089 im->extra_flags |= ALTYGRID;
3092 im->extra_flags |= ALTAUTOSCALE;
3095 im->extra_flags |= ALTAUTOSCALE_MAX;
3098 im->extra_flags |= ONLY_GRAPH;
3101 im->extra_flags |= NOLEGEND;
3104 im->extra_flags |= FORCE_RULES_LEGEND;
3106 case LONGOPT_UNITS_SI:
3107 if(im->extra_flags & FORCE_UNITS) {
3108 rrd_set_error("--units can only be used once!");
3111 if(strcmp(optarg,"si")==0)
3112 im->extra_flags |= FORCE_UNITS_SI;
3114 rrd_set_error("invalid argument for --units: %s", optarg );
3119 im->unitsexponent = atoi(optarg);
3122 im->unitslength = atoi(optarg);
3125 im->tabwidth = atof(optarg);
3128 im->step = atoi(optarg);
3134 if ((parsetime_error = parsetime(optarg, &start_tv))) {
3135 rrd_set_error( "start time: %s", parsetime_error );
3140 if ((parsetime_error = parsetime(optarg, &end_tv))) {
3141 rrd_set_error( "end time: %s", parsetime_error );
3146 if(strcmp(optarg,"none") == 0){
3152 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3154 &im->xlab_user.gridst,
3156 &im->xlab_user.mgridst,
3158 &im->xlab_user.labst,
3159 &im->xlab_user.precis,
3160 &stroff) == 7 && stroff != 0){
3161 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
3162 im->xlab_form[sizeof(im->xlab_form)-1] = '\0';
3163 if((int)(im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
3164 rrd_set_error("unknown keyword %s",scan_gtm);
3166 } else if ((int)(im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
3167 rrd_set_error("unknown keyword %s",scan_mtm);
3169 } else if ((int)(im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
3170 rrd_set_error("unknown keyword %s",scan_ltm);
3173 im->xlab_user.minsec = 1;
3174 im->xlab_user.stst = im->xlab_form;
3176 rrd_set_error("invalid x-grid format");
3182 if(strcmp(optarg,"none") == 0){
3190 &im->ylabfact) == 2) {
3191 if(im->ygridstep<=0){
3192 rrd_set_error("grid step must be > 0");
3194 } else if (im->ylabfact < 1){
3195 rrd_set_error("label factor must be > 0");
3199 rrd_set_error("invalid y-grid format");
3204 strncpy(im->ylegend,optarg,150);
3205 im->ylegend[150]='\0';
3208 im->maxval = atof(optarg);
3211 im->minval = atof(optarg);
3214 im->base = atol(optarg);
3215 if(im->base != 1024 && im->base != 1000 ){
3216 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
3221 long_tmp = atol(optarg);
3222 if (long_tmp < 10) {
3223 rrd_set_error("width below 10 pixels");
3226 im->xsize = long_tmp;
3229 long_tmp = atol(optarg);
3230 if (long_tmp < 10) {
3231 rrd_set_error("height below 10 pixels");
3234 im->ysize = long_tmp;
3237 im->canvas->interlaced = 1;
3243 im->imginfo = optarg;
3246 if((int)(im->canvas->imgformat = if_conv(optarg)) == -1) {
3247 rrd_set_error("unsupported graphics format '%s'",optarg);
3259 im->logarithmic = 1;
3260 if (isnan(im->minval))
3265 "%10[A-Z]#%n%8lx%n",
3266 col_nam,&col_start,&color,&col_end) == 2){
3268 int col_len = col_end - col_start;
3272 ((color & 0xF00) * 0x110000) |
3273 ((color & 0x0F0) * 0x011000) |
3274 ((color & 0x00F) * 0x001100) |
3280 ((color & 0xF000) * 0x11000) |
3281 ((color & 0x0F00) * 0x01100) |
3282 ((color & 0x00F0) * 0x00110) |
3283 ((color & 0x000F) * 0x00011)
3287 color = (color << 8) + 0xff /* shift left by 8 */;
3292 rrd_set_error("the color format is #RRGGBB[AA]");
3295 if((ci=grc_conv(col_nam)) != -1){
3296 im->graph_col[ci]=color;
3298 rrd_set_error("invalid color name '%s'",col_nam);
3302 rrd_set_error("invalid color def format");
3309 char font[1024] = "";
3312 "%10[A-Z]:%lf:%1000s",
3313 prop,&size,font) >= 2){
3315 if((sindex=text_prop_conv(prop)) != -1){
3316 for (propidx=sindex;propidx<TEXT_PROP_LAST;propidx++){
3318 im->text_prop[propidx].size=size;
3320 if (strlen(font) > 0){
3321 strcpy(im->text_prop[propidx].font,font);
3323 if (propidx==sindex && sindex != 0) break;
3326 rrd_set_error("invalid fonttag '%s'",prop);
3330 rrd_set_error("invalid text property format");
3336 im->canvas->zoom = atof(optarg);
3337 if (im->canvas->zoom <= 0.0) {
3338 rrd_set_error("zoom factor must be > 0");
3343 strncpy(im->title,optarg,150);
3344 im->title[150]='\0';
3348 if ( strcmp( optarg, "normal" ) == 0 )
3349 im->canvas->aa_type = AA_NORMAL;
3350 else if ( strcmp( optarg, "light" ) == 0 )
3351 im->canvas->aa_type = AA_LIGHT;
3352 else if ( strcmp( optarg, "mono" ) == 0 )
3353 im->canvas->aa_type = AA_NONE;
3356 rrd_set_error("unknown font-render-mode '%s'", optarg );
3362 im->canvas->font_aa_threshold = atof(optarg);
3366 strncpy(im->watermark,optarg,100);
3367 im->watermark[99]='\0';
3372 rrd_set_error("unknown option '%c'", optopt);
3374 rrd_set_error("unknown option '%s'",argv[optind-1]);
3379 if (optind >= argc) {
3380 rrd_set_error("missing filename");
3384 if (im->logarithmic == 1 && (im->minval <= 0 || isnan(im->minval))){
3385 rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");
3389 if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
3390 /* error string is set in parsetime.c */
3394 if (start_tmp < 3600*24*365*10){
3395 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
3399 if (end_tmp < start_tmp) {
3400 rrd_set_error("start (%ld) should be less than end (%ld)",
3401 start_tmp, end_tmp);
3405 im->start = start_tmp;
3407 im->step = max((long)im->step, (im->end-im->start)/im->xsize);
3411 rrd_graph_check_vname(image_desc_t *im, char *varname, char *err)
3413 if ((im->gdes[im->gdes_c-1].vidx=find_var(im,varname))==-1) {
3414 rrd_set_error("Unknown variable '%s' in %s",varname,err);
3420 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
3423 graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
3425 color=strstr(var,"#");
3428 rrd_set_error("Found no color in %s",err);
3437 rest=strstr(color,":");
3445 sscanf(color,"#%6lx%n",&col,&n);
3446 col = (col << 8) + 0xff /* shift left by 8 */;
3447 if (n!=7) rrd_set_error("Color problem in %s",err);
3450 sscanf(color,"#%8lx%n",&col,&n);
3453 rrd_set_error("Color problem in %s",err);
3455 if (rrd_test_error()) return 0;
3462 int bad_format(char *fmt) {
3466 while (*ptr != '\0')
3467 if (*ptr++ == '%') {
3469 /* line cannot end with percent char */
3470 if (*ptr == '\0') return 1;
3472 /* '%s', '%S' and '%%' are allowed */
3473 if (*ptr == 's' || *ptr == 'S' || *ptr == '%') ptr++;
3475 /* %c is allowed (but use only with vdef!) */
3476 else if (*ptr == 'c') {
3481 /* or else '% 6.2lf' and such are allowed */
3483 /* optional padding character */
3484 if (*ptr == ' ' || *ptr == '+' || *ptr == '-') ptr++;
3486 /* This should take care of 'm.n' with all three optional */
3487 while (*ptr >= '0' && *ptr <= '9') ptr++;
3488 if (*ptr == '.') ptr++;
3489 while (*ptr >= '0' && *ptr <= '9') ptr++;
3491 /* Either 'le', 'lf' or 'lg' must follow here */
3492 if (*ptr++ != 'l') return 1;
3493 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g') ptr++;
3504 vdef_parse(gdes,str)
3505 struct graph_desc_t *gdes;
3506 const char *const str;
3508 /* A VDEF currently is either "func" or "param,func"
3509 * so the parsing is rather simple. Change if needed.
3516 sscanf(str,"%le,%29[A-Z]%n",¶m,func,&n);
3517 if (n== (int)strlen(str)) { /* matched */
3521 sscanf(str,"%29[A-Z]%n",func,&n);
3522 if (n== (int)strlen(str)) { /* matched */
3525 rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3532 if (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3533 else if (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3534 else if (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3535 else if (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3536 else if (!strcmp("TOTAL", func)) gdes->vf.op = VDEF_TOTAL;
3537 else if (!strcmp("FIRST", func)) gdes->vf.op = VDEF_FIRST;
3538 else if (!strcmp("LAST", func)) gdes->vf.op = VDEF_LAST;
3539 else if (!strcmp("LSLSLOPE", func)) gdes->vf.op = VDEF_LSLSLOPE;
3540 else if (!strcmp("LSLINT", func)) gdes->vf.op = VDEF_LSLINT;
3541 else if (!strcmp("LSLCORREL",func)) gdes->vf.op = VDEF_LSLCORREL;
3543 rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3550 switch (gdes->vf.op) {
3552 if (isnan(param)) { /* no parameter given */
3553 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3559 if (param>=0.0 && param<=100.0) {
3560 gdes->vf.param = param;
3561 gdes->vf.val = DNAN; /* undefined */
3562 gdes->vf.when = 0; /* undefined */
3564 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3579 case VDEF_LSLCORREL:
3581 gdes->vf.param = DNAN;
3582 gdes->vf.val = DNAN;
3585 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3602 graph_desc_t *src,*dst;
3606 dst = &im->gdes[gdi];
3607 src = &im->gdes[dst->vidx];
3608 data = src->data + src->ds;
3609 steps = (src->end - src->start) / src->step;
3612 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3619 switch (dst->vf.op) {
3620 case VDEF_PERCENT: {
3621 rrd_value_t * array;
3625 if ((array = malloc(steps*sizeof(double)))==NULL) {
3626 rrd_set_error("malloc VDEV_PERCENT");
3629 for (step=0;step < steps; step++) {
3630 array[step]=data[step*src->ds_cnt];
3632 qsort(array,step,sizeof(double),vdef_percent_compar);
3634 field = (steps-1)*dst->vf.param/100;
3635 dst->vf.val = array[field];
3636 dst->vf.when = 0; /* no time component */
3639 for(step=0;step<steps;step++)
3640 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3646 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3647 if (step == steps) {
3651 dst->vf.val = data[step*src->ds_cnt];
3652 dst->vf.when = src->start + (step+1)*src->step;
3654 while (step != steps) {
3655 if (finite(data[step*src->ds_cnt])) {
3656 if (data[step*src->ds_cnt] > dst->vf.val) {
3657 dst->vf.val = data[step*src->ds_cnt];
3658 dst->vf.when = src->start + (step+1)*src->step;
3665 case VDEF_AVERAGE: {
3668 for (step=0;step<steps;step++) {
3669 if (finite(data[step*src->ds_cnt])) {
3670 sum += data[step*src->ds_cnt];
3675 if (dst->vf.op == VDEF_TOTAL) {
3676 dst->vf.val = sum*src->step;
3677 dst->vf.when = cnt*src->step; /* not really "when" */
3679 dst->vf.val = sum/cnt;
3680 dst->vf.when = 0; /* no time component */
3690 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3691 if (step == steps) {
3695 dst->vf.val = data[step*src->ds_cnt];
3696 dst->vf.when = src->start + (step+1)*src->step;
3698 while (step != steps) {
3699 if (finite(data[step*src->ds_cnt])) {
3700 if (data[step*src->ds_cnt] < dst->vf.val) {
3701 dst->vf.val = data[step*src->ds_cnt];
3702 dst->vf.when = src->start + (step+1)*src->step;
3709 /* The time value returned here is one step before the
3710 * actual time value. This is the start of the first
3714 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3715 if (step == steps) { /* all entries were NaN */
3719 dst->vf.val = data[step*src->ds_cnt];
3720 dst->vf.when = src->start + step*src->step;
3724 /* The time value returned here is the
3725 * actual time value. This is the end of the last
3729 while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3730 if (step < 0) { /* all entries were NaN */
3734 dst->vf.val = data[step*src->ds_cnt];
3735 dst->vf.when = src->start + (step+1)*src->step;
3740 case VDEF_LSLCORREL:{
3741 /* Bestfit line by linear least squares method */
3744 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl ;
3745 SUMx = 0; SUMy = 0; SUMxy = 0; SUMxx = 0; SUMyy = 0;
3747 for (step=0;step<steps;step++) {
3748 if (finite(data[step*src->ds_cnt])) {
3751 SUMxx += step * step;
3752 SUMxy += step * data[step*src->ds_cnt];
3753 SUMy += data[step*src->ds_cnt];
3754 SUMyy += data[step*src->ds_cnt]*data[step*src->ds_cnt];
3758 slope = ( SUMx*SUMy - cnt*SUMxy ) / ( SUMx*SUMx - cnt*SUMxx );
3759 y_intercept = ( SUMy - slope*SUMx ) / cnt;
3760 correl = (SUMxy - (SUMx*SUMy)/cnt) / sqrt((SUMxx - (SUMx*SUMx)/cnt)*(SUMyy - (SUMy*SUMy)/cnt));
3763 if (dst->vf.op == VDEF_LSLSLOPE) {
3764 dst->vf.val = slope;
3765 dst->vf.when = cnt*src->step;
3766 } else if (dst->vf.op == VDEF_LSLINT) {
3767 dst->vf.val = y_intercept;
3768 dst->vf.when = cnt*src->step;
3769 } else if (dst->vf.op == VDEF_LSLCORREL) {
3770 dst->vf.val = correl;
3771 dst->vf.when = cnt*src->step;
3784 /* NaN < -INF < finite_values < INF */
3786 vdef_percent_compar(a,b)
3789 /* Equality is not returned; this doesn't hurt except
3790 * (maybe) for a little performance.
3793 /* First catch NaN values. They are smallest */
3794 if (isnan( *(double *)a )) return -1;
3795 if (isnan( *(double *)b )) return 1;
3797 /* NaN doesn't reach this part so INF and -INF are extremes.
3798 * The sign from isinf() is compatible with the sign we return
3800 if (isinf( *(double *)a )) return isinf( *(double *)a );
3801 if (isinf( *(double *)b )) return isinf( *(double *)b );
3803 /* If we reach this, both values must be finite */
3804 if ( *(double *)a < *(double *)b ) return -1; else return 1;