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