1 /****************************************************************************
2 * RRDtool 1.2.9 Copyright by Tobi Oetiker, 1997-2005
3 ****************************************************************************
4 * rrd__graph.c produce graphs from data in rrdfiles
5 ****************************************************************************/
12 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
25 #include "rrd_graph.h"
27 /* some constant definitions */
31 #ifndef RRD_DEFAULT_FONT
32 /* there is special code later to pick Cour.ttf when running on windows */
33 #define RRD_DEFAULT_FONT "DejaVuSansMono-Roman.ttf"
36 text_prop_t text_prop[] = {
37 { 8.0, RRD_DEFAULT_FONT }, /* default */
38 { 9.0, RRD_DEFAULT_FONT }, /* title */
39 { 7.0, RRD_DEFAULT_FONT }, /* axis */
40 { 8.0, RRD_DEFAULT_FONT }, /* unit */
41 { 8.0, RRD_DEFAULT_FONT } /* legend */
45 {0, TMT_SECOND,30, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
46 {2, TMT_MINUTE,1, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
47 {5, TMT_MINUTE,2, TMT_MINUTE,10, TMT_MINUTE,10, 0,"%H:%M"},
48 {10, TMT_MINUTE,5, TMT_MINUTE,20, TMT_MINUTE,20, 0,"%H:%M"},
49 {30, TMT_MINUTE,10, TMT_HOUR,1, TMT_HOUR,1, 0,"%H:%M"},
50 {60, TMT_MINUTE,30, TMT_HOUR,2, TMT_HOUR,2, 0,"%H:%M"},
51 {180, TMT_HOUR,1, TMT_HOUR,6, TMT_HOUR,6, 0,"%H:%M"},
52 /*{300, TMT_HOUR,3, TMT_HOUR,12, TMT_HOUR,12, 12*3600,"%a %p"}, this looks silly*/
53 {600, TMT_HOUR,6, TMT_DAY,1, TMT_DAY,1, 24*3600,"%a"},
54 {1800, TMT_HOUR,12, TMT_DAY,1, TMT_DAY,2, 24*3600,"%a"},
55 {3600, TMT_DAY,1, TMT_WEEK,1, TMT_WEEK,1, 7*24*3600,"Week %V"},
56 {3*3600, TMT_WEEK,1, TMT_MONTH,1, TMT_WEEK,2, 7*24*3600,"Week %V"},
57 {6*3600, TMT_MONTH,1, TMT_MONTH,1, TMT_MONTH,1, 30*24*3600,"%b"},
58 {48*3600, TMT_MONTH,1, TMT_MONTH,3, TMT_MONTH,3, 30*24*3600,"%b"},
59 {10*24*3600, TMT_YEAR,1, TMT_YEAR,1, TMT_YEAR,1, 365*24*3600,"%y"},
60 {-1,TMT_MONTH,0,TMT_MONTH,0,TMT_MONTH,0,0,""}
63 /* sensible logarithmic y label intervals ...
64 the first element of each row defines the possible starting points on the
65 y axis ... the other specify the */
67 double yloglab[][12]= {{ 1e9, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
68 { 1e3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
69 { 1e1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
70 /* { 1e1, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, */
71 { 1e1, 1, 2.5, 5, 7.5, 0, 0, 0, 0, 0, 0, 0 },
72 { 1e1, 1, 2, 4, 6, 8, 0, 0, 0, 0, 0, 0 },
73 { 1e1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0 },
74 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }};
76 /* sensible y label intervals ...*/
94 gfx_color_t graph_col[] = /* default colors */
95 { 0xFFFFFFFF, /* canvas */
96 0xF0F0F0FF, /* background */
97 0xD0D0D0FF, /* shade A */
98 0xA0A0A0FF, /* shade B */
99 0x90909080, /* grid */
100 0xE0505080, /* major grid */
101 0x000000FF, /* font */
102 0x802020FF, /* arrow */
103 0x202020FF, /* axis */
104 0x000000FF /* frame */
111 # define DPRINT(x) (void)(printf x, printf("\n"))
117 /* initialize with xtr(im,0); */
119 xtr(image_desc_t *im,time_t mytime){
122 pixie = (double) im->xsize / (double)(im->end - im->start);
125 return (int)((double)im->xorigin
126 + pixie * ( mytime - im->start ) );
129 /* translate data values into y coordinates */
131 ytr(image_desc_t *im, double value){
136 pixie = (double) im->ysize / (im->maxval - im->minval);
138 pixie = (double) im->ysize / (log10(im->maxval) - log10(im->minval));
140 } else if(!im->logarithmic) {
141 yval = im->yorigin - pixie * (value - im->minval);
143 if (value < im->minval) {
146 yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
149 /* make sure we don't return anything too unreasonable. GD lib can
150 get terribly slow when drawing lines outside its scope. This is
151 especially problematic in connection with the rigid option */
153 /* keep yval as-is */
154 } else if (yval > im->yorigin) {
155 yval = im->yorigin +0.00001;
156 } else if (yval < im->yorigin - im->ysize){
157 yval = im->yorigin - im->ysize - 0.00001;
164 /* conversion function for symbolic entry names */
167 #define conv_if(VV,VVV) \
168 if (strcmp(#VV, string) == 0) return VVV ;
170 enum gf_en gf_conv(char *string){
172 conv_if(PRINT,GF_PRINT)
173 conv_if(GPRINT,GF_GPRINT)
174 conv_if(COMMENT,GF_COMMENT)
175 conv_if(HRULE,GF_HRULE)
176 conv_if(VRULE,GF_VRULE)
177 conv_if(LINE,GF_LINE)
178 conv_if(AREA,GF_AREA)
179 conv_if(STACK,GF_STACK)
180 conv_if(TICK,GF_TICK)
182 conv_if(CDEF,GF_CDEF)
183 conv_if(VDEF,GF_VDEF)
185 conv_if(PART,GF_PART)
187 conv_if(XPORT,GF_XPORT)
188 conv_if(SHIFT,GF_SHIFT)
193 enum gfx_if_en if_conv(char *string){
203 enum tmt_en tmt_conv(char *string){
205 conv_if(SECOND,TMT_SECOND)
206 conv_if(MINUTE,TMT_MINUTE)
207 conv_if(HOUR,TMT_HOUR)
209 conv_if(WEEK,TMT_WEEK)
210 conv_if(MONTH,TMT_MONTH)
211 conv_if(YEAR,TMT_YEAR)
215 enum grc_en grc_conv(char *string){
217 conv_if(BACK,GRC_BACK)
218 conv_if(CANVAS,GRC_CANVAS)
219 conv_if(SHADEA,GRC_SHADEA)
220 conv_if(SHADEB,GRC_SHADEB)
221 conv_if(GRID,GRC_GRID)
222 conv_if(MGRID,GRC_MGRID)
223 conv_if(FONT,GRC_FONT)
224 conv_if(ARROW,GRC_ARROW)
225 conv_if(AXIS,GRC_AXIS)
226 conv_if(FRAME,GRC_FRAME)
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 */
334 double digits,viewdigits=0;
336 digits = floor( log( max( fabs(im->minval),fabs(im->maxval)))/log((double)im->base));
338 if (im->unitsexponent != 9999) {
339 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
340 viewdigits = floor(im->unitsexponent / 3);
345 im->magfact = pow((double)im->base , digits);
348 printf("digits %6.3f im->magfact %6.3f\n",digits,im->magfact);
351 im->viewfactor = im->magfact / pow((double)im->base , viewdigits);
353 pow((double)im->base , viewdigits);
355 if ( ((viewdigits+symbcenter) < sizeof(symbol)) &&
356 ((viewdigits+symbcenter) >= 0) )
357 im->symbol = symbol[(int)viewdigits+symbcenter];
362 /* move min and max values around to become sensible */
365 expand_range(image_desc_t *im)
367 double sensiblevalues[] ={1000.0,900.0,800.0,750.0,700.0,
368 600.0,500.0,400.0,300.0,250.0,
369 200.0,125.0,100.0,90.0,80.0,
370 75.0,70.0,60.0,50.0,40.0,30.0,
371 25.0,20.0,10.0,9.0,8.0,
372 7.0,6.0,5.0,4.0,3.5,3.0,
373 2.5,2.0,1.8,1.5,1.2,1.0,
374 0.8,0.7,0.6,0.5,0.4,0.3,0.2,0.1,0.0,-1};
376 double scaled_min,scaled_max;
383 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
384 im->minval,im->maxval,im->magfact);
387 if (isnan(im->ygridstep)){
388 if(im->extra_flags & ALTAUTOSCALE) {
389 /* measure the amplitude of the function. Make sure that
390 graph boundaries are slightly higher then max/min vals
391 so we can see amplitude on the graph */
394 delt = im->maxval - im->minval;
396 fact = 2.0 * pow(10.0,
397 floor(log10(max(fabs(im->minval), fabs(im->maxval)))) - 2);
399 adj = (fact - delt) * 0.55;
401 printf("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n", im->minval, im->maxval, delt, fact, adj);
407 else if(im->extra_flags & ALTAUTOSCALE_MAX) {
408 /* measure the amplitude of the function. Make sure that
409 graph boundaries are slightly higher than max vals
410 so we can see amplitude on the graph */
411 adj = (im->maxval - im->minval) * 0.1;
415 scaled_min = im->minval / im->magfact;
416 scaled_max = im->maxval / im->magfact;
418 for (i=1; sensiblevalues[i] > 0; i++){
419 if (sensiblevalues[i-1]>=scaled_min &&
420 sensiblevalues[i]<=scaled_min)
421 im->minval = sensiblevalues[i]*(im->magfact);
423 if (-sensiblevalues[i-1]<=scaled_min &&
424 -sensiblevalues[i]>=scaled_min)
425 im->minval = -sensiblevalues[i-1]*(im->magfact);
427 if (sensiblevalues[i-1] >= scaled_max &&
428 sensiblevalues[i] <= scaled_max)
429 im->maxval = sensiblevalues[i-1]*(im->magfact);
431 if (-sensiblevalues[i-1]<=scaled_max &&
432 -sensiblevalues[i] >=scaled_max)
433 im->maxval = -sensiblevalues[i]*(im->magfact);
435 /* no sensiblevalues found. we switch to ALTYGRID mode */
436 if (sensiblevalues[i] == 0){
437 im->extra_flags |= ALTYGRID;
441 /* adjust min and max to the grid definition if there is one */
442 im->minval = (double)im->ylabfact * im->ygridstep *
443 floor(im->minval / ((double)im->ylabfact * im->ygridstep));
444 im->maxval = (double)im->ylabfact * im->ygridstep *
445 ceil(im->maxval /( (double)im->ylabfact * im->ygridstep));
449 fprintf(stderr,"SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
450 im->minval,im->maxval,im->magfact);
455 apply_gridfit(image_desc_t *im)
457 if (isnan(im->minval) || isnan(im->maxval))
460 if (im->logarithmic) {
461 double ya, yb, ypix, ypixfrac;
462 double log10_range = log10(im->maxval) - log10(im->minval);
463 ya = pow((double)10, floor(log10(im->minval)));
464 while (ya < im->minval)
467 return; /* don't have y=10^x gridline */
469 if (yb <= im->maxval) {
470 /* we have at least 2 y=10^x gridlines.
471 Make sure distance between them in pixels
472 are an integer by expanding im->maxval */
473 double y_pixel_delta = ytr(im, ya) - ytr(im, yb);
474 double factor = y_pixel_delta / floor(y_pixel_delta);
475 double new_log10_range = factor * log10_range;
476 double new_ymax_log10 = log10(im->minval) + new_log10_range;
477 im->maxval = pow(10, new_ymax_log10);
478 ytr(im, DNAN); /* reset precalc */
479 log10_range = log10(im->maxval) - log10(im->minval);
481 /* make sure first y=10^x gridline is located on
482 integer pixel position by moving scale slightly
483 downwards (sub-pixel movement) */
484 ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
485 ypixfrac = ypix - floor(ypix);
486 if (ypixfrac > 0 && ypixfrac < 1) {
487 double yfrac = ypixfrac / im->ysize;
488 im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
489 im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
490 ytr(im, DNAN); /* reset precalc */
493 /* Make sure we have an integer pixel distance between
494 each minor gridline */
495 double ypos1 = ytr(im, im->minval);
496 double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
497 double y_pixel_delta = ypos1 - ypos2;
498 double factor = y_pixel_delta / floor(y_pixel_delta);
499 double new_range = factor * (im->maxval - im->minval);
500 double gridstep = im->ygrid_scale.gridstep;
501 double minor_y, minor_y_px, minor_y_px_frac;
502 im->maxval = im->minval + new_range;
503 ytr(im, DNAN); /* reset precalc */
504 /* make sure first minor gridline is on integer pixel y coord */
505 minor_y = gridstep * floor(im->minval / gridstep);
506 while (minor_y < im->minval)
508 minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
509 minor_y_px_frac = minor_y_px - floor(minor_y_px);
510 if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
511 double yfrac = minor_y_px_frac / im->ysize;
512 double range = im->maxval - im->minval;
513 im->minval = im->minval - yfrac * range;
514 im->maxval = im->maxval - yfrac * range;
515 ytr(im, DNAN); /* reset precalc */
517 calc_horizontal_grid(im); /* recalc with changed im->maxval */
521 /* reduce data reimplementation by Alex */
525 enum cf_en cf, /* which consolidation function ?*/
526 unsigned long cur_step, /* step the data currently is in */
527 time_t *start, /* start, end and step as requested ... */
528 time_t *end, /* ... by the application will be ... */
529 unsigned long *step, /* ... adjusted to represent reality */
530 unsigned long *ds_cnt, /* number of data sources in file */
531 rrd_value_t **data) /* two dimensional array containing the data */
533 int i,reduce_factor = ceil((double)(*step) / (double)cur_step);
534 unsigned long col,dst_row,row_cnt,start_offset,end_offset,skiprows=0;
535 rrd_value_t *srcptr,*dstptr;
537 (*step) = cur_step*reduce_factor; /* set new step size for reduced data */
540 row_cnt = ((*end)-(*start))/cur_step;
546 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
547 row_cnt,reduce_factor,*start,*end,cur_step);
548 for (col=0;col<row_cnt;col++) {
549 printf("time %10lu: ",*start+(col+1)*cur_step);
550 for (i=0;i<*ds_cnt;i++)
551 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
556 /* We have to combine [reduce_factor] rows of the source
557 ** into one row for the destination. Doing this we also
558 ** need to take care to combine the correct rows. First
559 ** alter the start and end time so that they are multiples
560 ** of the new step time. We cannot reduce the amount of
561 ** time so we have to move the end towards the future and
562 ** the start towards the past.
564 end_offset = (*end) % (*step);
565 start_offset = (*start) % (*step);
567 /* If there is a start offset (which cannot be more than
568 ** one destination row), skip the appropriate number of
569 ** source rows and one destination row. The appropriate
570 ** number is what we do know (start_offset/cur_step) of
571 ** the new interval (*step/cur_step aka reduce_factor).
574 printf("start_offset: %lu end_offset: %lu\n",start_offset,end_offset);
575 printf("row_cnt before: %lu\n",row_cnt);
578 (*start) = (*start)-start_offset;
579 skiprows=reduce_factor-start_offset/cur_step;
580 srcptr+=skiprows* *ds_cnt;
581 for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
585 printf("row_cnt between: %lu\n",row_cnt);
588 /* At the end we have some rows that are not going to be
589 ** used, the amount is end_offset/cur_step
592 (*end) = (*end)-end_offset+(*step);
593 skiprows = end_offset/cur_step;
597 printf("row_cnt after: %lu\n",row_cnt);
600 /* Sanity check: row_cnt should be multiple of reduce_factor */
601 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
603 if (row_cnt%reduce_factor) {
604 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
605 row_cnt,reduce_factor);
606 printf("BUG in reduce_data()\n");
610 /* Now combine reduce_factor intervals at a time
611 ** into one interval for the destination.
614 for (dst_row=0;(long int)row_cnt>=reduce_factor;dst_row++) {
615 for (col=0;col<(*ds_cnt);col++) {
616 rrd_value_t newval=DNAN;
617 unsigned long validval=0;
619 for (i=0;i<reduce_factor;i++) {
620 if (isnan(srcptr[i*(*ds_cnt)+col])) {
624 if (isnan(newval)) newval = srcptr[i*(*ds_cnt)+col];
632 newval += srcptr[i*(*ds_cnt)+col];
635 newval = min (newval,srcptr[i*(*ds_cnt)+col]);
638 /* an interval contains a failure if any subintervals contained a failure */
640 newval = max (newval,srcptr[i*(*ds_cnt)+col]);
643 newval = srcptr[i*(*ds_cnt)+col];
648 if (validval == 0){newval = DNAN;} else{
666 srcptr+=(*ds_cnt)*reduce_factor;
667 row_cnt-=reduce_factor;
669 /* If we had to alter the endtime, we didn't have enough
670 ** source rows to fill the last row. Fill it with NaN.
672 if (end_offset) for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
674 row_cnt = ((*end)-(*start))/ *step;
676 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
677 row_cnt,*start,*end,*step);
678 for (col=0;col<row_cnt;col++) {
679 printf("time %10lu: ",*start+(col+1)*(*step));
680 for (i=0;i<*ds_cnt;i++)
681 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
688 /* get the data required for the graphs from the
692 data_fetch(image_desc_t *im )
697 /* pull the data from the log files ... */
698 for (i=0;i< (int)im->gdes_c;i++){
699 /* only GF_DEF elements fetch data */
700 if (im->gdes[i].gf != GF_DEF)
704 /* do we have it already ?*/
705 for (ii=0;ii<i;ii++) {
706 if (im->gdes[ii].gf != GF_DEF)
708 if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
709 && (im->gdes[i].cf == im->gdes[ii].cf)
710 && (im->gdes[i].cf_reduce == im->gdes[ii].cf_reduce)
711 && (im->gdes[i].start == im->gdes[ii].start)
712 && (im->gdes[i].end == im->gdes[ii].end)
713 && (im->gdes[i].step == im->gdes[ii].step)) {
714 /* OK, the data is already there.
715 ** Just copy the header portion
717 im->gdes[i].start = im->gdes[ii].start;
718 im->gdes[i].end = im->gdes[ii].end;
719 im->gdes[i].step = im->gdes[ii].step;
720 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
721 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
722 im->gdes[i].data = im->gdes[ii].data;
723 im->gdes[i].data_first = 0;
730 unsigned long ft_step = im->gdes[i].step ;
732 if((rrd_fetch_fn(im->gdes[i].rrd,
738 &im->gdes[i].ds_namv,
739 &im->gdes[i].data)) == -1){
742 im->gdes[i].data_first = 1;
743 im->gdes[i].step = im->step;
745 if (ft_step < im->gdes[i].step) {
746 reduce_data(im->gdes[i].cf_reduce,
754 im->gdes[i].step = ft_step;
758 /* lets see if the required data source is really there */
759 for(ii=0;ii<(int)im->gdes[i].ds_cnt;ii++){
760 if(strcmp(im->gdes[i].ds_namv[ii],im->gdes[i].ds_nam) == 0){
763 if (im->gdes[i].ds== -1){
764 rrd_set_error("No DS called '%s' in '%s'",
765 im->gdes[i].ds_nam,im->gdes[i].rrd);
773 /* evaluate the expressions in the CDEF functions */
775 /*************************************************************
777 *************************************************************/
780 find_var_wrapper(void *arg1, char *key)
782 return find_var((image_desc_t *) arg1, key);
785 /* find gdes containing var*/
787 find_var(image_desc_t *im, char *key){
789 for(ii=0;ii<im->gdes_c-1;ii++){
790 if((im->gdes[ii].gf == GF_DEF
791 || im->gdes[ii].gf == GF_VDEF
792 || im->gdes[ii].gf == GF_CDEF)
793 && (strcmp(im->gdes[ii].vname,key) == 0)){
800 /* find the largest common denominator for all the numbers
801 in the 0 terminated num array */
806 for (i=0;num[i+1]!=0;i++){
808 rest=num[i] % num[i+1];
809 num[i]=num[i+1]; num[i+1]=rest;
813 /* return i==0?num[i]:num[i-1]; */
817 /* run the rpn calculator on all the VDEF and CDEF arguments */
819 data_calc( image_desc_t *im){
823 long *steparray, rpi;
828 rpnstack_init(&rpnstack);
830 for (gdi=0;gdi<im->gdes_c;gdi++){
831 /* Look for GF_VDEF and GF_CDEF in the same loop,
832 * so CDEFs can use VDEFs and vice versa
834 switch (im->gdes[gdi].gf) {
838 graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx];
840 /* remove current shift */
841 vdp->start -= vdp->shift;
842 vdp->end -= vdp->shift;
845 if (im->gdes[gdi].shidx >= 0)
846 vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
849 vdp->shift = im->gdes[gdi].shval;
851 /* normalize shift to multiple of consolidated step */
852 vdp->shift = (vdp->shift / (long)vdp->step) * (long)vdp->step;
855 vdp->start += vdp->shift;
856 vdp->end += vdp->shift;
860 /* A VDEF has no DS. This also signals other parts
861 * of rrdtool that this is a VDEF value, not a CDEF.
863 im->gdes[gdi].ds_cnt = 0;
864 if (vdef_calc(im,gdi)) {
865 rrd_set_error("Error processing VDEF '%s'"
868 rpnstack_free(&rpnstack);
873 im->gdes[gdi].ds_cnt = 1;
874 im->gdes[gdi].ds = 0;
875 im->gdes[gdi].data_first = 1;
876 im->gdes[gdi].start = 0;
877 im->gdes[gdi].end = 0;
882 /* Find the variables in the expression.
883 * - VDEF variables are substituted by their values
884 * and the opcode is changed into OP_NUMBER.
885 * - CDEF variables are analized for their step size,
886 * the lowest common denominator of all the step
887 * sizes of the data sources involved is calculated
888 * and the resulting number is the step size for the
889 * resulting data source.
891 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
892 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
893 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER){
894 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
895 if (im->gdes[ptr].ds_cnt == 0) { /* this is a VDEF data source */
897 printf("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
899 im->gdes[ptr].vname);
900 printf("DEBUG: value from vdef is %f\n",im->gdes[ptr].vf.val);
902 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
903 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
904 } else { /* normal variables and PREF(variables) */
906 /* add one entry to the array that keeps track of the step sizes of the
907 * data sources going into the CDEF. */
909 rrd_realloc(steparray,
910 (++stepcnt+1)*sizeof(*steparray)))==NULL){
911 rrd_set_error("realloc steparray");
912 rpnstack_free(&rpnstack);
916 steparray[stepcnt-1] = im->gdes[ptr].step;
918 /* adjust start and end of cdef (gdi) so
919 * that it runs from the latest start point
920 * to the earliest endpoint of any of the
921 * rras involved (ptr)
924 if(im->gdes[gdi].start < im->gdes[ptr].start)
925 im->gdes[gdi].start = im->gdes[ptr].start;
927 if(im->gdes[gdi].end == 0 ||
928 im->gdes[gdi].end > im->gdes[ptr].end)
929 im->gdes[gdi].end = im->gdes[ptr].end;
931 /* store pointer to the first element of
932 * the rra providing data for variable,
933 * further save step size and data source
936 im->gdes[gdi].rpnp[rpi].data = im->gdes[ptr].data + im->gdes[ptr].ds;
937 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
938 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
940 /* backoff the *.data ptr; this is done so
941 * rpncalc() function doesn't have to treat
942 * the first case differently
944 } /* if ds_cnt != 0 */
945 } /* if OP_VARIABLE */
946 } /* loop through all rpi */
948 /* move the data pointers to the correct period */
949 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
950 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
951 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER){
952 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
953 long diff = im->gdes[gdi].start - im->gdes[ptr].start;
956 im->gdes[gdi].rpnp[rpi].data += (diff / im->gdes[ptr].step) * im->gdes[ptr].ds_cnt;
960 if(steparray == NULL){
961 rrd_set_error("rpn expressions without DEF"
962 " or CDEF variables are not supported");
963 rpnstack_free(&rpnstack);
966 steparray[stepcnt]=0;
967 /* Now find the resulting step. All steps in all
968 * used RRAs have to be visited
970 im->gdes[gdi].step = lcd(steparray);
972 if((im->gdes[gdi].data = malloc((
973 (im->gdes[gdi].end-im->gdes[gdi].start)
974 / im->gdes[gdi].step)
975 * sizeof(double)))==NULL){
976 rrd_set_error("malloc im->gdes[gdi].data");
977 rpnstack_free(&rpnstack);
981 /* Step through the new cdef results array and
982 * calculate the values
984 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
985 now<=im->gdes[gdi].end;
986 now += im->gdes[gdi].step)
988 rpnp_t *rpnp = im -> gdes[gdi].rpnp;
990 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
991 * in this case we are advancing by timesteps;
992 * we use the fact that time_t is a synonym for long
994 if (rpn_calc(rpnp,&rpnstack,(long) now,
995 im->gdes[gdi].data,++dataidx) == -1) {
996 /* rpn_calc sets the error string */
997 rpnstack_free(&rpnstack);
1000 } /* enumerate over time steps within a CDEF */
1005 } /* enumerate over CDEFs */
1006 rpnstack_free(&rpnstack);
1010 /* massage data so, that we get one value for each x coordinate in the graph */
1012 data_proc( image_desc_t *im ){
1014 double pixstep = (double)(im->end-im->start)
1015 /(double)im->xsize; /* how much time
1016 passes in one pixel */
1018 double minval=DNAN,maxval=DNAN;
1020 unsigned long gr_time;
1022 /* memory for the processed data */
1023 for(i=0;i<im->gdes_c;i++) {
1024 if((im->gdes[i].gf==GF_LINE) ||
1025 (im->gdes[i].gf==GF_AREA) ||
1026 (im->gdes[i].gf==GF_TICK) ||
1027 (im->gdes[i].gf==GF_STACK)) {
1028 if((im->gdes[i].p_data = malloc((im->xsize +1)
1029 * sizeof(rrd_value_t)))==NULL){
1030 rrd_set_error("malloc data_proc");
1036 for (i=0;i<im->xsize;i++) { /* for each pixel */
1038 gr_time = im->start+pixstep*i; /* time of the current step */
1041 for (ii=0;ii<im->gdes_c;ii++) {
1043 switch (im->gdes[ii].gf) {
1047 if (!im->gdes[ii].stack)
1050 value = im->gdes[ii].yrule;
1051 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1052 /* The time of the data doesn't necessarily match
1053 ** the time of the graph. Beware.
1055 vidx = im->gdes[ii].vidx;
1056 if (im->gdes[vidx].gf == GF_VDEF) {
1057 value = im->gdes[vidx].vf.val;
1058 } else if (((long int)gr_time >= (long int)im->gdes[vidx].start) &&
1059 ((long int)gr_time <= (long int)im->gdes[vidx].end) ) {
1060 value = im->gdes[vidx].data[
1061 (unsigned long) floor(
1062 (double)(gr_time - im->gdes[vidx].start)
1063 / im->gdes[vidx].step)
1064 * im->gdes[vidx].ds_cnt
1072 if (! isnan(value)) {
1074 im->gdes[ii].p_data[i] = paintval;
1075 /* GF_TICK: the data values are not
1076 ** relevant for min and max
1078 if (finite(paintval) && im->gdes[ii].gf != GF_TICK ) {
1079 if (isnan(minval) || paintval < minval)
1081 if (isnan(maxval) || paintval > maxval)
1085 im->gdes[ii].p_data[i] = DNAN;
1094 /* if min or max have not been asigned a value this is because
1095 there was no data in the graph ... this is not good ...
1096 lets set these to dummy values then ... */
1098 if (isnan(minval)) minval = 0.0;
1099 if (isnan(maxval)) maxval = 1.0;
1101 /* adjust min and max values */
1102 if (isnan(im->minval)
1103 /* don't adjust low-end with log scale */
1104 || ((!im->logarithmic && !im->rigid) && im->minval > minval)
1106 im->minval = minval;
1107 if (isnan(im->maxval)
1108 || (!im->rigid && im->maxval < maxval)
1110 if (im->logarithmic)
1111 im->maxval = maxval * 1.1;
1113 im->maxval = maxval;
1115 /* make sure min is smaller than max */
1116 if (im->minval > im->maxval) {
1117 im->minval = 0.99 * im->maxval;
1120 /* make sure min and max are not equal */
1121 if (im->minval == im->maxval) {
1123 if (! im->logarithmic) {
1126 /* make sure min and max are not both zero */
1127 if (im->maxval == 0.0) {
1136 /* identify the point where the first gridline, label ... gets placed */
1140 time_t start, /* what is the initial time */
1141 enum tmt_en baseint, /* what is the basic interval */
1142 long basestep /* how many if these do we jump a time */
1146 localtime_r(&start, &tm);
1149 tm.tm_sec -= tm.tm_sec % basestep; break;
1152 tm.tm_min -= tm.tm_min % basestep;
1157 tm.tm_hour -= tm.tm_hour % basestep; break;
1159 /* we do NOT look at the basestep for this ... */
1162 tm.tm_hour = 0; break;
1164 /* we do NOT look at the basestep for this ... */
1168 tm.tm_mday -= tm.tm_wday -1; /* -1 because we want the monday */
1169 if (tm.tm_wday==0) tm.tm_mday -= 7; /* we want the *previous* monday */
1176 tm.tm_mon -= tm.tm_mon % basestep; break;
1184 tm.tm_year -= (tm.tm_year+1900) % basestep;
1189 /* identify the point where the next gridline, label ... gets placed */
1192 time_t current, /* what is the initial time */
1193 enum tmt_en baseint, /* what is the basic interval */
1194 long basestep /* how many if these do we jump a time */
1199 localtime_r(¤t, &tm);
1203 tm.tm_sec += basestep; break;
1205 tm.tm_min += basestep; break;
1207 tm.tm_hour += basestep; break;
1209 tm.tm_mday += basestep; break;
1211 tm.tm_mday += 7*basestep; break;
1213 tm.tm_mon += basestep; break;
1215 tm.tm_year += basestep;
1217 madetime = mktime(&tm);
1218 } while (madetime == -1); /* this is necessary to skip impssible times
1219 like the daylight saving time skips */
1225 /* calculate values required for PRINT and GPRINT functions */
1228 print_calc(image_desc_t *im, char ***prdata)
1230 long i,ii,validsteps;
1233 int graphelement = 0;
1236 double magfact = -1;
1240 if (im->imginfo) prlines++;
1241 for(i=0;i<im->gdes_c;i++){
1242 switch(im->gdes[i].gf){
1245 if(((*prdata) = rrd_realloc((*prdata),prlines*sizeof(char *)))==NULL){
1246 rrd_set_error("realloc prdata");
1250 /* PRINT and GPRINT can now print VDEF generated values.
1251 * There's no need to do any calculations on them as these
1252 * calculations were already made.
1254 vidx = im->gdes[i].vidx;
1255 if (im->gdes[vidx].gf==GF_VDEF) { /* simply use vals */
1256 printval = im->gdes[vidx].vf.val;
1257 printtime = im->gdes[vidx].vf.when;
1258 } else { /* need to calculate max,min,avg etcetera */
1259 max_ii =((im->gdes[vidx].end
1260 - im->gdes[vidx].start)
1261 / im->gdes[vidx].step
1262 * im->gdes[vidx].ds_cnt);
1265 for( ii=im->gdes[vidx].ds;
1267 ii+=im->gdes[vidx].ds_cnt){
1268 if (! finite(im->gdes[vidx].data[ii]))
1270 if (isnan(printval)){
1271 printval = im->gdes[vidx].data[ii];
1276 switch (im->gdes[i].cf){
1279 case CF_DEVSEASONAL:
1283 printval += im->gdes[vidx].data[ii];
1286 printval = min( printval, im->gdes[vidx].data[ii]);
1290 printval = max( printval, im->gdes[vidx].data[ii]);
1293 printval = im->gdes[vidx].data[ii];
1296 if (im->gdes[i].cf==CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1297 if (validsteps > 1) {
1298 printval = (printval / validsteps);
1301 } /* prepare printval */
1303 if (!strcmp(im->gdes[i].format,"%c")) { /* VDEF time print */
1304 char ctime_buf[128]; /* PS: for ctime_r, must be >= 26 chars */
1306 ctime_r(&printtime,ctime_buf);
1307 while(isprint(ctime_buf[iii])){iii++;}
1308 ctime_buf[iii]='\0';
1309 if (im->gdes[i].gf == GF_PRINT){
1310 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1311 sprintf((*prdata)[prlines-2],"%s (%lu)",ctime_buf,printtime);
1312 (*prdata)[prlines-1] = NULL;
1314 sprintf(im->gdes[i].legend,"%s (%lu)",ctime_buf,printtime);
1318 if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) {
1319 /* Magfact is set to -1 upon entry to print_calc. If it
1320 * is still less than 0, then we need to run auto_scale.
1321 * Otherwise, put the value into the correct units. If
1322 * the value is 0, then do not set the symbol or magnification
1323 * so next the calculation will be performed again. */
1324 if (magfact < 0.0) {
1325 auto_scale(im,&printval,&si_symb,&magfact);
1326 if (printval == 0.0)
1329 printval /= magfact;
1331 *(++percent_s) = 's';
1332 } else if (strstr(im->gdes[i].format,"%s") != NULL) {
1333 auto_scale(im,&printval,&si_symb,&magfact);
1336 if (im->gdes[i].gf == GF_PRINT){
1337 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1338 (*prdata)[prlines-1] = NULL;
1339 if (bad_format(im->gdes[i].format)) {
1340 rrd_set_error("bad format for PRINT in '%s'", im->gdes[i].format);
1343 #ifdef HAVE_SNPRINTF
1344 snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
1346 sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
1351 if (bad_format(im->gdes[i].format)) {
1352 rrd_set_error("bad format for GPRINT in '%s'", im->gdes[i].format);
1355 #ifdef HAVE_SNPRINTF
1356 snprintf(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
1358 sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
1376 #ifdef WITH_PIECHART
1384 return graphelement;
1388 /* place legends with color spots */
1390 leg_place(image_desc_t *im)
1393 int interleg = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1394 int border = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1395 int fill=0, fill_last;
1397 int leg_x = border, leg_y = im->yimg;
1401 char prt_fctn; /*special printfunctions */
1404 if( !(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH) ) {
1405 if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1406 rrd_set_error("malloc for legspace");
1410 for(i=0;i<im->gdes_c;i++){
1413 /* hid legends for rules which are not displayed */
1415 if(!(im->extra_flags & FORCE_RULES_LEGEND)) {
1416 if (im->gdes[i].gf == GF_HRULE &&
1417 (im->gdes[i].yrule < im->minval || im->gdes[i].yrule > im->maxval))
1418 im->gdes[i].legend[0] = '\0';
1420 if (im->gdes[i].gf == GF_VRULE &&
1421 (im->gdes[i].xrule < im->start || im->gdes[i].xrule > im->end))
1422 im->gdes[i].legend[0] = '\0';
1425 leg_cc = strlen(im->gdes[i].legend);
1427 /* is there a controle code ant the end of the legend string ? */
1428 /* and it is not a tab \\t */
1429 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\' && im->gdes[i].legend[leg_cc-1] != 't') {
1430 prt_fctn = im->gdes[i].legend[leg_cc-1];
1432 im->gdes[i].legend[leg_cc] = '\0';
1436 /* remove exess space */
1437 while (prt_fctn=='g' &&
1439 im->gdes[i].legend[leg_cc-1]==' '){
1441 im->gdes[i].legend[leg_cc]='\0';
1444 legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1447 /* no interleg space if string ends in \g */
1448 fill += legspace[i];
1450 fill += gfx_get_text_width(im->canvas, fill+border,
1451 im->text_prop[TEXT_PROP_LEGEND].font,
1452 im->text_prop[TEXT_PROP_LEGEND].size,
1454 im->gdes[i].legend, 0);
1459 /* who said there was a special tag ... ?*/
1460 if (prt_fctn=='g') {
1463 if (prt_fctn == '\0') {
1464 if (i == im->gdes_c -1 ) prt_fctn ='l';
1466 /* is it time to place the legends ? */
1467 if (fill > im->ximg - 2*border){
1482 if (prt_fctn != '\0'){
1484 if (leg_c >= 2 && prt_fctn == 'j') {
1485 glue = (im->ximg - fill - 2* border) / (leg_c-1);
1489 if (prt_fctn =='c') leg_x = (im->ximg - fill) / 2.0;
1490 if (prt_fctn =='r') leg_x = im->ximg - fill - border;
1492 for(ii=mark;ii<=i;ii++){
1493 if(im->gdes[ii].legend[0]=='\0')
1494 continue; /* skip empty legends */
1495 im->gdes[ii].leg_x = leg_x;
1496 im->gdes[ii].leg_y = leg_y;
1498 gfx_get_text_width(im->canvas, leg_x,
1499 im->text_prop[TEXT_PROP_LEGEND].font,
1500 im->text_prop[TEXT_PROP_LEGEND].size,
1502 im->gdes[ii].legend, 0)
1506 leg_y += im->text_prop[TEXT_PROP_LEGEND].size*1.8;
1507 if (prt_fctn == 's') leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1519 /* create a grid on the graph. it determines what to do
1520 from the values of xsize, start and end */
1522 /* the xaxis labels are determined from the number of seconds per pixel
1523 in the requested graph */
1528 calc_horizontal_grid(image_desc_t *im)
1534 int decimals, fractionals;
1536 im->ygrid_scale.labfact=2;
1538 range = im->maxval - im->minval;
1539 scaledrange = range / im->magfact;
1541 /* does the scale of this graph make it impossible to put lines
1542 on it? If so, give up. */
1543 if (isnan(scaledrange)) {
1547 /* find grid spaceing */
1549 if(isnan(im->ygridstep)){
1550 if(im->extra_flags & ALTYGRID) {
1551 /* find the value with max number of digits. Get number of digits */
1552 decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))));
1553 if(decimals <= 0) /* everything is small. make place for zero */
1556 fractionals = floor(log10(range));
1557 if(fractionals < 0) { /* small amplitude. */
1558 int len = decimals - fractionals + 1;
1559 if (im->unitslength < len) im->unitslength = len;
1560 sprintf(im->ygrid_scale.labfmt, "%%%d.%df", len, -fractionals + 1);
1562 int len = decimals + 1;
1563 if (im->unitslength < len) im->unitslength = len;
1564 sprintf(im->ygrid_scale.labfmt, "%%%d.1f", len);
1566 im->ygrid_scale.gridstep = pow((double)10, (double)fractionals);
1567 if(im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1568 im->ygrid_scale.gridstep = 0.1;
1569 /* should have at least 5 lines but no more then 15 */
1570 if(range/im->ygrid_scale.gridstep < 5)
1571 im->ygrid_scale.gridstep /= 10;
1572 if(range/im->ygrid_scale.gridstep > 15)
1573 im->ygrid_scale.gridstep *= 10;
1574 if(range/im->ygrid_scale.gridstep > 5) {
1575 im->ygrid_scale.labfact = 1;
1576 if(range/im->ygrid_scale.gridstep > 8)
1577 im->ygrid_scale.labfact = 2;
1580 im->ygrid_scale.gridstep /= 5;
1581 im->ygrid_scale.labfact = 5;
1585 for(i=0;ylab[i].grid > 0;i++){
1586 pixel = im->ysize / (scaledrange / ylab[i].grid);
1594 if (pixel * ylab[gridind].lfac[i] >= 2.5 * im->text_prop[TEXT_PROP_AXIS].size) {
1595 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1600 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1603 im->ygrid_scale.gridstep = im->ygridstep;
1604 im->ygrid_scale.labfact = im->ylabfact;
1609 int draw_horizontal_grid(image_desc_t *im)
1613 char graph_label[100];
1614 double X0=im->xorigin;
1615 double X1=im->xorigin+im->xsize;
1617 int sgrid = (int)( im->minval / im->ygrid_scale.gridstep - 1);
1618 int egrid = (int)( im->maxval / im->ygrid_scale.gridstep + 1);
1620 scaledstep = im->ygrid_scale.gridstep/im->magfact;
1621 MaxY = scaledstep*(double)im->viewfactor*(double)egrid;
1622 for (i = sgrid; i <= egrid; i++){
1623 double Y0=ytr(im,im->ygrid_scale.gridstep*i);
1624 if ( Y0 >= im->yorigin-im->ysize
1625 && Y0 <= im->yorigin){
1626 if(i % im->ygrid_scale.labfact == 0){
1627 if (i==0 || im->symbol == ' ') {
1629 if(im->extra_flags & ALTYGRID) {
1630 sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*im->viewfactor*i);
1633 sprintf(graph_label,"%4.1f",scaledstep*im->viewfactor*i);
1636 sprintf(graph_label,"%4.0f",scaledstep*im->viewfactor*i);
1640 sprintf(graph_label,"%4.1f %c",scaledstep*im->viewfactor*i, im->symbol);
1642 sprintf(graph_label,"%4.0f %c",scaledstep*im->viewfactor*i, im->symbol);
1646 gfx_new_text ( im->canvas,
1647 X0-im->text_prop[TEXT_PROP_AXIS].size, Y0,
1648 im->graph_col[GRC_FONT],
1649 im->text_prop[TEXT_PROP_AXIS].font,
1650 im->text_prop[TEXT_PROP_AXIS].size,
1651 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1653 gfx_new_dashed_line ( im->canvas,
1656 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1657 im->grid_dash_on, im->grid_dash_off);
1659 } else if (!(im->extra_flags & NOMINOR)) {
1660 gfx_new_dashed_line ( im->canvas,
1663 GRIDWIDTH, im->graph_col[GRC_GRID],
1664 im->grid_dash_on, im->grid_dash_off);
1672 /* logaritmic horizontal grid */
1674 horizontal_log_grid(image_desc_t *im)
1678 int minoridx=0, majoridx=0;
1679 char graph_label[100];
1681 double value, pixperstep, minstep;
1683 /* find grid spaceing */
1684 pixpex= (double)im->ysize / (log10(im->maxval) - log10(im->minval));
1686 if (isnan(pixpex)) {
1690 for(i=0;yloglab[i][0] > 0;i++){
1691 minstep = log10(yloglab[i][0]);
1692 for(ii=1;yloglab[i][ii+1] > 0;ii++){
1693 if(yloglab[i][ii+2]==0){
1694 minstep = log10(yloglab[i][ii+1])-log10(yloglab[i][ii]);
1698 pixperstep = pixpex * minstep;
1699 if(pixperstep > 5){minoridx = i;}
1700 if(pixperstep > 2 * im->text_prop[TEXT_PROP_LEGEND].size){majoridx = i;}
1704 X1=im->xorigin+im->xsize;
1705 /* paint minor grid */
1706 for (value = pow((double)10, log10(im->minval)
1707 - fmod(log10(im->minval),log10(yloglab[minoridx][0])));
1708 value <= im->maxval;
1709 value *= yloglab[minoridx][0]){
1710 if (value < im->minval) continue;
1712 while(yloglab[minoridx][++i] > 0){
1713 Y0 = ytr(im,value * yloglab[minoridx][i]);
1714 if (Y0 <= im->yorigin - im->ysize) break;
1715 gfx_new_dashed_line ( im->canvas,
1718 GRIDWIDTH, im->graph_col[GRC_GRID],
1719 im->grid_dash_on, im->grid_dash_off);
1723 /* paint major grid and labels*/
1724 for (value = pow((double)10, log10(im->minval)
1725 - fmod(log10(im->minval),log10(yloglab[majoridx][0])));
1726 value <= im->maxval;
1727 value *= yloglab[majoridx][0]){
1728 if (value < im->minval) continue;
1730 while(yloglab[majoridx][++i] > 0){
1731 Y0 = ytr(im,value * yloglab[majoridx][i]);
1732 if (Y0 <= im->yorigin - im->ysize) break;
1733 gfx_new_dashed_line ( im->canvas,
1736 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1737 im->grid_dash_on, im->grid_dash_off);
1739 sprintf(graph_label,"%3.0e",value * yloglab[majoridx][i]);
1740 gfx_new_text ( im->canvas,
1741 X0-im->text_prop[TEXT_PROP_AXIS].size, Y0,
1742 im->graph_col[GRC_FONT],
1743 im->text_prop[TEXT_PROP_AXIS].font,
1744 im->text_prop[TEXT_PROP_AXIS].size,
1745 im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1757 int xlab_sel; /* which sort of label and grid ? */
1758 time_t ti, tilab, timajor;
1760 char graph_label[100];
1761 double X0,Y0,Y1; /* points for filled graph and more*/
1764 /* the type of time grid is determined by finding
1765 the number of seconds per pixel in the graph */
1768 if(im->xlab_user.minsec == -1){
1769 factor=(im->end - im->start)/im->xsize;
1771 while ( xlab[xlab_sel+1].minsec != -1
1772 && xlab[xlab_sel+1].minsec <= factor){ xlab_sel++; }
1773 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
1774 im->xlab_user.gridst = xlab[xlab_sel].gridst;
1775 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
1776 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
1777 im->xlab_user.labtm = xlab[xlab_sel].labtm;
1778 im->xlab_user.labst = xlab[xlab_sel].labst;
1779 im->xlab_user.precis = xlab[xlab_sel].precis;
1780 im->xlab_user.stst = xlab[xlab_sel].stst;
1783 /* y coords are the same for every line ... */
1785 Y1 = im->yorigin-im->ysize;
1788 /* paint the minor grid */
1789 if (!(im->extra_flags & NOMINOR))
1791 for(ti = find_first_time(im->start,
1792 im->xlab_user.gridtm,
1793 im->xlab_user.gridst),
1794 timajor = find_first_time(im->start,
1795 im->xlab_user.mgridtm,
1796 im->xlab_user.mgridst);
1798 ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
1800 /* are we inside the graph ? */
1801 if (ti < im->start || ti > im->end) continue;
1802 while (timajor < ti) {
1803 timajor = find_next_time(timajor,
1804 im->xlab_user.mgridtm, im->xlab_user.mgridst);
1806 if (ti == timajor) continue; /* skip as falls on major grid line */
1808 gfx_new_dashed_line(im->canvas,X0,Y0+1, X0,Y1-1,GRIDWIDTH,
1809 im->graph_col[GRC_GRID],
1810 im->grid_dash_on, im->grid_dash_off);
1815 /* paint the major grid */
1816 for(ti = find_first_time(im->start,
1817 im->xlab_user.mgridtm,
1818 im->xlab_user.mgridst);
1820 ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
1822 /* are we inside the graph ? */
1823 if (ti < im->start || ti > im->end) continue;
1825 gfx_new_dashed_line(im->canvas,X0,Y0+3, X0,Y1-2,MGRIDWIDTH,
1826 im->graph_col[GRC_MGRID],
1827 im->grid_dash_on, im->grid_dash_off);
1830 /* paint the labels below the graph */
1831 for(ti = find_first_time(im->start - im->xlab_user.precis/2,
1832 im->xlab_user.labtm,
1833 im->xlab_user.labst);
1834 ti <= im->end - im->xlab_user.precis/2;
1835 ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
1837 tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
1838 /* are we inside the graph ? */
1839 if (tilab < im->start || tilab > im->end) continue;
1842 localtime_r(&tilab, &tm);
1843 strftime(graph_label,99,im->xlab_user.stst, &tm);
1845 # error "your libc has no strftime I guess we'll abort the exercise here."
1847 gfx_new_text ( im->canvas,
1848 xtr(im,tilab), Y0+im->text_prop[TEXT_PROP_AXIS].size,
1849 im->graph_col[GRC_FONT],
1850 im->text_prop[TEXT_PROP_AXIS].font,
1851 im->text_prop[TEXT_PROP_AXIS].size,
1852 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP,
1865 /* draw x and y axis */
1866 /* gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
1867 im->xorigin+im->xsize,im->yorigin-im->ysize,
1868 GRIDWIDTH, im->graph_col[GRC_AXIS]);
1870 gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
1871 im->xorigin+im->xsize,im->yorigin-im->ysize,
1872 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
1874 gfx_new_line ( im->canvas, im->xorigin-4,im->yorigin,
1875 im->xorigin+im->xsize+4,im->yorigin,
1876 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
1878 gfx_new_line ( im->canvas, im->xorigin,im->yorigin+4,
1879 im->xorigin,im->yorigin-im->ysize-4,
1880 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
1883 /* arrow for X and Y axis direction */
1884 gfx_new_area ( im->canvas,
1885 im->xorigin+im->xsize+2, im->yorigin-2,
1886 im->xorigin+im->xsize+2, im->yorigin+3,
1887 im->xorigin+im->xsize+7, im->yorigin+0.5, /* LINEOFFSET */
1888 im->graph_col[GRC_ARROW]);
1890 gfx_new_area ( im->canvas,
1891 im->xorigin-2, im->yorigin-im->ysize-2,
1892 im->xorigin+3, im->yorigin-im->ysize-2,
1893 im->xorigin+0.5, im->yorigin-im->ysize-7, /* LINEOFFSET */
1894 im->graph_col[GRC_ARROW]);
1899 grid_paint(image_desc_t *im)
1903 double X0,Y0; /* points for filled graph and more*/
1906 /* draw 3d border */
1907 node = gfx_new_area (im->canvas, 0,im->yimg,
1909 2,2,im->graph_col[GRC_SHADEA]);
1910 gfx_add_point( node , im->ximg - 2, 2 );
1911 gfx_add_point( node , im->ximg, 0 );
1912 gfx_add_point( node , 0,0 );
1913 /* gfx_add_point( node , 0,im->yimg ); */
1915 node = gfx_new_area (im->canvas, 2,im->yimg-2,
1916 im->ximg-2,im->yimg-2,
1918 im->graph_col[GRC_SHADEB]);
1919 gfx_add_point( node , im->ximg,0);
1920 gfx_add_point( node , im->ximg,im->yimg);
1921 gfx_add_point( node , 0,im->yimg);
1922 /* gfx_add_point( node , 0,im->yimg ); */
1925 if (im->draw_x_grid == 1 )
1928 if (im->draw_y_grid == 1){
1929 if(im->logarithmic){
1930 res = horizontal_log_grid(im);
1932 res = draw_horizontal_grid(im);
1935 /* dont draw horizontal grid if there is no min and max val */
1937 char *nodata = "No Data found";
1938 gfx_new_text(im->canvas,im->ximg/2, (2*im->yorigin-im->ysize) / 2,
1939 im->graph_col[GRC_FONT],
1940 im->text_prop[TEXT_PROP_AXIS].font,
1941 im->text_prop[TEXT_PROP_AXIS].size,
1942 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
1947 /* yaxis unit description */
1948 gfx_new_text( im->canvas,
1949 10, (im->yorigin - im->ysize/2),
1950 im->graph_col[GRC_FONT],
1951 im->text_prop[TEXT_PROP_UNIT].font,
1952 im->text_prop[TEXT_PROP_UNIT].size, im->tabwidth,
1953 RRDGRAPH_YLEGEND_ANGLE,
1954 GFX_H_LEFT, GFX_V_CENTER,
1958 gfx_new_text( im->canvas,
1959 im->ximg/2, im->text_prop[TEXT_PROP_TITLE].size*1.3+4,
1960 im->graph_col[GRC_FONT],
1961 im->text_prop[TEXT_PROP_TITLE].font,
1962 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
1963 GFX_H_CENTER, GFX_V_CENTER,
1965 /* rrdtool 'logo' */
1966 gfx_new_text( im->canvas,
1968 ( im->graph_col[GRC_FONT] & 0xffffff00 ) | 0x00000044,
1969 im->text_prop[TEXT_PROP_AXIS].font,
1970 5.5, im->tabwidth, 270,
1971 GFX_H_RIGHT, GFX_V_TOP,
1972 "RRDTOOL / TOBI OETIKER");
1975 if( !(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH) ) {
1976 for(i=0;i<im->gdes_c;i++){
1977 if(im->gdes[i].legend[0] =='\0')
1980 /* im->gdes[i].leg_y is the bottom of the legend */
1981 X0 = im->gdes[i].leg_x;
1982 Y0 = im->gdes[i].leg_y;
1983 gfx_new_text ( im->canvas, X0, Y0,
1984 im->graph_col[GRC_FONT],
1985 im->text_prop[TEXT_PROP_LEGEND].font,
1986 im->text_prop[TEXT_PROP_LEGEND].size,
1987 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
1988 im->gdes[i].legend );
1989 /* The legend for GRAPH items starts with "M " to have
1990 enough space for the box */
1991 if ( im->gdes[i].gf != GF_PRINT &&
1992 im->gdes[i].gf != GF_GPRINT &&
1993 im->gdes[i].gf != GF_COMMENT) {
1996 boxH = gfx_get_text_width(im->canvas, 0,
1997 im->text_prop[TEXT_PROP_LEGEND].font,
1998 im->text_prop[TEXT_PROP_LEGEND].size,
1999 im->tabwidth,"o", 0) * 1.2;
2002 /* make sure transparent colors show up the same way as in the graph */
2003 node = gfx_new_area(im->canvas,
2007 im->graph_col[GRC_BACK]);
2008 gfx_add_point ( node, X0+boxH, Y0-boxV );
2010 node = gfx_new_area(im->canvas,
2015 gfx_add_point ( node, X0+boxH, Y0-boxV );
2016 node = gfx_new_line(im->canvas,
2019 1.0,im->graph_col[GRC_FRAME]);
2020 gfx_add_point(node,X0+boxH,Y0);
2021 gfx_add_point(node,X0+boxH,Y0-boxV);
2022 gfx_close_path(node);
2029 /*****************************************************
2030 * lazy check make sure we rely need to create this graph
2031 *****************************************************/
2033 int lazy_check(image_desc_t *im){
2036 struct stat imgstat;
2038 if (im->lazy == 0) return 0; /* no lazy option */
2039 if (stat(im->graphfile,&imgstat) != 0)
2040 return 0; /* can't stat */
2041 /* one pixel in the existing graph is more then what we would
2043 if (time(NULL) - imgstat.st_mtime >
2044 (im->end - im->start) / im->xsize)
2046 if ((fd = fopen(im->graphfile,"rb")) == NULL)
2047 return 0; /* the file does not exist */
2048 switch (im->canvas->imgformat) {
2050 size = PngSize(fd,&(im->ximg),&(im->yimg));
2059 #ifdef WITH_PIECHART
2061 pie_part(image_desc_t *im, gfx_color_t color,
2062 double PieCenterX, double PieCenterY, double Radius,
2063 double startangle, double endangle)
2067 double step=M_PI/50; /* Number of iterations for the circle;
2068 ** 10 is definitely too low, more than
2069 ** 50 seems to be overkill
2072 /* Strange but true: we have to work clockwise or else
2073 ** anti aliasing nor transparency don't work.
2075 ** This test is here to make sure we do it right, also
2076 ** this makes the for...next loop more easy to implement.
2077 ** The return will occur if the user enters a negative number
2078 ** (which shouldn't be done according to the specs) or if the
2079 ** programmers do something wrong (which, as we all know, never
2080 ** happens anyway :)
2082 if (endangle<startangle) return;
2084 /* Hidden feature: Radius decreases each full circle */
2086 while (angle>=2*M_PI) {
2091 node=gfx_new_area(im->canvas,
2092 PieCenterX+sin(startangle)*Radius,
2093 PieCenterY-cos(startangle)*Radius,
2096 PieCenterX+sin(endangle)*Radius,
2097 PieCenterY-cos(endangle)*Radius,
2099 for (angle=endangle;angle-startangle>=step;angle-=step) {
2101 PieCenterX+sin(angle)*Radius,
2102 PieCenterY-cos(angle)*Radius );
2109 graph_size_location(image_desc_t *im, int elements
2111 #ifdef WITH_PIECHART
2117 /* The actual size of the image to draw is determined from
2118 ** several sources. The size given on the command line is
2119 ** the graph area but we need more as we have to draw labels
2120 ** and other things outside the graph area
2123 /* +-+-------------------------------------------+
2124 ** |l|.................title.....................|
2125 ** |e+--+-------------------------------+--------+
2128 ** |l| l| main graph area | chart |
2131 ** |r+--+-------------------------------+--------+
2132 ** |e| | x-axis labels | |
2133 ** |v+--+-------------------------------+--------+
2134 ** | |..............legends......................|
2135 ** +-+-------------------------------------------+
2141 #ifdef WITH_PIECHART
2146 Xlegend =0, Ylegend =0,
2148 Xspacing =15, Yspacing =15;
2150 if (im->extra_flags & ONLY_GRAPH) {
2152 im->ximg = im->xsize;
2153 im->yimg = im->ysize;
2154 im->yorigin = im->ysize;
2158 if (im->ylegend[0] != '\0' ) {
2159 Xvertical = im->text_prop[TEXT_PROP_UNIT].size *2;
2163 if (im->title[0] != '\0') {
2164 /* The title is placed "inbetween" two text lines so it
2165 ** automatically has some vertical spacing. The horizontal
2166 ** spacing is added here, on each side.
2168 /* don't care for the with of the title
2169 Xtitle = gfx_get_text_width(im->canvas, 0,
2170 im->text_prop[TEXT_PROP_TITLE].font,
2171 im->text_prop[TEXT_PROP_TITLE].size,
2173 im->title, 0) + 2*Xspacing; */
2174 Ytitle = im->text_prop[TEXT_PROP_TITLE].size*2.6+10;
2180 if (im->draw_x_grid) {
2181 Yxlabel=im->text_prop[TEXT_PROP_AXIS].size *2.5;
2183 if (im->draw_y_grid) {
2184 Xylabel=gfx_get_text_width(im->canvas, 0,
2185 im->text_prop[TEXT_PROP_AXIS].font,
2186 im->text_prop[TEXT_PROP_AXIS].size,
2188 "0", 0) * im->unitslength;
2192 #ifdef WITH_PIECHART
2194 im->piesize=im->xsize<im->ysize?im->xsize:im->ysize;
2200 /* Now calculate the total size. Insert some spacing where
2201 desired. im->xorigin and im->yorigin need to correspond
2202 with the lower left corner of the main graph area or, if
2203 this one is not set, the imaginary box surrounding the
2206 /* The legend width cannot yet be determined, as a result we
2207 ** have problems adjusting the image to it. For now, we just
2208 ** forget about it at all; the legend will have to fit in the
2209 ** size already allocated.
2211 im->ximg = Xylabel + Xmain + 2 * Xspacing;
2213 #ifdef WITH_PIECHART
2217 if (Xmain) im->ximg += Xspacing;
2218 #ifdef WITH_PIECHART
2219 if (Xpie) im->ximg += Xspacing;
2222 im->xorigin = Xspacing + Xylabel;
2224 /* the length of the title should not influence with width of the graph
2225 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2227 if (Xvertical) { /* unit description */
2228 im->ximg += Xvertical;
2229 im->xorigin += Xvertical;
2233 /* The vertical size is interesting... we need to compare
2234 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend} with Yvertical
2235 ** however we need to know {Ytitle+Ymain+Yxlabel} in order to
2236 ** start even thinking about Ylegend.
2238 ** Do it in three portions: First calculate the inner part,
2239 ** then do the legend, then adjust the total height of the img.
2242 /* reserve space for main and/or pie */
2244 im->yimg = Ymain + Yxlabel;
2246 #ifdef WITH_PIECHART
2247 if (im->yimg < Ypie) im->yimg = Ypie;
2250 im->yorigin = im->yimg - Yxlabel;
2252 /* reserve space for the title *or* some padding above the graph */
2255 im->yorigin += Ytitle;
2257 im->yimg += 1.5*Yspacing;
2258 im->yorigin += 1.5*Yspacing;
2260 /* reserve space for padding below the graph */
2261 im->yimg += Yspacing;
2264 /* Determine where to place the legends onto the image.
2265 ** Adjust im->yimg to match the space requirements.
2267 if(leg_place(im)==-1)
2272 if (Xlegend > im->ximg) {
2274 /* reposition Pie */
2278 #ifdef WITH_PIECHART
2279 /* The pie is placed in the upper right hand corner,
2280 ** just below the title (if any) and with sufficient
2284 im->pie_x = im->ximg - Xspacing - Xpie/2;
2285 im->pie_y = im->yorigin-Ymain+Ypie/2;
2287 im->pie_x = im->ximg/2;
2288 im->pie_y = im->yorigin-Ypie/2;
2295 /* draw that picture thing ... */
2297 graph_paint(image_desc_t *im, char ***calcpr)
2300 int lazy = lazy_check(im);
2301 #ifdef WITH_PIECHART
2303 double PieStart=0.0;
2308 double areazero = 0.0;
2309 enum gf_en stack_gf = GF_PRINT;
2310 graph_desc_t *lastgdes = NULL;
2312 /* if we are lazy and there is nothing to PRINT ... quit now */
2313 if (lazy && im->prt_c==0) return 0;
2315 /* pull the data from the rrd files ... */
2317 if(data_fetch(im)==-1)
2320 /* evaluate VDEF and CDEF operations ... */
2321 if(data_calc(im)==-1)
2324 #ifdef WITH_PIECHART
2325 /* check if we need to draw a piechart */
2326 for(i=0;i<im->gdes_c;i++){
2327 if (im->gdes[i].gf == GF_PART) {
2334 /* calculate and PRINT and GPRINT definitions. We have to do it at
2335 * this point because it will affect the length of the legends
2336 * if there are no graph elements we stop here ...
2337 * if we are lazy, try to quit ...
2339 i=print_calc(im,calcpr);
2342 #ifdef WITH_PIECHART
2345 ) || lazy) return 0;
2347 #ifdef WITH_PIECHART
2348 /* If there's only the pie chart to draw, signal this */
2349 if (i==0) piechart=2;
2352 /* get actual drawing data and find min and max values*/
2353 if(data_proc(im)==-1)
2356 if(!im->logarithmic){si_unit(im);} /* identify si magnitude Kilo, Mega Giga ? */
2358 if(!im->rigid && ! im->logarithmic)
2359 expand_range(im); /* make sure the upper and lower limit are
2362 if (!calc_horizontal_grid(im))
2369 /**************************************************************
2370 *** Calculating sizes and locations became a bit confusing ***
2371 *** so I moved this into a separate function. ***
2372 **************************************************************/
2373 if(graph_size_location(im,i
2374 #ifdef WITH_PIECHART
2380 /* the actual graph is created by going through the individual
2381 graph elements and then drawing them */
2383 node=gfx_new_area ( im->canvas,
2387 im->graph_col[GRC_BACK]);
2389 gfx_add_point(node,im->ximg, 0);
2391 #ifdef WITH_PIECHART
2392 if (piechart != 2) {
2394 node=gfx_new_area ( im->canvas,
2395 im->xorigin, im->yorigin,
2396 im->xorigin + im->xsize, im->yorigin,
2397 im->xorigin + im->xsize, im->yorigin-im->ysize,
2398 im->graph_col[GRC_CANVAS]);
2400 gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
2402 if (im->minval > 0.0)
2403 areazero = im->minval;
2404 if (im->maxval < 0.0)
2405 areazero = im->maxval;
2406 #ifdef WITH_PIECHART
2410 #ifdef WITH_PIECHART
2412 pie_part(im,im->graph_col[GRC_CANVAS],im->pie_x,im->pie_y,im->piesize*0.5,0,2*M_PI);
2416 for(i=0;i<im->gdes_c;i++){
2417 switch(im->gdes[i].gf){
2430 for (ii = 0; ii < im->xsize; ii++)
2432 if (!isnan(im->gdes[i].p_data[ii]) &&
2433 im->gdes[i].p_data[ii] > 0.0)
2435 /* generate a tick */
2436 gfx_new_line(im->canvas, im -> xorigin + ii,
2437 im -> yorigin - (im -> gdes[i].yrule * im -> ysize),
2441 im -> gdes[i].col );
2447 stack_gf = im->gdes[i].gf;
2449 /* fix data points at oo and -oo */
2450 for(ii=0;ii<im->xsize;ii++){
2451 if (isinf(im->gdes[i].p_data[ii])){
2452 if (im->gdes[i].p_data[ii] > 0) {
2453 im->gdes[i].p_data[ii] = im->maxval ;
2455 im->gdes[i].p_data[ii] = im->minval ;
2461 /* *******************************************************
2466 -------|--t-1--t--------------------------------
2468 if we know the value at time t was a then
2469 we draw a square from t-1 to t with the value a.
2471 ********************************************************* */
2472 if (im->gdes[i].col != 0x0){
2473 /* GF_LINE and friend */
2474 if(stack_gf == GF_LINE ){
2476 for(ii=1;ii<im->xsize;ii++){
2477 if (isnan(im->gdes[i].p_data[ii]) || (im->slopemode==1 && isnan(im->gdes[i].p_data[ii-1]))){
2481 if ( node == NULL ) {
2482 if ( im->slopemode == 0 ){
2483 node = gfx_new_line(im->canvas,
2484 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2485 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2486 im->gdes[i].linewidth,
2489 node = gfx_new_line(im->canvas,
2490 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2491 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2492 im->gdes[i].linewidth,
2496 if ( im->slopemode==0 ){
2497 gfx_add_point(node,ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2499 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2504 double ybase0 = DNAN,ytop0=DNAN;
2505 for(ii=0;ii<im->xsize;ii++){
2506 /* keep things simple for now, just draw these bars
2507 do not try to build a big and complex area */
2509 if ( im->slopemode == 0 && ii==0){
2512 if ( isnan(im->gdes[i].p_data[ii]) ) {
2516 ytop = ytr(im,im->gdes[i].p_data[ii]);
2517 if ( lastgdes && im->gdes[i].stack ) {
2518 ybase = ytr(im,lastgdes->p_data[ii]);
2520 ybase = ytr(im,areazero);
2522 if ( ybase == ytop ){
2526 /* every area has to be wound clock-wise,
2527 so we have to make sur base remains base */
2529 double extra = ytop;
2533 if ( im->slopemode == 0){
2537 if ( !isnan(ybase0) ){
2538 node = gfx_new_area(im->canvas,
2539 (double)ii-1.2+(double)im->xorigin,ybase0-0.2,
2540 (double)ii-1.2+(double)im->xorigin,ytop0+0.2,
2541 (double)ii+0.2+(double)im->xorigin,ytop+0.2,
2545 (double)ii+0.02+im->xorigin,ybase-0.2
2551 } /* else GF_LINE */
2552 } /* if color != 0x0 */
2553 /* make sure we do not run into trouble when stacking on NaN */
2554 for(ii=0;ii<im->xsize;ii++){
2555 if (isnan(im->gdes[i].p_data[ii])) {
2556 if (lastgdes && (im->gdes[i].stack)) {
2557 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
2559 im->gdes[i].p_data[ii] = ytr(im,areazero);
2563 lastgdes = &(im->gdes[i]);
2565 #ifdef WITH_PIECHART
2567 if(isnan(im->gdes[i].yrule)) /* fetch variable */
2568 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2570 if (finite(im->gdes[i].yrule)) { /* even the fetched var can be NaN */
2571 pie_part(im,im->gdes[i].col,
2572 im->pie_x,im->pie_y,im->piesize*0.4,
2573 M_PI*2.0*PieStart/100.0,
2574 M_PI*2.0*(PieStart+im->gdes[i].yrule)/100.0);
2575 PieStart += im->gdes[i].yrule;
2582 #ifdef WITH_PIECHART
2590 /* grid_paint also does the text */
2591 if( !(im->extra_flags & ONLY_GRAPH) )
2595 if( !(im->extra_flags & ONLY_GRAPH) )
2598 /* the RULES are the last thing to paint ... */
2599 for(i=0;i<im->gdes_c;i++){
2601 switch(im->gdes[i].gf){
2603 if(isnan(im->gdes[i].yrule)) { /* fetch variable */
2604 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2606 if(im->gdes[i].yrule >= im->minval
2607 && im->gdes[i].yrule <= im->maxval)
2608 gfx_new_line(im->canvas,
2609 im->xorigin,ytr(im,im->gdes[i].yrule),
2610 im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2611 1.0,im->gdes[i].col);
2614 if(im->gdes[i].xrule == 0) { /* fetch variable */
2615 im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
2617 if(im->gdes[i].xrule >= im->start
2618 && im->gdes[i].xrule <= im->end)
2619 gfx_new_line(im->canvas,
2620 xtr(im,im->gdes[i].xrule),im->yorigin,
2621 xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2622 1.0,im->gdes[i].col);
2630 if (strcmp(im->graphfile,"-")==0) {
2631 fo = im->graphhandle ? im->graphhandle : stdout;
2632 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
2633 /* Change translation mode for stdout to BINARY */
2634 _setmode( _fileno( fo ), O_BINARY );
2637 if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2638 rrd_set_error("Opening '%s' for write: %s",im->graphfile,
2639 rrd_strerror(errno));
2643 gfx_render (im->canvas,im->ximg,im->yimg,0x00000000,fo);
2644 if (strcmp(im->graphfile,"-") != 0)
2650 /*****************************************************
2652 *****************************************************/
2655 gdes_alloc(image_desc_t *im){
2658 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2659 * sizeof(graph_desc_t)))==NULL){
2660 rrd_set_error("realloc graph_descs");
2665 im->gdes[im->gdes_c-1].step=im->step;
2666 im->gdes[im->gdes_c-1].stack=0;
2667 im->gdes[im->gdes_c-1].debug=0;
2668 im->gdes[im->gdes_c-1].start=im->start;
2669 im->gdes[im->gdes_c-1].end=im->end;
2670 im->gdes[im->gdes_c-1].vname[0]='\0';
2671 im->gdes[im->gdes_c-1].data=NULL;
2672 im->gdes[im->gdes_c-1].ds_namv=NULL;
2673 im->gdes[im->gdes_c-1].data_first=0;
2674 im->gdes[im->gdes_c-1].p_data=NULL;
2675 im->gdes[im->gdes_c-1].rpnp=NULL;
2676 im->gdes[im->gdes_c-1].shift=0;
2677 im->gdes[im->gdes_c-1].col = 0x0;
2678 im->gdes[im->gdes_c-1].legend[0]='\0';
2679 im->gdes[im->gdes_c-1].format[0]='\0';
2680 im->gdes[im->gdes_c-1].rrd[0]='\0';
2681 im->gdes[im->gdes_c-1].ds=-1;
2682 im->gdes[im->gdes_c-1].p_data=NULL;
2683 im->gdes[im->gdes_c-1].yrule=DNAN;
2684 im->gdes[im->gdes_c-1].xrule=0;
2688 /* copies input untill the first unescaped colon is found
2689 or until input ends. backslashes have to be escaped as well */
2691 scan_for_col(char *input, int len, char *output)
2696 input[inp] != ':' &&
2699 if (input[inp] == '\\' &&
2700 input[inp+1] != '\0' &&
2701 (input[inp+1] == '\\' ||
2702 input[inp+1] == ':')){
2703 output[outp++] = input[++inp];
2706 output[outp++] = input[inp];
2709 output[outp] = '\0';
2712 /* Some surgery done on this function, it became ridiculously big.
2714 ** - initializing now in rrd_graph_init()
2715 ** - options parsing now in rrd_graph_options()
2716 ** - script parsing now in rrd_graph_script()
2719 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize, FILE *stream, double *ymin, double *ymax)
2722 rrd_graph_init(&im);
2723 im.graphhandle = stream;
2725 rrd_graph_options(argc,argv,&im);
2726 if (rrd_test_error()) {
2731 if (strlen(argv[optind])>=MAXPATH) {
2732 rrd_set_error("filename (including path) too long");
2736 strncpy(im.graphfile,argv[optind],MAXPATH-1);
2737 im.graphfile[MAXPATH-1]='\0';
2739 rrd_graph_script(argc,argv,&im,1);
2740 if (rrd_test_error()) {
2745 /* Everything is now read and the actual work can start */
2748 if (graph_paint(&im,prdata)==-1){
2753 /* The image is generated and needs to be output.
2754 ** Also, if needed, print a line with information about the image.
2764 /* maybe prdata is not allocated yet ... lets do it now */
2765 if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
2766 rrd_set_error("malloc imginfo");
2770 if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
2772 rrd_set_error("malloc imginfo");
2775 filename=im.graphfile+strlen(im.graphfile);
2776 while(filename > im.graphfile) {
2777 if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
2781 sprintf((*prdata)[0],im.imginfo,filename,(long)(im.canvas->zoom*im.ximg),(long)(im.canvas->zoom*im.yimg));
2788 rrd_graph_init(image_desc_t *im)
2795 #ifdef HAVE_SETLOCALE
2796 setlocale(LC_TIME,"");
2801 im->xlab_user.minsec = -1;
2807 im->ylegend[0] = '\0';
2808 im->title[0] = '\0';
2811 im->unitsexponent= 9999;
2814 im->viewfactor = 1.0;
2821 im->logarithmic = 0;
2822 im->ygridstep = DNAN;
2823 im->draw_x_grid = 1;
2824 im->draw_y_grid = 1;
2829 im->canvas = gfx_new_canvas();
2830 im->grid_dash_on = 1;
2831 im->grid_dash_off = 1;
2832 im->tabwidth = 40.0;
2834 for(i=0;i<DIM(graph_col);i++)
2835 im->graph_col[i]=graph_col[i];
2837 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
2840 char rrd_win_default_font[1000];
2841 windir = getenv("windir");
2842 /* %windir% is something like D:\windows or C:\winnt */
2843 if (windir != NULL) {
2844 strncpy(rrd_win_default_font,windir,999);
2845 rrd_win_default_font[999] = '\0';
2846 strcat(rrd_win_default_font,"\\fonts\\");
2847 strcat(rrd_win_default_font,RRD_DEFAULT_FONT);
2848 for(i=0;i<DIM(text_prop);i++){
2849 strncpy(text_prop[i].font,rrd_win_default_font,sizeof(text_prop[i].font)-1);
2850 text_prop[i].font[sizeof(text_prop[i].font)-1] = '\0';
2857 deffont = getenv("RRD_DEFAULT_FONT");
2858 if (deffont != NULL) {
2859 for(i=0;i<DIM(text_prop);i++){
2860 strncpy(text_prop[i].font,deffont,sizeof(text_prop[i].font)-1);
2861 text_prop[i].font[sizeof(text_prop[i].font)-1] = '\0';
2865 for(i=0;i<DIM(text_prop);i++){
2866 im->text_prop[i].size = text_prop[i].size;
2867 strcpy(im->text_prop[i].font,text_prop[i].font);
2872 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
2875 char *parsetime_error = NULL;
2876 char scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
2877 time_t start_tmp=0,end_tmp=0;
2879 struct rrd_time_value start_tv, end_tv;
2881 optind = 0; opterr = 0; /* initialize getopt */
2883 parsetime("end-24h", &start_tv);
2884 parsetime("now", &end_tv);
2887 static struct option long_options[] =
2889 {"start", required_argument, 0, 's'},
2890 {"end", required_argument, 0, 'e'},
2891 {"x-grid", required_argument, 0, 'x'},
2892 {"y-grid", required_argument, 0, 'y'},
2893 {"vertical-label",required_argument,0,'v'},
2894 {"width", required_argument, 0, 'w'},
2895 {"height", required_argument, 0, 'h'},
2896 {"interlaced", no_argument, 0, 'i'},
2897 {"upper-limit",required_argument, 0, 'u'},
2898 {"lower-limit",required_argument, 0, 'l'},
2899 {"rigid", no_argument, 0, 'r'},
2900 {"base", required_argument, 0, 'b'},
2901 {"logarithmic",no_argument, 0, 'o'},
2902 {"color", required_argument, 0, 'c'},
2903 {"font", required_argument, 0, 'n'},
2904 {"title", required_argument, 0, 't'},
2905 {"imginfo", required_argument, 0, 'f'},
2906 {"imgformat", required_argument, 0, 'a'},
2907 {"lazy", no_argument, 0, 'z'},
2908 {"zoom", required_argument, 0, 'm'},
2909 {"no-legend", no_argument, 0, 'g'},
2910 {"force-rules-legend",no_argument,0, 'F'},
2911 {"only-graph", no_argument, 0, 'j'},
2912 {"alt-y-grid", no_argument, 0, 'Y'},
2913 {"no-minor", no_argument, 0, 'I'},
2914 {"slope-mode", no_argument, 0, 'E'},
2915 {"alt-autoscale", no_argument, 0, 'A'},
2916 {"alt-autoscale-max", no_argument, 0, 'M'},
2917 {"no-gridfit", no_argument, 0, 'N'},
2918 {"units-exponent",required_argument, 0, 'X'},
2919 {"units-length",required_argument, 0, 'L'},
2920 {"step", required_argument, 0, 'S'},
2921 {"tabwidth", required_argument, 0, 'T'},
2922 {"font-render-mode", required_argument, 0, 'R'},
2923 {"font-smoothing-threshold", required_argument, 0, 'B'},
2924 {"alt-y-mrtg", no_argument, 0, 1000}, /* this has no effect it is just here to save old apps from crashing when they use it */
2926 int option_index = 0;
2928 int col_start,col_end;
2930 opt = getopt_long(argc, argv,
2931 "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:I:zgjFYAMEX:L:S:T:NR:B:",
2932 long_options, &option_index);
2939 im->extra_flags |= NOMINOR;
2942 im->extra_flags |= ALTYGRID;
2945 im->extra_flags |= ALTAUTOSCALE;
2948 im->extra_flags |= ALTAUTOSCALE_MAX;
2951 im->extra_flags |= ONLY_GRAPH;
2954 im->extra_flags |= NOLEGEND;
2957 im->extra_flags |= FORCE_RULES_LEGEND;
2960 im->unitsexponent = atoi(optarg);
2963 im->unitslength = atoi(optarg);
2966 im->tabwidth = atof(optarg);
2969 im->step = atoi(optarg);
2975 if ((parsetime_error = parsetime(optarg, &start_tv))) {
2976 rrd_set_error( "start time: %s", parsetime_error );
2981 if ((parsetime_error = parsetime(optarg, &end_tv))) {
2982 rrd_set_error( "end time: %s", parsetime_error );
2987 if(strcmp(optarg,"none") == 0){
2993 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
2995 &im->xlab_user.gridst,
2997 &im->xlab_user.mgridst,
2999 &im->xlab_user.labst,
3000 &im->xlab_user.precis,
3001 &stroff) == 7 && stroff != 0){
3002 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
3003 im->xlab_form[sizeof(im->xlab_form)-1] = '\0';
3004 if((int)(im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
3005 rrd_set_error("unknown keyword %s",scan_gtm);
3007 } else if ((int)(im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
3008 rrd_set_error("unknown keyword %s",scan_mtm);
3010 } else if ((int)(im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
3011 rrd_set_error("unknown keyword %s",scan_ltm);
3014 im->xlab_user.minsec = 1;
3015 im->xlab_user.stst = im->xlab_form;
3017 rrd_set_error("invalid x-grid format");
3023 if(strcmp(optarg,"none") == 0){
3031 &im->ylabfact) == 2) {
3032 if(im->ygridstep<=0){
3033 rrd_set_error("grid step must be > 0");
3035 } else if (im->ylabfact < 1){
3036 rrd_set_error("label factor must be > 0");
3040 rrd_set_error("invalid y-grid format");
3045 strncpy(im->ylegend,optarg,150);
3046 im->ylegend[150]='\0';
3049 im->maxval = atof(optarg);
3052 im->minval = atof(optarg);
3055 im->base = atol(optarg);
3056 if(im->base != 1024 && im->base != 1000 ){
3057 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
3062 long_tmp = atol(optarg);
3063 if (long_tmp < 10) {
3064 rrd_set_error("width below 10 pixels");
3067 im->xsize = long_tmp;
3070 long_tmp = atol(optarg);
3071 if (long_tmp < 10) {
3072 rrd_set_error("height below 10 pixels");
3075 im->ysize = long_tmp;
3078 im->canvas->interlaced = 1;
3084 im->imginfo = optarg;
3087 if((int)(im->canvas->imgformat = if_conv(optarg)) == -1) {
3088 rrd_set_error("unsupported graphics format '%s'",optarg);
3100 im->logarithmic = 1;
3101 if (isnan(im->minval))
3106 "%10[A-Z]#%n%8lx%n",
3107 col_nam,&col_start,&color,&col_end) == 2){
3109 int col_len = col_end - col_start;
3113 ((color & 0xF00) * 0x110000) |
3114 ((color & 0x0F0) * 0x011000) |
3115 ((color & 0x00F) * 0x001100) |
3121 ((color & 0xF000) * 0x11000) |
3122 ((color & 0x0F00) * 0x01100) |
3123 ((color & 0x00F0) * 0x00110) |
3124 ((color & 0x000F) * 0x00011)
3128 color = (color << 8) + 0xff /* shift left by 8 */;
3133 rrd_set_error("the color format is #RRGGBB[AA]");
3136 if((ci=grc_conv(col_nam)) != -1){
3137 im->graph_col[ci]=color;
3139 rrd_set_error("invalid color name '%s'",col_nam);
3143 rrd_set_error("invalid color def format");
3153 "%10[A-Z]:%lf:%1000s",
3154 prop,&size,font) == 3){
3156 if((sindex=text_prop_conv(prop)) != -1){
3157 for (propidx=sindex;propidx<TEXT_PROP_LAST;propidx++){
3159 im->text_prop[propidx].size=size;
3161 if (strlen(font) > 0){
3162 strcpy(im->text_prop[propidx].font,font);
3164 if (propidx==sindex && sindex != 0) break;
3167 rrd_set_error("invalid fonttag '%s'",prop);
3171 rrd_set_error("invalid text property format");
3177 im->canvas->zoom = atof(optarg);
3178 if (im->canvas->zoom <= 0.0) {
3179 rrd_set_error("zoom factor must be > 0");
3184 strncpy(im->title,optarg,150);
3185 im->title[150]='\0';
3189 if ( strcmp( optarg, "normal" ) == 0 )
3190 im->canvas->aa_type = AA_NORMAL;
3191 else if ( strcmp( optarg, "light" ) == 0 )
3192 im->canvas->aa_type = AA_LIGHT;
3193 else if ( strcmp( optarg, "mono" ) == 0 )
3194 im->canvas->aa_type = AA_NONE;
3197 rrd_set_error("unknown font-render-mode '%s'", optarg );
3203 im->canvas->font_aa_threshold = atof(optarg);
3208 rrd_set_error("unknown option '%c'", optopt);
3210 rrd_set_error("unknown option '%s'",argv[optind-1]);
3215 if (optind >= argc) {
3216 rrd_set_error("missing filename");
3220 if (im->logarithmic == 1 && (im->minval <= 0 || isnan(im->minval))){
3221 rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");
3225 if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
3226 /* error string is set in parsetime.c */
3230 if (start_tmp < 3600*24*365*10){
3231 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
3235 if (end_tmp < start_tmp) {
3236 rrd_set_error("start (%ld) should be less than end (%ld)",
3237 start_tmp, end_tmp);
3241 im->start = start_tmp;
3243 im->step = max((long)im->step, (im->end-im->start)/im->xsize);
3247 rrd_graph_check_vname(image_desc_t *im, char *varname, char *err)
3249 if ((im->gdes[im->gdes_c-1].vidx=find_var(im,varname))==-1) {
3250 rrd_set_error("Unknown variable '%s' in %s",varname,err);
3256 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
3259 graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
3261 color=strstr(var,"#");
3264 rrd_set_error("Found no color in %s",err);
3273 rest=strstr(color,":");
3281 sscanf(color,"#%6lx%n",&col,&n);
3282 col = (col << 8) + 0xff /* shift left by 8 */;
3283 if (n!=7) rrd_set_error("Color problem in %s",err);
3286 sscanf(color,"#%8lx%n",&col,&n);
3289 rrd_set_error("Color problem in %s",err);
3291 if (rrd_test_error()) return 0;
3298 int bad_format(char *fmt) {
3302 while (*ptr != '\0')
3303 if (*ptr++ == '%') {
3305 /* line cannot end with percent char */
3306 if (*ptr == '\0') return 1;
3308 /* '%s', '%S' and '%%' are allowed */
3309 if (*ptr == 's' || *ptr == 'S' || *ptr == '%') ptr++;
3311 /* or else '% 6.2lf' and such are allowed */
3314 /* optional padding character */
3315 if (*ptr == ' ' || *ptr == '+' || *ptr == '-') ptr++;
3317 /* This should take care of 'm.n' with all three optional */
3318 while (*ptr >= '0' && *ptr <= '9') ptr++;
3319 if (*ptr == '.') ptr++;
3320 while (*ptr >= '0' && *ptr <= '9') ptr++;
3322 /* Either 'le', 'lf' or 'lg' must follow here */
3323 if (*ptr++ != 'l') return 1;
3324 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g') ptr++;
3335 vdef_parse(gdes,str)
3336 struct graph_desc_t *gdes;
3339 /* A VDEF currently is either "func" or "param,func"
3340 * so the parsing is rather simple. Change if needed.
3347 sscanf(str,"%le,%29[A-Z]%n",¶m,func,&n);
3348 if (n== (int)strlen(str)) { /* matched */
3352 sscanf(str,"%29[A-Z]%n",func,&n);
3353 if (n== (int)strlen(str)) { /* matched */
3356 rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3363 if (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3364 else if (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3365 else if (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3366 else if (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3367 else if (!strcmp("TOTAL", func)) gdes->vf.op = VDEF_TOTAL;
3368 else if (!strcmp("FIRST", func)) gdes->vf.op = VDEF_FIRST;
3369 else if (!strcmp("LAST", func)) gdes->vf.op = VDEF_LAST;
3371 rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3378 switch (gdes->vf.op) {
3380 if (isnan(param)) { /* no parameter given */
3381 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3387 if (param>=0.0 && param<=100.0) {
3388 gdes->vf.param = param;
3389 gdes->vf.val = DNAN; /* undefined */
3390 gdes->vf.when = 0; /* undefined */
3392 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3406 gdes->vf.param = DNAN;
3407 gdes->vf.val = DNAN;
3410 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3427 graph_desc_t *src,*dst;
3431 dst = &im->gdes[gdi];
3432 src = &im->gdes[dst->vidx];
3433 data = src->data + src->ds;
3434 steps = (src->end - src->start) / src->step;
3437 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3444 switch (dst->vf.op) {
3445 case VDEF_PERCENT: {
3446 rrd_value_t * array;
3450 if ((array = malloc(steps*sizeof(double)))==NULL) {
3451 rrd_set_error("malloc VDEV_PERCENT");
3454 for (step=0;step < steps; step++) {
3455 array[step]=data[step*src->ds_cnt];
3457 qsort(array,step,sizeof(double),vdef_percent_compar);
3459 field = (steps-1)*dst->vf.param/100;
3460 dst->vf.val = array[field];
3461 dst->vf.when = 0; /* no time component */
3464 for(step=0;step<steps;step++)
3465 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3471 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3472 if (step == steps) {
3476 dst->vf.val = data[step*src->ds_cnt];
3477 dst->vf.when = src->start + (step+1)*src->step;
3479 while (step != steps) {
3480 if (finite(data[step*src->ds_cnt])) {
3481 if (data[step*src->ds_cnt] > dst->vf.val) {
3482 dst->vf.val = data[step*src->ds_cnt];
3483 dst->vf.when = src->start + (step+1)*src->step;
3490 case VDEF_AVERAGE: {
3493 for (step=0;step<steps;step++) {
3494 if (finite(data[step*src->ds_cnt])) {
3495 sum += data[step*src->ds_cnt];
3500 if (dst->vf.op == VDEF_TOTAL) {
3501 dst->vf.val = sum*src->step;
3502 dst->vf.when = cnt*src->step; /* not really "when" */
3504 dst->vf.val = sum/cnt;
3505 dst->vf.when = 0; /* no time component */
3515 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3516 if (step == steps) {
3520 dst->vf.val = data[step*src->ds_cnt];
3521 dst->vf.when = src->start + (step+1)*src->step;
3523 while (step != steps) {
3524 if (finite(data[step*src->ds_cnt])) {
3525 if (data[step*src->ds_cnt] < dst->vf.val) {
3526 dst->vf.val = data[step*src->ds_cnt];
3527 dst->vf.when = src->start + (step+1)*src->step;
3534 /* The time value returned here is one step before the
3535 * actual time value. This is the start of the first
3539 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3540 if (step == steps) { /* all entries were NaN */
3544 dst->vf.val = data[step*src->ds_cnt];
3545 dst->vf.when = src->start + step*src->step;
3549 /* The time value returned here is the
3550 * actual time value. This is the end of the last
3554 while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3555 if (step < 0) { /* all entries were NaN */
3559 dst->vf.val = data[step*src->ds_cnt];
3560 dst->vf.when = src->start + (step+1)*src->step;
3567 /* NaN < -INF < finite_values < INF */
3569 vdef_percent_compar(a,b)
3572 /* Equality is not returned; this doesn't hurt except
3573 * (maybe) for a little performance.
3576 /* First catch NaN values. They are smallest */
3577 if (isnan( *(double *)a )) return -1;
3578 if (isnan( *(double *)b )) return 1;
3580 /* NaN doesn't reach this part so INF and -INF are extremes.
3581 * The sign from isinf() is compatible with the sign we return
3583 if (isinf( *(double *)a )) return isinf( *(double *)a );
3584 if (isinf( *(double *)b )) return isinf( *(double *)b );
3586 /* If we reach this, both values must be finite */
3587 if ( *(double *)a < *(double *)b ) return -1; else return 1;