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