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