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){
190 enum tmt_en tmt_conv(char *string){
192 conv_if(SECOND,TMT_SECOND)
193 conv_if(MINUTE,TMT_MINUTE)
194 conv_if(HOUR,TMT_HOUR)
196 conv_if(WEEK,TMT_WEEK)
197 conv_if(MONTH,TMT_MONTH)
198 conv_if(YEAR,TMT_YEAR)
202 enum grc_en grc_conv(char *string){
204 conv_if(BACK,GRC_BACK)
205 conv_if(CANVAS,GRC_CANVAS)
206 conv_if(SHADEA,GRC_SHADEA)
207 conv_if(SHADEB,GRC_SHADEB)
208 conv_if(GRID,GRC_GRID)
209 conv_if(MGRID,GRC_MGRID)
210 conv_if(FONT,GRC_FONT)
211 conv_if(FRAME,GRC_FRAME)
212 conv_if(ARROW,GRC_ARROW)
217 enum text_prop_en text_prop_conv(char *string){
219 conv_if(DEFAULT,TEXT_PROP_DEFAULT)
220 conv_if(TITLE,TEXT_PROP_TITLE)
221 conv_if(AXIS,TEXT_PROP_AXIS)
222 conv_if(UNIT,TEXT_PROP_UNIT)
223 conv_if(LEGEND,TEXT_PROP_LEGEND)
233 im_free(image_desc_t *im)
236 if (im == NULL) return 0;
237 for(i=0;i<im->gdes_c;i++){
238 if (im->gdes[i].data_first){
239 /* careful here, because a single pointer can occur several times */
240 free (im->gdes[i].data);
241 if (im->gdes[i].ds_namv){
242 for (ii=0;ii<im->gdes[i].ds_cnt;ii++)
243 free(im->gdes[i].ds_namv[ii]);
244 free(im->gdes[i].ds_namv);
247 free (im->gdes[i].p_data);
248 free (im->gdes[i].rpnp);
254 /* find SI magnitude symbol for the given number*/
257 image_desc_t *im, /* image description */
264 char *symbol[] = {"a", /* 10e-18 Atto */
265 "f", /* 10e-15 Femto */
266 "p", /* 10e-12 Pico */
267 "n", /* 10e-9 Nano */
268 "u", /* 10e-6 Micro */
269 "m", /* 10e-3 Milli */
274 "T", /* 10e12 Tera */
275 "P", /* 10e15 Peta */
281 if (*value == 0.0 || isnan(*value) ) {
285 sindex = floor(log(fabs(*value))/log((double)im->base));
286 *magfact = pow((double)im->base, (double)sindex);
287 (*value) /= (*magfact);
289 if ( sindex <= symbcenter && sindex >= -symbcenter) {
290 (*symb_ptr) = symbol[sindex+symbcenter];
298 /* find SI magnitude symbol for the numbers on the y-axis*/
301 image_desc_t *im /* image description */
305 char symbol[] = {'a', /* 10e-18 Atto */
306 'f', /* 10e-15 Femto */
307 'p', /* 10e-12 Pico */
308 'n', /* 10e-9 Nano */
309 'u', /* 10e-6 Micro */
310 'm', /* 10e-3 Milli */
315 'T', /* 10e12 Tera */
316 'P', /* 10e15 Peta */
322 if (im->unitsexponent != 9999) {
323 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
324 digits = floor(im->unitsexponent / 3);
326 digits = floor( log( max( fabs(im->minval),fabs(im->maxval)))/log((double)im->base));
328 im->magfact = pow((double)im->base , digits);
331 printf("digits %6.3f im->magfact %6.3f\n",digits,im->magfact);
334 if ( ((digits+symbcenter) < sizeof(symbol)) &&
335 ((digits+symbcenter) >= 0) )
336 im->symbol = symbol[(int)digits+symbcenter];
341 /* move min and max values around to become sensible */
344 expand_range(image_desc_t *im)
346 double sensiblevalues[] ={1000.0,900.0,800.0,750.0,700.0,
347 600.0,500.0,400.0,300.0,250.0,
348 200.0,125.0,100.0,90.0,80.0,
349 75.0,70.0,60.0,50.0,40.0,30.0,
350 25.0,20.0,10.0,9.0,8.0,
351 7.0,6.0,5.0,4.0,3.5,3.0,
352 2.5,2.0,1.8,1.5,1.2,1.0,
353 0.8,0.7,0.6,0.5,0.4,0.3,0.2,0.1,0.0,-1};
355 double scaled_min,scaled_max;
362 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
363 im->minval,im->maxval,im->magfact);
366 if (isnan(im->ygridstep)){
367 if(im->extra_flags & ALTAUTOSCALE) {
368 /* measure the amplitude of the function. Make sure that
369 graph boundaries are slightly higher then max/min vals
370 so we can see amplitude on the graph */
373 delt = im->maxval - im->minval;
375 fact = 2.0 * pow(10.0,
376 floor(log10(max(fabs(im->minval), fabs(im->maxval)))) - 2);
378 adj = (fact - delt) * 0.55;
380 printf("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n", im->minval, im->maxval, delt, fact, adj);
386 else if(im->extra_flags & ALTAUTOSCALE_MAX) {
387 /* measure the amplitude of the function. Make sure that
388 graph boundaries are slightly higher than max vals
389 so we can see amplitude on the graph */
390 adj = (im->maxval - im->minval) * 0.1;
394 scaled_min = im->minval / im->magfact;
395 scaled_max = im->maxval / im->magfact;
397 for (i=1; sensiblevalues[i] > 0; i++){
398 if (sensiblevalues[i-1]>=scaled_min &&
399 sensiblevalues[i]<=scaled_min)
400 im->minval = sensiblevalues[i]*(im->magfact);
402 if (-sensiblevalues[i-1]<=scaled_min &&
403 -sensiblevalues[i]>=scaled_min)
404 im->minval = -sensiblevalues[i-1]*(im->magfact);
406 if (sensiblevalues[i-1] >= scaled_max &&
407 sensiblevalues[i] <= scaled_max)
408 im->maxval = sensiblevalues[i-1]*(im->magfact);
410 if (-sensiblevalues[i-1]<=scaled_max &&
411 -sensiblevalues[i] >=scaled_max)
412 im->maxval = -sensiblevalues[i]*(im->magfact);
416 /* adjust min and max to the grid definition if there is one */
417 im->minval = (double)im->ylabfact * im->ygridstep *
418 floor(im->minval / ((double)im->ylabfact * im->ygridstep));
419 im->maxval = (double)im->ylabfact * im->ygridstep *
420 ceil(im->maxval /( (double)im->ylabfact * im->ygridstep));
424 fprintf(stderr,"SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
425 im->minval,im->maxval,im->magfact);
430 /* reduce data reimplementation by Alex */
434 enum cf_en cf, /* which consolidation function ?*/
435 unsigned long cur_step, /* step the data currently is in */
436 time_t *start, /* start, end and step as requested ... */
437 time_t *end, /* ... by the application will be ... */
438 unsigned long *step, /* ... adjusted to represent reality */
439 unsigned long *ds_cnt, /* number of data sources in file */
440 rrd_value_t **data) /* two dimensional array containing the data */
442 int i,reduce_factor = ceil((double)(*step) / (double)cur_step);
443 unsigned long col,dst_row,row_cnt,start_offset,end_offset,skiprows=0;
444 rrd_value_t *srcptr,*dstptr;
446 (*step) = cur_step*reduce_factor; /* set new step size for reduced data */
449 row_cnt = ((*end)-(*start))/cur_step;
455 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
456 row_cnt,reduce_factor,*start,*end,cur_step);
457 for (col=0;col<row_cnt;col++) {
458 printf("time %10lu: ",*start+(col+1)*cur_step);
459 for (i=0;i<*ds_cnt;i++)
460 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
465 /* We have to combine [reduce_factor] rows of the source
466 ** into one row for the destination. Doing this we also
467 ** need to take care to combine the correct rows. First
468 ** alter the start and end time so that they are multiples
469 ** of the new step time. We cannot reduce the amount of
470 ** time so we have to move the end towards the future and
471 ** the start towards the past.
473 end_offset = (*end) % (*step);
474 start_offset = (*start) % (*step);
476 /* If there is a start offset (which cannot be more than
477 ** one destination row), skip the appropriate number of
478 ** source rows and one destination row. The appropriate
479 ** number is what we do know (start_offset/cur_step) of
480 ** the new interval (*step/cur_step aka reduce_factor).
483 printf("start_offset: %lu end_offset: %lu\n",start_offset,end_offset);
484 printf("row_cnt before: %lu\n",row_cnt);
487 (*start) = (*start)-start_offset;
488 skiprows=reduce_factor-start_offset/cur_step;
489 srcptr+=skiprows* *ds_cnt;
490 for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
494 printf("row_cnt between: %lu\n",row_cnt);
497 /* At the end we have some rows that are not going to be
498 ** used, the amount is end_offset/cur_step
501 (*end) = (*end)-end_offset+(*step);
502 skiprows = end_offset/cur_step;
506 printf("row_cnt after: %lu\n",row_cnt);
509 /* Sanity check: row_cnt should be multiple of reduce_factor */
510 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
512 if (row_cnt%reduce_factor) {
513 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
514 row_cnt,reduce_factor);
515 printf("BUG in reduce_data()\n");
519 /* Now combine reduce_factor intervals at a time
520 ** into one interval for the destination.
523 for (dst_row=0;row_cnt>=reduce_factor;dst_row++) {
524 for (col=0;col<(*ds_cnt);col++) {
525 rrd_value_t newval=DNAN;
526 unsigned long validval=0;
528 for (i=0;i<reduce_factor;i++) {
529 if (isnan(srcptr[i*(*ds_cnt)+col])) {
533 if (isnan(newval)) newval = srcptr[i*(*ds_cnt)+col];
541 newval += srcptr[i*(*ds_cnt)+col];
544 newval = min (newval,srcptr[i*(*ds_cnt)+col]);
547 /* an interval contains a failure if any subintervals contained a failure */
549 newval = max (newval,srcptr[i*(*ds_cnt)+col]);
552 newval = srcptr[i*(*ds_cnt)+col];
557 if (validval == 0){newval = DNAN;} else{
575 srcptr+=(*ds_cnt)*reduce_factor;
576 row_cnt-=reduce_factor;
578 /* If we had to alter the endtime, we didn't have enough
579 ** source rows to fill the last row. Fill it with NaN.
581 if (end_offset) for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
583 row_cnt = ((*end)-(*start))/ *step;
585 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
586 row_cnt,*start,*end,*step);
587 for (col=0;col<row_cnt;col++) {
588 printf("time %10lu: ",*start+(col+1)*(*step));
589 for (i=0;i<*ds_cnt;i++)
590 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
597 /* get the data required for the graphs from the
601 data_fetch( image_desc_t *im )
605 /* pull the data from the log files ... */
606 for (i=0;i<im->gdes_c;i++){
607 /* only GF_DEF elements fetch data */
608 if (im->gdes[i].gf != GF_DEF)
612 /* do we have it already ?*/
613 for (ii=0;ii<i;ii++){
614 if (im->gdes[ii].gf != GF_DEF)
616 if((strcmp(im->gdes[i].rrd,im->gdes[ii].rrd) == 0)
617 && (im->gdes[i].cf == im->gdes[ii].cf)){
618 /* OK the data it is here already ...
619 * we just copy the header portion */
620 im->gdes[i].start = im->gdes[ii].start;
621 im->gdes[i].end = im->gdes[ii].end;
622 im->gdes[i].step = im->gdes[ii].step;
623 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
624 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
625 im->gdes[i].data = im->gdes[ii].data;
626 im->gdes[i].data_first = 0;
633 unsigned long ft_step = im->gdes[i].step ;
635 if((rrd_fetch_fn(im->gdes[i].rrd,
641 &im->gdes[i].ds_namv,
642 &im->gdes[i].data)) == -1){
645 im->gdes[i].data_first = 1;
647 if (ft_step < im->gdes[i].step) {
648 reduce_data(im->gdes[i].cf,
656 im->gdes[i].step = ft_step;
660 /* lets see if the required data source is realy there */
661 for(ii=0;ii<im->gdes[i].ds_cnt;ii++){
662 if(strcmp(im->gdes[i].ds_namv[ii],im->gdes[i].ds_nam) == 0){
665 if (im->gdes[i].ds== -1){
666 rrd_set_error("No DS called '%s' in '%s'",
667 im->gdes[i].ds_nam,im->gdes[i].rrd);
675 /* evaluate the expressions in the CDEF functions */
677 /*************************************************************
679 *************************************************************/
682 find_var_wrapper(void *arg1, char *key)
684 return find_var((image_desc_t *) arg1, key);
687 /* find gdes containing var*/
689 find_var(image_desc_t *im, char *key){
691 for(ii=0;ii<im->gdes_c-1;ii++){
692 if((im->gdes[ii].gf == GF_DEF
693 || im->gdes[ii].gf == GF_VDEF
694 || im->gdes[ii].gf == GF_CDEF)
695 && (strcmp(im->gdes[ii].vname,key) == 0)){
702 /* find the largest common denominator for all the numbers
703 in the 0 terminated num array */
708 for (i=0;num[i+1]!=0;i++){
710 rest=num[i] % num[i+1];
711 num[i]=num[i+1]; num[i+1]=rest;
715 /* return i==0?num[i]:num[i-1]; */
719 /* run the rpn calculator on all the VDEF and CDEF arguments */
721 data_calc( image_desc_t *im){
725 long *steparray, rpi;
730 rpnstack_init(&rpnstack);
732 for (gdi=0;gdi<im->gdes_c;gdi++){
733 /* Look for GF_VDEF and GF_CDEF in the same loop,
734 * so CDEFs can use VDEFs and vice versa
736 switch (im->gdes[gdi].gf) {
738 /* A VDEF has no DS. This also signals other parts
739 * of rrdtool that this is a VDEF value, not a CDEF.
741 im->gdes[gdi].ds_cnt = 0;
742 if (vdef_calc(im,gdi)) {
743 rrd_set_error("Error processing VDEF '%s'"
746 rpnstack_free(&rpnstack);
751 im->gdes[gdi].ds_cnt = 1;
752 im->gdes[gdi].ds = 0;
753 im->gdes[gdi].data_first = 1;
754 im->gdes[gdi].start = 0;
755 im->gdes[gdi].end = 0;
760 /* Find the variables in the expression.
761 * - VDEF variables are substituted by their values
762 * and the opcode is changed into OP_NUMBER.
763 * - CDEF variables are analized for their step size,
764 * the lowest common denominator of all the step
765 * sizes of the data sources involved is calculated
766 * and the resulting number is the step size for the
767 * resulting data source.
769 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
770 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE){
771 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
772 if (im->gdes[ptr].ds_cnt == 0) {
774 printf("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
776 im->gdes[ptr].vname);
777 printf("DEBUG: value from vdef is %f\n",im->gdes[ptr].vf.val);
779 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
780 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
782 if ((steparray = rrd_realloc(steparray, (++stepcnt+1)*sizeof(*steparray)))==NULL){
783 rrd_set_error("realloc steparray");
784 rpnstack_free(&rpnstack);
788 steparray[stepcnt-1] = im->gdes[ptr].step;
790 /* adjust start and end of cdef (gdi) so
791 * that it runs from the latest start point
792 * to the earliest endpoint of any of the
793 * rras involved (ptr)
795 if(im->gdes[gdi].start < im->gdes[ptr].start)
796 im->gdes[gdi].start = im->gdes[ptr].start;
798 if(im->gdes[gdi].end == 0 ||
799 im->gdes[gdi].end > im->gdes[ptr].end)
800 im->gdes[gdi].end = im->gdes[ptr].end;
802 /* store pointer to the first element of
803 * the rra providing data for variable,
804 * further save step size and data source
807 im->gdes[gdi].rpnp[rpi].data = im->gdes[ptr].data + im->gdes[ptr].ds;
808 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
809 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
811 /* backoff the *.data ptr; this is done so
812 * rpncalc() function doesn't have to treat
813 * the first case differently
815 } /* if ds_cnt != 0 */
816 } /* if OP_VARIABLE */
817 } /* loop through all rpi */
819 if(steparray == NULL){
820 rrd_set_error("rpn expressions without DEF"
821 " or CDEF variables are not supported");
822 rpnstack_free(&rpnstack);
825 steparray[stepcnt]=0;
826 /* Now find the resulting step. All steps in all
827 * used RRAs have to be visited
829 im->gdes[gdi].step = lcd(steparray);
831 if((im->gdes[gdi].data = malloc((
832 (im->gdes[gdi].end-im->gdes[gdi].start)
833 / im->gdes[gdi].step)
834 * sizeof(double)))==NULL){
835 rrd_set_error("malloc im->gdes[gdi].data");
836 rpnstack_free(&rpnstack);
840 /* Step through the new cdef results array and
841 * calculate the values
843 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
844 now<=im->gdes[gdi].end;
845 now += im->gdes[gdi].step)
847 rpnp_t *rpnp = im -> gdes[gdi].rpnp;
849 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
850 * in this case we are advancing by timesteps;
851 * we use the fact that time_t is a synonym for long
853 if (rpn_calc(rpnp,&rpnstack,(long) now,
854 im->gdes[gdi].data,++dataidx) == -1) {
855 /* rpn_calc sets the error string */
856 rpnstack_free(&rpnstack);
859 } /* enumerate over time steps within a CDEF */
864 } /* enumerate over CDEFs */
865 rpnstack_free(&rpnstack);
869 /* massage data so, that we get one value for each x coordinate in the graph */
871 data_proc( image_desc_t *im ){
873 double pixstep = (double)(im->end-im->start)
874 /(double)im->xsize; /* how much time
875 passes in one pixel */
877 double minval=DNAN,maxval=DNAN;
879 unsigned long gr_time;
881 /* memory for the processed data */
882 for(i=0;i<im->gdes_c;i++){
883 if((im->gdes[i].gf==GF_LINE) ||
884 (im->gdes[i].gf==GF_AREA) ||
885 (im->gdes[i].gf==GF_TICK) ||
886 (im->gdes[i].gf==GF_STACK)){
887 if((im->gdes[i].p_data = malloc((im->xsize +1)
888 * sizeof(rrd_value_t)))==NULL){
889 rrd_set_error("malloc data_proc");
895 for(i=0;i<im->xsize;i++){
897 gr_time = im->start+pixstep*i; /* time of the
901 for(ii=0;ii<im->gdes_c;ii++){
903 switch(im->gdes[ii].gf){
909 vidx = im->gdes[ii].vidx;
913 ((unsigned long)floor(
914 (double)(gr_time-im->gdes[vidx].start) / im->gdes[vidx].step
916 ) *im->gdes[vidx].ds_cnt
919 if (! isnan(value)) {
921 im->gdes[ii].p_data[i] = paintval;
922 /* GF_TICK: the data values are not relevant for min and max */
923 if (finite(paintval) && im->gdes[ii].gf != GF_TICK ){
924 if (isnan(minval) || paintval < minval)
926 if (isnan(maxval) || paintval > maxval)
930 im->gdes[ii].p_data[i] = DNAN;
947 /* if min or max have not been asigned a value this is because
948 there was no data in the graph ... this is not good ...
949 lets set these to dummy values then ... */
951 if (isnan(minval)) minval = 0.0;
952 if (isnan(maxval)) maxval = 1.0;
954 /* adjust min and max values */
955 if (isnan(im->minval)
956 || ((!im->logarithmic && !im->rigid) /* don't adjust low-end with log scale */
957 && im->minval > minval))
959 if (isnan(im->maxval)
961 && im->maxval < maxval)){
963 im->maxval = maxval * 1.1;
967 /* make sure min and max are not equal */
968 if (im->minval == im->maxval) {
970 if (! im->logarithmic) {
974 /* make sure min and max are not both zero */
975 if (im->maxval == 0.0) {
985 /* identify the point where the first gridline, label ... gets placed */
989 time_t start, /* what is the initial time */
990 enum tmt_en baseint, /* what is the basic interval */
991 long basestep /* how many if these do we jump a time */
995 tm = *localtime(&start);
998 tm.tm_sec -= tm.tm_sec % basestep; break;
1001 tm.tm_min -= tm.tm_min % basestep;
1006 tm.tm_hour -= tm.tm_hour % basestep; break;
1008 /* we do NOT look at the basestep for this ... */
1011 tm.tm_hour = 0; break;
1013 /* we do NOT look at the basestep for this ... */
1017 tm.tm_mday -= tm.tm_wday -1; /* -1 because we want the monday */
1018 if (tm.tm_wday==0) tm.tm_mday -= 7; /* we want the *previous* monday */
1025 tm.tm_mon -= tm.tm_mon % basestep; break;
1033 tm.tm_year -= (tm.tm_year+1900) % basestep;
1038 /* identify the point where the next gridline, label ... gets placed */
1041 time_t current, /* what is the initial time */
1042 enum tmt_en baseint, /* what is the basic interval */
1043 long basestep /* how many if these do we jump a time */
1048 tm = *localtime(¤t);
1052 tm.tm_sec += basestep; break;
1054 tm.tm_min += basestep; break;
1056 tm.tm_hour += basestep; break;
1058 tm.tm_mday += basestep; break;
1060 tm.tm_mday += 7*basestep; break;
1062 tm.tm_mon += basestep; break;
1064 tm.tm_year += basestep;
1066 madetime = mktime(&tm);
1067 } while (madetime == -1); /* this is necessary to skip impssible times
1068 like the daylight saving time skips */
1074 /* calculate values required for PRINT and GPRINT functions */
1077 print_calc(image_desc_t *im, char ***prdata)
1079 long i,ii,validsteps;
1082 int graphelement = 0;
1085 double magfact = -1;
1089 if (im->imginfo) prlines++;
1090 for(i=0;i<im->gdes_c;i++){
1091 switch(im->gdes[i].gf){
1094 if(((*prdata) = rrd_realloc((*prdata),prlines*sizeof(char *)))==NULL){
1095 rrd_set_error("realloc prdata");
1099 /* PRINT and GPRINT can now print VDEF generated values.
1100 * There's no need to do any calculations on them as these
1101 * calculations were already made.
1103 vidx = im->gdes[i].vidx;
1104 if (im->gdes[vidx].gf==GF_VDEF) { /* simply use vals */
1105 printval = im->gdes[vidx].vf.val;
1106 printtime = im->gdes[vidx].vf.when;
1107 } else { /* need to calculate max,min,avg etcetera */
1108 max_ii =((im->gdes[vidx].end
1109 - im->gdes[vidx].start)
1110 / im->gdes[vidx].step
1111 * im->gdes[vidx].ds_cnt);
1114 for( ii=im->gdes[vidx].ds;
1116 ii+=im->gdes[vidx].ds_cnt){
1117 if (! finite(im->gdes[vidx].data[ii]))
1119 if (isnan(printval)){
1120 printval = im->gdes[vidx].data[ii];
1125 switch (im->gdes[i].cf){
1128 case CF_DEVSEASONAL:
1132 printval += im->gdes[vidx].data[ii];
1135 printval = min( printval, im->gdes[vidx].data[ii]);
1139 printval = max( printval, im->gdes[vidx].data[ii]);
1142 printval = im->gdes[vidx].data[ii];
1145 if (im->gdes[i].cf==CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1146 if (validsteps > 1) {
1147 printval = (printval / validsteps);
1150 } /* prepare printval */
1152 if (!strcmp(im->gdes[i].format,"%c")) { /* VDEF time print */
1153 if (im->gdes[i].gf == GF_PRINT){
1154 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1155 sprintf((*prdata)[prlines-2],"%s (%lu)",
1156 ctime(&printtime),printtime);
1157 (*prdata)[prlines-1] = NULL;
1159 sprintf(im->gdes[i].legend,"%s (%lu)",
1160 ctime(&printtime),printtime);
1164 if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) {
1165 /* Magfact is set to -1 upon entry to print_calc. If it
1166 * is still less than 0, then we need to run auto_scale.
1167 * Otherwise, put the value into the correct units. If
1168 * the value is 0, then do not set the symbol or magnification
1169 * so next the calculation will be performed again. */
1170 if (magfact < 0.0) {
1171 auto_scale(im,&printval,&si_symb,&magfact);
1172 if (printval == 0.0)
1175 printval /= magfact;
1177 *(++percent_s) = 's';
1178 } else if (strstr(im->gdes[i].format,"%s") != NULL) {
1179 auto_scale(im,&printval,&si_symb,&magfact);
1182 if (im->gdes[i].gf == GF_PRINT){
1183 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1184 if (bad_format(im->gdes[i].format)) {
1185 rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1188 #ifdef HAVE_SNPRINTF
1189 snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
1191 sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
1193 (*prdata)[prlines-1] = NULL;
1197 if (bad_format(im->gdes[i].format)) {
1198 rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1201 #ifdef HAVE_SNPRINTF
1202 snprintf(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
1204 sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
1226 return graphelement;
1230 /* place legends with color spots */
1232 leg_place(image_desc_t *im)
1235 int interleg = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1236 int box =im->text_prop[TEXT_PROP_LEGEND].size*1.5;
1237 int border = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1238 int fill=0, fill_last;
1240 int leg_x = border, leg_y = im->ygif;
1244 char prt_fctn; /*special printfunctions */
1247 if( !(im->extra_flags & NOLEGEND) ) {
1248 if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1249 rrd_set_error("malloc for legspace");
1253 for(i=0;i<im->gdes_c;i++){
1256 leg_cc = strlen(im->gdes[i].legend);
1258 /* is there a controle code ant the end of the legend string ? */
1259 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\') {
1260 prt_fctn = im->gdes[i].legend[leg_cc-1];
1262 im->gdes[i].legend[leg_cc] = '\0';
1266 /* remove exess space */
1267 while (prt_fctn=='g' &&
1269 im->gdes[i].legend[leg_cc-1]==' '){
1271 im->gdes[i].legend[leg_cc]='\0';
1274 legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1277 /* no interleg space if string ends in \g */
1278 fill += legspace[i];
1280 if (im->gdes[i].gf != GF_GPRINT &&
1281 im->gdes[i].gf != GF_COMMENT) {
1284 fill += gfx_get_text_width(fill+border,im->text_prop[TEXT_PROP_LEGEND].font,
1285 im->text_prop[TEXT_PROP_LEGEND].size,
1287 im->gdes[i].legend);
1292 /* who said there was a special tag ... ?*/
1293 if (prt_fctn=='g') {
1296 if (prt_fctn == '\0') {
1297 if (i == im->gdes_c -1 ) prt_fctn ='l';
1299 /* is it time to place the legends ? */
1300 if (fill > im->xgif - 2*border){
1315 if (prt_fctn != '\0'){
1317 if (leg_c >= 2 && prt_fctn == 'j') {
1318 glue = (im->xgif - fill - 2* border) / (leg_c-1);
1322 if (prt_fctn =='c') leg_x = (im->xgif - fill) / 2.0;
1323 if (prt_fctn =='r') leg_x = im->xgif - fill - border;
1325 for(ii=mark;ii<=i;ii++){
1326 if(im->gdes[ii].legend[0]=='\0')
1328 im->gdes[ii].leg_x = leg_x;
1329 im->gdes[ii].leg_y = leg_y;
1331 gfx_get_text_width(leg_x,im->text_prop[TEXT_PROP_LEGEND].font,
1332 im->text_prop[TEXT_PROP_LEGEND].size,
1334 im->gdes[ii].legend)
1337 if (im->gdes[ii].gf != GF_GPRINT &&
1338 im->gdes[ii].gf != GF_COMMENT)
1341 leg_y = leg_y + im->text_prop[TEXT_PROP_LEGEND].size*1.2;
1342 if (prt_fctn == 's') leg_y -= im->text_prop[TEXT_PROP_LEGEND].size*1.2;
1354 /* create a grid on the graph. it determines what to do
1355 from the values of xsize, start and end */
1357 /* the xaxis labels are determined from the number of seconds per pixel
1358 in the requested graph */
1363 horizontal_grid(gfx_canvas_t *canvas, image_desc_t *im)
1371 char graph_label[100];
1373 int labfact,gridind;
1374 int decimals, fractionals;
1379 range = im->maxval - im->minval;
1380 scaledrange = range / im->magfact;
1382 /* does the scale of this graph make it impossible to put lines
1383 on it? If so, give up. */
1384 if (isnan(scaledrange)) {
1388 /* find grid spaceing */
1390 if(isnan(im->ygridstep)){
1391 if(im->extra_flags & ALTYGRID) {
1392 /* find the value with max number of digits. Get number of digits */
1393 decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))));
1394 if(decimals <= 0) /* everything is small. make place for zero */
1397 fractionals = floor(log10(range));
1398 if(fractionals < 0) /* small amplitude. */
1399 sprintf(labfmt, "%%%d.%df", decimals - fractionals + 1, -fractionals + 1);
1401 sprintf(labfmt, "%%%d.1f", decimals + 1);
1402 gridstep = pow((double)10, (double)fractionals);
1403 if(gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1405 /* should have at least 5 lines but no more then 15 */
1406 if(range/gridstep < 5)
1408 if(range/gridstep > 15)
1410 if(range/gridstep > 5) {
1412 if(range/gridstep > 8)
1421 for(i=0;ylab[i].grid > 0;i++){
1422 pixel = im->ysize / (scaledrange / ylab[i].grid);
1423 if (gridind == -1 && pixel > 5) {
1430 if (pixel * ylab[gridind].lfac[i] >= 2 * im->text_prop[TEXT_PROP_AXIS].size) {
1431 labfact = ylab[gridind].lfac[i];
1436 gridstep = ylab[gridind].grid * im->magfact;
1439 gridstep = im->ygridstep;
1440 labfact = im->ylabfact;
1444 x1=im->xorigin+im->xsize;
1446 sgrid = (int)( im->minval / gridstep - 1);
1447 egrid = (int)( im->maxval / gridstep + 1);
1448 scaledstep = gridstep/im->magfact;
1449 for (i = sgrid; i <= egrid; i++){
1450 y0=ytr(im,gridstep*i);
1451 if ( y0 >= im->yorigin-im->ysize
1452 && y0 <= im->yorigin){
1453 if(i % labfact == 0){
1454 if (i==0 || im->symbol == ' ') {
1456 if(im->extra_flags & ALTYGRID) {
1457 sprintf(graph_label,labfmt,scaledstep*i);
1460 sprintf(graph_label,"%4.1f",scaledstep*i);
1463 sprintf(graph_label,"%4.0f",scaledstep*i);
1467 sprintf(graph_label,"%4.1f %c",scaledstep*i, im->symbol);
1469 sprintf(graph_label,"%4.0f %c",scaledstep*i, im->symbol);
1473 gfx_new_text ( canvas,
1474 x0-im->text_prop[TEXT_PROP_AXIS].size/1.5, y0,
1475 im->graph_col[GRC_FONT],
1476 im->text_prop[TEXT_PROP_AXIS].font,
1477 im->text_prop[TEXT_PROP_AXIS].size,
1478 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1480 gfx_new_line ( canvas,
1483 MGRIDWIDTH, im->graph_col[GRC_MGRID] );
1486 gfx_new_line ( canvas,
1489 GRIDWIDTH, im->graph_col[GRC_GRID] );
1497 /* logaritmic horizontal grid */
1499 horizontal_log_grid(gfx_canvas_t *canvas, image_desc_t *im)
1503 int minoridx=0, majoridx=0;
1504 char graph_label[100];
1506 double value, pixperstep, minstep;
1508 /* find grid spaceing */
1509 pixpex= (double)im->ysize / (log10(im->maxval) - log10(im->minval));
1511 if (isnan(pixpex)) {
1515 for(i=0;yloglab[i][0] > 0;i++){
1516 minstep = log10(yloglab[i][0]);
1517 for(ii=1;yloglab[i][ii+1] > 0;ii++){
1518 if(yloglab[i][ii+2]==0){
1519 minstep = log10(yloglab[i][ii+1])-log10(yloglab[i][ii]);
1523 pixperstep = pixpex * minstep;
1524 if(pixperstep > 5){minoridx = i;}
1525 if(pixperstep > 2 * im->text_prop[TEXT_PROP_LEGEND].size){majoridx = i;}
1529 x1=im->xorigin+im->xsize;
1530 /* paint minor grid */
1531 for (value = pow((double)10, log10(im->minval)
1532 - fmod(log10(im->minval),log10(yloglab[minoridx][0])));
1533 value <= im->maxval;
1534 value *= yloglab[minoridx][0]){
1535 if (value < im->minval) continue;
1537 while(yloglab[minoridx][++i] > 0){
1538 y0 = ytr(im,value * yloglab[minoridx][i]);
1539 if (y0 <= im->yorigin - im->ysize) break;
1540 gfx_new_line ( canvas,
1543 GRIDWIDTH, im->graph_col[GRC_GRID] );
1547 /* paint major grid and labels*/
1548 for (value = pow((double)10, log10(im->minval)
1549 - fmod(log10(im->minval),log10(yloglab[majoridx][0])));
1550 value <= im->maxval;
1551 value *= yloglab[majoridx][0]){
1552 if (value < im->minval) continue;
1554 while(yloglab[majoridx][++i] > 0){
1555 y0 = ytr(im,value * yloglab[majoridx][i]);
1556 if (y0 <= im->yorigin - im->ysize) break;
1557 gfx_new_line ( canvas,
1560 MGRIDWIDTH, im->graph_col[GRC_MGRID] );
1562 sprintf(graph_label,"%3.0e",value * yloglab[majoridx][i]);
1563 gfx_new_text ( canvas,
1564 x0-im->text_prop[TEXT_PROP_AXIS].size/1.5, y0,
1565 im->graph_col[GRC_FONT],
1566 im->text_prop[TEXT_PROP_AXIS].font,
1567 im->text_prop[TEXT_PROP_AXIS].size,
1568 im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1578 gfx_canvas_t *canvas,
1581 int xlab_sel; /* which sort of label and grid ? */
1584 char graph_label[100];
1585 double x0,y0,y1; /* points for filled graph and more*/
1588 /* the type of time grid is determined by finding
1589 the number of seconds per pixel in the graph */
1592 if(im->xlab_user.minsec == -1){
1593 factor=(im->end - im->start)/im->xsize;
1595 while ( xlab[xlab_sel+1].minsec != -1
1596 && xlab[xlab_sel+1].minsec <= factor){ xlab_sel++; }
1597 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
1598 im->xlab_user.gridst = xlab[xlab_sel].gridst;
1599 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
1600 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
1601 im->xlab_user.labtm = xlab[xlab_sel].labtm;
1602 im->xlab_user.labst = xlab[xlab_sel].labst;
1603 im->xlab_user.precis = xlab[xlab_sel].precis;
1604 im->xlab_user.stst = xlab[xlab_sel].stst;
1607 /* y coords are the same for every line ... */
1609 y1 = im->yorigin-im->ysize;
1612 /* paint the minor grid */
1613 for(ti = find_first_time(im->start,
1614 im->xlab_user.gridtm,
1615 im->xlab_user.gridst);
1617 ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
1619 /* are we inside the graph ? */
1620 if (ti < im->start || ti > im->end) continue;
1622 gfx_new_line(canvas,x0,y0+1, x0,y1-1,GRIDWIDTH, im->graph_col[GRC_GRID]);
1626 /* paint the major grid */
1627 for(ti = find_first_time(im->start,
1628 im->xlab_user.mgridtm,
1629 im->xlab_user.mgridst);
1631 ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
1633 /* are we inside the graph ? */
1634 if (ti < im->start || ti > im->end) continue;
1636 gfx_new_line(canvas,x0,y0+2, x0,y1-2,MGRIDWIDTH, im->graph_col[GRC_MGRID]);
1639 /* paint the labels below the graph */
1640 for(ti = find_first_time(im->start,
1641 im->xlab_user.labtm,
1642 im->xlab_user.labst);
1644 ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
1646 tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
1649 strftime(graph_label,99,im->xlab_user.stst,localtime(&tilab));
1651 # error "your libc has no strftime I guess we'll abort the exercise here."
1653 gfx_new_text ( canvas,
1654 xtr(im,tilab), y0+im->text_prop[TEXT_PROP_AXIS].size/1.5,
1655 im->graph_col[GRC_FONT],
1656 im->text_prop[TEXT_PROP_AXIS].font,
1657 im->text_prop[TEXT_PROP_AXIS].size,
1658 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP,
1669 gfx_canvas_t *canvas
1672 /* draw x and y axis */
1673 gfx_new_line ( canvas, im->xorigin+im->xsize,im->yorigin,
1674 im->xorigin+im->xsize,im->yorigin-im->ysize,
1675 GRIDWIDTH, im->graph_col[GRC_GRID]);
1677 gfx_new_line ( canvas, im->xorigin,im->yorigin-im->ysize,
1678 im->xorigin+im->xsize,im->yorigin-im->ysize,
1679 GRIDWIDTH, im->graph_col[GRC_GRID]);
1681 gfx_new_line ( canvas, im->xorigin-4,im->yorigin,
1682 im->xorigin+im->xsize+4,im->yorigin,
1683 MGRIDWIDTH, im->graph_col[GRC_GRID]);
1685 gfx_new_line ( canvas, im->xorigin,im->yorigin+4,
1686 im->xorigin,im->yorigin-im->ysize-4,
1687 MGRIDWIDTH, im->graph_col[GRC_GRID]);
1690 /* arrow for X axis direction */
1691 gfx_new_area ( canvas,
1692 im->xorigin+im->xsize+4, im->yorigin-3,
1693 im->xorigin+im->xsize+4, im->yorigin+3,
1694 im->xorigin+im->xsize+9, im->yorigin,
1695 im->graph_col[GRC_ARROW]);
1704 gfx_canvas_t *canvas
1711 double x0,x1,x2,x3,y0,y1,y2,y3; /* points for filled graph and more*/
1715 /* draw 3d border */
1716 node = gfx_new_area (canvas, 0,im->ygif,
1718 2,2,im->graph_col[GRC_SHADEA]);
1719 gfx_add_point( node , im->xgif - 2, 2 );
1720 gfx_add_point( node , im->xgif, 0 );
1721 gfx_add_point( node , 0,0 );
1722 /* gfx_add_point( node , 0,im->ygif ); */
1724 node = gfx_new_area (canvas, 2,im->ygif-2,
1725 im->xgif-2,im->ygif-2,
1727 im->graph_col[GRC_SHADEB]);
1728 gfx_add_point( node , im->xgif,0);
1729 gfx_add_point( node , im->xgif,im->ygif);
1730 gfx_add_point( node , 0,im->ygif);
1731 /* gfx_add_point( node , 0,im->ygif ); */
1734 if (im->draw_x_grid == 1 )
1735 vertical_grid(canvas, im);
1737 if (im->draw_y_grid == 1){
1738 if(im->logarithmic){
1739 res = horizontal_log_grid(canvas,im);
1741 res = horizontal_grid(canvas,im);
1744 /* dont draw horizontal grid if there is no min and max val */
1746 char *nodata = "No Data found";
1747 gfx_new_text(canvas,im->xgif/2, (2*im->yorigin-im->ysize) / 2,
1748 im->graph_col[GRC_FONT],
1749 im->text_prop[TEXT_PROP_AXIS].font,
1750 im->text_prop[TEXT_PROP_AXIS].size,
1751 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
1756 /* yaxis description */
1757 gfx_new_text( canvas,
1758 7, (im->yorigin - im->ysize/2),
1759 im->graph_col[GRC_FONT],
1760 im->text_prop[TEXT_PROP_AXIS].font,
1761 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 270.0,
1762 GFX_H_CENTER, GFX_V_CENTER,
1766 gfx_new_text( canvas,
1767 im->xgif/2, im->text_prop[TEXT_PROP_TITLE].size*1.5,
1768 im->graph_col[GRC_FONT],
1769 im->text_prop[TEXT_PROP_TITLE].font,
1770 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
1771 GFX_H_CENTER, GFX_V_CENTER,
1775 if( !(im->extra_flags & NOLEGEND) ) {
1776 for(i=0;i<im->gdes_c;i++){
1777 if(im->gdes[i].legend[0] =='\0')
1780 if(im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT){
1781 x0 = im->gdes[i].leg_x;
1782 y0 = im->gdes[i].leg_y+1.0;
1789 node = gfx_new_area(canvas, x0,y0,x1,y1,x2,y2 ,im->gdes[i].col);
1790 gfx_add_point ( node, x3, y3 );
1791 /* gfx_add_point ( node, x0, y0 ); */
1792 node = gfx_new_line(canvas, x0,y0,x1,y1 ,GRIDWIDTH, im->graph_col[GRC_FRAME]);
1793 gfx_add_point ( node, x2, y2 );
1794 gfx_add_point ( node, x3, y3 );
1795 gfx_add_point ( node, x0, y0 );
1797 gfx_new_text ( canvas, x0+boxH+6, (y0+y2) / 2.0,
1798 im->graph_col[GRC_FONT],
1799 im->text_prop[TEXT_PROP_AXIS].font,
1800 im->text_prop[TEXT_PROP_AXIS].size,
1801 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
1802 im->gdes[i].legend );
1805 x0 = im->gdes[i].leg_x;
1806 y0 = im->gdes[i].leg_y;
1808 gfx_new_text ( canvas, x0, (y0+y2) / 2.0,
1809 im->graph_col[GRC_FONT],
1810 im->text_prop[TEXT_PROP_AXIS].font,
1811 im->text_prop[TEXT_PROP_AXIS].size,
1812 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
1813 im->gdes[i].legend );
1821 /*****************************************************
1822 * lazy check make sure we rely need to create this graph
1823 *****************************************************/
1825 int lazy_check(image_desc_t *im){
1828 struct stat gifstat;
1830 if (im->lazy == 0) return 0; /* no lazy option */
1831 if (stat(im->graphfile,&gifstat) != 0)
1832 return 0; /* can't stat */
1833 /* one pixel in the existing graph is more then what we would
1835 if (time(NULL) - gifstat.st_mtime >
1836 (im->end - im->start) / im->xsize)
1838 if ((fd = fopen(im->graphfile,"rb")) == NULL)
1839 return 0; /* the file does not exist */
1840 switch (im->imgformat) {
1842 size = GifSize(fd,&(im->xgif),&(im->ygif));
1845 size = PngSize(fd,&(im->xgif),&(im->ygif));
1853 pie_part(gfx_canvas_t *canvas, gfx_color_t color,
1854 double PieCenterX, double PieCenterY, double Radius,
1855 double startangle, double endangle)
1859 double step=M_PI/50; /* Number of iterations for the circle;
1860 ** 10 is definitely too low, more than
1861 ** 50 seems to be overkill
1864 /* Strange but true: we have to work clockwise or else
1865 ** anti aliasing nor transparency don't work.
1867 ** This test is here to make sure we do it right, also
1868 ** this makes the for...next loop more easy to implement.
1869 ** The return will occur if the user enters a negative number
1870 ** (which shouldn't be done according to the specs) or if the
1871 ** programmers do something wrong (which, as we all know, never
1872 ** happens anyway :)
1874 if (endangle<startangle) return;
1876 /* Hidden feature: Radius decreases each full circle */
1878 while (angle>=2*M_PI) {
1883 node=gfx_new_area(canvas,
1884 PieCenterX+sin(startangle)*Radius,
1885 PieCenterY-cos(startangle)*Radius,
1888 PieCenterX+sin(endangle)*Radius,
1889 PieCenterY-cos(endangle)*Radius,
1891 for (angle=endangle;angle-startangle>=step;angle-=step) {
1893 PieCenterX+sin(angle)*Radius,
1894 PieCenterY-cos(angle)*Radius );
1898 /* draw that picture thing ... */
1900 graph_paint(image_desc_t *im, char ***calcpr)
1903 int lazy = lazy_check(im);
1905 double PieStart=0.0, PieSize=0.0, PieCenterX=0.0, PieCenterY=0.0;
1907 gfx_canvas_t *canvas;
1910 double areazero = 0.0;
1911 enum gf_en stack_gf = GF_PRINT;
1912 graph_desc_t *lastgdes = NULL;
1914 /* if we are lazy and there is nothing to PRINT ... quit now */
1915 if (lazy && im->prt_c==0) return 0;
1917 /* pull the data from the rrd files ... */
1919 if(data_fetch(im)==-1)
1922 /* evaluate VDEF and CDEF operations ... */
1923 if(data_calc(im)==-1)
1926 /* calculate and PRINT and GPRINT definitions. We have to do it at
1927 * this point because it will affect the length of the legends
1928 * if there are no graph elements we stop here ...
1929 * if we are lazy, try to quit ...
1931 i=print_calc(im,calcpr);
1933 if(i==0 || lazy) return 0;
1935 /* get actual drawing data and find min and max values*/
1936 if(data_proc(im)==-1)
1939 if(!im->logarithmic){si_unit(im);} /* identify si magnitude Kilo, Mega Giga ? */
1941 if(!im->rigid && ! im->logarithmic)
1942 expand_range(im); /* make sure the upper and lower limit are
1945 /* init xtr and ytr */
1946 /* determine the actual size of the gif to draw. The size given
1947 on the cmdline is the graph area. But we need more as we have
1948 draw labels and other things outside the graph area */
1951 im->xorigin = 10 + 9 * im->text_prop[TEXT_PROP_LEGEND].size;
1955 im->yorigin = 10 + im->ysize;
1959 if(im->title[0] != '\0')
1960 im->yorigin += im->text_prop[TEXT_PROP_TITLE].size*3+4;
1962 im->xgif= 20 +im->xsize + im->xorigin;
1963 im->ygif= im->yorigin+2* im->text_prop[TEXT_PROP_LEGEND].size;
1965 /* check if we need to draw a piechart */
1966 for(i=0;i<im->gdes_c;i++){
1967 if (im->gdes[i].gf == GF_PART) {
1974 /* allocate enough space for the piechart itself (PieSize), 20%
1975 ** more for the background and an additional 50 pixels spacing.
1977 if (im->xsize < im->ysize)
1978 PieSize = im->xsize;
1980 PieSize = im->ysize;
1981 im->xgif += PieSize*1.2 + 50;
1983 PieCenterX = im->xorigin + im->xsize + 50 + PieSize*0.6;
1984 PieCenterY = im->yorigin - PieSize*0.5;
1987 /* determine where to place the legends onto the graphics.
1988 and set im->ygif to match space requirements for text */
1989 if(leg_place(im)==-1)
1992 canvas=gfx_new_canvas();
1995 /* the actual graph is created by going through the individual
1996 graph elements and then drawing them */
1998 node=gfx_new_area ( canvas,
2002 im->graph_col[GRC_BACK]);
2004 gfx_add_point(node,0, im->ygif);
2006 node=gfx_new_area ( canvas,
2007 im->xorigin, im->yorigin,
2008 im->xorigin + im->xsize, im->yorigin,
2009 im->xorigin + im->xsize, im->yorigin-im->ysize,
2010 im->graph_col[GRC_CANVAS]);
2012 gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
2015 /******************************************************************
2016 ** Just to play around. If you see this, I forgot to remove it **
2017 ******************************************************************/
2019 node=gfx_new_area(canvas,
2021 im->xgif, im->ygif-100,
2023 im->graph_col[GRC_CANVAS]);
2024 gfx_add_point(node,0,im->ygif);
2027 ** top left: current way, solid color
2028 ** top right: proper way, solid color
2029 ** bottom left: current way, alpha=0x80, partially overlapping
2030 ** bottom right: proper way, alpha=0x80, partially overlapping
2033 double x,y,x1,y1,x2,y2,x3,y3,x4,y4;
2037 x1= 20; y1=im->ygif-100+20;
2038 x2=3*x+20; y2=im->ygif-100+20;
2039 x3= x+20; y3=im->ygif-100+20+2*y;
2040 x4=4*x+20; y4=im->ygif-100+20+2*y;
2042 node=gfx_new_area(canvas,
2047 gfx_add_point(node,x1,y1+3*y);
2048 node=gfx_new_area(canvas,
2053 gfx_add_point(node,x2+3*x,y2);
2054 node=gfx_new_area(canvas,
2059 gfx_add_point(node,x3,y3+3*y);
2060 node=gfx_new_area(canvas,
2065 gfx_add_point(node,x4+2*x,y4);
2071 pie_part(canvas,im->graph_col[GRC_CANVAS],PieCenterX,PieCenterY,PieSize*0.6,0,2*M_PI);
2074 if (im->minval > 0.0)
2075 areazero = im->minval;
2076 if (im->maxval < 0.0)
2077 areazero = im->maxval;
2079 axis_paint(im,canvas);
2082 for(i=0;i<im->gdes_c;i++){
2083 switch(im->gdes[i].gf){
2094 for (ii = 0; ii < im->xsize; ii++)
2096 if (!isnan(im->gdes[i].p_data[ii]) &&
2097 im->gdes[i].p_data[ii] > 0.0)
2099 /* generate a tick */
2100 gfx_new_line(canvas, im -> xorigin + ii,
2101 im -> yorigin - (im -> gdes[i].yrule * im -> ysize),
2105 im -> gdes[i].col );
2111 stack_gf = im->gdes[i].gf;
2113 /* fix data points at oo and -oo */
2114 for(ii=0;ii<im->xsize;ii++){
2115 if (isinf(im->gdes[i].p_data[ii])){
2116 if (im->gdes[i].p_data[ii] > 0) {
2117 im->gdes[i].p_data[ii] = im->maxval ;
2119 im->gdes[i].p_data[ii] = im->minval ;
2125 if (im->gdes[i].col != 0x0){
2126 /* GF_LINE and friend */
2127 if(stack_gf == GF_LINE ){
2129 for(ii=1;ii<im->xsize;ii++){
2130 if ( ! isnan(im->gdes[i].p_data[ii-1])
2131 && ! isnan(im->gdes[i].p_data[ii])){
2133 node = gfx_new_line(canvas,
2134 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2135 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2136 im->gdes[i].linewidth,
2139 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2148 for(ii=1;ii<im->xsize;ii++){
2150 if ( ! isnan(im->gdes[i].p_data[ii-1])
2151 && ! isnan(im->gdes[i].p_data[ii])){
2154 if (im->gdes[i].gf == GF_STACK) {
2155 ybase = ytr(im,lastgdes->p_data[ii-1]);
2157 ybase = ytr(im,areazero);
2160 node = gfx_new_area(canvas,
2161 ii-1+im->xorigin,ybase,
2162 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2163 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2167 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2171 if ( node != NULL && (ii+1==im->xsize || isnan(im->gdes[i].p_data[ii]) )){
2172 /* GF_AREA STACK type*/
2173 if (im->gdes[i].gf == GF_STACK ) {
2175 for (iii=ii-1;iii>area_start;iii--){
2176 gfx_add_point(node,iii+im->xorigin,ytr(im,lastgdes->p_data[iii]));
2179 gfx_add_point(node,ii+im->xorigin,ytr(im,areazero));
2184 } /* else GF_LINE */
2185 } /* if color != 0x0 */
2186 /* make sure we do not run into trouble when stacking on NaN */
2187 for(ii=0;ii<im->xsize;ii++){
2188 if (isnan(im->gdes[i].p_data[ii])) {
2191 ybase = ytr(im,lastgdes->p_data[ii-1]);
2193 if (isnan(ybase) || !lastgdes ){
2194 ybase = ytr(im,areazero);
2196 im->gdes[i].p_data[ii] = ybase;
2199 lastgdes = &(im->gdes[i]);
2202 if(isnan(im->gdes[i].yrule)) /* fetch variable */
2203 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2205 if (finite(im->gdes[i].yrule)) { /* even the fetched var can be NaN */
2206 pie_part(canvas,im->gdes[i].col,
2207 PieCenterX,PieCenterY,PieSize/2,
2208 M_PI*2.0*PieStart/100.0,
2209 M_PI*2.0*(PieStart+im->gdes[i].yrule)/100.0);
2210 PieStart += im->gdes[i].yrule;
2215 grid_paint(im,canvas);
2217 /* the RULES are the last thing to paint ... */
2218 for(i=0;i<im->gdes_c;i++){
2220 switch(im->gdes[i].gf){
2222 if(isnan(im->gdes[i].yrule)) { /* fetch variable */
2223 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2225 if(im->gdes[i].yrule >= im->minval
2226 && im->gdes[i].yrule <= im->maxval)
2227 gfx_new_line(canvas,
2228 im->xorigin,ytr(im,im->gdes[i].yrule),
2229 im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2230 1.0,im->gdes[i].col);
2233 if(im->gdes[i].xrule == 0) { /* fetch variable */
2234 im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
2236 if(im->gdes[i].xrule >= im->start
2237 && im->gdes[i].xrule <= im->end)
2238 gfx_new_line(canvas,
2239 xtr(im,im->gdes[i].xrule),im->yorigin,
2240 xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2241 1.0,im->gdes[i].col);
2249 if (strcmp(im->graphfile,"-")==0) {
2251 /* Change translation mode for stdout to BINARY */
2252 _setmode( _fileno( stdout ), O_BINARY );
2256 if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2257 rrd_set_error("Opening '%s' for write: %s",im->graphfile,
2262 switch (im->imgformat) {
2266 gfx_render_png (canvas,im->xgif,im->ygif,im->zoom,0x0,fo);
2269 if (strcmp(im->graphfile,"-") != 0)
2272 gfx_destroy(canvas);
2277 /*****************************************************
2279 *****************************************************/
2282 gdes_alloc(image_desc_t *im){
2284 long def_step = (im->end-im->start)/im->xsize;
2286 if (im->step > def_step) /* step can be increassed ... no decreassed */
2287 def_step = im->step;
2291 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2292 * sizeof(graph_desc_t)))==NULL){
2293 rrd_set_error("realloc graph_descs");
2298 im->gdes[im->gdes_c-1].step=def_step;
2299 im->gdes[im->gdes_c-1].start=im->start;
2300 im->gdes[im->gdes_c-1].end=im->end;
2301 im->gdes[im->gdes_c-1].vname[0]='\0';
2302 im->gdes[im->gdes_c-1].data=NULL;
2303 im->gdes[im->gdes_c-1].ds_namv=NULL;
2304 im->gdes[im->gdes_c-1].data_first=0;
2305 im->gdes[im->gdes_c-1].p_data=NULL;
2306 im->gdes[im->gdes_c-1].rpnp=NULL;
2307 im->gdes[im->gdes_c-1].col = 0x0;
2308 im->gdes[im->gdes_c-1].legend[0]='\0';
2309 im->gdes[im->gdes_c-1].rrd[0]='\0';
2310 im->gdes[im->gdes_c-1].ds=-1;
2311 im->gdes[im->gdes_c-1].p_data=NULL;
2315 /* copies input untill the first unescaped colon is found
2316 or until input ends. backslashes have to be escaped as well */
2318 scan_for_col(char *input, int len, char *output)
2323 input[inp] != ':' &&
2326 if (input[inp] == '\\' &&
2327 input[inp+1] != '\0' &&
2328 (input[inp+1] == '\\' ||
2329 input[inp+1] == ':')){
2330 output[outp++] = input[++inp];
2333 output[outp++] = input[inp];
2336 output[outp] = '\0';
2340 /* Some surgery done on this function, it became ridiculously big.
2342 ** - initializing now in rrd_graph_init()
2343 ** - options parsing now in rrd_graph_options()
2344 ** - script parsing now in rrd_graph_script()
2347 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize)
2351 rrd_graph_init(&im);
2353 rrd_graph_options(argc,argv,&im);
2354 if (rrd_test_error()) return -1;
2356 if (strlen(argv[optind])>=MAXPATH) {
2357 rrd_set_error("filename (including path) too long");
2360 strncpy(im.graphfile,argv[optind],MAXPATH-1);
2361 im.graphfile[MAXPATH-1]='\0';
2363 rrd_graph_script(argc,argv,&im);
2364 if (rrd_test_error()) return -1;
2366 /* Everything is now read and the actual work can start */
2369 if (graph_paint(&im,prdata)==-1){
2374 /* The image is generated and needs to be output.
2375 ** Also, if needed, print a line with information about the image.
2383 /* maybe prdata is not allocated yet ... lets do it now */
2384 if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
2385 rrd_set_error("malloc imginfo");
2389 if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
2391 rrd_set_error("malloc imginfo");
2394 filename=im.graphfile+strlen(im.graphfile);
2395 while(filename > im.graphfile) {
2396 if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
2400 sprintf((*prdata)[0],im.imginfo,filename,(long)(im.zoom*im.xgif),(long)(im.zoom*im.ygif));
2407 rrd_graph_init(image_desc_t *im)
2411 im->xlab_user.minsec = -1;
2417 im->ylegend[0] = '\0';
2418 im->title[0] = '\0';
2422 im->unitsexponent= 9999;
2427 im->logarithmic = 0;
2428 im->ygridstep = DNAN;
2429 im->draw_x_grid = 1;
2430 im->draw_y_grid = 1;
2436 im->imgformat = IF_GIF; /* we default to GIF output */
2438 for(i=0;i<DIM(graph_col);i++)
2439 im->graph_col[i]=graph_col[i];
2441 for(i=0;i<DIM(text_prop);i++){
2442 im->text_prop[i].size = text_prop[i].size;
2443 im->text_prop[i].font = text_prop[i].font;
2448 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
2451 char *parsetime_error = NULL;
2452 char scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
2453 time_t start_tmp=0,end_tmp=0;
2455 struct time_value start_tv, end_tv;
2458 parsetime("end-24h", &start_tv);
2459 parsetime("now", &end_tv);
2462 static struct option long_options[] =
2464 {"start", required_argument, 0, 's'},
2465 {"end", required_argument, 0, 'e'},
2466 {"x-grid", required_argument, 0, 'x'},
2467 {"y-grid", required_argument, 0, 'y'},
2468 {"vertical-label",required_argument,0,'v'},
2469 {"width", required_argument, 0, 'w'},
2470 {"height", required_argument, 0, 'h'},
2471 {"interlaced", no_argument, 0, 'i'},
2472 {"upper-limit",required_argument, 0, 'u'},
2473 {"lower-limit",required_argument, 0, 'l'},
2474 {"rigid", no_argument, 0, 'r'},
2475 {"base", required_argument, 0, 'b'},
2476 {"logarithmic",no_argument, 0, 'o'},
2477 {"color", required_argument, 0, 'c'},
2478 {"font", required_argument, 0, 'n'},
2479 {"title", required_argument, 0, 't'},
2480 {"imginfo", required_argument, 0, 'f'},
2481 {"imgformat", required_argument, 0, 'a'},
2482 {"lazy", no_argument, 0, 'z'},
2483 {"zoom", required_argument, 0, 'm'},
2484 {"no-legend", no_argument, 0, 'g'},
2485 {"alt-y-grid", no_argument, 0, 257 },
2486 {"alt-autoscale", no_argument, 0, 258 },
2487 {"alt-autoscale-max", no_argument, 0, 259 },
2488 {"units-exponent",required_argument, 0, 260},
2489 {"step", required_argument, 0, 261},
2491 int option_index = 0;
2495 opt = getopt_long(argc, argv,
2496 "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:z:g",
2497 long_options, &option_index);
2504 im->extra_flags |= ALTYGRID;
2507 im->extra_flags |= ALTAUTOSCALE;
2510 im->extra_flags |= ALTAUTOSCALE_MAX;
2513 im->extra_flags |= NOLEGEND;
2516 im->unitsexponent = atoi(optarg);
2519 im->step = atoi(optarg);
2522 if ((parsetime_error = parsetime(optarg, &start_tv))) {
2523 rrd_set_error( "start time: %s", parsetime_error );
2528 if ((parsetime_error = parsetime(optarg, &end_tv))) {
2529 rrd_set_error( "end time: %s", parsetime_error );
2534 if(strcmp(optarg,"none") == 0){
2540 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
2542 &im->xlab_user.gridst,
2544 &im->xlab_user.mgridst,
2546 &im->xlab_user.labst,
2547 &im->xlab_user.precis,
2548 &stroff) == 7 && stroff != 0){
2549 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
2550 if((im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
2551 rrd_set_error("unknown keyword %s",scan_gtm);
2553 } else if ((im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
2554 rrd_set_error("unknown keyword %s",scan_mtm);
2556 } else if ((im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
2557 rrd_set_error("unknown keyword %s",scan_ltm);
2560 im->xlab_user.minsec = 1;
2561 im->xlab_user.stst = im->xlab_form;
2563 rrd_set_error("invalid x-grid format");
2569 if(strcmp(optarg,"none") == 0){
2577 &im->ylabfact) == 2) {
2578 if(im->ygridstep<=0){
2579 rrd_set_error("grid step must be > 0");
2581 } else if (im->ylabfact < 1){
2582 rrd_set_error("label factor must be > 0");
2586 rrd_set_error("invalid y-grid format");
2591 strncpy(im->ylegend,optarg,150);
2592 im->ylegend[150]='\0';
2595 im->maxval = atof(optarg);
2598 im->minval = atof(optarg);
2601 im->base = atol(optarg);
2602 if(im->base != 1024 && im->base != 1000 ){
2603 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
2608 long_tmp = atol(optarg);
2609 if (long_tmp < 10) {
2610 rrd_set_error("width below 10 pixels");
2613 im->xsize = long_tmp;
2616 long_tmp = atol(optarg);
2617 if (long_tmp < 10) {
2618 rrd_set_error("height below 10 pixels");
2621 im->ysize = long_tmp;
2630 im->imginfo = optarg;
2633 if((im->imgformat = if_conv(optarg)) == -1) {
2634 rrd_set_error("unsupported graphics format '%s'",optarg);
2642 im->logarithmic = 1;
2643 if (isnan(im->minval))
2649 col_nam,&color) == 2){
2651 if((ci=grc_conv(col_nam)) != -1){
2652 im->graph_col[ci]=color;
2654 rrd_set_error("invalid color name '%s'",col_nam);
2657 rrd_set_error("invalid color def format");
2662 /* originally this used char *prop = "" and
2663 ** char *font = "dummy" however this results
2664 ** in a SEG fault, at least on RH7.1
2666 ** The current implementation isn't proper
2667 ** either, font is never freed and prop uses
2668 ** a fixed width string
2677 prop,&size,font) == 3){
2679 if((sindex=text_prop_conv(prop)) != -1){
2680 im->text_prop[sindex].size=size;
2681 im->text_prop[sindex].font=font;
2682 if (sindex==0) { /* the default */
2683 im->text_prop[TEXT_PROP_TITLE].size=size;
2684 im->text_prop[TEXT_PROP_TITLE].font=font;
2685 im->text_prop[TEXT_PROP_AXIS].size=size;
2686 im->text_prop[TEXT_PROP_AXIS].font=font;
2687 im->text_prop[TEXT_PROP_UNIT].size=size;
2688 im->text_prop[TEXT_PROP_UNIT].font=font;
2689 im->text_prop[TEXT_PROP_LEGEND].size=size;
2690 im->text_prop[TEXT_PROP_LEGEND].font=font;
2693 rrd_set_error("invalid fonttag '%s'",prop);
2697 rrd_set_error("invalid text property format");
2703 im->zoom= atof(optarg);
2704 if (im->zoom <= 0.0) {
2705 rrd_set_error("zoom factor must be > 0");
2710 strncpy(im->title,optarg,150);
2711 im->title[150]='\0';
2716 rrd_set_error("unknown option '%c'", optopt);
2718 rrd_set_error("unknown option '%s'",argv[optind-1]);
2723 if (optind >= argc) {
2724 rrd_set_error("missing filename");
2728 if (im->logarithmic == 1 && (im->minval <= 0 || isnan(im->minval))){
2729 rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");
2733 if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
2734 /* error string is set in parsetime.c */
2738 if (start_tmp < 3600*24*365*10){
2739 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
2743 if (end_tmp < start_tmp) {
2744 rrd_set_error("start (%ld) should be less than end (%ld)",
2745 start_tmp, end_tmp);
2749 im->start = start_tmp;
2754 rrd_graph_script(int argc, char *argv[], image_desc_t *im)
2758 int linepass = 0; /* stack must follow LINE*, AREA or STACK */
2760 for (i=optind+1;i<argc;i++) {
2765 char funcname[10],vname[MAX_VNAME_LEN+1],sep[1];
2770 /* Each command is one element from *argv[], we call this "line".
2772 ** Each command defines the most current gdes inside struct im.
2773 ** In stead of typing "im->gdes[im->gdes_c-1]" we use "gdp".
2776 gdp=&im->gdes[im->gdes_c-1];
2779 /* function:newvname=string[:ds-name:CF] for xDEF
2780 ** function:vname[#color[:string]] for LINEx,AREA,STACK
2781 ** function:vname#color[:num[:string]] for TICK
2782 ** function:vname-or-num#color[:string] for xRULE,PART
2783 ** function:vname:CF:string for xPRINT
2784 ** function:string for COMMENT
2788 sscanf(line, "%10[A-Z0-9]:%n", funcname,&argstart);
2790 rrd_set_error("Cannot parse function in line: %s",line);
2794 if(sscanf(funcname,"LINE%lf",&linewidth)){
2795 im->gdes[im->gdes_c-1].gf = GF_LINE;
2796 im->gdes[im->gdes_c-1].linewidth = linewidth;
2798 if ((gdp->gf=gf_conv(funcname))==-1) {
2799 rrd_set_error("'%s' is not a valid function name",funcname);
2805 /* If the error string is set, we exit at the end of the switch */
2808 if (rrd_graph_legend(gdp,&line[argstart])==0)
2809 rrd_set_error("Cannot parse comment in line: %s",line);
2815 sscanf(&line[argstart], "%lf%n#%n", &d, &j, &k);
2816 sscanf(&line[argstart], DEF_NAM_FMT "%n#%n", vname, &l, &m);
2818 rrd_set_error("Cannot parse name or num in line: %s",line);
2825 } else if (!rrd_graph_check_vname(im,vname,line)) {
2829 } else break; /* exit due to wrong vname */
2830 if ((j=rrd_graph_color(im,&line[argstart],line,0))==0) break;
2832 if (strlen(&line[argstart])!=0) {
2833 if (rrd_graph_legend(gdp,&line[++argstart])==0)
2834 rrd_set_error("Cannot parse comment in line: %s",line);
2839 rrd_set_error("STACK must follow another graphing element");
2847 sscanf(&line[argstart],DEF_NAM_FMT"%n%1[#:]%n",vname,&j,sep,&k);
2849 rrd_set_error("Cannot parse vname in line: %s",line);
2850 else if (rrd_graph_check_vname(im,vname,line))
2851 rrd_set_error("Undefined vname '%s' in line: %s",line);
2853 k=rrd_graph_color(im,&line[argstart],line,1);
2854 if (rrd_test_error()) break;
2855 argstart=argstart+j+k;
2856 if ((strlen(&line[argstart])!=0)&&(gdp->gf==GF_TICK)) {
2858 sscanf(&line[argstart], ":%lf%n", &gdp->yrule,&j);
2861 if (strlen(&line[argstart])!=0)
2862 if (rrd_graph_legend(gdp,&line[++argstart])==0)
2863 rrd_set_error("Cannot parse legend in line: %s",line);
2869 sscanf(&line[argstart], DEF_NAM_FMT ":%n",gdp->vname,&j);
2871 rrd_set_error("Cannot parse vname in line: '%s'",line);
2875 if (rrd_graph_check_vname(im,gdp->vname,line)) return;
2877 sscanf(&line[argstart], CF_NAM_FMT ":%n",symname,&j);
2879 k=(j!=0)?rrd_graph_check_CF(im,symname,line):1;
2880 #define VIDX im->gdes[gdp->vidx]
2882 case -1: /* looks CF but is not really CF */
2883 if (VIDX.gf == GF_VDEF) rrd_clear_error();
2885 case 0: /* CF present and correct */
2886 if (VIDX.gf == GF_VDEF)
2887 rrd_set_error("Don't use CF when printing VDEF");
2890 case 1: /* CF not present */
2891 if (VIDX.gf == GF_VDEF) rrd_clear_error();
2892 else rrd_set_error("Printing DEF or CDEF needs CF");
2895 rrd_set_error("Oops, bug in GPRINT scanning");
2898 if (rrd_test_error()) break;
2900 if (strlen(&line[argstart])!=0) {
2901 if (rrd_graph_legend(gdp,&line[argstart])==0)
2902 rrd_set_error("Cannot parse legend in line: %s",line);
2903 } else rrd_set_error("No legend in (G)PRINT line: %s",line);
2904 strcpy(gdp->format, gdp->legend);
2910 sscanf(&line[argstart], DEF_NAM_FMT "=%n",gdp->vname,&j);
2912 rrd_set_error("Could not parse line: %s",line);
2915 if (find_var(im,gdp->vname)!=-1) {
2916 rrd_set_error("Variable '%s' in line '%s' already in use\n",
2923 argstart+=scan_for_col(&line[argstart],MAXPATH,gdp->rrd);
2925 sscanf(&line[argstart],
2926 ":" DS_NAM_FMT ":" CF_NAM_FMT "%n%*s%n",
2927 gdp->ds_nam, symname, &j, &k);
2928 if ((j==0)||(k!=0)) {
2929 rrd_set_error("Cannot parse DS or CF in '%s'",line);
2932 rrd_graph_check_CF(im,symname,line);
2936 sscanf(&line[argstart],DEF_NAM_FMT ",%n",vname,&j);
2938 rrd_set_error("Cannot parse vname in line '%s'",line);
2942 if (rrd_graph_check_vname(im,vname,line)) return;
2943 if ( im->gdes[gdp->vidx].gf != GF_DEF
2944 && im->gdes[gdp->vidx].gf != GF_CDEF) {
2945 rrd_set_error("variable '%s' not DEF nor "
2946 "CDEF in VDEF '%s'", vname,gdp->vname);
2949 vdef_parse(gdp,&line[argstart+strstart]);
2952 if (strstr(&line[argstart],":")!=NULL) {
2953 rrd_set_error("Error in RPN, line: %s",line);
2956 if ((gdp->rpnp = rpn_parse(
2961 rrd_set_error("invalid rpn expression in: %s",line);
2966 default: rrd_set_error("Big oops");
2968 if (rrd_test_error()) {
2975 rrd_set_error("can't make a graph without contents");
2976 im_free(im); /* ??? is this set ??? */
2981 rrd_graph_check_vname(image_desc_t *im, char *varname, char *err)
2983 if ((im->gdes[im->gdes_c-1].vidx=find_var(im,varname))==-1) {
2984 rrd_set_error("Unknown variable '%s' in %s",varname,err);
2990 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
2993 graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
2995 color=strstr(var,"#");
2998 rrd_set_error("Found no color in %s",err);
3007 rest=strstr(color,":");
3015 sscanf(color,"#%6x%n",&col,&n);
3016 col = (col << 8) + 0xff /* shift left by 8 */;
3017 if (n!=7) rrd_set_error("Color problem in %s",err);
3020 sscanf(color,"#%8x%n",&col,&n);
3023 rrd_set_error("Color problem in %s",err);
3025 if (rrd_test_error()) return 0;
3031 rrd_graph_check_CF(image_desc_t *im, char *symname, char *err)
3033 if ((im->gdes[im->gdes_c-1].cf=cf_conv(symname))==-1) {
3034 rrd_set_error("Unknown CF '%s' in %s",symname,err);
3040 rrd_graph_legend(graph_desc_t *gdp, char *line)
3044 i=scan_for_col(line,FMT_LEG_LEN,gdp->legend);
3046 return (strlen(&line[i])==0);
3050 int bad_format(char *fmt) {
3055 while (*ptr != '\0') {
3056 if (*ptr == '%') {ptr++;
3057 if (*ptr == '\0') return 1;
3058 while ((*ptr >= '0' && *ptr <= '9') || *ptr == '.') {
3061 if (*ptr == '\0') return 1;
3065 if (*ptr == '\0') return 1;
3066 if (*ptr == 'e' || *ptr == 'f') {
3068 } else { return 1; }
3070 else if (*ptr == 's' || *ptr == 'S' || *ptr == '%') { ++ptr; }
3079 vdef_parse(gdes,str)
3080 struct graph_desc_t *gdes;
3083 /* A VDEF currently is either "func" or "param,func"
3084 * so the parsing is rather simple. Change if needed.
3091 sscanf(str,"%le,%29[A-Z]%n",¶m,func,&n);
3092 if (n==strlen(str)) { /* matched */
3096 sscanf(str,"%29[A-Z]%n",func,&n);
3097 if (n==strlen(str)) { /* matched */
3100 rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3107 if (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3108 else if (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3109 else if (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3110 else if (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3111 else if (!strcmp("TOTAL", func)) gdes->vf.op = VDEF_TOTAL;
3112 else if (!strcmp("FIRST", func)) gdes->vf.op = VDEF_FIRST;
3113 else if (!strcmp("LAST", func)) gdes->vf.op = VDEF_LAST;
3115 rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3122 switch (gdes->vf.op) {
3124 if (isnan(param)) { /* no parameter given */
3125 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3131 if (param>=0.0 && param<=100.0) {
3132 gdes->vf.param = param;
3133 gdes->vf.val = DNAN; /* undefined */
3134 gdes->vf.when = 0; /* undefined */
3136 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3150 gdes->vf.param = DNAN;
3151 gdes->vf.val = DNAN;
3154 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3169 graph_desc_t *src,*dst;
3173 dst = &im->gdes[gdi];
3174 src = &im->gdes[dst->vidx];
3175 data = src->data + src->ds;
3176 steps = (src->end - src->start) / src->step;
3179 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3186 switch (dst->vf.op) {
3187 case VDEF_PERCENT: {
3188 rrd_value_t * array;
3192 if ((array = malloc(steps*sizeof(double)))==NULL) {
3193 rrd_set_error("malloc VDEV_PERCENT");
3196 for (step=0;step < steps; step++) {
3197 array[step]=data[step*src->ds_cnt];
3199 qsort(array,step,sizeof(double),vdef_percent_compar);
3201 field = (steps-1)*dst->vf.param/100;
3202 dst->vf.val = array[field];
3203 dst->vf.when = 0; /* no time component */
3205 for(step=0;step<steps;step++)
3206 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3212 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3213 if (step == steps) {
3217 dst->vf.val = data[step*src->ds_cnt];
3218 dst->vf.when = src->start + (step+1)*src->step;
3220 while (step != steps) {
3221 if (finite(data[step*src->ds_cnt])) {
3222 if (data[step*src->ds_cnt] > dst->vf.val) {
3223 dst->vf.val = data[step*src->ds_cnt];
3224 dst->vf.when = src->start + (step+1)*src->step;
3231 case VDEF_AVERAGE: {
3234 for (step=0;step<steps;step++) {
3235 if (finite(data[step*src->ds_cnt])) {
3236 sum += data[step*src->ds_cnt];
3241 if (dst->vf.op == VDEF_TOTAL) {
3242 dst->vf.val = sum*src->step;
3243 dst->vf.when = cnt*src->step; /* not really "when" */
3245 dst->vf.val = sum/cnt;
3246 dst->vf.when = 0; /* no time component */
3256 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3257 if (step == steps) {
3261 dst->vf.val = data[step*src->ds_cnt];
3262 dst->vf.when = src->start + (step+1)*src->step;
3264 while (step != steps) {
3265 if (finite(data[step*src->ds_cnt])) {
3266 if (data[step*src->ds_cnt] < dst->vf.val) {
3267 dst->vf.val = data[step*src->ds_cnt];
3268 dst->vf.when = src->start + (step+1)*src->step;
3275 /* The time value returned here is one step before the
3276 * actual time value. This is the start of the first
3280 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3281 if (step == steps) { /* all entries were NaN */
3285 dst->vf.val = data[step*src->ds_cnt];
3286 dst->vf.when = src->start + step*src->step;
3290 /* The time value returned here is the
3291 * actual time value. This is the end of the last
3295 while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3296 if (step < 0) { /* all entries were NaN */
3300 dst->vf.val = data[step*src->ds_cnt];
3301 dst->vf.when = src->start + (step+1)*src->step;
3308 /* NaN < -INF < finite_values < INF */
3310 vdef_percent_compar(a,b)
3313 /* Equality is not returned; this doesn't hurt except
3314 * (maybe) for a little performance.
3317 /* First catch NaN values. They are smallest */
3318 if (isnan( *(double *)a )) return -1;
3319 if (isnan( *(double *)b )) return 1;
3321 /* NaN doesn't reach this part so INF and -INF are extremes.
3322 * The sign from isinf() is compatible with the sign we return
3324 if (isinf( *(double *)a )) return isinf( *(double *)a );
3325 if (isinf( *(double *)b )) return isinf( *(double *)b );
3327 /* If we reach this, both values must be finite */
3328 if ( *(double *)a < *(double *)b ) return -1; else return 1;