1 /****************************************************************************
2 * RRDtool 1.1.x Copyright Tobias Oetiker, 1997 - 2002
3 ****************************************************************************
4 * rrd__graph.c make creates ne rrds
5 ****************************************************************************/
8 #include <sys/stat.h>
10 #include "rrd_tool.h"
12 #ifdef WIN32
13 #include <io.h>
14 #include <fcntl.h>
15 #endif
17 #ifdef HAVE_TIME_H
18 #include <time.h>
19 #endif
21 #ifdef HAVE_LOCALE_H
22 #include <locale.h>
23 #endif
25 #include "rrd_graph.h"
26 #include "rrd_graph_helper.h"
28 /* some constant definitions */
31 #ifndef RRD_DEFAULT_FONT
32 #ifdef WIN32
33 #define RRD_DEFAULT_FONT "c:/winnt/fonts/COUR.TTF"
34 #else
35 #define RRD_DEFAULT_FONT "/usr/share/fonts/truetype/openoffice/ariosor.ttf"
36 /* #define RRD_DEFAULT_FONT "/usr/share/fonts/truetype/Arial.ttf" */
37 #endif
38 #endif
41 text_prop_t text_prop[] = {
42 { 10.0, RRD_DEFAULT_FONT }, /* default */
43 { 12.0, RRD_DEFAULT_FONT }, /* title */
44 { 8.0, RRD_DEFAULT_FONT }, /* axis */
45 { 10.0, RRD_DEFAULT_FONT }, /* unit */
46 { 10.0, RRD_DEFAULT_FONT } /* legend */
47 };
49 xlab_t xlab[] = {
50 {0, TMT_SECOND,30, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
51 {2, TMT_MINUTE,1, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
52 {5, TMT_MINUTE,2, TMT_MINUTE,10, TMT_MINUTE,10, 0,"%H:%M"},
53 {10, TMT_MINUTE,5, TMT_MINUTE,20, TMT_MINUTE,20, 0,"%H:%M"},
54 {30, TMT_MINUTE,10, TMT_HOUR,1, TMT_HOUR,1, 0,"%H:%M"},
55 {60, TMT_MINUTE,30, TMT_HOUR,2, TMT_HOUR,2, 0,"%H:%M"},
56 {180, TMT_HOUR,1, TMT_HOUR,6, TMT_HOUR,6, 0,"%H:%M"},
57 /*{300, TMT_HOUR,3, TMT_HOUR,12, TMT_HOUR,12, 12*3600,"%a %p"}, this looks silly*/
58 {600, TMT_HOUR,6, TMT_DAY,1, TMT_DAY,1, 24*3600,"%a"},
59 {1800, TMT_HOUR,12, TMT_DAY,1, TMT_DAY,2, 24*3600,"%a"},
60 {3600, TMT_DAY,1, TMT_WEEK,1, TMT_WEEK,1, 7*24*3600,"Week %V"},
61 {3*3600, TMT_WEEK,1, TMT_MONTH,1, TMT_WEEK,2, 7*24*3600,"Week %V"},
62 {6*3600, TMT_MONTH,1, TMT_MONTH,1, TMT_MONTH,1, 30*24*3600,"%b"},
63 {48*3600, TMT_MONTH,1, TMT_MONTH,3, TMT_MONTH,3, 30*24*3600,"%b"},
64 {10*24*3600, TMT_YEAR,1, TMT_YEAR,1, TMT_YEAR,1, 365*24*3600,"%y"},
65 {-1,TMT_MONTH,0,TMT_MONTH,0,TMT_MONTH,0,0,""}
66 };
68 /* sensible logarithmic y label intervals ...
69 the first element of each row defines the possible starting points on the
70 y axis ... the other specify the */
72 double yloglab[][12]= {{ 1e9, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
73 { 1e3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
74 { 1e1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
75 /* { 1e1, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, */
76 { 1e1, 1, 2.5, 5, 7.5, 0, 0, 0, 0, 0, 0, 0 },
77 { 1e1, 1, 2, 4, 6, 8, 0, 0, 0, 0, 0, 0 },
78 { 1e1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0 },
79 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }};
81 /* sensible y label intervals ...*/
83 ylab_t ylab[]= {
84 {0.1, {1,2, 5,10}},
85 {0.2, {1,5,10,20}},
86 {0.5, {1,2, 4,10}},
87 {1.0, {1,2, 5,10}},
88 {2.0, {1,5,10,20}},
89 {5.0, {1,2, 4,10}},
90 {10.0, {1,2, 5,10}},
91 {20.0, {1,5,10,20}},
92 {50.0, {1,2, 4,10}},
93 {100.0, {1,2, 5,10}},
94 {200.0, {1,5,10,20}},
95 {500.0, {1,2, 4,10}},
96 {0.0, {0,0,0,0}}};
99 gfx_color_t graph_col[] = /* default colors */
100 { 0xFFFFFFFF, /* canvas */
101 0xF0F0F0FF, /* background */
102 0xD0D0D0FF, /* shade A */
103 0xA0A0A0FF, /* shade B */
104 0x909090FF, /* grid */
105 0xE05050FF, /* major grid */
106 0x000000FF, /* font */
107 0x000000FF, /* frame */
108 0xFF0000FF /* arrow */
109 };
112 /* #define DEBUG */
114 #ifdef DEBUG
115 # define DPRINT(x) (void)(printf x, printf("\n"))
116 #else
117 # define DPRINT(x)
118 #endif
121 /* initialize with xtr(im,0); */
122 int
123 xtr(image_desc_t *im,time_t mytime){
124 static double pixie;
125 if (mytime==0){
126 pixie = (double) im->xsize / (double)(im->end - im->start);
127 return im->xorigin;
128 }
129 return (int)((double)im->xorigin
130 + pixie * ( mytime - im->start ) );
131 }
133 /* translate data values into y coordinates */
134 double
135 ytr(image_desc_t *im, double value){
136 static double pixie;
137 double yval;
138 if (isnan(value)){
139 if(!im->logarithmic)
140 pixie = (double) im->ysize / (im->maxval - im->minval);
141 else
142 pixie = (double) im->ysize / (log10(im->maxval) - log10(im->minval));
143 yval = im->yorigin;
144 } else if(!im->logarithmic) {
145 yval = im->yorigin - pixie * (value - im->minval);
146 } else {
147 if (value < im->minval) {
148 yval = im->yorigin;
149 } else {
150 yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
151 }
152 }
153 /* make sure we don't return anything too unreasonable. GD lib can
154 get terribly slow when drawing lines outside its scope. This is
155 especially problematic in connection with the rigid option */
156 if (! im->rigid) {
157 /* keep yval as-is */
158 } else if (yval > im->yorigin) {
159 yval = im->yorigin+2;
160 } else if (yval < im->yorigin - im->ysize){
161 yval = im->yorigin - im->ysize - 2;
162 }
163 return yval;
164 }
168 /* conversion function for symbolic entry names */
171 #define conv_if(VV,VVV) \
172 if (strcmp(#VV, string) == 0) return VVV ;
174 enum gf_en gf_conv(char *string){
176 conv_if(PRINT,GF_PRINT)
177 conv_if(GPRINT,GF_GPRINT)
178 conv_if(COMMENT,GF_COMMENT)
179 conv_if(HRULE,GF_HRULE)
180 conv_if(VRULE,GF_VRULE)
181 conv_if(LINE,GF_LINE)
182 conv_if(AREA,GF_AREA)
183 conv_if(STACK,GF_STACK)
184 conv_if(TICK,GF_TICK)
185 conv_if(DEF,GF_DEF)
186 conv_if(CDEF,GF_CDEF)
187 conv_if(VDEF,GF_VDEF)
188 conv_if(PART,GF_PART)
189 conv_if(XPORT,GF_XPORT)
191 return (-1);
192 }
194 enum gfx_if_en if_conv(char *string){
196 conv_if(PNG,IF_PNG)
197 conv_if(SVG,IF_SVG)
198 conv_if(EPS,IF_EPS)
199 conv_if(PDF,IF_PDF)
201 return (-1);
202 }
204 enum tmt_en tmt_conv(char *string){
206 conv_if(SECOND,TMT_SECOND)
207 conv_if(MINUTE,TMT_MINUTE)
208 conv_if(HOUR,TMT_HOUR)
209 conv_if(DAY,TMT_DAY)
210 conv_if(WEEK,TMT_WEEK)
211 conv_if(MONTH,TMT_MONTH)
212 conv_if(YEAR,TMT_YEAR)
213 return (-1);
214 }
216 enum grc_en grc_conv(char *string){
218 conv_if(BACK,GRC_BACK)
219 conv_if(CANVAS,GRC_CANVAS)
220 conv_if(SHADEA,GRC_SHADEA)
221 conv_if(SHADEB,GRC_SHADEB)
222 conv_if(GRID,GRC_GRID)
223 conv_if(MGRID,GRC_MGRID)
224 conv_if(FONT,GRC_FONT)
225 conv_if(FRAME,GRC_FRAME)
226 conv_if(ARROW,GRC_ARROW)
228 return -1;
229 }
231 enum text_prop_en text_prop_conv(char *string){
233 conv_if(DEFAULT,TEXT_PROP_DEFAULT)
234 conv_if(TITLE,TEXT_PROP_TITLE)
235 conv_if(AXIS,TEXT_PROP_AXIS)
236 conv_if(UNIT,TEXT_PROP_UNIT)
237 conv_if(LEGEND,TEXT_PROP_LEGEND)
238 return -1;
239 }
242 #undef conv_if
246 int
247 im_free(image_desc_t *im)
248 {
249 long i,ii;
250 if (im == NULL) return 0;
251 for(i=0;i<im->gdes_c;i++){
252 if (im->gdes[i].data_first){
253 /* careful here, because a single pointer can occur several times */
254 free (im->gdes[i].data);
255 if (im->gdes[i].ds_namv){
256 for (ii=0;ii<im->gdes[i].ds_cnt;ii++)
257 free(im->gdes[i].ds_namv[ii]);
258 free(im->gdes[i].ds_namv);
259 }
260 }
261 free (im->gdes[i].p_data);
262 free (im->gdes[i].rpnp);
263 }
264 free(im->gdes);
265 gfx_destroy(im->canvas);
266 return 0;
267 }
269 /* find SI magnitude symbol for the given number*/
270 void
271 auto_scale(
272 image_desc_t *im, /* image description */
273 double *value,
274 char **symb_ptr,
275 double *magfact
276 )
277 {
279 char *symbol[] = {"a", /* 10e-18 Atto */
280 "f", /* 10e-15 Femto */
281 "p", /* 10e-12 Pico */
282 "n", /* 10e-9 Nano */
283 "u", /* 10e-6 Micro */
284 "m", /* 10e-3 Milli */
285 " ", /* Base */
286 "k", /* 10e3 Kilo */
287 "M", /* 10e6 Mega */
288 "G", /* 10e9 Giga */
289 "T", /* 10e12 Tera */
290 "P", /* 10e15 Peta */
291 "E"};/* 10e18 Exa */
293 int symbcenter = 6;
294 int sindex;
296 if (*value == 0.0 || isnan(*value) ) {
297 sindex = 0;
298 *magfact = 1.0;
299 } else {
300 sindex = floor(log(fabs(*value))/log((double)im->base));
301 *magfact = pow((double)im->base, (double)sindex);
302 (*value) /= (*magfact);
303 }
304 if ( sindex <= symbcenter && sindex >= -symbcenter) {
305 (*symb_ptr) = symbol[sindex+symbcenter];
306 }
307 else {
308 (*symb_ptr) = "?";
309 }
310 }
313 /* find SI magnitude symbol for the numbers on the y-axis*/
314 void
315 si_unit(
316 image_desc_t *im /* image description */
317 )
318 {
320 char symbol[] = {'a', /* 10e-18 Atto */
321 'f', /* 10e-15 Femto */
322 'p', /* 10e-12 Pico */
323 'n', /* 10e-9 Nano */
324 'u', /* 10e-6 Micro */
325 'm', /* 10e-3 Milli */
326 ' ', /* Base */
327 'k', /* 10e3 Kilo */
328 'M', /* 10e6 Mega */
329 'G', /* 10e9 Giga */
330 'T', /* 10e12 Tera */
331 'P', /* 10e15 Peta */
332 'E'};/* 10e18 Exa */
334 int symbcenter = 6;
335 double digits;
337 if (im->unitsexponent != 9999) {
338 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
339 digits = floor(im->unitsexponent / 3);
340 } else {
341 digits = floor( log( max( fabs(im->minval),fabs(im->maxval)))/log((double)im->base));
342 }
343 im->magfact = pow((double)im->base , digits);
345 #ifdef DEBUG
346 printf("digits %6.3f im->magfact %6.3f\n",digits,im->magfact);
347 #endif
349 if ( ((digits+symbcenter) < sizeof(symbol)) &&
350 ((digits+symbcenter) >= 0) )
351 im->symbol = symbol[(int)digits+symbcenter];
352 else
353 im->symbol = ' ';
354 }
356 /* move min and max values around to become sensible */
358 void
359 expand_range(image_desc_t *im)
360 {
361 double sensiblevalues[] ={1000.0,900.0,800.0,750.0,700.0,
362 600.0,500.0,400.0,300.0,250.0,
363 200.0,125.0,100.0,90.0,80.0,
364 75.0,70.0,60.0,50.0,40.0,30.0,
365 25.0,20.0,10.0,9.0,8.0,
366 7.0,6.0,5.0,4.0,3.5,3.0,
367 2.5,2.0,1.8,1.5,1.2,1.0,
368 0.8,0.7,0.6,0.5,0.4,0.3,0.2,0.1,0.0,-1};
370 double scaled_min,scaled_max;
371 double adj;
372 int i;
376 #ifdef DEBUG
377 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
378 im->minval,im->maxval,im->magfact);
379 #endif
381 if (isnan(im->ygridstep)){
382 if(im->extra_flags & ALTAUTOSCALE) {
383 /* measure the amplitude of the function. Make sure that
384 graph boundaries are slightly higher then max/min vals
385 so we can see amplitude on the graph */
386 double delt, fact;
388 delt = im->maxval - im->minval;
389 adj = delt * 0.1;
390 fact = 2.0 * pow(10.0,
391 floor(log10(max(fabs(im->minval), fabs(im->maxval)))) - 2);
392 if (delt < fact) {
393 adj = (fact - delt) * 0.55;
394 #ifdef DEBUG
395 printf("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n", im->minval, im->maxval, delt, fact, adj);
396 #endif
397 }
398 im->minval -= adj;
399 im->maxval += adj;
400 }
401 else if(im->extra_flags & ALTAUTOSCALE_MAX) {
402 /* measure the amplitude of the function. Make sure that
403 graph boundaries are slightly higher than max vals
404 so we can see amplitude on the graph */
405 adj = (im->maxval - im->minval) * 0.1;
406 im->maxval += adj;
407 }
408 else {
409 scaled_min = im->minval / im->magfact;
410 scaled_max = im->maxval / im->magfact;
412 for (i=1; sensiblevalues[i] > 0; i++){
413 if (sensiblevalues[i-1]>=scaled_min &&
414 sensiblevalues[i]<=scaled_min)
415 im->minval = sensiblevalues[i]*(im->magfact);
417 if (-sensiblevalues[i-1]<=scaled_min &&
418 -sensiblevalues[i]>=scaled_min)
419 im->minval = -sensiblevalues[i-1]*(im->magfact);
421 if (sensiblevalues[i-1] >= scaled_max &&
422 sensiblevalues[i] <= scaled_max)
423 im->maxval = sensiblevalues[i-1]*(im->magfact);
425 if (-sensiblevalues[i-1]<=scaled_max &&
426 -sensiblevalues[i] >=scaled_max)
427 im->maxval = -sensiblevalues[i]*(im->magfact);
428 }
429 }
430 } else {
431 /* adjust min and max to the grid definition if there is one */
432 im->minval = (double)im->ylabfact * im->ygridstep *
433 floor(im->minval / ((double)im->ylabfact * im->ygridstep));
434 im->maxval = (double)im->ylabfact * im->ygridstep *
435 ceil(im->maxval /( (double)im->ylabfact * im->ygridstep));
436 }
438 #ifdef DEBUG
439 fprintf(stderr,"SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
440 im->minval,im->maxval,im->magfact);
441 #endif
442 }
444 void
445 apply_gridfit(image_desc_t *im)
446 {
447 if (isnan(im->minval) || isnan(im->maxval))
448 return;
449 ytr(im,DNAN);
450 if (im->logarithmic) {
451 double ya, yb, ypix, ypixfrac;
452 double log10_range = log10(im->maxval) - log10(im->minval);
453 ya = pow((double)10, floor(log10(im->minval)));
454 while (ya < im->minval)
455 ya *= 10;
456 if (ya > im->maxval)
457 return; /* don't have y=10^x gridline */
458 yb = ya * 10;
459 if (yb <= im->maxval) {
460 /* we have at least 2 y=10^x gridlines.
461 Make sure distance between them in pixels
462 are an integer by expanding im->maxval */
463 double y_pixel_delta = ytr(im, ya) - ytr(im, yb);
464 double factor = y_pixel_delta / floor(y_pixel_delta);
465 double new_log10_range = factor * log10_range;
466 double new_ymax_log10 = log10(im->minval) + new_log10_range;
467 im->maxval = pow(10, new_ymax_log10);
468 ytr(im, DNAN); /* reset precalc */
469 log10_range = log10(im->maxval) - log10(im->minval);
470 }
471 /* make sure first y=10^x gridline is located on
472 integer pixel position by moving scale slightly
473 downwards (sub-pixel movement) */
474 ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
475 ypixfrac = ypix - floor(ypix);
476 if (ypixfrac > 0 && ypixfrac < 1) {
477 double yfrac = ypixfrac / im->ysize;
478 im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
479 im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
480 ytr(im, DNAN); /* reset precalc */
481 }
482 } else {
483 /* Make sure we have an integer pixel distance between
484 each minor gridline */
485 double ypos1 = ytr(im, im->minval);
486 double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
487 double y_pixel_delta = ypos1 - ypos2;
488 double factor = y_pixel_delta / floor(y_pixel_delta);
489 double new_range = factor * (im->maxval - im->minval);
490 double gridstep = im->ygrid_scale.gridstep;
491 double minor_y, minor_y_px, minor_y_px_frac;
492 im->maxval = im->minval + new_range;
493 ytr(im, DNAN); /* reset precalc */
494 /* make sure first minor gridline is on integer pixel y coord */
495 minor_y = gridstep * floor(im->minval / gridstep);
496 while (minor_y < im->minval)
497 minor_y += gridstep;
498 minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
499 minor_y_px_frac = minor_y_px - floor(minor_y_px);
500 if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
501 double yfrac = minor_y_px_frac / im->ysize;
502 double range = im->maxval - im->minval;
503 im->minval = im->minval - yfrac * range;
504 im->maxval = im->maxval - yfrac * range;
505 ytr(im, DNAN); /* reset precalc */
506 }
507 calc_horizontal_grid(im); /* recalc with changed im->maxval */
508 }
509 }
511 /* reduce data reimplementation by Alex */
513 void
514 reduce_data(
515 enum cf_en cf, /* which consolidation function ?*/
516 unsigned long cur_step, /* step the data currently is in */
517 time_t *start, /* start, end and step as requested ... */
518 time_t *end, /* ... by the application will be ... */
519 unsigned long *step, /* ... adjusted to represent reality */
520 unsigned long *ds_cnt, /* number of data sources in file */
521 rrd_value_t **data) /* two dimensional array containing the data */
522 {
523 int i,reduce_factor = ceil((double)(*step) / (double)cur_step);
524 unsigned long col,dst_row,row_cnt,start_offset,end_offset,skiprows=0;
525 rrd_value_t *srcptr,*dstptr;
527 (*step) = cur_step*reduce_factor; /* set new step size for reduced data */
528 dstptr = *data;
529 srcptr = *data;
530 row_cnt = ((*end)-(*start))/cur_step;
532 #ifdef DEBUG
533 #define DEBUG_REDUCE
534 #endif
535 #ifdef DEBUG_REDUCE
536 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
537 row_cnt,reduce_factor,*start,*end,cur_step);
538 for (col=0;col<row_cnt;col++) {
539 printf("time %10lu: ",*start+(col+1)*cur_step);
540 for (i=0;i<*ds_cnt;i++)
541 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
542 printf("\n");
543 }
544 #endif
546 /* We have to combine [reduce_factor] rows of the source
547 ** into one row for the destination. Doing this we also
548 ** need to take care to combine the correct rows. First
549 ** alter the start and end time so that they are multiples
550 ** of the new step time. We cannot reduce the amount of
551 ** time so we have to move the end towards the future and
552 ** the start towards the past.
553 */
554 end_offset = (*end) % (*step);
555 start_offset = (*start) % (*step);
557 /* If there is a start offset (which cannot be more than
558 ** one destination row), skip the appropriate number of
559 ** source rows and one destination row. The appropriate
560 ** number is what we do know (start_offset/cur_step) of
561 ** the new interval (*step/cur_step aka reduce_factor).
562 */
563 #ifdef DEBUG_REDUCE
564 printf("start_offset: %lu end_offset: %lu\n",start_offset,end_offset);
565 printf("row_cnt before: %lu\n",row_cnt);
566 #endif
567 if (start_offset) {
568 (*start) = (*start)-start_offset;
569 skiprows=reduce_factor-start_offset/cur_step;
570 srcptr+=skiprows* *ds_cnt;
571 for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
572 row_cnt-=skiprows;
573 }
574 #ifdef DEBUG_REDUCE
575 printf("row_cnt between: %lu\n",row_cnt);
576 #endif
578 /* At the end we have some rows that are not going to be
579 ** used, the amount is end_offset/cur_step
580 */
581 if (end_offset) {
582 (*end) = (*end)-end_offset+(*step);
583 skiprows = end_offset/cur_step;
584 row_cnt-=skiprows;
585 }
586 #ifdef DEBUG_REDUCE
587 printf("row_cnt after: %lu\n",row_cnt);
588 #endif
590 /* Sanity check: row_cnt should be multiple of reduce_factor */
591 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
593 if (row_cnt%reduce_factor) {
594 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
595 row_cnt,reduce_factor);
596 printf("BUG in reduce_data()\n");
597 exit(1);
598 }
600 /* Now combine reduce_factor intervals at a time
601 ** into one interval for the destination.
602 */
604 for (dst_row=0;row_cnt>=reduce_factor;dst_row++) {
605 for (col=0;col<(*ds_cnt);col++) {
606 rrd_value_t newval=DNAN;
607 unsigned long validval=0;
609 for (i=0;i<reduce_factor;i++) {
610 if (isnan(srcptr[i*(*ds_cnt)+col])) {
611 continue;
612 }
613 validval++;
614 if (isnan(newval)) newval = srcptr[i*(*ds_cnt)+col];
615 else {
616 switch (cf) {
617 case CF_HWPREDICT:
618 case CF_DEVSEASONAL:
619 case CF_DEVPREDICT:
620 case CF_SEASONAL:
621 case CF_AVERAGE:
622 newval += srcptr[i*(*ds_cnt)+col];
623 break;
624 case CF_MINIMUM:
625 newval = min (newval,srcptr[i*(*ds_cnt)+col]);
626 break;
627 case CF_FAILURES:
628 /* an interval contains a failure if any subintervals contained a failure */
629 case CF_MAXIMUM:
630 newval = max (newval,srcptr[i*(*ds_cnt)+col]);
631 break;
632 case CF_LAST:
633 newval = srcptr[i*(*ds_cnt)+col];
634 break;
635 }
636 }
637 }
638 if (validval == 0){newval = DNAN;} else{
639 switch (cf) {
640 case CF_HWPREDICT:
641 case CF_DEVSEASONAL:
642 case CF_DEVPREDICT:
643 case CF_SEASONAL:
644 case CF_AVERAGE:
645 newval /= validval;
646 break;
647 case CF_MINIMUM:
648 case CF_FAILURES:
649 case CF_MAXIMUM:
650 case CF_LAST:
651 break;
652 }
653 }
654 *dstptr++=newval;
655 }
656 srcptr+=(*ds_cnt)*reduce_factor;
657 row_cnt-=reduce_factor;
658 }
659 /* If we had to alter the endtime, we didn't have enough
660 ** source rows to fill the last row. Fill it with NaN.
661 */
662 if (end_offset) for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
663 #ifdef DEBUG_REDUCE
664 row_cnt = ((*end)-(*start))/ *step;
665 srcptr = *data;
666 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
667 row_cnt,*start,*end,*step);
668 for (col=0;col<row_cnt;col++) {
669 printf("time %10lu: ",*start+(col+1)*(*step));
670 for (i=0;i<*ds_cnt;i++)
671 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
672 printf("\n");
673 }
674 #endif
675 }
678 /* get the data required for the graphs from the
679 relevant rrds ... */
681 int
682 data_fetch( image_desc_t *im )
683 {
684 int i,ii;
685 int skip;
687 /* pull the data from the log files ... */
688 for (i=0;i<im->gdes_c;i++){
689 /* only GF_DEF elements fetch data */
690 if (im->gdes[i].gf != GF_DEF)
691 continue;
693 skip=0;
694 /* do we have it already ?*/
695 for (ii=0;ii<i;ii++) {
696 if (im->gdes[ii].gf != GF_DEF)
697 continue;
698 if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
699 && (im->gdes[i].cf == im->gdes[ii].cf)
700 && (im->gdes[i].start == im->gdes[ii].start)
701 && (im->gdes[i].end == im->gdes[ii].end)
702 && (im->gdes[i].step == im->gdes[ii].step)) {
703 /* OK, the data is already there.
704 ** Just copy the header portion
705 */
706 im->gdes[i].start = im->gdes[ii].start;
707 im->gdes[i].end = im->gdes[ii].end;
708 im->gdes[i].step = im->gdes[ii].step;
709 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
710 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
711 im->gdes[i].data = im->gdes[ii].data;
712 im->gdes[i].data_first = 0;
713 skip=1;
714 }
715 if (skip)
716 break;
717 }
718 if (! skip) {
719 unsigned long ft_step = im->gdes[i].step ;
721 if((rrd_fetch_fn(im->gdes[i].rrd,
722 im->gdes[i].cf,
723 &im->gdes[i].start,
724 &im->gdes[i].end,
725 &ft_step,
726 &im->gdes[i].ds_cnt,
727 &im->gdes[i].ds_namv,
728 &im->gdes[i].data)) == -1){
729 return -1;
730 }
731 im->gdes[i].data_first = 1;
733 if (ft_step < im->gdes[i].step) {
734 reduce_data(im->gdes[i].cf,
735 ft_step,
736 &im->gdes[i].start,
737 &im->gdes[i].end,
738 &im->gdes[i].step,
739 &im->gdes[i].ds_cnt,
740 &im->gdes[i].data);
741 } else {
742 im->gdes[i].step = ft_step;
743 }
744 }
746 /* lets see if the required data source is realy there */
747 for(ii=0;ii<im->gdes[i].ds_cnt;ii++){
748 if(strcmp(im->gdes[i].ds_namv[ii],im->gdes[i].ds_nam) == 0){
749 im->gdes[i].ds=ii; }
750 }
751 if (im->gdes[i].ds== -1){
752 rrd_set_error("No DS called '%s' in '%s'",
753 im->gdes[i].ds_nam,im->gdes[i].rrd);
754 return -1;
755 }
757 }
758 return 0;
759 }
761 /* evaluate the expressions in the CDEF functions */
763 /*************************************************************
764 * CDEF stuff
765 *************************************************************/
767 long
768 find_var_wrapper(void *arg1, char *key)
769 {
770 return find_var((image_desc_t *) arg1, key);
771 }
773 /* find gdes containing var*/
774 long
775 find_var(image_desc_t *im, char *key){
776 long ii;
777 for(ii=0;ii<im->gdes_c-1;ii++){
778 if((im->gdes[ii].gf == GF_DEF
779 || im->gdes[ii].gf == GF_VDEF
780 || im->gdes[ii].gf == GF_CDEF)
781 && (strcmp(im->gdes[ii].vname,key) == 0)){
782 return ii;
783 }
784 }
785 return -1;
786 }
788 /* find the largest common denominator for all the numbers
789 in the 0 terminated num array */
790 long
791 lcd(long *num){
792 long rest;
793 int i;
794 for (i=0;num[i+1]!=0;i++){
795 do {
796 rest=num[i] % num[i+1];
797 num[i]=num[i+1]; num[i+1]=rest;
798 } while (rest!=0);
799 num[i+1] = num[i];
800 }
801 /* return i==0?num[i]:num[i-1]; */
802 return num[i];
803 }
805 /* run the rpn calculator on all the VDEF and CDEF arguments */
806 int
807 data_calc( image_desc_t *im){
809 int gdi;
810 int dataidx;
811 long *steparray, rpi;
812 int stepcnt;
813 time_t now;
814 rpnstack_t rpnstack;
816 rpnstack_init(&rpnstack);
818 for (gdi=0;gdi<im->gdes_c;gdi++){
819 /* Look for GF_VDEF and GF_CDEF in the same loop,
820 * so CDEFs can use VDEFs and vice versa
821 */
822 switch (im->gdes[gdi].gf) {
823 case GF_XPORT:
824 break;
825 case GF_VDEF:
826 /* A VDEF has no DS. This also signals other parts
827 * of rrdtool that this is a VDEF value, not a CDEF.
828 */
829 im->gdes[gdi].ds_cnt = 0;
830 if (vdef_calc(im,gdi)) {
831 rrd_set_error("Error processing VDEF '%s'"
832 ,im->gdes[gdi].vname
833 );
834 rpnstack_free(&rpnstack);
835 return -1;
836 }
837 break;
838 case GF_CDEF:
839 im->gdes[gdi].ds_cnt = 1;
840 im->gdes[gdi].ds = 0;
841 im->gdes[gdi].data_first = 1;
842 im->gdes[gdi].start = 0;
843 im->gdes[gdi].end = 0;
844 steparray=NULL;
845 stepcnt = 0;
846 dataidx=-1;
848 /* Find the variables in the expression.
849 * - VDEF variables are substituted by their values
850 * and the opcode is changed into OP_NUMBER.
851 * - CDEF variables are analized for their step size,
852 * the lowest common denominator of all the step
853 * sizes of the data sources involved is calculated
854 * and the resulting number is the step size for the
855 * resulting data source.
856 */
857 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
858 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE){
859 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
860 if (im->gdes[ptr].ds_cnt == 0) {
861 #if 0
862 printf("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
863 im->gdes[gdi].vname,
864 im->gdes[ptr].vname);
865 printf("DEBUG: value from vdef is %f\n",im->gdes[ptr].vf.val);
866 #endif
867 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
868 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
869 } else {
870 if ((steparray = rrd_realloc(steparray, (++stepcnt+1)*sizeof(*steparray)))==NULL){
871 rrd_set_error("realloc steparray");
872 rpnstack_free(&rpnstack);
873 return -1;
874 };
876 steparray[stepcnt-1] = im->gdes[ptr].step;
878 /* adjust start and end of cdef (gdi) so
879 * that it runs from the latest start point
880 * to the earliest endpoint of any of the
881 * rras involved (ptr)
882 */
883 if(im->gdes[gdi].start < im->gdes[ptr].start)
884 im->gdes[gdi].start = im->gdes[ptr].start;
886 if(im->gdes[gdi].end == 0 ||
887 im->gdes[gdi].end > im->gdes[ptr].end)
888 im->gdes[gdi].end = im->gdes[ptr].end;
890 /* store pointer to the first element of
891 * the rra providing data for variable,
892 * further save step size and data source
893 * count of this rra
894 */
895 im->gdes[gdi].rpnp[rpi].data = im->gdes[ptr].data + im->gdes[ptr].ds;
896 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
897 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
899 /* backoff the *.data ptr; this is done so
900 * rpncalc() function doesn't have to treat
901 * the first case differently
902 */
903 } /* if ds_cnt != 0 */
904 } /* if OP_VARIABLE */
905 } /* loop through all rpi */
907 /* move the data pointers to the correct period */
908 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
909 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE){
910 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
911 if(im->gdes[gdi].start > im->gdes[ptr].start) {
912 im->gdes[gdi].rpnp[rpi].data += im->gdes[gdi].rpnp[rpi].ds_cnt;
913 }
914 }
915 }
918 if(steparray == NULL){
919 rrd_set_error("rpn expressions without DEF"
920 " or CDEF variables are not supported");
921 rpnstack_free(&rpnstack);
922 return -1;
923 }
924 steparray[stepcnt]=0;
925 /* Now find the resulting step. All steps in all
926 * used RRAs have to be visited
927 */
928 im->gdes[gdi].step = lcd(steparray);
929 free(steparray);
930 if((im->gdes[gdi].data = malloc((
931 (im->gdes[gdi].end-im->gdes[gdi].start)
932 / im->gdes[gdi].step)
933 * sizeof(double)))==NULL){
934 rrd_set_error("malloc im->gdes[gdi].data");
935 rpnstack_free(&rpnstack);
936 return -1;
937 }
939 /* Step through the new cdef results array and
940 * calculate the values
941 */
942 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
943 now<=im->gdes[gdi].end;
944 now += im->gdes[gdi].step)
945 {
946 rpnp_t *rpnp = im -> gdes[gdi].rpnp;
948 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
949 * in this case we are advancing by timesteps;
950 * we use the fact that time_t is a synonym for long
951 */
952 if (rpn_calc(rpnp,&rpnstack,(long) now,
953 im->gdes[gdi].data,++dataidx) == -1) {
954 /* rpn_calc sets the error string */
955 rpnstack_free(&rpnstack);
956 return -1;
957 }
958 } /* enumerate over time steps within a CDEF */
959 break;
960 default:
961 continue;
962 }
963 } /* enumerate over CDEFs */
964 rpnstack_free(&rpnstack);
965 return 0;
966 }
968 /* massage data so, that we get one value for each x coordinate in the graph */
969 int
970 data_proc( image_desc_t *im ){
971 long i,ii;
972 double pixstep = (double)(im->end-im->start)
973 /(double)im->xsize; /* how much time
974 passes in one pixel */
975 double paintval;
976 double minval=DNAN,maxval=DNAN;
978 unsigned long gr_time;
980 /* memory for the processed data */
981 for(i=0;i<im->gdes_c;i++) {
982 if((im->gdes[i].gf==GF_LINE) ||
983 (im->gdes[i].gf==GF_AREA) ||
984 (im->gdes[i].gf==GF_TICK) ||
985 (im->gdes[i].gf==GF_STACK)) {
986 if((im->gdes[i].p_data = malloc((im->xsize +1)
987 * sizeof(rrd_value_t)))==NULL){
988 rrd_set_error("malloc data_proc");
989 return -1;
990 }
991 }
992 }
994 for (i=0;i<im->xsize;i++) { /* for each pixel */
995 long vidx;
996 gr_time = im->start+pixstep*i; /* time of the current step */
997 paintval=0.0;
999 for (ii=0;ii<im->gdes_c;ii++) {
1000 double value;
1001 switch (im->gdes[ii].gf) {
1002 case GF_LINE:
1003 case GF_AREA:
1004 case GF_TICK:
1005 if (!im->gdes[ii].stack)
1006 paintval = 0.0;
1007 case GF_STACK:
1008 /* The time of the data doesn't necessarily match
1009 ** the time of the graph. Beware.
1010 */
1011 vidx = im->gdes[ii].vidx;
1012 if ( (gr_time >= im->gdes[vidx].start) &&
1013 (gr_time <= im->gdes[vidx].end) ) {
1014 value = im->gdes[vidx].data[
1015 (unsigned long) floor(
1016 (double)(gr_time - im->gdes[vidx].start)
1017 / im->gdes[vidx].step)
1018 * im->gdes[vidx].ds_cnt
1019 + im->gdes[vidx].ds
1020 ];
1021 } else {
1022 value = DNAN;
1023 }
1025 if (! isnan(value)) {
1026 paintval += value;
1027 im->gdes[ii].p_data[i] = paintval;
1028 /* GF_TICK: the data values are not
1029 ** relevant for min and max
1030 */
1031 if (finite(paintval) && im->gdes[ii].gf != GF_TICK ) {
1032 if (isnan(minval) || paintval < minval)
1033 minval = paintval;
1034 if (isnan(maxval) || paintval > maxval)
1035 maxval = paintval;
1036 }
1037 } else {
1038 im->gdes[ii].p_data[i] = DNAN;
1039 }
1040 break;
1041 default:
1042 break;
1043 }
1044 }
1045 }
1047 /* if min or max have not been asigned a value this is because
1048 there was no data in the graph ... this is not good ...
1049 lets set these to dummy values then ... */
1051 if (isnan(minval)) minval = 0.0;
1052 if (isnan(maxval)) maxval = 1.0;
1054 /* adjust min and max values */
1055 if (isnan(im->minval)
1056 /* don't adjust low-end with log scale */
1057 || ((!im->logarithmic && !im->rigid) && im->minval > minval)
1058 )
1059 im->minval = minval;
1060 if (isnan(im->maxval)
1061 || (!im->rigid && im->maxval < maxval)
1062 ) {
1063 if (im->logarithmic)
1064 im->maxval = maxval * 1.1;
1065 else
1066 im->maxval = maxval;
1067 }
1068 /* make sure min and max are not equal */
1069 if (im->minval == im->maxval) {
1070 im->maxval *= 1.01;
1071 if (! im->logarithmic) {
1072 im->minval *= 0.99;
1073 }
1074 /* make sure min and max are not both zero */
1075 if (im->maxval == 0.0) {
1076 im->maxval = 1.0;
1077 }
1078 }
1079 return 0;
1080 }
1084 /* identify the point where the first gridline, label ... gets placed */
1086 time_t
1087 find_first_time(
1088 time_t start, /* what is the initial time */
1089 enum tmt_en baseint, /* what is the basic interval */
1090 long basestep /* how many if these do we jump a time */
1091 )
1092 {
1093 struct tm tm;
1094 tm = *localtime(&start);
1095 switch(baseint){
1096 case TMT_SECOND:
1097 tm.tm_sec -= tm.tm_sec % basestep; break;
1098 case TMT_MINUTE:
1099 tm.tm_sec=0;
1100 tm.tm_min -= tm.tm_min % basestep;
1101 break;
1102 case TMT_HOUR:
1103 tm.tm_sec=0;
1104 tm.tm_min = 0;
1105 tm.tm_hour -= tm.tm_hour % basestep; break;
1106 case TMT_DAY:
1107 /* we do NOT look at the basestep for this ... */
1108 tm.tm_sec=0;
1109 tm.tm_min = 0;
1110 tm.tm_hour = 0; break;
1111 case TMT_WEEK:
1112 /* we do NOT look at the basestep for this ... */
1113 tm.tm_sec=0;
1114 tm.tm_min = 0;
1115 tm.tm_hour = 0;
1116 tm.tm_mday -= tm.tm_wday -1; /* -1 because we want the monday */
1117 if (tm.tm_wday==0) tm.tm_mday -= 7; /* we want the *previous* monday */
1118 break;
1119 case TMT_MONTH:
1120 tm.tm_sec=0;
1121 tm.tm_min = 0;
1122 tm.tm_hour = 0;
1123 tm.tm_mday = 1;
1124 tm.tm_mon -= tm.tm_mon % basestep; break;
1126 case TMT_YEAR:
1127 tm.tm_sec=0;
1128 tm.tm_min = 0;
1129 tm.tm_hour = 0;
1130 tm.tm_mday = 1;
1131 tm.tm_mon = 0;
1132 tm.tm_year -= (tm.tm_year+1900) % basestep;
1134 }
1135 return mktime(&tm);
1136 }
1137 /* identify the point where the next gridline, label ... gets placed */
1138 time_t
1139 find_next_time(
1140 time_t current, /* what is the initial time */
1141 enum tmt_en baseint, /* what is the basic interval */
1142 long basestep /* how many if these do we jump a time */
1143 )
1144 {
1145 struct tm tm;
1146 time_t madetime;
1147 tm = *localtime(¤t);
1148 do {
1149 switch(baseint){
1150 case TMT_SECOND:
1151 tm.tm_sec += basestep; break;
1152 case TMT_MINUTE:
1153 tm.tm_min += basestep; break;
1154 case TMT_HOUR:
1155 tm.tm_hour += basestep; break;
1156 case TMT_DAY:
1157 tm.tm_mday += basestep; break;
1158 case TMT_WEEK:
1159 tm.tm_mday += 7*basestep; break;
1160 case TMT_MONTH:
1161 tm.tm_mon += basestep; break;
1162 case TMT_YEAR:
1163 tm.tm_year += basestep;
1164 }
1165 madetime = mktime(&tm);
1166 } while (madetime == -1); /* this is necessary to skip impssible times
1167 like the daylight saving time skips */
1168 return madetime;
1170 }
1173 /* calculate values required for PRINT and GPRINT functions */
1175 int
1176 print_calc(image_desc_t *im, char ***prdata)
1177 {
1178 long i,ii,validsteps;
1179 double printval;
1180 time_t printtime;
1181 int graphelement = 0;
1182 long vidx;
1183 int max_ii;
1184 double magfact = -1;
1185 char *si_symb = "";
1186 char *percent_s;
1187 int prlines = 1;
1188 if (im->imginfo) prlines++;
1189 for(i=0;i<im->gdes_c;i++){
1190 switch(im->gdes[i].gf){
1191 case GF_PRINT:
1192 prlines++;
1193 if(((*prdata) = rrd_realloc((*prdata),prlines*sizeof(char *)))==NULL){
1194 rrd_set_error("realloc prdata");
1195 return 0;
1196 }
1197 case GF_GPRINT:
1198 /* PRINT and GPRINT can now print VDEF generated values.
1199 * There's no need to do any calculations on them as these
1200 * calculations were already made.
1201 */
1202 vidx = im->gdes[i].vidx;
1203 if (im->gdes[vidx].gf==GF_VDEF) { /* simply use vals */
1204 printval = im->gdes[vidx].vf.val;
1205 printtime = im->gdes[vidx].vf.when;
1206 } else { /* need to calculate max,min,avg etcetera */
1207 max_ii =((im->gdes[vidx].end
1208 - im->gdes[vidx].start)
1209 / im->gdes[vidx].step
1210 * im->gdes[vidx].ds_cnt);
1211 printval = DNAN;
1212 validsteps = 0;
1213 for( ii=im->gdes[vidx].ds;
1214 ii < max_ii;
1215 ii+=im->gdes[vidx].ds_cnt){
1216 if (! finite(im->gdes[vidx].data[ii]))
1217 continue;
1218 if (isnan(printval)){
1219 printval = im->gdes[vidx].data[ii];
1220 validsteps++;
1221 continue;
1222 }
1224 switch (im->gdes[i].cf){
1225 case CF_HWPREDICT:
1226 case CF_DEVPREDICT:
1227 case CF_DEVSEASONAL:
1228 case CF_SEASONAL:
1229 case CF_AVERAGE:
1230 validsteps++;
1231 printval += im->gdes[vidx].data[ii];
1232 break;
1233 case CF_MINIMUM:
1234 printval = min( printval, im->gdes[vidx].data[ii]);
1235 break;
1236 case CF_FAILURES:
1237 case CF_MAXIMUM:
1238 printval = max( printval, im->gdes[vidx].data[ii]);
1239 break;
1240 case CF_LAST:
1241 printval = im->gdes[vidx].data[ii];
1242 }
1243 }
1244 if (im->gdes[i].cf==CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1245 if (validsteps > 1) {
1246 printval = (printval / validsteps);
1247 }
1248 }
1249 } /* prepare printval */
1251 if (!strcmp(im->gdes[i].format,"%c")) { /* VDEF time print */
1252 if (im->gdes[i].gf == GF_PRINT){
1253 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1254 sprintf((*prdata)[prlines-2],"%s (%lu)",
1255 ctime(&printtime),printtime);
1256 (*prdata)[prlines-1] = NULL;
1257 } else {
1258 sprintf(im->gdes[i].legend,"%s (%lu)",
1259 ctime(&printtime),printtime);
1260 graphelement = 1;
1261 }
1262 } else {
1263 if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) {
1264 /* Magfact is set to -1 upon entry to print_calc. If it
1265 * is still less than 0, then we need to run auto_scale.
1266 * Otherwise, put the value into the correct units. If
1267 * the value is 0, then do not set the symbol or magnification
1268 * so next the calculation will be performed again. */
1269 if (magfact < 0.0) {
1270 auto_scale(im,&printval,&si_symb,&magfact);
1271 if (printval == 0.0)
1272 magfact = -1.0;
1273 } else {
1274 printval /= magfact;
1275 }
1276 *(++percent_s) = 's';
1277 } else if (strstr(im->gdes[i].format,"%s") != NULL) {
1278 auto_scale(im,&printval,&si_symb,&magfact);
1279 }
1281 if (im->gdes[i].gf == GF_PRINT){
1282 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1283 if (bad_format(im->gdes[i].format)) {
1284 rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1285 return -1;
1286 }
1287 #ifdef HAVE_SNPRINTF
1288 snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
1289 #else
1290 sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
1291 #endif
1292 (*prdata)[prlines-1] = NULL;
1293 } else {
1294 /* GF_GPRINT */
1296 if (bad_format(im->gdes[i].format)) {
1297 rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1298 return -1;
1299 }
1300 #ifdef HAVE_SNPRINTF
1301 snprintf(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
1302 #else
1303 sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
1304 #endif
1305 graphelement = 1;
1306 }
1307 }
1308 break;
1309 case GF_LINE:
1310 case GF_AREA:
1311 case GF_TICK:
1312 case GF_STACK:
1313 case GF_HRULE:
1314 case GF_VRULE:
1315 graphelement = 1;
1316 break;
1317 case GF_COMMENT:
1318 case GF_DEF:
1319 case GF_CDEF:
1320 case GF_VDEF:
1321 case GF_PART:
1322 case GF_XPORT:
1323 break;
1324 }
1325 }
1326 return graphelement;
1327 }
1330 /* place legends with color spots */
1331 int
1332 leg_place(image_desc_t *im)
1333 {
1334 /* graph labels */
1335 int interleg = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1336 int box =im->text_prop[TEXT_PROP_LEGEND].size*1.5;
1337 int border = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1338 int fill=0, fill_last;
1339 int leg_c = 0;
1340 int leg_x = border, leg_y = im->yimg;
1341 int leg_cc;
1342 int glue = 0;
1343 int i,ii, mark = 0;
1344 char prt_fctn; /*special printfunctions */
1345 int *legspace;
1347 if( !(im->extra_flags & NOLEGEND) ) {
1348 if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1349 rrd_set_error("malloc for legspace");
1350 return -1;
1351 }
1353 for(i=0;i<im->gdes_c;i++){
1354 fill_last = fill;
1356 leg_cc = strlen(im->gdes[i].legend);
1358 /* is there a controle code ant the end of the legend string ? */
1359 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\') {
1360 prt_fctn = im->gdes[i].legend[leg_cc-1];
1361 leg_cc -= 2;
1362 im->gdes[i].legend[leg_cc] = '\0';
1363 } else {
1364 prt_fctn = '\0';
1365 }
1366 /* remove exess space */
1367 while (prt_fctn=='g' &&
1368 leg_cc > 0 &&
1369 im->gdes[i].legend[leg_cc-1]==' '){
1370 leg_cc--;
1371 im->gdes[i].legend[leg_cc]='\0';
1372 }
1373 if (leg_cc != 0 ){
1374 legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1376 if (fill > 0){
1377 /* no interleg space if string ends in \g */
1378 fill += legspace[i];
1379 }
1380 if (im->gdes[i].gf != GF_GPRINT &&
1381 im->gdes[i].gf != GF_COMMENT) {
1382 fill += box;
1383 }
1384 fill += gfx_get_text_width(im->canvas, fill+border,
1385 im->text_prop[TEXT_PROP_LEGEND].font,
1386 im->text_prop[TEXT_PROP_LEGEND].size,
1387 im->tabwidth,
1388 im->gdes[i].legend);
1389 leg_c++;
1390 } else {
1391 legspace[i]=0;
1392 }
1393 /* who said there was a special tag ... ?*/
1394 if (prt_fctn=='g') {
1395 prt_fctn = '\0';
1396 }
1397 if (prt_fctn == '\0') {
1398 if (i == im->gdes_c -1 ) prt_fctn ='l';
1400 /* is it time to place the legends ? */
1401 if (fill > im->ximg - 2*border){
1402 if (leg_c > 1) {
1403 /* go back one */
1404 i--;
1405 fill = fill_last;
1406 leg_c--;
1407 prt_fctn = 'j';
1408 } else {
1409 prt_fctn = 'l';
1410 }
1412 }
1413 }
1416 if (prt_fctn != '\0'){
1417 leg_x = border;
1418 if (leg_c >= 2 && prt_fctn == 'j') {
1419 glue = (im->ximg - fill - 2* border) / (leg_c-1);
1420 } else {
1421 glue = 0;
1422 }
1423 if (prt_fctn =='c') leg_x = (im->ximg - fill) / 2.0;
1424 if (prt_fctn =='r') leg_x = im->ximg - fill - border;
1426 for(ii=mark;ii<=i;ii++){
1427 if(im->gdes[ii].legend[0]=='\0')
1428 continue;
1429 im->gdes[ii].leg_x = leg_x;
1430 im->gdes[ii].leg_y = leg_y;
1431 leg_x +=
1432 gfx_get_text_width(im->canvas, leg_x,
1433 im->text_prop[TEXT_PROP_LEGEND].font,
1434 im->text_prop[TEXT_PROP_LEGEND].size,
1435 im->tabwidth,
1436 im->gdes[ii].legend)
1437 + legspace[ii]
1438 + glue;
1439 if (im->gdes[ii].gf != GF_GPRINT &&
1440 im->gdes[ii].gf != GF_COMMENT)
1441 leg_x += box;
1442 }
1443 leg_y = leg_y + im->text_prop[TEXT_PROP_LEGEND].size*1.2;
1444 if (prt_fctn == 's') leg_y -= im->text_prop[TEXT_PROP_LEGEND].size*1.2;
1445 fill = 0;
1446 leg_c = 0;
1447 mark = ii;
1448 }
1449 }
1450 im->yimg = leg_y;
1451 free(legspace);
1452 }
1453 return 0;
1454 }
1456 /* create a grid on the graph. it determines what to do
1457 from the values of xsize, start and end */
1459 /* the xaxis labels are determined from the number of seconds per pixel
1460 in the requested graph */
1464 int
1465 calc_horizontal_grid(image_desc_t *im)
1466 {
1467 double range;
1468 double scaledrange;
1469 int pixel,i;
1470 int gridind;
1471 int decimals, fractionals;
1473 im->ygrid_scale.labfact=2;
1474 gridind=-1;
1475 range = im->maxval - im->minval;
1476 scaledrange = range / im->magfact;
1478 /* does the scale of this graph make it impossible to put lines
1479 on it? If so, give up. */
1480 if (isnan(scaledrange)) {
1481 return 0;
1482 }
1484 /* find grid spaceing */
1485 pixel=1;
1486 if(isnan(im->ygridstep)){
1487 if(im->extra_flags & ALTYGRID) {
1488 /* find the value with max number of digits. Get number of digits */
1489 decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))));
1490 if(decimals <= 0) /* everything is small. make place for zero */
1491 decimals = 1;
1493 fractionals = floor(log10(range));
1494 if(fractionals < 0) /* small amplitude. */
1495 sprintf(im->ygrid_scale.labfmt, "%%%d.%df", decimals - fractionals + 1, -fractionals + 1);
1496 else
1497 sprintf(im->ygrid_scale.labfmt, "%%%d.1f", decimals + 1);
1498 im->ygrid_scale.gridstep = pow((double)10, (double)fractionals);
1499 if(im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1500 im->ygrid_scale.gridstep = 0.1;
1501 /* should have at least 5 lines but no more then 15 */
1502 if(range/im->ygrid_scale.gridstep < 5)
1503 im->ygrid_scale.gridstep /= 10;
1504 if(range/im->ygrid_scale.gridstep > 15)
1505 im->ygrid_scale.gridstep *= 10;
1506 if(range/im->ygrid_scale.gridstep > 5) {
1507 im->ygrid_scale.labfact = 1;
1508 if(range/im->ygrid_scale.gridstep > 8)
1509 im->ygrid_scale.labfact = 2;
1510 }
1511 else {
1512 im->ygrid_scale.gridstep /= 5;
1513 im->ygrid_scale.labfact = 5;
1514 }
1515 }
1516 else {
1517 for(i=0;ylab[i].grid > 0;i++){
1518 pixel = im->ysize / (scaledrange / ylab[i].grid);
1519 if (gridind == -1 && pixel > 5) {
1520 gridind = i;
1521 break;
1522 }
1523 }
1525 for(i=0; i<4;i++) {
1526 if (pixel * ylab[gridind].lfac[i] >= 2 * im->text_prop[TEXT_PROP_AXIS].size) {
1527 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1528 break;
1529 }
1530 }
1532 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1533 }
1534 } else {
1535 im->ygrid_scale.gridstep = im->ygridstep;
1536 im->ygrid_scale.labfact = im->ylabfact;
1537 }
1538 return 1;
1539 }
1541 int draw_horizontal_grid(image_desc_t *im)
1542 {
1543 int i;
1544 double scaledstep;
1545 char graph_label[100];
1546 double X0=im->xorigin;
1547 double X1=im->xorigin+im->xsize;
1549 int sgrid = (int)( im->minval / im->ygrid_scale.gridstep - 1);
1550 int egrid = (int)( im->maxval / im->ygrid_scale.gridstep + 1);
1551 scaledstep = im->ygrid_scale.gridstep/im->magfact;
1552 for (i = sgrid; i <= egrid; i++){
1553 double Y0=ytr(im,im->ygrid_scale.gridstep*i);
1554 if ( Y0 >= im->yorigin-im->ysize
1555 && Y0 <= im->yorigin){
1556 if(i % im->ygrid_scale.labfact == 0){
1557 if (i==0 || im->symbol == ' ') {
1558 if(scaledstep < 1){
1559 if(im->extra_flags & ALTYGRID) {
1560 sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*i);
1561 }
1562 else {
1563 sprintf(graph_label,"%4.1f",scaledstep*i);
1564 }
1565 } else {
1566 sprintf(graph_label,"%4.0f",scaledstep*i);
1567 }
1568 }else {
1569 if(scaledstep < 1){
1570 sprintf(graph_label,"%4.1f %c",scaledstep*i, im->symbol);
1571 } else {
1572 sprintf(graph_label,"%4.0f %c",scaledstep*i, im->symbol);
1573 }
1574 }
1576 gfx_new_text ( im->canvas,
1577 X0-im->text_prop[TEXT_PROP_AXIS].size/1.5, Y0,
1578 im->graph_col[GRC_FONT],
1579 im->text_prop[TEXT_PROP_AXIS].font,
1580 im->text_prop[TEXT_PROP_AXIS].size,
1581 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1582 graph_label );
1583 gfx_new_dashed_line ( im->canvas,
1584 X0-2,Y0,
1585 X1+2,Y0,
1586 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1587 im->grid_dash_on, im->grid_dash_off);
1589 } else {
1590 gfx_new_dashed_line ( im->canvas,
1591 X0-1,Y0,
1592 X1+1,Y0,
1593 GRIDWIDTH, im->graph_col[GRC_GRID],
1594 im->grid_dash_on, im->grid_dash_off);
1596 }
1597 }
1598 }
1599 return 1;
1600 }
1602 /* logaritmic horizontal grid */
1603 int
1604 horizontal_log_grid(image_desc_t *im)
1605 {
1606 double pixpex;
1607 int ii,i;
1608 int minoridx=0, majoridx=0;
1609 char graph_label[100];
1610 double X0,X1,Y0;
1611 double value, pixperstep, minstep;
1613 /* find grid spaceing */
1614 pixpex= (double)im->ysize / (log10(im->maxval) - log10(im->minval));
1616 if (isnan(pixpex)) {
1617 return 0;
1618 }
1620 for(i=0;yloglab[i][0] > 0;i++){
1621 minstep = log10(yloglab[i][0]);
1622 for(ii=1;yloglab[i][ii+1] > 0;ii++){
1623 if(yloglab[i][ii+2]==0){
1624 minstep = log10(yloglab[i][ii+1])-log10(yloglab[i][ii]);
1625 break;
1626 }
1627 }
1628 pixperstep = pixpex * minstep;
1629 if(pixperstep > 5){minoridx = i;}
1630 if(pixperstep > 2 * im->text_prop[TEXT_PROP_LEGEND].size){majoridx = i;}
1631 }
1633 X0=im->xorigin;
1634 X1=im->xorigin+im->xsize;
1635 /* paint minor grid */
1636 for (value = pow((double)10, log10(im->minval)
1637 - fmod(log10(im->minval),log10(yloglab[minoridx][0])));
1638 value <= im->maxval;
1639 value *= yloglab[minoridx][0]){
1640 if (value < im->minval) continue;
1641 i=0;
1642 while(yloglab[minoridx][++i] > 0){
1643 Y0 = ytr(im,value * yloglab[minoridx][i]);
1644 if (Y0 <= im->yorigin - im->ysize) break;
1645 gfx_new_dashed_line ( im->canvas,
1646 X0-1,Y0,
1647 X1+1,Y0,
1648 GRIDWIDTH, im->graph_col[GRC_GRID],
1649 im->grid_dash_on, im->grid_dash_off);
1650 }
1651 }
1653 /* paint major grid and labels*/
1654 for (value = pow((double)10, log10(im->minval)
1655 - fmod(log10(im->minval),log10(yloglab[majoridx][0])));
1656 value <= im->maxval;
1657 value *= yloglab[majoridx][0]){
1658 if (value < im->minval) continue;
1659 i=0;
1660 while(yloglab[majoridx][++i] > 0){
1661 Y0 = ytr(im,value * yloglab[majoridx][i]);
1662 if (Y0 <= im->yorigin - im->ysize) break;
1663 gfx_new_dashed_line ( im->canvas,
1664 X0-2,Y0,
1665 X1+2,Y0,
1666 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1667 im->grid_dash_on, im->grid_dash_off);
1669 sprintf(graph_label,"%3.0e",value * yloglab[majoridx][i]);
1670 gfx_new_text ( im->canvas,
1671 X0-im->text_prop[TEXT_PROP_AXIS].size/1.5, Y0,
1672 im->graph_col[GRC_FONT],
1673 im->text_prop[TEXT_PROP_AXIS].font,
1674 im->text_prop[TEXT_PROP_AXIS].size,
1675 im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1676 graph_label );
1677 }
1678 }
1679 return 1;
1680 }
1683 void
1684 vertical_grid(
1685 image_desc_t *im )
1686 {
1687 int xlab_sel; /* which sort of label and grid ? */
1688 time_t ti, tilab, timajor;
1689 long factor;
1690 char graph_label[100];
1691 double X0,Y0,Y1; /* points for filled graph and more*/
1694 /* the type of time grid is determined by finding
1695 the number of seconds per pixel in the graph */
1698 if(im->xlab_user.minsec == -1){
1699 factor=(im->end - im->start)/im->xsize;
1700 xlab_sel=0;
1701 while ( xlab[xlab_sel+1].minsec != -1
1702 && xlab[xlab_sel+1].minsec <= factor){ xlab_sel++; }
1703 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
1704 im->xlab_user.gridst = xlab[xlab_sel].gridst;
1705 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
1706 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
1707 im->xlab_user.labtm = xlab[xlab_sel].labtm;
1708 im->xlab_user.labst = xlab[xlab_sel].labst;
1709 im->xlab_user.precis = xlab[xlab_sel].precis;
1710 im->xlab_user.stst = xlab[xlab_sel].stst;
1711 }
1713 /* y coords are the same for every line ... */
1714 Y0 = im->yorigin;
1715 Y1 = im->yorigin-im->ysize;
1718 /* paint the minor grid */
1719 for(ti = find_first_time(im->start,
1720 im->xlab_user.gridtm,
1721 im->xlab_user.gridst),
1722 timajor = find_first_time(im->start,
1723 im->xlab_user.mgridtm,
1724 im->xlab_user.mgridst);
1725 ti < im->end;
1726 ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
1727 ){
1728 /* are we inside the graph ? */
1729 if (ti < im->start || ti > im->end) continue;
1730 while (timajor < ti) {
1731 timajor = find_next_time(timajor,
1732 im->xlab_user.mgridtm, im->xlab_user.mgridst);
1733 }
1734 if (ti == timajor) continue; /* skip as falls on major grid line */
1735 X0 = xtr(im,ti);
1736 gfx_new_dashed_line(im->canvas,X0,Y0+1, X0,Y1-1,GRIDWIDTH,
1737 im->graph_col[GRC_GRID],
1738 im->grid_dash_on, im->grid_dash_off);
1740 }
1742 /* paint the major grid */
1743 for(ti = find_first_time(im->start,
1744 im->xlab_user.mgridtm,
1745 im->xlab_user.mgridst);
1746 ti < im->end;
1747 ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
1748 ){
1749 /* are we inside the graph ? */
1750 if (ti < im->start || ti > im->end) continue;
1751 X0 = xtr(im,ti);
1752 gfx_new_dashed_line(im->canvas,X0,Y0+3, X0,Y1-2,MGRIDWIDTH,
1753 im->graph_col[GRC_MGRID],
1754 im->grid_dash_on, im->grid_dash_off);
1756 }
1757 /* paint the labels below the graph */
1758 for(ti = find_first_time(im->start,
1759 im->xlab_user.labtm,
1760 im->xlab_user.labst);
1761 ti <= im->end;
1762 ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
1763 ){
1764 tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
1765 /* are we inside the graph ? */
1766 if (ti < im->start || ti > im->end) continue;
1768 #if HAVE_STRFTIME
1769 strftime(graph_label,99,im->xlab_user.stst,localtime(&tilab));
1770 #else
1771 # error "your libc has no strftime I guess we'll abort the exercise here."
1772 #endif
1773 gfx_new_text ( im->canvas,
1774 xtr(im,tilab), Y0+im->text_prop[TEXT_PROP_AXIS].size/1.5,
1775 im->graph_col[GRC_FONT],
1776 im->text_prop[TEXT_PROP_AXIS].font,
1777 im->text_prop[TEXT_PROP_AXIS].size,
1778 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP,
1779 graph_label );
1781 }
1783 }
1786 void
1787 axis_paint(
1788 image_desc_t *im
1789 )
1790 {
1791 /* draw x and y axis */
1792 gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
1793 im->xorigin+im->xsize,im->yorigin-im->ysize,
1794 GRIDWIDTH, im->graph_col[GRC_GRID]);
1796 gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
1797 im->xorigin+im->xsize,im->yorigin-im->ysize,
1798 GRIDWIDTH, im->graph_col[GRC_GRID]);
1800 gfx_new_line ( im->canvas, im->xorigin-4,im->yorigin,
1801 im->xorigin+im->xsize+4,im->yorigin,
1802 MGRIDWIDTH, im->graph_col[GRC_GRID]);
1804 gfx_new_line ( im->canvas, im->xorigin,im->yorigin+4,
1805 im->xorigin,im->yorigin-im->ysize-4,
1806 MGRIDWIDTH, im->graph_col[GRC_GRID]);
1809 /* arrow for X axis direction */
1810 gfx_new_area ( im->canvas,
1811 im->xorigin+im->xsize+3, im->yorigin-3,
1812 im->xorigin+im->xsize+3, im->yorigin+4,
1813 im->xorigin+im->xsize+8, im->yorigin+0.5, /* LINEOFFSET */
1814 im->graph_col[GRC_ARROW]);
1818 }
1820 void
1821 grid_paint(image_desc_t *im)
1822 {
1823 long i;
1824 int res=0;
1825 double X0,Y0; /* points for filled graph and more*/
1826 gfx_node_t *node;
1828 /* draw 3d border */
1829 node = gfx_new_area (im->canvas, 0,im->yimg,
1830 2,im->yimg-2,
1831 2,2,im->graph_col[GRC_SHADEA]);
1832 gfx_add_point( node , im->ximg - 2, 2 );
1833 gfx_add_point( node , im->ximg, 0 );
1834 gfx_add_point( node , 0,0 );
1835 /* gfx_add_point( node , 0,im->yimg ); */
1837 node = gfx_new_area (im->canvas, 2,im->yimg-2,
1838 im->ximg-2,im->yimg-2,
1839 im->ximg - 2, 2,
1840 im->graph_col[GRC_SHADEB]);
1841 gfx_add_point( node , im->ximg,0);
1842 gfx_add_point( node , im->ximg,im->yimg);
1843 gfx_add_point( node , 0,im->yimg);
1844 /* gfx_add_point( node , 0,im->yimg ); */
1847 if (im->draw_x_grid == 1 )
1848 vertical_grid(im);
1850 if (im->draw_y_grid == 1){
1851 if(im->logarithmic){
1852 res = horizontal_log_grid(im);
1853 } else {
1854 res = draw_horizontal_grid(im);
1855 }
1857 /* dont draw horizontal grid if there is no min and max val */
1858 if (! res ) {
1859 char *nodata = "No Data found";
1860 gfx_new_text(im->canvas,im->ximg/2, (2*im->yorigin-im->ysize) / 2,
1861 im->graph_col[GRC_FONT],
1862 im->text_prop[TEXT_PROP_AXIS].font,
1863 im->text_prop[TEXT_PROP_AXIS].size,
1864 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
1865 nodata );
1866 }
1867 }
1869 /* yaxis description */
1870 if (im->canvas->imgformat != IF_PNG) {
1871 gfx_new_text( im->canvas,
1872 7, (im->yorigin - im->ysize/2),
1873 im->graph_col[GRC_FONT],
1874 im->text_prop[TEXT_PROP_AXIS].font,
1875 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 270.0,
1876 GFX_H_CENTER, GFX_V_CENTER,
1877 im->ylegend);
1878 } else {
1879 /* horrible hack until we can actually print vertically */
1880 {
1881 int n;
1882 int l=strlen(im->ylegend);
1883 char s[2];
1884 for (n=0;n<strlen(im->ylegend);n++) {
1885 s[0]=im->ylegend[n];
1886 s[1]='\0';
1887 gfx_new_text(im->canvas,7,im->text_prop[TEXT_PROP_AXIS].size*(l-n),
1888 im->graph_col[GRC_FONT],
1889 im->text_prop[TEXT_PROP_AXIS].font,
1890 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 270.0,
1891 GFX_H_CENTER, GFX_V_CENTER,
1892 s);
1893 }
1894 }
1895 }
1897 /* graph title */
1898 gfx_new_text( im->canvas,
1899 im->ximg/2, im->text_prop[TEXT_PROP_TITLE].size,
1900 im->graph_col[GRC_FONT],
1901 im->text_prop[TEXT_PROP_TITLE].font,
1902 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
1903 GFX_H_CENTER, GFX_V_CENTER,
1904 im->title);
1906 /* graph labels */
1907 if( !(im->extra_flags & NOLEGEND) ) {
1908 for(i=0;i<im->gdes_c;i++){
1909 if(im->gdes[i].legend[0] =='\0')
1910 continue;
1912 /* im->gdes[i].leg_y is the bottom of the legend */
1913 X0 = im->gdes[i].leg_x;
1914 Y0 = im->gdes[i].leg_y;
1915 /* Box needed? */
1916 if ( im->gdes[i].gf != GF_GPRINT
1917 && im->gdes[i].gf != GF_COMMENT) {
1918 int boxH, boxV;
1920 boxH = gfx_get_text_width(im->canvas, 0,
1921 im->text_prop[TEXT_PROP_AXIS].font,
1922 im->text_prop[TEXT_PROP_AXIS].size,
1923 im->tabwidth,"M") * 1.25;
1924 boxV = boxH;
1926 node = gfx_new_area(im->canvas,
1927 X0,Y0-boxV,
1928 X0,Y0,
1929 X0+boxH,Y0,
1930 im->gdes[i].col);
1931 gfx_add_point ( node, X0+boxH, Y0-boxV );
1932 node = gfx_new_line(im->canvas,
1933 X0,Y0-boxV, X0,Y0,
1934 1,0x000000FF);
1935 gfx_add_point(node,X0+boxH,Y0);
1936 gfx_add_point(node,X0+boxH,Y0-boxV);
1937 gfx_close_path(node);
1938 X0 += boxH / 1.25 * 2;
1939 }
1940 gfx_new_text ( im->canvas, X0, Y0,
1941 im->graph_col[GRC_FONT],
1942 im->text_prop[TEXT_PROP_AXIS].font,
1943 im->text_prop[TEXT_PROP_AXIS].size,
1944 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
1945 im->gdes[i].legend );
1946 }
1947 }
1948 }
1951 /*****************************************************
1952 * lazy check make sure we rely need to create this graph
1953 *****************************************************/
1955 int lazy_check(image_desc_t *im){
1956 FILE *fd = NULL;
1957 int size = 1;
1958 struct stat imgstat;
1960 if (im->lazy == 0) return 0; /* no lazy option */
1961 if (stat(im->graphfile,&imgstat) != 0)
1962 return 0; /* can't stat */
1963 /* one pixel in the existing graph is more then what we would
1964 change here ... */
1965 if (time(NULL) - imgstat.st_mtime >
1966 (im->end - im->start) / im->xsize)
1967 return 0;
1968 if ((fd = fopen(im->graphfile,"rb")) == NULL)
1969 return 0; /* the file does not exist */
1970 switch (im->canvas->imgformat) {
1971 case IF_PNG:
1972 size = PngSize(fd,&(im->ximg),&(im->yimg));
1973 break;
1974 default:
1975 size = 1;
1976 }
1977 fclose(fd);
1978 return size;
1979 }
1981 void
1982 pie_part(image_desc_t *im, gfx_color_t color,
1983 double PieCenterX, double PieCenterY, double Radius,
1984 double startangle, double endangle)
1985 {
1986 gfx_node_t *node;
1987 double angle;
1988 double step=M_PI/50; /* Number of iterations for the circle;
1989 ** 10 is definitely too low, more than
1990 ** 50 seems to be overkill
1991 */
1993 /* Strange but true: we have to work clockwise or else
1994 ** anti aliasing nor transparency don't work.
1995 **
1996 ** This test is here to make sure we do it right, also
1997 ** this makes the for...next loop more easy to implement.
1998 ** The return will occur if the user enters a negative number
1999 ** (which shouldn't be done according to the specs) or if the
2000 ** programmers do something wrong (which, as we all know, never
2001 ** happens anyway :)
2002 */
2003 if (endangle<startangle) return;
2005 /* Hidden feature: Radius decreases each full circle */
2006 angle=startangle;
2007 while (angle>=2*M_PI) {
2008 angle -= 2*M_PI;
2009 Radius *= 0.8;
2010 }
2012 node=gfx_new_area(im->canvas,
2013 PieCenterX+sin(startangle)*Radius,
2014 PieCenterY-cos(startangle)*Radius,
2015 PieCenterX,
2016 PieCenterY,
2017 PieCenterX+sin(endangle)*Radius,
2018 PieCenterY-cos(endangle)*Radius,
2019 color);
2020 for (angle=endangle;angle-startangle>=step;angle-=step) {
2021 gfx_add_point(node,
2022 PieCenterX+sin(angle)*Radius,
2023 PieCenterY-cos(angle)*Radius );
2024 }
2025 }
2027 int
2028 graph_size_location(image_desc_t *im, int elements, int piechart )
2029 {
2030 /* The actual size of the image to draw is determined from
2031 ** several sources. The size given on the command line is
2032 ** the graph area but we need more as we have to draw labels
2033 ** and other things outside the graph area
2034 */
2036 /* +-+-------------------------------------------+
2037 ** |l|.................title.....................|
2038 ** |e+--+-------------------------------+--------+
2039 ** |b| b| | |
2040 ** |a| a| | pie |
2041 ** |l| l| main graph area | chart |
2042 ** |.| .| | area |
2043 ** |t| y| | |
2044 ** |r+--+-------------------------------+--------+
2045 ** |e| | x-axis labels | |
2046 ** |v+--+-------------------------------+--------+
2047 ** | |..............legends......................|
2048 ** +-+-------------------------------------------+
2049 */
2050 int Xvertical=0, Yvertical=0,
2051 Xtitle =0, Ytitle =0,
2052 Xylabel =0, Yylabel =0,
2053 Xmain =0, Ymain =0,
2054 Xpie =0, Ypie =0,
2055 Xxlabel =0, Yxlabel =0,
2056 #if 0
2057 Xlegend =0, Ylegend =0,
2058 #endif
2059 Xspacing =10, Yspacing =10;
2061 if (im->ylegend[0] != '\0') {
2062 Xvertical = im->text_prop[TEXT_PROP_LEGEND].size *2;
2063 Yvertical = im->text_prop[TEXT_PROP_LEGEND].size * (strlen(im->ylegend)+1);
2064 }
2066 if (im->title[0] != '\0') {
2067 /* The title is placed "inbetween" two text lines so it
2068 ** automatically has some vertical spacing. The horizontal
2069 ** spacing is added here, on each side.
2070 */
2071 Xtitle = gfx_get_text_width(im->canvas, 0,
2072 im->text_prop[TEXT_PROP_TITLE].font,
2073 im->text_prop[TEXT_PROP_TITLE].size,
2074 im->tabwidth,
2075 im->title) + 2*Xspacing;
2076 Ytitle = im->text_prop[TEXT_PROP_TITLE].size*2;
2077 }
2079 if (elements) {
2080 Xmain=im->xsize;
2081 Ymain=im->ysize;
2082 if (im->draw_x_grid) {
2083 Xxlabel=Xmain;
2084 Yxlabel=im->text_prop[TEXT_PROP_LEGEND].size *2;
2085 }
2086 if (im->draw_y_grid) {
2087 Xylabel=im->text_prop[TEXT_PROP_LEGEND].size *6;
2088 Yylabel=Ymain;
2089 }
2090 }
2092 if (piechart) {
2093 im->piesize=im->xsize<im->ysize?im->xsize:im->ysize;
2094 Xpie=im->piesize;
2095 Ypie=im->piesize;
2096 }
2098 /* Now calculate the total size. Insert some spacing where
2099 desired. im->xorigin and im->yorigin need to correspond
2100 with the lower left corner of the main graph area or, if
2101 this one is not set, the imaginary box surrounding the
2102 pie chart area. */
2104 /* The legend width cannot yet be determined, as a result we
2105 ** have problems adjusting the image to it. For now, we just
2106 ** forget about it at all; the legend will have to fit in the
2107 ** size already allocated.
2108 */
2109 im->ximg = Xylabel + Xmain + Xpie + Xspacing;
2110 if (Xmain) im->ximg += Xspacing;
2111 if (Xpie) im->ximg += Xspacing;
2112 im->xorigin = Xspacing + Xylabel;
2113 if (Xtitle > im->ximg) im->ximg = Xtitle;
2114 if (Xvertical) {
2115 im->ximg += Xvertical;
2116 im->xorigin += Xvertical;
2117 }
2118 xtr(im,0);
2120 /* The vertical size is interesting... we need to compare
2121 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend} with Yvertical
2122 ** however we need to know {Ytitle+Ymain+Yxlabel} in order to
2123 ** start even thinking about Ylegend.
2124 **
2125 ** Do it in three portions: First calculate the inner part,
2126 ** then do the legend, then adjust the total height of the img.
2127 */
2129 /* reserve space for main and/or pie */
2130 im->yimg = Ymain + Yxlabel;
2131 if (im->yimg < Ypie) im->yimg = Ypie;
2132 im->yorigin = im->yimg - Yxlabel;
2133 /* reserve space for the title *or* some padding above the graph */
2134 if (Ytitle) {
2135 im->yimg += Ytitle;
2136 im->yorigin += Ytitle;
2137 } else {
2138 im->yimg += Yspacing;
2139 im->yorigin += Yspacing;
2140 }
2141 /* reserve space for padding below the graph */
2142 im->yimg += Yspacing;
2143 ytr(im,DNAN);
2145 /* Determine where to place the legends onto the image.
2146 ** Adjust im->yimg to match the space requirements.
2147 */
2148 if(leg_place(im)==-1)
2149 return -1;
2151 /* last of three steps: check total height of image */
2152 if (im->yimg < Yvertical) im->yimg = Yvertical;
2154 #if 0
2155 if (Xlegend > im->ximg) {
2156 im->ximg = Xlegend;
2157 /* reposition Pie */
2158 }
2159 #endif
2161 /* The pie is placed in the upper right hand corner,
2162 ** just below the title (if any) and with sufficient
2163 ** padding.
2164 */
2165 if (elements) {
2166 im->pie_x = im->ximg - Xspacing - Xpie/2;
2167 im->pie_y = im->yorigin-Ymain+Ypie/2;
2168 } else {
2169 im->pie_x = im->ximg/2;
2170 im->pie_y = im->yorigin-Ypie/2;
2171 }
2173 return 0;
2174 }
2176 /* draw that picture thing ... */
2177 int
2178 graph_paint(image_desc_t *im, char ***calcpr)
2179 {
2180 int i,ii;
2181 int lazy = lazy_check(im);
2182 int piechart = 0;
2183 double PieStart=0.0;
2184 FILE *fo;
2185 gfx_node_t *node;
2187 double areazero = 0.0;
2188 enum gf_en stack_gf = GF_PRINT;
2189 graph_desc_t *lastgdes = NULL;
2191 /* if we are lazy and there is nothing to PRINT ... quit now */
2192 if (lazy && im->prt_c==0) return 0;
2194 /* pull the data from the rrd files ... */
2196 if(data_fetch(im)==-1)
2197 return -1;
2199 /* evaluate VDEF and CDEF operations ... */
2200 if(data_calc(im)==-1)
2201 return -1;
2203 /* check if we need to draw a piechart */
2204 for(i=0;i<im->gdes_c;i++){
2205 if (im->gdes[i].gf == GF_PART) {
2206 piechart=1;
2207 break;
2208 }
2209 }
2211 /* calculate and PRINT and GPRINT definitions. We have to do it at
2212 * this point because it will affect the length of the legends
2213 * if there are no graph elements we stop here ...
2214 * if we are lazy, try to quit ...
2215 */
2216 i=print_calc(im,calcpr);
2217 if(i<0) return -1;
2218 if(((i==0)&&(piechart==0)) || lazy) return 0;
2220 /* If there's only the pie chart to draw, signal this */
2221 if (i==0) piechart=2;
2223 /* get actual drawing data and find min and max values*/
2224 if(data_proc(im)==-1)
2225 return -1;
2227 if(!im->logarithmic){si_unit(im);} /* identify si magnitude Kilo, Mega Giga ? */
2229 if(!im->rigid && ! im->logarithmic)
2230 expand_range(im); /* make sure the upper and lower limit are
2231 sensible values */
2233 if (!calc_horizontal_grid(im))
2234 return -1;
2235 if (im->gridfit)
2236 apply_gridfit(im);
2238 /**************************************************************
2239 *** Calculating sizes and locations became a bit confusing ***
2240 *** so I moved this into a separate function. ***
2241 **************************************************************/
2242 if(graph_size_location(im,i,piechart)==-1)
2243 return -1;
2245 /* the actual graph is created by going through the individual
2246 graph elements and then drawing them */
2248 node=gfx_new_area ( im->canvas,
2249 0, 0,
2250 im->ximg, 0,
2251 im->ximg, im->yimg,
2252 im->graph_col[GRC_BACK]);
2254 gfx_add_point(node,0, im->yimg);
2256 if (piechart != 2) {
2257 node=gfx_new_area ( im->canvas,
2258 im->xorigin, im->yorigin,
2259 im->xorigin + im->xsize, im->yorigin,
2260 im->xorigin + im->xsize, im->yorigin-im->ysize,
2261 im->graph_col[GRC_CANVAS]);
2263 gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
2265 if (im->minval > 0.0)
2266 areazero = im->minval;
2267 if (im->maxval < 0.0)
2268 areazero = im->maxval;
2270 axis_paint(im);
2271 }
2273 if (piechart) {
2274 pie_part(im,im->graph_col[GRC_CANVAS],im->pie_x,im->pie_y,im->piesize*0.5,0,2*M_PI);
2275 }
2277 for(i=0;i<im->gdes_c;i++){
2278 switch(im->gdes[i].gf){
2279 case GF_CDEF:
2280 case GF_VDEF:
2281 case GF_DEF:
2282 case GF_PRINT:
2283 case GF_GPRINT:
2284 case GF_COMMENT:
2285 case GF_HRULE:
2286 case GF_VRULE:
2287 case GF_XPORT:
2288 break;
2289 case GF_TICK:
2290 for (ii = 0; ii < im->xsize; ii++)
2291 {
2292 if (!isnan(im->gdes[i].p_data[ii]) &&
2293 im->gdes[i].p_data[ii] > 0.0)
2294 {
2295 /* generate a tick */
2296 gfx_new_line(im->canvas, im -> xorigin + ii,
2297 im -> yorigin - (im -> gdes[i].yrule * im -> ysize),
2298 im -> xorigin + ii,
2299 im -> yorigin,
2300 1.0,
2301 im -> gdes[i].col );
2302 }
2303 }
2304 break;
2305 case GF_LINE:
2306 case GF_AREA:
2307 stack_gf = im->gdes[i].gf;
2308 case GF_STACK:
2309 /* fix data points at oo and -oo */
2310 for(ii=0;ii<im->xsize;ii++){
2311 if (isinf(im->gdes[i].p_data[ii])){
2312 if (im->gdes[i].p_data[ii] > 0) {
2313 im->gdes[i].p_data[ii] = im->maxval ;
2314 } else {
2315 im->gdes[i].p_data[ii] = im->minval ;
2316 }
2318 }
2319 } /* for */
2321 if (im->gdes[i].col != 0x0){
2322 /* GF_LINE and friend */
2323 if(stack_gf == GF_LINE ){
2324 node = NULL;
2325 for(ii=1;ii<im->xsize;ii++){
2326 if ( ! isnan(im->gdes[i].p_data[ii-1])
2327 && ! isnan(im->gdes[i].p_data[ii])){
2328 if (node == NULL){
2329 node = gfx_new_line(im->canvas,
2330 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2331 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2332 im->gdes[i].linewidth,
2333 im->gdes[i].col);
2334 } else {
2335 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2336 }
2337 } else {
2338 node = NULL;
2339 }
2340 }
2341 } else {
2342 int area_start=-1;
2343 node = NULL;
2344 for(ii=1;ii<im->xsize;ii++){
2345 /* open an area */
2346 if ( ! isnan(im->gdes[i].p_data[ii-1])
2347 && ! isnan(im->gdes[i].p_data[ii])){
2348 if (node == NULL){
2349 float ybase = 0.0;
2350 if (im->gdes[i].gf == GF_STACK) {
2351 ybase = ytr(im,lastgdes->p_data[ii-1]);
2352 } else {
2353 ybase = ytr(im,areazero);
2354 }
2355 area_start = ii-1;
2356 node = gfx_new_area(im->canvas,
2357 ii-1+im->xorigin,ybase,
2358 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2359 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2360 im->gdes[i].col
2361 );
2362 } else {
2363 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2364 }
2365 }
2367 if ( node != NULL && (ii+1==im->xsize || isnan(im->gdes[i].p_data[ii]) )){
2368 /* GF_AREA STACK type*/
2369 if (im->gdes[i].gf == GF_STACK ) {
2370 int iii;
2371 for (iii=ii-1;iii>area_start;iii--){
2372 gfx_add_point(node,iii+im->xorigin,ytr(im,lastgdes->p_data[iii]));
2373 }
2374 } else {
2375 gfx_add_point(node,ii+im->xorigin,ytr(im,areazero));
2376 };
2377 node=NULL;
2378 };
2379 }
2380 } /* else GF_LINE */
2381 } /* if color != 0x0 */
2382 /* make sure we do not run into trouble when stacking on NaN */
2383 for(ii=0;ii<im->xsize;ii++){
2384 if (isnan(im->gdes[i].p_data[ii])) {
2385 double ybase = 0.0;
2386 if (lastgdes) {
2387 ybase = ytr(im,lastgdes->p_data[ii-1]);
2388 };
2389 if (isnan(ybase) || !lastgdes ){
2390 ybase = ytr(im,areazero);
2391 }
2392 im->gdes[i].p_data[ii] = ybase;
2393 }
2394 }
2395 lastgdes = &(im->gdes[i]);
2396 break;
2397 case GF_PART:
2398 if(isnan(im->gdes[i].yrule)) /* fetch variable */
2399 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2401 if (finite(im->gdes[i].yrule)) { /* even the fetched var can be NaN */
2402 pie_part(im,im->gdes[i].col,
2403 im->pie_x,im->pie_y,im->piesize*0.4,
2404 M_PI*2.0*PieStart/100.0,
2405 M_PI*2.0*(PieStart+im->gdes[i].yrule)/100.0);
2406 PieStart += im->gdes[i].yrule;
2407 }
2408 break;
2409 } /* switch */
2410 }
2411 if (piechart==2) {
2412 im->draw_x_grid=0;
2413 im->draw_y_grid=0;
2414 }
2415 /* grid_paint also does the text */
2416 grid_paint(im);
2418 /* the RULES are the last thing to paint ... */
2419 for(i=0;i<im->gdes_c;i++){
2421 switch(im->gdes[i].gf){
2422 case GF_HRULE:
2423 if(isnan(im->gdes[i].yrule)) { /* fetch variable */
2424 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2425 };
2426 if(im->gdes[i].yrule >= im->minval
2427 && im->gdes[i].yrule <= im->maxval)
2428 gfx_new_line(im->canvas,
2429 im->xorigin,ytr(im,im->gdes[i].yrule),
2430 im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2431 1.0,im->gdes[i].col);
2432 break;
2433 case GF_VRULE:
2434 if(im->gdes[i].xrule == 0) { /* fetch variable */
2435 im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
2436 };
2437 if(im->gdes[i].xrule >= im->start
2438 && im->gdes[i].xrule <= im->end)
2439 gfx_new_line(im->canvas,
2440 xtr(im,im->gdes[i].xrule),im->yorigin,
2441 xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2442 1.0,im->gdes[i].col);
2443 break;
2444 default:
2445 break;
2446 }
2447 }
2450 if (strcmp(im->graphfile,"-")==0) {
2451 #ifdef WIN32
2452 /* Change translation mode for stdout to BINARY */
2453 _setmode( _fileno( stdout ), O_BINARY );
2454 #endif
2455 fo = stdout;
2456 } else {
2457 if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2458 rrd_set_error("Opening '%s' for write: %s",im->graphfile,
2459 strerror(errno));
2460 return (-1);
2461 }
2462 }
2463 gfx_render (im->canvas,im->ximg,im->yimg,0x0,fo);
2464 if (strcmp(im->graphfile,"-") != 0)
2465 fclose(fo);
2466 return 0;
2467 }
2470 /*****************************************************
2471 * graph stuff
2472 *****************************************************/
2474 int
2475 gdes_alloc(image_desc_t *im){
2477 long def_step = (im->end-im->start)/im->xsize;
2479 if (im->step > def_step) /* step can be increassed ... no decreassed */
2480 def_step = im->step;
2482 im->gdes_c++;
2484 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2485 * sizeof(graph_desc_t)))==NULL){
2486 rrd_set_error("realloc graph_descs");
2487 return -1;
2488 }
2491 im->gdes[im->gdes_c-1].step=def_step;
2492 im->gdes[im->gdes_c-1].stack=0;
2493 im->gdes[im->gdes_c-1].start=im->start;
2494 im->gdes[im->gdes_c-1].end=im->end;
2495 im->gdes[im->gdes_c-1].vname[0]='\0';
2496 im->gdes[im->gdes_c-1].data=NULL;
2497 im->gdes[im->gdes_c-1].ds_namv=NULL;
2498 im->gdes[im->gdes_c-1].data_first=0;
2499 im->gdes[im->gdes_c-1].p_data=NULL;
2500 im->gdes[im->gdes_c-1].rpnp=NULL;
2501 im->gdes[im->gdes_c-1].col = 0x0;
2502 im->gdes[im->gdes_c-1].legend[0]='\0';
2503 im->gdes[im->gdes_c-1].rrd[0]='\0';
2504 im->gdes[im->gdes_c-1].ds=-1;
2505 im->gdes[im->gdes_c-1].p_data=NULL;
2506 return 0;
2507 }
2509 /* copies input untill the first unescaped colon is found
2510 or until input ends. backslashes have to be escaped as well */
2511 int
2512 scan_for_col(char *input, int len, char *output)
2513 {
2514 int inp,outp=0;
2515 for (inp=0;
2516 inp < len &&
2517 input[inp] != ':' &&
2518 input[inp] != '\0';
2519 inp++){
2520 if (input[inp] == '\\' &&
2521 input[inp+1] != '\0' &&
2522 (input[inp+1] == '\\' ||
2523 input[inp+1] == ':')){
2524 output[outp++] = input[++inp];
2525 }
2526 else {
2527 output[outp++] = input[inp];
2528 }
2529 }
2530 output[outp] = '\0';
2531 return inp;
2532 }
2533 /* Some surgery done on this function, it became ridiculously big.
2534 ** Things moved:
2535 ** - initializing now in rrd_graph_init()
2536 ** - options parsing now in rrd_graph_options()
2537 ** - script parsing now in rrd_graph_script()
2538 */
2539 int
2540 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize)
2541 {
2542 image_desc_t im;
2544 #ifdef HAVE_TZSET
2545 tzset();
2546 #endif
2547 #ifdef HAVE_SETLOCALE
2548 setlocale(LC_TIME,"");
2549 #endif
2552 rrd_graph_init(&im);
2554 rrd_graph_options(argc,argv,&im);
2555 if (rrd_test_error()) return -1;
2557 if (strlen(argv[optind])>=MAXPATH) {
2558 rrd_set_error("filename (including path) too long");
2559 return -1;
2560 }
2561 strncpy(im.graphfile,argv[optind],MAXPATH-1);
2562 im.graphfile[MAXPATH-1]='\0';
2564 rrd_graph_script(argc,argv,&im);
2565 if (rrd_test_error()) return -1;
2567 /* Everything is now read and the actual work can start */
2569 (*prdata)=NULL;
2570 if (graph_paint(&im,prdata)==-1){
2571 im_free(&im);
2572 return -1;
2573 }
2575 /* The image is generated and needs to be output.
2576 ** Also, if needed, print a line with information about the image.
2577 */
2579 *xsize=im.ximg;
2580 *ysize=im.yimg;
2581 if (im.imginfo) {
2582 char *filename;
2583 if (!(*prdata)) {
2584 /* maybe prdata is not allocated yet ... lets do it now */
2585 if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
2586 rrd_set_error("malloc imginfo");
2587 return -1;
2588 };
2589 }
2590 if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
2591 ==NULL){
2592 rrd_set_error("malloc imginfo");
2593 return -1;
2594 }
2595 filename=im.graphfile+strlen(im.graphfile);
2596 while(filename > im.graphfile) {
2597 if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
2598 filename--;
2599 }
2601 sprintf((*prdata)[0],im.imginfo,filename,(long)(im.canvas->zoom*im.ximg),(long)(im.canvas->zoom*im.yimg));
2602 }
2603 im_free(&im);
2604 return 0;
2605 }
2607 void
2608 rrd_graph_init(image_desc_t *im)
2609 {
2610 int i;
2612 im->xlab_user.minsec = -1;
2613 im->ximg=0;
2614 im->yimg=0;
2615 im->xsize = 400;
2616 im->ysize = 100;
2617 im->step = 0;
2618 im->ylegend[0] = '\0';
2619 im->title[0] = '\0';
2620 im->minval = DNAN;
2621 im->maxval = DNAN;
2622 im->unitsexponent= 9999;
2623 im->extra_flags= 0;
2624 im->rigid = 0;
2625 im->gridfit = 1;
2626 im->imginfo = NULL;
2627 im->lazy = 0;
2628 im->logarithmic = 0;
2629 im->ygridstep = DNAN;
2630 im->draw_x_grid = 1;
2631 im->draw_y_grid = 1;
2632 im->base = 1000;
2633 im->prt_c = 0;
2634 im->gdes_c = 0;
2635 im->gdes = NULL;
2636 im->canvas = gfx_new_canvas();
2637 im->grid_dash_on = 1;
2638 im->grid_dash_off = 1;
2640 for(i=0;i<DIM(graph_col);i++)
2641 im->graph_col[i]=graph_col[i];
2643 for(i=0;i<DIM(text_prop);i++){
2644 im->text_prop[i].size = text_prop[i].size;
2645 im->text_prop[i].font = text_prop[i].font;
2646 }
2647 }
2649 void
2650 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
2651 {
2652 int stroff;
2653 char *parsetime_error = NULL;
2654 char scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
2655 time_t start_tmp=0,end_tmp=0;
2656 long long_tmp;
2657 struct time_value start_tv, end_tv;
2658 gfx_color_t color;
2660 parsetime("end-24h", &start_tv);
2661 parsetime("now", &end_tv);
2663 while (1){
2664 static struct option long_options[] =
2665 {
2666 {"start", required_argument, 0, 's'},
2667 {"end", required_argument, 0, 'e'},
2668 {"x-grid", required_argument, 0, 'x'},
2669 {"y-grid", required_argument, 0, 'y'},
2670 {"vertical-label",required_argument,0,'v'},
2671 {"width", required_argument, 0, 'w'},
2672 {"height", required_argument, 0, 'h'},
2673 {"interlaced", no_argument, 0, 'i'},
2674 {"upper-limit",required_argument, 0, 'u'},
2675 {"lower-limit",required_argument, 0, 'l'},
2676 {"rigid", no_argument, 0, 'r'},
2677 {"base", required_argument, 0, 'b'},
2678 {"logarithmic",no_argument, 0, 'o'},
2679 {"color", required_argument, 0, 'c'},
2680 {"font", required_argument, 0, 'n'},
2681 {"title", required_argument, 0, 't'},
2682 {"imginfo", required_argument, 0, 'f'},
2683 {"imgformat", required_argument, 0, 'a'},
2684 {"lazy", no_argument, 0, 'z'},
2685 {"zoom", required_argument, 0, 'm'},
2686 {"no-legend", no_argument, 0, 'g'},
2687 {"alt-y-grid", no_argument, 0, 257 },
2688 {"alt-autoscale", no_argument, 0, 258 },
2689 {"alt-autoscale-max", no_argument, 0, 259 },
2690 {"units-exponent",required_argument, 0, 260},
2691 {"step", required_argument, 0, 261},
2692 {"no-gridfit", no_argument, 0, 262},
2693 {0,0,0,0}};
2694 int option_index = 0;
2695 int opt;
2698 opt = getopt_long(argc, argv,
2699 "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:z:g",
2700 long_options, &option_index);
2702 if (opt == EOF)
2703 break;
2705 switch(opt) {
2706 case 257:
2707 im->extra_flags |= ALTYGRID;
2708 break;
2709 case 258:
2710 im->extra_flags |= ALTAUTOSCALE;
2711 break;
2712 case 259:
2713 im->extra_flags |= ALTAUTOSCALE_MAX;
2714 break;
2715 case 'g':
2716 im->extra_flags |= NOLEGEND;
2717 break;
2718 case 260:
2719 im->unitsexponent = atoi(optarg);
2720 break;
2721 case 261:
2722 im->step = atoi(optarg);
2723 break;
2724 case 262:
2725 im->gridfit = 0;
2726 break;
2727 case 's':
2728 if ((parsetime_error = parsetime(optarg, &start_tv))) {
2729 rrd_set_error( "start time: %s", parsetime_error );
2730 return;
2731 }
2732 break;
2733 case 'e':
2734 if ((parsetime_error = parsetime(optarg, &end_tv))) {
2735 rrd_set_error( "end time: %s", parsetime_error );
2736 return;
2737 }
2738 break;
2739 case 'x':
2740 if(strcmp(optarg,"none") == 0){
2741 im->draw_x_grid=0;
2742 break;
2743 };
2745 if(sscanf(optarg,
2746 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
2747 scan_gtm,
2748 &im->xlab_user.gridst,
2749 scan_mtm,
2750 &im->xlab_user.mgridst,
2751 scan_ltm,
2752 &im->xlab_user.labst,
2753 &im->xlab_user.precis,
2754 &stroff) == 7 && stroff != 0){
2755 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
2756 if((im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
2757 rrd_set_error("unknown keyword %s",scan_gtm);
2758 return;
2759 } else if ((im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
2760 rrd_set_error("unknown keyword %s",scan_mtm);
2761 return;
2762 } else if ((im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
2763 rrd_set_error("unknown keyword %s",scan_ltm);
2764 return;
2765 }
2766 im->xlab_user.minsec = 1;
2767 im->xlab_user.stst = im->xlab_form;
2768 } else {
2769 rrd_set_error("invalid x-grid format");
2770 return;
2771 }
2772 break;
2773 case 'y':
2775 if(strcmp(optarg,"none") == 0){
2776 im->draw_y_grid=0;
2777 break;
2778 };
2780 if(sscanf(optarg,
2781 "%lf:%d",
2782 &im->ygridstep,
2783 &im->ylabfact) == 2) {
2784 if(im->ygridstep<=0){
2785 rrd_set_error("grid step must be > 0");
2786 return;
2787 } else if (im->ylabfact < 1){
2788 rrd_set_error("label factor must be > 0");
2789 return;
2790 }
2791 } else {
2792 rrd_set_error("invalid y-grid format");
2793 return;
2794 }
2795 break;
2796 case 'v':
2797 strncpy(im->ylegend,optarg,150);
2798 im->ylegend[150]='\0';
2799 break;
2800 case 'u':
2801 im->maxval = atof(optarg);
2802 break;
2803 case 'l':
2804 im->minval = atof(optarg);
2805 break;
2806 case 'b':
2807 im->base = atol(optarg);
2808 if(im->base != 1024 && im->base != 1000 ){
2809 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
2810 return;
2811 }
2812 break;
2813 case 'w':
2814 long_tmp = atol(optarg);
2815 if (long_tmp < 10) {
2816 rrd_set_error("width below 10 pixels");
2817 return;
2818 }
2819 im->xsize = long_tmp;
2820 break;
2821 case 'h':
2822 long_tmp = atol(optarg);
2823 if (long_tmp < 10) {
2824 rrd_set_error("height below 10 pixels");
2825 return;
2826 }
2827 im->ysize = long_tmp;
2828 break;
2829 case 'i':
2830 im->canvas->interlaced = 1;
2831 break;
2832 case 'r':
2833 im->rigid = 1;
2834 break;
2835 case 'f':
2836 im->imginfo = optarg;
2837 break;
2838 case 'a':
2839 if((im->canvas->imgformat = if_conv(optarg)) == -1) {
2840 rrd_set_error("unsupported graphics format '%s'",optarg);
2841 return;
2842 }
2843 break;
2844 case 'z':
2845 im->lazy = 1;
2846 break;
2847 case 'o':
2848 im->logarithmic = 1;
2849 if (isnan(im->minval))
2850 im->minval=1;
2851 break;
2852 case 'c':
2853 if(sscanf(optarg,
2854 "%10[A-Z]#%8lx",
2855 col_nam,&color) == 2){
2856 int ci;
2857 if((ci=grc_conv(col_nam)) != -1){
2858 im->graph_col[ci]=color;
2859 } else {
2860 rrd_set_error("invalid color name '%s'",col_nam);
2861 }
2862 } else {
2863 rrd_set_error("invalid color def format");
2864 return;
2865 }
2866 break;
2867 case 'n':{
2868 /* originally this used char *prop = "" and
2869 ** char *font = "dummy" however this results
2870 ** in a SEG fault, at least on RH7.1
2871 **
2872 ** The current implementation isn't proper
2873 ** either, font is never freed and prop uses
2874 ** a fixed width string
2875 */
2876 char prop[100];
2877 double size = 1;
2878 char *font;
2880 font=malloc(255);
2881 if(sscanf(optarg,
2882 "%10[A-Z]:%lf:%s",
2883 prop,&size,font) == 3){
2884 int sindex;
2885 if((sindex=text_prop_conv(prop)) != -1){
2886 im->text_prop[sindex].size=size;
2887 im->text_prop[sindex].font=font;
2888 if (sindex==0) { /* the default */
2889 im->text_prop[TEXT_PROP_TITLE].size=size;
2890 im->text_prop[TEXT_PROP_TITLE].font=font;
2891 im->text_prop[TEXT_PROP_AXIS].size=size;
2892 im->text_prop[TEXT_PROP_AXIS].font=font;
2893 im->text_prop[TEXT_PROP_UNIT].size=size;
2894 im->text_prop[TEXT_PROP_UNIT].font=font;
2895 im->text_prop[TEXT_PROP_LEGEND].size=size;
2896 im->text_prop[TEXT_PROP_LEGEND].font=font;
2897 }
2898 } else {
2899 rrd_set_error("invalid fonttag '%s'",prop);
2900 return;
2901 }
2902 } else {
2903 rrd_set_error("invalid text property format");
2904 return;
2905 }
2906 break;
2907 }
2908 case 'm':
2909 im->canvas->zoom = atof(optarg);
2910 if (im->canvas->zoom <= 0.0) {
2911 rrd_set_error("zoom factor must be > 0");
2912 return;
2913 }
2914 break;
2915 case 't':
2916 strncpy(im->title,optarg,150);
2917 im->title[150]='\0';
2918 break;
2920 case '?':
2921 if (optopt != 0)
2922 rrd_set_error("unknown option '%c'", optopt);
2923 else
2924 rrd_set_error("unknown option '%s'",argv[optind-1]);
2925 return;
2926 }
2927 }
2929 if (optind >= argc) {
2930 rrd_set_error("missing filename");
2931 return;
2932 }
2934 if (im->logarithmic == 1 && (im->minval <= 0 || isnan(im->minval))){
2935 rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");
2936 return;
2937 }
2939 if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
2940 /* error string is set in parsetime.c */
2941 return;
2942 }
2944 if (start_tmp < 3600*24*365*10){
2945 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
2946 return;
2947 }
2949 if (end_tmp < start_tmp) {
2950 rrd_set_error("start (%ld) should be less than end (%ld)",
2951 start_tmp, end_tmp);
2952 return;
2953 }
2955 im->start = start_tmp;
2956 im->end = end_tmp;
2957 }
2959 /* rrd_name_or_num()
2960 **
2961 ** Scans for a VDEF-variable or a number
2962 **
2963 ** Returns an integer describing what was found:
2964 **
2965 ** 0: error
2966 ** 1: found an integer; it is returned in both l and d
2967 ** 2: found a float; it is returned in d
2968 ** 3: found a vname; its index is returned in l
2969 **
2970 ** l and d are undefined unless described above
2971 */
2972 static int
2973 rrd_name_or_num(image_desc_t *im, char *param, long *l, double *d)
2974 {
2975 int i1=0,i2=0,i3=0,i4=0,i5=0,i6=0;
2976 char vname[MAX_VNAME_LEN+1];
2978 sscanf(param, "%li%n%*s%n", l,&i1,&i2);
2979 sscanf(param, "%lf%n%*s%n", d,&i3,&i4);
2980 sscanf(param, DEF_NAM_FMT "%n%*s%n", vname, &i5,&i6);
2982 if ( (i1) && (!i2) ) return 1;
2983 if ( (i3) && (!i4) ) return 2;
2984 if ( (i5) && (!i6) ) {
2985 if ((*l = find_var(im,vname))!=-1) return 3;
2986 }
2987 return 0;
2988 }
2990 /* rrd_vname_color()
2991 **
2992 ** Parses "[<vname|number>][#color]" where at least one
2993 ** of the optional strings must exist.
2994 **
2995 ** Returns an integer describing what was found.
2996 ** If the result is 0, the rrd_error string may be set.
2997 **
2998 ** ...CVVVV
2999 ** ---------:-----------------------------------
3000 ** 00000000 : error
3001 ** ....0000 : a value/variable was not found
3002 ** ....0001 : an integer number was found, returned in both l and d
3003 ** ....0010 : a floating point number was found, returned in d
3004 ** ....0011 : reserved for future values
3005 ** ....01xx : reserved for future values
3006 ** ....1000 : an existing DEF vname was found, idx returned in l
3007 ** ....1001 : an existing CDEF vname was found, idx returned in l
3008 ** ....1010 : an existing VDEF vname was found, idx returned in l
3009 ** ....1011 : reserved for future variables
3010 ** ....11xx : reserved for future variables
3011 ** ...0.... : a color was not found, returned in color
3012 ** ...1.... : a color was found, returned in color
3013 */
3014 static int
3015 rrd_vname_color(image_desc_t *im, char * param,
3016 long *l,
3017 double *d,
3018 gfx_color_t *color)
3019 {
3020 int result=0,i=0;
3022 if (param[0]!='#') { /* vname or num present, or empty string */
3023 char *s,*c=param;
3024 while ((*c!='\0')&&(*c!='#')) c++,i++;
3025 if (*c!='\0') {
3026 s=malloc(i+1);
3027 if (s==NULL) {
3028 rrd_set_error("Out of memory in function rrd_vname_color");
3029 return 0;
3030 }
3031 strncpy(s,param,i);
3032 s[i]='\0';
3033 result=rrd_name_or_num(im, s, l, d);
3034 if (!result) {
3035 rrd_set_error("Use of uninitialized vname %s",s);
3036 free(s);
3037 }
3038 } else {
3039 result=rrd_name_or_num(im, param, l, d);
3040 if (!result) {
3041 rrd_set_error("Use of uninitialized vname %s",param);
3042 }
3043 }
3044 switch (result) {
3045 case 0: return 0; /* error set above */
3046 case 1:
3047 case 2: break;
3048 case 3:
3049 switch (im->gdes[*l].gf) {
3050 case GF_DEF: result=0x08;break;
3051 case GF_CDEF: result=0x09;break;
3052 case GF_VDEF: result=0x0A;break;
3053 default:
3054 rrd_set_error("Unexpected GF result from function "
3055 "rrd_name_or_num() called from rrd_vname_color");
3056 return 0;
3057 }
3058 break;
3059 default:
3060 rrd_set_error("Unexpected result from function "
3061 "rrd_name_or_num() called from rrd_vname_color");
3062 return 0;
3063 }
3064 }
3065 /* Parse color, if any. */
3066 if (param[i] == '\0') return result;
3067 else {
3068 unsigned int r=0,g=0,b=0,a=0xFF;
3069 int i1=0,i2=0;
3070 sscanf(¶m[i], "#%02x%02x%02x%n%02x%n",
3071 &r,&g,&b,&i1,&a,&i2);
3072 if (!i1) {
3073 rrd_set_error("Unparsable color %s",¶m[i]);
3074 return 0;
3075 }
3076 if (i2) i1=i2;
3077 i2=0;
3078 sscanf(¶m[i+i1],"%*s%n",&i2);
3079 if (i2) {
3080 rrd_set_error("Garbage after color %s",param[i]);
3081 return 0;
3082 }
3083 *color=r<<24|g<<16|b<<8|a;
3084 return result|0x10;
3085 }
3086 }
3088 /* rrd_find_function()
3089 **
3090 ** Checks if the parameter is a valid function and
3091 ** if so, returns it in the graph description pointer.
3092 **
3093 ** The return value is a boolean; true if found
3094 */
3095 static int
3096 rrd_find_function(char *param, graph_desc_t *gdp)
3097 {
3098 size_t i1=0,i2=0;
3099 char funcname[11];
3101 sscanf(param,"%10[A-Z]%n%*1[1-3]%n",funcname,(int *)&i1,(int *)&i2);
3102 gdp->gf=gf_conv(funcname);
3103 if ((int)gdp->gf == -1) {
3104 rrd_set_error("'%s' is not a valid function name",funcname);
3105 return 0;
3106 }
3107 if (gdp->gf==GF_LINE) {
3108 if (i2) {
3109 gdp->linewidth=param[i1]-'0';
3110 } else {
3111 rrd_set_error("LINE should have a width");
3112 return 0;
3113 }
3114 } else {
3115 if (i2) {
3116 rrd_set_error("Only LINE should have a width: %s",param);
3117 return 0;
3118 } else {
3119 i2=i1;
3120 }
3121 }
3122 if (strlen(param) != i2) {
3123 rrd_set_error("Garbage after function name: %s",param);
3124 return 0;
3125 }
3126 return 1;
3127 }
3128 /* rrd_split_line()
3129 **
3130 ** Takes a string as input; splits this line into multiple
3131 ** parameters on each ":" boundary.
3132 **
3133 ** If this function returns successful, the caller will have
3134 ** to free() the allocated memory for param.
3135 **
3136 ** The input string is destroyed, its memory is used by the
3137 ** output array.
3138 */
3139 static int
3140 rrd_split_line(char *line,char ***param)
3141 {
3142 int i=0,n=0,quoted=0;
3143 char *src=line;
3144 char *dst=line;
3146 /* scan the amount of colons in the line. We need
3147 ** at most this amount+1 pointers for the array. If
3148 ** any colons are escaped we waste some space.
3149 */
3150 if (*src!='\0') n=1;
3152 while (*src != '\0')
3153 if (*src++ == ':') n++;
3155 if (n==0) {
3156 rrd_set_error("No line to split. rrd_split_line was given the empty string.");
3157 return -1;
3158 }
3160 src = line;
3162 /* Allocate memory for an array of n char pointers */
3163 *param=calloc(n,sizeof(char *));
3164 if (*param==NULL) {
3165 rrd_set_error("Memory allocation failed inside rrd_split_line");
3166 return -1;
3167 }
3169 /* split the line and fill the array */
3171 i=0;
3172 (*param)[i] = dst;
3173 while (*src != '\0') {
3174 switch (*src) {
3175 case '\'':
3176 quoted^=1;
3177 src++;
3178 break;
3179 case '\\': /* could be \: but also \n */
3180 src++;
3181 switch (*src) {
3182 case '\0':
3183 free(*param);
3184 rrd_set_error("Lone backslash inside rrd_split_line");
3185 return -1;
3186 case ':':
3187 *dst++=*src++;
3188 break;
3189 default:
3190 *dst++='\\';
3191 *dst++=*src++;
3192 }
3193 break;
3194 case ':':
3195 if (quoted) *dst++=*src++;
3196 else {
3197 *dst++ = '\0';
3198 src++;
3199 i++;
3200 (*param)[i] = dst;
3201 }
3202 break;
3203 default:
3204 *dst++=*src++;
3205 }
3206 }
3207 *dst='\0';
3208 i++; /* i separators means i+1 parameters */
3210 return i;
3211 }
3213 void
3214 rrd_graph_script_parse_def(int argc,char *argv[],int used,graph_desc_t *gdp) {
3215 time_t start_tmp=0,end_tmp=0;
3216 struct time_value start_tv, end_tv;
3217 char *parsetime_error = NULL;
3218 struct option long_options[] = {
3219 { "step", required_argument, NULL, 256 },
3220 { "start", required_argument, NULL, 's' },
3221 { "end", required_argument, NULL, 'e' },
3222 { NULL,0,NULL,0 }};
3223 int option_index = 0;
3224 int opt;
3226 opterr=0;
3227 optind=used;
3229 start_tv.type=ABSOLUTE_TIME;
3230 start_tv.offset=0;
3231 end_tv.type=ABSOLUTE_TIME;
3232 end_tv.offset=0;
3233 memcpy(&start_tv.tm, gmtime(&gdp->start) , sizeof(struct tm) );
3234 memcpy(&end_tv.tm, gmtime(&gdp->end) , sizeof(struct tm) );
3236 while (1) {
3237 opt = getopt_long(argc,argv,"-s:e:", long_options,&option_index);
3238 if (opt==-1) break;
3239 switch (opt) {
3240 case 1:
3241 printf("DEBUG: found non-option[%i] %s\n",optind,optarg);
3242 break;
3243 case 's':
3244 if ((parsetime_error = parsetime(optarg, &start_tv))) {
3245 rrd_set_error( "DEF start time: %s", parsetime_error );
3246 return;
3247 }
3248 break;
3249 case 'e':
3250 if ((parsetime_error = parsetime(optarg, &end_tv))) {
3251 rrd_set_error( "DEF end time: %s", parsetime_error );
3252 return;
3253 }
3254 break;
3255 case 256:
3256 gdp->step = atol(optarg);
3257 break;
3258 case '?':
3259 printf("DEBUG: unrecognized option %s\n",argv[optind]);
3260 break;
3261 case ':':
3262 printf("DEBUG: option %c needs parameter\n",optopt);
3263 break;
3264 default:
3265 printf("DEBUG: getopt returned unknown char %c\n",opt);
3266 break;
3267 }
3268 }
3269 if (optind < argc) {
3270 printf("DEBUG: %i remaining parameters:\n",argc-optind);
3271 while (optind<argc)
3272 printf("DEBUG: %3i: %s\n",optind,argv[optind++]);
3273 printf("\n");
3274 }
3276 if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
3277 /* error string is set in parsetime.c */
3278 return;
3279 }
3281 if (start_tmp < 3600*24*365*10){
3282 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
3283 return;
3284 }
3286 if (end_tmp < start_tmp) {
3287 rrd_set_error("start (%ld) should be less than end (%ld)",
3288 start_tmp, end_tmp);
3289 return;
3290 }
3292 gdp->start = start_tmp;
3293 gdp->end = end_tmp;
3294 }
3296 void
3297 rrd_graph_script(int argc, char *argv[], image_desc_t *im)
3298 {
3299 int i;
3300 char symname[100];
3301 int linepass = 0; /* stack must follow LINE*, AREA or STACK */
3302 char ** param;
3303 int paramcnt,paramused;
3305 for (i=optind+1;i<argc;i++) {
3306 int argstart=0;
3307 int strstart=0;
3308 graph_desc_t *gdp;
3309 char *line;
3310 char tmpline[256];
3311 char vname[MAX_VNAME_LEN+1],sep[1];
3312 /* double d; */
3313 int j,k,l/*,m*/;
3315 /* Each command is one element from *argv[]. This command is
3316 ** split at every unescaped colon.
3317 **
3318 ** Each command defines the most current gdes inside struct im.
3319 ** In stead of typing "im->gdes[im->gdes_c-1]" we use "gdp".
3320 */
3321 gdes_alloc(im);
3322 gdp=&im->gdes[im->gdes_c-1];
3323 strcpy(tmpline,argv[i]);
3324 line=tmpline;
3325 if ((paramcnt=rrd_split_line(argv[i],¶m))==-1) return;
3326 paramused=0;
3328 #ifdef DEBUG
3329 printf("DEBUG: after splitting line:\n");
3330 for (j=0;j<paramcnt;j++)
3331 printf("DEBUG: %3i: %s\n",j,param[j]);
3332 #endif
3334 if (!rrd_find_function(param[paramused],gdp)) {
3335 im_free(im);
3336 free(param);
3337 return;
3338 }
3339 paramused++;
3341 /* function:newvname=string[:ds-name:CF] for xDEF
3342 ** function:vname[#color[:string]] for LINEx,AREA,STACK
3343 ** function:vname#color[:num[:string]] for TICK
3344 ** function:vname-or-num#color[:string] for xRULE,PART
3345 ** function:vname:CF:string for xPRINT
3346 ** function:string for COMMENT
3347 */
3349 /*TEMP*/argstart=strlen(param[paramused-1])+1;
3351 /* If anything fails just use rrd_set_error() and break from the
3352 ** switch. Just after the switch we call rrd_test_error() and
3353 ** clean up if it is set.
3354 */
3355 switch (gdp->gf) {
3356 case GF_XPORT:
3357 break;
3358 case GF_COMMENT:
3359 if (paramcnt<2) {
3360 rrd_set_error("Not enough parameters for %s",param[0]);
3361 break;
3362 }
3363 if (strlen(param[1])>FMT_LEG_LEN) {
3364 rrd_set_error("Comment too long: %s:%s",param[0],param[1]);
3365 break;
3366 }
3367 strcpy(gdp->legend,param[1]);
3368 paramused++;
3369 break;
3370 case GF_PART:
3371 case GF_VRULE:
3372 case GF_HRULE:
3373 if (paramcnt<2) {
3374 rrd_set_error("No name or number in %s",param[0]);
3375 break;
3376 }
3377 j=rrd_vname_color(im,param[1],
3378 &gdp->xrule,&gdp->yrule,&gdp->col);
3379 paramused++;
3380 if (!j) break; /* error string set by function */
3381 switch (j&0x0F) {
3382 case 0x00:
3383 rrd_set_error("Cannot parse name nor number "
3384 "in %s:%s",param[0],param[1]);
3385 break;
3386 case 0x08:
3387 case 0x09:
3388 rrd_set_error("Cannot use DEF or CDEF based "
3389 "variable in %s:%s",param[0],param[1]);
3390 break;
3391 case 0x0A:
3392 gdp->vidx=gdp->xrule;
3393 gdp->xrule=0;
3394 gdp->yrule=DNAN;
3395 break;
3396 case 0x01:
3397 case 0x02:
3398 break;
3399 default:
3400 rrd_set_error("Unexpected result while parsing "
3401 "%s:%s, program error",param[0],param[1]);
3402 }
3403 if (rrd_test_error()) break;
3405 if (paramcnt>paramused) {
3406 if (strlen(param[paramused])>FMT_LEG_LEN) {
3407 rrd_set_error("Comment too long: %s:%s",
3408 param[0],param[1]);
3409 break;
3410 }
3411 strcpy(gdp->legend,param[paramused]);
3412 paramused++;
3413 }
3414 break;
3415 case GF_STACK:
3416 if (linepass==0) {
3417 rrd_set_error("STACK must follow another graphing element");
3418 break;
3419 }
3420 case GF_LINE:
3421 case GF_AREA:
3422 case GF_TICK:
3423 /* LINEx:vname[#color[:legend]][:STACK]
3424 ** AREA:vname[#color[:legend]][:STACK]
3425 ** STACK:vname[#color[:legend]]
3426 ** TICK:vname#color[:num[:legend]]
3427 */
3428 linepass=1;
3429 j=rrd_vname_color(im,param[paramused++],
3430 &gdp->vidx,&gdp->yrule,&gdp->col);
3431 if (!j) break; /* error string set by function */
3432 switch (j&0x0F) {
3433 case 0x00:
3434 rrd_set_error("Cannot parse name nor number "
3435 "in %s:%s",param[0],param[1]);
3436 break;
3437 case 0x01:
3438 case 0x02:
3439 rrd_set_error("Cannot %s a number",param[0]);
3440 break;
3441 case 0x08:
3442 case 0x09:
3443 break;
3444 case 0x0A:
3445 rrd_set_error("Cannot use VDEF based variable "
3446 "with %s %s",param[0],gdp->vname);
3447 break;
3448 }
3449 if (rrd_test_error()) break;
3451 if (gdp->gf == GF_TICK) {
3452 if (!(j&0x10)) {
3453 rrd_set_error("Color not optional for TICK");
3454 break;
3455 } else { /* parse optional number */
3456 k=l=0;
3457 sscanf(param[paramused], "%lf%n%*s%n",
3458 &gdp->yrule,&k,&l);
3459 if ((k!=0)&&(l==0)) paramused++;
3460 if (paramused<paramcnt)
3461 strcpy(gdp->legend,param[paramused++]);
3462 }
3463 } else {
3464 if (j&0x10) { /* color present */
3465 /* next should be legend or STACK. If it
3466 ** is STACK then leave it at is, else parse
3467 ** the legend (if any)
3468 */
3469 if (paramused<paramcnt)
3470 if (strcmp("STACK",param[paramused]))
3471 strcpy(gdp->legend,param[paramused++]);
3472 }
3473 if (paramused<paramcnt)
3474 if (!strcmp("STACK",param[paramused])) {
3475 gdp->stack=1;
3476 paramused++;
3477 }
3478 }
3479 break;
3480 case GF_PRINT:
3481 im->prt_c++;
3482 case GF_GPRINT:
3483 j=0;
3484 sscanf(&line[argstart], DEF_NAM_FMT ":%n",gdp->vname,&j);
3485 if (j==0) {
3486 rrd_set_error("Cannot parse vname in line: '%s'",line);
3487 break;
3488 }
3489 argstart+=j;
3490 if (rrd_graph_check_vname(im,gdp->vname,line)) return;
3491 j=0;
3492 sscanf(&line[argstart], CF_NAM_FMT ":%n",symname,&j);
3494 k=(j!=0)?rrd_graph_check_CF(im,symname,line):1;
3495 #define VIDX im->gdes[gdp->vidx]
3496 switch (k) {
3497 case -1: /* looks CF but is not really CF */
3498 if (VIDX.gf == GF_VDEF) rrd_clear_error();
3499 break;
3500 case 0: /* CF present and correct */
3501 if (VIDX.gf == GF_VDEF)
3502 rrd_set_error("Don't use CF when printing VDEF");
3503 argstart+=j;
3504 break;
3505 case 1: /* CF not present */
3506 if (VIDX.gf == GF_VDEF) rrd_clear_error();
3507 else rrd_set_error("Printing DEF or CDEF needs CF");
3508 break;
3509 default:
3510 rrd_set_error("Oops, bug in GPRINT scanning");
3511 }
3512 #undef VIDX
3513 if (rrd_test_error()) break;
3515 if (strlen(&line[argstart])!=0) {
3516 if (rrd_graph_legend(gdp,&line[argstart])==0)
3517 rrd_set_error("Cannot parse legend in line: %s",line);
3518 } else rrd_set_error("No legend in (G)PRINT line: %s",line);
3519 strcpy(gdp->format, gdp->legend);
3520 break;
3521 case GF_DEF:
3522 case GF_VDEF:
3523 case GF_CDEF:
3524 j=0;
3525 if (paramcnt<2) {
3526 rrd_set_error("Nothing following %s",param[0]);
3527 break;
3528 }
3529 sscanf(param[1], DEF_NAM_FMT "=%n",gdp->vname,&j);
3530 if (j==0) {
3531 rrd_set_error("Could not parse %s:%s",param[0],param[1]);
3532 break;
3533 }
3534 if (find_var(im,gdp->vname)!=-1) {
3535 rrd_set_error("Variable '%s' in %s:%s' already in use\n",
3536 gdp->vname,param[0],param[1]);
3537 break;
3538 }
3539 paramused++;
3540 argstart+=j; /* parsed upto and including "xDEF:vname=" */
3541 switch (gdp->gf) {
3542 case GF_DEF:
3543 if (strlen(¶m[1][j])>MAXPATH) {
3544 rrd_set_error("Path too long: %s:%s",param[0],param[1]);
3545 break;
3546 }
3547 strcpy(gdp->rrd,¶m[1][j]);
3549 if (paramcnt<3) {
3550 rrd_set_error("No DS for %s:%s",param[0],param[1]);
3551 break;
3552 }
3553 j=k=0;
3554 sscanf(param[2],DS_NAM_FMT "%n%*s%n",gdp->ds_nam,&j,&k);
3555 if ((j==0)||(k!=0)) {
3556 rrd_set_error("Cannot parse DS in %s:%s:%s",
3557 param[0],param[1],param[2]);
3558 break;
3559 }
3560 paramused++;
3561 if (paramcnt<4) {
3562 rrd_set_error("No CF for %s:%s:%s",
3563 param[0],param[1],param[2]);
3564 break;
3565 }
3566 j=k=0;
3567 sscanf(param[3],CF_NAM_FMT "%n%*s%n",symname,&j,&k);
3568 if ((j==0)||(k!=0)) {
3569 rrd_set_error("Cannot parse CF in %s:%s:%s:%s",
3570 param[0],param[1],param[2],param[3]);
3571 break;
3572 }
3573 if ((gdp->cf = cf_conv(symname))==-1) {
3574 rrd_set_error("Unknown CF '%s' in %s:%s:%s:%s",
3575 param[0],param[1],param[2],param[3]);
3576 break;
3577 }
3578 paramused++;
3579 if (paramcnt>paramused) { /* optional parameters */
3580 rrd_graph_script_parse_def(paramcnt,param,paramused,gdp);
3581 if (rrd_test_error()) break;
3582 }
3583 break;
3584 case GF_VDEF:
3585 k=0;
3586 sscanf(¶m[1][j],DEF_NAM_FMT ",%n",vname,&k);
3587 if (k==0) {
3588 rrd_set_error("Cannot parse vname: %s:%s",
3589 param[0],param[1]);
3590 break;
3591 }
3592 j+=k;
3593 if (rrd_graph_check_vname(im,vname,line)) return;
3594 if ( im->gdes[gdp->vidx].gf != GF_DEF
3595 && im->gdes[gdp->vidx].gf != GF_CDEF) {
3596 rrd_set_error("variable '%s' not DEF nor "
3597 "CDEF in VDEF '%s'", vname,gdp->vname);
3598 break;
3599 }
3600 vdef_parse(gdp,¶m[1][j]);
3601 break;
3602 case GF_CDEF:
3603 if ((gdp->rpnp = rpn_parse(
3604 (void *)im,
3605 ¶m[1][j],
3606 &find_var_wrapper)
3607 )==NULL)
3608 rrd_set_error("invalid rpn expression in: %s",param[1]);
3609 break;
3610 default: break;
3611 }
3612 break;
3613 default: rrd_set_error("Big oops");
3614 }
3615 if (rrd_test_error()) {
3616 im_free(im);
3617 return;
3618 }
3619 }
3621 if (im->gdes_c==0){
3622 rrd_set_error("can't make a graph without contents");
3623 im_free(im); /* ??? is this set ??? */
3624 return;
3625 }
3626 }
3628 int
3629 rrd_graph_check_vname(image_desc_t *im, char *varname, char *err)
3630 {
3631 if ((im->gdes[im->gdes_c-1].vidx=find_var(im,varname))==-1) {
3632 rrd_set_error("Unknown variable '%s' in %s",varname,err);
3633 return -1;
3634 }
3635 return 0;
3636 }
3637 int
3638 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
3639 {
3640 char *color;
3641 graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
3643 color=strstr(var,"#");
3644 if (color==NULL) {
3645 if (optional==0) {
3646 rrd_set_error("Found no color in %s",err);
3647 return 0;
3648 }
3649 return 0;
3650 } else {
3651 int n=0;
3652 char *rest;
3653 gfx_color_t col;
3655 rest=strstr(color,":");
3656 if (rest!=NULL)
3657 n=rest-color;
3658 else
3659 n=strlen(color);
3661 switch (n) {
3662 case 7:
3663 sscanf(color,"#%6lx%n",&col,&n);
3664 col = (col << 8) + 0xff /* shift left by 8 */;
3665 if (n!=7) rrd_set_error("Color problem in %s",err);
3666 break;
3667 case 9:
3668 sscanf(color,"#%8lx%n",&col,&n);
3669 if (n==9) break;
3670 default:
3671 rrd_set_error("Color problem in %s",err);
3672 }
3673 if (rrd_test_error()) return 0;
3674 gdp->col = col;
3675 return n;
3676 }
3677 }
3678 int
3679 rrd_graph_check_CF(image_desc_t *im, char *symname, char *err)
3680 {
3681 if ((im->gdes[im->gdes_c-1].cf=cf_conv(symname))==-1) {
3682 rrd_set_error("Unknown CF '%s' in %s",symname,err);
3683 return -1;
3684 }
3685 return 0;
3686 }
3687 int
3688 rrd_graph_legend(graph_desc_t *gdp, char *line)
3689 {
3690 int i;
3692 i=scan_for_col(line,FMT_LEG_LEN,gdp->legend);
3694 return (strlen(&line[i])==0);
3695 }
3698 int bad_format(char *fmt) {
3699 char *ptr;
3700 int n=0;
3701 ptr = fmt;
3702 while (*ptr != '\0')
3703 if (*ptr++ == '%') {
3705 /* line cannot end with percent char */
3706 if (*ptr == '\0') return 1;
3708 /* '%s', '%S' and '%%' are allowed */
3709 if (*ptr == 's' || *ptr == 'S' || *ptr == '%') ptr++;
3711 /* or else '% 6.2lf' and such are allowed */
3712 else {
3714 /* optional padding character */
3715 if (*ptr == ' ' || *ptr == '+' || *ptr == '-') ptr++;
3717 /* This should take care of 'm.n' with all three optional */
3718 while (*ptr >= '0' && *ptr <= '9') ptr++;
3719 if (*ptr == '.') ptr++;
3720 while (*ptr >= '0' && *ptr <= '9') ptr++;
3722 /* Either 'le' or 'lf' must follow here */
3723 if (*ptr++ != 'l') return 1;
3724 if (*ptr == 'e' || *ptr == 'f') ptr++;
3725 else return 1;
3726 n++;
3727 }
3728 }
3730 return (n!=1);
3731 }
3734 int
3735 vdef_parse(gdes,str)
3736 struct graph_desc_t *gdes;
3737 char *str;
3738 {
3739 /* A VDEF currently is either "func" or "param,func"
3740 * so the parsing is rather simple. Change if needed.
3741 */
3742 double param;
3743 char func[30];
3744 int n;
3746 n=0;
3747 sscanf(str,"%le,%29[A-Z]%n",¶m,func,&n);
3748 if (n==strlen(str)) { /* matched */
3749 ;
3750 } else {
3751 n=0;
3752 sscanf(str,"%29[A-Z]%n",func,&n);
3753 if (n==strlen(str)) { /* matched */
3754 param=DNAN;
3755 } else {
3756 rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3757 ,str
3758 ,gdes->vname
3759 );
3760 return -1;
3761 }
3762 }
3763 if (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3764 else if (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3765 else if (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3766 else if (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3767 else if (!strcmp("TOTAL", func)) gdes->vf.op = VDEF_TOTAL;
3768 else if (!strcmp("FIRST", func)) gdes->vf.op = VDEF_FIRST;
3769 else if (!strcmp("LAST", func)) gdes->vf.op = VDEF_LAST;
3770 else {
3771 rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3772 ,func
3773 ,gdes->vname
3774 );
3775 return -1;
3776 };
3778 switch (gdes->vf.op) {
3779 case VDEF_PERCENT:
3780 if (isnan(param)) { /* no parameter given */
3781 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3782 ,func
3783 ,gdes->vname
3784 );
3785 return -1;
3786 };
3787 if (param>=0.0 && param<=100.0) {
3788 gdes->vf.param = param;
3789 gdes->vf.val = DNAN; /* undefined */
3790 gdes->vf.when = 0; /* undefined */
3791 } else {
3792 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3793 ,param
3794 ,gdes->vname
3795 );
3796 return -1;
3797 };
3798 break;
3799 case VDEF_MAXIMUM:
3800 case VDEF_AVERAGE:
3801 case VDEF_MINIMUM:
3802 case VDEF_TOTAL:
3803 case VDEF_FIRST:
3804 case VDEF_LAST:
3805 if (isnan(param)) {
3806 gdes->vf.param = DNAN;
3807 gdes->vf.val = DNAN;
3808 gdes->vf.when = 0;
3809 } else {
3810 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3811 ,func
3812 ,gdes->vname
3813 );
3814 return -1;
3815 };
3816 break;
3817 };
3818 return 0;
3819 }
3822 int
3823 vdef_calc(im,gdi)
3824 image_desc_t *im;
3825 int gdi;
3826 {
3827 graph_desc_t *src,*dst;
3828 rrd_value_t *data;
3829 long step,steps;
3831 dst = &im->gdes[gdi];
3832 src = &im->gdes[dst->vidx];
3833 data = src->data + src->ds;
3834 steps = (src->end - src->start) / src->step;
3836 #if 0
3837 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3838 ,src->start
3839 ,src->end
3840 ,steps
3841 );
3842 #endif
3844 switch (dst->vf.op) {
3845 case VDEF_PERCENT: {
3846 rrd_value_t * array;
3847 int field;
3850 if ((array = malloc(steps*sizeof(double)))==NULL) {
3851 rrd_set_error("malloc VDEV_PERCENT");
3852 return -1;
3853 }
3854 for (step=0;step < steps; step++) {
3855 array[step]=data[step*src->ds_cnt];
3856 }
3857 qsort(array,step,sizeof(double),vdef_percent_compar);
3859 field = (steps-1)*dst->vf.param/100;
3860 dst->vf.val = array[field];
3861 dst->vf.when = 0; /* no time component */
3862 #if 0
3863 for(step=0;step<steps;step++)
3864 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3865 #endif
3866 }
3867 break;
3868 case VDEF_MAXIMUM:
3869 step=0;
3870 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3871 if (step == steps) {
3872 dst->vf.val = DNAN;
3873 dst->vf.when = 0;
3874 } else {
3875 dst->vf.val = data[step*src->ds_cnt];
3876 dst->vf.when = src->start + (step+1)*src->step;
3877 }
3878 while (step != steps) {
3879 if (finite(data[step*src->ds_cnt])) {
3880 if (data[step*src->ds_cnt] > dst->vf.val) {
3881 dst->vf.val = data[step*src->ds_cnt];
3882 dst->vf.when = src->start + (step+1)*src->step;
3883 }
3884 }
3885 step++;
3886 }
3887 break;
3888 case VDEF_TOTAL:
3889 case VDEF_AVERAGE: {
3890 int cnt=0;
3891 double sum=0.0;
3892 for (step=0;step<steps;step++) {
3893 if (finite(data[step*src->ds_cnt])) {
3894 sum += data[step*src->ds_cnt];
3895 cnt ++;
3896 };
3897 }
3898 if (cnt) {
3899 if (dst->vf.op == VDEF_TOTAL) {
3900 dst->vf.val = sum*src->step;
3901 dst->vf.when = cnt*src->step; /* not really "when" */
3902 } else {
3903 dst->vf.val = sum/cnt;
3904 dst->vf.when = 0; /* no time component */
3905 };
3906 } else {
3907 dst->vf.val = DNAN;
3908 dst->vf.when = 0;
3909 }
3910 }
3911 break;
3912 case VDEF_MINIMUM:
3913 step=0;
3914 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3915 if (step == steps) {
3916 dst->vf.val = DNAN;
3917 dst->vf.when = 0;
3918 } else {
3919 dst->vf.val = data[step*src->ds_cnt];
3920 dst->vf.when = src->start + (step+1)*src->step;
3921 }
3922 while (step != steps) {
3923 if (finite(data[step*src->ds_cnt])) {
3924 if (data[step*src->ds_cnt] < dst->vf.val) {
3925 dst->vf.val = data[step*src->ds_cnt];
3926 dst->vf.when = src->start + (step+1)*src->step;
3927 }
3928 }
3929 step++;
3930 }
3931 break;
3932 case VDEF_FIRST:
3933 /* The time value returned here is one step before the
3934 * actual time value. This is the start of the first
3935 * non-NaN interval.
3936 */
3937 step=0;
3938 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3939 if (step == steps) { /* all entries were NaN */
3940 dst->vf.val = DNAN;
3941 dst->vf.when = 0;
3942 } else {
3943 dst->vf.val = data[step*src->ds_cnt];
3944 dst->vf.when = src->start + step*src->step;
3945 }
3946 break;
3947 case VDEF_LAST:
3948 /* The time value returned here is the
3949 * actual time value. This is the end of the last
3950 * non-NaN interval.
3951 */
3952 step=steps-1;
3953 while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3954 if (step < 0) { /* all entries were NaN */
3955 dst->vf.val = DNAN;
3956 dst->vf.when = 0;
3957 } else {
3958 dst->vf.val = data[step*src->ds_cnt];
3959 dst->vf.when = src->start + (step+1)*src->step;
3960 }
3961 break;
3962 }
3963 return 0;
3964 }
3966 /* NaN < -INF < finite_values < INF */
3967 int
3968 vdef_percent_compar(a,b)
3969 const void *a,*b;
3970 {
3971 /* Equality is not returned; this doesn't hurt except
3972 * (maybe) for a little performance.
3973 */
3975 /* First catch NaN values. They are smallest */
3976 if (isnan( *(double *)a )) return -1;
3977 if (isnan( *(double *)b )) return 1;
3979 /* NaN doesn't reach this part so INF and -INF are extremes.
3980 * The sign from isinf() is compatible with the sign we return
3981 */
3982 if (isinf( *(double *)a )) return isinf( *(double *)a );
3983 if (isinf( *(double *)b )) return isinf( *(double *)b );
3985 /* If we reach this, both values must be finite */
3986 if ( *(double *)a < *(double *)b ) return -1; else return 1;
3987 }