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 leg_cc = strlen(im->gdes[i].legend);
1364 /* is there a controle code ant the end of the legend string ? */
1365 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\') {
1366 prt_fctn = im->gdes[i].legend[leg_cc-1];
1367 leg_cc -= 2;
1368 im->gdes[i].legend[leg_cc] = '\0';
1369 } else {
1370 prt_fctn = '\0';
1371 }
1372 /* remove exess space */
1373 while (prt_fctn=='g' &&
1374 leg_cc > 0 &&
1375 im->gdes[i].legend[leg_cc-1]==' '){
1376 leg_cc--;
1377 im->gdes[i].legend[leg_cc]='\0';
1378 }
1379 if (leg_cc != 0 ){
1380 legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1382 if (fill > 0){
1383 /* no interleg space if string ends in \g */
1384 fill += legspace[i];
1385 }
1386 if (im->gdes[i].gf != GF_GPRINT &&
1387 im->gdes[i].gf != GF_COMMENT) {
1388 fill += box;
1389 }
1390 fill += gfx_get_text_width(im->canvas, fill+border,
1391 im->text_prop[TEXT_PROP_LEGEND].font,
1392 im->text_prop[TEXT_PROP_LEGEND].size,
1393 im->tabwidth,
1394 im->gdes[i].legend);
1395 leg_c++;
1396 } else {
1397 legspace[i]=0;
1398 }
1399 /* who said there was a special tag ... ?*/
1400 if (prt_fctn=='g') {
1401 prt_fctn = '\0';
1402 }
1403 if (prt_fctn == '\0') {
1404 if (i == im->gdes_c -1 ) prt_fctn ='l';
1406 /* is it time to place the legends ? */
1407 if (fill > im->ximg - 2*border){
1408 if (leg_c > 1) {
1409 /* go back one */
1410 i--;
1411 fill = fill_last;
1412 leg_c--;
1413 prt_fctn = 'j';
1414 } else {
1415 prt_fctn = 'l';
1416 }
1418 }
1419 }
1422 if (prt_fctn != '\0'){
1423 leg_x = border;
1424 if (leg_c >= 2 && prt_fctn == 'j') {
1425 glue = (im->ximg - fill - 2* border) / (leg_c-1);
1426 } else {
1427 glue = 0;
1428 }
1429 if (prt_fctn =='c') leg_x = (im->ximg - fill) / 2.0;
1430 if (prt_fctn =='r') leg_x = im->ximg - fill - border;
1432 for(ii=mark;ii<=i;ii++){
1433 if(im->gdes[ii].legend[0]=='\0')
1434 continue;
1435 im->gdes[ii].leg_x = leg_x;
1436 im->gdes[ii].leg_y = leg_y;
1437 leg_x +=
1438 gfx_get_text_width(im->canvas, leg_x,
1439 im->text_prop[TEXT_PROP_LEGEND].font,
1440 im->text_prop[TEXT_PROP_LEGEND].size,
1441 im->tabwidth,
1442 im->gdes[ii].legend)
1443 + legspace[ii]
1444 + glue;
1445 if (im->gdes[ii].gf != GF_GPRINT &&
1446 im->gdes[ii].gf != GF_COMMENT)
1447 leg_x += box;
1448 }
1449 leg_y = leg_y + im->text_prop[TEXT_PROP_LEGEND].size*1.2;
1450 if (prt_fctn == 's') leg_y -= im->text_prop[TEXT_PROP_LEGEND].size*1.2;
1451 fill = 0;
1452 leg_c = 0;
1453 mark = ii;
1454 }
1455 }
1456 im->yimg = leg_y;
1457 free(legspace);
1458 }
1459 return 0;
1460 }
1462 /* create a grid on the graph. it determines what to do
1463 from the values of xsize, start and end */
1465 /* the xaxis labels are determined from the number of seconds per pixel
1466 in the requested graph */
1470 int
1471 calc_horizontal_grid(image_desc_t *im)
1472 {
1473 double range;
1474 double scaledrange;
1475 int pixel,i;
1476 int gridind;
1477 int decimals, fractionals;
1479 im->ygrid_scale.labfact=2;
1480 gridind=-1;
1481 range = im->maxval - im->minval;
1482 scaledrange = range / im->magfact;
1484 /* does the scale of this graph make it impossible to put lines
1485 on it? If so, give up. */
1486 if (isnan(scaledrange)) {
1487 return 0;
1488 }
1490 /* find grid spaceing */
1491 pixel=1;
1492 if(isnan(im->ygridstep)){
1493 if(im->extra_flags & ALTYGRID) {
1494 /* find the value with max number of digits. Get number of digits */
1495 decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))));
1496 if(decimals <= 0) /* everything is small. make place for zero */
1497 decimals = 1;
1499 fractionals = floor(log10(range));
1500 if(fractionals < 0) /* small amplitude. */
1501 sprintf(im->ygrid_scale.labfmt, "%%%d.%df", decimals - fractionals + 1, -fractionals + 1);
1502 else
1503 sprintf(im->ygrid_scale.labfmt, "%%%d.1f", decimals + 1);
1504 im->ygrid_scale.gridstep = pow((double)10, (double)fractionals);
1505 if(im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1506 im->ygrid_scale.gridstep = 0.1;
1507 /* should have at least 5 lines but no more then 15 */
1508 if(range/im->ygrid_scale.gridstep < 5)
1509 im->ygrid_scale.gridstep /= 10;
1510 if(range/im->ygrid_scale.gridstep > 15)
1511 im->ygrid_scale.gridstep *= 10;
1512 if(range/im->ygrid_scale.gridstep > 5) {
1513 im->ygrid_scale.labfact = 1;
1514 if(range/im->ygrid_scale.gridstep > 8)
1515 im->ygrid_scale.labfact = 2;
1516 }
1517 else {
1518 im->ygrid_scale.gridstep /= 5;
1519 im->ygrid_scale.labfact = 5;
1520 }
1521 }
1522 else {
1523 for(i=0;ylab[i].grid > 0;i++){
1524 pixel = im->ysize / (scaledrange / ylab[i].grid);
1525 if (gridind == -1 && pixel > 5) {
1526 gridind = i;
1527 break;
1528 }
1529 }
1531 for(i=0; i<4;i++) {
1532 if (pixel * ylab[gridind].lfac[i] >= 2 * im->text_prop[TEXT_PROP_AXIS].size) {
1533 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1534 break;
1535 }
1536 }
1538 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1539 }
1540 } else {
1541 im->ygrid_scale.gridstep = im->ygridstep;
1542 im->ygrid_scale.labfact = im->ylabfact;
1543 }
1544 return 1;
1545 }
1547 int draw_horizontal_grid(image_desc_t *im)
1548 {
1549 int i;
1550 double scaledstep;
1551 char graph_label[100];
1552 double X0=im->xorigin;
1553 double X1=im->xorigin+im->xsize;
1555 int sgrid = (int)( im->minval / im->ygrid_scale.gridstep - 1);
1556 int egrid = (int)( im->maxval / im->ygrid_scale.gridstep + 1);
1557 scaledstep = im->ygrid_scale.gridstep/im->magfact;
1558 for (i = sgrid; i <= egrid; i++){
1559 double Y0=ytr(im,im->ygrid_scale.gridstep*i);
1560 if ( Y0 >= im->yorigin-im->ysize
1561 && Y0 <= im->yorigin){
1562 if(i % im->ygrid_scale.labfact == 0){
1563 if (i==0 || im->symbol == ' ') {
1564 if(scaledstep < 1){
1565 if(im->extra_flags & ALTYGRID) {
1566 sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*i);
1567 }
1568 else {
1569 sprintf(graph_label,"%4.1f",scaledstep*i);
1570 }
1571 } else {
1572 sprintf(graph_label,"%4.0f",scaledstep*i);
1573 }
1574 }else {
1575 if(scaledstep < 1){
1576 sprintf(graph_label,"%4.1f %c",scaledstep*i, im->symbol);
1577 } else {
1578 sprintf(graph_label,"%4.0f %c",scaledstep*i, im->symbol);
1579 }
1580 }
1582 gfx_new_text ( im->canvas,
1583 X0-im->text_prop[TEXT_PROP_AXIS].size/1.5, Y0,
1584 im->graph_col[GRC_FONT],
1585 im->text_prop[TEXT_PROP_AXIS].font,
1586 im->text_prop[TEXT_PROP_AXIS].size,
1587 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1588 graph_label );
1589 gfx_new_dashed_line ( im->canvas,
1590 X0-2,Y0,
1591 X1+2,Y0,
1592 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1593 im->grid_dash_on, im->grid_dash_off);
1595 } else if (!(im->extra_flags & NOMINOR)) {
1596 gfx_new_dashed_line ( im->canvas,
1597 X0-1,Y0,
1598 X1+1,Y0,
1599 GRIDWIDTH, im->graph_col[GRC_GRID],
1600 im->grid_dash_on, im->grid_dash_off);
1602 }
1603 }
1604 }
1605 return 1;
1606 }
1608 /* logaritmic horizontal grid */
1609 int
1610 horizontal_log_grid(image_desc_t *im)
1611 {
1612 double pixpex;
1613 int ii,i;
1614 int minoridx=0, majoridx=0;
1615 char graph_label[100];
1616 double X0,X1,Y0;
1617 double value, pixperstep, minstep;
1619 /* find grid spaceing */
1620 pixpex= (double)im->ysize / (log10(im->maxval) - log10(im->minval));
1622 if (isnan(pixpex)) {
1623 return 0;
1624 }
1626 for(i=0;yloglab[i][0] > 0;i++){
1627 minstep = log10(yloglab[i][0]);
1628 for(ii=1;yloglab[i][ii+1] > 0;ii++){
1629 if(yloglab[i][ii+2]==0){
1630 minstep = log10(yloglab[i][ii+1])-log10(yloglab[i][ii]);
1631 break;
1632 }
1633 }
1634 pixperstep = pixpex * minstep;
1635 if(pixperstep > 5){minoridx = i;}
1636 if(pixperstep > 2 * im->text_prop[TEXT_PROP_LEGEND].size){majoridx = i;}
1637 }
1639 X0=im->xorigin;
1640 X1=im->xorigin+im->xsize;
1641 /* paint minor grid */
1642 for (value = pow((double)10, log10(im->minval)
1643 - fmod(log10(im->minval),log10(yloglab[minoridx][0])));
1644 value <= im->maxval;
1645 value *= yloglab[minoridx][0]){
1646 if (value < im->minval) continue;
1647 i=0;
1648 while(yloglab[minoridx][++i] > 0){
1649 Y0 = ytr(im,value * yloglab[minoridx][i]);
1650 if (Y0 <= im->yorigin - im->ysize) break;
1651 gfx_new_dashed_line ( im->canvas,
1652 X0-1,Y0,
1653 X1+1,Y0,
1654 GRIDWIDTH, im->graph_col[GRC_GRID],
1655 im->grid_dash_on, im->grid_dash_off);
1656 }
1657 }
1659 /* paint major grid and labels*/
1660 for (value = pow((double)10, log10(im->minval)
1661 - fmod(log10(im->minval),log10(yloglab[majoridx][0])));
1662 value <= im->maxval;
1663 value *= yloglab[majoridx][0]){
1664 if (value < im->minval) continue;
1665 i=0;
1666 while(yloglab[majoridx][++i] > 0){
1667 Y0 = ytr(im,value * yloglab[majoridx][i]);
1668 if (Y0 <= im->yorigin - im->ysize) break;
1669 gfx_new_dashed_line ( im->canvas,
1670 X0-2,Y0,
1671 X1+2,Y0,
1672 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1673 im->grid_dash_on, im->grid_dash_off);
1675 sprintf(graph_label,"%3.0e",value * yloglab[majoridx][i]);
1676 gfx_new_text ( im->canvas,
1677 X0-im->text_prop[TEXT_PROP_AXIS].size/1.5, Y0,
1678 im->graph_col[GRC_FONT],
1679 im->text_prop[TEXT_PROP_AXIS].font,
1680 im->text_prop[TEXT_PROP_AXIS].size,
1681 im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1682 graph_label );
1683 }
1684 }
1685 return 1;
1686 }
1689 void
1690 vertical_grid(
1691 image_desc_t *im )
1692 {
1693 int xlab_sel; /* which sort of label and grid ? */
1694 time_t ti, tilab, timajor;
1695 long factor;
1696 char graph_label[100];
1697 double X0,Y0,Y1; /* points for filled graph and more*/
1698 struct tm tm;
1700 /* the type of time grid is determined by finding
1701 the number of seconds per pixel in the graph */
1704 if(im->xlab_user.minsec == -1){
1705 factor=(im->end - im->start)/im->xsize;
1706 xlab_sel=0;
1707 while ( xlab[xlab_sel+1].minsec != -1
1708 && xlab[xlab_sel+1].minsec <= factor){ xlab_sel++; }
1709 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
1710 im->xlab_user.gridst = xlab[xlab_sel].gridst;
1711 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
1712 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
1713 im->xlab_user.labtm = xlab[xlab_sel].labtm;
1714 im->xlab_user.labst = xlab[xlab_sel].labst;
1715 im->xlab_user.precis = xlab[xlab_sel].precis;
1716 im->xlab_user.stst = xlab[xlab_sel].stst;
1717 }
1719 /* y coords are the same for every line ... */
1720 Y0 = im->yorigin;
1721 Y1 = im->yorigin-im->ysize;
1724 /* paint the minor grid */
1725 if (!(im->extra_flags & NOMINOR))
1726 {
1727 for(ti = find_first_time(im->start,
1728 im->xlab_user.gridtm,
1729 im->xlab_user.gridst),
1730 timajor = find_first_time(im->start,
1731 im->xlab_user.mgridtm,
1732 im->xlab_user.mgridst);
1733 ti < im->end;
1734 ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
1735 ){
1736 /* are we inside the graph ? */
1737 if (ti < im->start || ti > im->end) continue;
1738 while (timajor < ti) {
1739 timajor = find_next_time(timajor,
1740 im->xlab_user.mgridtm, im->xlab_user.mgridst);
1741 }
1742 if (ti == timajor) continue; /* skip as falls on major grid line */
1743 X0 = xtr(im,ti);
1744 gfx_new_dashed_line(im->canvas,X0,Y0+1, X0,Y1-1,GRIDWIDTH,
1745 im->graph_col[GRC_GRID],
1746 im->grid_dash_on, im->grid_dash_off);
1748 }
1749 }
1751 /* paint the major grid */
1752 for(ti = find_first_time(im->start,
1753 im->xlab_user.mgridtm,
1754 im->xlab_user.mgridst);
1755 ti < im->end;
1756 ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
1757 ){
1758 /* are we inside the graph ? */
1759 if (ti < im->start || ti > im->end) continue;
1760 X0 = xtr(im,ti);
1761 gfx_new_dashed_line(im->canvas,X0,Y0+3, X0,Y1-2,MGRIDWIDTH,
1762 im->graph_col[GRC_MGRID],
1763 im->grid_dash_on, im->grid_dash_off);
1765 }
1766 /* paint the labels below the graph */
1767 for(ti = find_first_time(im->start,
1768 im->xlab_user.labtm,
1769 im->xlab_user.labst);
1770 ti <= im->end;
1771 ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
1772 ){
1773 tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
1774 /* are we inside the graph ? */
1775 if (ti < im->start || ti > im->end) continue;
1777 #if HAVE_STRFTIME
1778 localtime_r(&tilab, &tm);
1779 strftime(graph_label,99,im->xlab_user.stst, &tm);
1780 #else
1781 # error "your libc has no strftime I guess we'll abort the exercise here."
1782 #endif
1783 gfx_new_text ( im->canvas,
1784 xtr(im,tilab), Y0+im->text_prop[TEXT_PROP_AXIS].size/1.5,
1785 im->graph_col[GRC_FONT],
1786 im->text_prop[TEXT_PROP_AXIS].font,
1787 im->text_prop[TEXT_PROP_AXIS].size,
1788 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP,
1789 graph_label );
1791 }
1793 }
1796 void
1797 axis_paint(
1798 image_desc_t *im
1799 )
1800 {
1801 /* draw x and y axis */
1802 gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
1803 im->xorigin+im->xsize,im->yorigin-im->ysize,
1804 GRIDWIDTH, im->graph_col[GRC_GRID]);
1806 gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
1807 im->xorigin+im->xsize,im->yorigin-im->ysize,
1808 GRIDWIDTH, im->graph_col[GRC_GRID]);
1810 gfx_new_line ( im->canvas, im->xorigin-4,im->yorigin,
1811 im->xorigin+im->xsize+4,im->yorigin,
1812 MGRIDWIDTH, im->graph_col[GRC_GRID]);
1814 gfx_new_line ( im->canvas, im->xorigin,im->yorigin+4,
1815 im->xorigin,im->yorigin-im->ysize-4,
1816 MGRIDWIDTH, im->graph_col[GRC_GRID]);
1819 /* arrow for X axis direction */
1820 gfx_new_area ( im->canvas,
1821 im->xorigin+im->xsize+3, im->yorigin-3,
1822 im->xorigin+im->xsize+3, im->yorigin+4,
1823 im->xorigin+im->xsize+8, im->yorigin+0.5, /* LINEOFFSET */
1824 im->graph_col[GRC_ARROW]);
1828 }
1830 void
1831 grid_paint(image_desc_t *im)
1832 {
1833 long i;
1834 int res=0;
1835 double X0,Y0; /* points for filled graph and more*/
1836 gfx_node_t *node;
1838 /* draw 3d border */
1839 node = gfx_new_area (im->canvas, 0,im->yimg,
1840 2,im->yimg-2,
1841 2,2,im->graph_col[GRC_SHADEA]);
1842 gfx_add_point( node , im->ximg - 2, 2 );
1843 gfx_add_point( node , im->ximg, 0 );
1844 gfx_add_point( node , 0,0 );
1845 /* gfx_add_point( node , 0,im->yimg ); */
1847 node = gfx_new_area (im->canvas, 2,im->yimg-2,
1848 im->ximg-2,im->yimg-2,
1849 im->ximg - 2, 2,
1850 im->graph_col[GRC_SHADEB]);
1851 gfx_add_point( node , im->ximg,0);
1852 gfx_add_point( node , im->ximg,im->yimg);
1853 gfx_add_point( node , 0,im->yimg);
1854 /* gfx_add_point( node , 0,im->yimg ); */
1857 if (im->draw_x_grid == 1 )
1858 vertical_grid(im);
1860 if (im->draw_y_grid == 1){
1861 if(im->logarithmic){
1862 res = horizontal_log_grid(im);
1863 } else {
1864 res = draw_horizontal_grid(im);
1865 }
1867 /* dont draw horizontal grid if there is no min and max val */
1868 if (! res ) {
1869 char *nodata = "No Data found";
1870 gfx_new_text(im->canvas,im->ximg/2, (2*im->yorigin-im->ysize) / 2,
1871 im->graph_col[GRC_FONT],
1872 im->text_prop[TEXT_PROP_AXIS].font,
1873 im->text_prop[TEXT_PROP_AXIS].size,
1874 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
1875 nodata );
1876 }
1877 }
1879 /* yaxis description */
1880 if (im->canvas->imgformat != IF_PNG) {
1881 gfx_new_text( im->canvas,
1882 7, (im->yorigin - im->ysize/2),
1883 im->graph_col[GRC_FONT],
1884 im->text_prop[TEXT_PROP_AXIS].font,
1885 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 270.0,
1886 GFX_H_CENTER, GFX_V_CENTER,
1887 im->ylegend);
1888 } else {
1889 /* horrible hack until we can actually print vertically */
1890 {
1891 int n;
1892 int l=strlen(im->ylegend);
1893 char s[2];
1894 for (n=0;n<strlen(im->ylegend);n++) {
1895 s[0]=im->ylegend[n];
1896 s[1]='\0';
1897 gfx_new_text(im->canvas,7,im->text_prop[TEXT_PROP_AXIS].size*(l-n),
1898 im->graph_col[GRC_FONT],
1899 im->text_prop[TEXT_PROP_AXIS].font,
1900 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 270.0,
1901 GFX_H_CENTER, GFX_V_CENTER,
1902 s);
1903 }
1904 }
1905 }
1907 /* graph title */
1908 gfx_new_text( im->canvas,
1909 im->ximg/2, im->text_prop[TEXT_PROP_TITLE].size,
1910 im->graph_col[GRC_FONT],
1911 im->text_prop[TEXT_PROP_TITLE].font,
1912 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
1913 GFX_H_CENTER, GFX_V_CENTER,
1914 im->title);
1916 /* graph labels */
1917 if( !(im->extra_flags & NOLEGEND) ) {
1918 for(i=0;i<im->gdes_c;i++){
1919 if(im->gdes[i].legend[0] =='\0')
1920 continue;
1922 /* im->gdes[i].leg_y is the bottom of the legend */
1923 X0 = im->gdes[i].leg_x;
1924 Y0 = im->gdes[i].leg_y;
1925 /* Box needed? */
1926 if ( im->gdes[i].gf != GF_GPRINT
1927 && im->gdes[i].gf != GF_COMMENT) {
1928 int boxH, boxV;
1930 boxH = gfx_get_text_width(im->canvas, 0,
1931 im->text_prop[TEXT_PROP_AXIS].font,
1932 im->text_prop[TEXT_PROP_AXIS].size,
1933 im->tabwidth,"M") * 1.25;
1934 boxV = boxH;
1936 node = gfx_new_area(im->canvas,
1937 X0,Y0-boxV,
1938 X0,Y0,
1939 X0+boxH,Y0,
1940 im->gdes[i].col);
1941 gfx_add_point ( node, X0+boxH, Y0-boxV );
1942 node = gfx_new_line(im->canvas,
1943 X0,Y0-boxV, X0,Y0,
1944 1,0x000000FF);
1945 gfx_add_point(node,X0+boxH,Y0);
1946 gfx_add_point(node,X0+boxH,Y0-boxV);
1947 gfx_close_path(node);
1948 X0 += boxH / 1.25 * 2;
1949 }
1950 gfx_new_text ( im->canvas, X0, Y0,
1951 im->graph_col[GRC_FONT],
1952 im->text_prop[TEXT_PROP_AXIS].font,
1953 im->text_prop[TEXT_PROP_AXIS].size,
1954 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
1955 im->gdes[i].legend );
1956 }
1957 }
1958 }
1961 /*****************************************************
1962 * lazy check make sure we rely need to create this graph
1963 *****************************************************/
1965 int lazy_check(image_desc_t *im){
1966 FILE *fd = NULL;
1967 int size = 1;
1968 struct stat imgstat;
1970 if (im->lazy == 0) return 0; /* no lazy option */
1971 if (stat(im->graphfile,&imgstat) != 0)
1972 return 0; /* can't stat */
1973 /* one pixel in the existing graph is more then what we would
1974 change here ... */
1975 if (time(NULL) - imgstat.st_mtime >
1976 (im->end - im->start) / im->xsize)
1977 return 0;
1978 if ((fd = fopen(im->graphfile,"rb")) == NULL)
1979 return 0; /* the file does not exist */
1980 switch (im->canvas->imgformat) {
1981 case IF_PNG:
1982 size = PngSize(fd,&(im->ximg),&(im->yimg));
1983 break;
1984 default:
1985 size = 1;
1986 }
1987 fclose(fd);
1988 return size;
1989 }
1991 void
1992 pie_part(image_desc_t *im, gfx_color_t color,
1993 double PieCenterX, double PieCenterY, double Radius,
1994 double startangle, double endangle)
1995 {
1996 gfx_node_t *node;
1997 double angle;
1998 double step=M_PI/50; /* Number of iterations for the circle;
1999 ** 10 is definitely too low, more than
2000 ** 50 seems to be overkill
2001 */
2003 /* Strange but true: we have to work clockwise or else
2004 ** anti aliasing nor transparency don't work.
2005 **
2006 ** This test is here to make sure we do it right, also
2007 ** this makes the for...next loop more easy to implement.
2008 ** The return will occur if the user enters a negative number
2009 ** (which shouldn't be done according to the specs) or if the
2010 ** programmers do something wrong (which, as we all know, never
2011 ** happens anyway :)
2012 */
2013 if (endangle<startangle) return;
2015 /* Hidden feature: Radius decreases each full circle */
2016 angle=startangle;
2017 while (angle>=2*M_PI) {
2018 angle -= 2*M_PI;
2019 Radius *= 0.8;
2020 }
2022 node=gfx_new_area(im->canvas,
2023 PieCenterX+sin(startangle)*Radius,
2024 PieCenterY-cos(startangle)*Radius,
2025 PieCenterX,
2026 PieCenterY,
2027 PieCenterX+sin(endangle)*Radius,
2028 PieCenterY-cos(endangle)*Radius,
2029 color);
2030 for (angle=endangle;angle-startangle>=step;angle-=step) {
2031 gfx_add_point(node,
2032 PieCenterX+sin(angle)*Radius,
2033 PieCenterY-cos(angle)*Radius );
2034 }
2035 }
2037 int
2038 graph_size_location(image_desc_t *im, int elements, int piechart )
2039 {
2040 /* The actual size of the image to draw is determined from
2041 ** several sources. The size given on the command line is
2042 ** the graph area but we need more as we have to draw labels
2043 ** and other things outside the graph area
2044 */
2046 /* +-+-------------------------------------------+
2047 ** |l|.................title.....................|
2048 ** |e+--+-------------------------------+--------+
2049 ** |b| b| | |
2050 ** |a| a| | pie |
2051 ** |l| l| main graph area | chart |
2052 ** |.| .| | area |
2053 ** |t| y| | |
2054 ** |r+--+-------------------------------+--------+
2055 ** |e| | x-axis labels | |
2056 ** |v+--+-------------------------------+--------+
2057 ** | |..............legends......................|
2058 ** +-+-------------------------------------------+
2059 */
2060 int Xvertical=0, Yvertical=0,
2061 Xtitle =0, Ytitle =0,
2062 Xylabel =0, Yylabel =0,
2063 Xmain =0, Ymain =0,
2064 Xpie =0, Ypie =0,
2065 Xxlabel =0, Yxlabel =0,
2066 #if 0
2067 Xlegend =0, Ylegend =0,
2068 #endif
2069 Xspacing =10, Yspacing =10;
2071 if (im->ylegend[0] != '\0') {
2072 Xvertical = im->text_prop[TEXT_PROP_LEGEND].size *2;
2073 Yvertical = im->text_prop[TEXT_PROP_LEGEND].size * (strlen(im->ylegend)+1);
2074 }
2076 if (im->title[0] != '\0') {
2077 /* The title is placed "inbetween" two text lines so it
2078 ** automatically has some vertical spacing. The horizontal
2079 ** spacing is added here, on each side.
2080 */
2081 Xtitle = gfx_get_text_width(im->canvas, 0,
2082 im->text_prop[TEXT_PROP_TITLE].font,
2083 im->text_prop[TEXT_PROP_TITLE].size,
2084 im->tabwidth,
2085 im->title) + 2*Xspacing;
2086 Ytitle = im->text_prop[TEXT_PROP_TITLE].size*2;
2087 }
2089 if (elements) {
2090 Xmain=im->xsize;
2091 Ymain=im->ysize;
2092 if (im->draw_x_grid) {
2093 Xxlabel=Xmain;
2094 Yxlabel=im->text_prop[TEXT_PROP_LEGEND].size *2;
2095 }
2096 if (im->draw_y_grid) {
2097 Xylabel=im->text_prop[TEXT_PROP_LEGEND].size *6;
2098 Yylabel=Ymain;
2099 }
2100 }
2102 if (piechart) {
2103 im->piesize=im->xsize<im->ysize?im->xsize:im->ysize;
2104 Xpie=im->piesize;
2105 Ypie=im->piesize;
2106 }
2108 /* Now calculate the total size. Insert some spacing where
2109 desired. im->xorigin and im->yorigin need to correspond
2110 with the lower left corner of the main graph area or, if
2111 this one is not set, the imaginary box surrounding the
2112 pie chart area. */
2114 /* The legend width cannot yet be determined, as a result we
2115 ** have problems adjusting the image to it. For now, we just
2116 ** forget about it at all; the legend will have to fit in the
2117 ** size already allocated.
2118 */
2119 im->ximg = Xylabel + Xmain + Xpie + Xspacing;
2120 if (Xmain) im->ximg += Xspacing;
2121 if (Xpie) im->ximg += Xspacing;
2122 im->xorigin = Xspacing + Xylabel;
2123 if (Xtitle > im->ximg) im->ximg = Xtitle;
2124 if (Xvertical) {
2125 im->ximg += Xvertical;
2126 im->xorigin += Xvertical;
2127 }
2128 xtr(im,0);
2130 /* The vertical size is interesting... we need to compare
2131 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend} with Yvertical
2132 ** however we need to know {Ytitle+Ymain+Yxlabel} in order to
2133 ** start even thinking about Ylegend.
2134 **
2135 ** Do it in three portions: First calculate the inner part,
2136 ** then do the legend, then adjust the total height of the img.
2137 */
2139 /* reserve space for main and/or pie */
2140 im->yimg = Ymain + Yxlabel;
2141 if (im->yimg < Ypie) im->yimg = Ypie;
2142 im->yorigin = im->yimg - Yxlabel;
2143 /* reserve space for the title *or* some padding above the graph */
2144 if (Ytitle) {
2145 im->yimg += Ytitle;
2146 im->yorigin += Ytitle;
2147 } else {
2148 im->yimg += Yspacing;
2149 im->yorigin += Yspacing;
2150 }
2151 /* reserve space for padding below the graph */
2152 im->yimg += Yspacing;
2153 ytr(im,DNAN);
2155 /* Determine where to place the legends onto the image.
2156 ** Adjust im->yimg to match the space requirements.
2157 */
2158 if(leg_place(im)==-1)
2159 return -1;
2161 /* last of three steps: check total height of image */
2162 if (im->yimg < Yvertical) im->yimg = Yvertical;
2164 #if 0
2165 if (Xlegend > im->ximg) {
2166 im->ximg = Xlegend;
2167 /* reposition Pie */
2168 }
2169 #endif
2171 /* The pie is placed in the upper right hand corner,
2172 ** just below the title (if any) and with sufficient
2173 ** padding.
2174 */
2175 if (elements) {
2176 im->pie_x = im->ximg - Xspacing - Xpie/2;
2177 im->pie_y = im->yorigin-Ymain+Ypie/2;
2178 } else {
2179 im->pie_x = im->ximg/2;
2180 im->pie_y = im->yorigin-Ypie/2;
2181 }
2183 return 0;
2184 }
2186 /* draw that picture thing ... */
2187 int
2188 graph_paint(image_desc_t *im, char ***calcpr)
2189 {
2190 int i,ii;
2191 int lazy = lazy_check(im);
2192 int piechart = 0;
2193 double PieStart=0.0;
2194 FILE *fo;
2195 gfx_node_t *node;
2197 double areazero = 0.0;
2198 enum gf_en stack_gf = GF_PRINT;
2199 graph_desc_t *lastgdes = NULL;
2201 /* if we are lazy and there is nothing to PRINT ... quit now */
2202 if (lazy && im->prt_c==0) return 0;
2204 /* pull the data from the rrd files ... */
2206 if(data_fetch(im)==-1)
2207 return -1;
2209 /* evaluate VDEF and CDEF operations ... */
2210 if(data_calc(im)==-1)
2211 return -1;
2213 /* check if we need to draw a piechart */
2214 for(i=0;i<im->gdes_c;i++){
2215 if (im->gdes[i].gf == GF_PART) {
2216 piechart=1;
2217 break;
2218 }
2219 }
2221 /* calculate and PRINT and GPRINT definitions. We have to do it at
2222 * this point because it will affect the length of the legends
2223 * if there are no graph elements we stop here ...
2224 * if we are lazy, try to quit ...
2225 */
2226 i=print_calc(im,calcpr);
2227 if(i<0) return -1;
2228 if(((i==0)&&(piechart==0)) || lazy) return 0;
2230 /* If there's only the pie chart to draw, signal this */
2231 if (i==0) piechart=2;
2233 /* get actual drawing data and find min and max values*/
2234 if(data_proc(im)==-1)
2235 return -1;
2237 if(!im->logarithmic){si_unit(im);} /* identify si magnitude Kilo, Mega Giga ? */
2239 if(!im->rigid && ! im->logarithmic)
2240 expand_range(im); /* make sure the upper and lower limit are
2241 sensible values */
2243 if (!calc_horizontal_grid(im))
2244 return -1;
2245 if (im->gridfit)
2246 apply_gridfit(im);
2248 /**************************************************************
2249 *** Calculating sizes and locations became a bit confusing ***
2250 *** so I moved this into a separate function. ***
2251 **************************************************************/
2252 if(graph_size_location(im,i,piechart)==-1)
2253 return -1;
2255 /* the actual graph is created by going through the individual
2256 graph elements and then drawing them */
2258 node=gfx_new_area ( im->canvas,
2259 0, 0,
2260 im->ximg, 0,
2261 im->ximg, im->yimg,
2262 im->graph_col[GRC_BACK]);
2264 gfx_add_point(node,0, im->yimg);
2266 if (piechart != 2) {
2267 node=gfx_new_area ( im->canvas,
2268 im->xorigin, im->yorigin,
2269 im->xorigin + im->xsize, im->yorigin,
2270 im->xorigin + im->xsize, im->yorigin-im->ysize,
2271 im->graph_col[GRC_CANVAS]);
2273 gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
2275 if (im->minval > 0.0)
2276 areazero = im->minval;
2277 if (im->maxval < 0.0)
2278 areazero = im->maxval;
2280 axis_paint(im);
2281 }
2283 if (piechart) {
2284 pie_part(im,im->graph_col[GRC_CANVAS],im->pie_x,im->pie_y,im->piesize*0.5,0,2*M_PI);
2285 }
2287 for(i=0;i<im->gdes_c;i++){
2288 switch(im->gdes[i].gf){
2289 case GF_CDEF:
2290 case GF_VDEF:
2291 case GF_DEF:
2292 case GF_PRINT:
2293 case GF_GPRINT:
2294 case GF_COMMENT:
2295 case GF_HRULE:
2296 case GF_VRULE:
2297 case GF_XPORT:
2298 break;
2299 case GF_TICK:
2300 for (ii = 0; ii < im->xsize; ii++)
2301 {
2302 if (!isnan(im->gdes[i].p_data[ii]) &&
2303 im->gdes[i].p_data[ii] > 0.0)
2304 {
2305 /* generate a tick */
2306 gfx_new_line(im->canvas, im -> xorigin + ii,
2307 im -> yorigin - (im -> gdes[i].yrule * im -> ysize),
2308 im -> xorigin + ii,
2309 im -> yorigin,
2310 1.0,
2311 im -> gdes[i].col );
2312 }
2313 }
2314 break;
2315 case GF_LINE:
2316 case GF_AREA:
2317 stack_gf = im->gdes[i].gf;
2318 case GF_STACK:
2319 /* fix data points at oo and -oo */
2320 for(ii=0;ii<im->xsize;ii++){
2321 if (isinf(im->gdes[i].p_data[ii])){
2322 if (im->gdes[i].p_data[ii] > 0) {
2323 im->gdes[i].p_data[ii] = im->maxval ;
2324 } else {
2325 im->gdes[i].p_data[ii] = im->minval ;
2326 }
2328 }
2329 } /* for */
2331 if (im->gdes[i].col != 0x0){
2332 /* GF_LINE and friend */
2333 if(stack_gf == GF_LINE ){
2334 node = NULL;
2335 for(ii=1;ii<im->xsize;ii++){
2336 if ( ! isnan(im->gdes[i].p_data[ii-1])
2337 && ! isnan(im->gdes[i].p_data[ii])){
2338 if (node == NULL){
2339 node = gfx_new_line(im->canvas,
2340 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2341 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2342 im->gdes[i].linewidth,
2343 im->gdes[i].col);
2344 } else {
2345 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2346 }
2347 } else {
2348 node = NULL;
2349 }
2350 }
2351 } else {
2352 int area_start=-1;
2353 node = NULL;
2354 for(ii=1;ii<im->xsize;ii++){
2355 /* open an area */
2356 if ( ! isnan(im->gdes[i].p_data[ii-1])
2357 && ! isnan(im->gdes[i].p_data[ii])){
2358 if (node == NULL){
2359 float ybase = 0.0;
2360 /*
2361 if (im->gdes[i].gf == GF_STACK) {
2362 */
2363 if ( (im->gdes[i].gf == GF_STACK)
2364 || (im->gdes[i].stack) ) {
2366 ybase = ytr(im,lastgdes->p_data[ii-1]);
2367 } else {
2368 ybase = ytr(im,areazero);
2369 }
2370 area_start = ii-1;
2371 node = gfx_new_area(im->canvas,
2372 ii-1+im->xorigin,ybase,
2373 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2374 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2375 im->gdes[i].col
2376 );
2377 } else {
2378 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2379 }
2380 }
2382 if ( node != NULL && (ii+1==im->xsize || isnan(im->gdes[i].p_data[ii]) )){
2383 /* GF_AREA STACK type*/
2384 /*
2385 if (im->gdes[i].gf == GF_STACK ) {
2386 */
2387 if ( (im->gdes[i].gf == GF_STACK)
2388 || (im->gdes[i].stack) ) {
2389 int iii;
2390 for (iii=ii-1;iii>area_start;iii--){
2391 gfx_add_point(node,iii+im->xorigin,ytr(im,lastgdes->p_data[iii]));
2392 }
2393 } else {
2394 gfx_add_point(node,ii+im->xorigin,ytr(im,areazero));
2395 };
2396 node=NULL;
2397 };
2398 }
2399 } /* else GF_LINE */
2400 } /* if color != 0x0 */
2401 /* make sure we do not run into trouble when stacking on NaN */
2402 for(ii=0;ii<im->xsize;ii++){
2403 if (isnan(im->gdes[i].p_data[ii])) {
2404 if (lastgdes && (im->gdes[i].gf == GF_STACK)) {
2405 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
2406 } else {
2407 im->gdes[i].p_data[ii] = ytr(im,areazero);
2408 }
2409 }
2410 }
2411 lastgdes = &(im->gdes[i]);
2412 break;
2413 case GF_PART:
2414 if(isnan(im->gdes[i].yrule)) /* fetch variable */
2415 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2417 if (finite(im->gdes[i].yrule)) { /* even the fetched var can be NaN */
2418 pie_part(im,im->gdes[i].col,
2419 im->pie_x,im->pie_y,im->piesize*0.4,
2420 M_PI*2.0*PieStart/100.0,
2421 M_PI*2.0*(PieStart+im->gdes[i].yrule)/100.0);
2422 PieStart += im->gdes[i].yrule;
2423 }
2424 break;
2425 } /* switch */
2426 }
2427 if (piechart==2) {
2428 im->draw_x_grid=0;
2429 im->draw_y_grid=0;
2430 }
2431 /* grid_paint also does the text */
2432 grid_paint(im);
2434 /* the RULES are the last thing to paint ... */
2435 for(i=0;i<im->gdes_c;i++){
2437 switch(im->gdes[i].gf){
2438 case GF_HRULE:
2439 if(isnan(im->gdes[i].yrule)) { /* fetch variable */
2440 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2441 };
2442 if(im->gdes[i].yrule >= im->minval
2443 && im->gdes[i].yrule <= im->maxval)
2444 gfx_new_line(im->canvas,
2445 im->xorigin,ytr(im,im->gdes[i].yrule),
2446 im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2447 1.0,im->gdes[i].col);
2448 break;
2449 case GF_VRULE:
2450 if(im->gdes[i].xrule == 0) { /* fetch variable */
2451 im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
2452 };
2453 if(im->gdes[i].xrule >= im->start
2454 && im->gdes[i].xrule <= im->end)
2455 gfx_new_line(im->canvas,
2456 xtr(im,im->gdes[i].xrule),im->yorigin,
2457 xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2458 1.0,im->gdes[i].col);
2459 break;
2460 default:
2461 break;
2462 }
2463 }
2466 if (strcmp(im->graphfile,"-")==0) {
2467 #ifdef WIN32
2468 /* Change translation mode for stdout to BINARY */
2469 _setmode( _fileno( stdout ), O_BINARY );
2470 #endif
2471 fo = stdout;
2472 } else {
2473 if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2474 rrd_set_error("Opening '%s' for write: %s",im->graphfile,
2475 rrd_strerror(errno));
2476 return (-1);
2477 }
2478 }
2479 gfx_render (im->canvas,im->ximg,im->yimg,0x0,fo);
2480 if (strcmp(im->graphfile,"-") != 0)
2481 fclose(fo);
2482 return 0;
2483 }
2486 /*****************************************************
2487 * graph stuff
2488 *****************************************************/
2490 int
2491 gdes_alloc(image_desc_t *im){
2493 unsigned long def_step = (im->end-im->start)/im->xsize;
2495 if (im->step > def_step) /* step can be increassed ... no decreassed */
2496 def_step = im->step;
2498 im->gdes_c++;
2500 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2501 * sizeof(graph_desc_t)))==NULL){
2502 rrd_set_error("realloc graph_descs");
2503 return -1;
2504 }
2507 im->gdes[im->gdes_c-1].step=def_step;
2508 im->gdes[im->gdes_c-1].stack=0;
2509 im->gdes[im->gdes_c-1].debug=0;
2510 im->gdes[im->gdes_c-1].start=im->start;
2511 im->gdes[im->gdes_c-1].end=im->end;
2512 im->gdes[im->gdes_c-1].vname[0]='\0';
2513 im->gdes[im->gdes_c-1].data=NULL;
2514 im->gdes[im->gdes_c-1].ds_namv=NULL;
2515 im->gdes[im->gdes_c-1].data_first=0;
2516 im->gdes[im->gdes_c-1].p_data=NULL;
2517 im->gdes[im->gdes_c-1].rpnp=NULL;
2518 im->gdes[im->gdes_c-1].col = 0x0;
2519 im->gdes[im->gdes_c-1].legend[0]='\0';
2520 im->gdes[im->gdes_c-1].rrd[0]='\0';
2521 im->gdes[im->gdes_c-1].ds=-1;
2522 im->gdes[im->gdes_c-1].p_data=NULL;
2523 im->gdes[im->gdes_c-1].yrule=DNAN;
2524 im->gdes[im->gdes_c-1].xrule=0;
2525 return 0;
2526 }
2528 /* copies input untill the first unescaped colon is found
2529 or until input ends. backslashes have to be escaped as well */
2530 int
2531 scan_for_col(char *input, int len, char *output)
2532 {
2533 int inp,outp=0;
2534 for (inp=0;
2535 inp < len &&
2536 input[inp] != ':' &&
2537 input[inp] != '\0';
2538 inp++){
2539 if (input[inp] == '\\' &&
2540 input[inp+1] != '\0' &&
2541 (input[inp+1] == '\\' ||
2542 input[inp+1] == ':')){
2543 output[outp++] = input[++inp];
2544 }
2545 else {
2546 output[outp++] = input[inp];
2547 }
2548 }
2549 output[outp] = '\0';
2550 return inp;
2551 }
2552 /* Some surgery done on this function, it became ridiculously big.
2553 ** Things moved:
2554 ** - initializing now in rrd_graph_init()
2555 ** - options parsing now in rrd_graph_options()
2556 ** - script parsing now in rrd_graph_script()
2557 */
2558 int
2559 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize)
2560 {
2561 image_desc_t im;
2563 rrd_graph_init(&im);
2565 rrd_graph_options(argc,argv,&im);
2566 if (rrd_test_error()) {
2567 im_free(&im);
2568 return -1;
2569 }
2571 if (strlen(argv[optind])>=MAXPATH) {
2572 rrd_set_error("filename (including path) too long");
2573 im_free(&im);
2574 return -1;
2575 }
2576 strncpy(im.graphfile,argv[optind],MAXPATH-1);
2577 im.graphfile[MAXPATH-1]='\0';
2579 rrd_graph_script(argc,argv,&im);
2580 if (rrd_test_error()) {
2581 im_free(&im);
2582 return -1;
2583 }
2585 /* Everything is now read and the actual work can start */
2587 (*prdata)=NULL;
2588 if (graph_paint(&im,prdata)==-1){
2589 im_free(&im);
2590 return -1;
2591 }
2593 /* The image is generated and needs to be output.
2594 ** Also, if needed, print a line with information about the image.
2595 */
2597 *xsize=im.ximg;
2598 *ysize=im.yimg;
2599 if (im.imginfo) {
2600 char *filename;
2601 if (!(*prdata)) {
2602 /* maybe prdata is not allocated yet ... lets do it now */
2603 if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
2604 rrd_set_error("malloc imginfo");
2605 return -1;
2606 };
2607 }
2608 if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
2609 ==NULL){
2610 rrd_set_error("malloc imginfo");
2611 return -1;
2612 }
2613 filename=im.graphfile+strlen(im.graphfile);
2614 while(filename > im.graphfile) {
2615 if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
2616 filename--;
2617 }
2619 sprintf((*prdata)[0],im.imginfo,filename,(long)(im.canvas->zoom*im.ximg),(long)(im.canvas->zoom*im.yimg));
2620 }
2621 im_free(&im);
2622 return 0;
2623 }
2625 void
2626 rrd_graph_init(image_desc_t *im)
2627 {
2628 unsigned int i;
2630 #ifdef HAVE_TZSET
2631 tzset();
2632 #endif
2633 #ifdef HAVE_SETLOCALE
2634 setlocale(LC_TIME,"");
2635 #endif
2637 im->xlab_user.minsec = -1;
2638 im->ximg=0;
2639 im->yimg=0;
2640 im->xsize = 400;
2641 im->ysize = 100;
2642 im->step = 0;
2643 im->ylegend[0] = '\0';
2644 im->title[0] = '\0';
2645 im->minval = DNAN;
2646 im->maxval = DNAN;
2647 im->unitsexponent= 9999;
2648 im->extra_flags= 0;
2649 im->rigid = 0;
2650 im->gridfit = 1;
2651 im->imginfo = NULL;
2652 im->lazy = 0;
2653 im->logarithmic = 0;
2654 im->ygridstep = DNAN;
2655 im->draw_x_grid = 1;
2656 im->draw_y_grid = 1;
2657 im->base = 1000;
2658 im->prt_c = 0;
2659 im->gdes_c = 0;
2660 im->gdes = NULL;
2661 im->canvas = gfx_new_canvas();
2662 im->grid_dash_on = 1;
2663 im->grid_dash_off = 1;
2665 for(i=0;i<DIM(graph_col);i++)
2666 im->graph_col[i]=graph_col[i];
2668 for(i=0;i<DIM(text_prop);i++){
2669 im->text_prop[i].size = text_prop[i].size;
2670 im->text_prop[i].font = text_prop[i].font;
2671 }
2672 }
2674 void
2675 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
2676 {
2677 int stroff;
2678 char *parsetime_error = NULL;
2679 char scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
2680 time_t start_tmp=0,end_tmp=0;
2681 long long_tmp;
2682 struct time_value start_tv, end_tv;
2683 gfx_color_t color;
2685 parsetime("end-24h", &start_tv);
2686 parsetime("now", &end_tv);
2688 while (1){
2689 static struct option long_options[] =
2690 {
2691 {"start", required_argument, 0, 's'},
2692 {"end", required_argument, 0, 'e'},
2693 {"x-grid", required_argument, 0, 'x'},
2694 {"y-grid", required_argument, 0, 'y'},
2695 {"vertical-label",required_argument,0,'v'},
2696 {"width", required_argument, 0, 'w'},
2697 {"height", required_argument, 0, 'h'},
2698 {"interlaced", no_argument, 0, 'i'},
2699 {"upper-limit",required_argument, 0, 'u'},
2700 {"lower-limit",required_argument, 0, 'l'},
2701 {"rigid", no_argument, 0, 'r'},
2702 {"base", required_argument, 0, 'b'},
2703 {"logarithmic",no_argument, 0, 'o'},
2704 {"color", required_argument, 0, 'c'},
2705 {"font", required_argument, 0, 'n'},
2706 {"title", required_argument, 0, 't'},
2707 {"imginfo", required_argument, 0, 'f'},
2708 {"imgformat", required_argument, 0, 'a'},
2709 {"lazy", no_argument, 0, 'z'},
2710 {"zoom", required_argument, 0, 'm'},
2711 {"no-legend", no_argument, 0, 'g'},
2712 {"alt-y-grid", no_argument, 0, 'Y'},
2713 {"no-minor", no_argument, 0, 'I'},
2714 {"alt-autoscale", no_argument, 0, 'A'},
2715 {"alt-autoscale-max", no_argument, 0, 'M'},
2716 {"units-exponent",required_argument, 0, 'X'},
2717 {"step", required_argument, 0, 'S'},
2718 {"no-gridfit", no_argument, 0, 'N'},
2719 {0,0,0,0}};
2720 int option_index = 0;
2721 int opt;
2724 opt = getopt_long(argc, argv,
2725 "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:I:zgYAMX:S:N",
2726 long_options, &option_index);
2728 if (opt == EOF)
2729 break;
2731 switch(opt) {
2732 case 'I':
2733 im->extra_flags |= NOMINOR;
2734 break;
2735 case 'Y':
2736 im->extra_flags |= ALTYGRID;
2737 break;
2738 case 'A':
2739 im->extra_flags |= ALTAUTOSCALE;
2740 break;
2741 case 'M':
2742 im->extra_flags |= ALTAUTOSCALE_MAX;
2743 break;
2744 case 'g':
2745 im->extra_flags |= NOLEGEND;
2746 break;
2747 case 'X':
2748 im->unitsexponent = atoi(optarg);
2749 break;
2750 case 'S':
2751 im->step = atoi(optarg);
2752 break;
2753 case 262:
2754 im->gridfit = 0;
2755 break;
2756 case 's':
2757 if ((parsetime_error = parsetime(optarg, &start_tv))) {
2758 rrd_set_error( "start time: %s", parsetime_error );
2759 return;
2760 }
2761 break;
2762 case 'e':
2763 if ((parsetime_error = parsetime(optarg, &end_tv))) {
2764 rrd_set_error( "end time: %s", parsetime_error );
2765 return;
2766 }
2767 break;
2768 case 'x':
2769 if(strcmp(optarg,"none") == 0){
2770 im->draw_x_grid=0;
2771 break;
2772 };
2774 if(sscanf(optarg,
2775 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
2776 scan_gtm,
2777 &im->xlab_user.gridst,
2778 scan_mtm,
2779 &im->xlab_user.mgridst,
2780 scan_ltm,
2781 &im->xlab_user.labst,
2782 &im->xlab_user.precis,
2783 &stroff) == 7 && stroff != 0){
2784 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
2785 if((im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
2786 rrd_set_error("unknown keyword %s",scan_gtm);
2787 return;
2788 } else if ((im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
2789 rrd_set_error("unknown keyword %s",scan_mtm);
2790 return;
2791 } else if ((im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
2792 rrd_set_error("unknown keyword %s",scan_ltm);
2793 return;
2794 }
2795 im->xlab_user.minsec = 1;
2796 im->xlab_user.stst = im->xlab_form;
2797 } else {
2798 rrd_set_error("invalid x-grid format");
2799 return;
2800 }
2801 break;
2802 case 'y':
2804 if(strcmp(optarg,"none") == 0){
2805 im->draw_y_grid=0;
2806 break;
2807 };
2809 if(sscanf(optarg,
2810 "%lf:%d",
2811 &im->ygridstep,
2812 &im->ylabfact) == 2) {
2813 if(im->ygridstep<=0){
2814 rrd_set_error("grid step must be > 0");
2815 return;
2816 } else if (im->ylabfact < 1){
2817 rrd_set_error("label factor must be > 0");
2818 return;
2819 }
2820 } else {
2821 rrd_set_error("invalid y-grid format");
2822 return;
2823 }
2824 break;
2825 case 'v':
2826 strncpy(im->ylegend,optarg,150);
2827 im->ylegend[150]='\0';
2828 break;
2829 case 'u':
2830 im->maxval = atof(optarg);
2831 break;
2832 case 'l':
2833 im->minval = atof(optarg);
2834 break;
2835 case 'b':
2836 im->base = atol(optarg);
2837 if(im->base != 1024 && im->base != 1000 ){
2838 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
2839 return;
2840 }
2841 break;
2842 case 'w':
2843 long_tmp = atol(optarg);
2844 if (long_tmp < 10) {
2845 rrd_set_error("width below 10 pixels");
2846 return;
2847 }
2848 im->xsize = long_tmp;
2849 break;
2850 case 'h':
2851 long_tmp = atol(optarg);
2852 if (long_tmp < 10) {
2853 rrd_set_error("height below 10 pixels");
2854 return;
2855 }
2856 im->ysize = long_tmp;
2857 break;
2858 case 'i':
2859 im->canvas->interlaced = 1;
2860 break;
2861 case 'r':
2862 im->rigid = 1;
2863 break;
2864 case 'f':
2865 im->imginfo = optarg;
2866 break;
2867 case 'a':
2868 if((im->canvas->imgformat = if_conv(optarg)) == -1) {
2869 rrd_set_error("unsupported graphics format '%s'",optarg);
2870 return;
2871 }
2872 break;
2873 case 'z':
2874 im->lazy = 1;
2875 break;
2876 case 'o':
2877 im->logarithmic = 1;
2878 if (isnan(im->minval))
2879 im->minval=1;
2880 break;
2881 case 'c':
2882 if(sscanf(optarg,
2883 "%10[A-Z]#%8lx",
2884 col_nam,&color) == 2){
2885 int ci;
2886 if((ci=grc_conv(col_nam)) != -1){
2887 im->graph_col[ci]=color;
2888 } else {
2889 rrd_set_error("invalid color name '%s'",col_nam);
2890 }
2891 } else {
2892 rrd_set_error("invalid color def format");
2893 return;
2894 }
2895 break;
2896 case 'n':{
2897 /* originally this used char *prop = "" and
2898 ** char *font = "dummy" however this results
2899 ** in a SEG fault, at least on RH7.1
2900 **
2901 ** The current implementation isn't proper
2902 ** either, font is never freed and prop uses
2903 ** a fixed width string
2904 */
2905 char prop[100];
2906 double size = 1;
2907 char *font;
2909 font=malloc(255);
2910 if(sscanf(optarg,
2911 "%10[A-Z]:%lf:%s",
2912 prop,&size,font) == 3){
2913 int sindex;
2914 if((sindex=text_prop_conv(prop)) != -1){
2915 im->text_prop[sindex].size=size;
2916 im->text_prop[sindex].font=font;
2917 if (sindex==0) { /* the default */
2918 im->text_prop[TEXT_PROP_TITLE].size=size;
2919 im->text_prop[TEXT_PROP_TITLE].font=font;
2920 im->text_prop[TEXT_PROP_AXIS].size=size;
2921 im->text_prop[TEXT_PROP_AXIS].font=font;
2922 im->text_prop[TEXT_PROP_UNIT].size=size;
2923 im->text_prop[TEXT_PROP_UNIT].font=font;
2924 im->text_prop[TEXT_PROP_LEGEND].size=size;
2925 im->text_prop[TEXT_PROP_LEGEND].font=font;
2926 }
2927 } else {
2928 rrd_set_error("invalid fonttag '%s'",prop);
2929 return;
2930 }
2931 } else {
2932 rrd_set_error("invalid text property format");
2933 return;
2934 }
2935 break;
2936 }
2937 case 'm':
2938 im->canvas->zoom = atof(optarg);
2939 if (im->canvas->zoom <= 0.0) {
2940 rrd_set_error("zoom factor must be > 0");
2941 return;
2942 }
2943 break;
2944 case 't':
2945 strncpy(im->title,optarg,150);
2946 im->title[150]='\0';
2947 break;
2949 case '?':
2950 if (optopt != 0)
2951 rrd_set_error("unknown option '%c'", optopt);
2952 else
2953 rrd_set_error("unknown option '%s'",argv[optind-1]);
2954 return;
2955 }
2956 }
2958 if (optind >= argc) {
2959 rrd_set_error("missing filename");
2960 return;
2961 }
2963 if (im->logarithmic == 1 && (im->minval <= 0 || isnan(im->minval))){
2964 rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");
2965 return;
2966 }
2968 if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
2969 /* error string is set in parsetime.c */
2970 return;
2971 }
2973 if (start_tmp < 3600*24*365*10){
2974 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
2975 return;
2976 }
2978 if (end_tmp < start_tmp) {
2979 rrd_set_error("start (%ld) should be less than end (%ld)",
2980 start_tmp, end_tmp);
2981 return;
2982 }
2984 im->start = start_tmp;
2985 im->end = end_tmp;
2986 }
2988 int
2989 rrd_graph_check_vname(image_desc_t *im, char *varname, char *err)
2990 {
2991 if ((im->gdes[im->gdes_c-1].vidx=find_var(im,varname))==-1) {
2992 rrd_set_error("Unknown variable '%s' in %s",varname,err);
2993 return -1;
2994 }
2995 return 0;
2996 }
2997 int
2998 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
2999 {
3000 char *color;
3001 graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
3003 color=strstr(var,"#");
3004 if (color==NULL) {
3005 if (optional==0) {
3006 rrd_set_error("Found no color in %s",err);
3007 return 0;
3008 }
3009 return 0;
3010 } else {
3011 int n=0;
3012 char *rest;
3013 gfx_color_t col;
3015 rest=strstr(color,":");
3016 if (rest!=NULL)
3017 n=rest-color;
3018 else
3019 n=strlen(color);
3021 switch (n) {
3022 case 7:
3023 sscanf(color,"#%6lx%n",&col,&n);
3024 col = (col << 8) + 0xff /* shift left by 8 */;
3025 if (n!=7) rrd_set_error("Color problem in %s",err);
3026 break;
3027 case 9:
3028 sscanf(color,"#%8lx%n",&col,&n);
3029 if (n==9) break;
3030 default:
3031 rrd_set_error("Color problem in %s",err);
3032 }
3033 if (rrd_test_error()) return 0;
3034 gdp->col = col;
3035 return n;
3036 }
3037 }
3038 int
3039 rrd_graph_legend(graph_desc_t *gdp, char *line)
3040 {
3041 int i;
3043 i=scan_for_col(line,FMT_LEG_LEN,gdp->legend);
3045 return (strlen(&line[i])==0);
3046 }
3049 int bad_format(char *fmt) {
3050 char *ptr;
3051 int n=0;
3052 ptr = fmt;
3053 while (*ptr != '\0')
3054 if (*ptr++ == '%') {
3056 /* line cannot end with percent char */
3057 if (*ptr == '\0') return 1;
3059 /* '%s', '%S' and '%%' are allowed */
3060 if (*ptr == 's' || *ptr == 'S' || *ptr == '%') ptr++;
3062 /* or else '% 6.2lf' and such are allowed */
3063 else {
3065 /* optional padding character */
3066 if (*ptr == ' ' || *ptr == '+' || *ptr == '-') ptr++;
3068 /* This should take care of 'm.n' with all three optional */
3069 while (*ptr >= '0' && *ptr <= '9') ptr++;
3070 if (*ptr == '.') ptr++;
3071 while (*ptr >= '0' && *ptr <= '9') ptr++;
3073 /* Either 'le', 'lf' or 'lg' must follow here */
3074 if (*ptr++ != 'l') return 1;
3075 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g') ptr++;
3076 else return 1;
3077 n++;
3078 }
3079 }
3081 return (n!=1);
3082 }
3085 int
3086 vdef_parse(gdes,str)
3087 struct graph_desc_t *gdes;
3088 char *str;
3089 {
3090 /* A VDEF currently is either "func" or "param,func"
3091 * so the parsing is rather simple. Change if needed.
3092 */
3093 double param;
3094 char func[30];
3095 int n;
3097 n=0;
3098 sscanf(str,"%le,%29[A-Z]%n",¶m,func,&n);
3099 if (n==strlen(str)) { /* matched */
3100 ;
3101 } else {
3102 n=0;
3103 sscanf(str,"%29[A-Z]%n",func,&n);
3104 if (n==strlen(str)) { /* matched */
3105 param=DNAN;
3106 } else {
3107 rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3108 ,str
3109 ,gdes->vname
3110 );
3111 return -1;
3112 }
3113 }
3114 if (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3115 else if (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3116 else if (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3117 else if (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3118 else if (!strcmp("TOTAL", func)) gdes->vf.op = VDEF_TOTAL;
3119 else if (!strcmp("FIRST", func)) gdes->vf.op = VDEF_FIRST;
3120 else if (!strcmp("LAST", func)) gdes->vf.op = VDEF_LAST;
3121 else {
3122 rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3123 ,func
3124 ,gdes->vname
3125 );
3126 return -1;
3127 };
3129 switch (gdes->vf.op) {
3130 case VDEF_PERCENT:
3131 if (isnan(param)) { /* no parameter given */
3132 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3133 ,func
3134 ,gdes->vname
3135 );
3136 return -1;
3137 };
3138 if (param>=0.0 && param<=100.0) {
3139 gdes->vf.param = param;
3140 gdes->vf.val = DNAN; /* undefined */
3141 gdes->vf.when = 0; /* undefined */
3142 } else {
3143 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3144 ,param
3145 ,gdes->vname
3146 );
3147 return -1;
3148 };
3149 break;
3150 case VDEF_MAXIMUM:
3151 case VDEF_AVERAGE:
3152 case VDEF_MINIMUM:
3153 case VDEF_TOTAL:
3154 case VDEF_FIRST:
3155 case VDEF_LAST:
3156 if (isnan(param)) {
3157 gdes->vf.param = DNAN;
3158 gdes->vf.val = DNAN;
3159 gdes->vf.when = 0;
3160 } else {
3161 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3162 ,func
3163 ,gdes->vname
3164 );
3165 return -1;
3166 };
3167 break;
3168 };
3169 return 0;
3170 }
3173 int
3174 vdef_calc(im,gdi)
3175 image_desc_t *im;
3176 int gdi;
3177 {
3178 graph_desc_t *src,*dst;
3179 rrd_value_t *data;
3180 long step,steps;
3182 dst = &im->gdes[gdi];
3183 src = &im->gdes[dst->vidx];
3184 data = src->data + src->ds;
3185 steps = (src->end - src->start) / src->step;
3187 #if 0
3188 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3189 ,src->start
3190 ,src->end
3191 ,steps
3192 );
3193 #endif
3195 switch (dst->vf.op) {
3196 case VDEF_PERCENT: {
3197 rrd_value_t * array;
3198 int field;
3201 if ((array = malloc(steps*sizeof(double)))==NULL) {
3202 rrd_set_error("malloc VDEV_PERCENT");
3203 return -1;
3204 }
3205 for (step=0;step < steps; step++) {
3206 array[step]=data[step*src->ds_cnt];
3207 }
3208 qsort(array,step,sizeof(double),vdef_percent_compar);
3210 field = (steps-1)*dst->vf.param/100;
3211 dst->vf.val = array[field];
3212 dst->vf.when = 0; /* no time component */
3213 free(array);
3214 #if 0
3215 for(step=0;step<steps;step++)
3216 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3217 #endif
3218 }
3219 break;
3220 case VDEF_MAXIMUM:
3221 step=0;
3222 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3223 if (step == steps) {
3224 dst->vf.val = DNAN;
3225 dst->vf.when = 0;
3226 } else {
3227 dst->vf.val = data[step*src->ds_cnt];
3228 dst->vf.when = src->start + (step+1)*src->step;
3229 }
3230 while (step != steps) {
3231 if (finite(data[step*src->ds_cnt])) {
3232 if (data[step*src->ds_cnt] > dst->vf.val) {
3233 dst->vf.val = data[step*src->ds_cnt];
3234 dst->vf.when = src->start + (step+1)*src->step;
3235 }
3236 }
3237 step++;
3238 }
3239 break;
3240 case VDEF_TOTAL:
3241 case VDEF_AVERAGE: {
3242 int cnt=0;
3243 double sum=0.0;
3244 for (step=0;step<steps;step++) {
3245 if (finite(data[step*src->ds_cnt])) {
3246 sum += data[step*src->ds_cnt];
3247 cnt ++;
3248 };
3249 }
3250 if (cnt) {
3251 if (dst->vf.op == VDEF_TOTAL) {
3252 dst->vf.val = sum*src->step;
3253 dst->vf.when = cnt*src->step; /* not really "when" */
3254 } else {
3255 dst->vf.val = sum/cnt;
3256 dst->vf.when = 0; /* no time component */
3257 };
3258 } else {
3259 dst->vf.val = DNAN;
3260 dst->vf.when = 0;
3261 }
3262 }
3263 break;
3264 case VDEF_MINIMUM:
3265 step=0;
3266 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3267 if (step == steps) {
3268 dst->vf.val = DNAN;
3269 dst->vf.when = 0;
3270 } else {
3271 dst->vf.val = data[step*src->ds_cnt];
3272 dst->vf.when = src->start + (step+1)*src->step;
3273 }
3274 while (step != steps) {
3275 if (finite(data[step*src->ds_cnt])) {
3276 if (data[step*src->ds_cnt] < dst->vf.val) {
3277 dst->vf.val = data[step*src->ds_cnt];
3278 dst->vf.when = src->start + (step+1)*src->step;
3279 }
3280 }
3281 step++;
3282 }
3283 break;
3284 case VDEF_FIRST:
3285 /* The time value returned here is one step before the
3286 * actual time value. This is the start of the first
3287 * non-NaN interval.
3288 */
3289 step=0;
3290 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3291 if (step == steps) { /* all entries were NaN */
3292 dst->vf.val = DNAN;
3293 dst->vf.when = 0;
3294 } else {
3295 dst->vf.val = data[step*src->ds_cnt];
3296 dst->vf.when = src->start + step*src->step;
3297 }
3298 break;
3299 case VDEF_LAST:
3300 /* The time value returned here is the
3301 * actual time value. This is the end of the last
3302 * non-NaN interval.
3303 */
3304 step=steps-1;
3305 while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3306 if (step < 0) { /* all entries were NaN */
3307 dst->vf.val = DNAN;
3308 dst->vf.when = 0;
3309 } else {
3310 dst->vf.val = data[step*src->ds_cnt];
3311 dst->vf.when = src->start + (step+1)*src->step;
3312 }
3313 break;
3314 }
3315 return 0;
3316 }
3318 /* NaN < -INF < finite_values < INF */
3319 int
3320 vdef_percent_compar(a,b)
3321 const void *a,*b;
3322 {
3323 /* Equality is not returned; this doesn't hurt except
3324 * (maybe) for a little performance.
3325 */
3327 /* First catch NaN values. They are smallest */
3328 if (isnan( *(double *)a )) return -1;
3329 if (isnan( *(double *)b )) return 1;
3331 /* NaN doesn't reach this part so INF and -INF are extremes.
3332 * The sign from isinf() is compatible with the sign we return
3333 */
3334 if (isinf( *(double *)a )) return isinf( *(double *)a );
3335 if (isinf( *(double *)b )) return isinf( *(double *)b );
3337 /* If we reach this, both values must be finite */
3338 if ( *(double *)a < *(double *)b ) return -1; else return 1;
3339 }