1 /****************************************************************************
2 * RRDtool 1.0.33 Copyright Tobias Oetiker, 1997 - 2000
3 ****************************************************************************
4 * rrd__graph.c make creates ne rrds
5 ****************************************************************************/
7 #if 0
8 #include "rrd_tool.h"
9 #endif
11 #include <gd.h>
12 #include <gdlucidan10.h>
13 #include <gdlucidab12.h>
14 #include <sys/stat.h>
15 #ifdef WIN32
16 #include <io.h>
17 #include <fcntl.h>
18 #endif
20 #include "rrd_graph.h"
21 #include "rrd_graph_helper.h"
23 #define SmallFont gdLucidaNormal10
24 #define LargeFont gdLucidaBold12
26 /* #define DEBUG */
28 #ifdef DEBUG
29 # define DPRINT(x) (void)(printf x, printf("\n"))
30 #else
31 # define DPRINT(x)
32 #endif
34 xlab_t xlab[] = {
35 {0, TMT_SECOND,30, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
36 {2, TMT_MINUTE,1, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
37 {5, TMT_MINUTE,2, TMT_MINUTE,10, TMT_MINUTE,10, 0,"%H:%M"},
38 {10, TMT_MINUTE,5, TMT_MINUTE,20, TMT_MINUTE,20, 0,"%H:%M"},
39 {30, TMT_MINUTE,10, TMT_HOUR,1, TMT_HOUR,1, 0,"%H:%M"},
40 {60, TMT_MINUTE,30, TMT_HOUR,2, TMT_HOUR,2, 0,"%H:%M"},
41 {180, TMT_HOUR,1, TMT_HOUR,6, TMT_HOUR,6, 0,"%H:%M"},
42 /*{300, TMT_HOUR,3, TMT_HOUR,12, TMT_HOUR,12, 12*3600,"%a %p"}, this looks silly*/
43 {600, TMT_HOUR,6, TMT_DAY,1, TMT_DAY,1, 24*3600,"%a"},
44 {1800, TMT_HOUR,12, TMT_DAY,1, TMT_DAY,2, 24*3600,"%a"},
45 {3600, TMT_DAY,1, TMT_WEEK,1, TMT_WEEK,1, 7*24*3600,"Week %W"},
46 {3*3600, TMT_WEEK,1, TMT_MONTH,1, TMT_WEEK,2, 7*24*3600,"Week %W"},
47 {6*3600, TMT_MONTH,1, TMT_MONTH,1, TMT_MONTH,1, 30*24*3600,"%b"},
48 {48*3600, TMT_MONTH,1, TMT_MONTH,3, TMT_MONTH,3, 30*24*3600,"%b"},
49 {10*24*3600, TMT_YEAR,1, TMT_YEAR,1, TMT_YEAR,1, 365*24*3600,"%y"},
50 {-1,TMT_MONTH,0,TMT_MONTH,0,TMT_MONTH,0,0,""}
51 };
53 /* sensible logarithmic y label intervals ...
54 the first element of each row defines the possible starting points on the
55 y axis ... the other specify the */
57 double yloglab[][12]= {{ 1e9, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
58 { 1e3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
59 { 1e1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
60 /* { 1e1, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, */
61 { 1e1, 1, 2.5, 5, 7.5, 0, 0, 0, 0, 0, 0, 0 },
62 { 1e1, 1, 2, 4, 6, 8, 0, 0, 0, 0, 0, 0 },
63 { 1e1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0 },
64 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }};
66 /* sensible y label intervals ...*/
68 ylab_t ylab[]= {
69 {0.1, {1,2, 5,10}},
70 {0.2, {1,5,10,20}},
71 {0.5, {1,2, 4,10}},
72 {1.0, {1,2, 5,10}},
73 {2.0, {1,5,10,20}},
74 {5.0, {1,2, 4,10}},
75 {10.0, {1,2, 5,10}},
76 {20.0, {1,5,10,20}},
77 {50.0, {1,2, 4,10}},
78 {100.0, {1,2, 5,10}},
79 {200.0, {1,5,10,20}},
80 {500.0, {1,2, 4,10}},
81 {0.0, {0,0,0,0}}};
85 col_trip_t graph_col[] = { /* default colors */
86 {255,255,255,-1}, /* canvas */
87 {245,245,245,-1}, /* background */
88 {200,200,200,-1}, /* shade A */
89 {150,150,150,-1}, /* shade B */
90 {140,140,140,-1}, /* grid */
91 {130,30,30,-1}, /* major grid */
92 {0,0,0,-1}, /* font */
93 {0,0,0,-1}, /* frame */
94 {255,0,0,-1} /*arrow*/
95 };
98 /* translate time values into x coordinates */
99 /*#define xtr(x) (int)((double)im->xorigin \
100 + ((double) im->xsize / (double)(im->end - im->start) ) \
101 * ((double)(x) - im->start)+0.5) */
102 /* initialize with xtr(im,0); */
103 int
104 xtr(image_desc_t *im,time_t mytime){
105 static double pixie;
106 if (mytime==0){
107 pixie = (double) im->xsize / (double)(im->end - im->start);
108 return im->xorigin;
109 }
110 return (int)((double)im->xorigin
111 + pixie * ( mytime - im->start ) );
112 }
114 /* translate data values into y coordinates */
116 /* #define ytr(x) (int)((double)im->yorigin \
117 - ((double) im->ysize / (im->maxval - im->minval) ) \
118 * ((double)(x) - im->minval)+0.5) */
119 int
120 ytr(image_desc_t *im, double value){
121 static double pixie;
122 double yval;
123 if (isnan(value)){
124 if(!im->logarithmic)
125 pixie = (double) im->ysize / (im->maxval - im->minval);
126 else
127 pixie = (double) im->ysize / (log10(im->maxval) - log10(im->minval));
128 yval = im->yorigin;
129 } else if(!im->logarithmic) {
130 yval = im->yorigin - pixie * (value - im->minval) + 0.5;
131 } else {
132 if (value < im->minval) {
133 yval = im->yorigin;
134 } else {
135 yval = im->yorigin - pixie * (log10(value) - log10(im->minval)) + 0.5;
136 }
137 }
138 /* make sure we don't return anything too unreasonable. GD lib can
139 get terribly slow when drawing lines outside its scope. This is
140 especially problematic in connection with the rigid option */
141 if (! im->rigid) {
142 return (int)yval;
143 } else if ((int)yval > im->yorigin) {
144 return im->yorigin+2;
145 } else if ((int) yval < im->yorigin - im->ysize){
146 return im->yorigin - im->ysize - 2;
147 } else {
148 return (int)yval;
149 }
150 }
154 /* conversion function for symbolic entry names */
157 #define conv_if(VV,VVV) \
158 if (strcmp(#VV, string) == 0) return VVV ;
160 enum gf_en gf_conv(char *string){
162 conv_if(PRINT,GF_PRINT)
163 conv_if(GPRINT,GF_GPRINT)
164 conv_if(COMMENT,GF_COMMENT)
165 conv_if(HRULE,GF_HRULE)
166 conv_if(VRULE,GF_VRULE)
167 conv_if(LINE1,GF_LINE1)
168 conv_if(LINE2,GF_LINE2)
169 conv_if(LINE3,GF_LINE3)
170 conv_if(AREA,GF_AREA)
171 conv_if(STACK,GF_STACK)
172 conv_if(TICK,GF_TICK)
173 conv_if(DEF,GF_DEF)
174 conv_if(CDEF,GF_CDEF)
175 conv_if(VDEF,GF_VDEF)
177 return (-1);
178 }
180 enum if_en if_conv(char *string){
182 conv_if(GIF,IF_GIF)
183 conv_if(PNG,IF_PNG)
185 return (-1);
186 }
188 enum tmt_en tmt_conv(char *string){
190 conv_if(SECOND,TMT_SECOND)
191 conv_if(MINUTE,TMT_MINUTE)
192 conv_if(HOUR,TMT_HOUR)
193 conv_if(DAY,TMT_DAY)
194 conv_if(WEEK,TMT_WEEK)
195 conv_if(MONTH,TMT_MONTH)
196 conv_if(YEAR,TMT_YEAR)
197 return (-1);
198 }
200 enum grc_en grc_conv(char *string){
202 conv_if(BACK,GRC_BACK)
203 conv_if(CANVAS,GRC_CANVAS)
204 conv_if(SHADEA,GRC_SHADEA)
205 conv_if(SHADEB,GRC_SHADEB)
206 conv_if(GRID,GRC_GRID)
207 conv_if(MGRID,GRC_MGRID)
208 conv_if(FONT,GRC_FONT)
209 conv_if(FRAME,GRC_FRAME)
210 conv_if(ARROW,GRC_ARROW)
212 return -1;
213 }
215 #undef conv_if
219 int
220 im_free(image_desc_t *im)
221 {
222 long i,ii;
223 if (im == NULL) return 0;
224 for(i=0;i<im->gdes_c;i++){
225 if (im->gdes[i].data_first){
226 /* careful here, because a single pointer can occur several times */
227 free (im->gdes[i].data);
228 if (im->gdes[i].ds_namv){
229 for (ii=0;ii<im->gdes[i].ds_cnt;ii++)
230 free(im->gdes[i].ds_namv[ii]);
231 free(im->gdes[i].ds_namv);
232 }
233 }
234 free (im->gdes[i].p_data);
235 free (im->gdes[i].rpnp);
236 }
237 free(im->gdes);
238 return 0;
239 }
241 /* find SI magnitude symbol for the given number*/
242 void
243 auto_scale(
244 image_desc_t *im, /* image description */
245 double *value,
246 char **symb_ptr,
247 double *magfact
248 )
249 {
251 char *symbol[] = {"a", /* 10e-18 Atto */
252 "f", /* 10e-15 Femto */
253 "p", /* 10e-12 Pico */
254 "n", /* 10e-9 Nano */
255 "u", /* 10e-6 Micro */
256 "m", /* 10e-3 Milli */
257 " ", /* Base */
258 "k", /* 10e3 Kilo */
259 "M", /* 10e6 Mega */
260 "G", /* 10e9 Giga */
261 "T", /* 10e12 Tera */
262 "P", /* 10e15 Peta */
263 "E"};/* 10e18 Exa */
265 int symbcenter = 6;
266 int sindex;
268 if (*value == 0.0 || isnan(*value) ) {
269 sindex = 0;
270 *magfact = 1.0;
271 } else {
272 sindex = floor(log(fabs(*value))/log((double)im->base));
273 *magfact = pow((double)im->base, (double)sindex);
274 (*value) /= (*magfact);
275 }
276 if ( sindex <= symbcenter && sindex >= -symbcenter) {
277 (*symb_ptr) = symbol[sindex+symbcenter];
278 }
279 else {
280 (*symb_ptr) = "?";
281 }
282 }
285 /* find SI magnitude symbol for the numbers on the y-axis*/
286 void
287 si_unit(
288 image_desc_t *im /* image description */
289 )
290 {
292 char symbol[] = {'a', /* 10e-18 Atto */
293 'f', /* 10e-15 Femto */
294 'p', /* 10e-12 Pico */
295 'n', /* 10e-9 Nano */
296 'u', /* 10e-6 Micro */
297 'm', /* 10e-3 Milli */
298 ' ', /* Base */
299 'k', /* 10e3 Kilo */
300 'M', /* 10e6 Mega */
301 'G', /* 10e9 Giga */
302 'T', /* 10e12 Tera */
303 'P', /* 10e15 Peta */
304 'E'};/* 10e18 Exa */
306 int symbcenter = 6;
307 double digits;
309 if (im->unitsexponent != 9999) {
310 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
311 digits = floor(im->unitsexponent / 3);
312 } else {
313 digits = floor( log( max( fabs(im->minval),fabs(im->maxval)))/log((double)im->base));
314 }
315 im->magfact = pow((double)im->base , digits);
317 #ifdef DEBUG
318 printf("digits %6.3f im->magfact %6.3f\n",digits,im->magfact);
319 #endif
321 if ( ((digits+symbcenter) < sizeof(symbol)) &&
322 ((digits+symbcenter) >= 0) )
323 im->symbol = symbol[(int)digits+symbcenter];
324 else
325 im->symbol = ' ';
326 }
328 /* move min and max values around to become sensible */
330 void
331 expand_range(image_desc_t *im)
332 {
333 double sensiblevalues[] ={1000.0,900.0,800.0,750.0,700.0,
334 600.0,500.0,400.0,300.0,250.0,
335 200.0,125.0,100.0,90.0,80.0,
336 75.0,70.0,60.0,50.0,40.0,30.0,
337 25.0,20.0,10.0,9.0,8.0,
338 7.0,6.0,5.0,4.0,3.5,3.0,
339 2.5,2.0,1.8,1.5,1.2,1.0,
340 0.8,0.7,0.6,0.5,0.4,0.3,0.2,0.1,0.0,-1};
342 double scaled_min,scaled_max;
343 double adj;
344 int i;
348 #ifdef DEBUG
349 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
350 im->minval,im->maxval,im->magfact);
351 #endif
353 if (isnan(im->ygridstep)){
354 if(im->extra_flags & ALTAUTOSCALE) {
355 /* measure the amplitude of the function. Make sure that
356 graph boundaries are slightly higher then max/min vals
357 so we can see amplitude on the graph */
358 double delt, fact;
360 delt = im->maxval - im->minval;
361 adj = delt * 0.1;
362 fact = 2.0 * pow(10.0,
363 floor(log10(max(fabs(im->minval), fabs(im->maxval)))) - 2);
364 if (delt < fact) {
365 adj = (fact - delt) * 0.55;
366 #ifdef DEBUG
367 printf("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n", im->minval, im->maxval, delt, fact, adj);
368 #endif
369 }
370 im->minval -= adj;
371 im->maxval += adj;
372 }
373 else if(im->extra_flags & ALTAUTOSCALE_MAX) {
374 /* measure the amplitude of the function. Make sure that
375 graph boundaries are slightly higher than max vals
376 so we can see amplitude on the graph */
377 adj = (im->maxval - im->minval) * 0.1;
378 im->maxval += adj;
379 }
380 else {
381 scaled_min = im->minval / im->magfact;
382 scaled_max = im->maxval / im->magfact;
384 for (i=1; sensiblevalues[i] > 0; i++){
385 if (sensiblevalues[i-1]>=scaled_min &&
386 sensiblevalues[i]<=scaled_min)
387 im->minval = sensiblevalues[i]*(im->magfact);
389 if (-sensiblevalues[i-1]<=scaled_min &&
390 -sensiblevalues[i]>=scaled_min)
391 im->minval = -sensiblevalues[i-1]*(im->magfact);
393 if (sensiblevalues[i-1] >= scaled_max &&
394 sensiblevalues[i] <= scaled_max)
395 im->maxval = sensiblevalues[i-1]*(im->magfact);
397 if (-sensiblevalues[i-1]<=scaled_max &&
398 -sensiblevalues[i] >=scaled_max)
399 im->maxval = -sensiblevalues[i]*(im->magfact);
400 }
401 }
402 } else {
403 /* adjust min and max to the grid definition if there is one */
404 im->minval = (double)im->ylabfact * im->ygridstep *
405 floor(im->minval / ((double)im->ylabfact * im->ygridstep));
406 im->maxval = (double)im->ylabfact * im->ygridstep *
407 ceil(im->maxval /( (double)im->ylabfact * im->ygridstep));
408 }
410 #ifdef DEBUG
411 fprintf(stderr,"SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
412 im->minval,im->maxval,im->magfact);
413 #endif
414 }
417 /* reduce data reimplementation by Alex */
419 void
420 reduce_data(
421 enum cf_en cf, /* which consolidation function ?*/
422 unsigned long cur_step, /* step the data currently is in */
423 time_t *start, /* start, end and step as requested ... */
424 time_t *end, /* ... by the application will be ... */
425 unsigned long *step, /* ... adjusted to represent reality */
426 unsigned long *ds_cnt, /* number of data sources in file */
427 rrd_value_t **data) /* two dimensional array containing the data */
428 {
429 int i,reduce_factor = ceil((double)(*step) / (double)cur_step);
430 unsigned long col,dst_row,row_cnt,start_offset,end_offset,skiprows=0;
431 rrd_value_t *srcptr,*dstptr;
433 (*step) = cur_step*reduce_factor; /* set new step size for reduced data */
434 dstptr = *data;
435 srcptr = *data;
436 row_cnt = ((*end)-(*start))/cur_step;
438 #ifdef DEBUG
439 #define DEBUG_REDUCE
440 #endif
441 #ifdef DEBUG_REDUCE
442 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
443 row_cnt,reduce_factor,*start,*end,cur_step);
444 for (col=0;col<row_cnt;col++) {
445 printf("time %10lu: ",*start+(col+1)*cur_step);
446 for (i=0;i<*ds_cnt;i++)
447 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
448 printf("\n");
449 }
450 #endif
452 /* We have to combine [reduce_factor] rows of the source
453 ** into one row for the destination. Doing this we also
454 ** need to take care to combine the correct rows. First
455 ** alter the start and end time so that they are multiples
456 ** of the new step time. We cannot reduce the amount of
457 ** time so we have to move the end towards the future and
458 ** the start towards the past.
459 */
460 end_offset = (*end) % (*step);
461 start_offset = (*start) % (*step);
463 /* If there is a start offset (which cannot be more than
464 ** one destination row), skip the appropriate number of
465 ** source rows and one destination row. The appropriate
466 ** number is what we do know (start_offset/cur_step) of
467 ** the new interval (*step/cur_step aka reduce_factor).
468 */
469 #ifdef DEBUG_REDUCE
470 printf("start_offset: %lu end_offset: %lu\n",start_offset,end_offset);
471 printf("row_cnt before: %lu\n",row_cnt);
472 #endif
473 if (start_offset) {
474 (*start) = (*start)-start_offset;
475 skiprows=reduce_factor-start_offset/cur_step;
476 srcptr+=skiprows* *ds_cnt;
477 for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
478 row_cnt-=skiprows;
479 }
480 #ifdef DEBUG_REDUCE
481 printf("row_cnt between: %lu\n",row_cnt);
482 #endif
484 /* At the end we have some rows that are not going to be
485 ** used, the amount is end_offset/cur_step
486 */
487 if (end_offset) {
488 (*end) = (*end)-end_offset+(*step);
489 skiprows = end_offset/cur_step;
490 row_cnt-=skiprows;
491 }
492 #ifdef DEBUG_REDUCE
493 printf("row_cnt after: %lu\n",row_cnt);
494 #endif
496 /* Sanity check: row_cnt should be multiple of reduce_factor */
497 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
499 if (row_cnt%reduce_factor) {
500 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
501 row_cnt,reduce_factor);
502 printf("BUG in reduce_data()\n");
503 exit(1);
504 }
506 /* Now combine reduce_factor intervals at a time
507 ** into one interval for the destination.
508 */
510 for (dst_row=0;row_cnt>=reduce_factor;dst_row++) {
511 for (col=0;col<(*ds_cnt);col++) {
512 rrd_value_t newval=DNAN;
513 unsigned long validval=0;
515 for (i=0;i<reduce_factor;i++) {
516 if (isnan(srcptr[i*(*ds_cnt)+col])) {
517 continue;
518 }
519 validval++;
520 if (isnan(newval)) newval = srcptr[i*(*ds_cnt)+col];
521 else {
522 switch (cf) {
523 case CF_HWPREDICT:
524 case CF_DEVSEASONAL:
525 case CF_DEVPREDICT:
526 case CF_SEASONAL:
527 case CF_AVERAGE:
528 newval += srcptr[i*(*ds_cnt)+col];
529 break;
530 case CF_MINIMUM:
531 newval = min (newval,srcptr[i*(*ds_cnt)+col]);
532 break;
533 case CF_FAILURES:
534 /* an interval contains a failure if any subintervals contained a failure */
535 case CF_MAXIMUM:
536 newval = max (newval,srcptr[i*(*ds_cnt)+col]);
537 break;
538 case CF_LAST:
539 newval = srcptr[i*(*ds_cnt)+col];
540 break;
541 }
542 }
543 }
544 if (validval == 0){newval = DNAN;} else{
545 switch (cf) {
546 case CF_HWPREDICT:
547 case CF_DEVSEASONAL:
548 case CF_DEVPREDICT:
549 case CF_SEASONAL:
550 case CF_AVERAGE:
551 newval /= validval;
552 break;
553 case CF_MINIMUM:
554 case CF_FAILURES:
555 case CF_MAXIMUM:
556 case CF_LAST:
557 break;
558 }
559 }
560 *dstptr++=newval;
561 }
562 srcptr+=(*ds_cnt)*reduce_factor;
563 row_cnt-=reduce_factor;
564 }
565 /* If we had to alter the endtime, we didn't have enough
566 ** source rows to fill the last row. Fill it with NaN.
567 */
568 if (end_offset) for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
569 #ifdef DEBUG_REDUCE
570 row_cnt = ((*end)-(*start))/ *step;
571 srcptr = *data;
572 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
573 row_cnt,*start,*end,*step);
574 for (col=0;col<row_cnt;col++) {
575 printf("time %10lu: ",*start+(col+1)*(*step));
576 for (i=0;i<*ds_cnt;i++)
577 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
578 printf("\n");
579 }
580 #endif
581 }
584 /* get the data required for the graphs from the
585 relevant rrds ... */
587 int
588 data_fetch( image_desc_t *im )
589 {
590 int i,ii;
591 int skip;
592 /* pull the data from the log files ... */
593 for (i=0;i<im->gdes_c;i++){
594 /* only GF_DEF elements fetch data */
595 if (im->gdes[i].gf != GF_DEF)
596 continue;
598 skip=0;
599 /* do we have it already ?*/
600 for (ii=0;ii<i;ii++){
601 if (im->gdes[ii].gf != GF_DEF)
602 continue;
603 if((strcmp(im->gdes[i].rrd,im->gdes[ii].rrd) == 0)
604 && (im->gdes[i].cf == im->gdes[ii].cf)){
605 /* OK the data it is here already ...
606 * we just copy the header portion */
607 im->gdes[i].start = im->gdes[ii].start;
608 im->gdes[i].end = im->gdes[ii].end;
609 im->gdes[i].step = im->gdes[ii].step;
610 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
611 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
612 im->gdes[i].data = im->gdes[ii].data;
613 im->gdes[i].data_first = 0;
614 skip=1;
615 }
616 if (skip)
617 break;
618 }
619 if (! skip) {
620 unsigned long ft_step = im->gdes[i].step ;
622 if((rrd_fetch_fn(im->gdes[i].rrd,
623 im->gdes[i].cf,
624 &im->gdes[i].start,
625 &im->gdes[i].end,
626 &ft_step,
627 &im->gdes[i].ds_cnt,
628 &im->gdes[i].ds_namv,
629 &im->gdes[i].data)) == -1){
630 return -1;
631 }
632 im->gdes[i].data_first = 1;
634 if (ft_step < im->gdes[i].step) {
635 reduce_data(im->gdes[i].cf,
636 ft_step,
637 &im->gdes[i].start,
638 &im->gdes[i].end,
639 &im->gdes[i].step,
640 &im->gdes[i].ds_cnt,
641 &im->gdes[i].data);
642 } else {
643 im->gdes[i].step = ft_step;
644 }
645 }
647 /* lets see if the required data source is realy there */
648 for(ii=0;ii<im->gdes[i].ds_cnt;ii++){
649 if(strcmp(im->gdes[i].ds_namv[ii],im->gdes[i].ds_nam) == 0){
650 im->gdes[i].ds=ii; }
651 }
652 if (im->gdes[i].ds== -1){
653 rrd_set_error("No DS called '%s' in '%s'",
654 im->gdes[i].ds_nam,im->gdes[i].rrd);
655 return -1;
656 }
658 }
659 return 0;
660 }
662 /* evaluate the expressions in the CDEF functions */
664 /*************************************************************
665 * CDEF stuff
666 *************************************************************/
668 long
669 find_var_wrapper(void *arg1, char *key)
670 {
671 return find_var((image_desc_t *) arg1, key);
672 }
674 /* find gdes containing var*/
675 long
676 find_var(image_desc_t *im, char *key){
677 long ii;
678 for(ii=0;ii<im->gdes_c-1;ii++){
679 if((im->gdes[ii].gf == GF_DEF
680 || im->gdes[ii].gf == GF_VDEF
681 || im->gdes[ii].gf == GF_CDEF)
682 && (strcmp(im->gdes[ii].vname,key) == 0)){
683 return ii;
684 }
685 }
686 return -1;
687 }
689 /* find the largest common denominator for all the numbers
690 in the 0 terminated num array */
691 long
692 lcd(long *num){
693 long rest;
694 int i;
695 for (i=0;num[i+1]!=0;i++){
696 do {
697 rest=num[i] % num[i+1];
698 num[i]=num[i+1]; num[i+1]=rest;
699 } while (rest!=0);
700 num[i+1] = num[i];
701 }
702 /* return i==0?num[i]:num[i-1]; */
703 return num[i];
704 }
706 /* run the rpn calculator on all the VDEF and CDEF arguments */
707 int
708 data_calc( image_desc_t *im){
710 int gdi;
711 int dataidx;
712 long *steparray, rpi;
713 int stepcnt;
714 time_t now;
715 rpnstack_t rpnstack;
717 rpnstack_init(&rpnstack);
719 for (gdi=0;gdi<im->gdes_c;gdi++){
720 /* Look for GF_VDEF and GF_CDEF in the same loop,
721 * so CDEFs can use VDEFs and vice versa
722 */
723 switch (im->gdes[gdi].gf) {
724 case GF_VDEF:
725 /* A VDEF has no DS. This also signals other parts
726 * of rrdtool that this is a VDEF value, not a CDEF.
727 */
728 im->gdes[gdi].ds_cnt = 0;
729 if (vdef_calc(im,gdi)) {
730 rrd_set_error("Error processing VDEF '%s'"
731 ,im->gdes[gdi].vname
732 );
733 rpnstack_free(&rpnstack);
734 return -1;
735 }
736 break;
737 case GF_CDEF:
738 im->gdes[gdi].ds_cnt = 1;
739 im->gdes[gdi].ds = 0;
740 im->gdes[gdi].data_first = 1;
741 im->gdes[gdi].start = 0;
742 im->gdes[gdi].end = 0;
743 steparray=NULL;
744 stepcnt = 0;
745 dataidx=-1;
747 /* Find the variables in the expression.
748 * - VDEF variables are substituted by their values
749 * and the opcode is changed into OP_NUMBER.
750 * - CDEF variables are analized for their step size,
751 * the lowest common denominator of all the step
752 * sizes of the data sources involved is calculated
753 * and the resulting number is the step size for the
754 * resulting data source.
755 */
756 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
757 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE){
758 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
759 if (im->gdes[ptr].ds_cnt == 0) {
760 #if 0
761 printf("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
762 im->gdes[gdi].vname,
763 im->gdes[ptr].vname);
764 printf("DEBUG: value from vdef is %f\n",im->gdes[ptr].vf.val);
765 #endif
766 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
767 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
768 } else {
769 if ((steparray = rrd_realloc(steparray, (++stepcnt+1)*sizeof(*steparray)))==NULL){
770 rrd_set_error("realloc steparray");
771 rpnstack_free(&rpnstack);
772 return -1;
773 };
775 steparray[stepcnt-1] = im->gdes[ptr].step;
777 /* adjust start and end of cdef (gdi) so
778 * that it runs from the latest start point
779 * to the earliest endpoint of any of the
780 * rras involved (ptr)
781 */
782 if(im->gdes[gdi].start < im->gdes[ptr].start)
783 im->gdes[gdi].start = im->gdes[ptr].start;
785 if(im->gdes[gdi].end == 0 ||
786 im->gdes[gdi].end > im->gdes[ptr].end)
787 im->gdes[gdi].end = im->gdes[ptr].end;
789 /* store pointer to the first element of
790 * the rra providing data for variable,
791 * further save step size and data source
792 * count of this rra
793 */
794 im->gdes[gdi].rpnp[rpi].data = im->gdes[ptr].data;
795 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
796 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
798 /* backoff the *.data ptr; this is done so
799 * rpncalc() function doesn't have to treat
800 * the first case differently
801 */
802 } /* if ds_cnt != 0 */
803 } /* if OP_VARIABLE */
804 } /* loop through all rpi */
806 if(steparray == NULL){
807 rrd_set_error("rpn expressions without DEF"
808 " or CDEF variables are not supported");
809 rpnstack_free(&rpnstack);
810 return -1;
811 }
812 steparray[stepcnt]=0;
813 /* Now find the resulting step. All steps in all
814 * used RRAs have to be visited
815 */
816 im->gdes[gdi].step = lcd(steparray);
817 free(steparray);
818 if((im->gdes[gdi].data = malloc((
819 (im->gdes[gdi].end-im->gdes[gdi].start)
820 / im->gdes[gdi].step)
821 * sizeof(double)))==NULL){
822 rrd_set_error("malloc im->gdes[gdi].data");
823 rpnstack_free(&rpnstack);
824 return -1;
825 }
827 /* Step through the new cdef results array and
828 * calculate the values
829 */
830 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
831 now<=im->gdes[gdi].end;
832 now += im->gdes[gdi].step)
833 {
834 rpnp_t *rpnp = im -> gdes[gdi].rpnp;
836 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
837 * in this case we are advancing by timesteps;
838 * we use the fact that time_t is a synonym for long
839 */
840 if (rpn_calc(rpnp,&rpnstack,(long) now,
841 im->gdes[gdi].data,++dataidx) == -1) {
842 /* rpn_calc sets the error string */
843 rpnstack_free(&rpnstack);
844 return -1;
845 }
846 } /* enumerate over time steps within a CDEF */
847 break;
848 default:
849 continue;
850 }
851 } /* enumerate over CDEFs */
852 rpnstack_free(&rpnstack);
853 return 0;
854 }
856 /* massage data so, that we get one value for each x coordinate in the graph */
857 int
858 data_proc( image_desc_t *im ){
859 long i,ii;
860 double pixstep = (double)(im->end-im->start)
861 /(double)im->xsize; /* how much time
862 passes in one pixel */
863 double paintval;
864 double minval=DNAN,maxval=DNAN;
866 unsigned long gr_time;
868 /* memory for the processed data */
869 for(i=0;i<im->gdes_c;i++){
870 if((im->gdes[i].gf==GF_LINE1) ||
871 (im->gdes[i].gf==GF_LINE2) ||
872 (im->gdes[i].gf==GF_LINE3) ||
873 (im->gdes[i].gf==GF_AREA) ||
874 (im->gdes[i].gf==GF_TICK) ||
875 (im->gdes[i].gf==GF_STACK)){
876 if((im->gdes[i].p_data = malloc((im->xsize +1)
877 * sizeof(rrd_value_t)))==NULL){
878 rrd_set_error("malloc data_proc");
879 return -1;
880 }
881 }
882 }
884 for(i=0;i<im->xsize;i++){
885 long vidx;
886 gr_time = im->start+pixstep*i; /* time of the
887 current step */
888 paintval=0.0;
890 for(ii=0;ii<im->gdes_c;ii++){
891 double value;
892 switch(im->gdes[ii].gf){
893 case GF_LINE1:
894 case GF_LINE2:
895 case GF_LINE3:
896 case GF_AREA:
897 case GF_TICK:
898 paintval = 0.0;
899 case GF_STACK:
900 vidx = im->gdes[ii].vidx;
902 value =
903 im->gdes[vidx].data[
904 ((unsigned long)floor(
905 (double)(gr_time-im->gdes[vidx].start) / im->gdes[vidx].step
906 )
907 ) *im->gdes[vidx].ds_cnt
908 +im->gdes[vidx].ds];
910 if (! isnan(value)) {
911 paintval += value;
912 im->gdes[ii].p_data[i] = paintval;
913 /* GF_TICK: the data values are not relevant for min and max */
914 if (finite(paintval) && im->gdes[ii].gf != GF_TICK ){
915 if (isnan(minval) || paintval < minval)
916 minval = paintval;
917 if (isnan(maxval) || paintval > maxval)
918 maxval = paintval;
919 }
920 } else {
921 im->gdes[ii].p_data[i] = DNAN;
922 }
923 break;
924 case GF_PRINT:
925 case GF_GPRINT:
926 case GF_COMMENT:
927 case GF_HRULE:
928 case GF_VRULE:
929 case GF_DEF:
930 case GF_CDEF:
931 case GF_VDEF:
932 break;
933 }
934 }
935 }
937 /* if min or max have not been asigned a value this is because
938 there was no data in the graph ... this is not good ...
939 lets set these to dummy values then ... */
941 if (isnan(minval)) minval = 0.0;
942 if (isnan(maxval)) maxval = 1.0;
944 /* adjust min and max values */
945 if (isnan(im->minval)
946 || ((!im->logarithmic && !im->rigid) /* don't adjust low-end with log scale */
947 && im->minval > minval))
948 im->minval = minval;
949 if (isnan(im->maxval)
950 || (!im->rigid
951 && im->maxval < maxval)){
952 if (im->logarithmic)
953 im->maxval = maxval * 1.1;
954 else
955 im->maxval = maxval;
956 }
957 /* make sure min and max are not equal */
958 if (im->minval == im->maxval) {
959 im->maxval *= 1.01;
960 if (! im->logarithmic) {
961 im->minval *= 0.99;
962 }
964 /* make sure min and max are not both zero */
965 if (im->maxval == 0.0) {
966 im->maxval = 1.0;
967 }
969 }
970 return 0;
971 }
975 /* identify the point where the first gridline, label ... gets placed */
977 time_t
978 find_first_time(
979 time_t start, /* what is the initial time */
980 enum tmt_en baseint, /* what is the basic interval */
981 long basestep /* how many if these do we jump a time */
982 )
983 {
984 struct tm tm;
985 tm = *localtime(&start);
986 switch(baseint){
987 case TMT_SECOND:
988 tm.tm_sec -= tm.tm_sec % basestep; break;
989 case TMT_MINUTE:
990 tm.tm_sec=0;
991 tm.tm_min -= tm.tm_min % basestep;
992 break;
993 case TMT_HOUR:
994 tm.tm_sec=0;
995 tm.tm_min = 0;
996 tm.tm_hour -= tm.tm_hour % basestep; break;
997 case TMT_DAY:
998 /* we do NOT look at the basestep for this ... */
999 tm.tm_sec=0;
1000 tm.tm_min = 0;
1001 tm.tm_hour = 0; break;
1002 case TMT_WEEK:
1003 /* we do NOT look at the basestep for this ... */
1004 tm.tm_sec=0;
1005 tm.tm_min = 0;
1006 tm.tm_hour = 0;
1007 tm.tm_mday -= tm.tm_wday -1; /* -1 because we want the monday */
1008 if (tm.tm_wday==0) tm.tm_mday -= 7; /* we want the *previous* monday */
1009 break;
1010 case TMT_MONTH:
1011 tm.tm_sec=0;
1012 tm.tm_min = 0;
1013 tm.tm_hour = 0;
1014 tm.tm_mday = 1;
1015 tm.tm_mon -= tm.tm_mon % basestep; break;
1017 case TMT_YEAR:
1018 tm.tm_sec=0;
1019 tm.tm_min = 0;
1020 tm.tm_hour = 0;
1021 tm.tm_mday = 1;
1022 tm.tm_mon = 0;
1023 tm.tm_year -= (tm.tm_year+1900) % basestep;
1025 }
1026 return mktime(&tm);
1027 }
1028 /* identify the point where the next gridline, label ... gets placed */
1029 time_t
1030 find_next_time(
1031 time_t current, /* what is the initial time */
1032 enum tmt_en baseint, /* what is the basic interval */
1033 long basestep /* how many if these do we jump a time */
1034 )
1035 {
1036 struct tm tm;
1037 time_t madetime;
1038 tm = *localtime(¤t);
1039 do {
1040 switch(baseint){
1041 case TMT_SECOND:
1042 tm.tm_sec += basestep; break;
1043 case TMT_MINUTE:
1044 tm.tm_min += basestep; break;
1045 case TMT_HOUR:
1046 tm.tm_hour += basestep; break;
1047 case TMT_DAY:
1048 tm.tm_mday += basestep; break;
1049 case TMT_WEEK:
1050 tm.tm_mday += 7*basestep; break;
1051 case TMT_MONTH:
1052 tm.tm_mon += basestep; break;
1053 case TMT_YEAR:
1054 tm.tm_year += basestep;
1055 }
1056 madetime = mktime(&tm);
1057 } while (madetime == -1); /* this is necessary to skip impssible times
1058 like the daylight saving time skips */
1059 return madetime;
1061 }
1063 void gator( gdImagePtr gif, int x, int y){
1065 /* this function puts the name of the author and the tool into the
1066 graph. Remove if you must, but please note, that it is here,
1067 because I would like people who look at rrdtool generated graphs to
1068 see what was used to do it. No obviously you can also add a credit
1069 line to your webpage or printed document, this is fine with me. But
1070 as I have no control over this, I added the little tag in here.
1071 */
1073 /* the fact that the text of what gets put into the graph is not
1074 visible in the function, has lead some to think this is for
1075 obfuscation reasons. While this is a nice side effect (I addmit),
1076 it is not the prime reason. The prime reason is, that the font
1077 used, is so small, that I had to hand edit the characters to ensure
1078 readability. I could thus not use the normal gd functions to write,
1079 but had to embed a slightly compressed bitmap version into the code.
1080 */
1082 int li[]={0,0,1, 0,4,5, 0,8,9, 0,12,14, 0,17,17, 0,21,21,
1083 0,24,24, 0,34,34, 0,40,42, 0,45,45, 0,48,49, 0,52,54,
1084 0,61,61, 0,64,66, 0,68,70, 0,72,74, 0,76,76, 0,78,78,
1085 0,80,82, 0,84,85,
1086 1,0,0, 1,2,2, 1,4,4, 1,6,6, 1,8,8, 1,10,10,
1087 1,13,13, 1,16,16, 1,18,18, 1,20,20, 1,22,22, 1,24,24,
1088 1,34,34, 1,41,41, 1,44,44, 1,46,46, 1,48,48, 1,50,50,
1089 1,53,53, 1,60,60, 1,62,62, 1,64,64, 1,69,69, 1,73,73,
1090 1,76,76, 1,78,78, 1,80,80, 1,84,84, 1,86,86,
1091 2,0,1, 2,4,5, 2,8,8, 2,10,10, 2,13,13, 2,16,16,
1092 2,18,18, 2,20,20, 2,22,22, 2,24,24, 2,33,33, 2,41,41,
1093 2,44,44, 2,46,46, 2,48,49, 2,53,53, 2,60,60, 2,62,62,
1094 2,64,65, 2,69,69, 2,73,73, 2,76,77, 2,80,81, 2,84,85,
1095 3,0,0, 3,2,2, 3,4,4, 3,6,6, 3,8,8, 3,10,10,
1096 3,13,13, 3,16,16, 3,18,18, 3,20,20, 3,22,22, 3,24,24,
1097 3,32,32, 3,41,41, 3,44,44, 3,46,46, 3,48,48, 3,50,50,
1098 3,53,53, 3,60,60, 3,62,62, 3,64,64, 3,69,69, 3,73,73,
1099 3,76,76, 3,78,78, 3,80,80, 3,84,84, 3,86,86,
1100 4,0,0, 4,2,2, 4,4,4, 4,6,6, 4,8,9, 4,13,13,
1101 4,17,17, 4,21,21, 4,24,26, 4,32,32, 4,41,41, 4,45,45,
1102 4,48,49, 4,52,54, 4,61,61, 4,64,66, 4,69,69, 4,72,74,
1103 4,76,76, 4,78,78, 4,80,82, 4,84,84};
1104 int i,ii;
1105 for(i=0; i<DIM(li); i=i+3)
1106 for(ii=y+li[i+1]; ii<=y+li[i+2];ii++)
1107 gdImageSetPixel(gif,x-li[i],ii,graph_col[GRC_GRID].i);
1108 }
1111 /* calculate values required for PRINT and GPRINT functions */
1113 int
1114 print_calc(image_desc_t *im, char ***prdata)
1115 {
1116 long i,ii,validsteps;
1117 double printval;
1118 time_t printtime;
1119 int graphelement = 0;
1120 long vidx;
1121 int max_ii;
1122 double magfact = -1;
1123 char *si_symb = "";
1124 char *percent_s;
1125 int prlines = 1;
1126 if (im->imginfo) prlines++;
1127 for(i=0;i<im->gdes_c;i++){
1128 switch(im->gdes[i].gf){
1129 case GF_PRINT:
1130 prlines++;
1131 if(((*prdata) = rrd_realloc((*prdata),prlines*sizeof(char *)))==NULL){
1132 rrd_set_error("realloc prdata");
1133 return 0;
1134 }
1135 case GF_GPRINT:
1136 /* PRINT and GPRINT can now print VDEF generated values.
1137 * There's no need to do any calculations on them as these
1138 * calculations were already made.
1139 */
1140 vidx = im->gdes[i].vidx;
1141 if (im->gdes[vidx].gf==GF_VDEF) { /* simply use vals */
1142 printval = im->gdes[vidx].vf.val;
1143 printtime = im->gdes[vidx].vf.when;
1144 } else { /* need to calculate max,min,avg etcetera */
1145 max_ii =((im->gdes[vidx].end
1146 - im->gdes[vidx].start)
1147 / im->gdes[vidx].step
1148 * im->gdes[vidx].ds_cnt);
1149 printval = DNAN;
1150 validsteps = 0;
1151 for( ii=im->gdes[vidx].ds;
1152 ii < max_ii;
1153 ii+=im->gdes[vidx].ds_cnt){
1154 if (! finite(im->gdes[vidx].data[ii]))
1155 continue;
1156 if (isnan(printval)){
1157 printval = im->gdes[vidx].data[ii];
1158 validsteps++;
1159 continue;
1160 }
1162 switch (im->gdes[i].cf){
1163 case CF_HWPREDICT:
1164 case CF_DEVPREDICT:
1165 case CF_DEVSEASONAL:
1166 case CF_SEASONAL:
1167 case CF_AVERAGE:
1168 validsteps++;
1169 printval += im->gdes[vidx].data[ii];
1170 break;
1171 case CF_MINIMUM:
1172 printval = min( printval, im->gdes[vidx].data[ii]);
1173 break;
1174 case CF_FAILURES:
1175 case CF_MAXIMUM:
1176 printval = max( printval, im->gdes[vidx].data[ii]);
1177 break;
1178 case CF_LAST:
1179 printval = im->gdes[vidx].data[ii];
1180 }
1181 }
1182 if (im->gdes[i].cf==CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1183 if (validsteps > 1) {
1184 printval = (printval / validsteps);
1185 }
1186 }
1187 } /* prepare printval */
1189 if (!strcmp(im->gdes[i].format,"%c")) { /* VDEF time print */
1190 if (im->gdes[i].gf == GF_PRINT){
1191 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1192 sprintf((*prdata)[prlines-2],"%s (%lu)",
1193 ctime(&printtime),printtime);
1194 (*prdata)[prlines-1] = NULL;
1195 } else {
1196 sprintf(im->gdes[i].legend,"%s (%lu)",
1197 ctime(&printtime),printtime);
1198 graphelement = 1;
1199 }
1200 } else {
1201 if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) {
1202 /* Magfact is set to -1 upon entry to print_calc. If it
1203 * is still less than 0, then we need to run auto_scale.
1204 * Otherwise, put the value into the correct units. If
1205 * the value is 0, then do not set the symbol or magnification
1206 * so next the calculation will be performed again. */
1207 if (magfact < 0.0) {
1208 auto_scale(im,&printval,&si_symb,&magfact);
1209 if (printval == 0.0)
1210 magfact = -1.0;
1211 } else {
1212 printval /= magfact;
1213 }
1214 *(++percent_s) = 's';
1215 } else if (strstr(im->gdes[i].format,"%s") != NULL) {
1216 auto_scale(im,&printval,&si_symb,&magfact);
1217 }
1219 if (im->gdes[i].gf == GF_PRINT){
1220 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1221 if (bad_format(im->gdes[i].format)) {
1222 rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1223 return -1;
1224 }
1225 #ifdef HAVE_SNPRINTF
1226 snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
1227 #else
1228 sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
1229 #endif
1230 (*prdata)[prlines-1] = NULL;
1231 } else {
1232 /* GF_GPRINT */
1234 if (bad_format(im->gdes[i].format)) {
1235 rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1236 return -1;
1237 }
1238 #ifdef HAVE_SNPRINTF
1239 snprintf(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
1240 #else
1241 sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
1242 #endif
1243 graphelement = 1;
1244 }
1245 }
1246 break;
1247 case GF_COMMENT:
1248 case GF_LINE1:
1249 case GF_LINE2:
1250 case GF_LINE3:
1251 case GF_AREA:
1252 case GF_TICK:
1253 case GF_STACK:
1254 case GF_HRULE:
1255 case GF_VRULE:
1256 graphelement = 1;
1257 break;
1258 case GF_DEF:
1259 case GF_CDEF:
1260 case GF_VDEF:
1261 break;
1262 }
1263 }
1264 return graphelement;
1265 }
1268 /* place legends with color spots */
1269 int
1270 leg_place(image_desc_t *im)
1271 {
1272 /* graph labels */
1273 int interleg = SmallFont->w*2;
1274 int box = SmallFont->h*1.2;
1275 int border = SmallFont->w*2;
1276 int fill=0, fill_last;
1277 int leg_c = 0;
1278 int leg_x = border, leg_y = im->ygif;
1279 int leg_cc;
1280 int glue = 0;
1281 int i,ii, mark = 0;
1282 char prt_fctn; /*special printfunctions */
1283 int *legspace;
1285 if( !(im->extra_flags & NOLEGEND) ) {
1286 if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1287 rrd_set_error("malloc for legspace");
1288 return -1;
1289 }
1291 for(i=0;i<im->gdes_c;i++){
1292 fill_last = fill;
1294 leg_cc = strlen(im->gdes[i].legend);
1296 /* is there a controle code ant the end of the legend string ? */
1297 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\') {
1298 prt_fctn = im->gdes[i].legend[leg_cc-1];
1299 leg_cc -= 2;
1300 im->gdes[i].legend[leg_cc] = '\0';
1301 } else {
1302 prt_fctn = '\0';
1303 }
1304 /* remove exess space */
1305 while (prt_fctn=='g' &&
1306 leg_cc > 0 &&
1307 im->gdes[i].legend[leg_cc-1]==' '){
1308 leg_cc--;
1309 im->gdes[i].legend[leg_cc]='\0';
1310 }
1311 if (leg_cc != 0 ){
1312 legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1314 if (fill > 0){
1315 /* no interleg space if string ends in \g */
1316 fill += legspace[i];
1317 }
1318 if (im->gdes[i].gf != GF_GPRINT &&
1319 im->gdes[i].gf != GF_COMMENT) {
1320 fill += box;
1321 }
1322 fill += leg_cc * SmallFont->w;
1323 leg_c++;
1324 } else {
1325 legspace[i]=0;
1326 }
1327 /* who said there was a special tag ... ?*/
1328 if (prt_fctn=='g') {
1329 prt_fctn = '\0';
1330 }
1331 if (prt_fctn == '\0') {
1332 if (i == im->gdes_c -1 ) prt_fctn ='l';
1334 /* is it time to place the legends ? */
1335 if (fill > im->xgif - 2*border){
1336 if (leg_c > 1) {
1337 /* go back one */
1338 i--;
1339 fill = fill_last;
1340 leg_c--;
1341 prt_fctn = 'j';
1342 } else {
1343 prt_fctn = 'l';
1344 }
1346 }
1347 }
1350 if (prt_fctn != '\0'){
1351 leg_x = border;
1352 if (leg_c >= 2 && prt_fctn == 'j') {
1353 glue = (im->xgif - fill - 2* border) / (leg_c-1);
1354 /* if (glue > 2 * SmallFont->w) glue = 0; */
1355 } else {
1356 glue = 0;
1357 }
1358 if (prt_fctn =='c') leg_x = (im->xgif - fill) / 2.0;
1359 if (prt_fctn =='r') leg_x = im->xgif - fill - border;
1361 for(ii=mark;ii<=i;ii++){
1362 if(im->gdes[ii].legend[0]=='\0')
1363 continue;
1364 im->gdes[ii].legloc.x = leg_x;
1365 im->gdes[ii].legloc.y = leg_y;
1366 leg_x = leg_x
1367 + strlen(im->gdes[ii].legend)*SmallFont->w
1368 + legspace[ii]
1369 + glue;
1370 if (im->gdes[ii].gf != GF_GPRINT &&
1371 im->gdes[ii].gf != GF_COMMENT)
1372 leg_x += box;
1373 }
1374 leg_y = leg_y + SmallFont->h*1.2;
1375 if (prt_fctn == 's') leg_y -= SmallFont->h *0.5;
1376 fill = 0;
1377 leg_c = 0;
1378 mark = ii;
1379 }
1380 }
1381 im->ygif = leg_y+6;
1382 free(legspace);
1383 }
1384 return 0;
1385 }
1387 /* create a grid on the graph. it determines what to do
1388 from the values of xsize, start and end */
1390 /* the xaxis labels are determined from the number of seconds per pixel
1391 in the requested graph */
1395 int
1396 horizontal_grid(gdImagePtr gif, image_desc_t *im)
1397 {
1398 double range;
1399 double scaledrange;
1400 int pixel,i;
1401 int sgrid,egrid;
1402 double gridstep;
1403 double scaledstep;
1404 char graph_label[100];
1405 gdPoint polyPoints[4];
1406 int labfact,gridind;
1407 int styleMinor[2],styleMajor[2];
1408 int decimals, fractionals;
1409 char labfmt[64];
1411 labfact=2;
1412 gridind=-1;
1413 range = im->maxval - im->minval;
1414 scaledrange = range / im->magfact;
1416 /* does the scale of this graph make it impossible to put lines
1417 on it? If so, give up. */
1418 if (isnan(scaledrange)) {
1419 return 0;
1420 }
1422 styleMinor[0] = graph_col[GRC_GRID].i;
1423 styleMinor[1] = gdTransparent;
1425 styleMajor[0] = graph_col[GRC_MGRID].i;
1426 styleMajor[1] = gdTransparent;
1428 /* find grid spaceing */
1429 pixel=1;
1430 if(isnan(im->ygridstep)){
1431 if(im->extra_flags & ALTYGRID) {
1432 /* find the value with max number of digits. Get number of digits */
1433 decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))));
1434 if(decimals <= 0) /* everything is small. make place for zero */
1435 decimals = 1;
1437 fractionals = floor(log10(range));
1438 if(fractionals < 0) /* small amplitude. */
1439 sprintf(labfmt, "%%%d.%df", decimals - fractionals + 1, -fractionals + 1);
1440 else
1441 sprintf(labfmt, "%%%d.1f", decimals + 1);
1442 gridstep = pow((double)10, (double)fractionals);
1443 if(gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1444 gridstep = 0.1;
1445 /* should have at least 5 lines but no more then 15 */
1446 if(range/gridstep < 5)
1447 gridstep /= 10;
1448 if(range/gridstep > 15)
1449 gridstep *= 10;
1450 if(range/gridstep > 5) {
1451 labfact = 1;
1452 if(range/gridstep > 8)
1453 labfact = 2;
1454 }
1455 else {
1456 gridstep /= 5;
1457 labfact = 5;
1458 }
1459 }
1460 else {
1461 for(i=0;ylab[i].grid > 0;i++){
1462 pixel = im->ysize / (scaledrange / ylab[i].grid);
1463 if (gridind == -1 && pixel > 5) {
1464 gridind = i;
1465 break;
1466 }
1467 }
1469 for(i=0; i<4;i++) {
1470 if (pixel * ylab[gridind].lfac[i] >= 2 * SmallFont->h) {
1471 labfact = ylab[gridind].lfac[i];
1472 break;
1473 }
1474 }
1476 gridstep = ylab[gridind].grid * im->magfact;
1477 }
1478 } else {
1479 gridstep = im->ygridstep;
1480 labfact = im->ylabfact;
1481 }
1483 polyPoints[0].x=im->xorigin;
1484 polyPoints[1].x=im->xorigin+im->xsize;
1485 sgrid = (int)( im->minval / gridstep - 1);
1486 egrid = (int)( im->maxval / gridstep + 1);
1487 scaledstep = gridstep/im->magfact;
1488 for (i = sgrid; i <= egrid; i++){
1489 polyPoints[0].y=ytr(im,gridstep*i);
1490 if ( polyPoints[0].y >= im->yorigin-im->ysize
1491 && polyPoints[0].y <= im->yorigin) {
1492 if(i % labfact == 0){
1493 if (i==0 || im->symbol == ' ') {
1494 if(scaledstep < 1){
1495 if(im->extra_flags & ALTYGRID) {
1496 sprintf(graph_label,labfmt,scaledstep*i);
1497 }
1498 else {
1499 sprintf(graph_label,"%4.1f",scaledstep*i);
1500 }
1501 } else {
1502 sprintf(graph_label,"%4.0f",scaledstep*i);
1503 }
1504 }else {
1505 if(scaledstep < 1){
1506 sprintf(graph_label,"%4.1f %c",scaledstep*i, im->symbol);
1507 } else {
1508 sprintf(graph_label,"%4.0f %c",scaledstep*i, im->symbol);
1509 }
1510 }
1512 gdImageString(gif, SmallFont,
1513 (polyPoints[0].x - (strlen(graph_label) *
1514 SmallFont->w)-7),
1515 polyPoints[0].y - SmallFont->h/2+1,
1516 (unsigned char *)graph_label, graph_col[GRC_FONT].i);
1518 gdImageSetStyle(gif, styleMajor, 2);
1520 gdImageLine(gif, polyPoints[0].x-2,polyPoints[0].y,
1521 polyPoints[0].x+2,polyPoints[0].y,graph_col[GRC_MGRID].i);
1522 gdImageLine(gif, polyPoints[1].x-2,polyPoints[0].y,
1523 polyPoints[1].x+2,polyPoints[0].y,graph_col[GRC_MGRID].i);
1524 } else {
1525 gdImageSetStyle(gif, styleMinor, 2);
1526 gdImageLine(gif, polyPoints[0].x-1,polyPoints[0].y,
1527 polyPoints[0].x+1,polyPoints[0].y,graph_col[GRC_GRID].i);
1528 gdImageLine(gif, polyPoints[1].x-1,polyPoints[0].y,
1529 polyPoints[1].x+1,polyPoints[0].y,graph_col[GRC_GRID].i);
1530 }
1531 gdImageLine(gif, polyPoints[0].x,polyPoints[0].y,
1532 polyPoints[1].x,polyPoints[0].y,gdStyled);
1533 }
1534 }
1535 /* if(im->minval * im->maxval < 0){
1536 polyPoints[0].y=ytr(0);
1537 gdImageLine(gif, polyPoints[0].x,polyPoints[0].y,
1538 polyPoints[1].x,polyPoints[0].y,graph_col[GRC_MGRID].i);
1539 } */
1541 return 1;
1542 }
1544 /* logaritmic horizontal grid */
1545 int
1546 horizontal_log_grid(gdImagePtr gif, image_desc_t *im)
1547 {
1548 double pixpex;
1549 int ii,i;
1550 int minoridx=0, majoridx=0;
1551 char graph_label[100];
1552 gdPoint polyPoints[4];
1553 int styleMinor[2],styleMajor[2];
1554 double value, pixperstep, minstep;
1556 /* find grid spaceing */
1557 pixpex= (double)im->ysize / (log10(im->maxval) - log10(im->minval));
1559 if (isnan(pixpex)) {
1560 return 0;
1561 }
1563 for(i=0;yloglab[i][0] > 0;i++){
1564 minstep = log10(yloglab[i][0]);
1565 for(ii=1;yloglab[i][ii+1] > 0;ii++){
1566 if(yloglab[i][ii+2]==0){
1567 minstep = log10(yloglab[i][ii+1])-log10(yloglab[i][ii]);
1568 break;
1569 }
1570 }
1571 pixperstep = pixpex * minstep;
1572 if(pixperstep > 5){minoridx = i;}
1573 if(pixperstep > 2 * SmallFont->h){majoridx = i;}
1574 }
1576 styleMinor[0] = graph_col[GRC_GRID].i;
1577 styleMinor[1] = gdTransparent;
1579 styleMajor[0] = graph_col[GRC_MGRID].i;
1580 styleMajor[1] = gdTransparent;
1582 polyPoints[0].x=im->xorigin;
1583 polyPoints[1].x=im->xorigin+im->xsize;
1584 /* paint minor grid */
1585 for (value = pow((double)10, log10(im->minval)
1586 - fmod(log10(im->minval),log10(yloglab[minoridx][0])));
1587 value <= im->maxval;
1588 value *= yloglab[minoridx][0]){
1589 if (value < im->minval) continue;
1590 i=0;
1591 while(yloglab[minoridx][++i] > 0){
1592 polyPoints[0].y = ytr(im,value * yloglab[minoridx][i]);
1593 if (polyPoints[0].y <= im->yorigin - im->ysize) break;
1594 gdImageSetStyle(gif, styleMinor, 2);
1595 gdImageLine(gif, polyPoints[0].x-1,polyPoints[0].y,
1596 polyPoints[0].x+1,polyPoints[0].y,graph_col[GRC_GRID].i);
1597 gdImageLine(gif, polyPoints[1].x-1,polyPoints[0].y,
1598 polyPoints[1].x+1,polyPoints[0].y,graph_col[GRC_GRID].i);
1600 gdImageLine(gif, polyPoints[0].x,polyPoints[0].y,
1601 polyPoints[1].x,polyPoints[0].y,gdStyled);
1602 }
1603 }
1605 /* paint major grid and labels*/
1606 for (value = pow((double)10, log10(im->minval)
1607 - fmod(log10(im->minval),log10(yloglab[majoridx][0])));
1608 value <= im->maxval;
1609 value *= yloglab[majoridx][0]){
1610 if (value < im->minval) continue;
1611 i=0;
1612 while(yloglab[majoridx][++i] > 0){
1613 polyPoints[0].y = ytr(im,value * yloglab[majoridx][i]);
1614 if (polyPoints[0].y <= im->yorigin - im->ysize) break;
1615 gdImageSetStyle(gif, styleMajor, 2);
1616 gdImageLine(gif, polyPoints[0].x-2,polyPoints[0].y,
1617 polyPoints[0].x+2,polyPoints[0].y,graph_col[GRC_MGRID].i);
1618 gdImageLine(gif, polyPoints[1].x-2,polyPoints[0].y,
1619 polyPoints[1].x+2,polyPoints[0].y,graph_col[GRC_MGRID].i);
1621 gdImageLine(gif, polyPoints[0].x,polyPoints[0].y,
1622 polyPoints[1].x,polyPoints[0].y,gdStyled);
1623 sprintf(graph_label,"%3.0e",value * yloglab[majoridx][i]);
1624 gdImageString(gif, SmallFont,
1625 (polyPoints[0].x - (strlen(graph_label) *
1626 SmallFont->w)-7),
1627 polyPoints[0].y - SmallFont->h/2+1,
1628 (unsigned char *)graph_label, graph_col[GRC_FONT].i);
1629 }
1630 }
1631 return 1;
1632 }
1635 void
1636 vertical_grid(
1637 gdImagePtr gif,
1638 image_desc_t *im )
1639 {
1640 int xlab_sel; /* which sort of label and grid ? */
1641 time_t ti, tilab;
1642 long factor;
1643 char graph_label[100];
1644 gdPoint polyPoints[4]; /* points for filled graph and more*/
1646 /* style for grid lines */
1647 int styleDotted[4];
1650 /* the type of time grid is determined by finding
1651 the number of seconds per pixel in the graph */
1654 if(im->xlab_user.minsec == -1){
1655 factor=(im->end - im->start)/im->xsize;
1656 xlab_sel=0;
1657 while ( xlab[xlab_sel+1].minsec != -1
1658 && xlab[xlab_sel+1].minsec <= factor){ xlab_sel++; }
1659 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
1660 im->xlab_user.gridst = xlab[xlab_sel].gridst;
1661 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
1662 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
1663 im->xlab_user.labtm = xlab[xlab_sel].labtm;
1664 im->xlab_user.labst = xlab[xlab_sel].labst;
1665 im->xlab_user.precis = xlab[xlab_sel].precis;
1666 im->xlab_user.stst = xlab[xlab_sel].stst;
1667 }
1669 /* y coords are the same for every line ... */
1670 polyPoints[0].y = im->yorigin;
1671 polyPoints[1].y = im->yorigin-im->ysize;
1673 /* paint the minor grid */
1674 for(ti = find_first_time(im->start,
1675 im->xlab_user.gridtm,
1676 im->xlab_user.gridst);
1677 ti < im->end;
1678 ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
1679 ){
1680 /* are we inside the graph ? */
1681 if (ti < im->start || ti > im->end) continue;
1682 polyPoints[0].x = xtr(im,ti);
1683 styleDotted[0] = graph_col[GRC_GRID].i;
1684 styleDotted[1] = gdTransparent;
1686 gdImageSetStyle(gif, styleDotted, 2);
1688 gdImageLine(gif, polyPoints[0].x,polyPoints[0].y,
1689 polyPoints[0].x,polyPoints[1].y,gdStyled);
1690 gdImageLine(gif, polyPoints[0].x,polyPoints[0].y-1,
1691 polyPoints[0].x,polyPoints[0].y+1,graph_col[GRC_GRID].i);
1692 gdImageLine(gif, polyPoints[0].x,polyPoints[1].y-1,
1693 polyPoints[0].x,polyPoints[1].y+1,graph_col[GRC_GRID].i);
1694 }
1696 /* paint the major grid */
1697 for(ti = find_first_time(im->start,
1698 im->xlab_user.mgridtm,
1699 im->xlab_user.mgridst);
1700 ti < im->end;
1701 ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
1702 ){
1703 /* are we inside the graph ? */
1704 if (ti < im->start || ti > im->end) continue;
1705 polyPoints[0].x = xtr(im,ti);
1706 styleDotted[0] = graph_col[GRC_MGRID].i;
1707 styleDotted[1] = gdTransparent;
1708 gdImageSetStyle(gif, styleDotted, 2);
1710 gdImageLine(gif, polyPoints[0].x,polyPoints[0].y,
1711 polyPoints[0].x,polyPoints[1].y,gdStyled);
1712 gdImageLine(gif, polyPoints[0].x,polyPoints[0].y-2,
1713 polyPoints[0].x,polyPoints[0].y+2,graph_col[GRC_MGRID].i);
1714 gdImageLine(gif, polyPoints[0].x,polyPoints[1].y-2,
1715 polyPoints[0].x,polyPoints[1].y+2,graph_col[GRC_MGRID].i);
1716 }
1717 /* paint the labels below the graph */
1718 for(ti = find_first_time(im->start,
1719 im->xlab_user.labtm,
1720 im->xlab_user.labst);
1721 ti <= im->end;
1722 ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
1723 ){
1724 int gr_pos,width;
1725 tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
1727 #if HAVE_STRFTIME
1728 strftime(graph_label,99,im->xlab_user.stst,localtime(&tilab));
1729 #else
1730 # error "your libc has no strftime I guess we'll abort the exercise here."
1731 #endif
1732 width=strlen(graph_label) * SmallFont->w;
1733 gr_pos=xtr(im,tilab) - width/2;
1734 if (gr_pos >= im->xorigin
1735 && gr_pos + width <= im->xorigin+im->xsize)
1736 gdImageString(gif, SmallFont,
1737 gr_pos, polyPoints[0].y+4,
1738 (unsigned char *)graph_label, graph_col[GRC_FONT].i);
1739 }
1741 }
1744 void
1745 axis_paint(
1746 image_desc_t *im,
1747 gdImagePtr gif
1748 )
1749 {
1750 /* draw x and y axis */
1751 gdImageLine(gif, im->xorigin+im->xsize,im->yorigin,
1752 im->xorigin+im->xsize,im->yorigin-im->ysize,
1753 graph_col[GRC_GRID].i);
1755 gdImageLine(gif, im->xorigin,im->yorigin-im->ysize,
1756 im->xorigin+im->xsize,im->yorigin-im->ysize,
1757 graph_col[GRC_GRID].i);
1759 gdImageLine(gif, im->xorigin-4,im->yorigin,
1760 im->xorigin+im->xsize+4,im->yorigin,
1761 graph_col[GRC_FONT].i);
1763 gdImageLine(gif, im->xorigin,im->yorigin,
1764 im->xorigin,im->yorigin-im->ysize,
1765 graph_col[GRC_GRID].i);
1767 /* arrow for X axis direction */
1768 gdImageLine(gif, im->xorigin+im->xsize+4, im->yorigin-3, im->xorigin+im->xsize+4, im->yorigin+3,graph_col[GRC_ARROW].i);
1769 gdImageLine(gif, im->xorigin+im->xsize+4, im->yorigin-3, im->xorigin+im->xsize+9, im->yorigin,graph_col[GRC_ARROW].i);
1770 gdImageLine(gif, im->xorigin+im->xsize+4, im->yorigin+3, im->xorigin+im->xsize+9, im->yorigin,graph_col[GRC_ARROW].i);
1772 /* gdImageLine(gif, im->xorigin+im->xsize-1, im->yorigin-3, im->xorigin+im->xsize-1, im->yorigin+3,graph_col[GRC_MGRID].i);
1773 gdImageLine(gif, im->xorigin+im->xsize, im->yorigin-2, im->xorigin+im->xsize, im->yorigin+2,graph_col[GRC_MGRID].i);
1774 gdImageLine(gif, im->xorigin+im->xsize+1, im->yorigin-2, im->xorigin+im->xsize+1, im->yorigin+2,graph_col[GRC_MGRID].i);
1775 gdImageLine(gif, im->xorigin+im->xsize+2, im->yorigin-2, im->xorigin+im->xsize+2, im->yorigin+2,graph_col[GRC_MGRID].i);
1776 gdImageLine(gif, im->xorigin+im->xsize+3, im->yorigin-1, im->xorigin+im->xsize+3, im->yorigin+1,graph_col[GRC_MGRID].i);
1777 gdImageLine(gif, im->xorigin+im->xsize+4, im->yorigin-1, im->xorigin+im->xsize+4, im->yorigin+1,graph_col[GRC_MGRID].i);
1778 gdImageLine(gif, im->xorigin+im->xsize+5, im->yorigin, im->xorigin+im->xsize+5, im->yorigin,graph_col[GRC_MGRID].i); */
1782 }
1784 void
1785 grid_paint(
1786 image_desc_t *im,
1787 gdImagePtr gif
1788 )
1789 {
1790 long i;
1791 int boxH=8, boxV=8;
1792 int res=0;
1793 gdPoint polyPoints[4]; /* points for filled graph and more*/
1795 /* draw 3d border */
1796 gdImageLine(gif,0,0,im->xgif-1,0,graph_col[GRC_SHADEA].i);
1797 gdImageLine(gif,1,1,im->xgif-2,1,graph_col[GRC_SHADEA].i);
1798 gdImageLine(gif,0,0,0,im->ygif-1,graph_col[GRC_SHADEA].i);
1799 gdImageLine(gif,1,1,1,im->ygif-2,graph_col[GRC_SHADEA].i);
1800 gdImageLine(gif,im->xgif-1,0,im->xgif-1,im->ygif-1,graph_col[GRC_SHADEB].i);
1801 gdImageLine(gif,0,im->ygif-1,im->xgif-1,im->ygif-1,graph_col[GRC_SHADEB].i);
1802 gdImageLine(gif,im->xgif-2,1,im->xgif-2,im->ygif-2,graph_col[GRC_SHADEB].i);
1803 gdImageLine(gif,1,im->ygif-2,im->xgif-2,im->ygif-2,graph_col[GRC_SHADEB].i);
1806 if (im->draw_x_grid == 1 )
1807 vertical_grid(gif, im);
1809 if (im->draw_y_grid == 1){
1810 if(im->logarithmic){
1811 res = horizontal_log_grid(gif,im);
1812 } else {
1813 res = horizontal_grid(gif,im);
1814 }
1816 /* dont draw horizontal grid if there is no min and max val */
1817 if (! res ) {
1818 char *nodata = "No Data found";
1819 gdImageString(gif, LargeFont,
1820 im->xgif/2
1821 - (strlen(nodata)*LargeFont->w)/2,
1822 (2*im->yorigin-im->ysize) / 2,
1823 (unsigned char *)nodata, graph_col[GRC_FONT].i);
1824 }
1825 }
1827 /* yaxis description */
1828 gdImageStringUp(gif, SmallFont,
1829 7,
1830 (im->yorigin - im->ysize/2
1831 +(strlen(im->ylegend)*SmallFont->w)/2 ),
1832 (unsigned char *)im->ylegend, graph_col[GRC_FONT].i);
1835 /* graph title */
1836 gdImageString(gif, LargeFont,
1837 im->xgif/2
1838 - (strlen(im->title)*LargeFont->w)/2,
1839 8,
1840 (unsigned char *)im->title, graph_col[GRC_FONT].i);
1842 /* graph labels */
1843 if( !(im->extra_flags & NOLEGEND) ) {
1844 for(i=0;i<im->gdes_c;i++){
1845 if(im->gdes[i].legend[0] =='\0')
1846 continue;
1848 if(im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT){
1850 polyPoints[0].x = im->gdes[i].legloc.x;
1851 polyPoints[0].y = im->gdes[i].legloc.y+1;
1852 polyPoints[1].x = polyPoints[0].x+boxH;
1853 polyPoints[2].x = polyPoints[0].x+boxH;
1854 polyPoints[3].x = polyPoints[0].x;
1855 polyPoints[1].y = polyPoints[0].y;
1856 polyPoints[2].y = polyPoints[0].y+boxV;
1857 polyPoints[3].y = polyPoints[0].y+boxV;
1858 gdImageFilledPolygon(gif,polyPoints,4,im->gdes[i].col.i);
1859 gdImagePolygon(gif,polyPoints,4,graph_col[GRC_FRAME].i);
1861 gdImageString(gif, SmallFont,
1862 polyPoints[0].x+boxH+6,
1863 polyPoints[0].y-1,
1864 (unsigned char *)im->gdes[i].legend,
1865 graph_col[GRC_FONT].i);
1866 } else {
1867 polyPoints[0].x = im->gdes[i].legloc.x;
1868 polyPoints[0].y = im->gdes[i].legloc.y;
1870 gdImageString(gif, SmallFont,
1871 polyPoints[0].x,
1872 polyPoints[0].y,
1873 (unsigned char *)im->gdes[i].legend,
1874 graph_col[GRC_FONT].i);
1875 }
1876 }
1877 }
1880 gator(gif, (int) im->xgif-5, 5);
1882 }
1885 gdImagePtr
1886 MkLineBrush(image_desc_t *im,long cosel, enum gf_en typsel){
1887 gdImagePtr brush;
1888 int pen;
1889 switch (typsel){
1890 case GF_LINE1:
1891 brush=gdImageCreate(1,1);
1892 break;
1893 case GF_LINE2:
1894 brush=gdImageCreate(2,2);
1895 break;
1896 case GF_LINE3:
1897 brush=gdImageCreate(3,3);
1898 break;
1899 default:
1900 return NULL;
1901 }
1903 gdImageColorTransparent(brush,
1904 gdImageColorAllocate(brush, 0, 0, 0));
1906 pen = gdImageColorAllocate(brush,
1907 im->gdes[cosel].col.red,
1908 im->gdes[cosel].col.green,
1909 im->gdes[cosel].col.blue);
1911 switch (typsel){
1912 case GF_LINE1:
1913 gdImageSetPixel(brush,0,0,pen);
1914 break;
1915 case GF_LINE2:
1916 gdImageSetPixel(brush,0,0,pen);
1917 gdImageSetPixel(brush,0,1,pen);
1918 gdImageSetPixel(brush,1,0,pen);
1919 gdImageSetPixel(brush,1,1,pen);
1920 break;
1921 case GF_LINE3:
1922 gdImageSetPixel(brush,1,0,pen);
1923 gdImageSetPixel(brush,0,1,pen);
1924 gdImageSetPixel(brush,1,1,pen);
1925 gdImageSetPixel(brush,2,1,pen);
1926 gdImageSetPixel(brush,1,2,pen);
1927 break;
1928 default:
1929 return NULL;
1930 }
1931 return brush;
1932 }
1933 /*****************************************************
1934 * lazy check make sure we rely need to create this graph
1935 *****************************************************/
1937 int lazy_check(image_desc_t *im){
1938 FILE *fd = NULL;
1939 int size = 1;
1940 struct stat gifstat;
1942 if (im->lazy == 0) return 0; /* no lazy option */
1943 if (stat(im->graphfile,&gifstat) != 0)
1944 return 0; /* can't stat */
1945 /* one pixel in the existing graph is more then what we would
1946 change here ... */
1947 if (time(NULL) - gifstat.st_mtime >
1948 (im->end - im->start) / im->xsize)
1949 return 0;
1950 if ((fd = fopen(im->graphfile,"rb")) == NULL)
1951 return 0; /* the file does not exist */
1952 switch (im->imgformat) {
1953 case IF_GIF:
1954 size = GifSize(fd,&(im->xgif),&(im->ygif));
1955 break;
1956 case IF_PNG:
1957 size = PngSize(fd,&(im->xgif),&(im->ygif));
1958 break;
1959 }
1960 fclose(fd);
1961 return size;
1962 }
1964 /* draw that picture thing ... */
1965 int
1966 graph_paint(image_desc_t *im, char ***calcpr)
1967 {
1968 int i,ii;
1969 int lazy = lazy_check(im);
1970 FILE *fo;
1972 /* gif stuff */
1973 gdImagePtr gif,brush;
1975 double areazero = 0.0;
1976 enum gf_en stack_gf = GF_PRINT;
1977 graph_desc_t *lastgdes = NULL;
1978 gdPoint canvas[4], back[4]; /* points for canvas*/
1980 /* if we are lazy and there is nothing to PRINT ... quit now */
1981 if (lazy && im->prt_c==0) return 0;
1983 /* pull the data from the rrd files ... */
1985 if(data_fetch(im)==-1)
1986 return -1;
1988 /* evaluate VDEF and CDEF operations ... */
1989 if(data_calc(im)==-1)
1990 return -1;
1992 /* calculate and PRINT and GPRINT definitions. We have to do it at
1993 * this point because it will affect the length of the legends
1994 * if there are no graph elements we stop here ...
1995 * if we are lazy, try to quit ...
1996 */
1997 i=print_calc(im,calcpr);
1998 if(i<0) return -1;
1999 if(i==0 || lazy) return 0;
2001 /* get actual drawing data and find min and max values*/
2002 if(data_proc(im)==-1)
2003 return -1;
2005 if(!im->logarithmic){si_unit(im);} /* identify si magnitude Kilo, Mega Giga ? */
2007 if(!im->rigid && ! im->logarithmic)
2008 expand_range(im); /* make sure the upper and lower limit are
2009 sensible values */
2011 /* init xtr and ytr */
2012 /* determine the actual size of the gif to draw. The size given
2013 on the cmdline is the graph area. But we need more as we have
2014 draw labels and other things outside the graph area */
2017 im->xorigin = 10 + 9 * SmallFont->w+SmallFont->h;
2018 xtr(im,0);
2020 im->yorigin = 14 + im->ysize;
2021 ytr(im,DNAN);
2023 if(im->title[0] != '\0')
2024 im->yorigin += (LargeFont->h+4);
2026 im->xgif=20+im->xsize + im->xorigin;
2027 im->ygif= im->yorigin+2*SmallFont->h;
2029 /* determine where to place the legends onto the graphics.
2030 and set im->ygif to match space requirements for text */
2031 if(leg_place(im)==-1)
2032 return -1;
2034 gif=gdImageCreate(im->xgif,im->ygif);
2036 gdImageInterlace(gif, im->interlaced);
2038 /* allocate colors for the screen elements */
2039 for(i=0;i<DIM(graph_col);i++)
2040 /* check for user override values */
2041 if(im->graph_col[i].red != -1)
2042 graph_col[i].i =
2043 gdImageColorAllocate( gif,
2044 im->graph_col[i].red,
2045 im->graph_col[i].green,
2046 im->graph_col[i].blue);
2047 else
2048 graph_col[i].i =
2049 gdImageColorAllocate( gif,
2050 graph_col[i].red,
2051 graph_col[i].green,
2052 graph_col[i].blue);
2055 /* allocate colors for the graph */
2056 for(i=0;i<im->gdes_c;i++)
2057 /* only for elements which have a color defined */
2058 if (im->gdes[i].col.red != -1)
2059 im->gdes[i].col.i =
2060 gdImageColorAllocate(gif,
2061 im->gdes[i].col.red,
2062 im->gdes[i].col.green,
2063 im->gdes[i].col.blue);
2066 /* the actual graph is created by going through the individual
2067 graph elements and then drawing them */
2069 back[0].x = 0;
2070 back[0].y = 0;
2071 back[1].x = back[0].x+im->xgif;
2072 back[1].y = back[0].y;
2073 back[2].x = back[1].x;
2074 back[2].y = back[0].y+im->ygif;
2075 back[3].x = back[0].x;
2076 back[3].y = back[2].y;
2078 gdImageFilledPolygon(gif,back,4,graph_col[GRC_BACK].i);
2080 canvas[0].x = im->xorigin;
2081 canvas[0].y = im->yorigin;
2082 canvas[1].x = canvas[0].x+im->xsize;
2083 canvas[1].y = canvas[0].y;
2084 canvas[2].x = canvas[1].x;
2085 canvas[2].y = canvas[0].y-im->ysize;
2086 canvas[3].x = canvas[0].x;
2087 canvas[3].y = canvas[2].y;
2089 gdImageFilledPolygon(gif,canvas,4,graph_col[GRC_CANVAS].i);
2091 if (im->minval > 0.0)
2092 areazero = im->minval;
2093 if (im->maxval < 0.0)
2094 areazero = im->maxval;
2096 axis_paint(im,gif);
2098 for(i=0;i<im->gdes_c;i++){
2100 switch(im->gdes[i].gf){
2101 case GF_CDEF:
2102 case GF_VDEF:
2103 case GF_DEF:
2104 case GF_PRINT:
2105 case GF_GPRINT:
2106 case GF_COMMENT:
2107 case GF_HRULE:
2108 case GF_VRULE:
2109 break;
2110 case GF_TICK:
2111 for (ii = 0; ii < im->xsize; ii++)
2112 {
2113 if (!isnan(im->gdes[i].p_data[ii]) &&
2114 im->gdes[i].p_data[ii] > 0.0)
2115 {
2116 /* generate a tick */
2117 gdImageLine(gif, im -> xorigin + ii,
2118 im -> yorigin - (im -> gdes[i].yrule * im -> ysize),
2119 im -> xorigin + ii,
2120 im -> yorigin,
2121 im -> gdes[i].col.i);
2122 }
2123 }
2124 break;
2125 case GF_LINE1:
2126 case GF_LINE2:
2127 case GF_LINE3:
2128 case GF_AREA:
2129 stack_gf = im->gdes[i].gf;
2130 case GF_STACK:
2131 /* fix data points at oo and -oo */
2132 for(ii=0;ii<im->xsize;ii++){
2133 if (isinf(im->gdes[i].p_data[ii])){
2134 if (im->gdes[i].p_data[ii] > 0) {
2135 im->gdes[i].p_data[ii] = im->maxval ;
2136 } else {
2137 im->gdes[i].p_data[ii] = im->minval ;
2138 }
2140 }
2141 }
2143 if (im->gdes[i].col.i != -1){
2144 /* GF_LINE and frined */
2145 if(stack_gf == GF_LINE1 || stack_gf == GF_LINE2 || stack_gf == GF_LINE3 ){
2146 brush = MkLineBrush(im,i,stack_gf);
2147 gdImageSetBrush(gif, brush);
2148 for(ii=1;ii<im->xsize;ii++){
2149 if (isnan(im->gdes[i].p_data[ii-1]) ||
2150 isnan(im->gdes[i].p_data[ii]))
2151 continue;
2152 gdImageLine(gif,
2153 ii+im->xorigin-1,ytr(im,im->gdes[i].p_data[ii-1]),
2154 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2155 gdBrushed);
2157 }
2158 gdImageDestroy(brush);
2159 }
2160 else
2161 /* GF_AREA STACK type*/
2162 if (im->gdes[i].gf == GF_STACK )
2163 for(ii=0;ii<im->xsize;ii++){
2164 if(isnan(im->gdes[i].p_data[ii])){
2165 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
2166 continue;
2167 }
2169 if (lastgdes->p_data[ii] == im->gdes[i].p_data[ii]){
2170 continue;
2171 }
2172 gdImageLine(gif,
2173 ii+im->xorigin,ytr(im,lastgdes->p_data[ii]),
2174 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2175 im->gdes[i].col.i);
2176 }
2178 else /* simple GF_AREA */
2179 for(ii=0;ii<im->xsize;ii++){
2180 if (isnan(im->gdes[i].p_data[ii])) {
2181 im->gdes[i].p_data[ii] = 0;
2182 continue;
2183 }
2184 gdImageLine(gif,
2185 ii+im->xorigin,ytr(im,areazero),
2186 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2187 im->gdes[i].col.i);
2188 }
2189 }
2190 lastgdes = &(im->gdes[i]);
2191 break;
2192 }
2193 }
2195 grid_paint(im,gif);
2197 /* the RULES are the last thing to paint ... */
2198 for(i=0;i<im->gdes_c;i++){
2200 switch(im->gdes[i].gf){
2201 case GF_HRULE:
2202 if(isnan(im->gdes[i].yrule)) { /* fetch variable */
2203 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2204 };
2205 if(im->gdes[i].yrule >= im->minval
2206 && im->gdes[i].yrule <= im->maxval)
2207 gdImageLine(gif,
2208 im->xorigin,ytr(im,im->gdes[i].yrule),
2209 im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2210 im->gdes[i].col.i);
2211 break;
2212 case GF_VRULE:
2213 if(im->gdes[i].xrule == 0) { /* fetch variable */
2214 im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
2215 };
2216 if(im->gdes[i].xrule >= im->start
2217 && im->gdes[i].xrule <= im->end)
2218 gdImageLine(gif,
2219 xtr(im,im->gdes[i].xrule),im->yorigin,
2220 xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2221 im->gdes[i].col.i);
2222 break;
2223 default:
2224 break;
2225 }
2226 }
2228 if (strcmp(im->graphfile,"-")==0) {
2229 #ifdef WIN32
2230 /* Change translation mode for stdout to BINARY */
2231 _setmode( _fileno( stdout ), O_BINARY );
2232 #endif
2233 fo = stdout;
2234 } else {
2235 if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2236 rrd_set_error("Opening '%s' for write: %s",im->graphfile, strerror(errno));
2237 return (-1);
2238 }
2239 }
2240 switch (im->imgformat) {
2241 case IF_GIF:
2242 gdImageGif(gif, fo);
2243 break;
2244 case IF_PNG:
2245 gdImagePng(gif, fo);
2246 break;
2247 }
2248 if (strcmp(im->graphfile,"-") != 0)
2249 fclose(fo);
2250 gdImageDestroy(gif);
2252 return 0;
2253 }
2256 /*****************************************************
2257 * graph stuff
2258 *****************************************************/
2260 int
2261 gdes_alloc(image_desc_t *im){
2263 long def_step = (im->end-im->start)/im->xsize;
2265 if (im->step > def_step) /* step can be increassed ... no decreassed */
2266 def_step = im->step;
2268 im->gdes_c++;
2270 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2271 * sizeof(graph_desc_t)))==NULL){
2272 rrd_set_error("realloc graph_descs");
2273 return -1;
2274 }
2277 im->gdes[im->gdes_c-1].step=def_step;
2278 im->gdes[im->gdes_c-1].start=im->start;
2279 im->gdes[im->gdes_c-1].end=im->end;
2280 im->gdes[im->gdes_c-1].vname[0]='\0';
2281 im->gdes[im->gdes_c-1].data=NULL;
2282 im->gdes[im->gdes_c-1].ds_namv=NULL;
2283 im->gdes[im->gdes_c-1].data_first=0;
2284 im->gdes[im->gdes_c-1].p_data=NULL;
2285 im->gdes[im->gdes_c-1].rpnp=NULL;
2286 im->gdes[im->gdes_c-1].col.red = -1;
2287 im->gdes[im->gdes_c-1].col.i=-1;
2288 im->gdes[im->gdes_c-1].legend[0]='\0';
2289 im->gdes[im->gdes_c-1].rrd[0]='\0';
2290 im->gdes[im->gdes_c-1].ds=-1;
2291 im->gdes[im->gdes_c-1].p_data=NULL;
2292 return 0;
2293 }
2295 /* copies input untill the first unescaped colon is found
2296 or until input ends. backslashes have to be escaped as well */
2297 int
2298 scan_for_col(char *input, int len, char *output)
2299 {
2300 int inp,outp=0;
2301 for (inp=0;
2302 inp < len &&
2303 input[inp] != ':' &&
2304 input[inp] != '\0';
2305 inp++){
2306 if (input[inp] == '\\' &&
2307 input[inp+1] != '\0' &&
2308 (input[inp+1] == '\\' ||
2309 input[inp+1] == ':')){
2310 output[outp++] = input[++inp];
2311 }
2312 else {
2313 output[outp++] = input[inp];
2314 }
2315 }
2316 output[outp] = '\0';
2317 return inp;
2318 }
2320 /* Some surgery done on this function, it became ridiculously big.
2321 ** Things moved:
2322 ** - initializing now in rrd_graph_init()
2323 ** - options parsing now in rrd_graph_options()
2324 ** - script parsing now in rrd_graph_script()
2325 */
2326 int
2327 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize)
2328 {
2329 image_desc_t im;
2331 rrd_graph_init(&im);
2333 rrd_graph_options(argc,argv,&im);
2334 if (rrd_test_error()) return -1;
2336 if (strlen(argv[optind])>=MAXPATH) {
2337 rrd_set_error("filename (including path) too long");
2338 return -1;
2339 }
2340 strncpy(im.graphfile,argv[optind],MAXPATH-1);
2341 im.graphfile[MAXPATH-1]='\0';
2343 rrd_graph_script(argc,argv,&im);
2344 if (rrd_test_error()) return -1;
2346 /* Everything is now read and the actual work can start */
2348 (*prdata)=NULL;
2349 if (graph_paint(&im,prdata)==-1){
2350 im_free(&im);
2351 return -1;
2352 }
2354 /* The image is generated and needs to be output.
2355 ** Also, if needed, print a line with information about the image.
2356 */
2358 *xsize=im.xgif;
2359 *ysize=im.ygif;
2360 if (im.imginfo) {
2361 char *filename;
2362 if (!(*prdata)) {
2363 /* maybe prdata is not allocated yet ... lets do it now */
2364 if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
2365 rrd_set_error("malloc imginfo");
2366 return -1;
2367 };
2368 }
2369 if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
2370 ==NULL){
2371 rrd_set_error("malloc imginfo");
2372 return -1;
2373 }
2374 filename=im.graphfile+strlen(im.graphfile);
2375 while(filename > im.graphfile) {
2376 if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
2377 filename--;
2378 }
2380 sprintf((*prdata)[0],im.imginfo,filename,im.xgif,im.ygif);
2381 }
2382 im_free(&im);
2383 return 0;
2384 }
2386 void
2387 rrd_graph_init(image_desc_t *im)
2388 {
2389 int i;
2391 im->xlab_user.minsec = -1;
2392 im->xgif=0;
2393 im->ygif=0;
2394 im->xsize = 400;
2395 im->ysize = 100;
2396 im->step = 0;
2397 im->ylegend[0] = '\0';
2398 im->title[0] = '\0';
2399 im->minval = DNAN;
2400 im->maxval = DNAN;
2401 im->interlaced = 0;
2402 im->unitsexponent= 9999;
2403 im->extra_flags= 0;
2404 im->rigid = 0;
2405 im->imginfo = NULL;
2406 im->lazy = 0;
2407 im->logarithmic = 0;
2408 im->ygridstep = DNAN;
2409 im->draw_x_grid = 1;
2410 im->draw_y_grid = 1;
2411 im->base = 1000;
2412 im->prt_c = 0;
2413 im->gdes_c = 0;
2414 im->gdes = NULL;
2415 im->imgformat = IF_GIF; /* we default to GIF output */
2417 for(i=0;i<DIM(graph_col);i++)
2418 im->graph_col[i].red=-1;
2419 }
2421 void
2422 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
2423 {
2424 int stroff;
2425 char *parsetime_error = NULL;
2426 char scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
2427 time_t start_tmp=0,end_tmp=0;
2428 long long_tmp;
2429 struct time_value start_tv, end_tv;
2430 unsigned int col_red,col_green,col_blue;
2432 parsetime("end-24h", &start_tv);
2433 parsetime("now", &end_tv);
2435 while (1){
2436 static struct option long_options[] =
2437 {
2438 {"start", required_argument, 0, 's'},
2439 {"end", required_argument, 0, 'e'},
2440 {"x-grid", required_argument, 0, 'x'},
2441 {"y-grid", required_argument, 0, 'y'},
2442 {"vertical-label",required_argument,0,'v'},
2443 {"width", required_argument, 0, 'w'},
2444 {"height", required_argument, 0, 'h'},
2445 {"interlaced", no_argument, 0, 'i'},
2446 {"upper-limit",required_argument, 0, 'u'},
2447 {"lower-limit",required_argument, 0, 'l'},
2448 {"rigid", no_argument, 0, 'r'},
2449 {"base", required_argument, 0, 'b'},
2450 {"logarithmic",no_argument, 0, 'o'},
2451 {"color", required_argument, 0, 'c'},
2452 {"title", required_argument, 0, 't'},
2453 {"imginfo", required_argument, 0, 'f'},
2454 {"imgformat", required_argument, 0, 'a'},
2455 {"lazy", no_argument, 0, 'z'},
2456 {"no-legend", no_argument, 0, 'g'},
2457 {"alt-y-grid", no_argument, 0, 257 },
2458 {"alt-autoscale", no_argument, 0, 258 },
2459 {"alt-autoscale-max", no_argument, 0, 259 },
2460 {"units-exponent",required_argument, 0, 260},
2461 {"step", required_argument, 0, 261},
2462 {0,0,0,0}};
2463 int option_index = 0;
2464 int opt;
2467 opt = getopt_long(argc, argv,
2468 "s:e:x:y:v:w:h:iu:l:rb:oc:t:f:a:z:g",
2469 long_options, &option_index);
2471 if (opt == EOF)
2472 break;
2474 switch(opt) {
2475 case 257:
2476 im->extra_flags |= ALTYGRID;
2477 break;
2478 case 258:
2479 im->extra_flags |= ALTAUTOSCALE;
2480 break;
2481 case 259:
2482 im->extra_flags |= ALTAUTOSCALE_MAX;
2483 break;
2484 case 'g':
2485 im->extra_flags |= NOLEGEND;
2486 break;
2487 case 260:
2488 im->unitsexponent = atoi(optarg);
2489 break;
2490 case 261:
2491 im->step = atoi(optarg);
2492 break;
2493 case 's':
2494 if ((parsetime_error = parsetime(optarg, &start_tv))) {
2495 rrd_set_error( "start time: %s", parsetime_error );
2496 return;
2497 }
2498 break;
2499 case 'e':
2500 if ((parsetime_error = parsetime(optarg, &end_tv))) {
2501 rrd_set_error( "end time: %s", parsetime_error );
2502 return;
2503 }
2504 break;
2505 case 'x':
2506 if(strcmp(optarg,"none") == 0){
2507 im->draw_x_grid=0;
2508 break;
2509 };
2511 if(sscanf(optarg,
2512 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
2513 scan_gtm,
2514 &im->xlab_user.gridst,
2515 scan_mtm,
2516 &im->xlab_user.mgridst,
2517 scan_ltm,
2518 &im->xlab_user.labst,
2519 &im->xlab_user.precis,
2520 &stroff) == 7 && stroff != 0){
2521 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
2522 if((im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
2523 rrd_set_error("unknown keyword %s",scan_gtm);
2524 return;
2525 } else if ((im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
2526 rrd_set_error("unknown keyword %s",scan_mtm);
2527 return;
2528 } else if ((im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
2529 rrd_set_error("unknown keyword %s",scan_ltm);
2530 return;
2531 }
2532 im->xlab_user.minsec = 1;
2533 im->xlab_user.stst = im->xlab_form;
2534 } else {
2535 rrd_set_error("invalid x-grid format");
2536 return;
2537 }
2538 break;
2539 case 'y':
2541 if(strcmp(optarg,"none") == 0){
2542 im->draw_y_grid=0;
2543 break;
2544 };
2546 if(sscanf(optarg,
2547 "%lf:%d",
2548 &im->ygridstep,
2549 &im->ylabfact) == 2) {
2550 if(im->ygridstep<=0){
2551 rrd_set_error("grid step must be > 0");
2552 return;
2553 } else if (im->ylabfact < 1){
2554 rrd_set_error("label factor must be > 0");
2555 return;
2556 }
2557 } else {
2558 rrd_set_error("invalid y-grid format");
2559 return;
2560 }
2561 break;
2562 case 'v':
2563 strncpy(im->ylegend,optarg,150);
2564 im->ylegend[150]='\0';
2565 break;
2566 case 'u':
2567 im->maxval = atof(optarg);
2568 break;
2569 case 'l':
2570 im->minval = atof(optarg);
2571 break;
2572 case 'b':
2573 im->base = atol(optarg);
2574 if(im->base != 1024 && im->base != 1000 ){
2575 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
2576 return;
2577 }
2578 break;
2579 case 'w':
2580 long_tmp = atol(optarg);
2581 if (long_tmp < 10) {
2582 rrd_set_error("width below 10 pixels");
2583 return;
2584 }
2585 im->xsize = long_tmp;
2586 break;
2587 case 'h':
2588 long_tmp = atol(optarg);
2589 if (long_tmp < 10) {
2590 rrd_set_error("height below 10 pixels");
2591 return;
2592 }
2593 im->ysize = long_tmp;
2594 break;
2595 case 'i':
2596 im->interlaced = 1;
2597 break;
2598 case 'r':
2599 im->rigid = 1;
2600 break;
2601 case 'f':
2602 im->imginfo = optarg;
2603 break;
2604 case 'a':
2605 if((im->imgformat = if_conv(optarg)) == -1) {
2606 rrd_set_error("unsupported graphics format '%s'",optarg);
2607 return;
2608 }
2609 break;
2610 case 'z':
2611 im->lazy = 1;
2612 break;
2613 case 'o':
2614 im->logarithmic = 1;
2615 if (isnan(im->minval))
2616 im->minval=1;
2617 break;
2618 case 'c':
2619 if(sscanf(optarg,
2620 "%10[A-Z]#%2x%2x%2x",
2621 col_nam,&col_red,&col_green,&col_blue) == 4){
2622 int ci;
2623 if((ci=grc_conv(col_nam)) != -1){
2624 im->graph_col[ci].red=col_red;
2625 im->graph_col[ci].green=col_green;
2626 im->graph_col[ci].blue=col_blue;
2627 } else {
2628 rrd_set_error("invalid color name '%s'",col_nam);
2629 }
2630 } else {
2631 rrd_set_error("invalid color def format");
2632 return;
2633 }
2634 break;
2635 case 't':
2636 strncpy(im->title,optarg,150);
2637 im->title[150]='\0';
2638 break;
2640 case '?':
2641 if (optopt != 0)
2642 rrd_set_error("unknown option '%c'", optopt);
2643 else
2644 rrd_set_error("unknown option '%s'",argv[optind-1]);
2645 return;
2646 }
2647 }
2649 if (optind >= argc) {
2650 rrd_set_error("missing filename");
2651 return;
2652 }
2654 if (im->logarithmic == 1 && (im->minval <= 0 || isnan(im->minval))){
2655 rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");
2656 return;
2657 }
2659 if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
2660 /* error string is set in parsetime.c */
2661 return;
2662 }
2664 if (start_tmp < 3600*24*365*10){
2665 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
2666 return;
2667 }
2669 if (end_tmp < start_tmp) {
2670 rrd_set_error("start (%ld) should be less than end (%ld)",
2671 start_tmp, end_tmp);
2672 return;
2673 }
2675 im->start = start_tmp;
2676 im->end = end_tmp;
2677 }
2679 void
2680 rrd_graph_script(int argc, char *argv[], image_desc_t *im)
2681 {
2682 int i;
2683 char symname[100];
2684 int linepass = 0; /* stack must follow LINE*, AREA or STACK */
2686 for (i=optind+1;i<argc;i++) {
2687 int argstart=0;
2688 int strstart=0;
2689 graph_desc_t *gdp;
2690 char *line;
2691 char funcname[10],vname[MAX_VNAME_LEN+1],sep[1];
2692 double d;
2693 int j,k,l,m;
2695 /* Each command is one element from *argv[], we call this "line".
2696 **
2697 ** Each command defines the most current gdes inside struct im.
2698 ** In stead of typing "im->gdes[im->gdes_c-1]" we use "gdp".
2699 */
2700 gdes_alloc(im);
2701 gdp=&im->gdes[im->gdes_c-1];
2702 line=argv[i];
2704 /* function:newvname=string[:ds-name:CF] for xDEF
2705 ** function:vname[#color[:string]] for LINEx,AREA,STACK
2706 ** function:vname#color[:num[:string]] for TICK
2707 ** function:vname-or-num#color[:string] for xRULE
2708 ** function:vname:CF:string for xPRINT
2709 ** function:string for COMMENT
2710 */
2711 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 ((gdp->gf=gf_conv(funcname))==-1) {
2719 rrd_set_error("'%s' is not a valid function name",funcname);
2720 im_free(im);
2721 return;
2722 }
2724 /* If the error string is set, we exit at the end of the switch */
2725 switch (gdp->gf) {
2726 case GF_COMMENT:
2727 if (rrd_graph_legend(gdp,&line[argstart])==0)
2728 rrd_set_error("Cannot parse comment in line: %s",line);
2729 break;
2730 case GF_VRULE:
2731 case GF_HRULE:
2732 j=k=l=m=0;
2733 sscanf(&line[argstart], "%lf%n#%n", &d, &j, &k);
2734 sscanf(&line[argstart], DEF_NAM_FMT "%n#%n", vname, &l, &m);
2735 if (k+m==0) {
2736 rrd_set_error("Cannot parse name or num in line: %s",line);
2737 break;
2738 }
2739 if (j!=0) {
2740 gdp->xrule=d;
2741 gdp->yrule=d;
2742 argstart+=j;
2743 } else if (!rrd_graph_check_vname(im,vname,line)) {
2744 gdp->xrule=0;
2745 gdp->yrule=DNAN;
2746 argstart+=l;
2747 } else break; /* exit due to wrong vname */
2748 if ((j=rrd_graph_color(im,&line[argstart],line,0))==0) break;
2749 argstart+=j;
2750 if (strlen(&line[argstart])!=0) {
2751 if (rrd_graph_legend(gdp,&line[++argstart])==0)
2752 rrd_set_error("Cannot parse comment in line: %s",line);
2753 }
2754 break;
2755 case GF_STACK:
2756 if (linepass==0) {
2757 rrd_set_error("STACK must follow another graphing element");
2758 break;
2759 }
2760 case GF_LINE1:
2761 case GF_LINE2:
2762 case GF_LINE3:
2763 case GF_AREA:
2764 case GF_TICK:
2765 j=k=0;
2766 linepass=1;
2767 sscanf(&line[argstart],DEF_NAM_FMT"%n%1[#:]%n",vname,&j,sep,&k);
2768 if (j+1!=k)
2769 rrd_set_error("Cannot parse vname in line: %s",line);
2770 else if (rrd_graph_check_vname(im,vname,line))
2771 rrd_set_error("Undefined vname '%s' in line: %s",line);
2772 else
2773 k=rrd_graph_color(im,&line[argstart],line,1);
2774 if (rrd_test_error()) break;
2775 argstart=argstart+j+k;
2776 if ((strlen(&line[argstart])!=0)&&(gdp->gf==GF_TICK)) {
2777 j=0;
2778 sscanf(&line[argstart], ":%lf%n", &gdp->yrule,&j);
2779 argstart+=j;
2780 }
2781 if (strlen(&line[argstart])!=0)
2782 if (rrd_graph_legend(gdp,&line[++argstart])==0)
2783 rrd_set_error("Cannot parse legend in line: %s",line);
2784 break;
2785 case GF_PRINT:
2786 im->prt_c++;
2787 case GF_GPRINT:
2788 j=0;
2789 sscanf(&line[argstart], DEF_NAM_FMT ":%n",gdp->vname,&j);
2790 if (j==0) {
2791 rrd_set_error("Cannot parse vname in line: '%s'",line);
2792 break;
2793 }
2794 argstart+=j;
2795 if (rrd_graph_check_vname(im,gdp->vname,line)) return;
2796 j=0;
2797 sscanf(&line[argstart], CF_NAM_FMT ":%n",symname,&j);
2799 k=(j!=0)?rrd_graph_check_CF(im,symname,line):1;
2800 #define VIDX im->gdes[gdp->vidx]
2801 switch (k) {
2802 case -1: /* looks CF but is not really CF */
2803 if (VIDX.gf == GF_VDEF) rrd_clear_error();
2804 break;
2805 case 0: /* CF present and correct */
2806 if (VIDX.gf == GF_VDEF)
2807 rrd_set_error("Don't use CF when printing VDEF");
2808 argstart+=j;
2809 break;
2810 case 1: /* CF not present */
2811 if (VIDX.gf == GF_VDEF) rrd_clear_error();
2812 else rrd_set_error("Printing DEF or CDEF needs CF");
2813 break;
2814 default:
2815 rrd_set_error("Oops, bug in GPRINT scanning");
2816 }
2817 #undef VIDX
2818 if (rrd_test_error()) break;
2820 if (strlen(&line[argstart])!=0) {
2821 if (rrd_graph_legend(gdp,&line[argstart])==0)
2822 rrd_set_error("Cannot parse legend in line: %s",line);
2823 } else rrd_set_error("No legend in (G)PRINT line: %s",line);
2824 strcpy(gdp->format, gdp->legend);
2825 break;
2826 case GF_DEF:
2827 case GF_VDEF:
2828 case GF_CDEF:
2829 j=0;
2830 sscanf(&line[argstart], DEF_NAM_FMT "=%n",gdp->vname,&j);
2831 if (j==0) {
2832 rrd_set_error("Could not parse line: %s",line);
2833 break;
2834 }
2835 if (find_var(im,gdp->vname)!=-1) {
2836 rrd_set_error("Variable '%s' in line '%s' already in use\n",
2837 gdp->vname,line);
2838 break;
2839 }
2840 argstart+=j;
2841 switch (gdp->gf) {
2842 case GF_DEF:
2843 argstart+=scan_for_col(&line[argstart],MAXPATH,gdp->rrd);
2844 j=k=0;
2845 sscanf(&line[argstart],
2846 ":" DS_NAM_FMT ":" CF_NAM_FMT "%n%*s%n",
2847 gdp->ds_nam, symname, &j, &k);
2848 if ((j==0)||(k!=0)) {
2849 rrd_set_error("Cannot parse DS or CF in '%s'",line);
2850 break;
2851 }
2852 rrd_graph_check_CF(im,symname,line);
2853 break;
2854 case GF_VDEF:
2855 j=0;
2856 sscanf(&line[argstart],DEF_NAM_FMT ",%n",vname,&j);
2857 if (j==0) {
2858 rrd_set_error("Cannot parse vname in line '%s'",line);
2859 break;
2860 }
2861 argstart+=j;
2862 if (rrd_graph_check_vname(im,vname,line)) return;
2863 if ( im->gdes[gdp->vidx].gf != GF_DEF
2864 && im->gdes[gdp->vidx].gf != GF_CDEF) {
2865 rrd_set_error("variable '%s' not DEF nor "
2866 "CDEF in VDEF '%s'", vname,gdp->vname);
2867 break;
2868 }
2869 vdef_parse(gdp,&line[argstart+strstart]);
2870 break;
2871 case GF_CDEF:
2872 if (strstr(&line[argstart],":")!=NULL) {
2873 rrd_set_error("Error in RPN, line: %s",line);
2874 break;
2875 }
2876 if ((gdp->rpnp = rpn_parse(
2877 (void *)im,
2878 &line[argstart],
2879 &find_var_wrapper)
2880 )==NULL)
2881 rrd_set_error("invalid rpn expression in: %s",line);
2882 break;
2883 default: break;
2884 }
2885 break;
2886 default: rrd_set_error("Big oops");
2887 }
2888 if (rrd_test_error()) {
2889 im_free(im);
2890 return;
2891 }
2892 }
2894 if (im->gdes_c==0){
2895 rrd_set_error("can't make a graph without contents");
2896 im_free(im); /* ??? is this set ??? */
2897 return;
2898 }
2899 }
2900 int
2901 rrd_graph_check_vname(image_desc_t *im, char *varname, char *err)
2902 {
2903 if ((im->gdes[im->gdes_c-1].vidx=find_var(im,varname))==-1) {
2904 rrd_set_error("Unknown variable '%s' in %s",varname,err);
2905 return -1;
2906 }
2907 return 0;
2908 }
2909 int
2910 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
2911 {
2912 char *color;
2913 graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
2915 color=strstr(var,"#");
2916 if (color==NULL) {
2917 if (optional==0) {
2918 rrd_set_error("Found no color in %s",err);
2919 return 0;
2920 }
2921 return 0;
2922 } else {
2923 int n=0;
2924 char *rest;
2925 unsigned int R,G,B,A;
2927 rest=strstr(color,":");
2928 if (rest!=NULL)
2929 n=rest-color;
2930 else
2931 n=strlen(color);
2933 switch (n) {
2934 case 7:
2935 sscanf(color,"#%2x%2x%2x%n",&R,&G,&B,&n);
2936 A=255;
2937 if (n!=7) rrd_set_error("Color problem in %s",err);
2938 break;
2939 case 9:
2940 sscanf(color,"#%2x%2x%2x%2x%n",&R,&G,&B,&A,&n);
2941 if (n==9) break;
2942 default:
2943 rrd_set_error("Color problem in %s",err);
2944 }
2945 if (rrd_test_error()) return 0;
2946 gdp->col.red = R;
2947 gdp->col.green = G;
2948 gdp->col.blue = B;
2949 /* gdp->col.alpha = A; */
2950 if (n==9) printf("WARNING: alpha channel not yet supported\n");
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 }