1 /****************************************************************************
2 * RRDtool 1.2.30 Copyright by Tobi Oetiker, 1997-2009
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 (im->second_axis_format[0] == '\0'){
1753 if (!second_axis_magfact){
1754 double dummy = im->ygrid_scale.gridstep*(double)(sgrid+egrid)/2.0*im->second_axis_scale+im->second_axis_shift;
1755 auto_scale(im,&dummy,&second_axis_symb,&second_axis_magfact);
1756 }
1757 sval /= second_axis_magfact;
1759 if(MaxY < 10) {
1760 sprintf(graph_label_right,"%5.1f %s",sval,second_axis_symb);
1761 } else {
1762 sprintf(graph_label_right,"%5.0f %s",sval,second_axis_symb);
1763 }
1764 }
1765 else {
1766 sprintf(graph_label_right,im->second_axis_format,sval);
1767 }
1768 gfx_new_text ( im->canvas,
1769 X1+7, Y0,
1770 im->graph_col[GRC_FONT],
1771 im->text_prop[TEXT_PROP_AXIS].font,
1772 im->text_prop[TEXT_PROP_AXIS].size,
1773 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
1774 graph_label_right );
1775 }
1777 gfx_new_text ( im->canvas,
1778 X0-im->text_prop[TEXT_PROP_AXIS].size, Y0,
1779 im->graph_col[GRC_FONT],
1780 im->text_prop[TEXT_PROP_AXIS].font,
1781 im->text_prop[TEXT_PROP_AXIS].size,
1782 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1783 graph_label );
1784 gfx_new_dashed_line ( im->canvas,
1785 X0-2,Y0,
1786 X1+2,Y0,
1787 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1788 im->grid_dash_on, im->grid_dash_off);
1790 } else if (!(im->extra_flags & NOMINOR)) {
1791 gfx_new_dashed_line ( im->canvas,
1792 X0-1,Y0,
1793 X1+1,Y0,
1794 GRIDWIDTH, im->graph_col[GRC_GRID],
1795 im->grid_dash_on, im->grid_dash_off);
1797 }
1798 }
1799 }
1800 return 1;
1801 }
1803 /* this is frexp for base 10 */
1804 double frexp10(double, double *);
1805 double frexp10(double x, double *e) {
1806 double mnt;
1807 int iexp;
1809 iexp = floor(log(fabs(x)) / log(10));
1810 mnt = x / pow(10.0, iexp);
1811 if(mnt >= 10.0) {
1812 iexp++;
1813 mnt = x / pow(10.0, iexp);
1814 }
1815 *e = iexp;
1816 return mnt;
1817 }
1819 /* logaritmic horizontal grid */
1820 int
1821 horizontal_log_grid(image_desc_t *im)
1822 {
1823 double yloglab[][10] = {
1824 {1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
1825 {1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
1826 {1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0},
1827 {1.0, 2.0, 4.0, 6.0, 8.0, 10., 0.0, 0.0, 0.0, 0.0},
1828 {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.},
1829 {0,0,0,0,0, 0,0,0,0,0} /* last line */ };
1831 int i, j, val_exp, min_exp;
1832 double nex; /* number of decades in data */
1833 double logscale; /* scale in logarithmic space */
1834 int exfrac = 1; /* decade spacing */
1835 int mid = -1; /* row in yloglab for major grid */
1836 double mspac; /* smallest major grid spacing (pixels) */
1837 int flab; /* first value in yloglab to use */
1838 double value, tmp, pre_value;
1839 double X0,X1,Y0;
1840 char graph_label[100];
1842 nex = log10(im->maxval / im->minval);
1843 logscale = im->ysize / nex;
1845 /* major spacing for data with high dynamic range */
1846 while(logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
1847 if(exfrac == 1) exfrac = 3;
1848 else exfrac += 3;
1849 }
1851 /* major spacing for less dynamic data */
1852 do {
1853 /* search best row in yloglab */
1854 mid++;
1855 for(i = 0; yloglab[mid][i + 1] < 10.0; i++);
1856 mspac = logscale * log10(10.0 / yloglab[mid][i]);
1857 } while(mspac > 2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0);
1858 if(mid) mid--;
1860 /* find first value in yloglab */
1861 for(flab = 0; yloglab[mid][flab] < 10 && frexp10(im->minval, &tmp) > yloglab[mid][flab] ; flab++);
1862 if(yloglab[mid][flab] == 10.0) {
1863 tmp += 1.0;
1864 flab = 0;
1865 }
1866 val_exp = tmp;
1867 if(val_exp % exfrac) val_exp += abs(-val_exp % exfrac);
1869 X0=im->xorigin;
1870 X1=im->xorigin+im->xsize;
1872 /* draw grid */
1873 pre_value = DNAN;
1874 while(1) {
1876 value = yloglab[mid][flab] * pow(10.0, val_exp);
1877 if ( AlmostEqual2sComplement(value,pre_value,4) ) break; /* it seems we are not converging */
1879 pre_value = value;
1881 Y0 = ytr(im, value);
1882 if(floor(Y0+0.5) <= im->yorigin - im->ysize) break;
1884 /* major grid line */
1885 gfx_new_dashed_line ( im->canvas,
1886 X0-2,Y0,
1887 X1+2,Y0,
1888 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1889 im->grid_dash_on, im->grid_dash_off);
1891 /* label */
1892 if (im->extra_flags & FORCE_UNITS_SI) {
1893 int scale;
1894 double pvalue;
1895 char symbol;
1897 scale = floor(val_exp / 3.0);
1898 if( value >= 1.0 ) pvalue = pow(10.0, val_exp % 3);
1899 else pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
1900 pvalue *= yloglab[mid][flab];
1902 if ( ((scale+si_symbcenter) < (int)sizeof(si_symbol)) &&
1903 ((scale+si_symbcenter) >= 0) )
1904 symbol = si_symbol[scale+si_symbcenter];
1905 else
1906 symbol = '?';
1908 sprintf(graph_label,"%3.0f %c", pvalue, symbol);
1909 } else {
1910 sprintf(graph_label,"%3.0e", value);
1911 }
1912 if (im->second_axis_scale != 0){
1913 char graph_label_right[100];
1914 double sval = value*im->second_axis_scale+im->second_axis_shift;
1915 if (im->second_axis_format[0] == '\0'){
1916 if (im->extra_flags & FORCE_UNITS_SI) {
1917 double mfac = 1;
1918 char *symb = "";
1919 auto_scale(im,&sval,&symb,&mfac);
1920 sprintf(graph_label_right,"%4.0f %s", sval,symb);
1921 }
1922 else {
1923 sprintf(graph_label_right,"%3.0e", sval);
1924 }
1925 }
1926 else {
1927 sprintf(graph_label_right,im->second_axis_format,sval);
1928 }
1930 gfx_new_text ( im->canvas,
1931 X1+7, Y0,
1932 im->graph_col[GRC_FONT],
1933 im->text_prop[TEXT_PROP_AXIS].font,
1934 im->text_prop[TEXT_PROP_AXIS].size,
1935 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
1936 graph_label_right );
1937 }
1939 gfx_new_text ( im->canvas,
1940 X0-im->text_prop[TEXT_PROP_AXIS].size, Y0,
1941 im->graph_col[GRC_FONT],
1942 im->text_prop[TEXT_PROP_AXIS].font,
1943 im->text_prop[TEXT_PROP_AXIS].size,
1944 im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1945 graph_label );
1947 /* minor grid */
1948 if(mid < 4 && exfrac == 1) {
1949 /* find first and last minor line behind current major line
1950 * i is the first line and j tha last */
1951 if(flab == 0) {
1952 min_exp = val_exp - 1;
1953 for(i = 1; yloglab[mid][i] < 10.0; i++);
1954 i = yloglab[mid][i - 1] + 1;
1955 j = 10;
1956 }
1957 else {
1958 min_exp = val_exp;
1959 i = yloglab[mid][flab - 1] + 1;
1960 j = yloglab[mid][flab];
1961 }
1963 /* draw minor lines below current major line */
1964 for(; i < j; i++) {
1966 value = i * pow(10.0, min_exp);
1967 if(value < im->minval) continue;
1969 Y0 = ytr(im, value);
1970 if(floor(Y0+0.5) <= im->yorigin - im->ysize) break;
1972 /* draw lines */
1973 gfx_new_dashed_line ( im->canvas,
1974 X0-1,Y0,
1975 X1+1,Y0,
1976 GRIDWIDTH, im->graph_col[GRC_GRID],
1977 im->grid_dash_on, im->grid_dash_off);
1978 }
1979 }
1980 else if(exfrac > 1) {
1981 for(i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
1982 value = pow(10.0, i);
1983 if(value < im->minval) continue;
1985 Y0 = ytr(im, value);
1986 if(floor(Y0+0.5) <= im->yorigin - im->ysize) break;
1988 /* draw lines */
1989 gfx_new_dashed_line ( im->canvas,
1990 X0-1,Y0,
1991 X1+1,Y0,
1992 GRIDWIDTH, im->graph_col[GRC_GRID],
1993 im->grid_dash_on, im->grid_dash_off);
1994 }
1995 }
1997 /* next decade */
1998 if(yloglab[mid][++flab] == 10.0) {
1999 flab = 0;
2000 val_exp += exfrac;
2001 }
2002 }
2004 /* draw minor lines after highest major line */
2005 if(mid < 4 && exfrac == 1) {
2006 /* find first and last minor line below current major line
2007 * i is the first line and j tha last */
2008 if(flab == 0) {
2009 min_exp = val_exp - 1;
2010 for(i = 1; yloglab[mid][i] < 10.0; i++);
2011 i = yloglab[mid][i - 1] + 1;
2012 j = 10;
2013 }
2014 else {
2015 min_exp = val_exp;
2016 i = yloglab[mid][flab - 1] + 1;
2017 j = yloglab[mid][flab];
2018 }
2020 /* draw minor lines below current major line */
2021 for(; i < j; i++) {
2023 value = i * pow(10.0, min_exp);
2024 if(value < im->minval) continue;
2026 Y0 = ytr(im, value);
2027 if(floor(Y0+0.5) <= im->yorigin - im->ysize) break;
2029 /* draw lines */
2030 gfx_new_dashed_line ( im->canvas,
2031 X0-1,Y0,
2032 X1+1,Y0,
2033 GRIDWIDTH, im->graph_col[GRC_GRID],
2034 im->grid_dash_on, im->grid_dash_off);
2035 }
2036 }
2037 /* fancy minor gridlines */
2038 else if(exfrac > 1) {
2039 for(i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2040 value = pow(10.0, i);
2041 if(value < im->minval) continue;
2043 Y0 = ytr(im, value);
2044 if(floor(Y0+0.5) <= im->yorigin - im->ysize) break;
2046 /* draw lines */
2047 gfx_new_dashed_line ( im->canvas,
2048 X0-1,Y0,
2049 X1+1,Y0,
2050 GRIDWIDTH, im->graph_col[GRC_GRID],
2051 im->grid_dash_on, im->grid_dash_off);
2052 }
2053 }
2055 return 1;
2056 }
2059 void
2060 vertical_grid(
2061 image_desc_t *im )
2062 {
2063 int xlab_sel; /* which sort of label and grid ? */
2064 time_t ti, tilab, timajor;
2065 long factor;
2066 char graph_label[100];
2067 double X0,Y0,Y1; /* points for filled graph and more*/
2068 struct tm tm;
2070 /* the type of time grid is determined by finding
2071 the number of seconds per pixel in the graph */
2074 if(im->xlab_user.minsec == -1){
2075 factor=(im->end - im->start)/im->xsize;
2076 xlab_sel=0;
2077 while ( xlab[xlab_sel+1].minsec != -1
2078 && xlab[xlab_sel+1].minsec <= factor) { xlab_sel++; } /* pick the last one */
2079 while ( xlab[xlab_sel-1].minsec == xlab[xlab_sel].minsec
2080 && xlab[xlab_sel].length > (im->end - im->start)) { xlab_sel--; } /* go back to the smallest size */
2081 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2082 im->xlab_user.gridst = xlab[xlab_sel].gridst;
2083 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2084 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2085 im->xlab_user.labtm = xlab[xlab_sel].labtm;
2086 im->xlab_user.labst = xlab[xlab_sel].labst;
2087 im->xlab_user.precis = xlab[xlab_sel].precis;
2088 im->xlab_user.stst = xlab[xlab_sel].stst;
2089 }
2091 /* y coords are the same for every line ... */
2092 Y0 = im->yorigin;
2093 Y1 = im->yorigin-im->ysize;
2096 /* paint the minor grid */
2097 if (!(im->extra_flags & NOMINOR))
2098 {
2099 for(ti = find_first_time(im->start,
2100 im->xlab_user.gridtm,
2101 im->xlab_user.gridst),
2102 timajor = find_first_time(im->start,
2103 im->xlab_user.mgridtm,
2104 im->xlab_user.mgridst);
2105 ti < im->end;
2106 ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
2107 ){
2108 /* are we inside the graph ? */
2109 if (ti < im->start || ti > im->end) continue;
2110 while (timajor < ti) {
2111 timajor = find_next_time(timajor,
2112 im->xlab_user.mgridtm, im->xlab_user.mgridst);
2113 }
2114 if (ti == timajor) continue; /* skip as falls on major grid line */
2115 X0 = xtr(im,ti);
2116 gfx_new_dashed_line(im->canvas,X0,Y0+1, X0,Y1-1,GRIDWIDTH,
2117 im->graph_col[GRC_GRID],
2118 im->grid_dash_on, im->grid_dash_off);
2120 }
2121 }
2123 /* paint the major grid */
2124 for(ti = find_first_time(im->start,
2125 im->xlab_user.mgridtm,
2126 im->xlab_user.mgridst);
2127 ti < im->end;
2128 ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
2129 ){
2130 /* are we inside the graph ? */
2131 if (ti < im->start || ti > im->end) continue;
2132 X0 = xtr(im,ti);
2133 gfx_new_dashed_line(im->canvas,X0,Y0+3, X0,Y1-2,MGRIDWIDTH,
2134 im->graph_col[GRC_MGRID],
2135 im->grid_dash_on, im->grid_dash_off);
2137 }
2138 /* paint the labels below the graph */
2139 for(ti = find_first_time(im->start - im->xlab_user.precis/2,
2140 im->xlab_user.labtm,
2141 im->xlab_user.labst);
2142 ti <= im->end - im->xlab_user.precis/2;
2143 ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
2144 ){
2145 tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
2146 /* are we inside the graph ? */
2147 if (tilab < im->start || tilab > im->end) continue;
2149 #if HAVE_STRFTIME
2150 localtime_r(&tilab, &tm);
2151 strftime(graph_label,99,im->xlab_user.stst, &tm);
2152 #else
2153 # error "your libc has no strftime I guess we'll abort the exercise here."
2154 #endif
2155 gfx_new_text ( im->canvas,
2156 xtr(im,tilab), Y0+im->text_prop[TEXT_PROP_AXIS].size*1.4+5,
2157 im->graph_col[GRC_FONT],
2158 im->text_prop[TEXT_PROP_AXIS].font,
2159 im->text_prop[TEXT_PROP_AXIS].size,
2160 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_BOTTOM,
2161 graph_label );
2163 }
2165 }
2168 void
2169 axis_paint(
2170 image_desc_t *im
2171 )
2172 {
2173 /* draw x and y axis */
2174 /* gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2175 im->xorigin+im->xsize,im->yorigin-im->ysize,
2176 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2178 gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2179 im->xorigin+im->xsize,im->yorigin-im->ysize,
2180 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2182 gfx_new_line ( im->canvas, im->xorigin-4,im->yorigin,
2183 im->xorigin+im->xsize+4,im->yorigin,
2184 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2186 gfx_new_line ( im->canvas, im->xorigin,im->yorigin+4,
2187 im->xorigin,im->yorigin-im->ysize-4,
2188 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2191 /* arrow for X and Y axis direction */
2192 gfx_new_area ( im->canvas,
2193 im->xorigin+im->xsize+2, im->yorigin-2,
2194 im->xorigin+im->xsize+2, im->yorigin+3,
2195 im->xorigin+im->xsize+7, im->yorigin+0.5, /* LINEOFFSET */
2196 im->graph_col[GRC_ARROW]);
2198 gfx_new_area ( im->canvas,
2199 im->xorigin-2, im->yorigin-im->ysize-2,
2200 im->xorigin+3, im->yorigin-im->ysize-2,
2201 im->xorigin+0.5, im->yorigin-im->ysize-7, /* LINEOFFSET */
2202 im->graph_col[GRC_ARROW]);
2204 if (im->second_axis_scale != 0){
2205 gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin+4,
2206 im->xorigin+im->xsize,im->yorigin-im->ysize-4,
2207 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2208 gfx_new_area ( im->canvas,
2209 im->xorigin+im->xsize-2, im->yorigin-im->ysize-2,
2210 im->xorigin+im->xsize+3, im->yorigin-im->ysize-2,
2211 im->xorigin+im->xsize+0.5, im->yorigin-im->ysize-7, /* LINEOFFSET */
2212 im->graph_col[GRC_ARROW]);
2213 }
2214 }
2216 void
2217 grid_paint(image_desc_t *im)
2218 {
2219 long i;
2220 int res=0;
2221 double X0,Y0; /* points for filled graph and more*/
2222 gfx_node_t *node;
2224 /* draw 3d border */
2225 node = gfx_new_area (im->canvas, 0,im->yimg,
2226 2,im->yimg-2,
2227 2,2,im->graph_col[GRC_SHADEA]);
2228 gfx_add_point( node , im->ximg - 2, 2 );
2229 gfx_add_point( node , im->ximg, 0 );
2230 gfx_add_point( node , 0,0 );
2231 /* gfx_add_point( node , 0,im->yimg ); */
2233 node = gfx_new_area (im->canvas, 2,im->yimg-2,
2234 im->ximg-2,im->yimg-2,
2235 im->ximg - 2, 2,
2236 im->graph_col[GRC_SHADEB]);
2237 gfx_add_point( node , im->ximg,0);
2238 gfx_add_point( node , im->ximg,im->yimg);
2239 gfx_add_point( node , 0,im->yimg);
2240 /* gfx_add_point( node , 0,im->yimg ); */
2243 if (im->draw_x_grid == 1 )
2244 vertical_grid(im);
2246 if (im->draw_y_grid == 1){
2247 if(im->logarithmic){
2248 res = horizontal_log_grid(im);
2249 } else {
2250 res = draw_horizontal_grid(im);
2251 }
2253 /* dont draw horizontal grid if there is no min and max val */
2254 if (! res ) {
2255 char *nodata = "No Data found";
2256 gfx_new_text(im->canvas,im->ximg/2, (2*im->yorigin-im->ysize) / 2,
2257 im->graph_col[GRC_FONT],
2258 im->text_prop[TEXT_PROP_AXIS].font,
2259 im->text_prop[TEXT_PROP_AXIS].size,
2260 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
2261 nodata );
2262 }
2263 }
2265 /* yaxis unit description */
2266 if (im->ylegend[0] != '\0'){
2267 gfx_new_text( im->canvas,
2268 10, (im->yorigin - im->ysize/2),
2269 im->graph_col[GRC_FONT],
2270 im->text_prop[TEXT_PROP_UNIT].font,
2271 im->text_prop[TEXT_PROP_UNIT].size, im->tabwidth,
2272 RRDGRAPH_YLEGEND_ANGLE,
2273 GFX_H_LEFT, GFX_V_CENTER,
2274 im->ylegend);
2275 }
2276 if (im->second_axis_legend[0] != '\0'){
2277 double Xylabel=gfx_get_text_width(im->canvas, 0,
2278 im->text_prop[TEXT_PROP_AXIS].font,
2279 im->text_prop[TEXT_PROP_AXIS].size,
2280 im->tabwidth,
2281 "0", 0) * im->unitslength
2282 + im->text_prop[TEXT_PROP_UNIT].size *2;
2283 gfx_new_text( im->canvas,
2284 im->xorigin+im->xsize+Xylabel+4, (im->yorigin - im->ysize/2),
2285 im->graph_col[GRC_FONT],
2286 im->text_prop[TEXT_PROP_UNIT].font,
2287 im->text_prop[TEXT_PROP_UNIT].size, im->tabwidth,
2288 RRDGRAPH_YLEGEND_ANGLE,
2289 GFX_H_LEFT, GFX_V_CENTER,
2290 im->second_axis_legend);
2291 }
2292 /* graph title */
2293 gfx_new_text( im->canvas,
2294 im->ximg/2, im->text_prop[TEXT_PROP_TITLE].size*1.3+4,
2295 im->graph_col[GRC_FONT],
2296 im->text_prop[TEXT_PROP_TITLE].font,
2297 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
2298 GFX_H_CENTER, GFX_V_CENTER,
2299 im->title);
2300 /* rrdtool 'logo' */
2301 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
2302 gfx_new_text( im->canvas,
2303 im->ximg-7, 7,
2304 ( im->graph_col[GRC_FONT] & 0xffffff00 ) | 0x00000044,
2305 im->text_prop[TEXT_PROP_AXIS].font,
2306 5.5, im->tabwidth, 270,
2307 GFX_H_RIGHT, GFX_V_TOP,
2308 "RRDTOOL / TOBI OETIKER");
2309 }
2310 /* graph watermark */
2311 if(im->watermark[0] != '\0') {
2312 gfx_new_text( im->canvas,
2313 im->ximg/2, im->yimg-6,
2314 ( im->graph_col[GRC_FONT] & 0xffffff00 ) | 0x00000044,
2315 im->text_prop[TEXT_PROP_AXIS].font,
2316 5.5, im->tabwidth, 0,
2317 GFX_H_CENTER, GFX_V_BOTTOM,
2318 im->watermark);
2319 }
2321 /* graph labels */
2322 if( !(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH) ) {
2323 for(i=0;i<im->gdes_c;i++){
2324 if(im->gdes[i].legend[0] =='\0')
2325 continue;
2327 /* im->gdes[i].leg_y is the bottom of the legend */
2328 X0 = im->gdes[i].leg_x;
2329 Y0 = im->gdes[i].leg_y;
2330 gfx_new_text ( im->canvas, X0, Y0,
2331 im->graph_col[GRC_FONT],
2332 im->text_prop[TEXT_PROP_LEGEND].font,
2333 im->text_prop[TEXT_PROP_LEGEND].size,
2334 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
2335 im->gdes[i].legend );
2336 /* The legend for GRAPH items starts with "M " to have
2337 enough space for the box */
2338 if ( im->gdes[i].gf != GF_PRINT &&
2339 im->gdes[i].gf != GF_GPRINT &&
2340 im->gdes[i].gf != GF_COMMENT) {
2341 int boxH, boxV;
2343 boxH = gfx_get_text_width(im->canvas, 0,
2344 im->text_prop[TEXT_PROP_LEGEND].font,
2345 im->text_prop[TEXT_PROP_LEGEND].size,
2346 im->tabwidth,"o", 0) * 1.2;
2347 boxV = boxH*1.1;
2349 /* make sure transparent colors show up the same way as in the graph */
2350 node = gfx_new_area(im->canvas,
2351 X0,Y0-boxV,
2352 X0,Y0,
2353 X0+boxH,Y0,
2354 im->graph_col[GRC_BACK]);
2355 gfx_add_point ( node, X0+boxH, Y0-boxV );
2357 node = gfx_new_area(im->canvas,
2358 X0,Y0-boxV,
2359 X0,Y0,
2360 X0+boxH,Y0,
2361 im->gdes[i].col);
2362 gfx_add_point ( node, X0+boxH, Y0-boxV );
2363 node = gfx_new_line(im->canvas,
2364 X0,Y0-boxV,
2365 X0,Y0,
2366 1.0,im->graph_col[GRC_FRAME]);
2367 gfx_add_point(node,X0+boxH,Y0);
2368 gfx_add_point(node,X0+boxH,Y0-boxV);
2369 gfx_close_path(node);
2370 }
2371 }
2372 }
2373 }
2376 /*****************************************************
2377 * lazy check make sure we rely need to create this graph
2378 *****************************************************/
2380 int lazy_check(image_desc_t *im){
2381 FILE *fd = NULL;
2382 int size = 1;
2383 struct stat imgstat;
2385 if (im->lazy == 0) return 0; /* no lazy option */
2386 if (stat(im->graphfile,&imgstat) != 0)
2387 return 0; /* can't stat */
2388 /* one pixel in the existing graph is more then what we would
2389 change here ... */
2390 if (time(NULL) - imgstat.st_mtime >
2391 (im->end - im->start) / im->xsize)
2392 return 0;
2393 if ((fd = fopen(im->graphfile,"rb")) == NULL)
2394 return 0; /* the file does not exist */
2395 switch (im->canvas->imgformat) {
2396 case IF_PNG:
2397 size = PngSize(fd,&(im->ximg),&(im->yimg));
2398 break;
2399 default:
2400 size = 1;
2401 }
2402 fclose(fd);
2403 return size;
2404 }
2406 #ifdef WITH_PIECHART
2407 void
2408 pie_part(image_desc_t *im, gfx_color_t color,
2409 double PieCenterX, double PieCenterY, double Radius,
2410 double startangle, double endangle)
2411 {
2412 gfx_node_t *node;
2413 double angle;
2414 double step=M_PI/50; /* Number of iterations for the circle;
2415 ** 10 is definitely too low, more than
2416 ** 50 seems to be overkill
2417 */
2419 /* Strange but true: we have to work clockwise or else
2420 ** anti aliasing nor transparency don't work.
2421 **
2422 ** This test is here to make sure we do it right, also
2423 ** this makes the for...next loop more easy to implement.
2424 ** The return will occur if the user enters a negative number
2425 ** (which shouldn't be done according to the specs) or if the
2426 ** programmers do something wrong (which, as we all know, never
2427 ** happens anyway :)
2428 */
2429 if (endangle<startangle) return;
2431 /* Hidden feature: Radius decreases each full circle */
2432 angle=startangle;
2433 while (angle>=2*M_PI) {
2434 angle -= 2*M_PI;
2435 Radius *= 0.8;
2436 }
2438 node=gfx_new_area(im->canvas,
2439 PieCenterX+sin(startangle)*Radius,
2440 PieCenterY-cos(startangle)*Radius,
2441 PieCenterX,
2442 PieCenterY,
2443 PieCenterX+sin(endangle)*Radius,
2444 PieCenterY-cos(endangle)*Radius,
2445 color);
2446 for (angle=endangle;angle-startangle>=step;angle-=step) {
2447 gfx_add_point(node,
2448 PieCenterX+sin(angle)*Radius,
2449 PieCenterY-cos(angle)*Radius );
2450 }
2451 }
2453 #endif
2455 int
2456 graph_size_location(image_desc_t *im, int elements
2458 #ifdef WITH_PIECHART
2459 , int piechart
2460 #endif
2462 )
2463 {
2464 /* The actual size of the image to draw is determined from
2465 ** several sources. The size given on the command line is
2466 ** the graph area but we need more as we have to draw labels
2467 ** and other things outside the graph area
2468 */
2470 /* +-+-------------------------------------------+
2471 ** |l|.................title.....................|
2472 ** |e+--+-------------------------------+--------+
2473 ** |b| b| | |
2474 ** |a| a| | pie |
2475 ** |l| l| main graph area | chart |
2476 ** |.| .| | area |
2477 ** |t| y| | |
2478 ** |r+--+-------------------------------+--------+
2479 ** |e| | x-axis labels | |
2480 ** |v+--+-------------------------------+--------+
2481 ** | |..............legends......................|
2482 ** +-+-------------------------------------------+
2483 ** | watermark |
2484 ** +---------------------------------------------+
2485 */
2486 int Xvertical=0,
2487 Ytitle =0,
2488 Xylabel =0,
2489 Xmain =0, Ymain =0,
2490 #ifdef WITH_PIECHART
2491 Xpie =0, Ypie =0,
2492 #endif
2493 Yxlabel =0,
2494 #if 0
2495 Xlegend =0, Ylegend =0,
2496 #endif
2497 Xspacing =15, Yspacing =15,
2499 Ywatermark =4;
2501 if (im->extra_flags & ONLY_GRAPH) {
2502 im->xorigin =0;
2503 im->ximg = im->xsize;
2504 im->yimg = im->ysize;
2505 im->yorigin = im->ysize;
2506 ytr(im,DNAN);
2507 return 0;
2508 }
2510 if (im->ylegend[0] != '\0' ) {
2511 Xvertical = im->text_prop[TEXT_PROP_UNIT].size *2;
2512 }
2515 if (im->title[0] != '\0') {
2516 /* The title is placed "inbetween" two text lines so it
2517 ** automatically has some vertical spacing. The horizontal
2518 ** spacing is added here, on each side.
2519 */
2520 /* don't care for the with of the title
2521 Xtitle = gfx_get_text_width(im->canvas, 0,
2522 im->text_prop[TEXT_PROP_TITLE].font,
2523 im->text_prop[TEXT_PROP_TITLE].size,
2524 im->tabwidth,
2525 im->title, 0) + 2*Xspacing; */
2526 Ytitle = im->text_prop[TEXT_PROP_TITLE].size*2.6+10;
2527 }
2529 if (elements) {
2530 Xmain=im->xsize;
2531 Ymain=im->ysize;
2532 if (im->draw_x_grid) {
2533 Yxlabel=im->text_prop[TEXT_PROP_AXIS].size *2.5;
2534 }
2535 if (im->draw_y_grid || im->forceleftspace ) {
2536 Xylabel=gfx_get_text_width(im->canvas, 0,
2537 im->text_prop[TEXT_PROP_AXIS].font,
2538 im->text_prop[TEXT_PROP_AXIS].size,
2539 im->tabwidth,
2540 "0", 0) * im->unitslength;
2541 }
2542 }
2544 #ifdef WITH_PIECHART
2545 if (piechart) {
2546 im->piesize=im->xsize<im->ysize?im->xsize:im->ysize;
2547 Xpie=im->piesize;
2548 Ypie=im->piesize;
2549 }
2550 #endif
2552 /* Now calculate the total size. Insert some spacing where
2553 desired. im->xorigin and im->yorigin need to correspond
2554 with the lower left corner of the main graph area or, if
2555 this one is not set, the imaginary box surrounding the
2556 pie chart area. */
2558 /* The legend width cannot yet be determined, as a result we
2559 ** have problems adjusting the image to it. For now, we just
2560 ** forget about it at all; the legend will have to fit in the
2561 ** size already allocated.
2562 */
2563 im->ximg = Xylabel + Xmain + 2 * Xspacing;
2565 if (im->second_axis_scale != 0){
2566 im->ximg += Xylabel + Xspacing;
2567 }
2568 if (im->extra_flags & NO_RRDTOOL_TAG){
2569 im->ximg -= Xspacing;
2570 }
2572 #ifdef WITH_PIECHART
2573 im->ximg += Xpie;
2574 #endif
2576 if (Xmain) im->ximg += Xspacing;
2577 #ifdef WITH_PIECHART
2578 if (Xpie) im->ximg += Xspacing;
2579 #endif
2581 im->xorigin = Xspacing + Xylabel;
2583 /* the length of the title should not influence with width of the graph
2584 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2586 if (Xvertical) { /* unit description */
2587 im->ximg += Xvertical;
2588 im->xorigin += Xvertical;
2589 }
2590 if (im->second_axis_legend[0] != '\0' ) {
2591 im->ximg += Xvertical;
2592 }
2594 xtr(im,0);
2596 /* The vertical size is interesting... we need to compare
2597 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with
2598 ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2599 ** in order to start even thinking about Ylegend or Ywatermark.
2600 **
2601 ** Do it in three portions: First calculate the inner part,
2602 ** then do the legend, then adjust the total height of the img,
2603 ** adding space for a watermark if one exists;
2604 */
2606 /* reserve space for main and/or pie */
2608 im->yimg = Ymain + Yxlabel;
2610 #ifdef WITH_PIECHART
2611 if (im->yimg < Ypie) im->yimg = Ypie;
2612 #endif
2614 im->yorigin = im->yimg - Yxlabel;
2616 /* reserve space for the title *or* some padding above the graph */
2617 if (Ytitle) {
2618 im->yimg += Ytitle;
2619 im->yorigin += Ytitle;
2620 } else {
2621 im->yimg += 1.5*Yspacing;
2622 im->yorigin += 1.5*Yspacing;
2623 }
2624 /* reserve space for padding below the graph */
2625 im->yimg += Yspacing;
2627 /* Determine where to place the legends onto the image.
2628 ** Adjust im->yimg to match the space requirements.
2629 */
2630 if(leg_place(im)==-1)
2631 return -1;
2633 if (im->watermark[0] != '\0') {
2634 im->yimg += Ywatermark;
2635 }
2637 #if 0
2638 if (Xlegend > im->ximg) {
2639 im->ximg = Xlegend;
2640 /* reposition Pie */
2641 }
2642 #endif
2644 #ifdef WITH_PIECHART
2645 /* The pie is placed in the upper right hand corner,
2646 ** just below the title (if any) and with sufficient
2647 ** padding.
2648 */
2649 if (elements) {
2650 im->pie_x = im->ximg - Xspacing - Xpie/2;
2651 im->pie_y = im->yorigin-Ymain+Ypie/2;
2652 } else {
2653 im->pie_x = im->ximg/2;
2654 im->pie_y = im->yorigin-Ypie/2;
2655 }
2656 #endif
2658 ytr(im,DNAN);
2659 return 0;
2660 }
2662 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
2663 /* yes we are loosing precision by doing tos with floats instead of doubles
2664 but it seems more stable this way. */
2667 /* draw that picture thing ... */
2668 int
2669 graph_paint(image_desc_t *im, char ***calcpr)
2670 {
2671 int i,ii;
2672 int lazy = lazy_check(im);
2673 #ifdef WITH_PIECHART
2674 int piechart = 0;
2675 double PieStart=0.0;
2676 #endif
2677 FILE *fo;
2678 gfx_node_t *node;
2680 double areazero = 0.0;
2681 graph_desc_t *lastgdes = NULL;
2683 /* if we are lazy and there is nothing to PRINT ... quit now */
2684 if (lazy && im->prt_c==0) return 0;
2686 /* pull the data from the rrd files ... */
2688 if(data_fetch(im)==-1)
2689 return -1;
2691 /* evaluate VDEF and CDEF operations ... */
2692 if(data_calc(im)==-1)
2693 return -1;
2695 #ifdef WITH_PIECHART
2696 /* check if we need to draw a piechart */
2697 for(i=0;i<im->gdes_c;i++){
2698 if (im->gdes[i].gf == GF_PART) {
2699 piechart=1;
2700 break;
2701 }
2702 }
2703 #endif
2705 /* calculate and PRINT and GPRINT definitions. We have to do it at
2706 * this point because it will affect the length of the legends
2707 * if there are no graph elements we stop here ...
2708 * if we are lazy, try to quit ...
2709 */
2710 i=print_calc(im,calcpr);
2711 if(i<0) return -1;
2712 if(((i==0)
2713 #ifdef WITH_PIECHART
2714 &&(piechart==0)
2715 #endif
2716 ) || lazy) return 0;
2718 #ifdef WITH_PIECHART
2719 /* If there's only the pie chart to draw, signal this */
2720 if (i==0) piechart=2;
2721 #endif
2723 /* get actual drawing data and find min and max values*/
2724 if(data_proc(im)==-1)
2725 return -1;
2727 if(!im->logarithmic){si_unit(im);} /* identify si magnitude Kilo, Mega Giga ? */
2729 if(!im->rigid && ! im->logarithmic)
2730 expand_range(im); /* make sure the upper and lower limit are
2731 sensible values */
2733 if (!calc_horizontal_grid(im))
2734 return -1;
2736 if (im->gridfit)
2737 apply_gridfit(im);
2740 /**************************************************************
2741 *** Calculating sizes and locations became a bit confusing ***
2742 *** so I moved this into a separate function. ***
2743 **************************************************************/
2744 if(graph_size_location(im,i
2745 #ifdef WITH_PIECHART
2746 ,piechart
2747 #endif
2748 )==-1)
2749 return -1;
2751 /* the actual graph is created by going through the individual
2752 graph elements and then drawing them */
2754 node=gfx_new_area ( im->canvas,
2755 0, 0,
2756 0, im->yimg,
2757 im->ximg, im->yimg,
2758 im->graph_col[GRC_BACK]);
2760 gfx_add_point(node,im->ximg, 0);
2762 #ifdef WITH_PIECHART
2763 if (piechart != 2) {
2764 #endif
2765 node=gfx_new_area ( im->canvas,
2766 im->xorigin, im->yorigin,
2767 im->xorigin + im->xsize, im->yorigin,
2768 im->xorigin + im->xsize, im->yorigin-im->ysize,
2769 im->graph_col[GRC_CANVAS]);
2771 gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
2773 if (im->minval > 0.0)
2774 areazero = im->minval;
2775 if (im->maxval < 0.0)
2776 areazero = im->maxval;
2777 #ifdef WITH_PIECHART
2778 }
2779 #endif
2781 #ifdef WITH_PIECHART
2782 if (piechart) {
2783 pie_part(im,im->graph_col[GRC_CANVAS],im->pie_x,im->pie_y,im->piesize*0.5,0,2*M_PI);
2784 }
2785 #endif
2787 for(i=0;i<im->gdes_c;i++){
2788 switch(im->gdes[i].gf){
2789 case GF_CDEF:
2790 case GF_VDEF:
2791 case GF_DEF:
2792 case GF_PRINT:
2793 case GF_GPRINT:
2794 case GF_COMMENT:
2795 case GF_HRULE:
2796 case GF_VRULE:
2797 case GF_XPORT:
2798 case GF_SHIFT:
2799 break;
2800 case GF_TICK:
2801 for (ii = 0; ii < im->xsize; ii++)
2802 {
2803 if (!isnan(im->gdes[i].p_data[ii]) &&
2804 im->gdes[i].p_data[ii] != 0.0)
2805 {
2806 if (im -> gdes[i].yrule > 0 ) {
2807 gfx_new_line(im->canvas,
2808 im -> xorigin + ii, im->yorigin,
2809 im -> xorigin + ii, im->yorigin - im -> gdes[i].yrule * im -> ysize,
2810 1.0,
2811 im -> gdes[i].col );
2812 } else if ( im -> gdes[i].yrule < 0 ) {
2813 gfx_new_line(im->canvas,
2814 im -> xorigin + ii, im->yorigin - im->ysize,
2815 im -> xorigin + ii, im->yorigin - im->ysize - im->gdes[i].yrule * im->ysize,
2816 1.0,
2817 im -> gdes[i].col );
2819 }
2820 }
2821 }
2822 break;
2823 case GF_LINE:
2824 case GF_AREA:
2825 /* fix data points at oo and -oo */
2826 for(ii=0;ii<im->xsize;ii++){
2827 if (isinf(im->gdes[i].p_data[ii])){
2828 if (im->gdes[i].p_data[ii] > 0) {
2829 im->gdes[i].p_data[ii] = im->maxval ;
2830 } else {
2831 im->gdes[i].p_data[ii] = im->minval ;
2832 }
2834 }
2835 } /* for */
2837 /* *******************************************************
2838 a ___. (a,t)
2839 | | ___
2840 ____| | | |
2841 | |___|
2842 -------|--t-1--t--------------------------------
2844 if we know the value at time t was a then
2845 we draw a square from t-1 to t with the value a.
2847 ********************************************************* */
2848 if (im->gdes[i].col != 0x0){
2849 /* GF_LINE and friend */
2850 if(im->gdes[i].gf == GF_LINE ){
2851 double last_y=0.0;
2852 node = NULL;
2853 for(ii=1;ii<im->xsize;ii++){
2854 if (isnan(im->gdes[i].p_data[ii]) || (im->slopemode==1 && isnan(im->gdes[i].p_data[ii-1]))){
2855 node = NULL;
2856 continue;
2857 }
2858 if ( node == NULL ) {
2859 last_y = ytr(im,im->gdes[i].p_data[ii]);
2860 if ( im->slopemode == 0 ){
2861 node = gfx_new_line(im->canvas,
2862 ii-1+im->xorigin,last_y,
2863 ii+im->xorigin,last_y,
2864 im->gdes[i].linewidth,
2865 im->gdes[i].col);
2866 } else {
2867 node = gfx_new_line(im->canvas,
2868 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2869 ii+im->xorigin,last_y,
2870 im->gdes[i].linewidth,
2871 im->gdes[i].col);
2872 }
2873 } else {
2874 double new_y = ytr(im,im->gdes[i].p_data[ii]);
2875 if ( im->slopemode==0 && ! AlmostEqual2sComplement(new_y,last_y,4)){
2876 gfx_add_point(node,ii-1+im->xorigin,new_y);
2877 };
2878 last_y = new_y;
2879 gfx_add_point(node,ii+im->xorigin,new_y);
2880 };
2882 }
2883 } else {
2884 int idxI=-1;
2885 double *foreY=malloc(sizeof(double)*im->xsize*2);
2886 double *foreX=malloc(sizeof(double)*im->xsize*2);
2887 double *backY=malloc(sizeof(double)*im->xsize*2);
2888 double *backX=malloc(sizeof(double)*im->xsize*2);
2889 int drawem = 0;
2890 for(ii=0;ii<=im->xsize;ii++){
2891 double ybase,ytop;
2892 if ( idxI > 0 && ( drawem != 0 || ii==im->xsize)){
2893 int cntI=1;
2894 int lastI=0;
2895 while (cntI < idxI && AlmostEqual2sComplement(foreY[lastI],foreY[cntI],4) && AlmostEqual2sComplement(foreY[lastI],foreY[cntI+1],4)){cntI++;}
2896 node = gfx_new_area(im->canvas,
2897 backX[0],backY[0],
2898 foreX[0],foreY[0],
2899 foreX[cntI],foreY[cntI], im->gdes[i].col);
2900 while (cntI < idxI) {
2901 lastI = cntI;
2902 cntI++;
2903 while ( cntI < idxI && AlmostEqual2sComplement(foreY[lastI],foreY[cntI],4) && AlmostEqual2sComplement(foreY[lastI],foreY[cntI+1],4)){cntI++;}
2904 gfx_add_point(node,foreX[cntI],foreY[cntI]);
2905 }
2906 gfx_add_point(node,backX[idxI],backY[idxI]);
2907 while (idxI > 1){
2908 lastI = idxI;
2909 idxI--;
2910 while ( idxI > 1 && AlmostEqual2sComplement(backY[lastI], backY[idxI],4) && AlmostEqual2sComplement(backY[lastI],backY[idxI-1],4)){idxI--;}
2911 gfx_add_point(node,backX[idxI],backY[idxI]);
2912 }
2913 idxI=-1;
2914 drawem = 0;
2915 }
2916 if (drawem != 0){
2917 drawem = 0;
2918 idxI=-1;
2919 }
2920 if (ii == im->xsize) break;
2922 /* keep things simple for now, just draw these bars
2923 do not try to build a big and complex area */
2926 if ( im->slopemode == 0 && ii==0){
2927 continue;
2928 }
2929 if ( isnan(im->gdes[i].p_data[ii]) ) {
2930 drawem = 1;
2931 continue;
2932 }
2933 ytop = ytr(im,im->gdes[i].p_data[ii]);
2934 if ( lastgdes && im->gdes[i].stack ) {
2935 ybase = ytr(im,lastgdes->p_data[ii]);
2936 } else {
2937 ybase = ytr(im,areazero);
2938 }
2939 if ( ybase == ytop ){
2940 drawem = 1;
2941 continue;
2942 }
2943 /* every area has to be wound clock-wise,
2944 so we have to make sur base remains base */
2945 if (ybase > ytop){
2946 double extra = ytop;
2947 ytop = ybase;
2948 ybase = extra;
2949 }
2950 if ( im->slopemode == 0 ){
2951 backY[++idxI] = ybase-0.2;
2952 backX[idxI] = ii+im->xorigin-1;
2953 foreY[idxI] = ytop+0.2;
2954 foreX[idxI] = ii+im->xorigin-1;
2955 }
2956 backY[++idxI] = ybase-0.2;
2957 backX[idxI] = ii+im->xorigin;
2958 foreY[idxI] = ytop+0.2;
2959 foreX[idxI] = ii+im->xorigin;
2960 }
2961 /* close up any remaining area */
2962 free(foreY);
2963 free(foreX);
2964 free(backY);
2965 free(backX);
2966 } /* else GF_LINE */
2967 } /* if color != 0x0 */
2968 /* make sure we do not run into trouble when stacking on NaN */
2969 for(ii=0;ii<im->xsize;ii++){
2970 if (isnan(im->gdes[i].p_data[ii])) {
2971 if (lastgdes && (im->gdes[i].stack)) {
2972 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
2973 } else {
2974 im->gdes[i].p_data[ii] = areazero;
2975 }
2976 }
2977 }
2978 lastgdes = &(im->gdes[i]);
2979 break;
2980 #ifdef WITH_PIECHART
2981 case GF_PART:
2982 if(isnan(im->gdes[i].yrule)) /* fetch variable */
2983 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2985 if (finite(im->gdes[i].yrule)) { /* even the fetched var can be NaN */
2986 pie_part(im,im->gdes[i].col,
2987 im->pie_x,im->pie_y,im->piesize*0.4,
2988 M_PI*2.0*PieStart/100.0,
2989 M_PI*2.0*(PieStart+im->gdes[i].yrule)/100.0);
2990 PieStart += im->gdes[i].yrule;
2991 }
2992 break;
2993 #endif
2994 case GF_STACK:
2995 rrd_set_error("STACK should already be turned into LINE or AREA here");
2996 return -1;
2997 break;
2999 } /* switch */
3000 }
3001 #ifdef WITH_PIECHART
3002 if (piechart==2) {
3003 im->draw_x_grid=0;
3004 im->draw_y_grid=0;
3005 }
3006 #endif
3009 /* grid_paint also does the text */
3010 if( !(im->extra_flags & ONLY_GRAPH) )
3011 grid_paint(im);
3014 if( !(im->extra_flags & ONLY_GRAPH) )
3015 axis_paint(im);
3017 /* the RULES are the last thing to paint ... */
3018 for(i=0;i<im->gdes_c;i++){
3020 switch(im->gdes[i].gf){
3021 case GF_HRULE:
3022 if(im->gdes[i].yrule >= im->minval
3023 && im->gdes[i].yrule <= im->maxval)
3024 gfx_new_line(im->canvas,
3025 im->xorigin,ytr(im,im->gdes[i].yrule),
3026 im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
3027 1.0,im->gdes[i].col);
3028 break;
3029 case GF_VRULE:
3030 if(im->gdes[i].xrule >= im->start
3031 && im->gdes[i].xrule <= im->end)
3032 gfx_new_line(im->canvas,
3033 xtr(im,im->gdes[i].xrule),im->yorigin,
3034 xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
3035 1.0,im->gdes[i].col);
3036 break;
3037 default:
3038 break;
3039 }
3040 }
3043 if (strcmp(im->graphfile,"-")==0) {
3044 fo = im->graphhandle ? im->graphhandle : stdout;
3045 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3046 /* Change translation mode for stdout to BINARY */
3047 _setmode( _fileno( fo ), O_BINARY );
3048 #endif
3049 } else {
3050 if ((fo = fopen(im->graphfile,"wb")) == NULL) {
3051 rrd_set_error("Opening '%s' for write: %s",im->graphfile,
3052 rrd_strerror(errno));
3053 return (-1);
3054 }
3055 }
3056 gfx_render (im->canvas,im->ximg,im->yimg,0x00000000,fo);
3057 if (strcmp(im->graphfile,"-") != 0)
3058 fclose(fo);
3059 return 0;
3060 }
3063 /*****************************************************
3064 * graph stuff
3065 *****************************************************/
3067 int
3068 gdes_alloc(image_desc_t *im){
3070 im->gdes_c++;
3071 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
3072 * sizeof(graph_desc_t)))==NULL){
3073 rrd_set_error("realloc graph_descs");
3074 return -1;
3075 }
3078 im->gdes[im->gdes_c-1].step=im->step;
3079 im->gdes[im->gdes_c-1].step_orig=im->step;
3080 im->gdes[im->gdes_c-1].stack=0;
3081 im->gdes[im->gdes_c-1].linewidth=0;
3082 im->gdes[im->gdes_c-1].debug=0;
3083 im->gdes[im->gdes_c-1].start=im->start;
3084 im->gdes[im->gdes_c-1].start_orig=im->start;
3085 im->gdes[im->gdes_c-1].end=im->end;
3086 im->gdes[im->gdes_c-1].end_orig=im->end;
3087 im->gdes[im->gdes_c-1].vname[0]='\0';
3088 im->gdes[im->gdes_c-1].data=NULL;
3089 im->gdes[im->gdes_c-1].ds_namv=NULL;
3090 im->gdes[im->gdes_c-1].data_first=0;
3091 im->gdes[im->gdes_c-1].p_data=NULL;
3092 im->gdes[im->gdes_c-1].rpnp=NULL;
3093 im->gdes[im->gdes_c-1].shift=0;
3094 im->gdes[im->gdes_c-1].col = 0x0;
3095 im->gdes[im->gdes_c-1].legend[0]='\0';
3096 im->gdes[im->gdes_c-1].format[0]='\0';
3097 im->gdes[im->gdes_c-1].strftm=0;
3098 im->gdes[im->gdes_c-1].rrd[0]='\0';
3099 im->gdes[im->gdes_c-1].ds=-1;
3100 im->gdes[im->gdes_c-1].cf_reduce=CF_AVERAGE;
3101 im->gdes[im->gdes_c-1].cf=CF_AVERAGE;
3102 im->gdes[im->gdes_c-1].p_data=NULL;
3103 im->gdes[im->gdes_c-1].yrule=DNAN;
3104 im->gdes[im->gdes_c-1].xrule=0;
3105 return 0;
3106 }
3108 /* copies input untill the first unescaped colon is found
3109 or until input ends. backslashes have to be escaped as well */
3110 int
3111 scan_for_col(const char *const input, int len, char *const output)
3112 {
3113 int inp,outp=0;
3114 for (inp=0;
3115 inp < len &&
3116 input[inp] != ':' &&
3117 input[inp] != '\0';
3118 inp++){
3119 if (input[inp] == '\\' &&
3120 input[inp+1] != '\0' &&
3121 (input[inp+1] == '\\' ||
3122 input[inp+1] == ':')){
3123 output[outp++] = input[++inp];
3124 }
3125 else {
3126 output[outp++] = input[inp];
3127 }
3128 }
3129 output[outp] = '\0';
3130 return inp;
3131 }
3132 /* Some surgery done on this function, it became ridiculously big.
3133 ** Things moved:
3134 ** - initializing now in rrd_graph_init()
3135 ** - options parsing now in rrd_graph_options()
3136 ** - script parsing now in rrd_graph_script()
3137 */
3138 int
3139 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize, FILE *stream, double *ymin, double *ymax)
3140 {
3141 image_desc_t im;
3142 rrd_graph_init(&im);
3143 im.graphhandle = stream;
3145 rrd_graph_options(argc,argv,&im);
3146 if (rrd_test_error()) {
3147 im_free(&im);
3148 return -1;
3149 }
3151 if (strlen(argv[optind])>=MAXPATH) {
3152 rrd_set_error("filename (including path) too long");
3153 im_free(&im);
3154 return -1;
3155 }
3156 strncpy(im.graphfile,argv[optind],MAXPATH-1);
3157 im.graphfile[MAXPATH-1]='\0';
3159 rrd_graph_script(argc,argv,&im,1);
3160 if (rrd_test_error()) {
3161 im_free(&im);
3162 return -1;
3163 }
3165 /* Everything is now read and the actual work can start */
3167 (*prdata)=NULL;
3168 if (graph_paint(&im,prdata)==-1){
3169 im_free(&im);
3170 return -1;
3171 }
3173 /* The image is generated and needs to be output.
3174 ** Also, if needed, print a line with information about the image.
3175 */
3177 *xsize=im.ximg;
3178 *ysize=im.yimg;
3179 *ymin=im.minval;
3180 *ymax=im.maxval;
3181 if (im.imginfo) {
3182 char *filename;
3183 char *path;
3184 if (!(*prdata)) {
3185 /* maybe prdata is not allocated yet ... lets do it now */
3186 if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
3187 rrd_set_error("malloc imginfo");
3188 return -1;
3189 };
3190 }
3191 if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
3192 ==NULL){
3193 rrd_set_error("malloc imginfo");
3194 return -1;
3195 }
3196 path = strdup(im.graphfile);
3197 filename = basename(path);
3198 sprintf((*prdata)[0],im.imginfo,filename,(long)(im.canvas->zoom*im.ximg),(long)(im.canvas->zoom*im.yimg));
3199 free(path);
3200 }
3201 im_free(&im);
3202 return 0;
3203 }
3205 void
3206 rrd_graph_init(image_desc_t *im)
3207 {
3208 unsigned int i;
3210 #ifdef HAVE_TZSET
3211 tzset();
3212 #endif
3213 #ifdef HAVE_SETLOCALE
3214 setlocale(LC_TIME,"");
3215 #ifdef HAVE_MBSTOWCS
3216 setlocale(LC_CTYPE,"");
3217 #endif
3218 #endif
3219 im->yorigin=0;
3220 im->xorigin=0;
3221 im->minval=0;
3222 im->xlab_user.minsec = -1;
3223 im->ximg=0;
3224 im->yimg=0;
3225 im->xsize = 400;
3226 im->ysize = 100;
3227 im->step = 0;
3228 im->ylegend[0] = '\0';
3229 im->second_axis_scale = 0; /* 0 disables it */
3230 im->second_axis_shift = 0; /* no shift by default */
3231 im->second_axis_legend[0] = '\0';
3232 im->second_axis_format[0] = '\0';
3233 im->title[0] = '\0';
3234 im->watermark[0] = '\0';
3235 im->minval = DNAN;
3236 im->maxval = DNAN;
3237 im->unitsexponent= 9999;
3238 im->unitslength= 6;
3239 im->forceleftspace = 0;
3240 im->symbol = ' ';
3241 im->viewfactor = 1.0;
3242 im->extra_flags= 0;
3243 im->rigid = 0;
3244 im->gridfit = 1;
3245 im->imginfo = NULL;
3246 im->lazy = 0;
3247 im->slopemode = 0;
3248 im->logarithmic = 0;
3249 im->ygridstep = DNAN;
3250 im->draw_x_grid = 1;
3251 im->draw_y_grid = 1;
3252 im->base = 1000;
3253 im->prt_c = 0;
3254 im->gdes_c = 0;
3255 im->gdes = NULL;
3256 im->canvas = gfx_new_canvas();
3257 im->grid_dash_on = 1;
3258 im->grid_dash_off = 1;
3259 im->tabwidth = 40.0;
3261 for(i=0;i<DIM(graph_col);i++)
3262 im->graph_col[i]=graph_col[i];
3264 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3265 {
3266 char *windir;
3267 char rrd_win_default_font[1000];
3268 windir = getenv("windir");
3269 /* %windir% is something like D:\windows or C:\winnt */
3270 if (windir != NULL) {
3271 strncpy(rrd_win_default_font,windir,500);
3272 rrd_win_default_font[500] = '\0';
3273 strcat(rrd_win_default_font,"\\fonts\\");
3274 strcat(rrd_win_default_font,RRD_DEFAULT_FONT);
3275 for(i=0;i<DIM(text_prop);i++){
3276 strncpy(text_prop[i].font,rrd_win_default_font,sizeof(text_prop[i].font)-1);
3277 text_prop[i].font[sizeof(text_prop[i].font)-1] = '\0';
3278 }
3279 }
3280 }
3281 #endif
3282 {
3283 char *deffont;
3284 deffont = getenv("RRD_DEFAULT_FONT");
3285 if (deffont != NULL) {
3286 for(i=0;i<DIM(text_prop);i++){
3287 strncpy(text_prop[i].font,deffont,sizeof(text_prop[i].font)-1);
3288 text_prop[i].font[sizeof(text_prop[i].font)-1] = '\0';
3289 }
3290 }
3291 }
3292 for(i=0;i<DIM(text_prop);i++){
3293 im->text_prop[i].size = text_prop[i].size;
3294 strcpy(im->text_prop[i].font,text_prop[i].font);
3295 }
3296 }
3298 void
3299 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
3300 {
3301 int stroff;
3302 char *parsetime_error = NULL;
3303 char scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
3304 time_t start_tmp=0,end_tmp=0;
3305 long long_tmp;
3306 struct rrd_time_value start_tv, end_tv;
3307 gfx_color_t color;
3308 optind = 0; opterr = 0; /* initialize getopt */
3310 parsetime("end-24h", &start_tv);
3311 parsetime("now", &end_tv);
3313 /* defines for long options without a short equivalent. should be bytes,
3314 and may not collide with (the ASCII value of) short options */
3315 #define LONGOPT_UNITS_SI 255
3317 while (1){
3318 static struct option long_options[] =
3319 {
3320 {"start", required_argument, 0, 's'},
3321 {"end", required_argument, 0, 'e'},
3322 {"x-grid", required_argument, 0, 'x'},
3323 {"y-grid", required_argument, 0, 'y'},
3324 {"vertical-label",required_argument,0,'v'},
3325 {"width", required_argument, 0, 'w'},
3326 {"height", required_argument, 0, 'h'},
3327 {"interlaced", no_argument, 0, 'i'},
3328 {"upper-limit",required_argument, 0, 'u'},
3329 {"lower-limit",required_argument, 0, 'l'},
3330 {"rigid", no_argument, 0, 'r'},
3331 {"base", required_argument, 0, 'b'},
3332 {"logarithmic",no_argument, 0, 'o'},
3333 {"color", required_argument, 0, 'c'},
3334 {"font", required_argument, 0, 'n'},
3335 {"title", required_argument, 0, 't'},
3336 {"imginfo", required_argument, 0, 'f'},
3337 {"imgformat", required_argument, 0, 'a'},
3338 {"lazy", no_argument, 0, 'z'},
3339 {"zoom", required_argument, 0, 'm'},
3340 {"no-legend", no_argument, 0, 'g'},
3341 {"force-rules-legend",no_argument,0, 'F'},
3342 {"only-graph", no_argument, 0, 'j'},
3343 {"alt-y-grid", no_argument, 0, 'Y'},
3344 {"no-minor", no_argument, 0, 'I'},
3345 {"slope-mode", no_argument, 0, 'E'},
3346 {"alt-autoscale", no_argument, 0, 'A'},
3347 {"alt-autoscale-min", no_argument, 0, 'J'},
3348 {"alt-autoscale-max", no_argument, 0, 'M'},
3349 {"no-gridfit", no_argument, 0, 'N'},
3350 {"units-exponent",required_argument, 0, 'X'},
3351 {"units-length",required_argument, 0, 'L'},
3352 {"units", required_argument, 0, LONGOPT_UNITS_SI },
3353 {"step", required_argument, 0, 'S'},
3354 {"tabwidth", required_argument, 0, 'T'},
3355 {"font-render-mode", required_argument, 0, 'R'},
3356 {"font-smoothing-threshold", required_argument, 0, 'B'},
3357 {"watermark", required_argument, 0, 'W'},
3358 {"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 */
3359 {"disable-rrdtool-tag", no_argument, 0, 1001},
3360 {"right-axis", required_argument, 0, 1002},
3361 {"right-axis-label", required_argument, 0, 1003},
3362 {"right-axis-format", required_argument, 0, 1004},
3363 {0,0,0,0}};
3364 int option_index = 0;
3365 int opt;
3366 int col_start,col_end;
3368 opt = getopt_long(argc, argv,
3369 "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:I:zgjFYAMEX:L:S:T:NR:B:W:",
3370 long_options, &option_index);
3372 if (opt == EOF)
3373 break;
3375 switch(opt) {
3376 case 'I':
3377 im->extra_flags |= NOMINOR;
3378 break;
3379 case 'Y':
3380 im->extra_flags |= ALTYGRID;
3381 break;
3382 case 'A':
3383 im->extra_flags |= ALTAUTOSCALE;
3384 break;
3385 case 'J':
3386 im->extra_flags |= ALTAUTOSCALE_MIN;
3387 break;
3388 case 'M':
3389 im->extra_flags |= ALTAUTOSCALE_MAX;
3390 break;
3391 case 'j':
3392 im->extra_flags |= ONLY_GRAPH;
3393 break;
3394 case 'g':
3395 im->extra_flags |= NOLEGEND;
3396 break;
3397 case 'F':
3398 im->extra_flags |= FORCE_RULES_LEGEND;
3399 break;
3400 case 1001:
3401 im->extra_flags |= NO_RRDTOOL_TAG;
3402 break;
3403 case LONGOPT_UNITS_SI:
3404 if(im->extra_flags & FORCE_UNITS) {
3405 rrd_set_error("--units can only be used once!");
3406 return;
3407 }
3408 if(strcmp(optarg,"si")==0)
3409 im->extra_flags |= FORCE_UNITS_SI;
3410 else {
3411 rrd_set_error("invalid argument for --units: %s", optarg );
3412 return;
3413 }
3414 break;
3415 case 'X':
3416 im->unitsexponent = atoi(optarg);
3417 break;
3418 case 'L':
3419 im->unitslength = atoi(optarg);
3420 im->forceleftspace = 1;
3421 break;
3422 case 'T':
3423 im->tabwidth = atof(optarg);
3424 break;
3425 case 'S':
3426 im->step = atoi(optarg);
3427 break;
3428 case 'N':
3429 im->gridfit = 0;
3430 break;
3431 case 's':
3432 if ((parsetime_error = parsetime(optarg, &start_tv))) {
3433 rrd_set_error( "start time: %s", parsetime_error );
3434 return;
3435 }
3436 break;
3437 case 'e':
3438 if ((parsetime_error = parsetime(optarg, &end_tv))) {
3439 rrd_set_error( "end time: %s", parsetime_error );
3440 return;
3441 }
3442 break;
3443 case 'x':
3444 if(strcmp(optarg,"none") == 0){
3445 im->draw_x_grid=0;
3446 break;
3447 };
3449 if(sscanf(optarg,
3450 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3451 scan_gtm,
3452 &im->xlab_user.gridst,
3453 scan_mtm,
3454 &im->xlab_user.mgridst,
3455 scan_ltm,
3456 &im->xlab_user.labst,
3457 &im->xlab_user.precis,
3458 &stroff) == 7 && stroff != 0){
3459 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
3460 im->xlab_form[sizeof(im->xlab_form)-1] = '\0';
3461 if((int)(im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
3462 rrd_set_error("unknown keyword %s",scan_gtm);
3463 return;
3464 } else if ((int)(im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
3465 rrd_set_error("unknown keyword %s",scan_mtm);
3466 return;
3467 } else if ((int)(im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
3468 rrd_set_error("unknown keyword %s",scan_ltm);
3469 return;
3470 }
3471 im->xlab_user.minsec = 1;
3472 im->xlab_user.stst = im->xlab_form;
3473 } else {
3474 rrd_set_error("invalid x-grid format");
3475 return;
3476 }
3477 break;
3478 case 'y':
3480 if(strcmp(optarg,"none") == 0){
3481 im->draw_y_grid=0;
3482 break;
3483 };
3485 if(sscanf(optarg,
3486 "%lf:%d",
3487 &im->ygridstep,
3488 &im->ylabfact) == 2) {
3489 if(im->ygridstep<=0){
3490 rrd_set_error("grid step must be > 0");
3491 return;
3492 } else if (im->ylabfact < 1){
3493 rrd_set_error("label factor must be > 0");
3494 return;
3495 }
3496 } else {
3497 rrd_set_error("invalid y-grid format");
3498 return;
3499 }
3500 break;
3501 case 1002: /* right y axis */
3503 if(sscanf(optarg,
3504 "%lf:%lf",
3505 &im->second_axis_scale,
3506 &im->second_axis_shift) == 2) {
3507 if(im->second_axis_scale==0){
3508 rrd_set_error("the second_axis_scale must not be 0");
3509 return;
3510 }
3511 } else {
3512 rrd_set_error("invalid right-axis format expected scale:shift");
3513 return;
3514 }
3515 break;
3516 case 1003:
3517 strncpy(im->second_axis_legend,optarg,150);
3518 im->second_axis_legend[150]='\0';
3519 break;
3520 case 1004:
3521 if (bad_format(optarg)){
3522 rrd_set_error("use either %le or %lf formats");
3523 return;
3524 }
3525 strncpy(im->second_axis_format,optarg,150);
3526 im->second_axis_format[150]='\0';
3527 break;
3528 case 'v':
3529 strncpy(im->ylegend,optarg,150);
3530 im->ylegend[150]='\0';
3531 break;
3532 case 'u':
3533 im->maxval = atof(optarg);
3534 break;
3535 case 'l':
3536 im->minval = atof(optarg);
3537 break;
3538 case 'b':
3539 im->base = atol(optarg);
3540 if(im->base != 1024 && im->base != 1000 ){
3541 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
3542 return;
3543 }
3544 break;
3545 case 'w':
3546 long_tmp = atol(optarg);
3547 if (long_tmp < 10) {
3548 rrd_set_error("width below 10 pixels");
3549 return;
3550 }
3551 im->xsize = long_tmp;
3552 break;
3553 case 'h':
3554 long_tmp = atol(optarg);
3555 if (long_tmp < 10) {
3556 rrd_set_error("height below 10 pixels");
3557 return;
3558 }
3559 im->ysize = long_tmp;
3560 break;
3561 case 'i':
3562 im->canvas->interlaced = 1;
3563 break;
3564 case 'r':
3565 im->rigid = 1;
3566 break;
3567 case 'f':
3568 im->imginfo = optarg;
3569 break;
3570 case 'a':
3571 if((int)(im->canvas->imgformat = if_conv(optarg)) == -1) {
3572 rrd_set_error("unsupported graphics format '%s'",optarg);
3573 return;
3574 }
3575 break;
3576 case 'z':
3577 im->lazy = 1;
3578 break;
3579 case 'E':
3580 im->slopemode = 1;
3581 break;
3583 case 'o':
3584 im->logarithmic = 1;
3585 break;
3586 case 'c':
3587 if(sscanf(optarg,
3588 "%10[A-Z]#%n%8lx%n",
3589 col_nam,&col_start,&color,&col_end) == 2){
3590 int ci;
3591 int col_len = col_end - col_start;
3592 switch (col_len){
3593 case 3:
3594 color = (
3595 ((color & 0xF00) * 0x110000) |
3596 ((color & 0x0F0) * 0x011000) |
3597 ((color & 0x00F) * 0x001100) |
3598 0x000000FF
3599 );
3600 break;
3601 case 4:
3602 color = (
3603 ((color & 0xF000) * 0x11000) |
3604 ((color & 0x0F00) * 0x01100) |
3605 ((color & 0x00F0) * 0x00110) |
3606 ((color & 0x000F) * 0x00011)
3607 );
3608 break;
3609 case 6:
3610 color = (color << 8) + 0xff /* shift left by 8 */;
3611 break;
3612 case 8:
3613 break;
3614 default:
3615 rrd_set_error("the color format is #RRGGBB[AA]");
3616 return;
3617 }
3618 if((ci=grc_conv(col_nam)) != -1){
3619 im->graph_col[ci]=color;
3620 } else {
3621 rrd_set_error("invalid color name '%s'",col_nam);
3622 return;
3623 }
3624 } else {
3625 rrd_set_error("invalid color def format");
3626 return;
3627 }
3628 break;
3629 case 'n':{
3630 char prop[15];
3631 double size = 1;
3632 int end;
3633 if(sscanf(optarg,
3634 "%10[A-Z]:%lf%n",
3635 prop,&size,&end) >= 2){
3636 int sindex,propidx;
3637 if((sindex=text_prop_conv(prop)) != -1){
3638 for (propidx=sindex;propidx<TEXT_PROP_LAST;propidx++){
3639 if (size > 0){
3640 im->text_prop[propidx].size=size;
3641 }
3642 if ((int)strlen(optarg) > end+2){
3643 if (optarg[end] == ':'){
3644 strncpy(im->text_prop[propidx].font,optarg+end+1,255);
3645 im->text_prop[propidx].font[255] = '\0';
3646 } else {
3647 rrd_set_error("expected : after font size in '%s'",optarg);
3648 return;
3649 }
3650 }
3651 /* only run the for loop for DEFAULT (0) for
3652 all others, we break here. woodo programming */
3653 if (propidx==sindex && sindex != 0) break;
3654 }
3655 } else {
3656 rrd_set_error("invalid fonttag '%s'",prop);
3657 return;
3658 }
3659 } else {
3660 rrd_set_error("invalid text property format");
3661 return;
3662 }
3663 break;
3664 }
3665 case 'm':
3666 im->canvas->zoom = atof(optarg);
3667 if (im->canvas->zoom <= 0.0) {
3668 rrd_set_error("zoom factor must be > 0");
3669 return;
3670 }
3671 break;
3672 case 't':
3673 strncpy(im->title,optarg,150);
3674 im->title[150]='\0';
3675 break;
3677 case 'R':
3678 if ( strcmp( optarg, "normal" ) == 0 )
3679 im->canvas->aa_type = AA_NORMAL;
3680 else if ( strcmp( optarg, "light" ) == 0 )
3681 im->canvas->aa_type = AA_LIGHT;
3682 else if ( strcmp( optarg, "mono" ) == 0 )
3683 im->canvas->aa_type = AA_NONE;
3684 else
3685 {
3686 rrd_set_error("unknown font-render-mode '%s'", optarg );
3687 return;
3688 }
3689 break;
3691 case 'B':
3692 im->canvas->font_aa_threshold = atof(optarg);
3693 break;
3695 case 'W':
3696 strncpy(im->watermark,optarg,100);
3697 im->watermark[99]='\0';
3698 break;
3700 case '?':
3701 if (optopt != 0)
3702 rrd_set_error("unknown option '%c'", optopt);
3703 else
3704 rrd_set_error("unknown option '%s'",argv[optind-1]);
3705 return;
3706 }
3707 }
3709 if (optind >= argc) {
3710 rrd_set_error("missing filename");
3711 return;
3712 }
3714 if (im->logarithmic == 1 && im->minval <= 0){
3715 rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");
3716 return;
3717 }
3719 if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
3720 /* error string is set in parsetime.c */
3721 return;
3722 }
3724 if (start_tmp < 3600*24*365*10){
3725 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
3726 return;
3727 }
3729 if (end_tmp < start_tmp) {
3730 rrd_set_error("start (%ld) should be less than end (%ld)",
3731 start_tmp, end_tmp);
3732 return;
3733 }
3735 im->start = start_tmp;
3736 im->end = end_tmp;
3737 im->step = max((long)im->step, (im->end-im->start)/im->xsize);
3738 }
3740 int
3741 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
3742 {
3743 char *color;
3744 graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
3746 color=strstr(var,"#");
3747 if (color==NULL) {
3748 if (optional==0) {
3749 rrd_set_error("Found no color in %s",err);
3750 return 0;
3751 }
3752 return 0;
3753 } else {
3754 int n=0;
3755 char *rest;
3756 gfx_color_t col;
3758 rest=strstr(color,":");
3759 if (rest!=NULL)
3760 n=rest-color;
3761 else
3762 n=strlen(color);
3764 switch (n) {
3765 case 7:
3766 sscanf(color,"#%6lx%n",&col,&n);
3767 col = (col << 8) + 0xff /* shift left by 8 */;
3768 if (n!=7) rrd_set_error("Color problem in %s",err);
3769 break;
3770 case 9:
3771 sscanf(color,"#%8lx%n",&col,&n);
3772 if (n==9) break;
3773 default:
3774 rrd_set_error("Color problem in %s",err);
3775 }
3776 if (rrd_test_error()) return 0;
3777 gdp->col = col;
3778 return n;
3779 }
3780 }
3783 int bad_format(char *fmt) {
3784 char *ptr;
3785 int n=0;
3786 ptr = fmt;
3787 while (*ptr != '\0')
3788 if (*ptr++ == '%') {
3790 /* line cannot end with percent char */
3791 if (*ptr == '\0') return 1;
3793 /* '%s', '%S' and '%%' are allowed */
3794 if (*ptr == 's' || *ptr == 'S' || *ptr == '%') ptr++;
3796 /* %c is allowed (but use only with vdef!) */
3797 else if (*ptr == 'c') {
3798 ptr++;
3799 n=1;
3800 }
3802 /* or else '% 6.2lf' and such are allowed */
3803 else {
3804 /* optional padding character */
3805 if (*ptr == ' ' || *ptr == '+' || *ptr == '-') ptr++;
3807 /* This should take care of 'm.n' with all three optional */
3808 while (*ptr >= '0' && *ptr <= '9') ptr++;
3809 if (*ptr == '.') ptr++;
3810 while (*ptr >= '0' && *ptr <= '9') ptr++;
3812 /* Either 'le', 'lf' or 'lg' must follow here */
3813 if (*ptr++ != 'l') return 1;
3814 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g') ptr++;
3815 else return 1;
3816 n++;
3817 }
3818 }
3820 return (n!=1);
3821 }
3824 int
3825 vdef_parse(gdes,str)
3826 struct graph_desc_t *gdes;
3827 const char *const str;
3828 {
3829 /* A VDEF currently is either "func" or "param,func"
3830 * so the parsing is rather simple. Change if needed.
3831 */
3832 double param;
3833 char func[30];
3834 int n;
3836 n=0;
3837 sscanf(str,"%le,%29[A-Z]%n",¶m,func,&n);
3838 if (n== (int)strlen(str)) { /* matched */
3839 ;
3840 } else {
3841 n=0;
3842 sscanf(str,"%29[A-Z]%n",func,&n);
3843 if (n== (int)strlen(str)) { /* matched */
3844 param=DNAN;
3845 } else {
3846 rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3847 ,str
3848 ,gdes->vname
3849 );
3850 return -1;
3851 }
3852 }
3853 if (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3854 else if (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3855 else if (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3856 else if (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3857 else if (!strcmp("TOTAL", func)) gdes->vf.op = VDEF_TOTAL;
3858 else if (!strcmp("FIRST", func)) gdes->vf.op = VDEF_FIRST;
3859 else if (!strcmp("LAST", func)) gdes->vf.op = VDEF_LAST;
3860 else if (!strcmp("LSLSLOPE", func)) gdes->vf.op = VDEF_LSLSLOPE;
3861 else if (!strcmp("LSLINT", func)) gdes->vf.op = VDEF_LSLINT;
3862 else if (!strcmp("LSLCORREL",func)) gdes->vf.op = VDEF_LSLCORREL;
3863 else {
3864 rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3865 ,func
3866 ,gdes->vname
3867 );
3868 return -1;
3869 };
3871 switch (gdes->vf.op) {
3872 case VDEF_PERCENT:
3873 if (isnan(param)) { /* no parameter given */
3874 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3875 ,func
3876 ,gdes->vname
3877 );
3878 return -1;
3879 };
3880 if (param>=0.0 && param<=100.0) {
3881 gdes->vf.param = param;
3882 gdes->vf.val = DNAN; /* undefined */
3883 gdes->vf.when = 0; /* undefined */
3884 } else {
3885 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3886 ,param
3887 ,gdes->vname
3888 );
3889 return -1;
3890 };
3891 break;
3892 case VDEF_MAXIMUM:
3893 case VDEF_AVERAGE:
3894 case VDEF_MINIMUM:
3895 case VDEF_TOTAL:
3896 case VDEF_FIRST:
3897 case VDEF_LAST:
3898 case VDEF_LSLSLOPE:
3899 case VDEF_LSLINT:
3900 case VDEF_LSLCORREL:
3901 if (isnan(param)) {
3902 gdes->vf.param = DNAN;
3903 gdes->vf.val = DNAN;
3904 gdes->vf.when = 0;
3905 } else {
3906 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3907 ,func
3908 ,gdes->vname
3909 );
3910 return -1;
3911 };
3912 break;
3913 };
3914 return 0;
3915 }
3918 int
3919 vdef_calc(im,gdi)
3920 image_desc_t *im;
3921 int gdi;
3922 {
3923 graph_desc_t *src,*dst;
3924 rrd_value_t *data;
3925 long step,steps;
3927 dst = &im->gdes[gdi];
3928 src = &im->gdes[dst->vidx];
3929 data = src->data + src->ds;
3930 steps = (src->end - src->start) / src->step;
3931 #if 0
3932 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3933 ,src->start
3934 ,src->end
3935 ,steps
3936 );
3937 #endif
3939 switch (dst->vf.op) {
3940 case VDEF_PERCENT: {
3941 rrd_value_t * array;
3942 int field;
3945 if ((array = malloc(steps*sizeof(double)))==NULL) {
3946 rrd_set_error("malloc VDEV_PERCENT");
3947 return -1;
3948 }
3949 for (step=0;step < steps; step++) {
3950 array[step]=data[step*src->ds_cnt];
3951 }
3952 qsort(array,step,sizeof(double),vdef_percent_compar);
3954 field = (steps-1)*dst->vf.param/100;
3955 dst->vf.val = array[field];
3956 dst->vf.when = 0; /* no time component */
3957 free(array);
3958 #if 0
3959 for(step=0;step<steps;step++)
3960 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3961 #endif
3962 }
3963 break;
3964 case VDEF_MAXIMUM:
3965 step=0;
3966 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3967 if (step == steps) {
3968 dst->vf.val = DNAN;
3969 dst->vf.when = 0;
3970 } else {
3971 dst->vf.val = data[step*src->ds_cnt];
3972 dst->vf.when = src->start + (step+1)*src->step;
3973 }
3974 while (step != steps) {
3975 if (finite(data[step*src->ds_cnt])) {
3976 if (data[step*src->ds_cnt] > dst->vf.val) {
3977 dst->vf.val = data[step*src->ds_cnt];
3978 dst->vf.when = src->start + (step+1)*src->step;
3979 }
3980 }
3981 step++;
3982 }
3983 break;
3984 case VDEF_TOTAL:
3985 case VDEF_AVERAGE: {
3986 int cnt=0;
3987 double sum=0.0;
3988 for (step=0;step<steps;step++) {
3989 if (finite(data[step*src->ds_cnt])) {
3990 sum += data[step*src->ds_cnt];
3991 cnt ++;
3992 };
3993 }
3994 if (cnt) {
3995 if (dst->vf.op == VDEF_TOTAL) {
3996 dst->vf.val = sum*src->step;
3997 dst->vf.when = 0; /* no time component */
3998 } else {
3999 dst->vf.val = sum/cnt;
4000 dst->vf.when = 0; /* no time component */
4001 };
4002 } else {
4003 dst->vf.val = DNAN;
4004 dst->vf.when = 0;
4005 }
4006 }
4007 break;
4008 case VDEF_MINIMUM:
4009 step=0;
4010 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
4011 if (step == steps) {
4012 dst->vf.val = DNAN;
4013 dst->vf.when = 0;
4014 } else {
4015 dst->vf.val = data[step*src->ds_cnt];
4016 dst->vf.when = src->start + (step+1)*src->step;
4017 }
4018 while (step != steps) {
4019 if (finite(data[step*src->ds_cnt])) {
4020 if (data[step*src->ds_cnt] < dst->vf.val) {
4021 dst->vf.val = data[step*src->ds_cnt];
4022 dst->vf.when = src->start + (step+1)*src->step;
4023 }
4024 }
4025 step++;
4026 }
4027 break;
4028 case VDEF_FIRST:
4029 /* The time value returned here is one step before the
4030 * actual time value. This is the start of the first
4031 * non-NaN interval.
4032 */
4033 step=0;
4034 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
4035 if (step == steps) { /* all entries were NaN */
4036 dst->vf.val = DNAN;
4037 dst->vf.when = 0;
4038 } else {
4039 dst->vf.val = data[step*src->ds_cnt];
4040 dst->vf.when = src->start + step*src->step;
4041 }
4042 break;
4043 case VDEF_LAST:
4044 /* The time value returned here is the
4045 * actual time value. This is the end of the last
4046 * non-NaN interval.
4047 */
4048 step=steps-1;
4049 while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
4050 if (step < 0) { /* all entries were NaN */
4051 dst->vf.val = DNAN;
4052 dst->vf.when = 0;
4053 } else {
4054 dst->vf.val = data[step*src->ds_cnt];
4055 dst->vf.when = src->start + (step+1)*src->step;
4056 }
4057 break;
4058 case VDEF_LSLSLOPE:
4059 case VDEF_LSLINT:
4060 case VDEF_LSLCORREL:{
4061 /* Bestfit line by linear least squares method */
4063 int cnt=0;
4064 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl ;
4065 SUMx = 0; SUMy = 0; SUMxy = 0; SUMxx = 0; SUMyy = 0;
4067 for (step=0;step<steps;step++) {
4068 if (finite(data[step*src->ds_cnt])) {
4069 cnt++;
4070 SUMx += step;
4071 SUMxx += step * step;
4072 SUMxy += step * data[step*src->ds_cnt];
4073 SUMy += data[step*src->ds_cnt];
4074 SUMyy += data[step*src->ds_cnt]*data[step*src->ds_cnt];
4075 };
4076 }
4078 slope = ( SUMx*SUMy - cnt*SUMxy ) / ( SUMx*SUMx - cnt*SUMxx );
4079 y_intercept = ( SUMy - slope*SUMx ) / cnt;
4080 correl = (SUMxy - (SUMx*SUMy)/cnt) / sqrt((SUMxx - (SUMx*SUMx)/cnt)*(SUMyy - (SUMy*SUMy)/cnt));
4082 if (cnt) {
4083 if (dst->vf.op == VDEF_LSLSLOPE) {
4084 dst->vf.val = slope;
4085 dst->vf.when = 0;
4086 } else if (dst->vf.op == VDEF_LSLINT) {
4087 dst->vf.val = y_intercept;
4088 dst->vf.when = 0;
4089 } else if (dst->vf.op == VDEF_LSLCORREL) {
4090 dst->vf.val = correl;
4091 dst->vf.when = 0;
4092 };
4094 } else {
4095 dst->vf.val = DNAN;
4096 dst->vf.when = 0;
4097 }
4098 }
4099 break;
4100 }
4101 return 0;
4102 }
4104 /* NaN < -INF < finite_values < INF */
4105 int
4106 vdef_percent_compar(a,b)
4107 const void *a,*b;
4108 {
4109 /* Equality is not returned; this doesn't hurt except
4110 * (maybe) for a little performance.
4111 */
4113 /* First catch NaN values. They are smallest */
4114 if (isnan( *(double *)a )) return -1;
4115 if (isnan( *(double *)b )) return 1;
4117 /* NaN doesn't reach this part so INF and -INF are extremes.
4118 * The sign from isinf() is compatible with the sign we return
4119 */
4120 if (isinf( *(double *)a )) return isinf( *(double *)a );
4121 if (isinf( *(double *)b )) return isinf( *(double *)b );
4123 /* If we reach this, both values must be finite */
4124 if ( *(double *)a < *(double *)b ) return -1; else return 1;
4125 }