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 */
30 #ifndef RRD_DEFAULT_FONT
32 #define RRD_DEFAULT_FONT "c:/winnt/fonts/COUR.TTF"
34 #define RRD_DEFAULT_FONT "/usr/share/fonts/truetype/openoffice/ariosor.ttf"
35 /* #define RRD_DEFAULT_FONT "/usr/share/fonts/truetype/Arial.ttf" */
40 text_prop_t text_prop[] = {
41 { 10.0, RRD_DEFAULT_FONT }, /* default */
42 { 12.0, RRD_DEFAULT_FONT }, /* title */
43 { 8.0, RRD_DEFAULT_FONT }, /* axis */
44 { 10.0, RRD_DEFAULT_FONT }, /* unit */
45 { 10.0, RRD_DEFAULT_FONT } /* legend */
49 {0, TMT_SECOND,30, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
50 {2, TMT_MINUTE,1, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
51 {5, TMT_MINUTE,2, TMT_MINUTE,10, TMT_MINUTE,10, 0,"%H:%M"},
52 {10, TMT_MINUTE,5, TMT_MINUTE,20, TMT_MINUTE,20, 0,"%H:%M"},
53 {30, TMT_MINUTE,10, TMT_HOUR,1, TMT_HOUR,1, 0,"%H:%M"},
54 {60, TMT_MINUTE,30, TMT_HOUR,2, TMT_HOUR,2, 0,"%H:%M"},
55 {180, TMT_HOUR,1, TMT_HOUR,6, TMT_HOUR,6, 0,"%H:%M"},
56 /*{300, TMT_HOUR,3, TMT_HOUR,12, TMT_HOUR,12, 12*3600,"%a %p"}, this looks silly*/
57 {600, TMT_HOUR,6, TMT_DAY,1, TMT_DAY,1, 24*3600,"%a"},
58 {1800, TMT_HOUR,12, TMT_DAY,1, TMT_DAY,2, 24*3600,"%a"},
59 {3600, TMT_DAY,1, TMT_WEEK,1, TMT_WEEK,1, 7*24*3600,"Week %V"},
60 {3*3600, TMT_WEEK,1, TMT_MONTH,1, TMT_WEEK,2, 7*24*3600,"Week %V"},
61 {6*3600, TMT_MONTH,1, TMT_MONTH,1, TMT_MONTH,1, 30*24*3600,"%b"},
62 {48*3600, TMT_MONTH,1, TMT_MONTH,3, TMT_MONTH,3, 30*24*3600,"%b"},
63 {10*24*3600, TMT_YEAR,1, TMT_YEAR,1, TMT_YEAR,1, 365*24*3600,"%y"},
64 {-1,TMT_MONTH,0,TMT_MONTH,0,TMT_MONTH,0,0,""}
67 /* sensible logarithmic y label intervals ...
68 the first element of each row defines the possible starting points on the
69 y axis ... the other specify the */
71 double yloglab[][12]= {{ 1e9, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
72 { 1e3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
73 { 1e1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
74 /* { 1e1, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, */
75 { 1e1, 1, 2.5, 5, 7.5, 0, 0, 0, 0, 0, 0, 0 },
76 { 1e1, 1, 2, 4, 6, 8, 0, 0, 0, 0, 0, 0 },
77 { 1e1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0 },
78 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }};
80 /* sensible y label intervals ...*/
98 gfx_color_t graph_col[] = /* default colors */
99 { 0xFFFFFFFF, /* canvas */
100 0xF0F0F0FF, /* background */
101 0xD0D0D0FF, /* shade A */
102 0xA0A0A0FF, /* shade B */
103 0x909090FF, /* grid */
104 0xE05050FF, /* major grid */
105 0x000000FF, /* font */
106 0x000000FF, /* frame */
107 0xFF0000FF /* arrow */
114 # define DPRINT(x) (void)(printf x, printf("\n"))
120 /* initialize with xtr(im,0); */
122 xtr(image_desc_t *im,time_t mytime){
125 pixie = (double) im->xsize / (double)(im->end - im->start);
128 return (int)((double)im->xorigin
129 + pixie * ( mytime - im->start ) );
132 /* translate data values into y coordinates */
134 ytr(image_desc_t *im, double value){
139 pixie = (double) im->ysize / (im->maxval - im->minval);
141 pixie = (double) im->ysize / (log10(im->maxval) - log10(im->minval));
143 } else if(!im->logarithmic) {
144 yval = im->yorigin - pixie * (value - im->minval);
146 if (value < im->minval) {
149 yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
152 /* make sure we don't return anything too unreasonable. GD lib can
153 get terribly slow when drawing lines outside its scope. This is
154 especially problematic in connection with the rigid option */
156 /* keep yval as-is */
157 } else if (yval > im->yorigin) {
158 yval = im->yorigin+2;
159 } else if (yval < im->yorigin - im->ysize){
160 yval = im->yorigin - im->ysize - 2;
167 /* conversion function for symbolic entry names */
170 #define conv_if(VV,VVV) \
171 if (strcmp(#VV, string) == 0) return VVV ;
173 enum gf_en gf_conv(char *string){
175 conv_if(PRINT,GF_PRINT)
176 conv_if(GPRINT,GF_GPRINT)
177 conv_if(COMMENT,GF_COMMENT)
178 conv_if(HRULE,GF_HRULE)
179 conv_if(VRULE,GF_VRULE)
180 conv_if(LINE,GF_LINE)
181 conv_if(AREA,GF_AREA)
182 conv_if(STACK,GF_STACK)
183 conv_if(TICK,GF_TICK)
185 conv_if(CDEF,GF_CDEF)
186 conv_if(VDEF,GF_VDEF)
187 conv_if(PART,GF_PART)
188 conv_if(XPORT,GF_XPORT)
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(FRAME,GRC_FRAME)
225 conv_if(ARROW,GRC_ARROW)
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;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<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].start == im->gdes[ii].start)
699 && (im->gdes[i].end == im->gdes[ii].end)
700 && (im->gdes[i].step == im->gdes[ii].step)) {
701 /* OK, the data is already there.
702 ** Just copy the header portion
704 im->gdes[i].start = im->gdes[ii].start;
705 im->gdes[i].end = im->gdes[ii].end;
706 im->gdes[i].step = im->gdes[ii].step;
707 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
708 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
709 im->gdes[i].data = im->gdes[ii].data;
710 im->gdes[i].data_first = 0;
717 unsigned long ft_step = im->gdes[i].step ;
719 if((rrd_fetch_fn(im->gdes[i].rrd,
725 &im->gdes[i].ds_namv,
726 &im->gdes[i].data)) == -1){
729 im->gdes[i].data_first = 1;
731 if (ft_step < im->gdes[i].step) {
732 reduce_data(im->gdes[i].cf,
740 im->gdes[i].step = ft_step;
744 /* lets see if the required data source is realy there */
745 for(ii=0;ii<im->gdes[i].ds_cnt;ii++){
746 if(strcmp(im->gdes[i].ds_namv[ii],im->gdes[i].ds_nam) == 0){
749 if (im->gdes[i].ds== -1){
750 rrd_set_error("No DS called '%s' in '%s'",
751 im->gdes[i].ds_nam,im->gdes[i].rrd);
759 /* evaluate the expressions in the CDEF functions */
761 /*************************************************************
763 *************************************************************/
766 find_var_wrapper(void *arg1, char *key)
768 return find_var((image_desc_t *) arg1, key);
771 /* find gdes containing var*/
773 find_var(image_desc_t *im, char *key){
775 for(ii=0;ii<im->gdes_c-1;ii++){
776 if((im->gdes[ii].gf == GF_DEF
777 || im->gdes[ii].gf == GF_VDEF
778 || im->gdes[ii].gf == GF_CDEF)
779 && (strcmp(im->gdes[ii].vname,key) == 0)){
786 /* find the largest common denominator for all the numbers
787 in the 0 terminated num array */
792 for (i=0;num[i+1]!=0;i++){
794 rest=num[i] % num[i+1];
795 num[i]=num[i+1]; num[i+1]=rest;
799 /* return i==0?num[i]:num[i-1]; */
803 /* run the rpn calculator on all the VDEF and CDEF arguments */
805 data_calc( image_desc_t *im){
809 long *steparray, rpi;
814 rpnstack_init(&rpnstack);
816 for (gdi=0;gdi<im->gdes_c;gdi++){
817 /* Look for GF_VDEF and GF_CDEF in the same loop,
818 * so CDEFs can use VDEFs and vice versa
820 switch (im->gdes[gdi].gf) {
824 /* A VDEF has no DS. This also signals other parts
825 * of rrdtool that this is a VDEF value, not a CDEF.
827 im->gdes[gdi].ds_cnt = 0;
828 if (vdef_calc(im,gdi)) {
829 rrd_set_error("Error processing VDEF '%s'"
832 rpnstack_free(&rpnstack);
837 im->gdes[gdi].ds_cnt = 1;
838 im->gdes[gdi].ds = 0;
839 im->gdes[gdi].data_first = 1;
840 im->gdes[gdi].start = 0;
841 im->gdes[gdi].end = 0;
846 /* Find the variables in the expression.
847 * - VDEF variables are substituted by their values
848 * and the opcode is changed into OP_NUMBER.
849 * - CDEF variables are analized for their step size,
850 * the lowest common denominator of all the step
851 * sizes of the data sources involved is calculated
852 * and the resulting number is the step size for the
853 * resulting data source.
855 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
856 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
857 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER){
858 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
859 if (im->gdes[ptr].ds_cnt == 0) {
861 printf("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
863 im->gdes[ptr].vname);
864 printf("DEBUG: value from vdef is %f\n",im->gdes[ptr].vf.val);
866 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
867 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
870 rrd_realloc(steparray,
871 (++stepcnt+1)*sizeof(*steparray)))==NULL){
872 rrd_set_error("realloc steparray");
873 rpnstack_free(&rpnstack);
877 steparray[stepcnt-1] = im->gdes[ptr].step;
879 /* adjust start and end of cdef (gdi) so
880 * that it runs from the latest start point
881 * to the earliest endpoint of any of the
882 * rras involved (ptr)
884 if(im->gdes[gdi].start < im->gdes[ptr].start)
885 im->gdes[gdi].start = im->gdes[ptr].start;
887 if(im->gdes[gdi].end == 0 ||
888 im->gdes[gdi].end > im->gdes[ptr].end)
889 im->gdes[gdi].end = im->gdes[ptr].end;
891 /* store pointer to the first element of
892 * the rra providing data for variable,
893 * further save step size and data source
896 im->gdes[gdi].rpnp[rpi].data = im->gdes[ptr].data + im->gdes[ptr].ds;
897 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
898 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
900 /* backoff the *.data ptr; this is done so
901 * rpncalc() function doesn't have to treat
902 * the first case differently
904 } /* if ds_cnt != 0 */
905 } /* if OP_VARIABLE */
906 } /* loop through all rpi */
908 /* move the data pointers to the correct period */
909 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
910 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
911 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER){
912 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
913 if(im->gdes[gdi].start > im->gdes[ptr].start) {
914 im->gdes[gdi].rpnp[rpi].data += im->gdes[gdi].rpnp[rpi].ds_cnt;
920 if(steparray == NULL){
921 rrd_set_error("rpn expressions without DEF"
922 " or CDEF variables are not supported");
923 rpnstack_free(&rpnstack);
926 steparray[stepcnt]=0;
927 /* Now find the resulting step. All steps in all
928 * used RRAs have to be visited
930 im->gdes[gdi].step = lcd(steparray);
932 if((im->gdes[gdi].data = malloc((
933 (im->gdes[gdi].end-im->gdes[gdi].start)
934 / im->gdes[gdi].step)
935 * sizeof(double)))==NULL){
936 rrd_set_error("malloc im->gdes[gdi].data");
937 rpnstack_free(&rpnstack);
941 /* Step through the new cdef results array and
942 * calculate the values
944 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
945 now<=im->gdes[gdi].end;
946 now += im->gdes[gdi].step)
948 rpnp_t *rpnp = im -> gdes[gdi].rpnp;
950 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
951 * in this case we are advancing by timesteps;
952 * we use the fact that time_t is a synonym for long
954 if (rpn_calc(rpnp,&rpnstack,(long) now,
955 im->gdes[gdi].data,++dataidx) == -1) {
956 /* rpn_calc sets the error string */
957 rpnstack_free(&rpnstack);
960 } /* enumerate over time steps within a CDEF */
965 } /* enumerate over CDEFs */
966 rpnstack_free(&rpnstack);
970 /* massage data so, that we get one value for each x coordinate in the graph */
972 data_proc( image_desc_t *im ){
974 double pixstep = (double)(im->end-im->start)
975 /(double)im->xsize; /* how much time
976 passes in one pixel */
978 double minval=DNAN,maxval=DNAN;
980 unsigned long gr_time;
982 /* memory for the processed data */
983 for(i=0;i<im->gdes_c;i++) {
984 if((im->gdes[i].gf==GF_LINE) ||
985 (im->gdes[i].gf==GF_AREA) ||
986 (im->gdes[i].gf==GF_TICK) ||
987 (im->gdes[i].gf==GF_STACK)) {
988 if((im->gdes[i].p_data = malloc((im->xsize +1)
989 * sizeof(rrd_value_t)))==NULL){
990 rrd_set_error("malloc data_proc");
996 for (i=0;i<im->xsize;i++) { /* for each pixel */
998 gr_time = im->start+pixstep*i; /* time of the current step */
1001 for (ii=0;ii<im->gdes_c;ii++) {
1003 switch (im->gdes[ii].gf) {
1007 if (!im->gdes[ii].stack)
1010 value = im->gdes[ii].yrule;
1011 if (isnan(value)) { /* not a number or VDEF */
1012 /* The time of the data doesn't necessarily match
1013 ** the time of the graph. Beware.
1015 vidx = im->gdes[ii].vidx;
1016 if ( (gr_time >= im->gdes[vidx].start) &&
1017 (gr_time <= im->gdes[vidx].end) ) {
1018 value = im->gdes[vidx].data[
1019 (unsigned long) floor(
1020 (double)(gr_time - im->gdes[vidx].start)
1021 / im->gdes[vidx].step)
1022 * im->gdes[vidx].ds_cnt
1030 if (! isnan(value)) {
1032 im->gdes[ii].p_data[i] = paintval;
1033 /* GF_TICK: the data values are not
1034 ** relevant for min and max
1036 if (finite(paintval) && im->gdes[ii].gf != GF_TICK ) {
1037 if (isnan(minval) || paintval < minval)
1039 if (isnan(maxval) || paintval > maxval)
1043 im->gdes[ii].p_data[i] = DNAN;
1052 /* if min or max have not been asigned a value this is because
1053 there was no data in the graph ... this is not good ...
1054 lets set these to dummy values then ... */
1056 if (isnan(minval)) minval = 0.0;
1057 if (isnan(maxval)) maxval = 1.0;
1059 /* adjust min and max values */
1060 if (isnan(im->minval)
1061 /* don't adjust low-end with log scale */
1062 || ((!im->logarithmic && !im->rigid) && im->minval > minval)
1064 im->minval = minval;
1065 if (isnan(im->maxval)
1066 || (!im->rigid && im->maxval < maxval)
1068 if (im->logarithmic)
1069 im->maxval = maxval * 1.1;
1071 im->maxval = maxval;
1073 /* make sure min and max are not equal */
1074 if (im->minval == im->maxval) {
1076 if (! im->logarithmic) {
1079 /* make sure min and max are not both zero */
1080 if (im->maxval == 0.0) {
1089 /* identify the point where the first gridline, label ... gets placed */
1093 time_t start, /* what is the initial time */
1094 enum tmt_en baseint, /* what is the basic interval */
1095 long basestep /* how many if these do we jump a time */
1099 tm = *localtime(&start);
1102 tm.tm_sec -= tm.tm_sec % basestep; break;
1105 tm.tm_min -= tm.tm_min % basestep;
1110 tm.tm_hour -= tm.tm_hour % basestep; break;
1112 /* we do NOT look at the basestep for this ... */
1115 tm.tm_hour = 0; break;
1117 /* we do NOT look at the basestep for this ... */
1121 tm.tm_mday -= tm.tm_wday -1; /* -1 because we want the monday */
1122 if (tm.tm_wday==0) tm.tm_mday -= 7; /* we want the *previous* monday */
1129 tm.tm_mon -= tm.tm_mon % basestep; break;
1137 tm.tm_year -= (tm.tm_year+1900) % basestep;
1142 /* identify the point where the next gridline, label ... gets placed */
1145 time_t current, /* what is the initial time */
1146 enum tmt_en baseint, /* what is the basic interval */
1147 long basestep /* how many if these do we jump a time */
1152 tm = *localtime(¤t);
1156 tm.tm_sec += basestep; break;
1158 tm.tm_min += basestep; break;
1160 tm.tm_hour += basestep; break;
1162 tm.tm_mday += basestep; break;
1164 tm.tm_mday += 7*basestep; break;
1166 tm.tm_mon += basestep; break;
1168 tm.tm_year += basestep;
1170 madetime = mktime(&tm);
1171 } while (madetime == -1); /* this is necessary to skip impssible times
1172 like the daylight saving time skips */
1178 /* calculate values required for PRINT and GPRINT functions */
1181 print_calc(image_desc_t *im, char ***prdata)
1183 long i,ii,validsteps;
1186 int graphelement = 0;
1189 double magfact = -1;
1193 if (im->imginfo) prlines++;
1194 for(i=0;i<im->gdes_c;i++){
1195 switch(im->gdes[i].gf){
1198 if(((*prdata) = rrd_realloc((*prdata),prlines*sizeof(char *)))==NULL){
1199 rrd_set_error("realloc prdata");
1203 /* PRINT and GPRINT can now print VDEF generated values.
1204 * There's no need to do any calculations on them as these
1205 * calculations were already made.
1207 vidx = im->gdes[i].vidx;
1208 if (im->gdes[vidx].gf==GF_VDEF) { /* simply use vals */
1209 printval = im->gdes[vidx].vf.val;
1210 printtime = im->gdes[vidx].vf.when;
1211 } else { /* need to calculate max,min,avg etcetera */
1212 max_ii =((im->gdes[vidx].end
1213 - im->gdes[vidx].start)
1214 / im->gdes[vidx].step
1215 * im->gdes[vidx].ds_cnt);
1218 for( ii=im->gdes[vidx].ds;
1220 ii+=im->gdes[vidx].ds_cnt){
1221 if (! finite(im->gdes[vidx].data[ii]))
1223 if (isnan(printval)){
1224 printval = im->gdes[vidx].data[ii];
1229 switch (im->gdes[i].cf){
1232 case CF_DEVSEASONAL:
1236 printval += im->gdes[vidx].data[ii];
1239 printval = min( printval, im->gdes[vidx].data[ii]);
1243 printval = max( printval, im->gdes[vidx].data[ii]);
1246 printval = im->gdes[vidx].data[ii];
1249 if (im->gdes[i].cf==CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1250 if (validsteps > 1) {
1251 printval = (printval / validsteps);
1254 } /* prepare printval */
1256 if (!strcmp(im->gdes[i].format,"%c")) { /* VDEF time print */
1257 if (im->gdes[i].gf == GF_PRINT){
1258 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1259 sprintf((*prdata)[prlines-2],"%s (%lu)",
1260 ctime(&printtime),printtime);
1261 (*prdata)[prlines-1] = NULL;
1263 sprintf(im->gdes[i].legend,"%s (%lu)",
1264 ctime(&printtime),printtime);
1268 if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) {
1269 /* Magfact is set to -1 upon entry to print_calc. If it
1270 * is still less than 0, then we need to run auto_scale.
1271 * Otherwise, put the value into the correct units. If
1272 * the value is 0, then do not set the symbol or magnification
1273 * so next the calculation will be performed again. */
1274 if (magfact < 0.0) {
1275 auto_scale(im,&printval,&si_symb,&magfact);
1276 if (printval == 0.0)
1279 printval /= magfact;
1281 *(++percent_s) = 's';
1282 } else if (strstr(im->gdes[i].format,"%s") != NULL) {
1283 auto_scale(im,&printval,&si_symb,&magfact);
1286 if (im->gdes[i].gf == GF_PRINT){
1287 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1288 (*prdata)[prlines-1] = NULL;
1289 if (bad_format(im->gdes[i].format)) {
1290 rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1293 #ifdef HAVE_SNPRINTF
1294 snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
1296 sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
1301 if (bad_format(im->gdes[i].format)) {
1302 rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1305 #ifdef HAVE_SNPRINTF
1306 snprintf(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
1308 sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
1331 return graphelement;
1335 /* place legends with color spots */
1337 leg_place(image_desc_t *im)
1340 int interleg = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1341 int box =im->text_prop[TEXT_PROP_LEGEND].size*1.5;
1342 int border = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1343 int fill=0, fill_last;
1345 int leg_x = border, leg_y = im->yimg;
1349 char prt_fctn; /*special printfunctions */
1352 if( !(im->extra_flags & NOLEGEND) ) {
1353 if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1354 rrd_set_error("malloc for legspace");
1358 for(i=0;i<im->gdes_c;i++){
1361 leg_cc = strlen(im->gdes[i].legend);
1363 /* is there a controle code ant the end of the legend string ? */
1364 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\') {
1365 prt_fctn = im->gdes[i].legend[leg_cc-1];
1367 im->gdes[i].legend[leg_cc] = '\0';
1371 /* remove exess space */
1372 while (prt_fctn=='g' &&
1374 im->gdes[i].legend[leg_cc-1]==' '){
1376 im->gdes[i].legend[leg_cc]='\0';
1379 legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1382 /* no interleg space if string ends in \g */
1383 fill += legspace[i];
1385 if (im->gdes[i].gf != GF_GPRINT &&
1386 im->gdes[i].gf != GF_COMMENT) {
1389 fill += gfx_get_text_width(im->canvas, fill+border,
1390 im->text_prop[TEXT_PROP_LEGEND].font,
1391 im->text_prop[TEXT_PROP_LEGEND].size,
1393 im->gdes[i].legend);
1398 /* who said there was a special tag ... ?*/
1399 if (prt_fctn=='g') {
1402 if (prt_fctn == '\0') {
1403 if (i == im->gdes_c -1 ) prt_fctn ='l';
1405 /* is it time to place the legends ? */
1406 if (fill > im->ximg - 2*border){
1421 if (prt_fctn != '\0'){
1423 if (leg_c >= 2 && prt_fctn == 'j') {
1424 glue = (im->ximg - fill - 2* border) / (leg_c-1);
1428 if (prt_fctn =='c') leg_x = (im->ximg - fill) / 2.0;
1429 if (prt_fctn =='r') leg_x = im->ximg - fill - border;
1431 for(ii=mark;ii<=i;ii++){
1432 if(im->gdes[ii].legend[0]=='\0')
1434 im->gdes[ii].leg_x = leg_x;
1435 im->gdes[ii].leg_y = leg_y;
1437 gfx_get_text_width(im->canvas, leg_x,
1438 im->text_prop[TEXT_PROP_LEGEND].font,
1439 im->text_prop[TEXT_PROP_LEGEND].size,
1441 im->gdes[ii].legend)
1444 if (im->gdes[ii].gf != GF_GPRINT &&
1445 im->gdes[ii].gf != GF_COMMENT)
1448 leg_y = leg_y + im->text_prop[TEXT_PROP_LEGEND].size*1.2;
1449 if (prt_fctn == 's') leg_y -= im->text_prop[TEXT_PROP_LEGEND].size*1.2;
1461 /* create a grid on the graph. it determines what to do
1462 from the values of xsize, start and end */
1464 /* the xaxis labels are determined from the number of seconds per pixel
1465 in the requested graph */
1470 calc_horizontal_grid(image_desc_t *im)
1476 int decimals, fractionals;
1478 im->ygrid_scale.labfact=2;
1480 range = im->maxval - im->minval;
1481 scaledrange = range / im->magfact;
1483 /* does the scale of this graph make it impossible to put lines
1484 on it? If so, give up. */
1485 if (isnan(scaledrange)) {
1489 /* find grid spaceing */
1491 if(isnan(im->ygridstep)){
1492 if(im->extra_flags & ALTYGRID) {
1493 /* find the value with max number of digits. Get number of digits */
1494 decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))));
1495 if(decimals <= 0) /* everything is small. make place for zero */
1498 fractionals = floor(log10(range));
1499 if(fractionals < 0) /* small amplitude. */
1500 sprintf(im->ygrid_scale.labfmt, "%%%d.%df", decimals - fractionals + 1, -fractionals + 1);
1502 sprintf(im->ygrid_scale.labfmt, "%%%d.1f", decimals + 1);
1503 im->ygrid_scale.gridstep = pow((double)10, (double)fractionals);
1504 if(im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1505 im->ygrid_scale.gridstep = 0.1;
1506 /* should have at least 5 lines but no more then 15 */
1507 if(range/im->ygrid_scale.gridstep < 5)
1508 im->ygrid_scale.gridstep /= 10;
1509 if(range/im->ygrid_scale.gridstep > 15)
1510 im->ygrid_scale.gridstep *= 10;
1511 if(range/im->ygrid_scale.gridstep > 5) {
1512 im->ygrid_scale.labfact = 1;
1513 if(range/im->ygrid_scale.gridstep > 8)
1514 im->ygrid_scale.labfact = 2;
1517 im->ygrid_scale.gridstep /= 5;
1518 im->ygrid_scale.labfact = 5;
1522 for(i=0;ylab[i].grid > 0;i++){
1523 pixel = im->ysize / (scaledrange / ylab[i].grid);
1524 if (gridind == -1 && pixel > 5) {
1531 if (pixel * ylab[gridind].lfac[i] >= 2 * im->text_prop[TEXT_PROP_AXIS].size) {
1532 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1537 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1540 im->ygrid_scale.gridstep = im->ygridstep;
1541 im->ygrid_scale.labfact = im->ylabfact;
1546 int draw_horizontal_grid(image_desc_t *im)
1550 char graph_label[100];
1551 double X0=im->xorigin;
1552 double X1=im->xorigin+im->xsize;
1554 int sgrid = (int)( im->minval / im->ygrid_scale.gridstep - 1);
1555 int egrid = (int)( im->maxval / im->ygrid_scale.gridstep + 1);
1556 scaledstep = im->ygrid_scale.gridstep/im->magfact;
1557 for (i = sgrid; i <= egrid; i++){
1558 double Y0=ytr(im,im->ygrid_scale.gridstep*i);
1559 if ( Y0 >= im->yorigin-im->ysize
1560 && Y0 <= im->yorigin){
1561 if(i % im->ygrid_scale.labfact == 0){
1562 if (i==0 || im->symbol == ' ') {
1564 if(im->extra_flags & ALTYGRID) {
1565 sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*i);
1568 sprintf(graph_label,"%4.1f",scaledstep*i);
1571 sprintf(graph_label,"%4.0f",scaledstep*i);
1575 sprintf(graph_label,"%4.1f %c",scaledstep*i, im->symbol);
1577 sprintf(graph_label,"%4.0f %c",scaledstep*i, im->symbol);
1581 gfx_new_text ( im->canvas,
1582 X0-im->text_prop[TEXT_PROP_AXIS].size/1.5, Y0,
1583 im->graph_col[GRC_FONT],
1584 im->text_prop[TEXT_PROP_AXIS].font,
1585 im->text_prop[TEXT_PROP_AXIS].size,
1586 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1588 gfx_new_dashed_line ( im->canvas,
1591 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1592 im->grid_dash_on, im->grid_dash_off);
1595 gfx_new_dashed_line ( im->canvas,
1598 GRIDWIDTH, im->graph_col[GRC_GRID],
1599 im->grid_dash_on, im->grid_dash_off);
1607 /* logaritmic horizontal grid */
1609 horizontal_log_grid(image_desc_t *im)
1613 int minoridx=0, majoridx=0;
1614 char graph_label[100];
1616 double value, pixperstep, minstep;
1618 /* find grid spaceing */
1619 pixpex= (double)im->ysize / (log10(im->maxval) - log10(im->minval));
1621 if (isnan(pixpex)) {
1625 for(i=0;yloglab[i][0] > 0;i++){
1626 minstep = log10(yloglab[i][0]);
1627 for(ii=1;yloglab[i][ii+1] > 0;ii++){
1628 if(yloglab[i][ii+2]==0){
1629 minstep = log10(yloglab[i][ii+1])-log10(yloglab[i][ii]);
1633 pixperstep = pixpex * minstep;
1634 if(pixperstep > 5){minoridx = i;}
1635 if(pixperstep > 2 * im->text_prop[TEXT_PROP_LEGEND].size){majoridx = i;}
1639 X1=im->xorigin+im->xsize;
1640 /* paint minor grid */
1641 for (value = pow((double)10, log10(im->minval)
1642 - fmod(log10(im->minval),log10(yloglab[minoridx][0])));
1643 value <= im->maxval;
1644 value *= yloglab[minoridx][0]){
1645 if (value < im->minval) continue;
1647 while(yloglab[minoridx][++i] > 0){
1648 Y0 = ytr(im,value * yloglab[minoridx][i]);
1649 if (Y0 <= im->yorigin - im->ysize) break;
1650 gfx_new_dashed_line ( im->canvas,
1653 GRIDWIDTH, im->graph_col[GRC_GRID],
1654 im->grid_dash_on, im->grid_dash_off);
1658 /* paint major grid and labels*/
1659 for (value = pow((double)10, log10(im->minval)
1660 - fmod(log10(im->minval),log10(yloglab[majoridx][0])));
1661 value <= im->maxval;
1662 value *= yloglab[majoridx][0]){
1663 if (value < im->minval) continue;
1665 while(yloglab[majoridx][++i] > 0){
1666 Y0 = ytr(im,value * yloglab[majoridx][i]);
1667 if (Y0 <= im->yorigin - im->ysize) break;
1668 gfx_new_dashed_line ( im->canvas,
1671 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1672 im->grid_dash_on, im->grid_dash_off);
1674 sprintf(graph_label,"%3.0e",value * yloglab[majoridx][i]);
1675 gfx_new_text ( im->canvas,
1676 X0-im->text_prop[TEXT_PROP_AXIS].size/1.5, Y0,
1677 im->graph_col[GRC_FONT],
1678 im->text_prop[TEXT_PROP_AXIS].font,
1679 im->text_prop[TEXT_PROP_AXIS].size,
1680 im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1692 int xlab_sel; /* which sort of label and grid ? */
1693 time_t ti, tilab, timajor;
1695 char graph_label[100];
1696 double X0,Y0,Y1; /* points for filled graph and more*/
1699 /* the type of time grid is determined by finding
1700 the number of seconds per pixel in the graph */
1703 if(im->xlab_user.minsec == -1){
1704 factor=(im->end - im->start)/im->xsize;
1706 while ( xlab[xlab_sel+1].minsec != -1
1707 && xlab[xlab_sel+1].minsec <= factor){ xlab_sel++; }
1708 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
1709 im->xlab_user.gridst = xlab[xlab_sel].gridst;
1710 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
1711 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
1712 im->xlab_user.labtm = xlab[xlab_sel].labtm;
1713 im->xlab_user.labst = xlab[xlab_sel].labst;
1714 im->xlab_user.precis = xlab[xlab_sel].precis;
1715 im->xlab_user.stst = xlab[xlab_sel].stst;
1718 /* y coords are the same for every line ... */
1720 Y1 = im->yorigin-im->ysize;
1723 /* paint the minor grid */
1724 for(ti = find_first_time(im->start,
1725 im->xlab_user.gridtm,
1726 im->xlab_user.gridst),
1727 timajor = find_first_time(im->start,
1728 im->xlab_user.mgridtm,
1729 im->xlab_user.mgridst);
1731 ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
1733 /* are we inside the graph ? */
1734 if (ti < im->start || ti > im->end) continue;
1735 while (timajor < ti) {
1736 timajor = find_next_time(timajor,
1737 im->xlab_user.mgridtm, im->xlab_user.mgridst);
1739 if (ti == timajor) continue; /* skip as falls on major grid line */
1741 gfx_new_dashed_line(im->canvas,X0,Y0+1, X0,Y1-1,GRIDWIDTH,
1742 im->graph_col[GRC_GRID],
1743 im->grid_dash_on, im->grid_dash_off);
1747 /* paint the major grid */
1748 for(ti = find_first_time(im->start,
1749 im->xlab_user.mgridtm,
1750 im->xlab_user.mgridst);
1752 ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
1754 /* are we inside the graph ? */
1755 if (ti < im->start || ti > im->end) continue;
1757 gfx_new_dashed_line(im->canvas,X0,Y0+3, X0,Y1-2,MGRIDWIDTH,
1758 im->graph_col[GRC_MGRID],
1759 im->grid_dash_on, im->grid_dash_off);
1762 /* paint the labels below the graph */
1763 for(ti = find_first_time(im->start,
1764 im->xlab_user.labtm,
1765 im->xlab_user.labst);
1767 ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
1769 tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
1770 /* are we inside the graph ? */
1771 if (ti < im->start || ti > im->end) continue;
1774 strftime(graph_label,99,im->xlab_user.stst,localtime(&tilab));
1776 # error "your libc has no strftime I guess we'll abort the exercise here."
1778 gfx_new_text ( im->canvas,
1779 xtr(im,tilab), Y0+im->text_prop[TEXT_PROP_AXIS].size/1.5,
1780 im->graph_col[GRC_FONT],
1781 im->text_prop[TEXT_PROP_AXIS].font,
1782 im->text_prop[TEXT_PROP_AXIS].size,
1783 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP,
1796 /* draw x and y axis */
1797 gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
1798 im->xorigin+im->xsize,im->yorigin-im->ysize,
1799 GRIDWIDTH, im->graph_col[GRC_GRID]);
1801 gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
1802 im->xorigin+im->xsize,im->yorigin-im->ysize,
1803 GRIDWIDTH, im->graph_col[GRC_GRID]);
1805 gfx_new_line ( im->canvas, im->xorigin-4,im->yorigin,
1806 im->xorigin+im->xsize+4,im->yorigin,
1807 MGRIDWIDTH, im->graph_col[GRC_GRID]);
1809 gfx_new_line ( im->canvas, im->xorigin,im->yorigin+4,
1810 im->xorigin,im->yorigin-im->ysize-4,
1811 MGRIDWIDTH, im->graph_col[GRC_GRID]);
1814 /* arrow for X axis direction */
1815 gfx_new_area ( im->canvas,
1816 im->xorigin+im->xsize+3, im->yorigin-3,
1817 im->xorigin+im->xsize+3, im->yorigin+4,
1818 im->xorigin+im->xsize+8, im->yorigin+0.5, /* LINEOFFSET */
1819 im->graph_col[GRC_ARROW]);
1826 grid_paint(image_desc_t *im)
1830 double X0,Y0; /* points for filled graph and more*/
1833 /* draw 3d border */
1834 node = gfx_new_area (im->canvas, 0,im->yimg,
1836 2,2,im->graph_col[GRC_SHADEA]);
1837 gfx_add_point( node , im->ximg - 2, 2 );
1838 gfx_add_point( node , im->ximg, 0 );
1839 gfx_add_point( node , 0,0 );
1840 /* gfx_add_point( node , 0,im->yimg ); */
1842 node = gfx_new_area (im->canvas, 2,im->yimg-2,
1843 im->ximg-2,im->yimg-2,
1845 im->graph_col[GRC_SHADEB]);
1846 gfx_add_point( node , im->ximg,0);
1847 gfx_add_point( node , im->ximg,im->yimg);
1848 gfx_add_point( node , 0,im->yimg);
1849 /* gfx_add_point( node , 0,im->yimg ); */
1852 if (im->draw_x_grid == 1 )
1855 if (im->draw_y_grid == 1){
1856 if(im->logarithmic){
1857 res = horizontal_log_grid(im);
1859 res = draw_horizontal_grid(im);
1862 /* dont draw horizontal grid if there is no min and max val */
1864 char *nodata = "No Data found";
1865 gfx_new_text(im->canvas,im->ximg/2, (2*im->yorigin-im->ysize) / 2,
1866 im->graph_col[GRC_FONT],
1867 im->text_prop[TEXT_PROP_AXIS].font,
1868 im->text_prop[TEXT_PROP_AXIS].size,
1869 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
1874 /* yaxis description */
1875 if (im->canvas->imgformat != IF_PNG) {
1876 gfx_new_text( im->canvas,
1877 7, (im->yorigin - im->ysize/2),
1878 im->graph_col[GRC_FONT],
1879 im->text_prop[TEXT_PROP_AXIS].font,
1880 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 270.0,
1881 GFX_H_CENTER, GFX_V_CENTER,
1884 /* horrible hack until we can actually print vertically */
1887 int l=strlen(im->ylegend);
1889 for (n=0;n<strlen(im->ylegend);n++) {
1890 s[0]=im->ylegend[n];
1892 gfx_new_text(im->canvas,7,im->text_prop[TEXT_PROP_AXIS].size*(l-n),
1893 im->graph_col[GRC_FONT],
1894 im->text_prop[TEXT_PROP_AXIS].font,
1895 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 270.0,
1896 GFX_H_CENTER, GFX_V_CENTER,
1903 gfx_new_text( im->canvas,
1904 im->ximg/2, im->text_prop[TEXT_PROP_TITLE].size,
1905 im->graph_col[GRC_FONT],
1906 im->text_prop[TEXT_PROP_TITLE].font,
1907 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
1908 GFX_H_CENTER, GFX_V_CENTER,
1912 if( !(im->extra_flags & NOLEGEND) ) {
1913 for(i=0;i<im->gdes_c;i++){
1914 if(im->gdes[i].legend[0] =='\0')
1917 /* im->gdes[i].leg_y is the bottom of the legend */
1918 X0 = im->gdes[i].leg_x;
1919 Y0 = im->gdes[i].leg_y;
1921 if ( im->gdes[i].gf != GF_GPRINT
1922 && im->gdes[i].gf != GF_COMMENT) {
1925 boxH = gfx_get_text_width(im->canvas, 0,
1926 im->text_prop[TEXT_PROP_AXIS].font,
1927 im->text_prop[TEXT_PROP_AXIS].size,
1928 im->tabwidth,"M") * 1.25;
1931 node = gfx_new_area(im->canvas,
1936 gfx_add_point ( node, X0+boxH, Y0-boxV );
1937 node = gfx_new_line(im->canvas,
1940 gfx_add_point(node,X0+boxH,Y0);
1941 gfx_add_point(node,X0+boxH,Y0-boxV);
1942 gfx_close_path(node);
1943 X0 += boxH / 1.25 * 2;
1945 gfx_new_text ( im->canvas, X0, Y0,
1946 im->graph_col[GRC_FONT],
1947 im->text_prop[TEXT_PROP_AXIS].font,
1948 im->text_prop[TEXT_PROP_AXIS].size,
1949 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
1950 im->gdes[i].legend );
1956 /*****************************************************
1957 * lazy check make sure we rely need to create this graph
1958 *****************************************************/
1960 int lazy_check(image_desc_t *im){
1963 struct stat imgstat;
1965 if (im->lazy == 0) return 0; /* no lazy option */
1966 if (stat(im->graphfile,&imgstat) != 0)
1967 return 0; /* can't stat */
1968 /* one pixel in the existing graph is more then what we would
1970 if (time(NULL) - imgstat.st_mtime >
1971 (im->end - im->start) / im->xsize)
1973 if ((fd = fopen(im->graphfile,"rb")) == NULL)
1974 return 0; /* the file does not exist */
1975 switch (im->canvas->imgformat) {
1977 size = PngSize(fd,&(im->ximg),&(im->yimg));
1987 pie_part(image_desc_t *im, gfx_color_t color,
1988 double PieCenterX, double PieCenterY, double Radius,
1989 double startangle, double endangle)
1993 double step=M_PI/50; /* Number of iterations for the circle;
1994 ** 10 is definitely too low, more than
1995 ** 50 seems to be overkill
1998 /* Strange but true: we have to work clockwise or else
1999 ** anti aliasing nor transparency don't work.
2001 ** This test is here to make sure we do it right, also
2002 ** this makes the for...next loop more easy to implement.
2003 ** The return will occur if the user enters a negative number
2004 ** (which shouldn't be done according to the specs) or if the
2005 ** programmers do something wrong (which, as we all know, never
2006 ** happens anyway :)
2008 if (endangle<startangle) return;
2010 /* Hidden feature: Radius decreases each full circle */
2012 while (angle>=2*M_PI) {
2017 node=gfx_new_area(im->canvas,
2018 PieCenterX+sin(startangle)*Radius,
2019 PieCenterY-cos(startangle)*Radius,
2022 PieCenterX+sin(endangle)*Radius,
2023 PieCenterY-cos(endangle)*Radius,
2025 for (angle=endangle;angle-startangle>=step;angle-=step) {
2027 PieCenterX+sin(angle)*Radius,
2028 PieCenterY-cos(angle)*Radius );
2033 graph_size_location(image_desc_t *im, int elements, int piechart )
2035 /* The actual size of the image to draw is determined from
2036 ** several sources. The size given on the command line is
2037 ** the graph area but we need more as we have to draw labels
2038 ** and other things outside the graph area
2041 /* +-+-------------------------------------------+
2042 ** |l|.................title.....................|
2043 ** |e+--+-------------------------------+--------+
2046 ** |l| l| main graph area | chart |
2049 ** |r+--+-------------------------------+--------+
2050 ** |e| | x-axis labels | |
2051 ** |v+--+-------------------------------+--------+
2052 ** | |..............legends......................|
2053 ** +-+-------------------------------------------+
2055 int Xvertical=0, Yvertical=0,
2056 Xtitle =0, Ytitle =0,
2057 Xylabel =0, Yylabel =0,
2060 Xxlabel =0, Yxlabel =0,
2062 Xlegend =0, Ylegend =0,
2064 Xspacing =10, Yspacing =10;
2066 if (im->ylegend[0] != '\0') {
2067 Xvertical = im->text_prop[TEXT_PROP_LEGEND].size *2;
2068 Yvertical = im->text_prop[TEXT_PROP_LEGEND].size * (strlen(im->ylegend)+1);
2071 if (im->title[0] != '\0') {
2072 /* The title is placed "inbetween" two text lines so it
2073 ** automatically has some vertical spacing. The horizontal
2074 ** spacing is added here, on each side.
2076 Xtitle = gfx_get_text_width(im->canvas, 0,
2077 im->text_prop[TEXT_PROP_TITLE].font,
2078 im->text_prop[TEXT_PROP_TITLE].size,
2080 im->title) + 2*Xspacing;
2081 Ytitle = im->text_prop[TEXT_PROP_TITLE].size*2;
2087 if (im->draw_x_grid) {
2089 Yxlabel=im->text_prop[TEXT_PROP_LEGEND].size *2;
2091 if (im->draw_y_grid) {
2092 Xylabel=im->text_prop[TEXT_PROP_LEGEND].size *6;
2098 im->piesize=im->xsize<im->ysize?im->xsize:im->ysize;
2103 /* Now calculate the total size. Insert some spacing where
2104 desired. im->xorigin and im->yorigin need to correspond
2105 with the lower left corner of the main graph area or, if
2106 this one is not set, the imaginary box surrounding the
2109 /* The legend width cannot yet be determined, as a result we
2110 ** have problems adjusting the image to it. For now, we just
2111 ** forget about it at all; the legend will have to fit in the
2112 ** size already allocated.
2114 im->ximg = Xylabel + Xmain + Xpie + Xspacing;
2115 if (Xmain) im->ximg += Xspacing;
2116 if (Xpie) im->ximg += Xspacing;
2117 im->xorigin = Xspacing + Xylabel;
2118 if (Xtitle > im->ximg) im->ximg = Xtitle;
2120 im->ximg += Xvertical;
2121 im->xorigin += Xvertical;
2125 /* The vertical size is interesting... we need to compare
2126 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend} with Yvertical
2127 ** however we need to know {Ytitle+Ymain+Yxlabel} in order to
2128 ** start even thinking about Ylegend.
2130 ** Do it in three portions: First calculate the inner part,
2131 ** then do the legend, then adjust the total height of the img.
2134 /* reserve space for main and/or pie */
2135 im->yimg = Ymain + Yxlabel;
2136 if (im->yimg < Ypie) im->yimg = Ypie;
2137 im->yorigin = im->yimg - Yxlabel;
2138 /* reserve space for the title *or* some padding above the graph */
2141 im->yorigin += Ytitle;
2143 im->yimg += Yspacing;
2144 im->yorigin += Yspacing;
2146 /* reserve space for padding below the graph */
2147 im->yimg += Yspacing;
2150 /* Determine where to place the legends onto the image.
2151 ** Adjust im->yimg to match the space requirements.
2153 if(leg_place(im)==-1)
2156 /* last of three steps: check total height of image */
2157 if (im->yimg < Yvertical) im->yimg = Yvertical;
2160 if (Xlegend > im->ximg) {
2162 /* reposition Pie */
2166 /* The pie is placed in the upper right hand corner,
2167 ** just below the title (if any) and with sufficient
2171 im->pie_x = im->ximg - Xspacing - Xpie/2;
2172 im->pie_y = im->yorigin-Ymain+Ypie/2;
2174 im->pie_x = im->ximg/2;
2175 im->pie_y = im->yorigin-Ypie/2;
2181 /* draw that picture thing ... */
2183 graph_paint(image_desc_t *im, char ***calcpr)
2186 int lazy = lazy_check(im);
2188 double PieStart=0.0;
2192 double areazero = 0.0;
2193 enum gf_en stack_gf = GF_PRINT;
2194 graph_desc_t *lastgdes = NULL;
2196 /* if we are lazy and there is nothing to PRINT ... quit now */
2197 if (lazy && im->prt_c==0) return 0;
2199 /* pull the data from the rrd files ... */
2201 if(data_fetch(im)==-1)
2204 /* evaluate VDEF and CDEF operations ... */
2205 if(data_calc(im)==-1)
2208 /* check if we need to draw a piechart */
2209 for(i=0;i<im->gdes_c;i++){
2210 if (im->gdes[i].gf == GF_PART) {
2216 /* calculate and PRINT and GPRINT definitions. We have to do it at
2217 * this point because it will affect the length of the legends
2218 * if there are no graph elements we stop here ...
2219 * if we are lazy, try to quit ...
2221 i=print_calc(im,calcpr);
2223 if(((i==0)&&(piechart==0)) || lazy) return 0;
2225 /* If there's only the pie chart to draw, signal this */
2226 if (i==0) piechart=2;
2228 /* get actual drawing data and find min and max values*/
2229 if(data_proc(im)==-1)
2232 if(!im->logarithmic){si_unit(im);} /* identify si magnitude Kilo, Mega Giga ? */
2234 if(!im->rigid && ! im->logarithmic)
2235 expand_range(im); /* make sure the upper and lower limit are
2238 if (!calc_horizontal_grid(im))
2243 /**************************************************************
2244 *** Calculating sizes and locations became a bit confusing ***
2245 *** so I moved this into a separate function. ***
2246 **************************************************************/
2247 if(graph_size_location(im,i,piechart)==-1)
2250 /* the actual graph is created by going through the individual
2251 graph elements and then drawing them */
2253 node=gfx_new_area ( im->canvas,
2257 im->graph_col[GRC_BACK]);
2259 gfx_add_point(node,0, im->yimg);
2261 if (piechart != 2) {
2262 node=gfx_new_area ( im->canvas,
2263 im->xorigin, im->yorigin,
2264 im->xorigin + im->xsize, im->yorigin,
2265 im->xorigin + im->xsize, im->yorigin-im->ysize,
2266 im->graph_col[GRC_CANVAS]);
2268 gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
2270 if (im->minval > 0.0)
2271 areazero = im->minval;
2272 if (im->maxval < 0.0)
2273 areazero = im->maxval;
2279 pie_part(im,im->graph_col[GRC_CANVAS],im->pie_x,im->pie_y,im->piesize*0.5,0,2*M_PI);
2282 for(i=0;i<im->gdes_c;i++){
2283 switch(im->gdes[i].gf){
2295 for (ii = 0; ii < im->xsize; ii++)
2297 if (!isnan(im->gdes[i].p_data[ii]) &&
2298 im->gdes[i].p_data[ii] > 0.0)
2300 /* generate a tick */
2301 gfx_new_line(im->canvas, im -> xorigin + ii,
2302 im -> yorigin - (im -> gdes[i].yrule * im -> ysize),
2306 im -> gdes[i].col );
2312 stack_gf = im->gdes[i].gf;
2314 /* fix data points at oo and -oo */
2315 for(ii=0;ii<im->xsize;ii++){
2316 if (isinf(im->gdes[i].p_data[ii])){
2317 if (im->gdes[i].p_data[ii] > 0) {
2318 im->gdes[i].p_data[ii] = im->maxval ;
2320 im->gdes[i].p_data[ii] = im->minval ;
2326 if (im->gdes[i].col != 0x0){
2327 /* GF_LINE and friend */
2328 if(stack_gf == GF_LINE ){
2330 for(ii=1;ii<im->xsize;ii++){
2331 if ( ! isnan(im->gdes[i].p_data[ii-1])
2332 && ! isnan(im->gdes[i].p_data[ii])){
2334 node = gfx_new_line(im->canvas,
2335 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2336 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2337 im->gdes[i].linewidth,
2340 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2349 for(ii=1;ii<im->xsize;ii++){
2351 if ( ! isnan(im->gdes[i].p_data[ii-1])
2352 && ! isnan(im->gdes[i].p_data[ii])){
2356 if (im->gdes[i].gf == GF_STACK) {
2358 if ( (im->gdes[i].gf == GF_STACK)
2359 || (im->gdes[i].stack) ) {
2361 ybase = ytr(im,lastgdes->p_data[ii-1]);
2363 ybase = ytr(im,areazero);
2366 node = gfx_new_area(im->canvas,
2367 ii-1+im->xorigin,ybase,
2368 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2369 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2373 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2377 if ( node != NULL && (ii+1==im->xsize || isnan(im->gdes[i].p_data[ii]) )){
2378 /* GF_AREA STACK type*/
2380 if (im->gdes[i].gf == GF_STACK ) {
2382 if ( (im->gdes[i].gf == GF_STACK)
2383 || (im->gdes[i].stack) ) {
2385 for (iii=ii-1;iii>area_start;iii--){
2386 gfx_add_point(node,iii+im->xorigin,ytr(im,lastgdes->p_data[iii]));
2389 gfx_add_point(node,ii+im->xorigin,ytr(im,areazero));
2394 } /* else GF_LINE */
2395 } /* if color != 0x0 */
2396 /* make sure we do not run into trouble when stacking on NaN */
2397 for(ii=0;ii<im->xsize;ii++){
2398 if (isnan(im->gdes[i].p_data[ii])) {
2401 ybase = ytr(im,lastgdes->p_data[ii-1]);
2403 if (isnan(ybase) || !lastgdes ){
2404 ybase = ytr(im,areazero);
2406 im->gdes[i].p_data[ii] = ybase;
2409 lastgdes = &(im->gdes[i]);
2412 if(isnan(im->gdes[i].yrule)) /* fetch variable */
2413 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2415 if (finite(im->gdes[i].yrule)) { /* even the fetched var can be NaN */
2416 pie_part(im,im->gdes[i].col,
2417 im->pie_x,im->pie_y,im->piesize*0.4,
2418 M_PI*2.0*PieStart/100.0,
2419 M_PI*2.0*(PieStart+im->gdes[i].yrule)/100.0);
2420 PieStart += im->gdes[i].yrule;
2429 /* grid_paint also does the text */
2432 /* the RULES are the last thing to paint ... */
2433 for(i=0;i<im->gdes_c;i++){
2435 switch(im->gdes[i].gf){
2437 if(isnan(im->gdes[i].yrule)) { /* fetch variable */
2438 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2440 if(im->gdes[i].yrule >= im->minval
2441 && im->gdes[i].yrule <= im->maxval)
2442 gfx_new_line(im->canvas,
2443 im->xorigin,ytr(im,im->gdes[i].yrule),
2444 im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2445 1.0,im->gdes[i].col);
2448 if(im->gdes[i].xrule == 0) { /* fetch variable */
2449 im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
2451 if(im->gdes[i].xrule >= im->start
2452 && im->gdes[i].xrule <= im->end)
2453 gfx_new_line(im->canvas,
2454 xtr(im,im->gdes[i].xrule),im->yorigin,
2455 xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2456 1.0,im->gdes[i].col);
2464 if (strcmp(im->graphfile,"-")==0) {
2466 /* Change translation mode for stdout to BINARY */
2467 _setmode( _fileno( stdout ), O_BINARY );
2471 if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2472 rrd_set_error("Opening '%s' for write: %s",im->graphfile,
2477 gfx_render (im->canvas,im->ximg,im->yimg,0x0,fo);
2478 if (strcmp(im->graphfile,"-") != 0)
2484 /*****************************************************
2486 *****************************************************/
2489 gdes_alloc(image_desc_t *im){
2491 long def_step = (im->end-im->start)/im->xsize;
2493 if (im->step > def_step) /* step can be increassed ... no decreassed */
2494 def_step = im->step;
2498 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2499 * sizeof(graph_desc_t)))==NULL){
2500 rrd_set_error("realloc graph_descs");
2505 im->gdes[im->gdes_c-1].step=def_step;
2506 im->gdes[im->gdes_c-1].stack=0;
2507 im->gdes[im->gdes_c-1].debug=0;
2508 im->gdes[im->gdes_c-1].start=im->start;
2509 im->gdes[im->gdes_c-1].end=im->end;
2510 im->gdes[im->gdes_c-1].vname[0]='\0';
2511 im->gdes[im->gdes_c-1].data=NULL;
2512 im->gdes[im->gdes_c-1].ds_namv=NULL;
2513 im->gdes[im->gdes_c-1].data_first=0;
2514 im->gdes[im->gdes_c-1].p_data=NULL;
2515 im->gdes[im->gdes_c-1].rpnp=NULL;
2516 im->gdes[im->gdes_c-1].col = 0x0;
2517 im->gdes[im->gdes_c-1].legend[0]='\0';
2518 im->gdes[im->gdes_c-1].rrd[0]='\0';
2519 im->gdes[im->gdes_c-1].ds=-1;
2520 im->gdes[im->gdes_c-1].p_data=NULL;
2521 im->gdes[im->gdes_c-1].yrule=DNAN;
2522 im->gdes[im->gdes_c-1].xrule=0;
2526 /* copies input untill the first unescaped colon is found
2527 or until input ends. backslashes have to be escaped as well */
2529 scan_for_col(char *input, int len, char *output)
2534 input[inp] != ':' &&
2537 if (input[inp] == '\\' &&
2538 input[inp+1] != '\0' &&
2539 (input[inp+1] == '\\' ||
2540 input[inp+1] == ':')){
2541 output[outp++] = input[++inp];
2544 output[outp++] = input[inp];
2547 output[outp] = '\0';
2550 /* Some surgery done on this function, it became ridiculously big.
2552 ** - initializing now in rrd_graph_init()
2553 ** - options parsing now in rrd_graph_options()
2554 ** - script parsing now in rrd_graph_script()
2557 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize)
2561 rrd_graph_init(&im);
2563 rrd_graph_options(argc,argv,&im);
2564 if (rrd_test_error()) {
2569 if (strlen(argv[optind])>=MAXPATH) {
2570 rrd_set_error("filename (including path) too long");
2574 strncpy(im.graphfile,argv[optind],MAXPATH-1);
2575 im.graphfile[MAXPATH-1]='\0';
2577 rrd_graph_script(argc,argv,&im);
2578 if (rrd_test_error()) {
2583 /* Everything is now read and the actual work can start */
2586 if (graph_paint(&im,prdata)==-1){
2591 /* The image is generated and needs to be output.
2592 ** Also, if needed, print a line with information about the image.
2600 /* maybe prdata is not allocated yet ... lets do it now */
2601 if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
2602 rrd_set_error("malloc imginfo");
2606 if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
2608 rrd_set_error("malloc imginfo");
2611 filename=im.graphfile+strlen(im.graphfile);
2612 while(filename > im.graphfile) {
2613 if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
2617 sprintf((*prdata)[0],im.imginfo,filename,(long)(im.canvas->zoom*im.ximg),(long)(im.canvas->zoom*im.yimg));
2624 rrd_graph_init(image_desc_t *im)
2631 #ifdef HAVE_SETLOCALE
2632 setlocale(LC_TIME,"");
2635 im->xlab_user.minsec = -1;
2641 im->ylegend[0] = '\0';
2642 im->title[0] = '\0';
2645 im->unitsexponent= 9999;
2651 im->logarithmic = 0;
2652 im->ygridstep = DNAN;
2653 im->draw_x_grid = 1;
2654 im->draw_y_grid = 1;
2659 im->canvas = gfx_new_canvas();
2660 im->grid_dash_on = 1;
2661 im->grid_dash_off = 1;
2663 for(i=0;i<DIM(graph_col);i++)
2664 im->graph_col[i]=graph_col[i];
2666 for(i=0;i<DIM(text_prop);i++){
2667 im->text_prop[i].size = text_prop[i].size;
2668 im->text_prop[i].font = text_prop[i].font;
2673 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
2676 char *parsetime_error = NULL;
2677 char scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
2678 time_t start_tmp=0,end_tmp=0;
2680 struct time_value start_tv, end_tv;
2683 parsetime("end-24h", &start_tv);
2684 parsetime("now", &end_tv);
2687 static struct option long_options[] =
2689 {"start", required_argument, 0, 's'},
2690 {"end", required_argument, 0, 'e'},
2691 {"x-grid", required_argument, 0, 'x'},
2692 {"y-grid", required_argument, 0, 'y'},
2693 {"vertical-label",required_argument,0,'v'},
2694 {"width", required_argument, 0, 'w'},
2695 {"height", required_argument, 0, 'h'},
2696 {"interlaced", no_argument, 0, 'i'},
2697 {"upper-limit",required_argument, 0, 'u'},
2698 {"lower-limit",required_argument, 0, 'l'},
2699 {"rigid", no_argument, 0, 'r'},
2700 {"base", required_argument, 0, 'b'},
2701 {"logarithmic",no_argument, 0, 'o'},
2702 {"color", required_argument, 0, 'c'},
2703 {"font", required_argument, 0, 'n'},
2704 {"title", required_argument, 0, 't'},
2705 {"imginfo", required_argument, 0, 'f'},
2706 {"imgformat", required_argument, 0, 'a'},
2707 {"lazy", no_argument, 0, 'z'},
2708 {"zoom", required_argument, 0, 'm'},
2709 {"no-legend", no_argument, 0, 'g'},
2710 {"alt-y-grid", no_argument, 0, 257 },
2711 {"alt-autoscale", no_argument, 0, 258 },
2712 {"alt-autoscale-max", no_argument, 0, 259 },
2713 {"units-exponent",required_argument, 0, 260},
2714 {"step", required_argument, 0, 261},
2715 {"no-gridfit", no_argument, 0, 262},
2717 int option_index = 0;
2721 opt = getopt_long(argc, argv,
2722 "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:zg",
2723 long_options, &option_index);
2730 im->extra_flags |= ALTYGRID;
2733 im->extra_flags |= ALTAUTOSCALE;
2736 im->extra_flags |= ALTAUTOSCALE_MAX;
2739 im->extra_flags |= NOLEGEND;
2742 im->unitsexponent = atoi(optarg);
2745 im->step = atoi(optarg);
2751 if ((parsetime_error = parsetime(optarg, &start_tv))) {
2752 rrd_set_error( "start time: %s", parsetime_error );
2757 if ((parsetime_error = parsetime(optarg, &end_tv))) {
2758 rrd_set_error( "end time: %s", parsetime_error );
2763 if(strcmp(optarg,"none") == 0){
2769 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
2771 &im->xlab_user.gridst,
2773 &im->xlab_user.mgridst,
2775 &im->xlab_user.labst,
2776 &im->xlab_user.precis,
2777 &stroff) == 7 && stroff != 0){
2778 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
2779 if((im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
2780 rrd_set_error("unknown keyword %s",scan_gtm);
2782 } else if ((im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
2783 rrd_set_error("unknown keyword %s",scan_mtm);
2785 } else if ((im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
2786 rrd_set_error("unknown keyword %s",scan_ltm);
2789 im->xlab_user.minsec = 1;
2790 im->xlab_user.stst = im->xlab_form;
2792 rrd_set_error("invalid x-grid format");
2798 if(strcmp(optarg,"none") == 0){
2806 &im->ylabfact) == 2) {
2807 if(im->ygridstep<=0){
2808 rrd_set_error("grid step must be > 0");
2810 } else if (im->ylabfact < 1){
2811 rrd_set_error("label factor must be > 0");
2815 rrd_set_error("invalid y-grid format");
2820 strncpy(im->ylegend,optarg,150);
2821 im->ylegend[150]='\0';
2824 im->maxval = atof(optarg);
2827 im->minval = atof(optarg);
2830 im->base = atol(optarg);
2831 if(im->base != 1024 && im->base != 1000 ){
2832 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
2837 long_tmp = atol(optarg);
2838 if (long_tmp < 10) {
2839 rrd_set_error("width below 10 pixels");
2842 im->xsize = long_tmp;
2845 long_tmp = atol(optarg);
2846 if (long_tmp < 10) {
2847 rrd_set_error("height below 10 pixels");
2850 im->ysize = long_tmp;
2853 im->canvas->interlaced = 1;
2859 im->imginfo = optarg;
2862 if((im->canvas->imgformat = if_conv(optarg)) == -1) {
2863 rrd_set_error("unsupported graphics format '%s'",optarg);
2871 im->logarithmic = 1;
2872 if (isnan(im->minval))
2878 col_nam,&color) == 2){
2880 if((ci=grc_conv(col_nam)) != -1){
2881 im->graph_col[ci]=color;
2883 rrd_set_error("invalid color name '%s'",col_nam);
2886 rrd_set_error("invalid color def format");
2891 /* originally this used char *prop = "" and
2892 ** char *font = "dummy" however this results
2893 ** in a SEG fault, at least on RH7.1
2895 ** The current implementation isn't proper
2896 ** either, font is never freed and prop uses
2897 ** a fixed width string
2906 prop,&size,font) == 3){
2908 if((sindex=text_prop_conv(prop)) != -1){
2909 im->text_prop[sindex].size=size;
2910 im->text_prop[sindex].font=font;
2911 if (sindex==0) { /* the default */
2912 im->text_prop[TEXT_PROP_TITLE].size=size;
2913 im->text_prop[TEXT_PROP_TITLE].font=font;
2914 im->text_prop[TEXT_PROP_AXIS].size=size;
2915 im->text_prop[TEXT_PROP_AXIS].font=font;
2916 im->text_prop[TEXT_PROP_UNIT].size=size;
2917 im->text_prop[TEXT_PROP_UNIT].font=font;
2918 im->text_prop[TEXT_PROP_LEGEND].size=size;
2919 im->text_prop[TEXT_PROP_LEGEND].font=font;
2922 rrd_set_error("invalid fonttag '%s'",prop);
2926 rrd_set_error("invalid text property format");
2932 im->canvas->zoom = atof(optarg);
2933 if (im->canvas->zoom <= 0.0) {
2934 rrd_set_error("zoom factor must be > 0");
2939 strncpy(im->title,optarg,150);
2940 im->title[150]='\0';
2945 rrd_set_error("unknown option '%c'", optopt);
2947 rrd_set_error("unknown option '%s'",argv[optind-1]);
2952 if (optind >= argc) {
2953 rrd_set_error("missing filename");
2957 if (im->logarithmic == 1 && (im->minval <= 0 || isnan(im->minval))){
2958 rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");
2962 if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
2963 /* error string is set in parsetime.c */
2967 if (start_tmp < 3600*24*365*10){
2968 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
2972 if (end_tmp < start_tmp) {
2973 rrd_set_error("start (%ld) should be less than end (%ld)",
2974 start_tmp, end_tmp);
2978 im->start = start_tmp;
2983 rrd_graph_check_vname(image_desc_t *im, char *varname, char *err)
2985 if ((im->gdes[im->gdes_c-1].vidx=find_var(im,varname))==-1) {
2986 rrd_set_error("Unknown variable '%s' in %s",varname,err);
2992 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
2995 graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
2997 color=strstr(var,"#");
3000 rrd_set_error("Found no color in %s",err);
3009 rest=strstr(color,":");
3017 sscanf(color,"#%6lx%n",&col,&n);
3018 col = (col << 8) + 0xff /* shift left by 8 */;
3019 if (n!=7) rrd_set_error("Color problem in %s",err);
3022 sscanf(color,"#%8lx%n",&col,&n);
3025 rrd_set_error("Color problem in %s",err);
3027 if (rrd_test_error()) return 0;
3033 rrd_graph_legend(graph_desc_t *gdp, char *line)
3037 i=scan_for_col(line,FMT_LEG_LEN,gdp->legend);
3039 return (strlen(&line[i])==0);
3043 int bad_format(char *fmt) {
3047 while (*ptr != '\0')
3048 if (*ptr++ == '%') {
3050 /* line cannot end with percent char */
3051 if (*ptr == '\0') return 1;
3053 /* '%s', '%S' and '%%' are allowed */
3054 if (*ptr == 's' || *ptr == 'S' || *ptr == '%') ptr++;
3056 /* or else '% 6.2lf' and such are allowed */
3059 /* optional padding character */
3060 if (*ptr == ' ' || *ptr == '+' || *ptr == '-') ptr++;
3062 /* This should take care of 'm.n' with all three optional */
3063 while (*ptr >= '0' && *ptr <= '9') ptr++;
3064 if (*ptr == '.') ptr++;
3065 while (*ptr >= '0' && *ptr <= '9') ptr++;
3067 /* Either 'le', 'lf' or 'lg' must follow here */
3068 if (*ptr++ != 'l') return 1;
3069 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g') ptr++;
3080 vdef_parse(gdes,str)
3081 struct graph_desc_t *gdes;
3084 /* A VDEF currently is either "func" or "param,func"
3085 * so the parsing is rather simple. Change if needed.
3092 sscanf(str,"%le,%29[A-Z]%n",¶m,func,&n);
3093 if (n==strlen(str)) { /* matched */
3097 sscanf(str,"%29[A-Z]%n",func,&n);
3098 if (n==strlen(str)) { /* matched */
3101 rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3108 if (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3109 else if (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3110 else if (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3111 else if (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3112 else if (!strcmp("TOTAL", func)) gdes->vf.op = VDEF_TOTAL;
3113 else if (!strcmp("FIRST", func)) gdes->vf.op = VDEF_FIRST;
3114 else if (!strcmp("LAST", func)) gdes->vf.op = VDEF_LAST;
3116 rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3123 switch (gdes->vf.op) {
3125 if (isnan(param)) { /* no parameter given */
3126 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3132 if (param>=0.0 && param<=100.0) {
3133 gdes->vf.param = param;
3134 gdes->vf.val = DNAN; /* undefined */
3135 gdes->vf.when = 0; /* undefined */
3137 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3151 gdes->vf.param = DNAN;
3152 gdes->vf.val = DNAN;
3155 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3172 graph_desc_t *src,*dst;
3176 dst = &im->gdes[gdi];
3177 src = &im->gdes[dst->vidx];
3178 data = src->data + src->ds;
3179 steps = (src->end - src->start) / src->step;
3182 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3189 switch (dst->vf.op) {
3190 case VDEF_PERCENT: {
3191 rrd_value_t * array;
3195 if ((array = malloc(steps*sizeof(double)))==NULL) {
3196 rrd_set_error("malloc VDEV_PERCENT");
3199 for (step=0;step < steps; step++) {
3200 array[step]=data[step*src->ds_cnt];
3202 qsort(array,step,sizeof(double),vdef_percent_compar);
3204 field = (steps-1)*dst->vf.param/100;
3205 dst->vf.val = array[field];
3206 dst->vf.when = 0; /* no time component */
3209 for(step=0;step<steps;step++)
3210 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3216 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3217 if (step == steps) {
3221 dst->vf.val = data[step*src->ds_cnt];
3222 dst->vf.when = src->start + (step+1)*src->step;
3224 while (step != steps) {
3225 if (finite(data[step*src->ds_cnt])) {
3226 if (data[step*src->ds_cnt] > dst->vf.val) {
3227 dst->vf.val = data[step*src->ds_cnt];
3228 dst->vf.when = src->start + (step+1)*src->step;
3235 case VDEF_AVERAGE: {
3238 for (step=0;step<steps;step++) {
3239 if (finite(data[step*src->ds_cnt])) {
3240 sum += data[step*src->ds_cnt];
3245 if (dst->vf.op == VDEF_TOTAL) {
3246 dst->vf.val = sum*src->step;
3247 dst->vf.when = cnt*src->step; /* not really "when" */
3249 dst->vf.val = sum/cnt;
3250 dst->vf.when = 0; /* no time component */
3260 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3261 if (step == steps) {
3265 dst->vf.val = data[step*src->ds_cnt];
3266 dst->vf.when = src->start + (step+1)*src->step;
3268 while (step != steps) {
3269 if (finite(data[step*src->ds_cnt])) {
3270 if (data[step*src->ds_cnt] < dst->vf.val) {
3271 dst->vf.val = data[step*src->ds_cnt];
3272 dst->vf.when = src->start + (step+1)*src->step;
3279 /* The time value returned here is one step before the
3280 * actual time value. This is the start of the first
3284 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3285 if (step == steps) { /* all entries were NaN */
3289 dst->vf.val = data[step*src->ds_cnt];
3290 dst->vf.when = src->start + step*src->step;
3294 /* The time value returned here is the
3295 * actual time value. This is the end of the last
3299 while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3300 if (step < 0) { /* all entries were NaN */
3304 dst->vf.val = data[step*src->ds_cnt];
3305 dst->vf.when = src->start + (step+1)*src->step;
3312 /* NaN < -INF < finite_values < INF */
3314 vdef_percent_compar(a,b)
3317 /* Equality is not returned; this doesn't hurt except
3318 * (maybe) for a little performance.
3321 /* First catch NaN values. They are smallest */
3322 if (isnan( *(double *)a )) return -1;
3323 if (isnan( *(double *)b )) return 1;
3325 /* NaN doesn't reach this part so INF and -INF are extremes.
3326 * The sign from isinf() is compatible with the sign we return
3328 if (isinf( *(double *)a )) return isinf( *(double *)a );
3329 if (isinf( *(double *)b )) return isinf( *(double *)b );
3331 /* If we reach this, both values must be finite */
3332 if ( *(double *)a < *(double *)b ) return -1; else return 1;