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