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