1 /****************************************************************************
2 * RRDtool 1.0.33 Copyright Tobias Oetiker, 1997 - 2000
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;
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 leg_x +=
1328 gfx_get_text_width(leg_x,im->text_prop[TEXT_PROP_LEGEND].font,
1329 im->text_prop[TEXT_PROP_LEGEND].size,
1330 im->tabwidth,
1331 im->gdes[ii].legend)
1332 + legspace[ii]
1333 + glue;
1334 if (im->gdes[ii].gf != GF_GPRINT &&
1335 im->gdes[ii].gf != GF_COMMENT)
1336 leg_x += box;
1337 }
1338 leg_y = leg_y + im->text_prop[TEXT_PROP_LEGEND].size*1.2;
1339 if (prt_fctn == 's') leg_y -= im->text_prop[TEXT_PROP_LEGEND].size*1.2;
1340 fill = 0;
1341 leg_c = 0;
1342 mark = ii;
1343 }
1344 }
1345 im->ygif = leg_y+6;
1346 free(legspace);
1347 }
1348 return 0;
1349 }
1351 /* create a grid on the graph. it determines what to do
1352 from the values of xsize, start and end */
1354 /* the xaxis labels are determined from the number of seconds per pixel
1355 in the requested graph */
1359 int
1360 horizontal_grid(gfx_canvas_t *canvas, image_desc_t *im)
1361 {
1362 double range;
1363 double scaledrange;
1364 int pixel,i;
1365 int sgrid,egrid;
1366 double gridstep;
1367 double scaledstep;
1368 char graph_label[100];
1369 double x0,x1,y0,y1;
1370 int labfact,gridind;
1371 int decimals, fractionals;
1372 char labfmt[64];
1374 labfact=2;
1375 gridind=-1;
1376 range = im->maxval - im->minval;
1377 scaledrange = range / im->magfact;
1379 /* does the scale of this graph make it impossible to put lines
1380 on it? If so, give up. */
1381 if (isnan(scaledrange)) {
1382 return 0;
1383 }
1385 /* find grid spaceing */
1386 pixel=1;
1387 if(isnan(im->ygridstep)){
1388 if(im->extra_flags & ALTYGRID) {
1389 /* find the value with max number of digits. Get number of digits */
1390 decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))));
1391 if(decimals <= 0) /* everything is small. make place for zero */
1392 decimals = 1;
1394 fractionals = floor(log10(range));
1395 if(fractionals < 0) /* small amplitude. */
1396 sprintf(labfmt, "%%%d.%df", decimals - fractionals + 1, -fractionals + 1);
1397 else
1398 sprintf(labfmt, "%%%d.1f", decimals + 1);
1399 gridstep = pow((double)10, (double)fractionals);
1400 if(gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1401 gridstep = 0.1;
1402 /* should have at least 5 lines but no more then 15 */
1403 if(range/gridstep < 5)
1404 gridstep /= 10;
1405 if(range/gridstep > 15)
1406 gridstep *= 10;
1407 if(range/gridstep > 5) {
1408 labfact = 1;
1409 if(range/gridstep > 8)
1410 labfact = 2;
1411 }
1412 else {
1413 gridstep /= 5;
1414 labfact = 5;
1415 }
1416 }
1417 else {
1418 for(i=0;ylab[i].grid > 0;i++){
1419 pixel = im->ysize / (scaledrange / ylab[i].grid);
1420 if (gridind == -1 && pixel > 5) {
1421 gridind = i;
1422 break;
1423 }
1424 }
1426 for(i=0; i<4;i++) {
1427 if (pixel * ylab[gridind].lfac[i] >= 2 * im->text_prop[TEXT_PROP_AXIS].size) {
1428 labfact = ylab[gridind].lfac[i];
1429 break;
1430 }
1431 }
1433 gridstep = ylab[gridind].grid * im->magfact;
1434 }
1435 } else {
1436 gridstep = im->ygridstep;
1437 labfact = im->ylabfact;
1438 }
1440 x0=im->xorigin;
1441 x1=im->xorigin+im->xsize;
1443 sgrid = (int)( im->minval / gridstep - 1);
1444 egrid = (int)( im->maxval / gridstep + 1);
1445 scaledstep = gridstep/im->magfact;
1446 for (i = sgrid; i <= egrid; i++){
1447 y0=ytr(im,gridstep*i);
1448 if ( y0 >= im->yorigin-im->ysize
1449 && y0 <= im->yorigin){
1450 if(i % labfact == 0){
1451 if (i==0 || im->symbol == ' ') {
1452 if(scaledstep < 1){
1453 if(im->extra_flags & ALTYGRID) {
1454 sprintf(graph_label,labfmt,scaledstep*i);
1455 }
1456 else {
1457 sprintf(graph_label,"%4.1f",scaledstep*i);
1458 }
1459 } else {
1460 sprintf(graph_label,"%4.0f",scaledstep*i);
1461 }
1462 }else {
1463 if(scaledstep < 1){
1464 sprintf(graph_label,"%4.1f %c",scaledstep*i, im->symbol);
1465 } else {
1466 sprintf(graph_label,"%4.0f %c",scaledstep*i, im->symbol);
1467 }
1468 }
1470 gfx_new_text ( canvas,
1471 x0-im->text_prop[TEXT_PROP_AXIS].size/1.5, y0,
1472 im->graph_col[GRC_FONT],
1473 im->text_prop[TEXT_PROP_AXIS].font,
1474 im->text_prop[TEXT_PROP_AXIS].size,
1475 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1476 graph_label );
1477 gfx_new_line ( canvas,
1478 x0-2,y0,
1479 x1+2,y0,
1480 MGRIDWIDTH, im->graph_col[GRC_MGRID] );
1482 } else {
1483 gfx_new_line ( canvas,
1484 x0-1,y0,
1485 x1+1,y0,
1486 GRIDWIDTH, im->graph_col[GRC_GRID] );
1488 }
1489 }
1490 }
1491 return 1;
1492 }
1494 /* logaritmic horizontal grid */
1495 int
1496 horizontal_log_grid(gfx_canvas_t *canvas, image_desc_t *im)
1497 {
1498 double pixpex;
1499 int ii,i;
1500 int minoridx=0, majoridx=0;
1501 char graph_label[100];
1502 double x0,x1,y0,y1;
1503 double value, pixperstep, minstep;
1505 /* find grid spaceing */
1506 pixpex= (double)im->ysize / (log10(im->maxval) - log10(im->minval));
1508 if (isnan(pixpex)) {
1509 return 0;
1510 }
1512 for(i=0;yloglab[i][0] > 0;i++){
1513 minstep = log10(yloglab[i][0]);
1514 for(ii=1;yloglab[i][ii+1] > 0;ii++){
1515 if(yloglab[i][ii+2]==0){
1516 minstep = log10(yloglab[i][ii+1])-log10(yloglab[i][ii]);
1517 break;
1518 }
1519 }
1520 pixperstep = pixpex * minstep;
1521 if(pixperstep > 5){minoridx = i;}
1522 if(pixperstep > 2 * im->text_prop[TEXT_PROP_LEGEND].size){majoridx = i;}
1523 }
1525 x0=im->xorigin;
1526 x1=im->xorigin+im->xsize;
1527 /* paint minor grid */
1528 for (value = pow((double)10, log10(im->minval)
1529 - fmod(log10(im->minval),log10(yloglab[minoridx][0])));
1530 value <= im->maxval;
1531 value *= yloglab[minoridx][0]){
1532 if (value < im->minval) continue;
1533 i=0;
1534 while(yloglab[minoridx][++i] > 0){
1535 y0 = ytr(im,value * yloglab[minoridx][i]);
1536 if (y0 <= im->yorigin - im->ysize) break;
1537 gfx_new_line ( canvas,
1538 x0-1,y0,
1539 x1+1,y0,
1540 GRIDWIDTH, im->graph_col[GRC_GRID] );
1541 }
1542 }
1544 /* paint major grid and labels*/
1545 for (value = pow((double)10, log10(im->minval)
1546 - fmod(log10(im->minval),log10(yloglab[majoridx][0])));
1547 value <= im->maxval;
1548 value *= yloglab[majoridx][0]){
1549 if (value < im->minval) continue;
1550 i=0;
1551 while(yloglab[majoridx][++i] > 0){
1552 y0 = ytr(im,value * yloglab[majoridx][i]);
1553 if (y0 <= im->yorigin - im->ysize) break;
1554 gfx_new_line ( canvas,
1555 x0-2,y0,
1556 x1+2,y0,
1557 MGRIDWIDTH, im->graph_col[GRC_MGRID] );
1559 sprintf(graph_label,"%3.0e",value * yloglab[majoridx][i]);
1560 gfx_new_text ( canvas,
1561 x0-im->text_prop[TEXT_PROP_AXIS].size/1.5, y0,
1562 im->graph_col[GRC_FONT],
1563 im->text_prop[TEXT_PROP_AXIS].font,
1564 im->text_prop[TEXT_PROP_AXIS].size,
1565 im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1566 graph_label );
1567 }
1568 }
1569 return 1;
1570 }
1573 void
1574 vertical_grid(
1575 gfx_canvas_t *canvas,
1576 image_desc_t *im )
1577 {
1578 int xlab_sel; /* which sort of label and grid ? */
1579 time_t ti, tilab;
1580 long factor;
1581 char graph_label[100];
1582 double x0,y0,y1; /* points for filled graph and more*/
1585 /* the type of time grid is determined by finding
1586 the number of seconds per pixel in the graph */
1589 if(im->xlab_user.minsec == -1){
1590 factor=(im->end - im->start)/im->xsize;
1591 xlab_sel=0;
1592 while ( xlab[xlab_sel+1].minsec != -1
1593 && xlab[xlab_sel+1].minsec <= factor){ xlab_sel++; }
1594 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
1595 im->xlab_user.gridst = xlab[xlab_sel].gridst;
1596 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
1597 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
1598 im->xlab_user.labtm = xlab[xlab_sel].labtm;
1599 im->xlab_user.labst = xlab[xlab_sel].labst;
1600 im->xlab_user.precis = xlab[xlab_sel].precis;
1601 im->xlab_user.stst = xlab[xlab_sel].stst;
1602 }
1604 /* y coords are the same for every line ... */
1605 y0 = im->yorigin;
1606 y1 = im->yorigin-im->ysize;
1609 /* paint the minor grid */
1610 for(ti = find_first_time(im->start,
1611 im->xlab_user.gridtm,
1612 im->xlab_user.gridst);
1613 ti < im->end;
1614 ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
1615 ){
1616 /* are we inside the graph ? */
1617 if (ti < im->start || ti > im->end) continue;
1618 x0 = xtr(im,ti);
1619 gfx_new_line(canvas,x0,y0+1, x0,y1-1,GRIDWIDTH, im->graph_col[GRC_GRID]);
1621 }
1623 /* paint the major grid */
1624 for(ti = find_first_time(im->start,
1625 im->xlab_user.mgridtm,
1626 im->xlab_user.mgridst);
1627 ti < im->end;
1628 ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
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+2, x0,y1-2,MGRIDWIDTH, im->graph_col[GRC_MGRID]);
1635 }
1636 /* paint the labels below the graph */
1637 for(ti = find_first_time(im->start,
1638 im->xlab_user.labtm,
1639 im->xlab_user.labst);
1640 ti <= im->end;
1641 ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
1642 ){
1643 tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
1645 #if HAVE_STRFTIME
1646 strftime(graph_label,99,im->xlab_user.stst,localtime(&tilab));
1647 #else
1648 # error "your libc has no strftime I guess we'll abort the exercise here."
1649 #endif
1650 gfx_new_text ( canvas,
1651 xtr(im,tilab), y0+im->text_prop[TEXT_PROP_AXIS].size/1.5,
1652 im->graph_col[GRC_FONT],
1653 im->text_prop[TEXT_PROP_AXIS].font,
1654 im->text_prop[TEXT_PROP_AXIS].size,
1655 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP,
1656 graph_label );
1658 }
1660 }
1663 void
1664 axis_paint(
1665 image_desc_t *im,
1666 gfx_canvas_t *canvas
1667 )
1668 {
1669 /* draw x and y axis */
1670 gfx_new_line ( canvas, im->xorigin+im->xsize,im->yorigin,
1671 im->xorigin+im->xsize,im->yorigin-im->ysize,
1672 GRIDWIDTH, im->graph_col[GRC_GRID]);
1674 gfx_new_line ( canvas, im->xorigin,im->yorigin-im->ysize,
1675 im->xorigin+im->xsize,im->yorigin-im->ysize,
1676 GRIDWIDTH, im->graph_col[GRC_GRID]);
1678 gfx_new_line ( canvas, im->xorigin-4,im->yorigin,
1679 im->xorigin+im->xsize+4,im->yorigin,
1680 MGRIDWIDTH, im->graph_col[GRC_GRID]);
1682 gfx_new_line ( canvas, im->xorigin,im->yorigin+4,
1683 im->xorigin,im->yorigin-im->ysize-4,
1684 MGRIDWIDTH, im->graph_col[GRC_GRID]);
1687 /* arrow for X axis direction */
1688 gfx_new_area ( canvas,
1689 im->xorigin+im->xsize+4, im->yorigin-3,
1690 im->xorigin+im->xsize+4, im->yorigin+3,
1691 im->xorigin+im->xsize+9, im->yorigin,
1692 im->graph_col[GRC_ARROW]);
1696 }
1698 void
1699 grid_paint(
1700 image_desc_t *im,
1701 gfx_canvas_t *canvas
1703 )
1704 {
1705 long i;
1706 int boxH=8, boxV=8;
1707 int res=0;
1708 double x0,x1,x2,x3,y0,y1,y2,y3; /* points for filled graph and more*/
1709 gfx_node_t *node;
1712 /* draw 3d border */
1713 node = gfx_new_area (canvas, 0,im->ygif, 0,0, im->xgif, 0,im->graph_col[GRC_SHADEA]);
1714 gfx_add_point( node , im->xgif - 2, 2 );
1715 gfx_add_point( node , 2,2 );
1716 gfx_add_point( node , 2,im->ygif-2 );
1717 gfx_add_point( node , 0,im->ygif );
1719 node = gfx_new_area (canvas, 0,im->ygif, im->xgif,im->ygif, im->xgif,0,im->graph_col[GRC_SHADEB]);
1720 gfx_add_point( node , im->xgif - 2, 2 );
1721 gfx_add_point( node , im->xgif-2,im->ygif-2 );
1722 gfx_add_point( node , 2,im->ygif-2 );
1723 gfx_add_point( node , 0,im->ygif );
1726 if (im->draw_x_grid == 1 )
1727 vertical_grid(canvas, im);
1729 if (im->draw_y_grid == 1){
1730 if(im->logarithmic){
1731 res = horizontal_log_grid(canvas,im);
1732 } else {
1733 res = horizontal_grid(canvas,im);
1734 }
1736 /* dont draw horizontal grid if there is no min and max val */
1737 if (! res ) {
1738 char *nodata = "No Data found";
1739 gfx_new_text(canvas,im->xgif/2, (2*im->yorigin-im->ysize) / 2,
1740 im->graph_col[GRC_FONT],
1741 im->text_prop[TEXT_PROP_AXIS].font,
1742 im->text_prop[TEXT_PROP_AXIS].size,
1743 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
1744 nodata );
1745 }
1746 }
1748 /* yaxis description */
1749 gfx_new_text( canvas,
1750 7, (im->yorigin - im->ysize/2),
1751 im->graph_col[GRC_FONT],
1752 im->text_prop[TEXT_PROP_AXIS].font,
1753 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 270.0,
1754 GFX_H_CENTER, GFX_V_CENTER,
1755 im->ylegend);
1757 /* graph title */
1758 gfx_new_text( canvas,
1759 im->xgif/2, im->text_prop[TEXT_PROP_TITLE].size*1.5,
1760 im->graph_col[GRC_FONT],
1761 im->text_prop[TEXT_PROP_TITLE].font,
1762 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
1763 GFX_H_CENTER, GFX_V_CENTER,
1764 im->title);
1766 /* graph labels */
1767 if( !(im->extra_flags & NOLEGEND) ) {
1768 for(i=0;i<im->gdes_c;i++){
1769 if(im->gdes[i].legend[0] =='\0')
1770 continue;
1772 if(im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT){
1773 x0 = im->gdes[i].leg_x;
1774 y0 = im->gdes[i].leg_y+1.0;
1775 x1 = x0+boxH;
1776 x2 = x0+boxH;
1777 x3 = x0;
1778 y1 = y0;
1779 y2 = y0+boxV;
1780 y3 = y0+boxV;
1781 node = gfx_new_area(canvas, x0,y0,x1,y1,x2,y2 ,im->gdes[i].col);
1782 gfx_add_point ( node, x3, y3 );
1783 gfx_add_point ( node, x0, y0 );
1784 node = gfx_new_line(canvas, x0,y0,x1,y1 ,GRIDWIDTH, im->graph_col[GRC_FRAME]);
1785 gfx_add_point ( node, x2, y2 );
1786 gfx_add_point ( node, x3, y3 );
1787 gfx_add_point ( node, x0, y0 );
1789 gfx_new_text ( canvas, x0+boxH+6, (y0+y2) / 2.0,
1790 im->graph_col[GRC_FONT],
1791 im->text_prop[TEXT_PROP_AXIS].font,
1792 im->text_prop[TEXT_PROP_AXIS].size,
1793 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
1794 im->gdes[i].legend );
1796 } else {
1797 x0 = im->gdes[i].leg_x;
1798 y0 = im->gdes[i].leg_y;
1800 gfx_new_text ( canvas, x0, (y0+y2) / 2.0,
1801 im->graph_col[GRC_FONT],
1802 im->text_prop[TEXT_PROP_AXIS].font,
1803 im->text_prop[TEXT_PROP_AXIS].size,
1804 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
1805 im->gdes[i].legend );
1807 }
1808 }
1809 }
1810 }
1813 /*****************************************************
1814 * lazy check make sure we rely need to create this graph
1815 *****************************************************/
1817 int lazy_check(image_desc_t *im){
1818 FILE *fd = NULL;
1819 int size = 1;
1820 struct stat gifstat;
1822 if (im->lazy == 0) return 0; /* no lazy option */
1823 if (stat(im->graphfile,&gifstat) != 0)
1824 return 0; /* can't stat */
1825 /* one pixel in the existing graph is more then what we would
1826 change here ... */
1827 if (time(NULL) - gifstat.st_mtime >
1828 (im->end - im->start) / im->xsize)
1829 return 0;
1830 if ((fd = fopen(im->graphfile,"rb")) == NULL)
1831 return 0; /* the file does not exist */
1832 switch (im->imgformat) {
1833 case IF_GIF:
1834 size = GifSize(fd,&(im->xgif),&(im->ygif));
1835 break;
1836 case IF_PNG:
1837 size = PngSize(fd,&(im->xgif),&(im->ygif));
1838 break;
1839 }
1840 fclose(fd);
1841 return size;
1842 }
1845 /* draw that picture thing ... */
1846 int
1847 graph_paint(image_desc_t *im, char ***calcpr)
1848 {
1849 int i,ii;
1850 int lazy = lazy_check(im);
1851 FILE *fo;
1852 gfx_canvas_t *canvas;
1853 gfx_node_t *node;
1855 double areazero = 0.0;
1856 enum gf_en stack_gf = GF_PRINT;
1857 graph_desc_t *lastgdes = NULL;
1859 /* if we are lazy and there is nothing to PRINT ... quit now */
1860 if (lazy && im->prt_c==0) return 0;
1862 /* pull the data from the rrd files ... */
1864 if(data_fetch(im)==-1)
1865 return -1;
1867 /* evaluate VDEF and CDEF operations ... */
1868 if(data_calc(im)==-1)
1869 return -1;
1871 /* calculate and PRINT and GPRINT definitions. We have to do it at
1872 * this point because it will affect the length of the legends
1873 * if there are no graph elements we stop here ...
1874 * if we are lazy, try to quit ...
1875 */
1876 i=print_calc(im,calcpr);
1877 if(i<0) return -1;
1878 if(i==0 || lazy) return 0;
1880 /* get actual drawing data and find min and max values*/
1881 if(data_proc(im)==-1)
1882 return -1;
1884 if(!im->logarithmic){si_unit(im);} /* identify si magnitude Kilo, Mega Giga ? */
1886 if(!im->rigid && ! im->logarithmic)
1887 expand_range(im); /* make sure the upper and lower limit are
1888 sensible values */
1890 /* init xtr and ytr */
1891 /* determine the actual size of the gif to draw. The size given
1892 on the cmdline is the graph area. But we need more as we have
1893 draw labels and other things outside the graph area */
1896 im->xorigin = 10 + 9 * im->text_prop[TEXT_PROP_LEGEND].size;
1898 xtr(im,0);
1900 im->yorigin = 10 + im->ysize;
1902 ytr(im,DNAN);
1904 if(im->title[0] != '\0')
1905 im->yorigin += im->text_prop[TEXT_PROP_TITLE].size*3+4;
1907 im->xgif=20+im->xsize + im->xorigin;
1908 im->ygif= im->yorigin+2* im->text_prop[TEXT_PROP_LEGEND].size;
1910 /* determine where to place the legends onto the graphics.
1911 and set im->ygif to match space requirements for text */
1912 if(leg_place(im)==-1)
1913 return -1;
1915 canvas=gfx_new_canvas();
1917 /* the actual graph is created by going through the individual
1918 graph elements and then drawing them */
1920 node=gfx_new_area ( canvas,
1921 0, 0,
1922 im->xgif, 0,
1923 im->xgif, im->ygif,
1924 im->graph_col[GRC_BACK]);
1926 gfx_add_point(node,0, im->ygif);
1928 node=gfx_new_area ( canvas,
1929 im->xorigin, im->yorigin,
1930 im->xorigin + im->xsize, im->yorigin,
1931 im->xorigin + im->xsize, im->yorigin-im->ysize,
1932 im->graph_col[GRC_CANVAS]);
1934 gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
1937 if (im->minval > 0.0)
1938 areazero = im->minval;
1939 if (im->maxval < 0.0)
1940 areazero = im->maxval;
1942 axis_paint(im,canvas);
1945 for(i=0;i<im->gdes_c;i++){
1946 switch(im->gdes[i].gf){
1947 case GF_CDEF:
1948 case GF_VDEF:
1949 case GF_DEF:
1950 case GF_PRINT:
1951 case GF_GPRINT:
1952 case GF_COMMENT:
1953 case GF_HRULE:
1954 case GF_VRULE:
1955 break;
1956 case GF_TICK:
1957 for (ii = 0; ii < im->xsize; ii++)
1958 {
1959 if (!isnan(im->gdes[i].p_data[ii]) &&
1960 im->gdes[i].p_data[ii] > 0.0)
1961 {
1962 /* generate a tick */
1963 gfx_new_line(canvas, im -> xorigin + ii,
1964 im -> yorigin - (im -> gdes[i].yrule * im -> ysize),
1965 im -> xorigin + ii,
1966 im -> yorigin,
1967 1.0,
1968 im -> gdes[i].col );
1969 }
1970 }
1971 break;
1972 case GF_LINE:
1973 case GF_AREA:
1974 stack_gf = im->gdes[i].gf;
1975 case GF_STACK:
1976 /* fix data points at oo and -oo */
1977 for(ii=0;ii<im->xsize;ii++){
1978 if (isinf(im->gdes[i].p_data[ii])){
1979 if (im->gdes[i].p_data[ii] > 0) {
1980 im->gdes[i].p_data[ii] = im->maxval ;
1981 } else {
1982 im->gdes[i].p_data[ii] = im->minval ;
1983 }
1985 }
1986 } /* for */
1988 if (im->gdes[i].col != 0x0){
1989 /* GF_LINE and friend */
1990 if(stack_gf == GF_LINE ){
1991 node = NULL;
1992 for(ii=1;ii<im->xsize;ii++){
1993 if ( ! isnan(im->gdes[i].p_data[ii-1])
1994 && ! isnan(im->gdes[i].p_data[ii])){
1995 if (node == NULL){
1996 node = gfx_new_line(canvas,
1997 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
1998 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
1999 im->gdes[i].linewidth,
2000 im->gdes[i].col);
2001 } else {
2002 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2003 }
2004 } else {
2005 node = NULL;
2006 }
2007 }
2008 } else {
2009 int area_start=-1;
2010 node = NULL;
2011 for(ii=1;ii<im->xsize;ii++){
2012 /* open an area */
2013 if ( ! isnan(im->gdes[i].p_data[ii-1])
2014 && ! isnan(im->gdes[i].p_data[ii])){
2015 if (node == NULL){
2016 float ybase = 0.0;
2017 if (im->gdes[i].gf == GF_STACK) {
2018 ybase = ytr(im,lastgdes->p_data[ii-1]);
2019 } else {
2020 ybase = ytr(im,areazero);
2021 }
2022 area_start = ii-1;
2023 node = gfx_new_area(canvas,
2024 ii-1+im->xorigin,ybase,
2025 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2026 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2027 im->gdes[i].col
2028 );
2029 } else {
2030 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2031 }
2032 }
2034 if ( node != NULL && (ii+1==im->xsize || isnan(im->gdes[i].p_data[ii]) )){
2035 /* GF_AREA STACK type*/
2036 if (im->gdes[i].gf == GF_STACK ) {
2037 int iii;
2038 for (iii=ii-1;iii>area_start;iii--){
2039 gfx_add_point(node,iii+im->xorigin,ytr(im,lastgdes->p_data[iii]));
2040 }
2041 } else {
2042 gfx_add_point(node,ii+im->xorigin,ytr(im,areazero));
2043 };
2044 node=NULL;
2045 };
2046 }
2047 } /* else GF_LINE */
2048 } /* if color != 0x0 */
2049 /* make sure we do not run into trouble when stacking on NaN */
2050 for(ii=0;ii<im->xsize;ii++){
2051 if (isnan(im->gdes[i].p_data[ii])) {
2052 double ybase = 0.0;
2053 if (lastgdes) {
2054 ybase = ytr(im,lastgdes->p_data[ii-1]);
2055 };
2056 if (isnan(ybase) || !lastgdes ){
2057 ybase = ytr(im,areazero);
2058 }
2059 im->gdes[i].p_data[ii] = ybase;
2060 }
2061 }
2062 lastgdes = &(im->gdes[i]);
2063 break;
2064 } /* switch */
2065 }
2066 grid_paint(im,canvas);
2068 /* the RULES are the last thing to paint ... */
2069 for(i=0;i<im->gdes_c;i++){
2071 switch(im->gdes[i].gf){
2072 case GF_HRULE:
2073 printf("DEBUG: HRULE at %f\n",im->gdes[i].yrule);
2074 if(isnan(im->gdes[i].yrule)) { /* fetch variable */
2075 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2076 };
2077 if(im->gdes[i].yrule >= im->minval
2078 && im->gdes[i].yrule <= im->maxval)
2079 gfx_new_line(canvas,
2080 im->xorigin,ytr(im,im->gdes[i].yrule),
2081 im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2082 1.0,im->gdes[i].col);
2083 break;
2084 case GF_VRULE:
2085 if(im->gdes[i].xrule == 0) { /* fetch variable */
2086 im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
2087 };
2088 if(im->gdes[i].xrule >= im->start
2089 && im->gdes[i].xrule <= im->end)
2090 gfx_new_line(canvas,
2091 xtr(im,im->gdes[i].xrule),im->yorigin,
2092 xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2093 1.0,im->gdes[i].col);
2094 break;
2095 default:
2096 break;
2097 }
2098 }
2101 if (strcmp(im->graphfile,"-")==0) {
2102 #ifdef WIN32
2103 /* Change translation mode for stdout to BINARY */
2104 _setmode( _fileno( stdout ), O_BINARY );
2105 #endif
2106 fo = stdout;
2107 } else {
2108 if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2109 rrd_set_error("Opening '%s' for write: %s",im->graphfile,
2110 strerror(errno));
2111 return (-1);
2112 }
2113 }
2114 switch (im->imgformat) {
2115 case IF_GIF:
2116 break;
2117 case IF_PNG:
2118 gfx_render_png (canvas,im->xgif,im->ygif,im->zoom,0x0,fo);
2119 break;
2120 }
2121 if (strcmp(im->graphfile,"-") != 0)
2122 fclose(fo);
2124 gfx_destroy(canvas);
2125 return 0;
2126 }
2129 /*****************************************************
2130 * graph stuff
2131 *****************************************************/
2133 int
2134 gdes_alloc(image_desc_t *im){
2136 long def_step = (im->end-im->start)/im->xsize;
2138 if (im->step > def_step) /* step can be increassed ... no decreassed */
2139 def_step = im->step;
2141 im->gdes_c++;
2143 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2144 * sizeof(graph_desc_t)))==NULL){
2145 rrd_set_error("realloc graph_descs");
2146 return -1;
2147 }
2150 im->gdes[im->gdes_c-1].step=def_step;
2151 im->gdes[im->gdes_c-1].start=im->start;
2152 im->gdes[im->gdes_c-1].end=im->end;
2153 im->gdes[im->gdes_c-1].vname[0]='\0';
2154 im->gdes[im->gdes_c-1].data=NULL;
2155 im->gdes[im->gdes_c-1].ds_namv=NULL;
2156 im->gdes[im->gdes_c-1].data_first=0;
2157 im->gdes[im->gdes_c-1].p_data=NULL;
2158 im->gdes[im->gdes_c-1].rpnp=NULL;
2159 im->gdes[im->gdes_c-1].col = 0x0;
2160 im->gdes[im->gdes_c-1].legend[0]='\0';
2161 im->gdes[im->gdes_c-1].rrd[0]='\0';
2162 im->gdes[im->gdes_c-1].ds=-1;
2163 im->gdes[im->gdes_c-1].p_data=NULL;
2164 return 0;
2165 }
2167 /* copies input untill the first unescaped colon is found
2168 or until input ends. backslashes have to be escaped as well */
2169 int
2170 scan_for_col(char *input, int len, char *output)
2171 {
2172 int inp,outp=0;
2173 for (inp=0;
2174 inp < len &&
2175 input[inp] != ':' &&
2176 input[inp] != '\0';
2177 inp++){
2178 if (input[inp] == '\\' &&
2179 input[inp+1] != '\0' &&
2180 (input[inp+1] == '\\' ||
2181 input[inp+1] == ':')){
2182 output[outp++] = input[++inp];
2183 }
2184 else {
2185 output[outp++] = input[inp];
2186 }
2187 }
2188 output[outp] = '\0';
2189 return inp;
2190 }
2192 /* Some surgery done on this function, it became ridiculously big.
2193 ** Things moved:
2194 ** - initializing now in rrd_graph_init()
2195 ** - options parsing now in rrd_graph_options()
2196 ** - script parsing now in rrd_graph_script()
2197 */
2198 int
2199 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize)
2200 {
2201 image_desc_t im;
2203 rrd_graph_init(&im);
2205 rrd_graph_options(argc,argv,&im);
2206 if (rrd_test_error()) return -1;
2208 if (strlen(argv[optind])>=MAXPATH) {
2209 rrd_set_error("filename (including path) too long");
2210 return -1;
2211 }
2212 strncpy(im.graphfile,argv[optind],MAXPATH-1);
2213 im.graphfile[MAXPATH-1]='\0';
2215 rrd_graph_script(argc,argv,&im);
2216 if (rrd_test_error()) return -1;
2218 /* Everything is now read and the actual work can start */
2220 (*prdata)=NULL;
2221 if (graph_paint(&im,prdata)==-1){
2222 im_free(&im);
2223 return -1;
2224 }
2226 /* The image is generated and needs to be output.
2227 ** Also, if needed, print a line with information about the image.
2228 */
2230 *xsize=im.xgif;
2231 *ysize=im.ygif;
2232 if (im.imginfo) {
2233 char *filename;
2234 if (!(*prdata)) {
2235 /* maybe prdata is not allocated yet ... lets do it now */
2236 if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
2237 rrd_set_error("malloc imginfo");
2238 return -1;
2239 };
2240 }
2241 if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
2242 ==NULL){
2243 rrd_set_error("malloc imginfo");
2244 return -1;
2245 }
2246 filename=im.graphfile+strlen(im.graphfile);
2247 while(filename > im.graphfile) {
2248 if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
2249 filename--;
2250 }
2252 sprintf((*prdata)[0],im.imginfo,filename,(long)(im.zoom*im.xgif),(long)(im.zoom*im.ygif));
2253 }
2254 im_free(&im);
2255 return 0;
2256 }
2258 void
2259 rrd_graph_init(image_desc_t *im)
2260 {
2261 int i;
2263 im->xlab_user.minsec = -1;
2264 im->xgif=0;
2265 im->ygif=0;
2266 im->xsize = 400;
2267 im->ysize = 100;
2268 im->step = 0;
2269 im->ylegend[0] = '\0';
2270 im->title[0] = '\0';
2271 im->minval = DNAN;
2272 im->maxval = DNAN;
2273 im->interlaced = 0;
2274 im->unitsexponent= 9999;
2275 im->extra_flags= 0;
2276 im->rigid = 0;
2277 im->imginfo = NULL;
2278 im->lazy = 0;
2279 im->logarithmic = 0;
2280 im->ygridstep = DNAN;
2281 im->draw_x_grid = 1;
2282 im->draw_y_grid = 1;
2283 im->base = 1000;
2284 im->prt_c = 0;
2285 im->gdes_c = 0;
2286 im->gdes = NULL;
2287 im->zoom = 1.0;
2288 im->imgformat = IF_GIF; /* we default to GIF output */
2290 for(i=0;i<DIM(graph_col);i++)
2291 im->graph_col[i]=graph_col[i];
2293 for(i=0;i<DIM(text_prop);i++){
2294 im->text_prop[i].size = text_prop[i].size;
2295 im->text_prop[i].font = text_prop[i].font;
2296 }
2297 }
2299 void
2300 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
2301 {
2302 int stroff;
2303 char *parsetime_error = NULL;
2304 char scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
2305 time_t start_tmp=0,end_tmp=0;
2306 long long_tmp;
2307 struct time_value start_tv, end_tv;
2308 gfx_color_t color;
2310 parsetime("end-24h", &start_tv);
2311 parsetime("now", &end_tv);
2313 while (1){
2314 static struct option long_options[] =
2315 {
2316 {"start", required_argument, 0, 's'},
2317 {"end", required_argument, 0, 'e'},
2318 {"x-grid", required_argument, 0, 'x'},
2319 {"y-grid", required_argument, 0, 'y'},
2320 {"vertical-label",required_argument,0,'v'},
2321 {"width", required_argument, 0, 'w'},
2322 {"height", required_argument, 0, 'h'},
2323 {"interlaced", no_argument, 0, 'i'},
2324 {"upper-limit",required_argument, 0, 'u'},
2325 {"lower-limit",required_argument, 0, 'l'},
2326 {"rigid", no_argument, 0, 'r'},
2327 {"base", required_argument, 0, 'b'},
2328 {"logarithmic",no_argument, 0, 'o'},
2329 {"color", required_argument, 0, 'c'},
2330 {"font", required_argument, 0, 'n'},
2331 {"title", required_argument, 0, 't'},
2332 {"imginfo", required_argument, 0, 'f'},
2333 {"imgformat", required_argument, 0, 'a'},
2334 {"lazy", no_argument, 0, 'z'},
2335 {"zoom", required_argument, 0, 'm'},
2336 {"no-legend", no_argument, 0, 'g'},
2337 {"alt-y-grid", no_argument, 0, 257 },
2338 {"alt-autoscale", no_argument, 0, 258 },
2339 {"alt-autoscale-max", no_argument, 0, 259 },
2340 {"units-exponent",required_argument, 0, 260},
2341 {"step", required_argument, 0, 261},
2342 {0,0,0,0}};
2343 int option_index = 0;
2344 int opt;
2347 opt = getopt_long(argc, argv,
2348 "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:z:g",
2349 long_options, &option_index);
2351 if (opt == EOF)
2352 break;
2354 switch(opt) {
2355 case 257:
2356 im->extra_flags |= ALTYGRID;
2357 break;
2358 case 258:
2359 im->extra_flags |= ALTAUTOSCALE;
2360 break;
2361 case 259:
2362 im->extra_flags |= ALTAUTOSCALE_MAX;
2363 break;
2364 case 'g':
2365 im->extra_flags |= NOLEGEND;
2366 break;
2367 case 260:
2368 im->unitsexponent = atoi(optarg);
2369 break;
2370 case 261:
2371 im->step = atoi(optarg);
2372 break;
2373 case 's':
2374 if ((parsetime_error = parsetime(optarg, &start_tv))) {
2375 rrd_set_error( "start time: %s", parsetime_error );
2376 return;
2377 }
2378 break;
2379 case 'e':
2380 if ((parsetime_error = parsetime(optarg, &end_tv))) {
2381 rrd_set_error( "end time: %s", parsetime_error );
2382 return;
2383 }
2384 break;
2385 case 'x':
2386 if(strcmp(optarg,"none") == 0){
2387 im->draw_x_grid=0;
2388 break;
2389 };
2391 if(sscanf(optarg,
2392 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
2393 scan_gtm,
2394 &im->xlab_user.gridst,
2395 scan_mtm,
2396 &im->xlab_user.mgridst,
2397 scan_ltm,
2398 &im->xlab_user.labst,
2399 &im->xlab_user.precis,
2400 &stroff) == 7 && stroff != 0){
2401 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
2402 if((im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
2403 rrd_set_error("unknown keyword %s",scan_gtm);
2404 return;
2405 } else if ((im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
2406 rrd_set_error("unknown keyword %s",scan_mtm);
2407 return;
2408 } else if ((im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
2409 rrd_set_error("unknown keyword %s",scan_ltm);
2410 return;
2411 }
2412 im->xlab_user.minsec = 1;
2413 im->xlab_user.stst = im->xlab_form;
2414 } else {
2415 rrd_set_error("invalid x-grid format");
2416 return;
2417 }
2418 break;
2419 case 'y':
2421 if(strcmp(optarg,"none") == 0){
2422 im->draw_y_grid=0;
2423 break;
2424 };
2426 if(sscanf(optarg,
2427 "%lf:%d",
2428 &im->ygridstep,
2429 &im->ylabfact) == 2) {
2430 if(im->ygridstep<=0){
2431 rrd_set_error("grid step must be > 0");
2432 return;
2433 } else if (im->ylabfact < 1){
2434 rrd_set_error("label factor must be > 0");
2435 return;
2436 }
2437 } else {
2438 rrd_set_error("invalid y-grid format");
2439 return;
2440 }
2441 break;
2442 case 'v':
2443 strncpy(im->ylegend,optarg,150);
2444 im->ylegend[150]='\0';
2445 break;
2446 case 'u':
2447 im->maxval = atof(optarg);
2448 break;
2449 case 'l':
2450 im->minval = atof(optarg);
2451 break;
2452 case 'b':
2453 im->base = atol(optarg);
2454 if(im->base != 1024 && im->base != 1000 ){
2455 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
2456 return;
2457 }
2458 break;
2459 case 'w':
2460 long_tmp = atol(optarg);
2461 if (long_tmp < 10) {
2462 rrd_set_error("width below 10 pixels");
2463 return;
2464 }
2465 im->xsize = long_tmp;
2466 break;
2467 case 'h':
2468 long_tmp = atol(optarg);
2469 if (long_tmp < 10) {
2470 rrd_set_error("height below 10 pixels");
2471 return;
2472 }
2473 im->ysize = long_tmp;
2474 break;
2475 case 'i':
2476 im->interlaced = 1;
2477 break;
2478 case 'r':
2479 im->rigid = 1;
2480 break;
2481 case 'f':
2482 im->imginfo = optarg;
2483 break;
2484 case 'a':
2485 if((im->imgformat = if_conv(optarg)) == -1) {
2486 rrd_set_error("unsupported graphics format '%s'",optarg);
2487 return;
2488 }
2489 break;
2490 case 'z':
2491 im->lazy = 1;
2492 break;
2493 case 'o':
2494 im->logarithmic = 1;
2495 if (isnan(im->minval))
2496 im->minval=1;
2497 break;
2498 case 'c':
2499 if(sscanf(optarg,
2500 "%10[A-Z]#%8x",
2501 col_nam,&color) == 2){
2502 int ci;
2503 if((ci=grc_conv(col_nam)) != -1){
2504 im->graph_col[ci]=color;
2505 } else {
2506 rrd_set_error("invalid color name '%s'",col_nam);
2507 }
2508 } else {
2509 rrd_set_error("invalid color def format");
2510 return -1;
2511 }
2512 break;
2513 case 'n':{
2514 char *prop = "";
2515 double size = 1;
2516 char *font = "dummy";
2518 if(sscanf(optarg,
2519 "%10[A-Z]:%lf:%s",
2520 prop,&size,font) == 3){
2521 int sindex;
2522 if((sindex=text_prop_conv(prop)) != -1){
2523 im->text_prop[sindex].size=size;
2524 im->text_prop[sindex].font=font;
2526 } else {
2527 rrd_set_error("invalid color name '%s'",col_nam);
2528 }
2529 } else {
2530 rrd_set_error("invalid text property format");
2531 return;
2532 }
2533 break;
2534 }
2535 case 'm':
2536 im->zoom= atof(optarg);
2537 if (im->zoom <= 0.0) {
2538 rrd_set_error("zoom factor must be > 0");
2539 return;
2540 }
2541 break;
2542 case 't':
2543 strncpy(im->title,optarg,150);
2544 im->title[150]='\0';
2545 break;
2547 case '?':
2548 if (optopt != 0)
2549 rrd_set_error("unknown option '%c'", optopt);
2550 else
2551 rrd_set_error("unknown option '%s'",argv[optind-1]);
2552 return;
2553 }
2554 }
2556 if (optind >= argc) {
2557 rrd_set_error("missing filename");
2558 return;
2559 }
2561 if (im->logarithmic == 1 && (im->minval <= 0 || isnan(im->minval))){
2562 rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");
2563 return;
2564 }
2566 if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
2567 /* error string is set in parsetime.c */
2568 return;
2569 }
2571 if (start_tmp < 3600*24*365*10){
2572 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
2573 return;
2574 }
2576 if (end_tmp < start_tmp) {
2577 rrd_set_error("start (%ld) should be less than end (%ld)",
2578 start_tmp, end_tmp);
2579 return;
2580 }
2582 im->start = start_tmp;
2583 im->end = end_tmp;
2584 }
2586 void
2587 rrd_graph_script(int argc, char *argv[], image_desc_t *im)
2588 {
2589 int i;
2590 char symname[100];
2591 int linepass = 0; /* stack must follow LINE*, AREA or STACK */
2593 for (i=optind+1;i<argc;i++) {
2594 int argstart=0;
2595 int strstart=0;
2596 graph_desc_t *gdp;
2597 char *line;
2598 char funcname[10],vname[MAX_VNAME_LEN+1],sep[1];
2599 double d;
2600 double linewidth;
2601 int j,k,l,m;
2603 /* Each command is one element from *argv[], we call this "line".
2604 **
2605 ** Each command defines the most current gdes inside struct im.
2606 ** In stead of typing "im->gdes[im->gdes_c-1]" we use "gdp".
2607 */
2608 gdes_alloc(im);
2609 gdp=&im->gdes[im->gdes_c-1];
2610 line=argv[i];
2612 /* function:newvname=string[:ds-name:CF] for xDEF
2613 ** function:vname[#color[:string]] for LINEx,AREA,STACK
2614 ** function:vname#color[:num[:string]] for TICK
2615 ** function:vname-or-num#color[:string] for xRULE
2616 ** function:vname:CF:string for xPRINT
2617 ** function:string for COMMENT
2618 */
2619 argstart=0;
2621 sscanf(line, "%10[A-Z0-9]:%n", funcname,&argstart);
2622 if (argstart==0) {
2623 rrd_set_error("Cannot parse function in line: %s",line);
2624 im_free(im);
2625 return;
2626 }
2627 if(sscanf(funcname,"LINE%lf",&linewidth)){
2628 im->gdes[im->gdes_c-1].gf = GF_LINE;
2629 im->gdes[im->gdes_c-1].linewidth = linewidth;
2630 } else {
2631 if ((gdp->gf=gf_conv(funcname))==-1) {
2632 rrd_set_error("'%s' is not a valid function name",funcname);
2633 im_free(im);
2634 return;
2635 }
2636 }
2638 /* If the error string is set, we exit at the end of the switch */
2639 switch (gdp->gf) {
2640 case GF_COMMENT:
2641 if (rrd_graph_legend(gdp,&line[argstart])==0)
2642 rrd_set_error("Cannot parse comment in line: %s",line);
2643 break;
2644 case GF_VRULE:
2645 case GF_HRULE:
2646 j=k=l=m=0;
2647 sscanf(&line[argstart], "%lf%n#%n", &d, &j, &k);
2648 sscanf(&line[argstart], DEF_NAM_FMT "%n#%n", vname, &l, &m);
2649 if (k+m==0) {
2650 rrd_set_error("Cannot parse name or num in line: %s",line);
2651 break;
2652 }
2653 if (j!=0) {
2654 gdp->xrule=d;
2655 gdp->yrule=d;
2656 argstart+=j;
2657 } else if (!rrd_graph_check_vname(im,vname,line)) {
2658 gdp->xrule=0;
2659 gdp->yrule=DNAN;
2660 argstart+=l;
2661 } else break; /* exit due to wrong vname */
2662 if ((j=rrd_graph_color(im,&line[argstart],line,0))==0) break;
2663 argstart+=j;
2664 if (strlen(&line[argstart])!=0) {
2665 if (rrd_graph_legend(gdp,&line[++argstart])==0)
2666 rrd_set_error("Cannot parse comment in line: %s",line);
2667 }
2668 break;
2669 case GF_STACK:
2670 if (linepass==0) {
2671 rrd_set_error("STACK must follow another graphing element");
2672 break;
2673 }
2674 case GF_LINE:
2675 case GF_AREA:
2676 case GF_TICK:
2677 j=k=0;
2678 linepass=1;
2679 sscanf(&line[argstart],DEF_NAM_FMT"%n%1[#:]%n",vname,&j,sep,&k);
2680 if (j+1!=k)
2681 rrd_set_error("Cannot parse vname in line: %s",line);
2682 else if (rrd_graph_check_vname(im,vname,line))
2683 rrd_set_error("Undefined vname '%s' in line: %s",line);
2684 else
2685 k=rrd_graph_color(im,&line[argstart],line,1);
2686 if (rrd_test_error()) break;
2687 argstart=argstart+j+k;
2688 if ((strlen(&line[argstart])!=0)&&(gdp->gf==GF_TICK)) {
2689 j=0;
2690 sscanf(&line[argstart], ":%lf%n", &gdp->yrule,&j);
2691 argstart+=j;
2692 }
2693 if (strlen(&line[argstart])!=0)
2694 if (rrd_graph_legend(gdp,&line[++argstart])==0)
2695 rrd_set_error("Cannot parse legend in line: %s",line);
2696 break;
2697 case GF_PRINT:
2698 im->prt_c++;
2699 case GF_GPRINT:
2700 j=0;
2701 sscanf(&line[argstart], DEF_NAM_FMT ":%n",gdp->vname,&j);
2702 if (j==0) {
2703 rrd_set_error("Cannot parse vname in line: '%s'",line);
2704 break;
2705 }
2706 argstart+=j;
2707 if (rrd_graph_check_vname(im,gdp->vname,line)) return;
2708 j=0;
2709 sscanf(&line[argstart], CF_NAM_FMT ":%n",symname,&j);
2711 k=(j!=0)?rrd_graph_check_CF(im,symname,line):1;
2712 #define VIDX im->gdes[gdp->vidx]
2713 switch (k) {
2714 case -1: /* looks CF but is not really CF */
2715 if (VIDX.gf == GF_VDEF) rrd_clear_error();
2716 break;
2717 case 0: /* CF present and correct */
2718 if (VIDX.gf == GF_VDEF)
2719 rrd_set_error("Don't use CF when printing VDEF");
2720 argstart+=j;
2721 break;
2722 case 1: /* CF not present */
2723 if (VIDX.gf == GF_VDEF) rrd_clear_error();
2724 else rrd_set_error("Printing DEF or CDEF needs CF");
2725 break;
2726 default:
2727 rrd_set_error("Oops, bug in GPRINT scanning");
2728 }
2729 #undef VIDX
2730 if (rrd_test_error()) break;
2732 if (strlen(&line[argstart])!=0) {
2733 if (rrd_graph_legend(gdp,&line[argstart])==0)
2734 rrd_set_error("Cannot parse legend in line: %s",line);
2735 } else rrd_set_error("No legend in (G)PRINT line: %s",line);
2736 strcpy(gdp->format, gdp->legend);
2737 break;
2738 case GF_DEF:
2739 case GF_VDEF:
2740 case GF_CDEF:
2741 j=0;
2742 sscanf(&line[argstart], DEF_NAM_FMT "=%n",gdp->vname,&j);
2743 if (j==0) {
2744 rrd_set_error("Could not parse line: %s",line);
2745 break;
2746 }
2747 if (find_var(im,gdp->vname)!=-1) {
2748 rrd_set_error("Variable '%s' in line '%s' already in use\n",
2749 gdp->vname,line);
2750 break;
2751 }
2752 argstart+=j;
2753 switch (gdp->gf) {
2754 case GF_DEF:
2755 argstart+=scan_for_col(&line[argstart],MAXPATH,gdp->rrd);
2756 j=k=0;
2757 sscanf(&line[argstart],
2758 ":" DS_NAM_FMT ":" CF_NAM_FMT "%n%*s%n",
2759 gdp->ds_nam, symname, &j, &k);
2760 if ((j==0)||(k!=0)) {
2761 rrd_set_error("Cannot parse DS or CF in '%s'",line);
2762 break;
2763 }
2764 rrd_graph_check_CF(im,symname,line);
2765 break;
2766 case GF_VDEF:
2767 j=0;
2768 sscanf(&line[argstart],DEF_NAM_FMT ",%n",vname,&j);
2769 if (j==0) {
2770 rrd_set_error("Cannot parse vname in line '%s'",line);
2771 break;
2772 }
2773 argstart+=j;
2774 if (rrd_graph_check_vname(im,vname,line)) return;
2775 if ( im->gdes[gdp->vidx].gf != GF_DEF
2776 && im->gdes[gdp->vidx].gf != GF_CDEF) {
2777 rrd_set_error("variable '%s' not DEF nor "
2778 "CDEF in VDEF '%s'", vname,gdp->vname);
2779 break;
2780 }
2781 vdef_parse(gdp,&line[argstart+strstart]);
2782 break;
2783 case GF_CDEF:
2784 if (strstr(&line[argstart],":")!=NULL) {
2785 rrd_set_error("Error in RPN, line: %s",line);
2786 break;
2787 }
2788 if ((gdp->rpnp = rpn_parse(
2789 (void *)im,
2790 &line[argstart],
2791 &find_var_wrapper)
2792 )==NULL)
2793 rrd_set_error("invalid rpn expression in: %s",line);
2794 break;
2795 default: break;
2796 }
2797 break;
2798 default: rrd_set_error("Big oops");
2799 }
2800 if (rrd_test_error()) {
2801 im_free(im);
2802 return;
2803 }
2804 }
2806 if (im->gdes_c==0){
2807 rrd_set_error("can't make a graph without contents");
2808 im_free(im); /* ??? is this set ??? */
2809 return;
2810 }
2811 }
2812 int
2813 rrd_graph_check_vname(image_desc_t *im, char *varname, char *err)
2814 {
2815 if ((im->gdes[im->gdes_c-1].vidx=find_var(im,varname))==-1) {
2816 rrd_set_error("Unknown variable '%s' in %s",varname,err);
2817 return -1;
2818 }
2819 return 0;
2820 }
2821 int
2822 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
2823 {
2824 char *color;
2825 graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
2827 color=strstr(var,"#");
2828 if (color==NULL) {
2829 if (optional==0) {
2830 rrd_set_error("Found no color in %s",err);
2831 return 0;
2832 }
2833 return 0;
2834 } else {
2835 int n=0;
2836 char *rest;
2837 gfx_color_t col;
2839 rest=strstr(color,":");
2840 if (rest!=NULL)
2841 n=rest-color;
2842 else
2843 n=strlen(color);
2845 switch (n) {
2846 case 7:
2847 sscanf(color,"#%6x%n",&col,&n);
2848 col = (col << 8) + 0xff /* shift left by 8 */;
2849 if (n!=7) rrd_set_error("Color problem in %s",err);
2850 break;
2851 case 9:
2852 sscanf(color,"#%8x%n",&col,&n);
2853 if (n==9) break;
2854 default:
2855 rrd_set_error("Color problem in %s",err);
2856 }
2857 if (rrd_test_error()) return 0;
2858 gdp->col = col;
2859 return n;
2860 }
2861 }
2862 int
2863 rrd_graph_check_CF(image_desc_t *im, char *symname, char *err)
2864 {
2865 if ((im->gdes[im->gdes_c-1].cf=cf_conv(symname))==-1) {
2866 rrd_set_error("Unknown CF '%s' in %s",symname,err);
2867 return -1;
2868 }
2869 return 0;
2870 }
2871 int
2872 rrd_graph_legend(graph_desc_t *gdp, char *line)
2873 {
2874 int i;
2876 i=scan_for_col(line,FMT_LEG_LEN,gdp->legend);
2878 return (strlen(&line[i])==0);
2879 }
2882 int bad_format(char *fmt) {
2883 char *ptr;
2884 int n=0;
2886 ptr = fmt;
2887 while (*ptr != '\0') {
2888 if (*ptr == '%') {ptr++;
2889 if (*ptr == '\0') return 1;
2890 while ((*ptr >= '0' && *ptr <= '9') || *ptr == '.') {
2891 ptr++;
2892 }
2893 if (*ptr == '\0') return 1;
2894 if (*ptr == 'l') {
2895 ptr++;
2896 n++;
2897 if (*ptr == '\0') return 1;
2898 if (*ptr == 'e' || *ptr == 'f') {
2899 ptr++;
2900 } else { return 1; }
2901 }
2902 else if (*ptr == 's' || *ptr == 'S' || *ptr == '%') { ++ptr; }
2903 else { return 1; }
2904 } else {
2905 ++ptr;
2906 }
2907 }
2908 return (n!=1);
2909 }
2910 int
2911 vdef_parse(gdes,str)
2912 struct graph_desc_t *gdes;
2913 char *str;
2914 {
2915 /* A VDEF currently is either "func" or "param,func"
2916 * so the parsing is rather simple. Change if needed.
2917 */
2918 double param;
2919 char func[30];
2920 int n;
2922 n=0;
2923 sscanf(str,"%le,%29[A-Z]%n",¶m,func,&n);
2924 if (n==strlen(str)) { /* matched */
2925 ;
2926 } else {
2927 n=0;
2928 sscanf(str,"%29[A-Z]%n",func,&n);
2929 if (n==strlen(str)) { /* matched */
2930 param=DNAN;
2931 } else {
2932 rrd_set_error("Unknown function string '%s' in VDEF '%s'"
2933 ,str
2934 ,gdes->vname
2935 );
2936 return -1;
2937 }
2938 }
2939 if (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
2940 else if (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
2941 else if (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
2942 else if (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
2943 else if (!strcmp("TOTAL", func)) gdes->vf.op = VDEF_TOTAL;
2944 else if (!strcmp("FIRST", func)) gdes->vf.op = VDEF_FIRST;
2945 else if (!strcmp("LAST", func)) gdes->vf.op = VDEF_LAST;
2946 else {
2947 rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
2948 ,func
2949 ,gdes->vname
2950 );
2951 return -1;
2952 };
2954 switch (gdes->vf.op) {
2955 case VDEF_PERCENT:
2956 if (isnan(param)) { /* no parameter given */
2957 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
2958 ,func
2959 ,gdes->vname
2960 );
2961 return -1;
2962 };
2963 if (param>=0.0 && param<=100.0) {
2964 gdes->vf.param = param;
2965 gdes->vf.val = DNAN; /* undefined */
2966 gdes->vf.when = 0; /* undefined */
2967 } else {
2968 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
2969 ,param
2970 ,gdes->vname
2971 );
2972 return -1;
2973 };
2974 break;
2975 case VDEF_MAXIMUM:
2976 case VDEF_AVERAGE:
2977 case VDEF_MINIMUM:
2978 case VDEF_TOTAL:
2979 case VDEF_FIRST:
2980 case VDEF_LAST:
2981 if (isnan(param)) {
2982 gdes->vf.param = DNAN;
2983 gdes->vf.val = DNAN;
2984 gdes->vf.when = 0;
2985 } else {
2986 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
2987 ,func
2988 ,gdes->vname
2989 );
2990 return -1;
2991 };
2992 break;
2993 };
2994 return 0;
2995 }
2996 int
2997 vdef_calc(im,gdi)
2998 image_desc_t *im;
2999 int gdi;
3000 {
3001 graph_desc_t *src,*dst;
3002 rrd_value_t *data;
3003 long step,steps;
3005 dst = &im->gdes[gdi];
3006 src = &im->gdes[dst->vidx];
3007 data = src->data + src->ds;
3008 steps = (src->end - src->start) / src->step;
3010 #if 0
3011 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3012 ,src->start
3013 ,src->end
3014 ,steps
3015 );
3016 #endif
3018 switch (dst->vf.op) {
3019 case VDEF_PERCENT: {
3020 rrd_value_t * array;
3021 int field;
3024 if ((array = malloc(steps*sizeof(double)))==NULL) {
3025 rrd_set_error("malloc VDEV_PERCENT");
3026 return -1;
3027 }
3028 for (step=0;step < steps; step++) {
3029 array[step]=data[step*src->ds_cnt];
3030 }
3031 qsort(array,step,sizeof(double),vdef_percent_compar);
3033 field = (steps-1)*dst->vf.param/100;
3034 dst->vf.val = array[field];
3035 dst->vf.when = 0; /* no time component */
3036 #if 0
3037 for(step=0;step<steps;step++)
3038 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3039 #endif
3040 }
3041 break;
3042 case VDEF_MAXIMUM:
3043 step=0;
3044 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3045 if (step == steps) {
3046 dst->vf.val = DNAN;
3047 dst->vf.when = 0;
3048 } else {
3049 dst->vf.val = data[step*src->ds_cnt];
3050 dst->vf.when = src->start + (step+1)*src->step;
3051 }
3052 while (step != steps) {
3053 if (finite(data[step*src->ds_cnt])) {
3054 if (data[step*src->ds_cnt] > dst->vf.val) {
3055 dst->vf.val = data[step*src->ds_cnt];
3056 dst->vf.when = src->start + (step+1)*src->step;
3057 }
3058 }
3059 step++;
3060 }
3061 break;
3062 case VDEF_TOTAL:
3063 case VDEF_AVERAGE: {
3064 int cnt=0;
3065 double sum=0.0;
3066 for (step=0;step<steps;step++) {
3067 if (finite(data[step*src->ds_cnt])) {
3068 sum += data[step*src->ds_cnt];
3069 cnt ++;
3070 };
3071 }
3072 if (cnt) {
3073 if (dst->vf.op == VDEF_TOTAL) {
3074 dst->vf.val = sum*src->step;
3075 dst->vf.when = cnt*src->step; /* not really "when" */
3076 } else {
3077 dst->vf.val = sum/cnt;
3078 dst->vf.when = 0; /* no time component */
3079 };
3080 } else {
3081 dst->vf.val = DNAN;
3082 dst->vf.when = 0;
3083 }
3084 }
3085 break;
3086 case VDEF_MINIMUM:
3087 step=0;
3088 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3089 if (step == steps) {
3090 dst->vf.val = DNAN;
3091 dst->vf.when = 0;
3092 } else {
3093 dst->vf.val = data[step*src->ds_cnt];
3094 dst->vf.when = src->start + (step+1)*src->step;
3095 }
3096 while (step != steps) {
3097 if (finite(data[step*src->ds_cnt])) {
3098 if (data[step*src->ds_cnt] < dst->vf.val) {
3099 dst->vf.val = data[step*src->ds_cnt];
3100 dst->vf.when = src->start + (step+1)*src->step;
3101 }
3102 }
3103 step++;
3104 }
3105 break;
3106 case VDEF_FIRST:
3107 /* The time value returned here is one step before the
3108 * actual time value. This is the start of the first
3109 * non-NaN interval.
3110 */
3111 step=0;
3112 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3113 if (step == steps) { /* all entries were NaN */
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*src->step;
3119 }
3120 break;
3121 case VDEF_LAST:
3122 /* The time value returned here is the
3123 * actual time value. This is the end of the last
3124 * non-NaN interval.
3125 */
3126 step=steps-1;
3127 while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3128 if (step < 0) { /* all entries were NaN */
3129 dst->vf.val = DNAN;
3130 dst->vf.when = 0;
3131 } else {
3132 dst->vf.val = data[step*src->ds_cnt];
3133 dst->vf.when = src->start + (step+1)*src->step;
3134 }
3135 break;
3136 }
3137 return 0;
3138 }
3140 /* NaN < -INF < finite_values < INF */
3141 int
3142 vdef_percent_compar(a,b)
3143 const void *a,*b;
3144 {
3145 /* Equality is not returned; this doesn't hurt except
3146 * (maybe) for a little performance.
3147 */
3149 /* First catch NaN values. They are smallest */
3150 if (isnan( *(double *)a )) return -1;
3151 if (isnan( *(double *)b )) return 1;
3153 /* NaN doesn't reach this part so INF and -INF are extremes.
3154 * The sign from isinf() is compatible with the sign we return
3155 */
3156 if (isinf( *(double *)a )) return isinf( *(double *)a );
3157 if (isinf( *(double *)b )) return isinf( *(double *)b );
3159 /* If we reach this, both values must be finite */
3160 if ( *(double *)a < *(double *)b ) return -1; else return 1;
3161 }