1 /****************************************************************************
2 * RRDtool 1.1.x Copyright Tobias Oetiker, 1997 - 2002
3 ****************************************************************************
4 * rrd__graph.c make creates ne rrds
5 ****************************************************************************/
25 #include "rrd_graph.h"
27 /* some constant definitions */
31 char rrd_win_default_font[80];
34 #ifndef RRD_DEFAULT_FONT
36 #define RRD_DEFAULT_FONT "VeraMono.ttf"
37 /* #define RRD_DEFAULT_FONT "/usr/share/fonts/truetype/openoffice/ariosor.ttf" */
38 /* #define RRD_DEFAULT_FONT "/usr/share/fonts/truetype/Arial.ttf" */
42 text_prop_t text_prop[] = {
43 { 10.0, RRD_DEFAULT_FONT }, /* default */
44 { 10.0, RRD_DEFAULT_FONT }, /* title */
45 { 8.0, RRD_DEFAULT_FONT }, /* axis */
46 { 10.0, RRD_DEFAULT_FONT }, /* unit */
47 { 10.0, RRD_DEFAULT_FONT } /* legend */
51 {0, TMT_SECOND,30, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
52 {2, TMT_MINUTE,1, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
53 {5, TMT_MINUTE,2, TMT_MINUTE,10, TMT_MINUTE,10, 0,"%H:%M"},
54 {10, TMT_MINUTE,5, TMT_MINUTE,20, TMT_MINUTE,20, 0,"%H:%M"},
55 {30, TMT_MINUTE,10, TMT_HOUR,1, TMT_HOUR,1, 0,"%H:%M"},
56 {60, TMT_MINUTE,30, TMT_HOUR,2, TMT_HOUR,2, 0,"%H:%M"},
57 {180, TMT_HOUR,1, TMT_HOUR,6, TMT_HOUR,6, 0,"%H:%M"},
58 /*{300, TMT_HOUR,3, TMT_HOUR,12, TMT_HOUR,12, 12*3600,"%a %p"}, this looks silly*/
59 {600, TMT_HOUR,6, TMT_DAY,1, TMT_DAY,1, 24*3600,"%a"},
60 {1800, TMT_HOUR,12, TMT_DAY,1, TMT_DAY,2, 24*3600,"%a"},
61 {3600, TMT_DAY,1, TMT_WEEK,1, TMT_WEEK,1, 7*24*3600,"Week %V"},
62 {3*3600, TMT_WEEK,1, TMT_MONTH,1, TMT_WEEK,2, 7*24*3600,"Week %V"},
63 {6*3600, TMT_MONTH,1, TMT_MONTH,1, TMT_MONTH,1, 30*24*3600,"%b"},
64 {48*3600, TMT_MONTH,1, TMT_MONTH,3, TMT_MONTH,3, 30*24*3600,"%b"},
65 {10*24*3600, TMT_YEAR,1, TMT_YEAR,1, TMT_YEAR,1, 365*24*3600,"%y"},
66 {-1,TMT_MONTH,0,TMT_MONTH,0,TMT_MONTH,0,0,""}
69 /* sensible logarithmic y label intervals ...
70 the first element of each row defines the possible starting points on the
71 y axis ... the other specify the */
73 double yloglab[][12]= {{ 1e9, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
74 { 1e3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
75 { 1e1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
76 /* { 1e1, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, */
77 { 1e1, 1, 2.5, 5, 7.5, 0, 0, 0, 0, 0, 0, 0 },
78 { 1e1, 1, 2, 4, 6, 8, 0, 0, 0, 0, 0, 0 },
79 { 1e1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0 },
80 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }};
82 /* sensible y label intervals ...*/
100 gfx_color_t graph_col[] = /* default colors */
101 { 0xFFFFFFFF, /* canvas */
102 0xF0F0F0FF, /* background */
103 0xD0D0D0FF, /* shade A */
104 0xA0A0A0FF, /* shade B */
105 0x909090FF, /* grid */
106 0xE05050FF, /* major grid */
107 0x000000FF, /* font */
108 0x000000FF, /* frame */
109 0xFF0000FF /* arrow */
116 # define DPRINT(x) (void)(printf x, printf("\n"))
122 /* initialize with xtr(im,0); */
124 xtr(image_desc_t *im,time_t mytime){
127 pixie = (double) im->xsize / (double)(im->end - im->start);
130 return (int)((double)im->xorigin
131 + pixie * ( mytime - im->start ) );
134 /* translate data values into y coordinates */
136 ytr(image_desc_t *im, double value){
141 pixie = (double) im->ysize / (im->maxval - im->minval);
143 pixie = (double) im->ysize / (log10(im->maxval) - log10(im->minval));
145 } else if(!im->logarithmic) {
146 yval = im->yorigin - pixie * (value - im->minval);
148 if (value < im->minval) {
151 yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
154 /* make sure we don't return anything too unreasonable. GD lib can
155 get terribly slow when drawing lines outside its scope. This is
156 especially problematic in connection with the rigid option */
158 /* keep yval as-is */
159 } else if (yval > im->yorigin) {
160 yval = im->yorigin+2;
161 } else if (yval < im->yorigin - im->ysize){
162 yval = im->yorigin - im->ysize - 2;
169 /* conversion function for symbolic entry names */
172 #define conv_if(VV,VVV) \
173 if (strcmp(#VV, string) == 0) return VVV ;
175 enum gf_en gf_conv(char *string){
177 conv_if(PRINT,GF_PRINT)
178 conv_if(GPRINT,GF_GPRINT)
179 conv_if(COMMENT,GF_COMMENT)
180 conv_if(HRULE,GF_HRULE)
181 conv_if(VRULE,GF_VRULE)
182 conv_if(LINE,GF_LINE)
183 conv_if(AREA,GF_AREA)
184 conv_if(STACK,GF_STACK)
185 conv_if(TICK,GF_TICK)
187 conv_if(CDEF,GF_CDEF)
188 conv_if(VDEF,GF_VDEF)
189 conv_if(PART,GF_PART)
190 conv_if(XPORT,GF_XPORT)
195 enum gfx_if_en if_conv(char *string){
205 enum tmt_en tmt_conv(char *string){
207 conv_if(SECOND,TMT_SECOND)
208 conv_if(MINUTE,TMT_MINUTE)
209 conv_if(HOUR,TMT_HOUR)
211 conv_if(WEEK,TMT_WEEK)
212 conv_if(MONTH,TMT_MONTH)
213 conv_if(YEAR,TMT_YEAR)
217 enum grc_en grc_conv(char *string){
219 conv_if(BACK,GRC_BACK)
220 conv_if(CANVAS,GRC_CANVAS)
221 conv_if(SHADEA,GRC_SHADEA)
222 conv_if(SHADEB,GRC_SHADEB)
223 conv_if(GRID,GRC_GRID)
224 conv_if(MGRID,GRC_MGRID)
225 conv_if(FONT,GRC_FONT)
226 conv_if(FRAME,GRC_FRAME)
227 conv_if(ARROW,GRC_ARROW)
232 enum text_prop_en text_prop_conv(char *string){
234 conv_if(DEFAULT,TEXT_PROP_DEFAULT)
235 conv_if(TITLE,TEXT_PROP_TITLE)
236 conv_if(AXIS,TEXT_PROP_AXIS)
237 conv_if(UNIT,TEXT_PROP_UNIT)
238 conv_if(LEGEND,TEXT_PROP_LEGEND)
246 im_free(image_desc_t *im)
250 if (im == NULL) return 0;
251 for(i=0;i<(unsigned)im->gdes_c;i++){
252 if (im->gdes[i].data_first){
253 /* careful here, because a single pointer can occur several times */
254 free (im->gdes[i].data);
255 if (im->gdes[i].ds_namv){
256 for (ii=0;ii<im->gdes[i].ds_cnt;ii++)
257 free(im->gdes[i].ds_namv[ii]);
258 free(im->gdes[i].ds_namv);
261 free (im->gdes[i].p_data);
262 free (im->gdes[i].rpnp);
265 gfx_destroy(im->canvas);
269 /* find SI magnitude symbol for the given number*/
272 image_desc_t *im, /* image description */
279 char *symbol[] = {"a", /* 10e-18 Atto */
280 "f", /* 10e-15 Femto */
281 "p", /* 10e-12 Pico */
282 "n", /* 10e-9 Nano */
283 "u", /* 10e-6 Micro */
284 "m", /* 10e-3 Milli */
289 "T", /* 10e12 Tera */
290 "P", /* 10e15 Peta */
296 if (*value == 0.0 || isnan(*value) ) {
300 sindex = floor(log(fabs(*value))/log((double)im->base));
301 *magfact = pow((double)im->base, (double)sindex);
302 (*value) /= (*magfact);
304 if ( sindex <= symbcenter && sindex >= -symbcenter) {
305 (*symb_ptr) = symbol[sindex+symbcenter];
313 /* find SI magnitude symbol for the numbers on the y-axis*/
316 image_desc_t *im /* image description */
320 char symbol[] = {'a', /* 10e-18 Atto */
321 'f', /* 10e-15 Femto */
322 'p', /* 10e-12 Pico */
323 'n', /* 10e-9 Nano */
324 'u', /* 10e-6 Micro */
325 'm', /* 10e-3 Milli */
330 'T', /* 10e12 Tera */
331 'P', /* 10e15 Peta */
337 if (im->unitsexponent != 9999) {
338 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
339 digits = floor(im->unitsexponent / 3);
341 digits = floor( log( max( fabs(im->minval),fabs(im->maxval)))/log((double)im->base));
343 im->magfact = pow((double)im->base , digits);
346 printf("digits %6.3f im->magfact %6.3f\n",digits,im->magfact);
349 if ( ((digits+symbcenter) < sizeof(symbol)) &&
350 ((digits+symbcenter) >= 0) )
351 im->symbol = symbol[(int)digits+symbcenter];
356 /* move min and max values around to become sensible */
359 expand_range(image_desc_t *im)
361 double sensiblevalues[] ={1000.0,900.0,800.0,750.0,700.0,
362 600.0,500.0,400.0,300.0,250.0,
363 200.0,125.0,100.0,90.0,80.0,
364 75.0,70.0,60.0,50.0,40.0,30.0,
365 25.0,20.0,10.0,9.0,8.0,
366 7.0,6.0,5.0,4.0,3.5,3.0,
367 2.5,2.0,1.8,1.5,1.2,1.0,
368 0.8,0.7,0.6,0.5,0.4,0.3,0.2,0.1,0.0,-1};
370 double scaled_min,scaled_max;
377 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
378 im->minval,im->maxval,im->magfact);
381 if (isnan(im->ygridstep)){
382 if(im->extra_flags & ALTAUTOSCALE) {
383 /* measure the amplitude of the function. Make sure that
384 graph boundaries are slightly higher then max/min vals
385 so we can see amplitude on the graph */
388 delt = im->maxval - im->minval;
390 fact = 2.0 * pow(10.0,
391 floor(log10(max(fabs(im->minval), fabs(im->maxval)))) - 2);
393 adj = (fact - delt) * 0.55;
395 printf("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n", im->minval, im->maxval, delt, fact, adj);
401 else if(im->extra_flags & ALTAUTOSCALE_MAX) {
402 /* measure the amplitude of the function. Make sure that
403 graph boundaries are slightly higher than max vals
404 so we can see amplitude on the graph */
405 adj = (im->maxval - im->minval) * 0.1;
409 scaled_min = im->minval / im->magfact;
410 scaled_max = im->maxval / im->magfact;
412 for (i=1; sensiblevalues[i] > 0; i++){
413 if (sensiblevalues[i-1]>=scaled_min &&
414 sensiblevalues[i]<=scaled_min)
415 im->minval = sensiblevalues[i]*(im->magfact);
417 if (-sensiblevalues[i-1]<=scaled_min &&
418 -sensiblevalues[i]>=scaled_min)
419 im->minval = -sensiblevalues[i-1]*(im->magfact);
421 if (sensiblevalues[i-1] >= scaled_max &&
422 sensiblevalues[i] <= scaled_max)
423 im->maxval = sensiblevalues[i-1]*(im->magfact);
425 if (-sensiblevalues[i-1]<=scaled_max &&
426 -sensiblevalues[i] >=scaled_max)
427 im->maxval = -sensiblevalues[i]*(im->magfact);
431 /* adjust min and max to the grid definition if there is one */
432 im->minval = (double)im->ylabfact * im->ygridstep *
433 floor(im->minval / ((double)im->ylabfact * im->ygridstep));
434 im->maxval = (double)im->ylabfact * im->ygridstep *
435 ceil(im->maxval /( (double)im->ylabfact * im->ygridstep));
439 fprintf(stderr,"SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
440 im->minval,im->maxval,im->magfact);
445 apply_gridfit(image_desc_t *im)
447 if (isnan(im->minval) || isnan(im->maxval))
450 if (im->logarithmic) {
451 double ya, yb, ypix, ypixfrac;
452 double log10_range = log10(im->maxval) - log10(im->minval);
453 ya = pow((double)10, floor(log10(im->minval)));
454 while (ya < im->minval)
457 return; /* don't have y=10^x gridline */
459 if (yb <= im->maxval) {
460 /* we have at least 2 y=10^x gridlines.
461 Make sure distance between them in pixels
462 are an integer by expanding im->maxval */
463 double y_pixel_delta = ytr(im, ya) - ytr(im, yb);
464 double factor = y_pixel_delta / floor(y_pixel_delta);
465 double new_log10_range = factor * log10_range;
466 double new_ymax_log10 = log10(im->minval) + new_log10_range;
467 im->maxval = pow(10, new_ymax_log10);
468 ytr(im, DNAN); /* reset precalc */
469 log10_range = log10(im->maxval) - log10(im->minval);
471 /* make sure first y=10^x gridline is located on
472 integer pixel position by moving scale slightly
473 downwards (sub-pixel movement) */
474 ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
475 ypixfrac = ypix - floor(ypix);
476 if (ypixfrac > 0 && ypixfrac < 1) {
477 double yfrac = ypixfrac / im->ysize;
478 im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
479 im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
480 ytr(im, DNAN); /* reset precalc */
483 /* Make sure we have an integer pixel distance between
484 each minor gridline */
485 double ypos1 = ytr(im, im->minval);
486 double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
487 double y_pixel_delta = ypos1 - ypos2;
488 double factor = y_pixel_delta / floor(y_pixel_delta);
489 double new_range = factor * (im->maxval - im->minval);
490 double gridstep = im->ygrid_scale.gridstep;
491 double minor_y, minor_y_px, minor_y_px_frac;
492 im->maxval = im->minval + new_range;
493 ytr(im, DNAN); /* reset precalc */
494 /* make sure first minor gridline is on integer pixel y coord */
495 minor_y = gridstep * floor(im->minval / gridstep);
496 while (minor_y < im->minval)
498 minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
499 minor_y_px_frac = minor_y_px - floor(minor_y_px);
500 if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
501 double yfrac = minor_y_px_frac / im->ysize;
502 double range = im->maxval - im->minval;
503 im->minval = im->minval - yfrac * range;
504 im->maxval = im->maxval - yfrac * range;
505 ytr(im, DNAN); /* reset precalc */
507 calc_horizontal_grid(im); /* recalc with changed im->maxval */
511 /* reduce data reimplementation by Alex */
515 enum cf_en cf, /* which consolidation function ?*/
516 unsigned long cur_step, /* step the data currently is in */
517 time_t *start, /* start, end and step as requested ... */
518 time_t *end, /* ... by the application will be ... */
519 unsigned long *step, /* ... adjusted to represent reality */
520 unsigned long *ds_cnt, /* number of data sources in file */
521 rrd_value_t **data) /* two dimensional array containing the data */
523 int i,reduce_factor = ceil((double)(*step) / (double)cur_step);
524 unsigned long col,dst_row,row_cnt,start_offset,end_offset,skiprows=0;
525 rrd_value_t *srcptr,*dstptr;
527 (*step) = cur_step*reduce_factor; /* set new step size for reduced data */
530 row_cnt = ((*end)-(*start))/cur_step;
536 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
537 row_cnt,reduce_factor,*start,*end,cur_step);
538 for (col=0;col<row_cnt;col++) {
539 printf("time %10lu: ",*start+(col+1)*cur_step);
540 for (i=0;i<*ds_cnt;i++)
541 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
546 /* We have to combine [reduce_factor] rows of the source
547 ** into one row for the destination. Doing this we also
548 ** need to take care to combine the correct rows. First
549 ** alter the start and end time so that they are multiples
550 ** of the new step time. We cannot reduce the amount of
551 ** time so we have to move the end towards the future and
552 ** the start towards the past.
554 end_offset = (*end) % (*step);
555 start_offset = (*start) % (*step);
557 /* If there is a start offset (which cannot be more than
558 ** one destination row), skip the appropriate number of
559 ** source rows and one destination row. The appropriate
560 ** number is what we do know (start_offset/cur_step) of
561 ** the new interval (*step/cur_step aka reduce_factor).
564 printf("start_offset: %lu end_offset: %lu\n",start_offset,end_offset);
565 printf("row_cnt before: %lu\n",row_cnt);
568 (*start) = (*start)-start_offset;
569 skiprows=reduce_factor-start_offset/cur_step;
570 srcptr+=skiprows* *ds_cnt;
571 for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
575 printf("row_cnt between: %lu\n",row_cnt);
578 /* At the end we have some rows that are not going to be
579 ** used, the amount is end_offset/cur_step
582 (*end) = (*end)-end_offset+(*step);
583 skiprows = end_offset/cur_step;
587 printf("row_cnt after: %lu\n",row_cnt);
590 /* Sanity check: row_cnt should be multiple of reduce_factor */
591 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
593 if (row_cnt%reduce_factor) {
594 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
595 row_cnt,reduce_factor);
596 printf("BUG in reduce_data()\n");
600 /* Now combine reduce_factor intervals at a time
601 ** into one interval for the destination.
604 for (dst_row=0;(long int)row_cnt>=reduce_factor;dst_row++) {
605 for (col=0;col<(*ds_cnt);col++) {
606 rrd_value_t newval=DNAN;
607 unsigned long validval=0;
609 for (i=0;i<reduce_factor;i++) {
610 if (isnan(srcptr[i*(*ds_cnt)+col])) {
614 if (isnan(newval)) newval = srcptr[i*(*ds_cnt)+col];
622 newval += srcptr[i*(*ds_cnt)+col];
625 newval = min (newval,srcptr[i*(*ds_cnt)+col]);
628 /* an interval contains a failure if any subintervals contained a failure */
630 newval = max (newval,srcptr[i*(*ds_cnt)+col]);
633 newval = srcptr[i*(*ds_cnt)+col];
638 if (validval == 0){newval = DNAN;} else{
656 srcptr+=(*ds_cnt)*reduce_factor;
657 row_cnt-=reduce_factor;
659 /* If we had to alter the endtime, we didn't have enough
660 ** source rows to fill the last row. Fill it with NaN.
662 if (end_offset) for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
664 row_cnt = ((*end)-(*start))/ *step;
666 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
667 row_cnt,*start,*end,*step);
668 for (col=0;col<row_cnt;col++) {
669 printf("time %10lu: ",*start+(col+1)*(*step));
670 for (i=0;i<*ds_cnt;i++)
671 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
678 /* get the data required for the graphs from the
682 data_fetch(image_desc_t *im )
687 /* pull the data from the log files ... */
688 for (i=0;i< (int)im->gdes_c;i++){
689 /* only GF_DEF elements fetch data */
690 if (im->gdes[i].gf != GF_DEF)
694 /* do we have it already ?*/
695 for (ii=0;ii<i;ii++) {
696 if (im->gdes[ii].gf != GF_DEF)
698 if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
699 && (im->gdes[i].cf == im->gdes[ii].cf)
700 && (im->gdes[i].start == im->gdes[ii].start)
701 && (im->gdes[i].end == im->gdes[ii].end)
702 && (im->gdes[i].step == im->gdes[ii].step)) {
703 /* OK, the data is already there.
704 ** Just copy the header portion
706 im->gdes[i].start = im->gdes[ii].start;
707 im->gdes[i].end = im->gdes[ii].end;
708 im->gdes[i].step = im->gdes[ii].step;
709 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
710 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
711 im->gdes[i].data = im->gdes[ii].data;
712 im->gdes[i].data_first = 0;
719 unsigned long ft_step = im->gdes[i].step ;
721 if((rrd_fetch_fn(im->gdes[i].rrd,
727 &im->gdes[i].ds_namv,
728 &im->gdes[i].data)) == -1){
731 im->gdes[i].data_first = 1;
733 if (ft_step < im->gdes[i].step) {
734 reduce_data(im->gdes[i].cf,
742 im->gdes[i].step = ft_step;
746 /* lets see if the required data source is really there */
747 for(ii=0;ii<(int)im->gdes[i].ds_cnt;ii++){
748 if(strcmp(im->gdes[i].ds_namv[ii],im->gdes[i].ds_nam) == 0){
751 if (im->gdes[i].ds== -1){
752 rrd_set_error("No DS called '%s' in '%s'",
753 im->gdes[i].ds_nam,im->gdes[i].rrd);
761 /* evaluate the expressions in the CDEF functions */
763 /*************************************************************
765 *************************************************************/
768 find_var_wrapper(void *arg1, char *key)
770 return find_var((image_desc_t *) arg1, key);
773 /* find gdes containing var*/
775 find_var(image_desc_t *im, char *key){
777 for(ii=0;ii<im->gdes_c-1;ii++){
778 if((im->gdes[ii].gf == GF_DEF
779 || im->gdes[ii].gf == GF_VDEF
780 || im->gdes[ii].gf == GF_CDEF)
781 && (strcmp(im->gdes[ii].vname,key) == 0)){
788 /* find the largest common denominator for all the numbers
789 in the 0 terminated num array */
794 for (i=0;num[i+1]!=0;i++){
796 rest=num[i] % num[i+1];
797 num[i]=num[i+1]; num[i+1]=rest;
801 /* return i==0?num[i]:num[i-1]; */
805 /* run the rpn calculator on all the VDEF and CDEF arguments */
807 data_calc( image_desc_t *im){
811 long *steparray, rpi;
816 rpnstack_init(&rpnstack);
818 for (gdi=0;gdi<im->gdes_c;gdi++){
819 /* Look for GF_VDEF and GF_CDEF in the same loop,
820 * so CDEFs can use VDEFs and vice versa
822 switch (im->gdes[gdi].gf) {
826 /* A VDEF has no DS. This also signals other parts
827 * of rrdtool that this is a VDEF value, not a CDEF.
829 im->gdes[gdi].ds_cnt = 0;
830 if (vdef_calc(im,gdi)) {
831 rrd_set_error("Error processing VDEF '%s'"
834 rpnstack_free(&rpnstack);
839 im->gdes[gdi].ds_cnt = 1;
840 im->gdes[gdi].ds = 0;
841 im->gdes[gdi].data_first = 1;
842 im->gdes[gdi].start = 0;
843 im->gdes[gdi].end = 0;
848 /* Find the variables in the expression.
849 * - VDEF variables are substituted by their values
850 * and the opcode is changed into OP_NUMBER.
851 * - CDEF variables are analized for their step size,
852 * the lowest common denominator of all the step
853 * sizes of the data sources involved is calculated
854 * and the resulting number is the step size for the
855 * resulting data source.
857 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
858 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
859 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER){
860 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
861 if (im->gdes[ptr].ds_cnt == 0) {
863 printf("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
865 im->gdes[ptr].vname);
866 printf("DEBUG: value from vdef is %f\n",im->gdes[ptr].vf.val);
868 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
869 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
872 rrd_realloc(steparray,
873 (++stepcnt+1)*sizeof(*steparray)))==NULL){
874 rrd_set_error("realloc steparray");
875 rpnstack_free(&rpnstack);
879 steparray[stepcnt-1] = im->gdes[ptr].step;
881 /* adjust start and end of cdef (gdi) so
882 * that it runs from the latest start point
883 * to the earliest endpoint of any of the
884 * rras involved (ptr)
886 if(im->gdes[gdi].start < im->gdes[ptr].start)
887 im->gdes[gdi].start = im->gdes[ptr].start;
889 if(im->gdes[gdi].end == 0 ||
890 im->gdes[gdi].end > im->gdes[ptr].end)
891 im->gdes[gdi].end = im->gdes[ptr].end;
893 /* store pointer to the first element of
894 * the rra providing data for variable,
895 * further save step size and data source
898 im->gdes[gdi].rpnp[rpi].data = im->gdes[ptr].data + im->gdes[ptr].ds;
899 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
900 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
902 /* backoff the *.data ptr; this is done so
903 * rpncalc() function doesn't have to treat
904 * the first case differently
906 } /* if ds_cnt != 0 */
907 } /* if OP_VARIABLE */
908 } /* loop through all rpi */
910 /* move the data pointers to the correct period */
911 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
912 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
913 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER){
914 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
915 if(im->gdes[gdi].start > im->gdes[ptr].start) {
916 im->gdes[gdi].rpnp[rpi].data += im->gdes[gdi].rpnp[rpi].ds_cnt;
922 if(steparray == NULL){
923 rrd_set_error("rpn expressions without DEF"
924 " or CDEF variables are not supported");
925 rpnstack_free(&rpnstack);
928 steparray[stepcnt]=0;
929 /* Now find the resulting step. All steps in all
930 * used RRAs have to be visited
932 im->gdes[gdi].step = lcd(steparray);
934 if((im->gdes[gdi].data = malloc((
935 (im->gdes[gdi].end-im->gdes[gdi].start)
936 / im->gdes[gdi].step)
937 * sizeof(double)))==NULL){
938 rrd_set_error("malloc im->gdes[gdi].data");
939 rpnstack_free(&rpnstack);
943 /* Step through the new cdef results array and
944 * calculate the values
946 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
947 now<=im->gdes[gdi].end;
948 now += im->gdes[gdi].step)
950 rpnp_t *rpnp = im -> gdes[gdi].rpnp;
952 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
953 * in this case we are advancing by timesteps;
954 * we use the fact that time_t is a synonym for long
956 if (rpn_calc(rpnp,&rpnstack,(long) now,
957 im->gdes[gdi].data,++dataidx) == -1) {
958 /* rpn_calc sets the error string */
959 rpnstack_free(&rpnstack);
962 } /* enumerate over time steps within a CDEF */
967 } /* enumerate over CDEFs */
968 rpnstack_free(&rpnstack);
972 /* massage data so, that we get one value for each x coordinate in the graph */
974 data_proc( image_desc_t *im ){
976 double pixstep = (double)(im->end-im->start)
977 /(double)im->xsize; /* how much time
978 passes in one pixel */
980 double minval=DNAN,maxval=DNAN;
982 unsigned long gr_time;
984 /* memory for the processed data */
985 for(i=0;i<im->gdes_c;i++) {
986 if((im->gdes[i].gf==GF_LINE) ||
987 (im->gdes[i].gf==GF_AREA) ||
988 (im->gdes[i].gf==GF_TICK) ||
989 (im->gdes[i].gf==GF_STACK)) {
990 if((im->gdes[i].p_data = malloc((im->xsize +1)
991 * sizeof(rrd_value_t)))==NULL){
992 rrd_set_error("malloc data_proc");
998 for (i=0;i<im->xsize;i++) { /* for each pixel */
1000 gr_time = im->start+pixstep*i; /* time of the current step */
1003 for (ii=0;ii<im->gdes_c;ii++) {
1005 switch (im->gdes[ii].gf) {
1009 if (!im->gdes[ii].stack)
1012 value = im->gdes[ii].yrule;
1013 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1014 /* The time of the data doesn't necessarily match
1015 ** the time of the graph. Beware.
1017 vidx = im->gdes[ii].vidx;
1018 if ( ((long int)gr_time >= (long int)im->gdes[vidx].start) &&
1019 ((long int)gr_time <= (long int)im->gdes[vidx].end) ) {
1020 value = im->gdes[vidx].data[
1021 (unsigned long) floor(
1022 (double)(gr_time - im->gdes[vidx].start)
1023 / im->gdes[vidx].step)
1024 * im->gdes[vidx].ds_cnt
1032 if (! isnan(value)) {
1034 im->gdes[ii].p_data[i] = paintval;
1035 /* GF_TICK: the data values are not
1036 ** relevant for min and max
1038 if (finite(paintval) && im->gdes[ii].gf != GF_TICK ) {
1039 if (isnan(minval) || paintval < minval)
1041 if (isnan(maxval) || paintval > maxval)
1045 im->gdes[ii].p_data[i] = DNAN;
1054 /* if min or max have not been asigned a value this is because
1055 there was no data in the graph ... this is not good ...
1056 lets set these to dummy values then ... */
1058 if (isnan(minval)) minval = 0.0;
1059 if (isnan(maxval)) maxval = 1.0;
1061 /* adjust min and max values */
1062 if (isnan(im->minval)
1063 /* don't adjust low-end with log scale */
1064 || ((!im->logarithmic && !im->rigid) && im->minval > minval)
1066 im->minval = minval;
1067 if (isnan(im->maxval)
1068 || (!im->rigid && im->maxval < maxval)
1070 if (im->logarithmic)
1071 im->maxval = maxval * 1.1;
1073 im->maxval = maxval;
1075 /* make sure min is smaller than max */
1076 if (im->minval > im->maxval) {
1077 im->minval = 0.99 * im->maxval;
1080 /* make sure min and max are not equal */
1081 if (im->minval == im->maxval) {
1083 if (! im->logarithmic) {
1086 /* make sure min and max are not both zero */
1087 if (im->maxval == 0.0) {
1096 /* identify the point where the first gridline, label ... gets placed */
1100 time_t start, /* what is the initial time */
1101 enum tmt_en baseint, /* what is the basic interval */
1102 long basestep /* how many if these do we jump a time */
1106 localtime_r(&start, &tm);
1109 tm.tm_sec -= tm.tm_sec % basestep; break;
1112 tm.tm_min -= tm.tm_min % basestep;
1117 tm.tm_hour -= tm.tm_hour % basestep; break;
1119 /* we do NOT look at the basestep for this ... */
1122 tm.tm_hour = 0; break;
1124 /* we do NOT look at the basestep for this ... */
1128 tm.tm_mday -= tm.tm_wday -1; /* -1 because we want the monday */
1129 if (tm.tm_wday==0) tm.tm_mday -= 7; /* we want the *previous* monday */
1136 tm.tm_mon -= tm.tm_mon % basestep; break;
1144 tm.tm_year -= (tm.tm_year+1900) % basestep;
1149 /* identify the point where the next gridline, label ... gets placed */
1152 time_t current, /* what is the initial time */
1153 enum tmt_en baseint, /* what is the basic interval */
1154 long basestep /* how many if these do we jump a time */
1159 localtime_r(¤t, &tm);
1163 tm.tm_sec += basestep; break;
1165 tm.tm_min += basestep; break;
1167 tm.tm_hour += basestep; break;
1169 tm.tm_mday += basestep; break;
1171 tm.tm_mday += 7*basestep; break;
1173 tm.tm_mon += basestep; break;
1175 tm.tm_year += basestep;
1177 madetime = mktime(&tm);
1178 } while (madetime == -1); /* this is necessary to skip impssible times
1179 like the daylight saving time skips */
1185 /* calculate values required for PRINT and GPRINT functions */
1188 print_calc(image_desc_t *im, char ***prdata)
1190 long i,ii,validsteps;
1193 int graphelement = 0;
1196 double magfact = -1;
1200 if (im->imginfo) prlines++;
1201 for(i=0;i<im->gdes_c;i++){
1202 switch(im->gdes[i].gf){
1205 if(((*prdata) = rrd_realloc((*prdata),prlines*sizeof(char *)))==NULL){
1206 rrd_set_error("realloc prdata");
1210 /* PRINT and GPRINT can now print VDEF generated values.
1211 * There's no need to do any calculations on them as these
1212 * calculations were already made.
1214 vidx = im->gdes[i].vidx;
1215 if (im->gdes[vidx].gf==GF_VDEF) { /* simply use vals */
1216 printval = im->gdes[vidx].vf.val;
1217 printtime = im->gdes[vidx].vf.when;
1218 } else { /* need to calculate max,min,avg etcetera */
1219 max_ii =((im->gdes[vidx].end
1220 - im->gdes[vidx].start)
1221 / im->gdes[vidx].step
1222 * im->gdes[vidx].ds_cnt);
1225 for( ii=im->gdes[vidx].ds;
1227 ii+=im->gdes[vidx].ds_cnt){
1228 if (! finite(im->gdes[vidx].data[ii]))
1230 if (isnan(printval)){
1231 printval = im->gdes[vidx].data[ii];
1236 switch (im->gdes[i].cf){
1239 case CF_DEVSEASONAL:
1243 printval += im->gdes[vidx].data[ii];
1246 printval = min( printval, im->gdes[vidx].data[ii]);
1250 printval = max( printval, im->gdes[vidx].data[ii]);
1253 printval = im->gdes[vidx].data[ii];
1256 if (im->gdes[i].cf==CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1257 if (validsteps > 1) {
1258 printval = (printval / validsteps);
1261 } /* prepare printval */
1263 if (!strcmp(im->gdes[i].format,"%c")) { /* VDEF time print */
1264 char ctime_buf[128]; /* PS: for ctime_r, must be >= 26 chars */
1265 if (im->gdes[i].gf == GF_PRINT){
1266 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1267 sprintf((*prdata)[prlines-2],"%s (%lu)",
1268 ctime_r(&printtime,ctime_buf),printtime);
1269 (*prdata)[prlines-1] = NULL;
1271 sprintf(im->gdes[i].legend,"%s (%lu)",
1272 ctime_r(&printtime,ctime_buf),printtime);
1276 if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) {
1277 /* Magfact is set to -1 upon entry to print_calc. If it
1278 * is still less than 0, then we need to run auto_scale.
1279 * Otherwise, put the value into the correct units. If
1280 * the value is 0, then do not set the symbol or magnification
1281 * so next the calculation will be performed again. */
1282 if (magfact < 0.0) {
1283 auto_scale(im,&printval,&si_symb,&magfact);
1284 if (printval == 0.0)
1287 printval /= magfact;
1289 *(++percent_s) = 's';
1290 } else if (strstr(im->gdes[i].format,"%s") != NULL) {
1291 auto_scale(im,&printval,&si_symb,&magfact);
1294 if (im->gdes[i].gf == GF_PRINT){
1295 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1296 (*prdata)[prlines-1] = NULL;
1297 if (bad_format(im->gdes[i].format)) {
1298 rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1301 #ifdef HAVE_SNPRINTF
1302 snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
1304 sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
1309 if (bad_format(im->gdes[i].format)) {
1310 rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1313 #ifdef HAVE_SNPRINTF
1314 snprintf(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
1316 sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
1339 return graphelement;
1343 /* place legends with color spots */
1345 leg_place(image_desc_t *im)
1348 int interleg = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1349 int box =im->text_prop[TEXT_PROP_LEGEND].size*1.5;
1350 int border = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1351 int fill=0, fill_last;
1353 int leg_x = border, leg_y = im->yimg;
1357 char prt_fctn; /*special printfunctions */
1360 if( !(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH) ) {
1361 if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1362 rrd_set_error("malloc for legspace");
1366 for(i=0;i<im->gdes_c;i++){
1369 /* hid legends for rules which are not displayed */
1371 if (im->gdes[i].gf == GF_HRULE &&
1372 (im->gdes[i].yrule < im->minval || im->gdes[i].yrule > im->maxval))
1373 im->gdes[i].legend[0] = '\0';
1375 if (im->gdes[i].gf == GF_VRULE &&
1376 (im->gdes[i].xrule < im->start || im->gdes[i].xrule > im->end))
1377 im->gdes[i].legend[0] = '\0';
1379 leg_cc = strlen(im->gdes[i].legend);
1381 /* is there a controle code ant the end of the legend string ? */
1382 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\') {
1383 prt_fctn = im->gdes[i].legend[leg_cc-1];
1385 im->gdes[i].legend[leg_cc] = '\0';
1389 /* remove exess space */
1390 while (prt_fctn=='g' &&
1392 im->gdes[i].legend[leg_cc-1]==' '){
1394 im->gdes[i].legend[leg_cc]='\0';
1397 legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1400 /* no interleg space if string ends in \g */
1401 fill += legspace[i];
1403 if (im->gdes[i].gf != GF_GPRINT &&
1404 im->gdes[i].gf != GF_COMMENT) {
1407 fill += gfx_get_text_width(im->canvas, fill+border,
1408 im->text_prop[TEXT_PROP_LEGEND].font,
1409 im->text_prop[TEXT_PROP_LEGEND].size,
1411 im->gdes[i].legend, 0);
1416 /* who said there was a special tag ... ?*/
1417 if (prt_fctn=='g') {
1420 if (prt_fctn == '\0') {
1421 if (i == im->gdes_c -1 ) prt_fctn ='l';
1423 /* is it time to place the legends ? */
1424 if (fill > im->ximg - 2*border){
1439 if (prt_fctn != '\0'){
1441 if (leg_c >= 2 && prt_fctn == 'j') {
1442 glue = (im->ximg - fill - 2* border) / (leg_c-1);
1446 if (prt_fctn =='c') leg_x = (im->ximg - fill) / 2.0;
1447 if (prt_fctn =='r') leg_x = im->ximg - fill - border;
1449 for(ii=mark;ii<=i;ii++){
1450 if(im->gdes[ii].legend[0]=='\0')
1452 im->gdes[ii].leg_x = leg_x;
1453 im->gdes[ii].leg_y = leg_y;
1455 gfx_get_text_width(im->canvas, leg_x,
1456 im->text_prop[TEXT_PROP_LEGEND].font,
1457 im->text_prop[TEXT_PROP_LEGEND].size,
1459 im->gdes[ii].legend, 0)
1462 if (im->gdes[ii].gf != GF_GPRINT &&
1463 im->gdes[ii].gf != GF_COMMENT)
1466 leg_y = leg_y + im->text_prop[TEXT_PROP_LEGEND].size*1.2;
1467 if (prt_fctn == 's') leg_y -= im->text_prop[TEXT_PROP_LEGEND].size*1.2;
1479 /* create a grid on the graph. it determines what to do
1480 from the values of xsize, start and end */
1482 /* the xaxis labels are determined from the number of seconds per pixel
1483 in the requested graph */
1488 calc_horizontal_grid(image_desc_t *im)
1494 int decimals, fractionals;
1496 im->ygrid_scale.labfact=2;
1498 range = im->maxval - im->minval;
1499 scaledrange = range / im->magfact;
1501 /* does the scale of this graph make it impossible to put lines
1502 on it? If so, give up. */
1503 if (isnan(scaledrange)) {
1507 /* find grid spaceing */
1509 if(isnan(im->ygridstep)){
1510 if(im->extra_flags & ALTYGRID) {
1511 /* find the value with max number of digits. Get number of digits */
1512 decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))));
1513 if(decimals <= 0) /* everything is small. make place for zero */
1516 fractionals = floor(log10(range));
1517 if(fractionals < 0) /* small amplitude. */
1518 sprintf(im->ygrid_scale.labfmt, "%%%d.%df", decimals - fractionals + 1, -fractionals + 1);
1520 sprintf(im->ygrid_scale.labfmt, "%%%d.1f", decimals + 1);
1521 im->ygrid_scale.gridstep = pow((double)10, (double)fractionals);
1522 if(im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1523 im->ygrid_scale.gridstep = 0.1;
1524 /* should have at least 5 lines but no more then 15 */
1525 if(range/im->ygrid_scale.gridstep < 5)
1526 im->ygrid_scale.gridstep /= 10;
1527 if(range/im->ygrid_scale.gridstep > 15)
1528 im->ygrid_scale.gridstep *= 10;
1529 if(range/im->ygrid_scale.gridstep > 5) {
1530 im->ygrid_scale.labfact = 1;
1531 if(range/im->ygrid_scale.gridstep > 8)
1532 im->ygrid_scale.labfact = 2;
1535 im->ygrid_scale.gridstep /= 5;
1536 im->ygrid_scale.labfact = 5;
1540 for(i=0;ylab[i].grid > 0;i++){
1541 pixel = im->ysize / (scaledrange / ylab[i].grid);
1542 if (gridind == -1 && pixel > 5) {
1549 if (pixel * ylab[gridind].lfac[i] >= 2 * im->text_prop[TEXT_PROP_AXIS].size) {
1550 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1555 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1558 im->ygrid_scale.gridstep = im->ygridstep;
1559 im->ygrid_scale.labfact = im->ylabfact;
1564 int draw_horizontal_grid(image_desc_t *im)
1568 char graph_label[100];
1569 double X0=im->xorigin;
1570 double X1=im->xorigin+im->xsize;
1572 int sgrid = (int)( im->minval / im->ygrid_scale.gridstep - 1);
1573 int egrid = (int)( im->maxval / im->ygrid_scale.gridstep + 1);
1574 scaledstep = im->ygrid_scale.gridstep/im->magfact;
1575 for (i = sgrid; i <= egrid; i++){
1576 double Y0=ytr(im,im->ygrid_scale.gridstep*i);
1577 if ( Y0 >= im->yorigin-im->ysize
1578 && Y0 <= im->yorigin){
1579 if(i % im->ygrid_scale.labfact == 0){
1580 if (i==0 || im->symbol == ' ') {
1582 if(im->extra_flags & ALTYGRID) {
1583 sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*i);
1586 sprintf(graph_label,"%4.1f",scaledstep*i);
1589 sprintf(graph_label,"%4.0f",scaledstep*i);
1593 sprintf(graph_label,"%4.1f %c",scaledstep*i, im->symbol);
1595 sprintf(graph_label,"%4.0f %c",scaledstep*i, im->symbol);
1599 gfx_new_text ( im->canvas,
1600 X0-im->text_prop[TEXT_PROP_AXIS].size/1.5, Y0,
1601 im->graph_col[GRC_FONT],
1602 im->text_prop[TEXT_PROP_AXIS].font,
1603 im->text_prop[TEXT_PROP_AXIS].size,
1604 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1606 gfx_new_dashed_line ( im->canvas,
1609 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1610 im->grid_dash_on, im->grid_dash_off);
1612 } else if (!(im->extra_flags & NOMINOR)) {
1613 gfx_new_dashed_line ( im->canvas,
1616 GRIDWIDTH, im->graph_col[GRC_GRID],
1617 im->grid_dash_on, im->grid_dash_off);
1625 /* logaritmic horizontal grid */
1627 horizontal_log_grid(image_desc_t *im)
1631 int minoridx=0, majoridx=0;
1632 char graph_label[100];
1634 double value, pixperstep, minstep;
1636 /* find grid spaceing */
1637 pixpex= (double)im->ysize / (log10(im->maxval) - log10(im->minval));
1639 if (isnan(pixpex)) {
1643 for(i=0;yloglab[i][0] > 0;i++){
1644 minstep = log10(yloglab[i][0]);
1645 for(ii=1;yloglab[i][ii+1] > 0;ii++){
1646 if(yloglab[i][ii+2]==0){
1647 minstep = log10(yloglab[i][ii+1])-log10(yloglab[i][ii]);
1651 pixperstep = pixpex * minstep;
1652 if(pixperstep > 5){minoridx = i;}
1653 if(pixperstep > 2 * im->text_prop[TEXT_PROP_LEGEND].size){majoridx = i;}
1657 X1=im->xorigin+im->xsize;
1658 /* paint minor grid */
1659 for (value = pow((double)10, log10(im->minval)
1660 - fmod(log10(im->minval),log10(yloglab[minoridx][0])));
1661 value <= im->maxval;
1662 value *= yloglab[minoridx][0]){
1663 if (value < im->minval) continue;
1665 while(yloglab[minoridx][++i] > 0){
1666 Y0 = ytr(im,value * yloglab[minoridx][i]);
1667 if (Y0 <= im->yorigin - im->ysize) break;
1668 gfx_new_dashed_line ( im->canvas,
1671 GRIDWIDTH, im->graph_col[GRC_GRID],
1672 im->grid_dash_on, im->grid_dash_off);
1676 /* paint major grid and labels*/
1677 for (value = pow((double)10, log10(im->minval)
1678 - fmod(log10(im->minval),log10(yloglab[majoridx][0])));
1679 value <= im->maxval;
1680 value *= yloglab[majoridx][0]){
1681 if (value < im->minval) continue;
1683 while(yloglab[majoridx][++i] > 0){
1684 Y0 = ytr(im,value * yloglab[majoridx][i]);
1685 if (Y0 <= im->yorigin - im->ysize) break;
1686 gfx_new_dashed_line ( im->canvas,
1689 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1690 im->grid_dash_on, im->grid_dash_off);
1692 sprintf(graph_label,"%3.0e",value * yloglab[majoridx][i]);
1693 gfx_new_text ( im->canvas,
1694 X0-im->text_prop[TEXT_PROP_AXIS].size/1.5, Y0,
1695 im->graph_col[GRC_FONT],
1696 im->text_prop[TEXT_PROP_AXIS].font,
1697 im->text_prop[TEXT_PROP_AXIS].size,
1698 im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1710 int xlab_sel; /* which sort of label and grid ? */
1711 time_t ti, tilab, timajor;
1713 char graph_label[100];
1714 double X0,Y0,Y1; /* points for filled graph and more*/
1717 /* the type of time grid is determined by finding
1718 the number of seconds per pixel in the graph */
1721 if(im->xlab_user.minsec == -1){
1722 factor=(im->end - im->start)/im->xsize;
1724 while ( xlab[xlab_sel+1].minsec != -1
1725 && xlab[xlab_sel+1].minsec <= factor){ xlab_sel++; }
1726 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
1727 im->xlab_user.gridst = xlab[xlab_sel].gridst;
1728 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
1729 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
1730 im->xlab_user.labtm = xlab[xlab_sel].labtm;
1731 im->xlab_user.labst = xlab[xlab_sel].labst;
1732 im->xlab_user.precis = xlab[xlab_sel].precis;
1733 im->xlab_user.stst = xlab[xlab_sel].stst;
1736 /* y coords are the same for every line ... */
1738 Y1 = im->yorigin-im->ysize;
1741 /* paint the minor grid */
1742 if (!(im->extra_flags & NOMINOR))
1744 for(ti = find_first_time(im->start,
1745 im->xlab_user.gridtm,
1746 im->xlab_user.gridst),
1747 timajor = find_first_time(im->start,
1748 im->xlab_user.mgridtm,
1749 im->xlab_user.mgridst);
1751 ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
1753 /* are we inside the graph ? */
1754 if (ti < im->start || ti > im->end) continue;
1755 while (timajor < ti) {
1756 timajor = find_next_time(timajor,
1757 im->xlab_user.mgridtm, im->xlab_user.mgridst);
1759 if (ti == timajor) continue; /* skip as falls on major grid line */
1761 gfx_new_dashed_line(im->canvas,X0,Y0+1, X0,Y1-1,GRIDWIDTH,
1762 im->graph_col[GRC_GRID],
1763 im->grid_dash_on, im->grid_dash_off);
1768 /* paint the major grid */
1769 for(ti = find_first_time(im->start,
1770 im->xlab_user.mgridtm,
1771 im->xlab_user.mgridst);
1773 ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
1775 /* are we inside the graph ? */
1776 if (ti < im->start || ti > im->end) continue;
1778 gfx_new_dashed_line(im->canvas,X0,Y0+3, X0,Y1-2,MGRIDWIDTH,
1779 im->graph_col[GRC_MGRID],
1780 im->grid_dash_on, im->grid_dash_off);
1783 /* paint the labels below the graph */
1784 for(ti = find_first_time(im->start,
1785 im->xlab_user.labtm,
1786 im->xlab_user.labst);
1788 ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
1790 tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
1791 /* are we inside the graph ? */
1792 if (ti < im->start || ti > im->end) continue;
1795 localtime_r(&tilab, &tm);
1796 strftime(graph_label,99,im->xlab_user.stst, &tm);
1798 # error "your libc has no strftime I guess we'll abort the exercise here."
1800 gfx_new_text ( im->canvas,
1801 xtr(im,tilab), Y0+im->text_prop[TEXT_PROP_AXIS].size/1.5,
1802 im->graph_col[GRC_FONT],
1803 im->text_prop[TEXT_PROP_AXIS].font,
1804 im->text_prop[TEXT_PROP_AXIS].size,
1805 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP,
1818 /* draw x and y axis */
1819 gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
1820 im->xorigin+im->xsize,im->yorigin-im->ysize,
1821 GRIDWIDTH, im->graph_col[GRC_GRID]);
1823 gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
1824 im->xorigin+im->xsize,im->yorigin-im->ysize,
1825 GRIDWIDTH, im->graph_col[GRC_GRID]);
1827 gfx_new_line ( im->canvas, im->xorigin-4,im->yorigin,
1828 im->xorigin+im->xsize+4,im->yorigin,
1829 MGRIDWIDTH, im->graph_col[GRC_GRID]);
1831 gfx_new_line ( im->canvas, im->xorigin,im->yorigin+4,
1832 im->xorigin,im->yorigin-im->ysize-4,
1833 MGRIDWIDTH, im->graph_col[GRC_GRID]);
1836 /* arrow for X axis direction */
1837 gfx_new_area ( im->canvas,
1838 im->xorigin+im->xsize+3, im->yorigin-3,
1839 im->xorigin+im->xsize+3, im->yorigin+4,
1840 im->xorigin+im->xsize+8, im->yorigin+0.5, /* LINEOFFSET */
1841 im->graph_col[GRC_ARROW]);
1848 grid_paint(image_desc_t *im)
1852 double X0,Y0; /* points for filled graph and more*/
1855 /* draw 3d border */
1856 node = gfx_new_area (im->canvas, 0,im->yimg,
1858 2,2,im->graph_col[GRC_SHADEA]);
1859 gfx_add_point( node , im->ximg - 2, 2 );
1860 gfx_add_point( node , im->ximg, 0 );
1861 gfx_add_point( node , 0,0 );
1862 /* gfx_add_point( node , 0,im->yimg ); */
1864 node = gfx_new_area (im->canvas, 2,im->yimg-2,
1865 im->ximg-2,im->yimg-2,
1867 im->graph_col[GRC_SHADEB]);
1868 gfx_add_point( node , im->ximg,0);
1869 gfx_add_point( node , im->ximg,im->yimg);
1870 gfx_add_point( node , 0,im->yimg);
1871 /* gfx_add_point( node , 0,im->yimg ); */
1874 if (im->draw_x_grid == 1 )
1877 if (im->draw_y_grid == 1){
1878 if(im->logarithmic){
1879 res = horizontal_log_grid(im);
1881 res = draw_horizontal_grid(im);
1884 /* dont draw horizontal grid if there is no min and max val */
1886 char *nodata = "No Data found";
1887 gfx_new_text(im->canvas,im->ximg/2, (2*im->yorigin-im->ysize) / 2,
1888 im->graph_col[GRC_FONT],
1889 im->text_prop[TEXT_PROP_AXIS].font,
1890 im->text_prop[TEXT_PROP_AXIS].size,
1891 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
1896 /* yaxis description */
1897 /* if (im->canvas->imgformat != IF_PNG) {*/
1899 gfx_new_text( im->canvas,
1900 7, (im->yorigin - im->ysize/2),
1901 im->graph_col[GRC_FONT],
1902 im->text_prop[TEXT_PROP_AXIS].font,
1903 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth,
1904 RRDGRAPH_YLEGEND_ANGLE,
1905 GFX_H_LEFT, GFX_V_CENTER,
1908 /* horrible hack until we can actually print vertically */
1912 for (n=0;n< (int)strlen(im->ylegend);n++) {
1913 s[0]=im->ylegend[n];
1915 gfx_new_text(im->canvas,7,im->text_prop[TEXT_PROP_AXIS].size*(n+1),
1916 im->graph_col[GRC_FONT],
1917 im->text_prop[TEXT_PROP_AXIS].font,
1918 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 270.0,
1919 GFX_H_CENTER, GFX_V_CENTER,
1926 gfx_new_text( im->canvas,
1927 im->ximg/2, im->text_prop[TEXT_PROP_TITLE].size,
1928 im->graph_col[GRC_FONT],
1929 im->text_prop[TEXT_PROP_TITLE].font,
1930 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
1931 GFX_H_CENTER, GFX_V_CENTER,
1935 if( !(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH) ) {
1936 for(i=0;i<im->gdes_c;i++){
1937 if(im->gdes[i].legend[0] =='\0')
1940 /* im->gdes[i].leg_y is the bottom of the legend */
1941 X0 = im->gdes[i].leg_x;
1942 Y0 = im->gdes[i].leg_y;
1944 if ( im->gdes[i].gf != GF_GPRINT
1945 && im->gdes[i].gf != GF_COMMENT) {
1948 boxH = gfx_get_text_width(im->canvas, 0,
1949 im->text_prop[TEXT_PROP_AXIS].font,
1950 im->text_prop[TEXT_PROP_AXIS].size,
1951 im->tabwidth,"M", 0) * 1.25;
1954 node = gfx_new_area(im->canvas,
1959 gfx_add_point ( node, X0+boxH, Y0-boxV );
1960 node = gfx_new_line(im->canvas,
1963 gfx_add_point(node,X0+boxH,Y0);
1964 gfx_add_point(node,X0+boxH,Y0-boxV);
1965 gfx_close_path(node);
1966 X0 += boxH / 1.25 * 2;
1968 gfx_new_text ( im->canvas, X0, Y0,
1969 im->graph_col[GRC_FONT],
1970 im->text_prop[TEXT_PROP_AXIS].font,
1971 im->text_prop[TEXT_PROP_AXIS].size,
1972 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
1973 im->gdes[i].legend );
1979 /*****************************************************
1980 * lazy check make sure we rely need to create this graph
1981 *****************************************************/
1983 int lazy_check(image_desc_t *im){
1986 struct stat imgstat;
1988 if (im->lazy == 0) return 0; /* no lazy option */
1989 if (stat(im->graphfile,&imgstat) != 0)
1990 return 0; /* can't stat */
1991 /* one pixel in the existing graph is more then what we would
1993 if (time(NULL) - imgstat.st_mtime >
1994 (im->end - im->start) / im->xsize)
1996 if ((fd = fopen(im->graphfile,"rb")) == NULL)
1997 return 0; /* the file does not exist */
1998 switch (im->canvas->imgformat) {
2000 size = PngSize(fd,&(im->ximg),&(im->yimg));
2010 pie_part(image_desc_t *im, gfx_color_t color,
2011 double PieCenterX, double PieCenterY, double Radius,
2012 double startangle, double endangle)
2016 double step=M_PI/50; /* Number of iterations for the circle;
2017 ** 10 is definitely too low, more than
2018 ** 50 seems to be overkill
2021 /* Strange but true: we have to work clockwise or else
2022 ** anti aliasing nor transparency don't work.
2024 ** This test is here to make sure we do it right, also
2025 ** this makes the for...next loop more easy to implement.
2026 ** The return will occur if the user enters a negative number
2027 ** (which shouldn't be done according to the specs) or if the
2028 ** programmers do something wrong (which, as we all know, never
2029 ** happens anyway :)
2031 if (endangle<startangle) return;
2033 /* Hidden feature: Radius decreases each full circle */
2035 while (angle>=2*M_PI) {
2040 node=gfx_new_area(im->canvas,
2041 PieCenterX+sin(startangle)*Radius,
2042 PieCenterY-cos(startangle)*Radius,
2045 PieCenterX+sin(endangle)*Radius,
2046 PieCenterY-cos(endangle)*Radius,
2048 for (angle=endangle;angle-startangle>=step;angle-=step) {
2050 PieCenterX+sin(angle)*Radius,
2051 PieCenterY-cos(angle)*Radius );
2056 graph_size_location(image_desc_t *im, int elements, int piechart )
2058 /* The actual size of the image to draw is determined from
2059 ** several sources. The size given on the command line is
2060 ** the graph area but we need more as we have to draw labels
2061 ** and other things outside the graph area
2064 /* +-+-------------------------------------------+
2065 ** |l|.................title.....................|
2066 ** |e+--+-------------------------------+--------+
2069 ** |l| l| main graph area | chart |
2072 ** |r+--+-------------------------------+--------+
2073 ** |e| | x-axis labels | |
2074 ** |v+--+-------------------------------+--------+
2075 ** | |..............legends......................|
2076 ** +-+-------------------------------------------+
2078 int Xvertical=0, Yvertical=0,
2079 Xtitle =0, Ytitle =0,
2080 Xylabel =0, Yylabel =0,
2083 Xxlabel =0, Yxlabel =0,
2085 Xlegend =0, Ylegend =0,
2087 Xspacing =10, Yspacing =10;
2089 if (im->extra_flags & ONLY_GRAPH) {
2090 if ( im->ysize > 32 ) {
2091 rrd_set_error("height > 32 is not possible with --only-graph option");
2097 if (im->ylegend[0] != '\0') {
2098 Xvertical = im->text_prop[TEXT_PROP_LEGEND].size *2;
2099 Yvertical = im->text_prop[TEXT_PROP_LEGEND].size * (strlen(im->ylegend)+1);
2103 if (im->title[0] != '\0') {
2104 /* The title is placed "inbetween" two text lines so it
2105 ** automatically has some vertical spacing. The horizontal
2106 ** spacing is added here, on each side.
2108 Xtitle = gfx_get_text_width(im->canvas, 0,
2109 im->text_prop[TEXT_PROP_TITLE].font,
2110 im->text_prop[TEXT_PROP_TITLE].size,
2112 im->title, 0) + 2*Xspacing;
2113 Ytitle = im->text_prop[TEXT_PROP_TITLE].size*2;
2119 if (im->draw_x_grid) {
2121 Yxlabel=im->text_prop[TEXT_PROP_LEGEND].size *2;
2123 if (im->draw_y_grid) {
2124 Xylabel=im->text_prop[TEXT_PROP_LEGEND].size *6;
2130 im->piesize=im->xsize<im->ysize?im->xsize:im->ysize;
2135 /* Now calculate the total size. Insert some spacing where
2136 desired. im->xorigin and im->yorigin need to correspond
2137 with the lower left corner of the main graph area or, if
2138 this one is not set, the imaginary box surrounding the
2141 /* The legend width cannot yet be determined, as a result we
2142 ** have problems adjusting the image to it. For now, we just
2143 ** forget about it at all; the legend will have to fit in the
2144 ** size already allocated.
2148 if ( !(im->extra_flags & ONLY_GRAPH) ) {
2149 im->ximg = Xylabel + Xmain + Xpie + Xspacing;
2152 if (Xmain) im->ximg += Xspacing;
2153 if (Xpie) im->ximg += Xspacing;
2155 if (im->extra_flags & ONLY_GRAPH) {
2158 im->xorigin = Xspacing + Xylabel;
2161 if (Xtitle > im->ximg) im->ximg = Xtitle;
2163 im->ximg += Xvertical;
2164 im->xorigin += Xvertical;
2168 /* The vertical size is interesting... we need to compare
2169 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend} with Yvertical
2170 ** however we need to know {Ytitle+Ymain+Yxlabel} in order to
2171 ** start even thinking about Ylegend.
2173 ** Do it in three portions: First calculate the inner part,
2174 ** then do the legend, then adjust the total height of the img.
2177 /* reserve space for main and/or pie */
2179 if (im->extra_flags & ONLY_GRAPH) {
2182 im->yimg = Ymain + Yxlabel;
2185 if (im->yimg < Ypie) im->yimg = Ypie;
2187 if (im->extra_flags & ONLY_GRAPH) {
2188 im->yorigin = im->yimg;
2190 im->yorigin = im->yimg - Yxlabel;
2193 /* reserve space for the title *or* some padding above the graph */
2196 im->yorigin += Ytitle;
2198 im->yimg += Yspacing;
2199 im->yorigin += Yspacing;
2201 /* reserve space for padding below the graph */
2202 im->yimg += Yspacing;
2205 /* Determine where to place the legends onto the image.
2206 ** Adjust im->yimg to match the space requirements.
2208 if(leg_place(im)==-1)
2211 /* last of three steps: check total height of image */
2212 if (im->yimg < Yvertical) im->yimg = Yvertical;
2215 if (Xlegend > im->ximg) {
2217 /* reposition Pie */
2221 /* The pie is placed in the upper right hand corner,
2222 ** just below the title (if any) and with sufficient
2226 im->pie_x = im->ximg - Xspacing - Xpie/2;
2227 im->pie_y = im->yorigin-Ymain+Ypie/2;
2229 im->pie_x = im->ximg/2;
2230 im->pie_y = im->yorigin-Ypie/2;
2236 /* draw that picture thing ... */
2238 graph_paint(image_desc_t *im, char ***calcpr)
2241 int lazy = lazy_check(im);
2243 double PieStart=0.0;
2247 double areazero = 0.0;
2248 enum gf_en stack_gf = GF_PRINT;
2249 graph_desc_t *lastgdes = NULL;
2251 /* if we are lazy and there is nothing to PRINT ... quit now */
2252 if (lazy && im->prt_c==0) return 0;
2254 /* pull the data from the rrd files ... */
2256 if(data_fetch(im)==-1)
2259 /* evaluate VDEF and CDEF operations ... */
2260 if(data_calc(im)==-1)
2263 /* check if we need to draw a piechart */
2264 for(i=0;i<im->gdes_c;i++){
2265 if (im->gdes[i].gf == GF_PART) {
2271 /* calculate and PRINT and GPRINT definitions. We have to do it at
2272 * this point because it will affect the length of the legends
2273 * if there are no graph elements we stop here ...
2274 * if we are lazy, try to quit ...
2276 i=print_calc(im,calcpr);
2278 if(((i==0)&&(piechart==0)) || lazy) return 0;
2280 /* If there's only the pie chart to draw, signal this */
2281 if (i==0) piechart=2;
2283 /* get actual drawing data and find min and max values*/
2284 if(data_proc(im)==-1)
2287 if(!im->logarithmic){si_unit(im);} /* identify si magnitude Kilo, Mega Giga ? */
2289 if(!im->rigid && ! im->logarithmic)
2290 expand_range(im); /* make sure the upper and lower limit are
2293 if (!calc_horizontal_grid(im))
2300 /**************************************************************
2301 *** Calculating sizes and locations became a bit confusing ***
2302 *** so I moved this into a separate function. ***
2303 **************************************************************/
2304 if(graph_size_location(im,i,piechart)==-1)
2307 /* the actual graph is created by going through the individual
2308 graph elements and then drawing them */
2310 node=gfx_new_area ( im->canvas,
2314 im->graph_col[GRC_BACK]);
2316 gfx_add_point(node,0, im->yimg);
2318 if (piechart != 2) {
2319 node=gfx_new_area ( im->canvas,
2320 im->xorigin, im->yorigin,
2321 im->xorigin + im->xsize, im->yorigin,
2322 im->xorigin + im->xsize, im->yorigin-im->ysize,
2323 im->graph_col[GRC_CANVAS]);
2325 gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
2327 if (im->minval > 0.0)
2328 areazero = im->minval;
2329 if (im->maxval < 0.0)
2330 areazero = im->maxval;
2331 if( !(im->extra_flags & ONLY_GRAPH) )
2336 pie_part(im,im->graph_col[GRC_CANVAS],im->pie_x,im->pie_y,im->piesize*0.5,0,2*M_PI);
2339 for(i=0;i<im->gdes_c;i++){
2340 switch(im->gdes[i].gf){
2352 for (ii = 0; ii < im->xsize; ii++)
2354 if (!isnan(im->gdes[i].p_data[ii]) &&
2355 im->gdes[i].p_data[ii] > 0.0)
2357 /* generate a tick */
2358 gfx_new_line(im->canvas, im -> xorigin + ii,
2359 im -> yorigin - (im -> gdes[i].yrule * im -> ysize),
2363 im -> gdes[i].col );
2369 stack_gf = im->gdes[i].gf;
2371 /* fix data points at oo and -oo */
2372 for(ii=0;ii<im->xsize;ii++){
2373 if (isinf(im->gdes[i].p_data[ii])){
2374 if (im->gdes[i].p_data[ii] > 0) {
2375 im->gdes[i].p_data[ii] = im->maxval ;
2377 im->gdes[i].p_data[ii] = im->minval ;
2383 if (im->gdes[i].col != 0x0){
2384 /* GF_LINE and friend */
2385 if(stack_gf == GF_LINE ){
2387 for(ii=1;ii<im->xsize;ii++){
2388 if ( ! isnan(im->gdes[i].p_data[ii-1])
2389 && ! isnan(im->gdes[i].p_data[ii])){
2391 node = gfx_new_line(im->canvas,
2392 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2393 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2394 im->gdes[i].linewidth,
2397 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2406 for(ii=1;ii<im->xsize;ii++){
2408 if ( ! isnan(im->gdes[i].p_data[ii-1])
2409 && ! isnan(im->gdes[i].p_data[ii])){
2413 if (im->gdes[i].gf == GF_STACK) {
2415 if ( (im->gdes[i].gf == GF_STACK)
2416 || (im->gdes[i].stack) ) {
2418 ybase = ytr(im,lastgdes->p_data[ii-1]);
2420 ybase = ytr(im,areazero);
2423 node = gfx_new_area(im->canvas,
2424 ii-1+im->xorigin,ybase,
2425 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2426 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2430 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2434 if ( node != NULL && (ii+1==im->xsize || isnan(im->gdes[i].p_data[ii]) )){
2435 /* GF_AREA STACK type*/
2437 if (im->gdes[i].gf == GF_STACK ) {
2439 if ( (im->gdes[i].gf == GF_STACK)
2440 || (im->gdes[i].stack) ) {
2442 for (iii=ii-1;iii>area_start;iii--){
2443 gfx_add_point(node,iii+im->xorigin,ytr(im,lastgdes->p_data[iii]));
2446 gfx_add_point(node,ii+im->xorigin,ytr(im,areazero));
2451 } /* else GF_LINE */
2452 } /* if color != 0x0 */
2453 /* make sure we do not run into trouble when stacking on NaN */
2454 for(ii=0;ii<im->xsize;ii++){
2455 if (isnan(im->gdes[i].p_data[ii])) {
2456 if (lastgdes && (im->gdes[i].gf == GF_STACK)) {
2457 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
2459 im->gdes[i].p_data[ii] = ytr(im,areazero);
2463 lastgdes = &(im->gdes[i]);
2466 if(isnan(im->gdes[i].yrule)) /* fetch variable */
2467 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2469 if (finite(im->gdes[i].yrule)) { /* even the fetched var can be NaN */
2470 pie_part(im,im->gdes[i].col,
2471 im->pie_x,im->pie_y,im->piesize*0.4,
2472 M_PI*2.0*PieStart/100.0,
2473 M_PI*2.0*(PieStart+im->gdes[i].yrule)/100.0);
2474 PieStart += im->gdes[i].yrule;
2483 /* grid_paint also does the text */
2484 if( !(im->extra_flags & ONLY_GRAPH) )
2487 /* the RULES are the last thing to paint ... */
2488 for(i=0;i<im->gdes_c;i++){
2490 switch(im->gdes[i].gf){
2492 if(isnan(im->gdes[i].yrule)) { /* fetch variable */
2493 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2495 if(im->gdes[i].yrule >= im->minval
2496 && im->gdes[i].yrule <= im->maxval)
2497 gfx_new_line(im->canvas,
2498 im->xorigin,ytr(im,im->gdes[i].yrule),
2499 im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2500 1.0,im->gdes[i].col);
2503 if(im->gdes[i].xrule == 0) { /* fetch variable */
2504 im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
2506 if(im->gdes[i].xrule >= im->start
2507 && im->gdes[i].xrule <= im->end)
2508 gfx_new_line(im->canvas,
2509 xtr(im,im->gdes[i].xrule),im->yorigin,
2510 xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2511 1.0,im->gdes[i].col);
2519 if (strcmp(im->graphfile,"-")==0) {
2520 fo = im->graphhandle ? im->graphhandle : stdout;
2522 /* Change translation mode for stdout to BINARY */
2523 _setmode( _fileno( fo ), O_BINARY );
2526 if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2527 rrd_set_error("Opening '%s' for write: %s",im->graphfile,
2528 rrd_strerror(errno));
2532 gfx_render (im->canvas,im->ximg,im->yimg,0x0,fo);
2533 if (strcmp(im->graphfile,"-") != 0)
2539 /*****************************************************
2541 *****************************************************/
2544 gdes_alloc(image_desc_t *im){
2546 unsigned long def_step = (im->end-im->start)/im->xsize;
2548 if (im->step > def_step) /* step can be increassed ... no decreassed */
2549 def_step = im->step;
2553 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2554 * sizeof(graph_desc_t)))==NULL){
2555 rrd_set_error("realloc graph_descs");
2560 im->gdes[im->gdes_c-1].step=def_step;
2561 im->gdes[im->gdes_c-1].stack=0;
2562 im->gdes[im->gdes_c-1].debug=0;
2563 im->gdes[im->gdes_c-1].start=im->start;
2564 im->gdes[im->gdes_c-1].end=im->end;
2565 im->gdes[im->gdes_c-1].vname[0]='\0';
2566 im->gdes[im->gdes_c-1].data=NULL;
2567 im->gdes[im->gdes_c-1].ds_namv=NULL;
2568 im->gdes[im->gdes_c-1].data_first=0;
2569 im->gdes[im->gdes_c-1].p_data=NULL;
2570 im->gdes[im->gdes_c-1].rpnp=NULL;
2571 im->gdes[im->gdes_c-1].col = 0x0;
2572 im->gdes[im->gdes_c-1].legend[0]='\0';
2573 im->gdes[im->gdes_c-1].rrd[0]='\0';
2574 im->gdes[im->gdes_c-1].ds=-1;
2575 im->gdes[im->gdes_c-1].p_data=NULL;
2576 im->gdes[im->gdes_c-1].yrule=DNAN;
2577 im->gdes[im->gdes_c-1].xrule=0;
2581 /* copies input untill the first unescaped colon is found
2582 or until input ends. backslashes have to be escaped as well */
2584 scan_for_col(char *input, int len, char *output)
2589 input[inp] != ':' &&
2592 if (input[inp] == '\\' &&
2593 input[inp+1] != '\0' &&
2594 (input[inp+1] == '\\' ||
2595 input[inp+1] == ':')){
2596 output[outp++] = input[++inp];
2599 output[outp++] = input[inp];
2602 output[outp] = '\0';
2605 /* Some surgery done on this function, it became ridiculously big.
2607 ** - initializing now in rrd_graph_init()
2608 ** - options parsing now in rrd_graph_options()
2609 ** - script parsing now in rrd_graph_script()
2612 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize, FILE *stream)
2616 rrd_graph_init(&im);
2617 im.graphhandle = stream;
2619 rrd_graph_options(argc,argv,&im);
2620 if (rrd_test_error()) {
2625 if (strlen(argv[optind])>=MAXPATH) {
2626 rrd_set_error("filename (including path) too long");
2630 strncpy(im.graphfile,argv[optind],MAXPATH-1);
2631 im.graphfile[MAXPATH-1]='\0';
2633 rrd_graph_script(argc,argv,&im);
2634 if (rrd_test_error()) {
2639 /* Everything is now read and the actual work can start */
2642 if (graph_paint(&im,prdata)==-1){
2647 /* The image is generated and needs to be output.
2648 ** Also, if needed, print a line with information about the image.
2656 /* maybe prdata is not allocated yet ... lets do it now */
2657 if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
2658 rrd_set_error("malloc imginfo");
2662 if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
2664 rrd_set_error("malloc imginfo");
2667 filename=im.graphfile+strlen(im.graphfile);
2668 while(filename > im.graphfile) {
2669 if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
2673 sprintf((*prdata)[0],im.imginfo,filename,(long)(im.canvas->zoom*im.ximg),(long)(im.canvas->zoom*im.yimg));
2680 rrd_graph_init(image_desc_t *im)
2687 #ifdef HAVE_SETLOCALE
2688 setlocale(LC_TIME,"");
2691 im->xlab_user.minsec = -1;
2697 im->ylegend[0] = '\0';
2698 im->title[0] = '\0';
2701 im->unitsexponent= 9999;
2707 im->logarithmic = 0;
2708 im->ygridstep = DNAN;
2709 im->draw_x_grid = 1;
2710 im->draw_y_grid = 1;
2715 im->canvas = gfx_new_canvas();
2716 im->grid_dash_on = 1;
2717 im->grid_dash_off = 1;
2719 for(i=0;i<DIM(graph_col);i++)
2720 im->graph_col[i]=graph_col[i];
2724 windir = getenv("windir");
2725 /* %windir% is something like D:\windows or C:\winnt */
2726 if (windir != NULL) {
2727 strcpy(rrd_win_default_font,windir);
2728 strcat(rrd_win_default_font,"\\fonts\\cour.ttf");
2729 for(i=0;i<DIM(text_prop);i++)
2730 text_prop[i].font = rrd_win_default_font;
2734 for(i=0;i<DIM(text_prop);i++){
2735 im->text_prop[i].size = text_prop[i].size;
2736 im->text_prop[i].font = text_prop[i].font;
2741 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
2744 char *parsetime_error = NULL;
2745 char scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
2746 time_t start_tmp=0,end_tmp=0;
2748 struct rrd_time_value start_tv, end_tv;
2751 parsetime("end-24h", &start_tv);
2752 parsetime("now", &end_tv);
2755 static struct option long_options[] =
2757 {"start", required_argument, 0, 's'},
2758 {"end", required_argument, 0, 'e'},
2759 {"x-grid", required_argument, 0, 'x'},
2760 {"y-grid", required_argument, 0, 'y'},
2761 {"vertical-label",required_argument,0,'v'},
2762 {"width", required_argument, 0, 'w'},
2763 {"height", required_argument, 0, 'h'},
2764 {"interlaced", no_argument, 0, 'i'},
2765 {"upper-limit",required_argument, 0, 'u'},
2766 {"lower-limit",required_argument, 0, 'l'},
2767 {"rigid", no_argument, 0, 'r'},
2768 {"base", required_argument, 0, 'b'},
2769 {"logarithmic",no_argument, 0, 'o'},
2770 {"color", required_argument, 0, 'c'},
2771 {"font", required_argument, 0, 'n'},
2772 {"title", required_argument, 0, 't'},
2773 {"imginfo", required_argument, 0, 'f'},
2774 {"imgformat", required_argument, 0, 'a'},
2775 {"lazy", no_argument, 0, 'z'},
2776 {"zoom", required_argument, 0, 'm'},
2777 {"no-legend", no_argument, 0, 'g'},
2778 {"only-graph", no_argument, 0, 'j'},
2779 {"alt-y-grid", no_argument, 0, 'Y'},
2780 {"no-minor", no_argument, 0, 'I'},
2781 {"alt-autoscale", no_argument, 0, 'A'},
2782 {"alt-autoscale-max", no_argument, 0, 'M'},
2783 {"units-exponent",required_argument, 0, 'X'},
2784 {"step", required_argument, 0, 'S'},
2785 {"no-gridfit", no_argument, 0, 'N'},
2787 int option_index = 0;
2791 opt = getopt_long(argc, argv,
2792 "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:I:zgjYAMX:S:N",
2793 long_options, &option_index);
2800 im->extra_flags |= NOMINOR;
2803 im->extra_flags |= ALTYGRID;
2806 im->extra_flags |= ALTAUTOSCALE;
2809 im->extra_flags |= ALTAUTOSCALE_MAX;
2812 im->extra_flags |= ONLY_GRAPH;
2815 im->extra_flags |= NOLEGEND;
2818 im->unitsexponent = atoi(optarg);
2821 im->step = atoi(optarg);
2827 if ((parsetime_error = parsetime(optarg, &start_tv))) {
2828 rrd_set_error( "start time: %s", parsetime_error );
2833 if ((parsetime_error = parsetime(optarg, &end_tv))) {
2834 rrd_set_error( "end time: %s", parsetime_error );
2839 if(strcmp(optarg,"none") == 0){
2845 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
2847 &im->xlab_user.gridst,
2849 &im->xlab_user.mgridst,
2851 &im->xlab_user.labst,
2852 &im->xlab_user.precis,
2853 &stroff) == 7 && stroff != 0){
2854 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
2855 if((int)(im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
2856 rrd_set_error("unknown keyword %s",scan_gtm);
2858 } else if ((int)(im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
2859 rrd_set_error("unknown keyword %s",scan_mtm);
2861 } else if ((int)(im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
2862 rrd_set_error("unknown keyword %s",scan_ltm);
2865 im->xlab_user.minsec = 1;
2866 im->xlab_user.stst = im->xlab_form;
2868 rrd_set_error("invalid x-grid format");
2874 if(strcmp(optarg,"none") == 0){
2882 &im->ylabfact) == 2) {
2883 if(im->ygridstep<=0){
2884 rrd_set_error("grid step must be > 0");
2886 } else if (im->ylabfact < 1){
2887 rrd_set_error("label factor must be > 0");
2891 rrd_set_error("invalid y-grid format");
2896 strncpy(im->ylegend,optarg,150);
2897 im->ylegend[150]='\0';
2900 im->maxval = atof(optarg);
2903 im->minval = atof(optarg);
2906 im->base = atol(optarg);
2907 if(im->base != 1024 && im->base != 1000 ){
2908 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
2913 long_tmp = atol(optarg);
2914 if (long_tmp < 10) {
2915 rrd_set_error("width below 10 pixels");
2918 im->xsize = long_tmp;
2921 long_tmp = atol(optarg);
2922 if (long_tmp < 10) {
2923 rrd_set_error("height below 10 pixels");
2926 im->ysize = long_tmp;
2929 im->canvas->interlaced = 1;
2935 im->imginfo = optarg;
2938 if((int)(im->canvas->imgformat = if_conv(optarg)) == -1) {
2939 rrd_set_error("unsupported graphics format '%s'",optarg);
2947 im->logarithmic = 1;
2948 if (isnan(im->minval))
2954 col_nam,&color) == 2){
2956 if((ci=grc_conv(col_nam)) != -1){
2957 im->graph_col[ci]=color;
2959 rrd_set_error("invalid color name '%s'",col_nam);
2962 rrd_set_error("invalid color def format");
2967 /* originally this used char *prop = "" and
2968 ** char *font = "dummy" however this results
2969 ** in a SEG fault, at least on RH7.1
2971 ** The current implementation isn't proper
2972 ** either, font is never freed and prop uses
2973 ** a fixed width string
2982 prop,&size,font) == 3){
2984 if((sindex=text_prop_conv(prop)) != -1){
2985 im->text_prop[sindex].size=size;
2986 im->text_prop[sindex].font=font;
2987 if (sindex==0) { /* the default */
2988 im->text_prop[TEXT_PROP_TITLE].size=size;
2989 im->text_prop[TEXT_PROP_TITLE].font=font;
2990 im->text_prop[TEXT_PROP_AXIS].size=size;
2991 im->text_prop[TEXT_PROP_AXIS].font=font;
2992 im->text_prop[TEXT_PROP_UNIT].size=size;
2993 im->text_prop[TEXT_PROP_UNIT].font=font;
2994 im->text_prop[TEXT_PROP_LEGEND].size=size;
2995 im->text_prop[TEXT_PROP_LEGEND].font=font;
2998 rrd_set_error("invalid fonttag '%s'",prop);
3002 rrd_set_error("invalid text property format");
3008 im->canvas->zoom = atof(optarg);
3009 if (im->canvas->zoom <= 0.0) {
3010 rrd_set_error("zoom factor must be > 0");
3015 strncpy(im->title,optarg,150);
3016 im->title[150]='\0';
3021 rrd_set_error("unknown option '%c'", optopt);
3023 rrd_set_error("unknown option '%s'",argv[optind-1]);
3028 if (optind >= argc) {
3029 rrd_set_error("missing filename");
3033 if (im->logarithmic == 1 && (im->minval <= 0 || isnan(im->minval))){
3034 rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");
3038 if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
3039 /* error string is set in parsetime.c */
3043 if (start_tmp < 3600*24*365*10){
3044 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
3048 if (end_tmp < start_tmp) {
3049 rrd_set_error("start (%ld) should be less than end (%ld)",
3050 start_tmp, end_tmp);
3054 im->start = start_tmp;
3059 rrd_graph_check_vname(image_desc_t *im, char *varname, char *err)
3061 if ((im->gdes[im->gdes_c-1].vidx=find_var(im,varname))==-1) {
3062 rrd_set_error("Unknown variable '%s' in %s",varname,err);
3068 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
3071 graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
3073 color=strstr(var,"#");
3076 rrd_set_error("Found no color in %s",err);
3085 rest=strstr(color,":");
3093 sscanf(color,"#%6lx%n",&col,&n);
3094 col = (col << 8) + 0xff /* shift left by 8 */;
3095 if (n!=7) rrd_set_error("Color problem in %s",err);
3098 sscanf(color,"#%8lx%n",&col,&n);
3101 rrd_set_error("Color problem in %s",err);
3103 if (rrd_test_error()) return 0;
3109 rrd_graph_legend(graph_desc_t *gdp, char *line)
3113 i=scan_for_col(line,FMT_LEG_LEN,gdp->legend);
3115 return (strlen(&line[i])==0);
3119 int bad_format(char *fmt) {
3123 while (*ptr != '\0')
3124 if (*ptr++ == '%') {
3126 /* line cannot end with percent char */
3127 if (*ptr == '\0') return 1;
3129 /* '%s', '%S' and '%%' are allowed */
3130 if (*ptr == 's' || *ptr == 'S' || *ptr == '%') ptr++;
3132 /* or else '% 6.2lf' and such are allowed */
3135 /* optional padding character */
3136 if (*ptr == ' ' || *ptr == '+' || *ptr == '-') ptr++;
3138 /* This should take care of 'm.n' with all three optional */
3139 while (*ptr >= '0' && *ptr <= '9') ptr++;
3140 if (*ptr == '.') ptr++;
3141 while (*ptr >= '0' && *ptr <= '9') ptr++;
3143 /* Either 'le', 'lf' or 'lg' must follow here */
3144 if (*ptr++ != 'l') return 1;
3145 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g') ptr++;
3156 vdef_parse(gdes,str)
3157 struct graph_desc_t *gdes;
3160 /* A VDEF currently is either "func" or "param,func"
3161 * so the parsing is rather simple. Change if needed.
3168 sscanf(str,"%le,%29[A-Z]%n",¶m,func,&n);
3169 if (n== (int)strlen(str)) { /* matched */
3173 sscanf(str,"%29[A-Z]%n",func,&n);
3174 if (n== (int)strlen(str)) { /* matched */
3177 rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3184 if (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3185 else if (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3186 else if (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3187 else if (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3188 else if (!strcmp("TOTAL", func)) gdes->vf.op = VDEF_TOTAL;
3189 else if (!strcmp("FIRST", func)) gdes->vf.op = VDEF_FIRST;
3190 else if (!strcmp("LAST", func)) gdes->vf.op = VDEF_LAST;
3192 rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3199 switch (gdes->vf.op) {
3201 if (isnan(param)) { /* no parameter given */
3202 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3208 if (param>=0.0 && param<=100.0) {
3209 gdes->vf.param = param;
3210 gdes->vf.val = DNAN; /* undefined */
3211 gdes->vf.when = 0; /* undefined */
3213 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3227 gdes->vf.param = DNAN;
3228 gdes->vf.val = DNAN;
3231 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3248 graph_desc_t *src,*dst;
3252 dst = &im->gdes[gdi];
3253 src = &im->gdes[dst->vidx];
3254 data = src->data + src->ds;
3255 steps = (src->end - src->start) / src->step;
3258 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3265 switch (dst->vf.op) {
3266 case VDEF_PERCENT: {
3267 rrd_value_t * array;
3271 if ((array = malloc(steps*sizeof(double)))==NULL) {
3272 rrd_set_error("malloc VDEV_PERCENT");
3275 for (step=0;step < steps; step++) {
3276 array[step]=data[step*src->ds_cnt];
3278 qsort(array,step,sizeof(double),vdef_percent_compar);
3280 field = (steps-1)*dst->vf.param/100;
3281 dst->vf.val = array[field];
3282 dst->vf.when = 0; /* no time component */
3285 for(step=0;step<steps;step++)
3286 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3292 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3293 if (step == steps) {
3297 dst->vf.val = data[step*src->ds_cnt];
3298 dst->vf.when = src->start + (step+1)*src->step;
3300 while (step != steps) {
3301 if (finite(data[step*src->ds_cnt])) {
3302 if (data[step*src->ds_cnt] > dst->vf.val) {
3303 dst->vf.val = data[step*src->ds_cnt];
3304 dst->vf.when = src->start + (step+1)*src->step;
3311 case VDEF_AVERAGE: {
3314 for (step=0;step<steps;step++) {
3315 if (finite(data[step*src->ds_cnt])) {
3316 sum += data[step*src->ds_cnt];
3321 if (dst->vf.op == VDEF_TOTAL) {
3322 dst->vf.val = sum*src->step;
3323 dst->vf.when = cnt*src->step; /* not really "when" */
3325 dst->vf.val = sum/cnt;
3326 dst->vf.when = 0; /* no time component */
3336 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3337 if (step == steps) {
3341 dst->vf.val = data[step*src->ds_cnt];
3342 dst->vf.when = src->start + (step+1)*src->step;
3344 while (step != steps) {
3345 if (finite(data[step*src->ds_cnt])) {
3346 if (data[step*src->ds_cnt] < dst->vf.val) {
3347 dst->vf.val = data[step*src->ds_cnt];
3348 dst->vf.when = src->start + (step+1)*src->step;
3355 /* The time value returned here is one step before the
3356 * actual time value. This is the start of the first
3360 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3361 if (step == steps) { /* all entries were NaN */
3365 dst->vf.val = data[step*src->ds_cnt];
3366 dst->vf.when = src->start + step*src->step;
3370 /* The time value returned here is the
3371 * actual time value. This is the end of the last
3375 while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3376 if (step < 0) { /* all entries were NaN */
3380 dst->vf.val = data[step*src->ds_cnt];
3381 dst->vf.when = src->start + (step+1)*src->step;
3388 /* NaN < -INF < finite_values < INF */
3390 vdef_percent_compar(a,b)
3393 /* Equality is not returned; this doesn't hurt except
3394 * (maybe) for a little performance.
3397 /* First catch NaN values. They are smallest */
3398 if (isnan( *(double *)a )) return -1;
3399 if (isnan( *(double *)b )) return 1;
3401 /* NaN doesn't reach this part so INF and -INF are extremes.
3402 * The sign from isinf() is compatible with the sign we return
3404 if (isinf( *(double *)a )) return isinf( *(double *)a );
3405 if (isinf( *(double *)b )) return isinf( *(double *)b );
3407 /* If we reach this, both values must be finite */
3408 if ( *(double *)a < *(double *)b ) return -1; else return 1;