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