1 /****************************************************************************
2 * RRDtool 1.1.x Copyright Tobias Oetiker, 1997 - 2002
3 ****************************************************************************
4 * rrd__graph.c make creates ne rrds
5 ****************************************************************************/
23 #include "rrd_graph.h"
24 #include "rrd_graph_helper.h"
26 /* some constant definitions */
29 #ifndef RRD_DEFAULT_FONT
31 #define RRD_DEFAULT_FONT "c:/winnt/fonts/COUR.TTF"
33 #define RRD_DEFAULT_FONT "/usr/share/fonts/truetype/openoffice/ariosor.ttf"
34 /* #define RRD_DEFAULT_FONT "/usr/share/fonts/truetype/Arial.ttf" */
39 text_prop_t text_prop[] = {
40 { 10.0, RRD_DEFAULT_FONT }, /* default */
41 { 12.0, RRD_DEFAULT_FONT }, /* title */
42 { 8.0, RRD_DEFAULT_FONT }, /* axis */
43 { 10.0, RRD_DEFAULT_FONT }, /* unit */
44 { 10.0, RRD_DEFAULT_FONT } /* legend */
48 {0, TMT_SECOND,30, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
49 {2, TMT_MINUTE,1, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
50 {5, TMT_MINUTE,2, TMT_MINUTE,10, TMT_MINUTE,10, 0,"%H:%M"},
51 {10, TMT_MINUTE,5, TMT_MINUTE,20, TMT_MINUTE,20, 0,"%H:%M"},
52 {30, TMT_MINUTE,10, TMT_HOUR,1, TMT_HOUR,1, 0,"%H:%M"},
53 {60, TMT_MINUTE,30, TMT_HOUR,2, TMT_HOUR,2, 0,"%H:%M"},
54 {180, TMT_HOUR,1, TMT_HOUR,6, TMT_HOUR,6, 0,"%H:%M"},
55 /*{300, TMT_HOUR,3, TMT_HOUR,12, TMT_HOUR,12, 12*3600,"%a %p"}, this looks silly*/
56 {600, TMT_HOUR,6, TMT_DAY,1, TMT_DAY,1, 24*3600,"%a"},
57 {1800, TMT_HOUR,12, TMT_DAY,1, TMT_DAY,2, 24*3600,"%a"},
58 {3600, TMT_DAY,1, TMT_WEEK,1, TMT_WEEK,1, 7*24*3600,"Week %V"},
59 {3*3600, TMT_WEEK,1, TMT_MONTH,1, TMT_WEEK,2, 7*24*3600,"Week %V"},
60 {6*3600, TMT_MONTH,1, TMT_MONTH,1, TMT_MONTH,1, 30*24*3600,"%b"},
61 {48*3600, TMT_MONTH,1, TMT_MONTH,3, TMT_MONTH,3, 30*24*3600,"%b"},
62 {10*24*3600, TMT_YEAR,1, TMT_YEAR,1, TMT_YEAR,1, 365*24*3600,"%y"},
63 {-1,TMT_MONTH,0,TMT_MONTH,0,TMT_MONTH,0,0,""}
66 /* sensible logarithmic y label intervals ...
67 the first element of each row defines the possible starting points on the
68 y axis ... the other specify the */
70 double yloglab[][12]= {{ 1e9, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
71 { 1e3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
72 { 1e1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
73 /* { 1e1, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, */
74 { 1e1, 1, 2.5, 5, 7.5, 0, 0, 0, 0, 0, 0, 0 },
75 { 1e1, 1, 2, 4, 6, 8, 0, 0, 0, 0, 0, 0 },
76 { 1e1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0 },
77 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }};
79 /* sensible y label intervals ...*/
97 gfx_color_t graph_col[] = /* default colors */
98 { 0xFFFFFFFF, /* canvas */
99 0xF0F0F0FF, /* background */
100 0xD0D0D0FF, /* shade A */
101 0xA0A0A0FF, /* shade B */
102 0x909090FF, /* grid */
103 0xE05050FF, /* major grid */
104 0x000000FF, /* font */
105 0x000000FF, /* frame */
106 0xFF0000FF /* arrow */
113 # define DPRINT(x) (void)(printf x, printf("\n"))
119 /* initialize with xtr(im,0); */
121 xtr(image_desc_t *im,time_t mytime){
124 pixie = (double) im->xsize / (double)(im->end - im->start);
127 return (int)((double)im->xorigin
128 + pixie * ( mytime - im->start ) );
131 /* translate data values into y coordinates */
133 ytr(image_desc_t *im, double value){
138 pixie = (double) im->ysize / (im->maxval - im->minval);
140 pixie = (double) im->ysize / (log10(im->maxval) - log10(im->minval));
142 } else if(!im->logarithmic) {
143 yval = im->yorigin - pixie * (value - im->minval) + 0.5;
145 if (value < im->minval) {
148 yval = im->yorigin - pixie * (log10(value) - log10(im->minval)) + 0.5;
151 /* make sure we don't return anything too unreasonable. GD lib can
152 get terribly slow when drawing lines outside its scope. This is
153 especially problematic in connection with the rigid option */
156 } else if ((int)yval > im->yorigin) {
157 return im->yorigin+2;
158 } else if ((int) yval < im->yorigin - im->ysize){
159 return im->yorigin - im->ysize - 2;
167 /* conversion function for symbolic entry names */
170 #define conv_if(VV,VVV) \
171 if (strcmp(#VV, string) == 0) return VVV ;
173 enum gf_en gf_conv(char *string){
175 conv_if(PRINT,GF_PRINT)
176 conv_if(GPRINT,GF_GPRINT)
177 conv_if(COMMENT,GF_COMMENT)
178 conv_if(HRULE,GF_HRULE)
179 conv_if(VRULE,GF_VRULE)
180 conv_if(LINE,GF_LINE)
181 conv_if(AREA,GF_AREA)
182 conv_if(STACK,GF_STACK)
183 conv_if(TICK,GF_TICK)
185 conv_if(CDEF,GF_CDEF)
186 conv_if(VDEF,GF_VDEF)
187 conv_if(PART,GF_PART)
192 enum gfx_if_en if_conv(char *string){
201 enum tmt_en tmt_conv(char *string){
203 conv_if(SECOND,TMT_SECOND)
204 conv_if(MINUTE,TMT_MINUTE)
205 conv_if(HOUR,TMT_HOUR)
207 conv_if(WEEK,TMT_WEEK)
208 conv_if(MONTH,TMT_MONTH)
209 conv_if(YEAR,TMT_YEAR)
213 enum grc_en grc_conv(char *string){
215 conv_if(BACK,GRC_BACK)
216 conv_if(CANVAS,GRC_CANVAS)
217 conv_if(SHADEA,GRC_SHADEA)
218 conv_if(SHADEB,GRC_SHADEB)
219 conv_if(GRID,GRC_GRID)
220 conv_if(MGRID,GRC_MGRID)
221 conv_if(FONT,GRC_FONT)
222 conv_if(FRAME,GRC_FRAME)
223 conv_if(ARROW,GRC_ARROW)
228 enum text_prop_en text_prop_conv(char *string){
230 conv_if(DEFAULT,TEXT_PROP_DEFAULT)
231 conv_if(TITLE,TEXT_PROP_TITLE)
232 conv_if(AXIS,TEXT_PROP_AXIS)
233 conv_if(UNIT,TEXT_PROP_UNIT)
234 conv_if(LEGEND,TEXT_PROP_LEGEND)
244 im_free(image_desc_t *im)
247 if (im == NULL) return 0;
248 for(i=0;i<im->gdes_c;i++){
249 if (im->gdes[i].data_first){
250 /* careful here, because a single pointer can occur several times */
251 free (im->gdes[i].data);
252 if (im->gdes[i].ds_namv){
253 for (ii=0;ii<im->gdes[i].ds_cnt;ii++)
254 free(im->gdes[i].ds_namv[ii]);
255 free(im->gdes[i].ds_namv);
258 free (im->gdes[i].p_data);
259 free (im->gdes[i].rpnp);
262 gfx_destroy(im->canvas);
266 /* find SI magnitude symbol for the given number*/
269 image_desc_t *im, /* image description */
276 char *symbol[] = {"a", /* 10e-18 Atto */
277 "f", /* 10e-15 Femto */
278 "p", /* 10e-12 Pico */
279 "n", /* 10e-9 Nano */
280 "u", /* 10e-6 Micro */
281 "m", /* 10e-3 Milli */
286 "T", /* 10e12 Tera */
287 "P", /* 10e15 Peta */
293 if (*value == 0.0 || isnan(*value) ) {
297 sindex = floor(log(fabs(*value))/log((double)im->base));
298 *magfact = pow((double)im->base, (double)sindex);
299 (*value) /= (*magfact);
301 if ( sindex <= symbcenter && sindex >= -symbcenter) {
302 (*symb_ptr) = symbol[sindex+symbcenter];
310 /* find SI magnitude symbol for the numbers on the y-axis*/
313 image_desc_t *im /* image description */
317 char symbol[] = {'a', /* 10e-18 Atto */
318 'f', /* 10e-15 Femto */
319 'p', /* 10e-12 Pico */
320 'n', /* 10e-9 Nano */
321 'u', /* 10e-6 Micro */
322 'm', /* 10e-3 Milli */
327 'T', /* 10e12 Tera */
328 'P', /* 10e15 Peta */
334 if (im->unitsexponent != 9999) {
335 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
336 digits = floor(im->unitsexponent / 3);
338 digits = floor( log( max( fabs(im->minval),fabs(im->maxval)))/log((double)im->base));
340 im->magfact = pow((double)im->base , digits);
343 printf("digits %6.3f im->magfact %6.3f\n",digits,im->magfact);
346 if ( ((digits+symbcenter) < sizeof(symbol)) &&
347 ((digits+symbcenter) >= 0) )
348 im->symbol = symbol[(int)digits+symbcenter];
353 /* move min and max values around to become sensible */
356 expand_range(image_desc_t *im)
358 double sensiblevalues[] ={1000.0,900.0,800.0,750.0,700.0,
359 600.0,500.0,400.0,300.0,250.0,
360 200.0,125.0,100.0,90.0,80.0,
361 75.0,70.0,60.0,50.0,40.0,30.0,
362 25.0,20.0,10.0,9.0,8.0,
363 7.0,6.0,5.0,4.0,3.5,3.0,
364 2.5,2.0,1.8,1.5,1.2,1.0,
365 0.8,0.7,0.6,0.5,0.4,0.3,0.2,0.1,0.0,-1};
367 double scaled_min,scaled_max;
374 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
375 im->minval,im->maxval,im->magfact);
378 if (isnan(im->ygridstep)){
379 if(im->extra_flags & ALTAUTOSCALE) {
380 /* measure the amplitude of the function. Make sure that
381 graph boundaries are slightly higher then max/min vals
382 so we can see amplitude on the graph */
385 delt = im->maxval - im->minval;
387 fact = 2.0 * pow(10.0,
388 floor(log10(max(fabs(im->minval), fabs(im->maxval)))) - 2);
390 adj = (fact - delt) * 0.55;
392 printf("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n", im->minval, im->maxval, delt, fact, adj);
398 else if(im->extra_flags & ALTAUTOSCALE_MAX) {
399 /* measure the amplitude of the function. Make sure that
400 graph boundaries are slightly higher than max vals
401 so we can see amplitude on the graph */
402 adj = (im->maxval - im->minval) * 0.1;
406 scaled_min = im->minval / im->magfact;
407 scaled_max = im->maxval / im->magfact;
409 for (i=1; sensiblevalues[i] > 0; i++){
410 if (sensiblevalues[i-1]>=scaled_min &&
411 sensiblevalues[i]<=scaled_min)
412 im->minval = sensiblevalues[i]*(im->magfact);
414 if (-sensiblevalues[i-1]<=scaled_min &&
415 -sensiblevalues[i]>=scaled_min)
416 im->minval = -sensiblevalues[i-1]*(im->magfact);
418 if (sensiblevalues[i-1] >= scaled_max &&
419 sensiblevalues[i] <= scaled_max)
420 im->maxval = sensiblevalues[i-1]*(im->magfact);
422 if (-sensiblevalues[i-1]<=scaled_max &&
423 -sensiblevalues[i] >=scaled_max)
424 im->maxval = -sensiblevalues[i]*(im->magfact);
428 /* adjust min and max to the grid definition if there is one */
429 im->minval = (double)im->ylabfact * im->ygridstep *
430 floor(im->minval / ((double)im->ylabfact * im->ygridstep));
431 im->maxval = (double)im->ylabfact * im->ygridstep *
432 ceil(im->maxval /( (double)im->ylabfact * im->ygridstep));
436 fprintf(stderr,"SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
437 im->minval,im->maxval,im->magfact);
442 /* reduce data reimplementation by Alex */
446 enum cf_en cf, /* which consolidation function ?*/
447 unsigned long cur_step, /* step the data currently is in */
448 time_t *start, /* start, end and step as requested ... */
449 time_t *end, /* ... by the application will be ... */
450 unsigned long *step, /* ... adjusted to represent reality */
451 unsigned long *ds_cnt, /* number of data sources in file */
452 rrd_value_t **data) /* two dimensional array containing the data */
454 int i,reduce_factor = ceil((double)(*step) / (double)cur_step);
455 unsigned long col,dst_row,row_cnt,start_offset,end_offset,skiprows=0;
456 rrd_value_t *srcptr,*dstptr;
458 (*step) = cur_step*reduce_factor; /* set new step size for reduced data */
461 row_cnt = ((*end)-(*start))/cur_step;
467 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
468 row_cnt,reduce_factor,*start,*end,cur_step);
469 for (col=0;col<row_cnt;col++) {
470 printf("time %10lu: ",*start+(col+1)*cur_step);
471 for (i=0;i<*ds_cnt;i++)
472 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
477 /* We have to combine [reduce_factor] rows of the source
478 ** into one row for the destination. Doing this we also
479 ** need to take care to combine the correct rows. First
480 ** alter the start and end time so that they are multiples
481 ** of the new step time. We cannot reduce the amount of
482 ** time so we have to move the end towards the future and
483 ** the start towards the past.
485 end_offset = (*end) % (*step);
486 start_offset = (*start) % (*step);
488 /* If there is a start offset (which cannot be more than
489 ** one destination row), skip the appropriate number of
490 ** source rows and one destination row. The appropriate
491 ** number is what we do know (start_offset/cur_step) of
492 ** the new interval (*step/cur_step aka reduce_factor).
495 printf("start_offset: %lu end_offset: %lu\n",start_offset,end_offset);
496 printf("row_cnt before: %lu\n",row_cnt);
499 (*start) = (*start)-start_offset;
500 skiprows=reduce_factor-start_offset/cur_step;
501 srcptr+=skiprows* *ds_cnt;
502 for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
506 printf("row_cnt between: %lu\n",row_cnt);
509 /* At the end we have some rows that are not going to be
510 ** used, the amount is end_offset/cur_step
513 (*end) = (*end)-end_offset+(*step);
514 skiprows = end_offset/cur_step;
518 printf("row_cnt after: %lu\n",row_cnt);
521 /* Sanity check: row_cnt should be multiple of reduce_factor */
522 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
524 if (row_cnt%reduce_factor) {
525 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
526 row_cnt,reduce_factor);
527 printf("BUG in reduce_data()\n");
531 /* Now combine reduce_factor intervals at a time
532 ** into one interval for the destination.
535 for (dst_row=0;row_cnt>=reduce_factor;dst_row++) {
536 for (col=0;col<(*ds_cnt);col++) {
537 rrd_value_t newval=DNAN;
538 unsigned long validval=0;
540 for (i=0;i<reduce_factor;i++) {
541 if (isnan(srcptr[i*(*ds_cnt)+col])) {
545 if (isnan(newval)) newval = srcptr[i*(*ds_cnt)+col];
553 newval += srcptr[i*(*ds_cnt)+col];
556 newval = min (newval,srcptr[i*(*ds_cnt)+col]);
559 /* an interval contains a failure if any subintervals contained a failure */
561 newval = max (newval,srcptr[i*(*ds_cnt)+col]);
564 newval = srcptr[i*(*ds_cnt)+col];
569 if (validval == 0){newval = DNAN;} else{
587 srcptr+=(*ds_cnt)*reduce_factor;
588 row_cnt-=reduce_factor;
590 /* If we had to alter the endtime, we didn't have enough
591 ** source rows to fill the last row. Fill it with NaN.
593 if (end_offset) for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
595 row_cnt = ((*end)-(*start))/ *step;
597 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
598 row_cnt,*start,*end,*step);
599 for (col=0;col<row_cnt;col++) {
600 printf("time %10lu: ",*start+(col+1)*(*step));
601 for (i=0;i<*ds_cnt;i++)
602 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
609 /* get the data required for the graphs from the
613 data_fetch( image_desc_t *im )
617 /* pull the data from the log files ... */
618 for (i=0;i<im->gdes_c;i++){
619 /* only GF_DEF elements fetch data */
620 if (im->gdes[i].gf != GF_DEF)
624 /* do we have it already ?*/
625 for (ii=0;ii<i;ii++){
626 if (im->gdes[ii].gf != GF_DEF)
628 if((strcmp(im->gdes[i].rrd,im->gdes[ii].rrd) == 0)
629 && (im->gdes[i].cf == im->gdes[ii].cf)){
630 /* OK the data it is here already ...
631 * we just copy the header portion */
632 im->gdes[i].start = im->gdes[ii].start;
633 im->gdes[i].end = im->gdes[ii].end;
634 im->gdes[i].step = im->gdes[ii].step;
635 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
636 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
637 im->gdes[i].data = im->gdes[ii].data;
638 im->gdes[i].data_first = 0;
645 unsigned long ft_step = im->gdes[i].step ;
647 if((rrd_fetch_fn(im->gdes[i].rrd,
653 &im->gdes[i].ds_namv,
654 &im->gdes[i].data)) == -1){
657 im->gdes[i].data_first = 1;
659 if (ft_step < im->gdes[i].step) {
660 reduce_data(im->gdes[i].cf,
668 im->gdes[i].step = ft_step;
672 /* lets see if the required data source is realy there */
673 for(ii=0;ii<im->gdes[i].ds_cnt;ii++){
674 if(strcmp(im->gdes[i].ds_namv[ii],im->gdes[i].ds_nam) == 0){
677 if (im->gdes[i].ds== -1){
678 rrd_set_error("No DS called '%s' in '%s'",
679 im->gdes[i].ds_nam,im->gdes[i].rrd);
687 /* evaluate the expressions in the CDEF functions */
689 /*************************************************************
691 *************************************************************/
694 find_var_wrapper(void *arg1, char *key)
696 return find_var((image_desc_t *) arg1, key);
699 /* find gdes containing var*/
701 find_var(image_desc_t *im, char *key){
703 for(ii=0;ii<im->gdes_c-1;ii++){
704 if((im->gdes[ii].gf == GF_DEF
705 || im->gdes[ii].gf == GF_VDEF
706 || im->gdes[ii].gf == GF_CDEF)
707 && (strcmp(im->gdes[ii].vname,key) == 0)){
714 /* find the largest common denominator for all the numbers
715 in the 0 terminated num array */
720 for (i=0;num[i+1]!=0;i++){
722 rest=num[i] % num[i+1];
723 num[i]=num[i+1]; num[i+1]=rest;
727 /* return i==0?num[i]:num[i-1]; */
731 /* run the rpn calculator on all the VDEF and CDEF arguments */
733 data_calc( image_desc_t *im){
737 long *steparray, rpi;
742 rpnstack_init(&rpnstack);
744 for (gdi=0;gdi<im->gdes_c;gdi++){
745 /* Look for GF_VDEF and GF_CDEF in the same loop,
746 * so CDEFs can use VDEFs and vice versa
748 switch (im->gdes[gdi].gf) {
750 /* A VDEF has no DS. This also signals other parts
751 * of rrdtool that this is a VDEF value, not a CDEF.
753 im->gdes[gdi].ds_cnt = 0;
754 if (vdef_calc(im,gdi)) {
755 rrd_set_error("Error processing VDEF '%s'"
758 rpnstack_free(&rpnstack);
763 im->gdes[gdi].ds_cnt = 1;
764 im->gdes[gdi].ds = 0;
765 im->gdes[gdi].data_first = 1;
766 im->gdes[gdi].start = 0;
767 im->gdes[gdi].end = 0;
772 /* Find the variables in the expression.
773 * - VDEF variables are substituted by their values
774 * and the opcode is changed into OP_NUMBER.
775 * - CDEF variables are analized for their step size,
776 * the lowest common denominator of all the step
777 * sizes of the data sources involved is calculated
778 * and the resulting number is the step size for the
779 * resulting data source.
781 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
782 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE){
783 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
784 if (im->gdes[ptr].ds_cnt == 0) {
786 printf("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
788 im->gdes[ptr].vname);
789 printf("DEBUG: value from vdef is %f\n",im->gdes[ptr].vf.val);
791 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
792 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
794 if ((steparray = rrd_realloc(steparray, (++stepcnt+1)*sizeof(*steparray)))==NULL){
795 rrd_set_error("realloc steparray");
796 rpnstack_free(&rpnstack);
800 steparray[stepcnt-1] = im->gdes[ptr].step;
802 /* adjust start and end of cdef (gdi) so
803 * that it runs from the latest start point
804 * to the earliest endpoint of any of the
805 * rras involved (ptr)
807 if(im->gdes[gdi].start < im->gdes[ptr].start)
808 im->gdes[gdi].start = im->gdes[ptr].start;
810 if(im->gdes[gdi].end == 0 ||
811 im->gdes[gdi].end > im->gdes[ptr].end)
812 im->gdes[gdi].end = im->gdes[ptr].end;
814 /* store pointer to the first element of
815 * the rra providing data for variable,
816 * further save step size and data source
819 im->gdes[gdi].rpnp[rpi].data = im->gdes[ptr].data + im->gdes[ptr].ds;
820 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
821 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
823 /* backoff the *.data ptr; this is done so
824 * rpncalc() function doesn't have to treat
825 * the first case differently
827 } /* if ds_cnt != 0 */
828 } /* if OP_VARIABLE */
829 } /* loop through all rpi */
831 /* move the data pointers to the correct period */
832 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
833 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE){
834 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
835 if(im->gdes[gdi].start > im->gdes[ptr].start) {
836 im->gdes[gdi].rpnp[rpi].data += im->gdes[gdi].rpnp[rpi].ds_cnt;
842 if(steparray == NULL){
843 rrd_set_error("rpn expressions without DEF"
844 " or CDEF variables are not supported");
845 rpnstack_free(&rpnstack);
848 steparray[stepcnt]=0;
849 /* Now find the resulting step. All steps in all
850 * used RRAs have to be visited
852 im->gdes[gdi].step = lcd(steparray);
854 if((im->gdes[gdi].data = malloc((
855 (im->gdes[gdi].end-im->gdes[gdi].start)
856 / im->gdes[gdi].step)
857 * sizeof(double)))==NULL){
858 rrd_set_error("malloc im->gdes[gdi].data");
859 rpnstack_free(&rpnstack);
863 /* Step through the new cdef results array and
864 * calculate the values
866 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
867 now<=im->gdes[gdi].end;
868 now += im->gdes[gdi].step)
870 rpnp_t *rpnp = im -> gdes[gdi].rpnp;
872 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
873 * in this case we are advancing by timesteps;
874 * we use the fact that time_t is a synonym for long
876 if (rpn_calc(rpnp,&rpnstack,(long) now,
877 im->gdes[gdi].data,++dataidx) == -1) {
878 /* rpn_calc sets the error string */
879 rpnstack_free(&rpnstack);
882 } /* enumerate over time steps within a CDEF */
887 } /* enumerate over CDEFs */
888 rpnstack_free(&rpnstack);
892 /* massage data so, that we get one value for each x coordinate in the graph */
894 data_proc( image_desc_t *im ){
896 double pixstep = (double)(im->end-im->start)
897 /(double)im->xsize; /* how much time
898 passes in one pixel */
900 double minval=DNAN,maxval=DNAN;
902 unsigned long gr_time;
904 /* memory for the processed data */
905 for(i=0;i<im->gdes_c;i++){
906 if((im->gdes[i].gf==GF_LINE) ||
907 (im->gdes[i].gf==GF_AREA) ||
908 (im->gdes[i].gf==GF_TICK) ||
909 (im->gdes[i].gf==GF_STACK)){
910 if((im->gdes[i].p_data = malloc((im->xsize +1)
911 * sizeof(rrd_value_t)))==NULL){
912 rrd_set_error("malloc data_proc");
918 for(i=0;i<im->xsize;i++){
920 gr_time = im->start+pixstep*i; /* time of the
924 for(ii=0;ii<im->gdes_c;ii++){
926 switch(im->gdes[ii].gf){
932 vidx = im->gdes[ii].vidx;
936 ((unsigned long)floor(
937 (double)(gr_time-im->gdes[vidx].start) / im->gdes[vidx].step
939 ) *im->gdes[vidx].ds_cnt
942 if (! isnan(value)) {
944 im->gdes[ii].p_data[i] = paintval;
945 /* GF_TICK: the data values are not relevant for min and max */
946 if (finite(paintval) && im->gdes[ii].gf != GF_TICK ){
947 if (isnan(minval) || paintval < minval)
949 if (isnan(maxval) || paintval > maxval)
953 im->gdes[ii].p_data[i] = DNAN;
970 /* if min or max have not been asigned a value this is because
971 there was no data in the graph ... this is not good ...
972 lets set these to dummy values then ... */
974 if (isnan(minval)) minval = 0.0;
975 if (isnan(maxval)) maxval = 1.0;
977 /* adjust min and max values */
978 if (isnan(im->minval)
979 || ((!im->logarithmic && !im->rigid) /* don't adjust low-end with log scale */
980 && im->minval > minval))
982 if (isnan(im->maxval)
984 && im->maxval < maxval)){
986 im->maxval = maxval * 1.1;
990 /* make sure min and max are not equal */
991 if (im->minval == im->maxval) {
993 if (! im->logarithmic) {
997 /* make sure min and max are not both zero */
998 if (im->maxval == 0.0) {
1008 /* identify the point where the first gridline, label ... gets placed */
1012 time_t start, /* what is the initial time */
1013 enum tmt_en baseint, /* what is the basic interval */
1014 long basestep /* how many if these do we jump a time */
1018 tm = *localtime(&start);
1021 tm.tm_sec -= tm.tm_sec % basestep; break;
1024 tm.tm_min -= tm.tm_min % basestep;
1029 tm.tm_hour -= tm.tm_hour % basestep; break;
1031 /* we do NOT look at the basestep for this ... */
1034 tm.tm_hour = 0; break;
1036 /* we do NOT look at the basestep for this ... */
1040 tm.tm_mday -= tm.tm_wday -1; /* -1 because we want the monday */
1041 if (tm.tm_wday==0) tm.tm_mday -= 7; /* we want the *previous* monday */
1048 tm.tm_mon -= tm.tm_mon % basestep; break;
1056 tm.tm_year -= (tm.tm_year+1900) % basestep;
1061 /* identify the point where the next gridline, label ... gets placed */
1064 time_t current, /* what is the initial time */
1065 enum tmt_en baseint, /* what is the basic interval */
1066 long basestep /* how many if these do we jump a time */
1071 tm = *localtime(¤t);
1075 tm.tm_sec += basestep; break;
1077 tm.tm_min += basestep; break;
1079 tm.tm_hour += basestep; break;
1081 tm.tm_mday += basestep; break;
1083 tm.tm_mday += 7*basestep; break;
1085 tm.tm_mon += basestep; break;
1087 tm.tm_year += basestep;
1089 madetime = mktime(&tm);
1090 } while (madetime == -1); /* this is necessary to skip impssible times
1091 like the daylight saving time skips */
1097 /* calculate values required for PRINT and GPRINT functions */
1100 print_calc(image_desc_t *im, char ***prdata)
1102 long i,ii,validsteps;
1105 int graphelement = 0;
1108 double magfact = -1;
1112 if (im->imginfo) prlines++;
1113 for(i=0;i<im->gdes_c;i++){
1114 switch(im->gdes[i].gf){
1117 if(((*prdata) = rrd_realloc((*prdata),prlines*sizeof(char *)))==NULL){
1118 rrd_set_error("realloc prdata");
1122 /* PRINT and GPRINT can now print VDEF generated values.
1123 * There's no need to do any calculations on them as these
1124 * calculations were already made.
1126 vidx = im->gdes[i].vidx;
1127 if (im->gdes[vidx].gf==GF_VDEF) { /* simply use vals */
1128 printval = im->gdes[vidx].vf.val;
1129 printtime = im->gdes[vidx].vf.when;
1130 } else { /* need to calculate max,min,avg etcetera */
1131 max_ii =((im->gdes[vidx].end
1132 - im->gdes[vidx].start)
1133 / im->gdes[vidx].step
1134 * im->gdes[vidx].ds_cnt);
1137 for( ii=im->gdes[vidx].ds;
1139 ii+=im->gdes[vidx].ds_cnt){
1140 if (! finite(im->gdes[vidx].data[ii]))
1142 if (isnan(printval)){
1143 printval = im->gdes[vidx].data[ii];
1148 switch (im->gdes[i].cf){
1151 case CF_DEVSEASONAL:
1155 printval += im->gdes[vidx].data[ii];
1158 printval = min( printval, im->gdes[vidx].data[ii]);
1162 printval = max( printval, im->gdes[vidx].data[ii]);
1165 printval = im->gdes[vidx].data[ii];
1168 if (im->gdes[i].cf==CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1169 if (validsteps > 1) {
1170 printval = (printval / validsteps);
1173 } /* prepare printval */
1175 if (!strcmp(im->gdes[i].format,"%c")) { /* VDEF time print */
1176 if (im->gdes[i].gf == GF_PRINT){
1177 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1178 sprintf((*prdata)[prlines-2],"%s (%lu)",
1179 ctime(&printtime),printtime);
1180 (*prdata)[prlines-1] = NULL;
1182 sprintf(im->gdes[i].legend,"%s (%lu)",
1183 ctime(&printtime),printtime);
1187 if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) {
1188 /* Magfact is set to -1 upon entry to print_calc. If it
1189 * is still less than 0, then we need to run auto_scale.
1190 * Otherwise, put the value into the correct units. If
1191 * the value is 0, then do not set the symbol or magnification
1192 * so next the calculation will be performed again. */
1193 if (magfact < 0.0) {
1194 auto_scale(im,&printval,&si_symb,&magfact);
1195 if (printval == 0.0)
1198 printval /= magfact;
1200 *(++percent_s) = 's';
1201 } else if (strstr(im->gdes[i].format,"%s") != NULL) {
1202 auto_scale(im,&printval,&si_symb,&magfact);
1205 if (im->gdes[i].gf == GF_PRINT){
1206 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1207 if (bad_format(im->gdes[i].format)) {
1208 rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1211 #ifdef HAVE_SNPRINTF
1212 snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
1214 sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
1216 (*prdata)[prlines-1] = NULL;
1220 if (bad_format(im->gdes[i].format)) {
1221 rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1224 #ifdef HAVE_SNPRINTF
1225 snprintf(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
1227 sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
1249 return graphelement;
1253 /* place legends with color spots */
1255 leg_place(image_desc_t *im)
1258 int interleg = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1259 int box =im->text_prop[TEXT_PROP_LEGEND].size*1.5;
1260 int border = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1261 int fill=0, fill_last;
1263 int leg_x = border, leg_y = im->yimg;
1267 char prt_fctn; /*special printfunctions */
1270 if( !(im->extra_flags & NOLEGEND) ) {
1271 if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1272 rrd_set_error("malloc for legspace");
1276 for(i=0;i<im->gdes_c;i++){
1279 leg_cc = strlen(im->gdes[i].legend);
1281 /* is there a controle code ant the end of the legend string ? */
1282 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\') {
1283 prt_fctn = im->gdes[i].legend[leg_cc-1];
1285 im->gdes[i].legend[leg_cc] = '\0';
1289 /* remove exess space */
1290 while (prt_fctn=='g' &&
1292 im->gdes[i].legend[leg_cc-1]==' '){
1294 im->gdes[i].legend[leg_cc]='\0';
1297 legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1300 /* no interleg space if string ends in \g */
1301 fill += legspace[i];
1303 if (im->gdes[i].gf != GF_GPRINT &&
1304 im->gdes[i].gf != GF_COMMENT) {
1307 fill += gfx_get_text_width(im->canvas, fill+border,
1308 im->text_prop[TEXT_PROP_LEGEND].font,
1309 im->text_prop[TEXT_PROP_LEGEND].size,
1311 im->gdes[i].legend);
1316 /* who said there was a special tag ... ?*/
1317 if (prt_fctn=='g') {
1320 if (prt_fctn == '\0') {
1321 if (i == im->gdes_c -1 ) prt_fctn ='l';
1323 /* is it time to place the legends ? */
1324 if (fill > im->ximg - 2*border){
1339 if (prt_fctn != '\0'){
1341 if (leg_c >= 2 && prt_fctn == 'j') {
1342 glue = (im->ximg - fill - 2* border) / (leg_c-1);
1346 if (prt_fctn =='c') leg_x = (im->ximg - fill) / 2.0;
1347 if (prt_fctn =='r') leg_x = im->ximg - fill - border;
1349 for(ii=mark;ii<=i;ii++){
1350 if(im->gdes[ii].legend[0]=='\0')
1352 im->gdes[ii].leg_x = leg_x;
1353 im->gdes[ii].leg_y = leg_y;
1355 gfx_get_text_width(im->canvas, leg_x,
1356 im->text_prop[TEXT_PROP_LEGEND].font,
1357 im->text_prop[TEXT_PROP_LEGEND].size,
1359 im->gdes[ii].legend)
1362 if (im->gdes[ii].gf != GF_GPRINT &&
1363 im->gdes[ii].gf != GF_COMMENT)
1366 leg_y = leg_y + im->text_prop[TEXT_PROP_LEGEND].size*1.2;
1367 if (prt_fctn == 's') leg_y -= im->text_prop[TEXT_PROP_LEGEND].size*1.2;
1379 /* create a grid on the graph. it determines what to do
1380 from the values of xsize, start and end */
1382 /* the xaxis labels are determined from the number of seconds per pixel
1383 in the requested graph */
1388 horizontal_grid(image_desc_t *im)
1396 char graph_label[100];
1398 int labfact,gridind;
1399 int decimals, fractionals;
1404 range = im->maxval - im->minval;
1405 scaledrange = range / im->magfact;
1407 /* does the scale of this graph make it impossible to put lines
1408 on it? If so, give up. */
1409 if (isnan(scaledrange)) {
1413 /* find grid spaceing */
1415 if(isnan(im->ygridstep)){
1416 if(im->extra_flags & ALTYGRID) {
1417 /* find the value with max number of digits. Get number of digits */
1418 decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))));
1419 if(decimals <= 0) /* everything is small. make place for zero */
1422 fractionals = floor(log10(range));
1423 if(fractionals < 0) /* small amplitude. */
1424 sprintf(labfmt, "%%%d.%df", decimals - fractionals + 1, -fractionals + 1);
1426 sprintf(labfmt, "%%%d.1f", decimals + 1);
1427 gridstep = pow((double)10, (double)fractionals);
1428 if(gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1430 /* should have at least 5 lines but no more then 15 */
1431 if(range/gridstep < 5)
1433 if(range/gridstep > 15)
1435 if(range/gridstep > 5) {
1437 if(range/gridstep > 8)
1446 for(i=0;ylab[i].grid > 0;i++){
1447 pixel = im->ysize / (scaledrange / ylab[i].grid);
1448 if (gridind == -1 && pixel > 5) {
1455 if (pixel * ylab[gridind].lfac[i] >= 2 * im->text_prop[TEXT_PROP_AXIS].size) {
1456 labfact = ylab[gridind].lfac[i];
1461 gridstep = ylab[gridind].grid * im->magfact;
1464 gridstep = im->ygridstep;
1465 labfact = im->ylabfact;
1469 X1=im->xorigin+im->xsize;
1471 sgrid = (int)( im->minval / gridstep - 1);
1472 egrid = (int)( im->maxval / gridstep + 1);
1473 scaledstep = gridstep/im->magfact;
1474 for (i = sgrid; i <= egrid; i++){
1475 Y0=ytr(im,gridstep*i);
1476 if ( Y0 >= im->yorigin-im->ysize
1477 && Y0 <= im->yorigin){
1478 if(i % labfact == 0){
1479 if (i==0 || im->symbol == ' ') {
1481 if(im->extra_flags & ALTYGRID) {
1482 sprintf(graph_label,labfmt,scaledstep*i);
1485 sprintf(graph_label,"%4.1f",scaledstep*i);
1488 sprintf(graph_label,"%4.0f",scaledstep*i);
1492 sprintf(graph_label,"%4.1f %c",scaledstep*i, im->symbol);
1494 sprintf(graph_label,"%4.0f %c",scaledstep*i, im->symbol);
1498 gfx_new_text ( im->canvas,
1499 X0-im->text_prop[TEXT_PROP_AXIS].size/1.5, Y0,
1500 im->graph_col[GRC_FONT],
1501 im->text_prop[TEXT_PROP_AXIS].font,
1502 im->text_prop[TEXT_PROP_AXIS].size,
1503 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1505 gfx_new_line ( im->canvas,
1508 MGRIDWIDTH, im->graph_col[GRC_MGRID] );
1511 gfx_new_line ( im->canvas,
1514 GRIDWIDTH, im->graph_col[GRC_GRID] );
1522 /* logaritmic horizontal grid */
1524 horizontal_log_grid(image_desc_t *im)
1528 int minoridx=0, majoridx=0;
1529 char graph_label[100];
1531 double value, pixperstep, minstep;
1533 /* find grid spaceing */
1534 pixpex= (double)im->ysize / (log10(im->maxval) - log10(im->minval));
1536 if (isnan(pixpex)) {
1540 for(i=0;yloglab[i][0] > 0;i++){
1541 minstep = log10(yloglab[i][0]);
1542 for(ii=1;yloglab[i][ii+1] > 0;ii++){
1543 if(yloglab[i][ii+2]==0){
1544 minstep = log10(yloglab[i][ii+1])-log10(yloglab[i][ii]);
1548 pixperstep = pixpex * minstep;
1549 if(pixperstep > 5){minoridx = i;}
1550 if(pixperstep > 2 * im->text_prop[TEXT_PROP_LEGEND].size){majoridx = i;}
1554 X1=im->xorigin+im->xsize;
1555 /* paint minor grid */
1556 for (value = pow((double)10, log10(im->minval)
1557 - fmod(log10(im->minval),log10(yloglab[minoridx][0])));
1558 value <= im->maxval;
1559 value *= yloglab[minoridx][0]){
1560 if (value < im->minval) continue;
1562 while(yloglab[minoridx][++i] > 0){
1563 Y0 = ytr(im,value * yloglab[minoridx][i]);
1564 if (Y0 <= im->yorigin - im->ysize) break;
1565 gfx_new_line ( im->canvas,
1568 GRIDWIDTH, im->graph_col[GRC_GRID] );
1572 /* paint major grid and labels*/
1573 for (value = pow((double)10, log10(im->minval)
1574 - fmod(log10(im->minval),log10(yloglab[majoridx][0])));
1575 value <= im->maxval;
1576 value *= yloglab[majoridx][0]){
1577 if (value < im->minval) continue;
1579 while(yloglab[majoridx][++i] > 0){
1580 Y0 = ytr(im,value * yloglab[majoridx][i]);
1581 if (Y0 <= im->yorigin - im->ysize) break;
1582 gfx_new_line ( im->canvas,
1585 MGRIDWIDTH, im->graph_col[GRC_MGRID] );
1587 sprintf(graph_label,"%3.0e",value * yloglab[majoridx][i]);
1588 gfx_new_text ( im->canvas,
1589 X0-im->text_prop[TEXT_PROP_AXIS].size/1.5, Y0,
1590 im->graph_col[GRC_FONT],
1591 im->text_prop[TEXT_PROP_AXIS].font,
1592 im->text_prop[TEXT_PROP_AXIS].size,
1593 im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1605 int xlab_sel; /* which sort of label and grid ? */
1608 char graph_label[100];
1609 double X0,Y0,Y1; /* points for filled graph and more*/
1612 /* the type of time grid is determined by finding
1613 the number of seconds per pixel in the graph */
1616 if(im->xlab_user.minsec == -1){
1617 factor=(im->end - im->start)/im->xsize;
1619 while ( xlab[xlab_sel+1].minsec != -1
1620 && xlab[xlab_sel+1].minsec <= factor){ xlab_sel++; }
1621 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
1622 im->xlab_user.gridst = xlab[xlab_sel].gridst;
1623 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
1624 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
1625 im->xlab_user.labtm = xlab[xlab_sel].labtm;
1626 im->xlab_user.labst = xlab[xlab_sel].labst;
1627 im->xlab_user.precis = xlab[xlab_sel].precis;
1628 im->xlab_user.stst = xlab[xlab_sel].stst;
1631 /* y coords are the same for every line ... */
1633 Y1 = im->yorigin-im->ysize;
1636 /* paint the minor grid */
1637 for(ti = find_first_time(im->start,
1638 im->xlab_user.gridtm,
1639 im->xlab_user.gridst);
1641 ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
1643 /* are we inside the graph ? */
1644 if (ti < im->start || ti > im->end) continue;
1646 gfx_new_line(im->canvas,X0,Y0+1, X0,Y1-1,GRIDWIDTH, im->graph_col[GRC_GRID]);
1650 /* paint the major grid */
1651 for(ti = find_first_time(im->start,
1652 im->xlab_user.mgridtm,
1653 im->xlab_user.mgridst);
1655 ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
1657 /* are we inside the graph ? */
1658 if (ti < im->start || ti > im->end) continue;
1660 gfx_new_line(im->canvas,X0,Y0+2, X0,Y1-2,MGRIDWIDTH, im->graph_col[GRC_MGRID]);
1663 /* paint the labels below the graph */
1664 for(ti = find_first_time(im->start,
1665 im->xlab_user.labtm,
1666 im->xlab_user.labst);
1668 ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
1670 tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
1671 /* are we inside the graph ? */
1672 if (ti < im->start || ti > im->end) continue;
1675 strftime(graph_label,99,im->xlab_user.stst,localtime(&tilab));
1677 # error "your libc has no strftime I guess we'll abort the exercise here."
1679 gfx_new_text ( im->canvas,
1680 xtr(im,tilab), Y0+im->text_prop[TEXT_PROP_AXIS].size/1.5,
1681 im->graph_col[GRC_FONT],
1682 im->text_prop[TEXT_PROP_AXIS].font,
1683 im->text_prop[TEXT_PROP_AXIS].size,
1684 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP,
1697 /* draw x and y axis */
1698 gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
1699 im->xorigin+im->xsize,im->yorigin-im->ysize,
1700 GRIDWIDTH, im->graph_col[GRC_GRID]);
1702 gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
1703 im->xorigin+im->xsize,im->yorigin-im->ysize,
1704 GRIDWIDTH, im->graph_col[GRC_GRID]);
1706 gfx_new_line ( im->canvas, im->xorigin-4,im->yorigin,
1707 im->xorigin+im->xsize+4,im->yorigin,
1708 MGRIDWIDTH, im->graph_col[GRC_GRID]);
1710 gfx_new_line ( im->canvas, im->xorigin,im->yorigin+4,
1711 im->xorigin,im->yorigin-im->ysize-4,
1712 MGRIDWIDTH, im->graph_col[GRC_GRID]);
1715 /* arrow for X axis direction */
1716 gfx_new_area ( im->canvas,
1717 im->xorigin+im->xsize+3, im->yorigin-3,
1718 im->xorigin+im->xsize+3, im->yorigin+4,
1719 im->xorigin+im->xsize+8, im->yorigin+0.5, /* LINEOFFSET */
1720 im->graph_col[GRC_ARROW]);
1727 grid_paint(image_desc_t *im)
1731 double X0,Y0; /* points for filled graph and more*/
1734 /* draw 3d border */
1735 node = gfx_new_area (im->canvas, 0,im->yimg,
1737 2,2,im->graph_col[GRC_SHADEA]);
1738 gfx_add_point( node , im->ximg - 2, 2 );
1739 gfx_add_point( node , im->ximg, 0 );
1740 gfx_add_point( node , 0,0 );
1741 /* gfx_add_point( node , 0,im->yimg ); */
1743 node = gfx_new_area (im->canvas, 2,im->yimg-2,
1744 im->ximg-2,im->yimg-2,
1746 im->graph_col[GRC_SHADEB]);
1747 gfx_add_point( node , im->ximg,0);
1748 gfx_add_point( node , im->ximg,im->yimg);
1749 gfx_add_point( node , 0,im->yimg);
1750 /* gfx_add_point( node , 0,im->yimg ); */
1753 if (im->draw_x_grid == 1 )
1756 if (im->draw_y_grid == 1){
1757 if(im->logarithmic){
1758 res = horizontal_log_grid(im);
1760 res = horizontal_grid(im);
1763 /* dont draw horizontal grid if there is no min and max val */
1765 char *nodata = "No Data found";
1766 gfx_new_text(im->canvas,im->ximg/2, (2*im->yorigin-im->ysize) / 2,
1767 im->graph_col[GRC_FONT],
1768 im->text_prop[TEXT_PROP_AXIS].font,
1769 im->text_prop[TEXT_PROP_AXIS].size,
1770 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
1775 /* yaxis description */
1776 if (im->canvas->imgformat != IF_PNG) {
1777 gfx_new_text( im->canvas,
1778 7, (im->yorigin - im->ysize/2),
1779 im->graph_col[GRC_FONT],
1780 im->text_prop[TEXT_PROP_AXIS].font,
1781 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 270.0,
1782 GFX_H_CENTER, GFX_V_CENTER,
1785 /* horrible hack until we can actually print vertically */
1788 int l=strlen(im->ylegend);
1790 for (n=0;n<strlen(im->ylegend);n++) {
1791 s[0]=im->ylegend[n];
1793 gfx_new_text(im->canvas,7,im->text_prop[TEXT_PROP_AXIS].size*(l-n),
1794 im->graph_col[GRC_FONT],
1795 im->text_prop[TEXT_PROP_AXIS].font,
1796 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 270.0,
1797 GFX_H_CENTER, GFX_V_CENTER,
1804 gfx_new_text( im->canvas,
1805 im->ximg/2, im->text_prop[TEXT_PROP_TITLE].size,
1806 im->graph_col[GRC_FONT],
1807 im->text_prop[TEXT_PROP_TITLE].font,
1808 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
1809 GFX_H_CENTER, GFX_V_CENTER,
1813 if( !(im->extra_flags & NOLEGEND) ) {
1814 for(i=0;i<im->gdes_c;i++){
1815 if(im->gdes[i].legend[0] =='\0')
1818 /* im->gdes[i].leg_y is the bottom of the legend */
1819 X0 = im->gdes[i].leg_x;
1820 Y0 = im->gdes[i].leg_y;
1822 if ( im->gdes[i].gf != GF_GPRINT
1823 && im->gdes[i].gf != GF_COMMENT) {
1826 boxH = gfx_get_text_width(im->canvas, 0,
1827 im->text_prop[TEXT_PROP_AXIS].font,
1828 im->text_prop[TEXT_PROP_AXIS].size,
1829 im->tabwidth,"M") * 1.25;
1832 node = gfx_new_area(im->canvas,
1837 gfx_add_point ( node, X0+boxH, Y0-boxV );
1838 node = gfx_new_line(im->canvas,
1841 gfx_add_point(node,X0+boxH,Y0);
1842 gfx_add_point(node,X0+boxH,Y0-boxV);
1843 gfx_close_path(node);
1844 X0 += boxH / 1.25 * 2;
1846 gfx_new_text ( im->canvas, X0, Y0,
1847 im->graph_col[GRC_FONT],
1848 im->text_prop[TEXT_PROP_AXIS].font,
1849 im->text_prop[TEXT_PROP_AXIS].size,
1850 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
1851 im->gdes[i].legend );
1857 /*****************************************************
1858 * lazy check make sure we rely need to create this graph
1859 *****************************************************/
1861 int lazy_check(image_desc_t *im){
1864 struct stat imgstat;
1866 if (im->lazy == 0) return 0; /* no lazy option */
1867 if (stat(im->graphfile,&imgstat) != 0)
1868 return 0; /* can't stat */
1869 /* one pixel in the existing graph is more then what we would
1871 if (time(NULL) - imgstat.st_mtime >
1872 (im->end - im->start) / im->xsize)
1874 if ((fd = fopen(im->graphfile,"rb")) == NULL)
1875 return 0; /* the file does not exist */
1876 switch (im->canvas->imgformat) {
1878 size = PngSize(fd,&(im->ximg),&(im->yimg));
1888 pie_part(image_desc_t *im, gfx_color_t color,
1889 double PieCenterX, double PieCenterY, double Radius,
1890 double startangle, double endangle)
1894 double step=M_PI/50; /* Number of iterations for the circle;
1895 ** 10 is definitely too low, more than
1896 ** 50 seems to be overkill
1899 /* Strange but true: we have to work clockwise or else
1900 ** anti aliasing nor transparency don't work.
1902 ** This test is here to make sure we do it right, also
1903 ** this makes the for...next loop more easy to implement.
1904 ** The return will occur if the user enters a negative number
1905 ** (which shouldn't be done according to the specs) or if the
1906 ** programmers do something wrong (which, as we all know, never
1907 ** happens anyway :)
1909 if (endangle<startangle) return;
1911 /* Hidden feature: Radius decreases each full circle */
1913 while (angle>=2*M_PI) {
1918 node=gfx_new_area(im->canvas,
1919 PieCenterX+sin(startangle)*Radius,
1920 PieCenterY-cos(startangle)*Radius,
1923 PieCenterX+sin(endangle)*Radius,
1924 PieCenterY-cos(endangle)*Radius,
1926 for (angle=endangle;angle-startangle>=step;angle-=step) {
1928 PieCenterX+sin(angle)*Radius,
1929 PieCenterY-cos(angle)*Radius );
1934 graph_size_location(image_desc_t *im, int elements, int piechart )
1936 /* The actual size of the image to draw is determined from
1937 ** several sources. The size given on the command line is
1938 ** the graph area but we need more as we have to draw labels
1939 ** and other things outside the graph area
1942 /* +-+-------------------------------------------+
1943 ** |l|.................title.....................|
1944 ** |e+--+-------------------------------+--------+
1947 ** |l| l| main graph area | chart |
1950 ** |r+--+-------------------------------+--------+
1951 ** |e| | x-axis labels | |
1952 ** |v+--+-------------------------------+--------+
1953 ** | |..............legends......................|
1954 ** +-+-------------------------------------------+
1956 int Xvertical=0, Yvertical=0,
1957 Xtitle =0, Ytitle =0,
1958 Xylabel =0, Yylabel =0,
1961 Xxlabel =0, Yxlabel =0,
1963 Xlegend =0, Ylegend =0,
1965 Xspacing =10, Yspacing =10;
1967 if (im->ylegend[0] != '\0') {
1968 Xvertical = im->text_prop[TEXT_PROP_LEGEND].size *2;
1969 Yvertical = im->text_prop[TEXT_PROP_LEGEND].size * (strlen(im->ylegend)+1);
1972 if (im->title[0] != '\0') {
1973 /* The title is placed "inbetween" two text lines so it
1974 ** automatically has some vertical spacing. The horizontal
1975 ** spacing is added here, on each side.
1977 Xtitle = gfx_get_text_width(im->canvas, 0,
1978 im->text_prop[TEXT_PROP_TITLE].font,
1979 im->text_prop[TEXT_PROP_TITLE].size,
1981 im->title) + 2*Xspacing;
1982 Ytitle = im->text_prop[TEXT_PROP_TITLE].size*2;
1988 if (im->draw_x_grid) {
1990 Yxlabel=im->text_prop[TEXT_PROP_LEGEND].size *2;
1992 if (im->draw_y_grid) {
1993 Xylabel=im->text_prop[TEXT_PROP_LEGEND].size *6;
1999 im->piesize=im->xsize<im->ysize?im->xsize:im->ysize;
2004 /* Now calculate the total size. Insert some spacing where
2005 desired. im->xorigin and im->yorigin need to correspond
2006 with the lower left corner of the main graph area or, if
2007 this one is not set, the imaginary box surrounding the
2010 /* The legend width cannot yet be determined, as a result we
2011 ** have problems adjusting the image to it. For now, we just
2012 ** forget about it at all; the legend will have to fit in the
2013 ** size already allocated.
2015 im->ximg = Xylabel + Xmain + Xpie + Xspacing;
2016 if (Xmain) im->ximg += Xspacing;
2017 if (Xpie) im->ximg += Xspacing;
2018 im->xorigin = Xspacing + Xylabel;
2019 if (Xtitle > im->ximg) im->ximg = Xtitle;
2021 im->ximg += Xvertical;
2022 im->xorigin += Xvertical;
2026 /* The vertical size is interesting... we need to compare
2027 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend} with Yvertical
2028 ** however we need to know {Ytitle+Ymain+Yxlabel} in order to
2029 ** start even thinking about Ylegend.
2031 ** Do it in three portions: First calculate the inner part,
2032 ** then do the legend, then adjust the total height of the img.
2035 /* reserve space for main and/or pie */
2036 im->yimg = Ymain + Yxlabel;
2037 if (im->yimg < Ypie) im->yimg = Ypie;
2038 im->yorigin = im->yimg - Yxlabel;
2039 /* reserve space for the title *or* some padding above the graph */
2042 im->yorigin += Ytitle;
2044 im->yimg += Yspacing;
2045 im->yorigin += Yspacing;
2047 /* reserve space for padding below the graph */
2048 im->yimg += Yspacing;
2051 /* Determine where to place the legends onto the image.
2052 ** Adjust im->yimg to match the space requirements.
2054 if(leg_place(im)==-1)
2057 /* last of three steps: check total height of image */
2058 if (im->yimg < Yvertical) im->yimg = Yvertical;
2061 if (Xlegend > im->ximg) {
2063 /* reposition Pie */
2066 /* The pie is placed in the upper right hand corner,
2067 ** just below the title (if any) and with sufficient
2071 im->pie_x = im->ximg - Xspacing - Xpie/2;
2072 im->pie_y = im->yorigin-Ymain+Ypie/2;
2074 im->pie_x = im->ximg/2;
2075 im->pie_y = im->yorigin-Ypie/2;
2081 /* draw that picture thing ... */
2083 graph_paint(image_desc_t *im, char ***calcpr)
2086 int lazy = lazy_check(im);
2088 double PieStart=0.0;
2092 double areazero = 0.0;
2093 enum gf_en stack_gf = GF_PRINT;
2094 graph_desc_t *lastgdes = NULL;
2096 /* if we are lazy and there is nothing to PRINT ... quit now */
2097 if (lazy && im->prt_c==0) return 0;
2099 /* pull the data from the rrd files ... */
2101 if(data_fetch(im)==-1)
2104 /* evaluate VDEF and CDEF operations ... */
2105 if(data_calc(im)==-1)
2108 /* check if we need to draw a piechart */
2109 for(i=0;i<im->gdes_c;i++){
2110 if (im->gdes[i].gf == GF_PART) {
2116 /* calculate and PRINT and GPRINT definitions. We have to do it at
2117 * this point because it will affect the length of the legends
2118 * if there are no graph elements we stop here ...
2119 * if we are lazy, try to quit ...
2121 i=print_calc(im,calcpr);
2123 if(((i==0)&&(piechart==0)) || lazy) return 0;
2125 /* If there's only the pie chart to draw, signal this */
2126 if (i==0) piechart=2;
2128 /* get actual drawing data and find min and max values*/
2129 if(data_proc(im)==-1)
2132 if(!im->logarithmic){si_unit(im);} /* identify si magnitude Kilo, Mega Giga ? */
2134 if(!im->rigid && ! im->logarithmic)
2135 expand_range(im); /* make sure the upper and lower limit are
2138 /**************************************************************
2139 *** Calculating sizes and locations became a bit confusing ***
2140 *** so I moved this into a separate function. ***
2141 **************************************************************/
2142 if(graph_size_location(im,i,piechart)==-1)
2145 /* the actual graph is created by going through the individual
2146 graph elements and then drawing them */
2148 node=gfx_new_area ( im->canvas,
2152 im->graph_col[GRC_BACK]);
2154 gfx_add_point(node,0, im->yimg);
2156 if (piechart != 2) {
2157 node=gfx_new_area ( im->canvas,
2158 im->xorigin, im->yorigin,
2159 im->xorigin + im->xsize, im->yorigin,
2160 im->xorigin + im->xsize, im->yorigin-im->ysize,
2161 im->graph_col[GRC_CANVAS]);
2163 gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
2165 if (im->minval > 0.0)
2166 areazero = im->minval;
2167 if (im->maxval < 0.0)
2168 areazero = im->maxval;
2174 pie_part(im,im->graph_col[GRC_CANVAS],im->pie_x,im->pie_y,im->piesize*0.5,0,2*M_PI);
2177 for(i=0;i<im->gdes_c;i++){
2178 switch(im->gdes[i].gf){
2189 for (ii = 0; ii < im->xsize; ii++)
2191 if (!isnan(im->gdes[i].p_data[ii]) &&
2192 im->gdes[i].p_data[ii] > 0.0)
2194 /* generate a tick */
2195 gfx_new_line(im->canvas, im -> xorigin + ii,
2196 im -> yorigin - (im -> gdes[i].yrule * im -> ysize),
2200 im -> gdes[i].col );
2206 stack_gf = im->gdes[i].gf;
2208 /* fix data points at oo and -oo */
2209 for(ii=0;ii<im->xsize;ii++){
2210 if (isinf(im->gdes[i].p_data[ii])){
2211 if (im->gdes[i].p_data[ii] > 0) {
2212 im->gdes[i].p_data[ii] = im->maxval ;
2214 im->gdes[i].p_data[ii] = im->minval ;
2220 if (im->gdes[i].col != 0x0){
2221 /* GF_LINE and friend */
2222 if(stack_gf == GF_LINE ){
2224 for(ii=1;ii<im->xsize;ii++){
2225 if ( ! isnan(im->gdes[i].p_data[ii-1])
2226 && ! isnan(im->gdes[i].p_data[ii])){
2228 node = gfx_new_line(im->canvas,
2229 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2230 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2231 im->gdes[i].linewidth,
2234 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2243 for(ii=1;ii<im->xsize;ii++){
2245 if ( ! isnan(im->gdes[i].p_data[ii-1])
2246 && ! isnan(im->gdes[i].p_data[ii])){
2249 if (im->gdes[i].gf == GF_STACK) {
2250 ybase = ytr(im,lastgdes->p_data[ii-1]);
2252 ybase = ytr(im,areazero);
2255 node = gfx_new_area(im->canvas,
2256 ii-1+im->xorigin,ybase,
2257 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2258 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2262 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2266 if ( node != NULL && (ii+1==im->xsize || isnan(im->gdes[i].p_data[ii]) )){
2267 /* GF_AREA STACK type*/
2268 if (im->gdes[i].gf == GF_STACK ) {
2270 for (iii=ii-1;iii>area_start;iii--){
2271 gfx_add_point(node,iii+im->xorigin,ytr(im,lastgdes->p_data[iii]));
2274 gfx_add_point(node,ii+im->xorigin,ytr(im,areazero));
2279 } /* else GF_LINE */
2280 } /* if color != 0x0 */
2281 /* make sure we do not run into trouble when stacking on NaN */
2282 for(ii=0;ii<im->xsize;ii++){
2283 if (isnan(im->gdes[i].p_data[ii])) {
2286 ybase = ytr(im,lastgdes->p_data[ii-1]);
2288 if (isnan(ybase) || !lastgdes ){
2289 ybase = ytr(im,areazero);
2291 im->gdes[i].p_data[ii] = ybase;
2294 lastgdes = &(im->gdes[i]);
2297 if(isnan(im->gdes[i].yrule)) /* fetch variable */
2298 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2300 if (finite(im->gdes[i].yrule)) { /* even the fetched var can be NaN */
2301 pie_part(im,im->gdes[i].col,
2302 im->pie_x,im->pie_y,im->piesize*0.4,
2303 M_PI*2.0*PieStart/100.0,
2304 M_PI*2.0*(PieStart+im->gdes[i].yrule)/100.0);
2305 PieStart += im->gdes[i].yrule;
2314 /* grid_paint also does the text */
2317 /* the RULES are the last thing to paint ... */
2318 for(i=0;i<im->gdes_c;i++){
2320 switch(im->gdes[i].gf){
2322 if(isnan(im->gdes[i].yrule)) { /* fetch variable */
2323 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2325 if(im->gdes[i].yrule >= im->minval
2326 && im->gdes[i].yrule <= im->maxval)
2327 gfx_new_line(im->canvas,
2328 im->xorigin,ytr(im,im->gdes[i].yrule),
2329 im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2330 1.0,im->gdes[i].col);
2333 if(im->gdes[i].xrule == 0) { /* fetch variable */
2334 im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
2336 if(im->gdes[i].xrule >= im->start
2337 && im->gdes[i].xrule <= im->end)
2338 gfx_new_line(im->canvas,
2339 xtr(im,im->gdes[i].xrule),im->yorigin,
2340 xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2341 1.0,im->gdes[i].col);
2349 if (strcmp(im->graphfile,"-")==0) {
2351 /* Change translation mode for stdout to BINARY */
2352 _setmode( _fileno( stdout ), O_BINARY );
2356 if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2357 rrd_set_error("Opening '%s' for write: %s",im->graphfile,
2362 gfx_render (im->canvas,im->ximg,im->yimg,0x0,fo);
2363 if (strcmp(im->graphfile,"-") != 0)
2369 /*****************************************************
2371 *****************************************************/
2374 gdes_alloc(image_desc_t *im){
2376 long def_step = (im->end-im->start)/im->xsize;
2378 if (im->step > def_step) /* step can be increassed ... no decreassed */
2379 def_step = im->step;
2383 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2384 * sizeof(graph_desc_t)))==NULL){
2385 rrd_set_error("realloc graph_descs");
2390 im->gdes[im->gdes_c-1].step=def_step;
2391 im->gdes[im->gdes_c-1].start=im->start;
2392 im->gdes[im->gdes_c-1].end=im->end;
2393 im->gdes[im->gdes_c-1].vname[0]='\0';
2394 im->gdes[im->gdes_c-1].data=NULL;
2395 im->gdes[im->gdes_c-1].ds_namv=NULL;
2396 im->gdes[im->gdes_c-1].data_first=0;
2397 im->gdes[im->gdes_c-1].p_data=NULL;
2398 im->gdes[im->gdes_c-1].rpnp=NULL;
2399 im->gdes[im->gdes_c-1].col = 0x0;
2400 im->gdes[im->gdes_c-1].legend[0]='\0';
2401 im->gdes[im->gdes_c-1].rrd[0]='\0';
2402 im->gdes[im->gdes_c-1].ds=-1;
2403 im->gdes[im->gdes_c-1].p_data=NULL;
2407 /* copies input untill the first unescaped colon is found
2408 or until input ends. backslashes have to be escaped as well */
2410 scan_for_col(char *input, int len, char *output)
2415 input[inp] != ':' &&
2418 if (input[inp] == '\\' &&
2419 input[inp+1] != '\0' &&
2420 (input[inp+1] == '\\' ||
2421 input[inp+1] == ':')){
2422 output[outp++] = input[++inp];
2425 output[outp++] = input[inp];
2428 output[outp] = '\0';
2432 /* Some surgery done on this function, it became ridiculously big.
2434 ** - initializing now in rrd_graph_init()
2435 ** - options parsing now in rrd_graph_options()
2436 ** - script parsing now in rrd_graph_script()
2439 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize)
2446 #ifdef HAVE_SETLOCALE
2447 setlocale(LC_ALL,"");
2451 rrd_graph_init(&im);
2453 rrd_graph_options(argc,argv,&im);
2454 if (rrd_test_error()) return -1;
2456 if (strlen(argv[optind])>=MAXPATH) {
2457 rrd_set_error("filename (including path) too long");
2460 strncpy(im.graphfile,argv[optind],MAXPATH-1);
2461 im.graphfile[MAXPATH-1]='\0';
2463 rrd_graph_script(argc,argv,&im);
2464 if (rrd_test_error()) return -1;
2466 /* Everything is now read and the actual work can start */
2469 if (graph_paint(&im,prdata)==-1){
2474 /* The image is generated and needs to be output.
2475 ** Also, if needed, print a line with information about the image.
2483 /* maybe prdata is not allocated yet ... lets do it now */
2484 if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
2485 rrd_set_error("malloc imginfo");
2489 if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
2491 rrd_set_error("malloc imginfo");
2494 filename=im.graphfile+strlen(im.graphfile);
2495 while(filename > im.graphfile) {
2496 if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
2500 sprintf((*prdata)[0],im.imginfo,filename,(long)(im.canvas->zoom*im.ximg),(long)(im.canvas->zoom*im.yimg));
2507 rrd_graph_init(image_desc_t *im)
2511 im->xlab_user.minsec = -1;
2517 im->ylegend[0] = '\0';
2518 im->title[0] = '\0';
2521 im->unitsexponent= 9999;
2526 im->logarithmic = 0;
2527 im->ygridstep = DNAN;
2528 im->draw_x_grid = 1;
2529 im->draw_y_grid = 1;
2534 im->canvas = gfx_new_canvas();
2536 for(i=0;i<DIM(graph_col);i++)
2537 im->graph_col[i]=graph_col[i];
2539 for(i=0;i<DIM(text_prop);i++){
2540 im->text_prop[i].size = text_prop[i].size;
2541 im->text_prop[i].font = text_prop[i].font;
2546 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
2549 char *parsetime_error = NULL;
2550 char scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
2551 time_t start_tmp=0,end_tmp=0;
2553 struct time_value start_tv, end_tv;
2556 parsetime("end-24h", &start_tv);
2557 parsetime("now", &end_tv);
2560 static struct option long_options[] =
2562 {"start", required_argument, 0, 's'},
2563 {"end", required_argument, 0, 'e'},
2564 {"x-grid", required_argument, 0, 'x'},
2565 {"y-grid", required_argument, 0, 'y'},
2566 {"vertical-label",required_argument,0,'v'},
2567 {"width", required_argument, 0, 'w'},
2568 {"height", required_argument, 0, 'h'},
2569 {"interlaced", no_argument, 0, 'i'},
2570 {"upper-limit",required_argument, 0, 'u'},
2571 {"lower-limit",required_argument, 0, 'l'},
2572 {"rigid", no_argument, 0, 'r'},
2573 {"base", required_argument, 0, 'b'},
2574 {"logarithmic",no_argument, 0, 'o'},
2575 {"color", required_argument, 0, 'c'},
2576 {"font", required_argument, 0, 'n'},
2577 {"title", required_argument, 0, 't'},
2578 {"imginfo", required_argument, 0, 'f'},
2579 {"imgformat", required_argument, 0, 'a'},
2580 {"lazy", no_argument, 0, 'z'},
2581 {"zoom", required_argument, 0, 'm'},
2582 {"no-legend", no_argument, 0, 'g'},
2583 {"alt-y-grid", no_argument, 0, 257 },
2584 {"alt-autoscale", no_argument, 0, 258 },
2585 {"alt-autoscale-max", no_argument, 0, 259 },
2586 {"units-exponent",required_argument, 0, 260},
2587 {"step", required_argument, 0, 261},
2589 int option_index = 0;
2593 opt = getopt_long(argc, argv,
2594 "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:z:g",
2595 long_options, &option_index);
2602 im->extra_flags |= ALTYGRID;
2605 im->extra_flags |= ALTAUTOSCALE;
2608 im->extra_flags |= ALTAUTOSCALE_MAX;
2611 im->extra_flags |= NOLEGEND;
2614 im->unitsexponent = atoi(optarg);
2617 im->step = atoi(optarg);
2620 if ((parsetime_error = parsetime(optarg, &start_tv))) {
2621 rrd_set_error( "start time: %s", parsetime_error );
2626 if ((parsetime_error = parsetime(optarg, &end_tv))) {
2627 rrd_set_error( "end time: %s", parsetime_error );
2632 if(strcmp(optarg,"none") == 0){
2638 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
2640 &im->xlab_user.gridst,
2642 &im->xlab_user.mgridst,
2644 &im->xlab_user.labst,
2645 &im->xlab_user.precis,
2646 &stroff) == 7 && stroff != 0){
2647 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
2648 if((im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
2649 rrd_set_error("unknown keyword %s",scan_gtm);
2651 } else if ((im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
2652 rrd_set_error("unknown keyword %s",scan_mtm);
2654 } else if ((im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
2655 rrd_set_error("unknown keyword %s",scan_ltm);
2658 im->xlab_user.minsec = 1;
2659 im->xlab_user.stst = im->xlab_form;
2661 rrd_set_error("invalid x-grid format");
2667 if(strcmp(optarg,"none") == 0){
2675 &im->ylabfact) == 2) {
2676 if(im->ygridstep<=0){
2677 rrd_set_error("grid step must be > 0");
2679 } else if (im->ylabfact < 1){
2680 rrd_set_error("label factor must be > 0");
2684 rrd_set_error("invalid y-grid format");
2689 strncpy(im->ylegend,optarg,150);
2690 im->ylegend[150]='\0';
2693 im->maxval = atof(optarg);
2696 im->minval = atof(optarg);
2699 im->base = atol(optarg);
2700 if(im->base != 1024 && im->base != 1000 ){
2701 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
2706 long_tmp = atol(optarg);
2707 if (long_tmp < 10) {
2708 rrd_set_error("width below 10 pixels");
2711 im->xsize = long_tmp;
2714 long_tmp = atol(optarg);
2715 if (long_tmp < 10) {
2716 rrd_set_error("height below 10 pixels");
2719 im->ysize = long_tmp;
2722 im->canvas->interlaced = 1;
2728 im->imginfo = optarg;
2731 if((im->canvas->imgformat = if_conv(optarg)) == -1) {
2732 rrd_set_error("unsupported graphics format '%s'",optarg);
2740 im->logarithmic = 1;
2741 if (isnan(im->minval))
2747 col_nam,&color) == 2){
2749 if((ci=grc_conv(col_nam)) != -1){
2750 im->graph_col[ci]=color;
2752 rrd_set_error("invalid color name '%s'",col_nam);
2755 rrd_set_error("invalid color def format");
2760 /* originally this used char *prop = "" and
2761 ** char *font = "dummy" however this results
2762 ** in a SEG fault, at least on RH7.1
2764 ** The current implementation isn't proper
2765 ** either, font is never freed and prop uses
2766 ** a fixed width string
2775 prop,&size,font) == 3){
2777 if((sindex=text_prop_conv(prop)) != -1){
2778 im->text_prop[sindex].size=size;
2779 im->text_prop[sindex].font=font;
2780 if (sindex==0) { /* the default */
2781 im->text_prop[TEXT_PROP_TITLE].size=size;
2782 im->text_prop[TEXT_PROP_TITLE].font=font;
2783 im->text_prop[TEXT_PROP_AXIS].size=size;
2784 im->text_prop[TEXT_PROP_AXIS].font=font;
2785 im->text_prop[TEXT_PROP_UNIT].size=size;
2786 im->text_prop[TEXT_PROP_UNIT].font=font;
2787 im->text_prop[TEXT_PROP_LEGEND].size=size;
2788 im->text_prop[TEXT_PROP_LEGEND].font=font;
2791 rrd_set_error("invalid fonttag '%s'",prop);
2795 rrd_set_error("invalid text property format");
2801 im->canvas->zoom = atof(optarg);
2802 if (im->canvas->zoom <= 0.0) {
2803 rrd_set_error("zoom factor must be > 0");
2808 strncpy(im->title,optarg,150);
2809 im->title[150]='\0';
2814 rrd_set_error("unknown option '%c'", optopt);
2816 rrd_set_error("unknown option '%s'",argv[optind-1]);
2821 if (optind >= argc) {
2822 rrd_set_error("missing filename");
2826 if (im->logarithmic == 1 && (im->minval <= 0 || isnan(im->minval))){
2827 rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");
2831 if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
2832 /* error string is set in parsetime.c */
2836 if (start_tmp < 3600*24*365*10){
2837 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
2841 if (end_tmp < start_tmp) {
2842 rrd_set_error("start (%ld) should be less than end (%ld)",
2843 start_tmp, end_tmp);
2847 im->start = start_tmp;
2852 rrd_graph_script(int argc, char *argv[], image_desc_t *im)
2856 int linepass = 0; /* stack must follow LINE*, AREA or STACK */
2858 for (i=optind+1;i<argc;i++) {
2863 char funcname[10],vname[MAX_VNAME_LEN+1],sep[1];
2868 /* Each command is one element from *argv[], we call this "line".
2870 ** Each command defines the most current gdes inside struct im.
2871 ** In stead of typing "im->gdes[im->gdes_c-1]" we use "gdp".
2874 gdp=&im->gdes[im->gdes_c-1];
2877 /* function:newvname=string[:ds-name:CF] for xDEF
2878 ** function:vname[#color[:string]] for LINEx,AREA,STACK
2879 ** function:vname#color[:num[:string]] for TICK
2880 ** function:vname-or-num#color[:string] for xRULE,PART
2881 ** function:vname:CF:string for xPRINT
2882 ** function:string for COMMENT
2886 sscanf(line, "%10[A-Z0-9]:%n", funcname,&argstart);
2888 rrd_set_error("Cannot parse function in line: %s",line);
2892 if(sscanf(funcname,"LINE%lf",&linewidth)){
2893 im->gdes[im->gdes_c-1].gf = GF_LINE;
2894 im->gdes[im->gdes_c-1].linewidth = linewidth;
2896 if ((gdp->gf=gf_conv(funcname))==-1) {
2897 rrd_set_error("'%s' is not a valid function name",funcname);
2903 /* If the error string is set, we exit at the end of the switch */
2906 if (rrd_graph_legend(gdp,&line[argstart])==0)
2907 rrd_set_error("Cannot parse comment in line: %s",line);
2913 sscanf(&line[argstart], "%lf%n#%n", &d, &j, &k);
2914 sscanf(&line[argstart], DEF_NAM_FMT "%n#%n", vname, &l, &m);
2916 rrd_set_error("Cannot parse name or num in line: %s",line);
2923 } else if (!rrd_graph_check_vname(im,vname,line)) {
2927 } else break; /* exit due to wrong vname */
2928 if ((j=rrd_graph_color(im,&line[argstart],line,0))==0) break;
2930 if (strlen(&line[argstart])!=0) {
2931 if (rrd_graph_legend(gdp,&line[++argstart])==0)
2932 rrd_set_error("Cannot parse comment in line: %s",line);
2937 rrd_set_error("STACK must follow another graphing element");
2945 sscanf(&line[argstart],DEF_NAM_FMT"%n%1[#:]%n",vname,&j,sep,&k);
2947 rrd_set_error("Cannot parse vname in line: %s",line);
2948 else if (rrd_graph_check_vname(im,vname,line))
2949 rrd_set_error("Undefined vname '%s' in line: %s",line);
2951 k=rrd_graph_color(im,&line[argstart],line,1);
2952 if (rrd_test_error()) break;
2953 argstart=argstart+j+k;
2954 if ((strlen(&line[argstart])!=0)&&(gdp->gf==GF_TICK)) {
2956 sscanf(&line[argstart], ":%lf%n", &gdp->yrule,&j);
2959 if (strlen(&line[argstart])!=0)
2960 if (rrd_graph_legend(gdp,&line[++argstart])==0)
2961 rrd_set_error("Cannot parse legend in line: %s",line);
2967 sscanf(&line[argstart], DEF_NAM_FMT ":%n",gdp->vname,&j);
2969 rrd_set_error("Cannot parse vname in line: '%s'",line);
2973 if (rrd_graph_check_vname(im,gdp->vname,line)) return;
2975 sscanf(&line[argstart], CF_NAM_FMT ":%n",symname,&j);
2977 k=(j!=0)?rrd_graph_check_CF(im,symname,line):1;
2978 #define VIDX im->gdes[gdp->vidx]
2980 case -1: /* looks CF but is not really CF */
2981 if (VIDX.gf == GF_VDEF) rrd_clear_error();
2983 case 0: /* CF present and correct */
2984 if (VIDX.gf == GF_VDEF)
2985 rrd_set_error("Don't use CF when printing VDEF");
2988 case 1: /* CF not present */
2989 if (VIDX.gf == GF_VDEF) rrd_clear_error();
2990 else rrd_set_error("Printing DEF or CDEF needs CF");
2993 rrd_set_error("Oops, bug in GPRINT scanning");
2996 if (rrd_test_error()) break;
2998 if (strlen(&line[argstart])!=0) {
2999 if (rrd_graph_legend(gdp,&line[argstart])==0)
3000 rrd_set_error("Cannot parse legend in line: %s",line);
3001 } else rrd_set_error("No legend in (G)PRINT line: %s",line);
3002 strcpy(gdp->format, gdp->legend);
3008 sscanf(&line[argstart], DEF_NAM_FMT "=%n",gdp->vname,&j);
3010 rrd_set_error("Could not parse line: %s",line);
3013 if (find_var(im,gdp->vname)!=-1) {
3014 rrd_set_error("Variable '%s' in line '%s' already in use\n",
3021 argstart+=scan_for_col(&line[argstart],MAXPATH,gdp->rrd);
3023 sscanf(&line[argstart],
3024 ":" DS_NAM_FMT ":" CF_NAM_FMT "%n%*s%n",
3025 gdp->ds_nam, symname, &j, &k);
3026 if ((j==0)||(k!=0)) {
3027 rrd_set_error("Cannot parse DS or CF in '%s'",line);
3030 rrd_graph_check_CF(im,symname,line);
3034 sscanf(&line[argstart],DEF_NAM_FMT ",%n",vname,&j);
3036 rrd_set_error("Cannot parse vname in line '%s'",line);
3040 if (rrd_graph_check_vname(im,vname,line)) return;
3041 if ( im->gdes[gdp->vidx].gf != GF_DEF
3042 && im->gdes[gdp->vidx].gf != GF_CDEF) {
3043 rrd_set_error("variable '%s' not DEF nor "
3044 "CDEF in VDEF '%s'", vname,gdp->vname);
3047 vdef_parse(gdp,&line[argstart+strstart]);
3050 if (strstr(&line[argstart],":")!=NULL) {
3051 rrd_set_error("Error in RPN, line: %s",line);
3054 if ((gdp->rpnp = rpn_parse(
3059 rrd_set_error("invalid rpn expression in: %s",line);
3064 default: rrd_set_error("Big oops");
3066 if (rrd_test_error()) {
3073 rrd_set_error("can't make a graph without contents");
3074 im_free(im); /* ??? is this set ??? */
3079 rrd_graph_check_vname(image_desc_t *im, char *varname, char *err)
3081 if ((im->gdes[im->gdes_c-1].vidx=find_var(im,varname))==-1) {
3082 rrd_set_error("Unknown variable '%s' in %s",varname,err);
3088 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
3091 graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
3093 color=strstr(var,"#");
3096 rrd_set_error("Found no color in %s",err);
3105 rest=strstr(color,":");
3113 sscanf(color,"#%6lx%n",&col,&n);
3114 col = (col << 8) + 0xff /* shift left by 8 */;
3115 if (n!=7) rrd_set_error("Color problem in %s",err);
3118 sscanf(color,"#%8lx%n",&col,&n);
3121 rrd_set_error("Color problem in %s",err);
3123 if (rrd_test_error()) return 0;
3129 rrd_graph_check_CF(image_desc_t *im, char *symname, char *err)
3131 if ((im->gdes[im->gdes_c-1].cf=cf_conv(symname))==-1) {
3132 rrd_set_error("Unknown CF '%s' in %s",symname,err);
3138 rrd_graph_legend(graph_desc_t *gdp, char *line)
3142 i=scan_for_col(line,FMT_LEG_LEN,gdp->legend);
3144 return (strlen(&line[i])==0);
3148 int bad_format(char *fmt) {
3153 while (*ptr != '\0') {
3154 if (*ptr == '%') {ptr++;
3155 if (*ptr == '\0') return 1;
3156 while ((*ptr >= '0' && *ptr <= '9') || *ptr == '.') {
3159 if (*ptr == '\0') return 1;
3163 if (*ptr == '\0') return 1;
3164 if (*ptr == 'e' || *ptr == 'f') {
3166 } else { return 1; }
3168 else if (*ptr == 's' || *ptr == 'S' || *ptr == '%') { ++ptr; }
3177 vdef_parse(gdes,str)
3178 struct graph_desc_t *gdes;
3181 /* A VDEF currently is either "func" or "param,func"
3182 * so the parsing is rather simple. Change if needed.
3189 sscanf(str,"%le,%29[A-Z]%n",¶m,func,&n);
3190 if (n==strlen(str)) { /* matched */
3194 sscanf(str,"%29[A-Z]%n",func,&n);
3195 if (n==strlen(str)) { /* matched */
3198 rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3205 if (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3206 else if (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3207 else if (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3208 else if (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3209 else if (!strcmp("TOTAL", func)) gdes->vf.op = VDEF_TOTAL;
3210 else if (!strcmp("FIRST", func)) gdes->vf.op = VDEF_FIRST;
3211 else if (!strcmp("LAST", func)) gdes->vf.op = VDEF_LAST;
3213 rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3220 switch (gdes->vf.op) {
3222 if (isnan(param)) { /* no parameter given */
3223 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3229 if (param>=0.0 && param<=100.0) {
3230 gdes->vf.param = param;
3231 gdes->vf.val = DNAN; /* undefined */
3232 gdes->vf.when = 0; /* undefined */
3234 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3248 gdes->vf.param = DNAN;
3249 gdes->vf.val = DNAN;
3252 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3267 graph_desc_t *src,*dst;
3271 dst = &im->gdes[gdi];
3272 src = &im->gdes[dst->vidx];
3273 data = src->data + src->ds;
3274 steps = (src->end - src->start) / src->step;
3277 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3284 switch (dst->vf.op) {
3285 case VDEF_PERCENT: {
3286 rrd_value_t * array;
3290 if ((array = malloc(steps*sizeof(double)))==NULL) {
3291 rrd_set_error("malloc VDEV_PERCENT");
3294 for (step=0;step < steps; step++) {
3295 array[step]=data[step*src->ds_cnt];
3297 qsort(array,step,sizeof(double),vdef_percent_compar);
3299 field = (steps-1)*dst->vf.param/100;
3300 dst->vf.val = array[field];
3301 dst->vf.when = 0; /* no time component */
3303 for(step=0;step<steps;step++)
3304 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3310 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3311 if (step == steps) {
3315 dst->vf.val = data[step*src->ds_cnt];
3316 dst->vf.when = src->start + (step+1)*src->step;
3318 while (step != steps) {
3319 if (finite(data[step*src->ds_cnt])) {
3320 if (data[step*src->ds_cnt] > dst->vf.val) {
3321 dst->vf.val = data[step*src->ds_cnt];
3322 dst->vf.when = src->start + (step+1)*src->step;
3329 case VDEF_AVERAGE: {
3332 for (step=0;step<steps;step++) {
3333 if (finite(data[step*src->ds_cnt])) {
3334 sum += data[step*src->ds_cnt];
3339 if (dst->vf.op == VDEF_TOTAL) {
3340 dst->vf.val = sum*src->step;
3341 dst->vf.when = cnt*src->step; /* not really "when" */
3343 dst->vf.val = sum/cnt;
3344 dst->vf.when = 0; /* no time component */
3354 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3355 if (step == steps) {
3359 dst->vf.val = data[step*src->ds_cnt];
3360 dst->vf.when = src->start + (step+1)*src->step;
3362 while (step != steps) {
3363 if (finite(data[step*src->ds_cnt])) {
3364 if (data[step*src->ds_cnt] < dst->vf.val) {
3365 dst->vf.val = data[step*src->ds_cnt];
3366 dst->vf.when = src->start + (step+1)*src->step;
3373 /* The time value returned here is one step before the
3374 * actual time value. This is the start of the first
3378 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3379 if (step == steps) { /* all entries were NaN */
3383 dst->vf.val = data[step*src->ds_cnt];
3384 dst->vf.when = src->start + step*src->step;
3388 /* The time value returned here is the
3389 * actual time value. This is the end of the last
3393 while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3394 if (step < 0) { /* all entries were NaN */
3398 dst->vf.val = data[step*src->ds_cnt];
3399 dst->vf.when = src->start + (step+1)*src->step;
3406 /* NaN < -INF < finite_values < INF */
3408 vdef_percent_compar(a,b)
3411 /* Equality is not returned; this doesn't hurt except
3412 * (maybe) for a little performance.
3415 /* First catch NaN values. They are smallest */
3416 if (isnan( *(double *)a )) return -1;
3417 if (isnan( *(double *)b )) return 1;
3419 /* NaN doesn't reach this part so INF and -INF are extremes.
3420 * The sign from isinf() is compatible with the sign we return
3422 if (isinf( *(double *)a )) return isinf( *(double *)a );
3423 if (isinf( *(double *)b )) return isinf( *(double *)b );
3425 /* If we reach this, both values must be finite */
3426 if ( *(double *)a < *(double *)b ) return -1; else return 1;