04cdc5478e7fc6b68bb2c7649f89d40ca15ec7a4
1 /****************************************************************************
2 * RRDtool 1.1.x Copyright Tobias Oetiker, 1997 - 2002
3 ****************************************************************************
4 * rrd__graph.c make creates ne rrds
5 ****************************************************************************/
7 #if 0
8 #include "rrd_tool.h"
9 #endif
11 #include <sys/stat.h>
12 #ifdef WIN32
13 #include <io.h>
14 #include <fcntl.h>
15 #endif
17 #include "rrd_graph.h"
18 #include "rrd_graph_helper.h"
20 /* some constant definitions */
23 #ifndef RRD_DEFAULT_FONT
24 #define RRD_DEFAULT_FONT "/usr/share/fonts/truetype/openoffice/ariosor.ttf"
25 /* #define RRD_DEFAULT_FONT "/usr/share/fonts/truetype/Arial.ttf" */
26 #endif
29 text_prop_t text_prop[] = {
30 { 10.0, RRD_DEFAULT_FONT }, /* default */
31 { 12.0, RRD_DEFAULT_FONT }, /* title */
32 { 8.0, RRD_DEFAULT_FONT }, /* axis */
33 { 10.0, RRD_DEFAULT_FONT }, /* unit */
34 { 10.0, RRD_DEFAULT_FONT } /* legend */
35 };
37 xlab_t xlab[] = {
38 {0, TMT_SECOND,30, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
39 {2, TMT_MINUTE,1, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
40 {5, TMT_MINUTE,2, TMT_MINUTE,10, TMT_MINUTE,10, 0,"%H:%M"},
41 {10, TMT_MINUTE,5, TMT_MINUTE,20, TMT_MINUTE,20, 0,"%H:%M"},
42 {30, TMT_MINUTE,10, TMT_HOUR,1, TMT_HOUR,1, 0,"%H:%M"},
43 {60, TMT_MINUTE,30, TMT_HOUR,2, TMT_HOUR,2, 0,"%H:%M"},
44 {180, TMT_HOUR,1, TMT_HOUR,6, TMT_HOUR,6, 0,"%H:%M"},
45 /*{300, TMT_HOUR,3, TMT_HOUR,12, TMT_HOUR,12, 12*3600,"%a %p"}, this looks silly*/
46 {600, TMT_HOUR,6, TMT_DAY,1, TMT_DAY,1, 24*3600,"%a"},
47 {1800, TMT_HOUR,12, TMT_DAY,1, TMT_DAY,2, 24*3600,"%a"},
48 {3600, TMT_DAY,1, TMT_WEEK,1, TMT_WEEK,1, 7*24*3600,"Week %V"},
49 {3*3600, TMT_WEEK,1, TMT_MONTH,1, TMT_WEEK,2, 7*24*3600,"Week %V"},
50 {6*3600, TMT_MONTH,1, TMT_MONTH,1, TMT_MONTH,1, 30*24*3600,"%b"},
51 {48*3600, TMT_MONTH,1, TMT_MONTH,3, TMT_MONTH,3, 30*24*3600,"%b"},
52 {10*24*3600, TMT_YEAR,1, TMT_YEAR,1, TMT_YEAR,1, 365*24*3600,"%y"},
53 {-1,TMT_MONTH,0,TMT_MONTH,0,TMT_MONTH,0,0,""}
54 };
56 /* sensible logarithmic y label intervals ...
57 the first element of each row defines the possible starting points on the
58 y axis ... the other specify the */
60 double yloglab[][12]= {{ 1e9, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
61 { 1e3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
62 { 1e1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
63 /* { 1e1, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, */
64 { 1e1, 1, 2.5, 5, 7.5, 0, 0, 0, 0, 0, 0, 0 },
65 { 1e1, 1, 2, 4, 6, 8, 0, 0, 0, 0, 0, 0 },
66 { 1e1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0 },
67 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }};
69 /* sensible y label intervals ...*/
71 ylab_t ylab[]= {
72 {0.1, {1,2, 5,10}},
73 {0.2, {1,5,10,20}},
74 {0.5, {1,2, 4,10}},
75 {1.0, {1,2, 5,10}},
76 {2.0, {1,5,10,20}},
77 {5.0, {1,2, 4,10}},
78 {10.0, {1,2, 5,10}},
79 {20.0, {1,5,10,20}},
80 {50.0, {1,2, 4,10}},
81 {100.0, {1,2, 5,10}},
82 {200.0, {1,5,10,20}},
83 {500.0, {1,2, 4,10}},
84 {0.0, {0,0,0,0}}};
87 gfx_color_t graph_col[] = /* default colors */
88 { 0xFFFFFFFF, /* canvas */
89 0xF0F0F0FF, /* background */
90 0xD0D0D0FF, /* shade A */
91 0xA0A0A0FF, /* shade B */
92 0x909090FF, /* grid */
93 0xE05050FF, /* major grid */
94 0x000000FF, /* font */
95 0x000000FF, /* frame */
96 0xFF0000FF /* arrow */
97 };
100 /* #define DEBUG */
102 #ifdef DEBUG
103 # define DPRINT(x) (void)(printf x, printf("\n"))
104 #else
105 # define DPRINT(x)
106 #endif
109 /* initialize with xtr(im,0); */
110 int
111 xtr(image_desc_t *im,time_t mytime){
112 static double pixie;
113 if (mytime==0){
114 pixie = (double) im->xsize / (double)(im->end - im->start);
115 return im->xorigin;
116 }
117 return (int)((double)im->xorigin
118 + pixie * ( mytime - im->start ) );
119 }
121 /* translate data values into y coordinates */
122 int
123 ytr(image_desc_t *im, double value){
124 static double pixie;
125 double yval;
126 if (isnan(value)){
127 if(!im->logarithmic)
128 pixie = (double) im->ysize / (im->maxval - im->minval);
129 else
130 pixie = (double) im->ysize / (log10(im->maxval) - log10(im->minval));
131 yval = im->yorigin;
132 } else if(!im->logarithmic) {
133 yval = im->yorigin - pixie * (value - im->minval) + 0.5;
134 } else {
135 if (value < im->minval) {
136 yval = im->yorigin;
137 } else {
138 yval = im->yorigin - pixie * (log10(value) - log10(im->minval)) + 0.5;
139 }
140 }
141 /* make sure we don't return anything too unreasonable. GD lib can
142 get terribly slow when drawing lines outside its scope. This is
143 especially problematic in connection with the rigid option */
144 if (! im->rigid) {
145 return (int)yval;
146 } else if ((int)yval > im->yorigin) {
147 return im->yorigin+2;
148 } else if ((int) yval < im->yorigin - im->ysize){
149 return im->yorigin - im->ysize - 2;
150 } else {
151 return (int)yval;
152 }
153 }
157 /* conversion function for symbolic entry names */
160 #define conv_if(VV,VVV) \
161 if (strcmp(#VV, string) == 0) return VVV ;
163 enum gf_en gf_conv(char *string){
165 conv_if(PRINT,GF_PRINT)
166 conv_if(GPRINT,GF_GPRINT)
167 conv_if(COMMENT,GF_COMMENT)
168 conv_if(HRULE,GF_HRULE)
169 conv_if(VRULE,GF_VRULE)
170 conv_if(LINE,GF_LINE)
171 conv_if(AREA,GF_AREA)
172 conv_if(STACK,GF_STACK)
173 conv_if(TICK,GF_TICK)
174 conv_if(DEF,GF_DEF)
175 conv_if(CDEF,GF_CDEF)
176 conv_if(VDEF,GF_VDEF)
177 conv_if(PART,GF_PART)
179 return (-1);
180 }
182 enum if_en if_conv(char *string){
184 conv_if(GIF,IF_GIF)
185 conv_if(PNG,IF_PNG)
187 return (-1);
188 }
190 enum tmt_en tmt_conv(char *string){
192 conv_if(SECOND,TMT_SECOND)
193 conv_if(MINUTE,TMT_MINUTE)
194 conv_if(HOUR,TMT_HOUR)
195 conv_if(DAY,TMT_DAY)
196 conv_if(WEEK,TMT_WEEK)
197 conv_if(MONTH,TMT_MONTH)
198 conv_if(YEAR,TMT_YEAR)
199 return (-1);
200 }
202 enum grc_en grc_conv(char *string){
204 conv_if(BACK,GRC_BACK)
205 conv_if(CANVAS,GRC_CANVAS)
206 conv_if(SHADEA,GRC_SHADEA)
207 conv_if(SHADEB,GRC_SHADEB)
208 conv_if(GRID,GRC_GRID)
209 conv_if(MGRID,GRC_MGRID)
210 conv_if(FONT,GRC_FONT)
211 conv_if(FRAME,GRC_FRAME)
212 conv_if(ARROW,GRC_ARROW)
214 return -1;
215 }
217 enum text_prop_en text_prop_conv(char *string){
219 conv_if(DEFAULT,TEXT_PROP_DEFAULT)
220 conv_if(TITLE,TEXT_PROP_TITLE)
221 conv_if(AXIS,TEXT_PROP_AXIS)
222 conv_if(UNIT,TEXT_PROP_UNIT)
223 conv_if(LEGEND,TEXT_PROP_LEGEND)
224 return -1;
225 }
228 #undef conv_if
232 int
233 im_free(image_desc_t *im)
234 {
235 long i,ii;
236 if (im == NULL) return 0;
237 for(i=0;i<im->gdes_c;i++){
238 if (im->gdes[i].data_first){
239 /* careful here, because a single pointer can occur several times */
240 free (im->gdes[i].data);
241 if (im->gdes[i].ds_namv){
242 for (ii=0;ii<im->gdes[i].ds_cnt;ii++)
243 free(im->gdes[i].ds_namv[ii]);
244 free(im->gdes[i].ds_namv);
245 }
246 }
247 free (im->gdes[i].p_data);
248 free (im->gdes[i].rpnp);
249 }
250 free(im->gdes);
251 return 0;
252 }
254 /* find SI magnitude symbol for the given number*/
255 void
256 auto_scale(
257 image_desc_t *im, /* image description */
258 double *value,
259 char **symb_ptr,
260 double *magfact
261 )
262 {
264 char *symbol[] = {"a", /* 10e-18 Atto */
265 "f", /* 10e-15 Femto */
266 "p", /* 10e-12 Pico */
267 "n", /* 10e-9 Nano */
268 "u", /* 10e-6 Micro */
269 "m", /* 10e-3 Milli */
270 " ", /* Base */
271 "k", /* 10e3 Kilo */
272 "M", /* 10e6 Mega */
273 "G", /* 10e9 Giga */
274 "T", /* 10e12 Tera */
275 "P", /* 10e15 Peta */
276 "E"};/* 10e18 Exa */
278 int symbcenter = 6;
279 int sindex;
281 if (*value == 0.0 || isnan(*value) ) {
282 sindex = 0;
283 *magfact = 1.0;
284 } else {
285 sindex = floor(log(fabs(*value))/log((double)im->base));
286 *magfact = pow((double)im->base, (double)sindex);
287 (*value) /= (*magfact);
288 }
289 if ( sindex <= symbcenter && sindex >= -symbcenter) {
290 (*symb_ptr) = symbol[sindex+symbcenter];
291 }
292 else {
293 (*symb_ptr) = "?";
294 }
295 }
298 /* find SI magnitude symbol for the numbers on the y-axis*/
299 void
300 si_unit(
301 image_desc_t *im /* image description */
302 )
303 {
305 char symbol[] = {'a', /* 10e-18 Atto */
306 'f', /* 10e-15 Femto */
307 'p', /* 10e-12 Pico */
308 'n', /* 10e-9 Nano */
309 'u', /* 10e-6 Micro */
310 'm', /* 10e-3 Milli */
311 ' ', /* Base */
312 'k', /* 10e3 Kilo */
313 'M', /* 10e6 Mega */
314 'G', /* 10e9 Giga */
315 'T', /* 10e12 Tera */
316 'P', /* 10e15 Peta */
317 'E'};/* 10e18 Exa */
319 int symbcenter = 6;
320 double digits;
322 if (im->unitsexponent != 9999) {
323 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
324 digits = floor(im->unitsexponent / 3);
325 } else {
326 digits = floor( log( max( fabs(im->minval),fabs(im->maxval)))/log((double)im->base));
327 }
328 im->magfact = pow((double)im->base , digits);
330 #ifdef DEBUG
331 printf("digits %6.3f im->magfact %6.3f\n",digits,im->magfact);
332 #endif
334 if ( ((digits+symbcenter) < sizeof(symbol)) &&
335 ((digits+symbcenter) >= 0) )
336 im->symbol = symbol[(int)digits+symbcenter];
337 else
338 im->symbol = ' ';
339 }
341 /* move min and max values around to become sensible */
343 void
344 expand_range(image_desc_t *im)
345 {
346 double sensiblevalues[] ={1000.0,900.0,800.0,750.0,700.0,
347 600.0,500.0,400.0,300.0,250.0,
348 200.0,125.0,100.0,90.0,80.0,
349 75.0,70.0,60.0,50.0,40.0,30.0,
350 25.0,20.0,10.0,9.0,8.0,
351 7.0,6.0,5.0,4.0,3.5,3.0,
352 2.5,2.0,1.8,1.5,1.2,1.0,
353 0.8,0.7,0.6,0.5,0.4,0.3,0.2,0.1,0.0,-1};
355 double scaled_min,scaled_max;
356 double adj;
357 int i;
361 #ifdef DEBUG
362 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
363 im->minval,im->maxval,im->magfact);
364 #endif
366 if (isnan(im->ygridstep)){
367 if(im->extra_flags & ALTAUTOSCALE) {
368 /* measure the amplitude of the function. Make sure that
369 graph boundaries are slightly higher then max/min vals
370 so we can see amplitude on the graph */
371 double delt, fact;
373 delt = im->maxval - im->minval;
374 adj = delt * 0.1;
375 fact = 2.0 * pow(10.0,
376 floor(log10(max(fabs(im->minval), fabs(im->maxval)))) - 2);
377 if (delt < fact) {
378 adj = (fact - delt) * 0.55;
379 #ifdef DEBUG
380 printf("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n", im->minval, im->maxval, delt, fact, adj);
381 #endif
382 }
383 im->minval -= adj;
384 im->maxval += adj;
385 }
386 else if(im->extra_flags & ALTAUTOSCALE_MAX) {
387 /* measure the amplitude of the function. Make sure that
388 graph boundaries are slightly higher than max vals
389 so we can see amplitude on the graph */
390 adj = (im->maxval - im->minval) * 0.1;
391 im->maxval += adj;
392 }
393 else {
394 scaled_min = im->minval / im->magfact;
395 scaled_max = im->maxval / im->magfact;
397 for (i=1; sensiblevalues[i] > 0; i++){
398 if (sensiblevalues[i-1]>=scaled_min &&
399 sensiblevalues[i]<=scaled_min)
400 im->minval = sensiblevalues[i]*(im->magfact);
402 if (-sensiblevalues[i-1]<=scaled_min &&
403 -sensiblevalues[i]>=scaled_min)
404 im->minval = -sensiblevalues[i-1]*(im->magfact);
406 if (sensiblevalues[i-1] >= scaled_max &&
407 sensiblevalues[i] <= scaled_max)
408 im->maxval = sensiblevalues[i-1]*(im->magfact);
410 if (-sensiblevalues[i-1]<=scaled_max &&
411 -sensiblevalues[i] >=scaled_max)
412 im->maxval = -sensiblevalues[i]*(im->magfact);
413 }
414 }
415 } else {
416 /* adjust min and max to the grid definition if there is one */
417 im->minval = (double)im->ylabfact * im->ygridstep *
418 floor(im->minval / ((double)im->ylabfact * im->ygridstep));
419 im->maxval = (double)im->ylabfact * im->ygridstep *
420 ceil(im->maxval /( (double)im->ylabfact * im->ygridstep));
421 }
423 #ifdef DEBUG
424 fprintf(stderr,"SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
425 im->minval,im->maxval,im->magfact);
426 #endif
427 }
430 /* reduce data reimplementation by Alex */
432 void
433 reduce_data(
434 enum cf_en cf, /* which consolidation function ?*/
435 unsigned long cur_step, /* step the data currently is in */
436 time_t *start, /* start, end and step as requested ... */
437 time_t *end, /* ... by the application will be ... */
438 unsigned long *step, /* ... adjusted to represent reality */
439 unsigned long *ds_cnt, /* number of data sources in file */
440 rrd_value_t **data) /* two dimensional array containing the data */
441 {
442 int i,reduce_factor = ceil((double)(*step) / (double)cur_step);
443 unsigned long col,dst_row,row_cnt,start_offset,end_offset,skiprows=0;
444 rrd_value_t *srcptr,*dstptr;
446 (*step) = cur_step*reduce_factor; /* set new step size for reduced data */
447 dstptr = *data;
448 srcptr = *data;
449 row_cnt = ((*end)-(*start))/cur_step;
451 #ifdef DEBUG
452 #define DEBUG_REDUCE
453 #endif
454 #ifdef DEBUG_REDUCE
455 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
456 row_cnt,reduce_factor,*start,*end,cur_step);
457 for (col=0;col<row_cnt;col++) {
458 printf("time %10lu: ",*start+(col+1)*cur_step);
459 for (i=0;i<*ds_cnt;i++)
460 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
461 printf("\n");
462 }
463 #endif
465 /* We have to combine [reduce_factor] rows of the source
466 ** into one row for the destination. Doing this we also
467 ** need to take care to combine the correct rows. First
468 ** alter the start and end time so that they are multiples
469 ** of the new step time. We cannot reduce the amount of
470 ** time so we have to move the end towards the future and
471 ** the start towards the past.
472 */
473 end_offset = (*end) % (*step);
474 start_offset = (*start) % (*step);
476 /* If there is a start offset (which cannot be more than
477 ** one destination row), skip the appropriate number of
478 ** source rows and one destination row. The appropriate
479 ** number is what we do know (start_offset/cur_step) of
480 ** the new interval (*step/cur_step aka reduce_factor).
481 */
482 #ifdef DEBUG_REDUCE
483 printf("start_offset: %lu end_offset: %lu\n",start_offset,end_offset);
484 printf("row_cnt before: %lu\n",row_cnt);
485 #endif
486 if (start_offset) {
487 (*start) = (*start)-start_offset;
488 skiprows=reduce_factor-start_offset/cur_step;
489 srcptr+=skiprows* *ds_cnt;
490 for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
491 row_cnt-=skiprows;
492 }
493 #ifdef DEBUG_REDUCE
494 printf("row_cnt between: %lu\n",row_cnt);
495 #endif
497 /* At the end we have some rows that are not going to be
498 ** used, the amount is end_offset/cur_step
499 */
500 if (end_offset) {
501 (*end) = (*end)-end_offset+(*step);
502 skiprows = end_offset/cur_step;
503 row_cnt-=skiprows;
504 }
505 #ifdef DEBUG_REDUCE
506 printf("row_cnt after: %lu\n",row_cnt);
507 #endif
509 /* Sanity check: row_cnt should be multiple of reduce_factor */
510 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
512 if (row_cnt%reduce_factor) {
513 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
514 row_cnt,reduce_factor);
515 printf("BUG in reduce_data()\n");
516 exit(1);
517 }
519 /* Now combine reduce_factor intervals at a time
520 ** into one interval for the destination.
521 */
523 for (dst_row=0;row_cnt>=reduce_factor;dst_row++) {
524 for (col=0;col<(*ds_cnt);col++) {
525 rrd_value_t newval=DNAN;
526 unsigned long validval=0;
528 for (i=0;i<reduce_factor;i++) {
529 if (isnan(srcptr[i*(*ds_cnt)+col])) {
530 continue;
531 }
532 validval++;
533 if (isnan(newval)) newval = srcptr[i*(*ds_cnt)+col];
534 else {
535 switch (cf) {
536 case CF_HWPREDICT:
537 case CF_DEVSEASONAL:
538 case CF_DEVPREDICT:
539 case CF_SEASONAL:
540 case CF_AVERAGE:
541 newval += srcptr[i*(*ds_cnt)+col];
542 break;
543 case CF_MINIMUM:
544 newval = min (newval,srcptr[i*(*ds_cnt)+col]);
545 break;
546 case CF_FAILURES:
547 /* an interval contains a failure if any subintervals contained a failure */
548 case CF_MAXIMUM:
549 newval = max (newval,srcptr[i*(*ds_cnt)+col]);
550 break;
551 case CF_LAST:
552 newval = srcptr[i*(*ds_cnt)+col];
553 break;
554 }
555 }
556 }
557 if (validval == 0){newval = DNAN;} else{
558 switch (cf) {
559 case CF_HWPREDICT:
560 case CF_DEVSEASONAL:
561 case CF_DEVPREDICT:
562 case CF_SEASONAL:
563 case CF_AVERAGE:
564 newval /= validval;
565 break;
566 case CF_MINIMUM:
567 case CF_FAILURES:
568 case CF_MAXIMUM:
569 case CF_LAST:
570 break;
571 }
572 }
573 *dstptr++=newval;
574 }
575 srcptr+=(*ds_cnt)*reduce_factor;
576 row_cnt-=reduce_factor;
577 }
578 /* If we had to alter the endtime, we didn't have enough
579 ** source rows to fill the last row. Fill it with NaN.
580 */
581 if (end_offset) for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
582 #ifdef DEBUG_REDUCE
583 row_cnt = ((*end)-(*start))/ *step;
584 srcptr = *data;
585 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
586 row_cnt,*start,*end,*step);
587 for (col=0;col<row_cnt;col++) {
588 printf("time %10lu: ",*start+(col+1)*(*step));
589 for (i=0;i<*ds_cnt;i++)
590 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
591 printf("\n");
592 }
593 #endif
594 }
597 /* get the data required for the graphs from the
598 relevant rrds ... */
600 int
601 data_fetch( image_desc_t *im )
602 {
603 int i,ii;
604 int skip;
605 /* pull the data from the log files ... */
606 for (i=0;i<im->gdes_c;i++){
607 /* only GF_DEF elements fetch data */
608 if (im->gdes[i].gf != GF_DEF)
609 continue;
611 skip=0;
612 /* do we have it already ?*/
613 for (ii=0;ii<i;ii++){
614 if (im->gdes[ii].gf != GF_DEF)
615 continue;
616 if((strcmp(im->gdes[i].rrd,im->gdes[ii].rrd) == 0)
617 && (im->gdes[i].cf == im->gdes[ii].cf)){
618 /* OK the data it is here already ...
619 * we just copy the header portion */
620 im->gdes[i].start = im->gdes[ii].start;
621 im->gdes[i].end = im->gdes[ii].end;
622 im->gdes[i].step = im->gdes[ii].step;
623 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
624 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
625 im->gdes[i].data = im->gdes[ii].data;
626 im->gdes[i].data_first = 0;
627 skip=1;
628 }
629 if (skip)
630 break;
631 }
632 if (! skip) {
633 unsigned long ft_step = im->gdes[i].step ;
635 if((rrd_fetch_fn(im->gdes[i].rrd,
636 im->gdes[i].cf,
637 &im->gdes[i].start,
638 &im->gdes[i].end,
639 &ft_step,
640 &im->gdes[i].ds_cnt,
641 &im->gdes[i].ds_namv,
642 &im->gdes[i].data)) == -1){
643 return -1;
644 }
645 im->gdes[i].data_first = 1;
647 if (ft_step < im->gdes[i].step) {
648 reduce_data(im->gdes[i].cf,
649 ft_step,
650 &im->gdes[i].start,
651 &im->gdes[i].end,
652 &im->gdes[i].step,
653 &im->gdes[i].ds_cnt,
654 &im->gdes[i].data);
655 } else {
656 im->gdes[i].step = ft_step;
657 }
658 }
660 /* lets see if the required data source is realy there */
661 for(ii=0;ii<im->gdes[i].ds_cnt;ii++){
662 if(strcmp(im->gdes[i].ds_namv[ii],im->gdes[i].ds_nam) == 0){
663 im->gdes[i].ds=ii; }
664 }
665 if (im->gdes[i].ds== -1){
666 rrd_set_error("No DS called '%s' in '%s'",
667 im->gdes[i].ds_nam,im->gdes[i].rrd);
668 return -1;
669 }
671 }
672 return 0;
673 }
675 /* evaluate the expressions in the CDEF functions */
677 /*************************************************************
678 * CDEF stuff
679 *************************************************************/
681 long
682 find_var_wrapper(void *arg1, char *key)
683 {
684 return find_var((image_desc_t *) arg1, key);
685 }
687 /* find gdes containing var*/
688 long
689 find_var(image_desc_t *im, char *key){
690 long ii;
691 for(ii=0;ii<im->gdes_c-1;ii++){
692 if((im->gdes[ii].gf == GF_DEF
693 || im->gdes[ii].gf == GF_VDEF
694 || im->gdes[ii].gf == GF_CDEF)
695 && (strcmp(im->gdes[ii].vname,key) == 0)){
696 return ii;
697 }
698 }
699 return -1;
700 }
702 /* find the largest common denominator for all the numbers
703 in the 0 terminated num array */
704 long
705 lcd(long *num){
706 long rest;
707 int i;
708 for (i=0;num[i+1]!=0;i++){
709 do {
710 rest=num[i] % num[i+1];
711 num[i]=num[i+1]; num[i+1]=rest;
712 } while (rest!=0);
713 num[i+1] = num[i];
714 }
715 /* return i==0?num[i]:num[i-1]; */
716 return num[i];
717 }
719 /* run the rpn calculator on all the VDEF and CDEF arguments */
720 int
721 data_calc( image_desc_t *im){
723 int gdi;
724 int dataidx;
725 long *steparray, rpi;
726 int stepcnt;
727 time_t now;
728 rpnstack_t rpnstack;
730 rpnstack_init(&rpnstack);
732 for (gdi=0;gdi<im->gdes_c;gdi++){
733 /* Look for GF_VDEF and GF_CDEF in the same loop,
734 * so CDEFs can use VDEFs and vice versa
735 */
736 switch (im->gdes[gdi].gf) {
737 case GF_VDEF:
738 /* A VDEF has no DS. This also signals other parts
739 * of rrdtool that this is a VDEF value, not a CDEF.
740 */
741 im->gdes[gdi].ds_cnt = 0;
742 if (vdef_calc(im,gdi)) {
743 rrd_set_error("Error processing VDEF '%s'"
744 ,im->gdes[gdi].vname
745 );
746 rpnstack_free(&rpnstack);
747 return -1;
748 }
749 break;
750 case GF_CDEF:
751 im->gdes[gdi].ds_cnt = 1;
752 im->gdes[gdi].ds = 0;
753 im->gdes[gdi].data_first = 1;
754 im->gdes[gdi].start = 0;
755 im->gdes[gdi].end = 0;
756 steparray=NULL;
757 stepcnt = 0;
758 dataidx=-1;
760 /* Find the variables in the expression.
761 * - VDEF variables are substituted by their values
762 * and the opcode is changed into OP_NUMBER.
763 * - CDEF variables are analized for their step size,
764 * the lowest common denominator of all the step
765 * sizes of the data sources involved is calculated
766 * and the resulting number is the step size for the
767 * resulting data source.
768 */
769 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
770 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE){
771 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
772 if (im->gdes[ptr].ds_cnt == 0) {
773 #if 0
774 printf("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
775 im->gdes[gdi].vname,
776 im->gdes[ptr].vname);
777 printf("DEBUG: value from vdef is %f\n",im->gdes[ptr].vf.val);
778 #endif
779 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
780 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
781 } else {
782 if ((steparray = rrd_realloc(steparray, (++stepcnt+1)*sizeof(*steparray)))==NULL){
783 rrd_set_error("realloc steparray");
784 rpnstack_free(&rpnstack);
785 return -1;
786 };
788 steparray[stepcnt-1] = im->gdes[ptr].step;
790 /* adjust start and end of cdef (gdi) so
791 * that it runs from the latest start point
792 * to the earliest endpoint of any of the
793 * rras involved (ptr)
794 */
795 if(im->gdes[gdi].start < im->gdes[ptr].start)
796 im->gdes[gdi].start = im->gdes[ptr].start;
798 if(im->gdes[gdi].end == 0 ||
799 im->gdes[gdi].end > im->gdes[ptr].end)
800 im->gdes[gdi].end = im->gdes[ptr].end;
802 /* store pointer to the first element of
803 * the rra providing data for variable,
804 * further save step size and data source
805 * count of this rra
806 */
807 im->gdes[gdi].rpnp[rpi].data = im->gdes[ptr].data + im->gdes[ptr].ds;
808 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
809 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
811 /* backoff the *.data ptr; this is done so
812 * rpncalc() function doesn't have to treat
813 * the first case differently
814 */
815 } /* if ds_cnt != 0 */
816 } /* if OP_VARIABLE */
817 } /* loop through all rpi */
819 if(steparray == NULL){
820 rrd_set_error("rpn expressions without DEF"
821 " or CDEF variables are not supported");
822 rpnstack_free(&rpnstack);
823 return -1;
824 }
825 steparray[stepcnt]=0;
826 /* Now find the resulting step. All steps in all
827 * used RRAs have to be visited
828 */
829 im->gdes[gdi].step = lcd(steparray);
830 free(steparray);
831 if((im->gdes[gdi].data = malloc((
832 (im->gdes[gdi].end-im->gdes[gdi].start)
833 / im->gdes[gdi].step)
834 * sizeof(double)))==NULL){
835 rrd_set_error("malloc im->gdes[gdi].data");
836 rpnstack_free(&rpnstack);
837 return -1;
838 }
840 /* Step through the new cdef results array and
841 * calculate the values
842 */
843 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
844 now<=im->gdes[gdi].end;
845 now += im->gdes[gdi].step)
846 {
847 rpnp_t *rpnp = im -> gdes[gdi].rpnp;
849 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
850 * in this case we are advancing by timesteps;
851 * we use the fact that time_t is a synonym for long
852 */
853 if (rpn_calc(rpnp,&rpnstack,(long) now,
854 im->gdes[gdi].data,++dataidx) == -1) {
855 /* rpn_calc sets the error string */
856 rpnstack_free(&rpnstack);
857 return -1;
858 }
859 } /* enumerate over time steps within a CDEF */
860 break;
861 default:
862 continue;
863 }
864 } /* enumerate over CDEFs */
865 rpnstack_free(&rpnstack);
866 return 0;
867 }
869 /* massage data so, that we get one value for each x coordinate in the graph */
870 int
871 data_proc( image_desc_t *im ){
872 long i,ii;
873 double pixstep = (double)(im->end-im->start)
874 /(double)im->xsize; /* how much time
875 passes in one pixel */
876 double paintval;
877 double minval=DNAN,maxval=DNAN;
879 unsigned long gr_time;
881 /* memory for the processed data */
882 for(i=0;i<im->gdes_c;i++){
883 if((im->gdes[i].gf==GF_LINE) ||
884 (im->gdes[i].gf==GF_AREA) ||
885 (im->gdes[i].gf==GF_TICK) ||
886 (im->gdes[i].gf==GF_STACK)){
887 if((im->gdes[i].p_data = malloc((im->xsize +1)
888 * sizeof(rrd_value_t)))==NULL){
889 rrd_set_error("malloc data_proc");
890 return -1;
891 }
892 }
893 }
895 for(i=0;i<im->xsize;i++){
896 long vidx;
897 gr_time = im->start+pixstep*i; /* time of the
898 current step */
899 paintval=0.0;
901 for(ii=0;ii<im->gdes_c;ii++){
902 double value;
903 switch(im->gdes[ii].gf){
904 case GF_LINE:
905 case GF_AREA:
906 case GF_TICK:
907 paintval = 0.0;
908 case GF_STACK:
909 vidx = im->gdes[ii].vidx;
911 value =
912 im->gdes[vidx].data[
913 ((unsigned long)floor(
914 (double)(gr_time-im->gdes[vidx].start) / im->gdes[vidx].step
915 )
916 ) *im->gdes[vidx].ds_cnt
917 +im->gdes[vidx].ds];
919 if (! isnan(value)) {
920 paintval += value;
921 im->gdes[ii].p_data[i] = paintval;
922 /* GF_TICK: the data values are not relevant for min and max */
923 if (finite(paintval) && im->gdes[ii].gf != GF_TICK ){
924 if (isnan(minval) || paintval < minval)
925 minval = paintval;
926 if (isnan(maxval) || paintval > maxval)
927 maxval = paintval;
928 }
929 } else {
930 im->gdes[ii].p_data[i] = DNAN;
931 }
932 break;
933 case GF_PRINT:
934 case GF_GPRINT:
935 case GF_COMMENT:
936 case GF_HRULE:
937 case GF_VRULE:
938 case GF_DEF:
939 case GF_CDEF:
940 case GF_VDEF:
941 case GF_PART:
942 break;
943 }
944 }
945 }
947 /* if min or max have not been asigned a value this is because
948 there was no data in the graph ... this is not good ...
949 lets set these to dummy values then ... */
951 if (isnan(minval)) minval = 0.0;
952 if (isnan(maxval)) maxval = 1.0;
954 /* adjust min and max values */
955 if (isnan(im->minval)
956 || ((!im->logarithmic && !im->rigid) /* don't adjust low-end with log scale */
957 && im->minval > minval))
958 im->minval = minval;
959 if (isnan(im->maxval)
960 || (!im->rigid
961 && im->maxval < maxval)){
962 if (im->logarithmic)
963 im->maxval = maxval * 1.1;
964 else
965 im->maxval = maxval;
966 }
967 /* make sure min and max are not equal */
968 if (im->minval == im->maxval) {
969 im->maxval *= 1.01;
970 if (! im->logarithmic) {
971 im->minval *= 0.99;
972 }
974 /* make sure min and max are not both zero */
975 if (im->maxval == 0.0) {
976 im->maxval = 1.0;
977 }
979 }
980 return 0;
981 }
985 /* identify the point where the first gridline, label ... gets placed */
987 time_t
988 find_first_time(
989 time_t start, /* what is the initial time */
990 enum tmt_en baseint, /* what is the basic interval */
991 long basestep /* how many if these do we jump a time */
992 )
993 {
994 struct tm tm;
995 tm = *localtime(&start);
996 switch(baseint){
997 case TMT_SECOND:
998 tm.tm_sec -= tm.tm_sec % basestep; break;
999 case TMT_MINUTE:
1000 tm.tm_sec=0;
1001 tm.tm_min -= tm.tm_min % basestep;
1002 break;
1003 case TMT_HOUR:
1004 tm.tm_sec=0;
1005 tm.tm_min = 0;
1006 tm.tm_hour -= tm.tm_hour % basestep; break;
1007 case TMT_DAY:
1008 /* we do NOT look at the basestep for this ... */
1009 tm.tm_sec=0;
1010 tm.tm_min = 0;
1011 tm.tm_hour = 0; break;
1012 case TMT_WEEK:
1013 /* we do NOT look at the basestep for this ... */
1014 tm.tm_sec=0;
1015 tm.tm_min = 0;
1016 tm.tm_hour = 0;
1017 tm.tm_mday -= tm.tm_wday -1; /* -1 because we want the monday */
1018 if (tm.tm_wday==0) tm.tm_mday -= 7; /* we want the *previous* monday */
1019 break;
1020 case TMT_MONTH:
1021 tm.tm_sec=0;
1022 tm.tm_min = 0;
1023 tm.tm_hour = 0;
1024 tm.tm_mday = 1;
1025 tm.tm_mon -= tm.tm_mon % basestep; break;
1027 case TMT_YEAR:
1028 tm.tm_sec=0;
1029 tm.tm_min = 0;
1030 tm.tm_hour = 0;
1031 tm.tm_mday = 1;
1032 tm.tm_mon = 0;
1033 tm.tm_year -= (tm.tm_year+1900) % basestep;
1035 }
1036 return mktime(&tm);
1037 }
1038 /* identify the point where the next gridline, label ... gets placed */
1039 time_t
1040 find_next_time(
1041 time_t current, /* what is the initial time */
1042 enum tmt_en baseint, /* what is the basic interval */
1043 long basestep /* how many if these do we jump a time */
1044 )
1045 {
1046 struct tm tm;
1047 time_t madetime;
1048 tm = *localtime(¤t);
1049 do {
1050 switch(baseint){
1051 case TMT_SECOND:
1052 tm.tm_sec += basestep; break;
1053 case TMT_MINUTE:
1054 tm.tm_min += basestep; break;
1055 case TMT_HOUR:
1056 tm.tm_hour += basestep; break;
1057 case TMT_DAY:
1058 tm.tm_mday += basestep; break;
1059 case TMT_WEEK:
1060 tm.tm_mday += 7*basestep; break;
1061 case TMT_MONTH:
1062 tm.tm_mon += basestep; break;
1063 case TMT_YEAR:
1064 tm.tm_year += basestep;
1065 }
1066 madetime = mktime(&tm);
1067 } while (madetime == -1); /* this is necessary to skip impssible times
1068 like the daylight saving time skips */
1069 return madetime;
1071 }
1074 /* calculate values required for PRINT and GPRINT functions */
1076 int
1077 print_calc(image_desc_t *im, char ***prdata)
1078 {
1079 long i,ii,validsteps;
1080 double printval;
1081 time_t printtime;
1082 int graphelement = 0;
1083 long vidx;
1084 int max_ii;
1085 double magfact = -1;
1086 char *si_symb = "";
1087 char *percent_s;
1088 int prlines = 1;
1089 if (im->imginfo) prlines++;
1090 for(i=0;i<im->gdes_c;i++){
1091 switch(im->gdes[i].gf){
1092 case GF_PRINT:
1093 prlines++;
1094 if(((*prdata) = rrd_realloc((*prdata),prlines*sizeof(char *)))==NULL){
1095 rrd_set_error("realloc prdata");
1096 return 0;
1097 }
1098 case GF_GPRINT:
1099 /* PRINT and GPRINT can now print VDEF generated values.
1100 * There's no need to do any calculations on them as these
1101 * calculations were already made.
1102 */
1103 vidx = im->gdes[i].vidx;
1104 if (im->gdes[vidx].gf==GF_VDEF) { /* simply use vals */
1105 printval = im->gdes[vidx].vf.val;
1106 printtime = im->gdes[vidx].vf.when;
1107 } else { /* need to calculate max,min,avg etcetera */
1108 max_ii =((im->gdes[vidx].end
1109 - im->gdes[vidx].start)
1110 / im->gdes[vidx].step
1111 * im->gdes[vidx].ds_cnt);
1112 printval = DNAN;
1113 validsteps = 0;
1114 for( ii=im->gdes[vidx].ds;
1115 ii < max_ii;
1116 ii+=im->gdes[vidx].ds_cnt){
1117 if (! finite(im->gdes[vidx].data[ii]))
1118 continue;
1119 if (isnan(printval)){
1120 printval = im->gdes[vidx].data[ii];
1121 validsteps++;
1122 continue;
1123 }
1125 switch (im->gdes[i].cf){
1126 case CF_HWPREDICT:
1127 case CF_DEVPREDICT:
1128 case CF_DEVSEASONAL:
1129 case CF_SEASONAL:
1130 case CF_AVERAGE:
1131 validsteps++;
1132 printval += im->gdes[vidx].data[ii];
1133 break;
1134 case CF_MINIMUM:
1135 printval = min( printval, im->gdes[vidx].data[ii]);
1136 break;
1137 case CF_FAILURES:
1138 case CF_MAXIMUM:
1139 printval = max( printval, im->gdes[vidx].data[ii]);
1140 break;
1141 case CF_LAST:
1142 printval = im->gdes[vidx].data[ii];
1143 }
1144 }
1145 if (im->gdes[i].cf==CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1146 if (validsteps > 1) {
1147 printval = (printval / validsteps);
1148 }
1149 }
1150 } /* prepare printval */
1152 if (!strcmp(im->gdes[i].format,"%c")) { /* VDEF time print */
1153 if (im->gdes[i].gf == GF_PRINT){
1154 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1155 sprintf((*prdata)[prlines-2],"%s (%lu)",
1156 ctime(&printtime),printtime);
1157 (*prdata)[prlines-1] = NULL;
1158 } else {
1159 sprintf(im->gdes[i].legend,"%s (%lu)",
1160 ctime(&printtime),printtime);
1161 graphelement = 1;
1162 }
1163 } else {
1164 if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) {
1165 /* Magfact is set to -1 upon entry to print_calc. If it
1166 * is still less than 0, then we need to run auto_scale.
1167 * Otherwise, put the value into the correct units. If
1168 * the value is 0, then do not set the symbol or magnification
1169 * so next the calculation will be performed again. */
1170 if (magfact < 0.0) {
1171 auto_scale(im,&printval,&si_symb,&magfact);
1172 if (printval == 0.0)
1173 magfact = -1.0;
1174 } else {
1175 printval /= magfact;
1176 }
1177 *(++percent_s) = 's';
1178 } else if (strstr(im->gdes[i].format,"%s") != NULL) {
1179 auto_scale(im,&printval,&si_symb,&magfact);
1180 }
1182 if (im->gdes[i].gf == GF_PRINT){
1183 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1184 if (bad_format(im->gdes[i].format)) {
1185 rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1186 return -1;
1187 }
1188 #ifdef HAVE_SNPRINTF
1189 snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
1190 #else
1191 sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
1192 #endif
1193 (*prdata)[prlines-1] = NULL;
1194 } else {
1195 /* GF_GPRINT */
1197 if (bad_format(im->gdes[i].format)) {
1198 rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1199 return -1;
1200 }
1201 #ifdef HAVE_SNPRINTF
1202 snprintf(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
1203 #else
1204 sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
1205 #endif
1206 graphelement = 1;
1207 }
1208 }
1209 break;
1210 case GF_COMMENT:
1211 case GF_LINE:
1212 case GF_AREA:
1213 case GF_TICK:
1214 case GF_PART:
1215 case GF_STACK:
1216 case GF_HRULE:
1217 case GF_VRULE:
1218 graphelement = 1;
1219 break;
1220 case GF_DEF:
1221 case GF_CDEF:
1222 case GF_VDEF:
1223 break;
1224 }
1225 }
1226 return graphelement;
1227 }
1230 /* place legends with color spots */
1231 int
1232 leg_place(image_desc_t *im)
1233 {
1234 /* graph labels */
1235 int interleg = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1236 int box =im->text_prop[TEXT_PROP_LEGEND].size*1.5;
1237 int border = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1238 int fill=0, fill_last;
1239 int leg_c = 0;
1240 int leg_x = border, leg_y = im->ygif;
1241 int leg_cc;
1242 int glue = 0;
1243 int i,ii, mark = 0;
1244 char prt_fctn; /*special printfunctions */
1245 int *legspace;
1247 if( !(im->extra_flags & NOLEGEND) ) {
1248 if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1249 rrd_set_error("malloc for legspace");
1250 return -1;
1251 }
1253 for(i=0;i<im->gdes_c;i++){
1254 fill_last = fill;
1256 leg_cc = strlen(im->gdes[i].legend);
1258 /* is there a controle code ant the end of the legend string ? */
1259 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\') {
1260 prt_fctn = im->gdes[i].legend[leg_cc-1];
1261 leg_cc -= 2;
1262 im->gdes[i].legend[leg_cc] = '\0';
1263 } else {
1264 prt_fctn = '\0';
1265 }
1266 /* remove exess space */
1267 while (prt_fctn=='g' &&
1268 leg_cc > 0 &&
1269 im->gdes[i].legend[leg_cc-1]==' '){
1270 leg_cc--;
1271 im->gdes[i].legend[leg_cc]='\0';
1272 }
1273 if (leg_cc != 0 ){
1274 legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1276 if (fill > 0){
1277 /* no interleg space if string ends in \g */
1278 fill += legspace[i];
1279 }
1280 if (im->gdes[i].gf != GF_GPRINT &&
1281 im->gdes[i].gf != GF_COMMENT) {
1282 fill += box;
1283 }
1284 fill += gfx_get_text_width(fill+border,im->text_prop[TEXT_PROP_LEGEND].font,
1285 im->text_prop[TEXT_PROP_LEGEND].size,
1286 im->tabwidth,
1287 im->gdes[i].legend);
1288 leg_c++;
1289 } else {
1290 legspace[i]=0;
1291 }
1292 /* who said there was a special tag ... ?*/
1293 if (prt_fctn=='g') {
1294 prt_fctn = '\0';
1295 }
1296 if (prt_fctn == '\0') {
1297 if (i == im->gdes_c -1 ) prt_fctn ='l';
1299 /* is it time to place the legends ? */
1300 if (fill > im->xgif - 2*border){
1301 if (leg_c > 1) {
1302 /* go back one */
1303 i--;
1304 fill = fill_last;
1305 leg_c--;
1306 prt_fctn = 'j';
1307 } else {
1308 prt_fctn = 'l';
1309 }
1311 }
1312 }
1315 if (prt_fctn != '\0'){
1316 leg_x = border;
1317 if (leg_c >= 2 && prt_fctn == 'j') {
1318 glue = (im->xgif - fill - 2* border) / (leg_c-1);
1319 } else {
1320 glue = 0;
1321 }
1322 if (prt_fctn =='c') leg_x = (im->xgif - fill) / 2.0;
1323 if (prt_fctn =='r') leg_x = im->xgif - fill - border;
1325 for(ii=mark;ii<=i;ii++){
1326 if(im->gdes[ii].legend[0]=='\0')
1327 continue;
1328 im->gdes[ii].leg_x = leg_x;
1329 im->gdes[ii].leg_y = leg_y;
1330 leg_x +=
1331 gfx_get_text_width(leg_x,im->text_prop[TEXT_PROP_LEGEND].font,
1332 im->text_prop[TEXT_PROP_LEGEND].size,
1333 im->tabwidth,
1334 im->gdes[ii].legend)
1335 + legspace[ii]
1336 + glue;
1337 if (im->gdes[ii].gf != GF_GPRINT &&
1338 im->gdes[ii].gf != GF_COMMENT)
1339 leg_x += box;
1340 }
1341 leg_y = leg_y + im->text_prop[TEXT_PROP_LEGEND].size*1.2;
1342 if (prt_fctn == 's') leg_y -= im->text_prop[TEXT_PROP_LEGEND].size*1.2;
1343 fill = 0;
1344 leg_c = 0;
1345 mark = ii;
1346 }
1347 }
1348 im->ygif = leg_y+6;
1349 free(legspace);
1350 }
1351 return 0;
1352 }
1354 /* create a grid on the graph. it determines what to do
1355 from the values of xsize, start and end */
1357 /* the xaxis labels are determined from the number of seconds per pixel
1358 in the requested graph */
1362 int
1363 horizontal_grid(gfx_canvas_t *canvas, image_desc_t *im)
1364 {
1365 double range;
1366 double scaledrange;
1367 int pixel,i;
1368 int sgrid,egrid;
1369 double gridstep;
1370 double scaledstep;
1371 char graph_label[100];
1372 double x0,x1,y0,y1;
1373 int labfact,gridind;
1374 int decimals, fractionals;
1375 char labfmt[64];
1377 labfact=2;
1378 gridind=-1;
1379 range = im->maxval - im->minval;
1380 scaledrange = range / im->magfact;
1382 /* does the scale of this graph make it impossible to put lines
1383 on it? If so, give up. */
1384 if (isnan(scaledrange)) {
1385 return 0;
1386 }
1388 /* find grid spaceing */
1389 pixel=1;
1390 if(isnan(im->ygridstep)){
1391 if(im->extra_flags & ALTYGRID) {
1392 /* find the value with max number of digits. Get number of digits */
1393 decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))));
1394 if(decimals <= 0) /* everything is small. make place for zero */
1395 decimals = 1;
1397 fractionals = floor(log10(range));
1398 if(fractionals < 0) /* small amplitude. */
1399 sprintf(labfmt, "%%%d.%df", decimals - fractionals + 1, -fractionals + 1);
1400 else
1401 sprintf(labfmt, "%%%d.1f", decimals + 1);
1402 gridstep = pow((double)10, (double)fractionals);
1403 if(gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1404 gridstep = 0.1;
1405 /* should have at least 5 lines but no more then 15 */
1406 if(range/gridstep < 5)
1407 gridstep /= 10;
1408 if(range/gridstep > 15)
1409 gridstep *= 10;
1410 if(range/gridstep > 5) {
1411 labfact = 1;
1412 if(range/gridstep > 8)
1413 labfact = 2;
1414 }
1415 else {
1416 gridstep /= 5;
1417 labfact = 5;
1418 }
1419 }
1420 else {
1421 for(i=0;ylab[i].grid > 0;i++){
1422 pixel = im->ysize / (scaledrange / ylab[i].grid);
1423 if (gridind == -1 && pixel > 5) {
1424 gridind = i;
1425 break;
1426 }
1427 }
1429 for(i=0; i<4;i++) {
1430 if (pixel * ylab[gridind].lfac[i] >= 2 * im->text_prop[TEXT_PROP_AXIS].size) {
1431 labfact = ylab[gridind].lfac[i];
1432 break;
1433 }
1434 }
1436 gridstep = ylab[gridind].grid * im->magfact;
1437 }
1438 } else {
1439 gridstep = im->ygridstep;
1440 labfact = im->ylabfact;
1441 }
1443 x0=im->xorigin;
1444 x1=im->xorigin+im->xsize;
1446 sgrid = (int)( im->minval / gridstep - 1);
1447 egrid = (int)( im->maxval / gridstep + 1);
1448 scaledstep = gridstep/im->magfact;
1449 for (i = sgrid; i <= egrid; i++){
1450 y0=ytr(im,gridstep*i);
1451 if ( y0 >= im->yorigin-im->ysize
1452 && y0 <= im->yorigin){
1453 if(i % labfact == 0){
1454 if (i==0 || im->symbol == ' ') {
1455 if(scaledstep < 1){
1456 if(im->extra_flags & ALTYGRID) {
1457 sprintf(graph_label,labfmt,scaledstep*i);
1458 }
1459 else {
1460 sprintf(graph_label,"%4.1f",scaledstep*i);
1461 }
1462 } else {
1463 sprintf(graph_label,"%4.0f",scaledstep*i);
1464 }
1465 }else {
1466 if(scaledstep < 1){
1467 sprintf(graph_label,"%4.1f %c",scaledstep*i, im->symbol);
1468 } else {
1469 sprintf(graph_label,"%4.0f %c",scaledstep*i, im->symbol);
1470 }
1471 }
1473 gfx_new_text ( canvas,
1474 x0-im->text_prop[TEXT_PROP_AXIS].size/1.5, y0,
1475 im->graph_col[GRC_FONT],
1476 im->text_prop[TEXT_PROP_AXIS].font,
1477 im->text_prop[TEXT_PROP_AXIS].size,
1478 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1479 graph_label );
1480 gfx_new_line ( canvas,
1481 x0-2,y0,
1482 x1+2,y0,
1483 MGRIDWIDTH, im->graph_col[GRC_MGRID] );
1485 } else {
1486 gfx_new_line ( canvas,
1487 x0-1,y0,
1488 x1+1,y0,
1489 GRIDWIDTH, im->graph_col[GRC_GRID] );
1491 }
1492 }
1493 }
1494 return 1;
1495 }
1497 /* logaritmic horizontal grid */
1498 int
1499 horizontal_log_grid(gfx_canvas_t *canvas, image_desc_t *im)
1500 {
1501 double pixpex;
1502 int ii,i;
1503 int minoridx=0, majoridx=0;
1504 char graph_label[100];
1505 double x0,x1,y0,y1;
1506 double value, pixperstep, minstep;
1508 /* find grid spaceing */
1509 pixpex= (double)im->ysize / (log10(im->maxval) - log10(im->minval));
1511 if (isnan(pixpex)) {
1512 return 0;
1513 }
1515 for(i=0;yloglab[i][0] > 0;i++){
1516 minstep = log10(yloglab[i][0]);
1517 for(ii=1;yloglab[i][ii+1] > 0;ii++){
1518 if(yloglab[i][ii+2]==0){
1519 minstep = log10(yloglab[i][ii+1])-log10(yloglab[i][ii]);
1520 break;
1521 }
1522 }
1523 pixperstep = pixpex * minstep;
1524 if(pixperstep > 5){minoridx = i;}
1525 if(pixperstep > 2 * im->text_prop[TEXT_PROP_LEGEND].size){majoridx = i;}
1526 }
1528 x0=im->xorigin;
1529 x1=im->xorigin+im->xsize;
1530 /* paint minor grid */
1531 for (value = pow((double)10, log10(im->minval)
1532 - fmod(log10(im->minval),log10(yloglab[minoridx][0])));
1533 value <= im->maxval;
1534 value *= yloglab[minoridx][0]){
1535 if (value < im->minval) continue;
1536 i=0;
1537 while(yloglab[minoridx][++i] > 0){
1538 y0 = ytr(im,value * yloglab[minoridx][i]);
1539 if (y0 <= im->yorigin - im->ysize) break;
1540 gfx_new_line ( canvas,
1541 x0-1,y0,
1542 x1+1,y0,
1543 GRIDWIDTH, im->graph_col[GRC_GRID] );
1544 }
1545 }
1547 /* paint major grid and labels*/
1548 for (value = pow((double)10, log10(im->minval)
1549 - fmod(log10(im->minval),log10(yloglab[majoridx][0])));
1550 value <= im->maxval;
1551 value *= yloglab[majoridx][0]){
1552 if (value < im->minval) continue;
1553 i=0;
1554 while(yloglab[majoridx][++i] > 0){
1555 y0 = ytr(im,value * yloglab[majoridx][i]);
1556 if (y0 <= im->yorigin - im->ysize) break;
1557 gfx_new_line ( canvas,
1558 x0-2,y0,
1559 x1+2,y0,
1560 MGRIDWIDTH, im->graph_col[GRC_MGRID] );
1562 sprintf(graph_label,"%3.0e",value * yloglab[majoridx][i]);
1563 gfx_new_text ( canvas,
1564 x0-im->text_prop[TEXT_PROP_AXIS].size/1.5, y0,
1565 im->graph_col[GRC_FONT],
1566 im->text_prop[TEXT_PROP_AXIS].font,
1567 im->text_prop[TEXT_PROP_AXIS].size,
1568 im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1569 graph_label );
1570 }
1571 }
1572 return 1;
1573 }
1576 void
1577 vertical_grid(
1578 gfx_canvas_t *canvas,
1579 image_desc_t *im )
1580 {
1581 int xlab_sel; /* which sort of label and grid ? */
1582 time_t ti, tilab;
1583 long factor;
1584 char graph_label[100];
1585 double x0,y0,y1; /* points for filled graph and more*/
1588 /* the type of time grid is determined by finding
1589 the number of seconds per pixel in the graph */
1592 if(im->xlab_user.minsec == -1){
1593 factor=(im->end - im->start)/im->xsize;
1594 xlab_sel=0;
1595 while ( xlab[xlab_sel+1].minsec != -1
1596 && xlab[xlab_sel+1].minsec <= factor){ xlab_sel++; }
1597 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
1598 im->xlab_user.gridst = xlab[xlab_sel].gridst;
1599 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
1600 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
1601 im->xlab_user.labtm = xlab[xlab_sel].labtm;
1602 im->xlab_user.labst = xlab[xlab_sel].labst;
1603 im->xlab_user.precis = xlab[xlab_sel].precis;
1604 im->xlab_user.stst = xlab[xlab_sel].stst;
1605 }
1607 /* y coords are the same for every line ... */
1608 y0 = im->yorigin;
1609 y1 = im->yorigin-im->ysize;
1612 /* paint the minor grid */
1613 for(ti = find_first_time(im->start,
1614 im->xlab_user.gridtm,
1615 im->xlab_user.gridst);
1616 ti < im->end;
1617 ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
1618 ){
1619 /* are we inside the graph ? */
1620 if (ti < im->start || ti > im->end) continue;
1621 x0 = xtr(im,ti);
1622 gfx_new_line(canvas,x0,y0+1, x0,y1-1,GRIDWIDTH, im->graph_col[GRC_GRID]);
1624 }
1626 /* paint the major grid */
1627 for(ti = find_first_time(im->start,
1628 im->xlab_user.mgridtm,
1629 im->xlab_user.mgridst);
1630 ti < im->end;
1631 ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
1632 ){
1633 /* are we inside the graph ? */
1634 if (ti < im->start || ti > im->end) continue;
1635 x0 = xtr(im,ti);
1636 gfx_new_line(canvas,x0,y0+2, x0,y1-2,MGRIDWIDTH, im->graph_col[GRC_MGRID]);
1638 }
1639 /* paint the labels below the graph */
1640 for(ti = find_first_time(im->start,
1641 im->xlab_user.labtm,
1642 im->xlab_user.labst);
1643 ti <= im->end;
1644 ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
1645 ){
1646 tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
1648 #if HAVE_STRFTIME
1649 strftime(graph_label,99,im->xlab_user.stst,localtime(&tilab));
1650 #else
1651 # error "your libc has no strftime I guess we'll abort the exercise here."
1652 #endif
1653 gfx_new_text ( canvas,
1654 xtr(im,tilab), y0+im->text_prop[TEXT_PROP_AXIS].size/1.5,
1655 im->graph_col[GRC_FONT],
1656 im->text_prop[TEXT_PROP_AXIS].font,
1657 im->text_prop[TEXT_PROP_AXIS].size,
1658 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP,
1659 graph_label );
1661 }
1663 }
1666 void
1667 axis_paint(
1668 image_desc_t *im,
1669 gfx_canvas_t *canvas
1670 )
1671 {
1672 /* draw x and y axis */
1673 gfx_new_line ( canvas, im->xorigin+im->xsize,im->yorigin,
1674 im->xorigin+im->xsize,im->yorigin-im->ysize,
1675 GRIDWIDTH, im->graph_col[GRC_GRID]);
1677 gfx_new_line ( canvas, im->xorigin,im->yorigin-im->ysize,
1678 im->xorigin+im->xsize,im->yorigin-im->ysize,
1679 GRIDWIDTH, im->graph_col[GRC_GRID]);
1681 gfx_new_line ( canvas, im->xorigin-4,im->yorigin,
1682 im->xorigin+im->xsize+4,im->yorigin,
1683 MGRIDWIDTH, im->graph_col[GRC_GRID]);
1685 gfx_new_line ( canvas, im->xorigin,im->yorigin+4,
1686 im->xorigin,im->yorigin-im->ysize-4,
1687 MGRIDWIDTH, im->graph_col[GRC_GRID]);
1690 /* arrow for X axis direction */
1691 gfx_new_area ( canvas,
1692 im->xorigin+im->xsize+4, im->yorigin-3,
1693 im->xorigin+im->xsize+4, im->yorigin+3,
1694 im->xorigin+im->xsize+9, im->yorigin,
1695 im->graph_col[GRC_ARROW]);
1699 }
1701 void
1702 grid_paint(
1703 image_desc_t *im,
1704 gfx_canvas_t *canvas
1706 )
1707 {
1708 long i;
1709 int boxH=8, boxV=8;
1710 int res=0;
1711 double x0,x1,x2,x3,y0,y1,y2,y3; /* points for filled graph and more*/
1712 gfx_node_t *node;
1715 /* draw 3d border */
1716 node = gfx_new_area (canvas, 0,im->ygif,
1717 2,im->ygif-2,
1718 2,2,im->graph_col[GRC_SHADEA]);
1719 gfx_add_point( node , im->xgif - 2, 2 );
1720 gfx_add_point( node , im->xgif, 0 );
1721 gfx_add_point( node , 0,0 );
1722 /* gfx_add_point( node , 0,im->ygif ); */
1724 node = gfx_new_area (canvas, 2,im->ygif-2,
1725 im->xgif-2,im->ygif-2,
1726 im->xgif - 2, 2,
1727 im->graph_col[GRC_SHADEB]);
1728 gfx_add_point( node , im->xgif,0);
1729 gfx_add_point( node , im->xgif,im->ygif);
1730 gfx_add_point( node , 0,im->ygif);
1731 /* gfx_add_point( node , 0,im->ygif ); */
1734 if (im->draw_x_grid == 1 )
1735 vertical_grid(canvas, im);
1737 if (im->draw_y_grid == 1){
1738 if(im->logarithmic){
1739 res = horizontal_log_grid(canvas,im);
1740 } else {
1741 res = horizontal_grid(canvas,im);
1742 }
1744 /* dont draw horizontal grid if there is no min and max val */
1745 if (! res ) {
1746 char *nodata = "No Data found";
1747 gfx_new_text(canvas,im->xgif/2, (2*im->yorigin-im->ysize) / 2,
1748 im->graph_col[GRC_FONT],
1749 im->text_prop[TEXT_PROP_AXIS].font,
1750 im->text_prop[TEXT_PROP_AXIS].size,
1751 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
1752 nodata );
1753 }
1754 }
1756 /* yaxis description */
1757 gfx_new_text( canvas,
1758 7, (im->yorigin - im->ysize/2),
1759 im->graph_col[GRC_FONT],
1760 im->text_prop[TEXT_PROP_AXIS].font,
1761 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 270.0,
1762 GFX_H_CENTER, GFX_V_CENTER,
1763 im->ylegend);
1765 /* graph title */
1766 gfx_new_text( canvas,
1767 im->xgif/2, im->text_prop[TEXT_PROP_TITLE].size*1.5,
1768 im->graph_col[GRC_FONT],
1769 im->text_prop[TEXT_PROP_TITLE].font,
1770 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
1771 GFX_H_CENTER, GFX_V_CENTER,
1772 im->title);
1774 /* graph labels */
1775 if( !(im->extra_flags & NOLEGEND) ) {
1776 for(i=0;i<im->gdes_c;i++){
1777 if(im->gdes[i].legend[0] =='\0')
1778 continue;
1780 if(im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT){
1781 x0 = im->gdes[i].leg_x;
1782 y0 = im->gdes[i].leg_y+1.0;
1783 x1 = x0;
1784 x2 = x0+boxH;
1785 x3 = x0+boxH;
1786 y1 = y0+boxV;
1787 y2 = y0+boxV;
1788 y3 = y0;
1789 node = gfx_new_area(canvas, x0,y0,x1,y1,x2,y2 ,im->gdes[i].col);
1790 gfx_add_point ( node, x3, y3 );
1791 /* gfx_add_point ( node, x0, y0 ); */
1792 node = gfx_new_line(canvas, x0,y0,x1,y1 ,GRIDWIDTH, im->graph_col[GRC_FRAME]);
1793 gfx_add_point ( node, x2, y2 );
1794 gfx_add_point ( node, x3, y3 );
1795 gfx_add_point ( node, x0, y0 );
1797 gfx_new_text ( canvas, x0+boxH+6, (y0+y2) / 2.0,
1798 im->graph_col[GRC_FONT],
1799 im->text_prop[TEXT_PROP_AXIS].font,
1800 im->text_prop[TEXT_PROP_AXIS].size,
1801 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
1802 im->gdes[i].legend );
1804 } else {
1805 x0 = im->gdes[i].leg_x;
1806 y0 = im->gdes[i].leg_y;
1808 gfx_new_text ( canvas, x0, (y0+y2) / 2.0,
1809 im->graph_col[GRC_FONT],
1810 im->text_prop[TEXT_PROP_AXIS].font,
1811 im->text_prop[TEXT_PROP_AXIS].size,
1812 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
1813 im->gdes[i].legend );
1815 }
1816 }
1817 }
1818 }
1821 /*****************************************************
1822 * lazy check make sure we rely need to create this graph
1823 *****************************************************/
1825 int lazy_check(image_desc_t *im){
1826 FILE *fd = NULL;
1827 int size = 1;
1828 struct stat gifstat;
1830 if (im->lazy == 0) return 0; /* no lazy option */
1831 if (stat(im->graphfile,&gifstat) != 0)
1832 return 0; /* can't stat */
1833 /* one pixel in the existing graph is more then what we would
1834 change here ... */
1835 if (time(NULL) - gifstat.st_mtime >
1836 (im->end - im->start) / im->xsize)
1837 return 0;
1838 if ((fd = fopen(im->graphfile,"rb")) == NULL)
1839 return 0; /* the file does not exist */
1840 switch (im->imgformat) {
1841 case IF_GIF:
1842 size = GifSize(fd,&(im->xgif),&(im->ygif));
1843 break;
1844 case IF_PNG:
1845 size = PngSize(fd,&(im->xgif),&(im->ygif));
1846 break;
1847 }
1848 fclose(fd);
1849 return size;
1850 }
1852 void
1853 pie_part(gfx_canvas_t *canvas, gfx_color_t color,
1854 double PieCenterX, double PieCenterY, double Radius,
1855 double startangle, double endangle)
1856 {
1857 gfx_node_t *node;
1858 double angle;
1859 double step=M_PI/50; /* Number of iterations for the circle;
1860 ** 10 is definitely too low, more than
1861 ** 50 seems to be overkill
1862 */
1864 /* Strange but true: we have to work clockwise or else
1865 ** anti aliasing nor transparency don't work.
1866 **
1867 ** This test is here to make sure we do it right, also
1868 ** this makes the for...next loop more easy to implement.
1869 ** The return will occur if the user enters a negative number
1870 ** (which shouldn't be done according to the specs) or if the
1871 ** programmers do something wrong (which, as we all know, never
1872 ** happens anyway :)
1873 */
1874 if (endangle<startangle) return;
1876 /* Hidden feature: Radius decreases each full circle */
1877 angle=startangle;
1878 while (angle>=2*M_PI) {
1879 angle -= 2*M_PI;
1880 Radius *= 0.8;
1881 }
1883 node=gfx_new_area(canvas,
1884 PieCenterX+sin(startangle)*Radius,
1885 PieCenterY-cos(startangle)*Radius,
1886 PieCenterX,
1887 PieCenterY,
1888 PieCenterX+sin(endangle)*Radius,
1889 PieCenterY-cos(endangle)*Radius,
1890 color);
1891 for (angle=endangle;angle-startangle>=step;angle-=step) {
1892 gfx_add_point(node,
1893 PieCenterX+sin(angle)*Radius,
1894 PieCenterY-cos(angle)*Radius );
1895 }
1896 }
1898 /* draw that picture thing ... */
1899 int
1900 graph_paint(image_desc_t *im, char ***calcpr)
1901 {
1902 int i,ii;
1903 int lazy = lazy_check(im);
1904 int piechart = 0;
1905 double PieStart=0.0, PieSize=0.0, PieCenterX=0.0, PieCenterY=0.0;
1906 FILE *fo;
1907 gfx_canvas_t *canvas;
1908 gfx_node_t *node;
1910 double areazero = 0.0;
1911 enum gf_en stack_gf = GF_PRINT;
1912 graph_desc_t *lastgdes = NULL;
1914 /* if we are lazy and there is nothing to PRINT ... quit now */
1915 if (lazy && im->prt_c==0) return 0;
1917 /* pull the data from the rrd files ... */
1919 if(data_fetch(im)==-1)
1920 return -1;
1922 /* evaluate VDEF and CDEF operations ... */
1923 if(data_calc(im)==-1)
1924 return -1;
1926 /* calculate and PRINT and GPRINT definitions. We have to do it at
1927 * this point because it will affect the length of the legends
1928 * if there are no graph elements we stop here ...
1929 * if we are lazy, try to quit ...
1930 */
1931 i=print_calc(im,calcpr);
1932 if(i<0) return -1;
1933 if(i==0 || lazy) return 0;
1935 /* get actual drawing data and find min and max values*/
1936 if(data_proc(im)==-1)
1937 return -1;
1939 if(!im->logarithmic){si_unit(im);} /* identify si magnitude Kilo, Mega Giga ? */
1941 if(!im->rigid && ! im->logarithmic)
1942 expand_range(im); /* make sure the upper and lower limit are
1943 sensible values */
1945 /* init xtr and ytr */
1946 /* determine the actual size of the gif to draw. The size given
1947 on the cmdline is the graph area. But we need more as we have
1948 draw labels and other things outside the graph area */
1951 im->xorigin = 10 + 9 * im->text_prop[TEXT_PROP_LEGEND].size;
1953 xtr(im,0);
1955 im->yorigin = 10 + im->ysize;
1957 ytr(im,DNAN);
1959 if(im->title[0] != '\0')
1960 im->yorigin += im->text_prop[TEXT_PROP_TITLE].size*3+4;
1962 im->xgif= 20 +im->xsize + im->xorigin;
1963 im->ygif= im->yorigin+2* im->text_prop[TEXT_PROP_LEGEND].size;
1965 /* check if we need to draw a piechart */
1966 for(i=0;i<im->gdes_c;i++){
1967 if (im->gdes[i].gf == GF_PART) {
1968 piechart=1;
1969 break;
1970 }
1971 }
1973 if (piechart) {
1974 /* allocate enough space for the piechart itself (PieSize), 20%
1975 ** more for the background and an additional 50 pixels spacing.
1976 */
1977 if (im->xsize < im->ysize)
1978 PieSize = im->xsize;
1979 else
1980 PieSize = im->ysize;
1981 im->xgif += PieSize*1.2 + 50;
1983 PieCenterX = im->xorigin + im->xsize + 50 + PieSize*0.6;
1984 PieCenterY = im->yorigin - PieSize*0.5;
1985 }
1987 /* determine where to place the legends onto the graphics.
1988 and set im->ygif to match space requirements for text */
1989 if(leg_place(im)==-1)
1990 return -1;
1992 canvas=gfx_new_canvas();
1995 /* the actual graph is created by going through the individual
1996 graph elements and then drawing them */
1998 node=gfx_new_area ( canvas,
1999 0, 0,
2000 im->xgif, 0,
2001 im->xgif, im->ygif,
2002 im->graph_col[GRC_BACK]);
2004 gfx_add_point(node,0, im->ygif);
2006 node=gfx_new_area ( canvas,
2007 im->xorigin, im->yorigin,
2008 im->xorigin + im->xsize, im->yorigin,
2009 im->xorigin + im->xsize, im->yorigin-im->ysize,
2010 im->graph_col[GRC_CANVAS]);
2012 gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
2014 #if 0
2015 /******************************************************************
2016 ** Just to play around. If you see this, I forgot to remove it **
2017 ******************************************************************/
2018 im->ygif+=100;
2019 node=gfx_new_area(canvas,
2020 0, im->ygif-100,
2021 im->xgif, im->ygif-100,
2022 im->xgif, im->ygif,
2023 im->graph_col[GRC_CANVAS]);
2024 gfx_add_point(node,0,im->ygif);
2026 /* Four areas:
2027 ** top left: current way, solid color
2028 ** top right: proper way, solid color
2029 ** bottom left: current way, alpha=0x80, partially overlapping
2030 ** bottom right: proper way, alpha=0x80, partially overlapping
2031 */
2032 {
2033 double x,y,x1,y1,x2,y2,x3,y3,x4,y4;
2035 x=(im->xgif-40)/6;
2036 y= (100-40)/6;
2037 x1= 20; y1=im->ygif-100+20;
2038 x2=3*x+20; y2=im->ygif-100+20;
2039 x3= x+20; y3=im->ygif-100+20+2*y;
2040 x4=4*x+20; y4=im->ygif-100+20+2*y;
2042 node=gfx_new_area(canvas,
2043 x1,y1,
2044 x1+3*x,y1,
2045 x1+3*x,y1+3*y,
2046 0xFF0000FF);
2047 gfx_add_point(node,x1,y1+3*y);
2048 node=gfx_new_area(canvas,
2049 x2,y2,
2050 x2,y2+3*y,
2051 x2+3*x,y2+3*y,
2052 0xFFFF00FF);
2053 gfx_add_point(node,x2+3*x,y2);
2054 node=gfx_new_area(canvas,
2055 x3,y3,
2056 x3+2*x,y3,
2057 x3+2*x,y3+3*y,
2058 0x00FF007F);
2059 gfx_add_point(node,x3,y3+3*y);
2060 node=gfx_new_area(canvas,
2061 x4,y4,
2062 x4,y4+3*y,
2063 x4+2*x,y4+3*y,
2064 0x0000FF7F);
2065 gfx_add_point(node,x4+2*x,y4);
2066 }
2068 #endif
2070 if (piechart) {
2071 pie_part(canvas,im->graph_col[GRC_CANVAS],PieCenterX,PieCenterY,PieSize*0.6,0,2*M_PI);
2072 }
2074 if (im->minval > 0.0)
2075 areazero = im->minval;
2076 if (im->maxval < 0.0)
2077 areazero = im->maxval;
2079 axis_paint(im,canvas);
2082 for(i=0;i<im->gdes_c;i++){
2083 switch(im->gdes[i].gf){
2084 case GF_CDEF:
2085 case GF_VDEF:
2086 case GF_DEF:
2087 case GF_PRINT:
2088 case GF_GPRINT:
2089 case GF_COMMENT:
2090 case GF_HRULE:
2091 case GF_VRULE:
2092 break;
2093 case GF_TICK:
2094 for (ii = 0; ii < im->xsize; ii++)
2095 {
2096 if (!isnan(im->gdes[i].p_data[ii]) &&
2097 im->gdes[i].p_data[ii] > 0.0)
2098 {
2099 /* generate a tick */
2100 gfx_new_line(canvas, im -> xorigin + ii,
2101 im -> yorigin - (im -> gdes[i].yrule * im -> ysize),
2102 im -> xorigin + ii,
2103 im -> yorigin,
2104 1.0,
2105 im -> gdes[i].col );
2106 }
2107 }
2108 break;
2109 case GF_LINE:
2110 case GF_AREA:
2111 stack_gf = im->gdes[i].gf;
2112 case GF_STACK:
2113 /* fix data points at oo and -oo */
2114 for(ii=0;ii<im->xsize;ii++){
2115 if (isinf(im->gdes[i].p_data[ii])){
2116 if (im->gdes[i].p_data[ii] > 0) {
2117 im->gdes[i].p_data[ii] = im->maxval ;
2118 } else {
2119 im->gdes[i].p_data[ii] = im->minval ;
2120 }
2122 }
2123 } /* for */
2125 if (im->gdes[i].col != 0x0){
2126 /* GF_LINE and friend */
2127 if(stack_gf == GF_LINE ){
2128 node = NULL;
2129 for(ii=1;ii<im->xsize;ii++){
2130 if ( ! isnan(im->gdes[i].p_data[ii-1])
2131 && ! isnan(im->gdes[i].p_data[ii])){
2132 if (node == NULL){
2133 node = gfx_new_line(canvas,
2134 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2135 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2136 im->gdes[i].linewidth,
2137 im->gdes[i].col);
2138 } else {
2139 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2140 }
2141 } else {
2142 node = NULL;
2143 }
2144 }
2145 } else {
2146 int area_start=-1;
2147 node = NULL;
2148 for(ii=1;ii<im->xsize;ii++){
2149 /* open an area */
2150 if ( ! isnan(im->gdes[i].p_data[ii-1])
2151 && ! isnan(im->gdes[i].p_data[ii])){
2152 if (node == NULL){
2153 float ybase = 0.0;
2154 if (im->gdes[i].gf == GF_STACK) {
2155 ybase = ytr(im,lastgdes->p_data[ii-1]);
2156 } else {
2157 ybase = ytr(im,areazero);
2158 }
2159 area_start = ii-1;
2160 node = gfx_new_area(canvas,
2161 ii-1+im->xorigin,ybase,
2162 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2163 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2164 im->gdes[i].col
2165 );
2166 } else {
2167 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2168 }
2169 }
2171 if ( node != NULL && (ii+1==im->xsize || isnan(im->gdes[i].p_data[ii]) )){
2172 /* GF_AREA STACK type*/
2173 if (im->gdes[i].gf == GF_STACK ) {
2174 int iii;
2175 for (iii=ii-1;iii>area_start;iii--){
2176 gfx_add_point(node,iii+im->xorigin,ytr(im,lastgdes->p_data[iii]));
2177 }
2178 } else {
2179 gfx_add_point(node,ii+im->xorigin,ytr(im,areazero));
2180 };
2181 node=NULL;
2182 };
2183 }
2184 } /* else GF_LINE */
2185 } /* if color != 0x0 */
2186 /* make sure we do not run into trouble when stacking on NaN */
2187 for(ii=0;ii<im->xsize;ii++){
2188 if (isnan(im->gdes[i].p_data[ii])) {
2189 double ybase = 0.0;
2190 if (lastgdes) {
2191 ybase = ytr(im,lastgdes->p_data[ii-1]);
2192 };
2193 if (isnan(ybase) || !lastgdes ){
2194 ybase = ytr(im,areazero);
2195 }
2196 im->gdes[i].p_data[ii] = ybase;
2197 }
2198 }
2199 lastgdes = &(im->gdes[i]);
2200 break;
2201 case GF_PART:
2202 if(isnan(im->gdes[i].yrule)) /* fetch variable */
2203 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2205 if (finite(im->gdes[i].yrule)) { /* even the fetched var can be NaN */
2206 pie_part(canvas,im->gdes[i].col,
2207 PieCenterX,PieCenterY,PieSize/2,
2208 M_PI*2.0*PieStart/100.0,
2209 M_PI*2.0*(PieStart+im->gdes[i].yrule)/100.0);
2210 PieStart += im->gdes[i].yrule;
2211 }
2212 break;
2213 } /* switch */
2214 }
2215 grid_paint(im,canvas);
2217 /* the RULES are the last thing to paint ... */
2218 for(i=0;i<im->gdes_c;i++){
2220 switch(im->gdes[i].gf){
2221 case GF_HRULE:
2222 if(isnan(im->gdes[i].yrule)) { /* fetch variable */
2223 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2224 };
2225 if(im->gdes[i].yrule >= im->minval
2226 && im->gdes[i].yrule <= im->maxval)
2227 gfx_new_line(canvas,
2228 im->xorigin,ytr(im,im->gdes[i].yrule),
2229 im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2230 1.0,im->gdes[i].col);
2231 break;
2232 case GF_VRULE:
2233 if(im->gdes[i].xrule == 0) { /* fetch variable */
2234 im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
2235 };
2236 if(im->gdes[i].xrule >= im->start
2237 && im->gdes[i].xrule <= im->end)
2238 gfx_new_line(canvas,
2239 xtr(im,im->gdes[i].xrule),im->yorigin,
2240 xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2241 1.0,im->gdes[i].col);
2242 break;
2243 default:
2244 break;
2245 }
2246 }
2249 if (strcmp(im->graphfile,"-")==0) {
2250 #ifdef WIN32
2251 /* Change translation mode for stdout to BINARY */
2252 _setmode( _fileno( stdout ), O_BINARY );
2253 #endif
2254 fo = stdout;
2255 } else {
2256 if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2257 rrd_set_error("Opening '%s' for write: %s",im->graphfile,
2258 strerror(errno));
2259 return (-1);
2260 }
2261 }
2262 switch (im->imgformat) {
2263 case IF_GIF:
2264 break;
2265 case IF_PNG:
2266 gfx_render_png (canvas,im->xgif,im->ygif,im->zoom,0x0,fo);
2267 break;
2268 }
2269 if (strcmp(im->graphfile,"-") != 0)
2270 fclose(fo);
2272 gfx_destroy(canvas);
2273 return 0;
2274 }
2277 /*****************************************************
2278 * graph stuff
2279 *****************************************************/
2281 int
2282 gdes_alloc(image_desc_t *im){
2284 long def_step = (im->end-im->start)/im->xsize;
2286 if (im->step > def_step) /* step can be increassed ... no decreassed */
2287 def_step = im->step;
2289 im->gdes_c++;
2291 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2292 * sizeof(graph_desc_t)))==NULL){
2293 rrd_set_error("realloc graph_descs");
2294 return -1;
2295 }
2298 im->gdes[im->gdes_c-1].step=def_step;
2299 im->gdes[im->gdes_c-1].start=im->start;
2300 im->gdes[im->gdes_c-1].end=im->end;
2301 im->gdes[im->gdes_c-1].vname[0]='\0';
2302 im->gdes[im->gdes_c-1].data=NULL;
2303 im->gdes[im->gdes_c-1].ds_namv=NULL;
2304 im->gdes[im->gdes_c-1].data_first=0;
2305 im->gdes[im->gdes_c-1].p_data=NULL;
2306 im->gdes[im->gdes_c-1].rpnp=NULL;
2307 im->gdes[im->gdes_c-1].col = 0x0;
2308 im->gdes[im->gdes_c-1].legend[0]='\0';
2309 im->gdes[im->gdes_c-1].rrd[0]='\0';
2310 im->gdes[im->gdes_c-1].ds=-1;
2311 im->gdes[im->gdes_c-1].p_data=NULL;
2312 return 0;
2313 }
2315 /* copies input untill the first unescaped colon is found
2316 or until input ends. backslashes have to be escaped as well */
2317 int
2318 scan_for_col(char *input, int len, char *output)
2319 {
2320 int inp,outp=0;
2321 for (inp=0;
2322 inp < len &&
2323 input[inp] != ':' &&
2324 input[inp] != '\0';
2325 inp++){
2326 if (input[inp] == '\\' &&
2327 input[inp+1] != '\0' &&
2328 (input[inp+1] == '\\' ||
2329 input[inp+1] == ':')){
2330 output[outp++] = input[++inp];
2331 }
2332 else {
2333 output[outp++] = input[inp];
2334 }
2335 }
2336 output[outp] = '\0';
2337 return inp;
2338 }
2340 /* Some surgery done on this function, it became ridiculously big.
2341 ** Things moved:
2342 ** - initializing now in rrd_graph_init()
2343 ** - options parsing now in rrd_graph_options()
2344 ** - script parsing now in rrd_graph_script()
2345 */
2346 int
2347 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize)
2348 {
2349 image_desc_t im;
2351 rrd_graph_init(&im);
2353 rrd_graph_options(argc,argv,&im);
2354 if (rrd_test_error()) return -1;
2356 if (strlen(argv[optind])>=MAXPATH) {
2357 rrd_set_error("filename (including path) too long");
2358 return -1;
2359 }
2360 strncpy(im.graphfile,argv[optind],MAXPATH-1);
2361 im.graphfile[MAXPATH-1]='\0';
2363 rrd_graph_script(argc,argv,&im);
2364 if (rrd_test_error()) return -1;
2366 /* Everything is now read and the actual work can start */
2368 (*prdata)=NULL;
2369 if (graph_paint(&im,prdata)==-1){
2370 im_free(&im);
2371 return -1;
2372 }
2374 /* The image is generated and needs to be output.
2375 ** Also, if needed, print a line with information about the image.
2376 */
2378 *xsize=im.xgif;
2379 *ysize=im.ygif;
2380 if (im.imginfo) {
2381 char *filename;
2382 if (!(*prdata)) {
2383 /* maybe prdata is not allocated yet ... lets do it now */
2384 if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
2385 rrd_set_error("malloc imginfo");
2386 return -1;
2387 };
2388 }
2389 if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
2390 ==NULL){
2391 rrd_set_error("malloc imginfo");
2392 return -1;
2393 }
2394 filename=im.graphfile+strlen(im.graphfile);
2395 while(filename > im.graphfile) {
2396 if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
2397 filename--;
2398 }
2400 sprintf((*prdata)[0],im.imginfo,filename,(long)(im.zoom*im.xgif),(long)(im.zoom*im.ygif));
2401 }
2402 im_free(&im);
2403 return 0;
2404 }
2406 void
2407 rrd_graph_init(image_desc_t *im)
2408 {
2409 int i;
2411 im->xlab_user.minsec = -1;
2412 im->xgif=0;
2413 im->ygif=0;
2414 im->xsize = 400;
2415 im->ysize = 100;
2416 im->step = 0;
2417 im->ylegend[0] = '\0';
2418 im->title[0] = '\0';
2419 im->minval = DNAN;
2420 im->maxval = DNAN;
2421 im->interlaced = 0;
2422 im->unitsexponent= 9999;
2423 im->extra_flags= 0;
2424 im->rigid = 0;
2425 im->imginfo = NULL;
2426 im->lazy = 0;
2427 im->logarithmic = 0;
2428 im->ygridstep = DNAN;
2429 im->draw_x_grid = 1;
2430 im->draw_y_grid = 1;
2431 im->base = 1000;
2432 im->prt_c = 0;
2433 im->gdes_c = 0;
2434 im->gdes = NULL;
2435 im->zoom = 1.0;
2436 im->imgformat = IF_GIF; /* we default to GIF output */
2438 for(i=0;i<DIM(graph_col);i++)
2439 im->graph_col[i]=graph_col[i];
2441 for(i=0;i<DIM(text_prop);i++){
2442 im->text_prop[i].size = text_prop[i].size;
2443 im->text_prop[i].font = text_prop[i].font;
2444 }
2445 }
2447 void
2448 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
2449 {
2450 int stroff;
2451 char *parsetime_error = NULL;
2452 char scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
2453 time_t start_tmp=0,end_tmp=0;
2454 long long_tmp;
2455 struct time_value start_tv, end_tv;
2456 gfx_color_t color;
2458 parsetime("end-24h", &start_tv);
2459 parsetime("now", &end_tv);
2461 while (1){
2462 static struct option long_options[] =
2463 {
2464 {"start", required_argument, 0, 's'},
2465 {"end", required_argument, 0, 'e'},
2466 {"x-grid", required_argument, 0, 'x'},
2467 {"y-grid", required_argument, 0, 'y'},
2468 {"vertical-label",required_argument,0,'v'},
2469 {"width", required_argument, 0, 'w'},
2470 {"height", required_argument, 0, 'h'},
2471 {"interlaced", no_argument, 0, 'i'},
2472 {"upper-limit",required_argument, 0, 'u'},
2473 {"lower-limit",required_argument, 0, 'l'},
2474 {"rigid", no_argument, 0, 'r'},
2475 {"base", required_argument, 0, 'b'},
2476 {"logarithmic",no_argument, 0, 'o'},
2477 {"color", required_argument, 0, 'c'},
2478 {"font", required_argument, 0, 'n'},
2479 {"title", required_argument, 0, 't'},
2480 {"imginfo", required_argument, 0, 'f'},
2481 {"imgformat", required_argument, 0, 'a'},
2482 {"lazy", no_argument, 0, 'z'},
2483 {"zoom", required_argument, 0, 'm'},
2484 {"no-legend", no_argument, 0, 'g'},
2485 {"alt-y-grid", no_argument, 0, 257 },
2486 {"alt-autoscale", no_argument, 0, 258 },
2487 {"alt-autoscale-max", no_argument, 0, 259 },
2488 {"units-exponent",required_argument, 0, 260},
2489 {"step", required_argument, 0, 261},
2490 {0,0,0,0}};
2491 int option_index = 0;
2492 int opt;
2495 opt = getopt_long(argc, argv,
2496 "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:z:g",
2497 long_options, &option_index);
2499 if (opt == EOF)
2500 break;
2502 switch(opt) {
2503 case 257:
2504 im->extra_flags |= ALTYGRID;
2505 break;
2506 case 258:
2507 im->extra_flags |= ALTAUTOSCALE;
2508 break;
2509 case 259:
2510 im->extra_flags |= ALTAUTOSCALE_MAX;
2511 break;
2512 case 'g':
2513 im->extra_flags |= NOLEGEND;
2514 break;
2515 case 260:
2516 im->unitsexponent = atoi(optarg);
2517 break;
2518 case 261:
2519 im->step = atoi(optarg);
2520 break;
2521 case 's':
2522 if ((parsetime_error = parsetime(optarg, &start_tv))) {
2523 rrd_set_error( "start time: %s", parsetime_error );
2524 return;
2525 }
2526 break;
2527 case 'e':
2528 if ((parsetime_error = parsetime(optarg, &end_tv))) {
2529 rrd_set_error( "end time: %s", parsetime_error );
2530 return;
2531 }
2532 break;
2533 case 'x':
2534 if(strcmp(optarg,"none") == 0){
2535 im->draw_x_grid=0;
2536 break;
2537 };
2539 if(sscanf(optarg,
2540 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
2541 scan_gtm,
2542 &im->xlab_user.gridst,
2543 scan_mtm,
2544 &im->xlab_user.mgridst,
2545 scan_ltm,
2546 &im->xlab_user.labst,
2547 &im->xlab_user.precis,
2548 &stroff) == 7 && stroff != 0){
2549 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
2550 if((im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
2551 rrd_set_error("unknown keyword %s",scan_gtm);
2552 return;
2553 } else if ((im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
2554 rrd_set_error("unknown keyword %s",scan_mtm);
2555 return;
2556 } else if ((im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
2557 rrd_set_error("unknown keyword %s",scan_ltm);
2558 return;
2559 }
2560 im->xlab_user.minsec = 1;
2561 im->xlab_user.stst = im->xlab_form;
2562 } else {
2563 rrd_set_error("invalid x-grid format");
2564 return;
2565 }
2566 break;
2567 case 'y':
2569 if(strcmp(optarg,"none") == 0){
2570 im->draw_y_grid=0;
2571 break;
2572 };
2574 if(sscanf(optarg,
2575 "%lf:%d",
2576 &im->ygridstep,
2577 &im->ylabfact) == 2) {
2578 if(im->ygridstep<=0){
2579 rrd_set_error("grid step must be > 0");
2580 return;
2581 } else if (im->ylabfact < 1){
2582 rrd_set_error("label factor must be > 0");
2583 return;
2584 }
2585 } else {
2586 rrd_set_error("invalid y-grid format");
2587 return;
2588 }
2589 break;
2590 case 'v':
2591 strncpy(im->ylegend,optarg,150);
2592 im->ylegend[150]='\0';
2593 break;
2594 case 'u':
2595 im->maxval = atof(optarg);
2596 break;
2597 case 'l':
2598 im->minval = atof(optarg);
2599 break;
2600 case 'b':
2601 im->base = atol(optarg);
2602 if(im->base != 1024 && im->base != 1000 ){
2603 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
2604 return;
2605 }
2606 break;
2607 case 'w':
2608 long_tmp = atol(optarg);
2609 if (long_tmp < 10) {
2610 rrd_set_error("width below 10 pixels");
2611 return;
2612 }
2613 im->xsize = long_tmp;
2614 break;
2615 case 'h':
2616 long_tmp = atol(optarg);
2617 if (long_tmp < 10) {
2618 rrd_set_error("height below 10 pixels");
2619 return;
2620 }
2621 im->ysize = long_tmp;
2622 break;
2623 case 'i':
2624 im->interlaced = 1;
2625 break;
2626 case 'r':
2627 im->rigid = 1;
2628 break;
2629 case 'f':
2630 im->imginfo = optarg;
2631 break;
2632 case 'a':
2633 if((im->imgformat = if_conv(optarg)) == -1) {
2634 rrd_set_error("unsupported graphics format '%s'",optarg);
2635 return;
2636 }
2637 break;
2638 case 'z':
2639 im->lazy = 1;
2640 break;
2641 case 'o':
2642 im->logarithmic = 1;
2643 if (isnan(im->minval))
2644 im->minval=1;
2645 break;
2646 case 'c':
2647 if(sscanf(optarg,
2648 "%10[A-Z]#%8x",
2649 col_nam,&color) == 2){
2650 int ci;
2651 if((ci=grc_conv(col_nam)) != -1){
2652 im->graph_col[ci]=color;
2653 } else {
2654 rrd_set_error("invalid color name '%s'",col_nam);
2655 }
2656 } else {
2657 rrd_set_error("invalid color def format");
2658 return -1;
2659 }
2660 break;
2661 case 'n':{
2662 /* originally this used char *prop = "" and
2663 ** char *font = "dummy" however this results
2664 ** in a SEG fault, at least on RH7.1
2665 **
2666 ** The current implementation isn't proper
2667 ** either, font is never freed and prop uses
2668 ** a fixed width string
2669 */
2670 char prop[100];
2671 double size = 1;
2672 char *font;
2674 font=malloc(255);
2675 if(sscanf(optarg,
2676 "%10[A-Z]:%lf:%s",
2677 prop,&size,font) == 3){
2678 int sindex;
2679 if((sindex=text_prop_conv(prop)) != -1){
2680 im->text_prop[sindex].size=size;
2681 im->text_prop[sindex].font=font;
2682 if (sindex==0) { /* the default */
2683 im->text_prop[TEXT_PROP_TITLE].size=size;
2684 im->text_prop[TEXT_PROP_TITLE].font=font;
2685 im->text_prop[TEXT_PROP_AXIS].size=size;
2686 im->text_prop[TEXT_PROP_AXIS].font=font;
2687 im->text_prop[TEXT_PROP_UNIT].size=size;
2688 im->text_prop[TEXT_PROP_UNIT].font=font;
2689 im->text_prop[TEXT_PROP_LEGEND].size=size;
2690 im->text_prop[TEXT_PROP_LEGEND].font=font;
2691 }
2692 } else {
2693 rrd_set_error("invalid fonttag '%s'",prop);
2694 return;
2695 }
2696 } else {
2697 rrd_set_error("invalid text property format");
2698 return;
2699 }
2700 break;
2701 }
2702 case 'm':
2703 im->zoom= atof(optarg);
2704 if (im->zoom <= 0.0) {
2705 rrd_set_error("zoom factor must be > 0");
2706 return;
2707 }
2708 break;
2709 case 't':
2710 strncpy(im->title,optarg,150);
2711 im->title[150]='\0';
2712 break;
2714 case '?':
2715 if (optopt != 0)
2716 rrd_set_error("unknown option '%c'", optopt);
2717 else
2718 rrd_set_error("unknown option '%s'",argv[optind-1]);
2719 return;
2720 }
2721 }
2723 if (optind >= argc) {
2724 rrd_set_error("missing filename");
2725 return;
2726 }
2728 if (im->logarithmic == 1 && (im->minval <= 0 || isnan(im->minval))){
2729 rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");
2730 return;
2731 }
2733 if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
2734 /* error string is set in parsetime.c */
2735 return;
2736 }
2738 if (start_tmp < 3600*24*365*10){
2739 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
2740 return;
2741 }
2743 if (end_tmp < start_tmp) {
2744 rrd_set_error("start (%ld) should be less than end (%ld)",
2745 start_tmp, end_tmp);
2746 return;
2747 }
2749 im->start = start_tmp;
2750 im->end = end_tmp;
2751 }
2753 void
2754 rrd_graph_script(int argc, char *argv[], image_desc_t *im)
2755 {
2756 int i;
2757 char symname[100];
2758 int linepass = 0; /* stack must follow LINE*, AREA or STACK */
2760 for (i=optind+1;i<argc;i++) {
2761 int argstart=0;
2762 int strstart=0;
2763 graph_desc_t *gdp;
2764 char *line;
2765 char funcname[10],vname[MAX_VNAME_LEN+1],sep[1];
2766 double d;
2767 double linewidth;
2768 int j,k,l,m;
2770 /* Each command is one element from *argv[], we call this "line".
2771 **
2772 ** Each command defines the most current gdes inside struct im.
2773 ** In stead of typing "im->gdes[im->gdes_c-1]" we use "gdp".
2774 */
2775 gdes_alloc(im);
2776 gdp=&im->gdes[im->gdes_c-1];
2777 line=argv[i];
2779 /* function:newvname=string[:ds-name:CF] for xDEF
2780 ** function:vname[#color[:string]] for LINEx,AREA,STACK
2781 ** function:vname#color[:num[:string]] for TICK
2782 ** function:vname-or-num#color[:string] for xRULE,PART
2783 ** function:vname:CF:string for xPRINT
2784 ** function:string for COMMENT
2785 */
2786 argstart=0;
2788 sscanf(line, "%10[A-Z0-9]:%n", funcname,&argstart);
2789 if (argstart==0) {
2790 rrd_set_error("Cannot parse function in line: %s",line);
2791 im_free(im);
2792 return;
2793 }
2794 if(sscanf(funcname,"LINE%lf",&linewidth)){
2795 im->gdes[im->gdes_c-1].gf = GF_LINE;
2796 im->gdes[im->gdes_c-1].linewidth = linewidth;
2797 } else {
2798 if ((gdp->gf=gf_conv(funcname))==-1) {
2799 rrd_set_error("'%s' is not a valid function name",funcname);
2800 im_free(im);
2801 return;
2802 }
2803 }
2805 /* If the error string is set, we exit at the end of the switch */
2806 switch (gdp->gf) {
2807 case GF_COMMENT:
2808 if (rrd_graph_legend(gdp,&line[argstart])==0)
2809 rrd_set_error("Cannot parse comment in line: %s",line);
2810 break;
2811 case GF_PART:
2812 case GF_VRULE:
2813 case GF_HRULE:
2814 j=k=l=m=0;
2815 sscanf(&line[argstart], "%lf%n#%n", &d, &j, &k);
2816 sscanf(&line[argstart], DEF_NAM_FMT "%n#%n", vname, &l, &m);
2817 if (k+m==0) {
2818 rrd_set_error("Cannot parse name or num in line: %s",line);
2819 break;
2820 }
2821 if (j!=0) {
2822 gdp->xrule=d;
2823 gdp->yrule=d;
2824 argstart+=j;
2825 } else if (!rrd_graph_check_vname(im,vname,line)) {
2826 gdp->xrule=0;
2827 gdp->yrule=DNAN;
2828 argstart+=l;
2829 } else break; /* exit due to wrong vname */
2830 if ((j=rrd_graph_color(im,&line[argstart],line,0))==0) break;
2831 argstart+=j;
2832 if (strlen(&line[argstart])!=0) {
2833 if (rrd_graph_legend(gdp,&line[++argstart])==0)
2834 rrd_set_error("Cannot parse comment in line: %s",line);
2835 }
2836 break;
2837 case GF_STACK:
2838 if (linepass==0) {
2839 rrd_set_error("STACK must follow another graphing element");
2840 break;
2841 }
2842 case GF_LINE:
2843 case GF_AREA:
2844 case GF_TICK:
2845 j=k=0;
2846 linepass=1;
2847 sscanf(&line[argstart],DEF_NAM_FMT"%n%1[#:]%n",vname,&j,sep,&k);
2848 if (j+1!=k)
2849 rrd_set_error("Cannot parse vname in line: %s",line);
2850 else if (rrd_graph_check_vname(im,vname,line))
2851 rrd_set_error("Undefined vname '%s' in line: %s",line);
2852 else
2853 k=rrd_graph_color(im,&line[argstart],line,1);
2854 if (rrd_test_error()) break;
2855 argstart=argstart+j+k;
2856 if ((strlen(&line[argstart])!=0)&&(gdp->gf==GF_TICK)) {
2857 j=0;
2858 sscanf(&line[argstart], ":%lf%n", &gdp->yrule,&j);
2859 argstart+=j;
2860 }
2861 if (strlen(&line[argstart])!=0)
2862 if (rrd_graph_legend(gdp,&line[++argstart])==0)
2863 rrd_set_error("Cannot parse legend in line: %s",line);
2864 break;
2865 case GF_PRINT:
2866 im->prt_c++;
2867 case GF_GPRINT:
2868 j=0;
2869 sscanf(&line[argstart], DEF_NAM_FMT ":%n",gdp->vname,&j);
2870 if (j==0) {
2871 rrd_set_error("Cannot parse vname in line: '%s'",line);
2872 break;
2873 }
2874 argstart+=j;
2875 if (rrd_graph_check_vname(im,gdp->vname,line)) return;
2876 j=0;
2877 sscanf(&line[argstart], CF_NAM_FMT ":%n",symname,&j);
2879 k=(j!=0)?rrd_graph_check_CF(im,symname,line):1;
2880 #define VIDX im->gdes[gdp->vidx]
2881 switch (k) {
2882 case -1: /* looks CF but is not really CF */
2883 if (VIDX.gf == GF_VDEF) rrd_clear_error();
2884 break;
2885 case 0: /* CF present and correct */
2886 if (VIDX.gf == GF_VDEF)
2887 rrd_set_error("Don't use CF when printing VDEF");
2888 argstart+=j;
2889 break;
2890 case 1: /* CF not present */
2891 if (VIDX.gf == GF_VDEF) rrd_clear_error();
2892 else rrd_set_error("Printing DEF or CDEF needs CF");
2893 break;
2894 default:
2895 rrd_set_error("Oops, bug in GPRINT scanning");
2896 }
2897 #undef VIDX
2898 if (rrd_test_error()) break;
2900 if (strlen(&line[argstart])!=0) {
2901 if (rrd_graph_legend(gdp,&line[argstart])==0)
2902 rrd_set_error("Cannot parse legend in line: %s",line);
2903 } else rrd_set_error("No legend in (G)PRINT line: %s",line);
2904 strcpy(gdp->format, gdp->legend);
2905 break;
2906 case GF_DEF:
2907 case GF_VDEF:
2908 case GF_CDEF:
2909 j=0;
2910 sscanf(&line[argstart], DEF_NAM_FMT "=%n",gdp->vname,&j);
2911 if (j==0) {
2912 rrd_set_error("Could not parse line: %s",line);
2913 break;
2914 }
2915 if (find_var(im,gdp->vname)!=-1) {
2916 rrd_set_error("Variable '%s' in line '%s' already in use\n",
2917 gdp->vname,line);
2918 break;
2919 }
2920 argstart+=j;
2921 switch (gdp->gf) {
2922 case GF_DEF:
2923 argstart+=scan_for_col(&line[argstart],MAXPATH,gdp->rrd);
2924 j=k=0;
2925 sscanf(&line[argstart],
2926 ":" DS_NAM_FMT ":" CF_NAM_FMT "%n%*s%n",
2927 gdp->ds_nam, symname, &j, &k);
2928 if ((j==0)||(k!=0)) {
2929 rrd_set_error("Cannot parse DS or CF in '%s'",line);
2930 break;
2931 }
2932 rrd_graph_check_CF(im,symname,line);
2933 break;
2934 case GF_VDEF:
2935 j=0;
2936 sscanf(&line[argstart],DEF_NAM_FMT ",%n",vname,&j);
2937 if (j==0) {
2938 rrd_set_error("Cannot parse vname in line '%s'",line);
2939 break;
2940 }
2941 argstart+=j;
2942 if (rrd_graph_check_vname(im,vname,line)) return;
2943 if ( im->gdes[gdp->vidx].gf != GF_DEF
2944 && im->gdes[gdp->vidx].gf != GF_CDEF) {
2945 rrd_set_error("variable '%s' not DEF nor "
2946 "CDEF in VDEF '%s'", vname,gdp->vname);
2947 break;
2948 }
2949 vdef_parse(gdp,&line[argstart+strstart]);
2950 break;
2951 case GF_CDEF:
2952 if (strstr(&line[argstart],":")!=NULL) {
2953 rrd_set_error("Error in RPN, line: %s",line);
2954 break;
2955 }
2956 if ((gdp->rpnp = rpn_parse(
2957 (void *)im,
2958 &line[argstart],
2959 &find_var_wrapper)
2960 )==NULL)
2961 rrd_set_error("invalid rpn expression in: %s",line);
2962 break;
2963 default: break;
2964 }
2965 break;
2966 default: rrd_set_error("Big oops");
2967 }
2968 if (rrd_test_error()) {
2969 im_free(im);
2970 return;
2971 }
2972 }
2974 if (im->gdes_c==0){
2975 rrd_set_error("can't make a graph without contents");
2976 im_free(im); /* ??? is this set ??? */
2977 return;
2978 }
2979 }
2980 int
2981 rrd_graph_check_vname(image_desc_t *im, char *varname, char *err)
2982 {
2983 if ((im->gdes[im->gdes_c-1].vidx=find_var(im,varname))==-1) {
2984 rrd_set_error("Unknown variable '%s' in %s",varname,err);
2985 return -1;
2986 }
2987 return 0;
2988 }
2989 int
2990 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
2991 {
2992 char *color;
2993 graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
2995 color=strstr(var,"#");
2996 if (color==NULL) {
2997 if (optional==0) {
2998 rrd_set_error("Found no color in %s",err);
2999 return 0;
3000 }
3001 return 0;
3002 } else {
3003 int n=0;
3004 char *rest;
3005 gfx_color_t col;
3007 rest=strstr(color,":");
3008 if (rest!=NULL)
3009 n=rest-color;
3010 else
3011 n=strlen(color);
3013 switch (n) {
3014 case 7:
3015 sscanf(color,"#%6x%n",&col,&n);
3016 col = (col << 8) + 0xff /* shift left by 8 */;
3017 if (n!=7) rrd_set_error("Color problem in %s",err);
3018 break;
3019 case 9:
3020 sscanf(color,"#%8x%n",&col,&n);
3021 if (n==9) break;
3022 default:
3023 rrd_set_error("Color problem in %s",err);
3024 }
3025 if (rrd_test_error()) return 0;
3026 gdp->col = col;
3027 return n;
3028 }
3029 }
3030 int
3031 rrd_graph_check_CF(image_desc_t *im, char *symname, char *err)
3032 {
3033 if ((im->gdes[im->gdes_c-1].cf=cf_conv(symname))==-1) {
3034 rrd_set_error("Unknown CF '%s' in %s",symname,err);
3035 return -1;
3036 }
3037 return 0;
3038 }
3039 int
3040 rrd_graph_legend(graph_desc_t *gdp, char *line)
3041 {
3042 int i;
3044 i=scan_for_col(line,FMT_LEG_LEN,gdp->legend);
3046 return (strlen(&line[i])==0);
3047 }
3050 int bad_format(char *fmt) {
3051 char *ptr;
3052 int n=0;
3054 ptr = fmt;
3055 while (*ptr != '\0') {
3056 if (*ptr == '%') {ptr++;
3057 if (*ptr == '\0') return 1;
3058 while ((*ptr >= '0' && *ptr <= '9') || *ptr == '.') {
3059 ptr++;
3060 }
3061 if (*ptr == '\0') return 1;
3062 if (*ptr == 'l') {
3063 ptr++;
3064 n++;
3065 if (*ptr == '\0') return 1;
3066 if (*ptr == 'e' || *ptr == 'f') {
3067 ptr++;
3068 } else { return 1; }
3069 }
3070 else if (*ptr == 's' || *ptr == 'S' || *ptr == '%') { ++ptr; }
3071 else { return 1; }
3072 } else {
3073 ++ptr;
3074 }
3075 }
3076 return (n!=1);
3077 }
3078 int
3079 vdef_parse(gdes,str)
3080 struct graph_desc_t *gdes;
3081 char *str;
3082 {
3083 /* A VDEF currently is either "func" or "param,func"
3084 * so the parsing is rather simple. Change if needed.
3085 */
3086 double param;
3087 char func[30];
3088 int n;
3090 n=0;
3091 sscanf(str,"%le,%29[A-Z]%n",¶m,func,&n);
3092 if (n==strlen(str)) { /* matched */
3093 ;
3094 } else {
3095 n=0;
3096 sscanf(str,"%29[A-Z]%n",func,&n);
3097 if (n==strlen(str)) { /* matched */
3098 param=DNAN;
3099 } else {
3100 rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3101 ,str
3102 ,gdes->vname
3103 );
3104 return -1;
3105 }
3106 }
3107 if (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3108 else if (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3109 else if (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3110 else if (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3111 else if (!strcmp("TOTAL", func)) gdes->vf.op = VDEF_TOTAL;
3112 else if (!strcmp("FIRST", func)) gdes->vf.op = VDEF_FIRST;
3113 else if (!strcmp("LAST", func)) gdes->vf.op = VDEF_LAST;
3114 else {
3115 rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3116 ,func
3117 ,gdes->vname
3118 );
3119 return -1;
3120 };
3122 switch (gdes->vf.op) {
3123 case VDEF_PERCENT:
3124 if (isnan(param)) { /* no parameter given */
3125 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3126 ,func
3127 ,gdes->vname
3128 );
3129 return -1;
3130 };
3131 if (param>=0.0 && param<=100.0) {
3132 gdes->vf.param = param;
3133 gdes->vf.val = DNAN; /* undefined */
3134 gdes->vf.when = 0; /* undefined */
3135 } else {
3136 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3137 ,param
3138 ,gdes->vname
3139 );
3140 return -1;
3141 };
3142 break;
3143 case VDEF_MAXIMUM:
3144 case VDEF_AVERAGE:
3145 case VDEF_MINIMUM:
3146 case VDEF_TOTAL:
3147 case VDEF_FIRST:
3148 case VDEF_LAST:
3149 if (isnan(param)) {
3150 gdes->vf.param = DNAN;
3151 gdes->vf.val = DNAN;
3152 gdes->vf.when = 0;
3153 } else {
3154 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3155 ,func
3156 ,gdes->vname
3157 );
3158 return -1;
3159 };
3160 break;
3161 };
3162 return 0;
3163 }
3164 int
3165 vdef_calc(im,gdi)
3166 image_desc_t *im;
3167 int gdi;
3168 {
3169 graph_desc_t *src,*dst;
3170 rrd_value_t *data;
3171 long step,steps;
3173 dst = &im->gdes[gdi];
3174 src = &im->gdes[dst->vidx];
3175 data = src->data + src->ds;
3176 steps = (src->end - src->start) / src->step;
3178 #if 0
3179 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3180 ,src->start
3181 ,src->end
3182 ,steps
3183 );
3184 #endif
3186 switch (dst->vf.op) {
3187 case VDEF_PERCENT: {
3188 rrd_value_t * array;
3189 int field;
3192 if ((array = malloc(steps*sizeof(double)))==NULL) {
3193 rrd_set_error("malloc VDEV_PERCENT");
3194 return -1;
3195 }
3196 for (step=0;step < steps; step++) {
3197 array[step]=data[step*src->ds_cnt];
3198 }
3199 qsort(array,step,sizeof(double),vdef_percent_compar);
3201 field = (steps-1)*dst->vf.param/100;
3202 dst->vf.val = array[field];
3203 dst->vf.when = 0; /* no time component */
3204 #if 0
3205 for(step=0;step<steps;step++)
3206 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3207 #endif
3208 }
3209 break;
3210 case VDEF_MAXIMUM:
3211 step=0;
3212 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3213 if (step == steps) {
3214 dst->vf.val = DNAN;
3215 dst->vf.when = 0;
3216 } else {
3217 dst->vf.val = data[step*src->ds_cnt];
3218 dst->vf.when = src->start + (step+1)*src->step;
3219 }
3220 while (step != steps) {
3221 if (finite(data[step*src->ds_cnt])) {
3222 if (data[step*src->ds_cnt] > dst->vf.val) {
3223 dst->vf.val = data[step*src->ds_cnt];
3224 dst->vf.when = src->start + (step+1)*src->step;
3225 }
3226 }
3227 step++;
3228 }
3229 break;
3230 case VDEF_TOTAL:
3231 case VDEF_AVERAGE: {
3232 int cnt=0;
3233 double sum=0.0;
3234 for (step=0;step<steps;step++) {
3235 if (finite(data[step*src->ds_cnt])) {
3236 sum += data[step*src->ds_cnt];
3237 cnt ++;
3238 };
3239 }
3240 if (cnt) {
3241 if (dst->vf.op == VDEF_TOTAL) {
3242 dst->vf.val = sum*src->step;
3243 dst->vf.when = cnt*src->step; /* not really "when" */
3244 } else {
3245 dst->vf.val = sum/cnt;
3246 dst->vf.when = 0; /* no time component */
3247 };
3248 } else {
3249 dst->vf.val = DNAN;
3250 dst->vf.when = 0;
3251 }
3252 }
3253 break;
3254 case VDEF_MINIMUM:
3255 step=0;
3256 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3257 if (step == steps) {
3258 dst->vf.val = DNAN;
3259 dst->vf.when = 0;
3260 } else {
3261 dst->vf.val = data[step*src->ds_cnt];
3262 dst->vf.when = src->start + (step+1)*src->step;
3263 }
3264 while (step != steps) {
3265 if (finite(data[step*src->ds_cnt])) {
3266 if (data[step*src->ds_cnt] < dst->vf.val) {
3267 dst->vf.val = data[step*src->ds_cnt];
3268 dst->vf.when = src->start + (step+1)*src->step;
3269 }
3270 }
3271 step++;
3272 }
3273 break;
3274 case VDEF_FIRST:
3275 /* The time value returned here is one step before the
3276 * actual time value. This is the start of the first
3277 * non-NaN interval.
3278 */
3279 step=0;
3280 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3281 if (step == steps) { /* all entries were NaN */
3282 dst->vf.val = DNAN;
3283 dst->vf.when = 0;
3284 } else {
3285 dst->vf.val = data[step*src->ds_cnt];
3286 dst->vf.when = src->start + step*src->step;
3287 }
3288 break;
3289 case VDEF_LAST:
3290 /* The time value returned here is the
3291 * actual time value. This is the end of the last
3292 * non-NaN interval.
3293 */
3294 step=steps-1;
3295 while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3296 if (step < 0) { /* all entries were NaN */
3297 dst->vf.val = DNAN;
3298 dst->vf.when = 0;
3299 } else {
3300 dst->vf.val = data[step*src->ds_cnt];
3301 dst->vf.when = src->start + (step+1)*src->step;
3302 }
3303 break;
3304 }
3305 return 0;
3306 }
3308 /* NaN < -INF < finite_values < INF */
3309 int
3310 vdef_percent_compar(a,b)
3311 const void *a,*b;
3312 {
3313 /* Equality is not returned; this doesn't hurt except
3314 * (maybe) for a little performance.
3315 */
3317 /* First catch NaN values. They are smallest */
3318 if (isnan( *(double *)a )) return -1;
3319 if (isnan( *(double *)b )) return 1;
3321 /* NaN doesn't reach this part so INF and -INF are extremes.
3322 * The sign from isinf() is compatible with the sign we return
3323 */
3324 if (isinf( *(double *)a )) return isinf( *(double *)a );
3325 if (isinf( *(double *)b )) return isinf( *(double *)b );
3327 /* If we reach this, both values must be finite */
3328 if ( *(double *)a < *(double *)b ) return -1; else return 1;
3329 }