1 /****************************************************************************
2 * RRDtool 1.1.x Copyright Tobias Oetiker, 1997 - 2002
3 ****************************************************************************
4 * rrd__graph.c make creates ne rrds
5 ****************************************************************************/
25 #include "rrd_graph.h"
27 /* some constant definitions */
31 char rrd_win_default_font[80];
34 #ifndef RRD_DEFAULT_FONT
36 #define RRD_DEFAULT_FONT "/usr/share/fonts/truetype/openoffice/ariosor.ttf"
37 /* #define RRD_DEFAULT_FONT "/usr/share/fonts/truetype/Arial.ttf" */
41 text_prop_t text_prop[] = {
42 { 10.0, RRD_DEFAULT_FONT }, /* default */
43 { 12.0, RRD_DEFAULT_FONT }, /* title */
44 { 8.0, RRD_DEFAULT_FONT }, /* axis */
45 { 10.0, RRD_DEFAULT_FONT }, /* unit */
46 { 10.0, RRD_DEFAULT_FONT } /* legend */
50 {0, TMT_SECOND,30, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
51 {2, TMT_MINUTE,1, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
52 {5, TMT_MINUTE,2, TMT_MINUTE,10, TMT_MINUTE,10, 0,"%H:%M"},
53 {10, TMT_MINUTE,5, TMT_MINUTE,20, TMT_MINUTE,20, 0,"%H:%M"},
54 {30, TMT_MINUTE,10, TMT_HOUR,1, TMT_HOUR,1, 0,"%H:%M"},
55 {60, TMT_MINUTE,30, TMT_HOUR,2, TMT_HOUR,2, 0,"%H:%M"},
56 {180, TMT_HOUR,1, TMT_HOUR,6, TMT_HOUR,6, 0,"%H:%M"},
57 /*{300, TMT_HOUR,3, TMT_HOUR,12, TMT_HOUR,12, 12*3600,"%a %p"}, this looks silly*/
58 {600, TMT_HOUR,6, TMT_DAY,1, TMT_DAY,1, 24*3600,"%a"},
59 {1800, TMT_HOUR,12, TMT_DAY,1, TMT_DAY,2, 24*3600,"%a"},
60 {3600, TMT_DAY,1, TMT_WEEK,1, TMT_WEEK,1, 7*24*3600,"Week %V"},
61 {3*3600, TMT_WEEK,1, TMT_MONTH,1, TMT_WEEK,2, 7*24*3600,"Week %V"},
62 {6*3600, TMT_MONTH,1, TMT_MONTH,1, TMT_MONTH,1, 30*24*3600,"%b"},
63 {48*3600, TMT_MONTH,1, TMT_MONTH,3, TMT_MONTH,3, 30*24*3600,"%b"},
64 {10*24*3600, TMT_YEAR,1, TMT_YEAR,1, TMT_YEAR,1, 365*24*3600,"%y"},
65 {-1,TMT_MONTH,0,TMT_MONTH,0,TMT_MONTH,0,0,""}
68 /* sensible logarithmic y label intervals ...
69 the first element of each row defines the possible starting points on the
70 y axis ... the other specify the */
72 double yloglab[][12]= {{ 1e9, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
73 { 1e3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
74 { 1e1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
75 /* { 1e1, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, */
76 { 1e1, 1, 2.5, 5, 7.5, 0, 0, 0, 0, 0, 0, 0 },
77 { 1e1, 1, 2, 4, 6, 8, 0, 0, 0, 0, 0, 0 },
78 { 1e1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0 },
79 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }};
81 /* sensible y label intervals ...*/
99 gfx_color_t graph_col[] = /* default colors */
100 { 0xFFFFFFFF, /* canvas */
101 0xF0F0F0FF, /* background */
102 0xD0D0D0FF, /* shade A */
103 0xA0A0A0FF, /* shade B */
104 0x909090FF, /* grid */
105 0xE05050FF, /* major grid */
106 0x000000FF, /* font */
107 0x000000FF, /* frame */
108 0xFF0000FF /* arrow */
115 # define DPRINT(x) (void)(printf x, printf("\n"))
121 /* initialize with xtr(im,0); */
123 xtr(image_desc_t *im,time_t mytime){
126 pixie = (double) im->xsize / (double)(im->end - im->start);
129 return (int)((double)im->xorigin
130 + pixie * ( mytime - im->start ) );
133 /* translate data values into y coordinates */
135 ytr(image_desc_t *im, double value){
140 pixie = (double) im->ysize / (im->maxval - im->minval);
142 pixie = (double) im->ysize / (log10(im->maxval) - log10(im->minval));
144 } else if(!im->logarithmic) {
145 yval = im->yorigin - pixie * (value - im->minval);
147 if (value < im->minval) {
150 yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
153 /* make sure we don't return anything too unreasonable. GD lib can
154 get terribly slow when drawing lines outside its scope. This is
155 especially problematic in connection with the rigid option */
157 /* keep yval as-is */
158 } else if (yval > im->yorigin) {
159 yval = im->yorigin+2;
160 } else if (yval < im->yorigin - im->ysize){
161 yval = im->yorigin - im->ysize - 2;
168 /* conversion function for symbolic entry names */
171 #define conv_if(VV,VVV) \
172 if (strcmp(#VV, string) == 0) return VVV ;
174 enum gf_en gf_conv(char *string){
176 conv_if(PRINT,GF_PRINT)
177 conv_if(GPRINT,GF_GPRINT)
178 conv_if(COMMENT,GF_COMMENT)
179 conv_if(HRULE,GF_HRULE)
180 conv_if(VRULE,GF_VRULE)
181 conv_if(LINE,GF_LINE)
182 conv_if(AREA,GF_AREA)
183 conv_if(STACK,GF_STACK)
184 conv_if(TICK,GF_TICK)
186 conv_if(CDEF,GF_CDEF)
187 conv_if(VDEF,GF_VDEF)
188 conv_if(PART,GF_PART)
189 conv_if(XPORT,GF_XPORT)
194 enum gfx_if_en if_conv(char *string){
204 enum tmt_en tmt_conv(char *string){
206 conv_if(SECOND,TMT_SECOND)
207 conv_if(MINUTE,TMT_MINUTE)
208 conv_if(HOUR,TMT_HOUR)
210 conv_if(WEEK,TMT_WEEK)
211 conv_if(MONTH,TMT_MONTH)
212 conv_if(YEAR,TMT_YEAR)
216 enum grc_en grc_conv(char *string){
218 conv_if(BACK,GRC_BACK)
219 conv_if(CANVAS,GRC_CANVAS)
220 conv_if(SHADEA,GRC_SHADEA)
221 conv_if(SHADEB,GRC_SHADEB)
222 conv_if(GRID,GRC_GRID)
223 conv_if(MGRID,GRC_MGRID)
224 conv_if(FONT,GRC_FONT)
225 conv_if(FRAME,GRC_FRAME)
226 conv_if(ARROW,GRC_ARROW)
231 enum text_prop_en text_prop_conv(char *string){
233 conv_if(DEFAULT,TEXT_PROP_DEFAULT)
234 conv_if(TITLE,TEXT_PROP_TITLE)
235 conv_if(AXIS,TEXT_PROP_AXIS)
236 conv_if(UNIT,TEXT_PROP_UNIT)
237 conv_if(LEGEND,TEXT_PROP_LEGEND)
245 im_free(image_desc_t *im)
249 if (im == NULL) return 0;
250 for(i=0;i<(unsigned)im->gdes_c;i++){
251 if (im->gdes[i].data_first){
252 /* careful here, because a single pointer can occur several times */
253 free (im->gdes[i].data);
254 if (im->gdes[i].ds_namv){
255 for (ii=0;ii<im->gdes[i].ds_cnt;ii++)
256 free(im->gdes[i].ds_namv[ii]);
257 free(im->gdes[i].ds_namv);
260 free (im->gdes[i].p_data);
261 free (im->gdes[i].rpnp);
264 gfx_destroy(im->canvas);
268 /* find SI magnitude symbol for the given number*/
271 image_desc_t *im, /* image description */
278 char *symbol[] = {"a", /* 10e-18 Atto */
279 "f", /* 10e-15 Femto */
280 "p", /* 10e-12 Pico */
281 "n", /* 10e-9 Nano */
282 "u", /* 10e-6 Micro */
283 "m", /* 10e-3 Milli */
288 "T", /* 10e12 Tera */
289 "P", /* 10e15 Peta */
295 if (*value == 0.0 || isnan(*value) ) {
299 sindex = floor(log(fabs(*value))/log((double)im->base));
300 *magfact = pow((double)im->base, (double)sindex);
301 (*value) /= (*magfact);
303 if ( sindex <= symbcenter && sindex >= -symbcenter) {
304 (*symb_ptr) = symbol[sindex+symbcenter];
312 /* find SI magnitude symbol for the numbers on the y-axis*/
315 image_desc_t *im /* image description */
319 char symbol[] = {'a', /* 10e-18 Atto */
320 'f', /* 10e-15 Femto */
321 'p', /* 10e-12 Pico */
322 'n', /* 10e-9 Nano */
323 'u', /* 10e-6 Micro */
324 'm', /* 10e-3 Milli */
329 'T', /* 10e12 Tera */
330 'P', /* 10e15 Peta */
336 if (im->unitsexponent != 9999) {
337 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
338 digits = floor(im->unitsexponent / 3);
340 digits = floor( log( max( fabs(im->minval),fabs(im->maxval)))/log((double)im->base));
342 im->magfact = pow((double)im->base , digits);
345 printf("digits %6.3f im->magfact %6.3f\n",digits,im->magfact);
348 if ( ((digits+symbcenter) < sizeof(symbol)) &&
349 ((digits+symbcenter) >= 0) )
350 im->symbol = symbol[(int)digits+symbcenter];
355 /* move min and max values around to become sensible */
358 expand_range(image_desc_t *im)
360 double sensiblevalues[] ={1000.0,900.0,800.0,750.0,700.0,
361 600.0,500.0,400.0,300.0,250.0,
362 200.0,125.0,100.0,90.0,80.0,
363 75.0,70.0,60.0,50.0,40.0,30.0,
364 25.0,20.0,10.0,9.0,8.0,
365 7.0,6.0,5.0,4.0,3.5,3.0,
366 2.5,2.0,1.8,1.5,1.2,1.0,
367 0.8,0.7,0.6,0.5,0.4,0.3,0.2,0.1,0.0,-1};
369 double scaled_min,scaled_max;
376 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
377 im->minval,im->maxval,im->magfact);
380 if (isnan(im->ygridstep)){
381 if(im->extra_flags & ALTAUTOSCALE) {
382 /* measure the amplitude of the function. Make sure that
383 graph boundaries are slightly higher then max/min vals
384 so we can see amplitude on the graph */
387 delt = im->maxval - im->minval;
389 fact = 2.0 * pow(10.0,
390 floor(log10(max(fabs(im->minval), fabs(im->maxval)))) - 2);
392 adj = (fact - delt) * 0.55;
394 printf("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n", im->minval, im->maxval, delt, fact, adj);
400 else if(im->extra_flags & ALTAUTOSCALE_MAX) {
401 /* measure the amplitude of the function. Make sure that
402 graph boundaries are slightly higher than max vals
403 so we can see amplitude on the graph */
404 adj = (im->maxval - im->minval) * 0.1;
408 scaled_min = im->minval / im->magfact;
409 scaled_max = im->maxval / im->magfact;
411 for (i=1; sensiblevalues[i] > 0; i++){
412 if (sensiblevalues[i-1]>=scaled_min &&
413 sensiblevalues[i]<=scaled_min)
414 im->minval = sensiblevalues[i]*(im->magfact);
416 if (-sensiblevalues[i-1]<=scaled_min &&
417 -sensiblevalues[i]>=scaled_min)
418 im->minval = -sensiblevalues[i-1]*(im->magfact);
420 if (sensiblevalues[i-1] >= scaled_max &&
421 sensiblevalues[i] <= scaled_max)
422 im->maxval = sensiblevalues[i-1]*(im->magfact);
424 if (-sensiblevalues[i-1]<=scaled_max &&
425 -sensiblevalues[i] >=scaled_max)
426 im->maxval = -sensiblevalues[i]*(im->magfact);
430 /* adjust min and max to the grid definition if there is one */
431 im->minval = (double)im->ylabfact * im->ygridstep *
432 floor(im->minval / ((double)im->ylabfact * im->ygridstep));
433 im->maxval = (double)im->ylabfact * im->ygridstep *
434 ceil(im->maxval /( (double)im->ylabfact * im->ygridstep));
438 fprintf(stderr,"SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
439 im->minval,im->maxval,im->magfact);
444 apply_gridfit(image_desc_t *im)
446 if (isnan(im->minval) || isnan(im->maxval))
449 if (im->logarithmic) {
450 double ya, yb, ypix, ypixfrac;
451 double log10_range = log10(im->maxval) - log10(im->minval);
452 ya = pow((double)10, floor(log10(im->minval)));
453 while (ya < im->minval)
456 return; /* don't have y=10^x gridline */
458 if (yb <= im->maxval) {
459 /* we have at least 2 y=10^x gridlines.
460 Make sure distance between them in pixels
461 are an integer by expanding im->maxval */
462 double y_pixel_delta = ytr(im, ya) - ytr(im, yb);
463 double factor = y_pixel_delta / floor(y_pixel_delta);
464 double new_log10_range = factor * log10_range;
465 double new_ymax_log10 = log10(im->minval) + new_log10_range;
466 im->maxval = pow(10, new_ymax_log10);
467 ytr(im, DNAN); /* reset precalc */
468 log10_range = log10(im->maxval) - log10(im->minval);
470 /* make sure first y=10^x gridline is located on
471 integer pixel position by moving scale slightly
472 downwards (sub-pixel movement) */
473 ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
474 ypixfrac = ypix - floor(ypix);
475 if (ypixfrac > 0 && ypixfrac < 1) {
476 double yfrac = ypixfrac / im->ysize;
477 im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
478 im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
479 ytr(im, DNAN); /* reset precalc */
482 /* Make sure we have an integer pixel distance between
483 each minor gridline */
484 double ypos1 = ytr(im, im->minval);
485 double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
486 double y_pixel_delta = ypos1 - ypos2;
487 double factor = y_pixel_delta / floor(y_pixel_delta);
488 double new_range = factor * (im->maxval - im->minval);
489 double gridstep = im->ygrid_scale.gridstep;
490 double minor_y, minor_y_px, minor_y_px_frac;
491 im->maxval = im->minval + new_range;
492 ytr(im, DNAN); /* reset precalc */
493 /* make sure first minor gridline is on integer pixel y coord */
494 minor_y = gridstep * floor(im->minval / gridstep);
495 while (minor_y < im->minval)
497 minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
498 minor_y_px_frac = minor_y_px - floor(minor_y_px);
499 if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
500 double yfrac = minor_y_px_frac / im->ysize;
501 double range = im->maxval - im->minval;
502 im->minval = im->minval - yfrac * range;
503 im->maxval = im->maxval - yfrac * range;
504 ytr(im, DNAN); /* reset precalc */
506 calc_horizontal_grid(im); /* recalc with changed im->maxval */
510 /* reduce data reimplementation by Alex */
514 enum cf_en cf, /* which consolidation function ?*/
515 unsigned long cur_step, /* step the data currently is in */
516 time_t *start, /* start, end and step as requested ... */
517 time_t *end, /* ... by the application will be ... */
518 unsigned long *step, /* ... adjusted to represent reality */
519 unsigned long *ds_cnt, /* number of data sources in file */
520 rrd_value_t **data) /* two dimensional array containing the data */
522 int i,reduce_factor = ceil((double)(*step) / (double)cur_step);
523 unsigned long col,dst_row,row_cnt,start_offset,end_offset,skiprows=0;
524 rrd_value_t *srcptr,*dstptr;
526 (*step) = cur_step*reduce_factor; /* set new step size for reduced data */
529 row_cnt = ((*end)-(*start))/cur_step;
535 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
536 row_cnt,reduce_factor,*start,*end,cur_step);
537 for (col=0;col<row_cnt;col++) {
538 printf("time %10lu: ",*start+(col+1)*cur_step);
539 for (i=0;i<*ds_cnt;i++)
540 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
545 /* We have to combine [reduce_factor] rows of the source
546 ** into one row for the destination. Doing this we also
547 ** need to take care to combine the correct rows. First
548 ** alter the start and end time so that they are multiples
549 ** of the new step time. We cannot reduce the amount of
550 ** time so we have to move the end towards the future and
551 ** the start towards the past.
553 end_offset = (*end) % (*step);
554 start_offset = (*start) % (*step);
556 /* If there is a start offset (which cannot be more than
557 ** one destination row), skip the appropriate number of
558 ** source rows and one destination row. The appropriate
559 ** number is what we do know (start_offset/cur_step) of
560 ** the new interval (*step/cur_step aka reduce_factor).
563 printf("start_offset: %lu end_offset: %lu\n",start_offset,end_offset);
564 printf("row_cnt before: %lu\n",row_cnt);
567 (*start) = (*start)-start_offset;
568 skiprows=reduce_factor-start_offset/cur_step;
569 srcptr+=skiprows* *ds_cnt;
570 for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
574 printf("row_cnt between: %lu\n",row_cnt);
577 /* At the end we have some rows that are not going to be
578 ** used, the amount is end_offset/cur_step
581 (*end) = (*end)-end_offset+(*step);
582 skiprows = end_offset/cur_step;
586 printf("row_cnt after: %lu\n",row_cnt);
589 /* Sanity check: row_cnt should be multiple of reduce_factor */
590 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
592 if (row_cnt%reduce_factor) {
593 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
594 row_cnt,reduce_factor);
595 printf("BUG in reduce_data()\n");
599 /* Now combine reduce_factor intervals at a time
600 ** into one interval for the destination.
603 for (dst_row=0;row_cnt>=reduce_factor;dst_row++) {
604 for (col=0;col<(*ds_cnt);col++) {
605 rrd_value_t newval=DNAN;
606 unsigned long validval=0;
608 for (i=0;i<reduce_factor;i++) {
609 if (isnan(srcptr[i*(*ds_cnt)+col])) {
613 if (isnan(newval)) newval = srcptr[i*(*ds_cnt)+col];
621 newval += srcptr[i*(*ds_cnt)+col];
624 newval = min (newval,srcptr[i*(*ds_cnt)+col]);
627 /* an interval contains a failure if any subintervals contained a failure */
629 newval = max (newval,srcptr[i*(*ds_cnt)+col]);
632 newval = srcptr[i*(*ds_cnt)+col];
637 if (validval == 0){newval = DNAN;} else{
655 srcptr+=(*ds_cnt)*reduce_factor;
656 row_cnt-=reduce_factor;
658 /* If we had to alter the endtime, we didn't have enough
659 ** source rows to fill the last row. Fill it with NaN.
661 if (end_offset) for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
663 row_cnt = ((*end)-(*start))/ *step;
665 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
666 row_cnt,*start,*end,*step);
667 for (col=0;col<row_cnt;col++) {
668 printf("time %10lu: ",*start+(col+1)*(*step));
669 for (i=0;i<*ds_cnt;i++)
670 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
677 /* get the data required for the graphs from the
681 data_fetch(image_desc_t *im )
686 /* pull the data from the log files ... */
687 for (i=0;i<im->gdes_c;i++){
688 /* only GF_DEF elements fetch data */
689 if (im->gdes[i].gf != GF_DEF)
693 /* do we have it already ?*/
694 for (ii=0;ii<i;ii++) {
695 if (im->gdes[ii].gf != GF_DEF)
697 if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
698 && (im->gdes[i].cf == im->gdes[ii].cf)
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;
732 if (ft_step < im->gdes[i].step) {
733 reduce_data(im->gdes[i].cf,
741 im->gdes[i].step = ft_step;
745 /* lets see if the required data source is realy there */
746 for(ii=0;ii<im->gdes[i].ds_cnt;ii++){
747 if(strcmp(im->gdes[i].ds_namv[ii],im->gdes[i].ds_nam) == 0){
750 if (im->gdes[i].ds== -1){
751 rrd_set_error("No DS called '%s' in '%s'",
752 im->gdes[i].ds_nam,im->gdes[i].rrd);
760 /* evaluate the expressions in the CDEF functions */
762 /*************************************************************
764 *************************************************************/
767 find_var_wrapper(void *arg1, char *key)
769 return find_var((image_desc_t *) arg1, key);
772 /* find gdes containing var*/
774 find_var(image_desc_t *im, char *key){
776 for(ii=0;ii<im->gdes_c-1;ii++){
777 if((im->gdes[ii].gf == GF_DEF
778 || im->gdes[ii].gf == GF_VDEF
779 || im->gdes[ii].gf == GF_CDEF)
780 && (strcmp(im->gdes[ii].vname,key) == 0)){
787 /* find the largest common denominator for all the numbers
788 in the 0 terminated num array */
793 for (i=0;num[i+1]!=0;i++){
795 rest=num[i] % num[i+1];
796 num[i]=num[i+1]; num[i+1]=rest;
800 /* return i==0?num[i]:num[i-1]; */
804 /* run the rpn calculator on all the VDEF and CDEF arguments */
806 data_calc( image_desc_t *im){
810 long *steparray, rpi;
815 rpnstack_init(&rpnstack);
817 for (gdi=0;gdi<im->gdes_c;gdi++){
818 /* Look for GF_VDEF and GF_CDEF in the same loop,
819 * so CDEFs can use VDEFs and vice versa
821 switch (im->gdes[gdi].gf) {
825 /* A VDEF has no DS. This also signals other parts
826 * of rrdtool that this is a VDEF value, not a CDEF.
828 im->gdes[gdi].ds_cnt = 0;
829 if (vdef_calc(im,gdi)) {
830 rrd_set_error("Error processing VDEF '%s'"
833 rpnstack_free(&rpnstack);
838 im->gdes[gdi].ds_cnt = 1;
839 im->gdes[gdi].ds = 0;
840 im->gdes[gdi].data_first = 1;
841 im->gdes[gdi].start = 0;
842 im->gdes[gdi].end = 0;
847 /* Find the variables in the expression.
848 * - VDEF variables are substituted by their values
849 * and the opcode is changed into OP_NUMBER.
850 * - CDEF variables are analized for their step size,
851 * the lowest common denominator of all the step
852 * sizes of the data sources involved is calculated
853 * and the resulting number is the step size for the
854 * resulting data source.
856 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
857 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
858 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER){
859 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
860 if (im->gdes[ptr].ds_cnt == 0) {
862 printf("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
864 im->gdes[ptr].vname);
865 printf("DEBUG: value from vdef is %f\n",im->gdes[ptr].vf.val);
867 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
868 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
871 rrd_realloc(steparray,
872 (++stepcnt+1)*sizeof(*steparray)))==NULL){
873 rrd_set_error("realloc steparray");
874 rpnstack_free(&rpnstack);
878 steparray[stepcnt-1] = im->gdes[ptr].step;
880 /* adjust start and end of cdef (gdi) so
881 * that it runs from the latest start point
882 * to the earliest endpoint of any of the
883 * rras involved (ptr)
885 if(im->gdes[gdi].start < im->gdes[ptr].start)
886 im->gdes[gdi].start = im->gdes[ptr].start;
888 if(im->gdes[gdi].end == 0 ||
889 im->gdes[gdi].end > im->gdes[ptr].end)
890 im->gdes[gdi].end = im->gdes[ptr].end;
892 /* store pointer to the first element of
893 * the rra providing data for variable,
894 * further save step size and data source
897 im->gdes[gdi].rpnp[rpi].data = im->gdes[ptr].data + im->gdes[ptr].ds;
898 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
899 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
901 /* backoff the *.data ptr; this is done so
902 * rpncalc() function doesn't have to treat
903 * the first case differently
905 } /* if ds_cnt != 0 */
906 } /* if OP_VARIABLE */
907 } /* loop through all rpi */
909 /* move the data pointers to the correct period */
910 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
911 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
912 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER){
913 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
914 if(im->gdes[gdi].start > im->gdes[ptr].start) {
915 im->gdes[gdi].rpnp[rpi].data += im->gdes[gdi].rpnp[rpi].ds_cnt;
921 if(steparray == NULL){
922 rrd_set_error("rpn expressions without DEF"
923 " or CDEF variables are not supported");
924 rpnstack_free(&rpnstack);
927 steparray[stepcnt]=0;
928 /* Now find the resulting step. All steps in all
929 * used RRAs have to be visited
931 im->gdes[gdi].step = lcd(steparray);
933 if((im->gdes[gdi].data = malloc((
934 (im->gdes[gdi].end-im->gdes[gdi].start)
935 / im->gdes[gdi].step)
936 * sizeof(double)))==NULL){
937 rrd_set_error("malloc im->gdes[gdi].data");
938 rpnstack_free(&rpnstack);
942 /* Step through the new cdef results array and
943 * calculate the values
945 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
946 now<=im->gdes[gdi].end;
947 now += im->gdes[gdi].step)
949 rpnp_t *rpnp = im -> gdes[gdi].rpnp;
951 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
952 * in this case we are advancing by timesteps;
953 * we use the fact that time_t is a synonym for long
955 if (rpn_calc(rpnp,&rpnstack,(long) now,
956 im->gdes[gdi].data,++dataidx) == -1) {
957 /* rpn_calc sets the error string */
958 rpnstack_free(&rpnstack);
961 } /* enumerate over time steps within a CDEF */
966 } /* enumerate over CDEFs */
967 rpnstack_free(&rpnstack);
971 /* massage data so, that we get one value for each x coordinate in the graph */
973 data_proc( image_desc_t *im ){
975 double pixstep = (double)(im->end-im->start)
976 /(double)im->xsize; /* how much time
977 passes in one pixel */
979 double minval=DNAN,maxval=DNAN;
981 unsigned long gr_time;
983 /* memory for the processed data */
984 for(i=0;i<im->gdes_c;i++) {
985 if((im->gdes[i].gf==GF_LINE) ||
986 (im->gdes[i].gf==GF_AREA) ||
987 (im->gdes[i].gf==GF_TICK) ||
988 (im->gdes[i].gf==GF_STACK)) {
989 if((im->gdes[i].p_data = malloc((im->xsize +1)
990 * sizeof(rrd_value_t)))==NULL){
991 rrd_set_error("malloc data_proc");
997 for (i=0;i<im->xsize;i++) { /* for each pixel */
999 gr_time = im->start+pixstep*i; /* time of the current step */
1002 for (ii=0;ii<im->gdes_c;ii++) {
1004 switch (im->gdes[ii].gf) {
1008 if (!im->gdes[ii].stack)
1011 value = im->gdes[ii].yrule;
1012 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1013 /* The time of the data doesn't necessarily match
1014 ** the time of the graph. Beware.
1016 vidx = im->gdes[ii].vidx;
1017 if ( (gr_time >= im->gdes[vidx].start) &&
1018 (gr_time <= im->gdes[vidx].end) ) {
1019 value = im->gdes[vidx].data[
1020 (unsigned long) floor(
1021 (double)(gr_time - im->gdes[vidx].start)
1022 / im->gdes[vidx].step)
1023 * im->gdes[vidx].ds_cnt
1031 if (! isnan(value)) {
1033 im->gdes[ii].p_data[i] = paintval;
1034 /* GF_TICK: the data values are not
1035 ** relevant for min and max
1037 if (finite(paintval) && im->gdes[ii].gf != GF_TICK ) {
1038 if (isnan(minval) || paintval < minval)
1040 if (isnan(maxval) || paintval > maxval)
1044 im->gdes[ii].p_data[i] = DNAN;
1053 /* if min or max have not been asigned a value this is because
1054 there was no data in the graph ... this is not good ...
1055 lets set these to dummy values then ... */
1057 if (isnan(minval)) minval = 0.0;
1058 if (isnan(maxval)) maxval = 1.0;
1060 /* adjust min and max values */
1061 if (isnan(im->minval)
1062 /* don't adjust low-end with log scale */
1063 || ((!im->logarithmic && !im->rigid) && im->minval > minval)
1065 im->minval = minval;
1066 if (isnan(im->maxval)
1067 || (!im->rigid && im->maxval < maxval)
1069 if (im->logarithmic)
1070 im->maxval = maxval * 1.1;
1072 im->maxval = maxval;
1074 /* make sure min and max are not equal */
1075 if (im->minval == im->maxval) {
1077 if (! im->logarithmic) {
1080 /* make sure min and max are not both zero */
1081 if (im->maxval == 0.0) {
1090 /* identify the point where the first gridline, label ... gets placed */
1094 time_t start, /* what is the initial time */
1095 enum tmt_en baseint, /* what is the basic interval */
1096 long basestep /* how many if these do we jump a time */
1100 localtime_r(&start, &tm);
1103 tm.tm_sec -= tm.tm_sec % basestep; break;
1106 tm.tm_min -= tm.tm_min % basestep;
1111 tm.tm_hour -= tm.tm_hour % basestep; break;
1113 /* we do NOT look at the basestep for this ... */
1116 tm.tm_hour = 0; break;
1118 /* we do NOT look at the basestep for this ... */
1122 tm.tm_mday -= tm.tm_wday -1; /* -1 because we want the monday */
1123 if (tm.tm_wday==0) tm.tm_mday -= 7; /* we want the *previous* monday */
1130 tm.tm_mon -= tm.tm_mon % basestep; break;
1138 tm.tm_year -= (tm.tm_year+1900) % basestep;
1143 /* identify the point where the next gridline, label ... gets placed */
1146 time_t current, /* what is the initial time */
1147 enum tmt_en baseint, /* what is the basic interval */
1148 long basestep /* how many if these do we jump a time */
1153 localtime_r(¤t, &tm);
1157 tm.tm_sec += basestep; break;
1159 tm.tm_min += basestep; break;
1161 tm.tm_hour += basestep; break;
1163 tm.tm_mday += basestep; break;
1165 tm.tm_mday += 7*basestep; break;
1167 tm.tm_mon += basestep; break;
1169 tm.tm_year += basestep;
1171 madetime = mktime(&tm);
1172 } while (madetime == -1); /* this is necessary to skip impssible times
1173 like the daylight saving time skips */
1179 /* calculate values required for PRINT and GPRINT functions */
1182 print_calc(image_desc_t *im, char ***prdata)
1184 long i,ii,validsteps;
1187 int graphelement = 0;
1190 double magfact = -1;
1194 if (im->imginfo) prlines++;
1195 for(i=0;i<im->gdes_c;i++){
1196 switch(im->gdes[i].gf){
1199 if(((*prdata) = rrd_realloc((*prdata),prlines*sizeof(char *)))==NULL){
1200 rrd_set_error("realloc prdata");
1204 /* PRINT and GPRINT can now print VDEF generated values.
1205 * There's no need to do any calculations on them as these
1206 * calculations were already made.
1208 vidx = im->gdes[i].vidx;
1209 if (im->gdes[vidx].gf==GF_VDEF) { /* simply use vals */
1210 printval = im->gdes[vidx].vf.val;
1211 printtime = im->gdes[vidx].vf.when;
1212 } else { /* need to calculate max,min,avg etcetera */
1213 max_ii =((im->gdes[vidx].end
1214 - im->gdes[vidx].start)
1215 / im->gdes[vidx].step
1216 * im->gdes[vidx].ds_cnt);
1219 for( ii=im->gdes[vidx].ds;
1221 ii+=im->gdes[vidx].ds_cnt){
1222 if (! finite(im->gdes[vidx].data[ii]))
1224 if (isnan(printval)){
1225 printval = im->gdes[vidx].data[ii];
1230 switch (im->gdes[i].cf){
1233 case CF_DEVSEASONAL:
1237 printval += im->gdes[vidx].data[ii];
1240 printval = min( printval, im->gdes[vidx].data[ii]);
1244 printval = max( printval, im->gdes[vidx].data[ii]);
1247 printval = im->gdes[vidx].data[ii];
1250 if (im->gdes[i].cf==CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1251 if (validsteps > 1) {
1252 printval = (printval / validsteps);
1255 } /* prepare printval */
1257 if (!strcmp(im->gdes[i].format,"%c")) { /* VDEF time print */
1258 char ctime_buf[128]; /* PS: for ctime_r, must be >= 26 chars */
1259 if (im->gdes[i].gf == GF_PRINT){
1260 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1261 sprintf((*prdata)[prlines-2],"%s (%lu)",
1262 ctime_r(&printtime,ctime_buf),printtime);
1263 (*prdata)[prlines-1] = NULL;
1265 sprintf(im->gdes[i].legend,"%s (%lu)",
1266 ctime_r(&printtime,ctime_buf),printtime);
1270 if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) {
1271 /* Magfact is set to -1 upon entry to print_calc. If it
1272 * is still less than 0, then we need to run auto_scale.
1273 * Otherwise, put the value into the correct units. If
1274 * the value is 0, then do not set the symbol or magnification
1275 * so next the calculation will be performed again. */
1276 if (magfact < 0.0) {
1277 auto_scale(im,&printval,&si_symb,&magfact);
1278 if (printval == 0.0)
1281 printval /= magfact;
1283 *(++percent_s) = 's';
1284 } else if (strstr(im->gdes[i].format,"%s") != NULL) {
1285 auto_scale(im,&printval,&si_symb,&magfact);
1288 if (im->gdes[i].gf == GF_PRINT){
1289 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1290 (*prdata)[prlines-1] = NULL;
1291 if (bad_format(im->gdes[i].format)) {
1292 rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1295 #ifdef HAVE_SNPRINTF
1296 snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
1298 sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
1303 if (bad_format(im->gdes[i].format)) {
1304 rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1307 #ifdef HAVE_SNPRINTF
1308 snprintf(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
1310 sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
1333 return graphelement;
1337 /* place legends with color spots */
1339 leg_place(image_desc_t *im)
1342 int interleg = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1343 int box =im->text_prop[TEXT_PROP_LEGEND].size*1.5;
1344 int border = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1345 int fill=0, fill_last;
1347 int leg_x = border, leg_y = im->yimg;
1351 char prt_fctn; /*special printfunctions */
1354 if( !(im->extra_flags & NOLEGEND) ) {
1355 if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1356 rrd_set_error("malloc for legspace");
1360 for(i=0;i<im->gdes_c;i++){
1363 /* hid legends for rules which are not displayed */
1365 if (im->gdes[i].gf == GF_HRULE &&
1366 (im->gdes[i].yrule < im->minval || im->gdes[i].yrule > im->maxval))
1367 im->gdes[i].legend[0] = '\0';
1369 if (im->gdes[i].gf == GF_VRULE &&
1370 (im->gdes[i].xrule < im->start || im->gdes[i].xrule > im->end))
1371 im->gdes[i].legend[0] = '\0';
1373 leg_cc = strlen(im->gdes[i].legend);
1375 /* is there a controle code ant the end of the legend string ? */
1376 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\') {
1377 prt_fctn = im->gdes[i].legend[leg_cc-1];
1379 im->gdes[i].legend[leg_cc] = '\0';
1383 /* remove exess space */
1384 while (prt_fctn=='g' &&
1386 im->gdes[i].legend[leg_cc-1]==' '){
1388 im->gdes[i].legend[leg_cc]='\0';
1391 legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1394 /* no interleg space if string ends in \g */
1395 fill += legspace[i];
1397 if (im->gdes[i].gf != GF_GPRINT &&
1398 im->gdes[i].gf != GF_COMMENT) {
1401 fill += gfx_get_text_width(im->canvas, fill+border,
1402 im->text_prop[TEXT_PROP_LEGEND].font,
1403 im->text_prop[TEXT_PROP_LEGEND].size,
1405 im->gdes[i].legend);
1410 /* who said there was a special tag ... ?*/
1411 if (prt_fctn=='g') {
1414 if (prt_fctn == '\0') {
1415 if (i == im->gdes_c -1 ) prt_fctn ='l';
1417 /* is it time to place the legends ? */
1418 if (fill > im->ximg - 2*border){
1433 if (prt_fctn != '\0'){
1435 if (leg_c >= 2 && prt_fctn == 'j') {
1436 glue = (im->ximg - fill - 2* border) / (leg_c-1);
1440 if (prt_fctn =='c') leg_x = (im->ximg - fill) / 2.0;
1441 if (prt_fctn =='r') leg_x = im->ximg - fill - border;
1443 for(ii=mark;ii<=i;ii++){
1444 if(im->gdes[ii].legend[0]=='\0')
1446 im->gdes[ii].leg_x = leg_x;
1447 im->gdes[ii].leg_y = leg_y;
1449 gfx_get_text_width(im->canvas, leg_x,
1450 im->text_prop[TEXT_PROP_LEGEND].font,
1451 im->text_prop[TEXT_PROP_LEGEND].size,
1453 im->gdes[ii].legend)
1456 if (im->gdes[ii].gf != GF_GPRINT &&
1457 im->gdes[ii].gf != GF_COMMENT)
1460 leg_y = leg_y + im->text_prop[TEXT_PROP_LEGEND].size*1.2;
1461 if (prt_fctn == 's') leg_y -= im->text_prop[TEXT_PROP_LEGEND].size*1.2;
1473 /* create a grid on the graph. it determines what to do
1474 from the values of xsize, start and end */
1476 /* the xaxis labels are determined from the number of seconds per pixel
1477 in the requested graph */
1482 calc_horizontal_grid(image_desc_t *im)
1488 int decimals, fractionals;
1490 im->ygrid_scale.labfact=2;
1492 range = im->maxval - im->minval;
1493 scaledrange = range / im->magfact;
1495 /* does the scale of this graph make it impossible to put lines
1496 on it? If so, give up. */
1497 if (isnan(scaledrange)) {
1501 /* find grid spaceing */
1503 if(isnan(im->ygridstep)){
1504 if(im->extra_flags & ALTYGRID) {
1505 /* find the value with max number of digits. Get number of digits */
1506 decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))));
1507 if(decimals <= 0) /* everything is small. make place for zero */
1510 fractionals = floor(log10(range));
1511 if(fractionals < 0) /* small amplitude. */
1512 sprintf(im->ygrid_scale.labfmt, "%%%d.%df", decimals - fractionals + 1, -fractionals + 1);
1514 sprintf(im->ygrid_scale.labfmt, "%%%d.1f", decimals + 1);
1515 im->ygrid_scale.gridstep = pow((double)10, (double)fractionals);
1516 if(im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1517 im->ygrid_scale.gridstep = 0.1;
1518 /* should have at least 5 lines but no more then 15 */
1519 if(range/im->ygrid_scale.gridstep < 5)
1520 im->ygrid_scale.gridstep /= 10;
1521 if(range/im->ygrid_scale.gridstep > 15)
1522 im->ygrid_scale.gridstep *= 10;
1523 if(range/im->ygrid_scale.gridstep > 5) {
1524 im->ygrid_scale.labfact = 1;
1525 if(range/im->ygrid_scale.gridstep > 8)
1526 im->ygrid_scale.labfact = 2;
1529 im->ygrid_scale.gridstep /= 5;
1530 im->ygrid_scale.labfact = 5;
1534 for(i=0;ylab[i].grid > 0;i++){
1535 pixel = im->ysize / (scaledrange / ylab[i].grid);
1536 if (gridind == -1 && pixel > 5) {
1543 if (pixel * ylab[gridind].lfac[i] >= 2 * im->text_prop[TEXT_PROP_AXIS].size) {
1544 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1549 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1552 im->ygrid_scale.gridstep = im->ygridstep;
1553 im->ygrid_scale.labfact = im->ylabfact;
1558 int draw_horizontal_grid(image_desc_t *im)
1562 char graph_label[100];
1563 double X0=im->xorigin;
1564 double X1=im->xorigin+im->xsize;
1566 int sgrid = (int)( im->minval / im->ygrid_scale.gridstep - 1);
1567 int egrid = (int)( im->maxval / im->ygrid_scale.gridstep + 1);
1568 scaledstep = im->ygrid_scale.gridstep/im->magfact;
1569 for (i = sgrid; i <= egrid; i++){
1570 double Y0=ytr(im,im->ygrid_scale.gridstep*i);
1571 if ( Y0 >= im->yorigin-im->ysize
1572 && Y0 <= im->yorigin){
1573 if(i % im->ygrid_scale.labfact == 0){
1574 if (i==0 || im->symbol == ' ') {
1576 if(im->extra_flags & ALTYGRID) {
1577 sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*i);
1580 sprintf(graph_label,"%4.1f",scaledstep*i);
1583 sprintf(graph_label,"%4.0f",scaledstep*i);
1587 sprintf(graph_label,"%4.1f %c",scaledstep*i, im->symbol);
1589 sprintf(graph_label,"%4.0f %c",scaledstep*i, im->symbol);
1593 gfx_new_text ( im->canvas,
1594 X0-im->text_prop[TEXT_PROP_AXIS].size/1.5, Y0,
1595 im->graph_col[GRC_FONT],
1596 im->text_prop[TEXT_PROP_AXIS].font,
1597 im->text_prop[TEXT_PROP_AXIS].size,
1598 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1600 gfx_new_dashed_line ( im->canvas,
1603 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1604 im->grid_dash_on, im->grid_dash_off);
1606 } else if (!(im->extra_flags & NOMINOR)) {
1607 gfx_new_dashed_line ( im->canvas,
1610 GRIDWIDTH, im->graph_col[GRC_GRID],
1611 im->grid_dash_on, im->grid_dash_off);
1619 /* logaritmic horizontal grid */
1621 horizontal_log_grid(image_desc_t *im)
1625 int minoridx=0, majoridx=0;
1626 char graph_label[100];
1628 double value, pixperstep, minstep;
1630 /* find grid spaceing */
1631 pixpex= (double)im->ysize / (log10(im->maxval) - log10(im->minval));
1633 if (isnan(pixpex)) {
1637 for(i=0;yloglab[i][0] > 0;i++){
1638 minstep = log10(yloglab[i][0]);
1639 for(ii=1;yloglab[i][ii+1] > 0;ii++){
1640 if(yloglab[i][ii+2]==0){
1641 minstep = log10(yloglab[i][ii+1])-log10(yloglab[i][ii]);
1645 pixperstep = pixpex * minstep;
1646 if(pixperstep > 5){minoridx = i;}
1647 if(pixperstep > 2 * im->text_prop[TEXT_PROP_LEGEND].size){majoridx = i;}
1651 X1=im->xorigin+im->xsize;
1652 /* paint minor grid */
1653 for (value = pow((double)10, log10(im->minval)
1654 - fmod(log10(im->minval),log10(yloglab[minoridx][0])));
1655 value <= im->maxval;
1656 value *= yloglab[minoridx][0]){
1657 if (value < im->minval) continue;
1659 while(yloglab[minoridx][++i] > 0){
1660 Y0 = ytr(im,value * yloglab[minoridx][i]);
1661 if (Y0 <= im->yorigin - im->ysize) break;
1662 gfx_new_dashed_line ( im->canvas,
1665 GRIDWIDTH, im->graph_col[GRC_GRID],
1666 im->grid_dash_on, im->grid_dash_off);
1670 /* paint major grid and labels*/
1671 for (value = pow((double)10, log10(im->minval)
1672 - fmod(log10(im->minval),log10(yloglab[majoridx][0])));
1673 value <= im->maxval;
1674 value *= yloglab[majoridx][0]){
1675 if (value < im->minval) continue;
1677 while(yloglab[majoridx][++i] > 0){
1678 Y0 = ytr(im,value * yloglab[majoridx][i]);
1679 if (Y0 <= im->yorigin - im->ysize) break;
1680 gfx_new_dashed_line ( im->canvas,
1683 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1684 im->grid_dash_on, im->grid_dash_off);
1686 sprintf(graph_label,"%3.0e",value * yloglab[majoridx][i]);
1687 gfx_new_text ( im->canvas,
1688 X0-im->text_prop[TEXT_PROP_AXIS].size/1.5, Y0,
1689 im->graph_col[GRC_FONT],
1690 im->text_prop[TEXT_PROP_AXIS].font,
1691 im->text_prop[TEXT_PROP_AXIS].size,
1692 im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1704 int xlab_sel; /* which sort of label and grid ? */
1705 time_t ti, tilab, timajor;
1707 char graph_label[100];
1708 double X0,Y0,Y1; /* points for filled graph and more*/
1711 /* the type of time grid is determined by finding
1712 the number of seconds per pixel in the graph */
1715 if(im->xlab_user.minsec == -1){
1716 factor=(im->end - im->start)/im->xsize;
1718 while ( xlab[xlab_sel+1].minsec != -1
1719 && xlab[xlab_sel+1].minsec <= factor){ xlab_sel++; }
1720 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
1721 im->xlab_user.gridst = xlab[xlab_sel].gridst;
1722 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
1723 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
1724 im->xlab_user.labtm = xlab[xlab_sel].labtm;
1725 im->xlab_user.labst = xlab[xlab_sel].labst;
1726 im->xlab_user.precis = xlab[xlab_sel].precis;
1727 im->xlab_user.stst = xlab[xlab_sel].stst;
1730 /* y coords are the same for every line ... */
1732 Y1 = im->yorigin-im->ysize;
1735 /* paint the minor grid */
1736 if (!(im->extra_flags & NOMINOR))
1738 for(ti = find_first_time(im->start,
1739 im->xlab_user.gridtm,
1740 im->xlab_user.gridst),
1741 timajor = find_first_time(im->start,
1742 im->xlab_user.mgridtm,
1743 im->xlab_user.mgridst);
1745 ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
1747 /* are we inside the graph ? */
1748 if (ti < im->start || ti > im->end) continue;
1749 while (timajor < ti) {
1750 timajor = find_next_time(timajor,
1751 im->xlab_user.mgridtm, im->xlab_user.mgridst);
1753 if (ti == timajor) continue; /* skip as falls on major grid line */
1755 gfx_new_dashed_line(im->canvas,X0,Y0+1, X0,Y1-1,GRIDWIDTH,
1756 im->graph_col[GRC_GRID],
1757 im->grid_dash_on, im->grid_dash_off);
1762 /* paint the major grid */
1763 for(ti = find_first_time(im->start,
1764 im->xlab_user.mgridtm,
1765 im->xlab_user.mgridst);
1767 ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
1769 /* are we inside the graph ? */
1770 if (ti < im->start || ti > im->end) continue;
1772 gfx_new_dashed_line(im->canvas,X0,Y0+3, X0,Y1-2,MGRIDWIDTH,
1773 im->graph_col[GRC_MGRID],
1774 im->grid_dash_on, im->grid_dash_off);
1777 /* paint the labels below the graph */
1778 for(ti = find_first_time(im->start,
1779 im->xlab_user.labtm,
1780 im->xlab_user.labst);
1782 ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
1784 tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
1785 /* are we inside the graph ? */
1786 if (ti < im->start || ti > im->end) continue;
1789 localtime_r(&tilab, &tm);
1790 strftime(graph_label,99,im->xlab_user.stst, &tm);
1792 # error "your libc has no strftime I guess we'll abort the exercise here."
1794 gfx_new_text ( im->canvas,
1795 xtr(im,tilab), Y0+im->text_prop[TEXT_PROP_AXIS].size/1.5,
1796 im->graph_col[GRC_FONT],
1797 im->text_prop[TEXT_PROP_AXIS].font,
1798 im->text_prop[TEXT_PROP_AXIS].size,
1799 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP,
1812 /* draw x and y axis */
1813 gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
1814 im->xorigin+im->xsize,im->yorigin-im->ysize,
1815 GRIDWIDTH, im->graph_col[GRC_GRID]);
1817 gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
1818 im->xorigin+im->xsize,im->yorigin-im->ysize,
1819 GRIDWIDTH, im->graph_col[GRC_GRID]);
1821 gfx_new_line ( im->canvas, im->xorigin-4,im->yorigin,
1822 im->xorigin+im->xsize+4,im->yorigin,
1823 MGRIDWIDTH, im->graph_col[GRC_GRID]);
1825 gfx_new_line ( im->canvas, im->xorigin,im->yorigin+4,
1826 im->xorigin,im->yorigin-im->ysize-4,
1827 MGRIDWIDTH, im->graph_col[GRC_GRID]);
1830 /* arrow for X axis direction */
1831 gfx_new_area ( im->canvas,
1832 im->xorigin+im->xsize+3, im->yorigin-3,
1833 im->xorigin+im->xsize+3, im->yorigin+4,
1834 im->xorigin+im->xsize+8, im->yorigin+0.5, /* LINEOFFSET */
1835 im->graph_col[GRC_ARROW]);
1842 grid_paint(image_desc_t *im)
1846 double X0,Y0; /* points for filled graph and more*/
1849 /* draw 3d border */
1850 node = gfx_new_area (im->canvas, 0,im->yimg,
1852 2,2,im->graph_col[GRC_SHADEA]);
1853 gfx_add_point( node , im->ximg - 2, 2 );
1854 gfx_add_point( node , im->ximg, 0 );
1855 gfx_add_point( node , 0,0 );
1856 /* gfx_add_point( node , 0,im->yimg ); */
1858 node = gfx_new_area (im->canvas, 2,im->yimg-2,
1859 im->ximg-2,im->yimg-2,
1861 im->graph_col[GRC_SHADEB]);
1862 gfx_add_point( node , im->ximg,0);
1863 gfx_add_point( node , im->ximg,im->yimg);
1864 gfx_add_point( node , 0,im->yimg);
1865 /* gfx_add_point( node , 0,im->yimg ); */
1868 if (im->draw_x_grid == 1 )
1871 if (im->draw_y_grid == 1){
1872 if(im->logarithmic){
1873 res = horizontal_log_grid(im);
1875 res = draw_horizontal_grid(im);
1878 /* dont draw horizontal grid if there is no min and max val */
1880 char *nodata = "No Data found";
1881 gfx_new_text(im->canvas,im->ximg/2, (2*im->yorigin-im->ysize) / 2,
1882 im->graph_col[GRC_FONT],
1883 im->text_prop[TEXT_PROP_AXIS].font,
1884 im->text_prop[TEXT_PROP_AXIS].size,
1885 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
1890 /* yaxis description */
1891 if (im->canvas->imgformat != IF_PNG) {
1892 gfx_new_text( im->canvas,
1893 7, (im->yorigin - im->ysize/2),
1894 im->graph_col[GRC_FONT],
1895 im->text_prop[TEXT_PROP_AXIS].font,
1896 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 270.0,
1897 GFX_H_CENTER, GFX_V_CENTER,
1900 /* horrible hack until we can actually print vertically */
1903 int l=strlen(im->ylegend);
1905 for (n=0;n<strlen(im->ylegend);n++) {
1906 s[0]=im->ylegend[n];
1908 gfx_new_text(im->canvas,7,im->text_prop[TEXT_PROP_AXIS].size*(l-n),
1909 im->graph_col[GRC_FONT],
1910 im->text_prop[TEXT_PROP_AXIS].font,
1911 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 270.0,
1912 GFX_H_CENTER, GFX_V_CENTER,
1919 gfx_new_text( im->canvas,
1920 im->ximg/2, im->text_prop[TEXT_PROP_TITLE].size,
1921 im->graph_col[GRC_FONT],
1922 im->text_prop[TEXT_PROP_TITLE].font,
1923 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
1924 GFX_H_CENTER, GFX_V_CENTER,
1928 if( !(im->extra_flags & NOLEGEND) ) {
1929 for(i=0;i<im->gdes_c;i++){
1930 if(im->gdes[i].legend[0] =='\0')
1933 /* im->gdes[i].leg_y is the bottom of the legend */
1934 X0 = im->gdes[i].leg_x;
1935 Y0 = im->gdes[i].leg_y;
1937 if ( im->gdes[i].gf != GF_GPRINT
1938 && im->gdes[i].gf != GF_COMMENT) {
1941 boxH = gfx_get_text_width(im->canvas, 0,
1942 im->text_prop[TEXT_PROP_AXIS].font,
1943 im->text_prop[TEXT_PROP_AXIS].size,
1944 im->tabwidth,"M") * 1.25;
1947 node = gfx_new_area(im->canvas,
1952 gfx_add_point ( node, X0+boxH, Y0-boxV );
1953 node = gfx_new_line(im->canvas,
1956 gfx_add_point(node,X0+boxH,Y0);
1957 gfx_add_point(node,X0+boxH,Y0-boxV);
1958 gfx_close_path(node);
1959 X0 += boxH / 1.25 * 2;
1961 gfx_new_text ( im->canvas, X0, Y0,
1962 im->graph_col[GRC_FONT],
1963 im->text_prop[TEXT_PROP_AXIS].font,
1964 im->text_prop[TEXT_PROP_AXIS].size,
1965 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
1966 im->gdes[i].legend );
1972 /*****************************************************
1973 * lazy check make sure we rely need to create this graph
1974 *****************************************************/
1976 int lazy_check(image_desc_t *im){
1979 struct stat imgstat;
1981 if (im->lazy == 0) return 0; /* no lazy option */
1982 if (stat(im->graphfile,&imgstat) != 0)
1983 return 0; /* can't stat */
1984 /* one pixel in the existing graph is more then what we would
1986 if (time(NULL) - imgstat.st_mtime >
1987 (im->end - im->start) / im->xsize)
1989 if ((fd = fopen(im->graphfile,"rb")) == NULL)
1990 return 0; /* the file does not exist */
1991 switch (im->canvas->imgformat) {
1993 size = PngSize(fd,&(im->ximg),&(im->yimg));
2003 pie_part(image_desc_t *im, gfx_color_t color,
2004 double PieCenterX, double PieCenterY, double Radius,
2005 double startangle, double endangle)
2009 double step=M_PI/50; /* Number of iterations for the circle;
2010 ** 10 is definitely too low, more than
2011 ** 50 seems to be overkill
2014 /* Strange but true: we have to work clockwise or else
2015 ** anti aliasing nor transparency don't work.
2017 ** This test is here to make sure we do it right, also
2018 ** this makes the for...next loop more easy to implement.
2019 ** The return will occur if the user enters a negative number
2020 ** (which shouldn't be done according to the specs) or if the
2021 ** programmers do something wrong (which, as we all know, never
2022 ** happens anyway :)
2024 if (endangle<startangle) return;
2026 /* Hidden feature: Radius decreases each full circle */
2028 while (angle>=2*M_PI) {
2033 node=gfx_new_area(im->canvas,
2034 PieCenterX+sin(startangle)*Radius,
2035 PieCenterY-cos(startangle)*Radius,
2038 PieCenterX+sin(endangle)*Radius,
2039 PieCenterY-cos(endangle)*Radius,
2041 for (angle=endangle;angle-startangle>=step;angle-=step) {
2043 PieCenterX+sin(angle)*Radius,
2044 PieCenterY-cos(angle)*Radius );
2049 graph_size_location(image_desc_t *im, int elements, int piechart )
2051 /* The actual size of the image to draw is determined from
2052 ** several sources. The size given on the command line is
2053 ** the graph area but we need more as we have to draw labels
2054 ** and other things outside the graph area
2057 /* +-+-------------------------------------------+
2058 ** |l|.................title.....................|
2059 ** |e+--+-------------------------------+--------+
2062 ** |l| l| main graph area | chart |
2065 ** |r+--+-------------------------------+--------+
2066 ** |e| | x-axis labels | |
2067 ** |v+--+-------------------------------+--------+
2068 ** | |..............legends......................|
2069 ** +-+-------------------------------------------+
2071 int Xvertical=0, Yvertical=0,
2072 Xtitle =0, Ytitle =0,
2073 Xylabel =0, Yylabel =0,
2076 Xxlabel =0, Yxlabel =0,
2078 Xlegend =0, Ylegend =0,
2080 Xspacing =10, Yspacing =10;
2082 if (im->ylegend[0] != '\0') {
2083 Xvertical = im->text_prop[TEXT_PROP_LEGEND].size *2;
2084 Yvertical = im->text_prop[TEXT_PROP_LEGEND].size * (strlen(im->ylegend)+1);
2087 if (im->title[0] != '\0') {
2088 /* The title is placed "inbetween" two text lines so it
2089 ** automatically has some vertical spacing. The horizontal
2090 ** spacing is added here, on each side.
2092 Xtitle = gfx_get_text_width(im->canvas, 0,
2093 im->text_prop[TEXT_PROP_TITLE].font,
2094 im->text_prop[TEXT_PROP_TITLE].size,
2096 im->title) + 2*Xspacing;
2097 Ytitle = im->text_prop[TEXT_PROP_TITLE].size*2;
2103 if (im->draw_x_grid) {
2105 Yxlabel=im->text_prop[TEXT_PROP_LEGEND].size *2;
2107 if (im->draw_y_grid) {
2108 Xylabel=im->text_prop[TEXT_PROP_LEGEND].size *6;
2114 im->piesize=im->xsize<im->ysize?im->xsize:im->ysize;
2119 /* Now calculate the total size. Insert some spacing where
2120 desired. im->xorigin and im->yorigin need to correspond
2121 with the lower left corner of the main graph area or, if
2122 this one is not set, the imaginary box surrounding the
2125 /* The legend width cannot yet be determined, as a result we
2126 ** have problems adjusting the image to it. For now, we just
2127 ** forget about it at all; the legend will have to fit in the
2128 ** size already allocated.
2130 im->ximg = Xylabel + Xmain + Xpie + Xspacing;
2131 if (Xmain) im->ximg += Xspacing;
2132 if (Xpie) im->ximg += Xspacing;
2133 im->xorigin = Xspacing + Xylabel;
2134 if (Xtitle > im->ximg) im->ximg = Xtitle;
2136 im->ximg += Xvertical;
2137 im->xorigin += Xvertical;
2141 /* The vertical size is interesting... we need to compare
2142 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend} with Yvertical
2143 ** however we need to know {Ytitle+Ymain+Yxlabel} in order to
2144 ** start even thinking about Ylegend.
2146 ** Do it in three portions: First calculate the inner part,
2147 ** then do the legend, then adjust the total height of the img.
2150 /* reserve space for main and/or pie */
2151 im->yimg = Ymain + Yxlabel;
2152 if (im->yimg < Ypie) im->yimg = Ypie;
2153 im->yorigin = im->yimg - Yxlabel;
2154 /* reserve space for the title *or* some padding above the graph */
2157 im->yorigin += Ytitle;
2159 im->yimg += Yspacing;
2160 im->yorigin += Yspacing;
2162 /* reserve space for padding below the graph */
2163 im->yimg += Yspacing;
2166 /* Determine where to place the legends onto the image.
2167 ** Adjust im->yimg to match the space requirements.
2169 if(leg_place(im)==-1)
2172 /* last of three steps: check total height of image */
2173 if (im->yimg < Yvertical) im->yimg = Yvertical;
2176 if (Xlegend > im->ximg) {
2178 /* reposition Pie */
2182 /* The pie is placed in the upper right hand corner,
2183 ** just below the title (if any) and with sufficient
2187 im->pie_x = im->ximg - Xspacing - Xpie/2;
2188 im->pie_y = im->yorigin-Ymain+Ypie/2;
2190 im->pie_x = im->ximg/2;
2191 im->pie_y = im->yorigin-Ypie/2;
2197 /* draw that picture thing ... */
2199 graph_paint(image_desc_t *im, char ***calcpr)
2202 int lazy = lazy_check(im);
2204 double PieStart=0.0;
2208 double areazero = 0.0;
2209 enum gf_en stack_gf = GF_PRINT;
2210 graph_desc_t *lastgdes = NULL;
2212 /* if we are lazy and there is nothing to PRINT ... quit now */
2213 if (lazy && im->prt_c==0) return 0;
2215 /* pull the data from the rrd files ... */
2217 if(data_fetch(im)==-1)
2220 /* evaluate VDEF and CDEF operations ... */
2221 if(data_calc(im)==-1)
2224 /* check if we need to draw a piechart */
2225 for(i=0;i<im->gdes_c;i++){
2226 if (im->gdes[i].gf == GF_PART) {
2232 /* calculate and PRINT and GPRINT definitions. We have to do it at
2233 * this point because it will affect the length of the legends
2234 * if there are no graph elements we stop here ...
2235 * if we are lazy, try to quit ...
2237 i=print_calc(im,calcpr);
2239 if(((i==0)&&(piechart==0)) || lazy) return 0;
2241 /* If there's only the pie chart to draw, signal this */
2242 if (i==0) piechart=2;
2244 /* get actual drawing data and find min and max values*/
2245 if(data_proc(im)==-1)
2248 if(!im->logarithmic){si_unit(im);} /* identify si magnitude Kilo, Mega Giga ? */
2250 if(!im->rigid && ! im->logarithmic)
2251 expand_range(im); /* make sure the upper and lower limit are
2254 if (!calc_horizontal_grid(im))
2259 /**************************************************************
2260 *** Calculating sizes and locations became a bit confusing ***
2261 *** so I moved this into a separate function. ***
2262 **************************************************************/
2263 if(graph_size_location(im,i,piechart)==-1)
2266 /* the actual graph is created by going through the individual
2267 graph elements and then drawing them */
2269 node=gfx_new_area ( im->canvas,
2273 im->graph_col[GRC_BACK]);
2275 gfx_add_point(node,0, im->yimg);
2277 if (piechart != 2) {
2278 node=gfx_new_area ( im->canvas,
2279 im->xorigin, im->yorigin,
2280 im->xorigin + im->xsize, im->yorigin,
2281 im->xorigin + im->xsize, im->yorigin-im->ysize,
2282 im->graph_col[GRC_CANVAS]);
2284 gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
2286 if (im->minval > 0.0)
2287 areazero = im->minval;
2288 if (im->maxval < 0.0)
2289 areazero = im->maxval;
2295 pie_part(im,im->graph_col[GRC_CANVAS],im->pie_x,im->pie_y,im->piesize*0.5,0,2*M_PI);
2298 for(i=0;i<im->gdes_c;i++){
2299 switch(im->gdes[i].gf){
2311 for (ii = 0; ii < im->xsize; ii++)
2313 if (!isnan(im->gdes[i].p_data[ii]) &&
2314 im->gdes[i].p_data[ii] > 0.0)
2316 /* generate a tick */
2317 gfx_new_line(im->canvas, im -> xorigin + ii,
2318 im -> yorigin - (im -> gdes[i].yrule * im -> ysize),
2322 im -> gdes[i].col );
2328 stack_gf = im->gdes[i].gf;
2330 /* fix data points at oo and -oo */
2331 for(ii=0;ii<im->xsize;ii++){
2332 if (isinf(im->gdes[i].p_data[ii])){
2333 if (im->gdes[i].p_data[ii] > 0) {
2334 im->gdes[i].p_data[ii] = im->maxval ;
2336 im->gdes[i].p_data[ii] = im->minval ;
2342 if (im->gdes[i].col != 0x0){
2343 /* GF_LINE and friend */
2344 if(stack_gf == GF_LINE ){
2346 for(ii=1;ii<im->xsize;ii++){
2347 if ( ! isnan(im->gdes[i].p_data[ii-1])
2348 && ! isnan(im->gdes[i].p_data[ii])){
2350 node = gfx_new_line(im->canvas,
2351 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2352 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2353 im->gdes[i].linewidth,
2356 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2365 for(ii=1;ii<im->xsize;ii++){
2367 if ( ! isnan(im->gdes[i].p_data[ii-1])
2368 && ! isnan(im->gdes[i].p_data[ii])){
2372 if (im->gdes[i].gf == GF_STACK) {
2374 if ( (im->gdes[i].gf == GF_STACK)
2375 || (im->gdes[i].stack) ) {
2377 ybase = ytr(im,lastgdes->p_data[ii-1]);
2379 ybase = ytr(im,areazero);
2382 node = gfx_new_area(im->canvas,
2383 ii-1+im->xorigin,ybase,
2384 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2385 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2389 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2393 if ( node != NULL && (ii+1==im->xsize || isnan(im->gdes[i].p_data[ii]) )){
2394 /* GF_AREA STACK type*/
2396 if (im->gdes[i].gf == GF_STACK ) {
2398 if ( (im->gdes[i].gf == GF_STACK)
2399 || (im->gdes[i].stack) ) {
2401 for (iii=ii-1;iii>area_start;iii--){
2402 gfx_add_point(node,iii+im->xorigin,ytr(im,lastgdes->p_data[iii]));
2405 gfx_add_point(node,ii+im->xorigin,ytr(im,areazero));
2410 } /* else GF_LINE */
2411 } /* if color != 0x0 */
2412 /* make sure we do not run into trouble when stacking on NaN */
2413 for(ii=0;ii<im->xsize;ii++){
2414 if (isnan(im->gdes[i].p_data[ii])) {
2415 if (lastgdes && (im->gdes[i].gf == GF_STACK)) {
2416 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
2418 im->gdes[i].p_data[ii] = ytr(im,areazero);
2422 lastgdes = &(im->gdes[i]);
2425 if(isnan(im->gdes[i].yrule)) /* fetch variable */
2426 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2428 if (finite(im->gdes[i].yrule)) { /* even the fetched var can be NaN */
2429 pie_part(im,im->gdes[i].col,
2430 im->pie_x,im->pie_y,im->piesize*0.4,
2431 M_PI*2.0*PieStart/100.0,
2432 M_PI*2.0*(PieStart+im->gdes[i].yrule)/100.0);
2433 PieStart += im->gdes[i].yrule;
2442 /* grid_paint also does the text */
2445 /* the RULES are the last thing to paint ... */
2446 for(i=0;i<im->gdes_c;i++){
2448 switch(im->gdes[i].gf){
2450 if(isnan(im->gdes[i].yrule)) { /* fetch variable */
2451 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2453 if(im->gdes[i].yrule >= im->minval
2454 && im->gdes[i].yrule <= im->maxval)
2455 gfx_new_line(im->canvas,
2456 im->xorigin,ytr(im,im->gdes[i].yrule),
2457 im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2458 1.0,im->gdes[i].col);
2461 if(im->gdes[i].xrule == 0) { /* fetch variable */
2462 im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
2464 if(im->gdes[i].xrule >= im->start
2465 && im->gdes[i].xrule <= im->end)
2466 gfx_new_line(im->canvas,
2467 xtr(im,im->gdes[i].xrule),im->yorigin,
2468 xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2469 1.0,im->gdes[i].col);
2477 if (strcmp(im->graphfile,"-")==0) {
2479 /* Change translation mode for stdout to BINARY */
2480 _setmode( _fileno( stdout ), O_BINARY );
2484 if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2485 rrd_set_error("Opening '%s' for write: %s",im->graphfile,
2486 rrd_strerror(errno));
2490 gfx_render (im->canvas,im->ximg,im->yimg,0x0,fo);
2491 if (strcmp(im->graphfile,"-") != 0)
2497 /*****************************************************
2499 *****************************************************/
2502 gdes_alloc(image_desc_t *im){
2504 unsigned long def_step = (im->end-im->start)/im->xsize;
2506 if (im->step > def_step) /* step can be increassed ... no decreassed */
2507 def_step = im->step;
2511 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2512 * sizeof(graph_desc_t)))==NULL){
2513 rrd_set_error("realloc graph_descs");
2518 im->gdes[im->gdes_c-1].step=def_step;
2519 im->gdes[im->gdes_c-1].stack=0;
2520 im->gdes[im->gdes_c-1].debug=0;
2521 im->gdes[im->gdes_c-1].start=im->start;
2522 im->gdes[im->gdes_c-1].end=im->end;
2523 im->gdes[im->gdes_c-1].vname[0]='\0';
2524 im->gdes[im->gdes_c-1].data=NULL;
2525 im->gdes[im->gdes_c-1].ds_namv=NULL;
2526 im->gdes[im->gdes_c-1].data_first=0;
2527 im->gdes[im->gdes_c-1].p_data=NULL;
2528 im->gdes[im->gdes_c-1].rpnp=NULL;
2529 im->gdes[im->gdes_c-1].col = 0x0;
2530 im->gdes[im->gdes_c-1].legend[0]='\0';
2531 im->gdes[im->gdes_c-1].rrd[0]='\0';
2532 im->gdes[im->gdes_c-1].ds=-1;
2533 im->gdes[im->gdes_c-1].p_data=NULL;
2534 im->gdes[im->gdes_c-1].yrule=DNAN;
2535 im->gdes[im->gdes_c-1].xrule=0;
2539 /* copies input untill the first unescaped colon is found
2540 or until input ends. backslashes have to be escaped as well */
2542 scan_for_col(char *input, int len, char *output)
2547 input[inp] != ':' &&
2550 if (input[inp] == '\\' &&
2551 input[inp+1] != '\0' &&
2552 (input[inp+1] == '\\' ||
2553 input[inp+1] == ':')){
2554 output[outp++] = input[++inp];
2557 output[outp++] = input[inp];
2560 output[outp] = '\0';
2563 /* Some surgery done on this function, it became ridiculously big.
2565 ** - initializing now in rrd_graph_init()
2566 ** - options parsing now in rrd_graph_options()
2567 ** - script parsing now in rrd_graph_script()
2570 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize)
2574 rrd_graph_init(&im);
2576 rrd_graph_options(argc,argv,&im);
2577 if (rrd_test_error()) {
2582 if (strlen(argv[optind])>=MAXPATH) {
2583 rrd_set_error("filename (including path) too long");
2587 strncpy(im.graphfile,argv[optind],MAXPATH-1);
2588 im.graphfile[MAXPATH-1]='\0';
2590 rrd_graph_script(argc,argv,&im);
2591 if (rrd_test_error()) {
2596 /* Everything is now read and the actual work can start */
2599 if (graph_paint(&im,prdata)==-1){
2604 /* The image is generated and needs to be output.
2605 ** Also, if needed, print a line with information about the image.
2613 /* maybe prdata is not allocated yet ... lets do it now */
2614 if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
2615 rrd_set_error("malloc imginfo");
2619 if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
2621 rrd_set_error("malloc imginfo");
2624 filename=im.graphfile+strlen(im.graphfile);
2625 while(filename > im.graphfile) {
2626 if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
2630 sprintf((*prdata)[0],im.imginfo,filename,(long)(im.canvas->zoom*im.ximg),(long)(im.canvas->zoom*im.yimg));
2637 rrd_graph_init(image_desc_t *im)
2644 #ifdef HAVE_SETLOCALE
2645 setlocale(LC_TIME,"");
2648 im->xlab_user.minsec = -1;
2654 im->ylegend[0] = '\0';
2655 im->title[0] = '\0';
2658 im->unitsexponent= 9999;
2664 im->logarithmic = 0;
2665 im->ygridstep = DNAN;
2666 im->draw_x_grid = 1;
2667 im->draw_y_grid = 1;
2672 im->canvas = gfx_new_canvas();
2673 im->grid_dash_on = 1;
2674 im->grid_dash_off = 1;
2676 for(i=0;i<DIM(graph_col);i++)
2677 im->graph_col[i]=graph_col[i];
2681 windir = getenv("windir");
2682 /* %windir% is something like D:\windows or C:\winnt */
2683 if (windir != NULL) {
2684 strcpy(rrd_win_default_font,windir);
2685 strcat(rrd_win_default_font,"\\fonts\\cour.ttf");
2686 for(i=0;i<DIM(text_prop);i++)
2687 text_prop[i].font = rrd_win_default_font;
2691 for(i=0;i<DIM(text_prop);i++){
2692 im->text_prop[i].size = text_prop[i].size;
2693 im->text_prop[i].font = text_prop[i].font;
2698 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
2701 char *parsetime_error = NULL;
2702 char scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
2703 time_t start_tmp=0,end_tmp=0;
2705 struct time_value start_tv, end_tv;
2708 parsetime("end-24h", &start_tv);
2709 parsetime("now", &end_tv);
2712 static struct option long_options[] =
2714 {"start", required_argument, 0, 's'},
2715 {"end", required_argument, 0, 'e'},
2716 {"x-grid", required_argument, 0, 'x'},
2717 {"y-grid", required_argument, 0, 'y'},
2718 {"vertical-label",required_argument,0,'v'},
2719 {"width", required_argument, 0, 'w'},
2720 {"height", required_argument, 0, 'h'},
2721 {"interlaced", no_argument, 0, 'i'},
2722 {"upper-limit",required_argument, 0, 'u'},
2723 {"lower-limit",required_argument, 0, 'l'},
2724 {"rigid", no_argument, 0, 'r'},
2725 {"base", required_argument, 0, 'b'},
2726 {"logarithmic",no_argument, 0, 'o'},
2727 {"color", required_argument, 0, 'c'},
2728 {"font", required_argument, 0, 'n'},
2729 {"title", required_argument, 0, 't'},
2730 {"imginfo", required_argument, 0, 'f'},
2731 {"imgformat", required_argument, 0, 'a'},
2732 {"lazy", no_argument, 0, 'z'},
2733 {"zoom", required_argument, 0, 'm'},
2734 {"no-legend", no_argument, 0, 'g'},
2735 {"alt-y-grid", no_argument, 0, 'Y'},
2736 {"no-minor", no_argument, 0, 'I'},
2737 {"alt-autoscale", no_argument, 0, 'A'},
2738 {"alt-autoscale-max", no_argument, 0, 'M'},
2739 {"units-exponent",required_argument, 0, 'X'},
2740 {"step", required_argument, 0, 'S'},
2741 {"no-gridfit", no_argument, 0, 'N'},
2743 int option_index = 0;
2747 opt = getopt_long(argc, argv,
2748 "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:I:zgYAMX:S:N",
2749 long_options, &option_index);
2756 im->extra_flags |= NOMINOR;
2759 im->extra_flags |= ALTYGRID;
2762 im->extra_flags |= ALTAUTOSCALE;
2765 im->extra_flags |= ALTAUTOSCALE_MAX;
2768 im->extra_flags |= NOLEGEND;
2771 im->unitsexponent = atoi(optarg);
2774 im->step = atoi(optarg);
2780 if ((parsetime_error = parsetime(optarg, &start_tv))) {
2781 rrd_set_error( "start time: %s", parsetime_error );
2786 if ((parsetime_error = parsetime(optarg, &end_tv))) {
2787 rrd_set_error( "end time: %s", parsetime_error );
2792 if(strcmp(optarg,"none") == 0){
2798 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
2800 &im->xlab_user.gridst,
2802 &im->xlab_user.mgridst,
2804 &im->xlab_user.labst,
2805 &im->xlab_user.precis,
2806 &stroff) == 7 && stroff != 0){
2807 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
2808 if((im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
2809 rrd_set_error("unknown keyword %s",scan_gtm);
2811 } else if ((im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
2812 rrd_set_error("unknown keyword %s",scan_mtm);
2814 } else if ((im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
2815 rrd_set_error("unknown keyword %s",scan_ltm);
2818 im->xlab_user.minsec = 1;
2819 im->xlab_user.stst = im->xlab_form;
2821 rrd_set_error("invalid x-grid format");
2827 if(strcmp(optarg,"none") == 0){
2835 &im->ylabfact) == 2) {
2836 if(im->ygridstep<=0){
2837 rrd_set_error("grid step must be > 0");
2839 } else if (im->ylabfact < 1){
2840 rrd_set_error("label factor must be > 0");
2844 rrd_set_error("invalid y-grid format");
2849 strncpy(im->ylegend,optarg,150);
2850 im->ylegend[150]='\0';
2853 im->maxval = atof(optarg);
2856 im->minval = atof(optarg);
2859 im->base = atol(optarg);
2860 if(im->base != 1024 && im->base != 1000 ){
2861 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
2866 long_tmp = atol(optarg);
2867 if (long_tmp < 10) {
2868 rrd_set_error("width below 10 pixels");
2871 im->xsize = long_tmp;
2874 long_tmp = atol(optarg);
2875 if (long_tmp < 10) {
2876 rrd_set_error("height below 10 pixels");
2879 im->ysize = long_tmp;
2882 im->canvas->interlaced = 1;
2888 im->imginfo = optarg;
2891 if((im->canvas->imgformat = if_conv(optarg)) == -1) {
2892 rrd_set_error("unsupported graphics format '%s'",optarg);
2900 im->logarithmic = 1;
2901 if (isnan(im->minval))
2907 col_nam,&color) == 2){
2909 if((ci=grc_conv(col_nam)) != -1){
2910 im->graph_col[ci]=color;
2912 rrd_set_error("invalid color name '%s'",col_nam);
2915 rrd_set_error("invalid color def format");
2920 /* originally this used char *prop = "" and
2921 ** char *font = "dummy" however this results
2922 ** in a SEG fault, at least on RH7.1
2924 ** The current implementation isn't proper
2925 ** either, font is never freed and prop uses
2926 ** a fixed width string
2935 prop,&size,font) == 3){
2937 if((sindex=text_prop_conv(prop)) != -1){
2938 im->text_prop[sindex].size=size;
2939 im->text_prop[sindex].font=font;
2940 if (sindex==0) { /* the default */
2941 im->text_prop[TEXT_PROP_TITLE].size=size;
2942 im->text_prop[TEXT_PROP_TITLE].font=font;
2943 im->text_prop[TEXT_PROP_AXIS].size=size;
2944 im->text_prop[TEXT_PROP_AXIS].font=font;
2945 im->text_prop[TEXT_PROP_UNIT].size=size;
2946 im->text_prop[TEXT_PROP_UNIT].font=font;
2947 im->text_prop[TEXT_PROP_LEGEND].size=size;
2948 im->text_prop[TEXT_PROP_LEGEND].font=font;
2951 rrd_set_error("invalid fonttag '%s'",prop);
2955 rrd_set_error("invalid text property format");
2961 im->canvas->zoom = atof(optarg);
2962 if (im->canvas->zoom <= 0.0) {
2963 rrd_set_error("zoom factor must be > 0");
2968 strncpy(im->title,optarg,150);
2969 im->title[150]='\0';
2974 rrd_set_error("unknown option '%c'", optopt);
2976 rrd_set_error("unknown option '%s'",argv[optind-1]);
2981 if (optind >= argc) {
2982 rrd_set_error("missing filename");
2986 if (im->logarithmic == 1 && (im->minval <= 0 || isnan(im->minval))){
2987 rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");
2991 if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
2992 /* error string is set in parsetime.c */
2996 if (start_tmp < 3600*24*365*10){
2997 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
3001 if (end_tmp < start_tmp) {
3002 rrd_set_error("start (%ld) should be less than end (%ld)",
3003 start_tmp, end_tmp);
3007 im->start = start_tmp;
3012 rrd_graph_check_vname(image_desc_t *im, char *varname, char *err)
3014 if ((im->gdes[im->gdes_c-1].vidx=find_var(im,varname))==-1) {
3015 rrd_set_error("Unknown variable '%s' in %s",varname,err);
3021 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
3024 graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
3026 color=strstr(var,"#");
3029 rrd_set_error("Found no color in %s",err);
3038 rest=strstr(color,":");
3046 sscanf(color,"#%6lx%n",&col,&n);
3047 col = (col << 8) + 0xff /* shift left by 8 */;
3048 if (n!=7) rrd_set_error("Color problem in %s",err);
3051 sscanf(color,"#%8lx%n",&col,&n);
3054 rrd_set_error("Color problem in %s",err);
3056 if (rrd_test_error()) return 0;
3062 rrd_graph_legend(graph_desc_t *gdp, char *line)
3066 i=scan_for_col(line,FMT_LEG_LEN,gdp->legend);
3068 return (strlen(&line[i])==0);
3072 int bad_format(char *fmt) {
3076 while (*ptr != '\0')
3077 if (*ptr++ == '%') {
3079 /* line cannot end with percent char */
3080 if (*ptr == '\0') return 1;
3082 /* '%s', '%S' and '%%' are allowed */
3083 if (*ptr == 's' || *ptr == 'S' || *ptr == '%') ptr++;
3085 /* or else '% 6.2lf' and such are allowed */
3088 /* optional padding character */
3089 if (*ptr == ' ' || *ptr == '+' || *ptr == '-') ptr++;
3091 /* This should take care of 'm.n' with all three optional */
3092 while (*ptr >= '0' && *ptr <= '9') ptr++;
3093 if (*ptr == '.') ptr++;
3094 while (*ptr >= '0' && *ptr <= '9') ptr++;
3096 /* Either 'le', 'lf' or 'lg' must follow here */
3097 if (*ptr++ != 'l') return 1;
3098 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g') ptr++;
3109 vdef_parse(gdes,str)
3110 struct graph_desc_t *gdes;
3113 /* A VDEF currently is either "func" or "param,func"
3114 * so the parsing is rather simple. Change if needed.
3121 sscanf(str,"%le,%29[A-Z]%n",¶m,func,&n);
3122 if (n==strlen(str)) { /* matched */
3126 sscanf(str,"%29[A-Z]%n",func,&n);
3127 if (n==strlen(str)) { /* matched */
3130 rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3137 if (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3138 else if (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3139 else if (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3140 else if (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3141 else if (!strcmp("TOTAL", func)) gdes->vf.op = VDEF_TOTAL;
3142 else if (!strcmp("FIRST", func)) gdes->vf.op = VDEF_FIRST;
3143 else if (!strcmp("LAST", func)) gdes->vf.op = VDEF_LAST;
3145 rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3152 switch (gdes->vf.op) {
3154 if (isnan(param)) { /* no parameter given */
3155 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3161 if (param>=0.0 && param<=100.0) {
3162 gdes->vf.param = param;
3163 gdes->vf.val = DNAN; /* undefined */
3164 gdes->vf.when = 0; /* undefined */
3166 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3180 gdes->vf.param = DNAN;
3181 gdes->vf.val = DNAN;
3184 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3201 graph_desc_t *src,*dst;
3205 dst = &im->gdes[gdi];
3206 src = &im->gdes[dst->vidx];
3207 data = src->data + src->ds;
3208 steps = (src->end - src->start) / src->step;
3211 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3218 switch (dst->vf.op) {
3219 case VDEF_PERCENT: {
3220 rrd_value_t * array;
3224 if ((array = malloc(steps*sizeof(double)))==NULL) {
3225 rrd_set_error("malloc VDEV_PERCENT");
3228 for (step=0;step < steps; step++) {
3229 array[step]=data[step*src->ds_cnt];
3231 qsort(array,step,sizeof(double),vdef_percent_compar);
3233 field = (steps-1)*dst->vf.param/100;
3234 dst->vf.val = array[field];
3235 dst->vf.when = 0; /* no time component */
3238 for(step=0;step<steps;step++)
3239 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3245 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3246 if (step == steps) {
3250 dst->vf.val = data[step*src->ds_cnt];
3251 dst->vf.when = src->start + (step+1)*src->step;
3253 while (step != steps) {
3254 if (finite(data[step*src->ds_cnt])) {
3255 if (data[step*src->ds_cnt] > dst->vf.val) {
3256 dst->vf.val = data[step*src->ds_cnt];
3257 dst->vf.when = src->start + (step+1)*src->step;
3264 case VDEF_AVERAGE: {
3267 for (step=0;step<steps;step++) {
3268 if (finite(data[step*src->ds_cnt])) {
3269 sum += data[step*src->ds_cnt];
3274 if (dst->vf.op == VDEF_TOTAL) {
3275 dst->vf.val = sum*src->step;
3276 dst->vf.when = cnt*src->step; /* not really "when" */
3278 dst->vf.val = sum/cnt;
3279 dst->vf.when = 0; /* no time component */
3289 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3290 if (step == steps) {
3294 dst->vf.val = data[step*src->ds_cnt];
3295 dst->vf.when = src->start + (step+1)*src->step;
3297 while (step != steps) {
3298 if (finite(data[step*src->ds_cnt])) {
3299 if (data[step*src->ds_cnt] < dst->vf.val) {
3300 dst->vf.val = data[step*src->ds_cnt];
3301 dst->vf.when = src->start + (step+1)*src->step;
3308 /* The time value returned here is one step before the
3309 * actual time value. This is the start of the first
3313 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3314 if (step == steps) { /* all entries were NaN */
3318 dst->vf.val = data[step*src->ds_cnt];
3319 dst->vf.when = src->start + step*src->step;
3323 /* The time value returned here is the
3324 * actual time value. This is the end of the last
3328 while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3329 if (step < 0) { /* all entries were NaN */
3333 dst->vf.val = data[step*src->ds_cnt];
3334 dst->vf.when = src->start + (step+1)*src->step;
3341 /* NaN < -INF < finite_values < INF */
3343 vdef_percent_compar(a,b)
3346 /* Equality is not returned; this doesn't hurt except
3347 * (maybe) for a little performance.
3350 /* First catch NaN values. They are smallest */
3351 if (isnan( *(double *)a )) return -1;
3352 if (isnan( *(double *)b )) return 1;
3354 /* NaN doesn't reach this part so INF and -INF are extremes.
3355 * The sign from isinf() is compatible with the sign we return
3357 if (isinf( *(double *)a )) return isinf( *(double *)a );
3358 if (isinf( *(double *)b )) return isinf( *(double *)b );
3360 /* If we reach this, both values must be finite */
3361 if ( *(double *)a < *(double *)b ) return -1; else return 1;