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