1 /****************************************************************************
2 * RRDtool 1.0.33 Copyright Tobias Oetiker, 1997 - 2000
3 ****************************************************************************
4 * rrd__graph.c make creates ne rrds
5 ****************************************************************************/
12 #include <gdlucidan10.h>
13 #include <gdlucidab12.h>
20 #include "rrd_graph.h"
21 #include "rrd_graph_helper.h"
23 #define SmallFont gdLucidaNormal10
24 #define LargeFont gdLucidaBold12
29 # define DPRINT(x) (void)(printf x, printf("\n"))
35 {0, TMT_SECOND,30, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
36 {2, TMT_MINUTE,1, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
37 {5, TMT_MINUTE,2, TMT_MINUTE,10, TMT_MINUTE,10, 0,"%H:%M"},
38 {10, TMT_MINUTE,5, TMT_MINUTE,20, TMT_MINUTE,20, 0,"%H:%M"},
39 {30, TMT_MINUTE,10, TMT_HOUR,1, TMT_HOUR,1, 0,"%H:%M"},
40 {60, TMT_MINUTE,30, TMT_HOUR,2, TMT_HOUR,2, 0,"%H:%M"},
41 {180, TMT_HOUR,1, TMT_HOUR,6, TMT_HOUR,6, 0,"%H:%M"},
42 /*{300, TMT_HOUR,3, TMT_HOUR,12, TMT_HOUR,12, 12*3600,"%a %p"}, this looks silly*/
43 {600, TMT_HOUR,6, TMT_DAY,1, TMT_DAY,1, 24*3600,"%a"},
44 {1800, TMT_HOUR,12, TMT_DAY,1, TMT_DAY,2, 24*3600,"%a"},
45 {3600, TMT_DAY,1, TMT_WEEK,1, TMT_WEEK,1, 7*24*3600,"Week %W"},
46 {3*3600, TMT_WEEK,1, TMT_MONTH,1, TMT_WEEK,2, 7*24*3600,"Week %W"},
47 {6*3600, TMT_MONTH,1, TMT_MONTH,1, TMT_MONTH,1, 30*24*3600,"%b"},
48 {48*3600, TMT_MONTH,1, TMT_MONTH,3, TMT_MONTH,3, 30*24*3600,"%b"},
49 {10*24*3600, TMT_YEAR,1, TMT_YEAR,1, TMT_YEAR,1, 365*24*3600,"%y"},
50 {-1,TMT_MONTH,0,TMT_MONTH,0,TMT_MONTH,0,0,""}
53 /* sensible logarithmic y label intervals ...
54 the first element of each row defines the possible starting points on the
55 y axis ... the other specify the */
57 double yloglab[][12]= {{ 1e9, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
58 { 1e3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
59 { 1e1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
60 /* { 1e1, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, */
61 { 1e1, 1, 2.5, 5, 7.5, 0, 0, 0, 0, 0, 0, 0 },
62 { 1e1, 1, 2, 4, 6, 8, 0, 0, 0, 0, 0, 0 },
63 { 1e1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0 },
64 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }};
66 /* sensible y label intervals ...*/
85 col_trip_t graph_col[] = { /* default colors */
86 {255,255,255,-1}, /* canvas */
87 {245,245,245,-1}, /* background */
88 {200,200,200,-1}, /* shade A */
89 {150,150,150,-1}, /* shade B */
90 {140,140,140,-1}, /* grid */
91 {130,30,30,-1}, /* major grid */
92 {0,0,0,-1}, /* font */
93 {0,0,0,-1}, /* frame */
94 {255,0,0,-1} /*arrow*/
98 /* translate time values into x coordinates */
99 /*#define xtr(x) (int)((double)im->xorigin \
100 + ((double) im->xsize / (double)(im->end - im->start) ) \
101 * ((double)(x) - im->start)+0.5) */
102 /* initialize with xtr(im,0); */
104 xtr(image_desc_t *im,time_t mytime){
107 pixie = (double) im->xsize / (double)(im->end - im->start);
110 return (int)((double)im->xorigin
111 + pixie * ( mytime - im->start ) );
114 /* translate data values into y coordinates */
116 /* #define ytr(x) (int)((double)im->yorigin \
117 - ((double) im->ysize / (im->maxval - im->minval) ) \
118 * ((double)(x) - im->minval)+0.5) */
120 ytr(image_desc_t *im, double value){
125 pixie = (double) im->ysize / (im->maxval - im->minval);
127 pixie = (double) im->ysize / (log10(im->maxval) - log10(im->minval));
129 } else if(!im->logarithmic) {
130 yval = im->yorigin - pixie * (value - im->minval) + 0.5;
132 if (value < im->minval) {
135 yval = im->yorigin - pixie * (log10(value) - log10(im->minval)) + 0.5;
138 /* make sure we don't return anything too unreasonable. GD lib can
139 get terribly slow when drawing lines outside its scope. This is
140 especially problematic in connection with the rigid option */
143 } else if ((int)yval > im->yorigin) {
144 return im->yorigin+2;
145 } else if ((int) yval < im->yorigin - im->ysize){
146 return im->yorigin - im->ysize - 2;
154 /* conversion function for symbolic entry names */
157 #define conv_if(VV,VVV) \
158 if (strcmp(#VV, string) == 0) return VVV ;
160 enum gf_en gf_conv(char *string){
162 conv_if(PRINT,GF_PRINT)
163 conv_if(GPRINT,GF_GPRINT)
164 conv_if(COMMENT,GF_COMMENT)
165 conv_if(HRULE,GF_HRULE)
166 conv_if(VRULE,GF_VRULE)
167 conv_if(LINE1,GF_LINE1)
168 conv_if(LINE2,GF_LINE2)
169 conv_if(LINE3,GF_LINE3)
170 conv_if(AREA,GF_AREA)
171 conv_if(STACK,GF_STACK)
172 conv_if(TICK,GF_TICK)
174 conv_if(CDEF,GF_CDEF)
175 conv_if(VDEF,GF_VDEF)
180 enum if_en if_conv(char *string){
188 enum tmt_en tmt_conv(char *string){
190 conv_if(SECOND,TMT_SECOND)
191 conv_if(MINUTE,TMT_MINUTE)
192 conv_if(HOUR,TMT_HOUR)
194 conv_if(WEEK,TMT_WEEK)
195 conv_if(MONTH,TMT_MONTH)
196 conv_if(YEAR,TMT_YEAR)
200 enum grc_en grc_conv(char *string){
202 conv_if(BACK,GRC_BACK)
203 conv_if(CANVAS,GRC_CANVAS)
204 conv_if(SHADEA,GRC_SHADEA)
205 conv_if(SHADEB,GRC_SHADEB)
206 conv_if(GRID,GRC_GRID)
207 conv_if(MGRID,GRC_MGRID)
208 conv_if(FONT,GRC_FONT)
209 conv_if(FRAME,GRC_FRAME)
210 conv_if(ARROW,GRC_ARROW)
220 im_free(image_desc_t *im)
223 if (im == NULL) return 0;
224 for(i=0;i<im->gdes_c;i++){
225 if (im->gdes[i].data_first){
226 /* careful here, because a single pointer can occur several times */
227 free (im->gdes[i].data);
228 if (im->gdes[i].ds_namv){
229 for (ii=0;ii<im->gdes[i].ds_cnt;ii++)
230 free(im->gdes[i].ds_namv[ii]);
231 free(im->gdes[i].ds_namv);
234 free (im->gdes[i].p_data);
235 free (im->gdes[i].rpnp);
241 /* find SI magnitude symbol for the given number*/
244 image_desc_t *im, /* image description */
251 char *symbol[] = {"a", /* 10e-18 Atto */
252 "f", /* 10e-15 Femto */
253 "p", /* 10e-12 Pico */
254 "n", /* 10e-9 Nano */
255 "u", /* 10e-6 Micro */
256 "m", /* 10e-3 Milli */
261 "T", /* 10e12 Tera */
262 "P", /* 10e15 Peta */
268 if (*value == 0.0 || isnan(*value) ) {
272 sindex = floor(log(fabs(*value))/log((double)im->base));
273 *magfact = pow((double)im->base, (double)sindex);
274 (*value) /= (*magfact);
276 if ( sindex <= symbcenter && sindex >= -symbcenter) {
277 (*symb_ptr) = symbol[sindex+symbcenter];
285 /* find SI magnitude symbol for the numbers on the y-axis*/
288 image_desc_t *im /* image description */
292 char symbol[] = {'a', /* 10e-18 Atto */
293 'f', /* 10e-15 Femto */
294 'p', /* 10e-12 Pico */
295 'n', /* 10e-9 Nano */
296 'u', /* 10e-6 Micro */
297 'm', /* 10e-3 Milli */
302 'T', /* 10e12 Tera */
303 'P', /* 10e15 Peta */
309 if (im->unitsexponent != 9999) {
310 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
311 digits = floor(im->unitsexponent / 3);
313 digits = floor( log( max( fabs(im->minval),fabs(im->maxval)))/log((double)im->base));
315 im->magfact = pow((double)im->base , digits);
318 printf("digits %6.3f im->magfact %6.3f\n",digits,im->magfact);
321 if ( ((digits+symbcenter) < sizeof(symbol)) &&
322 ((digits+symbcenter) >= 0) )
323 im->symbol = symbol[(int)digits+symbcenter];
328 /* move min and max values around to become sensible */
331 expand_range(image_desc_t *im)
333 double sensiblevalues[] ={1000.0,900.0,800.0,750.0,700.0,
334 600.0,500.0,400.0,300.0,250.0,
335 200.0,125.0,100.0,90.0,80.0,
336 75.0,70.0,60.0,50.0,40.0,30.0,
337 25.0,20.0,10.0,9.0,8.0,
338 7.0,6.0,5.0,4.0,3.5,3.0,
339 2.5,2.0,1.8,1.5,1.2,1.0,
340 0.8,0.7,0.6,0.5,0.4,0.3,0.2,0.1,0.0,-1};
342 double scaled_min,scaled_max;
349 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
350 im->minval,im->maxval,im->magfact);
353 if (isnan(im->ygridstep)){
354 if(im->extra_flags & ALTAUTOSCALE) {
355 /* measure the amplitude of the function. Make sure that
356 graph boundaries are slightly higher then max/min vals
357 so we can see amplitude on the graph */
360 delt = im->maxval - im->minval;
362 fact = 2.0 * pow(10.0,
363 floor(log10(max(fabs(im->minval), fabs(im->maxval)))) - 2);
365 adj = (fact - delt) * 0.55;
367 printf("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n", im->minval, im->maxval, delt, fact, adj);
373 else if(im->extra_flags & ALTAUTOSCALE_MAX) {
374 /* measure the amplitude of the function. Make sure that
375 graph boundaries are slightly higher than max vals
376 so we can see amplitude on the graph */
377 adj = (im->maxval - im->minval) * 0.1;
381 scaled_min = im->minval / im->magfact;
382 scaled_max = im->maxval / im->magfact;
384 for (i=1; sensiblevalues[i] > 0; i++){
385 if (sensiblevalues[i-1]>=scaled_min &&
386 sensiblevalues[i]<=scaled_min)
387 im->minval = sensiblevalues[i]*(im->magfact);
389 if (-sensiblevalues[i-1]<=scaled_min &&
390 -sensiblevalues[i]>=scaled_min)
391 im->minval = -sensiblevalues[i-1]*(im->magfact);
393 if (sensiblevalues[i-1] >= scaled_max &&
394 sensiblevalues[i] <= scaled_max)
395 im->maxval = sensiblevalues[i-1]*(im->magfact);
397 if (-sensiblevalues[i-1]<=scaled_max &&
398 -sensiblevalues[i] >=scaled_max)
399 im->maxval = -sensiblevalues[i]*(im->magfact);
403 /* adjust min and max to the grid definition if there is one */
404 im->minval = (double)im->ylabfact * im->ygridstep *
405 floor(im->minval / ((double)im->ylabfact * im->ygridstep));
406 im->maxval = (double)im->ylabfact * im->ygridstep *
407 ceil(im->maxval /( (double)im->ylabfact * im->ygridstep));
411 fprintf(stderr,"SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
412 im->minval,im->maxval,im->magfact);
417 /* reduce data reimplementation by Alex */
421 enum cf_en cf, /* which consolidation function ?*/
422 unsigned long cur_step, /* step the data currently is in */
423 time_t *start, /* start, end and step as requested ... */
424 time_t *end, /* ... by the application will be ... */
425 unsigned long *step, /* ... adjusted to represent reality */
426 unsigned long *ds_cnt, /* number of data sources in file */
427 rrd_value_t **data) /* two dimensional array containing the data */
429 int i,reduce_factor = ceil((double)(*step) / (double)cur_step);
430 unsigned long col,dst_row,row_cnt,start_offset,end_offset,skiprows=0;
431 rrd_value_t *srcptr,*dstptr;
433 (*step) = cur_step*reduce_factor; /* set new step size for reduced data */
437 /* We were given one extra row at the beginning of the interval.
438 ** We also need to return one extra row. The extra interval is
439 ** the one defined by the start time in both cases. It is not
440 ** used when graphing but maybe we can use it while reducing the
443 row_cnt = ((*end)-(*start))/cur_step +1;
445 /* alter start and end so that they are multiples of the new steptime.
446 ** End will be shifted towards the future and start will be shifted
447 ** towards the past in order to include the requested interval
449 end_offset = (*end) % (*step);
450 if (end_offset) end_offset = (*step)-end_offset;
451 start_offset = (*start) % (*step);
452 (*end) = (*end)+end_offset;
453 (*start) = (*start)-start_offset;
455 /* The first destination row is unknown yet it still needs
456 ** to be present in the returned data. Skip it.
457 ** Don't make it NaN or we might overwrite the source.
461 /* Depending on the amount of extra data needed at the
462 ** start of the destination, three things can happen:
463 ** -1- start_offset == 0: skip the extra source row
464 ** -2- start_offset == cur_step: do nothing
465 ** -3- start_offset > cur_step: skip some source rows and
466 ** fill one destination row with NaN
468 if (start_offset==0) {
471 } else if (start_offset!=cur_step) {
472 skiprows=((*step)-start_offset)/cur_step+1;
473 srcptr += ((*ds_cnt)*skiprows);
475 for (col=0;col<(*ds_cnt);col++) *dstptr++=DNAN;
478 /* If we had to alter the endtime, there won't be
479 ** enough data to fill the last row. This means
480 ** we have to skip some rows at the end
483 skiprows = ((*step)-end_offset)/cur_step;
488 /* Sanity check: row_cnt should be multiple of reduce_factor */
489 /* if this gets triggered, something is REALY WRONG ... we die immediately */
491 if (row_cnt%reduce_factor) {
492 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
493 row_cnt,reduce_factor);
494 printf("BUG in reduce_data()\n");
498 /* Now combine reduce_factor intervals at a time
499 ** into one interval for the destination.
502 for (dst_row=0;row_cnt>=reduce_factor;dst_row++) {
503 for (col=0;col<(*ds_cnt);col++) {
504 rrd_value_t newval=DNAN;
505 unsigned long validval=0;
507 for (i=0;i<reduce_factor;i++) {
508 if (isnan(srcptr[i*(*ds_cnt)+col])) {
512 if (isnan(newval)) newval = srcptr[i*(*ds_cnt)+col];
520 newval += srcptr[i*(*ds_cnt)+col];
523 newval = min (newval,srcptr[i*(*ds_cnt)+col]);
526 /* an interval contains a failure if any subintervals contained a failure */
528 newval = max (newval,srcptr[i*(*ds_cnt)+col]);
531 newval = srcptr[i*(*ds_cnt)+col];
536 if (validval == 0){newval = DNAN;} else{
554 srcptr+=(*ds_cnt)*reduce_factor;
555 row_cnt-=reduce_factor;
558 /* If we had to alter the endtime, we didn't have enough
559 ** source rows to fill the last row. Fill it with NaN.
561 if (end_offset!=0) for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
565 /* get the data required for the graphs from the
569 data_fetch( image_desc_t *im )
573 /* pull the data from the log files ... */
574 for (i=0;i<im->gdes_c;i++){
575 /* only GF_DEF elements fetch data */
576 if (im->gdes[i].gf != GF_DEF)
580 /* do we have it already ?*/
581 for (ii=0;ii<i;ii++){
582 if (im->gdes[ii].gf != GF_DEF)
584 if((strcmp(im->gdes[i].rrd,im->gdes[ii].rrd) == 0)
585 && (im->gdes[i].cf == im->gdes[ii].cf)){
586 /* OK the data it is here already ...
587 * we just copy the header portion */
588 im->gdes[i].start = im->gdes[ii].start;
589 im->gdes[i].end = im->gdes[ii].end;
590 im->gdes[i].step = im->gdes[ii].step;
591 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
592 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
593 im->gdes[i].data = im->gdes[ii].data;
594 im->gdes[i].data_first = 0;
601 unsigned long ft_step = im->gdes[i].step ;
603 if((rrd_fetch_fn(im->gdes[i].rrd,
609 &im->gdes[i].ds_namv,
610 &im->gdes[i].data)) == -1){
613 im->gdes[i].data_first = 1;
615 if (ft_step < im->gdes[i].step) {
616 reduce_data(im->gdes[i].cf,
624 im->gdes[i].step = ft_step;
628 /* lets see if the required data source is realy there */
629 for(ii=0;ii<im->gdes[i].ds_cnt;ii++){
630 if(strcmp(im->gdes[i].ds_namv[ii],im->gdes[i].ds_nam) == 0){
633 if (im->gdes[i].ds== -1){
634 rrd_set_error("No DS called '%s' in '%s'",
635 im->gdes[i].ds_nam,im->gdes[i].rrd);
643 /* evaluate the expressions in the CDEF functions */
645 /*************************************************************
647 *************************************************************/
650 find_var_wrapper(void *arg1, char *key)
652 return find_var((image_desc_t *) arg1, key);
655 /* find gdes containing var*/
657 find_var(image_desc_t *im, char *key){
659 for(ii=0;ii<im->gdes_c-1;ii++){
660 if((im->gdes[ii].gf == GF_DEF
661 || im->gdes[ii].gf == GF_VDEF
662 || im->gdes[ii].gf == GF_CDEF)
663 && (strcmp(im->gdes[ii].vname,key) == 0)){
670 /* find the largest common denominator for all the numbers
671 in the 0 terminated num array */
676 for (i=0;num[i+1]!=0;i++){
678 rest=num[i] % num[i+1];
679 num[i]=num[i+1]; num[i+1]=rest;
683 /* return i==0?num[i]:num[i-1]; */
687 /* run the rpn calculator on all the VDEF and CDEF arguments */
689 data_calc( image_desc_t *im){
693 long *steparray, rpi;
698 rpnstack_init(&rpnstack);
700 for (gdi=0;gdi<im->gdes_c;gdi++){
701 /* Look for GF_VDEF and GF_CDEF in the same loop,
702 * so CDEFs can use VDEFs and vice versa
704 switch (im->gdes[gdi].gf) {
706 /* A VDEF has no DS. This also signals other parts
707 * of rrdtool that this is a VDEF value, not a CDEF.
709 im->gdes[gdi].ds_cnt = 0;
710 if (vdef_calc(im,gdi)) {
711 rrd_set_error("Error processing VDEF '%s'"
714 rpnstack_free(&rpnstack);
719 im->gdes[gdi].ds_cnt = 1;
720 im->gdes[gdi].ds = 0;
721 im->gdes[gdi].data_first = 1;
722 im->gdes[gdi].start = 0;
723 im->gdes[gdi].end = 0;
728 /* Find the variables in the expression.
729 * - VDEF variables are substituted by their values
730 * and the opcode is changed into OP_NUMBER.
732 * Note to Jake: I cannot oversee the implications for your
733 * COMPUTE DS stuff. Please check if VDEF and COMPUTE are
734 * compatible (or can be made so).
736 * - CDEF variables are analized for their step size,
737 * the lowest common denominator of all the step
738 * sizes of the data sources involved is calculated
739 * and the resulting number is the step size for the
740 * resulting data source.
742 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
743 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE){
744 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
745 if (im->gdes[ptr].ds_cnt == 0) {
747 printf("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
749 im->gdes[ptr].vname);
750 printf("DEBUG: value from vdef is %f\n",im->gdes[ptr].vf.val);
752 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
753 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
755 if ((steparray = rrd_realloc(steparray, (++stepcnt+1)*sizeof(*steparray)))==NULL){
756 rrd_set_error("realloc steparray");
757 rpnstack_free(&rpnstack);
761 steparray[stepcnt-1] = im->gdes[ptr].step;
763 /* adjust start and end of cdef (gdi) so
764 * that it runs from the latest start point
765 * to the earliest endpoint of any of the
766 * rras involved (ptr)
768 if(im->gdes[gdi].start < im->gdes[ptr].start)
769 im->gdes[gdi].start = im->gdes[ptr].start;
771 if(im->gdes[gdi].end == 0 ||
772 im->gdes[gdi].end > im->gdes[ptr].end)
773 im->gdes[gdi].end = im->gdes[ptr].end;
775 /* store pointer to the first element of
776 * the rra providing data for variable,
777 * further save step size and data source
780 im->gdes[gdi].rpnp[rpi].data =
781 im->gdes[ptr].data + im->gdes[ptr].ds;
782 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
783 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
785 /* backoff the *.data ptr; this is done so
786 * rpncalc() function doesn't have to treat
787 * the first case differently
789 im->gdes[gdi].rpnp[rpi].data-=im->gdes[ptr].ds_cnt;
790 } /* if ds_cnt != 0 */
791 } /* if OP_VARIABLE */
792 } /* loop through all rpi */
794 if(steparray == NULL){
795 rrd_set_error("rpn expressions without DEF"
796 " or CDEF variables are not supported");
797 rpnstack_free(&rpnstack);
800 steparray[stepcnt]=0;
801 /* Now find the resulting step. All steps in all
802 * used RRAs have to be visited
804 im->gdes[gdi].step = lcd(steparray);
806 if((im->gdes[gdi].data = malloc((
807 (im->gdes[gdi].end-im->gdes[gdi].start)
808 / im->gdes[gdi].step +1)
809 * sizeof(double)))==NULL){
810 rrd_set_error("malloc im->gdes[gdi].data");
811 rpnstack_free(&rpnstack);
815 /* Step through the new cdef results array and
816 * calculate the values
818 for (now = im->gdes[gdi].start;
819 now<=im->gdes[gdi].end;
820 now += im->gdes[gdi].step)
822 rpnp_t *rpnp = im -> gdes[gdi].rpnp;
824 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
825 * in this case we are advancing by timesteps;
826 * we use the fact that time_t is a synonym for long
828 if (rpn_calc(rpnp,&rpnstack,(long) now,
829 im->gdes[gdi].data,++dataidx) == -1) {
830 /* rpn_calc sets the error string */
831 rpnstack_free(&rpnstack);
834 } /* enumerate over time steps within a CDEF */
839 } /* enumerate over CDEFs */
840 rpnstack_free(&rpnstack);
844 /* massage data so, that we get one value for each x coordinate in the graph */
846 data_proc( image_desc_t *im ){
848 double pixstep = (double)(im->end-im->start)
849 /(double)im->xsize; /* how much time
850 passes in one pixel */
852 double minval=DNAN,maxval=DNAN;
854 unsigned long gr_time;
856 /* memory for the processed data */
857 for(i=0;i<im->gdes_c;i++){
858 if((im->gdes[i].gf==GF_LINE1) ||
859 (im->gdes[i].gf==GF_LINE2) ||
860 (im->gdes[i].gf==GF_LINE3) ||
861 (im->gdes[i].gf==GF_AREA) ||
862 (im->gdes[i].gf==GF_TICK) ||
863 (im->gdes[i].gf==GF_STACK)){
864 if((im->gdes[i].p_data = malloc((im->xsize +1)
865 * sizeof(rrd_value_t)))==NULL){
866 rrd_set_error("malloc data_proc");
872 for(i=0;i<im->xsize;i++){
874 gr_time = im->start+pixstep*i; /* time of the
878 for(ii=0;ii<im->gdes_c;ii++){
880 switch(im->gdes[ii].gf){
888 vidx = im->gdes[ii].vidx;
892 ((unsigned long)floor((double)
893 (gr_time - im->gdes[vidx].start )
894 / im->gdes[vidx].step)+1)
896 /* added one because data was not being aligned properly
897 this fixes it. We may also be having a problem in fetch ... */
899 *im->gdes[vidx].ds_cnt
902 if (! isnan(value)) {
904 im->gdes[ii].p_data[i] = paintval;
905 /* GF_TICK: the data values are not relevant for min and max */
906 if (finite(paintval) && im->gdes[ii].gf != GF_TICK ){
907 if (isnan(minval) || paintval < minval)
909 if (isnan(maxval) || paintval > maxval)
913 im->gdes[ii].p_data[i] = DNAN;
929 /* if min or max have not been asigned a value this is because
930 there was no data in the graph ... this is not good ...
931 lets set these to dummy values then ... */
933 if (isnan(minval)) minval = 0.0;
934 if (isnan(maxval)) maxval = 1.0;
936 /* adjust min and max values */
937 if (isnan(im->minval)
938 || ((!im->logarithmic && !im->rigid) /* don't adjust low-end with log scale */
939 && im->minval > minval))
941 if (isnan(im->maxval)
943 && im->maxval < maxval)){
945 im->maxval = maxval * 1.1;
949 /* make sure min and max are not equal */
950 if (im->minval == im->maxval) {
952 if (! im->logarithmic) {
956 /* make sure min and max are not both zero */
957 if (im->maxval == 0.0) {
967 /* identify the point where the first gridline, label ... gets placed */
971 time_t start, /* what is the initial time */
972 enum tmt_en baseint, /* what is the basic interval */
973 long basestep /* how many if these do we jump a time */
977 tm = *localtime(&start);
980 tm.tm_sec -= tm.tm_sec % basestep; break;
983 tm.tm_min -= tm.tm_min % basestep;
988 tm.tm_hour -= tm.tm_hour % basestep; break;
990 /* we do NOT look at the basestep for this ... */
993 tm.tm_hour = 0; break;
995 /* we do NOT look at the basestep for this ... */
999 tm.tm_mday -= tm.tm_wday -1; /* -1 because we want the monday */
1000 if (tm.tm_wday==0) tm.tm_mday -= 7; /* we want the *previous* monday */
1007 tm.tm_mon -= tm.tm_mon % basestep; break;
1015 tm.tm_year -= (tm.tm_year+1900) % basestep;
1020 /* identify the point where the next gridline, label ... gets placed */
1023 time_t current, /* what is the initial time */
1024 enum tmt_en baseint, /* what is the basic interval */
1025 long basestep /* how many if these do we jump a time */
1030 tm = *localtime(¤t);
1034 tm.tm_sec += basestep; break;
1036 tm.tm_min += basestep; break;
1038 tm.tm_hour += basestep; break;
1040 tm.tm_mday += basestep; break;
1042 tm.tm_mday += 7*basestep; break;
1044 tm.tm_mon += basestep; break;
1046 tm.tm_year += basestep;
1048 madetime = mktime(&tm);
1049 } while (madetime == -1); /* this is necessary to skip impssible times
1050 like the daylight saving time skips */
1055 void gator( gdImagePtr gif, int x, int y){
1057 /* this function puts the name of the author and the tool into the
1058 graph. Remove if you must, but please note, that it is here,
1059 because I would like people who look at rrdtool generated graphs to
1060 see what was used to do it. No obviously you can also add a credit
1061 line to your webpage or printed document, this is fine with me. But
1062 as I have no control over this, I added the little tag in here.
1065 /* the fact that the text of what gets put into the graph is not
1066 visible in the function, has lead some to think this is for
1067 obfuscation reasons. While this is a nice side effect (I addmit),
1068 it is not the prime reason. The prime reason is, that the font
1069 used, is so small, that I had to hand edit the characters to ensure
1070 readability. I could thus not use the normal gd functions to write,
1071 but had to embed a slightly compressed bitmap version into the code.
1074 int li[]={0,0,1, 0,4,5, 0,8,9, 0,12,14, 0,17,17, 0,21,21,
1075 0,24,24, 0,34,34, 0,40,42, 0,45,45, 0,48,49, 0,52,54,
1076 0,61,61, 0,64,66, 0,68,70, 0,72,74, 0,76,76, 0,78,78,
1078 1,0,0, 1,2,2, 1,4,4, 1,6,6, 1,8,8, 1,10,10,
1079 1,13,13, 1,16,16, 1,18,18, 1,20,20, 1,22,22, 1,24,24,
1080 1,34,34, 1,41,41, 1,44,44, 1,46,46, 1,48,48, 1,50,50,
1081 1,53,53, 1,60,60, 1,62,62, 1,64,64, 1,69,69, 1,73,73,
1082 1,76,76, 1,78,78, 1,80,80, 1,84,84, 1,86,86,
1083 2,0,1, 2,4,5, 2,8,8, 2,10,10, 2,13,13, 2,16,16,
1084 2,18,18, 2,20,20, 2,22,22, 2,24,24, 2,33,33, 2,41,41,
1085 2,44,44, 2,46,46, 2,48,49, 2,53,53, 2,60,60, 2,62,62,
1086 2,64,65, 2,69,69, 2,73,73, 2,76,77, 2,80,81, 2,84,85,
1087 3,0,0, 3,2,2, 3,4,4, 3,6,6, 3,8,8, 3,10,10,
1088 3,13,13, 3,16,16, 3,18,18, 3,20,20, 3,22,22, 3,24,24,
1089 3,32,32, 3,41,41, 3,44,44, 3,46,46, 3,48,48, 3,50,50,
1090 3,53,53, 3,60,60, 3,62,62, 3,64,64, 3,69,69, 3,73,73,
1091 3,76,76, 3,78,78, 3,80,80, 3,84,84, 3,86,86,
1092 4,0,0, 4,2,2, 4,4,4, 4,6,6, 4,8,9, 4,13,13,
1093 4,17,17, 4,21,21, 4,24,26, 4,32,32, 4,41,41, 4,45,45,
1094 4,48,49, 4,52,54, 4,61,61, 4,64,66, 4,69,69, 4,72,74,
1095 4,76,76, 4,78,78, 4,80,82, 4,84,84};
1097 for(i=0; i<DIM(li); i=i+3)
1098 for(ii=y+li[i+1]; ii<=y+li[i+2];ii++)
1099 gdImageSetPixel(gif,x-li[i],ii,graph_col[GRC_GRID].i);
1103 /* calculate values required for PRINT and GPRINT functions */
1106 print_calc(image_desc_t *im, char ***prdata)
1108 long i,ii,validsteps;
1110 int graphelement = 0;
1113 double magfact = -1;
1117 if (im->imginfo) prlines++;
1118 for(i=0;i<im->gdes_c;i++){
1119 switch(im->gdes[i].gf){
1122 if(((*prdata) = rrd_realloc((*prdata),prlines*sizeof(char *)))==NULL){
1123 rrd_set_error("realloc prdata");
1127 /* PRINT and GPRINT can now print VDEF generated values.
1128 * There's no need to do any calculations on them as these
1129 * calculations were already made.
1131 vidx = im->gdes[i].vidx;
1132 if (im->gdes[vidx].gf==GF_VDEF) { /* simply use vals */
1133 printval = im->gdes[vidx].vf.val;
1134 } else { /* need to calculate max,min,avg etcetera */
1135 max_ii =((im->gdes[vidx].end
1136 - im->gdes[vidx].start)
1137 / im->gdes[vidx].step
1138 * im->gdes[vidx].ds_cnt);
1141 for(ii=im->gdes[vidx].ds+im->gdes[vidx].ds_cnt;
1142 ii < max_ii+im->gdes[vidx].ds_cnt;
1143 ii+=im->gdes[vidx].ds_cnt){
1144 if (! finite(im->gdes[vidx].data[ii]))
1146 if (isnan(printval)){
1147 printval = im->gdes[vidx].data[ii];
1152 switch (im->gdes[i].cf){
1155 case CF_DEVSEASONAL:
1159 printval += im->gdes[vidx].data[ii];
1162 printval = min( printval, im->gdes[vidx].data[ii]);
1166 printval = max( printval, im->gdes[vidx].data[ii]);
1169 printval = im->gdes[vidx].data[ii];
1172 if (im->gdes[i].cf==CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1173 if (validsteps > 1) {
1174 printval = (printval / validsteps);
1177 } /* prepare printval */
1180 if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) {
1181 /* Magfact is set to -1 upon entry to print_calc. If it
1182 * is still less than 0, then we need to run auto_scale.
1183 * Otherwise, put the value into the correct units. If
1184 * the value is 0, then do not set the symbol or magnification
1185 * so next the calculation will be performed again. */
1186 if (magfact < 0.0) {
1187 auto_scale(im,&printval,&si_symb,&magfact);
1188 if (printval == 0.0)
1191 printval /= magfact;
1193 *(++percent_s) = 's';
1194 } else if (strstr(im->gdes[i].format,"%s") != NULL) {
1195 auto_scale(im,&printval,&si_symb,&magfact);
1198 if (im->gdes[i].gf == GF_PRINT){
1199 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1200 if (bad_format(im->gdes[i].format)) {
1201 rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1204 #ifdef HAVE_SNPRINTF
1205 snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
1207 sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
1209 (*prdata)[prlines-1] = NULL;
1213 if (bad_format(im->gdes[i].format)) {
1214 rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1217 #ifdef HAVE_SNPRINTF
1218 snprintf(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
1220 sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
1242 return graphelement;
1246 /* place legends with color spots */
1248 leg_place(image_desc_t *im)
1251 int interleg = SmallFont->w*2;
1252 int box = SmallFont->h*1.2;
1253 int border = SmallFont->w*2;
1254 int fill=0, fill_last;
1256 int leg_x = border, leg_y = im->ygif;
1260 char prt_fctn; /*special printfunctions */
1263 if( !(im->extra_flags & NOLEGEND) ) {
1264 if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1265 rrd_set_error("malloc for legspace");
1269 for(i=0;i<im->gdes_c;i++){
1272 leg_cc = strlen(im->gdes[i].legend);
1274 /* is there a controle code ant the end of the legend string ? */
1275 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\') {
1276 prt_fctn = im->gdes[i].legend[leg_cc-1];
1278 im->gdes[i].legend[leg_cc] = '\0';
1282 /* remove exess space */
1283 while (prt_fctn=='g' &&
1285 im->gdes[i].legend[leg_cc-1]==' '){
1287 im->gdes[i].legend[leg_cc]='\0';
1290 legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1293 /* no interleg space if string ends in \g */
1294 fill += legspace[i];
1296 if (im->gdes[i].gf != GF_GPRINT &&
1297 im->gdes[i].gf != GF_COMMENT) {
1300 fill += leg_cc * SmallFont->w;
1305 /* who said there was a special tag ... ?*/
1306 if (prt_fctn=='g') {
1309 if (prt_fctn == '\0') {
1310 if (i == im->gdes_c -1 ) prt_fctn ='l';
1312 /* is it time to place the legends ? */
1313 if (fill > im->xgif - 2*border){
1328 if (prt_fctn != '\0'){
1330 if (leg_c >= 2 && prt_fctn == 'j') {
1331 glue = (im->xgif - fill - 2* border) / (leg_c-1);
1332 /* if (glue > 2 * SmallFont->w) glue = 0; */
1336 if (prt_fctn =='c') leg_x = (im->xgif - fill) / 2.0;
1337 if (prt_fctn =='r') leg_x = im->xgif - fill - border;
1339 for(ii=mark;ii<=i;ii++){
1340 if(im->gdes[ii].legend[0]=='\0')
1342 im->gdes[ii].legloc.x = leg_x;
1343 im->gdes[ii].legloc.y = leg_y;
1345 + strlen(im->gdes[ii].legend)*SmallFont->w
1348 if (im->gdes[ii].gf != GF_GPRINT &&
1349 im->gdes[ii].gf != GF_COMMENT)
1352 leg_y = leg_y + SmallFont->h*1.2;
1353 if (prt_fctn == 's') leg_y -= SmallFont->h *0.5;
1365 /* create a grid on the graph. it determines what to do
1366 from the values of xsize, start and end */
1368 /* the xaxis labels are determined from the number of seconds per pixel
1369 in the requested graph */
1374 horizontal_grid(gdImagePtr gif, image_desc_t *im)
1382 char graph_label[100];
1383 gdPoint polyPoints[4];
1384 int labfact,gridind;
1385 int styleMinor[2],styleMajor[2];
1386 int decimals, fractionals;
1391 range = im->maxval - im->minval;
1392 scaledrange = range / im->magfact;
1394 /* does the scale of this graph make it impossible to put lines
1395 on it? If so, give up. */
1396 if (isnan(scaledrange)) {
1400 styleMinor[0] = graph_col[GRC_GRID].i;
1401 styleMinor[1] = gdTransparent;
1403 styleMajor[0] = graph_col[GRC_MGRID].i;
1404 styleMajor[1] = gdTransparent;
1406 /* find grid spaceing */
1408 if(isnan(im->ygridstep)){
1409 if(im->extra_flags & ALTYGRID) {
1410 /* find the value with max number of digits. Get number of digits */
1411 decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))));
1412 if(decimals <= 0) /* everything is small. make place for zero */
1415 fractionals = floor(log10(range));
1416 if(fractionals < 0) /* small amplitude. */
1417 sprintf(labfmt, "%%%d.%df", decimals - fractionals + 1, -fractionals + 1);
1419 sprintf(labfmt, "%%%d.1f", decimals + 1);
1420 gridstep = pow((double)10, (double)fractionals);
1421 if(gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1423 /* should have at least 5 lines but no more then 15 */
1424 if(range/gridstep < 5)
1426 if(range/gridstep > 15)
1428 if(range/gridstep > 5) {
1430 if(range/gridstep > 8)
1439 for(i=0;ylab[i].grid > 0;i++){
1440 pixel = im->ysize / (scaledrange / ylab[i].grid);
1441 if (gridind == -1 && pixel > 5) {
1448 if (pixel * ylab[gridind].lfac[i] >= 2 * SmallFont->h) {
1449 labfact = ylab[gridind].lfac[i];
1454 gridstep = ylab[gridind].grid * im->magfact;
1457 gridstep = im->ygridstep;
1458 labfact = im->ylabfact;
1461 polyPoints[0].x=im->xorigin;
1462 polyPoints[1].x=im->xorigin+im->xsize;
1463 sgrid = (int)( im->minval / gridstep - 1);
1464 egrid = (int)( im->maxval / gridstep + 1);
1465 scaledstep = gridstep/im->magfact;
1466 for (i = sgrid; i <= egrid; i++){
1467 polyPoints[0].y=ytr(im,gridstep*i);
1468 if ( polyPoints[0].y >= im->yorigin-im->ysize
1469 && polyPoints[0].y <= im->yorigin) {
1470 if(i % labfact == 0){
1471 if (i==0 || im->symbol == ' ') {
1473 if(im->extra_flags & ALTYGRID) {
1474 sprintf(graph_label,labfmt,scaledstep*i);
1477 sprintf(graph_label,"%4.1f",scaledstep*i);
1480 sprintf(graph_label,"%4.0f",scaledstep*i);
1484 sprintf(graph_label,"%4.1f %c",scaledstep*i, im->symbol);
1486 sprintf(graph_label,"%4.0f %c",scaledstep*i, im->symbol);
1490 gdImageString(gif, SmallFont,
1491 (polyPoints[0].x - (strlen(graph_label) *
1493 polyPoints[0].y - SmallFont->h/2+1,
1494 (unsigned char *)graph_label, graph_col[GRC_FONT].i);
1496 gdImageSetStyle(gif, styleMajor, 2);
1498 gdImageLine(gif, polyPoints[0].x-2,polyPoints[0].y,
1499 polyPoints[0].x+2,polyPoints[0].y,graph_col[GRC_MGRID].i);
1500 gdImageLine(gif, polyPoints[1].x-2,polyPoints[0].y,
1501 polyPoints[1].x+2,polyPoints[0].y,graph_col[GRC_MGRID].i);
1503 gdImageSetStyle(gif, styleMinor, 2);
1504 gdImageLine(gif, polyPoints[0].x-1,polyPoints[0].y,
1505 polyPoints[0].x+1,polyPoints[0].y,graph_col[GRC_GRID].i);
1506 gdImageLine(gif, polyPoints[1].x-1,polyPoints[0].y,
1507 polyPoints[1].x+1,polyPoints[0].y,graph_col[GRC_GRID].i);
1509 gdImageLine(gif, polyPoints[0].x,polyPoints[0].y,
1510 polyPoints[1].x,polyPoints[0].y,gdStyled);
1513 /* if(im->minval * im->maxval < 0){
1514 polyPoints[0].y=ytr(0);
1515 gdImageLine(gif, polyPoints[0].x,polyPoints[0].y,
1516 polyPoints[1].x,polyPoints[0].y,graph_col[GRC_MGRID].i);
1522 /* logaritmic horizontal grid */
1524 horizontal_log_grid(gdImagePtr gif, image_desc_t *im)
1528 int minoridx=0, majoridx=0;
1529 char graph_label[100];
1530 gdPoint polyPoints[4];
1531 int styleMinor[2],styleMajor[2];
1532 double value, pixperstep, minstep;
1534 /* find grid spaceing */
1535 pixpex= (double)im->ysize / (log10(im->maxval) - log10(im->minval));
1537 if (isnan(pixpex)) {
1541 for(i=0;yloglab[i][0] > 0;i++){
1542 minstep = log10(yloglab[i][0]);
1543 for(ii=1;yloglab[i][ii+1] > 0;ii++){
1544 if(yloglab[i][ii+2]==0){
1545 minstep = log10(yloglab[i][ii+1])-log10(yloglab[i][ii]);
1549 pixperstep = pixpex * minstep;
1550 if(pixperstep > 5){minoridx = i;}
1551 if(pixperstep > 2 * SmallFont->h){majoridx = i;}
1554 styleMinor[0] = graph_col[GRC_GRID].i;
1555 styleMinor[1] = gdTransparent;
1557 styleMajor[0] = graph_col[GRC_MGRID].i;
1558 styleMajor[1] = gdTransparent;
1560 polyPoints[0].x=im->xorigin;
1561 polyPoints[1].x=im->xorigin+im->xsize;
1562 /* paint minor grid */
1563 for (value = pow((double)10, log10(im->minval)
1564 - fmod(log10(im->minval),log10(yloglab[minoridx][0])));
1565 value <= im->maxval;
1566 value *= yloglab[minoridx][0]){
1567 if (value < im->minval) continue;
1569 while(yloglab[minoridx][++i] > 0){
1570 polyPoints[0].y = ytr(im,value * yloglab[minoridx][i]);
1571 if (polyPoints[0].y <= im->yorigin - im->ysize) break;
1572 gdImageSetStyle(gif, styleMinor, 2);
1573 gdImageLine(gif, polyPoints[0].x-1,polyPoints[0].y,
1574 polyPoints[0].x+1,polyPoints[0].y,graph_col[GRC_GRID].i);
1575 gdImageLine(gif, polyPoints[1].x-1,polyPoints[0].y,
1576 polyPoints[1].x+1,polyPoints[0].y,graph_col[GRC_GRID].i);
1578 gdImageLine(gif, polyPoints[0].x,polyPoints[0].y,
1579 polyPoints[1].x,polyPoints[0].y,gdStyled);
1583 /* paint major grid and labels*/
1584 for (value = pow((double)10, log10(im->minval)
1585 - fmod(log10(im->minval),log10(yloglab[majoridx][0])));
1586 value <= im->maxval;
1587 value *= yloglab[majoridx][0]){
1588 if (value < im->minval) continue;
1590 while(yloglab[majoridx][++i] > 0){
1591 polyPoints[0].y = ytr(im,value * yloglab[majoridx][i]);
1592 if (polyPoints[0].y <= im->yorigin - im->ysize) break;
1593 gdImageSetStyle(gif, styleMajor, 2);
1594 gdImageLine(gif, polyPoints[0].x-2,polyPoints[0].y,
1595 polyPoints[0].x+2,polyPoints[0].y,graph_col[GRC_MGRID].i);
1596 gdImageLine(gif, polyPoints[1].x-2,polyPoints[0].y,
1597 polyPoints[1].x+2,polyPoints[0].y,graph_col[GRC_MGRID].i);
1599 gdImageLine(gif, polyPoints[0].x,polyPoints[0].y,
1600 polyPoints[1].x,polyPoints[0].y,gdStyled);
1601 sprintf(graph_label,"%3.0e",value * yloglab[majoridx][i]);
1602 gdImageString(gif, SmallFont,
1603 (polyPoints[0].x - (strlen(graph_label) *
1605 polyPoints[0].y - SmallFont->h/2+1,
1606 (unsigned char *)graph_label, graph_col[GRC_FONT].i);
1618 int xlab_sel; /* which sort of label and grid ? */
1621 char graph_label[100];
1622 gdPoint polyPoints[4]; /* points for filled graph and more*/
1624 /* style for grid lines */
1628 /* the type of time grid is determined by finding
1629 the number of seconds per pixel in the graph */
1632 if(im->xlab_user.minsec == -1){
1633 factor=(im->end - im->start)/im->xsize;
1635 while ( xlab[xlab_sel+1].minsec != -1
1636 && xlab[xlab_sel+1].minsec <= factor){ xlab_sel++; }
1637 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
1638 im->xlab_user.gridst = xlab[xlab_sel].gridst;
1639 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
1640 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
1641 im->xlab_user.labtm = xlab[xlab_sel].labtm;
1642 im->xlab_user.labst = xlab[xlab_sel].labst;
1643 im->xlab_user.precis = xlab[xlab_sel].precis;
1644 im->xlab_user.stst = xlab[xlab_sel].stst;
1647 /* y coords are the same for every line ... */
1648 polyPoints[0].y = im->yorigin;
1649 polyPoints[1].y = im->yorigin-im->ysize;
1651 /* paint the minor grid */
1652 for(ti = find_first_time(im->start,
1653 im->xlab_user.gridtm,
1654 im->xlab_user.gridst);
1656 ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
1658 /* are we inside the graph ? */
1659 if (ti < im->start || ti > im->end) continue;
1660 polyPoints[0].x = xtr(im,ti);
1661 styleDotted[0] = graph_col[GRC_GRID].i;
1662 styleDotted[1] = gdTransparent;
1664 gdImageSetStyle(gif, styleDotted, 2);
1666 gdImageLine(gif, polyPoints[0].x,polyPoints[0].y,
1667 polyPoints[0].x,polyPoints[1].y,gdStyled);
1668 gdImageLine(gif, polyPoints[0].x,polyPoints[0].y-1,
1669 polyPoints[0].x,polyPoints[0].y+1,graph_col[GRC_GRID].i);
1670 gdImageLine(gif, polyPoints[0].x,polyPoints[1].y-1,
1671 polyPoints[0].x,polyPoints[1].y+1,graph_col[GRC_GRID].i);
1674 /* paint the major grid */
1675 for(ti = find_first_time(im->start,
1676 im->xlab_user.mgridtm,
1677 im->xlab_user.mgridst);
1679 ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
1681 /* are we inside the graph ? */
1682 if (ti < im->start || ti > im->end) continue;
1683 polyPoints[0].x = xtr(im,ti);
1684 styleDotted[0] = graph_col[GRC_MGRID].i;
1685 styleDotted[1] = gdTransparent;
1686 gdImageSetStyle(gif, styleDotted, 2);
1688 gdImageLine(gif, polyPoints[0].x,polyPoints[0].y,
1689 polyPoints[0].x,polyPoints[1].y,gdStyled);
1690 gdImageLine(gif, polyPoints[0].x,polyPoints[0].y-2,
1691 polyPoints[0].x,polyPoints[0].y+2,graph_col[GRC_MGRID].i);
1692 gdImageLine(gif, polyPoints[0].x,polyPoints[1].y-2,
1693 polyPoints[0].x,polyPoints[1].y+2,graph_col[GRC_MGRID].i);
1695 /* paint the labels below the graph */
1696 for(ti = find_first_time(im->start,
1697 im->xlab_user.labtm,
1698 im->xlab_user.labst);
1700 ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
1703 tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
1706 strftime(graph_label,99,im->xlab_user.stst,localtime(&tilab));
1708 # error "your libc has no strftime I guess we'll abort the exercise here."
1710 width=strlen(graph_label) * SmallFont->w;
1711 gr_pos=xtr(im,tilab) - width/2;
1712 if (gr_pos >= im->xorigin
1713 && gr_pos + width <= im->xorigin+im->xsize)
1714 gdImageString(gif, SmallFont,
1715 gr_pos, polyPoints[0].y+4,
1716 (unsigned char *)graph_label, graph_col[GRC_FONT].i);
1728 /* draw x and y axis */
1729 gdImageLine(gif, im->xorigin+im->xsize,im->yorigin,
1730 im->xorigin+im->xsize,im->yorigin-im->ysize,
1731 graph_col[GRC_GRID].i);
1733 gdImageLine(gif, im->xorigin,im->yorigin-im->ysize,
1734 im->xorigin+im->xsize,im->yorigin-im->ysize,
1735 graph_col[GRC_GRID].i);
1737 gdImageLine(gif, im->xorigin-4,im->yorigin,
1738 im->xorigin+im->xsize+4,im->yorigin,
1739 graph_col[GRC_FONT].i);
1741 gdImageLine(gif, im->xorigin,im->yorigin,
1742 im->xorigin,im->yorigin-im->ysize,
1743 graph_col[GRC_GRID].i);
1745 /* arrow for X axis direction */
1746 gdImageLine(gif, im->xorigin+im->xsize+4, im->yorigin-3, im->xorigin+im->xsize+4, im->yorigin+3,graph_col[GRC_ARROW].i);
1747 gdImageLine(gif, im->xorigin+im->xsize+4, im->yorigin-3, im->xorigin+im->xsize+9, im->yorigin,graph_col[GRC_ARROW].i);
1748 gdImageLine(gif, im->xorigin+im->xsize+4, im->yorigin+3, im->xorigin+im->xsize+9, im->yorigin,graph_col[GRC_ARROW].i);
1750 /* gdImageLine(gif, im->xorigin+im->xsize-1, im->yorigin-3, im->xorigin+im->xsize-1, im->yorigin+3,graph_col[GRC_MGRID].i);
1751 gdImageLine(gif, im->xorigin+im->xsize, im->yorigin-2, im->xorigin+im->xsize, im->yorigin+2,graph_col[GRC_MGRID].i);
1752 gdImageLine(gif, im->xorigin+im->xsize+1, im->yorigin-2, im->xorigin+im->xsize+1, im->yorigin+2,graph_col[GRC_MGRID].i);
1753 gdImageLine(gif, im->xorigin+im->xsize+2, im->yorigin-2, im->xorigin+im->xsize+2, im->yorigin+2,graph_col[GRC_MGRID].i);
1754 gdImageLine(gif, im->xorigin+im->xsize+3, im->yorigin-1, im->xorigin+im->xsize+3, im->yorigin+1,graph_col[GRC_MGRID].i);
1755 gdImageLine(gif, im->xorigin+im->xsize+4, im->yorigin-1, im->xorigin+im->xsize+4, im->yorigin+1,graph_col[GRC_MGRID].i);
1756 gdImageLine(gif, im->xorigin+im->xsize+5, im->yorigin, im->xorigin+im->xsize+5, im->yorigin,graph_col[GRC_MGRID].i); */
1771 gdPoint polyPoints[4]; /* points for filled graph and more*/
1773 /* draw 3d border */
1774 gdImageLine(gif,0,0,im->xgif-1,0,graph_col[GRC_SHADEA].i);
1775 gdImageLine(gif,1,1,im->xgif-2,1,graph_col[GRC_SHADEA].i);
1776 gdImageLine(gif,0,0,0,im->ygif-1,graph_col[GRC_SHADEA].i);
1777 gdImageLine(gif,1,1,1,im->ygif-2,graph_col[GRC_SHADEA].i);
1778 gdImageLine(gif,im->xgif-1,0,im->xgif-1,im->ygif-1,graph_col[GRC_SHADEB].i);
1779 gdImageLine(gif,0,im->ygif-1,im->xgif-1,im->ygif-1,graph_col[GRC_SHADEB].i);
1780 gdImageLine(gif,im->xgif-2,1,im->xgif-2,im->ygif-2,graph_col[GRC_SHADEB].i);
1781 gdImageLine(gif,1,im->ygif-2,im->xgif-2,im->ygif-2,graph_col[GRC_SHADEB].i);
1784 if (im->draw_x_grid == 1 )
1785 vertical_grid(gif, im);
1787 if (im->draw_y_grid == 1){
1788 if(im->logarithmic){
1789 res = horizontal_log_grid(gif,im);
1791 res = horizontal_grid(gif,im);
1794 /* dont draw horizontal grid if there is no min and max val */
1796 char *nodata = "No Data found";
1797 gdImageString(gif, LargeFont,
1799 - (strlen(nodata)*LargeFont->w)/2,
1800 (2*im->yorigin-im->ysize) / 2,
1801 (unsigned char *)nodata, graph_col[GRC_FONT].i);
1805 /* yaxis description */
1806 gdImageStringUp(gif, SmallFont,
1808 (im->yorigin - im->ysize/2
1809 +(strlen(im->ylegend)*SmallFont->w)/2 ),
1810 (unsigned char *)im->ylegend, graph_col[GRC_FONT].i);
1814 gdImageString(gif, LargeFont,
1816 - (strlen(im->title)*LargeFont->w)/2,
1818 (unsigned char *)im->title, graph_col[GRC_FONT].i);
1821 if( !(im->extra_flags & NOLEGEND) ) {
1822 for(i=0;i<im->gdes_c;i++){
1823 if(im->gdes[i].legend[0] =='\0')
1826 if(im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT){
1828 polyPoints[0].x = im->gdes[i].legloc.x;
1829 polyPoints[0].y = im->gdes[i].legloc.y+1;
1830 polyPoints[1].x = polyPoints[0].x+boxH;
1831 polyPoints[2].x = polyPoints[0].x+boxH;
1832 polyPoints[3].x = polyPoints[0].x;
1833 polyPoints[1].y = polyPoints[0].y;
1834 polyPoints[2].y = polyPoints[0].y+boxV;
1835 polyPoints[3].y = polyPoints[0].y+boxV;
1836 gdImageFilledPolygon(gif,polyPoints,4,im->gdes[i].col.i);
1837 gdImagePolygon(gif,polyPoints,4,graph_col[GRC_FRAME].i);
1839 gdImageString(gif, SmallFont,
1840 polyPoints[0].x+boxH+6,
1842 (unsigned char *)im->gdes[i].legend,
1843 graph_col[GRC_FONT].i);
1845 polyPoints[0].x = im->gdes[i].legloc.x;
1846 polyPoints[0].y = im->gdes[i].legloc.y;
1848 gdImageString(gif, SmallFont,
1851 (unsigned char *)im->gdes[i].legend,
1852 graph_col[GRC_FONT].i);
1858 gator(gif, (int) im->xgif-5, 5);
1864 MkLineBrush(image_desc_t *im,long cosel, enum gf_en typsel){
1869 brush=gdImageCreate(1,1);
1872 brush=gdImageCreate(2,2);
1875 brush=gdImageCreate(3,3);
1881 gdImageColorTransparent(brush,
1882 gdImageColorAllocate(brush, 0, 0, 0));
1884 pen = gdImageColorAllocate(brush,
1885 im->gdes[cosel].col.red,
1886 im->gdes[cosel].col.green,
1887 im->gdes[cosel].col.blue);
1891 gdImageSetPixel(brush,0,0,pen);
1894 gdImageSetPixel(brush,0,0,pen);
1895 gdImageSetPixel(brush,0,1,pen);
1896 gdImageSetPixel(brush,1,0,pen);
1897 gdImageSetPixel(brush,1,1,pen);
1900 gdImageSetPixel(brush,1,0,pen);
1901 gdImageSetPixel(brush,0,1,pen);
1902 gdImageSetPixel(brush,1,1,pen);
1903 gdImageSetPixel(brush,2,1,pen);
1904 gdImageSetPixel(brush,1,2,pen);
1911 /*****************************************************
1912 * lazy check make sure we rely need to create this graph
1913 *****************************************************/
1915 int lazy_check(image_desc_t *im){
1918 struct stat gifstat;
1920 if (im->lazy == 0) return 0; /* no lazy option */
1921 if (stat(im->graphfile,&gifstat) != 0)
1922 return 0; /* can't stat */
1923 /* one pixel in the existing graph is more then what we would
1925 if (time(NULL) - gifstat.st_mtime >
1926 (im->end - im->start) / im->xsize)
1928 if ((fd = fopen(im->graphfile,"rb")) == NULL)
1929 return 0; /* the file does not exist */
1930 switch (im->imgformat) {
1932 size = GifSize(fd,&(im->xgif),&(im->ygif));
1935 size = PngSize(fd,&(im->xgif),&(im->ygif));
1942 /* draw that picture thing ... */
1944 graph_paint(image_desc_t *im, char ***calcpr)
1947 int lazy = lazy_check(im);
1951 gdImagePtr gif,brush;
1953 double areazero = 0.0;
1954 enum gf_en stack_gf = GF_PRINT;
1955 graph_desc_t *lastgdes = NULL;
1956 gdPoint canvas[4], back[4]; /* points for canvas*/
1958 /* if we are lazy and there is nothing to PRINT ... quit now */
1959 if (lazy && im->prt_c==0) return 0;
1961 /* pull the data from the rrd files ... */
1963 if(data_fetch(im)==-1)
1966 /* evaluate VDEF and CDEF operations ... */
1967 if(data_calc(im)==-1)
1970 /* calculate and PRINT and GPRINT definitions. We have to do it at
1971 * this point because it will affect the length of the legends
1972 * if there are no graph elements we stop here ...
1973 * if we are lazy, try to quit ...
1975 i=print_calc(im,calcpr);
1977 if(i==0 || lazy) return 0;
1979 /* get actual drawing data and find min and max values*/
1980 if(data_proc(im)==-1)
1983 if(!im->logarithmic){si_unit(im);} /* identify si magnitude Kilo, Mega Giga ? */
1985 if(!im->rigid && ! im->logarithmic)
1986 expand_range(im); /* make sure the upper and lower limit are
1989 /* init xtr and ytr */
1990 /* determine the actual size of the gif to draw. The size given
1991 on the cmdline is the graph area. But we need more as we have
1992 draw labels and other things outside the graph area */
1995 im->xorigin = 10 + 9 * SmallFont->w+SmallFont->h;
1998 im->yorigin = 14 + im->ysize;
2001 if(im->title[0] != '\0')
2002 im->yorigin += (LargeFont->h+4);
2004 im->xgif=20+im->xsize + im->xorigin;
2005 im->ygif= im->yorigin+2*SmallFont->h;
2007 /* determine where to place the legends onto the graphics.
2008 and set im->ygif to match space requirements for text */
2009 if(leg_place(im)==-1)
2012 gif=gdImageCreate(im->xgif,im->ygif);
2014 gdImageInterlace(gif, im->interlaced);
2016 /* allocate colors for the screen elements */
2017 for(i=0;i<DIM(graph_col);i++)
2018 /* check for user override values */
2019 if(im->graph_col[i].red != -1)
2021 gdImageColorAllocate( gif,
2022 im->graph_col[i].red,
2023 im->graph_col[i].green,
2024 im->graph_col[i].blue);
2027 gdImageColorAllocate( gif,
2033 /* allocate colors for the graph */
2034 for(i=0;i<im->gdes_c;i++)
2035 /* only for elements which have a color defined */
2036 if (im->gdes[i].col.red != -1)
2038 gdImageColorAllocate(gif,
2039 im->gdes[i].col.red,
2040 im->gdes[i].col.green,
2041 im->gdes[i].col.blue);
2044 /* the actual graph is created by going through the individual
2045 graph elements and then drawing them */
2049 back[1].x = back[0].x+im->xgif;
2050 back[1].y = back[0].y;
2051 back[2].x = back[1].x;
2052 back[2].y = back[0].y+im->ygif;
2053 back[3].x = back[0].x;
2054 back[3].y = back[2].y;
2056 gdImageFilledPolygon(gif,back,4,graph_col[GRC_BACK].i);
2058 canvas[0].x = im->xorigin;
2059 canvas[0].y = im->yorigin;
2060 canvas[1].x = canvas[0].x+im->xsize;
2061 canvas[1].y = canvas[0].y;
2062 canvas[2].x = canvas[1].x;
2063 canvas[2].y = canvas[0].y-im->ysize;
2064 canvas[3].x = canvas[0].x;
2065 canvas[3].y = canvas[2].y;
2067 gdImageFilledPolygon(gif,canvas,4,graph_col[GRC_CANVAS].i);
2069 if (im->minval > 0.0)
2070 areazero = im->minval;
2071 if (im->maxval < 0.0)
2072 areazero = im->maxval;
2076 for(i=0;i<im->gdes_c;i++){
2078 switch(im->gdes[i].gf){
2089 for (ii = 0; ii < im->xsize; ii++)
2091 if (!isnan(im->gdes[i].p_data[ii]) &&
2092 im->gdes[i].p_data[ii] > 0.0)
2094 /* generate a tick */
2095 gdImageLine(gif, im -> xorigin + ii,
2096 im -> yorigin - (im -> gdes[i].yrule * im -> ysize),
2099 im -> gdes[i].col.i);
2107 stack_gf = im->gdes[i].gf;
2109 /* fix data points at oo and -oo */
2110 for(ii=0;ii<im->xsize;ii++){
2111 if (isinf(im->gdes[i].p_data[ii])){
2112 if (im->gdes[i].p_data[ii] > 0) {
2113 im->gdes[i].p_data[ii] = im->maxval ;
2115 im->gdes[i].p_data[ii] = im->minval ;
2121 if (im->gdes[i].col.i != -1){
2122 /* GF_LINE and frined */
2123 if(stack_gf == GF_LINE1 || stack_gf == GF_LINE2 || stack_gf == GF_LINE3 ){
2124 brush = MkLineBrush(im,i,stack_gf);
2125 gdImageSetBrush(gif, brush);
2126 for(ii=1;ii<im->xsize;ii++){
2127 if (isnan(im->gdes[i].p_data[ii-1]) ||
2128 isnan(im->gdes[i].p_data[ii]))
2131 ii+im->xorigin-1,ytr(im,im->gdes[i].p_data[ii-1]),
2132 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2136 gdImageDestroy(brush);
2139 /* GF_AREA STACK type*/
2140 if (im->gdes[i].gf == GF_STACK )
2141 for(ii=0;ii<im->xsize;ii++){
2142 if(isnan(im->gdes[i].p_data[ii])){
2143 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
2147 if (lastgdes->p_data[ii] == im->gdes[i].p_data[ii]){
2151 ii+im->xorigin,ytr(im,lastgdes->p_data[ii]),
2152 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2156 else /* simple GF_AREA */
2157 for(ii=0;ii<im->xsize;ii++){
2158 if (isnan(im->gdes[i].p_data[ii])) {
2159 im->gdes[i].p_data[ii] = 0;
2163 ii+im->xorigin,ytr(im,areazero),
2164 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2168 lastgdes = &(im->gdes[i]);
2175 /* the RULES are the last thing to paint ... */
2176 for(i=0;i<im->gdes_c;i++){
2178 switch(im->gdes[i].gf){
2180 printf("DEBUG: HRULE at %f\n",im->gdes[i].yrule);
2181 if(isnan(im->gdes[i].yrule)) { /* fetch variable */
2182 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2184 if(im->gdes[i].yrule >= im->minval
2185 && im->gdes[i].yrule <= im->maxval)
2187 im->xorigin,ytr(im,im->gdes[i].yrule),
2188 im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2192 if(im->gdes[i].xrule == 0) { /* fetch variable */
2193 im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
2195 if(im->gdes[i].xrule >= im->start
2196 && im->gdes[i].xrule <= im->end)
2198 xtr(im,im->gdes[i].xrule),im->yorigin,
2199 xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2207 if (strcmp(im->graphfile,"-")==0) {
2209 /* Change translation mode for stdout to BINARY */
2210 _setmode( _fileno( stdout ), O_BINARY );
2214 if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2215 rrd_set_error("Opening '%s' for write: %s",im->graphfile, strerror(errno));
2219 switch (im->imgformat) {
2221 gdImageGif(gif, fo);
2224 gdImagePng(gif, fo);
2227 if (strcmp(im->graphfile,"-") != 0)
2229 gdImageDestroy(gif);
2235 /*****************************************************
2237 *****************************************************/
2240 gdes_alloc(image_desc_t *im){
2242 long def_step = (im->end-im->start)/im->xsize;
2244 if (im->step > def_step) /* step can be increassed ... no decreassed */
2245 def_step = im->step;
2249 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2250 * sizeof(graph_desc_t)))==NULL){
2251 rrd_set_error("realloc graph_descs");
2256 im->gdes[im->gdes_c-1].step=def_step;
2257 im->gdes[im->gdes_c-1].start=im->start;
2258 im->gdes[im->gdes_c-1].end=im->end;
2259 im->gdes[im->gdes_c-1].vname[0]='\0';
2260 im->gdes[im->gdes_c-1].data=NULL;
2261 im->gdes[im->gdes_c-1].ds_namv=NULL;
2262 im->gdes[im->gdes_c-1].data_first=0;
2263 im->gdes[im->gdes_c-1].p_data=NULL;
2264 im->gdes[im->gdes_c-1].rpnp=NULL;
2265 im->gdes[im->gdes_c-1].col.red = -1;
2266 im->gdes[im->gdes_c-1].col.i=-1;
2267 im->gdes[im->gdes_c-1].legend[0]='\0';
2268 im->gdes[im->gdes_c-1].rrd[0]='\0';
2269 im->gdes[im->gdes_c-1].ds=-1;
2270 im->gdes[im->gdes_c-1].p_data=NULL;
2274 /* copies input untill the first unescaped colon is found
2275 or until input ends. backslashes have to be escaped as well */
2277 scan_for_col(char *input, int len, char *output)
2282 input[inp] != ':' &&
2285 if (input[inp] == '\\' &&
2286 input[inp+1] != '\0' &&
2287 (input[inp+1] == '\\' ||
2288 input[inp+1] == ':')){
2289 output[outp++] = input[++inp];
2292 output[outp++] = input[inp];
2295 output[outp] = '\0';
2300 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize)
2306 time_t start_tmp=0,end_tmp=0;
2307 char scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
2309 unsigned int col_red,col_green,col_blue;
2311 int linepass = 0; /* stack can only follow directly after LINE* AREA or STACK */
2312 struct time_value start_tv, end_tv;
2313 char *parsetime_error = NULL;
2318 parsetime("end-24h", &start_tv);
2319 parsetime("now", &end_tv);
2321 im.xlab_user.minsec = -1;
2327 im.ylegend[0] = '\0';
2332 im.unitsexponent= 9999;
2338 im.ygridstep = DNAN;
2345 im.imgformat = IF_GIF; /* we default to GIF output */
2347 for(i=0;i<DIM(graph_col);i++)
2348 im.graph_col[i].red=-1;
2352 static struct option long_options[] =
2354 {"start", required_argument, 0, 's'},
2355 {"end", required_argument, 0, 'e'},
2356 {"x-grid", required_argument, 0, 'x'},
2357 {"y-grid", required_argument, 0, 'y'},
2358 {"vertical-label",required_argument,0,'v'},
2359 {"width", required_argument, 0, 'w'},
2360 {"height", required_argument, 0, 'h'},
2361 {"interlaced", no_argument, 0, 'i'},
2362 {"upper-limit",required_argument, 0, 'u'},
2363 {"lower-limit",required_argument, 0, 'l'},
2364 {"rigid", no_argument, 0, 'r'},
2365 {"base", required_argument, 0, 'b'},
2366 {"logarithmic",no_argument, 0, 'o'},
2367 {"color", required_argument, 0, 'c'},
2368 {"title", required_argument, 0, 't'},
2369 {"imginfo", required_argument, 0, 'f'},
2370 {"imgformat", required_argument, 0, 'a'},
2371 {"lazy", no_argument, 0, 'z'},
2372 {"no-legend", no_argument, 0, 'g'},
2373 {"alt-y-grid", no_argument, 0, 257 },
2374 {"alt-autoscale", no_argument, 0, 258 },
2375 {"alt-autoscale-max", no_argument, 0, 259 },
2376 {"units-exponent",required_argument, 0, 260},
2377 {"step", required_argument, 0, 261},
2379 int option_index = 0;
2383 opt = getopt_long(argc, argv,
2384 "s:e:x:y:v:w:h:iu:l:rb:oc:t:f:a:z:g",
2385 long_options, &option_index);
2392 im.extra_flags |= ALTYGRID;
2395 im.extra_flags |= ALTAUTOSCALE;
2398 im.extra_flags |= ALTAUTOSCALE_MAX;
2401 im.extra_flags |= NOLEGEND;
2404 im.unitsexponent = atoi(optarg);
2407 im.step = atoi(optarg);
2410 if ((parsetime_error = parsetime(optarg, &start_tv))) {
2411 rrd_set_error( "start time: %s", parsetime_error );
2416 if ((parsetime_error = parsetime(optarg, &end_tv))) {
2417 rrd_set_error( "end time: %s", parsetime_error );
2422 if(strcmp(optarg,"none") == 0){
2428 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
2430 &im.xlab_user.gridst,
2432 &im.xlab_user.mgridst,
2434 &im.xlab_user.labst,
2435 &im.xlab_user.precis,
2436 &stroff) == 7 && stroff != 0){
2437 strncpy(im.xlab_form, optarg+stroff, sizeof(im.xlab_form) - 1);
2438 if((im.xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
2439 rrd_set_error("unknown keyword %s",scan_gtm);
2441 } else if ((im.xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
2442 rrd_set_error("unknown keyword %s",scan_mtm);
2444 } else if ((im.xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
2445 rrd_set_error("unknown keyword %s",scan_ltm);
2448 im.xlab_user.minsec = 1;
2449 im.xlab_user.stst = im.xlab_form;
2451 rrd_set_error("invalid x-grid format");
2457 if(strcmp(optarg,"none") == 0){
2465 &im.ylabfact) == 2) {
2466 if(im.ygridstep<=0){
2467 rrd_set_error("grid step must be > 0");
2469 } else if (im.ylabfact < 1){
2470 rrd_set_error("label factor must be > 0");
2474 rrd_set_error("invalid y-grid format");
2479 strncpy(im.ylegend,optarg,150);
2480 im.ylegend[150]='\0';
2483 im.maxval = atof(optarg);
2486 im.minval = atof(optarg);
2489 im.base = atol(optarg);
2490 if(im.base != 1024 && im.base != 1000 ){
2491 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
2496 long_tmp = atol(optarg);
2497 if (long_tmp < 10) {
2498 rrd_set_error("width below 10 pixels");
2501 im.xsize = long_tmp;
2504 long_tmp = atol(optarg);
2505 if (long_tmp < 10) {
2506 rrd_set_error("height below 10 pixels");
2509 im.ysize = long_tmp;
2518 im.imginfo = optarg;
2521 if((im.imgformat = if_conv(optarg)) == -1) {
2522 rrd_set_error("unsupported graphics format '%s'",optarg);
2531 if (isnan(im.minval))
2536 "%10[A-Z]#%2x%2x%2x",
2537 col_nam,&col_red,&col_green,&col_blue) == 4){
2539 if((ci=grc_conv(col_nam)) != -1){
2540 im.graph_col[ci].red=col_red;
2541 im.graph_col[ci].green=col_green;
2542 im.graph_col[ci].blue=col_blue;
2544 rrd_set_error("invalid color name '%s'",col_nam);
2547 rrd_set_error("invalid color def format");
2552 strncpy(im.title,optarg,150);
2558 rrd_set_error("unknown option '%c'", optopt);
2560 rrd_set_error("unknown option '%s'",argv[optind-1]);
2565 if (optind >= argc) {
2566 rrd_set_error("missing filename");
2570 if (im.logarithmic == 1 && (im.minval <= 0 || isnan(im.minval))){
2571 rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");
2575 strncpy(im.graphfile,argv[optind],MAXPATH-1);
2576 im.graphfile[MAXPATH-1]='\0';
2578 if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
2582 if (start_tmp < 3600*24*365*10){
2583 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
2587 if (end_tmp < start_tmp) {
2588 rrd_set_error("start (%ld) should be less than end (%ld)",
2589 start_tmp, end_tmp);
2593 im.start = start_tmp;
2597 for(i=optind+1;i<argc;i++){
2600 char varname[MAX_VNAME_LEN+1],*rpnex;
2602 if(sscanf(argv[i],"%10[A-Z0-9]:%n",symname,&argstart)==1){
2603 if((im.gdes[im.gdes_c-1].gf=gf_conv(symname))==-1){
2605 rrd_set_error("unknown function '%s'",symname);
2609 rrd_set_error("can't parse '%s'",argv[i]);
2614 /* reset linepass if a non LINE/STACK/AREA operator gets parsed
2616 if (im.gdes[im.gdes_c-1].gf != GF_LINE1 &&
2617 im.gdes[im.gdes_c-1].gf != GF_LINE2 &&
2618 im.gdes[im.gdes_c-1].gf != GF_LINE3 &&
2619 im.gdes[im.gdes_c-1].gf != GF_AREA &&
2620 im.gdes[im.gdes_c-1].gf != GF_STACK) {
2625 switch(im.gdes[im.gdes_c-1].gf){
2630 sscanf(&argv[i][argstart], DEF_NAM_FMT ":%n"
2637 rrd_set_error("can't parse vname in '%s'",&argv[i][argstart]);
2641 if ((im.gdes[im.gdes_c-1].vidx=find_var(&im,varname))==-1){
2643 rrd_set_error("Unknown variable '%s' in (G)PRINT",varname);
2648 sscanf(&argv[i][argstart+strstart],CF_NAM_FMT ":%n"
2652 if (im.gdes[im.gdes[im.gdes_c-1].vidx].gf==GF_VDEF) {
2653 /* No consolidation function should be present */
2655 rrd_set_error("(G)PRINT of VDEF needs no CF");
2660 /* A consolidation function should follow */
2663 rrd_set_error("Missing or incorrect CF in (G)PRINTing '%s' (%s)",varname,&argv[i][argstart]);
2666 if((im.gdes[im.gdes_c-1].cf=cf_conv(symname))==-1){
2675 &argv[i][argstart+strstart]
2677 ,im.gdes[im.gdes_c-1].format
2681 if(strlen(&argv[i][argstart])>FMT_LEG_LEN) argv[i][argstart+FMT_LEG_LEN-3]='\0' ;
2682 strcpy(im.gdes[im.gdes_c-1].legend, &argv[i][argstart]);
2685 /* scan for either "HRULE:vname#..." or "HRULE:num#..."
2687 * If a vname is used, the value NaN is set; this is catched
2688 * when graphing. Setting value NaN from the script is not
2692 sscanf(&argv[i][argstart], "%lf#%n"
2693 ,&im.gdes[im.gdes_c-1].yrule
2696 if (strstart==0) { /* no number, should be vname */
2697 sscanf(&argv[i][argstart], DEF_NAM_FMT "#%n"
2702 im.gdes[im.gdes_c-1].yrule = DNAN;/* signal use of vname */
2703 if((im.gdes[im.gdes_c-1].vidx=find_var(&im,varname))==-1){
2705 rrd_set_error("unknown variable '%s' in HRULE",varname);
2708 if(im.gdes[im.gdes[im.gdes_c-1].vidx].gf != GF_VDEF) {
2710 rrd_set_error("Only VDEF is allowed in HRULE",varname);
2715 printf("DEBUG: matched HRULE:num\n");
2716 printf("DEBUG: strstart==%i\n",strstart);
2720 rrd_set_error("can't parse '%s'",&argv[i][argstart]);
2725 &argv[i][argstart+strstart],
2731 im.gdes[im.gdes_c-1].col.red = col_red;
2732 im.gdes[im.gdes_c-1].col.green = col_green;
2733 im.gdes[im.gdes_c-1].col.blue = col_blue;
2735 im.gdes[im.gdes_c-1].legend[0] = '\0';
2737 scan_for_col(&argv[i][argstart+strstart+n],FMT_LEG_LEN,im.gdes[im.gdes_c-1].legend);
2741 rrd_set_error("can't parse '%s'",&argv[i][argstart]);
2748 /* scan for either "VRULE:vname#..." or "VRULE:num#..."
2750 * If a vname is used, the value 0 is set; this is catched
2751 * when graphing. Setting value 0 from the script is not
2755 sscanf(&argv[i][argstart], "%lu#%n"
2756 ,(long unsigned int *)&im.gdes[im.gdes_c-1].xrule
2759 if (strstart==0) { /* no number, should be vname */
2760 sscanf(&argv[i][argstart], DEF_NAM_FMT "#%n"
2764 if (strstart!=0) { /* vname matched */
2765 im.gdes[im.gdes_c-1].xrule = 0;/* signal use of vname */
2766 if((im.gdes[im.gdes_c-1].vidx=find_var(&im,varname))==-1){
2768 rrd_set_error("unknown variable '%s' in VRULE",varname);
2771 if(im.gdes[im.gdes[im.gdes_c-1].vidx].gf != GF_VDEF) {
2773 rrd_set_error("Only VDEF is allowed in VRULE",varname);
2778 if (im.gdes[im.gdes_c-1].xrule==0)
2784 rrd_set_error("can't parse '%s'",&argv[i][argstart]);
2789 &argv[i][argstart+strstart],
2795 im.gdes[im.gdes_c-1].col.red = col_red;
2796 im.gdes[im.gdes_c-1].col.green = col_green;
2797 im.gdes[im.gdes_c-1].col.blue = col_blue;
2799 im.gdes[im.gdes_c-1].legend[0] = '\0';
2801 scan_for_col(&argv[i][argstart+strstart+n],FMT_LEG_LEN,im.gdes[im.gdes_c-1].legend);
2805 rrd_set_error("can't parse '%s'",&argv[i][argstart]);
2811 if((scancount=sscanf(
2813 "%29[^:#]#%2x%2x%2x:%lf:%n",
2818 &(im.gdes[im.gdes_c-1].yrule),
2821 im.gdes[im.gdes_c-1].col.red = col_red;
2822 im.gdes[im.gdes_c-1].col.green = col_green;
2823 im.gdes[im.gdes_c-1].col.blue = col_blue;
2825 im.gdes[im.gdes_c-1].legend[0] = '\0';
2827 scan_for_col(&argv[i][argstart+strstart],FMT_LEG_LEN,im.gdes[im.gdes_c-1].legend);
2829 if((im.gdes[im.gdes_c-1].vidx=find_var(&im,varname))==-1){
2831 rrd_set_error("unknown variable '%s'",varname);
2834 if (im.gdes[im.gdes_c-1].yrule <= 0.0 || im.gdes[im.gdes_c-1].yrule > 1.0)
2837 rrd_set_error("Tick mark scaling factor out of range");
2841 im.gdes[im.gdes_c-1].col.red = -1;
2843 /* default tick marks: 10% of the y-axis */
2844 im.gdes[im.gdes_c-1].yrule = 0.1;
2848 rrd_set_error("can't parse '%s'",&argv[i][argstart]);
2850 } /* endif sscanf */
2855 rrd_set_error("STACK must follow AREA, LINE or STACK");
2863 if((scancount=sscanf(
2865 "%29[^:#]#%2x%2x%2x:%n",
2871 im.gdes[im.gdes_c-1].col.red = col_red;
2872 im.gdes[im.gdes_c-1].col.green = col_green;
2873 im.gdes[im.gdes_c-1].col.blue = col_blue;
2875 im.gdes[im.gdes_c-1].legend[0] = '\0';
2877 scan_for_col(&argv[i][argstart+strstart],FMT_LEG_LEN,im.gdes[im.gdes_c-1].legend);
2879 if((im.gdes[im.gdes_c-1].vidx=find_var(&im,varname))==-1){
2881 rrd_set_error("unknown variable '%s'",varname);
2885 im.gdes[im.gdes_c-1].col.red = -1;
2889 rrd_set_error("can't parse '%s'",&argv[i][argstart]);
2894 if((rpnex = malloc(strlen(&argv[i][argstart])*sizeof(char)))==NULL){
2895 rrd_set_error("malloc for CDEF");
2900 DEF_NAM_FMT "=%[^: ]",
2901 im.gdes[im.gdes_c-1].vname,
2905 rrd_set_error("can't parse CDEF '%s'",&argv[i][argstart]);
2908 /* checking for duplicate variable names */
2909 if(find_var(&im,im.gdes[im.gdes_c-1].vname) != -1){
2911 rrd_set_error("duplicate variable '%s'",
2912 im.gdes[im.gdes_c-1].vname);
2915 if((im.gdes[im.gdes_c-1].rpnp =
2916 rpn_parse((void*)&im,rpnex,&find_var_wrapper))== NULL){
2917 rrd_set_error("invalid rpn expression '%s'", rpnex);
2924 strstart=parse_vname1(&argv[i][argstart],&im,"VDEF");
2925 if (strstart==0) return -1;
2935 rrd_set_error("Cannot parse '%s' in VDEF '%s'",
2937 im.gdes[im.gdes_c-1].vname);
2940 if ((im.gdes[im.gdes_c-1].vidx=find_var(&im,varname)) == -1) {
2942 rrd_set_error("variable '%s' not known in VDEF '%s'",
2944 im.gdes[im.gdes_c-1].vname);
2948 im.gdes[im.gdes[im.gdes_c-1].vidx].gf != GF_DEF
2949 && im.gdes[im.gdes[im.gdes_c-1].vidx].gf != GF_CDEF) {
2950 rrd_set_error("variable '%s' not DEF nor CDEF in VDEF '%s'",
2952 im.gdes[im.gdes_c-1].vname);
2957 /* parsed upto and including the first comma. Now
2958 * see what function is requested. This function
2959 * sets the error string.
2961 if (vdef_parse(&im.gdes[im.gdes_c-1],&argv[i][argstart+strstart])<0) {
2970 im.gdes[im.gdes_c-1].vname,
2971 &strstart)== 1 && strstart){ /* is the = did not match %n returns 0 */
2972 if(sscanf(&argv[i][argstart
2974 +scan_for_col(&argv[i][argstart+strstart],
2975 MAXPATH,im.gdes[im.gdes_c-1].rrd)],
2976 ":" DS_NAM_FMT ":" CF_NAM_FMT,
2977 im.gdes[im.gdes_c-1].ds_nam,
2980 rrd_set_error("can't parse DEF '%s' -2",&argv[i][argstart]);
2985 rrd_set_error("can't parse DEF '%s'",&argv[i][argstart]);
2989 /* checking for duplicate DEF CDEFS */
2990 if (find_var(&im,im.gdes[im.gdes_c-1].vname) != -1){
2992 rrd_set_error("duplicate variable '%s'",
2993 im.gdes[im.gdes_c-1].vname);
2996 if((im.gdes[im.gdes_c-1].cf=cf_conv(symname))==-1){
2998 rrd_set_error("unknown cf '%s'",symname);
3007 rrd_set_error("can't make a graph without contents");
3012 /* parse rest of arguments containing information on what to draw*/
3013 if (graph_paint(&im,prdata)==-1){
3023 /* maybe prdata is not allocated yet ... lets do it now */
3024 if((*prdata = calloc(2,sizeof(char *)))==NULL){
3025 rrd_set_error("malloc imginfo");
3029 if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
3031 rrd_set_error("malloc imginfo");
3034 filename=im.graphfile+strlen(im.graphfile);
3035 while(filename > im.graphfile){
3036 if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
3040 sprintf((*prdata)[0],im.imginfo,filename,im.xgif,im.ygif);
3046 int bad_format(char *fmt) {
3050 while (*ptr != '\0') {
3051 if (*ptr == '%') {ptr++;
3052 if (*ptr == '\0') return 1;
3053 while ((*ptr >= '0' && *ptr <= '9') || *ptr == '.') {
3056 if (*ptr == '\0') return 1;
3059 if (*ptr == '\0') return 1;
3060 if (*ptr == 'e' || *ptr == 'f') {
3062 } else { return 1; }
3064 else if (*ptr == 's' || *ptr == 'S' || *ptr == '%') { ++ptr; }
3073 vdef_parse(gdes,str)
3074 struct graph_desc_t *gdes;
3077 /* A VDEF currently is either "func" or "param,func"
3078 * so the parsing is rather simple. Change if needed.
3085 sscanf(str,"%le,%29[A-Z]%n",¶m,func,&n);
3086 if (n==strlen(str)) { /* matched */
3090 sscanf(str,"%29[A-Z]%n",func,&n);
3091 if (n==strlen(str)) { /* matched */
3094 rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3101 if (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3102 else if (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3103 else if (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3104 else if (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3105 else if (!strcmp("TOTAL", func)) gdes->vf.op = VDEF_TOTAL;
3106 else if (!strcmp("FIRST", func)) gdes->vf.op = VDEF_FIRST;
3107 else if (!strcmp("LAST", func)) gdes->vf.op = VDEF_LAST;
3109 rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3116 switch (gdes->vf.op) {
3118 if (isnan(param)) { /* no parameter given */
3119 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3125 if (param>=0.0 && param<=100.0) {
3126 gdes->vf.param = param;
3127 gdes->vf.val = DNAN; /* undefined */
3128 gdes->vf.when = 0; /* undefined */
3130 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3144 gdes->vf.param = DNAN;
3145 gdes->vf.val = DNAN;
3148 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3163 graph_desc_t *src,*dst;
3167 dst = &im->gdes[gdi];
3168 src = &im->gdes[dst->vidx];
3169 data = src->data + src->ds + src->ds_cnt; /* skip first value! */
3170 steps = (src->end - src->start) / src->step;
3173 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3180 switch (dst->vf.op) {
3181 case VDEF_PERCENT: {
3182 rrd_value_t * array;
3186 if ((array = malloc(steps*sizeof(double)))==NULL) {
3187 rrd_set_error("malloc VDEV_PERCENT");
3190 for (step=0;step < steps; step++) {
3191 array[step]=data[step*src->ds_cnt];
3193 qsort(array,step,sizeof(double),vdef_percent_compar);
3195 field = (steps-1)*dst->vf.param/100;
3196 dst->vf.val = array[field];
3197 dst->vf.when = 0; /* no time component */
3199 for(step=0;step<steps;step++)
3200 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3206 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3207 if (step == steps) {
3211 dst->vf.val = data[step*src->ds_cnt];
3212 dst->vf.when = src->start + (step+1)*src->step;
3214 while (step != steps) {
3215 if (finite(data[step*src->ds_cnt])) {
3216 if (data[step*src->ds_cnt] > dst->vf.val) {
3217 dst->vf.val = data[step*src->ds_cnt];
3218 dst->vf.when = src->start + (step+1)*src->step;
3225 case VDEF_AVERAGE: {
3228 for (step=0;step<steps;step++) {
3229 if (finite(data[step*src->ds_cnt])) {
3230 sum += data[step*src->ds_cnt];
3235 if (dst->vf.op == VDEF_TOTAL) {
3236 dst->vf.val = sum*src->step;
3237 dst->vf.when = cnt*src->step; /* not really "when" */
3239 dst->vf.val = sum/cnt;
3240 dst->vf.when = 0; /* no time component */
3250 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3251 if (step == steps) {
3255 dst->vf.val = data[step*src->ds_cnt];
3256 dst->vf.when = src->start + (step+1)*src->step;
3258 while (step != steps) {
3259 if (finite(data[step*src->ds_cnt])) {
3260 if (data[step*src->ds_cnt] < dst->vf.val) {
3261 dst->vf.val = data[step*src->ds_cnt];
3262 dst->vf.when = src->start + (step+1)*src->step;
3269 /* The time value returned here is one step before the
3270 * actual time value. This is the start of the first
3274 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3275 if (step == steps) { /* all entries were NaN */
3279 dst->vf.val = data[step*src->ds_cnt];
3280 dst->vf.when = src->start + step*src->step;
3284 /* The time value returned here is the
3285 * actual time value. This is the end of the last
3289 while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3290 if (step < 0) { /* all entries were NaN */
3294 dst->vf.val = data[step*src->ds_cnt];
3295 dst->vf.when = src->start + (step+1)*src->step;
3302 /* NaN < -INF < finite_values < INF */
3304 vdef_percent_compar(a,b)
3307 /* Equality is not returned; this doesn't hurt except
3308 * (maybe) for a little performance.
3311 /* First catch NaN values. They are smallest */
3312 if (isnan( *(double *)a )) return -1;
3313 if (isnan( *(double *)b )) return 1;
3315 /* NaN doestn't reach this part so INF and -INF are extremes.
3316 * The sign from isinf() is compatible with the sign we return
3318 if (isinf( *(double *)a )) return isinf( *(double *)a );
3319 if (isinf( *(double *)b )) return isinf( *(double *)b );
3321 /* If we reach this, both values must be finite */
3322 if ( *(double *)a < *(double *)b ) return -1; else return 1;