5597c496b6a53cf2584a49527ba8445dcdd70e6c
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)
178 return (-1);
179 }
181 enum if_en if_conv(char *string){
183 conv_if(GIF,IF_GIF)
184 conv_if(PNG,IF_PNG)
186 return (-1);
187 }
189 enum tmt_en tmt_conv(char *string){
191 conv_if(SECOND,TMT_SECOND)
192 conv_if(MINUTE,TMT_MINUTE)
193 conv_if(HOUR,TMT_HOUR)
194 conv_if(DAY,TMT_DAY)
195 conv_if(WEEK,TMT_WEEK)
196 conv_if(MONTH,TMT_MONTH)
197 conv_if(YEAR,TMT_YEAR)
198 return (-1);
199 }
201 enum grc_en grc_conv(char *string){
203 conv_if(BACK,GRC_BACK)
204 conv_if(CANVAS,GRC_CANVAS)
205 conv_if(SHADEA,GRC_SHADEA)
206 conv_if(SHADEB,GRC_SHADEB)
207 conv_if(GRID,GRC_GRID)
208 conv_if(MGRID,GRC_MGRID)
209 conv_if(FONT,GRC_FONT)
210 conv_if(FRAME,GRC_FRAME)
211 conv_if(ARROW,GRC_ARROW)
213 return -1;
214 }
216 enum text_prop_en text_prop_conv(char *string){
218 conv_if(DEFAULT,TEXT_PROP_DEFAULT)
219 conv_if(TITLE,TEXT_PROP_TITLE)
220 conv_if(AXIS,TEXT_PROP_AXIS)
221 conv_if(UNIT,TEXT_PROP_UNIT)
222 conv_if(LEGEND,TEXT_PROP_LEGEND)
223 return -1;
224 }
227 #undef conv_if
231 int
232 im_free(image_desc_t *im)
233 {
234 long i,ii;
235 if (im == NULL) return 0;
236 for(i=0;i<im->gdes_c;i++){
237 if (im->gdes[i].data_first){
238 /* careful here, because a single pointer can occur several times */
239 free (im->gdes[i].data);
240 if (im->gdes[i].ds_namv){
241 for (ii=0;ii<im->gdes[i].ds_cnt;ii++)
242 free(im->gdes[i].ds_namv[ii]);
243 free(im->gdes[i].ds_namv);
244 }
245 }
246 free (im->gdes[i].p_data);
247 free (im->gdes[i].rpnp);
248 }
249 free(im->gdes);
250 return 0;
251 }
253 /* find SI magnitude symbol for the given number*/
254 void
255 auto_scale(
256 image_desc_t *im, /* image description */
257 double *value,
258 char **symb_ptr,
259 double *magfact
260 )
261 {
263 char *symbol[] = {"a", /* 10e-18 Atto */
264 "f", /* 10e-15 Femto */
265 "p", /* 10e-12 Pico */
266 "n", /* 10e-9 Nano */
267 "u", /* 10e-6 Micro */
268 "m", /* 10e-3 Milli */
269 " ", /* Base */
270 "k", /* 10e3 Kilo */
271 "M", /* 10e6 Mega */
272 "G", /* 10e9 Giga */
273 "T", /* 10e12 Tera */
274 "P", /* 10e15 Peta */
275 "E"};/* 10e18 Exa */
277 int symbcenter = 6;
278 int sindex;
280 if (*value == 0.0 || isnan(*value) ) {
281 sindex = 0;
282 *magfact = 1.0;
283 } else {
284 sindex = floor(log(fabs(*value))/log((double)im->base));
285 *magfact = pow((double)im->base, (double)sindex);
286 (*value) /= (*magfact);
287 }
288 if ( sindex <= symbcenter && sindex >= -symbcenter) {
289 (*symb_ptr) = symbol[sindex+symbcenter];
290 }
291 else {
292 (*symb_ptr) = "?";
293 }
294 }
297 /* find SI magnitude symbol for the numbers on the y-axis*/
298 void
299 si_unit(
300 image_desc_t *im /* image description */
301 )
302 {
304 char symbol[] = {'a', /* 10e-18 Atto */
305 'f', /* 10e-15 Femto */
306 'p', /* 10e-12 Pico */
307 'n', /* 10e-9 Nano */
308 'u', /* 10e-6 Micro */
309 'm', /* 10e-3 Milli */
310 ' ', /* Base */
311 'k', /* 10e3 Kilo */
312 'M', /* 10e6 Mega */
313 'G', /* 10e9 Giga */
314 'T', /* 10e12 Tera */
315 'P', /* 10e15 Peta */
316 'E'};/* 10e18 Exa */
318 int symbcenter = 6;
319 double digits;
321 if (im->unitsexponent != 9999) {
322 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
323 digits = floor(im->unitsexponent / 3);
324 } else {
325 digits = floor( log( max( fabs(im->minval),fabs(im->maxval)))/log((double)im->base));
326 }
327 im->magfact = pow((double)im->base , digits);
329 #ifdef DEBUG
330 printf("digits %6.3f im->magfact %6.3f\n",digits,im->magfact);
331 #endif
333 if ( ((digits+symbcenter) < sizeof(symbol)) &&
334 ((digits+symbcenter) >= 0) )
335 im->symbol = symbol[(int)digits+symbcenter];
336 else
337 im->symbol = ' ';
338 }
340 /* move min and max values around to become sensible */
342 void
343 expand_range(image_desc_t *im)
344 {
345 double sensiblevalues[] ={1000.0,900.0,800.0,750.0,700.0,
346 600.0,500.0,400.0,300.0,250.0,
347 200.0,125.0,100.0,90.0,80.0,
348 75.0,70.0,60.0,50.0,40.0,30.0,
349 25.0,20.0,10.0,9.0,8.0,
350 7.0,6.0,5.0,4.0,3.5,3.0,
351 2.5,2.0,1.8,1.5,1.2,1.0,
352 0.8,0.7,0.6,0.5,0.4,0.3,0.2,0.1,0.0,-1};
354 double scaled_min,scaled_max;
355 double adj;
356 int i;
360 #ifdef DEBUG
361 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
362 im->minval,im->maxval,im->magfact);
363 #endif
365 if (isnan(im->ygridstep)){
366 if(im->extra_flags & ALTAUTOSCALE) {
367 /* measure the amplitude of the function. Make sure that
368 graph boundaries are slightly higher then max/min vals
369 so we can see amplitude on the graph */
370 double delt, fact;
372 delt = im->maxval - im->minval;
373 adj = delt * 0.1;
374 fact = 2.0 * pow(10.0,
375 floor(log10(max(fabs(im->minval), fabs(im->maxval)))) - 2);
376 if (delt < fact) {
377 adj = (fact - delt) * 0.55;
378 #ifdef DEBUG
379 printf("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n", im->minval, im->maxval, delt, fact, adj);
380 #endif
381 }
382 im->minval -= adj;
383 im->maxval += adj;
384 }
385 else if(im->extra_flags & ALTAUTOSCALE_MAX) {
386 /* measure the amplitude of the function. Make sure that
387 graph boundaries are slightly higher than max vals
388 so we can see amplitude on the graph */
389 adj = (im->maxval - im->minval) * 0.1;
390 im->maxval += adj;
391 }
392 else {
393 scaled_min = im->minval / im->magfact;
394 scaled_max = im->maxval / im->magfact;
396 for (i=1; sensiblevalues[i] > 0; i++){
397 if (sensiblevalues[i-1]>=scaled_min &&
398 sensiblevalues[i]<=scaled_min)
399 im->minval = sensiblevalues[i]*(im->magfact);
401 if (-sensiblevalues[i-1]<=scaled_min &&
402 -sensiblevalues[i]>=scaled_min)
403 im->minval = -sensiblevalues[i-1]*(im->magfact);
405 if (sensiblevalues[i-1] >= scaled_max &&
406 sensiblevalues[i] <= scaled_max)
407 im->maxval = sensiblevalues[i-1]*(im->magfact);
409 if (-sensiblevalues[i-1]<=scaled_max &&
410 -sensiblevalues[i] >=scaled_max)
411 im->maxval = -sensiblevalues[i]*(im->magfact);
412 }
413 }
414 } else {
415 /* adjust min and max to the grid definition if there is one */
416 im->minval = (double)im->ylabfact * im->ygridstep *
417 floor(im->minval / ((double)im->ylabfact * im->ygridstep));
418 im->maxval = (double)im->ylabfact * im->ygridstep *
419 ceil(im->maxval /( (double)im->ylabfact * im->ygridstep));
420 }
422 #ifdef DEBUG
423 fprintf(stderr,"SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
424 im->minval,im->maxval,im->magfact);
425 #endif
426 }
429 /* reduce data reimplementation by Alex */
431 void
432 reduce_data(
433 enum cf_en cf, /* which consolidation function ?*/
434 unsigned long cur_step, /* step the data currently is in */
435 time_t *start, /* start, end and step as requested ... */
436 time_t *end, /* ... by the application will be ... */
437 unsigned long *step, /* ... adjusted to represent reality */
438 unsigned long *ds_cnt, /* number of data sources in file */
439 rrd_value_t **data) /* two dimensional array containing the data */
440 {
441 int i,reduce_factor = ceil((double)(*step) / (double)cur_step);
442 unsigned long col,dst_row,row_cnt,start_offset,end_offset,skiprows=0;
443 rrd_value_t *srcptr,*dstptr;
445 (*step) = cur_step*reduce_factor; /* set new step size for reduced data */
446 dstptr = *data;
447 srcptr = *data;
448 row_cnt = ((*end)-(*start))/cur_step;
450 #ifdef DEBUG
451 #define DEBUG_REDUCE
452 #endif
453 #ifdef DEBUG_REDUCE
454 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
455 row_cnt,reduce_factor,*start,*end,cur_step);
456 for (col=0;col<row_cnt;col++) {
457 printf("time %10lu: ",*start+(col+1)*cur_step);
458 for (i=0;i<*ds_cnt;i++)
459 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
460 printf("\n");
461 }
462 #endif
464 /* We have to combine [reduce_factor] rows of the source
465 ** into one row for the destination. Doing this we also
466 ** need to take care to combine the correct rows. First
467 ** alter the start and end time so that they are multiples
468 ** of the new step time. We cannot reduce the amount of
469 ** time so we have to move the end towards the future and
470 ** the start towards the past.
471 */
472 end_offset = (*end) % (*step);
473 start_offset = (*start) % (*step);
475 /* If there is a start offset (which cannot be more than
476 ** one destination row), skip the appropriate number of
477 ** source rows and one destination row. The appropriate
478 ** number is what we do know (start_offset/cur_step) of
479 ** the new interval (*step/cur_step aka reduce_factor).
480 */
481 #ifdef DEBUG_REDUCE
482 printf("start_offset: %lu end_offset: %lu\n",start_offset,end_offset);
483 printf("row_cnt before: %lu\n",row_cnt);
484 #endif
485 if (start_offset) {
486 (*start) = (*start)-start_offset;
487 skiprows=reduce_factor-start_offset/cur_step;
488 srcptr+=skiprows* *ds_cnt;
489 for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
490 row_cnt-=skiprows;
491 }
492 #ifdef DEBUG_REDUCE
493 printf("row_cnt between: %lu\n",row_cnt);
494 #endif
496 /* At the end we have some rows that are not going to be
497 ** used, the amount is end_offset/cur_step
498 */
499 if (end_offset) {
500 (*end) = (*end)-end_offset+(*step);
501 skiprows = end_offset/cur_step;
502 row_cnt-=skiprows;
503 }
504 #ifdef DEBUG_REDUCE
505 printf("row_cnt after: %lu\n",row_cnt);
506 #endif
508 /* Sanity check: row_cnt should be multiple of reduce_factor */
509 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
511 if (row_cnt%reduce_factor) {
512 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
513 row_cnt,reduce_factor);
514 printf("BUG in reduce_data()\n");
515 exit(1);
516 }
518 /* Now combine reduce_factor intervals at a time
519 ** into one interval for the destination.
520 */
522 for (dst_row=0;row_cnt>=reduce_factor;dst_row++) {
523 for (col=0;col<(*ds_cnt);col++) {
524 rrd_value_t newval=DNAN;
525 unsigned long validval=0;
527 for (i=0;i<reduce_factor;i++) {
528 if (isnan(srcptr[i*(*ds_cnt)+col])) {
529 continue;
530 }
531 validval++;
532 if (isnan(newval)) newval = srcptr[i*(*ds_cnt)+col];
533 else {
534 switch (cf) {
535 case CF_HWPREDICT:
536 case CF_DEVSEASONAL:
537 case CF_DEVPREDICT:
538 case CF_SEASONAL:
539 case CF_AVERAGE:
540 newval += srcptr[i*(*ds_cnt)+col];
541 break;
542 case CF_MINIMUM:
543 newval = min (newval,srcptr[i*(*ds_cnt)+col]);
544 break;
545 case CF_FAILURES:
546 /* an interval contains a failure if any subintervals contained a failure */
547 case CF_MAXIMUM:
548 newval = max (newval,srcptr[i*(*ds_cnt)+col]);
549 break;
550 case CF_LAST:
551 newval = srcptr[i*(*ds_cnt)+col];
552 break;
553 }
554 }
555 }
556 if (validval == 0){newval = DNAN;} else{
557 switch (cf) {
558 case CF_HWPREDICT:
559 case CF_DEVSEASONAL:
560 case CF_DEVPREDICT:
561 case CF_SEASONAL:
562 case CF_AVERAGE:
563 newval /= validval;
564 break;
565 case CF_MINIMUM:
566 case CF_FAILURES:
567 case CF_MAXIMUM:
568 case CF_LAST:
569 break;
570 }
571 }
572 *dstptr++=newval;
573 }
574 srcptr+=(*ds_cnt)*reduce_factor;
575 row_cnt-=reduce_factor;
576 }
577 /* If we had to alter the endtime, we didn't have enough
578 ** source rows to fill the last row. Fill it with NaN.
579 */
580 if (end_offset) for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
581 #ifdef DEBUG_REDUCE
582 row_cnt = ((*end)-(*start))/ *step;
583 srcptr = *data;
584 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
585 row_cnt,*start,*end,*step);
586 for (col=0;col<row_cnt;col++) {
587 printf("time %10lu: ",*start+(col+1)*(*step));
588 for (i=0;i<*ds_cnt;i++)
589 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
590 printf("\n");
591 }
592 #endif
593 }
596 /* get the data required for the graphs from the
597 relevant rrds ... */
599 int
600 data_fetch( image_desc_t *im )
601 {
602 int i,ii;
603 int skip;
604 /* pull the data from the log files ... */
605 for (i=0;i<im->gdes_c;i++){
606 /* only GF_DEF elements fetch data */
607 if (im->gdes[i].gf != GF_DEF)
608 continue;
610 skip=0;
611 /* do we have it already ?*/
612 for (ii=0;ii<i;ii++){
613 if (im->gdes[ii].gf != GF_DEF)
614 continue;
615 if((strcmp(im->gdes[i].rrd,im->gdes[ii].rrd) == 0)
616 && (im->gdes[i].cf == im->gdes[ii].cf)){
617 /* OK the data it is here already ...
618 * we just copy the header portion */
619 im->gdes[i].start = im->gdes[ii].start;
620 im->gdes[i].end = im->gdes[ii].end;
621 im->gdes[i].step = im->gdes[ii].step;
622 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
623 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
624 im->gdes[i].data = im->gdes[ii].data;
625 im->gdes[i].data_first = 0;
626 skip=1;
627 }
628 if (skip)
629 break;
630 }
631 if (! skip) {
632 unsigned long ft_step = im->gdes[i].step ;
634 if((rrd_fetch_fn(im->gdes[i].rrd,
635 im->gdes[i].cf,
636 &im->gdes[i].start,
637 &im->gdes[i].end,
638 &ft_step,
639 &im->gdes[i].ds_cnt,
640 &im->gdes[i].ds_namv,
641 &im->gdes[i].data)) == -1){
642 return -1;
643 }
644 im->gdes[i].data_first = 1;
646 if (ft_step < im->gdes[i].step) {
647 reduce_data(im->gdes[i].cf,
648 ft_step,
649 &im->gdes[i].start,
650 &im->gdes[i].end,
651 &im->gdes[i].step,
652 &im->gdes[i].ds_cnt,
653 &im->gdes[i].data);
654 } else {
655 im->gdes[i].step = ft_step;
656 }
657 }
659 /* lets see if the required data source is realy there */
660 for(ii=0;ii<im->gdes[i].ds_cnt;ii++){
661 if(strcmp(im->gdes[i].ds_namv[ii],im->gdes[i].ds_nam) == 0){
662 im->gdes[i].ds=ii; }
663 }
664 if (im->gdes[i].ds== -1){
665 rrd_set_error("No DS called '%s' in '%s'",
666 im->gdes[i].ds_nam,im->gdes[i].rrd);
667 return -1;
668 }
670 }
671 return 0;
672 }
674 /* evaluate the expressions in the CDEF functions */
676 /*************************************************************
677 * CDEF stuff
678 *************************************************************/
680 long
681 find_var_wrapper(void *arg1, char *key)
682 {
683 return find_var((image_desc_t *) arg1, key);
684 }
686 /* find gdes containing var*/
687 long
688 find_var(image_desc_t *im, char *key){
689 long ii;
690 for(ii=0;ii<im->gdes_c-1;ii++){
691 if((im->gdes[ii].gf == GF_DEF
692 || im->gdes[ii].gf == GF_VDEF
693 || im->gdes[ii].gf == GF_CDEF)
694 && (strcmp(im->gdes[ii].vname,key) == 0)){
695 return ii;
696 }
697 }
698 return -1;
699 }
701 /* find the largest common denominator for all the numbers
702 in the 0 terminated num array */
703 long
704 lcd(long *num){
705 long rest;
706 int i;
707 for (i=0;num[i+1]!=0;i++){
708 do {
709 rest=num[i] % num[i+1];
710 num[i]=num[i+1]; num[i+1]=rest;
711 } while (rest!=0);
712 num[i+1] = num[i];
713 }
714 /* return i==0?num[i]:num[i-1]; */
715 return num[i];
716 }
718 /* run the rpn calculator on all the VDEF and CDEF arguments */
719 int
720 data_calc( image_desc_t *im){
722 int gdi;
723 int dataidx;
724 long *steparray, rpi;
725 int stepcnt;
726 time_t now;
727 rpnstack_t rpnstack;
729 rpnstack_init(&rpnstack);
731 for (gdi=0;gdi<im->gdes_c;gdi++){
732 /* Look for GF_VDEF and GF_CDEF in the same loop,
733 * so CDEFs can use VDEFs and vice versa
734 */
735 switch (im->gdes[gdi].gf) {
736 case GF_VDEF:
737 /* A VDEF has no DS. This also signals other parts
738 * of rrdtool that this is a VDEF value, not a CDEF.
739 */
740 im->gdes[gdi].ds_cnt = 0;
741 if (vdef_calc(im,gdi)) {
742 rrd_set_error("Error processing VDEF '%s'"
743 ,im->gdes[gdi].vname
744 );
745 rpnstack_free(&rpnstack);
746 return -1;
747 }
748 break;
749 case GF_CDEF:
750 im->gdes[gdi].ds_cnt = 1;
751 im->gdes[gdi].ds = 0;
752 im->gdes[gdi].data_first = 1;
753 im->gdes[gdi].start = 0;
754 im->gdes[gdi].end = 0;
755 steparray=NULL;
756 stepcnt = 0;
757 dataidx=-1;
759 /* Find the variables in the expression.
760 * - VDEF variables are substituted by their values
761 * and the opcode is changed into OP_NUMBER.
762 * - CDEF variables are analized for their step size,
763 * the lowest common denominator of all the step
764 * sizes of the data sources involved is calculated
765 * and the resulting number is the step size for the
766 * resulting data source.
767 */
768 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
769 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE){
770 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
771 if (im->gdes[ptr].ds_cnt == 0) {
772 #if 0
773 printf("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
774 im->gdes[gdi].vname,
775 im->gdes[ptr].vname);
776 printf("DEBUG: value from vdef is %f\n",im->gdes[ptr].vf.val);
777 #endif
778 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
779 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
780 } else {
781 if ((steparray = rrd_realloc(steparray, (++stepcnt+1)*sizeof(*steparray)))==NULL){
782 rrd_set_error("realloc steparray");
783 rpnstack_free(&rpnstack);
784 return -1;
785 };
787 steparray[stepcnt-1] = im->gdes[ptr].step;
789 /* adjust start and end of cdef (gdi) so
790 * that it runs from the latest start point
791 * to the earliest endpoint of any of the
792 * rras involved (ptr)
793 */
794 if(im->gdes[gdi].start < im->gdes[ptr].start)
795 im->gdes[gdi].start = im->gdes[ptr].start;
797 if(im->gdes[gdi].end == 0 ||
798 im->gdes[gdi].end > im->gdes[ptr].end)
799 im->gdes[gdi].end = im->gdes[ptr].end;
801 /* store pointer to the first element of
802 * the rra providing data for variable,
803 * further save step size and data source
804 * count of this rra
805 */
806 im->gdes[gdi].rpnp[rpi].data = im->gdes[ptr].data + im->gdes[ptr].ds;
807 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
808 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
810 /* backoff the *.data ptr; this is done so
811 * rpncalc() function doesn't have to treat
812 * the first case differently
813 */
814 } /* if ds_cnt != 0 */
815 } /* if OP_VARIABLE */
816 } /* loop through all rpi */
818 if(steparray == NULL){
819 rrd_set_error("rpn expressions without DEF"
820 " or CDEF variables are not supported");
821 rpnstack_free(&rpnstack);
822 return -1;
823 }
824 steparray[stepcnt]=0;
825 /* Now find the resulting step. All steps in all
826 * used RRAs have to be visited
827 */
828 im->gdes[gdi].step = lcd(steparray);
829 free(steparray);
830 if((im->gdes[gdi].data = malloc((
831 (im->gdes[gdi].end-im->gdes[gdi].start)
832 / im->gdes[gdi].step)
833 * sizeof(double)))==NULL){
834 rrd_set_error("malloc im->gdes[gdi].data");
835 rpnstack_free(&rpnstack);
836 return -1;
837 }
839 /* Step through the new cdef results array and
840 * calculate the values
841 */
842 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
843 now<=im->gdes[gdi].end;
844 now += im->gdes[gdi].step)
845 {
846 rpnp_t *rpnp = im -> gdes[gdi].rpnp;
848 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
849 * in this case we are advancing by timesteps;
850 * we use the fact that time_t is a synonym for long
851 */
852 if (rpn_calc(rpnp,&rpnstack,(long) now,
853 im->gdes[gdi].data,++dataidx) == -1) {
854 /* rpn_calc sets the error string */
855 rpnstack_free(&rpnstack);
856 return -1;
857 }
858 } /* enumerate over time steps within a CDEF */
859 break;
860 default:
861 continue;
862 }
863 } /* enumerate over CDEFs */
864 rpnstack_free(&rpnstack);
865 return 0;
866 }
868 /* massage data so, that we get one value for each x coordinate in the graph */
869 int
870 data_proc( image_desc_t *im ){
871 long i,ii;
872 double pixstep = (double)(im->end-im->start)
873 /(double)im->xsize; /* how much time
874 passes in one pixel */
875 double paintval;
876 double minval=DNAN,maxval=DNAN;
878 unsigned long gr_time;
880 /* memory for the processed data */
881 for(i=0;i<im->gdes_c;i++){
882 if((im->gdes[i].gf==GF_LINE) ||
883 (im->gdes[i].gf==GF_AREA) ||
884 (im->gdes[i].gf==GF_TICK) ||
885 (im->gdes[i].gf==GF_STACK)){
886 if((im->gdes[i].p_data = malloc((im->xsize +1)
887 * sizeof(rrd_value_t)))==NULL){
888 rrd_set_error("malloc data_proc");
889 return -1;
890 }
891 }
892 }
894 for(i=0;i<im->xsize;i++){
895 long vidx;
896 gr_time = im->start+pixstep*i; /* time of the
897 current step */
898 paintval=0.0;
900 for(ii=0;ii<im->gdes_c;ii++){
901 double value;
902 switch(im->gdes[ii].gf){
903 case GF_LINE:
904 case GF_AREA:
905 case GF_TICK:
906 paintval = 0.0;
907 case GF_STACK:
908 vidx = im->gdes[ii].vidx;
910 value =
911 im->gdes[vidx].data[
912 ((unsigned long)floor(
913 (double)(gr_time-im->gdes[vidx].start) / im->gdes[vidx].step
914 )
915 ) *im->gdes[vidx].ds_cnt
916 +im->gdes[vidx].ds];
918 if (! isnan(value)) {
919 paintval += value;
920 im->gdes[ii].p_data[i] = paintval;
921 /* GF_TICK: the data values are not relevant for min and max */
922 if (finite(paintval) && im->gdes[ii].gf != GF_TICK ){
923 if (isnan(minval) || paintval < minval)
924 minval = paintval;
925 if (isnan(maxval) || paintval > maxval)
926 maxval = paintval;
927 }
928 } else {
929 im->gdes[ii].p_data[i] = DNAN;
930 }
931 break;
932 case GF_PRINT:
933 case GF_GPRINT:
934 case GF_COMMENT:
935 case GF_HRULE:
936 case GF_VRULE:
937 case GF_DEF:
938 case GF_CDEF:
939 case GF_VDEF:
940 break;
941 }
942 }
943 }
945 /* if min or max have not been asigned a value this is because
946 there was no data in the graph ... this is not good ...
947 lets set these to dummy values then ... */
949 if (isnan(minval)) minval = 0.0;
950 if (isnan(maxval)) maxval = 1.0;
952 /* adjust min and max values */
953 if (isnan(im->minval)
954 || ((!im->logarithmic && !im->rigid) /* don't adjust low-end with log scale */
955 && im->minval > minval))
956 im->minval = minval;
957 if (isnan(im->maxval)
958 || (!im->rigid
959 && im->maxval < maxval)){
960 if (im->logarithmic)
961 im->maxval = maxval * 1.1;
962 else
963 im->maxval = maxval;
964 }
965 /* make sure min and max are not equal */
966 if (im->minval == im->maxval) {
967 im->maxval *= 1.01;
968 if (! im->logarithmic) {
969 im->minval *= 0.99;
970 }
972 /* make sure min and max are not both zero */
973 if (im->maxval == 0.0) {
974 im->maxval = 1.0;
975 }
977 }
978 return 0;
979 }
983 /* identify the point where the first gridline, label ... gets placed */
985 time_t
986 find_first_time(
987 time_t start, /* what is the initial time */
988 enum tmt_en baseint, /* what is the basic interval */
989 long basestep /* how many if these do we jump a time */
990 )
991 {
992 struct tm tm;
993 tm = *localtime(&start);
994 switch(baseint){
995 case TMT_SECOND:
996 tm.tm_sec -= tm.tm_sec % basestep; break;
997 case TMT_MINUTE:
998 tm.tm_sec=0;
999 tm.tm_min -= tm.tm_min % basestep;
1000 break;
1001 case TMT_HOUR:
1002 tm.tm_sec=0;
1003 tm.tm_min = 0;
1004 tm.tm_hour -= tm.tm_hour % basestep; break;
1005 case TMT_DAY:
1006 /* we do NOT look at the basestep for this ... */
1007 tm.tm_sec=0;
1008 tm.tm_min = 0;
1009 tm.tm_hour = 0; break;
1010 case TMT_WEEK:
1011 /* we do NOT look at the basestep for this ... */
1012 tm.tm_sec=0;
1013 tm.tm_min = 0;
1014 tm.tm_hour = 0;
1015 tm.tm_mday -= tm.tm_wday -1; /* -1 because we want the monday */
1016 if (tm.tm_wday==0) tm.tm_mday -= 7; /* we want the *previous* monday */
1017 break;
1018 case TMT_MONTH:
1019 tm.tm_sec=0;
1020 tm.tm_min = 0;
1021 tm.tm_hour = 0;
1022 tm.tm_mday = 1;
1023 tm.tm_mon -= tm.tm_mon % basestep; break;
1025 case TMT_YEAR:
1026 tm.tm_sec=0;
1027 tm.tm_min = 0;
1028 tm.tm_hour = 0;
1029 tm.tm_mday = 1;
1030 tm.tm_mon = 0;
1031 tm.tm_year -= (tm.tm_year+1900) % basestep;
1033 }
1034 return mktime(&tm);
1035 }
1036 /* identify the point where the next gridline, label ... gets placed */
1037 time_t
1038 find_next_time(
1039 time_t current, /* what is the initial time */
1040 enum tmt_en baseint, /* what is the basic interval */
1041 long basestep /* how many if these do we jump a time */
1042 )
1043 {
1044 struct tm tm;
1045 time_t madetime;
1046 tm = *localtime(¤t);
1047 do {
1048 switch(baseint){
1049 case TMT_SECOND:
1050 tm.tm_sec += basestep; break;
1051 case TMT_MINUTE:
1052 tm.tm_min += basestep; break;
1053 case TMT_HOUR:
1054 tm.tm_hour += basestep; break;
1055 case TMT_DAY:
1056 tm.tm_mday += basestep; break;
1057 case TMT_WEEK:
1058 tm.tm_mday += 7*basestep; break;
1059 case TMT_MONTH:
1060 tm.tm_mon += basestep; break;
1061 case TMT_YEAR:
1062 tm.tm_year += basestep;
1063 }
1064 madetime = mktime(&tm);
1065 } while (madetime == -1); /* this is necessary to skip impssible times
1066 like the daylight saving time skips */
1067 return madetime;
1069 }
1072 /* calculate values required for PRINT and GPRINT functions */
1074 int
1075 print_calc(image_desc_t *im, char ***prdata)
1076 {
1077 long i,ii,validsteps;
1078 double printval;
1079 time_t printtime;
1080 int graphelement = 0;
1081 long vidx;
1082 int max_ii;
1083 double magfact = -1;
1084 char *si_symb = "";
1085 char *percent_s;
1086 int prlines = 1;
1087 if (im->imginfo) prlines++;
1088 for(i=0;i<im->gdes_c;i++){
1089 switch(im->gdes[i].gf){
1090 case GF_PRINT:
1091 prlines++;
1092 if(((*prdata) = rrd_realloc((*prdata),prlines*sizeof(char *)))==NULL){
1093 rrd_set_error("realloc prdata");
1094 return 0;
1095 }
1096 case GF_GPRINT:
1097 /* PRINT and GPRINT can now print VDEF generated values.
1098 * There's no need to do any calculations on them as these
1099 * calculations were already made.
1100 */
1101 vidx = im->gdes[i].vidx;
1102 if (im->gdes[vidx].gf==GF_VDEF) { /* simply use vals */
1103 printval = im->gdes[vidx].vf.val;
1104 printtime = im->gdes[vidx].vf.when;
1105 } else { /* need to calculate max,min,avg etcetera */
1106 max_ii =((im->gdes[vidx].end
1107 - im->gdes[vidx].start)
1108 / im->gdes[vidx].step
1109 * im->gdes[vidx].ds_cnt);
1110 printval = DNAN;
1111 validsteps = 0;
1112 for( ii=im->gdes[vidx].ds;
1113 ii < max_ii;
1114 ii+=im->gdes[vidx].ds_cnt){
1115 if (! finite(im->gdes[vidx].data[ii]))
1116 continue;
1117 if (isnan(printval)){
1118 printval = im->gdes[vidx].data[ii];
1119 validsteps++;
1120 continue;
1121 }
1123 switch (im->gdes[i].cf){
1124 case CF_HWPREDICT:
1125 case CF_DEVPREDICT:
1126 case CF_DEVSEASONAL:
1127 case CF_SEASONAL:
1128 case CF_AVERAGE:
1129 validsteps++;
1130 printval += im->gdes[vidx].data[ii];
1131 break;
1132 case CF_MINIMUM:
1133 printval = min( printval, im->gdes[vidx].data[ii]);
1134 break;
1135 case CF_FAILURES:
1136 case CF_MAXIMUM:
1137 printval = max( printval, im->gdes[vidx].data[ii]);
1138 break;
1139 case CF_LAST:
1140 printval = im->gdes[vidx].data[ii];
1141 }
1142 }
1143 if (im->gdes[i].cf==CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1144 if (validsteps > 1) {
1145 printval = (printval / validsteps);
1146 }
1147 }
1148 } /* prepare printval */
1150 if (!strcmp(im->gdes[i].format,"%c")) { /* VDEF time print */
1151 if (im->gdes[i].gf == GF_PRINT){
1152 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1153 sprintf((*prdata)[prlines-2],"%s (%lu)",
1154 ctime(&printtime),printtime);
1155 (*prdata)[prlines-1] = NULL;
1156 } else {
1157 sprintf(im->gdes[i].legend,"%s (%lu)",
1158 ctime(&printtime),printtime);
1159 graphelement = 1;
1160 }
1161 } else {
1162 if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) {
1163 /* Magfact is set to -1 upon entry to print_calc. If it
1164 * is still less than 0, then we need to run auto_scale.
1165 * Otherwise, put the value into the correct units. If
1166 * the value is 0, then do not set the symbol or magnification
1167 * so next the calculation will be performed again. */
1168 if (magfact < 0.0) {
1169 auto_scale(im,&printval,&si_symb,&magfact);
1170 if (printval == 0.0)
1171 magfact = -1.0;
1172 } else {
1173 printval /= magfact;
1174 }
1175 *(++percent_s) = 's';
1176 } else if (strstr(im->gdes[i].format,"%s") != NULL) {
1177 auto_scale(im,&printval,&si_symb,&magfact);
1178 }
1180 if (im->gdes[i].gf == GF_PRINT){
1181 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1182 if (bad_format(im->gdes[i].format)) {
1183 rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1184 return -1;
1185 }
1186 #ifdef HAVE_SNPRINTF
1187 snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
1188 #else
1189 sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
1190 #endif
1191 (*prdata)[prlines-1] = NULL;
1192 } else {
1193 /* GF_GPRINT */
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(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
1201 #else
1202 sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
1203 #endif
1204 graphelement = 1;
1205 }
1206 }
1207 break;
1208 case GF_COMMENT:
1209 case GF_LINE:
1210 case GF_AREA:
1211 case GF_TICK:
1212 case GF_STACK:
1213 case GF_HRULE:
1214 case GF_VRULE:
1215 graphelement = 1;
1216 break;
1217 case GF_DEF:
1218 case GF_CDEF:
1219 case GF_VDEF:
1220 break;
1221 }
1222 }
1223 return graphelement;
1224 }
1227 /* place legends with color spots */
1228 int
1229 leg_place(image_desc_t *im)
1230 {
1231 /* graph labels */
1232 int interleg = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1233 int box =im->text_prop[TEXT_PROP_LEGEND].size*1.5;
1234 int border = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1235 int fill=0, fill_last;
1236 int leg_c = 0;
1237 int leg_x = border, leg_y = im->ygif;
1238 int leg_cc;
1239 int glue = 0;
1240 int i,ii, mark = 0;
1241 char prt_fctn; /*special printfunctions */
1242 int *legspace;
1244 if( !(im->extra_flags & NOLEGEND) ) {
1245 if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1246 rrd_set_error("malloc for legspace");
1247 return -1;
1248 }
1250 for(i=0;i<im->gdes_c;i++){
1251 fill_last = fill;
1253 leg_cc = strlen(im->gdes[i].legend);
1255 /* is there a controle code ant the end of the legend string ? */
1256 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\') {
1257 prt_fctn = im->gdes[i].legend[leg_cc-1];
1258 leg_cc -= 2;
1259 im->gdes[i].legend[leg_cc] = '\0';
1260 } else {
1261 prt_fctn = '\0';
1262 }
1263 /* remove exess space */
1264 while (prt_fctn=='g' &&
1265 leg_cc > 0 &&
1266 im->gdes[i].legend[leg_cc-1]==' '){
1267 leg_cc--;
1268 im->gdes[i].legend[leg_cc]='\0';
1269 }
1270 if (leg_cc != 0 ){
1271 legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1273 if (fill > 0){
1274 /* no interleg space if string ends in \g */
1275 fill += legspace[i];
1276 }
1277 if (im->gdes[i].gf != GF_GPRINT &&
1278 im->gdes[i].gf != GF_COMMENT) {
1279 fill += box;
1280 }
1281 fill += gfx_get_text_width(fill+border,im->text_prop[TEXT_PROP_LEGEND].font,
1282 im->text_prop[TEXT_PROP_LEGEND].size,
1283 im->tabwidth,
1284 im->gdes[i].legend);
1285 leg_c++;
1286 } else {
1287 legspace[i]=0;
1288 }
1289 /* who said there was a special tag ... ?*/
1290 if (prt_fctn=='g') {
1291 prt_fctn = '\0';
1292 }
1293 if (prt_fctn == '\0') {
1294 if (i == im->gdes_c -1 ) prt_fctn ='l';
1296 /* is it time to place the legends ? */
1297 if (fill > im->xgif - 2*border){
1298 if (leg_c > 1) {
1299 /* go back one */
1300 i--;
1301 fill = fill_last;
1302 leg_c--;
1303 prt_fctn = 'j';
1304 } else {
1305 prt_fctn = 'l';
1306 }
1308 }
1309 }
1312 if (prt_fctn != '\0'){
1313 leg_x = border;
1314 if (leg_c >= 2 && prt_fctn == 'j') {
1315 glue = (im->xgif - fill - 2* border) / (leg_c-1);
1316 } else {
1317 glue = 0;
1318 }
1319 if (prt_fctn =='c') leg_x = (im->xgif - fill) / 2.0;
1320 if (prt_fctn =='r') leg_x = im->xgif - fill - border;
1322 for(ii=mark;ii<=i;ii++){
1323 if(im->gdes[ii].legend[0]=='\0')
1324 continue;
1325 im->gdes[ii].leg_x = leg_x;
1326 im->gdes[ii].leg_y = leg_y;
1327 printf("DEBUG: using font %s with width %lf\n",
1328 im->text_prop[TEXT_PROP_LEGEND].font,
1329 im->text_prop[TEXT_PROP_LEGEND].size);
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, 0,0, im->xgif, 0,im->graph_col[GRC_SHADEA]);
1717 gfx_add_point( node , im->xgif - 2, 2 );
1718 gfx_add_point( node , 2,2 );
1719 gfx_add_point( node , 2,im->ygif-2 );
1720 gfx_add_point( node , 0,im->ygif );
1722 node = gfx_new_area (canvas, 0,im->ygif, im->xgif,im->ygif, im->xgif,0,im->graph_col[GRC_SHADEB]);
1723 gfx_add_point( node , im->xgif - 2, 2 );
1724 gfx_add_point( node , im->xgif-2,im->ygif-2 );
1725 gfx_add_point( node , 2,im->ygif-2 );
1726 gfx_add_point( node , 0,im->ygif );
1729 if (im->draw_x_grid == 1 )
1730 vertical_grid(canvas, im);
1732 if (im->draw_y_grid == 1){
1733 if(im->logarithmic){
1734 res = horizontal_log_grid(canvas,im);
1735 } else {
1736 res = horizontal_grid(canvas,im);
1737 }
1739 /* dont draw horizontal grid if there is no min and max val */
1740 if (! res ) {
1741 char *nodata = "No Data found";
1742 gfx_new_text(canvas,im->xgif/2, (2*im->yorigin-im->ysize) / 2,
1743 im->graph_col[GRC_FONT],
1744 im->text_prop[TEXT_PROP_AXIS].font,
1745 im->text_prop[TEXT_PROP_AXIS].size,
1746 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
1747 nodata );
1748 }
1749 }
1751 /* yaxis description */
1752 gfx_new_text( canvas,
1753 7, (im->yorigin - im->ysize/2),
1754 im->graph_col[GRC_FONT],
1755 im->text_prop[TEXT_PROP_AXIS].font,
1756 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 270.0,
1757 GFX_H_CENTER, GFX_V_CENTER,
1758 im->ylegend);
1760 /* graph title */
1761 gfx_new_text( canvas,
1762 im->xgif/2, im->text_prop[TEXT_PROP_TITLE].size*1.5,
1763 im->graph_col[GRC_FONT],
1764 im->text_prop[TEXT_PROP_TITLE].font,
1765 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
1766 GFX_H_CENTER, GFX_V_CENTER,
1767 im->title);
1769 /* graph labels */
1770 if( !(im->extra_flags & NOLEGEND) ) {
1771 for(i=0;i<im->gdes_c;i++){
1772 if(im->gdes[i].legend[0] =='\0')
1773 continue;
1775 if(im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT){
1776 x0 = im->gdes[i].leg_x;
1777 y0 = im->gdes[i].leg_y+1.0;
1778 x1 = x0+boxH;
1779 x2 = x0+boxH;
1780 x3 = x0;
1781 y1 = y0;
1782 y2 = y0+boxV;
1783 y3 = y0+boxV;
1784 node = gfx_new_area(canvas, x0,y0,x1,y1,x2,y2 ,im->gdes[i].col);
1785 gfx_add_point ( node, x3, y3 );
1786 gfx_add_point ( node, x0, y0 );
1787 node = gfx_new_line(canvas, x0,y0,x1,y1 ,GRIDWIDTH, im->graph_col[GRC_FRAME]);
1788 gfx_add_point ( node, x2, y2 );
1789 gfx_add_point ( node, x3, y3 );
1790 gfx_add_point ( node, x0, y0 );
1792 gfx_new_text ( canvas, x0+boxH+6, (y0+y2) / 2.0,
1793 im->graph_col[GRC_FONT],
1794 im->text_prop[TEXT_PROP_AXIS].font,
1795 im->text_prop[TEXT_PROP_AXIS].size,
1796 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
1797 im->gdes[i].legend );
1799 } else {
1800 x0 = im->gdes[i].leg_x;
1801 y0 = im->gdes[i].leg_y;
1803 gfx_new_text ( canvas, x0, (y0+y2) / 2.0,
1804 im->graph_col[GRC_FONT],
1805 im->text_prop[TEXT_PROP_AXIS].font,
1806 im->text_prop[TEXT_PROP_AXIS].size,
1807 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
1808 im->gdes[i].legend );
1810 }
1811 }
1812 }
1813 }
1816 /*****************************************************
1817 * lazy check make sure we rely need to create this graph
1818 *****************************************************/
1820 int lazy_check(image_desc_t *im){
1821 FILE *fd = NULL;
1822 int size = 1;
1823 struct stat gifstat;
1825 if (im->lazy == 0) return 0; /* no lazy option */
1826 if (stat(im->graphfile,&gifstat) != 0)
1827 return 0; /* can't stat */
1828 /* one pixel in the existing graph is more then what we would
1829 change here ... */
1830 if (time(NULL) - gifstat.st_mtime >
1831 (im->end - im->start) / im->xsize)
1832 return 0;
1833 if ((fd = fopen(im->graphfile,"rb")) == NULL)
1834 return 0; /* the file does not exist */
1835 switch (im->imgformat) {
1836 case IF_GIF:
1837 size = GifSize(fd,&(im->xgif),&(im->ygif));
1838 break;
1839 case IF_PNG:
1840 size = PngSize(fd,&(im->xgif),&(im->ygif));
1841 break;
1842 }
1843 fclose(fd);
1844 return size;
1845 }
1848 /* draw that picture thing ... */
1849 int
1850 graph_paint(image_desc_t *im, char ***calcpr)
1851 {
1852 int i,ii;
1853 int lazy = lazy_check(im);
1854 FILE *fo;
1855 gfx_canvas_t *canvas;
1856 gfx_node_t *node;
1858 double areazero = 0.0;
1859 enum gf_en stack_gf = GF_PRINT;
1860 graph_desc_t *lastgdes = NULL;
1862 /* if we are lazy and there is nothing to PRINT ... quit now */
1863 if (lazy && im->prt_c==0) return 0;
1865 /* pull the data from the rrd files ... */
1867 if(data_fetch(im)==-1)
1868 return -1;
1870 /* evaluate VDEF and CDEF operations ... */
1871 if(data_calc(im)==-1)
1872 return -1;
1874 /* calculate and PRINT and GPRINT definitions. We have to do it at
1875 * this point because it will affect the length of the legends
1876 * if there are no graph elements we stop here ...
1877 * if we are lazy, try to quit ...
1878 */
1879 i=print_calc(im,calcpr);
1880 if(i<0) return -1;
1881 if(i==0 || lazy) return 0;
1883 /* get actual drawing data and find min and max values*/
1884 if(data_proc(im)==-1)
1885 return -1;
1887 if(!im->logarithmic){si_unit(im);} /* identify si magnitude Kilo, Mega Giga ? */
1889 if(!im->rigid && ! im->logarithmic)
1890 expand_range(im); /* make sure the upper and lower limit are
1891 sensible values */
1893 /* init xtr and ytr */
1894 /* determine the actual size of the gif to draw. The size given
1895 on the cmdline is the graph area. But we need more as we have
1896 draw labels and other things outside the graph area */
1899 im->xorigin = 10 + 9 * im->text_prop[TEXT_PROP_LEGEND].size;
1901 xtr(im,0);
1903 im->yorigin = 10 + im->ysize;
1905 ytr(im,DNAN);
1907 if(im->title[0] != '\0')
1908 im->yorigin += im->text_prop[TEXT_PROP_TITLE].size*3+4;
1910 im->xgif=20+im->xsize + im->xorigin;
1911 im->ygif= im->yorigin+2* im->text_prop[TEXT_PROP_LEGEND].size;
1913 /* determine where to place the legends onto the graphics.
1914 and set im->ygif to match space requirements for text */
1915 if(leg_place(im)==-1)
1916 return -1;
1918 canvas=gfx_new_canvas();
1920 /* the actual graph is created by going through the individual
1921 graph elements and then drawing them */
1923 node=gfx_new_area ( canvas,
1924 0, 0,
1925 im->xgif, 0,
1926 im->xgif, im->ygif,
1927 im->graph_col[GRC_BACK]);
1929 gfx_add_point(node,0, im->ygif);
1931 node=gfx_new_area ( canvas,
1932 im->xorigin, im->yorigin,
1933 im->xorigin + im->xsize, im->yorigin,
1934 im->xorigin + im->xsize, im->yorigin-im->ysize,
1935 im->graph_col[GRC_CANVAS]);
1937 gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
1940 if (im->minval > 0.0)
1941 areazero = im->minval;
1942 if (im->maxval < 0.0)
1943 areazero = im->maxval;
1945 axis_paint(im,canvas);
1948 for(i=0;i<im->gdes_c;i++){
1949 switch(im->gdes[i].gf){
1950 case GF_CDEF:
1951 case GF_VDEF:
1952 case GF_DEF:
1953 case GF_PRINT:
1954 case GF_GPRINT:
1955 case GF_COMMENT:
1956 case GF_HRULE:
1957 case GF_VRULE:
1958 break;
1959 case GF_TICK:
1960 for (ii = 0; ii < im->xsize; ii++)
1961 {
1962 if (!isnan(im->gdes[i].p_data[ii]) &&
1963 im->gdes[i].p_data[ii] > 0.0)
1964 {
1965 /* generate a tick */
1966 gfx_new_line(canvas, im -> xorigin + ii,
1967 im -> yorigin - (im -> gdes[i].yrule * im -> ysize),
1968 im -> xorigin + ii,
1969 im -> yorigin,
1970 1.0,
1971 im -> gdes[i].col );
1972 }
1973 }
1974 break;
1975 case GF_LINE:
1976 case GF_AREA:
1977 stack_gf = im->gdes[i].gf;
1978 case GF_STACK:
1979 /* fix data points at oo and -oo */
1980 for(ii=0;ii<im->xsize;ii++){
1981 if (isinf(im->gdes[i].p_data[ii])){
1982 if (im->gdes[i].p_data[ii] > 0) {
1983 im->gdes[i].p_data[ii] = im->maxval ;
1984 } else {
1985 im->gdes[i].p_data[ii] = im->minval ;
1986 }
1988 }
1989 } /* for */
1991 if (im->gdes[i].col != 0x0){
1992 /* GF_LINE and friend */
1993 if(stack_gf == GF_LINE ){
1994 node = NULL;
1995 for(ii=1;ii<im->xsize;ii++){
1996 if ( ! isnan(im->gdes[i].p_data[ii-1])
1997 && ! isnan(im->gdes[i].p_data[ii])){
1998 if (node == NULL){
1999 node = gfx_new_line(canvas,
2000 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2001 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2002 im->gdes[i].linewidth,
2003 im->gdes[i].col);
2004 } else {
2005 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2006 }
2007 } else {
2008 node = NULL;
2009 }
2010 }
2011 } else {
2012 int area_start=-1;
2013 node = NULL;
2014 for(ii=1;ii<im->xsize;ii++){
2015 /* open an area */
2016 if ( ! isnan(im->gdes[i].p_data[ii-1])
2017 && ! isnan(im->gdes[i].p_data[ii])){
2018 if (node == NULL){
2019 float ybase = 0.0;
2020 if (im->gdes[i].gf == GF_STACK) {
2021 ybase = ytr(im,lastgdes->p_data[ii-1]);
2022 } else {
2023 ybase = ytr(im,areazero);
2024 }
2025 area_start = ii-1;
2026 node = gfx_new_area(canvas,
2027 ii-1+im->xorigin,ybase,
2028 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2029 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2030 im->gdes[i].col
2031 );
2032 } else {
2033 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2034 }
2035 }
2037 if ( node != NULL && (ii+1==im->xsize || isnan(im->gdes[i].p_data[ii]) )){
2038 /* GF_AREA STACK type*/
2039 if (im->gdes[i].gf == GF_STACK ) {
2040 int iii;
2041 for (iii=ii-1;iii>area_start;iii--){
2042 gfx_add_point(node,iii+im->xorigin,ytr(im,lastgdes->p_data[iii]));
2043 }
2044 } else {
2045 gfx_add_point(node,ii+im->xorigin,ytr(im,areazero));
2046 };
2047 node=NULL;
2048 };
2049 }
2050 } /* else GF_LINE */
2051 } /* if color != 0x0 */
2052 /* make sure we do not run into trouble when stacking on NaN */
2053 for(ii=0;ii<im->xsize;ii++){
2054 if (isnan(im->gdes[i].p_data[ii])) {
2055 double ybase = 0.0;
2056 if (lastgdes) {
2057 ybase = ytr(im,lastgdes->p_data[ii-1]);
2058 };
2059 if (isnan(ybase) || !lastgdes ){
2060 ybase = ytr(im,areazero);
2061 }
2062 im->gdes[i].p_data[ii] = ybase;
2063 }
2064 }
2065 lastgdes = &(im->gdes[i]);
2066 break;
2067 } /* switch */
2068 }
2069 grid_paint(im,canvas);
2071 /* the RULES are the last thing to paint ... */
2072 for(i=0;i<im->gdes_c;i++){
2074 switch(im->gdes[i].gf){
2075 case GF_HRULE:
2076 printf("DEBUG: HRULE at %f\n",im->gdes[i].yrule);
2077 if(isnan(im->gdes[i].yrule)) { /* fetch variable */
2078 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2079 };
2080 if(im->gdes[i].yrule >= im->minval
2081 && im->gdes[i].yrule <= im->maxval)
2082 gfx_new_line(canvas,
2083 im->xorigin,ytr(im,im->gdes[i].yrule),
2084 im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2085 1.0,im->gdes[i].col);
2086 break;
2087 case GF_VRULE:
2088 if(im->gdes[i].xrule == 0) { /* fetch variable */
2089 im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
2090 };
2091 if(im->gdes[i].xrule >= im->start
2092 && im->gdes[i].xrule <= im->end)
2093 gfx_new_line(canvas,
2094 xtr(im,im->gdes[i].xrule),im->yorigin,
2095 xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2096 1.0,im->gdes[i].col);
2097 break;
2098 default:
2099 break;
2100 }
2101 }
2104 if (strcmp(im->graphfile,"-")==0) {
2105 #ifdef WIN32
2106 /* Change translation mode for stdout to BINARY */
2107 _setmode( _fileno( stdout ), O_BINARY );
2108 #endif
2109 fo = stdout;
2110 } else {
2111 if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2112 rrd_set_error("Opening '%s' for write: %s",im->graphfile,
2113 strerror(errno));
2114 return (-1);
2115 }
2116 }
2117 switch (im->imgformat) {
2118 case IF_GIF:
2119 break;
2120 case IF_PNG:
2121 gfx_render_png (canvas,im->xgif,im->ygif,im->zoom,0x0,fo);
2122 break;
2123 }
2124 if (strcmp(im->graphfile,"-") != 0)
2125 fclose(fo);
2127 gfx_destroy(canvas);
2128 return 0;
2129 }
2132 /*****************************************************
2133 * graph stuff
2134 *****************************************************/
2136 int
2137 gdes_alloc(image_desc_t *im){
2139 long def_step = (im->end-im->start)/im->xsize;
2141 if (im->step > def_step) /* step can be increassed ... no decreassed */
2142 def_step = im->step;
2144 im->gdes_c++;
2146 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2147 * sizeof(graph_desc_t)))==NULL){
2148 rrd_set_error("realloc graph_descs");
2149 return -1;
2150 }
2153 im->gdes[im->gdes_c-1].step=def_step;
2154 im->gdes[im->gdes_c-1].start=im->start;
2155 im->gdes[im->gdes_c-1].end=im->end;
2156 im->gdes[im->gdes_c-1].vname[0]='\0';
2157 im->gdes[im->gdes_c-1].data=NULL;
2158 im->gdes[im->gdes_c-1].ds_namv=NULL;
2159 im->gdes[im->gdes_c-1].data_first=0;
2160 im->gdes[im->gdes_c-1].p_data=NULL;
2161 im->gdes[im->gdes_c-1].rpnp=NULL;
2162 im->gdes[im->gdes_c-1].col = 0x0;
2163 im->gdes[im->gdes_c-1].legend[0]='\0';
2164 im->gdes[im->gdes_c-1].rrd[0]='\0';
2165 im->gdes[im->gdes_c-1].ds=-1;
2166 im->gdes[im->gdes_c-1].p_data=NULL;
2167 return 0;
2168 }
2170 /* copies input untill the first unescaped colon is found
2171 or until input ends. backslashes have to be escaped as well */
2172 int
2173 scan_for_col(char *input, int len, char *output)
2174 {
2175 int inp,outp=0;
2176 for (inp=0;
2177 inp < len &&
2178 input[inp] != ':' &&
2179 input[inp] != '\0';
2180 inp++){
2181 if (input[inp] == '\\' &&
2182 input[inp+1] != '\0' &&
2183 (input[inp+1] == '\\' ||
2184 input[inp+1] == ':')){
2185 output[outp++] = input[++inp];
2186 }
2187 else {
2188 output[outp++] = input[inp];
2189 }
2190 }
2191 output[outp] = '\0';
2192 return inp;
2193 }
2195 /* Some surgery done on this function, it became ridiculously big.
2196 ** Things moved:
2197 ** - initializing now in rrd_graph_init()
2198 ** - options parsing now in rrd_graph_options()
2199 ** - script parsing now in rrd_graph_script()
2200 */
2201 int
2202 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize)
2203 {
2204 image_desc_t im;
2206 rrd_graph_init(&im);
2208 rrd_graph_options(argc,argv,&im);
2209 if (rrd_test_error()) return -1;
2211 if (strlen(argv[optind])>=MAXPATH) {
2212 rrd_set_error("filename (including path) too long");
2213 return -1;
2214 }
2215 strncpy(im.graphfile,argv[optind],MAXPATH-1);
2216 im.graphfile[MAXPATH-1]='\0';
2218 rrd_graph_script(argc,argv,&im);
2219 if (rrd_test_error()) return -1;
2221 /* Everything is now read and the actual work can start */
2223 (*prdata)=NULL;
2224 if (graph_paint(&im,prdata)==-1){
2225 im_free(&im);
2226 return -1;
2227 }
2229 /* The image is generated and needs to be output.
2230 ** Also, if needed, print a line with information about the image.
2231 */
2233 *xsize=im.xgif;
2234 *ysize=im.ygif;
2235 if (im.imginfo) {
2236 char *filename;
2237 if (!(*prdata)) {
2238 /* maybe prdata is not allocated yet ... lets do it now */
2239 if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
2240 rrd_set_error("malloc imginfo");
2241 return -1;
2242 };
2243 }
2244 if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
2245 ==NULL){
2246 rrd_set_error("malloc imginfo");
2247 return -1;
2248 }
2249 filename=im.graphfile+strlen(im.graphfile);
2250 while(filename > im.graphfile) {
2251 if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
2252 filename--;
2253 }
2255 sprintf((*prdata)[0],im.imginfo,filename,(long)(im.zoom*im.xgif),(long)(im.zoom*im.ygif));
2256 }
2257 im_free(&im);
2258 return 0;
2259 }
2261 void
2262 rrd_graph_init(image_desc_t *im)
2263 {
2264 int i;
2266 im->xlab_user.minsec = -1;
2267 im->xgif=0;
2268 im->ygif=0;
2269 im->xsize = 400;
2270 im->ysize = 100;
2271 im->step = 0;
2272 im->ylegend[0] = '\0';
2273 im->title[0] = '\0';
2274 im->minval = DNAN;
2275 im->maxval = DNAN;
2276 im->interlaced = 0;
2277 im->unitsexponent= 9999;
2278 im->extra_flags= 0;
2279 im->rigid = 0;
2280 im->imginfo = NULL;
2281 im->lazy = 0;
2282 im->logarithmic = 0;
2283 im->ygridstep = DNAN;
2284 im->draw_x_grid = 1;
2285 im->draw_y_grid = 1;
2286 im->base = 1000;
2287 im->prt_c = 0;
2288 im->gdes_c = 0;
2289 im->gdes = NULL;
2290 im->zoom = 1.0;
2291 im->imgformat = IF_GIF; /* we default to GIF output */
2293 for(i=0;i<DIM(graph_col);i++)
2294 im->graph_col[i]=graph_col[i];
2296 for(i=0;i<DIM(text_prop);i++){
2297 im->text_prop[i].size = text_prop[i].size;
2298 im->text_prop[i].font = text_prop[i].font;
2299 }
2300 }
2302 void
2303 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
2304 {
2305 int stroff;
2306 char *parsetime_error = NULL;
2307 char scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
2308 time_t start_tmp=0,end_tmp=0;
2309 long long_tmp;
2310 struct time_value start_tv, end_tv;
2311 gfx_color_t color;
2313 parsetime("end-24h", &start_tv);
2314 parsetime("now", &end_tv);
2316 while (1){
2317 static struct option long_options[] =
2318 {
2319 {"start", required_argument, 0, 's'},
2320 {"end", required_argument, 0, 'e'},
2321 {"x-grid", required_argument, 0, 'x'},
2322 {"y-grid", required_argument, 0, 'y'},
2323 {"vertical-label",required_argument,0,'v'},
2324 {"width", required_argument, 0, 'w'},
2325 {"height", required_argument, 0, 'h'},
2326 {"interlaced", no_argument, 0, 'i'},
2327 {"upper-limit",required_argument, 0, 'u'},
2328 {"lower-limit",required_argument, 0, 'l'},
2329 {"rigid", no_argument, 0, 'r'},
2330 {"base", required_argument, 0, 'b'},
2331 {"logarithmic",no_argument, 0, 'o'},
2332 {"color", required_argument, 0, 'c'},
2333 {"font", required_argument, 0, 'n'},
2334 {"title", required_argument, 0, 't'},
2335 {"imginfo", required_argument, 0, 'f'},
2336 {"imgformat", required_argument, 0, 'a'},
2337 {"lazy", no_argument, 0, 'z'},
2338 {"zoom", required_argument, 0, 'm'},
2339 {"no-legend", no_argument, 0, 'g'},
2340 {"alt-y-grid", no_argument, 0, 257 },
2341 {"alt-autoscale", no_argument, 0, 258 },
2342 {"alt-autoscale-max", no_argument, 0, 259 },
2343 {"units-exponent",required_argument, 0, 260},
2344 {"step", required_argument, 0, 261},
2345 {0,0,0,0}};
2346 int option_index = 0;
2347 int opt;
2350 opt = getopt_long(argc, argv,
2351 "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:z:g",
2352 long_options, &option_index);
2354 if (opt == EOF)
2355 break;
2357 switch(opt) {
2358 case 257:
2359 im->extra_flags |= ALTYGRID;
2360 break;
2361 case 258:
2362 im->extra_flags |= ALTAUTOSCALE;
2363 break;
2364 case 259:
2365 im->extra_flags |= ALTAUTOSCALE_MAX;
2366 break;
2367 case 'g':
2368 im->extra_flags |= NOLEGEND;
2369 break;
2370 case 260:
2371 im->unitsexponent = atoi(optarg);
2372 break;
2373 case 261:
2374 im->step = atoi(optarg);
2375 break;
2376 case 's':
2377 if ((parsetime_error = parsetime(optarg, &start_tv))) {
2378 rrd_set_error( "start time: %s", parsetime_error );
2379 return;
2380 }
2381 break;
2382 case 'e':
2383 if ((parsetime_error = parsetime(optarg, &end_tv))) {
2384 rrd_set_error( "end time: %s", parsetime_error );
2385 return;
2386 }
2387 break;
2388 case 'x':
2389 if(strcmp(optarg,"none") == 0){
2390 im->draw_x_grid=0;
2391 break;
2392 };
2394 if(sscanf(optarg,
2395 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
2396 scan_gtm,
2397 &im->xlab_user.gridst,
2398 scan_mtm,
2399 &im->xlab_user.mgridst,
2400 scan_ltm,
2401 &im->xlab_user.labst,
2402 &im->xlab_user.precis,
2403 &stroff) == 7 && stroff != 0){
2404 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
2405 if((im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
2406 rrd_set_error("unknown keyword %s",scan_gtm);
2407 return;
2408 } else if ((im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
2409 rrd_set_error("unknown keyword %s",scan_mtm);
2410 return;
2411 } else if ((im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
2412 rrd_set_error("unknown keyword %s",scan_ltm);
2413 return;
2414 }
2415 im->xlab_user.minsec = 1;
2416 im->xlab_user.stst = im->xlab_form;
2417 } else {
2418 rrd_set_error("invalid x-grid format");
2419 return;
2420 }
2421 break;
2422 case 'y':
2424 if(strcmp(optarg,"none") == 0){
2425 im->draw_y_grid=0;
2426 break;
2427 };
2429 if(sscanf(optarg,
2430 "%lf:%d",
2431 &im->ygridstep,
2432 &im->ylabfact) == 2) {
2433 if(im->ygridstep<=0){
2434 rrd_set_error("grid step must be > 0");
2435 return;
2436 } else if (im->ylabfact < 1){
2437 rrd_set_error("label factor must be > 0");
2438 return;
2439 }
2440 } else {
2441 rrd_set_error("invalid y-grid format");
2442 return;
2443 }
2444 break;
2445 case 'v':
2446 strncpy(im->ylegend,optarg,150);
2447 im->ylegend[150]='\0';
2448 break;
2449 case 'u':
2450 im->maxval = atof(optarg);
2451 break;
2452 case 'l':
2453 im->minval = atof(optarg);
2454 break;
2455 case 'b':
2456 im->base = atol(optarg);
2457 if(im->base != 1024 && im->base != 1000 ){
2458 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
2459 return;
2460 }
2461 break;
2462 case 'w':
2463 long_tmp = atol(optarg);
2464 if (long_tmp < 10) {
2465 rrd_set_error("width below 10 pixels");
2466 return;
2467 }
2468 im->xsize = long_tmp;
2469 break;
2470 case 'h':
2471 long_tmp = atol(optarg);
2472 if (long_tmp < 10) {
2473 rrd_set_error("height below 10 pixels");
2474 return;
2475 }
2476 im->ysize = long_tmp;
2477 break;
2478 case 'i':
2479 im->interlaced = 1;
2480 break;
2481 case 'r':
2482 im->rigid = 1;
2483 break;
2484 case 'f':
2485 im->imginfo = optarg;
2486 break;
2487 case 'a':
2488 if((im->imgformat = if_conv(optarg)) == -1) {
2489 rrd_set_error("unsupported graphics format '%s'",optarg);
2490 return;
2491 }
2492 break;
2493 case 'z':
2494 im->lazy = 1;
2495 break;
2496 case 'o':
2497 im->logarithmic = 1;
2498 if (isnan(im->minval))
2499 im->minval=1;
2500 break;
2501 case 'c':
2502 if(sscanf(optarg,
2503 "%10[A-Z]#%8x",
2504 col_nam,&color) == 2){
2505 int ci;
2506 if((ci=grc_conv(col_nam)) != -1){
2507 im->graph_col[ci]=color;
2508 } else {
2509 rrd_set_error("invalid color name '%s'",col_nam);
2510 }
2511 } else {
2512 rrd_set_error("invalid color def format");
2513 return -1;
2514 }
2515 break;
2516 case 'n':{
2517 /* originally this used char *prop = "" and
2518 ** char *font = "dummy" however this results
2519 ** in a SEG fault, at least on RH7.1
2520 **
2521 ** The current implementation isn't proper
2522 ** either, font is never freed and prop uses
2523 ** a fixed width string
2524 */
2525 char prop[100];
2526 double size = 1;
2527 char *font;
2529 font=malloc(255);
2530 if(sscanf(optarg,
2531 "%10[A-Z]:%lf:%s",
2532 prop,&size,font) == 3){
2533 int sindex;
2534 if((sindex=text_prop_conv(prop)) != -1){
2535 printf("DEBUG: setting all to the default of font %s with width %lf\n",
2536 font,size);
2537 im->text_prop[sindex].size=size;
2538 im->text_prop[sindex].font=font;
2539 if (sindex==0) { /* the default */
2540 im->text_prop[TEXT_PROP_TITLE].size=size;
2541 im->text_prop[TEXT_PROP_TITLE].font=font;
2542 im->text_prop[TEXT_PROP_AXIS].size=size;
2543 im->text_prop[TEXT_PROP_AXIS].font=font;
2544 im->text_prop[TEXT_PROP_UNIT].size=size;
2545 im->text_prop[TEXT_PROP_UNIT].font=font;
2546 im->text_prop[TEXT_PROP_LEGEND].size=size;
2547 im->text_prop[TEXT_PROP_LEGEND].font=font;
2548 }
2549 } else {
2550 rrd_set_error("invalid fonttag '%s'",prop);
2551 return;
2552 }
2553 } else {
2554 rrd_set_error("invalid text property format");
2555 return;
2556 }
2557 break;
2558 }
2559 case 'm':
2560 im->zoom= atof(optarg);
2561 if (im->zoom <= 0.0) {
2562 rrd_set_error("zoom factor must be > 0");
2563 return;
2564 }
2565 break;
2566 case 't':
2567 strncpy(im->title,optarg,150);
2568 im->title[150]='\0';
2569 break;
2571 case '?':
2572 if (optopt != 0)
2573 rrd_set_error("unknown option '%c'", optopt);
2574 else
2575 rrd_set_error("unknown option '%s'",argv[optind-1]);
2576 return;
2577 }
2578 }
2580 if (optind >= argc) {
2581 rrd_set_error("missing filename");
2582 return;
2583 }
2585 if (im->logarithmic == 1 && (im->minval <= 0 || isnan(im->minval))){
2586 rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");
2587 return;
2588 }
2590 if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
2591 /* error string is set in parsetime.c */
2592 return;
2593 }
2595 if (start_tmp < 3600*24*365*10){
2596 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
2597 return;
2598 }
2600 if (end_tmp < start_tmp) {
2601 rrd_set_error("start (%ld) should be less than end (%ld)",
2602 start_tmp, end_tmp);
2603 return;
2604 }
2606 im->start = start_tmp;
2607 im->end = end_tmp;
2608 }
2610 void
2611 rrd_graph_script(int argc, char *argv[], image_desc_t *im)
2612 {
2613 int i;
2614 char symname[100];
2615 int linepass = 0; /* stack must follow LINE*, AREA or STACK */
2617 for (i=optind+1;i<argc;i++) {
2618 int argstart=0;
2619 int strstart=0;
2620 graph_desc_t *gdp;
2621 char *line;
2622 char funcname[10],vname[MAX_VNAME_LEN+1],sep[1];
2623 double d;
2624 double linewidth;
2625 int j,k,l,m;
2627 /* Each command is one element from *argv[], we call this "line".
2628 **
2629 ** Each command defines the most current gdes inside struct im.
2630 ** In stead of typing "im->gdes[im->gdes_c-1]" we use "gdp".
2631 */
2632 gdes_alloc(im);
2633 gdp=&im->gdes[im->gdes_c-1];
2634 line=argv[i];
2636 /* function:newvname=string[:ds-name:CF] for xDEF
2637 ** function:vname[#color[:string]] for LINEx,AREA,STACK
2638 ** function:vname#color[:num[:string]] for TICK
2639 ** function:vname-or-num#color[:string] for xRULE
2640 ** function:vname:CF:string for xPRINT
2641 ** function:string for COMMENT
2642 */
2643 argstart=0;
2645 sscanf(line, "%10[A-Z0-9]:%n", funcname,&argstart);
2646 if (argstart==0) {
2647 rrd_set_error("Cannot parse function in line: %s",line);
2648 im_free(im);
2649 return;
2650 }
2651 if(sscanf(funcname,"LINE%lf",&linewidth)){
2652 im->gdes[im->gdes_c-1].gf = GF_LINE;
2653 im->gdes[im->gdes_c-1].linewidth = linewidth;
2654 } else {
2655 if ((gdp->gf=gf_conv(funcname))==-1) {
2656 rrd_set_error("'%s' is not a valid function name",funcname);
2657 im_free(im);
2658 return;
2659 }
2660 }
2662 /* If the error string is set, we exit at the end of the switch */
2663 switch (gdp->gf) {
2664 case GF_COMMENT:
2665 if (rrd_graph_legend(gdp,&line[argstart])==0)
2666 rrd_set_error("Cannot parse comment in line: %s",line);
2667 break;
2668 case GF_VRULE:
2669 case GF_HRULE:
2670 j=k=l=m=0;
2671 sscanf(&line[argstart], "%lf%n#%n", &d, &j, &k);
2672 sscanf(&line[argstart], DEF_NAM_FMT "%n#%n", vname, &l, &m);
2673 if (k+m==0) {
2674 rrd_set_error("Cannot parse name or num in line: %s",line);
2675 break;
2676 }
2677 if (j!=0) {
2678 gdp->xrule=d;
2679 gdp->yrule=d;
2680 argstart+=j;
2681 } else if (!rrd_graph_check_vname(im,vname,line)) {
2682 gdp->xrule=0;
2683 gdp->yrule=DNAN;
2684 argstart+=l;
2685 } else break; /* exit due to wrong vname */
2686 if ((j=rrd_graph_color(im,&line[argstart],line,0))==0) break;
2687 argstart+=j;
2688 if (strlen(&line[argstart])!=0) {
2689 if (rrd_graph_legend(gdp,&line[++argstart])==0)
2690 rrd_set_error("Cannot parse comment in line: %s",line);
2691 }
2692 break;
2693 case GF_STACK:
2694 if (linepass==0) {
2695 rrd_set_error("STACK must follow another graphing element");
2696 break;
2697 }
2698 case GF_LINE:
2699 case GF_AREA:
2700 case GF_TICK:
2701 j=k=0;
2702 linepass=1;
2703 sscanf(&line[argstart],DEF_NAM_FMT"%n%1[#:]%n",vname,&j,sep,&k);
2704 if (j+1!=k)
2705 rrd_set_error("Cannot parse vname in line: %s",line);
2706 else if (rrd_graph_check_vname(im,vname,line))
2707 rrd_set_error("Undefined vname '%s' in line: %s",line);
2708 else
2709 k=rrd_graph_color(im,&line[argstart],line,1);
2710 if (rrd_test_error()) break;
2711 argstart=argstart+j+k;
2712 if ((strlen(&line[argstart])!=0)&&(gdp->gf==GF_TICK)) {
2713 j=0;
2714 sscanf(&line[argstart], ":%lf%n", &gdp->yrule,&j);
2715 argstart+=j;
2716 }
2717 if (strlen(&line[argstart])!=0)
2718 if (rrd_graph_legend(gdp,&line[++argstart])==0)
2719 rrd_set_error("Cannot parse legend in line: %s",line);
2720 break;
2721 case GF_PRINT:
2722 im->prt_c++;
2723 case GF_GPRINT:
2724 j=0;
2725 sscanf(&line[argstart], DEF_NAM_FMT ":%n",gdp->vname,&j);
2726 if (j==0) {
2727 rrd_set_error("Cannot parse vname in line: '%s'",line);
2728 break;
2729 }
2730 argstart+=j;
2731 if (rrd_graph_check_vname(im,gdp->vname,line)) return;
2732 j=0;
2733 sscanf(&line[argstart], CF_NAM_FMT ":%n",symname,&j);
2735 k=(j!=0)?rrd_graph_check_CF(im,symname,line):1;
2736 #define VIDX im->gdes[gdp->vidx]
2737 switch (k) {
2738 case -1: /* looks CF but is not really CF */
2739 if (VIDX.gf == GF_VDEF) rrd_clear_error();
2740 break;
2741 case 0: /* CF present and correct */
2742 if (VIDX.gf == GF_VDEF)
2743 rrd_set_error("Don't use CF when printing VDEF");
2744 argstart+=j;
2745 break;
2746 case 1: /* CF not present */
2747 if (VIDX.gf == GF_VDEF) rrd_clear_error();
2748 else rrd_set_error("Printing DEF or CDEF needs CF");
2749 break;
2750 default:
2751 rrd_set_error("Oops, bug in GPRINT scanning");
2752 }
2753 #undef VIDX
2754 if (rrd_test_error()) break;
2756 if (strlen(&line[argstart])!=0) {
2757 if (rrd_graph_legend(gdp,&line[argstart])==0)
2758 rrd_set_error("Cannot parse legend in line: %s",line);
2759 } else rrd_set_error("No legend in (G)PRINT line: %s",line);
2760 strcpy(gdp->format, gdp->legend);
2761 break;
2762 case GF_DEF:
2763 case GF_VDEF:
2764 case GF_CDEF:
2765 j=0;
2766 sscanf(&line[argstart], DEF_NAM_FMT "=%n",gdp->vname,&j);
2767 if (j==0) {
2768 rrd_set_error("Could not parse line: %s",line);
2769 break;
2770 }
2771 if (find_var(im,gdp->vname)!=-1) {
2772 rrd_set_error("Variable '%s' in line '%s' already in use\n",
2773 gdp->vname,line);
2774 break;
2775 }
2776 argstart+=j;
2777 switch (gdp->gf) {
2778 case GF_DEF:
2779 argstart+=scan_for_col(&line[argstart],MAXPATH,gdp->rrd);
2780 j=k=0;
2781 sscanf(&line[argstart],
2782 ":" DS_NAM_FMT ":" CF_NAM_FMT "%n%*s%n",
2783 gdp->ds_nam, symname, &j, &k);
2784 if ((j==0)||(k!=0)) {
2785 rrd_set_error("Cannot parse DS or CF in '%s'",line);
2786 break;
2787 }
2788 rrd_graph_check_CF(im,symname,line);
2789 break;
2790 case GF_VDEF:
2791 j=0;
2792 sscanf(&line[argstart],DEF_NAM_FMT ",%n",vname,&j);
2793 if (j==0) {
2794 rrd_set_error("Cannot parse vname in line '%s'",line);
2795 break;
2796 }
2797 argstart+=j;
2798 if (rrd_graph_check_vname(im,vname,line)) return;
2799 if ( im->gdes[gdp->vidx].gf != GF_DEF
2800 && im->gdes[gdp->vidx].gf != GF_CDEF) {
2801 rrd_set_error("variable '%s' not DEF nor "
2802 "CDEF in VDEF '%s'", vname,gdp->vname);
2803 break;
2804 }
2805 vdef_parse(gdp,&line[argstart+strstart]);
2806 break;
2807 case GF_CDEF:
2808 if (strstr(&line[argstart],":")!=NULL) {
2809 rrd_set_error("Error in RPN, line: %s",line);
2810 break;
2811 }
2812 if ((gdp->rpnp = rpn_parse(
2813 (void *)im,
2814 &line[argstart],
2815 &find_var_wrapper)
2816 )==NULL)
2817 rrd_set_error("invalid rpn expression in: %s",line);
2818 break;
2819 default: break;
2820 }
2821 break;
2822 default: rrd_set_error("Big oops");
2823 }
2824 if (rrd_test_error()) {
2825 im_free(im);
2826 return;
2827 }
2828 }
2830 if (im->gdes_c==0){
2831 rrd_set_error("can't make a graph without contents");
2832 im_free(im); /* ??? is this set ??? */
2833 return;
2834 }
2835 }
2836 int
2837 rrd_graph_check_vname(image_desc_t *im, char *varname, char *err)
2838 {
2839 if ((im->gdes[im->gdes_c-1].vidx=find_var(im,varname))==-1) {
2840 rrd_set_error("Unknown variable '%s' in %s",varname,err);
2841 return -1;
2842 }
2843 return 0;
2844 }
2845 int
2846 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
2847 {
2848 char *color;
2849 graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
2851 color=strstr(var,"#");
2852 if (color==NULL) {
2853 if (optional==0) {
2854 rrd_set_error("Found no color in %s",err);
2855 return 0;
2856 }
2857 return 0;
2858 } else {
2859 int n=0;
2860 char *rest;
2861 gfx_color_t col;
2863 rest=strstr(color,":");
2864 if (rest!=NULL)
2865 n=rest-color;
2866 else
2867 n=strlen(color);
2869 switch (n) {
2870 case 7:
2871 sscanf(color,"#%6x%n",&col,&n);
2872 col = (col << 8) + 0xff /* shift left by 8 */;
2873 if (n!=7) rrd_set_error("Color problem in %s",err);
2874 break;
2875 case 9:
2876 sscanf(color,"#%8x%n",&col,&n);
2877 if (n==9) break;
2878 default:
2879 rrd_set_error("Color problem in %s",err);
2880 }
2881 if (rrd_test_error()) return 0;
2882 gdp->col = col;
2883 return n;
2884 }
2885 }
2886 int
2887 rrd_graph_check_CF(image_desc_t *im, char *symname, char *err)
2888 {
2889 if ((im->gdes[im->gdes_c-1].cf=cf_conv(symname))==-1) {
2890 rrd_set_error("Unknown CF '%s' in %s",symname,err);
2891 return -1;
2892 }
2893 return 0;
2894 }
2895 int
2896 rrd_graph_legend(graph_desc_t *gdp, char *line)
2897 {
2898 int i;
2900 i=scan_for_col(line,FMT_LEG_LEN,gdp->legend);
2902 return (strlen(&line[i])==0);
2903 }
2906 int bad_format(char *fmt) {
2907 char *ptr;
2908 int n=0;
2910 ptr = fmt;
2911 while (*ptr != '\0') {
2912 if (*ptr == '%') {ptr++;
2913 if (*ptr == '\0') return 1;
2914 while ((*ptr >= '0' && *ptr <= '9') || *ptr == '.') {
2915 ptr++;
2916 }
2917 if (*ptr == '\0') return 1;
2918 if (*ptr == 'l') {
2919 ptr++;
2920 n++;
2921 if (*ptr == '\0') return 1;
2922 if (*ptr == 'e' || *ptr == 'f') {
2923 ptr++;
2924 } else { return 1; }
2925 }
2926 else if (*ptr == 's' || *ptr == 'S' || *ptr == '%') { ++ptr; }
2927 else { return 1; }
2928 } else {
2929 ++ptr;
2930 }
2931 }
2932 return (n!=1);
2933 }
2934 int
2935 vdef_parse(gdes,str)
2936 struct graph_desc_t *gdes;
2937 char *str;
2938 {
2939 /* A VDEF currently is either "func" or "param,func"
2940 * so the parsing is rather simple. Change if needed.
2941 */
2942 double param;
2943 char func[30];
2944 int n;
2946 n=0;
2947 sscanf(str,"%le,%29[A-Z]%n",¶m,func,&n);
2948 if (n==strlen(str)) { /* matched */
2949 ;
2950 } else {
2951 n=0;
2952 sscanf(str,"%29[A-Z]%n",func,&n);
2953 if (n==strlen(str)) { /* matched */
2954 param=DNAN;
2955 } else {
2956 rrd_set_error("Unknown function string '%s' in VDEF '%s'"
2957 ,str
2958 ,gdes->vname
2959 );
2960 return -1;
2961 }
2962 }
2963 if (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
2964 else if (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
2965 else if (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
2966 else if (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
2967 else if (!strcmp("TOTAL", func)) gdes->vf.op = VDEF_TOTAL;
2968 else if (!strcmp("FIRST", func)) gdes->vf.op = VDEF_FIRST;
2969 else if (!strcmp("LAST", func)) gdes->vf.op = VDEF_LAST;
2970 else {
2971 rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
2972 ,func
2973 ,gdes->vname
2974 );
2975 return -1;
2976 };
2978 switch (gdes->vf.op) {
2979 case VDEF_PERCENT:
2980 if (isnan(param)) { /* no parameter given */
2981 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
2982 ,func
2983 ,gdes->vname
2984 );
2985 return -1;
2986 };
2987 if (param>=0.0 && param<=100.0) {
2988 gdes->vf.param = param;
2989 gdes->vf.val = DNAN; /* undefined */
2990 gdes->vf.when = 0; /* undefined */
2991 } else {
2992 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
2993 ,param
2994 ,gdes->vname
2995 );
2996 return -1;
2997 };
2998 break;
2999 case VDEF_MAXIMUM:
3000 case VDEF_AVERAGE:
3001 case VDEF_MINIMUM:
3002 case VDEF_TOTAL:
3003 case VDEF_FIRST:
3004 case VDEF_LAST:
3005 if (isnan(param)) {
3006 gdes->vf.param = DNAN;
3007 gdes->vf.val = DNAN;
3008 gdes->vf.when = 0;
3009 } else {
3010 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3011 ,func
3012 ,gdes->vname
3013 );
3014 return -1;
3015 };
3016 break;
3017 };
3018 return 0;
3019 }
3020 int
3021 vdef_calc(im,gdi)
3022 image_desc_t *im;
3023 int gdi;
3024 {
3025 graph_desc_t *src,*dst;
3026 rrd_value_t *data;
3027 long step,steps;
3029 dst = &im->gdes[gdi];
3030 src = &im->gdes[dst->vidx];
3031 data = src->data + src->ds;
3032 steps = (src->end - src->start) / src->step;
3034 #if 0
3035 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3036 ,src->start
3037 ,src->end
3038 ,steps
3039 );
3040 #endif
3042 switch (dst->vf.op) {
3043 case VDEF_PERCENT: {
3044 rrd_value_t * array;
3045 int field;
3048 if ((array = malloc(steps*sizeof(double)))==NULL) {
3049 rrd_set_error("malloc VDEV_PERCENT");
3050 return -1;
3051 }
3052 for (step=0;step < steps; step++) {
3053 array[step]=data[step*src->ds_cnt];
3054 }
3055 qsort(array,step,sizeof(double),vdef_percent_compar);
3057 field = (steps-1)*dst->vf.param/100;
3058 dst->vf.val = array[field];
3059 dst->vf.when = 0; /* no time component */
3060 #if 0
3061 for(step=0;step<steps;step++)
3062 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3063 #endif
3064 }
3065 break;
3066 case VDEF_MAXIMUM:
3067 step=0;
3068 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3069 if (step == steps) {
3070 dst->vf.val = DNAN;
3071 dst->vf.when = 0;
3072 } else {
3073 dst->vf.val = data[step*src->ds_cnt];
3074 dst->vf.when = src->start + (step+1)*src->step;
3075 }
3076 while (step != steps) {
3077 if (finite(data[step*src->ds_cnt])) {
3078 if (data[step*src->ds_cnt] > dst->vf.val) {
3079 dst->vf.val = data[step*src->ds_cnt];
3080 dst->vf.when = src->start + (step+1)*src->step;
3081 }
3082 }
3083 step++;
3084 }
3085 break;
3086 case VDEF_TOTAL:
3087 case VDEF_AVERAGE: {
3088 int cnt=0;
3089 double sum=0.0;
3090 for (step=0;step<steps;step++) {
3091 if (finite(data[step*src->ds_cnt])) {
3092 sum += data[step*src->ds_cnt];
3093 cnt ++;
3094 };
3095 }
3096 if (cnt) {
3097 if (dst->vf.op == VDEF_TOTAL) {
3098 dst->vf.val = sum*src->step;
3099 dst->vf.when = cnt*src->step; /* not really "when" */
3100 } else {
3101 dst->vf.val = sum/cnt;
3102 dst->vf.when = 0; /* no time component */
3103 };
3104 } else {
3105 dst->vf.val = DNAN;
3106 dst->vf.when = 0;
3107 }
3108 }
3109 break;
3110 case VDEF_MINIMUM:
3111 step=0;
3112 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3113 if (step == steps) {
3114 dst->vf.val = DNAN;
3115 dst->vf.when = 0;
3116 } else {
3117 dst->vf.val = data[step*src->ds_cnt];
3118 dst->vf.when = src->start + (step+1)*src->step;
3119 }
3120 while (step != steps) {
3121 if (finite(data[step*src->ds_cnt])) {
3122 if (data[step*src->ds_cnt] < dst->vf.val) {
3123 dst->vf.val = data[step*src->ds_cnt];
3124 dst->vf.when = src->start + (step+1)*src->step;
3125 }
3126 }
3127 step++;
3128 }
3129 break;
3130 case VDEF_FIRST:
3131 /* The time value returned here is one step before the
3132 * actual time value. This is the start of the first
3133 * non-NaN interval.
3134 */
3135 step=0;
3136 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3137 if (step == steps) { /* all entries were NaN */
3138 dst->vf.val = DNAN;
3139 dst->vf.when = 0;
3140 } else {
3141 dst->vf.val = data[step*src->ds_cnt];
3142 dst->vf.when = src->start + step*src->step;
3143 }
3144 break;
3145 case VDEF_LAST:
3146 /* The time value returned here is the
3147 * actual time value. This is the end of the last
3148 * non-NaN interval.
3149 */
3150 step=steps-1;
3151 while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3152 if (step < 0) { /* all entries were NaN */
3153 dst->vf.val = DNAN;
3154 dst->vf.when = 0;
3155 } else {
3156 dst->vf.val = data[step*src->ds_cnt];
3157 dst->vf.when = src->start + (step+1)*src->step;
3158 }
3159 break;
3160 }
3161 return 0;
3162 }
3164 /* NaN < -INF < finite_values < INF */
3165 int
3166 vdef_percent_compar(a,b)
3167 const void *a,*b;
3168 {
3169 /* Equality is not returned; this doesn't hurt except
3170 * (maybe) for a little performance.
3171 */
3173 /* First catch NaN values. They are smallest */
3174 if (isnan( *(double *)a )) return -1;
3175 if (isnan( *(double *)b )) return 1;
3177 /* NaN doesn't reach this part so INF and -INF are extremes.
3178 * The sign from isinf() is compatible with the sign we return
3179 */
3180 if (isinf( *(double *)a )) return isinf( *(double *)a );
3181 if (isinf( *(double *)b )) return isinf( *(double *)b );
3183 /* If we reach this, both values must be finite */
3184 if ( *(double *)a < *(double *)b ) return -1; else return 1;
3185 }