1 /****************************************************************************
2 * RRDtool 1.1.x Copyright Tobias Oetiker, 1997 - 2002
3 ****************************************************************************
4 * rrd__graph.c make creates ne rrds
5 ****************************************************************************/
8 #include <sys/stat.h>
10 #include "rrd_tool.h"
12 #ifdef WIN32
13 #include <io.h>
14 #include <fcntl.h>
15 #endif
17 #ifdef HAVE_TIME_H
18 #include <time.h>
19 #endif
21 #ifdef HAVE_LOCALE_H
22 #include <locale.h>
23 #endif
25 #include "rrd_graph.h"
27 /* some constant definitions */
30 #ifndef RRD_DEFAULT_FONT
31 #ifdef WIN32
32 #define RRD_DEFAULT_FONT "c:/winnt/fonts/COUR.TTF"
33 #else
34 #define RRD_DEFAULT_FONT "/usr/share/fonts/truetype/openoffice/ariosor.ttf"
35 /* #define RRD_DEFAULT_FONT "/usr/share/fonts/truetype/Arial.ttf" */
36 #endif
37 #endif
40 text_prop_t text_prop[] = {
41 { 10.0, RRD_DEFAULT_FONT }, /* default */
42 { 12.0, RRD_DEFAULT_FONT }, /* title */
43 { 8.0, RRD_DEFAULT_FONT }, /* axis */
44 { 10.0, RRD_DEFAULT_FONT }, /* unit */
45 { 10.0, RRD_DEFAULT_FONT } /* legend */
46 };
48 xlab_t xlab[] = {
49 {0, TMT_SECOND,30, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
50 {2, TMT_MINUTE,1, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
51 {5, TMT_MINUTE,2, TMT_MINUTE,10, TMT_MINUTE,10, 0,"%H:%M"},
52 {10, TMT_MINUTE,5, TMT_MINUTE,20, TMT_MINUTE,20, 0,"%H:%M"},
53 {30, TMT_MINUTE,10, TMT_HOUR,1, TMT_HOUR,1, 0,"%H:%M"},
54 {60, TMT_MINUTE,30, TMT_HOUR,2, TMT_HOUR,2, 0,"%H:%M"},
55 {180, TMT_HOUR,1, TMT_HOUR,6, TMT_HOUR,6, 0,"%H:%M"},
56 /*{300, TMT_HOUR,3, TMT_HOUR,12, TMT_HOUR,12, 12*3600,"%a %p"}, this looks silly*/
57 {600, TMT_HOUR,6, TMT_DAY,1, TMT_DAY,1, 24*3600,"%a"},
58 {1800, TMT_HOUR,12, TMT_DAY,1, TMT_DAY,2, 24*3600,"%a"},
59 {3600, TMT_DAY,1, TMT_WEEK,1, TMT_WEEK,1, 7*24*3600,"Week %V"},
60 {3*3600, TMT_WEEK,1, TMT_MONTH,1, TMT_WEEK,2, 7*24*3600,"Week %V"},
61 {6*3600, TMT_MONTH,1, TMT_MONTH,1, TMT_MONTH,1, 30*24*3600,"%b"},
62 {48*3600, TMT_MONTH,1, TMT_MONTH,3, TMT_MONTH,3, 30*24*3600,"%b"},
63 {10*24*3600, TMT_YEAR,1, TMT_YEAR,1, TMT_YEAR,1, 365*24*3600,"%y"},
64 {-1,TMT_MONTH,0,TMT_MONTH,0,TMT_MONTH,0,0,""}
65 };
67 /* sensible logarithmic y label intervals ...
68 the first element of each row defines the possible starting points on the
69 y axis ... the other specify the */
71 double yloglab[][12]= {{ 1e9, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
72 { 1e3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
73 { 1e1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
74 /* { 1e1, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, */
75 { 1e1, 1, 2.5, 5, 7.5, 0, 0, 0, 0, 0, 0, 0 },
76 { 1e1, 1, 2, 4, 6, 8, 0, 0, 0, 0, 0, 0 },
77 { 1e1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0 },
78 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }};
80 /* sensible y label intervals ...*/
82 ylab_t ylab[]= {
83 {0.1, {1,2, 5,10}},
84 {0.2, {1,5,10,20}},
85 {0.5, {1,2, 4,10}},
86 {1.0, {1,2, 5,10}},
87 {2.0, {1,5,10,20}},
88 {5.0, {1,2, 4,10}},
89 {10.0, {1,2, 5,10}},
90 {20.0, {1,5,10,20}},
91 {50.0, {1,2, 4,10}},
92 {100.0, {1,2, 5,10}},
93 {200.0, {1,5,10,20}},
94 {500.0, {1,2, 4,10}},
95 {0.0, {0,0,0,0}}};
98 gfx_color_t graph_col[] = /* default colors */
99 { 0xFFFFFFFF, /* canvas */
100 0xF0F0F0FF, /* background */
101 0xD0D0D0FF, /* shade A */
102 0xA0A0A0FF, /* shade B */
103 0x909090FF, /* grid */
104 0xE05050FF, /* major grid */
105 0x000000FF, /* font */
106 0x000000FF, /* frame */
107 0xFF0000FF /* arrow */
108 };
111 /* #define DEBUG */
113 #ifdef DEBUG
114 # define DPRINT(x) (void)(printf x, printf("\n"))
115 #else
116 # define DPRINT(x)
117 #endif
120 /* initialize with xtr(im,0); */
121 int
122 xtr(image_desc_t *im,time_t mytime){
123 static double pixie;
124 if (mytime==0){
125 pixie = (double) im->xsize / (double)(im->end - im->start);
126 return im->xorigin;
127 }
128 return (int)((double)im->xorigin
129 + pixie * ( mytime - im->start ) );
130 }
132 /* translate data values into y coordinates */
133 double
134 ytr(image_desc_t *im, double value){
135 static double pixie;
136 double yval;
137 if (isnan(value)){
138 if(!im->logarithmic)
139 pixie = (double) im->ysize / (im->maxval - im->minval);
140 else
141 pixie = (double) im->ysize / (log10(im->maxval) - log10(im->minval));
142 yval = im->yorigin;
143 } else if(!im->logarithmic) {
144 yval = im->yorigin - pixie * (value - im->minval);
145 } else {
146 if (value < im->minval) {
147 yval = im->yorigin;
148 } else {
149 yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
150 }
151 }
152 /* make sure we don't return anything too unreasonable. GD lib can
153 get terribly slow when drawing lines outside its scope. This is
154 especially problematic in connection with the rigid option */
155 if (! im->rigid) {
156 /* keep yval as-is */
157 } else if (yval > im->yorigin) {
158 yval = im->yorigin+2;
159 } else if (yval < im->yorigin - im->ysize){
160 yval = im->yorigin - im->ysize - 2;
161 }
162 return yval;
163 }
167 /* conversion function for symbolic entry names */
170 #define conv_if(VV,VVV) \
171 if (strcmp(#VV, string) == 0) return VVV ;
173 enum gf_en gf_conv(char *string){
175 conv_if(PRINT,GF_PRINT)
176 conv_if(GPRINT,GF_GPRINT)
177 conv_if(COMMENT,GF_COMMENT)
178 conv_if(HRULE,GF_HRULE)
179 conv_if(VRULE,GF_VRULE)
180 conv_if(LINE,GF_LINE)
181 conv_if(AREA,GF_AREA)
182 conv_if(STACK,GF_STACK)
183 conv_if(TICK,GF_TICK)
184 conv_if(DEF,GF_DEF)
185 conv_if(CDEF,GF_CDEF)
186 conv_if(VDEF,GF_VDEF)
187 conv_if(PART,GF_PART)
188 conv_if(XPORT,GF_XPORT)
190 return (-1);
191 }
193 enum gfx_if_en if_conv(char *string){
195 conv_if(PNG,IF_PNG)
196 conv_if(SVG,IF_SVG)
197 conv_if(EPS,IF_EPS)
198 conv_if(PDF,IF_PDF)
200 return (-1);
201 }
203 enum tmt_en tmt_conv(char *string){
205 conv_if(SECOND,TMT_SECOND)
206 conv_if(MINUTE,TMT_MINUTE)
207 conv_if(HOUR,TMT_HOUR)
208 conv_if(DAY,TMT_DAY)
209 conv_if(WEEK,TMT_WEEK)
210 conv_if(MONTH,TMT_MONTH)
211 conv_if(YEAR,TMT_YEAR)
212 return (-1);
213 }
215 enum grc_en grc_conv(char *string){
217 conv_if(BACK,GRC_BACK)
218 conv_if(CANVAS,GRC_CANVAS)
219 conv_if(SHADEA,GRC_SHADEA)
220 conv_if(SHADEB,GRC_SHADEB)
221 conv_if(GRID,GRC_GRID)
222 conv_if(MGRID,GRC_MGRID)
223 conv_if(FONT,GRC_FONT)
224 conv_if(FRAME,GRC_FRAME)
225 conv_if(ARROW,GRC_ARROW)
227 return -1;
228 }
230 enum text_prop_en text_prop_conv(char *string){
232 conv_if(DEFAULT,TEXT_PROP_DEFAULT)
233 conv_if(TITLE,TEXT_PROP_TITLE)
234 conv_if(AXIS,TEXT_PROP_AXIS)
235 conv_if(UNIT,TEXT_PROP_UNIT)
236 conv_if(LEGEND,TEXT_PROP_LEGEND)
237 return -1;
238 }
241 #undef conv_if
243 int
244 im_free(image_desc_t *im)
245 {
246 unsigned long i,ii;
248 if (im == NULL) return 0;
249 for(i=0;i<(unsigned)im->gdes_c;i++){
250 if (im->gdes[i].data_first){
251 /* careful here, because a single pointer can occur several times */
252 free (im->gdes[i].data);
253 if (im->gdes[i].ds_namv){
254 for (ii=0;ii<im->gdes[i].ds_cnt;ii++)
255 free(im->gdes[i].ds_namv[ii]);
256 free(im->gdes[i].ds_namv);
257 }
258 }
259 free (im->gdes[i].p_data);
260 free (im->gdes[i].rpnp);
261 }
262 free(im->gdes);
263 gfx_destroy(im->canvas);
264 return 0;
265 }
267 /* find SI magnitude symbol for the given number*/
268 void
269 auto_scale(
270 image_desc_t *im, /* image description */
271 double *value,
272 char **symb_ptr,
273 double *magfact
274 )
275 {
277 char *symbol[] = {"a", /* 10e-18 Atto */
278 "f", /* 10e-15 Femto */
279 "p", /* 10e-12 Pico */
280 "n", /* 10e-9 Nano */
281 "u", /* 10e-6 Micro */
282 "m", /* 10e-3 Milli */
283 " ", /* Base */
284 "k", /* 10e3 Kilo */
285 "M", /* 10e6 Mega */
286 "G", /* 10e9 Giga */
287 "T", /* 10e12 Tera */
288 "P", /* 10e15 Peta */
289 "E"};/* 10e18 Exa */
291 int symbcenter = 6;
292 int sindex;
294 if (*value == 0.0 || isnan(*value) ) {
295 sindex = 0;
296 *magfact = 1.0;
297 } else {
298 sindex = floor(log(fabs(*value))/log((double)im->base));
299 *magfact = pow((double)im->base, (double)sindex);
300 (*value) /= (*magfact);
301 }
302 if ( sindex <= symbcenter && sindex >= -symbcenter) {
303 (*symb_ptr) = symbol[sindex+symbcenter];
304 }
305 else {
306 (*symb_ptr) = "?";
307 }
308 }
311 /* find SI magnitude symbol for the numbers on the y-axis*/
312 void
313 si_unit(
314 image_desc_t *im /* image description */
315 )
316 {
318 char symbol[] = {'a', /* 10e-18 Atto */
319 'f', /* 10e-15 Femto */
320 'p', /* 10e-12 Pico */
321 'n', /* 10e-9 Nano */
322 'u', /* 10e-6 Micro */
323 'm', /* 10e-3 Milli */
324 ' ', /* Base */
325 'k', /* 10e3 Kilo */
326 'M', /* 10e6 Mega */
327 'G', /* 10e9 Giga */
328 'T', /* 10e12 Tera */
329 'P', /* 10e15 Peta */
330 'E'};/* 10e18 Exa */
332 int symbcenter = 6;
333 double digits;
335 if (im->unitsexponent != 9999) {
336 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
337 digits = floor(im->unitsexponent / 3);
338 } else {
339 digits = floor( log( max( fabs(im->minval),fabs(im->maxval)))/log((double)im->base));
340 }
341 im->magfact = pow((double)im->base , digits);
343 #ifdef DEBUG
344 printf("digits %6.3f im->magfact %6.3f\n",digits,im->magfact);
345 #endif
347 if ( ((digits+symbcenter) < sizeof(symbol)) &&
348 ((digits+symbcenter) >= 0) )
349 im->symbol = symbol[(int)digits+symbcenter];
350 else
351 im->symbol = ' ';
352 }
354 /* move min and max values around to become sensible */
356 void
357 expand_range(image_desc_t *im)
358 {
359 double sensiblevalues[] ={1000.0,900.0,800.0,750.0,700.0,
360 600.0,500.0,400.0,300.0,250.0,
361 200.0,125.0,100.0,90.0,80.0,
362 75.0,70.0,60.0,50.0,40.0,30.0,
363 25.0,20.0,10.0,9.0,8.0,
364 7.0,6.0,5.0,4.0,3.5,3.0,
365 2.5,2.0,1.8,1.5,1.2,1.0,
366 0.8,0.7,0.6,0.5,0.4,0.3,0.2,0.1,0.0,-1};
368 double scaled_min,scaled_max;
369 double adj;
370 int i;
374 #ifdef DEBUG
375 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
376 im->minval,im->maxval,im->magfact);
377 #endif
379 if (isnan(im->ygridstep)){
380 if(im->extra_flags & ALTAUTOSCALE) {
381 /* measure the amplitude of the function. Make sure that
382 graph boundaries are slightly higher then max/min vals
383 so we can see amplitude on the graph */
384 double delt, fact;
386 delt = im->maxval - im->minval;
387 adj = delt * 0.1;
388 fact = 2.0 * pow(10.0,
389 floor(log10(max(fabs(im->minval), fabs(im->maxval)))) - 2);
390 if (delt < fact) {
391 adj = (fact - delt) * 0.55;
392 #ifdef DEBUG
393 printf("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n", im->minval, im->maxval, delt, fact, adj);
394 #endif
395 }
396 im->minval -= adj;
397 im->maxval += adj;
398 }
399 else if(im->extra_flags & ALTAUTOSCALE_MAX) {
400 /* measure the amplitude of the function. Make sure that
401 graph boundaries are slightly higher than max vals
402 so we can see amplitude on the graph */
403 adj = (im->maxval - im->minval) * 0.1;
404 im->maxval += adj;
405 }
406 else {
407 scaled_min = im->minval / im->magfact;
408 scaled_max = im->maxval / im->magfact;
410 for (i=1; sensiblevalues[i] > 0; i++){
411 if (sensiblevalues[i-1]>=scaled_min &&
412 sensiblevalues[i]<=scaled_min)
413 im->minval = sensiblevalues[i]*(im->magfact);
415 if (-sensiblevalues[i-1]<=scaled_min &&
416 -sensiblevalues[i]>=scaled_min)
417 im->minval = -sensiblevalues[i-1]*(im->magfact);
419 if (sensiblevalues[i-1] >= scaled_max &&
420 sensiblevalues[i] <= scaled_max)
421 im->maxval = sensiblevalues[i-1]*(im->magfact);
423 if (-sensiblevalues[i-1]<=scaled_max &&
424 -sensiblevalues[i] >=scaled_max)
425 im->maxval = -sensiblevalues[i]*(im->magfact);
426 }
427 }
428 } else {
429 /* adjust min and max to the grid definition if there is one */
430 im->minval = (double)im->ylabfact * im->ygridstep *
431 floor(im->minval / ((double)im->ylabfact * im->ygridstep));
432 im->maxval = (double)im->ylabfact * im->ygridstep *
433 ceil(im->maxval /( (double)im->ylabfact * im->ygridstep));
434 }
436 #ifdef DEBUG
437 fprintf(stderr,"SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
438 im->minval,im->maxval,im->magfact);
439 #endif
440 }
442 void
443 apply_gridfit(image_desc_t *im)
444 {
445 if (isnan(im->minval) || isnan(im->maxval))
446 return;
447 ytr(im,DNAN);
448 if (im->logarithmic) {
449 double ya, yb, ypix, ypixfrac;
450 double log10_range = log10(im->maxval) - log10(im->minval);
451 ya = pow((double)10, floor(log10(im->minval)));
452 while (ya < im->minval)
453 ya *= 10;
454 if (ya > im->maxval)
455 return; /* don't have y=10^x gridline */
456 yb = ya * 10;
457 if (yb <= im->maxval) {
458 /* we have at least 2 y=10^x gridlines.
459 Make sure distance between them in pixels
460 are an integer by expanding im->maxval */
461 double y_pixel_delta = ytr(im, ya) - ytr(im, yb);
462 double factor = y_pixel_delta / floor(y_pixel_delta);
463 double new_log10_range = factor * log10_range;
464 double new_ymax_log10 = log10(im->minval) + new_log10_range;
465 im->maxval = pow(10, new_ymax_log10);
466 ytr(im, DNAN); /* reset precalc */
467 log10_range = log10(im->maxval) - log10(im->minval);
468 }
469 /* make sure first y=10^x gridline is located on
470 integer pixel position by moving scale slightly
471 downwards (sub-pixel movement) */
472 ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
473 ypixfrac = ypix - floor(ypix);
474 if (ypixfrac > 0 && ypixfrac < 1) {
475 double yfrac = ypixfrac / im->ysize;
476 im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
477 im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
478 ytr(im, DNAN); /* reset precalc */
479 }
480 } else {
481 /* Make sure we have an integer pixel distance between
482 each minor gridline */
483 double ypos1 = ytr(im, im->minval);
484 double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
485 double y_pixel_delta = ypos1 - ypos2;
486 double factor = y_pixel_delta / floor(y_pixel_delta);
487 double new_range = factor * (im->maxval - im->minval);
488 double gridstep = im->ygrid_scale.gridstep;
489 double minor_y, minor_y_px, minor_y_px_frac;
490 im->maxval = im->minval + new_range;
491 ytr(im, DNAN); /* reset precalc */
492 /* make sure first minor gridline is on integer pixel y coord */
493 minor_y = gridstep * floor(im->minval / gridstep);
494 while (minor_y < im->minval)
495 minor_y += gridstep;
496 minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
497 minor_y_px_frac = minor_y_px - floor(minor_y_px);
498 if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
499 double yfrac = minor_y_px_frac / im->ysize;
500 double range = im->maxval - im->minval;
501 im->minval = im->minval - yfrac * range;
502 im->maxval = im->maxval - yfrac * range;
503 ytr(im, DNAN); /* reset precalc */
504 }
505 calc_horizontal_grid(im); /* recalc with changed im->maxval */
506 }
507 }
509 /* reduce data reimplementation by Alex */
511 void
512 reduce_data(
513 enum cf_en cf, /* which consolidation function ?*/
514 unsigned long cur_step, /* step the data currently is in */
515 time_t *start, /* start, end and step as requested ... */
516 time_t *end, /* ... by the application will be ... */
517 unsigned long *step, /* ... adjusted to represent reality */
518 unsigned long *ds_cnt, /* number of data sources in file */
519 rrd_value_t **data) /* two dimensional array containing the data */
520 {
521 int i,reduce_factor = ceil((double)(*step) / (double)cur_step);
522 unsigned long col,dst_row,row_cnt,start_offset,end_offset,skiprows=0;
523 rrd_value_t *srcptr,*dstptr;
525 (*step) = cur_step*reduce_factor; /* set new step size for reduced data */
526 dstptr = *data;
527 srcptr = *data;
528 row_cnt = ((*end)-(*start))/cur_step;
530 #ifdef DEBUG
531 #define DEBUG_REDUCE
532 #endif
533 #ifdef DEBUG_REDUCE
534 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
535 row_cnt,reduce_factor,*start,*end,cur_step);
536 for (col=0;col<row_cnt;col++) {
537 printf("time %10lu: ",*start+(col+1)*cur_step);
538 for (i=0;i<*ds_cnt;i++)
539 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
540 printf("\n");
541 }
542 #endif
544 /* We have to combine [reduce_factor] rows of the source
545 ** into one row for the destination. Doing this we also
546 ** need to take care to combine the correct rows. First
547 ** alter the start and end time so that they are multiples
548 ** of the new step time. We cannot reduce the amount of
549 ** time so we have to move the end towards the future and
550 ** the start towards the past.
551 */
552 end_offset = (*end) % (*step);
553 start_offset = (*start) % (*step);
555 /* If there is a start offset (which cannot be more than
556 ** one destination row), skip the appropriate number of
557 ** source rows and one destination row. The appropriate
558 ** number is what we do know (start_offset/cur_step) of
559 ** the new interval (*step/cur_step aka reduce_factor).
560 */
561 #ifdef DEBUG_REDUCE
562 printf("start_offset: %lu end_offset: %lu\n",start_offset,end_offset);
563 printf("row_cnt before: %lu\n",row_cnt);
564 #endif
565 if (start_offset) {
566 (*start) = (*start)-start_offset;
567 skiprows=reduce_factor-start_offset/cur_step;
568 srcptr+=skiprows* *ds_cnt;
569 for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
570 row_cnt-=skiprows;
571 }
572 #ifdef DEBUG_REDUCE
573 printf("row_cnt between: %lu\n",row_cnt);
574 #endif
576 /* At the end we have some rows that are not going to be
577 ** used, the amount is end_offset/cur_step
578 */
579 if (end_offset) {
580 (*end) = (*end)-end_offset+(*step);
581 skiprows = end_offset/cur_step;
582 row_cnt-=skiprows;
583 }
584 #ifdef DEBUG_REDUCE
585 printf("row_cnt after: %lu\n",row_cnt);
586 #endif
588 /* Sanity check: row_cnt should be multiple of reduce_factor */
589 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
591 if (row_cnt%reduce_factor) {
592 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
593 row_cnt,reduce_factor);
594 printf("BUG in reduce_data()\n");
595 exit(1);
596 }
598 /* Now combine reduce_factor intervals at a time
599 ** into one interval for the destination.
600 */
602 for (dst_row=0;row_cnt>=reduce_factor;dst_row++) {
603 for (col=0;col<(*ds_cnt);col++) {
604 rrd_value_t newval=DNAN;
605 unsigned long validval=0;
607 for (i=0;i<reduce_factor;i++) {
608 if (isnan(srcptr[i*(*ds_cnt)+col])) {
609 continue;
610 }
611 validval++;
612 if (isnan(newval)) newval = srcptr[i*(*ds_cnt)+col];
613 else {
614 switch (cf) {
615 case CF_HWPREDICT:
616 case CF_DEVSEASONAL:
617 case CF_DEVPREDICT:
618 case CF_SEASONAL:
619 case CF_AVERAGE:
620 newval += srcptr[i*(*ds_cnt)+col];
621 break;
622 case CF_MINIMUM:
623 newval = min (newval,srcptr[i*(*ds_cnt)+col]);
624 break;
625 case CF_FAILURES:
626 /* an interval contains a failure if any subintervals contained a failure */
627 case CF_MAXIMUM:
628 newval = max (newval,srcptr[i*(*ds_cnt)+col]);
629 break;
630 case CF_LAST:
631 newval = srcptr[i*(*ds_cnt)+col];
632 break;
633 }
634 }
635 }
636 if (validval == 0){newval = DNAN;} else{
637 switch (cf) {
638 case CF_HWPREDICT:
639 case CF_DEVSEASONAL:
640 case CF_DEVPREDICT:
641 case CF_SEASONAL:
642 case CF_AVERAGE:
643 newval /= validval;
644 break;
645 case CF_MINIMUM:
646 case CF_FAILURES:
647 case CF_MAXIMUM:
648 case CF_LAST:
649 break;
650 }
651 }
652 *dstptr++=newval;
653 }
654 srcptr+=(*ds_cnt)*reduce_factor;
655 row_cnt-=reduce_factor;
656 }
657 /* If we had to alter the endtime, we didn't have enough
658 ** source rows to fill the last row. Fill it with NaN.
659 */
660 if (end_offset) for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
661 #ifdef DEBUG_REDUCE
662 row_cnt = ((*end)-(*start))/ *step;
663 srcptr = *data;
664 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
665 row_cnt,*start,*end,*step);
666 for (col=0;col<row_cnt;col++) {
667 printf("time %10lu: ",*start+(col+1)*(*step));
668 for (i=0;i<*ds_cnt;i++)
669 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
670 printf("\n");
671 }
672 #endif
673 }
676 /* get the data required for the graphs from the
677 relevant rrds ... */
679 int
680 data_fetch(image_desc_t *im )
681 {
682 unsigned int i,ii;
683 int skip;
685 /* pull the data from the log files ... */
686 for (i=0;i<im->gdes_c;i++){
687 /* only GF_DEF elements fetch data */
688 if (im->gdes[i].gf != GF_DEF)
689 continue;
691 skip=0;
692 /* do we have it already ?*/
693 for (ii=0;ii<i;ii++) {
694 if (im->gdes[ii].gf != GF_DEF)
695 continue;
696 if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
697 && (im->gdes[i].cf == im->gdes[ii].cf)
698 && (im->gdes[i].start == im->gdes[ii].start)
699 && (im->gdes[i].end == im->gdes[ii].end)
700 && (im->gdes[i].step == im->gdes[ii].step)) {
701 /* OK, the data is already there.
702 ** Just copy the header portion
703 */
704 im->gdes[i].start = im->gdes[ii].start;
705 im->gdes[i].end = im->gdes[ii].end;
706 im->gdes[i].step = im->gdes[ii].step;
707 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
708 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
709 im->gdes[i].data = im->gdes[ii].data;
710 im->gdes[i].data_first = 0;
711 skip=1;
712 }
713 if (skip)
714 break;
715 }
716 if (! skip) {
717 unsigned long ft_step = im->gdes[i].step ;
719 if((rrd_fetch_fn(im->gdes[i].rrd,
720 im->gdes[i].cf,
721 &im->gdes[i].start,
722 &im->gdes[i].end,
723 &ft_step,
724 &im->gdes[i].ds_cnt,
725 &im->gdes[i].ds_namv,
726 &im->gdes[i].data)) == -1){
727 return -1;
728 }
729 im->gdes[i].data_first = 1;
731 if (ft_step < im->gdes[i].step) {
732 reduce_data(im->gdes[i].cf,
733 ft_step,
734 &im->gdes[i].start,
735 &im->gdes[i].end,
736 &im->gdes[i].step,
737 &im->gdes[i].ds_cnt,
738 &im->gdes[i].data);
739 } else {
740 im->gdes[i].step = ft_step;
741 }
742 }
744 /* lets see if the required data source is realy there */
745 for(ii=0;ii<im->gdes[i].ds_cnt;ii++){
746 if(strcmp(im->gdes[i].ds_namv[ii],im->gdes[i].ds_nam) == 0){
747 im->gdes[i].ds=ii; }
748 }
749 if (im->gdes[i].ds== -1){
750 rrd_set_error("No DS called '%s' in '%s'",
751 im->gdes[i].ds_nam,im->gdes[i].rrd);
752 return -1;
753 }
755 }
756 return 0;
757 }
759 /* evaluate the expressions in the CDEF functions */
761 /*************************************************************
762 * CDEF stuff
763 *************************************************************/
765 long
766 find_var_wrapper(void *arg1, char *key)
767 {
768 return find_var((image_desc_t *) arg1, key);
769 }
771 /* find gdes containing var*/
772 long
773 find_var(image_desc_t *im, char *key){
774 long ii;
775 for(ii=0;ii<im->gdes_c-1;ii++){
776 if((im->gdes[ii].gf == GF_DEF
777 || im->gdes[ii].gf == GF_VDEF
778 || im->gdes[ii].gf == GF_CDEF)
779 && (strcmp(im->gdes[ii].vname,key) == 0)){
780 return ii;
781 }
782 }
783 return -1;
784 }
786 /* find the largest common denominator for all the numbers
787 in the 0 terminated num array */
788 long
789 lcd(long *num){
790 long rest;
791 int i;
792 for (i=0;num[i+1]!=0;i++){
793 do {
794 rest=num[i] % num[i+1];
795 num[i]=num[i+1]; num[i+1]=rest;
796 } while (rest!=0);
797 num[i+1] = num[i];
798 }
799 /* return i==0?num[i]:num[i-1]; */
800 return num[i];
801 }
803 /* run the rpn calculator on all the VDEF and CDEF arguments */
804 int
805 data_calc( image_desc_t *im){
807 int gdi;
808 int dataidx;
809 long *steparray, rpi;
810 int stepcnt;
811 time_t now;
812 rpnstack_t rpnstack;
814 rpnstack_init(&rpnstack);
816 for (gdi=0;gdi<im->gdes_c;gdi++){
817 /* Look for GF_VDEF and GF_CDEF in the same loop,
818 * so CDEFs can use VDEFs and vice versa
819 */
820 switch (im->gdes[gdi].gf) {
821 case GF_XPORT:
822 break;
823 case GF_VDEF:
824 /* A VDEF has no DS. This also signals other parts
825 * of rrdtool that this is a VDEF value, not a CDEF.
826 */
827 im->gdes[gdi].ds_cnt = 0;
828 if (vdef_calc(im,gdi)) {
829 rrd_set_error("Error processing VDEF '%s'"
830 ,im->gdes[gdi].vname
831 );
832 rpnstack_free(&rpnstack);
833 return -1;
834 }
835 break;
836 case GF_CDEF:
837 im->gdes[gdi].ds_cnt = 1;
838 im->gdes[gdi].ds = 0;
839 im->gdes[gdi].data_first = 1;
840 im->gdes[gdi].start = 0;
841 im->gdes[gdi].end = 0;
842 steparray=NULL;
843 stepcnt = 0;
844 dataidx=-1;
846 /* Find the variables in the expression.
847 * - VDEF variables are substituted by their values
848 * and the opcode is changed into OP_NUMBER.
849 * - CDEF variables are analized for their step size,
850 * the lowest common denominator of all the step
851 * sizes of the data sources involved is calculated
852 * and the resulting number is the step size for the
853 * resulting data source.
854 */
855 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
856 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
857 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER){
858 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
859 if (im->gdes[ptr].ds_cnt == 0) {
860 #if 0
861 printf("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
862 im->gdes[gdi].vname,
863 im->gdes[ptr].vname);
864 printf("DEBUG: value from vdef is %f\n",im->gdes[ptr].vf.val);
865 #endif
866 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
867 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
868 } else {
869 if ((steparray =
870 rrd_realloc(steparray,
871 (++stepcnt+1)*sizeof(*steparray)))==NULL){
872 rrd_set_error("realloc steparray");
873 rpnstack_free(&rpnstack);
874 return -1;
875 };
877 steparray[stepcnt-1] = im->gdes[ptr].step;
879 /* adjust start and end of cdef (gdi) so
880 * that it runs from the latest start point
881 * to the earliest endpoint of any of the
882 * rras involved (ptr)
883 */
884 if(im->gdes[gdi].start < im->gdes[ptr].start)
885 im->gdes[gdi].start = im->gdes[ptr].start;
887 if(im->gdes[gdi].end == 0 ||
888 im->gdes[gdi].end > im->gdes[ptr].end)
889 im->gdes[gdi].end = im->gdes[ptr].end;
891 /* store pointer to the first element of
892 * the rra providing data for variable,
893 * further save step size and data source
894 * count of this rra
895 */
896 im->gdes[gdi].rpnp[rpi].data = im->gdes[ptr].data + im->gdes[ptr].ds;
897 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
898 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
900 /* backoff the *.data ptr; this is done so
901 * rpncalc() function doesn't have to treat
902 * the first case differently
903 */
904 } /* if ds_cnt != 0 */
905 } /* if OP_VARIABLE */
906 } /* loop through all rpi */
908 /* move the data pointers to the correct period */
909 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
910 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
911 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER){
912 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
913 if(im->gdes[gdi].start > im->gdes[ptr].start) {
914 im->gdes[gdi].rpnp[rpi].data += im->gdes[gdi].rpnp[rpi].ds_cnt;
915 }
916 }
917 }
920 if(steparray == NULL){
921 rrd_set_error("rpn expressions without DEF"
922 " or CDEF variables are not supported");
923 rpnstack_free(&rpnstack);
924 return -1;
925 }
926 steparray[stepcnt]=0;
927 /* Now find the resulting step. All steps in all
928 * used RRAs have to be visited
929 */
930 im->gdes[gdi].step = lcd(steparray);
931 free(steparray);
932 if((im->gdes[gdi].data = malloc((
933 (im->gdes[gdi].end-im->gdes[gdi].start)
934 / im->gdes[gdi].step)
935 * sizeof(double)))==NULL){
936 rrd_set_error("malloc im->gdes[gdi].data");
937 rpnstack_free(&rpnstack);
938 return -1;
939 }
941 /* Step through the new cdef results array and
942 * calculate the values
943 */
944 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
945 now<=im->gdes[gdi].end;
946 now += im->gdes[gdi].step)
947 {
948 rpnp_t *rpnp = im -> gdes[gdi].rpnp;
950 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
951 * in this case we are advancing by timesteps;
952 * we use the fact that time_t is a synonym for long
953 */
954 if (rpn_calc(rpnp,&rpnstack,(long) now,
955 im->gdes[gdi].data,++dataidx) == -1) {
956 /* rpn_calc sets the error string */
957 rpnstack_free(&rpnstack);
958 return -1;
959 }
960 } /* enumerate over time steps within a CDEF */
961 break;
962 default:
963 continue;
964 }
965 } /* enumerate over CDEFs */
966 rpnstack_free(&rpnstack);
967 return 0;
968 }
970 /* massage data so, that we get one value for each x coordinate in the graph */
971 int
972 data_proc( image_desc_t *im ){
973 long i,ii;
974 double pixstep = (double)(im->end-im->start)
975 /(double)im->xsize; /* how much time
976 passes in one pixel */
977 double paintval;
978 double minval=DNAN,maxval=DNAN;
980 unsigned long gr_time;
982 /* memory for the processed data */
983 for(i=0;i<im->gdes_c;i++) {
984 if((im->gdes[i].gf==GF_LINE) ||
985 (im->gdes[i].gf==GF_AREA) ||
986 (im->gdes[i].gf==GF_TICK) ||
987 (im->gdes[i].gf==GF_STACK)) {
988 if((im->gdes[i].p_data = malloc((im->xsize +1)
989 * sizeof(rrd_value_t)))==NULL){
990 rrd_set_error("malloc data_proc");
991 return -1;
992 }
993 }
994 }
996 for (i=0;i<im->xsize;i++) { /* for each pixel */
997 long vidx;
998 gr_time = im->start+pixstep*i; /* time of the current step */
999 paintval=0.0;
1001 for (ii=0;ii<im->gdes_c;ii++) {
1002 double value;
1003 switch (im->gdes[ii].gf) {
1004 case GF_LINE:
1005 case GF_AREA:
1006 case GF_TICK:
1007 if (!im->gdes[ii].stack)
1008 paintval = 0.0;
1009 case GF_STACK:
1010 value = im->gdes[ii].yrule;
1011 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1012 /* The time of the data doesn't necessarily match
1013 ** the time of the graph. Beware.
1014 */
1015 vidx = im->gdes[ii].vidx;
1016 if ( (gr_time >= im->gdes[vidx].start) &&
1017 (gr_time <= im->gdes[vidx].end) ) {
1018 value = im->gdes[vidx].data[
1019 (unsigned long) floor(
1020 (double)(gr_time - im->gdes[vidx].start)
1021 / im->gdes[vidx].step)
1022 * im->gdes[vidx].ds_cnt
1023 + im->gdes[vidx].ds
1024 ];
1025 } else {
1026 value = DNAN;
1027 }
1028 };
1030 if (! isnan(value)) {
1031 paintval += value;
1032 im->gdes[ii].p_data[i] = paintval;
1033 /* GF_TICK: the data values are not
1034 ** relevant for min and max
1035 */
1036 if (finite(paintval) && im->gdes[ii].gf != GF_TICK ) {
1037 if (isnan(minval) || paintval < minval)
1038 minval = paintval;
1039 if (isnan(maxval) || paintval > maxval)
1040 maxval = paintval;
1041 }
1042 } else {
1043 im->gdes[ii].p_data[i] = DNAN;
1044 }
1045 break;
1046 default:
1047 break;
1048 }
1049 }
1050 }
1052 /* if min or max have not been asigned a value this is because
1053 there was no data in the graph ... this is not good ...
1054 lets set these to dummy values then ... */
1056 if (isnan(minval)) minval = 0.0;
1057 if (isnan(maxval)) maxval = 1.0;
1059 /* adjust min and max values */
1060 if (isnan(im->minval)
1061 /* don't adjust low-end with log scale */
1062 || ((!im->logarithmic && !im->rigid) && im->minval > minval)
1063 )
1064 im->minval = minval;
1065 if (isnan(im->maxval)
1066 || (!im->rigid && im->maxval < maxval)
1067 ) {
1068 if (im->logarithmic)
1069 im->maxval = maxval * 1.1;
1070 else
1071 im->maxval = maxval;
1072 }
1073 /* make sure min and max are not equal */
1074 if (im->minval == im->maxval) {
1075 im->maxval *= 1.01;
1076 if (! im->logarithmic) {
1077 im->minval *= 0.99;
1078 }
1079 /* make sure min and max are not both zero */
1080 if (im->maxval == 0.0) {
1081 im->maxval = 1.0;
1082 }
1083 }
1084 return 0;
1085 }
1089 /* identify the point where the first gridline, label ... gets placed */
1091 time_t
1092 find_first_time(
1093 time_t start, /* what is the initial time */
1094 enum tmt_en baseint, /* what is the basic interval */
1095 long basestep /* how many if these do we jump a time */
1096 )
1097 {
1098 struct tm tm;
1099 localtime_r(&start, &tm);
1100 switch(baseint){
1101 case TMT_SECOND:
1102 tm.tm_sec -= tm.tm_sec % basestep; break;
1103 case TMT_MINUTE:
1104 tm.tm_sec=0;
1105 tm.tm_min -= tm.tm_min % basestep;
1106 break;
1107 case TMT_HOUR:
1108 tm.tm_sec=0;
1109 tm.tm_min = 0;
1110 tm.tm_hour -= tm.tm_hour % basestep; break;
1111 case TMT_DAY:
1112 /* we do NOT look at the basestep for this ... */
1113 tm.tm_sec=0;
1114 tm.tm_min = 0;
1115 tm.tm_hour = 0; break;
1116 case TMT_WEEK:
1117 /* we do NOT look at the basestep for this ... */
1118 tm.tm_sec=0;
1119 tm.tm_min = 0;
1120 tm.tm_hour = 0;
1121 tm.tm_mday -= tm.tm_wday -1; /* -1 because we want the monday */
1122 if (tm.tm_wday==0) tm.tm_mday -= 7; /* we want the *previous* monday */
1123 break;
1124 case TMT_MONTH:
1125 tm.tm_sec=0;
1126 tm.tm_min = 0;
1127 tm.tm_hour = 0;
1128 tm.tm_mday = 1;
1129 tm.tm_mon -= tm.tm_mon % basestep; break;
1131 case TMT_YEAR:
1132 tm.tm_sec=0;
1133 tm.tm_min = 0;
1134 tm.tm_hour = 0;
1135 tm.tm_mday = 1;
1136 tm.tm_mon = 0;
1137 tm.tm_year -= (tm.tm_year+1900) % basestep;
1139 }
1140 return mktime(&tm);
1141 }
1142 /* identify the point where the next gridline, label ... gets placed */
1143 time_t
1144 find_next_time(
1145 time_t current, /* what is the initial time */
1146 enum tmt_en baseint, /* what is the basic interval */
1147 long basestep /* how many if these do we jump a time */
1148 )
1149 {
1150 struct tm tm;
1151 time_t madetime;
1152 localtime_r(¤t, &tm);
1153 do {
1154 switch(baseint){
1155 case TMT_SECOND:
1156 tm.tm_sec += basestep; break;
1157 case TMT_MINUTE:
1158 tm.tm_min += basestep; break;
1159 case TMT_HOUR:
1160 tm.tm_hour += basestep; break;
1161 case TMT_DAY:
1162 tm.tm_mday += basestep; break;
1163 case TMT_WEEK:
1164 tm.tm_mday += 7*basestep; break;
1165 case TMT_MONTH:
1166 tm.tm_mon += basestep; break;
1167 case TMT_YEAR:
1168 tm.tm_year += basestep;
1169 }
1170 madetime = mktime(&tm);
1171 } while (madetime == -1); /* this is necessary to skip impssible times
1172 like the daylight saving time skips */
1173 return madetime;
1175 }
1178 /* calculate values required for PRINT and GPRINT functions */
1180 int
1181 print_calc(image_desc_t *im, char ***prdata)
1182 {
1183 long i,ii,validsteps;
1184 double printval;
1185 time_t printtime;
1186 int graphelement = 0;
1187 long vidx;
1188 int max_ii;
1189 double magfact = -1;
1190 char *si_symb = "";
1191 char *percent_s;
1192 int prlines = 1;
1193 if (im->imginfo) prlines++;
1194 for(i=0;i<im->gdes_c;i++){
1195 switch(im->gdes[i].gf){
1196 case GF_PRINT:
1197 prlines++;
1198 if(((*prdata) = rrd_realloc((*prdata),prlines*sizeof(char *)))==NULL){
1199 rrd_set_error("realloc prdata");
1200 return 0;
1201 }
1202 case GF_GPRINT:
1203 /* PRINT and GPRINT can now print VDEF generated values.
1204 * There's no need to do any calculations on them as these
1205 * calculations were already made.
1206 */
1207 vidx = im->gdes[i].vidx;
1208 if (im->gdes[vidx].gf==GF_VDEF) { /* simply use vals */
1209 printval = im->gdes[vidx].vf.val;
1210 printtime = im->gdes[vidx].vf.when;
1211 } else { /* need to calculate max,min,avg etcetera */
1212 max_ii =((im->gdes[vidx].end
1213 - im->gdes[vidx].start)
1214 / im->gdes[vidx].step
1215 * im->gdes[vidx].ds_cnt);
1216 printval = DNAN;
1217 validsteps = 0;
1218 for( ii=im->gdes[vidx].ds;
1219 ii < max_ii;
1220 ii+=im->gdes[vidx].ds_cnt){
1221 if (! finite(im->gdes[vidx].data[ii]))
1222 continue;
1223 if (isnan(printval)){
1224 printval = im->gdes[vidx].data[ii];
1225 validsteps++;
1226 continue;
1227 }
1229 switch (im->gdes[i].cf){
1230 case CF_HWPREDICT:
1231 case CF_DEVPREDICT:
1232 case CF_DEVSEASONAL:
1233 case CF_SEASONAL:
1234 case CF_AVERAGE:
1235 validsteps++;
1236 printval += im->gdes[vidx].data[ii];
1237 break;
1238 case CF_MINIMUM:
1239 printval = min( printval, im->gdes[vidx].data[ii]);
1240 break;
1241 case CF_FAILURES:
1242 case CF_MAXIMUM:
1243 printval = max( printval, im->gdes[vidx].data[ii]);
1244 break;
1245 case CF_LAST:
1246 printval = im->gdes[vidx].data[ii];
1247 }
1248 }
1249 if (im->gdes[i].cf==CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1250 if (validsteps > 1) {
1251 printval = (printval / validsteps);
1252 }
1253 }
1254 } /* prepare printval */
1256 if (!strcmp(im->gdes[i].format,"%c")) { /* VDEF time print */
1257 char ctime_buf[128]; /* PS: for ctime_r, must be >= 26 chars */
1258 if (im->gdes[i].gf == GF_PRINT){
1259 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1260 sprintf((*prdata)[prlines-2],"%s (%lu)",
1261 ctime_r(&printtime,ctime_buf),printtime);
1262 (*prdata)[prlines-1] = NULL;
1263 } else {
1264 sprintf(im->gdes[i].legend,"%s (%lu)",
1265 ctime_r(&printtime,ctime_buf),printtime);
1266 graphelement = 1;
1267 }
1268 } else {
1269 if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) {
1270 /* Magfact is set to -1 upon entry to print_calc. If it
1271 * is still less than 0, then we need to run auto_scale.
1272 * Otherwise, put the value into the correct units. If
1273 * the value is 0, then do not set the symbol or magnification
1274 * so next the calculation will be performed again. */
1275 if (magfact < 0.0) {
1276 auto_scale(im,&printval,&si_symb,&magfact);
1277 if (printval == 0.0)
1278 magfact = -1.0;
1279 } else {
1280 printval /= magfact;
1281 }
1282 *(++percent_s) = 's';
1283 } else if (strstr(im->gdes[i].format,"%s") != NULL) {
1284 auto_scale(im,&printval,&si_symb,&magfact);
1285 }
1287 if (im->gdes[i].gf == GF_PRINT){
1288 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1289 (*prdata)[prlines-1] = NULL;
1290 if (bad_format(im->gdes[i].format)) {
1291 rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1292 return -1;
1293 }
1294 #ifdef HAVE_SNPRINTF
1295 snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
1296 #else
1297 sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
1298 #endif
1299 } else {
1300 /* GF_GPRINT */
1302 if (bad_format(im->gdes[i].format)) {
1303 rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1304 return -1;
1305 }
1306 #ifdef HAVE_SNPRINTF
1307 snprintf(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
1308 #else
1309 sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
1310 #endif
1311 graphelement = 1;
1312 }
1313 }
1314 break;
1315 case GF_LINE:
1316 case GF_AREA:
1317 case GF_TICK:
1318 case GF_STACK:
1319 case GF_HRULE:
1320 case GF_VRULE:
1321 graphelement = 1;
1322 break;
1323 case GF_COMMENT:
1324 case GF_DEF:
1325 case GF_CDEF:
1326 case GF_VDEF:
1327 case GF_PART:
1328 case GF_XPORT:
1329 break;
1330 }
1331 }
1332 return graphelement;
1333 }
1336 /* place legends with color spots */
1337 int
1338 leg_place(image_desc_t *im)
1339 {
1340 /* graph labels */
1341 int interleg = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1342 int box =im->text_prop[TEXT_PROP_LEGEND].size*1.5;
1343 int border = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1344 int fill=0, fill_last;
1345 int leg_c = 0;
1346 int leg_x = border, leg_y = im->yimg;
1347 int leg_cc;
1348 int glue = 0;
1349 int i,ii, mark = 0;
1350 char prt_fctn; /*special printfunctions */
1351 int *legspace;
1353 if( !(im->extra_flags & NOLEGEND) ) {
1354 if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1355 rrd_set_error("malloc for legspace");
1356 return -1;
1357 }
1359 for(i=0;i<im->gdes_c;i++){
1360 fill_last = fill;
1362 /* hid legends for rules which are not displayed */
1364 if (im->gdes[i].gf == GF_HRULE &&
1365 (im->gdes[i].yrule < im->minval || im->gdes[i].yrule > im->maxval))
1366 im->gdes[i].legend[0] = '\0';
1368 if (im->gdes[i].gf == GF_VRULE &&
1369 (im->gdes[i].xrule < im->start || im->gdes[i].xrule > im->end))
1370 im->gdes[i].legend[0] = '\0';
1372 leg_cc = strlen(im->gdes[i].legend);
1374 /* is there a controle code ant the end of the legend string ? */
1375 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\') {
1376 prt_fctn = im->gdes[i].legend[leg_cc-1];
1377 leg_cc -= 2;
1378 im->gdes[i].legend[leg_cc] = '\0';
1379 } else {
1380 prt_fctn = '\0';
1381 }
1382 /* remove exess space */
1383 while (prt_fctn=='g' &&
1384 leg_cc > 0 &&
1385 im->gdes[i].legend[leg_cc-1]==' '){
1386 leg_cc--;
1387 im->gdes[i].legend[leg_cc]='\0';
1388 }
1389 if (leg_cc != 0 ){
1390 legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1392 if (fill > 0){
1393 /* no interleg space if string ends in \g */
1394 fill += legspace[i];
1395 }
1396 if (im->gdes[i].gf != GF_GPRINT &&
1397 im->gdes[i].gf != GF_COMMENT) {
1398 fill += box;
1399 }
1400 fill += gfx_get_text_width(im->canvas, fill+border,
1401 im->text_prop[TEXT_PROP_LEGEND].font,
1402 im->text_prop[TEXT_PROP_LEGEND].size,
1403 im->tabwidth,
1404 im->gdes[i].legend);
1405 leg_c++;
1406 } else {
1407 legspace[i]=0;
1408 }
1409 /* who said there was a special tag ... ?*/
1410 if (prt_fctn=='g') {
1411 prt_fctn = '\0';
1412 }
1413 if (prt_fctn == '\0') {
1414 if (i == im->gdes_c -1 ) prt_fctn ='l';
1416 /* is it time to place the legends ? */
1417 if (fill > im->ximg - 2*border){
1418 if (leg_c > 1) {
1419 /* go back one */
1420 i--;
1421 fill = fill_last;
1422 leg_c--;
1423 prt_fctn = 'j';
1424 } else {
1425 prt_fctn = 'l';
1426 }
1428 }
1429 }
1432 if (prt_fctn != '\0'){
1433 leg_x = border;
1434 if (leg_c >= 2 && prt_fctn == 'j') {
1435 glue = (im->ximg - fill - 2* border) / (leg_c-1);
1436 } else {
1437 glue = 0;
1438 }
1439 if (prt_fctn =='c') leg_x = (im->ximg - fill) / 2.0;
1440 if (prt_fctn =='r') leg_x = im->ximg - fill - border;
1442 for(ii=mark;ii<=i;ii++){
1443 if(im->gdes[ii].legend[0]=='\0')
1444 continue;
1445 im->gdes[ii].leg_x = leg_x;
1446 im->gdes[ii].leg_y = leg_y;
1447 leg_x +=
1448 gfx_get_text_width(im->canvas, leg_x,
1449 im->text_prop[TEXT_PROP_LEGEND].font,
1450 im->text_prop[TEXT_PROP_LEGEND].size,
1451 im->tabwidth,
1452 im->gdes[ii].legend)
1453 + legspace[ii]
1454 + glue;
1455 if (im->gdes[ii].gf != GF_GPRINT &&
1456 im->gdes[ii].gf != GF_COMMENT)
1457 leg_x += box;
1458 }
1459 leg_y = leg_y + im->text_prop[TEXT_PROP_LEGEND].size*1.2;
1460 if (prt_fctn == 's') leg_y -= im->text_prop[TEXT_PROP_LEGEND].size*1.2;
1461 fill = 0;
1462 leg_c = 0;
1463 mark = ii;
1464 }
1465 }
1466 im->yimg = leg_y;
1467 free(legspace);
1468 }
1469 return 0;
1470 }
1472 /* create a grid on the graph. it determines what to do
1473 from the values of xsize, start and end */
1475 /* the xaxis labels are determined from the number of seconds per pixel
1476 in the requested graph */
1480 int
1481 calc_horizontal_grid(image_desc_t *im)
1482 {
1483 double range;
1484 double scaledrange;
1485 int pixel,i;
1486 int gridind;
1487 int decimals, fractionals;
1489 im->ygrid_scale.labfact=2;
1490 gridind=-1;
1491 range = im->maxval - im->minval;
1492 scaledrange = range / im->magfact;
1494 /* does the scale of this graph make it impossible to put lines
1495 on it? If so, give up. */
1496 if (isnan(scaledrange)) {
1497 return 0;
1498 }
1500 /* find grid spaceing */
1501 pixel=1;
1502 if(isnan(im->ygridstep)){
1503 if(im->extra_flags & ALTYGRID) {
1504 /* find the value with max number of digits. Get number of digits */
1505 decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))));
1506 if(decimals <= 0) /* everything is small. make place for zero */
1507 decimals = 1;
1509 fractionals = floor(log10(range));
1510 if(fractionals < 0) /* small amplitude. */
1511 sprintf(im->ygrid_scale.labfmt, "%%%d.%df", decimals - fractionals + 1, -fractionals + 1);
1512 else
1513 sprintf(im->ygrid_scale.labfmt, "%%%d.1f", decimals + 1);
1514 im->ygrid_scale.gridstep = pow((double)10, (double)fractionals);
1515 if(im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1516 im->ygrid_scale.gridstep = 0.1;
1517 /* should have at least 5 lines but no more then 15 */
1518 if(range/im->ygrid_scale.gridstep < 5)
1519 im->ygrid_scale.gridstep /= 10;
1520 if(range/im->ygrid_scale.gridstep > 15)
1521 im->ygrid_scale.gridstep *= 10;
1522 if(range/im->ygrid_scale.gridstep > 5) {
1523 im->ygrid_scale.labfact = 1;
1524 if(range/im->ygrid_scale.gridstep > 8)
1525 im->ygrid_scale.labfact = 2;
1526 }
1527 else {
1528 im->ygrid_scale.gridstep /= 5;
1529 im->ygrid_scale.labfact = 5;
1530 }
1531 }
1532 else {
1533 for(i=0;ylab[i].grid > 0;i++){
1534 pixel = im->ysize / (scaledrange / ylab[i].grid);
1535 if (gridind == -1 && pixel > 5) {
1536 gridind = i;
1537 break;
1538 }
1539 }
1541 for(i=0; i<4;i++) {
1542 if (pixel * ylab[gridind].lfac[i] >= 2 * im->text_prop[TEXT_PROP_AXIS].size) {
1543 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1544 break;
1545 }
1546 }
1548 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1549 }
1550 } else {
1551 im->ygrid_scale.gridstep = im->ygridstep;
1552 im->ygrid_scale.labfact = im->ylabfact;
1553 }
1554 return 1;
1555 }
1557 int draw_horizontal_grid(image_desc_t *im)
1558 {
1559 int i;
1560 double scaledstep;
1561 char graph_label[100];
1562 double X0=im->xorigin;
1563 double X1=im->xorigin+im->xsize;
1565 int sgrid = (int)( im->minval / im->ygrid_scale.gridstep - 1);
1566 int egrid = (int)( im->maxval / im->ygrid_scale.gridstep + 1);
1567 scaledstep = im->ygrid_scale.gridstep/im->magfact;
1568 for (i = sgrid; i <= egrid; i++){
1569 double Y0=ytr(im,im->ygrid_scale.gridstep*i);
1570 if ( Y0 >= im->yorigin-im->ysize
1571 && Y0 <= im->yorigin){
1572 if(i % im->ygrid_scale.labfact == 0){
1573 if (i==0 || im->symbol == ' ') {
1574 if(scaledstep < 1){
1575 if(im->extra_flags & ALTYGRID) {
1576 sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*i);
1577 }
1578 else {
1579 sprintf(graph_label,"%4.1f",scaledstep*i);
1580 }
1581 } else {
1582 sprintf(graph_label,"%4.0f",scaledstep*i);
1583 }
1584 }else {
1585 if(scaledstep < 1){
1586 sprintf(graph_label,"%4.1f %c",scaledstep*i, im->symbol);
1587 } else {
1588 sprintf(graph_label,"%4.0f %c",scaledstep*i, im->symbol);
1589 }
1590 }
1592 gfx_new_text ( im->canvas,
1593 X0-im->text_prop[TEXT_PROP_AXIS].size/1.5, Y0,
1594 im->graph_col[GRC_FONT],
1595 im->text_prop[TEXT_PROP_AXIS].font,
1596 im->text_prop[TEXT_PROP_AXIS].size,
1597 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1598 graph_label );
1599 gfx_new_dashed_line ( im->canvas,
1600 X0-2,Y0,
1601 X1+2,Y0,
1602 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1603 im->grid_dash_on, im->grid_dash_off);
1605 } else if (!(im->extra_flags & NOMINOR)) {
1606 gfx_new_dashed_line ( im->canvas,
1607 X0-1,Y0,
1608 X1+1,Y0,
1609 GRIDWIDTH, im->graph_col[GRC_GRID],
1610 im->grid_dash_on, im->grid_dash_off);
1612 }
1613 }
1614 }
1615 return 1;
1616 }
1618 /* logaritmic horizontal grid */
1619 int
1620 horizontal_log_grid(image_desc_t *im)
1621 {
1622 double pixpex;
1623 int ii,i;
1624 int minoridx=0, majoridx=0;
1625 char graph_label[100];
1626 double X0,X1,Y0;
1627 double value, pixperstep, minstep;
1629 /* find grid spaceing */
1630 pixpex= (double)im->ysize / (log10(im->maxval) - log10(im->minval));
1632 if (isnan(pixpex)) {
1633 return 0;
1634 }
1636 for(i=0;yloglab[i][0] > 0;i++){
1637 minstep = log10(yloglab[i][0]);
1638 for(ii=1;yloglab[i][ii+1] > 0;ii++){
1639 if(yloglab[i][ii+2]==0){
1640 minstep = log10(yloglab[i][ii+1])-log10(yloglab[i][ii]);
1641 break;
1642 }
1643 }
1644 pixperstep = pixpex * minstep;
1645 if(pixperstep > 5){minoridx = i;}
1646 if(pixperstep > 2 * im->text_prop[TEXT_PROP_LEGEND].size){majoridx = i;}
1647 }
1649 X0=im->xorigin;
1650 X1=im->xorigin+im->xsize;
1651 /* paint minor grid */
1652 for (value = pow((double)10, log10(im->minval)
1653 - fmod(log10(im->minval),log10(yloglab[minoridx][0])));
1654 value <= im->maxval;
1655 value *= yloglab[minoridx][0]){
1656 if (value < im->minval) continue;
1657 i=0;
1658 while(yloglab[minoridx][++i] > 0){
1659 Y0 = ytr(im,value * yloglab[minoridx][i]);
1660 if (Y0 <= im->yorigin - im->ysize) break;
1661 gfx_new_dashed_line ( im->canvas,
1662 X0-1,Y0,
1663 X1+1,Y0,
1664 GRIDWIDTH, im->graph_col[GRC_GRID],
1665 im->grid_dash_on, im->grid_dash_off);
1666 }
1667 }
1669 /* paint major grid and labels*/
1670 for (value = pow((double)10, log10(im->minval)
1671 - fmod(log10(im->minval),log10(yloglab[majoridx][0])));
1672 value <= im->maxval;
1673 value *= yloglab[majoridx][0]){
1674 if (value < im->minval) continue;
1675 i=0;
1676 while(yloglab[majoridx][++i] > 0){
1677 Y0 = ytr(im,value * yloglab[majoridx][i]);
1678 if (Y0 <= im->yorigin - im->ysize) break;
1679 gfx_new_dashed_line ( im->canvas,
1680 X0-2,Y0,
1681 X1+2,Y0,
1682 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1683 im->grid_dash_on, im->grid_dash_off);
1685 sprintf(graph_label,"%3.0e",value * yloglab[majoridx][i]);
1686 gfx_new_text ( im->canvas,
1687 X0-im->text_prop[TEXT_PROP_AXIS].size/1.5, Y0,
1688 im->graph_col[GRC_FONT],
1689 im->text_prop[TEXT_PROP_AXIS].font,
1690 im->text_prop[TEXT_PROP_AXIS].size,
1691 im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1692 graph_label );
1693 }
1694 }
1695 return 1;
1696 }
1699 void
1700 vertical_grid(
1701 image_desc_t *im )
1702 {
1703 int xlab_sel; /* which sort of label and grid ? */
1704 time_t ti, tilab, timajor;
1705 long factor;
1706 char graph_label[100];
1707 double X0,Y0,Y1; /* points for filled graph and more*/
1708 struct tm tm;
1710 /* the type of time grid is determined by finding
1711 the number of seconds per pixel in the graph */
1714 if(im->xlab_user.minsec == -1){
1715 factor=(im->end - im->start)/im->xsize;
1716 xlab_sel=0;
1717 while ( xlab[xlab_sel+1].minsec != -1
1718 && xlab[xlab_sel+1].minsec <= factor){ xlab_sel++; }
1719 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
1720 im->xlab_user.gridst = xlab[xlab_sel].gridst;
1721 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
1722 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
1723 im->xlab_user.labtm = xlab[xlab_sel].labtm;
1724 im->xlab_user.labst = xlab[xlab_sel].labst;
1725 im->xlab_user.precis = xlab[xlab_sel].precis;
1726 im->xlab_user.stst = xlab[xlab_sel].stst;
1727 }
1729 /* y coords are the same for every line ... */
1730 Y0 = im->yorigin;
1731 Y1 = im->yorigin-im->ysize;
1734 /* paint the minor grid */
1735 if (!(im->extra_flags & NOMINOR))
1736 {
1737 for(ti = find_first_time(im->start,
1738 im->xlab_user.gridtm,
1739 im->xlab_user.gridst),
1740 timajor = find_first_time(im->start,
1741 im->xlab_user.mgridtm,
1742 im->xlab_user.mgridst);
1743 ti < im->end;
1744 ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
1745 ){
1746 /* are we inside the graph ? */
1747 if (ti < im->start || ti > im->end) continue;
1748 while (timajor < ti) {
1749 timajor = find_next_time(timajor,
1750 im->xlab_user.mgridtm, im->xlab_user.mgridst);
1751 }
1752 if (ti == timajor) continue; /* skip as falls on major grid line */
1753 X0 = xtr(im,ti);
1754 gfx_new_dashed_line(im->canvas,X0,Y0+1, X0,Y1-1,GRIDWIDTH,
1755 im->graph_col[GRC_GRID],
1756 im->grid_dash_on, im->grid_dash_off);
1758 }
1759 }
1761 /* paint the major grid */
1762 for(ti = find_first_time(im->start,
1763 im->xlab_user.mgridtm,
1764 im->xlab_user.mgridst);
1765 ti < im->end;
1766 ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
1767 ){
1768 /* are we inside the graph ? */
1769 if (ti < im->start || ti > im->end) continue;
1770 X0 = xtr(im,ti);
1771 gfx_new_dashed_line(im->canvas,X0,Y0+3, X0,Y1-2,MGRIDWIDTH,
1772 im->graph_col[GRC_MGRID],
1773 im->grid_dash_on, im->grid_dash_off);
1775 }
1776 /* paint the labels below the graph */
1777 for(ti = find_first_time(im->start,
1778 im->xlab_user.labtm,
1779 im->xlab_user.labst);
1780 ti <= im->end;
1781 ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
1782 ){
1783 tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
1784 /* are we inside the graph ? */
1785 if (ti < im->start || ti > im->end) continue;
1787 #if HAVE_STRFTIME
1788 localtime_r(&tilab, &tm);
1789 strftime(graph_label,99,im->xlab_user.stst, &tm);
1790 #else
1791 # error "your libc has no strftime I guess we'll abort the exercise here."
1792 #endif
1793 gfx_new_text ( im->canvas,
1794 xtr(im,tilab), Y0+im->text_prop[TEXT_PROP_AXIS].size/1.5,
1795 im->graph_col[GRC_FONT],
1796 im->text_prop[TEXT_PROP_AXIS].font,
1797 im->text_prop[TEXT_PROP_AXIS].size,
1798 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP,
1799 graph_label );
1801 }
1803 }
1806 void
1807 axis_paint(
1808 image_desc_t *im
1809 )
1810 {
1811 /* draw x and y axis */
1812 gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
1813 im->xorigin+im->xsize,im->yorigin-im->ysize,
1814 GRIDWIDTH, im->graph_col[GRC_GRID]);
1816 gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
1817 im->xorigin+im->xsize,im->yorigin-im->ysize,
1818 GRIDWIDTH, im->graph_col[GRC_GRID]);
1820 gfx_new_line ( im->canvas, im->xorigin-4,im->yorigin,
1821 im->xorigin+im->xsize+4,im->yorigin,
1822 MGRIDWIDTH, im->graph_col[GRC_GRID]);
1824 gfx_new_line ( im->canvas, im->xorigin,im->yorigin+4,
1825 im->xorigin,im->yorigin-im->ysize-4,
1826 MGRIDWIDTH, im->graph_col[GRC_GRID]);
1829 /* arrow for X axis direction */
1830 gfx_new_area ( im->canvas,
1831 im->xorigin+im->xsize+3, im->yorigin-3,
1832 im->xorigin+im->xsize+3, im->yorigin+4,
1833 im->xorigin+im->xsize+8, im->yorigin+0.5, /* LINEOFFSET */
1834 im->graph_col[GRC_ARROW]);
1838 }
1840 void
1841 grid_paint(image_desc_t *im)
1842 {
1843 long i;
1844 int res=0;
1845 double X0,Y0; /* points for filled graph and more*/
1846 gfx_node_t *node;
1848 /* draw 3d border */
1849 node = gfx_new_area (im->canvas, 0,im->yimg,
1850 2,im->yimg-2,
1851 2,2,im->graph_col[GRC_SHADEA]);
1852 gfx_add_point( node , im->ximg - 2, 2 );
1853 gfx_add_point( node , im->ximg, 0 );
1854 gfx_add_point( node , 0,0 );
1855 /* gfx_add_point( node , 0,im->yimg ); */
1857 node = gfx_new_area (im->canvas, 2,im->yimg-2,
1858 im->ximg-2,im->yimg-2,
1859 im->ximg - 2, 2,
1860 im->graph_col[GRC_SHADEB]);
1861 gfx_add_point( node , im->ximg,0);
1862 gfx_add_point( node , im->ximg,im->yimg);
1863 gfx_add_point( node , 0,im->yimg);
1864 /* gfx_add_point( node , 0,im->yimg ); */
1867 if (im->draw_x_grid == 1 )
1868 vertical_grid(im);
1870 if (im->draw_y_grid == 1){
1871 if(im->logarithmic){
1872 res = horizontal_log_grid(im);
1873 } else {
1874 res = draw_horizontal_grid(im);
1875 }
1877 /* dont draw horizontal grid if there is no min and max val */
1878 if (! res ) {
1879 char *nodata = "No Data found";
1880 gfx_new_text(im->canvas,im->ximg/2, (2*im->yorigin-im->ysize) / 2,
1881 im->graph_col[GRC_FONT],
1882 im->text_prop[TEXT_PROP_AXIS].font,
1883 im->text_prop[TEXT_PROP_AXIS].size,
1884 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
1885 nodata );
1886 }
1887 }
1889 /* yaxis description */
1890 if (im->canvas->imgformat != IF_PNG) {
1891 gfx_new_text( im->canvas,
1892 7, (im->yorigin - im->ysize/2),
1893 im->graph_col[GRC_FONT],
1894 im->text_prop[TEXT_PROP_AXIS].font,
1895 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 270.0,
1896 GFX_H_CENTER, GFX_V_CENTER,
1897 im->ylegend);
1898 } else {
1899 /* horrible hack until we can actually print vertically */
1900 {
1901 int n;
1902 int l=strlen(im->ylegend);
1903 char s[2];
1904 for (n=0;n<strlen(im->ylegend);n++) {
1905 s[0]=im->ylegend[n];
1906 s[1]='\0';
1907 gfx_new_text(im->canvas,7,im->text_prop[TEXT_PROP_AXIS].size*(l-n),
1908 im->graph_col[GRC_FONT],
1909 im->text_prop[TEXT_PROP_AXIS].font,
1910 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 270.0,
1911 GFX_H_CENTER, GFX_V_CENTER,
1912 s);
1913 }
1914 }
1915 }
1917 /* graph title */
1918 gfx_new_text( im->canvas,
1919 im->ximg/2, im->text_prop[TEXT_PROP_TITLE].size,
1920 im->graph_col[GRC_FONT],
1921 im->text_prop[TEXT_PROP_TITLE].font,
1922 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
1923 GFX_H_CENTER, GFX_V_CENTER,
1924 im->title);
1926 /* graph labels */
1927 if( !(im->extra_flags & NOLEGEND) ) {
1928 for(i=0;i<im->gdes_c;i++){
1929 if(im->gdes[i].legend[0] =='\0')
1930 continue;
1932 /* im->gdes[i].leg_y is the bottom of the legend */
1933 X0 = im->gdes[i].leg_x;
1934 Y0 = im->gdes[i].leg_y;
1935 /* Box needed? */
1936 if ( im->gdes[i].gf != GF_GPRINT
1937 && im->gdes[i].gf != GF_COMMENT) {
1938 int boxH, boxV;
1940 boxH = gfx_get_text_width(im->canvas, 0,
1941 im->text_prop[TEXT_PROP_AXIS].font,
1942 im->text_prop[TEXT_PROP_AXIS].size,
1943 im->tabwidth,"M") * 1.25;
1944 boxV = boxH;
1946 node = gfx_new_area(im->canvas,
1947 X0,Y0-boxV,
1948 X0,Y0,
1949 X0+boxH,Y0,
1950 im->gdes[i].col);
1951 gfx_add_point ( node, X0+boxH, Y0-boxV );
1952 node = gfx_new_line(im->canvas,
1953 X0,Y0-boxV, X0,Y0,
1954 1,0x000000FF);
1955 gfx_add_point(node,X0+boxH,Y0);
1956 gfx_add_point(node,X0+boxH,Y0-boxV);
1957 gfx_close_path(node);
1958 X0 += boxH / 1.25 * 2;
1959 }
1960 gfx_new_text ( im->canvas, X0, Y0,
1961 im->graph_col[GRC_FONT],
1962 im->text_prop[TEXT_PROP_AXIS].font,
1963 im->text_prop[TEXT_PROP_AXIS].size,
1964 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
1965 im->gdes[i].legend );
1966 }
1967 }
1968 }
1971 /*****************************************************
1972 * lazy check make sure we rely need to create this graph
1973 *****************************************************/
1975 int lazy_check(image_desc_t *im){
1976 FILE *fd = NULL;
1977 int size = 1;
1978 struct stat imgstat;
1980 if (im->lazy == 0) return 0; /* no lazy option */
1981 if (stat(im->graphfile,&imgstat) != 0)
1982 return 0; /* can't stat */
1983 /* one pixel in the existing graph is more then what we would
1984 change here ... */
1985 if (time(NULL) - imgstat.st_mtime >
1986 (im->end - im->start) / im->xsize)
1987 return 0;
1988 if ((fd = fopen(im->graphfile,"rb")) == NULL)
1989 return 0; /* the file does not exist */
1990 switch (im->canvas->imgformat) {
1991 case IF_PNG:
1992 size = PngSize(fd,&(im->ximg),&(im->yimg));
1993 break;
1994 default:
1995 size = 1;
1996 }
1997 fclose(fd);
1998 return size;
1999 }
2001 void
2002 pie_part(image_desc_t *im, gfx_color_t color,
2003 double PieCenterX, double PieCenterY, double Radius,
2004 double startangle, double endangle)
2005 {
2006 gfx_node_t *node;
2007 double angle;
2008 double step=M_PI/50; /* Number of iterations for the circle;
2009 ** 10 is definitely too low, more than
2010 ** 50 seems to be overkill
2011 */
2013 /* Strange but true: we have to work clockwise or else
2014 ** anti aliasing nor transparency don't work.
2015 **
2016 ** This test is here to make sure we do it right, also
2017 ** this makes the for...next loop more easy to implement.
2018 ** The return will occur if the user enters a negative number
2019 ** (which shouldn't be done according to the specs) or if the
2020 ** programmers do something wrong (which, as we all know, never
2021 ** happens anyway :)
2022 */
2023 if (endangle<startangle) return;
2025 /* Hidden feature: Radius decreases each full circle */
2026 angle=startangle;
2027 while (angle>=2*M_PI) {
2028 angle -= 2*M_PI;
2029 Radius *= 0.8;
2030 }
2032 node=gfx_new_area(im->canvas,
2033 PieCenterX+sin(startangle)*Radius,
2034 PieCenterY-cos(startangle)*Radius,
2035 PieCenterX,
2036 PieCenterY,
2037 PieCenterX+sin(endangle)*Radius,
2038 PieCenterY-cos(endangle)*Radius,
2039 color);
2040 for (angle=endangle;angle-startangle>=step;angle-=step) {
2041 gfx_add_point(node,
2042 PieCenterX+sin(angle)*Radius,
2043 PieCenterY-cos(angle)*Radius );
2044 }
2045 }
2047 int
2048 graph_size_location(image_desc_t *im, int elements, int piechart )
2049 {
2050 /* The actual size of the image to draw is determined from
2051 ** several sources. The size given on the command line is
2052 ** the graph area but we need more as we have to draw labels
2053 ** and other things outside the graph area
2054 */
2056 /* +-+-------------------------------------------+
2057 ** |l|.................title.....................|
2058 ** |e+--+-------------------------------+--------+
2059 ** |b| b| | |
2060 ** |a| a| | pie |
2061 ** |l| l| main graph area | chart |
2062 ** |.| .| | area |
2063 ** |t| y| | |
2064 ** |r+--+-------------------------------+--------+
2065 ** |e| | x-axis labels | |
2066 ** |v+--+-------------------------------+--------+
2067 ** | |..............legends......................|
2068 ** +-+-------------------------------------------+
2069 */
2070 int Xvertical=0, Yvertical=0,
2071 Xtitle =0, Ytitle =0,
2072 Xylabel =0, Yylabel =0,
2073 Xmain =0, Ymain =0,
2074 Xpie =0, Ypie =0,
2075 Xxlabel =0, Yxlabel =0,
2076 #if 0
2077 Xlegend =0, Ylegend =0,
2078 #endif
2079 Xspacing =10, Yspacing =10;
2081 if (im->ylegend[0] != '\0') {
2082 Xvertical = im->text_prop[TEXT_PROP_LEGEND].size *2;
2083 Yvertical = im->text_prop[TEXT_PROP_LEGEND].size * (strlen(im->ylegend)+1);
2084 }
2086 if (im->title[0] != '\0') {
2087 /* The title is placed "inbetween" two text lines so it
2088 ** automatically has some vertical spacing. The horizontal
2089 ** spacing is added here, on each side.
2090 */
2091 Xtitle = gfx_get_text_width(im->canvas, 0,
2092 im->text_prop[TEXT_PROP_TITLE].font,
2093 im->text_prop[TEXT_PROP_TITLE].size,
2094 im->tabwidth,
2095 im->title) + 2*Xspacing;
2096 Ytitle = im->text_prop[TEXT_PROP_TITLE].size*2;
2097 }
2099 if (elements) {
2100 Xmain=im->xsize;
2101 Ymain=im->ysize;
2102 if (im->draw_x_grid) {
2103 Xxlabel=Xmain;
2104 Yxlabel=im->text_prop[TEXT_PROP_LEGEND].size *2;
2105 }
2106 if (im->draw_y_grid) {
2107 Xylabel=im->text_prop[TEXT_PROP_LEGEND].size *6;
2108 Yylabel=Ymain;
2109 }
2110 }
2112 if (piechart) {
2113 im->piesize=im->xsize<im->ysize?im->xsize:im->ysize;
2114 Xpie=im->piesize;
2115 Ypie=im->piesize;
2116 }
2118 /* Now calculate the total size. Insert some spacing where
2119 desired. im->xorigin and im->yorigin need to correspond
2120 with the lower left corner of the main graph area or, if
2121 this one is not set, the imaginary box surrounding the
2122 pie chart area. */
2124 /* The legend width cannot yet be determined, as a result we
2125 ** have problems adjusting the image to it. For now, we just
2126 ** forget about it at all; the legend will have to fit in the
2127 ** size already allocated.
2128 */
2129 im->ximg = Xylabel + Xmain + Xpie + Xspacing;
2130 if (Xmain) im->ximg += Xspacing;
2131 if (Xpie) im->ximg += Xspacing;
2132 im->xorigin = Xspacing + Xylabel;
2133 if (Xtitle > im->ximg) im->ximg = Xtitle;
2134 if (Xvertical) {
2135 im->ximg += Xvertical;
2136 im->xorigin += Xvertical;
2137 }
2138 xtr(im,0);
2140 /* The vertical size is interesting... we need to compare
2141 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend} with Yvertical
2142 ** however we need to know {Ytitle+Ymain+Yxlabel} in order to
2143 ** start even thinking about Ylegend.
2144 **
2145 ** Do it in three portions: First calculate the inner part,
2146 ** then do the legend, then adjust the total height of the img.
2147 */
2149 /* reserve space for main and/or pie */
2150 im->yimg = Ymain + Yxlabel;
2151 if (im->yimg < Ypie) im->yimg = Ypie;
2152 im->yorigin = im->yimg - Yxlabel;
2153 /* reserve space for the title *or* some padding above the graph */
2154 if (Ytitle) {
2155 im->yimg += Ytitle;
2156 im->yorigin += Ytitle;
2157 } else {
2158 im->yimg += Yspacing;
2159 im->yorigin += Yspacing;
2160 }
2161 /* reserve space for padding below the graph */
2162 im->yimg += Yspacing;
2163 ytr(im,DNAN);
2165 /* Determine where to place the legends onto the image.
2166 ** Adjust im->yimg to match the space requirements.
2167 */
2168 if(leg_place(im)==-1)
2169 return -1;
2171 /* last of three steps: check total height of image */
2172 if (im->yimg < Yvertical) im->yimg = Yvertical;
2174 #if 0
2175 if (Xlegend > im->ximg) {
2176 im->ximg = Xlegend;
2177 /* reposition Pie */
2178 }
2179 #endif
2181 /* The pie is placed in the upper right hand corner,
2182 ** just below the title (if any) and with sufficient
2183 ** padding.
2184 */
2185 if (elements) {
2186 im->pie_x = im->ximg - Xspacing - Xpie/2;
2187 im->pie_y = im->yorigin-Ymain+Ypie/2;
2188 } else {
2189 im->pie_x = im->ximg/2;
2190 im->pie_y = im->yorigin-Ypie/2;
2191 }
2193 return 0;
2194 }
2196 /* draw that picture thing ... */
2197 int
2198 graph_paint(image_desc_t *im, char ***calcpr)
2199 {
2200 int i,ii;
2201 int lazy = lazy_check(im);
2202 int piechart = 0;
2203 double PieStart=0.0;
2204 FILE *fo;
2205 gfx_node_t *node;
2207 double areazero = 0.0;
2208 enum gf_en stack_gf = GF_PRINT;
2209 graph_desc_t *lastgdes = NULL;
2211 /* if we are lazy and there is nothing to PRINT ... quit now */
2212 if (lazy && im->prt_c==0) return 0;
2214 /* pull the data from the rrd files ... */
2216 if(data_fetch(im)==-1)
2217 return -1;
2219 /* evaluate VDEF and CDEF operations ... */
2220 if(data_calc(im)==-1)
2221 return -1;
2223 /* check if we need to draw a piechart */
2224 for(i=0;i<im->gdes_c;i++){
2225 if (im->gdes[i].gf == GF_PART) {
2226 piechart=1;
2227 break;
2228 }
2229 }
2231 /* calculate and PRINT and GPRINT definitions. We have to do it at
2232 * this point because it will affect the length of the legends
2233 * if there are no graph elements we stop here ...
2234 * if we are lazy, try to quit ...
2235 */
2236 i=print_calc(im,calcpr);
2237 if(i<0) return -1;
2238 if(((i==0)&&(piechart==0)) || lazy) return 0;
2240 /* If there's only the pie chart to draw, signal this */
2241 if (i==0) piechart=2;
2243 /* get actual drawing data and find min and max values*/
2244 if(data_proc(im)==-1)
2245 return -1;
2247 if(!im->logarithmic){si_unit(im);} /* identify si magnitude Kilo, Mega Giga ? */
2249 if(!im->rigid && ! im->logarithmic)
2250 expand_range(im); /* make sure the upper and lower limit are
2251 sensible values */
2253 if (!calc_horizontal_grid(im))
2254 return -1;
2255 if (im->gridfit)
2256 apply_gridfit(im);
2258 /**************************************************************
2259 *** Calculating sizes and locations became a bit confusing ***
2260 *** so I moved this into a separate function. ***
2261 **************************************************************/
2262 if(graph_size_location(im,i,piechart)==-1)
2263 return -1;
2265 /* the actual graph is created by going through the individual
2266 graph elements and then drawing them */
2268 node=gfx_new_area ( im->canvas,
2269 0, 0,
2270 im->ximg, 0,
2271 im->ximg, im->yimg,
2272 im->graph_col[GRC_BACK]);
2274 gfx_add_point(node,0, im->yimg);
2276 if (piechart != 2) {
2277 node=gfx_new_area ( im->canvas,
2278 im->xorigin, im->yorigin,
2279 im->xorigin + im->xsize, im->yorigin,
2280 im->xorigin + im->xsize, im->yorigin-im->ysize,
2281 im->graph_col[GRC_CANVAS]);
2283 gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
2285 if (im->minval > 0.0)
2286 areazero = im->minval;
2287 if (im->maxval < 0.0)
2288 areazero = im->maxval;
2290 axis_paint(im);
2291 }
2293 if (piechart) {
2294 pie_part(im,im->graph_col[GRC_CANVAS],im->pie_x,im->pie_y,im->piesize*0.5,0,2*M_PI);
2295 }
2297 for(i=0;i<im->gdes_c;i++){
2298 switch(im->gdes[i].gf){
2299 case GF_CDEF:
2300 case GF_VDEF:
2301 case GF_DEF:
2302 case GF_PRINT:
2303 case GF_GPRINT:
2304 case GF_COMMENT:
2305 case GF_HRULE:
2306 case GF_VRULE:
2307 case GF_XPORT:
2308 break;
2309 case GF_TICK:
2310 for (ii = 0; ii < im->xsize; ii++)
2311 {
2312 if (!isnan(im->gdes[i].p_data[ii]) &&
2313 im->gdes[i].p_data[ii] > 0.0)
2314 {
2315 /* generate a tick */
2316 gfx_new_line(im->canvas, im -> xorigin + ii,
2317 im -> yorigin - (im -> gdes[i].yrule * im -> ysize),
2318 im -> xorigin + ii,
2319 im -> yorigin,
2320 1.0,
2321 im -> gdes[i].col );
2322 }
2323 }
2324 break;
2325 case GF_LINE:
2326 case GF_AREA:
2327 stack_gf = im->gdes[i].gf;
2328 case GF_STACK:
2329 /* fix data points at oo and -oo */
2330 for(ii=0;ii<im->xsize;ii++){
2331 if (isinf(im->gdes[i].p_data[ii])){
2332 if (im->gdes[i].p_data[ii] > 0) {
2333 im->gdes[i].p_data[ii] = im->maxval ;
2334 } else {
2335 im->gdes[i].p_data[ii] = im->minval ;
2336 }
2338 }
2339 } /* for */
2341 if (im->gdes[i].col != 0x0){
2342 /* GF_LINE and friend */
2343 if(stack_gf == GF_LINE ){
2344 node = NULL;
2345 for(ii=1;ii<im->xsize;ii++){
2346 if ( ! isnan(im->gdes[i].p_data[ii-1])
2347 && ! isnan(im->gdes[i].p_data[ii])){
2348 if (node == NULL){
2349 node = gfx_new_line(im->canvas,
2350 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2351 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2352 im->gdes[i].linewidth,
2353 im->gdes[i].col);
2354 } else {
2355 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2356 }
2357 } else {
2358 node = NULL;
2359 }
2360 }
2361 } else {
2362 int area_start=-1;
2363 node = NULL;
2364 for(ii=1;ii<im->xsize;ii++){
2365 /* open an area */
2366 if ( ! isnan(im->gdes[i].p_data[ii-1])
2367 && ! isnan(im->gdes[i].p_data[ii])){
2368 if (node == NULL){
2369 float ybase = 0.0;
2370 /*
2371 if (im->gdes[i].gf == GF_STACK) {
2372 */
2373 if ( (im->gdes[i].gf == GF_STACK)
2374 || (im->gdes[i].stack) ) {
2376 ybase = ytr(im,lastgdes->p_data[ii-1]);
2377 } else {
2378 ybase = ytr(im,areazero);
2379 }
2380 area_start = ii-1;
2381 node = gfx_new_area(im->canvas,
2382 ii-1+im->xorigin,ybase,
2383 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2384 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2385 im->gdes[i].col
2386 );
2387 } else {
2388 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2389 }
2390 }
2392 if ( node != NULL && (ii+1==im->xsize || isnan(im->gdes[i].p_data[ii]) )){
2393 /* GF_AREA STACK type*/
2394 /*
2395 if (im->gdes[i].gf == GF_STACK ) {
2396 */
2397 if ( (im->gdes[i].gf == GF_STACK)
2398 || (im->gdes[i].stack) ) {
2399 int iii;
2400 for (iii=ii-1;iii>area_start;iii--){
2401 gfx_add_point(node,iii+im->xorigin,ytr(im,lastgdes->p_data[iii]));
2402 }
2403 } else {
2404 gfx_add_point(node,ii+im->xorigin,ytr(im,areazero));
2405 };
2406 node=NULL;
2407 };
2408 }
2409 } /* else GF_LINE */
2410 } /* if color != 0x0 */
2411 /* make sure we do not run into trouble when stacking on NaN */
2412 for(ii=0;ii<im->xsize;ii++){
2413 if (isnan(im->gdes[i].p_data[ii])) {
2414 if (lastgdes && (im->gdes[i].gf == GF_STACK)) {
2415 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
2416 } else {
2417 im->gdes[i].p_data[ii] = ytr(im,areazero);
2418 }
2419 }
2420 }
2421 lastgdes = &(im->gdes[i]);
2422 break;
2423 case GF_PART:
2424 if(isnan(im->gdes[i].yrule)) /* fetch variable */
2425 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2427 if (finite(im->gdes[i].yrule)) { /* even the fetched var can be NaN */
2428 pie_part(im,im->gdes[i].col,
2429 im->pie_x,im->pie_y,im->piesize*0.4,
2430 M_PI*2.0*PieStart/100.0,
2431 M_PI*2.0*(PieStart+im->gdes[i].yrule)/100.0);
2432 PieStart += im->gdes[i].yrule;
2433 }
2434 break;
2435 } /* switch */
2436 }
2437 if (piechart==2) {
2438 im->draw_x_grid=0;
2439 im->draw_y_grid=0;
2440 }
2441 /* grid_paint also does the text */
2442 grid_paint(im);
2444 /* the RULES are the last thing to paint ... */
2445 for(i=0;i<im->gdes_c;i++){
2447 switch(im->gdes[i].gf){
2448 case GF_HRULE:
2449 if(isnan(im->gdes[i].yrule)) { /* fetch variable */
2450 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2451 };
2452 if(im->gdes[i].yrule >= im->minval
2453 && im->gdes[i].yrule <= im->maxval)
2454 gfx_new_line(im->canvas,
2455 im->xorigin,ytr(im,im->gdes[i].yrule),
2456 im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2457 1.0,im->gdes[i].col);
2458 break;
2459 case GF_VRULE:
2460 if(im->gdes[i].xrule == 0) { /* fetch variable */
2461 im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
2462 };
2463 if(im->gdes[i].xrule >= im->start
2464 && im->gdes[i].xrule <= im->end)
2465 gfx_new_line(im->canvas,
2466 xtr(im,im->gdes[i].xrule),im->yorigin,
2467 xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2468 1.0,im->gdes[i].col);
2469 break;
2470 default:
2471 break;
2472 }
2473 }
2476 if (strcmp(im->graphfile,"-")==0) {
2477 #ifdef WIN32
2478 /* Change translation mode for stdout to BINARY */
2479 _setmode( _fileno( stdout ), O_BINARY );
2480 #endif
2481 fo = stdout;
2482 } else {
2483 if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2484 rrd_set_error("Opening '%s' for write: %s",im->graphfile,
2485 rrd_strerror(errno));
2486 return (-1);
2487 }
2488 }
2489 gfx_render (im->canvas,im->ximg,im->yimg,0x0,fo);
2490 if (strcmp(im->graphfile,"-") != 0)
2491 fclose(fo);
2492 return 0;
2493 }
2496 /*****************************************************
2497 * graph stuff
2498 *****************************************************/
2500 int
2501 gdes_alloc(image_desc_t *im){
2503 unsigned long def_step = (im->end-im->start)/im->xsize;
2505 if (im->step > def_step) /* step can be increassed ... no decreassed */
2506 def_step = im->step;
2508 im->gdes_c++;
2510 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2511 * sizeof(graph_desc_t)))==NULL){
2512 rrd_set_error("realloc graph_descs");
2513 return -1;
2514 }
2517 im->gdes[im->gdes_c-1].step=def_step;
2518 im->gdes[im->gdes_c-1].stack=0;
2519 im->gdes[im->gdes_c-1].debug=0;
2520 im->gdes[im->gdes_c-1].start=im->start;
2521 im->gdes[im->gdes_c-1].end=im->end;
2522 im->gdes[im->gdes_c-1].vname[0]='\0';
2523 im->gdes[im->gdes_c-1].data=NULL;
2524 im->gdes[im->gdes_c-1].ds_namv=NULL;
2525 im->gdes[im->gdes_c-1].data_first=0;
2526 im->gdes[im->gdes_c-1].p_data=NULL;
2527 im->gdes[im->gdes_c-1].rpnp=NULL;
2528 im->gdes[im->gdes_c-1].col = 0x0;
2529 im->gdes[im->gdes_c-1].legend[0]='\0';
2530 im->gdes[im->gdes_c-1].rrd[0]='\0';
2531 im->gdes[im->gdes_c-1].ds=-1;
2532 im->gdes[im->gdes_c-1].p_data=NULL;
2533 im->gdes[im->gdes_c-1].yrule=DNAN;
2534 im->gdes[im->gdes_c-1].xrule=0;
2535 return 0;
2536 }
2538 /* copies input untill the first unescaped colon is found
2539 or until input ends. backslashes have to be escaped as well */
2540 int
2541 scan_for_col(char *input, int len, char *output)
2542 {
2543 int inp,outp=0;
2544 for (inp=0;
2545 inp < len &&
2546 input[inp] != ':' &&
2547 input[inp] != '\0';
2548 inp++){
2549 if (input[inp] == '\\' &&
2550 input[inp+1] != '\0' &&
2551 (input[inp+1] == '\\' ||
2552 input[inp+1] == ':')){
2553 output[outp++] = input[++inp];
2554 }
2555 else {
2556 output[outp++] = input[inp];
2557 }
2558 }
2559 output[outp] = '\0';
2560 return inp;
2561 }
2562 /* Some surgery done on this function, it became ridiculously big.
2563 ** Things moved:
2564 ** - initializing now in rrd_graph_init()
2565 ** - options parsing now in rrd_graph_options()
2566 ** - script parsing now in rrd_graph_script()
2567 */
2568 int
2569 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize)
2570 {
2571 image_desc_t im;
2573 rrd_graph_init(&im);
2575 rrd_graph_options(argc,argv,&im);
2576 if (rrd_test_error()) {
2577 im_free(&im);
2578 return -1;
2579 }
2581 if (strlen(argv[optind])>=MAXPATH) {
2582 rrd_set_error("filename (including path) too long");
2583 im_free(&im);
2584 return -1;
2585 }
2586 strncpy(im.graphfile,argv[optind],MAXPATH-1);
2587 im.graphfile[MAXPATH-1]='\0';
2589 rrd_graph_script(argc,argv,&im);
2590 if (rrd_test_error()) {
2591 im_free(&im);
2592 return -1;
2593 }
2595 /* Everything is now read and the actual work can start */
2597 (*prdata)=NULL;
2598 if (graph_paint(&im,prdata)==-1){
2599 im_free(&im);
2600 return -1;
2601 }
2603 /* The image is generated and needs to be output.
2604 ** Also, if needed, print a line with information about the image.
2605 */
2607 *xsize=im.ximg;
2608 *ysize=im.yimg;
2609 if (im.imginfo) {
2610 char *filename;
2611 if (!(*prdata)) {
2612 /* maybe prdata is not allocated yet ... lets do it now */
2613 if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
2614 rrd_set_error("malloc imginfo");
2615 return -1;
2616 };
2617 }
2618 if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
2619 ==NULL){
2620 rrd_set_error("malloc imginfo");
2621 return -1;
2622 }
2623 filename=im.graphfile+strlen(im.graphfile);
2624 while(filename > im.graphfile) {
2625 if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
2626 filename--;
2627 }
2629 sprintf((*prdata)[0],im.imginfo,filename,(long)(im.canvas->zoom*im.ximg),(long)(im.canvas->zoom*im.yimg));
2630 }
2631 im_free(&im);
2632 return 0;
2633 }
2635 void
2636 rrd_graph_init(image_desc_t *im)
2637 {
2638 unsigned int i;
2640 #ifdef HAVE_TZSET
2641 tzset();
2642 #endif
2643 #ifdef HAVE_SETLOCALE
2644 setlocale(LC_TIME,"");
2645 #endif
2647 im->xlab_user.minsec = -1;
2648 im->ximg=0;
2649 im->yimg=0;
2650 im->xsize = 400;
2651 im->ysize = 100;
2652 im->step = 0;
2653 im->ylegend[0] = '\0';
2654 im->title[0] = '\0';
2655 im->minval = DNAN;
2656 im->maxval = DNAN;
2657 im->unitsexponent= 9999;
2658 im->extra_flags= 0;
2659 im->rigid = 0;
2660 im->gridfit = 1;
2661 im->imginfo = NULL;
2662 im->lazy = 0;
2663 im->logarithmic = 0;
2664 im->ygridstep = DNAN;
2665 im->draw_x_grid = 1;
2666 im->draw_y_grid = 1;
2667 im->base = 1000;
2668 im->prt_c = 0;
2669 im->gdes_c = 0;
2670 im->gdes = NULL;
2671 im->canvas = gfx_new_canvas();
2672 im->grid_dash_on = 1;
2673 im->grid_dash_off = 1;
2675 for(i=0;i<DIM(graph_col);i++)
2676 im->graph_col[i]=graph_col[i];
2678 for(i=0;i<DIM(text_prop);i++){
2679 im->text_prop[i].size = text_prop[i].size;
2680 im->text_prop[i].font = text_prop[i].font;
2681 }
2682 }
2684 void
2685 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
2686 {
2687 int stroff;
2688 char *parsetime_error = NULL;
2689 char scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
2690 time_t start_tmp=0,end_tmp=0;
2691 long long_tmp;
2692 struct time_value start_tv, end_tv;
2693 gfx_color_t color;
2695 parsetime("end-24h", &start_tv);
2696 parsetime("now", &end_tv);
2698 while (1){
2699 static struct option long_options[] =
2700 {
2701 {"start", required_argument, 0, 's'},
2702 {"end", required_argument, 0, 'e'},
2703 {"x-grid", required_argument, 0, 'x'},
2704 {"y-grid", required_argument, 0, 'y'},
2705 {"vertical-label",required_argument,0,'v'},
2706 {"width", required_argument, 0, 'w'},
2707 {"height", required_argument, 0, 'h'},
2708 {"interlaced", no_argument, 0, 'i'},
2709 {"upper-limit",required_argument, 0, 'u'},
2710 {"lower-limit",required_argument, 0, 'l'},
2711 {"rigid", no_argument, 0, 'r'},
2712 {"base", required_argument, 0, 'b'},
2713 {"logarithmic",no_argument, 0, 'o'},
2714 {"color", required_argument, 0, 'c'},
2715 {"font", required_argument, 0, 'n'},
2716 {"title", required_argument, 0, 't'},
2717 {"imginfo", required_argument, 0, 'f'},
2718 {"imgformat", required_argument, 0, 'a'},
2719 {"lazy", no_argument, 0, 'z'},
2720 {"zoom", required_argument, 0, 'm'},
2721 {"no-legend", no_argument, 0, 'g'},
2722 {"alt-y-grid", no_argument, 0, 'Y'},
2723 {"no-minor", no_argument, 0, 'I'},
2724 {"alt-autoscale", no_argument, 0, 'A'},
2725 {"alt-autoscale-max", no_argument, 0, 'M'},
2726 {"units-exponent",required_argument, 0, 'X'},
2727 {"step", required_argument, 0, 'S'},
2728 {"no-gridfit", no_argument, 0, 'N'},
2729 {0,0,0,0}};
2730 int option_index = 0;
2731 int opt;
2734 opt = getopt_long(argc, argv,
2735 "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:I:zgYAMX:S:N",
2736 long_options, &option_index);
2738 if (opt == EOF)
2739 break;
2741 switch(opt) {
2742 case 'I':
2743 im->extra_flags |= NOMINOR;
2744 break;
2745 case 'Y':
2746 im->extra_flags |= ALTYGRID;
2747 break;
2748 case 'A':
2749 im->extra_flags |= ALTAUTOSCALE;
2750 break;
2751 case 'M':
2752 im->extra_flags |= ALTAUTOSCALE_MAX;
2753 break;
2754 case 'g':
2755 im->extra_flags |= NOLEGEND;
2756 break;
2757 case 'X':
2758 im->unitsexponent = atoi(optarg);
2759 break;
2760 case 'S':
2761 im->step = atoi(optarg);
2762 break;
2763 case 262:
2764 im->gridfit = 0;
2765 break;
2766 case 's':
2767 if ((parsetime_error = parsetime(optarg, &start_tv))) {
2768 rrd_set_error( "start time: %s", parsetime_error );
2769 return;
2770 }
2771 break;
2772 case 'e':
2773 if ((parsetime_error = parsetime(optarg, &end_tv))) {
2774 rrd_set_error( "end time: %s", parsetime_error );
2775 return;
2776 }
2777 break;
2778 case 'x':
2779 if(strcmp(optarg,"none") == 0){
2780 im->draw_x_grid=0;
2781 break;
2782 };
2784 if(sscanf(optarg,
2785 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
2786 scan_gtm,
2787 &im->xlab_user.gridst,
2788 scan_mtm,
2789 &im->xlab_user.mgridst,
2790 scan_ltm,
2791 &im->xlab_user.labst,
2792 &im->xlab_user.precis,
2793 &stroff) == 7 && stroff != 0){
2794 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
2795 if((im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
2796 rrd_set_error("unknown keyword %s",scan_gtm);
2797 return;
2798 } else if ((im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
2799 rrd_set_error("unknown keyword %s",scan_mtm);
2800 return;
2801 } else if ((im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
2802 rrd_set_error("unknown keyword %s",scan_ltm);
2803 return;
2804 }
2805 im->xlab_user.minsec = 1;
2806 im->xlab_user.stst = im->xlab_form;
2807 } else {
2808 rrd_set_error("invalid x-grid format");
2809 return;
2810 }
2811 break;
2812 case 'y':
2814 if(strcmp(optarg,"none") == 0){
2815 im->draw_y_grid=0;
2816 break;
2817 };
2819 if(sscanf(optarg,
2820 "%lf:%d",
2821 &im->ygridstep,
2822 &im->ylabfact) == 2) {
2823 if(im->ygridstep<=0){
2824 rrd_set_error("grid step must be > 0");
2825 return;
2826 } else if (im->ylabfact < 1){
2827 rrd_set_error("label factor must be > 0");
2828 return;
2829 }
2830 } else {
2831 rrd_set_error("invalid y-grid format");
2832 return;
2833 }
2834 break;
2835 case 'v':
2836 strncpy(im->ylegend,optarg,150);
2837 im->ylegend[150]='\0';
2838 break;
2839 case 'u':
2840 im->maxval = atof(optarg);
2841 break;
2842 case 'l':
2843 im->minval = atof(optarg);
2844 break;
2845 case 'b':
2846 im->base = atol(optarg);
2847 if(im->base != 1024 && im->base != 1000 ){
2848 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
2849 return;
2850 }
2851 break;
2852 case 'w':
2853 long_tmp = atol(optarg);
2854 if (long_tmp < 10) {
2855 rrd_set_error("width below 10 pixels");
2856 return;
2857 }
2858 im->xsize = long_tmp;
2859 break;
2860 case 'h':
2861 long_tmp = atol(optarg);
2862 if (long_tmp < 10) {
2863 rrd_set_error("height below 10 pixels");
2864 return;
2865 }
2866 im->ysize = long_tmp;
2867 break;
2868 case 'i':
2869 im->canvas->interlaced = 1;
2870 break;
2871 case 'r':
2872 im->rigid = 1;
2873 break;
2874 case 'f':
2875 im->imginfo = optarg;
2876 break;
2877 case 'a':
2878 if((im->canvas->imgformat = if_conv(optarg)) == -1) {
2879 rrd_set_error("unsupported graphics format '%s'",optarg);
2880 return;
2881 }
2882 break;
2883 case 'z':
2884 im->lazy = 1;
2885 break;
2886 case 'o':
2887 im->logarithmic = 1;
2888 if (isnan(im->minval))
2889 im->minval=1;
2890 break;
2891 case 'c':
2892 if(sscanf(optarg,
2893 "%10[A-Z]#%8lx",
2894 col_nam,&color) == 2){
2895 int ci;
2896 if((ci=grc_conv(col_nam)) != -1){
2897 im->graph_col[ci]=color;
2898 } else {
2899 rrd_set_error("invalid color name '%s'",col_nam);
2900 }
2901 } else {
2902 rrd_set_error("invalid color def format");
2903 return;
2904 }
2905 break;
2906 case 'n':{
2907 /* originally this used char *prop = "" and
2908 ** char *font = "dummy" however this results
2909 ** in a SEG fault, at least on RH7.1
2910 **
2911 ** The current implementation isn't proper
2912 ** either, font is never freed and prop uses
2913 ** a fixed width string
2914 */
2915 char prop[100];
2916 double size = 1;
2917 char *font;
2919 font=malloc(255);
2920 if(sscanf(optarg,
2921 "%10[A-Z]:%lf:%s",
2922 prop,&size,font) == 3){
2923 int sindex;
2924 if((sindex=text_prop_conv(prop)) != -1){
2925 im->text_prop[sindex].size=size;
2926 im->text_prop[sindex].font=font;
2927 if (sindex==0) { /* the default */
2928 im->text_prop[TEXT_PROP_TITLE].size=size;
2929 im->text_prop[TEXT_PROP_TITLE].font=font;
2930 im->text_prop[TEXT_PROP_AXIS].size=size;
2931 im->text_prop[TEXT_PROP_AXIS].font=font;
2932 im->text_prop[TEXT_PROP_UNIT].size=size;
2933 im->text_prop[TEXT_PROP_UNIT].font=font;
2934 im->text_prop[TEXT_PROP_LEGEND].size=size;
2935 im->text_prop[TEXT_PROP_LEGEND].font=font;
2936 }
2937 } else {
2938 rrd_set_error("invalid fonttag '%s'",prop);
2939 return;
2940 }
2941 } else {
2942 rrd_set_error("invalid text property format");
2943 return;
2944 }
2945 break;
2946 }
2947 case 'm':
2948 im->canvas->zoom = atof(optarg);
2949 if (im->canvas->zoom <= 0.0) {
2950 rrd_set_error("zoom factor must be > 0");
2951 return;
2952 }
2953 break;
2954 case 't':
2955 strncpy(im->title,optarg,150);
2956 im->title[150]='\0';
2957 break;
2959 case '?':
2960 if (optopt != 0)
2961 rrd_set_error("unknown option '%c'", optopt);
2962 else
2963 rrd_set_error("unknown option '%s'",argv[optind-1]);
2964 return;
2965 }
2966 }
2968 if (optind >= argc) {
2969 rrd_set_error("missing filename");
2970 return;
2971 }
2973 if (im->logarithmic == 1 && (im->minval <= 0 || isnan(im->minval))){
2974 rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");
2975 return;
2976 }
2978 if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
2979 /* error string is set in parsetime.c */
2980 return;
2981 }
2983 if (start_tmp < 3600*24*365*10){
2984 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
2985 return;
2986 }
2988 if (end_tmp < start_tmp) {
2989 rrd_set_error("start (%ld) should be less than end (%ld)",
2990 start_tmp, end_tmp);
2991 return;
2992 }
2994 im->start = start_tmp;
2995 im->end = end_tmp;
2996 }
2998 int
2999 rrd_graph_check_vname(image_desc_t *im, char *varname, char *err)
3000 {
3001 if ((im->gdes[im->gdes_c-1].vidx=find_var(im,varname))==-1) {
3002 rrd_set_error("Unknown variable '%s' in %s",varname,err);
3003 return -1;
3004 }
3005 return 0;
3006 }
3007 int
3008 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
3009 {
3010 char *color;
3011 graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
3013 color=strstr(var,"#");
3014 if (color==NULL) {
3015 if (optional==0) {
3016 rrd_set_error("Found no color in %s",err);
3017 return 0;
3018 }
3019 return 0;
3020 } else {
3021 int n=0;
3022 char *rest;
3023 gfx_color_t col;
3025 rest=strstr(color,":");
3026 if (rest!=NULL)
3027 n=rest-color;
3028 else
3029 n=strlen(color);
3031 switch (n) {
3032 case 7:
3033 sscanf(color,"#%6lx%n",&col,&n);
3034 col = (col << 8) + 0xff /* shift left by 8 */;
3035 if (n!=7) rrd_set_error("Color problem in %s",err);
3036 break;
3037 case 9:
3038 sscanf(color,"#%8lx%n",&col,&n);
3039 if (n==9) break;
3040 default:
3041 rrd_set_error("Color problem in %s",err);
3042 }
3043 if (rrd_test_error()) return 0;
3044 gdp->col = col;
3045 return n;
3046 }
3047 }
3048 int
3049 rrd_graph_legend(graph_desc_t *gdp, char *line)
3050 {
3051 int i;
3053 i=scan_for_col(line,FMT_LEG_LEN,gdp->legend);
3055 return (strlen(&line[i])==0);
3056 }
3059 int bad_format(char *fmt) {
3060 char *ptr;
3061 int n=0;
3062 ptr = fmt;
3063 while (*ptr != '\0')
3064 if (*ptr++ == '%') {
3066 /* line cannot end with percent char */
3067 if (*ptr == '\0') return 1;
3069 /* '%s', '%S' and '%%' are allowed */
3070 if (*ptr == 's' || *ptr == 'S' || *ptr == '%') ptr++;
3072 /* or else '% 6.2lf' and such are allowed */
3073 else {
3075 /* optional padding character */
3076 if (*ptr == ' ' || *ptr == '+' || *ptr == '-') ptr++;
3078 /* This should take care of 'm.n' with all three optional */
3079 while (*ptr >= '0' && *ptr <= '9') ptr++;
3080 if (*ptr == '.') ptr++;
3081 while (*ptr >= '0' && *ptr <= '9') ptr++;
3083 /* Either 'le', 'lf' or 'lg' must follow here */
3084 if (*ptr++ != 'l') return 1;
3085 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g') ptr++;
3086 else return 1;
3087 n++;
3088 }
3089 }
3091 return (n!=1);
3092 }
3095 int
3096 vdef_parse(gdes,str)
3097 struct graph_desc_t *gdes;
3098 char *str;
3099 {
3100 /* A VDEF currently is either "func" or "param,func"
3101 * so the parsing is rather simple. Change if needed.
3102 */
3103 double param;
3104 char func[30];
3105 int n;
3107 n=0;
3108 sscanf(str,"%le,%29[A-Z]%n",¶m,func,&n);
3109 if (n==strlen(str)) { /* matched */
3110 ;
3111 } else {
3112 n=0;
3113 sscanf(str,"%29[A-Z]%n",func,&n);
3114 if (n==strlen(str)) { /* matched */
3115 param=DNAN;
3116 } else {
3117 rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3118 ,str
3119 ,gdes->vname
3120 );
3121 return -1;
3122 }
3123 }
3124 if (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3125 else if (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3126 else if (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3127 else if (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3128 else if (!strcmp("TOTAL", func)) gdes->vf.op = VDEF_TOTAL;
3129 else if (!strcmp("FIRST", func)) gdes->vf.op = VDEF_FIRST;
3130 else if (!strcmp("LAST", func)) gdes->vf.op = VDEF_LAST;
3131 else {
3132 rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3133 ,func
3134 ,gdes->vname
3135 );
3136 return -1;
3137 };
3139 switch (gdes->vf.op) {
3140 case VDEF_PERCENT:
3141 if (isnan(param)) { /* no parameter given */
3142 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3143 ,func
3144 ,gdes->vname
3145 );
3146 return -1;
3147 };
3148 if (param>=0.0 && param<=100.0) {
3149 gdes->vf.param = param;
3150 gdes->vf.val = DNAN; /* undefined */
3151 gdes->vf.when = 0; /* undefined */
3152 } else {
3153 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3154 ,param
3155 ,gdes->vname
3156 );
3157 return -1;
3158 };
3159 break;
3160 case VDEF_MAXIMUM:
3161 case VDEF_AVERAGE:
3162 case VDEF_MINIMUM:
3163 case VDEF_TOTAL:
3164 case VDEF_FIRST:
3165 case VDEF_LAST:
3166 if (isnan(param)) {
3167 gdes->vf.param = DNAN;
3168 gdes->vf.val = DNAN;
3169 gdes->vf.when = 0;
3170 } else {
3171 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3172 ,func
3173 ,gdes->vname
3174 );
3175 return -1;
3176 };
3177 break;
3178 };
3179 return 0;
3180 }
3183 int
3184 vdef_calc(im,gdi)
3185 image_desc_t *im;
3186 int gdi;
3187 {
3188 graph_desc_t *src,*dst;
3189 rrd_value_t *data;
3190 long step,steps;
3192 dst = &im->gdes[gdi];
3193 src = &im->gdes[dst->vidx];
3194 data = src->data + src->ds;
3195 steps = (src->end - src->start) / src->step;
3197 #if 0
3198 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3199 ,src->start
3200 ,src->end
3201 ,steps
3202 );
3203 #endif
3205 switch (dst->vf.op) {
3206 case VDEF_PERCENT: {
3207 rrd_value_t * array;
3208 int field;
3211 if ((array = malloc(steps*sizeof(double)))==NULL) {
3212 rrd_set_error("malloc VDEV_PERCENT");
3213 return -1;
3214 }
3215 for (step=0;step < steps; step++) {
3216 array[step]=data[step*src->ds_cnt];
3217 }
3218 qsort(array,step,sizeof(double),vdef_percent_compar);
3220 field = (steps-1)*dst->vf.param/100;
3221 dst->vf.val = array[field];
3222 dst->vf.when = 0; /* no time component */
3223 free(array);
3224 #if 0
3225 for(step=0;step<steps;step++)
3226 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3227 #endif
3228 }
3229 break;
3230 case VDEF_MAXIMUM:
3231 step=0;
3232 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3233 if (step == steps) {
3234 dst->vf.val = DNAN;
3235 dst->vf.when = 0;
3236 } else {
3237 dst->vf.val = data[step*src->ds_cnt];
3238 dst->vf.when = src->start + (step+1)*src->step;
3239 }
3240 while (step != steps) {
3241 if (finite(data[step*src->ds_cnt])) {
3242 if (data[step*src->ds_cnt] > dst->vf.val) {
3243 dst->vf.val = data[step*src->ds_cnt];
3244 dst->vf.when = src->start + (step+1)*src->step;
3245 }
3246 }
3247 step++;
3248 }
3249 break;
3250 case VDEF_TOTAL:
3251 case VDEF_AVERAGE: {
3252 int cnt=0;
3253 double sum=0.0;
3254 for (step=0;step<steps;step++) {
3255 if (finite(data[step*src->ds_cnt])) {
3256 sum += data[step*src->ds_cnt];
3257 cnt ++;
3258 };
3259 }
3260 if (cnt) {
3261 if (dst->vf.op == VDEF_TOTAL) {
3262 dst->vf.val = sum*src->step;
3263 dst->vf.when = cnt*src->step; /* not really "when" */
3264 } else {
3265 dst->vf.val = sum/cnt;
3266 dst->vf.when = 0; /* no time component */
3267 };
3268 } else {
3269 dst->vf.val = DNAN;
3270 dst->vf.when = 0;
3271 }
3272 }
3273 break;
3274 case VDEF_MINIMUM:
3275 step=0;
3276 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3277 if (step == steps) {
3278 dst->vf.val = DNAN;
3279 dst->vf.when = 0;
3280 } else {
3281 dst->vf.val = data[step*src->ds_cnt];
3282 dst->vf.when = src->start + (step+1)*src->step;
3283 }
3284 while (step != steps) {
3285 if (finite(data[step*src->ds_cnt])) {
3286 if (data[step*src->ds_cnt] < dst->vf.val) {
3287 dst->vf.val = data[step*src->ds_cnt];
3288 dst->vf.when = src->start + (step+1)*src->step;
3289 }
3290 }
3291 step++;
3292 }
3293 break;
3294 case VDEF_FIRST:
3295 /* The time value returned here is one step before the
3296 * actual time value. This is the start of the first
3297 * non-NaN interval.
3298 */
3299 step=0;
3300 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3301 if (step == steps) { /* all entries were NaN */
3302 dst->vf.val = DNAN;
3303 dst->vf.when = 0;
3304 } else {
3305 dst->vf.val = data[step*src->ds_cnt];
3306 dst->vf.when = src->start + step*src->step;
3307 }
3308 break;
3309 case VDEF_LAST:
3310 /* The time value returned here is the
3311 * actual time value. This is the end of the last
3312 * non-NaN interval.
3313 */
3314 step=steps-1;
3315 while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3316 if (step < 0) { /* all entries were NaN */
3317 dst->vf.val = DNAN;
3318 dst->vf.when = 0;
3319 } else {
3320 dst->vf.val = data[step*src->ds_cnt];
3321 dst->vf.when = src->start + (step+1)*src->step;
3322 }
3323 break;
3324 }
3325 return 0;
3326 }
3328 /* NaN < -INF < finite_values < INF */
3329 int
3330 vdef_percent_compar(a,b)
3331 const void *a,*b;
3332 {
3333 /* Equality is not returned; this doesn't hurt except
3334 * (maybe) for a little performance.
3335 */
3337 /* First catch NaN values. They are smallest */
3338 if (isnan( *(double *)a )) return -1;
3339 if (isnan( *(double *)b )) return 1;
3341 /* NaN doesn't reach this part so INF and -INF are extremes.
3342 * The sign from isinf() is compatible with the sign we return
3343 */
3344 if (isinf( *(double *)a )) return isinf( *(double *)a );
3345 if (isinf( *(double *)b )) return isinf( *(double *)b );
3347 /* If we reach this, both values must be finite */
3348 if ( *(double *)a < *(double *)b ) return -1; else return 1;
3349 }