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