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