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