e16e5d84ba60110a3c4070cbe9f6fa87a2735897
1 /****************************************************************************
2 * RRDtool 1.2.20 Copyright by Tobi Oetiker, 1997-2007
3 ****************************************************************************
4 * rrd__graph.c produce graphs from data in rrdfiles
5 ****************************************************************************/
8 #include <sys/stat.h>
10 #ifdef WIN32
11 #include "strftime.h"
12 #endif
13 #include "rrd_tool.h"
15 #if defined(WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
16 #include <io.h>
17 #include <fcntl.h>
18 #endif
20 #ifdef HAVE_TIME_H
21 #include <time.h>
22 #endif
24 #ifdef HAVE_LOCALE_H
25 #include <locale.h>
26 #endif
28 #include "rrd_graph.h"
30 /* some constant definitions */
34 #ifndef RRD_DEFAULT_FONT
35 /* there is special code later to pick Cour.ttf when running on windows */
36 #define RRD_DEFAULT_FONT "DejaVuSansMono-Roman.ttf"
37 #endif
39 text_prop_t text_prop[] = {
40 { 8.0, RRD_DEFAULT_FONT }, /* default */
41 { 9.0, RRD_DEFAULT_FONT }, /* title */
42 { 7.0, RRD_DEFAULT_FONT }, /* axis */
43 { 8.0, RRD_DEFAULT_FONT }, /* unit */
44 { 8.0, RRD_DEFAULT_FONT } /* legend */
45 };
47 xlab_t xlab[] = {
48 {0, 0, TMT_SECOND,30, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
49 {2, 0, TMT_MINUTE,1, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
50 {5, 0, TMT_MINUTE,2, TMT_MINUTE,10, TMT_MINUTE,10, 0,"%H:%M"},
51 {10, 0, TMT_MINUTE,5, TMT_MINUTE,20, TMT_MINUTE,20, 0,"%H:%M"},
52 {30, 0, TMT_MINUTE,10, TMT_HOUR,1, TMT_HOUR,1, 0,"%H:%M"},
53 {60, 0, TMT_MINUTE,30, TMT_HOUR,2, TMT_HOUR,2, 0,"%H:%M"},
54 {60, 24*3600, TMT_MINUTE,30, TMT_HOUR,2, TMT_HOUR,4, 0,"%a %H:%M"},
55 {180, 0, TMT_HOUR,1, TMT_HOUR,6, TMT_HOUR,6, 0,"%H:%M"},
56 {180, 24*3600, TMT_HOUR,1, TMT_HOUR,6, TMT_HOUR,12, 0,"%a %H:%M"},
57 /*{300, 0, TMT_HOUR,3, TMT_HOUR,12, TMT_HOUR,12, 12*3600,"%a %p"}, this looks silly*/
58 {600, 0, TMT_HOUR,6, TMT_DAY,1, TMT_DAY,1, 24*3600,"%a"},
59 {1200, 0, TMT_HOUR,6, TMT_DAY,1, TMT_DAY,1, 24*3600,"%d"},
60 {1800, 0, TMT_HOUR,12, TMT_DAY,1, TMT_DAY,2, 24*3600,"%a %d"},
61 {2400, 0, TMT_HOUR,12, TMT_DAY,1, TMT_DAY,2, 24*3600,"%a"},
62 {3600, 0, TMT_DAY,1, TMT_WEEK,1, TMT_WEEK,1, 7*24*3600,"Week %V"},
63 {3*3600, 0, TMT_WEEK,1, TMT_MONTH,1, TMT_WEEK,2, 7*24*3600,"Week %V"},
64 {6*3600, 0, TMT_MONTH,1, TMT_MONTH,1, TMT_MONTH,1, 30*24*3600,"%b"},
65 {48*3600, 0, TMT_MONTH,1, TMT_MONTH,3, TMT_MONTH,3, 30*24*3600,"%b"},
66 {315360, 0, TMT_MONTH,3, TMT_YEAR,1, TMT_YEAR,1, 365*24*3600,"%Y"},
67 {10*24*3600, 0, TMT_YEAR,1, TMT_YEAR,1, TMT_YEAR,1, 365*24*3600,"%y"},
68 {-1,0,TMT_MONTH,0,TMT_MONTH,0,TMT_MONTH,0,0,""}
69 };
71 /* sensible y label intervals ...*/
73 ylab_t ylab[]= {
74 {0.1, {1,2, 5,10}},
75 {0.2, {1,5,10,20}},
76 {0.5, {1,2, 4,10}},
77 {1.0, {1,2, 5,10}},
78 {2.0, {1,5,10,20}},
79 {5.0, {1,2, 4,10}},
80 {10.0, {1,2, 5,10}},
81 {20.0, {1,5,10,20}},
82 {50.0, {1,2, 4,10}},
83 {100.0, {1,2, 5,10}},
84 {200.0, {1,5,10,20}},
85 {500.0, {1,2, 4,10}},
86 {0.0, {0,0,0,0}}};
89 gfx_color_t graph_col[] = /* default colors */
90 { 0xFFFFFFFF, /* canvas */
91 0xF0F0F0FF, /* background */
92 0xD0D0D0FF, /* shade A */
93 0xA0A0A0FF, /* shade B */
94 0x90909080, /* grid */
95 0xE0505080, /* major grid */
96 0x000000FF, /* font */
97 0x802020FF, /* arrow */
98 0x202020FF, /* axis */
99 0x000000FF /* frame */
100 };
103 /* #define DEBUG */
105 #ifdef DEBUG
106 # define DPRINT(x) (void)(printf x, printf("\n"))
107 #else
108 # define DPRINT(x)
109 #endif
112 /* initialize with xtr(im,0); */
113 int
114 xtr(image_desc_t *im,time_t mytime){
115 static double pixie;
116 if (mytime==0){
117 pixie = (double) im->xsize / (double)(im->end - im->start);
118 return im->xorigin;
119 }
120 return (int)((double)im->xorigin
121 + pixie * ( mytime - im->start ) );
122 }
124 /* translate data values into y coordinates */
125 double
126 ytr(image_desc_t *im, double value){
127 static double pixie;
128 double yval;
129 if (isnan(value)){
130 if(!im->logarithmic)
131 pixie = (double) im->ysize / (im->maxval - im->minval);
132 else
133 pixie = (double) im->ysize / (log10(im->maxval) - log10(im->minval));
134 yval = im->yorigin;
135 } else if(!im->logarithmic) {
136 yval = im->yorigin - pixie * (value - im->minval);
137 } else {
138 if (value < im->minval) {
139 yval = im->yorigin;
140 } else {
141 yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
142 }
143 }
144 /* make sure we don't return anything too unreasonable. GD lib can
145 get terribly slow when drawing lines outside its scope. This is
146 especially problematic in connection with the rigid option */
147 if (! im->rigid) {
148 /* keep yval as-is */
149 } else if (yval > im->yorigin) {
150 yval = im->yorigin +0.00001;
151 } else if (yval < im->yorigin - im->ysize){
152 yval = im->yorigin - im->ysize - 0.00001;
153 }
154 return yval;
155 }
159 /* conversion function for symbolic entry names */
162 #define conv_if(VV,VVV) \
163 if (strcmp(#VV, string) == 0) return VVV ;
165 enum gf_en gf_conv(char *string){
167 conv_if(PRINT,GF_PRINT)
168 conv_if(GPRINT,GF_GPRINT)
169 conv_if(COMMENT,GF_COMMENT)
170 conv_if(HRULE,GF_HRULE)
171 conv_if(VRULE,GF_VRULE)
172 conv_if(LINE,GF_LINE)
173 conv_if(AREA,GF_AREA)
174 conv_if(STACK,GF_STACK)
175 conv_if(TICK,GF_TICK)
176 conv_if(DEF,GF_DEF)
177 conv_if(CDEF,GF_CDEF)
178 conv_if(VDEF,GF_VDEF)
179 #ifdef WITH_PIECHART
180 conv_if(PART,GF_PART)
181 #endif
182 conv_if(XPORT,GF_XPORT)
183 conv_if(SHIFT,GF_SHIFT)
185 return (-1);
186 }
188 enum gfx_if_en if_conv(char *string){
190 conv_if(PNG,IF_PNG)
191 conv_if(SVG,IF_SVG)
192 conv_if(EPS,IF_EPS)
193 conv_if(PDF,IF_PDF)
195 return (-1);
196 }
198 enum tmt_en tmt_conv(char *string){
200 conv_if(SECOND,TMT_SECOND)
201 conv_if(MINUTE,TMT_MINUTE)
202 conv_if(HOUR,TMT_HOUR)
203 conv_if(DAY,TMT_DAY)
204 conv_if(WEEK,TMT_WEEK)
205 conv_if(MONTH,TMT_MONTH)
206 conv_if(YEAR,TMT_YEAR)
207 return (-1);
208 }
210 enum grc_en grc_conv(char *string){
212 conv_if(BACK,GRC_BACK)
213 conv_if(CANVAS,GRC_CANVAS)
214 conv_if(SHADEA,GRC_SHADEA)
215 conv_if(SHADEB,GRC_SHADEB)
216 conv_if(GRID,GRC_GRID)
217 conv_if(MGRID,GRC_MGRID)
218 conv_if(FONT,GRC_FONT)
219 conv_if(ARROW,GRC_ARROW)
220 conv_if(AXIS,GRC_AXIS)
221 conv_if(FRAME,GRC_FRAME)
223 return -1;
224 }
226 enum text_prop_en text_prop_conv(char *string){
228 conv_if(DEFAULT,TEXT_PROP_DEFAULT)
229 conv_if(TITLE,TEXT_PROP_TITLE)
230 conv_if(AXIS,TEXT_PROP_AXIS)
231 conv_if(UNIT,TEXT_PROP_UNIT)
232 conv_if(LEGEND,TEXT_PROP_LEGEND)
233 return -1;
234 }
237 #undef conv_if
239 int
240 im_free(image_desc_t *im)
241 {
242 unsigned long i,ii;
244 if (im == NULL) return 0;
245 for(i=0;i<(unsigned)im->gdes_c;i++){
246 if (im->gdes[i].data_first){
247 /* careful here, because a single pointer can occur several times */
248 free (im->gdes[i].data);
249 if (im->gdes[i].ds_namv){
250 for (ii=0;ii<im->gdes[i].ds_cnt;ii++)
251 free(im->gdes[i].ds_namv[ii]);
252 free(im->gdes[i].ds_namv);
253 }
254 }
255 free (im->gdes[i].p_data);
256 free (im->gdes[i].rpnp);
257 }
258 free(im->gdes);
259 gfx_destroy(im->canvas);
260 return 0;
261 }
263 /* find SI magnitude symbol for the given number*/
264 void
265 auto_scale(
266 image_desc_t *im, /* image description */
267 double *value,
268 char **symb_ptr,
269 double *magfact
270 )
271 {
273 char *symbol[] = {"a", /* 10e-18 Atto */
274 "f", /* 10e-15 Femto */
275 "p", /* 10e-12 Pico */
276 "n", /* 10e-9 Nano */
277 "u", /* 10e-6 Micro */
278 "m", /* 10e-3 Milli */
279 " ", /* Base */
280 "k", /* 10e3 Kilo */
281 "M", /* 10e6 Mega */
282 "G", /* 10e9 Giga */
283 "T", /* 10e12 Tera */
284 "P", /* 10e15 Peta */
285 "E"};/* 10e18 Exa */
287 int symbcenter = 6;
288 int sindex;
290 if (*value == 0.0 || isnan(*value) ) {
291 sindex = 0;
292 *magfact = 1.0;
293 } else {
294 sindex = floor(log(fabs(*value))/log((double)im->base));
295 *magfact = pow((double)im->base, (double)sindex);
296 (*value) /= (*magfact);
297 }
298 if ( sindex <= symbcenter && sindex >= -symbcenter) {
299 (*symb_ptr) = symbol[sindex+symbcenter];
300 }
301 else {
302 (*symb_ptr) = "?";
303 }
304 }
307 static char si_symbol[] = {
308 'a', /* 10e-18 Atto */
309 'f', /* 10e-15 Femto */
310 'p', /* 10e-12 Pico */
311 'n', /* 10e-9 Nano */
312 'u', /* 10e-6 Micro */
313 'm', /* 10e-3 Milli */
314 ' ', /* Base */
315 'k', /* 10e3 Kilo */
316 'M', /* 10e6 Mega */
317 'G', /* 10e9 Giga */
318 'T', /* 10e12 Tera */
319 'P', /* 10e15 Peta */
320 'E', /* 10e18 Exa */
321 };
322 static const int si_symbcenter = 6;
324 /* find SI magnitude symbol for the numbers on the y-axis*/
325 void
326 si_unit(
327 image_desc_t *im /* image description */
328 )
329 {
331 double digits,viewdigits=0;
333 digits = floor( log( max( fabs(im->minval),fabs(im->maxval)))/log((double)im->base));
335 if (im->unitsexponent != 9999) {
336 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
337 viewdigits = floor(im->unitsexponent / 3);
338 } else {
339 viewdigits = digits;
340 }
342 im->magfact = pow((double)im->base , digits);
344 #ifdef DEBUG
345 printf("digits %6.3f im->magfact %6.3f\n",digits,im->magfact);
346 #endif
348 im->viewfactor = im->magfact / pow((double)im->base , viewdigits);
350 if ( ((viewdigits+si_symbcenter) < sizeof(si_symbol)) &&
351 ((viewdigits+si_symbcenter) >= 0) )
352 im->symbol = si_symbol[(int)viewdigits+si_symbcenter];
353 else
354 im->symbol = '?';
355 }
357 /* move min and max values around to become sensible */
359 void
360 expand_range(image_desc_t *im)
361 {
362 double sensiblevalues[] ={1000.0,900.0,800.0,750.0,700.0,
363 600.0,500.0,400.0,300.0,250.0,
364 200.0,125.0,100.0,90.0,80.0,
365 75.0,70.0,60.0,50.0,40.0,30.0,
366 25.0,20.0,10.0,9.0,8.0,
367 7.0,6.0,5.0,4.0,3.5,3.0,
368 2.5,2.0,1.8,1.5,1.2,1.0,
369 0.8,0.7,0.6,0.5,0.4,0.3,0.2,0.1,0.0,-1};
371 double scaled_min,scaled_max;
372 double adj;
373 int i;
377 #ifdef DEBUG
378 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
379 im->minval,im->maxval,im->magfact);
380 #endif
382 if (isnan(im->ygridstep)){
383 if(im->extra_flags & ALTAUTOSCALE) {
384 /* measure the amplitude of the function. Make sure that
385 graph boundaries are slightly higher then max/min vals
386 so we can see amplitude on the graph */
387 double delt, fact;
389 delt = im->maxval - im->minval;
390 adj = delt * 0.1;
391 fact = 2.0 * pow(10.0,
392 floor(log10(max(fabs(im->minval), fabs(im->maxval))/im->magfact)) - 2);
393 if (delt < fact) {
394 adj = (fact - delt) * 0.55;
395 #ifdef DEBUG
396 printf("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n", im->minval, im->maxval, delt, fact, adj);
397 #endif
398 }
399 im->minval -= adj;
400 im->maxval += adj;
401 }
402 else if(im->extra_flags & ALTAUTOSCALE_MIN) {
403 /* measure the amplitude of the function. Make sure that
404 graph boundaries are slightly lower than min vals
405 so we can see amplitude on the graph */
406 adj = (im->maxval - im->minval) * 0.1;
407 im->minval -= adj;
408 }
409 else if(im->extra_flags & ALTAUTOSCALE_MAX) {
410 /* measure the amplitude of the function. Make sure that
411 graph boundaries are slightly higher than max vals
412 so we can see amplitude on the graph */
413 adj = (im->maxval - im->minval) * 0.1;
414 im->maxval += adj;
415 }
416 else {
417 scaled_min = im->minval / im->magfact;
418 scaled_max = im->maxval / im->magfact;
420 for (i=1; sensiblevalues[i] > 0; i++){
421 if (sensiblevalues[i-1]>=scaled_min &&
422 sensiblevalues[i]<=scaled_min)
423 im->minval = sensiblevalues[i]*(im->magfact);
425 if (-sensiblevalues[i-1]<=scaled_min &&
426 -sensiblevalues[i]>=scaled_min)
427 im->minval = -sensiblevalues[i-1]*(im->magfact);
429 if (sensiblevalues[i-1] >= scaled_max &&
430 sensiblevalues[i] <= scaled_max)
431 im->maxval = sensiblevalues[i-1]*(im->magfact);
433 if (-sensiblevalues[i-1]<=scaled_max &&
434 -sensiblevalues[i] >=scaled_max)
435 im->maxval = -sensiblevalues[i]*(im->magfact);
436 }
437 }
438 } else {
439 /* adjust min and max to the grid definition if there is one */
440 im->minval = (double)im->ylabfact * im->ygridstep *
441 floor(im->minval / ((double)im->ylabfact * im->ygridstep));
442 im->maxval = (double)im->ylabfact * im->ygridstep *
443 ceil(im->maxval /( (double)im->ylabfact * im->ygridstep));
444 }
446 #ifdef DEBUG
447 fprintf(stderr,"SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
448 im->minval,im->maxval,im->magfact);
449 #endif
450 }
452 void
453 apply_gridfit(image_desc_t *im)
454 {
455 if (isnan(im->minval) || isnan(im->maxval))
456 return;
457 ytr(im,DNAN);
458 if (im->logarithmic) {
459 double ya, yb, ypix, ypixfrac;
460 double log10_range = log10(im->maxval) - log10(im->minval);
461 ya = pow((double)10, floor(log10(im->minval)));
462 while (ya < im->minval)
463 ya *= 10;
464 if (ya > im->maxval)
465 return; /* don't have y=10^x gridline */
466 yb = ya * 10;
467 if (yb <= im->maxval) {
468 /* we have at least 2 y=10^x gridlines.
469 Make sure distance between them in pixels
470 are an integer by expanding im->maxval */
471 double y_pixel_delta = ytr(im, ya) - ytr(im, yb);
472 double factor = y_pixel_delta / floor(y_pixel_delta);
473 double new_log10_range = factor * log10_range;
474 double new_ymax_log10 = log10(im->minval) + new_log10_range;
475 im->maxval = pow(10, new_ymax_log10);
476 ytr(im,DNAN); /* reset precalc */
477 log10_range = log10(im->maxval) - log10(im->minval);
478 }
479 /* make sure first y=10^x gridline is located on
480 integer pixel position by moving scale slightly
481 downwards (sub-pixel movement) */
482 ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
483 ypixfrac = ypix - floor(ypix);
484 if (ypixfrac > 0 && ypixfrac < 1) {
485 double yfrac = ypixfrac / im->ysize;
486 im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
487 im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
488 ytr(im,DNAN); /* reset precalc */
489 }
490 } else {
491 /* Make sure we have an integer pixel distance between
492 each minor gridline */
493 double ypos1 = ytr(im, im->minval);
494 double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
495 double y_pixel_delta = ypos1 - ypos2;
496 double factor = y_pixel_delta / floor(y_pixel_delta);
497 double new_range = factor * (im->maxval - im->minval);
498 double gridstep = im->ygrid_scale.gridstep;
499 double minor_y, minor_y_px, minor_y_px_frac;
500 im->maxval = im->minval + new_range;
501 ytr(im,DNAN); /* reset precalc */
502 /* make sure first minor gridline is on integer pixel y coord */
503 minor_y = gridstep * floor(im->minval / gridstep);
504 while (minor_y < im->minval)
505 minor_y += gridstep;
506 minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
507 minor_y_px_frac = minor_y_px - floor(minor_y_px);
508 if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
509 double yfrac = minor_y_px_frac / im->ysize;
510 double range = im->maxval - im->minval;
511 im->minval = im->minval - yfrac * range;
512 im->maxval = im->maxval - yfrac * range;
513 ytr(im,DNAN); /* reset precalc */
514 }
515 calc_horizontal_grid(im); /* recalc with changed im->maxval */
516 }
517 }
519 /* reduce data reimplementation by Alex */
521 void
522 reduce_data(
523 enum cf_en cf, /* which consolidation function ?*/
524 unsigned long cur_step, /* step the data currently is in */
525 time_t *start, /* start, end and step as requested ... */
526 time_t *end, /* ... by the application will be ... */
527 unsigned long *step, /* ... adjusted to represent reality */
528 unsigned long *ds_cnt, /* number of data sources in file */
529 rrd_value_t **data) /* two dimensional array containing the data */
530 {
531 int i,reduce_factor = ceil((double)(*step) / (double)cur_step);
532 unsigned long col,dst_row,row_cnt,start_offset,end_offset,skiprows=0;
533 rrd_value_t *srcptr,*dstptr;
535 (*step) = cur_step*reduce_factor; /* set new step size for reduced data */
536 dstptr = *data;
537 srcptr = *data;
538 row_cnt = ((*end)-(*start))/cur_step;
540 #ifdef DEBUG
541 #define DEBUG_REDUCE
542 #endif
543 #ifdef DEBUG_REDUCE
544 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
545 row_cnt,reduce_factor,*start,*end,cur_step);
546 for (col=0;col<row_cnt;col++) {
547 printf("time %10lu: ",*start+(col+1)*cur_step);
548 for (i=0;i<*ds_cnt;i++)
549 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
550 printf("\n");
551 }
552 #endif
554 /* We have to combine [reduce_factor] rows of the source
555 ** into one row for the destination. Doing this we also
556 ** need to take care to combine the correct rows. First
557 ** alter the start and end time so that they are multiples
558 ** of the new step time. We cannot reduce the amount of
559 ** time so we have to move the end towards the future and
560 ** the start towards the past.
561 */
562 end_offset = (*end) % (*step);
563 start_offset = (*start) % (*step);
565 /* If there is a start offset (which cannot be more than
566 ** one destination row), skip the appropriate number of
567 ** source rows and one destination row. The appropriate
568 ** number is what we do know (start_offset/cur_step) of
569 ** the new interval (*step/cur_step aka reduce_factor).
570 */
571 #ifdef DEBUG_REDUCE
572 printf("start_offset: %lu end_offset: %lu\n",start_offset,end_offset);
573 printf("row_cnt before: %lu\n",row_cnt);
574 #endif
575 if (start_offset) {
576 (*start) = (*start)-start_offset;
577 skiprows=reduce_factor-start_offset/cur_step;
578 srcptr+=skiprows* *ds_cnt;
579 for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
580 row_cnt-=skiprows;
581 }
582 #ifdef DEBUG_REDUCE
583 printf("row_cnt between: %lu\n",row_cnt);
584 #endif
586 /* At the end we have some rows that are not going to be
587 ** used, the amount is end_offset/cur_step
588 */
589 if (end_offset) {
590 (*end) = (*end)-end_offset+(*step);
591 skiprows = end_offset/cur_step;
592 row_cnt-=skiprows;
593 }
594 #ifdef DEBUG_REDUCE
595 printf("row_cnt after: %lu\n",row_cnt);
596 #endif
598 /* Sanity check: row_cnt should be multiple of reduce_factor */
599 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
601 if (row_cnt%reduce_factor) {
602 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
603 row_cnt,reduce_factor);
604 printf("BUG in reduce_data()\n");
605 exit(1);
606 }
608 /* Now combine reduce_factor intervals at a time
609 ** into one interval for the destination.
610 */
612 for (dst_row=0;(long int)row_cnt>=reduce_factor;dst_row++) {
613 for (col=0;col<(*ds_cnt);col++) {
614 rrd_value_t newval=DNAN;
615 unsigned long validval=0;
617 for (i=0;i<reduce_factor;i++) {
618 if (isnan(srcptr[i*(*ds_cnt)+col])) {
619 continue;
620 }
621 validval++;
622 if (isnan(newval)) newval = srcptr[i*(*ds_cnt)+col];
623 else {
624 switch (cf) {
625 case CF_HWPREDICT:
626 case CF_DEVSEASONAL:
627 case CF_DEVPREDICT:
628 case CF_SEASONAL:
629 case CF_AVERAGE:
630 newval += srcptr[i*(*ds_cnt)+col];
631 break;
632 case CF_MINIMUM:
633 newval = min (newval,srcptr[i*(*ds_cnt)+col]);
634 break;
635 case CF_FAILURES:
636 /* an interval contains a failure if any subintervals contained a failure */
637 case CF_MAXIMUM:
638 newval = max (newval,srcptr[i*(*ds_cnt)+col]);
639 break;
640 case CF_LAST:
641 newval = srcptr[i*(*ds_cnt)+col];
642 break;
643 }
644 }
645 }
646 if (validval == 0){newval = DNAN;} else{
647 switch (cf) {
648 case CF_HWPREDICT:
649 case CF_DEVSEASONAL:
650 case CF_DEVPREDICT:
651 case CF_SEASONAL:
652 case CF_AVERAGE:
653 newval /= validval;
654 break;
655 case CF_MINIMUM:
656 case CF_FAILURES:
657 case CF_MAXIMUM:
658 case CF_LAST:
659 break;
660 }
661 }
662 *dstptr++=newval;
663 }
664 srcptr+=(*ds_cnt)*reduce_factor;
665 row_cnt-=reduce_factor;
666 }
667 /* If we had to alter the endtime, we didn't have enough
668 ** source rows to fill the last row. Fill it with NaN.
669 */
670 if (end_offset) for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
671 #ifdef DEBUG_REDUCE
672 row_cnt = ((*end)-(*start))/ *step;
673 srcptr = *data;
674 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
675 row_cnt,*start,*end,*step);
676 for (col=0;col<row_cnt;col++) {
677 printf("time %10lu: ",*start+(col+1)*(*step));
678 for (i=0;i<*ds_cnt;i++)
679 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
680 printf("\n");
681 }
682 #endif
683 }
686 /* get the data required for the graphs from the
687 relevant rrds ... */
689 int
690 data_fetch(image_desc_t *im )
691 {
692 int i,ii;
693 int skip;
695 /* pull the data from the rrd files ... */
696 for (i=0;i< (int)im->gdes_c;i++){
697 /* only GF_DEF elements fetch data */
698 if (im->gdes[i].gf != GF_DEF)
699 continue;
701 skip=0;
702 /* do we have it already ?*/
703 for (ii=0;ii<i;ii++) {
704 if (im->gdes[ii].gf != GF_DEF)
705 continue;
706 if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
707 && (im->gdes[i].cf == im->gdes[ii].cf)
708 && (im->gdes[i].cf_reduce == im->gdes[ii].cf_reduce)
709 && (im->gdes[i].start_orig == im->gdes[ii].start_orig)
710 && (im->gdes[i].end_orig == im->gdes[ii].end_orig)
711 && (im->gdes[i].step_orig == im->gdes[ii].step_orig)) {
712 /* OK, the data is already there.
713 ** Just copy the header portion
714 */
715 im->gdes[i].start = im->gdes[ii].start;
716 im->gdes[i].end = im->gdes[ii].end;
717 im->gdes[i].step = im->gdes[ii].step;
718 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
719 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
720 im->gdes[i].data = im->gdes[ii].data;
721 im->gdes[i].data_first = 0;
722 skip=1;
723 }
724 if (skip)
725 break;
726 }
727 if (! skip) {
728 unsigned long ft_step = im->gdes[i].step ; /* ft_step will record what we got from fetch */
730 if((rrd_fetch_fn(im->gdes[i].rrd,
731 im->gdes[i].cf,
732 &im->gdes[i].start,
733 &im->gdes[i].end,
734 &ft_step,
735 &im->gdes[i].ds_cnt,
736 &im->gdes[i].ds_namv,
737 &im->gdes[i].data)) == -1){
738 return -1;
739 }
740 im->gdes[i].data_first = 1;
742 if (ft_step < im->gdes[i].step) {
743 reduce_data(im->gdes[i].cf_reduce,
744 ft_step,
745 &im->gdes[i].start,
746 &im->gdes[i].end,
747 &im->gdes[i].step,
748 &im->gdes[i].ds_cnt,
749 &im->gdes[i].data);
750 } else {
751 im->gdes[i].step = ft_step;
752 }
753 }
755 /* lets see if the required data source is really there */
756 for(ii=0;ii<(int)im->gdes[i].ds_cnt;ii++){
757 if(strcmp(im->gdes[i].ds_namv[ii],im->gdes[i].ds_nam) == 0){
758 im->gdes[i].ds=ii; }
759 }
760 if (im->gdes[i].ds== -1){
761 rrd_set_error("No DS called '%s' in '%s'",
762 im->gdes[i].ds_nam,im->gdes[i].rrd);
763 return -1;
764 }
766 }
767 return 0;
768 }
770 /* evaluate the expressions in the CDEF functions */
772 /*************************************************************
773 * CDEF stuff
774 *************************************************************/
776 long
777 find_var_wrapper(void *arg1, char *key)
778 {
779 return find_var((image_desc_t *) arg1, key);
780 }
782 /* find gdes containing var*/
783 long
784 find_var(image_desc_t *im, char *key){
785 long ii;
786 for(ii=0;ii<im->gdes_c-1;ii++){
787 if((im->gdes[ii].gf == GF_DEF
788 || im->gdes[ii].gf == GF_VDEF
789 || im->gdes[ii].gf == GF_CDEF)
790 && (strcmp(im->gdes[ii].vname,key) == 0)){
791 return ii;
792 }
793 }
794 return -1;
795 }
797 /* find the largest common denominator for all the numbers
798 in the 0 terminated num array */
799 long
800 lcd(long *num){
801 long rest;
802 int i;
803 for (i=0;num[i+1]!=0;i++){
804 do {
805 rest=num[i] % num[i+1];
806 num[i]=num[i+1]; num[i+1]=rest;
807 } while (rest!=0);
808 num[i+1] = num[i];
809 }
810 /* return i==0?num[i]:num[i-1]; */
811 return num[i];
812 }
814 /* run the rpn calculator on all the VDEF and CDEF arguments */
815 int
816 data_calc( image_desc_t *im){
818 int gdi;
819 int dataidx;
820 long *steparray, rpi;
821 int stepcnt;
822 time_t now;
823 rpnstack_t rpnstack;
825 rpnstack_init(&rpnstack);
827 for (gdi=0;gdi<im->gdes_c;gdi++){
828 /* Look for GF_VDEF and GF_CDEF in the same loop,
829 * so CDEFs can use VDEFs and vice versa
830 */
831 switch (im->gdes[gdi].gf) {
832 case GF_XPORT:
833 break;
834 case GF_SHIFT: {
835 graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx];
837 /* remove current shift */
838 vdp->start -= vdp->shift;
839 vdp->end -= vdp->shift;
841 /* vdef */
842 if (im->gdes[gdi].shidx >= 0)
843 vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
844 /* constant */
845 else
846 vdp->shift = im->gdes[gdi].shval;
848 /* normalize shift to multiple of consolidated step */
849 vdp->shift = (vdp->shift / (long)vdp->step) * (long)vdp->step;
851 /* apply shift */
852 vdp->start += vdp->shift;
853 vdp->end += vdp->shift;
854 break;
855 }
856 case GF_VDEF:
857 /* A VDEF has no DS. This also signals other parts
858 * of rrdtool that this is a VDEF value, not a CDEF.
859 */
860 im->gdes[gdi].ds_cnt = 0;
861 if (vdef_calc(im,gdi)) {
862 rrd_set_error("Error processing VDEF '%s'"
863 ,im->gdes[gdi].vname
864 );
865 rpnstack_free(&rpnstack);
866 return -1;
867 }
868 break;
869 case GF_CDEF:
870 im->gdes[gdi].ds_cnt = 1;
871 im->gdes[gdi].ds = 0;
872 im->gdes[gdi].data_first = 1;
873 im->gdes[gdi].start = 0;
874 im->gdes[gdi].end = 0;
875 steparray=NULL;
876 stepcnt = 0;
877 dataidx=-1;
879 /* Find the variables in the expression.
880 * - VDEF variables are substituted by their values
881 * and the opcode is changed into OP_NUMBER.
882 * - CDEF variables are analized for their step size,
883 * the lowest common denominator of all the step
884 * sizes of the data sources involved is calculated
885 * and the resulting number is the step size for the
886 * resulting data source.
887 */
888 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
889 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
890 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER){
891 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
892 if (im->gdes[ptr].ds_cnt == 0) { /* this is a VDEF data source */
893 #if 0
894 printf("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
895 im->gdes[gdi].vname,
896 im->gdes[ptr].vname);
897 printf("DEBUG: value from vdef is %f\n",im->gdes[ptr].vf.val);
898 #endif
899 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
900 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
901 } else { /* normal variables and PREF(variables) */
903 /* add one entry to the array that keeps track of the step sizes of the
904 * data sources going into the CDEF. */
905 if ((steparray =
906 rrd_realloc(steparray,
907 (++stepcnt+1)*sizeof(*steparray)))==NULL){
908 rrd_set_error("realloc steparray");
909 rpnstack_free(&rpnstack);
910 return -1;
911 };
913 steparray[stepcnt-1] = im->gdes[ptr].step;
915 /* adjust start and end of cdef (gdi) so
916 * that it runs from the latest start point
917 * to the earliest endpoint of any of the
918 * rras involved (ptr)
919 */
921 if(im->gdes[gdi].start < im->gdes[ptr].start)
922 im->gdes[gdi].start = im->gdes[ptr].start;
924 if(im->gdes[gdi].end == 0 ||
925 im->gdes[gdi].end > im->gdes[ptr].end)
926 im->gdes[gdi].end = im->gdes[ptr].end;
928 /* store pointer to the first element of
929 * the rra providing data for variable,
930 * further save step size and data source
931 * count of this rra
932 */
933 im->gdes[gdi].rpnp[rpi].data = im->gdes[ptr].data + im->gdes[ptr].ds;
934 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
935 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
937 /* backoff the *.data ptr; this is done so
938 * rpncalc() function doesn't have to treat
939 * the first case differently
940 */
941 } /* if ds_cnt != 0 */
942 } /* if OP_VARIABLE */
943 } /* loop through all rpi */
945 /* move the data pointers to the correct period */
946 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
947 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
948 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER){
949 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
950 long diff = im->gdes[gdi].start - im->gdes[ptr].start;
952 if(diff > 0)
953 im->gdes[gdi].rpnp[rpi].data += (diff / im->gdes[ptr].step) * im->gdes[ptr].ds_cnt;
954 }
955 }
957 if(steparray == NULL){
958 rrd_set_error("rpn expressions without DEF"
959 " or CDEF variables are not supported");
960 rpnstack_free(&rpnstack);
961 return -1;
962 }
963 steparray[stepcnt]=0;
964 /* Now find the resulting step. All steps in all
965 * used RRAs have to be visited
966 */
967 im->gdes[gdi].step = lcd(steparray);
968 free(steparray);
969 if((im->gdes[gdi].data = malloc((
970 (im->gdes[gdi].end-im->gdes[gdi].start)
971 / im->gdes[gdi].step)
972 * sizeof(double)))==NULL){
973 rrd_set_error("malloc im->gdes[gdi].data");
974 rpnstack_free(&rpnstack);
975 return -1;
976 }
978 /* Step through the new cdef results array and
979 * calculate the values
980 */
981 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
982 now<=im->gdes[gdi].end;
983 now += im->gdes[gdi].step)
984 {
985 rpnp_t *rpnp = im -> gdes[gdi].rpnp;
987 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
988 * in this case we are advancing by timesteps;
989 * we use the fact that time_t is a synonym for long
990 */
991 if (rpn_calc(rpnp,&rpnstack,(long) now,
992 im->gdes[gdi].data,++dataidx) == -1) {
993 /* rpn_calc sets the error string */
994 rpnstack_free(&rpnstack);
995 return -1;
996 }
997 } /* enumerate over time steps within a CDEF */
998 break;
999 default:
1000 continue;
1001 }
1002 } /* enumerate over CDEFs */
1003 rpnstack_free(&rpnstack);
1004 return 0;
1005 }
1007 /* massage data so, that we get one value for each x coordinate in the graph */
1008 int
1009 data_proc( image_desc_t *im ){
1010 long i,ii;
1011 double pixstep = (double)(im->end-im->start)
1012 /(double)im->xsize; /* how much time
1013 passes in one pixel */
1014 double paintval;
1015 double minval=DNAN,maxval=DNAN;
1017 unsigned long gr_time;
1019 /* memory for the processed data */
1020 for(i=0;i<im->gdes_c;i++) {
1021 if((im->gdes[i].gf==GF_LINE) ||
1022 (im->gdes[i].gf==GF_AREA) ||
1023 (im->gdes[i].gf==GF_TICK)) {
1024 if((im->gdes[i].p_data = malloc((im->xsize +1)
1025 * sizeof(rrd_value_t)))==NULL){
1026 rrd_set_error("malloc data_proc");
1027 return -1;
1028 }
1029 }
1030 }
1032 for (i=0;i<im->xsize;i++) { /* for each pixel */
1033 long vidx;
1034 gr_time = im->start+pixstep*i; /* time of the current step */
1035 paintval=0.0;
1037 for (ii=0;ii<im->gdes_c;ii++) {
1038 double value;
1039 switch (im->gdes[ii].gf) {
1040 case GF_LINE:
1041 case GF_AREA:
1042 case GF_TICK:
1043 if (!im->gdes[ii].stack)
1044 paintval = 0.0;
1045 value = im->gdes[ii].yrule;
1046 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1047 /* The time of the data doesn't necessarily match
1048 ** the time of the graph. Beware.
1049 */
1050 vidx = im->gdes[ii].vidx;
1051 if (im->gdes[vidx].gf == GF_VDEF) {
1052 value = im->gdes[vidx].vf.val;
1053 } else if (((long int)gr_time >= (long int)im->gdes[vidx].start) &&
1054 ((long int)gr_time <= (long int)im->gdes[vidx].end) ) {
1055 value = im->gdes[vidx].data[
1056 (unsigned long) floor(
1057 (double)(gr_time - im->gdes[vidx].start)
1058 / im->gdes[vidx].step)
1059 * im->gdes[vidx].ds_cnt
1060 + im->gdes[vidx].ds
1061 ];
1062 } else {
1063 value = DNAN;
1064 }
1065 };
1067 if (! isnan(value)) {
1068 paintval += value;
1069 im->gdes[ii].p_data[i] = paintval;
1070 /* GF_TICK: the data values are not
1071 ** relevant for min and max
1072 */
1073 if (finite(paintval) && im->gdes[ii].gf != GF_TICK ) {
1074 if ((isnan(minval) || paintval < minval ) &&
1075 ! (im->logarithmic && paintval <= 0.0))
1076 minval = paintval;
1077 if (isnan(maxval) || paintval > maxval)
1078 maxval = paintval;
1079 }
1080 } else {
1081 im->gdes[ii].p_data[i] = DNAN;
1082 }
1083 break;
1084 case GF_STACK:
1085 rrd_set_error("STACK should already be turned into LINE or AREA here");
1086 return -1;
1087 break;
1088 default:
1089 break;
1090 }
1091 }
1092 }
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 (im->logarithmic) {
1099 if (isnan(minval)) minval = 0.2;
1100 if (isnan(maxval)) maxval = 5.1;
1101 }
1102 else {
1103 if (isnan(minval)) minval = 0.0;
1104 if (isnan(maxval)) maxval = 1.0;
1105 }
1107 /* adjust min and max values */
1108 if (isnan(im->minval)
1109 /* don't adjust low-end with log scale */ /* why not? */
1110 || ((!im->rigid) && im->minval > minval)
1111 ) {
1112 if (im->logarithmic)
1113 im->minval = minval * 0.5;
1114 else
1115 im->minval = minval;
1116 }
1117 if (isnan(im->maxval)
1118 || (!im->rigid && im->maxval < maxval)
1119 ) {
1120 if (im->logarithmic)
1121 im->maxval = maxval * 2.0;
1122 else
1123 im->maxval = maxval;
1124 }
1125 /* make sure min is smaller than max */
1126 if (im->minval > im->maxval) {
1127 im->minval = 0.99 * im->maxval;
1128 }
1130 /* make sure min and max are not equal */
1131 if (im->minval == im->maxval) {
1132 im->maxval *= 1.01;
1133 if (! im->logarithmic) {
1134 im->minval *= 0.99;
1135 }
1136 /* make sure min and max are not both zero */
1137 if (im->maxval == 0.0) {
1138 im->maxval = 1.0;
1139 }
1140 }
1141 return 0;
1142 }
1146 /* identify the point where the first gridline, label ... gets placed */
1148 time_t
1149 find_first_time(
1150 time_t start, /* what is the initial time */
1151 enum tmt_en baseint, /* what is the basic interval */
1152 long basestep /* how many if these do we jump a time */
1153 )
1154 {
1155 struct tm tm;
1156 localtime_r(&start, &tm);
1157 switch(baseint){
1158 case TMT_SECOND:
1159 tm.tm_sec -= tm.tm_sec % basestep; break;
1160 case TMT_MINUTE:
1161 tm.tm_sec=0;
1162 tm.tm_min -= tm.tm_min % basestep;
1163 break;
1164 case TMT_HOUR:
1165 tm.tm_sec=0;
1166 tm.tm_min = 0;
1167 tm.tm_hour -= tm.tm_hour % basestep; break;
1168 case TMT_DAY:
1169 /* we do NOT look at the basestep for this ... */
1170 tm.tm_sec=0;
1171 tm.tm_min = 0;
1172 tm.tm_hour = 0; break;
1173 case TMT_WEEK:
1174 /* we do NOT look at the basestep for this ... */
1175 tm.tm_sec=0;
1176 tm.tm_min = 0;
1177 tm.tm_hour = 0;
1178 tm.tm_mday -= tm.tm_wday -1; /* -1 because we want the monday */
1179 if (tm.tm_wday==0) tm.tm_mday -= 7; /* we want the *previous* monday */
1180 break;
1181 case TMT_MONTH:
1182 tm.tm_sec=0;
1183 tm.tm_min = 0;
1184 tm.tm_hour = 0;
1185 tm.tm_mday = 1;
1186 tm.tm_mon -= tm.tm_mon % basestep; break;
1188 case TMT_YEAR:
1189 tm.tm_sec=0;
1190 tm.tm_min = 0;
1191 tm.tm_hour = 0;
1192 tm.tm_mday = 1;
1193 tm.tm_mon = 0;
1194 tm.tm_year -= (tm.tm_year+1900) % basestep;
1196 }
1197 return mktime(&tm);
1198 }
1199 /* identify the point where the next gridline, label ... gets placed */
1200 time_t
1201 find_next_time(
1202 time_t current, /* what is the initial time */
1203 enum tmt_en baseint, /* what is the basic interval */
1204 long basestep /* how many if these do we jump a time */
1205 )
1206 {
1207 struct tm tm;
1208 time_t madetime;
1209 localtime_r(¤t, &tm);
1210 do {
1211 switch(baseint){
1212 case TMT_SECOND:
1213 tm.tm_sec += basestep; break;
1214 case TMT_MINUTE:
1215 tm.tm_min += basestep; break;
1216 case TMT_HOUR:
1217 tm.tm_hour += basestep; break;
1218 case TMT_DAY:
1219 tm.tm_mday += basestep; break;
1220 case TMT_WEEK:
1221 tm.tm_mday += 7*basestep; break;
1222 case TMT_MONTH:
1223 tm.tm_mon += basestep; break;
1224 case TMT_YEAR:
1225 tm.tm_year += basestep;
1226 }
1227 madetime = mktime(&tm);
1228 } while (madetime == -1); /* this is necessary to skip impssible times
1229 like the daylight saving time skips */
1230 return madetime;
1232 }
1235 /* calculate values required for PRINT and GPRINT functions */
1237 int
1238 print_calc(image_desc_t *im, char ***prdata)
1239 {
1240 long i,ii,validsteps;
1241 double printval;
1242 struct tm tmvdef;
1243 int graphelement = 0;
1244 long vidx;
1245 int max_ii;
1246 double magfact = -1;
1247 char *si_symb = "";
1248 char *percent_s;
1249 int prlines = 1;
1250 /* wow initializing tmvdef is quite a task :-) */
1251 time_t now = time(NULL);
1252 localtime_r(&now,&tmvdef);
1253 if (im->imginfo) prlines++;
1254 for(i=0;i<im->gdes_c;i++){
1255 vidx = im->gdes[i].vidx;
1256 switch(im->gdes[i].gf){
1257 case GF_PRINT:
1258 prlines++;
1259 if(((*prdata) = rrd_realloc((*prdata),prlines*sizeof(char *)))==NULL){
1260 rrd_set_error("realloc prdata");
1261 return 0;
1262 }
1263 case GF_GPRINT:
1264 /* PRINT and GPRINT can now print VDEF generated values.
1265 * There's no need to do any calculations on them as these
1266 * calculations were already made.
1267 */
1268 if (im->gdes[vidx].gf==GF_VDEF) { /* simply use vals */
1269 printval = im->gdes[vidx].vf.val;
1270 localtime_r(&im->gdes[vidx].vf.when,&tmvdef);
1271 } else { /* need to calculate max,min,avg etcetera */
1272 max_ii =((im->gdes[vidx].end
1273 - im->gdes[vidx].start)
1274 / im->gdes[vidx].step
1275 * im->gdes[vidx].ds_cnt);
1276 printval = DNAN;
1277 validsteps = 0;
1278 for( ii=im->gdes[vidx].ds;
1279 ii < max_ii;
1280 ii+=im->gdes[vidx].ds_cnt){
1281 if (! finite(im->gdes[vidx].data[ii]))
1282 continue;
1283 if (isnan(printval)){
1284 printval = im->gdes[vidx].data[ii];
1285 validsteps++;
1286 continue;
1287 }
1289 switch (im->gdes[i].cf){
1290 case CF_HWPREDICT:
1291 case CF_DEVPREDICT:
1292 case CF_DEVSEASONAL:
1293 case CF_SEASONAL:
1294 case CF_AVERAGE:
1295 validsteps++;
1296 printval += im->gdes[vidx].data[ii];
1297 break;
1298 case CF_MINIMUM:
1299 printval = min( printval, im->gdes[vidx].data[ii]);
1300 break;
1301 case CF_FAILURES:
1302 case CF_MAXIMUM:
1303 printval = max( printval, im->gdes[vidx].data[ii]);
1304 break;
1305 case CF_LAST:
1306 printval = im->gdes[vidx].data[ii];
1307 }
1308 }
1309 if (im->gdes[i].cf==CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1310 if (validsteps > 1) {
1311 printval = (printval / validsteps);
1312 }
1313 }
1314 } /* prepare printval */
1316 if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) {
1317 /* Magfact is set to -1 upon entry to print_calc. If it
1318 * is still less than 0, then we need to run auto_scale.
1319 * Otherwise, put the value into the correct units. If
1320 * the value is 0, then do not set the symbol or magnification
1321 * so next the calculation will be performed again. */
1322 if (magfact < 0.0) {
1323 auto_scale(im,&printval,&si_symb,&magfact);
1324 if (printval == 0.0)
1325 magfact = -1.0;
1326 } else {
1327 printval /= magfact;
1328 }
1329 *(++percent_s) = 's';
1330 } else if (strstr(im->gdes[i].format,"%s") != NULL) {
1331 auto_scale(im,&printval,&si_symb,&magfact);
1332 }
1334 if (im->gdes[i].gf == GF_PRINT){
1335 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1336 (*prdata)[prlines-1] = NULL;
1337 if (im->gdes[i].strftm){
1338 strftime((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,&tmvdef);
1339 } else {
1340 if (bad_format(im->gdes[i].format)) {
1341 rrd_set_error("bad format for PRINT in '%s'", im->gdes[i].format);
1342 return -1;
1343 }
1345 #ifdef HAVE_SNPRINTF
1346 snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
1347 #else
1348 sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
1349 #endif
1350 }
1351 } else {
1352 /* GF_GPRINT */
1354 if (im->gdes[i].strftm){
1355 strftime(im->gdes[i].legend,FMT_LEG_LEN,im->gdes[i].format,&tmvdef);
1356 } else {
1357 if (bad_format(im->gdes[i].format)) {
1358 rrd_set_error("bad format for GPRINT in '%s'", im->gdes[i].format);
1359 return -1;
1360 }
1361 #ifdef HAVE_SNPRINTF
1362 snprintf(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
1363 #else
1364 sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
1365 #endif
1366 }
1367 graphelement = 1;
1368 }
1369 break;
1370 case GF_LINE:
1371 case GF_AREA:
1372 case GF_TICK:
1373 graphelement = 1;
1374 break;
1375 case GF_HRULE:
1376 if(isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1377 im->gdes[i].yrule=im->gdes[vidx].vf.val;
1378 };
1379 graphelement = 1;
1380 break;
1381 case GF_VRULE:
1382 if(im->gdes[i].xrule == 0) { /* again ... the legend printer needs it*/
1383 im->gdes[i].xrule = im->gdes[vidx].vf.when;
1384 };
1385 graphelement = 1;
1386 break;
1387 case GF_COMMENT:
1388 case GF_DEF:
1389 case GF_CDEF:
1390 case GF_VDEF:
1391 #ifdef WITH_PIECHART
1392 case GF_PART:
1393 #endif
1394 case GF_SHIFT:
1395 case GF_XPORT:
1396 break;
1397 case GF_STACK:
1398 rrd_set_error("STACK should already be turned into LINE or AREA here");
1399 return -1;
1400 break;
1401 }
1402 }
1403 return graphelement;
1404 }
1407 /* place legends with color spots */
1408 int
1409 leg_place(image_desc_t *im)
1410 {
1411 /* graph labels */
1412 int interleg = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1413 int border = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1414 int fill=0, fill_last;
1415 int leg_c = 0;
1416 int leg_x = border, leg_y = im->yimg;
1417 int leg_y_prev = im->yimg;
1418 int leg_cc;
1419 int glue = 0;
1420 int i,ii, mark = 0;
1421 char prt_fctn; /*special printfunctions */
1422 int *legspace;
1424 if( !(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH) ) {
1425 if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1426 rrd_set_error("malloc for legspace");
1427 return -1;
1428 }
1430 for(i=0;i<im->gdes_c;i++){
1431 fill_last = fill;
1433 /* hid legends for rules which are not displayed */
1435 if(!(im->extra_flags & FORCE_RULES_LEGEND)) {
1436 if (im->gdes[i].gf == GF_HRULE &&
1437 (im->gdes[i].yrule < im->minval || im->gdes[i].yrule > im->maxval))
1438 im->gdes[i].legend[0] = '\0';
1440 if (im->gdes[i].gf == GF_VRULE &&
1441 (im->gdes[i].xrule < im->start || im->gdes[i].xrule > im->end))
1442 im->gdes[i].legend[0] = '\0';
1443 }
1445 leg_cc = strlen(im->gdes[i].legend);
1447 /* is there a controle code ant the end of the legend string ? */
1448 /* and it is not a tab \\t */
1449 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\' && im->gdes[i].legend[leg_cc-1] != 't') {
1450 prt_fctn = im->gdes[i].legend[leg_cc-1];
1451 leg_cc -= 2;
1452 im->gdes[i].legend[leg_cc] = '\0';
1453 } else {
1454 prt_fctn = '\0';
1455 }
1456 /* only valid control codes */
1457 if (prt_fctn != 'l' &&
1458 prt_fctn != 'n' && /* a synonym for l */
1459 prt_fctn != 'r' &&
1460 prt_fctn != 'j' &&
1461 prt_fctn != 'c' &&
1462 prt_fctn != 's' &&
1463 prt_fctn != 't' &&
1464 prt_fctn != '\0' &&
1465 prt_fctn != 'g' ) {
1466 free(legspace);
1467 rrd_set_error("Unknown control code at the end of '%s\\%c'",im->gdes[i].legend,prt_fctn);
1468 return -1;
1470 }
1472 /* remove exess space */
1473 if ( prt_fctn == 'n' ){
1474 prt_fctn='l';
1475 }
1477 while (prt_fctn=='g' &&
1478 leg_cc > 0 &&
1479 im->gdes[i].legend[leg_cc-1]==' '){
1480 leg_cc--;
1481 im->gdes[i].legend[leg_cc]='\0';
1482 }
1483 if (leg_cc != 0 ){
1484 legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1486 if (fill > 0){
1487 /* no interleg space if string ends in \g */
1488 fill += legspace[i];
1489 }
1490 fill += gfx_get_text_width(im->canvas, fill+border,
1491 im->text_prop[TEXT_PROP_LEGEND].font,
1492 im->text_prop[TEXT_PROP_LEGEND].size,
1493 im->tabwidth,
1494 im->gdes[i].legend, 0);
1495 leg_c++;
1496 } else {
1497 legspace[i]=0;
1498 }
1499 /* who said there was a special tag ... ?*/
1500 if (prt_fctn=='g') {
1501 prt_fctn = '\0';
1502 }
1503 if (prt_fctn == '\0') {
1504 if (i == im->gdes_c -1 ) prt_fctn ='l';
1506 /* is it time to place the legends ? */
1507 if (fill > im->ximg - 2*border){
1508 if (leg_c > 1) {
1509 /* go back one */
1510 i--;
1511 fill = fill_last;
1512 leg_c--;
1513 prt_fctn = 'j';
1514 } else {
1515 prt_fctn = 'l';
1516 }
1518 }
1519 }
1522 if (prt_fctn != '\0'){
1523 leg_x = border;
1524 if (leg_c >= 2 && prt_fctn == 'j') {
1525 glue = (im->ximg - fill - 2* border) / (leg_c-1);
1526 } else {
1527 glue = 0;
1528 }
1529 if (prt_fctn =='c') leg_x = (im->ximg - fill) / 2.0;
1530 if (prt_fctn =='r') leg_x = im->ximg - fill - border;
1532 for(ii=mark;ii<=i;ii++){
1533 if(im->gdes[ii].legend[0]=='\0')
1534 continue; /* skip empty legends */
1535 im->gdes[ii].leg_x = leg_x;
1536 im->gdes[ii].leg_y = leg_y;
1537 leg_x +=
1538 gfx_get_text_width(im->canvas, leg_x,
1539 im->text_prop[TEXT_PROP_LEGEND].font,
1540 im->text_prop[TEXT_PROP_LEGEND].size,
1541 im->tabwidth,
1542 im->gdes[ii].legend, 0)
1543 + legspace[ii]
1544 + glue;
1545 }
1546 leg_y_prev = leg_y;
1547 /* only add y space if there was text on the line */
1548 if (leg_x > border || prt_fctn == 's')
1549 leg_y += im->text_prop[TEXT_PROP_LEGEND].size*1.8;
1550 if (prt_fctn == 's')
1551 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1552 fill = 0;
1553 leg_c = 0;
1554 mark = ii;
1555 }
1556 }
1557 im->yimg = leg_y_prev;
1558 /* if we did place some legends we have to add vertical space */
1559 if (leg_y != im->yimg){
1560 im->yimg += im->text_prop[TEXT_PROP_LEGEND].size*1.8;
1561 }
1562 free(legspace);
1563 }
1564 return 0;
1565 }
1567 /* create a grid on the graph. it determines what to do
1568 from the values of xsize, start and end */
1570 /* the xaxis labels are determined from the number of seconds per pixel
1571 in the requested graph */
1575 int
1576 calc_horizontal_grid(image_desc_t *im)
1577 {
1578 double range;
1579 double scaledrange;
1580 int pixel,i;
1581 int gridind=0;
1582 int decimals, fractionals;
1584 im->ygrid_scale.labfact=2;
1585 range = im->maxval - im->minval;
1586 scaledrange = range / im->magfact;
1588 /* does the scale of this graph make it impossible to put lines
1589 on it? If so, give up. */
1590 if (isnan(scaledrange)) {
1591 return 0;
1592 }
1594 /* find grid spaceing */
1595 pixel=1;
1596 if(isnan(im->ygridstep)){
1597 if(im->extra_flags & ALTYGRID) {
1598 /* find the value with max number of digits. Get number of digits */
1599 decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))*im->viewfactor/im->magfact));
1600 if(decimals <= 0) /* everything is small. make place for zero */
1601 decimals = 1;
1603 im->ygrid_scale.gridstep = pow((double)10, floor(log10(range*im->viewfactor/im->magfact)))/im->viewfactor*im->magfact;
1605 if(im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1606 im->ygrid_scale.gridstep = 0.1;
1607 /* should have at least 5 lines but no more then 15 */
1608 if(range/im->ygrid_scale.gridstep < 5)
1609 im->ygrid_scale.gridstep /= 10;
1610 if(range/im->ygrid_scale.gridstep > 15)
1611 im->ygrid_scale.gridstep *= 10;
1612 if(range/im->ygrid_scale.gridstep > 5) {
1613 im->ygrid_scale.labfact = 1;
1614 if(range/im->ygrid_scale.gridstep > 8)
1615 im->ygrid_scale.labfact = 2;
1616 }
1617 else {
1618 im->ygrid_scale.gridstep /= 5;
1619 im->ygrid_scale.labfact = 5;
1620 }
1621 fractionals = floor(log10(im->ygrid_scale.gridstep*(double)im->ygrid_scale.labfact*im->viewfactor/im->magfact));
1622 if(fractionals < 0) { /* small amplitude. */
1623 int len = decimals - fractionals + 1;
1624 if (im->unitslength < len+2) im->unitslength = len+2;
1625 sprintf(im->ygrid_scale.labfmt, "%%%d.%df%s", len, -fractionals,(im->symbol != ' ' ? " %c" : ""));
1626 } else {
1627 int len = decimals + 1;
1628 if (im->unitslength < len+2) im->unitslength = len+2;
1629 sprintf(im->ygrid_scale.labfmt, "%%%d.0f%s", len, ( im->symbol != ' ' ? " %c" : "" ));
1630 }
1631 }
1632 else {
1633 for(i=0;ylab[i].grid > 0;i++){
1634 pixel = im->ysize / (scaledrange / ylab[i].grid);
1635 gridind = i;
1636 if (pixel > 7)
1637 break;
1638 }
1640 for(i=0; i<4;i++) {
1641 if (pixel * ylab[gridind].lfac[i] >= 2.5 * im->text_prop[TEXT_PROP_AXIS].size) {
1642 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1643 break;
1644 }
1645 }
1647 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1648 }
1649 } else {
1650 im->ygrid_scale.gridstep = im->ygridstep;
1651 im->ygrid_scale.labfact = im->ylabfact;
1652 }
1653 return 1;
1654 }
1656 int draw_horizontal_grid(image_desc_t *im)
1657 {
1658 int i;
1659 double scaledstep;
1660 char graph_label[100];
1661 int nlabels=0;
1662 double X0=im->xorigin;
1663 double X1=im->xorigin+im->xsize;
1665 int sgrid = (int)( im->minval / im->ygrid_scale.gridstep - 1);
1666 int egrid = (int)( im->maxval / im->ygrid_scale.gridstep + 1);
1667 double MaxY;
1668 scaledstep = im->ygrid_scale.gridstep/(double)im->magfact*(double)im->viewfactor;
1669 MaxY = scaledstep*(double)egrid;
1670 for (i = sgrid; i <= egrid; i++){
1671 double Y0=ytr(im,im->ygrid_scale.gridstep*i);
1672 double YN=ytr(im,im->ygrid_scale.gridstep*(i+1));
1673 if ( Y0 >= im->yorigin-im->ysize
1674 && Y0 <= im->yorigin){
1675 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
1676 with the chosen settings. Add a label if required by settings, or if
1677 there is only one label so far and the next grid line is out of bounds. */
1678 if(i % im->ygrid_scale.labfact == 0 || ( nlabels==1 && (YN < im->yorigin-im->ysize || YN > im->yorigin) )){
1679 if (im->symbol == ' ') {
1680 if(im->extra_flags & ALTYGRID) {
1681 sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*(double)i);
1682 } else {
1683 if(MaxY < 10) {
1684 sprintf(graph_label,"%4.1f",scaledstep*(double)i);
1685 } else {
1686 sprintf(graph_label,"%4.0f",scaledstep*(double)i);
1687 }
1688 }
1689 }else {
1690 char sisym = ( i == 0 ? ' ' : im->symbol);
1691 if(im->extra_flags & ALTYGRID) {
1692 sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*(double)i,sisym);
1693 } else {
1694 if(MaxY < 10){
1695 sprintf(graph_label,"%4.1f %c",scaledstep*(double)i, sisym);
1696 } else {
1697 sprintf(graph_label,"%4.0f %c",scaledstep*(double)i, sisym);
1698 }
1699 }
1700 }
1701 nlabels++;
1703 gfx_new_text ( im->canvas,
1704 X0-im->text_prop[TEXT_PROP_AXIS].size, Y0,
1705 im->graph_col[GRC_FONT],
1706 im->text_prop[TEXT_PROP_AXIS].font,
1707 im->text_prop[TEXT_PROP_AXIS].size,
1708 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1709 graph_label );
1710 gfx_new_dashed_line ( im->canvas,
1711 X0-2,Y0,
1712 X1+2,Y0,
1713 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1714 im->grid_dash_on, im->grid_dash_off);
1716 } else if (!(im->extra_flags & NOMINOR)) {
1717 gfx_new_dashed_line ( im->canvas,
1718 X0-1,Y0,
1719 X1+1,Y0,
1720 GRIDWIDTH, im->graph_col[GRC_GRID],
1721 im->grid_dash_on, im->grid_dash_off);
1723 }
1724 }
1725 }
1726 return 1;
1727 }
1729 /* this is frexp for base 10 */
1730 double frexp10(double, double *);
1731 double frexp10(double x, double *e) {
1732 double mnt;
1733 int iexp;
1735 iexp = floor(log(fabs(x)) / log(10));
1736 mnt = x / pow(10.0, iexp);
1737 if(mnt >= 10.0) {
1738 iexp++;
1739 mnt = x / pow(10.0, iexp);
1740 }
1741 *e = iexp;
1742 return mnt;
1743 }
1745 static int AlmostEqual2sComplement (float A, float B, int maxUlps)
1746 {
1748 int aInt = *(int*)&A;
1749 int bInt = *(int*)&B;
1750 int intDiff;
1751 /* Make sure maxUlps is non-negative and small enough that the
1752 default NAN won't compare as equal to anything. */
1754 /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
1756 /* Make aInt lexicographically ordered as a twos-complement int */
1758 if (aInt < 0)
1759 aInt = 0x80000000l - aInt;
1761 /* Make bInt lexicographically ordered as a twos-complement int */
1763 if (bInt < 0)
1764 bInt = 0x80000000l - bInt;
1766 intDiff = abs(aInt - bInt);
1768 if (intDiff <= maxUlps)
1769 return 1;
1771 return 0;
1772 }
1774 /* logaritmic horizontal grid */
1775 int
1776 horizontal_log_grid(image_desc_t *im)
1777 {
1778 double yloglab[][10] = {
1779 {1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
1780 {1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
1781 {1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0},
1782 {1.0, 2.0, 4.0, 6.0, 8.0, 10., 0.0, 0.0, 0.0, 0.0},
1783 {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.},
1784 {0,0,0,0,0, 0,0,0,0,0} /* last line */ };
1786 int i, j, val_exp, min_exp;
1787 double nex; /* number of decades in data */
1788 double logscale; /* scale in logarithmic space */
1789 int exfrac = 1; /* decade spacing */
1790 int mid = -1; /* row in yloglab for major grid */
1791 double mspac; /* smallest major grid spacing (pixels) */
1792 int flab; /* first value in yloglab to use */
1793 double value, tmp, pre_value;
1794 double X0,X1,Y0;
1795 char graph_label[100];
1797 nex = log10(im->maxval / im->minval);
1798 logscale = im->ysize / nex;
1800 /* major spacing for data with high dynamic range */
1801 while(logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
1802 if(exfrac == 1) exfrac = 3;
1803 else exfrac += 3;
1804 }
1806 /* major spacing for less dynamic data */
1807 do {
1808 /* search best row in yloglab */
1809 mid++;
1810 for(i = 0; yloglab[mid][i + 1] < 10.0; i++);
1811 mspac = logscale * log10(10.0 / yloglab[mid][i]);
1812 } while(mspac > 2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0);
1813 if(mid) mid--;
1815 /* find first value in yloglab */
1816 for(flab = 0; yloglab[mid][flab] < 10 && frexp10(im->minval, &tmp) > yloglab[mid][flab] ; flab++);
1817 if(yloglab[mid][flab] == 10.0) {
1818 tmp += 1.0;
1819 flab = 0;
1820 }
1821 val_exp = tmp;
1822 if(val_exp % exfrac) val_exp += abs(-val_exp % exfrac);
1824 X0=im->xorigin;
1825 X1=im->xorigin+im->xsize;
1827 /* draw grid */
1828 pre_value = DNAN;
1829 while(1) {
1831 value = yloglab[mid][flab] * pow(10.0, val_exp);
1832 if ( AlmostEqual2sComplement(value,pre_value,4) ) break; /* it seems we are not converging */
1834 pre_value = value;
1836 Y0 = ytr(im, value);
1837 if(Y0 <= im->yorigin - im->ysize) break;
1839 /* major grid line */
1840 gfx_new_dashed_line ( im->canvas,
1841 X0-2,Y0,
1842 X1+2,Y0,
1843 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1844 im->grid_dash_on, im->grid_dash_off);
1846 /* label */
1847 if (im->extra_flags & FORCE_UNITS_SI) {
1848 int scale;
1849 double pvalue;
1850 char symbol;
1852 scale = floor(val_exp / 3.0);
1853 if( value >= 1.0 ) pvalue = pow(10.0, val_exp % 3);
1854 else pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
1855 pvalue *= yloglab[mid][flab];
1857 if ( ((scale+si_symbcenter) < (int)sizeof(si_symbol)) &&
1858 ((scale+si_symbcenter) >= 0) )
1859 symbol = si_symbol[scale+si_symbcenter];
1860 else
1861 symbol = '?';
1863 sprintf(graph_label,"%3.0f %c", pvalue, symbol);
1864 } else
1865 sprintf(graph_label,"%3.0e", value);
1866 gfx_new_text ( im->canvas,
1867 X0-im->text_prop[TEXT_PROP_AXIS].size, Y0,
1868 im->graph_col[GRC_FONT],
1869 im->text_prop[TEXT_PROP_AXIS].font,
1870 im->text_prop[TEXT_PROP_AXIS].size,
1871 im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1872 graph_label );
1874 /* minor grid */
1875 if(mid < 4 && exfrac == 1) {
1876 /* find first and last minor line behind current major line
1877 * i is the first line and j tha last */
1878 if(flab == 0) {
1879 min_exp = val_exp - 1;
1880 for(i = 1; yloglab[mid][i] < 10.0; i++);
1881 i = yloglab[mid][i - 1] + 1;
1882 j = 10;
1883 }
1884 else {
1885 min_exp = val_exp;
1886 i = yloglab[mid][flab - 1] + 1;
1887 j = yloglab[mid][flab];
1888 }
1890 /* draw minor lines below current major line */
1891 for(; i < j; i++) {
1893 value = i * pow(10.0, min_exp);
1894 if(value < im->minval) continue;
1896 Y0 = ytr(im, value);
1897 if(Y0 <= im->yorigin - im->ysize) break;
1899 /* draw lines */
1900 gfx_new_dashed_line ( im->canvas,
1901 X0-1,Y0,
1902 X1+1,Y0,
1903 GRIDWIDTH, im->graph_col[GRC_GRID],
1904 im->grid_dash_on, im->grid_dash_off);
1905 }
1906 }
1907 else if(exfrac > 1) {
1908 for(i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
1909 value = pow(10.0, i);
1910 if(value < im->minval) continue;
1912 Y0 = ytr(im, value);
1913 if(Y0 <= im->yorigin - im->ysize) break;
1915 /* draw lines */
1916 gfx_new_dashed_line ( im->canvas,
1917 X0-1,Y0,
1918 X1+1,Y0,
1919 GRIDWIDTH, im->graph_col[GRC_GRID],
1920 im->grid_dash_on, im->grid_dash_off);
1921 }
1922 }
1924 /* next decade */
1925 if(yloglab[mid][++flab] == 10.0) {
1926 flab = 0;
1927 val_exp += exfrac;
1928 }
1929 }
1931 /* draw minor lines after highest major line */
1932 if(mid < 4 && exfrac == 1) {
1933 /* find first and last minor line below current major line
1934 * i is the first line and j tha last */
1935 if(flab == 0) {
1936 min_exp = val_exp - 1;
1937 for(i = 1; yloglab[mid][i] < 10.0; i++);
1938 i = yloglab[mid][i - 1] + 1;
1939 j = 10;
1940 }
1941 else {
1942 min_exp = val_exp;
1943 i = yloglab[mid][flab - 1] + 1;
1944 j = yloglab[mid][flab];
1945 }
1947 /* draw minor lines below current major line */
1948 for(; i < j; i++) {
1950 value = i * pow(10.0, min_exp);
1951 if(value < im->minval) continue;
1953 Y0 = ytr(im, value);
1954 if(Y0 <= im->yorigin - im->ysize) break;
1956 /* draw lines */
1957 gfx_new_dashed_line ( im->canvas,
1958 X0-1,Y0,
1959 X1+1,Y0,
1960 GRIDWIDTH, im->graph_col[GRC_GRID],
1961 im->grid_dash_on, im->grid_dash_off);
1962 }
1963 }
1964 /* fancy minor gridlines */
1965 else if(exfrac > 1) {
1966 for(i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
1967 value = pow(10.0, i);
1968 if(value < im->minval) continue;
1970 Y0 = ytr(im, value);
1971 if(Y0 <= im->yorigin - im->ysize) break;
1973 /* draw lines */
1974 gfx_new_dashed_line ( im->canvas,
1975 X0-1,Y0,
1976 X1+1,Y0,
1977 GRIDWIDTH, im->graph_col[GRC_GRID],
1978 im->grid_dash_on, im->grid_dash_off);
1979 }
1980 }
1982 return 1;
1983 }
1986 void
1987 vertical_grid(
1988 image_desc_t *im )
1989 {
1990 int xlab_sel; /* which sort of label and grid ? */
1991 time_t ti, tilab, timajor;
1992 long factor;
1993 char graph_label[100];
1994 double X0,Y0,Y1; /* points for filled graph and more*/
1995 struct tm tm;
1997 /* the type of time grid is determined by finding
1998 the number of seconds per pixel in the graph */
2001 if(im->xlab_user.minsec == -1){
2002 factor=(im->end - im->start)/im->xsize;
2003 xlab_sel=0;
2004 while ( xlab[xlab_sel+1].minsec != -1
2005 && xlab[xlab_sel+1].minsec <= factor) { xlab_sel++; } /* pick the last one */
2006 while ( xlab[xlab_sel-1].minsec == xlab[xlab_sel].minsec
2007 && xlab[xlab_sel].length > (im->end - im->start)) { xlab_sel--; } /* go back to the smallest size */
2008 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2009 im->xlab_user.gridst = xlab[xlab_sel].gridst;
2010 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2011 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2012 im->xlab_user.labtm = xlab[xlab_sel].labtm;
2013 im->xlab_user.labst = xlab[xlab_sel].labst;
2014 im->xlab_user.precis = xlab[xlab_sel].precis;
2015 im->xlab_user.stst = xlab[xlab_sel].stst;
2016 }
2018 /* y coords are the same for every line ... */
2019 Y0 = im->yorigin;
2020 Y1 = im->yorigin-im->ysize;
2023 /* paint the minor grid */
2024 if (!(im->extra_flags & NOMINOR))
2025 {
2026 for(ti = find_first_time(im->start,
2027 im->xlab_user.gridtm,
2028 im->xlab_user.gridst),
2029 timajor = find_first_time(im->start,
2030 im->xlab_user.mgridtm,
2031 im->xlab_user.mgridst);
2032 ti < im->end;
2033 ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
2034 ){
2035 /* are we inside the graph ? */
2036 if (ti < im->start || ti > im->end) continue;
2037 while (timajor < ti) {
2038 timajor = find_next_time(timajor,
2039 im->xlab_user.mgridtm, im->xlab_user.mgridst);
2040 }
2041 if (ti == timajor) continue; /* skip as falls on major grid line */
2042 X0 = xtr(im,ti);
2043 gfx_new_dashed_line(im->canvas,X0,Y0+1, X0,Y1-1,GRIDWIDTH,
2044 im->graph_col[GRC_GRID],
2045 im->grid_dash_on, im->grid_dash_off);
2047 }
2048 }
2050 /* paint the major grid */
2051 for(ti = find_first_time(im->start,
2052 im->xlab_user.mgridtm,
2053 im->xlab_user.mgridst);
2054 ti < im->end;
2055 ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
2056 ){
2057 /* are we inside the graph ? */
2058 if (ti < im->start || ti > im->end) continue;
2059 X0 = xtr(im,ti);
2060 gfx_new_dashed_line(im->canvas,X0,Y0+3, X0,Y1-2,MGRIDWIDTH,
2061 im->graph_col[GRC_MGRID],
2062 im->grid_dash_on, im->grid_dash_off);
2064 }
2065 /* paint the labels below the graph */
2066 for(ti = find_first_time(im->start - im->xlab_user.precis/2,
2067 im->xlab_user.labtm,
2068 im->xlab_user.labst);
2069 ti <= im->end - im->xlab_user.precis/2;
2070 ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
2071 ){
2072 tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
2073 /* are we inside the graph ? */
2074 if (tilab < im->start || tilab > im->end) continue;
2076 #if HAVE_STRFTIME
2077 localtime_r(&tilab, &tm);
2078 strftime(graph_label,99,im->xlab_user.stst, &tm);
2079 #else
2080 # error "your libc has no strftime I guess we'll abort the exercise here."
2081 #endif
2082 gfx_new_text ( im->canvas,
2083 xtr(im,tilab), Y0+im->text_prop[TEXT_PROP_AXIS].size*1.4+5,
2084 im->graph_col[GRC_FONT],
2085 im->text_prop[TEXT_PROP_AXIS].font,
2086 im->text_prop[TEXT_PROP_AXIS].size,
2087 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_BOTTOM,
2088 graph_label );
2090 }
2092 }
2095 void
2096 axis_paint(
2097 image_desc_t *im
2098 )
2099 {
2100 /* draw x and y axis */
2101 /* gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2102 im->xorigin+im->xsize,im->yorigin-im->ysize,
2103 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2105 gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2106 im->xorigin+im->xsize,im->yorigin-im->ysize,
2107 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2109 gfx_new_line ( im->canvas, im->xorigin-4,im->yorigin,
2110 im->xorigin+im->xsize+4,im->yorigin,
2111 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2113 gfx_new_line ( im->canvas, im->xorigin,im->yorigin+4,
2114 im->xorigin,im->yorigin-im->ysize-4,
2115 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2118 /* arrow for X and Y axis direction */
2119 gfx_new_area ( im->canvas,
2120 im->xorigin+im->xsize+2, im->yorigin-2,
2121 im->xorigin+im->xsize+2, im->yorigin+3,
2122 im->xorigin+im->xsize+7, im->yorigin+0.5, /* LINEOFFSET */
2123 im->graph_col[GRC_ARROW]);
2125 gfx_new_area ( im->canvas,
2126 im->xorigin-2, im->yorigin-im->ysize-2,
2127 im->xorigin+3, im->yorigin-im->ysize-2,
2128 im->xorigin+0.5, im->yorigin-im->ysize-7, /* LINEOFFSET */
2129 im->graph_col[GRC_ARROW]);
2131 }
2133 void
2134 grid_paint(image_desc_t *im)
2135 {
2136 long i;
2137 int res=0;
2138 double X0,Y0; /* points for filled graph and more*/
2139 gfx_node_t *node;
2141 /* draw 3d border */
2142 node = gfx_new_area (im->canvas, 0,im->yimg,
2143 2,im->yimg-2,
2144 2,2,im->graph_col[GRC_SHADEA]);
2145 gfx_add_point( node , im->ximg - 2, 2 );
2146 gfx_add_point( node , im->ximg, 0 );
2147 gfx_add_point( node , 0,0 );
2148 /* gfx_add_point( node , 0,im->yimg ); */
2150 node = gfx_new_area (im->canvas, 2,im->yimg-2,
2151 im->ximg-2,im->yimg-2,
2152 im->ximg - 2, 2,
2153 im->graph_col[GRC_SHADEB]);
2154 gfx_add_point( node , im->ximg,0);
2155 gfx_add_point( node , im->ximg,im->yimg);
2156 gfx_add_point( node , 0,im->yimg);
2157 /* gfx_add_point( node , 0,im->yimg ); */
2160 if (im->draw_x_grid == 1 )
2161 vertical_grid(im);
2163 if (im->draw_y_grid == 1){
2164 if(im->logarithmic){
2165 res = horizontal_log_grid(im);
2166 } else {
2167 res = draw_horizontal_grid(im);
2168 }
2170 /* dont draw horizontal grid if there is no min and max val */
2171 if (! res ) {
2172 char *nodata = "No Data found";
2173 gfx_new_text(im->canvas,im->ximg/2, (2*im->yorigin-im->ysize) / 2,
2174 im->graph_col[GRC_FONT],
2175 im->text_prop[TEXT_PROP_AXIS].font,
2176 im->text_prop[TEXT_PROP_AXIS].size,
2177 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
2178 nodata );
2179 }
2180 }
2182 /* yaxis unit description */
2183 gfx_new_text( im->canvas,
2184 10, (im->yorigin - im->ysize/2),
2185 im->graph_col[GRC_FONT],
2186 im->text_prop[TEXT_PROP_UNIT].font,
2187 im->text_prop[TEXT_PROP_UNIT].size, im->tabwidth,
2188 RRDGRAPH_YLEGEND_ANGLE,
2189 GFX_H_LEFT, GFX_V_CENTER,
2190 im->ylegend);
2192 /* graph title */
2193 gfx_new_text( im->canvas,
2194 im->ximg/2, im->text_prop[TEXT_PROP_TITLE].size*1.3+4,
2195 im->graph_col[GRC_FONT],
2196 im->text_prop[TEXT_PROP_TITLE].font,
2197 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
2198 GFX_H_CENTER, GFX_V_CENTER,
2199 im->title);
2200 /* rrdtool 'logo' */
2201 gfx_new_text( im->canvas,
2202 im->ximg-7, 7,
2203 ( im->graph_col[GRC_FONT] & 0xffffff00 ) | 0x00000044,
2204 im->text_prop[TEXT_PROP_AXIS].font,
2205 5.5, im->tabwidth, 270,
2206 GFX_H_RIGHT, GFX_V_TOP,
2207 "RRDTOOL / TOBI OETIKER");
2209 /* graph watermark */
2210 if(im->watermark[0] != '\0') {
2211 gfx_new_text( im->canvas,
2212 im->ximg/2, im->yimg-6,
2213 ( im->graph_col[GRC_FONT] & 0xffffff00 ) | 0x00000044,
2214 im->text_prop[TEXT_PROP_AXIS].font,
2215 5.5, im->tabwidth, 0,
2216 GFX_H_CENTER, GFX_V_BOTTOM,
2217 im->watermark);
2218 }
2220 /* graph labels */
2221 if( !(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH) ) {
2222 for(i=0;i<im->gdes_c;i++){
2223 if(im->gdes[i].legend[0] =='\0')
2224 continue;
2226 /* im->gdes[i].leg_y is the bottom of the legend */
2227 X0 = im->gdes[i].leg_x;
2228 Y0 = im->gdes[i].leg_y;
2229 gfx_new_text ( im->canvas, X0, Y0,
2230 im->graph_col[GRC_FONT],
2231 im->text_prop[TEXT_PROP_LEGEND].font,
2232 im->text_prop[TEXT_PROP_LEGEND].size,
2233 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
2234 im->gdes[i].legend );
2235 /* The legend for GRAPH items starts with "M " to have
2236 enough space for the box */
2237 if ( im->gdes[i].gf != GF_PRINT &&
2238 im->gdes[i].gf != GF_GPRINT &&
2239 im->gdes[i].gf != GF_COMMENT) {
2240 int boxH, boxV;
2242 boxH = gfx_get_text_width(im->canvas, 0,
2243 im->text_prop[TEXT_PROP_LEGEND].font,
2244 im->text_prop[TEXT_PROP_LEGEND].size,
2245 im->tabwidth,"o", 0) * 1.2;
2246 boxV = boxH*1.1;
2248 /* make sure transparent colors show up the same way as in the graph */
2249 node = gfx_new_area(im->canvas,
2250 X0,Y0-boxV,
2251 X0,Y0,
2252 X0+boxH,Y0,
2253 im->graph_col[GRC_BACK]);
2254 gfx_add_point ( node, X0+boxH, Y0-boxV );
2256 node = gfx_new_area(im->canvas,
2257 X0,Y0-boxV,
2258 X0,Y0,
2259 X0+boxH,Y0,
2260 im->gdes[i].col);
2261 gfx_add_point ( node, X0+boxH, Y0-boxV );
2262 node = gfx_new_line(im->canvas,
2263 X0,Y0-boxV,
2264 X0,Y0,
2265 1.0,im->graph_col[GRC_FRAME]);
2266 gfx_add_point(node,X0+boxH,Y0);
2267 gfx_add_point(node,X0+boxH,Y0-boxV);
2268 gfx_close_path(node);
2269 }
2270 }
2271 }
2272 }
2275 /*****************************************************
2276 * lazy check make sure we rely need to create this graph
2277 *****************************************************/
2279 int lazy_check(image_desc_t *im){
2280 FILE *fd = NULL;
2281 int size = 1;
2282 struct stat imgstat;
2284 if (im->lazy == 0) return 0; /* no lazy option */
2285 if (stat(im->graphfile,&imgstat) != 0)
2286 return 0; /* can't stat */
2287 /* one pixel in the existing graph is more then what we would
2288 change here ... */
2289 if (time(NULL) - imgstat.st_mtime >
2290 (im->end - im->start) / im->xsize)
2291 return 0;
2292 if ((fd = fopen(im->graphfile,"rb")) == NULL)
2293 return 0; /* the file does not exist */
2294 switch (im->canvas->imgformat) {
2295 case IF_PNG:
2296 size = PngSize(fd,&(im->ximg),&(im->yimg));
2297 break;
2298 default:
2299 size = 1;
2300 }
2301 fclose(fd);
2302 return size;
2303 }
2305 #ifdef WITH_PIECHART
2306 void
2307 pie_part(image_desc_t *im, gfx_color_t color,
2308 double PieCenterX, double PieCenterY, double Radius,
2309 double startangle, double endangle)
2310 {
2311 gfx_node_t *node;
2312 double angle;
2313 double step=M_PI/50; /* Number of iterations for the circle;
2314 ** 10 is definitely too low, more than
2315 ** 50 seems to be overkill
2316 */
2318 /* Strange but true: we have to work clockwise or else
2319 ** anti aliasing nor transparency don't work.
2320 **
2321 ** This test is here to make sure we do it right, also
2322 ** this makes the for...next loop more easy to implement.
2323 ** The return will occur if the user enters a negative number
2324 ** (which shouldn't be done according to the specs) or if the
2325 ** programmers do something wrong (which, as we all know, never
2326 ** happens anyway :)
2327 */
2328 if (endangle<startangle) return;
2330 /* Hidden feature: Radius decreases each full circle */
2331 angle=startangle;
2332 while (angle>=2*M_PI) {
2333 angle -= 2*M_PI;
2334 Radius *= 0.8;
2335 }
2337 node=gfx_new_area(im->canvas,
2338 PieCenterX+sin(startangle)*Radius,
2339 PieCenterY-cos(startangle)*Radius,
2340 PieCenterX,
2341 PieCenterY,
2342 PieCenterX+sin(endangle)*Radius,
2343 PieCenterY-cos(endangle)*Radius,
2344 color);
2345 for (angle=endangle;angle-startangle>=step;angle-=step) {
2346 gfx_add_point(node,
2347 PieCenterX+sin(angle)*Radius,
2348 PieCenterY-cos(angle)*Radius );
2349 }
2350 }
2352 #endif
2354 int
2355 graph_size_location(image_desc_t *im, int elements
2357 #ifdef WITH_PIECHART
2358 , int piechart
2359 #endif
2361 )
2362 {
2363 /* The actual size of the image to draw is determined from
2364 ** several sources. The size given on the command line is
2365 ** the graph area but we need more as we have to draw labels
2366 ** and other things outside the graph area
2367 */
2369 /* +-+-------------------------------------------+
2370 ** |l|.................title.....................|
2371 ** |e+--+-------------------------------+--------+
2372 ** |b| b| | |
2373 ** |a| a| | pie |
2374 ** |l| l| main graph area | chart |
2375 ** |.| .| | area |
2376 ** |t| y| | |
2377 ** |r+--+-------------------------------+--------+
2378 ** |e| | x-axis labels | |
2379 ** |v+--+-------------------------------+--------+
2380 ** | |..............legends......................|
2381 ** +-+-------------------------------------------+
2382 ** | watermark |
2383 ** +---------------------------------------------+
2384 */
2385 int Xvertical=0,
2386 Ytitle =0,
2387 Xylabel =0,
2388 Xmain =0, Ymain =0,
2389 #ifdef WITH_PIECHART
2390 Xpie =0, Ypie =0,
2391 #endif
2392 Yxlabel =0,
2393 #if 0
2394 Xlegend =0, Ylegend =0,
2395 #endif
2396 Xspacing =15, Yspacing =15,
2398 Ywatermark =4;
2400 if (im->extra_flags & ONLY_GRAPH) {
2401 im->xorigin =0;
2402 im->ximg = im->xsize;
2403 im->yimg = im->ysize;
2404 im->yorigin = im->ysize;
2405 ytr(im,DNAN);
2406 return 0;
2407 }
2409 if (im->ylegend[0] != '\0' ) {
2410 Xvertical = im->text_prop[TEXT_PROP_UNIT].size *2;
2411 }
2414 if (im->title[0] != '\0') {
2415 /* The title is placed "inbetween" two text lines so it
2416 ** automatically has some vertical spacing. The horizontal
2417 ** spacing is added here, on each side.
2418 */
2419 /* don't care for the with of the title
2420 Xtitle = gfx_get_text_width(im->canvas, 0,
2421 im->text_prop[TEXT_PROP_TITLE].font,
2422 im->text_prop[TEXT_PROP_TITLE].size,
2423 im->tabwidth,
2424 im->title, 0) + 2*Xspacing; */
2425 Ytitle = im->text_prop[TEXT_PROP_TITLE].size*2.6+10;
2426 }
2428 if (elements) {
2429 Xmain=im->xsize;
2430 Ymain=im->ysize;
2431 if (im->draw_x_grid) {
2432 Yxlabel=im->text_prop[TEXT_PROP_AXIS].size *2.5;
2433 }
2434 if (im->draw_y_grid || im->forceleftspace ) {
2435 Xylabel=gfx_get_text_width(im->canvas, 0,
2436 im->text_prop[TEXT_PROP_AXIS].font,
2437 im->text_prop[TEXT_PROP_AXIS].size,
2438 im->tabwidth,
2439 "0", 0) * im->unitslength;
2440 }
2441 }
2443 #ifdef WITH_PIECHART
2444 if (piechart) {
2445 im->piesize=im->xsize<im->ysize?im->xsize:im->ysize;
2446 Xpie=im->piesize;
2447 Ypie=im->piesize;
2448 }
2449 #endif
2451 /* Now calculate the total size. Insert some spacing where
2452 desired. im->xorigin and im->yorigin need to correspond
2453 with the lower left corner of the main graph area or, if
2454 this one is not set, the imaginary box surrounding the
2455 pie chart area. */
2457 /* The legend width cannot yet be determined, as a result we
2458 ** have problems adjusting the image to it. For now, we just
2459 ** forget about it at all; the legend will have to fit in the
2460 ** size already allocated.
2461 */
2462 im->ximg = Xylabel + Xmain + 2 * Xspacing;
2464 #ifdef WITH_PIECHART
2465 im->ximg += Xpie;
2466 #endif
2468 if (Xmain) im->ximg += Xspacing;
2469 #ifdef WITH_PIECHART
2470 if (Xpie) im->ximg += Xspacing;
2471 #endif
2473 im->xorigin = Xspacing + Xylabel;
2475 /* the length of the title should not influence with width of the graph
2476 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2478 if (Xvertical) { /* unit description */
2479 im->ximg += Xvertical;
2480 im->xorigin += Xvertical;
2481 }
2482 xtr(im,0);
2484 /* The vertical size is interesting... we need to compare
2485 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with
2486 ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2487 ** in order to start even thinking about Ylegend or Ywatermark.
2488 **
2489 ** Do it in three portions: First calculate the inner part,
2490 ** then do the legend, then adjust the total height of the img,
2491 ** adding space for a watermark if one exists;
2492 */
2494 /* reserve space for main and/or pie */
2496 im->yimg = Ymain + Yxlabel;
2498 #ifdef WITH_PIECHART
2499 if (im->yimg < Ypie) im->yimg = Ypie;
2500 #endif
2502 im->yorigin = im->yimg - Yxlabel;
2504 /* reserve space for the title *or* some padding above the graph */
2505 if (Ytitle) {
2506 im->yimg += Ytitle;
2507 im->yorigin += Ytitle;
2508 } else {
2509 im->yimg += 1.5*Yspacing;
2510 im->yorigin += 1.5*Yspacing;
2511 }
2512 /* reserve space for padding below the graph */
2513 im->yimg += Yspacing;
2515 /* Determine where to place the legends onto the image.
2516 ** Adjust im->yimg to match the space requirements.
2517 */
2518 if(leg_place(im)==-1)
2519 return -1;
2521 if (im->watermark[0] != '\0') {
2522 im->yimg += Ywatermark;
2523 }
2525 #if 0
2526 if (Xlegend > im->ximg) {
2527 im->ximg = Xlegend;
2528 /* reposition Pie */
2529 }
2530 #endif
2532 #ifdef WITH_PIECHART
2533 /* The pie is placed in the upper right hand corner,
2534 ** just below the title (if any) and with sufficient
2535 ** padding.
2536 */
2537 if (elements) {
2538 im->pie_x = im->ximg - Xspacing - Xpie/2;
2539 im->pie_y = im->yorigin-Ymain+Ypie/2;
2540 } else {
2541 im->pie_x = im->ximg/2;
2542 im->pie_y = im->yorigin-Ypie/2;
2543 }
2544 #endif
2546 ytr(im,DNAN);
2547 return 0;
2548 }
2550 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
2551 /* yes we are loosing precision by doing tos with floats instead of doubles
2552 but it seems more stable this way. */
2555 /* draw that picture thing ... */
2556 int
2557 graph_paint(image_desc_t *im, char ***calcpr)
2558 {
2559 int i,ii;
2560 int lazy = lazy_check(im);
2561 #ifdef WITH_PIECHART
2562 int piechart = 0;
2563 double PieStart=0.0;
2564 #endif
2565 FILE *fo;
2566 gfx_node_t *node;
2568 double areazero = 0.0;
2569 graph_desc_t *lastgdes = NULL;
2571 /* if we are lazy and there is nothing to PRINT ... quit now */
2572 if (lazy && im->prt_c==0) return 0;
2574 /* pull the data from the rrd files ... */
2576 if(data_fetch(im)==-1)
2577 return -1;
2579 /* evaluate VDEF and CDEF operations ... */
2580 if(data_calc(im)==-1)
2581 return -1;
2583 #ifdef WITH_PIECHART
2584 /* check if we need to draw a piechart */
2585 for(i=0;i<im->gdes_c;i++){
2586 if (im->gdes[i].gf == GF_PART) {
2587 piechart=1;
2588 break;
2589 }
2590 }
2591 #endif
2593 /* calculate and PRINT and GPRINT definitions. We have to do it at
2594 * this point because it will affect the length of the legends
2595 * if there are no graph elements we stop here ...
2596 * if we are lazy, try to quit ...
2597 */
2598 i=print_calc(im,calcpr);
2599 if(i<0) return -1;
2600 if(((i==0)
2601 #ifdef WITH_PIECHART
2602 &&(piechart==0)
2603 #endif
2604 ) || lazy) return 0;
2606 #ifdef WITH_PIECHART
2607 /* If there's only the pie chart to draw, signal this */
2608 if (i==0) piechart=2;
2609 #endif
2611 /* get actual drawing data and find min and max values*/
2612 if(data_proc(im)==-1)
2613 return -1;
2615 if(!im->logarithmic){si_unit(im);} /* identify si magnitude Kilo, Mega Giga ? */
2617 if(!im->rigid && ! im->logarithmic)
2618 expand_range(im); /* make sure the upper and lower limit are
2619 sensible values */
2621 if (!calc_horizontal_grid(im))
2622 return -1;
2624 if (im->gridfit)
2625 apply_gridfit(im);
2628 /**************************************************************
2629 *** Calculating sizes and locations became a bit confusing ***
2630 *** so I moved this into a separate function. ***
2631 **************************************************************/
2632 if(graph_size_location(im,i
2633 #ifdef WITH_PIECHART
2634 ,piechart
2635 #endif
2636 )==-1)
2637 return -1;
2639 /* the actual graph is created by going through the individual
2640 graph elements and then drawing them */
2642 node=gfx_new_area ( im->canvas,
2643 0, 0,
2644 0, im->yimg,
2645 im->ximg, im->yimg,
2646 im->graph_col[GRC_BACK]);
2648 gfx_add_point(node,im->ximg, 0);
2650 #ifdef WITH_PIECHART
2651 if (piechart != 2) {
2652 #endif
2653 node=gfx_new_area ( im->canvas,
2654 im->xorigin, im->yorigin,
2655 im->xorigin + im->xsize, im->yorigin,
2656 im->xorigin + im->xsize, im->yorigin-im->ysize,
2657 im->graph_col[GRC_CANVAS]);
2659 gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
2661 if (im->minval > 0.0)
2662 areazero = im->minval;
2663 if (im->maxval < 0.0)
2664 areazero = im->maxval;
2665 #ifdef WITH_PIECHART
2666 }
2667 #endif
2669 #ifdef WITH_PIECHART
2670 if (piechart) {
2671 pie_part(im,im->graph_col[GRC_CANVAS],im->pie_x,im->pie_y,im->piesize*0.5,0,2*M_PI);
2672 }
2673 #endif
2675 for(i=0;i<im->gdes_c;i++){
2676 switch(im->gdes[i].gf){
2677 case GF_CDEF:
2678 case GF_VDEF:
2679 case GF_DEF:
2680 case GF_PRINT:
2681 case GF_GPRINT:
2682 case GF_COMMENT:
2683 case GF_HRULE:
2684 case GF_VRULE:
2685 case GF_XPORT:
2686 case GF_SHIFT:
2687 break;
2688 case GF_TICK:
2689 for (ii = 0; ii < im->xsize; ii++)
2690 {
2691 if (!isnan(im->gdes[i].p_data[ii]) &&
2692 im->gdes[i].p_data[ii] != 0.0)
2693 {
2694 if (im -> gdes[i].yrule > 0 ) {
2695 gfx_new_line(im->canvas,
2696 im -> xorigin + ii, im->yorigin,
2697 im -> xorigin + ii, im->yorigin - im -> gdes[i].yrule * im -> ysize,
2698 1.0,
2699 im -> gdes[i].col );
2700 } else if ( im -> gdes[i].yrule < 0 ) {
2701 gfx_new_line(im->canvas,
2702 im -> xorigin + ii, im->yorigin - im -> ysize,
2703 im -> xorigin + ii, im->yorigin - ( 1 - im -> gdes[i].yrule ) * im -> ysize,
2704 1.0,
2705 im -> gdes[i].col );
2707 }
2708 }
2709 }
2710 break;
2711 case GF_LINE:
2712 case GF_AREA:
2713 /* fix data points at oo and -oo */
2714 for(ii=0;ii<im->xsize;ii++){
2715 if (isinf(im->gdes[i].p_data[ii])){
2716 if (im->gdes[i].p_data[ii] > 0) {
2717 im->gdes[i].p_data[ii] = im->maxval ;
2718 } else {
2719 im->gdes[i].p_data[ii] = im->minval ;
2720 }
2722 }
2723 } /* for */
2725 /* *******************************************************
2726 a ___. (a,t)
2727 | | ___
2728 ____| | | |
2729 | |___|
2730 -------|--t-1--t--------------------------------
2732 if we know the value at time t was a then
2733 we draw a square from t-1 to t with the value a.
2735 ********************************************************* */
2736 if (im->gdes[i].col != 0x0){
2737 /* GF_LINE and friend */
2738 if(im->gdes[i].gf == GF_LINE ){
2739 double last_y=0.0;
2740 node = NULL;
2741 for(ii=1;ii<im->xsize;ii++){
2742 if (isnan(im->gdes[i].p_data[ii]) || (im->slopemode==1 && isnan(im->gdes[i].p_data[ii-1]))){
2743 node = NULL;
2744 continue;
2745 }
2746 if ( node == NULL ) {
2747 last_y = ytr(im,im->gdes[i].p_data[ii]);
2748 if ( im->slopemode == 0 ){
2749 node = gfx_new_line(im->canvas,
2750 ii-1+im->xorigin,last_y,
2751 ii+im->xorigin,last_y,
2752 im->gdes[i].linewidth,
2753 im->gdes[i].col);
2754 } else {
2755 node = gfx_new_line(im->canvas,
2756 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2757 ii+im->xorigin,last_y,
2758 im->gdes[i].linewidth,
2759 im->gdes[i].col);
2760 }
2761 } else {
2762 double new_y = ytr(im,im->gdes[i].p_data[ii]);
2763 if ( im->slopemode==0 && ! AlmostEqual2sComplement(new_y,last_y,4)){
2764 gfx_add_point(node,ii-1+im->xorigin,new_y);
2765 };
2766 last_y = new_y;
2767 gfx_add_point(node,ii+im->xorigin,new_y);
2768 };
2770 }
2771 } else {
2772 int idxI=-1;
2773 double *foreY=malloc(sizeof(double)*im->xsize*2);
2774 double *foreX=malloc(sizeof(double)*im->xsize*2);
2775 double *backY=malloc(sizeof(double)*im->xsize*2);
2776 double *backX=malloc(sizeof(double)*im->xsize*2);
2777 int drawem = 0;
2778 for(ii=0;ii<=im->xsize;ii++){
2779 double ybase,ytop;
2780 if ( idxI > 0 && ( drawem != 0 || ii==im->xsize)){
2781 int cntI=1;
2782 int lastI=0;
2783 while (cntI < idxI && AlmostEqual2sComplement(foreY[lastI],foreY[cntI],4) && AlmostEqual2sComplement(foreY[lastI],foreY[cntI+1],4)){cntI++;}
2784 node = gfx_new_area(im->canvas,
2785 backX[0],backY[0],
2786 foreX[0],foreY[0],
2787 foreX[cntI],foreY[cntI], im->gdes[i].col);
2788 while (cntI < idxI) {
2789 lastI = cntI;
2790 cntI++;
2791 while ( cntI < idxI && AlmostEqual2sComplement(foreY[lastI],foreY[cntI],4) && AlmostEqual2sComplement(foreY[lastI],foreY[cntI+1],4)){cntI++;}
2792 gfx_add_point(node,foreX[cntI],foreY[cntI]);
2793 }
2794 gfx_add_point(node,backX[idxI],backY[idxI]);
2795 while (idxI > 1){
2796 lastI = idxI;
2797 idxI--;
2798 while ( idxI > 1 && AlmostEqual2sComplement(backY[lastI], backY[idxI],4) && AlmostEqual2sComplement(backY[lastI],backY[idxI-1],4)){idxI--;}
2799 gfx_add_point(node,backX[idxI],backY[idxI]);
2800 }
2801 idxI=-1;
2802 drawem = 0;
2803 }
2804 if (drawem != 0){
2805 drawem = 0;
2806 idxI=-1;
2807 }
2808 if (ii == im->xsize) break;
2810 /* keep things simple for now, just draw these bars
2811 do not try to build a big and complex area */
2814 if ( im->slopemode == 0 && ii==0){
2815 continue;
2816 }
2817 if ( isnan(im->gdes[i].p_data[ii]) ) {
2818 drawem = 1;
2819 continue;
2820 }
2821 ytop = ytr(im,im->gdes[i].p_data[ii]);
2822 if ( lastgdes && im->gdes[i].stack ) {
2823 ybase = ytr(im,lastgdes->p_data[ii]);
2824 } else {
2825 ybase = ytr(im,areazero);
2826 }
2827 if ( ybase == ytop ){
2828 drawem = 1;
2829 continue;
2830 }
2831 /* every area has to be wound clock-wise,
2832 so we have to make sur base remains base */
2833 if (ybase > ytop){
2834 double extra = ytop;
2835 ytop = ybase;
2836 ybase = extra;
2837 }
2838 if ( im->slopemode == 0 ){
2839 backY[++idxI] = ybase-0.2;
2840 backX[idxI] = ii+im->xorigin-1;
2841 foreY[idxI] = ytop+0.2;
2842 foreX[idxI] = ii+im->xorigin-1;
2843 }
2844 backY[++idxI] = ybase-0.2;
2845 backX[idxI] = ii+im->xorigin;
2846 foreY[idxI] = ytop+0.2;
2847 foreX[idxI] = ii+im->xorigin;
2848 }
2849 /* close up any remaining area */
2850 free(foreY);
2851 free(foreX);
2852 free(backY);
2853 free(backX);
2854 } /* else GF_LINE */
2855 } /* if color != 0x0 */
2856 /* make sure we do not run into trouble when stacking on NaN */
2857 for(ii=0;ii<im->xsize;ii++){
2858 if (isnan(im->gdes[i].p_data[ii])) {
2859 if (lastgdes && (im->gdes[i].stack)) {
2860 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
2861 } else {
2862 im->gdes[i].p_data[ii] = areazero;
2863 }
2864 }
2865 }
2866 lastgdes = &(im->gdes[i]);
2867 break;
2868 #ifdef WITH_PIECHART
2869 case GF_PART:
2870 if(isnan(im->gdes[i].yrule)) /* fetch variable */
2871 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2873 if (finite(im->gdes[i].yrule)) { /* even the fetched var can be NaN */
2874 pie_part(im,im->gdes[i].col,
2875 im->pie_x,im->pie_y,im->piesize*0.4,
2876 M_PI*2.0*PieStart/100.0,
2877 M_PI*2.0*(PieStart+im->gdes[i].yrule)/100.0);
2878 PieStart += im->gdes[i].yrule;
2879 }
2880 break;
2881 #endif
2882 case GF_STACK:
2883 rrd_set_error("STACK should already be turned into LINE or AREA here");
2884 return -1;
2885 break;
2887 } /* switch */
2888 }
2889 #ifdef WITH_PIECHART
2890 if (piechart==2) {
2891 im->draw_x_grid=0;
2892 im->draw_y_grid=0;
2893 }
2894 #endif
2897 /* grid_paint also does the text */
2898 if( !(im->extra_flags & ONLY_GRAPH) )
2899 grid_paint(im);
2902 if( !(im->extra_flags & ONLY_GRAPH) )
2903 axis_paint(im);
2905 /* the RULES are the last thing to paint ... */
2906 for(i=0;i<im->gdes_c;i++){
2908 switch(im->gdes[i].gf){
2909 case GF_HRULE:
2910 if(im->gdes[i].yrule >= im->minval
2911 && im->gdes[i].yrule <= im->maxval)
2912 gfx_new_line(im->canvas,
2913 im->xorigin,ytr(im,im->gdes[i].yrule),
2914 im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2915 1.0,im->gdes[i].col);
2916 break;
2917 case GF_VRULE:
2918 if(im->gdes[i].xrule >= im->start
2919 && im->gdes[i].xrule <= im->end)
2920 gfx_new_line(im->canvas,
2921 xtr(im,im->gdes[i].xrule),im->yorigin,
2922 xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2923 1.0,im->gdes[i].col);
2924 break;
2925 default:
2926 break;
2927 }
2928 }
2931 if (strcmp(im->graphfile,"-")==0) {
2932 fo = im->graphhandle ? im->graphhandle : stdout;
2933 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
2934 /* Change translation mode for stdout to BINARY */
2935 _setmode( _fileno( fo ), O_BINARY );
2936 #endif
2937 } else {
2938 if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2939 rrd_set_error("Opening '%s' for write: %s",im->graphfile,
2940 rrd_strerror(errno));
2941 return (-1);
2942 }
2943 }
2944 gfx_render (im->canvas,im->ximg,im->yimg,0x00000000,fo);
2945 if (strcmp(im->graphfile,"-") != 0)
2946 fclose(fo);
2947 return 0;
2948 }
2951 /*****************************************************
2952 * graph stuff
2953 *****************************************************/
2955 int
2956 gdes_alloc(image_desc_t *im){
2958 im->gdes_c++;
2959 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2960 * sizeof(graph_desc_t)))==NULL){
2961 rrd_set_error("realloc graph_descs");
2962 return -1;
2963 }
2966 im->gdes[im->gdes_c-1].step=im->step;
2967 im->gdes[im->gdes_c-1].step_orig=im->step;
2968 im->gdes[im->gdes_c-1].stack=0;
2969 im->gdes[im->gdes_c-1].linewidth=0;
2970 im->gdes[im->gdes_c-1].debug=0;
2971 im->gdes[im->gdes_c-1].start=im->start;
2972 im->gdes[im->gdes_c-1].start_orig=im->start;
2973 im->gdes[im->gdes_c-1].end=im->end;
2974 im->gdes[im->gdes_c-1].end_orig=im->end;
2975 im->gdes[im->gdes_c-1].vname[0]='\0';
2976 im->gdes[im->gdes_c-1].data=NULL;
2977 im->gdes[im->gdes_c-1].ds_namv=NULL;
2978 im->gdes[im->gdes_c-1].data_first=0;
2979 im->gdes[im->gdes_c-1].p_data=NULL;
2980 im->gdes[im->gdes_c-1].rpnp=NULL;
2981 im->gdes[im->gdes_c-1].shift=0;
2982 im->gdes[im->gdes_c-1].col = 0x0;
2983 im->gdes[im->gdes_c-1].legend[0]='\0';
2984 im->gdes[im->gdes_c-1].format[0]='\0';
2985 im->gdes[im->gdes_c-1].strftm=0;
2986 im->gdes[im->gdes_c-1].rrd[0]='\0';
2987 im->gdes[im->gdes_c-1].ds=-1;
2988 im->gdes[im->gdes_c-1].cf_reduce=CF_AVERAGE;
2989 im->gdes[im->gdes_c-1].cf=CF_AVERAGE;
2990 im->gdes[im->gdes_c-1].p_data=NULL;
2991 im->gdes[im->gdes_c-1].yrule=DNAN;
2992 im->gdes[im->gdes_c-1].xrule=0;
2993 return 0;
2994 }
2996 /* copies input untill the first unescaped colon is found
2997 or until input ends. backslashes have to be escaped as well */
2998 int
2999 scan_for_col(const char *const input, int len, char *const output)
3000 {
3001 int inp,outp=0;
3002 for (inp=0;
3003 inp < len &&
3004 input[inp] != ':' &&
3005 input[inp] != '\0';
3006 inp++){
3007 if (input[inp] == '\\' &&
3008 input[inp+1] != '\0' &&
3009 (input[inp+1] == '\\' ||
3010 input[inp+1] == ':')){
3011 output[outp++] = input[++inp];
3012 }
3013 else {
3014 output[outp++] = input[inp];
3015 }
3016 }
3017 output[outp] = '\0';
3018 return inp;
3019 }
3020 /* Some surgery done on this function, it became ridiculously big.
3021 ** Things moved:
3022 ** - initializing now in rrd_graph_init()
3023 ** - options parsing now in rrd_graph_options()
3024 ** - script parsing now in rrd_graph_script()
3025 */
3026 int
3027 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize, FILE *stream, double *ymin, double *ymax)
3028 {
3029 image_desc_t im;
3030 rrd_graph_init(&im);
3031 im.graphhandle = stream;
3033 rrd_graph_options(argc,argv,&im);
3034 if (rrd_test_error()) {
3035 im_free(&im);
3036 return -1;
3037 }
3039 if (strlen(argv[optind])>=MAXPATH) {
3040 rrd_set_error("filename (including path) too long");
3041 im_free(&im);
3042 return -1;
3043 }
3044 strncpy(im.graphfile,argv[optind],MAXPATH-1);
3045 im.graphfile[MAXPATH-1]='\0';
3047 rrd_graph_script(argc,argv,&im,1);
3048 if (rrd_test_error()) {
3049 im_free(&im);
3050 return -1;
3051 }
3053 /* Everything is now read and the actual work can start */
3055 (*prdata)=NULL;
3056 if (graph_paint(&im,prdata)==-1){
3057 im_free(&im);
3058 return -1;
3059 }
3061 /* The image is generated and needs to be output.
3062 ** Also, if needed, print a line with information about the image.
3063 */
3065 *xsize=im.ximg;
3066 *ysize=im.yimg;
3067 *ymin=im.minval;
3068 *ymax=im.maxval;
3069 if (im.imginfo) {
3070 char *filename;
3071 if (!(*prdata)) {
3072 /* maybe prdata is not allocated yet ... lets do it now */
3073 if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
3074 rrd_set_error("malloc imginfo");
3075 return -1;
3076 };
3077 }
3078 if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
3079 ==NULL){
3080 rrd_set_error("malloc imginfo");
3081 return -1;
3082 }
3083 filename=im.graphfile+strlen(im.graphfile);
3084 while(filename > im.graphfile) {
3085 if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
3086 filename--;
3087 }
3089 sprintf((*prdata)[0],im.imginfo,filename,(long)(im.canvas->zoom*im.ximg),(long)(im.canvas->zoom*im.yimg));
3090 }
3091 im_free(&im);
3092 return 0;
3093 }
3095 void
3096 rrd_graph_init(image_desc_t *im)
3097 {
3098 unsigned int i;
3100 #ifdef HAVE_TZSET
3101 tzset();
3102 #endif
3103 #ifdef HAVE_SETLOCALE
3104 setlocale(LC_TIME,"");
3105 #ifdef HAVE_MBSTOWCS
3106 setlocale(LC_CTYPE,"");
3107 #endif
3108 #endif
3109 im->yorigin=0;
3110 im->xorigin=0;
3111 im->minval=0;
3112 im->xlab_user.minsec = -1;
3113 im->ximg=0;
3114 im->yimg=0;
3115 im->xsize = 400;
3116 im->ysize = 100;
3117 im->step = 0;
3118 im->ylegend[0] = '\0';
3119 im->title[0] = '\0';
3120 im->watermark[0] = '\0';
3121 im->minval = DNAN;
3122 im->maxval = DNAN;
3123 im->unitsexponent= 9999;
3124 im->unitslength= 6;
3125 im->forceleftspace = 0;
3126 im->symbol = ' ';
3127 im->viewfactor = 1.0;
3128 im->extra_flags= 0;
3129 im->rigid = 0;
3130 im->gridfit = 1;
3131 im->imginfo = NULL;
3132 im->lazy = 0;
3133 im->slopemode = 0;
3134 im->logarithmic = 0;
3135 im->ygridstep = DNAN;
3136 im->draw_x_grid = 1;
3137 im->draw_y_grid = 1;
3138 im->base = 1000;
3139 im->prt_c = 0;
3140 im->gdes_c = 0;
3141 im->gdes = NULL;
3142 im->canvas = gfx_new_canvas();
3143 im->grid_dash_on = 1;
3144 im->grid_dash_off = 1;
3145 im->tabwidth = 40.0;
3147 for(i=0;i<DIM(graph_col);i++)
3148 im->graph_col[i]=graph_col[i];
3150 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3151 {
3152 char *windir;
3153 char rrd_win_default_font[1000];
3154 windir = getenv("windir");
3155 /* %windir% is something like D:\windows or C:\winnt */
3156 if (windir != NULL) {
3157 strncpy(rrd_win_default_font,windir,500);
3158 rrd_win_default_font[500] = '\0';
3159 strcat(rrd_win_default_font,"\\fonts\\");
3160 strcat(rrd_win_default_font,RRD_DEFAULT_FONT);
3161 for(i=0;i<DIM(text_prop);i++){
3162 strncpy(text_prop[i].font,rrd_win_default_font,sizeof(text_prop[i].font)-1);
3163 text_prop[i].font[sizeof(text_prop[i].font)-1] = '\0';
3164 }
3165 }
3166 }
3167 #endif
3168 {
3169 char *deffont;
3170 deffont = getenv("RRD_DEFAULT_FONT");
3171 if (deffont != NULL) {
3172 for(i=0;i<DIM(text_prop);i++){
3173 strncpy(text_prop[i].font,deffont,sizeof(text_prop[i].font)-1);
3174 text_prop[i].font[sizeof(text_prop[i].font)-1] = '\0';
3175 }
3176 }
3177 }
3178 for(i=0;i<DIM(text_prop);i++){
3179 im->text_prop[i].size = text_prop[i].size;
3180 strcpy(im->text_prop[i].font,text_prop[i].font);
3181 }
3182 }
3184 void
3185 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
3186 {
3187 int stroff;
3188 char *parsetime_error = NULL;
3189 char scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
3190 time_t start_tmp=0,end_tmp=0;
3191 long long_tmp;
3192 struct rrd_time_value start_tv, end_tv;
3193 gfx_color_t color;
3194 optind = 0; opterr = 0; /* initialize getopt */
3196 parsetime("end-24h", &start_tv);
3197 parsetime("now", &end_tv);
3199 /* defines for long options without a short equivalent. should be bytes,
3200 and may not collide with (the ASCII value of) short options */
3201 #define LONGOPT_UNITS_SI 255
3203 while (1){
3204 static struct option long_options[] =
3205 {
3206 {"start", required_argument, 0, 's'},
3207 {"end", required_argument, 0, 'e'},
3208 {"x-grid", required_argument, 0, 'x'},
3209 {"y-grid", required_argument, 0, 'y'},
3210 {"vertical-label",required_argument,0,'v'},
3211 {"width", required_argument, 0, 'w'},
3212 {"height", required_argument, 0, 'h'},
3213 {"interlaced", no_argument, 0, 'i'},
3214 {"upper-limit",required_argument, 0, 'u'},
3215 {"lower-limit",required_argument, 0, 'l'},
3216 {"rigid", no_argument, 0, 'r'},
3217 {"base", required_argument, 0, 'b'},
3218 {"logarithmic",no_argument, 0, 'o'},
3219 {"color", required_argument, 0, 'c'},
3220 {"font", required_argument, 0, 'n'},
3221 {"title", required_argument, 0, 't'},
3222 {"imginfo", required_argument, 0, 'f'},
3223 {"imgformat", required_argument, 0, 'a'},
3224 {"lazy", no_argument, 0, 'z'},
3225 {"zoom", required_argument, 0, 'm'},
3226 {"no-legend", no_argument, 0, 'g'},
3227 {"force-rules-legend",no_argument,0, 'F'},
3228 {"only-graph", no_argument, 0, 'j'},
3229 {"alt-y-grid", no_argument, 0, 'Y'},
3230 {"no-minor", no_argument, 0, 'I'},
3231 {"slope-mode", no_argument, 0, 'E'},
3232 {"alt-autoscale", no_argument, 0, 'A'},
3233 {"alt-autoscale-min", no_argument, 0, 'J'},
3234 {"alt-autoscale-max", no_argument, 0, 'M'},
3235 {"no-gridfit", no_argument, 0, 'N'},
3236 {"units-exponent",required_argument, 0, 'X'},
3237 {"units-length",required_argument, 0, 'L'},
3238 {"units", required_argument, 0, LONGOPT_UNITS_SI },
3239 {"step", required_argument, 0, 'S'},
3240 {"tabwidth", required_argument, 0, 'T'},
3241 {"font-render-mode", required_argument, 0, 'R'},
3242 {"font-smoothing-threshold", required_argument, 0, 'B'},
3243 {"watermark", required_argument, 0, 'W'},
3244 {"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 */
3245 {0,0,0,0}};
3246 int option_index = 0;
3247 int opt;
3248 int col_start,col_end;
3250 opt = getopt_long(argc, argv,
3251 "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:I:zgjFYAMEX:L:S:T:NR:B:W:",
3252 long_options, &option_index);
3254 if (opt == EOF)
3255 break;
3257 switch(opt) {
3258 case 'I':
3259 im->extra_flags |= NOMINOR;
3260 break;
3261 case 'Y':
3262 im->extra_flags |= ALTYGRID;
3263 break;
3264 case 'A':
3265 im->extra_flags |= ALTAUTOSCALE;
3266 break;
3267 case 'J':
3268 im->extra_flags |= ALTAUTOSCALE_MIN;
3269 break;
3270 case 'M':
3271 im->extra_flags |= ALTAUTOSCALE_MAX;
3272 break;
3273 case 'j':
3274 im->extra_flags |= ONLY_GRAPH;
3275 break;
3276 case 'g':
3277 im->extra_flags |= NOLEGEND;
3278 break;
3279 case 'F':
3280 im->extra_flags |= FORCE_RULES_LEGEND;
3281 break;
3282 case LONGOPT_UNITS_SI:
3283 if(im->extra_flags & FORCE_UNITS) {
3284 rrd_set_error("--units can only be used once!");
3285 return;
3286 }
3287 if(strcmp(optarg,"si")==0)
3288 im->extra_flags |= FORCE_UNITS_SI;
3289 else {
3290 rrd_set_error("invalid argument for --units: %s", optarg );
3291 return;
3292 }
3293 break;
3294 case 'X':
3295 im->unitsexponent = atoi(optarg);
3296 break;
3297 case 'L':
3298 im->unitslength = atoi(optarg);
3299 im->forceleftspace = 1;
3300 break;
3301 case 'T':
3302 im->tabwidth = atof(optarg);
3303 break;
3304 case 'S':
3305 im->step = atoi(optarg);
3306 break;
3307 case 'N':
3308 im->gridfit = 0;
3309 break;
3310 case 's':
3311 if ((parsetime_error = parsetime(optarg, &start_tv))) {
3312 rrd_set_error( "start time: %s", parsetime_error );
3313 return;
3314 }
3315 break;
3316 case 'e':
3317 if ((parsetime_error = parsetime(optarg, &end_tv))) {
3318 rrd_set_error( "end time: %s", parsetime_error );
3319 return;
3320 }
3321 break;
3322 case 'x':
3323 if(strcmp(optarg,"none") == 0){
3324 im->draw_x_grid=0;
3325 break;
3326 };
3328 if(sscanf(optarg,
3329 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3330 scan_gtm,
3331 &im->xlab_user.gridst,
3332 scan_mtm,
3333 &im->xlab_user.mgridst,
3334 scan_ltm,
3335 &im->xlab_user.labst,
3336 &im->xlab_user.precis,
3337 &stroff) == 7 && stroff != 0){
3338 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
3339 im->xlab_form[sizeof(im->xlab_form)-1] = '\0';
3340 if((int)(im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
3341 rrd_set_error("unknown keyword %s",scan_gtm);
3342 return;
3343 } else if ((int)(im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
3344 rrd_set_error("unknown keyword %s",scan_mtm);
3345 return;
3346 } else if ((int)(im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
3347 rrd_set_error("unknown keyword %s",scan_ltm);
3348 return;
3349 }
3350 im->xlab_user.minsec = 1;
3351 im->xlab_user.stst = im->xlab_form;
3352 } else {
3353 rrd_set_error("invalid x-grid format");
3354 return;
3355 }
3356 break;
3357 case 'y':
3359 if(strcmp(optarg,"none") == 0){
3360 im->draw_y_grid=0;
3361 break;
3362 };
3364 if(sscanf(optarg,
3365 "%lf:%d",
3366 &im->ygridstep,
3367 &im->ylabfact) == 2) {
3368 if(im->ygridstep<=0){
3369 rrd_set_error("grid step must be > 0");
3370 return;
3371 } else if (im->ylabfact < 1){
3372 rrd_set_error("label factor must be > 0");
3373 return;
3374 }
3375 } else {
3376 rrd_set_error("invalid y-grid format");
3377 return;
3378 }
3379 break;
3380 case 'v':
3381 strncpy(im->ylegend,optarg,150);
3382 im->ylegend[150]='\0';
3383 break;
3384 case 'u':
3385 im->maxval = atof(optarg);
3386 break;
3387 case 'l':
3388 im->minval = atof(optarg);
3389 break;
3390 case 'b':
3391 im->base = atol(optarg);
3392 if(im->base != 1024 && im->base != 1000 ){
3393 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
3394 return;
3395 }
3396 break;
3397 case 'w':
3398 long_tmp = atol(optarg);
3399 if (long_tmp < 10) {
3400 rrd_set_error("width below 10 pixels");
3401 return;
3402 }
3403 im->xsize = long_tmp;
3404 break;
3405 case 'h':
3406 long_tmp = atol(optarg);
3407 if (long_tmp < 10) {
3408 rrd_set_error("height below 10 pixels");
3409 return;
3410 }
3411 im->ysize = long_tmp;
3412 break;
3413 case 'i':
3414 im->canvas->interlaced = 1;
3415 break;
3416 case 'r':
3417 im->rigid = 1;
3418 break;
3419 case 'f':
3420 im->imginfo = optarg;
3421 break;
3422 case 'a':
3423 if((int)(im->canvas->imgformat = if_conv(optarg)) == -1) {
3424 rrd_set_error("unsupported graphics format '%s'",optarg);
3425 return;
3426 }
3427 break;
3428 case 'z':
3429 im->lazy = 1;
3430 break;
3431 case 'E':
3432 im->slopemode = 1;
3433 break;
3435 case 'o':
3436 im->logarithmic = 1;
3437 break;
3438 case 'c':
3439 if(sscanf(optarg,
3440 "%10[A-Z]#%n%8lx%n",
3441 col_nam,&col_start,&color,&col_end) == 2){
3442 int ci;
3443 int col_len = col_end - col_start;
3444 switch (col_len){
3445 case 3:
3446 color = (
3447 ((color & 0xF00) * 0x110000) |
3448 ((color & 0x0F0) * 0x011000) |
3449 ((color & 0x00F) * 0x001100) |
3450 0x000000FF
3451 );
3452 break;
3453 case 4:
3454 color = (
3455 ((color & 0xF000) * 0x11000) |
3456 ((color & 0x0F00) * 0x01100) |
3457 ((color & 0x00F0) * 0x00110) |
3458 ((color & 0x000F) * 0x00011)
3459 );
3460 break;
3461 case 6:
3462 color = (color << 8) + 0xff /* shift left by 8 */;
3463 break;
3464 case 8:
3465 break;
3466 default:
3467 rrd_set_error("the color format is #RRGGBB[AA]");
3468 return;
3469 }
3470 if((ci=grc_conv(col_nam)) != -1){
3471 im->graph_col[ci]=color;
3472 } else {
3473 rrd_set_error("invalid color name '%s'",col_nam);
3474 return;
3475 }
3476 } else {
3477 rrd_set_error("invalid color def format");
3478 return;
3479 }
3480 break;
3481 case 'n':{
3482 char prop[15];
3483 double size = 1;
3484 char font[1024] = "";
3486 if(sscanf(optarg,
3487 "%10[A-Z]:%lf:%1000s",
3488 prop,&size,font) >= 2){
3489 int sindex,propidx;
3490 if((sindex=text_prop_conv(prop)) != -1){
3491 for (propidx=sindex;propidx<TEXT_PROP_LAST;propidx++){
3492 if (size > 0){
3493 im->text_prop[propidx].size=size;
3494 }
3495 if (strlen(font) > 0){
3496 strcpy(im->text_prop[propidx].font,font);
3497 }
3498 if (propidx==sindex && sindex != 0) break;
3499 }
3500 } else {
3501 rrd_set_error("invalid fonttag '%s'",prop);
3502 return;
3503 }
3504 } else {
3505 rrd_set_error("invalid text property format");
3506 return;
3507 }
3508 break;
3509 }
3510 case 'm':
3511 im->canvas->zoom = atof(optarg);
3512 if (im->canvas->zoom <= 0.0) {
3513 rrd_set_error("zoom factor must be > 0");
3514 return;
3515 }
3516 break;
3517 case 't':
3518 strncpy(im->title,optarg,150);
3519 im->title[150]='\0';
3520 break;
3522 case 'R':
3523 if ( strcmp( optarg, "normal" ) == 0 )
3524 im->canvas->aa_type = AA_NORMAL;
3525 else if ( strcmp( optarg, "light" ) == 0 )
3526 im->canvas->aa_type = AA_LIGHT;
3527 else if ( strcmp( optarg, "mono" ) == 0 )
3528 im->canvas->aa_type = AA_NONE;
3529 else
3530 {
3531 rrd_set_error("unknown font-render-mode '%s'", optarg );
3532 return;
3533 }
3534 break;
3536 case 'B':
3537 im->canvas->font_aa_threshold = atof(optarg);
3538 break;
3540 case 'W':
3541 strncpy(im->watermark,optarg,100);
3542 im->watermark[99]='\0';
3543 break;
3545 case '?':
3546 if (optopt != 0)
3547 rrd_set_error("unknown option '%c'", optopt);
3548 else
3549 rrd_set_error("unknown option '%s'",argv[optind-1]);
3550 return;
3551 }
3552 }
3554 if (optind >= argc) {
3555 rrd_set_error("missing filename");
3556 return;
3557 }
3559 if (im->logarithmic == 1 && im->minval <= 0){
3560 rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");
3561 return;
3562 }
3564 if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
3565 /* error string is set in parsetime.c */
3566 return;
3567 }
3569 if (start_tmp < 3600*24*365*10){
3570 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
3571 return;
3572 }
3574 if (end_tmp < start_tmp) {
3575 rrd_set_error("start (%ld) should be less than end (%ld)",
3576 start_tmp, end_tmp);
3577 return;
3578 }
3580 im->start = start_tmp;
3581 im->end = end_tmp;
3582 im->step = max((long)im->step, (im->end-im->start)/im->xsize);
3583 }
3585 int
3586 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
3587 {
3588 char *color;
3589 graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
3591 color=strstr(var,"#");
3592 if (color==NULL) {
3593 if (optional==0) {
3594 rrd_set_error("Found no color in %s",err);
3595 return 0;
3596 }
3597 return 0;
3598 } else {
3599 int n=0;
3600 char *rest;
3601 gfx_color_t col;
3603 rest=strstr(color,":");
3604 if (rest!=NULL)
3605 n=rest-color;
3606 else
3607 n=strlen(color);
3609 switch (n) {
3610 case 7:
3611 sscanf(color,"#%6lx%n",&col,&n);
3612 col = (col << 8) + 0xff /* shift left by 8 */;
3613 if (n!=7) rrd_set_error("Color problem in %s",err);
3614 break;
3615 case 9:
3616 sscanf(color,"#%8lx%n",&col,&n);
3617 if (n==9) break;
3618 default:
3619 rrd_set_error("Color problem in %s",err);
3620 }
3621 if (rrd_test_error()) return 0;
3622 gdp->col = col;
3623 return n;
3624 }
3625 }
3628 int bad_format(char *fmt) {
3629 char *ptr;
3630 int n=0;
3631 ptr = fmt;
3632 while (*ptr != '\0')
3633 if (*ptr++ == '%') {
3635 /* line cannot end with percent char */
3636 if (*ptr == '\0') return 1;
3638 /* '%s', '%S' and '%%' are allowed */
3639 if (*ptr == 's' || *ptr == 'S' || *ptr == '%') ptr++;
3641 /* %c is allowed (but use only with vdef!) */
3642 else if (*ptr == 'c') {
3643 ptr++;
3644 n=1;
3645 }
3647 /* or else '% 6.2lf' and such are allowed */
3648 else {
3649 /* optional padding character */
3650 if (*ptr == ' ' || *ptr == '+' || *ptr == '-') ptr++;
3652 /* This should take care of 'm.n' with all three optional */
3653 while (*ptr >= '0' && *ptr <= '9') ptr++;
3654 if (*ptr == '.') ptr++;
3655 while (*ptr >= '0' && *ptr <= '9') ptr++;
3657 /* Either 'le', 'lf' or 'lg' must follow here */
3658 if (*ptr++ != 'l') return 1;
3659 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g') ptr++;
3660 else return 1;
3661 n++;
3662 }
3663 }
3665 return (n!=1);
3666 }
3669 int
3670 vdef_parse(gdes,str)
3671 struct graph_desc_t *gdes;
3672 const char *const str;
3673 {
3674 /* A VDEF currently is either "func" or "param,func"
3675 * so the parsing is rather simple. Change if needed.
3676 */
3677 double param;
3678 char func[30];
3679 int n;
3681 n=0;
3682 sscanf(str,"%le,%29[A-Z]%n",¶m,func,&n);
3683 if (n== (int)strlen(str)) { /* matched */
3684 ;
3685 } else {
3686 n=0;
3687 sscanf(str,"%29[A-Z]%n",func,&n);
3688 if (n== (int)strlen(str)) { /* matched */
3689 param=DNAN;
3690 } else {
3691 rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3692 ,str
3693 ,gdes->vname
3694 );
3695 return -1;
3696 }
3697 }
3698 if (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3699 else if (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3700 else if (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3701 else if (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3702 else if (!strcmp("TOTAL", func)) gdes->vf.op = VDEF_TOTAL;
3703 else if (!strcmp("FIRST", func)) gdes->vf.op = VDEF_FIRST;
3704 else if (!strcmp("LAST", func)) gdes->vf.op = VDEF_LAST;
3705 else if (!strcmp("LSLSLOPE", func)) gdes->vf.op = VDEF_LSLSLOPE;
3706 else if (!strcmp("LSLINT", func)) gdes->vf.op = VDEF_LSLINT;
3707 else if (!strcmp("LSLCORREL",func)) gdes->vf.op = VDEF_LSLCORREL;
3708 else {
3709 rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3710 ,func
3711 ,gdes->vname
3712 );
3713 return -1;
3714 };
3716 switch (gdes->vf.op) {
3717 case VDEF_PERCENT:
3718 if (isnan(param)) { /* no parameter given */
3719 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3720 ,func
3721 ,gdes->vname
3722 );
3723 return -1;
3724 };
3725 if (param>=0.0 && param<=100.0) {
3726 gdes->vf.param = param;
3727 gdes->vf.val = DNAN; /* undefined */
3728 gdes->vf.when = 0; /* undefined */
3729 } else {
3730 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3731 ,param
3732 ,gdes->vname
3733 );
3734 return -1;
3735 };
3736 break;
3737 case VDEF_MAXIMUM:
3738 case VDEF_AVERAGE:
3739 case VDEF_MINIMUM:
3740 case VDEF_TOTAL:
3741 case VDEF_FIRST:
3742 case VDEF_LAST:
3743 case VDEF_LSLSLOPE:
3744 case VDEF_LSLINT:
3745 case VDEF_LSLCORREL:
3746 if (isnan(param)) {
3747 gdes->vf.param = DNAN;
3748 gdes->vf.val = DNAN;
3749 gdes->vf.when = 0;
3750 } else {
3751 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3752 ,func
3753 ,gdes->vname
3754 );
3755 return -1;
3756 };
3757 break;
3758 };
3759 return 0;
3760 }
3763 int
3764 vdef_calc(im,gdi)
3765 image_desc_t *im;
3766 int gdi;
3767 {
3768 graph_desc_t *src,*dst;
3769 rrd_value_t *data;
3770 long step,steps;
3772 dst = &im->gdes[gdi];
3773 src = &im->gdes[dst->vidx];
3774 data = src->data + src->ds;
3775 steps = (src->end - src->start) / src->step;
3777 #if 0
3778 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3779 ,src->start
3780 ,src->end
3781 ,steps
3782 );
3783 #endif
3785 switch (dst->vf.op) {
3786 case VDEF_PERCENT: {
3787 rrd_value_t * array;
3788 int field;
3791 if ((array = malloc(steps*sizeof(double)))==NULL) {
3792 rrd_set_error("malloc VDEV_PERCENT");
3793 return -1;
3794 }
3795 for (step=0;step < steps; step++) {
3796 array[step]=data[step*src->ds_cnt];
3797 }
3798 qsort(array,step,sizeof(double),vdef_percent_compar);
3800 field = (steps-1)*dst->vf.param/100;
3801 dst->vf.val = array[field];
3802 dst->vf.when = 0; /* no time component */
3803 free(array);
3804 #if 0
3805 for(step=0;step<steps;step++)
3806 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3807 #endif
3808 }
3809 break;
3810 case VDEF_MAXIMUM:
3811 step=0;
3812 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3813 if (step == steps) {
3814 dst->vf.val = DNAN;
3815 dst->vf.when = 0;
3816 } else {
3817 dst->vf.val = data[step*src->ds_cnt];
3818 dst->vf.when = src->start + (step+1)*src->step;
3819 }
3820 while (step != steps) {
3821 if (finite(data[step*src->ds_cnt])) {
3822 if (data[step*src->ds_cnt] > dst->vf.val) {
3823 dst->vf.val = data[step*src->ds_cnt];
3824 dst->vf.when = src->start + (step+1)*src->step;
3825 }
3826 }
3827 step++;
3828 }
3829 break;
3830 case VDEF_TOTAL:
3831 case VDEF_AVERAGE: {
3832 int cnt=0;
3833 double sum=0.0;
3834 for (step=0;step<steps;step++) {
3835 if (finite(data[step*src->ds_cnt])) {
3836 sum += data[step*src->ds_cnt];
3837 cnt ++;
3838 };
3839 }
3840 if (cnt) {
3841 if (dst->vf.op == VDEF_TOTAL) {
3842 dst->vf.val = sum*src->step;
3843 dst->vf.when = 0; /* no time component */
3844 } else {
3845 dst->vf.val = sum/cnt;
3846 dst->vf.when = 0; /* no time component */
3847 };
3848 } else {
3849 dst->vf.val = DNAN;
3850 dst->vf.when = 0;
3851 }
3852 }
3853 break;
3854 case VDEF_MINIMUM:
3855 step=0;
3856 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3857 if (step == steps) {
3858 dst->vf.val = DNAN;
3859 dst->vf.when = 0;
3860 } else {
3861 dst->vf.val = data[step*src->ds_cnt];
3862 dst->vf.when = src->start + (step+1)*src->step;
3863 }
3864 while (step != steps) {
3865 if (finite(data[step*src->ds_cnt])) {
3866 if (data[step*src->ds_cnt] < dst->vf.val) {
3867 dst->vf.val = data[step*src->ds_cnt];
3868 dst->vf.when = src->start + (step+1)*src->step;
3869 }
3870 }
3871 step++;
3872 }
3873 break;
3874 case VDEF_FIRST:
3875 /* The time value returned here is one step before the
3876 * actual time value. This is the start of the first
3877 * non-NaN interval.
3878 */
3879 step=0;
3880 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3881 if (step == steps) { /* all entries were NaN */
3882 dst->vf.val = DNAN;
3883 dst->vf.when = 0;
3884 } else {
3885 dst->vf.val = data[step*src->ds_cnt];
3886 dst->vf.when = src->start + step*src->step;
3887 }
3888 break;
3889 case VDEF_LAST:
3890 /* The time value returned here is the
3891 * actual time value. This is the end of the last
3892 * non-NaN interval.
3893 */
3894 step=steps-1;
3895 while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3896 if (step < 0) { /* all entries were NaN */
3897 dst->vf.val = DNAN;
3898 dst->vf.when = 0;
3899 } else {
3900 dst->vf.val = data[step*src->ds_cnt];
3901 dst->vf.when = src->start + (step+1)*src->step;
3902 }
3903 break;
3904 case VDEF_LSLSLOPE:
3905 case VDEF_LSLINT:
3906 case VDEF_LSLCORREL:{
3907 /* Bestfit line by linear least squares method */
3909 int cnt=0;
3910 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl ;
3911 SUMx = 0; SUMy = 0; SUMxy = 0; SUMxx = 0; SUMyy = 0;
3913 for (step=0;step<steps;step++) {
3914 if (finite(data[step*src->ds_cnt])) {
3915 cnt++;
3916 SUMx += step;
3917 SUMxx += step * step;
3918 SUMxy += step * data[step*src->ds_cnt];
3919 SUMy += data[step*src->ds_cnt];
3920 SUMyy += data[step*src->ds_cnt]*data[step*src->ds_cnt];
3921 };
3922 }
3924 slope = ( SUMx*SUMy - cnt*SUMxy ) / ( SUMx*SUMx - cnt*SUMxx );
3925 y_intercept = ( SUMy - slope*SUMx ) / cnt;
3926 correl = (SUMxy - (SUMx*SUMy)/cnt) / sqrt((SUMxx - (SUMx*SUMx)/cnt)*(SUMyy - (SUMy*SUMy)/cnt));
3928 if (cnt) {
3929 if (dst->vf.op == VDEF_LSLSLOPE) {
3930 dst->vf.val = slope;
3931 dst->vf.when = 0;
3932 } else if (dst->vf.op == VDEF_LSLINT) {
3933 dst->vf.val = y_intercept;
3934 dst->vf.when = 0;
3935 } else if (dst->vf.op == VDEF_LSLCORREL) {
3936 dst->vf.val = correl;
3937 dst->vf.when = 0;
3938 };
3940 } else {
3941 dst->vf.val = DNAN;
3942 dst->vf.when = 0;
3943 }
3944 }
3945 break;
3946 }
3947 return 0;
3948 }
3950 /* NaN < -INF < finite_values < INF */
3951 int
3952 vdef_percent_compar(a,b)
3953 const void *a,*b;
3954 {
3955 /* Equality is not returned; this doesn't hurt except
3956 * (maybe) for a little performance.
3957 */
3959 /* First catch NaN values. They are smallest */
3960 if (isnan( *(double *)a )) return -1;
3961 if (isnan( *(double *)b )) return 1;
3963 /* NaN doesn't reach this part so INF and -INF are extremes.
3964 * The sign from isinf() is compatible with the sign we return
3965 */
3966 if (isinf( *(double *)a )) return isinf( *(double *)a );
3967 if (isinf( *(double *)b )) return isinf( *(double *)b );
3969 /* If we reach this, both values must be finite */
3970 if ( *(double *)a < *(double *)b ) return -1; else return 1;
3971 }