0f6debdb5f8e4122c2a16ba61d7023d53c984e6b
1 /****************************************************************************
2 * RRDtool 1.2.22 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 if (im->maxval > 0.0)
501 im->maxval = im->minval + new_range;
502 else
503 im->minval = im->maxval - new_range;
504 ytr(im,DNAN); /* reset precalc */
505 /* make sure first minor gridline is on integer pixel y coord */
506 minor_y = gridstep * floor(im->minval / gridstep);
507 while (minor_y < im->minval)
508 minor_y += gridstep;
509 minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
510 minor_y_px_frac = minor_y_px - floor(minor_y_px);
511 if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
512 double yfrac = minor_y_px_frac / im->ysize;
513 double range = im->maxval - im->minval;
514 im->minval = im->minval - yfrac * range;
515 im->maxval = im->maxval - yfrac * range;
516 ytr(im,DNAN); /* reset precalc */
517 }
518 calc_horizontal_grid(im); /* recalc with changed im->maxval */
519 }
520 }
522 /* reduce data reimplementation by Alex */
524 void
525 reduce_data(
526 enum cf_en cf, /* which consolidation function ?*/
527 unsigned long cur_step, /* step the data currently is in */
528 time_t *start, /* start, end and step as requested ... */
529 time_t *end, /* ... by the application will be ... */
530 unsigned long *step, /* ... adjusted to represent reality */
531 unsigned long *ds_cnt, /* number of data sources in file */
532 rrd_value_t **data) /* two dimensional array containing the data */
533 {
534 int i,reduce_factor = ceil((double)(*step) / (double)cur_step);
535 unsigned long col,dst_row,row_cnt,start_offset,end_offset,skiprows=0;
536 rrd_value_t *srcptr,*dstptr;
538 (*step) = cur_step*reduce_factor; /* set new step size for reduced data */
539 dstptr = *data;
540 srcptr = *data;
541 row_cnt = ((*end)-(*start))/cur_step;
543 #ifdef DEBUG
544 #define DEBUG_REDUCE
545 #endif
546 #ifdef DEBUG_REDUCE
547 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
548 row_cnt,reduce_factor,*start,*end,cur_step);
549 for (col=0;col<row_cnt;col++) {
550 printf("time %10lu: ",*start+(col+1)*cur_step);
551 for (i=0;i<*ds_cnt;i++)
552 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
553 printf("\n");
554 }
555 #endif
557 /* We have to combine [reduce_factor] rows of the source
558 ** into one row for the destination. Doing this we also
559 ** need to take care to combine the correct rows. First
560 ** alter the start and end time so that they are multiples
561 ** of the new step time. We cannot reduce the amount of
562 ** time so we have to move the end towards the future and
563 ** the start towards the past.
564 */
565 end_offset = (*end) % (*step);
566 start_offset = (*start) % (*step);
568 /* If there is a start offset (which cannot be more than
569 ** one destination row), skip the appropriate number of
570 ** source rows and one destination row. The appropriate
571 ** number is what we do know (start_offset/cur_step) of
572 ** the new interval (*step/cur_step aka reduce_factor).
573 */
574 #ifdef DEBUG_REDUCE
575 printf("start_offset: %lu end_offset: %lu\n",start_offset,end_offset);
576 printf("row_cnt before: %lu\n",row_cnt);
577 #endif
578 if (start_offset) {
579 (*start) = (*start)-start_offset;
580 skiprows=reduce_factor-start_offset/cur_step;
581 srcptr+=skiprows* *ds_cnt;
582 for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
583 row_cnt-=skiprows;
584 }
585 #ifdef DEBUG_REDUCE
586 printf("row_cnt between: %lu\n",row_cnt);
587 #endif
589 /* At the end we have some rows that are not going to be
590 ** used, the amount is end_offset/cur_step
591 */
592 if (end_offset) {
593 (*end) = (*end)-end_offset+(*step);
594 skiprows = end_offset/cur_step;
595 row_cnt-=skiprows;
596 }
597 #ifdef DEBUG_REDUCE
598 printf("row_cnt after: %lu\n",row_cnt);
599 #endif
601 /* Sanity check: row_cnt should be multiple of reduce_factor */
602 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
604 if (row_cnt%reduce_factor) {
605 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
606 row_cnt,reduce_factor);
607 printf("BUG in reduce_data()\n");
608 exit(1);
609 }
611 /* Now combine reduce_factor intervals at a time
612 ** into one interval for the destination.
613 */
615 for (dst_row=0;(long int)row_cnt>=reduce_factor;dst_row++) {
616 for (col=0;col<(*ds_cnt);col++) {
617 rrd_value_t newval=DNAN;
618 unsigned long validval=0;
620 for (i=0;i<reduce_factor;i++) {
621 if (isnan(srcptr[i*(*ds_cnt)+col])) {
622 continue;
623 }
624 validval++;
625 if (isnan(newval)) newval = srcptr[i*(*ds_cnt)+col];
626 else {
627 switch (cf) {
628 case CF_HWPREDICT:
629 case CF_DEVSEASONAL:
630 case CF_DEVPREDICT:
631 case CF_SEASONAL:
632 case CF_AVERAGE:
633 newval += srcptr[i*(*ds_cnt)+col];
634 break;
635 case CF_MINIMUM:
636 newval = min (newval,srcptr[i*(*ds_cnt)+col]);
637 break;
638 case CF_FAILURES:
639 /* an interval contains a failure if any subintervals contained a failure */
640 case CF_MAXIMUM:
641 newval = max (newval,srcptr[i*(*ds_cnt)+col]);
642 break;
643 case CF_LAST:
644 newval = srcptr[i*(*ds_cnt)+col];
645 break;
646 }
647 }
648 }
649 if (validval == 0){newval = DNAN;} else{
650 switch (cf) {
651 case CF_HWPREDICT:
652 case CF_DEVSEASONAL:
653 case CF_DEVPREDICT:
654 case CF_SEASONAL:
655 case CF_AVERAGE:
656 newval /= validval;
657 break;
658 case CF_MINIMUM:
659 case CF_FAILURES:
660 case CF_MAXIMUM:
661 case CF_LAST:
662 break;
663 }
664 }
665 *dstptr++=newval;
666 }
667 srcptr+=(*ds_cnt)*reduce_factor;
668 row_cnt-=reduce_factor;
669 }
670 /* If we had to alter the endtime, we didn't have enough
671 ** source rows to fill the last row. Fill it with NaN.
672 */
673 if (end_offset) for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
674 #ifdef DEBUG_REDUCE
675 row_cnt = ((*end)-(*start))/ *step;
676 srcptr = *data;
677 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
678 row_cnt,*start,*end,*step);
679 for (col=0;col<row_cnt;col++) {
680 printf("time %10lu: ",*start+(col+1)*(*step));
681 for (i=0;i<*ds_cnt;i++)
682 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
683 printf("\n");
684 }
685 #endif
686 }
689 /* get the data required for the graphs from the
690 relevant rrds ... */
692 int
693 data_fetch(image_desc_t *im )
694 {
695 int i,ii;
696 int skip;
698 /* pull the data from the rrd files ... */
699 for (i=0;i< (int)im->gdes_c;i++){
700 /* only GF_DEF elements fetch data */
701 if (im->gdes[i].gf != GF_DEF)
702 continue;
704 skip=0;
705 /* do we have it already ?*/
706 for (ii=0;ii<i;ii++) {
707 if (im->gdes[ii].gf != GF_DEF)
708 continue;
709 if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
710 && (im->gdes[i].cf == im->gdes[ii].cf)
711 && (im->gdes[i].cf_reduce == im->gdes[ii].cf_reduce)
712 && (im->gdes[i].start_orig == im->gdes[ii].start_orig)
713 && (im->gdes[i].end_orig == im->gdes[ii].end_orig)
714 && (im->gdes[i].step_orig == im->gdes[ii].step_orig)) {
715 /* OK, the data is already there.
716 ** Just copy the header portion
717 */
718 im->gdes[i].start = im->gdes[ii].start;
719 im->gdes[i].end = im->gdes[ii].end;
720 im->gdes[i].step = im->gdes[ii].step;
721 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
722 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
723 im->gdes[i].data = im->gdes[ii].data;
724 im->gdes[i].data_first = 0;
725 skip=1;
726 }
727 if (skip)
728 break;
729 }
730 if (! skip) {
731 unsigned long ft_step = im->gdes[i].step ; /* ft_step will record what we got from fetch */
733 if((rrd_fetch_fn(im->gdes[i].rrd,
734 im->gdes[i].cf,
735 &im->gdes[i].start,
736 &im->gdes[i].end,
737 &ft_step,
738 &im->gdes[i].ds_cnt,
739 &im->gdes[i].ds_namv,
740 &im->gdes[i].data)) == -1){
741 return -1;
742 }
743 im->gdes[i].data_first = 1;
745 if (ft_step < im->gdes[i].step) {
746 reduce_data(im->gdes[i].cf_reduce,
747 ft_step,
748 &im->gdes[i].start,
749 &im->gdes[i].end,
750 &im->gdes[i].step,
751 &im->gdes[i].ds_cnt,
752 &im->gdes[i].data);
753 } else {
754 im->gdes[i].step = ft_step;
755 }
756 }
758 /* lets see if the required data source is really there */
759 for(ii=0;ii<(int)im->gdes[i].ds_cnt;ii++){
760 if(strcmp(im->gdes[i].ds_namv[ii],im->gdes[i].ds_nam) == 0){
761 im->gdes[i].ds=ii; }
762 }
763 if (im->gdes[i].ds== -1){
764 rrd_set_error("No DS called '%s' in '%s'",
765 im->gdes[i].ds_nam,im->gdes[i].rrd);
766 return -1;
767 }
769 }
770 return 0;
771 }
773 /* evaluate the expressions in the CDEF functions */
775 /*************************************************************
776 * CDEF stuff
777 *************************************************************/
779 long
780 find_var_wrapper(void *arg1, char *key)
781 {
782 return find_var((image_desc_t *) arg1, key);
783 }
785 /* find gdes containing var*/
786 long
787 find_var(image_desc_t *im, char *key){
788 long ii;
789 for(ii=0;ii<im->gdes_c-1;ii++){
790 if((im->gdes[ii].gf == GF_DEF
791 || im->gdes[ii].gf == GF_VDEF
792 || im->gdes[ii].gf == GF_CDEF)
793 && (strcmp(im->gdes[ii].vname,key) == 0)){
794 return ii;
795 }
796 }
797 return -1;
798 }
800 /* find the largest common denominator for all the numbers
801 in the 0 terminated num array */
802 long
803 lcd(long *num){
804 long rest;
805 int i;
806 for (i=0;num[i+1]!=0;i++){
807 do {
808 rest=num[i] % num[i+1];
809 num[i]=num[i+1]; num[i+1]=rest;
810 } while (rest!=0);
811 num[i+1] = num[i];
812 }
813 /* return i==0?num[i]:num[i-1]; */
814 return num[i];
815 }
817 /* run the rpn calculator on all the VDEF and CDEF arguments */
818 int
819 data_calc( image_desc_t *im){
821 int gdi;
822 int dataidx;
823 long *steparray, rpi;
824 int stepcnt;
825 time_t now;
826 rpnstack_t rpnstack;
828 rpnstack_init(&rpnstack);
830 for (gdi=0;gdi<im->gdes_c;gdi++){
831 /* Look for GF_VDEF and GF_CDEF in the same loop,
832 * so CDEFs can use VDEFs and vice versa
833 */
834 switch (im->gdes[gdi].gf) {
835 case GF_XPORT:
836 break;
837 case GF_SHIFT: {
838 graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx];
840 /* remove current shift */
841 vdp->start -= vdp->shift;
842 vdp->end -= vdp->shift;
844 /* vdef */
845 if (im->gdes[gdi].shidx >= 0)
846 vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
847 /* constant */
848 else
849 vdp->shift = im->gdes[gdi].shval;
851 /* normalize shift to multiple of consolidated step */
852 vdp->shift = (vdp->shift / (long)vdp->step) * (long)vdp->step;
854 /* apply shift */
855 vdp->start += vdp->shift;
856 vdp->end += vdp->shift;
857 break;
858 }
859 case GF_VDEF:
860 /* A VDEF has no DS. This also signals other parts
861 * of rrdtool that this is a VDEF value, not a CDEF.
862 */
863 im->gdes[gdi].ds_cnt = 0;
864 if (vdef_calc(im,gdi)) {
865 rrd_set_error("Error processing VDEF '%s'"
866 ,im->gdes[gdi].vname
867 );
868 rpnstack_free(&rpnstack);
869 return -1;
870 }
871 break;
872 case GF_CDEF:
873 im->gdes[gdi].ds_cnt = 1;
874 im->gdes[gdi].ds = 0;
875 im->gdes[gdi].data_first = 1;
876 im->gdes[gdi].start = 0;
877 im->gdes[gdi].end = 0;
878 steparray=NULL;
879 stepcnt = 0;
880 dataidx=-1;
882 /* Find the variables in the expression.
883 * - VDEF variables are substituted by their values
884 * and the opcode is changed into OP_NUMBER.
885 * - CDEF variables are analized for their step size,
886 * the lowest common denominator of all the step
887 * sizes of the data sources involved is calculated
888 * and the resulting number is the step size for the
889 * resulting data source.
890 */
891 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
892 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
893 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER){
894 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
895 if (im->gdes[ptr].ds_cnt == 0) { /* this is a VDEF data source */
896 #if 0
897 printf("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
898 im->gdes[gdi].vname,
899 im->gdes[ptr].vname);
900 printf("DEBUG: value from vdef is %f\n",im->gdes[ptr].vf.val);
901 #endif
902 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
903 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
904 } else { /* normal variables and PREF(variables) */
906 /* add one entry to the array that keeps track of the step sizes of the
907 * data sources going into the CDEF. */
908 if ((steparray =
909 rrd_realloc(steparray,
910 (++stepcnt+1)*sizeof(*steparray)))==NULL){
911 rrd_set_error("realloc steparray");
912 rpnstack_free(&rpnstack);
913 return -1;
914 };
916 steparray[stepcnt-1] = im->gdes[ptr].step;
918 /* adjust start and end of cdef (gdi) so
919 * that it runs from the latest start point
920 * to the earliest endpoint of any of the
921 * rras involved (ptr)
922 */
924 if(im->gdes[gdi].start < im->gdes[ptr].start)
925 im->gdes[gdi].start = im->gdes[ptr].start;
927 if(im->gdes[gdi].end == 0 ||
928 im->gdes[gdi].end > im->gdes[ptr].end)
929 im->gdes[gdi].end = im->gdes[ptr].end;
931 /* store pointer to the first element of
932 * the rra providing data for variable,
933 * further save step size and data source
934 * count of this rra
935 */
936 im->gdes[gdi].rpnp[rpi].data = im->gdes[ptr].data + im->gdes[ptr].ds;
937 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
938 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
940 /* backoff the *.data ptr; this is done so
941 * rpncalc() function doesn't have to treat
942 * the first case differently
943 */
944 } /* if ds_cnt != 0 */
945 } /* if OP_VARIABLE */
946 } /* loop through all rpi */
948 /* move the data pointers to the correct period */
949 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
950 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
951 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER){
952 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
953 long diff = im->gdes[gdi].start - im->gdes[ptr].start;
955 if(diff > 0)
956 im->gdes[gdi].rpnp[rpi].data += (diff / im->gdes[ptr].step) * im->gdes[ptr].ds_cnt;
957 }
958 }
960 if(steparray == NULL){
961 rrd_set_error("rpn expressions without DEF"
962 " or CDEF variables are not supported");
963 rpnstack_free(&rpnstack);
964 return -1;
965 }
966 steparray[stepcnt]=0;
967 /* Now find the resulting step. All steps in all
968 * used RRAs have to be visited
969 */
970 im->gdes[gdi].step = lcd(steparray);
971 free(steparray);
972 if((im->gdes[gdi].data = malloc((
973 (im->gdes[gdi].end-im->gdes[gdi].start)
974 / im->gdes[gdi].step)
975 * sizeof(double)))==NULL){
976 rrd_set_error("malloc im->gdes[gdi].data");
977 rpnstack_free(&rpnstack);
978 return -1;
979 }
981 /* Step through the new cdef results array and
982 * calculate the values
983 */
984 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
985 now<=im->gdes[gdi].end;
986 now += im->gdes[gdi].step)
987 {
988 rpnp_t *rpnp = im -> gdes[gdi].rpnp;
990 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
991 * in this case we are advancing by timesteps;
992 * we use the fact that time_t is a synonym for long
993 */
994 if (rpn_calc(rpnp,&rpnstack,(long) now,
995 im->gdes[gdi].data,++dataidx) == -1) {
996 /* rpn_calc sets the error string */
997 rpnstack_free(&rpnstack);
998 return -1;
999 }
1000 } /* enumerate over time steps within a CDEF */
1001 break;
1002 default:
1003 continue;
1004 }
1005 } /* enumerate over CDEFs */
1006 rpnstack_free(&rpnstack);
1007 return 0;
1008 }
1010 /* massage data so, that we get one value for each x coordinate in the graph */
1011 int
1012 data_proc( image_desc_t *im ){
1013 long i,ii;
1014 double pixstep = (double)(im->end-im->start)
1015 /(double)im->xsize; /* how much time
1016 passes in one pixel */
1017 double paintval;
1018 double minval=DNAN,maxval=DNAN;
1020 unsigned long gr_time;
1022 /* memory for the processed data */
1023 for(i=0;i<im->gdes_c;i++) {
1024 if((im->gdes[i].gf==GF_LINE) ||
1025 (im->gdes[i].gf==GF_AREA) ||
1026 (im->gdes[i].gf==GF_TICK)) {
1027 if((im->gdes[i].p_data = malloc((im->xsize +1)
1028 * sizeof(rrd_value_t)))==NULL){
1029 rrd_set_error("malloc data_proc");
1030 return -1;
1031 }
1032 }
1033 }
1035 for (i=0;i<im->xsize;i++) { /* for each pixel */
1036 long vidx;
1037 gr_time = im->start+pixstep*i; /* time of the current step */
1038 paintval=0.0;
1040 for (ii=0;ii<im->gdes_c;ii++) {
1041 double value;
1042 switch (im->gdes[ii].gf) {
1043 case GF_LINE:
1044 case GF_AREA:
1045 case GF_TICK:
1046 if (!im->gdes[ii].stack)
1047 paintval = 0.0;
1048 value = im->gdes[ii].yrule;
1049 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1050 /* The time of the data doesn't necessarily match
1051 ** the time of the graph. Beware.
1052 */
1053 vidx = im->gdes[ii].vidx;
1054 if (im->gdes[vidx].gf == GF_VDEF) {
1055 value = im->gdes[vidx].vf.val;
1056 } else if (((long int)gr_time >= (long int)im->gdes[vidx].start) &&
1057 ((long int)gr_time <= (long int)im->gdes[vidx].end) ) {
1058 value = im->gdes[vidx].data[
1059 (unsigned long) floor(
1060 (double)(gr_time - im->gdes[vidx].start)
1061 / im->gdes[vidx].step)
1062 * im->gdes[vidx].ds_cnt
1063 + im->gdes[vidx].ds
1064 ];
1065 } else {
1066 value = DNAN;
1067 }
1068 };
1070 if (! isnan(value)) {
1071 paintval += value;
1072 im->gdes[ii].p_data[i] = paintval;
1073 /* GF_TICK: the data values are not
1074 ** relevant for min and max
1075 */
1076 if (finite(paintval) && im->gdes[ii].gf != GF_TICK ) {
1077 if ((isnan(minval) || paintval < minval ) &&
1078 ! (im->logarithmic && paintval <= 0.0))
1079 minval = paintval;
1080 if (isnan(maxval) || paintval > maxval)
1081 maxval = paintval;
1082 }
1083 } else {
1084 im->gdes[ii].p_data[i] = DNAN;
1085 }
1086 break;
1087 case GF_STACK:
1088 rrd_set_error("STACK should already be turned into LINE or AREA here");
1089 return -1;
1090 break;
1091 default:
1092 break;
1093 }
1094 }
1095 }
1097 /* if min or max have not been asigned a value this is because
1098 there was no data in the graph ... this is not good ...
1099 lets set these to dummy values then ... */
1101 if (im->logarithmic) {
1102 if (isnan(minval)) minval = 0.2;
1103 if (isnan(maxval)) maxval = 5.1;
1104 }
1105 else {
1106 if (isnan(minval)) minval = 0.0;
1107 if (isnan(maxval)) maxval = 1.0;
1108 }
1110 /* adjust min and max values */
1111 if (isnan(im->minval)
1112 /* don't adjust low-end with log scale */ /* why not? */
1113 || ((!im->rigid) && im->minval > minval)
1114 ) {
1115 if (im->logarithmic)
1116 im->minval = minval * 0.5;
1117 else
1118 im->minval = minval;
1119 }
1120 if (isnan(im->maxval)
1121 || (!im->rigid && im->maxval < maxval)
1122 ) {
1123 if (im->logarithmic)
1124 im->maxval = maxval * 2.0;
1125 else
1126 im->maxval = maxval;
1127 }
1128 /* make sure min is smaller than max */
1129 if (im->minval > im->maxval) {
1130 im->minval = 0.99 * im->maxval;
1131 }
1133 /* make sure min and max are not equal */
1134 if (im->minval == im->maxval) {
1135 im->maxval *= 1.01;
1136 if (! im->logarithmic) {
1137 im->minval *= 0.99;
1138 }
1139 /* make sure min and max are not both zero */
1140 if (im->maxval == 0.0) {
1141 im->maxval = 1.0;
1142 }
1143 }
1144 return 0;
1145 }
1149 /* identify the point where the first gridline, label ... gets placed */
1151 time_t
1152 find_first_time(
1153 time_t start, /* what is the initial time */
1154 enum tmt_en baseint, /* what is the basic interval */
1155 long basestep /* how many if these do we jump a time */
1156 )
1157 {
1158 struct tm tm;
1159 localtime_r(&start, &tm);
1160 switch(baseint){
1161 case TMT_SECOND:
1162 tm.tm_sec -= tm.tm_sec % basestep; break;
1163 case TMT_MINUTE:
1164 tm.tm_sec=0;
1165 tm.tm_min -= tm.tm_min % basestep;
1166 break;
1167 case TMT_HOUR:
1168 tm.tm_sec=0;
1169 tm.tm_min = 0;
1170 tm.tm_hour -= tm.tm_hour % basestep; break;
1171 case TMT_DAY:
1172 /* we do NOT look at the basestep for this ... */
1173 tm.tm_sec=0;
1174 tm.tm_min = 0;
1175 tm.tm_hour = 0; break;
1176 case TMT_WEEK:
1177 /* we do NOT look at the basestep for this ... */
1178 tm.tm_sec=0;
1179 tm.tm_min = 0;
1180 tm.tm_hour = 0;
1181 tm.tm_mday -= tm.tm_wday -1; /* -1 because we want the monday */
1182 if (tm.tm_wday==0) tm.tm_mday -= 7; /* we want the *previous* monday */
1183 break;
1184 case TMT_MONTH:
1185 tm.tm_sec=0;
1186 tm.tm_min = 0;
1187 tm.tm_hour = 0;
1188 tm.tm_mday = 1;
1189 tm.tm_mon -= tm.tm_mon % basestep; break;
1191 case TMT_YEAR:
1192 tm.tm_sec=0;
1193 tm.tm_min = 0;
1194 tm.tm_hour = 0;
1195 tm.tm_mday = 1;
1196 tm.tm_mon = 0;
1197 tm.tm_year -= (tm.tm_year+1900) % basestep;
1199 }
1200 return mktime(&tm);
1201 }
1202 /* identify the point where the next gridline, label ... gets placed */
1203 time_t
1204 find_next_time(
1205 time_t current, /* what is the initial time */
1206 enum tmt_en baseint, /* what is the basic interval */
1207 long basestep /* how many if these do we jump a time */
1208 )
1209 {
1210 struct tm tm;
1211 time_t madetime;
1212 localtime_r(¤t, &tm);
1213 do {
1214 switch(baseint){
1215 case TMT_SECOND:
1216 tm.tm_sec += basestep; break;
1217 case TMT_MINUTE:
1218 tm.tm_min += basestep; break;
1219 case TMT_HOUR:
1220 tm.tm_hour += basestep; break;
1221 case TMT_DAY:
1222 tm.tm_mday += basestep; break;
1223 case TMT_WEEK:
1224 tm.tm_mday += 7*basestep; break;
1225 case TMT_MONTH:
1226 tm.tm_mon += basestep; break;
1227 case TMT_YEAR:
1228 tm.tm_year += basestep;
1229 }
1230 madetime = mktime(&tm);
1231 } while (madetime == -1); /* this is necessary to skip impssible times
1232 like the daylight saving time skips */
1233 return madetime;
1235 }
1238 /* calculate values required for PRINT and GPRINT functions */
1240 int
1241 print_calc(image_desc_t *im, char ***prdata)
1242 {
1243 long i,ii,validsteps;
1244 double printval;
1245 struct tm tmvdef;
1246 int graphelement = 0;
1247 long vidx;
1248 int max_ii;
1249 double magfact = -1;
1250 char *si_symb = "";
1251 char *percent_s;
1252 int prlines = 1;
1253 /* wow initializing tmvdef is quite a task :-) */
1254 time_t now = time(NULL);
1255 localtime_r(&now,&tmvdef);
1256 if (im->imginfo) prlines++;
1257 for(i=0;i<im->gdes_c;i++){
1258 vidx = im->gdes[i].vidx;
1259 switch(im->gdes[i].gf){
1260 case GF_PRINT:
1261 prlines++;
1262 if(((*prdata) = rrd_realloc((*prdata),prlines*sizeof(char *)))==NULL){
1263 rrd_set_error("realloc prdata");
1264 return 0;
1265 }
1266 case GF_GPRINT:
1267 /* PRINT and GPRINT can now print VDEF generated values.
1268 * There's no need to do any calculations on them as these
1269 * calculations were already made.
1270 */
1271 if (im->gdes[vidx].gf==GF_VDEF) { /* simply use vals */
1272 printval = im->gdes[vidx].vf.val;
1273 localtime_r(&im->gdes[vidx].vf.when,&tmvdef);
1274 } else { /* need to calculate max,min,avg etcetera */
1275 max_ii =((im->gdes[vidx].end
1276 - im->gdes[vidx].start)
1277 / im->gdes[vidx].step
1278 * im->gdes[vidx].ds_cnt);
1279 printval = DNAN;
1280 validsteps = 0;
1281 for( ii=im->gdes[vidx].ds;
1282 ii < max_ii;
1283 ii+=im->gdes[vidx].ds_cnt){
1284 if (! finite(im->gdes[vidx].data[ii]))
1285 continue;
1286 if (isnan(printval)){
1287 printval = im->gdes[vidx].data[ii];
1288 validsteps++;
1289 continue;
1290 }
1292 switch (im->gdes[i].cf){
1293 case CF_HWPREDICT:
1294 case CF_DEVPREDICT:
1295 case CF_DEVSEASONAL:
1296 case CF_SEASONAL:
1297 case CF_AVERAGE:
1298 validsteps++;
1299 printval += im->gdes[vidx].data[ii];
1300 break;
1301 case CF_MINIMUM:
1302 printval = min( printval, im->gdes[vidx].data[ii]);
1303 break;
1304 case CF_FAILURES:
1305 case CF_MAXIMUM:
1306 printval = max( printval, im->gdes[vidx].data[ii]);
1307 break;
1308 case CF_LAST:
1309 printval = im->gdes[vidx].data[ii];
1310 }
1311 }
1312 if (im->gdes[i].cf==CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1313 if (validsteps > 1) {
1314 printval = (printval / validsteps);
1315 }
1316 }
1317 } /* prepare printval */
1319 if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) {
1320 /* Magfact is set to -1 upon entry to print_calc. If it
1321 * is still less than 0, then we need to run auto_scale.
1322 * Otherwise, put the value into the correct units. If
1323 * the value is 0, then do not set the symbol or magnification
1324 * so next the calculation will be performed again. */
1325 if (magfact < 0.0) {
1326 auto_scale(im,&printval,&si_symb,&magfact);
1327 if (printval == 0.0)
1328 magfact = -1.0;
1329 } else {
1330 printval /= magfact;
1331 }
1332 *(++percent_s) = 's';
1333 } else if (strstr(im->gdes[i].format,"%s") != NULL) {
1334 auto_scale(im,&printval,&si_symb,&magfact);
1335 }
1337 if (im->gdes[i].gf == GF_PRINT){
1338 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1339 (*prdata)[prlines-1] = NULL;
1340 if (im->gdes[i].strftm){
1341 strftime((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,&tmvdef);
1342 } else {
1343 if (bad_format(im->gdes[i].format)) {
1344 rrd_set_error("bad format for PRINT in '%s'", im->gdes[i].format);
1345 return -1;
1346 }
1348 #ifdef HAVE_SNPRINTF
1349 snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
1350 #else
1351 sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
1352 #endif
1353 }
1354 } else {
1355 /* GF_GPRINT */
1357 if (im->gdes[i].strftm){
1358 strftime(im->gdes[i].legend,FMT_LEG_LEN,im->gdes[i].format,&tmvdef);
1359 } else {
1360 if (bad_format(im->gdes[i].format)) {
1361 rrd_set_error("bad format for GPRINT in '%s'", im->gdes[i].format);
1362 return -1;
1363 }
1364 #ifdef HAVE_SNPRINTF
1365 snprintf(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
1366 #else
1367 sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
1368 #endif
1369 }
1370 graphelement = 1;
1371 }
1372 break;
1373 case GF_LINE:
1374 case GF_AREA:
1375 case GF_TICK:
1376 graphelement = 1;
1377 break;
1378 case GF_HRULE:
1379 if(isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1380 im->gdes[i].yrule=im->gdes[vidx].vf.val;
1381 };
1382 graphelement = 1;
1383 break;
1384 case GF_VRULE:
1385 if(im->gdes[i].xrule == 0) { /* again ... the legend printer needs it*/
1386 im->gdes[i].xrule = im->gdes[vidx].vf.when;
1387 };
1388 graphelement = 1;
1389 break;
1390 case GF_COMMENT:
1391 case GF_DEF:
1392 case GF_CDEF:
1393 case GF_VDEF:
1394 #ifdef WITH_PIECHART
1395 case GF_PART:
1396 #endif
1397 case GF_SHIFT:
1398 case GF_XPORT:
1399 break;
1400 case GF_STACK:
1401 rrd_set_error("STACK should already be turned into LINE or AREA here");
1402 return -1;
1403 break;
1404 }
1405 }
1406 return graphelement;
1407 }
1410 /* place legends with color spots */
1411 int
1412 leg_place(image_desc_t *im)
1413 {
1414 /* graph labels */
1415 int interleg = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1416 int border = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1417 int fill=0, fill_last;
1418 int leg_c = 0;
1419 int leg_x = border, leg_y = im->yimg;
1420 int leg_y_prev = im->yimg;
1421 int leg_cc;
1422 int glue = 0;
1423 int i,ii, mark = 0;
1424 char prt_fctn; /*special printfunctions */
1425 int *legspace;
1427 if( !(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH) ) {
1428 if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1429 rrd_set_error("malloc for legspace");
1430 return -1;
1431 }
1433 for(i=0;i<im->gdes_c;i++){
1434 fill_last = fill;
1436 /* hid legends for rules which are not displayed */
1438 if(!(im->extra_flags & FORCE_RULES_LEGEND)) {
1439 if (im->gdes[i].gf == GF_HRULE &&
1440 (im->gdes[i].yrule < im->minval || im->gdes[i].yrule > im->maxval))
1441 im->gdes[i].legend[0] = '\0';
1443 if (im->gdes[i].gf == GF_VRULE &&
1444 (im->gdes[i].xrule < im->start || im->gdes[i].xrule > im->end))
1445 im->gdes[i].legend[0] = '\0';
1446 }
1448 leg_cc = strlen(im->gdes[i].legend);
1450 /* is there a controle code ant the end of the legend string ? */
1451 /* and it is not a tab \\t */
1452 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\' && im->gdes[i].legend[leg_cc-1] != 't') {
1453 prt_fctn = im->gdes[i].legend[leg_cc-1];
1454 leg_cc -= 2;
1455 im->gdes[i].legend[leg_cc] = '\0';
1456 } else {
1457 prt_fctn = '\0';
1458 }
1459 /* only valid control codes */
1460 if (prt_fctn != 'l' &&
1461 prt_fctn != 'n' && /* a synonym for l */
1462 prt_fctn != 'r' &&
1463 prt_fctn != 'j' &&
1464 prt_fctn != 'c' &&
1465 prt_fctn != 's' &&
1466 prt_fctn != 't' &&
1467 prt_fctn != '\0' &&
1468 prt_fctn != 'g' ) {
1469 free(legspace);
1470 rrd_set_error("Unknown control code at the end of '%s\\%c'",im->gdes[i].legend,prt_fctn);
1471 return -1;
1473 }
1475 /* remove exess space */
1476 if ( prt_fctn == 'n' ){
1477 prt_fctn='l';
1478 }
1480 while (prt_fctn=='g' &&
1481 leg_cc > 0 &&
1482 im->gdes[i].legend[leg_cc-1]==' '){
1483 leg_cc--;
1484 im->gdes[i].legend[leg_cc]='\0';
1485 }
1486 if (leg_cc != 0 ){
1487 legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1489 if (fill > 0){
1490 /* no interleg space if string ends in \g */
1491 fill += legspace[i];
1492 }
1493 fill += gfx_get_text_width(im->canvas, fill+border,
1494 im->text_prop[TEXT_PROP_LEGEND].font,
1495 im->text_prop[TEXT_PROP_LEGEND].size,
1496 im->tabwidth,
1497 im->gdes[i].legend, 0);
1498 leg_c++;
1499 } else {
1500 legspace[i]=0;
1501 }
1502 /* who said there was a special tag ... ?*/
1503 if (prt_fctn=='g') {
1504 prt_fctn = '\0';
1505 }
1506 if (prt_fctn == '\0') {
1507 if (i == im->gdes_c -1 ) prt_fctn ='l';
1509 /* is it time to place the legends ? */
1510 if (fill > im->ximg - 2*border){
1511 if (leg_c > 1) {
1512 /* go back one */
1513 i--;
1514 fill = fill_last;
1515 leg_c--;
1516 prt_fctn = 'j';
1517 } else {
1518 prt_fctn = 'l';
1519 }
1521 }
1522 }
1525 if (prt_fctn != '\0'){
1526 leg_x = border;
1527 if (leg_c >= 2 && prt_fctn == 'j') {
1528 glue = (im->ximg - fill - 2* border) / (leg_c-1);
1529 } else {
1530 glue = 0;
1531 }
1532 if (prt_fctn =='c') leg_x = (im->ximg - fill) / 2.0;
1533 if (prt_fctn =='r') leg_x = im->ximg - fill - border;
1535 for(ii=mark;ii<=i;ii++){
1536 if(im->gdes[ii].legend[0]=='\0')
1537 continue; /* skip empty legends */
1538 im->gdes[ii].leg_x = leg_x;
1539 im->gdes[ii].leg_y = leg_y;
1540 leg_x +=
1541 gfx_get_text_width(im->canvas, leg_x,
1542 im->text_prop[TEXT_PROP_LEGEND].font,
1543 im->text_prop[TEXT_PROP_LEGEND].size,
1544 im->tabwidth,
1545 im->gdes[ii].legend, 0)
1546 + legspace[ii]
1547 + glue;
1548 }
1549 leg_y_prev = leg_y;
1550 /* only add y space if there was text on the line */
1551 if (leg_x > border || prt_fctn == 's')
1552 leg_y += im->text_prop[TEXT_PROP_LEGEND].size*1.8;
1553 if (prt_fctn == 's')
1554 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1555 fill = 0;
1556 leg_c = 0;
1557 mark = ii;
1558 }
1559 }
1560 im->yimg = leg_y_prev;
1561 /* if we did place some legends we have to add vertical space */
1562 if (leg_y != im->yimg){
1563 im->yimg += im->text_prop[TEXT_PROP_LEGEND].size*1.8;
1564 }
1565 free(legspace);
1566 }
1567 return 0;
1568 }
1570 /* create a grid on the graph. it determines what to do
1571 from the values of xsize, start and end */
1573 /* the xaxis labels are determined from the number of seconds per pixel
1574 in the requested graph */
1578 int
1579 calc_horizontal_grid(image_desc_t *im)
1580 {
1581 double range;
1582 double scaledrange;
1583 int pixel,i;
1584 int gridind=0;
1585 int decimals, fractionals;
1587 im->ygrid_scale.labfact=2;
1588 range = im->maxval - im->minval;
1589 scaledrange = range / im->magfact;
1591 /* does the scale of this graph make it impossible to put lines
1592 on it? If so, give up. */
1593 if (isnan(scaledrange)) {
1594 return 0;
1595 }
1597 /* find grid spaceing */
1598 pixel=1;
1599 if(isnan(im->ygridstep)){
1600 if(im->extra_flags & ALTYGRID) {
1601 /* find the value with max number of digits. Get number of digits */
1602 decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))*im->viewfactor/im->magfact));
1603 if(decimals <= 0) /* everything is small. make place for zero */
1604 decimals = 1;
1606 im->ygrid_scale.gridstep = pow((double)10, floor(log10(range*im->viewfactor/im->magfact)))/im->viewfactor*im->magfact;
1608 if(im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1609 im->ygrid_scale.gridstep = 0.1;
1610 /* should have at least 5 lines but no more then 15 */
1611 if(range/im->ygrid_scale.gridstep < 5)
1612 im->ygrid_scale.gridstep /= 10;
1613 if(range/im->ygrid_scale.gridstep > 15)
1614 im->ygrid_scale.gridstep *= 10;
1615 if(range/im->ygrid_scale.gridstep > 5) {
1616 im->ygrid_scale.labfact = 1;
1617 if(range/im->ygrid_scale.gridstep > 8)
1618 im->ygrid_scale.labfact = 2;
1619 }
1620 else {
1621 im->ygrid_scale.gridstep /= 5;
1622 im->ygrid_scale.labfact = 5;
1623 }
1624 fractionals = floor(log10(im->ygrid_scale.gridstep*(double)im->ygrid_scale.labfact*im->viewfactor/im->magfact));
1625 if(fractionals < 0) { /* small amplitude. */
1626 int len = decimals - fractionals + 1;
1627 if (im->unitslength < len+2) im->unitslength = len+2;
1628 sprintf(im->ygrid_scale.labfmt, "%%%d.%df%s", len, -fractionals,(im->symbol != ' ' ? " %c" : ""));
1629 } else {
1630 int len = decimals + 1;
1631 if (im->unitslength < len+2) im->unitslength = len+2;
1632 sprintf(im->ygrid_scale.labfmt, "%%%d.0f%s", len, ( im->symbol != ' ' ? " %c" : "" ));
1633 }
1634 }
1635 else {
1636 for(i=0;ylab[i].grid > 0;i++){
1637 pixel = im->ysize / (scaledrange / ylab[i].grid);
1638 gridind = i;
1639 if (pixel > 7)
1640 break;
1641 }
1643 for(i=0; i<4;i++) {
1644 if (pixel * ylab[gridind].lfac[i] >= 2.5 * im->text_prop[TEXT_PROP_AXIS].size) {
1645 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1646 break;
1647 }
1648 }
1650 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1651 }
1652 } else {
1653 im->ygrid_scale.gridstep = im->ygridstep;
1654 im->ygrid_scale.labfact = im->ylabfact;
1655 }
1656 return 1;
1657 }
1659 int draw_horizontal_grid(image_desc_t *im)
1660 {
1661 int i;
1662 double scaledstep;
1663 char graph_label[100];
1664 int nlabels=0;
1665 double X0=im->xorigin;
1666 double X1=im->xorigin+im->xsize;
1668 int sgrid = (int)( im->minval / im->ygrid_scale.gridstep - 1);
1669 int egrid = (int)( im->maxval / im->ygrid_scale.gridstep + 1);
1670 double MaxY;
1671 scaledstep = im->ygrid_scale.gridstep/(double)im->magfact*(double)im->viewfactor;
1672 MaxY = scaledstep*(double)egrid;
1673 for (i = sgrid; i <= egrid; i++){
1674 double Y0=ytr(im,im->ygrid_scale.gridstep*i);
1675 double YN=ytr(im,im->ygrid_scale.gridstep*(i+1));
1676 if ( Y0 >= im->yorigin-im->ysize
1677 && Y0 <= im->yorigin){
1678 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
1679 with the chosen settings. Add a label if required by settings, or if
1680 there is only one label so far and the next grid line is out of bounds. */
1681 if(i % im->ygrid_scale.labfact == 0 || ( nlabels==1 && (YN < im->yorigin-im->ysize || YN > im->yorigin) )){
1682 if (im->symbol == ' ') {
1683 if(im->extra_flags & ALTYGRID) {
1684 sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*(double)i);
1685 } else {
1686 if(MaxY < 10) {
1687 sprintf(graph_label,"%4.1f",scaledstep*(double)i);
1688 } else {
1689 sprintf(graph_label,"%4.0f",scaledstep*(double)i);
1690 }
1691 }
1692 }else {
1693 char sisym = ( i == 0 ? ' ' : im->symbol);
1694 if(im->extra_flags & ALTYGRID) {
1695 sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*(double)i,sisym);
1696 } else {
1697 if(MaxY < 10){
1698 sprintf(graph_label,"%4.1f %c",scaledstep*(double)i, sisym);
1699 } else {
1700 sprintf(graph_label,"%4.0f %c",scaledstep*(double)i, sisym);
1701 }
1702 }
1703 }
1704 nlabels++;
1706 gfx_new_text ( im->canvas,
1707 X0-im->text_prop[TEXT_PROP_AXIS].size, Y0,
1708 im->graph_col[GRC_FONT],
1709 im->text_prop[TEXT_PROP_AXIS].font,
1710 im->text_prop[TEXT_PROP_AXIS].size,
1711 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1712 graph_label );
1713 gfx_new_dashed_line ( im->canvas,
1714 X0-2,Y0,
1715 X1+2,Y0,
1716 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1717 im->grid_dash_on, im->grid_dash_off);
1719 } else if (!(im->extra_flags & NOMINOR)) {
1720 gfx_new_dashed_line ( im->canvas,
1721 X0-1,Y0,
1722 X1+1,Y0,
1723 GRIDWIDTH, im->graph_col[GRC_GRID],
1724 im->grid_dash_on, im->grid_dash_off);
1726 }
1727 }
1728 }
1729 return 1;
1730 }
1732 /* this is frexp for base 10 */
1733 double frexp10(double, double *);
1734 double frexp10(double x, double *e) {
1735 double mnt;
1736 int iexp;
1738 iexp = floor(log(fabs(x)) / log(10));
1739 mnt = x / pow(10.0, iexp);
1740 if(mnt >= 10.0) {
1741 iexp++;
1742 mnt = x / pow(10.0, iexp);
1743 }
1744 *e = iexp;
1745 return mnt;
1746 }
1748 static int AlmostEqual2sComplement (float A, float B, int maxUlps)
1749 {
1751 int aInt = *(int*)&A;
1752 int bInt = *(int*)&B;
1753 int intDiff;
1754 /* Make sure maxUlps is non-negative and small enough that the
1755 default NAN won't compare as equal to anything. */
1757 /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
1759 /* Make aInt lexicographically ordered as a twos-complement int */
1761 if (aInt < 0)
1762 aInt = 0x80000000l - aInt;
1764 /* Make bInt lexicographically ordered as a twos-complement int */
1766 if (bInt < 0)
1767 bInt = 0x80000000l - bInt;
1769 intDiff = abs(aInt - bInt);
1771 if (intDiff <= maxUlps)
1772 return 1;
1774 return 0;
1775 }
1777 /* logaritmic horizontal grid */
1778 int
1779 horizontal_log_grid(image_desc_t *im)
1780 {
1781 double yloglab[][10] = {
1782 {1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
1783 {1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
1784 {1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0},
1785 {1.0, 2.0, 4.0, 6.0, 8.0, 10., 0.0, 0.0, 0.0, 0.0},
1786 {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.},
1787 {0,0,0,0,0, 0,0,0,0,0} /* last line */ };
1789 int i, j, val_exp, min_exp;
1790 double nex; /* number of decades in data */
1791 double logscale; /* scale in logarithmic space */
1792 int exfrac = 1; /* decade spacing */
1793 int mid = -1; /* row in yloglab for major grid */
1794 double mspac; /* smallest major grid spacing (pixels) */
1795 int flab; /* first value in yloglab to use */
1796 double value, tmp, pre_value;
1797 double X0,X1,Y0;
1798 char graph_label[100];
1800 nex = log10(im->maxval / im->minval);
1801 logscale = im->ysize / nex;
1803 /* major spacing for data with high dynamic range */
1804 while(logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
1805 if(exfrac == 1) exfrac = 3;
1806 else exfrac += 3;
1807 }
1809 /* major spacing for less dynamic data */
1810 do {
1811 /* search best row in yloglab */
1812 mid++;
1813 for(i = 0; yloglab[mid][i + 1] < 10.0; i++);
1814 mspac = logscale * log10(10.0 / yloglab[mid][i]);
1815 } while(mspac > 2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0);
1816 if(mid) mid--;
1818 /* find first value in yloglab */
1819 for(flab = 0; yloglab[mid][flab] < 10 && frexp10(im->minval, &tmp) > yloglab[mid][flab] ; flab++);
1820 if(yloglab[mid][flab] == 10.0) {
1821 tmp += 1.0;
1822 flab = 0;
1823 }
1824 val_exp = tmp;
1825 if(val_exp % exfrac) val_exp += abs(-val_exp % exfrac);
1827 X0=im->xorigin;
1828 X1=im->xorigin+im->xsize;
1830 /* draw grid */
1831 pre_value = DNAN;
1832 while(1) {
1834 value = yloglab[mid][flab] * pow(10.0, val_exp);
1835 if ( AlmostEqual2sComplement(value,pre_value,4) ) break; /* it seems we are not converging */
1837 pre_value = value;
1839 Y0 = ytr(im, value);
1840 if(Y0 <= im->yorigin - im->ysize) break;
1842 /* major grid line */
1843 gfx_new_dashed_line ( im->canvas,
1844 X0-2,Y0,
1845 X1+2,Y0,
1846 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1847 im->grid_dash_on, im->grid_dash_off);
1849 /* label */
1850 if (im->extra_flags & FORCE_UNITS_SI) {
1851 int scale;
1852 double pvalue;
1853 char symbol;
1855 scale = floor(val_exp / 3.0);
1856 if( value >= 1.0 ) pvalue = pow(10.0, val_exp % 3);
1857 else pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
1858 pvalue *= yloglab[mid][flab];
1860 if ( ((scale+si_symbcenter) < (int)sizeof(si_symbol)) &&
1861 ((scale+si_symbcenter) >= 0) )
1862 symbol = si_symbol[scale+si_symbcenter];
1863 else
1864 symbol = '?';
1866 sprintf(graph_label,"%3.0f %c", pvalue, symbol);
1867 } else
1868 sprintf(graph_label,"%3.0e", value);
1869 gfx_new_text ( im->canvas,
1870 X0-im->text_prop[TEXT_PROP_AXIS].size, Y0,
1871 im->graph_col[GRC_FONT],
1872 im->text_prop[TEXT_PROP_AXIS].font,
1873 im->text_prop[TEXT_PROP_AXIS].size,
1874 im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1875 graph_label );
1877 /* minor grid */
1878 if(mid < 4 && exfrac == 1) {
1879 /* find first and last minor line behind current major line
1880 * i is the first line and j tha last */
1881 if(flab == 0) {
1882 min_exp = val_exp - 1;
1883 for(i = 1; yloglab[mid][i] < 10.0; i++);
1884 i = yloglab[mid][i - 1] + 1;
1885 j = 10;
1886 }
1887 else {
1888 min_exp = val_exp;
1889 i = yloglab[mid][flab - 1] + 1;
1890 j = yloglab[mid][flab];
1891 }
1893 /* draw minor lines below current major line */
1894 for(; i < j; i++) {
1896 value = i * pow(10.0, min_exp);
1897 if(value < im->minval) continue;
1899 Y0 = ytr(im, value);
1900 if(Y0 <= im->yorigin - im->ysize) break;
1902 /* draw lines */
1903 gfx_new_dashed_line ( im->canvas,
1904 X0-1,Y0,
1905 X1+1,Y0,
1906 GRIDWIDTH, im->graph_col[GRC_GRID],
1907 im->grid_dash_on, im->grid_dash_off);
1908 }
1909 }
1910 else if(exfrac > 1) {
1911 for(i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
1912 value = pow(10.0, i);
1913 if(value < im->minval) continue;
1915 Y0 = ytr(im, value);
1916 if(Y0 <= im->yorigin - im->ysize) break;
1918 /* draw lines */
1919 gfx_new_dashed_line ( im->canvas,
1920 X0-1,Y0,
1921 X1+1,Y0,
1922 GRIDWIDTH, im->graph_col[GRC_GRID],
1923 im->grid_dash_on, im->grid_dash_off);
1924 }
1925 }
1927 /* next decade */
1928 if(yloglab[mid][++flab] == 10.0) {
1929 flab = 0;
1930 val_exp += exfrac;
1931 }
1932 }
1934 /* draw minor lines after highest major line */
1935 if(mid < 4 && exfrac == 1) {
1936 /* find first and last minor line below current major line
1937 * i is the first line and j tha last */
1938 if(flab == 0) {
1939 min_exp = val_exp - 1;
1940 for(i = 1; yloglab[mid][i] < 10.0; i++);
1941 i = yloglab[mid][i - 1] + 1;
1942 j = 10;
1943 }
1944 else {
1945 min_exp = val_exp;
1946 i = yloglab[mid][flab - 1] + 1;
1947 j = yloglab[mid][flab];
1948 }
1950 /* draw minor lines below current major line */
1951 for(; i < j; i++) {
1953 value = i * pow(10.0, min_exp);
1954 if(value < im->minval) continue;
1956 Y0 = ytr(im, value);
1957 if(Y0 <= im->yorigin - im->ysize) break;
1959 /* draw lines */
1960 gfx_new_dashed_line ( im->canvas,
1961 X0-1,Y0,
1962 X1+1,Y0,
1963 GRIDWIDTH, im->graph_col[GRC_GRID],
1964 im->grid_dash_on, im->grid_dash_off);
1965 }
1966 }
1967 /* fancy minor gridlines */
1968 else if(exfrac > 1) {
1969 for(i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
1970 value = pow(10.0, i);
1971 if(value < im->minval) continue;
1973 Y0 = ytr(im, value);
1974 if(Y0 <= im->yorigin - im->ysize) break;
1976 /* draw lines */
1977 gfx_new_dashed_line ( im->canvas,
1978 X0-1,Y0,
1979 X1+1,Y0,
1980 GRIDWIDTH, im->graph_col[GRC_GRID],
1981 im->grid_dash_on, im->grid_dash_off);
1982 }
1983 }
1985 return 1;
1986 }
1989 void
1990 vertical_grid(
1991 image_desc_t *im )
1992 {
1993 int xlab_sel; /* which sort of label and grid ? */
1994 time_t ti, tilab, timajor;
1995 long factor;
1996 char graph_label[100];
1997 double X0,Y0,Y1; /* points for filled graph and more*/
1998 struct tm tm;
2000 /* the type of time grid is determined by finding
2001 the number of seconds per pixel in the graph */
2004 if(im->xlab_user.minsec == -1){
2005 factor=(im->end - im->start)/im->xsize;
2006 xlab_sel=0;
2007 while ( xlab[xlab_sel+1].minsec != -1
2008 && xlab[xlab_sel+1].minsec <= factor) { xlab_sel++; } /* pick the last one */
2009 while ( xlab[xlab_sel-1].minsec == xlab[xlab_sel].minsec
2010 && xlab[xlab_sel].length > (im->end - im->start)) { xlab_sel--; } /* go back to the smallest size */
2011 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2012 im->xlab_user.gridst = xlab[xlab_sel].gridst;
2013 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2014 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2015 im->xlab_user.labtm = xlab[xlab_sel].labtm;
2016 im->xlab_user.labst = xlab[xlab_sel].labst;
2017 im->xlab_user.precis = xlab[xlab_sel].precis;
2018 im->xlab_user.stst = xlab[xlab_sel].stst;
2019 }
2021 /* y coords are the same for every line ... */
2022 Y0 = im->yorigin;
2023 Y1 = im->yorigin-im->ysize;
2026 /* paint the minor grid */
2027 if (!(im->extra_flags & NOMINOR))
2028 {
2029 for(ti = find_first_time(im->start,
2030 im->xlab_user.gridtm,
2031 im->xlab_user.gridst),
2032 timajor = find_first_time(im->start,
2033 im->xlab_user.mgridtm,
2034 im->xlab_user.mgridst);
2035 ti < im->end;
2036 ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
2037 ){
2038 /* are we inside the graph ? */
2039 if (ti < im->start || ti > im->end) continue;
2040 while (timajor < ti) {
2041 timajor = find_next_time(timajor,
2042 im->xlab_user.mgridtm, im->xlab_user.mgridst);
2043 }
2044 if (ti == timajor) continue; /* skip as falls on major grid line */
2045 X0 = xtr(im,ti);
2046 gfx_new_dashed_line(im->canvas,X0,Y0+1, X0,Y1-1,GRIDWIDTH,
2047 im->graph_col[GRC_GRID],
2048 im->grid_dash_on, im->grid_dash_off);
2050 }
2051 }
2053 /* paint the major grid */
2054 for(ti = find_first_time(im->start,
2055 im->xlab_user.mgridtm,
2056 im->xlab_user.mgridst);
2057 ti < im->end;
2058 ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
2059 ){
2060 /* are we inside the graph ? */
2061 if (ti < im->start || ti > im->end) continue;
2062 X0 = xtr(im,ti);
2063 gfx_new_dashed_line(im->canvas,X0,Y0+3, X0,Y1-2,MGRIDWIDTH,
2064 im->graph_col[GRC_MGRID],
2065 im->grid_dash_on, im->grid_dash_off);
2067 }
2068 /* paint the labels below the graph */
2069 for(ti = find_first_time(im->start - im->xlab_user.precis/2,
2070 im->xlab_user.labtm,
2071 im->xlab_user.labst);
2072 ti <= im->end - im->xlab_user.precis/2;
2073 ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
2074 ){
2075 tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
2076 /* are we inside the graph ? */
2077 if (tilab < im->start || tilab > im->end) continue;
2079 #if HAVE_STRFTIME
2080 localtime_r(&tilab, &tm);
2081 strftime(graph_label,99,im->xlab_user.stst, &tm);
2082 #else
2083 # error "your libc has no strftime I guess we'll abort the exercise here."
2084 #endif
2085 gfx_new_text ( im->canvas,
2086 xtr(im,tilab), Y0+im->text_prop[TEXT_PROP_AXIS].size*1.4+5,
2087 im->graph_col[GRC_FONT],
2088 im->text_prop[TEXT_PROP_AXIS].font,
2089 im->text_prop[TEXT_PROP_AXIS].size,
2090 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_BOTTOM,
2091 graph_label );
2093 }
2095 }
2098 void
2099 axis_paint(
2100 image_desc_t *im
2101 )
2102 {
2103 /* draw x and y axis */
2104 /* gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2105 im->xorigin+im->xsize,im->yorigin-im->ysize,
2106 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2108 gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2109 im->xorigin+im->xsize,im->yorigin-im->ysize,
2110 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2112 gfx_new_line ( im->canvas, im->xorigin-4,im->yorigin,
2113 im->xorigin+im->xsize+4,im->yorigin,
2114 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2116 gfx_new_line ( im->canvas, im->xorigin,im->yorigin+4,
2117 im->xorigin,im->yorigin-im->ysize-4,
2118 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2121 /* arrow for X and Y axis direction */
2122 gfx_new_area ( im->canvas,
2123 im->xorigin+im->xsize+2, im->yorigin-2,
2124 im->xorigin+im->xsize+2, im->yorigin+3,
2125 im->xorigin+im->xsize+7, im->yorigin+0.5, /* LINEOFFSET */
2126 im->graph_col[GRC_ARROW]);
2128 gfx_new_area ( im->canvas,
2129 im->xorigin-2, im->yorigin-im->ysize-2,
2130 im->xorigin+3, im->yorigin-im->ysize-2,
2131 im->xorigin+0.5, im->yorigin-im->ysize-7, /* LINEOFFSET */
2132 im->graph_col[GRC_ARROW]);
2134 }
2136 void
2137 grid_paint(image_desc_t *im)
2138 {
2139 long i;
2140 int res=0;
2141 double X0,Y0; /* points for filled graph and more*/
2142 gfx_node_t *node;
2144 /* draw 3d border */
2145 node = gfx_new_area (im->canvas, 0,im->yimg,
2146 2,im->yimg-2,
2147 2,2,im->graph_col[GRC_SHADEA]);
2148 gfx_add_point( node , im->ximg - 2, 2 );
2149 gfx_add_point( node , im->ximg, 0 );
2150 gfx_add_point( node , 0,0 );
2151 /* gfx_add_point( node , 0,im->yimg ); */
2153 node = gfx_new_area (im->canvas, 2,im->yimg-2,
2154 im->ximg-2,im->yimg-2,
2155 im->ximg - 2, 2,
2156 im->graph_col[GRC_SHADEB]);
2157 gfx_add_point( node , im->ximg,0);
2158 gfx_add_point( node , im->ximg,im->yimg);
2159 gfx_add_point( node , 0,im->yimg);
2160 /* gfx_add_point( node , 0,im->yimg ); */
2163 if (im->draw_x_grid == 1 )
2164 vertical_grid(im);
2166 if (im->draw_y_grid == 1){
2167 if(im->logarithmic){
2168 res = horizontal_log_grid(im);
2169 } else {
2170 res = draw_horizontal_grid(im);
2171 }
2173 /* dont draw horizontal grid if there is no min and max val */
2174 if (! res ) {
2175 char *nodata = "No Data found";
2176 gfx_new_text(im->canvas,im->ximg/2, (2*im->yorigin-im->ysize) / 2,
2177 im->graph_col[GRC_FONT],
2178 im->text_prop[TEXT_PROP_AXIS].font,
2179 im->text_prop[TEXT_PROP_AXIS].size,
2180 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
2181 nodata );
2182 }
2183 }
2185 /* yaxis unit description */
2186 gfx_new_text( im->canvas,
2187 10, (im->yorigin - im->ysize/2),
2188 im->graph_col[GRC_FONT],
2189 im->text_prop[TEXT_PROP_UNIT].font,
2190 im->text_prop[TEXT_PROP_UNIT].size, im->tabwidth,
2191 RRDGRAPH_YLEGEND_ANGLE,
2192 GFX_H_LEFT, GFX_V_CENTER,
2193 im->ylegend);
2195 /* graph title */
2196 gfx_new_text( im->canvas,
2197 im->ximg/2, im->text_prop[TEXT_PROP_TITLE].size*1.3+4,
2198 im->graph_col[GRC_FONT],
2199 im->text_prop[TEXT_PROP_TITLE].font,
2200 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
2201 GFX_H_CENTER, GFX_V_CENTER,
2202 im->title);
2203 /* rrdtool 'logo' */
2204 gfx_new_text( im->canvas,
2205 im->ximg-7, 7,
2206 ( im->graph_col[GRC_FONT] & 0xffffff00 ) | 0x00000044,
2207 im->text_prop[TEXT_PROP_AXIS].font,
2208 5.5, im->tabwidth, 270,
2209 GFX_H_RIGHT, GFX_V_TOP,
2210 "RRDTOOL / TOBI OETIKER");
2212 /* graph watermark */
2213 if(im->watermark[0] != '\0') {
2214 gfx_new_text( im->canvas,
2215 im->ximg/2, im->yimg-6,
2216 ( im->graph_col[GRC_FONT] & 0xffffff00 ) | 0x00000044,
2217 im->text_prop[TEXT_PROP_AXIS].font,
2218 5.5, im->tabwidth, 0,
2219 GFX_H_CENTER, GFX_V_BOTTOM,
2220 im->watermark);
2221 }
2223 /* graph labels */
2224 if( !(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH) ) {
2225 for(i=0;i<im->gdes_c;i++){
2226 if(im->gdes[i].legend[0] =='\0')
2227 continue;
2229 /* im->gdes[i].leg_y is the bottom of the legend */
2230 X0 = im->gdes[i].leg_x;
2231 Y0 = im->gdes[i].leg_y;
2232 gfx_new_text ( im->canvas, X0, Y0,
2233 im->graph_col[GRC_FONT],
2234 im->text_prop[TEXT_PROP_LEGEND].font,
2235 im->text_prop[TEXT_PROP_LEGEND].size,
2236 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
2237 im->gdes[i].legend );
2238 /* The legend for GRAPH items starts with "M " to have
2239 enough space for the box */
2240 if ( im->gdes[i].gf != GF_PRINT &&
2241 im->gdes[i].gf != GF_GPRINT &&
2242 im->gdes[i].gf != GF_COMMENT) {
2243 int boxH, boxV;
2245 boxH = gfx_get_text_width(im->canvas, 0,
2246 im->text_prop[TEXT_PROP_LEGEND].font,
2247 im->text_prop[TEXT_PROP_LEGEND].size,
2248 im->tabwidth,"o", 0) * 1.2;
2249 boxV = boxH*1.1;
2251 /* make sure transparent colors show up the same way as in the graph */
2252 node = gfx_new_area(im->canvas,
2253 X0,Y0-boxV,
2254 X0,Y0,
2255 X0+boxH,Y0,
2256 im->graph_col[GRC_BACK]);
2257 gfx_add_point ( node, X0+boxH, Y0-boxV );
2259 node = gfx_new_area(im->canvas,
2260 X0,Y0-boxV,
2261 X0,Y0,
2262 X0+boxH,Y0,
2263 im->gdes[i].col);
2264 gfx_add_point ( node, X0+boxH, Y0-boxV );
2265 node = gfx_new_line(im->canvas,
2266 X0,Y0-boxV,
2267 X0,Y0,
2268 1.0,im->graph_col[GRC_FRAME]);
2269 gfx_add_point(node,X0+boxH,Y0);
2270 gfx_add_point(node,X0+boxH,Y0-boxV);
2271 gfx_close_path(node);
2272 }
2273 }
2274 }
2275 }
2278 /*****************************************************
2279 * lazy check make sure we rely need to create this graph
2280 *****************************************************/
2282 int lazy_check(image_desc_t *im){
2283 FILE *fd = NULL;
2284 int size = 1;
2285 struct stat imgstat;
2287 if (im->lazy == 0) return 0; /* no lazy option */
2288 if (stat(im->graphfile,&imgstat) != 0)
2289 return 0; /* can't stat */
2290 /* one pixel in the existing graph is more then what we would
2291 change here ... */
2292 if (time(NULL) - imgstat.st_mtime >
2293 (im->end - im->start) / im->xsize)
2294 return 0;
2295 if ((fd = fopen(im->graphfile,"rb")) == NULL)
2296 return 0; /* the file does not exist */
2297 switch (im->canvas->imgformat) {
2298 case IF_PNG:
2299 size = PngSize(fd,&(im->ximg),&(im->yimg));
2300 break;
2301 default:
2302 size = 1;
2303 }
2304 fclose(fd);
2305 return size;
2306 }
2308 #ifdef WITH_PIECHART
2309 void
2310 pie_part(image_desc_t *im, gfx_color_t color,
2311 double PieCenterX, double PieCenterY, double Radius,
2312 double startangle, double endangle)
2313 {
2314 gfx_node_t *node;
2315 double angle;
2316 double step=M_PI/50; /* Number of iterations for the circle;
2317 ** 10 is definitely too low, more than
2318 ** 50 seems to be overkill
2319 */
2321 /* Strange but true: we have to work clockwise or else
2322 ** anti aliasing nor transparency don't work.
2323 **
2324 ** This test is here to make sure we do it right, also
2325 ** this makes the for...next loop more easy to implement.
2326 ** The return will occur if the user enters a negative number
2327 ** (which shouldn't be done according to the specs) or if the
2328 ** programmers do something wrong (which, as we all know, never
2329 ** happens anyway :)
2330 */
2331 if (endangle<startangle) return;
2333 /* Hidden feature: Radius decreases each full circle */
2334 angle=startangle;
2335 while (angle>=2*M_PI) {
2336 angle -= 2*M_PI;
2337 Radius *= 0.8;
2338 }
2340 node=gfx_new_area(im->canvas,
2341 PieCenterX+sin(startangle)*Radius,
2342 PieCenterY-cos(startangle)*Radius,
2343 PieCenterX,
2344 PieCenterY,
2345 PieCenterX+sin(endangle)*Radius,
2346 PieCenterY-cos(endangle)*Radius,
2347 color);
2348 for (angle=endangle;angle-startangle>=step;angle-=step) {
2349 gfx_add_point(node,
2350 PieCenterX+sin(angle)*Radius,
2351 PieCenterY-cos(angle)*Radius );
2352 }
2353 }
2355 #endif
2357 int
2358 graph_size_location(image_desc_t *im, int elements
2360 #ifdef WITH_PIECHART
2361 , int piechart
2362 #endif
2364 )
2365 {
2366 /* The actual size of the image to draw is determined from
2367 ** several sources. The size given on the command line is
2368 ** the graph area but we need more as we have to draw labels
2369 ** and other things outside the graph area
2370 */
2372 /* +-+-------------------------------------------+
2373 ** |l|.................title.....................|
2374 ** |e+--+-------------------------------+--------+
2375 ** |b| b| | |
2376 ** |a| a| | pie |
2377 ** |l| l| main graph area | chart |
2378 ** |.| .| | area |
2379 ** |t| y| | |
2380 ** |r+--+-------------------------------+--------+
2381 ** |e| | x-axis labels | |
2382 ** |v+--+-------------------------------+--------+
2383 ** | |..............legends......................|
2384 ** +-+-------------------------------------------+
2385 ** | watermark |
2386 ** +---------------------------------------------+
2387 */
2388 int Xvertical=0,
2389 Ytitle =0,
2390 Xylabel =0,
2391 Xmain =0, Ymain =0,
2392 #ifdef WITH_PIECHART
2393 Xpie =0, Ypie =0,
2394 #endif
2395 Yxlabel =0,
2396 #if 0
2397 Xlegend =0, Ylegend =0,
2398 #endif
2399 Xspacing =15, Yspacing =15,
2401 Ywatermark =4;
2403 if (im->extra_flags & ONLY_GRAPH) {
2404 im->xorigin =0;
2405 im->ximg = im->xsize;
2406 im->yimg = im->ysize;
2407 im->yorigin = im->ysize;
2408 ytr(im,DNAN);
2409 return 0;
2410 }
2412 if (im->ylegend[0] != '\0' ) {
2413 Xvertical = im->text_prop[TEXT_PROP_UNIT].size *2;
2414 }
2417 if (im->title[0] != '\0') {
2418 /* The title is placed "inbetween" two text lines so it
2419 ** automatically has some vertical spacing. The horizontal
2420 ** spacing is added here, on each side.
2421 */
2422 /* don't care for the with of the title
2423 Xtitle = gfx_get_text_width(im->canvas, 0,
2424 im->text_prop[TEXT_PROP_TITLE].font,
2425 im->text_prop[TEXT_PROP_TITLE].size,
2426 im->tabwidth,
2427 im->title, 0) + 2*Xspacing; */
2428 Ytitle = im->text_prop[TEXT_PROP_TITLE].size*2.6+10;
2429 }
2431 if (elements) {
2432 Xmain=im->xsize;
2433 Ymain=im->ysize;
2434 if (im->draw_x_grid) {
2435 Yxlabel=im->text_prop[TEXT_PROP_AXIS].size *2.5;
2436 }
2437 if (im->draw_y_grid || im->forceleftspace ) {
2438 Xylabel=gfx_get_text_width(im->canvas, 0,
2439 im->text_prop[TEXT_PROP_AXIS].font,
2440 im->text_prop[TEXT_PROP_AXIS].size,
2441 im->tabwidth,
2442 "0", 0) * im->unitslength;
2443 }
2444 }
2446 #ifdef WITH_PIECHART
2447 if (piechart) {
2448 im->piesize=im->xsize<im->ysize?im->xsize:im->ysize;
2449 Xpie=im->piesize;
2450 Ypie=im->piesize;
2451 }
2452 #endif
2454 /* Now calculate the total size. Insert some spacing where
2455 desired. im->xorigin and im->yorigin need to correspond
2456 with the lower left corner of the main graph area or, if
2457 this one is not set, the imaginary box surrounding the
2458 pie chart area. */
2460 /* The legend width cannot yet be determined, as a result we
2461 ** have problems adjusting the image to it. For now, we just
2462 ** forget about it at all; the legend will have to fit in the
2463 ** size already allocated.
2464 */
2465 im->ximg = Xylabel + Xmain + 2 * Xspacing;
2467 #ifdef WITH_PIECHART
2468 im->ximg += Xpie;
2469 #endif
2471 if (Xmain) im->ximg += Xspacing;
2472 #ifdef WITH_PIECHART
2473 if (Xpie) im->ximg += Xspacing;
2474 #endif
2476 im->xorigin = Xspacing + Xylabel;
2478 /* the length of the title should not influence with width of the graph
2479 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2481 if (Xvertical) { /* unit description */
2482 im->ximg += Xvertical;
2483 im->xorigin += Xvertical;
2484 }
2485 xtr(im,0);
2487 /* The vertical size is interesting... we need to compare
2488 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with
2489 ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2490 ** in order to start even thinking about Ylegend or Ywatermark.
2491 **
2492 ** Do it in three portions: First calculate the inner part,
2493 ** then do the legend, then adjust the total height of the img,
2494 ** adding space for a watermark if one exists;
2495 */
2497 /* reserve space for main and/or pie */
2499 im->yimg = Ymain + Yxlabel;
2501 #ifdef WITH_PIECHART
2502 if (im->yimg < Ypie) im->yimg = Ypie;
2503 #endif
2505 im->yorigin = im->yimg - Yxlabel;
2507 /* reserve space for the title *or* some padding above the graph */
2508 if (Ytitle) {
2509 im->yimg += Ytitle;
2510 im->yorigin += Ytitle;
2511 } else {
2512 im->yimg += 1.5*Yspacing;
2513 im->yorigin += 1.5*Yspacing;
2514 }
2515 /* reserve space for padding below the graph */
2516 im->yimg += Yspacing;
2518 /* Determine where to place the legends onto the image.
2519 ** Adjust im->yimg to match the space requirements.
2520 */
2521 if(leg_place(im)==-1)
2522 return -1;
2524 if (im->watermark[0] != '\0') {
2525 im->yimg += Ywatermark;
2526 }
2528 #if 0
2529 if (Xlegend > im->ximg) {
2530 im->ximg = Xlegend;
2531 /* reposition Pie */
2532 }
2533 #endif
2535 #ifdef WITH_PIECHART
2536 /* The pie is placed in the upper right hand corner,
2537 ** just below the title (if any) and with sufficient
2538 ** padding.
2539 */
2540 if (elements) {
2541 im->pie_x = im->ximg - Xspacing - Xpie/2;
2542 im->pie_y = im->yorigin-Ymain+Ypie/2;
2543 } else {
2544 im->pie_x = im->ximg/2;
2545 im->pie_y = im->yorigin-Ypie/2;
2546 }
2547 #endif
2549 ytr(im,DNAN);
2550 return 0;
2551 }
2553 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
2554 /* yes we are loosing precision by doing tos with floats instead of doubles
2555 but it seems more stable this way. */
2558 /* draw that picture thing ... */
2559 int
2560 graph_paint(image_desc_t *im, char ***calcpr)
2561 {
2562 int i,ii;
2563 int lazy = lazy_check(im);
2564 #ifdef WITH_PIECHART
2565 int piechart = 0;
2566 double PieStart=0.0;
2567 #endif
2568 FILE *fo;
2569 gfx_node_t *node;
2571 double areazero = 0.0;
2572 graph_desc_t *lastgdes = NULL;
2574 /* if we are lazy and there is nothing to PRINT ... quit now */
2575 if (lazy && im->prt_c==0) return 0;
2577 /* pull the data from the rrd files ... */
2579 if(data_fetch(im)==-1)
2580 return -1;
2582 /* evaluate VDEF and CDEF operations ... */
2583 if(data_calc(im)==-1)
2584 return -1;
2586 #ifdef WITH_PIECHART
2587 /* check if we need to draw a piechart */
2588 for(i=0;i<im->gdes_c;i++){
2589 if (im->gdes[i].gf == GF_PART) {
2590 piechart=1;
2591 break;
2592 }
2593 }
2594 #endif
2596 /* calculate and PRINT and GPRINT definitions. We have to do it at
2597 * this point because it will affect the length of the legends
2598 * if there are no graph elements we stop here ...
2599 * if we are lazy, try to quit ...
2600 */
2601 i=print_calc(im,calcpr);
2602 if(i<0) return -1;
2603 if(((i==0)
2604 #ifdef WITH_PIECHART
2605 &&(piechart==0)
2606 #endif
2607 ) || lazy) return 0;
2609 #ifdef WITH_PIECHART
2610 /* If there's only the pie chart to draw, signal this */
2611 if (i==0) piechart=2;
2612 #endif
2614 /* get actual drawing data and find min and max values*/
2615 if(data_proc(im)==-1)
2616 return -1;
2618 if(!im->logarithmic){si_unit(im);} /* identify si magnitude Kilo, Mega Giga ? */
2620 if(!im->rigid && ! im->logarithmic)
2621 expand_range(im); /* make sure the upper and lower limit are
2622 sensible values */
2624 if (!calc_horizontal_grid(im))
2625 return -1;
2627 if (im->gridfit)
2628 apply_gridfit(im);
2631 /**************************************************************
2632 *** Calculating sizes and locations became a bit confusing ***
2633 *** so I moved this into a separate function. ***
2634 **************************************************************/
2635 if(graph_size_location(im,i
2636 #ifdef WITH_PIECHART
2637 ,piechart
2638 #endif
2639 )==-1)
2640 return -1;
2642 /* the actual graph is created by going through the individual
2643 graph elements and then drawing them */
2645 node=gfx_new_area ( im->canvas,
2646 0, 0,
2647 0, im->yimg,
2648 im->ximg, im->yimg,
2649 im->graph_col[GRC_BACK]);
2651 gfx_add_point(node,im->ximg, 0);
2653 #ifdef WITH_PIECHART
2654 if (piechart != 2) {
2655 #endif
2656 node=gfx_new_area ( im->canvas,
2657 im->xorigin, im->yorigin,
2658 im->xorigin + im->xsize, im->yorigin,
2659 im->xorigin + im->xsize, im->yorigin-im->ysize,
2660 im->graph_col[GRC_CANVAS]);
2662 gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
2664 if (im->minval > 0.0)
2665 areazero = im->minval;
2666 if (im->maxval < 0.0)
2667 areazero = im->maxval;
2668 #ifdef WITH_PIECHART
2669 }
2670 #endif
2672 #ifdef WITH_PIECHART
2673 if (piechart) {
2674 pie_part(im,im->graph_col[GRC_CANVAS],im->pie_x,im->pie_y,im->piesize*0.5,0,2*M_PI);
2675 }
2676 #endif
2678 for(i=0;i<im->gdes_c;i++){
2679 switch(im->gdes[i].gf){
2680 case GF_CDEF:
2681 case GF_VDEF:
2682 case GF_DEF:
2683 case GF_PRINT:
2684 case GF_GPRINT:
2685 case GF_COMMENT:
2686 case GF_HRULE:
2687 case GF_VRULE:
2688 case GF_XPORT:
2689 case GF_SHIFT:
2690 break;
2691 case GF_TICK:
2692 for (ii = 0; ii < im->xsize; ii++)
2693 {
2694 if (!isnan(im->gdes[i].p_data[ii]) &&
2695 im->gdes[i].p_data[ii] != 0.0)
2696 {
2697 if (im -> gdes[i].yrule > 0 ) {
2698 gfx_new_line(im->canvas,
2699 im -> xorigin + ii, im->yorigin,
2700 im -> xorigin + ii, im->yorigin - im -> gdes[i].yrule * im -> ysize,
2701 1.0,
2702 im -> gdes[i].col );
2703 } else if ( im -> gdes[i].yrule < 0 ) {
2704 gfx_new_line(im->canvas,
2705 im -> xorigin + ii, im->yorigin - im -> ysize,
2706 im -> xorigin + ii, im->yorigin - ( 1 - im -> gdes[i].yrule ) * im -> ysize,
2707 1.0,
2708 im -> gdes[i].col );
2710 }
2711 }
2712 }
2713 break;
2714 case GF_LINE:
2715 case GF_AREA:
2716 /* fix data points at oo and -oo */
2717 for(ii=0;ii<im->xsize;ii++){
2718 if (isinf(im->gdes[i].p_data[ii])){
2719 if (im->gdes[i].p_data[ii] > 0) {
2720 im->gdes[i].p_data[ii] = im->maxval ;
2721 } else {
2722 im->gdes[i].p_data[ii] = im->minval ;
2723 }
2725 }
2726 } /* for */
2728 /* *******************************************************
2729 a ___. (a,t)
2730 | | ___
2731 ____| | | |
2732 | |___|
2733 -------|--t-1--t--------------------------------
2735 if we know the value at time t was a then
2736 we draw a square from t-1 to t with the value a.
2738 ********************************************************* */
2739 if (im->gdes[i].col != 0x0){
2740 /* GF_LINE and friend */
2741 if(im->gdes[i].gf == GF_LINE ){
2742 double last_y=0.0;
2743 node = NULL;
2744 for(ii=1;ii<im->xsize;ii++){
2745 if (isnan(im->gdes[i].p_data[ii]) || (im->slopemode==1 && isnan(im->gdes[i].p_data[ii-1]))){
2746 node = NULL;
2747 continue;
2748 }
2749 if ( node == NULL ) {
2750 last_y = ytr(im,im->gdes[i].p_data[ii]);
2751 if ( im->slopemode == 0 ){
2752 node = gfx_new_line(im->canvas,
2753 ii-1+im->xorigin,last_y,
2754 ii+im->xorigin,last_y,
2755 im->gdes[i].linewidth,
2756 im->gdes[i].col);
2757 } else {
2758 node = gfx_new_line(im->canvas,
2759 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2760 ii+im->xorigin,last_y,
2761 im->gdes[i].linewidth,
2762 im->gdes[i].col);
2763 }
2764 } else {
2765 double new_y = ytr(im,im->gdes[i].p_data[ii]);
2766 if ( im->slopemode==0 && ! AlmostEqual2sComplement(new_y,last_y,4)){
2767 gfx_add_point(node,ii-1+im->xorigin,new_y);
2768 };
2769 last_y = new_y;
2770 gfx_add_point(node,ii+im->xorigin,new_y);
2771 };
2773 }
2774 } else {
2775 int idxI=-1;
2776 double *foreY=malloc(sizeof(double)*im->xsize*2);
2777 double *foreX=malloc(sizeof(double)*im->xsize*2);
2778 double *backY=malloc(sizeof(double)*im->xsize*2);
2779 double *backX=malloc(sizeof(double)*im->xsize*2);
2780 int drawem = 0;
2781 for(ii=0;ii<=im->xsize;ii++){
2782 double ybase,ytop;
2783 if ( idxI > 0 && ( drawem != 0 || ii==im->xsize)){
2784 int cntI=1;
2785 int lastI=0;
2786 while (cntI < idxI && AlmostEqual2sComplement(foreY[lastI],foreY[cntI],4) && AlmostEqual2sComplement(foreY[lastI],foreY[cntI+1],4)){cntI++;}
2787 node = gfx_new_area(im->canvas,
2788 backX[0],backY[0],
2789 foreX[0],foreY[0],
2790 foreX[cntI],foreY[cntI], im->gdes[i].col);
2791 while (cntI < idxI) {
2792 lastI = cntI;
2793 cntI++;
2794 while ( cntI < idxI && AlmostEqual2sComplement(foreY[lastI],foreY[cntI],4) && AlmostEqual2sComplement(foreY[lastI],foreY[cntI+1],4)){cntI++;}
2795 gfx_add_point(node,foreX[cntI],foreY[cntI]);
2796 }
2797 gfx_add_point(node,backX[idxI],backY[idxI]);
2798 while (idxI > 1){
2799 lastI = idxI;
2800 idxI--;
2801 while ( idxI > 1 && AlmostEqual2sComplement(backY[lastI], backY[idxI],4) && AlmostEqual2sComplement(backY[lastI],backY[idxI-1],4)){idxI--;}
2802 gfx_add_point(node,backX[idxI],backY[idxI]);
2803 }
2804 idxI=-1;
2805 drawem = 0;
2806 }
2807 if (drawem != 0){
2808 drawem = 0;
2809 idxI=-1;
2810 }
2811 if (ii == im->xsize) break;
2813 /* keep things simple for now, just draw these bars
2814 do not try to build a big and complex area */
2817 if ( im->slopemode == 0 && ii==0){
2818 continue;
2819 }
2820 if ( isnan(im->gdes[i].p_data[ii]) ) {
2821 drawem = 1;
2822 continue;
2823 }
2824 ytop = ytr(im,im->gdes[i].p_data[ii]);
2825 if ( lastgdes && im->gdes[i].stack ) {
2826 ybase = ytr(im,lastgdes->p_data[ii]);
2827 } else {
2828 ybase = ytr(im,areazero);
2829 }
2830 if ( ybase == ytop ){
2831 drawem = 1;
2832 continue;
2833 }
2834 /* every area has to be wound clock-wise,
2835 so we have to make sur base remains base */
2836 if (ybase > ytop){
2837 double extra = ytop;
2838 ytop = ybase;
2839 ybase = extra;
2840 }
2841 if ( im->slopemode == 0 ){
2842 backY[++idxI] = ybase-0.2;
2843 backX[idxI] = ii+im->xorigin-1;
2844 foreY[idxI] = ytop+0.2;
2845 foreX[idxI] = ii+im->xorigin-1;
2846 }
2847 backY[++idxI] = ybase-0.2;
2848 backX[idxI] = ii+im->xorigin;
2849 foreY[idxI] = ytop+0.2;
2850 foreX[idxI] = ii+im->xorigin;
2851 }
2852 /* close up any remaining area */
2853 free(foreY);
2854 free(foreX);
2855 free(backY);
2856 free(backX);
2857 } /* else GF_LINE */
2858 } /* if color != 0x0 */
2859 /* make sure we do not run into trouble when stacking on NaN */
2860 for(ii=0;ii<im->xsize;ii++){
2861 if (isnan(im->gdes[i].p_data[ii])) {
2862 if (lastgdes && (im->gdes[i].stack)) {
2863 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
2864 } else {
2865 im->gdes[i].p_data[ii] = areazero;
2866 }
2867 }
2868 }
2869 lastgdes = &(im->gdes[i]);
2870 break;
2871 #ifdef WITH_PIECHART
2872 case GF_PART:
2873 if(isnan(im->gdes[i].yrule)) /* fetch variable */
2874 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2876 if (finite(im->gdes[i].yrule)) { /* even the fetched var can be NaN */
2877 pie_part(im,im->gdes[i].col,
2878 im->pie_x,im->pie_y,im->piesize*0.4,
2879 M_PI*2.0*PieStart/100.0,
2880 M_PI*2.0*(PieStart+im->gdes[i].yrule)/100.0);
2881 PieStart += im->gdes[i].yrule;
2882 }
2883 break;
2884 #endif
2885 case GF_STACK:
2886 rrd_set_error("STACK should already be turned into LINE or AREA here");
2887 return -1;
2888 break;
2890 } /* switch */
2891 }
2892 #ifdef WITH_PIECHART
2893 if (piechart==2) {
2894 im->draw_x_grid=0;
2895 im->draw_y_grid=0;
2896 }
2897 #endif
2900 /* grid_paint also does the text */
2901 if( !(im->extra_flags & ONLY_GRAPH) )
2902 grid_paint(im);
2905 if( !(im->extra_flags & ONLY_GRAPH) )
2906 axis_paint(im);
2908 /* the RULES are the last thing to paint ... */
2909 for(i=0;i<im->gdes_c;i++){
2911 switch(im->gdes[i].gf){
2912 case GF_HRULE:
2913 if(im->gdes[i].yrule >= im->minval
2914 && im->gdes[i].yrule <= im->maxval)
2915 gfx_new_line(im->canvas,
2916 im->xorigin,ytr(im,im->gdes[i].yrule),
2917 im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2918 1.0,im->gdes[i].col);
2919 break;
2920 case GF_VRULE:
2921 if(im->gdes[i].xrule >= im->start
2922 && im->gdes[i].xrule <= im->end)
2923 gfx_new_line(im->canvas,
2924 xtr(im,im->gdes[i].xrule),im->yorigin,
2925 xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2926 1.0,im->gdes[i].col);
2927 break;
2928 default:
2929 break;
2930 }
2931 }
2934 if (strcmp(im->graphfile,"-")==0) {
2935 fo = im->graphhandle ? im->graphhandle : stdout;
2936 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
2937 /* Change translation mode for stdout to BINARY */
2938 _setmode( _fileno( fo ), O_BINARY );
2939 #endif
2940 } else {
2941 if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2942 rrd_set_error("Opening '%s' for write: %s",im->graphfile,
2943 rrd_strerror(errno));
2944 return (-1);
2945 }
2946 }
2947 gfx_render (im->canvas,im->ximg,im->yimg,0x00000000,fo);
2948 if (strcmp(im->graphfile,"-") != 0)
2949 fclose(fo);
2950 return 0;
2951 }
2954 /*****************************************************
2955 * graph stuff
2956 *****************************************************/
2958 int
2959 gdes_alloc(image_desc_t *im){
2961 im->gdes_c++;
2962 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2963 * sizeof(graph_desc_t)))==NULL){
2964 rrd_set_error("realloc graph_descs");
2965 return -1;
2966 }
2969 im->gdes[im->gdes_c-1].step=im->step;
2970 im->gdes[im->gdes_c-1].step_orig=im->step;
2971 im->gdes[im->gdes_c-1].stack=0;
2972 im->gdes[im->gdes_c-1].linewidth=0;
2973 im->gdes[im->gdes_c-1].debug=0;
2974 im->gdes[im->gdes_c-1].start=im->start;
2975 im->gdes[im->gdes_c-1].start_orig=im->start;
2976 im->gdes[im->gdes_c-1].end=im->end;
2977 im->gdes[im->gdes_c-1].end_orig=im->end;
2978 im->gdes[im->gdes_c-1].vname[0]='\0';
2979 im->gdes[im->gdes_c-1].data=NULL;
2980 im->gdes[im->gdes_c-1].ds_namv=NULL;
2981 im->gdes[im->gdes_c-1].data_first=0;
2982 im->gdes[im->gdes_c-1].p_data=NULL;
2983 im->gdes[im->gdes_c-1].rpnp=NULL;
2984 im->gdes[im->gdes_c-1].shift=0;
2985 im->gdes[im->gdes_c-1].col = 0x0;
2986 im->gdes[im->gdes_c-1].legend[0]='\0';
2987 im->gdes[im->gdes_c-1].format[0]='\0';
2988 im->gdes[im->gdes_c-1].strftm=0;
2989 im->gdes[im->gdes_c-1].rrd[0]='\0';
2990 im->gdes[im->gdes_c-1].ds=-1;
2991 im->gdes[im->gdes_c-1].cf_reduce=CF_AVERAGE;
2992 im->gdes[im->gdes_c-1].cf=CF_AVERAGE;
2993 im->gdes[im->gdes_c-1].p_data=NULL;
2994 im->gdes[im->gdes_c-1].yrule=DNAN;
2995 im->gdes[im->gdes_c-1].xrule=0;
2996 return 0;
2997 }
2999 /* copies input untill the first unescaped colon is found
3000 or until input ends. backslashes have to be escaped as well */
3001 int
3002 scan_for_col(const char *const input, int len, char *const output)
3003 {
3004 int inp,outp=0;
3005 for (inp=0;
3006 inp < len &&
3007 input[inp] != ':' &&
3008 input[inp] != '\0';
3009 inp++){
3010 if (input[inp] == '\\' &&
3011 input[inp+1] != '\0' &&
3012 (input[inp+1] == '\\' ||
3013 input[inp+1] == ':')){
3014 output[outp++] = input[++inp];
3015 }
3016 else {
3017 output[outp++] = input[inp];
3018 }
3019 }
3020 output[outp] = '\0';
3021 return inp;
3022 }
3023 /* Some surgery done on this function, it became ridiculously big.
3024 ** Things moved:
3025 ** - initializing now in rrd_graph_init()
3026 ** - options parsing now in rrd_graph_options()
3027 ** - script parsing now in rrd_graph_script()
3028 */
3029 int
3030 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize, FILE *stream, double *ymin, double *ymax)
3031 {
3032 image_desc_t im;
3033 rrd_graph_init(&im);
3034 im.graphhandle = stream;
3036 rrd_graph_options(argc,argv,&im);
3037 if (rrd_test_error()) {
3038 im_free(&im);
3039 return -1;
3040 }
3042 if (strlen(argv[optind])>=MAXPATH) {
3043 rrd_set_error("filename (including path) too long");
3044 im_free(&im);
3045 return -1;
3046 }
3047 strncpy(im.graphfile,argv[optind],MAXPATH-1);
3048 im.graphfile[MAXPATH-1]='\0';
3050 rrd_graph_script(argc,argv,&im,1);
3051 if (rrd_test_error()) {
3052 im_free(&im);
3053 return -1;
3054 }
3056 /* Everything is now read and the actual work can start */
3058 (*prdata)=NULL;
3059 if (graph_paint(&im,prdata)==-1){
3060 im_free(&im);
3061 return -1;
3062 }
3064 /* The image is generated and needs to be output.
3065 ** Also, if needed, print a line with information about the image.
3066 */
3068 *xsize=im.ximg;
3069 *ysize=im.yimg;
3070 *ymin=im.minval;
3071 *ymax=im.maxval;
3072 if (im.imginfo) {
3073 char *filename;
3074 if (!(*prdata)) {
3075 /* maybe prdata is not allocated yet ... lets do it now */
3076 if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
3077 rrd_set_error("malloc imginfo");
3078 return -1;
3079 };
3080 }
3081 if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
3082 ==NULL){
3083 rrd_set_error("malloc imginfo");
3084 return -1;
3085 }
3086 filename=im.graphfile+strlen(im.graphfile);
3087 while(filename > im.graphfile) {
3088 if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
3089 filename--;
3090 }
3092 sprintf((*prdata)[0],im.imginfo,filename,(long)(im.canvas->zoom*im.ximg),(long)(im.canvas->zoom*im.yimg));
3093 }
3094 im_free(&im);
3095 return 0;
3096 }
3098 void
3099 rrd_graph_init(image_desc_t *im)
3100 {
3101 unsigned int i;
3103 #ifdef HAVE_TZSET
3104 tzset();
3105 #endif
3106 #ifdef HAVE_SETLOCALE
3107 setlocale(LC_TIME,"");
3108 #ifdef HAVE_MBSTOWCS
3109 setlocale(LC_CTYPE,"");
3110 #endif
3111 #endif
3112 im->yorigin=0;
3113 im->xorigin=0;
3114 im->minval=0;
3115 im->xlab_user.minsec = -1;
3116 im->ximg=0;
3117 im->yimg=0;
3118 im->xsize = 400;
3119 im->ysize = 100;
3120 im->step = 0;
3121 im->ylegend[0] = '\0';
3122 im->title[0] = '\0';
3123 im->watermark[0] = '\0';
3124 im->minval = DNAN;
3125 im->maxval = DNAN;
3126 im->unitsexponent= 9999;
3127 im->unitslength= 6;
3128 im->forceleftspace = 0;
3129 im->symbol = ' ';
3130 im->viewfactor = 1.0;
3131 im->extra_flags= 0;
3132 im->rigid = 0;
3133 im->gridfit = 1;
3134 im->imginfo = NULL;
3135 im->lazy = 0;
3136 im->slopemode = 0;
3137 im->logarithmic = 0;
3138 im->ygridstep = DNAN;
3139 im->draw_x_grid = 1;
3140 im->draw_y_grid = 1;
3141 im->base = 1000;
3142 im->prt_c = 0;
3143 im->gdes_c = 0;
3144 im->gdes = NULL;
3145 im->canvas = gfx_new_canvas();
3146 im->grid_dash_on = 1;
3147 im->grid_dash_off = 1;
3148 im->tabwidth = 40.0;
3150 for(i=0;i<DIM(graph_col);i++)
3151 im->graph_col[i]=graph_col[i];
3153 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3154 {
3155 char *windir;
3156 char rrd_win_default_font[1000];
3157 windir = getenv("windir");
3158 /* %windir% is something like D:\windows or C:\winnt */
3159 if (windir != NULL) {
3160 strncpy(rrd_win_default_font,windir,500);
3161 rrd_win_default_font[500] = '\0';
3162 strcat(rrd_win_default_font,"\\fonts\\");
3163 strcat(rrd_win_default_font,RRD_DEFAULT_FONT);
3164 for(i=0;i<DIM(text_prop);i++){
3165 strncpy(text_prop[i].font,rrd_win_default_font,sizeof(text_prop[i].font)-1);
3166 text_prop[i].font[sizeof(text_prop[i].font)-1] = '\0';
3167 }
3168 }
3169 }
3170 #endif
3171 {
3172 char *deffont;
3173 deffont = getenv("RRD_DEFAULT_FONT");
3174 if (deffont != NULL) {
3175 for(i=0;i<DIM(text_prop);i++){
3176 strncpy(text_prop[i].font,deffont,sizeof(text_prop[i].font)-1);
3177 text_prop[i].font[sizeof(text_prop[i].font)-1] = '\0';
3178 }
3179 }
3180 }
3181 for(i=0;i<DIM(text_prop);i++){
3182 im->text_prop[i].size = text_prop[i].size;
3183 strcpy(im->text_prop[i].font,text_prop[i].font);
3184 }
3185 }
3187 void
3188 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
3189 {
3190 int stroff;
3191 char *parsetime_error = NULL;
3192 char scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
3193 time_t start_tmp=0,end_tmp=0;
3194 long long_tmp;
3195 struct rrd_time_value start_tv, end_tv;
3196 gfx_color_t color;
3197 optind = 0; opterr = 0; /* initialize getopt */
3199 parsetime("end-24h", &start_tv);
3200 parsetime("now", &end_tv);
3202 /* defines for long options without a short equivalent. should be bytes,
3203 and may not collide with (the ASCII value of) short options */
3204 #define LONGOPT_UNITS_SI 255
3206 while (1){
3207 static struct option long_options[] =
3208 {
3209 {"start", required_argument, 0, 's'},
3210 {"end", required_argument, 0, 'e'},
3211 {"x-grid", required_argument, 0, 'x'},
3212 {"y-grid", required_argument, 0, 'y'},
3213 {"vertical-label",required_argument,0,'v'},
3214 {"width", required_argument, 0, 'w'},
3215 {"height", required_argument, 0, 'h'},
3216 {"interlaced", no_argument, 0, 'i'},
3217 {"upper-limit",required_argument, 0, 'u'},
3218 {"lower-limit",required_argument, 0, 'l'},
3219 {"rigid", no_argument, 0, 'r'},
3220 {"base", required_argument, 0, 'b'},
3221 {"logarithmic",no_argument, 0, 'o'},
3222 {"color", required_argument, 0, 'c'},
3223 {"font", required_argument, 0, 'n'},
3224 {"title", required_argument, 0, 't'},
3225 {"imginfo", required_argument, 0, 'f'},
3226 {"imgformat", required_argument, 0, 'a'},
3227 {"lazy", no_argument, 0, 'z'},
3228 {"zoom", required_argument, 0, 'm'},
3229 {"no-legend", no_argument, 0, 'g'},
3230 {"force-rules-legend",no_argument,0, 'F'},
3231 {"only-graph", no_argument, 0, 'j'},
3232 {"alt-y-grid", no_argument, 0, 'Y'},
3233 {"no-minor", no_argument, 0, 'I'},
3234 {"slope-mode", no_argument, 0, 'E'},
3235 {"alt-autoscale", no_argument, 0, 'A'},
3236 {"alt-autoscale-min", no_argument, 0, 'J'},
3237 {"alt-autoscale-max", no_argument, 0, 'M'},
3238 {"no-gridfit", no_argument, 0, 'N'},
3239 {"units-exponent",required_argument, 0, 'X'},
3240 {"units-length",required_argument, 0, 'L'},
3241 {"units", required_argument, 0, LONGOPT_UNITS_SI },
3242 {"step", required_argument, 0, 'S'},
3243 {"tabwidth", required_argument, 0, 'T'},
3244 {"font-render-mode", required_argument, 0, 'R'},
3245 {"font-smoothing-threshold", required_argument, 0, 'B'},
3246 {"watermark", required_argument, 0, 'W'},
3247 {"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 */
3248 {0,0,0,0}};
3249 int option_index = 0;
3250 int opt;
3251 int col_start,col_end;
3253 opt = getopt_long(argc, argv,
3254 "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:I:zgjFYAMEX:L:S:T:NR:B:W:",
3255 long_options, &option_index);
3257 if (opt == EOF)
3258 break;
3260 switch(opt) {
3261 case 'I':
3262 im->extra_flags |= NOMINOR;
3263 break;
3264 case 'Y':
3265 im->extra_flags |= ALTYGRID;
3266 break;
3267 case 'A':
3268 im->extra_flags |= ALTAUTOSCALE;
3269 break;
3270 case 'J':
3271 im->extra_flags |= ALTAUTOSCALE_MIN;
3272 break;
3273 case 'M':
3274 im->extra_flags |= ALTAUTOSCALE_MAX;
3275 break;
3276 case 'j':
3277 im->extra_flags |= ONLY_GRAPH;
3278 break;
3279 case 'g':
3280 im->extra_flags |= NOLEGEND;
3281 break;
3282 case 'F':
3283 im->extra_flags |= FORCE_RULES_LEGEND;
3284 break;
3285 case LONGOPT_UNITS_SI:
3286 if(im->extra_flags & FORCE_UNITS) {
3287 rrd_set_error("--units can only be used once!");
3288 return;
3289 }
3290 if(strcmp(optarg,"si")==0)
3291 im->extra_flags |= FORCE_UNITS_SI;
3292 else {
3293 rrd_set_error("invalid argument for --units: %s", optarg );
3294 return;
3295 }
3296 break;
3297 case 'X':
3298 im->unitsexponent = atoi(optarg);
3299 break;
3300 case 'L':
3301 im->unitslength = atoi(optarg);
3302 im->forceleftspace = 1;
3303 break;
3304 case 'T':
3305 im->tabwidth = atof(optarg);
3306 break;
3307 case 'S':
3308 im->step = atoi(optarg);
3309 break;
3310 case 'N':
3311 im->gridfit = 0;
3312 break;
3313 case 's':
3314 if ((parsetime_error = parsetime(optarg, &start_tv))) {
3315 rrd_set_error( "start time: %s", parsetime_error );
3316 return;
3317 }
3318 break;
3319 case 'e':
3320 if ((parsetime_error = parsetime(optarg, &end_tv))) {
3321 rrd_set_error( "end time: %s", parsetime_error );
3322 return;
3323 }
3324 break;
3325 case 'x':
3326 if(strcmp(optarg,"none") == 0){
3327 im->draw_x_grid=0;
3328 break;
3329 };
3331 if(sscanf(optarg,
3332 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3333 scan_gtm,
3334 &im->xlab_user.gridst,
3335 scan_mtm,
3336 &im->xlab_user.mgridst,
3337 scan_ltm,
3338 &im->xlab_user.labst,
3339 &im->xlab_user.precis,
3340 &stroff) == 7 && stroff != 0){
3341 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
3342 im->xlab_form[sizeof(im->xlab_form)-1] = '\0';
3343 if((int)(im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
3344 rrd_set_error("unknown keyword %s",scan_gtm);
3345 return;
3346 } else if ((int)(im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
3347 rrd_set_error("unknown keyword %s",scan_mtm);
3348 return;
3349 } else if ((int)(im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
3350 rrd_set_error("unknown keyword %s",scan_ltm);
3351 return;
3352 }
3353 im->xlab_user.minsec = 1;
3354 im->xlab_user.stst = im->xlab_form;
3355 } else {
3356 rrd_set_error("invalid x-grid format");
3357 return;
3358 }
3359 break;
3360 case 'y':
3362 if(strcmp(optarg,"none") == 0){
3363 im->draw_y_grid=0;
3364 break;
3365 };
3367 if(sscanf(optarg,
3368 "%lf:%d",
3369 &im->ygridstep,
3370 &im->ylabfact) == 2) {
3371 if(im->ygridstep<=0){
3372 rrd_set_error("grid step must be > 0");
3373 return;
3374 } else if (im->ylabfact < 1){
3375 rrd_set_error("label factor must be > 0");
3376 return;
3377 }
3378 } else {
3379 rrd_set_error("invalid y-grid format");
3380 return;
3381 }
3382 break;
3383 case 'v':
3384 strncpy(im->ylegend,optarg,150);
3385 im->ylegend[150]='\0';
3386 break;
3387 case 'u':
3388 im->maxval = atof(optarg);
3389 break;
3390 case 'l':
3391 im->minval = atof(optarg);
3392 break;
3393 case 'b':
3394 im->base = atol(optarg);
3395 if(im->base != 1024 && im->base != 1000 ){
3396 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
3397 return;
3398 }
3399 break;
3400 case 'w':
3401 long_tmp = atol(optarg);
3402 if (long_tmp < 10) {
3403 rrd_set_error("width below 10 pixels");
3404 return;
3405 }
3406 im->xsize = long_tmp;
3407 break;
3408 case 'h':
3409 long_tmp = atol(optarg);
3410 if (long_tmp < 10) {
3411 rrd_set_error("height below 10 pixels");
3412 return;
3413 }
3414 im->ysize = long_tmp;
3415 break;
3416 case 'i':
3417 im->canvas->interlaced = 1;
3418 break;
3419 case 'r':
3420 im->rigid = 1;
3421 break;
3422 case 'f':
3423 im->imginfo = optarg;
3424 break;
3425 case 'a':
3426 if((int)(im->canvas->imgformat = if_conv(optarg)) == -1) {
3427 rrd_set_error("unsupported graphics format '%s'",optarg);
3428 return;
3429 }
3430 break;
3431 case 'z':
3432 im->lazy = 1;
3433 break;
3434 case 'E':
3435 im->slopemode = 1;
3436 break;
3438 case 'o':
3439 im->logarithmic = 1;
3440 break;
3441 case 'c':
3442 if(sscanf(optarg,
3443 "%10[A-Z]#%n%8lx%n",
3444 col_nam,&col_start,&color,&col_end) == 2){
3445 int ci;
3446 int col_len = col_end - col_start;
3447 switch (col_len){
3448 case 3:
3449 color = (
3450 ((color & 0xF00) * 0x110000) |
3451 ((color & 0x0F0) * 0x011000) |
3452 ((color & 0x00F) * 0x001100) |
3453 0x000000FF
3454 );
3455 break;
3456 case 4:
3457 color = (
3458 ((color & 0xF000) * 0x11000) |
3459 ((color & 0x0F00) * 0x01100) |
3460 ((color & 0x00F0) * 0x00110) |
3461 ((color & 0x000F) * 0x00011)
3462 );
3463 break;
3464 case 6:
3465 color = (color << 8) + 0xff /* shift left by 8 */;
3466 break;
3467 case 8:
3468 break;
3469 default:
3470 rrd_set_error("the color format is #RRGGBB[AA]");
3471 return;
3472 }
3473 if((ci=grc_conv(col_nam)) != -1){
3474 im->graph_col[ci]=color;
3475 } else {
3476 rrd_set_error("invalid color name '%s'",col_nam);
3477 return;
3478 }
3479 } else {
3480 rrd_set_error("invalid color def format");
3481 return;
3482 }
3483 break;
3484 case 'n':{
3485 char prop[15];
3486 double size = 1;
3487 char font[1024] = "";
3489 if(sscanf(optarg,
3490 "%10[A-Z]:%lf:%1000s",
3491 prop,&size,font) >= 2){
3492 int sindex,propidx;
3493 if((sindex=text_prop_conv(prop)) != -1){
3494 for (propidx=sindex;propidx<TEXT_PROP_LAST;propidx++){
3495 if (size > 0){
3496 im->text_prop[propidx].size=size;
3497 }
3498 if (strlen(font) > 0){
3499 strcpy(im->text_prop[propidx].font,font);
3500 }
3501 if (propidx==sindex && sindex != 0) break;
3502 }
3503 } else {
3504 rrd_set_error("invalid fonttag '%s'",prop);
3505 return;
3506 }
3507 } else {
3508 rrd_set_error("invalid text property format");
3509 return;
3510 }
3511 break;
3512 }
3513 case 'm':
3514 im->canvas->zoom = atof(optarg);
3515 if (im->canvas->zoom <= 0.0) {
3516 rrd_set_error("zoom factor must be > 0");
3517 return;
3518 }
3519 break;
3520 case 't':
3521 strncpy(im->title,optarg,150);
3522 im->title[150]='\0';
3523 break;
3525 case 'R':
3526 if ( strcmp( optarg, "normal" ) == 0 )
3527 im->canvas->aa_type = AA_NORMAL;
3528 else if ( strcmp( optarg, "light" ) == 0 )
3529 im->canvas->aa_type = AA_LIGHT;
3530 else if ( strcmp( optarg, "mono" ) == 0 )
3531 im->canvas->aa_type = AA_NONE;
3532 else
3533 {
3534 rrd_set_error("unknown font-render-mode '%s'", optarg );
3535 return;
3536 }
3537 break;
3539 case 'B':
3540 im->canvas->font_aa_threshold = atof(optarg);
3541 break;
3543 case 'W':
3544 strncpy(im->watermark,optarg,100);
3545 im->watermark[99]='\0';
3546 break;
3548 case '?':
3549 if (optopt != 0)
3550 rrd_set_error("unknown option '%c'", optopt);
3551 else
3552 rrd_set_error("unknown option '%s'",argv[optind-1]);
3553 return;
3554 }
3555 }
3557 if (optind >= argc) {
3558 rrd_set_error("missing filename");
3559 return;
3560 }
3562 if (im->logarithmic == 1 && im->minval <= 0){
3563 rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");
3564 return;
3565 }
3567 if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
3568 /* error string is set in parsetime.c */
3569 return;
3570 }
3572 if (start_tmp < 3600*24*365*10){
3573 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
3574 return;
3575 }
3577 if (end_tmp < start_tmp) {
3578 rrd_set_error("start (%ld) should be less than end (%ld)",
3579 start_tmp, end_tmp);
3580 return;
3581 }
3583 im->start = start_tmp;
3584 im->end = end_tmp;
3585 im->step = max((long)im->step, (im->end-im->start)/im->xsize);
3586 }
3588 int
3589 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
3590 {
3591 char *color;
3592 graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
3594 color=strstr(var,"#");
3595 if (color==NULL) {
3596 if (optional==0) {
3597 rrd_set_error("Found no color in %s",err);
3598 return 0;
3599 }
3600 return 0;
3601 } else {
3602 int n=0;
3603 char *rest;
3604 gfx_color_t col;
3606 rest=strstr(color,":");
3607 if (rest!=NULL)
3608 n=rest-color;
3609 else
3610 n=strlen(color);
3612 switch (n) {
3613 case 7:
3614 sscanf(color,"#%6lx%n",&col,&n);
3615 col = (col << 8) + 0xff /* shift left by 8 */;
3616 if (n!=7) rrd_set_error("Color problem in %s",err);
3617 break;
3618 case 9:
3619 sscanf(color,"#%8lx%n",&col,&n);
3620 if (n==9) break;
3621 default:
3622 rrd_set_error("Color problem in %s",err);
3623 }
3624 if (rrd_test_error()) return 0;
3625 gdp->col = col;
3626 return n;
3627 }
3628 }
3631 int bad_format(char *fmt) {
3632 char *ptr;
3633 int n=0;
3634 ptr = fmt;
3635 while (*ptr != '\0')
3636 if (*ptr++ == '%') {
3638 /* line cannot end with percent char */
3639 if (*ptr == '\0') return 1;
3641 /* '%s', '%S' and '%%' are allowed */
3642 if (*ptr == 's' || *ptr == 'S' || *ptr == '%') ptr++;
3644 /* %c is allowed (but use only with vdef!) */
3645 else if (*ptr == 'c') {
3646 ptr++;
3647 n=1;
3648 }
3650 /* or else '% 6.2lf' and such are allowed */
3651 else {
3652 /* optional padding character */
3653 if (*ptr == ' ' || *ptr == '+' || *ptr == '-') ptr++;
3655 /* This should take care of 'm.n' with all three optional */
3656 while (*ptr >= '0' && *ptr <= '9') ptr++;
3657 if (*ptr == '.') ptr++;
3658 while (*ptr >= '0' && *ptr <= '9') ptr++;
3660 /* Either 'le', 'lf' or 'lg' must follow here */
3661 if (*ptr++ != 'l') return 1;
3662 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g') ptr++;
3663 else return 1;
3664 n++;
3665 }
3666 }
3668 return (n!=1);
3669 }
3672 int
3673 vdef_parse(gdes,str)
3674 struct graph_desc_t *gdes;
3675 const char *const str;
3676 {
3677 /* A VDEF currently is either "func" or "param,func"
3678 * so the parsing is rather simple. Change if needed.
3679 */
3680 double param;
3681 char func[30];
3682 int n;
3684 n=0;
3685 sscanf(str,"%le,%29[A-Z]%n",¶m,func,&n);
3686 if (n== (int)strlen(str)) { /* matched */
3687 ;
3688 } else {
3689 n=0;
3690 sscanf(str,"%29[A-Z]%n",func,&n);
3691 if (n== (int)strlen(str)) { /* matched */
3692 param=DNAN;
3693 } else {
3694 rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3695 ,str
3696 ,gdes->vname
3697 );
3698 return -1;
3699 }
3700 }
3701 if (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3702 else if (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3703 else if (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3704 else if (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3705 else if (!strcmp("TOTAL", func)) gdes->vf.op = VDEF_TOTAL;
3706 else if (!strcmp("FIRST", func)) gdes->vf.op = VDEF_FIRST;
3707 else if (!strcmp("LAST", func)) gdes->vf.op = VDEF_LAST;
3708 else if (!strcmp("LSLSLOPE", func)) gdes->vf.op = VDEF_LSLSLOPE;
3709 else if (!strcmp("LSLINT", func)) gdes->vf.op = VDEF_LSLINT;
3710 else if (!strcmp("LSLCORREL",func)) gdes->vf.op = VDEF_LSLCORREL;
3711 else {
3712 rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3713 ,func
3714 ,gdes->vname
3715 );
3716 return -1;
3717 };
3719 switch (gdes->vf.op) {
3720 case VDEF_PERCENT:
3721 if (isnan(param)) { /* no parameter given */
3722 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3723 ,func
3724 ,gdes->vname
3725 );
3726 return -1;
3727 };
3728 if (param>=0.0 && param<=100.0) {
3729 gdes->vf.param = param;
3730 gdes->vf.val = DNAN; /* undefined */
3731 gdes->vf.when = 0; /* undefined */
3732 } else {
3733 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3734 ,param
3735 ,gdes->vname
3736 );
3737 return -1;
3738 };
3739 break;
3740 case VDEF_MAXIMUM:
3741 case VDEF_AVERAGE:
3742 case VDEF_MINIMUM:
3743 case VDEF_TOTAL:
3744 case VDEF_FIRST:
3745 case VDEF_LAST:
3746 case VDEF_LSLSLOPE:
3747 case VDEF_LSLINT:
3748 case VDEF_LSLCORREL:
3749 if (isnan(param)) {
3750 gdes->vf.param = DNAN;
3751 gdes->vf.val = DNAN;
3752 gdes->vf.when = 0;
3753 } else {
3754 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3755 ,func
3756 ,gdes->vname
3757 );
3758 return -1;
3759 };
3760 break;
3761 };
3762 return 0;
3763 }
3766 int
3767 vdef_calc(im,gdi)
3768 image_desc_t *im;
3769 int gdi;
3770 {
3771 graph_desc_t *src,*dst;
3772 rrd_value_t *data;
3773 long step,steps;
3775 dst = &im->gdes[gdi];
3776 src = &im->gdes[dst->vidx];
3777 data = src->data + src->ds;
3778 steps = (src->end - src->start) / src->step;
3780 #if 0
3781 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3782 ,src->start
3783 ,src->end
3784 ,steps
3785 );
3786 #endif
3788 switch (dst->vf.op) {
3789 case VDEF_PERCENT: {
3790 rrd_value_t * array;
3791 int field;
3794 if ((array = malloc(steps*sizeof(double)))==NULL) {
3795 rrd_set_error("malloc VDEV_PERCENT");
3796 return -1;
3797 }
3798 for (step=0;step < steps; step++) {
3799 array[step]=data[step*src->ds_cnt];
3800 }
3801 qsort(array,step,sizeof(double),vdef_percent_compar);
3803 field = (steps-1)*dst->vf.param/100;
3804 dst->vf.val = array[field];
3805 dst->vf.when = 0; /* no time component */
3806 free(array);
3807 #if 0
3808 for(step=0;step<steps;step++)
3809 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3810 #endif
3811 }
3812 break;
3813 case VDEF_MAXIMUM:
3814 step=0;
3815 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3816 if (step == steps) {
3817 dst->vf.val = DNAN;
3818 dst->vf.when = 0;
3819 } else {
3820 dst->vf.val = data[step*src->ds_cnt];
3821 dst->vf.when = src->start + (step+1)*src->step;
3822 }
3823 while (step != steps) {
3824 if (finite(data[step*src->ds_cnt])) {
3825 if (data[step*src->ds_cnt] > dst->vf.val) {
3826 dst->vf.val = data[step*src->ds_cnt];
3827 dst->vf.when = src->start + (step+1)*src->step;
3828 }
3829 }
3830 step++;
3831 }
3832 break;
3833 case VDEF_TOTAL:
3834 case VDEF_AVERAGE: {
3835 int cnt=0;
3836 double sum=0.0;
3837 for (step=0;step<steps;step++) {
3838 if (finite(data[step*src->ds_cnt])) {
3839 sum += data[step*src->ds_cnt];
3840 cnt ++;
3841 };
3842 }
3843 if (cnt) {
3844 if (dst->vf.op == VDEF_TOTAL) {
3845 dst->vf.val = sum*src->step;
3846 dst->vf.when = 0; /* no time component */
3847 } else {
3848 dst->vf.val = sum/cnt;
3849 dst->vf.when = 0; /* no time component */
3850 };
3851 } else {
3852 dst->vf.val = DNAN;
3853 dst->vf.when = 0;
3854 }
3855 }
3856 break;
3857 case VDEF_MINIMUM:
3858 step=0;
3859 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3860 if (step == steps) {
3861 dst->vf.val = DNAN;
3862 dst->vf.when = 0;
3863 } else {
3864 dst->vf.val = data[step*src->ds_cnt];
3865 dst->vf.when = src->start + (step+1)*src->step;
3866 }
3867 while (step != steps) {
3868 if (finite(data[step*src->ds_cnt])) {
3869 if (data[step*src->ds_cnt] < dst->vf.val) {
3870 dst->vf.val = data[step*src->ds_cnt];
3871 dst->vf.when = src->start + (step+1)*src->step;
3872 }
3873 }
3874 step++;
3875 }
3876 break;
3877 case VDEF_FIRST:
3878 /* The time value returned here is one step before the
3879 * actual time value. This is the start of the first
3880 * non-NaN interval.
3881 */
3882 step=0;
3883 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3884 if (step == steps) { /* all entries were NaN */
3885 dst->vf.val = DNAN;
3886 dst->vf.when = 0;
3887 } else {
3888 dst->vf.val = data[step*src->ds_cnt];
3889 dst->vf.when = src->start + step*src->step;
3890 }
3891 break;
3892 case VDEF_LAST:
3893 /* The time value returned here is the
3894 * actual time value. This is the end of the last
3895 * non-NaN interval.
3896 */
3897 step=steps-1;
3898 while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3899 if (step < 0) { /* all entries were NaN */
3900 dst->vf.val = DNAN;
3901 dst->vf.when = 0;
3902 } else {
3903 dst->vf.val = data[step*src->ds_cnt];
3904 dst->vf.when = src->start + (step+1)*src->step;
3905 }
3906 break;
3907 case VDEF_LSLSLOPE:
3908 case VDEF_LSLINT:
3909 case VDEF_LSLCORREL:{
3910 /* Bestfit line by linear least squares method */
3912 int cnt=0;
3913 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl ;
3914 SUMx = 0; SUMy = 0; SUMxy = 0; SUMxx = 0; SUMyy = 0;
3916 for (step=0;step<steps;step++) {
3917 if (finite(data[step*src->ds_cnt])) {
3918 cnt++;
3919 SUMx += step;
3920 SUMxx += step * step;
3921 SUMxy += step * data[step*src->ds_cnt];
3922 SUMy += data[step*src->ds_cnt];
3923 SUMyy += data[step*src->ds_cnt]*data[step*src->ds_cnt];
3924 };
3925 }
3927 slope = ( SUMx*SUMy - cnt*SUMxy ) / ( SUMx*SUMx - cnt*SUMxx );
3928 y_intercept = ( SUMy - slope*SUMx ) / cnt;
3929 correl = (SUMxy - (SUMx*SUMy)/cnt) / sqrt((SUMxx - (SUMx*SUMx)/cnt)*(SUMyy - (SUMy*SUMy)/cnt));
3931 if (cnt) {
3932 if (dst->vf.op == VDEF_LSLSLOPE) {
3933 dst->vf.val = slope;
3934 dst->vf.when = 0;
3935 } else if (dst->vf.op == VDEF_LSLINT) {
3936 dst->vf.val = y_intercept;
3937 dst->vf.when = 0;
3938 } else if (dst->vf.op == VDEF_LSLCORREL) {
3939 dst->vf.val = correl;
3940 dst->vf.when = 0;
3941 };
3943 } else {
3944 dst->vf.val = DNAN;
3945 dst->vf.when = 0;
3946 }
3947 }
3948 break;
3949 }
3950 return 0;
3951 }
3953 /* NaN < -INF < finite_values < INF */
3954 int
3955 vdef_percent_compar(a,b)
3956 const void *a,*b;
3957 {
3958 /* Equality is not returned; this doesn't hurt except
3959 * (maybe) for a little performance.
3960 */
3962 /* First catch NaN values. They are smallest */
3963 if (isnan( *(double *)a )) return -1;
3964 if (isnan( *(double *)b )) return 1;
3966 /* NaN doesn't reach this part so INF and -INF are extremes.
3967 * The sign from isinf() is compatible with the sign we return
3968 */
3969 if (isinf( *(double *)a )) return isinf( *(double *)a );
3970 if (isinf( *(double *)b )) return isinf( *(double *)b );
3972 /* If we reach this, both values must be finite */
3973 if ( *(double *)a < *(double *)b ) return -1; else return 1;
3974 }