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