afcbc0147072fd6faf981b307f130c14b0e9e101
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 is smaller than max */
1076 if (im->minval > im->maxval) {
1077 im->minval = 0.99 * im->maxval;
1078 }
1080 /* make sure min and max are not equal */
1081 if (im->minval == im->maxval) {
1082 im->maxval *= 1.01;
1083 if (! im->logarithmic) {
1084 im->minval *= 0.99;
1085 }
1086 /* make sure min and max are not both zero */
1087 if (im->maxval == 0.0) {
1088 im->maxval = 1.0;
1089 }
1090 }
1091 return 0;
1092 }
1096 /* identify the point where the first gridline, label ... gets placed */
1098 time_t
1099 find_first_time(
1100 time_t start, /* what is the initial time */
1101 enum tmt_en baseint, /* what is the basic interval */
1102 long basestep /* how many if these do we jump a time */
1103 )
1104 {
1105 struct tm tm;
1106 localtime_r(&start, &tm);
1107 switch(baseint){
1108 case TMT_SECOND:
1109 tm.tm_sec -= tm.tm_sec % basestep; break;
1110 case TMT_MINUTE:
1111 tm.tm_sec=0;
1112 tm.tm_min -= tm.tm_min % basestep;
1113 break;
1114 case TMT_HOUR:
1115 tm.tm_sec=0;
1116 tm.tm_min = 0;
1117 tm.tm_hour -= tm.tm_hour % basestep; break;
1118 case TMT_DAY:
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; break;
1123 case TMT_WEEK:
1124 /* we do NOT look at the basestep for this ... */
1125 tm.tm_sec=0;
1126 tm.tm_min = 0;
1127 tm.tm_hour = 0;
1128 tm.tm_mday -= tm.tm_wday -1; /* -1 because we want the monday */
1129 if (tm.tm_wday==0) tm.tm_mday -= 7; /* we want the *previous* monday */
1130 break;
1131 case TMT_MONTH:
1132 tm.tm_sec=0;
1133 tm.tm_min = 0;
1134 tm.tm_hour = 0;
1135 tm.tm_mday = 1;
1136 tm.tm_mon -= tm.tm_mon % basestep; break;
1138 case TMT_YEAR:
1139 tm.tm_sec=0;
1140 tm.tm_min = 0;
1141 tm.tm_hour = 0;
1142 tm.tm_mday = 1;
1143 tm.tm_mon = 0;
1144 tm.tm_year -= (tm.tm_year+1900) % basestep;
1146 }
1147 return mktime(&tm);
1148 }
1149 /* identify the point where the next gridline, label ... gets placed */
1150 time_t
1151 find_next_time(
1152 time_t current, /* what is the initial time */
1153 enum tmt_en baseint, /* what is the basic interval */
1154 long basestep /* how many if these do we jump a time */
1155 )
1156 {
1157 struct tm tm;
1158 time_t madetime;
1159 localtime_r(¤t, &tm);
1160 do {
1161 switch(baseint){
1162 case TMT_SECOND:
1163 tm.tm_sec += basestep; break;
1164 case TMT_MINUTE:
1165 tm.tm_min += basestep; break;
1166 case TMT_HOUR:
1167 tm.tm_hour += basestep; break;
1168 case TMT_DAY:
1169 tm.tm_mday += basestep; break;
1170 case TMT_WEEK:
1171 tm.tm_mday += 7*basestep; break;
1172 case TMT_MONTH:
1173 tm.tm_mon += basestep; break;
1174 case TMT_YEAR:
1175 tm.tm_year += basestep;
1176 }
1177 madetime = mktime(&tm);
1178 } while (madetime == -1); /* this is necessary to skip impssible times
1179 like the daylight saving time skips */
1180 return madetime;
1182 }
1185 /* calculate values required for PRINT and GPRINT functions */
1187 int
1188 print_calc(image_desc_t *im, char ***prdata)
1189 {
1190 long i,ii,validsteps;
1191 double printval;
1192 time_t printtime;
1193 int graphelement = 0;
1194 long vidx;
1195 int max_ii;
1196 double magfact = -1;
1197 char *si_symb = "";
1198 char *percent_s;
1199 int prlines = 1;
1200 if (im->imginfo) prlines++;
1201 for(i=0;i<im->gdes_c;i++){
1202 switch(im->gdes[i].gf){
1203 case GF_PRINT:
1204 prlines++;
1205 if(((*prdata) = rrd_realloc((*prdata),prlines*sizeof(char *)))==NULL){
1206 rrd_set_error("realloc prdata");
1207 return 0;
1208 }
1209 case GF_GPRINT:
1210 /* PRINT and GPRINT can now print VDEF generated values.
1211 * There's no need to do any calculations on them as these
1212 * calculations were already made.
1213 */
1214 vidx = im->gdes[i].vidx;
1215 if (im->gdes[vidx].gf==GF_VDEF) { /* simply use vals */
1216 printval = im->gdes[vidx].vf.val;
1217 printtime = im->gdes[vidx].vf.when;
1218 } else { /* need to calculate max,min,avg etcetera */
1219 max_ii =((im->gdes[vidx].end
1220 - im->gdes[vidx].start)
1221 / im->gdes[vidx].step
1222 * im->gdes[vidx].ds_cnt);
1223 printval = DNAN;
1224 validsteps = 0;
1225 for( ii=im->gdes[vidx].ds;
1226 ii < max_ii;
1227 ii+=im->gdes[vidx].ds_cnt){
1228 if (! finite(im->gdes[vidx].data[ii]))
1229 continue;
1230 if (isnan(printval)){
1231 printval = im->gdes[vidx].data[ii];
1232 validsteps++;
1233 continue;
1234 }
1236 switch (im->gdes[i].cf){
1237 case CF_HWPREDICT:
1238 case CF_DEVPREDICT:
1239 case CF_DEVSEASONAL:
1240 case CF_SEASONAL:
1241 case CF_AVERAGE:
1242 validsteps++;
1243 printval += im->gdes[vidx].data[ii];
1244 break;
1245 case CF_MINIMUM:
1246 printval = min( printval, im->gdes[vidx].data[ii]);
1247 break;
1248 case CF_FAILURES:
1249 case CF_MAXIMUM:
1250 printval = max( printval, im->gdes[vidx].data[ii]);
1251 break;
1252 case CF_LAST:
1253 printval = im->gdes[vidx].data[ii];
1254 }
1255 }
1256 if (im->gdes[i].cf==CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1257 if (validsteps > 1) {
1258 printval = (printval / validsteps);
1259 }
1260 }
1261 } /* prepare printval */
1263 if (!strcmp(im->gdes[i].format,"%c")) { /* VDEF time print */
1264 char ctime_buf[128]; /* PS: for ctime_r, must be >= 26 chars */
1265 if (im->gdes[i].gf == GF_PRINT){
1266 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1267 sprintf((*prdata)[prlines-2],"%s (%lu)",
1268 ctime_r(&printtime,ctime_buf),printtime);
1269 (*prdata)[prlines-1] = NULL;
1270 } else {
1271 sprintf(im->gdes[i].legend,"%s (%lu)",
1272 ctime_r(&printtime,ctime_buf),printtime);
1273 graphelement = 1;
1274 }
1275 } else {
1276 if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) {
1277 /* Magfact is set to -1 upon entry to print_calc. If it
1278 * is still less than 0, then we need to run auto_scale.
1279 * Otherwise, put the value into the correct units. If
1280 * the value is 0, then do not set the symbol or magnification
1281 * so next the calculation will be performed again. */
1282 if (magfact < 0.0) {
1283 auto_scale(im,&printval,&si_symb,&magfact);
1284 if (printval == 0.0)
1285 magfact = -1.0;
1286 } else {
1287 printval /= magfact;
1288 }
1289 *(++percent_s) = 's';
1290 } else if (strstr(im->gdes[i].format,"%s") != NULL) {
1291 auto_scale(im,&printval,&si_symb,&magfact);
1292 }
1294 if (im->gdes[i].gf == GF_PRINT){
1295 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1296 (*prdata)[prlines-1] = NULL;
1297 if (bad_format(im->gdes[i].format)) {
1298 rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1299 return -1;
1300 }
1301 #ifdef HAVE_SNPRINTF
1302 snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
1303 #else
1304 sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
1305 #endif
1306 } else {
1307 /* GF_GPRINT */
1309 if (bad_format(im->gdes[i].format)) {
1310 rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1311 return -1;
1312 }
1313 #ifdef HAVE_SNPRINTF
1314 snprintf(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
1315 #else
1316 sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
1317 #endif
1318 graphelement = 1;
1319 }
1320 }
1321 break;
1322 case GF_LINE:
1323 case GF_AREA:
1324 case GF_TICK:
1325 case GF_STACK:
1326 case GF_HRULE:
1327 case GF_VRULE:
1328 graphelement = 1;
1329 break;
1330 case GF_COMMENT:
1331 case GF_DEF:
1332 case GF_CDEF:
1333 case GF_VDEF:
1334 case GF_PART:
1335 case GF_XPORT:
1336 break;
1337 }
1338 }
1339 return graphelement;
1340 }
1343 /* place legends with color spots */
1344 int
1345 leg_place(image_desc_t *im)
1346 {
1347 /* graph labels */
1348 int interleg = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1349 int box =im->text_prop[TEXT_PROP_LEGEND].size*1.5;
1350 int border = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1351 int fill=0, fill_last;
1352 int leg_c = 0;
1353 int leg_x = border, leg_y = im->yimg;
1354 int leg_cc;
1355 int glue = 0;
1356 int i,ii, mark = 0;
1357 char prt_fctn; /*special printfunctions */
1358 int *legspace;
1360 if( !(im->extra_flags & NOLEGEND) ) {
1361 if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1362 rrd_set_error("malloc for legspace");
1363 return -1;
1364 }
1366 for(i=0;i<im->gdes_c;i++){
1367 fill_last = fill;
1369 /* hid legends for rules which are not displayed */
1371 if (im->gdes[i].gf == GF_HRULE &&
1372 (im->gdes[i].yrule < im->minval || im->gdes[i].yrule > im->maxval))
1373 im->gdes[i].legend[0] = '\0';
1375 if (im->gdes[i].gf == GF_VRULE &&
1376 (im->gdes[i].xrule < im->start || im->gdes[i].xrule > im->end))
1377 im->gdes[i].legend[0] = '\0';
1379 leg_cc = strlen(im->gdes[i].legend);
1381 /* is there a controle code ant the end of the legend string ? */
1382 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\') {
1383 prt_fctn = im->gdes[i].legend[leg_cc-1];
1384 leg_cc -= 2;
1385 im->gdes[i].legend[leg_cc] = '\0';
1386 } else {
1387 prt_fctn = '\0';
1388 }
1389 /* remove exess space */
1390 while (prt_fctn=='g' &&
1391 leg_cc > 0 &&
1392 im->gdes[i].legend[leg_cc-1]==' '){
1393 leg_cc--;
1394 im->gdes[i].legend[leg_cc]='\0';
1395 }
1396 if (leg_cc != 0 ){
1397 legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1399 if (fill > 0){
1400 /* no interleg space if string ends in \g */
1401 fill += legspace[i];
1402 }
1403 if (im->gdes[i].gf != GF_GPRINT &&
1404 im->gdes[i].gf != GF_COMMENT) {
1405 fill += box;
1406 }
1407 fill += gfx_get_text_width(im->canvas, fill+border,
1408 im->text_prop[TEXT_PROP_LEGEND].font,
1409 im->text_prop[TEXT_PROP_LEGEND].size,
1410 im->tabwidth,
1411 im->gdes[i].legend, 0);
1412 leg_c++;
1413 } else {
1414 legspace[i]=0;
1415 }
1416 /* who said there was a special tag ... ?*/
1417 if (prt_fctn=='g') {
1418 prt_fctn = '\0';
1419 }
1420 if (prt_fctn == '\0') {
1421 if (i == im->gdes_c -1 ) prt_fctn ='l';
1423 /* is it time to place the legends ? */
1424 if (fill > im->ximg - 2*border){
1425 if (leg_c > 1) {
1426 /* go back one */
1427 i--;
1428 fill = fill_last;
1429 leg_c--;
1430 prt_fctn = 'j';
1431 } else {
1432 prt_fctn = 'l';
1433 }
1435 }
1436 }
1439 if (prt_fctn != '\0'){
1440 leg_x = border;
1441 if (leg_c >= 2 && prt_fctn == 'j') {
1442 glue = (im->ximg - fill - 2* border) / (leg_c-1);
1443 } else {
1444 glue = 0;
1445 }
1446 if (prt_fctn =='c') leg_x = (im->ximg - fill) / 2.0;
1447 if (prt_fctn =='r') leg_x = im->ximg - fill - border;
1449 for(ii=mark;ii<=i;ii++){
1450 if(im->gdes[ii].legend[0]=='\0')
1451 continue;
1452 im->gdes[ii].leg_x = leg_x;
1453 im->gdes[ii].leg_y = leg_y;
1454 leg_x +=
1455 gfx_get_text_width(im->canvas, leg_x,
1456 im->text_prop[TEXT_PROP_LEGEND].font,
1457 im->text_prop[TEXT_PROP_LEGEND].size,
1458 im->tabwidth,
1459 im->gdes[ii].legend, 0)
1460 + legspace[ii]
1461 + glue;
1462 if (im->gdes[ii].gf != GF_GPRINT &&
1463 im->gdes[ii].gf != GF_COMMENT)
1464 leg_x += box;
1465 }
1466 leg_y = leg_y + im->text_prop[TEXT_PROP_LEGEND].size*1.2;
1467 if (prt_fctn == 's') leg_y -= im->text_prop[TEXT_PROP_LEGEND].size*1.2;
1468 fill = 0;
1469 leg_c = 0;
1470 mark = ii;
1471 }
1472 }
1473 im->yimg = leg_y;
1474 free(legspace);
1475 }
1476 return 0;
1477 }
1479 /* create a grid on the graph. it determines what to do
1480 from the values of xsize, start and end */
1482 /* the xaxis labels are determined from the number of seconds per pixel
1483 in the requested graph */
1487 int
1488 calc_horizontal_grid(image_desc_t *im)
1489 {
1490 double range;
1491 double scaledrange;
1492 int pixel,i;
1493 int gridind;
1494 int decimals, fractionals;
1496 im->ygrid_scale.labfact=2;
1497 gridind=-1;
1498 range = im->maxval - im->minval;
1499 scaledrange = range / im->magfact;
1501 /* does the scale of this graph make it impossible to put lines
1502 on it? If so, give up. */
1503 if (isnan(scaledrange)) {
1504 return 0;
1505 }
1507 /* find grid spaceing */
1508 pixel=1;
1509 if(isnan(im->ygridstep)){
1510 if(im->extra_flags & ALTYGRID) {
1511 /* find the value with max number of digits. Get number of digits */
1512 decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))));
1513 if(decimals <= 0) /* everything is small. make place for zero */
1514 decimals = 1;
1516 fractionals = floor(log10(range));
1517 if(fractionals < 0) /* small amplitude. */
1518 sprintf(im->ygrid_scale.labfmt, "%%%d.%df", decimals - fractionals + 1, -fractionals + 1);
1519 else
1520 sprintf(im->ygrid_scale.labfmt, "%%%d.1f", decimals + 1);
1521 im->ygrid_scale.gridstep = pow((double)10, (double)fractionals);
1522 if(im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1523 im->ygrid_scale.gridstep = 0.1;
1524 /* should have at least 5 lines but no more then 15 */
1525 if(range/im->ygrid_scale.gridstep < 5)
1526 im->ygrid_scale.gridstep /= 10;
1527 if(range/im->ygrid_scale.gridstep > 15)
1528 im->ygrid_scale.gridstep *= 10;
1529 if(range/im->ygrid_scale.gridstep > 5) {
1530 im->ygrid_scale.labfact = 1;
1531 if(range/im->ygrid_scale.gridstep > 8)
1532 im->ygrid_scale.labfact = 2;
1533 }
1534 else {
1535 im->ygrid_scale.gridstep /= 5;
1536 im->ygrid_scale.labfact = 5;
1537 }
1538 }
1539 else {
1540 for(i=0;ylab[i].grid > 0;i++){
1541 pixel = im->ysize / (scaledrange / ylab[i].grid);
1542 if (gridind == -1 && pixel > 5) {
1543 gridind = i;
1544 break;
1545 }
1546 }
1548 for(i=0; i<4;i++) {
1549 if (pixel * ylab[gridind].lfac[i] >= 2 * im->text_prop[TEXT_PROP_AXIS].size) {
1550 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1551 break;
1552 }
1553 }
1555 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1556 }
1557 } else {
1558 im->ygrid_scale.gridstep = im->ygridstep;
1559 im->ygrid_scale.labfact = im->ylabfact;
1560 }
1561 return 1;
1562 }
1564 int draw_horizontal_grid(image_desc_t *im)
1565 {
1566 int i;
1567 double scaledstep;
1568 char graph_label[100];
1569 double X0=im->xorigin;
1570 double X1=im->xorigin+im->xsize;
1572 int sgrid = (int)( im->minval / im->ygrid_scale.gridstep - 1);
1573 int egrid = (int)( im->maxval / im->ygrid_scale.gridstep + 1);
1574 scaledstep = im->ygrid_scale.gridstep/im->magfact;
1575 for (i = sgrid; i <= egrid; i++){
1576 double Y0=ytr(im,im->ygrid_scale.gridstep*i);
1577 if ( Y0 >= im->yorigin-im->ysize
1578 && Y0 <= im->yorigin){
1579 if(i % im->ygrid_scale.labfact == 0){
1580 if (i==0 || im->symbol == ' ') {
1581 if(scaledstep < 1){
1582 if(im->extra_flags & ALTYGRID) {
1583 sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*i);
1584 }
1585 else {
1586 sprintf(graph_label,"%4.1f",scaledstep*i);
1587 }
1588 } else {
1589 sprintf(graph_label,"%4.0f",scaledstep*i);
1590 }
1591 }else {
1592 if(scaledstep < 1){
1593 sprintf(graph_label,"%4.1f %c",scaledstep*i, im->symbol);
1594 } else {
1595 sprintf(graph_label,"%4.0f %c",scaledstep*i, im->symbol);
1596 }
1597 }
1599 gfx_new_text ( im->canvas,
1600 X0-im->text_prop[TEXT_PROP_AXIS].size/1.5, Y0,
1601 im->graph_col[GRC_FONT],
1602 im->text_prop[TEXT_PROP_AXIS].font,
1603 im->text_prop[TEXT_PROP_AXIS].size,
1604 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1605 graph_label );
1606 gfx_new_dashed_line ( im->canvas,
1607 X0-2,Y0,
1608 X1+2,Y0,
1609 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1610 im->grid_dash_on, im->grid_dash_off);
1612 } else if (!(im->extra_flags & NOMINOR)) {
1613 gfx_new_dashed_line ( im->canvas,
1614 X0-1,Y0,
1615 X1+1,Y0,
1616 GRIDWIDTH, im->graph_col[GRC_GRID],
1617 im->grid_dash_on, im->grid_dash_off);
1619 }
1620 }
1621 }
1622 return 1;
1623 }
1625 /* logaritmic horizontal grid */
1626 int
1627 horizontal_log_grid(image_desc_t *im)
1628 {
1629 double pixpex;
1630 int ii,i;
1631 int minoridx=0, majoridx=0;
1632 char graph_label[100];
1633 double X0,X1,Y0;
1634 double value, pixperstep, minstep;
1636 /* find grid spaceing */
1637 pixpex= (double)im->ysize / (log10(im->maxval) - log10(im->minval));
1639 if (isnan(pixpex)) {
1640 return 0;
1641 }
1643 for(i=0;yloglab[i][0] > 0;i++){
1644 minstep = log10(yloglab[i][0]);
1645 for(ii=1;yloglab[i][ii+1] > 0;ii++){
1646 if(yloglab[i][ii+2]==0){
1647 minstep = log10(yloglab[i][ii+1])-log10(yloglab[i][ii]);
1648 break;
1649 }
1650 }
1651 pixperstep = pixpex * minstep;
1652 if(pixperstep > 5){minoridx = i;}
1653 if(pixperstep > 2 * im->text_prop[TEXT_PROP_LEGEND].size){majoridx = i;}
1654 }
1656 X0=im->xorigin;
1657 X1=im->xorigin+im->xsize;
1658 /* paint minor grid */
1659 for (value = pow((double)10, log10(im->minval)
1660 - fmod(log10(im->minval),log10(yloglab[minoridx][0])));
1661 value <= im->maxval;
1662 value *= yloglab[minoridx][0]){
1663 if (value < im->minval) continue;
1664 i=0;
1665 while(yloglab[minoridx][++i] > 0){
1666 Y0 = ytr(im,value * yloglab[minoridx][i]);
1667 if (Y0 <= im->yorigin - im->ysize) break;
1668 gfx_new_dashed_line ( im->canvas,
1669 X0-1,Y0,
1670 X1+1,Y0,
1671 GRIDWIDTH, im->graph_col[GRC_GRID],
1672 im->grid_dash_on, im->grid_dash_off);
1673 }
1674 }
1676 /* paint major grid and labels*/
1677 for (value = pow((double)10, log10(im->minval)
1678 - fmod(log10(im->minval),log10(yloglab[majoridx][0])));
1679 value <= im->maxval;
1680 value *= yloglab[majoridx][0]){
1681 if (value < im->minval) continue;
1682 i=0;
1683 while(yloglab[majoridx][++i] > 0){
1684 Y0 = ytr(im,value * yloglab[majoridx][i]);
1685 if (Y0 <= im->yorigin - im->ysize) break;
1686 gfx_new_dashed_line ( im->canvas,
1687 X0-2,Y0,
1688 X1+2,Y0,
1689 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1690 im->grid_dash_on, im->grid_dash_off);
1692 sprintf(graph_label,"%3.0e",value * yloglab[majoridx][i]);
1693 gfx_new_text ( im->canvas,
1694 X0-im->text_prop[TEXT_PROP_AXIS].size/1.5, Y0,
1695 im->graph_col[GRC_FONT],
1696 im->text_prop[TEXT_PROP_AXIS].font,
1697 im->text_prop[TEXT_PROP_AXIS].size,
1698 im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1699 graph_label );
1700 }
1701 }
1702 return 1;
1703 }
1706 void
1707 vertical_grid(
1708 image_desc_t *im )
1709 {
1710 int xlab_sel; /* which sort of label and grid ? */
1711 time_t ti, tilab, timajor;
1712 long factor;
1713 char graph_label[100];
1714 double X0,Y0,Y1; /* points for filled graph and more*/
1715 struct tm tm;
1717 /* the type of time grid is determined by finding
1718 the number of seconds per pixel in the graph */
1721 if(im->xlab_user.minsec == -1){
1722 factor=(im->end - im->start)/im->xsize;
1723 xlab_sel=0;
1724 while ( xlab[xlab_sel+1].minsec != -1
1725 && xlab[xlab_sel+1].minsec <= factor){ xlab_sel++; }
1726 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
1727 im->xlab_user.gridst = xlab[xlab_sel].gridst;
1728 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
1729 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
1730 im->xlab_user.labtm = xlab[xlab_sel].labtm;
1731 im->xlab_user.labst = xlab[xlab_sel].labst;
1732 im->xlab_user.precis = xlab[xlab_sel].precis;
1733 im->xlab_user.stst = xlab[xlab_sel].stst;
1734 }
1736 /* y coords are the same for every line ... */
1737 Y0 = im->yorigin;
1738 Y1 = im->yorigin-im->ysize;
1741 /* paint the minor grid */
1742 if (!(im->extra_flags & NOMINOR))
1743 {
1744 for(ti = find_first_time(im->start,
1745 im->xlab_user.gridtm,
1746 im->xlab_user.gridst),
1747 timajor = find_first_time(im->start,
1748 im->xlab_user.mgridtm,
1749 im->xlab_user.mgridst);
1750 ti < im->end;
1751 ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
1752 ){
1753 /* are we inside the graph ? */
1754 if (ti < im->start || ti > im->end) continue;
1755 while (timajor < ti) {
1756 timajor = find_next_time(timajor,
1757 im->xlab_user.mgridtm, im->xlab_user.mgridst);
1758 }
1759 if (ti == timajor) continue; /* skip as falls on major grid line */
1760 X0 = xtr(im,ti);
1761 gfx_new_dashed_line(im->canvas,X0,Y0+1, X0,Y1-1,GRIDWIDTH,
1762 im->graph_col[GRC_GRID],
1763 im->grid_dash_on, im->grid_dash_off);
1765 }
1766 }
1768 /* paint the major grid */
1769 for(ti = find_first_time(im->start,
1770 im->xlab_user.mgridtm,
1771 im->xlab_user.mgridst);
1772 ti < im->end;
1773 ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
1774 ){
1775 /* are we inside the graph ? */
1776 if (ti < im->start || ti > im->end) continue;
1777 X0 = xtr(im,ti);
1778 gfx_new_dashed_line(im->canvas,X0,Y0+3, X0,Y1-2,MGRIDWIDTH,
1779 im->graph_col[GRC_MGRID],
1780 im->grid_dash_on, im->grid_dash_off);
1782 }
1783 /* paint the labels below the graph */
1784 for(ti = find_first_time(im->start,
1785 im->xlab_user.labtm,
1786 im->xlab_user.labst);
1787 ti <= im->end;
1788 ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
1789 ){
1790 tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
1791 /* are we inside the graph ? */
1792 if (ti < im->start || ti > im->end) continue;
1794 #if HAVE_STRFTIME
1795 localtime_r(&tilab, &tm);
1796 strftime(graph_label,99,im->xlab_user.stst, &tm);
1797 #else
1798 # error "your libc has no strftime I guess we'll abort the exercise here."
1799 #endif
1800 gfx_new_text ( im->canvas,
1801 xtr(im,tilab), Y0+im->text_prop[TEXT_PROP_AXIS].size/1.5,
1802 im->graph_col[GRC_FONT],
1803 im->text_prop[TEXT_PROP_AXIS].font,
1804 im->text_prop[TEXT_PROP_AXIS].size,
1805 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP,
1806 graph_label );
1808 }
1810 }
1813 void
1814 axis_paint(
1815 image_desc_t *im
1816 )
1817 {
1818 /* draw x and y axis */
1819 gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
1820 im->xorigin+im->xsize,im->yorigin-im->ysize,
1821 GRIDWIDTH, im->graph_col[GRC_GRID]);
1823 gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
1824 im->xorigin+im->xsize,im->yorigin-im->ysize,
1825 GRIDWIDTH, im->graph_col[GRC_GRID]);
1827 gfx_new_line ( im->canvas, im->xorigin-4,im->yorigin,
1828 im->xorigin+im->xsize+4,im->yorigin,
1829 MGRIDWIDTH, im->graph_col[GRC_GRID]);
1831 gfx_new_line ( im->canvas, im->xorigin,im->yorigin+4,
1832 im->xorigin,im->yorigin-im->ysize-4,
1833 MGRIDWIDTH, im->graph_col[GRC_GRID]);
1836 /* arrow for X axis direction */
1837 gfx_new_area ( im->canvas,
1838 im->xorigin+im->xsize+3, im->yorigin-3,
1839 im->xorigin+im->xsize+3, im->yorigin+4,
1840 im->xorigin+im->xsize+8, im->yorigin+0.5, /* LINEOFFSET */
1841 im->graph_col[GRC_ARROW]);
1845 }
1847 void
1848 grid_paint(image_desc_t *im)
1849 {
1850 long i;
1851 int res=0;
1852 double X0,Y0; /* points for filled graph and more*/
1853 gfx_node_t *node;
1855 /* draw 3d border */
1856 node = gfx_new_area (im->canvas, 0,im->yimg,
1857 2,im->yimg-2,
1858 2,2,im->graph_col[GRC_SHADEA]);
1859 gfx_add_point( node , im->ximg - 2, 2 );
1860 gfx_add_point( node , im->ximg, 0 );
1861 gfx_add_point( node , 0,0 );
1862 /* gfx_add_point( node , 0,im->yimg ); */
1864 node = gfx_new_area (im->canvas, 2,im->yimg-2,
1865 im->ximg-2,im->yimg-2,
1866 im->ximg - 2, 2,
1867 im->graph_col[GRC_SHADEB]);
1868 gfx_add_point( node , im->ximg,0);
1869 gfx_add_point( node , im->ximg,im->yimg);
1870 gfx_add_point( node , 0,im->yimg);
1871 /* gfx_add_point( node , 0,im->yimg ); */
1874 if (im->draw_x_grid == 1 )
1875 vertical_grid(im);
1877 if (im->draw_y_grid == 1){
1878 if(im->logarithmic){
1879 res = horizontal_log_grid(im);
1880 } else {
1881 res = draw_horizontal_grid(im);
1882 }
1884 /* dont draw horizontal grid if there is no min and max val */
1885 if (! res ) {
1886 char *nodata = "No Data found";
1887 gfx_new_text(im->canvas,im->ximg/2, (2*im->yorigin-im->ysize) / 2,
1888 im->graph_col[GRC_FONT],
1889 im->text_prop[TEXT_PROP_AXIS].font,
1890 im->text_prop[TEXT_PROP_AXIS].size,
1891 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
1892 nodata );
1893 }
1894 }
1896 /* yaxis description */
1897 /* if (im->canvas->imgformat != IF_PNG) {*/
1898 if (1) {
1899 gfx_new_text( im->canvas,
1900 7, (im->yorigin - im->ysize/2),
1901 im->graph_col[GRC_FONT],
1902 im->text_prop[TEXT_PROP_AXIS].font,
1903 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 270.0,
1904 GFX_H_LEFT, GFX_V_CENTER,
1905 im->ylegend);
1906 } else {
1907 /* horrible hack until we can actually print vertically */
1908 {
1909 int n;
1910 int l=strlen(im->ylegend);
1911 char s[2];
1912 for (n=0;n<strlen(im->ylegend);n++) {
1913 s[0]=im->ylegend[n];
1914 s[1]='\0';
1915 gfx_new_text(im->canvas,7,im->text_prop[TEXT_PROP_AXIS].size*(n+1),
1916 im->graph_col[GRC_FONT],
1917 im->text_prop[TEXT_PROP_AXIS].font,
1918 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 270.0,
1919 GFX_H_CENTER, GFX_V_CENTER,
1920 s);
1921 }
1922 }
1923 }
1925 /* graph title */
1926 gfx_new_text( im->canvas,
1927 im->ximg/2, im->text_prop[TEXT_PROP_TITLE].size,
1928 im->graph_col[GRC_FONT],
1929 im->text_prop[TEXT_PROP_TITLE].font,
1930 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
1931 GFX_H_CENTER, GFX_V_CENTER,
1932 im->title);
1934 /* graph labels */
1935 if( !(im->extra_flags & NOLEGEND) ) {
1936 for(i=0;i<im->gdes_c;i++){
1937 if(im->gdes[i].legend[0] =='\0')
1938 continue;
1940 /* im->gdes[i].leg_y is the bottom of the legend */
1941 X0 = im->gdes[i].leg_x;
1942 Y0 = im->gdes[i].leg_y;
1943 /* Box needed? */
1944 if ( im->gdes[i].gf != GF_GPRINT
1945 && im->gdes[i].gf != GF_COMMENT) {
1946 int boxH, boxV;
1948 boxH = gfx_get_text_width(im->canvas, 0,
1949 im->text_prop[TEXT_PROP_AXIS].font,
1950 im->text_prop[TEXT_PROP_AXIS].size,
1951 im->tabwidth,"M", 0) * 1.25;
1952 boxV = boxH;
1954 node = gfx_new_area(im->canvas,
1955 X0,Y0-boxV,
1956 X0,Y0,
1957 X0+boxH,Y0,
1958 im->gdes[i].col);
1959 gfx_add_point ( node, X0+boxH, Y0-boxV );
1960 node = gfx_new_line(im->canvas,
1961 X0,Y0-boxV, X0,Y0,
1962 1,0x000000FF);
1963 gfx_add_point(node,X0+boxH,Y0);
1964 gfx_add_point(node,X0+boxH,Y0-boxV);
1965 gfx_close_path(node);
1966 X0 += boxH / 1.25 * 2;
1967 }
1968 gfx_new_text ( im->canvas, X0, Y0,
1969 im->graph_col[GRC_FONT],
1970 im->text_prop[TEXT_PROP_AXIS].font,
1971 im->text_prop[TEXT_PROP_AXIS].size,
1972 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
1973 im->gdes[i].legend );
1974 }
1975 }
1976 }
1979 /*****************************************************
1980 * lazy check make sure we rely need to create this graph
1981 *****************************************************/
1983 int lazy_check(image_desc_t *im){
1984 FILE *fd = NULL;
1985 int size = 1;
1986 struct stat imgstat;
1988 if (im->lazy == 0) return 0; /* no lazy option */
1989 if (stat(im->graphfile,&imgstat) != 0)
1990 return 0; /* can't stat */
1991 /* one pixel in the existing graph is more then what we would
1992 change here ... */
1993 if (time(NULL) - imgstat.st_mtime >
1994 (im->end - im->start) / im->xsize)
1995 return 0;
1996 if ((fd = fopen(im->graphfile,"rb")) == NULL)
1997 return 0; /* the file does not exist */
1998 switch (im->canvas->imgformat) {
1999 case IF_PNG:
2000 size = PngSize(fd,&(im->ximg),&(im->yimg));
2001 break;
2002 default:
2003 size = 1;
2004 }
2005 fclose(fd);
2006 return size;
2007 }
2009 void
2010 pie_part(image_desc_t *im, gfx_color_t color,
2011 double PieCenterX, double PieCenterY, double Radius,
2012 double startangle, double endangle)
2013 {
2014 gfx_node_t *node;
2015 double angle;
2016 double step=M_PI/50; /* Number of iterations for the circle;
2017 ** 10 is definitely too low, more than
2018 ** 50 seems to be overkill
2019 */
2021 /* Strange but true: we have to work clockwise or else
2022 ** anti aliasing nor transparency don't work.
2023 **
2024 ** This test is here to make sure we do it right, also
2025 ** this makes the for...next loop more easy to implement.
2026 ** The return will occur if the user enters a negative number
2027 ** (which shouldn't be done according to the specs) or if the
2028 ** programmers do something wrong (which, as we all know, never
2029 ** happens anyway :)
2030 */
2031 if (endangle<startangle) return;
2033 /* Hidden feature: Radius decreases each full circle */
2034 angle=startangle;
2035 while (angle>=2*M_PI) {
2036 angle -= 2*M_PI;
2037 Radius *= 0.8;
2038 }
2040 node=gfx_new_area(im->canvas,
2041 PieCenterX+sin(startangle)*Radius,
2042 PieCenterY-cos(startangle)*Radius,
2043 PieCenterX,
2044 PieCenterY,
2045 PieCenterX+sin(endangle)*Radius,
2046 PieCenterY-cos(endangle)*Radius,
2047 color);
2048 for (angle=endangle;angle-startangle>=step;angle-=step) {
2049 gfx_add_point(node,
2050 PieCenterX+sin(angle)*Radius,
2051 PieCenterY-cos(angle)*Radius );
2052 }
2053 }
2055 int
2056 graph_size_location(image_desc_t *im, int elements, int piechart )
2057 {
2058 /* The actual size of the image to draw is determined from
2059 ** several sources. The size given on the command line is
2060 ** the graph area but we need more as we have to draw labels
2061 ** and other things outside the graph area
2062 */
2064 /* +-+-------------------------------------------+
2065 ** |l|.................title.....................|
2066 ** |e+--+-------------------------------+--------+
2067 ** |b| b| | |
2068 ** |a| a| | pie |
2069 ** |l| l| main graph area | chart |
2070 ** |.| .| | area |
2071 ** |t| y| | |
2072 ** |r+--+-------------------------------+--------+
2073 ** |e| | x-axis labels | |
2074 ** |v+--+-------------------------------+--------+
2075 ** | |..............legends......................|
2076 ** +-+-------------------------------------------+
2077 */
2078 int Xvertical=0, Yvertical=0,
2079 Xtitle =0, Ytitle =0,
2080 Xylabel =0, Yylabel =0,
2081 Xmain =0, Ymain =0,
2082 Xpie =0, Ypie =0,
2083 Xxlabel =0, Yxlabel =0,
2084 #if 0
2085 Xlegend =0, Ylegend =0,
2086 #endif
2087 Xspacing =10, Yspacing =10;
2089 if (im->ylegend[0] != '\0') {
2090 Xvertical = im->text_prop[TEXT_PROP_LEGEND].size *2;
2091 Yvertical = im->text_prop[TEXT_PROP_LEGEND].size * (strlen(im->ylegend)+1);
2092 }
2094 if (im->title[0] != '\0') {
2095 /* The title is placed "inbetween" two text lines so it
2096 ** automatically has some vertical spacing. The horizontal
2097 ** spacing is added here, on each side.
2098 */
2099 Xtitle = gfx_get_text_width(im->canvas, 0,
2100 im->text_prop[TEXT_PROP_TITLE].font,
2101 im->text_prop[TEXT_PROP_TITLE].size,
2102 im->tabwidth,
2103 im->title, 0) + 2*Xspacing;
2104 Ytitle = im->text_prop[TEXT_PROP_TITLE].size*2;
2105 }
2107 if (elements) {
2108 Xmain=im->xsize;
2109 Ymain=im->ysize;
2110 if (im->draw_x_grid) {
2111 Xxlabel=Xmain;
2112 Yxlabel=im->text_prop[TEXT_PROP_LEGEND].size *2;
2113 }
2114 if (im->draw_y_grid) {
2115 Xylabel=im->text_prop[TEXT_PROP_LEGEND].size *6;
2116 Yylabel=Ymain;
2117 }
2118 }
2120 if (piechart) {
2121 im->piesize=im->xsize<im->ysize?im->xsize:im->ysize;
2122 Xpie=im->piesize;
2123 Ypie=im->piesize;
2124 }
2126 /* Now calculate the total size. Insert some spacing where
2127 desired. im->xorigin and im->yorigin need to correspond
2128 with the lower left corner of the main graph area or, if
2129 this one is not set, the imaginary box surrounding the
2130 pie chart area. */
2132 /* The legend width cannot yet be determined, as a result we
2133 ** have problems adjusting the image to it. For now, we just
2134 ** forget about it at all; the legend will have to fit in the
2135 ** size already allocated.
2136 */
2137 im->ximg = Xylabel + Xmain + Xpie + Xspacing;
2138 if (Xmain) im->ximg += Xspacing;
2139 if (Xpie) im->ximg += Xspacing;
2140 im->xorigin = Xspacing + Xylabel;
2141 if (Xtitle > im->ximg) im->ximg = Xtitle;
2142 if (Xvertical) {
2143 im->ximg += Xvertical;
2144 im->xorigin += Xvertical;
2145 }
2146 xtr(im,0);
2148 /* The vertical size is interesting... we need to compare
2149 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend} with Yvertical
2150 ** however we need to know {Ytitle+Ymain+Yxlabel} in order to
2151 ** start even thinking about Ylegend.
2152 **
2153 ** Do it in three portions: First calculate the inner part,
2154 ** then do the legend, then adjust the total height of the img.
2155 */
2157 /* reserve space for main and/or pie */
2158 im->yimg = Ymain + Yxlabel;
2159 if (im->yimg < Ypie) im->yimg = Ypie;
2160 im->yorigin = im->yimg - Yxlabel;
2161 /* reserve space for the title *or* some padding above the graph */
2162 if (Ytitle) {
2163 im->yimg += Ytitle;
2164 im->yorigin += Ytitle;
2165 } else {
2166 im->yimg += Yspacing;
2167 im->yorigin += Yspacing;
2168 }
2169 /* reserve space for padding below the graph */
2170 im->yimg += Yspacing;
2171 ytr(im,DNAN);
2173 /* Determine where to place the legends onto the image.
2174 ** Adjust im->yimg to match the space requirements.
2175 */
2176 if(leg_place(im)==-1)
2177 return -1;
2179 /* last of three steps: check total height of image */
2180 if (im->yimg < Yvertical) im->yimg = Yvertical;
2182 #if 0
2183 if (Xlegend > im->ximg) {
2184 im->ximg = Xlegend;
2185 /* reposition Pie */
2186 }
2187 #endif
2189 /* The pie is placed in the upper right hand corner,
2190 ** just below the title (if any) and with sufficient
2191 ** padding.
2192 */
2193 if (elements) {
2194 im->pie_x = im->ximg - Xspacing - Xpie/2;
2195 im->pie_y = im->yorigin-Ymain+Ypie/2;
2196 } else {
2197 im->pie_x = im->ximg/2;
2198 im->pie_y = im->yorigin-Ypie/2;
2199 }
2201 return 0;
2202 }
2204 /* draw that picture thing ... */
2205 int
2206 graph_paint(image_desc_t *im, char ***calcpr)
2207 {
2208 int i,ii;
2209 int lazy = lazy_check(im);
2210 int piechart = 0;
2211 double PieStart=0.0;
2212 FILE *fo;
2213 gfx_node_t *node;
2215 double areazero = 0.0;
2216 enum gf_en stack_gf = GF_PRINT;
2217 graph_desc_t *lastgdes = NULL;
2219 /* if we are lazy and there is nothing to PRINT ... quit now */
2220 if (lazy && im->prt_c==0) return 0;
2222 /* pull the data from the rrd files ... */
2224 if(data_fetch(im)==-1)
2225 return -1;
2227 /* evaluate VDEF and CDEF operations ... */
2228 if(data_calc(im)==-1)
2229 return -1;
2231 /* check if we need to draw a piechart */
2232 for(i=0;i<im->gdes_c;i++){
2233 if (im->gdes[i].gf == GF_PART) {
2234 piechart=1;
2235 break;
2236 }
2237 }
2239 /* calculate and PRINT and GPRINT definitions. We have to do it at
2240 * this point because it will affect the length of the legends
2241 * if there are no graph elements we stop here ...
2242 * if we are lazy, try to quit ...
2243 */
2244 i=print_calc(im,calcpr);
2245 if(i<0) return -1;
2246 if(((i==0)&&(piechart==0)) || lazy) return 0;
2248 /* If there's only the pie chart to draw, signal this */
2249 if (i==0) piechart=2;
2251 /* get actual drawing data and find min and max values*/
2252 if(data_proc(im)==-1)
2253 return -1;
2255 if(!im->logarithmic){si_unit(im);} /* identify si magnitude Kilo, Mega Giga ? */
2257 if(!im->rigid && ! im->logarithmic)
2258 expand_range(im); /* make sure the upper and lower limit are
2259 sensible values */
2261 if (!calc_horizontal_grid(im))
2262 return -1;
2263 if (im->gridfit)
2264 apply_gridfit(im);
2266 /**************************************************************
2267 *** Calculating sizes and locations became a bit confusing ***
2268 *** so I moved this into a separate function. ***
2269 **************************************************************/
2270 if(graph_size_location(im,i,piechart)==-1)
2271 return -1;
2273 /* the actual graph is created by going through the individual
2274 graph elements and then drawing them */
2276 node=gfx_new_area ( im->canvas,
2277 0, 0,
2278 im->ximg, 0,
2279 im->ximg, im->yimg,
2280 im->graph_col[GRC_BACK]);
2282 gfx_add_point(node,0, im->yimg);
2284 if (piechart != 2) {
2285 node=gfx_new_area ( im->canvas,
2286 im->xorigin, im->yorigin,
2287 im->xorigin + im->xsize, im->yorigin,
2288 im->xorigin + im->xsize, im->yorigin-im->ysize,
2289 im->graph_col[GRC_CANVAS]);
2291 gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
2293 if (im->minval > 0.0)
2294 areazero = im->minval;
2295 if (im->maxval < 0.0)
2296 areazero = im->maxval;
2298 axis_paint(im);
2299 }
2301 if (piechart) {
2302 pie_part(im,im->graph_col[GRC_CANVAS],im->pie_x,im->pie_y,im->piesize*0.5,0,2*M_PI);
2303 }
2305 for(i=0;i<im->gdes_c;i++){
2306 switch(im->gdes[i].gf){
2307 case GF_CDEF:
2308 case GF_VDEF:
2309 case GF_DEF:
2310 case GF_PRINT:
2311 case GF_GPRINT:
2312 case GF_COMMENT:
2313 case GF_HRULE:
2314 case GF_VRULE:
2315 case GF_XPORT:
2316 break;
2317 case GF_TICK:
2318 for (ii = 0; ii < im->xsize; ii++)
2319 {
2320 if (!isnan(im->gdes[i].p_data[ii]) &&
2321 im->gdes[i].p_data[ii] > 0.0)
2322 {
2323 /* generate a tick */
2324 gfx_new_line(im->canvas, im -> xorigin + ii,
2325 im -> yorigin - (im -> gdes[i].yrule * im -> ysize),
2326 im -> xorigin + ii,
2327 im -> yorigin,
2328 1.0,
2329 im -> gdes[i].col );
2330 }
2331 }
2332 break;
2333 case GF_LINE:
2334 case GF_AREA:
2335 stack_gf = im->gdes[i].gf;
2336 case GF_STACK:
2337 /* fix data points at oo and -oo */
2338 for(ii=0;ii<im->xsize;ii++){
2339 if (isinf(im->gdes[i].p_data[ii])){
2340 if (im->gdes[i].p_data[ii] > 0) {
2341 im->gdes[i].p_data[ii] = im->maxval ;
2342 } else {
2343 im->gdes[i].p_data[ii] = im->minval ;
2344 }
2346 }
2347 } /* for */
2349 if (im->gdes[i].col != 0x0){
2350 /* GF_LINE and friend */
2351 if(stack_gf == GF_LINE ){
2352 node = NULL;
2353 for(ii=1;ii<im->xsize;ii++){
2354 if ( ! isnan(im->gdes[i].p_data[ii-1])
2355 && ! isnan(im->gdes[i].p_data[ii])){
2356 if (node == NULL){
2357 node = gfx_new_line(im->canvas,
2358 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2359 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2360 im->gdes[i].linewidth,
2361 im->gdes[i].col);
2362 } else {
2363 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2364 }
2365 } else {
2366 node = NULL;
2367 }
2368 }
2369 } else {
2370 int area_start=-1;
2371 node = NULL;
2372 for(ii=1;ii<im->xsize;ii++){
2373 /* open an area */
2374 if ( ! isnan(im->gdes[i].p_data[ii-1])
2375 && ! isnan(im->gdes[i].p_data[ii])){
2376 if (node == NULL){
2377 float ybase = 0.0;
2378 /*
2379 if (im->gdes[i].gf == GF_STACK) {
2380 */
2381 if ( (im->gdes[i].gf == GF_STACK)
2382 || (im->gdes[i].stack) ) {
2384 ybase = ytr(im,lastgdes->p_data[ii-1]);
2385 } else {
2386 ybase = ytr(im,areazero);
2387 }
2388 area_start = ii-1;
2389 node = gfx_new_area(im->canvas,
2390 ii-1+im->xorigin,ybase,
2391 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2392 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2393 im->gdes[i].col
2394 );
2395 } else {
2396 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2397 }
2398 }
2400 if ( node != NULL && (ii+1==im->xsize || isnan(im->gdes[i].p_data[ii]) )){
2401 /* GF_AREA STACK type*/
2402 /*
2403 if (im->gdes[i].gf == GF_STACK ) {
2404 */
2405 if ( (im->gdes[i].gf == GF_STACK)
2406 || (im->gdes[i].stack) ) {
2407 int iii;
2408 for (iii=ii-1;iii>area_start;iii--){
2409 gfx_add_point(node,iii+im->xorigin,ytr(im,lastgdes->p_data[iii]));
2410 }
2411 } else {
2412 gfx_add_point(node,ii+im->xorigin,ytr(im,areazero));
2413 };
2414 node=NULL;
2415 };
2416 }
2417 } /* else GF_LINE */
2418 } /* if color != 0x0 */
2419 /* make sure we do not run into trouble when stacking on NaN */
2420 for(ii=0;ii<im->xsize;ii++){
2421 if (isnan(im->gdes[i].p_data[ii])) {
2422 if (lastgdes && (im->gdes[i].gf == GF_STACK)) {
2423 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
2424 } else {
2425 im->gdes[i].p_data[ii] = ytr(im,areazero);
2426 }
2427 }
2428 }
2429 lastgdes = &(im->gdes[i]);
2430 break;
2431 case GF_PART:
2432 if(isnan(im->gdes[i].yrule)) /* fetch variable */
2433 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2435 if (finite(im->gdes[i].yrule)) { /* even the fetched var can be NaN */
2436 pie_part(im,im->gdes[i].col,
2437 im->pie_x,im->pie_y,im->piesize*0.4,
2438 M_PI*2.0*PieStart/100.0,
2439 M_PI*2.0*(PieStart+im->gdes[i].yrule)/100.0);
2440 PieStart += im->gdes[i].yrule;
2441 }
2442 break;
2443 } /* switch */
2444 }
2445 if (piechart==2) {
2446 im->draw_x_grid=0;
2447 im->draw_y_grid=0;
2448 }
2449 /* grid_paint also does the text */
2450 grid_paint(im);
2452 /* the RULES are the last thing to paint ... */
2453 for(i=0;i<im->gdes_c;i++){
2455 switch(im->gdes[i].gf){
2456 case GF_HRULE:
2457 if(isnan(im->gdes[i].yrule)) { /* fetch variable */
2458 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2459 };
2460 if(im->gdes[i].yrule >= im->minval
2461 && im->gdes[i].yrule <= im->maxval)
2462 gfx_new_line(im->canvas,
2463 im->xorigin,ytr(im,im->gdes[i].yrule),
2464 im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2465 1.0,im->gdes[i].col);
2466 break;
2467 case GF_VRULE:
2468 if(im->gdes[i].xrule == 0) { /* fetch variable */
2469 im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
2470 };
2471 if(im->gdes[i].xrule >= im->start
2472 && im->gdes[i].xrule <= im->end)
2473 gfx_new_line(im->canvas,
2474 xtr(im,im->gdes[i].xrule),im->yorigin,
2475 xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2476 1.0,im->gdes[i].col);
2477 break;
2478 default:
2479 break;
2480 }
2481 }
2484 if (strcmp(im->graphfile,"-")==0) {
2485 fo = im->graphhandle ? im->graphhandle : stdout;
2486 #ifdef WIN32
2487 /* Change translation mode for stdout to BINARY */
2488 _setmode( _fileno( fo ), O_BINARY );
2489 #endif
2490 } else {
2491 if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2492 rrd_set_error("Opening '%s' for write: %s",im->graphfile,
2493 rrd_strerror(errno));
2494 return (-1);
2495 }
2496 }
2497 gfx_render (im->canvas,im->ximg,im->yimg,0x0,fo);
2498 if (strcmp(im->graphfile,"-") != 0)
2499 fclose(fo);
2500 return 0;
2501 }
2504 /*****************************************************
2505 * graph stuff
2506 *****************************************************/
2508 int
2509 gdes_alloc(image_desc_t *im){
2511 unsigned long def_step = (im->end-im->start)/im->xsize;
2513 if (im->step > def_step) /* step can be increassed ... no decreassed */
2514 def_step = im->step;
2516 im->gdes_c++;
2518 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2519 * sizeof(graph_desc_t)))==NULL){
2520 rrd_set_error("realloc graph_descs");
2521 return -1;
2522 }
2525 im->gdes[im->gdes_c-1].step=def_step;
2526 im->gdes[im->gdes_c-1].stack=0;
2527 im->gdes[im->gdes_c-1].debug=0;
2528 im->gdes[im->gdes_c-1].start=im->start;
2529 im->gdes[im->gdes_c-1].end=im->end;
2530 im->gdes[im->gdes_c-1].vname[0]='\0';
2531 im->gdes[im->gdes_c-1].data=NULL;
2532 im->gdes[im->gdes_c-1].ds_namv=NULL;
2533 im->gdes[im->gdes_c-1].data_first=0;
2534 im->gdes[im->gdes_c-1].p_data=NULL;
2535 im->gdes[im->gdes_c-1].rpnp=NULL;
2536 im->gdes[im->gdes_c-1].col = 0x0;
2537 im->gdes[im->gdes_c-1].legend[0]='\0';
2538 im->gdes[im->gdes_c-1].rrd[0]='\0';
2539 im->gdes[im->gdes_c-1].ds=-1;
2540 im->gdes[im->gdes_c-1].p_data=NULL;
2541 im->gdes[im->gdes_c-1].yrule=DNAN;
2542 im->gdes[im->gdes_c-1].xrule=0;
2543 return 0;
2544 }
2546 /* copies input untill the first unescaped colon is found
2547 or until input ends. backslashes have to be escaped as well */
2548 int
2549 scan_for_col(char *input, int len, char *output)
2550 {
2551 int inp,outp=0;
2552 for (inp=0;
2553 inp < len &&
2554 input[inp] != ':' &&
2555 input[inp] != '\0';
2556 inp++){
2557 if (input[inp] == '\\' &&
2558 input[inp+1] != '\0' &&
2559 (input[inp+1] == '\\' ||
2560 input[inp+1] == ':')){
2561 output[outp++] = input[++inp];
2562 }
2563 else {
2564 output[outp++] = input[inp];
2565 }
2566 }
2567 output[outp] = '\0';
2568 return inp;
2569 }
2570 /* Some surgery done on this function, it became ridiculously big.
2571 ** Things moved:
2572 ** - initializing now in rrd_graph_init()
2573 ** - options parsing now in rrd_graph_options()
2574 ** - script parsing now in rrd_graph_script()
2575 */
2576 int
2577 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize, FILE *stream)
2578 {
2579 image_desc_t im;
2581 rrd_graph_init(&im);
2582 im.graphhandle = stream;
2584 rrd_graph_options(argc,argv,&im);
2585 if (rrd_test_error()) {
2586 im_free(&im);
2587 return -1;
2588 }
2590 if (strlen(argv[optind])>=MAXPATH) {
2591 rrd_set_error("filename (including path) too long");
2592 im_free(&im);
2593 return -1;
2594 }
2595 strncpy(im.graphfile,argv[optind],MAXPATH-1);
2596 im.graphfile[MAXPATH-1]='\0';
2598 rrd_graph_script(argc,argv,&im);
2599 if (rrd_test_error()) {
2600 im_free(&im);
2601 return -1;
2602 }
2604 /* Everything is now read and the actual work can start */
2606 (*prdata)=NULL;
2607 if (graph_paint(&im,prdata)==-1){
2608 im_free(&im);
2609 return -1;
2610 }
2612 /* The image is generated and needs to be output.
2613 ** Also, if needed, print a line with information about the image.
2614 */
2616 *xsize=im.ximg;
2617 *ysize=im.yimg;
2618 if (im.imginfo) {
2619 char *filename;
2620 if (!(*prdata)) {
2621 /* maybe prdata is not allocated yet ... lets do it now */
2622 if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
2623 rrd_set_error("malloc imginfo");
2624 return -1;
2625 };
2626 }
2627 if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
2628 ==NULL){
2629 rrd_set_error("malloc imginfo");
2630 return -1;
2631 }
2632 filename=im.graphfile+strlen(im.graphfile);
2633 while(filename > im.graphfile) {
2634 if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
2635 filename--;
2636 }
2638 sprintf((*prdata)[0],im.imginfo,filename,(long)(im.canvas->zoom*im.ximg),(long)(im.canvas->zoom*im.yimg));
2639 }
2640 im_free(&im);
2641 return 0;
2642 }
2644 void
2645 rrd_graph_init(image_desc_t *im)
2646 {
2647 unsigned int i;
2649 #ifdef HAVE_TZSET
2650 tzset();
2651 #endif
2652 #ifdef HAVE_SETLOCALE
2653 setlocale(LC_TIME,"");
2654 #endif
2656 im->xlab_user.minsec = -1;
2657 im->ximg=0;
2658 im->yimg=0;
2659 im->xsize = 400;
2660 im->ysize = 100;
2661 im->step = 0;
2662 im->ylegend[0] = '\0';
2663 im->title[0] = '\0';
2664 im->minval = DNAN;
2665 im->maxval = DNAN;
2666 im->unitsexponent= 9999;
2667 im->extra_flags= 0;
2668 im->rigid = 0;
2669 im->gridfit = 1;
2670 im->imginfo = NULL;
2671 im->lazy = 0;
2672 im->logarithmic = 0;
2673 im->ygridstep = DNAN;
2674 im->draw_x_grid = 1;
2675 im->draw_y_grid = 1;
2676 im->base = 1000;
2677 im->prt_c = 0;
2678 im->gdes_c = 0;
2679 im->gdes = NULL;
2680 im->canvas = gfx_new_canvas();
2681 im->grid_dash_on = 1;
2682 im->grid_dash_off = 1;
2684 for(i=0;i<DIM(graph_col);i++)
2685 im->graph_col[i]=graph_col[i];
2686 #ifdef WIN32
2687 {
2688 char *windir;
2689 windir = getenv("windir");
2690 /* %windir% is something like D:\windows or C:\winnt */
2691 if (windir != NULL) {
2692 strcpy(rrd_win_default_font,windir);
2693 strcat(rrd_win_default_font,"\\fonts\\cour.ttf");
2694 for(i=0;i<DIM(text_prop);i++)
2695 text_prop[i].font = rrd_win_default_font;
2696 }
2697 }
2698 #endif
2699 for(i=0;i<DIM(text_prop);i++){
2700 im->text_prop[i].size = text_prop[i].size;
2701 im->text_prop[i].font = text_prop[i].font;
2702 }
2703 }
2705 void
2706 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
2707 {
2708 int stroff;
2709 char *parsetime_error = NULL;
2710 char scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
2711 time_t start_tmp=0,end_tmp=0;
2712 long long_tmp;
2713 struct rrd_time_value start_tv, end_tv;
2714 gfx_color_t color;
2716 parsetime("end-24h", &start_tv);
2717 parsetime("now", &end_tv);
2719 while (1){
2720 static struct option long_options[] =
2721 {
2722 {"start", required_argument, 0, 's'},
2723 {"end", required_argument, 0, 'e'},
2724 {"x-grid", required_argument, 0, 'x'},
2725 {"y-grid", required_argument, 0, 'y'},
2726 {"vertical-label",required_argument,0,'v'},
2727 {"width", required_argument, 0, 'w'},
2728 {"height", required_argument, 0, 'h'},
2729 {"interlaced", no_argument, 0, 'i'},
2730 {"upper-limit",required_argument, 0, 'u'},
2731 {"lower-limit",required_argument, 0, 'l'},
2732 {"rigid", no_argument, 0, 'r'},
2733 {"base", required_argument, 0, 'b'},
2734 {"logarithmic",no_argument, 0, 'o'},
2735 {"color", required_argument, 0, 'c'},
2736 {"font", required_argument, 0, 'n'},
2737 {"title", required_argument, 0, 't'},
2738 {"imginfo", required_argument, 0, 'f'},
2739 {"imgformat", required_argument, 0, 'a'},
2740 {"lazy", no_argument, 0, 'z'},
2741 {"zoom", required_argument, 0, 'm'},
2742 {"no-legend", no_argument, 0, 'g'},
2743 {"alt-y-grid", no_argument, 0, 'Y'},
2744 {"no-minor", no_argument, 0, 'I'},
2745 {"alt-autoscale", no_argument, 0, 'A'},
2746 {"alt-autoscale-max", no_argument, 0, 'M'},
2747 {"units-exponent",required_argument, 0, 'X'},
2748 {"step", required_argument, 0, 'S'},
2749 {"no-gridfit", no_argument, 0, 'N'},
2750 {0,0,0,0}};
2751 int option_index = 0;
2752 int opt;
2755 opt = getopt_long(argc, argv,
2756 "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:I:zgYAMX:S:N",
2757 long_options, &option_index);
2759 if (opt == EOF)
2760 break;
2762 switch(opt) {
2763 case 'I':
2764 im->extra_flags |= NOMINOR;
2765 break;
2766 case 'Y':
2767 im->extra_flags |= ALTYGRID;
2768 break;
2769 case 'A':
2770 im->extra_flags |= ALTAUTOSCALE;
2771 break;
2772 case 'M':
2773 im->extra_flags |= ALTAUTOSCALE_MAX;
2774 break;
2775 case 'g':
2776 im->extra_flags |= NOLEGEND;
2777 break;
2778 case 'X':
2779 im->unitsexponent = atoi(optarg);
2780 break;
2781 case 'S':
2782 im->step = atoi(optarg);
2783 break;
2784 case 262:
2785 im->gridfit = 0;
2786 break;
2787 case 's':
2788 if ((parsetime_error = parsetime(optarg, &start_tv))) {
2789 rrd_set_error( "start time: %s", parsetime_error );
2790 return;
2791 }
2792 break;
2793 case 'e':
2794 if ((parsetime_error = parsetime(optarg, &end_tv))) {
2795 rrd_set_error( "end time: %s", parsetime_error );
2796 return;
2797 }
2798 break;
2799 case 'x':
2800 if(strcmp(optarg,"none") == 0){
2801 im->draw_x_grid=0;
2802 break;
2803 };
2805 if(sscanf(optarg,
2806 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
2807 scan_gtm,
2808 &im->xlab_user.gridst,
2809 scan_mtm,
2810 &im->xlab_user.mgridst,
2811 scan_ltm,
2812 &im->xlab_user.labst,
2813 &im->xlab_user.precis,
2814 &stroff) == 7 && stroff != 0){
2815 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
2816 if((im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
2817 rrd_set_error("unknown keyword %s",scan_gtm);
2818 return;
2819 } else if ((im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
2820 rrd_set_error("unknown keyword %s",scan_mtm);
2821 return;
2822 } else if ((im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
2823 rrd_set_error("unknown keyword %s",scan_ltm);
2824 return;
2825 }
2826 im->xlab_user.minsec = 1;
2827 im->xlab_user.stst = im->xlab_form;
2828 } else {
2829 rrd_set_error("invalid x-grid format");
2830 return;
2831 }
2832 break;
2833 case 'y':
2835 if(strcmp(optarg,"none") == 0){
2836 im->draw_y_grid=0;
2837 break;
2838 };
2840 if(sscanf(optarg,
2841 "%lf:%d",
2842 &im->ygridstep,
2843 &im->ylabfact) == 2) {
2844 if(im->ygridstep<=0){
2845 rrd_set_error("grid step must be > 0");
2846 return;
2847 } else if (im->ylabfact < 1){
2848 rrd_set_error("label factor must be > 0");
2849 return;
2850 }
2851 } else {
2852 rrd_set_error("invalid y-grid format");
2853 return;
2854 }
2855 break;
2856 case 'v':
2857 strncpy(im->ylegend,optarg,150);
2858 im->ylegend[150]='\0';
2859 break;
2860 case 'u':
2861 im->maxval = atof(optarg);
2862 break;
2863 case 'l':
2864 im->minval = atof(optarg);
2865 break;
2866 case 'b':
2867 im->base = atol(optarg);
2868 if(im->base != 1024 && im->base != 1000 ){
2869 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
2870 return;
2871 }
2872 break;
2873 case 'w':
2874 long_tmp = atol(optarg);
2875 if (long_tmp < 10) {
2876 rrd_set_error("width below 10 pixels");
2877 return;
2878 }
2879 im->xsize = long_tmp;
2880 break;
2881 case 'h':
2882 long_tmp = atol(optarg);
2883 if (long_tmp < 10) {
2884 rrd_set_error("height below 10 pixels");
2885 return;
2886 }
2887 im->ysize = long_tmp;
2888 break;
2889 case 'i':
2890 im->canvas->interlaced = 1;
2891 break;
2892 case 'r':
2893 im->rigid = 1;
2894 break;
2895 case 'f':
2896 im->imginfo = optarg;
2897 break;
2898 case 'a':
2899 if((im->canvas->imgformat = if_conv(optarg)) == -1) {
2900 rrd_set_error("unsupported graphics format '%s'",optarg);
2901 return;
2902 }
2903 break;
2904 case 'z':
2905 im->lazy = 1;
2906 break;
2907 case 'o':
2908 im->logarithmic = 1;
2909 if (isnan(im->minval))
2910 im->minval=1;
2911 break;
2912 case 'c':
2913 if(sscanf(optarg,
2914 "%10[A-Z]#%8lx",
2915 col_nam,&color) == 2){
2916 int ci;
2917 if((ci=grc_conv(col_nam)) != -1){
2918 im->graph_col[ci]=color;
2919 } else {
2920 rrd_set_error("invalid color name '%s'",col_nam);
2921 }
2922 } else {
2923 rrd_set_error("invalid color def format");
2924 return;
2925 }
2926 break;
2927 case 'n':{
2928 /* originally this used char *prop = "" and
2929 ** char *font = "dummy" however this results
2930 ** in a SEG fault, at least on RH7.1
2931 **
2932 ** The current implementation isn't proper
2933 ** either, font is never freed and prop uses
2934 ** a fixed width string
2935 */
2936 char prop[100];
2937 double size = 1;
2938 char *font;
2940 font=malloc(255);
2941 if(sscanf(optarg,
2942 "%10[A-Z]:%lf:%s",
2943 prop,&size,font) == 3){
2944 int sindex;
2945 if((sindex=text_prop_conv(prop)) != -1){
2946 im->text_prop[sindex].size=size;
2947 im->text_prop[sindex].font=font;
2948 if (sindex==0) { /* the default */
2949 im->text_prop[TEXT_PROP_TITLE].size=size;
2950 im->text_prop[TEXT_PROP_TITLE].font=font;
2951 im->text_prop[TEXT_PROP_AXIS].size=size;
2952 im->text_prop[TEXT_PROP_AXIS].font=font;
2953 im->text_prop[TEXT_PROP_UNIT].size=size;
2954 im->text_prop[TEXT_PROP_UNIT].font=font;
2955 im->text_prop[TEXT_PROP_LEGEND].size=size;
2956 im->text_prop[TEXT_PROP_LEGEND].font=font;
2957 }
2958 } else {
2959 rrd_set_error("invalid fonttag '%s'",prop);
2960 return;
2961 }
2962 } else {
2963 rrd_set_error("invalid text property format");
2964 return;
2965 }
2966 break;
2967 }
2968 case 'm':
2969 im->canvas->zoom = atof(optarg);
2970 if (im->canvas->zoom <= 0.0) {
2971 rrd_set_error("zoom factor must be > 0");
2972 return;
2973 }
2974 break;
2975 case 't':
2976 strncpy(im->title,optarg,150);
2977 im->title[150]='\0';
2978 break;
2980 case '?':
2981 if (optopt != 0)
2982 rrd_set_error("unknown option '%c'", optopt);
2983 else
2984 rrd_set_error("unknown option '%s'",argv[optind-1]);
2985 return;
2986 }
2987 }
2989 if (optind >= argc) {
2990 rrd_set_error("missing filename");
2991 return;
2992 }
2994 if (im->logarithmic == 1 && (im->minval <= 0 || isnan(im->minval))){
2995 rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");
2996 return;
2997 }
2999 if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
3000 /* error string is set in parsetime.c */
3001 return;
3002 }
3004 if (start_tmp < 3600*24*365*10){
3005 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
3006 return;
3007 }
3009 if (end_tmp < start_tmp) {
3010 rrd_set_error("start (%ld) should be less than end (%ld)",
3011 start_tmp, end_tmp);
3012 return;
3013 }
3015 im->start = start_tmp;
3016 im->end = end_tmp;
3017 }
3019 int
3020 rrd_graph_check_vname(image_desc_t *im, char *varname, char *err)
3021 {
3022 if ((im->gdes[im->gdes_c-1].vidx=find_var(im,varname))==-1) {
3023 rrd_set_error("Unknown variable '%s' in %s",varname,err);
3024 return -1;
3025 }
3026 return 0;
3027 }
3028 int
3029 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
3030 {
3031 char *color;
3032 graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
3034 color=strstr(var,"#");
3035 if (color==NULL) {
3036 if (optional==0) {
3037 rrd_set_error("Found no color in %s",err);
3038 return 0;
3039 }
3040 return 0;
3041 } else {
3042 int n=0;
3043 char *rest;
3044 gfx_color_t col;
3046 rest=strstr(color,":");
3047 if (rest!=NULL)
3048 n=rest-color;
3049 else
3050 n=strlen(color);
3052 switch (n) {
3053 case 7:
3054 sscanf(color,"#%6lx%n",&col,&n);
3055 col = (col << 8) + 0xff /* shift left by 8 */;
3056 if (n!=7) rrd_set_error("Color problem in %s",err);
3057 break;
3058 case 9:
3059 sscanf(color,"#%8lx%n",&col,&n);
3060 if (n==9) break;
3061 default:
3062 rrd_set_error("Color problem in %s",err);
3063 }
3064 if (rrd_test_error()) return 0;
3065 gdp->col = col;
3066 return n;
3067 }
3068 }
3069 int
3070 rrd_graph_legend(graph_desc_t *gdp, char *line)
3071 {
3072 int i;
3074 i=scan_for_col(line,FMT_LEG_LEN,gdp->legend);
3076 return (strlen(&line[i])==0);
3077 }
3080 int bad_format(char *fmt) {
3081 char *ptr;
3082 int n=0;
3083 ptr = fmt;
3084 while (*ptr != '\0')
3085 if (*ptr++ == '%') {
3087 /* line cannot end with percent char */
3088 if (*ptr == '\0') return 1;
3090 /* '%s', '%S' and '%%' are allowed */
3091 if (*ptr == 's' || *ptr == 'S' || *ptr == '%') ptr++;
3093 /* or else '% 6.2lf' and such are allowed */
3094 else {
3096 /* optional padding character */
3097 if (*ptr == ' ' || *ptr == '+' || *ptr == '-') ptr++;
3099 /* This should take care of 'm.n' with all three optional */
3100 while (*ptr >= '0' && *ptr <= '9') ptr++;
3101 if (*ptr == '.') ptr++;
3102 while (*ptr >= '0' && *ptr <= '9') ptr++;
3104 /* Either 'le', 'lf' or 'lg' must follow here */
3105 if (*ptr++ != 'l') return 1;
3106 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g') ptr++;
3107 else return 1;
3108 n++;
3109 }
3110 }
3112 return (n!=1);
3113 }
3116 int
3117 vdef_parse(gdes,str)
3118 struct graph_desc_t *gdes;
3119 char *str;
3120 {
3121 /* A VDEF currently is either "func" or "param,func"
3122 * so the parsing is rather simple. Change if needed.
3123 */
3124 double param;
3125 char func[30];
3126 int n;
3128 n=0;
3129 sscanf(str,"%le,%29[A-Z]%n",¶m,func,&n);
3130 if (n==strlen(str)) { /* matched */
3131 ;
3132 } else {
3133 n=0;
3134 sscanf(str,"%29[A-Z]%n",func,&n);
3135 if (n==strlen(str)) { /* matched */
3136 param=DNAN;
3137 } else {
3138 rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3139 ,str
3140 ,gdes->vname
3141 );
3142 return -1;
3143 }
3144 }
3145 if (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3146 else if (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3147 else if (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3148 else if (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3149 else if (!strcmp("TOTAL", func)) gdes->vf.op = VDEF_TOTAL;
3150 else if (!strcmp("FIRST", func)) gdes->vf.op = VDEF_FIRST;
3151 else if (!strcmp("LAST", func)) gdes->vf.op = VDEF_LAST;
3152 else {
3153 rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3154 ,func
3155 ,gdes->vname
3156 );
3157 return -1;
3158 };
3160 switch (gdes->vf.op) {
3161 case VDEF_PERCENT:
3162 if (isnan(param)) { /* no parameter given */
3163 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3164 ,func
3165 ,gdes->vname
3166 );
3167 return -1;
3168 };
3169 if (param>=0.0 && param<=100.0) {
3170 gdes->vf.param = param;
3171 gdes->vf.val = DNAN; /* undefined */
3172 gdes->vf.when = 0; /* undefined */
3173 } else {
3174 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3175 ,param
3176 ,gdes->vname
3177 );
3178 return -1;
3179 };
3180 break;
3181 case VDEF_MAXIMUM:
3182 case VDEF_AVERAGE:
3183 case VDEF_MINIMUM:
3184 case VDEF_TOTAL:
3185 case VDEF_FIRST:
3186 case VDEF_LAST:
3187 if (isnan(param)) {
3188 gdes->vf.param = DNAN;
3189 gdes->vf.val = DNAN;
3190 gdes->vf.when = 0;
3191 } else {
3192 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3193 ,func
3194 ,gdes->vname
3195 );
3196 return -1;
3197 };
3198 break;
3199 };
3200 return 0;
3201 }
3204 int
3205 vdef_calc(im,gdi)
3206 image_desc_t *im;
3207 int gdi;
3208 {
3209 graph_desc_t *src,*dst;
3210 rrd_value_t *data;
3211 long step,steps;
3213 dst = &im->gdes[gdi];
3214 src = &im->gdes[dst->vidx];
3215 data = src->data + src->ds;
3216 steps = (src->end - src->start) / src->step;
3218 #if 0
3219 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3220 ,src->start
3221 ,src->end
3222 ,steps
3223 );
3224 #endif
3226 switch (dst->vf.op) {
3227 case VDEF_PERCENT: {
3228 rrd_value_t * array;
3229 int field;
3232 if ((array = malloc(steps*sizeof(double)))==NULL) {
3233 rrd_set_error("malloc VDEV_PERCENT");
3234 return -1;
3235 }
3236 for (step=0;step < steps; step++) {
3237 array[step]=data[step*src->ds_cnt];
3238 }
3239 qsort(array,step,sizeof(double),vdef_percent_compar);
3241 field = (steps-1)*dst->vf.param/100;
3242 dst->vf.val = array[field];
3243 dst->vf.when = 0; /* no time component */
3244 free(array);
3245 #if 0
3246 for(step=0;step<steps;step++)
3247 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3248 #endif
3249 }
3250 break;
3251 case VDEF_MAXIMUM:
3252 step=0;
3253 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3254 if (step == steps) {
3255 dst->vf.val = DNAN;
3256 dst->vf.when = 0;
3257 } else {
3258 dst->vf.val = data[step*src->ds_cnt];
3259 dst->vf.when = src->start + (step+1)*src->step;
3260 }
3261 while (step != steps) {
3262 if (finite(data[step*src->ds_cnt])) {
3263 if (data[step*src->ds_cnt] > dst->vf.val) {
3264 dst->vf.val = data[step*src->ds_cnt];
3265 dst->vf.when = src->start + (step+1)*src->step;
3266 }
3267 }
3268 step++;
3269 }
3270 break;
3271 case VDEF_TOTAL:
3272 case VDEF_AVERAGE: {
3273 int cnt=0;
3274 double sum=0.0;
3275 for (step=0;step<steps;step++) {
3276 if (finite(data[step*src->ds_cnt])) {
3277 sum += data[step*src->ds_cnt];
3278 cnt ++;
3279 };
3280 }
3281 if (cnt) {
3282 if (dst->vf.op == VDEF_TOTAL) {
3283 dst->vf.val = sum*src->step;
3284 dst->vf.when = cnt*src->step; /* not really "when" */
3285 } else {
3286 dst->vf.val = sum/cnt;
3287 dst->vf.when = 0; /* no time component */
3288 };
3289 } else {
3290 dst->vf.val = DNAN;
3291 dst->vf.when = 0;
3292 }
3293 }
3294 break;
3295 case VDEF_MINIMUM:
3296 step=0;
3297 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3298 if (step == steps) {
3299 dst->vf.val = DNAN;
3300 dst->vf.when = 0;
3301 } else {
3302 dst->vf.val = data[step*src->ds_cnt];
3303 dst->vf.when = src->start + (step+1)*src->step;
3304 }
3305 while (step != steps) {
3306 if (finite(data[step*src->ds_cnt])) {
3307 if (data[step*src->ds_cnt] < dst->vf.val) {
3308 dst->vf.val = data[step*src->ds_cnt];
3309 dst->vf.when = src->start + (step+1)*src->step;
3310 }
3311 }
3312 step++;
3313 }
3314 break;
3315 case VDEF_FIRST:
3316 /* The time value returned here is one step before the
3317 * actual time value. This is the start of the first
3318 * non-NaN interval.
3319 */
3320 step=0;
3321 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3322 if (step == steps) { /* all entries were NaN */
3323 dst->vf.val = DNAN;
3324 dst->vf.when = 0;
3325 } else {
3326 dst->vf.val = data[step*src->ds_cnt];
3327 dst->vf.when = src->start + step*src->step;
3328 }
3329 break;
3330 case VDEF_LAST:
3331 /* The time value returned here is the
3332 * actual time value. This is the end of the last
3333 * non-NaN interval.
3334 */
3335 step=steps-1;
3336 while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3337 if (step < 0) { /* all entries were NaN */
3338 dst->vf.val = DNAN;
3339 dst->vf.when = 0;
3340 } else {
3341 dst->vf.val = data[step*src->ds_cnt];
3342 dst->vf.when = src->start + (step+1)*src->step;
3343 }
3344 break;
3345 }
3346 return 0;
3347 }
3349 /* NaN < -INF < finite_values < INF */
3350 int
3351 vdef_percent_compar(a,b)
3352 const void *a,*b;
3353 {
3354 /* Equality is not returned; this doesn't hurt except
3355 * (maybe) for a little performance.
3356 */
3358 /* First catch NaN values. They are smallest */
3359 if (isnan( *(double *)a )) return -1;
3360 if (isnan( *(double *)b )) return 1;
3362 /* NaN doesn't reach this part so INF and -INF are extremes.
3363 * The sign from isinf() is compatible with the sign we return
3364 */
3365 if (isinf( *(double *)a )) return isinf( *(double *)a );
3366 if (isinf( *(double *)b )) return isinf( *(double *)b );
3368 /* If we reach this, both values must be finite */
3369 if ( *(double *)a < *(double *)b ) return -1; else return 1;
3370 }