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