1 /****************************************************************************
2 * RRDtool 1.2.13 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 ;
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;
730 im->gdes[i].step = im->step;
732 if (ft_step < im->gdes[i].step) {
733 reduce_data(im->gdes[i].cf_reduce,
734 ft_step,
735 &im->gdes[i].start,
736 &im->gdes[i].end,
737 &im->gdes[i].step,
738 &im->gdes[i].ds_cnt,
739 &im->gdes[i].data);
740 } else {
741 im->gdes[i].step = ft_step;
742 }
743 }
745 /* lets see if the required data source is really there */
746 for(ii=0;ii<(int)im->gdes[i].ds_cnt;ii++){
747 if(strcmp(im->gdes[i].ds_namv[ii],im->gdes[i].ds_nam) == 0){
748 im->gdes[i].ds=ii; }
749 }
750 if (im->gdes[i].ds== -1){
751 rrd_set_error("No DS called '%s' in '%s'",
752 im->gdes[i].ds_nam,im->gdes[i].rrd);
753 return -1;
754 }
756 }
757 return 0;
758 }
760 /* evaluate the expressions in the CDEF functions */
762 /*************************************************************
763 * CDEF stuff
764 *************************************************************/
766 long
767 find_var_wrapper(void *arg1, char *key)
768 {
769 return find_var((image_desc_t *) arg1, key);
770 }
772 /* find gdes containing var*/
773 long
774 find_var(image_desc_t *im, char *key){
775 long ii;
776 for(ii=0;ii<im->gdes_c-1;ii++){
777 if((im->gdes[ii].gf == GF_DEF
778 || im->gdes[ii].gf == GF_VDEF
779 || im->gdes[ii].gf == GF_CDEF)
780 && (strcmp(im->gdes[ii].vname,key) == 0)){
781 return ii;
782 }
783 }
784 return -1;
785 }
787 /* find the largest common denominator for all the numbers
788 in the 0 terminated num array */
789 long
790 lcd(long *num){
791 long rest;
792 int i;
793 for (i=0;num[i+1]!=0;i++){
794 do {
795 rest=num[i] % num[i+1];
796 num[i]=num[i+1]; num[i+1]=rest;
797 } while (rest!=0);
798 num[i+1] = num[i];
799 }
800 /* return i==0?num[i]:num[i-1]; */
801 return num[i];
802 }
804 /* run the rpn calculator on all the VDEF and CDEF arguments */
805 int
806 data_calc( image_desc_t *im){
808 int gdi;
809 int dataidx;
810 long *steparray, rpi;
811 int stepcnt;
812 time_t now;
813 rpnstack_t rpnstack;
815 rpnstack_init(&rpnstack);
817 for (gdi=0;gdi<im->gdes_c;gdi++){
818 /* Look for GF_VDEF and GF_CDEF in the same loop,
819 * so CDEFs can use VDEFs and vice versa
820 */
821 switch (im->gdes[gdi].gf) {
822 case GF_XPORT:
823 break;
824 case GF_SHIFT: {
825 graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx];
827 /* remove current shift */
828 vdp->start -= vdp->shift;
829 vdp->end -= vdp->shift;
831 /* vdef */
832 if (im->gdes[gdi].shidx >= 0)
833 vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
834 /* constant */
835 else
836 vdp->shift = im->gdes[gdi].shval;
838 /* normalize shift to multiple of consolidated step */
839 vdp->shift = (vdp->shift / (long)vdp->step) * (long)vdp->step;
841 /* apply shift */
842 vdp->start += vdp->shift;
843 vdp->end += vdp->shift;
844 break;
845 }
846 case GF_VDEF:
847 /* A VDEF has no DS. This also signals other parts
848 * of rrdtool that this is a VDEF value, not a CDEF.
849 */
850 im->gdes[gdi].ds_cnt = 0;
851 if (vdef_calc(im,gdi)) {
852 rrd_set_error("Error processing VDEF '%s'"
853 ,im->gdes[gdi].vname
854 );
855 rpnstack_free(&rpnstack);
856 return -1;
857 }
858 break;
859 case GF_CDEF:
860 im->gdes[gdi].ds_cnt = 1;
861 im->gdes[gdi].ds = 0;
862 im->gdes[gdi].data_first = 1;
863 im->gdes[gdi].start = 0;
864 im->gdes[gdi].end = 0;
865 steparray=NULL;
866 stepcnt = 0;
867 dataidx=-1;
869 /* Find the variables in the expression.
870 * - VDEF variables are substituted by their values
871 * and the opcode is changed into OP_NUMBER.
872 * - CDEF variables are analized for their step size,
873 * the lowest common denominator of all the step
874 * sizes of the data sources involved is calculated
875 * and the resulting number is the step size for the
876 * resulting data source.
877 */
878 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
879 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
880 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER){
881 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
882 if (im->gdes[ptr].ds_cnt == 0) { /* this is a VDEF data source */
883 #if 0
884 printf("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
885 im->gdes[gdi].vname,
886 im->gdes[ptr].vname);
887 printf("DEBUG: value from vdef is %f\n",im->gdes[ptr].vf.val);
888 #endif
889 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
890 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
891 } else { /* normal variables and PREF(variables) */
893 /* add one entry to the array that keeps track of the step sizes of the
894 * data sources going into the CDEF. */
895 if ((steparray =
896 rrd_realloc(steparray,
897 (++stepcnt+1)*sizeof(*steparray)))==NULL){
898 rrd_set_error("realloc steparray");
899 rpnstack_free(&rpnstack);
900 return -1;
901 };
903 steparray[stepcnt-1] = im->gdes[ptr].step;
905 /* adjust start and end of cdef (gdi) so
906 * that it runs from the latest start point
907 * to the earliest endpoint of any of the
908 * rras involved (ptr)
909 */
911 if(im->gdes[gdi].start < im->gdes[ptr].start)
912 im->gdes[gdi].start = im->gdes[ptr].start;
914 if(im->gdes[gdi].end == 0 ||
915 im->gdes[gdi].end > im->gdes[ptr].end)
916 im->gdes[gdi].end = im->gdes[ptr].end;
918 /* store pointer to the first element of
919 * the rra providing data for variable,
920 * further save step size and data source
921 * count of this rra
922 */
923 im->gdes[gdi].rpnp[rpi].data = im->gdes[ptr].data + im->gdes[ptr].ds;
924 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
925 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
927 /* backoff the *.data ptr; this is done so
928 * rpncalc() function doesn't have to treat
929 * the first case differently
930 */
931 } /* if ds_cnt != 0 */
932 } /* if OP_VARIABLE */
933 } /* loop through all rpi */
935 /* move the data pointers to the correct period */
936 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
937 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
938 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER){
939 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
940 long diff = im->gdes[gdi].start - im->gdes[ptr].start;
942 if(diff > 0)
943 im->gdes[gdi].rpnp[rpi].data += (diff / im->gdes[ptr].step) * im->gdes[ptr].ds_cnt;
944 }
945 }
947 if(steparray == NULL){
948 rrd_set_error("rpn expressions without DEF"
949 " or CDEF variables are not supported");
950 rpnstack_free(&rpnstack);
951 return -1;
952 }
953 steparray[stepcnt]=0;
954 /* Now find the resulting step. All steps in all
955 * used RRAs have to be visited
956 */
957 im->gdes[gdi].step = lcd(steparray);
958 free(steparray);
959 if((im->gdes[gdi].data = malloc((
960 (im->gdes[gdi].end-im->gdes[gdi].start)
961 / im->gdes[gdi].step)
962 * sizeof(double)))==NULL){
963 rrd_set_error("malloc im->gdes[gdi].data");
964 rpnstack_free(&rpnstack);
965 return -1;
966 }
968 /* Step through the new cdef results array and
969 * calculate the values
970 */
971 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
972 now<=im->gdes[gdi].end;
973 now += im->gdes[gdi].step)
974 {
975 rpnp_t *rpnp = im -> gdes[gdi].rpnp;
977 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
978 * in this case we are advancing by timesteps;
979 * we use the fact that time_t is a synonym for long
980 */
981 if (rpn_calc(rpnp,&rpnstack,(long) now,
982 im->gdes[gdi].data,++dataidx) == -1) {
983 /* rpn_calc sets the error string */
984 rpnstack_free(&rpnstack);
985 return -1;
986 }
987 } /* enumerate over time steps within a CDEF */
988 break;
989 default:
990 continue;
991 }
992 } /* enumerate over CDEFs */
993 rpnstack_free(&rpnstack);
994 return 0;
995 }
997 /* massage data so, that we get one value for each x coordinate in the graph */
998 int
999 data_proc( image_desc_t *im ){
1000 long i,ii;
1001 double pixstep = (double)(im->end-im->start)
1002 /(double)im->xsize; /* how much time
1003 passes in one pixel */
1004 double paintval;
1005 double minval=DNAN,maxval=DNAN;
1007 unsigned long gr_time;
1009 /* memory for the processed data */
1010 for(i=0;i<im->gdes_c;i++) {
1011 if((im->gdes[i].gf==GF_LINE) ||
1012 (im->gdes[i].gf==GF_AREA) ||
1013 (im->gdes[i].gf==GF_TICK)) {
1014 if((im->gdes[i].p_data = malloc((im->xsize +1)
1015 * sizeof(rrd_value_t)))==NULL){
1016 rrd_set_error("malloc data_proc");
1017 return -1;
1018 }
1019 }
1020 }
1022 for (i=0;i<im->xsize;i++) { /* for each pixel */
1023 long vidx;
1024 gr_time = im->start+pixstep*i; /* time of the current step */
1025 paintval=0.0;
1027 for (ii=0;ii<im->gdes_c;ii++) {
1028 double value;
1029 switch (im->gdes[ii].gf) {
1030 case GF_LINE:
1031 case GF_AREA:
1032 case GF_TICK:
1033 if (!im->gdes[ii].stack)
1034 paintval = 0.0;
1035 value = im->gdes[ii].yrule;
1036 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1037 /* The time of the data doesn't necessarily match
1038 ** the time of the graph. Beware.
1039 */
1040 vidx = im->gdes[ii].vidx;
1041 if (im->gdes[vidx].gf == GF_VDEF) {
1042 value = im->gdes[vidx].vf.val;
1043 } else if (((long int)gr_time >= (long int)im->gdes[vidx].start) &&
1044 ((long int)gr_time <= (long int)im->gdes[vidx].end) ) {
1045 value = im->gdes[vidx].data[
1046 (unsigned long) floor(
1047 (double)(gr_time - im->gdes[vidx].start)
1048 / im->gdes[vidx].step)
1049 * im->gdes[vidx].ds_cnt
1050 + im->gdes[vidx].ds
1051 ];
1052 } else {
1053 value = DNAN;
1054 }
1055 };
1057 if (! isnan(value)) {
1058 paintval += value;
1059 im->gdes[ii].p_data[i] = paintval;
1060 /* GF_TICK: the data values are not
1061 ** relevant for min and max
1062 */
1063 if (finite(paintval) && im->gdes[ii].gf != GF_TICK ) {
1064 if (isnan(minval) || paintval < minval)
1065 minval = paintval;
1066 if (isnan(maxval) || paintval > maxval)
1067 maxval = paintval;
1068 }
1069 } else {
1070 im->gdes[ii].p_data[i] = DNAN;
1071 }
1072 break;
1073 case GF_STACK:
1074 rrd_set_error("STACK should already be turned into LINE or AREA here");
1075 return -1;
1076 break;
1077 default:
1078 break;
1079 }
1080 }
1081 }
1083 /* if min or max have not been asigned a value this is because
1084 there was no data in the graph ... this is not good ...
1085 lets set these to dummy values then ... */
1087 if (im->logarithmic) {
1088 if (isnan(minval)) minval = 0.2;
1089 if (isnan(maxval)) maxval = 5.1;
1090 }
1091 else {
1092 if (isnan(minval)) minval = 0.0;
1093 if (isnan(maxval)) maxval = 1.0;
1094 }
1096 /* adjust min and max values */
1097 if (isnan(im->minval)
1098 /* don't adjust low-end with log scale */ /* why not? */
1099 || ((!im->rigid) && im->minval > minval)
1100 ) {
1101 if (im->logarithmic)
1102 im->minval = minval * 0.5;
1103 else
1104 im->minval = minval;
1105 }
1106 if (isnan(im->maxval)
1107 || (!im->rigid && im->maxval < maxval)
1108 ) {
1109 if (im->logarithmic)
1110 im->maxval = maxval * 2.0;
1111 else
1112 im->maxval = maxval;
1113 }
1114 /* make sure min is smaller than max */
1115 if (im->minval > im->maxval) {
1116 im->minval = 0.99 * im->maxval;
1117 }
1119 /* make sure min and max are not equal */
1120 if (im->minval == im->maxval) {
1121 im->maxval *= 1.01;
1122 if (! im->logarithmic) {
1123 im->minval *= 0.99;
1124 }
1125 /* make sure min and max are not both zero */
1126 if (im->maxval == 0.0) {
1127 im->maxval = 1.0;
1128 }
1129 }
1130 return 0;
1131 }
1135 /* identify the point where the first gridline, label ... gets placed */
1137 time_t
1138 find_first_time(
1139 time_t start, /* what is the initial time */
1140 enum tmt_en baseint, /* what is the basic interval */
1141 long basestep /* how many if these do we jump a time */
1142 )
1143 {
1144 struct tm tm;
1145 localtime_r(&start, &tm);
1146 switch(baseint){
1147 case TMT_SECOND:
1148 tm.tm_sec -= tm.tm_sec % basestep; break;
1149 case TMT_MINUTE:
1150 tm.tm_sec=0;
1151 tm.tm_min -= tm.tm_min % basestep;
1152 break;
1153 case TMT_HOUR:
1154 tm.tm_sec=0;
1155 tm.tm_min = 0;
1156 tm.tm_hour -= tm.tm_hour % basestep; break;
1157 case TMT_DAY:
1158 /* we do NOT look at the basestep for this ... */
1159 tm.tm_sec=0;
1160 tm.tm_min = 0;
1161 tm.tm_hour = 0; break;
1162 case TMT_WEEK:
1163 /* we do NOT look at the basestep for this ... */
1164 tm.tm_sec=0;
1165 tm.tm_min = 0;
1166 tm.tm_hour = 0;
1167 tm.tm_mday -= tm.tm_wday -1; /* -1 because we want the monday */
1168 if (tm.tm_wday==0) tm.tm_mday -= 7; /* we want the *previous* monday */
1169 break;
1170 case TMT_MONTH:
1171 tm.tm_sec=0;
1172 tm.tm_min = 0;
1173 tm.tm_hour = 0;
1174 tm.tm_mday = 1;
1175 tm.tm_mon -= tm.tm_mon % basestep; break;
1177 case TMT_YEAR:
1178 tm.tm_sec=0;
1179 tm.tm_min = 0;
1180 tm.tm_hour = 0;
1181 tm.tm_mday = 1;
1182 tm.tm_mon = 0;
1183 tm.tm_year -= (tm.tm_year+1900) % basestep;
1185 }
1186 return mktime(&tm);
1187 }
1188 /* identify the point where the next gridline, label ... gets placed */
1189 time_t
1190 find_next_time(
1191 time_t current, /* what is the initial time */
1192 enum tmt_en baseint, /* what is the basic interval */
1193 long basestep /* how many if these do we jump a time */
1194 )
1195 {
1196 struct tm tm;
1197 time_t madetime;
1198 localtime_r(¤t, &tm);
1199 do {
1200 switch(baseint){
1201 case TMT_SECOND:
1202 tm.tm_sec += basestep; break;
1203 case TMT_MINUTE:
1204 tm.tm_min += basestep; break;
1205 case TMT_HOUR:
1206 tm.tm_hour += basestep; break;
1207 case TMT_DAY:
1208 tm.tm_mday += basestep; break;
1209 case TMT_WEEK:
1210 tm.tm_mday += 7*basestep; break;
1211 case TMT_MONTH:
1212 tm.tm_mon += basestep; break;
1213 case TMT_YEAR:
1214 tm.tm_year += basestep;
1215 }
1216 madetime = mktime(&tm);
1217 } while (madetime == -1); /* this is necessary to skip impssible times
1218 like the daylight saving time skips */
1219 return madetime;
1221 }
1224 /* calculate values required for PRINT and GPRINT functions */
1226 int
1227 print_calc(image_desc_t *im, char ***prdata)
1228 {
1229 long i,ii,validsteps;
1230 double printval;
1231 struct tm tmvdef;
1232 int graphelement = 0;
1233 long vidx;
1234 int max_ii;
1235 double magfact = -1;
1236 char *si_symb = "";
1237 char *percent_s;
1238 int prlines = 1;
1239 /* wow initializing tmvdef is quite a task :-) */
1240 time_t now = time(NULL);
1241 localtime_r(&now,&tmvdef);
1242 if (im->imginfo) prlines++;
1243 for(i=0;i<im->gdes_c;i++){
1244 switch(im->gdes[i].gf){
1245 case GF_PRINT:
1246 prlines++;
1247 if(((*prdata) = rrd_realloc((*prdata),prlines*sizeof(char *)))==NULL){
1248 rrd_set_error("realloc prdata");
1249 return 0;
1250 }
1251 case GF_GPRINT:
1252 /* PRINT and GPRINT can now print VDEF generated values.
1253 * There's no need to do any calculations on them as these
1254 * calculations were already made.
1255 */
1256 vidx = im->gdes[i].vidx;
1257 if (im->gdes[vidx].gf==GF_VDEF) { /* simply use vals */
1258 printval = im->gdes[vidx].vf.val;
1259 localtime_r(&im->gdes[vidx].vf.when,&tmvdef);
1260 } else { /* need to calculate max,min,avg etcetera */
1261 max_ii =((im->gdes[vidx].end
1262 - im->gdes[vidx].start)
1263 / im->gdes[vidx].step
1264 * im->gdes[vidx].ds_cnt);
1265 printval = DNAN;
1266 validsteps = 0;
1267 for( ii=im->gdes[vidx].ds;
1268 ii < max_ii;
1269 ii+=im->gdes[vidx].ds_cnt){
1270 if (! finite(im->gdes[vidx].data[ii]))
1271 continue;
1272 if (isnan(printval)){
1273 printval = im->gdes[vidx].data[ii];
1274 validsteps++;
1275 continue;
1276 }
1278 switch (im->gdes[i].cf){
1279 case CF_HWPREDICT:
1280 case CF_DEVPREDICT:
1281 case CF_DEVSEASONAL:
1282 case CF_SEASONAL:
1283 case CF_AVERAGE:
1284 validsteps++;
1285 printval += im->gdes[vidx].data[ii];
1286 break;
1287 case CF_MINIMUM:
1288 printval = min( printval, im->gdes[vidx].data[ii]);
1289 break;
1290 case CF_FAILURES:
1291 case CF_MAXIMUM:
1292 printval = max( printval, im->gdes[vidx].data[ii]);
1293 break;
1294 case CF_LAST:
1295 printval = im->gdes[vidx].data[ii];
1296 }
1297 }
1298 if (im->gdes[i].cf==CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1299 if (validsteps > 1) {
1300 printval = (printval / validsteps);
1301 }
1302 }
1303 } /* prepare printval */
1305 if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) {
1306 /* Magfact is set to -1 upon entry to print_calc. If it
1307 * is still less than 0, then we need to run auto_scale.
1308 * Otherwise, put the value into the correct units. If
1309 * the value is 0, then do not set the symbol or magnification
1310 * so next the calculation will be performed again. */
1311 if (magfact < 0.0) {
1312 auto_scale(im,&printval,&si_symb,&magfact);
1313 if (printval == 0.0)
1314 magfact = -1.0;
1315 } else {
1316 printval /= magfact;
1317 }
1318 *(++percent_s) = 's';
1319 } else if (strstr(im->gdes[i].format,"%s") != NULL) {
1320 auto_scale(im,&printval,&si_symb,&magfact);
1321 }
1323 if (im->gdes[i].gf == GF_PRINT){
1324 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1325 (*prdata)[prlines-1] = NULL;
1326 if (im->gdes[i].strftm){
1327 strftime((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,&tmvdef);
1328 } else {
1329 if (bad_format(im->gdes[i].format)) {
1330 rrd_set_error("bad format for PRINT in '%s'", im->gdes[i].format);
1331 return -1;
1332 }
1334 #ifdef HAVE_SNPRINTF
1335 snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
1336 #else
1337 sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
1338 #endif
1339 }
1340 } else {
1341 /* GF_GPRINT */
1343 if (im->gdes[i].strftm){
1344 strftime(im->gdes[i].legend,FMT_LEG_LEN,im->gdes[i].format,&tmvdef);
1345 } else {
1346 if (bad_format(im->gdes[i].format)) {
1347 rrd_set_error("bad format for GPRINT in '%s'", im->gdes[i].format);
1348 return -1;
1349 }
1350 #ifdef HAVE_SNPRINTF
1351 snprintf(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
1352 #else
1353 sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
1354 #endif
1355 }
1356 graphelement = 1;
1357 }
1358 break;
1359 case GF_LINE:
1360 case GF_AREA:
1361 case GF_TICK:
1362 graphelement = 1;
1363 break;
1364 case GF_HRULE:
1365 if(isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1366 im->gdes[i].yrule=im->gdes[im->gdes[i].vidx].vf.val;
1367 };
1368 graphelement = 1;
1369 break;
1370 case GF_VRULE:
1371 if(im->gdes[i].xrule == 0) { /* again ... the legend printer needs it*/
1372 im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
1373 };
1374 graphelement = 1;
1375 break;
1376 case GF_COMMENT:
1377 case GF_DEF:
1378 case GF_CDEF:
1379 case GF_VDEF:
1380 #ifdef WITH_PIECHART
1381 case GF_PART:
1382 #endif
1383 case GF_SHIFT:
1384 case GF_XPORT:
1385 break;
1386 case GF_STACK:
1387 rrd_set_error("STACK should already be turned into LINE or AREA here");
1388 return -1;
1389 break;
1390 }
1391 }
1392 return graphelement;
1393 }
1396 /* place legends with color spots */
1397 int
1398 leg_place(image_desc_t *im)
1399 {
1400 /* graph labels */
1401 int interleg = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1402 int border = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1403 int fill=0, fill_last;
1404 int leg_c = 0;
1405 int leg_x = border, leg_y = im->yimg;
1406 int leg_y_prev = im->yimg;
1407 int leg_cc;
1408 int glue = 0;
1409 int i,ii, mark = 0;
1410 char prt_fctn; /*special printfunctions */
1411 int *legspace;
1413 if( !(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH) ) {
1414 if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1415 rrd_set_error("malloc for legspace");
1416 return -1;
1417 }
1419 for(i=0;i<im->gdes_c;i++){
1420 fill_last = fill;
1422 /* hid legends for rules which are not displayed */
1424 if(!(im->extra_flags & FORCE_RULES_LEGEND)) {
1425 if (im->gdes[i].gf == GF_HRULE &&
1426 (im->gdes[i].yrule < im->minval || im->gdes[i].yrule > im->maxval))
1427 im->gdes[i].legend[0] = '\0';
1429 if (im->gdes[i].gf == GF_VRULE &&
1430 (im->gdes[i].xrule < im->start || im->gdes[i].xrule > im->end))
1431 im->gdes[i].legend[0] = '\0';
1432 }
1434 leg_cc = strlen(im->gdes[i].legend);
1436 /* is there a controle code ant the end of the legend string ? */
1437 /* and it is not a tab \\t */
1438 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\' && im->gdes[i].legend[leg_cc-1] != 't') {
1439 prt_fctn = im->gdes[i].legend[leg_cc-1];
1440 leg_cc -= 2;
1441 im->gdes[i].legend[leg_cc] = '\0';
1442 } else {
1443 prt_fctn = '\0';
1444 }
1445 /* remove exess space */
1446 while (prt_fctn=='g' &&
1447 leg_cc > 0 &&
1448 im->gdes[i].legend[leg_cc-1]==' '){
1449 leg_cc--;
1450 im->gdes[i].legend[leg_cc]='\0';
1451 }
1452 if (leg_cc != 0 ){
1453 legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1455 if (fill > 0){
1456 /* no interleg space if string ends in \g */
1457 fill += legspace[i];
1458 }
1459 fill += gfx_get_text_width(im->canvas, fill+border,
1460 im->text_prop[TEXT_PROP_LEGEND].font,
1461 im->text_prop[TEXT_PROP_LEGEND].size,
1462 im->tabwidth,
1463 im->gdes[i].legend, 0);
1464 leg_c++;
1465 } else {
1466 legspace[i]=0;
1467 }
1468 /* who said there was a special tag ... ?*/
1469 if (prt_fctn=='g') {
1470 prt_fctn = '\0';
1471 }
1472 if (prt_fctn == '\0') {
1473 if (i == im->gdes_c -1 ) prt_fctn ='l';
1475 /* is it time to place the legends ? */
1476 if (fill > im->ximg - 2*border){
1477 if (leg_c > 1) {
1478 /* go back one */
1479 i--;
1480 fill = fill_last;
1481 leg_c--;
1482 prt_fctn = 'j';
1483 } else {
1484 prt_fctn = 'l';
1485 }
1487 }
1488 }
1491 if (prt_fctn != '\0'){
1492 leg_x = border;
1493 if (leg_c >= 2 && prt_fctn == 'j') {
1494 glue = (im->ximg - fill - 2* border) / (leg_c-1);
1495 } else {
1496 glue = 0;
1497 }
1498 if (prt_fctn =='c') leg_x = (im->ximg - fill) / 2.0;
1499 if (prt_fctn =='r') leg_x = im->ximg - fill - border;
1501 for(ii=mark;ii<=i;ii++){
1502 if(im->gdes[ii].legend[0]=='\0')
1503 continue; /* skip empty legends */
1504 im->gdes[ii].leg_x = leg_x;
1505 im->gdes[ii].leg_y = leg_y;
1506 leg_x +=
1507 gfx_get_text_width(im->canvas, leg_x,
1508 im->text_prop[TEXT_PROP_LEGEND].font,
1509 im->text_prop[TEXT_PROP_LEGEND].size,
1510 im->tabwidth,
1511 im->gdes[ii].legend, 0)
1512 + legspace[ii]
1513 + glue;
1514 }
1515 leg_y_prev = leg_y;
1516 /* only add y space if there was text on the line */
1517 if (leg_x > border || prt_fctn == 's')
1518 leg_y += im->text_prop[TEXT_PROP_LEGEND].size*1.8;
1519 if (prt_fctn == 's')
1520 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1521 fill = 0;
1522 leg_c = 0;
1523 mark = ii;
1524 }
1525 }
1526 im->yimg = leg_y_prev;
1527 /* if we did place some legends we have to add vertical space */
1528 if (leg_y != im->yimg){
1529 im->yimg += im->text_prop[TEXT_PROP_LEGEND].size*1.8;
1530 }
1531 free(legspace);
1532 }
1533 return 0;
1534 }
1536 /* create a grid on the graph. it determines what to do
1537 from the values of xsize, start and end */
1539 /* the xaxis labels are determined from the number of seconds per pixel
1540 in the requested graph */
1544 int
1545 calc_horizontal_grid(image_desc_t *im)
1546 {
1547 double range;
1548 double scaledrange;
1549 int pixel,i;
1550 int gridind=0;
1551 int decimals, fractionals;
1553 im->ygrid_scale.labfact=2;
1554 range = im->maxval - im->minval;
1555 scaledrange = range / im->magfact;
1557 /* does the scale of this graph make it impossible to put lines
1558 on it? If so, give up. */
1559 if (isnan(scaledrange)) {
1560 return 0;
1561 }
1563 /* find grid spaceing */
1564 pixel=1;
1565 if(isnan(im->ygridstep)){
1566 if(im->extra_flags & ALTYGRID) {
1567 /* find the value with max number of digits. Get number of digits */
1568 decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))*im->viewfactor/im->magfact));
1569 if(decimals <= 0) /* everything is small. make place for zero */
1570 decimals = 1;
1572 im->ygrid_scale.gridstep = pow((double)10, floor(log10(range*im->viewfactor/im->magfact)))/im->viewfactor*im->magfact;
1574 if(im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1575 im->ygrid_scale.gridstep = 0.1;
1576 /* should have at least 5 lines but no more then 15 */
1577 if(range/im->ygrid_scale.gridstep < 5)
1578 im->ygrid_scale.gridstep /= 10;
1579 if(range/im->ygrid_scale.gridstep > 15)
1580 im->ygrid_scale.gridstep *= 10;
1581 if(range/im->ygrid_scale.gridstep > 5) {
1582 im->ygrid_scale.labfact = 1;
1583 if(range/im->ygrid_scale.gridstep > 8)
1584 im->ygrid_scale.labfact = 2;
1585 }
1586 else {
1587 im->ygrid_scale.gridstep /= 5;
1588 im->ygrid_scale.labfact = 5;
1589 }
1590 fractionals = floor(log10(im->ygrid_scale.gridstep*(double)im->ygrid_scale.labfact*im->viewfactor/im->magfact));
1591 if(fractionals < 0) { /* small amplitude. */
1592 int len = decimals - fractionals + 1;
1593 if (im->unitslength < len+2) im->unitslength = len+2;
1594 sprintf(im->ygrid_scale.labfmt, "%%%d.%df%s", len, -fractionals,(im->symbol != ' ' ? " %c" : ""));
1595 } else {
1596 int len = decimals + 1;
1597 if (im->unitslength < len+2) im->unitslength = len+2;
1598 sprintf(im->ygrid_scale.labfmt, "%%%d.0f%s", len, ( im->symbol != ' ' ? " %c" : "" ));
1599 }
1600 }
1601 else {
1602 for(i=0;ylab[i].grid > 0;i++){
1603 pixel = im->ysize / (scaledrange / ylab[i].grid);
1604 gridind = i;
1605 if (pixel > 7)
1606 break;
1607 }
1609 for(i=0; i<4;i++) {
1610 if (pixel * ylab[gridind].lfac[i] >= 2.5 * im->text_prop[TEXT_PROP_AXIS].size) {
1611 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1612 break;
1613 }
1614 }
1616 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1617 }
1618 } else {
1619 im->ygrid_scale.gridstep = im->ygridstep;
1620 im->ygrid_scale.labfact = im->ylabfact;
1621 }
1622 return 1;
1623 }
1625 int draw_horizontal_grid(image_desc_t *im)
1626 {
1627 int i;
1628 double scaledstep;
1629 char graph_label[100];
1630 int nlabels=0;
1631 double X0=im->xorigin;
1632 double X1=im->xorigin+im->xsize;
1634 int sgrid = (int)( im->minval / im->ygrid_scale.gridstep - 1);
1635 int egrid = (int)( im->maxval / im->ygrid_scale.gridstep + 1);
1636 double MaxY;
1637 scaledstep = im->ygrid_scale.gridstep/(double)im->magfact*(double)im->viewfactor;
1638 MaxY = scaledstep*(double)egrid;
1639 for (i = sgrid; i <= egrid; i++){
1640 double Y0=ytr(im,im->ygrid_scale.gridstep*i);
1641 double YN=ytr(im,im->ygrid_scale.gridstep*(i+1));
1642 if ( Y0 >= im->yorigin-im->ysize
1643 && Y0 <= im->yorigin){
1644 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
1645 with the chosen settings. Add a label if required by settings, or if
1646 there is only one label so far and the next grid line is out of bounds. */
1647 if(i % im->ygrid_scale.labfact == 0 || ( nlabels==1 && (YN < im->yorigin-im->ysize || YN > im->yorigin) )){
1648 if (im->symbol == ' ') {
1649 if(im->extra_flags & ALTYGRID) {
1650 sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*(double)i);
1651 } else {
1652 if(MaxY < 10) {
1653 sprintf(graph_label,"%4.1f",scaledstep*(double)i);
1654 } else {
1655 sprintf(graph_label,"%4.0f",scaledstep*(double)i);
1656 }
1657 }
1658 }else {
1659 char sisym = ( i == 0 ? ' ' : im->symbol);
1660 if(im->extra_flags & ALTYGRID) {
1661 sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*(double)i,sisym);
1662 } else {
1663 if(MaxY < 10){
1664 sprintf(graph_label,"%4.1f %c",scaledstep*(double)i, sisym);
1665 } else {
1666 sprintf(graph_label,"%4.0f %c",scaledstep*(double)i, sisym);
1667 }
1668 }
1669 }
1670 nlabels++;
1672 gfx_new_text ( im->canvas,
1673 X0-im->text_prop[TEXT_PROP_AXIS].size, Y0,
1674 im->graph_col[GRC_FONT],
1675 im->text_prop[TEXT_PROP_AXIS].font,
1676 im->text_prop[TEXT_PROP_AXIS].size,
1677 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1678 graph_label );
1679 gfx_new_dashed_line ( im->canvas,
1680 X0-2,Y0,
1681 X1+2,Y0,
1682 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1683 im->grid_dash_on, im->grid_dash_off);
1685 } else if (!(im->extra_flags & NOMINOR)) {
1686 gfx_new_dashed_line ( im->canvas,
1687 X0-1,Y0,
1688 X1+1,Y0,
1689 GRIDWIDTH, im->graph_col[GRC_GRID],
1690 im->grid_dash_on, im->grid_dash_off);
1692 }
1693 }
1694 }
1695 return 1;
1696 }
1698 /* this is frexp for base 10 */
1699 double frexp10(double, double *);
1700 double frexp10(double x, double *e) {
1701 double mnt;
1702 int iexp;
1704 iexp = floor(log(fabs(x)) / log(10));
1705 mnt = x / pow(10.0, iexp);
1706 if(mnt >= 10.0) {
1707 iexp++;
1708 mnt = x / pow(10.0, iexp);
1709 }
1710 *e = iexp;
1711 return mnt;
1712 }
1714 /* logaritmic horizontal grid */
1715 int
1716 horizontal_log_grid(image_desc_t *im)
1717 {
1718 double yloglab[][10] = {
1719 {1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
1720 {1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
1721 {1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0},
1722 {1.0, 2.0, 4.0, 6.0, 8.0, 10., 0.0, 0.0, 0.0, 0.0},
1723 {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.}};
1725 int i, j, val_exp, min_exp;
1726 double nex; /* number of decades in data */
1727 double logscale; /* scale in logarithmic space */
1728 int exfrac = 1; /* decade spacing */
1729 int mid = -1; /* row in yloglab for major grid */
1730 double mspac; /* smallest major grid spacing (pixels) */
1731 int flab; /* first value in yloglab to use */
1732 double value, tmp;
1733 double X0,X1,Y0;
1734 char graph_label[100];
1736 nex = log10(im->maxval / im->minval);
1737 logscale = im->ysize / nex;
1739 /* major spacing for data with high dynamic range */
1740 while(logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
1741 if(exfrac == 1) exfrac = 3;
1742 else exfrac += 3;
1743 }
1745 /* major spacing for less dynamic data */
1746 do {
1747 /* search best row in yloglab */
1748 mid++;
1749 for(i = 0; yloglab[mid][i + 1] < 10.0; i++);
1750 mspac = logscale * log10(10.0 / yloglab[mid][i]);
1751 } while(mspac > 2 * im->text_prop[TEXT_PROP_LEGEND].size && mid < 5);
1752 if(mid) mid--;
1754 /* find first value in yloglab */
1755 for(flab = 0; frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
1756 if(yloglab[mid][flab] == 10.0) {
1757 tmp += 1.0;
1758 flab = 0;
1759 }
1760 val_exp = tmp;
1761 if(val_exp % exfrac) val_exp += abs(-val_exp % exfrac);
1763 X0=im->xorigin;
1764 X1=im->xorigin+im->xsize;
1766 /* draw grid */
1767 while(1) {
1768 value = yloglab[mid][flab] * pow(10.0, val_exp);
1770 Y0 = ytr(im, value);
1771 if(Y0 <= im->yorigin - im->ysize) break;
1773 /* major grid line */
1774 gfx_new_dashed_line ( im->canvas,
1775 X0-2,Y0,
1776 X1+2,Y0,
1777 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1778 im->grid_dash_on, im->grid_dash_off);
1780 /* label */
1781 if (im->extra_flags & FORCE_UNITS_SI) {
1782 int scale;
1783 double pvalue;
1784 char symbol;
1786 scale = floor(val_exp / 3.0);
1787 if( value >= 1.0 ) pvalue = pow(10.0, val_exp % 3);
1788 else pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
1789 pvalue *= yloglab[mid][flab];
1791 if ( ((scale+si_symbcenter) < (int)sizeof(si_symbol)) &&
1792 ((scale+si_symbcenter) >= 0) )
1793 symbol = si_symbol[scale+si_symbcenter];
1794 else
1795 symbol = '?';
1797 sprintf(graph_label,"%3.0f %c", pvalue, symbol);
1798 } else
1799 sprintf(graph_label,"%3.0e", value);
1800 gfx_new_text ( im->canvas,
1801 X0-im->text_prop[TEXT_PROP_AXIS].size, Y0,
1802 im->graph_col[GRC_FONT],
1803 im->text_prop[TEXT_PROP_AXIS].font,
1804 im->text_prop[TEXT_PROP_AXIS].size,
1805 im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1806 graph_label );
1808 /* minor grid */
1809 if(mid < 4 && exfrac == 1) {
1810 /* find first and last minor line behind current major line
1811 * i is the first line and j tha last */
1812 if(flab == 0) {
1813 min_exp = val_exp - 1;
1814 for(i = 1; yloglab[mid][i] < 10.0; i++);
1815 i = yloglab[mid][i - 1] + 1;
1816 j = 10;
1817 }
1818 else {
1819 min_exp = val_exp;
1820 i = yloglab[mid][flab - 1] + 1;
1821 j = yloglab[mid][flab];
1822 }
1824 /* draw minor lines below current major line */
1825 for(; i < j; i++) {
1827 value = i * pow(10.0, min_exp);
1828 if(value < im->minval) continue;
1830 Y0 = ytr(im, value);
1831 if(Y0 <= im->yorigin - im->ysize) break;
1833 /* draw lines */
1834 gfx_new_dashed_line ( im->canvas,
1835 X0-1,Y0,
1836 X1+1,Y0,
1837 GRIDWIDTH, im->graph_col[GRC_GRID],
1838 im->grid_dash_on, im->grid_dash_off);
1839 }
1840 }
1841 else if(exfrac > 1) {
1842 for(i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
1843 value = pow(10.0, i);
1844 if(value < im->minval) continue;
1846 Y0 = ytr(im, value);
1847 if(Y0 <= im->yorigin - im->ysize) break;
1849 /* draw lines */
1850 gfx_new_dashed_line ( im->canvas,
1851 X0-1,Y0,
1852 X1+1,Y0,
1853 GRIDWIDTH, im->graph_col[GRC_GRID],
1854 im->grid_dash_on, im->grid_dash_off);
1855 }
1856 }
1858 /* next decade */
1859 if(yloglab[mid][++flab] == 10.0) {
1860 flab = 0;
1861 val_exp += exfrac;
1862 }
1863 }
1865 /* draw minor lines after highest major line */
1866 if(mid < 4 && exfrac == 1) {
1867 /* find first and last minor line below current major line
1868 * i is the first line and j tha last */
1869 if(flab == 0) {
1870 min_exp = val_exp - 1;
1871 for(i = 1; yloglab[mid][i] < 10.0; i++);
1872 i = yloglab[mid][i - 1] + 1;
1873 j = 10;
1874 }
1875 else {
1876 min_exp = val_exp;
1877 i = yloglab[mid][flab - 1] + 1;
1878 j = yloglab[mid][flab];
1879 }
1881 /* draw minor lines below current major line */
1882 for(; i < j; i++) {
1884 value = i * pow(10.0, min_exp);
1885 if(value < im->minval) continue;
1887 Y0 = ytr(im, value);
1888 if(Y0 <= im->yorigin - im->ysize) break;
1890 /* draw lines */
1891 gfx_new_dashed_line ( im->canvas,
1892 X0-1,Y0,
1893 X1+1,Y0,
1894 GRIDWIDTH, im->graph_col[GRC_GRID],
1895 im->grid_dash_on, im->grid_dash_off);
1896 }
1897 }
1898 /* fancy minor gridlines */
1899 else if(exfrac > 1) {
1900 for(i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
1901 value = pow(10.0, i);
1902 if(value < im->minval) continue;
1904 Y0 = ytr(im, value);
1905 if(Y0 <= im->yorigin - im->ysize) break;
1907 /* draw lines */
1908 gfx_new_dashed_line ( im->canvas,
1909 X0-1,Y0,
1910 X1+1,Y0,
1911 GRIDWIDTH, im->graph_col[GRC_GRID],
1912 im->grid_dash_on, im->grid_dash_off);
1913 }
1914 }
1916 return 1;
1917 }
1920 void
1921 vertical_grid(
1922 image_desc_t *im )
1923 {
1924 int xlab_sel; /* which sort of label and grid ? */
1925 time_t ti, tilab, timajor;
1926 long factor;
1927 char graph_label[100];
1928 double X0,Y0,Y1; /* points for filled graph and more*/
1929 struct tm tm;
1931 /* the type of time grid is determined by finding
1932 the number of seconds per pixel in the graph */
1935 if(im->xlab_user.minsec == -1){
1936 factor=(im->end - im->start)/im->xsize;
1937 xlab_sel=0;
1938 while ( xlab[xlab_sel+1].minsec != -1
1939 && xlab[xlab_sel+1].minsec <= factor) { xlab_sel++; } /* pick the last one */
1940 while ( xlab[xlab_sel-1].minsec == xlab[xlab_sel].minsec
1941 && xlab[xlab_sel].length > (im->end - im->start)) { xlab_sel--; } /* go back to the smallest size */
1942 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
1943 im->xlab_user.gridst = xlab[xlab_sel].gridst;
1944 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
1945 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
1946 im->xlab_user.labtm = xlab[xlab_sel].labtm;
1947 im->xlab_user.labst = xlab[xlab_sel].labst;
1948 im->xlab_user.precis = xlab[xlab_sel].precis;
1949 im->xlab_user.stst = xlab[xlab_sel].stst;
1950 }
1952 /* y coords are the same for every line ... */
1953 Y0 = im->yorigin;
1954 Y1 = im->yorigin-im->ysize;
1957 /* paint the minor grid */
1958 if (!(im->extra_flags & NOMINOR))
1959 {
1960 for(ti = find_first_time(im->start,
1961 im->xlab_user.gridtm,
1962 im->xlab_user.gridst),
1963 timajor = find_first_time(im->start,
1964 im->xlab_user.mgridtm,
1965 im->xlab_user.mgridst);
1966 ti < im->end;
1967 ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
1968 ){
1969 /* are we inside the graph ? */
1970 if (ti < im->start || ti > im->end) continue;
1971 while (timajor < ti) {
1972 timajor = find_next_time(timajor,
1973 im->xlab_user.mgridtm, im->xlab_user.mgridst);
1974 }
1975 if (ti == timajor) continue; /* skip as falls on major grid line */
1976 X0 = xtr(im,ti);
1977 gfx_new_dashed_line(im->canvas,X0,Y0+1, X0,Y1-1,GRIDWIDTH,
1978 im->graph_col[GRC_GRID],
1979 im->grid_dash_on, im->grid_dash_off);
1981 }
1982 }
1984 /* paint the major grid */
1985 for(ti = find_first_time(im->start,
1986 im->xlab_user.mgridtm,
1987 im->xlab_user.mgridst);
1988 ti < im->end;
1989 ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
1990 ){
1991 /* are we inside the graph ? */
1992 if (ti < im->start || ti > im->end) continue;
1993 X0 = xtr(im,ti);
1994 gfx_new_dashed_line(im->canvas,X0,Y0+3, X0,Y1-2,MGRIDWIDTH,
1995 im->graph_col[GRC_MGRID],
1996 im->grid_dash_on, im->grid_dash_off);
1998 }
1999 /* paint the labels below the graph */
2000 for(ti = find_first_time(im->start - im->xlab_user.precis/2,
2001 im->xlab_user.labtm,
2002 im->xlab_user.labst);
2003 ti <= im->end - im->xlab_user.precis/2;
2004 ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
2005 ){
2006 tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
2007 /* are we inside the graph ? */
2008 if (tilab < im->start || tilab > im->end) continue;
2010 #if HAVE_STRFTIME
2011 localtime_r(&tilab, &tm);
2012 strftime(graph_label,99,im->xlab_user.stst, &tm);
2013 #else
2014 # error "your libc has no strftime I guess we'll abort the exercise here."
2015 #endif
2016 gfx_new_text ( im->canvas,
2017 xtr(im,tilab), Y0+im->text_prop[TEXT_PROP_AXIS].size*1.4+5,
2018 im->graph_col[GRC_FONT],
2019 im->text_prop[TEXT_PROP_AXIS].font,
2020 im->text_prop[TEXT_PROP_AXIS].size,
2021 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_BOTTOM,
2022 graph_label );
2024 }
2026 }
2029 void
2030 axis_paint(
2031 image_desc_t *im
2032 )
2033 {
2034 /* draw x and y axis */
2035 /* gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2036 im->xorigin+im->xsize,im->yorigin-im->ysize,
2037 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2039 gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2040 im->xorigin+im->xsize,im->yorigin-im->ysize,
2041 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2043 gfx_new_line ( im->canvas, im->xorigin-4,im->yorigin,
2044 im->xorigin+im->xsize+4,im->yorigin,
2045 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2047 gfx_new_line ( im->canvas, im->xorigin,im->yorigin+4,
2048 im->xorigin,im->yorigin-im->ysize-4,
2049 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2052 /* arrow for X and Y axis direction */
2053 gfx_new_area ( im->canvas,
2054 im->xorigin+im->xsize+2, im->yorigin-2,
2055 im->xorigin+im->xsize+2, im->yorigin+3,
2056 im->xorigin+im->xsize+7, im->yorigin+0.5, /* LINEOFFSET */
2057 im->graph_col[GRC_ARROW]);
2059 gfx_new_area ( im->canvas,
2060 im->xorigin-2, im->yorigin-im->ysize-2,
2061 im->xorigin+3, im->yorigin-im->ysize-2,
2062 im->xorigin+0.5, im->yorigin-im->ysize-7, /* LINEOFFSET */
2063 im->graph_col[GRC_ARROW]);
2065 }
2067 void
2068 grid_paint(image_desc_t *im)
2069 {
2070 long i;
2071 int res=0;
2072 double X0,Y0; /* points for filled graph and more*/
2073 gfx_node_t *node;
2075 /* draw 3d border */
2076 node = gfx_new_area (im->canvas, 0,im->yimg,
2077 2,im->yimg-2,
2078 2,2,im->graph_col[GRC_SHADEA]);
2079 gfx_add_point( node , im->ximg - 2, 2 );
2080 gfx_add_point( node , im->ximg, 0 );
2081 gfx_add_point( node , 0,0 );
2082 /* gfx_add_point( node , 0,im->yimg ); */
2084 node = gfx_new_area (im->canvas, 2,im->yimg-2,
2085 im->ximg-2,im->yimg-2,
2086 im->ximg - 2, 2,
2087 im->graph_col[GRC_SHADEB]);
2088 gfx_add_point( node , im->ximg,0);
2089 gfx_add_point( node , im->ximg,im->yimg);
2090 gfx_add_point( node , 0,im->yimg);
2091 /* gfx_add_point( node , 0,im->yimg ); */
2094 if (im->draw_x_grid == 1 )
2095 vertical_grid(im);
2097 if (im->draw_y_grid == 1){
2098 if(im->logarithmic){
2099 res = horizontal_log_grid(im);
2100 } else {
2101 res = draw_horizontal_grid(im);
2102 }
2104 /* dont draw horizontal grid if there is no min and max val */
2105 if (! res ) {
2106 char *nodata = "No Data found";
2107 gfx_new_text(im->canvas,im->ximg/2, (2*im->yorigin-im->ysize) / 2,
2108 im->graph_col[GRC_FONT],
2109 im->text_prop[TEXT_PROP_AXIS].font,
2110 im->text_prop[TEXT_PROP_AXIS].size,
2111 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
2112 nodata );
2113 }
2114 }
2116 /* yaxis unit description */
2117 gfx_new_text( im->canvas,
2118 10, (im->yorigin - im->ysize/2),
2119 im->graph_col[GRC_FONT],
2120 im->text_prop[TEXT_PROP_UNIT].font,
2121 im->text_prop[TEXT_PROP_UNIT].size, im->tabwidth,
2122 RRDGRAPH_YLEGEND_ANGLE,
2123 GFX_H_LEFT, GFX_V_CENTER,
2124 im->ylegend);
2126 /* graph title */
2127 gfx_new_text( im->canvas,
2128 im->ximg/2, im->text_prop[TEXT_PROP_TITLE].size*1.3+4,
2129 im->graph_col[GRC_FONT],
2130 im->text_prop[TEXT_PROP_TITLE].font,
2131 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
2132 GFX_H_CENTER, GFX_V_CENTER,
2133 im->title);
2134 /* rrdtool 'logo' */
2135 gfx_new_text( im->canvas,
2136 im->ximg-7, 7,
2137 ( im->graph_col[GRC_FONT] & 0xffffff00 ) | 0x00000044,
2138 im->text_prop[TEXT_PROP_AXIS].font,
2139 5.5, im->tabwidth, 270,
2140 GFX_H_RIGHT, GFX_V_TOP,
2141 "RRDTOOL / TOBI OETIKER");
2143 /* graph watermark */
2144 if(im->watermark[0] != '\0') {
2145 gfx_new_text( im->canvas,
2146 im->ximg/2, im->yimg-6,
2147 ( im->graph_col[GRC_FONT] & 0xffffff00 ) | 0x00000044,
2148 im->text_prop[TEXT_PROP_AXIS].font,
2149 5.5, im->tabwidth, 0,
2150 GFX_H_CENTER, GFX_V_BOTTOM,
2151 im->watermark);
2152 }
2154 /* graph labels */
2155 if( !(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH) ) {
2156 for(i=0;i<im->gdes_c;i++){
2157 if(im->gdes[i].legend[0] =='\0')
2158 continue;
2160 /* im->gdes[i].leg_y is the bottom of the legend */
2161 X0 = im->gdes[i].leg_x;
2162 Y0 = im->gdes[i].leg_y;
2163 gfx_new_text ( im->canvas, X0, Y0,
2164 im->graph_col[GRC_FONT],
2165 im->text_prop[TEXT_PROP_LEGEND].font,
2166 im->text_prop[TEXT_PROP_LEGEND].size,
2167 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
2168 im->gdes[i].legend );
2169 /* The legend for GRAPH items starts with "M " to have
2170 enough space for the box */
2171 if ( im->gdes[i].gf != GF_PRINT &&
2172 im->gdes[i].gf != GF_GPRINT &&
2173 im->gdes[i].gf != GF_COMMENT) {
2174 int boxH, boxV;
2176 boxH = gfx_get_text_width(im->canvas, 0,
2177 im->text_prop[TEXT_PROP_LEGEND].font,
2178 im->text_prop[TEXT_PROP_LEGEND].size,
2179 im->tabwidth,"o", 0) * 1.2;
2180 boxV = boxH*1.1;
2182 /* make sure transparent colors show up the same way as in the graph */
2183 node = gfx_new_area(im->canvas,
2184 X0,Y0-boxV,
2185 X0,Y0,
2186 X0+boxH,Y0,
2187 im->graph_col[GRC_BACK]);
2188 gfx_add_point ( node, X0+boxH, Y0-boxV );
2190 node = gfx_new_area(im->canvas,
2191 X0,Y0-boxV,
2192 X0,Y0,
2193 X0+boxH,Y0,
2194 im->gdes[i].col);
2195 gfx_add_point ( node, X0+boxH, Y0-boxV );
2196 node = gfx_new_line(im->canvas,
2197 X0,Y0-boxV,
2198 X0,Y0,
2199 1.0,im->graph_col[GRC_FRAME]);
2200 gfx_add_point(node,X0+boxH,Y0);
2201 gfx_add_point(node,X0+boxH,Y0-boxV);
2202 gfx_close_path(node);
2203 }
2204 }
2205 }
2206 }
2209 /*****************************************************
2210 * lazy check make sure we rely need to create this graph
2211 *****************************************************/
2213 int lazy_check(image_desc_t *im){
2214 FILE *fd = NULL;
2215 int size = 1;
2216 struct stat imgstat;
2218 if (im->lazy == 0) return 0; /* no lazy option */
2219 if (stat(im->graphfile,&imgstat) != 0)
2220 return 0; /* can't stat */
2221 /* one pixel in the existing graph is more then what we would
2222 change here ... */
2223 if (time(NULL) - imgstat.st_mtime >
2224 (im->end - im->start) / im->xsize)
2225 return 0;
2226 if ((fd = fopen(im->graphfile,"rb")) == NULL)
2227 return 0; /* the file does not exist */
2228 switch (im->canvas->imgformat) {
2229 case IF_PNG:
2230 size = PngSize(fd,&(im->ximg),&(im->yimg));
2231 break;
2232 default:
2233 size = 1;
2234 }
2235 fclose(fd);
2236 return size;
2237 }
2239 #ifdef WITH_PIECHART
2240 void
2241 pie_part(image_desc_t *im, gfx_color_t color,
2242 double PieCenterX, double PieCenterY, double Radius,
2243 double startangle, double endangle)
2244 {
2245 gfx_node_t *node;
2246 double angle;
2247 double step=M_PI/50; /* Number of iterations for the circle;
2248 ** 10 is definitely too low, more than
2249 ** 50 seems to be overkill
2250 */
2252 /* Strange but true: we have to work clockwise or else
2253 ** anti aliasing nor transparency don't work.
2254 **
2255 ** This test is here to make sure we do it right, also
2256 ** this makes the for...next loop more easy to implement.
2257 ** The return will occur if the user enters a negative number
2258 ** (which shouldn't be done according to the specs) or if the
2259 ** programmers do something wrong (which, as we all know, never
2260 ** happens anyway :)
2261 */
2262 if (endangle<startangle) return;
2264 /* Hidden feature: Radius decreases each full circle */
2265 angle=startangle;
2266 while (angle>=2*M_PI) {
2267 angle -= 2*M_PI;
2268 Radius *= 0.8;
2269 }
2271 node=gfx_new_area(im->canvas,
2272 PieCenterX+sin(startangle)*Radius,
2273 PieCenterY-cos(startangle)*Radius,
2274 PieCenterX,
2275 PieCenterY,
2276 PieCenterX+sin(endangle)*Radius,
2277 PieCenterY-cos(endangle)*Radius,
2278 color);
2279 for (angle=endangle;angle-startangle>=step;angle-=step) {
2280 gfx_add_point(node,
2281 PieCenterX+sin(angle)*Radius,
2282 PieCenterY-cos(angle)*Radius );
2283 }
2284 }
2286 #endif
2288 int
2289 graph_size_location(image_desc_t *im, int elements
2291 #ifdef WITH_PIECHART
2292 , int piechart
2293 #endif
2295 )
2296 {
2297 /* The actual size of the image to draw is determined from
2298 ** several sources. The size given on the command line is
2299 ** the graph area but we need more as we have to draw labels
2300 ** and other things outside the graph area
2301 */
2303 /* +-+-------------------------------------------+
2304 ** |l|.................title.....................|
2305 ** |e+--+-------------------------------+--------+
2306 ** |b| b| | |
2307 ** |a| a| | pie |
2308 ** |l| l| main graph area | chart |
2309 ** |.| .| | area |
2310 ** |t| y| | |
2311 ** |r+--+-------------------------------+--------+
2312 ** |e| | x-axis labels | |
2313 ** |v+--+-------------------------------+--------+
2314 ** | |..............legends......................|
2315 ** +-+-------------------------------------------+
2316 ** | watermark |
2317 ** +---------------------------------------------+
2318 */
2319 int Xvertical=0,
2320 Ytitle =0,
2321 Xylabel =0,
2322 Xmain =0, Ymain =0,
2323 #ifdef WITH_PIECHART
2324 Xpie =0, Ypie =0,
2325 #endif
2326 Yxlabel =0,
2327 #if 0
2328 Xlegend =0, Ylegend =0,
2329 #endif
2330 Xspacing =15, Yspacing =15,
2332 Ywatermark =4;
2334 if (im->extra_flags & ONLY_GRAPH) {
2335 im->xorigin =0;
2336 im->ximg = im->xsize;
2337 im->yimg = im->ysize;
2338 im->yorigin = im->ysize;
2339 ytr(im,DNAN);
2340 return 0;
2341 }
2343 if (im->ylegend[0] != '\0' ) {
2344 Xvertical = im->text_prop[TEXT_PROP_UNIT].size *2;
2345 }
2348 if (im->title[0] != '\0') {
2349 /* The title is placed "inbetween" two text lines so it
2350 ** automatically has some vertical spacing. The horizontal
2351 ** spacing is added here, on each side.
2352 */
2353 /* don't care for the with of the title
2354 Xtitle = gfx_get_text_width(im->canvas, 0,
2355 im->text_prop[TEXT_PROP_TITLE].font,
2356 im->text_prop[TEXT_PROP_TITLE].size,
2357 im->tabwidth,
2358 im->title, 0) + 2*Xspacing; */
2359 Ytitle = im->text_prop[TEXT_PROP_TITLE].size*2.6+10;
2360 }
2362 if (elements) {
2363 Xmain=im->xsize;
2364 Ymain=im->ysize;
2365 if (im->draw_x_grid) {
2366 Yxlabel=im->text_prop[TEXT_PROP_AXIS].size *2.5;
2367 }
2368 if (im->draw_y_grid) {
2369 Xylabel=gfx_get_text_width(im->canvas, 0,
2370 im->text_prop[TEXT_PROP_AXIS].font,
2371 im->text_prop[TEXT_PROP_AXIS].size,
2372 im->tabwidth,
2373 "0", 0) * im->unitslength;
2374 }
2375 }
2377 #ifdef WITH_PIECHART
2378 if (piechart) {
2379 im->piesize=im->xsize<im->ysize?im->xsize:im->ysize;
2380 Xpie=im->piesize;
2381 Ypie=im->piesize;
2382 }
2383 #endif
2385 /* Now calculate the total size. Insert some spacing where
2386 desired. im->xorigin and im->yorigin need to correspond
2387 with the lower left corner of the main graph area or, if
2388 this one is not set, the imaginary box surrounding the
2389 pie chart area. */
2391 /* The legend width cannot yet be determined, as a result we
2392 ** have problems adjusting the image to it. For now, we just
2393 ** forget about it at all; the legend will have to fit in the
2394 ** size already allocated.
2395 */
2396 im->ximg = Xylabel + Xmain + 2 * Xspacing;
2398 #ifdef WITH_PIECHART
2399 im->ximg += Xpie;
2400 #endif
2402 if (Xmain) im->ximg += Xspacing;
2403 #ifdef WITH_PIECHART
2404 if (Xpie) im->ximg += Xspacing;
2405 #endif
2407 im->xorigin = Xspacing + Xylabel;
2409 /* the length of the title should not influence with width of the graph
2410 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2412 if (Xvertical) { /* unit description */
2413 im->ximg += Xvertical;
2414 im->xorigin += Xvertical;
2415 }
2416 xtr(im,0);
2418 /* The vertical size is interesting... we need to compare
2419 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with
2420 ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2421 ** in order to start even thinking about Ylegend or Ywatermark.
2422 **
2423 ** Do it in three portions: First calculate the inner part,
2424 ** then do the legend, then adjust the total height of the img,
2425 ** adding space for a watermark if one exists;
2426 */
2428 /* reserve space for main and/or pie */
2430 im->yimg = Ymain + Yxlabel;
2432 #ifdef WITH_PIECHART
2433 if (im->yimg < Ypie) im->yimg = Ypie;
2434 #endif
2436 im->yorigin = im->yimg - Yxlabel;
2438 /* reserve space for the title *or* some padding above the graph */
2439 if (Ytitle) {
2440 im->yimg += Ytitle;
2441 im->yorigin += Ytitle;
2442 } else {
2443 im->yimg += 1.5*Yspacing;
2444 im->yorigin += 1.5*Yspacing;
2445 }
2446 /* reserve space for padding below the graph */
2447 im->yimg += Yspacing;
2449 /* Determine where to place the legends onto the image.
2450 ** Adjust im->yimg to match the space requirements.
2451 */
2452 if(leg_place(im)==-1)
2453 return -1;
2455 if (im->watermark[0] != '\0') {
2456 im->yimg += Ywatermark;
2457 }
2459 #if 0
2460 if (Xlegend > im->ximg) {
2461 im->ximg = Xlegend;
2462 /* reposition Pie */
2463 }
2464 #endif
2466 #ifdef WITH_PIECHART
2467 /* The pie is placed in the upper right hand corner,
2468 ** just below the title (if any) and with sufficient
2469 ** padding.
2470 */
2471 if (elements) {
2472 im->pie_x = im->ximg - Xspacing - Xpie/2;
2473 im->pie_y = im->yorigin-Ymain+Ypie/2;
2474 } else {
2475 im->pie_x = im->ximg/2;
2476 im->pie_y = im->yorigin-Ypie/2;
2477 }
2478 #endif
2480 ytr(im,DNAN);
2481 return 0;
2482 }
2484 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
2485 /* yes we are loosing precision by doing tos with floats instead of doubles
2486 but it seems more stable this way. */
2488 static int AlmostEqual2sComplement (float A, float B, int maxUlps)
2489 {
2491 int aInt = *(int*)&A;
2492 int bInt = *(int*)&B;
2493 int intDiff;
2494 /* Make sure maxUlps is non-negative and small enough that the
2495 default NAN won't compare as equal to anything. */
2497 /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
2499 /* Make aInt lexicographically ordered as a twos-complement int */
2501 if (aInt < 0)
2502 aInt = 0x80000000l - aInt;
2504 /* Make bInt lexicographically ordered as a twos-complement int */
2506 if (bInt < 0)
2507 bInt = 0x80000000l - bInt;
2509 intDiff = abs(aInt - bInt);
2511 if (intDiff <= maxUlps)
2512 return 1;
2514 return 0;
2515 }
2517 /* draw that picture thing ... */
2518 int
2519 graph_paint(image_desc_t *im, char ***calcpr)
2520 {
2521 int i,ii;
2522 int lazy = lazy_check(im);
2523 #ifdef WITH_PIECHART
2524 int piechart = 0;
2525 double PieStart=0.0;
2526 #endif
2527 FILE *fo;
2528 gfx_node_t *node;
2530 double areazero = 0.0;
2531 graph_desc_t *lastgdes = NULL;
2533 /* if we are lazy and there is nothing to PRINT ... quit now */
2534 if (lazy && im->prt_c==0) return 0;
2536 /* pull the data from the rrd files ... */
2538 if(data_fetch(im)==-1)
2539 return -1;
2541 /* evaluate VDEF and CDEF operations ... */
2542 if(data_calc(im)==-1)
2543 return -1;
2545 #ifdef WITH_PIECHART
2546 /* check if we need to draw a piechart */
2547 for(i=0;i<im->gdes_c;i++){
2548 if (im->gdes[i].gf == GF_PART) {
2549 piechart=1;
2550 break;
2551 }
2552 }
2553 #endif
2555 /* calculate and PRINT and GPRINT definitions. We have to do it at
2556 * this point because it will affect the length of the legends
2557 * if there are no graph elements we stop here ...
2558 * if we are lazy, try to quit ...
2559 */
2560 i=print_calc(im,calcpr);
2561 if(i<0) return -1;
2562 if(((i==0)
2563 #ifdef WITH_PIECHART
2564 &&(piechart==0)
2565 #endif
2566 ) || lazy) return 0;
2568 #ifdef WITH_PIECHART
2569 /* If there's only the pie chart to draw, signal this */
2570 if (i==0) piechart=2;
2571 #endif
2573 /* get actual drawing data and find min and max values*/
2574 if(data_proc(im)==-1)
2575 return -1;
2577 if(!im->logarithmic){si_unit(im);} /* identify si magnitude Kilo, Mega Giga ? */
2579 if(!im->rigid && ! im->logarithmic)
2580 expand_range(im); /* make sure the upper and lower limit are
2581 sensible values */
2583 if (!calc_horizontal_grid(im))
2584 return -1;
2586 if (im->gridfit)
2587 apply_gridfit(im);
2590 /**************************************************************
2591 *** Calculating sizes and locations became a bit confusing ***
2592 *** so I moved this into a separate function. ***
2593 **************************************************************/
2594 if(graph_size_location(im,i
2595 #ifdef WITH_PIECHART
2596 ,piechart
2597 #endif
2598 )==-1)
2599 return -1;
2601 /* the actual graph is created by going through the individual
2602 graph elements and then drawing them */
2604 node=gfx_new_area ( im->canvas,
2605 0, 0,
2606 0, im->yimg,
2607 im->ximg, im->yimg,
2608 im->graph_col[GRC_BACK]);
2610 gfx_add_point(node,im->ximg, 0);
2612 #ifdef WITH_PIECHART
2613 if (piechart != 2) {
2614 #endif
2615 node=gfx_new_area ( im->canvas,
2616 im->xorigin, im->yorigin,
2617 im->xorigin + im->xsize, im->yorigin,
2618 im->xorigin + im->xsize, im->yorigin-im->ysize,
2619 im->graph_col[GRC_CANVAS]);
2621 gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
2623 if (im->minval > 0.0)
2624 areazero = im->minval;
2625 if (im->maxval < 0.0)
2626 areazero = im->maxval;
2627 #ifdef WITH_PIECHART
2628 }
2629 #endif
2631 #ifdef WITH_PIECHART
2632 if (piechart) {
2633 pie_part(im,im->graph_col[GRC_CANVAS],im->pie_x,im->pie_y,im->piesize*0.5,0,2*M_PI);
2634 }
2635 #endif
2637 for(i=0;i<im->gdes_c;i++){
2638 switch(im->gdes[i].gf){
2639 case GF_CDEF:
2640 case GF_VDEF:
2641 case GF_DEF:
2642 case GF_PRINT:
2643 case GF_GPRINT:
2644 case GF_COMMENT:
2645 case GF_HRULE:
2646 case GF_VRULE:
2647 case GF_XPORT:
2648 case GF_SHIFT:
2649 break;
2650 case GF_TICK:
2651 for (ii = 0; ii < im->xsize; ii++)
2652 {
2653 if (!isnan(im->gdes[i].p_data[ii]) &&
2654 im->gdes[i].p_data[ii] != 0.0)
2655 {
2656 if (im -> gdes[i].yrule > 0 ) {
2657 gfx_new_line(im->canvas,
2658 im -> xorigin + ii, im->yorigin,
2659 im -> xorigin + ii, im->yorigin - im -> gdes[i].yrule * im -> ysize,
2660 1.0,
2661 im -> gdes[i].col );
2662 } else if ( im -> gdes[i].yrule < 0 ) {
2663 gfx_new_line(im->canvas,
2664 im -> xorigin + ii, im->yorigin - im -> ysize,
2665 im -> xorigin + ii, im->yorigin - ( 1 - im -> gdes[i].yrule ) * im -> ysize,
2666 1.0,
2667 im -> gdes[i].col );
2669 }
2670 }
2671 }
2672 break;
2673 case GF_LINE:
2674 case GF_AREA:
2675 /* fix data points at oo and -oo */
2676 for(ii=0;ii<im->xsize;ii++){
2677 if (isinf(im->gdes[i].p_data[ii])){
2678 if (im->gdes[i].p_data[ii] > 0) {
2679 im->gdes[i].p_data[ii] = im->maxval ;
2680 } else {
2681 im->gdes[i].p_data[ii] = im->minval ;
2682 }
2684 }
2685 } /* for */
2687 /* *******************************************************
2688 a ___. (a,t)
2689 | | ___
2690 ____| | | |
2691 | |___|
2692 -------|--t-1--t--------------------------------
2694 if we know the value at time t was a then
2695 we draw a square from t-1 to t with the value a.
2697 ********************************************************* */
2698 if (im->gdes[i].col != 0x0){
2699 /* GF_LINE and friend */
2700 if(im->gdes[i].gf == GF_LINE ){
2701 double last_y=0.0;
2702 node = NULL;
2703 for(ii=1;ii<im->xsize;ii++){
2704 if (isnan(im->gdes[i].p_data[ii]) || (im->slopemode==1 && isnan(im->gdes[i].p_data[ii-1]))){
2705 node = NULL;
2706 continue;
2707 }
2708 if ( node == NULL ) {
2709 last_y = ytr(im,im->gdes[i].p_data[ii]);
2710 if ( im->slopemode == 0 ){
2711 node = gfx_new_line(im->canvas,
2712 ii-1+im->xorigin,last_y,
2713 ii+im->xorigin,last_y,
2714 im->gdes[i].linewidth,
2715 im->gdes[i].col);
2716 } else {
2717 node = gfx_new_line(im->canvas,
2718 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2719 ii+im->xorigin,last_y,
2720 im->gdes[i].linewidth,
2721 im->gdes[i].col);
2722 }
2723 } else {
2724 double new_y = ytr(im,im->gdes[i].p_data[ii]);
2725 if ( im->slopemode==0 && ! AlmostEqual2sComplement(new_y,last_y,4)){
2726 gfx_add_point(node,ii-1+im->xorigin,new_y);
2727 };
2728 last_y = new_y;
2729 gfx_add_point(node,ii+im->xorigin,new_y);
2730 };
2732 }
2733 } else {
2734 int idxI=-1;
2735 double *foreY=malloc(sizeof(double)*im->xsize*2);
2736 double *foreX=malloc(sizeof(double)*im->xsize*2);
2737 double *backY=malloc(sizeof(double)*im->xsize*2);
2738 double *backX=malloc(sizeof(double)*im->xsize*2);
2739 int drawem = 0;
2740 for(ii=0;ii<=im->xsize;ii++){
2741 double ybase,ytop;
2742 if ( idxI > 0 && ( drawem != 0 || ii==im->xsize)){
2743 int cntI=1;
2744 int lastI=0;
2745 while (cntI < idxI && AlmostEqual2sComplement(foreY[lastI],foreY[cntI],4) && AlmostEqual2sComplement(foreY[lastI],foreY[cntI+1],4)){cntI++;}
2746 node = gfx_new_area(im->canvas,
2747 backX[0],backY[0],
2748 foreX[0],foreY[0],
2749 foreX[cntI],foreY[cntI], im->gdes[i].col);
2750 while (cntI < idxI) {
2751 lastI = cntI;
2752 cntI++;
2753 while ( cntI < idxI && AlmostEqual2sComplement(foreY[lastI],foreY[cntI],4) && AlmostEqual2sComplement(foreY[lastI],foreY[cntI+1],4)){cntI++;}
2754 gfx_add_point(node,foreX[cntI],foreY[cntI]);
2755 }
2756 gfx_add_point(node,backX[idxI],backY[idxI]);
2757 while (idxI > 1){
2758 lastI = idxI;
2759 idxI--;
2760 while ( idxI > 1 && AlmostEqual2sComplement(backY[lastI], backY[idxI],4) && AlmostEqual2sComplement(backY[lastI],backY[idxI-1],4)){idxI--;}
2761 gfx_add_point(node,backX[idxI],backY[idxI]);
2762 }
2763 idxI=-1;
2764 drawem = 0;
2765 }
2766 if (drawem != 0){
2767 drawem = 0;
2768 idxI=-1;
2769 }
2770 if (ii == im->xsize) break;
2772 /* keep things simple for now, just draw these bars
2773 do not try to build a big and complex area */
2776 if ( im->slopemode == 0 && ii==0){
2777 continue;
2778 }
2779 if ( isnan(im->gdes[i].p_data[ii]) ) {
2780 drawem = 1;
2781 continue;
2782 }
2783 ytop = ytr(im,im->gdes[i].p_data[ii]);
2784 if ( lastgdes && im->gdes[i].stack ) {
2785 ybase = ytr(im,lastgdes->p_data[ii]);
2786 } else {
2787 ybase = ytr(im,areazero);
2788 }
2789 if ( ybase == ytop ){
2790 drawem = 1;
2791 continue;
2792 }
2793 /* every area has to be wound clock-wise,
2794 so we have to make sur base remains base */
2795 if (ybase > ytop){
2796 double extra = ytop;
2797 ytop = ybase;
2798 ybase = extra;
2799 }
2800 if ( im->slopemode == 0 ){
2801 backY[++idxI] = ybase-0.2;
2802 backX[idxI] = ii+im->xorigin-1;
2803 foreY[idxI] = ytop+0.2;
2804 foreX[idxI] = ii+im->xorigin-1;
2805 }
2806 backY[++idxI] = ybase-0.2;
2807 backX[idxI] = ii+im->xorigin;
2808 foreY[idxI] = ytop+0.2;
2809 foreX[idxI] = ii+im->xorigin;
2810 }
2811 /* close up any remaining area */
2812 free(foreY);
2813 free(foreX);
2814 free(backY);
2815 free(backX);
2816 } /* else GF_LINE */
2817 } /* if color != 0x0 */
2818 /* make sure we do not run into trouble when stacking on NaN */
2819 for(ii=0;ii<im->xsize;ii++){
2820 if (isnan(im->gdes[i].p_data[ii])) {
2821 if (lastgdes && (im->gdes[i].stack)) {
2822 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
2823 } else {
2824 im->gdes[i].p_data[ii] = areazero;
2825 }
2826 }
2827 }
2828 lastgdes = &(im->gdes[i]);
2829 break;
2830 #ifdef WITH_PIECHART
2831 case GF_PART:
2832 if(isnan(im->gdes[i].yrule)) /* fetch variable */
2833 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2835 if (finite(im->gdes[i].yrule)) { /* even the fetched var can be NaN */
2836 pie_part(im,im->gdes[i].col,
2837 im->pie_x,im->pie_y,im->piesize*0.4,
2838 M_PI*2.0*PieStart/100.0,
2839 M_PI*2.0*(PieStart+im->gdes[i].yrule)/100.0);
2840 PieStart += im->gdes[i].yrule;
2841 }
2842 break;
2843 #endif
2844 case GF_STACK:
2845 rrd_set_error("STACK should already be turned into LINE or AREA here");
2846 return -1;
2847 break;
2849 } /* switch */
2850 }
2851 #ifdef WITH_PIECHART
2852 if (piechart==2) {
2853 im->draw_x_grid=0;
2854 im->draw_y_grid=0;
2855 }
2856 #endif
2859 /* grid_paint also does the text */
2860 if( !(im->extra_flags & ONLY_GRAPH) )
2861 grid_paint(im);
2864 if( !(im->extra_flags & ONLY_GRAPH) )
2865 axis_paint(im);
2867 /* the RULES are the last thing to paint ... */
2868 for(i=0;i<im->gdes_c;i++){
2870 switch(im->gdes[i].gf){
2871 case GF_HRULE:
2872 if(im->gdes[i].yrule >= im->minval
2873 && im->gdes[i].yrule <= im->maxval)
2874 gfx_new_line(im->canvas,
2875 im->xorigin,ytr(im,im->gdes[i].yrule),
2876 im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2877 1.0,im->gdes[i].col);
2878 break;
2879 case GF_VRULE:
2880 if(im->gdes[i].xrule >= im->start
2881 && im->gdes[i].xrule <= im->end)
2882 gfx_new_line(im->canvas,
2883 xtr(im,im->gdes[i].xrule),im->yorigin,
2884 xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2885 1.0,im->gdes[i].col);
2886 break;
2887 default:
2888 break;
2889 }
2890 }
2893 if (strcmp(im->graphfile,"-")==0) {
2894 fo = im->graphhandle ? im->graphhandle : stdout;
2895 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
2896 /* Change translation mode for stdout to BINARY */
2897 _setmode( _fileno( fo ), O_BINARY );
2898 #endif
2899 } else {
2900 if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2901 rrd_set_error("Opening '%s' for write: %s",im->graphfile,
2902 rrd_strerror(errno));
2903 return (-1);
2904 }
2905 }
2906 gfx_render (im->canvas,im->ximg,im->yimg,0x00000000,fo);
2907 if (strcmp(im->graphfile,"-") != 0)
2908 fclose(fo);
2909 return 0;
2910 }
2913 /*****************************************************
2914 * graph stuff
2915 *****************************************************/
2917 int
2918 gdes_alloc(image_desc_t *im){
2920 im->gdes_c++;
2921 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2922 * sizeof(graph_desc_t)))==NULL){
2923 rrd_set_error("realloc graph_descs");
2924 return -1;
2925 }
2928 im->gdes[im->gdes_c-1].step=im->step;
2929 im->gdes[im->gdes_c-1].step_orig=im->step;
2930 im->gdes[im->gdes_c-1].stack=0;
2931 im->gdes[im->gdes_c-1].linewidth=0;
2932 im->gdes[im->gdes_c-1].debug=0;
2933 im->gdes[im->gdes_c-1].start=im->start;
2934 im->gdes[im->gdes_c-1].start_orig=im->start;
2935 im->gdes[im->gdes_c-1].end=im->end;
2936 im->gdes[im->gdes_c-1].end_orig=im->end;
2937 im->gdes[im->gdes_c-1].vname[0]='\0';
2938 im->gdes[im->gdes_c-1].data=NULL;
2939 im->gdes[im->gdes_c-1].ds_namv=NULL;
2940 im->gdes[im->gdes_c-1].data_first=0;
2941 im->gdes[im->gdes_c-1].p_data=NULL;
2942 im->gdes[im->gdes_c-1].rpnp=NULL;
2943 im->gdes[im->gdes_c-1].shift=0;
2944 im->gdes[im->gdes_c-1].col = 0x0;
2945 im->gdes[im->gdes_c-1].legend[0]='\0';
2946 im->gdes[im->gdes_c-1].format[0]='\0';
2947 im->gdes[im->gdes_c-1].strftm=0;
2948 im->gdes[im->gdes_c-1].rrd[0]='\0';
2949 im->gdes[im->gdes_c-1].ds=-1;
2950 im->gdes[im->gdes_c-1].cf_reduce=CF_AVERAGE;
2951 im->gdes[im->gdes_c-1].cf=CF_AVERAGE;
2952 im->gdes[im->gdes_c-1].p_data=NULL;
2953 im->gdes[im->gdes_c-1].yrule=DNAN;
2954 im->gdes[im->gdes_c-1].xrule=0;
2955 return 0;
2956 }
2958 /* copies input untill the first unescaped colon is found
2959 or until input ends. backslashes have to be escaped as well */
2960 int
2961 scan_for_col(const char *const input, int len, char *const output)
2962 {
2963 int inp,outp=0;
2964 for (inp=0;
2965 inp < len &&
2966 input[inp] != ':' &&
2967 input[inp] != '\0';
2968 inp++){
2969 if (input[inp] == '\\' &&
2970 input[inp+1] != '\0' &&
2971 (input[inp+1] == '\\' ||
2972 input[inp+1] == ':')){
2973 output[outp++] = input[++inp];
2974 }
2975 else {
2976 output[outp++] = input[inp];
2977 }
2978 }
2979 output[outp] = '\0';
2980 return inp;
2981 }
2982 /* Some surgery done on this function, it became ridiculously big.
2983 ** Things moved:
2984 ** - initializing now in rrd_graph_init()
2985 ** - options parsing now in rrd_graph_options()
2986 ** - script parsing now in rrd_graph_script()
2987 */
2988 int
2989 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize, FILE *stream, double *ymin, double *ymax)
2990 {
2991 image_desc_t im;
2992 rrd_graph_init(&im);
2993 im.graphhandle = stream;
2995 rrd_graph_options(argc,argv,&im);
2996 if (rrd_test_error()) {
2997 im_free(&im);
2998 return -1;
2999 }
3001 if (strlen(argv[optind])>=MAXPATH) {
3002 rrd_set_error("filename (including path) too long");
3003 im_free(&im);
3004 return -1;
3005 }
3006 strncpy(im.graphfile,argv[optind],MAXPATH-1);
3007 im.graphfile[MAXPATH-1]='\0';
3009 rrd_graph_script(argc,argv,&im,1);
3010 if (rrd_test_error()) {
3011 im_free(&im);
3012 return -1;
3013 }
3015 /* Everything is now read and the actual work can start */
3017 (*prdata)=NULL;
3018 if (graph_paint(&im,prdata)==-1){
3019 im_free(&im);
3020 return -1;
3021 }
3023 /* The image is generated and needs to be output.
3024 ** Also, if needed, print a line with information about the image.
3025 */
3027 *xsize=im.ximg;
3028 *ysize=im.yimg;
3029 *ymin=im.minval;
3030 *ymax=im.maxval;
3031 if (im.imginfo) {
3032 char *filename;
3033 if (!(*prdata)) {
3034 /* maybe prdata is not allocated yet ... lets do it now */
3035 if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
3036 rrd_set_error("malloc imginfo");
3037 return -1;
3038 };
3039 }
3040 if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
3041 ==NULL){
3042 rrd_set_error("malloc imginfo");
3043 return -1;
3044 }
3045 filename=im.graphfile+strlen(im.graphfile);
3046 while(filename > im.graphfile) {
3047 if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
3048 filename--;
3049 }
3051 sprintf((*prdata)[0],im.imginfo,filename,(long)(im.canvas->zoom*im.ximg),(long)(im.canvas->zoom*im.yimg));
3052 }
3053 im_free(&im);
3054 return 0;
3055 }
3057 void
3058 rrd_graph_init(image_desc_t *im)
3059 {
3060 unsigned int i;
3062 #ifdef HAVE_TZSET
3063 tzset();
3064 #endif
3065 #ifdef HAVE_SETLOCALE
3066 setlocale(LC_TIME,"");
3067 #ifdef HAVE_MBSTOWCS
3068 setlocale(LC_CTYPE,"");
3069 #endif
3070 #endif
3071 im->yorigin=0;
3072 im->xorigin=0;
3073 im->minval=0;
3074 im->xlab_user.minsec = -1;
3075 im->ximg=0;
3076 im->yimg=0;
3077 im->xsize = 400;
3078 im->ysize = 100;
3079 im->step = 0;
3080 im->ylegend[0] = '\0';
3081 im->title[0] = '\0';
3082 im->watermark[0] = '\0';
3083 im->minval = DNAN;
3084 im->maxval = DNAN;
3085 im->unitsexponent= 9999;
3086 im->unitslength= 6;
3087 im->symbol = ' ';
3088 im->viewfactor = 1.0;
3089 im->extra_flags= 0;
3090 im->rigid = 0;
3091 im->gridfit = 1;
3092 im->imginfo = NULL;
3093 im->lazy = 0;
3094 im->slopemode = 0;
3095 im->logarithmic = 0;
3096 im->ygridstep = DNAN;
3097 im->draw_x_grid = 1;
3098 im->draw_y_grid = 1;
3099 im->base = 1000;
3100 im->prt_c = 0;
3101 im->gdes_c = 0;
3102 im->gdes = NULL;
3103 im->canvas = gfx_new_canvas();
3104 im->grid_dash_on = 1;
3105 im->grid_dash_off = 1;
3106 im->tabwidth = 40.0;
3108 for(i=0;i<DIM(graph_col);i++)
3109 im->graph_col[i]=graph_col[i];
3111 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3112 {
3113 char *windir;
3114 char rrd_win_default_font[1000];
3115 windir = getenv("windir");
3116 /* %windir% is something like D:\windows or C:\winnt */
3117 if (windir != NULL) {
3118 strncpy(rrd_win_default_font,windir,500);
3119 rrd_win_default_font[500] = '\0';
3120 strcat(rrd_win_default_font,"\\fonts\\");
3121 strcat(rrd_win_default_font,RRD_DEFAULT_FONT);
3122 for(i=0;i<DIM(text_prop);i++){
3123 strncpy(text_prop[i].font,rrd_win_default_font,sizeof(text_prop[i].font)-1);
3124 text_prop[i].font[sizeof(text_prop[i].font)-1] = '\0';
3125 }
3126 }
3127 }
3128 #endif
3129 {
3130 char *deffont;
3131 deffont = getenv("RRD_DEFAULT_FONT");
3132 if (deffont != NULL) {
3133 for(i=0;i<DIM(text_prop);i++){
3134 strncpy(text_prop[i].font,deffont,sizeof(text_prop[i].font)-1);
3135 text_prop[i].font[sizeof(text_prop[i].font)-1] = '\0';
3136 }
3137 }
3138 }
3139 for(i=0;i<DIM(text_prop);i++){
3140 im->text_prop[i].size = text_prop[i].size;
3141 strcpy(im->text_prop[i].font,text_prop[i].font);
3142 }
3143 }
3145 void
3146 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
3147 {
3148 int stroff;
3149 char *parsetime_error = NULL;
3150 char scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
3151 time_t start_tmp=0,end_tmp=0;
3152 long long_tmp;
3153 struct rrd_time_value start_tv, end_tv;
3154 gfx_color_t color;
3155 optind = 0; opterr = 0; /* initialize getopt */
3157 parsetime("end-24h", &start_tv);
3158 parsetime("now", &end_tv);
3160 /* defines for long options without a short equivalent. should be bytes,
3161 and may not collide with (the ASCII value of) short options */
3162 #define LONGOPT_UNITS_SI 255
3164 while (1){
3165 static struct option long_options[] =
3166 {
3167 {"start", required_argument, 0, 's'},
3168 {"end", required_argument, 0, 'e'},
3169 {"x-grid", required_argument, 0, 'x'},
3170 {"y-grid", required_argument, 0, 'y'},
3171 {"vertical-label",required_argument,0,'v'},
3172 {"width", required_argument, 0, 'w'},
3173 {"height", required_argument, 0, 'h'},
3174 {"interlaced", no_argument, 0, 'i'},
3175 {"upper-limit",required_argument, 0, 'u'},
3176 {"lower-limit",required_argument, 0, 'l'},
3177 {"rigid", no_argument, 0, 'r'},
3178 {"base", required_argument, 0, 'b'},
3179 {"logarithmic",no_argument, 0, 'o'},
3180 {"color", required_argument, 0, 'c'},
3181 {"font", required_argument, 0, 'n'},
3182 {"title", required_argument, 0, 't'},
3183 {"imginfo", required_argument, 0, 'f'},
3184 {"imgformat", required_argument, 0, 'a'},
3185 {"lazy", no_argument, 0, 'z'},
3186 {"zoom", required_argument, 0, 'm'},
3187 {"no-legend", no_argument, 0, 'g'},
3188 {"force-rules-legend",no_argument,0, 'F'},
3189 {"only-graph", no_argument, 0, 'j'},
3190 {"alt-y-grid", no_argument, 0, 'Y'},
3191 {"no-minor", no_argument, 0, 'I'},
3192 {"slope-mode", no_argument, 0, 'E'},
3193 {"alt-autoscale", no_argument, 0, 'A'},
3194 {"alt-autoscale-max", no_argument, 0, 'M'},
3195 {"no-gridfit", no_argument, 0, 'N'},
3196 {"units-exponent",required_argument, 0, 'X'},
3197 {"units-length",required_argument, 0, 'L'},
3198 {"units", required_argument, 0, LONGOPT_UNITS_SI },
3199 {"step", required_argument, 0, 'S'},
3200 {"tabwidth", required_argument, 0, 'T'},
3201 {"font-render-mode", required_argument, 0, 'R'},
3202 {"font-smoothing-threshold", required_argument, 0, 'B'},
3203 {"watermark", required_argument, 0, 'W'},
3204 {"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 */
3205 {0,0,0,0}};
3206 int option_index = 0;
3207 int opt;
3208 int col_start,col_end;
3210 opt = getopt_long(argc, argv,
3211 "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:I:zgjFYAMEX:L:S:T:NR:B:W:",
3212 long_options, &option_index);
3214 if (opt == EOF)
3215 break;
3217 switch(opt) {
3218 case 'I':
3219 im->extra_flags |= NOMINOR;
3220 break;
3221 case 'Y':
3222 im->extra_flags |= ALTYGRID;
3223 break;
3224 case 'A':
3225 im->extra_flags |= ALTAUTOSCALE;
3226 break;
3227 case 'M':
3228 im->extra_flags |= ALTAUTOSCALE_MAX;
3229 break;
3230 case 'j':
3231 im->extra_flags |= ONLY_GRAPH;
3232 break;
3233 case 'g':
3234 im->extra_flags |= NOLEGEND;
3235 break;
3236 case 'F':
3237 im->extra_flags |= FORCE_RULES_LEGEND;
3238 break;
3239 case LONGOPT_UNITS_SI:
3240 if(im->extra_flags & FORCE_UNITS) {
3241 rrd_set_error("--units can only be used once!");
3242 return;
3243 }
3244 if(strcmp(optarg,"si")==0)
3245 im->extra_flags |= FORCE_UNITS_SI;
3246 else {
3247 rrd_set_error("invalid argument for --units: %s", optarg );
3248 return;
3249 }
3250 break;
3251 case 'X':
3252 im->unitsexponent = atoi(optarg);
3253 break;
3254 case 'L':
3255 im->unitslength = atoi(optarg);
3256 break;
3257 case 'T':
3258 im->tabwidth = atof(optarg);
3259 break;
3260 case 'S':
3261 im->step = atoi(optarg);
3262 break;
3263 case 'N':
3264 im->gridfit = 0;
3265 break;
3266 case 's':
3267 if ((parsetime_error = parsetime(optarg, &start_tv))) {
3268 rrd_set_error( "start time: %s", parsetime_error );
3269 return;
3270 }
3271 break;
3272 case 'e':
3273 if ((parsetime_error = parsetime(optarg, &end_tv))) {
3274 rrd_set_error( "end time: %s", parsetime_error );
3275 return;
3276 }
3277 break;
3278 case 'x':
3279 if(strcmp(optarg,"none") == 0){
3280 im->draw_x_grid=0;
3281 break;
3282 };
3284 if(sscanf(optarg,
3285 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3286 scan_gtm,
3287 &im->xlab_user.gridst,
3288 scan_mtm,
3289 &im->xlab_user.mgridst,
3290 scan_ltm,
3291 &im->xlab_user.labst,
3292 &im->xlab_user.precis,
3293 &stroff) == 7 && stroff != 0){
3294 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
3295 im->xlab_form[sizeof(im->xlab_form)-1] = '\0';
3296 if((int)(im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
3297 rrd_set_error("unknown keyword %s",scan_gtm);
3298 return;
3299 } else if ((int)(im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
3300 rrd_set_error("unknown keyword %s",scan_mtm);
3301 return;
3302 } else if ((int)(im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
3303 rrd_set_error("unknown keyword %s",scan_ltm);
3304 return;
3305 }
3306 im->xlab_user.minsec = 1;
3307 im->xlab_user.stst = im->xlab_form;
3308 } else {
3309 rrd_set_error("invalid x-grid format");
3310 return;
3311 }
3312 break;
3313 case 'y':
3315 if(strcmp(optarg,"none") == 0){
3316 im->draw_y_grid=0;
3317 break;
3318 };
3320 if(sscanf(optarg,
3321 "%lf:%d",
3322 &im->ygridstep,
3323 &im->ylabfact) == 2) {
3324 if(im->ygridstep<=0){
3325 rrd_set_error("grid step must be > 0");
3326 return;
3327 } else if (im->ylabfact < 1){
3328 rrd_set_error("label factor must be > 0");
3329 return;
3330 }
3331 } else {
3332 rrd_set_error("invalid y-grid format");
3333 return;
3334 }
3335 break;
3336 case 'v':
3337 strncpy(im->ylegend,optarg,150);
3338 im->ylegend[150]='\0';
3339 break;
3340 case 'u':
3341 im->maxval = atof(optarg);
3342 break;
3343 case 'l':
3344 im->minval = atof(optarg);
3345 break;
3346 case 'b':
3347 im->base = atol(optarg);
3348 if(im->base != 1024 && im->base != 1000 ){
3349 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
3350 return;
3351 }
3352 break;
3353 case 'w':
3354 long_tmp = atol(optarg);
3355 if (long_tmp < 10) {
3356 rrd_set_error("width below 10 pixels");
3357 return;
3358 }
3359 im->xsize = long_tmp;
3360 break;
3361 case 'h':
3362 long_tmp = atol(optarg);
3363 if (long_tmp < 10) {
3364 rrd_set_error("height below 10 pixels");
3365 return;
3366 }
3367 im->ysize = long_tmp;
3368 break;
3369 case 'i':
3370 im->canvas->interlaced = 1;
3371 break;
3372 case 'r':
3373 im->rigid = 1;
3374 break;
3375 case 'f':
3376 im->imginfo = optarg;
3377 break;
3378 case 'a':
3379 if((int)(im->canvas->imgformat = if_conv(optarg)) == -1) {
3380 rrd_set_error("unsupported graphics format '%s'",optarg);
3381 return;
3382 }
3383 break;
3384 case 'z':
3385 im->lazy = 1;
3386 break;
3387 case 'E':
3388 im->slopemode = 1;
3389 break;
3391 case 'o':
3392 im->logarithmic = 1;
3393 break;
3394 case 'c':
3395 if(sscanf(optarg,
3396 "%10[A-Z]#%n%8lx%n",
3397 col_nam,&col_start,&color,&col_end) == 2){
3398 int ci;
3399 int col_len = col_end - col_start;
3400 switch (col_len){
3401 case 3:
3402 color = (
3403 ((color & 0xF00) * 0x110000) |
3404 ((color & 0x0F0) * 0x011000) |
3405 ((color & 0x00F) * 0x001100) |
3406 0x000000FF
3407 );
3408 break;
3409 case 4:
3410 color = (
3411 ((color & 0xF000) * 0x11000) |
3412 ((color & 0x0F00) * 0x01100) |
3413 ((color & 0x00F0) * 0x00110) |
3414 ((color & 0x000F) * 0x00011)
3415 );
3416 break;
3417 case 6:
3418 color = (color << 8) + 0xff /* shift left by 8 */;
3419 break;
3420 case 8:
3421 break;
3422 default:
3423 rrd_set_error("the color format is #RRGGBB[AA]");
3424 return;
3425 }
3426 if((ci=grc_conv(col_nam)) != -1){
3427 im->graph_col[ci]=color;
3428 } else {
3429 rrd_set_error("invalid color name '%s'",col_nam);
3430 return;
3431 }
3432 } else {
3433 rrd_set_error("invalid color def format");
3434 return;
3435 }
3436 break;
3437 case 'n':{
3438 char prop[15];
3439 double size = 1;
3440 char font[1024] = "";
3442 if(sscanf(optarg,
3443 "%10[A-Z]:%lf:%1000s",
3444 prop,&size,font) >= 2){
3445 int sindex,propidx;
3446 if((sindex=text_prop_conv(prop)) != -1){
3447 for (propidx=sindex;propidx<TEXT_PROP_LAST;propidx++){
3448 if (size > 0){
3449 im->text_prop[propidx].size=size;
3450 }
3451 if (strlen(font) > 0){
3452 strcpy(im->text_prop[propidx].font,font);
3453 }
3454 if (propidx==sindex && sindex != 0) break;
3455 }
3456 } else {
3457 rrd_set_error("invalid fonttag '%s'",prop);
3458 return;
3459 }
3460 } else {
3461 rrd_set_error("invalid text property format");
3462 return;
3463 }
3464 break;
3465 }
3466 case 'm':
3467 im->canvas->zoom = atof(optarg);
3468 if (im->canvas->zoom <= 0.0) {
3469 rrd_set_error("zoom factor must be > 0");
3470 return;
3471 }
3472 break;
3473 case 't':
3474 strncpy(im->title,optarg,150);
3475 im->title[150]='\0';
3476 break;
3478 case 'R':
3479 if ( strcmp( optarg, "normal" ) == 0 )
3480 im->canvas->aa_type = AA_NORMAL;
3481 else if ( strcmp( optarg, "light" ) == 0 )
3482 im->canvas->aa_type = AA_LIGHT;
3483 else if ( strcmp( optarg, "mono" ) == 0 )
3484 im->canvas->aa_type = AA_NONE;
3485 else
3486 {
3487 rrd_set_error("unknown font-render-mode '%s'", optarg );
3488 return;
3489 }
3490 break;
3492 case 'B':
3493 im->canvas->font_aa_threshold = atof(optarg);
3494 break;
3496 case 'W':
3497 strncpy(im->watermark,optarg,100);
3498 im->watermark[99]='\0';
3499 break;
3501 case '?':
3502 if (optopt != 0)
3503 rrd_set_error("unknown option '%c'", optopt);
3504 else
3505 rrd_set_error("unknown option '%s'",argv[optind-1]);
3506 return;
3507 }
3508 }
3510 if (optind >= argc) {
3511 rrd_set_error("missing filename");
3512 return;
3513 }
3515 if (im->logarithmic == 1 && im->minval <= 0){
3516 rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");
3517 return;
3518 }
3520 if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
3521 /* error string is set in parsetime.c */
3522 return;
3523 }
3525 if (start_tmp < 3600*24*365*10){
3526 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
3527 return;
3528 }
3530 if (end_tmp < start_tmp) {
3531 rrd_set_error("start (%ld) should be less than end (%ld)",
3532 start_tmp, end_tmp);
3533 return;
3534 }
3536 im->start = start_tmp;
3537 im->end = end_tmp;
3538 im->step = max((long)im->step, (im->end-im->start)/im->xsize);
3539 }
3541 int
3542 rrd_graph_check_vname(image_desc_t *im, char *varname, char *err)
3543 {
3544 if ((im->gdes[im->gdes_c-1].vidx=find_var(im,varname))==-1) {
3545 rrd_set_error("Unknown variable '%s' in %s",varname,err);
3546 return -1;
3547 }
3548 return 0;
3549 }
3550 int
3551 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
3552 {
3553 char *color;
3554 graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
3556 color=strstr(var,"#");
3557 if (color==NULL) {
3558 if (optional==0) {
3559 rrd_set_error("Found no color in %s",err);
3560 return 0;
3561 }
3562 return 0;
3563 } else {
3564 int n=0;
3565 char *rest;
3566 gfx_color_t col;
3568 rest=strstr(color,":");
3569 if (rest!=NULL)
3570 n=rest-color;
3571 else
3572 n=strlen(color);
3574 switch (n) {
3575 case 7:
3576 sscanf(color,"#%6lx%n",&col,&n);
3577 col = (col << 8) + 0xff /* shift left by 8 */;
3578 if (n!=7) rrd_set_error("Color problem in %s",err);
3579 break;
3580 case 9:
3581 sscanf(color,"#%8lx%n",&col,&n);
3582 if (n==9) break;
3583 default:
3584 rrd_set_error("Color problem in %s",err);
3585 }
3586 if (rrd_test_error()) return 0;
3587 gdp->col = col;
3588 return n;
3589 }
3590 }
3593 int bad_format(char *fmt) {
3594 char *ptr;
3595 int n=0;
3596 ptr = fmt;
3597 while (*ptr != '\0')
3598 if (*ptr++ == '%') {
3600 /* line cannot end with percent char */
3601 if (*ptr == '\0') return 1;
3603 /* '%s', '%S' and '%%' are allowed */
3604 if (*ptr == 's' || *ptr == 'S' || *ptr == '%') ptr++;
3606 /* %c is allowed (but use only with vdef!) */
3607 else if (*ptr == 'c') {
3608 ptr++;
3609 n=1;
3610 }
3612 /* or else '% 6.2lf' and such are allowed */
3613 else {
3614 /* optional padding character */
3615 if (*ptr == ' ' || *ptr == '+' || *ptr == '-') ptr++;
3617 /* This should take care of 'm.n' with all three optional */
3618 while (*ptr >= '0' && *ptr <= '9') ptr++;
3619 if (*ptr == '.') ptr++;
3620 while (*ptr >= '0' && *ptr <= '9') ptr++;
3622 /* Either 'le', 'lf' or 'lg' must follow here */
3623 if (*ptr++ != 'l') return 1;
3624 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g') ptr++;
3625 else return 1;
3626 n++;
3627 }
3628 }
3630 return (n!=1);
3631 }
3634 int
3635 vdef_parse(gdes,str)
3636 struct graph_desc_t *gdes;
3637 const char *const str;
3638 {
3639 /* A VDEF currently is either "func" or "param,func"
3640 * so the parsing is rather simple. Change if needed.
3641 */
3642 double param;
3643 char func[30];
3644 int n;
3646 n=0;
3647 sscanf(str,"%le,%29[A-Z]%n",¶m,func,&n);
3648 if (n== (int)strlen(str)) { /* matched */
3649 ;
3650 } else {
3651 n=0;
3652 sscanf(str,"%29[A-Z]%n",func,&n);
3653 if (n== (int)strlen(str)) { /* matched */
3654 param=DNAN;
3655 } else {
3656 rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3657 ,str
3658 ,gdes->vname
3659 );
3660 return -1;
3661 }
3662 }
3663 if (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3664 else if (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3665 else if (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3666 else if (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3667 else if (!strcmp("TOTAL", func)) gdes->vf.op = VDEF_TOTAL;
3668 else if (!strcmp("FIRST", func)) gdes->vf.op = VDEF_FIRST;
3669 else if (!strcmp("LAST", func)) gdes->vf.op = VDEF_LAST;
3670 else if (!strcmp("LSLSLOPE", func)) gdes->vf.op = VDEF_LSLSLOPE;
3671 else if (!strcmp("LSLINT", func)) gdes->vf.op = VDEF_LSLINT;
3672 else if (!strcmp("LSLCORREL",func)) gdes->vf.op = VDEF_LSLCORREL;
3673 else {
3674 rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3675 ,func
3676 ,gdes->vname
3677 );
3678 return -1;
3679 };
3681 switch (gdes->vf.op) {
3682 case VDEF_PERCENT:
3683 if (isnan(param)) { /* no parameter given */
3684 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3685 ,func
3686 ,gdes->vname
3687 );
3688 return -1;
3689 };
3690 if (param>=0.0 && param<=100.0) {
3691 gdes->vf.param = param;
3692 gdes->vf.val = DNAN; /* undefined */
3693 gdes->vf.when = 0; /* undefined */
3694 } else {
3695 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3696 ,param
3697 ,gdes->vname
3698 );
3699 return -1;
3700 };
3701 break;
3702 case VDEF_MAXIMUM:
3703 case VDEF_AVERAGE:
3704 case VDEF_MINIMUM:
3705 case VDEF_TOTAL:
3706 case VDEF_FIRST:
3707 case VDEF_LAST:
3708 case VDEF_LSLSLOPE:
3709 case VDEF_LSLINT:
3710 case VDEF_LSLCORREL:
3711 if (isnan(param)) {
3712 gdes->vf.param = DNAN;
3713 gdes->vf.val = DNAN;
3714 gdes->vf.when = 0;
3715 } else {
3716 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3717 ,func
3718 ,gdes->vname
3719 );
3720 return -1;
3721 };
3722 break;
3723 };
3724 return 0;
3725 }
3728 int
3729 vdef_calc(im,gdi)
3730 image_desc_t *im;
3731 int gdi;
3732 {
3733 graph_desc_t *src,*dst;
3734 rrd_value_t *data;
3735 long step,steps;
3737 dst = &im->gdes[gdi];
3738 src = &im->gdes[dst->vidx];
3739 data = src->data + src->ds;
3740 steps = (src->end - src->start) / src->step;
3742 #if 0
3743 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3744 ,src->start
3745 ,src->end
3746 ,steps
3747 );
3748 #endif
3750 switch (dst->vf.op) {
3751 case VDEF_PERCENT: {
3752 rrd_value_t * array;
3753 int field;
3756 if ((array = malloc(steps*sizeof(double)))==NULL) {
3757 rrd_set_error("malloc VDEV_PERCENT");
3758 return -1;
3759 }
3760 for (step=0;step < steps; step++) {
3761 array[step]=data[step*src->ds_cnt];
3762 }
3763 qsort(array,step,sizeof(double),vdef_percent_compar);
3765 field = (steps-1)*dst->vf.param/100;
3766 dst->vf.val = array[field];
3767 dst->vf.when = 0; /* no time component */
3768 free(array);
3769 #if 0
3770 for(step=0;step<steps;step++)
3771 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3772 #endif
3773 }
3774 break;
3775 case VDEF_MAXIMUM:
3776 step=0;
3777 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3778 if (step == steps) {
3779 dst->vf.val = DNAN;
3780 dst->vf.when = 0;
3781 } else {
3782 dst->vf.val = data[step*src->ds_cnt];
3783 dst->vf.when = src->start + (step+1)*src->step;
3784 }
3785 while (step != steps) {
3786 if (finite(data[step*src->ds_cnt])) {
3787 if (data[step*src->ds_cnt] > dst->vf.val) {
3788 dst->vf.val = data[step*src->ds_cnt];
3789 dst->vf.when = src->start + (step+1)*src->step;
3790 }
3791 }
3792 step++;
3793 }
3794 break;
3795 case VDEF_TOTAL:
3796 case VDEF_AVERAGE: {
3797 int cnt=0;
3798 double sum=0.0;
3799 for (step=0;step<steps;step++) {
3800 if (finite(data[step*src->ds_cnt])) {
3801 sum += data[step*src->ds_cnt];
3802 cnt ++;
3803 };
3804 }
3805 if (cnt) {
3806 if (dst->vf.op == VDEF_TOTAL) {
3807 dst->vf.val = sum*src->step;
3808 dst->vf.when = 0; /* no time component */
3809 } else {
3810 dst->vf.val = sum/cnt;
3811 dst->vf.when = 0; /* no time component */
3812 };
3813 } else {
3814 dst->vf.val = DNAN;
3815 dst->vf.when = 0;
3816 }
3817 }
3818 break;
3819 case VDEF_MINIMUM:
3820 step=0;
3821 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3822 if (step == steps) {
3823 dst->vf.val = DNAN;
3824 dst->vf.when = 0;
3825 } else {
3826 dst->vf.val = data[step*src->ds_cnt];
3827 dst->vf.when = src->start + (step+1)*src->step;
3828 }
3829 while (step != steps) {
3830 if (finite(data[step*src->ds_cnt])) {
3831 if (data[step*src->ds_cnt] < dst->vf.val) {
3832 dst->vf.val = data[step*src->ds_cnt];
3833 dst->vf.when = src->start + (step+1)*src->step;
3834 }
3835 }
3836 step++;
3837 }
3838 break;
3839 case VDEF_FIRST:
3840 /* The time value returned here is one step before the
3841 * actual time value. This is the start of the first
3842 * non-NaN interval.
3843 */
3844 step=0;
3845 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3846 if (step == steps) { /* all entries were NaN */
3847 dst->vf.val = DNAN;
3848 dst->vf.when = 0;
3849 } else {
3850 dst->vf.val = data[step*src->ds_cnt];
3851 dst->vf.when = src->start + step*src->step;
3852 }
3853 break;
3854 case VDEF_LAST:
3855 /* The time value returned here is the
3856 * actual time value. This is the end of the last
3857 * non-NaN interval.
3858 */
3859 step=steps-1;
3860 while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3861 if (step < 0) { /* all entries were NaN */
3862 dst->vf.val = DNAN;
3863 dst->vf.when = 0;
3864 } else {
3865 dst->vf.val = data[step*src->ds_cnt];
3866 dst->vf.when = src->start + (step+1)*src->step;
3867 }
3868 break;
3869 case VDEF_LSLSLOPE:
3870 case VDEF_LSLINT:
3871 case VDEF_LSLCORREL:{
3872 /* Bestfit line by linear least squares method */
3874 int cnt=0;
3875 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl ;
3876 SUMx = 0; SUMy = 0; SUMxy = 0; SUMxx = 0; SUMyy = 0;
3878 for (step=0;step<steps;step++) {
3879 if (finite(data[step*src->ds_cnt])) {
3880 cnt++;
3881 SUMx += step;
3882 SUMxx += step * step;
3883 SUMxy += step * data[step*src->ds_cnt];
3884 SUMy += data[step*src->ds_cnt];
3885 SUMyy += data[step*src->ds_cnt]*data[step*src->ds_cnt];
3886 };
3887 }
3889 slope = ( SUMx*SUMy - cnt*SUMxy ) / ( SUMx*SUMx - cnt*SUMxx );
3890 y_intercept = ( SUMy - slope*SUMx ) / cnt;
3891 correl = (SUMxy - (SUMx*SUMy)/cnt) / sqrt((SUMxx - (SUMx*SUMx)/cnt)*(SUMyy - (SUMy*SUMy)/cnt));
3893 if (cnt) {
3894 if (dst->vf.op == VDEF_LSLSLOPE) {
3895 dst->vf.val = slope;
3896 dst->vf.when = 0;
3897 } else if (dst->vf.op == VDEF_LSLINT) {
3898 dst->vf.val = y_intercept;
3899 dst->vf.when = 0;
3900 } else if (dst->vf.op == VDEF_LSLCORREL) {
3901 dst->vf.val = correl;
3902 dst->vf.when = 0;
3903 };
3905 } else {
3906 dst->vf.val = DNAN;
3907 dst->vf.when = 0;
3908 }
3909 }
3910 break;
3911 }
3912 return 0;
3913 }
3915 /* NaN < -INF < finite_values < INF */
3916 int
3917 vdef_percent_compar(a,b)
3918 const void *a,*b;
3919 {
3920 /* Equality is not returned; this doesn't hurt except
3921 * (maybe) for a little performance.
3922 */
3924 /* First catch NaN values. They are smallest */
3925 if (isnan( *(double *)a )) return -1;
3926 if (isnan( *(double *)b )) return 1;
3928 /* NaN doesn't reach this part so INF and -INF are extremes.
3929 * The sign from isinf() is compatible with the sign we return
3930 */
3931 if (isinf( *(double *)a )) return isinf( *(double *)a );
3932 if (isinf( *(double *)b )) return isinf( *(double *)b );
3934 /* If we reach this, both values must be finite */
3935 if ( *(double *)a < *(double *)b ) return -1; else return 1;
3936 }