1 /****************************************************************************
2 * RRDtool 1.0.33 Copyright Tobias Oetiker, 1997 - 2000
3 ****************************************************************************
4 * rrd__graph.c make creates ne rrds
5 ****************************************************************************/
12 #include <gdlucidan10.h>
13 #include <gdlucidab12.h>
20 #include "rrd_graph.h"
21 #include "rrd_graph_helper.h"
23 #define SmallFont gdLucidaNormal10
24 #define LargeFont gdLucidaBold12
29 # define DPRINT(x) (void)(printf x, printf("\n"))
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,""}
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 ...*/
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*/
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); */
104 xtr(image_desc_t *im,time_t mytime){
107 pixie = (double) im->xsize / (double)(im->end - im->start);
110 return (int)((double)im->xorigin
111 + pixie * ( mytime - im->start ) );
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) */
120 ytr(image_desc_t *im, double value){
125 pixie = (double) im->ysize / (im->maxval - im->minval);
127 pixie = (double) im->ysize / (log10(im->maxval) - log10(im->minval));
129 } else if(!im->logarithmic) {
130 yval = im->yorigin - pixie * (value - im->minval) + 0.5;
132 if (value < im->minval) {
135 yval = im->yorigin - pixie * (log10(value) - log10(im->minval)) + 0.5;
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 */
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;
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)
174 conv_if(CDEF,GF_CDEF)
175 conv_if(VDEF,GF_VDEF)
180 enum if_en if_conv(char *string){
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)
194 conv_if(WEEK,TMT_WEEK)
195 conv_if(MONTH,TMT_MONTH)
196 conv_if(YEAR,TMT_YEAR)
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)
220 im_free(image_desc_t *im)
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);
234 free (im->gdes[i].p_data);
235 free (im->gdes[i].rpnp);
241 /* find SI magnitude symbol for the given number*/
244 image_desc_t *im, /* image description */
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 */
261 "T", /* 10e12 Tera */
262 "P", /* 10e15 Peta */
268 if (*value == 0.0 || isnan(*value) ) {
272 sindex = floor(log(fabs(*value))/log((double)im->base));
273 *magfact = pow((double)im->base, (double)sindex);
274 (*value) /= (*magfact);
276 if ( sindex <= symbcenter && sindex >= -symbcenter) {
277 (*symb_ptr) = symbol[sindex+symbcenter];
285 /* find SI magnitude symbol for the numbers on the y-axis*/
288 image_desc_t *im /* image description */
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 */
302 'T', /* 10e12 Tera */
303 'P', /* 10e15 Peta */
309 if (im->unitsexponent != 9999) {
310 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
311 digits = floor(im->unitsexponent / 3);
313 digits = floor( log( max( fabs(im->minval),fabs(im->maxval)))/log((double)im->base));
315 im->magfact = pow((double)im->base , digits);
318 printf("digits %6.3f im->magfact %6.3f\n",digits,im->magfact);
321 if ( ((digits+symbcenter) < sizeof(symbol)) &&
322 ((digits+symbcenter) >= 0) )
323 im->symbol = symbol[(int)digits+symbcenter];
328 /* move min and max values around to become sensible */
331 expand_range(image_desc_t *im)
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;
349 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
350 im->minval,im->maxval,im->magfact);
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 */
360 delt = im->maxval - im->minval;
362 fact = 2.0 * pow(10.0,
363 floor(log10(max(fabs(im->minval), fabs(im->maxval)))) - 2);
365 adj = (fact - delt) * 0.55;
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);
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;
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);
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));
411 fprintf(stderr,"SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
412 im->minval,im->maxval,im->magfact);
417 /* reduce data reimplementation by Alex */
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 */
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 */
436 row_cnt = ((*end)-(*start))/cur_step;
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]);
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.
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).
470 printf("start_offset: %lu end_offset: %lu\n",start_offset,end_offset);
471 printf("row_cnt before: %lu\n",row_cnt);
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;
481 printf("row_cnt between: %lu\n",row_cnt);
484 /* At the end we have some rows that are not going to be
485 ** used, the amount is end_offset/cur_step
488 (*end) = (*end)-end_offset+(*step);
489 skiprows = end_offset/cur_step;
493 printf("row_cnt after: %lu\n",row_cnt);
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");
506 /* Now combine reduce_factor intervals at a time
507 ** into one interval for the destination.
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])) {
520 if (isnan(newval)) newval = srcptr[i*(*ds_cnt)+col];
528 newval += srcptr[i*(*ds_cnt)+col];
531 newval = min (newval,srcptr[i*(*ds_cnt)+col]);
534 /* an interval contains a failure if any subintervals contained a failure */
536 newval = max (newval,srcptr[i*(*ds_cnt)+col]);
539 newval = srcptr[i*(*ds_cnt)+col];
544 if (validval == 0){newval = DNAN;} else{
562 srcptr+=(*ds_cnt)*reduce_factor;
563 row_cnt-=reduce_factor;
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.
568 if (end_offset) for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
570 row_cnt = ((*end)-(*start))/ *step;
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]);
584 /* get the data required for the graphs from the
588 data_fetch( image_desc_t *im )
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)
599 /* do we have it already ?*/
600 for (ii=0;ii<i;ii++){
601 if (im->gdes[ii].gf != GF_DEF)
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;
620 unsigned long ft_step = im->gdes[i].step ;
622 if((rrd_fetch_fn(im->gdes[i].rrd,
628 &im->gdes[i].ds_namv,
629 &im->gdes[i].data)) == -1){
632 im->gdes[i].data_first = 1;
634 if (ft_step < im->gdes[i].step) {
635 reduce_data(im->gdes[i].cf,
643 im->gdes[i].step = ft_step;
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){
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);
662 /* evaluate the expressions in the CDEF functions */
664 /*************************************************************
666 *************************************************************/
669 find_var_wrapper(void *arg1, char *key)
671 return find_var((image_desc_t *) arg1, key);
674 /* find gdes containing var*/
676 find_var(image_desc_t *im, char *key){
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)){
689 /* find the largest common denominator for all the numbers
690 in the 0 terminated num array */
695 for (i=0;num[i+1]!=0;i++){
697 rest=num[i] % num[i+1];
698 num[i]=num[i+1]; num[i+1]=rest;
702 /* return i==0?num[i]:num[i-1]; */
706 /* run the rpn calculator on all the VDEF and CDEF arguments */
708 data_calc( image_desc_t *im){
712 long *steparray, rpi;
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
723 switch (im->gdes[gdi].gf) {
725 /* A VDEF has no DS. This also signals other parts
726 * of rrdtool that this is a VDEF value, not a CDEF.
728 im->gdes[gdi].ds_cnt = 0;
729 if (vdef_calc(im,gdi)) {
730 rrd_set_error("Error processing VDEF '%s'"
733 rpnstack_free(&rpnstack);
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;
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.
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) {
761 printf("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
763 im->gdes[ptr].vname);
764 printf("DEBUG: value from vdef is %f\n",im->gdes[ptr].vf.val);
766 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
767 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
769 if ((steparray = rrd_realloc(steparray, (++stepcnt+1)*sizeof(*steparray)))==NULL){
770 rrd_set_error("realloc steparray");
771 rpnstack_free(&rpnstack);
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)
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
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
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);
812 steparray[stepcnt]=0;
813 /* Now find the resulting step. All steps in all
814 * used RRAs have to be visited
816 im->gdes[gdi].step = lcd(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);
827 /* Step through the new cdef results array and
828 * calculate the values
830 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
831 now<=im->gdes[gdi].end;
832 now += im->gdes[gdi].step)
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
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);
846 } /* enumerate over time steps within a CDEF */
851 } /* enumerate over CDEFs */
852 rpnstack_free(&rpnstack);
856 /* massage data so, that we get one value for each x coordinate in the graph */
858 data_proc( image_desc_t *im ){
860 double pixstep = (double)(im->end-im->start)
861 /(double)im->xsize; /* how much time
862 passes in one pixel */
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");
884 for(i=0;i<im->xsize;i++){
886 gr_time = im->start+pixstep*i; /* time of the
890 for(ii=0;ii<im->gdes_c;ii++){
892 switch(im->gdes[ii].gf){
900 vidx = im->gdes[ii].vidx;
904 ((unsigned long)floor(
905 (double)(gr_time-im->gdes[vidx].start) / im->gdes[vidx].step
907 ) *im->gdes[vidx].ds_cnt
910 if (! isnan(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)
917 if (isnan(maxval) || paintval > maxval)
921 im->gdes[ii].p_data[i] = DNAN;
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))
949 if (isnan(im->maxval)
951 && im->maxval < maxval)){
953 im->maxval = maxval * 1.1;
957 /* make sure min and max are not equal */
958 if (im->minval == im->maxval) {
960 if (! im->logarithmic) {
964 /* make sure min and max are not both zero */
965 if (im->maxval == 0.0) {
975 /* identify the point where the first gridline, label ... gets placed */
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 */
985 tm = *localtime(&start);
988 tm.tm_sec -= tm.tm_sec % basestep; break;
991 tm.tm_min -= tm.tm_min % basestep;
996 tm.tm_hour -= tm.tm_hour % basestep; break;
998 /* we do NOT look at the basestep for this ... */
1001 tm.tm_hour = 0; break;
1003 /* we do NOT look at the basestep for this ... */
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 */
1015 tm.tm_mon -= tm.tm_mon % basestep; break;
1023 tm.tm_year -= (tm.tm_year+1900) % basestep;
1028 /* identify the point where the next gridline, label ... gets placed */
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 */
1038 tm = *localtime(¤t);
1042 tm.tm_sec += basestep; break;
1044 tm.tm_min += basestep; break;
1046 tm.tm_hour += basestep; break;
1048 tm.tm_mday += basestep; break;
1050 tm.tm_mday += 7*basestep; break;
1052 tm.tm_mon += basestep; break;
1054 tm.tm_year += basestep;
1056 madetime = mktime(&tm);
1057 } while (madetime == -1); /* this is necessary to skip impssible times
1058 like the daylight saving time skips */
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.
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.
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,
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};
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);
1111 /* calculate values required for PRINT and GPRINT functions */
1114 print_calc(image_desc_t *im, char ***prdata)
1116 long i,ii,validsteps;
1119 int graphelement = 0;
1122 double magfact = -1;
1126 if (im->imginfo) prlines++;
1127 for(i=0;i<im->gdes_c;i++){
1128 switch(im->gdes[i].gf){
1131 if(((*prdata) = rrd_realloc((*prdata),prlines*sizeof(char *)))==NULL){
1132 rrd_set_error("realloc prdata");
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.
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);
1151 for( ii=im->gdes[vidx].ds;
1153 ii+=im->gdes[vidx].ds_cnt){
1154 if (! finite(im->gdes[vidx].data[ii]))
1156 if (isnan(printval)){
1157 printval = im->gdes[vidx].data[ii];
1162 switch (im->gdes[i].cf){
1165 case CF_DEVSEASONAL:
1169 printval += im->gdes[vidx].data[ii];
1172 printval = min( printval, im->gdes[vidx].data[ii]);
1176 printval = max( printval, im->gdes[vidx].data[ii]);
1179 printval = im->gdes[vidx].data[ii];
1182 if (im->gdes[i].cf==CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1183 if (validsteps > 1) {
1184 printval = (printval / validsteps);
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;
1196 sprintf(im->gdes[i].legend,"%s (%lu)",
1197 ctime(&printtime),printtime);
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)
1212 printval /= magfact;
1214 *(++percent_s) = 's';
1215 } else if (strstr(im->gdes[i].format,"%s") != NULL) {
1216 auto_scale(im,&printval,&si_symb,&magfact);
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);
1225 #ifdef HAVE_SNPRINTF
1226 snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
1228 sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
1230 (*prdata)[prlines-1] = NULL;
1234 if (bad_format(im->gdes[i].format)) {
1235 rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1238 #ifdef HAVE_SNPRINTF
1239 snprintf(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
1241 sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
1264 return graphelement;
1268 /* place legends with color spots */
1270 leg_place(image_desc_t *im)
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;
1278 int leg_x = border, leg_y = im->ygif;
1282 char prt_fctn; /*special printfunctions */
1285 if( !(im->extra_flags & NOLEGEND) ) {
1286 if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1287 rrd_set_error("malloc for legspace");
1291 for(i=0;i<im->gdes_c;i++){
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];
1300 im->gdes[i].legend[leg_cc] = '\0';
1304 /* remove exess space */
1305 while (prt_fctn=='g' &&
1307 im->gdes[i].legend[leg_cc-1]==' '){
1309 im->gdes[i].legend[leg_cc]='\0';
1312 legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1315 /* no interleg space if string ends in \g */
1316 fill += legspace[i];
1318 if (im->gdes[i].gf != GF_GPRINT &&
1319 im->gdes[i].gf != GF_COMMENT) {
1322 fill += leg_cc * SmallFont->w;
1327 /* who said there was a special tag ... ?*/
1328 if (prt_fctn=='g') {
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){
1350 if (prt_fctn != '\0'){
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; */
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')
1364 im->gdes[ii].legloc.x = leg_x;
1365 im->gdes[ii].legloc.y = leg_y;
1367 + strlen(im->gdes[ii].legend)*SmallFont->w
1370 if (im->gdes[ii].gf != GF_GPRINT &&
1371 im->gdes[ii].gf != GF_COMMENT)
1374 leg_y = leg_y + SmallFont->h*1.2;
1375 if (prt_fctn == 's') leg_y -= SmallFont->h *0.5;
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 */
1396 horizontal_grid(gdImagePtr gif, image_desc_t *im)
1404 char graph_label[100];
1405 gdPoint polyPoints[4];
1406 int labfact,gridind;
1407 int styleMinor[2],styleMajor[2];
1408 int decimals, fractionals;
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)) {
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 */
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 */
1437 fractionals = floor(log10(range));
1438 if(fractionals < 0) /* small amplitude. */
1439 sprintf(labfmt, "%%%d.%df", decimals - fractionals + 1, -fractionals + 1);
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 */
1445 /* should have at least 5 lines but no more then 15 */
1446 if(range/gridstep < 5)
1448 if(range/gridstep > 15)
1450 if(range/gridstep > 5) {
1452 if(range/gridstep > 8)
1461 for(i=0;ylab[i].grid > 0;i++){
1462 pixel = im->ysize / (scaledrange / ylab[i].grid);
1463 if (gridind == -1 && pixel > 5) {
1470 if (pixel * ylab[gridind].lfac[i] >= 2 * SmallFont->h) {
1471 labfact = ylab[gridind].lfac[i];
1476 gridstep = ylab[gridind].grid * im->magfact;
1479 gridstep = im->ygridstep;
1480 labfact = im->ylabfact;
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 == ' ') {
1495 if(im->extra_flags & ALTYGRID) {
1496 sprintf(graph_label,labfmt,scaledstep*i);
1499 sprintf(graph_label,"%4.1f",scaledstep*i);
1502 sprintf(graph_label,"%4.0f",scaledstep*i);
1506 sprintf(graph_label,"%4.1f %c",scaledstep*i, im->symbol);
1508 sprintf(graph_label,"%4.0f %c",scaledstep*i, im->symbol);
1512 gdImageString(gif, SmallFont,
1513 (polyPoints[0].x - (strlen(graph_label) *
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);
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);
1531 gdImageLine(gif, polyPoints[0].x,polyPoints[0].y,
1532 polyPoints[1].x,polyPoints[0].y,gdStyled);
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);
1544 /* logaritmic horizontal grid */
1546 horizontal_log_grid(gdImagePtr gif, image_desc_t *im)
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)) {
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]);
1571 pixperstep = pixpex * minstep;
1572 if(pixperstep > 5){minoridx = i;}
1573 if(pixperstep > 2 * SmallFont->h){majoridx = i;}
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;
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);
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;
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) *
1627 polyPoints[0].y - SmallFont->h/2+1,
1628 (unsigned char *)graph_label, graph_col[GRC_FONT].i);
1640 int xlab_sel; /* which sort of label and grid ? */
1643 char graph_label[100];
1644 gdPoint polyPoints[4]; /* points for filled graph and more*/
1646 /* style for grid lines */
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;
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;
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);
1678 ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
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);
1696 /* paint the major grid */
1697 for(ti = find_first_time(im->start,
1698 im->xlab_user.mgridtm,
1699 im->xlab_user.mgridst);
1701 ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
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);
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);
1722 ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
1725 tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
1728 strftime(graph_label,99,im->xlab_user.stst,localtime(&tilab));
1730 # error "your libc has no strftime I guess we'll abort the exercise here."
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);
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); */
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);
1813 res = horizontal_grid(gif,im);
1816 /* dont draw horizontal grid if there is no min and max val */
1818 char *nodata = "No Data found";
1819 gdImageString(gif, LargeFont,
1821 - (strlen(nodata)*LargeFont->w)/2,
1822 (2*im->yorigin-im->ysize) / 2,
1823 (unsigned char *)nodata, graph_col[GRC_FONT].i);
1827 /* yaxis description */
1828 gdImageStringUp(gif, SmallFont,
1830 (im->yorigin - im->ysize/2
1831 +(strlen(im->ylegend)*SmallFont->w)/2 ),
1832 (unsigned char *)im->ylegend, graph_col[GRC_FONT].i);
1836 gdImageString(gif, LargeFont,
1838 - (strlen(im->title)*LargeFont->w)/2,
1840 (unsigned char *)im->title, graph_col[GRC_FONT].i);
1843 if( !(im->extra_flags & NOLEGEND) ) {
1844 for(i=0;i<im->gdes_c;i++){
1845 if(im->gdes[i].legend[0] =='\0')
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,
1864 (unsigned char *)im->gdes[i].legend,
1865 graph_col[GRC_FONT].i);
1867 polyPoints[0].x = im->gdes[i].legloc.x;
1868 polyPoints[0].y = im->gdes[i].legloc.y;
1870 gdImageString(gif, SmallFont,
1873 (unsigned char *)im->gdes[i].legend,
1874 graph_col[GRC_FONT].i);
1880 gator(gif, (int) im->xgif-5, 5);
1886 MkLineBrush(image_desc_t *im,long cosel, enum gf_en typsel){
1891 brush=gdImageCreate(1,1);
1894 brush=gdImageCreate(2,2);
1897 brush=gdImageCreate(3,3);
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);
1913 gdImageSetPixel(brush,0,0,pen);
1916 gdImageSetPixel(brush,0,0,pen);
1917 gdImageSetPixel(brush,0,1,pen);
1918 gdImageSetPixel(brush,1,0,pen);
1919 gdImageSetPixel(brush,1,1,pen);
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);
1933 /*****************************************************
1934 * lazy check make sure we rely need to create this graph
1935 *****************************************************/
1937 int lazy_check(image_desc_t *im){
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
1947 if (time(NULL) - gifstat.st_mtime >
1948 (im->end - im->start) / im->xsize)
1950 if ((fd = fopen(im->graphfile,"rb")) == NULL)
1951 return 0; /* the file does not exist */
1952 switch (im->imgformat) {
1954 size = GifSize(fd,&(im->xgif),&(im->ygif));
1957 size = PngSize(fd,&(im->xgif),&(im->ygif));
1964 /* draw that picture thing ... */
1966 graph_paint(image_desc_t *im, char ***calcpr)
1969 int lazy = lazy_check(im);
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)
1988 /* evaluate VDEF and CDEF operations ... */
1989 if(data_calc(im)==-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 ...
1997 i=print_calc(im,calcpr);
1999 if(i==0 || lazy) return 0;
2001 /* get actual drawing data and find min and max values*/
2002 if(data_proc(im)==-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
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;
2020 im->yorigin = 14 + im->ysize;
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)
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)
2043 gdImageColorAllocate( gif,
2044 im->graph_col[i].red,
2045 im->graph_col[i].green,
2046 im->graph_col[i].blue);
2049 gdImageColorAllocate( gif,
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)
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 */
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;
2098 for(i=0;i<im->gdes_c;i++){
2100 switch(im->gdes[i].gf){
2111 for (ii = 0; ii < im->xsize; ii++)
2113 if (!isnan(im->gdes[i].p_data[ii]) &&
2114 im->gdes[i].p_data[ii] > 0.0)
2116 /* generate a tick */
2117 gdImageLine(gif, im -> xorigin + ii,
2118 im -> yorigin - (im -> gdes[i].yrule * im -> ysize),
2121 im -> gdes[i].col.i);
2129 stack_gf = im->gdes[i].gf;
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 ;
2137 im->gdes[i].p_data[ii] = im->minval ;
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]))
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]),
2158 gdImageDestroy(brush);
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];
2169 if (lastgdes->p_data[ii] == im->gdes[i].p_data[ii]){
2173 ii+im->xorigin,ytr(im,lastgdes->p_data[ii]),
2174 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
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;
2185 ii+im->xorigin,ytr(im,areazero),
2186 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2190 lastgdes = &(im->gdes[i]);
2197 /* the RULES are the last thing to paint ... */
2198 for(i=0;i<im->gdes_c;i++){
2200 switch(im->gdes[i].gf){
2202 if(isnan(im->gdes[i].yrule)) { /* fetch variable */
2203 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2205 if(im->gdes[i].yrule >= im->minval
2206 && im->gdes[i].yrule <= im->maxval)
2208 im->xorigin,ytr(im,im->gdes[i].yrule),
2209 im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2213 if(im->gdes[i].xrule == 0) { /* fetch variable */
2214 im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
2216 if(im->gdes[i].xrule >= im->start
2217 && im->gdes[i].xrule <= im->end)
2219 xtr(im,im->gdes[i].xrule),im->yorigin,
2220 xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2228 if (strcmp(im->graphfile,"-")==0) {
2230 /* Change translation mode for stdout to BINARY */
2231 _setmode( _fileno( stdout ), O_BINARY );
2235 if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2236 rrd_set_error("Opening '%s' for write: %s",im->graphfile, strerror(errno));
2240 switch (im->imgformat) {
2242 gdImageGif(gif, fo);
2245 gdImagePng(gif, fo);
2248 if (strcmp(im->graphfile,"-") != 0)
2250 gdImageDestroy(gif);
2256 /*****************************************************
2258 *****************************************************/
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;
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");
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;
2295 /* copies input untill the first unescaped colon is found
2296 or until input ends. backslashes have to be escaped as well */
2298 scan_for_col(char *input, int len, char *output)
2303 input[inp] != ':' &&
2306 if (input[inp] == '\\' &&
2307 input[inp+1] != '\0' &&
2308 (input[inp+1] == '\\' ||
2309 input[inp+1] == ':')){
2310 output[outp++] = input[++inp];
2313 output[outp++] = input[inp];
2316 output[outp] = '\0';
2320 /* Some surgery done on this function, it became ridiculously big.
2322 ** - initializing now in rrd_graph_init()
2323 ** - options parsing now in rrd_graph_options()
2324 ** - script parsing now in rrd_graph_script()
2327 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize)
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");
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 */
2349 if (graph_paint(&im,prdata)==-1){
2354 /* The image is generated and needs to be output.
2355 ** Also, if needed, print a line with information about the image.
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");
2369 if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
2371 rrd_set_error("malloc imginfo");
2374 filename=im.graphfile+strlen(im.graphfile);
2375 while(filename > im.graphfile) {
2376 if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
2380 sprintf((*prdata)[0],im.imginfo,filename,im.xgif,im.ygif);
2387 rrd_graph_init(image_desc_t *im)
2391 im->xlab_user.minsec = -1;
2397 im->ylegend[0] = '\0';
2398 im->title[0] = '\0';
2402 im->unitsexponent= 9999;
2407 im->logarithmic = 0;
2408 im->ygridstep = DNAN;
2409 im->draw_x_grid = 1;
2410 im->draw_y_grid = 1;
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;
2422 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
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;
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);
2436 static struct option long_options[] =
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},
2463 int option_index = 0;
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);
2476 im->extra_flags |= ALTYGRID;
2479 im->extra_flags |= ALTAUTOSCALE;
2482 im->extra_flags |= ALTAUTOSCALE_MAX;
2485 im->extra_flags |= NOLEGEND;
2488 im->unitsexponent = atoi(optarg);
2491 im->step = atoi(optarg);
2494 if ((parsetime_error = parsetime(optarg, &start_tv))) {
2495 rrd_set_error( "start time: %s", parsetime_error );
2500 if ((parsetime_error = parsetime(optarg, &end_tv))) {
2501 rrd_set_error( "end time: %s", parsetime_error );
2506 if(strcmp(optarg,"none") == 0){
2512 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
2514 &im->xlab_user.gridst,
2516 &im->xlab_user.mgridst,
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);
2525 } else if ((im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
2526 rrd_set_error("unknown keyword %s",scan_mtm);
2528 } else if ((im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
2529 rrd_set_error("unknown keyword %s",scan_ltm);
2532 im->xlab_user.minsec = 1;
2533 im->xlab_user.stst = im->xlab_form;
2535 rrd_set_error("invalid x-grid format");
2541 if(strcmp(optarg,"none") == 0){
2549 &im->ylabfact) == 2) {
2550 if(im->ygridstep<=0){
2551 rrd_set_error("grid step must be > 0");
2553 } else if (im->ylabfact < 1){
2554 rrd_set_error("label factor must be > 0");
2558 rrd_set_error("invalid y-grid format");
2563 strncpy(im->ylegend,optarg,150);
2564 im->ylegend[150]='\0';
2567 im->maxval = atof(optarg);
2570 im->minval = atof(optarg);
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");
2580 long_tmp = atol(optarg);
2581 if (long_tmp < 10) {
2582 rrd_set_error("width below 10 pixels");
2585 im->xsize = long_tmp;
2588 long_tmp = atol(optarg);
2589 if (long_tmp < 10) {
2590 rrd_set_error("height below 10 pixels");
2593 im->ysize = long_tmp;
2602 im->imginfo = optarg;
2605 if((im->imgformat = if_conv(optarg)) == -1) {
2606 rrd_set_error("unsupported graphics format '%s'",optarg);
2614 im->logarithmic = 1;
2615 if (isnan(im->minval))
2620 "%10[A-Z]#%2x%2x%2x",
2621 col_nam,&col_red,&col_green,&col_blue) == 4){
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;
2628 rrd_set_error("invalid color name '%s'",col_nam);
2631 rrd_set_error("invalid color def format");
2636 strncpy(im->title,optarg,150);
2637 im->title[150]='\0';
2642 rrd_set_error("unknown option '%c'", optopt);
2644 rrd_set_error("unknown option '%s'",argv[optind-1]);
2649 if (optind >= argc) {
2650 rrd_set_error("missing filename");
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");
2659 if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
2660 /* error string is set in parsetime.c */
2664 if (start_tmp < 3600*24*365*10){
2665 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
2669 if (end_tmp < start_tmp) {
2670 rrd_set_error("start (%ld) should be less than end (%ld)",
2671 start_tmp, end_tmp);
2675 im->start = start_tmp;
2680 rrd_graph_check_vname(image_desc_t *im, char *varname, char *err)
2682 if ((im->gdes[im->gdes_c-1].vidx=find_var(im,varname))==-1) {
2684 rrd_set_error("Unknown variable '%s' in %s",varname,err);
2690 rrd_graph_check_CF(image_desc_t *im, char *symname, char *err)
2692 if ((im->gdes[im->gdes_c-1].cf=cf_conv(symname))==-1) {
2694 rrd_set_error("Unknown CF '%s' in %s",symname,err);
2701 rrd_graph_script(int argc, char *myarg[], image_desc_t *im)
2705 unsigned int col_red,col_green,col_blue;
2707 int linepass = 0; /* stack can only follow directly after LINE* AREA or STACK */
2709 /* All code worked on "argv[i]", it made sense to formalize this
2710 ** and use "arg" instead.
2712 ** The same can be said for "im->gdes[im->gdes_c-1]" which
2713 ** has been changed into a simple "gdp".
2716 for(i=optind+1;i<argc;i++){
2720 char varname[MAX_VNAME_LEN+1],*rpnex;
2724 gdp=&im->gdes[im->gdes_c-1];
2726 if(sscanf(arg,"%10[A-Z0-9]:%n",symname,&argstart)==1){
2727 if((gdp->gf=gf_conv(symname))==-1){
2729 rrd_set_error("unknown function '%s'",symname);
2733 rrd_set_error("can't parse '%s'",arg);
2743 sscanf(&arg[argstart], DEF_NAM_FMT ":%n"
2750 rrd_set_error("can't parse vname in '%s'",&arg[argstart]);
2754 if (rrd_graph_check_vname(im,varname,arg)) return;
2758 sscanf(&arg[argstart+strstart],CF_NAM_FMT ":%n"
2762 if (im->gdes[gdp->vidx].gf==GF_VDEF) {
2763 /* No consolidation function should be present */
2765 rrd_set_error("(G)PRINT of VDEF needs no CF");
2770 /* A consolidation function should follow */
2773 rrd_set_error("Missing or incorrect CF in (G)PRINTing '%s' (%s)",varname,&arg[argstart]);
2776 if (rrd_graph_check_CF(im,symname,arg)) return;
2781 scan_for_col(&arg[argstart+strstart],FMT_LEG_LEN,gdp->format);
2784 if(strlen(&arg[argstart])>FMT_LEG_LEN) arg[argstart+FMT_LEG_LEN-3]='\0' ;
2785 strcpy(gdp->legend, &arg[argstart]);
2788 /* scan for either "HRULE:vname#..." or "HRULE:num#..."
2790 * If a vname is used, the value NaN is set; this is catched
2791 * when graphing. Setting value NaN from the script is not
2795 sscanf(&arg[argstart], "%lf#%n"
2796 ,&im->gdes[im->gdes_c-1].yrule
2799 if (strstart==0) { /* no number, should be vname */
2800 sscanf(&arg[argstart], DEF_NAM_FMT "#%n"
2805 gdp->yrule = DNAN;/* signal use of vname */
2806 if (rrd_graph_check_vname(im,varname,arg)) return;
2807 if(im->gdes[gdp->vidx].gf != GF_VDEF) {
2809 rrd_set_error("Only VDEF is allowed in HRULE",varname);
2816 rrd_set_error("can't parse '%s'",&arg[argstart]);
2821 &arg[argstart+strstart],
2827 gdp->col.red = col_red;
2828 gdp->col.green = col_green;
2829 gdp->col.blue = col_blue;
2831 gdp->legend[0] = '\0';
2833 scan_for_col(&arg[argstart+strstart+n],FMT_LEG_LEN,gdp->legend);
2837 rrd_set_error("can't parse '%s'",&arg[argstart]);
2844 /* scan for either "VRULE:vname#..." or "VRULE:num#..."
2846 * If a vname is used, the value 0 is set; this is catched
2847 * when graphing. Setting value 0 from the script is not
2851 sscanf(&arg[argstart], "%lu#%n"
2852 ,(long unsigned int *)&gdp->xrule,&strstart);
2853 if (strstart==0) { /* no number, should be vname */
2854 sscanf(&arg[argstart], DEF_NAM_FMT "#%n"
2858 if (strstart!=0) { /* vname matched */
2859 gdp->xrule = 0;/* signal use of vname */
2860 if (rrd_graph_check_vname(im,varname,arg)) return;
2861 if(im->gdes[gdp->vidx].gf != GF_VDEF) {
2863 rrd_set_error("Only VDEF is allowed in VRULE",varname);
2874 rrd_set_error("can't parse '%s'",&arg[argstart]);
2879 &arg[argstart+strstart],
2885 gdp->col.red = col_red;
2886 gdp->col.green = col_green;
2887 gdp->col.blue = col_blue;
2889 gdp->legend[0] = '\0';
2891 scan_for_col(&arg[argstart+strstart+n],FMT_LEG_LEN,gdp->legend);
2895 rrd_set_error("can't parse '%s'",&arg[argstart]);
2901 if((scancount=sscanf(
2903 "%29[^:#]#%2x%2x%2x:%lf:%n",
2911 gdp->col.red = col_red;
2912 gdp->col.green = col_green;
2913 gdp->col.blue = col_blue;
2915 gdp->legend[0] = '\0';
2917 scan_for_col(&arg[argstart+strstart],FMT_LEG_LEN,gdp->legend);
2919 if (rrd_graph_check_vname(im,varname,arg)) return;
2920 if (gdp->yrule <= 0.0 || gdp->yrule > 1.0)
2923 rrd_set_error("Tick mark scaling factor out of range");
2929 /* default tick marks: 10% of the y-axis */
2934 rrd_set_error("can't parse '%s'",&arg[argstart]);
2936 } /* endif sscanf */
2941 rrd_set_error("STACK must follow AREA, LINE or STACK");
2949 if((scancount=sscanf(
2951 "%29[^:#]#%2x%2x%2x:%n",
2957 gdp->col.red = col_red;
2958 gdp->col.green = col_green;
2959 gdp->col.blue = col_blue;
2961 gdp->legend[0] = '\0';
2963 scan_for_col(&arg[argstart+strstart],FMT_LEG_LEN,gdp->legend);
2965 if (rrd_graph_check_vname(im,varname,arg)) return;
2970 rrd_set_error("can't parse '%s'",&arg[argstart]);
2975 if((rpnex = malloc(strlen(&arg[argstart])*sizeof(char)))==NULL){
2977 rrd_set_error("malloc for CDEF");
2980 strstart=parse_vname1(&arg[argstart],im,"CDEF");
2982 /* parse_vname1() did free(im) and rrd_set_error() */
2983 if (strstart==0) return;
2986 sscanf(&arg[argstart],"%[^: ]%n",rpnex,&strstart);
2988 rrd_set_error("can't parse RPN in CDEF:%s=%s",
2995 rpn_parse((void*)im,rpnex,&find_var_wrapper))== NULL){
2996 rrd_set_error("invalid rpn expression '%s'", rpnex);
3003 strstart=parse_vname1(&arg[argstart],im,"VDEF");
3005 /* parse_vname1() did free(im) and rrd_set_error() */
3006 if (strstart==0) return;
3009 sscanf(&arg[argstart],DEF_NAM_FMT ",%n",varname,&strstart);
3012 rrd_set_error("Cannot parse '%s' in VDEF '%s'",
3017 if (rrd_graph_check_vname(im,varname,arg)) return;
3018 if ( im->gdes[gdp->vidx].gf != GF_DEF
3019 && im->gdes[gdp->vidx].gf != GF_CDEF) {
3020 rrd_set_error("variable '%s' not DEF nor CDEF in VDEF '%s'",
3021 varname,gdp->vname);
3026 /* parsed upto and including the first comma. Now
3027 * see what function is requested. This function
3028 * sets the error string.
3030 if (vdef_parse(gdp,&arg[argstart+strstart])<0) {
3036 strstart=parse_vname1(&arg[argstart],im,"DEF");
3038 /* parse_vname1() did free(im) and rrd_set_error() */
3039 if (strstart==0) return;
3041 argstart+=scan_for_col(&arg[argstart],MAXPATH,gdp->rrd);
3042 if(sscanf(&arg[argstart], ":" DS_NAM_FMT ":" CF_NAM_FMT,
3043 gdp->ds_nam, symname) != 2){
3045 rrd_set_error("can't parse DEF '%s' -2",&arg[argstart]);
3049 if (rrd_graph_check_CF(im,symname,arg)) return;
3055 rrd_set_error("can't make a graph without contents");
3062 int bad_format(char *fmt) {
3066 while (*ptr != '\0') {
3067 if (*ptr == '%') {ptr++;
3068 if (*ptr == '\0') return 1;
3069 while ((*ptr >= '0' && *ptr <= '9') || *ptr == '.') {
3072 if (*ptr == '\0') return 1;
3075 if (*ptr == '\0') return 1;
3076 if (*ptr == 'e' || *ptr == 'f') {
3078 } else { return 1; }
3080 else if (*ptr == 's' || *ptr == 'S' || *ptr == '%') { ++ptr; }
3089 vdef_parse(gdes,str)
3090 struct graph_desc_t *gdes;
3093 /* A VDEF currently is either "func" or "param,func"
3094 * so the parsing is rather simple. Change if needed.
3101 sscanf(str,"%le,%29[A-Z]%n",¶m,func,&n);
3102 if (n==strlen(str)) { /* matched */
3106 sscanf(str,"%29[A-Z]%n",func,&n);
3107 if (n==strlen(str)) { /* matched */
3110 rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3117 if (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3118 else if (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3119 else if (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3120 else if (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3121 else if (!strcmp("TOTAL", func)) gdes->vf.op = VDEF_TOTAL;
3122 else if (!strcmp("FIRST", func)) gdes->vf.op = VDEF_FIRST;
3123 else if (!strcmp("LAST", func)) gdes->vf.op = VDEF_LAST;
3125 rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3132 switch (gdes->vf.op) {
3134 if (isnan(param)) { /* no parameter given */
3135 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3141 if (param>=0.0 && param<=100.0) {
3142 gdes->vf.param = param;
3143 gdes->vf.val = DNAN; /* undefined */
3144 gdes->vf.when = 0; /* undefined */
3146 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3160 gdes->vf.param = DNAN;
3161 gdes->vf.val = DNAN;
3164 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3179 graph_desc_t *src,*dst;
3183 dst = &im->gdes[gdi];
3184 src = &im->gdes[dst->vidx];
3185 data = src->data + src->ds;
3186 steps = (src->end - src->start) / src->step;
3189 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3196 switch (dst->vf.op) {
3197 case VDEF_PERCENT: {
3198 rrd_value_t * array;
3202 if ((array = malloc(steps*sizeof(double)))==NULL) {
3203 rrd_set_error("malloc VDEV_PERCENT");
3206 for (step=0;step < steps; step++) {
3207 array[step]=data[step*src->ds_cnt];
3209 qsort(array,step,sizeof(double),vdef_percent_compar);
3211 field = (steps-1)*dst->vf.param/100;
3212 dst->vf.val = array[field];
3213 dst->vf.when = 0; /* no time component */
3215 for(step=0;step<steps;step++)
3216 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3222 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3223 if (step == steps) {
3227 dst->vf.val = data[step*src->ds_cnt];
3228 dst->vf.when = src->start + (step+1)*src->step;
3230 while (step != steps) {
3231 if (finite(data[step*src->ds_cnt])) {
3232 if (data[step*src->ds_cnt] > dst->vf.val) {
3233 dst->vf.val = data[step*src->ds_cnt];
3234 dst->vf.when = src->start + (step+1)*src->step;
3241 case VDEF_AVERAGE: {
3244 for (step=0;step<steps;step++) {
3245 if (finite(data[step*src->ds_cnt])) {
3246 sum += data[step*src->ds_cnt];
3251 if (dst->vf.op == VDEF_TOTAL) {
3252 dst->vf.val = sum*src->step;
3253 dst->vf.when = cnt*src->step; /* not really "when" */
3255 dst->vf.val = sum/cnt;
3256 dst->vf.when = 0; /* no time component */
3266 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3267 if (step == steps) {
3271 dst->vf.val = data[step*src->ds_cnt];
3272 dst->vf.when = src->start + (step+1)*src->step;
3274 while (step != steps) {
3275 if (finite(data[step*src->ds_cnt])) {
3276 if (data[step*src->ds_cnt] < dst->vf.val) {
3277 dst->vf.val = data[step*src->ds_cnt];
3278 dst->vf.when = src->start + (step+1)*src->step;
3285 /* The time value returned here is one step before the
3286 * actual time value. This is the start of the first
3290 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3291 if (step == steps) { /* all entries were NaN */
3295 dst->vf.val = data[step*src->ds_cnt];
3296 dst->vf.when = src->start + step*src->step;
3300 /* The time value returned here is the
3301 * actual time value. This is the end of the last
3305 while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3306 if (step < 0) { /* all entries were NaN */
3310 dst->vf.val = data[step*src->ds_cnt];
3311 dst->vf.when = src->start + (step+1)*src->step;
3318 /* NaN < -INF < finite_values < INF */
3320 vdef_percent_compar(a,b)
3323 /* Equality is not returned; this doesn't hurt except
3324 * (maybe) for a little performance.
3327 /* First catch NaN values. They are smallest */
3328 if (isnan( *(double *)a )) return -1;
3329 if (isnan( *(double *)b )) return 1;
3331 /* NaN doesn't reach this part so INF and -INF are extremes.
3332 * The sign from isinf() is compatible with the sign we return
3334 if (isinf( *(double *)a )) return isinf( *(double *)a );
3335 if (isinf( *(double *)b )) return isinf( *(double *)b );
3337 /* If we reach this, both values must be finite */
3338 if ( *(double *)a < *(double *)b ) return -1; else return 1;