9b4631157148d61053cff68fd367eb874ce1fb67
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_dashed_line ( im->canvas,
1573 X0-2,Y0,
1574 X1+2,Y0,
1575 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1576 im->grid_dash_on, im->grid_dash_off);
1578 } else {
1579 gfx_new_dashed_line ( im->canvas,
1580 X0-1,Y0,
1581 X1+1,Y0,
1582 GRIDWIDTH, im->graph_col[GRC_GRID],
1583 im->grid_dash_on, im->grid_dash_off);
1585 }
1586 }
1587 }
1588 return 1;
1589 }
1591 /* logaritmic horizontal grid */
1592 int
1593 horizontal_log_grid(image_desc_t *im)
1594 {
1595 double pixpex;
1596 int ii,i;
1597 int minoridx=0, majoridx=0;
1598 char graph_label[100];
1599 double X0,X1,Y0;
1600 double value, pixperstep, minstep;
1602 /* find grid spaceing */
1603 pixpex= (double)im->ysize / (log10(im->maxval) - log10(im->minval));
1605 if (isnan(pixpex)) {
1606 return 0;
1607 }
1609 for(i=0;yloglab[i][0] > 0;i++){
1610 minstep = log10(yloglab[i][0]);
1611 for(ii=1;yloglab[i][ii+1] > 0;ii++){
1612 if(yloglab[i][ii+2]==0){
1613 minstep = log10(yloglab[i][ii+1])-log10(yloglab[i][ii]);
1614 break;
1615 }
1616 }
1617 pixperstep = pixpex * minstep;
1618 if(pixperstep > 5){minoridx = i;}
1619 if(pixperstep > 2 * im->text_prop[TEXT_PROP_LEGEND].size){majoridx = i;}
1620 }
1622 X0=im->xorigin;
1623 X1=im->xorigin+im->xsize;
1624 /* paint minor grid */
1625 for (value = pow((double)10, log10(im->minval)
1626 - fmod(log10(im->minval),log10(yloglab[minoridx][0])));
1627 value <= im->maxval;
1628 value *= yloglab[minoridx][0]){
1629 if (value < im->minval) continue;
1630 i=0;
1631 while(yloglab[minoridx][++i] > 0){
1632 Y0 = ytr(im,value * yloglab[minoridx][i]);
1633 if (Y0 <= im->yorigin - im->ysize) break;
1634 gfx_new_dashed_line ( im->canvas,
1635 X0-1,Y0,
1636 X1+1,Y0,
1637 GRIDWIDTH, im->graph_col[GRC_GRID],
1638 im->grid_dash_on, im->grid_dash_off);
1639 }
1640 }
1642 /* paint major grid and labels*/
1643 for (value = pow((double)10, log10(im->minval)
1644 - fmod(log10(im->minval),log10(yloglab[majoridx][0])));
1645 value <= im->maxval;
1646 value *= yloglab[majoridx][0]){
1647 if (value < im->minval) continue;
1648 i=0;
1649 while(yloglab[majoridx][++i] > 0){
1650 Y0 = ytr(im,value * yloglab[majoridx][i]);
1651 if (Y0 <= im->yorigin - im->ysize) break;
1652 gfx_new_dashed_line ( im->canvas,
1653 X0-2,Y0,
1654 X1+2,Y0,
1655 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1656 im->grid_dash_on, im->grid_dash_off);
1658 sprintf(graph_label,"%3.0e",value * yloglab[majoridx][i]);
1659 gfx_new_text ( im->canvas,
1660 X0-im->text_prop[TEXT_PROP_AXIS].size/1.5, Y0,
1661 im->graph_col[GRC_FONT],
1662 im->text_prop[TEXT_PROP_AXIS].font,
1663 im->text_prop[TEXT_PROP_AXIS].size,
1664 im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1665 graph_label );
1666 }
1667 }
1668 return 1;
1669 }
1672 void
1673 vertical_grid(
1674 image_desc_t *im )
1675 {
1676 int xlab_sel; /* which sort of label and grid ? */
1677 time_t ti, tilab, timajor;
1678 long factor;
1679 char graph_label[100];
1680 double X0,Y0,Y1; /* points for filled graph and more*/
1683 /* the type of time grid is determined by finding
1684 the number of seconds per pixel in the graph */
1687 if(im->xlab_user.minsec == -1){
1688 factor=(im->end - im->start)/im->xsize;
1689 xlab_sel=0;
1690 while ( xlab[xlab_sel+1].minsec != -1
1691 && xlab[xlab_sel+1].minsec <= factor){ xlab_sel++; }
1692 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
1693 im->xlab_user.gridst = xlab[xlab_sel].gridst;
1694 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
1695 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
1696 im->xlab_user.labtm = xlab[xlab_sel].labtm;
1697 im->xlab_user.labst = xlab[xlab_sel].labst;
1698 im->xlab_user.precis = xlab[xlab_sel].precis;
1699 im->xlab_user.stst = xlab[xlab_sel].stst;
1700 }
1702 /* y coords are the same for every line ... */
1703 Y0 = im->yorigin;
1704 Y1 = im->yorigin-im->ysize;
1707 /* paint the minor grid */
1708 for(ti = find_first_time(im->start,
1709 im->xlab_user.gridtm,
1710 im->xlab_user.gridst),
1711 timajor = find_first_time(im->start,
1712 im->xlab_user.mgridtm,
1713 im->xlab_user.mgridst);
1714 ti < im->end;
1715 ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
1716 ){
1717 /* are we inside the graph ? */
1718 if (ti < im->start || ti > im->end) continue;
1719 while (timajor < ti) {
1720 timajor = find_next_time(timajor,
1721 im->xlab_user.mgridtm, im->xlab_user.mgridst);
1722 }
1723 if (ti == timajor) continue; /* skip as falls on major grid line */
1724 X0 = xtr(im,ti);
1725 gfx_new_dashed_line(im->canvas,X0,Y0+1, X0,Y1-1,GRIDWIDTH,
1726 im->graph_col[GRC_GRID],
1727 im->grid_dash_on, im->grid_dash_off);
1729 }
1731 /* paint the major grid */
1732 for(ti = find_first_time(im->start,
1733 im->xlab_user.mgridtm,
1734 im->xlab_user.mgridst);
1735 ti < im->end;
1736 ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
1737 ){
1738 /* are we inside the graph ? */
1739 if (ti < im->start || ti > im->end) continue;
1740 X0 = xtr(im,ti);
1741 gfx_new_dashed_line(im->canvas,X0,Y0+3, X0,Y1-2,MGRIDWIDTH,
1742 im->graph_col[GRC_MGRID],
1743 im->grid_dash_on, im->grid_dash_off);
1745 }
1746 /* paint the labels below the graph */
1747 for(ti = find_first_time(im->start,
1748 im->xlab_user.labtm,
1749 im->xlab_user.labst);
1750 ti <= im->end;
1751 ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
1752 ){
1753 tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
1754 /* are we inside the graph ? */
1755 if (ti < im->start || ti > im->end) continue;
1757 #if HAVE_STRFTIME
1758 strftime(graph_label,99,im->xlab_user.stst,localtime(&tilab));
1759 #else
1760 # error "your libc has no strftime I guess we'll abort the exercise here."
1761 #endif
1762 gfx_new_text ( im->canvas,
1763 xtr(im,tilab), Y0+im->text_prop[TEXT_PROP_AXIS].size/1.5,
1764 im->graph_col[GRC_FONT],
1765 im->text_prop[TEXT_PROP_AXIS].font,
1766 im->text_prop[TEXT_PROP_AXIS].size,
1767 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP,
1768 graph_label );
1770 }
1772 }
1775 void
1776 axis_paint(
1777 image_desc_t *im
1778 )
1779 {
1780 /* draw x and y axis */
1781 gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
1782 im->xorigin+im->xsize,im->yorigin-im->ysize,
1783 GRIDWIDTH, im->graph_col[GRC_GRID]);
1785 gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
1786 im->xorigin+im->xsize,im->yorigin-im->ysize,
1787 GRIDWIDTH, im->graph_col[GRC_GRID]);
1789 gfx_new_line ( im->canvas, im->xorigin-4,im->yorigin,
1790 im->xorigin+im->xsize+4,im->yorigin,
1791 MGRIDWIDTH, im->graph_col[GRC_GRID]);
1793 gfx_new_line ( im->canvas, im->xorigin,im->yorigin+4,
1794 im->xorigin,im->yorigin-im->ysize-4,
1795 MGRIDWIDTH, im->graph_col[GRC_GRID]);
1798 /* arrow for X axis direction */
1799 gfx_new_area ( im->canvas,
1800 im->xorigin+im->xsize+3, im->yorigin-3,
1801 im->xorigin+im->xsize+3, im->yorigin+4,
1802 im->xorigin+im->xsize+8, im->yorigin+0.5, /* LINEOFFSET */
1803 im->graph_col[GRC_ARROW]);
1807 }
1809 void
1810 grid_paint(image_desc_t *im)
1811 {
1812 long i;
1813 int res=0;
1814 double X0,Y0; /* points for filled graph and more*/
1815 gfx_node_t *node;
1817 /* draw 3d border */
1818 node = gfx_new_area (im->canvas, 0,im->yimg,
1819 2,im->yimg-2,
1820 2,2,im->graph_col[GRC_SHADEA]);
1821 gfx_add_point( node , im->ximg - 2, 2 );
1822 gfx_add_point( node , im->ximg, 0 );
1823 gfx_add_point( node , 0,0 );
1824 /* gfx_add_point( node , 0,im->yimg ); */
1826 node = gfx_new_area (im->canvas, 2,im->yimg-2,
1827 im->ximg-2,im->yimg-2,
1828 im->ximg - 2, 2,
1829 im->graph_col[GRC_SHADEB]);
1830 gfx_add_point( node , im->ximg,0);
1831 gfx_add_point( node , im->ximg,im->yimg);
1832 gfx_add_point( node , 0,im->yimg);
1833 /* gfx_add_point( node , 0,im->yimg ); */
1836 if (im->draw_x_grid == 1 )
1837 vertical_grid(im);
1839 if (im->draw_y_grid == 1){
1840 if(im->logarithmic){
1841 res = horizontal_log_grid(im);
1842 } else {
1843 res = draw_horizontal_grid(im);
1844 }
1846 /* dont draw horizontal grid if there is no min and max val */
1847 if (! res ) {
1848 char *nodata = "No Data found";
1849 gfx_new_text(im->canvas,im->ximg/2, (2*im->yorigin-im->ysize) / 2,
1850 im->graph_col[GRC_FONT],
1851 im->text_prop[TEXT_PROP_AXIS].font,
1852 im->text_prop[TEXT_PROP_AXIS].size,
1853 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
1854 nodata );
1855 }
1856 }
1858 /* yaxis description */
1859 if (im->canvas->imgformat != IF_PNG) {
1860 gfx_new_text( im->canvas,
1861 7, (im->yorigin - im->ysize/2),
1862 im->graph_col[GRC_FONT],
1863 im->text_prop[TEXT_PROP_AXIS].font,
1864 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 270.0,
1865 GFX_H_CENTER, GFX_V_CENTER,
1866 im->ylegend);
1867 } else {
1868 /* horrible hack until we can actually print vertically */
1869 {
1870 int n;
1871 int l=strlen(im->ylegend);
1872 char s[2];
1873 for (n=0;n<strlen(im->ylegend);n++) {
1874 s[0]=im->ylegend[n];
1875 s[1]='\0';
1876 gfx_new_text(im->canvas,7,im->text_prop[TEXT_PROP_AXIS].size*(l-n),
1877 im->graph_col[GRC_FONT],
1878 im->text_prop[TEXT_PROP_AXIS].font,
1879 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 270.0,
1880 GFX_H_CENTER, GFX_V_CENTER,
1881 s);
1882 }
1883 }
1884 }
1886 /* graph title */
1887 gfx_new_text( im->canvas,
1888 im->ximg/2, im->text_prop[TEXT_PROP_TITLE].size,
1889 im->graph_col[GRC_FONT],
1890 im->text_prop[TEXT_PROP_TITLE].font,
1891 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
1892 GFX_H_CENTER, GFX_V_CENTER,
1893 im->title);
1895 /* graph labels */
1896 if( !(im->extra_flags & NOLEGEND) ) {
1897 for(i=0;i<im->gdes_c;i++){
1898 if(im->gdes[i].legend[0] =='\0')
1899 continue;
1901 /* im->gdes[i].leg_y is the bottom of the legend */
1902 X0 = im->gdes[i].leg_x;
1903 Y0 = im->gdes[i].leg_y;
1904 /* Box needed? */
1905 if ( im->gdes[i].gf != GF_GPRINT
1906 && im->gdes[i].gf != GF_COMMENT) {
1907 int boxH, boxV;
1909 boxH = gfx_get_text_width(im->canvas, 0,
1910 im->text_prop[TEXT_PROP_AXIS].font,
1911 im->text_prop[TEXT_PROP_AXIS].size,
1912 im->tabwidth,"M") * 1.25;
1913 boxV = boxH;
1915 node = gfx_new_area(im->canvas,
1916 X0,Y0-boxV,
1917 X0,Y0,
1918 X0+boxH,Y0,
1919 im->gdes[i].col);
1920 gfx_add_point ( node, X0+boxH, Y0-boxV );
1921 node = gfx_new_line(im->canvas,
1922 X0,Y0-boxV, X0,Y0,
1923 1,0x000000FF);
1924 gfx_add_point(node,X0+boxH,Y0);
1925 gfx_add_point(node,X0+boxH,Y0-boxV);
1926 gfx_close_path(node);
1927 X0 += boxH / 1.25 * 2;
1928 }
1929 gfx_new_text ( im->canvas, X0, Y0,
1930 im->graph_col[GRC_FONT],
1931 im->text_prop[TEXT_PROP_AXIS].font,
1932 im->text_prop[TEXT_PROP_AXIS].size,
1933 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
1934 im->gdes[i].legend );
1935 }
1936 }
1937 }
1940 /*****************************************************
1941 * lazy check make sure we rely need to create this graph
1942 *****************************************************/
1944 int lazy_check(image_desc_t *im){
1945 FILE *fd = NULL;
1946 int size = 1;
1947 struct stat imgstat;
1949 if (im->lazy == 0) return 0; /* no lazy option */
1950 if (stat(im->graphfile,&imgstat) != 0)
1951 return 0; /* can't stat */
1952 /* one pixel in the existing graph is more then what we would
1953 change here ... */
1954 if (time(NULL) - imgstat.st_mtime >
1955 (im->end - im->start) / im->xsize)
1956 return 0;
1957 if ((fd = fopen(im->graphfile,"rb")) == NULL)
1958 return 0; /* the file does not exist */
1959 switch (im->canvas->imgformat) {
1960 case IF_PNG:
1961 size = PngSize(fd,&(im->ximg),&(im->yimg));
1962 break;
1963 default:
1964 size = 1;
1965 }
1966 fclose(fd);
1967 return size;
1968 }
1970 void
1971 pie_part(image_desc_t *im, gfx_color_t color,
1972 double PieCenterX, double PieCenterY, double Radius,
1973 double startangle, double endangle)
1974 {
1975 gfx_node_t *node;
1976 double angle;
1977 double step=M_PI/50; /* Number of iterations for the circle;
1978 ** 10 is definitely too low, more than
1979 ** 50 seems to be overkill
1980 */
1982 /* Strange but true: we have to work clockwise or else
1983 ** anti aliasing nor transparency don't work.
1984 **
1985 ** This test is here to make sure we do it right, also
1986 ** this makes the for...next loop more easy to implement.
1987 ** The return will occur if the user enters a negative number
1988 ** (which shouldn't be done according to the specs) or if the
1989 ** programmers do something wrong (which, as we all know, never
1990 ** happens anyway :)
1991 */
1992 if (endangle<startangle) return;
1994 /* Hidden feature: Radius decreases each full circle */
1995 angle=startangle;
1996 while (angle>=2*M_PI) {
1997 angle -= 2*M_PI;
1998 Radius *= 0.8;
1999 }
2001 node=gfx_new_area(im->canvas,
2002 PieCenterX+sin(startangle)*Radius,
2003 PieCenterY-cos(startangle)*Radius,
2004 PieCenterX,
2005 PieCenterY,
2006 PieCenterX+sin(endangle)*Radius,
2007 PieCenterY-cos(endangle)*Radius,
2008 color);
2009 for (angle=endangle;angle-startangle>=step;angle-=step) {
2010 gfx_add_point(node,
2011 PieCenterX+sin(angle)*Radius,
2012 PieCenterY-cos(angle)*Radius );
2013 }
2014 }
2016 int
2017 graph_size_location(image_desc_t *im, int elements, int piechart )
2018 {
2019 /* The actual size of the image to draw is determined from
2020 ** several sources. The size given on the command line is
2021 ** the graph area but we need more as we have to draw labels
2022 ** and other things outside the graph area
2023 */
2025 /* +-+-------------------------------------------+
2026 ** |l|.................title.....................|
2027 ** |e+--+-------------------------------+--------+
2028 ** |b| b| | |
2029 ** |a| a| | pie |
2030 ** |l| l| main graph area | chart |
2031 ** |.| .| | area |
2032 ** |t| y| | |
2033 ** |r+--+-------------------------------+--------+
2034 ** |e| | x-axis labels | |
2035 ** |v+--+-------------------------------+--------+
2036 ** | |..............legends......................|
2037 ** +-+-------------------------------------------+
2038 */
2039 int Xvertical=0, Yvertical=0,
2040 Xtitle =0, Ytitle =0,
2041 Xylabel =0, Yylabel =0,
2042 Xmain =0, Ymain =0,
2043 Xpie =0, Ypie =0,
2044 Xxlabel =0, Yxlabel =0,
2045 #if 0
2046 Xlegend =0, Ylegend =0,
2047 #endif
2048 Xspacing =10, Yspacing =10;
2050 if (im->ylegend[0] != '\0') {
2051 Xvertical = im->text_prop[TEXT_PROP_LEGEND].size *2;
2052 Yvertical = im->text_prop[TEXT_PROP_LEGEND].size * (strlen(im->ylegend)+1);
2053 }
2055 if (im->title[0] != '\0') {
2056 /* The title is placed "inbetween" two text lines so it
2057 ** automatically has some vertical spacing. The horizontal
2058 ** spacing is added here, on each side.
2059 */
2060 Xtitle = gfx_get_text_width(im->canvas, 0,
2061 im->text_prop[TEXT_PROP_TITLE].font,
2062 im->text_prop[TEXT_PROP_TITLE].size,
2063 im->tabwidth,
2064 im->title) + 2*Xspacing;
2065 Ytitle = im->text_prop[TEXT_PROP_TITLE].size*2;
2066 }
2068 if (elements) {
2069 Xmain=im->xsize;
2070 Ymain=im->ysize;
2071 if (im->draw_x_grid) {
2072 Xxlabel=Xmain;
2073 Yxlabel=im->text_prop[TEXT_PROP_LEGEND].size *2;
2074 }
2075 if (im->draw_y_grid) {
2076 Xylabel=im->text_prop[TEXT_PROP_LEGEND].size *6;
2077 Yylabel=Ymain;
2078 }
2079 }
2081 if (piechart) {
2082 im->piesize=im->xsize<im->ysize?im->xsize:im->ysize;
2083 Xpie=im->piesize;
2084 Ypie=im->piesize;
2085 }
2087 /* Now calculate the total size. Insert some spacing where
2088 desired. im->xorigin and im->yorigin need to correspond
2089 with the lower left corner of the main graph area or, if
2090 this one is not set, the imaginary box surrounding the
2091 pie chart area. */
2093 /* The legend width cannot yet be determined, as a result we
2094 ** have problems adjusting the image to it. For now, we just
2095 ** forget about it at all; the legend will have to fit in the
2096 ** size already allocated.
2097 */
2098 im->ximg = Xylabel + Xmain + Xpie + Xspacing;
2099 if (Xmain) im->ximg += Xspacing;
2100 if (Xpie) im->ximg += Xspacing;
2101 im->xorigin = Xspacing + Xylabel;
2102 if (Xtitle > im->ximg) im->ximg = Xtitle;
2103 if (Xvertical) {
2104 im->ximg += Xvertical;
2105 im->xorigin += Xvertical;
2106 }
2107 xtr(im,0);
2109 /* The vertical size is interesting... we need to compare
2110 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend} with Yvertical
2111 ** however we need to know {Ytitle+Ymain+Yxlabel} in order to
2112 ** start even thinking about Ylegend.
2113 **
2114 ** Do it in three portions: First calculate the inner part,
2115 ** then do the legend, then adjust the total height of the img.
2116 */
2118 /* reserve space for main and/or pie */
2119 im->yimg = Ymain + Yxlabel;
2120 if (im->yimg < Ypie) im->yimg = Ypie;
2121 im->yorigin = im->yimg - Yxlabel;
2122 /* reserve space for the title *or* some padding above the graph */
2123 if (Ytitle) {
2124 im->yimg += Ytitle;
2125 im->yorigin += Ytitle;
2126 } else {
2127 im->yimg += Yspacing;
2128 im->yorigin += Yspacing;
2129 }
2130 /* reserve space for padding below the graph */
2131 im->yimg += Yspacing;
2132 ytr(im,DNAN);
2134 /* Determine where to place the legends onto the image.
2135 ** Adjust im->yimg to match the space requirements.
2136 */
2137 if(leg_place(im)==-1)
2138 return -1;
2140 /* last of three steps: check total height of image */
2141 if (im->yimg < Yvertical) im->yimg = Yvertical;
2143 #if 0
2144 if (Xlegend > im->ximg) {
2145 im->ximg = Xlegend;
2146 /* reposition Pie */
2147 #endif
2149 /* The pie is placed in the upper right hand corner,
2150 ** just below the title (if any) and with sufficient
2151 ** padding.
2152 */
2153 if (elements) {
2154 im->pie_x = im->ximg - Xspacing - Xpie/2;
2155 im->pie_y = im->yorigin-Ymain+Ypie/2;
2156 } else {
2157 im->pie_x = im->ximg/2;
2158 im->pie_y = im->yorigin-Ypie/2;
2159 }
2161 return 0;
2162 }
2164 /* draw that picture thing ... */
2165 int
2166 graph_paint(image_desc_t *im, char ***calcpr)
2167 {
2168 int i,ii;
2169 int lazy = lazy_check(im);
2170 int piechart = 0;
2171 double PieStart=0.0;
2172 FILE *fo;
2173 gfx_node_t *node;
2175 double areazero = 0.0;
2176 enum gf_en stack_gf = GF_PRINT;
2177 graph_desc_t *lastgdes = NULL;
2179 /* if we are lazy and there is nothing to PRINT ... quit now */
2180 if (lazy && im->prt_c==0) return 0;
2182 /* pull the data from the rrd files ... */
2184 if(data_fetch(im)==-1)
2185 return -1;
2187 /* evaluate VDEF and CDEF operations ... */
2188 if(data_calc(im)==-1)
2189 return -1;
2191 /* check if we need to draw a piechart */
2192 for(i=0;i<im->gdes_c;i++){
2193 if (im->gdes[i].gf == GF_PART) {
2194 piechart=1;
2195 break;
2196 }
2197 }
2199 /* calculate and PRINT and GPRINT definitions. We have to do it at
2200 * this point because it will affect the length of the legends
2201 * if there are no graph elements we stop here ...
2202 * if we are lazy, try to quit ...
2203 */
2204 i=print_calc(im,calcpr);
2205 if(i<0) return -1;
2206 if(((i==0)&&(piechart==0)) || lazy) return 0;
2208 /* If there's only the pie chart to draw, signal this */
2209 if (i==0) piechart=2;
2211 /* get actual drawing data and find min and max values*/
2212 if(data_proc(im)==-1)
2213 return -1;
2215 if(!im->logarithmic){si_unit(im);} /* identify si magnitude Kilo, Mega Giga ? */
2217 if(!im->rigid && ! im->logarithmic)
2218 expand_range(im); /* make sure the upper and lower limit are
2219 sensible values */
2221 if (!calc_horizontal_grid(im))
2222 return -1;
2223 if (im->gridfit)
2224 apply_gridfit(im);
2226 /**************************************************************
2227 *** Calculating sizes and locations became a bit confusing ***
2228 *** so I moved this into a separate function. ***
2229 **************************************************************/
2230 if(graph_size_location(im,i,piechart)==-1)
2231 return -1;
2233 /* the actual graph is created by going through the individual
2234 graph elements and then drawing them */
2236 node=gfx_new_area ( im->canvas,
2237 0, 0,
2238 im->ximg, 0,
2239 im->ximg, im->yimg,
2240 im->graph_col[GRC_BACK]);
2242 gfx_add_point(node,0, im->yimg);
2244 if (piechart != 2) {
2245 node=gfx_new_area ( im->canvas,
2246 im->xorigin, im->yorigin,
2247 im->xorigin + im->xsize, im->yorigin,
2248 im->xorigin + im->xsize, im->yorigin-im->ysize,
2249 im->graph_col[GRC_CANVAS]);
2251 gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
2253 if (im->minval > 0.0)
2254 areazero = im->minval;
2255 if (im->maxval < 0.0)
2256 areazero = im->maxval;
2258 axis_paint(im);
2259 }
2261 if (piechart) {
2262 pie_part(im,im->graph_col[GRC_CANVAS],im->pie_x,im->pie_y,im->piesize*0.5,0,2*M_PI);
2263 }
2265 for(i=0;i<im->gdes_c;i++){
2266 switch(im->gdes[i].gf){
2267 case GF_CDEF:
2268 case GF_VDEF:
2269 case GF_DEF:
2270 case GF_PRINT:
2271 case GF_GPRINT:
2272 case GF_COMMENT:
2273 case GF_HRULE:
2274 case GF_VRULE:
2275 break;
2276 case GF_TICK:
2277 for (ii = 0; ii < im->xsize; ii++)
2278 {
2279 if (!isnan(im->gdes[i].p_data[ii]) &&
2280 im->gdes[i].p_data[ii] > 0.0)
2281 {
2282 /* generate a tick */
2283 gfx_new_line(im->canvas, im -> xorigin + ii,
2284 im -> yorigin - (im -> gdes[i].yrule * im -> ysize),
2285 im -> xorigin + ii,
2286 im -> yorigin,
2287 1.0,
2288 im -> gdes[i].col );
2289 }
2290 }
2291 break;
2292 case GF_LINE:
2293 case GF_AREA:
2294 stack_gf = im->gdes[i].gf;
2295 case GF_STACK:
2296 /* fix data points at oo and -oo */
2297 for(ii=0;ii<im->xsize;ii++){
2298 if (isinf(im->gdes[i].p_data[ii])){
2299 if (im->gdes[i].p_data[ii] > 0) {
2300 im->gdes[i].p_data[ii] = im->maxval ;
2301 } else {
2302 im->gdes[i].p_data[ii] = im->minval ;
2303 }
2305 }
2306 } /* for */
2308 if (im->gdes[i].col != 0x0){
2309 /* GF_LINE and friend */
2310 if(stack_gf == GF_LINE ){
2311 node = NULL;
2312 for(ii=1;ii<im->xsize;ii++){
2313 if ( ! isnan(im->gdes[i].p_data[ii-1])
2314 && ! isnan(im->gdes[i].p_data[ii])){
2315 if (node == NULL){
2316 node = gfx_new_line(im->canvas,
2317 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2318 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2319 im->gdes[i].linewidth,
2320 im->gdes[i].col);
2321 } else {
2322 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2323 }
2324 } else {
2325 node = NULL;
2326 }
2327 }
2328 } else {
2329 int area_start=-1;
2330 node = NULL;
2331 for(ii=1;ii<im->xsize;ii++){
2332 /* open an area */
2333 if ( ! isnan(im->gdes[i].p_data[ii-1])
2334 && ! isnan(im->gdes[i].p_data[ii])){
2335 if (node == NULL){
2336 float ybase = 0.0;
2337 if (im->gdes[i].gf == GF_STACK) {
2338 ybase = ytr(im,lastgdes->p_data[ii-1]);
2339 } else {
2340 ybase = ytr(im,areazero);
2341 }
2342 area_start = ii-1;
2343 node = gfx_new_area(im->canvas,
2344 ii-1+im->xorigin,ybase,
2345 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2346 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2347 im->gdes[i].col
2348 );
2349 } else {
2350 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2351 }
2352 }
2354 if ( node != NULL && (ii+1==im->xsize || isnan(im->gdes[i].p_data[ii]) )){
2355 /* GF_AREA STACK type*/
2356 if (im->gdes[i].gf == GF_STACK ) {
2357 int iii;
2358 for (iii=ii-1;iii>area_start;iii--){
2359 gfx_add_point(node,iii+im->xorigin,ytr(im,lastgdes->p_data[iii]));
2360 }
2361 } else {
2362 gfx_add_point(node,ii+im->xorigin,ytr(im,areazero));
2363 };
2364 node=NULL;
2365 };
2366 }
2367 } /* else GF_LINE */
2368 } /* if color != 0x0 */
2369 /* make sure we do not run into trouble when stacking on NaN */
2370 for(ii=0;ii<im->xsize;ii++){
2371 if (isnan(im->gdes[i].p_data[ii])) {
2372 double ybase = 0.0;
2373 if (lastgdes) {
2374 ybase = ytr(im,lastgdes->p_data[ii-1]);
2375 };
2376 if (isnan(ybase) || !lastgdes ){
2377 ybase = ytr(im,areazero);
2378 }
2379 im->gdes[i].p_data[ii] = ybase;
2380 }
2381 }
2382 lastgdes = &(im->gdes[i]);
2383 break;
2384 case GF_PART:
2385 if(isnan(im->gdes[i].yrule)) /* fetch variable */
2386 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2388 if (finite(im->gdes[i].yrule)) { /* even the fetched var can be NaN */
2389 pie_part(im,im->gdes[i].col,
2390 im->pie_x,im->pie_y,im->piesize*0.4,
2391 M_PI*2.0*PieStart/100.0,
2392 M_PI*2.0*(PieStart+im->gdes[i].yrule)/100.0);
2393 PieStart += im->gdes[i].yrule;
2394 }
2395 break;
2396 } /* switch */
2397 }
2398 if (piechart==2) {
2399 im->draw_x_grid=0;
2400 im->draw_y_grid=0;
2401 }
2402 /* grid_paint also does the text */
2403 grid_paint(im);
2405 /* the RULES are the last thing to paint ... */
2406 for(i=0;i<im->gdes_c;i++){
2408 switch(im->gdes[i].gf){
2409 case GF_HRULE:
2410 if(isnan(im->gdes[i].yrule)) { /* fetch variable */
2411 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2412 };
2413 if(im->gdes[i].yrule >= im->minval
2414 && im->gdes[i].yrule <= im->maxval)
2415 gfx_new_line(im->canvas,
2416 im->xorigin,ytr(im,im->gdes[i].yrule),
2417 im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2418 1.0,im->gdes[i].col);
2419 break;
2420 case GF_VRULE:
2421 if(im->gdes[i].xrule == 0) { /* fetch variable */
2422 im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
2423 };
2424 if(im->gdes[i].xrule >= im->start
2425 && im->gdes[i].xrule <= im->end)
2426 gfx_new_line(im->canvas,
2427 xtr(im,im->gdes[i].xrule),im->yorigin,
2428 xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2429 1.0,im->gdes[i].col);
2430 break;
2431 default:
2432 break;
2433 }
2434 }
2437 if (strcmp(im->graphfile,"-")==0) {
2438 #ifdef WIN32
2439 /* Change translation mode for stdout to BINARY */
2440 _setmode( _fileno( stdout ), O_BINARY );
2441 #endif
2442 fo = stdout;
2443 } else {
2444 if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2445 rrd_set_error("Opening '%s' for write: %s",im->graphfile,
2446 strerror(errno));
2447 return (-1);
2448 }
2449 }
2450 gfx_render (im->canvas,im->ximg,im->yimg,0x0,fo);
2451 if (strcmp(im->graphfile,"-") != 0)
2452 fclose(fo);
2453 return 0;
2454 }
2457 /*****************************************************
2458 * graph stuff
2459 *****************************************************/
2461 int
2462 gdes_alloc(image_desc_t *im){
2464 long def_step = (im->end-im->start)/im->xsize;
2466 if (im->step > def_step) /* step can be increassed ... no decreassed */
2467 def_step = im->step;
2469 im->gdes_c++;
2471 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2472 * sizeof(graph_desc_t)))==NULL){
2473 rrd_set_error("realloc graph_descs");
2474 return -1;
2475 }
2478 im->gdes[im->gdes_c-1].step=def_step;
2479 im->gdes[im->gdes_c-1].start=im->start;
2480 im->gdes[im->gdes_c-1].end=im->end;
2481 im->gdes[im->gdes_c-1].vname[0]='\0';
2482 im->gdes[im->gdes_c-1].data=NULL;
2483 im->gdes[im->gdes_c-1].ds_namv=NULL;
2484 im->gdes[im->gdes_c-1].data_first=0;
2485 im->gdes[im->gdes_c-1].p_data=NULL;
2486 im->gdes[im->gdes_c-1].rpnp=NULL;
2487 im->gdes[im->gdes_c-1].col = 0x0;
2488 im->gdes[im->gdes_c-1].legend[0]='\0';
2489 im->gdes[im->gdes_c-1].rrd[0]='\0';
2490 im->gdes[im->gdes_c-1].ds=-1;
2491 im->gdes[im->gdes_c-1].p_data=NULL;
2492 return 0;
2493 }
2495 /* copies input untill the first unescaped colon is found
2496 or until input ends. backslashes have to be escaped as well */
2497 int
2498 scan_for_col(char *input, int len, char *output)
2499 {
2500 int inp,outp=0;
2501 for (inp=0;
2502 inp < len &&
2503 input[inp] != ':' &&
2504 input[inp] != '\0';
2505 inp++){
2506 if (input[inp] == '\\' &&
2507 input[inp+1] != '\0' &&
2508 (input[inp+1] == '\\' ||
2509 input[inp+1] == ':')){
2510 output[outp++] = input[++inp];
2511 }
2512 else {
2513 output[outp++] = input[inp];
2514 }
2515 }
2516 output[outp] = '\0';
2517 return inp;
2518 }
2520 /* Some surgery done on this function, it became ridiculously big.
2521 ** Things moved:
2522 ** - initializing now in rrd_graph_init()
2523 ** - options parsing now in rrd_graph_options()
2524 ** - script parsing now in rrd_graph_script()
2525 */
2526 int
2527 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize)
2528 {
2529 image_desc_t im;
2531 #ifdef HAVE_TZSET
2532 tzset();
2533 #endif
2534 #ifdef HAVE_SETLOCALE
2535 setlocale(LC_TIME,"");
2536 #endif
2539 rrd_graph_init(&im);
2541 rrd_graph_options(argc,argv,&im);
2542 if (rrd_test_error()) return -1;
2544 if (strlen(argv[optind])>=MAXPATH) {
2545 rrd_set_error("filename (including path) too long");
2546 return -1;
2547 }
2548 strncpy(im.graphfile,argv[optind],MAXPATH-1);
2549 im.graphfile[MAXPATH-1]='\0';
2551 rrd_graph_script(argc,argv,&im);
2552 if (rrd_test_error()) return -1;
2554 /* Everything is now read and the actual work can start */
2556 (*prdata)=NULL;
2557 if (graph_paint(&im,prdata)==-1){
2558 im_free(&im);
2559 return -1;
2560 }
2562 /* The image is generated and needs to be output.
2563 ** Also, if needed, print a line with information about the image.
2564 */
2566 *xsize=im.ximg;
2567 *ysize=im.yimg;
2568 if (im.imginfo) {
2569 char *filename;
2570 if (!(*prdata)) {
2571 /* maybe prdata is not allocated yet ... lets do it now */
2572 if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
2573 rrd_set_error("malloc imginfo");
2574 return -1;
2575 };
2576 }
2577 if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
2578 ==NULL){
2579 rrd_set_error("malloc imginfo");
2580 return -1;
2581 }
2582 filename=im.graphfile+strlen(im.graphfile);
2583 while(filename > im.graphfile) {
2584 if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
2585 filename--;
2586 }
2588 sprintf((*prdata)[0],im.imginfo,filename,(long)(im.canvas->zoom*im.ximg),(long)(im.canvas->zoom*im.yimg));
2589 }
2590 im_free(&im);
2591 return 0;
2592 }
2594 void
2595 rrd_graph_init(image_desc_t *im)
2596 {
2597 int i;
2599 im->xlab_user.minsec = -1;
2600 im->ximg=0;
2601 im->yimg=0;
2602 im->xsize = 400;
2603 im->ysize = 100;
2604 im->step = 0;
2605 im->ylegend[0] = '\0';
2606 im->title[0] = '\0';
2607 im->minval = DNAN;
2608 im->maxval = DNAN;
2609 im->unitsexponent= 9999;
2610 im->extra_flags= 0;
2611 im->rigid = 0;
2612 im->gridfit = 1;
2613 im->imginfo = NULL;
2614 im->lazy = 0;
2615 im->logarithmic = 0;
2616 im->ygridstep = DNAN;
2617 im->draw_x_grid = 1;
2618 im->draw_y_grid = 1;
2619 im->base = 1000;
2620 im->prt_c = 0;
2621 im->gdes_c = 0;
2622 im->gdes = NULL;
2623 im->canvas = gfx_new_canvas();
2624 im->grid_dash_on = 1;
2625 im->grid_dash_off = 1;
2627 for(i=0;i<DIM(graph_col);i++)
2628 im->graph_col[i]=graph_col[i];
2630 for(i=0;i<DIM(text_prop);i++){
2631 im->text_prop[i].size = text_prop[i].size;
2632 im->text_prop[i].font = text_prop[i].font;
2633 }
2634 }
2636 void
2637 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
2638 {
2639 int stroff;
2640 char *parsetime_error = NULL;
2641 char scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
2642 time_t start_tmp=0,end_tmp=0;
2643 long long_tmp;
2644 struct time_value start_tv, end_tv;
2645 gfx_color_t color;
2647 parsetime("end-24h", &start_tv);
2648 parsetime("now", &end_tv);
2650 while (1){
2651 static struct option long_options[] =
2652 {
2653 {"start", required_argument, 0, 's'},
2654 {"end", required_argument, 0, 'e'},
2655 {"x-grid", required_argument, 0, 'x'},
2656 {"y-grid", required_argument, 0, 'y'},
2657 {"vertical-label",required_argument,0,'v'},
2658 {"width", required_argument, 0, 'w'},
2659 {"height", required_argument, 0, 'h'},
2660 {"interlaced", no_argument, 0, 'i'},
2661 {"upper-limit",required_argument, 0, 'u'},
2662 {"lower-limit",required_argument, 0, 'l'},
2663 {"rigid", no_argument, 0, 'r'},
2664 {"base", required_argument, 0, 'b'},
2665 {"logarithmic",no_argument, 0, 'o'},
2666 {"color", required_argument, 0, 'c'},
2667 {"font", required_argument, 0, 'n'},
2668 {"title", required_argument, 0, 't'},
2669 {"imginfo", required_argument, 0, 'f'},
2670 {"imgformat", required_argument, 0, 'a'},
2671 {"lazy", no_argument, 0, 'z'},
2672 {"zoom", required_argument, 0, 'm'},
2673 {"no-legend", no_argument, 0, 'g'},
2674 {"alt-y-grid", no_argument, 0, 257 },
2675 {"alt-autoscale", no_argument, 0, 258 },
2676 {"alt-autoscale-max", no_argument, 0, 259 },
2677 {"units-exponent",required_argument, 0, 260},
2678 {"step", required_argument, 0, 261},
2679 {"no-gridfit", no_argument, 0, 262},
2680 {0,0,0,0}};
2681 int option_index = 0;
2682 int opt;
2685 opt = getopt_long(argc, argv,
2686 "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:z:g",
2687 long_options, &option_index);
2689 if (opt == EOF)
2690 break;
2692 switch(opt) {
2693 case 257:
2694 im->extra_flags |= ALTYGRID;
2695 break;
2696 case 258:
2697 im->extra_flags |= ALTAUTOSCALE;
2698 break;
2699 case 259:
2700 im->extra_flags |= ALTAUTOSCALE_MAX;
2701 break;
2702 case 'g':
2703 im->extra_flags |= NOLEGEND;
2704 break;
2705 case 260:
2706 im->unitsexponent = atoi(optarg);
2707 break;
2708 case 261:
2709 im->step = atoi(optarg);
2710 break;
2711 case 262:
2712 im->gridfit = 0;
2713 break;
2714 case 's':
2715 if ((parsetime_error = parsetime(optarg, &start_tv))) {
2716 rrd_set_error( "start time: %s", parsetime_error );
2717 return;
2718 }
2719 break;
2720 case 'e':
2721 if ((parsetime_error = parsetime(optarg, &end_tv))) {
2722 rrd_set_error( "end time: %s", parsetime_error );
2723 return;
2724 }
2725 break;
2726 case 'x':
2727 if(strcmp(optarg,"none") == 0){
2728 im->draw_x_grid=0;
2729 break;
2730 };
2732 if(sscanf(optarg,
2733 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
2734 scan_gtm,
2735 &im->xlab_user.gridst,
2736 scan_mtm,
2737 &im->xlab_user.mgridst,
2738 scan_ltm,
2739 &im->xlab_user.labst,
2740 &im->xlab_user.precis,
2741 &stroff) == 7 && stroff != 0){
2742 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
2743 if((im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
2744 rrd_set_error("unknown keyword %s",scan_gtm);
2745 return;
2746 } else if ((im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
2747 rrd_set_error("unknown keyword %s",scan_mtm);
2748 return;
2749 } else if ((im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
2750 rrd_set_error("unknown keyword %s",scan_ltm);
2751 return;
2752 }
2753 im->xlab_user.minsec = 1;
2754 im->xlab_user.stst = im->xlab_form;
2755 } else {
2756 rrd_set_error("invalid x-grid format");
2757 return;
2758 }
2759 break;
2760 case 'y':
2762 if(strcmp(optarg,"none") == 0){
2763 im->draw_y_grid=0;
2764 break;
2765 };
2767 if(sscanf(optarg,
2768 "%lf:%d",
2769 &im->ygridstep,
2770 &im->ylabfact) == 2) {
2771 if(im->ygridstep<=0){
2772 rrd_set_error("grid step must be > 0");
2773 return;
2774 } else if (im->ylabfact < 1){
2775 rrd_set_error("label factor must be > 0");
2776 return;
2777 }
2778 } else {
2779 rrd_set_error("invalid y-grid format");
2780 return;
2781 }
2782 break;
2783 case 'v':
2784 strncpy(im->ylegend,optarg,150);
2785 im->ylegend[150]='\0';
2786 break;
2787 case 'u':
2788 im->maxval = atof(optarg);
2789 break;
2790 case 'l':
2791 im->minval = atof(optarg);
2792 break;
2793 case 'b':
2794 im->base = atol(optarg);
2795 if(im->base != 1024 && im->base != 1000 ){
2796 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
2797 return;
2798 }
2799 break;
2800 case 'w':
2801 long_tmp = atol(optarg);
2802 if (long_tmp < 10) {
2803 rrd_set_error("width below 10 pixels");
2804 return;
2805 }
2806 im->xsize = long_tmp;
2807 break;
2808 case 'h':
2809 long_tmp = atol(optarg);
2810 if (long_tmp < 10) {
2811 rrd_set_error("height below 10 pixels");
2812 return;
2813 }
2814 im->ysize = long_tmp;
2815 break;
2816 case 'i':
2817 im->canvas->interlaced = 1;
2818 break;
2819 case 'r':
2820 im->rigid = 1;
2821 break;
2822 case 'f':
2823 im->imginfo = optarg;
2824 break;
2825 case 'a':
2826 if((im->canvas->imgformat = if_conv(optarg)) == -1) {
2827 rrd_set_error("unsupported graphics format '%s'",optarg);
2828 return;
2829 }
2830 break;
2831 case 'z':
2832 im->lazy = 1;
2833 break;
2834 case 'o':
2835 im->logarithmic = 1;
2836 if (isnan(im->minval))
2837 im->minval=1;
2838 break;
2839 case 'c':
2840 if(sscanf(optarg,
2841 "%10[A-Z]#%8lx",
2842 col_nam,&color) == 2){
2843 int ci;
2844 if((ci=grc_conv(col_nam)) != -1){
2845 im->graph_col[ci]=color;
2846 } else {
2847 rrd_set_error("invalid color name '%s'",col_nam);
2848 }
2849 } else {
2850 rrd_set_error("invalid color def format");
2851 return;
2852 }
2853 break;
2854 case 'n':{
2855 /* originally this used char *prop = "" and
2856 ** char *font = "dummy" however this results
2857 ** in a SEG fault, at least on RH7.1
2858 **
2859 ** The current implementation isn't proper
2860 ** either, font is never freed and prop uses
2861 ** a fixed width string
2862 */
2863 char prop[100];
2864 double size = 1;
2865 char *font;
2867 font=malloc(255);
2868 if(sscanf(optarg,
2869 "%10[A-Z]:%lf:%s",
2870 prop,&size,font) == 3){
2871 int sindex;
2872 if((sindex=text_prop_conv(prop)) != -1){
2873 im->text_prop[sindex].size=size;
2874 im->text_prop[sindex].font=font;
2875 if (sindex==0) { /* the default */
2876 im->text_prop[TEXT_PROP_TITLE].size=size;
2877 im->text_prop[TEXT_PROP_TITLE].font=font;
2878 im->text_prop[TEXT_PROP_AXIS].size=size;
2879 im->text_prop[TEXT_PROP_AXIS].font=font;
2880 im->text_prop[TEXT_PROP_UNIT].size=size;
2881 im->text_prop[TEXT_PROP_UNIT].font=font;
2882 im->text_prop[TEXT_PROP_LEGEND].size=size;
2883 im->text_prop[TEXT_PROP_LEGEND].font=font;
2884 }
2885 } else {
2886 rrd_set_error("invalid fonttag '%s'",prop);
2887 return;
2888 }
2889 } else {
2890 rrd_set_error("invalid text property format");
2891 return;
2892 }
2893 break;
2894 }
2895 case 'm':
2896 im->canvas->zoom = atof(optarg);
2897 if (im->canvas->zoom <= 0.0) {
2898 rrd_set_error("zoom factor must be > 0");
2899 return;
2900 }
2901 break;
2902 case 't':
2903 strncpy(im->title,optarg,150);
2904 im->title[150]='\0';
2905 break;
2907 case '?':
2908 if (optopt != 0)
2909 rrd_set_error("unknown option '%c'", optopt);
2910 else
2911 rrd_set_error("unknown option '%s'",argv[optind-1]);
2912 return;
2913 }
2914 }
2916 if (optind >= argc) {
2917 rrd_set_error("missing filename");
2918 return;
2919 }
2921 if (im->logarithmic == 1 && (im->minval <= 0 || isnan(im->minval))){
2922 rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");
2923 return;
2924 }
2926 if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
2927 /* error string is set in parsetime.c */
2928 return;
2929 }
2931 if (start_tmp < 3600*24*365*10){
2932 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
2933 return;
2934 }
2936 if (end_tmp < start_tmp) {
2937 rrd_set_error("start (%ld) should be less than end (%ld)",
2938 start_tmp, end_tmp);
2939 return;
2940 }
2942 im->start = start_tmp;
2943 im->end = end_tmp;
2944 }
2946 void
2947 rrd_graph_script(int argc, char *argv[], image_desc_t *im)
2948 {
2949 int i;
2950 char symname[100];
2951 int linepass = 0; /* stack must follow LINE*, AREA or STACK */
2953 for (i=optind+1;i<argc;i++) {
2954 int argstart=0;
2955 int strstart=0;
2956 graph_desc_t *gdp;
2957 char *line;
2958 char funcname[10],vname[MAX_VNAME_LEN+1],sep[1];
2959 double d;
2960 double linewidth;
2961 int j,k,l,m;
2963 /* Each command is one element from *argv[], we call this "line".
2964 **
2965 ** Each command defines the most current gdes inside struct im.
2966 ** In stead of typing "im->gdes[im->gdes_c-1]" we use "gdp".
2967 */
2968 gdes_alloc(im);
2969 gdp=&im->gdes[im->gdes_c-1];
2970 line=argv[i];
2972 /* function:newvname=string[:ds-name:CF] for xDEF
2973 ** function:vname[#color[:string]] for LINEx,AREA,STACK
2974 ** function:vname#color[:num[:string]] for TICK
2975 ** function:vname-or-num#color[:string] for xRULE,PART
2976 ** function:vname:CF:string for xPRINT
2977 ** function:string for COMMENT
2978 */
2979 argstart=0;
2981 sscanf(line, "%10[A-Z0-9]:%n", funcname,&argstart);
2982 if (argstart==0) {
2983 rrd_set_error("Cannot parse function in line: %s",line);
2984 im_free(im);
2985 return;
2986 }
2987 if(sscanf(funcname,"LINE%lf",&linewidth)){
2988 im->gdes[im->gdes_c-1].gf = GF_LINE;
2989 im->gdes[im->gdes_c-1].linewidth = linewidth;
2990 } else {
2991 if ((gdp->gf=gf_conv(funcname))==-1) {
2992 rrd_set_error("'%s' is not a valid function name",funcname);
2993 im_free(im);
2994 return;
2995 }
2996 }
2998 /* If the error string is set, we exit at the end of the switch */
2999 switch (gdp->gf) {
3000 case GF_COMMENT:
3001 if (rrd_graph_legend(gdp,&line[argstart])==0)
3002 rrd_set_error("Cannot parse comment in line: %s",line);
3003 break;
3004 case GF_PART:
3005 case GF_VRULE:
3006 case GF_HRULE:
3007 j=k=l=m=0;
3008 sscanf(&line[argstart], "%lf%n#%n", &d, &j, &k);
3009 sscanf(&line[argstart], DEF_NAM_FMT "%n#%n", vname, &l, &m);
3010 if (k+m==0) {
3011 rrd_set_error("Cannot parse name or num in line: %s",line);
3012 break;
3013 }
3014 if (j!=0) {
3015 gdp->xrule=d;
3016 gdp->yrule=d;
3017 argstart+=j;
3018 } else if (!rrd_graph_check_vname(im,vname,line)) {
3019 gdp->xrule=0;
3020 gdp->yrule=DNAN;
3021 argstart+=l;
3022 } else break; /* exit due to wrong vname */
3023 if ((j=rrd_graph_color(im,&line[argstart],line,0))==0) break;
3024 argstart+=j;
3025 if (strlen(&line[argstart])!=0) {
3026 if (rrd_graph_legend(gdp,&line[++argstart])==0)
3027 rrd_set_error("Cannot parse comment in line: %s",line);
3028 }
3029 break;
3030 case GF_STACK:
3031 if (linepass==0) {
3032 rrd_set_error("STACK must follow another graphing element");
3033 break;
3034 }
3035 case GF_LINE:
3036 case GF_AREA:
3037 case GF_TICK:
3038 j=k=0;
3039 linepass=1;
3040 sscanf(&line[argstart],DEF_NAM_FMT"%n%1[#:]%n",vname,&j,sep,&k);
3041 if (j+1!=k)
3042 rrd_set_error("Cannot parse vname in line: %s",line);
3043 else if (rrd_graph_check_vname(im,vname,line))
3044 rrd_set_error("Undefined vname '%s' in line: %s",line);
3045 else
3046 k=rrd_graph_color(im,&line[argstart],line,1);
3047 if (rrd_test_error()) break;
3048 argstart=argstart+j+k;
3049 if ((strlen(&line[argstart])!=0)&&(gdp->gf==GF_TICK)) {
3050 j=0;
3051 sscanf(&line[argstart], ":%lf%n", &gdp->yrule,&j);
3052 argstart+=j;
3053 }
3054 if (strlen(&line[argstart])!=0)
3055 if (rrd_graph_legend(gdp,&line[++argstart])==0)
3056 rrd_set_error("Cannot parse legend in line: %s",line);
3057 break;
3058 case GF_PRINT:
3059 im->prt_c++;
3060 case GF_GPRINT:
3061 j=0;
3062 sscanf(&line[argstart], DEF_NAM_FMT ":%n",gdp->vname,&j);
3063 if (j==0) {
3064 rrd_set_error("Cannot parse vname in line: '%s'",line);
3065 break;
3066 }
3067 argstart+=j;
3068 if (rrd_graph_check_vname(im,gdp->vname,line)) return;
3069 j=0;
3070 sscanf(&line[argstart], CF_NAM_FMT ":%n",symname,&j);
3072 k=(j!=0)?rrd_graph_check_CF(im,symname,line):1;
3073 #define VIDX im->gdes[gdp->vidx]
3074 switch (k) {
3075 case -1: /* looks CF but is not really CF */
3076 if (VIDX.gf == GF_VDEF) rrd_clear_error();
3077 break;
3078 case 0: /* CF present and correct */
3079 if (VIDX.gf == GF_VDEF)
3080 rrd_set_error("Don't use CF when printing VDEF");
3081 argstart+=j;
3082 break;
3083 case 1: /* CF not present */
3084 if (VIDX.gf == GF_VDEF) rrd_clear_error();
3085 else rrd_set_error("Printing DEF or CDEF needs CF");
3086 break;
3087 default:
3088 rrd_set_error("Oops, bug in GPRINT scanning");
3089 }
3090 #undef VIDX
3091 if (rrd_test_error()) break;
3093 if (strlen(&line[argstart])!=0) {
3094 if (rrd_graph_legend(gdp,&line[argstart])==0)
3095 rrd_set_error("Cannot parse legend in line: %s",line);
3096 } else rrd_set_error("No legend in (G)PRINT line: %s",line);
3097 strcpy(gdp->format, gdp->legend);
3098 break;
3099 case GF_DEF:
3100 case GF_VDEF:
3101 case GF_CDEF:
3102 j=0;
3103 sscanf(&line[argstart], DEF_NAM_FMT "=%n",gdp->vname,&j);
3104 if (j==0) {
3105 rrd_set_error("Could not parse line: %s",line);
3106 break;
3107 }
3108 if (find_var(im,gdp->vname)!=-1) {
3109 rrd_set_error("Variable '%s' in line '%s' already in use\n",
3110 gdp->vname,line);
3111 break;
3112 }
3113 argstart+=j;
3114 switch (gdp->gf) {
3115 case GF_DEF:
3116 argstart+=scan_for_col(&line[argstart],MAXPATH,gdp->rrd);
3117 j=k=0;
3118 sscanf(&line[argstart],
3119 ":" DS_NAM_FMT ":" CF_NAM_FMT "%n%*s%n",
3120 gdp->ds_nam, symname, &j, &k);
3121 if ((j==0)||(k!=0)) {
3122 rrd_set_error("Cannot parse DS or CF in '%s'",line);
3123 break;
3124 }
3125 rrd_graph_check_CF(im,symname,line);
3126 break;
3127 case GF_VDEF:
3128 j=0;
3129 sscanf(&line[argstart],DEF_NAM_FMT ",%n",vname,&j);
3130 if (j==0) {
3131 rrd_set_error("Cannot parse vname in line '%s'",line);
3132 break;
3133 }
3134 argstart+=j;
3135 if (rrd_graph_check_vname(im,vname,line)) return;
3136 if ( im->gdes[gdp->vidx].gf != GF_DEF
3137 && im->gdes[gdp->vidx].gf != GF_CDEF) {
3138 rrd_set_error("variable '%s' not DEF nor "
3139 "CDEF in VDEF '%s'", vname,gdp->vname);
3140 break;
3141 }
3142 vdef_parse(gdp,&line[argstart+strstart]);
3143 break;
3144 case GF_CDEF:
3145 if (strstr(&line[argstart],":")!=NULL) {
3146 rrd_set_error("Error in RPN, line: %s",line);
3147 break;
3148 }
3149 if ((gdp->rpnp = rpn_parse(
3150 (void *)im,
3151 &line[argstart],
3152 &find_var_wrapper)
3153 )==NULL)
3154 rrd_set_error("invalid rpn expression in: %s",line);
3155 break;
3156 default: break;
3157 }
3158 break;
3159 default: rrd_set_error("Big oops");
3160 }
3161 if (rrd_test_error()) {
3162 im_free(im);
3163 return;
3164 }
3165 }
3167 if (im->gdes_c==0){
3168 rrd_set_error("can't make a graph without contents");
3169 im_free(im); /* ??? is this set ??? */
3170 return;
3171 }
3172 }
3173 int
3174 rrd_graph_check_vname(image_desc_t *im, char *varname, char *err)
3175 {
3176 if ((im->gdes[im->gdes_c-1].vidx=find_var(im,varname))==-1) {
3177 rrd_set_error("Unknown variable '%s' in %s",varname,err);
3178 return -1;
3179 }
3180 return 0;
3181 }
3182 int
3183 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
3184 {
3185 char *color;
3186 graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
3188 color=strstr(var,"#");
3189 if (color==NULL) {
3190 if (optional==0) {
3191 rrd_set_error("Found no color in %s",err);
3192 return 0;
3193 }
3194 return 0;
3195 } else {
3196 int n=0;
3197 char *rest;
3198 gfx_color_t col;
3200 rest=strstr(color,":");
3201 if (rest!=NULL)
3202 n=rest-color;
3203 else
3204 n=strlen(color);
3206 switch (n) {
3207 case 7:
3208 sscanf(color,"#%6lx%n",&col,&n);
3209 col = (col << 8) + 0xff /* shift left by 8 */;
3210 if (n!=7) rrd_set_error("Color problem in %s",err);
3211 break;
3212 case 9:
3213 sscanf(color,"#%8lx%n",&col,&n);
3214 if (n==9) break;
3215 default:
3216 rrd_set_error("Color problem in %s",err);
3217 }
3218 if (rrd_test_error()) return 0;
3219 gdp->col = col;
3220 return n;
3221 }
3222 }
3223 int
3224 rrd_graph_check_CF(image_desc_t *im, char *symname, char *err)
3225 {
3226 if ((im->gdes[im->gdes_c-1].cf=cf_conv(symname))==-1) {
3227 rrd_set_error("Unknown CF '%s' in %s",symname,err);
3228 return -1;
3229 }
3230 return 0;
3231 }
3232 int
3233 rrd_graph_legend(graph_desc_t *gdp, char *line)
3234 {
3235 int i;
3237 i=scan_for_col(line,FMT_LEG_LEN,gdp->legend);
3239 return (strlen(&line[i])==0);
3240 }
3243 int bad_format(char *fmt) {
3244 char *ptr;
3245 int n=0;
3247 ptr = fmt;
3248 while (*ptr != '\0') {
3249 if (*ptr == '%') {ptr++;
3250 if (*ptr == '\0') return 1;
3251 while ((*ptr >= '0' && *ptr <= '9') || *ptr == '.') {
3252 ptr++;
3253 }
3254 if (*ptr == '\0') return 1;
3255 if (*ptr == 'l') {
3256 ptr++;
3257 n++;
3258 if (*ptr == '\0') return 1;
3259 if (*ptr == 'e' || *ptr == 'f') {
3260 ptr++;
3261 } else { return 1; }
3262 }
3263 else if (*ptr == 's' || *ptr == 'S' || *ptr == '%') { ++ptr; }
3264 else { return 1; }
3265 } else {
3266 ++ptr;
3267 }
3268 }
3269 return (n!=1);
3270 }
3271 int
3272 vdef_parse(gdes,str)
3273 struct graph_desc_t *gdes;
3274 char *str;
3275 {
3276 /* A VDEF currently is either "func" or "param,func"
3277 * so the parsing is rather simple. Change if needed.
3278 */
3279 double param;
3280 char func[30];
3281 int n;
3283 n=0;
3284 sscanf(str,"%le,%29[A-Z]%n",¶m,func,&n);
3285 if (n==strlen(str)) { /* matched */
3286 ;
3287 } else {
3288 n=0;
3289 sscanf(str,"%29[A-Z]%n",func,&n);
3290 if (n==strlen(str)) { /* matched */
3291 param=DNAN;
3292 } else {
3293 rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3294 ,str
3295 ,gdes->vname
3296 );
3297 return -1;
3298 }
3299 }
3300 if (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3301 else if (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3302 else if (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3303 else if (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3304 else if (!strcmp("TOTAL", func)) gdes->vf.op = VDEF_TOTAL;
3305 else if (!strcmp("FIRST", func)) gdes->vf.op = VDEF_FIRST;
3306 else if (!strcmp("LAST", func)) gdes->vf.op = VDEF_LAST;
3307 else {
3308 rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3309 ,func
3310 ,gdes->vname
3311 );
3312 return -1;
3313 };
3315 switch (gdes->vf.op) {
3316 case VDEF_PERCENT:
3317 if (isnan(param)) { /* no parameter given */
3318 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3319 ,func
3320 ,gdes->vname
3321 );
3322 return -1;
3323 };
3324 if (param>=0.0 && param<=100.0) {
3325 gdes->vf.param = param;
3326 gdes->vf.val = DNAN; /* undefined */
3327 gdes->vf.when = 0; /* undefined */
3328 } else {
3329 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3330 ,param
3331 ,gdes->vname
3332 );
3333 return -1;
3334 };
3335 break;
3336 case VDEF_MAXIMUM:
3337 case VDEF_AVERAGE:
3338 case VDEF_MINIMUM:
3339 case VDEF_TOTAL:
3340 case VDEF_FIRST:
3341 case VDEF_LAST:
3342 if (isnan(param)) {
3343 gdes->vf.param = DNAN;
3344 gdes->vf.val = DNAN;
3345 gdes->vf.when = 0;
3346 } else {
3347 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3348 ,func
3349 ,gdes->vname
3350 );
3351 return -1;
3352 };
3353 break;
3354 };
3355 return 0;
3356 }
3357 int
3358 vdef_calc(im,gdi)
3359 image_desc_t *im;
3360 int gdi;
3361 {
3362 graph_desc_t *src,*dst;
3363 rrd_value_t *data;
3364 long step,steps;
3366 dst = &im->gdes[gdi];
3367 src = &im->gdes[dst->vidx];
3368 data = src->data + src->ds;
3369 steps = (src->end - src->start) / src->step;
3371 #if 0
3372 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3373 ,src->start
3374 ,src->end
3375 ,steps
3376 );
3377 #endif
3379 switch (dst->vf.op) {
3380 case VDEF_PERCENT: {
3381 rrd_value_t * array;
3382 int field;
3385 if ((array = malloc(steps*sizeof(double)))==NULL) {
3386 rrd_set_error("malloc VDEV_PERCENT");
3387 return -1;
3388 }
3389 for (step=0;step < steps; step++) {
3390 array[step]=data[step*src->ds_cnt];
3391 }
3392 qsort(array,step,sizeof(double),vdef_percent_compar);
3394 field = (steps-1)*dst->vf.param/100;
3395 dst->vf.val = array[field];
3396 dst->vf.when = 0; /* no time component */
3397 #if 0
3398 for(step=0;step<steps;step++)
3399 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3400 #endif
3401 }
3402 break;
3403 case VDEF_MAXIMUM:
3404 step=0;
3405 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3406 if (step == steps) {
3407 dst->vf.val = DNAN;
3408 dst->vf.when = 0;
3409 } else {
3410 dst->vf.val = data[step*src->ds_cnt];
3411 dst->vf.when = src->start + (step+1)*src->step;
3412 }
3413 while (step != steps) {
3414 if (finite(data[step*src->ds_cnt])) {
3415 if (data[step*src->ds_cnt] > dst->vf.val) {
3416 dst->vf.val = data[step*src->ds_cnt];
3417 dst->vf.when = src->start + (step+1)*src->step;
3418 }
3419 }
3420 step++;
3421 }
3422 break;
3423 case VDEF_TOTAL:
3424 case VDEF_AVERAGE: {
3425 int cnt=0;
3426 double sum=0.0;
3427 for (step=0;step<steps;step++) {
3428 if (finite(data[step*src->ds_cnt])) {
3429 sum += data[step*src->ds_cnt];
3430 cnt ++;
3431 };
3432 }
3433 if (cnt) {
3434 if (dst->vf.op == VDEF_TOTAL) {
3435 dst->vf.val = sum*src->step;
3436 dst->vf.when = cnt*src->step; /* not really "when" */
3437 } else {
3438 dst->vf.val = sum/cnt;
3439 dst->vf.when = 0; /* no time component */
3440 };
3441 } else {
3442 dst->vf.val = DNAN;
3443 dst->vf.when = 0;
3444 }
3445 }
3446 break;
3447 case VDEF_MINIMUM:
3448 step=0;
3449 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3450 if (step == steps) {
3451 dst->vf.val = DNAN;
3452 dst->vf.when = 0;
3453 } else {
3454 dst->vf.val = data[step*src->ds_cnt];
3455 dst->vf.when = src->start + (step+1)*src->step;
3456 }
3457 while (step != steps) {
3458 if (finite(data[step*src->ds_cnt])) {
3459 if (data[step*src->ds_cnt] < dst->vf.val) {
3460 dst->vf.val = data[step*src->ds_cnt];
3461 dst->vf.when = src->start + (step+1)*src->step;
3462 }
3463 }
3464 step++;
3465 }
3466 break;
3467 case VDEF_FIRST:
3468 /* The time value returned here is one step before the
3469 * actual time value. This is the start of the first
3470 * non-NaN interval.
3471 */
3472 step=0;
3473 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3474 if (step == steps) { /* all entries were NaN */
3475 dst->vf.val = DNAN;
3476 dst->vf.when = 0;
3477 } else {
3478 dst->vf.val = data[step*src->ds_cnt];
3479 dst->vf.when = src->start + step*src->step;
3480 }
3481 break;
3482 case VDEF_LAST:
3483 /* The time value returned here is the
3484 * actual time value. This is the end of the last
3485 * non-NaN interval.
3486 */
3487 step=steps-1;
3488 while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3489 if (step < 0) { /* all entries were NaN */
3490 dst->vf.val = DNAN;
3491 dst->vf.when = 0;
3492 } else {
3493 dst->vf.val = data[step*src->ds_cnt];
3494 dst->vf.when = src->start + (step+1)*src->step;
3495 }
3496 break;
3497 }
3498 return 0;
3499 }
3501 /* NaN < -INF < finite_values < INF */
3502 int
3503 vdef_percent_compar(a,b)
3504 const void *a,*b;
3505 {
3506 /* Equality is not returned; this doesn't hurt except
3507 * (maybe) for a little performance.
3508 */
3510 /* First catch NaN values. They are smallest */
3511 if (isnan( *(double *)a )) return -1;
3512 if (isnan( *(double *)b )) return 1;
3514 /* NaN doesn't reach this part so INF and -INF are extremes.
3515 * The sign from isinf() is compatible with the sign we return
3516 */
3517 if (isinf( *(double *)a )) return isinf( *(double *)a );
3518 if (isinf( *(double *)b )) return isinf( *(double *)b );
3520 /* If we reach this, both values must be finite */
3521 if ( *(double *)a < *(double *)b ) return -1; else return 1;
3522 }