1 /****************************************************************************
2 * RRDtool 1.2.6 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)))) - 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 /* no sensiblevalues found. we switch to ALTYGRID mode */
436 if (sensiblevalues[i] == 0){
437 im->extra_flags |= ALTYGRID;
438 }
439 }
440 } else {
441 /* adjust min and max to the grid definition if there is one */
442 im->minval = (double)im->ylabfact * im->ygridstep *
443 floor(im->minval / ((double)im->ylabfact * im->ygridstep));
444 im->maxval = (double)im->ylabfact * im->ygridstep *
445 ceil(im->maxval /( (double)im->ylabfact * im->ygridstep));
446 }
448 #ifdef DEBUG
449 fprintf(stderr,"SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
450 im->minval,im->maxval,im->magfact);
451 #endif
452 }
454 void
455 apply_gridfit(image_desc_t *im)
456 {
457 if (isnan(im->minval) || isnan(im->maxval))
458 return;
459 ytr(im,DNAN);
460 if (im->logarithmic) {
461 double ya, yb, ypix, ypixfrac;
462 double log10_range = log10(im->maxval) - log10(im->minval);
463 ya = pow((double)10, floor(log10(im->minval)));
464 while (ya < im->minval)
465 ya *= 10;
466 if (ya > im->maxval)
467 return; /* don't have y=10^x gridline */
468 yb = ya * 10;
469 if (yb <= im->maxval) {
470 /* we have at least 2 y=10^x gridlines.
471 Make sure distance between them in pixels
472 are an integer by expanding im->maxval */
473 double y_pixel_delta = ytr(im, ya) - ytr(im, yb);
474 double factor = y_pixel_delta / floor(y_pixel_delta);
475 double new_log10_range = factor * log10_range;
476 double new_ymax_log10 = log10(im->minval) + new_log10_range;
477 im->maxval = pow(10, new_ymax_log10);
478 ytr(im, DNAN); /* reset precalc */
479 log10_range = log10(im->maxval) - log10(im->minval);
480 }
481 /* make sure first y=10^x gridline is located on
482 integer pixel position by moving scale slightly
483 downwards (sub-pixel movement) */
484 ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
485 ypixfrac = ypix - floor(ypix);
486 if (ypixfrac > 0 && ypixfrac < 1) {
487 double yfrac = ypixfrac / im->ysize;
488 im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
489 im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
490 ytr(im, DNAN); /* reset precalc */
491 }
492 } else {
493 /* Make sure we have an integer pixel distance between
494 each minor gridline */
495 double ypos1 = ytr(im, im->minval);
496 double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
497 double y_pixel_delta = ypos1 - ypos2;
498 double factor = y_pixel_delta / floor(y_pixel_delta);
499 double new_range = factor * (im->maxval - im->minval);
500 double gridstep = im->ygrid_scale.gridstep;
501 double minor_y, minor_y_px, minor_y_px_frac;
502 im->maxval = im->minval + new_range;
503 ytr(im, DNAN); /* reset precalc */
504 /* make sure first minor gridline is on integer pixel y coord */
505 minor_y = gridstep * floor(im->minval / gridstep);
506 while (minor_y < im->minval)
507 minor_y += gridstep;
508 minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
509 minor_y_px_frac = minor_y_px - floor(minor_y_px);
510 if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
511 double yfrac = minor_y_px_frac / im->ysize;
512 double range = im->maxval - im->minval;
513 im->minval = im->minval - yfrac * range;
514 im->maxval = im->maxval - yfrac * range;
515 ytr(im, DNAN); /* reset precalc */
516 }
517 calc_horizontal_grid(im); /* recalc with changed im->maxval */
518 }
519 }
521 /* reduce data reimplementation by Alex */
523 void
524 reduce_data(
525 enum cf_en cf, /* which consolidation function ?*/
526 unsigned long cur_step, /* step the data currently is in */
527 time_t *start, /* start, end and step as requested ... */
528 time_t *end, /* ... by the application will be ... */
529 unsigned long *step, /* ... adjusted to represent reality */
530 unsigned long *ds_cnt, /* number of data sources in file */
531 rrd_value_t **data) /* two dimensional array containing the data */
532 {
533 int i,reduce_factor = ceil((double)(*step) / (double)cur_step);
534 unsigned long col,dst_row,row_cnt,start_offset,end_offset,skiprows=0;
535 rrd_value_t *srcptr,*dstptr;
537 (*step) = cur_step*reduce_factor; /* set new step size for reduced data */
538 dstptr = *data;
539 srcptr = *data;
540 row_cnt = ((*end)-(*start))/cur_step;
542 #ifdef DEBUG
543 #define DEBUG_REDUCE
544 #endif
545 #ifdef DEBUG_REDUCE
546 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
547 row_cnt,reduce_factor,*start,*end,cur_step);
548 for (col=0;col<row_cnt;col++) {
549 printf("time %10lu: ",*start+(col+1)*cur_step);
550 for (i=0;i<*ds_cnt;i++)
551 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
552 printf("\n");
553 }
554 #endif
556 /* We have to combine [reduce_factor] rows of the source
557 ** into one row for the destination. Doing this we also
558 ** need to take care to combine the correct rows. First
559 ** alter the start and end time so that they are multiples
560 ** of the new step time. We cannot reduce the amount of
561 ** time so we have to move the end towards the future and
562 ** the start towards the past.
563 */
564 end_offset = (*end) % (*step);
565 start_offset = (*start) % (*step);
567 /* If there is a start offset (which cannot be more than
568 ** one destination row), skip the appropriate number of
569 ** source rows and one destination row. The appropriate
570 ** number is what we do know (start_offset/cur_step) of
571 ** the new interval (*step/cur_step aka reduce_factor).
572 */
573 #ifdef DEBUG_REDUCE
574 printf("start_offset: %lu end_offset: %lu\n",start_offset,end_offset);
575 printf("row_cnt before: %lu\n",row_cnt);
576 #endif
577 if (start_offset) {
578 (*start) = (*start)-start_offset;
579 skiprows=reduce_factor-start_offset/cur_step;
580 srcptr+=skiprows* *ds_cnt;
581 for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
582 row_cnt-=skiprows;
583 }
584 #ifdef DEBUG_REDUCE
585 printf("row_cnt between: %lu\n",row_cnt);
586 #endif
588 /* At the end we have some rows that are not going to be
589 ** used, the amount is end_offset/cur_step
590 */
591 if (end_offset) {
592 (*end) = (*end)-end_offset+(*step);
593 skiprows = end_offset/cur_step;
594 row_cnt-=skiprows;
595 }
596 #ifdef DEBUG_REDUCE
597 printf("row_cnt after: %lu\n",row_cnt);
598 #endif
600 /* Sanity check: row_cnt should be multiple of reduce_factor */
601 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
603 if (row_cnt%reduce_factor) {
604 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
605 row_cnt,reduce_factor);
606 printf("BUG in reduce_data()\n");
607 exit(1);
608 }
610 /* Now combine reduce_factor intervals at a time
611 ** into one interval for the destination.
612 */
614 for (dst_row=0;(long int)row_cnt>=reduce_factor;dst_row++) {
615 for (col=0;col<(*ds_cnt);col++) {
616 rrd_value_t newval=DNAN;
617 unsigned long validval=0;
619 for (i=0;i<reduce_factor;i++) {
620 if (isnan(srcptr[i*(*ds_cnt)+col])) {
621 continue;
622 }
623 validval++;
624 if (isnan(newval)) newval = srcptr[i*(*ds_cnt)+col];
625 else {
626 switch (cf) {
627 case CF_HWPREDICT:
628 case CF_DEVSEASONAL:
629 case CF_DEVPREDICT:
630 case CF_SEASONAL:
631 case CF_AVERAGE:
632 newval += srcptr[i*(*ds_cnt)+col];
633 break;
634 case CF_MINIMUM:
635 newval = min (newval,srcptr[i*(*ds_cnt)+col]);
636 break;
637 case CF_FAILURES:
638 /* an interval contains a failure if any subintervals contained a failure */
639 case CF_MAXIMUM:
640 newval = max (newval,srcptr[i*(*ds_cnt)+col]);
641 break;
642 case CF_LAST:
643 newval = srcptr[i*(*ds_cnt)+col];
644 break;
645 }
646 }
647 }
648 if (validval == 0){newval = DNAN;} else{
649 switch (cf) {
650 case CF_HWPREDICT:
651 case CF_DEVSEASONAL:
652 case CF_DEVPREDICT:
653 case CF_SEASONAL:
654 case CF_AVERAGE:
655 newval /= validval;
656 break;
657 case CF_MINIMUM:
658 case CF_FAILURES:
659 case CF_MAXIMUM:
660 case CF_LAST:
661 break;
662 }
663 }
664 *dstptr++=newval;
665 }
666 srcptr+=(*ds_cnt)*reduce_factor;
667 row_cnt-=reduce_factor;
668 }
669 /* If we had to alter the endtime, we didn't have enough
670 ** source rows to fill the last row. Fill it with NaN.
671 */
672 if (end_offset) for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
673 #ifdef DEBUG_REDUCE
674 row_cnt = ((*end)-(*start))/ *step;
675 srcptr = *data;
676 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
677 row_cnt,*start,*end,*step);
678 for (col=0;col<row_cnt;col++) {
679 printf("time %10lu: ",*start+(col+1)*(*step));
680 for (i=0;i<*ds_cnt;i++)
681 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
682 printf("\n");
683 }
684 #endif
685 }
688 /* get the data required for the graphs from the
689 relevant rrds ... */
691 int
692 data_fetch(image_desc_t *im )
693 {
694 int i,ii;
695 int skip;
697 /* pull the data from the log files ... */
698 for (i=0;i< (int)im->gdes_c;i++){
699 /* only GF_DEF elements fetch data */
700 if (im->gdes[i].gf != GF_DEF)
701 continue;
703 skip=0;
704 /* do we have it already ?*/
705 for (ii=0;ii<i;ii++) {
706 if (im->gdes[ii].gf != GF_DEF)
707 continue;
708 if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
709 && (im->gdes[i].cf == im->gdes[ii].cf)
710 && (im->gdes[i].cf_reduce == im->gdes[ii].cf_reduce)
711 && (im->gdes[i].start == im->gdes[ii].start)
712 && (im->gdes[i].end == im->gdes[ii].end)
713 && (im->gdes[i].step == im->gdes[ii].step)) {
714 /* OK, the data is already there.
715 ** Just copy the header portion
716 */
717 im->gdes[i].start = im->gdes[ii].start;
718 im->gdes[i].end = im->gdes[ii].end;
719 im->gdes[i].step = im->gdes[ii].step;
720 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
721 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
722 im->gdes[i].data = im->gdes[ii].data;
723 im->gdes[i].data_first = 0;
724 skip=1;
725 }
726 if (skip)
727 break;
728 }
729 if (! skip) {
730 unsigned long ft_step = im->gdes[i].step ;
732 if((rrd_fetch_fn(im->gdes[i].rrd,
733 im->gdes[i].cf,
734 &im->gdes[i].start,
735 &im->gdes[i].end,
736 &ft_step,
737 &im->gdes[i].ds_cnt,
738 &im->gdes[i].ds_namv,
739 &im->gdes[i].data)) == -1){
740 return -1;
741 }
742 im->gdes[i].data_first = 1;
743 im->gdes[i].step = im->step;
745 if (ft_step < im->gdes[i].step) {
746 reduce_data(im->gdes[i].cf_reduce,
747 ft_step,
748 &im->gdes[i].start,
749 &im->gdes[i].end,
750 &im->gdes[i].step,
751 &im->gdes[i].ds_cnt,
752 &im->gdes[i].data);
753 } else {
754 im->gdes[i].step = ft_step;
755 }
756 }
758 /* lets see if the required data source is really there */
759 for(ii=0;ii<(int)im->gdes[i].ds_cnt;ii++){
760 if(strcmp(im->gdes[i].ds_namv[ii],im->gdes[i].ds_nam) == 0){
761 im->gdes[i].ds=ii; }
762 }
763 if (im->gdes[i].ds== -1){
764 rrd_set_error("No DS called '%s' in '%s'",
765 im->gdes[i].ds_nam,im->gdes[i].rrd);
766 return -1;
767 }
769 }
770 return 0;
771 }
773 /* evaluate the expressions in the CDEF functions */
775 /*************************************************************
776 * CDEF stuff
777 *************************************************************/
779 long
780 find_var_wrapper(void *arg1, char *key)
781 {
782 return find_var((image_desc_t *) arg1, key);
783 }
785 /* find gdes containing var*/
786 long
787 find_var(image_desc_t *im, char *key){
788 long ii;
789 for(ii=0;ii<im->gdes_c-1;ii++){
790 if((im->gdes[ii].gf == GF_DEF
791 || im->gdes[ii].gf == GF_VDEF
792 || im->gdes[ii].gf == GF_CDEF)
793 && (strcmp(im->gdes[ii].vname,key) == 0)){
794 return ii;
795 }
796 }
797 return -1;
798 }
800 /* find the largest common denominator for all the numbers
801 in the 0 terminated num array */
802 long
803 lcd(long *num){
804 long rest;
805 int i;
806 for (i=0;num[i+1]!=0;i++){
807 do {
808 rest=num[i] % num[i+1];
809 num[i]=num[i+1]; num[i+1]=rest;
810 } while (rest!=0);
811 num[i+1] = num[i];
812 }
813 /* return i==0?num[i]:num[i-1]; */
814 return num[i];
815 }
817 /* run the rpn calculator on all the VDEF and CDEF arguments */
818 int
819 data_calc( image_desc_t *im){
821 int gdi;
822 int dataidx;
823 long *steparray, rpi;
824 int stepcnt;
825 time_t now;
826 rpnstack_t rpnstack;
828 rpnstack_init(&rpnstack);
830 for (gdi=0;gdi<im->gdes_c;gdi++){
831 /* Look for GF_VDEF and GF_CDEF in the same loop,
832 * so CDEFs can use VDEFs and vice versa
833 */
834 switch (im->gdes[gdi].gf) {
835 case GF_XPORT:
836 break;
837 case GF_SHIFT: {
838 graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx];
840 /* remove current shift */
841 vdp->start -= vdp->shift;
842 vdp->end -= vdp->shift;
844 /* vdef */
845 if (im->gdes[gdi].shidx >= 0)
846 vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
847 /* constant */
848 else
849 vdp->shift = im->gdes[gdi].shval;
851 /* normalize shift to multiple of consolidated step */
852 vdp->shift = (vdp->shift / (long)vdp->step) * (long)vdp->step;
854 /* apply shift */
855 vdp->start += vdp->shift;
856 vdp->end += vdp->shift;
857 break;
858 }
859 case GF_VDEF:
860 /* A VDEF has no DS. This also signals other parts
861 * of rrdtool that this is a VDEF value, not a CDEF.
862 */
863 im->gdes[gdi].ds_cnt = 0;
864 if (vdef_calc(im,gdi)) {
865 rrd_set_error("Error processing VDEF '%s'"
866 ,im->gdes[gdi].vname
867 );
868 rpnstack_free(&rpnstack);
869 return -1;
870 }
871 break;
872 case GF_CDEF:
873 im->gdes[gdi].ds_cnt = 1;
874 im->gdes[gdi].ds = 0;
875 im->gdes[gdi].data_first = 1;
876 im->gdes[gdi].start = 0;
877 im->gdes[gdi].end = 0;
878 steparray=NULL;
879 stepcnt = 0;
880 dataidx=-1;
882 /* Find the variables in the expression.
883 * - VDEF variables are substituted by their values
884 * and the opcode is changed into OP_NUMBER.
885 * - CDEF variables are analized for their step size,
886 * the lowest common denominator of all the step
887 * sizes of the data sources involved is calculated
888 * and the resulting number is the step size for the
889 * resulting data source.
890 */
891 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
892 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
893 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER){
894 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
895 if (im->gdes[ptr].ds_cnt == 0) { /* this is a VDEF data source */
896 #if 0
897 printf("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
898 im->gdes[gdi].vname,
899 im->gdes[ptr].vname);
900 printf("DEBUG: value from vdef is %f\n",im->gdes[ptr].vf.val);
901 #endif
902 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
903 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
904 } else { /* normal variables and PREF(variables) */
906 /* add one entry to the array that keeps track of the step sizes of the
907 * data sources going into the CDEF. */
908 if ((steparray =
909 rrd_realloc(steparray,
910 (++stepcnt+1)*sizeof(*steparray)))==NULL){
911 rrd_set_error("realloc steparray");
912 rpnstack_free(&rpnstack);
913 return -1;
914 };
916 steparray[stepcnt-1] = im->gdes[ptr].step;
918 /* adjust start and end of cdef (gdi) so
919 * that it runs from the latest start point
920 * to the earliest endpoint of any of the
921 * rras involved (ptr)
922 */
924 if(im->gdes[gdi].start < im->gdes[ptr].start)
925 im->gdes[gdi].start = im->gdes[ptr].start;
927 if(im->gdes[gdi].end == 0 ||
928 im->gdes[gdi].end > im->gdes[ptr].end)
929 im->gdes[gdi].end = im->gdes[ptr].end;
931 /* store pointer to the first element of
932 * the rra providing data for variable,
933 * further save step size and data source
934 * count of this rra
935 */
936 im->gdes[gdi].rpnp[rpi].data = im->gdes[ptr].data + im->gdes[ptr].ds;
937 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
938 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
940 /* backoff the *.data ptr; this is done so
941 * rpncalc() function doesn't have to treat
942 * the first case differently
943 */
944 } /* if ds_cnt != 0 */
945 } /* if OP_VARIABLE */
946 } /* loop through all rpi */
948 /* move the data pointers to the correct period */
949 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
950 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
951 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER){
952 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
953 long diff = im->gdes[gdi].start - im->gdes[ptr].start;
955 if(diff > 0)
956 im->gdes[gdi].rpnp[rpi].data += (diff / im->gdes[ptr].step) * im->gdes[ptr].ds_cnt;
957 }
958 }
960 if(steparray == NULL){
961 rrd_set_error("rpn expressions without DEF"
962 " or CDEF variables are not supported");
963 rpnstack_free(&rpnstack);
964 return -1;
965 }
966 steparray[stepcnt]=0;
967 /* Now find the resulting step. All steps in all
968 * used RRAs have to be visited
969 */
970 im->gdes[gdi].step = lcd(steparray);
971 free(steparray);
972 if((im->gdes[gdi].data = malloc((
973 (im->gdes[gdi].end-im->gdes[gdi].start)
974 / im->gdes[gdi].step)
975 * sizeof(double)))==NULL){
976 rrd_set_error("malloc im->gdes[gdi].data");
977 rpnstack_free(&rpnstack);
978 return -1;
979 }
981 /* Step through the new cdef results array and
982 * calculate the values
983 */
984 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
985 now<=im->gdes[gdi].end;
986 now += im->gdes[gdi].step)
987 {
988 rpnp_t *rpnp = im -> gdes[gdi].rpnp;
990 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
991 * in this case we are advancing by timesteps;
992 * we use the fact that time_t is a synonym for long
993 */
994 if (rpn_calc(rpnp,&rpnstack,(long) now,
995 im->gdes[gdi].data,++dataidx) == -1) {
996 /* rpn_calc sets the error string */
997 rpnstack_free(&rpnstack);
998 return -1;
999 }
1000 } /* enumerate over time steps within a CDEF */
1001 break;
1002 default:
1003 continue;
1004 }
1005 } /* enumerate over CDEFs */
1006 rpnstack_free(&rpnstack);
1007 return 0;
1008 }
1010 /* massage data so, that we get one value for each x coordinate in the graph */
1011 int
1012 data_proc( image_desc_t *im ){
1013 long i,ii;
1014 double pixstep = (double)(im->end-im->start)
1015 /(double)im->xsize; /* how much time
1016 passes in one pixel */
1017 double paintval;
1018 double minval=DNAN,maxval=DNAN;
1020 unsigned long gr_time;
1022 /* memory for the processed data */
1023 for(i=0;i<im->gdes_c;i++) {
1024 if((im->gdes[i].gf==GF_LINE) ||
1025 (im->gdes[i].gf==GF_AREA) ||
1026 (im->gdes[i].gf==GF_TICK) ||
1027 (im->gdes[i].gf==GF_STACK)) {
1028 if((im->gdes[i].p_data = malloc((im->xsize +1)
1029 * sizeof(rrd_value_t)))==NULL){
1030 rrd_set_error("malloc data_proc");
1031 return -1;
1032 }
1033 }
1034 }
1036 for (i=0;i<im->xsize;i++) { /* for each pixel */
1037 long vidx;
1038 gr_time = im->start+pixstep*i; /* time of the current step */
1039 paintval=0.0;
1041 for (ii=0;ii<im->gdes_c;ii++) {
1042 double value;
1043 switch (im->gdes[ii].gf) {
1044 case GF_LINE:
1045 case GF_AREA:
1046 case GF_TICK:
1047 if (!im->gdes[ii].stack)
1048 paintval = 0.0;
1049 case GF_STACK:
1050 value = im->gdes[ii].yrule;
1051 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1052 /* The time of the data doesn't necessarily match
1053 ** the time of the graph. Beware.
1054 */
1055 vidx = im->gdes[ii].vidx;
1056 if (im->gdes[vidx].gf == GF_VDEF) {
1057 value = im->gdes[vidx].vf.val;
1058 } else if (((long int)gr_time >= (long int)im->gdes[vidx].start) &&
1059 ((long int)gr_time <= (long int)im->gdes[vidx].end) ) {
1060 value = im->gdes[vidx].data[
1061 (unsigned long) floor(
1062 (double)(gr_time - im->gdes[vidx].start)
1063 / im->gdes[vidx].step)
1064 * im->gdes[vidx].ds_cnt
1065 + im->gdes[vidx].ds
1066 ];
1067 } else {
1068 value = DNAN;
1069 }
1070 };
1072 if (! isnan(value)) {
1073 paintval += value;
1074 im->gdes[ii].p_data[i] = paintval;
1075 /* GF_TICK: the data values are not
1076 ** relevant for min and max
1077 */
1078 if (finite(paintval) && im->gdes[ii].gf != GF_TICK ) {
1079 if (isnan(minval) || paintval < minval)
1080 minval = paintval;
1081 if (isnan(maxval) || paintval > maxval)
1082 maxval = paintval;
1083 }
1084 } else {
1085 im->gdes[ii].p_data[i] = DNAN;
1086 }
1087 break;
1088 default:
1089 break;
1090 }
1091 }
1092 }
1094 /* if min or max have not been asigned a value this is because
1095 there was no data in the graph ... this is not good ...
1096 lets set these to dummy values then ... */
1098 if (isnan(minval)) minval = 0.0;
1099 if (isnan(maxval)) maxval = 1.0;
1101 /* adjust min and max values */
1102 if (isnan(im->minval)
1103 /* don't adjust low-end with log scale */
1104 || ((!im->logarithmic && !im->rigid) && im->minval > minval)
1105 )
1106 im->minval = minval;
1107 if (isnan(im->maxval)
1108 || (!im->rigid && im->maxval < maxval)
1109 ) {
1110 if (im->logarithmic)
1111 im->maxval = maxval * 1.1;
1112 else
1113 im->maxval = maxval;
1114 }
1115 /* make sure min is smaller than max */
1116 if (im->minval > im->maxval) {
1117 im->minval = 0.99 * im->maxval;
1118 }
1120 /* make sure min and max are not equal */
1121 if (im->minval == im->maxval) {
1122 im->maxval *= 1.01;
1123 if (! im->logarithmic) {
1124 im->minval *= 0.99;
1125 }
1126 /* make sure min and max are not both zero */
1127 if (im->maxval == 0.0) {
1128 im->maxval = 1.0;
1129 }
1130 }
1131 return 0;
1132 }
1136 /* identify the point where the first gridline, label ... gets placed */
1138 time_t
1139 find_first_time(
1140 time_t start, /* what is the initial time */
1141 enum tmt_en baseint, /* what is the basic interval */
1142 long basestep /* how many if these do we jump a time */
1143 )
1144 {
1145 struct tm tm;
1146 localtime_r(&start, &tm);
1147 switch(baseint){
1148 case TMT_SECOND:
1149 tm.tm_sec -= tm.tm_sec % basestep; break;
1150 case TMT_MINUTE:
1151 tm.tm_sec=0;
1152 tm.tm_min -= tm.tm_min % basestep;
1153 break;
1154 case TMT_HOUR:
1155 tm.tm_sec=0;
1156 tm.tm_min = 0;
1157 tm.tm_hour -= tm.tm_hour % basestep; break;
1158 case TMT_DAY:
1159 /* we do NOT look at the basestep for this ... */
1160 tm.tm_sec=0;
1161 tm.tm_min = 0;
1162 tm.tm_hour = 0; break;
1163 case TMT_WEEK:
1164 /* we do NOT look at the basestep for this ... */
1165 tm.tm_sec=0;
1166 tm.tm_min = 0;
1167 tm.tm_hour = 0;
1168 tm.tm_mday -= tm.tm_wday -1; /* -1 because we want the monday */
1169 if (tm.tm_wday==0) tm.tm_mday -= 7; /* we want the *previous* monday */
1170 break;
1171 case TMT_MONTH:
1172 tm.tm_sec=0;
1173 tm.tm_min = 0;
1174 tm.tm_hour = 0;
1175 tm.tm_mday = 1;
1176 tm.tm_mon -= tm.tm_mon % basestep; break;
1178 case TMT_YEAR:
1179 tm.tm_sec=0;
1180 tm.tm_min = 0;
1181 tm.tm_hour = 0;
1182 tm.tm_mday = 1;
1183 tm.tm_mon = 0;
1184 tm.tm_year -= (tm.tm_year+1900) % basestep;
1186 }
1187 return mktime(&tm);
1188 }
1189 /* identify the point where the next gridline, label ... gets placed */
1190 time_t
1191 find_next_time(
1192 time_t current, /* what is the initial time */
1193 enum tmt_en baseint, /* what is the basic interval */
1194 long basestep /* how many if these do we jump a time */
1195 )
1196 {
1197 struct tm tm;
1198 time_t madetime;
1199 localtime_r(¤t, &tm);
1200 do {
1201 switch(baseint){
1202 case TMT_SECOND:
1203 tm.tm_sec += basestep; break;
1204 case TMT_MINUTE:
1205 tm.tm_min += basestep; break;
1206 case TMT_HOUR:
1207 tm.tm_hour += basestep; break;
1208 case TMT_DAY:
1209 tm.tm_mday += basestep; break;
1210 case TMT_WEEK:
1211 tm.tm_mday += 7*basestep; break;
1212 case TMT_MONTH:
1213 tm.tm_mon += basestep; break;
1214 case TMT_YEAR:
1215 tm.tm_year += basestep;
1216 }
1217 madetime = mktime(&tm);
1218 } while (madetime == -1); /* this is necessary to skip impssible times
1219 like the daylight saving time skips */
1220 return madetime;
1222 }
1225 /* calculate values required for PRINT and GPRINT functions */
1227 int
1228 print_calc(image_desc_t *im, char ***prdata)
1229 {
1230 long i,ii,validsteps;
1231 double printval;
1232 time_t printtime;
1233 int graphelement = 0;
1234 long vidx;
1235 int max_ii;
1236 double magfact = -1;
1237 char *si_symb = "";
1238 char *percent_s;
1239 int prlines = 1;
1240 if (im->imginfo) prlines++;
1241 for(i=0;i<im->gdes_c;i++){
1242 switch(im->gdes[i].gf){
1243 case GF_PRINT:
1244 prlines++;
1245 if(((*prdata) = rrd_realloc((*prdata),prlines*sizeof(char *)))==NULL){
1246 rrd_set_error("realloc prdata");
1247 return 0;
1248 }
1249 case GF_GPRINT:
1250 /* PRINT and GPRINT can now print VDEF generated values.
1251 * There's no need to do any calculations on them as these
1252 * calculations were already made.
1253 */
1254 vidx = im->gdes[i].vidx;
1255 if (im->gdes[vidx].gf==GF_VDEF) { /* simply use vals */
1256 printval = im->gdes[vidx].vf.val;
1257 printtime = im->gdes[vidx].vf.when;
1258 } else { /* need to calculate max,min,avg etcetera */
1259 max_ii =((im->gdes[vidx].end
1260 - im->gdes[vidx].start)
1261 / im->gdes[vidx].step
1262 * im->gdes[vidx].ds_cnt);
1263 printval = DNAN;
1264 validsteps = 0;
1265 for( ii=im->gdes[vidx].ds;
1266 ii < max_ii;
1267 ii+=im->gdes[vidx].ds_cnt){
1268 if (! finite(im->gdes[vidx].data[ii]))
1269 continue;
1270 if (isnan(printval)){
1271 printval = im->gdes[vidx].data[ii];
1272 validsteps++;
1273 continue;
1274 }
1276 switch (im->gdes[i].cf){
1277 case CF_HWPREDICT:
1278 case CF_DEVPREDICT:
1279 case CF_DEVSEASONAL:
1280 case CF_SEASONAL:
1281 case CF_AVERAGE:
1282 validsteps++;
1283 printval += im->gdes[vidx].data[ii];
1284 break;
1285 case CF_MINIMUM:
1286 printval = min( printval, im->gdes[vidx].data[ii]);
1287 break;
1288 case CF_FAILURES:
1289 case CF_MAXIMUM:
1290 printval = max( printval, im->gdes[vidx].data[ii]);
1291 break;
1292 case CF_LAST:
1293 printval = im->gdes[vidx].data[ii];
1294 }
1295 }
1296 if (im->gdes[i].cf==CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1297 if (validsteps > 1) {
1298 printval = (printval / validsteps);
1299 }
1300 }
1301 } /* prepare printval */
1303 if (!strcmp(im->gdes[i].format,"%c")) { /* VDEF time print */
1304 char ctime_buf[128]; /* PS: for ctime_r, must be >= 26 chars */
1305 int iii=0;
1306 ctime_r(&printtime,ctime_buf);
1307 while(isprint(ctime_buf[iii])){iii++;}
1308 ctime_buf[iii]='\0';
1309 if (im->gdes[i].gf == GF_PRINT){
1310 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1311 sprintf((*prdata)[prlines-2],"%s (%lu)",ctime_buf,printtime);
1312 (*prdata)[prlines-1] = NULL;
1313 } else {
1314 sprintf(im->gdes[i].legend,"%s (%lu)",ctime_buf,printtime);
1315 graphelement = 1;
1316 }
1317 } else {
1318 if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) {
1319 /* Magfact is set to -1 upon entry to print_calc. If it
1320 * is still less than 0, then we need to run auto_scale.
1321 * Otherwise, put the value into the correct units. If
1322 * the value is 0, then do not set the symbol or magnification
1323 * so next the calculation will be performed again. */
1324 if (magfact < 0.0) {
1325 auto_scale(im,&printval,&si_symb,&magfact);
1326 if (printval == 0.0)
1327 magfact = -1.0;
1328 } else {
1329 printval /= magfact;
1330 }
1331 *(++percent_s) = 's';
1332 } else if (strstr(im->gdes[i].format,"%s") != NULL) {
1333 auto_scale(im,&printval,&si_symb,&magfact);
1334 }
1336 if (im->gdes[i].gf == GF_PRINT){
1337 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1338 (*prdata)[prlines-1] = NULL;
1339 if (bad_format(im->gdes[i].format)) {
1340 rrd_set_error("bad format for PRINT in '%s'", im->gdes[i].format);
1341 return -1;
1342 }
1343 #ifdef HAVE_SNPRINTF
1344 snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
1345 #else
1346 sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
1347 #endif
1348 } else {
1349 /* GF_GPRINT */
1351 if (bad_format(im->gdes[i].format)) {
1352 rrd_set_error("bad format for GPRINT in '%s'", im->gdes[i].format);
1353 return -1;
1354 }
1355 #ifdef HAVE_SNPRINTF
1356 snprintf(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
1357 #else
1358 sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
1359 #endif
1360 graphelement = 1;
1361 }
1362 }
1363 break;
1364 case GF_LINE:
1365 case GF_AREA:
1366 case GF_TICK:
1367 case GF_STACK:
1368 case GF_HRULE:
1369 case GF_VRULE:
1370 graphelement = 1;
1371 break;
1372 case GF_COMMENT:
1373 case GF_DEF:
1374 case GF_CDEF:
1375 case GF_VDEF:
1376 #ifdef WITH_PIECHART
1377 case GF_PART:
1378 #endif
1379 case GF_SHIFT:
1380 case GF_XPORT:
1381 break;
1382 }
1383 }
1384 return graphelement;
1385 }
1388 /* place legends with color spots */
1389 int
1390 leg_place(image_desc_t *im)
1391 {
1392 /* graph labels */
1393 int interleg = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1394 int border = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1395 int fill=0, fill_last;
1396 int leg_c = 0;
1397 int leg_x = border, leg_y = im->yimg;
1398 int leg_cc;
1399 int glue = 0;
1400 int i,ii, mark = 0;
1401 char prt_fctn; /*special printfunctions */
1402 int *legspace;
1404 if( !(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH) ) {
1405 if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1406 rrd_set_error("malloc for legspace");
1407 return -1;
1408 }
1410 for(i=0;i<im->gdes_c;i++){
1411 fill_last = fill;
1413 /* hid legends for rules which are not displayed */
1415 if(!(im->extra_flags & FORCE_RULES_LEGEND)) {
1416 if (im->gdes[i].gf == GF_HRULE &&
1417 (im->gdes[i].yrule < im->minval || im->gdes[i].yrule > im->maxval))
1418 im->gdes[i].legend[0] = '\0';
1420 if (im->gdes[i].gf == GF_VRULE &&
1421 (im->gdes[i].xrule < im->start || im->gdes[i].xrule > im->end))
1422 im->gdes[i].legend[0] = '\0';
1423 }
1425 leg_cc = strlen(im->gdes[i].legend);
1427 /* is there a controle code ant the end of the legend string ? */
1428 /* and it is not a tab \\t */
1429 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\' && im->gdes[i].legend[leg_cc-1] != 't') {
1430 prt_fctn = im->gdes[i].legend[leg_cc-1];
1431 leg_cc -= 2;
1432 im->gdes[i].legend[leg_cc] = '\0';
1433 } else {
1434 prt_fctn = '\0';
1435 }
1436 /* remove exess space */
1437 while (prt_fctn=='g' &&
1438 leg_cc > 0 &&
1439 im->gdes[i].legend[leg_cc-1]==' '){
1440 leg_cc--;
1441 im->gdes[i].legend[leg_cc]='\0';
1442 }
1443 if (leg_cc != 0 ){
1444 legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1446 if (fill > 0){
1447 /* no interleg space if string ends in \g */
1448 fill += legspace[i];
1449 }
1450 fill += gfx_get_text_width(im->canvas, fill+border,
1451 im->text_prop[TEXT_PROP_LEGEND].font,
1452 im->text_prop[TEXT_PROP_LEGEND].size,
1453 im->tabwidth,
1454 im->gdes[i].legend, 0);
1455 leg_c++;
1456 } else {
1457 legspace[i]=0;
1458 }
1459 /* who said there was a special tag ... ?*/
1460 if (prt_fctn=='g') {
1461 prt_fctn = '\0';
1462 }
1463 if (prt_fctn == '\0') {
1464 if (i == im->gdes_c -1 ) prt_fctn ='l';
1466 /* is it time to place the legends ? */
1467 if (fill > im->ximg - 2*border){
1468 if (leg_c > 1) {
1469 /* go back one */
1470 i--;
1471 fill = fill_last;
1472 leg_c--;
1473 prt_fctn = 'j';
1474 } else {
1475 prt_fctn = 'l';
1476 }
1478 }
1479 }
1482 if (prt_fctn != '\0'){
1483 leg_x = border;
1484 if (leg_c >= 2 && prt_fctn == 'j') {
1485 glue = (im->ximg - fill - 2* border) / (leg_c-1);
1486 } else {
1487 glue = 0;
1488 }
1489 if (prt_fctn =='c') leg_x = (im->ximg - fill) / 2.0;
1490 if (prt_fctn =='r') leg_x = im->ximg - fill - border;
1492 for(ii=mark;ii<=i;ii++){
1493 if(im->gdes[ii].legend[0]=='\0')
1494 continue; /* skip empty legends */
1495 im->gdes[ii].leg_x = leg_x;
1496 im->gdes[ii].leg_y = leg_y;
1497 leg_x +=
1498 gfx_get_text_width(im->canvas, leg_x,
1499 im->text_prop[TEXT_PROP_LEGEND].font,
1500 im->text_prop[TEXT_PROP_LEGEND].size,
1501 im->tabwidth,
1502 im->gdes[ii].legend, 0)
1503 + legspace[ii]
1504 + glue;
1505 }
1506 leg_y += im->text_prop[TEXT_PROP_LEGEND].size*1.8;
1507 if (prt_fctn == 's') leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1508 fill = 0;
1509 leg_c = 0;
1510 mark = ii;
1511 }
1512 }
1513 im->yimg = leg_y;
1514 free(legspace);
1515 }
1516 return 0;
1517 }
1519 /* create a grid on the graph. it determines what to do
1520 from the values of xsize, start and end */
1522 /* the xaxis labels are determined from the number of seconds per pixel
1523 in the requested graph */
1527 int
1528 calc_horizontal_grid(image_desc_t *im)
1529 {
1530 double range;
1531 double scaledrange;
1532 int pixel,i;
1533 int gridind;
1534 int decimals, fractionals;
1536 im->ygrid_scale.labfact=2;
1537 gridind=-1;
1538 range = im->maxval - im->minval;
1539 scaledrange = range / im->magfact;
1541 /* does the scale of this graph make it impossible to put lines
1542 on it? If so, give up. */
1543 if (isnan(scaledrange)) {
1544 return 0;
1545 }
1547 /* find grid spaceing */
1548 pixel=1;
1549 if(isnan(im->ygridstep)){
1550 if(im->extra_flags & ALTYGRID) {
1551 /* find the value with max number of digits. Get number of digits */
1552 decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))));
1553 if(decimals <= 0) /* everything is small. make place for zero */
1554 decimals = 1;
1556 fractionals = floor(log10(range));
1557 if(fractionals < 0) { /* small amplitude. */
1558 int len = decimals - fractionals + 1;
1559 if (im->unitslength < len) im->unitslength = len;
1560 sprintf(im->ygrid_scale.labfmt, "%%%d.%df", len, -fractionals + 1);
1561 } else {
1562 int len = decimals + 1;
1563 if (im->unitslength < len) im->unitslength = len;
1564 sprintf(im->ygrid_scale.labfmt, "%%%d.1f", len);
1565 }
1566 im->ygrid_scale.gridstep = pow((double)10, (double)fractionals);
1567 if(im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1568 im->ygrid_scale.gridstep = 0.1;
1569 /* should have at least 5 lines but no more then 15 */
1570 if(range/im->ygrid_scale.gridstep < 5)
1571 im->ygrid_scale.gridstep /= 10;
1572 if(range/im->ygrid_scale.gridstep > 15)
1573 im->ygrid_scale.gridstep *= 10;
1574 if(range/im->ygrid_scale.gridstep > 5) {
1575 im->ygrid_scale.labfact = 1;
1576 if(range/im->ygrid_scale.gridstep > 8)
1577 im->ygrid_scale.labfact = 2;
1578 }
1579 else {
1580 im->ygrid_scale.gridstep /= 5;
1581 im->ygrid_scale.labfact = 5;
1582 }
1583 }
1584 else {
1585 for(i=0;ylab[i].grid > 0;i++){
1586 pixel = im->ysize / (scaledrange / ylab[i].grid);
1587 if (pixel > 7) {
1588 gridind = i;
1589 break;
1590 }
1591 }
1593 for(i=0; i<4;i++) {
1594 if (pixel * ylab[gridind].lfac[i] >= 2.5 * im->text_prop[TEXT_PROP_AXIS].size) {
1595 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1596 break;
1597 }
1598 }
1600 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1601 }
1602 } else {
1603 im->ygrid_scale.gridstep = im->ygridstep;
1604 im->ygrid_scale.labfact = im->ylabfact;
1605 }
1606 return 1;
1607 }
1609 int draw_horizontal_grid(image_desc_t *im)
1610 {
1611 int i;
1612 double scaledstep;
1613 char graph_label[100];
1614 double X0=im->xorigin;
1615 double X1=im->xorigin+im->xsize;
1617 int sgrid = (int)( im->minval / im->ygrid_scale.gridstep - 1);
1618 int egrid = (int)( im->maxval / im->ygrid_scale.gridstep + 1);
1619 double MaxY;
1620 scaledstep = im->ygrid_scale.gridstep/im->magfact;
1621 MaxY = scaledstep*(double)im->viewfactor*(double)egrid;
1622 for (i = sgrid; i <= egrid; i++){
1623 double Y0=ytr(im,im->ygrid_scale.gridstep*i);
1624 if ( Y0 >= im->yorigin-im->ysize
1625 && Y0 <= im->yorigin){
1626 if(i % im->ygrid_scale.labfact == 0){
1627 if (i==0 || im->symbol == ' ') {
1628 if(MaxY < 10) {
1629 if(im->extra_flags & ALTYGRID) {
1630 sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*im->viewfactor*i);
1631 }
1632 else {
1633 sprintf(graph_label,"%4.1f",scaledstep*im->viewfactor*i);
1634 }
1635 } else {
1636 sprintf(graph_label,"%4.0f",scaledstep*im->viewfactor*i);
1637 }
1638 }else {
1639 if(MaxY < 10){
1640 sprintf(graph_label,"%4.1f %c",scaledstep*im->viewfactor*i, im->symbol);
1641 } else {
1642 sprintf(graph_label,"%4.0f %c",scaledstep*im->viewfactor*i, im->symbol);
1643 }
1644 }
1646 gfx_new_text ( im->canvas,
1647 X0-im->text_prop[TEXT_PROP_AXIS].size, Y0,
1648 im->graph_col[GRC_FONT],
1649 im->text_prop[TEXT_PROP_AXIS].font,
1650 im->text_prop[TEXT_PROP_AXIS].size,
1651 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1652 graph_label );
1653 gfx_new_dashed_line ( im->canvas,
1654 X0-2,Y0,
1655 X1+2,Y0,
1656 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1657 im->grid_dash_on, im->grid_dash_off);
1659 } else if (!(im->extra_flags & NOMINOR)) {
1660 gfx_new_dashed_line ( im->canvas,
1661 X0-1,Y0,
1662 X1+1,Y0,
1663 GRIDWIDTH, im->graph_col[GRC_GRID],
1664 im->grid_dash_on, im->grid_dash_off);
1666 }
1667 }
1668 }
1669 return 1;
1670 }
1672 /* logaritmic horizontal grid */
1673 int
1674 horizontal_log_grid(image_desc_t *im)
1675 {
1676 double pixpex;
1677 int ii,i;
1678 int minoridx=0, majoridx=0;
1679 char graph_label[100];
1680 double X0,X1,Y0;
1681 double value, pixperstep, minstep;
1683 /* find grid spaceing */
1684 pixpex= (double)im->ysize / (log10(im->maxval) - log10(im->minval));
1686 if (isnan(pixpex)) {
1687 return 0;
1688 }
1690 for(i=0;yloglab[i][0] > 0;i++){
1691 minstep = log10(yloglab[i][0]);
1692 for(ii=1;yloglab[i][ii+1] > 0;ii++){
1693 if(yloglab[i][ii+2]==0){
1694 minstep = log10(yloglab[i][ii+1])-log10(yloglab[i][ii]);
1695 break;
1696 }
1697 }
1698 pixperstep = pixpex * minstep;
1699 if(pixperstep > 5){minoridx = i;}
1700 if(pixperstep > 2 * im->text_prop[TEXT_PROP_LEGEND].size){majoridx = i;}
1701 }
1703 X0=im->xorigin;
1704 X1=im->xorigin+im->xsize;
1705 /* paint minor grid */
1706 for (value = pow((double)10, log10(im->minval)
1707 - fmod(log10(im->minval),log10(yloglab[minoridx][0])));
1708 value <= im->maxval;
1709 value *= yloglab[minoridx][0]){
1710 if (value < im->minval) continue;
1711 i=0;
1712 while(yloglab[minoridx][++i] > 0){
1713 Y0 = ytr(im,value * yloglab[minoridx][i]);
1714 if (Y0 <= im->yorigin - im->ysize) break;
1715 gfx_new_dashed_line ( im->canvas,
1716 X0-1,Y0,
1717 X1+1,Y0,
1718 GRIDWIDTH, im->graph_col[GRC_GRID],
1719 im->grid_dash_on, im->grid_dash_off);
1720 }
1721 }
1723 /* paint major grid and labels*/
1724 for (value = pow((double)10, log10(im->minval)
1725 - fmod(log10(im->minval),log10(yloglab[majoridx][0])));
1726 value <= im->maxval;
1727 value *= yloglab[majoridx][0]){
1728 if (value < im->minval) continue;
1729 i=0;
1730 while(yloglab[majoridx][++i] > 0){
1731 Y0 = ytr(im,value * yloglab[majoridx][i]);
1732 if (Y0 <= im->yorigin - im->ysize) break;
1733 gfx_new_dashed_line ( im->canvas,
1734 X0-2,Y0,
1735 X1+2,Y0,
1736 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1737 im->grid_dash_on, im->grid_dash_off);
1739 sprintf(graph_label,"%3.0e",value * yloglab[majoridx][i]);
1740 gfx_new_text ( im->canvas,
1741 X0-im->text_prop[TEXT_PROP_AXIS].size, Y0,
1742 im->graph_col[GRC_FONT],
1743 im->text_prop[TEXT_PROP_AXIS].font,
1744 im->text_prop[TEXT_PROP_AXIS].size,
1745 im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1746 graph_label );
1747 }
1748 }
1749 return 1;
1750 }
1753 void
1754 vertical_grid(
1755 image_desc_t *im )
1756 {
1757 int xlab_sel; /* which sort of label and grid ? */
1758 time_t ti, tilab, timajor;
1759 long factor;
1760 char graph_label[100];
1761 double X0,Y0,Y1; /* points for filled graph and more*/
1762 struct tm tm;
1764 /* the type of time grid is determined by finding
1765 the number of seconds per pixel in the graph */
1768 if(im->xlab_user.minsec == -1){
1769 factor=(im->end - im->start)/im->xsize;
1770 xlab_sel=0;
1771 while ( xlab[xlab_sel+1].minsec != -1
1772 && xlab[xlab_sel+1].minsec <= factor){ xlab_sel++; }
1773 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
1774 im->xlab_user.gridst = xlab[xlab_sel].gridst;
1775 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
1776 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
1777 im->xlab_user.labtm = xlab[xlab_sel].labtm;
1778 im->xlab_user.labst = xlab[xlab_sel].labst;
1779 im->xlab_user.precis = xlab[xlab_sel].precis;
1780 im->xlab_user.stst = xlab[xlab_sel].stst;
1781 }
1783 /* y coords are the same for every line ... */
1784 Y0 = im->yorigin;
1785 Y1 = im->yorigin-im->ysize;
1788 /* paint the minor grid */
1789 if (!(im->extra_flags & NOMINOR))
1790 {
1791 for(ti = find_first_time(im->start,
1792 im->xlab_user.gridtm,
1793 im->xlab_user.gridst),
1794 timajor = find_first_time(im->start,
1795 im->xlab_user.mgridtm,
1796 im->xlab_user.mgridst);
1797 ti < im->end;
1798 ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
1799 ){
1800 /* are we inside the graph ? */
1801 if (ti < im->start || ti > im->end) continue;
1802 while (timajor < ti) {
1803 timajor = find_next_time(timajor,
1804 im->xlab_user.mgridtm, im->xlab_user.mgridst);
1805 }
1806 if (ti == timajor) continue; /* skip as falls on major grid line */
1807 X0 = xtr(im,ti);
1808 gfx_new_dashed_line(im->canvas,X0,Y0+1, X0,Y1-1,GRIDWIDTH,
1809 im->graph_col[GRC_GRID],
1810 im->grid_dash_on, im->grid_dash_off);
1812 }
1813 }
1815 /* paint the major grid */
1816 for(ti = find_first_time(im->start,
1817 im->xlab_user.mgridtm,
1818 im->xlab_user.mgridst);
1819 ti < im->end;
1820 ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
1821 ){
1822 /* are we inside the graph ? */
1823 if (ti < im->start || ti > im->end) continue;
1824 X0 = xtr(im,ti);
1825 gfx_new_dashed_line(im->canvas,X0,Y0+3, X0,Y1-2,MGRIDWIDTH,
1826 im->graph_col[GRC_MGRID],
1827 im->grid_dash_on, im->grid_dash_off);
1829 }
1830 /* paint the labels below the graph */
1831 for(ti = find_first_time(im->start - im->xlab_user.precis/2,
1832 im->xlab_user.labtm,
1833 im->xlab_user.labst);
1834 ti <= im->end - im->xlab_user.precis/2;
1835 ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
1836 ){
1837 tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
1838 /* are we inside the graph ? */
1839 if (tilab < im->start || tilab > im->end) continue;
1841 #if HAVE_STRFTIME
1842 localtime_r(&tilab, &tm);
1843 strftime(graph_label,99,im->xlab_user.stst, &tm);
1844 #else
1845 # error "your libc has no strftime I guess we'll abort the exercise here."
1846 #endif
1847 gfx_new_text ( im->canvas,
1848 xtr(im,tilab), Y0+im->text_prop[TEXT_PROP_AXIS].size,
1849 im->graph_col[GRC_FONT],
1850 im->text_prop[TEXT_PROP_AXIS].font,
1851 im->text_prop[TEXT_PROP_AXIS].size,
1852 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP,
1853 graph_label );
1855 }
1857 }
1860 void
1861 axis_paint(
1862 image_desc_t *im
1863 )
1864 {
1865 /* draw x and y axis */
1866 /* gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
1867 im->xorigin+im->xsize,im->yorigin-im->ysize,
1868 GRIDWIDTH, im->graph_col[GRC_AXIS]);
1870 gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
1871 im->xorigin+im->xsize,im->yorigin-im->ysize,
1872 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
1874 gfx_new_line ( im->canvas, im->xorigin-4,im->yorigin,
1875 im->xorigin+im->xsize+4,im->yorigin,
1876 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
1878 gfx_new_line ( im->canvas, im->xorigin,im->yorigin+4,
1879 im->xorigin,im->yorigin-im->ysize-4,
1880 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
1883 /* arrow for X and Y axis direction */
1884 gfx_new_area ( im->canvas,
1885 im->xorigin+im->xsize+2, im->yorigin-2,
1886 im->xorigin+im->xsize+2, im->yorigin+3,
1887 im->xorigin+im->xsize+7, im->yorigin+0.5, /* LINEOFFSET */
1888 im->graph_col[GRC_ARROW]);
1890 gfx_new_area ( im->canvas,
1891 im->xorigin-2, im->yorigin-im->ysize-2,
1892 im->xorigin+3, im->yorigin-im->ysize-2,
1893 im->xorigin+0.5, im->yorigin-im->ysize-7, /* LINEOFFSET */
1894 im->graph_col[GRC_ARROW]);
1896 }
1898 void
1899 grid_paint(image_desc_t *im)
1900 {
1901 long i;
1902 int res=0;
1903 double X0,Y0; /* points for filled graph and more*/
1904 gfx_node_t *node;
1906 /* draw 3d border */
1907 node = gfx_new_area (im->canvas, 0,im->yimg,
1908 2,im->yimg-2,
1909 2,2,im->graph_col[GRC_SHADEA]);
1910 gfx_add_point( node , im->ximg - 2, 2 );
1911 gfx_add_point( node , im->ximg, 0 );
1912 gfx_add_point( node , 0,0 );
1913 /* gfx_add_point( node , 0,im->yimg ); */
1915 node = gfx_new_area (im->canvas, 2,im->yimg-2,
1916 im->ximg-2,im->yimg-2,
1917 im->ximg - 2, 2,
1918 im->graph_col[GRC_SHADEB]);
1919 gfx_add_point( node , im->ximg,0);
1920 gfx_add_point( node , im->ximg,im->yimg);
1921 gfx_add_point( node , 0,im->yimg);
1922 /* gfx_add_point( node , 0,im->yimg ); */
1925 if (im->draw_x_grid == 1 )
1926 vertical_grid(im);
1928 if (im->draw_y_grid == 1){
1929 if(im->logarithmic){
1930 res = horizontal_log_grid(im);
1931 } else {
1932 res = draw_horizontal_grid(im);
1933 }
1935 /* dont draw horizontal grid if there is no min and max val */
1936 if (! res ) {
1937 char *nodata = "No Data found";
1938 gfx_new_text(im->canvas,im->ximg/2, (2*im->yorigin-im->ysize) / 2,
1939 im->graph_col[GRC_FONT],
1940 im->text_prop[TEXT_PROP_AXIS].font,
1941 im->text_prop[TEXT_PROP_AXIS].size,
1942 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
1943 nodata );
1944 }
1945 }
1947 /* yaxis unit description */
1948 gfx_new_text( im->canvas,
1949 10, (im->yorigin - im->ysize/2),
1950 im->graph_col[GRC_FONT],
1951 im->text_prop[TEXT_PROP_UNIT].font,
1952 im->text_prop[TEXT_PROP_UNIT].size, im->tabwidth,
1953 RRDGRAPH_YLEGEND_ANGLE,
1954 GFX_H_LEFT, GFX_V_CENTER,
1955 im->ylegend);
1957 /* graph title */
1958 gfx_new_text( im->canvas,
1959 im->ximg/2, im->text_prop[TEXT_PROP_TITLE].size*1.3+4,
1960 im->graph_col[GRC_FONT],
1961 im->text_prop[TEXT_PROP_TITLE].font,
1962 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
1963 GFX_H_CENTER, GFX_V_CENTER,
1964 im->title);
1965 /* rrdtool 'logo' */
1966 gfx_new_text( im->canvas,
1967 im->ximg-7, 7,
1968 ( im->graph_col[GRC_FONT] & 0xffffff00 ) | 0x00000044,
1969 im->text_prop[TEXT_PROP_AXIS].font,
1970 5.5, im->tabwidth, 270,
1971 GFX_H_RIGHT, GFX_V_TOP,
1972 "RRDTOOL / TOBI OETIKER");
1974 /* graph labels */
1975 if( !(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH) ) {
1976 for(i=0;i<im->gdes_c;i++){
1977 if(im->gdes[i].legend[0] =='\0')
1978 continue;
1980 /* im->gdes[i].leg_y is the bottom of the legend */
1981 X0 = im->gdes[i].leg_x;
1982 Y0 = im->gdes[i].leg_y;
1983 gfx_new_text ( im->canvas, X0, Y0,
1984 im->graph_col[GRC_FONT],
1985 im->text_prop[TEXT_PROP_LEGEND].font,
1986 im->text_prop[TEXT_PROP_LEGEND].size,
1987 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
1988 im->gdes[i].legend );
1989 /* The legend for GRAPH items starts with "M " to have
1990 enough space for the box */
1991 if ( im->gdes[i].gf != GF_PRINT &&
1992 im->gdes[i].gf != GF_GPRINT &&
1993 im->gdes[i].gf != GF_COMMENT) {
1994 int boxL, boxH, boxV;
1996 boxL = gfx_get_text_width(im->canvas, 0,
1997 im->text_prop[TEXT_PROP_LEGEND].font,
1998 im->text_prop[TEXT_PROP_LEGEND].size,
1999 im->tabwidth,"oo", 0);
2000 boxH = boxL / 1.9;
2001 boxV = boxH+1;
2003 /* make sure transparent colors show up all the same */
2004 node = gfx_new_area(im->canvas,
2005 X0-1,Y0-boxV,
2006 X0-1,Y0+1,
2007 X0+boxL+0.5,Y0+1,
2008 im->graph_col[GRC_BACK]);
2009 gfx_add_point ( node, X0+boxL+0.5, Y0-boxV );
2010 node = gfx_new_area(im->canvas,
2011 X0-1,Y0-boxV,
2012 X0-1,Y0,
2013 X0+boxH,Y0,
2014 im->graph_col[GRC_CANVAS]);
2015 gfx_add_point ( node, X0+boxH, Y0-boxV );
2017 node = gfx_new_area(im->canvas,
2018 X0-1,Y0-boxV,
2019 X0-1,Y0,
2020 X0+boxH,Y0,
2021 im->gdes[i].col);
2022 gfx_add_point ( node, X0+boxH, Y0-boxV );
2023 node = gfx_new_line(im->canvas,
2024 X0-1,Y0-boxV,
2025 X0-1,Y0,
2026 1,im->graph_col[GRC_FRAME]);
2027 gfx_add_point(node,X0+boxH,Y0);
2028 gfx_add_point(node,X0+boxH,Y0-boxV);
2029 gfx_close_path(node);
2030 }
2031 }
2032 }
2033 }
2036 /*****************************************************
2037 * lazy check make sure we rely need to create this graph
2038 *****************************************************/
2040 int lazy_check(image_desc_t *im){
2041 FILE *fd = NULL;
2042 int size = 1;
2043 struct stat imgstat;
2045 if (im->lazy == 0) return 0; /* no lazy option */
2046 if (stat(im->graphfile,&imgstat) != 0)
2047 return 0; /* can't stat */
2048 /* one pixel in the existing graph is more then what we would
2049 change here ... */
2050 if (time(NULL) - imgstat.st_mtime >
2051 (im->end - im->start) / im->xsize)
2052 return 0;
2053 if ((fd = fopen(im->graphfile,"rb")) == NULL)
2054 return 0; /* the file does not exist */
2055 switch (im->canvas->imgformat) {
2056 case IF_PNG:
2057 size = PngSize(fd,&(im->ximg),&(im->yimg));
2058 break;
2059 default:
2060 size = 1;
2061 }
2062 fclose(fd);
2063 return size;
2064 }
2066 #ifdef WITH_PIECHART
2067 void
2068 pie_part(image_desc_t *im, gfx_color_t color,
2069 double PieCenterX, double PieCenterY, double Radius,
2070 double startangle, double endangle)
2071 {
2072 gfx_node_t *node;
2073 double angle;
2074 double step=M_PI/50; /* Number of iterations for the circle;
2075 ** 10 is definitely too low, more than
2076 ** 50 seems to be overkill
2077 */
2079 /* Strange but true: we have to work clockwise or else
2080 ** anti aliasing nor transparency don't work.
2081 **
2082 ** This test is here to make sure we do it right, also
2083 ** this makes the for...next loop more easy to implement.
2084 ** The return will occur if the user enters a negative number
2085 ** (which shouldn't be done according to the specs) or if the
2086 ** programmers do something wrong (which, as we all know, never
2087 ** happens anyway :)
2088 */
2089 if (endangle<startangle) return;
2091 /* Hidden feature: Radius decreases each full circle */
2092 angle=startangle;
2093 while (angle>=2*M_PI) {
2094 angle -= 2*M_PI;
2095 Radius *= 0.8;
2096 }
2098 node=gfx_new_area(im->canvas,
2099 PieCenterX+sin(startangle)*Radius,
2100 PieCenterY-cos(startangle)*Radius,
2101 PieCenterX,
2102 PieCenterY,
2103 PieCenterX+sin(endangle)*Radius,
2104 PieCenterY-cos(endangle)*Radius,
2105 color);
2106 for (angle=endangle;angle-startangle>=step;angle-=step) {
2107 gfx_add_point(node,
2108 PieCenterX+sin(angle)*Radius,
2109 PieCenterY-cos(angle)*Radius );
2110 }
2111 }
2113 #endif
2115 int
2116 graph_size_location(image_desc_t *im, int elements
2118 #ifdef WITH_PIECHART
2119 , int piechart
2120 #endif
2122 )
2123 {
2124 /* The actual size of the image to draw is determined from
2125 ** several sources. The size given on the command line is
2126 ** the graph area but we need more as we have to draw labels
2127 ** and other things outside the graph area
2128 */
2130 /* +-+-------------------------------------------+
2131 ** |l|.................title.....................|
2132 ** |e+--+-------------------------------+--------+
2133 ** |b| b| | |
2134 ** |a| a| | pie |
2135 ** |l| l| main graph area | chart |
2136 ** |.| .| | area |
2137 ** |t| y| | |
2138 ** |r+--+-------------------------------+--------+
2139 ** |e| | x-axis labels | |
2140 ** |v+--+-------------------------------+--------+
2141 ** | |..............legends......................|
2142 ** +-+-------------------------------------------+
2143 */
2144 int Xvertical=0,
2145 Ytitle =0,
2146 Xylabel =0,
2147 Xmain =0, Ymain =0,
2148 #ifdef WITH_PIECHART
2149 Xpie =0, Ypie =0,
2150 #endif
2151 Yxlabel =0,
2152 #if 0
2153 Xlegend =0, Ylegend =0,
2154 #endif
2155 Xspacing =15, Yspacing =15;
2157 if (im->extra_flags & ONLY_GRAPH) {
2158 im->xorigin =0;
2159 im->ximg = im->xsize;
2160 im->yimg = im->ysize;
2161 im->yorigin = im->ysize;
2162 return 0;
2163 }
2165 if (im->ylegend[0] != '\0' ) {
2166 Xvertical = im->text_prop[TEXT_PROP_UNIT].size *2;
2167 }
2170 if (im->title[0] != '\0') {
2171 /* The title is placed "inbetween" two text lines so it
2172 ** automatically has some vertical spacing. The horizontal
2173 ** spacing is added here, on each side.
2174 */
2175 /* don't care for the with of the title
2176 Xtitle = gfx_get_text_width(im->canvas, 0,
2177 im->text_prop[TEXT_PROP_TITLE].font,
2178 im->text_prop[TEXT_PROP_TITLE].size,
2179 im->tabwidth,
2180 im->title, 0) + 2*Xspacing; */
2181 Ytitle = im->text_prop[TEXT_PROP_TITLE].size*2.6+10;
2182 }
2184 if (elements) {
2185 Xmain=im->xsize;
2186 Ymain=im->ysize;
2187 if (im->draw_x_grid) {
2188 Yxlabel=im->text_prop[TEXT_PROP_AXIS].size *2.5;
2189 }
2190 if (im->draw_y_grid) {
2191 Xylabel=gfx_get_text_width(im->canvas, 0,
2192 im->text_prop[TEXT_PROP_AXIS].font,
2193 im->text_prop[TEXT_PROP_AXIS].size,
2194 im->tabwidth,
2195 "0", 0) * im->unitslength;
2196 }
2197 }
2199 #ifdef WITH_PIECHART
2200 if (piechart) {
2201 im->piesize=im->xsize<im->ysize?im->xsize:im->ysize;
2202 Xpie=im->piesize;
2203 Ypie=im->piesize;
2204 }
2205 #endif
2207 /* Now calculate the total size. Insert some spacing where
2208 desired. im->xorigin and im->yorigin need to correspond
2209 with the lower left corner of the main graph area or, if
2210 this one is not set, the imaginary box surrounding the
2211 pie chart area. */
2213 /* The legend width cannot yet be determined, as a result we
2214 ** have problems adjusting the image to it. For now, we just
2215 ** forget about it at all; the legend will have to fit in the
2216 ** size already allocated.
2217 */
2218 im->ximg = Xylabel + Xmain + 2 * Xspacing;
2220 #ifdef WITH_PIECHART
2221 im->ximg += Xpie;
2222 #endif
2224 if (Xmain) im->ximg += Xspacing;
2225 #ifdef WITH_PIECHART
2226 if (Xpie) im->ximg += Xspacing;
2227 #endif
2229 im->xorigin = Xspacing + Xylabel;
2231 /* the length of the title should not influence with width of the graph
2232 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2234 if (Xvertical) { /* unit description */
2235 im->ximg += Xvertical;
2236 im->xorigin += Xvertical;
2237 }
2238 xtr(im,0);
2240 /* The vertical size is interesting... we need to compare
2241 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend} with Yvertical
2242 ** however we need to know {Ytitle+Ymain+Yxlabel} in order to
2243 ** start even thinking about Ylegend.
2244 **
2245 ** Do it in three portions: First calculate the inner part,
2246 ** then do the legend, then adjust the total height of the img.
2247 */
2249 /* reserve space for main and/or pie */
2251 im->yimg = Ymain + Yxlabel;
2253 #ifdef WITH_PIECHART
2254 if (im->yimg < Ypie) im->yimg = Ypie;
2255 #endif
2257 im->yorigin = im->yimg - Yxlabel;
2259 /* reserve space for the title *or* some padding above the graph */
2260 if (Ytitle) {
2261 im->yimg += Ytitle;
2262 im->yorigin += Ytitle;
2263 } else {
2264 im->yimg += 1.5*Yspacing;
2265 im->yorigin += 1.5*Yspacing;
2266 }
2267 /* reserve space for padding below the graph */
2268 im->yimg += Yspacing;
2269 ytr(im,DNAN);
2271 /* Determine where to place the legends onto the image.
2272 ** Adjust im->yimg to match the space requirements.
2273 */
2274 if(leg_place(im)==-1)
2275 return -1;
2278 #if 0
2279 if (Xlegend > im->ximg) {
2280 im->ximg = Xlegend;
2281 /* reposition Pie */
2282 }
2283 #endif
2285 #ifdef WITH_PIECHART
2286 /* The pie is placed in the upper right hand corner,
2287 ** just below the title (if any) and with sufficient
2288 ** padding.
2289 */
2290 if (elements) {
2291 im->pie_x = im->ximg - Xspacing - Xpie/2;
2292 im->pie_y = im->yorigin-Ymain+Ypie/2;
2293 } else {
2294 im->pie_x = im->ximg/2;
2295 im->pie_y = im->yorigin-Ypie/2;
2296 }
2297 #endif
2299 return 0;
2300 }
2302 /* draw that picture thing ... */
2303 int
2304 graph_paint(image_desc_t *im, char ***calcpr)
2305 {
2306 int i,ii;
2307 int lazy = lazy_check(im);
2308 #ifdef WITH_PIECHART
2309 int piechart = 0;
2310 double PieStart=0.0;
2311 #endif
2312 FILE *fo;
2313 gfx_node_t *node;
2315 double areazero = 0.0;
2316 enum gf_en stack_gf = GF_PRINT;
2317 graph_desc_t *lastgdes = NULL;
2319 /* if we are lazy and there is nothing to PRINT ... quit now */
2320 if (lazy && im->prt_c==0) return 0;
2322 /* pull the data from the rrd files ... */
2324 if(data_fetch(im)==-1)
2325 return -1;
2327 /* evaluate VDEF and CDEF operations ... */
2328 if(data_calc(im)==-1)
2329 return -1;
2331 #ifdef WITH_PIECHART
2332 /* check if we need to draw a piechart */
2333 for(i=0;i<im->gdes_c;i++){
2334 if (im->gdes[i].gf == GF_PART) {
2335 piechart=1;
2336 break;
2337 }
2338 }
2339 #endif
2341 /* calculate and PRINT and GPRINT definitions. We have to do it at
2342 * this point because it will affect the length of the legends
2343 * if there are no graph elements we stop here ...
2344 * if we are lazy, try to quit ...
2345 */
2346 i=print_calc(im,calcpr);
2347 if(i<0) return -1;
2348 if(((i==0)
2349 #ifdef WITH_PIECHART
2350 &&(piechart==0)
2351 #endif
2352 ) || lazy) return 0;
2354 #ifdef WITH_PIECHART
2355 /* If there's only the pie chart to draw, signal this */
2356 if (i==0) piechart=2;
2357 #endif
2359 /* get actual drawing data and find min and max values*/
2360 if(data_proc(im)==-1)
2361 return -1;
2363 if(!im->logarithmic){si_unit(im);} /* identify si magnitude Kilo, Mega Giga ? */
2365 if(!im->rigid && ! im->logarithmic)
2366 expand_range(im); /* make sure the upper and lower limit are
2367 sensible values */
2369 if (!calc_horizontal_grid(im))
2370 return -1;
2372 if (im->gridfit)
2373 apply_gridfit(im);
2376 /**************************************************************
2377 *** Calculating sizes and locations became a bit confusing ***
2378 *** so I moved this into a separate function. ***
2379 **************************************************************/
2380 if(graph_size_location(im,i
2381 #ifdef WITH_PIECHART
2382 ,piechart
2383 #endif
2384 )==-1)
2385 return -1;
2387 /* the actual graph is created by going through the individual
2388 graph elements and then drawing them */
2390 node=gfx_new_area ( im->canvas,
2391 0, 0,
2392 0, im->yimg,
2393 im->ximg, im->yimg,
2394 im->graph_col[GRC_BACK]);
2396 gfx_add_point(node,im->ximg, 0);
2398 #ifdef WITH_PIECHART
2399 if (piechart != 2) {
2400 #endif
2401 node=gfx_new_area ( im->canvas,
2402 im->xorigin, im->yorigin,
2403 im->xorigin + im->xsize, im->yorigin,
2404 im->xorigin + im->xsize, im->yorigin-im->ysize,
2405 im->graph_col[GRC_CANVAS]);
2407 gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
2409 if (im->minval > 0.0)
2410 areazero = im->minval;
2411 if (im->maxval < 0.0)
2412 areazero = im->maxval;
2413 #ifdef WITH_PIECHART
2414 }
2415 #endif
2417 #ifdef WITH_PIECHART
2418 if (piechart) {
2419 pie_part(im,im->graph_col[GRC_CANVAS],im->pie_x,im->pie_y,im->piesize*0.5,0,2*M_PI);
2420 }
2421 #endif
2423 for(i=0;i<im->gdes_c;i++){
2424 switch(im->gdes[i].gf){
2425 case GF_CDEF:
2426 case GF_VDEF:
2427 case GF_DEF:
2428 case GF_PRINT:
2429 case GF_GPRINT:
2430 case GF_COMMENT:
2431 case GF_HRULE:
2432 case GF_VRULE:
2433 case GF_XPORT:
2434 case GF_SHIFT:
2435 break;
2436 case GF_TICK:
2437 for (ii = 0; ii < im->xsize; ii++)
2438 {
2439 if (!isnan(im->gdes[i].p_data[ii]) &&
2440 im->gdes[i].p_data[ii] > 0.0)
2441 {
2442 /* generate a tick */
2443 gfx_new_line(im->canvas, im -> xorigin + ii,
2444 im -> yorigin - (im -> gdes[i].yrule * im -> ysize),
2445 im -> xorigin + ii,
2446 im -> yorigin,
2447 1.0,
2448 im -> gdes[i].col );
2449 }
2450 }
2451 break;
2452 case GF_LINE:
2453 case GF_AREA:
2454 stack_gf = im->gdes[i].gf;
2455 case GF_STACK:
2456 /* fix data points at oo and -oo */
2457 for(ii=0;ii<im->xsize;ii++){
2458 if (isinf(im->gdes[i].p_data[ii])){
2459 if (im->gdes[i].p_data[ii] > 0) {
2460 im->gdes[i].p_data[ii] = im->maxval ;
2461 } else {
2462 im->gdes[i].p_data[ii] = im->minval ;
2463 }
2465 }
2466 } /* for */
2468 /* *******************************************************
2469 a ___. (a,t)
2470 | | ___
2471 ____| | | |
2472 | |___|
2473 -------|--t-1--t--------------------------------
2475 if we know the value at time t was a then
2476 we draw a square from t-1 to t with the value a.
2478 ********************************************************* */
2479 if (im->gdes[i].col != 0x0){
2480 /* GF_LINE and friend */
2481 if(stack_gf == GF_LINE ){
2482 node = NULL;
2483 for(ii=1;ii<im->xsize;ii++){
2484 if (isnan(im->gdes[i].p_data[ii]) || (im->slopemode==1 && isnan(im->gdes[i].p_data[ii-1]))){
2485 node = NULL;
2486 continue;
2487 }
2488 if ( node == NULL ) {
2489 if ( im->slopemode == 0 ){
2490 node = gfx_new_line(im->canvas,
2491 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2492 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2493 im->gdes[i].linewidth,
2494 im->gdes[i].col);
2495 } else {
2496 node = gfx_new_line(im->canvas,
2497 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2498 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2499 im->gdes[i].linewidth,
2500 im->gdes[i].col);
2501 }
2502 } else {
2503 if ( im->slopemode==0 ){
2504 gfx_add_point(node,ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2505 };
2506 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2507 };
2509 }
2510 } else {
2511 double ybase0 = DNAN,ytop0=DNAN;
2512 for(ii=0;ii<im->xsize;ii++){
2513 /* keep things simple for now, just draw these bars
2514 do not try to build a big and complex area */
2515 double ybase,ytop;
2516 if ( im->slopemode == 0 && ii==0){
2517 continue;
2518 }
2519 if ( isnan(im->gdes[i].p_data[ii]) ) {
2520 ybase0 = DNAN;
2521 continue;
2522 }
2523 ytop = ytr(im,im->gdes[i].p_data[ii]);
2524 if ( lastgdes && im->gdes[i].stack ) {
2525 ybase = ytr(im,lastgdes->p_data[ii]);
2526 } else {
2527 ybase = ytr(im,areazero);
2528 }
2529 if ( ybase == ytop ){
2530 ybase0 = DNAN;
2531 continue;
2532 }
2533 /* every area has to be wound clock-wise,
2534 so we have to make sur base remains base */
2535 if (ybase > ytop){
2536 double extra = ytop;
2537 ytop = ybase;
2538 ybase = extra;
2539 }
2540 if ( im->slopemode == 0){
2541 ybase0 = ybase;
2542 ytop0 = ytop;
2543 }
2544 if ( !isnan(ybase0) ){
2545 node = gfx_new_area(im->canvas,
2546 (double)ii-1.2+(double)im->xorigin,ybase0-0.2,
2547 (double)ii-1.2+(double)im->xorigin,ytop0+0.2,
2548 (double)ii+0.2+(double)im->xorigin,ytop+0.2,
2549 im->gdes[i].col
2550 );
2551 gfx_add_point(node,
2552 (double)ii+0.02+im->xorigin,ybase-0.2
2553 );
2554 }
2555 ybase0=ybase;
2556 ytop0=ytop;
2557 }
2558 } /* else GF_LINE */
2559 } /* if color != 0x0 */
2560 /* make sure we do not run into trouble when stacking on NaN */
2561 for(ii=0;ii<im->xsize;ii++){
2562 if (isnan(im->gdes[i].p_data[ii])) {
2563 if (lastgdes && (im->gdes[i].stack)) {
2564 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
2565 } else {
2566 im->gdes[i].p_data[ii] = ytr(im,areazero);
2567 }
2568 }
2569 }
2570 lastgdes = &(im->gdes[i]);
2571 break;
2572 #ifdef WITH_PIECHART
2573 case GF_PART:
2574 if(isnan(im->gdes[i].yrule)) /* fetch variable */
2575 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2577 if (finite(im->gdes[i].yrule)) { /* even the fetched var can be NaN */
2578 pie_part(im,im->gdes[i].col,
2579 im->pie_x,im->pie_y,im->piesize*0.4,
2580 M_PI*2.0*PieStart/100.0,
2581 M_PI*2.0*(PieStart+im->gdes[i].yrule)/100.0);
2582 PieStart += im->gdes[i].yrule;
2583 }
2584 break;
2585 #endif
2587 } /* switch */
2588 }
2589 #ifdef WITH_PIECHART
2590 if (piechart==2) {
2591 im->draw_x_grid=0;
2592 im->draw_y_grid=0;
2593 }
2594 #endif
2597 /* grid_paint also does the text */
2598 if( !(im->extra_flags & ONLY_GRAPH) )
2599 grid_paint(im);
2602 if( !(im->extra_flags & ONLY_GRAPH) )
2603 axis_paint(im);
2605 /* the RULES are the last thing to paint ... */
2606 for(i=0;i<im->gdes_c;i++){
2608 switch(im->gdes[i].gf){
2609 case GF_HRULE:
2610 if(isnan(im->gdes[i].yrule)) { /* fetch variable */
2611 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2612 };
2613 if(im->gdes[i].yrule >= im->minval
2614 && im->gdes[i].yrule <= im->maxval)
2615 gfx_new_line(im->canvas,
2616 im->xorigin,ytr(im,im->gdes[i].yrule),
2617 im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2618 1.0,im->gdes[i].col);
2619 break;
2620 case GF_VRULE:
2621 if(im->gdes[i].xrule == 0) { /* fetch variable */
2622 im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
2623 };
2624 if(im->gdes[i].xrule >= im->start
2625 && im->gdes[i].xrule <= im->end)
2626 gfx_new_line(im->canvas,
2627 xtr(im,im->gdes[i].xrule),im->yorigin,
2628 xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2629 1.0,im->gdes[i].col);
2630 break;
2631 default:
2632 break;
2633 }
2634 }
2637 if (strcmp(im->graphfile,"-")==0) {
2638 fo = im->graphhandle ? im->graphhandle : stdout;
2639 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
2640 /* Change translation mode for stdout to BINARY */
2641 _setmode( _fileno( fo ), O_BINARY );
2642 #endif
2643 } else {
2644 if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2645 rrd_set_error("Opening '%s' for write: %s",im->graphfile,
2646 rrd_strerror(errno));
2647 return (-1);
2648 }
2649 }
2650 gfx_render (im->canvas,im->ximg,im->yimg,0x00000000,fo);
2651 if (strcmp(im->graphfile,"-") != 0)
2652 fclose(fo);
2653 return 0;
2654 }
2657 /*****************************************************
2658 * graph stuff
2659 *****************************************************/
2661 int
2662 gdes_alloc(image_desc_t *im){
2664 im->gdes_c++;
2665 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2666 * sizeof(graph_desc_t)))==NULL){
2667 rrd_set_error("realloc graph_descs");
2668 return -1;
2669 }
2672 im->gdes[im->gdes_c-1].step=im->step;
2673 im->gdes[im->gdes_c-1].stack=0;
2674 im->gdes[im->gdes_c-1].debug=0;
2675 im->gdes[im->gdes_c-1].start=im->start;
2676 im->gdes[im->gdes_c-1].end=im->end;
2677 im->gdes[im->gdes_c-1].vname[0]='\0';
2678 im->gdes[im->gdes_c-1].data=NULL;
2679 im->gdes[im->gdes_c-1].ds_namv=NULL;
2680 im->gdes[im->gdes_c-1].data_first=0;
2681 im->gdes[im->gdes_c-1].p_data=NULL;
2682 im->gdes[im->gdes_c-1].rpnp=NULL;
2683 im->gdes[im->gdes_c-1].shift=0;
2684 im->gdes[im->gdes_c-1].col = 0x0;
2685 im->gdes[im->gdes_c-1].legend[0]='\0';
2686 im->gdes[im->gdes_c-1].format[0]='\0';
2687 im->gdes[im->gdes_c-1].rrd[0]='\0';
2688 im->gdes[im->gdes_c-1].ds=-1;
2689 im->gdes[im->gdes_c-1].p_data=NULL;
2690 im->gdes[im->gdes_c-1].yrule=DNAN;
2691 im->gdes[im->gdes_c-1].xrule=0;
2692 return 0;
2693 }
2695 /* copies input untill the first unescaped colon is found
2696 or until input ends. backslashes have to be escaped as well */
2697 int
2698 scan_for_col(char *input, int len, char *output)
2699 {
2700 int inp,outp=0;
2701 for (inp=0;
2702 inp < len &&
2703 input[inp] != ':' &&
2704 input[inp] != '\0';
2705 inp++){
2706 if (input[inp] == '\\' &&
2707 input[inp+1] != '\0' &&
2708 (input[inp+1] == '\\' ||
2709 input[inp+1] == ':')){
2710 output[outp++] = input[++inp];
2711 }
2712 else {
2713 output[outp++] = input[inp];
2714 }
2715 }
2716 output[outp] = '\0';
2717 return inp;
2718 }
2719 /* Some surgery done on this function, it became ridiculously big.
2720 ** Things moved:
2721 ** - initializing now in rrd_graph_init()
2722 ** - options parsing now in rrd_graph_options()
2723 ** - script parsing now in rrd_graph_script()
2724 */
2725 int
2726 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize, FILE *stream, double *ymin, double *ymax)
2727 {
2728 image_desc_t im;
2729 rrd_graph_init(&im);
2730 im.graphhandle = stream;
2732 rrd_graph_options(argc,argv,&im);
2733 if (rrd_test_error()) {
2734 im_free(&im);
2735 return -1;
2736 }
2738 if (strlen(argv[optind])>=MAXPATH) {
2739 rrd_set_error("filename (including path) too long");
2740 im_free(&im);
2741 return -1;
2742 }
2743 strncpy(im.graphfile,argv[optind],MAXPATH-1);
2744 im.graphfile[MAXPATH-1]='\0';
2746 rrd_graph_script(argc,argv,&im,1);
2747 if (rrd_test_error()) {
2748 im_free(&im);
2749 return -1;
2750 }
2752 /* Everything is now read and the actual work can start */
2754 (*prdata)=NULL;
2755 if (graph_paint(&im,prdata)==-1){
2756 im_free(&im);
2757 return -1;
2758 }
2760 /* The image is generated and needs to be output.
2761 ** Also, if needed, print a line with information about the image.
2762 */
2764 *xsize=im.ximg;
2765 *ysize=im.yimg;
2766 *ymin=im.minval;
2767 *ymax=im.maxval;
2768 if (im.imginfo) {
2769 char *filename;
2770 if (!(*prdata)) {
2771 /* maybe prdata is not allocated yet ... lets do it now */
2772 if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
2773 rrd_set_error("malloc imginfo");
2774 return -1;
2775 };
2776 }
2777 if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
2778 ==NULL){
2779 rrd_set_error("malloc imginfo");
2780 return -1;
2781 }
2782 filename=im.graphfile+strlen(im.graphfile);
2783 while(filename > im.graphfile) {
2784 if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
2785 filename--;
2786 }
2788 sprintf((*prdata)[0],im.imginfo,filename,(long)(im.canvas->zoom*im.ximg),(long)(im.canvas->zoom*im.yimg));
2789 }
2790 im_free(&im);
2791 return 0;
2792 }
2794 void
2795 rrd_graph_init(image_desc_t *im)
2796 {
2797 unsigned int i;
2799 #ifdef HAVE_TZSET
2800 tzset();
2801 #endif
2802 #ifdef HAVE_SETLOCALE
2803 setlocale(LC_TIME,"");
2804 #endif
2805 im->yorigin=0;
2806 im->xorigin=0;
2807 im->minval=0;
2808 im->xlab_user.minsec = -1;
2809 im->ximg=0;
2810 im->yimg=0;
2811 im->xsize = 400;
2812 im->ysize = 100;
2813 im->step = 0;
2814 im->ylegend[0] = '\0';
2815 im->title[0] = '\0';
2816 im->minval = DNAN;
2817 im->maxval = DNAN;
2818 im->unitsexponent= 9999;
2819 im->unitslength= 6;
2820 im->symbol = ' ';
2821 im->viewfactor = 1.0;
2822 im->extra_flags= 0;
2823 im->rigid = 0;
2824 im->gridfit = 1;
2825 im->imginfo = NULL;
2826 im->lazy = 0;
2827 im->slopemode = 0;
2828 im->logarithmic = 0;
2829 im->ygridstep = DNAN;
2830 im->draw_x_grid = 1;
2831 im->draw_y_grid = 1;
2832 im->base = 1000;
2833 im->prt_c = 0;
2834 im->gdes_c = 0;
2835 im->gdes = NULL;
2836 im->canvas = gfx_new_canvas();
2837 im->grid_dash_on = 1;
2838 im->grid_dash_off = 1;
2839 im->tabwidth = 40.0;
2841 for(i=0;i<DIM(graph_col);i++)
2842 im->graph_col[i]=graph_col[i];
2844 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
2845 {
2846 char *windir;
2847 char rrd_win_default_font[1000];
2848 windir = getenv("windir");
2849 /* %windir% is something like D:\windows or C:\winnt */
2850 if (windir != NULL) {
2851 strncpy(rrd_win_default_font,windir,999);
2852 rrd_win_default_font[999] = '\0';
2853 strcat(rrd_win_default_font,"\\fonts\\");
2854 strcat(rrd_win_default_font,RRD_DEFAULT_FONT);
2855 for(i=0;i<DIM(text_prop);i++){
2856 strncpy(text_prop[i].font,rrd_win_default_font,sizeof(text_prop[i].font)-1);
2857 text_prop[i].font[sizeof(text_prop[i].font)-1] = '\0';
2858 }
2859 }
2860 }
2861 #endif
2862 {
2863 char *deffont;
2864 deffont = getenv("RRD_DEFAULT_FONT");
2865 if (deffont != NULL) {
2866 for(i=0;i<DIM(text_prop);i++){
2867 strncpy(text_prop[i].font,deffont,sizeof(text_prop[i].font)-1);
2868 text_prop[i].font[sizeof(text_prop[i].font)-1] = '\0';
2869 }
2870 }
2871 }
2872 for(i=0;i<DIM(text_prop);i++){
2873 im->text_prop[i].size = text_prop[i].size;
2874 strcpy(im->text_prop[i].font,text_prop[i].font);
2875 }
2876 }
2878 void
2879 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
2880 {
2881 int stroff;
2882 char *parsetime_error = NULL;
2883 char scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
2884 time_t start_tmp=0,end_tmp=0;
2885 long long_tmp;
2886 struct rrd_time_value start_tv, end_tv;
2887 gfx_color_t color;
2888 optind = 0; opterr = 0; /* initialize getopt */
2890 parsetime("end-24h", &start_tv);
2891 parsetime("now", &end_tv);
2893 while (1){
2894 static struct option long_options[] =
2895 {
2896 {"start", required_argument, 0, 's'},
2897 {"end", required_argument, 0, 'e'},
2898 {"x-grid", required_argument, 0, 'x'},
2899 {"y-grid", required_argument, 0, 'y'},
2900 {"vertical-label",required_argument,0,'v'},
2901 {"width", required_argument, 0, 'w'},
2902 {"height", required_argument, 0, 'h'},
2903 {"interlaced", no_argument, 0, 'i'},
2904 {"upper-limit",required_argument, 0, 'u'},
2905 {"lower-limit",required_argument, 0, 'l'},
2906 {"rigid", no_argument, 0, 'r'},
2907 {"base", required_argument, 0, 'b'},
2908 {"logarithmic",no_argument, 0, 'o'},
2909 {"color", required_argument, 0, 'c'},
2910 {"font", required_argument, 0, 'n'},
2911 {"title", required_argument, 0, 't'},
2912 {"imginfo", required_argument, 0, 'f'},
2913 {"imgformat", required_argument, 0, 'a'},
2914 {"lazy", no_argument, 0, 'z'},
2915 {"zoom", required_argument, 0, 'm'},
2916 {"no-legend", no_argument, 0, 'g'},
2917 {"force-rules-legend",no_argument,0, 'F'},
2918 {"only-graph", no_argument, 0, 'j'},
2919 {"alt-y-grid", no_argument, 0, 'Y'},
2920 {"no-minor", no_argument, 0, 'I'},
2921 {"slope-mode", no_argument, 0, 'E'},
2922 {"alt-autoscale", no_argument, 0, 'A'},
2923 {"alt-autoscale-max", no_argument, 0, 'M'},
2924 {"no-gridfit", no_argument, 0, 'N'},
2925 {"units-exponent",required_argument, 0, 'X'},
2926 {"units-length",required_argument, 0, 'L'},
2927 {"step", required_argument, 0, 'S'},
2928 {"tabwidth", required_argument, 0, 'T'},
2929 {"font-render-mode", required_argument, 0, 'R'},
2930 {"font-smoothing-threshold", required_argument, 0, 'B'},
2931 {"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 */
2932 {0,0,0,0}};
2933 int option_index = 0;
2934 int opt;
2935 int col_start,col_end;
2937 opt = getopt_long(argc, argv,
2938 "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:I:zgjFYAMEX:L:S:T:NR:B:",
2939 long_options, &option_index);
2941 if (opt == EOF)
2942 break;
2944 switch(opt) {
2945 case 'I':
2946 im->extra_flags |= NOMINOR;
2947 break;
2948 case 'Y':
2949 im->extra_flags |= ALTYGRID;
2950 break;
2951 case 'A':
2952 im->extra_flags |= ALTAUTOSCALE;
2953 break;
2954 case 'M':
2955 im->extra_flags |= ALTAUTOSCALE_MAX;
2956 break;
2957 case 'j':
2958 im->extra_flags |= ONLY_GRAPH;
2959 break;
2960 case 'g':
2961 im->extra_flags |= NOLEGEND;
2962 break;
2963 case 'F':
2964 im->extra_flags |= FORCE_RULES_LEGEND;
2965 break;
2966 case 'X':
2967 im->unitsexponent = atoi(optarg);
2968 break;
2969 case 'L':
2970 im->unitslength = atoi(optarg);
2971 break;
2972 case 'T':
2973 im->tabwidth = atof(optarg);
2974 break;
2975 case 'S':
2976 im->step = atoi(optarg);
2977 break;
2978 case 'N':
2979 im->gridfit = 0;
2980 break;
2981 case 's':
2982 if ((parsetime_error = parsetime(optarg, &start_tv))) {
2983 rrd_set_error( "start time: %s", parsetime_error );
2984 return;
2985 }
2986 break;
2987 case 'e':
2988 if ((parsetime_error = parsetime(optarg, &end_tv))) {
2989 rrd_set_error( "end time: %s", parsetime_error );
2990 return;
2991 }
2992 break;
2993 case 'x':
2994 if(strcmp(optarg,"none") == 0){
2995 im->draw_x_grid=0;
2996 break;
2997 };
2999 if(sscanf(optarg,
3000 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3001 scan_gtm,
3002 &im->xlab_user.gridst,
3003 scan_mtm,
3004 &im->xlab_user.mgridst,
3005 scan_ltm,
3006 &im->xlab_user.labst,
3007 &im->xlab_user.precis,
3008 &stroff) == 7 && stroff != 0){
3009 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
3010 im->xlab_form[sizeof(im->xlab_form)-1] = '\0';
3011 if((int)(im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
3012 rrd_set_error("unknown keyword %s",scan_gtm);
3013 return;
3014 } else if ((int)(im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
3015 rrd_set_error("unknown keyword %s",scan_mtm);
3016 return;
3017 } else if ((int)(im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
3018 rrd_set_error("unknown keyword %s",scan_ltm);
3019 return;
3020 }
3021 im->xlab_user.minsec = 1;
3022 im->xlab_user.stst = im->xlab_form;
3023 } else {
3024 rrd_set_error("invalid x-grid format");
3025 return;
3026 }
3027 break;
3028 case 'y':
3030 if(strcmp(optarg,"none") == 0){
3031 im->draw_y_grid=0;
3032 break;
3033 };
3035 if(sscanf(optarg,
3036 "%lf:%d",
3037 &im->ygridstep,
3038 &im->ylabfact) == 2) {
3039 if(im->ygridstep<=0){
3040 rrd_set_error("grid step must be > 0");
3041 return;
3042 } else if (im->ylabfact < 1){
3043 rrd_set_error("label factor must be > 0");
3044 return;
3045 }
3046 } else {
3047 rrd_set_error("invalid y-grid format");
3048 return;
3049 }
3050 break;
3051 case 'v':
3052 strncpy(im->ylegend,optarg,150);
3053 im->ylegend[150]='\0';
3054 break;
3055 case 'u':
3056 im->maxval = atof(optarg);
3057 break;
3058 case 'l':
3059 im->minval = atof(optarg);
3060 break;
3061 case 'b':
3062 im->base = atol(optarg);
3063 if(im->base != 1024 && im->base != 1000 ){
3064 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
3065 return;
3066 }
3067 break;
3068 case 'w':
3069 long_tmp = atol(optarg);
3070 if (long_tmp < 10) {
3071 rrd_set_error("width below 10 pixels");
3072 return;
3073 }
3074 im->xsize = long_tmp;
3075 break;
3076 case 'h':
3077 long_tmp = atol(optarg);
3078 if (long_tmp < 10) {
3079 rrd_set_error("height below 10 pixels");
3080 return;
3081 }
3082 im->ysize = long_tmp;
3083 break;
3084 case 'i':
3085 im->canvas->interlaced = 1;
3086 break;
3087 case 'r':
3088 im->rigid = 1;
3089 break;
3090 case 'f':
3091 im->imginfo = optarg;
3092 break;
3093 case 'a':
3094 if((int)(im->canvas->imgformat = if_conv(optarg)) == -1) {
3095 rrd_set_error("unsupported graphics format '%s'",optarg);
3096 return;
3097 }
3098 break;
3099 case 'z':
3100 im->lazy = 1;
3101 break;
3102 case 'E':
3103 im->slopemode = 1;
3104 break;
3106 case 'o':
3107 im->logarithmic = 1;
3108 if (isnan(im->minval))
3109 im->minval=1;
3110 break;
3111 case 'c':
3112 if(sscanf(optarg,
3113 "%10[A-Z]#%n%8lx%n",
3114 col_nam,&col_start,&color,&col_end) == 2){
3115 int ci;
3116 int col_len = col_end - col_start;
3117 switch (col_len){
3118 case 3:
3119 color = (
3120 ((color & 0xF00) * 0x110000) |
3121 ((color & 0x0F0) * 0x011000) |
3122 ((color & 0x00F) * 0x001100) |
3123 0x000000FF
3124 );
3125 break;
3126 case 4:
3127 color = (
3128 ((color & 0xF000) * 0x11000) |
3129 ((color & 0x0F00) * 0x01100) |
3130 ((color & 0x00F0) * 0x00110) |
3131 ((color & 0x000F) * 0x00011)
3132 );
3133 break;
3134 case 6:
3135 color = (color << 8) + 0xff /* shift left by 8 */;
3136 break;
3137 case 8:
3138 break;
3139 default:
3140 rrd_set_error("the color format is #RRGGBB[AA]");
3141 return;
3142 }
3143 if((ci=grc_conv(col_nam)) != -1){
3144 im->graph_col[ci]=color;
3145 } else {
3146 rrd_set_error("invalid color name '%s'",col_nam);
3147 return;
3148 }
3149 } else {
3150 rrd_set_error("invalid color def format");
3151 return;
3152 }
3153 break;
3154 case 'n':{
3155 char prop[15];
3156 double size = 1;
3157 char font[1024];
3159 if(sscanf(optarg,
3160 "%10[A-Z]:%lf:%1000s",
3161 prop,&size,font) == 3){
3162 int sindex;
3163 if((sindex=text_prop_conv(prop)) != -1){
3164 im->text_prop[sindex].size=size;
3165 strcpy(im->text_prop[sindex].font,font);
3166 if (sindex==0) { /* the default */
3167 im->text_prop[TEXT_PROP_TITLE].size=size;
3168 strcpy(im->text_prop[TEXT_PROP_TITLE].font,font);
3169 im->text_prop[TEXT_PROP_AXIS].size=size;
3170 strcpy(im->text_prop[TEXT_PROP_AXIS].font,font);
3171 im->text_prop[TEXT_PROP_UNIT].size=size;
3172 strcpy(im->text_prop[TEXT_PROP_UNIT].font,font);
3173 im->text_prop[TEXT_PROP_LEGEND].size=size;
3174 strcpy(im->text_prop[TEXT_PROP_LEGEND].font,font);
3175 }
3176 } else {
3177 rrd_set_error("invalid fonttag '%s'",prop);
3178 return;
3179 }
3180 } else {
3181 rrd_set_error("invalid text property format");
3182 return;
3183 }
3184 break;
3185 }
3186 case 'm':
3187 im->canvas->zoom = atof(optarg);
3188 if (im->canvas->zoom <= 0.0) {
3189 rrd_set_error("zoom factor must be > 0");
3190 return;
3191 }
3192 break;
3193 case 't':
3194 strncpy(im->title,optarg,150);
3195 im->title[150]='\0';
3196 break;
3198 case 'R':
3199 if ( strcmp( optarg, "normal" ) == 0 )
3200 im->canvas->aa_type = AA_NORMAL;
3201 else if ( strcmp( optarg, "light" ) == 0 )
3202 im->canvas->aa_type = AA_LIGHT;
3203 else if ( strcmp( optarg, "mono" ) == 0 )
3204 im->canvas->aa_type = AA_NONE;
3205 else
3206 {
3207 rrd_set_error("unknown font-render-mode '%s'", optarg );
3208 return;
3209 }
3210 break;
3212 case 'B':
3213 im->canvas->font_aa_threshold = atof(optarg);
3214 break;
3216 case '?':
3217 if (optopt != 0)
3218 rrd_set_error("unknown option '%c'", optopt);
3219 else
3220 rrd_set_error("unknown option '%s'",argv[optind-1]);
3221 return;
3222 }
3223 }
3225 if (optind >= argc) {
3226 rrd_set_error("missing filename");
3227 return;
3228 }
3230 if (im->logarithmic == 1 && (im->minval <= 0 || isnan(im->minval))){
3231 rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");
3232 return;
3233 }
3235 if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
3236 /* error string is set in parsetime.c */
3237 return;
3238 }
3240 if (start_tmp < 3600*24*365*10){
3241 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
3242 return;
3243 }
3245 if (end_tmp < start_tmp) {
3246 rrd_set_error("start (%ld) should be less than end (%ld)",
3247 start_tmp, end_tmp);
3248 return;
3249 }
3251 im->start = start_tmp;
3252 im->end = end_tmp;
3253 im->step = max((long)im->step, (im->end-im->start)/im->xsize);
3254 }
3256 int
3257 rrd_graph_check_vname(image_desc_t *im, char *varname, char *err)
3258 {
3259 if ((im->gdes[im->gdes_c-1].vidx=find_var(im,varname))==-1) {
3260 rrd_set_error("Unknown variable '%s' in %s",varname,err);
3261 return -1;
3262 }
3263 return 0;
3264 }
3265 int
3266 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
3267 {
3268 char *color;
3269 graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
3271 color=strstr(var,"#");
3272 if (color==NULL) {
3273 if (optional==0) {
3274 rrd_set_error("Found no color in %s",err);
3275 return 0;
3276 }
3277 return 0;
3278 } else {
3279 int n=0;
3280 char *rest;
3281 gfx_color_t col;
3283 rest=strstr(color,":");
3284 if (rest!=NULL)
3285 n=rest-color;
3286 else
3287 n=strlen(color);
3289 switch (n) {
3290 case 7:
3291 sscanf(color,"#%6lx%n",&col,&n);
3292 col = (col << 8) + 0xff /* shift left by 8 */;
3293 if (n!=7) rrd_set_error("Color problem in %s",err);
3294 break;
3295 case 9:
3296 sscanf(color,"#%8lx%n",&col,&n);
3297 if (n==9) break;
3298 default:
3299 rrd_set_error("Color problem in %s",err);
3300 }
3301 if (rrd_test_error()) return 0;
3302 gdp->col = col;
3303 return n;
3304 }
3305 }
3308 int bad_format(char *fmt) {
3309 char *ptr;
3310 int n=0;
3311 ptr = fmt;
3312 while (*ptr != '\0')
3313 if (*ptr++ == '%') {
3315 /* line cannot end with percent char */
3316 if (*ptr == '\0') return 1;
3318 /* '%s', '%S' and '%%' are allowed */
3319 if (*ptr == 's' || *ptr == 'S' || *ptr == '%') ptr++;
3321 /* or else '% 6.2lf' and such are allowed */
3322 else {
3324 /* optional padding character */
3325 if (*ptr == ' ' || *ptr == '+' || *ptr == '-') ptr++;
3327 /* This should take care of 'm.n' with all three optional */
3328 while (*ptr >= '0' && *ptr <= '9') ptr++;
3329 if (*ptr == '.') ptr++;
3330 while (*ptr >= '0' && *ptr <= '9') ptr++;
3332 /* Either 'le', 'lf' or 'lg' must follow here */
3333 if (*ptr++ != 'l') return 1;
3334 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g') ptr++;
3335 else return 1;
3336 n++;
3337 }
3338 }
3340 return (n!=1);
3341 }
3344 int
3345 vdef_parse(gdes,str)
3346 struct graph_desc_t *gdes;
3347 char *str;
3348 {
3349 /* A VDEF currently is either "func" or "param,func"
3350 * so the parsing is rather simple. Change if needed.
3351 */
3352 double param;
3353 char func[30];
3354 int n;
3356 n=0;
3357 sscanf(str,"%le,%29[A-Z]%n",¶m,func,&n);
3358 if (n== (int)strlen(str)) { /* matched */
3359 ;
3360 } else {
3361 n=0;
3362 sscanf(str,"%29[A-Z]%n",func,&n);
3363 if (n== (int)strlen(str)) { /* matched */
3364 param=DNAN;
3365 } else {
3366 rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3367 ,str
3368 ,gdes->vname
3369 );
3370 return -1;
3371 }
3372 }
3373 if (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3374 else if (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3375 else if (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3376 else if (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3377 else if (!strcmp("TOTAL", func)) gdes->vf.op = VDEF_TOTAL;
3378 else if (!strcmp("FIRST", func)) gdes->vf.op = VDEF_FIRST;
3379 else if (!strcmp("LAST", func)) gdes->vf.op = VDEF_LAST;
3380 else {
3381 rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3382 ,func
3383 ,gdes->vname
3384 );
3385 return -1;
3386 };
3388 switch (gdes->vf.op) {
3389 case VDEF_PERCENT:
3390 if (isnan(param)) { /* no parameter given */
3391 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3392 ,func
3393 ,gdes->vname
3394 );
3395 return -1;
3396 };
3397 if (param>=0.0 && param<=100.0) {
3398 gdes->vf.param = param;
3399 gdes->vf.val = DNAN; /* undefined */
3400 gdes->vf.when = 0; /* undefined */
3401 } else {
3402 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3403 ,param
3404 ,gdes->vname
3405 );
3406 return -1;
3407 };
3408 break;
3409 case VDEF_MAXIMUM:
3410 case VDEF_AVERAGE:
3411 case VDEF_MINIMUM:
3412 case VDEF_TOTAL:
3413 case VDEF_FIRST:
3414 case VDEF_LAST:
3415 if (isnan(param)) {
3416 gdes->vf.param = DNAN;
3417 gdes->vf.val = DNAN;
3418 gdes->vf.when = 0;
3419 } else {
3420 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3421 ,func
3422 ,gdes->vname
3423 );
3424 return -1;
3425 };
3426 break;
3427 };
3428 return 0;
3429 }
3432 int
3433 vdef_calc(im,gdi)
3434 image_desc_t *im;
3435 int gdi;
3436 {
3437 graph_desc_t *src,*dst;
3438 rrd_value_t *data;
3439 long step,steps;
3441 dst = &im->gdes[gdi];
3442 src = &im->gdes[dst->vidx];
3443 data = src->data + src->ds;
3444 steps = (src->end - src->start) / src->step;
3446 #if 0
3447 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3448 ,src->start
3449 ,src->end
3450 ,steps
3451 );
3452 #endif
3454 switch (dst->vf.op) {
3455 case VDEF_PERCENT: {
3456 rrd_value_t * array;
3457 int field;
3460 if ((array = malloc(steps*sizeof(double)))==NULL) {
3461 rrd_set_error("malloc VDEV_PERCENT");
3462 return -1;
3463 }
3464 for (step=0;step < steps; step++) {
3465 array[step]=data[step*src->ds_cnt];
3466 }
3467 qsort(array,step,sizeof(double),vdef_percent_compar);
3469 field = (steps-1)*dst->vf.param/100;
3470 dst->vf.val = array[field];
3471 dst->vf.when = 0; /* no time component */
3472 free(array);
3473 #if 0
3474 for(step=0;step<steps;step++)
3475 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3476 #endif
3477 }
3478 break;
3479 case VDEF_MAXIMUM:
3480 step=0;
3481 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3482 if (step == steps) {
3483 dst->vf.val = DNAN;
3484 dst->vf.when = 0;
3485 } else {
3486 dst->vf.val = data[step*src->ds_cnt];
3487 dst->vf.when = src->start + (step+1)*src->step;
3488 }
3489 while (step != steps) {
3490 if (finite(data[step*src->ds_cnt])) {
3491 if (data[step*src->ds_cnt] > dst->vf.val) {
3492 dst->vf.val = data[step*src->ds_cnt];
3493 dst->vf.when = src->start + (step+1)*src->step;
3494 }
3495 }
3496 step++;
3497 }
3498 break;
3499 case VDEF_TOTAL:
3500 case VDEF_AVERAGE: {
3501 int cnt=0;
3502 double sum=0.0;
3503 for (step=0;step<steps;step++) {
3504 if (finite(data[step*src->ds_cnt])) {
3505 sum += data[step*src->ds_cnt];
3506 cnt ++;
3507 };
3508 }
3509 if (cnt) {
3510 if (dst->vf.op == VDEF_TOTAL) {
3511 dst->vf.val = sum*src->step;
3512 dst->vf.when = cnt*src->step; /* not really "when" */
3513 } else {
3514 dst->vf.val = sum/cnt;
3515 dst->vf.when = 0; /* no time component */
3516 };
3517 } else {
3518 dst->vf.val = DNAN;
3519 dst->vf.when = 0;
3520 }
3521 }
3522 break;
3523 case VDEF_MINIMUM:
3524 step=0;
3525 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3526 if (step == steps) {
3527 dst->vf.val = DNAN;
3528 dst->vf.when = 0;
3529 } else {
3530 dst->vf.val = data[step*src->ds_cnt];
3531 dst->vf.when = src->start + (step+1)*src->step;
3532 }
3533 while (step != steps) {
3534 if (finite(data[step*src->ds_cnt])) {
3535 if (data[step*src->ds_cnt] < dst->vf.val) {
3536 dst->vf.val = data[step*src->ds_cnt];
3537 dst->vf.when = src->start + (step+1)*src->step;
3538 }
3539 }
3540 step++;
3541 }
3542 break;
3543 case VDEF_FIRST:
3544 /* The time value returned here is one step before the
3545 * actual time value. This is the start of the first
3546 * non-NaN interval.
3547 */
3548 step=0;
3549 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3550 if (step == steps) { /* all entries were NaN */
3551 dst->vf.val = DNAN;
3552 dst->vf.when = 0;
3553 } else {
3554 dst->vf.val = data[step*src->ds_cnt];
3555 dst->vf.when = src->start + step*src->step;
3556 }
3557 break;
3558 case VDEF_LAST:
3559 /* The time value returned here is the
3560 * actual time value. This is the end of the last
3561 * non-NaN interval.
3562 */
3563 step=steps-1;
3564 while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3565 if (step < 0) { /* all entries were NaN */
3566 dst->vf.val = DNAN;
3567 dst->vf.when = 0;
3568 } else {
3569 dst->vf.val = data[step*src->ds_cnt];
3570 dst->vf.when = src->start + (step+1)*src->step;
3571 }
3572 break;
3573 }
3574 return 0;
3575 }
3577 /* NaN < -INF < finite_values < INF */
3578 int
3579 vdef_percent_compar(a,b)
3580 const void *a,*b;
3581 {
3582 /* Equality is not returned; this doesn't hurt except
3583 * (maybe) for a little performance.
3584 */
3586 /* First catch NaN values. They are smallest */
3587 if (isnan( *(double *)a )) return -1;
3588 if (isnan( *(double *)b )) return 1;
3590 /* NaN doesn't reach this part so INF and -INF are extremes.
3591 * The sign from isinf() is compatible with the sign we return
3592 */
3593 if (isinf( *(double *)a )) return isinf( *(double *)a );
3594 if (isinf( *(double *)b )) return isinf( *(double *)b );
3596 /* If we reach this, both values must be finite */
3597 if ( *(double *)a < *(double *)b ) return -1; else return 1;
3598 }