1 /****************************************************************************
2 * RRDtool 1.1.x Copyright Tobias Oetiker, 1997 - 2002
3 ****************************************************************************
4 * rrd__graph.c make creates ne rrds
5 ****************************************************************************/
8 #include <sys/stat.h>
10 #include "rrd_tool.h"
12 #ifdef WIN32
13 #include <io.h>
14 #include <fcntl.h>
15 #endif
17 #ifdef HAVE_TIME_H
18 #include <time.h>
19 #endif
21 #ifdef HAVE_LOCALE_H
22 #include <locale.h>
23 #endif
25 #include "rrd_graph.h"
27 /* some constant definitions */
30 #ifndef RRD_DEFAULT_FONT
31 #ifdef WIN32
32 #define RRD_DEFAULT_FONT "c:/winnt/fonts/COUR.TTF"
33 #else
34 #define RRD_DEFAULT_FONT "/usr/share/fonts/truetype/openoffice/ariosor.ttf"
35 /* #define RRD_DEFAULT_FONT "/usr/share/fonts/truetype/Arial.ttf" */
36 #endif
37 #endif
40 text_prop_t text_prop[] = {
41 { 10.0, RRD_DEFAULT_FONT }, /* default */
42 { 12.0, RRD_DEFAULT_FONT }, /* title */
43 { 8.0, RRD_DEFAULT_FONT }, /* axis */
44 { 10.0, RRD_DEFAULT_FONT }, /* unit */
45 { 10.0, RRD_DEFAULT_FONT } /* legend */
46 };
48 xlab_t xlab[] = {
49 {0, TMT_SECOND,30, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
50 {2, TMT_MINUTE,1, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
51 {5, TMT_MINUTE,2, TMT_MINUTE,10, TMT_MINUTE,10, 0,"%H:%M"},
52 {10, TMT_MINUTE,5, TMT_MINUTE,20, TMT_MINUTE,20, 0,"%H:%M"},
53 {30, TMT_MINUTE,10, TMT_HOUR,1, TMT_HOUR,1, 0,"%H:%M"},
54 {60, TMT_MINUTE,30, TMT_HOUR,2, TMT_HOUR,2, 0,"%H:%M"},
55 {180, TMT_HOUR,1, TMT_HOUR,6, TMT_HOUR,6, 0,"%H:%M"},
56 /*{300, TMT_HOUR,3, TMT_HOUR,12, TMT_HOUR,12, 12*3600,"%a %p"}, this looks silly*/
57 {600, TMT_HOUR,6, TMT_DAY,1, TMT_DAY,1, 24*3600,"%a"},
58 {1800, TMT_HOUR,12, TMT_DAY,1, TMT_DAY,2, 24*3600,"%a"},
59 {3600, TMT_DAY,1, TMT_WEEK,1, TMT_WEEK,1, 7*24*3600,"Week %V"},
60 {3*3600, TMT_WEEK,1, TMT_MONTH,1, TMT_WEEK,2, 7*24*3600,"Week %V"},
61 {6*3600, TMT_MONTH,1, TMT_MONTH,1, TMT_MONTH,1, 30*24*3600,"%b"},
62 {48*3600, TMT_MONTH,1, TMT_MONTH,3, TMT_MONTH,3, 30*24*3600,"%b"},
63 {10*24*3600, TMT_YEAR,1, TMT_YEAR,1, TMT_YEAR,1, 365*24*3600,"%y"},
64 {-1,TMT_MONTH,0,TMT_MONTH,0,TMT_MONTH,0,0,""}
65 };
67 /* sensible logarithmic y label intervals ...
68 the first element of each row defines the possible starting points on the
69 y axis ... the other specify the */
71 double yloglab[][12]= {{ 1e9, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
72 { 1e3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
73 { 1e1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
74 /* { 1e1, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, */
75 { 1e1, 1, 2.5, 5, 7.5, 0, 0, 0, 0, 0, 0, 0 },
76 { 1e1, 1, 2, 4, 6, 8, 0, 0, 0, 0, 0, 0 },
77 { 1e1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0 },
78 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }};
80 /* sensible y label intervals ...*/
82 ylab_t ylab[]= {
83 {0.1, {1,2, 5,10}},
84 {0.2, {1,5,10,20}},
85 {0.5, {1,2, 4,10}},
86 {1.0, {1,2, 5,10}},
87 {2.0, {1,5,10,20}},
88 {5.0, {1,2, 4,10}},
89 {10.0, {1,2, 5,10}},
90 {20.0, {1,5,10,20}},
91 {50.0, {1,2, 4,10}},
92 {100.0, {1,2, 5,10}},
93 {200.0, {1,5,10,20}},
94 {500.0, {1,2, 4,10}},
95 {0.0, {0,0,0,0}}};
98 gfx_color_t graph_col[] = /* default colors */
99 { 0xFFFFFFFF, /* canvas */
100 0xF0F0F0FF, /* background */
101 0xD0D0D0FF, /* shade A */
102 0xA0A0A0FF, /* shade B */
103 0x909090FF, /* grid */
104 0xE05050FF, /* major grid */
105 0x000000FF, /* font */
106 0x000000FF, /* frame */
107 0xFF0000FF /* arrow */
108 };
111 /* #define DEBUG */
113 #ifdef DEBUG
114 # define DPRINT(x) (void)(printf x, printf("\n"))
115 #else
116 # define DPRINT(x)
117 #endif
120 /* initialize with xtr(im,0); */
121 int
122 xtr(image_desc_t *im,time_t mytime){
123 static double pixie;
124 if (mytime==0){
125 pixie = (double) im->xsize / (double)(im->end - im->start);
126 return im->xorigin;
127 }
128 return (int)((double)im->xorigin
129 + pixie * ( mytime - im->start ) );
130 }
132 /* translate data values into y coordinates */
133 double
134 ytr(image_desc_t *im, double value){
135 static double pixie;
136 double yval;
137 if (isnan(value)){
138 if(!im->logarithmic)
139 pixie = (double) im->ysize / (im->maxval - im->minval);
140 else
141 pixie = (double) im->ysize / (log10(im->maxval) - log10(im->minval));
142 yval = im->yorigin;
143 } else if(!im->logarithmic) {
144 yval = im->yorigin - pixie * (value - im->minval);
145 } else {
146 if (value < im->minval) {
147 yval = im->yorigin;
148 } else {
149 yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
150 }
151 }
152 /* make sure we don't return anything too unreasonable. GD lib can
153 get terribly slow when drawing lines outside its scope. This is
154 especially problematic in connection with the rigid option */
155 if (! im->rigid) {
156 /* keep yval as-is */
157 } else if (yval > im->yorigin) {
158 yval = im->yorigin+2;
159 } else if (yval < im->yorigin - im->ysize){
160 yval = im->yorigin - im->ysize - 2;
161 }
162 return yval;
163 }
167 /* conversion function for symbolic entry names */
170 #define conv_if(VV,VVV) \
171 if (strcmp(#VV, string) == 0) return VVV ;
173 enum gf_en gf_conv(char *string){
175 conv_if(PRINT,GF_PRINT)
176 conv_if(GPRINT,GF_GPRINT)
177 conv_if(COMMENT,GF_COMMENT)
178 conv_if(HRULE,GF_HRULE)
179 conv_if(VRULE,GF_VRULE)
180 conv_if(LINE,GF_LINE)
181 conv_if(AREA,GF_AREA)
182 conv_if(STACK,GF_STACK)
183 conv_if(TICK,GF_TICK)
184 conv_if(DEF,GF_DEF)
185 conv_if(CDEF,GF_CDEF)
186 conv_if(VDEF,GF_VDEF)
187 conv_if(PART,GF_PART)
188 conv_if(XPORT,GF_XPORT)
190 return (-1);
191 }
193 enum gfx_if_en if_conv(char *string){
195 conv_if(PNG,IF_PNG)
196 conv_if(SVG,IF_SVG)
197 conv_if(EPS,IF_EPS)
198 conv_if(PDF,IF_PDF)
200 return (-1);
201 }
203 enum tmt_en tmt_conv(char *string){
205 conv_if(SECOND,TMT_SECOND)
206 conv_if(MINUTE,TMT_MINUTE)
207 conv_if(HOUR,TMT_HOUR)
208 conv_if(DAY,TMT_DAY)
209 conv_if(WEEK,TMT_WEEK)
210 conv_if(MONTH,TMT_MONTH)
211 conv_if(YEAR,TMT_YEAR)
212 return (-1);
213 }
215 enum grc_en grc_conv(char *string){
217 conv_if(BACK,GRC_BACK)
218 conv_if(CANVAS,GRC_CANVAS)
219 conv_if(SHADEA,GRC_SHADEA)
220 conv_if(SHADEB,GRC_SHADEB)
221 conv_if(GRID,GRC_GRID)
222 conv_if(MGRID,GRC_MGRID)
223 conv_if(FONT,GRC_FONT)
224 conv_if(FRAME,GRC_FRAME)
225 conv_if(ARROW,GRC_ARROW)
227 return -1;
228 }
230 enum text_prop_en text_prop_conv(char *string){
232 conv_if(DEFAULT,TEXT_PROP_DEFAULT)
233 conv_if(TITLE,TEXT_PROP_TITLE)
234 conv_if(AXIS,TEXT_PROP_AXIS)
235 conv_if(UNIT,TEXT_PROP_UNIT)
236 conv_if(LEGEND,TEXT_PROP_LEGEND)
237 return -1;
238 }
241 #undef conv_if
243 int
244 im_free(image_desc_t *im)
245 {
246 unsigned long i,ii;
248 if (im == NULL) return 0;
249 for(i=0;i<(unsigned)im->gdes_c;i++){
250 if (im->gdes[i].data_first){
251 /* careful here, because a single pointer can occur several times */
252 free (im->gdes[i].data);
253 if (im->gdes[i].ds_namv){
254 for (ii=0;ii<im->gdes[i].ds_cnt;ii++)
255 free(im->gdes[i].ds_namv[ii]);
256 free(im->gdes[i].ds_namv);
257 }
258 }
259 free (im->gdes[i].p_data);
260 free (im->gdes[i].rpnp);
261 }
262 free(im->gdes);
263 gfx_destroy(im->canvas);
264 return 0;
265 }
267 /* find SI magnitude symbol for the given number*/
268 void
269 auto_scale(
270 image_desc_t *im, /* image description */
271 double *value,
272 char **symb_ptr,
273 double *magfact
274 )
275 {
277 char *symbol[] = {"a", /* 10e-18 Atto */
278 "f", /* 10e-15 Femto */
279 "p", /* 10e-12 Pico */
280 "n", /* 10e-9 Nano */
281 "u", /* 10e-6 Micro */
282 "m", /* 10e-3 Milli */
283 " ", /* Base */
284 "k", /* 10e3 Kilo */
285 "M", /* 10e6 Mega */
286 "G", /* 10e9 Giga */
287 "T", /* 10e12 Tera */
288 "P", /* 10e15 Peta */
289 "E"};/* 10e18 Exa */
291 int symbcenter = 6;
292 int sindex;
294 if (*value == 0.0 || isnan(*value) ) {
295 sindex = 0;
296 *magfact = 1.0;
297 } else {
298 sindex = floor(log(fabs(*value))/log((double)im->base));
299 *magfact = pow((double)im->base, (double)sindex);
300 (*value) /= (*magfact);
301 }
302 if ( sindex <= symbcenter && sindex >= -symbcenter) {
303 (*symb_ptr) = symbol[sindex+symbcenter];
304 }
305 else {
306 (*symb_ptr) = "?";
307 }
308 }
311 /* find SI magnitude symbol for the numbers on the y-axis*/
312 void
313 si_unit(
314 image_desc_t *im /* image description */
315 )
316 {
318 char symbol[] = {'a', /* 10e-18 Atto */
319 'f', /* 10e-15 Femto */
320 'p', /* 10e-12 Pico */
321 'n', /* 10e-9 Nano */
322 'u', /* 10e-6 Micro */
323 'm', /* 10e-3 Milli */
324 ' ', /* Base */
325 'k', /* 10e3 Kilo */
326 'M', /* 10e6 Mega */
327 'G', /* 10e9 Giga */
328 'T', /* 10e12 Tera */
329 'P', /* 10e15 Peta */
330 'E'};/* 10e18 Exa */
332 int symbcenter = 6;
333 double digits;
335 if (im->unitsexponent != 9999) {
336 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
337 digits = floor(im->unitsexponent / 3);
338 } else {
339 digits = floor( log( max( fabs(im->minval),fabs(im->maxval)))/log((double)im->base));
340 }
341 im->magfact = pow((double)im->base , digits);
343 #ifdef DEBUG
344 printf("digits %6.3f im->magfact %6.3f\n",digits,im->magfact);
345 #endif
347 if ( ((digits+symbcenter) < sizeof(symbol)) &&
348 ((digits+symbcenter) >= 0) )
349 im->symbol = symbol[(int)digits+symbcenter];
350 else
351 im->symbol = ' ';
352 }
354 /* move min and max values around to become sensible */
356 void
357 expand_range(image_desc_t *im)
358 {
359 double sensiblevalues[] ={1000.0,900.0,800.0,750.0,700.0,
360 600.0,500.0,400.0,300.0,250.0,
361 200.0,125.0,100.0,90.0,80.0,
362 75.0,70.0,60.0,50.0,40.0,30.0,
363 25.0,20.0,10.0,9.0,8.0,
364 7.0,6.0,5.0,4.0,3.5,3.0,
365 2.5,2.0,1.8,1.5,1.2,1.0,
366 0.8,0.7,0.6,0.5,0.4,0.3,0.2,0.1,0.0,-1};
368 double scaled_min,scaled_max;
369 double adj;
370 int i;
374 #ifdef DEBUG
375 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
376 im->minval,im->maxval,im->magfact);
377 #endif
379 if (isnan(im->ygridstep)){
380 if(im->extra_flags & ALTAUTOSCALE) {
381 /* measure the amplitude of the function. Make sure that
382 graph boundaries are slightly higher then max/min vals
383 so we can see amplitude on the graph */
384 double delt, fact;
386 delt = im->maxval - im->minval;
387 adj = delt * 0.1;
388 fact = 2.0 * pow(10.0,
389 floor(log10(max(fabs(im->minval), fabs(im->maxval)))) - 2);
390 if (delt < fact) {
391 adj = (fact - delt) * 0.55;
392 #ifdef DEBUG
393 printf("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n", im->minval, im->maxval, delt, fact, adj);
394 #endif
395 }
396 im->minval -= adj;
397 im->maxval += adj;
398 }
399 else if(im->extra_flags & ALTAUTOSCALE_MAX) {
400 /* measure the amplitude of the function. Make sure that
401 graph boundaries are slightly higher than max vals
402 so we can see amplitude on the graph */
403 adj = (im->maxval - im->minval) * 0.1;
404 im->maxval += adj;
405 }
406 else {
407 scaled_min = im->minval / im->magfact;
408 scaled_max = im->maxval / im->magfact;
410 for (i=1; sensiblevalues[i] > 0; i++){
411 if (sensiblevalues[i-1]>=scaled_min &&
412 sensiblevalues[i]<=scaled_min)
413 im->minval = sensiblevalues[i]*(im->magfact);
415 if (-sensiblevalues[i-1]<=scaled_min &&
416 -sensiblevalues[i]>=scaled_min)
417 im->minval = -sensiblevalues[i-1]*(im->magfact);
419 if (sensiblevalues[i-1] >= scaled_max &&
420 sensiblevalues[i] <= scaled_max)
421 im->maxval = sensiblevalues[i-1]*(im->magfact);
423 if (-sensiblevalues[i-1]<=scaled_max &&
424 -sensiblevalues[i] >=scaled_max)
425 im->maxval = -sensiblevalues[i]*(im->magfact);
426 }
427 }
428 } else {
429 /* adjust min and max to the grid definition if there is one */
430 im->minval = (double)im->ylabfact * im->ygridstep *
431 floor(im->minval / ((double)im->ylabfact * im->ygridstep));
432 im->maxval = (double)im->ylabfact * im->ygridstep *
433 ceil(im->maxval /( (double)im->ylabfact * im->ygridstep));
434 }
436 #ifdef DEBUG
437 fprintf(stderr,"SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
438 im->minval,im->maxval,im->magfact);
439 #endif
440 }
442 void
443 apply_gridfit(image_desc_t *im)
444 {
445 if (isnan(im->minval) || isnan(im->maxval))
446 return;
447 ytr(im,DNAN);
448 if (im->logarithmic) {
449 double ya, yb, ypix, ypixfrac;
450 double log10_range = log10(im->maxval) - log10(im->minval);
451 ya = pow((double)10, floor(log10(im->minval)));
452 while (ya < im->minval)
453 ya *= 10;
454 if (ya > im->maxval)
455 return; /* don't have y=10^x gridline */
456 yb = ya * 10;
457 if (yb <= im->maxval) {
458 /* we have at least 2 y=10^x gridlines.
459 Make sure distance between them in pixels
460 are an integer by expanding im->maxval */
461 double y_pixel_delta = ytr(im, ya) - ytr(im, yb);
462 double factor = y_pixel_delta / floor(y_pixel_delta);
463 double new_log10_range = factor * log10_range;
464 double new_ymax_log10 = log10(im->minval) + new_log10_range;
465 im->maxval = pow(10, new_ymax_log10);
466 ytr(im, DNAN); /* reset precalc */
467 log10_range = log10(im->maxval) - log10(im->minval);
468 }
469 /* make sure first y=10^x gridline is located on
470 integer pixel position by moving scale slightly
471 downwards (sub-pixel movement) */
472 ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
473 ypixfrac = ypix - floor(ypix);
474 if (ypixfrac > 0 && ypixfrac < 1) {
475 double yfrac = ypixfrac / im->ysize;
476 im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
477 im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
478 ytr(im, DNAN); /* reset precalc */
479 }
480 } else {
481 /* Make sure we have an integer pixel distance between
482 each minor gridline */
483 double ypos1 = ytr(im, im->minval);
484 double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
485 double y_pixel_delta = ypos1 - ypos2;
486 double factor = y_pixel_delta / floor(y_pixel_delta);
487 double new_range = factor * (im->maxval - im->minval);
488 double gridstep = im->ygrid_scale.gridstep;
489 double minor_y, minor_y_px, minor_y_px_frac;
490 im->maxval = im->minval + new_range;
491 ytr(im, DNAN); /* reset precalc */
492 /* make sure first minor gridline is on integer pixel y coord */
493 minor_y = gridstep * floor(im->minval / gridstep);
494 while (minor_y < im->minval)
495 minor_y += gridstep;
496 minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
497 minor_y_px_frac = minor_y_px - floor(minor_y_px);
498 if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
499 double yfrac = minor_y_px_frac / im->ysize;
500 double range = im->maxval - im->minval;
501 im->minval = im->minval - yfrac * range;
502 im->maxval = im->maxval - yfrac * range;
503 ytr(im, DNAN); /* reset precalc */
504 }
505 calc_horizontal_grid(im); /* recalc with changed im->maxval */
506 }
507 }
509 /* reduce data reimplementation by Alex */
511 void
512 reduce_data(
513 enum cf_en cf, /* which consolidation function ?*/
514 unsigned long cur_step, /* step the data currently is in */
515 time_t *start, /* start, end and step as requested ... */
516 time_t *end, /* ... by the application will be ... */
517 unsigned long *step, /* ... adjusted to represent reality */
518 unsigned long *ds_cnt, /* number of data sources in file */
519 rrd_value_t **data) /* two dimensional array containing the data */
520 {
521 int i,reduce_factor = ceil((double)(*step) / (double)cur_step);
522 unsigned long col,dst_row,row_cnt,start_offset,end_offset,skiprows=0;
523 rrd_value_t *srcptr,*dstptr;
525 (*step) = cur_step*reduce_factor; /* set new step size for reduced data */
526 dstptr = *data;
527 srcptr = *data;
528 row_cnt = ((*end)-(*start))/cur_step;
530 #ifdef DEBUG
531 #define DEBUG_REDUCE
532 #endif
533 #ifdef DEBUG_REDUCE
534 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
535 row_cnt,reduce_factor,*start,*end,cur_step);
536 for (col=0;col<row_cnt;col++) {
537 printf("time %10lu: ",*start+(col+1)*cur_step);
538 for (i=0;i<*ds_cnt;i++)
539 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
540 printf("\n");
541 }
542 #endif
544 /* We have to combine [reduce_factor] rows of the source
545 ** into one row for the destination. Doing this we also
546 ** need to take care to combine the correct rows. First
547 ** alter the start and end time so that they are multiples
548 ** of the new step time. We cannot reduce the amount of
549 ** time so we have to move the end towards the future and
550 ** the start towards the past.
551 */
552 end_offset = (*end) % (*step);
553 start_offset = (*start) % (*step);
555 /* If there is a start offset (which cannot be more than
556 ** one destination row), skip the appropriate number of
557 ** source rows and one destination row. The appropriate
558 ** number is what we do know (start_offset/cur_step) of
559 ** the new interval (*step/cur_step aka reduce_factor).
560 */
561 #ifdef DEBUG_REDUCE
562 printf("start_offset: %lu end_offset: %lu\n",start_offset,end_offset);
563 printf("row_cnt before: %lu\n",row_cnt);
564 #endif
565 if (start_offset) {
566 (*start) = (*start)-start_offset;
567 skiprows=reduce_factor-start_offset/cur_step;
568 srcptr+=skiprows* *ds_cnt;
569 for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
570 row_cnt-=skiprows;
571 }
572 #ifdef DEBUG_REDUCE
573 printf("row_cnt between: %lu\n",row_cnt);
574 #endif
576 /* At the end we have some rows that are not going to be
577 ** used, the amount is end_offset/cur_step
578 */
579 if (end_offset) {
580 (*end) = (*end)-end_offset+(*step);
581 skiprows = end_offset/cur_step;
582 row_cnt-=skiprows;
583 }
584 #ifdef DEBUG_REDUCE
585 printf("row_cnt after: %lu\n",row_cnt);
586 #endif
588 /* Sanity check: row_cnt should be multiple of reduce_factor */
589 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
591 if (row_cnt%reduce_factor) {
592 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
593 row_cnt,reduce_factor);
594 printf("BUG in reduce_data()\n");
595 exit(1);
596 }
598 /* Now combine reduce_factor intervals at a time
599 ** into one interval for the destination.
600 */
602 for (dst_row=0;row_cnt>=reduce_factor;dst_row++) {
603 for (col=0;col<(*ds_cnt);col++) {
604 rrd_value_t newval=DNAN;
605 unsigned long validval=0;
607 for (i=0;i<reduce_factor;i++) {
608 if (isnan(srcptr[i*(*ds_cnt)+col])) {
609 continue;
610 }
611 validval++;
612 if (isnan(newval)) newval = srcptr[i*(*ds_cnt)+col];
613 else {
614 switch (cf) {
615 case CF_HWPREDICT:
616 case CF_DEVSEASONAL:
617 case CF_DEVPREDICT:
618 case CF_SEASONAL:
619 case CF_AVERAGE:
620 newval += srcptr[i*(*ds_cnt)+col];
621 break;
622 case CF_MINIMUM:
623 newval = min (newval,srcptr[i*(*ds_cnt)+col]);
624 break;
625 case CF_FAILURES:
626 /* an interval contains a failure if any subintervals contained a failure */
627 case CF_MAXIMUM:
628 newval = max (newval,srcptr[i*(*ds_cnt)+col]);
629 break;
630 case CF_LAST:
631 newval = srcptr[i*(*ds_cnt)+col];
632 break;
633 }
634 }
635 }
636 if (validval == 0){newval = DNAN;} else{
637 switch (cf) {
638 case CF_HWPREDICT:
639 case CF_DEVSEASONAL:
640 case CF_DEVPREDICT:
641 case CF_SEASONAL:
642 case CF_AVERAGE:
643 newval /= validval;
644 break;
645 case CF_MINIMUM:
646 case CF_FAILURES:
647 case CF_MAXIMUM:
648 case CF_LAST:
649 break;
650 }
651 }
652 *dstptr++=newval;
653 }
654 srcptr+=(*ds_cnt)*reduce_factor;
655 row_cnt-=reduce_factor;
656 }
657 /* If we had to alter the endtime, we didn't have enough
658 ** source rows to fill the last row. Fill it with NaN.
659 */
660 if (end_offset) for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
661 #ifdef DEBUG_REDUCE
662 row_cnt = ((*end)-(*start))/ *step;
663 srcptr = *data;
664 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
665 row_cnt,*start,*end,*step);
666 for (col=0;col<row_cnt;col++) {
667 printf("time %10lu: ",*start+(col+1)*(*step));
668 for (i=0;i<*ds_cnt;i++)
669 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
670 printf("\n");
671 }
672 #endif
673 }
676 /* get the data required for the graphs from the
677 relevant rrds ... */
679 int
680 data_fetch( image_desc_t *im )
681 {
682 int i,ii;
683 int skip;
685 /* pull the data from the log files ... */
686 for (i=0;i<im->gdes_c;i++){
687 /* only GF_DEF elements fetch data */
688 if (im->gdes[i].gf != GF_DEF)
689 continue;
691 skip=0;
692 /* do we have it already ?*/
693 for (ii=0;ii<i;ii++) {
694 if (im->gdes[ii].gf != GF_DEF)
695 continue;
696 if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
697 && (im->gdes[i].cf == im->gdes[ii].cf)
698 && (im->gdes[i].start == im->gdes[ii].start)
699 && (im->gdes[i].end == im->gdes[ii].end)
700 && (im->gdes[i].step == im->gdes[ii].step)) {
701 /* OK, the data is already there.
702 ** Just copy the header portion
703 */
704 im->gdes[i].start = im->gdes[ii].start;
705 im->gdes[i].end = im->gdes[ii].end;
706 im->gdes[i].step = im->gdes[ii].step;
707 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
708 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
709 im->gdes[i].data = im->gdes[ii].data;
710 im->gdes[i].data_first = 0;
711 skip=1;
712 }
713 if (skip)
714 break;
715 }
716 if (! skip) {
717 unsigned long ft_step = im->gdes[i].step ;
719 if((rrd_fetch_fn(im->gdes[i].rrd,
720 im->gdes[i].cf,
721 &im->gdes[i].start,
722 &im->gdes[i].end,
723 &ft_step,
724 &im->gdes[i].ds_cnt,
725 &im->gdes[i].ds_namv,
726 &im->gdes[i].data)) == -1){
727 return -1;
728 }
729 im->gdes[i].data_first = 1;
731 if (ft_step < im->gdes[i].step) {
732 reduce_data(im->gdes[i].cf,
733 ft_step,
734 &im->gdes[i].start,
735 &im->gdes[i].end,
736 &im->gdes[i].step,
737 &im->gdes[i].ds_cnt,
738 &im->gdes[i].data);
739 } else {
740 im->gdes[i].step = ft_step;
741 }
742 }
744 /* lets see if the required data source is realy there */
745 for(ii=0;ii<im->gdes[i].ds_cnt;ii++){
746 if(strcmp(im->gdes[i].ds_namv[ii],im->gdes[i].ds_nam) == 0){
747 im->gdes[i].ds=ii; }
748 }
749 if (im->gdes[i].ds== -1){
750 rrd_set_error("No DS called '%s' in '%s'",
751 im->gdes[i].ds_nam,im->gdes[i].rrd);
752 return -1;
753 }
755 }
756 return 0;
757 }
759 /* evaluate the expressions in the CDEF functions */
761 /*************************************************************
762 * CDEF stuff
763 *************************************************************/
765 long
766 find_var_wrapper(void *arg1, char *key)
767 {
768 return find_var((image_desc_t *) arg1, key);
769 }
771 /* find gdes containing var*/
772 long
773 find_var(image_desc_t *im, char *key){
774 long ii;
775 for(ii=0;ii<im->gdes_c-1;ii++){
776 if((im->gdes[ii].gf == GF_DEF
777 || im->gdes[ii].gf == GF_VDEF
778 || im->gdes[ii].gf == GF_CDEF)
779 && (strcmp(im->gdes[ii].vname,key) == 0)){
780 return ii;
781 }
782 }
783 return -1;
784 }
786 /* find the largest common denominator for all the numbers
787 in the 0 terminated num array */
788 long
789 lcd(long *num){
790 long rest;
791 int i;
792 for (i=0;num[i+1]!=0;i++){
793 do {
794 rest=num[i] % num[i+1];
795 num[i]=num[i+1]; num[i+1]=rest;
796 } while (rest!=0);
797 num[i+1] = num[i];
798 }
799 /* return i==0?num[i]:num[i-1]; */
800 return num[i];
801 }
803 /* run the rpn calculator on all the VDEF and CDEF arguments */
804 int
805 data_calc( image_desc_t *im){
807 int gdi;
808 int dataidx;
809 long *steparray, rpi;
810 int stepcnt;
811 time_t now;
812 rpnstack_t rpnstack;
814 rpnstack_init(&rpnstack);
816 for (gdi=0;gdi<im->gdes_c;gdi++){
817 /* Look for GF_VDEF and GF_CDEF in the same loop,
818 * so CDEFs can use VDEFs and vice versa
819 */
820 switch (im->gdes[gdi].gf) {
821 case GF_XPORT:
822 break;
823 case GF_VDEF:
824 /* A VDEF has no DS. This also signals other parts
825 * of rrdtool that this is a VDEF value, not a CDEF.
826 */
827 im->gdes[gdi].ds_cnt = 0;
828 if (vdef_calc(im,gdi)) {
829 rrd_set_error("Error processing VDEF '%s'"
830 ,im->gdes[gdi].vname
831 );
832 rpnstack_free(&rpnstack);
833 return -1;
834 }
835 break;
836 case GF_CDEF:
837 im->gdes[gdi].ds_cnt = 1;
838 im->gdes[gdi].ds = 0;
839 im->gdes[gdi].data_first = 1;
840 im->gdes[gdi].start = 0;
841 im->gdes[gdi].end = 0;
842 steparray=NULL;
843 stepcnt = 0;
844 dataidx=-1;
846 /* Find the variables in the expression.
847 * - VDEF variables are substituted by their values
848 * and the opcode is changed into OP_NUMBER.
849 * - CDEF variables are analized for their step size,
850 * the lowest common denominator of all the step
851 * sizes of the data sources involved is calculated
852 * and the resulting number is the step size for the
853 * resulting data source.
854 */
855 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
856 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
857 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER){
858 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
859 if (im->gdes[ptr].ds_cnt == 0) {
860 #if 0
861 printf("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
862 im->gdes[gdi].vname,
863 im->gdes[ptr].vname);
864 printf("DEBUG: value from vdef is %f\n",im->gdes[ptr].vf.val);
865 #endif
866 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
867 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
868 } else {
869 if ((steparray =
870 rrd_realloc(steparray,
871 (++stepcnt+1)*sizeof(*steparray)))==NULL){
872 rrd_set_error("realloc steparray");
873 rpnstack_free(&rpnstack);
874 return -1;
875 };
877 steparray[stepcnt-1] = im->gdes[ptr].step;
879 /* adjust start and end of cdef (gdi) so
880 * that it runs from the latest start point
881 * to the earliest endpoint of any of the
882 * rras involved (ptr)
883 */
884 if(im->gdes[gdi].start < im->gdes[ptr].start)
885 im->gdes[gdi].start = im->gdes[ptr].start;
887 if(im->gdes[gdi].end == 0 ||
888 im->gdes[gdi].end > im->gdes[ptr].end)
889 im->gdes[gdi].end = im->gdes[ptr].end;
891 /* store pointer to the first element of
892 * the rra providing data for variable,
893 * further save step size and data source
894 * count of this rra
895 */
896 im->gdes[gdi].rpnp[rpi].data = im->gdes[ptr].data + im->gdes[ptr].ds;
897 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
898 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
900 /* backoff the *.data ptr; this is done so
901 * rpncalc() function doesn't have to treat
902 * the first case differently
903 */
904 } /* if ds_cnt != 0 */
905 } /* if OP_VARIABLE */
906 } /* loop through all rpi */
908 /* move the data pointers to the correct period */
909 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
910 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
911 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER){
912 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
913 if(im->gdes[gdi].start > im->gdes[ptr].start) {
914 im->gdes[gdi].rpnp[rpi].data += im->gdes[gdi].rpnp[rpi].ds_cnt;
915 }
916 }
917 }
920 if(steparray == NULL){
921 rrd_set_error("rpn expressions without DEF"
922 " or CDEF variables are not supported");
923 rpnstack_free(&rpnstack);
924 return -1;
925 }
926 steparray[stepcnt]=0;
927 /* Now find the resulting step. All steps in all
928 * used RRAs have to be visited
929 */
930 im->gdes[gdi].step = lcd(steparray);
931 free(steparray);
932 if((im->gdes[gdi].data = malloc((
933 (im->gdes[gdi].end-im->gdes[gdi].start)
934 / im->gdes[gdi].step)
935 * sizeof(double)))==NULL){
936 rrd_set_error("malloc im->gdes[gdi].data");
937 rpnstack_free(&rpnstack);
938 return -1;
939 }
941 /* Step through the new cdef results array and
942 * calculate the values
943 */
944 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
945 now<=im->gdes[gdi].end;
946 now += im->gdes[gdi].step)
947 {
948 rpnp_t *rpnp = im -> gdes[gdi].rpnp;
950 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
951 * in this case we are advancing by timesteps;
952 * we use the fact that time_t is a synonym for long
953 */
954 if (rpn_calc(rpnp,&rpnstack,(long) now,
955 im->gdes[gdi].data,++dataidx) == -1) {
956 /* rpn_calc sets the error string */
957 rpnstack_free(&rpnstack);
958 return -1;
959 }
960 } /* enumerate over time steps within a CDEF */
961 break;
962 default:
963 continue;
964 }
965 } /* enumerate over CDEFs */
966 rpnstack_free(&rpnstack);
967 return 0;
968 }
970 /* massage data so, that we get one value for each x coordinate in the graph */
971 int
972 data_proc( image_desc_t *im ){
973 long i,ii;
974 double pixstep = (double)(im->end-im->start)
975 /(double)im->xsize; /* how much time
976 passes in one pixel */
977 double paintval;
978 double minval=DNAN,maxval=DNAN;
980 unsigned long gr_time;
982 /* memory for the processed data */
983 for(i=0;i<im->gdes_c;i++) {
984 if((im->gdes[i].gf==GF_LINE) ||
985 (im->gdes[i].gf==GF_AREA) ||
986 (im->gdes[i].gf==GF_TICK) ||
987 (im->gdes[i].gf==GF_STACK)) {
988 if((im->gdes[i].p_data = malloc((im->xsize +1)
989 * sizeof(rrd_value_t)))==NULL){
990 rrd_set_error("malloc data_proc");
991 return -1;
992 }
993 }
994 }
996 for (i=0;i<im->xsize;i++) { /* for each pixel */
997 long vidx;
998 gr_time = im->start+pixstep*i; /* time of the current step */
999 paintval=0.0;
1001 for (ii=0;ii<im->gdes_c;ii++) {
1002 double value;
1003 switch (im->gdes[ii].gf) {
1004 case GF_LINE:
1005 case GF_AREA:
1006 case GF_TICK:
1007 if (!im->gdes[ii].stack)
1008 paintval = 0.0;
1009 case GF_STACK:
1010 value = im->gdes[ii].yrule;
1011 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1012 /* The time of the data doesn't necessarily match
1013 ** the time of the graph. Beware.
1014 */
1015 vidx = im->gdes[ii].vidx;
1016 if ( (gr_time >= im->gdes[vidx].start) &&
1017 (gr_time <= im->gdes[vidx].end) ) {
1018 value = im->gdes[vidx].data[
1019 (unsigned long) floor(
1020 (double)(gr_time - im->gdes[vidx].start)
1021 / im->gdes[vidx].step)
1022 * im->gdes[vidx].ds_cnt
1023 + im->gdes[vidx].ds
1024 ];
1025 } else {
1026 value = DNAN;
1027 }
1028 };
1030 if (! isnan(value)) {
1031 paintval += value;
1032 im->gdes[ii].p_data[i] = paintval;
1033 /* GF_TICK: the data values are not
1034 ** relevant for min and max
1035 */
1036 if (finite(paintval) && im->gdes[ii].gf != GF_TICK ) {
1037 if (isnan(minval) || paintval < minval)
1038 minval = paintval;
1039 if (isnan(maxval) || paintval > maxval)
1040 maxval = paintval;
1041 }
1042 } else {
1043 im->gdes[ii].p_data[i] = DNAN;
1044 }
1045 break;
1046 default:
1047 break;
1048 }
1049 }
1050 }
1052 /* if min or max have not been asigned a value this is because
1053 there was no data in the graph ... this is not good ...
1054 lets set these to dummy values then ... */
1056 if (isnan(minval)) minval = 0.0;
1057 if (isnan(maxval)) maxval = 1.0;
1059 /* adjust min and max values */
1060 if (isnan(im->minval)
1061 /* don't adjust low-end with log scale */
1062 || ((!im->logarithmic && !im->rigid) && im->minval > minval)
1063 )
1064 im->minval = minval;
1065 if (isnan(im->maxval)
1066 || (!im->rigid && im->maxval < maxval)
1067 ) {
1068 if (im->logarithmic)
1069 im->maxval = maxval * 1.1;
1070 else
1071 im->maxval = maxval;
1072 }
1073 /* make sure min and max are not equal */
1074 if (im->minval == im->maxval) {
1075 im->maxval *= 1.01;
1076 if (! im->logarithmic) {
1077 im->minval *= 0.99;
1078 }
1079 /* make sure min and max are not both zero */
1080 if (im->maxval == 0.0) {
1081 im->maxval = 1.0;
1082 }
1083 }
1084 return 0;
1085 }
1089 /* identify the point where the first gridline, label ... gets placed */
1091 time_t
1092 find_first_time(
1093 time_t start, /* what is the initial time */
1094 enum tmt_en baseint, /* what is the basic interval */
1095 long basestep /* how many if these do we jump a time */
1096 )
1097 {
1098 struct tm tm;
1099 tm = *localtime(&start);
1100 switch(baseint){
1101 case TMT_SECOND:
1102 tm.tm_sec -= tm.tm_sec % basestep; break;
1103 case TMT_MINUTE:
1104 tm.tm_sec=0;
1105 tm.tm_min -= tm.tm_min % basestep;
1106 break;
1107 case TMT_HOUR:
1108 tm.tm_sec=0;
1109 tm.tm_min = 0;
1110 tm.tm_hour -= tm.tm_hour % basestep; break;
1111 case TMT_DAY:
1112 /* we do NOT look at the basestep for this ... */
1113 tm.tm_sec=0;
1114 tm.tm_min = 0;
1115 tm.tm_hour = 0; break;
1116 case TMT_WEEK:
1117 /* we do NOT look at the basestep for this ... */
1118 tm.tm_sec=0;
1119 tm.tm_min = 0;
1120 tm.tm_hour = 0;
1121 tm.tm_mday -= tm.tm_wday -1; /* -1 because we want the monday */
1122 if (tm.tm_wday==0) tm.tm_mday -= 7; /* we want the *previous* monday */
1123 break;
1124 case TMT_MONTH:
1125 tm.tm_sec=0;
1126 tm.tm_min = 0;
1127 tm.tm_hour = 0;
1128 tm.tm_mday = 1;
1129 tm.tm_mon -= tm.tm_mon % basestep; break;
1131 case TMT_YEAR:
1132 tm.tm_sec=0;
1133 tm.tm_min = 0;
1134 tm.tm_hour = 0;
1135 tm.tm_mday = 1;
1136 tm.tm_mon = 0;
1137 tm.tm_year -= (tm.tm_year+1900) % basestep;
1139 }
1140 return mktime(&tm);
1141 }
1142 /* identify the point where the next gridline, label ... gets placed */
1143 time_t
1144 find_next_time(
1145 time_t current, /* what is the initial time */
1146 enum tmt_en baseint, /* what is the basic interval */
1147 long basestep /* how many if these do we jump a time */
1148 )
1149 {
1150 struct tm tm;
1151 time_t madetime;
1152 tm = *localtime(¤t);
1153 do {
1154 switch(baseint){
1155 case TMT_SECOND:
1156 tm.tm_sec += basestep; break;
1157 case TMT_MINUTE:
1158 tm.tm_min += basestep; break;
1159 case TMT_HOUR:
1160 tm.tm_hour += basestep; break;
1161 case TMT_DAY:
1162 tm.tm_mday += basestep; break;
1163 case TMT_WEEK:
1164 tm.tm_mday += 7*basestep; break;
1165 case TMT_MONTH:
1166 tm.tm_mon += basestep; break;
1167 case TMT_YEAR:
1168 tm.tm_year += basestep;
1169 }
1170 madetime = mktime(&tm);
1171 } while (madetime == -1); /* this is necessary to skip impssible times
1172 like the daylight saving time skips */
1173 return madetime;
1175 }
1178 /* calculate values required for PRINT and GPRINT functions */
1180 int
1181 print_calc(image_desc_t *im, char ***prdata)
1182 {
1183 long i,ii,validsteps;
1184 double printval;
1185 time_t printtime;
1186 int graphelement = 0;
1187 long vidx;
1188 int max_ii;
1189 double magfact = -1;
1190 char *si_symb = "";
1191 char *percent_s;
1192 int prlines = 1;
1193 if (im->imginfo) prlines++;
1194 for(i=0;i<im->gdes_c;i++){
1195 switch(im->gdes[i].gf){
1196 case GF_PRINT:
1197 prlines++;
1198 if(((*prdata) = rrd_realloc((*prdata),prlines*sizeof(char *)))==NULL){
1199 rrd_set_error("realloc prdata");
1200 return 0;
1201 }
1202 case GF_GPRINT:
1203 /* PRINT and GPRINT can now print VDEF generated values.
1204 * There's no need to do any calculations on them as these
1205 * calculations were already made.
1206 */
1207 vidx = im->gdes[i].vidx;
1208 if (im->gdes[vidx].gf==GF_VDEF) { /* simply use vals */
1209 printval = im->gdes[vidx].vf.val;
1210 printtime = im->gdes[vidx].vf.when;
1211 } else { /* need to calculate max,min,avg etcetera */
1212 max_ii =((im->gdes[vidx].end
1213 - im->gdes[vidx].start)
1214 / im->gdes[vidx].step
1215 * im->gdes[vidx].ds_cnt);
1216 printval = DNAN;
1217 validsteps = 0;
1218 for( ii=im->gdes[vidx].ds;
1219 ii < max_ii;
1220 ii+=im->gdes[vidx].ds_cnt){
1221 if (! finite(im->gdes[vidx].data[ii]))
1222 continue;
1223 if (isnan(printval)){
1224 printval = im->gdes[vidx].data[ii];
1225 validsteps++;
1226 continue;
1227 }
1229 switch (im->gdes[i].cf){
1230 case CF_HWPREDICT:
1231 case CF_DEVPREDICT:
1232 case CF_DEVSEASONAL:
1233 case CF_SEASONAL:
1234 case CF_AVERAGE:
1235 validsteps++;
1236 printval += im->gdes[vidx].data[ii];
1237 break;
1238 case CF_MINIMUM:
1239 printval = min( printval, im->gdes[vidx].data[ii]);
1240 break;
1241 case CF_FAILURES:
1242 case CF_MAXIMUM:
1243 printval = max( printval, im->gdes[vidx].data[ii]);
1244 break;
1245 case CF_LAST:
1246 printval = im->gdes[vidx].data[ii];
1247 }
1248 }
1249 if (im->gdes[i].cf==CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1250 if (validsteps > 1) {
1251 printval = (printval / validsteps);
1252 }
1253 }
1254 } /* prepare printval */
1256 if (!strcmp(im->gdes[i].format,"%c")) { /* VDEF time print */
1257 if (im->gdes[i].gf == GF_PRINT){
1258 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1259 sprintf((*prdata)[prlines-2],"%s (%lu)",
1260 ctime(&printtime),printtime);
1261 (*prdata)[prlines-1] = NULL;
1262 } else {
1263 sprintf(im->gdes[i].legend,"%s (%lu)",
1264 ctime(&printtime),printtime);
1265 graphelement = 1;
1266 }
1267 } else {
1268 if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) {
1269 /* Magfact is set to -1 upon entry to print_calc. If it
1270 * is still less than 0, then we need to run auto_scale.
1271 * Otherwise, put the value into the correct units. If
1272 * the value is 0, then do not set the symbol or magnification
1273 * so next the calculation will be performed again. */
1274 if (magfact < 0.0) {
1275 auto_scale(im,&printval,&si_symb,&magfact);
1276 if (printval == 0.0)
1277 magfact = -1.0;
1278 } else {
1279 printval /= magfact;
1280 }
1281 *(++percent_s) = 's';
1282 } else if (strstr(im->gdes[i].format,"%s") != NULL) {
1283 auto_scale(im,&printval,&si_symb,&magfact);
1284 }
1286 if (im->gdes[i].gf == GF_PRINT){
1287 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1288 (*prdata)[prlines-1] = NULL;
1289 if (bad_format(im->gdes[i].format)) {
1290 rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1291 return -1;
1292 }
1293 #ifdef HAVE_SNPRINTF
1294 snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
1295 #else
1296 sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
1297 #endif
1298 } else {
1299 /* GF_GPRINT */
1301 if (bad_format(im->gdes[i].format)) {
1302 rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1303 return -1;
1304 }
1305 #ifdef HAVE_SNPRINTF
1306 snprintf(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
1307 #else
1308 sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
1309 #endif
1310 graphelement = 1;
1311 }
1312 }
1313 break;
1314 case GF_LINE:
1315 case GF_AREA:
1316 case GF_TICK:
1317 case GF_STACK:
1318 case GF_HRULE:
1319 case GF_VRULE:
1320 graphelement = 1;
1321 break;
1322 case GF_COMMENT:
1323 case GF_DEF:
1324 case GF_CDEF:
1325 case GF_VDEF:
1326 case GF_PART:
1327 case GF_XPORT:
1328 break;
1329 }
1330 }
1331 return graphelement;
1332 }
1335 /* place legends with color spots */
1336 int
1337 leg_place(image_desc_t *im)
1338 {
1339 /* graph labels */
1340 int interleg = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1341 int box =im->text_prop[TEXT_PROP_LEGEND].size*1.5;
1342 int border = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1343 int fill=0, fill_last;
1344 int leg_c = 0;
1345 int leg_x = border, leg_y = im->yimg;
1346 int leg_cc;
1347 int glue = 0;
1348 int i,ii, mark = 0;
1349 char prt_fctn; /*special printfunctions */
1350 int *legspace;
1352 if( !(im->extra_flags & NOLEGEND) ) {
1353 if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1354 rrd_set_error("malloc for legspace");
1355 return -1;
1356 }
1358 for(i=0;i<im->gdes_c;i++){
1359 fill_last = fill;
1361 leg_cc = strlen(im->gdes[i].legend);
1363 /* is there a controle code ant the end of the legend string ? */
1364 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\') {
1365 prt_fctn = im->gdes[i].legend[leg_cc-1];
1366 leg_cc -= 2;
1367 im->gdes[i].legend[leg_cc] = '\0';
1368 } else {
1369 prt_fctn = '\0';
1370 }
1371 /* remove exess space */
1372 while (prt_fctn=='g' &&
1373 leg_cc > 0 &&
1374 im->gdes[i].legend[leg_cc-1]==' '){
1375 leg_cc--;
1376 im->gdes[i].legend[leg_cc]='\0';
1377 }
1378 if (leg_cc != 0 ){
1379 legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1381 if (fill > 0){
1382 /* no interleg space if string ends in \g */
1383 fill += legspace[i];
1384 }
1385 if (im->gdes[i].gf != GF_GPRINT &&
1386 im->gdes[i].gf != GF_COMMENT) {
1387 fill += box;
1388 }
1389 fill += gfx_get_text_width(im->canvas, fill+border,
1390 im->text_prop[TEXT_PROP_LEGEND].font,
1391 im->text_prop[TEXT_PROP_LEGEND].size,
1392 im->tabwidth,
1393 im->gdes[i].legend);
1394 leg_c++;
1395 } else {
1396 legspace[i]=0;
1397 }
1398 /* who said there was a special tag ... ?*/
1399 if (prt_fctn=='g') {
1400 prt_fctn = '\0';
1401 }
1402 if (prt_fctn == '\0') {
1403 if (i == im->gdes_c -1 ) prt_fctn ='l';
1405 /* is it time to place the legends ? */
1406 if (fill > im->ximg - 2*border){
1407 if (leg_c > 1) {
1408 /* go back one */
1409 i--;
1410 fill = fill_last;
1411 leg_c--;
1412 prt_fctn = 'j';
1413 } else {
1414 prt_fctn = 'l';
1415 }
1417 }
1418 }
1421 if (prt_fctn != '\0'){
1422 leg_x = border;
1423 if (leg_c >= 2 && prt_fctn == 'j') {
1424 glue = (im->ximg - fill - 2* border) / (leg_c-1);
1425 } else {
1426 glue = 0;
1427 }
1428 if (prt_fctn =='c') leg_x = (im->ximg - fill) / 2.0;
1429 if (prt_fctn =='r') leg_x = im->ximg - fill - border;
1431 for(ii=mark;ii<=i;ii++){
1432 if(im->gdes[ii].legend[0]=='\0')
1433 continue;
1434 im->gdes[ii].leg_x = leg_x;
1435 im->gdes[ii].leg_y = leg_y;
1436 leg_x +=
1437 gfx_get_text_width(im->canvas, leg_x,
1438 im->text_prop[TEXT_PROP_LEGEND].font,
1439 im->text_prop[TEXT_PROP_LEGEND].size,
1440 im->tabwidth,
1441 im->gdes[ii].legend)
1442 + legspace[ii]
1443 + glue;
1444 if (im->gdes[ii].gf != GF_GPRINT &&
1445 im->gdes[ii].gf != GF_COMMENT)
1446 leg_x += box;
1447 }
1448 leg_y = leg_y + im->text_prop[TEXT_PROP_LEGEND].size*1.2;
1449 if (prt_fctn == 's') leg_y -= im->text_prop[TEXT_PROP_LEGEND].size*1.2;
1450 fill = 0;
1451 leg_c = 0;
1452 mark = ii;
1453 }
1454 }
1455 im->yimg = leg_y;
1456 free(legspace);
1457 }
1458 return 0;
1459 }
1461 /* create a grid on the graph. it determines what to do
1462 from the values of xsize, start and end */
1464 /* the xaxis labels are determined from the number of seconds per pixel
1465 in the requested graph */
1469 int
1470 calc_horizontal_grid(image_desc_t *im)
1471 {
1472 double range;
1473 double scaledrange;
1474 int pixel,i;
1475 int gridind;
1476 int decimals, fractionals;
1478 im->ygrid_scale.labfact=2;
1479 gridind=-1;
1480 range = im->maxval - im->minval;
1481 scaledrange = range / im->magfact;
1483 /* does the scale of this graph make it impossible to put lines
1484 on it? If so, give up. */
1485 if (isnan(scaledrange)) {
1486 return 0;
1487 }
1489 /* find grid spaceing */
1490 pixel=1;
1491 if(isnan(im->ygridstep)){
1492 if(im->extra_flags & ALTYGRID) {
1493 /* find the value with max number of digits. Get number of digits */
1494 decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))));
1495 if(decimals <= 0) /* everything is small. make place for zero */
1496 decimals = 1;
1498 fractionals = floor(log10(range));
1499 if(fractionals < 0) /* small amplitude. */
1500 sprintf(im->ygrid_scale.labfmt, "%%%d.%df", decimals - fractionals + 1, -fractionals + 1);
1501 else
1502 sprintf(im->ygrid_scale.labfmt, "%%%d.1f", decimals + 1);
1503 im->ygrid_scale.gridstep = pow((double)10, (double)fractionals);
1504 if(im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1505 im->ygrid_scale.gridstep = 0.1;
1506 /* should have at least 5 lines but no more then 15 */
1507 if(range/im->ygrid_scale.gridstep < 5)
1508 im->ygrid_scale.gridstep /= 10;
1509 if(range/im->ygrid_scale.gridstep > 15)
1510 im->ygrid_scale.gridstep *= 10;
1511 if(range/im->ygrid_scale.gridstep > 5) {
1512 im->ygrid_scale.labfact = 1;
1513 if(range/im->ygrid_scale.gridstep > 8)
1514 im->ygrid_scale.labfact = 2;
1515 }
1516 else {
1517 im->ygrid_scale.gridstep /= 5;
1518 im->ygrid_scale.labfact = 5;
1519 }
1520 }
1521 else {
1522 for(i=0;ylab[i].grid > 0;i++){
1523 pixel = im->ysize / (scaledrange / ylab[i].grid);
1524 if (gridind == -1 && pixel > 5) {
1525 gridind = i;
1526 break;
1527 }
1528 }
1530 for(i=0; i<4;i++) {
1531 if (pixel * ylab[gridind].lfac[i] >= 2 * im->text_prop[TEXT_PROP_AXIS].size) {
1532 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1533 break;
1534 }
1535 }
1537 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1538 }
1539 } else {
1540 im->ygrid_scale.gridstep = im->ygridstep;
1541 im->ygrid_scale.labfact = im->ylabfact;
1542 }
1543 return 1;
1544 }
1546 int draw_horizontal_grid(image_desc_t *im)
1547 {
1548 int i;
1549 double scaledstep;
1550 char graph_label[100];
1551 double X0=im->xorigin;
1552 double X1=im->xorigin+im->xsize;
1554 int sgrid = (int)( im->minval / im->ygrid_scale.gridstep - 1);
1555 int egrid = (int)( im->maxval / im->ygrid_scale.gridstep + 1);
1556 scaledstep = im->ygrid_scale.gridstep/im->magfact;
1557 for (i = sgrid; i <= egrid; i++){
1558 double Y0=ytr(im,im->ygrid_scale.gridstep*i);
1559 if ( Y0 >= im->yorigin-im->ysize
1560 && Y0 <= im->yorigin){
1561 if(i % im->ygrid_scale.labfact == 0){
1562 if (i==0 || im->symbol == ' ') {
1563 if(scaledstep < 1){
1564 if(im->extra_flags & ALTYGRID) {
1565 sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*i);
1566 }
1567 else {
1568 sprintf(graph_label,"%4.1f",scaledstep*i);
1569 }
1570 } else {
1571 sprintf(graph_label,"%4.0f",scaledstep*i);
1572 }
1573 }else {
1574 if(scaledstep < 1){
1575 sprintf(graph_label,"%4.1f %c",scaledstep*i, im->symbol);
1576 } else {
1577 sprintf(graph_label,"%4.0f %c",scaledstep*i, im->symbol);
1578 }
1579 }
1581 gfx_new_text ( im->canvas,
1582 X0-im->text_prop[TEXT_PROP_AXIS].size/1.5, Y0,
1583 im->graph_col[GRC_FONT],
1584 im->text_prop[TEXT_PROP_AXIS].font,
1585 im->text_prop[TEXT_PROP_AXIS].size,
1586 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1587 graph_label );
1588 gfx_new_dashed_line ( im->canvas,
1589 X0-2,Y0,
1590 X1+2,Y0,
1591 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1592 im->grid_dash_on, im->grid_dash_off);
1594 } else if (!(im->extra_flags & NOMINOR)) {
1595 gfx_new_dashed_line ( im->canvas,
1596 X0-1,Y0,
1597 X1+1,Y0,
1598 GRIDWIDTH, im->graph_col[GRC_GRID],
1599 im->grid_dash_on, im->grid_dash_off);
1601 }
1602 }
1603 }
1604 return 1;
1605 }
1607 /* logaritmic horizontal grid */
1608 int
1609 horizontal_log_grid(image_desc_t *im)
1610 {
1611 double pixpex;
1612 int ii,i;
1613 int minoridx=0, majoridx=0;
1614 char graph_label[100];
1615 double X0,X1,Y0;
1616 double value, pixperstep, minstep;
1618 /* find grid spaceing */
1619 pixpex= (double)im->ysize / (log10(im->maxval) - log10(im->minval));
1621 if (isnan(pixpex)) {
1622 return 0;
1623 }
1625 for(i=0;yloglab[i][0] > 0;i++){
1626 minstep = log10(yloglab[i][0]);
1627 for(ii=1;yloglab[i][ii+1] > 0;ii++){
1628 if(yloglab[i][ii+2]==0){
1629 minstep = log10(yloglab[i][ii+1])-log10(yloglab[i][ii]);
1630 break;
1631 }
1632 }
1633 pixperstep = pixpex * minstep;
1634 if(pixperstep > 5){minoridx = i;}
1635 if(pixperstep > 2 * im->text_prop[TEXT_PROP_LEGEND].size){majoridx = i;}
1636 }
1638 X0=im->xorigin;
1639 X1=im->xorigin+im->xsize;
1640 /* paint minor grid */
1641 for (value = pow((double)10, log10(im->minval)
1642 - fmod(log10(im->minval),log10(yloglab[minoridx][0])));
1643 value <= im->maxval;
1644 value *= yloglab[minoridx][0]){
1645 if (value < im->minval) continue;
1646 i=0;
1647 while(yloglab[minoridx][++i] > 0){
1648 Y0 = ytr(im,value * yloglab[minoridx][i]);
1649 if (Y0 <= im->yorigin - im->ysize) break;
1650 gfx_new_dashed_line ( im->canvas,
1651 X0-1,Y0,
1652 X1+1,Y0,
1653 GRIDWIDTH, im->graph_col[GRC_GRID],
1654 im->grid_dash_on, im->grid_dash_off);
1655 }
1656 }
1658 /* paint major grid and labels*/
1659 for (value = pow((double)10, log10(im->minval)
1660 - fmod(log10(im->minval),log10(yloglab[majoridx][0])));
1661 value <= im->maxval;
1662 value *= yloglab[majoridx][0]){
1663 if (value < im->minval) continue;
1664 i=0;
1665 while(yloglab[majoridx][++i] > 0){
1666 Y0 = ytr(im,value * yloglab[majoridx][i]);
1667 if (Y0 <= im->yorigin - im->ysize) break;
1668 gfx_new_dashed_line ( im->canvas,
1669 X0-2,Y0,
1670 X1+2,Y0,
1671 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1672 im->grid_dash_on, im->grid_dash_off);
1674 sprintf(graph_label,"%3.0e",value * yloglab[majoridx][i]);
1675 gfx_new_text ( im->canvas,
1676 X0-im->text_prop[TEXT_PROP_AXIS].size/1.5, Y0,
1677 im->graph_col[GRC_FONT],
1678 im->text_prop[TEXT_PROP_AXIS].font,
1679 im->text_prop[TEXT_PROP_AXIS].size,
1680 im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1681 graph_label );
1682 }
1683 }
1684 return 1;
1685 }
1688 void
1689 vertical_grid(
1690 image_desc_t *im )
1691 {
1692 int xlab_sel; /* which sort of label and grid ? */
1693 time_t ti, tilab, timajor;
1694 long factor;
1695 char graph_label[100];
1696 double X0,Y0,Y1; /* points for filled graph and more*/
1699 /* the type of time grid is determined by finding
1700 the number of seconds per pixel in the graph */
1703 if(im->xlab_user.minsec == -1){
1704 factor=(im->end - im->start)/im->xsize;
1705 xlab_sel=0;
1706 while ( xlab[xlab_sel+1].minsec != -1
1707 && xlab[xlab_sel+1].minsec <= factor){ xlab_sel++; }
1708 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
1709 im->xlab_user.gridst = xlab[xlab_sel].gridst;
1710 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
1711 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
1712 im->xlab_user.labtm = xlab[xlab_sel].labtm;
1713 im->xlab_user.labst = xlab[xlab_sel].labst;
1714 im->xlab_user.precis = xlab[xlab_sel].precis;
1715 im->xlab_user.stst = xlab[xlab_sel].stst;
1716 }
1718 /* y coords are the same for every line ... */
1719 Y0 = im->yorigin;
1720 Y1 = im->yorigin-im->ysize;
1723 /* paint the minor grid */
1724 if (!(im->extra_flags & NOMINOR))
1725 {
1726 for(ti = find_first_time(im->start,
1727 im->xlab_user.gridtm,
1728 im->xlab_user.gridst),
1729 timajor = find_first_time(im->start,
1730 im->xlab_user.mgridtm,
1731 im->xlab_user.mgridst);
1732 ti < im->end;
1733 ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
1734 ){
1735 /* are we inside the graph ? */
1736 if (ti < im->start || ti > im->end) continue;
1737 while (timajor < ti) {
1738 timajor = find_next_time(timajor,
1739 im->xlab_user.mgridtm, im->xlab_user.mgridst);
1740 }
1741 if (ti == timajor) continue; /* skip as falls on major grid line */
1742 X0 = xtr(im,ti);
1743 gfx_new_dashed_line(im->canvas,X0,Y0+1, X0,Y1-1,GRIDWIDTH,
1744 im->graph_col[GRC_GRID],
1745 im->grid_dash_on, im->grid_dash_off);
1747 }
1748 }
1750 /* paint the major grid */
1751 for(ti = find_first_time(im->start,
1752 im->xlab_user.mgridtm,
1753 im->xlab_user.mgridst);
1754 ti < im->end;
1755 ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
1756 ){
1757 /* are we inside the graph ? */
1758 if (ti < im->start || ti > im->end) continue;
1759 X0 = xtr(im,ti);
1760 gfx_new_dashed_line(im->canvas,X0,Y0+3, X0,Y1-2,MGRIDWIDTH,
1761 im->graph_col[GRC_MGRID],
1762 im->grid_dash_on, im->grid_dash_off);
1764 }
1765 /* paint the labels below the graph */
1766 for(ti = find_first_time(im->start,
1767 im->xlab_user.labtm,
1768 im->xlab_user.labst);
1769 ti <= im->end;
1770 ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
1771 ){
1772 tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
1773 /* are we inside the graph ? */
1774 if (ti < im->start || ti > im->end) continue;
1776 #if HAVE_STRFTIME
1777 strftime(graph_label,99,im->xlab_user.stst,localtime(&tilab));
1778 #else
1779 # error "your libc has no strftime I guess we'll abort the exercise here."
1780 #endif
1781 gfx_new_text ( im->canvas,
1782 xtr(im,tilab), Y0+im->text_prop[TEXT_PROP_AXIS].size/1.5,
1783 im->graph_col[GRC_FONT],
1784 im->text_prop[TEXT_PROP_AXIS].font,
1785 im->text_prop[TEXT_PROP_AXIS].size,
1786 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP,
1787 graph_label );
1789 }
1791 }
1794 void
1795 axis_paint(
1796 image_desc_t *im
1797 )
1798 {
1799 /* draw x and y axis */
1800 gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
1801 im->xorigin+im->xsize,im->yorigin-im->ysize,
1802 GRIDWIDTH, im->graph_col[GRC_GRID]);
1804 gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
1805 im->xorigin+im->xsize,im->yorigin-im->ysize,
1806 GRIDWIDTH, im->graph_col[GRC_GRID]);
1808 gfx_new_line ( im->canvas, im->xorigin-4,im->yorigin,
1809 im->xorigin+im->xsize+4,im->yorigin,
1810 MGRIDWIDTH, im->graph_col[GRC_GRID]);
1812 gfx_new_line ( im->canvas, im->xorigin,im->yorigin+4,
1813 im->xorigin,im->yorigin-im->ysize-4,
1814 MGRIDWIDTH, im->graph_col[GRC_GRID]);
1817 /* arrow for X axis direction */
1818 gfx_new_area ( im->canvas,
1819 im->xorigin+im->xsize+3, im->yorigin-3,
1820 im->xorigin+im->xsize+3, im->yorigin+4,
1821 im->xorigin+im->xsize+8, im->yorigin+0.5, /* LINEOFFSET */
1822 im->graph_col[GRC_ARROW]);
1826 }
1828 void
1829 grid_paint(image_desc_t *im)
1830 {
1831 long i;
1832 int res=0;
1833 double X0,Y0; /* points for filled graph and more*/
1834 gfx_node_t *node;
1836 /* draw 3d border */
1837 node = gfx_new_area (im->canvas, 0,im->yimg,
1838 2,im->yimg-2,
1839 2,2,im->graph_col[GRC_SHADEA]);
1840 gfx_add_point( node , im->ximg - 2, 2 );
1841 gfx_add_point( node , im->ximg, 0 );
1842 gfx_add_point( node , 0,0 );
1843 /* gfx_add_point( node , 0,im->yimg ); */
1845 node = gfx_new_area (im->canvas, 2,im->yimg-2,
1846 im->ximg-2,im->yimg-2,
1847 im->ximg - 2, 2,
1848 im->graph_col[GRC_SHADEB]);
1849 gfx_add_point( node , im->ximg,0);
1850 gfx_add_point( node , im->ximg,im->yimg);
1851 gfx_add_point( node , 0,im->yimg);
1852 /* gfx_add_point( node , 0,im->yimg ); */
1855 if (im->draw_x_grid == 1 )
1856 vertical_grid(im);
1858 if (im->draw_y_grid == 1){
1859 if(im->logarithmic){
1860 res = horizontal_log_grid(im);
1861 } else {
1862 res = draw_horizontal_grid(im);
1863 }
1865 /* dont draw horizontal grid if there is no min and max val */
1866 if (! res ) {
1867 char *nodata = "No Data found";
1868 gfx_new_text(im->canvas,im->ximg/2, (2*im->yorigin-im->ysize) / 2,
1869 im->graph_col[GRC_FONT],
1870 im->text_prop[TEXT_PROP_AXIS].font,
1871 im->text_prop[TEXT_PROP_AXIS].size,
1872 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
1873 nodata );
1874 }
1875 }
1877 /* yaxis description */
1878 if (im->canvas->imgformat != IF_PNG) {
1879 gfx_new_text( im->canvas,
1880 7, (im->yorigin - im->ysize/2),
1881 im->graph_col[GRC_FONT],
1882 im->text_prop[TEXT_PROP_AXIS].font,
1883 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 270.0,
1884 GFX_H_CENTER, GFX_V_CENTER,
1885 im->ylegend);
1886 } else {
1887 /* horrible hack until we can actually print vertically */
1888 {
1889 int n;
1890 int l=strlen(im->ylegend);
1891 char s[2];
1892 for (n=0;n<strlen(im->ylegend);n++) {
1893 s[0]=im->ylegend[n];
1894 s[1]='\0';
1895 gfx_new_text(im->canvas,7,im->text_prop[TEXT_PROP_AXIS].size*(l-n),
1896 im->graph_col[GRC_FONT],
1897 im->text_prop[TEXT_PROP_AXIS].font,
1898 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 270.0,
1899 GFX_H_CENTER, GFX_V_CENTER,
1900 s);
1901 }
1902 }
1903 }
1905 /* graph title */
1906 gfx_new_text( im->canvas,
1907 im->ximg/2, im->text_prop[TEXT_PROP_TITLE].size,
1908 im->graph_col[GRC_FONT],
1909 im->text_prop[TEXT_PROP_TITLE].font,
1910 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
1911 GFX_H_CENTER, GFX_V_CENTER,
1912 im->title);
1914 /* graph labels */
1915 if( !(im->extra_flags & NOLEGEND) ) {
1916 for(i=0;i<im->gdes_c;i++){
1917 if(im->gdes[i].legend[0] =='\0')
1918 continue;
1920 /* im->gdes[i].leg_y is the bottom of the legend */
1921 X0 = im->gdes[i].leg_x;
1922 Y0 = im->gdes[i].leg_y;
1923 /* Box needed? */
1924 if ( im->gdes[i].gf != GF_GPRINT
1925 && im->gdes[i].gf != GF_COMMENT) {
1926 int boxH, boxV;
1928 boxH = gfx_get_text_width(im->canvas, 0,
1929 im->text_prop[TEXT_PROP_AXIS].font,
1930 im->text_prop[TEXT_PROP_AXIS].size,
1931 im->tabwidth,"M") * 1.25;
1932 boxV = boxH;
1934 node = gfx_new_area(im->canvas,
1935 X0,Y0-boxV,
1936 X0,Y0,
1937 X0+boxH,Y0,
1938 im->gdes[i].col);
1939 gfx_add_point ( node, X0+boxH, Y0-boxV );
1940 node = gfx_new_line(im->canvas,
1941 X0,Y0-boxV, X0,Y0,
1942 1,0x000000FF);
1943 gfx_add_point(node,X0+boxH,Y0);
1944 gfx_add_point(node,X0+boxH,Y0-boxV);
1945 gfx_close_path(node);
1946 X0 += boxH / 1.25 * 2;
1947 }
1948 gfx_new_text ( im->canvas, X0, Y0,
1949 im->graph_col[GRC_FONT],
1950 im->text_prop[TEXT_PROP_AXIS].font,
1951 im->text_prop[TEXT_PROP_AXIS].size,
1952 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
1953 im->gdes[i].legend );
1954 }
1955 }
1956 }
1959 /*****************************************************
1960 * lazy check make sure we rely need to create this graph
1961 *****************************************************/
1963 int lazy_check(image_desc_t *im){
1964 FILE *fd = NULL;
1965 int size = 1;
1966 struct stat imgstat;
1968 if (im->lazy == 0) return 0; /* no lazy option */
1969 if (stat(im->graphfile,&imgstat) != 0)
1970 return 0; /* can't stat */
1971 /* one pixel in the existing graph is more then what we would
1972 change here ... */
1973 if (time(NULL) - imgstat.st_mtime >
1974 (im->end - im->start) / im->xsize)
1975 return 0;
1976 if ((fd = fopen(im->graphfile,"rb")) == NULL)
1977 return 0; /* the file does not exist */
1978 switch (im->canvas->imgformat) {
1979 case IF_PNG:
1980 size = PngSize(fd,&(im->ximg),&(im->yimg));
1981 break;
1982 default:
1983 size = 1;
1984 }
1985 fclose(fd);
1986 return size;
1987 }
1989 void
1990 pie_part(image_desc_t *im, gfx_color_t color,
1991 double PieCenterX, double PieCenterY, double Radius,
1992 double startangle, double endangle)
1993 {
1994 gfx_node_t *node;
1995 double angle;
1996 double step=M_PI/50; /* Number of iterations for the circle;
1997 ** 10 is definitely too low, more than
1998 ** 50 seems to be overkill
1999 */
2001 /* Strange but true: we have to work clockwise or else
2002 ** anti aliasing nor transparency don't work.
2003 **
2004 ** This test is here to make sure we do it right, also
2005 ** this makes the for...next loop more easy to implement.
2006 ** The return will occur if the user enters a negative number
2007 ** (which shouldn't be done according to the specs) or if the
2008 ** programmers do something wrong (which, as we all know, never
2009 ** happens anyway :)
2010 */
2011 if (endangle<startangle) return;
2013 /* Hidden feature: Radius decreases each full circle */
2014 angle=startangle;
2015 while (angle>=2*M_PI) {
2016 angle -= 2*M_PI;
2017 Radius *= 0.8;
2018 }
2020 node=gfx_new_area(im->canvas,
2021 PieCenterX+sin(startangle)*Radius,
2022 PieCenterY-cos(startangle)*Radius,
2023 PieCenterX,
2024 PieCenterY,
2025 PieCenterX+sin(endangle)*Radius,
2026 PieCenterY-cos(endangle)*Radius,
2027 color);
2028 for (angle=endangle;angle-startangle>=step;angle-=step) {
2029 gfx_add_point(node,
2030 PieCenterX+sin(angle)*Radius,
2031 PieCenterY-cos(angle)*Radius );
2032 }
2033 }
2035 int
2036 graph_size_location(image_desc_t *im, int elements, int piechart )
2037 {
2038 /* The actual size of the image to draw is determined from
2039 ** several sources. The size given on the command line is
2040 ** the graph area but we need more as we have to draw labels
2041 ** and other things outside the graph area
2042 */
2044 /* +-+-------------------------------------------+
2045 ** |l|.................title.....................|
2046 ** |e+--+-------------------------------+--------+
2047 ** |b| b| | |
2048 ** |a| a| | pie |
2049 ** |l| l| main graph area | chart |
2050 ** |.| .| | area |
2051 ** |t| y| | |
2052 ** |r+--+-------------------------------+--------+
2053 ** |e| | x-axis labels | |
2054 ** |v+--+-------------------------------+--------+
2055 ** | |..............legends......................|
2056 ** +-+-------------------------------------------+
2057 */
2058 int Xvertical=0, Yvertical=0,
2059 Xtitle =0, Ytitle =0,
2060 Xylabel =0, Yylabel =0,
2061 Xmain =0, Ymain =0,
2062 Xpie =0, Ypie =0,
2063 Xxlabel =0, Yxlabel =0,
2064 #if 0
2065 Xlegend =0, Ylegend =0,
2066 #endif
2067 Xspacing =10, Yspacing =10;
2069 if (im->ylegend[0] != '\0') {
2070 Xvertical = im->text_prop[TEXT_PROP_LEGEND].size *2;
2071 Yvertical = im->text_prop[TEXT_PROP_LEGEND].size * (strlen(im->ylegend)+1);
2072 }
2074 if (im->title[0] != '\0') {
2075 /* The title is placed "inbetween" two text lines so it
2076 ** automatically has some vertical spacing. The horizontal
2077 ** spacing is added here, on each side.
2078 */
2079 Xtitle = gfx_get_text_width(im->canvas, 0,
2080 im->text_prop[TEXT_PROP_TITLE].font,
2081 im->text_prop[TEXT_PROP_TITLE].size,
2082 im->tabwidth,
2083 im->title) + 2*Xspacing;
2084 Ytitle = im->text_prop[TEXT_PROP_TITLE].size*2;
2085 }
2087 if (elements) {
2088 Xmain=im->xsize;
2089 Ymain=im->ysize;
2090 if (im->draw_x_grid) {
2091 Xxlabel=Xmain;
2092 Yxlabel=im->text_prop[TEXT_PROP_LEGEND].size *2;
2093 }
2094 if (im->draw_y_grid) {
2095 Xylabel=im->text_prop[TEXT_PROP_LEGEND].size *6;
2096 Yylabel=Ymain;
2097 }
2098 }
2100 if (piechart) {
2101 im->piesize=im->xsize<im->ysize?im->xsize:im->ysize;
2102 Xpie=im->piesize;
2103 Ypie=im->piesize;
2104 }
2106 /* Now calculate the total size. Insert some spacing where
2107 desired. im->xorigin and im->yorigin need to correspond
2108 with the lower left corner of the main graph area or, if
2109 this one is not set, the imaginary box surrounding the
2110 pie chart area. */
2112 /* The legend width cannot yet be determined, as a result we
2113 ** have problems adjusting the image to it. For now, we just
2114 ** forget about it at all; the legend will have to fit in the
2115 ** size already allocated.
2116 */
2117 im->ximg = Xylabel + Xmain + Xpie + Xspacing;
2118 if (Xmain) im->ximg += Xspacing;
2119 if (Xpie) im->ximg += Xspacing;
2120 im->xorigin = Xspacing + Xylabel;
2121 if (Xtitle > im->ximg) im->ximg = Xtitle;
2122 if (Xvertical) {
2123 im->ximg += Xvertical;
2124 im->xorigin += Xvertical;
2125 }
2126 xtr(im,0);
2128 /* The vertical size is interesting... we need to compare
2129 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend} with Yvertical
2130 ** however we need to know {Ytitle+Ymain+Yxlabel} in order to
2131 ** start even thinking about Ylegend.
2132 **
2133 ** Do it in three portions: First calculate the inner part,
2134 ** then do the legend, then adjust the total height of the img.
2135 */
2137 /* reserve space for main and/or pie */
2138 im->yimg = Ymain + Yxlabel;
2139 if (im->yimg < Ypie) im->yimg = Ypie;
2140 im->yorigin = im->yimg - Yxlabel;
2141 /* reserve space for the title *or* some padding above the graph */
2142 if (Ytitle) {
2143 im->yimg += Ytitle;
2144 im->yorigin += Ytitle;
2145 } else {
2146 im->yimg += Yspacing;
2147 im->yorigin += Yspacing;
2148 }
2149 /* reserve space for padding below the graph */
2150 im->yimg += Yspacing;
2151 ytr(im,DNAN);
2153 /* Determine where to place the legends onto the image.
2154 ** Adjust im->yimg to match the space requirements.
2155 */
2156 if(leg_place(im)==-1)
2157 return -1;
2159 /* last of three steps: check total height of image */
2160 if (im->yimg < Yvertical) im->yimg = Yvertical;
2162 #if 0
2163 if (Xlegend > im->ximg) {
2164 im->ximg = Xlegend;
2165 /* reposition Pie */
2166 }
2167 #endif
2169 /* The pie is placed in the upper right hand corner,
2170 ** just below the title (if any) and with sufficient
2171 ** padding.
2172 */
2173 if (elements) {
2174 im->pie_x = im->ximg - Xspacing - Xpie/2;
2175 im->pie_y = im->yorigin-Ymain+Ypie/2;
2176 } else {
2177 im->pie_x = im->ximg/2;
2178 im->pie_y = im->yorigin-Ypie/2;
2179 }
2181 return 0;
2182 }
2184 /* draw that picture thing ... */
2185 int
2186 graph_paint(image_desc_t *im, char ***calcpr)
2187 {
2188 int i,ii;
2189 int lazy = lazy_check(im);
2190 int piechart = 0;
2191 double PieStart=0.0;
2192 FILE *fo;
2193 gfx_node_t *node;
2195 double areazero = 0.0;
2196 enum gf_en stack_gf = GF_PRINT;
2197 graph_desc_t *lastgdes = NULL;
2199 /* if we are lazy and there is nothing to PRINT ... quit now */
2200 if (lazy && im->prt_c==0) return 0;
2202 /* pull the data from the rrd files ... */
2204 if(data_fetch(im)==-1)
2205 return -1;
2207 /* evaluate VDEF and CDEF operations ... */
2208 if(data_calc(im)==-1)
2209 return -1;
2211 /* check if we need to draw a piechart */
2212 for(i=0;i<im->gdes_c;i++){
2213 if (im->gdes[i].gf == GF_PART) {
2214 piechart=1;
2215 break;
2216 }
2217 }
2219 /* calculate and PRINT and GPRINT definitions. We have to do it at
2220 * this point because it will affect the length of the legends
2221 * if there are no graph elements we stop here ...
2222 * if we are lazy, try to quit ...
2223 */
2224 i=print_calc(im,calcpr);
2225 if(i<0) return -1;
2226 if(((i==0)&&(piechart==0)) || lazy) return 0;
2228 /* If there's only the pie chart to draw, signal this */
2229 if (i==0) piechart=2;
2231 /* get actual drawing data and find min and max values*/
2232 if(data_proc(im)==-1)
2233 return -1;
2235 if(!im->logarithmic){si_unit(im);} /* identify si magnitude Kilo, Mega Giga ? */
2237 if(!im->rigid && ! im->logarithmic)
2238 expand_range(im); /* make sure the upper and lower limit are
2239 sensible values */
2241 if (!calc_horizontal_grid(im))
2242 return -1;
2243 if (im->gridfit)
2244 apply_gridfit(im);
2246 /**************************************************************
2247 *** Calculating sizes and locations became a bit confusing ***
2248 *** so I moved this into a separate function. ***
2249 **************************************************************/
2250 if(graph_size_location(im,i,piechart)==-1)
2251 return -1;
2253 /* the actual graph is created by going through the individual
2254 graph elements and then drawing them */
2256 node=gfx_new_area ( im->canvas,
2257 0, 0,
2258 im->ximg, 0,
2259 im->ximg, im->yimg,
2260 im->graph_col[GRC_BACK]);
2262 gfx_add_point(node,0, im->yimg);
2264 if (piechart != 2) {
2265 node=gfx_new_area ( im->canvas,
2266 im->xorigin, im->yorigin,
2267 im->xorigin + im->xsize, im->yorigin,
2268 im->xorigin + im->xsize, im->yorigin-im->ysize,
2269 im->graph_col[GRC_CANVAS]);
2271 gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
2273 if (im->minval > 0.0)
2274 areazero = im->minval;
2275 if (im->maxval < 0.0)
2276 areazero = im->maxval;
2278 axis_paint(im);
2279 }
2281 if (piechart) {
2282 pie_part(im,im->graph_col[GRC_CANVAS],im->pie_x,im->pie_y,im->piesize*0.5,0,2*M_PI);
2283 }
2285 for(i=0;i<im->gdes_c;i++){
2286 switch(im->gdes[i].gf){
2287 case GF_CDEF:
2288 case GF_VDEF:
2289 case GF_DEF:
2290 case GF_PRINT:
2291 case GF_GPRINT:
2292 case GF_COMMENT:
2293 case GF_HRULE:
2294 case GF_VRULE:
2295 case GF_XPORT:
2296 break;
2297 case GF_TICK:
2298 for (ii = 0; ii < im->xsize; ii++)
2299 {
2300 if (!isnan(im->gdes[i].p_data[ii]) &&
2301 im->gdes[i].p_data[ii] > 0.0)
2302 {
2303 /* generate a tick */
2304 gfx_new_line(im->canvas, im -> xorigin + ii,
2305 im -> yorigin - (im -> gdes[i].yrule * im -> ysize),
2306 im -> xorigin + ii,
2307 im -> yorigin,
2308 1.0,
2309 im -> gdes[i].col );
2310 }
2311 }
2312 break;
2313 case GF_LINE:
2314 case GF_AREA:
2315 stack_gf = im->gdes[i].gf;
2316 case GF_STACK:
2317 /* fix data points at oo and -oo */
2318 for(ii=0;ii<im->xsize;ii++){
2319 if (isinf(im->gdes[i].p_data[ii])){
2320 if (im->gdes[i].p_data[ii] > 0) {
2321 im->gdes[i].p_data[ii] = im->maxval ;
2322 } else {
2323 im->gdes[i].p_data[ii] = im->minval ;
2324 }
2326 }
2327 } /* for */
2329 if (im->gdes[i].col != 0x0){
2330 /* GF_LINE and friend */
2331 if(stack_gf == GF_LINE ){
2332 node = NULL;
2333 for(ii=1;ii<im->xsize;ii++){
2334 if ( ! isnan(im->gdes[i].p_data[ii-1])
2335 && ! isnan(im->gdes[i].p_data[ii])){
2336 if (node == NULL){
2337 node = gfx_new_line(im->canvas,
2338 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2339 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2340 im->gdes[i].linewidth,
2341 im->gdes[i].col);
2342 } else {
2343 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2344 }
2345 } else {
2346 node = NULL;
2347 }
2348 }
2349 } else {
2350 int area_start=-1;
2351 node = NULL;
2352 for(ii=1;ii<im->xsize;ii++){
2353 /* open an area */
2354 if ( ! isnan(im->gdes[i].p_data[ii-1])
2355 && ! isnan(im->gdes[i].p_data[ii])){
2356 if (node == NULL){
2357 float ybase = 0.0;
2358 /*
2359 if (im->gdes[i].gf == GF_STACK) {
2360 */
2361 if ( (im->gdes[i].gf == GF_STACK)
2362 || (im->gdes[i].stack) ) {
2364 ybase = ytr(im,lastgdes->p_data[ii-1]);
2365 } else {
2366 ybase = ytr(im,areazero);
2367 }
2368 area_start = ii-1;
2369 node = gfx_new_area(im->canvas,
2370 ii-1+im->xorigin,ybase,
2371 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2372 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2373 im->gdes[i].col
2374 );
2375 } else {
2376 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2377 }
2378 }
2380 if ( node != NULL && (ii+1==im->xsize || isnan(im->gdes[i].p_data[ii]) )){
2381 /* GF_AREA STACK type*/
2382 /*
2383 if (im->gdes[i].gf == GF_STACK ) {
2384 */
2385 if ( (im->gdes[i].gf == GF_STACK)
2386 || (im->gdes[i].stack) ) {
2387 int iii;
2388 for (iii=ii-1;iii>area_start;iii--){
2389 gfx_add_point(node,iii+im->xorigin,ytr(im,lastgdes->p_data[iii]));
2390 }
2391 } else {
2392 gfx_add_point(node,ii+im->xorigin,ytr(im,areazero));
2393 };
2394 node=NULL;
2395 };
2396 }
2397 } /* else GF_LINE */
2398 } /* if color != 0x0 */
2399 /* make sure we do not run into trouble when stacking on NaN */
2400 for(ii=0;ii<im->xsize;ii++){
2401 if (isnan(im->gdes[i].p_data[ii])) {
2402 if (lastgdes) {
2403 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
2404 } else {
2405 im->gdes[i].p_data[ii] = ytr(im,areazero);
2406 }
2407 }
2408 }
2409 lastgdes = &(im->gdes[i]);
2410 break;
2411 case GF_PART:
2412 if(isnan(im->gdes[i].yrule)) /* fetch variable */
2413 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2415 if (finite(im->gdes[i].yrule)) { /* even the fetched var can be NaN */
2416 pie_part(im,im->gdes[i].col,
2417 im->pie_x,im->pie_y,im->piesize*0.4,
2418 M_PI*2.0*PieStart/100.0,
2419 M_PI*2.0*(PieStart+im->gdes[i].yrule)/100.0);
2420 PieStart += im->gdes[i].yrule;
2421 }
2422 break;
2423 } /* switch */
2424 }
2425 if (piechart==2) {
2426 im->draw_x_grid=0;
2427 im->draw_y_grid=0;
2428 }
2429 /* grid_paint also does the text */
2430 grid_paint(im);
2432 /* the RULES are the last thing to paint ... */
2433 for(i=0;i<im->gdes_c;i++){
2435 switch(im->gdes[i].gf){
2436 case GF_HRULE:
2437 if(isnan(im->gdes[i].yrule)) { /* fetch variable */
2438 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2439 };
2440 if(im->gdes[i].yrule >= im->minval
2441 && im->gdes[i].yrule <= im->maxval)
2442 gfx_new_line(im->canvas,
2443 im->xorigin,ytr(im,im->gdes[i].yrule),
2444 im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2445 1.0,im->gdes[i].col);
2446 break;
2447 case GF_VRULE:
2448 if(im->gdes[i].xrule == 0) { /* fetch variable */
2449 im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
2450 };
2451 if(im->gdes[i].xrule >= im->start
2452 && im->gdes[i].xrule <= im->end)
2453 gfx_new_line(im->canvas,
2454 xtr(im,im->gdes[i].xrule),im->yorigin,
2455 xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2456 1.0,im->gdes[i].col);
2457 break;
2458 default:
2459 break;
2460 }
2461 }
2464 if (strcmp(im->graphfile,"-")==0) {
2465 #ifdef WIN32
2466 /* Change translation mode for stdout to BINARY */
2467 _setmode( _fileno( stdout ), O_BINARY );
2468 #endif
2469 fo = stdout;
2470 } else {
2471 if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2472 rrd_set_error("Opening '%s' for write: %s",im->graphfile,
2473 strerror(errno));
2474 return (-1);
2475 }
2476 }
2477 gfx_render (im->canvas,im->ximg,im->yimg,0x0,fo);
2478 if (strcmp(im->graphfile,"-") != 0)
2479 fclose(fo);
2480 return 0;
2481 }
2484 /*****************************************************
2485 * graph stuff
2486 *****************************************************/
2488 int
2489 gdes_alloc(image_desc_t *im){
2491 long def_step = (im->end-im->start)/im->xsize;
2493 if (im->step > def_step) /* step can be increassed ... no decreassed */
2494 def_step = im->step;
2496 im->gdes_c++;
2498 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2499 * sizeof(graph_desc_t)))==NULL){
2500 rrd_set_error("realloc graph_descs");
2501 return -1;
2502 }
2505 im->gdes[im->gdes_c-1].step=def_step;
2506 im->gdes[im->gdes_c-1].stack=0;
2507 im->gdes[im->gdes_c-1].debug=0;
2508 im->gdes[im->gdes_c-1].start=im->start;
2509 im->gdes[im->gdes_c-1].end=im->end;
2510 im->gdes[im->gdes_c-1].vname[0]='\0';
2511 im->gdes[im->gdes_c-1].data=NULL;
2512 im->gdes[im->gdes_c-1].ds_namv=NULL;
2513 im->gdes[im->gdes_c-1].data_first=0;
2514 im->gdes[im->gdes_c-1].p_data=NULL;
2515 im->gdes[im->gdes_c-1].rpnp=NULL;
2516 im->gdes[im->gdes_c-1].col = 0x0;
2517 im->gdes[im->gdes_c-1].legend[0]='\0';
2518 im->gdes[im->gdes_c-1].rrd[0]='\0';
2519 im->gdes[im->gdes_c-1].ds=-1;
2520 im->gdes[im->gdes_c-1].p_data=NULL;
2521 im->gdes[im->gdes_c-1].yrule=DNAN;
2522 im->gdes[im->gdes_c-1].xrule=0;
2523 return 0;
2524 }
2526 /* copies input untill the first unescaped colon is found
2527 or until input ends. backslashes have to be escaped as well */
2528 int
2529 scan_for_col(char *input, int len, char *output)
2530 {
2531 int inp,outp=0;
2532 for (inp=0;
2533 inp < len &&
2534 input[inp] != ':' &&
2535 input[inp] != '\0';
2536 inp++){
2537 if (input[inp] == '\\' &&
2538 input[inp+1] != '\0' &&
2539 (input[inp+1] == '\\' ||
2540 input[inp+1] == ':')){
2541 output[outp++] = input[++inp];
2542 }
2543 else {
2544 output[outp++] = input[inp];
2545 }
2546 }
2547 output[outp] = '\0';
2548 return inp;
2549 }
2550 /* Some surgery done on this function, it became ridiculously big.
2551 ** Things moved:
2552 ** - initializing now in rrd_graph_init()
2553 ** - options parsing now in rrd_graph_options()
2554 ** - script parsing now in rrd_graph_script()
2555 */
2556 int
2557 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize)
2558 {
2559 image_desc_t im;
2561 rrd_graph_init(&im);
2563 rrd_graph_options(argc,argv,&im);
2564 if (rrd_test_error()) {
2565 im_free(&im);
2566 return -1;
2567 }
2569 if (strlen(argv[optind])>=MAXPATH) {
2570 rrd_set_error("filename (including path) too long");
2571 im_free(&im);
2572 return -1;
2573 }
2574 strncpy(im.graphfile,argv[optind],MAXPATH-1);
2575 im.graphfile[MAXPATH-1]='\0';
2577 rrd_graph_script(argc,argv,&im);
2578 if (rrd_test_error()) {
2579 im_free(&im);
2580 return -1;
2581 }
2583 /* Everything is now read and the actual work can start */
2585 (*prdata)=NULL;
2586 if (graph_paint(&im,prdata)==-1){
2587 im_free(&im);
2588 return -1;
2589 }
2591 /* The image is generated and needs to be output.
2592 ** Also, if needed, print a line with information about the image.
2593 */
2595 *xsize=im.ximg;
2596 *ysize=im.yimg;
2597 if (im.imginfo) {
2598 char *filename;
2599 if (!(*prdata)) {
2600 /* maybe prdata is not allocated yet ... lets do it now */
2601 if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
2602 rrd_set_error("malloc imginfo");
2603 return -1;
2604 };
2605 }
2606 if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
2607 ==NULL){
2608 rrd_set_error("malloc imginfo");
2609 return -1;
2610 }
2611 filename=im.graphfile+strlen(im.graphfile);
2612 while(filename > im.graphfile) {
2613 if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
2614 filename--;
2615 }
2617 sprintf((*prdata)[0],im.imginfo,filename,(long)(im.canvas->zoom*im.ximg),(long)(im.canvas->zoom*im.yimg));
2618 }
2619 im_free(&im);
2620 return 0;
2621 }
2623 void
2624 rrd_graph_init(image_desc_t *im)
2625 {
2626 int i;
2628 #ifdef HAVE_TZSET
2629 tzset();
2630 #endif
2631 #ifdef HAVE_SETLOCALE
2632 setlocale(LC_TIME,"");
2633 #endif
2635 im->xlab_user.minsec = -1;
2636 im->ximg=0;
2637 im->yimg=0;
2638 im->xsize = 400;
2639 im->ysize = 100;
2640 im->step = 0;
2641 im->ylegend[0] = '\0';
2642 im->title[0] = '\0';
2643 im->minval = DNAN;
2644 im->maxval = DNAN;
2645 im->unitsexponent= 9999;
2646 im->extra_flags= 0;
2647 im->rigid = 0;
2648 im->gridfit = 1;
2649 im->imginfo = NULL;
2650 im->lazy = 0;
2651 im->logarithmic = 0;
2652 im->ygridstep = DNAN;
2653 im->draw_x_grid = 1;
2654 im->draw_y_grid = 1;
2655 im->base = 1000;
2656 im->prt_c = 0;
2657 im->gdes_c = 0;
2658 im->gdes = NULL;
2659 im->canvas = gfx_new_canvas();
2660 im->grid_dash_on = 1;
2661 im->grid_dash_off = 1;
2663 for(i=0;i<DIM(graph_col);i++)
2664 im->graph_col[i]=graph_col[i];
2666 for(i=0;i<DIM(text_prop);i++){
2667 im->text_prop[i].size = text_prop[i].size;
2668 im->text_prop[i].font = text_prop[i].font;
2669 }
2670 }
2672 void
2673 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
2674 {
2675 int stroff;
2676 char *parsetime_error = NULL;
2677 char scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
2678 time_t start_tmp=0,end_tmp=0;
2679 long long_tmp;
2680 struct time_value start_tv, end_tv;
2681 gfx_color_t color;
2683 parsetime("end-24h", &start_tv);
2684 parsetime("now", &end_tv);
2686 while (1){
2687 static struct option long_options[] =
2688 {
2689 {"start", required_argument, 0, 's'},
2690 {"end", required_argument, 0, 'e'},
2691 {"x-grid", required_argument, 0, 'x'},
2692 {"y-grid", required_argument, 0, 'y'},
2693 {"vertical-label",required_argument,0,'v'},
2694 {"width", required_argument, 0, 'w'},
2695 {"height", required_argument, 0, 'h'},
2696 {"interlaced", no_argument, 0, 'i'},
2697 {"upper-limit",required_argument, 0, 'u'},
2698 {"lower-limit",required_argument, 0, 'l'},
2699 {"rigid", no_argument, 0, 'r'},
2700 {"base", required_argument, 0, 'b'},
2701 {"logarithmic",no_argument, 0, 'o'},
2702 {"color", required_argument, 0, 'c'},
2703 {"font", required_argument, 0, 'n'},
2704 {"title", required_argument, 0, 't'},
2705 {"imginfo", required_argument, 0, 'f'},
2706 {"imgformat", required_argument, 0, 'a'},
2707 {"lazy", no_argument, 0, 'z'},
2708 {"zoom", required_argument, 0, 'm'},
2709 {"no-legend", no_argument, 0, 'g'},
2710 {"alt-y-grid", no_argument, 0, 'Y'},
2711 {"no-minor", no_argument, 0, 'I'},
2712 {"alt-autoscale", no_argument, 0, 'A'},
2713 {"alt-autoscale-max", no_argument, 0, 'M'},
2714 {"units-exponent",required_argument, 0, 'X'},
2715 {"step", required_argument, 0, 'S'},
2716 {"no-gridfit", no_argument, 0, 'N'},
2717 {0,0,0,0}};
2718 int option_index = 0;
2719 int opt;
2722 opt = getopt_long(argc, argv,
2723 "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:I:zgYAMX:S:N",
2724 long_options, &option_index);
2726 if (opt == EOF)
2727 break;
2729 switch(opt) {
2730 case 'I':
2731 im->extra_flags |= NOMINOR;
2732 break;
2733 case 'Y':
2734 im->extra_flags |= ALTYGRID;
2735 break;
2736 case 'A':
2737 im->extra_flags |= ALTAUTOSCALE;
2738 break;
2739 case 'M':
2740 im->extra_flags |= ALTAUTOSCALE_MAX;
2741 break;
2742 case 'g':
2743 im->extra_flags |= NOLEGEND;
2744 break;
2745 case 'X':
2746 im->unitsexponent = atoi(optarg);
2747 break;
2748 case 'S':
2749 im->step = atoi(optarg);
2750 break;
2751 case 262:
2752 im->gridfit = 0;
2753 break;
2754 case 's':
2755 if ((parsetime_error = parsetime(optarg, &start_tv))) {
2756 rrd_set_error( "start time: %s", parsetime_error );
2757 return;
2758 }
2759 break;
2760 case 'e':
2761 if ((parsetime_error = parsetime(optarg, &end_tv))) {
2762 rrd_set_error( "end time: %s", parsetime_error );
2763 return;
2764 }
2765 break;
2766 case 'x':
2767 if(strcmp(optarg,"none") == 0){
2768 im->draw_x_grid=0;
2769 break;
2770 };
2772 if(sscanf(optarg,
2773 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
2774 scan_gtm,
2775 &im->xlab_user.gridst,
2776 scan_mtm,
2777 &im->xlab_user.mgridst,
2778 scan_ltm,
2779 &im->xlab_user.labst,
2780 &im->xlab_user.precis,
2781 &stroff) == 7 && stroff != 0){
2782 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
2783 if((im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
2784 rrd_set_error("unknown keyword %s",scan_gtm);
2785 return;
2786 } else if ((im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
2787 rrd_set_error("unknown keyword %s",scan_mtm);
2788 return;
2789 } else if ((im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
2790 rrd_set_error("unknown keyword %s",scan_ltm);
2791 return;
2792 }
2793 im->xlab_user.minsec = 1;
2794 im->xlab_user.stst = im->xlab_form;
2795 } else {
2796 rrd_set_error("invalid x-grid format");
2797 return;
2798 }
2799 break;
2800 case 'y':
2802 if(strcmp(optarg,"none") == 0){
2803 im->draw_y_grid=0;
2804 break;
2805 };
2807 if(sscanf(optarg,
2808 "%lf:%d",
2809 &im->ygridstep,
2810 &im->ylabfact) == 2) {
2811 if(im->ygridstep<=0){
2812 rrd_set_error("grid step must be > 0");
2813 return;
2814 } else if (im->ylabfact < 1){
2815 rrd_set_error("label factor must be > 0");
2816 return;
2817 }
2818 } else {
2819 rrd_set_error("invalid y-grid format");
2820 return;
2821 }
2822 break;
2823 case 'v':
2824 strncpy(im->ylegend,optarg,150);
2825 im->ylegend[150]='\0';
2826 break;
2827 case 'u':
2828 im->maxval = atof(optarg);
2829 break;
2830 case 'l':
2831 im->minval = atof(optarg);
2832 break;
2833 case 'b':
2834 im->base = atol(optarg);
2835 if(im->base != 1024 && im->base != 1000 ){
2836 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
2837 return;
2838 }
2839 break;
2840 case 'w':
2841 long_tmp = atol(optarg);
2842 if (long_tmp < 10) {
2843 rrd_set_error("width below 10 pixels");
2844 return;
2845 }
2846 im->xsize = long_tmp;
2847 break;
2848 case 'h':
2849 long_tmp = atol(optarg);
2850 if (long_tmp < 10) {
2851 rrd_set_error("height below 10 pixels");
2852 return;
2853 }
2854 im->ysize = long_tmp;
2855 break;
2856 case 'i':
2857 im->canvas->interlaced = 1;
2858 break;
2859 case 'r':
2860 im->rigid = 1;
2861 break;
2862 case 'f':
2863 im->imginfo = optarg;
2864 break;
2865 case 'a':
2866 if((im->canvas->imgformat = if_conv(optarg)) == -1) {
2867 rrd_set_error("unsupported graphics format '%s'",optarg);
2868 return;
2869 }
2870 break;
2871 case 'z':
2872 im->lazy = 1;
2873 break;
2874 case 'o':
2875 im->logarithmic = 1;
2876 if (isnan(im->minval))
2877 im->minval=1;
2878 break;
2879 case 'c':
2880 if(sscanf(optarg,
2881 "%10[A-Z]#%8lx",
2882 col_nam,&color) == 2){
2883 int ci;
2884 if((ci=grc_conv(col_nam)) != -1){
2885 im->graph_col[ci]=color;
2886 } else {
2887 rrd_set_error("invalid color name '%s'",col_nam);
2888 }
2889 } else {
2890 rrd_set_error("invalid color def format");
2891 return;
2892 }
2893 break;
2894 case 'n':{
2895 /* originally this used char *prop = "" and
2896 ** char *font = "dummy" however this results
2897 ** in a SEG fault, at least on RH7.1
2898 **
2899 ** The current implementation isn't proper
2900 ** either, font is never freed and prop uses
2901 ** a fixed width string
2902 */
2903 char prop[100];
2904 double size = 1;
2905 char *font;
2907 font=malloc(255);
2908 if(sscanf(optarg,
2909 "%10[A-Z]:%lf:%s",
2910 prop,&size,font) == 3){
2911 int sindex;
2912 if((sindex=text_prop_conv(prop)) != -1){
2913 im->text_prop[sindex].size=size;
2914 im->text_prop[sindex].font=font;
2915 if (sindex==0) { /* the default */
2916 im->text_prop[TEXT_PROP_TITLE].size=size;
2917 im->text_prop[TEXT_PROP_TITLE].font=font;
2918 im->text_prop[TEXT_PROP_AXIS].size=size;
2919 im->text_prop[TEXT_PROP_AXIS].font=font;
2920 im->text_prop[TEXT_PROP_UNIT].size=size;
2921 im->text_prop[TEXT_PROP_UNIT].font=font;
2922 im->text_prop[TEXT_PROP_LEGEND].size=size;
2923 im->text_prop[TEXT_PROP_LEGEND].font=font;
2924 }
2925 } else {
2926 rrd_set_error("invalid fonttag '%s'",prop);
2927 return;
2928 }
2929 } else {
2930 rrd_set_error("invalid text property format");
2931 return;
2932 }
2933 break;
2934 }
2935 case 'm':
2936 im->canvas->zoom = atof(optarg);
2937 if (im->canvas->zoom <= 0.0) {
2938 rrd_set_error("zoom factor must be > 0");
2939 return;
2940 }
2941 break;
2942 case 't':
2943 strncpy(im->title,optarg,150);
2944 im->title[150]='\0';
2945 break;
2947 case '?':
2948 if (optopt != 0)
2949 rrd_set_error("unknown option '%c'", optopt);
2950 else
2951 rrd_set_error("unknown option '%s'",argv[optind-1]);
2952 return;
2953 }
2954 }
2956 if (optind >= argc) {
2957 rrd_set_error("missing filename");
2958 return;
2959 }
2961 if (im->logarithmic == 1 && (im->minval <= 0 || isnan(im->minval))){
2962 rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");
2963 return;
2964 }
2966 if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
2967 /* error string is set in parsetime.c */
2968 return;
2969 }
2971 if (start_tmp < 3600*24*365*10){
2972 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
2973 return;
2974 }
2976 if (end_tmp < start_tmp) {
2977 rrd_set_error("start (%ld) should be less than end (%ld)",
2978 start_tmp, end_tmp);
2979 return;
2980 }
2982 im->start = start_tmp;
2983 im->end = end_tmp;
2984 }
2986 int
2987 rrd_graph_check_vname(image_desc_t *im, char *varname, char *err)
2988 {
2989 if ((im->gdes[im->gdes_c-1].vidx=find_var(im,varname))==-1) {
2990 rrd_set_error("Unknown variable '%s' in %s",varname,err);
2991 return -1;
2992 }
2993 return 0;
2994 }
2995 int
2996 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
2997 {
2998 char *color;
2999 graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
3001 color=strstr(var,"#");
3002 if (color==NULL) {
3003 if (optional==0) {
3004 rrd_set_error("Found no color in %s",err);
3005 return 0;
3006 }
3007 return 0;
3008 } else {
3009 int n=0;
3010 char *rest;
3011 gfx_color_t col;
3013 rest=strstr(color,":");
3014 if (rest!=NULL)
3015 n=rest-color;
3016 else
3017 n=strlen(color);
3019 switch (n) {
3020 case 7:
3021 sscanf(color,"#%6lx%n",&col,&n);
3022 col = (col << 8) + 0xff /* shift left by 8 */;
3023 if (n!=7) rrd_set_error("Color problem in %s",err);
3024 break;
3025 case 9:
3026 sscanf(color,"#%8lx%n",&col,&n);
3027 if (n==9) break;
3028 default:
3029 rrd_set_error("Color problem in %s",err);
3030 }
3031 if (rrd_test_error()) return 0;
3032 gdp->col = col;
3033 return n;
3034 }
3035 }
3036 int
3037 rrd_graph_legend(graph_desc_t *gdp, char *line)
3038 {
3039 int i;
3041 i=scan_for_col(line,FMT_LEG_LEN,gdp->legend);
3043 return (strlen(&line[i])==0);
3044 }
3047 int bad_format(char *fmt) {
3048 char *ptr;
3049 int n=0;
3050 ptr = fmt;
3051 while (*ptr != '\0')
3052 if (*ptr++ == '%') {
3054 /* line cannot end with percent char */
3055 if (*ptr == '\0') return 1;
3057 /* '%s', '%S' and '%%' are allowed */
3058 if (*ptr == 's' || *ptr == 'S' || *ptr == '%') ptr++;
3060 /* or else '% 6.2lf' and such are allowed */
3061 else {
3063 /* optional padding character */
3064 if (*ptr == ' ' || *ptr == '+' || *ptr == '-') ptr++;
3066 /* This should take care of 'm.n' with all three optional */
3067 while (*ptr >= '0' && *ptr <= '9') ptr++;
3068 if (*ptr == '.') ptr++;
3069 while (*ptr >= '0' && *ptr <= '9') ptr++;
3071 /* Either 'le', 'lf' or 'lg' must follow here */
3072 if (*ptr++ != 'l') return 1;
3073 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g') ptr++;
3074 else return 1;
3075 n++;
3076 }
3077 }
3079 return (n!=1);
3080 }
3083 int
3084 vdef_parse(gdes,str)
3085 struct graph_desc_t *gdes;
3086 char *str;
3087 {
3088 /* A VDEF currently is either "func" or "param,func"
3089 * so the parsing is rather simple. Change if needed.
3090 */
3091 double param;
3092 char func[30];
3093 int n;
3095 n=0;
3096 sscanf(str,"%le,%29[A-Z]%n",¶m,func,&n);
3097 if (n==strlen(str)) { /* matched */
3098 ;
3099 } else {
3100 n=0;
3101 sscanf(str,"%29[A-Z]%n",func,&n);
3102 if (n==strlen(str)) { /* matched */
3103 param=DNAN;
3104 } else {
3105 rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3106 ,str
3107 ,gdes->vname
3108 );
3109 return -1;
3110 }
3111 }
3112 if (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3113 else if (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3114 else if (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3115 else if (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3116 else if (!strcmp("TOTAL", func)) gdes->vf.op = VDEF_TOTAL;
3117 else if (!strcmp("FIRST", func)) gdes->vf.op = VDEF_FIRST;
3118 else if (!strcmp("LAST", func)) gdes->vf.op = VDEF_LAST;
3119 else {
3120 rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3121 ,func
3122 ,gdes->vname
3123 );
3124 return -1;
3125 };
3127 switch (gdes->vf.op) {
3128 case VDEF_PERCENT:
3129 if (isnan(param)) { /* no parameter given */
3130 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3131 ,func
3132 ,gdes->vname
3133 );
3134 return -1;
3135 };
3136 if (param>=0.0 && param<=100.0) {
3137 gdes->vf.param = param;
3138 gdes->vf.val = DNAN; /* undefined */
3139 gdes->vf.when = 0; /* undefined */
3140 } else {
3141 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3142 ,param
3143 ,gdes->vname
3144 );
3145 return -1;
3146 };
3147 break;
3148 case VDEF_MAXIMUM:
3149 case VDEF_AVERAGE:
3150 case VDEF_MINIMUM:
3151 case VDEF_TOTAL:
3152 case VDEF_FIRST:
3153 case VDEF_LAST:
3154 if (isnan(param)) {
3155 gdes->vf.param = DNAN;
3156 gdes->vf.val = DNAN;
3157 gdes->vf.when = 0;
3158 } else {
3159 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3160 ,func
3161 ,gdes->vname
3162 );
3163 return -1;
3164 };
3165 break;
3166 };
3167 return 0;
3168 }
3171 int
3172 vdef_calc(im,gdi)
3173 image_desc_t *im;
3174 int gdi;
3175 {
3176 graph_desc_t *src,*dst;
3177 rrd_value_t *data;
3178 long step,steps;
3180 dst = &im->gdes[gdi];
3181 src = &im->gdes[dst->vidx];
3182 data = src->data + src->ds;
3183 steps = (src->end - src->start) / src->step;
3185 #if 0
3186 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3187 ,src->start
3188 ,src->end
3189 ,steps
3190 );
3191 #endif
3193 switch (dst->vf.op) {
3194 case VDEF_PERCENT: {
3195 rrd_value_t * array;
3196 int field;
3199 if ((array = malloc(steps*sizeof(double)))==NULL) {
3200 rrd_set_error("malloc VDEV_PERCENT");
3201 return -1;
3202 }
3203 for (step=0;step < steps; step++) {
3204 array[step]=data[step*src->ds_cnt];
3205 }
3206 qsort(array,step,sizeof(double),vdef_percent_compar);
3208 field = (steps-1)*dst->vf.param/100;
3209 dst->vf.val = array[field];
3210 dst->vf.when = 0; /* no time component */
3211 free(array);
3212 #if 0
3213 for(step=0;step<steps;step++)
3214 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3215 #endif
3216 }
3217 break;
3218 case VDEF_MAXIMUM:
3219 step=0;
3220 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3221 if (step == steps) {
3222 dst->vf.val = DNAN;
3223 dst->vf.when = 0;
3224 } else {
3225 dst->vf.val = data[step*src->ds_cnt];
3226 dst->vf.when = src->start + (step+1)*src->step;
3227 }
3228 while (step != steps) {
3229 if (finite(data[step*src->ds_cnt])) {
3230 if (data[step*src->ds_cnt] > dst->vf.val) {
3231 dst->vf.val = data[step*src->ds_cnt];
3232 dst->vf.when = src->start + (step+1)*src->step;
3233 }
3234 }
3235 step++;
3236 }
3237 break;
3238 case VDEF_TOTAL:
3239 case VDEF_AVERAGE: {
3240 int cnt=0;
3241 double sum=0.0;
3242 for (step=0;step<steps;step++) {
3243 if (finite(data[step*src->ds_cnt])) {
3244 sum += data[step*src->ds_cnt];
3245 cnt ++;
3246 };
3247 }
3248 if (cnt) {
3249 if (dst->vf.op == VDEF_TOTAL) {
3250 dst->vf.val = sum*src->step;
3251 dst->vf.when = cnt*src->step; /* not really "when" */
3252 } else {
3253 dst->vf.val = sum/cnt;
3254 dst->vf.when = 0; /* no time component */
3255 };
3256 } else {
3257 dst->vf.val = DNAN;
3258 dst->vf.when = 0;
3259 }
3260 }
3261 break;
3262 case VDEF_MINIMUM:
3263 step=0;
3264 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3265 if (step == steps) {
3266 dst->vf.val = DNAN;
3267 dst->vf.when = 0;
3268 } else {
3269 dst->vf.val = data[step*src->ds_cnt];
3270 dst->vf.when = src->start + (step+1)*src->step;
3271 }
3272 while (step != steps) {
3273 if (finite(data[step*src->ds_cnt])) {
3274 if (data[step*src->ds_cnt] < dst->vf.val) {
3275 dst->vf.val = data[step*src->ds_cnt];
3276 dst->vf.when = src->start + (step+1)*src->step;
3277 }
3278 }
3279 step++;
3280 }
3281 break;
3282 case VDEF_FIRST:
3283 /* The time value returned here is one step before the
3284 * actual time value. This is the start of the first
3285 * non-NaN interval.
3286 */
3287 step=0;
3288 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3289 if (step == steps) { /* all entries were NaN */
3290 dst->vf.val = DNAN;
3291 dst->vf.when = 0;
3292 } else {
3293 dst->vf.val = data[step*src->ds_cnt];
3294 dst->vf.when = src->start + step*src->step;
3295 }
3296 break;
3297 case VDEF_LAST:
3298 /* The time value returned here is the
3299 * actual time value. This is the end of the last
3300 * non-NaN interval.
3301 */
3302 step=steps-1;
3303 while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3304 if (step < 0) { /* all entries were NaN */
3305 dst->vf.val = DNAN;
3306 dst->vf.when = 0;
3307 } else {
3308 dst->vf.val = data[step*src->ds_cnt];
3309 dst->vf.when = src->start + (step+1)*src->step;
3310 }
3311 break;
3312 }
3313 return 0;
3314 }
3316 /* NaN < -INF < finite_values < INF */
3317 int
3318 vdef_percent_compar(a,b)
3319 const void *a,*b;
3320 {
3321 /* Equality is not returned; this doesn't hurt except
3322 * (maybe) for a little performance.
3323 */
3325 /* First catch NaN values. They are smallest */
3326 if (isnan( *(double *)a )) return -1;
3327 if (isnan( *(double *)b )) return 1;
3329 /* NaN doesn't reach this part so INF and -INF are extremes.
3330 * The sign from isinf() is compatible with the sign we return
3331 */
3332 if (isinf( *(double *)a )) return isinf( *(double *)a );
3333 if (isinf( *(double *)b )) return isinf( *(double *)b );
3335 /* If we reach this, both values must be finite */
3336 if ( *(double *)a < *(double *)b ) return -1; else return 1;
3337 }