313c518369aa1c4fc63c98f6a2ecba86695a52f1
1 /****************************************************************************
2 * RRDtool 1.0.33 Copyright Tobias Oetiker, 1997 - 2000
3 ****************************************************************************
4 * rrd__graph.c make creates ne rrds
5 ****************************************************************************/
7 #include "rrd_tool.h"
8 #include <gd.h>
9 #include <gdlucidan10.h>
10 #include <gdlucidab12.h>
11 #include <sys/stat.h>
12 #ifdef WIN32
13 #include <io.h>
14 #include <fcntl.h>
15 #endif
16 #include "rrd_rpncalc.h"
18 #define SmallFont gdLucidaNormal10
19 #define LargeFont gdLucidaBold12
21 /* #define DEBUG */
23 #ifdef DEBUG
24 # define DPRINT(x) (void)(printf x, printf("\n"))
25 #else
26 # define DPRINT(x)
27 #endif
29 #define DEF_NAM_FMT "%29[_A-Za-z0-9]"
31 enum tmt_en {TMT_SECOND=0,TMT_MINUTE,TMT_HOUR,TMT_DAY,
32 TMT_WEEK,TMT_MONTH,TMT_YEAR};
34 enum grc_en {GRC_CANVAS=0,GRC_BACK,GRC_SHADEA,GRC_SHADEB,
35 GRC_GRID,GRC_MGRID,GRC_FONT,GRC_FRAME,GRC_ARROW,__GRC_END__};
38 enum gf_en {GF_PRINT=0,GF_GPRINT,GF_COMMENT,GF_HRULE,GF_VRULE,GF_LINE1,
39 GF_LINE2,GF_LINE3,GF_AREA,GF_STACK,GF_TICK,
40 GF_DEF, GF_CDEF, GF_VDEF};
42 enum if_en {IF_GIF=0,IF_PNG=1};
44 enum vdef_op_en {
45 VDEF_MAXIMUM /* like the MAX in (G)PRINT */
46 ,VDEF_MINIMUM /* like the MIN in (G)PRINT */
47 ,VDEF_AVERAGE /* like the AVERAGE in (G)PRINT */
48 ,VDEF_PERCENT /* Nth percentile */
49 ,VDEF_FIRST /* first non-unknown value and time */
50 ,VDEF_LAST /* last non-unknown value and time */
51 };
52 typedef struct vdef_t {
53 enum vdef_op_en op;
54 double param; /* parameter for function, if applicable */
55 double val; /* resulting value */
56 time_t when; /* timestamp, if applicable */
57 } vdef_t;
59 typedef struct col_trip_t {
60 int red; /* red = -1 is no color */
61 int green;
62 int blue;
63 int i; /* color index assigned in gif image i=-1 is unasigned*/
64 } col_trip_t;
67 typedef struct xlab_t {
68 long minsec; /* minimum sec per pix */
69 enum tmt_en gridtm; /* grid interval in what ?*/
70 long gridst; /* how many whats per grid*/
71 enum tmt_en mgridtm; /* label interval in what ?*/
72 long mgridst; /* how many whats per label*/
73 enum tmt_en labtm; /* label interval in what ?*/
74 long labst; /* how many whats per label*/
75 long precis; /* label precision -> label placement*/
76 char *stst; /* strftime string*/
77 } xlab_t;
79 xlab_t xlab[] = {
80 {0, TMT_SECOND,30, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
81 {2, TMT_MINUTE,1, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
82 {5, TMT_MINUTE,2, TMT_MINUTE,10, TMT_MINUTE,10, 0,"%H:%M"},
83 {10, TMT_MINUTE,5, TMT_MINUTE,20, TMT_MINUTE,20, 0,"%H:%M"},
84 {30, TMT_MINUTE,10, TMT_HOUR,1, TMT_HOUR,1, 0,"%H:%M"},
85 {60, TMT_MINUTE,30, TMT_HOUR,2, TMT_HOUR,2, 0,"%H:%M"},
86 {180, TMT_HOUR,1, TMT_HOUR,6, TMT_HOUR,6, 0,"%H:%M"},
87 /*{300, TMT_HOUR,3, TMT_HOUR,12, TMT_HOUR,12, 12*3600,"%a %p"}, this looks silly*/
88 {600, TMT_HOUR,6, TMT_DAY,1, TMT_DAY,1, 24*3600,"%a"},
89 {1800, TMT_HOUR,12, TMT_DAY,1, TMT_DAY,2, 24*3600,"%a"},
90 {3600, TMT_DAY,1, TMT_WEEK,1, TMT_WEEK,1, 7*24*3600,"Week %W"},
91 {3*3600, TMT_WEEK,1, TMT_MONTH,1, TMT_WEEK,2, 7*24*3600,"Week %W"},
92 {6*3600, TMT_MONTH,1, TMT_MONTH,1, TMT_MONTH,1, 30*24*3600,"%b"},
93 {48*3600, TMT_MONTH,1, TMT_MONTH,3, TMT_MONTH,3, 30*24*3600,"%b"},
94 {10*24*3600, TMT_YEAR,1, TMT_YEAR,1, TMT_YEAR,1, 365*24*3600,"%y"},
95 {-1,TMT_MONTH,0,TMT_MONTH,0,TMT_MONTH,0,0,""}
96 };
98 /* sensible logarithmic y label intervals ...
99 the first element of each row defines the possible starting points on the
100 y axis ... the other specify the */
102 double yloglab[][12]= {{ 1e9, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
103 { 1e3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
104 { 1e1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
105 /* { 1e1, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, */
106 { 1e1, 1, 2.5, 5, 7.5, 0, 0, 0, 0, 0, 0, 0 },
107 { 1e1, 1, 2, 4, 6, 8, 0, 0, 0, 0, 0, 0 },
108 { 1e1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0 },
109 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }};
111 /* sensible y label intervals ...*/
113 typedef struct ylab_t {
114 double grid; /* grid spacing */
115 int lfac[4]; /* associated label spacing*/
116 } ylab_t;
118 ylab_t ylab[]= {
119 {0.1, {1,2, 5,10}},
120 {0.2, {1,5,10,20}},
121 {0.5, {1,2, 4,10}},
122 {1.0, {1,2, 5,10}},
123 {2.0, {1,5,10,20}},
124 {5.0, {1,2, 4,10}},
125 {10.0, {1,2, 5,10}},
126 {20.0, {1,5,10,20}},
127 {50.0, {1,2, 4,10}},
128 {100.0, {1,2, 5,10}},
129 {200.0, {1,5,10,20}},
130 {500.0, {1,2, 4,10}},
131 {0.0, {0,0,0,0}}};
135 col_trip_t graph_col[] = { /* default colors */
136 {255,255,255,-1}, /* canvas */
137 {245,245,245,-1}, /* background */
138 {200,200,200,-1}, /* shade A */
139 {150,150,150,-1}, /* shade B */
140 {140,140,140,-1}, /* grid */
141 {130,30,30,-1}, /* major grid */
142 {0,0,0,-1}, /* font */
143 {0,0,0,-1}, /* frame */
144 {255,0,0,-1} /*arrow*/
145 };
147 /* this structure describes the elements which can make up a graph.
148 because they are quite diverse, not all elements will use all the
149 possible parts of the structure. */
150 #ifdef HAVE_SNPRINTF
151 #define FMT_LEG_LEN 200
152 #else
153 #define FMT_LEG_LEN 2000
154 #endif
156 typedef struct graph_desc_t {
157 enum gf_en gf; /* graphing function */
158 char vname[30]; /* name of the variable */
159 long vidx; /* gdes reference */
160 char rrd[255]; /* name of the rrd_file containing data */
161 char ds_nam[DS_NAM_SIZE]; /* data source name */
162 long ds; /* data source number */
163 enum cf_en cf; /* consolidation function */
164 col_trip_t col; /* graph color */
165 char format[FMT_LEG_LEN+5]; /* format for PRINT AND GPRINT */
166 char legend[FMT_LEG_LEN+5]; /* legend*/
167 gdPoint legloc; /* location of legend */
168 double yrule; /* value for y rule line and for VDEF */
169 time_t xrule; /* time for x rule line and for VDEF */
170 vdef_t vf; /* instruction for VDEF function */
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 */
182 } graph_desc_t;
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
210 values outside */
211 char* imginfo; /* construct an <IMG ... tag and return
212 as first retval */
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 */
228 /* data elements */
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 */
234 } image_desc_t;
236 /* Prototypes */
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 *);
250 long find_var_wrapper(void *arg1, char *key);
251 long lcd(long *);
252 int data_calc( image_desc_t *);
253 int data_proc( image_desc_t *);
254 time_t find_first_time( time_t, enum tmt_en, long);
255 time_t find_next_time( time_t, enum tmt_en, long);
256 void gator( gdImagePtr, int, int);
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 int vdef_parse(struct graph_desc_t *,char *);
272 int vdef_calc(image_desc_t *, int);
273 int vdef_percent_compar(const void *,const void *);
275 /* translate time values into x coordinates */
276 /*#define xtr(x) (int)((double)im->xorigin \
277 + ((double) im->xsize / (double)(im->end - im->start) ) \
278 * ((double)(x) - im->start)+0.5) */
279 /* initialize with xtr(im,0); */
280 int
281 xtr(image_desc_t *im,time_t mytime){
282 static double pixie;
283 if (mytime==0){
284 pixie = (double) im->xsize / (double)(im->end - im->start);
285 return im->xorigin;
286 }
287 return (int)((double)im->xorigin
288 + pixie * ( mytime - im->start ) );
289 }
291 /* translate data values into y coordinates */
293 /* #define ytr(x) (int)((double)im->yorigin \
294 - ((double) im->ysize / (im->maxval - im->minval) ) \
295 * ((double)(x) - im->minval)+0.5) */
296 int
297 ytr(image_desc_t *im, double value){
298 static double pixie;
299 double yval;
300 if (isnan(value)){
301 if(!im->logarithmic)
302 pixie = (double) im->ysize / (im->maxval - im->minval);
303 else
304 pixie = (double) im->ysize / (log10(im->maxval) - log10(im->minval));
305 yval = im->yorigin;
306 } else if(!im->logarithmic) {
307 yval = im->yorigin - pixie * (value - im->minval) + 0.5;
308 } else {
309 if (value < im->minval) {
310 yval = im->yorigin;
311 } else {
312 yval = im->yorigin - pixie * (log10(value) - log10(im->minval)) + 0.5;
313 }
314 }
315 /* make sure we don't return anything too unreasonable. GD lib can
316 get terribly slow when drawing lines outside its scope. This is
317 especially problematic in connection with the rigid option */
318 if (! im->rigid) {
319 return (int)yval;
320 } else if ((int)yval > im->yorigin) {
321 return im->yorigin+2;
322 } else if ((int) yval < im->yorigin - im->ysize){
323 return im->yorigin - im->ysize - 2;
324 } else {
325 return (int)yval;
326 }
327 }
333 /* conversion function for symbolic entry names */
336 #define conv_if(VV,VVV) \
337 if (strcmp(#VV, string) == 0) return VVV ;
339 enum gf_en gf_conv(char *string){
341 conv_if(PRINT,GF_PRINT)
342 conv_if(GPRINT,GF_GPRINT)
343 conv_if(COMMENT,GF_COMMENT)
344 conv_if(HRULE,GF_HRULE)
345 conv_if(VRULE,GF_VRULE)
346 conv_if(LINE1,GF_LINE1)
347 conv_if(LINE2,GF_LINE2)
348 conv_if(LINE3,GF_LINE3)
349 conv_if(AREA,GF_AREA)
350 conv_if(STACK,GF_STACK)
351 conv_if(TICK,GF_TICK)
352 conv_if(DEF,GF_DEF)
353 conv_if(CDEF,GF_CDEF)
354 conv_if(VDEF,GF_VDEF)
356 return (-1);
357 }
359 enum if_en if_conv(char *string){
361 conv_if(GIF,IF_GIF)
362 conv_if(PNG,IF_PNG)
364 return (-1);
365 }
367 enum tmt_en tmt_conv(char *string){
369 conv_if(SECOND,TMT_SECOND)
370 conv_if(MINUTE,TMT_MINUTE)
371 conv_if(HOUR,TMT_HOUR)
372 conv_if(DAY,TMT_DAY)
373 conv_if(WEEK,TMT_WEEK)
374 conv_if(MONTH,TMT_MONTH)
375 conv_if(YEAR,TMT_YEAR)
376 return (-1);
377 }
379 enum grc_en grc_conv(char *string){
381 conv_if(BACK,GRC_BACK)
382 conv_if(CANVAS,GRC_CANVAS)
383 conv_if(SHADEA,GRC_SHADEA)
384 conv_if(SHADEB,GRC_SHADEB)
385 conv_if(GRID,GRC_GRID)
386 conv_if(MGRID,GRC_MGRID)
387 conv_if(FONT,GRC_FONT)
388 conv_if(FRAME,GRC_FRAME)
389 conv_if(ARROW,GRC_ARROW)
391 return -1;
392 }
394 #undef conv_if
398 int
399 im_free(image_desc_t *im)
400 {
401 long i,ii;
402 if (im == NULL) return 0;
403 for(i=0;i<im->gdes_c;i++){
404 if (im->gdes[i].data_first){
405 /* careful here, because a single pointer can occur several times */
406 free (im->gdes[i].data);
407 if (im->gdes[i].ds_namv){
408 for (ii=0;ii<im->gdes[i].ds_cnt;ii++)
409 free(im->gdes[i].ds_namv[ii]);
410 free(im->gdes[i].ds_namv);
411 }
412 }
413 free (im->gdes[i].p_data);
414 free (im->gdes[i].rpnp);
415 }
416 free(im->gdes);
417 return 0;
418 }
420 /* find SI magnitude symbol for the given number*/
421 void
422 auto_scale(
423 image_desc_t *im, /* image description */
424 double *value,
425 char **symb_ptr,
426 double *magfact
427 )
428 {
430 char *symbol[] = {"a", /* 10e-18 Ato */
431 "f", /* 10e-15 Femto */
432 "p", /* 10e-12 Pico */
433 "n", /* 10e-9 Nano */
434 "u", /* 10e-6 Micro */
435 "m", /* 10e-3 Milli */
436 " ", /* Base */
437 "k", /* 10e3 Kilo */
438 "M", /* 10e6 Mega */
439 "G", /* 10e9 Giga */
440 "T", /* 10e12 Terra */
441 "P", /* 10e15 Peta */
442 "E"};/* 10e18 Exa */
444 int symbcenter = 6;
445 int sindex;
447 if (*value == 0.0 || isnan(*value) ) {
448 sindex = 0;
449 *magfact = 1.0;
450 } else {
451 sindex = floor(log(fabs(*value))/log((double)im->base));
452 *magfact = pow((double)im->base, (double)sindex);
453 (*value) /= (*magfact);
454 }
455 if ( sindex <= symbcenter && sindex >= -symbcenter) {
456 (*symb_ptr) = symbol[sindex+symbcenter];
457 }
458 else {
459 (*symb_ptr) = "?";
460 }
461 }
464 /* find SI magnitude symbol for the numbers on the y-axis*/
465 void
466 si_unit(
467 image_desc_t *im /* image description */
468 )
469 {
471 char symbol[] = {'a', /* 10e-18 Ato */
472 'f', /* 10e-15 Femto */
473 'p', /* 10e-12 Pico */
474 'n', /* 10e-9 Nano */
475 'u', /* 10e-6 Micro */
476 'm', /* 10e-3 Milli */
477 ' ', /* Base */
478 'k', /* 10e3 Kilo */
479 'M', /* 10e6 Mega */
480 'G', /* 10e9 Giga */
481 'T', /* 10e12 Terra */
482 'P', /* 10e15 Peta */
483 'E'};/* 10e18 Exa */
485 int symbcenter = 6;
486 double digits;
488 if (im->unitsexponent != 9999) {
489 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
490 digits = floor(im->unitsexponent / 3);
491 } else {
492 digits = floor( log( max( fabs(im->minval),fabs(im->maxval)))/log((double)im->base));
493 }
494 im->magfact = pow((double)im->base , digits);
496 #ifdef DEBUG
497 printf("digits %6.3f im->magfact %6.3f\n",digits,im->magfact);
498 #endif
500 if ( ((digits+symbcenter) < sizeof(symbol)) &&
501 ((digits+symbcenter) >= 0) )
502 im->symbol = symbol[(int)digits+symbcenter];
503 else
504 im->symbol = ' ';
505 }
507 /* move min and max values around to become sensible */
509 void
510 expand_range(image_desc_t *im)
511 {
512 double sensiblevalues[] ={1000.0,900.0,800.0,750.0,700.0,
513 600.0,500.0,400.0,300.0,250.0,
514 200.0,125.0,100.0,90.0,80.0,
515 75.0,70.0,60.0,50.0,40.0,30.0,
516 25.0,20.0,10.0,9.0,8.0,
517 7.0,6.0,5.0,4.0,3.5,3.0,
518 2.5,2.0,1.8,1.5,1.2,1.0,
519 0.8,0.7,0.6,0.5,0.4,0.3,0.2,0.1,0.0,-1};
521 double scaled_min,scaled_max;
522 double adj;
523 int i;
527 #ifdef DEBUG
528 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
529 im->minval,im->maxval,im->magfact);
530 #endif
532 if (isnan(im->ygridstep)){
533 if(im->extra_flags & ALTAUTOSCALE) {
534 /* measure the amplitude of the function. Make sure that
535 graph boundaries are slightly higher then max/min vals
536 so we can see amplitude on the graph */
537 double delt, fact;
539 delt = im->maxval - im->minval;
540 adj = delt * 0.1;
541 fact = 2.0 * pow(10.0,
542 floor(log10(max(fabs(im->minval), fabs(im->maxval)))) - 2);
543 if (delt < fact) {
544 adj = (fact - delt) * 0.55;
545 #ifdef DEBUG
546 printf("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n", im->minval, im->maxval, delt, fact, adj);
547 #endif
548 }
549 im->minval -= adj;
550 im->maxval += adj;
551 }
552 else if(im->extra_flags & ALTAUTOSCALE_MAX) {
553 /* measure the amplitude of the function. Make sure that
554 graph boundaries are slightly higher than max vals
555 so we can see amplitude on the graph */
556 adj = (im->maxval - im->minval) * 0.1;
557 im->maxval += adj;
558 }
559 else {
560 scaled_min = im->minval / im->magfact;
561 scaled_max = im->maxval / im->magfact;
563 for (i=1; sensiblevalues[i] > 0; i++){
564 if (sensiblevalues[i-1]>=scaled_min &&
565 sensiblevalues[i]<=scaled_min)
566 im->minval = sensiblevalues[i]*(im->magfact);
568 if (-sensiblevalues[i-1]<=scaled_min &&
569 -sensiblevalues[i]>=scaled_min)
570 im->minval = -sensiblevalues[i-1]*(im->magfact);
572 if (sensiblevalues[i-1] >= scaled_max &&
573 sensiblevalues[i] <= scaled_max)
574 im->maxval = sensiblevalues[i-1]*(im->magfact);
576 if (-sensiblevalues[i-1]<=scaled_max &&
577 -sensiblevalues[i] >=scaled_max)
578 im->maxval = -sensiblevalues[i]*(im->magfact);
579 }
580 }
581 } else {
582 /* adjust min and max to the grid definition if there is one */
583 im->minval = (double)im->ylabfact * im->ygridstep *
584 floor(im->minval / ((double)im->ylabfact * im->ygridstep));
585 im->maxval = (double)im->ylabfact * im->ygridstep *
586 ceil(im->maxval /( (double)im->ylabfact * im->ygridstep));
587 }
589 #ifdef DEBUG
590 fprintf(stderr,"SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
591 im->minval,im->maxval,im->magfact);
592 #endif
593 }
596 /* reduce data reimplementation by Alex */
598 void
599 reduce_data(
600 enum cf_en cf, /* which consolidation function ?*/
601 unsigned long cur_step, /* step the data currently is in */
602 time_t *start, /* start, end and step as requested ... */
603 time_t *end, /* ... by the application will be ... */
604 unsigned long *step, /* ... adjusted to represent reality */
605 unsigned long *ds_cnt, /* number of data sources in file */
606 rrd_value_t **data) /* two dimensional array containing the data */
607 {
608 int i,reduce_factor = ceil((double)(*step) / (double)cur_step);
609 unsigned long col,dst_row,row_cnt,start_offset,end_offset,skiprows=0;
610 rrd_value_t *srcptr,*dstptr;
612 (*step) = cur_step*reduce_factor; /* set new step size for reduced data */
613 dstptr = *data;
614 srcptr = *data;
616 /* We were given one extra row at the beginning of the interval.
617 ** We also need to return one extra row. The extra interval is
618 ** the one defined by the start time in both cases. It is not
619 ** used when graphing but maybe we can use it while reducing the
620 ** data.
621 */
622 row_cnt = ((*end)-(*start))/cur_step +1;
624 /* alter start and end so that they are multiples of the new steptime.
625 ** End will be shifted towards the future and start will be shifted
626 ** towards the past in order to include the requested interval
627 */
628 end_offset = (*end) % (*step);
629 if (end_offset) end_offset = (*step)-end_offset;
630 start_offset = (*start) % (*step);
631 (*end) = (*end)+end_offset;
632 (*start) = (*start)-start_offset;
634 /* The first destination row is unknown yet it still needs
635 ** to be present in the returned data. Skip it.
636 ** Don't make it NaN or we might overwrite the source.
637 */
638 dstptr += (*ds_cnt);
640 /* Depending on the amount of extra data needed at the
641 ** start of the destination, three things can happen:
642 ** -1- start_offset == 0: skip the extra source row
643 ** -2- start_offset == cur_step: do nothing
644 ** -3- start_offset > cur_step: skip some source rows and
645 ** fill one destination row with NaN
646 */
647 if (start_offset==0) {
648 srcptr+=(*ds_cnt);
649 row_cnt--;
650 } else if (start_offset!=cur_step) {
651 skiprows=((*step)-start_offset)/cur_step+1;
652 srcptr += ((*ds_cnt)*skiprows);
653 row_cnt-=skiprows;
654 for (col=0;col<(*ds_cnt);col++) *dstptr++=DNAN;
655 }
657 /* If we had to alter the endtime, there won't be
658 ** enough data to fill the last row. This means
659 ** we have to skip some rows at the end
660 */
661 if (end_offset) {
662 skiprows = ((*step)-end_offset)/cur_step;
663 row_cnt-=skiprows;
664 }
667 /* Sanity check: row_cnt should be multiple of reduce_factor */
668 /* if this gets triggered, something is REALY WRONG ... we die immediately */
670 if (row_cnt%reduce_factor) {
671 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
672 row_cnt,reduce_factor);
673 printf("BUG in reduce_data()\n");
674 exit(1);
675 }
677 /* Now combine reduce_factor intervals at a time
678 ** into one interval for the destination.
679 */
681 for (dst_row=0;row_cnt>=reduce_factor;dst_row++) {
682 for (col=0;col<(*ds_cnt);col++) {
683 rrd_value_t newval=DNAN;
684 unsigned long validval=0;
686 for (i=0;i<reduce_factor;i++) {
687 if (isnan(srcptr[i*(*ds_cnt)+col])) {
688 continue;
689 }
690 validval++;
691 if (isnan(newval)) newval = srcptr[i*(*ds_cnt)+col];
692 else {
693 switch (cf) {
694 case CF_HWPREDICT:
695 case CF_DEVSEASONAL:
696 case CF_DEVPREDICT:
697 case CF_SEASONAL:
698 case CF_AVERAGE:
699 newval += srcptr[i*(*ds_cnt)+col];
700 break;
701 case CF_MINIMUM:
702 newval = min (newval,srcptr[i*(*ds_cnt)+col]);
703 break;
704 case CF_FAILURES:
705 /* an interval contains a failure if any subintervals contained a failure */
706 case CF_MAXIMUM:
707 newval = max (newval,srcptr[i*(*ds_cnt)+col]);
708 break;
709 case CF_LAST:
710 newval = srcptr[i*(*ds_cnt)+col];
711 break;
712 }
713 }
714 }
715 if (validval == 0){newval = DNAN;} else{
716 switch (cf) {
717 case CF_HWPREDICT:
718 case CF_DEVSEASONAL:
719 case CF_DEVPREDICT:
720 case CF_SEASONAL:
721 case CF_AVERAGE:
722 newval /= validval;
723 break;
724 case CF_MINIMUM:
725 case CF_FAILURES:
726 case CF_MAXIMUM:
727 case CF_LAST:
728 break;
729 }
730 }
731 *dstptr++=newval;
732 }
733 srcptr+=(*ds_cnt)*reduce_factor;
734 row_cnt-=reduce_factor;
735 }
737 /* If we had to alter the endtime, we didn't have enough
738 ** source rows to fill the last row. Fill it with NaN.
739 */
740 if (end_offset!=0) for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
741 }
744 /* get the data required for the graphs from the
745 relevant rrds ... */
747 int
748 data_fetch( image_desc_t *im )
749 {
750 int i,ii;
751 int skip;
752 /* pull the data from the log files ... */
753 for (i=0;i<im->gdes_c;i++){
754 /* only GF_DEF elements fetch data */
755 if (im->gdes[i].gf != GF_DEF)
756 continue;
758 skip=0;
759 /* do we have it already ?*/
760 for (ii=0;ii<i;ii++){
761 if (im->gdes[ii].gf != GF_DEF)
762 continue;
763 if((strcmp(im->gdes[i].rrd,im->gdes[ii].rrd) == 0)
764 && (im->gdes[i].cf == im->gdes[ii].cf)){
765 /* OK the data it is here already ...
766 * we just copy the header portion */
767 im->gdes[i].start = im->gdes[ii].start;
768 im->gdes[i].end = im->gdes[ii].end;
769 im->gdes[i].step = im->gdes[ii].step;
770 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
771 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
772 im->gdes[i].data = im->gdes[ii].data;
773 im->gdes[i].data_first = 0;
774 skip=1;
775 }
776 if (skip)
777 break;
778 }
779 if (! skip) {
780 unsigned long ft_step = im->gdes[i].step ;
782 if((rrd_fetch_fn(im->gdes[i].rrd,
783 im->gdes[i].cf,
784 &im->gdes[i].start,
785 &im->gdes[i].end,
786 &ft_step,
787 &im->gdes[i].ds_cnt,
788 &im->gdes[i].ds_namv,
789 &im->gdes[i].data)) == -1){
790 return -1;
791 }
792 im->gdes[i].data_first = 1;
794 if (ft_step < im->gdes[i].step) {
795 reduce_data(im->gdes[i].cf,
796 ft_step,
797 &im->gdes[i].start,
798 &im->gdes[i].end,
799 &im->gdes[i].step,
800 &im->gdes[i].ds_cnt,
801 &im->gdes[i].data);
802 } else {
803 im->gdes[i].step = ft_step;
804 }
805 }
807 /* lets see if the required data source is realy there */
808 for(ii=0;ii<im->gdes[i].ds_cnt;ii++){
809 if(strcmp(im->gdes[i].ds_namv[ii],im->gdes[i].ds_nam) == 0){
810 im->gdes[i].ds=ii; }
811 }
812 if (im->gdes[i].ds== -1){
813 rrd_set_error("No DS called '%s' in '%s'",
814 im->gdes[i].ds_nam,im->gdes[i].rrd);
815 return -1;
816 }
818 }
819 return 0;
820 }
822 /* evaluate the expressions in the CDEF functions */
824 /*************************************************************
825 * CDEF stuff
826 *************************************************************/
828 long
829 find_var_wrapper(void *arg1, char *key)
830 {
831 return find_var((image_desc_t *) arg1, key);
832 }
834 /* find gdes containing var*/
835 long
836 find_var(image_desc_t *im, char *key){
837 long ii;
838 for(ii=0;ii<im->gdes_c-1;ii++){
839 if((im->gdes[ii].gf == GF_DEF
840 || im->gdes[ii].gf == GF_VDEF
841 || im->gdes[ii].gf == GF_CDEF)
842 && (strcmp(im->gdes[ii].vname,key) == 0)){
843 return ii;
844 }
845 }
846 return -1;
847 }
849 /* find the largest common denominator for all the numbers
850 in the 0 terminated num array */
851 long
852 lcd(long *num){
853 long rest;
854 int i;
855 for (i=0;num[i+1]!=0;i++){
856 do {
857 rest=num[i] % num[i+1];
858 num[i]=num[i+1]; num[i+1]=rest;
859 } while (rest!=0);
860 num[i+1] = num[i];
861 }
862 /* return i==0?num[i]:num[i-1]; */
863 return num[i];
864 }
866 /* run the rpn calculator on all the VDEF and CDEF arguments */
867 int
868 data_calc( image_desc_t *im){
870 int gdi;
871 int dataidx;
872 long *steparray, rpi;
873 int stepcnt;
874 time_t now;
875 rpnstack_t rpnstack;
877 rpnstack_init(&rpnstack);
879 for (gdi=0;gdi<im->gdes_c;gdi++){
880 /* Look for GF_VDEF and GF_CDEF in the same loop,
881 * so CDEFs can use VDEFs and vice versa
882 */
883 switch (im->gdes[gdi].gf) {
884 case GF_VDEF:
885 /* A VDEF has no DS. This also signals other parts
886 * of rrdtool that this is a VDEF value, not a CDEF.
887 */
888 im->gdes[gdi].ds_cnt = 0;
889 if (vdef_calc(im,gdi)) {
890 rrd_set_error("Error processing VDEF '%s'"
891 ,im->gdes[gdi].vname
892 );
893 rpnstack_free(&rpnstack);
894 return -1;
895 }
896 break;
897 case GF_CDEF:
898 im->gdes[gdi].ds_cnt = 1;
899 im->gdes[gdi].ds = 0;
900 im->gdes[gdi].data_first = 1;
901 im->gdes[gdi].start = 0;
902 im->gdes[gdi].end = 0;
903 steparray=NULL;
904 stepcnt = 0;
905 dataidx=-1;
907 /* Find the variables in the expression.
908 * - VDEF variables are substituted by their values
909 * and the opcode is changed into OP_NUMBER.
910 ******************
911 * Note to Jake: I cannot oversee the implications for your
912 * COMPUTE DS stuff. Please check if VDEF and COMPUTE are
913 * compatible (or can be made so).
914 ******************
915 * - CDEF variables are analized for their step size,
916 * the lowest common denominator of all the step
917 * sizes of the data sources involved is calculated
918 * and the resulting number is the step size for the
919 * resulting data source.
920 */
921 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
922 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE){
923 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
924 if (im->gdes[ptr].ds_cnt == 0) {
925 #if 0
926 printf("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
927 im->gdes[gdi].vname,
928 im->gdes[ptr].vname);
929 printf("DEBUG: value from vdef is %f\n",im->gdes[ptr].vf.val);
930 #endif
931 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
932 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
933 } else {
934 if ((steparray = rrd_realloc(steparray, (++stepcnt+1)*sizeof(*steparray)))==NULL){
935 rrd_set_error("realloc steparray");
936 rpnstack_free(&rpnstack);
937 return -1;
938 };
940 steparray[stepcnt-1] = im->gdes[ptr].step;
942 /* adjust start and end of cdef (gdi) so
943 * that it runs from the latest start point
944 * to the earliest endpoint of any of the
945 * rras involved (ptr)
946 */
947 if(im->gdes[gdi].start < im->gdes[ptr].start)
948 im->gdes[gdi].start = im->gdes[ptr].start;
950 if(im->gdes[gdi].end == 0 ||
951 im->gdes[gdi].end > im->gdes[ptr].end)
952 im->gdes[gdi].end = im->gdes[ptr].end;
954 /* store pointer to the first element of
955 * the rra providing data for variable,
956 * further save step size and data source
957 * count of this rra
958 */
959 im->gdes[gdi].rpnp[rpi].data =
960 im->gdes[ptr].data + im->gdes[ptr].ds;
961 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
962 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
964 /* backoff the *.data ptr; this is done so
965 * rpncalc() function doesn't have to treat
966 * the first case differently
967 */
968 im->gdes[gdi].rpnp[rpi].data-=im->gdes[ptr].ds_cnt;
969 } /* if ds_cnt != 0 */
970 } /* if OP_VARIABLE */
971 } /* loop through all rpi */
973 if(steparray == NULL){
974 rrd_set_error("rpn expressions without DEF"
975 " or CDEF variables are not supported");
976 rpnstack_free(&rpnstack);
977 return -1;
978 }
979 steparray[stepcnt]=0;
980 /* Now find the resulting step. All steps in all
981 * used RRAs have to be visited
982 */
983 im->gdes[gdi].step = lcd(steparray);
984 free(steparray);
985 if((im->gdes[gdi].data = malloc((
986 (im->gdes[gdi].end-im->gdes[gdi].start)
987 / im->gdes[gdi].step +1)
988 * sizeof(double)))==NULL){
989 rrd_set_error("malloc im->gdes[gdi].data");
990 rpnstack_free(&rpnstack);
991 return -1;
992 }
994 /* Step through the new cdef results array and
995 * calculate the values
996 */
997 for (now = im->gdes[gdi].start;
998 now<=im->gdes[gdi].end;
999 now += im->gdes[gdi].step)
1000 {
1001 rpnp_t *rpnp = im -> gdes[gdi].rpnp;
1003 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
1004 * in this case we are advancing by timesteps;
1005 * we use the fact that time_t is a synonym for long
1006 */
1007 if (rpn_calc(rpnp,&rpnstack,(long) now,
1008 im->gdes[gdi].data,++dataidx) == -1) {
1009 /* rpn_calc sets the error string */
1010 rpnstack_free(&rpnstack);
1011 return -1;
1012 }
1013 } /* enumerate over time steps within a CDEF */
1014 break;
1015 default:
1016 continue;
1017 }
1018 } /* enumerate over CDEFs */
1019 rpnstack_free(&rpnstack);
1020 return 0;
1021 }
1023 /* massage data so, that we get one value for each x coordinate in the graph */
1024 int
1025 data_proc( image_desc_t *im ){
1026 long i,ii;
1027 double pixstep = (double)(im->end-im->start)
1028 /(double)im->xsize; /* how much time
1029 passes in one pixel */
1030 double paintval;
1031 double minval=DNAN,maxval=DNAN;
1033 unsigned long gr_time;
1035 /* memory for the processed data */
1036 for(i=0;i<im->gdes_c;i++){
1037 if((im->gdes[i].gf==GF_LINE1) ||
1038 (im->gdes[i].gf==GF_LINE2) ||
1039 (im->gdes[i].gf==GF_LINE3) ||
1040 (im->gdes[i].gf==GF_AREA) ||
1041 (im->gdes[i].gf==GF_TICK) ||
1042 (im->gdes[i].gf==GF_STACK)){
1043 if((im->gdes[i].p_data = malloc((im->xsize +1)
1044 * sizeof(rrd_value_t)))==NULL){
1045 rrd_set_error("malloc data_proc");
1046 return -1;
1047 }
1048 }
1049 }
1051 for(i=0;i<im->xsize;i++){
1052 long vidx;
1053 gr_time = im->start+pixstep*i; /* time of the
1054 current step */
1055 paintval=0.0;
1057 for(ii=0;ii<im->gdes_c;ii++){
1058 double value;
1059 switch(im->gdes[ii].gf){
1060 case GF_LINE1:
1061 case GF_LINE2:
1062 case GF_LINE3:
1063 case GF_AREA:
1064 case GF_TICK:
1065 paintval = 0.0;
1066 case GF_STACK:
1067 vidx = im->gdes[ii].vidx;
1069 value =
1070 im->gdes[vidx].data[
1071 ((unsigned long)floor((double)
1072 (gr_time - im->gdes[vidx].start )
1073 / im->gdes[vidx].step)+1)
1075 /* added one because data was not being aligned properly
1076 this fixes it. We may also be having a problem in fetch ... */
1078 *im->gdes[vidx].ds_cnt
1079 +im->gdes[vidx].ds];
1081 if (! isnan(value)) {
1082 paintval += value;
1083 im->gdes[ii].p_data[i] = paintval;
1084 /* GF_TICK: the data values are not relevant for min and max */
1085 if (finite(paintval) && im->gdes[ii].gf != GF_TICK ){
1086 if (isnan(minval) || paintval < minval)
1087 minval = paintval;
1088 if (isnan(maxval) || paintval > maxval)
1089 maxval = paintval;
1090 }
1091 } else {
1092 im->gdes[ii].p_data[i] = DNAN;
1093 }
1094 break;
1095 case GF_PRINT:
1096 case GF_GPRINT:
1097 case GF_COMMENT:
1098 case GF_HRULE:
1099 case GF_VRULE:
1100 case GF_DEF:
1101 case GF_CDEF:
1102 case GF_VDEF:
1103 break;
1104 }
1105 }
1106 }
1108 /* if min or max have not been asigned a value this is because
1109 there was no data in the graph ... this is not good ...
1110 lets set these to dummy values then ... */
1112 if (isnan(minval)) minval = 0.0;
1113 if (isnan(maxval)) maxval = 1.0;
1115 /* adjust min and max values */
1116 if (isnan(im->minval)
1117 || ((!im->logarithmic && !im->rigid) /* don't adjust low-end with log scale */
1118 && im->minval > minval))
1119 im->minval = minval;
1120 if (isnan(im->maxval)
1121 || (!im->rigid
1122 && im->maxval < maxval)){
1123 if (im->logarithmic)
1124 im->maxval = maxval * 1.1;
1125 else
1126 im->maxval = maxval;
1127 }
1128 /* make sure min and max are not equal */
1129 if (im->minval == im->maxval) {
1130 im->maxval *= 1.01;
1131 if (! im->logarithmic) {
1132 im->minval *= 0.99;
1133 }
1135 /* make sure min and max are not both zero */
1136 if (im->maxval == 0.0) {
1137 im->maxval = 1.0;
1138 }
1140 }
1141 return 0;
1142 }
1146 /* identify the point where the first gridline, label ... gets placed */
1148 time_t
1149 find_first_time(
1150 time_t start, /* what is the initial time */
1151 enum tmt_en baseint, /* what is the basic interval */
1152 long basestep /* how many if these do we jump a time */
1153 )
1154 {
1155 struct tm tm;
1156 tm = *localtime(&start);
1157 switch(baseint){
1158 case TMT_SECOND:
1159 tm.tm_sec -= tm.tm_sec % basestep; break;
1160 case TMT_MINUTE:
1161 tm.tm_sec=0;
1162 tm.tm_min -= tm.tm_min % basestep;
1163 break;
1164 case TMT_HOUR:
1165 tm.tm_sec=0;
1166 tm.tm_min = 0;
1167 tm.tm_hour -= tm.tm_hour % basestep; break;
1168 case TMT_DAY:
1169 /* we do NOT look at the basestep for this ... */
1170 tm.tm_sec=0;
1171 tm.tm_min = 0;
1172 tm.tm_hour = 0; break;
1173 case TMT_WEEK:
1174 /* we do NOT look at the basestep for this ... */
1175 tm.tm_sec=0;
1176 tm.tm_min = 0;
1177 tm.tm_hour = 0;
1178 tm.tm_mday -= tm.tm_wday -1; /* -1 because we want the monday */
1179 if (tm.tm_wday==0) tm.tm_mday -= 7; /* we want the *previous* monday */
1180 break;
1181 case TMT_MONTH:
1182 tm.tm_sec=0;
1183 tm.tm_min = 0;
1184 tm.tm_hour = 0;
1185 tm.tm_mday = 1;
1186 tm.tm_mon -= tm.tm_mon % basestep; break;
1188 case TMT_YEAR:
1189 tm.tm_sec=0;
1190 tm.tm_min = 0;
1191 tm.tm_hour = 0;
1192 tm.tm_mday = 1;
1193 tm.tm_mon = 0;
1194 tm.tm_year -= (tm.tm_year+1900) % basestep;
1196 }
1197 return mktime(&tm);
1198 }
1199 /* identify the point where the next gridline, label ... gets placed */
1200 time_t
1201 find_next_time(
1202 time_t current, /* what is the initial time */
1203 enum tmt_en baseint, /* what is the basic interval */
1204 long basestep /* how many if these do we jump a time */
1205 )
1206 {
1207 struct tm tm;
1208 time_t madetime;
1209 tm = *localtime(¤t);
1210 do {
1211 switch(baseint){
1212 case TMT_SECOND:
1213 tm.tm_sec += basestep; break;
1214 case TMT_MINUTE:
1215 tm.tm_min += basestep; break;
1216 case TMT_HOUR:
1217 tm.tm_hour += basestep; break;
1218 case TMT_DAY:
1219 tm.tm_mday += basestep; break;
1220 case TMT_WEEK:
1221 tm.tm_mday += 7*basestep; break;
1222 case TMT_MONTH:
1223 tm.tm_mon += basestep; break;
1224 case TMT_YEAR:
1225 tm.tm_year += basestep;
1226 }
1227 madetime = mktime(&tm);
1228 } while (madetime == -1); /* this is necessary to skip impssible times
1229 like the daylight saving time skips */
1230 return madetime;
1232 }
1234 void gator( gdImagePtr gif, int x, int y){
1236 /* this function puts the name of the author and the tool into the
1237 graph. Remove if you must, but please note, that it is here,
1238 because I would like people who look at rrdtool generated graphs to
1239 see what was used to do it. No obviously you can also add a credit
1240 line to your webpage or printed document, this is fine with me. But
1241 as I have no control over this, I added the little tag in here.
1242 */
1244 /* the fact that the text of what gets put into the graph is not
1245 visible in the function, has lead some to think this is for
1246 obfuscation reasons. While this is a nice side effect (I addmit),
1247 it is not the prime reason. The prime reason is, that the font
1248 used, is so small, that I had to hand edit the characters to ensure
1249 readability. I could thus not use the normal gd functions to write,
1250 but had to embed a slightly compressed bitmap version into the code.
1251 */
1253 int li[]={0,0,1, 0,4,5, 0,8,9, 0,12,14, 0,17,17, 0,21,21,
1254 0,24,24, 0,34,34, 0,40,42, 0,45,45, 0,48,49, 0,52,54,
1255 0,61,61, 0,64,66, 0,68,70, 0,72,74, 0,76,76, 0,78,78,
1256 0,80,82, 0,84,85,
1257 1,0,0, 1,2,2, 1,4,4, 1,6,6, 1,8,8, 1,10,10,
1258 1,13,13, 1,16,16, 1,18,18, 1,20,20, 1,22,22, 1,24,24,
1259 1,34,34, 1,41,41, 1,44,44, 1,46,46, 1,48,48, 1,50,50,
1260 1,53,53, 1,60,60, 1,62,62, 1,64,64, 1,69,69, 1,73,73,
1261 1,76,76, 1,78,78, 1,80,80, 1,84,84, 1,86,86,
1262 2,0,1, 2,4,5, 2,8,8, 2,10,10, 2,13,13, 2,16,16,
1263 2,18,18, 2,20,20, 2,22,22, 2,24,24, 2,33,33, 2,41,41,
1264 2,44,44, 2,46,46, 2,48,49, 2,53,53, 2,60,60, 2,62,62,
1265 2,64,65, 2,69,69, 2,73,73, 2,76,77, 2,80,81, 2,84,85,
1266 3,0,0, 3,2,2, 3,4,4, 3,6,6, 3,8,8, 3,10,10,
1267 3,13,13, 3,16,16, 3,18,18, 3,20,20, 3,22,22, 3,24,24,
1268 3,32,32, 3,41,41, 3,44,44, 3,46,46, 3,48,48, 3,50,50,
1269 3,53,53, 3,60,60, 3,62,62, 3,64,64, 3,69,69, 3,73,73,
1270 3,76,76, 3,78,78, 3,80,80, 3,84,84, 3,86,86,
1271 4,0,0, 4,2,2, 4,4,4, 4,6,6, 4,8,9, 4,13,13,
1272 4,17,17, 4,21,21, 4,24,26, 4,32,32, 4,41,41, 4,45,45,
1273 4,48,49, 4,52,54, 4,61,61, 4,64,66, 4,69,69, 4,72,74,
1274 4,76,76, 4,78,78, 4,80,82, 4,84,84};
1275 int i,ii;
1276 for(i=0; i<DIM(li); i=i+3)
1277 for(ii=y+li[i+1]; ii<=y+li[i+2];ii++)
1278 gdImageSetPixel(gif,x-li[i],ii,graph_col[GRC_GRID].i);
1279 }
1282 /* calculate values required for PRINT and GPRINT functions */
1284 int
1285 print_calc(image_desc_t *im, char ***prdata)
1286 {
1287 long i,ii,validsteps;
1288 double printval;
1289 int graphelement = 0;
1290 long vidx;
1291 int max_ii;
1292 double magfact = -1;
1293 char *si_symb = "";
1294 char *percent_s;
1295 int prlines = 1;
1296 if (im->imginfo) prlines++;
1297 for(i=0;i<im->gdes_c;i++){
1298 switch(im->gdes[i].gf){
1299 case GF_PRINT:
1300 prlines++;
1301 if(((*prdata) = rrd_realloc((*prdata),prlines*sizeof(char *)))==NULL){
1302 rrd_set_error("realloc prdata");
1303 return 0;
1304 }
1305 case GF_GPRINT:
1306 /* PRINT and GPRINT can now print VDEF generated values.
1307 * There's no need to do any calculations on them as these
1308 * calculations were already made.
1309 */
1310 vidx = im->gdes[i].vidx;
1311 if (im->gdes[vidx].gf==GF_VDEF) { /* simply use vals */
1312 printval = im->gdes[vidx].yrule;
1313 } else { /* need to calculate max,min,avg etcetera */
1314 max_ii =((im->gdes[vidx].end
1315 - im->gdes[vidx].start)
1316 / im->gdes[vidx].step
1317 * im->gdes[vidx].ds_cnt);
1318 printval = DNAN;
1319 validsteps = 0;
1320 for(ii=im->gdes[vidx].ds+im->gdes[vidx].ds_cnt;
1321 ii < max_ii+im->gdes[vidx].ds_cnt;
1322 ii+=im->gdes[vidx].ds_cnt){
1323 if (! finite(im->gdes[vidx].data[ii]))
1324 continue;
1325 if (isnan(printval)){
1326 printval = im->gdes[vidx].data[ii];
1327 validsteps++;
1328 continue;
1329 }
1331 switch (im->gdes[i].cf){
1332 case CF_HWPREDICT:
1333 case CF_DEVPREDICT:
1334 case CF_DEVSEASONAL:
1335 case CF_SEASONAL:
1336 case CF_AVERAGE:
1337 validsteps++;
1338 printval += im->gdes[vidx].data[ii];
1339 break;
1340 case CF_MINIMUM:
1341 printval = min( printval, im->gdes[vidx].data[ii]);
1342 break;
1343 case CF_FAILURES:
1344 case CF_MAXIMUM:
1345 printval = max( printval, im->gdes[vidx].data[ii]);
1346 break;
1347 case CF_LAST:
1348 printval = im->gdes[vidx].data[ii];
1349 }
1350 }
1351 if (im->gdes[i].cf==CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1352 if (validsteps > 1) {
1353 printval = (printval / validsteps);
1354 }
1355 }
1356 } /* prepare printval */
1359 if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) {
1360 /* Magfact is set to -1 upon entry to print_calc. If it
1361 * is still less than 0, then we need to run auto_scale.
1362 * Otherwise, put the value into the correct units. If
1363 * the value is 0, then do not set the symbol or magnification
1364 * so next the calculation will be performed again. */
1365 if (magfact < 0.0) {
1366 auto_scale(im,&printval,&si_symb,&magfact);
1367 if (printval == 0.0)
1368 magfact = -1.0;
1369 } else {
1370 printval /= magfact;
1371 }
1372 *(++percent_s) = 's';
1373 } else if (strstr(im->gdes[i].format,"%s") != NULL) {
1374 auto_scale(im,&printval,&si_symb,&magfact);
1375 }
1377 if (im->gdes[i].gf == GF_PRINT){
1378 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1379 if (bad_format(im->gdes[i].format)) {
1380 rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1381 return -1;
1382 }
1383 #ifdef HAVE_SNPRINTF
1384 snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
1385 #else
1386 sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
1387 #endif
1388 (*prdata)[prlines-1] = NULL;
1389 } else {
1390 /* GF_GPRINT */
1392 if (bad_format(im->gdes[i].format)) {
1393 rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1394 return -1;
1395 }
1396 #ifdef HAVE_SNPRINTF
1397 snprintf(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
1398 #else
1399 sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
1400 #endif
1401 graphelement = 1;
1402 }
1403 break;
1404 case GF_COMMENT:
1405 case GF_LINE1:
1406 case GF_LINE2:
1407 case GF_LINE3:
1408 case GF_AREA:
1409 case GF_TICK:
1410 case GF_STACK:
1411 case GF_HRULE:
1412 case GF_VRULE:
1413 graphelement = 1;
1414 break;
1415 case GF_DEF:
1416 case GF_CDEF:
1417 case GF_VDEF:
1418 break;
1419 }
1420 }
1421 return graphelement;
1422 }
1425 /* place legends with color spots */
1426 int
1427 leg_place(image_desc_t *im)
1428 {
1429 /* graph labels */
1430 int interleg = SmallFont->w*2;
1431 int box = SmallFont->h*1.2;
1432 int border = SmallFont->w*2;
1433 int fill=0, fill_last;
1434 int leg_c = 0;
1435 int leg_x = border, leg_y = im->ygif;
1436 int leg_cc;
1437 int glue = 0;
1438 int i,ii, mark = 0;
1439 char prt_fctn; /*special printfunctions */
1440 int *legspace;
1442 if( !(im->extra_flags & NOLEGEND) ) {
1443 if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1444 rrd_set_error("malloc for legspace");
1445 return -1;
1446 }
1448 for(i=0;i<im->gdes_c;i++){
1449 fill_last = fill;
1451 leg_cc = strlen(im->gdes[i].legend);
1453 /* is there a controle code ant the end of the legend string ? */
1454 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\') {
1455 prt_fctn = im->gdes[i].legend[leg_cc-1];
1456 leg_cc -= 2;
1457 im->gdes[i].legend[leg_cc] = '\0';
1458 } else {
1459 prt_fctn = '\0';
1460 }
1461 /* remove exess space */
1462 while (prt_fctn=='g' &&
1463 leg_cc > 0 &&
1464 im->gdes[i].legend[leg_cc-1]==' '){
1465 leg_cc--;
1466 im->gdes[i].legend[leg_cc]='\0';
1467 }
1468 if (leg_cc != 0 ){
1469 legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1471 if (fill > 0){
1472 /* no interleg space if string ends in \g */
1473 fill += legspace[i];
1474 }
1475 if (im->gdes[i].gf != GF_GPRINT &&
1476 im->gdes[i].gf != GF_COMMENT) {
1477 fill += box;
1478 }
1479 fill += leg_cc * SmallFont->w;
1480 leg_c++;
1481 } else {
1482 legspace[i]=0;
1483 }
1484 /* who said there was a special tag ... ?*/
1485 if (prt_fctn=='g') {
1486 prt_fctn = '\0';
1487 }
1488 if (prt_fctn == '\0') {
1489 if (i == im->gdes_c -1 ) prt_fctn ='l';
1491 /* is it time to place the legends ? */
1492 if (fill > im->xgif - 2*border){
1493 if (leg_c > 1) {
1494 /* go back one */
1495 i--;
1496 fill = fill_last;
1497 leg_c--;
1498 prt_fctn = 'j';
1499 } else {
1500 prt_fctn = 'l';
1501 }
1503 }
1504 }
1507 if (prt_fctn != '\0'){
1508 leg_x = border;
1509 if (leg_c >= 2 && prt_fctn == 'j') {
1510 glue = (im->xgif - fill - 2* border) / (leg_c-1);
1511 /* if (glue > 2 * SmallFont->w) glue = 0; */
1512 } else {
1513 glue = 0;
1514 }
1515 if (prt_fctn =='c') leg_x = (im->xgif - fill) / 2.0;
1516 if (prt_fctn =='r') leg_x = im->xgif - fill - border;
1518 for(ii=mark;ii<=i;ii++){
1519 if(im->gdes[ii].legend[0]=='\0')
1520 continue;
1521 im->gdes[ii].legloc.x = leg_x;
1522 im->gdes[ii].legloc.y = leg_y;
1523 leg_x = leg_x
1524 + strlen(im->gdes[ii].legend)*SmallFont->w
1525 + legspace[ii]
1526 + glue;
1527 if (im->gdes[ii].gf != GF_GPRINT &&
1528 im->gdes[ii].gf != GF_COMMENT)
1529 leg_x += box;
1530 }
1531 leg_y = leg_y + SmallFont->h*1.2;
1532 if (prt_fctn == 's') leg_y -= SmallFont->h *0.5;
1533 fill = 0;
1534 leg_c = 0;
1535 mark = ii;
1536 }
1537 }
1538 im->ygif = leg_y+6;
1539 free(legspace);
1540 }
1541 return 0;
1542 }
1544 /* create a grid on the graph. it determines what to do
1545 from the values of xsize, start and end */
1547 /* the xaxis labels are determined from the number of seconds per pixel
1548 in the requested graph */
1552 int
1553 horizontal_grid(gdImagePtr gif, image_desc_t *im)
1554 {
1555 double range;
1556 double scaledrange;
1557 int pixel,i;
1558 int sgrid,egrid;
1559 double gridstep;
1560 double scaledstep;
1561 char graph_label[100];
1562 gdPoint polyPoints[4];
1563 int labfact,gridind;
1564 int styleMinor[2],styleMajor[2];
1565 int decimals, fractionals;
1566 char labfmt[64];
1568 labfact=2;
1569 gridind=-1;
1570 range = im->maxval - im->minval;
1571 scaledrange = range / im->magfact;
1573 /* does the scale of this graph make it impossible to put lines
1574 on it? If so, give up. */
1575 if (isnan(scaledrange)) {
1576 return 0;
1577 }
1579 styleMinor[0] = graph_col[GRC_GRID].i;
1580 styleMinor[1] = gdTransparent;
1582 styleMajor[0] = graph_col[GRC_MGRID].i;
1583 styleMajor[1] = gdTransparent;
1585 /* find grid spaceing */
1586 pixel=1;
1587 if(isnan(im->ygridstep)){
1588 if(im->extra_flags & ALTYGRID) {
1589 /* find the value with max number of digits. Get number of digits */
1590 decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))));
1591 if(decimals <= 0) /* everything is small. make place for zero */
1592 decimals = 1;
1594 fractionals = floor(log10(range));
1595 if(fractionals < 0) /* small amplitude. */
1596 sprintf(labfmt, "%%%d.%df", decimals - fractionals + 1, -fractionals + 1);
1597 else
1598 sprintf(labfmt, "%%%d.1f", decimals + 1);
1599 gridstep = pow((double)10, (double)fractionals);
1600 if(gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1601 gridstep = 0.1;
1602 /* should have at least 5 lines but no more then 15 */
1603 if(range/gridstep < 5)
1604 gridstep /= 10;
1605 if(range/gridstep > 15)
1606 gridstep *= 10;
1607 if(range/gridstep > 5) {
1608 labfact = 1;
1609 if(range/gridstep > 8)
1610 labfact = 2;
1611 }
1612 else {
1613 gridstep /= 5;
1614 labfact = 5;
1615 }
1616 }
1617 else {
1618 for(i=0;ylab[i].grid > 0;i++){
1619 pixel = im->ysize / (scaledrange / ylab[i].grid);
1620 if (gridind == -1 && pixel > 5) {
1621 gridind = i;
1622 break;
1623 }
1624 }
1626 for(i=0; i<4;i++) {
1627 if (pixel * ylab[gridind].lfac[i] >= 2 * SmallFont->h) {
1628 labfact = ylab[gridind].lfac[i];
1629 break;
1630 }
1631 }
1633 gridstep = ylab[gridind].grid * im->magfact;
1634 }
1635 } else {
1636 gridstep = im->ygridstep;
1637 labfact = im->ylabfact;
1638 }
1640 polyPoints[0].x=im->xorigin;
1641 polyPoints[1].x=im->xorigin+im->xsize;
1642 sgrid = (int)( im->minval / gridstep - 1);
1643 egrid = (int)( im->maxval / gridstep + 1);
1644 scaledstep = gridstep/im->magfact;
1645 for (i = sgrid; i <= egrid; i++){
1646 polyPoints[0].y=ytr(im,gridstep*i);
1647 if ( polyPoints[0].y >= im->yorigin-im->ysize
1648 && polyPoints[0].y <= im->yorigin) {
1649 if(i % labfact == 0){
1650 if (i==0 || im->symbol == ' ') {
1651 if(scaledstep < 1){
1652 if(im->extra_flags & ALTYGRID) {
1653 sprintf(graph_label,labfmt,scaledstep*i);
1654 }
1655 else {
1656 sprintf(graph_label,"%4.1f",scaledstep*i);
1657 }
1658 } else {
1659 sprintf(graph_label,"%4.0f",scaledstep*i);
1660 }
1661 }else {
1662 if(scaledstep < 1){
1663 sprintf(graph_label,"%4.1f %c",scaledstep*i, im->symbol);
1664 } else {
1665 sprintf(graph_label,"%4.0f %c",scaledstep*i, im->symbol);
1666 }
1667 }
1669 gdImageString(gif, SmallFont,
1670 (polyPoints[0].x - (strlen(graph_label) *
1671 SmallFont->w)-7),
1672 polyPoints[0].y - SmallFont->h/2+1,
1673 (unsigned char *)graph_label, graph_col[GRC_FONT].i);
1675 gdImageSetStyle(gif, styleMajor, 2);
1677 gdImageLine(gif, polyPoints[0].x-2,polyPoints[0].y,
1678 polyPoints[0].x+2,polyPoints[0].y,graph_col[GRC_MGRID].i);
1679 gdImageLine(gif, polyPoints[1].x-2,polyPoints[0].y,
1680 polyPoints[1].x+2,polyPoints[0].y,graph_col[GRC_MGRID].i);
1681 } else {
1682 gdImageSetStyle(gif, styleMinor, 2);
1683 gdImageLine(gif, polyPoints[0].x-1,polyPoints[0].y,
1684 polyPoints[0].x+1,polyPoints[0].y,graph_col[GRC_GRID].i);
1685 gdImageLine(gif, polyPoints[1].x-1,polyPoints[0].y,
1686 polyPoints[1].x+1,polyPoints[0].y,graph_col[GRC_GRID].i);
1687 }
1688 gdImageLine(gif, polyPoints[0].x,polyPoints[0].y,
1689 polyPoints[1].x,polyPoints[0].y,gdStyled);
1690 }
1691 }
1692 /* if(im->minval * im->maxval < 0){
1693 polyPoints[0].y=ytr(0);
1694 gdImageLine(gif, polyPoints[0].x,polyPoints[0].y,
1695 polyPoints[1].x,polyPoints[0].y,graph_col[GRC_MGRID].i);
1696 } */
1698 return 1;
1699 }
1701 /* logaritmic horizontal grid */
1702 int
1703 horizontal_log_grid(gdImagePtr gif, image_desc_t *im)
1704 {
1705 double pixpex;
1706 int ii,i;
1707 int minoridx=0, majoridx=0;
1708 char graph_label[100];
1709 gdPoint polyPoints[4];
1710 int styleMinor[2],styleMajor[2];
1711 double value, pixperstep, minstep;
1713 /* find grid spaceing */
1714 pixpex= (double)im->ysize / (log10(im->maxval) - log10(im->minval));
1716 if (isnan(pixpex)) {
1717 return 0;
1718 }
1720 for(i=0;yloglab[i][0] > 0;i++){
1721 minstep = log10(yloglab[i][0]);
1722 for(ii=1;yloglab[i][ii+1] > 0;ii++){
1723 if(yloglab[i][ii+2]==0){
1724 minstep = log10(yloglab[i][ii+1])-log10(yloglab[i][ii]);
1725 break;
1726 }
1727 }
1728 pixperstep = pixpex * minstep;
1729 if(pixperstep > 5){minoridx = i;}
1730 if(pixperstep > 2 * SmallFont->h){majoridx = i;}
1731 }
1733 styleMinor[0] = graph_col[GRC_GRID].i;
1734 styleMinor[1] = gdTransparent;
1736 styleMajor[0] = graph_col[GRC_MGRID].i;
1737 styleMajor[1] = gdTransparent;
1739 polyPoints[0].x=im->xorigin;
1740 polyPoints[1].x=im->xorigin+im->xsize;
1741 /* paint minor grid */
1742 for (value = pow((double)10, log10(im->minval)
1743 - fmod(log10(im->minval),log10(yloglab[minoridx][0])));
1744 value <= im->maxval;
1745 value *= yloglab[minoridx][0]){
1746 if (value < im->minval) continue;
1747 i=0;
1748 while(yloglab[minoridx][++i] > 0){
1749 polyPoints[0].y = ytr(im,value * yloglab[minoridx][i]);
1750 if (polyPoints[0].y <= im->yorigin - im->ysize) break;
1751 gdImageSetStyle(gif, styleMinor, 2);
1752 gdImageLine(gif, polyPoints[0].x-1,polyPoints[0].y,
1753 polyPoints[0].x+1,polyPoints[0].y,graph_col[GRC_GRID].i);
1754 gdImageLine(gif, polyPoints[1].x-1,polyPoints[0].y,
1755 polyPoints[1].x+1,polyPoints[0].y,graph_col[GRC_GRID].i);
1757 gdImageLine(gif, polyPoints[0].x,polyPoints[0].y,
1758 polyPoints[1].x,polyPoints[0].y,gdStyled);
1759 }
1760 }
1762 /* paint major grid and labels*/
1763 for (value = pow((double)10, log10(im->minval)
1764 - fmod(log10(im->minval),log10(yloglab[majoridx][0])));
1765 value <= im->maxval;
1766 value *= yloglab[majoridx][0]){
1767 if (value < im->minval) continue;
1768 i=0;
1769 while(yloglab[majoridx][++i] > 0){
1770 polyPoints[0].y = ytr(im,value * yloglab[majoridx][i]);
1771 if (polyPoints[0].y <= im->yorigin - im->ysize) break;
1772 gdImageSetStyle(gif, styleMajor, 2);
1773 gdImageLine(gif, polyPoints[0].x-2,polyPoints[0].y,
1774 polyPoints[0].x+2,polyPoints[0].y,graph_col[GRC_MGRID].i);
1775 gdImageLine(gif, polyPoints[1].x-2,polyPoints[0].y,
1776 polyPoints[1].x+2,polyPoints[0].y,graph_col[GRC_MGRID].i);
1778 gdImageLine(gif, polyPoints[0].x,polyPoints[0].y,
1779 polyPoints[1].x,polyPoints[0].y,gdStyled);
1780 sprintf(graph_label,"%3.0e",value * yloglab[majoridx][i]);
1781 gdImageString(gif, SmallFont,
1782 (polyPoints[0].x - (strlen(graph_label) *
1783 SmallFont->w)-7),
1784 polyPoints[0].y - SmallFont->h/2+1,
1785 (unsigned char *)graph_label, graph_col[GRC_FONT].i);
1786 }
1787 }
1788 return 1;
1789 }
1792 void
1793 vertical_grid(
1794 gdImagePtr gif,
1795 image_desc_t *im )
1796 {
1797 int xlab_sel; /* which sort of label and grid ? */
1798 time_t ti, tilab;
1799 long factor;
1800 char graph_label[100];
1801 gdPoint polyPoints[4]; /* points for filled graph and more*/
1803 /* style for grid lines */
1804 int styleDotted[4];
1807 /* the type of time grid is determined by finding
1808 the number of seconds per pixel in the graph */
1811 if(im->xlab_user.minsec == -1){
1812 factor=(im->end - im->start)/im->xsize;
1813 xlab_sel=0;
1814 while ( xlab[xlab_sel+1].minsec != -1
1815 && xlab[xlab_sel+1].minsec <= factor){ xlab_sel++; }
1816 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
1817 im->xlab_user.gridst = xlab[xlab_sel].gridst;
1818 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
1819 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
1820 im->xlab_user.labtm = xlab[xlab_sel].labtm;
1821 im->xlab_user.labst = xlab[xlab_sel].labst;
1822 im->xlab_user.precis = xlab[xlab_sel].precis;
1823 im->xlab_user.stst = xlab[xlab_sel].stst;
1824 }
1826 /* y coords are the same for every line ... */
1827 polyPoints[0].y = im->yorigin;
1828 polyPoints[1].y = im->yorigin-im->ysize;
1830 /* paint the minor grid */
1831 for(ti = find_first_time(im->start,
1832 im->xlab_user.gridtm,
1833 im->xlab_user.gridst);
1834 ti < im->end;
1835 ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
1836 ){
1837 /* are we inside the graph ? */
1838 if (ti < im->start || ti > im->end) continue;
1839 polyPoints[0].x = xtr(im,ti);
1840 styleDotted[0] = graph_col[GRC_GRID].i;
1841 styleDotted[1] = gdTransparent;
1843 gdImageSetStyle(gif, styleDotted, 2);
1845 gdImageLine(gif, polyPoints[0].x,polyPoints[0].y,
1846 polyPoints[0].x,polyPoints[1].y,gdStyled);
1847 gdImageLine(gif, polyPoints[0].x,polyPoints[0].y-1,
1848 polyPoints[0].x,polyPoints[0].y+1,graph_col[GRC_GRID].i);
1849 gdImageLine(gif, polyPoints[0].x,polyPoints[1].y-1,
1850 polyPoints[0].x,polyPoints[1].y+1,graph_col[GRC_GRID].i);
1851 }
1853 /* paint the major grid */
1854 for(ti = find_first_time(im->start,
1855 im->xlab_user.mgridtm,
1856 im->xlab_user.mgridst);
1857 ti < im->end;
1858 ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
1859 ){
1860 /* are we inside the graph ? */
1861 if (ti < im->start || ti > im->end) continue;
1862 polyPoints[0].x = xtr(im,ti);
1863 styleDotted[0] = graph_col[GRC_MGRID].i;
1864 styleDotted[1] = gdTransparent;
1865 gdImageSetStyle(gif, styleDotted, 2);
1867 gdImageLine(gif, polyPoints[0].x,polyPoints[0].y,
1868 polyPoints[0].x,polyPoints[1].y,gdStyled);
1869 gdImageLine(gif, polyPoints[0].x,polyPoints[0].y-2,
1870 polyPoints[0].x,polyPoints[0].y+2,graph_col[GRC_MGRID].i);
1871 gdImageLine(gif, polyPoints[0].x,polyPoints[1].y-2,
1872 polyPoints[0].x,polyPoints[1].y+2,graph_col[GRC_MGRID].i);
1873 }
1874 /* paint the labels below the graph */
1875 for(ti = find_first_time(im->start,
1876 im->xlab_user.labtm,
1877 im->xlab_user.labst);
1878 ti <= im->end;
1879 ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
1880 ){
1881 int gr_pos,width;
1882 tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
1884 #if HAVE_STRFTIME
1885 strftime(graph_label,99,im->xlab_user.stst,localtime(&tilab));
1886 #else
1887 # error "your libc has no strftime I guess we'll abort the exercise here."
1888 #endif
1889 width=strlen(graph_label) * SmallFont->w;
1890 gr_pos=xtr(im,tilab) - width/2;
1891 if (gr_pos >= im->xorigin
1892 && gr_pos + width <= im->xorigin+im->xsize)
1893 gdImageString(gif, SmallFont,
1894 gr_pos, polyPoints[0].y+4,
1895 (unsigned char *)graph_label, graph_col[GRC_FONT].i);
1896 }
1898 }
1901 void
1902 axis_paint(
1903 image_desc_t *im,
1904 gdImagePtr gif
1905 )
1906 {
1907 /* draw x and y axis */
1908 gdImageLine(gif, im->xorigin+im->xsize,im->yorigin,
1909 im->xorigin+im->xsize,im->yorigin-im->ysize,
1910 graph_col[GRC_GRID].i);
1912 gdImageLine(gif, im->xorigin,im->yorigin-im->ysize,
1913 im->xorigin+im->xsize,im->yorigin-im->ysize,
1914 graph_col[GRC_GRID].i);
1916 gdImageLine(gif, im->xorigin-4,im->yorigin,
1917 im->xorigin+im->xsize+4,im->yorigin,
1918 graph_col[GRC_FONT].i);
1920 gdImageLine(gif, im->xorigin,im->yorigin,
1921 im->xorigin,im->yorigin-im->ysize,
1922 graph_col[GRC_GRID].i);
1924 /* arrow for X axis direction */
1925 gdImageLine(gif, im->xorigin+im->xsize+4, im->yorigin-3, im->xorigin+im->xsize+4, im->yorigin+3,graph_col[GRC_ARROW].i);
1926 gdImageLine(gif, im->xorigin+im->xsize+4, im->yorigin-3, im->xorigin+im->xsize+9, im->yorigin,graph_col[GRC_ARROW].i);
1927 gdImageLine(gif, im->xorigin+im->xsize+4, im->yorigin+3, im->xorigin+im->xsize+9, im->yorigin,graph_col[GRC_ARROW].i);
1929 /* gdImageLine(gif, im->xorigin+im->xsize-1, im->yorigin-3, im->xorigin+im->xsize-1, im->yorigin+3,graph_col[GRC_MGRID].i);
1930 gdImageLine(gif, im->xorigin+im->xsize, im->yorigin-2, im->xorigin+im->xsize, im->yorigin+2,graph_col[GRC_MGRID].i);
1931 gdImageLine(gif, im->xorigin+im->xsize+1, im->yorigin-2, im->xorigin+im->xsize+1, im->yorigin+2,graph_col[GRC_MGRID].i);
1932 gdImageLine(gif, im->xorigin+im->xsize+2, im->yorigin-2, im->xorigin+im->xsize+2, im->yorigin+2,graph_col[GRC_MGRID].i);
1933 gdImageLine(gif, im->xorigin+im->xsize+3, im->yorigin-1, im->xorigin+im->xsize+3, im->yorigin+1,graph_col[GRC_MGRID].i);
1934 gdImageLine(gif, im->xorigin+im->xsize+4, im->yorigin-1, im->xorigin+im->xsize+4, im->yorigin+1,graph_col[GRC_MGRID].i);
1935 gdImageLine(gif, im->xorigin+im->xsize+5, im->yorigin, im->xorigin+im->xsize+5, im->yorigin,graph_col[GRC_MGRID].i); */
1939 }
1941 void
1942 grid_paint(
1943 image_desc_t *im,
1944 gdImagePtr gif
1945 )
1946 {
1947 long i;
1948 int boxH=8, boxV=8;
1949 int res=0;
1950 gdPoint polyPoints[4]; /* points for filled graph and more*/
1952 /* draw 3d border */
1953 gdImageLine(gif,0,0,im->xgif-1,0,graph_col[GRC_SHADEA].i);
1954 gdImageLine(gif,1,1,im->xgif-2,1,graph_col[GRC_SHADEA].i);
1955 gdImageLine(gif,0,0,0,im->ygif-1,graph_col[GRC_SHADEA].i);
1956 gdImageLine(gif,1,1,1,im->ygif-2,graph_col[GRC_SHADEA].i);
1957 gdImageLine(gif,im->xgif-1,0,im->xgif-1,im->ygif-1,graph_col[GRC_SHADEB].i);
1958 gdImageLine(gif,0,im->ygif-1,im->xgif-1,im->ygif-1,graph_col[GRC_SHADEB].i);
1959 gdImageLine(gif,im->xgif-2,1,im->xgif-2,im->ygif-2,graph_col[GRC_SHADEB].i);
1960 gdImageLine(gif,1,im->ygif-2,im->xgif-2,im->ygif-2,graph_col[GRC_SHADEB].i);
1963 if (im->draw_x_grid == 1 )
1964 vertical_grid(gif, im);
1966 if (im->draw_y_grid == 1){
1967 if(im->logarithmic){
1968 res = horizontal_log_grid(gif,im);
1969 } else {
1970 res = horizontal_grid(gif,im);
1971 }
1973 /* dont draw horizontal grid if there is no min and max val */
1974 if (! res ) {
1975 char *nodata = "No Data found";
1976 gdImageString(gif, LargeFont,
1977 im->xgif/2
1978 - (strlen(nodata)*LargeFont->w)/2,
1979 (2*im->yorigin-im->ysize) / 2,
1980 (unsigned char *)nodata, graph_col[GRC_FONT].i);
1981 }
1982 }
1984 /* yaxis description */
1985 gdImageStringUp(gif, SmallFont,
1986 7,
1987 (im->yorigin - im->ysize/2
1988 +(strlen(im->ylegend)*SmallFont->w)/2 ),
1989 (unsigned char *)im->ylegend, graph_col[GRC_FONT].i);
1992 /* graph title */
1993 gdImageString(gif, LargeFont,
1994 im->xgif/2
1995 - (strlen(im->title)*LargeFont->w)/2,
1996 8,
1997 (unsigned char *)im->title, graph_col[GRC_FONT].i);
1999 /* graph labels */
2000 if( !(im->extra_flags & NOLEGEND) ) {
2001 for(i=0;i<im->gdes_c;i++){
2002 if(im->gdes[i].legend[0] =='\0')
2003 continue;
2005 if(im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT){
2007 polyPoints[0].x = im->gdes[i].legloc.x;
2008 polyPoints[0].y = im->gdes[i].legloc.y+1;
2009 polyPoints[1].x = polyPoints[0].x+boxH;
2010 polyPoints[2].x = polyPoints[0].x+boxH;
2011 polyPoints[3].x = polyPoints[0].x;
2012 polyPoints[1].y = polyPoints[0].y;
2013 polyPoints[2].y = polyPoints[0].y+boxV;
2014 polyPoints[3].y = polyPoints[0].y+boxV;
2015 gdImageFilledPolygon(gif,polyPoints,4,im->gdes[i].col.i);
2016 gdImagePolygon(gif,polyPoints,4,graph_col[GRC_FRAME].i);
2018 gdImageString(gif, SmallFont,
2019 polyPoints[0].x+boxH+6,
2020 polyPoints[0].y-1,
2021 (unsigned char *)im->gdes[i].legend,
2022 graph_col[GRC_FONT].i);
2023 } else {
2024 polyPoints[0].x = im->gdes[i].legloc.x;
2025 polyPoints[0].y = im->gdes[i].legloc.y;
2027 gdImageString(gif, SmallFont,
2028 polyPoints[0].x,
2029 polyPoints[0].y,
2030 (unsigned char *)im->gdes[i].legend,
2031 graph_col[GRC_FONT].i);
2032 }
2033 }
2034 }
2037 gator(gif, (int) im->xgif-5, 5);
2039 }
2042 gdImagePtr
2043 MkLineBrush(image_desc_t *im,long cosel, enum gf_en typsel){
2044 gdImagePtr brush;
2045 int pen;
2046 switch (typsel){
2047 case GF_LINE1:
2048 brush=gdImageCreate(1,1);
2049 break;
2050 case GF_LINE2:
2051 brush=gdImageCreate(2,2);
2052 break;
2053 case GF_LINE3:
2054 brush=gdImageCreate(3,3);
2055 break;
2056 default:
2057 return NULL;
2058 }
2060 gdImageColorTransparent(brush,
2061 gdImageColorAllocate(brush, 0, 0, 0));
2063 pen = gdImageColorAllocate(brush,
2064 im->gdes[cosel].col.red,
2065 im->gdes[cosel].col.green,
2066 im->gdes[cosel].col.blue);
2068 switch (typsel){
2069 case GF_LINE1:
2070 gdImageSetPixel(brush,0,0,pen);
2071 break;
2072 case GF_LINE2:
2073 gdImageSetPixel(brush,0,0,pen);
2074 gdImageSetPixel(brush,0,1,pen);
2075 gdImageSetPixel(brush,1,0,pen);
2076 gdImageSetPixel(brush,1,1,pen);
2077 break;
2078 case GF_LINE3:
2079 gdImageSetPixel(brush,1,0,pen);
2080 gdImageSetPixel(brush,0,1,pen);
2081 gdImageSetPixel(brush,1,1,pen);
2082 gdImageSetPixel(brush,2,1,pen);
2083 gdImageSetPixel(brush,1,2,pen);
2084 break;
2085 default:
2086 return NULL;
2087 }
2088 return brush;
2089 }
2090 /*****************************************************
2091 * lazy check make sure we rely need to create this graph
2092 *****************************************************/
2094 int lazy_check(image_desc_t *im){
2095 FILE *fd = NULL;
2096 int size = 1;
2097 struct stat gifstat;
2099 if (im->lazy == 0) return 0; /* no lazy option */
2100 if (stat(im->graphfile,&gifstat) != 0)
2101 return 0; /* can't stat */
2102 /* one pixel in the existing graph is more then what we would
2103 change here ... */
2104 if (time(NULL) - gifstat.st_mtime >
2105 (im->end - im->start) / im->xsize)
2106 return 0;
2107 if ((fd = fopen(im->graphfile,"rb")) == NULL)
2108 return 0; /* the file does not exist */
2109 switch (im->imgformat) {
2110 case IF_GIF:
2111 size = GifSize(fd,&(im->xgif),&(im->ygif));
2112 break;
2113 case IF_PNG:
2114 size = PngSize(fd,&(im->xgif),&(im->ygif));
2115 break;
2116 }
2117 fclose(fd);
2118 return size;
2119 }
2121 /* draw that picture thing ... */
2122 int
2123 graph_paint(image_desc_t *im, char ***calcpr)
2124 {
2125 int i,ii;
2126 int lazy = lazy_check(im);
2127 FILE *fo;
2129 /* gif stuff */
2130 gdImagePtr gif,brush;
2132 double areazero = 0.0;
2133 enum gf_en stack_gf = GF_PRINT;
2134 graph_desc_t *lastgdes = NULL;
2135 gdPoint canvas[4], back[4]; /* points for canvas*/
2137 /* if we are lazy and there is nothing to PRINT ... quit now */
2138 if (lazy && im->prt_c==0) return 0;
2140 /* pull the data from the rrd files ... */
2142 if(data_fetch(im)==-1)
2143 return -1;
2145 /* evaluate VDEF and CDEF operations ... */
2146 if(data_calc(im)==-1)
2147 return -1;
2149 /* calculate and PRINT and GPRINT definitions. We have to do it at
2150 * this point because it will affect the length of the legends
2151 * if there are no graph elements we stop here ...
2152 * if we are lazy, try to quit ...
2153 */
2154 i=print_calc(im,calcpr);
2155 if(i<0) return -1;
2156 if(i==0 || lazy) return 0;
2158 /* get actual drawing data and find min and max values*/
2159 if(data_proc(im)==-1)
2160 return -1;
2162 if(!im->logarithmic){si_unit(im);} /* identify si magnitude Kilo, Mega Giga ? */
2164 if(!im->rigid && ! im->logarithmic)
2165 expand_range(im); /* make sure the upper and lower limit are
2166 sensible values */
2168 /* init xtr and ytr */
2169 /* determine the actual size of the gif to draw. The size given
2170 on the cmdline is the graph area. But we need more as we have
2171 draw labels and other things outside the graph area */
2174 im->xorigin = 10 + 9 * SmallFont->w+SmallFont->h;
2175 xtr(im,0);
2177 im->yorigin = 14 + im->ysize;
2178 ytr(im,DNAN);
2180 if(im->title[0] != '\0')
2181 im->yorigin += (LargeFont->h+4);
2183 im->xgif=20+im->xsize + im->xorigin;
2184 im->ygif= im->yorigin+2*SmallFont->h;
2186 /* determine where to place the legends onto the graphics.
2187 and set im->ygif to match space requirements for text */
2188 if(leg_place(im)==-1)
2189 return -1;
2191 gif=gdImageCreate(im->xgif,im->ygif);
2193 gdImageInterlace(gif, im->interlaced);
2195 /* allocate colors for the screen elements */
2196 for(i=0;i<DIM(graph_col);i++)
2197 /* check for user override values */
2198 if(im->graph_col[i].red != -1)
2199 graph_col[i].i =
2200 gdImageColorAllocate( gif,
2201 im->graph_col[i].red,
2202 im->graph_col[i].green,
2203 im->graph_col[i].blue);
2204 else
2205 graph_col[i].i =
2206 gdImageColorAllocate( gif,
2207 graph_col[i].red,
2208 graph_col[i].green,
2209 graph_col[i].blue);
2212 /* allocate colors for the graph */
2213 for(i=0;i<im->gdes_c;i++)
2214 /* only for elements which have a color defined */
2215 if (im->gdes[i].col.red != -1)
2216 im->gdes[i].col.i =
2217 gdImageColorAllocate(gif,
2218 im->gdes[i].col.red,
2219 im->gdes[i].col.green,
2220 im->gdes[i].col.blue);
2223 /* the actual graph is created by going through the individual
2224 graph elements and then drawing them */
2226 back[0].x = 0;
2227 back[0].y = 0;
2228 back[1].x = back[0].x+im->xgif;
2229 back[1].y = back[0].y;
2230 back[2].x = back[1].x;
2231 back[2].y = back[0].y+im->ygif;
2232 back[3].x = back[0].x;
2233 back[3].y = back[2].y;
2235 gdImageFilledPolygon(gif,back,4,graph_col[GRC_BACK].i);
2237 canvas[0].x = im->xorigin;
2238 canvas[0].y = im->yorigin;
2239 canvas[1].x = canvas[0].x+im->xsize;
2240 canvas[1].y = canvas[0].y;
2241 canvas[2].x = canvas[1].x;
2242 canvas[2].y = canvas[0].y-im->ysize;
2243 canvas[3].x = canvas[0].x;
2244 canvas[3].y = canvas[2].y;
2246 gdImageFilledPolygon(gif,canvas,4,graph_col[GRC_CANVAS].i);
2248 if (im->minval > 0.0)
2249 areazero = im->minval;
2250 if (im->maxval < 0.0)
2251 areazero = im->maxval;
2253 axis_paint(im,gif);
2255 for(i=0;i<im->gdes_c;i++){
2257 switch(im->gdes[i].gf){
2258 case GF_CDEF:
2259 case GF_VDEF:
2260 case GF_DEF:
2261 case GF_PRINT:
2262 case GF_GPRINT:
2263 case GF_COMMENT:
2264 case GF_HRULE:
2265 case GF_VRULE:
2266 break;
2267 case GF_TICK:
2268 for (ii = 0; ii < im->xsize; ii++)
2269 {
2270 if (!isnan(im->gdes[i].p_data[ii]) &&
2271 im->gdes[i].p_data[ii] > 0.0)
2272 {
2273 /* generate a tick */
2274 gdImageLine(gif, im -> xorigin + ii,
2275 im -> yorigin - (im -> gdes[i].yrule * im -> ysize),
2276 im -> xorigin + ii,
2277 im -> yorigin,
2278 im -> gdes[i].col.i);
2279 }
2280 }
2281 break;
2282 case GF_LINE1:
2283 case GF_LINE2:
2284 case GF_LINE3:
2285 case GF_AREA:
2286 stack_gf = im->gdes[i].gf;
2287 case GF_STACK:
2288 /* fix data points at oo and -oo */
2289 for(ii=0;ii<im->xsize;ii++){
2290 if (isinf(im->gdes[i].p_data[ii])){
2291 if (im->gdes[i].p_data[ii] > 0) {
2292 im->gdes[i].p_data[ii] = im->maxval ;
2293 } else {
2294 im->gdes[i].p_data[ii] = im->minval ;
2295 }
2297 }
2298 }
2300 if (im->gdes[i].col.i != -1){
2301 /* GF_LINE and frined */
2302 if(stack_gf == GF_LINE1 || stack_gf == GF_LINE2 || stack_gf == GF_LINE3 ){
2303 brush = MkLineBrush(im,i,stack_gf);
2304 gdImageSetBrush(gif, brush);
2305 for(ii=1;ii<im->xsize;ii++){
2306 if (isnan(im->gdes[i].p_data[ii-1]) ||
2307 isnan(im->gdes[i].p_data[ii]))
2308 continue;
2309 gdImageLine(gif,
2310 ii+im->xorigin-1,ytr(im,im->gdes[i].p_data[ii-1]),
2311 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2312 gdBrushed);
2314 }
2315 gdImageDestroy(brush);
2316 }
2317 else
2318 /* GF_AREA STACK type*/
2319 if (im->gdes[i].gf == GF_STACK )
2320 for(ii=0;ii<im->xsize;ii++){
2321 if(isnan(im->gdes[i].p_data[ii])){
2322 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
2323 continue;
2324 }
2326 if (lastgdes->p_data[ii] == im->gdes[i].p_data[ii]){
2327 continue;
2328 }
2329 gdImageLine(gif,
2330 ii+im->xorigin,ytr(im,lastgdes->p_data[ii]),
2331 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2332 im->gdes[i].col.i);
2333 }
2335 else /* simple GF_AREA */
2336 for(ii=0;ii<im->xsize;ii++){
2337 if (isnan(im->gdes[i].p_data[ii])) {
2338 im->gdes[i].p_data[ii] = 0;
2339 continue;
2340 }
2341 gdImageLine(gif,
2342 ii+im->xorigin,ytr(im,areazero),
2343 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2344 im->gdes[i].col.i);
2345 }
2346 }
2347 lastgdes = &(im->gdes[i]);
2348 break;
2349 }
2350 }
2352 grid_paint(im,gif);
2354 /* the RULES are the last thing to paint ... */
2355 for(i=0;i<im->gdes_c;i++){
2357 switch(im->gdes[i].gf){
2358 case GF_HRULE:
2359 printf("DEBUG: HRULE at %f\n",im->gdes[i].yrule);
2360 if(isnan(im->gdes[i].yrule)) { /* fetch variable */
2361 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2362 };
2363 if(im->gdes[i].yrule >= im->minval
2364 && im->gdes[i].yrule <= im->maxval)
2365 gdImageLine(gif,
2366 im->xorigin,ytr(im,im->gdes[i].yrule),
2367 im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2368 im->gdes[i].col.i);
2369 break;
2370 case GF_VRULE:
2371 if(im->gdes[i].xrule == 0) { /* fetch variable */
2372 im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
2373 };
2374 if(im->gdes[i].xrule >= im->start
2375 && im->gdes[i].xrule <= im->end)
2376 gdImageLine(gif,
2377 xtr(im,im->gdes[i].xrule),im->yorigin,
2378 xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2379 im->gdes[i].col.i);
2380 break;
2381 default:
2382 break;
2383 }
2384 }
2386 if (strcmp(im->graphfile,"-")==0) {
2387 #ifdef WIN32
2388 /* Change translation mode for stdout to BINARY */
2389 _setmode( _fileno( stdout ), O_BINARY );
2390 #endif
2391 fo = stdout;
2392 } else {
2393 if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2394 rrd_set_error("Opening '%s' for write: %s",im->graphfile, strerror(errno));
2395 return (-1);
2396 }
2397 }
2398 switch (im->imgformat) {
2399 case IF_GIF:
2400 gdImageGif(gif, fo);
2401 break;
2402 case IF_PNG:
2403 gdImagePng(gif, fo);
2404 break;
2405 }
2406 if (strcmp(im->graphfile,"-") != 0)
2407 fclose(fo);
2408 gdImageDestroy(gif);
2410 return 0;
2411 }
2414 /*****************************************************
2415 * graph stuff
2416 *****************************************************/
2418 int
2419 gdes_alloc(image_desc_t *im){
2421 long def_step = (im->end-im->start)/im->xsize;
2423 if (im->step > def_step) /* step can be increassed ... no decreassed */
2424 def_step = im->step;
2426 im->gdes_c++;
2428 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2429 * sizeof(graph_desc_t)))==NULL){
2430 rrd_set_error("realloc graph_descs");
2431 return -1;
2432 }
2435 im->gdes[im->gdes_c-1].step=def_step;
2436 im->gdes[im->gdes_c-1].start=im->start;
2437 im->gdes[im->gdes_c-1].end=im->end;
2438 im->gdes[im->gdes_c-1].vname[0]='\0';
2439 im->gdes[im->gdes_c-1].data=NULL;
2440 im->gdes[im->gdes_c-1].ds_namv=NULL;
2441 im->gdes[im->gdes_c-1].data_first=0;
2442 im->gdes[im->gdes_c-1].p_data=NULL;
2443 im->gdes[im->gdes_c-1].rpnp=NULL;
2444 im->gdes[im->gdes_c-1].col.red = -1;
2445 im->gdes[im->gdes_c-1].col.i=-1;
2446 im->gdes[im->gdes_c-1].legend[0]='\0';
2447 im->gdes[im->gdes_c-1].rrd[0]='\0';
2448 im->gdes[im->gdes_c-1].ds=-1;
2449 im->gdes[im->gdes_c-1].p_data=NULL;
2450 return 0;
2451 }
2453 /* copies input untill the first unescaped colon is found
2454 or until input ends. backslashes have to be escaped as well */
2455 int
2456 scan_for_col(char *input, int len, char *output)
2457 {
2458 int inp,outp=0;
2459 for (inp=0;
2460 inp < len &&
2461 input[inp] != ':' &&
2462 input[inp] != '\0';
2463 inp++){
2464 if (input[inp] == '\\' &&
2465 input[inp+1] != '\0' &&
2466 (input[inp+1] == '\\' ||
2467 input[inp+1] == ':')){
2468 output[outp++] = input[++inp];
2469 }
2470 else {
2471 output[outp++] = input[inp];
2472 }
2473 }
2474 output[outp] = '\0';
2475 return inp;
2476 }
2478 int
2479 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize)
2480 {
2482 image_desc_t im;
2483 int i;
2484 long long_tmp;
2485 time_t start_tmp=0,end_tmp=0;
2486 char scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
2487 char symname[100];
2488 unsigned int col_red,col_green,col_blue;
2489 long scancount;
2490 int linepass = 0; /* stack can only follow directly after LINE* AREA or STACK */
2491 struct time_value start_tv, end_tv;
2492 char *parsetime_error = NULL;
2493 int stroff;
2495 (*prdata)=NULL;
2497 parsetime("end-24h", &start_tv);
2498 parsetime("now", &end_tv);
2500 im.xlab_user.minsec = -1;
2501 im.xgif=0;
2502 im.ygif=0;
2503 im.xsize = 400;
2504 im.ysize = 100;
2505 im.step = 0;
2506 im.ylegend[0] = '\0';
2507 im.title[0] = '\0';
2508 im.minval = DNAN;
2509 im.maxval = DNAN;
2510 im.interlaced = 0;
2511 im.unitsexponent= 9999;
2512 im.extra_flags= 0;
2513 im.rigid = 0;
2514 im.imginfo = NULL;
2515 im.lazy = 0;
2516 im.logarithmic = 0;
2517 im.ygridstep = DNAN;
2518 im.draw_x_grid = 1;
2519 im.draw_y_grid = 1;
2520 im.base = 1000;
2521 im.prt_c = 0;
2522 im.gdes_c = 0;
2523 im.gdes = NULL;
2524 im.imgformat = IF_GIF; /* we default to GIF output */
2526 for(i=0;i<DIM(graph_col);i++)
2527 im.graph_col[i].red=-1;
2530 while (1){
2531 static struct option long_options[] =
2532 {
2533 {"start", required_argument, 0, 's'},
2534 {"end", required_argument, 0, 'e'},
2535 {"x-grid", required_argument, 0, 'x'},
2536 {"y-grid", required_argument, 0, 'y'},
2537 {"vertical-label",required_argument,0,'v'},
2538 {"width", required_argument, 0, 'w'},
2539 {"height", required_argument, 0, 'h'},
2540 {"interlaced", no_argument, 0, 'i'},
2541 {"upper-limit",required_argument, 0, 'u'},
2542 {"lower-limit",required_argument, 0, 'l'},
2543 {"rigid", no_argument, 0, 'r'},
2544 {"base", required_argument, 0, 'b'},
2545 {"logarithmic",no_argument, 0, 'o'},
2546 {"color", required_argument, 0, 'c'},
2547 {"title", required_argument, 0, 't'},
2548 {"imginfo", required_argument, 0, 'f'},
2549 {"imgformat", required_argument, 0, 'a'},
2550 {"lazy", no_argument, 0, 'z'},
2551 {"no-legend", no_argument, 0, 'g'},
2552 {"alt-y-grid", no_argument, 0, 257 },
2553 {"alt-autoscale", no_argument, 0, 258 },
2554 {"alt-autoscale-max", no_argument, 0, 259 },
2555 {"units-exponent",required_argument, 0, 260},
2556 {"step", required_argument, 0, 261},
2557 {0,0,0,0}};
2558 int option_index = 0;
2559 int opt;
2562 opt = getopt_long(argc, argv,
2563 "s:e:x:y:v:w:h:iu:l:rb:oc:t:f:a:z:g",
2564 long_options, &option_index);
2566 if (opt == EOF)
2567 break;
2569 switch(opt) {
2570 case 257:
2571 im.extra_flags |= ALTYGRID;
2572 break;
2573 case 258:
2574 im.extra_flags |= ALTAUTOSCALE;
2575 break;
2576 case 259:
2577 im.extra_flags |= ALTAUTOSCALE_MAX;
2578 break;
2579 case 'g':
2580 im.extra_flags |= NOLEGEND;
2581 break;
2582 case 260:
2583 im.unitsexponent = atoi(optarg);
2584 break;
2585 case 261:
2586 im.step = atoi(optarg);
2587 break;
2588 case 's':
2589 if ((parsetime_error = parsetime(optarg, &start_tv))) {
2590 rrd_set_error( "start time: %s", parsetime_error );
2591 return -1;
2592 }
2593 break;
2594 case 'e':
2595 if ((parsetime_error = parsetime(optarg, &end_tv))) {
2596 rrd_set_error( "end time: %s", parsetime_error );
2597 return -1;
2598 }
2599 break;
2600 case 'x':
2601 if(strcmp(optarg,"none") == 0){
2602 im.draw_x_grid=0;
2603 break;
2604 };
2606 if(sscanf(optarg,
2607 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
2608 scan_gtm,
2609 &im.xlab_user.gridst,
2610 scan_mtm,
2611 &im.xlab_user.mgridst,
2612 scan_ltm,
2613 &im.xlab_user.labst,
2614 &im.xlab_user.precis,
2615 &stroff) == 7 && stroff != 0){
2616 strncpy(im.xlab_form, optarg+stroff, sizeof(im.xlab_form) - 1);
2617 if((im.xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
2618 rrd_set_error("unknown keyword %s",scan_gtm);
2619 return -1;
2620 } else if ((im.xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
2621 rrd_set_error("unknown keyword %s",scan_mtm);
2622 return -1;
2623 } else if ((im.xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
2624 rrd_set_error("unknown keyword %s",scan_ltm);
2625 return -1;
2626 }
2627 im.xlab_user.minsec = 1;
2628 im.xlab_user.stst = im.xlab_form;
2629 } else {
2630 rrd_set_error("invalid x-grid format");
2631 return -1;
2632 }
2633 break;
2634 case 'y':
2636 if(strcmp(optarg,"none") == 0){
2637 im.draw_y_grid=0;
2638 break;
2639 };
2641 if(sscanf(optarg,
2642 "%lf:%d",
2643 &im.ygridstep,
2644 &im.ylabfact) == 2) {
2645 if(im.ygridstep<=0){
2646 rrd_set_error("grid step must be > 0");
2647 return -1;
2648 } else if (im.ylabfact < 1){
2649 rrd_set_error("label factor must be > 0");
2650 return -1;
2651 }
2652 } else {
2653 rrd_set_error("invalid y-grid format");
2654 return -1;
2655 }
2656 break;
2657 case 'v':
2658 strncpy(im.ylegend,optarg,150);
2659 im.ylegend[150]='\0';
2660 break;
2661 case 'u':
2662 im.maxval = atof(optarg);
2663 break;
2664 case 'l':
2665 im.minval = atof(optarg);
2666 break;
2667 case 'b':
2668 im.base = atol(optarg);
2669 if(im.base != 1024 && im.base != 1000 ){
2670 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
2671 return -1;
2672 }
2673 break;
2674 case 'w':
2675 long_tmp = atol(optarg);
2676 if (long_tmp < 10) {
2677 rrd_set_error("width below 10 pixels");
2678 return -1;
2679 }
2680 im.xsize = long_tmp;
2681 break;
2682 case 'h':
2683 long_tmp = atol(optarg);
2684 if (long_tmp < 10) {
2685 rrd_set_error("height below 10 pixels");
2686 return -1;
2687 }
2688 im.ysize = long_tmp;
2689 break;
2690 case 'i':
2691 im.interlaced = 1;
2692 break;
2693 case 'r':
2694 im.rigid = 1;
2695 break;
2696 case 'f':
2697 im.imginfo = optarg;
2698 break;
2699 case 'a':
2700 if((im.imgformat = if_conv(optarg)) == -1) {
2701 rrd_set_error("unsupported graphics format '%s'",optarg);
2702 return -1;
2703 }
2704 break;
2705 case 'z':
2706 im.lazy = 1;
2707 break;
2708 case 'o':
2709 im.logarithmic = 1;
2710 if (isnan(im.minval))
2711 im.minval=1;
2712 break;
2713 case 'c':
2714 if(sscanf(optarg,
2715 "%10[A-Z]#%2x%2x%2x",
2716 col_nam,&col_red,&col_green,&col_blue) == 4){
2717 int ci;
2718 if((ci=grc_conv(col_nam)) != -1){
2719 im.graph_col[ci].red=col_red;
2720 im.graph_col[ci].green=col_green;
2721 im.graph_col[ci].blue=col_blue;
2722 } else {
2723 rrd_set_error("invalid color name '%s'",col_nam);
2724 }
2725 } else {
2726 rrd_set_error("invalid color def format");
2727 return -1;
2728 }
2729 break;
2730 case 't':
2731 strncpy(im.title,optarg,150);
2732 im.title[150]='\0';
2733 break;
2735 case '?':
2736 if (optopt != 0)
2737 rrd_set_error("unknown option '%c'", optopt);
2738 else
2739 rrd_set_error("unknown option '%s'",argv[optind-1]);
2740 return -1;
2741 }
2742 }
2744 if (optind >= argc) {
2745 rrd_set_error("missing filename");
2746 return -1;
2747 }
2749 if (im.logarithmic == 1 && (im.minval <= 0 || isnan(im.minval))){
2750 rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");
2751 return -1;
2752 }
2754 strncpy(im.graphfile,argv[optind],MAXPATH-1);
2755 im.graphfile[MAXPATH-1]='\0';
2757 if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
2758 return -1;
2759 }
2761 if (start_tmp < 3600*24*365*10){
2762 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
2763 return -1;
2764 }
2766 if (end_tmp < start_tmp) {
2767 rrd_set_error("start (%ld) should be less than end (%ld)",
2768 start_tmp, end_tmp);
2769 return -1;
2770 }
2772 im.start = start_tmp;
2773 im.end = end_tmp;
2776 for(i=optind+1;i<argc;i++){
2777 int argstart=0;
2778 int strstart=0;
2779 char varname[30],*rpnex;
2780 gdes_alloc(&im);
2781 if(sscanf(argv[i],"%10[A-Z0-9]:%n",symname,&argstart)==1){
2782 if((im.gdes[im.gdes_c-1].gf=gf_conv(symname))==-1){
2783 im_free(&im);
2784 rrd_set_error("unknown function '%s'",symname);
2785 return -1;
2786 }
2787 } else {
2788 rrd_set_error("can't parse '%s'",argv[i]);
2789 im_free(&im);
2790 return -1;
2791 }
2793 /* reset linepass if a non LINE/STACK/AREA operator gets parsed
2795 if (im.gdes[im.gdes_c-1].gf != GF_LINE1 &&
2796 im.gdes[im.gdes_c-1].gf != GF_LINE2 &&
2797 im.gdes[im.gdes_c-1].gf != GF_LINE3 &&
2798 im.gdes[im.gdes_c-1].gf != GF_AREA &&
2799 im.gdes[im.gdes_c-1].gf != GF_STACK) {
2800 linepass = 0;
2801 }
2802 */
2804 switch(im.gdes[im.gdes_c-1].gf){
2805 case GF_PRINT:
2806 im.prt_c++;
2807 case GF_GPRINT:
2808 strstart=0;
2809 sscanf(&argv[i][argstart], DEF_NAM_FMT ":%n"
2810 ,varname
2811 ,&strstart
2812 );
2814 if (strstart==0) {
2815 im_free(&im);
2816 rrd_set_error("can't parse vname in '%s'",&argv[i][argstart]);
2817 return -1;
2818 };
2820 if ((im.gdes[im.gdes_c-1].vidx=find_var(&im,varname))==-1){
2821 im_free(&im);
2822 rrd_set_error("Unknown variable '%s' in (G)PRINT",varname);
2823 return -1;
2824 } else {
2825 int n=0;
2827 sscanf(&argv[i][argstart+strstart],CF_NAM_FMT ":%n"
2828 ,symname
2829 ,&n
2830 );
2831 if (im.gdes[im.gdes[im.gdes_c-1].vidx].gf==GF_VDEF) {
2832 /* No consolidation function should be present */
2833 if (n != 0) {
2834 rrd_set_error("(G)PRINT of VDEF needs no CF");
2835 im_free(&im);
2836 return -1;
2837 }
2838 } else {
2839 /* A consolidation function should follow */
2840 if (n==0) {
2841 im_free(&im);
2842 rrd_set_error("Missing or incorrect CF in (G)PRINTing '%s' (%s)",varname,&argv[i][argstart]);
2843 return -1;
2844 };
2845 if((im.gdes[im.gdes_c-1].cf=cf_conv(symname))==-1){
2846 im_free(&im);
2847 return -1;
2848 };
2849 strstart+=n;
2850 };
2851 };
2853 scan_for_col(
2854 &argv[i][argstart+strstart]
2855 ,FMT_LEG_LEN
2856 ,im.gdes[im.gdes_c-1].format
2857 );
2858 break;
2859 case GF_COMMENT:
2860 if(strlen(&argv[i][argstart])>FMT_LEG_LEN) argv[i][argstart+FMT_LEG_LEN-3]='\0' ;
2861 strcpy(im.gdes[im.gdes_c-1].legend, &argv[i][argstart]);
2862 break;
2863 case GF_HRULE:
2864 /* scan for either "HRULE:vname#..." or "HRULE:num#..."
2865 *
2866 * If a vname is used, the value NaN is set; this is catched
2867 * when graphing. Setting value NaN from the script is not
2868 * permitted
2869 */
2870 strstart=0;
2871 sscanf(&argv[i][argstart], "%lf#%n"
2872 ,&im.gdes[im.gdes_c-1].yrule
2873 ,&strstart
2874 );
2875 if (strstart==0) { /* no number, should be vname */
2876 sscanf(&argv[i][argstart], DEF_NAM_FMT "#%n"
2877 ,varname
2878 ,&strstart
2879 );
2880 if (strstart) {
2881 im.gdes[im.gdes_c-1].yrule = DNAN;/* signal use of vname */
2882 if((im.gdes[im.gdes_c-1].vidx=find_var(&im,varname))==-1){
2883 im_free(&im);
2884 rrd_set_error("unknown variable '%s' in HRULE",varname);
2885 return -1;
2886 }
2887 if(im.gdes[im.gdes[im.gdes_c-1].vidx].gf != GF_VDEF) {
2888 im_free(&im);
2889 rrd_set_error("Only VDEF is allowed in HRULE",varname);
2890 return -1;
2891 }
2892 }
2893 } else {
2894 printf("DEBUG: matched HRULE:num\n");
2895 printf("DEBUG: strstart==%i\n",strstart);
2896 };
2897 if (strstart==0) {
2898 im_free(&im);
2899 rrd_set_error("can't parse '%s'",&argv[i][argstart]);
2900 return -1;
2901 } else {
2902 int n=0;
2903 if(sscanf(
2904 &argv[i][argstart+strstart],
2905 "%2x%2x%2x:%n",
2906 &col_red,
2907 &col_green,
2908 &col_blue,
2909 &n)>=3) {
2910 im.gdes[im.gdes_c-1].col.red = col_red;
2911 im.gdes[im.gdes_c-1].col.green = col_green;
2912 im.gdes[im.gdes_c-1].col.blue = col_blue;
2913 if (n==0) {
2914 im.gdes[im.gdes_c-1].legend[0] = '\0';
2915 } else {
2916 scan_for_col(&argv[i][argstart+strstart+n],FMT_LEG_LEN,im.gdes[im.gdes_c-1].legend);
2917 }
2918 } else {
2919 im_free(&im);
2920 rrd_set_error("can't parse '%s'",&argv[i][argstart]);
2921 return -1;
2922 }
2923 }
2925 break;
2926 case GF_VRULE:
2927 /* scan for either "VRULE:vname#..." or "VRULE:num#..."
2928 *
2929 * If a vname is used, the value 0 is set; this is catched
2930 * when graphing. Setting value 0 from the script is not
2931 * permitted
2932 */
2933 strstart=0;
2934 sscanf(&argv[i][argstart], "%lu#%n"
2935 ,(long unsigned int *)&im.gdes[im.gdes_c-1].xrule
2936 ,&strstart
2937 );
2938 if (strstart==0) { /* no number, should be vname */
2939 sscanf(&argv[i][argstart], DEF_NAM_FMT "#%n"
2940 ,varname
2941 ,&strstart
2942 );
2943 if (strstart!=0) { /* vname matched */
2944 im.gdes[im.gdes_c-1].xrule = 0;/* signal use of vname */
2945 if((im.gdes[im.gdes_c-1].vidx=find_var(&im,varname))==-1){
2946 im_free(&im);
2947 rrd_set_error("unknown variable '%s' in VRULE",varname);
2948 return -1;
2949 }
2950 if(im.gdes[im.gdes[im.gdes_c-1].vidx].gf != GF_VDEF) {
2951 im_free(&im);
2952 rrd_set_error("Only VDEF is allowed in VRULE",varname);
2953 return -1;
2954 }
2955 }
2956 } else {
2957 if (im.gdes[im.gdes_c-1].xrule==0)
2958 strstart=0;
2959 }
2961 if (strstart==0) {
2962 im_free(&im);
2963 rrd_set_error("can't parse '%s'",&argv[i][argstart]);
2964 return -1;
2965 } else {
2966 int n=0;
2967 if(sscanf(
2968 &argv[i][argstart+strstart],
2969 "%2x%2x%2x:%n",
2970 &col_red,
2971 &col_green,
2972 &col_blue,
2973 &n)>=3) {
2974 im.gdes[im.gdes_c-1].col.red = col_red;
2975 im.gdes[im.gdes_c-1].col.green = col_green;
2976 im.gdes[im.gdes_c-1].col.blue = col_blue;
2977 if (n==0) {
2978 im.gdes[im.gdes_c-1].legend[0] = '\0';
2979 } else {
2980 scan_for_col(&argv[i][argstart+strstart+n],FMT_LEG_LEN,im.gdes[im.gdes_c-1].legend);
2981 }
2982 } else {
2983 im_free(&im);
2984 rrd_set_error("can't parse '%s'",&argv[i][argstart]);
2985 return -1;
2986 }
2987 }
2988 break;
2989 case GF_TICK:
2990 if((scancount=sscanf(
2991 &argv[i][argstart],
2992 "%29[^:#]#%2x%2x%2x:%lf:%n",
2993 varname,
2994 &col_red,
2995 &col_green,
2996 &col_blue,
2997 &(im.gdes[im.gdes_c-1].yrule),
2998 &strstart))>=1)
2999 {
3000 im.gdes[im.gdes_c-1].col.red = col_red;
3001 im.gdes[im.gdes_c-1].col.green = col_green;
3002 im.gdes[im.gdes_c-1].col.blue = col_blue;
3003 if(strstart <= 0){
3004 im.gdes[im.gdes_c-1].legend[0] = '\0';
3005 } else {
3006 scan_for_col(&argv[i][argstart+strstart],FMT_LEG_LEN,im.gdes[im.gdes_c-1].legend);
3007 }
3008 if((im.gdes[im.gdes_c-1].vidx=find_var(&im,varname))==-1){
3009 im_free(&im);
3010 rrd_set_error("unknown variable '%s'",varname);
3011 return -1;
3012 }
3013 if (im.gdes[im.gdes_c-1].yrule <= 0.0 || im.gdes[im.gdes_c-1].yrule > 1.0)
3014 {
3015 im_free(&im);
3016 rrd_set_error("Tick mark scaling factor out of range");
3017 return -1;
3018 }
3019 if (scancount < 4)
3020 im.gdes[im.gdes_c-1].col.red = -1;
3021 if (scancount < 5)
3022 /* default tick marks: 10% of the y-axis */
3023 im.gdes[im.gdes_c-1].yrule = 0.1;
3025 } else {
3026 im_free(&im);
3027 rrd_set_error("can't parse '%s'",&argv[i][argstart]);
3028 return -1;
3029 } /* endif sscanf */
3030 break;
3031 case GF_STACK:
3032 if(linepass == 0){
3033 im_free(&im);
3034 rrd_set_error("STACK must follow AREA, LINE or STACK");
3035 return -1;
3036 }
3037 case GF_LINE1:
3038 case GF_LINE2:
3039 case GF_LINE3:
3040 case GF_AREA:
3041 linepass = 1;
3042 if((scancount=sscanf(
3043 &argv[i][argstart],
3044 "%29[^:#]#%2x%2x%2x:%n",
3045 varname,
3046 &col_red,
3047 &col_green,
3048 &col_blue,
3049 &strstart))>=1){
3050 im.gdes[im.gdes_c-1].col.red = col_red;
3051 im.gdes[im.gdes_c-1].col.green = col_green;
3052 im.gdes[im.gdes_c-1].col.blue = col_blue;
3053 if(strstart <= 0){
3054 im.gdes[im.gdes_c-1].legend[0] = '\0';
3055 } else {
3056 scan_for_col(&argv[i][argstart+strstart],FMT_LEG_LEN,im.gdes[im.gdes_c-1].legend);
3057 }
3058 if((im.gdes[im.gdes_c-1].vidx=find_var(&im,varname))==-1){
3059 im_free(&im);
3060 rrd_set_error("unknown variable '%s'",varname);
3061 return -1;
3062 }
3063 if (scancount < 4)
3064 im.gdes[im.gdes_c-1].col.red = -1;
3066 } else {
3067 im_free(&im);
3068 rrd_set_error("can't parse '%s'",&argv[i][argstart]);
3069 return -1;
3070 }
3071 break;
3072 case GF_CDEF:
3073 if((rpnex = malloc(strlen(&argv[i][argstart])*sizeof(char)))==NULL){
3074 rrd_set_error("malloc for CDEF");
3075 return -1;
3076 }
3077 if(sscanf(
3078 &argv[i][argstart],
3079 DEF_NAM_FMT "=%[^: ]",
3080 im.gdes[im.gdes_c-1].vname,
3081 rpnex) != 2){
3082 im_free(&im);
3083 free(rpnex);
3084 rrd_set_error("can't parse CDEF '%s'",&argv[i][argstart]);
3085 return -1;
3086 }
3087 /* checking for duplicate variable names */
3088 if(find_var(&im,im.gdes[im.gdes_c-1].vname) != -1){
3089 im_free(&im);
3090 rrd_set_error("duplicate variable '%s'",
3091 im.gdes[im.gdes_c-1].vname);
3092 return -1;
3093 }
3094 if((im.gdes[im.gdes_c-1].rpnp =
3095 rpn_parse((void*)&im,rpnex,&find_var_wrapper))== NULL){
3096 rrd_set_error("invalid rpn expression '%s'", rpnex);
3097 im_free(&im);
3098 return -1;
3099 }
3100 free(rpnex);
3101 break;
3102 case GF_VDEF:
3103 /*
3104 * strstart is set to zero and will NOT be changed
3105 * if the comma is not matched. This means that it
3106 * remains zero. Although strstart is initialized to
3107 * zero at the beginning of this loop, we do it again
3108 * here just in case someone changes the code...
3109 *
3110 * According to the docs we cannot rely on the
3111 * returned value from sscanf; it can be 2 or 3,
3112 * depending on %n incrementing it or not.
3113 */
3114 strstart=0;
3115 sscanf(
3116 &argv[i][argstart],
3117 DEF_NAM_FMT "=" DEF_NAM_FMT ",%n",
3118 im.gdes[im.gdes_c-1].vname,
3119 varname,
3120 &strstart);
3121 if (strstart){
3122 /* checking both variable names */
3123 if (find_var(&im,im.gdes[im.gdes_c-1].vname) != -1){
3124 im_free(&im);
3125 rrd_set_error("duplicate variable '%s'",
3126 im.gdes[im.gdes_c-1].vname);
3127 return -1;
3128 } else {
3129 if ((im.gdes[im.gdes_c-1].vidx=find_var(&im,varname)) == -1){
3130 im_free(&im);
3131 rrd_set_error("variable '%s' not known in VDEF '%s'",
3132 varname,
3133 im.gdes[im.gdes_c-1].vname);
3134 return -1;
3135 } else {
3136 if(im.gdes[im.gdes[im.gdes_c-1].vidx].gf != GF_DEF
3137 && im.gdes[im.gdes[im.gdes_c-1].vidx].gf != GF_CDEF){
3138 rrd_set_error("variable '%s' not DEF nor CDEF in VDEF '%s'",
3139 varname,
3140 im.gdes[im.gdes_c-1].vname);
3141 im_free(&im);
3142 return -1;
3143 }
3144 }
3145 /* parsed upto and including the first comma. Now
3146 * see what function is requested. This function
3147 * sets the error string.
3148 */
3149 if (vdef_parse(&im.gdes[im.gdes_c-1],&argv[i][argstart+strstart])<0) {
3150 im_free(&im);
3151 return -1;
3152 };
3153 }
3154 } else {
3155 im_free(&im);
3156 rrd_set_error("can't parse VDEF '%s'",&argv[i][argstart]);
3157 return -1;
3158 }
3159 break;
3160 case GF_DEF:
3161 if (sscanf(
3162 &argv[i][argstart],
3163 DEF_NAM_FMT "=%n",
3164 im.gdes[im.gdes_c-1].vname,
3165 &strstart)== 1 && strstart){ /* is the = did not match %n returns 0 */
3166 if(sscanf(&argv[i][argstart
3167 +strstart
3168 +scan_for_col(&argv[i][argstart+strstart],
3169 MAXPATH,im.gdes[im.gdes_c-1].rrd)],
3170 ":" DS_NAM_FMT ":" CF_NAM_FMT,
3171 im.gdes[im.gdes_c-1].ds_nam,
3172 symname) != 2){
3173 im_free(&im);
3174 rrd_set_error("can't parse DEF '%s' -2",&argv[i][argstart]);
3175 return -1;
3176 }
3177 } else {
3178 im_free(&im);
3179 rrd_set_error("can't parse DEF '%s'",&argv[i][argstart]);
3180 return -1;
3181 }
3183 /* checking for duplicate DEF CDEFS */
3184 if (find_var(&im,im.gdes[im.gdes_c-1].vname) != -1){
3185 im_free(&im);
3186 rrd_set_error("duplicate variable '%s'",
3187 im.gdes[im.gdes_c-1].vname);
3188 return -1;
3189 }
3190 if((im.gdes[im.gdes_c-1].cf=cf_conv(symname))==-1){
3191 im_free(&im);
3192 rrd_set_error("unknown cf '%s'",symname);
3193 return -1;
3194 }
3195 break;
3196 }
3198 }
3200 if (im.gdes_c==0){
3201 rrd_set_error("can't make a graph without contents");
3202 im_free(&im);
3203 return(-1);
3204 }
3206 /* parse rest of arguments containing information on what to draw*/
3207 if (graph_paint(&im,prdata)==-1){
3208 im_free(&im);
3209 return -1;
3210 }
3212 *xsize=im.xgif;
3213 *ysize=im.ygif;
3214 if (im.imginfo){
3215 char *filename;
3216 if (! (*prdata)) {
3217 /* maybe prdata is not allocated yet ... lets do it now */
3218 if((*prdata = calloc(2,sizeof(char *)))==NULL){
3219 rrd_set_error("malloc imginfo");
3220 return -1;
3221 };
3222 }
3223 if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
3224 ==NULL){
3225 rrd_set_error("malloc imginfo");
3226 return -1;
3227 }
3228 filename=im.graphfile+strlen(im.graphfile);
3229 while(filename > im.graphfile){
3230 if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
3231 filename--;
3232 }
3234 sprintf((*prdata)[0],im.imginfo,filename,im.xgif,im.ygif);
3235 }
3236 im_free(&im);
3237 return 0;
3238 }
3240 int bad_format(char *fmt) {
3241 char *ptr;
3243 ptr = fmt;
3244 while (*ptr != '\0') {
3245 if (*ptr == '%') {ptr++;
3246 if (*ptr == '\0') return 1;
3247 while ((*ptr >= '0' && *ptr <= '9') || *ptr == '.') {
3248 ptr++;
3249 }
3250 if (*ptr == '\0') return 1;
3251 if (*ptr == 'l') {
3252 ptr++;
3253 if (*ptr == '\0') return 1;
3254 if (*ptr == 'e' || *ptr == 'f') {
3255 ptr++;
3256 } else { return 1; }
3257 }
3258 else if (*ptr == 's' || *ptr == 'S' || *ptr == '%') { ++ptr; }
3259 else { return 1; }
3260 } else {
3261 ++ptr;
3262 }
3263 }
3264 return 0;
3265 }
3266 int
3267 vdef_parse(gdes,str)
3268 struct graph_desc_t *gdes;
3269 char *str;
3270 {
3271 /* A VDEF currently is either "func" or "param,func"
3272 * so the parsing is rather simple. Change if needed.
3273 */
3274 double param;
3275 char func[30];
3276 int n;
3278 n=0;
3279 sscanf(str,"%le,%29[A-Z]%n",¶m,func,&n);
3280 if (n==strlen(str)) { /* matched */
3281 ;
3282 } else {
3283 n=0;
3284 sscanf(str,"%29[A-Z]%n",func,&n);
3285 if (n==strlen(str)) { /* matched */
3286 param=DNAN;
3287 } else {
3288 rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3289 ,str
3290 ,gdes->vname
3291 );
3292 return -1;
3293 }
3294 }
3295 if (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3296 else if (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3297 else if (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3298 else if (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3299 else if (!strcmp("FIRST", func)) gdes->vf.op = VDEF_FIRST;
3300 else if (!strcmp("LAST", func)) gdes->vf.op = VDEF_LAST;
3301 else {
3302 rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3303 ,func
3304 ,gdes->vname
3305 );
3306 return -1;
3307 };
3309 switch (gdes->vf.op) {
3310 case VDEF_PERCENT:
3311 if (isnan(param)) { /* no parameter given */
3312 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3313 ,func
3314 ,gdes->vname
3315 );
3316 return -1;
3317 };
3318 if (param>=0.0 && param<=100.0) {
3319 gdes->vf.param = param;
3320 gdes->vf.val = DNAN; /* undefined */
3321 gdes->vf.when = 0; /* undefined */
3322 } else {
3323 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3324 ,param
3325 ,gdes->vname
3326 );
3327 return -1;
3328 };
3329 break;
3330 case VDEF_MAXIMUM:
3331 case VDEF_AVERAGE:
3332 case VDEF_MINIMUM:
3333 case VDEF_FIRST:
3334 case VDEF_LAST:
3335 if (isnan(param)) {
3336 gdes->vf.param = DNAN;
3337 gdes->vf.val = DNAN;
3338 gdes->vf.when = 0;
3339 } else {
3340 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3341 ,func
3342 ,gdes->vname
3343 );
3344 return -1;
3345 };
3346 break;
3347 };
3348 return 0;
3349 }
3350 int
3351 vdef_calc(im,gdi)
3352 image_desc_t *im;
3353 int gdi;
3354 {
3355 graph_desc_t *src,*dst;
3356 rrd_value_t *data;
3357 long step,steps;
3359 dst = &im->gdes[gdi];
3360 src = &im->gdes[dst->vidx];
3361 data = src->data + src->ds + src->ds_cnt; /* skip first value! */
3362 steps = (src->end - src->start) / src->step;
3364 #if 0
3365 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3366 ,src->start
3367 ,src->end
3368 ,steps
3369 );
3370 #endif
3372 switch (im->gdes[gdi].vf.op) {
3373 case VDEF_PERCENT: {
3374 rrd_value_t * array;
3375 int field;
3378 if ((array = malloc(steps*sizeof(double)))==NULL) {
3379 rrd_set_error("malloc VDEV_PERCENT");
3380 return -1;
3381 }
3382 for (step=0;step < steps; step++) {
3383 array[step]=data[step*src->ds_cnt];
3384 }
3385 qsort(array,step,sizeof(double),vdef_percent_compar);
3387 field = (steps-1)*dst->vf.param/100;
3388 dst->vf.val = array[field];
3389 dst->vf.when = 0; /* no time component */
3390 #if 1
3391 for(step=0;step<steps;step++)
3392 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3393 #endif
3394 }
3395 break;
3396 case VDEF_MAXIMUM:
3397 step=0;
3398 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3399 if (step == steps) {
3400 dst->vf.val = DNAN;
3401 dst->vf.when = 0;
3402 } else {
3403 dst->vf.val = data[steps*src->ds_cnt];
3404 dst->vf.when = src->start + (step+1)*src->step;
3405 }
3406 while (step != steps) {
3407 if (finite(data[step*src->ds_cnt])) {
3408 if (data[step*src->ds_cnt] > dst->vf.val) {
3409 dst->vf.val = data[steps*src->ds_cnt];
3410 dst->vf.when = src->start + (step+1)*src->step;
3411 }
3412 }
3413 step++;
3414 }
3415 break;
3416 case VDEF_AVERAGE: {
3417 int cnt=0;
3418 double sum=0.0;
3419 for (step=0;step<steps;step++) {
3420 if (finite(data[step*src->ds_cnt])) {
3421 sum += data[step*src->ds_cnt];
3422 cnt ++;
3423 }
3424 step++;
3425 }
3426 if (cnt) {
3427 dst->vf.val = sum/cnt;
3428 dst->vf.when = 0; /* no time component */
3429 } else {
3430 dst->vf.val = DNAN;
3431 dst->vf.when = 0;
3432 }
3433 }
3434 break;
3435 case VDEF_MINIMUM:
3436 step=0;
3437 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3438 if (step == steps) {
3439 dst->vf.val = DNAN;
3440 dst->vf.when = 0;
3441 } else {
3442 dst->vf.val = data[steps*src->ds_cnt];
3443 dst->vf.when = src->start + (step+1)*src->step;
3444 }
3445 while (step != steps) {
3446 if (finite(data[step*src->ds_cnt])) {
3447 if (data[step*src->ds_cnt] < dst->vf.val) {
3448 dst->vf.val = data[steps*src->ds_cnt];
3449 dst->vf.when = src->start + (step+1)*src->step;
3450 }
3451 }
3452 step++;
3453 }
3454 break;
3455 case VDEF_FIRST:
3456 /* The time value returned here is one step before the
3457 * actual time value. This is the start of the first
3458 * non-NaN interval.
3459 */
3460 step=0;
3461 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3462 if (step == steps) { /* all entries were NaN */
3463 dst->vf.val = DNAN;
3464 dst->vf.when = 0;
3465 } else {
3466 dst->vf.val = data[step*src->ds_cnt];
3467 dst->vf.when = src->start + step*src->step;
3468 }
3469 break;
3470 case VDEF_LAST:
3471 /* The time value returned here is the
3472 * actual time value. This is the end of the last
3473 * non-NaN interval.
3474 */
3475 step=steps-1;
3476 while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3477 if (step < 0) { /* all entries were NaN */
3478 dst->vf.val = DNAN;
3479 dst->vf.when = 0;
3480 } else {
3481 dst->vf.val = data[step*src->ds_cnt];
3482 dst->vf.when = src->start + (step+1)*src->step;
3483 }
3484 break;
3485 }
3486 return 0;
3487 }
3489 /* NaN <= -INF <= finite_values <= INF */
3490 int
3491 vdef_percent_compar(a,b)
3492 const void *a,*b;
3493 {
3494 /* Equality is not returned; this doesn't hurt except
3495 * (maybe) for a little performance.
3496 */
3498 /* First catch NaN values. They are smallest */
3499 if (isnan( *(double *)a )) return -1;
3500 if (isnan( *(double *)b )) return 1;
3502 /* NaN doestn't reach this part so INF and -INF are extremes.
3503 * The sign from isinf() is compatible with the sign we return
3504 */
3505 if (isinf( *(double *)a )) return isinf( *(double *)a );
3506 if (isinf( *(double *)b )) return isinf( *(double *)b );
3508 /* If we reach this, both values must be finite */
3509 if ( *(double *)a < *(double *)b ) return -1; else return 1;
3510 }