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