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 static const 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;
1402 int leg_y_prev = im->yimg;
1406 char prt_fctn; /*special printfunctions */
1409 if( !(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH) ) {
1410 if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1411 rrd_set_error("malloc for legspace");
1415 for(i=0;i<im->gdes_c;i++){
1418 /* hid legends for rules which are not displayed */
1420 if(!(im->extra_flags & FORCE_RULES_LEGEND)) {
1421 if (im->gdes[i].gf == GF_HRULE &&
1422 (im->gdes[i].yrule < im->minval || im->gdes[i].yrule > im->maxval))
1423 im->gdes[i].legend[0] = '\0';
1425 if (im->gdes[i].gf == GF_VRULE &&
1426 (im->gdes[i].xrule < im->start || im->gdes[i].xrule > im->end))
1427 im->gdes[i].legend[0] = '\0';
1430 leg_cc = strlen(im->gdes[i].legend);
1432 /* is there a controle code ant the end of the legend string ? */
1433 /* and it is not a tab \\t */
1434 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\' && im->gdes[i].legend[leg_cc-1] != 't') {
1435 prt_fctn = im->gdes[i].legend[leg_cc-1];
1437 im->gdes[i].legend[leg_cc] = '\0';
1441 /* remove exess space */
1442 while (prt_fctn=='g' &&
1444 im->gdes[i].legend[leg_cc-1]==' '){
1446 im->gdes[i].legend[leg_cc]='\0';
1449 legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1452 /* no interleg space if string ends in \g */
1453 fill += legspace[i];
1455 fill += gfx_get_text_width(im->canvas, fill+border,
1456 im->text_prop[TEXT_PROP_LEGEND].font,
1457 im->text_prop[TEXT_PROP_LEGEND].size,
1459 im->gdes[i].legend, 0);
1464 /* who said there was a special tag ... ?*/
1465 if (prt_fctn=='g') {
1468 if (prt_fctn == '\0') {
1469 if (i == im->gdes_c -1 ) prt_fctn ='l';
1471 /* is it time to place the legends ? */
1472 if (fill > im->ximg - 2*border){
1487 if (prt_fctn != '\0'){
1489 if (leg_c >= 2 && prt_fctn == 'j') {
1490 glue = (im->ximg - fill - 2* border) / (leg_c-1);
1494 if (prt_fctn =='c') leg_x = (im->ximg - fill) / 2.0;
1495 if (prt_fctn =='r') leg_x = im->ximg - fill - border;
1497 for(ii=mark;ii<=i;ii++){
1498 if(im->gdes[ii].legend[0]=='\0')
1499 continue; /* skip empty legends */
1500 im->gdes[ii].leg_x = leg_x;
1501 im->gdes[ii].leg_y = leg_y;
1503 gfx_get_text_width(im->canvas, leg_x,
1504 im->text_prop[TEXT_PROP_LEGEND].font,
1505 im->text_prop[TEXT_PROP_LEGEND].size,
1507 im->gdes[ii].legend, 0)
1512 /* only add y space if there was text on the line */
1513 if (leg_x > border || prt_fctn == 's')
1514 leg_y += im->text_prop[TEXT_PROP_LEGEND].size*1.8;
1515 if (prt_fctn == 's')
1516 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1522 im->yimg = leg_y_prev;
1523 /* if we did place some legends we have to add vertical space */
1524 if (leg_y != im->yimg){
1525 im->yimg += im->text_prop[TEXT_PROP_LEGEND].size*1.8;
1532 /* create a grid on the graph. it determines what to do
1533 from the values of xsize, start and end */
1535 /* the xaxis labels are determined from the number of seconds per pixel
1536 in the requested graph */
1541 calc_horizontal_grid(image_desc_t *im)
1547 int decimals, fractionals;
1549 im->ygrid_scale.labfact=2;
1550 range = im->maxval - im->minval;
1551 scaledrange = range / im->magfact;
1553 /* does the scale of this graph make it impossible to put lines
1554 on it? If so, give up. */
1555 if (isnan(scaledrange)) {
1559 /* find grid spaceing */
1561 if(isnan(im->ygridstep)){
1562 if(im->extra_flags & ALTYGRID) {
1563 /* find the value with max number of digits. Get number of digits */
1564 decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))*im->viewfactor/im->magfact));
1565 if(decimals <= 0) /* everything is small. make place for zero */
1568 im->ygrid_scale.gridstep = pow((double)10, floor(log10(range*im->viewfactor/im->magfact)))/im->viewfactor*im->magfact;
1570 if(im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1571 im->ygrid_scale.gridstep = 0.1;
1572 /* should have at least 5 lines but no more then 15 */
1573 if(range/im->ygrid_scale.gridstep < 5)
1574 im->ygrid_scale.gridstep /= 10;
1575 if(range/im->ygrid_scale.gridstep > 15)
1576 im->ygrid_scale.gridstep *= 10;
1577 if(range/im->ygrid_scale.gridstep > 5) {
1578 im->ygrid_scale.labfact = 1;
1579 if(range/im->ygrid_scale.gridstep > 8)
1580 im->ygrid_scale.labfact = 2;
1583 im->ygrid_scale.gridstep /= 5;
1584 im->ygrid_scale.labfact = 5;
1586 fractionals = floor(log10(im->ygrid_scale.gridstep*(double)im->ygrid_scale.labfact*im->viewfactor/im->magfact));
1587 if(fractionals < 0) { /* small amplitude. */
1588 int len = decimals - fractionals + 1;
1589 if (im->unitslength < len+2) im->unitslength = len+2;
1590 sprintf(im->ygrid_scale.labfmt, "%%%d.%df%s", len, -fractionals,(im->symbol != ' ' ? " %c" : ""));
1592 int len = decimals + 1;
1593 if (im->unitslength < len+2) im->unitslength = len+2;
1594 sprintf(im->ygrid_scale.labfmt, "%%%d.0f%s", len, ( im->symbol != ' ' ? " %c" : "" ));
1598 for(i=0;ylab[i].grid > 0;i++){
1599 pixel = im->ysize / (scaledrange / ylab[i].grid);
1606 if (pixel * ylab[gridind].lfac[i] >= 2.5 * im->text_prop[TEXT_PROP_AXIS].size) {
1607 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1612 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1615 im->ygrid_scale.gridstep = im->ygridstep;
1616 im->ygrid_scale.labfact = im->ylabfact;
1621 int draw_horizontal_grid(image_desc_t *im)
1625 char graph_label[100];
1627 double X0=im->xorigin;
1628 double X1=im->xorigin+im->xsize;
1630 int sgrid = (int)( im->minval / im->ygrid_scale.gridstep - 1);
1631 int egrid = (int)( im->maxval / im->ygrid_scale.gridstep + 1);
1633 scaledstep = im->ygrid_scale.gridstep/(double)im->magfact*(double)im->viewfactor;
1634 MaxY = scaledstep*(double)egrid;
1635 for (i = sgrid; i <= egrid; i++){
1636 double Y0=ytr(im,im->ygrid_scale.gridstep*i);
1637 double YN=ytr(im,im->ygrid_scale.gridstep*(i+1));
1638 if ( Y0 >= im->yorigin-im->ysize
1639 && Y0 <= im->yorigin){
1640 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
1641 with the chosen settings. Add a label if required by settings, or if
1642 there is only one label so far and the next grid line is out of bounds. */
1643 if(i % im->ygrid_scale.labfact == 0 || ( nlabels==1 && (YN < im->yorigin-im->ysize || YN > im->yorigin) )){
1644 if (im->symbol == ' ') {
1645 if(im->extra_flags & ALTYGRID) {
1646 sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*(double)i);
1649 sprintf(graph_label,"%4.1f",scaledstep*(double)i);
1651 sprintf(graph_label,"%4.0f",scaledstep*(double)i);
1655 char sisym = ( i == 0 ? ' ' : im->symbol);
1656 if(im->extra_flags & ALTYGRID) {
1657 sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*(double)i,sisym);
1660 sprintf(graph_label,"%4.1f %c",scaledstep*(double)i, sisym);
1662 sprintf(graph_label,"%4.0f %c",scaledstep*(double)i, sisym);
1668 gfx_new_text ( im->canvas,
1669 X0-im->text_prop[TEXT_PROP_AXIS].size, Y0,
1670 im->graph_col[GRC_FONT],
1671 im->text_prop[TEXT_PROP_AXIS].font,
1672 im->text_prop[TEXT_PROP_AXIS].size,
1673 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1675 gfx_new_dashed_line ( im->canvas,
1678 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1679 im->grid_dash_on, im->grid_dash_off);
1681 } else if (!(im->extra_flags & NOMINOR)) {
1682 gfx_new_dashed_line ( im->canvas,
1685 GRIDWIDTH, im->graph_col[GRC_GRID],
1686 im->grid_dash_on, im->grid_dash_off);
1694 /* logaritmic horizontal grid */
1696 horizontal_log_grid(image_desc_t *im)
1700 int minoridx=0, majoridx=0;
1701 char graph_label[100];
1703 double value, pixperstep, minstep;
1705 /* find grid spaceing */
1706 pixpex= (double)im->ysize / (log10(im->maxval) - log10(im->minval));
1708 if (isnan(pixpex)) {
1712 for(i=0;yloglab[i][0] > 0;i++){
1713 minstep = log10(yloglab[i][0]);
1714 for(ii=1;yloglab[i][ii+1] > 0;ii++){
1715 if(yloglab[i][ii+2]==0){
1716 minstep = log10(yloglab[i][ii+1])-log10(yloglab[i][ii]);
1720 pixperstep = pixpex * minstep;
1721 if(pixperstep > 5){minoridx = i;}
1722 if(pixperstep > 2 * im->text_prop[TEXT_PROP_LEGEND].size){majoridx = i;}
1726 X1=im->xorigin+im->xsize;
1727 /* paint minor grid */
1728 for (value = pow((double)10, log10(im->minval)
1729 - fmod(log10(im->minval),log10(yloglab[minoridx][0])));
1730 value <= im->maxval;
1731 value *= yloglab[minoridx][0]){
1732 if (value < im->minval) continue;
1734 while(yloglab[minoridx][++i] > 0){
1735 Y0 = ytr(im,value * yloglab[minoridx][i]);
1736 if (Y0 <= im->yorigin - im->ysize) break;
1737 gfx_new_dashed_line ( im->canvas,
1740 GRIDWIDTH, im->graph_col[GRC_GRID],
1741 im->grid_dash_on, im->grid_dash_off);
1745 /* paint major grid and labels*/
1746 for (value = pow((double)10, log10(im->minval)
1747 - fmod(log10(im->minval),log10(yloglab[majoridx][0])));
1748 value <= im->maxval;
1749 value *= yloglab[majoridx][0]){
1750 if (value < im->minval) continue;
1752 while(yloglab[majoridx][++i] > 0){
1753 Y0 = ytr(im,value * yloglab[majoridx][i]);
1754 if (Y0 <= im->yorigin - im->ysize) break;
1755 gfx_new_dashed_line ( im->canvas,
1758 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1759 im->grid_dash_on, im->grid_dash_off);
1761 if (im->extra_flags & FORCE_UNITS_SI) {
1762 double pvalue = value * yloglab[majoridx][i];
1763 double scale = floor( log10( fabs(pvalue)) / 3);
1766 pvalue /= pow(10, 3*scale);
1768 if ( ((scale+si_symbcenter) < sizeof(si_symbol)) &&
1769 ((scale+si_symbcenter) >= 0) )
1770 symbol = si_symbol[(int)scale+si_symbcenter];
1774 sprintf(graph_label,"%3.0f %c", pvalue, symbol);
1776 sprintf(graph_label,"%3.0e",value * yloglab[majoridx][i]);
1778 gfx_new_text ( im->canvas,
1779 X0-im->text_prop[TEXT_PROP_AXIS].size, Y0,
1780 im->graph_col[GRC_FONT],
1781 im->text_prop[TEXT_PROP_AXIS].font,
1782 im->text_prop[TEXT_PROP_AXIS].size,
1783 im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1795 int xlab_sel; /* which sort of label and grid ? */
1796 time_t ti, tilab, timajor;
1798 char graph_label[100];
1799 double X0,Y0,Y1; /* points for filled graph and more*/
1802 /* the type of time grid is determined by finding
1803 the number of seconds per pixel in the graph */
1806 if(im->xlab_user.minsec == -1){
1807 factor=(im->end - im->start)/im->xsize;
1809 while ( xlab[xlab_sel+1].minsec != -1
1810 && xlab[xlab_sel+1].minsec <= factor) { xlab_sel++; } /* pick the last one */
1811 while ( xlab[xlab_sel-1].minsec == xlab[xlab_sel].minsec
1812 && xlab[xlab_sel].length > (im->end - im->start)) { xlab_sel--; } /* go back to the smallest size */
1813 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
1814 im->xlab_user.gridst = xlab[xlab_sel].gridst;
1815 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
1816 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
1817 im->xlab_user.labtm = xlab[xlab_sel].labtm;
1818 im->xlab_user.labst = xlab[xlab_sel].labst;
1819 im->xlab_user.precis = xlab[xlab_sel].precis;
1820 im->xlab_user.stst = xlab[xlab_sel].stst;
1823 /* y coords are the same for every line ... */
1825 Y1 = im->yorigin-im->ysize;
1828 /* paint the minor grid */
1829 if (!(im->extra_flags & NOMINOR))
1831 for(ti = find_first_time(im->start,
1832 im->xlab_user.gridtm,
1833 im->xlab_user.gridst),
1834 timajor = find_first_time(im->start,
1835 im->xlab_user.mgridtm,
1836 im->xlab_user.mgridst);
1838 ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
1840 /* are we inside the graph ? */
1841 if (ti < im->start || ti > im->end) continue;
1842 while (timajor < ti) {
1843 timajor = find_next_time(timajor,
1844 im->xlab_user.mgridtm, im->xlab_user.mgridst);
1846 if (ti == timajor) continue; /* skip as falls on major grid line */
1848 gfx_new_dashed_line(im->canvas,X0,Y0+1, X0,Y1-1,GRIDWIDTH,
1849 im->graph_col[GRC_GRID],
1850 im->grid_dash_on, im->grid_dash_off);
1855 /* paint the major grid */
1856 for(ti = find_first_time(im->start,
1857 im->xlab_user.mgridtm,
1858 im->xlab_user.mgridst);
1860 ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
1862 /* are we inside the graph ? */
1863 if (ti < im->start || ti > im->end) continue;
1865 gfx_new_dashed_line(im->canvas,X0,Y0+3, X0,Y1-2,MGRIDWIDTH,
1866 im->graph_col[GRC_MGRID],
1867 im->grid_dash_on, im->grid_dash_off);
1870 /* paint the labels below the graph */
1871 for(ti = find_first_time(im->start - im->xlab_user.precis/2,
1872 im->xlab_user.labtm,
1873 im->xlab_user.labst);
1874 ti <= im->end - im->xlab_user.precis/2;
1875 ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
1877 tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
1878 /* are we inside the graph ? */
1879 if (tilab < im->start || tilab > im->end) continue;
1882 localtime_r(&tilab, &tm);
1883 strftime(graph_label,99,im->xlab_user.stst, &tm);
1885 # error "your libc has no strftime I guess we'll abort the exercise here."
1887 gfx_new_text ( im->canvas,
1888 xtr(im,tilab), Y0+im->text_prop[TEXT_PROP_AXIS].size*1.4+5,
1889 im->graph_col[GRC_FONT],
1890 im->text_prop[TEXT_PROP_AXIS].font,
1891 im->text_prop[TEXT_PROP_AXIS].size,
1892 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_BOTTOM,
1905 /* draw x and y axis */
1906 /* gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
1907 im->xorigin+im->xsize,im->yorigin-im->ysize,
1908 GRIDWIDTH, im->graph_col[GRC_AXIS]);
1910 gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
1911 im->xorigin+im->xsize,im->yorigin-im->ysize,
1912 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
1914 gfx_new_line ( im->canvas, im->xorigin-4,im->yorigin,
1915 im->xorigin+im->xsize+4,im->yorigin,
1916 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
1918 gfx_new_line ( im->canvas, im->xorigin,im->yorigin+4,
1919 im->xorigin,im->yorigin-im->ysize-4,
1920 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
1923 /* arrow for X and Y axis direction */
1924 gfx_new_area ( im->canvas,
1925 im->xorigin+im->xsize+2, im->yorigin-2,
1926 im->xorigin+im->xsize+2, im->yorigin+3,
1927 im->xorigin+im->xsize+7, im->yorigin+0.5, /* LINEOFFSET */
1928 im->graph_col[GRC_ARROW]);
1930 gfx_new_area ( im->canvas,
1931 im->xorigin-2, im->yorigin-im->ysize-2,
1932 im->xorigin+3, im->yorigin-im->ysize-2,
1933 im->xorigin+0.5, im->yorigin-im->ysize-7, /* LINEOFFSET */
1934 im->graph_col[GRC_ARROW]);
1939 grid_paint(image_desc_t *im)
1943 double X0,Y0; /* points for filled graph and more*/
1946 /* draw 3d border */
1947 node = gfx_new_area (im->canvas, 0,im->yimg,
1949 2,2,im->graph_col[GRC_SHADEA]);
1950 gfx_add_point( node , im->ximg - 2, 2 );
1951 gfx_add_point( node , im->ximg, 0 );
1952 gfx_add_point( node , 0,0 );
1953 /* gfx_add_point( node , 0,im->yimg ); */
1955 node = gfx_new_area (im->canvas, 2,im->yimg-2,
1956 im->ximg-2,im->yimg-2,
1958 im->graph_col[GRC_SHADEB]);
1959 gfx_add_point( node , im->ximg,0);
1960 gfx_add_point( node , im->ximg,im->yimg);
1961 gfx_add_point( node , 0,im->yimg);
1962 /* gfx_add_point( node , 0,im->yimg ); */
1965 if (im->draw_x_grid == 1 )
1968 if (im->draw_y_grid == 1){
1969 if(im->logarithmic){
1970 res = horizontal_log_grid(im);
1972 res = draw_horizontal_grid(im);
1975 /* dont draw horizontal grid if there is no min and max val */
1977 char *nodata = "No Data found";
1978 gfx_new_text(im->canvas,im->ximg/2, (2*im->yorigin-im->ysize) / 2,
1979 im->graph_col[GRC_FONT],
1980 im->text_prop[TEXT_PROP_AXIS].font,
1981 im->text_prop[TEXT_PROP_AXIS].size,
1982 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
1987 /* yaxis unit description */
1988 gfx_new_text( im->canvas,
1989 10, (im->yorigin - im->ysize/2),
1990 im->graph_col[GRC_FONT],
1991 im->text_prop[TEXT_PROP_UNIT].font,
1992 im->text_prop[TEXT_PROP_UNIT].size, im->tabwidth,
1993 RRDGRAPH_YLEGEND_ANGLE,
1994 GFX_H_LEFT, GFX_V_CENTER,
1998 gfx_new_text( im->canvas,
1999 im->ximg/2, im->text_prop[TEXT_PROP_TITLE].size*1.3+4,
2000 im->graph_col[GRC_FONT],
2001 im->text_prop[TEXT_PROP_TITLE].font,
2002 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
2003 GFX_H_CENTER, GFX_V_CENTER,
2005 /* rrdtool 'logo' */
2006 gfx_new_text( im->canvas,
2008 ( im->graph_col[GRC_FONT] & 0xffffff00 ) | 0x00000044,
2009 im->text_prop[TEXT_PROP_AXIS].font,
2010 5.5, im->tabwidth, 270,
2011 GFX_H_RIGHT, GFX_V_TOP,
2012 "RRDTOOL / TOBI OETIKER");
2014 /* graph watermark */
2015 if(im->watermark[0] != '\0') {
2016 gfx_new_text( im->canvas,
2017 im->ximg/2, im->yimg-6,
2018 ( im->graph_col[GRC_FONT] & 0xffffff00 ) | 0x00000044,
2019 im->text_prop[TEXT_PROP_AXIS].font,
2020 5.5, im->tabwidth, 0,
2021 GFX_H_CENTER, GFX_V_BOTTOM,
2026 if( !(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH) ) {
2027 for(i=0;i<im->gdes_c;i++){
2028 if(im->gdes[i].legend[0] =='\0')
2031 /* im->gdes[i].leg_y is the bottom of the legend */
2032 X0 = im->gdes[i].leg_x;
2033 Y0 = im->gdes[i].leg_y;
2034 gfx_new_text ( im->canvas, X0, Y0,
2035 im->graph_col[GRC_FONT],
2036 im->text_prop[TEXT_PROP_LEGEND].font,
2037 im->text_prop[TEXT_PROP_LEGEND].size,
2038 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
2039 im->gdes[i].legend );
2040 /* The legend for GRAPH items starts with "M " to have
2041 enough space for the box */
2042 if ( im->gdes[i].gf != GF_PRINT &&
2043 im->gdes[i].gf != GF_GPRINT &&
2044 im->gdes[i].gf != GF_COMMENT) {
2047 boxH = gfx_get_text_width(im->canvas, 0,
2048 im->text_prop[TEXT_PROP_LEGEND].font,
2049 im->text_prop[TEXT_PROP_LEGEND].size,
2050 im->tabwidth,"o", 0) * 1.2;
2053 /* make sure transparent colors show up the same way as in the graph */
2054 node = gfx_new_area(im->canvas,
2058 im->graph_col[GRC_BACK]);
2059 gfx_add_point ( node, X0+boxH, Y0-boxV );
2061 node = gfx_new_area(im->canvas,
2066 gfx_add_point ( node, X0+boxH, Y0-boxV );
2067 node = gfx_new_line(im->canvas,
2070 1.0,im->graph_col[GRC_FRAME]);
2071 gfx_add_point(node,X0+boxH,Y0);
2072 gfx_add_point(node,X0+boxH,Y0-boxV);
2073 gfx_close_path(node);
2080 /*****************************************************
2081 * lazy check make sure we rely need to create this graph
2082 *****************************************************/
2084 int lazy_check(image_desc_t *im){
2087 struct stat imgstat;
2089 if (im->lazy == 0) return 0; /* no lazy option */
2090 if (stat(im->graphfile,&imgstat) != 0)
2091 return 0; /* can't stat */
2092 /* one pixel in the existing graph is more then what we would
2094 if (time(NULL) - imgstat.st_mtime >
2095 (im->end - im->start) / im->xsize)
2097 if ((fd = fopen(im->graphfile,"rb")) == NULL)
2098 return 0; /* the file does not exist */
2099 switch (im->canvas->imgformat) {
2101 size = PngSize(fd,&(im->ximg),&(im->yimg));
2110 #ifdef WITH_PIECHART
2112 pie_part(image_desc_t *im, gfx_color_t color,
2113 double PieCenterX, double PieCenterY, double Radius,
2114 double startangle, double endangle)
2118 double step=M_PI/50; /* Number of iterations for the circle;
2119 ** 10 is definitely too low, more than
2120 ** 50 seems to be overkill
2123 /* Strange but true: we have to work clockwise or else
2124 ** anti aliasing nor transparency don't work.
2126 ** This test is here to make sure we do it right, also
2127 ** this makes the for...next loop more easy to implement.
2128 ** The return will occur if the user enters a negative number
2129 ** (which shouldn't be done according to the specs) or if the
2130 ** programmers do something wrong (which, as we all know, never
2131 ** happens anyway :)
2133 if (endangle<startangle) return;
2135 /* Hidden feature: Radius decreases each full circle */
2137 while (angle>=2*M_PI) {
2142 node=gfx_new_area(im->canvas,
2143 PieCenterX+sin(startangle)*Radius,
2144 PieCenterY-cos(startangle)*Radius,
2147 PieCenterX+sin(endangle)*Radius,
2148 PieCenterY-cos(endangle)*Radius,
2150 for (angle=endangle;angle-startangle>=step;angle-=step) {
2152 PieCenterX+sin(angle)*Radius,
2153 PieCenterY-cos(angle)*Radius );
2160 graph_size_location(image_desc_t *im, int elements
2162 #ifdef WITH_PIECHART
2168 /* The actual size of the image to draw is determined from
2169 ** several sources. The size given on the command line is
2170 ** the graph area but we need more as we have to draw labels
2171 ** and other things outside the graph area
2174 /* +-+-------------------------------------------+
2175 ** |l|.................title.....................|
2176 ** |e+--+-------------------------------+--------+
2179 ** |l| l| main graph area | chart |
2182 ** |r+--+-------------------------------+--------+
2183 ** |e| | x-axis labels | |
2184 ** |v+--+-------------------------------+--------+
2185 ** | |..............legends......................|
2186 ** +-+-------------------------------------------+
2188 ** +---------------------------------------------+
2194 #ifdef WITH_PIECHART
2199 Xlegend =0, Ylegend =0,
2201 Xspacing =15, Yspacing =15,
2205 if (im->extra_flags & ONLY_GRAPH) {
2207 im->ximg = im->xsize;
2208 im->yimg = im->ysize;
2209 im->yorigin = im->ysize;
2214 if (im->ylegend[0] != '\0' ) {
2215 Xvertical = im->text_prop[TEXT_PROP_UNIT].size *2;
2219 if (im->title[0] != '\0') {
2220 /* The title is placed "inbetween" two text lines so it
2221 ** automatically has some vertical spacing. The horizontal
2222 ** spacing is added here, on each side.
2224 /* don't care for the with of the title
2225 Xtitle = gfx_get_text_width(im->canvas, 0,
2226 im->text_prop[TEXT_PROP_TITLE].font,
2227 im->text_prop[TEXT_PROP_TITLE].size,
2229 im->title, 0) + 2*Xspacing; */
2230 Ytitle = im->text_prop[TEXT_PROP_TITLE].size*2.6+10;
2236 if (im->draw_x_grid) {
2237 Yxlabel=im->text_prop[TEXT_PROP_AXIS].size *2.5;
2239 if (im->draw_y_grid) {
2240 Xylabel=gfx_get_text_width(im->canvas, 0,
2241 im->text_prop[TEXT_PROP_AXIS].font,
2242 im->text_prop[TEXT_PROP_AXIS].size,
2244 "0", 0) * im->unitslength;
2248 #ifdef WITH_PIECHART
2250 im->piesize=im->xsize<im->ysize?im->xsize:im->ysize;
2256 /* Now calculate the total size. Insert some spacing where
2257 desired. im->xorigin and im->yorigin need to correspond
2258 with the lower left corner of the main graph area or, if
2259 this one is not set, the imaginary box surrounding the
2262 /* The legend width cannot yet be determined, as a result we
2263 ** have problems adjusting the image to it. For now, we just
2264 ** forget about it at all; the legend will have to fit in the
2265 ** size already allocated.
2267 im->ximg = Xylabel + Xmain + 2 * Xspacing;
2269 #ifdef WITH_PIECHART
2273 if (Xmain) im->ximg += Xspacing;
2274 #ifdef WITH_PIECHART
2275 if (Xpie) im->ximg += Xspacing;
2278 im->xorigin = Xspacing + Xylabel;
2280 /* the length of the title should not influence with width of the graph
2281 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2283 if (Xvertical) { /* unit description */
2284 im->ximg += Xvertical;
2285 im->xorigin += Xvertical;
2289 /* The vertical size is interesting... we need to compare
2290 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with
2291 ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2292 ** in order to start even thinking about Ylegend or Ywatermark.
2294 ** Do it in three portions: First calculate the inner part,
2295 ** then do the legend, then adjust the total height of the img,
2296 ** adding space for a watermark if one exists;
2299 /* reserve space for main and/or pie */
2301 im->yimg = Ymain + Yxlabel;
2303 #ifdef WITH_PIECHART
2304 if (im->yimg < Ypie) im->yimg = Ypie;
2307 im->yorigin = im->yimg - Yxlabel;
2309 /* reserve space for the title *or* some padding above the graph */
2312 im->yorigin += Ytitle;
2314 im->yimg += 1.5*Yspacing;
2315 im->yorigin += 1.5*Yspacing;
2317 /* reserve space for padding below the graph */
2318 im->yimg += Yspacing;
2320 /* Determine where to place the legends onto the image.
2321 ** Adjust im->yimg to match the space requirements.
2323 if(leg_place(im)==-1)
2326 if (im->watermark[0] != '\0') {
2327 im->yimg += Ywatermark;
2331 if (Xlegend > im->ximg) {
2333 /* reposition Pie */
2337 #ifdef WITH_PIECHART
2338 /* The pie is placed in the upper right hand corner,
2339 ** just below the title (if any) and with sufficient
2343 im->pie_x = im->ximg - Xspacing - Xpie/2;
2344 im->pie_y = im->yorigin-Ymain+Ypie/2;
2346 im->pie_x = im->ximg/2;
2347 im->pie_y = im->yorigin-Ypie/2;
2355 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
2356 /* yes we are loosing precision by doing tos with floats instead of doubles
2357 but it seems more stable this way. */
2359 static int AlmostEqual2sComplement (float A, float B, int maxUlps)
2362 int aInt = *(int*)&A;
2363 int bInt = *(int*)&B;
2365 /* Make sure maxUlps is non-negative and small enough that the
2366 default NAN won't compare as equal to anything. */
2368 /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
2370 /* Make aInt lexicographically ordered as a twos-complement int */
2373 aInt = 0x80000000l - aInt;
2375 /* Make bInt lexicographically ordered as a twos-complement int */
2378 bInt = 0x80000000l - bInt;
2380 intDiff = abs(aInt - bInt);
2382 if (intDiff <= maxUlps)
2388 /* draw that picture thing ... */
2390 graph_paint(image_desc_t *im, char ***calcpr)
2393 int lazy = lazy_check(im);
2394 #ifdef WITH_PIECHART
2396 double PieStart=0.0;
2401 double areazero = 0.0;
2402 graph_desc_t *lastgdes = NULL;
2404 /* if we are lazy and there is nothing to PRINT ... quit now */
2405 if (lazy && im->prt_c==0) return 0;
2407 /* pull the data from the rrd files ... */
2409 if(data_fetch(im)==-1)
2412 /* evaluate VDEF and CDEF operations ... */
2413 if(data_calc(im)==-1)
2416 #ifdef WITH_PIECHART
2417 /* check if we need to draw a piechart */
2418 for(i=0;i<im->gdes_c;i++){
2419 if (im->gdes[i].gf == GF_PART) {
2426 /* calculate and PRINT and GPRINT definitions. We have to do it at
2427 * this point because it will affect the length of the legends
2428 * if there are no graph elements we stop here ...
2429 * if we are lazy, try to quit ...
2431 i=print_calc(im,calcpr);
2434 #ifdef WITH_PIECHART
2437 ) || lazy) return 0;
2439 #ifdef WITH_PIECHART
2440 /* If there's only the pie chart to draw, signal this */
2441 if (i==0) piechart=2;
2444 /* get actual drawing data and find min and max values*/
2445 if(data_proc(im)==-1)
2448 if(!im->logarithmic){si_unit(im);} /* identify si magnitude Kilo, Mega Giga ? */
2450 if(!im->rigid && ! im->logarithmic)
2451 expand_range(im); /* make sure the upper and lower limit are
2454 if (!calc_horizontal_grid(im))
2461 /**************************************************************
2462 *** Calculating sizes and locations became a bit confusing ***
2463 *** so I moved this into a separate function. ***
2464 **************************************************************/
2465 if(graph_size_location(im,i
2466 #ifdef WITH_PIECHART
2472 /* the actual graph is created by going through the individual
2473 graph elements and then drawing them */
2475 node=gfx_new_area ( im->canvas,
2479 im->graph_col[GRC_BACK]);
2481 gfx_add_point(node,im->ximg, 0);
2483 #ifdef WITH_PIECHART
2484 if (piechart != 2) {
2486 node=gfx_new_area ( im->canvas,
2487 im->xorigin, im->yorigin,
2488 im->xorigin + im->xsize, im->yorigin,
2489 im->xorigin + im->xsize, im->yorigin-im->ysize,
2490 im->graph_col[GRC_CANVAS]);
2492 gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
2494 if (im->minval > 0.0)
2495 areazero = im->minval;
2496 if (im->maxval < 0.0)
2497 areazero = im->maxval;
2498 #ifdef WITH_PIECHART
2502 #ifdef WITH_PIECHART
2504 pie_part(im,im->graph_col[GRC_CANVAS],im->pie_x,im->pie_y,im->piesize*0.5,0,2*M_PI);
2508 for(i=0;i<im->gdes_c;i++){
2509 switch(im->gdes[i].gf){
2522 for (ii = 0; ii < im->xsize; ii++)
2524 if (!isnan(im->gdes[i].p_data[ii]) &&
2525 im->gdes[i].p_data[ii] != 0.0)
2527 if (im -> gdes[i].yrule > 0 ) {
2528 gfx_new_line(im->canvas,
2529 im -> xorigin + ii, im->yorigin,
2530 im -> xorigin + ii, im->yorigin - im -> gdes[i].yrule * im -> ysize,
2532 im -> gdes[i].col );
2533 } else if ( im -> gdes[i].yrule < 0 ) {
2534 gfx_new_line(im->canvas,
2535 im -> xorigin + ii, im->yorigin - im -> ysize,
2536 im -> xorigin + ii, im->yorigin - ( 1 - im -> gdes[i].yrule ) * im -> ysize,
2538 im -> gdes[i].col );
2546 /* fix data points at oo and -oo */
2547 for(ii=0;ii<im->xsize;ii++){
2548 if (isinf(im->gdes[i].p_data[ii])){
2549 if (im->gdes[i].p_data[ii] > 0) {
2550 im->gdes[i].p_data[ii] = im->maxval ;
2552 im->gdes[i].p_data[ii] = im->minval ;
2558 /* *******************************************************
2563 -------|--t-1--t--------------------------------
2565 if we know the value at time t was a then
2566 we draw a square from t-1 to t with the value a.
2568 ********************************************************* */
2569 if (im->gdes[i].col != 0x0){
2570 /* GF_LINE and friend */
2571 if(im->gdes[i].gf == GF_LINE ){
2574 for(ii=1;ii<im->xsize;ii++){
2575 if (isnan(im->gdes[i].p_data[ii]) || (im->slopemode==1 && isnan(im->gdes[i].p_data[ii-1]))){
2579 if ( node == NULL ) {
2580 last_y = ytr(im,im->gdes[i].p_data[ii]);
2581 if ( im->slopemode == 0 ){
2582 node = gfx_new_line(im->canvas,
2583 ii-1+im->xorigin,last_y,
2584 ii+im->xorigin,last_y,
2585 im->gdes[i].linewidth,
2588 node = gfx_new_line(im->canvas,
2589 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2590 ii+im->xorigin,last_y,
2591 im->gdes[i].linewidth,
2595 double new_y = ytr(im,im->gdes[i].p_data[ii]);
2596 if ( im->slopemode==0 && ! AlmostEqual2sComplement(new_y,last_y,4)){
2597 gfx_add_point(node,ii-1+im->xorigin,new_y);
2600 gfx_add_point(node,ii+im->xorigin,new_y);
2606 double *foreY=malloc(sizeof(double)*im->xsize*2);
2607 double *foreX=malloc(sizeof(double)*im->xsize*2);
2608 double *backY=malloc(sizeof(double)*im->xsize*2);
2609 double *backX=malloc(sizeof(double)*im->xsize*2);
2611 for(ii=0;ii<=im->xsize;ii++){
2613 if ( idxI > 0 && ( drawem != 0 || ii==im->xsize)){
2616 while (cntI < idxI && AlmostEqual2sComplement(foreY[lastI],foreY[cntI],4) && AlmostEqual2sComplement(foreY[lastI],foreY[cntI+1],4)){cntI++;}
2617 node = gfx_new_area(im->canvas,
2620 foreX[cntI],foreY[cntI], im->gdes[i].col);
2621 while (cntI < idxI) {
2624 while ( cntI < idxI && AlmostEqual2sComplement(foreY[lastI],foreY[cntI],4) && AlmostEqual2sComplement(foreY[lastI],foreY[cntI+1],4)){cntI++;}
2625 gfx_add_point(node,foreX[cntI],foreY[cntI]);
2627 gfx_add_point(node,backX[idxI],backY[idxI]);
2631 while ( idxI > 1 && AlmostEqual2sComplement(backY[lastI], backY[idxI],4) && AlmostEqual2sComplement(backY[lastI],backY[idxI-1],4)){idxI--;}
2632 gfx_add_point(node,backX[idxI],backY[idxI]);
2641 if (ii == im->xsize) break;
2643 /* keep things simple for now, just draw these bars
2644 do not try to build a big and complex area */
2647 if ( im->slopemode == 0 && ii==0){
2650 if ( isnan(im->gdes[i].p_data[ii]) ) {
2654 ytop = ytr(im,im->gdes[i].p_data[ii]);
2655 if ( lastgdes && im->gdes[i].stack ) {
2656 ybase = ytr(im,lastgdes->p_data[ii]);
2658 ybase = ytr(im,areazero);
2660 if ( ybase == ytop ){
2664 /* every area has to be wound clock-wise,
2665 so we have to make sur base remains base */
2667 double extra = ytop;
2671 if ( im->slopemode == 0 ){
2672 backY[++idxI] = ybase-0.2;
2673 backX[idxI] = ii+im->xorigin-1;
2674 foreY[idxI] = ytop+0.2;
2675 foreX[idxI] = ii+im->xorigin-1;
2677 backY[++idxI] = ybase-0.2;
2678 backX[idxI] = ii+im->xorigin;
2679 foreY[idxI] = ytop+0.2;
2680 foreX[idxI] = ii+im->xorigin;
2682 /* close up any remaining area */
2687 } /* else GF_LINE */
2688 } /* if color != 0x0 */
2689 /* make sure we do not run into trouble when stacking on NaN */
2690 for(ii=0;ii<im->xsize;ii++){
2691 if (isnan(im->gdes[i].p_data[ii])) {
2692 if (lastgdes && (im->gdes[i].stack)) {
2693 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
2695 im->gdes[i].p_data[ii] = areazero;
2699 lastgdes = &(im->gdes[i]);
2701 #ifdef WITH_PIECHART
2703 if(isnan(im->gdes[i].yrule)) /* fetch variable */
2704 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2706 if (finite(im->gdes[i].yrule)) { /* even the fetched var can be NaN */
2707 pie_part(im,im->gdes[i].col,
2708 im->pie_x,im->pie_y,im->piesize*0.4,
2709 M_PI*2.0*PieStart/100.0,
2710 M_PI*2.0*(PieStart+im->gdes[i].yrule)/100.0);
2711 PieStart += im->gdes[i].yrule;
2716 rrd_set_error("STACK should already be turned into LINE or AREA here");
2722 #ifdef WITH_PIECHART
2730 /* grid_paint also does the text */
2731 if( !(im->extra_flags & ONLY_GRAPH) )
2735 if( !(im->extra_flags & ONLY_GRAPH) )
2738 /* the RULES are the last thing to paint ... */
2739 for(i=0;i<im->gdes_c;i++){
2741 switch(im->gdes[i].gf){
2743 if(isnan(im->gdes[i].yrule)) { /* fetch variable */
2744 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2746 if(im->gdes[i].yrule >= im->minval
2747 && im->gdes[i].yrule <= im->maxval)
2748 gfx_new_line(im->canvas,
2749 im->xorigin,ytr(im,im->gdes[i].yrule),
2750 im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2751 1.0,im->gdes[i].col);
2754 if(im->gdes[i].xrule == 0) { /* fetch variable */
2755 im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
2757 if(im->gdes[i].xrule >= im->start
2758 && im->gdes[i].xrule <= im->end)
2759 gfx_new_line(im->canvas,
2760 xtr(im,im->gdes[i].xrule),im->yorigin,
2761 xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2762 1.0,im->gdes[i].col);
2770 if (strcmp(im->graphfile,"-")==0) {
2771 fo = im->graphhandle ? im->graphhandle : stdout;
2772 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
2773 /* Change translation mode for stdout to BINARY */
2774 _setmode( _fileno( fo ), O_BINARY );
2777 if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2778 rrd_set_error("Opening '%s' for write: %s",im->graphfile,
2779 rrd_strerror(errno));
2783 gfx_render (im->canvas,im->ximg,im->yimg,0x00000000,fo);
2784 if (strcmp(im->graphfile,"-") != 0)
2790 /*****************************************************
2792 *****************************************************/
2795 gdes_alloc(image_desc_t *im){
2798 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2799 * sizeof(graph_desc_t)))==NULL){
2800 rrd_set_error("realloc graph_descs");
2805 im->gdes[im->gdes_c-1].step=im->step;
2806 im->gdes[im->gdes_c-1].step_orig=im->step;
2807 im->gdes[im->gdes_c-1].stack=0;
2808 im->gdes[im->gdes_c-1].linewidth=0;
2809 im->gdes[im->gdes_c-1].debug=0;
2810 im->gdes[im->gdes_c-1].start=im->start;
2811 im->gdes[im->gdes_c-1].start_orig=im->start;
2812 im->gdes[im->gdes_c-1].end=im->end;
2813 im->gdes[im->gdes_c-1].end_orig=im->end;
2814 im->gdes[im->gdes_c-1].vname[0]='\0';
2815 im->gdes[im->gdes_c-1].data=NULL;
2816 im->gdes[im->gdes_c-1].ds_namv=NULL;
2817 im->gdes[im->gdes_c-1].data_first=0;
2818 im->gdes[im->gdes_c-1].p_data=NULL;
2819 im->gdes[im->gdes_c-1].rpnp=NULL;
2820 im->gdes[im->gdes_c-1].shift=0;
2821 im->gdes[im->gdes_c-1].col = 0x0;
2822 im->gdes[im->gdes_c-1].legend[0]='\0';
2823 im->gdes[im->gdes_c-1].format[0]='\0';
2824 im->gdes[im->gdes_c-1].rrd[0]='\0';
2825 im->gdes[im->gdes_c-1].ds=-1;
2826 im->gdes[im->gdes_c-1].cf_reduce=CF_AVERAGE;
2827 im->gdes[im->gdes_c-1].cf=CF_AVERAGE;
2828 im->gdes[im->gdes_c-1].p_data=NULL;
2829 im->gdes[im->gdes_c-1].yrule=DNAN;
2830 im->gdes[im->gdes_c-1].xrule=0;
2834 /* copies input untill the first unescaped colon is found
2835 or until input ends. backslashes have to be escaped as well */
2837 scan_for_col(const char *const input, int len, char *const output)
2842 input[inp] != ':' &&
2845 if (input[inp] == '\\' &&
2846 input[inp+1] != '\0' &&
2847 (input[inp+1] == '\\' ||
2848 input[inp+1] == ':')){
2849 output[outp++] = input[++inp];
2852 output[outp++] = input[inp];
2855 output[outp] = '\0';
2858 /* Some surgery done on this function, it became ridiculously big.
2860 ** - initializing now in rrd_graph_init()
2861 ** - options parsing now in rrd_graph_options()
2862 ** - script parsing now in rrd_graph_script()
2865 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize, FILE *stream, double *ymin, double *ymax)
2868 rrd_graph_init(&im);
2869 im.graphhandle = stream;
2871 rrd_graph_options(argc,argv,&im);
2872 if (rrd_test_error()) {
2877 if (strlen(argv[optind])>=MAXPATH) {
2878 rrd_set_error("filename (including path) too long");
2882 strncpy(im.graphfile,argv[optind],MAXPATH-1);
2883 im.graphfile[MAXPATH-1]='\0';
2885 rrd_graph_script(argc,argv,&im,1);
2886 if (rrd_test_error()) {
2891 /* Everything is now read and the actual work can start */
2894 if (graph_paint(&im,prdata)==-1){
2899 /* The image is generated and needs to be output.
2900 ** Also, if needed, print a line with information about the image.
2910 /* maybe prdata is not allocated yet ... lets do it now */
2911 if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
2912 rrd_set_error("malloc imginfo");
2916 if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
2918 rrd_set_error("malloc imginfo");
2921 filename=im.graphfile+strlen(im.graphfile);
2922 while(filename > im.graphfile) {
2923 if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
2927 sprintf((*prdata)[0],im.imginfo,filename,(long)(im.canvas->zoom*im.ximg),(long)(im.canvas->zoom*im.yimg));
2934 rrd_graph_init(image_desc_t *im)
2941 #ifdef HAVE_SETLOCALE
2942 setlocale(LC_TIME,"");
2943 #ifdef HAVE_MBSTOWCS
2944 setlocale(LC_CTYPE,"");
2950 im->xlab_user.minsec = -1;
2956 im->ylegend[0] = '\0';
2957 im->title[0] = '\0';
2958 im->watermark[0] = '\0';
2961 im->unitsexponent= 9999;
2964 im->viewfactor = 1.0;
2971 im->logarithmic = 0;
2972 im->ygridstep = DNAN;
2973 im->draw_x_grid = 1;
2974 im->draw_y_grid = 1;
2979 im->canvas = gfx_new_canvas();
2980 im->grid_dash_on = 1;
2981 im->grid_dash_off = 1;
2982 im->tabwidth = 40.0;
2984 for(i=0;i<DIM(graph_col);i++)
2985 im->graph_col[i]=graph_col[i];
2987 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
2990 char rrd_win_default_font[1000];
2991 windir = getenv("windir");
2992 /* %windir% is something like D:\windows or C:\winnt */
2993 if (windir != NULL) {
2994 strncpy(rrd_win_default_font,windir,500);
2995 rrd_win_default_font[500] = '\0';
2996 strcat(rrd_win_default_font,"\\fonts\\");
2997 strcat(rrd_win_default_font,RRD_DEFAULT_FONT);
2998 for(i=0;i<DIM(text_prop);i++){
2999 strncpy(text_prop[i].font,rrd_win_default_font,sizeof(text_prop[i].font)-1);
3000 text_prop[i].font[sizeof(text_prop[i].font)-1] = '\0';
3007 deffont = getenv("RRD_DEFAULT_FONT");
3008 if (deffont != NULL) {
3009 for(i=0;i<DIM(text_prop);i++){
3010 strncpy(text_prop[i].font,deffont,sizeof(text_prop[i].font)-1);
3011 text_prop[i].font[sizeof(text_prop[i].font)-1] = '\0';
3015 for(i=0;i<DIM(text_prop);i++){
3016 im->text_prop[i].size = text_prop[i].size;
3017 strcpy(im->text_prop[i].font,text_prop[i].font);
3022 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
3025 char *parsetime_error = NULL;
3026 char scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
3027 time_t start_tmp=0,end_tmp=0;
3029 struct rrd_time_value start_tv, end_tv;
3031 optind = 0; opterr = 0; /* initialize getopt */
3033 parsetime("end-24h", &start_tv);
3034 parsetime("now", &end_tv);
3036 /* defines for long options without a short equivalent. should be bytes,
3037 and may not collide with (the ASCII value of) short options */
3038 #define LONGOPT_UNITS_SI 255
3041 static struct option long_options[] =
3043 {"start", required_argument, 0, 's'},
3044 {"end", required_argument, 0, 'e'},
3045 {"x-grid", required_argument, 0, 'x'},
3046 {"y-grid", required_argument, 0, 'y'},
3047 {"vertical-label",required_argument,0,'v'},
3048 {"width", required_argument, 0, 'w'},
3049 {"height", required_argument, 0, 'h'},
3050 {"interlaced", no_argument, 0, 'i'},
3051 {"upper-limit",required_argument, 0, 'u'},
3052 {"lower-limit",required_argument, 0, 'l'},
3053 {"rigid", no_argument, 0, 'r'},
3054 {"base", required_argument, 0, 'b'},
3055 {"logarithmic",no_argument, 0, 'o'},
3056 {"color", required_argument, 0, 'c'},
3057 {"font", required_argument, 0, 'n'},
3058 {"title", required_argument, 0, 't'},
3059 {"imginfo", required_argument, 0, 'f'},
3060 {"imgformat", required_argument, 0, 'a'},
3061 {"lazy", no_argument, 0, 'z'},
3062 {"zoom", required_argument, 0, 'm'},
3063 {"no-legend", no_argument, 0, 'g'},
3064 {"force-rules-legend",no_argument,0, 'F'},
3065 {"only-graph", no_argument, 0, 'j'},
3066 {"alt-y-grid", no_argument, 0, 'Y'},
3067 {"no-minor", no_argument, 0, 'I'},
3068 {"slope-mode", no_argument, 0, 'E'},
3069 {"alt-autoscale", no_argument, 0, 'A'},
3070 {"alt-autoscale-max", no_argument, 0, 'M'},
3071 {"no-gridfit", no_argument, 0, 'N'},
3072 {"units-exponent",required_argument, 0, 'X'},
3073 {"units-length",required_argument, 0, 'L'},
3074 {"units", required_argument, 0, LONGOPT_UNITS_SI },
3075 {"step", required_argument, 0, 'S'},
3076 {"tabwidth", required_argument, 0, 'T'},
3077 {"font-render-mode", required_argument, 0, 'R'},
3078 {"font-smoothing-threshold", required_argument, 0, 'B'},
3079 {"watermark", required_argument, 0, 'W'},
3080 {"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 */
3082 int option_index = 0;
3084 int col_start,col_end;
3086 opt = getopt_long(argc, argv,
3087 "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:I:zgjFYAMEX:L:S:T:NR:B:W:",
3088 long_options, &option_index);
3095 im->extra_flags |= NOMINOR;
3098 im->extra_flags |= ALTYGRID;
3101 im->extra_flags |= ALTAUTOSCALE;
3104 im->extra_flags |= ALTAUTOSCALE_MAX;
3107 im->extra_flags |= ONLY_GRAPH;
3110 im->extra_flags |= NOLEGEND;
3113 im->extra_flags |= FORCE_RULES_LEGEND;
3115 case LONGOPT_UNITS_SI:
3116 if(im->extra_flags & FORCE_UNITS) {
3117 rrd_set_error("--units can only be used once!");
3120 if(strcmp(optarg,"si")==0)
3121 im->extra_flags |= FORCE_UNITS_SI;
3123 rrd_set_error("invalid argument for --units: %s", optarg );
3128 im->unitsexponent = atoi(optarg);
3131 im->unitslength = atoi(optarg);
3134 im->tabwidth = atof(optarg);
3137 im->step = atoi(optarg);
3143 if ((parsetime_error = parsetime(optarg, &start_tv))) {
3144 rrd_set_error( "start time: %s", parsetime_error );
3149 if ((parsetime_error = parsetime(optarg, &end_tv))) {
3150 rrd_set_error( "end time: %s", parsetime_error );
3155 if(strcmp(optarg,"none") == 0){
3161 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3163 &im->xlab_user.gridst,
3165 &im->xlab_user.mgridst,
3167 &im->xlab_user.labst,
3168 &im->xlab_user.precis,
3169 &stroff) == 7 && stroff != 0){
3170 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
3171 im->xlab_form[sizeof(im->xlab_form)-1] = '\0';
3172 if((int)(im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
3173 rrd_set_error("unknown keyword %s",scan_gtm);
3175 } else if ((int)(im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
3176 rrd_set_error("unknown keyword %s",scan_mtm);
3178 } else if ((int)(im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
3179 rrd_set_error("unknown keyword %s",scan_ltm);
3182 im->xlab_user.minsec = 1;
3183 im->xlab_user.stst = im->xlab_form;
3185 rrd_set_error("invalid x-grid format");
3191 if(strcmp(optarg,"none") == 0){
3199 &im->ylabfact) == 2) {
3200 if(im->ygridstep<=0){
3201 rrd_set_error("grid step must be > 0");
3203 } else if (im->ylabfact < 1){
3204 rrd_set_error("label factor must be > 0");
3208 rrd_set_error("invalid y-grid format");
3213 strncpy(im->ylegend,optarg,150);
3214 im->ylegend[150]='\0';
3217 im->maxval = atof(optarg);
3220 im->minval = atof(optarg);
3223 im->base = atol(optarg);
3224 if(im->base != 1024 && im->base != 1000 ){
3225 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
3230 long_tmp = atol(optarg);
3231 if (long_tmp < 10) {
3232 rrd_set_error("width below 10 pixels");
3235 im->xsize = long_tmp;
3238 long_tmp = atol(optarg);
3239 if (long_tmp < 10) {
3240 rrd_set_error("height below 10 pixels");
3243 im->ysize = long_tmp;
3246 im->canvas->interlaced = 1;
3252 im->imginfo = optarg;
3255 if((int)(im->canvas->imgformat = if_conv(optarg)) == -1) {
3256 rrd_set_error("unsupported graphics format '%s'",optarg);
3268 im->logarithmic = 1;
3269 if (isnan(im->minval))
3274 "%10[A-Z]#%n%8lx%n",
3275 col_nam,&col_start,&color,&col_end) == 2){
3277 int col_len = col_end - col_start;
3281 ((color & 0xF00) * 0x110000) |
3282 ((color & 0x0F0) * 0x011000) |
3283 ((color & 0x00F) * 0x001100) |
3289 ((color & 0xF000) * 0x11000) |
3290 ((color & 0x0F00) * 0x01100) |
3291 ((color & 0x00F0) * 0x00110) |
3292 ((color & 0x000F) * 0x00011)
3296 color = (color << 8) + 0xff /* shift left by 8 */;
3301 rrd_set_error("the color format is #RRGGBB[AA]");
3304 if((ci=grc_conv(col_nam)) != -1){
3305 im->graph_col[ci]=color;
3307 rrd_set_error("invalid color name '%s'",col_nam);
3311 rrd_set_error("invalid color def format");
3318 char font[1024] = "";
3321 "%10[A-Z]:%lf:%1000s",
3322 prop,&size,font) >= 2){
3324 if((sindex=text_prop_conv(prop)) != -1){
3325 for (propidx=sindex;propidx<TEXT_PROP_LAST;propidx++){
3327 im->text_prop[propidx].size=size;
3329 if (strlen(font) > 0){
3330 strcpy(im->text_prop[propidx].font,font);
3332 if (propidx==sindex && sindex != 0) break;
3335 rrd_set_error("invalid fonttag '%s'",prop);
3339 rrd_set_error("invalid text property format");
3345 im->canvas->zoom = atof(optarg);
3346 if (im->canvas->zoom <= 0.0) {
3347 rrd_set_error("zoom factor must be > 0");
3352 strncpy(im->title,optarg,150);
3353 im->title[150]='\0';
3357 if ( strcmp( optarg, "normal" ) == 0 )
3358 im->canvas->aa_type = AA_NORMAL;
3359 else if ( strcmp( optarg, "light" ) == 0 )
3360 im->canvas->aa_type = AA_LIGHT;
3361 else if ( strcmp( optarg, "mono" ) == 0 )
3362 im->canvas->aa_type = AA_NONE;
3365 rrd_set_error("unknown font-render-mode '%s'", optarg );
3371 im->canvas->font_aa_threshold = atof(optarg);
3375 strncpy(im->watermark,optarg,100);
3376 im->watermark[99]='\0';
3381 rrd_set_error("unknown option '%c'", optopt);
3383 rrd_set_error("unknown option '%s'",argv[optind-1]);
3388 if (optind >= argc) {
3389 rrd_set_error("missing filename");
3393 if (im->logarithmic == 1 && (im->minval <= 0 || isnan(im->minval))){
3394 rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");
3398 if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
3399 /* error string is set in parsetime.c */
3403 if (start_tmp < 3600*24*365*10){
3404 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
3408 if (end_tmp < start_tmp) {
3409 rrd_set_error("start (%ld) should be less than end (%ld)",
3410 start_tmp, end_tmp);
3414 im->start = start_tmp;
3416 im->step = max((long)im->step, (im->end-im->start)/im->xsize);
3420 rrd_graph_check_vname(image_desc_t *im, char *varname, char *err)
3422 if ((im->gdes[im->gdes_c-1].vidx=find_var(im,varname))==-1) {
3423 rrd_set_error("Unknown variable '%s' in %s",varname,err);
3429 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
3432 graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
3434 color=strstr(var,"#");
3437 rrd_set_error("Found no color in %s",err);
3446 rest=strstr(color,":");
3454 sscanf(color,"#%6lx%n",&col,&n);
3455 col = (col << 8) + 0xff /* shift left by 8 */;
3456 if (n!=7) rrd_set_error("Color problem in %s",err);
3459 sscanf(color,"#%8lx%n",&col,&n);
3462 rrd_set_error("Color problem in %s",err);
3464 if (rrd_test_error()) return 0;
3471 int bad_format(char *fmt) {
3475 while (*ptr != '\0')
3476 if (*ptr++ == '%') {
3478 /* line cannot end with percent char */
3479 if (*ptr == '\0') return 1;
3481 /* '%s', '%S' and '%%' are allowed */
3482 if (*ptr == 's' || *ptr == 'S' || *ptr == '%') ptr++;
3484 /* %c is allowed (but use only with vdef!) */
3485 else if (*ptr == 'c') {
3490 /* or else '% 6.2lf' and such are allowed */
3492 /* optional padding character */
3493 if (*ptr == ' ' || *ptr == '+' || *ptr == '-') ptr++;
3495 /* This should take care of 'm.n' with all three optional */
3496 while (*ptr >= '0' && *ptr <= '9') ptr++;
3497 if (*ptr == '.') ptr++;
3498 while (*ptr >= '0' && *ptr <= '9') ptr++;
3500 /* Either 'le', 'lf' or 'lg' must follow here */
3501 if (*ptr++ != 'l') return 1;
3502 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g') ptr++;
3513 vdef_parse(gdes,str)
3514 struct graph_desc_t *gdes;
3515 const char *const str;
3517 /* A VDEF currently is either "func" or "param,func"
3518 * so the parsing is rather simple. Change if needed.
3525 sscanf(str,"%le,%29[A-Z]%n",¶m,func,&n);
3526 if (n== (int)strlen(str)) { /* matched */
3530 sscanf(str,"%29[A-Z]%n",func,&n);
3531 if (n== (int)strlen(str)) { /* matched */
3534 rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3541 if (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3542 else if (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3543 else if (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3544 else if (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3545 else if (!strcmp("TOTAL", func)) gdes->vf.op = VDEF_TOTAL;
3546 else if (!strcmp("FIRST", func)) gdes->vf.op = VDEF_FIRST;
3547 else if (!strcmp("LAST", func)) gdes->vf.op = VDEF_LAST;
3548 else if (!strcmp("LSLSLOPE", func)) gdes->vf.op = VDEF_LSLSLOPE;
3549 else if (!strcmp("LSLINT", func)) gdes->vf.op = VDEF_LSLINT;
3550 else if (!strcmp("LSLCORREL",func)) gdes->vf.op = VDEF_LSLCORREL;
3552 rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3559 switch (gdes->vf.op) {
3561 if (isnan(param)) { /* no parameter given */
3562 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3568 if (param>=0.0 && param<=100.0) {
3569 gdes->vf.param = param;
3570 gdes->vf.val = DNAN; /* undefined */
3571 gdes->vf.when = 0; /* undefined */
3573 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3588 case VDEF_LSLCORREL:
3590 gdes->vf.param = DNAN;
3591 gdes->vf.val = DNAN;
3594 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3611 graph_desc_t *src,*dst;
3615 dst = &im->gdes[gdi];
3616 src = &im->gdes[dst->vidx];
3617 data = src->data + src->ds;
3618 steps = (src->end - src->start) / src->step;
3621 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3628 switch (dst->vf.op) {
3629 case VDEF_PERCENT: {
3630 rrd_value_t * array;
3634 if ((array = malloc(steps*sizeof(double)))==NULL) {
3635 rrd_set_error("malloc VDEV_PERCENT");
3638 for (step=0;step < steps; step++) {
3639 array[step]=data[step*src->ds_cnt];
3641 qsort(array,step,sizeof(double),vdef_percent_compar);
3643 field = (steps-1)*dst->vf.param/100;
3644 dst->vf.val = array[field];
3645 dst->vf.when = 0; /* no time component */
3648 for(step=0;step<steps;step++)
3649 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3655 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3656 if (step == steps) {
3660 dst->vf.val = data[step*src->ds_cnt];
3661 dst->vf.when = src->start + (step+1)*src->step;
3663 while (step != steps) {
3664 if (finite(data[step*src->ds_cnt])) {
3665 if (data[step*src->ds_cnt] > dst->vf.val) {
3666 dst->vf.val = data[step*src->ds_cnt];
3667 dst->vf.when = src->start + (step+1)*src->step;
3674 case VDEF_AVERAGE: {
3677 for (step=0;step<steps;step++) {
3678 if (finite(data[step*src->ds_cnt])) {
3679 sum += data[step*src->ds_cnt];
3684 if (dst->vf.op == VDEF_TOTAL) {
3685 dst->vf.val = sum*src->step;
3686 dst->vf.when = cnt*src->step; /* not really "when" */
3688 dst->vf.val = sum/cnt;
3689 dst->vf.when = 0; /* no time component */
3699 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3700 if (step == steps) {
3704 dst->vf.val = data[step*src->ds_cnt];
3705 dst->vf.when = src->start + (step+1)*src->step;
3707 while (step != steps) {
3708 if (finite(data[step*src->ds_cnt])) {
3709 if (data[step*src->ds_cnt] < dst->vf.val) {
3710 dst->vf.val = data[step*src->ds_cnt];
3711 dst->vf.when = src->start + (step+1)*src->step;
3718 /* The time value returned here is one step before the
3719 * actual time value. This is the start of the first
3723 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3724 if (step == steps) { /* all entries were NaN */
3728 dst->vf.val = data[step*src->ds_cnt];
3729 dst->vf.when = src->start + step*src->step;
3733 /* The time value returned here is the
3734 * actual time value. This is the end of the last
3738 while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3739 if (step < 0) { /* all entries were NaN */
3743 dst->vf.val = data[step*src->ds_cnt];
3744 dst->vf.when = src->start + (step+1)*src->step;
3749 case VDEF_LSLCORREL:{
3750 /* Bestfit line by linear least squares method */
3753 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl ;
3754 SUMx = 0; SUMy = 0; SUMxy = 0; SUMxx = 0; SUMyy = 0;
3756 for (step=0;step<steps;step++) {
3757 if (finite(data[step*src->ds_cnt])) {
3760 SUMxx += step * step;
3761 SUMxy += step * data[step*src->ds_cnt];
3762 SUMy += data[step*src->ds_cnt];
3763 SUMyy += data[step*src->ds_cnt]*data[step*src->ds_cnt];
3767 slope = ( SUMx*SUMy - cnt*SUMxy ) / ( SUMx*SUMx - cnt*SUMxx );
3768 y_intercept = ( SUMy - slope*SUMx ) / cnt;
3769 correl = (SUMxy - (SUMx*SUMy)/cnt) / sqrt((SUMxx - (SUMx*SUMx)/cnt)*(SUMyy - (SUMy*SUMy)/cnt));
3772 if (dst->vf.op == VDEF_LSLSLOPE) {
3773 dst->vf.val = slope;
3774 dst->vf.when = cnt*src->step;
3775 } else if (dst->vf.op == VDEF_LSLINT) {
3776 dst->vf.val = y_intercept;
3777 dst->vf.when = cnt*src->step;
3778 } else if (dst->vf.op == VDEF_LSLCORREL) {
3779 dst->vf.val = correl;
3780 dst->vf.when = cnt*src->step;
3793 /* NaN < -INF < finite_values < INF */
3795 vdef_percent_compar(a,b)
3798 /* Equality is not returned; this doesn't hurt except
3799 * (maybe) for a little performance.
3802 /* First catch NaN values. They are smallest */
3803 if (isnan( *(double *)a )) return -1;
3804 if (isnan( *(double *)b )) return 1;
3806 /* NaN doesn't reach this part so INF and -INF are extremes.
3807 * The sign from isinf() is compatible with the sign we return
3809 if (isinf( *(double *)a )) return isinf( *(double *)a );
3810 if (isinf( *(double *)b )) return isinf( *(double *)b );
3812 /* If we reach this, both values must be finite */
3813 if ( *(double *)a < *(double *)b ) return -1; else return 1;