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