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