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