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