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