1 /****************************************************************************
2 * RRDtool 1.1.x Copyright Tobias Oetiker, 1997 - 2002
3 ****************************************************************************
4 * rrd__graph.c make creates ne rrds
5 ****************************************************************************/
17 #include "rrd_graph.h"
18 #include "rrd_graph_helper.h"
20 /* some constant definitions */
23 #ifndef RRD_DEFAULT_FONT
24 #define RRD_DEFAULT_FONT "/usr/share/fonts/truetype/openoffice/ariosor.ttf"
25 /* #define RRD_DEFAULT_FONT "/usr/share/fonts/truetype/Arial.ttf" */
29 text_prop_t text_prop[] = {
30 { 10.0, RRD_DEFAULT_FONT }, /* default */
31 { 12.0, RRD_DEFAULT_FONT }, /* title */
32 { 8.0, RRD_DEFAULT_FONT }, /* axis */
33 { 10.0, RRD_DEFAULT_FONT }, /* unit */
34 { 10.0, RRD_DEFAULT_FONT } /* legend */
38 {0, TMT_SECOND,30, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
39 {2, TMT_MINUTE,1, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
40 {5, TMT_MINUTE,2, TMT_MINUTE,10, TMT_MINUTE,10, 0,"%H:%M"},
41 {10, TMT_MINUTE,5, TMT_MINUTE,20, TMT_MINUTE,20, 0,"%H:%M"},
42 {30, TMT_MINUTE,10, TMT_HOUR,1, TMT_HOUR,1, 0,"%H:%M"},
43 {60, TMT_MINUTE,30, TMT_HOUR,2, TMT_HOUR,2, 0,"%H:%M"},
44 {180, TMT_HOUR,1, TMT_HOUR,6, TMT_HOUR,6, 0,"%H:%M"},
45 /*{300, TMT_HOUR,3, TMT_HOUR,12, TMT_HOUR,12, 12*3600,"%a %p"}, this looks silly*/
46 {600, TMT_HOUR,6, TMT_DAY,1, TMT_DAY,1, 24*3600,"%a"},
47 {1800, TMT_HOUR,12, TMT_DAY,1, TMT_DAY,2, 24*3600,"%a"},
48 {3600, TMT_DAY,1, TMT_WEEK,1, TMT_WEEK,1, 7*24*3600,"Week %V"},
49 {3*3600, TMT_WEEK,1, TMT_MONTH,1, TMT_WEEK,2, 7*24*3600,"Week %V"},
50 {6*3600, TMT_MONTH,1, TMT_MONTH,1, TMT_MONTH,1, 30*24*3600,"%b"},
51 {48*3600, TMT_MONTH,1, TMT_MONTH,3, TMT_MONTH,3, 30*24*3600,"%b"},
52 {10*24*3600, TMT_YEAR,1, TMT_YEAR,1, TMT_YEAR,1, 365*24*3600,"%y"},
53 {-1,TMT_MONTH,0,TMT_MONTH,0,TMT_MONTH,0,0,""}
56 /* sensible logarithmic y label intervals ...
57 the first element of each row defines the possible starting points on the
58 y axis ... the other specify the */
60 double yloglab[][12]= {{ 1e9, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
61 { 1e3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
62 { 1e1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
63 /* { 1e1, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, */
64 { 1e1, 1, 2.5, 5, 7.5, 0, 0, 0, 0, 0, 0, 0 },
65 { 1e1, 1, 2, 4, 6, 8, 0, 0, 0, 0, 0, 0 },
66 { 1e1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0 },
67 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }};
69 /* sensible y label intervals ...*/
87 gfx_color_t graph_col[] = /* default colors */
88 { 0xFFFFFFFF, /* canvas */
89 0xF0F0F0FF, /* background */
90 0xD0D0D0FF, /* shade A */
91 0xA0A0A0FF, /* shade B */
92 0x909090FF, /* grid */
93 0xE05050FF, /* major grid */
94 0x000000FF, /* font */
95 0x000000FF, /* frame */
96 0xFF0000FF /* arrow */
103 # define DPRINT(x) (void)(printf x, printf("\n"))
109 /* initialize with xtr(im,0); */
111 xtr(image_desc_t *im,time_t mytime){
114 pixie = (double) im->xsize / (double)(im->end - im->start);
117 return (int)((double)im->xorigin
118 + pixie * ( mytime - im->start ) );
121 /* translate data values into y coordinates */
123 ytr(image_desc_t *im, double value){
128 pixie = (double) im->ysize / (im->maxval - im->minval);
130 pixie = (double) im->ysize / (log10(im->maxval) - log10(im->minval));
132 } else if(!im->logarithmic) {
133 yval = im->yorigin - pixie * (value - im->minval) + 0.5;
135 if (value < im->minval) {
138 yval = im->yorigin - pixie * (log10(value) - log10(im->minval)) + 0.5;
141 /* make sure we don't return anything too unreasonable. GD lib can
142 get terribly slow when drawing lines outside its scope. This is
143 especially problematic in connection with the rigid option */
146 } else if ((int)yval > im->yorigin) {
147 return im->yorigin+2;
148 } else if ((int) yval < im->yorigin - im->ysize){
149 return im->yorigin - im->ysize - 2;
157 /* conversion function for symbolic entry names */
160 #define conv_if(VV,VVV) \
161 if (strcmp(#VV, string) == 0) return VVV ;
163 enum gf_en gf_conv(char *string){
165 conv_if(PRINT,GF_PRINT)
166 conv_if(GPRINT,GF_GPRINT)
167 conv_if(COMMENT,GF_COMMENT)
168 conv_if(HRULE,GF_HRULE)
169 conv_if(VRULE,GF_VRULE)
170 conv_if(LINE,GF_LINE)
171 conv_if(AREA,GF_AREA)
172 conv_if(STACK,GF_STACK)
173 conv_if(TICK,GF_TICK)
175 conv_if(CDEF,GF_CDEF)
176 conv_if(VDEF,GF_VDEF)
177 conv_if(PART,GF_PART)
182 enum if_en if_conv(char *string){
189 enum tmt_en tmt_conv(char *string){
191 conv_if(SECOND,TMT_SECOND)
192 conv_if(MINUTE,TMT_MINUTE)
193 conv_if(HOUR,TMT_HOUR)
195 conv_if(WEEK,TMT_WEEK)
196 conv_if(MONTH,TMT_MONTH)
197 conv_if(YEAR,TMT_YEAR)
201 enum grc_en grc_conv(char *string){
203 conv_if(BACK,GRC_BACK)
204 conv_if(CANVAS,GRC_CANVAS)
205 conv_if(SHADEA,GRC_SHADEA)
206 conv_if(SHADEB,GRC_SHADEB)
207 conv_if(GRID,GRC_GRID)
208 conv_if(MGRID,GRC_MGRID)
209 conv_if(FONT,GRC_FONT)
210 conv_if(FRAME,GRC_FRAME)
211 conv_if(ARROW,GRC_ARROW)
216 enum text_prop_en text_prop_conv(char *string){
218 conv_if(DEFAULT,TEXT_PROP_DEFAULT)
219 conv_if(TITLE,TEXT_PROP_TITLE)
220 conv_if(AXIS,TEXT_PROP_AXIS)
221 conv_if(UNIT,TEXT_PROP_UNIT)
222 conv_if(LEGEND,TEXT_PROP_LEGEND)
232 im_free(image_desc_t *im)
235 if (im == NULL) return 0;
236 for(i=0;i<im->gdes_c;i++){
237 if (im->gdes[i].data_first){
238 /* careful here, because a single pointer can occur several times */
239 free (im->gdes[i].data);
240 if (im->gdes[i].ds_namv){
241 for (ii=0;ii<im->gdes[i].ds_cnt;ii++)
242 free(im->gdes[i].ds_namv[ii]);
243 free(im->gdes[i].ds_namv);
246 free (im->gdes[i].p_data);
247 free (im->gdes[i].rpnp);
253 /* find SI magnitude symbol for the given number*/
256 image_desc_t *im, /* image description */
263 char *symbol[] = {"a", /* 10e-18 Atto */
264 "f", /* 10e-15 Femto */
265 "p", /* 10e-12 Pico */
266 "n", /* 10e-9 Nano */
267 "u", /* 10e-6 Micro */
268 "m", /* 10e-3 Milli */
273 "T", /* 10e12 Tera */
274 "P", /* 10e15 Peta */
280 if (*value == 0.0 || isnan(*value) ) {
284 sindex = floor(log(fabs(*value))/log((double)im->base));
285 *magfact = pow((double)im->base, (double)sindex);
286 (*value) /= (*magfact);
288 if ( sindex <= symbcenter && sindex >= -symbcenter) {
289 (*symb_ptr) = symbol[sindex+symbcenter];
297 /* find SI magnitude symbol for the numbers on the y-axis*/
300 image_desc_t *im /* image description */
304 char symbol[] = {'a', /* 10e-18 Atto */
305 'f', /* 10e-15 Femto */
306 'p', /* 10e-12 Pico */
307 'n', /* 10e-9 Nano */
308 'u', /* 10e-6 Micro */
309 'm', /* 10e-3 Milli */
314 'T', /* 10e12 Tera */
315 'P', /* 10e15 Peta */
321 if (im->unitsexponent != 9999) {
322 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
323 digits = floor(im->unitsexponent / 3);
325 digits = floor( log( max( fabs(im->minval),fabs(im->maxval)))/log((double)im->base));
327 im->magfact = pow((double)im->base , digits);
330 printf("digits %6.3f im->magfact %6.3f\n",digits,im->magfact);
333 if ( ((digits+symbcenter) < sizeof(symbol)) &&
334 ((digits+symbcenter) >= 0) )
335 im->symbol = symbol[(int)digits+symbcenter];
340 /* move min and max values around to become sensible */
343 expand_range(image_desc_t *im)
345 double sensiblevalues[] ={1000.0,900.0,800.0,750.0,700.0,
346 600.0,500.0,400.0,300.0,250.0,
347 200.0,125.0,100.0,90.0,80.0,
348 75.0,70.0,60.0,50.0,40.0,30.0,
349 25.0,20.0,10.0,9.0,8.0,
350 7.0,6.0,5.0,4.0,3.5,3.0,
351 2.5,2.0,1.8,1.5,1.2,1.0,
352 0.8,0.7,0.6,0.5,0.4,0.3,0.2,0.1,0.0,-1};
354 double scaled_min,scaled_max;
361 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
362 im->minval,im->maxval,im->magfact);
365 if (isnan(im->ygridstep)){
366 if(im->extra_flags & ALTAUTOSCALE) {
367 /* measure the amplitude of the function. Make sure that
368 graph boundaries are slightly higher then max/min vals
369 so we can see amplitude on the graph */
372 delt = im->maxval - im->minval;
374 fact = 2.0 * pow(10.0,
375 floor(log10(max(fabs(im->minval), fabs(im->maxval)))) - 2);
377 adj = (fact - delt) * 0.55;
379 printf("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n", im->minval, im->maxval, delt, fact, adj);
385 else if(im->extra_flags & ALTAUTOSCALE_MAX) {
386 /* measure the amplitude of the function. Make sure that
387 graph boundaries are slightly higher than max vals
388 so we can see amplitude on the graph */
389 adj = (im->maxval - im->minval) * 0.1;
393 scaled_min = im->minval / im->magfact;
394 scaled_max = im->maxval / im->magfact;
396 for (i=1; sensiblevalues[i] > 0; i++){
397 if (sensiblevalues[i-1]>=scaled_min &&
398 sensiblevalues[i]<=scaled_min)
399 im->minval = sensiblevalues[i]*(im->magfact);
401 if (-sensiblevalues[i-1]<=scaled_min &&
402 -sensiblevalues[i]>=scaled_min)
403 im->minval = -sensiblevalues[i-1]*(im->magfact);
405 if (sensiblevalues[i-1] >= scaled_max &&
406 sensiblevalues[i] <= scaled_max)
407 im->maxval = sensiblevalues[i-1]*(im->magfact);
409 if (-sensiblevalues[i-1]<=scaled_max &&
410 -sensiblevalues[i] >=scaled_max)
411 im->maxval = -sensiblevalues[i]*(im->magfact);
415 /* adjust min and max to the grid definition if there is one */
416 im->minval = (double)im->ylabfact * im->ygridstep *
417 floor(im->minval / ((double)im->ylabfact * im->ygridstep));
418 im->maxval = (double)im->ylabfact * im->ygridstep *
419 ceil(im->maxval /( (double)im->ylabfact * im->ygridstep));
423 fprintf(stderr,"SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
424 im->minval,im->maxval,im->magfact);
429 /* reduce data reimplementation by Alex */
433 enum cf_en cf, /* which consolidation function ?*/
434 unsigned long cur_step, /* step the data currently is in */
435 time_t *start, /* start, end and step as requested ... */
436 time_t *end, /* ... by the application will be ... */
437 unsigned long *step, /* ... adjusted to represent reality */
438 unsigned long *ds_cnt, /* number of data sources in file */
439 rrd_value_t **data) /* two dimensional array containing the data */
441 int i,reduce_factor = ceil((double)(*step) / (double)cur_step);
442 unsigned long col,dst_row,row_cnt,start_offset,end_offset,skiprows=0;
443 rrd_value_t *srcptr,*dstptr;
445 (*step) = cur_step*reduce_factor; /* set new step size for reduced data */
448 row_cnt = ((*end)-(*start))/cur_step;
454 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
455 row_cnt,reduce_factor,*start,*end,cur_step);
456 for (col=0;col<row_cnt;col++) {
457 printf("time %10lu: ",*start+(col+1)*cur_step);
458 for (i=0;i<*ds_cnt;i++)
459 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
464 /* We have to combine [reduce_factor] rows of the source
465 ** into one row for the destination. Doing this we also
466 ** need to take care to combine the correct rows. First
467 ** alter the start and end time so that they are multiples
468 ** of the new step time. We cannot reduce the amount of
469 ** time so we have to move the end towards the future and
470 ** the start towards the past.
472 end_offset = (*end) % (*step);
473 start_offset = (*start) % (*step);
475 /* If there is a start offset (which cannot be more than
476 ** one destination row), skip the appropriate number of
477 ** source rows and one destination row. The appropriate
478 ** number is what we do know (start_offset/cur_step) of
479 ** the new interval (*step/cur_step aka reduce_factor).
482 printf("start_offset: %lu end_offset: %lu\n",start_offset,end_offset);
483 printf("row_cnt before: %lu\n",row_cnt);
486 (*start) = (*start)-start_offset;
487 skiprows=reduce_factor-start_offset/cur_step;
488 srcptr+=skiprows* *ds_cnt;
489 for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
493 printf("row_cnt between: %lu\n",row_cnt);
496 /* At the end we have some rows that are not going to be
497 ** used, the amount is end_offset/cur_step
500 (*end) = (*end)-end_offset+(*step);
501 skiprows = end_offset/cur_step;
505 printf("row_cnt after: %lu\n",row_cnt);
508 /* Sanity check: row_cnt should be multiple of reduce_factor */
509 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
511 if (row_cnt%reduce_factor) {
512 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
513 row_cnt,reduce_factor);
514 printf("BUG in reduce_data()\n");
518 /* Now combine reduce_factor intervals at a time
519 ** into one interval for the destination.
522 for (dst_row=0;row_cnt>=reduce_factor;dst_row++) {
523 for (col=0;col<(*ds_cnt);col++) {
524 rrd_value_t newval=DNAN;
525 unsigned long validval=0;
527 for (i=0;i<reduce_factor;i++) {
528 if (isnan(srcptr[i*(*ds_cnt)+col])) {
532 if (isnan(newval)) newval = srcptr[i*(*ds_cnt)+col];
540 newval += srcptr[i*(*ds_cnt)+col];
543 newval = min (newval,srcptr[i*(*ds_cnt)+col]);
546 /* an interval contains a failure if any subintervals contained a failure */
548 newval = max (newval,srcptr[i*(*ds_cnt)+col]);
551 newval = srcptr[i*(*ds_cnt)+col];
556 if (validval == 0){newval = DNAN;} else{
574 srcptr+=(*ds_cnt)*reduce_factor;
575 row_cnt-=reduce_factor;
577 /* If we had to alter the endtime, we didn't have enough
578 ** source rows to fill the last row. Fill it with NaN.
580 if (end_offset) for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
582 row_cnt = ((*end)-(*start))/ *step;
584 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
585 row_cnt,*start,*end,*step);
586 for (col=0;col<row_cnt;col++) {
587 printf("time %10lu: ",*start+(col+1)*(*step));
588 for (i=0;i<*ds_cnt;i++)
589 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
596 /* get the data required for the graphs from the
600 data_fetch( image_desc_t *im )
604 /* pull the data from the log files ... */
605 for (i=0;i<im->gdes_c;i++){
606 /* only GF_DEF elements fetch data */
607 if (im->gdes[i].gf != GF_DEF)
611 /* do we have it already ?*/
612 for (ii=0;ii<i;ii++){
613 if (im->gdes[ii].gf != GF_DEF)
615 if((strcmp(im->gdes[i].rrd,im->gdes[ii].rrd) == 0)
616 && (im->gdes[i].cf == im->gdes[ii].cf)){
617 /* OK the data it is here already ...
618 * we just copy the header portion */
619 im->gdes[i].start = im->gdes[ii].start;
620 im->gdes[i].end = im->gdes[ii].end;
621 im->gdes[i].step = im->gdes[ii].step;
622 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
623 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
624 im->gdes[i].data = im->gdes[ii].data;
625 im->gdes[i].data_first = 0;
632 unsigned long ft_step = im->gdes[i].step ;
634 if((rrd_fetch_fn(im->gdes[i].rrd,
640 &im->gdes[i].ds_namv,
641 &im->gdes[i].data)) == -1){
644 im->gdes[i].data_first = 1;
646 if (ft_step < im->gdes[i].step) {
647 reduce_data(im->gdes[i].cf,
655 im->gdes[i].step = ft_step;
659 /* lets see if the required data source is realy there */
660 for(ii=0;ii<im->gdes[i].ds_cnt;ii++){
661 if(strcmp(im->gdes[i].ds_namv[ii],im->gdes[i].ds_nam) == 0){
664 if (im->gdes[i].ds== -1){
665 rrd_set_error("No DS called '%s' in '%s'",
666 im->gdes[i].ds_nam,im->gdes[i].rrd);
674 /* evaluate the expressions in the CDEF functions */
676 /*************************************************************
678 *************************************************************/
681 find_var_wrapper(void *arg1, char *key)
683 return find_var((image_desc_t *) arg1, key);
686 /* find gdes containing var*/
688 find_var(image_desc_t *im, char *key){
690 for(ii=0;ii<im->gdes_c-1;ii++){
691 if((im->gdes[ii].gf == GF_DEF
692 || im->gdes[ii].gf == GF_VDEF
693 || im->gdes[ii].gf == GF_CDEF)
694 && (strcmp(im->gdes[ii].vname,key) == 0)){
701 /* find the largest common denominator for all the numbers
702 in the 0 terminated num array */
707 for (i=0;num[i+1]!=0;i++){
709 rest=num[i] % num[i+1];
710 num[i]=num[i+1]; num[i+1]=rest;
714 /* return i==0?num[i]:num[i-1]; */
718 /* run the rpn calculator on all the VDEF and CDEF arguments */
720 data_calc( image_desc_t *im){
724 long *steparray, rpi;
729 rpnstack_init(&rpnstack);
731 for (gdi=0;gdi<im->gdes_c;gdi++){
732 /* Look for GF_VDEF and GF_CDEF in the same loop,
733 * so CDEFs can use VDEFs and vice versa
735 switch (im->gdes[gdi].gf) {
737 /* A VDEF has no DS. This also signals other parts
738 * of rrdtool that this is a VDEF value, not a CDEF.
740 im->gdes[gdi].ds_cnt = 0;
741 if (vdef_calc(im,gdi)) {
742 rrd_set_error("Error processing VDEF '%s'"
745 rpnstack_free(&rpnstack);
750 im->gdes[gdi].ds_cnt = 1;
751 im->gdes[gdi].ds = 0;
752 im->gdes[gdi].data_first = 1;
753 im->gdes[gdi].start = 0;
754 im->gdes[gdi].end = 0;
759 /* Find the variables in the expression.
760 * - VDEF variables are substituted by their values
761 * and the opcode is changed into OP_NUMBER.
762 * - CDEF variables are analized for their step size,
763 * the lowest common denominator of all the step
764 * sizes of the data sources involved is calculated
765 * and the resulting number is the step size for the
766 * resulting data source.
768 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
769 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE){
770 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
771 if (im->gdes[ptr].ds_cnt == 0) {
773 printf("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
775 im->gdes[ptr].vname);
776 printf("DEBUG: value from vdef is %f\n",im->gdes[ptr].vf.val);
778 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
779 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
781 if ((steparray = rrd_realloc(steparray, (++stepcnt+1)*sizeof(*steparray)))==NULL){
782 rrd_set_error("realloc steparray");
783 rpnstack_free(&rpnstack);
787 steparray[stepcnt-1] = im->gdes[ptr].step;
789 /* adjust start and end of cdef (gdi) so
790 * that it runs from the latest start point
791 * to the earliest endpoint of any of the
792 * rras involved (ptr)
794 if(im->gdes[gdi].start < im->gdes[ptr].start)
795 im->gdes[gdi].start = im->gdes[ptr].start;
797 if(im->gdes[gdi].end == 0 ||
798 im->gdes[gdi].end > im->gdes[ptr].end)
799 im->gdes[gdi].end = im->gdes[ptr].end;
801 /* store pointer to the first element of
802 * the rra providing data for variable,
803 * further save step size and data source
806 im->gdes[gdi].rpnp[rpi].data = im->gdes[ptr].data + im->gdes[ptr].ds;
807 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
808 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
810 /* backoff the *.data ptr; this is done so
811 * rpncalc() function doesn't have to treat
812 * the first case differently
814 } /* if ds_cnt != 0 */
815 } /* if OP_VARIABLE */
816 } /* loop through all rpi */
818 /* move the data pointers to the correct period */
819 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
820 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE){
821 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
822 if(im->gdes[gdi].start > im->gdes[ptr].start) {
823 im->gdes[gdi].rpnp[rpi].data += im->gdes[gdi].rpnp[rpi].ds_cnt;
829 if(steparray == NULL){
830 rrd_set_error("rpn expressions without DEF"
831 " or CDEF variables are not supported");
832 rpnstack_free(&rpnstack);
835 steparray[stepcnt]=0;
836 /* Now find the resulting step. All steps in all
837 * used RRAs have to be visited
839 im->gdes[gdi].step = lcd(steparray);
841 if((im->gdes[gdi].data = malloc((
842 (im->gdes[gdi].end-im->gdes[gdi].start)
843 / im->gdes[gdi].step)
844 * sizeof(double)))==NULL){
845 rrd_set_error("malloc im->gdes[gdi].data");
846 rpnstack_free(&rpnstack);
850 /* Step through the new cdef results array and
851 * calculate the values
853 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
854 now<=im->gdes[gdi].end;
855 now += im->gdes[gdi].step)
857 rpnp_t *rpnp = im -> gdes[gdi].rpnp;
859 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
860 * in this case we are advancing by timesteps;
861 * we use the fact that time_t is a synonym for long
863 if (rpn_calc(rpnp,&rpnstack,(long) now,
864 im->gdes[gdi].data,++dataidx) == -1) {
865 /* rpn_calc sets the error string */
866 rpnstack_free(&rpnstack);
869 } /* enumerate over time steps within a CDEF */
874 } /* enumerate over CDEFs */
875 rpnstack_free(&rpnstack);
879 /* massage data so, that we get one value for each x coordinate in the graph */
881 data_proc( image_desc_t *im ){
883 double pixstep = (double)(im->end-im->start)
884 /(double)im->xsize; /* how much time
885 passes in one pixel */
887 double minval=DNAN,maxval=DNAN;
889 unsigned long gr_time;
891 /* memory for the processed data */
892 for(i=0;i<im->gdes_c;i++){
893 if((im->gdes[i].gf==GF_LINE) ||
894 (im->gdes[i].gf==GF_AREA) ||
895 (im->gdes[i].gf==GF_TICK) ||
896 (im->gdes[i].gf==GF_STACK)){
897 if((im->gdes[i].p_data = malloc((im->xsize +1)
898 * sizeof(rrd_value_t)))==NULL){
899 rrd_set_error("malloc data_proc");
905 for(i=0;i<im->xsize;i++){
907 gr_time = im->start+pixstep*i; /* time of the
911 for(ii=0;ii<im->gdes_c;ii++){
913 switch(im->gdes[ii].gf){
919 vidx = im->gdes[ii].vidx;
923 ((unsigned long)floor(
924 (double)(gr_time-im->gdes[vidx].start) / im->gdes[vidx].step
926 ) *im->gdes[vidx].ds_cnt
929 if (! isnan(value)) {
931 im->gdes[ii].p_data[i] = paintval;
932 /* GF_TICK: the data values are not relevant for min and max */
933 if (finite(paintval) && im->gdes[ii].gf != GF_TICK ){
934 if (isnan(minval) || paintval < minval)
936 if (isnan(maxval) || paintval > maxval)
940 im->gdes[ii].p_data[i] = DNAN;
957 /* if min or max have not been asigned a value this is because
958 there was no data in the graph ... this is not good ...
959 lets set these to dummy values then ... */
961 if (isnan(minval)) minval = 0.0;
962 if (isnan(maxval)) maxval = 1.0;
964 /* adjust min and max values */
965 if (isnan(im->minval)
966 || ((!im->logarithmic && !im->rigid) /* don't adjust low-end with log scale */
967 && im->minval > minval))
969 if (isnan(im->maxval)
971 && im->maxval < maxval)){
973 im->maxval = maxval * 1.1;
977 /* make sure min and max are not equal */
978 if (im->minval == im->maxval) {
980 if (! im->logarithmic) {
984 /* make sure min and max are not both zero */
985 if (im->maxval == 0.0) {
995 /* identify the point where the first gridline, label ... gets placed */
999 time_t start, /* what is the initial time */
1000 enum tmt_en baseint, /* what is the basic interval */
1001 long basestep /* how many if these do we jump a time */
1005 tm = *localtime(&start);
1008 tm.tm_sec -= tm.tm_sec % basestep; break;
1011 tm.tm_min -= tm.tm_min % basestep;
1016 tm.tm_hour -= tm.tm_hour % basestep; break;
1018 /* we do NOT look at the basestep for this ... */
1021 tm.tm_hour = 0; break;
1023 /* we do NOT look at the basestep for this ... */
1027 tm.tm_mday -= tm.tm_wday -1; /* -1 because we want the monday */
1028 if (tm.tm_wday==0) tm.tm_mday -= 7; /* we want the *previous* monday */
1035 tm.tm_mon -= tm.tm_mon % basestep; break;
1043 tm.tm_year -= (tm.tm_year+1900) % basestep;
1048 /* identify the point where the next gridline, label ... gets placed */
1051 time_t current, /* what is the initial time */
1052 enum tmt_en baseint, /* what is the basic interval */
1053 long basestep /* how many if these do we jump a time */
1058 tm = *localtime(¤t);
1062 tm.tm_sec += basestep; break;
1064 tm.tm_min += basestep; break;
1066 tm.tm_hour += basestep; break;
1068 tm.tm_mday += basestep; break;
1070 tm.tm_mday += 7*basestep; break;
1072 tm.tm_mon += basestep; break;
1074 tm.tm_year += basestep;
1076 madetime = mktime(&tm);
1077 } while (madetime == -1); /* this is necessary to skip impssible times
1078 like the daylight saving time skips */
1084 /* calculate values required for PRINT and GPRINT functions */
1087 print_calc(image_desc_t *im, char ***prdata)
1089 long i,ii,validsteps;
1092 int graphelement = 0;
1095 double magfact = -1;
1099 if (im->imginfo) prlines++;
1100 for(i=0;i<im->gdes_c;i++){
1101 switch(im->gdes[i].gf){
1104 if(((*prdata) = rrd_realloc((*prdata),prlines*sizeof(char *)))==NULL){
1105 rrd_set_error("realloc prdata");
1109 /* PRINT and GPRINT can now print VDEF generated values.
1110 * There's no need to do any calculations on them as these
1111 * calculations were already made.
1113 vidx = im->gdes[i].vidx;
1114 if (im->gdes[vidx].gf==GF_VDEF) { /* simply use vals */
1115 printval = im->gdes[vidx].vf.val;
1116 printtime = im->gdes[vidx].vf.when;
1117 } else { /* need to calculate max,min,avg etcetera */
1118 max_ii =((im->gdes[vidx].end
1119 - im->gdes[vidx].start)
1120 / im->gdes[vidx].step
1121 * im->gdes[vidx].ds_cnt);
1124 for( ii=im->gdes[vidx].ds;
1126 ii+=im->gdes[vidx].ds_cnt){
1127 if (! finite(im->gdes[vidx].data[ii]))
1129 if (isnan(printval)){
1130 printval = im->gdes[vidx].data[ii];
1135 switch (im->gdes[i].cf){
1138 case CF_DEVSEASONAL:
1142 printval += im->gdes[vidx].data[ii];
1145 printval = min( printval, im->gdes[vidx].data[ii]);
1149 printval = max( printval, im->gdes[vidx].data[ii]);
1152 printval = im->gdes[vidx].data[ii];
1155 if (im->gdes[i].cf==CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1156 if (validsteps > 1) {
1157 printval = (printval / validsteps);
1160 } /* prepare printval */
1162 if (!strcmp(im->gdes[i].format,"%c")) { /* VDEF time print */
1163 if (im->gdes[i].gf == GF_PRINT){
1164 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1165 sprintf((*prdata)[prlines-2],"%s (%lu)",
1166 ctime(&printtime),printtime);
1167 (*prdata)[prlines-1] = NULL;
1169 sprintf(im->gdes[i].legend,"%s (%lu)",
1170 ctime(&printtime),printtime);
1174 if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) {
1175 /* Magfact is set to -1 upon entry to print_calc. If it
1176 * is still less than 0, then we need to run auto_scale.
1177 * Otherwise, put the value into the correct units. If
1178 * the value is 0, then do not set the symbol or magnification
1179 * so next the calculation will be performed again. */
1180 if (magfact < 0.0) {
1181 auto_scale(im,&printval,&si_symb,&magfact);
1182 if (printval == 0.0)
1185 printval /= magfact;
1187 *(++percent_s) = 's';
1188 } else if (strstr(im->gdes[i].format,"%s") != NULL) {
1189 auto_scale(im,&printval,&si_symb,&magfact);
1192 if (im->gdes[i].gf == GF_PRINT){
1193 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1194 if (bad_format(im->gdes[i].format)) {
1195 rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1198 #ifdef HAVE_SNPRINTF
1199 snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
1201 sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
1203 (*prdata)[prlines-1] = NULL;
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(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
1214 sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
1236 return graphelement;
1240 /* place legends with color spots */
1242 leg_place(image_desc_t *im)
1245 int interleg = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1246 int box =im->text_prop[TEXT_PROP_LEGEND].size*1.5;
1247 int border = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1248 int fill=0, fill_last;
1250 int leg_x = border, leg_y = im->ygif;
1254 char prt_fctn; /*special printfunctions */
1257 if( !(im->extra_flags & NOLEGEND) ) {
1258 if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1259 rrd_set_error("malloc for legspace");
1263 for(i=0;i<im->gdes_c;i++){
1266 leg_cc = strlen(im->gdes[i].legend);
1268 /* is there a controle code ant the end of the legend string ? */
1269 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\') {
1270 prt_fctn = im->gdes[i].legend[leg_cc-1];
1272 im->gdes[i].legend[leg_cc] = '\0';
1276 /* remove exess space */
1277 while (prt_fctn=='g' &&
1279 im->gdes[i].legend[leg_cc-1]==' '){
1281 im->gdes[i].legend[leg_cc]='\0';
1284 legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1287 /* no interleg space if string ends in \g */
1288 fill += legspace[i];
1290 if (im->gdes[i].gf != GF_GPRINT &&
1291 im->gdes[i].gf != GF_COMMENT) {
1294 fill += gfx_get_text_width(fill+border,im->text_prop[TEXT_PROP_LEGEND].font,
1295 im->text_prop[TEXT_PROP_LEGEND].size,
1297 im->gdes[i].legend);
1302 /* who said there was a special tag ... ?*/
1303 if (prt_fctn=='g') {
1306 if (prt_fctn == '\0') {
1307 if (i == im->gdes_c -1 ) prt_fctn ='l';
1309 /* is it time to place the legends ? */
1310 if (fill > im->xgif - 2*border){
1325 if (prt_fctn != '\0'){
1327 if (leg_c >= 2 && prt_fctn == 'j') {
1328 glue = (im->xgif - fill - 2* border) / (leg_c-1);
1332 if (prt_fctn =='c') leg_x = (im->xgif - fill) / 2.0;
1333 if (prt_fctn =='r') leg_x = im->xgif - fill - border;
1335 for(ii=mark;ii<=i;ii++){
1336 if(im->gdes[ii].legend[0]=='\0')
1338 im->gdes[ii].leg_x = leg_x;
1339 im->gdes[ii].leg_y = leg_y;
1341 gfx_get_text_width(leg_x,im->text_prop[TEXT_PROP_LEGEND].font,
1342 im->text_prop[TEXT_PROP_LEGEND].size,
1344 im->gdes[ii].legend)
1347 if (im->gdes[ii].gf != GF_GPRINT &&
1348 im->gdes[ii].gf != GF_COMMENT)
1351 leg_y = leg_y + im->text_prop[TEXT_PROP_LEGEND].size*1.2;
1352 if (prt_fctn == 's') leg_y -= im->text_prop[TEXT_PROP_LEGEND].size*1.2;
1364 /* create a grid on the graph. it determines what to do
1365 from the values of xsize, start and end */
1367 /* the xaxis labels are determined from the number of seconds per pixel
1368 in the requested graph */
1373 horizontal_grid(gfx_canvas_t *canvas, image_desc_t *im)
1381 char graph_label[100];
1383 int labfact,gridind;
1384 int decimals, fractionals;
1389 range = im->maxval - im->minval;
1390 scaledrange = range / im->magfact;
1392 /* does the scale of this graph make it impossible to put lines
1393 on it? If so, give up. */
1394 if (isnan(scaledrange)) {
1398 /* find grid spaceing */
1400 if(isnan(im->ygridstep)){
1401 if(im->extra_flags & ALTYGRID) {
1402 /* find the value with max number of digits. Get number of digits */
1403 decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))));
1404 if(decimals <= 0) /* everything is small. make place for zero */
1407 fractionals = floor(log10(range));
1408 if(fractionals < 0) /* small amplitude. */
1409 sprintf(labfmt, "%%%d.%df", decimals - fractionals + 1, -fractionals + 1);
1411 sprintf(labfmt, "%%%d.1f", decimals + 1);
1412 gridstep = pow((double)10, (double)fractionals);
1413 if(gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1415 /* should have at least 5 lines but no more then 15 */
1416 if(range/gridstep < 5)
1418 if(range/gridstep > 15)
1420 if(range/gridstep > 5) {
1422 if(range/gridstep > 8)
1431 for(i=0;ylab[i].grid > 0;i++){
1432 pixel = im->ysize / (scaledrange / ylab[i].grid);
1433 if (gridind == -1 && pixel > 5) {
1440 if (pixel * ylab[gridind].lfac[i] >= 2 * im->text_prop[TEXT_PROP_AXIS].size) {
1441 labfact = ylab[gridind].lfac[i];
1446 gridstep = ylab[gridind].grid * im->magfact;
1449 gridstep = im->ygridstep;
1450 labfact = im->ylabfact;
1454 x1=im->xorigin+im->xsize;
1456 sgrid = (int)( im->minval / gridstep - 1);
1457 egrid = (int)( im->maxval / gridstep + 1);
1458 scaledstep = gridstep/im->magfact;
1459 for (i = sgrid; i <= egrid; i++){
1460 y0=ytr(im,gridstep*i);
1461 if ( y0 >= im->yorigin-im->ysize
1462 && y0 <= im->yorigin){
1463 if(i % labfact == 0){
1464 if (i==0 || im->symbol == ' ') {
1466 if(im->extra_flags & ALTYGRID) {
1467 sprintf(graph_label,labfmt,scaledstep*i);
1470 sprintf(graph_label,"%4.1f",scaledstep*i);
1473 sprintf(graph_label,"%4.0f",scaledstep*i);
1477 sprintf(graph_label,"%4.1f %c",scaledstep*i, im->symbol);
1479 sprintf(graph_label,"%4.0f %c",scaledstep*i, im->symbol);
1483 gfx_new_text ( canvas,
1484 x0-im->text_prop[TEXT_PROP_AXIS].size/1.5, y0,
1485 im->graph_col[GRC_FONT],
1486 im->text_prop[TEXT_PROP_AXIS].font,
1487 im->text_prop[TEXT_PROP_AXIS].size,
1488 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1490 gfx_new_line ( canvas,
1493 MGRIDWIDTH, im->graph_col[GRC_MGRID] );
1496 gfx_new_line ( canvas,
1499 GRIDWIDTH, im->graph_col[GRC_GRID] );
1507 /* logaritmic horizontal grid */
1509 horizontal_log_grid(gfx_canvas_t *canvas, image_desc_t *im)
1513 int minoridx=0, majoridx=0;
1514 char graph_label[100];
1516 double value, pixperstep, minstep;
1518 /* find grid spaceing */
1519 pixpex= (double)im->ysize / (log10(im->maxval) - log10(im->minval));
1521 if (isnan(pixpex)) {
1525 for(i=0;yloglab[i][0] > 0;i++){
1526 minstep = log10(yloglab[i][0]);
1527 for(ii=1;yloglab[i][ii+1] > 0;ii++){
1528 if(yloglab[i][ii+2]==0){
1529 minstep = log10(yloglab[i][ii+1])-log10(yloglab[i][ii]);
1533 pixperstep = pixpex * minstep;
1534 if(pixperstep > 5){minoridx = i;}
1535 if(pixperstep > 2 * im->text_prop[TEXT_PROP_LEGEND].size){majoridx = i;}
1539 x1=im->xorigin+im->xsize;
1540 /* paint minor grid */
1541 for (value = pow((double)10, log10(im->minval)
1542 - fmod(log10(im->minval),log10(yloglab[minoridx][0])));
1543 value <= im->maxval;
1544 value *= yloglab[minoridx][0]){
1545 if (value < im->minval) continue;
1547 while(yloglab[minoridx][++i] > 0){
1548 y0 = ytr(im,value * yloglab[minoridx][i]);
1549 if (y0 <= im->yorigin - im->ysize) break;
1550 gfx_new_line ( canvas,
1553 GRIDWIDTH, im->graph_col[GRC_GRID] );
1557 /* paint major grid and labels*/
1558 for (value = pow((double)10, log10(im->minval)
1559 - fmod(log10(im->minval),log10(yloglab[majoridx][0])));
1560 value <= im->maxval;
1561 value *= yloglab[majoridx][0]){
1562 if (value < im->minval) continue;
1564 while(yloglab[majoridx][++i] > 0){
1565 y0 = ytr(im,value * yloglab[majoridx][i]);
1566 if (y0 <= im->yorigin - im->ysize) break;
1567 gfx_new_line ( canvas,
1570 MGRIDWIDTH, im->graph_col[GRC_MGRID] );
1572 sprintf(graph_label,"%3.0e",value * yloglab[majoridx][i]);
1573 gfx_new_text ( canvas,
1574 x0-im->text_prop[TEXT_PROP_AXIS].size/1.5, y0,
1575 im->graph_col[GRC_FONT],
1576 im->text_prop[TEXT_PROP_AXIS].font,
1577 im->text_prop[TEXT_PROP_AXIS].size,
1578 im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1588 gfx_canvas_t *canvas,
1591 int xlab_sel; /* which sort of label and grid ? */
1594 char graph_label[100];
1595 double x0,y0,y1; /* points for filled graph and more*/
1598 /* the type of time grid is determined by finding
1599 the number of seconds per pixel in the graph */
1602 if(im->xlab_user.minsec == -1){
1603 factor=(im->end - im->start)/im->xsize;
1605 while ( xlab[xlab_sel+1].minsec != -1
1606 && xlab[xlab_sel+1].minsec <= factor){ xlab_sel++; }
1607 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
1608 im->xlab_user.gridst = xlab[xlab_sel].gridst;
1609 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
1610 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
1611 im->xlab_user.labtm = xlab[xlab_sel].labtm;
1612 im->xlab_user.labst = xlab[xlab_sel].labst;
1613 im->xlab_user.precis = xlab[xlab_sel].precis;
1614 im->xlab_user.stst = xlab[xlab_sel].stst;
1617 /* y coords are the same for every line ... */
1619 y1 = im->yorigin-im->ysize;
1622 /* paint the minor grid */
1623 for(ti = find_first_time(im->start,
1624 im->xlab_user.gridtm,
1625 im->xlab_user.gridst);
1627 ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
1629 /* are we inside the graph ? */
1630 if (ti < im->start || ti > im->end) continue;
1632 gfx_new_line(canvas,x0,y0+1, x0,y1-1,GRIDWIDTH, im->graph_col[GRC_GRID]);
1636 /* paint the major grid */
1637 for(ti = find_first_time(im->start,
1638 im->xlab_user.mgridtm,
1639 im->xlab_user.mgridst);
1641 ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
1643 /* are we inside the graph ? */
1644 if (ti < im->start || ti > im->end) continue;
1646 gfx_new_line(canvas,x0,y0+2, x0,y1-2,MGRIDWIDTH, im->graph_col[GRC_MGRID]);
1649 /* paint the labels below the graph */
1650 for(ti = find_first_time(im->start,
1651 im->xlab_user.labtm,
1652 im->xlab_user.labst);
1654 ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
1656 tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
1657 /* are we inside the graph ? */
1658 if (ti < im->start || ti > im->end) continue;
1661 strftime(graph_label,99,im->xlab_user.stst,localtime(&tilab));
1663 # error "your libc has no strftime I guess we'll abort the exercise here."
1665 gfx_new_text ( canvas,
1666 xtr(im,tilab), y0+im->text_prop[TEXT_PROP_AXIS].size/1.5,
1667 im->graph_col[GRC_FONT],
1668 im->text_prop[TEXT_PROP_AXIS].font,
1669 im->text_prop[TEXT_PROP_AXIS].size,
1670 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP,
1681 gfx_canvas_t *canvas
1684 /* draw x and y axis */
1685 gfx_new_line ( canvas, im->xorigin+im->xsize,im->yorigin,
1686 im->xorigin+im->xsize,im->yorigin-im->ysize,
1687 GRIDWIDTH, im->graph_col[GRC_GRID]);
1689 gfx_new_line ( canvas, im->xorigin,im->yorigin-im->ysize,
1690 im->xorigin+im->xsize,im->yorigin-im->ysize,
1691 GRIDWIDTH, im->graph_col[GRC_GRID]);
1693 gfx_new_line ( canvas, im->xorigin-4,im->yorigin,
1694 im->xorigin+im->xsize+4,im->yorigin,
1695 MGRIDWIDTH, im->graph_col[GRC_GRID]);
1697 gfx_new_line ( canvas, im->xorigin,im->yorigin+4,
1698 im->xorigin,im->yorigin-im->ysize-4,
1699 MGRIDWIDTH, im->graph_col[GRC_GRID]);
1702 /* arrow for X axis direction */
1703 gfx_new_area ( canvas,
1704 im->xorigin+im->xsize+4, im->yorigin-3,
1705 im->xorigin+im->xsize+4, im->yorigin+3,
1706 im->xorigin+im->xsize+9, im->yorigin,
1707 im->graph_col[GRC_ARROW]);
1716 gfx_canvas_t *canvas
1722 double x0,x1,x2,x3,y0,y1,y2,y3; /* points for filled graph and more*/
1725 /* draw 3d border */
1726 node = gfx_new_area (canvas, 0,im->ygif,
1728 2,2,im->graph_col[GRC_SHADEA]);
1729 gfx_add_point( node , im->xgif - 2, 2 );
1730 gfx_add_point( node , im->xgif, 0 );
1731 gfx_add_point( node , 0,0 );
1732 /* gfx_add_point( node , 0,im->ygif ); */
1734 node = gfx_new_area (canvas, 2,im->ygif-2,
1735 im->xgif-2,im->ygif-2,
1737 im->graph_col[GRC_SHADEB]);
1738 gfx_add_point( node , im->xgif,0);
1739 gfx_add_point( node , im->xgif,im->ygif);
1740 gfx_add_point( node , 0,im->ygif);
1741 /* gfx_add_point( node , 0,im->ygif ); */
1744 if (im->draw_x_grid == 1 )
1745 vertical_grid(canvas, im);
1747 if (im->draw_y_grid == 1){
1748 if(im->logarithmic){
1749 res = horizontal_log_grid(canvas,im);
1751 res = horizontal_grid(canvas,im);
1754 /* dont draw horizontal grid if there is no min and max val */
1756 char *nodata = "No Data found";
1757 gfx_new_text(canvas,im->xgif/2, (2*im->yorigin-im->ysize) / 2,
1758 im->graph_col[GRC_FONT],
1759 im->text_prop[TEXT_PROP_AXIS].font,
1760 im->text_prop[TEXT_PROP_AXIS].size,
1761 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
1766 /* yaxis description */
1768 gfx_new_text( canvas,
1769 7, (im->yorigin - im->ysize/2),
1770 im->graph_col[GRC_FONT],
1771 im->text_prop[TEXT_PROP_AXIS].font,
1772 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 270.0,
1773 GFX_H_CENTER, GFX_V_CENTER,
1776 /* horrible hack until we can actually print vertically */
1779 int l=strlen(im->ylegend);
1781 for (n=0;n<strlen(im->ylegend);n++) {
1782 s[0]=im->ylegend[n];
1784 gfx_new_text(canvas,7,im->text_prop[TEXT_PROP_AXIS].size*(l-n),
1785 im->graph_col[GRC_FONT],
1786 im->text_prop[TEXT_PROP_AXIS].font,
1787 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 270.0,
1788 GFX_H_CENTER, GFX_V_CENTER,
1795 gfx_new_text( canvas,
1796 im->xgif/2, im->text_prop[TEXT_PROP_TITLE].size,
1797 im->graph_col[GRC_FONT],
1798 im->text_prop[TEXT_PROP_TITLE].font,
1799 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
1800 GFX_H_CENTER, GFX_V_CENTER,
1804 if( !(im->extra_flags & NOLEGEND) ) {
1805 for(i=0;i<im->gdes_c;i++){
1806 if(im->gdes[i].legend[0] =='\0')
1809 /* im->gdes[i].leg_y is the bottom of the legend */
1810 x0 = im->gdes[i].leg_x;
1811 y0 = im->gdes[i].leg_y;
1813 if ( im->gdes[i].gf != GF_GPRINT
1814 && im->gdes[i].gf != GF_COMMENT) {
1817 boxH = gfx_get_text_width(0,
1818 im->text_prop[TEXT_PROP_AXIS].font,
1819 im->text_prop[TEXT_PROP_AXIS].size,
1820 im->tabwidth,"M") * 1.25;
1823 node = gfx_new_area(canvas,
1828 gfx_add_point ( node, x0+boxH, y0-boxV );
1829 node = gfx_new_line(canvas,
1832 gfx_add_point(node,x0+boxH,y0);
1833 gfx_add_point(node,x0+boxH,y0-boxV);
1834 gfx_add_point(node,x0,y0-boxV);
1835 x0 += boxH / 1.25 * 2;
1837 gfx_new_text ( canvas, x0, y0,
1838 im->graph_col[GRC_FONT],
1839 im->text_prop[TEXT_PROP_AXIS].font,
1840 im->text_prop[TEXT_PROP_AXIS].size,
1841 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
1842 im->gdes[i].legend );
1848 /*****************************************************
1849 * lazy check make sure we rely need to create this graph
1850 *****************************************************/
1852 int lazy_check(image_desc_t *im){
1855 struct stat gifstat;
1857 if (im->lazy == 0) return 0; /* no lazy option */
1858 if (stat(im->graphfile,&gifstat) != 0)
1859 return 0; /* can't stat */
1860 /* one pixel in the existing graph is more then what we would
1862 if (time(NULL) - gifstat.st_mtime >
1863 (im->end - im->start) / im->xsize)
1865 if ((fd = fopen(im->graphfile,"rb")) == NULL)
1866 return 0; /* the file does not exist */
1867 switch (im->imgformat) {
1869 size = PngSize(fd,&(im->xgif),&(im->ygif));
1877 pie_part(gfx_canvas_t *canvas, gfx_color_t color,
1878 double PieCenterX, double PieCenterY, double Radius,
1879 double startangle, double endangle)
1883 double step=M_PI/50; /* Number of iterations for the circle;
1884 ** 10 is definitely too low, more than
1885 ** 50 seems to be overkill
1888 /* Strange but true: we have to work clockwise or else
1889 ** anti aliasing nor transparency don't work.
1891 ** This test is here to make sure we do it right, also
1892 ** this makes the for...next loop more easy to implement.
1893 ** The return will occur if the user enters a negative number
1894 ** (which shouldn't be done according to the specs) or if the
1895 ** programmers do something wrong (which, as we all know, never
1896 ** happens anyway :)
1898 if (endangle<startangle) return;
1900 /* Hidden feature: Radius decreases each full circle */
1902 while (angle>=2*M_PI) {
1907 node=gfx_new_area(canvas,
1908 PieCenterX+sin(startangle)*Radius,
1909 PieCenterY-cos(startangle)*Radius,
1912 PieCenterX+sin(endangle)*Radius,
1913 PieCenterY-cos(endangle)*Radius,
1915 for (angle=endangle;angle-startangle>=step;angle-=step) {
1917 PieCenterX+sin(angle)*Radius,
1918 PieCenterY-cos(angle)*Radius );
1923 graph_size_location(image_desc_t *im, int elements, int piechart )
1925 /* The actual size of the image to draw is determined from
1926 ** several sources. The size given on the command line is
1927 ** the graph area but we need more as we have to draw labels
1928 ** and other things outside the graph area
1931 /* +-+-------------------------------------------+
1932 ** |l|.................title.....................|
1933 ** |e+--+-------------------------------+--------+
1936 ** |l| l| main graph area | chart |
1939 ** |r+--+-------------------------------+--------+
1940 ** |e| | x-axis labels | |
1941 ** |v+--+-------------------------------+--------+
1942 ** | |..............legends......................|
1943 ** +-+-------------------------------------------+
1945 int Xvertical=0, Yvertical=0,
1946 Xtitle =0, Ytitle =0,
1947 Xylabel =0, Yylabel =0,
1950 Xxlabel =0, Yxlabel =0,
1951 Xlegend =0, Ylegend =0,
1952 Xspacing =10, Yspacing =10;
1954 if (im->ylegend[0] != '\0') {
1955 Xvertical = im->text_prop[TEXT_PROP_LEGEND].size *2;
1956 Yvertical = im->text_prop[TEXT_PROP_LEGEND].size * (strlen(im->ylegend)+1);
1959 if (im->title[0] != '\0') {
1960 /* The title is placed "inbetween" two text lines so it
1961 ** automatically has some vertical spacing. The horizontal
1962 ** spacing is added here, on each side.
1964 Xtitle = gfx_get_text_width(0,
1965 im->text_prop[TEXT_PROP_TITLE].font,
1966 im->text_prop[TEXT_PROP_TITLE].size,
1968 im->title) + 2*Xspacing;
1969 Ytitle = im->text_prop[TEXT_PROP_TITLE].size*2;
1975 if (im->draw_x_grid) {
1977 Yxlabel=im->text_prop[TEXT_PROP_LEGEND].size *2;
1979 if (im->draw_y_grid) {
1980 Xylabel=im->text_prop[TEXT_PROP_LEGEND].size *6;
1986 im->piesize=im->xsize<im->ysize?im->xsize:im->ysize;
1991 /* Now calculate the total size. Insert some spacing where
1992 desired. im->xorigin and im->yorigin need to correspond
1993 with the lower left corner of the main graph area or, if
1994 this one is not set, the imaginary box surrounding the
1997 /* The legend width cannot yet be determined, as a result we
1998 ** have problems adjusting the image to it. For now, we just
1999 ** forget about it at all; the legend will have to fit in the
2000 ** size already allocated.
2002 im->xgif = Xylabel + Xmain + Xpie + Xspacing;
2003 if (Xmain) im->xgif += Xspacing;
2004 if (Xpie) im->xgif += Xspacing;
2005 im->xorigin = Xspacing + Xylabel;
2006 if (Xtitle > im->xgif) im->xgif = Xtitle;
2008 im->xgif += Xvertical;
2009 im->xorigin += Xvertical;
2013 /* The vertical size is interesting... we need to compare
2014 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend} with Yvertical
2015 ** however we need to know {Ytitle+Ymain+Yxlabel} in order to
2016 ** start even thinking about Ylegend.
2018 ** Do it in three portions: First calculate the inner part,
2019 ** then do the legend, then adjust the total height of the img.
2022 /* reserve space for main and/or pie */
2023 im->ygif = Ymain + Yxlabel;
2024 if (im->ygif < Ypie) im->ygif = Ypie;
2025 im->yorigin = im->ygif - Yxlabel;
2026 /* reserve space for the title *or* some padding above the graph */
2029 im->yorigin += Ytitle;
2031 im->ygif += Yspacing;
2032 im->yorigin += Yspacing;
2034 /* reserve space for padding below the graph */
2035 im->ygif += Yspacing;
2038 /* Determine where to place the legends onto the image.
2039 ** Adjust im->ygif to match the space requirements.
2041 if(leg_place(im)==-1)
2044 /* last of three steps: check total height of image */
2045 if (im->ygif < Yvertical) im->ygif = Yvertical;
2048 if (Xlegend > im->xgif) {
2050 /* reposition Pie */
2053 /* The pie is placed in the upper right hand corner,
2054 ** just below the title (if any) and with sufficient
2057 im->pie_x = im->xgif - Xspacing - Xpie/2;
2058 im->pie_y = im->yorigin-Ymain+Ypie/2;
2063 /* draw that picture thing ... */
2065 graph_paint(image_desc_t *im, char ***calcpr)
2068 int lazy = lazy_check(im);
2070 double PieStart=0.0;
2072 gfx_canvas_t *canvas;
2075 double areazero = 0.0;
2076 enum gf_en stack_gf = GF_PRINT;
2077 graph_desc_t *lastgdes = NULL;
2079 /* if we are lazy and there is nothing to PRINT ... quit now */
2080 if (lazy && im->prt_c==0) return 0;
2082 /* pull the data from the rrd files ... */
2084 if(data_fetch(im)==-1)
2087 /* evaluate VDEF and CDEF operations ... */
2088 if(data_calc(im)==-1)
2091 /* check if we need to draw a piechart */
2092 for(i=0;i<im->gdes_c;i++){
2093 if (im->gdes[i].gf == GF_PART) {
2099 /* calculate and PRINT and GPRINT definitions. We have to do it at
2100 * this point because it will affect the length of the legends
2101 * if there are no graph elements we stop here ...
2102 * if we are lazy, try to quit ...
2104 i=print_calc(im,calcpr);
2106 if(((i==0)&&(piechart==0)) || lazy) return 0;
2108 /* If there's only the pie chart to draw, signal this */
2109 if (i==0) piechart=2;
2111 /* get actual drawing data and find min and max values*/
2112 if(data_proc(im)==-1)
2115 if(!im->logarithmic){si_unit(im);} /* identify si magnitude Kilo, Mega Giga ? */
2117 if(!im->rigid && ! im->logarithmic)
2118 expand_range(im); /* make sure the upper and lower limit are
2121 /**************************************************************
2122 *** Calculating sizes and locations became a bit confusing ***
2123 *** so I moved this into a separate function. ***
2124 **************************************************************/
2125 if(graph_size_location(im,i,piechart)==-1)
2128 canvas=gfx_new_canvas();
2130 /* the actual graph is created by going through the individual
2131 graph elements and then drawing them */
2133 node=gfx_new_area ( canvas,
2137 im->graph_col[GRC_BACK]);
2139 gfx_add_point(node,0, im->ygif);
2141 if (piechart != 2) {
2142 node=gfx_new_area ( canvas,
2143 im->xorigin, im->yorigin,
2144 im->xorigin + im->xsize, im->yorigin,
2145 im->xorigin + im->xsize, im->yorigin-im->ysize,
2146 im->graph_col[GRC_CANVAS]);
2148 gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
2150 if (im->minval > 0.0)
2151 areazero = im->minval;
2152 if (im->maxval < 0.0)
2153 areazero = im->maxval;
2155 axis_paint(im,canvas);
2159 pie_part(canvas,im->graph_col[GRC_CANVAS],im->pie_x,im->pie_y,im->piesize*0.5,0,2*M_PI);
2162 for(i=0;i<im->gdes_c;i++){
2163 switch(im->gdes[i].gf){
2174 for (ii = 0; ii < im->xsize; ii++)
2176 if (!isnan(im->gdes[i].p_data[ii]) &&
2177 im->gdes[i].p_data[ii] > 0.0)
2179 /* generate a tick */
2180 gfx_new_line(canvas, im -> xorigin + ii,
2181 im -> yorigin - (im -> gdes[i].yrule * im -> ysize),
2185 im -> gdes[i].col );
2191 stack_gf = im->gdes[i].gf;
2193 /* fix data points at oo and -oo */
2194 for(ii=0;ii<im->xsize;ii++){
2195 if (isinf(im->gdes[i].p_data[ii])){
2196 if (im->gdes[i].p_data[ii] > 0) {
2197 im->gdes[i].p_data[ii] = im->maxval ;
2199 im->gdes[i].p_data[ii] = im->minval ;
2205 if (im->gdes[i].col != 0x0){
2206 /* GF_LINE and friend */
2207 if(stack_gf == GF_LINE ){
2209 for(ii=1;ii<im->xsize;ii++){
2210 if ( ! isnan(im->gdes[i].p_data[ii-1])
2211 && ! isnan(im->gdes[i].p_data[ii])){
2213 node = gfx_new_line(canvas,
2214 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2215 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2216 im->gdes[i].linewidth,
2219 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2228 for(ii=1;ii<im->xsize;ii++){
2230 if ( ! isnan(im->gdes[i].p_data[ii-1])
2231 && ! isnan(im->gdes[i].p_data[ii])){
2234 if (im->gdes[i].gf == GF_STACK) {
2235 ybase = ytr(im,lastgdes->p_data[ii-1]);
2237 ybase = ytr(im,areazero);
2240 node = gfx_new_area(canvas,
2241 ii-1+im->xorigin,ybase,
2242 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2243 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2247 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2251 if ( node != NULL && (ii+1==im->xsize || isnan(im->gdes[i].p_data[ii]) )){
2252 /* GF_AREA STACK type*/
2253 if (im->gdes[i].gf == GF_STACK ) {
2255 for (iii=ii-1;iii>area_start;iii--){
2256 gfx_add_point(node,iii+im->xorigin,ytr(im,lastgdes->p_data[iii]));
2259 gfx_add_point(node,ii+im->xorigin,ytr(im,areazero));
2264 } /* else GF_LINE */
2265 } /* if color != 0x0 */
2266 /* make sure we do not run into trouble when stacking on NaN */
2267 for(ii=0;ii<im->xsize;ii++){
2268 if (isnan(im->gdes[i].p_data[ii])) {
2271 ybase = ytr(im,lastgdes->p_data[ii-1]);
2273 if (isnan(ybase) || !lastgdes ){
2274 ybase = ytr(im,areazero);
2276 im->gdes[i].p_data[ii] = ybase;
2279 lastgdes = &(im->gdes[i]);
2282 if(isnan(im->gdes[i].yrule)) /* fetch variable */
2283 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2285 if (finite(im->gdes[i].yrule)) { /* even the fetched var can be NaN */
2286 pie_part(canvas,im->gdes[i].col,
2287 im->pie_x,im->pie_y,im->piesize*0.4,
2288 M_PI*2.0*PieStart/100.0,
2289 M_PI*2.0*(PieStart+im->gdes[i].yrule)/100.0);
2290 PieStart += im->gdes[i].yrule;
2299 /* grid_paint also does the text */
2300 grid_paint(im,canvas);
2302 /* the RULES are the last thing to paint ... */
2303 for(i=0;i<im->gdes_c;i++){
2305 switch(im->gdes[i].gf){
2307 if(isnan(im->gdes[i].yrule)) { /* fetch variable */
2308 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2310 if(im->gdes[i].yrule >= im->minval
2311 && im->gdes[i].yrule <= im->maxval)
2312 gfx_new_line(canvas,
2313 im->xorigin,ytr(im,im->gdes[i].yrule),
2314 im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2315 1.0,im->gdes[i].col);
2318 if(im->gdes[i].xrule == 0) { /* fetch variable */
2319 im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
2321 if(im->gdes[i].xrule >= im->start
2322 && im->gdes[i].xrule <= im->end)
2323 gfx_new_line(canvas,
2324 xtr(im,im->gdes[i].xrule),im->yorigin,
2325 xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2326 1.0,im->gdes[i].col);
2334 if (strcmp(im->graphfile,"-")==0) {
2336 /* Change translation mode for stdout to BINARY */
2337 _setmode( _fileno( stdout ), O_BINARY );
2341 if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2342 rrd_set_error("Opening '%s' for write: %s",im->graphfile,
2347 switch (im->imgformat) {
2349 gfx_render_png (canvas,im->xgif,im->ygif,im->zoom,0x0,fo);
2352 if (strcmp(im->graphfile,"-") != 0)
2355 gfx_destroy(canvas);
2360 /*****************************************************
2362 *****************************************************/
2365 gdes_alloc(image_desc_t *im){
2367 long def_step = (im->end-im->start)/im->xsize;
2369 if (im->step > def_step) /* step can be increassed ... no decreassed */
2370 def_step = im->step;
2374 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2375 * sizeof(graph_desc_t)))==NULL){
2376 rrd_set_error("realloc graph_descs");
2381 im->gdes[im->gdes_c-1].step=def_step;
2382 im->gdes[im->gdes_c-1].start=im->start;
2383 im->gdes[im->gdes_c-1].end=im->end;
2384 im->gdes[im->gdes_c-1].vname[0]='\0';
2385 im->gdes[im->gdes_c-1].data=NULL;
2386 im->gdes[im->gdes_c-1].ds_namv=NULL;
2387 im->gdes[im->gdes_c-1].data_first=0;
2388 im->gdes[im->gdes_c-1].p_data=NULL;
2389 im->gdes[im->gdes_c-1].rpnp=NULL;
2390 im->gdes[im->gdes_c-1].col = 0x0;
2391 im->gdes[im->gdes_c-1].legend[0]='\0';
2392 im->gdes[im->gdes_c-1].rrd[0]='\0';
2393 im->gdes[im->gdes_c-1].ds=-1;
2394 im->gdes[im->gdes_c-1].p_data=NULL;
2398 /* copies input untill the first unescaped colon is found
2399 or until input ends. backslashes have to be escaped as well */
2401 scan_for_col(char *input, int len, char *output)
2406 input[inp] != ':' &&
2409 if (input[inp] == '\\' &&
2410 input[inp+1] != '\0' &&
2411 (input[inp+1] == '\\' ||
2412 input[inp+1] == ':')){
2413 output[outp++] = input[++inp];
2416 output[outp++] = input[inp];
2419 output[outp] = '\0';
2423 /* Some surgery done on this function, it became ridiculously big.
2425 ** - initializing now in rrd_graph_init()
2426 ** - options parsing now in rrd_graph_options()
2427 ** - script parsing now in rrd_graph_script()
2430 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize)
2434 rrd_graph_init(&im);
2436 rrd_graph_options(argc,argv,&im);
2437 if (rrd_test_error()) return -1;
2439 if (strlen(argv[optind])>=MAXPATH) {
2440 rrd_set_error("filename (including path) too long");
2443 strncpy(im.graphfile,argv[optind],MAXPATH-1);
2444 im.graphfile[MAXPATH-1]='\0';
2446 rrd_graph_script(argc,argv,&im);
2447 if (rrd_test_error()) return -1;
2449 /* Everything is now read and the actual work can start */
2452 if (graph_paint(&im,prdata)==-1){
2457 /* The image is generated and needs to be output.
2458 ** Also, if needed, print a line with information about the image.
2466 /* maybe prdata is not allocated yet ... lets do it now */
2467 if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
2468 rrd_set_error("malloc imginfo");
2472 if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
2474 rrd_set_error("malloc imginfo");
2477 filename=im.graphfile+strlen(im.graphfile);
2478 while(filename > im.graphfile) {
2479 if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
2483 sprintf((*prdata)[0],im.imginfo,filename,(long)(im.zoom*im.xgif),(long)(im.zoom*im.ygif));
2490 rrd_graph_init(image_desc_t *im)
2494 im->xlab_user.minsec = -1;
2500 im->ylegend[0] = '\0';
2501 im->title[0] = '\0';
2505 im->unitsexponent= 9999;
2510 im->logarithmic = 0;
2511 im->ygridstep = DNAN;
2512 im->draw_x_grid = 1;
2513 im->draw_y_grid = 1;
2519 im->imgformat = IF_PNG; /* we default to PNG output */
2521 for(i=0;i<DIM(graph_col);i++)
2522 im->graph_col[i]=graph_col[i];
2524 for(i=0;i<DIM(text_prop);i++){
2525 im->text_prop[i].size = text_prop[i].size;
2526 im->text_prop[i].font = text_prop[i].font;
2531 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
2534 char *parsetime_error = NULL;
2535 char scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
2536 time_t start_tmp=0,end_tmp=0;
2538 struct time_value start_tv, end_tv;
2541 parsetime("end-24h", &start_tv);
2542 parsetime("now", &end_tv);
2545 static struct option long_options[] =
2547 {"start", required_argument, 0, 's'},
2548 {"end", required_argument, 0, 'e'},
2549 {"x-grid", required_argument, 0, 'x'},
2550 {"y-grid", required_argument, 0, 'y'},
2551 {"vertical-label",required_argument,0,'v'},
2552 {"width", required_argument, 0, 'w'},
2553 {"height", required_argument, 0, 'h'},
2554 {"interlaced", no_argument, 0, 'i'},
2555 {"upper-limit",required_argument, 0, 'u'},
2556 {"lower-limit",required_argument, 0, 'l'},
2557 {"rigid", no_argument, 0, 'r'},
2558 {"base", required_argument, 0, 'b'},
2559 {"logarithmic",no_argument, 0, 'o'},
2560 {"color", required_argument, 0, 'c'},
2561 {"font", required_argument, 0, 'n'},
2562 {"title", required_argument, 0, 't'},
2563 {"imginfo", required_argument, 0, 'f'},
2564 {"imgformat", required_argument, 0, 'a'},
2565 {"lazy", no_argument, 0, 'z'},
2566 {"zoom", required_argument, 0, 'm'},
2567 {"no-legend", no_argument, 0, 'g'},
2568 {"alt-y-grid", no_argument, 0, 257 },
2569 {"alt-autoscale", no_argument, 0, 258 },
2570 {"alt-autoscale-max", no_argument, 0, 259 },
2571 {"units-exponent",required_argument, 0, 260},
2572 {"step", required_argument, 0, 261},
2574 int option_index = 0;
2578 opt = getopt_long(argc, argv,
2579 "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:z:g",
2580 long_options, &option_index);
2587 im->extra_flags |= ALTYGRID;
2590 im->extra_flags |= ALTAUTOSCALE;
2593 im->extra_flags |= ALTAUTOSCALE_MAX;
2596 im->extra_flags |= NOLEGEND;
2599 im->unitsexponent = atoi(optarg);
2602 im->step = atoi(optarg);
2605 if ((parsetime_error = parsetime(optarg, &start_tv))) {
2606 rrd_set_error( "start time: %s", parsetime_error );
2611 if ((parsetime_error = parsetime(optarg, &end_tv))) {
2612 rrd_set_error( "end time: %s", parsetime_error );
2617 if(strcmp(optarg,"none") == 0){
2623 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
2625 &im->xlab_user.gridst,
2627 &im->xlab_user.mgridst,
2629 &im->xlab_user.labst,
2630 &im->xlab_user.precis,
2631 &stroff) == 7 && stroff != 0){
2632 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
2633 if((im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
2634 rrd_set_error("unknown keyword %s",scan_gtm);
2636 } else if ((im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
2637 rrd_set_error("unknown keyword %s",scan_mtm);
2639 } else if ((im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
2640 rrd_set_error("unknown keyword %s",scan_ltm);
2643 im->xlab_user.minsec = 1;
2644 im->xlab_user.stst = im->xlab_form;
2646 rrd_set_error("invalid x-grid format");
2652 if(strcmp(optarg,"none") == 0){
2660 &im->ylabfact) == 2) {
2661 if(im->ygridstep<=0){
2662 rrd_set_error("grid step must be > 0");
2664 } else if (im->ylabfact < 1){
2665 rrd_set_error("label factor must be > 0");
2669 rrd_set_error("invalid y-grid format");
2674 strncpy(im->ylegend,optarg,150);
2675 im->ylegend[150]='\0';
2678 im->maxval = atof(optarg);
2681 im->minval = atof(optarg);
2684 im->base = atol(optarg);
2685 if(im->base != 1024 && im->base != 1000 ){
2686 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
2691 long_tmp = atol(optarg);
2692 if (long_tmp < 10) {
2693 rrd_set_error("width below 10 pixels");
2696 im->xsize = long_tmp;
2699 long_tmp = atol(optarg);
2700 if (long_tmp < 10) {
2701 rrd_set_error("height below 10 pixels");
2704 im->ysize = long_tmp;
2713 im->imginfo = optarg;
2716 if((im->imgformat = if_conv(optarg)) == -1) {
2717 rrd_set_error("unsupported graphics format '%s'",optarg);
2725 im->logarithmic = 1;
2726 if (isnan(im->minval))
2732 col_nam,&color) == 2){
2734 if((ci=grc_conv(col_nam)) != -1){
2735 im->graph_col[ci]=color;
2737 rrd_set_error("invalid color name '%s'",col_nam);
2740 rrd_set_error("invalid color def format");
2745 /* originally this used char *prop = "" and
2746 ** char *font = "dummy" however this results
2747 ** in a SEG fault, at least on RH7.1
2749 ** The current implementation isn't proper
2750 ** either, font is never freed and prop uses
2751 ** a fixed width string
2760 prop,&size,font) == 3){
2762 if((sindex=text_prop_conv(prop)) != -1){
2763 im->text_prop[sindex].size=size;
2764 im->text_prop[sindex].font=font;
2765 if (sindex==0) { /* the default */
2766 im->text_prop[TEXT_PROP_TITLE].size=size;
2767 im->text_prop[TEXT_PROP_TITLE].font=font;
2768 im->text_prop[TEXT_PROP_AXIS].size=size;
2769 im->text_prop[TEXT_PROP_AXIS].font=font;
2770 im->text_prop[TEXT_PROP_UNIT].size=size;
2771 im->text_prop[TEXT_PROP_UNIT].font=font;
2772 im->text_prop[TEXT_PROP_LEGEND].size=size;
2773 im->text_prop[TEXT_PROP_LEGEND].font=font;
2776 rrd_set_error("invalid fonttag '%s'",prop);
2780 rrd_set_error("invalid text property format");
2786 im->zoom= atof(optarg);
2787 if (im->zoom <= 0.0) {
2788 rrd_set_error("zoom factor must be > 0");
2793 strncpy(im->title,optarg,150);
2794 im->title[150]='\0';
2799 rrd_set_error("unknown option '%c'", optopt);
2801 rrd_set_error("unknown option '%s'",argv[optind-1]);
2806 if (optind >= argc) {
2807 rrd_set_error("missing filename");
2811 if (im->logarithmic == 1 && (im->minval <= 0 || isnan(im->minval))){
2812 rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");
2816 if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
2817 /* error string is set in parsetime.c */
2821 if (start_tmp < 3600*24*365*10){
2822 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
2826 if (end_tmp < start_tmp) {
2827 rrd_set_error("start (%ld) should be less than end (%ld)",
2828 start_tmp, end_tmp);
2832 im->start = start_tmp;
2837 rrd_graph_script(int argc, char *argv[], image_desc_t *im)
2841 int linepass = 0; /* stack must follow LINE*, AREA or STACK */
2843 for (i=optind+1;i<argc;i++) {
2848 char funcname[10],vname[MAX_VNAME_LEN+1],sep[1];
2853 /* Each command is one element from *argv[], we call this "line".
2855 ** Each command defines the most current gdes inside struct im.
2856 ** In stead of typing "im->gdes[im->gdes_c-1]" we use "gdp".
2859 gdp=&im->gdes[im->gdes_c-1];
2862 /* function:newvname=string[:ds-name:CF] for xDEF
2863 ** function:vname[#color[:string]] for LINEx,AREA,STACK
2864 ** function:vname#color[:num[:string]] for TICK
2865 ** function:vname-or-num#color[:string] for xRULE,PART
2866 ** function:vname:CF:string for xPRINT
2867 ** function:string for COMMENT
2871 sscanf(line, "%10[A-Z0-9]:%n", funcname,&argstart);
2873 rrd_set_error("Cannot parse function in line: %s",line);
2877 if(sscanf(funcname,"LINE%lf",&linewidth)){
2878 im->gdes[im->gdes_c-1].gf = GF_LINE;
2879 im->gdes[im->gdes_c-1].linewidth = linewidth;
2881 if ((gdp->gf=gf_conv(funcname))==-1) {
2882 rrd_set_error("'%s' is not a valid function name",funcname);
2888 /* If the error string is set, we exit at the end of the switch */
2891 if (rrd_graph_legend(gdp,&line[argstart])==0)
2892 rrd_set_error("Cannot parse comment in line: %s",line);
2898 sscanf(&line[argstart], "%lf%n#%n", &d, &j, &k);
2899 sscanf(&line[argstart], DEF_NAM_FMT "%n#%n", vname, &l, &m);
2901 rrd_set_error("Cannot parse name or num in line: %s",line);
2908 } else if (!rrd_graph_check_vname(im,vname,line)) {
2912 } else break; /* exit due to wrong vname */
2913 if ((j=rrd_graph_color(im,&line[argstart],line,0))==0) break;
2915 if (strlen(&line[argstart])!=0) {
2916 if (rrd_graph_legend(gdp,&line[++argstart])==0)
2917 rrd_set_error("Cannot parse comment in line: %s",line);
2922 rrd_set_error("STACK must follow another graphing element");
2930 sscanf(&line[argstart],DEF_NAM_FMT"%n%1[#:]%n",vname,&j,sep,&k);
2932 rrd_set_error("Cannot parse vname in line: %s",line);
2933 else if (rrd_graph_check_vname(im,vname,line))
2934 rrd_set_error("Undefined vname '%s' in line: %s",line);
2936 k=rrd_graph_color(im,&line[argstart],line,1);
2937 if (rrd_test_error()) break;
2938 argstart=argstart+j+k;
2939 if ((strlen(&line[argstart])!=0)&&(gdp->gf==GF_TICK)) {
2941 sscanf(&line[argstart], ":%lf%n", &gdp->yrule,&j);
2944 if (strlen(&line[argstart])!=0)
2945 if (rrd_graph_legend(gdp,&line[++argstart])==0)
2946 rrd_set_error("Cannot parse legend in line: %s",line);
2952 sscanf(&line[argstart], DEF_NAM_FMT ":%n",gdp->vname,&j);
2954 rrd_set_error("Cannot parse vname in line: '%s'",line);
2958 if (rrd_graph_check_vname(im,gdp->vname,line)) return;
2960 sscanf(&line[argstart], CF_NAM_FMT ":%n",symname,&j);
2962 k=(j!=0)?rrd_graph_check_CF(im,symname,line):1;
2963 #define VIDX im->gdes[gdp->vidx]
2965 case -1: /* looks CF but is not really CF */
2966 if (VIDX.gf == GF_VDEF) rrd_clear_error();
2968 case 0: /* CF present and correct */
2969 if (VIDX.gf == GF_VDEF)
2970 rrd_set_error("Don't use CF when printing VDEF");
2973 case 1: /* CF not present */
2974 if (VIDX.gf == GF_VDEF) rrd_clear_error();
2975 else rrd_set_error("Printing DEF or CDEF needs CF");
2978 rrd_set_error("Oops, bug in GPRINT scanning");
2981 if (rrd_test_error()) break;
2983 if (strlen(&line[argstart])!=0) {
2984 if (rrd_graph_legend(gdp,&line[argstart])==0)
2985 rrd_set_error("Cannot parse legend in line: %s",line);
2986 } else rrd_set_error("No legend in (G)PRINT line: %s",line);
2987 strcpy(gdp->format, gdp->legend);
2993 sscanf(&line[argstart], DEF_NAM_FMT "=%n",gdp->vname,&j);
2995 rrd_set_error("Could not parse line: %s",line);
2998 if (find_var(im,gdp->vname)!=-1) {
2999 rrd_set_error("Variable '%s' in line '%s' already in use\n",
3006 argstart+=scan_for_col(&line[argstart],MAXPATH,gdp->rrd);
3008 sscanf(&line[argstart],
3009 ":" DS_NAM_FMT ":" CF_NAM_FMT "%n%*s%n",
3010 gdp->ds_nam, symname, &j, &k);
3011 if ((j==0)||(k!=0)) {
3012 rrd_set_error("Cannot parse DS or CF in '%s'",line);
3015 rrd_graph_check_CF(im,symname,line);
3019 sscanf(&line[argstart],DEF_NAM_FMT ",%n",vname,&j);
3021 rrd_set_error("Cannot parse vname in line '%s'",line);
3025 if (rrd_graph_check_vname(im,vname,line)) return;
3026 if ( im->gdes[gdp->vidx].gf != GF_DEF
3027 && im->gdes[gdp->vidx].gf != GF_CDEF) {
3028 rrd_set_error("variable '%s' not DEF nor "
3029 "CDEF in VDEF '%s'", vname,gdp->vname);
3032 vdef_parse(gdp,&line[argstart+strstart]);
3035 if (strstr(&line[argstart],":")!=NULL) {
3036 rrd_set_error("Error in RPN, line: %s",line);
3039 if ((gdp->rpnp = rpn_parse(
3044 rrd_set_error("invalid rpn expression in: %s",line);
3049 default: rrd_set_error("Big oops");
3051 if (rrd_test_error()) {
3058 rrd_set_error("can't make a graph without contents");
3059 im_free(im); /* ??? is this set ??? */
3064 rrd_graph_check_vname(image_desc_t *im, char *varname, char *err)
3066 if ((im->gdes[im->gdes_c-1].vidx=find_var(im,varname))==-1) {
3067 rrd_set_error("Unknown variable '%s' in %s",varname,err);
3073 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
3076 graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
3078 color=strstr(var,"#");
3081 rrd_set_error("Found no color in %s",err);
3090 rest=strstr(color,":");
3098 sscanf(color,"#%6x%n",&col,&n);
3099 col = (col << 8) + 0xff /* shift left by 8 */;
3100 if (n!=7) rrd_set_error("Color problem in %s",err);
3103 sscanf(color,"#%8x%n",&col,&n);
3106 rrd_set_error("Color problem in %s",err);
3108 if (rrd_test_error()) return 0;
3114 rrd_graph_check_CF(image_desc_t *im, char *symname, char *err)
3116 if ((im->gdes[im->gdes_c-1].cf=cf_conv(symname))==-1) {
3117 rrd_set_error("Unknown CF '%s' in %s",symname,err);
3123 rrd_graph_legend(graph_desc_t *gdp, char *line)
3127 i=scan_for_col(line,FMT_LEG_LEN,gdp->legend);
3129 return (strlen(&line[i])==0);
3133 int bad_format(char *fmt) {
3138 while (*ptr != '\0') {
3139 if (*ptr == '%') {ptr++;
3140 if (*ptr == '\0') return 1;
3141 while ((*ptr >= '0' && *ptr <= '9') || *ptr == '.') {
3144 if (*ptr == '\0') return 1;
3148 if (*ptr == '\0') return 1;
3149 if (*ptr == 'e' || *ptr == 'f') {
3151 } else { return 1; }
3153 else if (*ptr == 's' || *ptr == 'S' || *ptr == '%') { ++ptr; }
3162 vdef_parse(gdes,str)
3163 struct graph_desc_t *gdes;
3166 /* A VDEF currently is either "func" or "param,func"
3167 * so the parsing is rather simple. Change if needed.
3174 sscanf(str,"%le,%29[A-Z]%n",¶m,func,&n);
3175 if (n==strlen(str)) { /* matched */
3179 sscanf(str,"%29[A-Z]%n",func,&n);
3180 if (n==strlen(str)) { /* matched */
3183 rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3190 if (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3191 else if (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3192 else if (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3193 else if (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3194 else if (!strcmp("TOTAL", func)) gdes->vf.op = VDEF_TOTAL;
3195 else if (!strcmp("FIRST", func)) gdes->vf.op = VDEF_FIRST;
3196 else if (!strcmp("LAST", func)) gdes->vf.op = VDEF_LAST;
3198 rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3205 switch (gdes->vf.op) {
3207 if (isnan(param)) { /* no parameter given */
3208 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3214 if (param>=0.0 && param<=100.0) {
3215 gdes->vf.param = param;
3216 gdes->vf.val = DNAN; /* undefined */
3217 gdes->vf.when = 0; /* undefined */
3219 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3233 gdes->vf.param = DNAN;
3234 gdes->vf.val = DNAN;
3237 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3252 graph_desc_t *src,*dst;
3256 dst = &im->gdes[gdi];
3257 src = &im->gdes[dst->vidx];
3258 data = src->data + src->ds;
3259 steps = (src->end - src->start) / src->step;
3262 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3269 switch (dst->vf.op) {
3270 case VDEF_PERCENT: {
3271 rrd_value_t * array;
3275 if ((array = malloc(steps*sizeof(double)))==NULL) {
3276 rrd_set_error("malloc VDEV_PERCENT");
3279 for (step=0;step < steps; step++) {
3280 array[step]=data[step*src->ds_cnt];
3282 qsort(array,step,sizeof(double),vdef_percent_compar);
3284 field = (steps-1)*dst->vf.param/100;
3285 dst->vf.val = array[field];
3286 dst->vf.when = 0; /* no time component */
3288 for(step=0;step<steps;step++)
3289 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3295 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3296 if (step == steps) {
3300 dst->vf.val = data[step*src->ds_cnt];
3301 dst->vf.when = src->start + (step+1)*src->step;
3303 while (step != steps) {
3304 if (finite(data[step*src->ds_cnt])) {
3305 if (data[step*src->ds_cnt] > dst->vf.val) {
3306 dst->vf.val = data[step*src->ds_cnt];
3307 dst->vf.when = src->start + (step+1)*src->step;
3314 case VDEF_AVERAGE: {
3317 for (step=0;step<steps;step++) {
3318 if (finite(data[step*src->ds_cnt])) {
3319 sum += data[step*src->ds_cnt];
3324 if (dst->vf.op == VDEF_TOTAL) {
3325 dst->vf.val = sum*src->step;
3326 dst->vf.when = cnt*src->step; /* not really "when" */
3328 dst->vf.val = sum/cnt;
3329 dst->vf.when = 0; /* no time component */
3339 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3340 if (step == steps) {
3344 dst->vf.val = data[step*src->ds_cnt];
3345 dst->vf.when = src->start + (step+1)*src->step;
3347 while (step != steps) {
3348 if (finite(data[step*src->ds_cnt])) {
3349 if (data[step*src->ds_cnt] < dst->vf.val) {
3350 dst->vf.val = data[step*src->ds_cnt];
3351 dst->vf.when = src->start + (step+1)*src->step;
3358 /* The time value returned here is one step before the
3359 * actual time value. This is the start of the first
3363 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3364 if (step == steps) { /* all entries were NaN */
3368 dst->vf.val = data[step*src->ds_cnt];
3369 dst->vf.when = src->start + step*src->step;
3373 /* The time value returned here is the
3374 * actual time value. This is the end of the last
3378 while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3379 if (step < 0) { /* all entries were NaN */
3383 dst->vf.val = data[step*src->ds_cnt];
3384 dst->vf.when = src->start + (step+1)*src->step;
3391 /* NaN < -INF < finite_values < INF */
3393 vdef_percent_compar(a,b)
3396 /* Equality is not returned; this doesn't hurt except
3397 * (maybe) for a little performance.
3400 /* First catch NaN values. They are smallest */
3401 if (isnan( *(double *)a )) return -1;
3402 if (isnan( *(double *)b )) return 1;
3404 /* NaN doesn't reach this part so INF and -INF are extremes.
3405 * The sign from isinf() is compatible with the sign we return
3407 if (isinf( *(double *)a )) return isinf( *(double *)a );
3408 if (isinf( *(double *)b )) return isinf( *(double *)b );
3410 /* If we reach this, both values must be finite */
3411 if ( *(double *)a < *(double *)b ) return -1; else return 1;