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