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 /* move the data pointers to the correct period */
820 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
821 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE){
822 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
823 if(im->gdes[gdi].start > im->gdes[ptr].start) {
824 im->gdes[gdi].rpnp[rpi].data += im->gdes[gdi].rpnp[rpi].ds_cnt;
825 }
826 }
827 }
830 if(steparray == NULL){
831 rrd_set_error("rpn expressions without DEF"
832 " or CDEF variables are not supported");
833 rpnstack_free(&rpnstack);
834 return -1;
835 }
836 steparray[stepcnt]=0;
837 /* Now find the resulting step. All steps in all
838 * used RRAs have to be visited
839 */
840 im->gdes[gdi].step = lcd(steparray);
841 free(steparray);
842 if((im->gdes[gdi].data = malloc((
843 (im->gdes[gdi].end-im->gdes[gdi].start)
844 / im->gdes[gdi].step)
845 * sizeof(double)))==NULL){
846 rrd_set_error("malloc im->gdes[gdi].data");
847 rpnstack_free(&rpnstack);
848 return -1;
849 }
851 /* Step through the new cdef results array and
852 * calculate the values
853 */
854 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
855 now<=im->gdes[gdi].end;
856 now += im->gdes[gdi].step)
857 {
858 rpnp_t *rpnp = im -> gdes[gdi].rpnp;
860 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
861 * in this case we are advancing by timesteps;
862 * we use the fact that time_t is a synonym for long
863 */
864 if (rpn_calc(rpnp,&rpnstack,(long) now,
865 im->gdes[gdi].data,++dataidx) == -1) {
866 /* rpn_calc sets the error string */
867 rpnstack_free(&rpnstack);
868 return -1;
869 }
870 } /* enumerate over time steps within a CDEF */
871 break;
872 default:
873 continue;
874 }
875 } /* enumerate over CDEFs */
876 rpnstack_free(&rpnstack);
877 return 0;
878 }
880 /* massage data so, that we get one value for each x coordinate in the graph */
881 int
882 data_proc( image_desc_t *im ){
883 long i,ii;
884 double pixstep = (double)(im->end-im->start)
885 /(double)im->xsize; /* how much time
886 passes in one pixel */
887 double paintval;
888 double minval=DNAN,maxval=DNAN;
890 unsigned long gr_time;
892 /* memory for the processed data */
893 for(i=0;i<im->gdes_c;i++){
894 if((im->gdes[i].gf==GF_LINE) ||
895 (im->gdes[i].gf==GF_AREA) ||
896 (im->gdes[i].gf==GF_TICK) ||
897 (im->gdes[i].gf==GF_STACK)){
898 if((im->gdes[i].p_data = malloc((im->xsize +1)
899 * sizeof(rrd_value_t)))==NULL){
900 rrd_set_error("malloc data_proc");
901 return -1;
902 }
903 }
904 }
906 for(i=0;i<im->xsize;i++){
907 long vidx;
908 gr_time = im->start+pixstep*i; /* time of the
909 current step */
910 paintval=0.0;
912 for(ii=0;ii<im->gdes_c;ii++){
913 double value;
914 switch(im->gdes[ii].gf){
915 case GF_LINE:
916 case GF_AREA:
917 case GF_TICK:
918 paintval = 0.0;
919 case GF_STACK:
920 vidx = im->gdes[ii].vidx;
922 value =
923 im->gdes[vidx].data[
924 ((unsigned long)floor(
925 (double)(gr_time-im->gdes[vidx].start) / im->gdes[vidx].step
926 )
927 ) *im->gdes[vidx].ds_cnt
928 +im->gdes[vidx].ds];
930 if (! isnan(value)) {
931 paintval += value;
932 im->gdes[ii].p_data[i] = paintval;
933 /* GF_TICK: the data values are not relevant for min and max */
934 if (finite(paintval) && im->gdes[ii].gf != GF_TICK ){
935 if (isnan(minval) || paintval < minval)
936 minval = paintval;
937 if (isnan(maxval) || paintval > maxval)
938 maxval = paintval;
939 }
940 } else {
941 im->gdes[ii].p_data[i] = DNAN;
942 }
943 break;
944 case GF_PRINT:
945 case GF_GPRINT:
946 case GF_COMMENT:
947 case GF_HRULE:
948 case GF_VRULE:
949 case GF_DEF:
950 case GF_CDEF:
951 case GF_VDEF:
952 case GF_PART:
953 break;
954 }
955 }
956 }
958 /* if min or max have not been asigned a value this is because
959 there was no data in the graph ... this is not good ...
960 lets set these to dummy values then ... */
962 if (isnan(minval)) minval = 0.0;
963 if (isnan(maxval)) maxval = 1.0;
965 /* adjust min and max values */
966 if (isnan(im->minval)
967 || ((!im->logarithmic && !im->rigid) /* don't adjust low-end with log scale */
968 && im->minval > minval))
969 im->minval = minval;
970 if (isnan(im->maxval)
971 || (!im->rigid
972 && im->maxval < maxval)){
973 if (im->logarithmic)
974 im->maxval = maxval * 1.1;
975 else
976 im->maxval = maxval;
977 }
978 /* make sure min and max are not equal */
979 if (im->minval == im->maxval) {
980 im->maxval *= 1.01;
981 if (! im->logarithmic) {
982 im->minval *= 0.99;
983 }
985 /* make sure min and max are not both zero */
986 if (im->maxval == 0.0) {
987 im->maxval = 1.0;
988 }
990 }
991 return 0;
992 }
996 /* identify the point where the first gridline, label ... gets placed */
998 time_t
999 find_first_time(
1000 time_t start, /* what is the initial time */
1001 enum tmt_en baseint, /* what is the basic interval */
1002 long basestep /* how many if these do we jump a time */
1003 )
1004 {
1005 struct tm tm;
1006 tm = *localtime(&start);
1007 switch(baseint){
1008 case TMT_SECOND:
1009 tm.tm_sec -= tm.tm_sec % basestep; break;
1010 case TMT_MINUTE:
1011 tm.tm_sec=0;
1012 tm.tm_min -= tm.tm_min % basestep;
1013 break;
1014 case TMT_HOUR:
1015 tm.tm_sec=0;
1016 tm.tm_min = 0;
1017 tm.tm_hour -= tm.tm_hour % basestep; break;
1018 case TMT_DAY:
1019 /* we do NOT look at the basestep for this ... */
1020 tm.tm_sec=0;
1021 tm.tm_min = 0;
1022 tm.tm_hour = 0; break;
1023 case TMT_WEEK:
1024 /* we do NOT look at the basestep for this ... */
1025 tm.tm_sec=0;
1026 tm.tm_min = 0;
1027 tm.tm_hour = 0;
1028 tm.tm_mday -= tm.tm_wday -1; /* -1 because we want the monday */
1029 if (tm.tm_wday==0) tm.tm_mday -= 7; /* we want the *previous* monday */
1030 break;
1031 case TMT_MONTH:
1032 tm.tm_sec=0;
1033 tm.tm_min = 0;
1034 tm.tm_hour = 0;
1035 tm.tm_mday = 1;
1036 tm.tm_mon -= tm.tm_mon % basestep; break;
1038 case TMT_YEAR:
1039 tm.tm_sec=0;
1040 tm.tm_min = 0;
1041 tm.tm_hour = 0;
1042 tm.tm_mday = 1;
1043 tm.tm_mon = 0;
1044 tm.tm_year -= (tm.tm_year+1900) % basestep;
1046 }
1047 return mktime(&tm);
1048 }
1049 /* identify the point where the next gridline, label ... gets placed */
1050 time_t
1051 find_next_time(
1052 time_t current, /* what is the initial time */
1053 enum tmt_en baseint, /* what is the basic interval */
1054 long basestep /* how many if these do we jump a time */
1055 )
1056 {
1057 struct tm tm;
1058 time_t madetime;
1059 tm = *localtime(¤t);
1060 do {
1061 switch(baseint){
1062 case TMT_SECOND:
1063 tm.tm_sec += basestep; break;
1064 case TMT_MINUTE:
1065 tm.tm_min += basestep; break;
1066 case TMT_HOUR:
1067 tm.tm_hour += basestep; break;
1068 case TMT_DAY:
1069 tm.tm_mday += basestep; break;
1070 case TMT_WEEK:
1071 tm.tm_mday += 7*basestep; break;
1072 case TMT_MONTH:
1073 tm.tm_mon += basestep; break;
1074 case TMT_YEAR:
1075 tm.tm_year += basestep;
1076 }
1077 madetime = mktime(&tm);
1078 } while (madetime == -1); /* this is necessary to skip impssible times
1079 like the daylight saving time skips */
1080 return madetime;
1082 }
1085 /* calculate values required for PRINT and GPRINT functions */
1087 int
1088 print_calc(image_desc_t *im, char ***prdata)
1089 {
1090 long i,ii,validsteps;
1091 double printval;
1092 time_t printtime;
1093 int graphelement = 0;
1094 long vidx;
1095 int max_ii;
1096 double magfact = -1;
1097 char *si_symb = "";
1098 char *percent_s;
1099 int prlines = 1;
1100 if (im->imginfo) prlines++;
1101 for(i=0;i<im->gdes_c;i++){
1102 switch(im->gdes[i].gf){
1103 case GF_PRINT:
1104 prlines++;
1105 if(((*prdata) = rrd_realloc((*prdata),prlines*sizeof(char *)))==NULL){
1106 rrd_set_error("realloc prdata");
1107 return 0;
1108 }
1109 case GF_GPRINT:
1110 /* PRINT and GPRINT can now print VDEF generated values.
1111 * There's no need to do any calculations on them as these
1112 * calculations were already made.
1113 */
1114 vidx = im->gdes[i].vidx;
1115 if (im->gdes[vidx].gf==GF_VDEF) { /* simply use vals */
1116 printval = im->gdes[vidx].vf.val;
1117 printtime = im->gdes[vidx].vf.when;
1118 } else { /* need to calculate max,min,avg etcetera */
1119 max_ii =((im->gdes[vidx].end
1120 - im->gdes[vidx].start)
1121 / im->gdes[vidx].step
1122 * im->gdes[vidx].ds_cnt);
1123 printval = DNAN;
1124 validsteps = 0;
1125 for( ii=im->gdes[vidx].ds;
1126 ii < max_ii;
1127 ii+=im->gdes[vidx].ds_cnt){
1128 if (! finite(im->gdes[vidx].data[ii]))
1129 continue;
1130 if (isnan(printval)){
1131 printval = im->gdes[vidx].data[ii];
1132 validsteps++;
1133 continue;
1134 }
1136 switch (im->gdes[i].cf){
1137 case CF_HWPREDICT:
1138 case CF_DEVPREDICT:
1139 case CF_DEVSEASONAL:
1140 case CF_SEASONAL:
1141 case CF_AVERAGE:
1142 validsteps++;
1143 printval += im->gdes[vidx].data[ii];
1144 break;
1145 case CF_MINIMUM:
1146 printval = min( printval, im->gdes[vidx].data[ii]);
1147 break;
1148 case CF_FAILURES:
1149 case CF_MAXIMUM:
1150 printval = max( printval, im->gdes[vidx].data[ii]);
1151 break;
1152 case CF_LAST:
1153 printval = im->gdes[vidx].data[ii];
1154 }
1155 }
1156 if (im->gdes[i].cf==CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1157 if (validsteps > 1) {
1158 printval = (printval / validsteps);
1159 }
1160 }
1161 } /* prepare printval */
1163 if (!strcmp(im->gdes[i].format,"%c")) { /* VDEF time print */
1164 if (im->gdes[i].gf == GF_PRINT){
1165 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1166 sprintf((*prdata)[prlines-2],"%s (%lu)",
1167 ctime(&printtime),printtime);
1168 (*prdata)[prlines-1] = NULL;
1169 } else {
1170 sprintf(im->gdes[i].legend,"%s (%lu)",
1171 ctime(&printtime),printtime);
1172 graphelement = 1;
1173 }
1174 } else {
1175 if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) {
1176 /* Magfact is set to -1 upon entry to print_calc. If it
1177 * is still less than 0, then we need to run auto_scale.
1178 * Otherwise, put the value into the correct units. If
1179 * the value is 0, then do not set the symbol or magnification
1180 * so next the calculation will be performed again. */
1181 if (magfact < 0.0) {
1182 auto_scale(im,&printval,&si_symb,&magfact);
1183 if (printval == 0.0)
1184 magfact = -1.0;
1185 } else {
1186 printval /= magfact;
1187 }
1188 *(++percent_s) = 's';
1189 } else if (strstr(im->gdes[i].format,"%s") != NULL) {
1190 auto_scale(im,&printval,&si_symb,&magfact);
1191 }
1193 if (im->gdes[i].gf == GF_PRINT){
1194 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1195 if (bad_format(im->gdes[i].format)) {
1196 rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1197 return -1;
1198 }
1199 #ifdef HAVE_SNPRINTF
1200 snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
1201 #else
1202 sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
1203 #endif
1204 (*prdata)[prlines-1] = NULL;
1205 } else {
1206 /* GF_GPRINT */
1208 if (bad_format(im->gdes[i].format)) {
1209 rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1210 return -1;
1211 }
1212 #ifdef HAVE_SNPRINTF
1213 snprintf(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
1214 #else
1215 sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
1216 #endif
1217 graphelement = 1;
1218 }
1219 }
1220 break;
1221 case GF_COMMENT:
1222 case GF_LINE:
1223 case GF_AREA:
1224 case GF_TICK:
1225 case GF_PART:
1226 case GF_STACK:
1227 case GF_HRULE:
1228 case GF_VRULE:
1229 graphelement = 1;
1230 break;
1231 case GF_DEF:
1232 case GF_CDEF:
1233 case GF_VDEF:
1234 break;
1235 }
1236 }
1237 return graphelement;
1238 }
1241 /* place legends with color spots */
1242 int
1243 leg_place(image_desc_t *im)
1244 {
1245 /* graph labels */
1246 int interleg = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1247 int box =im->text_prop[TEXT_PROP_LEGEND].size*1.5;
1248 int border = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1249 int fill=0, fill_last;
1250 int leg_c = 0;
1251 int leg_x = border, leg_y = im->ygif;
1252 int leg_cc;
1253 int glue = 0;
1254 int i,ii, mark = 0;
1255 char prt_fctn; /*special printfunctions */
1256 int *legspace;
1258 if( !(im->extra_flags & NOLEGEND) ) {
1259 if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1260 rrd_set_error("malloc for legspace");
1261 return -1;
1262 }
1264 for(i=0;i<im->gdes_c;i++){
1265 fill_last = fill;
1267 leg_cc = strlen(im->gdes[i].legend);
1269 /* is there a controle code ant the end of the legend string ? */
1270 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\') {
1271 prt_fctn = im->gdes[i].legend[leg_cc-1];
1272 leg_cc -= 2;
1273 im->gdes[i].legend[leg_cc] = '\0';
1274 } else {
1275 prt_fctn = '\0';
1276 }
1277 /* remove exess space */
1278 while (prt_fctn=='g' &&
1279 leg_cc > 0 &&
1280 im->gdes[i].legend[leg_cc-1]==' '){
1281 leg_cc--;
1282 im->gdes[i].legend[leg_cc]='\0';
1283 }
1284 if (leg_cc != 0 ){
1285 legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1287 if (fill > 0){
1288 /* no interleg space if string ends in \g */
1289 fill += legspace[i];
1290 }
1291 if (im->gdes[i].gf != GF_GPRINT &&
1292 im->gdes[i].gf != GF_COMMENT) {
1293 fill += box;
1294 }
1295 fill += gfx_get_text_width(fill+border,im->text_prop[TEXT_PROP_LEGEND].font,
1296 im->text_prop[TEXT_PROP_LEGEND].size,
1297 im->tabwidth,
1298 im->gdes[i].legend);
1299 leg_c++;
1300 } else {
1301 legspace[i]=0;
1302 }
1303 /* who said there was a special tag ... ?*/
1304 if (prt_fctn=='g') {
1305 prt_fctn = '\0';
1306 }
1307 if (prt_fctn == '\0') {
1308 if (i == im->gdes_c -1 ) prt_fctn ='l';
1310 /* is it time to place the legends ? */
1311 if (fill > im->xgif - 2*border){
1312 if (leg_c > 1) {
1313 /* go back one */
1314 i--;
1315 fill = fill_last;
1316 leg_c--;
1317 prt_fctn = 'j';
1318 } else {
1319 prt_fctn = 'l';
1320 }
1322 }
1323 }
1326 if (prt_fctn != '\0'){
1327 leg_x = border;
1328 if (leg_c >= 2 && prt_fctn == 'j') {
1329 glue = (im->xgif - fill - 2* border) / (leg_c-1);
1330 } else {
1331 glue = 0;
1332 }
1333 if (prt_fctn =='c') leg_x = (im->xgif - fill) / 2.0;
1334 if (prt_fctn =='r') leg_x = im->xgif - fill - border;
1336 for(ii=mark;ii<=i;ii++){
1337 if(im->gdes[ii].legend[0]=='\0')
1338 continue;
1339 im->gdes[ii].leg_x = leg_x;
1340 im->gdes[ii].leg_y = leg_y;
1341 leg_x +=
1342 gfx_get_text_width(leg_x,im->text_prop[TEXT_PROP_LEGEND].font,
1343 im->text_prop[TEXT_PROP_LEGEND].size,
1344 im->tabwidth,
1345 im->gdes[ii].legend)
1346 + legspace[ii]
1347 + glue;
1348 if (im->gdes[ii].gf != GF_GPRINT &&
1349 im->gdes[ii].gf != GF_COMMENT)
1350 leg_x += box;
1351 }
1352 leg_y = leg_y + im->text_prop[TEXT_PROP_LEGEND].size*1.2;
1353 if (prt_fctn == 's') leg_y -= im->text_prop[TEXT_PROP_LEGEND].size*1.2;
1354 fill = 0;
1355 leg_c = 0;
1356 mark = ii;
1357 }
1358 }
1359 im->ygif = leg_y+6;
1360 free(legspace);
1361 }
1362 return 0;
1363 }
1365 /* create a grid on the graph. it determines what to do
1366 from the values of xsize, start and end */
1368 /* the xaxis labels are determined from the number of seconds per pixel
1369 in the requested graph */
1373 int
1374 horizontal_grid(gfx_canvas_t *canvas, image_desc_t *im)
1375 {
1376 double range;
1377 double scaledrange;
1378 int pixel,i;
1379 int sgrid,egrid;
1380 double gridstep;
1381 double scaledstep;
1382 char graph_label[100];
1383 double x0,x1,y0,y1;
1384 int labfact,gridind;
1385 int decimals, fractionals;
1386 char labfmt[64];
1388 labfact=2;
1389 gridind=-1;
1390 range = im->maxval - im->minval;
1391 scaledrange = range / im->magfact;
1393 /* does the scale of this graph make it impossible to put lines
1394 on it? If so, give up. */
1395 if (isnan(scaledrange)) {
1396 return 0;
1397 }
1399 /* find grid spaceing */
1400 pixel=1;
1401 if(isnan(im->ygridstep)){
1402 if(im->extra_flags & ALTYGRID) {
1403 /* find the value with max number of digits. Get number of digits */
1404 decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))));
1405 if(decimals <= 0) /* everything is small. make place for zero */
1406 decimals = 1;
1408 fractionals = floor(log10(range));
1409 if(fractionals < 0) /* small amplitude. */
1410 sprintf(labfmt, "%%%d.%df", decimals - fractionals + 1, -fractionals + 1);
1411 else
1412 sprintf(labfmt, "%%%d.1f", decimals + 1);
1413 gridstep = pow((double)10, (double)fractionals);
1414 if(gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1415 gridstep = 0.1;
1416 /* should have at least 5 lines but no more then 15 */
1417 if(range/gridstep < 5)
1418 gridstep /= 10;
1419 if(range/gridstep > 15)
1420 gridstep *= 10;
1421 if(range/gridstep > 5) {
1422 labfact = 1;
1423 if(range/gridstep > 8)
1424 labfact = 2;
1425 }
1426 else {
1427 gridstep /= 5;
1428 labfact = 5;
1429 }
1430 }
1431 else {
1432 for(i=0;ylab[i].grid > 0;i++){
1433 pixel = im->ysize / (scaledrange / ylab[i].grid);
1434 if (gridind == -1 && pixel > 5) {
1435 gridind = i;
1436 break;
1437 }
1438 }
1440 for(i=0; i<4;i++) {
1441 if (pixel * ylab[gridind].lfac[i] >= 2 * im->text_prop[TEXT_PROP_AXIS].size) {
1442 labfact = ylab[gridind].lfac[i];
1443 break;
1444 }
1445 }
1447 gridstep = ylab[gridind].grid * im->magfact;
1448 }
1449 } else {
1450 gridstep = im->ygridstep;
1451 labfact = im->ylabfact;
1452 }
1454 x0=im->xorigin;
1455 x1=im->xorigin+im->xsize;
1457 sgrid = (int)( im->minval / gridstep - 1);
1458 egrid = (int)( im->maxval / gridstep + 1);
1459 scaledstep = gridstep/im->magfact;
1460 for (i = sgrid; i <= egrid; i++){
1461 y0=ytr(im,gridstep*i);
1462 if ( y0 >= im->yorigin-im->ysize
1463 && y0 <= im->yorigin){
1464 if(i % labfact == 0){
1465 if (i==0 || im->symbol == ' ') {
1466 if(scaledstep < 1){
1467 if(im->extra_flags & ALTYGRID) {
1468 sprintf(graph_label,labfmt,scaledstep*i);
1469 }
1470 else {
1471 sprintf(graph_label,"%4.1f",scaledstep*i);
1472 }
1473 } else {
1474 sprintf(graph_label,"%4.0f",scaledstep*i);
1475 }
1476 }else {
1477 if(scaledstep < 1){
1478 sprintf(graph_label,"%4.1f %c",scaledstep*i, im->symbol);
1479 } else {
1480 sprintf(graph_label,"%4.0f %c",scaledstep*i, im->symbol);
1481 }
1482 }
1484 gfx_new_text ( canvas,
1485 x0-im->text_prop[TEXT_PROP_AXIS].size/1.5, y0,
1486 im->graph_col[GRC_FONT],
1487 im->text_prop[TEXT_PROP_AXIS].font,
1488 im->text_prop[TEXT_PROP_AXIS].size,
1489 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1490 graph_label );
1491 gfx_new_line ( canvas,
1492 x0-2,y0,
1493 x1+2,y0,
1494 MGRIDWIDTH, im->graph_col[GRC_MGRID] );
1496 } else {
1497 gfx_new_line ( canvas,
1498 x0-1,y0,
1499 x1+1,y0,
1500 GRIDWIDTH, im->graph_col[GRC_GRID] );
1502 }
1503 }
1504 }
1505 return 1;
1506 }
1508 /* logaritmic horizontal grid */
1509 int
1510 horizontal_log_grid(gfx_canvas_t *canvas, image_desc_t *im)
1511 {
1512 double pixpex;
1513 int ii,i;
1514 int minoridx=0, majoridx=0;
1515 char graph_label[100];
1516 double x0,x1,y0,y1;
1517 double value, pixperstep, minstep;
1519 /* find grid spaceing */
1520 pixpex= (double)im->ysize / (log10(im->maxval) - log10(im->minval));
1522 if (isnan(pixpex)) {
1523 return 0;
1524 }
1526 for(i=0;yloglab[i][0] > 0;i++){
1527 minstep = log10(yloglab[i][0]);
1528 for(ii=1;yloglab[i][ii+1] > 0;ii++){
1529 if(yloglab[i][ii+2]==0){
1530 minstep = log10(yloglab[i][ii+1])-log10(yloglab[i][ii]);
1531 break;
1532 }
1533 }
1534 pixperstep = pixpex * minstep;
1535 if(pixperstep > 5){minoridx = i;}
1536 if(pixperstep > 2 * im->text_prop[TEXT_PROP_LEGEND].size){majoridx = i;}
1537 }
1539 x0=im->xorigin;
1540 x1=im->xorigin+im->xsize;
1541 /* paint minor grid */
1542 for (value = pow((double)10, log10(im->minval)
1543 - fmod(log10(im->minval),log10(yloglab[minoridx][0])));
1544 value <= im->maxval;
1545 value *= yloglab[minoridx][0]){
1546 if (value < im->minval) continue;
1547 i=0;
1548 while(yloglab[minoridx][++i] > 0){
1549 y0 = ytr(im,value * yloglab[minoridx][i]);
1550 if (y0 <= im->yorigin - im->ysize) break;
1551 gfx_new_line ( canvas,
1552 x0-1,y0,
1553 x1+1,y0,
1554 GRIDWIDTH, im->graph_col[GRC_GRID] );
1555 }
1556 }
1558 /* paint major grid and labels*/
1559 for (value = pow((double)10, log10(im->minval)
1560 - fmod(log10(im->minval),log10(yloglab[majoridx][0])));
1561 value <= im->maxval;
1562 value *= yloglab[majoridx][0]){
1563 if (value < im->minval) continue;
1564 i=0;
1565 while(yloglab[majoridx][++i] > 0){
1566 y0 = ytr(im,value * yloglab[majoridx][i]);
1567 if (y0 <= im->yorigin - im->ysize) break;
1568 gfx_new_line ( canvas,
1569 x0-2,y0,
1570 x1+2,y0,
1571 MGRIDWIDTH, im->graph_col[GRC_MGRID] );
1573 sprintf(graph_label,"%3.0e",value * yloglab[majoridx][i]);
1574 gfx_new_text ( canvas,
1575 x0-im->text_prop[TEXT_PROP_AXIS].size/1.5, y0,
1576 im->graph_col[GRC_FONT],
1577 im->text_prop[TEXT_PROP_AXIS].font,
1578 im->text_prop[TEXT_PROP_AXIS].size,
1579 im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1580 graph_label );
1581 }
1582 }
1583 return 1;
1584 }
1587 void
1588 vertical_grid(
1589 gfx_canvas_t *canvas,
1590 image_desc_t *im )
1591 {
1592 int xlab_sel; /* which sort of label and grid ? */
1593 time_t ti, tilab;
1594 long factor;
1595 char graph_label[100];
1596 double x0,y0,y1; /* points for filled graph and more*/
1599 /* the type of time grid is determined by finding
1600 the number of seconds per pixel in the graph */
1603 if(im->xlab_user.minsec == -1){
1604 factor=(im->end - im->start)/im->xsize;
1605 xlab_sel=0;
1606 while ( xlab[xlab_sel+1].minsec != -1
1607 && xlab[xlab_sel+1].minsec <= factor){ xlab_sel++; }
1608 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
1609 im->xlab_user.gridst = xlab[xlab_sel].gridst;
1610 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
1611 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
1612 im->xlab_user.labtm = xlab[xlab_sel].labtm;
1613 im->xlab_user.labst = xlab[xlab_sel].labst;
1614 im->xlab_user.precis = xlab[xlab_sel].precis;
1615 im->xlab_user.stst = xlab[xlab_sel].stst;
1616 }
1618 /* y coords are the same for every line ... */
1619 y0 = im->yorigin;
1620 y1 = im->yorigin-im->ysize;
1623 /* paint the minor grid */
1624 for(ti = find_first_time(im->start,
1625 im->xlab_user.gridtm,
1626 im->xlab_user.gridst);
1627 ti < im->end;
1628 ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
1629 ){
1630 /* are we inside the graph ? */
1631 if (ti < im->start || ti > im->end) continue;
1632 x0 = xtr(im,ti);
1633 gfx_new_line(canvas,x0,y0+1, x0,y1-1,GRIDWIDTH, im->graph_col[GRC_GRID]);
1635 }
1637 /* paint the major grid */
1638 for(ti = find_first_time(im->start,
1639 im->xlab_user.mgridtm,
1640 im->xlab_user.mgridst);
1641 ti < im->end;
1642 ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
1643 ){
1644 /* are we inside the graph ? */
1645 if (ti < im->start || ti > im->end) continue;
1646 x0 = xtr(im,ti);
1647 gfx_new_line(canvas,x0,y0+2, x0,y1-2,MGRIDWIDTH, im->graph_col[GRC_MGRID]);
1649 }
1650 /* paint the labels below the graph */
1651 for(ti = find_first_time(im->start,
1652 im->xlab_user.labtm,
1653 im->xlab_user.labst);
1654 ti <= im->end;
1655 ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
1656 ){
1657 tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
1659 #if HAVE_STRFTIME
1660 strftime(graph_label,99,im->xlab_user.stst,localtime(&tilab));
1661 #else
1662 # error "your libc has no strftime I guess we'll abort the exercise here."
1663 #endif
1664 gfx_new_text ( canvas,
1665 xtr(im,tilab), y0+im->text_prop[TEXT_PROP_AXIS].size/1.5,
1666 im->graph_col[GRC_FONT],
1667 im->text_prop[TEXT_PROP_AXIS].font,
1668 im->text_prop[TEXT_PROP_AXIS].size,
1669 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP,
1670 graph_label );
1672 }
1674 }
1677 void
1678 axis_paint(
1679 image_desc_t *im,
1680 gfx_canvas_t *canvas
1681 )
1682 {
1683 /* draw x and y axis */
1684 gfx_new_line ( canvas, im->xorigin+im->xsize,im->yorigin,
1685 im->xorigin+im->xsize,im->yorigin-im->ysize,
1686 GRIDWIDTH, im->graph_col[GRC_GRID]);
1688 gfx_new_line ( canvas, im->xorigin,im->yorigin-im->ysize,
1689 im->xorigin+im->xsize,im->yorigin-im->ysize,
1690 GRIDWIDTH, im->graph_col[GRC_GRID]);
1692 gfx_new_line ( canvas, im->xorigin-4,im->yorigin,
1693 im->xorigin+im->xsize+4,im->yorigin,
1694 MGRIDWIDTH, im->graph_col[GRC_GRID]);
1696 gfx_new_line ( canvas, im->xorigin,im->yorigin+4,
1697 im->xorigin,im->yorigin-im->ysize-4,
1698 MGRIDWIDTH, im->graph_col[GRC_GRID]);
1701 /* arrow for X axis direction */
1702 gfx_new_area ( canvas,
1703 im->xorigin+im->xsize+4, im->yorigin-3,
1704 im->xorigin+im->xsize+4, im->yorigin+3,
1705 im->xorigin+im->xsize+9, im->yorigin,
1706 im->graph_col[GRC_ARROW]);
1710 }
1712 void
1713 grid_paint(
1714 image_desc_t *im,
1715 gfx_canvas_t *canvas
1717 )
1718 {
1719 long i;
1720 int boxH=8, boxV=8;
1721 int res=0;
1722 double x0,x1,x2,x3,y0,y1,y2,y3; /* points for filled graph and more*/
1723 gfx_node_t *node;
1726 /* draw 3d border */
1727 node = gfx_new_area (canvas, 0,im->ygif,
1728 2,im->ygif-2,
1729 2,2,im->graph_col[GRC_SHADEA]);
1730 gfx_add_point( node , im->xgif - 2, 2 );
1731 gfx_add_point( node , im->xgif, 0 );
1732 gfx_add_point( node , 0,0 );
1733 /* gfx_add_point( node , 0,im->ygif ); */
1735 node = gfx_new_area (canvas, 2,im->ygif-2,
1736 im->xgif-2,im->ygif-2,
1737 im->xgif - 2, 2,
1738 im->graph_col[GRC_SHADEB]);
1739 gfx_add_point( node , im->xgif,0);
1740 gfx_add_point( node , im->xgif,im->ygif);
1741 gfx_add_point( node , 0,im->ygif);
1742 /* gfx_add_point( node , 0,im->ygif ); */
1745 if (im->draw_x_grid == 1 )
1746 vertical_grid(canvas, im);
1748 if (im->draw_y_grid == 1){
1749 if(im->logarithmic){
1750 res = horizontal_log_grid(canvas,im);
1751 } else {
1752 res = horizontal_grid(canvas,im);
1753 }
1755 /* dont draw horizontal grid if there is no min and max val */
1756 if (! res ) {
1757 char *nodata = "No Data found";
1758 gfx_new_text(canvas,im->xgif/2, (2*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,
1762 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
1763 nodata );
1764 }
1765 }
1767 /* yaxis description */
1768 gfx_new_text( canvas,
1769 7, (im->yorigin - im->ysize/2),
1770 im->graph_col[GRC_FONT],
1771 im->text_prop[TEXT_PROP_AXIS].font,
1772 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 270.0,
1773 GFX_H_CENTER, GFX_V_CENTER,
1774 im->ylegend);
1776 /* graph title */
1777 gfx_new_text( canvas,
1778 im->xgif/2, im->text_prop[TEXT_PROP_TITLE].size*1.5,
1779 im->graph_col[GRC_FONT],
1780 im->text_prop[TEXT_PROP_TITLE].font,
1781 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
1782 GFX_H_CENTER, GFX_V_CENTER,
1783 im->title);
1785 /* graph labels */
1786 if( !(im->extra_flags & NOLEGEND) ) {
1787 for(i=0;i<im->gdes_c;i++){
1788 if(im->gdes[i].legend[0] =='\0')
1789 continue;
1791 if(im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT){
1792 x0 = im->gdes[i].leg_x;
1793 y0 = im->gdes[i].leg_y+1.0;
1794 x1 = x0;
1795 x2 = x0+boxH;
1796 x3 = x0+boxH;
1797 y1 = y0+boxV;
1798 y2 = y0+boxV;
1799 y3 = y0;
1800 node = gfx_new_area(canvas, x0,y0,x1,y1,x2,y2 ,im->gdes[i].col);
1801 gfx_add_point ( node, x3, y3 );
1802 /* gfx_add_point ( node, x0, y0 ); */
1803 node = gfx_new_line(canvas, x0,y0,x1,y1 ,GRIDWIDTH, im->graph_col[GRC_FRAME]);
1804 gfx_add_point ( node, x2, y2 );
1805 gfx_add_point ( node, x3, y3 );
1806 gfx_add_point ( node, x0, y0 );
1808 gfx_new_text ( canvas, x0+boxH+6, (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_CENTER,
1813 im->gdes[i].legend );
1815 } else {
1816 x0 = im->gdes[i].leg_x;
1817 y0 = im->gdes[i].leg_y;
1819 gfx_new_text ( canvas, x0, (y0+y2) / 2.0,
1820 im->graph_col[GRC_FONT],
1821 im->text_prop[TEXT_PROP_AXIS].font,
1822 im->text_prop[TEXT_PROP_AXIS].size,
1823 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
1824 im->gdes[i].legend );
1826 }
1827 }
1828 }
1829 }
1832 /*****************************************************
1833 * lazy check make sure we rely need to create this graph
1834 *****************************************************/
1836 int lazy_check(image_desc_t *im){
1837 FILE *fd = NULL;
1838 int size = 1;
1839 struct stat gifstat;
1841 if (im->lazy == 0) return 0; /* no lazy option */
1842 if (stat(im->graphfile,&gifstat) != 0)
1843 return 0; /* can't stat */
1844 /* one pixel in the existing graph is more then what we would
1845 change here ... */
1846 if (time(NULL) - gifstat.st_mtime >
1847 (im->end - im->start) / im->xsize)
1848 return 0;
1849 if ((fd = fopen(im->graphfile,"rb")) == NULL)
1850 return 0; /* the file does not exist */
1851 switch (im->imgformat) {
1852 case IF_GIF:
1853 size = GifSize(fd,&(im->xgif),&(im->ygif));
1854 break;
1855 case IF_PNG:
1856 size = PngSize(fd,&(im->xgif),&(im->ygif));
1857 break;
1858 }
1859 fclose(fd);
1860 return size;
1861 }
1863 void
1864 pie_part(gfx_canvas_t *canvas, gfx_color_t color,
1865 double PieCenterX, double PieCenterY, double Radius,
1866 double startangle, double endangle)
1867 {
1868 gfx_node_t *node;
1869 double angle;
1870 double step=M_PI/50; /* Number of iterations for the circle;
1871 ** 10 is definitely too low, more than
1872 ** 50 seems to be overkill
1873 */
1875 /* Strange but true: we have to work clockwise or else
1876 ** anti aliasing nor transparency don't work.
1877 **
1878 ** This test is here to make sure we do it right, also
1879 ** this makes the for...next loop more easy to implement.
1880 ** The return will occur if the user enters a negative number
1881 ** (which shouldn't be done according to the specs) or if the
1882 ** programmers do something wrong (which, as we all know, never
1883 ** happens anyway :)
1884 */
1885 if (endangle<startangle) return;
1887 /* Hidden feature: Radius decreases each full circle */
1888 angle=startangle;
1889 while (angle>=2*M_PI) {
1890 angle -= 2*M_PI;
1891 Radius *= 0.8;
1892 }
1894 node=gfx_new_area(canvas,
1895 PieCenterX+sin(startangle)*Radius,
1896 PieCenterY-cos(startangle)*Radius,
1897 PieCenterX,
1898 PieCenterY,
1899 PieCenterX+sin(endangle)*Radius,
1900 PieCenterY-cos(endangle)*Radius,
1901 color);
1902 for (angle=endangle;angle-startangle>=step;angle-=step) {
1903 gfx_add_point(node,
1904 PieCenterX+sin(angle)*Radius,
1905 PieCenterY-cos(angle)*Radius );
1906 }
1907 }
1909 /* draw that picture thing ... */
1910 int
1911 graph_paint(image_desc_t *im, char ***calcpr)
1912 {
1913 int i,ii;
1914 int lazy = lazy_check(im);
1915 int piechart = 0;
1916 double PieStart=0.0, PieSize=0.0, PieCenterX=0.0, PieCenterY=0.0;
1917 FILE *fo;
1918 gfx_canvas_t *canvas;
1919 gfx_node_t *node;
1921 double areazero = 0.0;
1922 enum gf_en stack_gf = GF_PRINT;
1923 graph_desc_t *lastgdes = NULL;
1925 /* if we are lazy and there is nothing to PRINT ... quit now */
1926 if (lazy && im->prt_c==0) return 0;
1928 /* pull the data from the rrd files ... */
1930 if(data_fetch(im)==-1)
1931 return -1;
1933 /* evaluate VDEF and CDEF operations ... */
1934 if(data_calc(im)==-1)
1935 return -1;
1937 /* calculate and PRINT and GPRINT definitions. We have to do it at
1938 * this point because it will affect the length of the legends
1939 * if there are no graph elements we stop here ...
1940 * if we are lazy, try to quit ...
1941 */
1942 i=print_calc(im,calcpr);
1943 if(i<0) return -1;
1944 if(i==0 || lazy) return 0;
1946 /* get actual drawing data and find min and max values*/
1947 if(data_proc(im)==-1)
1948 return -1;
1950 if(!im->logarithmic){si_unit(im);} /* identify si magnitude Kilo, Mega Giga ? */
1952 if(!im->rigid && ! im->logarithmic)
1953 expand_range(im); /* make sure the upper and lower limit are
1954 sensible values */
1956 /* init xtr and ytr */
1957 /* determine the actual size of the gif to draw. The size given
1958 on the cmdline is the graph area. But we need more as we have
1959 draw labels and other things outside the graph area */
1962 im->xorigin = 10 + 9 * im->text_prop[TEXT_PROP_LEGEND].size;
1964 xtr(im,0);
1966 im->yorigin = 10 + im->ysize;
1968 ytr(im,DNAN);
1970 if(im->title[0] != '\0')
1971 im->yorigin += im->text_prop[TEXT_PROP_TITLE].size*3+4;
1973 im->xgif= 20 +im->xsize + im->xorigin;
1974 im->ygif= im->yorigin+2* im->text_prop[TEXT_PROP_LEGEND].size;
1976 /* check if we need to draw a piechart */
1977 for(i=0;i<im->gdes_c;i++){
1978 if (im->gdes[i].gf == GF_PART) {
1979 piechart=1;
1980 break;
1981 }
1982 }
1984 if (piechart) {
1985 /* allocate enough space for the piechart itself (PieSize), 20%
1986 ** more for the background and an additional 50 pixels spacing.
1987 */
1988 if (im->xsize < im->ysize)
1989 PieSize = im->xsize;
1990 else
1991 PieSize = im->ysize;
1992 im->xgif += PieSize*1.2 + 50;
1994 PieCenterX = im->xorigin + im->xsize + 50 + PieSize*0.6;
1995 PieCenterY = im->yorigin - PieSize*0.5;
1996 }
1998 /* determine where to place the legends onto the graphics.
1999 and set im->ygif to match space requirements for text */
2000 if(leg_place(im)==-1)
2001 return -1;
2003 canvas=gfx_new_canvas();
2006 /* the actual graph is created by going through the individual
2007 graph elements and then drawing them */
2009 node=gfx_new_area ( canvas,
2010 0, 0,
2011 im->xgif, 0,
2012 im->xgif, im->ygif,
2013 im->graph_col[GRC_BACK]);
2015 gfx_add_point(node,0, im->ygif);
2017 node=gfx_new_area ( canvas,
2018 im->xorigin, im->yorigin,
2019 im->xorigin + im->xsize, im->yorigin,
2020 im->xorigin + im->xsize, im->yorigin-im->ysize,
2021 im->graph_col[GRC_CANVAS]);
2023 gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
2025 #if 0
2026 /******************************************************************
2027 ** Just to play around. If you see this, I forgot to remove it **
2028 ******************************************************************/
2029 im->ygif+=100;
2030 node=gfx_new_area(canvas,
2031 0, im->ygif-100,
2032 im->xgif, im->ygif-100,
2033 im->xgif, im->ygif,
2034 im->graph_col[GRC_CANVAS]);
2035 gfx_add_point(node,0,im->ygif);
2037 /* Four areas:
2038 ** top left: current way, solid color
2039 ** top right: proper way, solid color
2040 ** bottom left: current way, alpha=0x80, partially overlapping
2041 ** bottom right: proper way, alpha=0x80, partially overlapping
2042 */
2043 {
2044 double x,y,x1,y1,x2,y2,x3,y3,x4,y4;
2046 x=(im->xgif-40)/6;
2047 y= (100-40)/6;
2048 x1= 20; y1=im->ygif-100+20;
2049 x2=3*x+20; y2=im->ygif-100+20;
2050 x3= x+20; y3=im->ygif-100+20+2*y;
2051 x4=4*x+20; y4=im->ygif-100+20+2*y;
2053 node=gfx_new_area(canvas,
2054 x1,y1,
2055 x1+3*x,y1,
2056 x1+3*x,y1+3*y,
2057 0xFF0000FF);
2058 gfx_add_point(node,x1,y1+3*y);
2059 node=gfx_new_area(canvas,
2060 x2,y2,
2061 x2,y2+3*y,
2062 x2+3*x,y2+3*y,
2063 0xFFFF00FF);
2064 gfx_add_point(node,x2+3*x,y2);
2065 node=gfx_new_area(canvas,
2066 x3,y3,
2067 x3+2*x,y3,
2068 x3+2*x,y3+3*y,
2069 0x00FF007F);
2070 gfx_add_point(node,x3,y3+3*y);
2071 node=gfx_new_area(canvas,
2072 x4,y4,
2073 x4,y4+3*y,
2074 x4+2*x,y4+3*y,
2075 0x0000FF7F);
2076 gfx_add_point(node,x4+2*x,y4);
2077 }
2079 #endif
2081 if (piechart) {
2082 pie_part(canvas,im->graph_col[GRC_CANVAS],PieCenterX,PieCenterY,PieSize*0.6,0,2*M_PI);
2083 }
2085 if (im->minval > 0.0)
2086 areazero = im->minval;
2087 if (im->maxval < 0.0)
2088 areazero = im->maxval;
2090 axis_paint(im,canvas);
2093 for(i=0;i<im->gdes_c;i++){
2094 switch(im->gdes[i].gf){
2095 case GF_CDEF:
2096 case GF_VDEF:
2097 case GF_DEF:
2098 case GF_PRINT:
2099 case GF_GPRINT:
2100 case GF_COMMENT:
2101 case GF_HRULE:
2102 case GF_VRULE:
2103 break;
2104 case GF_TICK:
2105 for (ii = 0; ii < im->xsize; ii++)
2106 {
2107 if (!isnan(im->gdes[i].p_data[ii]) &&
2108 im->gdes[i].p_data[ii] > 0.0)
2109 {
2110 /* generate a tick */
2111 gfx_new_line(canvas, im -> xorigin + ii,
2112 im -> yorigin - (im -> gdes[i].yrule * im -> ysize),
2113 im -> xorigin + ii,
2114 im -> yorigin,
2115 1.0,
2116 im -> gdes[i].col );
2117 }
2118 }
2119 break;
2120 case GF_LINE:
2121 case GF_AREA:
2122 stack_gf = im->gdes[i].gf;
2123 case GF_STACK:
2124 /* fix data points at oo and -oo */
2125 for(ii=0;ii<im->xsize;ii++){
2126 if (isinf(im->gdes[i].p_data[ii])){
2127 if (im->gdes[i].p_data[ii] > 0) {
2128 im->gdes[i].p_data[ii] = im->maxval ;
2129 } else {
2130 im->gdes[i].p_data[ii] = im->minval ;
2131 }
2133 }
2134 } /* for */
2136 if (im->gdes[i].col != 0x0){
2137 /* GF_LINE and friend */
2138 if(stack_gf == GF_LINE ){
2139 node = NULL;
2140 for(ii=1;ii<im->xsize;ii++){
2141 if ( ! isnan(im->gdes[i].p_data[ii-1])
2142 && ! isnan(im->gdes[i].p_data[ii])){
2143 if (node == NULL){
2144 node = gfx_new_line(canvas,
2145 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2146 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2147 im->gdes[i].linewidth,
2148 im->gdes[i].col);
2149 } else {
2150 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2151 }
2152 } else {
2153 node = NULL;
2154 }
2155 }
2156 } else {
2157 int area_start=-1;
2158 node = NULL;
2159 for(ii=1;ii<im->xsize;ii++){
2160 /* open an area */
2161 if ( ! isnan(im->gdes[i].p_data[ii-1])
2162 && ! isnan(im->gdes[i].p_data[ii])){
2163 if (node == NULL){
2164 float ybase = 0.0;
2165 if (im->gdes[i].gf == GF_STACK) {
2166 ybase = ytr(im,lastgdes->p_data[ii-1]);
2167 } else {
2168 ybase = ytr(im,areazero);
2169 }
2170 area_start = ii-1;
2171 node = gfx_new_area(canvas,
2172 ii-1+im->xorigin,ybase,
2173 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2174 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2175 im->gdes[i].col
2176 );
2177 } else {
2178 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2179 }
2180 }
2182 if ( node != NULL && (ii+1==im->xsize || isnan(im->gdes[i].p_data[ii]) )){
2183 /* GF_AREA STACK type*/
2184 if (im->gdes[i].gf == GF_STACK ) {
2185 int iii;
2186 for (iii=ii-1;iii>area_start;iii--){
2187 gfx_add_point(node,iii+im->xorigin,ytr(im,lastgdes->p_data[iii]));
2188 }
2189 } else {
2190 gfx_add_point(node,ii+im->xorigin,ytr(im,areazero));
2191 };
2192 node=NULL;
2193 };
2194 }
2195 } /* else GF_LINE */
2196 } /* if color != 0x0 */
2197 /* make sure we do not run into trouble when stacking on NaN */
2198 for(ii=0;ii<im->xsize;ii++){
2199 if (isnan(im->gdes[i].p_data[ii])) {
2200 double ybase = 0.0;
2201 if (lastgdes) {
2202 ybase = ytr(im,lastgdes->p_data[ii-1]);
2203 };
2204 if (isnan(ybase) || !lastgdes ){
2205 ybase = ytr(im,areazero);
2206 }
2207 im->gdes[i].p_data[ii] = ybase;
2208 }
2209 }
2210 lastgdes = &(im->gdes[i]);
2211 break;
2212 case GF_PART:
2213 if(isnan(im->gdes[i].yrule)) /* fetch variable */
2214 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2216 if (finite(im->gdes[i].yrule)) { /* even the fetched var can be NaN */
2217 pie_part(canvas,im->gdes[i].col,
2218 PieCenterX,PieCenterY,PieSize/2,
2219 M_PI*2.0*PieStart/100.0,
2220 M_PI*2.0*(PieStart+im->gdes[i].yrule)/100.0);
2221 PieStart += im->gdes[i].yrule;
2222 }
2223 break;
2224 } /* switch */
2225 }
2226 grid_paint(im,canvas);
2228 /* the RULES are the last thing to paint ... */
2229 for(i=0;i<im->gdes_c;i++){
2231 switch(im->gdes[i].gf){
2232 case GF_HRULE:
2233 if(isnan(im->gdes[i].yrule)) { /* fetch variable */
2234 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2235 };
2236 if(im->gdes[i].yrule >= im->minval
2237 && im->gdes[i].yrule <= im->maxval)
2238 gfx_new_line(canvas,
2239 im->xorigin,ytr(im,im->gdes[i].yrule),
2240 im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2241 1.0,im->gdes[i].col);
2242 break;
2243 case GF_VRULE:
2244 if(im->gdes[i].xrule == 0) { /* fetch variable */
2245 im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
2246 };
2247 if(im->gdes[i].xrule >= im->start
2248 && im->gdes[i].xrule <= im->end)
2249 gfx_new_line(canvas,
2250 xtr(im,im->gdes[i].xrule),im->yorigin,
2251 xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2252 1.0,im->gdes[i].col);
2253 break;
2254 default:
2255 break;
2256 }
2257 }
2260 if (strcmp(im->graphfile,"-")==0) {
2261 #ifdef WIN32
2262 /* Change translation mode for stdout to BINARY */
2263 _setmode( _fileno( stdout ), O_BINARY );
2264 #endif
2265 fo = stdout;
2266 } else {
2267 if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2268 rrd_set_error("Opening '%s' for write: %s",im->graphfile,
2269 strerror(errno));
2270 return (-1);
2271 }
2272 }
2273 switch (im->imgformat) {
2274 case IF_GIF:
2275 break;
2276 case IF_PNG:
2277 gfx_render_png (canvas,im->xgif,im->ygif,im->zoom,0x0,fo);
2278 break;
2279 }
2280 if (strcmp(im->graphfile,"-") != 0)
2281 fclose(fo);
2283 gfx_destroy(canvas);
2284 return 0;
2285 }
2288 /*****************************************************
2289 * graph stuff
2290 *****************************************************/
2292 int
2293 gdes_alloc(image_desc_t *im){
2295 long def_step = (im->end-im->start)/im->xsize;
2297 if (im->step > def_step) /* step can be increassed ... no decreassed */
2298 def_step = im->step;
2300 im->gdes_c++;
2302 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2303 * sizeof(graph_desc_t)))==NULL){
2304 rrd_set_error("realloc graph_descs");
2305 return -1;
2306 }
2309 im->gdes[im->gdes_c-1].step=def_step;
2310 im->gdes[im->gdes_c-1].start=im->start;
2311 im->gdes[im->gdes_c-1].end=im->end;
2312 im->gdes[im->gdes_c-1].vname[0]='\0';
2313 im->gdes[im->gdes_c-1].data=NULL;
2314 im->gdes[im->gdes_c-1].ds_namv=NULL;
2315 im->gdes[im->gdes_c-1].data_first=0;
2316 im->gdes[im->gdes_c-1].p_data=NULL;
2317 im->gdes[im->gdes_c-1].rpnp=NULL;
2318 im->gdes[im->gdes_c-1].col = 0x0;
2319 im->gdes[im->gdes_c-1].legend[0]='\0';
2320 im->gdes[im->gdes_c-1].rrd[0]='\0';
2321 im->gdes[im->gdes_c-1].ds=-1;
2322 im->gdes[im->gdes_c-1].p_data=NULL;
2323 return 0;
2324 }
2326 /* copies input untill the first unescaped colon is found
2327 or until input ends. backslashes have to be escaped as well */
2328 int
2329 scan_for_col(char *input, int len, char *output)
2330 {
2331 int inp,outp=0;
2332 for (inp=0;
2333 inp < len &&
2334 input[inp] != ':' &&
2335 input[inp] != '\0';
2336 inp++){
2337 if (input[inp] == '\\' &&
2338 input[inp+1] != '\0' &&
2339 (input[inp+1] == '\\' ||
2340 input[inp+1] == ':')){
2341 output[outp++] = input[++inp];
2342 }
2343 else {
2344 output[outp++] = input[inp];
2345 }
2346 }
2347 output[outp] = '\0';
2348 return inp;
2349 }
2351 /* Some surgery done on this function, it became ridiculously big.
2352 ** Things moved:
2353 ** - initializing now in rrd_graph_init()
2354 ** - options parsing now in rrd_graph_options()
2355 ** - script parsing now in rrd_graph_script()
2356 */
2357 int
2358 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize)
2359 {
2360 image_desc_t im;
2362 rrd_graph_init(&im);
2364 rrd_graph_options(argc,argv,&im);
2365 if (rrd_test_error()) return -1;
2367 if (strlen(argv[optind])>=MAXPATH) {
2368 rrd_set_error("filename (including path) too long");
2369 return -1;
2370 }
2371 strncpy(im.graphfile,argv[optind],MAXPATH-1);
2372 im.graphfile[MAXPATH-1]='\0';
2374 rrd_graph_script(argc,argv,&im);
2375 if (rrd_test_error()) return -1;
2377 /* Everything is now read and the actual work can start */
2379 (*prdata)=NULL;
2380 if (graph_paint(&im,prdata)==-1){
2381 im_free(&im);
2382 return -1;
2383 }
2385 /* The image is generated and needs to be output.
2386 ** Also, if needed, print a line with information about the image.
2387 */
2389 *xsize=im.xgif;
2390 *ysize=im.ygif;
2391 if (im.imginfo) {
2392 char *filename;
2393 if (!(*prdata)) {
2394 /* maybe prdata is not allocated yet ... lets do it now */
2395 if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
2396 rrd_set_error("malloc imginfo");
2397 return -1;
2398 };
2399 }
2400 if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
2401 ==NULL){
2402 rrd_set_error("malloc imginfo");
2403 return -1;
2404 }
2405 filename=im.graphfile+strlen(im.graphfile);
2406 while(filename > im.graphfile) {
2407 if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
2408 filename--;
2409 }
2411 sprintf((*prdata)[0],im.imginfo,filename,(long)(im.zoom*im.xgif),(long)(im.zoom*im.ygif));
2412 }
2413 im_free(&im);
2414 return 0;
2415 }
2417 void
2418 rrd_graph_init(image_desc_t *im)
2419 {
2420 int i;
2422 im->xlab_user.minsec = -1;
2423 im->xgif=0;
2424 im->ygif=0;
2425 im->xsize = 400;
2426 im->ysize = 100;
2427 im->step = 0;
2428 im->ylegend[0] = '\0';
2429 im->title[0] = '\0';
2430 im->minval = DNAN;
2431 im->maxval = DNAN;
2432 im->interlaced = 0;
2433 im->unitsexponent= 9999;
2434 im->extra_flags= 0;
2435 im->rigid = 0;
2436 im->imginfo = NULL;
2437 im->lazy = 0;
2438 im->logarithmic = 0;
2439 im->ygridstep = DNAN;
2440 im->draw_x_grid = 1;
2441 im->draw_y_grid = 1;
2442 im->base = 1000;
2443 im->prt_c = 0;
2444 im->gdes_c = 0;
2445 im->gdes = NULL;
2446 im->zoom = 1.0;
2447 im->imgformat = IF_GIF; /* we default to GIF output */
2449 for(i=0;i<DIM(graph_col);i++)
2450 im->graph_col[i]=graph_col[i];
2452 for(i=0;i<DIM(text_prop);i++){
2453 im->text_prop[i].size = text_prop[i].size;
2454 im->text_prop[i].font = text_prop[i].font;
2455 }
2456 }
2458 void
2459 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
2460 {
2461 int stroff;
2462 char *parsetime_error = NULL;
2463 char scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
2464 time_t start_tmp=0,end_tmp=0;
2465 long long_tmp;
2466 struct time_value start_tv, end_tv;
2467 gfx_color_t color;
2469 parsetime("end-24h", &start_tv);
2470 parsetime("now", &end_tv);
2472 while (1){
2473 static struct option long_options[] =
2474 {
2475 {"start", required_argument, 0, 's'},
2476 {"end", required_argument, 0, 'e'},
2477 {"x-grid", required_argument, 0, 'x'},
2478 {"y-grid", required_argument, 0, 'y'},
2479 {"vertical-label",required_argument,0,'v'},
2480 {"width", required_argument, 0, 'w'},
2481 {"height", required_argument, 0, 'h'},
2482 {"interlaced", no_argument, 0, 'i'},
2483 {"upper-limit",required_argument, 0, 'u'},
2484 {"lower-limit",required_argument, 0, 'l'},
2485 {"rigid", no_argument, 0, 'r'},
2486 {"base", required_argument, 0, 'b'},
2487 {"logarithmic",no_argument, 0, 'o'},
2488 {"color", required_argument, 0, 'c'},
2489 {"font", required_argument, 0, 'n'},
2490 {"title", required_argument, 0, 't'},
2491 {"imginfo", required_argument, 0, 'f'},
2492 {"imgformat", required_argument, 0, 'a'},
2493 {"lazy", no_argument, 0, 'z'},
2494 {"zoom", required_argument, 0, 'm'},
2495 {"no-legend", no_argument, 0, 'g'},
2496 {"alt-y-grid", no_argument, 0, 257 },
2497 {"alt-autoscale", no_argument, 0, 258 },
2498 {"alt-autoscale-max", no_argument, 0, 259 },
2499 {"units-exponent",required_argument, 0, 260},
2500 {"step", required_argument, 0, 261},
2501 {0,0,0,0}};
2502 int option_index = 0;
2503 int opt;
2506 opt = getopt_long(argc, argv,
2507 "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:z:g",
2508 long_options, &option_index);
2510 if (opt == EOF)
2511 break;
2513 switch(opt) {
2514 case 257:
2515 im->extra_flags |= ALTYGRID;
2516 break;
2517 case 258:
2518 im->extra_flags |= ALTAUTOSCALE;
2519 break;
2520 case 259:
2521 im->extra_flags |= ALTAUTOSCALE_MAX;
2522 break;
2523 case 'g':
2524 im->extra_flags |= NOLEGEND;
2525 break;
2526 case 260:
2527 im->unitsexponent = atoi(optarg);
2528 break;
2529 case 261:
2530 im->step = atoi(optarg);
2531 break;
2532 case 's':
2533 if ((parsetime_error = parsetime(optarg, &start_tv))) {
2534 rrd_set_error( "start time: %s", parsetime_error );
2535 return;
2536 }
2537 break;
2538 case 'e':
2539 if ((parsetime_error = parsetime(optarg, &end_tv))) {
2540 rrd_set_error( "end time: %s", parsetime_error );
2541 return;
2542 }
2543 break;
2544 case 'x':
2545 if(strcmp(optarg,"none") == 0){
2546 im->draw_x_grid=0;
2547 break;
2548 };
2550 if(sscanf(optarg,
2551 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
2552 scan_gtm,
2553 &im->xlab_user.gridst,
2554 scan_mtm,
2555 &im->xlab_user.mgridst,
2556 scan_ltm,
2557 &im->xlab_user.labst,
2558 &im->xlab_user.precis,
2559 &stroff) == 7 && stroff != 0){
2560 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
2561 if((im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
2562 rrd_set_error("unknown keyword %s",scan_gtm);
2563 return;
2564 } else if ((im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
2565 rrd_set_error("unknown keyword %s",scan_mtm);
2566 return;
2567 } else if ((im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
2568 rrd_set_error("unknown keyword %s",scan_ltm);
2569 return;
2570 }
2571 im->xlab_user.minsec = 1;
2572 im->xlab_user.stst = im->xlab_form;
2573 } else {
2574 rrd_set_error("invalid x-grid format");
2575 return;
2576 }
2577 break;
2578 case 'y':
2580 if(strcmp(optarg,"none") == 0){
2581 im->draw_y_grid=0;
2582 break;
2583 };
2585 if(sscanf(optarg,
2586 "%lf:%d",
2587 &im->ygridstep,
2588 &im->ylabfact) == 2) {
2589 if(im->ygridstep<=0){
2590 rrd_set_error("grid step must be > 0");
2591 return;
2592 } else if (im->ylabfact < 1){
2593 rrd_set_error("label factor must be > 0");
2594 return;
2595 }
2596 } else {
2597 rrd_set_error("invalid y-grid format");
2598 return;
2599 }
2600 break;
2601 case 'v':
2602 strncpy(im->ylegend,optarg,150);
2603 im->ylegend[150]='\0';
2604 break;
2605 case 'u':
2606 im->maxval = atof(optarg);
2607 break;
2608 case 'l':
2609 im->minval = atof(optarg);
2610 break;
2611 case 'b':
2612 im->base = atol(optarg);
2613 if(im->base != 1024 && im->base != 1000 ){
2614 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
2615 return;
2616 }
2617 break;
2618 case 'w':
2619 long_tmp = atol(optarg);
2620 if (long_tmp < 10) {
2621 rrd_set_error("width below 10 pixels");
2622 return;
2623 }
2624 im->xsize = long_tmp;
2625 break;
2626 case 'h':
2627 long_tmp = atol(optarg);
2628 if (long_tmp < 10) {
2629 rrd_set_error("height below 10 pixels");
2630 return;
2631 }
2632 im->ysize = long_tmp;
2633 break;
2634 case 'i':
2635 im->interlaced = 1;
2636 break;
2637 case 'r':
2638 im->rigid = 1;
2639 break;
2640 case 'f':
2641 im->imginfo = optarg;
2642 break;
2643 case 'a':
2644 if((im->imgformat = if_conv(optarg)) == -1) {
2645 rrd_set_error("unsupported graphics format '%s'",optarg);
2646 return;
2647 }
2648 break;
2649 case 'z':
2650 im->lazy = 1;
2651 break;
2652 case 'o':
2653 im->logarithmic = 1;
2654 if (isnan(im->minval))
2655 im->minval=1;
2656 break;
2657 case 'c':
2658 if(sscanf(optarg,
2659 "%10[A-Z]#%8x",
2660 col_nam,&color) == 2){
2661 int ci;
2662 if((ci=grc_conv(col_nam)) != -1){
2663 im->graph_col[ci]=color;
2664 } else {
2665 rrd_set_error("invalid color name '%s'",col_nam);
2666 }
2667 } else {
2668 rrd_set_error("invalid color def format");
2669 return -1;
2670 }
2671 break;
2672 case 'n':{
2673 /* originally this used char *prop = "" and
2674 ** char *font = "dummy" however this results
2675 ** in a SEG fault, at least on RH7.1
2676 **
2677 ** The current implementation isn't proper
2678 ** either, font is never freed and prop uses
2679 ** a fixed width string
2680 */
2681 char prop[100];
2682 double size = 1;
2683 char *font;
2685 font=malloc(255);
2686 if(sscanf(optarg,
2687 "%10[A-Z]:%lf:%s",
2688 prop,&size,font) == 3){
2689 int sindex;
2690 if((sindex=text_prop_conv(prop)) != -1){
2691 im->text_prop[sindex].size=size;
2692 im->text_prop[sindex].font=font;
2693 if (sindex==0) { /* the default */
2694 im->text_prop[TEXT_PROP_TITLE].size=size;
2695 im->text_prop[TEXT_PROP_TITLE].font=font;
2696 im->text_prop[TEXT_PROP_AXIS].size=size;
2697 im->text_prop[TEXT_PROP_AXIS].font=font;
2698 im->text_prop[TEXT_PROP_UNIT].size=size;
2699 im->text_prop[TEXT_PROP_UNIT].font=font;
2700 im->text_prop[TEXT_PROP_LEGEND].size=size;
2701 im->text_prop[TEXT_PROP_LEGEND].font=font;
2702 }
2703 } else {
2704 rrd_set_error("invalid fonttag '%s'",prop);
2705 return;
2706 }
2707 } else {
2708 rrd_set_error("invalid text property format");
2709 return;
2710 }
2711 break;
2712 }
2713 case 'm':
2714 im->zoom= atof(optarg);
2715 if (im->zoom <= 0.0) {
2716 rrd_set_error("zoom factor must be > 0");
2717 return;
2718 }
2719 break;
2720 case 't':
2721 strncpy(im->title,optarg,150);
2722 im->title[150]='\0';
2723 break;
2725 case '?':
2726 if (optopt != 0)
2727 rrd_set_error("unknown option '%c'", optopt);
2728 else
2729 rrd_set_error("unknown option '%s'",argv[optind-1]);
2730 return;
2731 }
2732 }
2734 if (optind >= argc) {
2735 rrd_set_error("missing filename");
2736 return;
2737 }
2739 if (im->logarithmic == 1 && (im->minval <= 0 || isnan(im->minval))){
2740 rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");
2741 return;
2742 }
2744 if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
2745 /* error string is set in parsetime.c */
2746 return;
2747 }
2749 if (start_tmp < 3600*24*365*10){
2750 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
2751 return;
2752 }
2754 if (end_tmp < start_tmp) {
2755 rrd_set_error("start (%ld) should be less than end (%ld)",
2756 start_tmp, end_tmp);
2757 return;
2758 }
2760 im->start = start_tmp;
2761 im->end = end_tmp;
2762 }
2764 void
2765 rrd_graph_script(int argc, char *argv[], image_desc_t *im)
2766 {
2767 int i;
2768 char symname[100];
2769 int linepass = 0; /* stack must follow LINE*, AREA or STACK */
2771 for (i=optind+1;i<argc;i++) {
2772 int argstart=0;
2773 int strstart=0;
2774 graph_desc_t *gdp;
2775 char *line;
2776 char funcname[10],vname[MAX_VNAME_LEN+1],sep[1];
2777 double d;
2778 double linewidth;
2779 int j,k,l,m;
2781 /* Each command is one element from *argv[], we call this "line".
2782 **
2783 ** Each command defines the most current gdes inside struct im.
2784 ** In stead of typing "im->gdes[im->gdes_c-1]" we use "gdp".
2785 */
2786 gdes_alloc(im);
2787 gdp=&im->gdes[im->gdes_c-1];
2788 line=argv[i];
2790 /* function:newvname=string[:ds-name:CF] for xDEF
2791 ** function:vname[#color[:string]] for LINEx,AREA,STACK
2792 ** function:vname#color[:num[:string]] for TICK
2793 ** function:vname-or-num#color[:string] for xRULE,PART
2794 ** function:vname:CF:string for xPRINT
2795 ** function:string for COMMENT
2796 */
2797 argstart=0;
2799 sscanf(line, "%10[A-Z0-9]:%n", funcname,&argstart);
2800 if (argstart==0) {
2801 rrd_set_error("Cannot parse function in line: %s",line);
2802 im_free(im);
2803 return;
2804 }
2805 if(sscanf(funcname,"LINE%lf",&linewidth)){
2806 im->gdes[im->gdes_c-1].gf = GF_LINE;
2807 im->gdes[im->gdes_c-1].linewidth = linewidth;
2808 } else {
2809 if ((gdp->gf=gf_conv(funcname))==-1) {
2810 rrd_set_error("'%s' is not a valid function name",funcname);
2811 im_free(im);
2812 return;
2813 }
2814 }
2816 /* If the error string is set, we exit at the end of the switch */
2817 switch (gdp->gf) {
2818 case GF_COMMENT:
2819 if (rrd_graph_legend(gdp,&line[argstart])==0)
2820 rrd_set_error("Cannot parse comment in line: %s",line);
2821 break;
2822 case GF_PART:
2823 case GF_VRULE:
2824 case GF_HRULE:
2825 j=k=l=m=0;
2826 sscanf(&line[argstart], "%lf%n#%n", &d, &j, &k);
2827 sscanf(&line[argstart], DEF_NAM_FMT "%n#%n", vname, &l, &m);
2828 if (k+m==0) {
2829 rrd_set_error("Cannot parse name or num in line: %s",line);
2830 break;
2831 }
2832 if (j!=0) {
2833 gdp->xrule=d;
2834 gdp->yrule=d;
2835 argstart+=j;
2836 } else if (!rrd_graph_check_vname(im,vname,line)) {
2837 gdp->xrule=0;
2838 gdp->yrule=DNAN;
2839 argstart+=l;
2840 } else break; /* exit due to wrong vname */
2841 if ((j=rrd_graph_color(im,&line[argstart],line,0))==0) break;
2842 argstart+=j;
2843 if (strlen(&line[argstart])!=0) {
2844 if (rrd_graph_legend(gdp,&line[++argstart])==0)
2845 rrd_set_error("Cannot parse comment in line: %s",line);
2846 }
2847 break;
2848 case GF_STACK:
2849 if (linepass==0) {
2850 rrd_set_error("STACK must follow another graphing element");
2851 break;
2852 }
2853 case GF_LINE:
2854 case GF_AREA:
2855 case GF_TICK:
2856 j=k=0;
2857 linepass=1;
2858 sscanf(&line[argstart],DEF_NAM_FMT"%n%1[#:]%n",vname,&j,sep,&k);
2859 if (j+1!=k)
2860 rrd_set_error("Cannot parse vname in line: %s",line);
2861 else if (rrd_graph_check_vname(im,vname,line))
2862 rrd_set_error("Undefined vname '%s' in line: %s",line);
2863 else
2864 k=rrd_graph_color(im,&line[argstart],line,1);
2865 if (rrd_test_error()) break;
2866 argstart=argstart+j+k;
2867 if ((strlen(&line[argstart])!=0)&&(gdp->gf==GF_TICK)) {
2868 j=0;
2869 sscanf(&line[argstart], ":%lf%n", &gdp->yrule,&j);
2870 argstart+=j;
2871 }
2872 if (strlen(&line[argstart])!=0)
2873 if (rrd_graph_legend(gdp,&line[++argstart])==0)
2874 rrd_set_error("Cannot parse legend in line: %s",line);
2875 break;
2876 case GF_PRINT:
2877 im->prt_c++;
2878 case GF_GPRINT:
2879 j=0;
2880 sscanf(&line[argstart], DEF_NAM_FMT ":%n",gdp->vname,&j);
2881 if (j==0) {
2882 rrd_set_error("Cannot parse vname in line: '%s'",line);
2883 break;
2884 }
2885 argstart+=j;
2886 if (rrd_graph_check_vname(im,gdp->vname,line)) return;
2887 j=0;
2888 sscanf(&line[argstart], CF_NAM_FMT ":%n",symname,&j);
2890 k=(j!=0)?rrd_graph_check_CF(im,symname,line):1;
2891 #define VIDX im->gdes[gdp->vidx]
2892 switch (k) {
2893 case -1: /* looks CF but is not really CF */
2894 if (VIDX.gf == GF_VDEF) rrd_clear_error();
2895 break;
2896 case 0: /* CF present and correct */
2897 if (VIDX.gf == GF_VDEF)
2898 rrd_set_error("Don't use CF when printing VDEF");
2899 argstart+=j;
2900 break;
2901 case 1: /* CF not present */
2902 if (VIDX.gf == GF_VDEF) rrd_clear_error();
2903 else rrd_set_error("Printing DEF or CDEF needs CF");
2904 break;
2905 default:
2906 rrd_set_error("Oops, bug in GPRINT scanning");
2907 }
2908 #undef VIDX
2909 if (rrd_test_error()) break;
2911 if (strlen(&line[argstart])!=0) {
2912 if (rrd_graph_legend(gdp,&line[argstart])==0)
2913 rrd_set_error("Cannot parse legend in line: %s",line);
2914 } else rrd_set_error("No legend in (G)PRINT line: %s",line);
2915 strcpy(gdp->format, gdp->legend);
2916 break;
2917 case GF_DEF:
2918 case GF_VDEF:
2919 case GF_CDEF:
2920 j=0;
2921 sscanf(&line[argstart], DEF_NAM_FMT "=%n",gdp->vname,&j);
2922 if (j==0) {
2923 rrd_set_error("Could not parse line: %s",line);
2924 break;
2925 }
2926 if (find_var(im,gdp->vname)!=-1) {
2927 rrd_set_error("Variable '%s' in line '%s' already in use\n",
2928 gdp->vname,line);
2929 break;
2930 }
2931 argstart+=j;
2932 switch (gdp->gf) {
2933 case GF_DEF:
2934 argstart+=scan_for_col(&line[argstart],MAXPATH,gdp->rrd);
2935 j=k=0;
2936 sscanf(&line[argstart],
2937 ":" DS_NAM_FMT ":" CF_NAM_FMT "%n%*s%n",
2938 gdp->ds_nam, symname, &j, &k);
2939 if ((j==0)||(k!=0)) {
2940 rrd_set_error("Cannot parse DS or CF in '%s'",line);
2941 break;
2942 }
2943 rrd_graph_check_CF(im,symname,line);
2944 break;
2945 case GF_VDEF:
2946 j=0;
2947 sscanf(&line[argstart],DEF_NAM_FMT ",%n",vname,&j);
2948 if (j==0) {
2949 rrd_set_error("Cannot parse vname in line '%s'",line);
2950 break;
2951 }
2952 argstart+=j;
2953 if (rrd_graph_check_vname(im,vname,line)) return;
2954 if ( im->gdes[gdp->vidx].gf != GF_DEF
2955 && im->gdes[gdp->vidx].gf != GF_CDEF) {
2956 rrd_set_error("variable '%s' not DEF nor "
2957 "CDEF in VDEF '%s'", vname,gdp->vname);
2958 break;
2959 }
2960 vdef_parse(gdp,&line[argstart+strstart]);
2961 break;
2962 case GF_CDEF:
2963 if (strstr(&line[argstart],":")!=NULL) {
2964 rrd_set_error("Error in RPN, line: %s",line);
2965 break;
2966 }
2967 if ((gdp->rpnp = rpn_parse(
2968 (void *)im,
2969 &line[argstart],
2970 &find_var_wrapper)
2971 )==NULL)
2972 rrd_set_error("invalid rpn expression in: %s",line);
2973 break;
2974 default: break;
2975 }
2976 break;
2977 default: rrd_set_error("Big oops");
2978 }
2979 if (rrd_test_error()) {
2980 im_free(im);
2981 return;
2982 }
2983 }
2985 if (im->gdes_c==0){
2986 rrd_set_error("can't make a graph without contents");
2987 im_free(im); /* ??? is this set ??? */
2988 return;
2989 }
2990 }
2991 int
2992 rrd_graph_check_vname(image_desc_t *im, char *varname, char *err)
2993 {
2994 if ((im->gdes[im->gdes_c-1].vidx=find_var(im,varname))==-1) {
2995 rrd_set_error("Unknown variable '%s' in %s",varname,err);
2996 return -1;
2997 }
2998 return 0;
2999 }
3000 int
3001 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
3002 {
3003 char *color;
3004 graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
3006 color=strstr(var,"#");
3007 if (color==NULL) {
3008 if (optional==0) {
3009 rrd_set_error("Found no color in %s",err);
3010 return 0;
3011 }
3012 return 0;
3013 } else {
3014 int n=0;
3015 char *rest;
3016 gfx_color_t col;
3018 rest=strstr(color,":");
3019 if (rest!=NULL)
3020 n=rest-color;
3021 else
3022 n=strlen(color);
3024 switch (n) {
3025 case 7:
3026 sscanf(color,"#%6x%n",&col,&n);
3027 col = (col << 8) + 0xff /* shift left by 8 */;
3028 if (n!=7) rrd_set_error("Color problem in %s",err);
3029 break;
3030 case 9:
3031 sscanf(color,"#%8x%n",&col,&n);
3032 if (n==9) break;
3033 default:
3034 rrd_set_error("Color problem in %s",err);
3035 }
3036 if (rrd_test_error()) return 0;
3037 gdp->col = col;
3038 return n;
3039 }
3040 }
3041 int
3042 rrd_graph_check_CF(image_desc_t *im, char *symname, char *err)
3043 {
3044 if ((im->gdes[im->gdes_c-1].cf=cf_conv(symname))==-1) {
3045 rrd_set_error("Unknown CF '%s' in %s",symname,err);
3046 return -1;
3047 }
3048 return 0;
3049 }
3050 int
3051 rrd_graph_legend(graph_desc_t *gdp, char *line)
3052 {
3053 int i;
3055 i=scan_for_col(line,FMT_LEG_LEN,gdp->legend);
3057 return (strlen(&line[i])==0);
3058 }
3061 int bad_format(char *fmt) {
3062 char *ptr;
3063 int n=0;
3065 ptr = fmt;
3066 while (*ptr != '\0') {
3067 if (*ptr == '%') {ptr++;
3068 if (*ptr == '\0') return 1;
3069 while ((*ptr >= '0' && *ptr <= '9') || *ptr == '.') {
3070 ptr++;
3071 }
3072 if (*ptr == '\0') return 1;
3073 if (*ptr == 'l') {
3074 ptr++;
3075 n++;
3076 if (*ptr == '\0') return 1;
3077 if (*ptr == 'e' || *ptr == 'f') {
3078 ptr++;
3079 } else { return 1; }
3080 }
3081 else if (*ptr == 's' || *ptr == 'S' || *ptr == '%') { ++ptr; }
3082 else { return 1; }
3083 } else {
3084 ++ptr;
3085 }
3086 }
3087 return (n!=1);
3088 }
3089 int
3090 vdef_parse(gdes,str)
3091 struct graph_desc_t *gdes;
3092 char *str;
3093 {
3094 /* A VDEF currently is either "func" or "param,func"
3095 * so the parsing is rather simple. Change if needed.
3096 */
3097 double param;
3098 char func[30];
3099 int n;
3101 n=0;
3102 sscanf(str,"%le,%29[A-Z]%n",¶m,func,&n);
3103 if (n==strlen(str)) { /* matched */
3104 ;
3105 } else {
3106 n=0;
3107 sscanf(str,"%29[A-Z]%n",func,&n);
3108 if (n==strlen(str)) { /* matched */
3109 param=DNAN;
3110 } else {
3111 rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3112 ,str
3113 ,gdes->vname
3114 );
3115 return -1;
3116 }
3117 }
3118 if (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3119 else if (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3120 else if (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3121 else if (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3122 else if (!strcmp("TOTAL", func)) gdes->vf.op = VDEF_TOTAL;
3123 else if (!strcmp("FIRST", func)) gdes->vf.op = VDEF_FIRST;
3124 else if (!strcmp("LAST", func)) gdes->vf.op = VDEF_LAST;
3125 else {
3126 rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3127 ,func
3128 ,gdes->vname
3129 );
3130 return -1;
3131 };
3133 switch (gdes->vf.op) {
3134 case VDEF_PERCENT:
3135 if (isnan(param)) { /* no parameter given */
3136 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3137 ,func
3138 ,gdes->vname
3139 );
3140 return -1;
3141 };
3142 if (param>=0.0 && param<=100.0) {
3143 gdes->vf.param = param;
3144 gdes->vf.val = DNAN; /* undefined */
3145 gdes->vf.when = 0; /* undefined */
3146 } else {
3147 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3148 ,param
3149 ,gdes->vname
3150 );
3151 return -1;
3152 };
3153 break;
3154 case VDEF_MAXIMUM:
3155 case VDEF_AVERAGE:
3156 case VDEF_MINIMUM:
3157 case VDEF_TOTAL:
3158 case VDEF_FIRST:
3159 case VDEF_LAST:
3160 if (isnan(param)) {
3161 gdes->vf.param = DNAN;
3162 gdes->vf.val = DNAN;
3163 gdes->vf.when = 0;
3164 } else {
3165 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3166 ,func
3167 ,gdes->vname
3168 );
3169 return -1;
3170 };
3171 break;
3172 };
3173 return 0;
3174 }
3175 int
3176 vdef_calc(im,gdi)
3177 image_desc_t *im;
3178 int gdi;
3179 {
3180 graph_desc_t *src,*dst;
3181 rrd_value_t *data;
3182 long step,steps;
3184 dst = &im->gdes[gdi];
3185 src = &im->gdes[dst->vidx];
3186 data = src->data + src->ds;
3187 steps = (src->end - src->start) / src->step;
3189 #if 0
3190 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3191 ,src->start
3192 ,src->end
3193 ,steps
3194 );
3195 #endif
3197 switch (dst->vf.op) {
3198 case VDEF_PERCENT: {
3199 rrd_value_t * array;
3200 int field;
3203 if ((array = malloc(steps*sizeof(double)))==NULL) {
3204 rrd_set_error("malloc VDEV_PERCENT");
3205 return -1;
3206 }
3207 for (step=0;step < steps; step++) {
3208 array[step]=data[step*src->ds_cnt];
3209 }
3210 qsort(array,step,sizeof(double),vdef_percent_compar);
3212 field = (steps-1)*dst->vf.param/100;
3213 dst->vf.val = array[field];
3214 dst->vf.when = 0; /* no time component */
3215 #if 0
3216 for(step=0;step<steps;step++)
3217 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3218 #endif
3219 }
3220 break;
3221 case VDEF_MAXIMUM:
3222 step=0;
3223 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3224 if (step == steps) {
3225 dst->vf.val = DNAN;
3226 dst->vf.when = 0;
3227 } else {
3228 dst->vf.val = data[step*src->ds_cnt];
3229 dst->vf.when = src->start + (step+1)*src->step;
3230 }
3231 while (step != steps) {
3232 if (finite(data[step*src->ds_cnt])) {
3233 if (data[step*src->ds_cnt] > dst->vf.val) {
3234 dst->vf.val = data[step*src->ds_cnt];
3235 dst->vf.when = src->start + (step+1)*src->step;
3236 }
3237 }
3238 step++;
3239 }
3240 break;
3241 case VDEF_TOTAL:
3242 case VDEF_AVERAGE: {
3243 int cnt=0;
3244 double sum=0.0;
3245 for (step=0;step<steps;step++) {
3246 if (finite(data[step*src->ds_cnt])) {
3247 sum += data[step*src->ds_cnt];
3248 cnt ++;
3249 };
3250 }
3251 if (cnt) {
3252 if (dst->vf.op == VDEF_TOTAL) {
3253 dst->vf.val = sum*src->step;
3254 dst->vf.when = cnt*src->step; /* not really "when" */
3255 } else {
3256 dst->vf.val = sum/cnt;
3257 dst->vf.when = 0; /* no time component */
3258 };
3259 } else {
3260 dst->vf.val = DNAN;
3261 dst->vf.when = 0;
3262 }
3263 }
3264 break;
3265 case VDEF_MINIMUM:
3266 step=0;
3267 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3268 if (step == steps) {
3269 dst->vf.val = DNAN;
3270 dst->vf.when = 0;
3271 } else {
3272 dst->vf.val = data[step*src->ds_cnt];
3273 dst->vf.when = src->start + (step+1)*src->step;
3274 }
3275 while (step != steps) {
3276 if (finite(data[step*src->ds_cnt])) {
3277 if (data[step*src->ds_cnt] < dst->vf.val) {
3278 dst->vf.val = data[step*src->ds_cnt];
3279 dst->vf.when = src->start + (step+1)*src->step;
3280 }
3281 }
3282 step++;
3283 }
3284 break;
3285 case VDEF_FIRST:
3286 /* The time value returned here is one step before the
3287 * actual time value. This is the start of the first
3288 * non-NaN interval.
3289 */
3290 step=0;
3291 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3292 if (step == steps) { /* all entries were NaN */
3293 dst->vf.val = DNAN;
3294 dst->vf.when = 0;
3295 } else {
3296 dst->vf.val = data[step*src->ds_cnt];
3297 dst->vf.when = src->start + step*src->step;
3298 }
3299 break;
3300 case VDEF_LAST:
3301 /* The time value returned here is the
3302 * actual time value. This is the end of the last
3303 * non-NaN interval.
3304 */
3305 step=steps-1;
3306 while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3307 if (step < 0) { /* all entries were NaN */
3308 dst->vf.val = DNAN;
3309 dst->vf.when = 0;
3310 } else {
3311 dst->vf.val = data[step*src->ds_cnt];
3312 dst->vf.when = src->start + (step+1)*src->step;
3313 }
3314 break;
3315 }
3316 return 0;
3317 }
3319 /* NaN < -INF < finite_values < INF */
3320 int
3321 vdef_percent_compar(a,b)
3322 const void *a,*b;
3323 {
3324 /* Equality is not returned; this doesn't hurt except
3325 * (maybe) for a little performance.
3326 */
3328 /* First catch NaN values. They are smallest */
3329 if (isnan( *(double *)a )) return -1;
3330 if (isnan( *(double *)b )) return 1;
3332 /* NaN doesn't reach this part so INF and -INF are extremes.
3333 * The sign from isinf() is compatible with the sign we return
3334 */
3335 if (isinf( *(double *)a )) return isinf( *(double *)a );
3336 if (isinf( *(double *)b )) return isinf( *(double *)b );
3338 /* If we reach this, both values must be finite */
3339 if ( *(double *)a < *(double *)b ) return -1; else return 1;
3340 }