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