1 /****************************************************************************
2 * RRDtool 1.4.3 Copyright by Tobi Oetiker, 1997-2010
3 ****************************************************************************
4 * rrd__graph.c produce graphs from data in rrdfiles
5 ****************************************************************************/
20 #include "plbasename.h"
23 #if defined(WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
32 #ifdef HAVE_LANGINFO_H
36 #include "rrd_graph.h"
37 #include "rrd_client.h"
39 /* some constant definitions */
43 #ifndef RRD_DEFAULT_FONT
44 /* there is special code later to pick Cour.ttf when running on windows */
45 #define RRD_DEFAULT_FONT "DejaVu Sans Mono,Bitstream Vera Sans Mono,monospace,Courier"
48 text_prop_t text_prop[] = {
49 {8.0, RRD_DEFAULT_FONT,NULL}
51 {9.0, RRD_DEFAULT_FONT,NULL}
53 {7.0, RRD_DEFAULT_FONT,NULL}
55 {8.0, RRD_DEFAULT_FONT,NULL}
57 {8.0, RRD_DEFAULT_FONT,NULL} /* legend */
59 {5.5, RRD_DEFAULT_FONT,NULL} /* watermark */
62 char week_fmt[128] = "Week %V";
65 {0, 0, TMT_SECOND, 30, TMT_MINUTE, 5, TMT_MINUTE, 5, 0, "%H:%M"}
67 {2, 0, TMT_MINUTE, 1, TMT_MINUTE, 5, TMT_MINUTE, 5, 0, "%H:%M"}
69 {5, 0, TMT_MINUTE, 2, TMT_MINUTE, 10, TMT_MINUTE, 10, 0, "%H:%M"}
71 {10, 0, TMT_MINUTE, 5, TMT_MINUTE, 20, TMT_MINUTE, 20, 0, "%H:%M"}
73 {30, 0, TMT_MINUTE, 10, TMT_HOUR, 1, TMT_HOUR, 1, 0, "%H:%M"}
75 {60, 0, TMT_MINUTE, 30, TMT_HOUR, 2, TMT_HOUR, 2, 0, "%H:%M"}
77 {60, 24 * 3600, TMT_MINUTE, 30, TMT_HOUR, 2, TMT_HOUR, 6, 0, "%a %H:%M"}
79 {180, 0, TMT_HOUR, 1, TMT_HOUR, 6, TMT_HOUR, 6, 0, "%H:%M"}
81 {180, 24 * 3600, TMT_HOUR, 1, TMT_HOUR, 6, TMT_HOUR, 12, 0, "%a %H:%M"}
83 /*{300, 0, TMT_HOUR,3, TMT_HOUR,12, TMT_HOUR,12, 12*3600,"%a %p"}, this looks silly */
84 {600, 0, TMT_HOUR, 6, TMT_DAY, 1, TMT_DAY, 1, 24 * 3600, "%a"}
86 {1200, 0, TMT_HOUR, 6, TMT_DAY, 1, TMT_DAY, 1, 24 * 3600, "%d"}
88 {1800, 0, TMT_HOUR, 12, TMT_DAY, 1, TMT_DAY, 2, 24 * 3600, "%a %d"}
90 {2400, 0, TMT_HOUR, 12, TMT_DAY, 1, TMT_DAY, 2, 24 * 3600, "%a"}
92 {3600, 0, TMT_DAY, 1, TMT_WEEK, 1, TMT_WEEK, 1, 7 * 24 * 3600, week_fmt}
94 {3 * 3600, 0, TMT_WEEK, 1, TMT_MONTH, 1, TMT_WEEK, 2, 7 * 24 * 3600,
97 {6 * 3600, 0, TMT_MONTH, 1, TMT_MONTH, 1, TMT_MONTH, 1, 30 * 24 * 3600,
100 {48 * 3600, 0, TMT_MONTH, 1, TMT_MONTH, 3, TMT_MONTH, 3, 30 * 24 * 3600,
103 {315360, 0, TMT_MONTH, 3, TMT_YEAR, 1, TMT_YEAR, 1, 365 * 24 * 3600, "%Y"}
105 {10 * 24 * 3600, 0, TMT_YEAR, 1, TMT_YEAR, 1, TMT_YEAR, 1,
106 365 * 24 * 3600, "%y"}
108 {-1, 0, TMT_MONTH, 0, TMT_MONTH, 0, TMT_MONTH, 0, 0, ""}
111 /* sensible y label intervals ...*/
135 {20.0, {1, 5, 10, 20}
141 {100.0, {1, 2, 5, 10}
144 {200.0, {1, 5, 10, 20}
147 {500.0, {1, 2, 4, 10}
155 gfx_color_t graph_col[] = /* default colors */
157 {1.00, 1.00, 1.00, 1.00}, /* canvas */
158 {0.95, 0.95, 0.95, 1.00}, /* background */
159 {0.81, 0.81, 0.81, 1.00}, /* shade A */
160 {0.62, 0.62, 0.62, 1.00}, /* shade B */
161 {0.56, 0.56, 0.56, 0.75}, /* grid */
162 {0.87, 0.31, 0.31, 0.60}, /* major grid */
163 {0.00, 0.00, 0.00, 1.00}, /* font */
164 {0.50, 0.12, 0.12, 1.00}, /* arrow */
165 {0.12, 0.12, 0.12, 1.00}, /* axis */
166 {0.00, 0.00, 0.00, 1.00} /* frame */
173 # define DPRINT(x) (void)(printf x, printf("\n"))
179 /* initialize with xtr(im,0); */
187 pixie = (double) im->xsize / (double) (im->end - im->start);
190 return (int) ((double) im->xorigin + pixie * (mytime - im->start));
193 /* translate data values into y coordinates */
202 if (!im->logarithmic)
203 pixie = (double) im->ysize / (im->maxval - im->minval);
206 (double) im->ysize / (log10(im->maxval) - log10(im->minval));
208 } else if (!im->logarithmic) {
209 yval = im->yorigin - pixie * (value - im->minval);
211 if (value < im->minval) {
214 yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
222 /* conversion function for symbolic entry names */
225 #define conv_if(VV,VVV) \
226 if (strcmp(#VV, string) == 0) return VVV ;
232 conv_if(PRINT, GF_PRINT);
233 conv_if(GPRINT, GF_GPRINT);
234 conv_if(COMMENT, GF_COMMENT);
235 conv_if(HRULE, GF_HRULE);
236 conv_if(VRULE, GF_VRULE);
237 conv_if(LINE, GF_LINE);
238 conv_if(AREA, GF_AREA);
239 conv_if(GRAD, GF_GRAD);
240 conv_if(STACK, GF_STACK);
241 conv_if(TICK, GF_TICK);
242 conv_if(TEXTALIGN, GF_TEXTALIGN);
243 conv_if(DEF, GF_DEF);
244 conv_if(CDEF, GF_CDEF);
245 conv_if(VDEF, GF_VDEF);
246 conv_if(XPORT, GF_XPORT);
247 conv_if(SHIFT, GF_SHIFT);
249 return (enum gf_en)(-1);
252 enum gfx_if_en if_conv(
256 conv_if(PNG, IF_PNG);
257 conv_if(SVG, IF_SVG);
258 conv_if(EPS, IF_EPS);
259 conv_if(PDF, IF_PDF);
261 return (enum gfx_if_en)(-1);
264 enum tmt_en tmt_conv(
268 conv_if(SECOND, TMT_SECOND);
269 conv_if(MINUTE, TMT_MINUTE);
270 conv_if(HOUR, TMT_HOUR);
271 conv_if(DAY, TMT_DAY);
272 conv_if(WEEK, TMT_WEEK);
273 conv_if(MONTH, TMT_MONTH);
274 conv_if(YEAR, TMT_YEAR);
275 return (enum tmt_en)(-1);
278 enum grc_en grc_conv(
282 conv_if(BACK, GRC_BACK);
283 conv_if(CANVAS, GRC_CANVAS);
284 conv_if(SHADEA, GRC_SHADEA);
285 conv_if(SHADEB, GRC_SHADEB);
286 conv_if(GRID, GRC_GRID);
287 conv_if(MGRID, GRC_MGRID);
288 conv_if(FONT, GRC_FONT);
289 conv_if(ARROW, GRC_ARROW);
290 conv_if(AXIS, GRC_AXIS);
291 conv_if(FRAME, GRC_FRAME);
293 return (enum grc_en)(-1);
296 enum text_prop_en text_prop_conv(
300 conv_if(DEFAULT, TEXT_PROP_DEFAULT);
301 conv_if(TITLE, TEXT_PROP_TITLE);
302 conv_if(AXIS, TEXT_PROP_AXIS);
303 conv_if(UNIT, TEXT_PROP_UNIT);
304 conv_if(LEGEND, TEXT_PROP_LEGEND);
305 conv_if(WATERMARK, TEXT_PROP_WATERMARK);
306 return (enum text_prop_en)(-1);
316 cairo_status_t status = (cairo_status_t) 0;
321 if (im->daemon_addr != NULL)
322 free(im->daemon_addr);
324 for (i = 0; i < (unsigned) im->gdes_c; i++) {
325 if (im->gdes[i].data_first) {
326 /* careful here, because a single pointer can occur several times */
327 free(im->gdes[i].data);
328 if (im->gdes[i].ds_namv) {
329 for (ii = 0; ii < im->gdes[i].ds_cnt; ii++)
330 free(im->gdes[i].ds_namv[ii]);
331 free(im->gdes[i].ds_namv);
334 /* free allocated memory used for dashed lines */
335 if (im->gdes[i].p_dashes != NULL)
336 free(im->gdes[i].p_dashes);
338 free(im->gdes[i].p_data);
339 free(im->gdes[i].rpnp);
343 for (i = 0; i < DIM(text_prop);i++){
344 pango_font_description_free(im->text_prop[i].font_desc);
345 im->text_prop[i].font_desc = NULL;
348 if (im->font_options)
349 cairo_font_options_destroy(im->font_options);
352 status = cairo_status(im->cr);
353 cairo_destroy(im->cr);
357 if (im->rendered_image) {
358 free(im->rendered_image);
362 g_object_unref (im->layout);
366 cairo_surface_destroy(im->surface);
369 fprintf(stderr, "OOPS: Cairo has issues it can't even die: %s\n",
370 cairo_status_to_string(status));
375 /* find SI magnitude symbol for the given number*/
377 image_desc_t *im, /* image description */
383 char *symbol[] = { "a", /* 10e-18 Atto */
384 "f", /* 10e-15 Femto */
385 "p", /* 10e-12 Pico */
386 "n", /* 10e-9 Nano */
387 "u", /* 10e-6 Micro */
388 "m", /* 10e-3 Milli */
393 "T", /* 10e12 Tera */
394 "P", /* 10e15 Peta */
401 if (*value == 0.0 || isnan(*value)) {
405 sindex = floor(log(fabs(*value)) / log((double) im->base));
406 *magfact = pow((double) im->base, (double) sindex);
407 (*value) /= (*magfact);
409 if (sindex <= symbcenter && sindex >= -symbcenter) {
410 (*symb_ptr) = symbol[sindex + symbcenter];
417 static char si_symbol[] = {
418 'a', /* 10e-18 Atto */
419 'f', /* 10e-15 Femto */
420 'p', /* 10e-12 Pico */
421 'n', /* 10e-9 Nano */
422 'u', /* 10e-6 Micro */
423 'm', /* 10e-3 Milli */
428 'T', /* 10e12 Tera */
429 'P', /* 10e15 Peta */
432 static const int si_symbcenter = 6;
434 /* find SI magnitude symbol for the numbers on the y-axis*/
436 image_desc_t *im /* image description */
440 double digits, viewdigits = 0;
443 floor(log(max(fabs(im->minval), fabs(im->maxval))) /
444 log((double) im->base));
446 if (im->unitsexponent != 9999) {
447 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
448 viewdigits = floor((double)(im->unitsexponent / 3));
453 im->magfact = pow((double) im->base, digits);
456 printf("digits %6.3f im->magfact %6.3f\n", digits, im->magfact);
459 im->viewfactor = im->magfact / pow((double) im->base, viewdigits);
461 if (((viewdigits + si_symbcenter) < sizeof(si_symbol)) &&
462 ((viewdigits + si_symbcenter) >= 0))
463 im->symbol = si_symbol[(int) viewdigits + si_symbcenter];
468 /* move min and max values around to become sensible */
473 double sensiblevalues[] = { 1000.0, 900.0, 800.0, 750.0, 700.0,
474 600.0, 500.0, 400.0, 300.0, 250.0,
475 200.0, 125.0, 100.0, 90.0, 80.0,
476 75.0, 70.0, 60.0, 50.0, 40.0, 30.0,
477 25.0, 20.0, 10.0, 9.0, 8.0,
478 7.0, 6.0, 5.0, 4.0, 3.5, 3.0,
479 2.5, 2.0, 1.8, 1.5, 1.2, 1.0,
480 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.0, -1
483 double scaled_min, scaled_max;
490 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
491 im->minval, im->maxval, im->magfact);
494 if (isnan(im->ygridstep)) {
495 if (im->extra_flags & ALTAUTOSCALE) {
496 /* measure the amplitude of the function. Make sure that
497 graph boundaries are slightly higher then max/min vals
498 so we can see amplitude on the graph */
501 delt = im->maxval - im->minval;
503 fact = 2.0 * pow(10.0,
505 (max(fabs(im->minval), fabs(im->maxval)) /
508 adj = (fact - delt) * 0.55;
511 ("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n",
512 im->minval, im->maxval, delt, fact, adj);
517 } else if (im->extra_flags & ALTAUTOSCALE_MIN) {
518 /* measure the amplitude of the function. Make sure that
519 graph boundaries are slightly lower than min vals
520 so we can see amplitude on the graph */
521 adj = (im->maxval - im->minval) * 0.1;
523 } else if (im->extra_flags & ALTAUTOSCALE_MAX) {
524 /* measure the amplitude of the function. Make sure that
525 graph boundaries are slightly higher than max vals
526 so we can see amplitude on the graph */
527 adj = (im->maxval - im->minval) * 0.1;
530 scaled_min = im->minval / im->magfact;
531 scaled_max = im->maxval / im->magfact;
533 for (i = 1; sensiblevalues[i] > 0; i++) {
534 if (sensiblevalues[i - 1] >= scaled_min &&
535 sensiblevalues[i] <= scaled_min)
536 im->minval = sensiblevalues[i] * (im->magfact);
538 if (-sensiblevalues[i - 1] <= scaled_min &&
539 -sensiblevalues[i] >= scaled_min)
540 im->minval = -sensiblevalues[i - 1] * (im->magfact);
542 if (sensiblevalues[i - 1] >= scaled_max &&
543 sensiblevalues[i] <= scaled_max)
544 im->maxval = sensiblevalues[i - 1] * (im->magfact);
546 if (-sensiblevalues[i - 1] <= scaled_max &&
547 -sensiblevalues[i] >= scaled_max)
548 im->maxval = -sensiblevalues[i] * (im->magfact);
552 /* adjust min and max to the grid definition if there is one */
553 im->minval = (double) im->ylabfact * im->ygridstep *
554 floor(im->minval / ((double) im->ylabfact * im->ygridstep));
555 im->maxval = (double) im->ylabfact * im->ygridstep *
556 ceil(im->maxval / ((double) im->ylabfact * im->ygridstep));
560 fprintf(stderr, "SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
561 im->minval, im->maxval, im->magfact);
569 if (isnan(im->minval) || isnan(im->maxval))
572 if (im->logarithmic) {
573 double ya, yb, ypix, ypixfrac;
574 double log10_range = log10(im->maxval) - log10(im->minval);
576 ya = pow((double) 10, floor(log10(im->minval)));
577 while (ya < im->minval)
580 return; /* don't have y=10^x gridline */
582 if (yb <= im->maxval) {
583 /* we have at least 2 y=10^x gridlines.
584 Make sure distance between them in pixels
585 are an integer by expanding im->maxval */
586 double y_pixel_delta = ytr(im, ya) - ytr(im, yb);
587 double factor = y_pixel_delta / floor(y_pixel_delta);
588 double new_log10_range = factor * log10_range;
589 double new_ymax_log10 = log10(im->minval) + new_log10_range;
591 im->maxval = pow(10, new_ymax_log10);
592 ytr(im, DNAN); /* reset precalc */
593 log10_range = log10(im->maxval) - log10(im->minval);
595 /* make sure first y=10^x gridline is located on
596 integer pixel position by moving scale slightly
597 downwards (sub-pixel movement) */
598 ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
599 ypixfrac = ypix - floor(ypix);
600 if (ypixfrac > 0 && ypixfrac < 1) {
601 double yfrac = ypixfrac / im->ysize;
603 im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
604 im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
605 ytr(im, DNAN); /* reset precalc */
608 /* Make sure we have an integer pixel distance between
609 each minor gridline */
610 double ypos1 = ytr(im, im->minval);
611 double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
612 double y_pixel_delta = ypos1 - ypos2;
613 double factor = y_pixel_delta / floor(y_pixel_delta);
614 double new_range = factor * (im->maxval - im->minval);
615 double gridstep = im->ygrid_scale.gridstep;
616 double minor_y, minor_y_px, minor_y_px_frac;
618 if (im->maxval > 0.0)
619 im->maxval = im->minval + new_range;
621 im->minval = im->maxval - new_range;
622 ytr(im, DNAN); /* reset precalc */
623 /* make sure first minor gridline is on integer pixel y coord */
624 minor_y = gridstep * floor(im->minval / gridstep);
625 while (minor_y < im->minval)
627 minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
628 minor_y_px_frac = minor_y_px - floor(minor_y_px);
629 if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
630 double yfrac = minor_y_px_frac / im->ysize;
631 double range = im->maxval - im->minval;
633 im->minval = im->minval - yfrac * range;
634 im->maxval = im->maxval - yfrac * range;
635 ytr(im, DNAN); /* reset precalc */
637 calc_horizontal_grid(im); /* recalc with changed im->maxval */
641 /* reduce data reimplementation by Alex */
644 enum cf_en cf, /* which consolidation function ? */
645 unsigned long cur_step, /* step the data currently is in */
646 time_t *start, /* start, end and step as requested ... */
647 time_t *end, /* ... by the application will be ... */
648 unsigned long *step, /* ... adjusted to represent reality */
649 unsigned long *ds_cnt, /* number of data sources in file */
651 { /* two dimensional array containing the data */
652 int i, reduce_factor = ceil((double) (*step) / (double) cur_step);
653 unsigned long col, dst_row, row_cnt, start_offset, end_offset, skiprows =
655 rrd_value_t *srcptr, *dstptr;
657 (*step) = cur_step * reduce_factor; /* set new step size for reduced data */
660 row_cnt = ((*end) - (*start)) / cur_step;
666 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
667 row_cnt, reduce_factor, *start, *end, cur_step);
668 for (col = 0; col < row_cnt; col++) {
669 printf("time %10lu: ", *start + (col + 1) * cur_step);
670 for (i = 0; i < *ds_cnt; i++)
671 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
676 /* We have to combine [reduce_factor] rows of the source
677 ** into one row for the destination. Doing this we also
678 ** need to take care to combine the correct rows. First
679 ** alter the start and end time so that they are multiples
680 ** of the new step time. We cannot reduce the amount of
681 ** time so we have to move the end towards the future and
682 ** the start towards the past.
684 end_offset = (*end) % (*step);
685 start_offset = (*start) % (*step);
687 /* If there is a start offset (which cannot be more than
688 ** one destination row), skip the appropriate number of
689 ** source rows and one destination row. The appropriate
690 ** number is what we do know (start_offset/cur_step) of
691 ** the new interval (*step/cur_step aka reduce_factor).
694 printf("start_offset: %lu end_offset: %lu\n", start_offset, end_offset);
695 printf("row_cnt before: %lu\n", row_cnt);
698 (*start) = (*start) - start_offset;
699 skiprows = reduce_factor - start_offset / cur_step;
700 srcptr += skiprows * *ds_cnt;
701 for (col = 0; col < (*ds_cnt); col++)
706 printf("row_cnt between: %lu\n", row_cnt);
709 /* At the end we have some rows that are not going to be
710 ** used, the amount is end_offset/cur_step
713 (*end) = (*end) - end_offset + (*step);
714 skiprows = end_offset / cur_step;
718 printf("row_cnt after: %lu\n", row_cnt);
721 /* Sanity check: row_cnt should be multiple of reduce_factor */
722 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
724 if (row_cnt % reduce_factor) {
725 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
726 row_cnt, reduce_factor);
727 printf("BUG in reduce_data()\n");
731 /* Now combine reduce_factor intervals at a time
732 ** into one interval for the destination.
735 for (dst_row = 0; (long int) row_cnt >= reduce_factor; dst_row++) {
736 for (col = 0; col < (*ds_cnt); col++) {
737 rrd_value_t newval = DNAN;
738 unsigned long validval = 0;
740 for (i = 0; i < reduce_factor; i++) {
741 if (isnan(srcptr[i * (*ds_cnt) + col])) {
746 newval = srcptr[i * (*ds_cnt) + col];
755 newval += srcptr[i * (*ds_cnt) + col];
758 newval = min(newval, srcptr[i * (*ds_cnt) + col]);
761 /* an interval contains a failure if any subintervals contained a failure */
763 newval = max(newval, srcptr[i * (*ds_cnt) + col]);
766 newval = srcptr[i * (*ds_cnt) + col];
792 srcptr += (*ds_cnt) * reduce_factor;
793 row_cnt -= reduce_factor;
795 /* If we had to alter the endtime, we didn't have enough
796 ** source rows to fill the last row. Fill it with NaN.
799 for (col = 0; col < (*ds_cnt); col++)
802 row_cnt = ((*end) - (*start)) / *step;
804 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
805 row_cnt, *start, *end, *step);
806 for (col = 0; col < row_cnt; col++) {
807 printf("time %10lu: ", *start + (col + 1) * (*step));
808 for (i = 0; i < *ds_cnt; i++)
809 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
816 /* get the data required for the graphs from the
825 /* pull the data from the rrd files ... */
826 for (i = 0; i < (int) im->gdes_c; i++) {
827 /* only GF_DEF elements fetch data */
828 if (im->gdes[i].gf != GF_DEF)
832 /* do we have it already ? */
833 for (ii = 0; ii < i; ii++) {
834 if (im->gdes[ii].gf != GF_DEF)
836 if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
837 && (im->gdes[i].cf == im->gdes[ii].cf)
838 && (im->gdes[i].cf_reduce == im->gdes[ii].cf_reduce)
839 && (im->gdes[i].start_orig == im->gdes[ii].start_orig)
840 && (im->gdes[i].end_orig == im->gdes[ii].end_orig)
841 && (im->gdes[i].step_orig == im->gdes[ii].step_orig)) {
842 /* OK, the data is already there.
843 ** Just copy the header portion
845 im->gdes[i].start = im->gdes[ii].start;
846 im->gdes[i].end = im->gdes[ii].end;
847 im->gdes[i].step = im->gdes[ii].step;
848 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
849 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
850 im->gdes[i].data = im->gdes[ii].data;
851 im->gdes[i].data_first = 0;
858 unsigned long ft_step = im->gdes[i].step; /* ft_step will record what we got from fetch */
859 const char *rrd_daemon;
862 if (im->gdes[i].daemon[0] != 0)
863 rrd_daemon = im->gdes[i].daemon;
865 rrd_daemon = im->daemon_addr;
867 /* "daemon" may be NULL. ENV_RRDCACHED_ADDRESS is evaluated in that
868 * case. If "daemon" holds the same value as in the previous
869 * iteration, no actual new connection is established - the
870 * existing connection is re-used. */
871 rrdc_connect (rrd_daemon);
873 /* If connecting was successfull, use the daemon to query the data.
874 * If there is no connection, for example because no daemon address
875 * was specified, (try to) use the local file directly. */
876 if (rrdc_is_connected (rrd_daemon))
878 status = rrdc_fetch (im->gdes[i].rrd,
879 cf_to_string (im->gdes[i].cf),
884 &im->gdes[i].ds_namv,
891 if ((rrd_fetch_fn(im->gdes[i].rrd,
897 &im->gdes[i].ds_namv,
898 &im->gdes[i].data)) == -1) {
902 im->gdes[i].data_first = 1;
904 /* must reduce to at least im->step
905 otherwhise we end up with more data than we can handle in the
906 chart and visibility of data will be random */
907 im->gdes[i].step = max(im->gdes[i].step,im->step);
908 if (ft_step < im->gdes[i].step) {
909 reduce_data(im->gdes[i].cf_reduce,
914 &im->gdes[i].ds_cnt, &im->gdes[i].data);
916 im->gdes[i].step = ft_step;
920 /* lets see if the required data source is really there */
921 for (ii = 0; ii < (int) im->gdes[i].ds_cnt; ii++) {
922 if (strcmp(im->gdes[i].ds_namv[ii], im->gdes[i].ds_nam) == 0) {
926 if (im->gdes[i].ds == -1) {
927 rrd_set_error("No DS called '%s' in '%s'",
928 im->gdes[i].ds_nam, im->gdes[i].rrd);
936 /* evaluate the expressions in the CDEF functions */
938 /*************************************************************
940 *************************************************************/
942 long find_var_wrapper(
946 return find_var((image_desc_t *) arg1, key);
949 /* find gdes containing var*/
956 for (ii = 0; ii < im->gdes_c - 1; ii++) {
957 if ((im->gdes[ii].gf == GF_DEF
958 || im->gdes[ii].gf == GF_VDEF || im->gdes[ii].gf == GF_CDEF)
959 && (strcmp(im->gdes[ii].vname, key) == 0)) {
966 /* find the greatest common divisor for all the numbers
967 in the 0 terminated num array */
974 for (i = 0; num[i + 1] != 0; i++) {
976 rest = num[i] % num[i + 1];
982 /* return i==0?num[i]:num[i-1]; */
986 /* run the rpn calculator on all the VDEF and CDEF arguments */
993 long *steparray, rpi;
998 rpnstack_init(&rpnstack);
1000 for (gdi = 0; gdi < im->gdes_c; gdi++) {
1001 /* Look for GF_VDEF and GF_CDEF in the same loop,
1002 * so CDEFs can use VDEFs and vice versa
1004 switch (im->gdes[gdi].gf) {
1008 graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx];
1010 /* remove current shift */
1011 vdp->start -= vdp->shift;
1012 vdp->end -= vdp->shift;
1015 if (im->gdes[gdi].shidx >= 0)
1016 vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
1019 vdp->shift = im->gdes[gdi].shval;
1021 /* normalize shift to multiple of consolidated step */
1022 vdp->shift = (vdp->shift / (long) vdp->step) * (long) vdp->step;
1025 vdp->start += vdp->shift;
1026 vdp->end += vdp->shift;
1030 /* A VDEF has no DS. This also signals other parts
1031 * of rrdtool that this is a VDEF value, not a CDEF.
1033 im->gdes[gdi].ds_cnt = 0;
1034 if (vdef_calc(im, gdi)) {
1035 rrd_set_error("Error processing VDEF '%s'",
1036 im->gdes[gdi].vname);
1037 rpnstack_free(&rpnstack);
1042 im->gdes[gdi].ds_cnt = 1;
1043 im->gdes[gdi].ds = 0;
1044 im->gdes[gdi].data_first = 1;
1045 im->gdes[gdi].start = 0;
1046 im->gdes[gdi].end = 0;
1051 /* Find the variables in the expression.
1052 * - VDEF variables are substituted by their values
1053 * and the opcode is changed into OP_NUMBER.
1054 * - CDEF variables are analized for their step size,
1055 * the lowest common denominator of all the step
1056 * sizes of the data sources involved is calculated
1057 * and the resulting number is the step size for the
1058 * resulting data source.
1060 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1061 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1062 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1063 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
1065 if (im->gdes[ptr].ds_cnt == 0) { /* this is a VDEF data source */
1068 ("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
1069 im->gdes[gdi].vname, im->gdes[ptr].vname);
1070 printf("DEBUG: value from vdef is %f\n",
1071 im->gdes[ptr].vf.val);
1073 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
1074 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
1075 } else { /* normal variables and PREF(variables) */
1077 /* add one entry to the array that keeps track of the step sizes of the
1078 * data sources going into the CDEF. */
1080 (long*)rrd_realloc(steparray,
1082 1) * sizeof(*steparray))) == NULL) {
1083 rrd_set_error("realloc steparray");
1084 rpnstack_free(&rpnstack);
1088 steparray[stepcnt - 1] = im->gdes[ptr].step;
1090 /* adjust start and end of cdef (gdi) so
1091 * that it runs from the latest start point
1092 * to the earliest endpoint of any of the
1093 * rras involved (ptr)
1096 if (im->gdes[gdi].start < im->gdes[ptr].start)
1097 im->gdes[gdi].start = im->gdes[ptr].start;
1099 if (im->gdes[gdi].end == 0 ||
1100 im->gdes[gdi].end > im->gdes[ptr].end)
1101 im->gdes[gdi].end = im->gdes[ptr].end;
1103 /* store pointer to the first element of
1104 * the rra providing data for variable,
1105 * further save step size and data source
1108 im->gdes[gdi].rpnp[rpi].data =
1109 im->gdes[ptr].data + im->gdes[ptr].ds;
1110 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
1111 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
1113 /* backoff the *.data ptr; this is done so
1114 * rpncalc() function doesn't have to treat
1115 * the first case differently
1117 } /* if ds_cnt != 0 */
1118 } /* if OP_VARIABLE */
1119 } /* loop through all rpi */
1121 /* move the data pointers to the correct period */
1122 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1123 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1124 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1125 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
1127 im->gdes[gdi].start - im->gdes[ptr].start;
1130 im->gdes[gdi].rpnp[rpi].data +=
1131 (diff / im->gdes[ptr].step) *
1132 im->gdes[ptr].ds_cnt;
1136 if (steparray == NULL) {
1137 rrd_set_error("rpn expressions without DEF"
1138 " or CDEF variables are not supported");
1139 rpnstack_free(&rpnstack);
1142 steparray[stepcnt] = 0;
1143 /* Now find the resulting step. All steps in all
1144 * used RRAs have to be visited
1146 im->gdes[gdi].step = lcd(steparray);
1148 if ((im->gdes[gdi].data = (rrd_value_t*)malloc(((im->gdes[gdi].end -
1149 im->gdes[gdi].start)
1150 / im->gdes[gdi].step)
1151 * sizeof(double))) == NULL) {
1152 rrd_set_error("malloc im->gdes[gdi].data");
1153 rpnstack_free(&rpnstack);
1157 /* Step through the new cdef results array and
1158 * calculate the values
1160 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
1161 now <= im->gdes[gdi].end; now += im->gdes[gdi].step) {
1162 rpnp_t *rpnp = im->gdes[gdi].rpnp;
1164 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
1165 * in this case we are advancing by timesteps;
1166 * we use the fact that time_t is a synonym for long
1168 if (rpn_calc(rpnp, &rpnstack, (long) now,
1169 im->gdes[gdi].data, ++dataidx) == -1) {
1170 /* rpn_calc sets the error string */
1171 rpnstack_free(&rpnstack);
1174 } /* enumerate over time steps within a CDEF */
1179 } /* enumerate over CDEFs */
1180 rpnstack_free(&rpnstack);
1184 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
1185 /* yes we are loosing precision by doing tos with floats instead of doubles
1186 but it seems more stable this way. */
1188 static int AlmostEqual2sComplement(
1194 int aInt = *(int *) &A;
1195 int bInt = *(int *) &B;
1198 /* Make sure maxUlps is non-negative and small enough that the
1199 default NAN won't compare as equal to anything. */
1201 /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
1203 /* Make aInt lexicographically ordered as a twos-complement int */
1206 aInt = 0x80000000l - aInt;
1208 /* Make bInt lexicographically ordered as a twos-complement int */
1211 bInt = 0x80000000l - bInt;
1213 intDiff = abs(aInt - bInt);
1215 if (intDiff <= maxUlps)
1221 /* massage data so, that we get one value for each x coordinate in the graph */
1226 double pixstep = (double) (im->end - im->start)
1227 / (double) im->xsize; /* how much time
1228 passes in one pixel */
1230 double minval = DNAN, maxval = DNAN;
1232 unsigned long gr_time;
1234 /* memory for the processed data */
1235 for (i = 0; i < im->gdes_c; i++) {
1236 if ((im->gdes[i].gf == GF_LINE)
1237 || (im->gdes[i].gf == GF_AREA)
1238 || (im->gdes[i].gf == GF_TICK)
1239 || (im->gdes[i].gf == GF_GRAD)
1241 if ((im->gdes[i].p_data = (rrd_value_t*)malloc((im->xsize + 1)
1242 * sizeof(rrd_value_t))) == NULL) {
1243 rrd_set_error("malloc data_proc");
1249 for (i = 0; i < im->xsize; i++) { /* for each pixel */
1252 gr_time = im->start + pixstep * i; /* time of the current step */
1255 for (ii = 0; ii < im->gdes_c; ii++) {
1258 switch (im->gdes[ii].gf) {
1263 if (!im->gdes[ii].stack)
1265 value = im->gdes[ii].yrule;
1266 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1267 /* The time of the data doesn't necessarily match
1268 ** the time of the graph. Beware.
1270 vidx = im->gdes[ii].vidx;
1271 if (im->gdes[vidx].gf == GF_VDEF) {
1272 value = im->gdes[vidx].vf.val;
1274 if (((long int) gr_time >=
1275 (long int) im->gdes[vidx].start)
1276 && ((long int) gr_time <
1277 (long int) im->gdes[vidx].end)) {
1278 value = im->gdes[vidx].data[(unsigned long)
1284 im->gdes[vidx].step)
1285 * im->gdes[vidx].ds_cnt +
1292 if (!isnan(value)) {
1294 im->gdes[ii].p_data[i] = paintval;
1295 /* GF_TICK: the data values are not
1296 ** relevant for min and max
1298 if (finite(paintval) && im->gdes[ii].gf != GF_TICK) {
1299 if ((isnan(minval) || paintval < minval) &&
1300 !(im->logarithmic && paintval <= 0.0))
1302 if (isnan(maxval) || paintval > maxval)
1306 im->gdes[ii].p_data[i] = DNAN;
1311 ("STACK should already be turned into LINE or AREA here");
1320 /* if min or max have not been asigned a value this is because
1321 there was no data in the graph ... this is not good ...
1322 lets set these to dummy values then ... */
1324 if (im->logarithmic) {
1325 if (isnan(minval) || isnan(maxval) || maxval <= 0) {
1326 minval = 0.0; /* catching this right away below */
1329 /* in logarithm mode, where minval is smaller or equal
1330 to 0 make the beast just way smaller than maxval */
1332 minval = maxval / 10e8;
1335 if (isnan(minval) || isnan(maxval)) {
1341 /* adjust min and max values given by the user */
1342 /* for logscale we add something on top */
1343 if (isnan(im->minval)
1344 || ((!im->rigid) && im->minval > minval)
1346 if (im->logarithmic)
1347 im->minval = minval / 2.0;
1349 im->minval = minval;
1351 if (isnan(im->maxval)
1352 || (!im->rigid && im->maxval < maxval)
1354 if (im->logarithmic)
1355 im->maxval = maxval * 2.0;
1357 im->maxval = maxval;
1360 /* make sure min is smaller than max */
1361 if (im->minval > im->maxval) {
1363 im->minval = 0.99 * im->maxval;
1365 im->minval = 1.01 * im->maxval;
1368 /* make sure min and max are not equal */
1369 if (AlmostEqual2sComplement(im->minval, im->maxval, 4)) {
1375 /* make sure min and max are not both zero */
1376 if (AlmostEqual2sComplement(im->maxval, 0, 4)) {
1383 static int find_first_weekday(void){
1384 static int first_weekday = -1;
1385 if (first_weekday == -1){
1386 #ifdef HAVE__NL_TIME_WEEK_1STDAY
1387 /* according to http://sourceware.org/ml/libc-locales/2009-q1/msg00011.html */
1388 long week_1stday_l = (long) nl_langinfo (_NL_TIME_WEEK_1STDAY);
1389 if (week_1stday_l == 19971130) first_weekday = 0; /* Sun */
1390 else if (week_1stday_l == 19971201) first_weekday = 1; /* Mon */
1391 else first_weekday = 1; /* we go for a monday default */
1396 return first_weekday;
1399 /* identify the point where the first gridline, label ... gets placed */
1401 time_t find_first_time(
1402 time_t start, /* what is the initial time */
1403 enum tmt_en baseint, /* what is the basic interval */
1404 long basestep /* how many if these do we jump a time */
1409 localtime_r(&start, &tm);
1413 tm. tm_sec -= tm.tm_sec % basestep;
1418 tm. tm_min -= tm.tm_min % basestep;
1424 tm. tm_hour -= tm.tm_hour % basestep;
1428 /* we do NOT look at the basestep for this ... */
1435 /* we do NOT look at the basestep for this ... */
1439 tm. tm_mday -= tm.tm_wday - find_first_weekday();
1441 if (tm.tm_wday == 0 && find_first_weekday() > 0)
1442 tm. tm_mday -= 7; /* we want the *previous* week */
1450 tm. tm_mon -= tm.tm_mon % basestep;
1461 tm.tm_year + 1900) %basestep;
1467 /* identify the point where the next gridline, label ... gets placed */
1468 time_t find_next_time(
1469 time_t current, /* what is the initial time */
1470 enum tmt_en baseint, /* what is the basic interval */
1471 long basestep /* how many if these do we jump a time */
1477 localtime_r(¤t, &tm);
1481 case TMT_SECOND: limit = 7200; break;
1482 case TMT_MINUTE: limit = 120; break;
1483 case TMT_HOUR: limit = 2; break;
1484 default: limit = 2; break;
1489 tm. tm_sec += basestep;
1493 tm. tm_min += basestep;
1497 tm. tm_hour += basestep;
1501 tm. tm_mday += basestep;
1505 tm. tm_mday += 7 * basestep;
1509 tm. tm_mon += basestep;
1513 tm. tm_year += basestep;
1515 madetime = mktime(&tm);
1516 } while (madetime == -1 && limit-- >= 0); /* this is necessary to skip impossible times
1517 like the daylight saving time skips */
1523 /* calculate values required for PRINT and GPRINT functions */
1528 long i, ii, validsteps;
1531 int graphelement = 0;
1534 double magfact = -1;
1539 /* wow initializing tmvdef is quite a task :-) */
1540 time_t now = time(NULL);
1542 localtime_r(&now, &tmvdef);
1543 for (i = 0; i < im->gdes_c; i++) {
1544 vidx = im->gdes[i].vidx;
1545 switch (im->gdes[i].gf) {
1548 /* PRINT and GPRINT can now print VDEF generated values.
1549 * There's no need to do any calculations on them as these
1550 * calculations were already made.
1552 if (im->gdes[vidx].gf == GF_VDEF) { /* simply use vals */
1553 printval = im->gdes[vidx].vf.val;
1554 localtime_r(&im->gdes[vidx].vf.when, &tmvdef);
1555 } else { /* need to calculate max,min,avg etcetera */
1556 max_ii = ((im->gdes[vidx].end - im->gdes[vidx].start)
1557 / im->gdes[vidx].step * im->gdes[vidx].ds_cnt);
1560 for (ii = im->gdes[vidx].ds;
1561 ii < max_ii; ii += im->gdes[vidx].ds_cnt) {
1562 if (!finite(im->gdes[vidx].data[ii]))
1564 if (isnan(printval)) {
1565 printval = im->gdes[vidx].data[ii];
1570 switch (im->gdes[i].cf) {
1574 case CF_DEVSEASONAL:
1578 printval += im->gdes[vidx].data[ii];
1581 printval = min(printval, im->gdes[vidx].data[ii]);
1585 printval = max(printval, im->gdes[vidx].data[ii]);
1588 printval = im->gdes[vidx].data[ii];
1591 if (im->gdes[i].cf == CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1592 if (validsteps > 1) {
1593 printval = (printval / validsteps);
1596 } /* prepare printval */
1598 if (!im->gdes[i].strftm && (percent_s = strstr(im->gdes[i].format, "%S")) != NULL) {
1599 /* Magfact is set to -1 upon entry to print_calc. If it
1600 * is still less than 0, then we need to run auto_scale.
1601 * Otherwise, put the value into the correct units. If
1602 * the value is 0, then do not set the symbol or magnification
1603 * so next the calculation will be performed again. */
1604 if (magfact < 0.0) {
1605 auto_scale(im, &printval, &si_symb, &magfact);
1606 if (printval == 0.0)
1609 printval /= magfact;
1611 *(++percent_s) = 's';
1612 } else if (!im->gdes[i].strftm && strstr(im->gdes[i].format, "%s") != NULL) {
1613 auto_scale(im, &printval, &si_symb, &magfact);
1616 if (im->gdes[i].gf == GF_PRINT) {
1617 rrd_infoval_t prline;
1619 if (im->gdes[i].strftm) {
1620 prline.u_str = (char*)malloc((FMT_LEG_LEN + 2) * sizeof(char));
1621 if (im->gdes[vidx].vf.never == 1) {
1622 time_clean(prline.u_str, im->gdes[i].format);
1624 strftime(prline.u_str,
1625 FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1627 } else if (bad_format(im->gdes[i].format)) {
1629 ("bad format for PRINT in '%s'", im->gdes[i].format);
1633 sprintf_alloc(im->gdes[i].format, printval, si_symb);
1637 ("print[%ld]", prline_cnt++), RD_I_STR, prline);
1642 if (im->gdes[i].strftm) {
1643 if (im->gdes[vidx].vf.never == 1) {
1644 time_clean(im->gdes[i].legend, im->gdes[i].format);
1646 strftime(im->gdes[i].legend,
1647 FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1650 if (bad_format(im->gdes[i].format)) {
1652 ("bad format for GPRINT in '%s'",
1653 im->gdes[i].format);
1656 #ifdef HAVE_SNPRINTF
1657 snprintf(im->gdes[i].legend,
1659 im->gdes[i].format, printval, si_symb);
1661 sprintf(im->gdes[i].legend,
1662 im->gdes[i].format, printval, si_symb);
1675 if (isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1676 im->gdes[i].yrule = im->gdes[vidx].vf.val;
1681 if (im->gdes[i].xrule == 0) { /* again ... the legend printer needs it */
1682 im->gdes[i].xrule = im->gdes[vidx].vf.when;
1691 #ifdef WITH_PIECHART
1699 ("STACK should already be turned into LINE or AREA here");
1704 return graphelement;
1709 /* place legends with color spots */
1715 int interleg = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1716 int border = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1717 int fill = 0, fill_last;
1718 double legendwidth; // = im->ximg - 2 * border;
1720 double leg_x = border;
1721 int leg_y = 0; //im->yimg;
1722 int leg_y_prev = 0; // im->yimg;
1725 int i, ii, mark = 0;
1726 char default_txtalign = TXA_JUSTIFIED; /*default line orientation */
1729 char saved_legend[FMT_LEG_LEN + 5];
1735 legendwidth = im->legendwidth - 2 * border;
1739 if (!(im->extra_flags & NOLEGEND) && !(im->extra_flags & ONLY_GRAPH)) {
1740 if ((legspace = (int*)malloc(im->gdes_c * sizeof(int))) == NULL) {
1741 rrd_set_error("malloc for legspace");
1745 for (i = 0; i < im->gdes_c; i++) {
1746 char prt_fctn; /*special printfunctions */
1748 strcpy(saved_legend, im->gdes[i].legend);
1752 /* hide legends for rules which are not displayed */
1753 if (im->gdes[i].gf == GF_TEXTALIGN) {
1754 default_txtalign = im->gdes[i].txtalign;
1757 if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1758 if (im->gdes[i].gf == GF_HRULE
1759 && (im->gdes[i].yrule <
1760 im->minval || im->gdes[i].yrule > im->maxval))
1761 im->gdes[i].legend[0] = '\0';
1762 if (im->gdes[i].gf == GF_VRULE
1763 && (im->gdes[i].xrule <
1764 im->start || im->gdes[i].xrule > im->end))
1765 im->gdes[i].legend[0] = '\0';
1768 /* turn \\t into tab */
1769 while ((tab = strstr(im->gdes[i].legend, "\\t"))) {
1770 memmove(tab, tab + 1, strlen(tab));
1774 leg_cc = strlen(im->gdes[i].legend);
1775 /* is there a controle code at the end of the legend string ? */
1776 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc - 2] == '\\') {
1777 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1779 im->gdes[i].legend[leg_cc] = '\0';
1783 /* only valid control codes */
1784 if (prt_fctn != 'l' && prt_fctn != 'n' && /* a synonym for l */
1789 prt_fctn != 's' && prt_fctn != '\0' && prt_fctn != 'g') {
1792 ("Unknown control code at the end of '%s\\%c'",
1793 im->gdes[i].legend, prt_fctn);
1797 if (prt_fctn == 'n') {
1801 /* remove exess space from the end of the legend for \g */
1802 while (prt_fctn == 'g' &&
1803 leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1805 im->gdes[i].legend[leg_cc] = '\0';
1810 /* no interleg space if string ends in \g */
1811 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1813 fill += legspace[i];
1816 gfx_get_text_width(im,
1822 im->tabwidth, im->gdes[i].legend);
1827 /* who said there was a special tag ... ? */
1828 if (prt_fctn == 'g') {
1832 if (prt_fctn == '\0') {
1833 if(calc_width && (fill > legendwidth)){
1836 if (i == im->gdes_c - 1 || fill > legendwidth) {
1837 /* just one legend item is left right or center */
1838 switch (default_txtalign) {
1853 /* is it time to place the legends ? */
1854 if (fill > legendwidth) {
1862 if (leg_c == 1 && prt_fctn == 'j') {
1867 if (prt_fctn != '\0') {
1869 if (leg_c >= 2 && prt_fctn == 'j') {
1870 glue = (double)(legendwidth - fill) / (double)(leg_c - 1);
1874 if (prt_fctn == 'c')
1875 leg_x = border + (double)(legendwidth - fill) / 2.0;
1876 if (prt_fctn == 'r')
1877 leg_x = legendwidth - fill + border;
1878 for (ii = mark; ii <= i; ii++) {
1879 if (im->gdes[ii].legend[0] == '\0')
1880 continue; /* skip empty legends */
1881 im->gdes[ii].leg_x = leg_x;
1882 im->gdes[ii].leg_y = leg_y + border;
1884 (double)gfx_get_text_width(im, leg_x,
1889 im->tabwidth, im->gdes[ii].legend)
1890 +(double)legspace[ii]
1894 if (leg_x > border || prt_fctn == 's')
1895 leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1896 if (prt_fctn == 's')
1897 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1898 if (prt_fctn == 'u')
1899 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size *1.8;
1901 if(calc_width && (fill > legendwidth)){
1910 strcpy(im->gdes[i].legend, saved_legend);
1915 im->legendwidth = legendwidth + 2 * border;
1918 im->legendheight = leg_y + border * 0.6;
1925 /* create a grid on the graph. it determines what to do
1926 from the values of xsize, start and end */
1928 /* the xaxis labels are determined from the number of seconds per pixel
1929 in the requested graph */
1931 int calc_horizontal_grid(
1939 int decimals, fractionals;
1941 im->ygrid_scale.labfact = 2;
1942 range = im->maxval - im->minval;
1943 scaledrange = range / im->magfact;
1944 /* does the scale of this graph make it impossible to put lines
1945 on it? If so, give up. */
1946 if (isnan(scaledrange)) {
1950 /* find grid spaceing */
1952 if (isnan(im->ygridstep)) {
1953 if (im->extra_flags & ALTYGRID) {
1954 /* find the value with max number of digits. Get number of digits */
1957 (max(fabs(im->maxval), fabs(im->minval)) *
1958 im->viewfactor / im->magfact));
1959 if (decimals <= 0) /* everything is small. make place for zero */
1961 im->ygrid_scale.gridstep =
1963 floor(log10(range * im->viewfactor / im->magfact))) /
1964 im->viewfactor * im->magfact;
1965 if (im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1966 im->ygrid_scale.gridstep = 0.1;
1967 /* should have at least 5 lines but no more then 15 */
1968 if (range / im->ygrid_scale.gridstep < 5
1969 && im->ygrid_scale.gridstep >= 30)
1970 im->ygrid_scale.gridstep /= 10;
1971 if (range / im->ygrid_scale.gridstep > 15)
1972 im->ygrid_scale.gridstep *= 10;
1973 if (range / im->ygrid_scale.gridstep > 5) {
1974 im->ygrid_scale.labfact = 1;
1975 if (range / im->ygrid_scale.gridstep > 8
1976 || im->ygrid_scale.gridstep <
1977 1.8 * im->text_prop[TEXT_PROP_AXIS].size)
1978 im->ygrid_scale.labfact = 2;
1980 im->ygrid_scale.gridstep /= 5;
1981 im->ygrid_scale.labfact = 5;
1985 (im->ygrid_scale.gridstep *
1986 (double) im->ygrid_scale.labfact * im->viewfactor /
1988 if (fractionals < 0) { /* small amplitude. */
1989 int len = decimals - fractionals + 1;
1991 if (im->unitslength < len + 2)
1992 im->unitslength = len + 2;
1993 sprintf(im->ygrid_scale.labfmt,
1995 -fractionals, (im->symbol != ' ' ? " %c" : ""));
1997 int len = decimals + 1;
1999 if (im->unitslength < len + 2)
2000 im->unitslength = len + 2;
2001 sprintf(im->ygrid_scale.labfmt,
2002 "%%%d.0f%s", len, (im->symbol != ' ' ? " %c" : ""));
2004 } else { /* classic rrd grid */
2005 for (i = 0; ylab[i].grid > 0; i++) {
2006 pixel = im->ysize / (scaledrange / ylab[i].grid);
2012 for (i = 0; i < 4; i++) {
2013 if (pixel * ylab[gridind].lfac[i] >=
2014 1.8 * im->text_prop[TEXT_PROP_AXIS].size) {
2015 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
2020 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
2023 im->ygrid_scale.gridstep = im->ygridstep;
2024 im->ygrid_scale.labfact = im->ylabfact;
2029 int draw_horizontal_grid(
2035 char graph_label[100];
2037 double X0 = im->xorigin;
2038 double X1 = im->xorigin + im->xsize;
2039 int sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
2040 int egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
2042 double second_axis_magfact = 0;
2043 char *second_axis_symb = "";
2046 im->ygrid_scale.gridstep /
2047 (double) im->magfact * (double) im->viewfactor;
2048 MaxY = scaledstep * (double) egrid;
2049 for (i = sgrid; i <= egrid; i++) {
2051 im->ygrid_scale.gridstep * i);
2053 im->ygrid_scale.gridstep * (i + 1));
2055 if (floor(Y0 + 0.5) >=
2056 im->yorigin - im->ysize && floor(Y0 + 0.5) <= im->yorigin) {
2057 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
2058 with the chosen settings. Add a label if required by settings, or if
2059 there is only one label so far and the next grid line is out of bounds. */
2060 if (i % im->ygrid_scale.labfact == 0
2062 && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
2063 if (im->symbol == ' ') {
2064 if (im->extra_flags & ALTYGRID) {
2065 sprintf(graph_label,
2066 im->ygrid_scale.labfmt,
2067 scaledstep * (double) i);
2070 sprintf(graph_label, "%4.1f",
2071 scaledstep * (double) i);
2073 sprintf(graph_label, "%4.0f",
2074 scaledstep * (double) i);
2078 char sisym = (i == 0 ? ' ' : im->symbol);
2080 if (im->extra_flags & ALTYGRID) {
2081 sprintf(graph_label,
2082 im->ygrid_scale.labfmt,
2083 scaledstep * (double) i, sisym);
2086 sprintf(graph_label, "%4.1f %c",
2087 scaledstep * (double) i, sisym);
2089 sprintf(graph_label, "%4.0f %c",
2090 scaledstep * (double) i, sisym);
2095 if (im->second_axis_scale != 0){
2096 char graph_label_right[100];
2097 double sval = im->ygrid_scale.gridstep*(double)i*im->second_axis_scale+im->second_axis_shift;
2098 if (im->second_axis_format[0] == '\0'){
2099 if (!second_axis_magfact){
2100 double dummy = im->ygrid_scale.gridstep*(double)(sgrid+egrid)/2.0*im->second_axis_scale+im->second_axis_shift;
2101 auto_scale(im,&dummy,&second_axis_symb,&second_axis_magfact);
2103 sval /= second_axis_magfact;
2106 sprintf(graph_label_right,"%5.1f %s",sval,second_axis_symb);
2108 sprintf(graph_label_right,"%5.0f %s",sval,second_axis_symb);
2112 sprintf(graph_label_right,im->second_axis_format,sval);
2116 im->graph_col[GRC_FONT],
2117 im->text_prop[TEXT_PROP_AXIS].font_desc,
2118 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2119 graph_label_right );
2125 text_prop[TEXT_PROP_AXIS].
2127 im->graph_col[GRC_FONT],
2129 text_prop[TEXT_PROP_AXIS].
2132 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2133 gfx_line(im, X0 - 2, Y0, X0, Y0,
2134 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2135 gfx_line(im, X1, Y0, X1 + 2, Y0,
2136 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2137 gfx_dashed_line(im, X0 - 2, Y0,
2143 im->grid_dash_on, im->grid_dash_off);
2144 } else if (!(im->extra_flags & NOMINOR)) {
2147 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2148 gfx_line(im, X1, Y0, X1 + 2, Y0,
2149 GRIDWIDTH, im->graph_col[GRC_GRID]);
2150 gfx_dashed_line(im, X0 - 1, Y0,
2154 graph_col[GRC_GRID],
2155 im->grid_dash_on, im->grid_dash_off);
2162 /* this is frexp for base 10 */
2173 iexp = floor(log((double)fabs(x)) / log((double)10));
2174 mnt = x / pow(10.0, iexp);
2177 mnt = x / pow(10.0, iexp);
2184 /* logaritmic horizontal grid */
2185 int horizontal_log_grid(
2189 double yloglab[][10] = {
2191 1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0,
2193 1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0,
2195 1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0,
2212 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} /* last line */
2214 int i, j, val_exp, min_exp;
2215 double nex; /* number of decades in data */
2216 double logscale; /* scale in logarithmic space */
2217 int exfrac = 1; /* decade spacing */
2218 int mid = -1; /* row in yloglab for major grid */
2219 double mspac; /* smallest major grid spacing (pixels) */
2220 int flab; /* first value in yloglab to use */
2221 double value, tmp, pre_value;
2223 char graph_label[100];
2225 nex = log10(im->maxval / im->minval);
2226 logscale = im->ysize / nex;
2227 /* major spacing for data with high dynamic range */
2228 while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2235 /* major spacing for less dynamic data */
2237 /* search best row in yloglab */
2239 for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2240 mspac = logscale * log10(10.0 / yloglab[mid][i]);
2243 2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0);
2246 /* find first value in yloglab */
2248 yloglab[mid][flab] < 10
2249 && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2250 if (yloglab[mid][flab] == 10.0) {
2255 if (val_exp % exfrac)
2256 val_exp += abs(-val_exp % exfrac);
2258 X1 = im->xorigin + im->xsize;
2263 value = yloglab[mid][flab] * pow(10.0, val_exp);
2264 if (AlmostEqual2sComplement(value, pre_value, 4))
2265 break; /* it seems we are not converging */
2267 Y0 = ytr(im, value);
2268 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2270 /* major grid line */
2272 X0 - 2, Y0, X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2273 gfx_line(im, X1, Y0, X1 + 2, Y0,
2274 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2275 gfx_dashed_line(im, X0 - 2, Y0,
2280 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2282 if (im->extra_flags & FORCE_UNITS_SI) {
2287 scale = floor(val_exp / 3.0);
2289 pvalue = pow(10.0, val_exp % 3);
2291 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2292 pvalue *= yloglab[mid][flab];
2293 if (((scale + si_symbcenter) < (int) sizeof(si_symbol))
2294 && ((scale + si_symbcenter) >= 0))
2295 symbol = si_symbol[scale + si_symbcenter];
2298 sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2300 sprintf(graph_label, "%3.0e", value);
2302 if (im->second_axis_scale != 0){
2303 char graph_label_right[100];
2304 double sval = value*im->second_axis_scale+im->second_axis_shift;
2305 if (im->second_axis_format[0] == '\0'){
2306 if (im->extra_flags & FORCE_UNITS_SI) {
2309 auto_scale(im,&sval,&symb,&mfac);
2310 sprintf(graph_label_right,"%4.0f %s", sval,symb);
2313 sprintf(graph_label_right,"%3.0e", sval);
2317 sprintf(graph_label_right,im->second_axis_format,sval,"");
2322 im->graph_col[GRC_FONT],
2323 im->text_prop[TEXT_PROP_AXIS].font_desc,
2324 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2325 graph_label_right );
2331 text_prop[TEXT_PROP_AXIS].
2333 im->graph_col[GRC_FONT],
2335 text_prop[TEXT_PROP_AXIS].
2338 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2340 if (mid < 4 && exfrac == 1) {
2341 /* find first and last minor line behind current major line
2342 * i is the first line and j tha last */
2344 min_exp = val_exp - 1;
2345 for (i = 1; yloglab[mid][i] < 10.0; i++);
2346 i = yloglab[mid][i - 1] + 1;
2350 i = yloglab[mid][flab - 1] + 1;
2351 j = yloglab[mid][flab];
2354 /* draw minor lines below current major line */
2355 for (; i < j; i++) {
2357 value = i * pow(10.0, min_exp);
2358 if (value < im->minval)
2360 Y0 = ytr(im, value);
2361 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2366 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2367 gfx_line(im, X1, Y0, X1 + 2, Y0,
2368 GRIDWIDTH, im->graph_col[GRC_GRID]);
2369 gfx_dashed_line(im, X0 - 1, Y0,
2373 graph_col[GRC_GRID],
2374 im->grid_dash_on, im->grid_dash_off);
2376 } else if (exfrac > 1) {
2377 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2378 value = pow(10.0, i);
2379 if (value < im->minval)
2381 Y0 = ytr(im, value);
2382 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2387 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2388 gfx_line(im, X1, Y0, X1 + 2, Y0,
2389 GRIDWIDTH, im->graph_col[GRC_GRID]);
2390 gfx_dashed_line(im, X0 - 1, Y0,
2394 graph_col[GRC_GRID],
2395 im->grid_dash_on, im->grid_dash_off);
2400 if (yloglab[mid][++flab] == 10.0) {
2406 /* draw minor lines after highest major line */
2407 if (mid < 4 && exfrac == 1) {
2408 /* find first and last minor line below current major line
2409 * i is the first line and j tha last */
2411 min_exp = val_exp - 1;
2412 for (i = 1; yloglab[mid][i] < 10.0; i++);
2413 i = yloglab[mid][i - 1] + 1;
2417 i = yloglab[mid][flab - 1] + 1;
2418 j = yloglab[mid][flab];
2421 /* draw minor lines below current major line */
2422 for (; i < j; i++) {
2424 value = i * pow(10.0, min_exp);
2425 if (value < im->minval)
2427 Y0 = ytr(im, value);
2428 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2432 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2433 gfx_line(im, X1, Y0, X1 + 2, Y0,
2434 GRIDWIDTH, im->graph_col[GRC_GRID]);
2435 gfx_dashed_line(im, X0 - 1, Y0,
2439 graph_col[GRC_GRID],
2440 im->grid_dash_on, im->grid_dash_off);
2443 /* fancy minor gridlines */
2444 else if (exfrac > 1) {
2445 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2446 value = pow(10.0, i);
2447 if (value < im->minval)
2449 Y0 = ytr(im, value);
2450 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2454 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2455 gfx_line(im, X1, Y0, X1 + 2, Y0,
2456 GRIDWIDTH, im->graph_col[GRC_GRID]);
2457 gfx_dashed_line(im, X0 - 1, Y0,
2461 graph_col[GRC_GRID],
2462 im->grid_dash_on, im->grid_dash_off);
2473 int xlab_sel; /* which sort of label and grid ? */
2474 time_t ti, tilab, timajor;
2476 char graph_label[100];
2477 double X0, Y0, Y1; /* points for filled graph and more */
2480 /* the type of time grid is determined by finding
2481 the number of seconds per pixel in the graph */
2482 if (im->xlab_user.minsec == -1) {
2483 factor = (im->end - im->start) / im->xsize;
2485 while (xlab[xlab_sel + 1].minsec !=
2486 -1 && xlab[xlab_sel + 1].minsec <= factor) {
2488 } /* pick the last one */
2489 while (xlab[xlab_sel - 1].minsec ==
2490 xlab[xlab_sel].minsec
2491 && xlab[xlab_sel].length > (im->end - im->start)) {
2493 } /* go back to the smallest size */
2494 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2495 im->xlab_user.gridst = xlab[xlab_sel].gridst;
2496 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2497 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2498 im->xlab_user.labtm = xlab[xlab_sel].labtm;
2499 im->xlab_user.labst = xlab[xlab_sel].labst;
2500 im->xlab_user.precis = xlab[xlab_sel].precis;
2501 im->xlab_user.stst = xlab[xlab_sel].stst;
2504 /* y coords are the same for every line ... */
2506 Y1 = im->yorigin - im->ysize;
2507 /* paint the minor grid */
2508 if (!(im->extra_flags & NOMINOR)) {
2509 for (ti = find_first_time(im->start,
2517 find_first_time(im->start,
2522 ti < im->end && ti != -1;
2524 find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2526 /* are we inside the graph ? */
2527 if (ti < im->start || ti > im->end)
2529 while (timajor < ti && timajor != -1) {
2530 timajor = find_next_time(timajor,
2533 mgridtm, im->xlab_user.mgridst);
2535 if (timajor == -1) break; /* fail in case of problems with time increments */
2537 continue; /* skip as falls on major grid line */
2539 gfx_line(im, X0, Y1 - 2, X0, Y1,
2540 GRIDWIDTH, im->graph_col[GRC_GRID]);
2541 gfx_line(im, X0, Y0, X0, Y0 + 2,
2542 GRIDWIDTH, im->graph_col[GRC_GRID]);
2543 gfx_dashed_line(im, X0, Y0 + 1, X0,
2546 graph_col[GRC_GRID],
2547 im->grid_dash_on, im->grid_dash_off);
2551 /* paint the major grid */
2552 for (ti = find_first_time(im->start,
2559 ti < im->end && ti != -1;
2560 ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2562 /* are we inside the graph ? */
2563 if (ti < im->start || ti > im->end)
2566 gfx_line(im, X0, Y1 - 2, X0, Y1,
2567 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2568 gfx_line(im, X0, Y0, X0, Y0 + 3,
2569 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2570 gfx_dashed_line(im, X0, Y0 + 3, X0,
2574 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2576 /* paint the labels below the graph */
2578 find_first_time(im->start -
2587 im->xlab_user.precis / 2) && ti != -1;
2588 ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2590 tilab = ti + im->xlab_user.precis / 2; /* correct time for the label */
2591 /* are we inside the graph ? */
2592 if (tilab < im->start || tilab > im->end)
2595 localtime_r(&tilab, &tm);
2596 strftime(graph_label, 99, im->xlab_user.stst, &tm);
2598 # error "your libc has no strftime I guess we'll abort the exercise here."
2603 im->graph_col[GRC_FONT],
2605 text_prop[TEXT_PROP_AXIS].
2608 GFX_H_CENTER, GFX_V_TOP, graph_label);
2617 /* draw x and y axis */
2618 /* gfx_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2619 im->xorigin+im->xsize,im->yorigin-im->ysize,
2620 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2622 gfx_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2623 im->xorigin+im->xsize,im->yorigin-im->ysize,
2624 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2626 gfx_line(im, im->xorigin - 4,
2628 im->xorigin + im->xsize +
2629 4, im->yorigin, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2630 gfx_line(im, im->xorigin,
2633 im->yorigin - im->ysize -
2634 4, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2635 /* arrow for X and Y axis direction */
2636 gfx_new_area(im, im->xorigin + im->xsize + 2, im->yorigin - 3, im->xorigin + im->xsize + 2, im->yorigin + 3, im->xorigin + im->xsize + 7, im->yorigin, /* horyzontal */
2637 im->graph_col[GRC_ARROW]);
2639 gfx_new_area(im, im->xorigin - 3, im->yorigin - im->ysize - 2, im->xorigin + 3, im->yorigin - im->ysize - 2, im->xorigin, im->yorigin - im->ysize - 7, /* vertical */
2640 im->graph_col[GRC_ARROW]);
2642 if (im->second_axis_scale != 0){
2643 gfx_line ( im, im->xorigin+im->xsize,im->yorigin+4,
2644 im->xorigin+im->xsize,im->yorigin-im->ysize-4,
2645 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2647 im->xorigin+im->xsize-2, im->yorigin-im->ysize-2,
2648 im->xorigin+im->xsize+3, im->yorigin-im->ysize-2,
2649 im->xorigin+im->xsize, im->yorigin-im->ysize-7, /* LINEOFFSET */
2650 im->graph_col[GRC_ARROW]);
2661 double X0, Y0; /* points for filled graph and more */
2662 struct gfx_color_t water_color;
2664 if (im->draw_3d_border > 0) {
2665 /* draw 3d border */
2666 i = im->draw_3d_border;
2667 gfx_new_area(im, 0, im->yimg,
2668 i, im->yimg - i, i, i, im->graph_col[GRC_SHADEA]);
2669 gfx_add_point(im, im->ximg - i, i);
2670 gfx_add_point(im, im->ximg, 0);
2671 gfx_add_point(im, 0, 0);
2673 gfx_new_area(im, i, im->yimg - i,
2675 im->yimg - i, im->ximg - i, i, im->graph_col[GRC_SHADEB]);
2676 gfx_add_point(im, im->ximg, 0);
2677 gfx_add_point(im, im->ximg, im->yimg);
2678 gfx_add_point(im, 0, im->yimg);
2681 if (im->draw_x_grid == 1)
2683 if (im->draw_y_grid == 1) {
2684 if (im->logarithmic) {
2685 res = horizontal_log_grid(im);
2687 res = draw_horizontal_grid(im);
2690 /* dont draw horizontal grid if there is no min and max val */
2692 char *nodata = "No Data found";
2694 gfx_text(im, im->ximg / 2,
2697 im->graph_col[GRC_FONT],
2699 text_prop[TEXT_PROP_AXIS].
2702 GFX_H_CENTER, GFX_V_CENTER, nodata);
2706 /* yaxis unit description */
2707 if (im->ylegend[0] != '\0'){
2709 im->xOriginLegendY+10,
2711 im->graph_col[GRC_FONT],
2713 text_prop[TEXT_PROP_UNIT].
2716 RRDGRAPH_YLEGEND_ANGLE, GFX_H_CENTER, GFX_V_CENTER, im->ylegend);
2719 if (im->second_axis_legend[0] != '\0'){
2721 im->xOriginLegendY2+10,
2722 im->yOriginLegendY2,
2723 im->graph_col[GRC_FONT],
2724 im->text_prop[TEXT_PROP_UNIT].font_desc,
2726 RRDGRAPH_YLEGEND_ANGLE,
2727 GFX_H_CENTER, GFX_V_CENTER,
2728 im->second_axis_legend);
2733 im->xOriginTitle, im->yOriginTitle+6,
2734 im->graph_col[GRC_FONT],
2736 text_prop[TEXT_PROP_TITLE].
2738 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP, im->title);
2739 /* rrdtool 'logo' */
2740 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
2741 water_color = im->graph_col[GRC_FONT];
2742 water_color.alpha = 0.3;
2743 double xpos = im->legendposition == EAST ? im->xOriginLegendY : im->ximg - 4;
2744 gfx_text(im, xpos, 5,
2747 text_prop[TEXT_PROP_WATERMARK].
2748 font_desc, im->tabwidth,
2749 -90, GFX_H_LEFT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2751 /* graph watermark */
2752 if (im->watermark[0] != '\0') {
2753 water_color = im->graph_col[GRC_FONT];
2754 water_color.alpha = 0.3;
2756 im->ximg / 2, im->yimg - 6,
2759 text_prop[TEXT_PROP_WATERMARK].
2760 font_desc, im->tabwidth, 0,
2761 GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2765 if (!(im->extra_flags & NOLEGEND) && !(im->extra_flags & ONLY_GRAPH)) {
2766 for (i = 0; i < im->gdes_c; i++) {
2767 if (im->gdes[i].legend[0] == '\0')
2769 /* im->gdes[i].leg_y is the bottom of the legend */
2770 X0 = im->xOriginLegend + im->gdes[i].leg_x;
2771 Y0 = im->legenddirection == TOP_DOWN ? im->yOriginLegend + im->gdes[i].leg_y : im->yOriginLegend + im->legendheight - im->gdes[i].leg_y;
2772 gfx_text(im, X0, Y0,
2773 im->graph_col[GRC_FONT],
2776 [TEXT_PROP_LEGEND].font_desc,
2778 GFX_H_LEFT, GFX_V_BOTTOM, im->gdes[i].legend);
2779 /* The legend for GRAPH items starts with "M " to have
2780 enough space for the box */
2781 if (im->gdes[i].gf != GF_PRINT &&
2782 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2786 boxH = gfx_get_text_width(im, 0,
2791 im->tabwidth, "o") * 1.2;
2793 /* shift the box up a bit */
2796 if (im->dynamic_labels && im->gdes[i].gf == GF_HRULE) { /* [-] */
2798 cairo_new_path(im->cr);
2799 cairo_set_line_width(im->cr, 1.0);
2802 X0 + boxH, Y0 - boxV / 2,
2803 1.0, im->gdes[i].col);
2805 } else if (im->dynamic_labels && im->gdes[i].gf == GF_VRULE) { /* [|] */
2807 cairo_new_path(im->cr);
2808 cairo_set_line_width(im->cr, 1.0);
2811 X0 + boxH / 2, Y0 - boxV,
2812 1.0, im->gdes[i].col);
2814 } else if (im->dynamic_labels && im->gdes[i].gf == GF_LINE) { /* [/] */
2816 cairo_new_path(im->cr);
2817 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
2820 X0 + boxH, Y0 - boxV,
2821 im->gdes[i].linewidth, im->gdes[i].col);
2824 /* make sure transparent colors show up the same way as in the graph */
2827 X0, Y0, X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2828 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2830 gfx_new_area(im, X0, Y0 - boxV, X0,
2831 Y0, X0 + boxH, Y0, im->gdes[i].col);
2832 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2835 cairo_new_path(im->cr);
2836 cairo_set_line_width(im->cr, 1.0);
2839 gfx_line_fit(im, &X0, &Y0);
2840 gfx_line_fit(im, &X1, &Y1);
2841 cairo_move_to(im->cr, X0, Y0);
2842 cairo_line_to(im->cr, X1, Y0);
2843 cairo_line_to(im->cr, X1, Y1);
2844 cairo_line_to(im->cr, X0, Y1);
2845 cairo_close_path(im->cr);
2846 cairo_set_source_rgba(im->cr,
2847 im->graph_col[GRC_FRAME].red,
2848 im->graph_col[GRC_FRAME].green,
2849 im->graph_col[GRC_FRAME].blue,
2850 im->graph_col[GRC_FRAME].alpha);
2852 if (im->gdes[i].dash) {
2853 /* make box borders in legend dashed if the graph is dashed */
2857 cairo_set_dash(im->cr, dashes, 1, 0.0);
2859 cairo_stroke(im->cr);
2860 cairo_restore(im->cr);
2867 /*****************************************************
2868 * lazy check make sure we rely need to create this graph
2869 *****************************************************/
2876 struct stat imgstat;
2879 return 0; /* no lazy option */
2880 if (strlen(im->graphfile) == 0)
2881 return 0; /* inmemory option */
2882 if (stat(im->graphfile, &imgstat) != 0)
2883 return 0; /* can't stat */
2884 /* one pixel in the existing graph is more then what we would
2886 if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2888 if ((fd = fopen(im->graphfile, "rb")) == NULL)
2889 return 0; /* the file does not exist */
2890 switch (im->imgformat) {
2892 size = PngSize(fd, &(im->ximg), &(im->yimg));
2902 int graph_size_location(
2907 /* The actual size of the image to draw is determined from
2908 ** several sources. The size given on the command line is
2909 ** the graph area but we need more as we have to draw labels
2910 ** and other things outside the graph area. If the option
2911 ** --full-size-mode is selected the size defines the total
2912 ** image size and the size available for the graph is
2916 /** +---+-----------------------------------+
2917 ** | y |...............graph title.........|
2918 ** | +---+-------------------------------+
2922 ** | s | x | main graph area |
2927 ** | l | b +-------------------------------+
2928 ** | e | l | x axis labels |
2929 ** +---+---+-------------------------------+
2930 ** |....................legends............|
2931 ** +---------------------------------------+
2933 ** +---------------------------------------+
2936 int Xvertical = 0, Xvertical2 = 0, Ytitle =
2937 0, Xylabel = 0, Xmain = 0, Ymain =
2938 0, Yxlabel = 0, Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2940 // no legends and no the shall be plotted it's easy
2941 if (im->extra_flags & ONLY_GRAPH) {
2943 im->ximg = im->xsize;
2944 im->yimg = im->ysize;
2945 im->yorigin = im->ysize;
2950 if(im->watermark[0] != '\0') {
2951 Ywatermark = im->text_prop[TEXT_PROP_WATERMARK].size * 2;
2954 // calculate the width of the left vertical legend
2955 if (im->ylegend[0] != '\0') {
2956 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2959 // calculate the width of the right vertical legend
2960 if (im->second_axis_legend[0] != '\0') {
2961 Xvertical2 = im->text_prop[TEXT_PROP_UNIT].size * 2;
2964 Xvertical2 = Xspacing;
2967 if (im->title[0] != '\0') {
2968 /* The title is placed "inbetween" two text lines so it
2969 ** automatically has some vertical spacing. The horizontal
2970 ** spacing is added here, on each side.
2972 /* if necessary, reduce the font size of the title until it fits the image width */
2973 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2976 // we have no title; get a little clearing from the top
2981 if (im->draw_x_grid) {
2982 // calculate the height of the horizontal labelling
2983 Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2985 if (im->draw_y_grid || im->forceleftspace) {
2986 // calculate the width of the vertical labelling
2988 gfx_get_text_width(im, 0,
2989 im->text_prop[TEXT_PROP_AXIS].font_desc,
2990 im->tabwidth, "0") * im->unitslength;
2994 // add some space to the labelling
2995 Xylabel += Xspacing;
2997 /* If the legend is printed besides the graph the width has to be
2998 ** calculated first. Placing the legend north or south of the
2999 ** graph requires the width calculation first, so the legend is
3000 ** skipped for the moment.
3002 im->legendheight = 0;
3003 im->legendwidth = 0;
3004 if (!(im->extra_flags & NOLEGEND)) {
3005 if(im->legendposition == WEST || im->legendposition == EAST){
3006 if (leg_place(im, 1) == -1){
3012 if (im->extra_flags & FULL_SIZE_MODE) {
3014 /* The actual size of the image to draw has been determined by the user.
3015 ** The graph area is the space remaining after accounting for the legend,
3016 ** the watermark, the axis labels, and the title.
3018 im->ximg = im->xsize;
3019 im->yimg = im->ysize;
3023 /* Now calculate the total size. Insert some spacing where
3024 desired. im->xorigin and im->yorigin need to correspond
3025 with the lower left corner of the main graph area or, if
3026 this one is not set, the imaginary box surrounding the
3028 /* Initial size calculation for the main graph area */
3030 Xmain -= Xylabel;// + Xspacing;
3031 if((im->legendposition == WEST || im->legendposition == EAST) && !(im->extra_flags & NOLEGEND) ){
3032 Xmain -= im->legendwidth;// + Xspacing;
3034 if (im->second_axis_scale != 0){
3037 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3041 Xmain -= Xvertical + Xvertical2;
3043 /* limit the remaining space to 0 */
3049 /* Putting the legend north or south, the height can now be calculated */
3050 if (!(im->extra_flags & NOLEGEND)) {
3051 if(im->legendposition == NORTH || im->legendposition == SOUTH){
3052 im->legendwidth = im->ximg;
3053 if (leg_place(im, 0) == -1){
3059 if( (im->legendposition == NORTH || im->legendposition == SOUTH) && !(im->extra_flags & NOLEGEND) ){
3060 Ymain -= Yxlabel + im->legendheight;
3066 /* reserve space for the title *or* some padding above the graph */
3069 /* reserve space for padding below the graph */
3070 if (im->extra_flags & NOLEGEND) {
3071 Ymain -= 0.5*Yspacing;
3074 if (im->watermark[0] != '\0') {
3075 Ymain -= Ywatermark;
3077 /* limit the remaining height to 0 */
3082 } else { /* dimension options -width and -height refer to the dimensions of the main graph area */
3084 /* The actual size of the image to draw is determined from
3085 ** several sources. The size given on the command line is
3086 ** the graph area but we need more as we have to draw labels
3087 ** and other things outside the graph area.
3091 Xmain = im->xsize; // + Xspacing;
3095 im->ximg = Xmain + Xylabel;
3096 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3097 im->ximg += Xspacing;
3100 if( (im->legendposition == WEST || im->legendposition == EAST) && !(im->extra_flags & NOLEGEND) ){
3101 im->ximg += im->legendwidth;// + Xspacing;
3103 if (im->second_axis_scale != 0){
3104 im->ximg += Xylabel;
3107 im->ximg += Xvertical + Xvertical2;
3109 if (!(im->extra_flags & NOLEGEND)) {
3110 if(im->legendposition == NORTH || im->legendposition == SOUTH){
3111 im->legendwidth = im->ximg;
3112 if (leg_place(im, 0) == -1){
3118 im->yimg = Ymain + Yxlabel;
3119 if( (im->legendposition == NORTH || im->legendposition == SOUTH) && !(im->extra_flags & NOLEGEND) ){
3120 im->yimg += im->legendheight;
3123 /* reserve space for the title *or* some padding above the graph */
3127 im->yimg += 1.5 * Yspacing;
3129 /* reserve space for padding below the graph */
3130 if (im->extra_flags & NOLEGEND) {
3131 im->yimg += 0.5*Yspacing;
3134 if (im->watermark[0] != '\0') {
3135 im->yimg += Ywatermark;
3140 /* In case of putting the legend in west or east position the first
3141 ** legend calculation might lead to wrong positions if some items
3142 ** are not aligned on the left hand side (e.g. centered) as the
3143 ** legendwidth wight have been increased after the item was placed.
3144 ** In this case the positions have to be recalculated.
3146 if (!(im->extra_flags & NOLEGEND)) {
3147 if(im->legendposition == WEST || im->legendposition == EAST){
3148 if (leg_place(im, 0) == -1){
3154 /* After calculating all dimensions
3155 ** it is now possible to calculate
3158 switch(im->legendposition){
3160 im->xOriginTitle = (im->ximg / 2);
3161 im->yOriginTitle = 0;
3163 im->xOriginLegend = 0;
3164 im->yOriginLegend = Ytitle;
3166 im->xOriginLegendY = 0;
3167 im->yOriginLegendY = Ytitle + im->legendheight + (Ymain / 2) + Yxlabel;
3169 im->xorigin = Xvertical + Xylabel;
3170 im->yorigin = Ytitle + im->legendheight + Ymain;
3172 im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3173 if (im->second_axis_scale != 0){
3174 im->xOriginLegendY2 += Xylabel;
3176 im->yOriginLegendY2 = Ytitle + im->legendheight + (Ymain / 2) + Yxlabel;
3181 im->xOriginTitle = im->legendwidth + im->xsize / 2;
3182 im->yOriginTitle = 0;
3184 im->xOriginLegend = 0;
3185 im->yOriginLegend = Ytitle;
3187 im->xOriginLegendY = im->legendwidth;
3188 im->yOriginLegendY = Ytitle + (Ymain / 2);
3190 im->xorigin = im->legendwidth + Xvertical + Xylabel;
3191 im->yorigin = Ytitle + Ymain;
3193 im->xOriginLegendY2 = im->legendwidth + Xvertical + Xylabel + Xmain;
3194 if (im->second_axis_scale != 0){
3195 im->xOriginLegendY2 += Xylabel;
3197 im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3202 im->xOriginTitle = im->ximg / 2;
3203 im->yOriginTitle = 0;
3205 im->xOriginLegend = 0;
3206 im->yOriginLegend = Ytitle + Ymain + Yxlabel;
3208 im->xOriginLegendY = 0;
3209 im->yOriginLegendY = Ytitle + (Ymain / 2);
3211 im->xorigin = Xvertical + Xylabel;
3212 im->yorigin = Ytitle + Ymain;
3214 im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3215 if (im->second_axis_scale != 0){
3216 im->xOriginLegendY2 += Xylabel;
3218 im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3223 im->xOriginTitle = im->xsize / 2;
3224 im->yOriginTitle = 0;
3226 im->xOriginLegend = Xvertical + Xylabel + Xmain + Xvertical2;
3227 if (im->second_axis_scale != 0){
3228 im->xOriginLegend += Xylabel;
3230 im->yOriginLegend = Ytitle;
3232 im->xOriginLegendY = 0;
3233 im->yOriginLegendY = Ytitle + (Ymain / 2);
3235 im->xorigin = Xvertical + Xylabel;
3236 im->yorigin = Ytitle + Ymain;
3238 im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3239 if (im->second_axis_scale != 0){
3240 im->xOriginLegendY2 += Xylabel;
3242 im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3244 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3245 im->xOriginTitle += Xspacing;
3246 im->xOriginLegend += Xspacing;
3247 im->xOriginLegendY += Xspacing;
3248 im->xorigin += Xspacing;
3249 im->xOriginLegendY2 += Xspacing;
3259 static cairo_status_t cairo_output(
3263 unsigned int length)
3265 image_desc_t *im = (image_desc_t*)closure;
3267 im->rendered_image =
3268 (unsigned char*)realloc(im->rendered_image, im->rendered_image_size + length);
3269 if (im->rendered_image == NULL)
3270 return CAIRO_STATUS_WRITE_ERROR;
3271 memcpy(im->rendered_image + im->rendered_image_size, data, length);
3272 im->rendered_image_size += length;
3273 return CAIRO_STATUS_SUCCESS;
3276 /* draw that picture thing ... */
3281 int lazy = lazy_check(im);
3282 double areazero = 0.0;
3283 graph_desc_t *lastgdes = NULL;
3286 // PangoFontMap *font_map = pango_cairo_font_map_get_default();
3288 /* pull the data from the rrd files ... */
3289 if (data_fetch(im) == -1)
3291 /* evaluate VDEF and CDEF operations ... */
3292 if (data_calc(im) == -1)
3294 /* calculate and PRINT and GPRINT definitions. We have to do it at
3295 * this point because it will affect the length of the legends
3296 * if there are no graph elements (i==0) we stop here ...
3297 * if we are lazy, try to quit ...
3303 /* if we want and can be lazy ... quit now */
3307 /**************************************************************
3308 *** Calculating sizes and locations became a bit confusing ***
3309 *** so I moved this into a separate function. ***
3310 **************************************************************/
3311 if (graph_size_location(im, i) == -1)
3314 info.u_cnt = im->xorigin;
3315 grinfo_push(im, sprintf_alloc("graph_left"), RD_I_CNT, info);
3316 info.u_cnt = im->yorigin - im->ysize;
3317 grinfo_push(im, sprintf_alloc("graph_top"), RD_I_CNT, info);
3318 info.u_cnt = im->xsize;
3319 grinfo_push(im, sprintf_alloc("graph_width"), RD_I_CNT, info);
3320 info.u_cnt = im->ysize;
3321 grinfo_push(im, sprintf_alloc("graph_height"), RD_I_CNT, info);
3322 info.u_cnt = im->ximg;
3323 grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
3324 info.u_cnt = im->yimg;
3325 grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
3326 info.u_cnt = im->start;
3327 grinfo_push(im, sprintf_alloc("graph_start"), RD_I_CNT, info);
3328 info.u_cnt = im->end;
3329 grinfo_push(im, sprintf_alloc("graph_end"), RD_I_CNT, info);
3331 /* if we want and can be lazy ... quit now */
3335 /* get actual drawing data and find min and max values */
3336 if (data_proc(im) == -1)
3338 if (!im->logarithmic) {
3342 /* identify si magnitude Kilo, Mega Giga ? */
3343 if (!im->rigid && !im->logarithmic)
3344 expand_range(im); /* make sure the upper and lower limit are
3347 info.u_val = im->minval;
3348 grinfo_push(im, sprintf_alloc("value_min"), RD_I_VAL, info);
3349 info.u_val = im->maxval;
3350 grinfo_push(im, sprintf_alloc("value_max"), RD_I_VAL, info);
3353 if (!calc_horizontal_grid(im))
3358 apply_gridfit(im); */
3359 /* the actual graph is created by going through the individual
3360 graph elements and then drawing them */
3361 cairo_surface_destroy(im->surface);
3362 switch (im->imgformat) {
3365 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
3366 im->ximg * im->zoom,
3367 im->yimg * im->zoom);
3371 im->surface = strlen(im->graphfile)
3372 ? cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
3373 im->yimg * im->zoom)
3374 : cairo_pdf_surface_create_for_stream
3375 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3379 im->surface = strlen(im->graphfile)
3381 cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
3382 im->yimg * im->zoom)
3383 : cairo_ps_surface_create_for_stream
3384 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3388 im->surface = strlen(im->graphfile)
3390 cairo_svg_surface_create(im->
3392 im->ximg * im->zoom, im->yimg * im->zoom)
3393 : cairo_svg_surface_create_for_stream
3394 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3395 cairo_svg_surface_restrict_to_version
3396 (im->surface, CAIRO_SVG_VERSION_1_1);
3399 cairo_destroy(im->cr);
3400 im->cr = cairo_create(im->surface);
3401 cairo_set_antialias(im->cr, im->graph_antialias);
3402 cairo_scale(im->cr, im->zoom, im->zoom);
3403 // pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3404 gfx_new_area(im, 0, 0, 0, im->yimg,
3405 im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3406 gfx_add_point(im, im->ximg, 0);
3408 gfx_new_area(im, im->xorigin,
3411 im->xsize, im->yorigin,
3414 im->yorigin - im->ysize, im->graph_col[GRC_CANVAS]);
3415 gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3417 cairo_rectangle(im->cr, im->xorigin, im->yorigin - im->ysize - 1.0,
3418 im->xsize, im->ysize + 2.0);
3420 if (im->minval > 0.0)
3421 areazero = im->minval;
3422 if (im->maxval < 0.0)
3423 areazero = im->maxval;
3424 for (i = 0; i < im->gdes_c; i++) {
3425 switch (im->gdes[i].gf) {
3439 for (ii = 0; ii < im->xsize; ii++) {
3440 if (!isnan(im->gdes[i].p_data[ii])
3441 && im->gdes[i].p_data[ii] != 0.0) {
3442 if (im->gdes[i].yrule > 0) {
3449 im->ysize, 1.0, im->gdes[i].col);
3450 } else if (im->gdes[i].yrule < 0) {
3453 im->yorigin - im->ysize - 1.0,
3455 im->yorigin - im->ysize -
3458 im->ysize, 1.0, im->gdes[i].col);
3466 rrd_value_t diffval = im->maxval - im->minval;
3467 rrd_value_t maxlimit = im->maxval + 9 * diffval;
3468 rrd_value_t minlimit = im->minval - 9 * diffval;
3469 for (ii = 0; ii < im->xsize; ii++) {
3470 /* fix data points at oo and -oo */
3471 if (isinf(im->gdes[i].p_data[ii])) {
3472 if (im->gdes[i].p_data[ii] > 0) {
3473 im->gdes[i].p_data[ii] = im->maxval;
3475 im->gdes[i].p_data[ii] = im->minval;
3478 /* some versions of cairo go unstable when trying
3479 to draw way out of the canvas ... lets not even try */
3480 if (im->gdes[i].p_data[ii] > maxlimit) {
3481 im->gdes[i].p_data[ii] = maxlimit;
3483 if (im->gdes[i].p_data[ii] < minlimit) {
3484 im->gdes[i].p_data[ii] = minlimit;
3488 /* *******************************************************
3493 -------|--t-1--t--------------------------------
3495 if we know the value at time t was a then
3496 we draw a square from t-1 to t with the value a.
3498 ********************************************************* */
3499 if (im->gdes[i].col.alpha != 0.0) {
3500 /* GF_LINE and friend */
3501 if (im->gdes[i].gf == GF_LINE) {
3502 double last_y = 0.0;
3506 cairo_new_path(im->cr);
3507 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3508 if (im->gdes[i].dash) {
3509 cairo_set_dash(im->cr,
3510 im->gdes[i].p_dashes,
3511 im->gdes[i].ndash, im->gdes[i].offset);
3514 for (ii = 1; ii < im->xsize; ii++) {
3515 if (isnan(im->gdes[i].p_data[ii])
3516 || (im->slopemode == 1
3517 && isnan(im->gdes[i].p_data[ii - 1]))) {
3522 last_y = ytr(im, im->gdes[i].p_data[ii]);
3523 if (im->slopemode == 0) {
3524 double x = ii - 1 + im->xorigin;
3527 gfx_line_fit(im, &x, &y);
3528 cairo_move_to(im->cr, x, y);
3529 x = ii + im->xorigin;
3531 gfx_line_fit(im, &x, &y);
3532 cairo_line_to(im->cr, x, y);
3534 double x = ii - 1 + im->xorigin;
3536 ytr(im, im->gdes[i].p_data[ii - 1]);
3537 gfx_line_fit(im, &x, &y);
3538 cairo_move_to(im->cr, x, y);
3539 x = ii + im->xorigin;
3541 gfx_line_fit(im, &x, &y);
3542 cairo_line_to(im->cr, x, y);
3546 double x1 = ii + im->xorigin;
3547 double y1 = ytr(im, im->gdes[i].p_data[ii]);
3549 if (im->slopemode == 0
3550 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3551 double x = ii - 1 + im->xorigin;
3554 gfx_line_fit(im, &x, &y);
3555 cairo_line_to(im->cr, x, y);
3558 gfx_line_fit(im, &x1, &y1);
3559 cairo_line_to(im->cr, x1, y1);
3562 cairo_set_source_rgba(im->cr,
3568 col.blue, im->gdes[i].col.alpha);
3569 cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3570 cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3571 cairo_stroke(im->cr);
3572 cairo_restore(im->cr);
3578 (double *) malloc(sizeof(double) * im->xsize * 2);
3580 (double *) malloc(sizeof(double) * im->xsize * 2);
3582 (double *) malloc(sizeof(double) * im->xsize * 2);
3584 (double *) malloc(sizeof(double) * im->xsize * 2);
3587 for (ii = 0; ii <= im->xsize; ii++) {
3590 if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3596 AlmostEqual2sComplement(foreY
3600 AlmostEqual2sComplement(foreY
3606 if (im->gdes[i].gf != GF_GRAD) {
3611 foreY[cntI], im->gdes[i].col);
3613 lastx = foreX[cntI];
3614 lasty = foreY[cntI];
3616 while (cntI < idxI) {
3621 AlmostEqual2sComplement(foreY
3625 AlmostEqual2sComplement(foreY
3632 if (im->gdes[i].gf != GF_GRAD) {
3633 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3635 gfx_add_rect_fadey(im,
3637 foreX[cntI], foreY[cntI], lasty,
3640 im->gdes[i].gradheight
3642 lastx = foreX[cntI];
3643 lasty = foreY[cntI];
3646 if (im->gdes[i].gf != GF_GRAD) {
3647 gfx_add_point(im, backX[idxI], backY[idxI]);
3649 gfx_add_rect_fadey(im,
3651 backX[idxI], backY[idxI], lasty,
3654 im->gdes[i].gradheight);
3655 lastx = backX[idxI];
3656 lasty = backY[idxI];
3663 AlmostEqual2sComplement(backY
3667 AlmostEqual2sComplement(backY
3674 if (im->gdes[i].gf != GF_GRAD) {
3675 gfx_add_point(im, backX[idxI], backY[idxI]);
3677 gfx_add_rect_fadey(im,
3679 backX[idxI], backY[idxI], lasty,
3682 im->gdes[i].gradheight);
3683 lastx = backX[idxI];
3684 lasty = backY[idxI];
3689 if (im->gdes[i].gf != GF_GRAD)
3696 if (ii == im->xsize)
3698 if (im->slopemode == 0 && ii == 0) {
3701 if (isnan(im->gdes[i].p_data[ii])) {
3705 ytop = ytr(im, im->gdes[i].p_data[ii]);
3706 if (lastgdes && im->gdes[i].stack) {
3707 ybase = ytr(im, lastgdes->p_data[ii]);
3709 ybase = ytr(im, areazero);
3711 if (ybase == ytop) {
3717 double extra = ytop;
3722 if (im->slopemode == 0) {
3723 backY[++idxI] = ybase - 0.2;
3724 backX[idxI] = ii + im->xorigin - 1;
3725 foreY[idxI] = ytop + 0.2;
3726 foreX[idxI] = ii + im->xorigin - 1;
3728 backY[++idxI] = ybase - 0.2;
3729 backX[idxI] = ii + im->xorigin;
3730 foreY[idxI] = ytop + 0.2;
3731 foreX[idxI] = ii + im->xorigin;
3733 /* close up any remaining area */
3738 } /* else GF_LINE */
3740 /* if color != 0x0 */
3741 /* make sure we do not run into trouble when stacking on NaN */
3742 for (ii = 0; ii < im->xsize; ii++) {
3743 if (isnan(im->gdes[i].p_data[ii])) {
3744 if (lastgdes && (im->gdes[i].stack)) {
3745 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3747 im->gdes[i].p_data[ii] = areazero;
3751 lastgdes = &(im->gdes[i]);
3753 } /* GF_AREA, GF_LINE, GF_GRAD */
3756 ("STACK should already be turned into LINE or AREA here");
3761 cairo_reset_clip(im->cr);
3763 /* grid_paint also does the text */
3764 if (!(im->extra_flags & ONLY_GRAPH))
3766 if (!(im->extra_flags & ONLY_GRAPH))
3768 /* the RULES are the last thing to paint ... */
3769 for (i = 0; i < im->gdes_c; i++) {
3771 switch (im->gdes[i].gf) {
3773 if (im->gdes[i].yrule >= im->minval
3774 && im->gdes[i].yrule <= im->maxval) {
3776 if (im->gdes[i].dash) {
3777 cairo_set_dash(im->cr,
3778 im->gdes[i].p_dashes,
3779 im->gdes[i].ndash, im->gdes[i].offset);
3781 gfx_line(im, im->xorigin,
3782 ytr(im, im->gdes[i].yrule),
3783 im->xorigin + im->xsize,
3784 ytr(im, im->gdes[i].yrule), 1.0, im->gdes[i].col);
3785 cairo_stroke(im->cr);
3786 cairo_restore(im->cr);
3790 if (im->gdes[i].xrule >= im->start
3791 && im->gdes[i].xrule <= im->end) {
3793 if (im->gdes[i].dash) {
3794 cairo_set_dash(im->cr,
3795 im->gdes[i].p_dashes,
3796 im->gdes[i].ndash, im->gdes[i].offset);
3799 xtr(im, im->gdes[i].xrule),
3800 im->yorigin, xtr(im,
3804 im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3805 cairo_stroke(im->cr);
3806 cairo_restore(im->cr);
3815 switch (im->imgformat) {
3818 cairo_status_t status;
3820 status = strlen(im->graphfile) ?
3821 cairo_surface_write_to_png(im->surface, im->graphfile)
3822 : cairo_surface_write_to_png_stream(im->surface, &cairo_output,
3825 if (status != CAIRO_STATUS_SUCCESS) {
3826 rrd_set_error("Could not save png to '%s'", im->graphfile);
3832 if (strlen(im->graphfile)) {
3833 cairo_show_page(im->cr);
3835 cairo_surface_finish(im->surface);
3844 /*****************************************************
3846 *****************************************************/
3853 if ((im->gdes = (graph_desc_t *)
3854 rrd_realloc(im->gdes, (im->gdes_c)
3855 * sizeof(graph_desc_t))) == NULL) {
3856 rrd_set_error("realloc graph_descs");
3861 im->gdes[im->gdes_c - 1].step = im->step;
3862 im->gdes[im->gdes_c - 1].step_orig = im->step;
3863 im->gdes[im->gdes_c - 1].stack = 0;
3864 im->gdes[im->gdes_c - 1].linewidth = 0;
3865 im->gdes[im->gdes_c - 1].debug = 0;
3866 im->gdes[im->gdes_c - 1].start = im->start;
3867 im->gdes[im->gdes_c - 1].start_orig = im->start;
3868 im->gdes[im->gdes_c - 1].end = im->end;
3869 im->gdes[im->gdes_c - 1].end_orig = im->end;
3870 im->gdes[im->gdes_c - 1].vname[0] = '\0';
3871 im->gdes[im->gdes_c - 1].data = NULL;
3872 im->gdes[im->gdes_c - 1].ds_namv = NULL;
3873 im->gdes[im->gdes_c - 1].data_first = 0;
3874 im->gdes[im->gdes_c - 1].p_data = NULL;
3875 im->gdes[im->gdes_c - 1].rpnp = NULL;
3876 im->gdes[im->gdes_c - 1].p_dashes = NULL;
3877 im->gdes[im->gdes_c - 1].shift = 0.0;
3878 im->gdes[im->gdes_c - 1].dash = 0;
3879 im->gdes[im->gdes_c - 1].ndash = 0;
3880 im->gdes[im->gdes_c - 1].offset = 0;
3881 im->gdes[im->gdes_c - 1].col.red = 0.0;
3882 im->gdes[im->gdes_c - 1].col.green = 0.0;
3883 im->gdes[im->gdes_c - 1].col.blue = 0.0;
3884 im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3885 im->gdes[im->gdes_c - 1].col2.red = 0.0;
3886 im->gdes[im->gdes_c - 1].col2.green = 0.0;
3887 im->gdes[im->gdes_c - 1].col2.blue = 0.0;
3888 im->gdes[im->gdes_c - 1].col2.alpha = 0.0;
3889 im->gdes[im->gdes_c - 1].gradheight = 50.0;
3890 im->gdes[im->gdes_c - 1].legend[0] = '\0';
3891 im->gdes[im->gdes_c - 1].format[0] = '\0';
3892 im->gdes[im->gdes_c - 1].strftm = 0;
3893 im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3894 im->gdes[im->gdes_c - 1].ds = -1;
3895 im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3896 im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3897 im->gdes[im->gdes_c - 1].yrule = DNAN;
3898 im->gdes[im->gdes_c - 1].xrule = 0;
3899 im->gdes[im->gdes_c - 1].daemon[0] = 0;
3903 /* copies input untill the first unescaped colon is found
3904 or until input ends. backslashes have to be escaped as well */
3906 const char *const input,
3912 for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3913 if (input[inp] == '\\'
3914 && input[inp + 1] != '\0'
3915 && (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3916 output[outp++] = input[++inp];
3918 output[outp++] = input[inp];
3921 output[outp] = '\0';
3925 /* Now just a wrapper around rrd_graph_v */
3937 rrd_info_t *grinfo = NULL;
3940 grinfo = rrd_graph_v(argc, argv);
3946 if (strcmp(walker->key, "image_info") == 0) {
3949 (char**)rrd_realloc((*prdata),
3950 (prlines + 1) * sizeof(char *))) == NULL) {
3951 rrd_set_error("realloc prdata");
3954 /* imginfo goes to position 0 in the prdata array */
3955 (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
3956 + 2) * sizeof(char));
3957 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3958 (*prdata)[prlines] = NULL;
3960 /* skip anything else */
3961 walker = walker->next;
3969 if (strcmp(walker->key, "image_width") == 0) {
3970 *xsize = walker->value.u_cnt;
3971 } else if (strcmp(walker->key, "image_height") == 0) {
3972 *ysize = walker->value.u_cnt;
3973 } else if (strcmp(walker->key, "value_min") == 0) {
3974 *ymin = walker->value.u_val;
3975 } else if (strcmp(walker->key, "value_max") == 0) {
3976 *ymax = walker->value.u_val;
3977 } else if (strncmp(walker->key, "print", 5) == 0) { /* keys are prdate[0..] */
3980 (char**)rrd_realloc((*prdata),
3981 (prlines + 1) * sizeof(char *))) == NULL) {
3982 rrd_set_error("realloc prdata");
3985 (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
3986 + 2) * sizeof(char));
3987 (*prdata)[prlines] = NULL;
3988 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3989 } else if (strcmp(walker->key, "image") == 0) {
3990 if ( fwrite(walker->value.u_blo.ptr, walker->value.u_blo.size, 1,
3991 (stream ? stream : stdout)) == 0 && ferror(stream ? stream : stdout)){
3992 rrd_set_error("writing image");
3996 /* skip anything else */
3997 walker = walker->next;
3999 rrd_info_free(grinfo);
4004 /* Some surgery done on this function, it became ridiculously big.
4006 ** - initializing now in rrd_graph_init()
4007 ** - options parsing now in rrd_graph_options()
4008 ** - script parsing now in rrd_graph_script()
4010 rrd_info_t *rrd_graph_v(
4017 rrd_graph_init(&im);
4018 /* a dummy surface so that we can measure text sizes for placements */
4019 old_locale = setlocale(LC_NUMERIC, NULL);
4020 setlocale(LC_NUMERIC, "C");
4021 rrd_graph_options(argc, argv, &im);
4022 if (rrd_test_error()) {
4023 setlocale(LC_NUMERIC, old_locale); /* reenable locale */
4024 rrd_info_free(im.grinfo);
4029 if (optind >= argc) {
4030 setlocale(LC_NUMERIC, old_locale); /* reenable locale */
4031 rrd_info_free(im.grinfo);
4033 rrd_set_error("missing filename");
4037 if (strlen(argv[optind]) >= MAXPATH) {
4038 setlocale(LC_NUMERIC, old_locale); /* reenable locale */
4039 rrd_set_error("filename (including path) too long");
4040 rrd_info_free(im.grinfo);
4045 strncpy(im.graphfile, argv[optind], MAXPATH - 1);
4046 im.graphfile[MAXPATH - 1] = '\0';
4048 if (strcmp(im.graphfile, "-") == 0) {
4049 im.graphfile[0] = '\0';
4052 rrd_graph_script(argc, argv, &im, 1);
4053 setlocale(LC_NUMERIC, old_locale); /* reenable locale for rendering the graph */
4055 if (rrd_test_error()) {
4056 rrd_info_free(im.grinfo);
4061 /* Everything is now read and the actual work can start */
4063 if (graph_paint(&im) == -1) {
4064 rrd_info_free(im.grinfo);
4070 /* The image is generated and needs to be output.
4071 ** Also, if needed, print a line with information about the image.
4079 path = strdup(im.graphfile);
4080 filename = basename(path);
4082 sprintf_alloc(im.imginfo,
4085 im.ximg), (long) (im.zoom * im.yimg));
4086 grinfo_push(&im, sprintf_alloc("image_info"), RD_I_STR, info);
4090 if (im.rendered_image) {
4093 img.u_blo.size = im.rendered_image_size;
4094 img.u_blo.ptr = im.rendered_image;
4095 grinfo_push(&im, sprintf_alloc("image"), RD_I_BLO, img);
4104 image_desc_t *im,int prop,char *font, double size ){
4106 strncpy(im->text_prop[prop].font, font, sizeof(text_prop[prop].font) - 1);
4107 im->text_prop[prop].font[sizeof(text_prop[prop].font) - 1] = '\0';
4108 /* if we already got one, drop it first */
4109 pango_font_description_free(im->text_prop[prop].font_desc);
4110 im->text_prop[prop].font_desc = pango_font_description_from_string( font );
4113 im->text_prop[prop].size = size;
4115 if (im->text_prop[prop].font_desc && im->text_prop[prop].size ){
4116 pango_font_description_set_size(im->text_prop[prop].font_desc, im->text_prop[prop].size * PANGO_SCALE);
4120 void rrd_graph_init(
4125 char *deffont = getenv("RRD_DEFAULT_FONT");
4126 static PangoFontMap *fontmap = NULL;
4127 PangoContext *context;
4134 im->daemon_addr = NULL;
4135 im->draw_x_grid = 1;
4136 im->draw_y_grid = 1;
4137 im->draw_3d_border = 2;
4138 im->dynamic_labels = 0;
4139 im->extra_flags = 0;
4140 im->font_options = cairo_font_options_create();
4141 im->forceleftspace = 0;
4144 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4145 im->grid_dash_off = 1;
4146 im->grid_dash_on = 1;
4148 im->grinfo = (rrd_info_t *) NULL;
4149 im->grinfo_current = (rrd_info_t *) NULL;
4150 im->imgformat = IF_PNG;
4153 im->legenddirection = TOP_DOWN;
4154 im->legendheight = 0;
4155 im->legendposition = SOUTH;
4156 im->legendwidth = 0;
4157 im->logarithmic = 0;
4163 im->rendered_image_size = 0;
4164 im->rendered_image = NULL;
4168 im->tabwidth = 40.0;
4169 im->title[0] = '\0';
4170 im->unitsexponent = 9999;
4171 im->unitslength = 6;
4172 im->viewfactor = 1.0;
4173 im->watermark[0] = '\0';
4174 im->with_markup = 0;
4176 im->xlab_user.minsec = -1;
4178 im->xOriginLegend = 0;
4179 im->xOriginLegendY = 0;
4180 im->xOriginLegendY2 = 0;
4181 im->xOriginTitle = 0;
4183 im->ygridstep = DNAN;
4185 im->ylegend[0] = '\0';
4186 im->second_axis_scale = 0; /* 0 disables it */
4187 im->second_axis_shift = 0; /* no shift by default */
4188 im->second_axis_legend[0] = '\0';
4189 im->second_axis_format[0] = '\0';
4191 im->yOriginLegend = 0;
4192 im->yOriginLegendY = 0;
4193 im->yOriginLegendY2 = 0;
4194 im->yOriginTitle = 0;
4198 im->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
4199 im->cr = cairo_create(im->surface);
4201 for (i = 0; i < DIM(text_prop); i++) {
4202 im->text_prop[i].size = -1;
4203 im->text_prop[i].font_desc = NULL;
4204 rrd_set_font_desc(im,i, deffont ? deffont : text_prop[i].font,text_prop[i].size);
4207 if (fontmap == NULL){
4208 fontmap = pango_cairo_font_map_get_default();
4211 context = pango_cairo_font_map_create_context((PangoCairoFontMap*)fontmap);
4213 pango_cairo_context_set_resolution(context, 100);
4215 pango_cairo_update_context(im->cr,context);
4217 im->layout = pango_layout_new(context);
4218 g_object_unref (context);
4220 // im->layout = pango_cairo_create_layout(im->cr);
4223 cairo_font_options_set_hint_style
4224 (im->font_options, CAIRO_HINT_STYLE_FULL);
4225 cairo_font_options_set_hint_metrics
4226 (im->font_options, CAIRO_HINT_METRICS_ON);
4227 cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
4231 for (i = 0; i < DIM(graph_col); i++)
4232 im->graph_col[i] = graph_col[i];
4238 void rrd_graph_options(
4245 char *parsetime_error = NULL;
4246 char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
4247 time_t start_tmp = 0, end_tmp = 0;
4249 rrd_time_value_t start_tv, end_tv;
4250 long unsigned int color;
4252 /* defines for long options without a short equivalent. should be bytes,
4253 and may not collide with (the ASCII value of) short options */
4254 #define LONGOPT_UNITS_SI 255
4257 struct option long_options[] = {
4258 { "alt-autoscale", no_argument, 0, 'A'},
4259 { "imgformat", required_argument, 0, 'a'},
4260 { "font-smoothing-threshold", required_argument, 0, 'B'},
4261 { "base", required_argument, 0, 'b'},
4262 { "color", required_argument, 0, 'c'},
4263 { "full-size-mode", no_argument, 0, 'D'},
4264 { "daemon", required_argument, 0, 'd'},
4265 { "slope-mode", no_argument, 0, 'E'},
4266 { "end", required_argument, 0, 'e'},
4267 { "force-rules-legend", no_argument, 0, 'F'},
4268 { "imginfo", required_argument, 0, 'f'},
4269 { "graph-render-mode", required_argument, 0, 'G'},
4270 { "no-legend", no_argument, 0, 'g'},
4271 { "height", required_argument, 0, 'h'},
4272 { "no-minor", no_argument, 0, 'I'},
4273 { "interlaced", no_argument, 0, 'i'},
4274 { "alt-autoscale-min", no_argument, 0, 'J'},
4275 { "only-graph", no_argument, 0, 'j'},
4276 { "units-length", required_argument, 0, 'L'},
4277 { "lower-limit", required_argument, 0, 'l'},
4278 { "alt-autoscale-max", no_argument, 0, 'M'},
4279 { "zoom", required_argument, 0, 'm'},
4280 { "no-gridfit", no_argument, 0, 'N'},
4281 { "font", required_argument, 0, 'n'},
4282 { "logarithmic", no_argument, 0, 'o'},
4283 { "pango-markup", no_argument, 0, 'P'},
4284 { "font-render-mode", required_argument, 0, 'R'},
4285 { "rigid", no_argument, 0, 'r'},
4286 { "step", required_argument, 0, 'S'},
4287 { "start", required_argument, 0, 's'},
4288 { "tabwidth", required_argument, 0, 'T'},
4289 { "title", required_argument, 0, 't'},
4290 { "upper-limit", required_argument, 0, 'u'},
4291 { "vertical-label", required_argument, 0, 'v'},
4292 { "watermark", required_argument, 0, 'W'},
4293 { "width", required_argument, 0, 'w'},
4294 { "units-exponent", required_argument, 0, 'X'},
4295 { "x-grid", required_argument, 0, 'x'},
4296 { "alt-y-grid", no_argument, 0, 'Y'},
4297 { "y-grid", required_argument, 0, 'y'},
4298 { "lazy", no_argument, 0, 'z'},
4299 { "units", required_argument, 0, LONGOPT_UNITS_SI},
4300 { "alt-y-mrtg", no_argument, 0, 1000}, /* this has no effect it is just here to save old apps from crashing when they use it */
4301 { "disable-rrdtool-tag",no_argument, 0, 1001},
4302 { "right-axis", required_argument, 0, 1002},
4303 { "right-axis-label", required_argument, 0, 1003},
4304 { "right-axis-format", required_argument, 0, 1004},
4305 { "legend-position", required_argument, 0, 1005},
4306 { "legend-direction", required_argument, 0, 1006},
4307 { "border", required_argument, 0, 1007},
4308 { "grid-dash", required_argument, 0, 1008},
4309 { "dynamic-labels", no_argument, 0, 1009},
4310 { "week-fmt", required_argument, 0, 1010},
4316 opterr = 0; /* initialize getopt */
4317 rrd_parsetime("end-24h", &start_tv);
4318 rrd_parsetime("now", &end_tv);
4320 int option_index = 0;
4322 int col_start, col_end;
4324 opt = getopt_long(argc, argv,
4325 "Aa:B:b:c:Dd:Ee:Ff:G:gh:IiJjL:l:Mm:Nn:oPR:rS:s:T:t:u:v:W:w:X:x:Yy:z",
4326 long_options, &option_index);
4331 im->extra_flags |= NOMINOR;
4334 im->extra_flags |= ALTYGRID;
4337 im->extra_flags |= ALTAUTOSCALE;
4340 im->extra_flags |= ALTAUTOSCALE_MIN;
4343 im->extra_flags |= ALTAUTOSCALE_MAX;
4346 im->extra_flags |= ONLY_GRAPH;
4349 im->extra_flags |= NOLEGEND;
4352 if (strcmp(optarg, "north") == 0) {
4353 im->legendposition = NORTH;
4354 } else if (strcmp(optarg, "west") == 0) {
4355 im->legendposition = WEST;
4356 } else if (strcmp(optarg, "south") == 0) {
4357 im->legendposition = SOUTH;
4358 } else if (strcmp(optarg, "east") == 0) {
4359 im->legendposition = EAST;
4361 rrd_set_error("unknown legend-position '%s'", optarg);
4366 if (strcmp(optarg, "topdown") == 0) {
4367 im->legenddirection = TOP_DOWN;
4368 } else if (strcmp(optarg, "bottomup") == 0) {
4369 im->legenddirection = BOTTOM_UP;
4371 rrd_set_error("unknown legend-position '%s'", optarg);
4376 im->extra_flags |= FORCE_RULES_LEGEND;
4379 im->extra_flags |= NO_RRDTOOL_TAG;
4381 case LONGOPT_UNITS_SI:
4382 if (im->extra_flags & FORCE_UNITS) {
4383 rrd_set_error("--units can only be used once!");
4386 if (strcmp(optarg, "si") == 0)
4387 im->extra_flags |= FORCE_UNITS_SI;
4389 rrd_set_error("invalid argument for --units: %s", optarg);
4394 im->unitsexponent = atoi(optarg);
4397 im->unitslength = atoi(optarg);
4398 im->forceleftspace = 1;
4401 im->tabwidth = atof(optarg);
4404 im->step = atoi(optarg);
4410 im->with_markup = 1;
4413 if ((parsetime_error = rrd_parsetime(optarg, &start_tv))) {
4414 rrd_set_error("start time: %s", parsetime_error);
4419 if ((parsetime_error = rrd_parsetime(optarg, &end_tv))) {
4420 rrd_set_error("end time: %s", parsetime_error);
4425 if (strcmp(optarg, "none") == 0) {
4426 im->draw_x_grid = 0;
4430 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
4432 &im->xlab_user.gridst,
4434 &im->xlab_user.mgridst,
4436 &im->xlab_user.labst,
4437 &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
4438 strncpy(im->xlab_form, optarg + stroff,
4439 sizeof(im->xlab_form) - 1);
4440 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
4442 (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
4443 rrd_set_error("unknown keyword %s", scan_gtm);
4446 (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
4448 rrd_set_error("unknown keyword %s", scan_mtm);
4451 (im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1) {
4452 rrd_set_error("unknown keyword %s", scan_ltm);
4455 im->xlab_user.minsec = 1;
4456 im->xlab_user.stst = im->xlab_form;
4458 rrd_set_error("invalid x-grid format");
4464 if (strcmp(optarg, "none") == 0) {
4465 im->draw_y_grid = 0;
4468 if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
4469 if (im->ygridstep <= 0) {
4470 rrd_set_error("grid step must be > 0");
4472 } else if (im->ylabfact < 1) {
4473 rrd_set_error("label factor must be > 0");
4477 rrd_set_error("invalid y-grid format");
4482 im->draw_3d_border = atoi(optarg);
4484 case 1008: /* grid-dash */
4488 &im->grid_dash_off) != 2) {
4489 rrd_set_error("expected grid-dash format float:float");
4493 case 1009: /* enable dynamic labels */
4494 im->dynamic_labels = 1;
4497 strncpy(week_fmt,optarg,sizeof week_fmt);
4498 week_fmt[(sizeof week_fmt)-1]='\0';
4500 case 1002: /* right y axis */
4504 &im->second_axis_scale,
4505 &im->second_axis_shift) == 2) {
4506 if(im->second_axis_scale==0){
4507 rrd_set_error("the second_axis_scale must not be 0");
4511 rrd_set_error("invalid right-axis format expected scale:shift");
4516 strncpy(im->second_axis_legend,optarg,150);
4517 im->second_axis_legend[150]='\0';
4520 if (bad_format(optarg)){
4521 rrd_set_error("use either %le or %lf formats");
4524 strncpy(im->second_axis_format,optarg,150);
4525 im->second_axis_format[150]='\0';
4528 strncpy(im->ylegend, optarg, 150);
4529 im->ylegend[150] = '\0';
4532 im->maxval = atof(optarg);
4535 im->minval = atof(optarg);
4538 im->base = atol(optarg);
4539 if (im->base != 1024 && im->base != 1000) {
4541 ("the only sensible value for base apart from 1000 is 1024");
4546 long_tmp = atol(optarg);
4547 if (long_tmp < 10) {
4548 rrd_set_error("width below 10 pixels");
4551 im->xsize = long_tmp;
4554 long_tmp = atol(optarg);
4555 if (long_tmp < 10) {
4556 rrd_set_error("height below 10 pixels");
4559 im->ysize = long_tmp;
4562 im->extra_flags |= FULL_SIZE_MODE;
4565 /* interlaced png not supported at the moment */
4571 im->imginfo = optarg;
4575 (im->imgformat = if_conv(optarg)) == -1) {
4576 rrd_set_error("unsupported graphics format '%s'", optarg);
4587 im->logarithmic = 1;
4591 "%10[A-Z]#%n%8lx%n",
4592 col_nam, &col_start, &color, &col_end) == 2) {
4594 int col_len = col_end - col_start;
4599 (((color & 0xF00) * 0x110000) | ((color & 0x0F0) *
4607 (((color & 0xF000) *
4608 0x11000) | ((color & 0x0F00) *
4609 0x01100) | ((color &
4612 ((color & 0x000F) * 0x00011)
4616 color = (color << 8) + 0xff /* shift left by 8 */ ;
4621 rrd_set_error("the color format is #RRGGBB[AA]");
4624 if ((ci = grc_conv(col_nam)) != -1) {
4625 im->graph_col[ci] = gfx_hex_to_col(color);
4627 rrd_set_error("invalid color name '%s'", col_nam);
4631 rrd_set_error("invalid color def format");
4640 if (sscanf(optarg, "%10[A-Z]:%lf%n", prop, &size, &end) >= 2) {
4641 int sindex, propidx;
4643 if ((sindex = text_prop_conv(prop)) != -1) {
4644 for (propidx = sindex;
4645 propidx < TEXT_PROP_LAST; propidx++) {
4647 rrd_set_font_desc(im,propidx,NULL,size);
4649 if ((int) strlen(optarg) > end+2) {
4650 if (optarg[end] == ':') {
4651 rrd_set_font_desc(im,propidx,optarg + end + 1,0);
4654 ("expected : after font size in '%s'",
4659 /* only run the for loop for DEFAULT (0) for
4660 all others, we break here. woodo programming */
4661 if (propidx == sindex && sindex != 0)
4665 rrd_set_error("invalid fonttag '%s'", prop);
4669 rrd_set_error("invalid text property format");
4675 im->zoom = atof(optarg);
4676 if (im->zoom <= 0.0) {
4677 rrd_set_error("zoom factor must be > 0");
4682 strncpy(im->title, optarg, 150);
4683 im->title[150] = '\0';
4686 if (strcmp(optarg, "normal") == 0) {
4687 cairo_font_options_set_antialias
4688 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4689 cairo_font_options_set_hint_style
4690 (im->font_options, CAIRO_HINT_STYLE_FULL);
4691 } else if (strcmp(optarg, "light") == 0) {
4692 cairo_font_options_set_antialias
4693 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4694 cairo_font_options_set_hint_style
4695 (im->font_options, CAIRO_HINT_STYLE_SLIGHT);
4696 } else if (strcmp(optarg, "mono") == 0) {
4697 cairo_font_options_set_antialias
4698 (im->font_options, CAIRO_ANTIALIAS_NONE);
4699 cairo_font_options_set_hint_style
4700 (im->font_options, CAIRO_HINT_STYLE_FULL);
4702 rrd_set_error("unknown font-render-mode '%s'", optarg);
4707 if (strcmp(optarg, "normal") == 0)
4708 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4709 else if (strcmp(optarg, "mono") == 0)
4710 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4712 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4717 /* not supported curently */
4720 strncpy(im->watermark, optarg, 100);
4721 im->watermark[99] = '\0';
4725 if (im->daemon_addr != NULL)
4727 rrd_set_error ("You cannot specify --daemon "
4732 im->daemon_addr = strdup(optarg);
4733 if (im->daemon_addr == NULL)
4735 rrd_set_error("strdup failed");
4743 rrd_set_error("unknown option '%c'", optopt);
4745 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4750 pango_cairo_context_set_font_options(pango_layout_get_context(im->layout), im->font_options);
4751 pango_layout_context_changed(im->layout);
4755 if (im->logarithmic && im->minval <= 0) {
4757 ("for a logarithmic yaxis you must specify a lower-limit > 0");
4761 if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4762 /* error string is set in rrd_parsetime.c */
4766 if (start_tmp < 3600 * 24 * 365 * 10) {
4768 ("the first entry to fetch should be after 1980 (%ld)",
4773 if (end_tmp < start_tmp) {
4775 ("start (%ld) should be less than end (%ld)", start_tmp, end_tmp);
4779 im->start = start_tmp;
4781 im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4784 int rrd_graph_color(
4792 graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4794 color = strstr(var, "#");
4795 if (color == NULL) {
4796 if (optional == 0) {
4797 rrd_set_error("Found no color in %s", err);
4804 long unsigned int col;
4806 rest = strstr(color, ":");
4813 sscanf(color, "#%6lx%n", &col, &n);
4814 col = (col << 8) + 0xff /* shift left by 8 */ ;
4816 rrd_set_error("Color problem in %s", err);
4819 sscanf(color, "#%8lx%n", &col, &n);
4823 rrd_set_error("Color problem in %s", err);
4825 if (rrd_test_error())
4827 gdp->col = gfx_hex_to_col(col);
4840 while (*ptr != '\0')
4841 if (*ptr++ == '%') {
4843 /* line cannot end with percent char */
4846 /* '%s', '%S' and '%%' are allowed */
4847 if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4849 /* %c is allowed (but use only with vdef!) */
4850 else if (*ptr == 'c') {
4855 /* or else '% 6.2lf' and such are allowed */
4857 /* optional padding character */
4858 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4860 /* This should take care of 'm.n' with all three optional */
4861 while (*ptr >= '0' && *ptr <= '9')
4865 while (*ptr >= '0' && *ptr <= '9')
4867 /* Either 'le', 'lf' or 'lg' must follow here */
4870 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4885 const char *const str)
4887 /* A VDEF currently is either "func" or "param,func"
4888 * so the parsing is rather simple. Change if needed.
4895 sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
4896 if (n == (int) strlen(str)) { /* matched */
4900 sscanf(str, "%29[A-Z]%n", func, &n);
4901 if (n == (int) strlen(str)) { /* matched */
4905 ("Unknown function string '%s' in VDEF '%s'",
4910 if (!strcmp("PERCENT", func))
4911 gdes->vf.op = VDEF_PERCENT;
4912 else if (!strcmp("PERCENTNAN", func))
4913 gdes->vf.op = VDEF_PERCENTNAN;
4914 else if (!strcmp("MAXIMUM", func))
4915 gdes->vf.op = VDEF_MAXIMUM;
4916 else if (!strcmp("AVERAGE", func))
4917 gdes->vf.op = VDEF_AVERAGE;
4918 else if (!strcmp("STDEV", func))
4919 gdes->vf.op = VDEF_STDEV;
4920 else if (!strcmp("MINIMUM", func))
4921 gdes->vf.op = VDEF_MINIMUM;
4922 else if (!strcmp("TOTAL", func))
4923 gdes->vf.op = VDEF_TOTAL;
4924 else if (!strcmp("FIRST", func))
4925 gdes->vf.op = VDEF_FIRST;
4926 else if (!strcmp("LAST", func))
4927 gdes->vf.op = VDEF_LAST;
4928 else if (!strcmp("LSLSLOPE", func))
4929 gdes->vf.op = VDEF_LSLSLOPE;
4930 else if (!strcmp("LSLINT", func))
4931 gdes->vf.op = VDEF_LSLINT;
4932 else if (!strcmp("LSLCORREL", func))
4933 gdes->vf.op = VDEF_LSLCORREL;
4936 ("Unknown function '%s' in VDEF '%s'\n", func, gdes->vname);
4939 switch (gdes->vf.op) {
4941 case VDEF_PERCENTNAN:
4942 if (isnan(param)) { /* no parameter given */
4944 ("Function '%s' needs parameter in VDEF '%s'\n",
4948 if (param >= 0.0 && param <= 100.0) {
4949 gdes->vf.param = param;
4950 gdes->vf.val = DNAN; /* undefined */
4951 gdes->vf.when = 0; /* undefined */
4955 ("Parameter '%f' out of range in VDEF '%s'\n",
4956 param, gdes->vname);
4969 case VDEF_LSLCORREL:
4971 gdes->vf.param = DNAN;
4972 gdes->vf.val = DNAN;
4977 ("Function '%s' needs no parameter in VDEF '%s'\n",
4991 graph_desc_t *src, *dst;
4995 dst = &im->gdes[gdi];
4996 src = &im->gdes[dst->vidx];
4997 data = src->data + src->ds;
4999 steps = (src->end - src->start) / src->step;
5002 ("DEBUG: start == %lu, end == %lu, %lu steps\n",
5003 src->start, src->end, steps);
5005 switch (dst->vf.op) {
5009 if ((array = (rrd_value_t*)malloc(steps * sizeof(double))) == NULL) {
5010 rrd_set_error("malloc VDEV_PERCENT");
5013 for (step = 0; step < steps; step++) {
5014 array[step] = data[step * src->ds_cnt];
5016 qsort(array, step, sizeof(double), vdef_percent_compar);
5017 field = round((dst->vf.param * (double)(steps - 1)) / 100.0);
5018 dst->vf.val = array[field];
5019 dst->vf.when = 0; /* no time component */
5023 for (step = 0; step < steps; step++)
5024 printf("DEBUG: %3li:%10.2f %c\n",
5025 step, array[step], step == field ? '*' : ' ');
5029 case VDEF_PERCENTNAN:{
5032 /* count number of "valid" values */
5034 for (step = 0; step < steps; step++) {
5035 if (!isnan(data[step * src->ds_cnt])) { nancount++; }
5037 /* and allocate it */
5038 if ((array = (rrd_value_t*)malloc(nancount * sizeof(double))) == NULL) {
5039 rrd_set_error("malloc VDEV_PERCENT");
5042 /* and fill it in */
5044 for (step = 0; step < steps; step++) {
5045 if (!isnan(data[step * src->ds_cnt])) {
5046 array[field] = data[step * src->ds_cnt];
5050 qsort(array, nancount, sizeof(double), vdef_percent_compar);
5051 field = round( dst->vf.param * (double)(nancount - 1) / 100.0);
5052 dst->vf.val = array[field];
5053 dst->vf.when = 0; /* no time component */
5060 while (step != steps && isnan(data[step * src->ds_cnt]))
5062 if (step == steps) {
5067 dst->vf.val = data[step * src->ds_cnt];
5068 dst->vf.when = src->start + (step + 1) * src->step;
5071 while (step != steps) {
5072 if (finite(data[step * src->ds_cnt])) {
5073 if (data[step * src->ds_cnt] > dst->vf.val) {
5074 dst->vf.val = data[step * src->ds_cnt];
5075 dst->vf.when = src->start + (step + 1) * src->step;
5087 double average = 0.0;
5089 for (step = 0; step < steps; step++) {
5090 if (finite(data[step * src->ds_cnt])) {
5091 sum += data[step * src->ds_cnt];
5096 if (dst->vf.op == VDEF_TOTAL) {
5097 dst->vf.val = sum * src->step;
5098 dst->vf.when = 0; /* no time component */
5100 } else if (dst->vf.op == VDEF_AVERAGE) {
5101 dst->vf.val = sum / cnt;
5102 dst->vf.when = 0; /* no time component */
5105 average = sum / cnt;
5107 for (step = 0; step < steps; step++) {
5108 if (finite(data[step * src->ds_cnt])) {
5109 sum += pow((data[step * src->ds_cnt] - average), 2.0);
5112 dst->vf.val = pow(sum / cnt, 0.5);
5113 dst->vf.when = 0; /* no time component */
5125 while (step != steps && isnan(data[step * src->ds_cnt]))
5127 if (step == steps) {
5132 dst->vf.val = data[step * src->ds_cnt];
5133 dst->vf.when = src->start + (step + 1) * src->step;
5136 while (step != steps) {
5137 if (finite(data[step * src->ds_cnt])) {
5138 if (data[step * src->ds_cnt] < dst->vf.val) {
5139 dst->vf.val = data[step * src->ds_cnt];
5140 dst->vf.when = src->start + (step + 1) * src->step;
5148 /* The time value returned here is one step before the
5149 * actual time value. This is the start of the first
5153 while (step != steps && isnan(data[step * src->ds_cnt]))
5155 if (step == steps) { /* all entries were NaN */
5160 dst->vf.val = data[step * src->ds_cnt];
5161 dst->vf.when = src->start + step * src->step;
5166 /* The time value returned here is the
5167 * actual time value. This is the end of the last
5171 while (step >= 0 && isnan(data[step * src->ds_cnt]))
5173 if (step < 0) { /* all entries were NaN */
5178 dst->vf.val = data[step * src->ds_cnt];
5179 dst->vf.when = src->start + (step + 1) * src->step;
5185 case VDEF_LSLCORREL:{
5186 /* Bestfit line by linear least squares method */
5189 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
5196 for (step = 0; step < steps; step++) {
5197 if (finite(data[step * src->ds_cnt])) {
5200 SUMxx += step * step;
5201 SUMxy += step * data[step * src->ds_cnt];
5202 SUMy += data[step * src->ds_cnt];
5203 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
5207 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
5208 y_intercept = (SUMy - slope * SUMx) / cnt;
5211 (SUMx * SUMy) / cnt) /
5213 (SUMx * SUMx) / cnt) * (SUMyy - (SUMy * SUMy) / cnt));
5215 if (dst->vf.op == VDEF_LSLSLOPE) {
5216 dst->vf.val = slope;
5219 } else if (dst->vf.op == VDEF_LSLINT) {
5220 dst->vf.val = y_intercept;
5223 } else if (dst->vf.op == VDEF_LSLCORREL) {
5224 dst->vf.val = correl;
5239 /* NaN < -INF < finite_values < INF */
5240 int vdef_percent_compar(
5246 /* Equality is not returned; this doesn't hurt except
5247 * (maybe) for a little performance.
5250 /* First catch NaN values. They are smallest */
5251 if (isnan(*(double *) a))
5253 if (isnan(*(double *) b))
5255 /* NaN doesn't reach this part so INF and -INF are extremes.
5256 * The sign from isinf() is compatible with the sign we return
5258 if (isinf(*(double *) a))
5259 return isinf(*(double *) a);
5260 if (isinf(*(double *) b))
5261 return isinf(*(double *) b);
5262 /* If we reach this, both values must be finite */
5263 if (*(double *) a < *(double *) b)
5272 rrd_info_type_t type,
5273 rrd_infoval_t value)
5275 im->grinfo_current = rrd_info_push(im->grinfo_current, key, type, value);
5276 if (im->grinfo == NULL) {
5277 im->grinfo = im->grinfo_current;
5288 /* Handling based on
5289 - ANSI C99 Specifications http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf
5290 - Single UNIX Specification version 2 http://www.opengroup.org/onlinepubs/007908799/xsh/strftime.html
5291 - POSIX:2001/Single UNIX Specification version 3 http://www.opengroup.org/onlinepubs/009695399/functions/strftime.html
5292 - POSIX:2008 Specifications http://www.opengroup.org/onlinepubs/9699919799/functions/strftime.html
5293 Specifications tells
5294 "If a conversion specifier is not one of the above, the behavior is undefined."
5297 "A conversion specifier consists of a % character, possibly followed by an E or O modifier character (described below), followed by a character that determines the behavior of the conversion specifier.
5300 "A conversion specification consists of a '%' character, possibly followed by an E or O modifier, and a terminating conversion specifier character that determines the conversion specification's behavior."
5302 POSIX:2008 introduce more complexe behavior that are not handled here.
5304 According to this, this code will replace:
5305 - % followed by @ by a %@
5306 - % followed by by a %SPACE
5307 - % followed by . by a %.
5308 - % followed by % by a %
5309 - % followed by t by a TAB
5310 - % followed by E then anything by '-'
5311 - % followed by O then anything by '-'
5312 - % followed by anything else by at least one '-'. More characters may be added to better fit expected output length
5316 for(j = 0; (j < FMT_LEG_LEN - 1) && (jj < FMT_LEG_LEN); j++) { /* we don't need to parse the last char */
5317 if (format[j] == '%') {
5318 if ((format[j+1] == 'E') || (format[j+1] == 'O')) {
5320 j+=2; /* We skip next 2 following char */
5321 } else if ((format[j+1] == 'C') || (format[j+1] == 'd') ||
5322 (format[j+1] == 'g') || (format[j+1] == 'H') ||
5323 (format[j+1] == 'I') || (format[j+1] == 'm') ||
5324 (format[j+1] == 'M') || (format[j+1] == 'S') ||
5325 (format[j+1] == 'U') || (format[j+1] == 'V') ||
5326 (format[j+1] == 'W') || (format[j+1] == 'y')) {
5328 if (jj < FMT_LEG_LEN) {
5331 j++; /* We skip the following char */
5332 } else if (format[j+1] == 'j') {
5334 if (jj < FMT_LEG_LEN - 1) {
5338 j++; /* We skip the following char */
5339 } else if ((format[j+1] == 'G') || (format[j+1] == 'Y')) {
5340 /* Assuming Year on 4 digit */
5342 if (jj < FMT_LEG_LEN - 2) {
5347 j++; /* We skip the following char */
5348 } else if (format[j+1] == 'R') {
5350 if (jj < FMT_LEG_LEN - 3) {
5356 j++; /* We skip the following char */
5357 } else if (format[j+1] == 'T') {
5359 if (jj < FMT_LEG_LEN - 6) {
5368 j++; /* We skip the following char */
5369 } else if (format[j+1] == 'F') {
5371 if (jj < FMT_LEG_LEN - 8) {
5382 j++; /* We skip the following char */
5383 } else if (format[j+1] == 'D') {
5385 if (jj < FMT_LEG_LEN - 6) {
5394 j++; /* We skip the following char */
5395 } else if (format[j+1] == 'n') {
5396 result[jj++] = '\r';
5397 result[jj++] = '\n';
5398 j++; /* We skip the following char */
5399 } else if (format[j+1] == 't') {
5400 result[jj++] = '\t';
5401 j++; /* We skip the following char */
5402 } else if (format[j+1] == '%') {
5404 j++; /* We skip the following char */
5405 } else if (format[j+1] == ' ') {
5406 if (jj < FMT_LEG_LEN - 1) {
5410 j++; /* We skip the following char */
5411 } else if (format[j+1] == '.') {
5412 if (jj < FMT_LEG_LEN - 1) {
5416 j++; /* We skip the following char */
5417 } else if (format[j+1] == '@') {
5418 if (jj < FMT_LEG_LEN - 1) {
5422 j++; /* We skip the following char */
5425 j++; /* We skip the following char */
5428 result[jj++] = format[j];
5431 result[jj] = '\0'; /* We must force the end of the string */