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