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