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;
946 /* if min or max have not been asigned a value this is because
947 there was no data in the graph ... this is not good ...
948 lets set these to dummy values then ... */
950 if (isnan(minval)) minval = 0.0;
951 if (isnan(maxval)) maxval = 1.0;
953 /* adjust min and max values */
954 if (isnan(im->minval)
955 || ((!im->logarithmic && !im->rigid) /* don't adjust low-end with log scale */
956 && im->minval > minval))
958 if (isnan(im->maxval)
960 && im->maxval < maxval)){
962 im->maxval = maxval * 1.1;
966 /* make sure min and max are not equal */
967 if (im->minval == im->maxval) {
969 if (! im->logarithmic) {
973 /* make sure min and max are not both zero */
974 if (im->maxval == 0.0) {
984 /* identify the point where the first gridline, label ... gets placed */
988 time_t start, /* what is the initial time */
989 enum tmt_en baseint, /* what is the basic interval */
990 long basestep /* how many if these do we jump a time */
994 tm = *localtime(&start);
997 tm.tm_sec -= tm.tm_sec % basestep; break;
1000 tm.tm_min -= tm.tm_min % basestep;
1005 tm.tm_hour -= tm.tm_hour % basestep; break;
1007 /* we do NOT look at the basestep for this ... */
1010 tm.tm_hour = 0; break;
1012 /* we do NOT look at the basestep for this ... */
1016 tm.tm_mday -= tm.tm_wday -1; /* -1 because we want the monday */
1017 if (tm.tm_wday==0) tm.tm_mday -= 7; /* we want the *previous* monday */
1024 tm.tm_mon -= tm.tm_mon % basestep; break;
1032 tm.tm_year -= (tm.tm_year+1900) % basestep;
1037 /* identify the point where the next gridline, label ... gets placed */
1040 time_t current, /* what is the initial time */
1041 enum tmt_en baseint, /* what is the basic interval */
1042 long basestep /* how many if these do we jump a time */
1047 tm = *localtime(¤t);
1051 tm.tm_sec += basestep; break;
1053 tm.tm_min += basestep; break;
1055 tm.tm_hour += basestep; break;
1057 tm.tm_mday += basestep; break;
1059 tm.tm_mday += 7*basestep; break;
1061 tm.tm_mon += basestep; break;
1063 tm.tm_year += basestep;
1065 madetime = mktime(&tm);
1066 } while (madetime == -1); /* this is necessary to skip impssible times
1067 like the daylight saving time skips */
1073 /* calculate values required for PRINT and GPRINT functions */
1076 print_calc(image_desc_t *im, char ***prdata)
1078 long i,ii,validsteps;
1081 int graphelement = 0;
1084 double magfact = -1;
1088 if (im->imginfo) prlines++;
1089 for(i=0;i<im->gdes_c;i++){
1090 switch(im->gdes[i].gf){
1093 if(((*prdata) = rrd_realloc((*prdata),prlines*sizeof(char *)))==NULL){
1094 rrd_set_error("realloc prdata");
1098 /* PRINT and GPRINT can now print VDEF generated values.
1099 * There's no need to do any calculations on them as these
1100 * calculations were already made.
1102 vidx = im->gdes[i].vidx;
1103 if (im->gdes[vidx].gf==GF_VDEF) { /* simply use vals */
1104 printval = im->gdes[vidx].vf.val;
1105 printtime = im->gdes[vidx].vf.when;
1106 } else { /* need to calculate max,min,avg etcetera */
1107 max_ii =((im->gdes[vidx].end
1108 - im->gdes[vidx].start)
1109 / im->gdes[vidx].step
1110 * im->gdes[vidx].ds_cnt);
1113 for( ii=im->gdes[vidx].ds;
1115 ii+=im->gdes[vidx].ds_cnt){
1116 if (! finite(im->gdes[vidx].data[ii]))
1118 if (isnan(printval)){
1119 printval = im->gdes[vidx].data[ii];
1124 switch (im->gdes[i].cf){
1127 case CF_DEVSEASONAL:
1131 printval += im->gdes[vidx].data[ii];
1134 printval = min( printval, im->gdes[vidx].data[ii]);
1138 printval = max( printval, im->gdes[vidx].data[ii]);
1141 printval = im->gdes[vidx].data[ii];
1144 if (im->gdes[i].cf==CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1145 if (validsteps > 1) {
1146 printval = (printval / validsteps);
1149 } /* prepare printval */
1151 if (!strcmp(im->gdes[i].format,"%c")) { /* VDEF time print */
1152 if (im->gdes[i].gf == GF_PRINT){
1153 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1154 sprintf((*prdata)[prlines-2],"%s (%lu)",
1155 ctime(&printtime),printtime);
1156 (*prdata)[prlines-1] = NULL;
1158 sprintf(im->gdes[i].legend,"%s (%lu)",
1159 ctime(&printtime),printtime);
1163 if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) {
1164 /* Magfact is set to -1 upon entry to print_calc. If it
1165 * is still less than 0, then we need to run auto_scale.
1166 * Otherwise, put the value into the correct units. If
1167 * the value is 0, then do not set the symbol or magnification
1168 * so next the calculation will be performed again. */
1169 if (magfact < 0.0) {
1170 auto_scale(im,&printval,&si_symb,&magfact);
1171 if (printval == 0.0)
1174 printval /= magfact;
1176 *(++percent_s) = 's';
1177 } else if (strstr(im->gdes[i].format,"%s") != NULL) {
1178 auto_scale(im,&printval,&si_symb,&magfact);
1181 if (im->gdes[i].gf == GF_PRINT){
1182 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1183 if (bad_format(im->gdes[i].format)) {
1184 rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1187 #ifdef HAVE_SNPRINTF
1188 snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
1190 sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
1192 (*prdata)[prlines-1] = NULL;
1196 if (bad_format(im->gdes[i].format)) {
1197 rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1200 #ifdef HAVE_SNPRINTF
1201 snprintf(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
1203 sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
1225 return graphelement;
1229 /* place legends with color spots */
1231 leg_place(image_desc_t *im)
1234 int interleg = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1235 int box =im->text_prop[TEXT_PROP_LEGEND].size*1.5;
1236 int border = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1237 int fill=0, fill_last;
1239 int leg_x = border, leg_y = im->ygif;
1243 char prt_fctn; /*special printfunctions */
1246 if( !(im->extra_flags & NOLEGEND) ) {
1247 if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1248 rrd_set_error("malloc for legspace");
1252 for(i=0;i<im->gdes_c;i++){
1255 leg_cc = strlen(im->gdes[i].legend);
1257 /* is there a controle code ant the end of the legend string ? */
1258 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\') {
1259 prt_fctn = im->gdes[i].legend[leg_cc-1];
1261 im->gdes[i].legend[leg_cc] = '\0';
1265 /* remove exess space */
1266 while (prt_fctn=='g' &&
1268 im->gdes[i].legend[leg_cc-1]==' '){
1270 im->gdes[i].legend[leg_cc]='\0';
1273 legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1276 /* no interleg space if string ends in \g */
1277 fill += legspace[i];
1279 if (im->gdes[i].gf != GF_GPRINT &&
1280 im->gdes[i].gf != GF_COMMENT) {
1283 fill += gfx_get_text_width(fill+border,im->text_prop[TEXT_PROP_LEGEND].font,
1284 im->text_prop[TEXT_PROP_LEGEND].size,
1286 im->gdes[i].legend);
1291 /* who said there was a special tag ... ?*/
1292 if (prt_fctn=='g') {
1295 if (prt_fctn == '\0') {
1296 if (i == im->gdes_c -1 ) prt_fctn ='l';
1298 /* is it time to place the legends ? */
1299 if (fill > im->xgif - 2*border){
1314 if (prt_fctn != '\0'){
1316 if (leg_c >= 2 && prt_fctn == 'j') {
1317 glue = (im->xgif - fill - 2* border) / (leg_c-1);
1321 if (prt_fctn =='c') leg_x = (im->xgif - fill) / 2.0;
1322 if (prt_fctn =='r') leg_x = im->xgif - fill - border;
1324 for(ii=mark;ii<=i;ii++){
1325 if(im->gdes[ii].legend[0]=='\0')
1327 im->gdes[ii].leg_x = leg_x;
1328 im->gdes[ii].leg_y = leg_y;
1330 gfx_get_text_width(leg_x,im->text_prop[TEXT_PROP_LEGEND].font,
1331 im->text_prop[TEXT_PROP_LEGEND].size,
1333 im->gdes[ii].legend)
1336 if (im->gdes[ii].gf != GF_GPRINT &&
1337 im->gdes[ii].gf != GF_COMMENT)
1340 leg_y = leg_y + im->text_prop[TEXT_PROP_LEGEND].size*1.2;
1341 if (prt_fctn == 's') leg_y -= im->text_prop[TEXT_PROP_LEGEND].size*1.2;
1353 /* create a grid on the graph. it determines what to do
1354 from the values of xsize, start and end */
1356 /* the xaxis labels are determined from the number of seconds per pixel
1357 in the requested graph */
1362 horizontal_grid(gfx_canvas_t *canvas, image_desc_t *im)
1370 char graph_label[100];
1372 int labfact,gridind;
1373 int decimals, fractionals;
1378 range = im->maxval - im->minval;
1379 scaledrange = range / im->magfact;
1381 /* does the scale of this graph make it impossible to put lines
1382 on it? If so, give up. */
1383 if (isnan(scaledrange)) {
1387 /* find grid spaceing */
1389 if(isnan(im->ygridstep)){
1390 if(im->extra_flags & ALTYGRID) {
1391 /* find the value with max number of digits. Get number of digits */
1392 decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))));
1393 if(decimals <= 0) /* everything is small. make place for zero */
1396 fractionals = floor(log10(range));
1397 if(fractionals < 0) /* small amplitude. */
1398 sprintf(labfmt, "%%%d.%df", decimals - fractionals + 1, -fractionals + 1);
1400 sprintf(labfmt, "%%%d.1f", decimals + 1);
1401 gridstep = pow((double)10, (double)fractionals);
1402 if(gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1404 /* should have at least 5 lines but no more then 15 */
1405 if(range/gridstep < 5)
1407 if(range/gridstep > 15)
1409 if(range/gridstep > 5) {
1411 if(range/gridstep > 8)
1420 for(i=0;ylab[i].grid > 0;i++){
1421 pixel = im->ysize / (scaledrange / ylab[i].grid);
1422 if (gridind == -1 && pixel > 5) {
1429 if (pixel * ylab[gridind].lfac[i] >= 2 * im->text_prop[TEXT_PROP_AXIS].size) {
1430 labfact = ylab[gridind].lfac[i];
1435 gridstep = ylab[gridind].grid * im->magfact;
1438 gridstep = im->ygridstep;
1439 labfact = im->ylabfact;
1443 x1=im->xorigin+im->xsize;
1445 sgrid = (int)( im->minval / gridstep - 1);
1446 egrid = (int)( im->maxval / gridstep + 1);
1447 scaledstep = gridstep/im->magfact;
1448 for (i = sgrid; i <= egrid; i++){
1449 y0=ytr(im,gridstep*i);
1450 if ( y0 >= im->yorigin-im->ysize
1451 && y0 <= im->yorigin){
1452 if(i % labfact == 0){
1453 if (i==0 || im->symbol == ' ') {
1455 if(im->extra_flags & ALTYGRID) {
1456 sprintf(graph_label,labfmt,scaledstep*i);
1459 sprintf(graph_label,"%4.1f",scaledstep*i);
1462 sprintf(graph_label,"%4.0f",scaledstep*i);
1466 sprintf(graph_label,"%4.1f %c",scaledstep*i, im->symbol);
1468 sprintf(graph_label,"%4.0f %c",scaledstep*i, im->symbol);
1472 gfx_new_text ( canvas,
1473 x0-im->text_prop[TEXT_PROP_AXIS].size/1.5, y0,
1474 im->graph_col[GRC_FONT],
1475 im->text_prop[TEXT_PROP_AXIS].font,
1476 im->text_prop[TEXT_PROP_AXIS].size,
1477 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1479 gfx_new_line ( canvas,
1482 MGRIDWIDTH, im->graph_col[GRC_MGRID] );
1485 gfx_new_line ( canvas,
1488 GRIDWIDTH, im->graph_col[GRC_GRID] );
1496 /* logaritmic horizontal grid */
1498 horizontal_log_grid(gfx_canvas_t *canvas, image_desc_t *im)
1502 int minoridx=0, majoridx=0;
1503 char graph_label[100];
1505 double value, pixperstep, minstep;
1507 /* find grid spaceing */
1508 pixpex= (double)im->ysize / (log10(im->maxval) - log10(im->minval));
1510 if (isnan(pixpex)) {
1514 for(i=0;yloglab[i][0] > 0;i++){
1515 minstep = log10(yloglab[i][0]);
1516 for(ii=1;yloglab[i][ii+1] > 0;ii++){
1517 if(yloglab[i][ii+2]==0){
1518 minstep = log10(yloglab[i][ii+1])-log10(yloglab[i][ii]);
1522 pixperstep = pixpex * minstep;
1523 if(pixperstep > 5){minoridx = i;}
1524 if(pixperstep > 2 * im->text_prop[TEXT_PROP_LEGEND].size){majoridx = i;}
1528 x1=im->xorigin+im->xsize;
1529 /* paint minor grid */
1530 for (value = pow((double)10, log10(im->minval)
1531 - fmod(log10(im->minval),log10(yloglab[minoridx][0])));
1532 value <= im->maxval;
1533 value *= yloglab[minoridx][0]){
1534 if (value < im->minval) continue;
1536 while(yloglab[minoridx][++i] > 0){
1537 y0 = ytr(im,value * yloglab[minoridx][i]);
1538 if (y0 <= im->yorigin - im->ysize) break;
1539 gfx_new_line ( canvas,
1542 GRIDWIDTH, im->graph_col[GRC_GRID] );
1546 /* paint major grid and labels*/
1547 for (value = pow((double)10, log10(im->minval)
1548 - fmod(log10(im->minval),log10(yloglab[majoridx][0])));
1549 value <= im->maxval;
1550 value *= yloglab[majoridx][0]){
1551 if (value < im->minval) continue;
1553 while(yloglab[majoridx][++i] > 0){
1554 y0 = ytr(im,value * yloglab[majoridx][i]);
1555 if (y0 <= im->yorigin - im->ysize) break;
1556 gfx_new_line ( canvas,
1559 MGRIDWIDTH, im->graph_col[GRC_MGRID] );
1561 sprintf(graph_label,"%3.0e",value * yloglab[majoridx][i]);
1562 gfx_new_text ( canvas,
1563 x0-im->text_prop[TEXT_PROP_AXIS].size/1.5, y0,
1564 im->graph_col[GRC_FONT],
1565 im->text_prop[TEXT_PROP_AXIS].font,
1566 im->text_prop[TEXT_PROP_AXIS].size,
1567 im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1577 gfx_canvas_t *canvas,
1580 int xlab_sel; /* which sort of label and grid ? */
1583 char graph_label[100];
1584 double x0,y0,y1; /* points for filled graph and more*/
1587 /* the type of time grid is determined by finding
1588 the number of seconds per pixel in the graph */
1591 if(im->xlab_user.minsec == -1){
1592 factor=(im->end - im->start)/im->xsize;
1594 while ( xlab[xlab_sel+1].minsec != -1
1595 && xlab[xlab_sel+1].minsec <= factor){ xlab_sel++; }
1596 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
1597 im->xlab_user.gridst = xlab[xlab_sel].gridst;
1598 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
1599 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
1600 im->xlab_user.labtm = xlab[xlab_sel].labtm;
1601 im->xlab_user.labst = xlab[xlab_sel].labst;
1602 im->xlab_user.precis = xlab[xlab_sel].precis;
1603 im->xlab_user.stst = xlab[xlab_sel].stst;
1606 /* y coords are the same for every line ... */
1608 y1 = im->yorigin-im->ysize;
1611 /* paint the minor grid */
1612 for(ti = find_first_time(im->start,
1613 im->xlab_user.gridtm,
1614 im->xlab_user.gridst);
1616 ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
1618 /* are we inside the graph ? */
1619 if (ti < im->start || ti > im->end) continue;
1621 gfx_new_line(canvas,x0,y0+1, x0,y1-1,GRIDWIDTH, im->graph_col[GRC_GRID]);
1625 /* paint the major grid */
1626 for(ti = find_first_time(im->start,
1627 im->xlab_user.mgridtm,
1628 im->xlab_user.mgridst);
1630 ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
1632 /* are we inside the graph ? */
1633 if (ti < im->start || ti > im->end) continue;
1635 gfx_new_line(canvas,x0,y0+2, x0,y1-2,MGRIDWIDTH, im->graph_col[GRC_MGRID]);
1638 /* paint the labels below the graph */
1639 for(ti = find_first_time(im->start,
1640 im->xlab_user.labtm,
1641 im->xlab_user.labst);
1643 ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
1645 tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
1648 strftime(graph_label,99,im->xlab_user.stst,localtime(&tilab));
1650 # error "your libc has no strftime I guess we'll abort the exercise here."
1652 gfx_new_text ( canvas,
1653 xtr(im,tilab), y0+im->text_prop[TEXT_PROP_AXIS].size/1.5,
1654 im->graph_col[GRC_FONT],
1655 im->text_prop[TEXT_PROP_AXIS].font,
1656 im->text_prop[TEXT_PROP_AXIS].size,
1657 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP,
1668 gfx_canvas_t *canvas
1671 /* draw x and y axis */
1672 gfx_new_line ( canvas, im->xorigin+im->xsize,im->yorigin,
1673 im->xorigin+im->xsize,im->yorigin-im->ysize,
1674 GRIDWIDTH, im->graph_col[GRC_GRID]);
1676 gfx_new_line ( canvas, im->xorigin,im->yorigin-im->ysize,
1677 im->xorigin+im->xsize,im->yorigin-im->ysize,
1678 GRIDWIDTH, im->graph_col[GRC_GRID]);
1680 gfx_new_line ( canvas, im->xorigin-4,im->yorigin,
1681 im->xorigin+im->xsize+4,im->yorigin,
1682 MGRIDWIDTH, im->graph_col[GRC_GRID]);
1684 gfx_new_line ( canvas, im->xorigin,im->yorigin+4,
1685 im->xorigin,im->yorigin-im->ysize-4,
1686 MGRIDWIDTH, im->graph_col[GRC_GRID]);
1689 /* arrow for X axis direction */
1690 gfx_new_area ( canvas,
1691 im->xorigin+im->xsize+4, im->yorigin-3,
1692 im->xorigin+im->xsize+4, im->yorigin+3,
1693 im->xorigin+im->xsize+9, im->yorigin,
1694 im->graph_col[GRC_ARROW]);
1703 gfx_canvas_t *canvas
1710 double x0,x1,x2,x3,y0,y1,y2,y3; /* points for filled graph and more*/
1714 /* draw 3d border */
1715 node = gfx_new_area (canvas, 0,im->ygif, 0,0, im->xgif, 0,im->graph_col[GRC_SHADEA]);
1716 gfx_add_point( node , im->xgif - 2, 2 );
1717 gfx_add_point( node , 2,2 );
1718 gfx_add_point( node , 2,im->ygif-2 );
1719 gfx_add_point( node , 0,im->ygif );
1721 node = gfx_new_area (canvas, 0,im->ygif, im->xgif,im->ygif, im->xgif,0,im->graph_col[GRC_SHADEB]);
1722 gfx_add_point( node , im->xgif - 2, 2 );
1723 gfx_add_point( node , im->xgif-2,im->ygif-2 );
1724 gfx_add_point( node , 2,im->ygif-2 );
1725 gfx_add_point( node , 0,im->ygif );
1728 if (im->draw_x_grid == 1 )
1729 vertical_grid(canvas, im);
1731 if (im->draw_y_grid == 1){
1732 if(im->logarithmic){
1733 res = horizontal_log_grid(canvas,im);
1735 res = horizontal_grid(canvas,im);
1738 /* dont draw horizontal grid if there is no min and max val */
1740 char *nodata = "No Data found";
1741 gfx_new_text(canvas,im->xgif/2, (2*im->yorigin-im->ysize) / 2,
1742 im->graph_col[GRC_FONT],
1743 im->text_prop[TEXT_PROP_AXIS].font,
1744 im->text_prop[TEXT_PROP_AXIS].size,
1745 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
1750 /* yaxis description */
1751 gfx_new_text( canvas,
1752 7, (im->yorigin - im->ysize/2),
1753 im->graph_col[GRC_FONT],
1754 im->text_prop[TEXT_PROP_AXIS].font,
1755 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 270.0,
1756 GFX_H_CENTER, GFX_V_CENTER,
1760 gfx_new_text( canvas,
1761 im->xgif/2, im->text_prop[TEXT_PROP_TITLE].size*1.5,
1762 im->graph_col[GRC_FONT],
1763 im->text_prop[TEXT_PROP_TITLE].font,
1764 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
1765 GFX_H_CENTER, GFX_V_CENTER,
1769 if( !(im->extra_flags & NOLEGEND) ) {
1770 for(i=0;i<im->gdes_c;i++){
1771 if(im->gdes[i].legend[0] =='\0')
1774 if(im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT){
1775 x0 = im->gdes[i].leg_x;
1776 y0 = im->gdes[i].leg_y+1.0;
1783 node = gfx_new_area(canvas, x0,y0,x1,y1,x2,y2 ,im->gdes[i].col);
1784 gfx_add_point ( node, x3, y3 );
1785 gfx_add_point ( node, x0, y0 );
1786 node = gfx_new_line(canvas, x0,y0,x1,y1 ,GRIDWIDTH, im->graph_col[GRC_FRAME]);
1787 gfx_add_point ( node, x2, y2 );
1788 gfx_add_point ( node, x3, y3 );
1789 gfx_add_point ( node, x0, y0 );
1791 gfx_new_text ( canvas, x0+boxH+6, (y0+y2) / 2.0,
1792 im->graph_col[GRC_FONT],
1793 im->text_prop[TEXT_PROP_AXIS].font,
1794 im->text_prop[TEXT_PROP_AXIS].size,
1795 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
1796 im->gdes[i].legend );
1799 x0 = im->gdes[i].leg_x;
1800 y0 = im->gdes[i].leg_y;
1802 gfx_new_text ( canvas, x0, (y0+y2) / 2.0,
1803 im->graph_col[GRC_FONT],
1804 im->text_prop[TEXT_PROP_AXIS].font,
1805 im->text_prop[TEXT_PROP_AXIS].size,
1806 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
1807 im->gdes[i].legend );
1815 /*****************************************************
1816 * lazy check make sure we rely need to create this graph
1817 *****************************************************/
1819 int lazy_check(image_desc_t *im){
1822 struct stat gifstat;
1824 if (im->lazy == 0) return 0; /* no lazy option */
1825 if (stat(im->graphfile,&gifstat) != 0)
1826 return 0; /* can't stat */
1827 /* one pixel in the existing graph is more then what we would
1829 if (time(NULL) - gifstat.st_mtime >
1830 (im->end - im->start) / im->xsize)
1832 if ((fd = fopen(im->graphfile,"rb")) == NULL)
1833 return 0; /* the file does not exist */
1834 switch (im->imgformat) {
1836 size = GifSize(fd,&(im->xgif),&(im->ygif));
1839 size = PngSize(fd,&(im->xgif),&(im->ygif));
1847 /* draw that picture thing ... */
1849 graph_paint(image_desc_t *im, char ***calcpr)
1852 int lazy = lazy_check(im);
1853 int piechart = 0, PieSize, PieCenterX, PieCenterY;
1854 double PieStart=0.0;
1856 gfx_canvas_t *canvas;
1859 double areazero = 0.0;
1860 enum gf_en stack_gf = GF_PRINT;
1861 graph_desc_t *lastgdes = NULL;
1863 /* if we are lazy and there is nothing to PRINT ... quit now */
1864 if (lazy && im->prt_c==0) return 0;
1866 /* pull the data from the rrd files ... */
1868 if(data_fetch(im)==-1)
1871 /* evaluate VDEF and CDEF operations ... */
1872 if(data_calc(im)==-1)
1875 /* calculate and PRINT and GPRINT definitions. We have to do it at
1876 * this point because it will affect the length of the legends
1877 * if there are no graph elements we stop here ...
1878 * if we are lazy, try to quit ...
1880 i=print_calc(im,calcpr);
1882 if(i==0 || lazy) return 0;
1884 /* get actual drawing data and find min and max values*/
1885 if(data_proc(im)==-1)
1888 if(!im->logarithmic){si_unit(im);} /* identify si magnitude Kilo, Mega Giga ? */
1890 if(!im->rigid && ! im->logarithmic)
1891 expand_range(im); /* make sure the upper and lower limit are
1894 /* init xtr and ytr */
1895 /* determine the actual size of the gif to draw. The size given
1896 on the cmdline is the graph area. But we need more as we have
1897 draw labels and other things outside the graph area */
1900 im->xorigin = 10 + 9 * im->text_prop[TEXT_PROP_LEGEND].size;
1904 im->yorigin = 10 + im->ysize;
1908 if(im->title[0] != '\0')
1909 im->yorigin += im->text_prop[TEXT_PROP_TITLE].size*3+4;
1911 im->xgif= 20 +im->xsize + im->xorigin;
1912 im->ygif= im->yorigin+2* im->text_prop[TEXT_PROP_LEGEND].size;
1914 /* check if we need to draw a piechart */
1915 for(i=0;i<im->gdes_c;i++){
1916 if (im->gdes[i].gf == GF_PART) {
1923 if (im->xsize < im->ysize)
1924 PieSize = im->xsize;
1926 PieSize = im->ysize;
1927 im->xgif += PieSize + 50;
1929 PieCenterX = im->xorigin + im->xsize + 50 + PieSize/2;
1930 PieCenterY = im->yorigin - PieSize/2;
1933 /* determine where to place the legends onto the graphics.
1934 and set im->ygif to match space requirements for text */
1935 if(leg_place(im)==-1)
1938 canvas=gfx_new_canvas();
1940 /* the actual graph is created by going through the individual
1941 graph elements and then drawing them */
1943 node=gfx_new_area ( canvas,
1947 im->graph_col[GRC_BACK]);
1949 gfx_add_point(node,0, im->ygif);
1951 node=gfx_new_area ( canvas,
1952 im->xorigin, im->yorigin,
1953 im->xorigin + im->xsize, im->yorigin,
1954 im->xorigin + im->xsize, im->yorigin-im->ysize,
1955 im->graph_col[GRC_CANVAS]);
1957 gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
1960 node=gfx_new_area ( canvas,
1961 im->xorigin + im->xsize + 50, im->yorigin,
1962 im->xorigin + im->xsize + 50 + PieSize, im->yorigin,
1963 im->xorigin + im->xsize + 50 + PieSize, im->yorigin - PieSize,
1964 im->graph_col[GRC_CANVAS]);
1965 gfx_add_point(node,im->xorigin+im->xsize+50, im->yorigin - PieSize);
1968 if (im->minval > 0.0)
1969 areazero = im->minval;
1970 if (im->maxval < 0.0)
1971 areazero = im->maxval;
1973 axis_paint(im,canvas);
1976 for(i=0;i<im->gdes_c;i++){
1977 switch(im->gdes[i].gf){
1988 for (ii = 0; ii < im->xsize; ii++)
1990 if (!isnan(im->gdes[i].p_data[ii]) &&
1991 im->gdes[i].p_data[ii] > 0.0)
1993 /* generate a tick */
1994 gfx_new_line(canvas, im -> xorigin + ii,
1995 im -> yorigin - (im -> gdes[i].yrule * im -> ysize),
1999 im -> gdes[i].col );
2005 stack_gf = im->gdes[i].gf;
2007 /* fix data points at oo and -oo */
2008 for(ii=0;ii<im->xsize;ii++){
2009 if (isinf(im->gdes[i].p_data[ii])){
2010 if (im->gdes[i].p_data[ii] > 0) {
2011 im->gdes[i].p_data[ii] = im->maxval ;
2013 im->gdes[i].p_data[ii] = im->minval ;
2019 if (im->gdes[i].col != 0x0){
2020 /* GF_LINE and friend */
2021 if(stack_gf == GF_LINE ){
2023 for(ii=1;ii<im->xsize;ii++){
2024 if ( ! isnan(im->gdes[i].p_data[ii-1])
2025 && ! isnan(im->gdes[i].p_data[ii])){
2027 node = gfx_new_line(canvas,
2028 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2029 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2030 im->gdes[i].linewidth,
2033 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2042 for(ii=1;ii<im->xsize;ii++){
2044 if ( ! isnan(im->gdes[i].p_data[ii-1])
2045 && ! isnan(im->gdes[i].p_data[ii])){
2048 if (im->gdes[i].gf == GF_STACK) {
2049 ybase = ytr(im,lastgdes->p_data[ii-1]);
2051 ybase = ytr(im,areazero);
2054 node = gfx_new_area(canvas,
2055 ii-1+im->xorigin,ybase,
2056 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2057 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2061 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2065 if ( node != NULL && (ii+1==im->xsize || isnan(im->gdes[i].p_data[ii]) )){
2066 /* GF_AREA STACK type*/
2067 if (im->gdes[i].gf == GF_STACK ) {
2069 for (iii=ii-1;iii>area_start;iii--){
2070 gfx_add_point(node,iii+im->xorigin,ytr(im,lastgdes->p_data[iii]));
2073 gfx_add_point(node,ii+im->xorigin,ytr(im,areazero));
2078 } /* else GF_LINE */
2079 } /* if color != 0x0 */
2080 /* make sure we do not run into trouble when stacking on NaN */
2081 for(ii=0;ii<im->xsize;ii++){
2082 if (isnan(im->gdes[i].p_data[ii])) {
2085 ybase = ytr(im,lastgdes->p_data[ii-1]);
2087 if (isnan(ybase) || !lastgdes ){
2088 ybase = ytr(im,areazero);
2090 im->gdes[i].p_data[ii] = ybase;
2093 lastgdes = &(im->gdes[i]);
2099 /* This probably is not the most efficient algorithm...
2100 ** If you know how to help, please do!
2102 ** If you change this routine be aware that I optimized
2103 ** the following algorithm:
2105 ** Full circle == 100
2106 ** relative X-position is sin(2*pi * position/100)
2107 ** relative Y-position is cos(2*pi * position/100)
2109 ** Position is incremented from start to end in a number
2110 ** of steps. This number of steps corresponds with the
2111 ** size of the pie canvas, each step being 1/PieSize.
2114 if(isnan(im->gdes[i].yrule)) /* fetch variable */
2115 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2117 if (finite(im->gdes[i].yrule)) {
2118 d1 = 2 * M_PI / 100;
2119 d2 = im->gdes[i].yrule / PieSize;
2122 for (counter=0;counter<=PieSize;counter++) {
2123 d4 = d1 * (PieStart + d2 * counter);
2127 gfx_new_line(canvas,
2128 PieCenterX,PieCenterY ,
2129 PieCenterX+x,PieCenterY-y,
2133 PieStart += im->gdes[i].yrule;
2139 grid_paint(im,canvas);
2141 /* the RULES are the last thing to paint ... */
2142 for(i=0;i<im->gdes_c;i++){
2144 switch(im->gdes[i].gf){
2146 if(isnan(im->gdes[i].yrule)) { /* fetch variable */
2147 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2149 if(im->gdes[i].yrule >= im->minval
2150 && im->gdes[i].yrule <= im->maxval)
2151 gfx_new_line(canvas,
2152 im->xorigin,ytr(im,im->gdes[i].yrule),
2153 im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2154 1.0,im->gdes[i].col);
2157 if(im->gdes[i].xrule == 0) { /* fetch variable */
2158 im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
2160 if(im->gdes[i].xrule >= im->start
2161 && im->gdes[i].xrule <= im->end)
2162 gfx_new_line(canvas,
2163 xtr(im,im->gdes[i].xrule),im->yorigin,
2164 xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2165 1.0,im->gdes[i].col);
2173 if (strcmp(im->graphfile,"-")==0) {
2175 /* Change translation mode for stdout to BINARY */
2176 _setmode( _fileno( stdout ), O_BINARY );
2180 if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2181 rrd_set_error("Opening '%s' for write: %s",im->graphfile,
2186 switch (im->imgformat) {
2190 gfx_render_png (canvas,im->xgif,im->ygif,im->zoom,0x0,fo);
2193 if (strcmp(im->graphfile,"-") != 0)
2196 gfx_destroy(canvas);
2201 /*****************************************************
2203 *****************************************************/
2206 gdes_alloc(image_desc_t *im){
2208 long def_step = (im->end-im->start)/im->xsize;
2210 if (im->step > def_step) /* step can be increassed ... no decreassed */
2211 def_step = im->step;
2215 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2216 * sizeof(graph_desc_t)))==NULL){
2217 rrd_set_error("realloc graph_descs");
2222 im->gdes[im->gdes_c-1].step=def_step;
2223 im->gdes[im->gdes_c-1].start=im->start;
2224 im->gdes[im->gdes_c-1].end=im->end;
2225 im->gdes[im->gdes_c-1].vname[0]='\0';
2226 im->gdes[im->gdes_c-1].data=NULL;
2227 im->gdes[im->gdes_c-1].ds_namv=NULL;
2228 im->gdes[im->gdes_c-1].data_first=0;
2229 im->gdes[im->gdes_c-1].p_data=NULL;
2230 im->gdes[im->gdes_c-1].rpnp=NULL;
2231 im->gdes[im->gdes_c-1].col = 0x0;
2232 im->gdes[im->gdes_c-1].legend[0]='\0';
2233 im->gdes[im->gdes_c-1].rrd[0]='\0';
2234 im->gdes[im->gdes_c-1].ds=-1;
2235 im->gdes[im->gdes_c-1].p_data=NULL;
2239 /* copies input untill the first unescaped colon is found
2240 or until input ends. backslashes have to be escaped as well */
2242 scan_for_col(char *input, int len, char *output)
2247 input[inp] != ':' &&
2250 if (input[inp] == '\\' &&
2251 input[inp+1] != '\0' &&
2252 (input[inp+1] == '\\' ||
2253 input[inp+1] == ':')){
2254 output[outp++] = input[++inp];
2257 output[outp++] = input[inp];
2260 output[outp] = '\0';
2264 /* Some surgery done on this function, it became ridiculously big.
2266 ** - initializing now in rrd_graph_init()
2267 ** - options parsing now in rrd_graph_options()
2268 ** - script parsing now in rrd_graph_script()
2271 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize)
2275 rrd_graph_init(&im);
2277 rrd_graph_options(argc,argv,&im);
2278 if (rrd_test_error()) return -1;
2280 if (strlen(argv[optind])>=MAXPATH) {
2281 rrd_set_error("filename (including path) too long");
2284 strncpy(im.graphfile,argv[optind],MAXPATH-1);
2285 im.graphfile[MAXPATH-1]='\0';
2287 rrd_graph_script(argc,argv,&im);
2288 if (rrd_test_error()) return -1;
2290 /* Everything is now read and the actual work can start */
2293 if (graph_paint(&im,prdata)==-1){
2298 /* The image is generated and needs to be output.
2299 ** Also, if needed, print a line with information about the image.
2307 /* maybe prdata is not allocated yet ... lets do it now */
2308 if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
2309 rrd_set_error("malloc imginfo");
2313 if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
2315 rrd_set_error("malloc imginfo");
2318 filename=im.graphfile+strlen(im.graphfile);
2319 while(filename > im.graphfile) {
2320 if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
2324 sprintf((*prdata)[0],im.imginfo,filename,(long)(im.zoom*im.xgif),(long)(im.zoom*im.ygif));
2331 rrd_graph_init(image_desc_t *im)
2335 im->xlab_user.minsec = -1;
2341 im->ylegend[0] = '\0';
2342 im->title[0] = '\0';
2346 im->unitsexponent= 9999;
2351 im->logarithmic = 0;
2352 im->ygridstep = DNAN;
2353 im->draw_x_grid = 1;
2354 im->draw_y_grid = 1;
2360 im->imgformat = IF_GIF; /* we default to GIF output */
2362 for(i=0;i<DIM(graph_col);i++)
2363 im->graph_col[i]=graph_col[i];
2365 for(i=0;i<DIM(text_prop);i++){
2366 im->text_prop[i].size = text_prop[i].size;
2367 im->text_prop[i].font = text_prop[i].font;
2372 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
2375 char *parsetime_error = NULL;
2376 char scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
2377 time_t start_tmp=0,end_tmp=0;
2379 struct time_value start_tv, end_tv;
2382 parsetime("end-24h", &start_tv);
2383 parsetime("now", &end_tv);
2386 static struct option long_options[] =
2388 {"start", required_argument, 0, 's'},
2389 {"end", required_argument, 0, 'e'},
2390 {"x-grid", required_argument, 0, 'x'},
2391 {"y-grid", required_argument, 0, 'y'},
2392 {"vertical-label",required_argument,0,'v'},
2393 {"width", required_argument, 0, 'w'},
2394 {"height", required_argument, 0, 'h'},
2395 {"interlaced", no_argument, 0, 'i'},
2396 {"upper-limit",required_argument, 0, 'u'},
2397 {"lower-limit",required_argument, 0, 'l'},
2398 {"rigid", no_argument, 0, 'r'},
2399 {"base", required_argument, 0, 'b'},
2400 {"logarithmic",no_argument, 0, 'o'},
2401 {"color", required_argument, 0, 'c'},
2402 {"font", required_argument, 0, 'n'},
2403 {"title", required_argument, 0, 't'},
2404 {"imginfo", required_argument, 0, 'f'},
2405 {"imgformat", required_argument, 0, 'a'},
2406 {"lazy", no_argument, 0, 'z'},
2407 {"zoom", required_argument, 0, 'm'},
2408 {"no-legend", no_argument, 0, 'g'},
2409 {"alt-y-grid", no_argument, 0, 257 },
2410 {"alt-autoscale", no_argument, 0, 258 },
2411 {"alt-autoscale-max", no_argument, 0, 259 },
2412 {"units-exponent",required_argument, 0, 260},
2413 {"step", required_argument, 0, 261},
2415 int option_index = 0;
2419 opt = getopt_long(argc, argv,
2420 "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:z:g",
2421 long_options, &option_index);
2428 im->extra_flags |= ALTYGRID;
2431 im->extra_flags |= ALTAUTOSCALE;
2434 im->extra_flags |= ALTAUTOSCALE_MAX;
2437 im->extra_flags |= NOLEGEND;
2440 im->unitsexponent = atoi(optarg);
2443 im->step = atoi(optarg);
2446 if ((parsetime_error = parsetime(optarg, &start_tv))) {
2447 rrd_set_error( "start time: %s", parsetime_error );
2452 if ((parsetime_error = parsetime(optarg, &end_tv))) {
2453 rrd_set_error( "end time: %s", parsetime_error );
2458 if(strcmp(optarg,"none") == 0){
2464 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
2466 &im->xlab_user.gridst,
2468 &im->xlab_user.mgridst,
2470 &im->xlab_user.labst,
2471 &im->xlab_user.precis,
2472 &stroff) == 7 && stroff != 0){
2473 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
2474 if((im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
2475 rrd_set_error("unknown keyword %s",scan_gtm);
2477 } else if ((im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
2478 rrd_set_error("unknown keyword %s",scan_mtm);
2480 } else if ((im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
2481 rrd_set_error("unknown keyword %s",scan_ltm);
2484 im->xlab_user.minsec = 1;
2485 im->xlab_user.stst = im->xlab_form;
2487 rrd_set_error("invalid x-grid format");
2493 if(strcmp(optarg,"none") == 0){
2501 &im->ylabfact) == 2) {
2502 if(im->ygridstep<=0){
2503 rrd_set_error("grid step must be > 0");
2505 } else if (im->ylabfact < 1){
2506 rrd_set_error("label factor must be > 0");
2510 rrd_set_error("invalid y-grid format");
2515 strncpy(im->ylegend,optarg,150);
2516 im->ylegend[150]='\0';
2519 im->maxval = atof(optarg);
2522 im->minval = atof(optarg);
2525 im->base = atol(optarg);
2526 if(im->base != 1024 && im->base != 1000 ){
2527 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
2532 long_tmp = atol(optarg);
2533 if (long_tmp < 10) {
2534 rrd_set_error("width below 10 pixels");
2537 im->xsize = long_tmp;
2540 long_tmp = atol(optarg);
2541 if (long_tmp < 10) {
2542 rrd_set_error("height below 10 pixels");
2545 im->ysize = long_tmp;
2554 im->imginfo = optarg;
2557 if((im->imgformat = if_conv(optarg)) == -1) {
2558 rrd_set_error("unsupported graphics format '%s'",optarg);
2566 im->logarithmic = 1;
2567 if (isnan(im->minval))
2573 col_nam,&color) == 2){
2575 if((ci=grc_conv(col_nam)) != -1){
2576 im->graph_col[ci]=color;
2578 rrd_set_error("invalid color name '%s'",col_nam);
2581 rrd_set_error("invalid color def format");
2586 /* originally this used char *prop = "" and
2587 ** char *font = "dummy" however this results
2588 ** in a SEG fault, at least on RH7.1
2590 ** The current implementation isn't proper
2591 ** either, font is never freed and prop uses
2592 ** a fixed width string
2601 prop,&size,font) == 3){
2603 if((sindex=text_prop_conv(prop)) != -1){
2604 im->text_prop[sindex].size=size;
2605 im->text_prop[sindex].font=font;
2606 if (sindex==0) { /* the default */
2607 im->text_prop[TEXT_PROP_TITLE].size=size;
2608 im->text_prop[TEXT_PROP_TITLE].font=font;
2609 im->text_prop[TEXT_PROP_AXIS].size=size;
2610 im->text_prop[TEXT_PROP_AXIS].font=font;
2611 im->text_prop[TEXT_PROP_UNIT].size=size;
2612 im->text_prop[TEXT_PROP_UNIT].font=font;
2613 im->text_prop[TEXT_PROP_LEGEND].size=size;
2614 im->text_prop[TEXT_PROP_LEGEND].font=font;
2617 rrd_set_error("invalid fonttag '%s'",prop);
2621 rrd_set_error("invalid text property format");
2627 im->zoom= atof(optarg);
2628 if (im->zoom <= 0.0) {
2629 rrd_set_error("zoom factor must be > 0");
2634 strncpy(im->title,optarg,150);
2635 im->title[150]='\0';
2640 rrd_set_error("unknown option '%c'", optopt);
2642 rrd_set_error("unknown option '%s'",argv[optind-1]);
2647 if (optind >= argc) {
2648 rrd_set_error("missing filename");
2652 if (im->logarithmic == 1 && (im->minval <= 0 || isnan(im->minval))){
2653 rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");
2657 if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
2658 /* error string is set in parsetime.c */
2662 if (start_tmp < 3600*24*365*10){
2663 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
2667 if (end_tmp < start_tmp) {
2668 rrd_set_error("start (%ld) should be less than end (%ld)",
2669 start_tmp, end_tmp);
2673 im->start = start_tmp;
2678 rrd_graph_script(int argc, char *argv[], image_desc_t *im)
2682 int linepass = 0; /* stack must follow LINE*, AREA or STACK */
2684 for (i=optind+1;i<argc;i++) {
2689 char funcname[10],vname[MAX_VNAME_LEN+1],sep[1];
2694 /* Each command is one element from *argv[], we call this "line".
2696 ** Each command defines the most current gdes inside struct im.
2697 ** In stead of typing "im->gdes[im->gdes_c-1]" we use "gdp".
2700 gdp=&im->gdes[im->gdes_c-1];
2703 /* function:newvname=string[:ds-name:CF] for xDEF
2704 ** function:vname[#color[:string]] for LINEx,AREA,STACK
2705 ** function:vname#color[:num[:string]] for TICK
2706 ** function:vname-or-num#color[:string] for xRULE,PART
2707 ** function:vname:CF:string for xPRINT
2708 ** function:string for COMMENT
2712 sscanf(line, "%10[A-Z0-9]:%n", funcname,&argstart);
2714 rrd_set_error("Cannot parse function in line: %s",line);
2718 if(sscanf(funcname,"LINE%lf",&linewidth)){
2719 im->gdes[im->gdes_c-1].gf = GF_LINE;
2720 im->gdes[im->gdes_c-1].linewidth = linewidth;
2722 if ((gdp->gf=gf_conv(funcname))==-1) {
2723 rrd_set_error("'%s' is not a valid function name",funcname);
2729 /* If the error string is set, we exit at the end of the switch */
2732 if (rrd_graph_legend(gdp,&line[argstart])==0)
2733 rrd_set_error("Cannot parse comment in line: %s",line);
2739 sscanf(&line[argstart], "%lf%n#%n", &d, &j, &k);
2740 sscanf(&line[argstart], DEF_NAM_FMT "%n#%n", vname, &l, &m);
2742 rrd_set_error("Cannot parse name or num in line: %s",line);
2749 } else if (!rrd_graph_check_vname(im,vname,line)) {
2753 } else break; /* exit due to wrong vname */
2754 if ((j=rrd_graph_color(im,&line[argstart],line,0))==0) break;
2756 if (strlen(&line[argstart])!=0) {
2757 if (rrd_graph_legend(gdp,&line[++argstart])==0)
2758 rrd_set_error("Cannot parse comment in line: %s",line);
2763 rrd_set_error("STACK must follow another graphing element");
2771 sscanf(&line[argstart],DEF_NAM_FMT"%n%1[#:]%n",vname,&j,sep,&k);
2773 rrd_set_error("Cannot parse vname in line: %s",line);
2774 else if (rrd_graph_check_vname(im,vname,line))
2775 rrd_set_error("Undefined vname '%s' in line: %s",line);
2777 k=rrd_graph_color(im,&line[argstart],line,1);
2778 if (rrd_test_error()) break;
2779 argstart=argstart+j+k;
2780 if ((strlen(&line[argstart])!=0)&&(gdp->gf==GF_TICK)) {
2782 sscanf(&line[argstart], ":%lf%n", &gdp->yrule,&j);
2785 if (strlen(&line[argstart])!=0)
2786 if (rrd_graph_legend(gdp,&line[++argstart])==0)
2787 rrd_set_error("Cannot parse legend in line: %s",line);
2793 sscanf(&line[argstart], DEF_NAM_FMT ":%n",gdp->vname,&j);
2795 rrd_set_error("Cannot parse vname in line: '%s'",line);
2799 if (rrd_graph_check_vname(im,gdp->vname,line)) return;
2801 sscanf(&line[argstart], CF_NAM_FMT ":%n",symname,&j);
2803 k=(j!=0)?rrd_graph_check_CF(im,symname,line):1;
2804 #define VIDX im->gdes[gdp->vidx]
2806 case -1: /* looks CF but is not really CF */
2807 if (VIDX.gf == GF_VDEF) rrd_clear_error();
2809 case 0: /* CF present and correct */
2810 if (VIDX.gf == GF_VDEF)
2811 rrd_set_error("Don't use CF when printing VDEF");
2814 case 1: /* CF not present */
2815 if (VIDX.gf == GF_VDEF) rrd_clear_error();
2816 else rrd_set_error("Printing DEF or CDEF needs CF");
2819 rrd_set_error("Oops, bug in GPRINT scanning");
2822 if (rrd_test_error()) break;
2824 if (strlen(&line[argstart])!=0) {
2825 if (rrd_graph_legend(gdp,&line[argstart])==0)
2826 rrd_set_error("Cannot parse legend in line: %s",line);
2827 } else rrd_set_error("No legend in (G)PRINT line: %s",line);
2828 strcpy(gdp->format, gdp->legend);
2834 sscanf(&line[argstart], DEF_NAM_FMT "=%n",gdp->vname,&j);
2836 rrd_set_error("Could not parse line: %s",line);
2839 if (find_var(im,gdp->vname)!=-1) {
2840 rrd_set_error("Variable '%s' in line '%s' already in use\n",
2847 argstart+=scan_for_col(&line[argstart],MAXPATH,gdp->rrd);
2849 sscanf(&line[argstart],
2850 ":" DS_NAM_FMT ":" CF_NAM_FMT "%n%*s%n",
2851 gdp->ds_nam, symname, &j, &k);
2852 if ((j==0)||(k!=0)) {
2853 rrd_set_error("Cannot parse DS or CF in '%s'",line);
2856 rrd_graph_check_CF(im,symname,line);
2860 sscanf(&line[argstart],DEF_NAM_FMT ",%n",vname,&j);
2862 rrd_set_error("Cannot parse vname in line '%s'",line);
2866 if (rrd_graph_check_vname(im,vname,line)) return;
2867 if ( im->gdes[gdp->vidx].gf != GF_DEF
2868 && im->gdes[gdp->vidx].gf != GF_CDEF) {
2869 rrd_set_error("variable '%s' not DEF nor "
2870 "CDEF in VDEF '%s'", vname,gdp->vname);
2873 vdef_parse(gdp,&line[argstart+strstart]);
2876 if (strstr(&line[argstart],":")!=NULL) {
2877 rrd_set_error("Error in RPN, line: %s",line);
2880 if ((gdp->rpnp = rpn_parse(
2885 rrd_set_error("invalid rpn expression in: %s",line);
2890 default: rrd_set_error("Big oops");
2892 if (rrd_test_error()) {
2899 rrd_set_error("can't make a graph without contents");
2900 im_free(im); /* ??? is this set ??? */
2905 rrd_graph_check_vname(image_desc_t *im, char *varname, char *err)
2907 if ((im->gdes[im->gdes_c-1].vidx=find_var(im,varname))==-1) {
2908 rrd_set_error("Unknown variable '%s' in %s",varname,err);
2914 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
2917 graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
2919 color=strstr(var,"#");
2922 rrd_set_error("Found no color in %s",err);
2931 rest=strstr(color,":");
2939 sscanf(color,"#%6x%n",&col,&n);
2940 col = (col << 8) + 0xff /* shift left by 8 */;
2941 if (n!=7) rrd_set_error("Color problem in %s",err);
2944 sscanf(color,"#%8x%n",&col,&n);
2947 rrd_set_error("Color problem in %s",err);
2949 if (rrd_test_error()) return 0;
2955 rrd_graph_check_CF(image_desc_t *im, char *symname, char *err)
2957 if ((im->gdes[im->gdes_c-1].cf=cf_conv(symname))==-1) {
2958 rrd_set_error("Unknown CF '%s' in %s",symname,err);
2964 rrd_graph_legend(graph_desc_t *gdp, char *line)
2968 i=scan_for_col(line,FMT_LEG_LEN,gdp->legend);
2970 return (strlen(&line[i])==0);
2974 int bad_format(char *fmt) {
2979 while (*ptr != '\0') {
2980 if (*ptr == '%') {ptr++;
2981 if (*ptr == '\0') return 1;
2982 while ((*ptr >= '0' && *ptr <= '9') || *ptr == '.') {
2985 if (*ptr == '\0') return 1;
2989 if (*ptr == '\0') return 1;
2990 if (*ptr == 'e' || *ptr == 'f') {
2992 } else { return 1; }
2994 else if (*ptr == 's' || *ptr == 'S' || *ptr == '%') { ++ptr; }
3003 vdef_parse(gdes,str)
3004 struct graph_desc_t *gdes;
3007 /* A VDEF currently is either "func" or "param,func"
3008 * so the parsing is rather simple. Change if needed.
3015 sscanf(str,"%le,%29[A-Z]%n",¶m,func,&n);
3016 if (n==strlen(str)) { /* matched */
3020 sscanf(str,"%29[A-Z]%n",func,&n);
3021 if (n==strlen(str)) { /* matched */
3024 rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3031 if (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3032 else if (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3033 else if (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3034 else if (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3035 else if (!strcmp("TOTAL", func)) gdes->vf.op = VDEF_TOTAL;
3036 else if (!strcmp("FIRST", func)) gdes->vf.op = VDEF_FIRST;
3037 else if (!strcmp("LAST", func)) gdes->vf.op = VDEF_LAST;
3039 rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3046 switch (gdes->vf.op) {
3048 if (isnan(param)) { /* no parameter given */
3049 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3055 if (param>=0.0 && param<=100.0) {
3056 gdes->vf.param = param;
3057 gdes->vf.val = DNAN; /* undefined */
3058 gdes->vf.when = 0; /* undefined */
3060 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3074 gdes->vf.param = DNAN;
3075 gdes->vf.val = DNAN;
3078 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3093 graph_desc_t *src,*dst;
3097 dst = &im->gdes[gdi];
3098 src = &im->gdes[dst->vidx];
3099 data = src->data + src->ds;
3100 steps = (src->end - src->start) / src->step;
3103 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3110 switch (dst->vf.op) {
3111 case VDEF_PERCENT: {
3112 rrd_value_t * array;
3116 if ((array = malloc(steps*sizeof(double)))==NULL) {
3117 rrd_set_error("malloc VDEV_PERCENT");
3120 for (step=0;step < steps; step++) {
3121 array[step]=data[step*src->ds_cnt];
3123 qsort(array,step,sizeof(double),vdef_percent_compar);
3125 field = (steps-1)*dst->vf.param/100;
3126 dst->vf.val = array[field];
3127 dst->vf.when = 0; /* no time component */
3129 for(step=0;step<steps;step++)
3130 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3136 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3137 if (step == steps) {
3141 dst->vf.val = data[step*src->ds_cnt];
3142 dst->vf.when = src->start + (step+1)*src->step;
3144 while (step != steps) {
3145 if (finite(data[step*src->ds_cnt])) {
3146 if (data[step*src->ds_cnt] > dst->vf.val) {
3147 dst->vf.val = data[step*src->ds_cnt];
3148 dst->vf.when = src->start + (step+1)*src->step;
3155 case VDEF_AVERAGE: {
3158 for (step=0;step<steps;step++) {
3159 if (finite(data[step*src->ds_cnt])) {
3160 sum += data[step*src->ds_cnt];
3165 if (dst->vf.op == VDEF_TOTAL) {
3166 dst->vf.val = sum*src->step;
3167 dst->vf.when = cnt*src->step; /* not really "when" */
3169 dst->vf.val = sum/cnt;
3170 dst->vf.when = 0; /* no time component */
3180 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3181 if (step == steps) {
3185 dst->vf.val = data[step*src->ds_cnt];
3186 dst->vf.when = src->start + (step+1)*src->step;
3188 while (step != steps) {
3189 if (finite(data[step*src->ds_cnt])) {
3190 if (data[step*src->ds_cnt] < dst->vf.val) {
3191 dst->vf.val = data[step*src->ds_cnt];
3192 dst->vf.when = src->start + (step+1)*src->step;
3199 /* The time value returned here is one step before the
3200 * actual time value. This is the start of the first
3204 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3205 if (step == steps) { /* all entries were NaN */
3209 dst->vf.val = data[step*src->ds_cnt];
3210 dst->vf.when = src->start + step*src->step;
3214 /* The time value returned here is the
3215 * actual time value. This is the end of the last
3219 while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3220 if (step < 0) { /* all entries were NaN */
3224 dst->vf.val = data[step*src->ds_cnt];
3225 dst->vf.when = src->start + (step+1)*src->step;
3232 /* NaN < -INF < finite_values < INF */
3234 vdef_percent_compar(a,b)
3237 /* Equality is not returned; this doesn't hurt except
3238 * (maybe) for a little performance.
3241 /* First catch NaN values. They are smallest */
3242 if (isnan( *(double *)a )) return -1;
3243 if (isnan( *(double *)b )) return 1;
3245 /* NaN doesn't reach this part so INF and -INF are extremes.
3246 * The sign from isinf() is compatible with the sign we return
3248 if (isinf( *(double *)a )) return isinf( *(double *)a );
3249 if (isinf( *(double *)b )) return isinf( *(double *)b );
3251 /* If we reach this, both values must be finite */
3252 if ( *(double *)a < *(double *)b ) return -1; else return 1;