f0ec67e880b1930d1b7cf7f3e85a2d054f5d34d0
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++){
995 long vidx;
996 gr_time = im->start+pixstep*i; /* time of the
997 current step */
998 paintval=0.0;
1000 for(ii=0;ii<im->gdes_c;ii++){
1001 double value;
1002 switch(im->gdes[ii].gf){
1003 case GF_LINE:
1004 case GF_AREA:
1005 case GF_TICK:
1006 paintval = 0.0;
1007 case GF_STACK:
1008 vidx = im->gdes[ii].vidx;
1010 value =
1011 im->gdes[vidx].data[
1012 ((unsigned long)floor(
1013 (double)(gr_time-im->gdes[vidx].start) / im->gdes[vidx].step
1014 )
1015 ) *im->gdes[vidx].ds_cnt
1016 +im->gdes[vidx].ds];
1018 if (! isnan(value)) {
1019 paintval += value;
1020 im->gdes[ii].p_data[i] = paintval;
1021 /* GF_TICK: the data values are not relevant for min and max */
1022 if (finite(paintval) && im->gdes[ii].gf != GF_TICK ){
1023 if (isnan(minval) || paintval < minval)
1024 minval = paintval;
1025 if (isnan(maxval) || paintval > maxval)
1026 maxval = paintval;
1027 }
1028 } else {
1029 im->gdes[ii].p_data[i] = DNAN;
1030 }
1031 break;
1032 case GF_PRINT:
1033 case GF_GPRINT:
1034 case GF_COMMENT:
1035 case GF_HRULE:
1036 case GF_VRULE:
1037 case GF_DEF:
1038 case GF_CDEF:
1039 case GF_VDEF:
1040 case GF_PART:
1041 case GF_XPORT:
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 || ((!im->logarithmic && !im->rigid) /* don't adjust low-end with log scale */
1057 && im->minval > minval))
1058 im->minval = minval;
1059 if (isnan(im->maxval)
1060 || (!im->rigid
1061 && im->maxval < maxval)){
1062 if (im->logarithmic)
1063 im->maxval = maxval * 1.1;
1064 else
1065 im->maxval = maxval;
1066 }
1067 /* make sure min and max are not equal */
1068 if (im->minval == im->maxval) {
1069 im->maxval *= 1.01;
1070 if (! im->logarithmic) {
1071 im->minval *= 0.99;
1072 }
1074 /* make sure min and max are not both zero */
1075 if (im->maxval == 0.0) {
1076 im->maxval = 1.0;
1077 }
1079 }
1080 return 0;
1081 }
1085 /* identify the point where the first gridline, label ... gets placed */
1087 time_t
1088 find_first_time(
1089 time_t start, /* what is the initial time */
1090 enum tmt_en baseint, /* what is the basic interval */
1091 long basestep /* how many if these do we jump a time */
1092 )
1093 {
1094 struct tm tm;
1095 tm = *localtime(&start);
1096 switch(baseint){
1097 case TMT_SECOND:
1098 tm.tm_sec -= tm.tm_sec % basestep; break;
1099 case TMT_MINUTE:
1100 tm.tm_sec=0;
1101 tm.tm_min -= tm.tm_min % basestep;
1102 break;
1103 case TMT_HOUR:
1104 tm.tm_sec=0;
1105 tm.tm_min = 0;
1106 tm.tm_hour -= tm.tm_hour % basestep; break;
1107 case TMT_DAY:
1108 /* we do NOT look at the basestep for this ... */
1109 tm.tm_sec=0;
1110 tm.tm_min = 0;
1111 tm.tm_hour = 0; break;
1112 case TMT_WEEK:
1113 /* we do NOT look at the basestep for this ... */
1114 tm.tm_sec=0;
1115 tm.tm_min = 0;
1116 tm.tm_hour = 0;
1117 tm.tm_mday -= tm.tm_wday -1; /* -1 because we want the monday */
1118 if (tm.tm_wday==0) tm.tm_mday -= 7; /* we want the *previous* monday */
1119 break;
1120 case TMT_MONTH:
1121 tm.tm_sec=0;
1122 tm.tm_min = 0;
1123 tm.tm_hour = 0;
1124 tm.tm_mday = 1;
1125 tm.tm_mon -= tm.tm_mon % basestep; break;
1127 case TMT_YEAR:
1128 tm.tm_sec=0;
1129 tm.tm_min = 0;
1130 tm.tm_hour = 0;
1131 tm.tm_mday = 1;
1132 tm.tm_mon = 0;
1133 tm.tm_year -= (tm.tm_year+1900) % basestep;
1135 }
1136 return mktime(&tm);
1137 }
1138 /* identify the point where the next gridline, label ... gets placed */
1139 time_t
1140 find_next_time(
1141 time_t current, /* what is the initial time */
1142 enum tmt_en baseint, /* what is the basic interval */
1143 long basestep /* how many if these do we jump a time */
1144 )
1145 {
1146 struct tm tm;
1147 time_t madetime;
1148 tm = *localtime(¤t);
1149 do {
1150 switch(baseint){
1151 case TMT_SECOND:
1152 tm.tm_sec += basestep; break;
1153 case TMT_MINUTE:
1154 tm.tm_min += basestep; break;
1155 case TMT_HOUR:
1156 tm.tm_hour += basestep; break;
1157 case TMT_DAY:
1158 tm.tm_mday += basestep; break;
1159 case TMT_WEEK:
1160 tm.tm_mday += 7*basestep; break;
1161 case TMT_MONTH:
1162 tm.tm_mon += basestep; break;
1163 case TMT_YEAR:
1164 tm.tm_year += basestep;
1165 }
1166 madetime = mktime(&tm);
1167 } while (madetime == -1); /* this is necessary to skip impssible times
1168 like the daylight saving time skips */
1169 return madetime;
1171 }
1174 /* calculate values required for PRINT and GPRINT functions */
1176 int
1177 print_calc(image_desc_t *im, char ***prdata)
1178 {
1179 long i,ii,validsteps;
1180 double printval;
1181 time_t printtime;
1182 int graphelement = 0;
1183 long vidx;
1184 int max_ii;
1185 double magfact = -1;
1186 char *si_symb = "";
1187 char *percent_s;
1188 int prlines = 1;
1189 if (im->imginfo) prlines++;
1190 for(i=0;i<im->gdes_c;i++){
1191 switch(im->gdes[i].gf){
1192 case GF_PRINT:
1193 prlines++;
1194 if(((*prdata) = rrd_realloc((*prdata),prlines*sizeof(char *)))==NULL){
1195 rrd_set_error("realloc prdata");
1196 return 0;
1197 }
1198 case GF_GPRINT:
1199 /* PRINT and GPRINT can now print VDEF generated values.
1200 * There's no need to do any calculations on them as these
1201 * calculations were already made.
1202 */
1203 vidx = im->gdes[i].vidx;
1204 if (im->gdes[vidx].gf==GF_VDEF) { /* simply use vals */
1205 printval = im->gdes[vidx].vf.val;
1206 printtime = im->gdes[vidx].vf.when;
1207 } else { /* need to calculate max,min,avg etcetera */
1208 max_ii =((im->gdes[vidx].end
1209 - im->gdes[vidx].start)
1210 / im->gdes[vidx].step
1211 * im->gdes[vidx].ds_cnt);
1212 printval = DNAN;
1213 validsteps = 0;
1214 for( ii=im->gdes[vidx].ds;
1215 ii < max_ii;
1216 ii+=im->gdes[vidx].ds_cnt){
1217 if (! finite(im->gdes[vidx].data[ii]))
1218 continue;
1219 if (isnan(printval)){
1220 printval = im->gdes[vidx].data[ii];
1221 validsteps++;
1222 continue;
1223 }
1225 switch (im->gdes[i].cf){
1226 case CF_HWPREDICT:
1227 case CF_DEVPREDICT:
1228 case CF_DEVSEASONAL:
1229 case CF_SEASONAL:
1230 case CF_AVERAGE:
1231 validsteps++;
1232 printval += im->gdes[vidx].data[ii];
1233 break;
1234 case CF_MINIMUM:
1235 printval = min( printval, im->gdes[vidx].data[ii]);
1236 break;
1237 case CF_FAILURES:
1238 case CF_MAXIMUM:
1239 printval = max( printval, im->gdes[vidx].data[ii]);
1240 break;
1241 case CF_LAST:
1242 printval = im->gdes[vidx].data[ii];
1243 }
1244 }
1245 if (im->gdes[i].cf==CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1246 if (validsteps > 1) {
1247 printval = (printval / validsteps);
1248 }
1249 }
1250 } /* prepare printval */
1252 if (!strcmp(im->gdes[i].format,"%c")) { /* VDEF time print */
1253 if (im->gdes[i].gf == GF_PRINT){
1254 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1255 sprintf((*prdata)[prlines-2],"%s (%lu)",
1256 ctime(&printtime),printtime);
1257 (*prdata)[prlines-1] = NULL;
1258 } else {
1259 sprintf(im->gdes[i].legend,"%s (%lu)",
1260 ctime(&printtime),printtime);
1261 graphelement = 1;
1262 }
1263 } else {
1264 if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) {
1265 /* Magfact is set to -1 upon entry to print_calc. If it
1266 * is still less than 0, then we need to run auto_scale.
1267 * Otherwise, put the value into the correct units. If
1268 * the value is 0, then do not set the symbol or magnification
1269 * so next the calculation will be performed again. */
1270 if (magfact < 0.0) {
1271 auto_scale(im,&printval,&si_symb,&magfact);
1272 if (printval == 0.0)
1273 magfact = -1.0;
1274 } else {
1275 printval /= magfact;
1276 }
1277 *(++percent_s) = 's';
1278 } else if (strstr(im->gdes[i].format,"%s") != NULL) {
1279 auto_scale(im,&printval,&si_symb,&magfact);
1280 }
1282 if (im->gdes[i].gf == GF_PRINT){
1283 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1284 if (bad_format(im->gdes[i].format)) {
1285 rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1286 return -1;
1287 }
1288 #ifdef HAVE_SNPRINTF
1289 snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
1290 #else
1291 sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
1292 #endif
1293 (*prdata)[prlines-1] = NULL;
1294 } else {
1295 /* GF_GPRINT */
1297 if (bad_format(im->gdes[i].format)) {
1298 rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1299 return -1;
1300 }
1301 #ifdef HAVE_SNPRINTF
1302 snprintf(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
1303 #else
1304 sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
1305 #endif
1306 graphelement = 1;
1307 }
1308 }
1309 break;
1310 case GF_LINE:
1311 case GF_AREA:
1312 case GF_TICK:
1313 case GF_STACK:
1314 case GF_HRULE:
1315 case GF_VRULE:
1316 graphelement = 1;
1317 break;
1318 case GF_COMMENT:
1319 case GF_DEF:
1320 case GF_CDEF:
1321 case GF_VDEF:
1322 case GF_PART:
1323 case GF_XPORT:
1324 break;
1325 }
1326 }
1327 return graphelement;
1328 }
1331 /* place legends with color spots */
1332 int
1333 leg_place(image_desc_t *im)
1334 {
1335 /* graph labels */
1336 int interleg = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1337 int box =im->text_prop[TEXT_PROP_LEGEND].size*1.5;
1338 int border = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1339 int fill=0, fill_last;
1340 int leg_c = 0;
1341 int leg_x = border, leg_y = im->yimg;
1342 int leg_cc;
1343 int glue = 0;
1344 int i,ii, mark = 0;
1345 char prt_fctn; /*special printfunctions */
1346 int *legspace;
1348 if( !(im->extra_flags & NOLEGEND) ) {
1349 if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1350 rrd_set_error("malloc for legspace");
1351 return -1;
1352 }
1354 for(i=0;i<im->gdes_c;i++){
1355 fill_last = fill;
1357 leg_cc = strlen(im->gdes[i].legend);
1359 /* is there a controle code ant the end of the legend string ? */
1360 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\') {
1361 prt_fctn = im->gdes[i].legend[leg_cc-1];
1362 leg_cc -= 2;
1363 im->gdes[i].legend[leg_cc] = '\0';
1364 } else {
1365 prt_fctn = '\0';
1366 }
1367 /* remove exess space */
1368 while (prt_fctn=='g' &&
1369 leg_cc > 0 &&
1370 im->gdes[i].legend[leg_cc-1]==' '){
1371 leg_cc--;
1372 im->gdes[i].legend[leg_cc]='\0';
1373 }
1374 if (leg_cc != 0 ){
1375 legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1377 if (fill > 0){
1378 /* no interleg space if string ends in \g */
1379 fill += legspace[i];
1380 }
1381 if (im->gdes[i].gf != GF_GPRINT &&
1382 im->gdes[i].gf != GF_COMMENT) {
1383 fill += box;
1384 }
1385 fill += gfx_get_text_width(im->canvas, fill+border,
1386 im->text_prop[TEXT_PROP_LEGEND].font,
1387 im->text_prop[TEXT_PROP_LEGEND].size,
1388 im->tabwidth,
1389 im->gdes[i].legend);
1390 leg_c++;
1391 } else {
1392 legspace[i]=0;
1393 }
1394 /* who said there was a special tag ... ?*/
1395 if (prt_fctn=='g') {
1396 prt_fctn = '\0';
1397 }
1398 if (prt_fctn == '\0') {
1399 if (i == im->gdes_c -1 ) prt_fctn ='l';
1401 /* is it time to place the legends ? */
1402 if (fill > im->ximg - 2*border){
1403 if (leg_c > 1) {
1404 /* go back one */
1405 i--;
1406 fill = fill_last;
1407 leg_c--;
1408 prt_fctn = 'j';
1409 } else {
1410 prt_fctn = 'l';
1411 }
1413 }
1414 }
1417 if (prt_fctn != '\0'){
1418 leg_x = border;
1419 if (leg_c >= 2 && prt_fctn == 'j') {
1420 glue = (im->ximg - fill - 2* border) / (leg_c-1);
1421 } else {
1422 glue = 0;
1423 }
1424 if (prt_fctn =='c') leg_x = (im->ximg - fill) / 2.0;
1425 if (prt_fctn =='r') leg_x = im->ximg - fill - border;
1427 for(ii=mark;ii<=i;ii++){
1428 if(im->gdes[ii].legend[0]=='\0')
1429 continue;
1430 im->gdes[ii].leg_x = leg_x;
1431 im->gdes[ii].leg_y = leg_y;
1432 leg_x +=
1433 gfx_get_text_width(im->canvas, leg_x,
1434 im->text_prop[TEXT_PROP_LEGEND].font,
1435 im->text_prop[TEXT_PROP_LEGEND].size,
1436 im->tabwidth,
1437 im->gdes[ii].legend)
1438 + legspace[ii]
1439 + glue;
1440 if (im->gdes[ii].gf != GF_GPRINT &&
1441 im->gdes[ii].gf != GF_COMMENT)
1442 leg_x += box;
1443 }
1444 leg_y = leg_y + im->text_prop[TEXT_PROP_LEGEND].size*1.2;
1445 if (prt_fctn == 's') leg_y -= im->text_prop[TEXT_PROP_LEGEND].size*1.2;
1446 fill = 0;
1447 leg_c = 0;
1448 mark = ii;
1449 }
1450 }
1451 im->yimg = leg_y;
1452 free(legspace);
1453 }
1454 return 0;
1455 }
1457 /* create a grid on the graph. it determines what to do
1458 from the values of xsize, start and end */
1460 /* the xaxis labels are determined from the number of seconds per pixel
1461 in the requested graph */
1465 int
1466 calc_horizontal_grid(image_desc_t *im)
1467 {
1468 double range;
1469 double scaledrange;
1470 int pixel,i;
1471 int gridind;
1472 int decimals, fractionals;
1474 im->ygrid_scale.labfact=2;
1475 gridind=-1;
1476 range = im->maxval - im->minval;
1477 scaledrange = range / im->magfact;
1479 /* does the scale of this graph make it impossible to put lines
1480 on it? If so, give up. */
1481 if (isnan(scaledrange)) {
1482 return 0;
1483 }
1485 /* find grid spaceing */
1486 pixel=1;
1487 if(isnan(im->ygridstep)){
1488 if(im->extra_flags & ALTYGRID) {
1489 /* find the value with max number of digits. Get number of digits */
1490 decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))));
1491 if(decimals <= 0) /* everything is small. make place for zero */
1492 decimals = 1;
1494 fractionals = floor(log10(range));
1495 if(fractionals < 0) /* small amplitude. */
1496 sprintf(im->ygrid_scale.labfmt, "%%%d.%df", decimals - fractionals + 1, -fractionals + 1);
1497 else
1498 sprintf(im->ygrid_scale.labfmt, "%%%d.1f", decimals + 1);
1499 im->ygrid_scale.gridstep = pow((double)10, (double)fractionals);
1500 if(im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1501 im->ygrid_scale.gridstep = 0.1;
1502 /* should have at least 5 lines but no more then 15 */
1503 if(range/im->ygrid_scale.gridstep < 5)
1504 im->ygrid_scale.gridstep /= 10;
1505 if(range/im->ygrid_scale.gridstep > 15)
1506 im->ygrid_scale.gridstep *= 10;
1507 if(range/im->ygrid_scale.gridstep > 5) {
1508 im->ygrid_scale.labfact = 1;
1509 if(range/im->ygrid_scale.gridstep > 8)
1510 im->ygrid_scale.labfact = 2;
1511 }
1512 else {
1513 im->ygrid_scale.gridstep /= 5;
1514 im->ygrid_scale.labfact = 5;
1515 }
1516 }
1517 else {
1518 for(i=0;ylab[i].grid > 0;i++){
1519 pixel = im->ysize / (scaledrange / ylab[i].grid);
1520 if (gridind == -1 && pixel > 5) {
1521 gridind = i;
1522 break;
1523 }
1524 }
1526 for(i=0; i<4;i++) {
1527 if (pixel * ylab[gridind].lfac[i] >= 2 * im->text_prop[TEXT_PROP_AXIS].size) {
1528 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1529 break;
1530 }
1531 }
1533 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1534 }
1535 } else {
1536 im->ygrid_scale.gridstep = im->ygridstep;
1537 im->ygrid_scale.labfact = im->ylabfact;
1538 }
1539 return 1;
1540 }
1542 int draw_horizontal_grid(image_desc_t *im)
1543 {
1544 int i;
1545 double scaledstep;
1546 char graph_label[100];
1547 double X0=im->xorigin;
1548 double X1=im->xorigin+im->xsize;
1550 int sgrid = (int)( im->minval / im->ygrid_scale.gridstep - 1);
1551 int egrid = (int)( im->maxval / im->ygrid_scale.gridstep + 1);
1552 scaledstep = im->ygrid_scale.gridstep/im->magfact;
1553 for (i = sgrid; i <= egrid; i++){
1554 double Y0=ytr(im,im->ygrid_scale.gridstep*i);
1555 if ( Y0 >= im->yorigin-im->ysize
1556 && Y0 <= im->yorigin){
1557 if(i % im->ygrid_scale.labfact == 0){
1558 if (i==0 || im->symbol == ' ') {
1559 if(scaledstep < 1){
1560 if(im->extra_flags & ALTYGRID) {
1561 sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*i);
1562 }
1563 else {
1564 sprintf(graph_label,"%4.1f",scaledstep*i);
1565 }
1566 } else {
1567 sprintf(graph_label,"%4.0f",scaledstep*i);
1568 }
1569 }else {
1570 if(scaledstep < 1){
1571 sprintf(graph_label,"%4.1f %c",scaledstep*i, im->symbol);
1572 } else {
1573 sprintf(graph_label,"%4.0f %c",scaledstep*i, im->symbol);
1574 }
1575 }
1577 gfx_new_text ( im->canvas,
1578 X0-im->text_prop[TEXT_PROP_AXIS].size/1.5, Y0,
1579 im->graph_col[GRC_FONT],
1580 im->text_prop[TEXT_PROP_AXIS].font,
1581 im->text_prop[TEXT_PROP_AXIS].size,
1582 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1583 graph_label );
1584 gfx_new_dashed_line ( im->canvas,
1585 X0-2,Y0,
1586 X1+2,Y0,
1587 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1588 im->grid_dash_on, im->grid_dash_off);
1590 } else {
1591 gfx_new_dashed_line ( im->canvas,
1592 X0-1,Y0,
1593 X1+1,Y0,
1594 GRIDWIDTH, im->graph_col[GRC_GRID],
1595 im->grid_dash_on, im->grid_dash_off);
1597 }
1598 }
1599 }
1600 return 1;
1601 }
1603 /* logaritmic horizontal grid */
1604 int
1605 horizontal_log_grid(image_desc_t *im)
1606 {
1607 double pixpex;
1608 int ii,i;
1609 int minoridx=0, majoridx=0;
1610 char graph_label[100];
1611 double X0,X1,Y0;
1612 double value, pixperstep, minstep;
1614 /* find grid spaceing */
1615 pixpex= (double)im->ysize / (log10(im->maxval) - log10(im->minval));
1617 if (isnan(pixpex)) {
1618 return 0;
1619 }
1621 for(i=0;yloglab[i][0] > 0;i++){
1622 minstep = log10(yloglab[i][0]);
1623 for(ii=1;yloglab[i][ii+1] > 0;ii++){
1624 if(yloglab[i][ii+2]==0){
1625 minstep = log10(yloglab[i][ii+1])-log10(yloglab[i][ii]);
1626 break;
1627 }
1628 }
1629 pixperstep = pixpex * minstep;
1630 if(pixperstep > 5){minoridx = i;}
1631 if(pixperstep > 2 * im->text_prop[TEXT_PROP_LEGEND].size){majoridx = i;}
1632 }
1634 X0=im->xorigin;
1635 X1=im->xorigin+im->xsize;
1636 /* paint minor grid */
1637 for (value = pow((double)10, log10(im->minval)
1638 - fmod(log10(im->minval),log10(yloglab[minoridx][0])));
1639 value <= im->maxval;
1640 value *= yloglab[minoridx][0]){
1641 if (value < im->minval) continue;
1642 i=0;
1643 while(yloglab[minoridx][++i] > 0){
1644 Y0 = ytr(im,value * yloglab[minoridx][i]);
1645 if (Y0 <= im->yorigin - im->ysize) break;
1646 gfx_new_dashed_line ( im->canvas,
1647 X0-1,Y0,
1648 X1+1,Y0,
1649 GRIDWIDTH, im->graph_col[GRC_GRID],
1650 im->grid_dash_on, im->grid_dash_off);
1651 }
1652 }
1654 /* paint major grid and labels*/
1655 for (value = pow((double)10, log10(im->minval)
1656 - fmod(log10(im->minval),log10(yloglab[majoridx][0])));
1657 value <= im->maxval;
1658 value *= yloglab[majoridx][0]){
1659 if (value < im->minval) continue;
1660 i=0;
1661 while(yloglab[majoridx][++i] > 0){
1662 Y0 = ytr(im,value * yloglab[majoridx][i]);
1663 if (Y0 <= im->yorigin - im->ysize) break;
1664 gfx_new_dashed_line ( im->canvas,
1665 X0-2,Y0,
1666 X1+2,Y0,
1667 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1668 im->grid_dash_on, im->grid_dash_off);
1670 sprintf(graph_label,"%3.0e",value * yloglab[majoridx][i]);
1671 gfx_new_text ( im->canvas,
1672 X0-im->text_prop[TEXT_PROP_AXIS].size/1.5, Y0,
1673 im->graph_col[GRC_FONT],
1674 im->text_prop[TEXT_PROP_AXIS].font,
1675 im->text_prop[TEXT_PROP_AXIS].size,
1676 im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1677 graph_label );
1678 }
1679 }
1680 return 1;
1681 }
1684 void
1685 vertical_grid(
1686 image_desc_t *im )
1687 {
1688 int xlab_sel; /* which sort of label and grid ? */
1689 time_t ti, tilab, timajor;
1690 long factor;
1691 char graph_label[100];
1692 double X0,Y0,Y1; /* points for filled graph and more*/
1695 /* the type of time grid is determined by finding
1696 the number of seconds per pixel in the graph */
1699 if(im->xlab_user.minsec == -1){
1700 factor=(im->end - im->start)/im->xsize;
1701 xlab_sel=0;
1702 while ( xlab[xlab_sel+1].minsec != -1
1703 && xlab[xlab_sel+1].minsec <= factor){ xlab_sel++; }
1704 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
1705 im->xlab_user.gridst = xlab[xlab_sel].gridst;
1706 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
1707 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
1708 im->xlab_user.labtm = xlab[xlab_sel].labtm;
1709 im->xlab_user.labst = xlab[xlab_sel].labst;
1710 im->xlab_user.precis = xlab[xlab_sel].precis;
1711 im->xlab_user.stst = xlab[xlab_sel].stst;
1712 }
1714 /* y coords are the same for every line ... */
1715 Y0 = im->yorigin;
1716 Y1 = im->yorigin-im->ysize;
1719 /* paint the minor grid */
1720 for(ti = find_first_time(im->start,
1721 im->xlab_user.gridtm,
1722 im->xlab_user.gridst),
1723 timajor = find_first_time(im->start,
1724 im->xlab_user.mgridtm,
1725 im->xlab_user.mgridst);
1726 ti < im->end;
1727 ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
1728 ){
1729 /* are we inside the graph ? */
1730 if (ti < im->start || ti > im->end) continue;
1731 while (timajor < ti) {
1732 timajor = find_next_time(timajor,
1733 im->xlab_user.mgridtm, im->xlab_user.mgridst);
1734 }
1735 if (ti == timajor) continue; /* skip as falls on major grid line */
1736 X0 = xtr(im,ti);
1737 gfx_new_dashed_line(im->canvas,X0,Y0+1, X0,Y1-1,GRIDWIDTH,
1738 im->graph_col[GRC_GRID],
1739 im->grid_dash_on, im->grid_dash_off);
1741 }
1743 /* paint the major grid */
1744 for(ti = find_first_time(im->start,
1745 im->xlab_user.mgridtm,
1746 im->xlab_user.mgridst);
1747 ti < im->end;
1748 ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
1749 ){
1750 /* are we inside the graph ? */
1751 if (ti < im->start || ti > im->end) continue;
1752 X0 = xtr(im,ti);
1753 gfx_new_dashed_line(im->canvas,X0,Y0+3, X0,Y1-2,MGRIDWIDTH,
1754 im->graph_col[GRC_MGRID],
1755 im->grid_dash_on, im->grid_dash_off);
1757 }
1758 /* paint the labels below the graph */
1759 for(ti = find_first_time(im->start,
1760 im->xlab_user.labtm,
1761 im->xlab_user.labst);
1762 ti <= im->end;
1763 ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
1764 ){
1765 tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
1766 /* are we inside the graph ? */
1767 if (ti < im->start || ti > im->end) continue;
1769 #if HAVE_STRFTIME
1770 strftime(graph_label,99,im->xlab_user.stst,localtime(&tilab));
1771 #else
1772 # error "your libc has no strftime I guess we'll abort the exercise here."
1773 #endif
1774 gfx_new_text ( im->canvas,
1775 xtr(im,tilab), Y0+im->text_prop[TEXT_PROP_AXIS].size/1.5,
1776 im->graph_col[GRC_FONT],
1777 im->text_prop[TEXT_PROP_AXIS].font,
1778 im->text_prop[TEXT_PROP_AXIS].size,
1779 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP,
1780 graph_label );
1782 }
1784 }
1787 void
1788 axis_paint(
1789 image_desc_t *im
1790 )
1791 {
1792 /* draw x and y axis */
1793 gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
1794 im->xorigin+im->xsize,im->yorigin-im->ysize,
1795 GRIDWIDTH, im->graph_col[GRC_GRID]);
1797 gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
1798 im->xorigin+im->xsize,im->yorigin-im->ysize,
1799 GRIDWIDTH, im->graph_col[GRC_GRID]);
1801 gfx_new_line ( im->canvas, im->xorigin-4,im->yorigin,
1802 im->xorigin+im->xsize+4,im->yorigin,
1803 MGRIDWIDTH, im->graph_col[GRC_GRID]);
1805 gfx_new_line ( im->canvas, im->xorigin,im->yorigin+4,
1806 im->xorigin,im->yorigin-im->ysize-4,
1807 MGRIDWIDTH, im->graph_col[GRC_GRID]);
1810 /* arrow for X axis direction */
1811 gfx_new_area ( im->canvas,
1812 im->xorigin+im->xsize+3, im->yorigin-3,
1813 im->xorigin+im->xsize+3, im->yorigin+4,
1814 im->xorigin+im->xsize+8, im->yorigin+0.5, /* LINEOFFSET */
1815 im->graph_col[GRC_ARROW]);
1819 }
1821 void
1822 grid_paint(image_desc_t *im)
1823 {
1824 long i;
1825 int res=0;
1826 double X0,Y0; /* points for filled graph and more*/
1827 gfx_node_t *node;
1829 /* draw 3d border */
1830 node = gfx_new_area (im->canvas, 0,im->yimg,
1831 2,im->yimg-2,
1832 2,2,im->graph_col[GRC_SHADEA]);
1833 gfx_add_point( node , im->ximg - 2, 2 );
1834 gfx_add_point( node , im->ximg, 0 );
1835 gfx_add_point( node , 0,0 );
1836 /* gfx_add_point( node , 0,im->yimg ); */
1838 node = gfx_new_area (im->canvas, 2,im->yimg-2,
1839 im->ximg-2,im->yimg-2,
1840 im->ximg - 2, 2,
1841 im->graph_col[GRC_SHADEB]);
1842 gfx_add_point( node , im->ximg,0);
1843 gfx_add_point( node , im->ximg,im->yimg);
1844 gfx_add_point( node , 0,im->yimg);
1845 /* gfx_add_point( node , 0,im->yimg ); */
1848 if (im->draw_x_grid == 1 )
1849 vertical_grid(im);
1851 if (im->draw_y_grid == 1){
1852 if(im->logarithmic){
1853 res = horizontal_log_grid(im);
1854 } else {
1855 res = draw_horizontal_grid(im);
1856 }
1858 /* dont draw horizontal grid if there is no min and max val */
1859 if (! res ) {
1860 char *nodata = "No Data found";
1861 gfx_new_text(im->canvas,im->ximg/2, (2*im->yorigin-im->ysize) / 2,
1862 im->graph_col[GRC_FONT],
1863 im->text_prop[TEXT_PROP_AXIS].font,
1864 im->text_prop[TEXT_PROP_AXIS].size,
1865 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
1866 nodata );
1867 }
1868 }
1870 /* yaxis description */
1871 if (im->canvas->imgformat != IF_PNG) {
1872 gfx_new_text( im->canvas,
1873 7, (im->yorigin - im->ysize/2),
1874 im->graph_col[GRC_FONT],
1875 im->text_prop[TEXT_PROP_AXIS].font,
1876 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 270.0,
1877 GFX_H_CENTER, GFX_V_CENTER,
1878 im->ylegend);
1879 } else {
1880 /* horrible hack until we can actually print vertically */
1881 {
1882 int n;
1883 int l=strlen(im->ylegend);
1884 char s[2];
1885 for (n=0;n<strlen(im->ylegend);n++) {
1886 s[0]=im->ylegend[n];
1887 s[1]='\0';
1888 gfx_new_text(im->canvas,7,im->text_prop[TEXT_PROP_AXIS].size*(l-n),
1889 im->graph_col[GRC_FONT],
1890 im->text_prop[TEXT_PROP_AXIS].font,
1891 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 270.0,
1892 GFX_H_CENTER, GFX_V_CENTER,
1893 s);
1894 }
1895 }
1896 }
1898 /* graph title */
1899 gfx_new_text( im->canvas,
1900 im->ximg/2, im->text_prop[TEXT_PROP_TITLE].size,
1901 im->graph_col[GRC_FONT],
1902 im->text_prop[TEXT_PROP_TITLE].font,
1903 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
1904 GFX_H_CENTER, GFX_V_CENTER,
1905 im->title);
1907 /* graph labels */
1908 if( !(im->extra_flags & NOLEGEND) ) {
1909 for(i=0;i<im->gdes_c;i++){
1910 if(im->gdes[i].legend[0] =='\0')
1911 continue;
1913 /* im->gdes[i].leg_y is the bottom of the legend */
1914 X0 = im->gdes[i].leg_x;
1915 Y0 = im->gdes[i].leg_y;
1916 /* Box needed? */
1917 if ( im->gdes[i].gf != GF_GPRINT
1918 && im->gdes[i].gf != GF_COMMENT) {
1919 int boxH, boxV;
1921 boxH = gfx_get_text_width(im->canvas, 0,
1922 im->text_prop[TEXT_PROP_AXIS].font,
1923 im->text_prop[TEXT_PROP_AXIS].size,
1924 im->tabwidth,"M") * 1.25;
1925 boxV = boxH;
1927 node = gfx_new_area(im->canvas,
1928 X0,Y0-boxV,
1929 X0,Y0,
1930 X0+boxH,Y0,
1931 im->gdes[i].col);
1932 gfx_add_point ( node, X0+boxH, Y0-boxV );
1933 node = gfx_new_line(im->canvas,
1934 X0,Y0-boxV, X0,Y0,
1935 1,0x000000FF);
1936 gfx_add_point(node,X0+boxH,Y0);
1937 gfx_add_point(node,X0+boxH,Y0-boxV);
1938 gfx_close_path(node);
1939 X0 += boxH / 1.25 * 2;
1940 }
1941 gfx_new_text ( im->canvas, X0, Y0,
1942 im->graph_col[GRC_FONT],
1943 im->text_prop[TEXT_PROP_AXIS].font,
1944 im->text_prop[TEXT_PROP_AXIS].size,
1945 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
1946 im->gdes[i].legend );
1947 }
1948 }
1949 }
1952 /*****************************************************
1953 * lazy check make sure we rely need to create this graph
1954 *****************************************************/
1956 int lazy_check(image_desc_t *im){
1957 FILE *fd = NULL;
1958 int size = 1;
1959 struct stat imgstat;
1961 if (im->lazy == 0) return 0; /* no lazy option */
1962 if (stat(im->graphfile,&imgstat) != 0)
1963 return 0; /* can't stat */
1964 /* one pixel in the existing graph is more then what we would
1965 change here ... */
1966 if (time(NULL) - imgstat.st_mtime >
1967 (im->end - im->start) / im->xsize)
1968 return 0;
1969 if ((fd = fopen(im->graphfile,"rb")) == NULL)
1970 return 0; /* the file does not exist */
1971 switch (im->canvas->imgformat) {
1972 case IF_PNG:
1973 size = PngSize(fd,&(im->ximg),&(im->yimg));
1974 break;
1975 default:
1976 size = 1;
1977 }
1978 fclose(fd);
1979 return size;
1980 }
1982 void
1983 pie_part(image_desc_t *im, gfx_color_t color,
1984 double PieCenterX, double PieCenterY, double Radius,
1985 double startangle, double endangle)
1986 {
1987 gfx_node_t *node;
1988 double angle;
1989 double step=M_PI/50; /* Number of iterations for the circle;
1990 ** 10 is definitely too low, more than
1991 ** 50 seems to be overkill
1992 */
1994 /* Strange but true: we have to work clockwise or else
1995 ** anti aliasing nor transparency don't work.
1996 **
1997 ** This test is here to make sure we do it right, also
1998 ** this makes the for...next loop more easy to implement.
1999 ** The return will occur if the user enters a negative number
2000 ** (which shouldn't be done according to the specs) or if the
2001 ** programmers do something wrong (which, as we all know, never
2002 ** happens anyway :)
2003 */
2004 if (endangle<startangle) return;
2006 /* Hidden feature: Radius decreases each full circle */
2007 angle=startangle;
2008 while (angle>=2*M_PI) {
2009 angle -= 2*M_PI;
2010 Radius *= 0.8;
2011 }
2013 node=gfx_new_area(im->canvas,
2014 PieCenterX+sin(startangle)*Radius,
2015 PieCenterY-cos(startangle)*Radius,
2016 PieCenterX,
2017 PieCenterY,
2018 PieCenterX+sin(endangle)*Radius,
2019 PieCenterY-cos(endangle)*Radius,
2020 color);
2021 for (angle=endangle;angle-startangle>=step;angle-=step) {
2022 gfx_add_point(node,
2023 PieCenterX+sin(angle)*Radius,
2024 PieCenterY-cos(angle)*Radius );
2025 }
2026 }
2028 int
2029 graph_size_location(image_desc_t *im, int elements, int piechart )
2030 {
2031 /* The actual size of the image to draw is determined from
2032 ** several sources. The size given on the command line is
2033 ** the graph area but we need more as we have to draw labels
2034 ** and other things outside the graph area
2035 */
2037 /* +-+-------------------------------------------+
2038 ** |l|.................title.....................|
2039 ** |e+--+-------------------------------+--------+
2040 ** |b| b| | |
2041 ** |a| a| | pie |
2042 ** |l| l| main graph area | chart |
2043 ** |.| .| | area |
2044 ** |t| y| | |
2045 ** |r+--+-------------------------------+--------+
2046 ** |e| | x-axis labels | |
2047 ** |v+--+-------------------------------+--------+
2048 ** | |..............legends......................|
2049 ** +-+-------------------------------------------+
2050 */
2051 int Xvertical=0, Yvertical=0,
2052 Xtitle =0, Ytitle =0,
2053 Xylabel =0, Yylabel =0,
2054 Xmain =0, Ymain =0,
2055 Xpie =0, Ypie =0,
2056 Xxlabel =0, Yxlabel =0,
2057 #if 0
2058 Xlegend =0, Ylegend =0,
2059 #endif
2060 Xspacing =10, Yspacing =10;
2062 if (im->ylegend[0] != '\0') {
2063 Xvertical = im->text_prop[TEXT_PROP_LEGEND].size *2;
2064 Yvertical = im->text_prop[TEXT_PROP_LEGEND].size * (strlen(im->ylegend)+1);
2065 }
2067 if (im->title[0] != '\0') {
2068 /* The title is placed "inbetween" two text lines so it
2069 ** automatically has some vertical spacing. The horizontal
2070 ** spacing is added here, on each side.
2071 */
2072 Xtitle = gfx_get_text_width(im->canvas, 0,
2073 im->text_prop[TEXT_PROP_TITLE].font,
2074 im->text_prop[TEXT_PROP_TITLE].size,
2075 im->tabwidth,
2076 im->title) + 2*Xspacing;
2077 Ytitle = im->text_prop[TEXT_PROP_TITLE].size*2;
2078 }
2080 if (elements) {
2081 Xmain=im->xsize;
2082 Ymain=im->ysize;
2083 if (im->draw_x_grid) {
2084 Xxlabel=Xmain;
2085 Yxlabel=im->text_prop[TEXT_PROP_LEGEND].size *2;
2086 }
2087 if (im->draw_y_grid) {
2088 Xylabel=im->text_prop[TEXT_PROP_LEGEND].size *6;
2089 Yylabel=Ymain;
2090 }
2091 }
2093 if (piechart) {
2094 im->piesize=im->xsize<im->ysize?im->xsize:im->ysize;
2095 Xpie=im->piesize;
2096 Ypie=im->piesize;
2097 }
2099 /* Now calculate the total size. Insert some spacing where
2100 desired. im->xorigin and im->yorigin need to correspond
2101 with the lower left corner of the main graph area or, if
2102 this one is not set, the imaginary box surrounding the
2103 pie chart area. */
2105 /* The legend width cannot yet be determined, as a result we
2106 ** have problems adjusting the image to it. For now, we just
2107 ** forget about it at all; the legend will have to fit in the
2108 ** size already allocated.
2109 */
2110 im->ximg = Xylabel + Xmain + Xpie + Xspacing;
2111 if (Xmain) im->ximg += Xspacing;
2112 if (Xpie) im->ximg += Xspacing;
2113 im->xorigin = Xspacing + Xylabel;
2114 if (Xtitle > im->ximg) im->ximg = Xtitle;
2115 if (Xvertical) {
2116 im->ximg += Xvertical;
2117 im->xorigin += Xvertical;
2118 }
2119 xtr(im,0);
2121 /* The vertical size is interesting... we need to compare
2122 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend} with Yvertical
2123 ** however we need to know {Ytitle+Ymain+Yxlabel} in order to
2124 ** start even thinking about Ylegend.
2125 **
2126 ** Do it in three portions: First calculate the inner part,
2127 ** then do the legend, then adjust the total height of the img.
2128 */
2130 /* reserve space for main and/or pie */
2131 im->yimg = Ymain + Yxlabel;
2132 if (im->yimg < Ypie) im->yimg = Ypie;
2133 im->yorigin = im->yimg - Yxlabel;
2134 /* reserve space for the title *or* some padding above the graph */
2135 if (Ytitle) {
2136 im->yimg += Ytitle;
2137 im->yorigin += Ytitle;
2138 } else {
2139 im->yimg += Yspacing;
2140 im->yorigin += Yspacing;
2141 }
2142 /* reserve space for padding below the graph */
2143 im->yimg += Yspacing;
2144 ytr(im,DNAN);
2146 /* Determine where to place the legends onto the image.
2147 ** Adjust im->yimg to match the space requirements.
2148 */
2149 if(leg_place(im)==-1)
2150 return -1;
2152 /* last of three steps: check total height of image */
2153 if (im->yimg < Yvertical) im->yimg = Yvertical;
2155 #if 0
2156 if (Xlegend > im->ximg) {
2157 im->ximg = Xlegend;
2158 /* reposition Pie */
2159 #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].start=im->start;
2493 im->gdes[im->gdes_c-1].end=im->end;
2494 im->gdes[im->gdes_c-1].vname[0]='\0';
2495 im->gdes[im->gdes_c-1].data=NULL;
2496 im->gdes[im->gdes_c-1].ds_namv=NULL;
2497 im->gdes[im->gdes_c-1].data_first=0;
2498 im->gdes[im->gdes_c-1].p_data=NULL;
2499 im->gdes[im->gdes_c-1].rpnp=NULL;
2500 im->gdes[im->gdes_c-1].col = 0x0;
2501 im->gdes[im->gdes_c-1].legend[0]='\0';
2502 im->gdes[im->gdes_c-1].rrd[0]='\0';
2503 im->gdes[im->gdes_c-1].ds=-1;
2504 im->gdes[im->gdes_c-1].p_data=NULL;
2505 return 0;
2506 }
2508 /* copies input untill the first unescaped colon is found
2509 or until input ends. backslashes have to be escaped as well */
2510 int
2511 scan_for_col(char *input, int len, char *output)
2512 {
2513 int inp,outp=0;
2514 for (inp=0;
2515 inp < len &&
2516 input[inp] != ':' &&
2517 input[inp] != '\0';
2518 inp++){
2519 if (input[inp] == '\\' &&
2520 input[inp+1] != '\0' &&
2521 (input[inp+1] == '\\' ||
2522 input[inp+1] == ':')){
2523 output[outp++] = input[++inp];
2524 }
2525 else {
2526 output[outp++] = input[inp];
2527 }
2528 }
2529 output[outp] = '\0';
2530 return inp;
2531 }
2532 /* Some surgery done on this function, it became ridiculously big.
2533 ** Things moved:
2534 ** - initializing now in rrd_graph_init()
2535 ** - options parsing now in rrd_graph_options()
2536 ** - script parsing now in rrd_graph_script()
2537 */
2538 int
2539 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize)
2540 {
2541 image_desc_t im;
2543 #ifdef HAVE_TZSET
2544 tzset();
2545 #endif
2546 #ifdef HAVE_SETLOCALE
2547 setlocale(LC_TIME,"");
2548 #endif
2551 rrd_graph_init(&im);
2553 rrd_graph_options(argc,argv,&im);
2554 if (rrd_test_error()) return -1;
2556 if (strlen(argv[optind])>=MAXPATH) {
2557 rrd_set_error("filename (including path) too long");
2558 return -1;
2559 }
2560 strncpy(im.graphfile,argv[optind],MAXPATH-1);
2561 im.graphfile[MAXPATH-1]='\0';
2563 rrd_graph_script(argc,argv,&im);
2564 if (rrd_test_error()) return -1;
2566 /* Everything is now read and the actual work can start */
2568 (*prdata)=NULL;
2569 if (graph_paint(&im,prdata)==-1){
2570 im_free(&im);
2571 return -1;
2572 }
2574 /* The image is generated and needs to be output.
2575 ** Also, if needed, print a line with information about the image.
2576 */
2578 *xsize=im.ximg;
2579 *ysize=im.yimg;
2580 if (im.imginfo) {
2581 char *filename;
2582 if (!(*prdata)) {
2583 /* maybe prdata is not allocated yet ... lets do it now */
2584 if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
2585 rrd_set_error("malloc imginfo");
2586 return -1;
2587 };
2588 }
2589 if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
2590 ==NULL){
2591 rrd_set_error("malloc imginfo");
2592 return -1;
2593 }
2594 filename=im.graphfile+strlen(im.graphfile);
2595 while(filename > im.graphfile) {
2596 if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
2597 filename--;
2598 }
2600 sprintf((*prdata)[0],im.imginfo,filename,(long)(im.canvas->zoom*im.ximg),(long)(im.canvas->zoom*im.yimg));
2601 }
2602 im_free(&im);
2603 return 0;
2604 }
2606 void
2607 rrd_graph_init(image_desc_t *im)
2608 {
2609 int i;
2611 im->xlab_user.minsec = -1;
2612 im->ximg=0;
2613 im->yimg=0;
2614 im->xsize = 400;
2615 im->ysize = 100;
2616 im->step = 0;
2617 im->ylegend[0] = '\0';
2618 im->title[0] = '\0';
2619 im->minval = DNAN;
2620 im->maxval = DNAN;
2621 im->unitsexponent= 9999;
2622 im->extra_flags= 0;
2623 im->rigid = 0;
2624 im->gridfit = 1;
2625 im->imginfo = NULL;
2626 im->lazy = 0;
2627 im->logarithmic = 0;
2628 im->ygridstep = DNAN;
2629 im->draw_x_grid = 1;
2630 im->draw_y_grid = 1;
2631 im->base = 1000;
2632 im->prt_c = 0;
2633 im->gdes_c = 0;
2634 im->gdes = NULL;
2635 im->canvas = gfx_new_canvas();
2636 im->grid_dash_on = 1;
2637 im->grid_dash_off = 1;
2639 for(i=0;i<DIM(graph_col);i++)
2640 im->graph_col[i]=graph_col[i];
2642 for(i=0;i<DIM(text_prop);i++){
2643 im->text_prop[i].size = text_prop[i].size;
2644 im->text_prop[i].font = text_prop[i].font;
2645 }
2646 }
2648 void
2649 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
2650 {
2651 int stroff;
2652 char *parsetime_error = NULL;
2653 char scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
2654 time_t start_tmp=0,end_tmp=0;
2655 long long_tmp;
2656 struct time_value start_tv, end_tv;
2657 gfx_color_t color;
2659 parsetime("end-24h", &start_tv);
2660 parsetime("now", &end_tv);
2662 while (1){
2663 static struct option long_options[] =
2664 {
2665 {"start", required_argument, 0, 's'},
2666 {"end", required_argument, 0, 'e'},
2667 {"x-grid", required_argument, 0, 'x'},
2668 {"y-grid", required_argument, 0, 'y'},
2669 {"vertical-label",required_argument,0,'v'},
2670 {"width", required_argument, 0, 'w'},
2671 {"height", required_argument, 0, 'h'},
2672 {"interlaced", no_argument, 0, 'i'},
2673 {"upper-limit",required_argument, 0, 'u'},
2674 {"lower-limit",required_argument, 0, 'l'},
2675 {"rigid", no_argument, 0, 'r'},
2676 {"base", required_argument, 0, 'b'},
2677 {"logarithmic",no_argument, 0, 'o'},
2678 {"color", required_argument, 0, 'c'},
2679 {"font", required_argument, 0, 'n'},
2680 {"title", required_argument, 0, 't'},
2681 {"imginfo", required_argument, 0, 'f'},
2682 {"imgformat", required_argument, 0, 'a'},
2683 {"lazy", no_argument, 0, 'z'},
2684 {"zoom", required_argument, 0, 'm'},
2685 {"no-legend", no_argument, 0, 'g'},
2686 {"alt-y-grid", no_argument, 0, 257 },
2687 {"alt-autoscale", no_argument, 0, 258 },
2688 {"alt-autoscale-max", no_argument, 0, 259 },
2689 {"units-exponent",required_argument, 0, 260},
2690 {"step", required_argument, 0, 261},
2691 {"no-gridfit", no_argument, 0, 262},
2692 {0,0,0,0}};
2693 int option_index = 0;
2694 int opt;
2697 opt = getopt_long(argc, argv,
2698 "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:z:g",
2699 long_options, &option_index);
2701 if (opt == EOF)
2702 break;
2704 switch(opt) {
2705 case 257:
2706 im->extra_flags |= ALTYGRID;
2707 break;
2708 case 258:
2709 im->extra_flags |= ALTAUTOSCALE;
2710 break;
2711 case 259:
2712 im->extra_flags |= ALTAUTOSCALE_MAX;
2713 break;
2714 case 'g':
2715 im->extra_flags |= NOLEGEND;
2716 break;
2717 case 260:
2718 im->unitsexponent = atoi(optarg);
2719 break;
2720 case 261:
2721 im->step = atoi(optarg);
2722 break;
2723 case 262:
2724 im->gridfit = 0;
2725 break;
2726 case 's':
2727 if ((parsetime_error = parsetime(optarg, &start_tv))) {
2728 rrd_set_error( "start time: %s", parsetime_error );
2729 return;
2730 }
2731 break;
2732 case 'e':
2733 if ((parsetime_error = parsetime(optarg, &end_tv))) {
2734 rrd_set_error( "end time: %s", parsetime_error );
2735 return;
2736 }
2737 break;
2738 case 'x':
2739 if(strcmp(optarg,"none") == 0){
2740 im->draw_x_grid=0;
2741 break;
2742 };
2744 if(sscanf(optarg,
2745 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
2746 scan_gtm,
2747 &im->xlab_user.gridst,
2748 scan_mtm,
2749 &im->xlab_user.mgridst,
2750 scan_ltm,
2751 &im->xlab_user.labst,
2752 &im->xlab_user.precis,
2753 &stroff) == 7 && stroff != 0){
2754 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
2755 if((im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
2756 rrd_set_error("unknown keyword %s",scan_gtm);
2757 return;
2758 } else if ((im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
2759 rrd_set_error("unknown keyword %s",scan_mtm);
2760 return;
2761 } else if ((im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
2762 rrd_set_error("unknown keyword %s",scan_ltm);
2763 return;
2764 }
2765 im->xlab_user.minsec = 1;
2766 im->xlab_user.stst = im->xlab_form;
2767 } else {
2768 rrd_set_error("invalid x-grid format");
2769 return;
2770 }
2771 break;
2772 case 'y':
2774 if(strcmp(optarg,"none") == 0){
2775 im->draw_y_grid=0;
2776 break;
2777 };
2779 if(sscanf(optarg,
2780 "%lf:%d",
2781 &im->ygridstep,
2782 &im->ylabfact) == 2) {
2783 if(im->ygridstep<=0){
2784 rrd_set_error("grid step must be > 0");
2785 return;
2786 } else if (im->ylabfact < 1){
2787 rrd_set_error("label factor must be > 0");
2788 return;
2789 }
2790 } else {
2791 rrd_set_error("invalid y-grid format");
2792 return;
2793 }
2794 break;
2795 case 'v':
2796 strncpy(im->ylegend,optarg,150);
2797 im->ylegend[150]='\0';
2798 break;
2799 case 'u':
2800 im->maxval = atof(optarg);
2801 break;
2802 case 'l':
2803 im->minval = atof(optarg);
2804 break;
2805 case 'b':
2806 im->base = atol(optarg);
2807 if(im->base != 1024 && im->base != 1000 ){
2808 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
2809 return;
2810 }
2811 break;
2812 case 'w':
2813 long_tmp = atol(optarg);
2814 if (long_tmp < 10) {
2815 rrd_set_error("width below 10 pixels");
2816 return;
2817 }
2818 im->xsize = long_tmp;
2819 break;
2820 case 'h':
2821 long_tmp = atol(optarg);
2822 if (long_tmp < 10) {
2823 rrd_set_error("height below 10 pixels");
2824 return;
2825 }
2826 im->ysize = long_tmp;
2827 break;
2828 case 'i':
2829 im->canvas->interlaced = 1;
2830 break;
2831 case 'r':
2832 im->rigid = 1;
2833 break;
2834 case 'f':
2835 im->imginfo = optarg;
2836 break;
2837 case 'a':
2838 if((im->canvas->imgformat = if_conv(optarg)) == -1) {
2839 rrd_set_error("unsupported graphics format '%s'",optarg);
2840 return;
2841 }
2842 break;
2843 case 'z':
2844 im->lazy = 1;
2845 break;
2846 case 'o':
2847 im->logarithmic = 1;
2848 if (isnan(im->minval))
2849 im->minval=1;
2850 break;
2851 case 'c':
2852 if(sscanf(optarg,
2853 "%10[A-Z]#%8lx",
2854 col_nam,&color) == 2){
2855 int ci;
2856 if((ci=grc_conv(col_nam)) != -1){
2857 im->graph_col[ci]=color;
2858 } else {
2859 rrd_set_error("invalid color name '%s'",col_nam);
2860 }
2861 } else {
2862 rrd_set_error("invalid color def format");
2863 return;
2864 }
2865 break;
2866 case 'n':{
2867 /* originally this used char *prop = "" and
2868 ** char *font = "dummy" however this results
2869 ** in a SEG fault, at least on RH7.1
2870 **
2871 ** The current implementation isn't proper
2872 ** either, font is never freed and prop uses
2873 ** a fixed width string
2874 */
2875 char prop[100];
2876 double size = 1;
2877 char *font;
2879 font=malloc(255);
2880 if(sscanf(optarg,
2881 "%10[A-Z]:%lf:%s",
2882 prop,&size,font) == 3){
2883 int sindex;
2884 if((sindex=text_prop_conv(prop)) != -1){
2885 im->text_prop[sindex].size=size;
2886 im->text_prop[sindex].font=font;
2887 if (sindex==0) { /* the default */
2888 im->text_prop[TEXT_PROP_TITLE].size=size;
2889 im->text_prop[TEXT_PROP_TITLE].font=font;
2890 im->text_prop[TEXT_PROP_AXIS].size=size;
2891 im->text_prop[TEXT_PROP_AXIS].font=font;
2892 im->text_prop[TEXT_PROP_UNIT].size=size;
2893 im->text_prop[TEXT_PROP_UNIT].font=font;
2894 im->text_prop[TEXT_PROP_LEGEND].size=size;
2895 im->text_prop[TEXT_PROP_LEGEND].font=font;
2896 }
2897 } else {
2898 rrd_set_error("invalid fonttag '%s'",prop);
2899 return;
2900 }
2901 } else {
2902 rrd_set_error("invalid text property format");
2903 return;
2904 }
2905 break;
2906 }
2907 case 'm':
2908 im->canvas->zoom = atof(optarg);
2909 if (im->canvas->zoom <= 0.0) {
2910 rrd_set_error("zoom factor must be > 0");
2911 return;
2912 }
2913 break;
2914 case 't':
2915 strncpy(im->title,optarg,150);
2916 im->title[150]='\0';
2917 break;
2919 case '?':
2920 if (optopt != 0)
2921 rrd_set_error("unknown option '%c'", optopt);
2922 else
2923 rrd_set_error("unknown option '%s'",argv[optind-1]);
2924 return;
2925 }
2926 }
2928 if (optind >= argc) {
2929 rrd_set_error("missing filename");
2930 return;
2931 }
2933 if (im->logarithmic == 1 && (im->minval <= 0 || isnan(im->minval))){
2934 rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");
2935 return;
2936 }
2938 if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
2939 /* error string is set in parsetime.c */
2940 return;
2941 }
2943 if (start_tmp < 3600*24*365*10){
2944 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
2945 return;
2946 }
2948 if (end_tmp < start_tmp) {
2949 rrd_set_error("start (%ld) should be less than end (%ld)",
2950 start_tmp, end_tmp);
2951 return;
2952 }
2954 im->start = start_tmp;
2955 im->end = end_tmp;
2956 }
2958 /* rrd_name_or_num()
2959 **
2960 ** Scans for a VDEF-variable or a number
2961 **
2962 ** Returns an integer describing what was found:
2963 **
2964 ** 0: error
2965 ** 1: found an integer; it is returned in both l and d
2966 ** 2: found a float; it is returned in d
2967 ** 3: found a vname; its index is returned in l
2968 **
2969 ** l and d are undefined unless described above
2970 */
2971 static int
2972 rrd_name_or_num(image_desc_t *im, char *param, long *l, double *d)
2973 {
2974 int i1=0,i2=0,i3=0,i4=0,i5=0,i6=0;
2975 char vname[MAX_VNAME_LEN+1];
2977 sscanf(param, "%li%n%*s%n", l,&i1,&i2);
2978 sscanf(param, "%lf%n%*s%n", d,&i3,&i4);
2979 sscanf(param, DEF_NAM_FMT "%n%*s%n", vname, &i5,&i6);
2981 if ( (i1) && (!i2) ) return 1;
2982 if ( (i3) && (!i4) ) return 2;
2983 if ( (i5) && (!i6) ) {
2984 if ((*l = find_var(im,vname))!=-1) return 3;
2985 }
2986 return 0;
2987 }
2989 /* rrd_vname_color()
2990 **
2991 ** Parses "[<vname|number>][#color]" where at least one
2992 ** of the optional strings must exist.
2993 **
2994 ** Returns an integer describing what was found.
2995 ** If the result is 0, the rrd_error string may be set.
2996 **
2997 ** ...CVVVV
2998 ** ---------:-----------------------------------
2999 ** 00000000 : error
3000 ** ....0000 : a value/variable was not found
3001 ** ....0001 : an integer number was found, returned in both l and d
3002 ** ....0010 : a floating point number was found, returned in d
3003 ** ....0011 : reserved for future values
3004 ** ....01xx : reserved for future values
3005 ** ....1000 : an existing DEF vname was found, idx returned in l
3006 ** ....1001 : an existing CDEF vname was found, idx returned in l
3007 ** ....1010 : an existing VDEF vname was found, idx returned in l
3008 ** ....1011 : reserved for future variables
3009 ** ....11xx : reserved for future variables
3010 ** ...0.... : a color was not found, returned in color
3011 ** ...1.... : a color was found, returned in color
3012 */
3013 static int
3014 rrd_vname_color(image_desc_t *im, char * param,
3015 long *l,
3016 double *d,
3017 gfx_color_t *color)
3018 {
3019 int result=0,i=0;
3021 if (param[0]!='#') { /* vname or num present or empty string */
3022 char *s,*c=param;
3023 while ((*c!='\0')&&(*c!='#')) c++,i++;
3024 if (*c!='\0') {
3025 s=malloc(i+1);
3026 if (s==NULL) {
3027 rrd_set_error("Out of memory in function rrd_vname_color");
3028 return 0;
3029 }
3030 strncpy(s,param,i);
3031 s[i]='\0';
3032 result=rrd_name_or_num(im, s, l, d);
3033 if (!result) {
3034 rrd_set_error("Use of uninitialized vname %s",s);
3035 free(s);
3036 }
3037 } else {
3038 result=rrd_name_or_num(im, param, l, d);
3039 if (!result) {
3040 rrd_set_error("Use of uninitialized vname %s",param);
3041 }
3042 }
3043 switch (result) {
3044 case 0: return 0; /* error set above */
3045 case 1:
3046 case 2: break;
3047 case 3:
3048 switch (im->gdes[*l].gf) {
3049 case GF_DEF: result=0x08;break;
3050 case GF_CDEF: result=0x09;break;
3051 case GF_VDEF: result=0x0A;break;
3052 default:
3053 rrd_set_error("Unexpected GF result from function "
3054 "rrd_name_or_num() called from rrd_vname_color");
3055 return 0;
3056 }
3057 break;
3058 default:
3059 rrd_set_error("Unexpected result from function "
3060 "rrd_name_or_num() called from rrd_vname_color");
3061 return 0;
3062 }
3063 }
3064 /* Parse color, if any. */
3065 if (param[i] == '\0') return result;
3066 else {
3067 unsigned int r=0,g=0,b=0,a=0xFF;
3068 int i1=0,i2=0;
3069 sscanf(¶m[i], "#%02x%02x%02x%n%02x%n",
3070 &r,&g,&b,&i1,&a,&i2);
3071 if (!i1) {
3072 rrd_set_error("Unparsable color %s",¶m[i]);
3073 return 0;
3074 }
3075 if (i2) i1=i2;
3076 i2=0;
3077 sscanf(¶m[i+i1],"%*s%n",&i2);
3078 if (i2) {
3079 rrd_set_error("Garbage after color %s",param[i]);
3080 return 0;
3081 }
3082 *color=r<<24|g<<16|b<<8|a;
3083 return result|0x10;
3084 }
3085 }
3087 /* rrd_find_function()
3088 **
3089 ** Checks if the parameter is a valid function and
3090 ** if so, returns it in the graph description pointer.
3091 **
3092 ** The return value is a boolean; true if found
3093 */
3094 static int
3095 rrd_find_function(char *param, graph_desc_t *gdp)
3096 {
3097 size_t i1=0,i2=0;
3098 char funcname[11];
3100 sscanf(param,"%10[A-Z]%n%*1[1-3]%n",funcname,(int *)&i1,(int *)&i2);
3101 gdp->gf=gf_conv(funcname);
3102 if ((int)gdp->gf == -1) {
3103 rrd_set_error("'%s' is not a valid function name",funcname);
3104 return 0;
3105 }
3106 if (gdp->gf==GF_LINE) {
3107 if (i2) {
3108 gdp->linewidth=param[i1]-'0';
3109 } else {
3110 rrd_set_error("LINE should have a width");
3111 return 0;
3112 }
3113 } else {
3114 if (i2) {
3115 rrd_set_error("Only LINE should have a width: %s",param);
3116 return 0;
3117 } else {
3118 i2=i1;
3119 }
3120 }
3121 if (strlen(param) != i2) {
3122 rrd_set_error("Garbage after function name: %s",param);
3123 return 0;
3124 }
3125 return 1;
3126 }
3127 /* rrd_split_line()
3128 **
3129 ** Takes a string as input; splits this line into multiple
3130 ** parameters on each ":" boundary.
3131 **
3132 ** If this function returns successful, the caller will have
3133 ** to free() the allocated memory for param.
3134 **
3135 ** The input string is destroyed, its memory is used by the
3136 ** output array.
3137 */
3138 static int
3139 rrd_split_line(char *line,char ***param)
3140 {
3141 int i=0,n=0;
3142 char *c=line;
3144 /* scan the amount of colons in the line. We need
3145 ** at most this amount+1 pointers for the array. If
3146 ** any colons are escaped we waste some space.
3147 */
3148 if (*c!='\0') n=1;
3149 while (*c != '\0')
3150 if (*c++ == ':') n++;
3152 if (n==0) {
3153 rrd_set_error("No line to split. rrd_split_line was given the empty string.");
3154 return -1;
3155 }
3157 /* Allocate memory for an array of n char pointers */
3158 *param=calloc(n,sizeof(char *));
3159 if (*param==NULL) {
3160 rrd_set_error("Memory allocation failed inside rrd_split_line");
3161 return -1;
3162 }
3164 /* split the line and fill the array */
3165 c = line;
3166 i=0;
3167 (*param)[i] = c;
3168 while (*c != '\0') {
3169 switch (*c) {
3170 case '\\':
3171 c++;
3172 if (*c=='\0') {
3173 free(*param);
3174 rrd_set_error("Lone backslash found inside rrd_split_line");
3175 return -1;
3176 }
3177 c++;
3178 break;
3179 case ':':
3180 *c = '\0';
3181 c++;
3182 i++;
3183 (*param)[i] = c;
3184 break;
3185 default:
3186 c++;
3187 }
3188 }
3189 i++; /* i separators means i+1 parameters */
3191 return i;
3192 }
3193 void
3194 rrd_graph_script(int argc, char *argv[], image_desc_t *im)
3195 {
3196 int i;
3197 char symname[100];
3198 int linepass = 0; /* stack must follow LINE*, AREA or STACK */
3199 char ** param;
3200 int paramcnt,paramused;
3202 for (i=optind+1;i<argc;i++) {
3203 int argstart=0;
3204 int strstart=0;
3205 graph_desc_t *gdp;
3206 char *line;
3207 char tmpline[256];
3208 char vname[MAX_VNAME_LEN+1],sep[1];
3209 /* double d; */
3210 int j,k,l/*,m*/;
3212 /* Each command is one element from *argv[]. This command is
3213 ** split at every unescaped colon.
3214 **
3215 ** Each command defines the most current gdes inside struct im.
3216 ** In stead of typing "im->gdes[im->gdes_c-1]" we use "gdp".
3217 */
3218 gdes_alloc(im);
3219 gdp=&im->gdes[im->gdes_c-1];
3220 strcpy(tmpline,argv[i]);
3221 line=tmpline;
3222 if ((paramcnt=rrd_split_line(argv[i],¶m))==-1) return;
3223 paramused=0;
3225 #ifdef DEBUG
3226 printf("DEBUG: after splitting line:\n");
3227 for (j=0;j<paramcnt;j++)
3228 printf("DEBUG: %3i: %s\n",j,param[j]);
3229 #endif
3231 if (!rrd_find_function(param[paramused],gdp)) {
3232 im_free(im);
3233 free(param);
3234 return;
3235 }
3236 paramused++;
3238 /* function:newvname=string[:ds-name:CF] for xDEF
3239 ** function:vname[#color[:string]] for LINEx,AREA,STACK
3240 ** function:vname#color[:num[:string]] for TICK
3241 ** function:vname-or-num#color[:string] for xRULE,PART
3242 ** function:vname:CF:string for xPRINT
3243 ** function:string for COMMENT
3244 */
3246 /*TEMP*/argstart=strlen(param[paramused-1])+1;
3248 /* If anything fails just use rrd_set_error() and break from the
3249 ** switch. Just after the switch we call rrd_test_error() and
3250 ** clean up if it is set.
3251 */
3252 switch (gdp->gf) {
3253 case GF_XPORT:
3254 break;
3255 case GF_COMMENT:
3256 if (paramcnt<2) {
3257 rrd_set_error("Not enough parameters for %s",param[0]);
3258 break;
3259 }
3260 if (strlen(param[1])>FMT_LEG_LEN) {
3261 rrd_set_error("Comment too long: %s:%s",param[0],param[1]);
3262 break;
3263 }
3264 strcpy(gdp->legend,param[1]);
3265 paramused++;
3266 break;
3267 case GF_PART:
3268 case GF_VRULE:
3269 case GF_HRULE:
3270 if (paramcnt<2) {
3271 rrd_set_error("No name or number in %s",param[0]);
3272 break;
3273 }
3274 j=rrd_vname_color(im,param[1],
3275 &gdp->xrule,&gdp->yrule,&gdp->col);
3276 paramused++;
3277 if (!j) break; /* error string set by function */
3278 switch (j&0x0F) {
3279 case 0x00:
3280 rrd_set_error("Cannot parse name nor number "
3281 "in %s:%s",param[0],param[1]);
3282 break;
3283 case 0x08:
3284 case 0x09:
3285 rrd_set_error("Cannot use DEF or CDEF based "
3286 "variable in %s:%s",param[0],param[1]);
3287 break;
3288 case 0x0A:
3289 gdp->vidx=gdp->xrule;
3290 gdp->xrule=0;
3291 gdp->yrule=DNAN;
3292 break;
3293 case 0x01:
3294 case 0x02:
3295 break;
3296 default:
3297 rrd_set_error("Unexpected result while parsing "
3298 "%s:%s, program error",param[0],param[1]);
3299 }
3300 if (rrd_test_error()) break;
3302 if (paramcnt>paramused) {
3303 if (strlen(param[paramused])>FMT_LEG_LEN) {
3304 rrd_set_error("Comment too long: %s:%s",
3305 param[0],param[1]);
3306 break;
3307 }
3308 strcpy(gdp->legend,param[paramused]);
3309 paramused++;
3310 }
3311 break;
3312 case GF_STACK:
3313 if (linepass==0) {
3314 rrd_set_error("STACK must follow another graphing element");
3315 break;
3316 }
3317 case GF_LINE:
3318 case GF_AREA:
3319 case GF_TICK:
3320 j=k=0;
3321 linepass=1;
3322 sscanf(&line[argstart],DEF_NAM_FMT"%n%1[#:]%n",vname,&j,sep,&k);
3323 if (j+1!=k)
3324 rrd_set_error("Cannot parse vname in line: %s",line);
3325 else if (rrd_graph_check_vname(im,vname,line))
3326 rrd_set_error("Undefined vname '%s' in line: %s",line);
3327 else
3328 k=rrd_graph_color(im,&line[argstart],line,1);
3329 if (rrd_test_error()) break;
3330 argstart=argstart+j+k;
3331 if ((strlen(&line[argstart])!=0)&&(gdp->gf==GF_TICK)) {
3332 j=0;
3333 sscanf(&line[argstart], ":%lf%n", &gdp->yrule,&j);
3334 argstart+=j;
3335 }
3336 if (strlen(&line[argstart])!=0)
3337 if (rrd_graph_legend(gdp,&line[++argstart])==0)
3338 rrd_set_error("Cannot parse legend in line: %s",line);
3339 break;
3340 case GF_PRINT:
3341 im->prt_c++;
3342 case GF_GPRINT:
3343 j=0;
3344 sscanf(&line[argstart], DEF_NAM_FMT ":%n",gdp->vname,&j);
3345 if (j==0) {
3346 rrd_set_error("Cannot parse vname in line: '%s'",line);
3347 break;
3348 }
3349 argstart+=j;
3350 if (rrd_graph_check_vname(im,gdp->vname,line)) return;
3351 j=0;
3352 sscanf(&line[argstart], CF_NAM_FMT ":%n",symname,&j);
3354 k=(j!=0)?rrd_graph_check_CF(im,symname,line):1;
3355 #define VIDX im->gdes[gdp->vidx]
3356 switch (k) {
3357 case -1: /* looks CF but is not really CF */
3358 if (VIDX.gf == GF_VDEF) rrd_clear_error();
3359 break;
3360 case 0: /* CF present and correct */
3361 if (VIDX.gf == GF_VDEF)
3362 rrd_set_error("Don't use CF when printing VDEF");
3363 argstart+=j;
3364 break;
3365 case 1: /* CF not present */
3366 if (VIDX.gf == GF_VDEF) rrd_clear_error();
3367 else rrd_set_error("Printing DEF or CDEF needs CF");
3368 break;
3369 default:
3370 rrd_set_error("Oops, bug in GPRINT scanning");
3371 }
3372 #undef VIDX
3373 if (rrd_test_error()) break;
3375 if (strlen(&line[argstart])!=0) {
3376 if (rrd_graph_legend(gdp,&line[argstart])==0)
3377 rrd_set_error("Cannot parse legend in line: %s",line);
3378 } else rrd_set_error("No legend in (G)PRINT line: %s",line);
3379 strcpy(gdp->format, gdp->legend);
3380 break;
3381 case GF_DEF:
3382 case GF_VDEF:
3383 case GF_CDEF:
3384 j=0;
3385 if (paramcnt<2) {
3386 rrd_set_error("Nothing following %s",param[0]);
3387 break;
3388 }
3389 sscanf(param[1], DEF_NAM_FMT "=%n",gdp->vname,&j);
3390 if (j==0) {
3391 rrd_set_error("Could not parse %s:%s",param[0],param[1]);
3392 break;
3393 }
3394 if (find_var(im,gdp->vname)!=-1) {
3395 rrd_set_error("Variable '%s' in %s:%s' already in use\n",
3396 gdp->vname,param[0],param[1]);
3397 break;
3398 }
3399 paramused++;
3400 argstart+=j;
3401 switch (gdp->gf) {
3402 case GF_DEF:
3403 if (strlen(¶m[1][j])>MAXPATH) {
3404 rrd_set_error("Path too long: %s:%s",param[0],param[1]);
3405 break;
3406 }
3407 strcpy(gdp->rrd,¶m[1][j]);
3409 if (paramcnt<3) {
3410 rrd_set_error("No DS for %s:%s",param[0],param[1]);
3411 break;
3412 }
3413 j=k=0;
3414 sscanf(param[2],DS_NAM_FMT "%n%*s%n",gdp->ds_nam,&j,&k);
3415 if ((j==0)||(k!=0)) {
3416 rrd_set_error("Cannot parse DS in %s:%s:%s",
3417 param[0],param[1],param[2]);
3418 break;
3419 }
3420 paramused++;
3421 if (paramcnt<4) {
3422 rrd_set_error("No CF for %s:%s:%s",
3423 param[0],param[1],param[2]);
3424 break;
3425 }
3426 j=k=0;
3427 sscanf(param[3],CF_NAM_FMT "%n%*s%n",symname,&j,&k);
3428 if ((j==0)||(k!=0)) {
3429 rrd_set_error("Cannot parse CF in %s:%s:%s:%s",
3430 param[0],param[1],param[2],param[3]);
3431 break;
3432 }
3433 if ((gdp->cf = cf_conv(symname))==-1) {
3434 rrd_set_error("Unknown CF '%s' in %s:%s:%s:%s",
3435 param[0],param[1],param[2],param[3]);
3436 break;
3437 }
3438 paramused++;
3439 if (paramcnt>paramused) {
3440 k=0;l=0;
3441 sscanf(param[4],
3442 "step=%lu%n%*s%n",
3443 &gdp->step,&k,&l);
3444 if ((k==0)||(l!=0)) {
3445 rrd_set_error("Cannot parse step in "
3446 "%s:%s:%s:%s:%s",
3447 param[0],param[1],param[2],param[3],param[4]);
3448 break;
3449 }
3450 paramused++;
3451 }
3452 break;
3453 case GF_VDEF:
3454 j=0;
3455 sscanf(&line[argstart],DEF_NAM_FMT ",%n",vname,&j);
3456 if (j==0) {
3457 rrd_set_error("Cannot parse vname in line '%s'",line);
3458 break;
3459 }
3460 argstart+=j;
3461 if (rrd_graph_check_vname(im,vname,line)) return;
3462 if ( im->gdes[gdp->vidx].gf != GF_DEF
3463 && im->gdes[gdp->vidx].gf != GF_CDEF) {
3464 rrd_set_error("variable '%s' not DEF nor "
3465 "CDEF in VDEF '%s'", vname,gdp->vname);
3466 break;
3467 }
3468 vdef_parse(gdp,&line[argstart+strstart]);
3469 break;
3470 case GF_CDEF:
3471 if (strstr(&line[argstart],":")!=NULL) {
3472 rrd_set_error("Error in RPN, line: %s",line);
3473 break;
3474 }
3475 if ((gdp->rpnp = rpn_parse(
3476 (void *)im,
3477 &line[argstart],
3478 &find_var_wrapper)
3479 )==NULL)
3480 rrd_set_error("invalid rpn expression in: %s",line);
3481 break;
3482 default: break;
3483 }
3484 break;
3485 default: rrd_set_error("Big oops");
3486 }
3487 if (rrd_test_error()) {
3488 im_free(im);
3489 return;
3490 }
3491 }
3493 if (im->gdes_c==0){
3494 rrd_set_error("can't make a graph without contents");
3495 im_free(im); /* ??? is this set ??? */
3496 return;
3497 }
3498 }
3500 int
3501 rrd_graph_check_vname(image_desc_t *im, char *varname, char *err)
3502 {
3503 if ((im->gdes[im->gdes_c-1].vidx=find_var(im,varname))==-1) {
3504 rrd_set_error("Unknown variable '%s' in %s",varname,err);
3505 return -1;
3506 }
3507 return 0;
3508 }
3509 int
3510 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
3511 {
3512 char *color;
3513 graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
3515 color=strstr(var,"#");
3516 if (color==NULL) {
3517 if (optional==0) {
3518 rrd_set_error("Found no color in %s",err);
3519 return 0;
3520 }
3521 return 0;
3522 } else {
3523 int n=0;
3524 char *rest;
3525 gfx_color_t col;
3527 rest=strstr(color,":");
3528 if (rest!=NULL)
3529 n=rest-color;
3530 else
3531 n=strlen(color);
3533 switch (n) {
3534 case 7:
3535 sscanf(color,"#%6lx%n",&col,&n);
3536 col = (col << 8) + 0xff /* shift left by 8 */;
3537 if (n!=7) rrd_set_error("Color problem in %s",err);
3538 break;
3539 case 9:
3540 sscanf(color,"#%8lx%n",&col,&n);
3541 if (n==9) break;
3542 default:
3543 rrd_set_error("Color problem in %s",err);
3544 }
3545 if (rrd_test_error()) return 0;
3546 gdp->col = col;
3547 return n;
3548 }
3549 }
3550 int
3551 rrd_graph_check_CF(image_desc_t *im, char *symname, char *err)
3552 {
3553 if ((im->gdes[im->gdes_c-1].cf=cf_conv(symname))==-1) {
3554 rrd_set_error("Unknown CF '%s' in %s",symname,err);
3555 return -1;
3556 }
3557 return 0;
3558 }
3559 int
3560 rrd_graph_legend(graph_desc_t *gdp, char *line)
3561 {
3562 int i;
3564 i=scan_for_col(line,FMT_LEG_LEN,gdp->legend);
3566 return (strlen(&line[i])==0);
3567 }
3570 int bad_format(char *fmt) {
3571 char *ptr;
3572 int n=0;
3573 ptr = fmt;
3574 while (*ptr != '\0')
3575 if (*ptr++ == '%') {
3577 /* line cannot end with percent char */
3578 if (*ptr == '\0') return 1;
3580 /* '%s', '%S' and '%%' are allowed */
3581 if (*ptr == 's' || *ptr == 'S' || *ptr == '%') ptr++;
3583 /* or else '% 6.2lf' and such are allowed */
3584 else {
3586 /* optional padding character */
3587 if (*ptr == ' ' || *ptr == '+' || *ptr == '-') ptr++;
3589 /* This should take care of 'm.n' with all three optional */
3590 while (*ptr >= '0' && *ptr <= '9') ptr++;
3591 if (*ptr == '.') ptr++;
3592 while (*ptr >= '0' && *ptr <= '9') ptr++;
3594 /* Either 'le' or 'lf' must follow here */
3595 if (*ptr++ != 'l') return 1;
3596 if (*ptr == 'e' || *ptr == 'f') ptr++;
3597 else return 1;
3598 n++;
3599 }
3600 }
3602 return (n!=1);
3603 }
3606 int
3607 vdef_parse(gdes,str)
3608 struct graph_desc_t *gdes;
3609 char *str;
3610 {
3611 /* A VDEF currently is either "func" or "param,func"
3612 * so the parsing is rather simple. Change if needed.
3613 */
3614 double param;
3615 char func[30];
3616 int n;
3618 n=0;
3619 sscanf(str,"%le,%29[A-Z]%n",¶m,func,&n);
3620 if (n==strlen(str)) { /* matched */
3621 ;
3622 } else {
3623 n=0;
3624 sscanf(str,"%29[A-Z]%n",func,&n);
3625 if (n==strlen(str)) { /* matched */
3626 param=DNAN;
3627 } else {
3628 rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3629 ,str
3630 ,gdes->vname
3631 );
3632 return -1;
3633 }
3634 }
3635 if (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3636 else if (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3637 else if (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3638 else if (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3639 else if (!strcmp("TOTAL", func)) gdes->vf.op = VDEF_TOTAL;
3640 else if (!strcmp("FIRST", func)) gdes->vf.op = VDEF_FIRST;
3641 else if (!strcmp("LAST", func)) gdes->vf.op = VDEF_LAST;
3642 else {
3643 rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3644 ,func
3645 ,gdes->vname
3646 );
3647 return -1;
3648 };
3650 switch (gdes->vf.op) {
3651 case VDEF_PERCENT:
3652 if (isnan(param)) { /* no parameter given */
3653 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3654 ,func
3655 ,gdes->vname
3656 );
3657 return -1;
3658 };
3659 if (param>=0.0 && param<=100.0) {
3660 gdes->vf.param = param;
3661 gdes->vf.val = DNAN; /* undefined */
3662 gdes->vf.when = 0; /* undefined */
3663 } else {
3664 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3665 ,param
3666 ,gdes->vname
3667 );
3668 return -1;
3669 };
3670 break;
3671 case VDEF_MAXIMUM:
3672 case VDEF_AVERAGE:
3673 case VDEF_MINIMUM:
3674 case VDEF_TOTAL:
3675 case VDEF_FIRST:
3676 case VDEF_LAST:
3677 if (isnan(param)) {
3678 gdes->vf.param = DNAN;
3679 gdes->vf.val = DNAN;
3680 gdes->vf.when = 0;
3681 } else {
3682 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3683 ,func
3684 ,gdes->vname
3685 );
3686 return -1;
3687 };
3688 break;
3689 };
3690 return 0;
3691 }
3694 int
3695 vdef_calc(im,gdi)
3696 image_desc_t *im;
3697 int gdi;
3698 {
3699 graph_desc_t *src,*dst;
3700 rrd_value_t *data;
3701 long step,steps;
3703 dst = &im->gdes[gdi];
3704 src = &im->gdes[dst->vidx];
3705 data = src->data + src->ds;
3706 steps = (src->end - src->start) / src->step;
3708 #if 0
3709 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3710 ,src->start
3711 ,src->end
3712 ,steps
3713 );
3714 #endif
3716 switch (dst->vf.op) {
3717 case VDEF_PERCENT: {
3718 rrd_value_t * array;
3719 int field;
3722 if ((array = malloc(steps*sizeof(double)))==NULL) {
3723 rrd_set_error("malloc VDEV_PERCENT");
3724 return -1;
3725 }
3726 for (step=0;step < steps; step++) {
3727 array[step]=data[step*src->ds_cnt];
3728 }
3729 qsort(array,step,sizeof(double),vdef_percent_compar);
3731 field = (steps-1)*dst->vf.param/100;
3732 dst->vf.val = array[field];
3733 dst->vf.when = 0; /* no time component */
3734 #if 0
3735 for(step=0;step<steps;step++)
3736 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3737 #endif
3738 }
3739 break;
3740 case VDEF_MAXIMUM:
3741 step=0;
3742 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3743 if (step == steps) {
3744 dst->vf.val = DNAN;
3745 dst->vf.when = 0;
3746 } else {
3747 dst->vf.val = data[step*src->ds_cnt];
3748 dst->vf.when = src->start + (step+1)*src->step;
3749 }
3750 while (step != steps) {
3751 if (finite(data[step*src->ds_cnt])) {
3752 if (data[step*src->ds_cnt] > dst->vf.val) {
3753 dst->vf.val = data[step*src->ds_cnt];
3754 dst->vf.when = src->start + (step+1)*src->step;
3755 }
3756 }
3757 step++;
3758 }
3759 break;
3760 case VDEF_TOTAL:
3761 case VDEF_AVERAGE: {
3762 int cnt=0;
3763 double sum=0.0;
3764 for (step=0;step<steps;step++) {
3765 if (finite(data[step*src->ds_cnt])) {
3766 sum += data[step*src->ds_cnt];
3767 cnt ++;
3768 };
3769 }
3770 if (cnt) {
3771 if (dst->vf.op == VDEF_TOTAL) {
3772 dst->vf.val = sum*src->step;
3773 dst->vf.when = cnt*src->step; /* not really "when" */
3774 } else {
3775 dst->vf.val = sum/cnt;
3776 dst->vf.when = 0; /* no time component */
3777 };
3778 } else {
3779 dst->vf.val = DNAN;
3780 dst->vf.when = 0;
3781 }
3782 }
3783 break;
3784 case VDEF_MINIMUM:
3785 step=0;
3786 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3787 if (step == steps) {
3788 dst->vf.val = DNAN;
3789 dst->vf.when = 0;
3790 } else {
3791 dst->vf.val = data[step*src->ds_cnt];
3792 dst->vf.when = src->start + (step+1)*src->step;
3793 }
3794 while (step != steps) {
3795 if (finite(data[step*src->ds_cnt])) {
3796 if (data[step*src->ds_cnt] < dst->vf.val) {
3797 dst->vf.val = data[step*src->ds_cnt];
3798 dst->vf.when = src->start + (step+1)*src->step;
3799 }
3800 }
3801 step++;
3802 }
3803 break;
3804 case VDEF_FIRST:
3805 /* The time value returned here is one step before the
3806 * actual time value. This is the start of the first
3807 * non-NaN interval.
3808 */
3809 step=0;
3810 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3811 if (step == steps) { /* all entries were NaN */
3812 dst->vf.val = DNAN;
3813 dst->vf.when = 0;
3814 } else {
3815 dst->vf.val = data[step*src->ds_cnt];
3816 dst->vf.when = src->start + step*src->step;
3817 }
3818 break;
3819 case VDEF_LAST:
3820 /* The time value returned here is the
3821 * actual time value. This is the end of the last
3822 * non-NaN interval.
3823 */
3824 step=steps-1;
3825 while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3826 if (step < 0) { /* all entries were NaN */
3827 dst->vf.val = DNAN;
3828 dst->vf.when = 0;
3829 } else {
3830 dst->vf.val = data[step*src->ds_cnt];
3831 dst->vf.when = src->start + (step+1)*src->step;
3832 }
3833 break;
3834 }
3835 return 0;
3836 }
3838 /* NaN < -INF < finite_values < INF */
3839 int
3840 vdef_percent_compar(a,b)
3841 const void *a,*b;
3842 {
3843 /* Equality is not returned; this doesn't hurt except
3844 * (maybe) for a little performance.
3845 */
3847 /* First catch NaN values. They are smallest */
3848 if (isnan( *(double *)a )) return -1;
3849 if (isnan( *(double *)b )) return 1;
3851 /* NaN doesn't reach this part so INF and -INF are extremes.
3852 * The sign from isinf() is compatible with the sign we return
3853 */
3854 if (isinf( *(double *)a )) return isinf( *(double *)a );
3855 if (isinf( *(double *)b )) return isinf( *(double *)b );
3857 /* If we reach this, both values must be finite */
3858 if ( *(double *)a < *(double *)b ) return -1; else return 1;
3859 }