1 /****************************************************************************
2 * RRDtool 1.2.x Copyright Tobias Oetiker, 1997 - 2005
3 ****************************************************************************
4 * rrd__graph.c produce graphs from data in rrdfiles
5 ****************************************************************************/
25 #include "rrd_graph.h"
27 /* some constant definitions */
31 char rrd_win_default_font[80];
34 #ifndef RRD_DEFAULT_FONT
35 /* there is special code later to pick Cour.ttf when running on windows */
36 #define RRD_DEFAULT_FONT "VeraMono.ttf"
39 text_prop_t text_prop[] = {
40 { 9.0, RRD_DEFAULT_FONT }, /* default */
41 { 11.0, RRD_DEFAULT_FONT }, /* title */
42 { 8.0, RRD_DEFAULT_FONT }, /* axis */
43 { 9.0, RRD_DEFAULT_FONT }, /* unit */
44 { 9.0, RRD_DEFAULT_FONT } /* legend */
48 {0, TMT_SECOND,30, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
49 {2, TMT_MINUTE,1, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
50 {5, TMT_MINUTE,2, TMT_MINUTE,10, TMT_MINUTE,10, 0,"%H:%M"},
51 {10, TMT_MINUTE,5, TMT_MINUTE,20, TMT_MINUTE,20, 0,"%H:%M"},
52 {30, TMT_MINUTE,10, TMT_HOUR,1, TMT_HOUR,1, 0,"%H:%M"},
53 {60, TMT_MINUTE,30, TMT_HOUR,2, TMT_HOUR,2, 0,"%H:%M"},
54 {180, TMT_HOUR,1, TMT_HOUR,6, TMT_HOUR,6, 0,"%H:%M"},
55 /*{300, TMT_HOUR,3, TMT_HOUR,12, TMT_HOUR,12, 12*3600,"%a %p"}, this looks silly*/
56 {600, TMT_HOUR,6, TMT_DAY,1, TMT_DAY,1, 24*3600,"%a"},
57 {1800, TMT_HOUR,12, TMT_DAY,1, TMT_DAY,2, 24*3600,"%a"},
58 {3600, TMT_DAY,1, TMT_WEEK,1, TMT_WEEK,1, 7*24*3600,"Week %V"},
59 {3*3600, TMT_WEEK,1, TMT_MONTH,1, TMT_WEEK,2, 7*24*3600,"Week %V"},
60 {6*3600, TMT_MONTH,1, TMT_MONTH,1, TMT_MONTH,1, 30*24*3600,"%b"},
61 {48*3600, TMT_MONTH,1, TMT_MONTH,3, TMT_MONTH,3, 30*24*3600,"%b"},
62 {10*24*3600, TMT_YEAR,1, TMT_YEAR,1, TMT_YEAR,1, 365*24*3600,"%y"},
63 {-1,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 0xFF0000FF, /* arrow */
106 0x404040FF /* axis */
113 # define DPRINT(x) (void)(printf x, printf("\n"))
119 /* initialize with xtr(im,0); */
121 xtr(image_desc_t *im,time_t mytime){
124 pixie = (double) im->xsize / (double)(im->end - im->start);
127 return (int)((double)im->xorigin
128 + pixie * ( mytime - im->start ) );
131 /* translate data values into y coordinates */
133 ytr(image_desc_t *im, double value){
138 pixie = (double) im->ysize / (im->maxval - im->minval);
140 pixie = (double) im->ysize / (log10(im->maxval) - log10(im->minval));
142 } else if(!im->logarithmic) {
143 yval = im->yorigin - pixie * (value - im->minval);
145 if (value < im->minval) {
148 yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
151 /* make sure we don't return anything too unreasonable. GD lib can
152 get terribly slow when drawing lines outside its scope. This is
153 especially problematic in connection with the rigid option */
155 /* keep yval as-is */
156 } else if (yval > im->yorigin) {
157 yval = im->yorigin+2;
158 } else if (yval < im->yorigin - im->ysize){
159 yval = im->yorigin - im->ysize - 2;
166 /* conversion function for symbolic entry names */
169 #define conv_if(VV,VVV) \
170 if (strcmp(#VV, string) == 0) return VVV ;
172 enum gf_en gf_conv(char *string){
174 conv_if(PRINT,GF_PRINT)
175 conv_if(GPRINT,GF_GPRINT)
176 conv_if(COMMENT,GF_COMMENT)
177 conv_if(HRULE,GF_HRULE)
178 conv_if(VRULE,GF_VRULE)
179 conv_if(LINE,GF_LINE)
180 conv_if(AREA,GF_AREA)
181 conv_if(STACK,GF_STACK)
182 conv_if(TICK,GF_TICK)
184 conv_if(CDEF,GF_CDEF)
185 conv_if(VDEF,GF_VDEF)
186 conv_if(PART,GF_PART)
187 conv_if(XPORT,GF_XPORT)
188 conv_if(SHIFT,GF_SHIFT)
193 enum gfx_if_en if_conv(char *string){
203 enum tmt_en tmt_conv(char *string){
205 conv_if(SECOND,TMT_SECOND)
206 conv_if(MINUTE,TMT_MINUTE)
207 conv_if(HOUR,TMT_HOUR)
209 conv_if(WEEK,TMT_WEEK)
210 conv_if(MONTH,TMT_MONTH)
211 conv_if(YEAR,TMT_YEAR)
215 enum grc_en grc_conv(char *string){
217 conv_if(BACK,GRC_BACK)
218 conv_if(CANVAS,GRC_CANVAS)
219 conv_if(SHADEA,GRC_SHADEA)
220 conv_if(SHADEB,GRC_SHADEB)
221 conv_if(GRID,GRC_GRID)
222 conv_if(MGRID,GRC_MGRID)
223 conv_if(FONT,GRC_FONT)
224 conv_if(ARROW,GRC_ARROW)
225 conv_if(AXIS,GRC_AXIS)
230 enum text_prop_en text_prop_conv(char *string){
232 conv_if(DEFAULT,TEXT_PROP_DEFAULT)
233 conv_if(TITLE,TEXT_PROP_TITLE)
234 conv_if(AXIS,TEXT_PROP_AXIS)
235 conv_if(UNIT,TEXT_PROP_UNIT)
236 conv_if(LEGEND,TEXT_PROP_LEGEND)
244 im_free(image_desc_t *im)
248 if (im == NULL) return 0;
249 for(i=0;i<(unsigned)im->gdes_c;i++){
250 if (im->gdes[i].data_first){
251 /* careful here, because a single pointer can occur several times */
252 free (im->gdes[i].data);
253 if (im->gdes[i].ds_namv){
254 for (ii=0;ii<im->gdes[i].ds_cnt;ii++)
255 free(im->gdes[i].ds_namv[ii]);
256 free(im->gdes[i].ds_namv);
259 free (im->gdes[i].p_data);
260 free (im->gdes[i].rpnp);
263 gfx_destroy(im->canvas);
267 /* find SI magnitude symbol for the given number*/
270 image_desc_t *im, /* image description */
277 char *symbol[] = {"a", /* 10e-18 Atto */
278 "f", /* 10e-15 Femto */
279 "p", /* 10e-12 Pico */
280 "n", /* 10e-9 Nano */
281 "u", /* 10e-6 Micro */
282 "m", /* 10e-3 Milli */
287 "T", /* 10e12 Tera */
288 "P", /* 10e15 Peta */
294 if (*value == 0.0 || isnan(*value) ) {
298 sindex = floor(log(fabs(*value))/log((double)im->base));
299 *magfact = pow((double)im->base, (double)sindex);
300 (*value) /= (*magfact);
302 if ( sindex <= symbcenter && sindex >= -symbcenter) {
303 (*symb_ptr) = symbol[sindex+symbcenter];
311 /* find SI magnitude symbol for the numbers on the y-axis*/
314 image_desc_t *im /* image description */
318 char symbol[] = {'a', /* 10e-18 Atto */
319 'f', /* 10e-15 Femto */
320 'p', /* 10e-12 Pico */
321 'n', /* 10e-9 Nano */
322 'u', /* 10e-6 Micro */
323 'm', /* 10e-3 Milli */
328 'T', /* 10e12 Tera */
329 'P', /* 10e15 Peta */
335 if (im->unitsexponent != 9999) {
336 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
337 digits = floor(im->unitsexponent / 3);
339 digits = floor( log( max( fabs(im->minval),fabs(im->maxval)))/log((double)im->base));
341 im->magfact = pow((double)im->base , digits);
344 printf("digits %6.3f im->magfact %6.3f\n",digits,im->magfact);
347 if ( ((digits+symbcenter) < sizeof(symbol)) &&
348 ((digits+symbcenter) >= 0) )
349 im->symbol = symbol[(int)digits+symbcenter];
354 /* move min and max values around to become sensible */
357 expand_range(image_desc_t *im)
359 double sensiblevalues[] ={1000.0,900.0,800.0,750.0,700.0,
360 600.0,500.0,400.0,300.0,250.0,
361 200.0,125.0,100.0,90.0,80.0,
362 75.0,70.0,60.0,50.0,40.0,30.0,
363 25.0,20.0,10.0,9.0,8.0,
364 7.0,6.0,5.0,4.0,3.5,3.0,
365 2.5,2.0,1.8,1.5,1.2,1.0,
366 0.8,0.7,0.6,0.5,0.4,0.3,0.2,0.1,0.0,-1};
368 double scaled_min,scaled_max;
375 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
376 im->minval,im->maxval,im->magfact);
379 if (isnan(im->ygridstep)){
380 if(im->extra_flags & ALTAUTOSCALE) {
381 /* measure the amplitude of the function. Make sure that
382 graph boundaries are slightly higher then max/min vals
383 so we can see amplitude on the graph */
386 delt = im->maxval - im->minval;
388 fact = 2.0 * pow(10.0,
389 floor(log10(max(fabs(im->minval), fabs(im->maxval)))) - 2);
391 adj = (fact - delt) * 0.55;
393 printf("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n", im->minval, im->maxval, delt, fact, adj);
399 else if(im->extra_flags & ALTAUTOSCALE_MAX) {
400 /* measure the amplitude of the function. Make sure that
401 graph boundaries are slightly higher than max vals
402 so we can see amplitude on the graph */
403 adj = (im->maxval - im->minval) * 0.1;
407 scaled_min = im->minval / im->magfact;
408 scaled_max = im->maxval / im->magfact;
410 for (i=1; sensiblevalues[i] > 0; i++){
411 if (sensiblevalues[i-1]>=scaled_min &&
412 sensiblevalues[i]<=scaled_min)
413 im->minval = sensiblevalues[i]*(im->magfact);
415 if (-sensiblevalues[i-1]<=scaled_min &&
416 -sensiblevalues[i]>=scaled_min)
417 im->minval = -sensiblevalues[i-1]*(im->magfact);
419 if (sensiblevalues[i-1] >= scaled_max &&
420 sensiblevalues[i] <= scaled_max)
421 im->maxval = sensiblevalues[i-1]*(im->magfact);
423 if (-sensiblevalues[i-1]<=scaled_max &&
424 -sensiblevalues[i] >=scaled_max)
425 im->maxval = -sensiblevalues[i]*(im->magfact);
429 /* adjust min and max to the grid definition if there is one */
430 im->minval = (double)im->ylabfact * im->ygridstep *
431 floor(im->minval / ((double)im->ylabfact * im->ygridstep));
432 im->maxval = (double)im->ylabfact * im->ygridstep *
433 ceil(im->maxval /( (double)im->ylabfact * im->ygridstep));
437 fprintf(stderr,"SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
438 im->minval,im->maxval,im->magfact);
443 apply_gridfit(image_desc_t *im)
445 if (isnan(im->minval) || isnan(im->maxval))
448 if (im->logarithmic) {
449 double ya, yb, ypix, ypixfrac;
450 double log10_range = log10(im->maxval) - log10(im->minval);
451 ya = pow((double)10, floor(log10(im->minval)));
452 while (ya < im->minval)
455 return; /* don't have y=10^x gridline */
457 if (yb <= im->maxval) {
458 /* we have at least 2 y=10^x gridlines.
459 Make sure distance between them in pixels
460 are an integer by expanding im->maxval */
461 double y_pixel_delta = ytr(im, ya) - ytr(im, yb);
462 double factor = y_pixel_delta / floor(y_pixel_delta);
463 double new_log10_range = factor * log10_range;
464 double new_ymax_log10 = log10(im->minval) + new_log10_range;
465 im->maxval = pow(10, new_ymax_log10);
466 ytr(im, DNAN); /* reset precalc */
467 log10_range = log10(im->maxval) - log10(im->minval);
469 /* make sure first y=10^x gridline is located on
470 integer pixel position by moving scale slightly
471 downwards (sub-pixel movement) */
472 ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
473 ypixfrac = ypix - floor(ypix);
474 if (ypixfrac > 0 && ypixfrac < 1) {
475 double yfrac = ypixfrac / im->ysize;
476 im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
477 im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
478 ytr(im, DNAN); /* reset precalc */
481 /* Make sure we have an integer pixel distance between
482 each minor gridline */
483 double ypos1 = ytr(im, im->minval);
484 double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
485 double y_pixel_delta = ypos1 - ypos2;
486 double factor = y_pixel_delta / floor(y_pixel_delta);
487 double new_range = factor * (im->maxval - im->minval);
488 double gridstep = im->ygrid_scale.gridstep;
489 double minor_y, minor_y_px, minor_y_px_frac;
490 im->maxval = im->minval + new_range;
491 ytr(im, DNAN); /* reset precalc */
492 /* make sure first minor gridline is on integer pixel y coord */
493 minor_y = gridstep * floor(im->minval / gridstep);
494 while (minor_y < im->minval)
496 minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
497 minor_y_px_frac = minor_y_px - floor(minor_y_px);
498 if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
499 double yfrac = minor_y_px_frac / im->ysize;
500 double range = im->maxval - im->minval;
501 im->minval = im->minval - yfrac * range;
502 im->maxval = im->maxval - yfrac * range;
503 ytr(im, DNAN); /* reset precalc */
505 calc_horizontal_grid(im); /* recalc with changed im->maxval */
509 /* reduce data reimplementation by Alex */
513 enum cf_en cf, /* which consolidation function ?*/
514 unsigned long cur_step, /* step the data currently is in */
515 time_t *start, /* start, end and step as requested ... */
516 time_t *end, /* ... by the application will be ... */
517 unsigned long *step, /* ... adjusted to represent reality */
518 unsigned long *ds_cnt, /* number of data sources in file */
519 rrd_value_t **data) /* two dimensional array containing the data */
521 int i,reduce_factor = ceil((double)(*step) / (double)cur_step);
522 unsigned long col,dst_row,row_cnt,start_offset,end_offset,skiprows=0;
523 rrd_value_t *srcptr,*dstptr;
525 (*step) = cur_step*reduce_factor; /* set new step size for reduced data */
528 row_cnt = ((*end)-(*start))/cur_step;
534 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
535 row_cnt,reduce_factor,*start,*end,cur_step);
536 for (col=0;col<row_cnt;col++) {
537 printf("time %10lu: ",*start+(col+1)*cur_step);
538 for (i=0;i<*ds_cnt;i++)
539 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
544 /* We have to combine [reduce_factor] rows of the source
545 ** into one row for the destination. Doing this we also
546 ** need to take care to combine the correct rows. First
547 ** alter the start and end time so that they are multiples
548 ** of the new step time. We cannot reduce the amount of
549 ** time so we have to move the end towards the future and
550 ** the start towards the past.
552 end_offset = (*end) % (*step);
553 start_offset = (*start) % (*step);
555 /* If there is a start offset (which cannot be more than
556 ** one destination row), skip the appropriate number of
557 ** source rows and one destination row. The appropriate
558 ** number is what we do know (start_offset/cur_step) of
559 ** the new interval (*step/cur_step aka reduce_factor).
562 printf("start_offset: %lu end_offset: %lu\n",start_offset,end_offset);
563 printf("row_cnt before: %lu\n",row_cnt);
566 (*start) = (*start)-start_offset;
567 skiprows=reduce_factor-start_offset/cur_step;
568 srcptr+=skiprows* *ds_cnt;
569 for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
573 printf("row_cnt between: %lu\n",row_cnt);
576 /* At the end we have some rows that are not going to be
577 ** used, the amount is end_offset/cur_step
580 (*end) = (*end)-end_offset+(*step);
581 skiprows = end_offset/cur_step;
585 printf("row_cnt after: %lu\n",row_cnt);
588 /* Sanity check: row_cnt should be multiple of reduce_factor */
589 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
591 if (row_cnt%reduce_factor) {
592 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
593 row_cnt,reduce_factor);
594 printf("BUG in reduce_data()\n");
598 /* Now combine reduce_factor intervals at a time
599 ** into one interval for the destination.
602 for (dst_row=0;(long int)row_cnt>=reduce_factor;dst_row++) {
603 for (col=0;col<(*ds_cnt);col++) {
604 rrd_value_t newval=DNAN;
605 unsigned long validval=0;
607 for (i=0;i<reduce_factor;i++) {
608 if (isnan(srcptr[i*(*ds_cnt)+col])) {
612 if (isnan(newval)) newval = srcptr[i*(*ds_cnt)+col];
620 newval += srcptr[i*(*ds_cnt)+col];
623 newval = min (newval,srcptr[i*(*ds_cnt)+col]);
626 /* an interval contains a failure if any subintervals contained a failure */
628 newval = max (newval,srcptr[i*(*ds_cnt)+col]);
631 newval = srcptr[i*(*ds_cnt)+col];
636 if (validval == 0){newval = DNAN;} else{
654 srcptr+=(*ds_cnt)*reduce_factor;
655 row_cnt-=reduce_factor;
657 /* If we had to alter the endtime, we didn't have enough
658 ** source rows to fill the last row. Fill it with NaN.
660 if (end_offset) for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
662 row_cnt = ((*end)-(*start))/ *step;
664 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
665 row_cnt,*start,*end,*step);
666 for (col=0;col<row_cnt;col++) {
667 printf("time %10lu: ",*start+(col+1)*(*step));
668 for (i=0;i<*ds_cnt;i++)
669 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
676 /* get the data required for the graphs from the
680 data_fetch(image_desc_t *im )
685 /* pull the data from the log files ... */
686 for (i=0;i< (int)im->gdes_c;i++){
687 /* only GF_DEF elements fetch data */
688 if (im->gdes[i].gf != GF_DEF)
692 /* do we have it already ?*/
693 for (ii=0;ii<i;ii++) {
694 if (im->gdes[ii].gf != GF_DEF)
696 if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
697 && (im->gdes[i].cf == im->gdes[ii].cf)
698 && (im->gdes[i].cf_reduce == im->gdes[ii].cf_reduce)
699 && (im->gdes[i].start == im->gdes[ii].start)
700 && (im->gdes[i].end == im->gdes[ii].end)
701 && (im->gdes[i].step == im->gdes[ii].step)) {
702 /* OK, the data is already there.
703 ** Just copy the header portion
705 im->gdes[i].start = im->gdes[ii].start;
706 im->gdes[i].end = im->gdes[ii].end;
707 im->gdes[i].step = im->gdes[ii].step;
708 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
709 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
710 im->gdes[i].data = im->gdes[ii].data;
711 im->gdes[i].data_first = 0;
718 unsigned long ft_step = im->gdes[i].step ;
720 if((rrd_fetch_fn(im->gdes[i].rrd,
726 &im->gdes[i].ds_namv,
727 &im->gdes[i].data)) == -1){
730 im->gdes[i].data_first = 1;
731 im->gdes[i].step = im->step;
733 if (ft_step < im->gdes[i].step) {
734 reduce_data(im->gdes[i].cf_reduce,
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 graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx];
828 /* remove current shift */
829 vdp->start -= vdp->shift;
830 vdp->end -= vdp->shift;
833 if (im->gdes[gdi].shidx >= 0)
834 vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
837 vdp->shift = im->gdes[gdi].shval;
839 /* normalize shift to multiple of consolidated step */
840 vdp->shift = (vdp->shift / (long)vdp->step) * (long)vdp->step;
843 vdp->start += vdp->shift;
844 vdp->end += vdp->shift;
848 /* A VDEF has no DS. This also signals other parts
849 * of rrdtool that this is a VDEF value, not a CDEF.
851 im->gdes[gdi].ds_cnt = 0;
852 if (vdef_calc(im,gdi)) {
853 rrd_set_error("Error processing VDEF '%s'"
856 rpnstack_free(&rpnstack);
861 im->gdes[gdi].ds_cnt = 1;
862 im->gdes[gdi].ds = 0;
863 im->gdes[gdi].data_first = 1;
864 im->gdes[gdi].start = 0;
865 im->gdes[gdi].end = 0;
870 /* Find the variables in the expression.
871 * - VDEF variables are substituted by their values
872 * and the opcode is changed into OP_NUMBER.
873 * - CDEF variables are analized for their step size,
874 * the lowest common denominator of all the step
875 * sizes of the data sources involved is calculated
876 * and the resulting number is the step size for the
877 * resulting data source.
879 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
880 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
881 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER){
882 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
883 if (im->gdes[ptr].ds_cnt == 0) {
885 printf("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
887 im->gdes[ptr].vname);
888 printf("DEBUG: value from vdef is %f\n",im->gdes[ptr].vf.val);
890 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
891 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
894 rrd_realloc(steparray,
895 (++stepcnt+1)*sizeof(*steparray)))==NULL){
896 rrd_set_error("realloc steparray");
897 rpnstack_free(&rpnstack);
901 steparray[stepcnt-1] = im->gdes[ptr].step;
903 /* adjust start and end of cdef (gdi) so
904 * that it runs from the latest start point
905 * to the earliest endpoint of any of the
906 * rras involved (ptr)
908 if(im->gdes[gdi].start < im->gdes[ptr].start)
909 im->gdes[gdi].start = im->gdes[ptr].start;
911 if(im->gdes[gdi].end == 0 ||
912 im->gdes[gdi].end > im->gdes[ptr].end)
913 im->gdes[gdi].end = im->gdes[ptr].end;
915 /* store pointer to the first element of
916 * the rra providing data for variable,
917 * further save step size and data source
920 im->gdes[gdi].rpnp[rpi].data = im->gdes[ptr].data + im->gdes[ptr].ds;
921 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
922 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
924 /* backoff the *.data ptr; this is done so
925 * rpncalc() function doesn't have to treat
926 * the first case differently
928 } /* if ds_cnt != 0 */
929 } /* if OP_VARIABLE */
930 } /* loop through all rpi */
932 /* move the data pointers to the correct period */
933 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
934 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
935 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER){
936 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
937 long diff = im->gdes[gdi].start - im->gdes[ptr].start;
940 im->gdes[gdi].rpnp[rpi].data += (diff / im->gdes[ptr].step) * im->gdes[ptr].ds_cnt;
944 if(steparray == NULL){
945 rrd_set_error("rpn expressions without DEF"
946 " or CDEF variables are not supported");
947 rpnstack_free(&rpnstack);
950 steparray[stepcnt]=0;
951 /* Now find the resulting step. All steps in all
952 * used RRAs have to be visited
954 im->gdes[gdi].step = lcd(steparray);
956 if((im->gdes[gdi].data = malloc((
957 (im->gdes[gdi].end-im->gdes[gdi].start)
958 / im->gdes[gdi].step)
959 * sizeof(double)))==NULL){
960 rrd_set_error("malloc im->gdes[gdi].data");
961 rpnstack_free(&rpnstack);
965 /* Step through the new cdef results array and
966 * calculate the values
968 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
969 now<=im->gdes[gdi].end;
970 now += im->gdes[gdi].step)
972 rpnp_t *rpnp = im -> gdes[gdi].rpnp;
974 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
975 * in this case we are advancing by timesteps;
976 * we use the fact that time_t is a synonym for long
978 if (rpn_calc(rpnp,&rpnstack,(long) now,
979 im->gdes[gdi].data,++dataidx) == -1) {
980 /* rpn_calc sets the error string */
981 rpnstack_free(&rpnstack);
984 } /* enumerate over time steps within a CDEF */
989 } /* enumerate over CDEFs */
990 rpnstack_free(&rpnstack);
994 /* massage data so, that we get one value for each x coordinate in the graph */
996 data_proc( image_desc_t *im ){
998 double pixstep = (double)(im->end-im->start)
999 /(double)im->xsize; /* how much time
1000 passes in one pixel */
1002 double minval=DNAN,maxval=DNAN;
1004 unsigned long gr_time;
1006 /* memory for the processed data */
1007 for(i=0;i<im->gdes_c;i++) {
1008 if((im->gdes[i].gf==GF_LINE) ||
1009 (im->gdes[i].gf==GF_AREA) ||
1010 (im->gdes[i].gf==GF_TICK) ||
1011 (im->gdes[i].gf==GF_STACK)) {
1012 if((im->gdes[i].p_data = malloc((im->xsize +1)
1013 * sizeof(rrd_value_t)))==NULL){
1014 rrd_set_error("malloc data_proc");
1020 for (i=0;i<im->xsize;i++) { /* for each pixel */
1022 gr_time = im->start+pixstep*i; /* time of the current step */
1025 for (ii=0;ii<im->gdes_c;ii++) {
1027 switch (im->gdes[ii].gf) {
1031 if (!im->gdes[ii].stack)
1034 value = im->gdes[ii].yrule;
1035 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1036 /* The time of the data doesn't necessarily match
1037 ** the time of the graph. Beware.
1039 vidx = im->gdes[ii].vidx;
1040 if (im->gdes[vidx].gf == GF_VDEF) {
1041 value = im->gdes[vidx].vf.val;
1042 } else if (((long int)gr_time >= (long int)im->gdes[vidx].start) &&
1043 ((long int)gr_time <= (long int)im->gdes[vidx].end) ) {
1044 value = im->gdes[vidx].data[
1045 (unsigned long) floor(
1046 (double)(gr_time - im->gdes[vidx].start)
1047 / im->gdes[vidx].step)
1048 * im->gdes[vidx].ds_cnt
1056 if (! isnan(value)) {
1058 im->gdes[ii].p_data[i] = paintval;
1059 /* GF_TICK: the data values are not
1060 ** relevant for min and max
1062 if (finite(paintval) && im->gdes[ii].gf != GF_TICK ) {
1063 if (isnan(minval) || paintval < minval)
1065 if (isnan(maxval) || paintval > maxval)
1069 im->gdes[ii].p_data[i] = DNAN;
1078 /* if min or max have not been asigned a value this is because
1079 there was no data in the graph ... this is not good ...
1080 lets set these to dummy values then ... */
1082 if (isnan(minval)) minval = 0.0;
1083 if (isnan(maxval)) maxval = 1.0;
1085 /* adjust min and max values */
1086 if (isnan(im->minval)
1087 /* don't adjust low-end with log scale */
1088 || ((!im->logarithmic && !im->rigid) && im->minval > minval)
1090 im->minval = minval;
1091 if (isnan(im->maxval)
1092 || (!im->rigid && im->maxval < maxval)
1094 if (im->logarithmic)
1095 im->maxval = maxval * 1.1;
1097 im->maxval = maxval;
1099 /* make sure min is smaller than max */
1100 if (im->minval > im->maxval) {
1101 im->minval = 0.99 * im->maxval;
1104 /* make sure min and max are not equal */
1105 if (im->minval == im->maxval) {
1107 if (! im->logarithmic) {
1110 /* make sure min and max are not both zero */
1111 if (im->maxval == 0.0) {
1120 /* identify the point where the first gridline, label ... gets placed */
1124 time_t start, /* what is the initial time */
1125 enum tmt_en baseint, /* what is the basic interval */
1126 long basestep /* how many if these do we jump a time */
1130 localtime_r(&start, &tm);
1133 tm.tm_sec -= tm.tm_sec % basestep; break;
1136 tm.tm_min -= tm.tm_min % basestep;
1141 tm.tm_hour -= tm.tm_hour % basestep; break;
1143 /* we do NOT look at the basestep for this ... */
1146 tm.tm_hour = 0; break;
1148 /* we do NOT look at the basestep for this ... */
1152 tm.tm_mday -= tm.tm_wday -1; /* -1 because we want the monday */
1153 if (tm.tm_wday==0) tm.tm_mday -= 7; /* we want the *previous* monday */
1160 tm.tm_mon -= tm.tm_mon % basestep; break;
1168 tm.tm_year -= (tm.tm_year+1900) % basestep;
1173 /* identify the point where the next gridline, label ... gets placed */
1176 time_t current, /* what is the initial time */
1177 enum tmt_en baseint, /* what is the basic interval */
1178 long basestep /* how many if these do we jump a time */
1183 localtime_r(¤t, &tm);
1187 tm.tm_sec += basestep; break;
1189 tm.tm_min += basestep; break;
1191 tm.tm_hour += basestep; break;
1193 tm.tm_mday += basestep; break;
1195 tm.tm_mday += 7*basestep; break;
1197 tm.tm_mon += basestep; break;
1199 tm.tm_year += basestep;
1201 madetime = mktime(&tm);
1202 } while (madetime == -1); /* this is necessary to skip impssible times
1203 like the daylight saving time skips */
1209 /* calculate values required for PRINT and GPRINT functions */
1212 print_calc(image_desc_t *im, char ***prdata)
1214 long i,ii,validsteps;
1217 int graphelement = 0;
1220 double magfact = -1;
1224 if (im->imginfo) prlines++;
1225 for(i=0;i<im->gdes_c;i++){
1226 switch(im->gdes[i].gf){
1229 if(((*prdata) = rrd_realloc((*prdata),prlines*sizeof(char *)))==NULL){
1230 rrd_set_error("realloc prdata");
1234 /* PRINT and GPRINT can now print VDEF generated values.
1235 * There's no need to do any calculations on them as these
1236 * calculations were already made.
1238 vidx = im->gdes[i].vidx;
1239 if (im->gdes[vidx].gf==GF_VDEF) { /* simply use vals */
1240 printval = im->gdes[vidx].vf.val;
1241 printtime = im->gdes[vidx].vf.when;
1242 } else { /* need to calculate max,min,avg etcetera */
1243 max_ii =((im->gdes[vidx].end
1244 - im->gdes[vidx].start)
1245 / im->gdes[vidx].step
1246 * im->gdes[vidx].ds_cnt);
1249 for( ii=im->gdes[vidx].ds;
1251 ii+=im->gdes[vidx].ds_cnt){
1252 if (! finite(im->gdes[vidx].data[ii]))
1254 if (isnan(printval)){
1255 printval = im->gdes[vidx].data[ii];
1260 switch (im->gdes[i].cf){
1263 case CF_DEVSEASONAL:
1267 printval += im->gdes[vidx].data[ii];
1270 printval = min( printval, im->gdes[vidx].data[ii]);
1274 printval = max( printval, im->gdes[vidx].data[ii]);
1277 printval = im->gdes[vidx].data[ii];
1280 if (im->gdes[i].cf==CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1281 if (validsteps > 1) {
1282 printval = (printval / validsteps);
1285 } /* prepare printval */
1287 if (!strcmp(im->gdes[i].format,"%c")) { /* VDEF time print */
1288 char ctime_buf[128]; /* PS: for ctime_r, must be >= 26 chars */
1289 if (im->gdes[i].gf == GF_PRINT){
1290 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1291 sprintf((*prdata)[prlines-2],"%s (%lu)",
1292 ctime_r(&printtime,ctime_buf),printtime);
1293 (*prdata)[prlines-1] = NULL;
1295 sprintf(im->gdes[i].legend,"%s (%lu)",
1296 ctime_r(&printtime,ctime_buf),printtime);
1300 if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) {
1301 /* Magfact is set to -1 upon entry to print_calc. If it
1302 * is still less than 0, then we need to run auto_scale.
1303 * Otherwise, put the value into the correct units. If
1304 * the value is 0, then do not set the symbol or magnification
1305 * so next the calculation will be performed again. */
1306 if (magfact < 0.0) {
1307 auto_scale(im,&printval,&si_symb,&magfact);
1308 if (printval == 0.0)
1311 printval /= magfact;
1313 *(++percent_s) = 's';
1314 } else if (strstr(im->gdes[i].format,"%s") != NULL) {
1315 auto_scale(im,&printval,&si_symb,&magfact);
1318 if (im->gdes[i].gf == GF_PRINT){
1319 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1320 (*prdata)[prlines-1] = NULL;
1321 if (bad_format(im->gdes[i].format)) {
1322 rrd_set_error("bad format for PRINT in '%s'", im->gdes[i].format);
1325 #ifdef HAVE_SNPRINTF
1326 snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
1328 sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
1333 if (bad_format(im->gdes[i].format)) {
1334 rrd_set_error("bad format for GPRINT in '%s'", im->gdes[i].format);
1337 #ifdef HAVE_SNPRINTF
1338 snprintf(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
1340 sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
1364 return graphelement;
1368 /* place legends with color spots */
1370 leg_place(image_desc_t *im)
1373 int interleg = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1374 int border = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1375 int fill=0, fill_last;
1377 int leg_x = border, leg_y = im->yimg;
1381 char prt_fctn; /*special printfunctions */
1384 if( !(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH) ) {
1385 if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1386 rrd_set_error("malloc for legspace");
1390 for(i=0;i<im->gdes_c;i++){
1393 /* hid legends for rules which are not displayed */
1395 if(!(im->extra_flags & FORCE_RULES_LEGEND)) {
1396 if (im->gdes[i].gf == GF_HRULE &&
1397 (im->gdes[i].yrule < im->minval || im->gdes[i].yrule > im->maxval))
1398 im->gdes[i].legend[0] = '\0';
1400 if (im->gdes[i].gf == GF_VRULE &&
1401 (im->gdes[i].xrule < im->start || im->gdes[i].xrule > im->end))
1402 im->gdes[i].legend[0] = '\0';
1405 leg_cc = strlen(im->gdes[i].legend);
1407 /* is there a controle code ant the end of the legend string ? */
1408 /* and it is not a tab \\t */
1409 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\' && im->gdes[i].legend[leg_cc-1] != 't') {
1410 prt_fctn = im->gdes[i].legend[leg_cc-1];
1412 im->gdes[i].legend[leg_cc] = '\0';
1416 /* remove exess space */
1417 while (prt_fctn=='g' &&
1419 im->gdes[i].legend[leg_cc-1]==' '){
1421 im->gdes[i].legend[leg_cc]='\0';
1424 legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1427 /* no interleg space if string ends in \g */
1428 fill += legspace[i];
1430 fill += gfx_get_text_width(im->canvas, fill+border,
1431 im->text_prop[TEXT_PROP_LEGEND].font,
1432 im->text_prop[TEXT_PROP_LEGEND].size,
1434 im->gdes[i].legend, 0);
1439 /* who said there was a special tag ... ?*/
1440 if (prt_fctn=='g') {
1443 if (prt_fctn == '\0') {
1444 if (i == im->gdes_c -1 ) prt_fctn ='l';
1446 /* is it time to place the legends ? */
1447 if (fill > im->ximg - 2*border){
1462 if (prt_fctn != '\0'){
1464 if (leg_c >= 2 && prt_fctn == 'j') {
1465 glue = (im->ximg - fill - 2* border) / (leg_c-1);
1469 if (prt_fctn =='c') leg_x = (im->ximg - fill) / 2.0;
1470 if (prt_fctn =='r') leg_x = im->ximg - fill - border;
1472 for(ii=mark;ii<=i;ii++){
1473 if(im->gdes[ii].legend[0]=='\0')
1474 continue; /* skip empty legends */
1475 im->gdes[ii].leg_x = leg_x;
1476 im->gdes[ii].leg_y = leg_y;
1478 gfx_get_text_width(im->canvas, leg_x,
1479 im->text_prop[TEXT_PROP_LEGEND].font,
1480 im->text_prop[TEXT_PROP_LEGEND].size,
1482 im->gdes[ii].legend, 0)
1486 leg_y += im->text_prop[TEXT_PROP_LEGEND].size*1.7;
1487 if (prt_fctn == 's') leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1499 /* create a grid on the graph. it determines what to do
1500 from the values of xsize, start and end */
1502 /* the xaxis labels are determined from the number of seconds per pixel
1503 in the requested graph */
1508 calc_horizontal_grid(image_desc_t *im)
1514 int decimals, fractionals;
1516 im->ygrid_scale.labfact=2;
1518 range = im->maxval - im->minval;
1519 scaledrange = range / im->magfact;
1521 /* does the scale of this graph make it impossible to put lines
1522 on it? If so, give up. */
1523 if (isnan(scaledrange)) {
1527 /* find grid spaceing */
1529 if(isnan(im->ygridstep)){
1530 if(im->extra_flags & ALTYGRID) {
1531 /* find the value with max number of digits. Get number of digits */
1532 decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))));
1533 if(decimals <= 0) /* everything is small. make place for zero */
1536 fractionals = floor(log10(range));
1537 if(fractionals < 0) /* small amplitude. */
1538 sprintf(im->ygrid_scale.labfmt, "%%%d.%df", decimals - fractionals + 1, -fractionals + 1);
1540 sprintf(im->ygrid_scale.labfmt, "%%%d.1f", decimals + 1);
1541 im->ygrid_scale.gridstep = pow((double)10, (double)fractionals);
1542 if(im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1543 im->ygrid_scale.gridstep = 0.1;
1544 /* should have at least 5 lines but no more then 15 */
1545 if(range/im->ygrid_scale.gridstep < 5)
1546 im->ygrid_scale.gridstep /= 10;
1547 if(range/im->ygrid_scale.gridstep > 15)
1548 im->ygrid_scale.gridstep *= 10;
1549 if(range/im->ygrid_scale.gridstep > 5) {
1550 im->ygrid_scale.labfact = 1;
1551 if(range/im->ygrid_scale.gridstep > 8)
1552 im->ygrid_scale.labfact = 2;
1555 im->ygrid_scale.gridstep /= 5;
1556 im->ygrid_scale.labfact = 5;
1560 for(i=0;ylab[i].grid > 0;i++){
1561 pixel = im->ysize / (scaledrange / ylab[i].grid);
1562 if (gridind == -1 && pixel > 5) {
1569 if (pixel * ylab[gridind].lfac[i] >= 2 * im->text_prop[TEXT_PROP_AXIS].size) {
1570 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1575 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1578 im->ygrid_scale.gridstep = im->ygridstep;
1579 im->ygrid_scale.labfact = im->ylabfact;
1584 int draw_horizontal_grid(image_desc_t *im)
1588 char graph_label[100];
1589 double X0=im->xorigin;
1590 double X1=im->xorigin+im->xsize;
1592 int sgrid = (int)( im->minval / im->ygrid_scale.gridstep - 1);
1593 int egrid = (int)( im->maxval / im->ygrid_scale.gridstep + 1);
1594 scaledstep = im->ygrid_scale.gridstep/im->magfact;
1595 for (i = sgrid; i <= egrid; i++){
1596 double Y0=ytr(im,im->ygrid_scale.gridstep*i);
1597 if ( Y0 >= im->yorigin-im->ysize
1598 && Y0 <= im->yorigin){
1599 if(i % im->ygrid_scale.labfact == 0){
1600 if (i==0 || im->symbol == ' ') {
1602 if(im->extra_flags & ALTYGRID) {
1603 sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*i);
1606 sprintf(graph_label,"%4.1f",scaledstep*i);
1609 sprintf(graph_label,"%4.0f",scaledstep*i);
1613 sprintf(graph_label,"%4.1f %c",scaledstep*i, im->symbol);
1615 sprintf(graph_label,"%4.0f %c",scaledstep*i, im->symbol);
1619 gfx_new_text ( im->canvas,
1620 X0-im->text_prop[TEXT_PROP_AXIS].size/1.5, Y0,
1621 im->graph_col[GRC_FONT],
1622 im->text_prop[TEXT_PROP_AXIS].font,
1623 im->text_prop[TEXT_PROP_AXIS].size,
1624 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1626 gfx_new_dashed_line ( im->canvas,
1629 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1630 im->grid_dash_on, im->grid_dash_off);
1632 } else if (!(im->extra_flags & NOMINOR)) {
1633 gfx_new_dashed_line ( im->canvas,
1636 GRIDWIDTH, im->graph_col[GRC_GRID],
1637 im->grid_dash_on, im->grid_dash_off);
1645 /* logaritmic horizontal grid */
1647 horizontal_log_grid(image_desc_t *im)
1651 int minoridx=0, majoridx=0;
1652 char graph_label[100];
1654 double value, pixperstep, minstep;
1656 /* find grid spaceing */
1657 pixpex= (double)im->ysize / (log10(im->maxval) - log10(im->minval));
1659 if (isnan(pixpex)) {
1663 for(i=0;yloglab[i][0] > 0;i++){
1664 minstep = log10(yloglab[i][0]);
1665 for(ii=1;yloglab[i][ii+1] > 0;ii++){
1666 if(yloglab[i][ii+2]==0){
1667 minstep = log10(yloglab[i][ii+1])-log10(yloglab[i][ii]);
1671 pixperstep = pixpex * minstep;
1672 if(pixperstep > 5){minoridx = i;}
1673 if(pixperstep > 2 * im->text_prop[TEXT_PROP_LEGEND].size){majoridx = i;}
1677 X1=im->xorigin+im->xsize;
1678 /* paint minor grid */
1679 for (value = pow((double)10, log10(im->minval)
1680 - fmod(log10(im->minval),log10(yloglab[minoridx][0])));
1681 value <= im->maxval;
1682 value *= yloglab[minoridx][0]){
1683 if (value < im->minval) continue;
1685 while(yloglab[minoridx][++i] > 0){
1686 Y0 = ytr(im,value * yloglab[minoridx][i]);
1687 if (Y0 <= im->yorigin - im->ysize) break;
1688 gfx_new_dashed_line ( im->canvas,
1691 GRIDWIDTH, im->graph_col[GRC_GRID],
1692 im->grid_dash_on, im->grid_dash_off);
1696 /* paint major grid and labels*/
1697 for (value = pow((double)10, log10(im->minval)
1698 - fmod(log10(im->minval),log10(yloglab[majoridx][0])));
1699 value <= im->maxval;
1700 value *= yloglab[majoridx][0]){
1701 if (value < im->minval) continue;
1703 while(yloglab[majoridx][++i] > 0){
1704 Y0 = ytr(im,value * yloglab[majoridx][i]);
1705 if (Y0 <= im->yorigin - im->ysize) break;
1706 gfx_new_dashed_line ( im->canvas,
1709 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1710 im->grid_dash_on, im->grid_dash_off);
1712 sprintf(graph_label,"%3.0e",value * yloglab[majoridx][i]);
1713 gfx_new_text ( im->canvas,
1714 X0-im->text_prop[TEXT_PROP_AXIS].size/1.5, Y0,
1715 im->graph_col[GRC_FONT],
1716 im->text_prop[TEXT_PROP_AXIS].font,
1717 im->text_prop[TEXT_PROP_AXIS].size,
1718 im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1730 int xlab_sel; /* which sort of label and grid ? */
1731 time_t ti, tilab, timajor;
1733 char graph_label[100];
1734 double X0,Y0,Y1; /* points for filled graph and more*/
1737 /* the type of time grid is determined by finding
1738 the number of seconds per pixel in the graph */
1741 if(im->xlab_user.minsec == -1){
1742 factor=(im->end - im->start)/im->xsize;
1744 while ( xlab[xlab_sel+1].minsec != -1
1745 && xlab[xlab_sel+1].minsec <= factor){ xlab_sel++; }
1746 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
1747 im->xlab_user.gridst = xlab[xlab_sel].gridst;
1748 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
1749 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
1750 im->xlab_user.labtm = xlab[xlab_sel].labtm;
1751 im->xlab_user.labst = xlab[xlab_sel].labst;
1752 im->xlab_user.precis = xlab[xlab_sel].precis;
1753 im->xlab_user.stst = xlab[xlab_sel].stst;
1756 /* y coords are the same for every line ... */
1758 Y1 = im->yorigin-im->ysize;
1761 /* paint the minor grid */
1762 if (!(im->extra_flags & NOMINOR))
1764 for(ti = find_first_time(im->start,
1765 im->xlab_user.gridtm,
1766 im->xlab_user.gridst),
1767 timajor = find_first_time(im->start,
1768 im->xlab_user.mgridtm,
1769 im->xlab_user.mgridst);
1771 ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
1773 /* are we inside the graph ? */
1774 if (ti < im->start || ti > im->end) continue;
1775 while (timajor < ti) {
1776 timajor = find_next_time(timajor,
1777 im->xlab_user.mgridtm, im->xlab_user.mgridst);
1779 if (ti == timajor) continue; /* skip as falls on major grid line */
1781 gfx_new_dashed_line(im->canvas,X0,Y0+1, X0,Y1-1,GRIDWIDTH,
1782 im->graph_col[GRC_GRID],
1783 im->grid_dash_on, im->grid_dash_off);
1788 /* paint the major grid */
1789 for(ti = find_first_time(im->start,
1790 im->xlab_user.mgridtm,
1791 im->xlab_user.mgridst);
1793 ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
1795 /* are we inside the graph ? */
1796 if (ti < im->start || ti > im->end) continue;
1798 gfx_new_dashed_line(im->canvas,X0,Y0+3, X0,Y1-2,MGRIDWIDTH,
1799 im->graph_col[GRC_MGRID],
1800 im->grid_dash_on, im->grid_dash_off);
1803 /* paint the labels below the graph */
1804 for(ti = find_first_time(im->start,
1805 im->xlab_user.labtm,
1806 im->xlab_user.labst);
1808 ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
1810 tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
1811 /* are we inside the graph ? */
1812 if (ti < im->start || ti > im->end) continue;
1815 localtime_r(&tilab, &tm);
1816 strftime(graph_label,99,im->xlab_user.stst, &tm);
1818 # error "your libc has no strftime I guess we'll abort the exercise here."
1820 gfx_new_text ( im->canvas,
1821 xtr(im,tilab), Y0+im->text_prop[TEXT_PROP_AXIS].size/1.5,
1822 im->graph_col[GRC_FONT],
1823 im->text_prop[TEXT_PROP_AXIS].font,
1824 im->text_prop[TEXT_PROP_AXIS].size,
1825 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP,
1838 /* draw x and y axis */
1839 /* gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
1840 im->xorigin+im->xsize,im->yorigin-im->ysize,
1841 GRIDWIDTH, im->graph_col[GRC_AXIS]);
1843 gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
1844 im->xorigin+im->xsize,im->yorigin-im->ysize,
1845 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
1847 gfx_new_line ( im->canvas, im->xorigin-4,im->yorigin,
1848 im->xorigin+im->xsize+4,im->yorigin,
1849 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
1851 gfx_new_line ( im->canvas, im->xorigin,im->yorigin+4,
1852 im->xorigin,im->yorigin-im->ysize-4,
1853 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
1856 /* arrow for X axis direction */
1857 gfx_new_area ( im->canvas,
1858 im->xorigin+im->xsize+3, im->yorigin-3,
1859 im->xorigin+im->xsize+3, im->yorigin+4,
1860 im->xorigin+im->xsize+8, im->yorigin+0.5, /* LINEOFFSET */
1861 im->graph_col[GRC_ARROW]);
1866 grid_paint(image_desc_t *im)
1870 double X0,Y0; /* points for filled graph and more*/
1873 /* draw 3d border */
1874 node = gfx_new_area (im->canvas, 0,im->yimg,
1876 2,2,im->graph_col[GRC_SHADEA]);
1877 gfx_add_point( node , im->ximg - 2, 2 );
1878 gfx_add_point( node , im->ximg, 0 );
1879 gfx_add_point( node , 0,0 );
1880 /* gfx_add_point( node , 0,im->yimg ); */
1882 node = gfx_new_area (im->canvas, 2,im->yimg-2,
1883 im->ximg-2,im->yimg-2,
1885 im->graph_col[GRC_SHADEB]);
1886 gfx_add_point( node , im->ximg,0);
1887 gfx_add_point( node , im->ximg,im->yimg);
1888 gfx_add_point( node , 0,im->yimg);
1889 /* gfx_add_point( node , 0,im->yimg ); */
1892 if (im->draw_x_grid == 1 )
1895 if (im->draw_y_grid == 1){
1896 if(im->logarithmic){
1897 res = horizontal_log_grid(im);
1899 res = draw_horizontal_grid(im);
1902 /* dont draw horizontal grid if there is no min and max val */
1904 char *nodata = "No Data found";
1905 gfx_new_text(im->canvas,im->ximg/2, (2*im->yorigin-im->ysize) / 2,
1906 im->graph_col[GRC_FONT],
1907 im->text_prop[TEXT_PROP_AXIS].font,
1908 im->text_prop[TEXT_PROP_AXIS].size,
1909 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
1914 /* yaxis unit description */
1915 gfx_new_text( im->canvas,
1916 7, (im->yorigin - im->ysize/2),
1917 im->graph_col[GRC_FONT],
1918 im->text_prop[TEXT_PROP_UNIT].font,
1919 im->text_prop[TEXT_PROP_UNIT].size, im->tabwidth,
1920 RRDGRAPH_YLEGEND_ANGLE,
1921 GFX_H_LEFT, GFX_V_CENTER,
1925 gfx_new_text( im->canvas,
1926 im->ximg/2, im->text_prop[TEXT_PROP_TITLE].size*1.2,
1927 im->graph_col[GRC_FONT],
1928 im->text_prop[TEXT_PROP_TITLE].font,
1929 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
1930 GFX_H_CENTER, GFX_V_CENTER,
1934 if( !(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH) ) {
1935 for(i=0;i<im->gdes_c;i++){
1936 if(im->gdes[i].legend[0] =='\0')
1939 /* im->gdes[i].leg_y is the bottom of the legend */
1940 X0 = im->gdes[i].leg_x;
1941 Y0 = im->gdes[i].leg_y;
1942 gfx_new_text ( im->canvas, X0, Y0,
1943 im->graph_col[GRC_FONT],
1944 im->text_prop[TEXT_PROP_LEGEND].font,
1945 im->text_prop[TEXT_PROP_LEGEND].size,
1946 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
1947 im->gdes[i].legend );
1948 /* The legend for GRAPH items starts with "M " to have
1949 enough space for the box */
1950 if ( im->gdes[i].gf != GF_PRINT &&
1951 im->gdes[i].gf != GF_GPRINT &&
1952 im->gdes[i].gf != GF_COMMENT) {
1955 boxH = gfx_get_text_width(im->canvas, 0,
1956 im->text_prop[TEXT_PROP_LEGEND].font,
1957 im->text_prop[TEXT_PROP_LEGEND].size,
1958 im->tabwidth,"M", 0)*1.2;
1961 node = gfx_new_area(im->canvas,
1966 gfx_add_point ( node, X0+boxH, Y0-boxV );
1967 node = gfx_new_line(im->canvas,
1970 gfx_add_point(node,X0+boxH,Y0);
1971 gfx_add_point(node,X0+boxH,Y0-boxV);
1972 gfx_close_path(node);
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) {
2093 if (im->ylegend[0] != '\0') {
2094 Xvertical = im->text_prop[TEXT_PROP_UNIT].size *2;
2095 Yvertical = gfx_get_text_width(im->canvas, 0,
2096 im->text_prop[TEXT_PROP_UNIT].font,
2097 im->text_prop[TEXT_PROP_UNIT].size,
2098 im->tabwidth,im->ylegend, 0);
2102 if (im->title[0] != '\0') {
2103 /* The title is placed "inbetween" two text lines so it
2104 ** automatically has some vertical spacing. The horizontal
2105 ** spacing is added here, on each side.
2107 Xtitle = gfx_get_text_width(im->canvas, 0,
2108 im->text_prop[TEXT_PROP_TITLE].font,
2109 im->text_prop[TEXT_PROP_TITLE].size,
2111 im->title, 0) + 2*Xspacing;
2112 Ytitle = im->text_prop[TEXT_PROP_TITLE].size*2.5;
2118 if (im->draw_x_grid) {
2120 Yxlabel=im->text_prop[TEXT_PROP_AXIS].size *2.5;
2122 if (im->draw_y_grid) {
2123 Xylabel=im->text_prop[TEXT_PROP_AXIS].size *6;
2129 im->piesize=im->xsize<im->ysize?im->xsize:im->ysize;
2134 /* Now calculate the total size. Insert some spacing where
2135 desired. im->xorigin and im->yorigin need to correspond
2136 with the lower left corner of the main graph area or, if
2137 this one is not set, the imaginary box surrounding the
2140 /* The legend width cannot yet be determined, as a result we
2141 ** have problems adjusting the image to it. For now, we just
2142 ** forget about it at all; the legend will have to fit in the
2143 ** size already allocated.
2147 if ( !(im->extra_flags & ONLY_GRAPH) ) {
2148 im->ximg = Xylabel + Xmain + Xpie + 2 * Xspacing;
2151 if (Xmain) im->ximg += Xspacing;
2152 if (Xpie) im->ximg += Xspacing;
2154 if (im->extra_flags & ONLY_GRAPH) {
2157 im->xorigin = Xspacing + Xylabel;
2160 if (Xtitle > im->ximg) im->ximg = Xtitle;
2162 im->ximg += Xvertical;
2163 im->xorigin += Xvertical;
2167 /* The vertical size is interesting... we need to compare
2168 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend} with Yvertical
2169 ** however we need to know {Ytitle+Ymain+Yxlabel} in order to
2170 ** start even thinking about Ylegend.
2172 ** Do it in three portions: First calculate the inner part,
2173 ** then do the legend, then adjust the total height of the img.
2176 /* reserve space for main and/or pie */
2178 if (im->extra_flags & ONLY_GRAPH) {
2181 im->yimg = Ymain + Yxlabel;
2184 if (im->yimg < Ypie) im->yimg = Ypie;
2186 if (im->extra_flags & ONLY_GRAPH) {
2187 im->yorigin = im->yimg;
2189 im->yorigin = im->yimg - Yxlabel;
2192 /* reserve space for the title *or* some padding above the graph */
2195 im->yorigin += Ytitle;
2197 im->yimg += Yspacing;
2198 im->yorigin += Yspacing;
2200 /* reserve space for padding below the graph */
2201 im->yimg += Yspacing;
2204 /* Determine where to place the legends onto the image.
2205 ** Adjust im->yimg to match the space requirements.
2207 if(leg_place(im)==-1)
2210 /* last of three steps: check total height of image */
2211 if (im->yimg < Yvertical) im->yimg = Yvertical;
2214 if (Xlegend > im->ximg) {
2216 /* reposition Pie */
2220 /* The pie is placed in the upper right hand corner,
2221 ** just below the title (if any) and with sufficient
2225 im->pie_x = im->ximg - Xspacing - Xpie/2;
2226 im->pie_y = im->yorigin-Ymain+Ypie/2;
2228 im->pie_x = im->ximg/2;
2229 im->pie_y = im->yorigin-Ypie/2;
2235 /* draw that picture thing ... */
2237 graph_paint(image_desc_t *im, char ***calcpr)
2240 int lazy = lazy_check(im);
2242 double PieStart=0.0;
2246 double areazero = 0.0;
2247 enum gf_en stack_gf = GF_PRINT;
2248 graph_desc_t *lastgdes = NULL;
2250 /* if we are lazy and there is nothing to PRINT ... quit now */
2251 if (lazy && im->prt_c==0) return 0;
2253 /* pull the data from the rrd files ... */
2255 if(data_fetch(im)==-1)
2258 /* evaluate VDEF and CDEF operations ... */
2259 if(data_calc(im)==-1)
2262 /* check if we need to draw a piechart */
2263 for(i=0;i<im->gdes_c;i++){
2264 if (im->gdes[i].gf == GF_PART) {
2270 /* calculate and PRINT and GPRINT definitions. We have to do it at
2271 * this point because it will affect the length of the legends
2272 * if there are no graph elements we stop here ...
2273 * if we are lazy, try to quit ...
2275 i=print_calc(im,calcpr);
2277 if(((i==0)&&(piechart==0)) || lazy) return 0;
2279 /* If there's only the pie chart to draw, signal this */
2280 if (i==0) piechart=2;
2282 /* get actual drawing data and find min and max values*/
2283 if(data_proc(im)==-1)
2286 if(!im->logarithmic){si_unit(im);} /* identify si magnitude Kilo, Mega Giga ? */
2288 if(!im->rigid && ! im->logarithmic)
2289 expand_range(im); /* make sure the upper and lower limit are
2292 if (!calc_horizontal_grid(im))
2299 /**************************************************************
2300 *** Calculating sizes and locations became a bit confusing ***
2301 *** so I moved this into a separate function. ***
2302 **************************************************************/
2303 if(graph_size_location(im,i,piechart)==-1)
2306 /* the actual graph is created by going through the individual
2307 graph elements and then drawing them */
2309 node=gfx_new_area ( im->canvas,
2313 im->graph_col[GRC_BACK]);
2315 gfx_add_point(node,0, im->yimg);
2317 if (piechart != 2) {
2318 node=gfx_new_area ( im->canvas,
2319 im->xorigin, im->yorigin,
2320 im->xorigin + im->xsize, im->yorigin,
2321 im->xorigin + im->xsize, im->yorigin-im->ysize,
2322 im->graph_col[GRC_CANVAS]);
2324 gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
2326 if (im->minval > 0.0)
2327 areazero = im->minval;
2328 if (im->maxval < 0.0)
2329 areazero = im->maxval;
2333 pie_part(im,im->graph_col[GRC_CANVAS],im->pie_x,im->pie_y,im->piesize*0.5,0,2*M_PI);
2336 for(i=0;i<im->gdes_c;i++){
2337 switch(im->gdes[i].gf){
2350 for (ii = 0; ii < im->xsize; ii++)
2352 if (!isnan(im->gdes[i].p_data[ii]) &&
2353 im->gdes[i].p_data[ii] > 0.0)
2355 /* generate a tick */
2356 gfx_new_line(im->canvas, im -> xorigin + ii,
2357 im -> yorigin - (im -> gdes[i].yrule * im -> ysize),
2361 im -> gdes[i].col );
2367 stack_gf = im->gdes[i].gf;
2369 /* fix data points at oo and -oo */
2370 for(ii=0;ii<im->xsize;ii++){
2371 if (isinf(im->gdes[i].p_data[ii])){
2372 if (im->gdes[i].p_data[ii] > 0) {
2373 im->gdes[i].p_data[ii] = im->maxval ;
2375 im->gdes[i].p_data[ii] = im->minval ;
2381 if (im->gdes[i].col != 0x0){
2382 /* GF_LINE and friend */
2383 if(stack_gf == GF_LINE ){
2385 for(ii=1;ii<im->xsize;ii++){
2386 if ( ! isnan(im->gdes[i].p_data[ii-1])
2387 && ! isnan(im->gdes[i].p_data[ii])){
2389 node = gfx_new_line(im->canvas,
2390 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2391 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2392 im->gdes[i].linewidth,
2395 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2404 for(ii=1;ii<im->xsize;ii++){
2406 if ( ! isnan(im->gdes[i].p_data[ii-1])
2407 && ! isnan(im->gdes[i].p_data[ii])){
2411 if (im->gdes[i].gf == GF_STACK) {
2413 if ( (im->gdes[i].gf == GF_STACK)
2414 || (im->gdes[i].stack) ) {
2416 ybase = ytr(im,lastgdes->p_data[ii-1]);
2418 ybase = ytr(im,areazero);
2421 node = gfx_new_area(im->canvas,
2422 ii-1+im->xorigin,ybase,
2423 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2424 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2428 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2432 if ( node != NULL && (ii+1==im->xsize || isnan(im->gdes[i].p_data[ii]) )){
2433 /* GF_AREA STACK type*/
2435 if (im->gdes[i].gf == GF_STACK ) {
2437 if ( (im->gdes[i].gf == GF_STACK)
2438 || (im->gdes[i].stack) ) {
2440 for (iii=ii-1;iii>area_start;iii--){
2441 gfx_add_point(node,iii+im->xorigin,ytr(im,lastgdes->p_data[iii]));
2444 gfx_add_point(node,ii+im->xorigin,ytr(im,areazero));
2449 } /* else GF_LINE */
2450 } /* if color != 0x0 */
2451 /* make sure we do not run into trouble when stacking on NaN */
2452 for(ii=0;ii<im->xsize;ii++){
2453 if (isnan(im->gdes[i].p_data[ii])) {
2454 if (lastgdes && (im->gdes[i].gf == GF_STACK)) {
2455 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
2457 im->gdes[i].p_data[ii] = ytr(im,areazero);
2461 lastgdes = &(im->gdes[i]);
2464 if(isnan(im->gdes[i].yrule)) /* fetch variable */
2465 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2467 if (finite(im->gdes[i].yrule)) { /* even the fetched var can be NaN */
2468 pie_part(im,im->gdes[i].col,
2469 im->pie_x,im->pie_y,im->piesize*0.4,
2470 M_PI*2.0*PieStart/100.0,
2471 M_PI*2.0*(PieStart+im->gdes[i].yrule)/100.0);
2472 PieStart += im->gdes[i].yrule;
2482 if( !(im->extra_flags & ONLY_GRAPH) )
2485 /* grid_paint also does the text */
2486 if( !(im->extra_flags & ONLY_GRAPH) )
2489 /* the RULES are the last thing to paint ... */
2490 for(i=0;i<im->gdes_c;i++){
2492 switch(im->gdes[i].gf){
2494 if(isnan(im->gdes[i].yrule)) { /* fetch variable */
2495 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2497 if(im->gdes[i].yrule >= im->minval
2498 && im->gdes[i].yrule <= im->maxval)
2499 gfx_new_line(im->canvas,
2500 im->xorigin,ytr(im,im->gdes[i].yrule),
2501 im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2502 1.0,im->gdes[i].col);
2505 if(im->gdes[i].xrule == 0) { /* fetch variable */
2506 im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
2508 if(im->gdes[i].xrule >= im->start
2509 && im->gdes[i].xrule <= im->end)
2510 gfx_new_line(im->canvas,
2511 xtr(im,im->gdes[i].xrule),im->yorigin,
2512 xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2513 1.0,im->gdes[i].col);
2521 if (strcmp(im->graphfile,"-")==0) {
2522 fo = im->graphhandle ? im->graphhandle : stdout;
2524 /* Change translation mode for stdout to BINARY */
2525 _setmode( _fileno( fo ), O_BINARY );
2528 if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2529 rrd_set_error("Opening '%s' for write: %s",im->graphfile,
2530 rrd_strerror(errno));
2534 gfx_render (im->canvas,im->ximg,im->yimg,0x0,fo);
2535 if (strcmp(im->graphfile,"-") != 0)
2541 /*****************************************************
2543 *****************************************************/
2546 gdes_alloc(image_desc_t *im){
2549 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2550 * sizeof(graph_desc_t)))==NULL){
2551 rrd_set_error("realloc graph_descs");
2556 im->gdes[im->gdes_c-1].step=im->step;
2557 im->gdes[im->gdes_c-1].stack=0;
2558 im->gdes[im->gdes_c-1].debug=0;
2559 im->gdes[im->gdes_c-1].start=im->start;
2560 im->gdes[im->gdes_c-1].end=im->end;
2561 im->gdes[im->gdes_c-1].vname[0]='\0';
2562 im->gdes[im->gdes_c-1].data=NULL;
2563 im->gdes[im->gdes_c-1].ds_namv=NULL;
2564 im->gdes[im->gdes_c-1].data_first=0;
2565 im->gdes[im->gdes_c-1].p_data=NULL;
2566 im->gdes[im->gdes_c-1].rpnp=NULL;
2567 im->gdes[im->gdes_c-1].shift=0;
2568 im->gdes[im->gdes_c-1].col = 0x0;
2569 im->gdes[im->gdes_c-1].legend[0]='\0';
2570 im->gdes[im->gdes_c-1].format[0]='\0';
2571 im->gdes[im->gdes_c-1].rrd[0]='\0';
2572 im->gdes[im->gdes_c-1].ds=-1;
2573 im->gdes[im->gdes_c-1].p_data=NULL;
2574 im->gdes[im->gdes_c-1].yrule=DNAN;
2575 im->gdes[im->gdes_c-1].xrule=0;
2579 /* copies input untill the first unescaped colon is found
2580 or until input ends. backslashes have to be escaped as well */
2582 scan_for_col(char *input, int len, char *output)
2587 input[inp] != ':' &&
2590 if (input[inp] == '\\' &&
2591 input[inp+1] != '\0' &&
2592 (input[inp+1] == '\\' ||
2593 input[inp+1] == ':')){
2594 output[outp++] = input[++inp];
2597 output[outp++] = input[inp];
2600 output[outp] = '\0';
2603 /* Some surgery done on this function, it became ridiculously big.
2605 ** - initializing now in rrd_graph_init()
2606 ** - options parsing now in rrd_graph_options()
2607 ** - script parsing now in rrd_graph_script()
2610 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize, FILE *stream, double *ymin, double *ymax)
2614 rrd_graph_init(&im);
2615 im.graphhandle = stream;
2617 rrd_graph_options(argc,argv,&im);
2618 if (rrd_test_error()) {
2623 if (strlen(argv[optind])>=MAXPATH) {
2624 rrd_set_error("filename (including path) too long");
2628 strncpy(im.graphfile,argv[optind],MAXPATH-1);
2629 im.graphfile[MAXPATH-1]='\0';
2631 rrd_graph_script(argc,argv,&im,1);
2632 if (rrd_test_error()) {
2637 /* Everything is now read and the actual work can start */
2640 if (graph_paint(&im,prdata)==-1){
2645 /* The image is generated and needs to be output.
2646 ** 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;
2718 im->tabwidth = 40.0;
2720 for(i=0;i<DIM(graph_col);i++)
2721 im->graph_col[i]=graph_col[i];
2725 windir = getenv("windir");
2726 /* %windir% is something like D:\windows or C:\winnt */
2727 if (windir != NULL) {
2728 strcpy(rrd_win_default_font,windir);
2729 strcat(rrd_win_default_font,"\\fonts\\cour.ttf");
2730 for(i=0;i<DIM(text_prop);i++)
2731 strcpy(text_prop[i].font,rrd_win_default_font);
2735 for(i=0;i<DIM(text_prop);i++){
2736 im->text_prop[i].size = text_prop[i].size;
2737 strcpy(im->text_prop[i].font,text_prop[i].font);
2742 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
2745 char *parsetime_error = NULL;
2746 char scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
2747 time_t start_tmp=0,end_tmp=0;
2749 struct rrd_time_value start_tv, end_tv;
2752 parsetime("end-24h", &start_tv);
2753 parsetime("now", &end_tv);
2756 static struct option long_options[] =
2758 {"start", required_argument, 0, 's'},
2759 {"end", required_argument, 0, 'e'},
2760 {"x-grid", required_argument, 0, 'x'},
2761 {"y-grid", required_argument, 0, 'y'},
2762 {"vertical-label",required_argument,0,'v'},
2763 {"width", required_argument, 0, 'w'},
2764 {"height", required_argument, 0, 'h'},
2765 {"interlaced", no_argument, 0, 'i'},
2766 {"upper-limit",required_argument, 0, 'u'},
2767 {"lower-limit",required_argument, 0, 'l'},
2768 {"rigid", no_argument, 0, 'r'},
2769 {"base", required_argument, 0, 'b'},
2770 {"logarithmic",no_argument, 0, 'o'},
2771 {"color", required_argument, 0, 'c'},
2772 {"font", required_argument, 0, 'n'},
2773 {"title", required_argument, 0, 't'},
2774 {"imginfo", required_argument, 0, 'f'},
2775 {"imgformat", required_argument, 0, 'a'},
2776 {"lazy", no_argument, 0, 'z'},
2777 {"zoom", required_argument, 0, 'm'},
2778 {"no-legend", no_argument, 0, 'g'},
2779 {"force-rules-legend",no_argument,0, 'F'},
2780 {"only-graph", no_argument, 0, 'j'},
2781 {"alt-y-grid", no_argument, 0, 'Y'},
2782 {"no-minor", no_argument, 0, 'I'},
2783 {"alt-autoscale", no_argument, 0, 'A'},
2784 {"alt-autoscale-max", no_argument, 0, 'M'},
2785 {"units-exponent",required_argument, 0, 'X'},
2786 {"step", required_argument, 0, 'S'},
2787 {"tabwidth", required_argument, 0, 'T'},
2788 {"no-gridfit", no_argument, 0, 'N'},
2790 int option_index = 0;
2794 opt = getopt_long(argc, argv,
2795 "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:I:zgjFYAMX:S:NT:",
2796 long_options, &option_index);
2803 im->extra_flags |= NOMINOR;
2806 im->extra_flags |= ALTYGRID;
2809 im->extra_flags |= ALTAUTOSCALE;
2812 im->extra_flags |= ALTAUTOSCALE_MAX;
2815 im->extra_flags |= ONLY_GRAPH;
2818 im->extra_flags |= NOLEGEND;
2821 im->extra_flags |= FORCE_RULES_LEGEND;
2824 im->unitsexponent = atoi(optarg);
2827 im->tabwidth = atof(optarg);
2830 im->step = atoi(optarg);
2836 if ((parsetime_error = parsetime(optarg, &start_tv))) {
2837 rrd_set_error( "start time: %s", parsetime_error );
2842 if ((parsetime_error = parsetime(optarg, &end_tv))) {
2843 rrd_set_error( "end time: %s", parsetime_error );
2848 if(strcmp(optarg,"none") == 0){
2854 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
2856 &im->xlab_user.gridst,
2858 &im->xlab_user.mgridst,
2860 &im->xlab_user.labst,
2861 &im->xlab_user.precis,
2862 &stroff) == 7 && stroff != 0){
2863 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
2864 if((int)(im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
2865 rrd_set_error("unknown keyword %s",scan_gtm);
2867 } else if ((int)(im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
2868 rrd_set_error("unknown keyword %s",scan_mtm);
2870 } else if ((int)(im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
2871 rrd_set_error("unknown keyword %s",scan_ltm);
2874 im->xlab_user.minsec = 1;
2875 im->xlab_user.stst = im->xlab_form;
2877 rrd_set_error("invalid x-grid format");
2883 if(strcmp(optarg,"none") == 0){
2891 &im->ylabfact) == 2) {
2892 if(im->ygridstep<=0){
2893 rrd_set_error("grid step must be > 0");
2895 } else if (im->ylabfact < 1){
2896 rrd_set_error("label factor must be > 0");
2900 rrd_set_error("invalid y-grid format");
2905 strncpy(im->ylegend,optarg,150);
2906 im->ylegend[150]='\0';
2909 im->maxval = atof(optarg);
2912 im->minval = atof(optarg);
2915 im->base = atol(optarg);
2916 if(im->base != 1024 && im->base != 1000 ){
2917 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
2922 long_tmp = atol(optarg);
2923 if (long_tmp < 10) {
2924 rrd_set_error("width below 10 pixels");
2927 im->xsize = long_tmp;
2930 long_tmp = atol(optarg);
2931 if (long_tmp < 10) {
2932 rrd_set_error("height below 10 pixels");
2935 im->ysize = long_tmp;
2938 im->canvas->interlaced = 1;
2944 im->imginfo = optarg;
2947 if((int)(im->canvas->imgformat = if_conv(optarg)) == -1) {
2948 rrd_set_error("unsupported graphics format '%s'",optarg);
2956 im->logarithmic = 1;
2957 if (isnan(im->minval))
2963 col_nam,&color) == 2){
2965 if((ci=grc_conv(col_nam)) != -1){
2966 im->graph_col[ci]=color;
2968 rrd_set_error("invalid color name '%s'",col_nam);
2971 rrd_set_error("invalid color def format");
2982 prop,&size,font) == 3){
2984 if((sindex=text_prop_conv(prop)) != -1){
2985 im->text_prop[sindex].size=size;
2986 strcpy(im->text_prop[sindex].font,font);
2987 if (sindex==0) { /* the default */
2988 im->text_prop[TEXT_PROP_TITLE].size=size;
2989 strcpy(im->text_prop[TEXT_PROP_TITLE].font,font);
2990 im->text_prop[TEXT_PROP_AXIS].size=size;
2991 strcpy(im->text_prop[TEXT_PROP_AXIS].font,font);
2992 im->text_prop[TEXT_PROP_UNIT].size=size;
2993 strcpy(im->text_prop[TEXT_PROP_UNIT].font,font);
2994 im->text_prop[TEXT_PROP_LEGEND].size=size;
2995 strcpy(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;
3056 im->step = max((long)im->step, (im->end-im->start)/im->xsize);
3060 rrd_graph_check_vname(image_desc_t *im, char *varname, char *err)
3062 if ((im->gdes[im->gdes_c-1].vidx=find_var(im,varname))==-1) {
3063 rrd_set_error("Unknown variable '%s' in %s",varname,err);
3069 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
3072 graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
3074 color=strstr(var,"#");
3077 rrd_set_error("Found no color in %s",err);
3086 rest=strstr(color,":");
3094 sscanf(color,"#%6lx%n",&col,&n);
3095 col = (col << 8) + 0xff /* shift left by 8 */;
3096 if (n!=7) rrd_set_error("Color problem in %s",err);
3099 sscanf(color,"#%8lx%n",&col,&n);
3102 rrd_set_error("Color problem in %s",err);
3104 if (rrd_test_error()) return 0;
3111 int bad_format(char *fmt) {
3115 while (*ptr != '\0')
3116 if (*ptr++ == '%') {
3118 /* line cannot end with percent char */
3119 if (*ptr == '\0') return 1;
3121 /* '%s', '%S' and '%%' are allowed */
3122 if (*ptr == 's' || *ptr == 'S' || *ptr == '%') ptr++;
3124 /* or else '% 6.2lf' and such are allowed */
3127 /* optional padding character */
3128 if (*ptr == ' ' || *ptr == '+' || *ptr == '-') ptr++;
3130 /* This should take care of 'm.n' with all three optional */
3131 while (*ptr >= '0' && *ptr <= '9') ptr++;
3132 if (*ptr == '.') ptr++;
3133 while (*ptr >= '0' && *ptr <= '9') ptr++;
3135 /* Either 'le', 'lf' or 'lg' must follow here */
3136 if (*ptr++ != 'l') return 1;
3137 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g') ptr++;
3148 vdef_parse(gdes,str)
3149 struct graph_desc_t *gdes;
3152 /* A VDEF currently is either "func" or "param,func"
3153 * so the parsing is rather simple. Change if needed.
3160 sscanf(str,"%le,%29[A-Z]%n",¶m,func,&n);
3161 if (n== (int)strlen(str)) { /* matched */
3165 sscanf(str,"%29[A-Z]%n",func,&n);
3166 if (n== (int)strlen(str)) { /* matched */
3169 rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3176 if (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3177 else if (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3178 else if (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3179 else if (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3180 else if (!strcmp("TOTAL", func)) gdes->vf.op = VDEF_TOTAL;
3181 else if (!strcmp("FIRST", func)) gdes->vf.op = VDEF_FIRST;
3182 else if (!strcmp("LAST", func)) gdes->vf.op = VDEF_LAST;
3184 rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3191 switch (gdes->vf.op) {
3193 if (isnan(param)) { /* no parameter given */
3194 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3200 if (param>=0.0 && param<=100.0) {
3201 gdes->vf.param = param;
3202 gdes->vf.val = DNAN; /* undefined */
3203 gdes->vf.when = 0; /* undefined */
3205 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3219 gdes->vf.param = DNAN;
3220 gdes->vf.val = DNAN;
3223 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3240 graph_desc_t *src,*dst;
3244 dst = &im->gdes[gdi];
3245 src = &im->gdes[dst->vidx];
3246 data = src->data + src->ds;
3247 steps = (src->end - src->start) / src->step;
3250 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3257 switch (dst->vf.op) {
3258 case VDEF_PERCENT: {
3259 rrd_value_t * array;
3263 if ((array = malloc(steps*sizeof(double)))==NULL) {
3264 rrd_set_error("malloc VDEV_PERCENT");
3267 for (step=0;step < steps; step++) {
3268 array[step]=data[step*src->ds_cnt];
3270 qsort(array,step,sizeof(double),vdef_percent_compar);
3272 field = (steps-1)*dst->vf.param/100;
3273 dst->vf.val = array[field];
3274 dst->vf.when = 0; /* no time component */
3277 for(step=0;step<steps;step++)
3278 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3284 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3285 if (step == steps) {
3289 dst->vf.val = data[step*src->ds_cnt];
3290 dst->vf.when = src->start + (step+1)*src->step;
3292 while (step != steps) {
3293 if (finite(data[step*src->ds_cnt])) {
3294 if (data[step*src->ds_cnt] > dst->vf.val) {
3295 dst->vf.val = data[step*src->ds_cnt];
3296 dst->vf.when = src->start + (step+1)*src->step;
3303 case VDEF_AVERAGE: {
3306 for (step=0;step<steps;step++) {
3307 if (finite(data[step*src->ds_cnt])) {
3308 sum += data[step*src->ds_cnt];
3313 if (dst->vf.op == VDEF_TOTAL) {
3314 dst->vf.val = sum*src->step;
3315 dst->vf.when = cnt*src->step; /* not really "when" */
3317 dst->vf.val = sum/cnt;
3318 dst->vf.when = 0; /* no time component */
3328 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3329 if (step == steps) {
3333 dst->vf.val = data[step*src->ds_cnt];
3334 dst->vf.when = src->start + (step+1)*src->step;
3336 while (step != steps) {
3337 if (finite(data[step*src->ds_cnt])) {
3338 if (data[step*src->ds_cnt] < dst->vf.val) {
3339 dst->vf.val = data[step*src->ds_cnt];
3340 dst->vf.when = src->start + (step+1)*src->step;
3347 /* The time value returned here is one step before the
3348 * actual time value. This is the start of the first
3352 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3353 if (step == steps) { /* all entries were NaN */
3357 dst->vf.val = data[step*src->ds_cnt];
3358 dst->vf.when = src->start + step*src->step;
3362 /* The time value returned here is the
3363 * actual time value. This is the end of the last
3367 while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3368 if (step < 0) { /* all entries were NaN */
3372 dst->vf.val = data[step*src->ds_cnt];
3373 dst->vf.when = src->start + (step+1)*src->step;
3380 /* NaN < -INF < finite_values < INF */
3382 vdef_percent_compar(a,b)
3385 /* Equality is not returned; this doesn't hurt except
3386 * (maybe) for a little performance.
3389 /* First catch NaN values. They are smallest */
3390 if (isnan( *(double *)a )) return -1;
3391 if (isnan( *(double *)b )) return 1;
3393 /* NaN doesn't reach this part so INF and -INF are extremes.
3394 * The sign from isinf() is compatible with the sign we return
3396 if (isinf( *(double *)a )) return isinf( *(double *)a );
3397 if (isinf( *(double *)b )) return isinf( *(double *)b );
3399 /* If we reach this, both values must be finite */
3400 if ( *(double *)a < *(double *)b ) return -1; else return 1;