1 /****************************************************************************
2 * RRDtool 1.0.33 Copyright Tobias Oetiker, 1997 - 2000
3 ****************************************************************************
4 * rrd__graph.c make creates ne rrds
5 ****************************************************************************/
9 #include <gdlucidan10.h>
10 #include <gdlucidab12.h>
17 #define SmallFont gdLucidaNormal10
18 #define LargeFont gdLucidaBold12
23 # define DPRINT(x) (void)(printf x, printf("\n"))
28 #define DEF_NAM_FMT "%29[_A-Za-z0-9]"
30 enum tmt_en {TMT_SECOND=0,TMT_MINUTE,TMT_HOUR,TMT_DAY,
31 TMT_WEEK,TMT_MONTH,TMT_YEAR};
33 enum grc_en {GRC_CANVAS=0,GRC_BACK,GRC_SHADEA,GRC_SHADEB,
34 GRC_GRID,GRC_MGRID,GRC_FONT,GRC_FRAME,GRC_ARROW,__GRC_END__};
37 enum gf_en {GF_PRINT=0,GF_GPRINT,GF_COMMENT,GF_HRULE,GF_VRULE,GF_LINE1,
38 GF_LINE2,GF_LINE3,GF_AREA,GF_STACK, GF_DEF, GF_CDEF };
40 enum op_en {OP_NUMBER=0,OP_VARIABLE,OP_INF,OP_PREV,OP_NEGINF,
41 OP_UNKN,OP_NOW,OP_TIME,OP_LTIME,OP_ADD,OP_MOD,
43 OP_DIV,OP_SIN, OP_DUP, OP_EXC, OP_POP,
44 OP_COS,OP_LOG,OP_EXP,OP_LT,OP_LE,OP_GT,OP_GE,OP_EQ,OP_IF,
45 OP_MIN,OP_MAX,OP_LIMIT, OP_FLOOR, OP_CEIL,
48 enum if_en {IF_GIF=0,IF_PNG=1};
50 typedef struct rpnp_t {
52 double val; /* value for a OP_NUMBER */
53 long ptr; /* pointer into the gdes array for OP_VAR */
54 double *data; /* pointer to the current value from OP_VAR DAS*/
55 long ds_cnt; /* data source count for data pointer */
56 long step; /* time step for OP_VAR das */
60 typedef struct col_trip_t {
61 int red; /* red = -1 is no color */
64 int i; /* color index assigned in gif image i=-1 is unasigned*/
68 typedef struct xlab_t {
69 long minsec; /* minimum sec per pix */
70 enum tmt_en gridtm; /* grid interval in what ?*/
71 long gridst; /* how many whats per grid*/
72 enum tmt_en mgridtm; /* label interval in what ?*/
73 long mgridst; /* how many whats per label*/
74 enum tmt_en labtm; /* label interval in what ?*/
75 long labst; /* how many whats per label*/
76 long precis; /* label precision -> label placement*/
77 char *stst; /* strftime string*/
81 {0, TMT_SECOND,30, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
82 {2, TMT_MINUTE,1, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
83 {5, TMT_MINUTE,2, TMT_MINUTE,10, TMT_MINUTE,10, 0,"%H:%M"},
84 {10, TMT_MINUTE,5, TMT_MINUTE,20, TMT_MINUTE,20, 0,"%H:%M"},
85 {30, TMT_MINUTE,10, TMT_HOUR,1, TMT_HOUR,1, 0,"%H:%M"},
86 {60, TMT_MINUTE,30, TMT_HOUR,2, TMT_HOUR,2, 0,"%H:%M"},
87 {180, TMT_HOUR,1, TMT_HOUR,6, TMT_HOUR,6, 0,"%H:%M"},
88 /*{300, TMT_HOUR,3, TMT_HOUR,12, TMT_HOUR,12, 12*3600,"%a %p"}, this looks silly*/
89 {600, TMT_HOUR,6, TMT_DAY,1, TMT_DAY,1, 24*3600,"%a"},
90 {1800, TMT_HOUR,12, TMT_DAY,1, TMT_DAY,2, 24*3600,"%a"},
91 {3600, TMT_DAY,1, TMT_WEEK,1, TMT_WEEK,1, 7*24*3600,"Week %W"},
92 {3*3600, TMT_WEEK,1, TMT_MONTH,1, TMT_WEEK,2, 7*24*3600,"Week %W"},
93 {6*3600, TMT_MONTH,1, TMT_MONTH,1, TMT_MONTH,1, 30*24*3600,"%b"},
94 {48*3600, TMT_MONTH,1, TMT_MONTH,3, TMT_MONTH,3, 30*24*3600,"%b"},
95 {10*24*3600, TMT_YEAR,1, TMT_YEAR,1, TMT_YEAR,1, 365*24*3600,"%y"},
96 {-1,TMT_MONTH,0,TMT_MONTH,0,TMT_MONTH,0,0,""}
99 /* sensible logarithmic y label intervals ...
100 the first element of each row defines the possible starting points on the
101 y axis ... the other specify the */
103 double yloglab[][12]= {{ 1e9, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
104 { 1e3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
105 { 1e1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
106 /* { 1e1, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, */
107 { 1e1, 1, 2.5, 5, 7.5, 0, 0, 0, 0, 0, 0, 0 },
108 { 1e1, 1, 2, 4, 6, 8, 0, 0, 0, 0, 0, 0 },
109 { 1e1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0 },
110 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }};
112 /* sensible y label intervals ...*/
114 typedef struct ylab_t {
115 double grid; /* grid spacing */
116 int lfac[4]; /* associated label spacing*/
129 {100.0, {1,2, 5,10}},
130 {200.0, {1,5,10,20}},
131 {500.0, {1,2, 4,10}},
136 col_trip_t graph_col[] = { /* default colors */
137 {255,255,255,-1}, /* canvas */
138 {245,245,245,-1}, /* background */
139 {200,200,200,-1}, /* shade A */
140 {150,150,150,-1}, /* shade B */
141 {140,140,140,-1}, /* grid */
142 {130,30,30,-1}, /* major grid */
143 {0,0,0,-1}, /* font */
144 {0,0,0,-1}, /* frame */
145 {255,0,0,-1} /*arrow*/
148 /* this structure describes the elements which can make up a graph.
149 because they are quite diverse, not all elements will use all the
150 possible parts of the structure. */
152 #define FMT_LEG_LEN 200
154 #define FMT_LEG_LEN 2000
157 typedef struct graph_desc_t {
158 enum gf_en gf; /* graphing function */
159 char vname[30]; /* name of the variable */
160 long vidx; /* gdes reference */
161 char rrd[255]; /* name of the rrd_file containing data */
162 char ds_nam[DS_NAM_SIZE]; /* data source name */
163 long ds; /* data source number */
164 enum cf_en cf; /* consolidation function */
165 col_trip_t col; /* graph color */
166 char format[FMT_LEG_LEN+5]; /* format for PRINT AND GPRINT */
167 char legend[FMT_LEG_LEN+5]; /* legend*/
168 gdPoint legloc; /* location of legend */
169 double yrule; /* value for y rule line */
170 time_t xrule; /* value for x rule line */
171 rpnp_t *rpnp; /* instructions for CDEF function */
173 /* description of data fetched for the graph element */
174 time_t start,end; /* timestaps for first and last data element */
175 unsigned long step; /* time between samples */
176 unsigned long ds_cnt; /* how many data sources are there in the fetch */
177 long data_first; /* first pointer to this data */
178 char **ds_namv; /* name of datasources in the fetch. */
179 rrd_value_t *data; /* the raw data drawn from the rrd */
180 rrd_value_t *p_data; /* processed data, xsize elments */
184 #define ALTYGRID 0x01 /* use alternative y grid algorithm */
185 #define ALTAUTOSCALE 0x02 /* use alternative algorithm to find lower and upper bounds */
186 #define ALTAUTOSCALE_MAX 0x04 /* use alternative algorithm to find upper bounds */
187 #define NOLEGEND 0x08 /* use no legend */
189 typedef struct image_desc_t {
191 /* configuration of graph */
193 char graphfile[MAXPATH]; /* filename for graphic */
194 long xsize,ysize; /* graph area size in pixels */
195 col_trip_t graph_col[__GRC_END__]; /* real colors for the graph */
196 char ylegend[200]; /* legend along the yaxis */
197 char title[200]; /* title for graph */
198 int draw_x_grid; /* no x-grid at all */
199 int draw_y_grid; /* no x-grid at all */
200 xlab_t xlab_user; /* user defined labeling for xaxis */
201 char xlab_form[200]; /* format for the label on the xaxis */
203 double ygridstep; /* user defined step for y grid */
204 int ylabfact; /* every how many y grid shall a label be written ? */
206 time_t start,end; /* what time does the graph cover */
207 unsigned long step; /* any preference for the default step ? */
208 rrd_value_t minval,maxval; /* extreme values in the data */
209 int rigid; /* do not expand range even with
211 char* imginfo; /* construct an <IMG ... tag and return
213 int lazy; /* only update the gif if there is reasonable
214 probablility that the existing one is out of date */
215 int logarithmic; /* scale the yaxis logarithmic */
216 enum if_en imgformat; /* image format */
218 /* status information */
220 long xorigin,yorigin;/* where is (0,0) of the graph */
221 long xgif,ygif; /* total size of the gif */
222 int interlaced; /* will the graph be interlaced? */
223 double magfact; /* numerical magnitude*/
224 long base; /* 1000 or 1024 depending on what we graph */
225 char symbol; /* magnitude symbol for y-axis */
226 int unitsexponent; /* 10*exponent for units on y-asis */
227 int extra_flags; /* flags for boolean options */
230 long prt_c; /* number of print elements */
231 long gdes_c; /* number of graphics elements */
232 graph_desc_t *gdes; /* points to an array of graph elements */
237 int xtr(image_desc_t *,time_t);
238 int ytr(image_desc_t *, double);
239 enum gf_en gf_conv(char *);
240 enum if_en if_conv(char *);
241 enum tmt_en tmt_conv(char *);
242 enum grc_en grc_conv(char *);
243 int im_free(image_desc_t *);
244 void auto_scale( image_desc_t *, double *, char **, double *);
245 void si_unit( image_desc_t *);
246 void expand_range(image_desc_t *);
247 void reduce_data( enum cf_en, unsigned long, time_t *, time_t *, unsigned long *, unsigned long *, rrd_value_t **);
248 int data_fetch( image_desc_t *);
249 long find_var(image_desc_t *, char *);
251 int data_calc( image_desc_t *);
252 int data_proc( image_desc_t *);
253 time_t find_first_time( time_t, enum tmt_en, long);
254 time_t find_next_time( time_t, enum tmt_en, long);
255 void gator( gdImagePtr, int, int);
256 int tzoffset(time_t);
257 int print_calc(image_desc_t *, char ***);
258 int leg_place(image_desc_t *);
259 int horizontal_grid(gdImagePtr, image_desc_t *);
260 int horizontal_log_grid(gdImagePtr, image_desc_t *);
261 void vertical_grid( gdImagePtr, image_desc_t *);
262 void axis_paint( image_desc_t *, gdImagePtr);
263 void grid_paint( image_desc_t *, gdImagePtr);
264 gdImagePtr MkLineBrush(image_desc_t *,long, enum gf_en);
265 int lazy_check(image_desc_t *);
266 int graph_paint(image_desc_t *, char ***);
267 int gdes_alloc(image_desc_t *);
268 int scan_for_col(char *, int, char *);
269 int rrd_graph(int, char **, char ***, int *, int *);
270 int bad_format(char *);
271 rpnp_t * str2rpn(image_desc_t *,char *);
273 /* translate time values into x coordinates */
274 /*#define xtr(x) (int)((double)im->xorigin \
275 + ((double) im->xsize / (double)(im->end - im->start) ) \
276 * ((double)(x) - im->start)+0.5) */
277 /* initialize with xtr(im,0); */
279 xtr(image_desc_t *im,time_t mytime){
282 pixie = (double) im->xsize / (double)(im->end - im->start);
285 return (int)((double)im->xorigin
286 + pixie * ( mytime - im->start ) );
289 /* translate data values into y coordinates */
291 /* #define ytr(x) (int)((double)im->yorigin \
292 - ((double) im->ysize / (im->maxval - im->minval) ) \
293 * ((double)(x) - im->minval)+0.5) */
295 ytr(image_desc_t *im, double value){
300 pixie = (double) im->ysize / (im->maxval - im->minval);
302 pixie = (double) im->ysize / (log10(im->maxval) - log10(im->minval));
304 } else if(!im->logarithmic) {
305 yval = im->yorigin - pixie * (value - im->minval) + 0.5;
307 if (value < im->minval) {
310 yval = im->yorigin - pixie * (log10(value) - log10(im->minval)) + 0.5;
313 /* make sure we don't return anything too unreasonable. GD lib can
314 get terribly slow when drawing lines outside its scope. This is
315 especially problematic in connection with the rigid option */
318 } else if ((int)yval > im->yorigin) {
319 return im->yorigin+2;
320 } else if ((int) yval < im->yorigin - im->ysize){
321 return im->yorigin - im->ysize - 2;
331 /* conversion function for symbolic entry names */
334 #define conv_if(VV,VVV) \
335 if (strcmp(#VV, string) == 0) return VVV ;
337 enum gf_en gf_conv(char *string){
339 conv_if(PRINT,GF_PRINT)
340 conv_if(GPRINT,GF_GPRINT)
341 conv_if(COMMENT,GF_COMMENT)
342 conv_if(HRULE,GF_HRULE)
343 conv_if(VRULE,GF_VRULE)
344 conv_if(LINE1,GF_LINE1)
345 conv_if(LINE2,GF_LINE2)
346 conv_if(LINE3,GF_LINE3)
347 conv_if(AREA,GF_AREA)
348 conv_if(STACK,GF_STACK)
350 conv_if(CDEF,GF_CDEF)
355 enum if_en if_conv(char *string){
363 enum tmt_en tmt_conv(char *string){
365 conv_if(SECOND,TMT_SECOND)
366 conv_if(MINUTE,TMT_MINUTE)
367 conv_if(HOUR,TMT_HOUR)
369 conv_if(WEEK,TMT_WEEK)
370 conv_if(MONTH,TMT_MONTH)
371 conv_if(YEAR,TMT_YEAR)
375 enum grc_en grc_conv(char *string){
377 conv_if(BACK,GRC_BACK)
378 conv_if(CANVAS,GRC_CANVAS)
379 conv_if(SHADEA,GRC_SHADEA)
380 conv_if(SHADEB,GRC_SHADEB)
381 conv_if(GRID,GRC_GRID)
382 conv_if(MGRID,GRC_MGRID)
383 conv_if(FONT,GRC_FONT)
384 conv_if(FRAME,GRC_FRAME)
385 conv_if(ARROW,GRC_ARROW)
395 im_free(image_desc_t *im)
398 if (im == NULL) return 0;
399 for(i=0;i<im->gdes_c;i++){
400 if (im->gdes[i].data_first){
401 /* careful here, because a single pointer can occur several times */
402 free (im->gdes[i].data);
403 if (im->gdes[i].ds_namv){
404 for (ii=0;ii<im->gdes[i].ds_cnt;ii++)
405 free(im->gdes[i].ds_namv[ii]);
406 free(im->gdes[i].ds_namv);
409 free (im->gdes[i].p_data);
410 free (im->gdes[i].rpnp);
416 /* find SI magnitude symbol for the given number*/
419 image_desc_t *im, /* image description */
426 char *symbol[] = {"a", /* 10e-18 Ato */
427 "f", /* 10e-15 Femto */
428 "p", /* 10e-12 Pico */
429 "n", /* 10e-9 Nano */
430 "u", /* 10e-6 Micro */
431 "m", /* 10e-3 Milli */
436 "T", /* 10e12 Terra */
437 "P", /* 10e15 Peta */
443 if (*value == 0.0 || isnan(*value) ) {
447 sindex = floor(log(fabs(*value))/log((double)im->base));
448 *magfact = pow((double)im->base, (double)sindex);
449 (*value) /= (*magfact);
451 if ( sindex <= symbcenter && sindex >= -symbcenter) {
452 (*symb_ptr) = symbol[sindex+symbcenter];
460 /* find SI magnitude symbol for the numbers on the y-axis*/
463 image_desc_t *im /* image description */
467 char symbol[] = {'a', /* 10e-18 Ato */
468 'f', /* 10e-15 Femto */
469 'p', /* 10e-12 Pico */
470 'n', /* 10e-9 Nano */
471 'u', /* 10e-6 Micro */
472 'm', /* 10e-3 Milli */
477 'T', /* 10e12 Terra */
478 'P', /* 10e15 Peta */
484 if (im->unitsexponent != 9999) {
485 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
486 digits = floor(im->unitsexponent / 3);
488 digits = floor( log( max( fabs(im->minval),fabs(im->maxval)))/log((double)im->base));
490 im->magfact = pow((double)im->base , digits);
493 printf("digits %6.3f im->magfact %6.3f\n",digits,im->magfact);
496 if ( ((digits+symbcenter) < sizeof(symbol)) &&
497 ((digits+symbcenter) >= 0) )
498 im->symbol = symbol[(int)digits+symbcenter];
503 /* move min and max values around to become sensible */
506 expand_range(image_desc_t *im)
508 double sensiblevalues[] ={1000.0,900.0,800.0,750.0,700.0,
509 600.0,500.0,400.0,300.0,250.0,
510 200.0,125.0,100.0,90.0,80.0,
511 75.0,70.0,60.0,50.0,40.0,30.0,
512 25.0,20.0,10.0,9.0,8.0,
513 7.0,6.0,5.0,4.0,3.5,3.0,
514 2.5,2.0,1.8,1.5,1.2,1.0,
515 0.8,0.7,0.6,0.5,0.4,0.3,0.2,0.1,0.0,-1};
517 double scaled_min,scaled_max;
524 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
525 im->minval,im->maxval,im->magfact);
528 if (isnan(im->ygridstep)){
529 if(im->extra_flags & ALTAUTOSCALE) {
530 /* measure the amplitude of the function. Make sure that
531 graph boundaries are slightly higher then max/min vals
532 so we can see amplitude on the graph */
535 delt = im->maxval - im->minval;
537 fact = 2.0 * pow(10.0,
538 floor(log10(max(fabs(im->minval), fabs(im->maxval)))) - 2);
540 adj = (fact - delt) * 0.55;
542 printf("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n", im->minval, im->maxval, delt, fact, adj);
548 else if(im->extra_flags & ALTAUTOSCALE_MAX) {
549 /* measure the amplitude of the function. Make sure that
550 graph boundaries are slightly higher than max vals
551 so we can see amplitude on the graph */
552 adj = (im->maxval - im->minval) * 0.1;
556 scaled_min = im->minval / im->magfact;
557 scaled_max = im->maxval / im->magfact;
559 for (i=1; sensiblevalues[i] > 0; i++){
560 if (sensiblevalues[i-1]>=scaled_min &&
561 sensiblevalues[i]<=scaled_min)
562 im->minval = sensiblevalues[i]*(im->magfact);
564 if (-sensiblevalues[i-1]<=scaled_min &&
565 -sensiblevalues[i]>=scaled_min)
566 im->minval = -sensiblevalues[i-1]*(im->magfact);
568 if (sensiblevalues[i-1] >= scaled_max &&
569 sensiblevalues[i] <= scaled_max)
570 im->maxval = sensiblevalues[i-1]*(im->magfact);
572 if (-sensiblevalues[i-1]<=scaled_max &&
573 -sensiblevalues[i] >=scaled_max)
574 im->maxval = -sensiblevalues[i]*(im->magfact);
578 /* adjust min and max to the grid definition if there is one */
579 im->minval = (double)im->ylabfact * im->ygridstep *
580 floor(im->minval / ((double)im->ylabfact * im->ygridstep));
581 im->maxval = (double)im->ylabfact * im->ygridstep *
582 ceil(im->maxval /( (double)im->ylabfact * im->ygridstep));
586 fprintf(stderr,"SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
587 im->minval,im->maxval,im->magfact);
592 /* reduce data reimplementation by Alex */
596 enum cf_en cf, /* which consolidation function ?*/
597 unsigned long cur_step, /* step the data currently is in */
598 time_t *start, /* start, end and step as requested ... */
599 time_t *end, /* ... by the application will be ... */
600 unsigned long *step, /* ... adjusted to represent reality */
601 unsigned long *ds_cnt, /* number of data sources in file */
602 rrd_value_t **data) /* two dimensional array containing the data */
604 int i,reduce_factor = ceil((double)(*step) / (double)cur_step);
605 unsigned long col,dst_row,row_cnt,start_offset,end_offset,skiprows=0;
606 rrd_value_t *srcptr,*dstptr;
608 (*step) = cur_step*reduce_factor; /* set new step size for reduced data */
612 /* We were given one extra row at the beginning of the interval.
613 ** We also need to return one extra row. The extra interval is
614 ** the one defined by the start time in both cases. It is not
615 ** used when graphing but maybe we can use it while reducing the
618 row_cnt = ((*end)-(*start))/cur_step +1;
620 /* alter start and end so that they are multiples of the new steptime.
621 ** End will be shifted towards the future and start will be shifted
622 ** towards the past in order to include the requested interval
624 end_offset = (*end) % (*step);
625 if (end_offset) end_offset = (*step)-end_offset;
626 start_offset = (*start) % (*step);
627 (*end) = (*end)+end_offset;
628 (*start) = (*start)-start_offset;
630 /* The first destination row is unknown yet it still needs
631 ** to be present in the returned data. Skip it.
632 ** Don't make it NaN or we might overwrite the source.
636 /* Depending on the amount of extra data needed at the
637 ** start of the destination, three things can happen:
638 ** -1- start_offset == 0: skip the extra source row
639 ** -2- start_offset == cur_step: do nothing
640 ** -3- start_offset > cur_step: skip some source rows and
641 ** fill one destination row with NaN
643 if (start_offset==0) {
646 } else if (start_offset!=cur_step) {
647 skiprows=((*step)-start_offset)/cur_step+1;
648 srcptr += ((*ds_cnt)*skiprows);
650 for (col=0;col<(*ds_cnt);col++) *dstptr++=DNAN;
653 /* If we had to alter the endtime, there won't be
654 ** enough data to fill the last row. This means
655 ** we have to skip some rows at the end
658 skiprows = ((*step)-end_offset)/cur_step;
663 /* Sanity check: row_cnt should be multiple of reduce_factor */
664 /* if this gets triggered, something is REALY WRONG ... we die immediately */
666 if (row_cnt%reduce_factor) {
667 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
668 row_cnt,reduce_factor);
669 printf("BUG in reduce_data()\n");
673 /* Now combine reduce_factor intervals at a time
674 ** into one interval for the destination.
677 for (dst_row=0;row_cnt>=reduce_factor;dst_row++) {
678 for (col=0;col<(*ds_cnt);col++) {
679 rrd_value_t newval=DNAN;
680 unsigned long validval=0;
682 for (i=0;i<reduce_factor;i++) {
683 if (isnan(srcptr[i*(*ds_cnt)+col])) {
687 if (isnan(newval)) newval = srcptr[i*(*ds_cnt)+col];
691 newval += srcptr[i*(*ds_cnt)+col];
694 newval = min (newval,srcptr[i*(*ds_cnt)+col]);
697 newval = max (newval,srcptr[i*(*ds_cnt)+col]);
700 newval = srcptr[i*(*ds_cnt)+col];
705 if (validval == 0){newval = DNAN;} else{
718 srcptr+=(*ds_cnt)*reduce_factor;
719 row_cnt-=reduce_factor;
722 /* If we had to alter the endtime, we didn't have enough
723 ** source rows to fill the last row. Fill it with NaN.
725 if (end_offset!=0) for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
729 /* get the data required for the graphs from the
733 data_fetch( image_desc_t *im )
737 /* pull the data from the log files ... */
738 for (i=0;i<im->gdes_c;i++){
739 /* only GF_DEF elements fetch data */
740 if (im->gdes[i].gf != GF_DEF)
744 /* do we have it already ?*/
745 for (ii=0;ii<i;ii++){
746 if (im->gdes[ii].gf != GF_DEF)
748 if((strcmp(im->gdes[i].rrd,im->gdes[ii].rrd) == 0)
749 && (im->gdes[i].cf == im->gdes[ii].cf)){
750 /* OK the data it is here already ...
751 * we just copy the header portion */
752 im->gdes[i].start = im->gdes[ii].start;
753 im->gdes[i].end = im->gdes[ii].end;
754 im->gdes[i].step = im->gdes[ii].step;
755 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
756 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
757 im->gdes[i].data = im->gdes[ii].data;
758 im->gdes[i].data_first = 0;
765 unsigned long ft_step = im->gdes[i].step ;
767 if((rrd_fetch_fn(im->gdes[i].rrd,
773 &im->gdes[i].ds_namv,
774 &im->gdes[i].data)) == -1){
777 im->gdes[i].data_first = 1;
779 if (ft_step < im->gdes[i].step) {
780 reduce_data(im->gdes[i].cf,
788 im->gdes[i].step = ft_step;
792 /* lets see if the required data source is realy there */
793 for(ii=0;ii<im->gdes[i].ds_cnt;ii++){
794 if(strcmp(im->gdes[i].ds_namv[ii],im->gdes[i].ds_nam) == 0){
797 if (im->gdes[i].ds== -1){
798 rrd_set_error("No DS called '%s' in '%s'",
799 im->gdes[i].ds_nam,im->gdes[i].rrd);
807 /* evaluate the expressions in the CDEF functions */
809 /*************************************************************
811 *************************************************************/
813 /* find gdes containing var*/
815 find_var(image_desc_t *im, char *key){
817 for(ii=0;ii<im->gdes_c-1;ii++){
818 if((im->gdes[ii].gf == GF_DEF
819 || im->gdes[ii].gf == GF_CDEF)
820 && (strcmp(im->gdes[ii].vname,key) == 0)){
827 /* find the largest common denominator for all the numbers
828 in the 0 terminated num array */
833 for (i=0;num[i+1]!=0;i++){
835 rest=num[i] % num[i+1];
836 num[i]=num[i+1]; num[i+1]=rest;
840 /* return i==0?num[i]:num[i-1]; */
845 /* convert string to rpnp */
847 str2rpn(image_desc_t *im,char *expr){
856 if ((rpnp = (rpnp_t *) rrd_realloc(rpnp, (++steps + 2)*
857 sizeof(rpnp_t)))==NULL){
861 else if((sscanf(expr,"%lf%n",&rpnp[steps].val,&pos) == 1)
862 && (expr[pos] == ',')){
863 rpnp[steps].op = OP_NUMBER;
867 #define match_op(VV,VVV) \
868 else if (strncmp(expr, #VVV, strlen(#VVV))==0 && \
869 (expr[strlen(#VVV)] == ',' || expr[strlen(#VVV)] == '\0') ){ \
870 rpnp[steps].op = VV; \
871 expr+=strlen(#VVV); \
882 match_op(OP_FLOOR,FLOOR)
883 match_op(OP_CEIL,CEIL)
896 match_op(OP_LIMIT,LIMIT)
897 /* order is important here ! .. match longest first */
898 match_op(OP_UNKN,UNKN)
900 match_op(OP_NEGINF,NEGINF)
901 match_op(OP_PREV,PREV)
904 match_op(OP_LTIME,LTIME)
905 match_op(OP_TIME,TIME)
911 else if ((sscanf(expr,DEF_NAM_FMT "%n",
913 && ((rpnp[steps].ptr = find_var(im,vname)) != -1)){
914 rpnp[steps].op = OP_VARIABLE;
931 rpnp[steps+1].op = OP_END;
935 /* figure out what the local timezone offset for any point in
936 time was. Return it in seconds */
939 tzoffset( time_t now ){
940 int gm_sec, gm_min, gm_hour, gm_yday, gm_year,
941 l_sec, l_min, l_hour, l_yday, l_year;
947 gm_hour = t->tm_hour;
948 gm_yday = t->tm_yday;
949 gm_year = t->tm_year;
956 off = (l_sec-gm_sec)+(l_min-gm_min)*60+(l_hour-gm_hour)*3600;
957 if ( l_yday > gm_yday || l_year > gm_year){
959 } else if ( l_yday < gm_yday || l_year < gm_year){
968 #define dc_stackblock 100
970 /* run the rpn calculator on all the CDEF arguments */
973 data_calc( image_desc_t *im){
980 double *stack = NULL;
981 long dc_stacksize = 0;
983 for (gdi=0;gdi<im->gdes_c;gdi++){
984 /* only GF_CDEF elements are of interest */
985 if (im->gdes[gdi].gf != GF_CDEF)
987 im->gdes[gdi].ds_cnt = 1;
988 im->gdes[gdi].ds = 0;
989 im->gdes[gdi].data_first = 1;
990 im->gdes[gdi].start = 0;
991 im->gdes[gdi].end = 0;
996 /* find the variables in the expression. And calc the lowest
997 common denominator of all step sizes of the data sources involved.
998 this will be the step size for the cdef created data source*/
1000 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
1001 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE){
1002 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
1003 if ((steparray = rrd_realloc(steparray, (++stepcnt+1)*sizeof(*steparray)))==NULL){
1004 rrd_set_error("realloc steparray");
1010 steparray[stepcnt-1] = im->gdes[ptr].step;
1012 /* adjust start and end of cdef (gdi) so that it runs from
1013 the latest start point to the earliest endpoint of any of the
1014 rras involved (ptr) */
1016 if(im->gdes[gdi].start < im->gdes[ptr].start)
1017 im->gdes[gdi].start = im->gdes[ptr].start;
1019 if(im->gdes[gdi].end == 0
1020 || im->gdes[gdi].end > im->gdes[ptr].end)
1021 im->gdes[gdi].end = im->gdes[ptr].end;
1023 /* store pointer to the first element of the rra providing
1024 data for variable, further save step size and data source count
1026 im->gdes[gdi].rpnp[rpi].data =
1027 im->gdes[ptr].data + im->gdes[ptr].ds;
1028 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
1029 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
1033 if(steparray == NULL){
1034 rrd_set_error("rpn expressions without variables are not supported");
1038 steparray[stepcnt]=0;
1039 /* now find the step for the result of the cdef. so that we land on
1040 each step in all of the variables rras */
1042 im->gdes[gdi].step = lcd(steparray);
1047 if((im->gdes[gdi].data = malloc(((im->gdes[gdi].end
1048 -im->gdes[gdi].start)
1049 / im->gdes[gdi].step +1)
1050 * sizeof(double)))==NULL){
1051 rrd_set_error("malloc im->gdes[gdi].data");
1056 /* step through the new cdef results array and calculate the values */
1057 for (now = im->gdes[gdi].start;
1058 now<=im->gdes[gdi].end;
1059 now += im->gdes[gdi].step){
1061 /* process each op from the rpn in turn */
1062 for (rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
1063 if (stptr +5 > dc_stacksize){
1064 dc_stacksize += dc_stackblock;
1065 stack = rrd_realloc(stack,dc_stacksize*sizeof(*stack));
1067 rrd_set_error("RPN stack overflow");
1071 switch (im->gdes[gdi].rpnp[rpi].op){
1073 stack[++stptr] = im->gdes[gdi].rpnp[rpi].val;
1076 /* make sure we pull the correct value from the *.data array */
1077 /* adjust the pointer into the array acordingly. */
1078 if(now > im->gdes[gdi].start &&
1079 now % im->gdes[gdi].rpnp[rpi].step == 0){
1080 im->gdes[gdi].rpnp[rpi].data +=
1081 im->gdes[gdi].rpnp[rpi].ds_cnt;
1083 stack[++stptr] = *im->gdes[gdi].rpnp[rpi].data;
1087 stack[++stptr] = DNAN;
1089 stack[++stptr] = im->gdes[gdi].data[dataidx];
1093 stack[++stptr] = DNAN;
1096 stack[++stptr] = DINF;
1099 stack[++stptr] = -DINF;
1102 stack[++stptr] = (double)time(NULL);
1105 stack[++stptr] = (double)now;
1108 stack[++stptr] = (double)tzoffset(now)+(double)now;
1112 rrd_set_error("RPN stack underflow");
1116 stack[stptr-1] = stack[stptr-1] + stack[stptr];
1121 rrd_set_error("RPN stack underflow");
1125 stack[stptr-1] = stack[stptr-1] - stack[stptr];
1130 rrd_set_error("RPN stack underflow");
1134 stack[stptr-1] = stack[stptr-1] * stack[stptr];
1139 rrd_set_error("RPN stack underflow");
1143 stack[stptr-1] = stack[stptr-1] / stack[stptr];
1148 rrd_set_error("RPN stack underflow");
1152 stack[stptr-1] = fmod(stack[stptr-1],stack[stptr]);
1157 rrd_set_error("RPN stack underflow");
1161 stack[stptr] = sin(stack[stptr]);
1165 rrd_set_error("RPN stack underflow");
1169 stack[stptr] = cos(stack[stptr]);
1173 rrd_set_error("RPN stack underflow");
1177 stack[stptr] = ceil(stack[stptr]);
1181 rrd_set_error("RPN stack underflow");
1185 stack[stptr] = floor(stack[stptr]);
1189 rrd_set_error("RPN stack underflow");
1193 stack[stptr] = log(stack[stptr]);
1197 rrd_set_error("RPN stack underflow");
1201 stack[stptr+1] = stack[stptr];
1206 rrd_set_error("RPN stack underflow");
1214 rrd_set_error("RPN stack underflow");
1219 dummy = stack[stptr] ;
1220 stack[stptr] = stack[stptr-1];
1221 stack[stptr-1] = dummy;
1226 rrd_set_error("RPN stack underflow");
1230 stack[stptr] = exp(stack[stptr]);
1234 rrd_set_error("RPN stack underflow");
1238 if (isnan(stack[stptr-1]) || isnan(stack[stptr]))
1239 stack[stptr-1] = 0.0;
1241 stack[stptr-1] = stack[stptr-1] < stack[stptr] ? 1.0 : 0.0;
1246 rrd_set_error("RPN stack underflow");
1250 if (isnan(stack[stptr-1]) || isnan(stack[stptr]))
1251 stack[stptr-1] = 0.0;
1253 stack[stptr-1] = stack[stptr-1] <= stack[stptr] ? 1.0 : 0.0;
1258 rrd_set_error("RPN stack underflow");
1262 if (isnan(stack[stptr-1]) || isnan(stack[stptr]))
1263 stack[stptr-1] = 0.0;
1265 stack[stptr-1] = stack[stptr-1] > stack[stptr] ? 1.0 : 0.0;
1270 rrd_set_error("RPN stack underflow");
1274 if (isnan(stack[stptr-1]) || isnan(stack[stptr]))
1275 stack[stptr-1] = 0.0;
1277 stack[stptr-1] = stack[stptr-1] >= stack[stptr] ? 1.0 : 0.0;
1282 rrd_set_error("RPN stack underflow");
1286 if (isnan(stack[stptr-1]) || isnan(stack[stptr]))
1287 stack[stptr-1] = 0.0;
1289 stack[stptr-1] = stack[stptr-1] == stack[stptr] ? 1.0 : 0.0;
1294 rrd_set_error("RPN stack underflow");
1298 stack[stptr-2] = stack[stptr-2] != 0.0 ? stack[stptr-1] : stack[stptr];
1304 rrd_set_error("RPN stack underflow");
1308 if (isnan(stack[stptr-1]))
1310 else if (isnan(stack[stptr]))
1311 stack[stptr-1] = stack[stptr];
1312 else if (stack[stptr-1] > stack[stptr])
1313 stack[stptr-1] = stack[stptr];
1318 rrd_set_error("RPN stack underflow");
1322 if (isnan(stack[stptr-1]))
1324 else if (isnan(stack[stptr]))
1325 stack[stptr-1] = stack[stptr];
1326 else if (stack[stptr-1] < stack[stptr])
1327 stack[stptr-1] = stack[stptr];
1332 rrd_set_error("RPN stack underflow");
1336 if (isnan(stack[stptr-2]))
1338 else if (isnan(stack[stptr-1]))
1339 stack[stptr-2] = stack[stptr-1];
1340 else if (isnan(stack[stptr]))
1341 stack[stptr-2] = stack[stptr];
1342 else if (stack[stptr-2] < stack[stptr-1])
1343 stack[stptr-2] = DNAN;
1344 else if (stack[stptr-2] > stack[stptr])
1345 stack[stptr-2] = DNAN;
1350 rrd_set_error("RPN stack underflow");
1354 stack[stptr] = isnan(stack[stptr]) ? 1.0 : 0.0;
1361 rrd_set_error("RPN final stack size != 1");
1365 im->gdes[gdi].data[++dataidx] = stack[0];
1374 /* massage data so, that we get one value for each x coordinate in the graph */
1376 data_proc( image_desc_t *im ){
1378 double pixstep = (double)(im->end-im->start)
1379 /(double)im->xsize; /* how much time
1380 passes in one pixel */
1382 double minval=DNAN,maxval=DNAN;
1384 unsigned long gr_time;
1386 /* memory for the processed data */
1387 for(i=0;i<im->gdes_c;i++){
1388 if((im->gdes[i].gf==GF_LINE1) ||
1389 (im->gdes[i].gf==GF_LINE2) ||
1390 (im->gdes[i].gf==GF_LINE3) ||
1391 (im->gdes[i].gf==GF_AREA) ||
1392 (im->gdes[i].gf==GF_STACK)){
1393 if((im->gdes[i].p_data = malloc((im->xsize +1)
1394 * sizeof(rrd_value_t)))==NULL){
1395 rrd_set_error("malloc data_proc");
1401 for(i=0;i<im->xsize;i++){
1403 gr_time = im->start+pixstep*i; /* time of the
1407 for(ii=0;ii<im->gdes_c;ii++){
1409 switch(im->gdes[ii].gf){
1416 vidx = im->gdes[ii].vidx;
1419 im->gdes[vidx].data[
1420 ((unsigned long)floor((double)
1421 (gr_time - im->gdes[vidx].start )
1422 / im->gdes[vidx].step)+1)
1424 /* added one because data was not being aligned properly
1425 this fixes it. We may also be having a problem in fetch ... */
1427 *im->gdes[vidx].ds_cnt
1428 +im->gdes[vidx].ds];
1430 if (! isnan(value)) {
1432 im->gdes[ii].p_data[i] = paintval;
1433 if (finite(paintval)){
1434 if (isnan(minval) || paintval < minval)
1436 if (isnan(maxval) || paintval > maxval)
1440 im->gdes[ii].p_data[i] = DNAN;
1455 /* if min or max have not been asigned a value this is because
1456 there was no data in the graph ... this is not good ...
1457 lets set these to dummy values then ... */
1459 if (isnan(minval)) minval = 0.0;
1460 if (isnan(maxval)) maxval = 1.0;
1462 /* adjust min and max values */
1463 if (isnan(im->minval)
1464 || ((!im->logarithmic && !im->rigid) /* don't adjust low-end with log scale */
1465 && im->minval > minval))
1466 im->minval = minval;
1467 if (isnan(im->maxval)
1469 && im->maxval < maxval)){
1470 if (im->logarithmic)
1471 im->maxval = maxval * 1.1;
1473 im->maxval = maxval;
1475 /* make sure min and max are not equal */
1476 if (im->minval == im->maxval) {
1478 if (! im->logarithmic) {
1482 /* make sure min and max are not both zero */
1483 if (im->maxval == 0.0) {
1493 /* identify the point where the first gridline, label ... gets placed */
1497 time_t start, /* what is the initial time */
1498 enum tmt_en baseint, /* what is the basic interval */
1499 long basestep /* how many if these do we jump a time */
1503 tm = *localtime(&start);
1506 tm.tm_sec -= tm.tm_sec % basestep; break;
1509 tm.tm_min -= tm.tm_min % basestep;
1514 tm.tm_hour -= tm.tm_hour % basestep; break;
1516 /* we do NOT look at the basestep for this ... */
1519 tm.tm_hour = 0; break;
1521 /* we do NOT look at the basestep for this ... */
1525 tm.tm_mday -= tm.tm_wday -1; /* -1 because we want the monday */
1526 if (tm.tm_wday==0) tm.tm_mday -= 7; /* we want the *previous* monday */
1533 tm.tm_mon -= tm.tm_mon % basestep; break;
1541 tm.tm_year -= (tm.tm_year+1900) % basestep;
1546 /* identify the point where the next gridline, label ... gets placed */
1549 time_t current, /* what is the initial time */
1550 enum tmt_en baseint, /* what is the basic interval */
1551 long basestep /* how many if these do we jump a time */
1556 tm = *localtime(¤t);
1560 tm.tm_sec += basestep; break;
1562 tm.tm_min += basestep; break;
1564 tm.tm_hour += basestep; break;
1566 tm.tm_mday += basestep; break;
1568 tm.tm_mday += 7*basestep; break;
1570 tm.tm_mon += basestep; break;
1572 tm.tm_year += basestep;
1574 madetime = mktime(&tm);
1575 } while (madetime == -1); /* this is necessary to skip impssible times
1576 like the daylight saving time skips */
1581 void gator( gdImagePtr gif, int x, int y){
1583 /* this function puts the name of the author and the tool into the
1584 graph. Remove if you must, but please note, that it is here,
1585 because I would like people who look at rrdtool generated graphs to
1586 see what was used to do it. No obviously you can also add a credit
1587 line to your webpage or printed document, this is fine with me. But
1588 as I have no control over this, I added the little tag in here.
1591 /* the fact that the text of what gets put into the graph is not
1592 visible in the function, has lead some to think this is for
1593 obfuscation reasons. While this is a nice side effect (I addmit),
1594 it is not the prime reason. The prime reason is, that the font
1595 used, is so small, that I had to hand edit the characters to ensure
1596 readability. I could thus not use the normal gd functions to write,
1597 but had to embed a slightly compressed bitmap version into the code.
1600 int li[]={0,0,1, 0,4,5, 0,8,9, 0,12,14, 0,17,17, 0,21,21,
1601 0,24,24, 0,34,34, 0,40,42, 0,45,45, 0,48,49, 0,52,54,
1602 0,61,61, 0,64,66, 0,68,70, 0,72,74, 0,76,76, 0,78,78,
1604 1,0,0, 1,2,2, 1,4,4, 1,6,6, 1,8,8, 1,10,10,
1605 1,13,13, 1,16,16, 1,18,18, 1,20,20, 1,22,22, 1,24,24,
1606 1,34,34, 1,41,41, 1,44,44, 1,46,46, 1,48,48, 1,50,50,
1607 1,53,53, 1,60,60, 1,62,62, 1,64,64, 1,69,69, 1,73,73,
1608 1,76,76, 1,78,78, 1,80,80, 1,84,84, 1,86,86,
1609 2,0,1, 2,4,5, 2,8,8, 2,10,10, 2,13,13, 2,16,16,
1610 2,18,18, 2,20,20, 2,22,22, 2,24,24, 2,33,33, 2,41,41,
1611 2,44,44, 2,46,46, 2,48,49, 2,53,53, 2,60,60, 2,62,62,
1612 2,64,65, 2,69,69, 2,73,73, 2,76,77, 2,80,81, 2,84,85,
1613 3,0,0, 3,2,2, 3,4,4, 3,6,6, 3,8,8, 3,10,10,
1614 3,13,13, 3,16,16, 3,18,18, 3,20,20, 3,22,22, 3,24,24,
1615 3,32,32, 3,41,41, 3,44,44, 3,46,46, 3,48,48, 3,50,50,
1616 3,53,53, 3,60,60, 3,62,62, 3,64,64, 3,69,69, 3,73,73,
1617 3,76,76, 3,78,78, 3,80,80, 3,84,84, 3,86,86,
1618 4,0,0, 4,2,2, 4,4,4, 4,6,6, 4,8,9, 4,13,13,
1619 4,17,17, 4,21,21, 4,24,26, 4,32,32, 4,41,41, 4,45,45,
1620 4,48,49, 4,52,54, 4,61,61, 4,64,66, 4,69,69, 4,72,74,
1621 4,76,76, 4,78,78, 4,80,82, 4,84,84};
1623 for(i=0; i<DIM(li); i=i+3)
1624 for(ii=y+li[i+1]; ii<=y+li[i+2];ii++)
1625 gdImageSetPixel(gif,x-li[i],ii,graph_col[GRC_GRID].i);
1629 /* calculate values required for PRINT and GPRINT functions */
1632 print_calc(image_desc_t *im, char ***prdata)
1634 long i,ii,validsteps;
1636 int graphelement = 0;
1639 double magfact = -1;
1643 if (im->imginfo) prlines++;
1644 for(i=0;i<im->gdes_c;i++){
1645 switch(im->gdes[i].gf){
1648 if(((*prdata) = rrd_realloc((*prdata),prlines*sizeof(char *)))==NULL){
1649 rrd_set_error("realloc prdata");
1653 vidx = im->gdes[i].vidx;
1654 max_ii =((im->gdes[vidx].end
1655 - im->gdes[vidx].start)
1656 /im->gdes[vidx].step
1657 *im->gdes[vidx].ds_cnt);
1660 for(ii=im->gdes[vidx].ds+im->gdes[vidx].ds_cnt;
1661 ii < max_ii+im->gdes[vidx].ds_cnt;
1662 ii+=im->gdes[vidx].ds_cnt){
1663 if (! finite(im->gdes[vidx].data[ii]))
1665 if (isnan(printval)){
1666 printval = im->gdes[vidx].data[ii];
1671 switch (im->gdes[i].cf){
1674 printval += im->gdes[vidx].data[ii];
1677 printval = min( printval, im->gdes[vidx].data[ii]);
1680 printval = max( printval, im->gdes[vidx].data[ii]);
1683 printval = im->gdes[vidx].data[ii];
1686 if (im->gdes[i].cf == CF_AVERAGE) {
1687 if (validsteps > 1) {
1688 printval = (printval / validsteps);
1691 if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) {
1692 /* Magfact is set to -1 upon entry to print_calc. If it
1693 * is still less than 0, then we need to run auto_scale.
1694 * Otherwise, put the value into the correct units. If
1695 * the value is 0, then do not set the symbol or magnification
1696 * so next the calculation will be performed again. */
1697 if (magfact < 0.0) {
1698 auto_scale(im,&printval,&si_symb,&magfact);
1699 if (printval == 0.0)
1702 printval /= magfact;
1704 *(++percent_s) = 's';
1706 else if (strstr(im->gdes[i].format,"%s") != NULL) {
1707 auto_scale(im,&printval,&si_symb,&magfact);
1709 if (im->gdes[i].gf == GF_PRINT){
1710 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1711 if (bad_format(im->gdes[i].format)) {
1712 rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1715 #ifdef HAVE_SNPRINTF
1716 snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
1718 sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
1720 (*prdata)[prlines-1] = NULL;
1724 if (bad_format(im->gdes[i].format)) {
1725 rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1728 #ifdef HAVE_SNPRINTF
1729 snprintf(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
1731 sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
1751 return graphelement;
1755 /* place legends with color spots */
1757 leg_place(image_desc_t *im)
1760 int interleg = SmallFont->w*2;
1761 int box = SmallFont->h*1.2;
1762 int border = SmallFont->w*2;
1763 int fill=0, fill_last;
1765 int leg_x = border, leg_y = im->ygif;
1769 char prt_fctn; /*special printfunctions */
1772 if( !(im->extra_flags & NOLEGEND) ) {
1773 if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1774 rrd_set_error("malloc for legspace");
1778 for(i=0;i<im->gdes_c;i++){
1781 leg_cc = strlen(im->gdes[i].legend);
1783 /* is there a controle code ant the end of the legend string ? */
1784 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\') {
1785 prt_fctn = im->gdes[i].legend[leg_cc-1];
1787 im->gdes[i].legend[leg_cc] = '\0';
1791 /* remove exess space */
1792 while (prt_fctn=='g' &&
1794 im->gdes[i].legend[leg_cc-1]==' '){
1796 im->gdes[i].legend[leg_cc]='\0';
1799 legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1802 /* no interleg space if string ends in \g */
1803 fill += legspace[i];
1805 if (im->gdes[i].gf != GF_GPRINT &&
1806 im->gdes[i].gf != GF_COMMENT) {
1809 fill += leg_cc * SmallFont->w;
1814 /* who said there was a special tag ... ?*/
1815 if (prt_fctn=='g') {
1818 if (prt_fctn == '\0') {
1819 if (i == im->gdes_c -1 ) prt_fctn ='l';
1821 /* is it time to place the legends ? */
1822 if (fill > im->xgif - 2*border){
1837 if (prt_fctn != '\0'){
1839 if (leg_c >= 2 && prt_fctn == 'j') {
1840 glue = (im->xgif - fill - 2* border) / (leg_c-1);
1841 /* if (glue > 2 * SmallFont->w) glue = 0; */
1845 if (prt_fctn =='c') leg_x = (im->xgif - fill) / 2.0;
1846 if (prt_fctn =='r') leg_x = im->xgif - fill - border;
1848 for(ii=mark;ii<=i;ii++){
1849 if(im->gdes[ii].legend[0]=='\0')
1851 im->gdes[ii].legloc.x = leg_x;
1852 im->gdes[ii].legloc.y = leg_y;
1854 + strlen(im->gdes[ii].legend)*SmallFont->w
1857 if (im->gdes[ii].gf != GF_GPRINT &&
1858 im->gdes[ii].gf != GF_COMMENT)
1861 leg_y = leg_y + SmallFont->h*1.2;
1862 if (prt_fctn == 's') leg_y -= SmallFont->h *0.5;
1874 /* create a grid on the graph. it determines what to do
1875 from the values of xsize, start and end */
1877 /* the xaxis labels are determined from the number of seconds per pixel
1878 in the requested graph */
1883 horizontal_grid(gdImagePtr gif, image_desc_t *im)
1891 char graph_label[100];
1892 gdPoint polyPoints[4];
1893 int labfact,gridind;
1894 int styleMinor[2],styleMajor[2];
1895 int decimals, fractionals;
1900 range = im->maxval - im->minval;
1901 scaledrange = range / im->magfact;
1903 /* does the scale of this graph make it impossible to put lines
1904 on it? If so, give up. */
1905 if (isnan(scaledrange)) {
1909 styleMinor[0] = graph_col[GRC_GRID].i;
1910 styleMinor[1] = gdTransparent;
1912 styleMajor[0] = graph_col[GRC_MGRID].i;
1913 styleMajor[1] = gdTransparent;
1915 /* find grid spaceing */
1917 if(isnan(im->ygridstep)){
1918 if(im->extra_flags & ALTYGRID) {
1919 /* find the value with max number of digits. Get number of digits */
1920 decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))));
1921 if(decimals <= 0) /* everything is small. make place for zero */
1924 fractionals = floor(log10(range));
1925 if(fractionals < 0) /* small amplitude. */
1926 sprintf(labfmt, "%%%d.%df", decimals - fractionals + 1, -fractionals + 1);
1928 sprintf(labfmt, "%%%d.1f", decimals + 1);
1929 gridstep = pow((double)10, (double)fractionals);
1930 if(gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1932 /* should have at least 5 lines but no more then 15 */
1933 if(range/gridstep < 5)
1935 if(range/gridstep > 15)
1937 if(range/gridstep > 5) {
1939 if(range/gridstep > 8)
1948 for(i=0;ylab[i].grid > 0;i++){
1949 pixel = im->ysize / (scaledrange / ylab[i].grid);
1950 if (gridind == -1 && pixel > 5) {
1957 if (pixel * ylab[gridind].lfac[i] >= 2 * SmallFont->h) {
1958 labfact = ylab[gridind].lfac[i];
1963 gridstep = ylab[gridind].grid * im->magfact;
1966 gridstep = im->ygridstep;
1967 labfact = im->ylabfact;
1970 polyPoints[0].x=im->xorigin;
1971 polyPoints[1].x=im->xorigin+im->xsize;
1972 sgrid = (int)( im->minval / gridstep - 1);
1973 egrid = (int)( im->maxval / gridstep + 1);
1974 scaledstep = gridstep/im->magfact;
1975 for (i = sgrid; i <= egrid; i++){
1976 polyPoints[0].y=ytr(im,gridstep*i);
1977 if ( polyPoints[0].y >= im->yorigin-im->ysize
1978 && polyPoints[0].y <= im->yorigin) {
1979 if(i % labfact == 0){
1980 if (i==0 || im->symbol == ' ') {
1982 if(im->extra_flags & ALTYGRID) {
1983 sprintf(graph_label,labfmt,scaledstep*i);
1986 sprintf(graph_label,"%4.1f",scaledstep*i);
1989 sprintf(graph_label,"%4.0f",scaledstep*i);
1993 sprintf(graph_label,"%4.1f %c",scaledstep*i, im->symbol);
1995 sprintf(graph_label,"%4.0f %c",scaledstep*i, im->symbol);
1999 gdImageString(gif, SmallFont,
2000 (polyPoints[0].x - (strlen(graph_label) *
2002 polyPoints[0].y - SmallFont->h/2+1,
2003 (unsigned char *)graph_label, graph_col[GRC_FONT].i);
2005 gdImageSetStyle(gif, styleMajor, 2);
2007 gdImageLine(gif, polyPoints[0].x-2,polyPoints[0].y,
2008 polyPoints[0].x+2,polyPoints[0].y,graph_col[GRC_MGRID].i);
2009 gdImageLine(gif, polyPoints[1].x-2,polyPoints[0].y,
2010 polyPoints[1].x+2,polyPoints[0].y,graph_col[GRC_MGRID].i);
2012 gdImageSetStyle(gif, styleMinor, 2);
2013 gdImageLine(gif, polyPoints[0].x-1,polyPoints[0].y,
2014 polyPoints[0].x+1,polyPoints[0].y,graph_col[GRC_GRID].i);
2015 gdImageLine(gif, polyPoints[1].x-1,polyPoints[0].y,
2016 polyPoints[1].x+1,polyPoints[0].y,graph_col[GRC_GRID].i);
2018 gdImageLine(gif, polyPoints[0].x,polyPoints[0].y,
2019 polyPoints[1].x,polyPoints[0].y,gdStyled);
2022 /* if(im->minval * im->maxval < 0){
2023 polyPoints[0].y=ytr(0);
2024 gdImageLine(gif, polyPoints[0].x,polyPoints[0].y,
2025 polyPoints[1].x,polyPoints[0].y,graph_col[GRC_MGRID].i);
2031 /* logaritmic horizontal grid */
2033 horizontal_log_grid(gdImagePtr gif, image_desc_t *im)
2037 int minoridx=0, majoridx=0;
2038 char graph_label[100];
2039 gdPoint polyPoints[4];
2040 int styleMinor[2],styleMajor[2];
2041 double value, pixperstep, minstep;
2043 /* find grid spaceing */
2044 pixpex= (double)im->ysize / (log10(im->maxval) - log10(im->minval));
2046 if (isnan(pixpex)) {
2050 for(i=0;yloglab[i][0] > 0;i++){
2051 minstep = log10(yloglab[i][0]);
2052 for(ii=1;yloglab[i][ii+1] > 0;ii++){
2053 if(yloglab[i][ii+2]==0){
2054 minstep = log10(yloglab[i][ii+1])-log10(yloglab[i][ii]);
2058 pixperstep = pixpex * minstep;
2059 if(pixperstep > 5){minoridx = i;}
2060 if(pixperstep > 2 * SmallFont->h){majoridx = i;}
2063 styleMinor[0] = graph_col[GRC_GRID].i;
2064 styleMinor[1] = gdTransparent;
2066 styleMajor[0] = graph_col[GRC_MGRID].i;
2067 styleMajor[1] = gdTransparent;
2069 polyPoints[0].x=im->xorigin;
2070 polyPoints[1].x=im->xorigin+im->xsize;
2071 /* paint minor grid */
2072 for (value = pow((double)10, log10(im->minval)
2073 - fmod(log10(im->minval),log10(yloglab[minoridx][0])));
2074 value <= im->maxval;
2075 value *= yloglab[minoridx][0]){
2076 if (value < im->minval) continue;
2078 while(yloglab[minoridx][++i] > 0){
2079 polyPoints[0].y = ytr(im,value * yloglab[minoridx][i]);
2080 if (polyPoints[0].y <= im->yorigin - im->ysize) break;
2081 gdImageSetStyle(gif, styleMinor, 2);
2082 gdImageLine(gif, polyPoints[0].x-1,polyPoints[0].y,
2083 polyPoints[0].x+1,polyPoints[0].y,graph_col[GRC_GRID].i);
2084 gdImageLine(gif, polyPoints[1].x-1,polyPoints[0].y,
2085 polyPoints[1].x+1,polyPoints[0].y,graph_col[GRC_GRID].i);
2087 gdImageLine(gif, polyPoints[0].x,polyPoints[0].y,
2088 polyPoints[1].x,polyPoints[0].y,gdStyled);
2092 /* paint major grid and labels*/
2093 for (value = pow((double)10, log10(im->minval)
2094 - fmod(log10(im->minval),log10(yloglab[majoridx][0])));
2095 value <= im->maxval;
2096 value *= yloglab[majoridx][0]){
2097 if (value < im->minval) continue;
2099 while(yloglab[majoridx][++i] > 0){
2100 polyPoints[0].y = ytr(im,value * yloglab[majoridx][i]);
2101 if (polyPoints[0].y <= im->yorigin - im->ysize) break;
2102 gdImageSetStyle(gif, styleMajor, 2);
2103 gdImageLine(gif, polyPoints[0].x-2,polyPoints[0].y,
2104 polyPoints[0].x+2,polyPoints[0].y,graph_col[GRC_MGRID].i);
2105 gdImageLine(gif, polyPoints[1].x-2,polyPoints[0].y,
2106 polyPoints[1].x+2,polyPoints[0].y,graph_col[GRC_MGRID].i);
2108 gdImageLine(gif, polyPoints[0].x,polyPoints[0].y,
2109 polyPoints[1].x,polyPoints[0].y,gdStyled);
2110 sprintf(graph_label,"%3.0e",value * yloglab[majoridx][i]);
2111 gdImageString(gif, SmallFont,
2112 (polyPoints[0].x - (strlen(graph_label) *
2114 polyPoints[0].y - SmallFont->h/2+1,
2115 (unsigned char *)graph_label, graph_col[GRC_FONT].i);
2127 int xlab_sel; /* which sort of label and grid ? */
2130 char graph_label[100];
2131 gdPoint polyPoints[4]; /* points for filled graph and more*/
2133 /* style for grid lines */
2137 /* the type of time grid is determined by finding
2138 the number of seconds per pixel in the graph */
2141 if(im->xlab_user.minsec == -1){
2142 factor=(im->end - im->start)/im->xsize;
2144 while ( xlab[xlab_sel+1].minsec != -1
2145 && xlab[xlab_sel+1].minsec <= factor){ xlab_sel++; }
2146 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2147 im->xlab_user.gridst = xlab[xlab_sel].gridst;
2148 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2149 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2150 im->xlab_user.labtm = xlab[xlab_sel].labtm;
2151 im->xlab_user.labst = xlab[xlab_sel].labst;
2152 im->xlab_user.precis = xlab[xlab_sel].precis;
2153 im->xlab_user.stst = xlab[xlab_sel].stst;
2156 /* y coords are the same for every line ... */
2157 polyPoints[0].y = im->yorigin;
2158 polyPoints[1].y = im->yorigin-im->ysize;
2160 /* paint the minor grid */
2161 for(ti = find_first_time(im->start,
2162 im->xlab_user.gridtm,
2163 im->xlab_user.gridst);
2165 ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
2167 /* are we inside the graph ? */
2168 if (ti < im->start || ti > im->end) continue;
2169 polyPoints[0].x = xtr(im,ti);
2170 styleDotted[0] = graph_col[GRC_GRID].i;
2171 styleDotted[1] = gdTransparent;
2173 gdImageSetStyle(gif, styleDotted, 2);
2175 gdImageLine(gif, polyPoints[0].x,polyPoints[0].y,
2176 polyPoints[0].x,polyPoints[1].y,gdStyled);
2177 gdImageLine(gif, polyPoints[0].x,polyPoints[0].y-1,
2178 polyPoints[0].x,polyPoints[0].y+1,graph_col[GRC_GRID].i);
2179 gdImageLine(gif, polyPoints[0].x,polyPoints[1].y-1,
2180 polyPoints[0].x,polyPoints[1].y+1,graph_col[GRC_GRID].i);
2183 /* paint the major grid */
2184 for(ti = find_first_time(im->start,
2185 im->xlab_user.mgridtm,
2186 im->xlab_user.mgridst);
2188 ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
2190 /* are we inside the graph ? */
2191 if (ti < im->start || ti > im->end) continue;
2192 polyPoints[0].x = xtr(im,ti);
2193 styleDotted[0] = graph_col[GRC_MGRID].i;
2194 styleDotted[1] = gdTransparent;
2195 gdImageSetStyle(gif, styleDotted, 2);
2197 gdImageLine(gif, polyPoints[0].x,polyPoints[0].y,
2198 polyPoints[0].x,polyPoints[1].y,gdStyled);
2199 gdImageLine(gif, polyPoints[0].x,polyPoints[0].y-2,
2200 polyPoints[0].x,polyPoints[0].y+2,graph_col[GRC_MGRID].i);
2201 gdImageLine(gif, polyPoints[0].x,polyPoints[1].y-2,
2202 polyPoints[0].x,polyPoints[1].y+2,graph_col[GRC_MGRID].i);
2204 /* paint the labels below the graph */
2205 for(ti = find_first_time(im->start,
2206 im->xlab_user.labtm,
2207 im->xlab_user.labst);
2209 ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
2212 tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
2215 strftime(graph_label,99,im->xlab_user.stst,localtime(&tilab));
2217 # error "your libc has no strftime I guess we'll abort the exercise here."
2219 width=strlen(graph_label) * SmallFont->w;
2220 gr_pos=xtr(im,tilab) - width/2;
2221 if (gr_pos >= im->xorigin
2222 && gr_pos + width <= im->xorigin+im->xsize)
2223 gdImageString(gif, SmallFont,
2224 gr_pos, polyPoints[0].y+4,
2225 (unsigned char *)graph_label, graph_col[GRC_FONT].i);
2237 /* draw x and y axis */
2238 gdImageLine(gif, im->xorigin+im->xsize,im->yorigin,
2239 im->xorigin+im->xsize,im->yorigin-im->ysize,
2240 graph_col[GRC_GRID].i);
2242 gdImageLine(gif, im->xorigin,im->yorigin-im->ysize,
2243 im->xorigin+im->xsize,im->yorigin-im->ysize,
2244 graph_col[GRC_GRID].i);
2246 gdImageLine(gif, im->xorigin-4,im->yorigin,
2247 im->xorigin+im->xsize+4,im->yorigin,
2248 graph_col[GRC_FONT].i);
2250 gdImageLine(gif, im->xorigin,im->yorigin,
2251 im->xorigin,im->yorigin-im->ysize,
2252 graph_col[GRC_GRID].i);
2254 /* arrow for X axis direction */
2255 gdImageLine(gif, im->xorigin+im->xsize+4, im->yorigin-3, im->xorigin+im->xsize+4, im->yorigin+3,graph_col[GRC_ARROW].i);
2256 gdImageLine(gif, im->xorigin+im->xsize+4, im->yorigin-3, im->xorigin+im->xsize+9, im->yorigin,graph_col[GRC_ARROW].i);
2257 gdImageLine(gif, im->xorigin+im->xsize+4, im->yorigin+3, im->xorigin+im->xsize+9, im->yorigin,graph_col[GRC_ARROW].i);
2259 /* gdImageLine(gif, im->xorigin+im->xsize-1, im->yorigin-3, im->xorigin+im->xsize-1, im->yorigin+3,graph_col[GRC_MGRID].i);
2260 gdImageLine(gif, im->xorigin+im->xsize, im->yorigin-2, im->xorigin+im->xsize, im->yorigin+2,graph_col[GRC_MGRID].i);
2261 gdImageLine(gif, im->xorigin+im->xsize+1, im->yorigin-2, im->xorigin+im->xsize+1, im->yorigin+2,graph_col[GRC_MGRID].i);
2262 gdImageLine(gif, im->xorigin+im->xsize+2, im->yorigin-2, im->xorigin+im->xsize+2, im->yorigin+2,graph_col[GRC_MGRID].i);
2263 gdImageLine(gif, im->xorigin+im->xsize+3, im->yorigin-1, im->xorigin+im->xsize+3, im->yorigin+1,graph_col[GRC_MGRID].i);
2264 gdImageLine(gif, im->xorigin+im->xsize+4, im->yorigin-1, im->xorigin+im->xsize+4, im->yorigin+1,graph_col[GRC_MGRID].i);
2265 gdImageLine(gif, im->xorigin+im->xsize+5, im->yorigin, im->xorigin+im->xsize+5, im->yorigin,graph_col[GRC_MGRID].i); */
2280 gdPoint polyPoints[4]; /* points for filled graph and more*/
2282 /* draw 3d border */
2283 gdImageLine(gif,0,0,im->xgif-1,0,graph_col[GRC_SHADEA].i);
2284 gdImageLine(gif,1,1,im->xgif-2,1,graph_col[GRC_SHADEA].i);
2285 gdImageLine(gif,0,0,0,im->ygif-1,graph_col[GRC_SHADEA].i);
2286 gdImageLine(gif,1,1,1,im->ygif-2,graph_col[GRC_SHADEA].i);
2287 gdImageLine(gif,im->xgif-1,0,im->xgif-1,im->ygif-1,graph_col[GRC_SHADEB].i);
2288 gdImageLine(gif,0,im->ygif-1,im->xgif-1,im->ygif-1,graph_col[GRC_SHADEB].i);
2289 gdImageLine(gif,im->xgif-2,1,im->xgif-2,im->ygif-2,graph_col[GRC_SHADEB].i);
2290 gdImageLine(gif,1,im->ygif-2,im->xgif-2,im->ygif-2,graph_col[GRC_SHADEB].i);
2293 if (im->draw_x_grid == 1 )
2294 vertical_grid(gif, im);
2296 if (im->draw_y_grid == 1){
2297 if(im->logarithmic){
2298 res = horizontal_log_grid(gif,im);
2300 res = horizontal_grid(gif,im);
2303 /* dont draw horizontal grid if there is no min and max val */
2305 char *nodata = "No Data found";
2306 gdImageString(gif, LargeFont,
2308 - (strlen(nodata)*LargeFont->w)/2,
2309 (2*im->yorigin-im->ysize) / 2,
2310 (unsigned char *)nodata, graph_col[GRC_FONT].i);
2314 /* yaxis description */
2315 gdImageStringUp(gif, SmallFont,
2317 (im->yorigin - im->ysize/2
2318 +(strlen(im->ylegend)*SmallFont->w)/2 ),
2319 (unsigned char *)im->ylegend, graph_col[GRC_FONT].i);
2323 gdImageString(gif, LargeFont,
2325 - (strlen(im->title)*LargeFont->w)/2,
2327 (unsigned char *)im->title, graph_col[GRC_FONT].i);
2330 if( !(im->extra_flags & NOLEGEND) ) {
2331 for(i=0;i<im->gdes_c;i++){
2332 if(im->gdes[i].legend[0] =='\0')
2335 if(im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT){
2337 polyPoints[0].x = im->gdes[i].legloc.x;
2338 polyPoints[0].y = im->gdes[i].legloc.y+1;
2339 polyPoints[1].x = polyPoints[0].x+boxH;
2340 polyPoints[2].x = polyPoints[0].x+boxH;
2341 polyPoints[3].x = polyPoints[0].x;
2342 polyPoints[1].y = polyPoints[0].y;
2343 polyPoints[2].y = polyPoints[0].y+boxV;
2344 polyPoints[3].y = polyPoints[0].y+boxV;
2345 gdImageFilledPolygon(gif,polyPoints,4,im->gdes[i].col.i);
2346 gdImagePolygon(gif,polyPoints,4,graph_col[GRC_FRAME].i);
2348 gdImageString(gif, SmallFont,
2349 polyPoints[0].x+boxH+6,
2351 (unsigned char *)im->gdes[i].legend,
2352 graph_col[GRC_FONT].i);
2354 polyPoints[0].x = im->gdes[i].legloc.x;
2355 polyPoints[0].y = im->gdes[i].legloc.y;
2357 gdImageString(gif, SmallFont,
2360 (unsigned char *)im->gdes[i].legend,
2361 graph_col[GRC_FONT].i);
2367 gator(gif, (int) im->xgif-5, 5);
2373 MkLineBrush(image_desc_t *im,long cosel, enum gf_en typsel){
2378 brush=gdImageCreate(1,1);
2381 brush=gdImageCreate(2,2);
2384 brush=gdImageCreate(3,3);
2390 gdImageColorTransparent(brush,
2391 gdImageColorAllocate(brush, 0, 0, 0));
2393 pen = gdImageColorAllocate(brush,
2394 im->gdes[cosel].col.red,
2395 im->gdes[cosel].col.green,
2396 im->gdes[cosel].col.blue);
2400 gdImageSetPixel(brush,0,0,pen);
2403 gdImageSetPixel(brush,0,0,pen);
2404 gdImageSetPixel(brush,0,1,pen);
2405 gdImageSetPixel(brush,1,0,pen);
2406 gdImageSetPixel(brush,1,1,pen);
2409 gdImageSetPixel(brush,1,0,pen);
2410 gdImageSetPixel(brush,0,1,pen);
2411 gdImageSetPixel(brush,1,1,pen);
2412 gdImageSetPixel(brush,2,1,pen);
2413 gdImageSetPixel(brush,1,2,pen);
2420 /*****************************************************
2421 * lazy check make sure we rely need to create this graph
2422 *****************************************************/
2424 int lazy_check(image_desc_t *im){
2427 struct stat gifstat;
2429 if (im->lazy == 0) return 0; /* no lazy option */
2430 if (stat(im->graphfile,&gifstat) != 0)
2431 return 0; /* can't stat */
2432 /* one pixel in the existing graph is more then what we would
2434 if (time(NULL) - gifstat.st_mtime >
2435 (im->end - im->start) / im->xsize)
2437 if ((fd = fopen(im->graphfile,"rb")) == NULL)
2438 return 0; /* the file does not exist */
2439 switch (im->imgformat) {
2441 size = GifSize(fd,&(im->xgif),&(im->ygif));
2444 size = PngSize(fd,&(im->xgif),&(im->ygif));
2451 /* draw that picture thing ... */
2453 graph_paint(image_desc_t *im, char ***calcpr)
2456 int lazy = lazy_check(im);
2460 gdImagePtr gif,brush;
2462 double areazero = 0.0;
2463 enum gf_en stack_gf = GF_PRINT;
2464 graph_desc_t *lastgdes = NULL;
2465 gdPoint canvas[4], back[4]; /* points for canvas*/
2467 /* if we are lazy and there is nothing to PRINT ... quit now */
2468 if (lazy && im->prt_c==0) return 0;
2470 /* pull the data from the rrd files ... */
2472 if(data_fetch(im)==-1)
2475 /* evaluate CDEF operations ... */
2476 if(data_calc(im)==-1)
2479 /* calculate and PRINT and GPRINT definitions. We have to do it at
2480 * this point because it will affect the length of the legends
2481 * if there are no graph elements we stop here ...
2482 * if we are lazy, try to quit ...
2484 i=print_calc(im,calcpr);
2486 if(i==0 || lazy) return 0;
2488 /* get actual drawing data and find min and max values*/
2489 if(data_proc(im)==-1)
2492 if(!im->logarithmic){si_unit(im);} /* identify si magnitude Kilo, Mega Giga ? */
2494 if(!im->rigid && ! im->logarithmic)
2495 expand_range(im); /* make sure the upper and lower limit are
2498 /* init xtr and ytr */
2499 /* determine the actual size of the gif to draw. The size given
2500 on the cmdline is the graph area. But we need more as we have
2501 draw labels and other things outside the graph area */
2504 im->xorigin = 10 + 9 * SmallFont->w+SmallFont->h;
2507 im->yorigin = 14 + im->ysize;
2510 if(im->title[0] != '\0')
2511 im->yorigin += (LargeFont->h+4);
2513 im->xgif=20+im->xsize + im->xorigin;
2514 im->ygif= im->yorigin+2*SmallFont->h;
2516 /* determine where to place the legends onto the graphics.
2517 and set im->ygif to match space requirements for text */
2518 if(leg_place(im)==-1)
2521 gif=gdImageCreate(im->xgif,im->ygif);
2523 gdImageInterlace(gif, im->interlaced);
2525 /* allocate colors for the screen elements */
2526 for(i=0;i<DIM(graph_col);i++)
2527 /* check for user override values */
2528 if(im->graph_col[i].red != -1)
2530 gdImageColorAllocate( gif,
2531 im->graph_col[i].red,
2532 im->graph_col[i].green,
2533 im->graph_col[i].blue);
2536 gdImageColorAllocate( gif,
2542 /* allocate colors for the graph */
2543 for(i=0;i<im->gdes_c;i++)
2544 /* only for elements which have a color defined */
2545 if (im->gdes[i].col.red != -1)
2547 gdImageColorAllocate(gif,
2548 im->gdes[i].col.red,
2549 im->gdes[i].col.green,
2550 im->gdes[i].col.blue);
2553 /* the actual graph is created by going through the individual
2554 graph elements and then drawing them */
2558 back[1].x = back[0].x+im->xgif;
2559 back[1].y = back[0].y;
2560 back[2].x = back[1].x;
2561 back[2].y = back[0].y+im->ygif;
2562 back[3].x = back[0].x;
2563 back[3].y = back[2].y;
2565 gdImageFilledPolygon(gif,back,4,graph_col[GRC_BACK].i);
2567 canvas[0].x = im->xorigin;
2568 canvas[0].y = im->yorigin;
2569 canvas[1].x = canvas[0].x+im->xsize;
2570 canvas[1].y = canvas[0].y;
2571 canvas[2].x = canvas[1].x;
2572 canvas[2].y = canvas[0].y-im->ysize;
2573 canvas[3].x = canvas[0].x;
2574 canvas[3].y = canvas[2].y;
2576 gdImageFilledPolygon(gif,canvas,4,graph_col[GRC_CANVAS].i);
2578 if (im->minval > 0.0)
2579 areazero = im->minval;
2580 if (im->maxval < 0.0)
2581 areazero = im->maxval;
2585 for(i=0;i<im->gdes_c;i++){
2587 switch(im->gdes[i].gf){
2600 stack_gf = im->gdes[i].gf;
2602 /* fix data points at oo and -oo */
2603 for(ii=0;ii<im->xsize;ii++){
2604 if (isinf(im->gdes[i].p_data[ii])){
2605 if (im->gdes[i].p_data[ii] > 0) {
2606 im->gdes[i].p_data[ii] = im->maxval ;
2608 im->gdes[i].p_data[ii] = im->minval ;
2614 if (im->gdes[i].col.i != -1){
2615 /* GF_LINE and frined */
2616 if(stack_gf == GF_LINE1 || stack_gf == GF_LINE2 || stack_gf == GF_LINE3 ){
2617 brush = MkLineBrush(im,i,stack_gf);
2618 gdImageSetBrush(gif, brush);
2619 for(ii=1;ii<im->xsize;ii++){
2620 if (isnan(im->gdes[i].p_data[ii-1]) ||
2621 isnan(im->gdes[i].p_data[ii]))
2624 ii+im->xorigin-1,ytr(im,im->gdes[i].p_data[ii-1]),
2625 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2629 gdImageDestroy(brush);
2632 /* GF_AREA STACK type*/
2633 if (im->gdes[i].gf == GF_STACK )
2634 for(ii=0;ii<im->xsize;ii++){
2635 if(isnan(im->gdes[i].p_data[ii])){
2636 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
2640 if (lastgdes->p_data[ii] == im->gdes[i].p_data[ii]){
2644 ii+im->xorigin,ytr(im,lastgdes->p_data[ii]),
2645 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2649 else /* simple GF_AREA */
2650 for(ii=0;ii<im->xsize;ii++){
2651 if (isnan(im->gdes[i].p_data[ii])) {
2652 im->gdes[i].p_data[ii] = 0;
2656 ii+im->xorigin,ytr(im,areazero),
2657 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2661 lastgdes = &(im->gdes[i]);
2668 /* the RULES are the last thing to paint ... */
2669 for(i=0;i<im->gdes_c;i++){
2671 switch(im->gdes[i].gf){
2673 if(im->gdes[i].yrule >= im->minval
2674 && im->gdes[i].yrule <= im->maxval)
2676 im->xorigin,ytr(im,im->gdes[i].yrule),
2677 im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2681 if(im->gdes[i].xrule >= im->start
2682 && im->gdes[i].xrule <= im->end)
2684 xtr(im,im->gdes[i].xrule),im->yorigin,
2685 xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2693 if (strcmp(im->graphfile,"-")==0) {
2695 /* Change translation mode for stdout to BINARY */
2696 _setmode( _fileno( stdout ), O_BINARY );
2700 if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2701 rrd_set_error("Opening '%s' for write: %s",im->graphfile, strerror(errno));
2705 switch (im->imgformat) {
2707 gdImageGif(gif, fo);
2710 gdImagePng(gif, fo);
2713 if (strcmp(im->graphfile,"-") != 0)
2715 gdImageDestroy(gif);
2721 /*****************************************************
2723 *****************************************************/
2726 gdes_alloc(image_desc_t *im){
2728 long def_step = (im->end-im->start)/im->xsize;
2730 if (im->step > def_step) /* step can be increassed ... no decreassed */
2731 def_step = im->step;
2735 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2736 * sizeof(graph_desc_t)))==NULL){
2737 rrd_set_error("realloc graph_descs");
2742 im->gdes[im->gdes_c-1].step=def_step;
2743 im->gdes[im->gdes_c-1].start=im->start;
2744 im->gdes[im->gdes_c-1].end=im->end;
2745 im->gdes[im->gdes_c-1].vname[0]='\0';
2746 im->gdes[im->gdes_c-1].data=NULL;
2747 im->gdes[im->gdes_c-1].ds_namv=NULL;
2748 im->gdes[im->gdes_c-1].data_first=0;
2749 im->gdes[im->gdes_c-1].p_data=NULL;
2750 im->gdes[im->gdes_c-1].rpnp=NULL;
2751 im->gdes[im->gdes_c-1].col.red = -1;
2752 im->gdes[im->gdes_c-1].col.i=-1;
2753 im->gdes[im->gdes_c-1].legend[0]='\0';
2754 im->gdes[im->gdes_c-1].rrd[0]='\0';
2755 im->gdes[im->gdes_c-1].ds=-1;
2756 im->gdes[im->gdes_c-1].p_data=NULL;
2760 /* copies input untill the first unescaped colon is found
2761 or until input ends. backslashes have to be escaped as well */
2763 scan_for_col(char *input, int len, char *output)
2768 input[inp] != ':' &&
2771 if (input[inp] == '\\' &&
2772 input[inp+1] != '\0' &&
2773 (input[inp+1] == '\\' ||
2774 input[inp+1] == ':')){
2775 output[outp++] = input[++inp];
2778 output[outp++] = input[inp];
2781 output[outp] = '\0';
2786 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize)
2792 time_t start_tmp=0,end_tmp=0;
2793 char scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
2795 unsigned int col_red,col_green,col_blue;
2797 int linepass = 0; /* stack can only follow directly after LINE* AREA or STACK */
2798 struct time_value start_tv, end_tv;
2799 char *parsetime_error = NULL;
2804 parsetime("end-24h", &start_tv);
2805 parsetime("now", &end_tv);
2807 im.xlab_user.minsec = -1;
2813 im.ylegend[0] = '\0';
2818 im.unitsexponent= 9999;
2824 im.ygridstep = DNAN;
2831 im.imgformat = IF_GIF; /* we default to GIF output */
2833 for(i=0;i<DIM(graph_col);i++)
2834 im.graph_col[i].red=-1;
2838 static struct option long_options[] =
2840 {"start", required_argument, 0, 's'},
2841 {"end", required_argument, 0, 'e'},
2842 {"x-grid", required_argument, 0, 'x'},
2843 {"y-grid", required_argument, 0, 'y'},
2844 {"vertical-label",required_argument,0,'v'},
2845 {"width", required_argument, 0, 'w'},
2846 {"height", required_argument, 0, 'h'},
2847 {"interlaced", no_argument, 0, 'i'},
2848 {"upper-limit",required_argument, 0, 'u'},
2849 {"lower-limit",required_argument, 0, 'l'},
2850 {"rigid", no_argument, 0, 'r'},
2851 {"base", required_argument, 0, 'b'},
2852 {"logarithmic",no_argument, 0, 'o'},
2853 {"color", required_argument, 0, 'c'},
2854 {"title", required_argument, 0, 't'},
2855 {"imginfo", required_argument, 0, 'f'},
2856 {"imgformat", required_argument, 0, 'a'},
2857 {"lazy", no_argument, 0, 'z'},
2858 {"no-legend", no_argument, 0, 'g'},
2859 {"alt-y-grid", no_argument, 0, 257 },
2860 {"alt-autoscale", no_argument, 0, 258 },
2861 {"alt-autoscale-max", no_argument, 0, 259 },
2862 {"units-exponent",required_argument, 0, 260},
2863 {"step", required_argument, 0, 261},
2865 int option_index = 0;
2869 opt = getopt_long(argc, argv,
2870 "s:e:x:y:v:w:h:iu:l:rb:oc:t:f:a:z:g",
2871 long_options, &option_index);
2878 im.extra_flags |= ALTYGRID;
2881 im.extra_flags |= ALTAUTOSCALE;
2884 im.extra_flags |= ALTAUTOSCALE_MAX;
2887 im.extra_flags |= NOLEGEND;
2890 im.unitsexponent = atoi(optarg);
2893 im.step = atoi(optarg);
2896 if ((parsetime_error = parsetime(optarg, &start_tv))) {
2897 rrd_set_error( "start time: %s", parsetime_error );
2902 if ((parsetime_error = parsetime(optarg, &end_tv))) {
2903 rrd_set_error( "end time: %s", parsetime_error );
2908 if(strcmp(optarg,"none") == 0){
2914 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
2916 &im.xlab_user.gridst,
2918 &im.xlab_user.mgridst,
2920 &im.xlab_user.labst,
2921 &im.xlab_user.precis,
2922 &stroff) == 7 && stroff != 0){
2923 strncpy(im.xlab_form, optarg+stroff, sizeof(im.xlab_form) - 1);
2924 if((im.xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
2925 rrd_set_error("unknown keyword %s",scan_gtm);
2927 } else if ((im.xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
2928 rrd_set_error("unknown keyword %s",scan_mtm);
2930 } else if ((im.xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
2931 rrd_set_error("unknown keyword %s",scan_ltm);
2934 im.xlab_user.minsec = 1;
2935 im.xlab_user.stst = im.xlab_form;
2937 rrd_set_error("invalid x-grid format");
2943 if(strcmp(optarg,"none") == 0){
2951 &im.ylabfact) == 2) {
2952 if(im.ygridstep<=0){
2953 rrd_set_error("grid step must be > 0");
2955 } else if (im.ylabfact < 1){
2956 rrd_set_error("label factor must be > 0");
2960 rrd_set_error("invalid y-grid format");
2965 strncpy(im.ylegend,optarg,150);
2966 im.ylegend[150]='\0';
2969 im.maxval = atof(optarg);
2972 im.minval = atof(optarg);
2975 im.base = atol(optarg);
2976 if(im.base != 1024 && im.base != 1000 ){
2977 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
2982 long_tmp = atol(optarg);
2983 if (long_tmp < 10) {
2984 rrd_set_error("width below 10 pixels");
2987 im.xsize = long_tmp;
2990 long_tmp = atol(optarg);
2991 if (long_tmp < 10) {
2992 rrd_set_error("height below 10 pixels");
2995 im.ysize = long_tmp;
3004 im.imginfo = optarg;
3007 if((im.imgformat = if_conv(optarg)) == -1) {
3008 rrd_set_error("unsupported graphics format '%s'",optarg);
3017 if (isnan(im.minval))
3022 "%10[A-Z]#%2x%2x%2x",
3023 col_nam,&col_red,&col_green,&col_blue) == 4){
3025 if((ci=grc_conv(col_nam)) != -1){
3026 im.graph_col[ci].red=col_red;
3027 im.graph_col[ci].green=col_green;
3028 im.graph_col[ci].blue=col_blue;
3030 rrd_set_error("invalid color name '%s'",col_nam);
3033 rrd_set_error("invalid color def format");
3038 strncpy(im.title,optarg,150);
3044 rrd_set_error("unknown option '%c'", optopt);
3046 rrd_set_error("unknown option '%s'",argv[optind-1]);
3051 if (optind >= argc) {
3052 rrd_set_error("missing filename");
3056 if (im.logarithmic == 1 && (im.minval <= 0 || isnan(im.minval))){
3057 rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");
3061 strncpy(im.graphfile,argv[optind],MAXPATH-1);
3062 im.graphfile[MAXPATH-1]='\0';
3064 if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
3068 if (start_tmp < 3600*24*365*10){
3069 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
3073 if (end_tmp < start_tmp) {
3074 rrd_set_error("start (%ld) should be less than end (%ld)",
3075 start_tmp, end_tmp);
3079 im.start = start_tmp;
3083 for(i=optind+1;i<argc;i++){
3086 char varname[30],*rpnex;
3088 if(sscanf(argv[i],"%10[A-Z0-9]:%n",symname,&argstart)==1){
3089 if((im.gdes[im.gdes_c-1].gf=gf_conv(symname))==-1){
3091 rrd_set_error("unknown function '%s'",symname);
3095 rrd_set_error("can't parse '%s'",argv[i]);
3100 /* reset linepass if a non LINE/STACK/AREA operator gets parsed
3102 if (im.gdes[im.gdes_c-1].gf != GF_LINE1 &&
3103 im.gdes[im.gdes_c-1].gf != GF_LINE2 &&
3104 im.gdes[im.gdes_c-1].gf != GF_LINE3 &&
3105 im.gdes[im.gdes_c-1].gf != GF_AREA &&
3106 im.gdes[im.gdes_c-1].gf != GF_STACK) {
3111 switch(im.gdes[im.gdes_c-1].gf){
3117 "%29[^#:]:" CF_NAM_FMT ":%n",
3118 varname,symname,&strstart) == 2){
3119 scan_for_col(&argv[i][argstart+strstart],FMT_LEG_LEN,im.gdes[im.gdes_c-1].format);
3120 if((im.gdes[im.gdes_c-1].vidx=find_var(&im,varname))==-1){
3122 rrd_set_error("unknown variable '%s'",varname);
3125 if((im.gdes[im.gdes_c-1].cf=cf_conv(symname))==-1){
3132 rrd_set_error("can't parse '%s'",&argv[i][argstart]);
3137 if(strlen(&argv[i][argstart])>FMT_LEG_LEN) argv[i][argstart+FMT_LEG_LEN-3]='\0' ;
3138 strcpy(im.gdes[im.gdes_c-1].legend, &argv[i][argstart]);
3144 &im.gdes[im.gdes_c-1].yrule,
3145 &col_red,&col_green,&col_blue,
3147 im.gdes[im.gdes_c-1].col.red = col_red;
3148 im.gdes[im.gdes_c-1].col.green = col_green;
3149 im.gdes[im.gdes_c-1].col.blue = col_blue;
3151 im.gdes[im.gdes_c-1].legend[0] = '\0';
3153 scan_for_col(&argv[i][argstart+strstart],FMT_LEG_LEN,im.gdes[im.gdes_c-1].legend);
3157 rrd_set_error("can't parse '%s'",&argv[i][argstart]);
3165 &im.gdes[im.gdes_c-1].xrule,
3170 im.gdes[im.gdes_c-1].col.red = col_red;
3171 im.gdes[im.gdes_c-1].col.green = col_green;
3172 im.gdes[im.gdes_c-1].col.blue = col_blue;
3174 im.gdes[im.gdes_c-1].legend[0] = '\0';
3176 scan_for_col(&argv[i][argstart+strstart],FMT_LEG_LEN,im.gdes[im.gdes_c-1].legend);
3180 rrd_set_error("can't parse '%s'",&argv[i][argstart]);
3187 rrd_set_error("STACK must follow AREA, LINE or STACK");
3195 if((scancount=sscanf(
3197 "%29[^:#]#%2x%2x%2x:%n",
3203 im.gdes[im.gdes_c-1].col.red = col_red;
3204 im.gdes[im.gdes_c-1].col.green = col_green;
3205 im.gdes[im.gdes_c-1].col.blue = col_blue;
3207 im.gdes[im.gdes_c-1].legend[0] = '\0';
3209 scan_for_col(&argv[i][argstart+strstart],FMT_LEG_LEN,im.gdes[im.gdes_c-1].legend);
3211 if((im.gdes[im.gdes_c-1].vidx=find_var(&im,varname))==-1){
3213 rrd_set_error("unknown variable '%s'",varname);
3217 im.gdes[im.gdes_c-1].col.red = -1;
3221 rrd_set_error("can't parse '%s'",&argv[i][argstart]);
3226 if((rpnex = malloc(strlen(&argv[i][argstart])*sizeof(char)))==NULL){
3227 rrd_set_error("malloc for CDEF");
3232 DEF_NAM_FMT "=%[^: ]",
3233 im.gdes[im.gdes_c-1].vname,
3237 rrd_set_error("can't parse CDEF '%s'",&argv[i][argstart]);
3240 /* checking for duplicate DEF CDEFS */
3241 if(find_var(&im,im.gdes[im.gdes_c-1].vname) != -1){
3243 rrd_set_error("duplicate variable '%s'",
3244 im.gdes[im.gdes_c-1].vname);
3247 if((im.gdes[im.gdes_c-1].rpnp = str2rpn(&im,rpnex))== NULL){
3248 rrd_set_error("invalid rpn expression '%s'", rpnex);
3258 im.gdes[im.gdes_c-1].vname,
3259 &strstart)== 1 && strstart){ /* is the = did not match %n returns 0 */
3260 if(sscanf(&argv[i][argstart
3262 +scan_for_col(&argv[i][argstart+strstart],
3263 MAXPATH,im.gdes[im.gdes_c-1].rrd)],
3264 ":" DS_NAM_FMT ":" CF_NAM_FMT,
3265 im.gdes[im.gdes_c-1].ds_nam,
3268 rrd_set_error("can't parse DEF '%s' -2",&argv[i][argstart]);
3273 rrd_set_error("can't parse DEF '%s'",&argv[i][argstart]);
3277 /* checking for duplicate DEF CDEFS */
3278 if (find_var(&im,im.gdes[im.gdes_c-1].vname) != -1){
3280 rrd_set_error("duplicate variable '%s'",
3281 im.gdes[im.gdes_c-1].vname);
3284 if((im.gdes[im.gdes_c-1].cf=cf_conv(symname))==-1){
3286 rrd_set_error("unknown cf '%s'",symname);
3295 rrd_set_error("can't make a graph without contents");
3300 /* parse rest of arguments containing information on what to draw*/
3301 if (graph_paint(&im,prdata)==-1){
3311 /* maybe prdata is not allocated yet ... lets do it now */
3312 if((*prdata = calloc(2,sizeof(char *)))==NULL){
3313 rrd_set_error("malloc imginfo");
3317 if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
3319 rrd_set_error("malloc imginfo");
3322 filename=im.graphfile+strlen(im.graphfile);
3323 while(filename > im.graphfile){
3324 if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
3328 sprintf((*prdata)[0],im.imginfo,filename,im.xgif,im.ygif);
3334 int bad_format(char *fmt) {
3338 while (*ptr != '\0') {
3339 if (*ptr == '%') {ptr++;
3340 if (*ptr == '\0') return 1;
3341 while ((*ptr >= '0' && *ptr <= '9') || *ptr == '.') {
3344 if (*ptr == '\0') return 1;
3347 if (*ptr == '\0') return 1;
3348 if (*ptr == 'e' || *ptr == 'f') {
3350 } else { return 1; }
3352 else if (*ptr == 's' || *ptr == 'S' || *ptr == '%') { ++ptr; }