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_TICK,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)
349 conv_if(TICK,GF_TICK)
351 conv_if(CDEF,GF_CDEF)
356 enum if_en if_conv(char *string){
364 enum tmt_en tmt_conv(char *string){
366 conv_if(SECOND,TMT_SECOND)
367 conv_if(MINUTE,TMT_MINUTE)
368 conv_if(HOUR,TMT_HOUR)
370 conv_if(WEEK,TMT_WEEK)
371 conv_if(MONTH,TMT_MONTH)
372 conv_if(YEAR,TMT_YEAR)
376 enum grc_en grc_conv(char *string){
378 conv_if(BACK,GRC_BACK)
379 conv_if(CANVAS,GRC_CANVAS)
380 conv_if(SHADEA,GRC_SHADEA)
381 conv_if(SHADEB,GRC_SHADEB)
382 conv_if(GRID,GRC_GRID)
383 conv_if(MGRID,GRC_MGRID)
384 conv_if(FONT,GRC_FONT)
385 conv_if(FRAME,GRC_FRAME)
386 conv_if(ARROW,GRC_ARROW)
396 im_free(image_desc_t *im)
399 if (im == NULL) return 0;
400 for(i=0;i<im->gdes_c;i++){
401 if (im->gdes[i].data_first){
402 /* careful here, because a single pointer can occur several times */
403 free (im->gdes[i].data);
404 if (im->gdes[i].ds_namv){
405 for (ii=0;ii<im->gdes[i].ds_cnt;ii++)
406 free(im->gdes[i].ds_namv[ii]);
407 free(im->gdes[i].ds_namv);
410 free (im->gdes[i].p_data);
411 free (im->gdes[i].rpnp);
417 /* find SI magnitude symbol for the given number*/
420 image_desc_t *im, /* image description */
427 char *symbol[] = {"a", /* 10e-18 Ato */
428 "f", /* 10e-15 Femto */
429 "p", /* 10e-12 Pico */
430 "n", /* 10e-9 Nano */
431 "u", /* 10e-6 Micro */
432 "m", /* 10e-3 Milli */
437 "T", /* 10e12 Terra */
438 "P", /* 10e15 Peta */
444 if (*value == 0.0 || isnan(*value) ) {
448 sindex = floor(log(fabs(*value))/log((double)im->base));
449 *magfact = pow((double)im->base, (double)sindex);
450 (*value) /= (*magfact);
452 if ( sindex <= symbcenter && sindex >= -symbcenter) {
453 (*symb_ptr) = symbol[sindex+symbcenter];
461 /* find SI magnitude symbol for the numbers on the y-axis*/
464 image_desc_t *im /* image description */
468 char symbol[] = {'a', /* 10e-18 Ato */
469 'f', /* 10e-15 Femto */
470 'p', /* 10e-12 Pico */
471 'n', /* 10e-9 Nano */
472 'u', /* 10e-6 Micro */
473 'm', /* 10e-3 Milli */
478 'T', /* 10e12 Terra */
479 'P', /* 10e15 Peta */
485 if (im->unitsexponent != 9999) {
486 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
487 digits = floor(im->unitsexponent / 3);
489 digits = floor( log( max( fabs(im->minval),fabs(im->maxval)))/log((double)im->base));
491 im->magfact = pow((double)im->base , digits);
494 printf("digits %6.3f im->magfact %6.3f\n",digits,im->magfact);
497 if ( ((digits+symbcenter) < sizeof(symbol)) &&
498 ((digits+symbcenter) >= 0) )
499 im->symbol = symbol[(int)digits+symbcenter];
504 /* move min and max values around to become sensible */
507 expand_range(image_desc_t *im)
509 double sensiblevalues[] ={1000.0,900.0,800.0,750.0,700.0,
510 600.0,500.0,400.0,300.0,250.0,
511 200.0,125.0,100.0,90.0,80.0,
512 75.0,70.0,60.0,50.0,40.0,30.0,
513 25.0,20.0,10.0,9.0,8.0,
514 7.0,6.0,5.0,4.0,3.5,3.0,
515 2.5,2.0,1.8,1.5,1.2,1.0,
516 0.8,0.7,0.6,0.5,0.4,0.3,0.2,0.1,0.0,-1};
518 double scaled_min,scaled_max;
525 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
526 im->minval,im->maxval,im->magfact);
529 if (isnan(im->ygridstep)){
530 if(im->extra_flags & ALTAUTOSCALE) {
531 /* measure the amplitude of the function. Make sure that
532 graph boundaries are slightly higher then max/min vals
533 so we can see amplitude on the graph */
536 delt = im->maxval - im->minval;
538 fact = 2.0 * pow(10.0,
539 floor(log10(max(fabs(im->minval), fabs(im->maxval)))) - 2);
541 adj = (fact - delt) * 0.55;
543 printf("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n", im->minval, im->maxval, delt, fact, adj);
549 else if(im->extra_flags & ALTAUTOSCALE_MAX) {
550 /* measure the amplitude of the function. Make sure that
551 graph boundaries are slightly higher than max vals
552 so we can see amplitude on the graph */
553 adj = (im->maxval - im->minval) * 0.1;
557 scaled_min = im->minval / im->magfact;
558 scaled_max = im->maxval / im->magfact;
560 for (i=1; sensiblevalues[i] > 0; i++){
561 if (sensiblevalues[i-1]>=scaled_min &&
562 sensiblevalues[i]<=scaled_min)
563 im->minval = sensiblevalues[i]*(im->magfact);
565 if (-sensiblevalues[i-1]<=scaled_min &&
566 -sensiblevalues[i]>=scaled_min)
567 im->minval = -sensiblevalues[i-1]*(im->magfact);
569 if (sensiblevalues[i-1] >= scaled_max &&
570 sensiblevalues[i] <= scaled_max)
571 im->maxval = sensiblevalues[i-1]*(im->magfact);
573 if (-sensiblevalues[i-1]<=scaled_max &&
574 -sensiblevalues[i] >=scaled_max)
575 im->maxval = -sensiblevalues[i]*(im->magfact);
579 /* adjust min and max to the grid definition if there is one */
580 im->minval = (double)im->ylabfact * im->ygridstep *
581 floor(im->minval / ((double)im->ylabfact * im->ygridstep));
582 im->maxval = (double)im->ylabfact * im->ygridstep *
583 ceil(im->maxval /( (double)im->ylabfact * im->ygridstep));
587 fprintf(stderr,"SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
588 im->minval,im->maxval,im->magfact);
593 /* reduce data reimplementation by Alex */
597 enum cf_en cf, /* which consolidation function ?*/
598 unsigned long cur_step, /* step the data currently is in */
599 time_t *start, /* start, end and step as requested ... */
600 time_t *end, /* ... by the application will be ... */
601 unsigned long *step, /* ... adjusted to represent reality */
602 unsigned long *ds_cnt, /* number of data sources in file */
603 rrd_value_t **data) /* two dimensional array containing the data */
605 int i,reduce_factor = ceil((double)(*step) / (double)cur_step);
606 unsigned long col,dst_row,row_cnt,start_offset,end_offset,skiprows=0;
607 rrd_value_t *srcptr,*dstptr;
609 (*step) = cur_step*reduce_factor; /* set new step size for reduced data */
613 /* We were given one extra row at the beginning of the interval.
614 ** We also need to return one extra row. The extra interval is
615 ** the one defined by the start time in both cases. It is not
616 ** used when graphing but maybe we can use it while reducing the
619 row_cnt = ((*end)-(*start))/cur_step +1;
621 /* alter start and end so that they are multiples of the new steptime.
622 ** End will be shifted towards the future and start will be shifted
623 ** towards the past in order to include the requested interval
625 end_offset = (*end) % (*step);
626 if (end_offset) end_offset = (*step)-end_offset;
627 start_offset = (*start) % (*step);
628 (*end) = (*end)+end_offset;
629 (*start) = (*start)-start_offset;
631 /* The first destination row is unknown yet it still needs
632 ** to be present in the returned data. Skip it.
633 ** Don't make it NaN or we might overwrite the source.
637 /* Depending on the amount of extra data needed at the
638 ** start of the destination, three things can happen:
639 ** -1- start_offset == 0: skip the extra source row
640 ** -2- start_offset == cur_step: do nothing
641 ** -3- start_offset > cur_step: skip some source rows and
642 ** fill one destination row with NaN
644 if (start_offset==0) {
647 } else if (start_offset!=cur_step) {
648 skiprows=((*step)-start_offset)/cur_step+1;
649 srcptr += ((*ds_cnt)*skiprows);
651 for (col=0;col<(*ds_cnt);col++) *dstptr++=DNAN;
654 /* If we had to alter the endtime, there won't be
655 ** enough data to fill the last row. This means
656 ** we have to skip some rows at the end
659 skiprows = ((*step)-end_offset)/cur_step;
664 /* Sanity check: row_cnt should be multiple of reduce_factor */
665 /* if this gets triggered, something is REALY WRONG ... we die immediately */
667 if (row_cnt%reduce_factor) {
668 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
669 row_cnt,reduce_factor);
670 printf("BUG in reduce_data()\n");
674 /* Now combine reduce_factor intervals at a time
675 ** into one interval for the destination.
678 for (dst_row=0;row_cnt>=reduce_factor;dst_row++) {
679 for (col=0;col<(*ds_cnt);col++) {
680 rrd_value_t newval=DNAN;
681 unsigned long validval=0;
683 for (i=0;i<reduce_factor;i++) {
684 if (isnan(srcptr[i*(*ds_cnt)+col])) {
688 if (isnan(newval)) newval = srcptr[i*(*ds_cnt)+col];
696 newval += srcptr[i*(*ds_cnt)+col];
699 newval = min (newval,srcptr[i*(*ds_cnt)+col]);
702 /* an interval contains a failure if any subintervals contained a failure */
704 newval = max (newval,srcptr[i*(*ds_cnt)+col]);
707 newval = srcptr[i*(*ds_cnt)+col];
712 if (validval == 0){newval = DNAN;} else{
730 srcptr+=(*ds_cnt)*reduce_factor;
731 row_cnt-=reduce_factor;
734 /* If we had to alter the endtime, we didn't have enough
735 ** source rows to fill the last row. Fill it with NaN.
737 if (end_offset!=0) for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
741 /* get the data required for the graphs from the
745 data_fetch( image_desc_t *im )
749 /* pull the data from the log files ... */
750 for (i=0;i<im->gdes_c;i++){
751 /* only GF_DEF elements fetch data */
752 if (im->gdes[i].gf != GF_DEF)
756 /* do we have it already ?*/
757 for (ii=0;ii<i;ii++){
758 if (im->gdes[ii].gf != GF_DEF)
760 if((strcmp(im->gdes[i].rrd,im->gdes[ii].rrd) == 0)
761 && (im->gdes[i].cf == im->gdes[ii].cf)){
762 /* OK the data it is here already ...
763 * we just copy the header portion */
764 im->gdes[i].start = im->gdes[ii].start;
765 im->gdes[i].end = im->gdes[ii].end;
766 im->gdes[i].step = im->gdes[ii].step;
767 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
768 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
769 im->gdes[i].data = im->gdes[ii].data;
770 im->gdes[i].data_first = 0;
777 unsigned long ft_step = im->gdes[i].step ;
779 if((rrd_fetch_fn(im->gdes[i].rrd,
785 &im->gdes[i].ds_namv,
786 &im->gdes[i].data)) == -1){
789 im->gdes[i].data_first = 1;
791 if (ft_step < im->gdes[i].step) {
792 reduce_data(im->gdes[i].cf,
800 im->gdes[i].step = ft_step;
804 /* lets see if the required data source is realy there */
805 for(ii=0;ii<im->gdes[i].ds_cnt;ii++){
806 if(strcmp(im->gdes[i].ds_namv[ii],im->gdes[i].ds_nam) == 0){
809 if (im->gdes[i].ds== -1){
810 rrd_set_error("No DS called '%s' in '%s'",
811 im->gdes[i].ds_nam,im->gdes[i].rrd);
819 /* evaluate the expressions in the CDEF functions */
821 /*************************************************************
823 *************************************************************/
825 /* find gdes containing var*/
827 find_var(image_desc_t *im, char *key){
829 for(ii=0;ii<im->gdes_c-1;ii++){
830 if((im->gdes[ii].gf == GF_DEF
831 || im->gdes[ii].gf == GF_CDEF)
832 && (strcmp(im->gdes[ii].vname,key) == 0)){
839 /* find the largest common denominator for all the numbers
840 in the 0 terminated num array */
845 for (i=0;num[i+1]!=0;i++){
847 rest=num[i] % num[i+1];
848 num[i]=num[i+1]; num[i+1]=rest;
852 /* return i==0?num[i]:num[i-1]; */
857 /* convert string to rpnp */
859 str2rpn(image_desc_t *im,char *expr){
868 if ((rpnp = (rpnp_t *) rrd_realloc(rpnp, (++steps + 2)*
869 sizeof(rpnp_t)))==NULL){
873 else if((sscanf(expr,"%lf%n",&rpnp[steps].val,&pos) == 1)
874 && (expr[pos] == ',')){
875 rpnp[steps].op = OP_NUMBER;
879 #define match_op(VV,VVV) \
880 else if (strncmp(expr, #VVV, strlen(#VVV))==0 && \
881 (expr[strlen(#VVV)] == ',' || expr[strlen(#VVV)] == '\0') ){ \
882 rpnp[steps].op = VV; \
883 expr+=strlen(#VVV); \
894 match_op(OP_FLOOR,FLOOR)
895 match_op(OP_CEIL,CEIL)
908 match_op(OP_LIMIT,LIMIT)
909 /* order is important here ! .. match longest first */
910 match_op(OP_UNKN,UNKN)
912 match_op(OP_NEGINF,NEGINF)
913 match_op(OP_PREV,PREV)
916 match_op(OP_LTIME,LTIME)
917 match_op(OP_TIME,TIME)
923 else if ((sscanf(expr,DEF_NAM_FMT "%n",
925 && ((rpnp[steps].ptr = find_var(im,vname)) != -1)){
926 rpnp[steps].op = OP_VARIABLE;
943 rpnp[steps+1].op = OP_END;
947 /* figure out what the local timezone offset for any point in
948 time was. Return it in seconds */
951 tzoffset( time_t now ){
952 int gm_sec, gm_min, gm_hour, gm_yday, gm_year,
953 l_sec, l_min, l_hour, l_yday, l_year;
959 gm_hour = t->tm_hour;
960 gm_yday = t->tm_yday;
961 gm_year = t->tm_year;
968 off = (l_sec-gm_sec)+(l_min-gm_min)*60+(l_hour-gm_hour)*3600;
969 if ( l_yday > gm_yday || l_year > gm_year){
971 } else if ( l_yday < gm_yday || l_year < gm_year){
980 #define dc_stackblock 100
982 /* run the rpn calculator on all the CDEF arguments */
985 data_calc( image_desc_t *im){
992 double *stack = NULL;
993 long dc_stacksize = 0;
995 for (gdi=0;gdi<im->gdes_c;gdi++){
996 /* only GF_CDEF elements are of interest */
997 if (im->gdes[gdi].gf != GF_CDEF)
999 im->gdes[gdi].ds_cnt = 1;
1000 im->gdes[gdi].ds = 0;
1001 im->gdes[gdi].data_first = 1;
1002 im->gdes[gdi].start = 0;
1003 im->gdes[gdi].end = 0;
1008 /* find the variables in the expression. And calc the lowest
1009 common denominator of all step sizes of the data sources involved.
1010 this will be the step size for the cdef created data source*/
1012 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
1013 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE){
1014 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
1015 if ((steparray = rrd_realloc(steparray, (++stepcnt+1)*sizeof(*steparray)))==NULL){
1016 rrd_set_error("realloc steparray");
1022 steparray[stepcnt-1] = im->gdes[ptr].step;
1024 /* adjust start and end of cdef (gdi) so that it runs from
1025 the latest start point to the earliest endpoint of any of the
1026 rras involved (ptr) */
1028 if(im->gdes[gdi].start < im->gdes[ptr].start)
1029 im->gdes[gdi].start = im->gdes[ptr].start;
1031 if(im->gdes[gdi].end == 0
1032 || im->gdes[gdi].end > im->gdes[ptr].end)
1033 im->gdes[gdi].end = im->gdes[ptr].end;
1035 /* store pointer to the first element of the rra providing
1036 data for variable, further save step size and data source count
1038 im->gdes[gdi].rpnp[rpi].data =
1039 im->gdes[ptr].data + im->gdes[ptr].ds;
1040 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
1041 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
1045 if(steparray == NULL){
1046 rrd_set_error("rpn expressions without variables are not supported");
1050 steparray[stepcnt]=0;
1051 /* now find the step for the result of the cdef. so that we land on
1052 each step in all of the variables rras */
1054 im->gdes[gdi].step = lcd(steparray);
1059 if((im->gdes[gdi].data = malloc(((im->gdes[gdi].end
1060 -im->gdes[gdi].start)
1061 / im->gdes[gdi].step +1)
1062 * sizeof(double)))==NULL){
1063 rrd_set_error("malloc im->gdes[gdi].data");
1068 /* step through the new cdef results array and calculate the values */
1069 for (now = im->gdes[gdi].start;
1070 now<=im->gdes[gdi].end;
1071 now += im->gdes[gdi].step){
1073 /* process each op from the rpn in turn */
1074 for (rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
1075 if (stptr +5 > dc_stacksize){
1076 dc_stacksize += dc_stackblock;
1077 stack = rrd_realloc(stack,dc_stacksize*sizeof(*stack));
1079 rrd_set_error("RPN stack overflow");
1083 switch (im->gdes[gdi].rpnp[rpi].op){
1085 stack[++stptr] = im->gdes[gdi].rpnp[rpi].val;
1088 /* make sure we pull the correct value from the *.data array */
1089 /* adjust the pointer into the array acordingly. */
1090 if(now > im->gdes[gdi].start &&
1091 now % im->gdes[gdi].rpnp[rpi].step == 0){
1092 im->gdes[gdi].rpnp[rpi].data +=
1093 im->gdes[gdi].rpnp[rpi].ds_cnt;
1095 stack[++stptr] = *im->gdes[gdi].rpnp[rpi].data;
1099 stack[++stptr] = DNAN;
1101 stack[++stptr] = im->gdes[gdi].data[dataidx];
1105 stack[++stptr] = DNAN;
1108 stack[++stptr] = DINF;
1111 stack[++stptr] = -DINF;
1114 stack[++stptr] = (double)time(NULL);
1117 stack[++stptr] = (double)now;
1120 stack[++stptr] = (double)tzoffset(now)+(double)now;
1124 rrd_set_error("RPN stack underflow");
1128 stack[stptr-1] = stack[stptr-1] + stack[stptr];
1133 rrd_set_error("RPN stack underflow");
1137 stack[stptr-1] = stack[stptr-1] - stack[stptr];
1142 rrd_set_error("RPN stack underflow");
1146 stack[stptr-1] = stack[stptr-1] * stack[stptr];
1151 rrd_set_error("RPN stack underflow");
1155 stack[stptr-1] = stack[stptr-1] / stack[stptr];
1160 rrd_set_error("RPN stack underflow");
1164 stack[stptr-1] = fmod(stack[stptr-1],stack[stptr]);
1169 rrd_set_error("RPN stack underflow");
1173 stack[stptr] = sin(stack[stptr]);
1177 rrd_set_error("RPN stack underflow");
1181 stack[stptr] = cos(stack[stptr]);
1185 rrd_set_error("RPN stack underflow");
1189 stack[stptr] = ceil(stack[stptr]);
1193 rrd_set_error("RPN stack underflow");
1197 stack[stptr] = floor(stack[stptr]);
1201 rrd_set_error("RPN stack underflow");
1205 stack[stptr] = log(stack[stptr]);
1209 rrd_set_error("RPN stack underflow");
1213 stack[stptr+1] = stack[stptr];
1218 rrd_set_error("RPN stack underflow");
1226 rrd_set_error("RPN stack underflow");
1231 dummy = stack[stptr] ;
1232 stack[stptr] = stack[stptr-1];
1233 stack[stptr-1] = dummy;
1238 rrd_set_error("RPN stack underflow");
1242 stack[stptr] = exp(stack[stptr]);
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 if (isnan(stack[stptr-1]) || isnan(stack[stptr]))
1299 stack[stptr-1] = 0.0;
1301 stack[stptr-1] = stack[stptr-1] == stack[stptr] ? 1.0 : 0.0;
1306 rrd_set_error("RPN stack underflow");
1310 stack[stptr-2] = stack[stptr-2] != 0.0 ? stack[stptr-1] : stack[stptr];
1316 rrd_set_error("RPN stack underflow");
1320 if (isnan(stack[stptr-1]))
1322 else if (isnan(stack[stptr]))
1323 stack[stptr-1] = stack[stptr];
1324 else if (stack[stptr-1] > stack[stptr])
1325 stack[stptr-1] = stack[stptr];
1330 rrd_set_error("RPN stack underflow");
1334 if (isnan(stack[stptr-1]))
1336 else if (isnan(stack[stptr]))
1337 stack[stptr-1] = stack[stptr];
1338 else if (stack[stptr-1] < stack[stptr])
1339 stack[stptr-1] = stack[stptr];
1344 rrd_set_error("RPN stack underflow");
1348 if (isnan(stack[stptr-2]))
1350 else if (isnan(stack[stptr-1]))
1351 stack[stptr-2] = stack[stptr-1];
1352 else if (isnan(stack[stptr]))
1353 stack[stptr-2] = stack[stptr];
1354 else if (stack[stptr-2] < stack[stptr-1])
1355 stack[stptr-2] = DNAN;
1356 else if (stack[stptr-2] > stack[stptr])
1357 stack[stptr-2] = DNAN;
1362 rrd_set_error("RPN stack underflow");
1366 stack[stptr] = isnan(stack[stptr]) ? 1.0 : 0.0;
1373 rrd_set_error("RPN final stack size != 1");
1377 im->gdes[gdi].data[++dataidx] = stack[0];
1386 /* massage data so, that we get one value for each x coordinate in the graph */
1388 data_proc( image_desc_t *im ){
1390 double pixstep = (double)(im->end-im->start)
1391 /(double)im->xsize; /* how much time
1392 passes in one pixel */
1394 double minval=DNAN,maxval=DNAN;
1396 unsigned long gr_time;
1398 /* memory for the processed data */
1399 for(i=0;i<im->gdes_c;i++){
1400 if((im->gdes[i].gf==GF_LINE1) ||
1401 (im->gdes[i].gf==GF_LINE2) ||
1402 (im->gdes[i].gf==GF_LINE3) ||
1403 (im->gdes[i].gf==GF_AREA) ||
1404 (im->gdes[i].gf==GF_TICK) ||
1405 (im->gdes[i].gf==GF_STACK)){
1406 if((im->gdes[i].p_data = malloc((im->xsize +1)
1407 * sizeof(rrd_value_t)))==NULL){
1408 rrd_set_error("malloc data_proc");
1414 for(i=0;i<im->xsize;i++){
1416 gr_time = im->start+pixstep*i; /* time of the
1420 for(ii=0;ii<im->gdes_c;ii++){
1422 switch(im->gdes[ii].gf){
1430 vidx = im->gdes[ii].vidx;
1433 im->gdes[vidx].data[
1434 ((unsigned long)floor((double)
1435 (gr_time - im->gdes[vidx].start )
1436 / im->gdes[vidx].step)+1)
1438 /* added one because data was not being aligned properly
1439 this fixes it. We may also be having a problem in fetch ... */
1441 *im->gdes[vidx].ds_cnt
1442 +im->gdes[vidx].ds];
1444 if (! isnan(value)) {
1446 im->gdes[ii].p_data[i] = paintval;
1447 /* GF_TICK: the data values are not relevant for min and max */
1448 if (finite(paintval) && im->gdes[ii].gf != GF_TICK ){
1449 if (isnan(minval) || paintval < minval)
1451 if (isnan(maxval) || paintval > maxval)
1455 im->gdes[ii].p_data[i] = DNAN;
1470 /* if min or max have not been asigned a value this is because
1471 there was no data in the graph ... this is not good ...
1472 lets set these to dummy values then ... */
1474 if (isnan(minval)) minval = 0.0;
1475 if (isnan(maxval)) maxval = 1.0;
1477 /* adjust min and max values */
1478 if (isnan(im->minval)
1479 || ((!im->logarithmic && !im->rigid) /* don't adjust low-end with log scale */
1480 && im->minval > minval))
1481 im->minval = minval;
1482 if (isnan(im->maxval)
1484 && im->maxval < maxval)){
1485 if (im->logarithmic)
1486 im->maxval = maxval * 1.1;
1488 im->maxval = maxval;
1490 /* make sure min and max are not equal */
1491 if (im->minval == im->maxval) {
1493 if (! im->logarithmic) {
1497 /* make sure min and max are not both zero */
1498 if (im->maxval == 0.0) {
1508 /* identify the point where the first gridline, label ... gets placed */
1512 time_t start, /* what is the initial time */
1513 enum tmt_en baseint, /* what is the basic interval */
1514 long basestep /* how many if these do we jump a time */
1518 tm = *localtime(&start);
1521 tm.tm_sec -= tm.tm_sec % basestep; break;
1524 tm.tm_min -= tm.tm_min % basestep;
1529 tm.tm_hour -= tm.tm_hour % basestep; break;
1531 /* we do NOT look at the basestep for this ... */
1534 tm.tm_hour = 0; break;
1536 /* we do NOT look at the basestep for this ... */
1540 tm.tm_mday -= tm.tm_wday -1; /* -1 because we want the monday */
1541 if (tm.tm_wday==0) tm.tm_mday -= 7; /* we want the *previous* monday */
1548 tm.tm_mon -= tm.tm_mon % basestep; break;
1556 tm.tm_year -= (tm.tm_year+1900) % basestep;
1561 /* identify the point where the next gridline, label ... gets placed */
1564 time_t current, /* what is the initial time */
1565 enum tmt_en baseint, /* what is the basic interval */
1566 long basestep /* how many if these do we jump a time */
1571 tm = *localtime(¤t);
1575 tm.tm_sec += basestep; break;
1577 tm.tm_min += basestep; break;
1579 tm.tm_hour += basestep; break;
1581 tm.tm_mday += basestep; break;
1583 tm.tm_mday += 7*basestep; break;
1585 tm.tm_mon += basestep; break;
1587 tm.tm_year += basestep;
1589 madetime = mktime(&tm);
1590 } while (madetime == -1); /* this is necessary to skip impssible times
1591 like the daylight saving time skips */
1596 void gator( gdImagePtr gif, int x, int y){
1598 /* this function puts the name of the author and the tool into the
1599 graph. Remove if you must, but please note, that it is here,
1600 because I would like people who look at rrdtool generated graphs to
1601 see what was used to do it. No obviously you can also add a credit
1602 line to your webpage or printed document, this is fine with me. But
1603 as I have no control over this, I added the little tag in here.
1606 /* the fact that the text of what gets put into the graph is not
1607 visible in the function, has lead some to think this is for
1608 obfuscation reasons. While this is a nice side effect (I addmit),
1609 it is not the prime reason. The prime reason is, that the font
1610 used, is so small, that I had to hand edit the characters to ensure
1611 readability. I could thus not use the normal gd functions to write,
1612 but had to embed a slightly compressed bitmap version into the code.
1615 int li[]={0,0,1, 0,4,5, 0,8,9, 0,12,14, 0,17,17, 0,21,21,
1616 0,24,24, 0,34,34, 0,40,42, 0,45,45, 0,48,49, 0,52,54,
1617 0,61,61, 0,64,66, 0,68,70, 0,72,74, 0,76,76, 0,78,78,
1619 1,0,0, 1,2,2, 1,4,4, 1,6,6, 1,8,8, 1,10,10,
1620 1,13,13, 1,16,16, 1,18,18, 1,20,20, 1,22,22, 1,24,24,
1621 1,34,34, 1,41,41, 1,44,44, 1,46,46, 1,48,48, 1,50,50,
1622 1,53,53, 1,60,60, 1,62,62, 1,64,64, 1,69,69, 1,73,73,
1623 1,76,76, 1,78,78, 1,80,80, 1,84,84, 1,86,86,
1624 2,0,1, 2,4,5, 2,8,8, 2,10,10, 2,13,13, 2,16,16,
1625 2,18,18, 2,20,20, 2,22,22, 2,24,24, 2,33,33, 2,41,41,
1626 2,44,44, 2,46,46, 2,48,49, 2,53,53, 2,60,60, 2,62,62,
1627 2,64,65, 2,69,69, 2,73,73, 2,76,77, 2,80,81, 2,84,85,
1628 3,0,0, 3,2,2, 3,4,4, 3,6,6, 3,8,8, 3,10,10,
1629 3,13,13, 3,16,16, 3,18,18, 3,20,20, 3,22,22, 3,24,24,
1630 3,32,32, 3,41,41, 3,44,44, 3,46,46, 3,48,48, 3,50,50,
1631 3,53,53, 3,60,60, 3,62,62, 3,64,64, 3,69,69, 3,73,73,
1632 3,76,76, 3,78,78, 3,80,80, 3,84,84, 3,86,86,
1633 4,0,0, 4,2,2, 4,4,4, 4,6,6, 4,8,9, 4,13,13,
1634 4,17,17, 4,21,21, 4,24,26, 4,32,32, 4,41,41, 4,45,45,
1635 4,48,49, 4,52,54, 4,61,61, 4,64,66, 4,69,69, 4,72,74,
1636 4,76,76, 4,78,78, 4,80,82, 4,84,84};
1638 for(i=0; i<DIM(li); i=i+3)
1639 for(ii=y+li[i+1]; ii<=y+li[i+2];ii++)
1640 gdImageSetPixel(gif,x-li[i],ii,graph_col[GRC_GRID].i);
1644 /* calculate values required for PRINT and GPRINT functions */
1647 print_calc(image_desc_t *im, char ***prdata)
1649 long i,ii,validsteps;
1651 int graphelement = 0;
1654 double magfact = -1;
1658 if (im->imginfo) prlines++;
1659 for(i=0;i<im->gdes_c;i++){
1660 switch(im->gdes[i].gf){
1663 if(((*prdata) = rrd_realloc((*prdata),prlines*sizeof(char *)))==NULL){
1664 rrd_set_error("realloc prdata");
1668 vidx = im->gdes[i].vidx;
1669 max_ii =((im->gdes[vidx].end
1670 - im->gdes[vidx].start)
1671 /im->gdes[vidx].step
1672 *im->gdes[vidx].ds_cnt);
1675 for(ii=im->gdes[vidx].ds+im->gdes[vidx].ds_cnt;
1676 ii < max_ii+im->gdes[vidx].ds_cnt;
1677 ii+=im->gdes[vidx].ds_cnt){
1678 if (! finite(im->gdes[vidx].data[ii]))
1680 if (isnan(printval)){
1681 printval = im->gdes[vidx].data[ii];
1686 switch (im->gdes[i].cf){
1689 case CF_DEVSEASONAL:
1693 printval += im->gdes[vidx].data[ii];
1696 printval = min( printval, im->gdes[vidx].data[ii]);
1700 printval = max( printval, im->gdes[vidx].data[ii]);
1703 printval = im->gdes[vidx].data[ii];
1706 if (im->gdes[i].cf == CF_AVERAGE || im -> gdes[i].cf > CF_LAST) {
1707 if (validsteps > 1) {
1708 printval = (printval / validsteps);
1711 if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) {
1712 /* Magfact is set to -1 upon entry to print_calc. If it
1713 * is still less than 0, then we need to run auto_scale.
1714 * Otherwise, put the value into the correct units. If
1715 * the value is 0, then do not set the symbol or magnification
1716 * so next the calculation will be performed again. */
1717 if (magfact < 0.0) {
1718 auto_scale(im,&printval,&si_symb,&magfact);
1719 if (printval == 0.0)
1722 printval /= magfact;
1724 *(++percent_s) = 's';
1726 else if (strstr(im->gdes[i].format,"%s") != NULL) {
1727 auto_scale(im,&printval,&si_symb,&magfact);
1729 if (im->gdes[i].gf == GF_PRINT){
1730 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1731 if (bad_format(im->gdes[i].format)) {
1732 rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1735 #ifdef HAVE_SNPRINTF
1736 snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
1738 sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
1740 (*prdata)[prlines-1] = NULL;
1744 if (bad_format(im->gdes[i].format)) {
1745 rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1748 #ifdef HAVE_SNPRINTF
1749 snprintf(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
1751 sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
1772 return graphelement;
1776 /* place legends with color spots */
1778 leg_place(image_desc_t *im)
1781 int interleg = SmallFont->w*2;
1782 int box = SmallFont->h*1.2;
1783 int border = SmallFont->w*2;
1784 int fill=0, fill_last;
1786 int leg_x = border, leg_y = im->ygif;
1790 char prt_fctn; /*special printfunctions */
1793 if( !(im->extra_flags & NOLEGEND) ) {
1794 if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1795 rrd_set_error("malloc for legspace");
1799 for(i=0;i<im->gdes_c;i++){
1802 leg_cc = strlen(im->gdes[i].legend);
1804 /* is there a controle code ant the end of the legend string ? */
1805 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\') {
1806 prt_fctn = im->gdes[i].legend[leg_cc-1];
1808 im->gdes[i].legend[leg_cc] = '\0';
1812 /* remove exess space */
1813 while (prt_fctn=='g' &&
1815 im->gdes[i].legend[leg_cc-1]==' '){
1817 im->gdes[i].legend[leg_cc]='\0';
1820 legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1823 /* no interleg space if string ends in \g */
1824 fill += legspace[i];
1826 if (im->gdes[i].gf != GF_GPRINT &&
1827 im->gdes[i].gf != GF_COMMENT) {
1830 fill += leg_cc * SmallFont->w;
1835 /* who said there was a special tag ... ?*/
1836 if (prt_fctn=='g') {
1839 if (prt_fctn == '\0') {
1840 if (i == im->gdes_c -1 ) prt_fctn ='l';
1842 /* is it time to place the legends ? */
1843 if (fill > im->xgif - 2*border){
1858 if (prt_fctn != '\0'){
1860 if (leg_c >= 2 && prt_fctn == 'j') {
1861 glue = (im->xgif - fill - 2* border) / (leg_c-1);
1862 /* if (glue > 2 * SmallFont->w) glue = 0; */
1866 if (prt_fctn =='c') leg_x = (im->xgif - fill) / 2.0;
1867 if (prt_fctn =='r') leg_x = im->xgif - fill - border;
1869 for(ii=mark;ii<=i;ii++){
1870 if(im->gdes[ii].legend[0]=='\0')
1872 im->gdes[ii].legloc.x = leg_x;
1873 im->gdes[ii].legloc.y = leg_y;
1875 + strlen(im->gdes[ii].legend)*SmallFont->w
1878 if (im->gdes[ii].gf != GF_GPRINT &&
1879 im->gdes[ii].gf != GF_COMMENT)
1882 leg_y = leg_y + SmallFont->h*1.2;
1883 if (prt_fctn == 's') leg_y -= SmallFont->h *0.5;
1895 /* create a grid on the graph. it determines what to do
1896 from the values of xsize, start and end */
1898 /* the xaxis labels are determined from the number of seconds per pixel
1899 in the requested graph */
1904 horizontal_grid(gdImagePtr gif, image_desc_t *im)
1912 char graph_label[100];
1913 gdPoint polyPoints[4];
1914 int labfact,gridind;
1915 int styleMinor[2],styleMajor[2];
1916 int decimals, fractionals;
1921 range = im->maxval - im->minval;
1922 scaledrange = range / im->magfact;
1924 /* does the scale of this graph make it impossible to put lines
1925 on it? If so, give up. */
1926 if (isnan(scaledrange)) {
1930 styleMinor[0] = graph_col[GRC_GRID].i;
1931 styleMinor[1] = gdTransparent;
1933 styleMajor[0] = graph_col[GRC_MGRID].i;
1934 styleMajor[1] = gdTransparent;
1936 /* find grid spaceing */
1938 if(isnan(im->ygridstep)){
1939 if(im->extra_flags & ALTYGRID) {
1940 /* find the value with max number of digits. Get number of digits */
1941 decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))));
1942 if(decimals <= 0) /* everything is small. make place for zero */
1945 fractionals = floor(log10(range));
1946 if(fractionals < 0) /* small amplitude. */
1947 sprintf(labfmt, "%%%d.%df", decimals - fractionals + 1, -fractionals + 1);
1949 sprintf(labfmt, "%%%d.1f", decimals + 1);
1950 gridstep = pow((double)10, (double)fractionals);
1951 if(gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1953 /* should have at least 5 lines but no more then 15 */
1954 if(range/gridstep < 5)
1956 if(range/gridstep > 15)
1958 if(range/gridstep > 5) {
1960 if(range/gridstep > 8)
1969 for(i=0;ylab[i].grid > 0;i++){
1970 pixel = im->ysize / (scaledrange / ylab[i].grid);
1971 if (gridind == -1 && pixel > 5) {
1978 if (pixel * ylab[gridind].lfac[i] >= 2 * SmallFont->h) {
1979 labfact = ylab[gridind].lfac[i];
1984 gridstep = ylab[gridind].grid * im->magfact;
1987 gridstep = im->ygridstep;
1988 labfact = im->ylabfact;
1991 polyPoints[0].x=im->xorigin;
1992 polyPoints[1].x=im->xorigin+im->xsize;
1993 sgrid = (int)( im->minval / gridstep - 1);
1994 egrid = (int)( im->maxval / gridstep + 1);
1995 scaledstep = gridstep/im->magfact;
1996 for (i = sgrid; i <= egrid; i++){
1997 polyPoints[0].y=ytr(im,gridstep*i);
1998 if ( polyPoints[0].y >= im->yorigin-im->ysize
1999 && polyPoints[0].y <= im->yorigin) {
2000 if(i % labfact == 0){
2001 if (i==0 || im->symbol == ' ') {
2003 if(im->extra_flags & ALTYGRID) {
2004 sprintf(graph_label,labfmt,scaledstep*i);
2007 sprintf(graph_label,"%4.1f",scaledstep*i);
2010 sprintf(graph_label,"%4.0f",scaledstep*i);
2014 sprintf(graph_label,"%4.1f %c",scaledstep*i, im->symbol);
2016 sprintf(graph_label,"%4.0f %c",scaledstep*i, im->symbol);
2020 gdImageString(gif, SmallFont,
2021 (polyPoints[0].x - (strlen(graph_label) *
2023 polyPoints[0].y - SmallFont->h/2+1,
2024 (unsigned char *)graph_label, graph_col[GRC_FONT].i);
2026 gdImageSetStyle(gif, styleMajor, 2);
2028 gdImageLine(gif, polyPoints[0].x-2,polyPoints[0].y,
2029 polyPoints[0].x+2,polyPoints[0].y,graph_col[GRC_MGRID].i);
2030 gdImageLine(gif, polyPoints[1].x-2,polyPoints[0].y,
2031 polyPoints[1].x+2,polyPoints[0].y,graph_col[GRC_MGRID].i);
2033 gdImageSetStyle(gif, styleMinor, 2);
2034 gdImageLine(gif, polyPoints[0].x-1,polyPoints[0].y,
2035 polyPoints[0].x+1,polyPoints[0].y,graph_col[GRC_GRID].i);
2036 gdImageLine(gif, polyPoints[1].x-1,polyPoints[0].y,
2037 polyPoints[1].x+1,polyPoints[0].y,graph_col[GRC_GRID].i);
2039 gdImageLine(gif, polyPoints[0].x,polyPoints[0].y,
2040 polyPoints[1].x,polyPoints[0].y,gdStyled);
2043 /* if(im->minval * im->maxval < 0){
2044 polyPoints[0].y=ytr(0);
2045 gdImageLine(gif, polyPoints[0].x,polyPoints[0].y,
2046 polyPoints[1].x,polyPoints[0].y,graph_col[GRC_MGRID].i);
2052 /* logaritmic horizontal grid */
2054 horizontal_log_grid(gdImagePtr gif, image_desc_t *im)
2058 int minoridx=0, majoridx=0;
2059 char graph_label[100];
2060 gdPoint polyPoints[4];
2061 int styleMinor[2],styleMajor[2];
2062 double value, pixperstep, minstep;
2064 /* find grid spaceing */
2065 pixpex= (double)im->ysize / (log10(im->maxval) - log10(im->minval));
2067 if (isnan(pixpex)) {
2071 for(i=0;yloglab[i][0] > 0;i++){
2072 minstep = log10(yloglab[i][0]);
2073 for(ii=1;yloglab[i][ii+1] > 0;ii++){
2074 if(yloglab[i][ii+2]==0){
2075 minstep = log10(yloglab[i][ii+1])-log10(yloglab[i][ii]);
2079 pixperstep = pixpex * minstep;
2080 if(pixperstep > 5){minoridx = i;}
2081 if(pixperstep > 2 * SmallFont->h){majoridx = i;}
2084 styleMinor[0] = graph_col[GRC_GRID].i;
2085 styleMinor[1] = gdTransparent;
2087 styleMajor[0] = graph_col[GRC_MGRID].i;
2088 styleMajor[1] = gdTransparent;
2090 polyPoints[0].x=im->xorigin;
2091 polyPoints[1].x=im->xorigin+im->xsize;
2092 /* paint minor grid */
2093 for (value = pow((double)10, log10(im->minval)
2094 - fmod(log10(im->minval),log10(yloglab[minoridx][0])));
2095 value <= im->maxval;
2096 value *= yloglab[minoridx][0]){
2097 if (value < im->minval) continue;
2099 while(yloglab[minoridx][++i] > 0){
2100 polyPoints[0].y = ytr(im,value * yloglab[minoridx][i]);
2101 if (polyPoints[0].y <= im->yorigin - im->ysize) break;
2102 gdImageSetStyle(gif, styleMinor, 2);
2103 gdImageLine(gif, polyPoints[0].x-1,polyPoints[0].y,
2104 polyPoints[0].x+1,polyPoints[0].y,graph_col[GRC_GRID].i);
2105 gdImageLine(gif, polyPoints[1].x-1,polyPoints[0].y,
2106 polyPoints[1].x+1,polyPoints[0].y,graph_col[GRC_GRID].i);
2108 gdImageLine(gif, polyPoints[0].x,polyPoints[0].y,
2109 polyPoints[1].x,polyPoints[0].y,gdStyled);
2113 /* paint major grid and labels*/
2114 for (value = pow((double)10, log10(im->minval)
2115 - fmod(log10(im->minval),log10(yloglab[majoridx][0])));
2116 value <= im->maxval;
2117 value *= yloglab[majoridx][0]){
2118 if (value < im->minval) continue;
2120 while(yloglab[majoridx][++i] > 0){
2121 polyPoints[0].y = ytr(im,value * yloglab[majoridx][i]);
2122 if (polyPoints[0].y <= im->yorigin - im->ysize) break;
2123 gdImageSetStyle(gif, styleMajor, 2);
2124 gdImageLine(gif, polyPoints[0].x-2,polyPoints[0].y,
2125 polyPoints[0].x+2,polyPoints[0].y,graph_col[GRC_MGRID].i);
2126 gdImageLine(gif, polyPoints[1].x-2,polyPoints[0].y,
2127 polyPoints[1].x+2,polyPoints[0].y,graph_col[GRC_MGRID].i);
2129 gdImageLine(gif, polyPoints[0].x,polyPoints[0].y,
2130 polyPoints[1].x,polyPoints[0].y,gdStyled);
2131 sprintf(graph_label,"%3.0e",value * yloglab[majoridx][i]);
2132 gdImageString(gif, SmallFont,
2133 (polyPoints[0].x - (strlen(graph_label) *
2135 polyPoints[0].y - SmallFont->h/2+1,
2136 (unsigned char *)graph_label, graph_col[GRC_FONT].i);
2148 int xlab_sel; /* which sort of label and grid ? */
2151 char graph_label[100];
2152 gdPoint polyPoints[4]; /* points for filled graph and more*/
2154 /* style for grid lines */
2158 /* the type of time grid is determined by finding
2159 the number of seconds per pixel in the graph */
2162 if(im->xlab_user.minsec == -1){
2163 factor=(im->end - im->start)/im->xsize;
2165 while ( xlab[xlab_sel+1].minsec != -1
2166 && xlab[xlab_sel+1].minsec <= factor){ xlab_sel++; }
2167 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2168 im->xlab_user.gridst = xlab[xlab_sel].gridst;
2169 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2170 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2171 im->xlab_user.labtm = xlab[xlab_sel].labtm;
2172 im->xlab_user.labst = xlab[xlab_sel].labst;
2173 im->xlab_user.precis = xlab[xlab_sel].precis;
2174 im->xlab_user.stst = xlab[xlab_sel].stst;
2177 /* y coords are the same for every line ... */
2178 polyPoints[0].y = im->yorigin;
2179 polyPoints[1].y = im->yorigin-im->ysize;
2181 /* paint the minor grid */
2182 for(ti = find_first_time(im->start,
2183 im->xlab_user.gridtm,
2184 im->xlab_user.gridst);
2186 ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
2188 /* are we inside the graph ? */
2189 if (ti < im->start || ti > im->end) continue;
2190 polyPoints[0].x = xtr(im,ti);
2191 styleDotted[0] = graph_col[GRC_GRID].i;
2192 styleDotted[1] = gdTransparent;
2194 gdImageSetStyle(gif, styleDotted, 2);
2196 gdImageLine(gif, polyPoints[0].x,polyPoints[0].y,
2197 polyPoints[0].x,polyPoints[1].y,gdStyled);
2198 gdImageLine(gif, polyPoints[0].x,polyPoints[0].y-1,
2199 polyPoints[0].x,polyPoints[0].y+1,graph_col[GRC_GRID].i);
2200 gdImageLine(gif, polyPoints[0].x,polyPoints[1].y-1,
2201 polyPoints[0].x,polyPoints[1].y+1,graph_col[GRC_GRID].i);
2204 /* paint the major grid */
2205 for(ti = find_first_time(im->start,
2206 im->xlab_user.mgridtm,
2207 im->xlab_user.mgridst);
2209 ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
2211 /* are we inside the graph ? */
2212 if (ti < im->start || ti > im->end) continue;
2213 polyPoints[0].x = xtr(im,ti);
2214 styleDotted[0] = graph_col[GRC_MGRID].i;
2215 styleDotted[1] = gdTransparent;
2216 gdImageSetStyle(gif, styleDotted, 2);
2218 gdImageLine(gif, polyPoints[0].x,polyPoints[0].y,
2219 polyPoints[0].x,polyPoints[1].y,gdStyled);
2220 gdImageLine(gif, polyPoints[0].x,polyPoints[0].y-2,
2221 polyPoints[0].x,polyPoints[0].y+2,graph_col[GRC_MGRID].i);
2222 gdImageLine(gif, polyPoints[0].x,polyPoints[1].y-2,
2223 polyPoints[0].x,polyPoints[1].y+2,graph_col[GRC_MGRID].i);
2225 /* paint the labels below the graph */
2226 for(ti = find_first_time(im->start,
2227 im->xlab_user.labtm,
2228 im->xlab_user.labst);
2230 ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
2233 tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
2236 strftime(graph_label,99,im->xlab_user.stst,localtime(&tilab));
2238 # error "your libc has no strftime I guess we'll abort the exercise here."
2240 width=strlen(graph_label) * SmallFont->w;
2241 gr_pos=xtr(im,tilab) - width/2;
2242 if (gr_pos >= im->xorigin
2243 && gr_pos + width <= im->xorigin+im->xsize)
2244 gdImageString(gif, SmallFont,
2245 gr_pos, polyPoints[0].y+4,
2246 (unsigned char *)graph_label, graph_col[GRC_FONT].i);
2258 /* draw x and y axis */
2259 gdImageLine(gif, im->xorigin+im->xsize,im->yorigin,
2260 im->xorigin+im->xsize,im->yorigin-im->ysize,
2261 graph_col[GRC_GRID].i);
2263 gdImageLine(gif, im->xorigin,im->yorigin-im->ysize,
2264 im->xorigin+im->xsize,im->yorigin-im->ysize,
2265 graph_col[GRC_GRID].i);
2267 gdImageLine(gif, im->xorigin-4,im->yorigin,
2268 im->xorigin+im->xsize+4,im->yorigin,
2269 graph_col[GRC_FONT].i);
2271 gdImageLine(gif, im->xorigin,im->yorigin,
2272 im->xorigin,im->yorigin-im->ysize,
2273 graph_col[GRC_GRID].i);
2275 /* arrow for X axis direction */
2276 gdImageLine(gif, im->xorigin+im->xsize+4, im->yorigin-3, im->xorigin+im->xsize+4, im->yorigin+3,graph_col[GRC_ARROW].i);
2277 gdImageLine(gif, im->xorigin+im->xsize+4, im->yorigin-3, im->xorigin+im->xsize+9, im->yorigin,graph_col[GRC_ARROW].i);
2278 gdImageLine(gif, im->xorigin+im->xsize+4, im->yorigin+3, im->xorigin+im->xsize+9, im->yorigin,graph_col[GRC_ARROW].i);
2280 /* gdImageLine(gif, im->xorigin+im->xsize-1, im->yorigin-3, im->xorigin+im->xsize-1, im->yorigin+3,graph_col[GRC_MGRID].i);
2281 gdImageLine(gif, im->xorigin+im->xsize, im->yorigin-2, im->xorigin+im->xsize, im->yorigin+2,graph_col[GRC_MGRID].i);
2282 gdImageLine(gif, im->xorigin+im->xsize+1, im->yorigin-2, im->xorigin+im->xsize+1, im->yorigin+2,graph_col[GRC_MGRID].i);
2283 gdImageLine(gif, im->xorigin+im->xsize+2, im->yorigin-2, im->xorigin+im->xsize+2, im->yorigin+2,graph_col[GRC_MGRID].i);
2284 gdImageLine(gif, im->xorigin+im->xsize+3, im->yorigin-1, im->xorigin+im->xsize+3, im->yorigin+1,graph_col[GRC_MGRID].i);
2285 gdImageLine(gif, im->xorigin+im->xsize+4, im->yorigin-1, im->xorigin+im->xsize+4, im->yorigin+1,graph_col[GRC_MGRID].i);
2286 gdImageLine(gif, im->xorigin+im->xsize+5, im->yorigin, im->xorigin+im->xsize+5, im->yorigin,graph_col[GRC_MGRID].i); */
2301 gdPoint polyPoints[4]; /* points for filled graph and more*/
2303 /* draw 3d border */
2304 gdImageLine(gif,0,0,im->xgif-1,0,graph_col[GRC_SHADEA].i);
2305 gdImageLine(gif,1,1,im->xgif-2,1,graph_col[GRC_SHADEA].i);
2306 gdImageLine(gif,0,0,0,im->ygif-1,graph_col[GRC_SHADEA].i);
2307 gdImageLine(gif,1,1,1,im->ygif-2,graph_col[GRC_SHADEA].i);
2308 gdImageLine(gif,im->xgif-1,0,im->xgif-1,im->ygif-1,graph_col[GRC_SHADEB].i);
2309 gdImageLine(gif,0,im->ygif-1,im->xgif-1,im->ygif-1,graph_col[GRC_SHADEB].i);
2310 gdImageLine(gif,im->xgif-2,1,im->xgif-2,im->ygif-2,graph_col[GRC_SHADEB].i);
2311 gdImageLine(gif,1,im->ygif-2,im->xgif-2,im->ygif-2,graph_col[GRC_SHADEB].i);
2314 if (im->draw_x_grid == 1 )
2315 vertical_grid(gif, im);
2317 if (im->draw_y_grid == 1){
2318 if(im->logarithmic){
2319 res = horizontal_log_grid(gif,im);
2321 res = horizontal_grid(gif,im);
2324 /* dont draw horizontal grid if there is no min and max val */
2326 char *nodata = "No Data found";
2327 gdImageString(gif, LargeFont,
2329 - (strlen(nodata)*LargeFont->w)/2,
2330 (2*im->yorigin-im->ysize) / 2,
2331 (unsigned char *)nodata, graph_col[GRC_FONT].i);
2335 /* yaxis description */
2336 gdImageStringUp(gif, SmallFont,
2338 (im->yorigin - im->ysize/2
2339 +(strlen(im->ylegend)*SmallFont->w)/2 ),
2340 (unsigned char *)im->ylegend, graph_col[GRC_FONT].i);
2344 gdImageString(gif, LargeFont,
2346 - (strlen(im->title)*LargeFont->w)/2,
2348 (unsigned char *)im->title, graph_col[GRC_FONT].i);
2351 if( !(im->extra_flags & NOLEGEND) ) {
2352 for(i=0;i<im->gdes_c;i++){
2353 if(im->gdes[i].legend[0] =='\0')
2356 if(im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT){
2358 polyPoints[0].x = im->gdes[i].legloc.x;
2359 polyPoints[0].y = im->gdes[i].legloc.y+1;
2360 polyPoints[1].x = polyPoints[0].x+boxH;
2361 polyPoints[2].x = polyPoints[0].x+boxH;
2362 polyPoints[3].x = polyPoints[0].x;
2363 polyPoints[1].y = polyPoints[0].y;
2364 polyPoints[2].y = polyPoints[0].y+boxV;
2365 polyPoints[3].y = polyPoints[0].y+boxV;
2366 gdImageFilledPolygon(gif,polyPoints,4,im->gdes[i].col.i);
2367 gdImagePolygon(gif,polyPoints,4,graph_col[GRC_FRAME].i);
2369 gdImageString(gif, SmallFont,
2370 polyPoints[0].x+boxH+6,
2372 (unsigned char *)im->gdes[i].legend,
2373 graph_col[GRC_FONT].i);
2375 polyPoints[0].x = im->gdes[i].legloc.x;
2376 polyPoints[0].y = im->gdes[i].legloc.y;
2378 gdImageString(gif, SmallFont,
2381 (unsigned char *)im->gdes[i].legend,
2382 graph_col[GRC_FONT].i);
2388 gator(gif, (int) im->xgif-5, 5);
2394 MkLineBrush(image_desc_t *im,long cosel, enum gf_en typsel){
2399 brush=gdImageCreate(1,1);
2402 brush=gdImageCreate(2,2);
2405 brush=gdImageCreate(3,3);
2411 gdImageColorTransparent(brush,
2412 gdImageColorAllocate(brush, 0, 0, 0));
2414 pen = gdImageColorAllocate(brush,
2415 im->gdes[cosel].col.red,
2416 im->gdes[cosel].col.green,
2417 im->gdes[cosel].col.blue);
2421 gdImageSetPixel(brush,0,0,pen);
2424 gdImageSetPixel(brush,0,0,pen);
2425 gdImageSetPixel(brush,0,1,pen);
2426 gdImageSetPixel(brush,1,0,pen);
2427 gdImageSetPixel(brush,1,1,pen);
2430 gdImageSetPixel(brush,1,0,pen);
2431 gdImageSetPixel(brush,0,1,pen);
2432 gdImageSetPixel(brush,1,1,pen);
2433 gdImageSetPixel(brush,2,1,pen);
2434 gdImageSetPixel(brush,1,2,pen);
2441 /*****************************************************
2442 * lazy check make sure we rely need to create this graph
2443 *****************************************************/
2445 int lazy_check(image_desc_t *im){
2448 struct stat gifstat;
2450 if (im->lazy == 0) return 0; /* no lazy option */
2451 if (stat(im->graphfile,&gifstat) != 0)
2452 return 0; /* can't stat */
2453 /* one pixel in the existing graph is more then what we would
2455 if (time(NULL) - gifstat.st_mtime >
2456 (im->end - im->start) / im->xsize)
2458 if ((fd = fopen(im->graphfile,"rb")) == NULL)
2459 return 0; /* the file does not exist */
2460 switch (im->imgformat) {
2462 size = GifSize(fd,&(im->xgif),&(im->ygif));
2465 size = PngSize(fd,&(im->xgif),&(im->ygif));
2472 /* draw that picture thing ... */
2474 graph_paint(image_desc_t *im, char ***calcpr)
2477 int lazy = lazy_check(im);
2481 gdImagePtr gif,brush;
2483 double areazero = 0.0;
2484 enum gf_en stack_gf = GF_PRINT;
2485 graph_desc_t *lastgdes = NULL;
2486 gdPoint canvas[4], back[4]; /* points for canvas*/
2488 /* if we are lazy and there is nothing to PRINT ... quit now */
2489 if (lazy && im->prt_c==0) return 0;
2491 /* pull the data from the rrd files ... */
2493 if(data_fetch(im)==-1)
2496 /* evaluate CDEF operations ... */
2497 if(data_calc(im)==-1)
2500 /* calculate and PRINT and GPRINT definitions. We have to do it at
2501 * this point because it will affect the length of the legends
2502 * if there are no graph elements we stop here ...
2503 * if we are lazy, try to quit ...
2505 i=print_calc(im,calcpr);
2507 if(i==0 || lazy) return 0;
2509 /* get actual drawing data and find min and max values*/
2510 if(data_proc(im)==-1)
2513 if(!im->logarithmic){si_unit(im);} /* identify si magnitude Kilo, Mega Giga ? */
2515 if(!im->rigid && ! im->logarithmic)
2516 expand_range(im); /* make sure the upper and lower limit are
2519 /* init xtr and ytr */
2520 /* determine the actual size of the gif to draw. The size given
2521 on the cmdline is the graph area. But we need more as we have
2522 draw labels and other things outside the graph area */
2525 im->xorigin = 10 + 9 * SmallFont->w+SmallFont->h;
2528 im->yorigin = 14 + im->ysize;
2531 if(im->title[0] != '\0')
2532 im->yorigin += (LargeFont->h+4);
2534 im->xgif=20+im->xsize + im->xorigin;
2535 im->ygif= im->yorigin+2*SmallFont->h;
2537 /* determine where to place the legends onto the graphics.
2538 and set im->ygif to match space requirements for text */
2539 if(leg_place(im)==-1)
2542 gif=gdImageCreate(im->xgif,im->ygif);
2544 gdImageInterlace(gif, im->interlaced);
2546 /* allocate colors for the screen elements */
2547 for(i=0;i<DIM(graph_col);i++)
2548 /* check for user override values */
2549 if(im->graph_col[i].red != -1)
2551 gdImageColorAllocate( gif,
2552 im->graph_col[i].red,
2553 im->graph_col[i].green,
2554 im->graph_col[i].blue);
2557 gdImageColorAllocate( gif,
2563 /* allocate colors for the graph */
2564 for(i=0;i<im->gdes_c;i++)
2565 /* only for elements which have a color defined */
2566 if (im->gdes[i].col.red != -1)
2568 gdImageColorAllocate(gif,
2569 im->gdes[i].col.red,
2570 im->gdes[i].col.green,
2571 im->gdes[i].col.blue);
2574 /* the actual graph is created by going through the individual
2575 graph elements and then drawing them */
2579 back[1].x = back[0].x+im->xgif;
2580 back[1].y = back[0].y;
2581 back[2].x = back[1].x;
2582 back[2].y = back[0].y+im->ygif;
2583 back[3].x = back[0].x;
2584 back[3].y = back[2].y;
2586 gdImageFilledPolygon(gif,back,4,graph_col[GRC_BACK].i);
2588 canvas[0].x = im->xorigin;
2589 canvas[0].y = im->yorigin;
2590 canvas[1].x = canvas[0].x+im->xsize;
2591 canvas[1].y = canvas[0].y;
2592 canvas[2].x = canvas[1].x;
2593 canvas[2].y = canvas[0].y-im->ysize;
2594 canvas[3].x = canvas[0].x;
2595 canvas[3].y = canvas[2].y;
2597 gdImageFilledPolygon(gif,canvas,4,graph_col[GRC_CANVAS].i);
2599 if (im->minval > 0.0)
2600 areazero = im->minval;
2601 if (im->maxval < 0.0)
2602 areazero = im->maxval;
2606 for(i=0;i<im->gdes_c;i++){
2608 switch(im->gdes[i].gf){
2618 for (ii = 0; ii < im->xsize; ii++)
2620 if (!isnan(im->gdes[i].p_data[ii]) &&
2621 im->gdes[i].p_data[ii] > 0.0)
2623 /* generate a tick */
2624 gdImageLine(gif, im -> xorigin + ii,
2625 im -> yorigin - (im -> gdes[i].yrule * im -> ysize),
2628 im -> gdes[i].col.i);
2636 stack_gf = im->gdes[i].gf;
2638 /* fix data points at oo and -oo */
2639 for(ii=0;ii<im->xsize;ii++){
2640 if (isinf(im->gdes[i].p_data[ii])){
2641 if (im->gdes[i].p_data[ii] > 0) {
2642 im->gdes[i].p_data[ii] = im->maxval ;
2644 im->gdes[i].p_data[ii] = im->minval ;
2650 if (im->gdes[i].col.i != -1){
2651 /* GF_LINE and frined */
2652 if(stack_gf == GF_LINE1 || stack_gf == GF_LINE2 || stack_gf == GF_LINE3 ){
2653 brush = MkLineBrush(im,i,stack_gf);
2654 gdImageSetBrush(gif, brush);
2655 for(ii=1;ii<im->xsize;ii++){
2656 if (isnan(im->gdes[i].p_data[ii-1]) ||
2657 isnan(im->gdes[i].p_data[ii]))
2660 ii+im->xorigin-1,ytr(im,im->gdes[i].p_data[ii-1]),
2661 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2665 gdImageDestroy(brush);
2668 /* GF_AREA STACK type*/
2669 if (im->gdes[i].gf == GF_STACK )
2670 for(ii=0;ii<im->xsize;ii++){
2671 if(isnan(im->gdes[i].p_data[ii])){
2672 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
2676 if (lastgdes->p_data[ii] == im->gdes[i].p_data[ii]){
2680 ii+im->xorigin,ytr(im,lastgdes->p_data[ii]),
2681 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2685 else /* simple GF_AREA */
2686 for(ii=0;ii<im->xsize;ii++){
2687 if (isnan(im->gdes[i].p_data[ii])) {
2688 im->gdes[i].p_data[ii] = 0;
2692 ii+im->xorigin,ytr(im,areazero),
2693 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2697 lastgdes = &(im->gdes[i]);
2704 /* the RULES are the last thing to paint ... */
2705 for(i=0;i<im->gdes_c;i++){
2707 switch(im->gdes[i].gf){
2709 if(im->gdes[i].yrule >= im->minval
2710 && im->gdes[i].yrule <= im->maxval)
2712 im->xorigin,ytr(im,im->gdes[i].yrule),
2713 im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2717 if(im->gdes[i].xrule >= im->start
2718 && im->gdes[i].xrule <= im->end)
2720 xtr(im,im->gdes[i].xrule),im->yorigin,
2721 xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2729 if (strcmp(im->graphfile,"-")==0) {
2731 /* Change translation mode for stdout to BINARY */
2732 _setmode( _fileno( stdout ), O_BINARY );
2736 if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2737 rrd_set_error("Opening '%s' for write: %s",im->graphfile, strerror(errno));
2741 switch (im->imgformat) {
2743 gdImageGif(gif, fo);
2746 gdImagePng(gif, fo);
2749 if (strcmp(im->graphfile,"-") != 0)
2751 gdImageDestroy(gif);
2757 /*****************************************************
2759 *****************************************************/
2762 gdes_alloc(image_desc_t *im){
2764 long def_step = (im->end-im->start)/im->xsize;
2766 if (im->step > def_step) /* step can be increassed ... no decreassed */
2767 def_step = im->step;
2771 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2772 * sizeof(graph_desc_t)))==NULL){
2773 rrd_set_error("realloc graph_descs");
2778 im->gdes[im->gdes_c-1].step=def_step;
2779 im->gdes[im->gdes_c-1].start=im->start;
2780 im->gdes[im->gdes_c-1].end=im->end;
2781 im->gdes[im->gdes_c-1].vname[0]='\0';
2782 im->gdes[im->gdes_c-1].data=NULL;
2783 im->gdes[im->gdes_c-1].ds_namv=NULL;
2784 im->gdes[im->gdes_c-1].data_first=0;
2785 im->gdes[im->gdes_c-1].p_data=NULL;
2786 im->gdes[im->gdes_c-1].rpnp=NULL;
2787 im->gdes[im->gdes_c-1].col.red = -1;
2788 im->gdes[im->gdes_c-1].col.i=-1;
2789 im->gdes[im->gdes_c-1].legend[0]='\0';
2790 im->gdes[im->gdes_c-1].rrd[0]='\0';
2791 im->gdes[im->gdes_c-1].ds=-1;
2792 im->gdes[im->gdes_c-1].p_data=NULL;
2796 /* copies input untill the first unescaped colon is found
2797 or until input ends. backslashes have to be escaped as well */
2799 scan_for_col(char *input, int len, char *output)
2804 input[inp] != ':' &&
2807 if (input[inp] == '\\' &&
2808 input[inp+1] != '\0' &&
2809 (input[inp+1] == '\\' ||
2810 input[inp+1] == ':')){
2811 output[outp++] = input[++inp];
2814 output[outp++] = input[inp];
2817 output[outp] = '\0';
2822 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize)
2828 time_t start_tmp=0,end_tmp=0;
2829 char scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
2831 unsigned int col_red,col_green,col_blue;
2833 int linepass = 0; /* stack can only follow directly after LINE* AREA or STACK */
2834 struct time_value start_tv, end_tv;
2835 char *parsetime_error = NULL;
2840 parsetime("end-24h", &start_tv);
2841 parsetime("now", &end_tv);
2843 im.xlab_user.minsec = -1;
2849 im.ylegend[0] = '\0';
2854 im.unitsexponent= 9999;
2860 im.ygridstep = DNAN;
2867 im.imgformat = IF_GIF; /* we default to GIF output */
2869 for(i=0;i<DIM(graph_col);i++)
2870 im.graph_col[i].red=-1;
2874 static struct option long_options[] =
2876 {"start", required_argument, 0, 's'},
2877 {"end", required_argument, 0, 'e'},
2878 {"x-grid", required_argument, 0, 'x'},
2879 {"y-grid", required_argument, 0, 'y'},
2880 {"vertical-label",required_argument,0,'v'},
2881 {"width", required_argument, 0, 'w'},
2882 {"height", required_argument, 0, 'h'},
2883 {"interlaced", no_argument, 0, 'i'},
2884 {"upper-limit",required_argument, 0, 'u'},
2885 {"lower-limit",required_argument, 0, 'l'},
2886 {"rigid", no_argument, 0, 'r'},
2887 {"base", required_argument, 0, 'b'},
2888 {"logarithmic",no_argument, 0, 'o'},
2889 {"color", required_argument, 0, 'c'},
2890 {"title", required_argument, 0, 't'},
2891 {"imginfo", required_argument, 0, 'f'},
2892 {"imgformat", required_argument, 0, 'a'},
2893 {"lazy", no_argument, 0, 'z'},
2894 {"no-legend", no_argument, 0, 'g'},
2895 {"alt-y-grid", no_argument, 0, 257 },
2896 {"alt-autoscale", no_argument, 0, 258 },
2897 {"alt-autoscale-max", no_argument, 0, 259 },
2898 {"units-exponent",required_argument, 0, 260},
2899 {"step", required_argument, 0, 261},
2901 int option_index = 0;
2905 opt = getopt_long(argc, argv,
2906 "s:e:x:y:v:w:h:iu:l:rb:oc:t:f:a:z:g",
2907 long_options, &option_index);
2914 im.extra_flags |= ALTYGRID;
2917 im.extra_flags |= ALTAUTOSCALE;
2920 im.extra_flags |= ALTAUTOSCALE_MAX;
2923 im.extra_flags |= NOLEGEND;
2926 im.unitsexponent = atoi(optarg);
2929 im.step = atoi(optarg);
2932 if ((parsetime_error = parsetime(optarg, &start_tv))) {
2933 rrd_set_error( "start time: %s", parsetime_error );
2938 if ((parsetime_error = parsetime(optarg, &end_tv))) {
2939 rrd_set_error( "end time: %s", parsetime_error );
2944 if(strcmp(optarg,"none") == 0){
2950 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
2952 &im.xlab_user.gridst,
2954 &im.xlab_user.mgridst,
2956 &im.xlab_user.labst,
2957 &im.xlab_user.precis,
2958 &stroff) == 7 && stroff != 0){
2959 strncpy(im.xlab_form, optarg+stroff, sizeof(im.xlab_form) - 1);
2960 if((im.xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
2961 rrd_set_error("unknown keyword %s",scan_gtm);
2963 } else if ((im.xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
2964 rrd_set_error("unknown keyword %s",scan_mtm);
2966 } else if ((im.xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
2967 rrd_set_error("unknown keyword %s",scan_ltm);
2970 im.xlab_user.minsec = 1;
2971 im.xlab_user.stst = im.xlab_form;
2973 rrd_set_error("invalid x-grid format");
2979 if(strcmp(optarg,"none") == 0){
2987 &im.ylabfact) == 2) {
2988 if(im.ygridstep<=0){
2989 rrd_set_error("grid step must be > 0");
2991 } else if (im.ylabfact < 1){
2992 rrd_set_error("label factor must be > 0");
2996 rrd_set_error("invalid y-grid format");
3001 strncpy(im.ylegend,optarg,150);
3002 im.ylegend[150]='\0';
3005 im.maxval = atof(optarg);
3008 im.minval = atof(optarg);
3011 im.base = atol(optarg);
3012 if(im.base != 1024 && im.base != 1000 ){
3013 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
3018 long_tmp = atol(optarg);
3019 if (long_tmp < 10) {
3020 rrd_set_error("width below 10 pixels");
3023 im.xsize = long_tmp;
3026 long_tmp = atol(optarg);
3027 if (long_tmp < 10) {
3028 rrd_set_error("height below 10 pixels");
3031 im.ysize = long_tmp;
3040 im.imginfo = optarg;
3043 if((im.imgformat = if_conv(optarg)) == -1) {
3044 rrd_set_error("unsupported graphics format '%s'",optarg);
3053 if (isnan(im.minval))
3058 "%10[A-Z]#%2x%2x%2x",
3059 col_nam,&col_red,&col_green,&col_blue) == 4){
3061 if((ci=grc_conv(col_nam)) != -1){
3062 im.graph_col[ci].red=col_red;
3063 im.graph_col[ci].green=col_green;
3064 im.graph_col[ci].blue=col_blue;
3066 rrd_set_error("invalid color name '%s'",col_nam);
3069 rrd_set_error("invalid color def format");
3074 strncpy(im.title,optarg,150);
3080 rrd_set_error("unknown option '%c'", optopt);
3082 rrd_set_error("unknown option '%s'",argv[optind-1]);
3087 if (optind >= argc) {
3088 rrd_set_error("missing filename");
3092 if (im.logarithmic == 1 && (im.minval <= 0 || isnan(im.minval))){
3093 rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");
3097 strncpy(im.graphfile,argv[optind],MAXPATH-1);
3098 im.graphfile[MAXPATH-1]='\0';
3100 if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
3104 if (start_tmp < 3600*24*365*10){
3105 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
3109 if (end_tmp < start_tmp) {
3110 rrd_set_error("start (%ld) should be less than end (%ld)",
3111 start_tmp, end_tmp);
3115 im.start = start_tmp;
3119 for(i=optind+1;i<argc;i++){
3122 char varname[30],*rpnex;
3124 if(sscanf(argv[i],"%10[A-Z0-9]:%n",symname,&argstart)==1){
3125 if((im.gdes[im.gdes_c-1].gf=gf_conv(symname))==-1){
3127 rrd_set_error("unknown function '%s'",symname);
3131 rrd_set_error("can't parse '%s'",argv[i]);
3136 /* reset linepass if a non LINE/STACK/AREA operator gets parsed
3138 if (im.gdes[im.gdes_c-1].gf != GF_LINE1 &&
3139 im.gdes[im.gdes_c-1].gf != GF_LINE2 &&
3140 im.gdes[im.gdes_c-1].gf != GF_LINE3 &&
3141 im.gdes[im.gdes_c-1].gf != GF_AREA &&
3142 im.gdes[im.gdes_c-1].gf != GF_STACK) {
3147 switch(im.gdes[im.gdes_c-1].gf){
3153 "%29[^#:]:" CF_NAM_FMT ":%n",
3154 varname,symname,&strstart) == 2){
3155 scan_for_col(&argv[i][argstart+strstart],FMT_LEG_LEN,im.gdes[im.gdes_c-1].format);
3156 if((im.gdes[im.gdes_c-1].vidx=find_var(&im,varname))==-1){
3158 rrd_set_error("unknown variable '%s'",varname);
3161 if((im.gdes[im.gdes_c-1].cf=cf_conv(symname))==-1){
3168 rrd_set_error("can't parse '%s'",&argv[i][argstart]);
3173 if(strlen(&argv[i][argstart])>FMT_LEG_LEN) argv[i][argstart+FMT_LEG_LEN-3]='\0' ;
3174 strcpy(im.gdes[im.gdes_c-1].legend, &argv[i][argstart]);
3180 &im.gdes[im.gdes_c-1].yrule,
3181 &col_red,&col_green,&col_blue,
3183 im.gdes[im.gdes_c-1].col.red = col_red;
3184 im.gdes[im.gdes_c-1].col.green = col_green;
3185 im.gdes[im.gdes_c-1].col.blue = col_blue;
3187 im.gdes[im.gdes_c-1].legend[0] = '\0';
3189 scan_for_col(&argv[i][argstart+strstart],FMT_LEG_LEN,im.gdes[im.gdes_c-1].legend);
3193 rrd_set_error("can't parse '%s'",&argv[i][argstart]);
3201 &im.gdes[im.gdes_c-1].xrule,
3206 im.gdes[im.gdes_c-1].col.red = col_red;
3207 im.gdes[im.gdes_c-1].col.green = col_green;
3208 im.gdes[im.gdes_c-1].col.blue = col_blue;
3210 im.gdes[im.gdes_c-1].legend[0] = '\0';
3212 scan_for_col(&argv[i][argstart+strstart],FMT_LEG_LEN,im.gdes[im.gdes_c-1].legend);
3216 rrd_set_error("can't parse '%s'",&argv[i][argstart]);
3221 if((scancount=sscanf(
3223 "%29[^:#]#%2x%2x%2x:%lf:%n",
3228 &(im.gdes[im.gdes_c-1].yrule),
3231 im.gdes[im.gdes_c-1].col.red = col_red;
3232 im.gdes[im.gdes_c-1].col.green = col_green;
3233 im.gdes[im.gdes_c-1].col.blue = col_blue;
3235 im.gdes[im.gdes_c-1].legend[0] = '\0';
3237 scan_for_col(&argv[i][argstart+strstart],FMT_LEG_LEN,im.gdes[im.gdes_c-1].legend);
3239 if((im.gdes[im.gdes_c-1].vidx=find_var(&im,varname))==-1){
3241 rrd_set_error("unknown variable '%s'",varname);
3244 if (im.gdes[im.gdes_c-1].yrule <= 0.0 || im.gdes[im.gdes_c-1].yrule > 1.0)
3247 rrd_set_error("Tick mark scaling factor out of range");
3251 im.gdes[im.gdes_c-1].col.red = -1;
3253 /* default tick marks: 10% of the y-axis */
3254 im.gdes[im.gdes_c-1].yrule = 0.1;
3258 rrd_set_error("can't parse '%s'",&argv[i][argstart]);
3260 } /* endif sscanf */
3265 rrd_set_error("STACK must follow AREA, LINE or STACK");
3273 if((scancount=sscanf(
3275 "%29[^:#]#%2x%2x%2x:%n",
3281 im.gdes[im.gdes_c-1].col.red = col_red;
3282 im.gdes[im.gdes_c-1].col.green = col_green;
3283 im.gdes[im.gdes_c-1].col.blue = col_blue;
3285 im.gdes[im.gdes_c-1].legend[0] = '\0';
3287 scan_for_col(&argv[i][argstart+strstart],FMT_LEG_LEN,im.gdes[im.gdes_c-1].legend);
3289 if((im.gdes[im.gdes_c-1].vidx=find_var(&im,varname))==-1){
3291 rrd_set_error("unknown variable '%s'",varname);
3295 im.gdes[im.gdes_c-1].col.red = -1;
3299 rrd_set_error("can't parse '%s'",&argv[i][argstart]);
3304 if((rpnex = malloc(strlen(&argv[i][argstart])*sizeof(char)))==NULL){
3305 rrd_set_error("malloc for CDEF");
3310 DEF_NAM_FMT "=%[^: ]",
3311 im.gdes[im.gdes_c-1].vname,
3315 rrd_set_error("can't parse CDEF '%s'",&argv[i][argstart]);
3318 /* checking for duplicate DEF CDEFS */
3319 if(find_var(&im,im.gdes[im.gdes_c-1].vname) != -1){
3321 rrd_set_error("duplicate variable '%s'",
3322 im.gdes[im.gdes_c-1].vname);
3325 if((im.gdes[im.gdes_c-1].rpnp = str2rpn(&im,rpnex))== NULL){
3326 rrd_set_error("invalid rpn expression '%s'", rpnex);
3336 im.gdes[im.gdes_c-1].vname,
3337 &strstart)== 1 && strstart){ /* is the = did not match %n returns 0 */
3338 if(sscanf(&argv[i][argstart
3340 +scan_for_col(&argv[i][argstart+strstart],
3341 MAXPATH,im.gdes[im.gdes_c-1].rrd)],
3342 ":" DS_NAM_FMT ":" CF_NAM_FMT,
3343 im.gdes[im.gdes_c-1].ds_nam,
3346 rrd_set_error("can't parse DEF '%s' -2",&argv[i][argstart]);
3351 rrd_set_error("can't parse DEF '%s'",&argv[i][argstart]);
3355 /* checking for duplicate DEF CDEFS */
3356 if (find_var(&im,im.gdes[im.gdes_c-1].vname) != -1){
3358 rrd_set_error("duplicate variable '%s'",
3359 im.gdes[im.gdes_c-1].vname);
3362 if((im.gdes[im.gdes_c-1].cf=cf_conv(symname))==-1){
3364 rrd_set_error("unknown cf '%s'",symname);
3373 rrd_set_error("can't make a graph without contents");
3378 /* parse rest of arguments containing information on what to draw*/
3379 if (graph_paint(&im,prdata)==-1){
3389 /* maybe prdata is not allocated yet ... lets do it now */
3390 if((*prdata = calloc(2,sizeof(char *)))==NULL){
3391 rrd_set_error("malloc imginfo");
3395 if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
3397 rrd_set_error("malloc imginfo");
3400 filename=im.graphfile+strlen(im.graphfile);
3401 while(filename > im.graphfile){
3402 if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
3406 sprintf((*prdata)[0],im.imginfo,filename,im.xgif,im.ygif);
3412 int bad_format(char *fmt) {
3416 while (*ptr != '\0') {
3417 if (*ptr == '%') {ptr++;
3418 if (*ptr == '\0') return 1;
3419 while ((*ptr >= '0' && *ptr <= '9') || *ptr == '.') {
3422 if (*ptr == '\0') return 1;
3425 if (*ptr == '\0') return 1;
3426 if (*ptr == 'e' || *ptr == 'f') {
3428 } else { return 1; }
3430 else if (*ptr == 's' || *ptr == 'S' || *ptr == '%') { ++ptr; }