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 struct tm tmvdef;
1231 int graphelement = 0;
1232 long vidx;
1233 int max_ii;
1234 double magfact = -1;
1235 char *si_symb = "";
1236 char *percent_s;
1237 int prlines = 1;
1238 /* wow initializing tmvdef is quite a task :-) */
1239 time_t now = time(NULL);
1240 localtime_r(&now,&tmvdef);
1241 if (im->imginfo) prlines++;
1242 for(i=0;i<im->gdes_c;i++){
1243 switch(im->gdes[i].gf){
1244 case GF_PRINT:
1245 prlines++;
1246 if(((*prdata) = rrd_realloc((*prdata),prlines*sizeof(char *)))==NULL){
1247 rrd_set_error("realloc prdata");
1248 return 0;
1249 }
1250 case GF_GPRINT:
1251 /* PRINT and GPRINT can now print VDEF generated values.
1252 * There's no need to do any calculations on them as these
1253 * calculations were already made.
1254 */
1255 vidx = im->gdes[i].vidx;
1256 if (im->gdes[vidx].gf==GF_VDEF) { /* simply use vals */
1257 printval = im->gdes[vidx].vf.val;
1258 localtime_r(&im->gdes[vidx].vf.when,&tmvdef);
1259 } else { /* need to calculate max,min,avg etcetera */
1260 max_ii =((im->gdes[vidx].end
1261 - im->gdes[vidx].start)
1262 / im->gdes[vidx].step
1263 * im->gdes[vidx].ds_cnt);
1264 printval = DNAN;
1265 validsteps = 0;
1266 for( ii=im->gdes[vidx].ds;
1267 ii < max_ii;
1268 ii+=im->gdes[vidx].ds_cnt){
1269 if (! finite(im->gdes[vidx].data[ii]))
1270 continue;
1271 if (isnan(printval)){
1272 printval = im->gdes[vidx].data[ii];
1273 validsteps++;
1274 continue;
1275 }
1277 switch (im->gdes[i].cf){
1278 case CF_HWPREDICT:
1279 case CF_DEVPREDICT:
1280 case CF_DEVSEASONAL:
1281 case CF_SEASONAL:
1282 case CF_AVERAGE:
1283 validsteps++;
1284 printval += im->gdes[vidx].data[ii];
1285 break;
1286 case CF_MINIMUM:
1287 printval = min( printval, im->gdes[vidx].data[ii]);
1288 break;
1289 case CF_FAILURES:
1290 case CF_MAXIMUM:
1291 printval = max( printval, im->gdes[vidx].data[ii]);
1292 break;
1293 case CF_LAST:
1294 printval = im->gdes[vidx].data[ii];
1295 }
1296 }
1297 if (im->gdes[i].cf==CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1298 if (validsteps > 1) {
1299 printval = (printval / validsteps);
1300 }
1301 }
1302 } /* prepare printval */
1304 if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) {
1305 /* Magfact is set to -1 upon entry to print_calc. If it
1306 * is still less than 0, then we need to run auto_scale.
1307 * Otherwise, put the value into the correct units. If
1308 * the value is 0, then do not set the symbol or magnification
1309 * so next the calculation will be performed again. */
1310 if (magfact < 0.0) {
1311 auto_scale(im,&printval,&si_symb,&magfact);
1312 if (printval == 0.0)
1313 magfact = -1.0;
1314 } else {
1315 printval /= magfact;
1316 }
1317 *(++percent_s) = 's';
1318 } else if (strstr(im->gdes[i].format,"%s") != NULL) {
1319 auto_scale(im,&printval,&si_symb,&magfact);
1320 }
1322 if (im->gdes[i].gf == GF_PRINT){
1323 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1324 (*prdata)[prlines-1] = NULL;
1325 if (im->gdes[i].strftm){
1326 strftime((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,&tmvdef);
1327 } else {
1328 if (bad_format(im->gdes[i].format)) {
1329 rrd_set_error("bad format for PRINT in '%s'", im->gdes[i].format);
1330 return -1;
1331 }
1333 #ifdef HAVE_SNPRINTF
1334 snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
1335 #else
1336 sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
1337 #endif
1338 }
1339 } else {
1340 /* GF_GPRINT */
1342 if (im->gdes[i].strftm){
1343 strftime(im->gdes[i].legend,FMT_LEG_LEN,im->gdes[i].format,&tmvdef);
1344 } else {
1345 if (bad_format(im->gdes[i].format)) {
1346 rrd_set_error("bad format for GPRINT in '%s'", im->gdes[i].format);
1347 return -1;
1348 }
1349 #ifdef HAVE_SNPRINTF
1350 snprintf(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
1351 #else
1352 sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
1353 #endif
1354 }
1355 graphelement = 1;
1356 }
1357 break;
1358 case GF_LINE:
1359 case GF_AREA:
1360 case GF_TICK:
1361 case GF_HRULE:
1362 case GF_VRULE:
1363 graphelement = 1;
1364 break;
1365 case GF_COMMENT:
1366 case GF_DEF:
1367 case GF_CDEF:
1368 case GF_VDEF:
1369 #ifdef WITH_PIECHART
1370 case GF_PART:
1371 #endif
1372 case GF_SHIFT:
1373 case GF_XPORT:
1374 break;
1375 case GF_STACK:
1376 rrd_set_error("STACK should already be turned into LINE or AREA here");
1377 return -1;
1378 break;
1379 }
1380 }
1381 return graphelement;
1382 }
1385 /* place legends with color spots */
1386 int
1387 leg_place(image_desc_t *im)
1388 {
1389 /* graph labels */
1390 int interleg = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1391 int border = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1392 int fill=0, fill_last;
1393 int leg_c = 0;
1394 int leg_x = border, leg_y = im->yimg;
1395 int leg_y_prev = im->yimg;
1396 int leg_cc;
1397 int glue = 0;
1398 int i,ii, mark = 0;
1399 char prt_fctn; /*special printfunctions */
1400 int *legspace;
1402 if( !(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH) ) {
1403 if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1404 rrd_set_error("malloc for legspace");
1405 return -1;
1406 }
1408 for(i=0;i<im->gdes_c;i++){
1409 fill_last = fill;
1411 /* hid legends for rules which are not displayed */
1413 if(!(im->extra_flags & FORCE_RULES_LEGEND)) {
1414 if (im->gdes[i].gf == GF_HRULE &&
1415 (im->gdes[i].yrule < im->minval || im->gdes[i].yrule > im->maxval))
1416 im->gdes[i].legend[0] = '\0';
1418 if (im->gdes[i].gf == GF_VRULE &&
1419 (im->gdes[i].xrule < im->start || im->gdes[i].xrule > im->end))
1420 im->gdes[i].legend[0] = '\0';
1421 }
1423 leg_cc = strlen(im->gdes[i].legend);
1425 /* is there a controle code ant the end of the legend string ? */
1426 /* and it is not a tab \\t */
1427 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\' && im->gdes[i].legend[leg_cc-1] != 't') {
1428 prt_fctn = im->gdes[i].legend[leg_cc-1];
1429 leg_cc -= 2;
1430 im->gdes[i].legend[leg_cc] = '\0';
1431 } else {
1432 prt_fctn = '\0';
1433 }
1434 /* remove exess space */
1435 while (prt_fctn=='g' &&
1436 leg_cc > 0 &&
1437 im->gdes[i].legend[leg_cc-1]==' '){
1438 leg_cc--;
1439 im->gdes[i].legend[leg_cc]='\0';
1440 }
1441 if (leg_cc != 0 ){
1442 legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1444 if (fill > 0){
1445 /* no interleg space if string ends in \g */
1446 fill += legspace[i];
1447 }
1448 fill += gfx_get_text_width(im->canvas, fill+border,
1449 im->text_prop[TEXT_PROP_LEGEND].font,
1450 im->text_prop[TEXT_PROP_LEGEND].size,
1451 im->tabwidth,
1452 im->gdes[i].legend, 0);
1453 leg_c++;
1454 } else {
1455 legspace[i]=0;
1456 }
1457 /* who said there was a special tag ... ?*/
1458 if (prt_fctn=='g') {
1459 prt_fctn = '\0';
1460 }
1461 if (prt_fctn == '\0') {
1462 if (i == im->gdes_c -1 ) prt_fctn ='l';
1464 /* is it time to place the legends ? */
1465 if (fill > im->ximg - 2*border){
1466 if (leg_c > 1) {
1467 /* go back one */
1468 i--;
1469 fill = fill_last;
1470 leg_c--;
1471 prt_fctn = 'j';
1472 } else {
1473 prt_fctn = 'l';
1474 }
1476 }
1477 }
1480 if (prt_fctn != '\0'){
1481 leg_x = border;
1482 if (leg_c >= 2 && prt_fctn == 'j') {
1483 glue = (im->ximg - fill - 2* border) / (leg_c-1);
1484 } else {
1485 glue = 0;
1486 }
1487 if (prt_fctn =='c') leg_x = (im->ximg - fill) / 2.0;
1488 if (prt_fctn =='r') leg_x = im->ximg - fill - border;
1490 for(ii=mark;ii<=i;ii++){
1491 if(im->gdes[ii].legend[0]=='\0')
1492 continue; /* skip empty legends */
1493 im->gdes[ii].leg_x = leg_x;
1494 im->gdes[ii].leg_y = leg_y;
1495 leg_x +=
1496 gfx_get_text_width(im->canvas, leg_x,
1497 im->text_prop[TEXT_PROP_LEGEND].font,
1498 im->text_prop[TEXT_PROP_LEGEND].size,
1499 im->tabwidth,
1500 im->gdes[ii].legend, 0)
1501 + legspace[ii]
1502 + glue;
1503 }
1504 leg_y_prev = leg_y;
1505 /* only add y space if there was text on the line */
1506 if (leg_x > border || prt_fctn == 's')
1507 leg_y += im->text_prop[TEXT_PROP_LEGEND].size*1.8;
1508 if (prt_fctn == 's')
1509 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1510 fill = 0;
1511 leg_c = 0;
1512 mark = ii;
1513 }
1514 }
1515 im->yimg = leg_y_prev;
1516 /* if we did place some legends we have to add vertical space */
1517 if (leg_y != im->yimg){
1518 im->yimg += im->text_prop[TEXT_PROP_LEGEND].size*1.8;
1519 }
1520 free(legspace);
1521 }
1522 return 0;
1523 }
1525 /* create a grid on the graph. it determines what to do
1526 from the values of xsize, start and end */
1528 /* the xaxis labels are determined from the number of seconds per pixel
1529 in the requested graph */
1533 int
1534 calc_horizontal_grid(image_desc_t *im)
1535 {
1536 double range;
1537 double scaledrange;
1538 int pixel,i;
1539 int gridind=0;
1540 int decimals, fractionals;
1542 im->ygrid_scale.labfact=2;
1543 range = im->maxval - im->minval;
1544 scaledrange = range / im->magfact;
1546 /* does the scale of this graph make it impossible to put lines
1547 on it? If so, give up. */
1548 if (isnan(scaledrange)) {
1549 return 0;
1550 }
1552 /* find grid spaceing */
1553 pixel=1;
1554 if(isnan(im->ygridstep)){
1555 if(im->extra_flags & ALTYGRID) {
1556 /* find the value with max number of digits. Get number of digits */
1557 decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))*im->viewfactor/im->magfact));
1558 if(decimals <= 0) /* everything is small. make place for zero */
1559 decimals = 1;
1561 im->ygrid_scale.gridstep = pow((double)10, floor(log10(range*im->viewfactor/im->magfact)))/im->viewfactor*im->magfact;
1563 if(im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1564 im->ygrid_scale.gridstep = 0.1;
1565 /* should have at least 5 lines but no more then 15 */
1566 if(range/im->ygrid_scale.gridstep < 5)
1567 im->ygrid_scale.gridstep /= 10;
1568 if(range/im->ygrid_scale.gridstep > 15)
1569 im->ygrid_scale.gridstep *= 10;
1570 if(range/im->ygrid_scale.gridstep > 5) {
1571 im->ygrid_scale.labfact = 1;
1572 if(range/im->ygrid_scale.gridstep > 8)
1573 im->ygrid_scale.labfact = 2;
1574 }
1575 else {
1576 im->ygrid_scale.gridstep /= 5;
1577 im->ygrid_scale.labfact = 5;
1578 }
1579 fractionals = floor(log10(im->ygrid_scale.gridstep*(double)im->ygrid_scale.labfact*im->viewfactor/im->magfact));
1580 if(fractionals < 0) { /* small amplitude. */
1581 int len = decimals - fractionals + 1;
1582 if (im->unitslength < len+2) im->unitslength = len+2;
1583 sprintf(im->ygrid_scale.labfmt, "%%%d.%df%s", len, -fractionals,(im->symbol != ' ' ? " %c" : ""));
1584 } else {
1585 int len = decimals + 1;
1586 if (im->unitslength < len+2) im->unitslength = len+2;
1587 sprintf(im->ygrid_scale.labfmt, "%%%d.0f%s", len, ( im->symbol != ' ' ? " %c" : "" ));
1588 }
1589 }
1590 else {
1591 for(i=0;ylab[i].grid > 0;i++){
1592 pixel = im->ysize / (scaledrange / ylab[i].grid);
1593 gridind = i;
1594 if (pixel > 7)
1595 break;
1596 }
1598 for(i=0; i<4;i++) {
1599 if (pixel * ylab[gridind].lfac[i] >= 2.5 * im->text_prop[TEXT_PROP_AXIS].size) {
1600 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1601 break;
1602 }
1603 }
1605 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1606 }
1607 } else {
1608 im->ygrid_scale.gridstep = im->ygridstep;
1609 im->ygrid_scale.labfact = im->ylabfact;
1610 }
1611 return 1;
1612 }
1614 int draw_horizontal_grid(image_desc_t *im)
1615 {
1616 int i;
1617 double scaledstep;
1618 char graph_label[100];
1619 int nlabels=0;
1620 double X0=im->xorigin;
1621 double X1=im->xorigin+im->xsize;
1623 int sgrid = (int)( im->minval / im->ygrid_scale.gridstep - 1);
1624 int egrid = (int)( im->maxval / im->ygrid_scale.gridstep + 1);
1625 double MaxY;
1626 scaledstep = im->ygrid_scale.gridstep/(double)im->magfact*(double)im->viewfactor;
1627 MaxY = scaledstep*(double)egrid;
1628 for (i = sgrid; i <= egrid; i++){
1629 double Y0=ytr(im,im->ygrid_scale.gridstep*i);
1630 double YN=ytr(im,im->ygrid_scale.gridstep*(i+1));
1631 if ( Y0 >= im->yorigin-im->ysize
1632 && Y0 <= im->yorigin){
1633 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
1634 with the chosen settings. Add a label if required by settings, or if
1635 there is only one label so far and the next grid line is out of bounds. */
1636 if(i % im->ygrid_scale.labfact == 0 || ( nlabels==1 && (YN < im->yorigin-im->ysize || YN > im->yorigin) )){
1637 if (im->symbol == ' ') {
1638 if(im->extra_flags & ALTYGRID) {
1639 sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*(double)i);
1640 } else {
1641 if(MaxY < 10) {
1642 sprintf(graph_label,"%4.1f",scaledstep*(double)i);
1643 } else {
1644 sprintf(graph_label,"%4.0f",scaledstep*(double)i);
1645 }
1646 }
1647 }else {
1648 char sisym = ( i == 0 ? ' ' : im->symbol);
1649 if(im->extra_flags & ALTYGRID) {
1650 sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*(double)i,sisym);
1651 } else {
1652 if(MaxY < 10){
1653 sprintf(graph_label,"%4.1f %c",scaledstep*(double)i, sisym);
1654 } else {
1655 sprintf(graph_label,"%4.0f %c",scaledstep*(double)i, sisym);
1656 }
1657 }
1658 }
1659 nlabels++;
1661 gfx_new_text ( im->canvas,
1662 X0-im->text_prop[TEXT_PROP_AXIS].size, Y0,
1663 im->graph_col[GRC_FONT],
1664 im->text_prop[TEXT_PROP_AXIS].font,
1665 im->text_prop[TEXT_PROP_AXIS].size,
1666 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1667 graph_label );
1668 gfx_new_dashed_line ( im->canvas,
1669 X0-2,Y0,
1670 X1+2,Y0,
1671 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1672 im->grid_dash_on, im->grid_dash_off);
1674 } else if (!(im->extra_flags & NOMINOR)) {
1675 gfx_new_dashed_line ( im->canvas,
1676 X0-1,Y0,
1677 X1+1,Y0,
1678 GRIDWIDTH, im->graph_col[GRC_GRID],
1679 im->grid_dash_on, im->grid_dash_off);
1681 }
1682 }
1683 }
1684 return 1;
1685 }
1687 /* this is frexp for base 10 */
1688 double frexp10(double, double *);
1689 double frexp10(double x, double *e) {
1690 double mnt;
1691 int iexp;
1693 iexp = floor(log(fabs(x)) / log(10));
1694 mnt = x / pow(10.0, iexp);
1695 if(mnt >= 10.0) {
1696 iexp++;
1697 mnt = x / pow(10.0, iexp);
1698 }
1699 *e = iexp;
1700 return mnt;
1701 }
1703 /* logaritmic horizontal grid */
1704 int
1705 horizontal_log_grid(image_desc_t *im)
1706 {
1707 double yloglab[][10] = {
1708 {1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
1709 {1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
1710 {1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0},
1711 {1.0, 2.0, 4.0, 6.0, 8.0, 10., 0.0, 0.0, 0.0, 0.0},
1712 {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.}};
1714 int i, j, val_exp, min_exp;
1715 double nex; /* number of decades in data */
1716 double logscale; /* scale in logarithmic space */
1717 int exfrac = 1; /* decade spacing */
1718 int mid = -1; /* row in yloglab for major grid */
1719 double mspac; /* smallest major grid spacing (pixels) */
1720 int flab; /* first value in yloglab to use */
1721 double value, tmp;
1722 double X0,X1,Y0;
1723 char graph_label[100];
1725 nex = log10(im->maxval / im->minval);
1726 logscale = im->ysize / nex;
1728 /* major spacing for data with high dynamic range */
1729 while(logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
1730 if(exfrac == 1) exfrac = 3;
1731 else exfrac += 3;
1732 }
1734 /* major spacing for less dynamic data */
1735 do {
1736 /* search best row in yloglab */
1737 mid++;
1738 for(i = 0; yloglab[mid][i + 1] < 10.0; i++);
1739 mspac = logscale * log10(10.0 / yloglab[mid][i]);
1740 } while(mspac > 2 * im->text_prop[TEXT_PROP_LEGEND].size && mid < 5);
1741 if(mid) mid--;
1743 /* find first value in yloglab */
1744 for(flab = 0; frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
1745 if(yloglab[mid][flab] == 10.0) {
1746 tmp += 1.0;
1747 flab = 0;
1748 }
1749 val_exp = tmp;
1750 if(val_exp % exfrac) val_exp += abs(-val_exp % exfrac);
1752 X0=im->xorigin;
1753 X1=im->xorigin+im->xsize;
1755 /* draw grid */
1756 while(1) {
1757 value = yloglab[mid][flab] * pow(10.0, val_exp);
1759 Y0 = ytr(im, value);
1760 if(Y0 <= im->yorigin - im->ysize) break;
1762 /* major grid line */
1763 gfx_new_dashed_line ( im->canvas,
1764 X0-2,Y0,
1765 X1+2,Y0,
1766 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1767 im->grid_dash_on, im->grid_dash_off);
1769 /* label */
1770 if (im->extra_flags & FORCE_UNITS_SI) {
1771 int scale;
1772 double pvalue;
1773 char symbol;
1775 scale = floor(val_exp / 3.0);
1776 if( value >= 1.0 ) pvalue = pow(10.0, val_exp % 3);
1777 else pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
1778 pvalue *= yloglab[mid][flab];
1780 if ( ((scale+si_symbcenter) < (int)sizeof(si_symbol)) &&
1781 ((scale+si_symbcenter) >= 0) )
1782 symbol = si_symbol[scale+si_symbcenter];
1783 else
1784 symbol = '?';
1786 sprintf(graph_label,"%3.0f %c", pvalue, symbol);
1787 } else
1788 sprintf(graph_label,"%3.0e", value);
1789 gfx_new_text ( im->canvas,
1790 X0-im->text_prop[TEXT_PROP_AXIS].size, Y0,
1791 im->graph_col[GRC_FONT],
1792 im->text_prop[TEXT_PROP_AXIS].font,
1793 im->text_prop[TEXT_PROP_AXIS].size,
1794 im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1795 graph_label );
1797 /* minor grid */
1798 if(mid < 4 && exfrac == 1) {
1799 /* find first and last minor line behind current major line
1800 * i is the first line and j tha last */
1801 if(flab == 0) {
1802 min_exp = val_exp - 1;
1803 for(i = 1; yloglab[mid][i] < 10.0; i++);
1804 i = yloglab[mid][i - 1] + 1;
1805 j = 10;
1806 }
1807 else {
1808 min_exp = val_exp;
1809 i = yloglab[mid][flab - 1] + 1;
1810 j = yloglab[mid][flab];
1811 }
1813 /* draw minor lines below current major line */
1814 for(; i < j; i++) {
1816 value = i * pow(10.0, min_exp);
1817 if(value < im->minval) continue;
1819 Y0 = ytr(im, value);
1820 if(Y0 <= im->yorigin - im->ysize) break;
1822 /* draw lines */
1823 gfx_new_dashed_line ( im->canvas,
1824 X0-1,Y0,
1825 X1+1,Y0,
1826 GRIDWIDTH, im->graph_col[GRC_GRID],
1827 im->grid_dash_on, im->grid_dash_off);
1828 }
1829 }
1830 else if(exfrac > 1) {
1831 for(i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
1832 value = pow(10.0, i);
1833 if(value < im->minval) continue;
1835 Y0 = ytr(im, value);
1836 if(Y0 <= im->yorigin - im->ysize) break;
1838 /* draw lines */
1839 gfx_new_dashed_line ( im->canvas,
1840 X0-1,Y0,
1841 X1+1,Y0,
1842 GRIDWIDTH, im->graph_col[GRC_GRID],
1843 im->grid_dash_on, im->grid_dash_off);
1844 }
1845 }
1847 /* next decade */
1848 if(yloglab[mid][++flab] == 10.0) {
1849 flab = 0;
1850 val_exp += exfrac;
1851 }
1852 }
1854 /* draw minor lines after highest major line */
1855 if(mid < 4 && exfrac == 1) {
1856 /* find first and last minor line below current major line
1857 * i is the first line and j tha last */
1858 if(flab == 0) {
1859 min_exp = val_exp - 1;
1860 for(i = 1; yloglab[mid][i] < 10.0; i++);
1861 i = yloglab[mid][i - 1] + 1;
1862 j = 10;
1863 }
1864 else {
1865 min_exp = val_exp;
1866 i = yloglab[mid][flab - 1] + 1;
1867 j = yloglab[mid][flab];
1868 }
1870 /* draw minor lines below current major line */
1871 for(; i < j; i++) {
1873 value = i * pow(10.0, min_exp);
1874 if(value < im->minval) continue;
1876 Y0 = ytr(im, value);
1877 if(Y0 <= im->yorigin - im->ysize) break;
1879 /* draw lines */
1880 gfx_new_dashed_line ( im->canvas,
1881 X0-1,Y0,
1882 X1+1,Y0,
1883 GRIDWIDTH, im->graph_col[GRC_GRID],
1884 im->grid_dash_on, im->grid_dash_off);
1885 }
1886 }
1887 /* fancy minor gridlines */
1888 else if(exfrac > 1) {
1889 for(i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
1890 value = pow(10.0, i);
1891 if(value < im->minval) continue;
1893 Y0 = ytr(im, value);
1894 if(Y0 <= im->yorigin - im->ysize) break;
1896 /* draw lines */
1897 gfx_new_dashed_line ( im->canvas,
1898 X0-1,Y0,
1899 X1+1,Y0,
1900 GRIDWIDTH, im->graph_col[GRC_GRID],
1901 im->grid_dash_on, im->grid_dash_off);
1902 }
1903 }
1905 return 1;
1906 }
1909 void
1910 vertical_grid(
1911 image_desc_t *im )
1912 {
1913 int xlab_sel; /* which sort of label and grid ? */
1914 time_t ti, tilab, timajor;
1915 long factor;
1916 char graph_label[100];
1917 double X0,Y0,Y1; /* points for filled graph and more*/
1918 struct tm tm;
1920 /* the type of time grid is determined by finding
1921 the number of seconds per pixel in the graph */
1924 if(im->xlab_user.minsec == -1){
1925 factor=(im->end - im->start)/im->xsize;
1926 xlab_sel=0;
1927 while ( xlab[xlab_sel+1].minsec != -1
1928 && xlab[xlab_sel+1].minsec <= factor) { xlab_sel++; } /* pick the last one */
1929 while ( xlab[xlab_sel-1].minsec == xlab[xlab_sel].minsec
1930 && xlab[xlab_sel].length > (im->end - im->start)) { xlab_sel--; } /* go back to the smallest size */
1931 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
1932 im->xlab_user.gridst = xlab[xlab_sel].gridst;
1933 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
1934 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
1935 im->xlab_user.labtm = xlab[xlab_sel].labtm;
1936 im->xlab_user.labst = xlab[xlab_sel].labst;
1937 im->xlab_user.precis = xlab[xlab_sel].precis;
1938 im->xlab_user.stst = xlab[xlab_sel].stst;
1939 }
1941 /* y coords are the same for every line ... */
1942 Y0 = im->yorigin;
1943 Y1 = im->yorigin-im->ysize;
1946 /* paint the minor grid */
1947 if (!(im->extra_flags & NOMINOR))
1948 {
1949 for(ti = find_first_time(im->start,
1950 im->xlab_user.gridtm,
1951 im->xlab_user.gridst),
1952 timajor = find_first_time(im->start,
1953 im->xlab_user.mgridtm,
1954 im->xlab_user.mgridst);
1955 ti < im->end;
1956 ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
1957 ){
1958 /* are we inside the graph ? */
1959 if (ti < im->start || ti > im->end) continue;
1960 while (timajor < ti) {
1961 timajor = find_next_time(timajor,
1962 im->xlab_user.mgridtm, im->xlab_user.mgridst);
1963 }
1964 if (ti == timajor) continue; /* skip as falls on major grid line */
1965 X0 = xtr(im,ti);
1966 gfx_new_dashed_line(im->canvas,X0,Y0+1, X0,Y1-1,GRIDWIDTH,
1967 im->graph_col[GRC_GRID],
1968 im->grid_dash_on, im->grid_dash_off);
1970 }
1971 }
1973 /* paint the major grid */
1974 for(ti = find_first_time(im->start,
1975 im->xlab_user.mgridtm,
1976 im->xlab_user.mgridst);
1977 ti < im->end;
1978 ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
1979 ){
1980 /* are we inside the graph ? */
1981 if (ti < im->start || ti > im->end) continue;
1982 X0 = xtr(im,ti);
1983 gfx_new_dashed_line(im->canvas,X0,Y0+3, X0,Y1-2,MGRIDWIDTH,
1984 im->graph_col[GRC_MGRID],
1985 im->grid_dash_on, im->grid_dash_off);
1987 }
1988 /* paint the labels below the graph */
1989 for(ti = find_first_time(im->start - im->xlab_user.precis/2,
1990 im->xlab_user.labtm,
1991 im->xlab_user.labst);
1992 ti <= im->end - im->xlab_user.precis/2;
1993 ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
1994 ){
1995 tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
1996 /* are we inside the graph ? */
1997 if (tilab < im->start || tilab > im->end) continue;
1999 #if HAVE_STRFTIME
2000 localtime_r(&tilab, &tm);
2001 strftime(graph_label,99,im->xlab_user.stst, &tm);
2002 #else
2003 # error "your libc has no strftime I guess we'll abort the exercise here."
2004 #endif
2005 gfx_new_text ( im->canvas,
2006 xtr(im,tilab), Y0+im->text_prop[TEXT_PROP_AXIS].size*1.4+5,
2007 im->graph_col[GRC_FONT],
2008 im->text_prop[TEXT_PROP_AXIS].font,
2009 im->text_prop[TEXT_PROP_AXIS].size,
2010 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_BOTTOM,
2011 graph_label );
2013 }
2015 }
2018 void
2019 axis_paint(
2020 image_desc_t *im
2021 )
2022 {
2023 /* draw x and y axis */
2024 /* gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2025 im->xorigin+im->xsize,im->yorigin-im->ysize,
2026 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2028 gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2029 im->xorigin+im->xsize,im->yorigin-im->ysize,
2030 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2032 gfx_new_line ( im->canvas, im->xorigin-4,im->yorigin,
2033 im->xorigin+im->xsize+4,im->yorigin,
2034 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2036 gfx_new_line ( im->canvas, im->xorigin,im->yorigin+4,
2037 im->xorigin,im->yorigin-im->ysize-4,
2038 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2041 /* arrow for X and Y axis direction */
2042 gfx_new_area ( im->canvas,
2043 im->xorigin+im->xsize+2, im->yorigin-2,
2044 im->xorigin+im->xsize+2, im->yorigin+3,
2045 im->xorigin+im->xsize+7, im->yorigin+0.5, /* LINEOFFSET */
2046 im->graph_col[GRC_ARROW]);
2048 gfx_new_area ( im->canvas,
2049 im->xorigin-2, im->yorigin-im->ysize-2,
2050 im->xorigin+3, im->yorigin-im->ysize-2,
2051 im->xorigin+0.5, im->yorigin-im->ysize-7, /* LINEOFFSET */
2052 im->graph_col[GRC_ARROW]);
2054 }
2056 void
2057 grid_paint(image_desc_t *im)
2058 {
2059 long i;
2060 int res=0;
2061 double X0,Y0; /* points for filled graph and more*/
2062 gfx_node_t *node;
2064 /* draw 3d border */
2065 node = gfx_new_area (im->canvas, 0,im->yimg,
2066 2,im->yimg-2,
2067 2,2,im->graph_col[GRC_SHADEA]);
2068 gfx_add_point( node , im->ximg - 2, 2 );
2069 gfx_add_point( node , im->ximg, 0 );
2070 gfx_add_point( node , 0,0 );
2071 /* gfx_add_point( node , 0,im->yimg ); */
2073 node = gfx_new_area (im->canvas, 2,im->yimg-2,
2074 im->ximg-2,im->yimg-2,
2075 im->ximg - 2, 2,
2076 im->graph_col[GRC_SHADEB]);
2077 gfx_add_point( node , im->ximg,0);
2078 gfx_add_point( node , im->ximg,im->yimg);
2079 gfx_add_point( node , 0,im->yimg);
2080 /* gfx_add_point( node , 0,im->yimg ); */
2083 if (im->draw_x_grid == 1 )
2084 vertical_grid(im);
2086 if (im->draw_y_grid == 1){
2087 if(im->logarithmic){
2088 res = horizontal_log_grid(im);
2089 } else {
2090 res = draw_horizontal_grid(im);
2091 }
2093 /* dont draw horizontal grid if there is no min and max val */
2094 if (! res ) {
2095 char *nodata = "No Data found";
2096 gfx_new_text(im->canvas,im->ximg/2, (2*im->yorigin-im->ysize) / 2,
2097 im->graph_col[GRC_FONT],
2098 im->text_prop[TEXT_PROP_AXIS].font,
2099 im->text_prop[TEXT_PROP_AXIS].size,
2100 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
2101 nodata );
2102 }
2103 }
2105 /* yaxis unit description */
2106 gfx_new_text( im->canvas,
2107 10, (im->yorigin - im->ysize/2),
2108 im->graph_col[GRC_FONT],
2109 im->text_prop[TEXT_PROP_UNIT].font,
2110 im->text_prop[TEXT_PROP_UNIT].size, im->tabwidth,
2111 RRDGRAPH_YLEGEND_ANGLE,
2112 GFX_H_LEFT, GFX_V_CENTER,
2113 im->ylegend);
2115 /* graph title */
2116 gfx_new_text( im->canvas,
2117 im->ximg/2, im->text_prop[TEXT_PROP_TITLE].size*1.3+4,
2118 im->graph_col[GRC_FONT],
2119 im->text_prop[TEXT_PROP_TITLE].font,
2120 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
2121 GFX_H_CENTER, GFX_V_CENTER,
2122 im->title);
2123 /* rrdtool 'logo' */
2124 gfx_new_text( im->canvas,
2125 im->ximg-7, 7,
2126 ( im->graph_col[GRC_FONT] & 0xffffff00 ) | 0x00000044,
2127 im->text_prop[TEXT_PROP_AXIS].font,
2128 5.5, im->tabwidth, 270,
2129 GFX_H_RIGHT, GFX_V_TOP,
2130 "RRDTOOL / TOBI OETIKER");
2132 /* graph watermark */
2133 if(im->watermark[0] != '\0') {
2134 gfx_new_text( im->canvas,
2135 im->ximg/2, im->yimg-6,
2136 ( im->graph_col[GRC_FONT] & 0xffffff00 ) | 0x00000044,
2137 im->text_prop[TEXT_PROP_AXIS].font,
2138 5.5, im->tabwidth, 0,
2139 GFX_H_CENTER, GFX_V_BOTTOM,
2140 im->watermark);
2141 }
2143 /* graph labels */
2144 if( !(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH) ) {
2145 for(i=0;i<im->gdes_c;i++){
2146 if(im->gdes[i].legend[0] =='\0')
2147 continue;
2149 /* im->gdes[i].leg_y is the bottom of the legend */
2150 X0 = im->gdes[i].leg_x;
2151 Y0 = im->gdes[i].leg_y;
2152 gfx_new_text ( im->canvas, X0, Y0,
2153 im->graph_col[GRC_FONT],
2154 im->text_prop[TEXT_PROP_LEGEND].font,
2155 im->text_prop[TEXT_PROP_LEGEND].size,
2156 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
2157 im->gdes[i].legend );
2158 /* The legend for GRAPH items starts with "M " to have
2159 enough space for the box */
2160 if ( im->gdes[i].gf != GF_PRINT &&
2161 im->gdes[i].gf != GF_GPRINT &&
2162 im->gdes[i].gf != GF_COMMENT) {
2163 int boxH, boxV;
2165 boxH = gfx_get_text_width(im->canvas, 0,
2166 im->text_prop[TEXT_PROP_LEGEND].font,
2167 im->text_prop[TEXT_PROP_LEGEND].size,
2168 im->tabwidth,"o", 0) * 1.2;
2169 boxV = boxH*1.1;
2171 /* make sure transparent colors show up the same way as in the graph */
2172 node = gfx_new_area(im->canvas,
2173 X0,Y0-boxV,
2174 X0,Y0,
2175 X0+boxH,Y0,
2176 im->graph_col[GRC_BACK]);
2177 gfx_add_point ( node, X0+boxH, Y0-boxV );
2179 node = gfx_new_area(im->canvas,
2180 X0,Y0-boxV,
2181 X0,Y0,
2182 X0+boxH,Y0,
2183 im->gdes[i].col);
2184 gfx_add_point ( node, X0+boxH, Y0-boxV );
2185 node = gfx_new_line(im->canvas,
2186 X0,Y0-boxV,
2187 X0,Y0,
2188 1.0,im->graph_col[GRC_FRAME]);
2189 gfx_add_point(node,X0+boxH,Y0);
2190 gfx_add_point(node,X0+boxH,Y0-boxV);
2191 gfx_close_path(node);
2192 }
2193 }
2194 }
2195 }
2198 /*****************************************************
2199 * lazy check make sure we rely need to create this graph
2200 *****************************************************/
2202 int lazy_check(image_desc_t *im){
2203 FILE *fd = NULL;
2204 int size = 1;
2205 struct stat imgstat;
2207 if (im->lazy == 0) return 0; /* no lazy option */
2208 if (stat(im->graphfile,&imgstat) != 0)
2209 return 0; /* can't stat */
2210 /* one pixel in the existing graph is more then what we would
2211 change here ... */
2212 if (time(NULL) - imgstat.st_mtime >
2213 (im->end - im->start) / im->xsize)
2214 return 0;
2215 if ((fd = fopen(im->graphfile,"rb")) == NULL)
2216 return 0; /* the file does not exist */
2217 switch (im->canvas->imgformat) {
2218 case IF_PNG:
2219 size = PngSize(fd,&(im->ximg),&(im->yimg));
2220 break;
2221 default:
2222 size = 1;
2223 }
2224 fclose(fd);
2225 return size;
2226 }
2228 #ifdef WITH_PIECHART
2229 void
2230 pie_part(image_desc_t *im, gfx_color_t color,
2231 double PieCenterX, double PieCenterY, double Radius,
2232 double startangle, double endangle)
2233 {
2234 gfx_node_t *node;
2235 double angle;
2236 double step=M_PI/50; /* Number of iterations for the circle;
2237 ** 10 is definitely too low, more than
2238 ** 50 seems to be overkill
2239 */
2241 /* Strange but true: we have to work clockwise or else
2242 ** anti aliasing nor transparency don't work.
2243 **
2244 ** This test is here to make sure we do it right, also
2245 ** this makes the for...next loop more easy to implement.
2246 ** The return will occur if the user enters a negative number
2247 ** (which shouldn't be done according to the specs) or if the
2248 ** programmers do something wrong (which, as we all know, never
2249 ** happens anyway :)
2250 */
2251 if (endangle<startangle) return;
2253 /* Hidden feature: Radius decreases each full circle */
2254 angle=startangle;
2255 while (angle>=2*M_PI) {
2256 angle -= 2*M_PI;
2257 Radius *= 0.8;
2258 }
2260 node=gfx_new_area(im->canvas,
2261 PieCenterX+sin(startangle)*Radius,
2262 PieCenterY-cos(startangle)*Radius,
2263 PieCenterX,
2264 PieCenterY,
2265 PieCenterX+sin(endangle)*Radius,
2266 PieCenterY-cos(endangle)*Radius,
2267 color);
2268 for (angle=endangle;angle-startangle>=step;angle-=step) {
2269 gfx_add_point(node,
2270 PieCenterX+sin(angle)*Radius,
2271 PieCenterY-cos(angle)*Radius );
2272 }
2273 }
2275 #endif
2277 int
2278 graph_size_location(image_desc_t *im, int elements
2280 #ifdef WITH_PIECHART
2281 , int piechart
2282 #endif
2284 )
2285 {
2286 /* The actual size of the image to draw is determined from
2287 ** several sources. The size given on the command line is
2288 ** the graph area but we need more as we have to draw labels
2289 ** and other things outside the graph area
2290 */
2292 /* +-+-------------------------------------------+
2293 ** |l|.................title.....................|
2294 ** |e+--+-------------------------------+--------+
2295 ** |b| b| | |
2296 ** |a| a| | pie |
2297 ** |l| l| main graph area | chart |
2298 ** |.| .| | area |
2299 ** |t| y| | |
2300 ** |r+--+-------------------------------+--------+
2301 ** |e| | x-axis labels | |
2302 ** |v+--+-------------------------------+--------+
2303 ** | |..............legends......................|
2304 ** +-+-------------------------------------------+
2305 ** | watermark |
2306 ** +---------------------------------------------+
2307 */
2308 int Xvertical=0,
2309 Ytitle =0,
2310 Xylabel =0,
2311 Xmain =0, Ymain =0,
2312 #ifdef WITH_PIECHART
2313 Xpie =0, Ypie =0,
2314 #endif
2315 Yxlabel =0,
2316 #if 0
2317 Xlegend =0, Ylegend =0,
2318 #endif
2319 Xspacing =15, Yspacing =15,
2321 Ywatermark =4;
2323 if (im->extra_flags & ONLY_GRAPH) {
2324 im->xorigin =0;
2325 im->ximg = im->xsize;
2326 im->yimg = im->ysize;
2327 im->yorigin = im->ysize;
2328 ytr(im,DNAN);
2329 return 0;
2330 }
2332 if (im->ylegend[0] != '\0' ) {
2333 Xvertical = im->text_prop[TEXT_PROP_UNIT].size *2;
2334 }
2337 if (im->title[0] != '\0') {
2338 /* The title is placed "inbetween" two text lines so it
2339 ** automatically has some vertical spacing. The horizontal
2340 ** spacing is added here, on each side.
2341 */
2342 /* don't care for the with of the title
2343 Xtitle = gfx_get_text_width(im->canvas, 0,
2344 im->text_prop[TEXT_PROP_TITLE].font,
2345 im->text_prop[TEXT_PROP_TITLE].size,
2346 im->tabwidth,
2347 im->title, 0) + 2*Xspacing; */
2348 Ytitle = im->text_prop[TEXT_PROP_TITLE].size*2.6+10;
2349 }
2351 if (elements) {
2352 Xmain=im->xsize;
2353 Ymain=im->ysize;
2354 if (im->draw_x_grid) {
2355 Yxlabel=im->text_prop[TEXT_PROP_AXIS].size *2.5;
2356 }
2357 if (im->draw_y_grid) {
2358 Xylabel=gfx_get_text_width(im->canvas, 0,
2359 im->text_prop[TEXT_PROP_AXIS].font,
2360 im->text_prop[TEXT_PROP_AXIS].size,
2361 im->tabwidth,
2362 "0", 0) * im->unitslength;
2363 }
2364 }
2366 #ifdef WITH_PIECHART
2367 if (piechart) {
2368 im->piesize=im->xsize<im->ysize?im->xsize:im->ysize;
2369 Xpie=im->piesize;
2370 Ypie=im->piesize;
2371 }
2372 #endif
2374 /* Now calculate the total size. Insert some spacing where
2375 desired. im->xorigin and im->yorigin need to correspond
2376 with the lower left corner of the main graph area or, if
2377 this one is not set, the imaginary box surrounding the
2378 pie chart area. */
2380 /* The legend width cannot yet be determined, as a result we
2381 ** have problems adjusting the image to it. For now, we just
2382 ** forget about it at all; the legend will have to fit in the
2383 ** size already allocated.
2384 */
2385 im->ximg = Xylabel + Xmain + 2 * Xspacing;
2387 #ifdef WITH_PIECHART
2388 im->ximg += Xpie;
2389 #endif
2391 if (Xmain) im->ximg += Xspacing;
2392 #ifdef WITH_PIECHART
2393 if (Xpie) im->ximg += Xspacing;
2394 #endif
2396 im->xorigin = Xspacing + Xylabel;
2398 /* the length of the title should not influence with width of the graph
2399 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2401 if (Xvertical) { /* unit description */
2402 im->ximg += Xvertical;
2403 im->xorigin += Xvertical;
2404 }
2405 xtr(im,0);
2407 /* The vertical size is interesting... we need to compare
2408 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with
2409 ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2410 ** in order to start even thinking about Ylegend or Ywatermark.
2411 **
2412 ** Do it in three portions: First calculate the inner part,
2413 ** then do the legend, then adjust the total height of the img,
2414 ** adding space for a watermark if one exists;
2415 */
2417 /* reserve space for main and/or pie */
2419 im->yimg = Ymain + Yxlabel;
2421 #ifdef WITH_PIECHART
2422 if (im->yimg < Ypie) im->yimg = Ypie;
2423 #endif
2425 im->yorigin = im->yimg - Yxlabel;
2427 /* reserve space for the title *or* some padding above the graph */
2428 if (Ytitle) {
2429 im->yimg += Ytitle;
2430 im->yorigin += Ytitle;
2431 } else {
2432 im->yimg += 1.5*Yspacing;
2433 im->yorigin += 1.5*Yspacing;
2434 }
2435 /* reserve space for padding below the graph */
2436 im->yimg += Yspacing;
2438 /* Determine where to place the legends onto the image.
2439 ** Adjust im->yimg to match the space requirements.
2440 */
2441 if(leg_place(im)==-1)
2442 return -1;
2444 if (im->watermark[0] != '\0') {
2445 im->yimg += Ywatermark;
2446 }
2448 #if 0
2449 if (Xlegend > im->ximg) {
2450 im->ximg = Xlegend;
2451 /* reposition Pie */
2452 }
2453 #endif
2455 #ifdef WITH_PIECHART
2456 /* The pie is placed in the upper right hand corner,
2457 ** just below the title (if any) and with sufficient
2458 ** padding.
2459 */
2460 if (elements) {
2461 im->pie_x = im->ximg - Xspacing - Xpie/2;
2462 im->pie_y = im->yorigin-Ymain+Ypie/2;
2463 } else {
2464 im->pie_x = im->ximg/2;
2465 im->pie_y = im->yorigin-Ypie/2;
2466 }
2467 #endif
2469 ytr(im,DNAN);
2470 return 0;
2471 }
2473 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
2474 /* yes we are loosing precision by doing tos with floats instead of doubles
2475 but it seems more stable this way. */
2477 static int AlmostEqual2sComplement (float A, float B, int maxUlps)
2478 {
2480 int aInt = *(int*)&A;
2481 int bInt = *(int*)&B;
2482 int intDiff;
2483 /* Make sure maxUlps is non-negative and small enough that the
2484 default NAN won't compare as equal to anything. */
2486 /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
2488 /* Make aInt lexicographically ordered as a twos-complement int */
2490 if (aInt < 0)
2491 aInt = 0x80000000l - aInt;
2493 /* Make bInt lexicographically ordered as a twos-complement int */
2495 if (bInt < 0)
2496 bInt = 0x80000000l - bInt;
2498 intDiff = abs(aInt - bInt);
2500 if (intDiff <= maxUlps)
2501 return 1;
2503 return 0;
2504 }
2506 /* draw that picture thing ... */
2507 int
2508 graph_paint(image_desc_t *im, char ***calcpr)
2509 {
2510 int i,ii;
2511 int lazy = lazy_check(im);
2512 #ifdef WITH_PIECHART
2513 int piechart = 0;
2514 double PieStart=0.0;
2515 #endif
2516 FILE *fo;
2517 gfx_node_t *node;
2519 double areazero = 0.0;
2520 graph_desc_t *lastgdes = NULL;
2522 /* if we are lazy and there is nothing to PRINT ... quit now */
2523 if (lazy && im->prt_c==0) return 0;
2525 /* pull the data from the rrd files ... */
2527 if(data_fetch(im)==-1)
2528 return -1;
2530 /* evaluate VDEF and CDEF operations ... */
2531 if(data_calc(im)==-1)
2532 return -1;
2534 #ifdef WITH_PIECHART
2535 /* check if we need to draw a piechart */
2536 for(i=0;i<im->gdes_c;i++){
2537 if (im->gdes[i].gf == GF_PART) {
2538 piechart=1;
2539 break;
2540 }
2541 }
2542 #endif
2544 /* calculate and PRINT and GPRINT definitions. We have to do it at
2545 * this point because it will affect the length of the legends
2546 * if there are no graph elements we stop here ...
2547 * if we are lazy, try to quit ...
2548 */
2549 i=print_calc(im,calcpr);
2550 if(i<0) return -1;
2551 if(((i==0)
2552 #ifdef WITH_PIECHART
2553 &&(piechart==0)
2554 #endif
2555 ) || lazy) return 0;
2557 #ifdef WITH_PIECHART
2558 /* If there's only the pie chart to draw, signal this */
2559 if (i==0) piechart=2;
2560 #endif
2562 /* get actual drawing data and find min and max values*/
2563 if(data_proc(im)==-1)
2564 return -1;
2566 if(!im->logarithmic){si_unit(im);} /* identify si magnitude Kilo, Mega Giga ? */
2568 if(!im->rigid && ! im->logarithmic)
2569 expand_range(im); /* make sure the upper and lower limit are
2570 sensible values */
2572 if (!calc_horizontal_grid(im))
2573 return -1;
2575 if (im->gridfit)
2576 apply_gridfit(im);
2579 /**************************************************************
2580 *** Calculating sizes and locations became a bit confusing ***
2581 *** so I moved this into a separate function. ***
2582 **************************************************************/
2583 if(graph_size_location(im,i
2584 #ifdef WITH_PIECHART
2585 ,piechart
2586 #endif
2587 )==-1)
2588 return -1;
2590 /* the actual graph is created by going through the individual
2591 graph elements and then drawing them */
2593 node=gfx_new_area ( im->canvas,
2594 0, 0,
2595 0, im->yimg,
2596 im->ximg, im->yimg,
2597 im->graph_col[GRC_BACK]);
2599 gfx_add_point(node,im->ximg, 0);
2601 #ifdef WITH_PIECHART
2602 if (piechart != 2) {
2603 #endif
2604 node=gfx_new_area ( im->canvas,
2605 im->xorigin, im->yorigin,
2606 im->xorigin + im->xsize, im->yorigin,
2607 im->xorigin + im->xsize, im->yorigin-im->ysize,
2608 im->graph_col[GRC_CANVAS]);
2610 gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
2612 if (im->minval > 0.0)
2613 areazero = im->minval;
2614 if (im->maxval < 0.0)
2615 areazero = im->maxval;
2616 #ifdef WITH_PIECHART
2617 }
2618 #endif
2620 #ifdef WITH_PIECHART
2621 if (piechart) {
2622 pie_part(im,im->graph_col[GRC_CANVAS],im->pie_x,im->pie_y,im->piesize*0.5,0,2*M_PI);
2623 }
2624 #endif
2626 for(i=0;i<im->gdes_c;i++){
2627 switch(im->gdes[i].gf){
2628 case GF_CDEF:
2629 case GF_VDEF:
2630 case GF_DEF:
2631 case GF_PRINT:
2632 case GF_GPRINT:
2633 case GF_COMMENT:
2634 case GF_HRULE:
2635 case GF_VRULE:
2636 case GF_XPORT:
2637 case GF_SHIFT:
2638 break;
2639 case GF_TICK:
2640 for (ii = 0; ii < im->xsize; ii++)
2641 {
2642 if (!isnan(im->gdes[i].p_data[ii]) &&
2643 im->gdes[i].p_data[ii] != 0.0)
2644 {
2645 if (im -> gdes[i].yrule > 0 ) {
2646 gfx_new_line(im->canvas,
2647 im -> xorigin + ii, im->yorigin,
2648 im -> xorigin + ii, im->yorigin - im -> gdes[i].yrule * im -> ysize,
2649 1.0,
2650 im -> gdes[i].col );
2651 } else if ( im -> gdes[i].yrule < 0 ) {
2652 gfx_new_line(im->canvas,
2653 im -> xorigin + ii, im->yorigin - im -> ysize,
2654 im -> xorigin + ii, im->yorigin - ( 1 - im -> gdes[i].yrule ) * im -> ysize,
2655 1.0,
2656 im -> gdes[i].col );
2658 }
2659 }
2660 }
2661 break;
2662 case GF_LINE:
2663 case GF_AREA:
2664 /* fix data points at oo and -oo */
2665 for(ii=0;ii<im->xsize;ii++){
2666 if (isinf(im->gdes[i].p_data[ii])){
2667 if (im->gdes[i].p_data[ii] > 0) {
2668 im->gdes[i].p_data[ii] = im->maxval ;
2669 } else {
2670 im->gdes[i].p_data[ii] = im->minval ;
2671 }
2673 }
2674 } /* for */
2676 /* *******************************************************
2677 a ___. (a,t)
2678 | | ___
2679 ____| | | |
2680 | |___|
2681 -------|--t-1--t--------------------------------
2683 if we know the value at time t was a then
2684 we draw a square from t-1 to t with the value a.
2686 ********************************************************* */
2687 if (im->gdes[i].col != 0x0){
2688 /* GF_LINE and friend */
2689 if(im->gdes[i].gf == GF_LINE ){
2690 double last_y=0.0;
2691 node = NULL;
2692 for(ii=1;ii<im->xsize;ii++){
2693 if (isnan(im->gdes[i].p_data[ii]) || (im->slopemode==1 && isnan(im->gdes[i].p_data[ii-1]))){
2694 node = NULL;
2695 continue;
2696 }
2697 if ( node == NULL ) {
2698 last_y = ytr(im,im->gdes[i].p_data[ii]);
2699 if ( im->slopemode == 0 ){
2700 node = gfx_new_line(im->canvas,
2701 ii-1+im->xorigin,last_y,
2702 ii+im->xorigin,last_y,
2703 im->gdes[i].linewidth,
2704 im->gdes[i].col);
2705 } else {
2706 node = gfx_new_line(im->canvas,
2707 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2708 ii+im->xorigin,last_y,
2709 im->gdes[i].linewidth,
2710 im->gdes[i].col);
2711 }
2712 } else {
2713 double new_y = ytr(im,im->gdes[i].p_data[ii]);
2714 if ( im->slopemode==0 && ! AlmostEqual2sComplement(new_y,last_y,4)){
2715 gfx_add_point(node,ii-1+im->xorigin,new_y);
2716 };
2717 last_y = new_y;
2718 gfx_add_point(node,ii+im->xorigin,new_y);
2719 };
2721 }
2722 } else {
2723 int idxI=-1;
2724 double *foreY=malloc(sizeof(double)*im->xsize*2);
2725 double *foreX=malloc(sizeof(double)*im->xsize*2);
2726 double *backY=malloc(sizeof(double)*im->xsize*2);
2727 double *backX=malloc(sizeof(double)*im->xsize*2);
2728 int drawem = 0;
2729 for(ii=0;ii<=im->xsize;ii++){
2730 double ybase,ytop;
2731 if ( idxI > 0 && ( drawem != 0 || ii==im->xsize)){
2732 int cntI=1;
2733 int lastI=0;
2734 while (cntI < idxI && AlmostEqual2sComplement(foreY[lastI],foreY[cntI],4) && AlmostEqual2sComplement(foreY[lastI],foreY[cntI+1],4)){cntI++;}
2735 node = gfx_new_area(im->canvas,
2736 backX[0],backY[0],
2737 foreX[0],foreY[0],
2738 foreX[cntI],foreY[cntI], im->gdes[i].col);
2739 while (cntI < idxI) {
2740 lastI = cntI;
2741 cntI++;
2742 while ( cntI < idxI && AlmostEqual2sComplement(foreY[lastI],foreY[cntI],4) && AlmostEqual2sComplement(foreY[lastI],foreY[cntI+1],4)){cntI++;}
2743 gfx_add_point(node,foreX[cntI],foreY[cntI]);
2744 }
2745 gfx_add_point(node,backX[idxI],backY[idxI]);
2746 while (idxI > 1){
2747 lastI = idxI;
2748 idxI--;
2749 while ( idxI > 1 && AlmostEqual2sComplement(backY[lastI], backY[idxI],4) && AlmostEqual2sComplement(backY[lastI],backY[idxI-1],4)){idxI--;}
2750 gfx_add_point(node,backX[idxI],backY[idxI]);
2751 }
2752 idxI=-1;
2753 drawem = 0;
2754 }
2755 if (drawem != 0){
2756 drawem = 0;
2757 idxI=-1;
2758 }
2759 if (ii == im->xsize) break;
2761 /* keep things simple for now, just draw these bars
2762 do not try to build a big and complex area */
2765 if ( im->slopemode == 0 && ii==0){
2766 continue;
2767 }
2768 if ( isnan(im->gdes[i].p_data[ii]) ) {
2769 drawem = 1;
2770 continue;
2771 }
2772 ytop = ytr(im,im->gdes[i].p_data[ii]);
2773 if ( lastgdes && im->gdes[i].stack ) {
2774 ybase = ytr(im,lastgdes->p_data[ii]);
2775 } else {
2776 ybase = ytr(im,areazero);
2777 }
2778 if ( ybase == ytop ){
2779 drawem = 1;
2780 continue;
2781 }
2782 /* every area has to be wound clock-wise,
2783 so we have to make sur base remains base */
2784 if (ybase > ytop){
2785 double extra = ytop;
2786 ytop = ybase;
2787 ybase = extra;
2788 }
2789 if ( im->slopemode == 0 ){
2790 backY[++idxI] = ybase-0.2;
2791 backX[idxI] = ii+im->xorigin-1;
2792 foreY[idxI] = ytop+0.2;
2793 foreX[idxI] = ii+im->xorigin-1;
2794 }
2795 backY[++idxI] = ybase-0.2;
2796 backX[idxI] = ii+im->xorigin;
2797 foreY[idxI] = ytop+0.2;
2798 foreX[idxI] = ii+im->xorigin;
2799 }
2800 /* close up any remaining area */
2801 free(foreY);
2802 free(foreX);
2803 free(backY);
2804 free(backX);
2805 } /* else GF_LINE */
2806 } /* if color != 0x0 */
2807 /* make sure we do not run into trouble when stacking on NaN */
2808 for(ii=0;ii<im->xsize;ii++){
2809 if (isnan(im->gdes[i].p_data[ii])) {
2810 if (lastgdes && (im->gdes[i].stack)) {
2811 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
2812 } else {
2813 im->gdes[i].p_data[ii] = areazero;
2814 }
2815 }
2816 }
2817 lastgdes = &(im->gdes[i]);
2818 break;
2819 #ifdef WITH_PIECHART
2820 case GF_PART:
2821 if(isnan(im->gdes[i].yrule)) /* fetch variable */
2822 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2824 if (finite(im->gdes[i].yrule)) { /* even the fetched var can be NaN */
2825 pie_part(im,im->gdes[i].col,
2826 im->pie_x,im->pie_y,im->piesize*0.4,
2827 M_PI*2.0*PieStart/100.0,
2828 M_PI*2.0*(PieStart+im->gdes[i].yrule)/100.0);
2829 PieStart += im->gdes[i].yrule;
2830 }
2831 break;
2832 #endif
2833 case GF_STACK:
2834 rrd_set_error("STACK should already be turned into LINE or AREA here");
2835 return -1;
2836 break;
2838 } /* switch */
2839 }
2840 #ifdef WITH_PIECHART
2841 if (piechart==2) {
2842 im->draw_x_grid=0;
2843 im->draw_y_grid=0;
2844 }
2845 #endif
2848 /* grid_paint also does the text */
2849 if( !(im->extra_flags & ONLY_GRAPH) )
2850 grid_paint(im);
2853 if( !(im->extra_flags & ONLY_GRAPH) )
2854 axis_paint(im);
2856 /* the RULES are the last thing to paint ... */
2857 for(i=0;i<im->gdes_c;i++){
2859 switch(im->gdes[i].gf){
2860 case GF_HRULE:
2861 if(isnan(im->gdes[i].yrule)) { /* fetch variable */
2862 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2863 };
2864 if(im->gdes[i].yrule >= im->minval
2865 && im->gdes[i].yrule <= im->maxval)
2866 gfx_new_line(im->canvas,
2867 im->xorigin,ytr(im,im->gdes[i].yrule),
2868 im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2869 1.0,im->gdes[i].col);
2870 break;
2871 case GF_VRULE:
2872 if(im->gdes[i].xrule == 0) { /* fetch variable */
2873 im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
2874 };
2875 if(im->gdes[i].xrule >= im->start
2876 && im->gdes[i].xrule <= im->end)
2877 gfx_new_line(im->canvas,
2878 xtr(im,im->gdes[i].xrule),im->yorigin,
2879 xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2880 1.0,im->gdes[i].col);
2881 break;
2882 default:
2883 break;
2884 }
2885 }
2888 if (strcmp(im->graphfile,"-")==0) {
2889 fo = im->graphhandle ? im->graphhandle : stdout;
2890 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
2891 /* Change translation mode for stdout to BINARY */
2892 _setmode( _fileno( fo ), O_BINARY );
2893 #endif
2894 } else {
2895 if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2896 rrd_set_error("Opening '%s' for write: %s",im->graphfile,
2897 rrd_strerror(errno));
2898 return (-1);
2899 }
2900 }
2901 gfx_render (im->canvas,im->ximg,im->yimg,0x00000000,fo);
2902 if (strcmp(im->graphfile,"-") != 0)
2903 fclose(fo);
2904 return 0;
2905 }
2908 /*****************************************************
2909 * graph stuff
2910 *****************************************************/
2912 int
2913 gdes_alloc(image_desc_t *im){
2915 im->gdes_c++;
2916 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2917 * sizeof(graph_desc_t)))==NULL){
2918 rrd_set_error("realloc graph_descs");
2919 return -1;
2920 }
2923 im->gdes[im->gdes_c-1].step=im->step;
2924 im->gdes[im->gdes_c-1].step_orig=im->step;
2925 im->gdes[im->gdes_c-1].stack=0;
2926 im->gdes[im->gdes_c-1].linewidth=0;
2927 im->gdes[im->gdes_c-1].debug=0;
2928 im->gdes[im->gdes_c-1].start=im->start;
2929 im->gdes[im->gdes_c-1].start_orig=im->start;
2930 im->gdes[im->gdes_c-1].end=im->end;
2931 im->gdes[im->gdes_c-1].end_orig=im->end;
2932 im->gdes[im->gdes_c-1].vname[0]='\0';
2933 im->gdes[im->gdes_c-1].data=NULL;
2934 im->gdes[im->gdes_c-1].ds_namv=NULL;
2935 im->gdes[im->gdes_c-1].data_first=0;
2936 im->gdes[im->gdes_c-1].p_data=NULL;
2937 im->gdes[im->gdes_c-1].rpnp=NULL;
2938 im->gdes[im->gdes_c-1].shift=0;
2939 im->gdes[im->gdes_c-1].col = 0x0;
2940 im->gdes[im->gdes_c-1].legend[0]='\0';
2941 im->gdes[im->gdes_c-1].format[0]='\0';
2942 im->gdes[im->gdes_c-1].strftm=0;
2943 im->gdes[im->gdes_c-1].rrd[0]='\0';
2944 im->gdes[im->gdes_c-1].ds=-1;
2945 im->gdes[im->gdes_c-1].cf_reduce=CF_AVERAGE;
2946 im->gdes[im->gdes_c-1].cf=CF_AVERAGE;
2947 im->gdes[im->gdes_c-1].p_data=NULL;
2948 im->gdes[im->gdes_c-1].yrule=DNAN;
2949 im->gdes[im->gdes_c-1].xrule=0;
2950 return 0;
2951 }
2953 /* copies input untill the first unescaped colon is found
2954 or until input ends. backslashes have to be escaped as well */
2955 int
2956 scan_for_col(const char *const input, int len, char *const output)
2957 {
2958 int inp,outp=0;
2959 for (inp=0;
2960 inp < len &&
2961 input[inp] != ':' &&
2962 input[inp] != '\0';
2963 inp++){
2964 if (input[inp] == '\\' &&
2965 input[inp+1] != '\0' &&
2966 (input[inp+1] == '\\' ||
2967 input[inp+1] == ':')){
2968 output[outp++] = input[++inp];
2969 }
2970 else {
2971 output[outp++] = input[inp];
2972 }
2973 }
2974 output[outp] = '\0';
2975 return inp;
2976 }
2977 /* Some surgery done on this function, it became ridiculously big.
2978 ** Things moved:
2979 ** - initializing now in rrd_graph_init()
2980 ** - options parsing now in rrd_graph_options()
2981 ** - script parsing now in rrd_graph_script()
2982 */
2983 int
2984 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize, FILE *stream, double *ymin, double *ymax)
2985 {
2986 image_desc_t im;
2987 rrd_graph_init(&im);
2988 im.graphhandle = stream;
2990 rrd_graph_options(argc,argv,&im);
2991 if (rrd_test_error()) {
2992 im_free(&im);
2993 return -1;
2994 }
2996 if (strlen(argv[optind])>=MAXPATH) {
2997 rrd_set_error("filename (including path) too long");
2998 im_free(&im);
2999 return -1;
3000 }
3001 strncpy(im.graphfile,argv[optind],MAXPATH-1);
3002 im.graphfile[MAXPATH-1]='\0';
3004 rrd_graph_script(argc,argv,&im,1);
3005 if (rrd_test_error()) {
3006 im_free(&im);
3007 return -1;
3008 }
3010 /* Everything is now read and the actual work can start */
3012 (*prdata)=NULL;
3013 if (graph_paint(&im,prdata)==-1){
3014 im_free(&im);
3015 return -1;
3016 }
3018 /* The image is generated and needs to be output.
3019 ** Also, if needed, print a line with information about the image.
3020 */
3022 *xsize=im.ximg;
3023 *ysize=im.yimg;
3024 *ymin=im.minval;
3025 *ymax=im.maxval;
3026 if (im.imginfo) {
3027 char *filename;
3028 if (!(*prdata)) {
3029 /* maybe prdata is not allocated yet ... lets do it now */
3030 if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
3031 rrd_set_error("malloc imginfo");
3032 return -1;
3033 };
3034 }
3035 if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
3036 ==NULL){
3037 rrd_set_error("malloc imginfo");
3038 return -1;
3039 }
3040 filename=im.graphfile+strlen(im.graphfile);
3041 while(filename > im.graphfile) {
3042 if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
3043 filename--;
3044 }
3046 sprintf((*prdata)[0],im.imginfo,filename,(long)(im.canvas->zoom*im.ximg),(long)(im.canvas->zoom*im.yimg));
3047 }
3048 im_free(&im);
3049 return 0;
3050 }
3052 void
3053 rrd_graph_init(image_desc_t *im)
3054 {
3055 unsigned int i;
3057 #ifdef HAVE_TZSET
3058 tzset();
3059 #endif
3060 #ifdef HAVE_SETLOCALE
3061 setlocale(LC_TIME,"");
3062 #ifdef HAVE_MBSTOWCS
3063 setlocale(LC_CTYPE,"");
3064 #endif
3065 #endif
3066 im->yorigin=0;
3067 im->xorigin=0;
3068 im->minval=0;
3069 im->xlab_user.minsec = -1;
3070 im->ximg=0;
3071 im->yimg=0;
3072 im->xsize = 400;
3073 im->ysize = 100;
3074 im->step = 0;
3075 im->ylegend[0] = '\0';
3076 im->title[0] = '\0';
3077 im->watermark[0] = '\0';
3078 im->minval = DNAN;
3079 im->maxval = DNAN;
3080 im->unitsexponent= 9999;
3081 im->unitslength= 6;
3082 im->symbol = ' ';
3083 im->viewfactor = 1.0;
3084 im->extra_flags= 0;
3085 im->rigid = 0;
3086 im->gridfit = 1;
3087 im->imginfo = NULL;
3088 im->lazy = 0;
3089 im->slopemode = 0;
3090 im->logarithmic = 0;
3091 im->ygridstep = DNAN;
3092 im->draw_x_grid = 1;
3093 im->draw_y_grid = 1;
3094 im->base = 1000;
3095 im->prt_c = 0;
3096 im->gdes_c = 0;
3097 im->gdes = NULL;
3098 im->canvas = gfx_new_canvas();
3099 im->grid_dash_on = 1;
3100 im->grid_dash_off = 1;
3101 im->tabwidth = 40.0;
3103 for(i=0;i<DIM(graph_col);i++)
3104 im->graph_col[i]=graph_col[i];
3106 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3107 {
3108 char *windir;
3109 char rrd_win_default_font[1000];
3110 windir = getenv("windir");
3111 /* %windir% is something like D:\windows or C:\winnt */
3112 if (windir != NULL) {
3113 strncpy(rrd_win_default_font,windir,500);
3114 rrd_win_default_font[500] = '\0';
3115 strcat(rrd_win_default_font,"\\fonts\\");
3116 strcat(rrd_win_default_font,RRD_DEFAULT_FONT);
3117 for(i=0;i<DIM(text_prop);i++){
3118 strncpy(text_prop[i].font,rrd_win_default_font,sizeof(text_prop[i].font)-1);
3119 text_prop[i].font[sizeof(text_prop[i].font)-1] = '\0';
3120 }
3121 }
3122 }
3123 #endif
3124 {
3125 char *deffont;
3126 deffont = getenv("RRD_DEFAULT_FONT");
3127 if (deffont != NULL) {
3128 for(i=0;i<DIM(text_prop);i++){
3129 strncpy(text_prop[i].font,deffont,sizeof(text_prop[i].font)-1);
3130 text_prop[i].font[sizeof(text_prop[i].font)-1] = '\0';
3131 }
3132 }
3133 }
3134 for(i=0;i<DIM(text_prop);i++){
3135 im->text_prop[i].size = text_prop[i].size;
3136 strcpy(im->text_prop[i].font,text_prop[i].font);
3137 }
3138 }
3140 void
3141 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
3142 {
3143 int stroff;
3144 char *parsetime_error = NULL;
3145 char scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
3146 time_t start_tmp=0,end_tmp=0;
3147 long long_tmp;
3148 struct rrd_time_value start_tv, end_tv;
3149 gfx_color_t color;
3150 optind = 0; opterr = 0; /* initialize getopt */
3152 parsetime("end-24h", &start_tv);
3153 parsetime("now", &end_tv);
3155 /* defines for long options without a short equivalent. should be bytes,
3156 and may not collide with (the ASCII value of) short options */
3157 #define LONGOPT_UNITS_SI 255
3159 while (1){
3160 static struct option long_options[] =
3161 {
3162 {"start", required_argument, 0, 's'},
3163 {"end", required_argument, 0, 'e'},
3164 {"x-grid", required_argument, 0, 'x'},
3165 {"y-grid", required_argument, 0, 'y'},
3166 {"vertical-label",required_argument,0,'v'},
3167 {"width", required_argument, 0, 'w'},
3168 {"height", required_argument, 0, 'h'},
3169 {"interlaced", no_argument, 0, 'i'},
3170 {"upper-limit",required_argument, 0, 'u'},
3171 {"lower-limit",required_argument, 0, 'l'},
3172 {"rigid", no_argument, 0, 'r'},
3173 {"base", required_argument, 0, 'b'},
3174 {"logarithmic",no_argument, 0, 'o'},
3175 {"color", required_argument, 0, 'c'},
3176 {"font", required_argument, 0, 'n'},
3177 {"title", required_argument, 0, 't'},
3178 {"imginfo", required_argument, 0, 'f'},
3179 {"imgformat", required_argument, 0, 'a'},
3180 {"lazy", no_argument, 0, 'z'},
3181 {"zoom", required_argument, 0, 'm'},
3182 {"no-legend", no_argument, 0, 'g'},
3183 {"force-rules-legend",no_argument,0, 'F'},
3184 {"only-graph", no_argument, 0, 'j'},
3185 {"alt-y-grid", no_argument, 0, 'Y'},
3186 {"no-minor", no_argument, 0, 'I'},
3187 {"slope-mode", no_argument, 0, 'E'},
3188 {"alt-autoscale", no_argument, 0, 'A'},
3189 {"alt-autoscale-max", no_argument, 0, 'M'},
3190 {"no-gridfit", no_argument, 0, 'N'},
3191 {"units-exponent",required_argument, 0, 'X'},
3192 {"units-length",required_argument, 0, 'L'},
3193 {"units", required_argument, 0, LONGOPT_UNITS_SI },
3194 {"step", required_argument, 0, 'S'},
3195 {"tabwidth", required_argument, 0, 'T'},
3196 {"font-render-mode", required_argument, 0, 'R'},
3197 {"font-smoothing-threshold", required_argument, 0, 'B'},
3198 {"watermark", required_argument, 0, 'W'},
3199 {"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 */
3200 {0,0,0,0}};
3201 int option_index = 0;
3202 int opt;
3203 int col_start,col_end;
3205 opt = getopt_long(argc, argv,
3206 "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:I:zgjFYAMEX:L:S:T:NR:B:W:",
3207 long_options, &option_index);
3209 if (opt == EOF)
3210 break;
3212 switch(opt) {
3213 case 'I':
3214 im->extra_flags |= NOMINOR;
3215 break;
3216 case 'Y':
3217 im->extra_flags |= ALTYGRID;
3218 break;
3219 case 'A':
3220 im->extra_flags |= ALTAUTOSCALE;
3221 break;
3222 case 'M':
3223 im->extra_flags |= ALTAUTOSCALE_MAX;
3224 break;
3225 case 'j':
3226 im->extra_flags |= ONLY_GRAPH;
3227 break;
3228 case 'g':
3229 im->extra_flags |= NOLEGEND;
3230 break;
3231 case 'F':
3232 im->extra_flags |= FORCE_RULES_LEGEND;
3233 break;
3234 case LONGOPT_UNITS_SI:
3235 if(im->extra_flags & FORCE_UNITS) {
3236 rrd_set_error("--units can only be used once!");
3237 return;
3238 }
3239 if(strcmp(optarg,"si")==0)
3240 im->extra_flags |= FORCE_UNITS_SI;
3241 else {
3242 rrd_set_error("invalid argument for --units: %s", optarg );
3243 return;
3244 }
3245 break;
3246 case 'X':
3247 im->unitsexponent = atoi(optarg);
3248 break;
3249 case 'L':
3250 im->unitslength = atoi(optarg);
3251 break;
3252 case 'T':
3253 im->tabwidth = atof(optarg);
3254 break;
3255 case 'S':
3256 im->step = atoi(optarg);
3257 break;
3258 case 'N':
3259 im->gridfit = 0;
3260 break;
3261 case 's':
3262 if ((parsetime_error = parsetime(optarg, &start_tv))) {
3263 rrd_set_error( "start time: %s", parsetime_error );
3264 return;
3265 }
3266 break;
3267 case 'e':
3268 if ((parsetime_error = parsetime(optarg, &end_tv))) {
3269 rrd_set_error( "end time: %s", parsetime_error );
3270 return;
3271 }
3272 break;
3273 case 'x':
3274 if(strcmp(optarg,"none") == 0){
3275 im->draw_x_grid=0;
3276 break;
3277 };
3279 if(sscanf(optarg,
3280 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3281 scan_gtm,
3282 &im->xlab_user.gridst,
3283 scan_mtm,
3284 &im->xlab_user.mgridst,
3285 scan_ltm,
3286 &im->xlab_user.labst,
3287 &im->xlab_user.precis,
3288 &stroff) == 7 && stroff != 0){
3289 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
3290 im->xlab_form[sizeof(im->xlab_form)-1] = '\0';
3291 if((int)(im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
3292 rrd_set_error("unknown keyword %s",scan_gtm);
3293 return;
3294 } else if ((int)(im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
3295 rrd_set_error("unknown keyword %s",scan_mtm);
3296 return;
3297 } else if ((int)(im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
3298 rrd_set_error("unknown keyword %s",scan_ltm);
3299 return;
3300 }
3301 im->xlab_user.minsec = 1;
3302 im->xlab_user.stst = im->xlab_form;
3303 } else {
3304 rrd_set_error("invalid x-grid format");
3305 return;
3306 }
3307 break;
3308 case 'y':
3310 if(strcmp(optarg,"none") == 0){
3311 im->draw_y_grid=0;
3312 break;
3313 };
3315 if(sscanf(optarg,
3316 "%lf:%d",
3317 &im->ygridstep,
3318 &im->ylabfact) == 2) {
3319 if(im->ygridstep<=0){
3320 rrd_set_error("grid step must be > 0");
3321 return;
3322 } else if (im->ylabfact < 1){
3323 rrd_set_error("label factor must be > 0");
3324 return;
3325 }
3326 } else {
3327 rrd_set_error("invalid y-grid format");
3328 return;
3329 }
3330 break;
3331 case 'v':
3332 strncpy(im->ylegend,optarg,150);
3333 im->ylegend[150]='\0';
3334 break;
3335 case 'u':
3336 im->maxval = atof(optarg);
3337 break;
3338 case 'l':
3339 im->minval = atof(optarg);
3340 break;
3341 case 'b':
3342 im->base = atol(optarg);
3343 if(im->base != 1024 && im->base != 1000 ){
3344 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
3345 return;
3346 }
3347 break;
3348 case 'w':
3349 long_tmp = atol(optarg);
3350 if (long_tmp < 10) {
3351 rrd_set_error("width below 10 pixels");
3352 return;
3353 }
3354 im->xsize = long_tmp;
3355 break;
3356 case 'h':
3357 long_tmp = atol(optarg);
3358 if (long_tmp < 10) {
3359 rrd_set_error("height below 10 pixels");
3360 return;
3361 }
3362 im->ysize = long_tmp;
3363 break;
3364 case 'i':
3365 im->canvas->interlaced = 1;
3366 break;
3367 case 'r':
3368 im->rigid = 1;
3369 break;
3370 case 'f':
3371 im->imginfo = optarg;
3372 break;
3373 case 'a':
3374 if((int)(im->canvas->imgformat = if_conv(optarg)) == -1) {
3375 rrd_set_error("unsupported graphics format '%s'",optarg);
3376 return;
3377 }
3378 break;
3379 case 'z':
3380 im->lazy = 1;
3381 break;
3382 case 'E':
3383 im->slopemode = 1;
3384 break;
3386 case 'o':
3387 im->logarithmic = 1;
3388 break;
3389 case 'c':
3390 if(sscanf(optarg,
3391 "%10[A-Z]#%n%8lx%n",
3392 col_nam,&col_start,&color,&col_end) == 2){
3393 int ci;
3394 int col_len = col_end - col_start;
3395 switch (col_len){
3396 case 3:
3397 color = (
3398 ((color & 0xF00) * 0x110000) |
3399 ((color & 0x0F0) * 0x011000) |
3400 ((color & 0x00F) * 0x001100) |
3401 0x000000FF
3402 );
3403 break;
3404 case 4:
3405 color = (
3406 ((color & 0xF000) * 0x11000) |
3407 ((color & 0x0F00) * 0x01100) |
3408 ((color & 0x00F0) * 0x00110) |
3409 ((color & 0x000F) * 0x00011)
3410 );
3411 break;
3412 case 6:
3413 color = (color << 8) + 0xff /* shift left by 8 */;
3414 break;
3415 case 8:
3416 break;
3417 default:
3418 rrd_set_error("the color format is #RRGGBB[AA]");
3419 return;
3420 }
3421 if((ci=grc_conv(col_nam)) != -1){
3422 im->graph_col[ci]=color;
3423 } else {
3424 rrd_set_error("invalid color name '%s'",col_nam);
3425 return;
3426 }
3427 } else {
3428 rrd_set_error("invalid color def format");
3429 return;
3430 }
3431 break;
3432 case 'n':{
3433 char prop[15];
3434 double size = 1;
3435 char font[1024] = "";
3437 if(sscanf(optarg,
3438 "%10[A-Z]:%lf:%1000s",
3439 prop,&size,font) >= 2){
3440 int sindex,propidx;
3441 if((sindex=text_prop_conv(prop)) != -1){
3442 for (propidx=sindex;propidx<TEXT_PROP_LAST;propidx++){
3443 if (size > 0){
3444 im->text_prop[propidx].size=size;
3445 }
3446 if (strlen(font) > 0){
3447 strcpy(im->text_prop[propidx].font,font);
3448 }
3449 if (propidx==sindex && sindex != 0) break;
3450 }
3451 } else {
3452 rrd_set_error("invalid fonttag '%s'",prop);
3453 return;
3454 }
3455 } else {
3456 rrd_set_error("invalid text property format");
3457 return;
3458 }
3459 break;
3460 }
3461 case 'm':
3462 im->canvas->zoom = atof(optarg);
3463 if (im->canvas->zoom <= 0.0) {
3464 rrd_set_error("zoom factor must be > 0");
3465 return;
3466 }
3467 break;
3468 case 't':
3469 strncpy(im->title,optarg,150);
3470 im->title[150]='\0';
3471 break;
3473 case 'R':
3474 if ( strcmp( optarg, "normal" ) == 0 )
3475 im->canvas->aa_type = AA_NORMAL;
3476 else if ( strcmp( optarg, "light" ) == 0 )
3477 im->canvas->aa_type = AA_LIGHT;
3478 else if ( strcmp( optarg, "mono" ) == 0 )
3479 im->canvas->aa_type = AA_NONE;
3480 else
3481 {
3482 rrd_set_error("unknown font-render-mode '%s'", optarg );
3483 return;
3484 }
3485 break;
3487 case 'B':
3488 im->canvas->font_aa_threshold = atof(optarg);
3489 break;
3491 case 'W':
3492 strncpy(im->watermark,optarg,100);
3493 im->watermark[99]='\0';
3494 break;
3496 case '?':
3497 if (optopt != 0)
3498 rrd_set_error("unknown option '%c'", optopt);
3499 else
3500 rrd_set_error("unknown option '%s'",argv[optind-1]);
3501 return;
3502 }
3503 }
3505 if (optind >= argc) {
3506 rrd_set_error("missing filename");
3507 return;
3508 }
3510 if (im->logarithmic == 1 && im->minval <= 0){
3511 rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");
3512 return;
3513 }
3515 if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
3516 /* error string is set in parsetime.c */
3517 return;
3518 }
3520 if (start_tmp < 3600*24*365*10){
3521 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
3522 return;
3523 }
3525 if (end_tmp < start_tmp) {
3526 rrd_set_error("start (%ld) should be less than end (%ld)",
3527 start_tmp, end_tmp);
3528 return;
3529 }
3531 im->start = start_tmp;
3532 im->end = end_tmp;
3533 im->step = max((long)im->step, (im->end-im->start)/im->xsize);
3534 }
3536 int
3537 rrd_graph_check_vname(image_desc_t *im, char *varname, char *err)
3538 {
3539 if ((im->gdes[im->gdes_c-1].vidx=find_var(im,varname))==-1) {
3540 rrd_set_error("Unknown variable '%s' in %s",varname,err);
3541 return -1;
3542 }
3543 return 0;
3544 }
3545 int
3546 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
3547 {
3548 char *color;
3549 graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
3551 color=strstr(var,"#");
3552 if (color==NULL) {
3553 if (optional==0) {
3554 rrd_set_error("Found no color in %s",err);
3555 return 0;
3556 }
3557 return 0;
3558 } else {
3559 int n=0;
3560 char *rest;
3561 gfx_color_t col;
3563 rest=strstr(color,":");
3564 if (rest!=NULL)
3565 n=rest-color;
3566 else
3567 n=strlen(color);
3569 switch (n) {
3570 case 7:
3571 sscanf(color,"#%6lx%n",&col,&n);
3572 col = (col << 8) + 0xff /* shift left by 8 */;
3573 if (n!=7) rrd_set_error("Color problem in %s",err);
3574 break;
3575 case 9:
3576 sscanf(color,"#%8lx%n",&col,&n);
3577 if (n==9) break;
3578 default:
3579 rrd_set_error("Color problem in %s",err);
3580 }
3581 if (rrd_test_error()) return 0;
3582 gdp->col = col;
3583 return n;
3584 }
3585 }
3588 int bad_format(char *fmt) {
3589 char *ptr;
3590 int n=0;
3591 ptr = fmt;
3592 while (*ptr != '\0')
3593 if (*ptr++ == '%') {
3595 /* line cannot end with percent char */
3596 if (*ptr == '\0') return 1;
3598 /* '%s', '%S' and '%%' are allowed */
3599 if (*ptr == 's' || *ptr == 'S' || *ptr == '%') ptr++;
3601 /* %c is allowed (but use only with vdef!) */
3602 else if (*ptr == 'c') {
3603 ptr++;
3604 n=1;
3605 }
3607 /* or else '% 6.2lf' and such are allowed */
3608 else {
3609 /* optional padding character */
3610 if (*ptr == ' ' || *ptr == '+' || *ptr == '-') ptr++;
3612 /* This should take care of 'm.n' with all three optional */
3613 while (*ptr >= '0' && *ptr <= '9') ptr++;
3614 if (*ptr == '.') ptr++;
3615 while (*ptr >= '0' && *ptr <= '9') ptr++;
3617 /* Either 'le', 'lf' or 'lg' must follow here */
3618 if (*ptr++ != 'l') return 1;
3619 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g') ptr++;
3620 else return 1;
3621 n++;
3622 }
3623 }
3625 return (n!=1);
3626 }
3629 int
3630 vdef_parse(gdes,str)
3631 struct graph_desc_t *gdes;
3632 const char *const str;
3633 {
3634 /* A VDEF currently is either "func" or "param,func"
3635 * so the parsing is rather simple. Change if needed.
3636 */
3637 double param;
3638 char func[30];
3639 int n;
3641 n=0;
3642 sscanf(str,"%le,%29[A-Z]%n",¶m,func,&n);
3643 if (n== (int)strlen(str)) { /* matched */
3644 ;
3645 } else {
3646 n=0;
3647 sscanf(str,"%29[A-Z]%n",func,&n);
3648 if (n== (int)strlen(str)) { /* matched */
3649 param=DNAN;
3650 } else {
3651 rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3652 ,str
3653 ,gdes->vname
3654 );
3655 return -1;
3656 }
3657 }
3658 if (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3659 else if (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3660 else if (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3661 else if (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3662 else if (!strcmp("TOTAL", func)) gdes->vf.op = VDEF_TOTAL;
3663 else if (!strcmp("FIRST", func)) gdes->vf.op = VDEF_FIRST;
3664 else if (!strcmp("LAST", func)) gdes->vf.op = VDEF_LAST;
3665 else if (!strcmp("LSLSLOPE", func)) gdes->vf.op = VDEF_LSLSLOPE;
3666 else if (!strcmp("LSLINT", func)) gdes->vf.op = VDEF_LSLINT;
3667 else if (!strcmp("LSLCORREL",func)) gdes->vf.op = VDEF_LSLCORREL;
3668 else {
3669 rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3670 ,func
3671 ,gdes->vname
3672 );
3673 return -1;
3674 };
3676 switch (gdes->vf.op) {
3677 case VDEF_PERCENT:
3678 if (isnan(param)) { /* no parameter given */
3679 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3680 ,func
3681 ,gdes->vname
3682 );
3683 return -1;
3684 };
3685 if (param>=0.0 && param<=100.0) {
3686 gdes->vf.param = param;
3687 gdes->vf.val = DNAN; /* undefined */
3688 gdes->vf.when = 0; /* undefined */
3689 } else {
3690 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3691 ,param
3692 ,gdes->vname
3693 );
3694 return -1;
3695 };
3696 break;
3697 case VDEF_MAXIMUM:
3698 case VDEF_AVERAGE:
3699 case VDEF_MINIMUM:
3700 case VDEF_TOTAL:
3701 case VDEF_FIRST:
3702 case VDEF_LAST:
3703 case VDEF_LSLSLOPE:
3704 case VDEF_LSLINT:
3705 case VDEF_LSLCORREL:
3706 if (isnan(param)) {
3707 gdes->vf.param = DNAN;
3708 gdes->vf.val = DNAN;
3709 gdes->vf.when = 0;
3710 } else {
3711 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3712 ,func
3713 ,gdes->vname
3714 );
3715 return -1;
3716 };
3717 break;
3718 };
3719 return 0;
3720 }
3723 int
3724 vdef_calc(im,gdi)
3725 image_desc_t *im;
3726 int gdi;
3727 {
3728 graph_desc_t *src,*dst;
3729 rrd_value_t *data;
3730 long step,steps;
3732 dst = &im->gdes[gdi];
3733 src = &im->gdes[dst->vidx];
3734 data = src->data + src->ds;
3735 steps = (src->end - src->start) / src->step;
3737 #if 0
3738 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3739 ,src->start
3740 ,src->end
3741 ,steps
3742 );
3743 #endif
3745 switch (dst->vf.op) {
3746 case VDEF_PERCENT: {
3747 rrd_value_t * array;
3748 int field;
3751 if ((array = malloc(steps*sizeof(double)))==NULL) {
3752 rrd_set_error("malloc VDEV_PERCENT");
3753 return -1;
3754 }
3755 for (step=0;step < steps; step++) {
3756 array[step]=data[step*src->ds_cnt];
3757 }
3758 qsort(array,step,sizeof(double),vdef_percent_compar);
3760 field = (steps-1)*dst->vf.param/100;
3761 dst->vf.val = array[field];
3762 dst->vf.when = 0; /* no time component */
3763 free(array);
3764 #if 0
3765 for(step=0;step<steps;step++)
3766 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3767 #endif
3768 }
3769 break;
3770 case VDEF_MAXIMUM:
3771 step=0;
3772 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3773 if (step == steps) {
3774 dst->vf.val = DNAN;
3775 dst->vf.when = 0;
3776 } else {
3777 dst->vf.val = data[step*src->ds_cnt];
3778 dst->vf.when = src->start + (step+1)*src->step;
3779 }
3780 while (step != steps) {
3781 if (finite(data[step*src->ds_cnt])) {
3782 if (data[step*src->ds_cnt] > dst->vf.val) {
3783 dst->vf.val = data[step*src->ds_cnt];
3784 dst->vf.when = src->start + (step+1)*src->step;
3785 }
3786 }
3787 step++;
3788 }
3789 break;
3790 case VDEF_TOTAL:
3791 case VDEF_AVERAGE: {
3792 int cnt=0;
3793 double sum=0.0;
3794 for (step=0;step<steps;step++) {
3795 if (finite(data[step*src->ds_cnt])) {
3796 sum += data[step*src->ds_cnt];
3797 cnt ++;
3798 };
3799 }
3800 if (cnt) {
3801 if (dst->vf.op == VDEF_TOTAL) {
3802 dst->vf.val = sum*src->step;
3803 dst->vf.when = cnt*src->step; /* not really "when" */
3804 } else {
3805 dst->vf.val = sum/cnt;
3806 dst->vf.when = 0; /* no time component */
3807 };
3808 } else {
3809 dst->vf.val = DNAN;
3810 dst->vf.when = 0;
3811 }
3812 }
3813 break;
3814 case VDEF_MINIMUM:
3815 step=0;
3816 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3817 if (step == steps) {
3818 dst->vf.val = DNAN;
3819 dst->vf.when = 0;
3820 } else {
3821 dst->vf.val = data[step*src->ds_cnt];
3822 dst->vf.when = src->start + (step+1)*src->step;
3823 }
3824 while (step != steps) {
3825 if (finite(data[step*src->ds_cnt])) {
3826 if (data[step*src->ds_cnt] < dst->vf.val) {
3827 dst->vf.val = data[step*src->ds_cnt];
3828 dst->vf.when = src->start + (step+1)*src->step;
3829 }
3830 }
3831 step++;
3832 }
3833 break;
3834 case VDEF_FIRST:
3835 /* The time value returned here is one step before the
3836 * actual time value. This is the start of the first
3837 * non-NaN interval.
3838 */
3839 step=0;
3840 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3841 if (step == steps) { /* all entries were NaN */
3842 dst->vf.val = DNAN;
3843 dst->vf.when = 0;
3844 } else {
3845 dst->vf.val = data[step*src->ds_cnt];
3846 dst->vf.when = src->start + step*src->step;
3847 }
3848 break;
3849 case VDEF_LAST:
3850 /* The time value returned here is the
3851 * actual time value. This is the end of the last
3852 * non-NaN interval.
3853 */
3854 step=steps-1;
3855 while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3856 if (step < 0) { /* all entries were NaN */
3857 dst->vf.val = DNAN;
3858 dst->vf.when = 0;
3859 } else {
3860 dst->vf.val = data[step*src->ds_cnt];
3861 dst->vf.when = src->start + (step+1)*src->step;
3862 }
3863 break;
3864 case VDEF_LSLSLOPE:
3865 case VDEF_LSLINT:
3866 case VDEF_LSLCORREL:{
3867 /* Bestfit line by linear least squares method */
3869 int cnt=0;
3870 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl ;
3871 SUMx = 0; SUMy = 0; SUMxy = 0; SUMxx = 0; SUMyy = 0;
3873 for (step=0;step<steps;step++) {
3874 if (finite(data[step*src->ds_cnt])) {
3875 cnt++;
3876 SUMx += step;
3877 SUMxx += step * step;
3878 SUMxy += step * data[step*src->ds_cnt];
3879 SUMy += data[step*src->ds_cnt];
3880 SUMyy += data[step*src->ds_cnt]*data[step*src->ds_cnt];
3881 };
3882 }
3884 slope = ( SUMx*SUMy - cnt*SUMxy ) / ( SUMx*SUMx - cnt*SUMxx );
3885 y_intercept = ( SUMy - slope*SUMx ) / cnt;
3886 correl = (SUMxy - (SUMx*SUMy)/cnt) / sqrt((SUMxx - (SUMx*SUMx)/cnt)*(SUMyy - (SUMy*SUMy)/cnt));
3888 if (cnt) {
3889 if (dst->vf.op == VDEF_LSLSLOPE) {
3890 dst->vf.val = slope;
3891 dst->vf.when = cnt*src->step;
3892 } else if (dst->vf.op == VDEF_LSLINT) {
3893 dst->vf.val = y_intercept;
3894 dst->vf.when = cnt*src->step;
3895 } else if (dst->vf.op == VDEF_LSLCORREL) {
3896 dst->vf.val = correl;
3897 dst->vf.when = cnt*src->step;
3898 };
3900 } else {
3901 dst->vf.val = DNAN;
3902 dst->vf.when = 0;
3903 }
3904 }
3905 break;
3906 }
3907 return 0;
3908 }
3910 /* NaN < -INF < finite_values < INF */
3911 int
3912 vdef_percent_compar(a,b)
3913 const void *a,*b;
3914 {
3915 /* Equality is not returned; this doesn't hurt except
3916 * (maybe) for a little performance.
3917 */
3919 /* First catch NaN values. They are smallest */
3920 if (isnan( *(double *)a )) return -1;
3921 if (isnan( *(double *)b )) return 1;
3923 /* NaN doesn't reach this part so INF and -INF are extremes.
3924 * The sign from isinf() is compatible with the sign we return
3925 */
3926 if (isinf( *(double *)a )) return isinf( *(double *)a );
3927 if (isinf( *(double *)b )) return isinf( *(double *)b );
3929 /* If we reach this, both values must be finite */
3930 if ( *(double *)a < *(double *)b ) return -1; else return 1;
3931 }