1 /****************************************************************************
2 * RRDtool 1.2.28 Copyright by Tobi Oetiker, 1997-2008
3 ****************************************************************************
4 * rrd__graph.c produce graphs from data in rrdfiles
5 ****************************************************************************/
8 #include <sys/stat.h>
10 #ifdef WIN32
11 #include "strftime.h"
12 #endif
13 #include "rrd_tool.h"
15 #if defined(WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
16 #include <io.h>
17 #include <fcntl.h>
18 #endif
20 #ifdef HAVE_TIME_H
21 #include <time.h>
22 #endif
24 #ifdef HAVE_LOCALE_H
25 #include <locale.h>
26 #endif
28 #include "rrd_graph.h"
30 /* some constant definitions */
34 #ifndef RRD_DEFAULT_FONT
35 /* there is special code later to pick Cour.ttf when running on windows */
36 #define RRD_DEFAULT_FONT "DejaVuSansMono-Roman.ttf"
37 #endif
39 text_prop_t text_prop[] = {
40 { 8.0, RRD_DEFAULT_FONT }, /* default */
41 { 9.0, RRD_DEFAULT_FONT }, /* title */
42 { 7.0, RRD_DEFAULT_FONT }, /* axis */
43 { 8.0, RRD_DEFAULT_FONT }, /* unit */
44 { 8.0, RRD_DEFAULT_FONT } /* legend */
45 };
47 xlab_t xlab[] = {
48 {0, 0, TMT_SECOND,30, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
49 {2, 0, TMT_MINUTE,1, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
50 {5, 0, TMT_MINUTE,2, TMT_MINUTE,10, TMT_MINUTE,10, 0,"%H:%M"},
51 {10, 0, TMT_MINUTE,5, TMT_MINUTE,20, TMT_MINUTE,20, 0,"%H:%M"},
52 {30, 0, TMT_MINUTE,10, TMT_HOUR,1, TMT_HOUR,1, 0,"%H:%M"},
53 {60, 0, TMT_MINUTE,30, TMT_HOUR,2, TMT_HOUR,2, 0,"%H:%M"},
54 {60, 24*3600, TMT_MINUTE,30, TMT_HOUR,2, TMT_HOUR,6, 0,"%a %H:%M"},
55 {180, 0, TMT_HOUR,1, TMT_HOUR,6, TMT_HOUR,6, 0,"%H:%M"},
56 {180, 24*3600, TMT_HOUR,1, TMT_HOUR,6, TMT_HOUR,12, 0,"%a %H:%M"},
57 /*{300, 0, TMT_HOUR,3, TMT_HOUR,12, TMT_HOUR,12, 12*3600,"%a %p"}, this looks silly*/
58 {600, 0, TMT_HOUR,6, TMT_DAY,1, TMT_DAY,1, 24*3600,"%a"},
59 {1200, 0, TMT_HOUR,6, TMT_DAY,1, TMT_DAY,1, 24*3600,"%d"},
60 {1800, 0, TMT_HOUR,12, TMT_DAY,1, TMT_DAY,2, 24*3600,"%a %d"},
61 {2400, 0, TMT_HOUR,12, TMT_DAY,1, TMT_DAY,2, 24*3600,"%a"},
62 {3600, 0, TMT_DAY,1, TMT_WEEK,1, TMT_WEEK,1, 7*24*3600,"Week %V"},
63 {3*3600, 0, TMT_WEEK,1, TMT_MONTH,1, TMT_WEEK,2, 7*24*3600,"Week %V"},
64 {6*3600, 0, TMT_MONTH,1, TMT_MONTH,1, TMT_MONTH,1, 30*24*3600,"%b"},
65 {48*3600, 0, TMT_MONTH,1, TMT_MONTH,3, TMT_MONTH,3, 30*24*3600,"%b"},
66 {315360, 0, TMT_MONTH,3, TMT_YEAR,1, TMT_YEAR,1, 365*24*3600,"%Y"},
67 {10*24*3600, 0, TMT_YEAR,1, TMT_YEAR,1, TMT_YEAR,1, 365*24*3600,"%y"},
68 {-1,0,TMT_MONTH,0,TMT_MONTH,0,TMT_MONTH,0,0,""}
69 };
71 /* sensible y label intervals ...*/
73 ylab_t ylab[]= {
74 {0.1, {1,2, 5,10}},
75 {0.2, {1,5,10,20}},
76 {0.5, {1,2, 4,10}},
77 {1.0, {1,2, 5,10}},
78 {2.0, {1,5,10,20}},
79 {5.0, {1,2, 4,10}},
80 {10.0, {1,2, 5,10}},
81 {20.0, {1,5,10,20}},
82 {50.0, {1,2, 4,10}},
83 {100.0, {1,2, 5,10}},
84 {200.0, {1,5,10,20}},
85 {500.0, {1,2, 4,10}},
86 {0.0, {0,0,0,0}}};
89 gfx_color_t graph_col[] = /* default colors */
90 { 0xFFFFFFFF, /* canvas */
91 0xF0F0F0FF, /* background */
92 0xD0D0D0FF, /* shade A */
93 0xA0A0A0FF, /* shade B */
94 0x90909080, /* grid */
95 0xE0505080, /* major grid */
96 0x000000FF, /* font */
97 0x802020FF, /* arrow */
98 0x202020FF, /* axis */
99 0x000000FF /* frame */
100 };
103 /* #define DEBUG */
105 #ifdef DEBUG
106 # define DPRINT(x) (void)(printf x, printf("\n"))
107 #else
108 # define DPRINT(x)
109 #endif
112 /* initialize with xtr(im,0); */
113 int
114 xtr(image_desc_t *im,time_t mytime){
115 static double pixie;
116 if (mytime==0){
117 pixie = (double) im->xsize / (double)(im->end - im->start);
118 return im->xorigin;
119 }
120 return (int)((double)im->xorigin
121 + pixie * ( mytime - im->start ) );
122 }
124 /* translate data values into y coordinates */
125 double
126 ytr(image_desc_t *im, double value){
127 static double pixie;
128 double yval;
129 if (isnan(value)){
130 if(!im->logarithmic)
131 pixie = (double) im->ysize / (im->maxval - im->minval);
132 else
133 pixie = (double) im->ysize / (log10(im->maxval) - log10(im->minval));
134 yval = im->yorigin;
135 } else if(!im->logarithmic) {
136 yval = im->yorigin - pixie * (value - im->minval);
137 } else {
138 if (value < im->minval) {
139 yval = im->yorigin;
140 } else {
141 yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
142 }
143 }
144 /* make sure we don't return anything too unreasonable. GD lib can
145 get terribly slow when drawing lines outside its scope. This is
146 especially problematic in connection with the rigid option */
147 if (! im->rigid) {
148 /* keep yval as-is */
149 } else if (yval > im->yorigin) {
150 yval = im->yorigin +0.00001;
151 } else if (yval < im->yorigin - im->ysize){
152 yval = im->yorigin - im->ysize - 0.00001;
153 }
154 return yval;
155 }
159 /* conversion function for symbolic entry names */
162 #define conv_if(VV,VVV) \
163 if (strcmp(#VV, string) == 0) return VVV ;
165 enum gf_en gf_conv(char *string){
167 conv_if(PRINT,GF_PRINT)
168 conv_if(GPRINT,GF_GPRINT)
169 conv_if(COMMENT,GF_COMMENT)
170 conv_if(HRULE,GF_HRULE)
171 conv_if(VRULE,GF_VRULE)
172 conv_if(LINE,GF_LINE)
173 conv_if(AREA,GF_AREA)
174 conv_if(STACK,GF_STACK)
175 conv_if(TICK,GF_TICK)
176 conv_if(DEF,GF_DEF)
177 conv_if(CDEF,GF_CDEF)
178 conv_if(VDEF,GF_VDEF)
179 #ifdef WITH_PIECHART
180 conv_if(PART,GF_PART)
181 #endif
182 conv_if(XPORT,GF_XPORT)
183 conv_if(SHIFT,GF_SHIFT)
185 return (-1);
186 }
188 enum gfx_if_en if_conv(char *string){
190 conv_if(PNG,IF_PNG)
191 conv_if(SVG,IF_SVG)
192 conv_if(EPS,IF_EPS)
193 conv_if(PDF,IF_PDF)
195 return (-1);
196 }
198 enum tmt_en tmt_conv(char *string){
200 conv_if(SECOND,TMT_SECOND)
201 conv_if(MINUTE,TMT_MINUTE)
202 conv_if(HOUR,TMT_HOUR)
203 conv_if(DAY,TMT_DAY)
204 conv_if(WEEK,TMT_WEEK)
205 conv_if(MONTH,TMT_MONTH)
206 conv_if(YEAR,TMT_YEAR)
207 return (-1);
208 }
210 enum grc_en grc_conv(char *string){
212 conv_if(BACK,GRC_BACK)
213 conv_if(CANVAS,GRC_CANVAS)
214 conv_if(SHADEA,GRC_SHADEA)
215 conv_if(SHADEB,GRC_SHADEB)
216 conv_if(GRID,GRC_GRID)
217 conv_if(MGRID,GRC_MGRID)
218 conv_if(FONT,GRC_FONT)
219 conv_if(ARROW,GRC_ARROW)
220 conv_if(AXIS,GRC_AXIS)
221 conv_if(FRAME,GRC_FRAME)
223 return -1;
224 }
226 enum text_prop_en text_prop_conv(char *string){
228 conv_if(DEFAULT,TEXT_PROP_DEFAULT)
229 conv_if(TITLE,TEXT_PROP_TITLE)
230 conv_if(AXIS,TEXT_PROP_AXIS)
231 conv_if(UNIT,TEXT_PROP_UNIT)
232 conv_if(LEGEND,TEXT_PROP_LEGEND)
233 return -1;
234 }
237 #undef conv_if
239 int
240 im_free(image_desc_t *im)
241 {
242 unsigned long i,ii;
244 if (im == NULL) return 0;
245 for(i=0;i<(unsigned)im->gdes_c;i++){
246 if (im->gdes[i].data_first){
247 /* careful here, because a single pointer can occur several times */
248 free (im->gdes[i].data);
249 if (im->gdes[i].ds_namv){
250 for (ii=0;ii<im->gdes[i].ds_cnt;ii++)
251 free(im->gdes[i].ds_namv[ii]);
252 free(im->gdes[i].ds_namv);
253 }
254 }
255 free (im->gdes[i].p_data);
256 free (im->gdes[i].rpnp);
257 }
258 free(im->gdes);
259 gfx_destroy(im->canvas);
260 return 0;
261 }
263 /* find SI magnitude symbol for the given number*/
264 void
265 auto_scale(
266 image_desc_t *im, /* image description */
267 double *value,
268 char **symb_ptr,
269 double *magfact
270 )
271 {
273 char *symbol[] = {"a", /* 10e-18 Atto */
274 "f", /* 10e-15 Femto */
275 "p", /* 10e-12 Pico */
276 "n", /* 10e-9 Nano */
277 "u", /* 10e-6 Micro */
278 "m", /* 10e-3 Milli */
279 " ", /* Base */
280 "k", /* 10e3 Kilo */
281 "M", /* 10e6 Mega */
282 "G", /* 10e9 Giga */
283 "T", /* 10e12 Tera */
284 "P", /* 10e15 Peta */
285 "E"};/* 10e18 Exa */
287 int symbcenter = 6;
288 int sindex;
290 if (*value == 0.0 || isnan(*value) ) {
291 sindex = 0;
292 *magfact = 1.0;
293 } else {
294 sindex = floor(log(fabs(*value))/log((double)im->base));
295 *magfact = pow((double)im->base, (double)sindex);
296 (*value) /= (*magfact);
297 }
298 if ( sindex <= symbcenter && sindex >= -symbcenter) {
299 (*symb_ptr) = symbol[sindex+symbcenter];
300 }
301 else {
302 (*symb_ptr) = "?";
303 }
304 }
307 static char si_symbol[] = {
308 'a', /* 10e-18 Atto */
309 'f', /* 10e-15 Femto */
310 'p', /* 10e-12 Pico */
311 'n', /* 10e-9 Nano */
312 'u', /* 10e-6 Micro */
313 'm', /* 10e-3 Milli */
314 ' ', /* Base */
315 'k', /* 10e3 Kilo */
316 'M', /* 10e6 Mega */
317 'G', /* 10e9 Giga */
318 'T', /* 10e12 Tera */
319 'P', /* 10e15 Peta */
320 'E', /* 10e18 Exa */
321 };
322 static const int si_symbcenter = 6;
324 /* find SI magnitude symbol for the numbers on the y-axis*/
325 void
326 si_unit(
327 image_desc_t *im /* image description */
328 )
329 {
331 double digits,viewdigits=0;
333 digits = floor( log( max( fabs(im->minval),fabs(im->maxval)))/log((double)im->base));
335 if (im->unitsexponent != 9999) {
336 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
337 viewdigits = floor(im->unitsexponent / 3);
338 } else {
339 viewdigits = digits;
340 }
342 im->magfact = pow((double)im->base , digits);
344 #ifdef DEBUG
345 printf("digits %6.3f im->magfact %6.3f\n",digits,im->magfact);
346 #endif
348 im->viewfactor = im->magfact / pow((double)im->base , viewdigits);
350 if ( ((viewdigits+si_symbcenter) < sizeof(si_symbol)) &&
351 ((viewdigits+si_symbcenter) >= 0) )
352 im->symbol = si_symbol[(int)viewdigits+si_symbcenter];
353 else
354 im->symbol = '?';
355 }
357 /* move min and max values around to become sensible */
359 void
360 expand_range(image_desc_t *im)
361 {
362 double sensiblevalues[] ={1000.0,900.0,800.0,750.0,700.0,
363 600.0,500.0,400.0,300.0,250.0,
364 200.0,125.0,100.0,90.0,80.0,
365 75.0,70.0,60.0,50.0,40.0,30.0,
366 25.0,20.0,10.0,9.0,8.0,
367 7.0,6.0,5.0,4.0,3.5,3.0,
368 2.5,2.0,1.8,1.5,1.2,1.0,
369 0.8,0.7,0.6,0.5,0.4,0.3,0.2,0.1,0.0,-1};
371 double scaled_min,scaled_max;
372 double adj;
373 int i;
377 #ifdef DEBUG
378 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
379 im->minval,im->maxval,im->magfact);
380 #endif
382 if (isnan(im->ygridstep)){
383 if(im->extra_flags & ALTAUTOSCALE) {
384 /* measure the amplitude of the function. Make sure that
385 graph boundaries are slightly higher then max/min vals
386 so we can see amplitude on the graph */
387 double delt, fact;
389 delt = im->maxval - im->minval;
390 adj = delt * 0.1;
391 fact = 2.0 * pow(10.0,
392 floor(log10(max(fabs(im->minval), fabs(im->maxval))/im->magfact)) - 2);
393 if (delt < fact) {
394 adj = (fact - delt) * 0.55;
395 #ifdef DEBUG
396 printf("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n", im->minval, im->maxval, delt, fact, adj);
397 #endif
398 }
399 im->minval -= adj;
400 im->maxval += adj;
401 }
402 else if(im->extra_flags & ALTAUTOSCALE_MIN) {
403 /* measure the amplitude of the function. Make sure that
404 graph boundaries are slightly lower than min vals
405 so we can see amplitude on the graph */
406 adj = (im->maxval - im->minval) * 0.1;
407 im->minval -= adj;
408 }
409 else if(im->extra_flags & ALTAUTOSCALE_MAX) {
410 /* measure the amplitude of the function. Make sure that
411 graph boundaries are slightly higher than max vals
412 so we can see amplitude on the graph */
413 adj = (im->maxval - im->minval) * 0.1;
414 im->maxval += adj;
415 }
416 else {
417 scaled_min = im->minval / im->magfact;
418 scaled_max = im->maxval / im->magfact;
420 for (i=1; sensiblevalues[i] > 0; i++){
421 if (sensiblevalues[i-1]>=scaled_min &&
422 sensiblevalues[i]<=scaled_min)
423 im->minval = sensiblevalues[i]*(im->magfact);
425 if (-sensiblevalues[i-1]<=scaled_min &&
426 -sensiblevalues[i]>=scaled_min)
427 im->minval = -sensiblevalues[i-1]*(im->magfact);
429 if (sensiblevalues[i-1] >= scaled_max &&
430 sensiblevalues[i] <= scaled_max)
431 im->maxval = sensiblevalues[i-1]*(im->magfact);
433 if (-sensiblevalues[i-1]<=scaled_max &&
434 -sensiblevalues[i] >=scaled_max)
435 im->maxval = -sensiblevalues[i]*(im->magfact);
436 }
437 }
438 } else {
439 /* adjust min and max to the grid definition if there is one */
440 im->minval = (double)im->ylabfact * im->ygridstep *
441 floor(im->minval / ((double)im->ylabfact * im->ygridstep));
442 im->maxval = (double)im->ylabfact * im->ygridstep *
443 ceil(im->maxval /( (double)im->ylabfact * im->ygridstep));
444 }
446 #ifdef DEBUG
447 fprintf(stderr,"SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
448 im->minval,im->maxval,im->magfact);
449 #endif
450 }
452 void
453 apply_gridfit(image_desc_t *im)
454 {
455 if (isnan(im->minval) || isnan(im->maxval))
456 return;
457 ytr(im,DNAN);
458 if (im->logarithmic) {
459 double ya, yb, ypix, ypixfrac;
460 double log10_range = log10(im->maxval) - log10(im->minval);
461 ya = pow((double)10, floor(log10(im->minval)));
462 while (ya < im->minval)
463 ya *= 10;
464 if (ya > im->maxval)
465 return; /* don't have y=10^x gridline */
466 yb = ya * 10;
467 if (yb <= im->maxval) {
468 /* we have at least 2 y=10^x gridlines.
469 Make sure distance between them in pixels
470 are an integer by expanding im->maxval */
471 double y_pixel_delta = ytr(im, ya) - ytr(im, yb);
472 double factor = y_pixel_delta / floor(y_pixel_delta);
473 double new_log10_range = factor * log10_range;
474 double new_ymax_log10 = log10(im->minval) + new_log10_range;
475 im->maxval = pow(10, new_ymax_log10);
476 ytr(im,DNAN); /* reset precalc */
477 log10_range = log10(im->maxval) - log10(im->minval);
478 }
479 /* make sure first y=10^x gridline is located on
480 integer pixel position by moving scale slightly
481 downwards (sub-pixel movement) */
482 ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
483 ypixfrac = ypix - floor(ypix);
484 if (ypixfrac > 0 && ypixfrac < 1) {
485 double yfrac = ypixfrac / im->ysize;
486 im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
487 im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
488 ytr(im,DNAN); /* reset precalc */
489 }
490 } else {
491 /* Make sure we have an integer pixel distance between
492 each minor gridline */
493 double ypos1 = ytr(im, im->minval);
494 double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
495 double y_pixel_delta = ypos1 - ypos2;
496 double factor = y_pixel_delta / floor(y_pixel_delta);
497 double new_range = factor * (im->maxval - im->minval);
498 double gridstep = im->ygrid_scale.gridstep;
499 double minor_y, minor_y_px, minor_y_px_frac;
502 if (im->maxval > 0.0)
503 im->maxval = im->minval + new_range;
504 else
505 im->minval = im->maxval - new_range;
506 ytr(im,DNAN); /* reset precalc */
508 /* make sure first minor gridline is on integer pixel y coord */
509 minor_y = gridstep * floor(im->minval / gridstep);
510 while (minor_y < im->minval)
511 minor_y += gridstep;
512 minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
513 minor_y_px_frac = minor_y_px - floor(minor_y_px);
514 if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
515 double yfrac = minor_y_px_frac / im->ysize;
516 double range = im->maxval - im->minval;
517 im->minval = im->minval - yfrac * range;
518 im->maxval = im->maxval - yfrac * range;
519 ytr(im,DNAN); /* reset precalc */
520 }
521 calc_horizontal_grid(im); /* recalc with changed im->maxval */
522 }
523 }
525 /* reduce data reimplementation by Alex */
527 void
528 reduce_data(
529 enum cf_en cf, /* which consolidation function ?*/
530 unsigned long cur_step, /* step the data currently is in */
531 time_t *start, /* start, end and step as requested ... */
532 time_t *end, /* ... by the application will be ... */
533 unsigned long *step, /* ... adjusted to represent reality */
534 unsigned long *ds_cnt, /* number of data sources in file */
535 rrd_value_t **data) /* two dimensional array containing the data */
536 {
537 int i,reduce_factor = ceil((double)(*step) / (double)cur_step);
538 unsigned long col,dst_row,row_cnt,start_offset,end_offset,skiprows=0;
539 rrd_value_t *srcptr,*dstptr;
541 (*step) = cur_step*reduce_factor; /* set new step size for reduced data */
542 dstptr = *data;
543 srcptr = *data;
544 row_cnt = ((*end)-(*start))/cur_step;
546 #ifdef DEBUG
547 #define DEBUG_REDUCE
548 #endif
549 #ifdef DEBUG_REDUCE
550 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
551 row_cnt,reduce_factor,*start,*end,cur_step);
552 for (col=0;col<row_cnt;col++) {
553 printf("time %10lu: ",*start+(col+1)*cur_step);
554 for (i=0;i<*ds_cnt;i++)
555 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
556 printf("\n");
557 }
558 #endif
560 /* We have to combine [reduce_factor] rows of the source
561 ** into one row for the destination. Doing this we also
562 ** need to take care to combine the correct rows. First
563 ** alter the start and end time so that they are multiples
564 ** of the new step time. We cannot reduce the amount of
565 ** time so we have to move the end towards the future and
566 ** the start towards the past.
567 */
568 end_offset = (*end) % (*step);
569 start_offset = (*start) % (*step);
571 /* If there is a start offset (which cannot be more than
572 ** one destination row), skip the appropriate number of
573 ** source rows and one destination row. The appropriate
574 ** number is what we do know (start_offset/cur_step) of
575 ** the new interval (*step/cur_step aka reduce_factor).
576 */
577 #ifdef DEBUG_REDUCE
578 printf("start_offset: %lu end_offset: %lu\n",start_offset,end_offset);
579 printf("row_cnt before: %lu\n",row_cnt);
580 #endif
581 if (start_offset) {
582 (*start) = (*start)-start_offset;
583 skiprows=reduce_factor-start_offset/cur_step;
584 srcptr+=skiprows* *ds_cnt;
585 for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
586 row_cnt-=skiprows;
587 }
588 #ifdef DEBUG_REDUCE
589 printf("row_cnt between: %lu\n",row_cnt);
590 #endif
592 /* At the end we have some rows that are not going to be
593 ** used, the amount is end_offset/cur_step
594 */
595 if (end_offset) {
596 (*end) = (*end)-end_offset+(*step);
597 skiprows = end_offset/cur_step;
598 row_cnt-=skiprows;
599 }
600 #ifdef DEBUG_REDUCE
601 printf("row_cnt after: %lu\n",row_cnt);
602 #endif
604 /* Sanity check: row_cnt should be multiple of reduce_factor */
605 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
607 if (row_cnt%reduce_factor) {
608 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
609 row_cnt,reduce_factor);
610 printf("BUG in reduce_data()\n");
611 exit(1);
612 }
614 /* Now combine reduce_factor intervals at a time
615 ** into one interval for the destination.
616 */
618 for (dst_row=0;(long int)row_cnt>=reduce_factor;dst_row++) {
619 for (col=0;col<(*ds_cnt);col++) {
620 rrd_value_t newval=DNAN;
621 unsigned long validval=0;
623 for (i=0;i<reduce_factor;i++) {
624 if (isnan(srcptr[i*(*ds_cnt)+col])) {
625 continue;
626 }
627 validval++;
628 if (isnan(newval)) newval = srcptr[i*(*ds_cnt)+col];
629 else {
630 switch (cf) {
631 case CF_HWPREDICT:
632 case CF_DEVSEASONAL:
633 case CF_DEVPREDICT:
634 case CF_SEASONAL:
635 case CF_AVERAGE:
636 newval += srcptr[i*(*ds_cnt)+col];
637 break;
638 case CF_MINIMUM:
639 newval = min (newval,srcptr[i*(*ds_cnt)+col]);
640 break;
641 case CF_FAILURES:
642 /* an interval contains a failure if any subintervals contained a failure */
643 case CF_MAXIMUM:
644 newval = max (newval,srcptr[i*(*ds_cnt)+col]);
645 break;
646 case CF_LAST:
647 newval = srcptr[i*(*ds_cnt)+col];
648 break;
649 }
650 }
651 }
652 if (validval == 0){newval = DNAN;} else{
653 switch (cf) {
654 case CF_HWPREDICT:
655 case CF_DEVSEASONAL:
656 case CF_DEVPREDICT:
657 case CF_SEASONAL:
658 case CF_AVERAGE:
659 newval /= validval;
660 break;
661 case CF_MINIMUM:
662 case CF_FAILURES:
663 case CF_MAXIMUM:
664 case CF_LAST:
665 break;
666 }
667 }
668 *dstptr++=newval;
669 }
670 srcptr+=(*ds_cnt)*reduce_factor;
671 row_cnt-=reduce_factor;
672 }
673 /* If we had to alter the endtime, we didn't have enough
674 ** source rows to fill the last row. Fill it with NaN.
675 */
676 if (end_offset) for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
677 #ifdef DEBUG_REDUCE
678 row_cnt = ((*end)-(*start))/ *step;
679 srcptr = *data;
680 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
681 row_cnt,*start,*end,*step);
682 for (col=0;col<row_cnt;col++) {
683 printf("time %10lu: ",*start+(col+1)*(*step));
684 for (i=0;i<*ds_cnt;i++)
685 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
686 printf("\n");
687 }
688 #endif
689 }
692 /* get the data required for the graphs from the
693 relevant rrds ... */
695 int
696 data_fetch(image_desc_t *im )
697 {
698 int i,ii;
699 int skip;
701 /* pull the data from the rrd files ... */
702 for (i=0;i< (int)im->gdes_c;i++){
703 /* only GF_DEF elements fetch data */
704 if (im->gdes[i].gf != GF_DEF)
705 continue;
707 skip=0;
708 /* do we have it already ?*/
709 for (ii=0;ii<i;ii++) {
710 if (im->gdes[ii].gf != GF_DEF)
711 continue;
712 if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
713 && (im->gdes[i].cf == im->gdes[ii].cf)
714 && (im->gdes[i].cf_reduce == im->gdes[ii].cf_reduce)
715 && (im->gdes[i].start_orig == im->gdes[ii].start_orig)
716 && (im->gdes[i].end_orig == im->gdes[ii].end_orig)
717 && (im->gdes[i].step_orig == im->gdes[ii].step_orig)) {
718 /* OK, the data is already there.
719 ** Just copy the header portion
720 */
721 im->gdes[i].start = im->gdes[ii].start;
722 im->gdes[i].end = im->gdes[ii].end;
723 im->gdes[i].step = im->gdes[ii].step;
724 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
725 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
726 im->gdes[i].data = im->gdes[ii].data;
727 im->gdes[i].data_first = 0;
728 skip=1;
729 }
730 if (skip)
731 break;
732 }
733 if (! skip) {
734 unsigned long ft_step = im->gdes[i].step ; /* ft_step will record what we got from fetch */
736 if((rrd_fetch_fn(im->gdes[i].rrd,
737 im->gdes[i].cf,
738 &im->gdes[i].start,
739 &im->gdes[i].end,
740 &ft_step,
741 &im->gdes[i].ds_cnt,
742 &im->gdes[i].ds_namv,
743 &im->gdes[i].data)) == -1){
744 return -1;
745 }
746 im->gdes[i].data_first = 1;
748 if (ft_step < im->gdes[i].step) {
749 reduce_data(im->gdes[i].cf_reduce,
750 ft_step,
751 &im->gdes[i].start,
752 &im->gdes[i].end,
753 &im->gdes[i].step,
754 &im->gdes[i].ds_cnt,
755 &im->gdes[i].data);
756 } else {
757 im->gdes[i].step = ft_step;
758 }
759 }
761 /* lets see if the required data source is really there */
762 for(ii=0;ii<(int)im->gdes[i].ds_cnt;ii++){
763 if(strcmp(im->gdes[i].ds_namv[ii],im->gdes[i].ds_nam) == 0){
764 im->gdes[i].ds=ii; }
765 }
766 if (im->gdes[i].ds== -1){
767 rrd_set_error("No DS called '%s' in '%s'",
768 im->gdes[i].ds_nam,im->gdes[i].rrd);
769 return -1;
770 }
772 }
773 return 0;
774 }
776 /* evaluate the expressions in the CDEF functions */
778 /*************************************************************
779 * CDEF stuff
780 *************************************************************/
782 long
783 find_var_wrapper(void *arg1, char *key)
784 {
785 return find_var((image_desc_t *) arg1, key);
786 }
788 /* find gdes containing var*/
789 long
790 find_var(image_desc_t *im, char *key){
791 long ii;
792 for(ii=0;ii<im->gdes_c-1;ii++){
793 if((im->gdes[ii].gf == GF_DEF
794 || im->gdes[ii].gf == GF_VDEF
795 || im->gdes[ii].gf == GF_CDEF)
796 && (strcmp(im->gdes[ii].vname,key) == 0)){
797 return ii;
798 }
799 }
800 return -1;
801 }
803 /* find the largest common denominator for all the numbers
804 in the 0 terminated num array */
805 long
806 lcd(long *num){
807 long rest;
808 int i;
809 for (i=0;num[i+1]!=0;i++){
810 do {
811 rest=num[i] % num[i+1];
812 num[i]=num[i+1]; num[i+1]=rest;
813 } while (rest!=0);
814 num[i+1] = num[i];
815 }
816 /* return i==0?num[i]:num[i-1]; */
817 return num[i];
818 }
820 /* run the rpn calculator on all the VDEF and CDEF arguments */
821 int
822 data_calc( image_desc_t *im){
824 int gdi;
825 int dataidx;
826 long *steparray, rpi;
827 int stepcnt;
828 time_t now;
829 rpnstack_t rpnstack;
831 rpnstack_init(&rpnstack);
833 for (gdi=0;gdi<im->gdes_c;gdi++){
834 /* Look for GF_VDEF and GF_CDEF in the same loop,
835 * so CDEFs can use VDEFs and vice versa
836 */
837 switch (im->gdes[gdi].gf) {
838 case GF_XPORT:
839 break;
840 case GF_SHIFT: {
841 graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx];
843 /* remove current shift */
844 vdp->start -= vdp->shift;
845 vdp->end -= vdp->shift;
847 /* vdef */
848 if (im->gdes[gdi].shidx >= 0)
849 vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
850 /* constant */
851 else
852 vdp->shift = im->gdes[gdi].shval;
854 /* normalize shift to multiple of consolidated step */
855 vdp->shift = (vdp->shift / (long)vdp->step) * (long)vdp->step;
857 /* apply shift */
858 vdp->start += vdp->shift;
859 vdp->end += vdp->shift;
860 break;
861 }
862 case GF_VDEF:
863 /* A VDEF has no DS. This also signals other parts
864 * of rrdtool that this is a VDEF value, not a CDEF.
865 */
866 im->gdes[gdi].ds_cnt = 0;
867 if (vdef_calc(im,gdi)) {
868 rrd_set_error("Error processing VDEF '%s'"
869 ,im->gdes[gdi].vname
870 );
871 rpnstack_free(&rpnstack);
872 return -1;
873 }
874 break;
875 case GF_CDEF:
876 im->gdes[gdi].ds_cnt = 1;
877 im->gdes[gdi].ds = 0;
878 im->gdes[gdi].data_first = 1;
879 im->gdes[gdi].start = 0;
880 im->gdes[gdi].end = 0;
881 steparray=NULL;
882 stepcnt = 0;
883 dataidx=-1;
885 /* Find the variables in the expression.
886 * - VDEF variables are substituted by their values
887 * and the opcode is changed into OP_NUMBER.
888 * - CDEF variables are analized for their step size,
889 * the lowest common denominator of all the step
890 * sizes of the data sources involved is calculated
891 * and the resulting number is the step size for the
892 * resulting data source.
893 */
894 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
895 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
896 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER){
897 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
898 if (im->gdes[ptr].ds_cnt == 0) { /* this is a VDEF data source */
899 #if 0
900 printf("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
901 im->gdes[gdi].vname,
902 im->gdes[ptr].vname);
903 printf("DEBUG: value from vdef is %f\n",im->gdes[ptr].vf.val);
904 #endif
905 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
906 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
907 } else { /* normal variables and PREF(variables) */
909 /* add one entry to the array that keeps track of the step sizes of the
910 * data sources going into the CDEF. */
911 if ((steparray =
912 rrd_realloc(steparray,
913 (++stepcnt+1)*sizeof(*steparray)))==NULL){
914 rrd_set_error("realloc steparray");
915 rpnstack_free(&rpnstack);
916 return -1;
917 };
919 steparray[stepcnt-1] = im->gdes[ptr].step;
921 /* adjust start and end of cdef (gdi) so
922 * that it runs from the latest start point
923 * to the earliest endpoint of any of the
924 * rras involved (ptr)
925 */
927 if(im->gdes[gdi].start < im->gdes[ptr].start)
928 im->gdes[gdi].start = im->gdes[ptr].start;
930 if(im->gdes[gdi].end == 0 ||
931 im->gdes[gdi].end > im->gdes[ptr].end)
932 im->gdes[gdi].end = im->gdes[ptr].end;
934 /* store pointer to the first element of
935 * the rra providing data for variable,
936 * further save step size and data source
937 * count of this rra
938 */
939 im->gdes[gdi].rpnp[rpi].data = im->gdes[ptr].data + im->gdes[ptr].ds;
940 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
941 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
943 /* backoff the *.data ptr; this is done so
944 * rpncalc() function doesn't have to treat
945 * the first case differently
946 */
947 } /* if ds_cnt != 0 */
948 } /* if OP_VARIABLE */
949 } /* loop through all rpi */
951 /* move the data pointers to the correct period */
952 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
953 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
954 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER){
955 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
956 long diff = im->gdes[gdi].start - im->gdes[ptr].start;
958 if(diff > 0)
959 im->gdes[gdi].rpnp[rpi].data += (diff / im->gdes[ptr].step) * im->gdes[ptr].ds_cnt;
960 }
961 }
963 if(steparray == NULL){
964 rrd_set_error("rpn expressions without DEF"
965 " or CDEF variables are not supported");
966 rpnstack_free(&rpnstack);
967 return -1;
968 }
969 steparray[stepcnt]=0;
970 /* Now find the resulting step. All steps in all
971 * used RRAs have to be visited
972 */
973 im->gdes[gdi].step = lcd(steparray);
974 free(steparray);
975 if((im->gdes[gdi].data = malloc((
976 (im->gdes[gdi].end-im->gdes[gdi].start)
977 / im->gdes[gdi].step)
978 * sizeof(double)))==NULL){
979 rrd_set_error("malloc im->gdes[gdi].data");
980 rpnstack_free(&rpnstack);
981 return -1;
982 }
984 /* Step through the new cdef results array and
985 * calculate the values
986 */
987 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
988 now<=im->gdes[gdi].end;
989 now += im->gdes[gdi].step)
990 {
991 rpnp_t *rpnp = im -> gdes[gdi].rpnp;
993 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
994 * in this case we are advancing by timesteps;
995 * we use the fact that time_t is a synonym for long
996 */
997 if (rpn_calc(rpnp,&rpnstack,(long) now,
998 im->gdes[gdi].data,++dataidx) == -1) {
999 /* rpn_calc sets the error string */
1000 rpnstack_free(&rpnstack);
1001 return -1;
1002 }
1003 } /* enumerate over time steps within a CDEF */
1004 break;
1005 default:
1006 continue;
1007 }
1008 } /* enumerate over CDEFs */
1009 rpnstack_free(&rpnstack);
1010 return 0;
1011 }
1013 static int AlmostEqual2sComplement (float A, float B, int maxUlps)
1014 {
1016 int aInt = *(int*)&A;
1017 int bInt = *(int*)&B;
1018 int intDiff;
1019 /* Make sure maxUlps is non-negative and small enough that the
1020 default NAN won't compare as equal to anything. */
1022 /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
1024 /* Make aInt lexicographically ordered as a twos-complement int */
1026 if (aInt < 0)
1027 aInt = 0x80000000l - aInt;
1029 /* Make bInt lexicographically ordered as a twos-complement int */
1031 if (bInt < 0)
1032 bInt = 0x80000000l - bInt;
1034 intDiff = abs(aInt - bInt);
1036 if (intDiff <= maxUlps)
1037 return 1;
1039 return 0;
1040 }
1042 /* massage data so, that we get one value for each x coordinate in the graph */
1043 int
1044 data_proc( image_desc_t *im ){
1045 long i,ii;
1046 double pixstep = (double)(im->end-im->start)
1047 /(double)im->xsize; /* how much time
1048 passes in one pixel */
1049 double paintval;
1050 double minval=DNAN,maxval=DNAN;
1052 unsigned long gr_time;
1054 /* memory for the processed data */
1055 for(i=0;i<im->gdes_c;i++) {
1056 if((im->gdes[i].gf==GF_LINE) ||
1057 (im->gdes[i].gf==GF_AREA) ||
1058 (im->gdes[i].gf==GF_TICK)) {
1059 if((im->gdes[i].p_data = malloc((im->xsize +1)
1060 * sizeof(rrd_value_t)))==NULL){
1061 rrd_set_error("malloc data_proc");
1062 return -1;
1063 }
1064 }
1065 }
1067 for (i=0;i<im->xsize;i++) { /* for each pixel */
1068 long vidx;
1069 gr_time = im->start+pixstep*i; /* time of the current step */
1070 paintval=0.0;
1072 for (ii=0;ii<im->gdes_c;ii++) {
1073 double value;
1074 switch (im->gdes[ii].gf) {
1075 case GF_LINE:
1076 case GF_AREA:
1077 case GF_TICK:
1078 if (!im->gdes[ii].stack)
1079 paintval = 0.0;
1080 value = im->gdes[ii].yrule;
1081 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1082 /* The time of the data doesn't necessarily match
1083 ** the time of the graph. Beware.
1084 */
1085 vidx = im->gdes[ii].vidx;
1086 if (im->gdes[vidx].gf == GF_VDEF) {
1087 value = im->gdes[vidx].vf.val;
1088 } else if (((long int)gr_time >= (long int)im->gdes[vidx].start) &&
1089 ((long int)gr_time <= (long int)im->gdes[vidx].end) ) {
1090 value = im->gdes[vidx].data[
1091 (unsigned long) floor(
1092 (double)(gr_time - im->gdes[vidx].start)
1093 / im->gdes[vidx].step)
1094 * im->gdes[vidx].ds_cnt
1095 + im->gdes[vidx].ds
1096 ];
1097 } else {
1098 value = DNAN;
1099 }
1100 };
1102 if (! isnan(value)) {
1103 paintval += value;
1104 im->gdes[ii].p_data[i] = paintval;
1105 /* GF_TICK: the data values are not
1106 ** relevant for min and max
1107 */
1108 if (finite(paintval) && im->gdes[ii].gf != GF_TICK ) {
1109 if ((isnan(minval) || paintval < minval ) &&
1110 ! (im->logarithmic && paintval <= 0.0))
1111 minval = paintval;
1112 if (isnan(maxval) || paintval > maxval)
1113 maxval = paintval;
1114 }
1115 } else {
1116 im->gdes[ii].p_data[i] = DNAN;
1117 }
1118 break;
1119 case GF_STACK:
1120 rrd_set_error("STACK should already be turned into LINE or AREA here");
1121 return -1;
1122 break;
1123 default:
1124 break;
1125 }
1126 }
1127 }
1129 /* if min or max have not been asigned a value this is because
1130 there was no data in the graph ... this is not good ...
1131 lets set these to dummy values then ... */
1133 if (im->logarithmic) {
1134 if (isnan(minval)) minval = 0.2;
1135 if (isnan(maxval)) maxval = 5.1;
1136 }
1137 else {
1138 if (isnan(minval)) minval = 0.0;
1139 if (isnan(maxval)) maxval = 1.0;
1140 }
1142 /* adjust min and max values */
1143 if (isnan(im->minval)
1144 /* don't adjust low-end with log scale */ /* why not? */
1145 || ((!im->rigid) && im->minval > minval)
1146 ) {
1147 if (im->logarithmic)
1148 im->minval = minval * 0.5;
1149 else
1150 im->minval = minval;
1151 }
1152 if (isnan(im->maxval)
1153 || (!im->rigid && im->maxval < maxval)
1154 ) {
1155 if (im->logarithmic)
1156 im->maxval = maxval * 2.0;
1157 else
1158 im->maxval = maxval;
1159 }
1160 /* make sure min is smaller than max */
1161 if (im->minval > im->maxval ) {
1162 if (im->maxval > 0)
1163 im->minval = 0.99 * im->maxval;
1164 else
1165 im->minval = 1.01 * im->maxval;
1166 }
1168 /* make sure min and max are not equal */
1169 if ( AlmostEqual2sComplement(im->minval,im->maxval,4)) {
1170 if (im->maxval > 0)
1171 im->maxval *= 1.01;
1172 else
1173 im->maxval *= 0.99;
1175 if (! im->logarithmic) {
1176 if (im->minval > 0)
1177 im->minval *= 0.99;
1178 else
1179 im->minval *= 1.01;
1180 }
1181 /* make sure min and max are not both zero */
1182 if (AlmostEqual2sComplement(im->maxval,0,4)) {
1183 im->maxval = 1.0;
1184 }
1185 }
1186 return 0;
1187 }
1191 /* identify the point where the first gridline, label ... gets placed */
1193 time_t
1194 find_first_time(
1195 time_t start, /* what is the initial time */
1196 enum tmt_en baseint, /* what is the basic interval */
1197 long basestep /* how many if these do we jump a time */
1198 )
1199 {
1200 struct tm tm;
1201 localtime_r(&start, &tm);
1202 switch(baseint){
1203 case TMT_SECOND:
1204 tm.tm_sec -= tm.tm_sec % basestep; break;
1205 case TMT_MINUTE:
1206 tm.tm_sec=0;
1207 tm.tm_min -= tm.tm_min % basestep;
1208 break;
1209 case TMT_HOUR:
1210 tm.tm_sec=0;
1211 tm.tm_min = 0;
1212 tm.tm_hour -= tm.tm_hour % basestep; break;
1213 case TMT_DAY:
1214 /* we do NOT look at the basestep for this ... */
1215 tm.tm_sec=0;
1216 tm.tm_min = 0;
1217 tm.tm_hour = 0; break;
1218 case TMT_WEEK:
1219 /* we do NOT look at the basestep for this ... */
1220 tm.tm_sec=0;
1221 tm.tm_min = 0;
1222 tm.tm_hour = 0;
1223 tm.tm_mday -= tm.tm_wday -1; /* -1 because we want the monday */
1224 if (tm.tm_wday==0) tm.tm_mday -= 7; /* we want the *previous* monday */
1225 break;
1226 case TMT_MONTH:
1227 tm.tm_sec=0;
1228 tm.tm_min = 0;
1229 tm.tm_hour = 0;
1230 tm.tm_mday = 1;
1231 tm.tm_mon -= tm.tm_mon % basestep; break;
1233 case TMT_YEAR:
1234 tm.tm_sec=0;
1235 tm.tm_min = 0;
1236 tm.tm_hour = 0;
1237 tm.tm_mday = 1;
1238 tm.tm_mon = 0;
1239 tm.tm_year -= (tm.tm_year+1900) % basestep;
1241 }
1242 return mktime(&tm);
1243 }
1244 /* identify the point where the next gridline, label ... gets placed */
1245 time_t
1246 find_next_time(
1247 time_t current, /* what is the initial time */
1248 enum tmt_en baseint, /* what is the basic interval */
1249 long basestep /* how many if these do we jump a time */
1250 )
1251 {
1252 struct tm tm;
1253 time_t madetime;
1254 localtime_r(¤t, &tm);
1255 do {
1256 switch(baseint){
1257 case TMT_SECOND:
1258 tm.tm_sec += basestep; break;
1259 case TMT_MINUTE:
1260 tm.tm_min += basestep; break;
1261 case TMT_HOUR:
1262 tm.tm_hour += basestep; break;
1263 case TMT_DAY:
1264 tm.tm_mday += basestep; break;
1265 case TMT_WEEK:
1266 tm.tm_mday += 7*basestep; break;
1267 case TMT_MONTH:
1268 tm.tm_mon += basestep; break;
1269 case TMT_YEAR:
1270 tm.tm_year += basestep;
1271 }
1272 madetime = mktime(&tm);
1273 } while (madetime == -1); /* this is necessary to skip impssible times
1274 like the daylight saving time skips */
1275 return madetime;
1277 }
1280 /* calculate values required for PRINT and GPRINT functions */
1282 int
1283 print_calc(image_desc_t *im, char ***prdata)
1284 {
1285 long i,ii,validsteps;
1286 double printval;
1287 struct tm tmvdef;
1288 int graphelement = 0;
1289 long vidx;
1290 int max_ii;
1291 double magfact = -1;
1292 char *si_symb = "";
1293 char *percent_s;
1294 int prlines = 1;
1295 /* wow initializing tmvdef is quite a task :-) */
1296 time_t now = time(NULL);
1297 localtime_r(&now,&tmvdef);
1298 if (im->imginfo) prlines++;
1299 for(i=0;i<im->gdes_c;i++){
1300 vidx = im->gdes[i].vidx;
1301 switch(im->gdes[i].gf){
1302 case GF_PRINT:
1303 prlines++;
1304 if(((*prdata) = rrd_realloc((*prdata),prlines*sizeof(char *)))==NULL){
1305 rrd_set_error("realloc prdata");
1306 return 0;
1307 }
1308 case GF_GPRINT:
1309 /* PRINT and GPRINT can now print VDEF generated values.
1310 * There's no need to do any calculations on them as these
1311 * calculations were already made.
1312 */
1313 if (im->gdes[vidx].gf==GF_VDEF) { /* simply use vals */
1314 printval = im->gdes[vidx].vf.val;
1315 localtime_r(&im->gdes[vidx].vf.when,&tmvdef);
1316 } else { /* need to calculate max,min,avg etcetera */
1317 max_ii =((im->gdes[vidx].end
1318 - im->gdes[vidx].start)
1319 / im->gdes[vidx].step
1320 * im->gdes[vidx].ds_cnt);
1321 printval = DNAN;
1322 validsteps = 0;
1323 for( ii=im->gdes[vidx].ds;
1324 ii < max_ii;
1325 ii+=im->gdes[vidx].ds_cnt){
1326 if (! finite(im->gdes[vidx].data[ii]))
1327 continue;
1328 if (isnan(printval)){
1329 printval = im->gdes[vidx].data[ii];
1330 validsteps++;
1331 continue;
1332 }
1334 switch (im->gdes[i].cf){
1335 case CF_HWPREDICT:
1336 case CF_DEVPREDICT:
1337 case CF_DEVSEASONAL:
1338 case CF_SEASONAL:
1339 case CF_AVERAGE:
1340 validsteps++;
1341 printval += im->gdes[vidx].data[ii];
1342 break;
1343 case CF_MINIMUM:
1344 printval = min( printval, im->gdes[vidx].data[ii]);
1345 break;
1346 case CF_FAILURES:
1347 case CF_MAXIMUM:
1348 printval = max( printval, im->gdes[vidx].data[ii]);
1349 break;
1350 case CF_LAST:
1351 printval = im->gdes[vidx].data[ii];
1352 }
1353 }
1354 if (im->gdes[i].cf==CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1355 if (validsteps > 1) {
1356 printval = (printval / validsteps);
1357 }
1358 }
1359 } /* prepare printval */
1361 if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) {
1362 /* Magfact is set to -1 upon entry to print_calc. If it
1363 * is still less than 0, then we need to run auto_scale.
1364 * Otherwise, put the value into the correct units. If
1365 * the value is 0, then do not set the symbol or magnification
1366 * so next the calculation will be performed again. */
1367 if (magfact < 0.0) {
1368 auto_scale(im,&printval,&si_symb,&magfact);
1369 if (printval == 0.0)
1370 magfact = -1.0;
1371 } else {
1372 printval /= magfact;
1373 }
1374 *(++percent_s) = 's';
1375 } else if (strstr(im->gdes[i].format,"%s") != NULL) {
1376 auto_scale(im,&printval,&si_symb,&magfact);
1377 }
1379 if (im->gdes[i].gf == GF_PRINT){
1380 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1381 (*prdata)[prlines-1] = NULL;
1382 if (im->gdes[i].strftm){
1383 strftime((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,&tmvdef);
1384 } else {
1385 if (bad_format(im->gdes[i].format)) {
1386 rrd_set_error("bad format for PRINT in '%s'", im->gdes[i].format);
1387 return -1;
1388 }
1390 #ifdef HAVE_SNPRINTF
1391 snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
1392 #else
1393 sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
1394 #endif
1395 }
1396 } else {
1397 /* GF_GPRINT */
1399 if (im->gdes[i].strftm){
1400 strftime(im->gdes[i].legend,FMT_LEG_LEN,im->gdes[i].format,&tmvdef);
1401 } else {
1402 if (bad_format(im->gdes[i].format)) {
1403 rrd_set_error("bad format for GPRINT in '%s'", im->gdes[i].format);
1404 return -1;
1405 }
1406 #ifdef HAVE_SNPRINTF
1407 snprintf(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
1408 #else
1409 sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
1410 #endif
1411 }
1412 graphelement = 1;
1413 }
1414 break;
1415 case GF_LINE:
1416 case GF_AREA:
1417 case GF_TICK:
1418 graphelement = 1;
1419 break;
1420 case GF_HRULE:
1421 if(isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1422 im->gdes[i].yrule=im->gdes[vidx].vf.val;
1423 };
1424 graphelement = 1;
1425 break;
1426 case GF_VRULE:
1427 if(im->gdes[i].xrule == 0) { /* again ... the legend printer needs it*/
1428 im->gdes[i].xrule = im->gdes[vidx].vf.when;
1429 };
1430 graphelement = 1;
1431 break;
1432 case GF_COMMENT:
1433 case GF_DEF:
1434 case GF_CDEF:
1435 case GF_VDEF:
1436 #ifdef WITH_PIECHART
1437 case GF_PART:
1438 #endif
1439 case GF_SHIFT:
1440 case GF_XPORT:
1441 break;
1442 case GF_STACK:
1443 rrd_set_error("STACK should already be turned into LINE or AREA here");
1444 return -1;
1445 break;
1446 }
1447 }
1448 return graphelement;
1449 }
1452 /* place legends with color spots */
1453 int
1454 leg_place(image_desc_t *im)
1455 {
1456 /* graph labels */
1457 int interleg = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1458 int border = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1459 int fill=0, fill_last;
1460 int leg_c = 0;
1461 int leg_x = border, leg_y = im->yimg;
1462 int leg_y_prev = im->yimg;
1463 int leg_cc;
1464 int glue = 0;
1465 int i,ii, mark = 0;
1466 char prt_fctn; /*special printfunctions */
1467 int *legspace;
1469 if( !(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH) ) {
1470 if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1471 rrd_set_error("malloc for legspace");
1472 return -1;
1473 }
1475 for(i=0;i<im->gdes_c;i++){
1476 fill_last = fill;
1478 /* hid legends for rules which are not displayed */
1480 if(!(im->extra_flags & FORCE_RULES_LEGEND)) {
1481 if (im->gdes[i].gf == GF_HRULE &&
1482 (im->gdes[i].yrule < im->minval || im->gdes[i].yrule > im->maxval))
1483 im->gdes[i].legend[0] = '\0';
1485 if (im->gdes[i].gf == GF_VRULE &&
1486 (im->gdes[i].xrule < im->start || im->gdes[i].xrule > im->end))
1487 im->gdes[i].legend[0] = '\0';
1488 }
1490 leg_cc = strlen(im->gdes[i].legend);
1492 /* is there a controle code ant the end of the legend string ? */
1493 /* and it is not a tab \\t */
1494 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\' && im->gdes[i].legend[leg_cc-1] != 't') {
1495 prt_fctn = im->gdes[i].legend[leg_cc-1];
1496 leg_cc -= 2;
1497 im->gdes[i].legend[leg_cc] = '\0';
1498 } else {
1499 prt_fctn = '\0';
1500 }
1501 /* only valid control codes */
1502 if (prt_fctn != 'l' &&
1503 prt_fctn != 'n' && /* a synonym for l */
1504 prt_fctn != 'r' &&
1505 prt_fctn != 'j' &&
1506 prt_fctn != 'c' &&
1507 prt_fctn != 's' &&
1508 prt_fctn != 't' &&
1509 prt_fctn != '\0' &&
1510 prt_fctn != 'g' ) {
1511 free(legspace);
1512 rrd_set_error("Unknown control code at the end of '%s\\%c'",im->gdes[i].legend,prt_fctn);
1513 return -1;
1515 }
1517 /* remove exess space */
1518 if ( prt_fctn == 'n' ){
1519 prt_fctn='l';
1520 }
1522 while (prt_fctn=='g' &&
1523 leg_cc > 0 &&
1524 im->gdes[i].legend[leg_cc-1]==' '){
1525 leg_cc--;
1526 im->gdes[i].legend[leg_cc]='\0';
1527 }
1528 if (leg_cc != 0 ){
1529 legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1531 if (fill > 0){
1532 /* no interleg space if string ends in \g */
1533 fill += legspace[i];
1534 }
1535 fill += gfx_get_text_width(im->canvas, fill+border,
1536 im->text_prop[TEXT_PROP_LEGEND].font,
1537 im->text_prop[TEXT_PROP_LEGEND].size,
1538 im->tabwidth,
1539 im->gdes[i].legend, 0);
1540 leg_c++;
1541 } else {
1542 legspace[i]=0;
1543 }
1544 /* who said there was a special tag ... ?*/
1545 if (prt_fctn=='g') {
1546 prt_fctn = '\0';
1547 }
1548 if (prt_fctn == '\0') {
1549 if (i == im->gdes_c -1 ) prt_fctn ='l';
1551 /* is it time to place the legends ? */
1552 if (fill > im->ximg - 2*border){
1553 if (leg_c > 1) {
1554 /* go back one */
1555 i--;
1556 fill = fill_last;
1557 leg_c--;
1558 prt_fctn = 'j';
1559 } else {
1560 prt_fctn = 'l';
1561 }
1563 }
1564 }
1567 if (prt_fctn != '\0'){
1568 leg_x = border;
1569 if (leg_c >= 2 && prt_fctn == 'j') {
1570 glue = (im->ximg - fill - 2* border) / (leg_c-1);
1571 } else {
1572 glue = 0;
1573 }
1574 if (prt_fctn =='c') leg_x = (im->ximg - fill) / 2.0;
1575 if (prt_fctn =='r') leg_x = im->ximg - fill - border;
1577 for(ii=mark;ii<=i;ii++){
1578 if(im->gdes[ii].legend[0]=='\0')
1579 continue; /* skip empty legends */
1580 im->gdes[ii].leg_x = leg_x;
1581 im->gdes[ii].leg_y = leg_y;
1582 leg_x +=
1583 gfx_get_text_width(im->canvas, leg_x,
1584 im->text_prop[TEXT_PROP_LEGEND].font,
1585 im->text_prop[TEXT_PROP_LEGEND].size,
1586 im->tabwidth,
1587 im->gdes[ii].legend, 0)
1588 + legspace[ii]
1589 + glue;
1590 }
1591 leg_y_prev = leg_y;
1592 /* only add y space if there was text on the line */
1593 if (leg_x > border || prt_fctn == 's')
1594 leg_y += im->text_prop[TEXT_PROP_LEGEND].size*1.8;
1595 if (prt_fctn == 's')
1596 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1597 fill = 0;
1598 leg_c = 0;
1599 mark = ii;
1600 }
1601 }
1602 im->yimg = leg_y_prev;
1603 /* if we did place some legends we have to add vertical space */
1604 if (leg_y != im->yimg){
1605 im->yimg += im->text_prop[TEXT_PROP_LEGEND].size*1.8;
1606 }
1607 free(legspace);
1608 }
1609 return 0;
1610 }
1612 /* create a grid on the graph. it determines what to do
1613 from the values of xsize, start and end */
1615 /* the xaxis labels are determined from the number of seconds per pixel
1616 in the requested graph */
1620 int
1621 calc_horizontal_grid(image_desc_t *im)
1622 {
1623 double range;
1624 double scaledrange;
1625 int pixel,i;
1626 int gridind=0;
1627 int decimals, fractionals;
1629 im->ygrid_scale.labfact=2;
1630 range = im->maxval - im->minval;
1631 scaledrange = range / im->magfact;
1633 /* does the scale of this graph make it impossible to put lines
1634 on it? If so, give up. */
1635 if (isnan(scaledrange)) {
1636 return 0;
1637 }
1639 /* find grid spaceing */
1640 pixel=1;
1641 if(isnan(im->ygridstep)){
1642 if(im->extra_flags & ALTYGRID) {
1643 /* find the value with max number of digits. Get number of digits */
1644 decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))*im->viewfactor/im->magfact));
1645 if(decimals <= 0) /* everything is small. make place for zero */
1646 decimals = 1;
1648 im->ygrid_scale.gridstep = pow((double)10, floor(log10(range*im->viewfactor/im->magfact)))/im->viewfactor*im->magfact;
1650 if(im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1651 im->ygrid_scale.gridstep = 0.1;
1652 /* should have at least 5 lines but no more then 15 */
1653 if(range/im->ygrid_scale.gridstep < 5)
1654 im->ygrid_scale.gridstep /= 10;
1655 if(range/im->ygrid_scale.gridstep > 15)
1656 im->ygrid_scale.gridstep *= 10;
1657 if(range/im->ygrid_scale.gridstep > 5) {
1658 im->ygrid_scale.labfact = 1;
1659 if(range/im->ygrid_scale.gridstep > 8)
1660 im->ygrid_scale.labfact = 2;
1661 }
1662 else {
1663 im->ygrid_scale.gridstep /= 5;
1664 im->ygrid_scale.labfact = 5;
1665 }
1666 fractionals = floor(log10(im->ygrid_scale.gridstep*(double)im->ygrid_scale.labfact*im->viewfactor/im->magfact));
1667 if(fractionals < 0) { /* small amplitude. */
1668 int len = decimals - fractionals + 1;
1669 if (im->unitslength < len+2) im->unitslength = len+2;
1670 sprintf(im->ygrid_scale.labfmt, "%%%d.%df%s", len, -fractionals,(im->symbol != ' ' ? " %c" : ""));
1671 } else {
1672 int len = decimals + 1;
1673 if (im->unitslength < len+2) im->unitslength = len+2;
1674 sprintf(im->ygrid_scale.labfmt, "%%%d.0f%s", len, ( im->symbol != ' ' ? " %c" : "" ));
1675 }
1676 }
1677 else {
1678 for(i=0;ylab[i].grid > 0;i++){
1679 pixel = im->ysize / (scaledrange / ylab[i].grid);
1680 gridind = i;
1681 if (pixel > 7)
1682 break;
1683 }
1685 for(i=0; i<4;i++) {
1686 if (pixel * ylab[gridind].lfac[i] >= 2.5 * im->text_prop[TEXT_PROP_AXIS].size) {
1687 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1688 break;
1689 }
1690 }
1692 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1693 }
1694 } else {
1695 im->ygrid_scale.gridstep = im->ygridstep;
1696 im->ygrid_scale.labfact = im->ylabfact;
1697 }
1698 return 1;
1699 }
1701 int draw_horizontal_grid(image_desc_t *im)
1702 {
1703 int i;
1704 double scaledstep;
1705 char graph_label[100];
1706 int nlabels=0;
1707 double X0=im->xorigin;
1708 double X1=im->xorigin+im->xsize;
1710 int sgrid = (int)( im->minval / im->ygrid_scale.gridstep - 1);
1711 int egrid = (int)( im->maxval / im->ygrid_scale.gridstep + 1);
1712 double MaxY;
1713 double second_axis_magfact = 0;
1714 char *second_axis_symb = "";
1715 scaledstep = im->ygrid_scale.gridstep/(double)im->magfact*(double)im->viewfactor;
1716 MaxY = scaledstep*(double)egrid;
1717 for (i = sgrid; i <= egrid; i++){
1718 double Y0=ytr(im,im->ygrid_scale.gridstep*i);
1719 double YN=ytr(im,im->ygrid_scale.gridstep*(i+1));
1720 if ( floor(Y0+0.5) >= im->yorigin-im->ysize
1721 && floor(Y0+0.5) <= im->yorigin){
1722 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
1723 with the chosen settings. Add a label if required by settings, or if
1724 there is only one label so far and the next grid line is out of bounds. */
1725 if(i % im->ygrid_scale.labfact == 0 || ( nlabels==1 && (YN < im->yorigin-im->ysize || YN > im->yorigin) )){
1726 if (im->symbol == ' ') {
1727 if(im->extra_flags & ALTYGRID) {
1728 sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*(double)i);
1729 } else {
1730 if(MaxY < 10) {
1731 sprintf(graph_label,"%4.1f",scaledstep*(double)i);
1732 } else {
1733 sprintf(graph_label,"%4.0f",scaledstep*(double)i);
1734 }
1735 }
1736 }else {
1737 char sisym = ( i == 0 ? ' ' : im->symbol);
1738 if(im->extra_flags & ALTYGRID) {
1739 sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*(double)i,sisym);
1740 } else {
1741 if(MaxY < 10){
1742 sprintf(graph_label,"%4.1f %c",scaledstep*(double)i, sisym);
1743 } else {
1744 sprintf(graph_label,"%4.0f %c",scaledstep*(double)i, sisym);
1745 }
1746 }
1747 }
1748 nlabels++;
1749 if (im->second_axis_scale != 0){
1750 char graph_label_right[100];
1751 double sval = im->ygrid_scale.gridstep*(double)i/im->second_axis_scale+im->second_axis_shift;
1752 if (!second_axis_magfact){
1753 double dummy = im->ygrid_scale.gridstep*(double)(sgrid+egrid)/2.0/im->second_axis_scale+im->second_axis_shift;
1754 auto_scale(im,&dummy,&second_axis_symb,&second_axis_magfact);
1755 }
1756 sval /= second_axis_magfact;
1758 if(MaxY < 10) {
1759 sprintf(graph_label_right,"%5.1f %s",sval,second_axis_symb);
1760 } else {
1761 sprintf(graph_label_right,"%5.0f %s",sval,second_axis_symb);
1762 }
1763 gfx_new_text ( im->canvas,
1764 X1+7, Y0,
1765 im->graph_col[GRC_FONT],
1766 im->text_prop[TEXT_PROP_AXIS].font,
1767 im->text_prop[TEXT_PROP_AXIS].size,
1768 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
1769 graph_label_right );
1770 }
1772 gfx_new_text ( im->canvas,
1773 X0-im->text_prop[TEXT_PROP_AXIS].size, Y0,
1774 im->graph_col[GRC_FONT],
1775 im->text_prop[TEXT_PROP_AXIS].font,
1776 im->text_prop[TEXT_PROP_AXIS].size,
1777 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1778 graph_label );
1779 gfx_new_dashed_line ( im->canvas,
1780 X0-2,Y0,
1781 X1+2,Y0,
1782 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1783 im->grid_dash_on, im->grid_dash_off);
1785 } else if (!(im->extra_flags & NOMINOR)) {
1786 gfx_new_dashed_line ( im->canvas,
1787 X0-1,Y0,
1788 X1+1,Y0,
1789 GRIDWIDTH, im->graph_col[GRC_GRID],
1790 im->grid_dash_on, im->grid_dash_off);
1792 }
1793 }
1794 }
1795 return 1;
1796 }
1798 /* this is frexp for base 10 */
1799 double frexp10(double, double *);
1800 double frexp10(double x, double *e) {
1801 double mnt;
1802 int iexp;
1804 iexp = floor(log(fabs(x)) / log(10));
1805 mnt = x / pow(10.0, iexp);
1806 if(mnt >= 10.0) {
1807 iexp++;
1808 mnt = x / pow(10.0, iexp);
1809 }
1810 *e = iexp;
1811 return mnt;
1812 }
1814 /* logaritmic horizontal grid */
1815 int
1816 horizontal_log_grid(image_desc_t *im)
1817 {
1818 double yloglab[][10] = {
1819 {1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
1820 {1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
1821 {1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0},
1822 {1.0, 2.0, 4.0, 6.0, 8.0, 10., 0.0, 0.0, 0.0, 0.0},
1823 {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.},
1824 {0,0,0,0,0, 0,0,0,0,0} /* last line */ };
1826 int i, j, val_exp, min_exp;
1827 double nex; /* number of decades in data */
1828 double logscale; /* scale in logarithmic space */
1829 int exfrac = 1; /* decade spacing */
1830 int mid = -1; /* row in yloglab for major grid */
1831 double mspac; /* smallest major grid spacing (pixels) */
1832 int flab; /* first value in yloglab to use */
1833 double value, tmp, pre_value;
1834 double X0,X1,Y0;
1835 char graph_label[100];
1837 nex = log10(im->maxval / im->minval);
1838 logscale = im->ysize / nex;
1840 /* major spacing for data with high dynamic range */
1841 while(logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
1842 if(exfrac == 1) exfrac = 3;
1843 else exfrac += 3;
1844 }
1846 /* major spacing for less dynamic data */
1847 do {
1848 /* search best row in yloglab */
1849 mid++;
1850 for(i = 0; yloglab[mid][i + 1] < 10.0; i++);
1851 mspac = logscale * log10(10.0 / yloglab[mid][i]);
1852 } while(mspac > 2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0);
1853 if(mid) mid--;
1855 /* find first value in yloglab */
1856 for(flab = 0; yloglab[mid][flab] < 10 && frexp10(im->minval, &tmp) > yloglab[mid][flab] ; flab++);
1857 if(yloglab[mid][flab] == 10.0) {
1858 tmp += 1.0;
1859 flab = 0;
1860 }
1861 val_exp = tmp;
1862 if(val_exp % exfrac) val_exp += abs(-val_exp % exfrac);
1864 X0=im->xorigin;
1865 X1=im->xorigin+im->xsize;
1867 /* draw grid */
1868 pre_value = DNAN;
1869 while(1) {
1871 value = yloglab[mid][flab] * pow(10.0, val_exp);
1872 if ( AlmostEqual2sComplement(value,pre_value,4) ) break; /* it seems we are not converging */
1874 pre_value = value;
1876 Y0 = ytr(im, value);
1877 if(floor(Y0+0.5) <= im->yorigin - im->ysize) break;
1879 /* major grid line */
1880 gfx_new_dashed_line ( im->canvas,
1881 X0-2,Y0,
1882 X1+2,Y0,
1883 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1884 im->grid_dash_on, im->grid_dash_off);
1886 /* label */
1887 if (im->extra_flags & FORCE_UNITS_SI) {
1888 int scale;
1889 double pvalue;
1890 char symbol;
1892 scale = floor(val_exp / 3.0);
1893 if( value >= 1.0 ) pvalue = pow(10.0, val_exp % 3);
1894 else pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
1895 pvalue *= yloglab[mid][flab];
1897 if ( ((scale+si_symbcenter) < (int)sizeof(si_symbol)) &&
1898 ((scale+si_symbcenter) >= 0) )
1899 symbol = si_symbol[scale+si_symbcenter];
1900 else
1901 symbol = '?';
1903 sprintf(graph_label,"%3.0f %c", pvalue, symbol);
1904 } else {
1905 sprintf(graph_label,"%3.0e", value);
1906 }
1907 if (im->second_axis_scale != 0){
1908 char graph_label_right[100];
1909 if (im->extra_flags & FORCE_UNITS_SI) {
1910 double sval = value/im->second_axis_scale+im->second_axis_shift;
1911 double mfac = 1;
1912 char *symb = "";
1913 auto_scale(im,&sval,&symb,&mfac);
1914 sprintf(graph_label_right,"%4.0f %s", sval,symb);
1915 }
1916 else {
1917 sprintf(graph_label_right,"%3.0e", value/im->second_axis_scale+im->second_axis_shift);
1918 }
1919 gfx_new_text ( im->canvas,
1920 X1+7, Y0,
1921 im->graph_col[GRC_FONT],
1922 im->text_prop[TEXT_PROP_AXIS].font,
1923 im->text_prop[TEXT_PROP_AXIS].size,
1924 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
1925 graph_label_right );
1926 }
1928 gfx_new_text ( im->canvas,
1929 X0-im->text_prop[TEXT_PROP_AXIS].size, Y0,
1930 im->graph_col[GRC_FONT],
1931 im->text_prop[TEXT_PROP_AXIS].font,
1932 im->text_prop[TEXT_PROP_AXIS].size,
1933 im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1934 graph_label );
1936 /* minor grid */
1937 if(mid < 4 && exfrac == 1) {
1938 /* find first and last minor line behind current major line
1939 * i is the first line and j tha last */
1940 if(flab == 0) {
1941 min_exp = val_exp - 1;
1942 for(i = 1; yloglab[mid][i] < 10.0; i++);
1943 i = yloglab[mid][i - 1] + 1;
1944 j = 10;
1945 }
1946 else {
1947 min_exp = val_exp;
1948 i = yloglab[mid][flab - 1] + 1;
1949 j = yloglab[mid][flab];
1950 }
1952 /* draw minor lines below current major line */
1953 for(; i < j; i++) {
1955 value = i * pow(10.0, min_exp);
1956 if(value < im->minval) continue;
1958 Y0 = ytr(im, value);
1959 if(floor(Y0+0.5) <= im->yorigin - im->ysize) break;
1961 /* draw lines */
1962 gfx_new_dashed_line ( im->canvas,
1963 X0-1,Y0,
1964 X1+1,Y0,
1965 GRIDWIDTH, im->graph_col[GRC_GRID],
1966 im->grid_dash_on, im->grid_dash_off);
1967 }
1968 }
1969 else if(exfrac > 1) {
1970 for(i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
1971 value = pow(10.0, i);
1972 if(value < im->minval) continue;
1974 Y0 = ytr(im, value);
1975 if(floor(Y0+0.5) <= im->yorigin - im->ysize) break;
1977 /* draw lines */
1978 gfx_new_dashed_line ( im->canvas,
1979 X0-1,Y0,
1980 X1+1,Y0,
1981 GRIDWIDTH, im->graph_col[GRC_GRID],
1982 im->grid_dash_on, im->grid_dash_off);
1983 }
1984 }
1986 /* next decade */
1987 if(yloglab[mid][++flab] == 10.0) {
1988 flab = 0;
1989 val_exp += exfrac;
1990 }
1991 }
1993 /* draw minor lines after highest major line */
1994 if(mid < 4 && exfrac == 1) {
1995 /* find first and last minor line below current major line
1996 * i is the first line and j tha last */
1997 if(flab == 0) {
1998 min_exp = val_exp - 1;
1999 for(i = 1; yloglab[mid][i] < 10.0; i++);
2000 i = yloglab[mid][i - 1] + 1;
2001 j = 10;
2002 }
2003 else {
2004 min_exp = val_exp;
2005 i = yloglab[mid][flab - 1] + 1;
2006 j = yloglab[mid][flab];
2007 }
2009 /* draw minor lines below current major line */
2010 for(; i < j; i++) {
2012 value = i * pow(10.0, min_exp);
2013 if(value < im->minval) continue;
2015 Y0 = ytr(im, value);
2016 if(floor(Y0+0.5) <= im->yorigin - im->ysize) break;
2018 /* draw lines */
2019 gfx_new_dashed_line ( im->canvas,
2020 X0-1,Y0,
2021 X1+1,Y0,
2022 GRIDWIDTH, im->graph_col[GRC_GRID],
2023 im->grid_dash_on, im->grid_dash_off);
2024 }
2025 }
2026 /* fancy minor gridlines */
2027 else if(exfrac > 1) {
2028 for(i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2029 value = pow(10.0, i);
2030 if(value < im->minval) continue;
2032 Y0 = ytr(im, value);
2033 if(floor(Y0+0.5) <= im->yorigin - im->ysize) break;
2035 /* draw lines */
2036 gfx_new_dashed_line ( im->canvas,
2037 X0-1,Y0,
2038 X1+1,Y0,
2039 GRIDWIDTH, im->graph_col[GRC_GRID],
2040 im->grid_dash_on, im->grid_dash_off);
2041 }
2042 }
2044 return 1;
2045 }
2048 void
2049 vertical_grid(
2050 image_desc_t *im )
2051 {
2052 int xlab_sel; /* which sort of label and grid ? */
2053 time_t ti, tilab, timajor;
2054 long factor;
2055 char graph_label[100];
2056 double X0,Y0,Y1; /* points for filled graph and more*/
2057 struct tm tm;
2059 /* the type of time grid is determined by finding
2060 the number of seconds per pixel in the graph */
2063 if(im->xlab_user.minsec == -1){
2064 factor=(im->end - im->start)/im->xsize;
2065 xlab_sel=0;
2066 while ( xlab[xlab_sel+1].minsec != -1
2067 && xlab[xlab_sel+1].minsec <= factor) { xlab_sel++; } /* pick the last one */
2068 while ( xlab[xlab_sel-1].minsec == xlab[xlab_sel].minsec
2069 && xlab[xlab_sel].length > (im->end - im->start)) { xlab_sel--; } /* go back to the smallest size */
2070 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2071 im->xlab_user.gridst = xlab[xlab_sel].gridst;
2072 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2073 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2074 im->xlab_user.labtm = xlab[xlab_sel].labtm;
2075 im->xlab_user.labst = xlab[xlab_sel].labst;
2076 im->xlab_user.precis = xlab[xlab_sel].precis;
2077 im->xlab_user.stst = xlab[xlab_sel].stst;
2078 }
2080 /* y coords are the same for every line ... */
2081 Y0 = im->yorigin;
2082 Y1 = im->yorigin-im->ysize;
2085 /* paint the minor grid */
2086 if (!(im->extra_flags & NOMINOR))
2087 {
2088 for(ti = find_first_time(im->start,
2089 im->xlab_user.gridtm,
2090 im->xlab_user.gridst),
2091 timajor = find_first_time(im->start,
2092 im->xlab_user.mgridtm,
2093 im->xlab_user.mgridst);
2094 ti < im->end;
2095 ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
2096 ){
2097 /* are we inside the graph ? */
2098 if (ti < im->start || ti > im->end) continue;
2099 while (timajor < ti) {
2100 timajor = find_next_time(timajor,
2101 im->xlab_user.mgridtm, im->xlab_user.mgridst);
2102 }
2103 if (ti == timajor) continue; /* skip as falls on major grid line */
2104 X0 = xtr(im,ti);
2105 gfx_new_dashed_line(im->canvas,X0,Y0+1, X0,Y1-1,GRIDWIDTH,
2106 im->graph_col[GRC_GRID],
2107 im->grid_dash_on, im->grid_dash_off);
2109 }
2110 }
2112 /* paint the major grid */
2113 for(ti = find_first_time(im->start,
2114 im->xlab_user.mgridtm,
2115 im->xlab_user.mgridst);
2116 ti < im->end;
2117 ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
2118 ){
2119 /* are we inside the graph ? */
2120 if (ti < im->start || ti > im->end) continue;
2121 X0 = xtr(im,ti);
2122 gfx_new_dashed_line(im->canvas,X0,Y0+3, X0,Y1-2,MGRIDWIDTH,
2123 im->graph_col[GRC_MGRID],
2124 im->grid_dash_on, im->grid_dash_off);
2126 }
2127 /* paint the labels below the graph */
2128 for(ti = find_first_time(im->start - im->xlab_user.precis/2,
2129 im->xlab_user.labtm,
2130 im->xlab_user.labst);
2131 ti <= im->end - im->xlab_user.precis/2;
2132 ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
2133 ){
2134 tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
2135 /* are we inside the graph ? */
2136 if (tilab < im->start || tilab > im->end) continue;
2138 #if HAVE_STRFTIME
2139 localtime_r(&tilab, &tm);
2140 strftime(graph_label,99,im->xlab_user.stst, &tm);
2141 #else
2142 # error "your libc has no strftime I guess we'll abort the exercise here."
2143 #endif
2144 gfx_new_text ( im->canvas,
2145 xtr(im,tilab), Y0+im->text_prop[TEXT_PROP_AXIS].size*1.4+5,
2146 im->graph_col[GRC_FONT],
2147 im->text_prop[TEXT_PROP_AXIS].font,
2148 im->text_prop[TEXT_PROP_AXIS].size,
2149 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_BOTTOM,
2150 graph_label );
2152 }
2154 }
2157 void
2158 axis_paint(
2159 image_desc_t *im
2160 )
2161 {
2162 /* draw x and y axis */
2163 /* gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2164 im->xorigin+im->xsize,im->yorigin-im->ysize,
2165 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2167 gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2168 im->xorigin+im->xsize,im->yorigin-im->ysize,
2169 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2171 gfx_new_line ( im->canvas, im->xorigin-4,im->yorigin,
2172 im->xorigin+im->xsize+4,im->yorigin,
2173 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2175 gfx_new_line ( im->canvas, im->xorigin,im->yorigin+4,
2176 im->xorigin,im->yorigin-im->ysize-4,
2177 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2180 /* arrow for X and Y axis direction */
2181 gfx_new_area ( im->canvas,
2182 im->xorigin+im->xsize+2, im->yorigin-2,
2183 im->xorigin+im->xsize+2, im->yorigin+3,
2184 im->xorigin+im->xsize+7, im->yorigin+0.5, /* LINEOFFSET */
2185 im->graph_col[GRC_ARROW]);
2187 gfx_new_area ( im->canvas,
2188 im->xorigin-2, im->yorigin-im->ysize-2,
2189 im->xorigin+3, im->yorigin-im->ysize-2,
2190 im->xorigin+0.5, im->yorigin-im->ysize-7, /* LINEOFFSET */
2191 im->graph_col[GRC_ARROW]);
2193 if (im->second_axis_scale != 0){
2194 gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin+4,
2195 im->xorigin+im->xsize,im->yorigin-im->ysize-4,
2196 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2197 gfx_new_area ( im->canvas,
2198 im->xorigin+im->xsize-2, im->yorigin-im->ysize-2,
2199 im->xorigin+im->xsize+3, im->yorigin-im->ysize-2,
2200 im->xorigin+im->xsize+0.5, im->yorigin-im->ysize-7, /* LINEOFFSET */
2201 im->graph_col[GRC_ARROW]);
2202 }
2203 }
2205 void
2206 grid_paint(image_desc_t *im)
2207 {
2208 long i;
2209 int res=0;
2210 double X0,Y0; /* points for filled graph and more*/
2211 gfx_node_t *node;
2213 /* draw 3d border */
2214 node = gfx_new_area (im->canvas, 0,im->yimg,
2215 2,im->yimg-2,
2216 2,2,im->graph_col[GRC_SHADEA]);
2217 gfx_add_point( node , im->ximg - 2, 2 );
2218 gfx_add_point( node , im->ximg, 0 );
2219 gfx_add_point( node , 0,0 );
2220 /* gfx_add_point( node , 0,im->yimg ); */
2222 node = gfx_new_area (im->canvas, 2,im->yimg-2,
2223 im->ximg-2,im->yimg-2,
2224 im->ximg - 2, 2,
2225 im->graph_col[GRC_SHADEB]);
2226 gfx_add_point( node , im->ximg,0);
2227 gfx_add_point( node , im->ximg,im->yimg);
2228 gfx_add_point( node , 0,im->yimg);
2229 /* gfx_add_point( node , 0,im->yimg ); */
2232 if (im->draw_x_grid == 1 )
2233 vertical_grid(im);
2235 if (im->draw_y_grid == 1){
2236 if(im->logarithmic){
2237 res = horizontal_log_grid(im);
2238 } else {
2239 res = draw_horizontal_grid(im);
2240 }
2242 /* dont draw horizontal grid if there is no min and max val */
2243 if (! res ) {
2244 char *nodata = "No Data found";
2245 gfx_new_text(im->canvas,im->ximg/2, (2*im->yorigin-im->ysize) / 2,
2246 im->graph_col[GRC_FONT],
2247 im->text_prop[TEXT_PROP_AXIS].font,
2248 im->text_prop[TEXT_PROP_AXIS].size,
2249 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
2250 nodata );
2251 }
2252 }
2254 /* yaxis unit description */
2255 if (im->ylegend[0] != '\0'){
2256 gfx_new_text( im->canvas,
2257 10, (im->yorigin - im->ysize/2),
2258 im->graph_col[GRC_FONT],
2259 im->text_prop[TEXT_PROP_UNIT].font,
2260 im->text_prop[TEXT_PROP_UNIT].size, im->tabwidth,
2261 RRDGRAPH_YLEGEND_ANGLE,
2262 GFX_H_LEFT, GFX_V_CENTER,
2263 im->ylegend);
2264 }
2265 if (im->second_axis_legend[0] != '\0'){
2266 double Xylabel=gfx_get_text_width(im->canvas, 0,
2267 im->text_prop[TEXT_PROP_AXIS].font,
2268 im->text_prop[TEXT_PROP_AXIS].size,
2269 im->tabwidth,
2270 "0", 0) * im->unitslength
2271 + im->text_prop[TEXT_PROP_UNIT].size *2;
2272 gfx_new_text( im->canvas,
2273 im->xorigin+im->xsize+Xylabel+4, (im->yorigin - im->ysize/2),
2274 im->graph_col[GRC_FONT],
2275 im->text_prop[TEXT_PROP_UNIT].font,
2276 im->text_prop[TEXT_PROP_UNIT].size, im->tabwidth,
2277 RRDGRAPH_YLEGEND_ANGLE,
2278 GFX_H_LEFT, GFX_V_CENTER,
2279 im->second_axis_legend);
2280 }
2281 /* graph title */
2282 gfx_new_text( im->canvas,
2283 im->ximg/2, im->text_prop[TEXT_PROP_TITLE].size*1.3+4,
2284 im->graph_col[GRC_FONT],
2285 im->text_prop[TEXT_PROP_TITLE].font,
2286 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
2287 GFX_H_CENTER, GFX_V_CENTER,
2288 im->title);
2289 /* rrdtool 'logo' */
2290 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
2291 gfx_new_text( im->canvas,
2292 im->ximg-7, 7,
2293 ( im->graph_col[GRC_FONT] & 0xffffff00 ) | 0x00000044,
2294 im->text_prop[TEXT_PROP_AXIS].font,
2295 5.5, im->tabwidth, 270,
2296 GFX_H_RIGHT, GFX_V_TOP,
2297 "RRDTOOL / TOBI OETIKER");
2298 }
2299 /* graph watermark */
2300 if(im->watermark[0] != '\0') {
2301 gfx_new_text( im->canvas,
2302 im->ximg/2, im->yimg-6,
2303 ( im->graph_col[GRC_FONT] & 0xffffff00 ) | 0x00000044,
2304 im->text_prop[TEXT_PROP_AXIS].font,
2305 5.5, im->tabwidth, 0,
2306 GFX_H_CENTER, GFX_V_BOTTOM,
2307 im->watermark);
2308 }
2310 /* graph labels */
2311 if( !(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH) ) {
2312 for(i=0;i<im->gdes_c;i++){
2313 if(im->gdes[i].legend[0] =='\0')
2314 continue;
2316 /* im->gdes[i].leg_y is the bottom of the legend */
2317 X0 = im->gdes[i].leg_x;
2318 Y0 = im->gdes[i].leg_y;
2319 gfx_new_text ( im->canvas, X0, Y0,
2320 im->graph_col[GRC_FONT],
2321 im->text_prop[TEXT_PROP_LEGEND].font,
2322 im->text_prop[TEXT_PROP_LEGEND].size,
2323 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
2324 im->gdes[i].legend );
2325 /* The legend for GRAPH items starts with "M " to have
2326 enough space for the box */
2327 if ( im->gdes[i].gf != GF_PRINT &&
2328 im->gdes[i].gf != GF_GPRINT &&
2329 im->gdes[i].gf != GF_COMMENT) {
2330 int boxH, boxV;
2332 boxH = gfx_get_text_width(im->canvas, 0,
2333 im->text_prop[TEXT_PROP_LEGEND].font,
2334 im->text_prop[TEXT_PROP_LEGEND].size,
2335 im->tabwidth,"o", 0) * 1.2;
2336 boxV = boxH*1.1;
2338 /* make sure transparent colors show up the same way as in the graph */
2339 node = gfx_new_area(im->canvas,
2340 X0,Y0-boxV,
2341 X0,Y0,
2342 X0+boxH,Y0,
2343 im->graph_col[GRC_BACK]);
2344 gfx_add_point ( node, X0+boxH, Y0-boxV );
2346 node = gfx_new_area(im->canvas,
2347 X0,Y0-boxV,
2348 X0,Y0,
2349 X0+boxH,Y0,
2350 im->gdes[i].col);
2351 gfx_add_point ( node, X0+boxH, Y0-boxV );
2352 node = gfx_new_line(im->canvas,
2353 X0,Y0-boxV,
2354 X0,Y0,
2355 1.0,im->graph_col[GRC_FRAME]);
2356 gfx_add_point(node,X0+boxH,Y0);
2357 gfx_add_point(node,X0+boxH,Y0-boxV);
2358 gfx_close_path(node);
2359 }
2360 }
2361 }
2362 }
2365 /*****************************************************
2366 * lazy check make sure we rely need to create this graph
2367 *****************************************************/
2369 int lazy_check(image_desc_t *im){
2370 FILE *fd = NULL;
2371 int size = 1;
2372 struct stat imgstat;
2374 if (im->lazy == 0) return 0; /* no lazy option */
2375 if (stat(im->graphfile,&imgstat) != 0)
2376 return 0; /* can't stat */
2377 /* one pixel in the existing graph is more then what we would
2378 change here ... */
2379 if (time(NULL) - imgstat.st_mtime >
2380 (im->end - im->start) / im->xsize)
2381 return 0;
2382 if ((fd = fopen(im->graphfile,"rb")) == NULL)
2383 return 0; /* the file does not exist */
2384 switch (im->canvas->imgformat) {
2385 case IF_PNG:
2386 size = PngSize(fd,&(im->ximg),&(im->yimg));
2387 break;
2388 default:
2389 size = 1;
2390 }
2391 fclose(fd);
2392 return size;
2393 }
2395 #ifdef WITH_PIECHART
2396 void
2397 pie_part(image_desc_t *im, gfx_color_t color,
2398 double PieCenterX, double PieCenterY, double Radius,
2399 double startangle, double endangle)
2400 {
2401 gfx_node_t *node;
2402 double angle;
2403 double step=M_PI/50; /* Number of iterations for the circle;
2404 ** 10 is definitely too low, more than
2405 ** 50 seems to be overkill
2406 */
2408 /* Strange but true: we have to work clockwise or else
2409 ** anti aliasing nor transparency don't work.
2410 **
2411 ** This test is here to make sure we do it right, also
2412 ** this makes the for...next loop more easy to implement.
2413 ** The return will occur if the user enters a negative number
2414 ** (which shouldn't be done according to the specs) or if the
2415 ** programmers do something wrong (which, as we all know, never
2416 ** happens anyway :)
2417 */
2418 if (endangle<startangle) return;
2420 /* Hidden feature: Radius decreases each full circle */
2421 angle=startangle;
2422 while (angle>=2*M_PI) {
2423 angle -= 2*M_PI;
2424 Radius *= 0.8;
2425 }
2427 node=gfx_new_area(im->canvas,
2428 PieCenterX+sin(startangle)*Radius,
2429 PieCenterY-cos(startangle)*Radius,
2430 PieCenterX,
2431 PieCenterY,
2432 PieCenterX+sin(endangle)*Radius,
2433 PieCenterY-cos(endangle)*Radius,
2434 color);
2435 for (angle=endangle;angle-startangle>=step;angle-=step) {
2436 gfx_add_point(node,
2437 PieCenterX+sin(angle)*Radius,
2438 PieCenterY-cos(angle)*Radius );
2439 }
2440 }
2442 #endif
2444 int
2445 graph_size_location(image_desc_t *im, int elements
2447 #ifdef WITH_PIECHART
2448 , int piechart
2449 #endif
2451 )
2452 {
2453 /* The actual size of the image to draw is determined from
2454 ** several sources. The size given on the command line is
2455 ** the graph area but we need more as we have to draw labels
2456 ** and other things outside the graph area
2457 */
2459 /* +-+-------------------------------------------+
2460 ** |l|.................title.....................|
2461 ** |e+--+-------------------------------+--------+
2462 ** |b| b| | |
2463 ** |a| a| | pie |
2464 ** |l| l| main graph area | chart |
2465 ** |.| .| | area |
2466 ** |t| y| | |
2467 ** |r+--+-------------------------------+--------+
2468 ** |e| | x-axis labels | |
2469 ** |v+--+-------------------------------+--------+
2470 ** | |..............legends......................|
2471 ** +-+-------------------------------------------+
2472 ** | watermark |
2473 ** +---------------------------------------------+
2474 */
2475 int Xvertical=0,
2476 Ytitle =0,
2477 Xylabel =0,
2478 Xmain =0, Ymain =0,
2479 #ifdef WITH_PIECHART
2480 Xpie =0, Ypie =0,
2481 #endif
2482 Yxlabel =0,
2483 #if 0
2484 Xlegend =0, Ylegend =0,
2485 #endif
2486 Xspacing =15, Yspacing =15,
2488 Ywatermark =4;
2490 if (im->extra_flags & ONLY_GRAPH) {
2491 im->xorigin =0;
2492 im->ximg = im->xsize;
2493 im->yimg = im->ysize;
2494 im->yorigin = im->ysize;
2495 ytr(im,DNAN);
2496 return 0;
2497 }
2499 if (im->ylegend[0] != '\0' ) {
2500 Xvertical = im->text_prop[TEXT_PROP_UNIT].size *2;
2501 }
2504 if (im->title[0] != '\0') {
2505 /* The title is placed "inbetween" two text lines so it
2506 ** automatically has some vertical spacing. The horizontal
2507 ** spacing is added here, on each side.
2508 */
2509 /* don't care for the with of the title
2510 Xtitle = gfx_get_text_width(im->canvas, 0,
2511 im->text_prop[TEXT_PROP_TITLE].font,
2512 im->text_prop[TEXT_PROP_TITLE].size,
2513 im->tabwidth,
2514 im->title, 0) + 2*Xspacing; */
2515 Ytitle = im->text_prop[TEXT_PROP_TITLE].size*2.6+10;
2516 }
2518 if (elements) {
2519 Xmain=im->xsize;
2520 Ymain=im->ysize;
2521 if (im->draw_x_grid) {
2522 Yxlabel=im->text_prop[TEXT_PROP_AXIS].size *2.5;
2523 }
2524 if (im->draw_y_grid || im->forceleftspace ) {
2525 Xylabel=gfx_get_text_width(im->canvas, 0,
2526 im->text_prop[TEXT_PROP_AXIS].font,
2527 im->text_prop[TEXT_PROP_AXIS].size,
2528 im->tabwidth,
2529 "0", 0) * im->unitslength;
2530 }
2531 }
2533 #ifdef WITH_PIECHART
2534 if (piechart) {
2535 im->piesize=im->xsize<im->ysize?im->xsize:im->ysize;
2536 Xpie=im->piesize;
2537 Ypie=im->piesize;
2538 }
2539 #endif
2541 /* Now calculate the total size. Insert some spacing where
2542 desired. im->xorigin and im->yorigin need to correspond
2543 with the lower left corner of the main graph area or, if
2544 this one is not set, the imaginary box surrounding the
2545 pie chart area. */
2547 /* The legend width cannot yet be determined, as a result we
2548 ** have problems adjusting the image to it. For now, we just
2549 ** forget about it at all; the legend will have to fit in the
2550 ** size already allocated.
2551 */
2552 im->ximg = Xylabel + Xmain + 2 * Xspacing;
2554 if (im->second_axis_scale != 0){
2555 im->ximg += Xylabel + Xspacing;
2556 }
2557 if (im->extra_flags & NO_RRDTOOL_TAG){
2558 im->ximg -= Xspacing;
2559 }
2561 #ifdef WITH_PIECHART
2562 im->ximg += Xpie;
2563 #endif
2565 if (Xmain) im->ximg += Xspacing;
2566 #ifdef WITH_PIECHART
2567 if (Xpie) im->ximg += Xspacing;
2568 #endif
2570 im->xorigin = Xspacing + Xylabel;
2572 /* the length of the title should not influence with width of the graph
2573 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2575 if (Xvertical) { /* unit description */
2576 im->ximg += Xvertical;
2577 im->xorigin += Xvertical;
2578 }
2579 if (im->second_axis_legend[0] != '\0' ) {
2580 im->ximg += Xvertical;
2581 }
2583 xtr(im,0);
2585 /* The vertical size is interesting... we need to compare
2586 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with
2587 ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2588 ** in order to start even thinking about Ylegend or Ywatermark.
2589 **
2590 ** Do it in three portions: First calculate the inner part,
2591 ** then do the legend, then adjust the total height of the img,
2592 ** adding space for a watermark if one exists;
2593 */
2595 /* reserve space for main and/or pie */
2597 im->yimg = Ymain + Yxlabel;
2599 #ifdef WITH_PIECHART
2600 if (im->yimg < Ypie) im->yimg = Ypie;
2601 #endif
2603 im->yorigin = im->yimg - Yxlabel;
2605 /* reserve space for the title *or* some padding above the graph */
2606 if (Ytitle) {
2607 im->yimg += Ytitle;
2608 im->yorigin += Ytitle;
2609 } else {
2610 im->yimg += 1.5*Yspacing;
2611 im->yorigin += 1.5*Yspacing;
2612 }
2613 /* reserve space for padding below the graph */
2614 im->yimg += Yspacing;
2616 /* Determine where to place the legends onto the image.
2617 ** Adjust im->yimg to match the space requirements.
2618 */
2619 if(leg_place(im)==-1)
2620 return -1;
2622 if (im->watermark[0] != '\0') {
2623 im->yimg += Ywatermark;
2624 }
2626 #if 0
2627 if (Xlegend > im->ximg) {
2628 im->ximg = Xlegend;
2629 /* reposition Pie */
2630 }
2631 #endif
2633 #ifdef WITH_PIECHART
2634 /* The pie is placed in the upper right hand corner,
2635 ** just below the title (if any) and with sufficient
2636 ** padding.
2637 */
2638 if (elements) {
2639 im->pie_x = im->ximg - Xspacing - Xpie/2;
2640 im->pie_y = im->yorigin-Ymain+Ypie/2;
2641 } else {
2642 im->pie_x = im->ximg/2;
2643 im->pie_y = im->yorigin-Ypie/2;
2644 }
2645 #endif
2647 ytr(im,DNAN);
2648 return 0;
2649 }
2651 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
2652 /* yes we are loosing precision by doing tos with floats instead of doubles
2653 but it seems more stable this way. */
2656 /* draw that picture thing ... */
2657 int
2658 graph_paint(image_desc_t *im, char ***calcpr)
2659 {
2660 int i,ii;
2661 int lazy = lazy_check(im);
2662 #ifdef WITH_PIECHART
2663 int piechart = 0;
2664 double PieStart=0.0;
2665 #endif
2666 FILE *fo;
2667 gfx_node_t *node;
2669 double areazero = 0.0;
2670 graph_desc_t *lastgdes = NULL;
2672 /* if we are lazy and there is nothing to PRINT ... quit now */
2673 if (lazy && im->prt_c==0) return 0;
2675 /* pull the data from the rrd files ... */
2677 if(data_fetch(im)==-1)
2678 return -1;
2680 /* evaluate VDEF and CDEF operations ... */
2681 if(data_calc(im)==-1)
2682 return -1;
2684 #ifdef WITH_PIECHART
2685 /* check if we need to draw a piechart */
2686 for(i=0;i<im->gdes_c;i++){
2687 if (im->gdes[i].gf == GF_PART) {
2688 piechart=1;
2689 break;
2690 }
2691 }
2692 #endif
2694 /* calculate and PRINT and GPRINT definitions. We have to do it at
2695 * this point because it will affect the length of the legends
2696 * if there are no graph elements we stop here ...
2697 * if we are lazy, try to quit ...
2698 */
2699 i=print_calc(im,calcpr);
2700 if(i<0) return -1;
2701 if(((i==0)
2702 #ifdef WITH_PIECHART
2703 &&(piechart==0)
2704 #endif
2705 ) || lazy) return 0;
2707 #ifdef WITH_PIECHART
2708 /* If there's only the pie chart to draw, signal this */
2709 if (i==0) piechart=2;
2710 #endif
2712 /* get actual drawing data and find min and max values*/
2713 if(data_proc(im)==-1)
2714 return -1;
2716 if(!im->logarithmic){si_unit(im);} /* identify si magnitude Kilo, Mega Giga ? */
2718 if(!im->rigid && ! im->logarithmic)
2719 expand_range(im); /* make sure the upper and lower limit are
2720 sensible values */
2722 if (!calc_horizontal_grid(im))
2723 return -1;
2725 if (im->gridfit)
2726 apply_gridfit(im);
2729 /**************************************************************
2730 *** Calculating sizes and locations became a bit confusing ***
2731 *** so I moved this into a separate function. ***
2732 **************************************************************/
2733 if(graph_size_location(im,i
2734 #ifdef WITH_PIECHART
2735 ,piechart
2736 #endif
2737 )==-1)
2738 return -1;
2740 /* the actual graph is created by going through the individual
2741 graph elements and then drawing them */
2743 node=gfx_new_area ( im->canvas,
2744 0, 0,
2745 0, im->yimg,
2746 im->ximg, im->yimg,
2747 im->graph_col[GRC_BACK]);
2749 gfx_add_point(node,im->ximg, 0);
2751 #ifdef WITH_PIECHART
2752 if (piechart != 2) {
2753 #endif
2754 node=gfx_new_area ( im->canvas,
2755 im->xorigin, im->yorigin,
2756 im->xorigin + im->xsize, im->yorigin,
2757 im->xorigin + im->xsize, im->yorigin-im->ysize,
2758 im->graph_col[GRC_CANVAS]);
2760 gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
2762 if (im->minval > 0.0)
2763 areazero = im->minval;
2764 if (im->maxval < 0.0)
2765 areazero = im->maxval;
2766 #ifdef WITH_PIECHART
2767 }
2768 #endif
2770 #ifdef WITH_PIECHART
2771 if (piechart) {
2772 pie_part(im,im->graph_col[GRC_CANVAS],im->pie_x,im->pie_y,im->piesize*0.5,0,2*M_PI);
2773 }
2774 #endif
2776 for(i=0;i<im->gdes_c;i++){
2777 switch(im->gdes[i].gf){
2778 case GF_CDEF:
2779 case GF_VDEF:
2780 case GF_DEF:
2781 case GF_PRINT:
2782 case GF_GPRINT:
2783 case GF_COMMENT:
2784 case GF_HRULE:
2785 case GF_VRULE:
2786 case GF_XPORT:
2787 case GF_SHIFT:
2788 break;
2789 case GF_TICK:
2790 for (ii = 0; ii < im->xsize; ii++)
2791 {
2792 if (!isnan(im->gdes[i].p_data[ii]) &&
2793 im->gdes[i].p_data[ii] != 0.0)
2794 {
2795 if (im -> gdes[i].yrule > 0 ) {
2796 gfx_new_line(im->canvas,
2797 im -> xorigin + ii, im->yorigin,
2798 im -> xorigin + ii, im->yorigin - im -> gdes[i].yrule * im -> ysize,
2799 1.0,
2800 im -> gdes[i].col );
2801 } else if ( im -> gdes[i].yrule < 0 ) {
2802 gfx_new_line(im->canvas,
2803 im -> xorigin + ii, im->yorigin - im -> ysize,
2804 im -> xorigin + ii, im->yorigin - ( 1 - im -> gdes[i].yrule ) * im -> ysize,
2805 1.0,
2806 im -> gdes[i].col );
2808 }
2809 }
2810 }
2811 break;
2812 case GF_LINE:
2813 case GF_AREA:
2814 /* fix data points at oo and -oo */
2815 for(ii=0;ii<im->xsize;ii++){
2816 if (isinf(im->gdes[i].p_data[ii])){
2817 if (im->gdes[i].p_data[ii] > 0) {
2818 im->gdes[i].p_data[ii] = im->maxval ;
2819 } else {
2820 im->gdes[i].p_data[ii] = im->minval ;
2821 }
2823 }
2824 } /* for */
2826 /* *******************************************************
2827 a ___. (a,t)
2828 | | ___
2829 ____| | | |
2830 | |___|
2831 -------|--t-1--t--------------------------------
2833 if we know the value at time t was a then
2834 we draw a square from t-1 to t with the value a.
2836 ********************************************************* */
2837 if (im->gdes[i].col != 0x0){
2838 /* GF_LINE and friend */
2839 if(im->gdes[i].gf == GF_LINE ){
2840 double last_y=0.0;
2841 node = NULL;
2842 for(ii=1;ii<im->xsize;ii++){
2843 if (isnan(im->gdes[i].p_data[ii]) || (im->slopemode==1 && isnan(im->gdes[i].p_data[ii-1]))){
2844 node = NULL;
2845 continue;
2846 }
2847 if ( node == NULL ) {
2848 last_y = ytr(im,im->gdes[i].p_data[ii]);
2849 if ( im->slopemode == 0 ){
2850 node = gfx_new_line(im->canvas,
2851 ii-1+im->xorigin,last_y,
2852 ii+im->xorigin,last_y,
2853 im->gdes[i].linewidth,
2854 im->gdes[i].col);
2855 } else {
2856 node = gfx_new_line(im->canvas,
2857 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2858 ii+im->xorigin,last_y,
2859 im->gdes[i].linewidth,
2860 im->gdes[i].col);
2861 }
2862 } else {
2863 double new_y = ytr(im,im->gdes[i].p_data[ii]);
2864 if ( im->slopemode==0 && ! AlmostEqual2sComplement(new_y,last_y,4)){
2865 gfx_add_point(node,ii-1+im->xorigin,new_y);
2866 };
2867 last_y = new_y;
2868 gfx_add_point(node,ii+im->xorigin,new_y);
2869 };
2871 }
2872 } else {
2873 int idxI=-1;
2874 double *foreY=malloc(sizeof(double)*im->xsize*2);
2875 double *foreX=malloc(sizeof(double)*im->xsize*2);
2876 double *backY=malloc(sizeof(double)*im->xsize*2);
2877 double *backX=malloc(sizeof(double)*im->xsize*2);
2878 int drawem = 0;
2879 for(ii=0;ii<=im->xsize;ii++){
2880 double ybase,ytop;
2881 if ( idxI > 0 && ( drawem != 0 || ii==im->xsize)){
2882 int cntI=1;
2883 int lastI=0;
2884 while (cntI < idxI && AlmostEqual2sComplement(foreY[lastI],foreY[cntI],4) && AlmostEqual2sComplement(foreY[lastI],foreY[cntI+1],4)){cntI++;}
2885 node = gfx_new_area(im->canvas,
2886 backX[0],backY[0],
2887 foreX[0],foreY[0],
2888 foreX[cntI],foreY[cntI], im->gdes[i].col);
2889 while (cntI < idxI) {
2890 lastI = cntI;
2891 cntI++;
2892 while ( cntI < idxI && AlmostEqual2sComplement(foreY[lastI],foreY[cntI],4) && AlmostEqual2sComplement(foreY[lastI],foreY[cntI+1],4)){cntI++;}
2893 gfx_add_point(node,foreX[cntI],foreY[cntI]);
2894 }
2895 gfx_add_point(node,backX[idxI],backY[idxI]);
2896 while (idxI > 1){
2897 lastI = idxI;
2898 idxI--;
2899 while ( idxI > 1 && AlmostEqual2sComplement(backY[lastI], backY[idxI],4) && AlmostEqual2sComplement(backY[lastI],backY[idxI-1],4)){idxI--;}
2900 gfx_add_point(node,backX[idxI],backY[idxI]);
2901 }
2902 idxI=-1;
2903 drawem = 0;
2904 }
2905 if (drawem != 0){
2906 drawem = 0;
2907 idxI=-1;
2908 }
2909 if (ii == im->xsize) break;
2911 /* keep things simple for now, just draw these bars
2912 do not try to build a big and complex area */
2915 if ( im->slopemode == 0 && ii==0){
2916 continue;
2917 }
2918 if ( isnan(im->gdes[i].p_data[ii]) ) {
2919 drawem = 1;
2920 continue;
2921 }
2922 ytop = ytr(im,im->gdes[i].p_data[ii]);
2923 if ( lastgdes && im->gdes[i].stack ) {
2924 ybase = ytr(im,lastgdes->p_data[ii]);
2925 } else {
2926 ybase = ytr(im,areazero);
2927 }
2928 if ( ybase == ytop ){
2929 drawem = 1;
2930 continue;
2931 }
2932 /* every area has to be wound clock-wise,
2933 so we have to make sur base remains base */
2934 if (ybase > ytop){
2935 double extra = ytop;
2936 ytop = ybase;
2937 ybase = extra;
2938 }
2939 if ( im->slopemode == 0 ){
2940 backY[++idxI] = ybase-0.2;
2941 backX[idxI] = ii+im->xorigin-1;
2942 foreY[idxI] = ytop+0.2;
2943 foreX[idxI] = ii+im->xorigin-1;
2944 }
2945 backY[++idxI] = ybase-0.2;
2946 backX[idxI] = ii+im->xorigin;
2947 foreY[idxI] = ytop+0.2;
2948 foreX[idxI] = ii+im->xorigin;
2949 }
2950 /* close up any remaining area */
2951 free(foreY);
2952 free(foreX);
2953 free(backY);
2954 free(backX);
2955 } /* else GF_LINE */
2956 } /* if color != 0x0 */
2957 /* make sure we do not run into trouble when stacking on NaN */
2958 for(ii=0;ii<im->xsize;ii++){
2959 if (isnan(im->gdes[i].p_data[ii])) {
2960 if (lastgdes && (im->gdes[i].stack)) {
2961 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
2962 } else {
2963 im->gdes[i].p_data[ii] = areazero;
2964 }
2965 }
2966 }
2967 lastgdes = &(im->gdes[i]);
2968 break;
2969 #ifdef WITH_PIECHART
2970 case GF_PART:
2971 if(isnan(im->gdes[i].yrule)) /* fetch variable */
2972 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2974 if (finite(im->gdes[i].yrule)) { /* even the fetched var can be NaN */
2975 pie_part(im,im->gdes[i].col,
2976 im->pie_x,im->pie_y,im->piesize*0.4,
2977 M_PI*2.0*PieStart/100.0,
2978 M_PI*2.0*(PieStart+im->gdes[i].yrule)/100.0);
2979 PieStart += im->gdes[i].yrule;
2980 }
2981 break;
2982 #endif
2983 case GF_STACK:
2984 rrd_set_error("STACK should already be turned into LINE or AREA here");
2985 return -1;
2986 break;
2988 } /* switch */
2989 }
2990 #ifdef WITH_PIECHART
2991 if (piechart==2) {
2992 im->draw_x_grid=0;
2993 im->draw_y_grid=0;
2994 }
2995 #endif
2998 /* grid_paint also does the text */
2999 if( !(im->extra_flags & ONLY_GRAPH) )
3000 grid_paint(im);
3003 if( !(im->extra_flags & ONLY_GRAPH) )
3004 axis_paint(im);
3006 /* the RULES are the last thing to paint ... */
3007 for(i=0;i<im->gdes_c;i++){
3009 switch(im->gdes[i].gf){
3010 case GF_HRULE:
3011 if(im->gdes[i].yrule >= im->minval
3012 && im->gdes[i].yrule <= im->maxval)
3013 gfx_new_line(im->canvas,
3014 im->xorigin,ytr(im,im->gdes[i].yrule),
3015 im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
3016 1.0,im->gdes[i].col);
3017 break;
3018 case GF_VRULE:
3019 if(im->gdes[i].xrule >= im->start
3020 && im->gdes[i].xrule <= im->end)
3021 gfx_new_line(im->canvas,
3022 xtr(im,im->gdes[i].xrule),im->yorigin,
3023 xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
3024 1.0,im->gdes[i].col);
3025 break;
3026 default:
3027 break;
3028 }
3029 }
3032 if (strcmp(im->graphfile,"-")==0) {
3033 fo = im->graphhandle ? im->graphhandle : stdout;
3034 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3035 /* Change translation mode for stdout to BINARY */
3036 _setmode( _fileno( fo ), O_BINARY );
3037 #endif
3038 } else {
3039 if ((fo = fopen(im->graphfile,"wb")) == NULL) {
3040 rrd_set_error("Opening '%s' for write: %s",im->graphfile,
3041 rrd_strerror(errno));
3042 return (-1);
3043 }
3044 }
3045 gfx_render (im->canvas,im->ximg,im->yimg,0x00000000,fo);
3046 if (strcmp(im->graphfile,"-") != 0)
3047 fclose(fo);
3048 return 0;
3049 }
3052 /*****************************************************
3053 * graph stuff
3054 *****************************************************/
3056 int
3057 gdes_alloc(image_desc_t *im){
3059 im->gdes_c++;
3060 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
3061 * sizeof(graph_desc_t)))==NULL){
3062 rrd_set_error("realloc graph_descs");
3063 return -1;
3064 }
3067 im->gdes[im->gdes_c-1].step=im->step;
3068 im->gdes[im->gdes_c-1].step_orig=im->step;
3069 im->gdes[im->gdes_c-1].stack=0;
3070 im->gdes[im->gdes_c-1].linewidth=0;
3071 im->gdes[im->gdes_c-1].debug=0;
3072 im->gdes[im->gdes_c-1].start=im->start;
3073 im->gdes[im->gdes_c-1].start_orig=im->start;
3074 im->gdes[im->gdes_c-1].end=im->end;
3075 im->gdes[im->gdes_c-1].end_orig=im->end;
3076 im->gdes[im->gdes_c-1].vname[0]='\0';
3077 im->gdes[im->gdes_c-1].data=NULL;
3078 im->gdes[im->gdes_c-1].ds_namv=NULL;
3079 im->gdes[im->gdes_c-1].data_first=0;
3080 im->gdes[im->gdes_c-1].p_data=NULL;
3081 im->gdes[im->gdes_c-1].rpnp=NULL;
3082 im->gdes[im->gdes_c-1].shift=0;
3083 im->gdes[im->gdes_c-1].col = 0x0;
3084 im->gdes[im->gdes_c-1].legend[0]='\0';
3085 im->gdes[im->gdes_c-1].format[0]='\0';
3086 im->gdes[im->gdes_c-1].strftm=0;
3087 im->gdes[im->gdes_c-1].rrd[0]='\0';
3088 im->gdes[im->gdes_c-1].ds=-1;
3089 im->gdes[im->gdes_c-1].cf_reduce=CF_AVERAGE;
3090 im->gdes[im->gdes_c-1].cf=CF_AVERAGE;
3091 im->gdes[im->gdes_c-1].p_data=NULL;
3092 im->gdes[im->gdes_c-1].yrule=DNAN;
3093 im->gdes[im->gdes_c-1].xrule=0;
3094 return 0;
3095 }
3097 /* copies input untill the first unescaped colon is found
3098 or until input ends. backslashes have to be escaped as well */
3099 int
3100 scan_for_col(const char *const input, int len, char *const output)
3101 {
3102 int inp,outp=0;
3103 for (inp=0;
3104 inp < len &&
3105 input[inp] != ':' &&
3106 input[inp] != '\0';
3107 inp++){
3108 if (input[inp] == '\\' &&
3109 input[inp+1] != '\0' &&
3110 (input[inp+1] == '\\' ||
3111 input[inp+1] == ':')){
3112 output[outp++] = input[++inp];
3113 }
3114 else {
3115 output[outp++] = input[inp];
3116 }
3117 }
3118 output[outp] = '\0';
3119 return inp;
3120 }
3121 /* Some surgery done on this function, it became ridiculously big.
3122 ** Things moved:
3123 ** - initializing now in rrd_graph_init()
3124 ** - options parsing now in rrd_graph_options()
3125 ** - script parsing now in rrd_graph_script()
3126 */
3127 int
3128 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize, FILE *stream, double *ymin, double *ymax)
3129 {
3130 image_desc_t im;
3131 rrd_graph_init(&im);
3132 im.graphhandle = stream;
3134 rrd_graph_options(argc,argv,&im);
3135 if (rrd_test_error()) {
3136 im_free(&im);
3137 return -1;
3138 }
3140 if (strlen(argv[optind])>=MAXPATH) {
3141 rrd_set_error("filename (including path) too long");
3142 im_free(&im);
3143 return -1;
3144 }
3145 strncpy(im.graphfile,argv[optind],MAXPATH-1);
3146 im.graphfile[MAXPATH-1]='\0';
3148 rrd_graph_script(argc,argv,&im,1);
3149 if (rrd_test_error()) {
3150 im_free(&im);
3151 return -1;
3152 }
3154 /* Everything is now read and the actual work can start */
3156 (*prdata)=NULL;
3157 if (graph_paint(&im,prdata)==-1){
3158 im_free(&im);
3159 return -1;
3160 }
3162 /* The image is generated and needs to be output.
3163 ** Also, if needed, print a line with information about the image.
3164 */
3166 *xsize=im.ximg;
3167 *ysize=im.yimg;
3168 *ymin=im.minval;
3169 *ymax=im.maxval;
3170 if (im.imginfo) {
3171 char *filename;
3172 if (!(*prdata)) {
3173 /* maybe prdata is not allocated yet ... lets do it now */
3174 if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
3175 rrd_set_error("malloc imginfo");
3176 return -1;
3177 };
3178 }
3179 if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
3180 ==NULL){
3181 rrd_set_error("malloc imginfo");
3182 return -1;
3183 }
3184 filename=im.graphfile+strlen(im.graphfile);
3185 while(filename > im.graphfile) {
3186 if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
3187 filename--;
3188 }
3190 sprintf((*prdata)[0],im.imginfo,filename,(long)(im.canvas->zoom*im.ximg),(long)(im.canvas->zoom*im.yimg));
3191 }
3192 im_free(&im);
3193 return 0;
3194 }
3196 void
3197 rrd_graph_init(image_desc_t *im)
3198 {
3199 unsigned int i;
3201 #ifdef HAVE_TZSET
3202 tzset();
3203 #endif
3204 #ifdef HAVE_SETLOCALE
3205 setlocale(LC_TIME,"");
3206 #ifdef HAVE_MBSTOWCS
3207 setlocale(LC_CTYPE,"");
3208 #endif
3209 #endif
3210 im->yorigin=0;
3211 im->xorigin=0;
3212 im->minval=0;
3213 im->xlab_user.minsec = -1;
3214 im->ximg=0;
3215 im->yimg=0;
3216 im->xsize = 400;
3217 im->ysize = 100;
3218 im->step = 0;
3219 im->ylegend[0] = '\0';
3220 im->second_axis_scale = 0; /* 0 disables it */
3221 im->second_axis_shift = 0; /* no shift by default */
3222 im->second_axis_legend[0] = '\0';
3223 im->title[0] = '\0';
3224 im->watermark[0] = '\0';
3225 im->minval = DNAN;
3226 im->maxval = DNAN;
3227 im->unitsexponent= 9999;
3228 im->unitslength= 6;
3229 im->forceleftspace = 0;
3230 im->symbol = ' ';
3231 im->viewfactor = 1.0;
3232 im->extra_flags= 0;
3233 im->rigid = 0;
3234 im->gridfit = 1;
3235 im->imginfo = NULL;
3236 im->lazy = 0;
3237 im->slopemode = 0;
3238 im->logarithmic = 0;
3239 im->ygridstep = DNAN;
3240 im->draw_x_grid = 1;
3241 im->draw_y_grid = 1;
3242 im->base = 1000;
3243 im->prt_c = 0;
3244 im->gdes_c = 0;
3245 im->gdes = NULL;
3246 im->canvas = gfx_new_canvas();
3247 im->grid_dash_on = 1;
3248 im->grid_dash_off = 1;
3249 im->tabwidth = 40.0;
3251 for(i=0;i<DIM(graph_col);i++)
3252 im->graph_col[i]=graph_col[i];
3254 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3255 {
3256 char *windir;
3257 char rrd_win_default_font[1000];
3258 windir = getenv("windir");
3259 /* %windir% is something like D:\windows or C:\winnt */
3260 if (windir != NULL) {
3261 strncpy(rrd_win_default_font,windir,500);
3262 rrd_win_default_font[500] = '\0';
3263 strcat(rrd_win_default_font,"\\fonts\\");
3264 strcat(rrd_win_default_font,RRD_DEFAULT_FONT);
3265 for(i=0;i<DIM(text_prop);i++){
3266 strncpy(text_prop[i].font,rrd_win_default_font,sizeof(text_prop[i].font)-1);
3267 text_prop[i].font[sizeof(text_prop[i].font)-1] = '\0';
3268 }
3269 }
3270 }
3271 #endif
3272 {
3273 char *deffont;
3274 deffont = getenv("RRD_DEFAULT_FONT");
3275 if (deffont != NULL) {
3276 for(i=0;i<DIM(text_prop);i++){
3277 strncpy(text_prop[i].font,deffont,sizeof(text_prop[i].font)-1);
3278 text_prop[i].font[sizeof(text_prop[i].font)-1] = '\0';
3279 }
3280 }
3281 }
3282 for(i=0;i<DIM(text_prop);i++){
3283 im->text_prop[i].size = text_prop[i].size;
3284 strcpy(im->text_prop[i].font,text_prop[i].font);
3285 }
3286 }
3288 void
3289 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
3290 {
3291 int stroff;
3292 char *parsetime_error = NULL;
3293 char scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
3294 time_t start_tmp=0,end_tmp=0;
3295 long long_tmp;
3296 struct rrd_time_value start_tv, end_tv;
3297 gfx_color_t color;
3298 optind = 0; opterr = 0; /* initialize getopt */
3300 parsetime("end-24h", &start_tv);
3301 parsetime("now", &end_tv);
3303 /* defines for long options without a short equivalent. should be bytes,
3304 and may not collide with (the ASCII value of) short options */
3305 #define LONGOPT_UNITS_SI 255
3307 while (1){
3308 static struct option long_options[] =
3309 {
3310 {"start", required_argument, 0, 's'},
3311 {"end", required_argument, 0, 'e'},
3312 {"x-grid", required_argument, 0, 'x'},
3313 {"y-grid", required_argument, 0, 'y'},
3314 {"vertical-label",required_argument,0,'v'},
3315 {"width", required_argument, 0, 'w'},
3316 {"height", required_argument, 0, 'h'},
3317 {"interlaced", no_argument, 0, 'i'},
3318 {"upper-limit",required_argument, 0, 'u'},
3319 {"lower-limit",required_argument, 0, 'l'},
3320 {"rigid", no_argument, 0, 'r'},
3321 {"base", required_argument, 0, 'b'},
3322 {"logarithmic",no_argument, 0, 'o'},
3323 {"color", required_argument, 0, 'c'},
3324 {"font", required_argument, 0, 'n'},
3325 {"title", required_argument, 0, 't'},
3326 {"imginfo", required_argument, 0, 'f'},
3327 {"imgformat", required_argument, 0, 'a'},
3328 {"lazy", no_argument, 0, 'z'},
3329 {"zoom", required_argument, 0, 'm'},
3330 {"no-legend", no_argument, 0, 'g'},
3331 {"force-rules-legend",no_argument,0, 'F'},
3332 {"only-graph", no_argument, 0, 'j'},
3333 {"alt-y-grid", no_argument, 0, 'Y'},
3334 {"no-minor", no_argument, 0, 'I'},
3335 {"slope-mode", no_argument, 0, 'E'},
3336 {"alt-autoscale", no_argument, 0, 'A'},
3337 {"alt-autoscale-min", no_argument, 0, 'J'},
3338 {"alt-autoscale-max", no_argument, 0, 'M'},
3339 {"no-gridfit", no_argument, 0, 'N'},
3340 {"units-exponent",required_argument, 0, 'X'},
3341 {"units-length",required_argument, 0, 'L'},
3342 {"units", required_argument, 0, LONGOPT_UNITS_SI },
3343 {"step", required_argument, 0, 'S'},
3344 {"tabwidth", required_argument, 0, 'T'},
3345 {"font-render-mode", required_argument, 0, 'R'},
3346 {"font-smoothing-threshold", required_argument, 0, 'B'},
3347 {"watermark", required_argument, 0, 'W'},
3348 {"alt-y-mrtg", no_argument, 0, 1000}, /* this has no effect it is just here to save old apps from crashing when they use it */
3349 {"disable-rrdtool-tag", no_argument, 0, 1001},
3350 {"right-axis", required_argument, 0, 1002},
3351 {"right-axis-label", required_argument, 0, 1003},
3352 {0,0,0,0}};
3353 int option_index = 0;
3354 int opt;
3355 int col_start,col_end;
3357 opt = getopt_long(argc, argv,
3358 "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:I:zgjFYAMEX:L:S:T:NR:B:W:",
3359 long_options, &option_index);
3361 if (opt == EOF)
3362 break;
3364 switch(opt) {
3365 case 'I':
3366 im->extra_flags |= NOMINOR;
3367 break;
3368 case 'Y':
3369 im->extra_flags |= ALTYGRID;
3370 break;
3371 case 'A':
3372 im->extra_flags |= ALTAUTOSCALE;
3373 break;
3374 case 'J':
3375 im->extra_flags |= ALTAUTOSCALE_MIN;
3376 break;
3377 case 'M':
3378 im->extra_flags |= ALTAUTOSCALE_MAX;
3379 break;
3380 case 'j':
3381 im->extra_flags |= ONLY_GRAPH;
3382 break;
3383 case 'g':
3384 im->extra_flags |= NOLEGEND;
3385 break;
3386 case 'F':
3387 im->extra_flags |= FORCE_RULES_LEGEND;
3388 break;
3389 case 1001:
3390 im->extra_flags |= NO_RRDTOOL_TAG;
3391 break;
3392 case LONGOPT_UNITS_SI:
3393 if(im->extra_flags & FORCE_UNITS) {
3394 rrd_set_error("--units can only be used once!");
3395 return;
3396 }
3397 if(strcmp(optarg,"si")==0)
3398 im->extra_flags |= FORCE_UNITS_SI;
3399 else {
3400 rrd_set_error("invalid argument for --units: %s", optarg );
3401 return;
3402 }
3403 break;
3404 case 'X':
3405 im->unitsexponent = atoi(optarg);
3406 break;
3407 case 'L':
3408 im->unitslength = atoi(optarg);
3409 im->forceleftspace = 1;
3410 break;
3411 case 'T':
3412 im->tabwidth = atof(optarg);
3413 break;
3414 case 'S':
3415 im->step = atoi(optarg);
3416 break;
3417 case 'N':
3418 im->gridfit = 0;
3419 break;
3420 case 's':
3421 if ((parsetime_error = parsetime(optarg, &start_tv))) {
3422 rrd_set_error( "start time: %s", parsetime_error );
3423 return;
3424 }
3425 break;
3426 case 'e':
3427 if ((parsetime_error = parsetime(optarg, &end_tv))) {
3428 rrd_set_error( "end time: %s", parsetime_error );
3429 return;
3430 }
3431 break;
3432 case 'x':
3433 if(strcmp(optarg,"none") == 0){
3434 im->draw_x_grid=0;
3435 break;
3436 };
3438 if(sscanf(optarg,
3439 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3440 scan_gtm,
3441 &im->xlab_user.gridst,
3442 scan_mtm,
3443 &im->xlab_user.mgridst,
3444 scan_ltm,
3445 &im->xlab_user.labst,
3446 &im->xlab_user.precis,
3447 &stroff) == 7 && stroff != 0){
3448 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
3449 im->xlab_form[sizeof(im->xlab_form)-1] = '\0';
3450 if((int)(im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
3451 rrd_set_error("unknown keyword %s",scan_gtm);
3452 return;
3453 } else if ((int)(im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
3454 rrd_set_error("unknown keyword %s",scan_mtm);
3455 return;
3456 } else if ((int)(im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
3457 rrd_set_error("unknown keyword %s",scan_ltm);
3458 return;
3459 }
3460 im->xlab_user.minsec = 1;
3461 im->xlab_user.stst = im->xlab_form;
3462 } else {
3463 rrd_set_error("invalid x-grid format");
3464 return;
3465 }
3466 break;
3467 case 'y':
3469 if(strcmp(optarg,"none") == 0){
3470 im->draw_y_grid=0;
3471 break;
3472 };
3474 if(sscanf(optarg,
3475 "%lf:%d",
3476 &im->ygridstep,
3477 &im->ylabfact) == 2) {
3478 if(im->ygridstep<=0){
3479 rrd_set_error("grid step must be > 0");
3480 return;
3481 } else if (im->ylabfact < 1){
3482 rrd_set_error("label factor must be > 0");
3483 return;
3484 }
3485 } else {
3486 rrd_set_error("invalid y-grid format");
3487 return;
3488 }
3489 break;
3490 case 1002: /* right y axis */
3492 if(sscanf(optarg,
3493 "%lf:%lf",
3494 &im->second_axis_scale,
3495 &im->second_axis_shift) == 2) {
3496 if(im->second_axis_scale==0){
3497 rrd_set_error("the second_axis_scale must not be 0");
3498 return;
3499 }
3500 } else {
3501 rrd_set_error("invalid right-axis format expected scale:shift");
3502 return;
3503 }
3504 break;
3505 case 1003:
3506 strncpy(im->second_axis_legend,optarg,150);
3507 im->second_axis_legend[150]='\0';
3508 break;
3509 case 'v':
3510 strncpy(im->ylegend,optarg,150);
3511 im->ylegend[150]='\0';
3512 break;
3513 case 'u':
3514 im->maxval = atof(optarg);
3515 break;
3516 case 'l':
3517 im->minval = atof(optarg);
3518 break;
3519 case 'b':
3520 im->base = atol(optarg);
3521 if(im->base != 1024 && im->base != 1000 ){
3522 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
3523 return;
3524 }
3525 break;
3526 case 'w':
3527 long_tmp = atol(optarg);
3528 if (long_tmp < 10) {
3529 rrd_set_error("width below 10 pixels");
3530 return;
3531 }
3532 im->xsize = long_tmp;
3533 break;
3534 case 'h':
3535 long_tmp = atol(optarg);
3536 if (long_tmp < 10) {
3537 rrd_set_error("height below 10 pixels");
3538 return;
3539 }
3540 im->ysize = long_tmp;
3541 break;
3542 case 'i':
3543 im->canvas->interlaced = 1;
3544 break;
3545 case 'r':
3546 im->rigid = 1;
3547 break;
3548 case 'f':
3549 im->imginfo = optarg;
3550 break;
3551 case 'a':
3552 if((int)(im->canvas->imgformat = if_conv(optarg)) == -1) {
3553 rrd_set_error("unsupported graphics format '%s'",optarg);
3554 return;
3555 }
3556 break;
3557 case 'z':
3558 im->lazy = 1;
3559 break;
3560 case 'E':
3561 im->slopemode = 1;
3562 break;
3564 case 'o':
3565 im->logarithmic = 1;
3566 break;
3567 case 'c':
3568 if(sscanf(optarg,
3569 "%10[A-Z]#%n%8lx%n",
3570 col_nam,&col_start,&color,&col_end) == 2){
3571 int ci;
3572 int col_len = col_end - col_start;
3573 switch (col_len){
3574 case 3:
3575 color = (
3576 ((color & 0xF00) * 0x110000) |
3577 ((color & 0x0F0) * 0x011000) |
3578 ((color & 0x00F) * 0x001100) |
3579 0x000000FF
3580 );
3581 break;
3582 case 4:
3583 color = (
3584 ((color & 0xF000) * 0x11000) |
3585 ((color & 0x0F00) * 0x01100) |
3586 ((color & 0x00F0) * 0x00110) |
3587 ((color & 0x000F) * 0x00011)
3588 );
3589 break;
3590 case 6:
3591 color = (color << 8) + 0xff /* shift left by 8 */;
3592 break;
3593 case 8:
3594 break;
3595 default:
3596 rrd_set_error("the color format is #RRGGBB[AA]");
3597 return;
3598 }
3599 if((ci=grc_conv(col_nam)) != -1){
3600 im->graph_col[ci]=color;
3601 } else {
3602 rrd_set_error("invalid color name '%s'",col_nam);
3603 return;
3604 }
3605 } else {
3606 rrd_set_error("invalid color def format");
3607 return;
3608 }
3609 break;
3610 case 'n':{
3611 char prop[15];
3612 double size = 1;
3613 int end;
3614 if(sscanf(optarg,
3615 "%10[A-Z]:%lf%n",
3616 prop,&size,&end) >= 2){
3617 int sindex,propidx;
3618 if((sindex=text_prop_conv(prop)) != -1){
3619 for (propidx=sindex;propidx<TEXT_PROP_LAST;propidx++){
3620 if (size > 0){
3621 im->text_prop[propidx].size=size;
3622 }
3623 if ((int)strlen(optarg) > end){
3624 if (optarg[end] == ':'){
3625 strncpy(im->text_prop[propidx].font,optarg+end+1,255);
3626 im->text_prop[propidx].font[255] = '\0';
3627 } else {
3628 rrd_set_error("expected : after font size in '%s'",optarg);
3629 return;
3630 }
3631 }
3632 /* only run the for loop for DEFAULT (0) for
3633 all others, we break here. woodo programming */
3634 if (propidx==sindex && sindex != 0) break;
3635 }
3636 } else {
3637 rrd_set_error("invalid fonttag '%s'",prop);
3638 return;
3639 }
3640 } else {
3641 rrd_set_error("invalid text property format");
3642 return;
3643 }
3644 break;
3645 }
3646 case 'm':
3647 im->canvas->zoom = atof(optarg);
3648 if (im->canvas->zoom <= 0.0) {
3649 rrd_set_error("zoom factor must be > 0");
3650 return;
3651 }
3652 break;
3653 case 't':
3654 strncpy(im->title,optarg,150);
3655 im->title[150]='\0';
3656 break;
3658 case 'R':
3659 if ( strcmp( optarg, "normal" ) == 0 )
3660 im->canvas->aa_type = AA_NORMAL;
3661 else if ( strcmp( optarg, "light" ) == 0 )
3662 im->canvas->aa_type = AA_LIGHT;
3663 else if ( strcmp( optarg, "mono" ) == 0 )
3664 im->canvas->aa_type = AA_NONE;
3665 else
3666 {
3667 rrd_set_error("unknown font-render-mode '%s'", optarg );
3668 return;
3669 }
3670 break;
3672 case 'B':
3673 im->canvas->font_aa_threshold = atof(optarg);
3674 break;
3676 case 'W':
3677 strncpy(im->watermark,optarg,100);
3678 im->watermark[99]='\0';
3679 break;
3681 case '?':
3682 if (optopt != 0)
3683 rrd_set_error("unknown option '%c'", optopt);
3684 else
3685 rrd_set_error("unknown option '%s'",argv[optind-1]);
3686 return;
3687 }
3688 }
3690 if (optind >= argc) {
3691 rrd_set_error("missing filename");
3692 return;
3693 }
3695 if (im->logarithmic == 1 && im->minval <= 0){
3696 rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");
3697 return;
3698 }
3700 if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
3701 /* error string is set in parsetime.c */
3702 return;
3703 }
3705 if (start_tmp < 3600*24*365*10){
3706 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
3707 return;
3708 }
3710 if (end_tmp < start_tmp) {
3711 rrd_set_error("start (%ld) should be less than end (%ld)",
3712 start_tmp, end_tmp);
3713 return;
3714 }
3716 im->start = start_tmp;
3717 im->end = end_tmp;
3718 im->step = max((long)im->step, (im->end-im->start)/im->xsize);
3719 }
3721 int
3722 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
3723 {
3724 char *color;
3725 graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
3727 color=strstr(var,"#");
3728 if (color==NULL) {
3729 if (optional==0) {
3730 rrd_set_error("Found no color in %s",err);
3731 return 0;
3732 }
3733 return 0;
3734 } else {
3735 int n=0;
3736 char *rest;
3737 gfx_color_t col;
3739 rest=strstr(color,":");
3740 if (rest!=NULL)
3741 n=rest-color;
3742 else
3743 n=strlen(color);
3745 switch (n) {
3746 case 7:
3747 sscanf(color,"#%6lx%n",&col,&n);
3748 col = (col << 8) + 0xff /* shift left by 8 */;
3749 if (n!=7) rrd_set_error("Color problem in %s",err);
3750 break;
3751 case 9:
3752 sscanf(color,"#%8lx%n",&col,&n);
3753 if (n==9) break;
3754 default:
3755 rrd_set_error("Color problem in %s",err);
3756 }
3757 if (rrd_test_error()) return 0;
3758 gdp->col = col;
3759 return n;
3760 }
3761 }
3764 int bad_format(char *fmt) {
3765 char *ptr;
3766 int n=0;
3767 ptr = fmt;
3768 while (*ptr != '\0')
3769 if (*ptr++ == '%') {
3771 /* line cannot end with percent char */
3772 if (*ptr == '\0') return 1;
3774 /* '%s', '%S' and '%%' are allowed */
3775 if (*ptr == 's' || *ptr == 'S' || *ptr == '%') ptr++;
3777 /* %c is allowed (but use only with vdef!) */
3778 else if (*ptr == 'c') {
3779 ptr++;
3780 n=1;
3781 }
3783 /* or else '% 6.2lf' and such are allowed */
3784 else {
3785 /* optional padding character */
3786 if (*ptr == ' ' || *ptr == '+' || *ptr == '-') ptr++;
3788 /* This should take care of 'm.n' with all three optional */
3789 while (*ptr >= '0' && *ptr <= '9') ptr++;
3790 if (*ptr == '.') ptr++;
3791 while (*ptr >= '0' && *ptr <= '9') ptr++;
3793 /* Either 'le', 'lf' or 'lg' must follow here */
3794 if (*ptr++ != 'l') return 1;
3795 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g') ptr++;
3796 else return 1;
3797 n++;
3798 }
3799 }
3801 return (n!=1);
3802 }
3805 int
3806 vdef_parse(gdes,str)
3807 struct graph_desc_t *gdes;
3808 const char *const str;
3809 {
3810 /* A VDEF currently is either "func" or "param,func"
3811 * so the parsing is rather simple. Change if needed.
3812 */
3813 double param;
3814 char func[30];
3815 int n;
3817 n=0;
3818 sscanf(str,"%le,%29[A-Z]%n",¶m,func,&n);
3819 if (n== (int)strlen(str)) { /* matched */
3820 ;
3821 } else {
3822 n=0;
3823 sscanf(str,"%29[A-Z]%n",func,&n);
3824 if (n== (int)strlen(str)) { /* matched */
3825 param=DNAN;
3826 } else {
3827 rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3828 ,str
3829 ,gdes->vname
3830 );
3831 return -1;
3832 }
3833 }
3834 if (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3835 else if (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3836 else if (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3837 else if (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3838 else if (!strcmp("TOTAL", func)) gdes->vf.op = VDEF_TOTAL;
3839 else if (!strcmp("FIRST", func)) gdes->vf.op = VDEF_FIRST;
3840 else if (!strcmp("LAST", func)) gdes->vf.op = VDEF_LAST;
3841 else if (!strcmp("LSLSLOPE", func)) gdes->vf.op = VDEF_LSLSLOPE;
3842 else if (!strcmp("LSLINT", func)) gdes->vf.op = VDEF_LSLINT;
3843 else if (!strcmp("LSLCORREL",func)) gdes->vf.op = VDEF_LSLCORREL;
3844 else {
3845 rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3846 ,func
3847 ,gdes->vname
3848 );
3849 return -1;
3850 };
3852 switch (gdes->vf.op) {
3853 case VDEF_PERCENT:
3854 if (isnan(param)) { /* no parameter given */
3855 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3856 ,func
3857 ,gdes->vname
3858 );
3859 return -1;
3860 };
3861 if (param>=0.0 && param<=100.0) {
3862 gdes->vf.param = param;
3863 gdes->vf.val = DNAN; /* undefined */
3864 gdes->vf.when = 0; /* undefined */
3865 } else {
3866 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3867 ,param
3868 ,gdes->vname
3869 );
3870 return -1;
3871 };
3872 break;
3873 case VDEF_MAXIMUM:
3874 case VDEF_AVERAGE:
3875 case VDEF_MINIMUM:
3876 case VDEF_TOTAL:
3877 case VDEF_FIRST:
3878 case VDEF_LAST:
3879 case VDEF_LSLSLOPE:
3880 case VDEF_LSLINT:
3881 case VDEF_LSLCORREL:
3882 if (isnan(param)) {
3883 gdes->vf.param = DNAN;
3884 gdes->vf.val = DNAN;
3885 gdes->vf.when = 0;
3886 } else {
3887 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3888 ,func
3889 ,gdes->vname
3890 );
3891 return -1;
3892 };
3893 break;
3894 };
3895 return 0;
3896 }
3899 int
3900 vdef_calc(im,gdi)
3901 image_desc_t *im;
3902 int gdi;
3903 {
3904 graph_desc_t *src,*dst;
3905 rrd_value_t *data;
3906 long step,steps;
3908 dst = &im->gdes[gdi];
3909 src = &im->gdes[dst->vidx];
3910 data = src->data + src->ds;
3911 steps = (src->end - src->start) / src->step;
3912 #if 0
3913 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3914 ,src->start
3915 ,src->end
3916 ,steps
3917 );
3918 #endif
3920 switch (dst->vf.op) {
3921 case VDEF_PERCENT: {
3922 rrd_value_t * array;
3923 int field;
3926 if ((array = malloc(steps*sizeof(double)))==NULL) {
3927 rrd_set_error("malloc VDEV_PERCENT");
3928 return -1;
3929 }
3930 for (step=0;step < steps; step++) {
3931 array[step]=data[step*src->ds_cnt];
3932 }
3933 qsort(array,step,sizeof(double),vdef_percent_compar);
3935 field = (steps-1)*dst->vf.param/100;
3936 dst->vf.val = array[field];
3937 dst->vf.when = 0; /* no time component */
3938 free(array);
3939 #if 0
3940 for(step=0;step<steps;step++)
3941 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3942 #endif
3943 }
3944 break;
3945 case VDEF_MAXIMUM:
3946 step=0;
3947 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3948 if (step == steps) {
3949 dst->vf.val = DNAN;
3950 dst->vf.when = 0;
3951 } else {
3952 dst->vf.val = data[step*src->ds_cnt];
3953 dst->vf.when = src->start + (step+1)*src->step;
3954 }
3955 while (step != steps) {
3956 if (finite(data[step*src->ds_cnt])) {
3957 if (data[step*src->ds_cnt] > dst->vf.val) {
3958 dst->vf.val = data[step*src->ds_cnt];
3959 dst->vf.when = src->start + (step+1)*src->step;
3960 }
3961 }
3962 step++;
3963 }
3964 break;
3965 case VDEF_TOTAL:
3966 case VDEF_AVERAGE: {
3967 int cnt=0;
3968 double sum=0.0;
3969 for (step=0;step<steps;step++) {
3970 if (finite(data[step*src->ds_cnt])) {
3971 sum += data[step*src->ds_cnt];
3972 cnt ++;
3973 };
3974 }
3975 if (cnt) {
3976 if (dst->vf.op == VDEF_TOTAL) {
3977 dst->vf.val = sum*src->step;
3978 dst->vf.when = 0; /* no time component */
3979 } else {
3980 dst->vf.val = sum/cnt;
3981 dst->vf.when = 0; /* no time component */
3982 };
3983 } else {
3984 dst->vf.val = DNAN;
3985 dst->vf.when = 0;
3986 }
3987 }
3988 break;
3989 case VDEF_MINIMUM:
3990 step=0;
3991 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3992 if (step == steps) {
3993 dst->vf.val = DNAN;
3994 dst->vf.when = 0;
3995 } else {
3996 dst->vf.val = data[step*src->ds_cnt];
3997 dst->vf.when = src->start + (step+1)*src->step;
3998 }
3999 while (step != steps) {
4000 if (finite(data[step*src->ds_cnt])) {
4001 if (data[step*src->ds_cnt] < dst->vf.val) {
4002 dst->vf.val = data[step*src->ds_cnt];
4003 dst->vf.when = src->start + (step+1)*src->step;
4004 }
4005 }
4006 step++;
4007 }
4008 break;
4009 case VDEF_FIRST:
4010 /* The time value returned here is one step before the
4011 * actual time value. This is the start of the first
4012 * non-NaN interval.
4013 */
4014 step=0;
4015 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
4016 if (step == steps) { /* all entries were NaN */
4017 dst->vf.val = DNAN;
4018 dst->vf.when = 0;
4019 } else {
4020 dst->vf.val = data[step*src->ds_cnt];
4021 dst->vf.when = src->start + step*src->step;
4022 }
4023 break;
4024 case VDEF_LAST:
4025 /* The time value returned here is the
4026 * actual time value. This is the end of the last
4027 * non-NaN interval.
4028 */
4029 step=steps-1;
4030 while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
4031 if (step < 0) { /* all entries were NaN */
4032 dst->vf.val = DNAN;
4033 dst->vf.when = 0;
4034 } else {
4035 dst->vf.val = data[step*src->ds_cnt];
4036 dst->vf.when = src->start + (step+1)*src->step;
4037 }
4038 break;
4039 case VDEF_LSLSLOPE:
4040 case VDEF_LSLINT:
4041 case VDEF_LSLCORREL:{
4042 /* Bestfit line by linear least squares method */
4044 int cnt=0;
4045 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl ;
4046 SUMx = 0; SUMy = 0; SUMxy = 0; SUMxx = 0; SUMyy = 0;
4048 for (step=0;step<steps;step++) {
4049 if (finite(data[step*src->ds_cnt])) {
4050 cnt++;
4051 SUMx += step;
4052 SUMxx += step * step;
4053 SUMxy += step * data[step*src->ds_cnt];
4054 SUMy += data[step*src->ds_cnt];
4055 SUMyy += data[step*src->ds_cnt]*data[step*src->ds_cnt];
4056 };
4057 }
4059 slope = ( SUMx*SUMy - cnt*SUMxy ) / ( SUMx*SUMx - cnt*SUMxx );
4060 y_intercept = ( SUMy - slope*SUMx ) / cnt;
4061 correl = (SUMxy - (SUMx*SUMy)/cnt) / sqrt((SUMxx - (SUMx*SUMx)/cnt)*(SUMyy - (SUMy*SUMy)/cnt));
4063 if (cnt) {
4064 if (dst->vf.op == VDEF_LSLSLOPE) {
4065 dst->vf.val = slope;
4066 dst->vf.when = 0;
4067 } else if (dst->vf.op == VDEF_LSLINT) {
4068 dst->vf.val = y_intercept;
4069 dst->vf.when = 0;
4070 } else if (dst->vf.op == VDEF_LSLCORREL) {
4071 dst->vf.val = correl;
4072 dst->vf.when = 0;
4073 };
4075 } else {
4076 dst->vf.val = DNAN;
4077 dst->vf.when = 0;
4078 }
4079 }
4080 break;
4081 }
4082 return 0;
4083 }
4085 /* NaN < -INF < finite_values < INF */
4086 int
4087 vdef_percent_compar(a,b)
4088 const void *a,*b;
4089 {
4090 /* Equality is not returned; this doesn't hurt except
4091 * (maybe) for a little performance.
4092 */
4094 /* First catch NaN values. They are smallest */
4095 if (isnan( *(double *)a )) return -1;
4096 if (isnan( *(double *)b )) return 1;
4098 /* NaN doesn't reach this part so INF and -INF are extremes.
4099 * The sign from isinf() is compatible with the sign we return
4100 */
4101 if (isinf( *(double *)a )) return isinf( *(double *)a );
4102 if (isinf( *(double *)b )) return isinf( *(double *)b );
4104 /* If we reach this, both values must be finite */
4105 if ( *(double *)a < *(double *)b ) return -1; else return 1;
4106 }